From 6e265ea24b6e26f5e3cc439b433987d4cb3747a0 Mon Sep 17 00:00:00 2001 From: hondacrx Date: Mon, 19 Jun 2017 17:30:18 -0400 Subject: [PATCH] initial commit --- .gitattributes | 63 + .gitignore | 159 + BNetServer/BNetServer.conf | 242 + BNetServer/BNetServer.csproj | 168 + BNetServer/Managers/Global.cs | 28 + BNetServer/Managers/SessionManager.cs | 164 + BNetServer/Networking/RestSession.cs | 215 + BNetServer/Networking/Session.cs | 661 ++ BNetServer/Properties/AssemblyInfo.cs | 36 + BNetServer/Red.ico | Bin 0 -> 144639 bytes BNetServer/Server.cs | 154 + BNetServer/Services/AccountService.cs | 582 + BNetServer/Services/AuthenticationService.cs | 442 + BNetServer/Services/ChallengeService.cs | 228 + BNetServer/Services/ChannelService.cs | 312 + BNetServer/Services/ConnectionService.cs | 187 + BNetServer/Services/FriendService.cs | 520 + BNetServer/Services/GameUtilitiesService.cs | 215 + BNetServer/Services/PresenceService.cs | 203 + BNetServer/Services/ReportService.cs | 87 + BNetServer/Services/ResourcesService.cs | 60 + BNetServer/Services/ServiceDispatcher.cs | 93 + BNetServer/Services/UserManagerService.cs | 319 + CypherCore.sln | 115 + Framework/Collections/Array.cs | 81 + Framework/Collections/BitSet.cs | 414 + Framework/Collections/IMultiMap.cs | 41 + Framework/Collections/LinkedListElement.cs | 117 + Framework/Collections/MultiMap.cs | 508 + Framework/Collections/MultiMapEnumerator.cs | 129 + Framework/Collections/StringArray.cs | 59 + Framework/Configuration/ConfigManager.cs | 85 + Framework/Constants/AccountConst.cs | 762 ++ Framework/Constants/AchievementConst.cs | 440 + Framework/Constants/AreaTriggerConst.cs | 62 + Framework/Constants/AuctionConst.cs | 50 + .../Constants/Authentication/AuthConst.cs | 133 + .../Authentication/BattlenetConst.cs | 668 ++ .../Constants/Authentication/RealmConst.cs | 88 + Framework/Constants/BattleFieldConst.cs | 62 + Framework/Constants/BattleGroundsConst.cs | 386 + Framework/Constants/BattlePetConst.cs | 75 + Framework/Constants/BlackMarketConst.cs | 40 + Framework/Constants/CalendarConst.cs | 117 + Framework/Constants/ChatConst.cs | 109 + Framework/Constants/CliDBConst.cs | 1669 +++ Framework/Constants/ConditionConst.cs | 123 + Framework/Constants/ConnectConst.cs | 53 + Framework/Constants/CreatureConst.cs | 338 + Framework/Constants/GameObjectConst.cs | 128 + Framework/Constants/GarrisonConst.cs | 164 + Framework/Constants/GossipConst.cs | 125 + Framework/Constants/GroupConst.cs | 166 + Framework/Constants/GuildConst.cs | 283 + Framework/Constants/ItemConst.cs | 1076 ++ Framework/Constants/LFGConst.cs | 179 + Framework/Constants/Language.cs | 1287 +++ Framework/Constants/LootConst.cs | 115 + Framework/Constants/MailConst.cs | 94 + Framework/Constants/MapConst.cs | 184 + Framework/Constants/Movement/MovementConst.cs | 87 + Framework/Constants/Movement/MovementFlags.cs | 101 + Framework/Constants/Movement/SplineConst.cs | 72 + Framework/Constants/Network/NetworkConst.cs | 35 + Framework/Constants/Network/Opcodes.cs | 1712 +++ Framework/Constants/ObjectConst.cs | 229 + Framework/Constants/OutdoorPvPConst.cs | 40 + Framework/Constants/PetConst.cs | 114 + Framework/Constants/PlayerConst.cs | 771 ++ Framework/Constants/QuestConst.cs | 339 + Framework/Constants/ScriptsConst.cs | 78 + Framework/Constants/ServiceHash.cs | 61 + Framework/Constants/SharedConst.cs | 2316 ++++ Framework/Constants/SmartAIConst.cs | 364 + Framework/Constants/Spells/SkillConst.cs | 246 + Framework/Constants/Spells/SpellAuraConst.cs | 565 + Framework/Constants/Spells/SpellConst.cs | 2433 ++++ Framework/Constants/SupportSystemConst.cs | 42 + Framework/Constants/UnitConst.cs | 577 + .../Constants/Update/UpdateFieldFlags.cs | 5039 +++++++++ Framework/Constants/Update/UpdateFields.cs | 411 + Framework/Constants/Update/UpdateFlags.cs | 45 + Framework/Constants/Update/UpdateType.cs | 27 + Framework/Constants/VehicleConst.cs | 64 + Framework/Cryptography/PacketCrypt.cs | 101 + Framework/Cryptography/RsaCrypt.cs | 117 + Framework/Cryptography/SARC4.cs | 68 + .../Cryptography/SessionKeyGeneration.cs | 68 + Framework/Cryptography/ShaHmac.cs | 153 + Framework/Database/DB.cs | 27 + Framework/Database/DatabaseLoader.cs | 181 + Framework/Database/DatabaseUpdater.cs | 466 + .../Database/Databases/CharacterDatabase.cs | 1336 +++ .../Database/Databases/HotfixDatabase.cs | 1490 +++ Framework/Database/Databases/LoginDatabase.cs | 320 + Framework/Database/Databases/WorldDatabase.cs | 171 + Framework/Database/MySqlBase.cs | 422 + Framework/Database/PreparedStatement.cs | 42 + Framework/Database/QueryCallback.cs | 109 + Framework/Database/QueryCallbackProcessor.cs | 42 + Framework/Database/SQLQueryHolder.cs | 60 + Framework/Database/SQLResult.cs | 122 + Framework/Database/SQLTransaction.cs | 46 + Framework/Dynamic/EventMap.cs | 399 + Framework/Dynamic/EventSystem.cs | 149 + Framework/Dynamic/FlagArray.cs | 144 + Framework/Dynamic/OptionalType.cs | 47 + Framework/Dynamic/Reference.cs | 105 + Framework/Dynamic/TaskScheduler.cs | 847 ++ Framework/Framework.csproj | 282 + Framework/GameMath/AxisAlignedBox.cs | 321 + Framework/GameMath/CollisionDetection.cs | 118 + Framework/GameMath/Matrix2.cs | 615 + Framework/GameMath/Matrix3.cs | 808 ++ Framework/GameMath/Matrix4.cs | 900 ++ Framework/GameMath/Plane.cs | 247 + Framework/GameMath/QuaternionD.cs | 815 ++ Framework/GameMath/Ray.cs | 282 + Framework/GameMath/Vector2.cs | 948 ++ Framework/GameMath/Vector3.cs | 1097 ++ Framework/GameMath/Vector4.cs | 1035 ++ Framework/IO/ByteBuffer.cs | 480 + Framework/IO/StringArguments.cs | 277 + Framework/IO/Zlib/Adler32.cs | 168 + Framework/IO/Zlib/Crc32.cs | 368 + Framework/IO/Zlib/Deflate.cs | 2298 ++++ Framework/IO/Zlib/Trees.cs | 1069 ++ Framework/IO/Zlib/ZLib.cs | 171 + Framework/IO/Zlib/ZUtil.cs | 105 + Framework/IO/Zlib/compress.cs | 63 + Framework/Logging/Appender.cs | 238 + Framework/Logging/Log.cs | 436 + Framework/Logging/Logger.cs | 65 + Framework/Logging/Metric.cs | 273 + Framework/Networking/AsyncAcceptor.cs | 77 + Framework/Networking/NetworkThread.cs | 121 + Framework/Networking/SSLSocket.cs | 168 + Framework/Networking/SocketBase.cs | 185 + Framework/Networking/SocketManager.cs | 100 + Framework/Properties/AssemblyInfo.cs | 36 + Framework/Proto/AccountService.cs | 6230 +++++++++++ Framework/Proto/AccountTypes.cs | 9082 +++++++++++++++ Framework/Proto/AttributeTypes.cs | 794 ++ Framework/Proto/AuthenticationService.cs | 4023 +++++++ Framework/Proto/ChallengeService.cs | 2524 +++++ Framework/Proto/ChannelService.cs | 2992 +++++ Framework/Proto/ChannelTypes.cs | 1764 +++ Framework/Proto/ConnectionService.cs | 2025 ++++ Framework/Proto/ContentHandleTypes.cs | 228 + Framework/Proto/EntityTypes.cs | 568 + Framework/Proto/FriendsService.cs | 2154 ++++ Framework/Proto/FriendsTypes.cs | 759 ++ Framework/Proto/GameUtilitiesService.cs | 2398 ++++ Framework/Proto/GameUtilitiesTypes.cs | 336 + .../Proto/GlobalExtensions/FieldOptions.cs | 48 + .../Proto/GlobalExtensions/MethodOptions.cs | 38 + .../Proto/GlobalExtensions/ServiceOptions.cs | 40 + Framework/Proto/InvitationTypes.cs | 1751 +++ Framework/Proto/NotificationTypes.cs | 727 ++ Framework/Proto/PresenceService.cs | 1157 ++ Framework/Proto/PresenceTypes.cs | 882 ++ Framework/Proto/ProfanityFilterConfig.cs | 271 + Framework/Proto/ReportService.cs | 406 + Framework/Proto/ResourceService.cs | 244 + Framework/Proto/RoleTypes.cs | 357 + Framework/Proto/RpcConfig.cs | 972 ++ Framework/Proto/RpcTypes.cs | 1428 +++ Framework/Proto/UserManagerService.cs | 1880 ++++ Framework/Proto/UserManagerTypes.cs | 471 + Framework/Realm/Realm.cs | 160 + Framework/Realm/RealmManager.cs | 347 + Framework/RecastDetour/Detour/DetourCommon.cs | 989 ++ .../RecastDetour/Detour/DetourNavMesh.cs | 1784 +++ .../Detour/DetourNavMeshBuilder.cs | 1632 +++ .../RecastDetour/Detour/DetourNavMeshQuery.cs | 3708 ++++++ Framework/RecastDetour/Detour/DetourNode.cs | 301 + Framework/RecastDetour/Detour/DetourStatus.cs | 44 + Framework/RecastDetour/Recast/Recast.cs | 1692 +++ Framework/RecastDetour/Recast/RecastArea.cs | 526 + .../RecastDetour/Recast/RecastContour.cs | 860 ++ Framework/RecastDetour/Recast/RecastFilter.cs | 192 + Framework/RecastDetour/Recast/RecastLayers.cs | 641 ++ Framework/RecastDetour/Recast/RecastMesh.cs | 1513 +++ .../RecastDetour/Recast/RecastMeshDetail.cs | 1650 +++ .../Recast/RecastRasterization.cs | 430 + Framework/RecastDetour/Recast/RecastRegion.cs | 1426 +++ Framework/Rest/Authentication/LogonData.cs | 41 + Framework/Rest/Authentication/LogonResult.cs | 52 + Framework/Rest/Forms/FormInput.cs | 37 + Framework/Rest/Forms/FormInputValue.cs | 31 + Framework/Rest/Forms/FormInputs.cs | 35 + Framework/Rest/Misc/Address.cs | 31 + Framework/Rest/Misc/AddressFamily.cs | 32 + Framework/Rest/Misc/ClientVersion.cs | 37 + .../Rest/Misc/RealmCharacterCountEntry.cs | 31 + .../Rest/Realmlist/RealmCharacterCountList.cs | 29 + Framework/Rest/Realmlist/RealmEntry.cs | 56 + .../Realmlist/RealmListServerIPAddresses.cs | 29 + .../RealmListTicketClientInformation.cs | 28 + .../Rest/Realmlist/RealmListTicketIdentity.cs | 31 + .../Realmlist/RealmListTicketInformation.cs | 68 + Framework/Rest/Realmlist/RealmListUpdate.cs | 31 + Framework/Rest/Realmlist/RealmListUpdates.cs | 29 + Framework/Runtime/ApplicationSignal.cs | 68 + Framework/Serialization/Json.cs | 79 + Framework/Singleton/Singleton.cs | 47 + Framework/Util/CollectionExtensions.cs | 176 + Framework/Util/Extensions.cs | 372 + Framework/Util/MathFunctions.cs | 286 + Framework/Util/NetworkExtensions.cs | 38 + Framework/Util/RandomHelper.cs | 108 + Framework/Util/Time.cs | 351 + Framework/Web/Http.cs | 139 + Game/AI/AISelector.cs | 129 + Game/AI/CoreAI/AreaTriggerAI.cs | 60 + Game/AI/CoreAI/CombatAI.cs | 361 + Game/AI/CoreAI/CreatureAI.cs | 496 + Game/AI/CoreAI/GameObjectAI.cs | 67 + Game/AI/CoreAI/GuardAI.cs | 76 + Game/AI/CoreAI/PassiveAI.cs | 134 + Game/AI/CoreAI/PetAI.cs | 645 ++ Game/AI/CoreAI/TotemAI.cs | 83 + Game/AI/CoreAI/UnitAI.cs | 566 + Game/AI/PlayerAI/PlayerAI.cs | 1349 +++ Game/AI/ScriptedAI/ScriptedAI.cs | 802 ++ Game/AI/ScriptedAI/ScriptedEscortAI.cs | 627 ++ Game/AI/ScriptedAI/ScriptedFollowerAI.cs | 400 + Game/AI/SmartScripts/SmartAI.cs | 1114 ++ Game/AI/SmartScripts/SmartAIManager.cs | 2776 +++++ Game/AI/SmartScripts/SmartScript.cs | 4057 +++++++ Game/Accounts/AccountManager.cs | 515 + Game/Accounts/BNetAccountManager.cs | 183 + Game/Accounts/RoleAccessControl.cs | 385 + Game/Achievements/AchievementManager.cs | 1325 +++ Game/Achievements/CriteriaHandler.cs | 2299 ++++ Game/Arenas/Arena.cs | 293 + Game/Arenas/ArenaScore.cs | 61 + Game/Arenas/ArenaTeam.cs | 861 ++ Game/Arenas/ArenaTeamManager.cs | 129 + Game/Arenas/Zones/BladesEdgeArena.cs | 111 + Game/Arenas/Zones/DalaranSewers.cs | 241 + Game/Arenas/Zones/NagrandArena.cs | 108 + Game/Arenas/Zones/RingofValor.cs | 264 + Game/Arenas/Zones/RuinsofLordaeron.cs | 102 + Game/Arenas/Zones/TigersPeak.cs | 23 + Game/Arenas/Zones/TolvironArena.cs | 23 + Game/AuctionHouse/AuctionManager.cs | 845 ++ Game/BattleFields/BattleField.cs | 1354 +++ Game/BattleFields/BattleFieldManager.cs | 155 + Game/BattleFields/Zones/WinterGrasp.cs | 1705 +++ Game/BattleFields/Zones/WinterGraspConst.cs | 836 ++ Game/BattleGrounds/BattleGround.cs | 2144 ++++ Game/BattleGrounds/BattleGroundManager.cs | 917 ++ Game/BattleGrounds/BattleGroundQueue.cs | 1192 ++ Game/BattleGrounds/BattleGroundScore.cs | 80 + Game/BattleGrounds/Zones/AlteracValley.cs | 23 + Game/BattleGrounds/Zones/ArathiBasin.cs | 1017 ++ Game/BattleGrounds/Zones/BattleforGilneas.cs | 23 + Game/BattleGrounds/Zones/DeepwindGorge.cs | 23 + Game/BattleGrounds/Zones/EyeofStorm.cs | 1341 +++ Game/BattleGrounds/Zones/IsleofConquest.cs | 63 + Game/BattleGrounds/Zones/SilvershardMines.cs | 23 + Game/BattleGrounds/Zones/StrandofAncients.cs | 36 + Game/BattleGrounds/Zones/TempleofKotmogu.cs | 23 + Game/BattleGrounds/Zones/TwinPeaks.cs | 23 + Game/BattleGrounds/Zones/WarsongGluch.cs | 1108 ++ Game/BattlePets/BattlePetManager.cs | 498 + Game/BlackMarket/BlackMarketEntry.cs | 231 + Game/BlackMarket/BlackMarketManager.cs | 310 + Game/Calendar/CalendarManager.cs | 751 ++ Game/Chat/Channels/Channel.cs | 980 ++ Game/Chat/Channels/ChannelAppenders.cs | 716 ++ Game/Chat/Channels/ChannelManager.cs | 166 + Game/Chat/CommandAttribute.cs | 66 + Game/Chat/CommandHandler.cs | 877 ++ Game/Chat/CommandManager.cs | 197 + Game/Chat/Commands/AccountCommands.cs | 815 ++ Game/Chat/Commands/AhBotCommands.cs | 224 + Game/Chat/Commands/ArenaCommands.cs | 294 + Game/Chat/Commands/BNetAccountCommands.cs | 429 + Game/Chat/Commands/BanCommands.cs | 664 ++ Game/Chat/Commands/BattleFieldCommands.cs | 121 + Game/Chat/Commands/CastCommands.cs | 248 + Game/Chat/Commands/CharacterCommands.cs | 895 ++ Game/Chat/Commands/CheatCommands.cs | 256 + Game/Chat/Commands/DebugCommands.cs | 1382 +++ Game/Chat/Commands/DeserterCommands.cs | 116 + Game/Chat/Commands/DisableCommands.cs | 360 + Game/Chat/Commands/EventCommands.cs | 180 + Game/Chat/Commands/GMCommands.cs | 234 + Game/Chat/Commands/GameObjectCommands.cs | 617 + Game/Chat/Commands/GoCommands.cs | 622 ++ Game/Chat/Commands/GroupCommands.cs | 351 + Game/Chat/Commands/GuildCommands.cs | 226 + Game/Chat/Commands/HonorCommands.cs | 91 + Game/Chat/Commands/InstanceCommands.cs | 273 + Game/Chat/Commands/LFGCommands.cs | 143 + Game/Chat/Commands/LearnCommands.cs | 348 + Game/Chat/Commands/ListCommands.cs | 555 + Game/Chat/Commands/LookupCommands.cs | 1212 ++ Game/Chat/Commands/MMapsCommands.cs | 261 + Game/Chat/Commands/MessageCommands.cs | 233 + Game/Chat/Commands/MiscCommands.cs | 2463 ++++ Game/Chat/Commands/ModifyCommands.cs | 1083 ++ Game/Chat/Commands/NPCCommands.cs | 1312 +++ Game/Chat/Commands/PetCommands.cs | 203 + Game/Chat/Commands/QuestCommands.cs | 250 + Game/Chat/Commands/RbacCommands.cs | 317 + Game/Chat/Commands/ReloadCommand.cs | 1114 ++ Game/Chat/Commands/ResetCommands.cs | 282 + Game/Chat/Commands/SceneCommands.cs | 118 + Game/Chat/Commands/SendCommands.cs | 248 + Game/Chat/Commands/ServerCommands.cs | 376 + Game/Chat/Commands/SpellCommands.cs | 194 + Game/Chat/Commands/TeleCommands.cs | 170 + Game/Chat/Commands/TicketCommands.cs | 490 + Game/Chat/Commands/TitleCommands.cs | 207 + Game/Chat/Commands/WPCommands.cs | 969 ++ Game/Collision/BoundingIntervalHierarchy.cs | 636 ++ .../BoundingIntervalHierarchyWrap.cs | 111 + Game/Collision/Callbacks.cs | 250 + Game/Collision/DynamicTree.cs | 179 + Game/Collision/Management/VMapManager.cs | 289 + Game/Collision/Maps/MapTree.cs | 387 + Game/Collision/Models/GameObjectModel.cs | 213 + Game/Collision/Models/IModel.cs | 27 + Game/Collision/Models/ModelInstance.cs | 201 + Game/Collision/Models/WorldModel.cs | 405 + Game/Collision/RegularGrid2D.cs | 211 + Game/Combat/Events.cs | 161 + Game/Combat/HostileRegManager.cs | 377 + Game/Combat/ThreatManager.cs | 470 + Game/Conditions/Condition.cs | 578 + Game/Conditions/ConditionManager.cs | 2153 ++++ Game/Conditions/DisableManager.cs | 433 + Game/DataStorage/AreaTriggerDataStorage.cs | 218 + .../CharacterTemplateDataStorage.cs | 127 + Game/DataStorage/CliDB.cs | 683 ++ Game/DataStorage/ClientReader/CliDBReader.cs | 759 ++ Game/DataStorage/ClientReader/DB6Meta.cs | 630 ++ Game/DataStorage/ClientReader/DB6MetaOld.cs | 5985 ++++++++++ Game/DataStorage/ClientReader/DB6Storage.cs | 359 + Game/DataStorage/ClientReader/GameTables.cs | 38 + Game/DataStorage/ConversationDataStorage.cs | 212 + Game/DataStorage/DB2Manager.cs | 1366 +++ Game/DataStorage/M2Storage.cs | 227 + Game/DataStorage/Structs/A_Records.cs | 221 + Game/DataStorage/Structs/B_Records.cs | 119 + Game/DataStorage/Structs/C_Records.cs | 340 + Game/DataStorage/Structs/D_Records.cs | 92 + Game/DataStorage/Structs/E_Records.cs | 50 + Game/DataStorage/Structs/F_Records.cs | 102 + Game/DataStorage/Structs/G_Records.cs | 270 + Game/DataStorage/Structs/GameTablesRecords.cs | 183 + Game/DataStorage/Structs/H_Records.cs | 50 + Game/DataStorage/Structs/I_Records.cs | 383 + Game/DataStorage/Structs/K_Records.cs | 25 + Game/DataStorage/Structs/L_Records.cs | 106 + Game/DataStorage/Structs/M2Structure.cs | 240 + Game/DataStorage/Structs/M_Records.cs | 179 + Game/DataStorage/Structs/MiscStructs.cs | 46 + Game/DataStorage/Structs/N_Records.cs | 47 + Game/DataStorage/Structs/O_Records.cs | 29 + Game/DataStorage/Structs/P_Records.cs | 178 + Game/DataStorage/Structs/Q_Records.cs | 61 + Game/DataStorage/Structs/R_Records.cs | 53 + Game/DataStorage/Structs/S_Records.cs | 560 + Game/DataStorage/Structs/T_Records.cs | 113 + Game/DataStorage/Structs/U_Records.cs | 40 + Game/DataStorage/Structs/V_Records.cs | 137 + Game/DataStorage/Structs/W_Records.cs | 104 + Game/DungeonFinding/LFGGroupData.cs | 151 + Game/DungeonFinding/LFGManager.cs | 2192 ++++ Game/DungeonFinding/LFGPlayerData.cs | 132 + Game/DungeonFinding/LFGQueue.cs | 780 ++ Game/DungeonFinding/LFGScripts.cs | 245 + Game/Entities/AreaTrigger/AreaTrigger.cs | 770 ++ .../AreaTrigger/AreaTriggerTemplate.cs | 211 + Game/Entities/Conversation.cs | 189 + Game/Entities/Corpse.cs | 218 + Game/Entities/Creature/Creature.Fields.cs | 106 + Game/Entities/Creature/Creature.cs | 3102 ++++++ Game/Entities/Creature/CreatureData.cs | 423 + Game/Entities/Creature/CreatureGroups.cs | 274 + Game/Entities/Creature/Gossip.cs | 852 ++ Game/Entities/DynamicObject.cs | 265 + Game/Entities/GameObject/GameObject.cs | 2729 +++++ Game/Entities/GameObject/GameObjectData.cs | 1069 ++ Game/Entities/Item/Bag.cs | 261 + Game/Entities/Item/Item.cs | 2881 +++++ Game/Entities/Item/ItemEnchantment.cs | 293 + Game/Entities/Item/ItemTemplate.cs | 333 + Game/Entities/Object/ObjectGuid.cs | 349 + Game/Entities/Object/Position.cs | 363 + Game/Entities/Object/Update/UpdateData.cs | 91 + Game/Entities/Object/Update/UpdateMask.cs | 101 + Game/Entities/Object/WorldObject.cs | 3265 ++++++ Game/Entities/Pet.cs | 1626 +++ Game/Entities/Player/CinematicManager.cs | 192 + Game/Entities/Player/CollectionManager.cs | 801 ++ Game/Entities/Player/KillRewarder.cs | 281 + Game/Entities/Player/Player.Achievement.cs | 89 + Game/Entities/Player/Player.Combat.cs | 582 + Game/Entities/Player/Player.DB.cs | 4026 +++++++ Game/Entities/Player/Player.Fields.cs | 612 + Game/Entities/Player/Player.Groups.cs | 290 + Game/Entities/Player/Player.Items.cs | 6601 +++++++++++ Game/Entities/Player/Player.Map.cs | 797 ++ Game/Entities/Player/Player.PvP.cs | 753 ++ Game/Entities/Player/Player.Quest.cs | 2992 +++++ Game/Entities/Player/Player.Spells.cs | 3218 ++++++ Game/Entities/Player/Player.Talents.cs | 555 + Game/Entities/Player/Player.cs | 7411 ++++++++++++ Game/Entities/Player/PlayerTaxi.cs | 244 + Game/Entities/Player/RestMgr.cs | 170 + Game/Entities/Player/SceneMgr.cs | 246 + Game/Entities/Player/SocialMgr.cs | 362 + Game/Entities/Player/TradeData.cs | 186 + Game/Entities/StatSystem.cs | 1878 ++++ Game/Entities/Taxi/Graph.cs | 515 + Game/Entities/Taxi/TaxiPathGraph.cs | 187 + Game/Entities/TemporarySummon.cs | 1044 ++ Game/Entities/Totem.cs | 211 + Game/Entities/Transport.cs | 799 ++ Game/Entities/Unit/CharmInfo.cs | 441 + Game/Entities/Unit/Comparer.cs | 80 + Game/Entities/Unit/Unit.Combat.cs | 3173 ++++++ Game/Entities/Unit/Unit.Fields.cs | 646 ++ Game/Entities/Unit/Unit.Movement.cs | 1531 +++ Game/Entities/Unit/Unit.Pets.cs | 796 ++ Game/Entities/Unit/Unit.Spells.cs | 5119 +++++++++ Game/Entities/Unit/Unit.cs | 2942 +++++ Game/Entities/Vehicle.cs | 696 ++ Game/Events/GameEventManager.cs | 1722 +++ Game/Game.csproj | 519 + Game/Garrisons/GarrisonManager.cs | 502 + Game/Garrisons/GarrisonMap.cs | 142 + Game/Garrisons/Garrisons.cs | 864 ++ Game/Globals/Global.cs | 109 + Game/Globals/ObjectAccessor.cs | 273 + Game/Globals/ObjectManager.cs | 9891 +++++++++++++++++ Game/Groups/Group.cs | 2665 +++++ Game/Groups/GroupManager.cs | 239 + Game/Groups/GroupReference.cs | 50 + Game/Guilds/Guild.cs | 3781 +++++++ Game/Guilds/GuildFinderManager.cs | 512 + Game/Guilds/GuildManager.cs | 531 + Game/Handlers/ArenaHandler.cs | 33 + Game/Handlers/ArtifactHandler.cs | 236 + Game/Handlers/AuctionHandler.cs | 673 ++ Game/Handlers/AuthenticationHandler.cs | 104 + Game/Handlers/BankHandler.cs | 151 + Game/Handlers/BattleFieldHandler.cs | 156 + Game/Handlers/BattleGroundHandler.cs | 694 ++ Game/Handlers/BattlePetHandler.cs | 96 + Game/Handlers/BattlenetHandler.cs | 103 + Game/Handlers/BlackMarketHandlers.cs | 165 + Game/Handlers/CalendarHandler.cs | 524 + Game/Handlers/ChannelHandler.cs | 200 + Game/Handlers/CharacterHandler.cs | 2675 +++++ Game/Handlers/ChatHandler.cs | 668 ++ Game/Handlers/CollectionsHandler.cs | 50 + Game/Handlers/CombatHandler.cs | 89 + Game/Handlers/DuelHandler.cs | 104 + Game/Handlers/GarrisonHandler.cs | 73 + Game/Handlers/GroupHandler.cs | 655 ++ Game/Handlers/GuildFinderHandler.cs | 249 + Game/Handlers/GuildHandler.cs | 467 + Game/Handlers/HotfixHandler.cs | 94 + Game/Handlers/InspectHandler.cs | 154 + Game/Handlers/ItemHandler.cs | 1127 ++ Game/Handlers/LFGHandler.cs | 577 + Game/Handlers/LogoutHandler.cs | 102 + Game/Handlers/LootHandler.cs | 585 + Game/Handlers/MailHandler.cs | 692 ++ Game/Handlers/MiscHandler.cs | 782 ++ Game/Handlers/MovementHandler.cs | 651 ++ Game/Handlers/NPCHandler.cs | 675 ++ Game/Handlers/PetHandler.cs | 726 ++ Game/Handlers/PetitionsHandler.cs | 535 + Game/Handlers/QueryHandler.cs | 447 + Game/Handlers/QuestHandler.cs | 647 ++ Game/Handlers/RAFHandler.cs | 86 + Game/Handlers/ScenarioHandler.cs | 52 + Game/Handlers/SceneHandler.cs | 49 + Game/Handlers/SkillHandler.cs | 95 + Game/Handlers/SocialHandler.cs | 355 + Game/Handlers/SpellHandler.cs | 616 + Game/Handlers/TaxiHandler.cs | 238 + Game/Handlers/TicketHandler.cs | 115 + Game/Handlers/TimeHandler.cs | 54 + Game/Handlers/TokenHandler.cs | 52 + Game/Handlers/ToyHandler.cs | 96 + Game/Handlers/TradeHandler.cs | 765 ++ Game/Handlers/TransmogrificationHandler.cs | 295 + Game/Handlers/VehicleHandler.cs | 216 + Game/Handlers/VoidStorageHandler.cs | 266 + Game/Loot/Loot.cs | 918 ++ Game/Loot/LootManager.cs | 1098 ++ Game/Loot/LootStorage.cs | 35 + Game/Mails/Mail.cs | 179 + Game/Mails/MailDraft.cs | 265 + Game/Maps/AreaBoundary.cs | 271 + Game/Maps/Cell.cs | 319 + Game/Maps/Grid.cs | 477 + Game/Maps/GridDefines.cs | 286 + Game/Maps/GridMap.cs | 712 ++ Game/Maps/GridNotifiers.cs | 2258 ++++ Game/Maps/Instances/InstanceSaveManager.cs | 779 ++ Game/Maps/Instances/InstanceScript.cs | 946 ++ Game/Maps/Instances/MapInstance.cs | 307 + Game/Maps/MMapManager.cs | 581 + Game/Maps/Map.cs | 4675 ++++++++ Game/Maps/MapManager.cs | 428 + Game/Maps/MapUpdater.cs | 106 + Game/Maps/ObjectGridLoader.cs | 241 + Game/Maps/TransportManager.cs | 603 + Game/Maps/ZoneScript.cs | 51 + Game/Miscellaneous/Formulas.cs | 253 + Game/Movement/Generators/ConfusedGenerator.cs | 125 + Game/Movement/Generators/FleeingGenerator.cs | 223 + Game/Movement/Generators/HomeMovement.cs | 85 + Game/Movement/Generators/IdleMovement.cs | 159 + .../Movement/Generators/MovementGenerators.cs | 91 + Game/Movement/Generators/PathGenerator.cs | 1008 ++ Game/Movement/Generators/PointMovement.cs | 207 + Game/Movement/Generators/RandomMovement.cs | 179 + .../SplineChainMovementGenerator.cs | 186 + Game/Movement/Generators/TargetMovement.cs | 401 + Game/Movement/Generators/WaypointMovement.cs | 503 + Game/Movement/MotionMaster.cs | 764 ++ Game/Movement/MoveSpline.cs | 421 + Game/Movement/MoveSplineFlags.cs | 55 + Game/Movement/MoveSplineInit.cs | 341 + Game/Movement/MoveSplineInitArgs.cs | 95 + Game/Movement/Spline.cs | 378 + Game/Movement/WaypointManager.cs | 157 + Game/Network/Packet.cs | 223 + Game/Network/PacketLog.cs | 114 + Game/Network/PacketManager.cs | 239 + Game/Network/PacketUtilities.cs | 99 + Game/Network/Packets/AchievementPackets.cs | 345 + Game/Network/Packets/AreaTriggerPackets.cs | 159 + Game/Network/Packets/ArtifactPackets.cs | 125 + Game/Network/Packets/AuctionHousePackets.cs | 562 + Game/Network/Packets/AuthenticationPackets.cs | 444 + Game/Network/Packets/BankPackets.cs | 65 + Game/Network/Packets/BattleGroundPackets.cs | 601 + Game/Network/Packets/BattlePetPackets.cs | 301 + Game/Network/Packets/BattlefieldPackets.cs | 179 + Game/Network/Packets/BattlenetPackets.cs | 139 + Game/Network/Packets/BlackMarketPackets.cs | 190 + Game/Network/Packets/CalendarPackets.cs | 896 ++ Game/Network/Packets/ChannelPackets.cs | 327 + Game/Network/Packets/CharacterPackets.cs | 1073 ++ Game/Network/Packets/ChatPackets.cs | 471 + Game/Network/Packets/ClientConfigPackets.cs | 126 + Game/Network/Packets/CollectionPackets.cs | 43 + Game/Network/Packets/CombatLogPackets.cs | 704 ++ Game/Network/Packets/CombatPackets.cs | 295 + Game/Network/Packets/DuelPackets.cs | 143 + Game/Network/Packets/EquipmentPackets.cs | 172 + Game/Network/Packets/GameObjectPackets.cs | 138 + Game/Network/Packets/GarrisonPackets.cs | 610 + Game/Network/Packets/GuildFinderPackets.cs | 342 + Game/Network/Packets/GuildPackets.cs | 1550 +++ Game/Network/Packets/HotfixPackets.cs | 150 + Game/Network/Packets/InspectPackets.cs | 275 + Game/Network/Packets/InstancePackets.cs | 294 + Game/Network/Packets/ItemPackets.cs | 999 ++ Game/Network/Packets/LFGPackets.cs | 724 ++ Game/Network/Packets/LootPackets.cs | 412 + Game/Network/Packets/MailPackets.cs | 459 + Game/Network/Packets/MiscPackets.cs | 1290 +++ Game/Network/Packets/MovementPackets.cs | 1254 +++ Game/Network/Packets/NPCPackets.cs | 355 + Game/Network/Packets/PartyPackets.cs | 1115 ++ Game/Network/Packets/PetPackets.cs | 401 + Game/Network/Packets/PetitionPackets.cs | 345 + Game/Network/Packets/QueryPackets.cs | 773 ++ Game/Network/Packets/QuestPackets.cs | 1129 ++ Game/Network/Packets/ReferAFriendPackets.cs | 75 + Game/Network/Packets/ReputationPackets.cs | 123 + Game/Network/Packets/ScenarioPackets.cs | 196 + Game/Network/Packets/ScenePackets.cs | 95 + Game/Network/Packets/SocialPackets.cs | 221 + Game/Network/Packets/SpellPackets.cs | 1682 +++ Game/Network/Packets/SystemPackets.cs | 293 + Game/Network/Packets/TalentPackets.cs | 165 + Game/Network/Packets/TaxiPackets.cs | 150 + Game/Network/Packets/TicketPackets.cs | 456 + Game/Network/Packets/TokenPackets.cs | 98 + Game/Network/Packets/TotemPackets.cs | 75 + Game/Network/Packets/ToyPackets.cs | 75 + Game/Network/Packets/TradePackets.cs | 268 + .../Packets/TransmogrificationPackets.cs | 80 + Game/Network/Packets/UpdatePackets.cs | 37 + Game/Network/Packets/VehiclePackets.cs | 160 + Game/Network/Packets/VoidStoragePackets.cs | 183 + Game/Network/Packets/WardenPackets.cs | 179 + Game/Network/Packets/WhoPackets.cs | 172 + Game/Network/Packets/WorldStatePackets.cs | 86 + Game/Network/WorldSocket.cs | 760 ++ Game/Network/WorldSocketManager.cs | 88 + Game/OutdoorPVP/OutdoorPvP.cs | 802 ++ Game/OutdoorPVP/OutdoorPvPManager.cs | 251 + Game/OutdoorPVP/Zones/HellfirePeninsulaPvP.cs | 400 + Game/OutdoorPVP/Zones/NagrandPvP.cs | 755 ++ Game/Pools/PoolManager.cs | 1041 ++ Game/Properties/AssemblyInfo.cs | 36 + Game/Quest/Quest.cs | 601 + Game/Reputation/ReputationManager.cs | 700 ++ Game/Scenarios/InstanceScenario.cs | 183 + Game/Scenarios/Scenario.cs | 368 + Game/Scenarios/ScenarioManager.cs | 266 + Game/Scripting/CoreScripts.cs | 707 ++ Game/Scripting/ScriptManager.cs | 1420 +++ Game/Scripting/SpellScript.cs | 1401 +++ Game/Server/WorldConfig.cs | 959 ++ Game/Server/WorldManager.cs | 2542 +++++ Game/Server/WorldSession.cs | 1052 ++ Game/Services/GameUtilitiesService.cs | 164 + Game/Services/ServiceManager.cs | 116 + Game/Spells/Auras/Aura.cs | 2704 +++++ Game/Spells/Auras/AuraEffect.cs | 6259 +++++++++++ Game/Spells/Skills/SkillDiscovery.cs | 240 + Game/Spells/Skills/SkillExtraItems.cs | 227 + Game/Spells/Spell.cs | 7748 +++++++++++++ Game/Spells/SpellCastTargets.cs | 440 + Game/Spells/SpellEffects.cs | 5867 ++++++++++ Game/Spells/SpellHistory.cs | 983 ++ Game/Spells/SpellInfo.cs | 3690 ++++++ Game/Spells/SpellManager.cs | 3671 ++++++ Game/SupportSystem/SupportManager.cs | 386 + Game/SupportSystem/SupportTickets.cs | 454 + Game/Text/ChatTextBuilder.cs | 83 + Game/Text/CreatureTextManager.cs | 685 ++ Game/Tools/CharacterDatabaseCleaner.cs | 172 + Game/Warden/Warden.cs | 220 + Game/Warden/WardenKeyGeneration.cs | 68 + Game/Warden/WardenModuleWin.cs | 1225 ++ Game/Warden/WardenWin.cs | 455 + Game/Warden/WargenCheckManager.cs | 211 + Game/Weather/WeatherManager.cs | 468 + LICENSE | 674 ++ Libs/Google.Protobuf.dll | Bin 0 -> 296448 bytes Libs/Logo.png | Bin 0 -> 44493 bytes Libs/MySql.Data.dll | Bin 0 -> 457216 bytes README.md | 5 + .../Deadmines/InstanceDeadmines.cs | 212 + Scripts/EasternKingdoms/EversongWoods.cs | 296 + .../Karazhan/AttumenMidnight.cs | 325 + .../EasternKingdoms/Karazhan/ChessEvent.cs | 1703 +++ Scripts/EasternKingdoms/Karazhan/Curator.cs | 168 + .../Karazhan/InstanceKarazhan.cs | 462 + Scripts/EasternKingdoms/Karazhan/Moroes.cs | 778 ++ .../EasternKingdoms/Karazhan/OperaEvent.cs | 1795 +++ .../ScarletEnclave/Chapter1.cs | 384 + .../EasternKingdoms/TheStockade/BossHogger.cs | 157 + .../TheStockade/InstanceTheStockade.cs | 56 + Scripts/Kalimdor/Durotar.cs | 151 + Scripts/Kalimdor/RageFireChasm.cs | 38 + .../AzjolNerub/Ahnkahet/BossAmanitar.cs | 209 + .../AzjolNerub/Ahnkahet/BossElderNadox.cs | 276 + .../AzjolNerub/Ahnkahet/BossHeraldVolazj.cs | 295 + .../Ahnkahet/BossJedogaShadowseeker.cs | 602 + .../AzjolNerub/Ahnkahet/BossPrinceTaldaram.cs | 476 + .../AzjolNerub/Ahnkahet/InstanceAhnahet.cs | 334 + .../AzjolNerub/AzjolNerub/BossAnubarak.cs | 722 ++ .../AzjolNerub/BossKrikthirTheGatewatcher.cs | 1003 ++ .../AzjolNerub/AzjolNerub/BossNadronox.cs | 1086 ++ .../AzjolNerub/InstanceAzjolNerub.cs | 131 + .../TrialOfTheChampion/GrandChampions.cs | 729 ++ .../InstanceTrialOfTheChampion.cs | 333 + .../TrialOfTheChampion/TrialOfTheChampion.cs | 587 + .../TrialOfTheCrusader/LordJaraxxus.cs | 568 + .../TrialOfTheCrusader/NorthrendBeasts.cs | 1106 ++ .../TrialOfTheCrusader/TrialOfTheCrusader.cs | 1719 +++ .../TrialOfTheCrusaderConst.cs | 409 + Scripts/Northrend/Dalaran.cs | 223 + .../Northrend/DraktharonKeep/BossKingDred.cs | 281 + Scripts/Northrend/DraktharonKeep/BossNovos.cs | 440 + .../Northrend/DraktharonKeep/BossTharonJa.cs | 251 + .../Northrend/DraktharonKeep/BossTrollgore.cs | 335 + .../DraktharonKeep/InstanceDrakTharonKeep.cs | 224 + .../Northrend/Gundrak/BossDrakkariColossus.cs | 444 + Scripts/Northrend/Gundrak/BossEck.cs | 128 + Scripts/Northrend/Gundrak/InstanceGundrak.cs | 439 + .../IcecrownCitadel/GunshipBattle.cs | 2455 ++++ .../IcecrownCitadel/IcecrownCitadel.cs | 1948 ++++ .../IcecrownCitadel/IcecrownCitadelConst.cs | 733 ++ .../IcecrownCitadelTeleport.cs | 106 + .../InstanceIcecrownCitadel.cs | 1593 +++ .../IcecrownCitadel/LadyDeathwhisper.cs | 1107 ++ .../IcecrownCitadel/LordMarrowgar.cs | 734 ++ .../IcecrownCitadel/ProfessorPutricide.cs | 1524 +++ .../Northrend/IcecrownCitadel/Sindragosa.cs | 35 + .../Northrend/IcecrownCitadel/TheLichKing.cs | 39 + .../IcecrownCitadel/ValithriaDreamwalker.cs | 26 + Scripts/Northrend/Naxxramas/BossAnubrekhan.cs | 63 + .../Nexus/EyeOfEternity/BossMalygos.cs | 29 + .../Nexus/EyeOfEternity/EyeOfEternity.cs | 351 + Scripts/Northrend/Nexus/Nexus/BossAnomalus.cs | 291 + .../Northrend/Nexus/Nexus/BossKeristrasza.cs | 277 + .../Nexus/Nexus/BossMagusTelestra.cs | 349 + .../Nexus/Nexus/BossNexusCommanders.cs | 113 + Scripts/Northrend/Nexus/Nexus/BossOrmorok.cs | 291 + .../Northrend/Nexus/Nexus/InstanceNexus.cs | 227 + .../Northrend/Nexus/Oculus/InstanceOculus.cs | 368 + .../Northrend/Ulduar/AlgalonTheObserver.cs | 30 + Scripts/Northrend/Ulduar/FlameLeviathan.cs | 1768 +++ Scripts/Northrend/Ulduar/Mimiron.cs | 2734 +++++ Scripts/Northrend/Ulduar/Razorscale.cs | 162 + Scripts/Northrend/Ulduar/UlduarConst.cs | 409 + Scripts/Northrend/Ulduar/UlduarInstance.cs | 1241 +++ Scripts/Northrend/Ulduar/Xt002.cs | 1024 ++ Scripts/Northrend/Ulduar/YoggSaron.cs | 39 + .../VioletHold/InstanceVioletHold.cs | 756 ++ Scripts/Northrend/VioletHold/VioletHold.cs | 21 + Scripts/Northrend/WinterGrasp.cs | 116 + Scripts/Outlands/HellfirePeninsula.cs | 340 + Scripts/Outlands/Zangarmarsh.cs | 402 + Scripts/Pets/DeathKinght.cs | 137 + Scripts/Pets/Generic.cs | 85 + Scripts/Pets/Hunter.cs | 127 + Scripts/Pets/Mage.cs | 252 + Scripts/Pets/Priest.cs | 83 + Scripts/Pets/Shaman.cs | 133 + Scripts/Properties/AssemblyInfo.cs | 36 + Scripts/Scripts.csproj | 193 + Scripts/Spells/DeathKnight.cs | 1258 +++ Scripts/Spells/DemonHunter.cs | 29 + Scripts/Spells/Druid.cs | 1167 ++ Scripts/Spells/Generic.cs | 4171 +++++++ Scripts/Spells/Holiday.cs | 984 ++ Scripts/Spells/Hunter.cs | 1049 ++ Scripts/Spells/Items.cs | 4237 +++++++ Scripts/Spells/Mage.cs | 1031 ++ Scripts/Spells/Monk.cs | 169 + Scripts/Spells/Paladin.cs | 1073 ++ Scripts/Spells/Priest.cs | 1471 +++ Scripts/Spells/Quest.cs | 2435 ++++ Scripts/Spells/Rogue.cs | 217 + Scripts/Spells/Shaman.cs | 1188 ++ Scripts/Spells/Warlock.cs | 1347 +++ Scripts/Spells/Warrior.cs | 1272 +++ Scripts/World/Achievements.cs | 304 + Scripts/World/AreaTrigger.cs | 398 + Scripts/World/BossEmeraldDragons.cs | 621 ++ Scripts/World/DuelReset.cs | 144 + Scripts/World/GameObjects.cs | 979 ++ Scripts/World/Guards.cs | 382 + Scripts/World/ItemScripts.cs | 361 + Scripts/World/MobGenericCreature.cs | 53 + Scripts/World/NpcProfessions.cs | 139 + Scripts/World/NpcSpecial.cs | 2469 ++++ Scripts/World/SceneScripts.cs | 41 + THANKS | 7 + WorldServer/Blue.ico | Bin 0 -> 150531 bytes WorldServer/Properties/AssemblyInfo.cs | 36 + WorldServer/Server.cs | 202 + WorldServer/WorldServer.conf | 3642 ++++++ WorldServer/WorldServer.csproj | 136 + 763 files changed, 488824 insertions(+) create mode 100644 .gitattributes create mode 100644 .gitignore create mode 100644 BNetServer/BNetServer.conf create mode 100644 BNetServer/BNetServer.csproj create mode 100644 BNetServer/Managers/Global.cs create mode 100644 BNetServer/Managers/SessionManager.cs create mode 100644 BNetServer/Networking/RestSession.cs create mode 100644 BNetServer/Networking/Session.cs create mode 100644 BNetServer/Properties/AssemblyInfo.cs create mode 100644 BNetServer/Red.ico create mode 100644 BNetServer/Server.cs create mode 100644 BNetServer/Services/AccountService.cs create mode 100644 BNetServer/Services/AuthenticationService.cs create mode 100644 BNetServer/Services/ChallengeService.cs create mode 100644 BNetServer/Services/ChannelService.cs create mode 100644 BNetServer/Services/ConnectionService.cs create mode 100644 BNetServer/Services/FriendService.cs create mode 100644 BNetServer/Services/GameUtilitiesService.cs create mode 100644 BNetServer/Services/PresenceService.cs create mode 100644 BNetServer/Services/ReportService.cs create mode 100644 BNetServer/Services/ResourcesService.cs create mode 100644 BNetServer/Services/ServiceDispatcher.cs create mode 100644 BNetServer/Services/UserManagerService.cs create mode 100644 CypherCore.sln create mode 100644 Framework/Collections/Array.cs create mode 100644 Framework/Collections/BitSet.cs create mode 100644 Framework/Collections/IMultiMap.cs create mode 100644 Framework/Collections/LinkedListElement.cs create mode 100644 Framework/Collections/MultiMap.cs create mode 100644 Framework/Collections/MultiMapEnumerator.cs create mode 100644 Framework/Collections/StringArray.cs create mode 100644 Framework/Configuration/ConfigManager.cs create mode 100644 Framework/Constants/AccountConst.cs create mode 100644 Framework/Constants/AchievementConst.cs create mode 100644 Framework/Constants/AreaTriggerConst.cs create mode 100644 Framework/Constants/AuctionConst.cs create mode 100644 Framework/Constants/Authentication/AuthConst.cs create mode 100644 Framework/Constants/Authentication/BattlenetConst.cs create mode 100644 Framework/Constants/Authentication/RealmConst.cs create mode 100644 Framework/Constants/BattleFieldConst.cs create mode 100644 Framework/Constants/BattleGroundsConst.cs create mode 100644 Framework/Constants/BattlePetConst.cs create mode 100644 Framework/Constants/BlackMarketConst.cs create mode 100644 Framework/Constants/CalendarConst.cs create mode 100644 Framework/Constants/ChatConst.cs create mode 100644 Framework/Constants/CliDBConst.cs create mode 100644 Framework/Constants/ConditionConst.cs create mode 100644 Framework/Constants/ConnectConst.cs create mode 100644 Framework/Constants/CreatureConst.cs create mode 100644 Framework/Constants/GameObjectConst.cs create mode 100644 Framework/Constants/GarrisonConst.cs create mode 100644 Framework/Constants/GossipConst.cs create mode 100644 Framework/Constants/GroupConst.cs create mode 100644 Framework/Constants/GuildConst.cs create mode 100644 Framework/Constants/ItemConst.cs create mode 100644 Framework/Constants/LFGConst.cs create mode 100644 Framework/Constants/Language.cs create mode 100644 Framework/Constants/LootConst.cs create mode 100644 Framework/Constants/MailConst.cs create mode 100644 Framework/Constants/MapConst.cs create mode 100644 Framework/Constants/Movement/MovementConst.cs create mode 100644 Framework/Constants/Movement/MovementFlags.cs create mode 100644 Framework/Constants/Movement/SplineConst.cs create mode 100644 Framework/Constants/Network/NetworkConst.cs create mode 100644 Framework/Constants/Network/Opcodes.cs create mode 100644 Framework/Constants/ObjectConst.cs create mode 100644 Framework/Constants/OutdoorPvPConst.cs create mode 100644 Framework/Constants/PetConst.cs create mode 100644 Framework/Constants/PlayerConst.cs create mode 100644 Framework/Constants/QuestConst.cs create mode 100644 Framework/Constants/ScriptsConst.cs create mode 100644 Framework/Constants/ServiceHash.cs create mode 100644 Framework/Constants/SharedConst.cs create mode 100644 Framework/Constants/SmartAIConst.cs create mode 100644 Framework/Constants/Spells/SkillConst.cs create mode 100644 Framework/Constants/Spells/SpellAuraConst.cs create mode 100644 Framework/Constants/Spells/SpellConst.cs create mode 100644 Framework/Constants/SupportSystemConst.cs create mode 100644 Framework/Constants/UnitConst.cs create mode 100644 Framework/Constants/Update/UpdateFieldFlags.cs create mode 100644 Framework/Constants/Update/UpdateFields.cs create mode 100644 Framework/Constants/Update/UpdateFlags.cs create mode 100644 Framework/Constants/Update/UpdateType.cs create mode 100644 Framework/Constants/VehicleConst.cs create mode 100644 Framework/Cryptography/PacketCrypt.cs create mode 100644 Framework/Cryptography/RsaCrypt.cs create mode 100644 Framework/Cryptography/SARC4.cs create mode 100644 Framework/Cryptography/SessionKeyGeneration.cs create mode 100644 Framework/Cryptography/ShaHmac.cs create mode 100644 Framework/Database/DB.cs create mode 100644 Framework/Database/DatabaseLoader.cs create mode 100644 Framework/Database/DatabaseUpdater.cs create mode 100644 Framework/Database/Databases/CharacterDatabase.cs create mode 100644 Framework/Database/Databases/HotfixDatabase.cs create mode 100644 Framework/Database/Databases/LoginDatabase.cs create mode 100644 Framework/Database/Databases/WorldDatabase.cs create mode 100644 Framework/Database/MySqlBase.cs create mode 100644 Framework/Database/PreparedStatement.cs create mode 100644 Framework/Database/QueryCallback.cs create mode 100644 Framework/Database/QueryCallbackProcessor.cs create mode 100644 Framework/Database/SQLQueryHolder.cs create mode 100644 Framework/Database/SQLResult.cs create mode 100644 Framework/Database/SQLTransaction.cs create mode 100644 Framework/Dynamic/EventMap.cs create mode 100644 Framework/Dynamic/EventSystem.cs create mode 100644 Framework/Dynamic/FlagArray.cs create mode 100644 Framework/Dynamic/OptionalType.cs create mode 100644 Framework/Dynamic/Reference.cs create mode 100644 Framework/Dynamic/TaskScheduler.cs create mode 100644 Framework/Framework.csproj create mode 100644 Framework/GameMath/AxisAlignedBox.cs create mode 100644 Framework/GameMath/CollisionDetection.cs create mode 100644 Framework/GameMath/Matrix2.cs create mode 100644 Framework/GameMath/Matrix3.cs create mode 100644 Framework/GameMath/Matrix4.cs create mode 100644 Framework/GameMath/Plane.cs create mode 100644 Framework/GameMath/QuaternionD.cs create mode 100644 Framework/GameMath/Ray.cs create mode 100644 Framework/GameMath/Vector2.cs create mode 100644 Framework/GameMath/Vector3.cs create mode 100644 Framework/GameMath/Vector4.cs create mode 100644 Framework/IO/ByteBuffer.cs create mode 100644 Framework/IO/StringArguments.cs create mode 100644 Framework/IO/Zlib/Adler32.cs create mode 100644 Framework/IO/Zlib/Crc32.cs create mode 100644 Framework/IO/Zlib/Deflate.cs create mode 100644 Framework/IO/Zlib/Trees.cs create mode 100644 Framework/IO/Zlib/ZLib.cs create mode 100644 Framework/IO/Zlib/ZUtil.cs create mode 100644 Framework/IO/Zlib/compress.cs create mode 100644 Framework/Logging/Appender.cs create mode 100644 Framework/Logging/Log.cs create mode 100644 Framework/Logging/Logger.cs create mode 100644 Framework/Logging/Metric.cs create mode 100644 Framework/Networking/AsyncAcceptor.cs create mode 100644 Framework/Networking/NetworkThread.cs create mode 100644 Framework/Networking/SSLSocket.cs create mode 100644 Framework/Networking/SocketBase.cs create mode 100644 Framework/Networking/SocketManager.cs create mode 100644 Framework/Properties/AssemblyInfo.cs create mode 100644 Framework/Proto/AccountService.cs create mode 100644 Framework/Proto/AccountTypes.cs create mode 100644 Framework/Proto/AttributeTypes.cs create mode 100644 Framework/Proto/AuthenticationService.cs create mode 100644 Framework/Proto/ChallengeService.cs create mode 100644 Framework/Proto/ChannelService.cs create mode 100644 Framework/Proto/ChannelTypes.cs create mode 100644 Framework/Proto/ConnectionService.cs create mode 100644 Framework/Proto/ContentHandleTypes.cs create mode 100644 Framework/Proto/EntityTypes.cs create mode 100644 Framework/Proto/FriendsService.cs create mode 100644 Framework/Proto/FriendsTypes.cs create mode 100644 Framework/Proto/GameUtilitiesService.cs create mode 100644 Framework/Proto/GameUtilitiesTypes.cs create mode 100644 Framework/Proto/GlobalExtensions/FieldOptions.cs create mode 100644 Framework/Proto/GlobalExtensions/MethodOptions.cs create mode 100644 Framework/Proto/GlobalExtensions/ServiceOptions.cs create mode 100644 Framework/Proto/InvitationTypes.cs create mode 100644 Framework/Proto/NotificationTypes.cs create mode 100644 Framework/Proto/PresenceService.cs create mode 100644 Framework/Proto/PresenceTypes.cs create mode 100644 Framework/Proto/ProfanityFilterConfig.cs create mode 100644 Framework/Proto/ReportService.cs create mode 100644 Framework/Proto/ResourceService.cs create mode 100644 Framework/Proto/RoleTypes.cs create mode 100644 Framework/Proto/RpcConfig.cs create mode 100644 Framework/Proto/RpcTypes.cs create mode 100644 Framework/Proto/UserManagerService.cs create mode 100644 Framework/Proto/UserManagerTypes.cs create mode 100644 Framework/Realm/Realm.cs create mode 100644 Framework/Realm/RealmManager.cs create mode 100644 Framework/RecastDetour/Detour/DetourCommon.cs create mode 100644 Framework/RecastDetour/Detour/DetourNavMesh.cs create mode 100644 Framework/RecastDetour/Detour/DetourNavMeshBuilder.cs create mode 100644 Framework/RecastDetour/Detour/DetourNavMeshQuery.cs create mode 100644 Framework/RecastDetour/Detour/DetourNode.cs create mode 100644 Framework/RecastDetour/Detour/DetourStatus.cs create mode 100644 Framework/RecastDetour/Recast/Recast.cs create mode 100644 Framework/RecastDetour/Recast/RecastArea.cs create mode 100644 Framework/RecastDetour/Recast/RecastContour.cs create mode 100644 Framework/RecastDetour/Recast/RecastFilter.cs create mode 100644 Framework/RecastDetour/Recast/RecastLayers.cs create mode 100644 Framework/RecastDetour/Recast/RecastMesh.cs create mode 100644 Framework/RecastDetour/Recast/RecastMeshDetail.cs create mode 100644 Framework/RecastDetour/Recast/RecastRasterization.cs create mode 100644 Framework/RecastDetour/Recast/RecastRegion.cs create mode 100644 Framework/Rest/Authentication/LogonData.cs create mode 100644 Framework/Rest/Authentication/LogonResult.cs create mode 100644 Framework/Rest/Forms/FormInput.cs create mode 100644 Framework/Rest/Forms/FormInputValue.cs create mode 100644 Framework/Rest/Forms/FormInputs.cs create mode 100644 Framework/Rest/Misc/Address.cs create mode 100644 Framework/Rest/Misc/AddressFamily.cs create mode 100644 Framework/Rest/Misc/ClientVersion.cs create mode 100644 Framework/Rest/Misc/RealmCharacterCountEntry.cs create mode 100644 Framework/Rest/Realmlist/RealmCharacterCountList.cs create mode 100644 Framework/Rest/Realmlist/RealmEntry.cs create mode 100644 Framework/Rest/Realmlist/RealmListServerIPAddresses.cs create mode 100644 Framework/Rest/Realmlist/RealmListTicketClientInformation.cs create mode 100644 Framework/Rest/Realmlist/RealmListTicketIdentity.cs create mode 100644 Framework/Rest/Realmlist/RealmListTicketInformation.cs create mode 100644 Framework/Rest/Realmlist/RealmListUpdate.cs create mode 100644 Framework/Rest/Realmlist/RealmListUpdates.cs create mode 100644 Framework/Runtime/ApplicationSignal.cs create mode 100644 Framework/Serialization/Json.cs create mode 100644 Framework/Singleton/Singleton.cs create mode 100644 Framework/Util/CollectionExtensions.cs create mode 100644 Framework/Util/Extensions.cs create mode 100644 Framework/Util/MathFunctions.cs create mode 100644 Framework/Util/NetworkExtensions.cs create mode 100644 Framework/Util/RandomHelper.cs create mode 100644 Framework/Util/Time.cs create mode 100644 Framework/Web/Http.cs create mode 100644 Game/AI/AISelector.cs create mode 100644 Game/AI/CoreAI/AreaTriggerAI.cs create mode 100644 Game/AI/CoreAI/CombatAI.cs create mode 100644 Game/AI/CoreAI/CreatureAI.cs create mode 100644 Game/AI/CoreAI/GameObjectAI.cs create mode 100644 Game/AI/CoreAI/GuardAI.cs create mode 100644 Game/AI/CoreAI/PassiveAI.cs create mode 100644 Game/AI/CoreAI/PetAI.cs create mode 100644 Game/AI/CoreAI/TotemAI.cs create mode 100644 Game/AI/CoreAI/UnitAI.cs create mode 100644 Game/AI/PlayerAI/PlayerAI.cs create mode 100644 Game/AI/ScriptedAI/ScriptedAI.cs create mode 100644 Game/AI/ScriptedAI/ScriptedEscortAI.cs create mode 100644 Game/AI/ScriptedAI/ScriptedFollowerAI.cs create mode 100644 Game/AI/SmartScripts/SmartAI.cs create mode 100644 Game/AI/SmartScripts/SmartAIManager.cs create mode 100644 Game/AI/SmartScripts/SmartScript.cs create mode 100644 Game/Accounts/AccountManager.cs create mode 100644 Game/Accounts/BNetAccountManager.cs create mode 100644 Game/Accounts/RoleAccessControl.cs create mode 100644 Game/Achievements/AchievementManager.cs create mode 100644 Game/Achievements/CriteriaHandler.cs create mode 100644 Game/Arenas/Arena.cs create mode 100644 Game/Arenas/ArenaScore.cs create mode 100644 Game/Arenas/ArenaTeam.cs create mode 100644 Game/Arenas/ArenaTeamManager.cs create mode 100644 Game/Arenas/Zones/BladesEdgeArena.cs create mode 100644 Game/Arenas/Zones/DalaranSewers.cs create mode 100644 Game/Arenas/Zones/NagrandArena.cs create mode 100644 Game/Arenas/Zones/RingofValor.cs create mode 100644 Game/Arenas/Zones/RuinsofLordaeron.cs create mode 100644 Game/Arenas/Zones/TigersPeak.cs create mode 100644 Game/Arenas/Zones/TolvironArena.cs create mode 100644 Game/AuctionHouse/AuctionManager.cs create mode 100644 Game/BattleFields/BattleField.cs create mode 100644 Game/BattleFields/BattleFieldManager.cs create mode 100644 Game/BattleFields/Zones/WinterGrasp.cs create mode 100644 Game/BattleFields/Zones/WinterGraspConst.cs create mode 100644 Game/BattleGrounds/BattleGround.cs create mode 100644 Game/BattleGrounds/BattleGroundManager.cs create mode 100644 Game/BattleGrounds/BattleGroundQueue.cs create mode 100644 Game/BattleGrounds/BattleGroundScore.cs create mode 100644 Game/BattleGrounds/Zones/AlteracValley.cs create mode 100644 Game/BattleGrounds/Zones/ArathiBasin.cs create mode 100644 Game/BattleGrounds/Zones/BattleforGilneas.cs create mode 100644 Game/BattleGrounds/Zones/DeepwindGorge.cs create mode 100644 Game/BattleGrounds/Zones/EyeofStorm.cs create mode 100644 Game/BattleGrounds/Zones/IsleofConquest.cs create mode 100644 Game/BattleGrounds/Zones/SilvershardMines.cs create mode 100644 Game/BattleGrounds/Zones/StrandofAncients.cs create mode 100644 Game/BattleGrounds/Zones/TempleofKotmogu.cs create mode 100644 Game/BattleGrounds/Zones/TwinPeaks.cs create mode 100644 Game/BattleGrounds/Zones/WarsongGluch.cs create mode 100644 Game/BattlePets/BattlePetManager.cs create mode 100644 Game/BlackMarket/BlackMarketEntry.cs create mode 100644 Game/BlackMarket/BlackMarketManager.cs create mode 100644 Game/Calendar/CalendarManager.cs create mode 100644 Game/Chat/Channels/Channel.cs create mode 100644 Game/Chat/Channels/ChannelAppenders.cs create mode 100644 Game/Chat/Channels/ChannelManager.cs create mode 100644 Game/Chat/CommandAttribute.cs create mode 100644 Game/Chat/CommandHandler.cs create mode 100644 Game/Chat/CommandManager.cs create mode 100644 Game/Chat/Commands/AccountCommands.cs create mode 100644 Game/Chat/Commands/AhBotCommands.cs create mode 100644 Game/Chat/Commands/ArenaCommands.cs create mode 100644 Game/Chat/Commands/BNetAccountCommands.cs create mode 100644 Game/Chat/Commands/BanCommands.cs create mode 100644 Game/Chat/Commands/BattleFieldCommands.cs create mode 100644 Game/Chat/Commands/CastCommands.cs create mode 100644 Game/Chat/Commands/CharacterCommands.cs create mode 100644 Game/Chat/Commands/CheatCommands.cs create mode 100644 Game/Chat/Commands/DebugCommands.cs create mode 100644 Game/Chat/Commands/DeserterCommands.cs create mode 100644 Game/Chat/Commands/DisableCommands.cs create mode 100644 Game/Chat/Commands/EventCommands.cs create mode 100644 Game/Chat/Commands/GMCommands.cs create mode 100644 Game/Chat/Commands/GameObjectCommands.cs create mode 100644 Game/Chat/Commands/GoCommands.cs create mode 100644 Game/Chat/Commands/GroupCommands.cs create mode 100644 Game/Chat/Commands/GuildCommands.cs create mode 100644 Game/Chat/Commands/HonorCommands.cs create mode 100644 Game/Chat/Commands/InstanceCommands.cs create mode 100644 Game/Chat/Commands/LFGCommands.cs create mode 100644 Game/Chat/Commands/LearnCommands.cs create mode 100644 Game/Chat/Commands/ListCommands.cs create mode 100644 Game/Chat/Commands/LookupCommands.cs create mode 100644 Game/Chat/Commands/MMapsCommands.cs create mode 100644 Game/Chat/Commands/MessageCommands.cs create mode 100644 Game/Chat/Commands/MiscCommands.cs create mode 100644 Game/Chat/Commands/ModifyCommands.cs create mode 100644 Game/Chat/Commands/NPCCommands.cs create mode 100644 Game/Chat/Commands/PetCommands.cs create mode 100644 Game/Chat/Commands/QuestCommands.cs create mode 100644 Game/Chat/Commands/RbacCommands.cs create mode 100644 Game/Chat/Commands/ReloadCommand.cs create mode 100644 Game/Chat/Commands/ResetCommands.cs create mode 100644 Game/Chat/Commands/SceneCommands.cs create mode 100644 Game/Chat/Commands/SendCommands.cs create mode 100644 Game/Chat/Commands/ServerCommands.cs create mode 100644 Game/Chat/Commands/SpellCommands.cs create mode 100644 Game/Chat/Commands/TeleCommands.cs create mode 100644 Game/Chat/Commands/TicketCommands.cs create mode 100644 Game/Chat/Commands/TitleCommands.cs create mode 100644 Game/Chat/Commands/WPCommands.cs create mode 100644 Game/Collision/BoundingIntervalHierarchy.cs create mode 100644 Game/Collision/BoundingIntervalHierarchyWrap.cs create mode 100644 Game/Collision/Callbacks.cs create mode 100644 Game/Collision/DynamicTree.cs create mode 100644 Game/Collision/Management/VMapManager.cs create mode 100644 Game/Collision/Maps/MapTree.cs create mode 100644 Game/Collision/Models/GameObjectModel.cs create mode 100644 Game/Collision/Models/IModel.cs create mode 100644 Game/Collision/Models/ModelInstance.cs create mode 100644 Game/Collision/Models/WorldModel.cs create mode 100644 Game/Collision/RegularGrid2D.cs create mode 100644 Game/Combat/Events.cs create mode 100644 Game/Combat/HostileRegManager.cs create mode 100644 Game/Combat/ThreatManager.cs create mode 100644 Game/Conditions/Condition.cs create mode 100644 Game/Conditions/ConditionManager.cs create mode 100644 Game/Conditions/DisableManager.cs create mode 100644 Game/DataStorage/AreaTriggerDataStorage.cs create mode 100644 Game/DataStorage/CharacterTemplateDataStorage.cs create mode 100644 Game/DataStorage/CliDB.cs create mode 100644 Game/DataStorage/ClientReader/CliDBReader.cs create mode 100644 Game/DataStorage/ClientReader/DB6Meta.cs create mode 100644 Game/DataStorage/ClientReader/DB6MetaOld.cs create mode 100644 Game/DataStorage/ClientReader/DB6Storage.cs create mode 100644 Game/DataStorage/ClientReader/GameTables.cs create mode 100644 Game/DataStorage/ConversationDataStorage.cs create mode 100644 Game/DataStorage/DB2Manager.cs create mode 100644 Game/DataStorage/M2Storage.cs create mode 100644 Game/DataStorage/Structs/A_Records.cs create mode 100644 Game/DataStorage/Structs/B_Records.cs create mode 100644 Game/DataStorage/Structs/C_Records.cs create mode 100644 Game/DataStorage/Structs/D_Records.cs create mode 100644 Game/DataStorage/Structs/E_Records.cs create mode 100644 Game/DataStorage/Structs/F_Records.cs create mode 100644 Game/DataStorage/Structs/G_Records.cs create mode 100644 Game/DataStorage/Structs/GameTablesRecords.cs create mode 100644 Game/DataStorage/Structs/H_Records.cs create mode 100644 Game/DataStorage/Structs/I_Records.cs create mode 100644 Game/DataStorage/Structs/K_Records.cs create mode 100644 Game/DataStorage/Structs/L_Records.cs create mode 100644 Game/DataStorage/Structs/M2Structure.cs create mode 100644 Game/DataStorage/Structs/M_Records.cs create mode 100644 Game/DataStorage/Structs/MiscStructs.cs create mode 100644 Game/DataStorage/Structs/N_Records.cs create mode 100644 Game/DataStorage/Structs/O_Records.cs create mode 100644 Game/DataStorage/Structs/P_Records.cs create mode 100644 Game/DataStorage/Structs/Q_Records.cs create mode 100644 Game/DataStorage/Structs/R_Records.cs create mode 100644 Game/DataStorage/Structs/S_Records.cs create mode 100644 Game/DataStorage/Structs/T_Records.cs create mode 100644 Game/DataStorage/Structs/U_Records.cs create mode 100644 Game/DataStorage/Structs/V_Records.cs create mode 100644 Game/DataStorage/Structs/W_Records.cs create mode 100644 Game/DungeonFinding/LFGGroupData.cs create mode 100644 Game/DungeonFinding/LFGManager.cs create mode 100644 Game/DungeonFinding/LFGPlayerData.cs create mode 100644 Game/DungeonFinding/LFGQueue.cs create mode 100644 Game/DungeonFinding/LFGScripts.cs create mode 100644 Game/Entities/AreaTrigger/AreaTrigger.cs create mode 100644 Game/Entities/AreaTrigger/AreaTriggerTemplate.cs create mode 100644 Game/Entities/Conversation.cs create mode 100644 Game/Entities/Corpse.cs create mode 100644 Game/Entities/Creature/Creature.Fields.cs create mode 100644 Game/Entities/Creature/Creature.cs create mode 100644 Game/Entities/Creature/CreatureData.cs create mode 100644 Game/Entities/Creature/CreatureGroups.cs create mode 100644 Game/Entities/Creature/Gossip.cs create mode 100644 Game/Entities/DynamicObject.cs create mode 100644 Game/Entities/GameObject/GameObject.cs create mode 100644 Game/Entities/GameObject/GameObjectData.cs create mode 100644 Game/Entities/Item/Bag.cs create mode 100644 Game/Entities/Item/Item.cs create mode 100644 Game/Entities/Item/ItemEnchantment.cs create mode 100644 Game/Entities/Item/ItemTemplate.cs create mode 100644 Game/Entities/Object/ObjectGuid.cs create mode 100644 Game/Entities/Object/Position.cs create mode 100644 Game/Entities/Object/Update/UpdateData.cs create mode 100644 Game/Entities/Object/Update/UpdateMask.cs create mode 100644 Game/Entities/Object/WorldObject.cs create mode 100644 Game/Entities/Pet.cs create mode 100644 Game/Entities/Player/CinematicManager.cs create mode 100644 Game/Entities/Player/CollectionManager.cs create mode 100644 Game/Entities/Player/KillRewarder.cs create mode 100644 Game/Entities/Player/Player.Achievement.cs create mode 100644 Game/Entities/Player/Player.Combat.cs create mode 100644 Game/Entities/Player/Player.DB.cs create mode 100644 Game/Entities/Player/Player.Fields.cs create mode 100644 Game/Entities/Player/Player.Groups.cs create mode 100644 Game/Entities/Player/Player.Items.cs create mode 100644 Game/Entities/Player/Player.Map.cs create mode 100644 Game/Entities/Player/Player.PvP.cs create mode 100644 Game/Entities/Player/Player.Quest.cs create mode 100644 Game/Entities/Player/Player.Spells.cs create mode 100644 Game/Entities/Player/Player.Talents.cs create mode 100644 Game/Entities/Player/Player.cs create mode 100644 Game/Entities/Player/PlayerTaxi.cs create mode 100644 Game/Entities/Player/RestMgr.cs create mode 100644 Game/Entities/Player/SceneMgr.cs create mode 100644 Game/Entities/Player/SocialMgr.cs create mode 100644 Game/Entities/Player/TradeData.cs create mode 100644 Game/Entities/StatSystem.cs create mode 100644 Game/Entities/Taxi/Graph.cs create mode 100644 Game/Entities/Taxi/TaxiPathGraph.cs create mode 100644 Game/Entities/TemporarySummon.cs create mode 100644 Game/Entities/Totem.cs create mode 100644 Game/Entities/Transport.cs create mode 100644 Game/Entities/Unit/CharmInfo.cs create mode 100644 Game/Entities/Unit/Comparer.cs create mode 100644 Game/Entities/Unit/Unit.Combat.cs create mode 100644 Game/Entities/Unit/Unit.Fields.cs create mode 100644 Game/Entities/Unit/Unit.Movement.cs create mode 100644 Game/Entities/Unit/Unit.Pets.cs create mode 100644 Game/Entities/Unit/Unit.Spells.cs create mode 100644 Game/Entities/Unit/Unit.cs create mode 100644 Game/Entities/Vehicle.cs create mode 100644 Game/Events/GameEventManager.cs create mode 100644 Game/Game.csproj create mode 100644 Game/Garrisons/GarrisonManager.cs create mode 100644 Game/Garrisons/GarrisonMap.cs create mode 100644 Game/Garrisons/Garrisons.cs create mode 100644 Game/Globals/Global.cs create mode 100644 Game/Globals/ObjectAccessor.cs create mode 100644 Game/Globals/ObjectManager.cs create mode 100644 Game/Groups/Group.cs create mode 100644 Game/Groups/GroupManager.cs create mode 100644 Game/Groups/GroupReference.cs create mode 100644 Game/Guilds/Guild.cs create mode 100644 Game/Guilds/GuildFinderManager.cs create mode 100644 Game/Guilds/GuildManager.cs create mode 100644 Game/Handlers/ArenaHandler.cs create mode 100644 Game/Handlers/ArtifactHandler.cs create mode 100644 Game/Handlers/AuctionHandler.cs create mode 100644 Game/Handlers/AuthenticationHandler.cs create mode 100644 Game/Handlers/BankHandler.cs create mode 100644 Game/Handlers/BattleFieldHandler.cs create mode 100644 Game/Handlers/BattleGroundHandler.cs create mode 100644 Game/Handlers/BattlePetHandler.cs create mode 100644 Game/Handlers/BattlenetHandler.cs create mode 100644 Game/Handlers/BlackMarketHandlers.cs create mode 100644 Game/Handlers/CalendarHandler.cs create mode 100644 Game/Handlers/ChannelHandler.cs create mode 100644 Game/Handlers/CharacterHandler.cs create mode 100644 Game/Handlers/ChatHandler.cs create mode 100644 Game/Handlers/CollectionsHandler.cs create mode 100644 Game/Handlers/CombatHandler.cs create mode 100644 Game/Handlers/DuelHandler.cs create mode 100644 Game/Handlers/GarrisonHandler.cs create mode 100644 Game/Handlers/GroupHandler.cs create mode 100644 Game/Handlers/GuildFinderHandler.cs create mode 100644 Game/Handlers/GuildHandler.cs create mode 100644 Game/Handlers/HotfixHandler.cs create mode 100644 Game/Handlers/InspectHandler.cs create mode 100644 Game/Handlers/ItemHandler.cs create mode 100644 Game/Handlers/LFGHandler.cs create mode 100644 Game/Handlers/LogoutHandler.cs create mode 100644 Game/Handlers/LootHandler.cs create mode 100644 Game/Handlers/MailHandler.cs create mode 100644 Game/Handlers/MiscHandler.cs create mode 100644 Game/Handlers/MovementHandler.cs create mode 100644 Game/Handlers/NPCHandler.cs create mode 100644 Game/Handlers/PetHandler.cs create mode 100644 Game/Handlers/PetitionsHandler.cs create mode 100644 Game/Handlers/QueryHandler.cs create mode 100644 Game/Handlers/QuestHandler.cs create mode 100644 Game/Handlers/RAFHandler.cs create mode 100644 Game/Handlers/ScenarioHandler.cs create mode 100644 Game/Handlers/SceneHandler.cs create mode 100644 Game/Handlers/SkillHandler.cs create mode 100644 Game/Handlers/SocialHandler.cs create mode 100644 Game/Handlers/SpellHandler.cs create mode 100644 Game/Handlers/TaxiHandler.cs create mode 100644 Game/Handlers/TicketHandler.cs create mode 100644 Game/Handlers/TimeHandler.cs create mode 100644 Game/Handlers/TokenHandler.cs create mode 100644 Game/Handlers/ToyHandler.cs create mode 100644 Game/Handlers/TradeHandler.cs create mode 100644 Game/Handlers/TransmogrificationHandler.cs create mode 100644 Game/Handlers/VehicleHandler.cs create mode 100644 Game/Handlers/VoidStorageHandler.cs create mode 100644 Game/Loot/Loot.cs create mode 100644 Game/Loot/LootManager.cs create mode 100644 Game/Loot/LootStorage.cs create mode 100644 Game/Mails/Mail.cs create mode 100644 Game/Mails/MailDraft.cs create mode 100644 Game/Maps/AreaBoundary.cs create mode 100644 Game/Maps/Cell.cs create mode 100644 Game/Maps/Grid.cs create mode 100644 Game/Maps/GridDefines.cs create mode 100644 Game/Maps/GridMap.cs create mode 100644 Game/Maps/GridNotifiers.cs create mode 100644 Game/Maps/Instances/InstanceSaveManager.cs create mode 100644 Game/Maps/Instances/InstanceScript.cs create mode 100644 Game/Maps/Instances/MapInstance.cs create mode 100644 Game/Maps/MMapManager.cs create mode 100644 Game/Maps/Map.cs create mode 100644 Game/Maps/MapManager.cs create mode 100644 Game/Maps/MapUpdater.cs create mode 100644 Game/Maps/ObjectGridLoader.cs create mode 100644 Game/Maps/TransportManager.cs create mode 100644 Game/Maps/ZoneScript.cs create mode 100644 Game/Miscellaneous/Formulas.cs create mode 100644 Game/Movement/Generators/ConfusedGenerator.cs create mode 100644 Game/Movement/Generators/FleeingGenerator.cs create mode 100644 Game/Movement/Generators/HomeMovement.cs create mode 100644 Game/Movement/Generators/IdleMovement.cs create mode 100644 Game/Movement/Generators/MovementGenerators.cs create mode 100644 Game/Movement/Generators/PathGenerator.cs create mode 100644 Game/Movement/Generators/PointMovement.cs create mode 100644 Game/Movement/Generators/RandomMovement.cs create mode 100644 Game/Movement/Generators/SplineChainMovementGenerator.cs create mode 100644 Game/Movement/Generators/TargetMovement.cs create mode 100644 Game/Movement/Generators/WaypointMovement.cs create mode 100644 Game/Movement/MotionMaster.cs create mode 100644 Game/Movement/MoveSpline.cs create mode 100644 Game/Movement/MoveSplineFlags.cs create mode 100644 Game/Movement/MoveSplineInit.cs create mode 100644 Game/Movement/MoveSplineInitArgs.cs create mode 100644 Game/Movement/Spline.cs create mode 100644 Game/Movement/WaypointManager.cs create mode 100644 Game/Network/Packet.cs create mode 100644 Game/Network/PacketLog.cs create mode 100644 Game/Network/PacketManager.cs create mode 100644 Game/Network/PacketUtilities.cs create mode 100644 Game/Network/Packets/AchievementPackets.cs create mode 100644 Game/Network/Packets/AreaTriggerPackets.cs create mode 100644 Game/Network/Packets/ArtifactPackets.cs create mode 100644 Game/Network/Packets/AuctionHousePackets.cs create mode 100644 Game/Network/Packets/AuthenticationPackets.cs create mode 100644 Game/Network/Packets/BankPackets.cs create mode 100644 Game/Network/Packets/BattleGroundPackets.cs create mode 100644 Game/Network/Packets/BattlePetPackets.cs create mode 100644 Game/Network/Packets/BattlefieldPackets.cs create mode 100644 Game/Network/Packets/BattlenetPackets.cs create mode 100644 Game/Network/Packets/BlackMarketPackets.cs create mode 100644 Game/Network/Packets/CalendarPackets.cs create mode 100644 Game/Network/Packets/ChannelPackets.cs create mode 100644 Game/Network/Packets/CharacterPackets.cs create mode 100644 Game/Network/Packets/ChatPackets.cs create mode 100644 Game/Network/Packets/ClientConfigPackets.cs create mode 100644 Game/Network/Packets/CollectionPackets.cs create mode 100644 Game/Network/Packets/CombatLogPackets.cs create mode 100644 Game/Network/Packets/CombatPackets.cs create mode 100644 Game/Network/Packets/DuelPackets.cs create mode 100644 Game/Network/Packets/EquipmentPackets.cs create mode 100644 Game/Network/Packets/GameObjectPackets.cs create mode 100644 Game/Network/Packets/GarrisonPackets.cs create mode 100644 Game/Network/Packets/GuildFinderPackets.cs create mode 100644 Game/Network/Packets/GuildPackets.cs create mode 100644 Game/Network/Packets/HotfixPackets.cs create mode 100644 Game/Network/Packets/InspectPackets.cs create mode 100644 Game/Network/Packets/InstancePackets.cs create mode 100644 Game/Network/Packets/ItemPackets.cs create mode 100644 Game/Network/Packets/LFGPackets.cs create mode 100644 Game/Network/Packets/LootPackets.cs create mode 100644 Game/Network/Packets/MailPackets.cs create mode 100644 Game/Network/Packets/MiscPackets.cs create mode 100644 Game/Network/Packets/MovementPackets.cs create mode 100644 Game/Network/Packets/NPCPackets.cs create mode 100644 Game/Network/Packets/PartyPackets.cs create mode 100644 Game/Network/Packets/PetPackets.cs create mode 100644 Game/Network/Packets/PetitionPackets.cs create mode 100644 Game/Network/Packets/QueryPackets.cs create mode 100644 Game/Network/Packets/QuestPackets.cs create mode 100644 Game/Network/Packets/ReferAFriendPackets.cs create mode 100644 Game/Network/Packets/ReputationPackets.cs create mode 100644 Game/Network/Packets/ScenarioPackets.cs create mode 100644 Game/Network/Packets/ScenePackets.cs create mode 100644 Game/Network/Packets/SocialPackets.cs create mode 100644 Game/Network/Packets/SpellPackets.cs create mode 100644 Game/Network/Packets/SystemPackets.cs create mode 100644 Game/Network/Packets/TalentPackets.cs create mode 100644 Game/Network/Packets/TaxiPackets.cs create mode 100644 Game/Network/Packets/TicketPackets.cs create mode 100644 Game/Network/Packets/TokenPackets.cs create mode 100644 Game/Network/Packets/TotemPackets.cs create mode 100644 Game/Network/Packets/ToyPackets.cs create mode 100644 Game/Network/Packets/TradePackets.cs create mode 100644 Game/Network/Packets/TransmogrificationPackets.cs create mode 100644 Game/Network/Packets/UpdatePackets.cs create mode 100644 Game/Network/Packets/VehiclePackets.cs create mode 100644 Game/Network/Packets/VoidStoragePackets.cs create mode 100644 Game/Network/Packets/WardenPackets.cs create mode 100644 Game/Network/Packets/WhoPackets.cs create mode 100644 Game/Network/Packets/WorldStatePackets.cs create mode 100644 Game/Network/WorldSocket.cs create mode 100644 Game/Network/WorldSocketManager.cs create mode 100644 Game/OutdoorPVP/OutdoorPvP.cs create mode 100644 Game/OutdoorPVP/OutdoorPvPManager.cs create mode 100644 Game/OutdoorPVP/Zones/HellfirePeninsulaPvP.cs create mode 100644 Game/OutdoorPVP/Zones/NagrandPvP.cs create mode 100644 Game/Pools/PoolManager.cs create mode 100644 Game/Properties/AssemblyInfo.cs create mode 100644 Game/Quest/Quest.cs create mode 100644 Game/Reputation/ReputationManager.cs create mode 100644 Game/Scenarios/InstanceScenario.cs create mode 100644 Game/Scenarios/Scenario.cs create mode 100644 Game/Scenarios/ScenarioManager.cs create mode 100644 Game/Scripting/CoreScripts.cs create mode 100644 Game/Scripting/ScriptManager.cs create mode 100644 Game/Scripting/SpellScript.cs create mode 100644 Game/Server/WorldConfig.cs create mode 100644 Game/Server/WorldManager.cs create mode 100644 Game/Server/WorldSession.cs create mode 100644 Game/Services/GameUtilitiesService.cs create mode 100644 Game/Services/ServiceManager.cs create mode 100644 Game/Spells/Auras/Aura.cs create mode 100644 Game/Spells/Auras/AuraEffect.cs create mode 100644 Game/Spells/Skills/SkillDiscovery.cs create mode 100644 Game/Spells/Skills/SkillExtraItems.cs create mode 100644 Game/Spells/Spell.cs create mode 100644 Game/Spells/SpellCastTargets.cs create mode 100644 Game/Spells/SpellEffects.cs create mode 100644 Game/Spells/SpellHistory.cs create mode 100644 Game/Spells/SpellInfo.cs create mode 100644 Game/Spells/SpellManager.cs create mode 100644 Game/SupportSystem/SupportManager.cs create mode 100644 Game/SupportSystem/SupportTickets.cs create mode 100644 Game/Text/ChatTextBuilder.cs create mode 100644 Game/Text/CreatureTextManager.cs create mode 100644 Game/Tools/CharacterDatabaseCleaner.cs create mode 100644 Game/Warden/Warden.cs create mode 100644 Game/Warden/WardenKeyGeneration.cs create mode 100644 Game/Warden/WardenModuleWin.cs create mode 100644 Game/Warden/WardenWin.cs create mode 100644 Game/Warden/WargenCheckManager.cs create mode 100644 Game/Weather/WeatherManager.cs create mode 100644 LICENSE create mode 100644 Libs/Google.Protobuf.dll create mode 100644 Libs/Logo.png create mode 100644 Libs/MySql.Data.dll create mode 100644 README.md create mode 100644 Scripts/EasternKingdoms/Deadmines/InstanceDeadmines.cs create mode 100644 Scripts/EasternKingdoms/EversongWoods.cs create mode 100644 Scripts/EasternKingdoms/Karazhan/AttumenMidnight.cs create mode 100644 Scripts/EasternKingdoms/Karazhan/ChessEvent.cs create mode 100644 Scripts/EasternKingdoms/Karazhan/Curator.cs create mode 100644 Scripts/EasternKingdoms/Karazhan/InstanceKarazhan.cs create mode 100644 Scripts/EasternKingdoms/Karazhan/Moroes.cs create mode 100644 Scripts/EasternKingdoms/Karazhan/OperaEvent.cs create mode 100644 Scripts/EasternKingdoms/ScarletEnclave/Chapter1.cs create mode 100644 Scripts/EasternKingdoms/TheStockade/BossHogger.cs create mode 100644 Scripts/EasternKingdoms/TheStockade/InstanceTheStockade.cs create mode 100644 Scripts/Kalimdor/Durotar.cs create mode 100644 Scripts/Kalimdor/RageFireChasm.cs create mode 100644 Scripts/Northrend/AzjolNerub/Ahnkahet/BossAmanitar.cs create mode 100644 Scripts/Northrend/AzjolNerub/Ahnkahet/BossElderNadox.cs create mode 100644 Scripts/Northrend/AzjolNerub/Ahnkahet/BossHeraldVolazj.cs create mode 100644 Scripts/Northrend/AzjolNerub/Ahnkahet/BossJedogaShadowseeker.cs create mode 100644 Scripts/Northrend/AzjolNerub/Ahnkahet/BossPrinceTaldaram.cs create mode 100644 Scripts/Northrend/AzjolNerub/Ahnkahet/InstanceAhnahet.cs create mode 100644 Scripts/Northrend/AzjolNerub/AzjolNerub/BossAnubarak.cs create mode 100644 Scripts/Northrend/AzjolNerub/AzjolNerub/BossKrikthirTheGatewatcher.cs create mode 100644 Scripts/Northrend/AzjolNerub/AzjolNerub/BossNadronox.cs create mode 100644 Scripts/Northrend/AzjolNerub/AzjolNerub/InstanceAzjolNerub.cs create mode 100644 Scripts/Northrend/CrusadersColiseum/TrialOfTheChampion/GrandChampions.cs create mode 100644 Scripts/Northrend/CrusadersColiseum/TrialOfTheChampion/InstanceTrialOfTheChampion.cs create mode 100644 Scripts/Northrend/CrusadersColiseum/TrialOfTheChampion/TrialOfTheChampion.cs create mode 100644 Scripts/Northrend/CrusadersColiseum/TrialOfTheCrusader/LordJaraxxus.cs create mode 100644 Scripts/Northrend/CrusadersColiseum/TrialOfTheCrusader/NorthrendBeasts.cs create mode 100644 Scripts/Northrend/CrusadersColiseum/TrialOfTheCrusader/TrialOfTheCrusader.cs create mode 100644 Scripts/Northrend/CrusadersColiseum/TrialOfTheCrusader/TrialOfTheCrusaderConst.cs create mode 100644 Scripts/Northrend/Dalaran.cs create mode 100644 Scripts/Northrend/DraktharonKeep/BossKingDred.cs create mode 100644 Scripts/Northrend/DraktharonKeep/BossNovos.cs create mode 100644 Scripts/Northrend/DraktharonKeep/BossTharonJa.cs create mode 100644 Scripts/Northrend/DraktharonKeep/BossTrollgore.cs create mode 100644 Scripts/Northrend/DraktharonKeep/InstanceDrakTharonKeep.cs create mode 100644 Scripts/Northrend/Gundrak/BossDrakkariColossus.cs create mode 100644 Scripts/Northrend/Gundrak/BossEck.cs create mode 100644 Scripts/Northrend/Gundrak/InstanceGundrak.cs create mode 100644 Scripts/Northrend/IcecrownCitadel/GunshipBattle.cs create mode 100644 Scripts/Northrend/IcecrownCitadel/IcecrownCitadel.cs create mode 100644 Scripts/Northrend/IcecrownCitadel/IcecrownCitadelConst.cs create mode 100644 Scripts/Northrend/IcecrownCitadel/IcecrownCitadelTeleport.cs create mode 100644 Scripts/Northrend/IcecrownCitadel/InstanceIcecrownCitadel.cs create mode 100644 Scripts/Northrend/IcecrownCitadel/LadyDeathwhisper.cs create mode 100644 Scripts/Northrend/IcecrownCitadel/LordMarrowgar.cs create mode 100644 Scripts/Northrend/IcecrownCitadel/ProfessorPutricide.cs create mode 100644 Scripts/Northrend/IcecrownCitadel/Sindragosa.cs create mode 100644 Scripts/Northrend/IcecrownCitadel/TheLichKing.cs create mode 100644 Scripts/Northrend/IcecrownCitadel/ValithriaDreamwalker.cs create mode 100644 Scripts/Northrend/Naxxramas/BossAnubrekhan.cs create mode 100644 Scripts/Northrend/Nexus/EyeOfEternity/BossMalygos.cs create mode 100644 Scripts/Northrend/Nexus/EyeOfEternity/EyeOfEternity.cs create mode 100644 Scripts/Northrend/Nexus/Nexus/BossAnomalus.cs create mode 100644 Scripts/Northrend/Nexus/Nexus/BossKeristrasza.cs create mode 100644 Scripts/Northrend/Nexus/Nexus/BossMagusTelestra.cs create mode 100644 Scripts/Northrend/Nexus/Nexus/BossNexusCommanders.cs create mode 100644 Scripts/Northrend/Nexus/Nexus/BossOrmorok.cs create mode 100644 Scripts/Northrend/Nexus/Nexus/InstanceNexus.cs create mode 100644 Scripts/Northrend/Nexus/Oculus/InstanceOculus.cs create mode 100644 Scripts/Northrend/Ulduar/AlgalonTheObserver.cs create mode 100644 Scripts/Northrend/Ulduar/FlameLeviathan.cs create mode 100644 Scripts/Northrend/Ulduar/Mimiron.cs create mode 100644 Scripts/Northrend/Ulduar/Razorscale.cs create mode 100644 Scripts/Northrend/Ulduar/UlduarConst.cs create mode 100644 Scripts/Northrend/Ulduar/UlduarInstance.cs create mode 100644 Scripts/Northrend/Ulduar/Xt002.cs create mode 100644 Scripts/Northrend/Ulduar/YoggSaron.cs create mode 100644 Scripts/Northrend/VioletHold/InstanceVioletHold.cs create mode 100644 Scripts/Northrend/VioletHold/VioletHold.cs create mode 100644 Scripts/Northrend/WinterGrasp.cs create mode 100644 Scripts/Outlands/HellfirePeninsula.cs create mode 100644 Scripts/Outlands/Zangarmarsh.cs create mode 100644 Scripts/Pets/DeathKinght.cs create mode 100644 Scripts/Pets/Generic.cs create mode 100644 Scripts/Pets/Hunter.cs create mode 100644 Scripts/Pets/Mage.cs create mode 100644 Scripts/Pets/Priest.cs create mode 100644 Scripts/Pets/Shaman.cs create mode 100644 Scripts/Properties/AssemblyInfo.cs create mode 100644 Scripts/Scripts.csproj create mode 100644 Scripts/Spells/DeathKnight.cs create mode 100644 Scripts/Spells/DemonHunter.cs create mode 100644 Scripts/Spells/Druid.cs create mode 100644 Scripts/Spells/Generic.cs create mode 100644 Scripts/Spells/Holiday.cs create mode 100644 Scripts/Spells/Hunter.cs create mode 100644 Scripts/Spells/Items.cs create mode 100644 Scripts/Spells/Mage.cs create mode 100644 Scripts/Spells/Monk.cs create mode 100644 Scripts/Spells/Paladin.cs create mode 100644 Scripts/Spells/Priest.cs create mode 100644 Scripts/Spells/Quest.cs create mode 100644 Scripts/Spells/Rogue.cs create mode 100644 Scripts/Spells/Shaman.cs create mode 100644 Scripts/Spells/Warlock.cs create mode 100644 Scripts/Spells/Warrior.cs create mode 100644 Scripts/World/Achievements.cs create mode 100644 Scripts/World/AreaTrigger.cs create mode 100644 Scripts/World/BossEmeraldDragons.cs create mode 100644 Scripts/World/DuelReset.cs create mode 100644 Scripts/World/GameObjects.cs create mode 100644 Scripts/World/Guards.cs create mode 100644 Scripts/World/ItemScripts.cs create mode 100644 Scripts/World/MobGenericCreature.cs create mode 100644 Scripts/World/NpcProfessions.cs create mode 100644 Scripts/World/NpcSpecial.cs create mode 100644 Scripts/World/SceneScripts.cs create mode 100644 THANKS create mode 100644 WorldServer/Blue.ico create mode 100644 WorldServer/Properties/AssemblyInfo.cs create mode 100644 WorldServer/Server.cs create mode 100644 WorldServer/WorldServer.conf create mode 100644 WorldServer/WorldServer.csproj diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 000000000..1ff0c4230 --- /dev/null +++ b/.gitattributes @@ -0,0 +1,63 @@ +############################################################################### +# Set default behavior to automatically normalize line endings. +############################################################################### +* text=auto + +############################################################################### +# Set default behavior for command prompt diff. +# +# This is need for earlier builds of msysgit that does not have it on by +# default for csharp files. +# Note: This is only used by command line +############################################################################### +#*.cs diff=csharp + +############################################################################### +# Set the merge driver for project and solution files +# +# Merging from the command prompt will add diff markers to the files if there +# are conflicts (Merging from VS is not affected by the settings below, in VS +# the diff markers are never inserted). Diff markers may cause the following +# file extensions to fail to load in VS. An alternative would be to treat +# these files as binary and thus will always conflict and require user +# intervention with every merge. To do so, just uncomment the entries below +############################################################################### +#*.sln merge=binary +#*.csproj merge=binary +#*.vbproj merge=binary +#*.vcxproj merge=binary +#*.vcproj merge=binary +#*.dbproj merge=binary +#*.fsproj merge=binary +#*.lsproj merge=binary +#*.wixproj merge=binary +#*.modelproj merge=binary +#*.sqlproj merge=binary +#*.wwaproj merge=binary + +############################################################################### +# behavior for image files +# +# image files are treated as binary by default. +############################################################################### +#*.jpg binary +#*.png binary +#*.gif binary + +############################################################################### +# diff behavior for common document formats +# +# Convert binary document formats to text before diffing them. This feature +# is only available from the command line. Turn it on by uncommenting the +# entries below. +############################################################################### +#*.doc diff=astextplain +#*.DOC diff=astextplain +#*.docx diff=astextplain +#*.DOCX diff=astextplain +#*.dot diff=astextplain +#*.DOT diff=astextplain +#*.pdf diff=astextplain +#*.PDF diff=astextplain +#*.rtf diff=astextplain +#*.RTF diff=astextplain diff --git a/.gitignore b/.gitignore new file mode 100644 index 000000000..8e826767d --- /dev/null +++ b/.gitignore @@ -0,0 +1,159 @@ +## Ignore Visual Studio temporary files, build results, and +## files generated by popular Visual Studio add-ons. + +# User-specific files +*.suo +*.user +*.sln.docstates + +# Build results + +[Dd]ebug/ +[Rr]elease/ +x64/ +build/ +[Bb]in/ +[Oo]bj/ + +# Enable "build/" folder in the NuGet Packages folder since NuGet packages use it for MSBuild targets +!packages/*/build/ + +# MSTest test Results +[Tt]est[Rr]esult*/ +[Bb]uild[Ll]og.* +UnitTestResults.html + +*_i.c +*_p.c +*.ilk +*.meta +*.obj +*.pch +*.pdb +*.pgc +*.pgd +*.rsp +*.sbr +*.tlb +*.tli +*.tlh +*.tmp +*.tmp_proj +*.log +*.vspscc +*.vssscc +.builds +*.pidb +*.log +*.scc + +# Visual Studio cache files +*.sln.ide/ + +# Visual C++ cache files +ipch/ +*.aps +*.ncb +*.opensdf +*.sdf +*.cachefile + +# Visual Studio profiler +*.psess +*.vsp +*.vspx + +# Guidance Automation Toolkit +*.gpState + +# ReSharper is a .NET coding add-in +_ReSharper*/ +*.[Rr]e[Ss]harper + +# TeamCity is a build add-in +_TeamCity* + +# DotCover is a Code Coverage Tool +*.dotCover + +# NCrunch +*.ncrunch* +.*crunch*.local.xml + +# Installshield output folder +[Ee]xpress/ + +# DocProject is a documentation generator add-in +DocProject/buildhelp/ +DocProject/Help/*.HxT +DocProject/Help/*.HxC +DocProject/Help/*.hhc +DocProject/Help/*.hhk +DocProject/Help/*.hhp +DocProject/Help/Html2 +DocProject/Help/html + +# Click-Once directory +publish/ + +# Publish Web Output +*.Publish.xml + +# NuGet Packages Directory +packages/ + +# Windows Azure Build Output +csx +*.build.csdef + +# Windows Store app package directory +AppPackages/ + +# Others +sql/ +*.Cache +ClientBin/ +[Ss]tyle[Cc]op.* +~$* +*~ +*.dbmdl +*.[Pp]ublish.xml +*.pfx +*.publishsettings + +# RIA/Silverlight projects +Generated_Code/ + +# Backup & report files from converting an old project file to a newer +# Visual Studio version. Backup files are not needed, because we have git ;-) +_UpgradeReport_Files/ +Backup*/ +UpgradeLog*.XML +UpgradeLog*.htm + +# SQL Server files +App_Data/*.mdf +App_Data/*.ldf + + +#LightSwitch generated files +GeneratedArtifacts/ +_Pvt_Extensions/ +ModelManifest.xml + +# ========================= +# Windows detritus +# ========================= + +# Windows image file caches +Thumbs.db +ehthumbs.db + +# Folder config file +Desktop.ini + +# Recycle Bin used on file shares +$RECYCLE.BIN/ + +# Mac desktop service store files +.DS_Store \ No newline at end of file diff --git a/BNetServer/BNetServer.conf b/BNetServer/BNetServer.conf new file mode 100644 index 000000000..82c08bce7 --- /dev/null +++ b/BNetServer/BNetServer.conf @@ -0,0 +1,242 @@ +################################################################################################### +# Cypher BNet Server Configuration File # +################################################################################################### + +################################################################################################### +# BNet SERVER SETTINGS +# +# LogsDir +# Description: Logs directory setting. +# Important: LogsDir needs to be quoted, as the string might contain space characters. +# Logs directory must exists, or log file creation will be disabled. +# Default: "" - (Log files will be stored in the current path) + +LogsDir = "./Logs" + +# +# BattlenetPort +# Description: TCP port to reach the auth server for battle.net connections. +# Default: 1119 + +BattlenetPort = 1119 + +# +# LoginREST.Port +# Description: TCP port to reach the REST login method. +# Default: 8081 +# +# LoginREST.ExternalAddress +# Description: IP address sent to clients connecting from outside the network where bnetserver runs +# +# LoginREST.LocalAddress +# Description: IP address sent to clients connecting from inside the network where bnetserver runs +# + +LoginREST.Port = 8081 +LoginREST.ExternalAddress=127.0.0.1 +LoginREST.LocalAddress=127.0.0.1 + +# +# BindIP +# Description: Bind auth server to IP/hostname +# Default: "0.0.0.0" - (Bind to all IPs on the system) + +BindIP = "0.0.0.0" + +# +# RealmsStateUpdateDelay +# Description: Time (in seconds) between realm list updates. +# Default: 10 +# 0 - (Disabled) + +RealmsStateUpdateDelay = 10 + +# +# WrongPass.MaxCount +# Description: Number of login attemps with wrong password before the account or IP will be +# banned. +# Default: 0 - (Disabled) +# 1+ - (Enabled) + +WrongPass.MaxCount = 0 + +# +# WrongPass.BanTime +# Description: Time (in seconds) for banning account or IP for invalid login attempts. +# Default: 600 - (10 minutes) +# 0 - (Permanent ban) + +WrongPass.BanTime = 600 + +# +# WrongPass.BanType +# Description: Ban type for invalid login attempts. +# Default: 0 - (Ban IP) +# 1 - (Ban Account) + +WrongPass.BanType = 0 + +# +################################################################################################### + +################################################################################################### +# MYSQL SETTINGS +# +# LoginDatabaseInfo +# Description: Database connection settings for the realm server. +# Example: "hostname;port;username;password;database" +# ".;somenumber;username;password;database" - (Use named pipes on Windows +# "enable-named-pipe" to [mysqld] +# section my.ini) +# ".;/path/to/unix_socket;username;password;database" - (use Unix sockets on +# Unix/Linux) +# Default: "127.0.0.1;3306;cypher;cypher;auth" + +LoginDatabaseInfo = "127.0.0.1;3306;username;password;auth" + +# +# DBQueryWorkerInterval +# Description: Time (milliseconds) for Query Task Worker interval. +# Default: 1000 + +DBQueryWorkerInterval = 1000 + +# +################################################################################################### + +################################################################################################### +# UPDATE SETTINGS +# +# Updates.EnableDatabases +# Description: A mask that describes which databases shall be updated. +# +# Following flags are available +# DATABASE_LOGIN = 1, // Auth database +# +# Default: 0 - (All Disabled) +# 1 - (All Enabled) + +Updates.EnableDatabases = 0 + +# +# Updates.SourcePath +# Description: The path to your CypherCore source directory. +# If the path is left empty, built-in CMAKE_SOURCE_DIR is used. +# Example: "../CypherCore" +# Default: "" + +Updates.SourcePath = "" + +# +# Updates.AutoSetup +# Description: Auto populate empty databases. +# Default: 1 - (Enabled) +# 0 - (Disabled) + +Updates.AutoSetup = 1 + +# +# Updates.Redundancy +# Description: Perform data redundancy checks through hashing +# to detect changes on sql updates and reapply it. +# Default: 1 - (Enabled) +# 0 - (Disabled) + +Updates.Redundancy = 1 + +# +# Updates.ArchivedRedundancy +# Description: Check hashes of archived updates (slows down startup). +# Default: 0 - (Disabled) +# 1 - (Enabled) + +Updates.ArchivedRedundancy = 0 + +# +# Updates.AllowRehash +# Description: Inserts the current file hash in the database if it is left empty. +# Useful if you want to mark a file as applied but you don't know its hash. +# Default: 1 - (Enabled) +# 0 - (Disabled) + +Updates.AllowRehash = 1 + +# +# Updates.CleanDeadRefMaxCount +# Description: Cleans dead/ orphaned references that occur if an update was removed or renamed and edited in one step. +# It only starts the clean up if the count of the missing updates is below or equal the Updates.CleanDeadRefMaxCount value. +# This way prevents erasing of the update history due to wrong source directory state (maybe wrong branch or bad revision). +# Disable this if you want to know if the database is in a possible "dirty state". +# Default: 3 - (Enabled) +# 0 - (Disabled) +# -1 - (Enabled - unlimited) + +Updates.CleanDeadRefMaxCount = 3 + +# +################################################################################################### + +################################################################################################### +# +# LOGGING SYSTEM SETTINGS +# +# Appender config values: Given a appender "name" +# Appender.name +# Description: Defines 'where to log' +# Format: Type,LogLevel,Flags,optional1,optional2,optional3 +# +# Type +# 0 - (None) +# 1 - (Console) +# 2 - (File) +# 3 - (DB) +# +# LogLevel +# 0 - (Disabled) +# 1 - (Trace) +# 2 - (Debug) +# 3 - (Info) +# 4 - (Warn) +# 5 - (Error) +# 6 - (Fatal) +# +# Flags: +# 0 - None +# 1 - Prefix Timestamp to the text +# 2 - Prefix Log Level to the text +# 4 - Prefix Log Filter type to the text +# +# File: Name of the file (read as optional1 if Type = File) +# Allows to use one "{0}" to create dynamic files +# + +Appender.Console=1,2,0 +Appender.Bnet=2,2,0,Bnet.log + +# Logger config values: Given a logger "name" +# Logger.name +# Description: Defines 'What to log' +# Format: LogLevel,AppenderList +# +# LogLevel +# 0 - (Disabled) +# 1 - (Trace) +# 2 - (Debug) +# 3 - (Info) +# 4 - (Warn) +# 5 - (Error) +# 6 - (Fatal) +# +# AppenderList: List of appenders linked to logger +# (Using spaces as separator). +# + +Logger.Server=3,Console Bnet +Logger.Realmlist=3,Console Bnet +Logger.Session=3,Console Bnet +Logger.Network=3,Console Bnet +Logger.SqlUpdates=3,Console Bnet +Logger.ServiceProtobuf=2,Console Bnet + +# +################################################################################################### \ No newline at end of file diff --git a/BNetServer/BNetServer.csproj b/BNetServer/BNetServer.csproj new file mode 100644 index 000000000..e2d77cc03 --- /dev/null +++ b/BNetServer/BNetServer.csproj @@ -0,0 +1,168 @@ + + + + + Debug + AnyCPU + {668E1664-FB63-47DA-9EA1-93B59851BF1E} + Exe + Properties + BNetServer + BNetServer + v4.6.2 + 512 + + false + publish\ + true + Disk + false + Foreground + 7 + Days + false + false + true + 0 + 1.0.0.%2a + false + true + + + BNetServer.Server + + + Red.ico + + + true + ..\Build\x64\Debug\ + DEBUG;TRACE + full + x64 + prompt + true + false + + + bin\x64\Release\ + TRACE + true + pdbonly + x64 + prompt + true + + + true + ..\Build\Debug\ + DEBUG + full + x86 + prompt + true + true + false + + + ..\Build\Release\ + TRACE + true + pdbonly + x86 + prompt + true + + + + + false + + + 7EA13AA3A2003652283599344471651FF1C1100A + + + BNetServer_TemporaryKey.pfx + + + false + + + + False + ..\Libs\Google.Protobuf.dll + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + PreserveNewest + + + + + {82d442a9-18c0-4c59-8ec6-0dfe3e34d334} + Framework + + + + + False + Microsoft .NET Framework 4.5.2 %28x86 and x64%29 + true + + + False + .NET Framework 3.5 SP1 + false + + + + + + + + + + \ No newline at end of file diff --git a/BNetServer/Managers/Global.cs b/BNetServer/Managers/Global.cs new file mode 100644 index 000000000..091c44249 --- /dev/null +++ b/BNetServer/Managers/Global.cs @@ -0,0 +1,28 @@ +/* + * Copyright (C) 2012-2017 CypherCore + * + * 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, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +using BNetServer.Services; + +namespace BNetServer +{ + public static class Global + { + public static RealmManager RealmMgr { get { return RealmManager.Instance; } } + public static SessionManager SessionMgr { get { return SessionManager.Instance; } } + public static ServiceDispatcher ServiceDispatcher { get { return ServiceDispatcher.Instance; } } + } +} diff --git a/BNetServer/Managers/SessionManager.cs b/BNetServer/Managers/SessionManager.cs new file mode 100644 index 000000000..edc9bd0a2 --- /dev/null +++ b/BNetServer/Managers/SessionManager.cs @@ -0,0 +1,164 @@ +/* + * Copyright (C) 2012-2017 CypherCore + * + * 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, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +using BNetServer.Networking; +using Framework.Configuration; +using Framework.Rest; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Net; +using System.Security.Cryptography.X509Certificates; +using System.Threading; + +namespace BNetServer +{ + public class SessionManager : Singleton + { + SessionManager() + { + _formInputs = new FormInputs(); + } + + public bool Initialize() + { + int _port = ConfigMgr.GetDefaultValue("LoginREST.Port", 8081); + if (_port < 0 || _port > 0xFFFF) + { + Log.outError(LogFilter.Network, "Specified login service port ({0}) out of allowed range (1-65535), defaulting to 8081", _port); + _port = 8081; + } + + string configuredAddress = ConfigMgr.GetDefaultValue("LoginREST.ExternalAddress", "127.0.0.1"); + IPAddress address; + if (!IPAddress.TryParse(configuredAddress, out address)) + { + Log.outError(LogFilter.Network, "Could not resolve LoginREST.ExternalAddress {0}", configuredAddress); + return false; + } + _externalAddress = new IPEndPoint(address, _port); + + configuredAddress = ConfigMgr.GetDefaultValue("LoginREST.LocalAddress", "127.0.0.1"); + if (!IPAddress.TryParse(configuredAddress, out address)) + { + Log.outError(LogFilter.Network, "Could not resolve LoginREST.ExternalAddress {0}", configuredAddress); + return false; + } + + _localAddress = new IPEndPoint(address, _port); + + // set up form inputs + _formInputs.Type = "LOGIN_FORM"; + + var input = new FormInput(); + input.Id = "account_name"; + input.Type = "text"; + input.Label = "E-mail"; + input.MaxLength = 320; + _formInputs.Inputs.Add(input); + + input = new FormInput(); + input.Id = "password"; + input.Type = "password"; + input.Label = "Password"; + input.MaxLength = 16; + _formInputs.Inputs.Add(input); + + input = new FormInput(); + input.Id = "log_in_submit"; + input.Type = "submit"; + input.Label = "Log In"; + _formInputs.Inputs.Add(input); + + _loginTicketCleanupTimer = new Timer(CleanupLoginTickets); + _loginTicketCleanupTimer.Change(TimeSpan.FromSeconds(10), TimeSpan.FromSeconds(10)); + + _certificate = new X509Certificate2("BNetServer.pfx"); + + return true; + } + + public IPEndPoint GetAddressForClient(IPAddress address) + { + if (IPAddress.IsLoopback(address)) + return _localAddress; + + return _externalAddress; + } + + public FormInputs GetFormInput() + { + return _formInputs; + } + + public X509Certificate2 GetCertificate() + { + return _certificate; + } + + public void AddLoginTicket(string id, AccountInfo accountInfo) + { + _validLoginTickets[id] = new LoginTicket() { Id = id, Account = accountInfo, ExpiryTime = Time.UnixTime + 10 }; + } + + public AccountInfo VerifyLoginTicket(string id) + { + var accountInfo = _validLoginTickets.LookupByKey(id); + if (accountInfo != null) + { + if (accountInfo.ExpiryTime > Time.UnixTime) + { + _validLoginTickets.Remove(id); + return accountInfo.Account; + } + } + + return new AccountInfo(); + } + + public void CleanupLoginTickets(object state) + { + long now = Time.UnixTime; + + foreach (var pair in _validLoginTickets.ToList()) + { + if (pair.Value.ExpiryTime < now) + _validLoginTickets.Remove(pair.Key); + } + } + + FormInputs _formInputs; + IPEndPoint _externalAddress; + IPEndPoint _localAddress; + Dictionary _validLoginTickets = new Dictionary(); + Timer _loginTicketCleanupTimer; + X509Certificate2 _certificate; + } + + class LoginTicket + { + public string Id; + public AccountInfo Account; + public long ExpiryTime; + } + + public enum BanMode + { + Ip = 0, + Account = 1 + } +} diff --git a/BNetServer/Networking/RestSession.cs b/BNetServer/Networking/RestSession.cs new file mode 100644 index 000000000..69e8940eb --- /dev/null +++ b/BNetServer/Networking/RestSession.cs @@ -0,0 +1,215 @@ +/* + * Copyright (C) 2012-2017 CypherCore + * + * 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, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +using Framework.Configuration; +using Framework.Database; +using Framework.Networking; +using Framework.Rest; +using Framework.Serialization; +using Framework.Web; +using System; +using System.Net.Sockets; +using System.Security.Cryptography; +using System.Text; + +namespace BNetServer.Networking +{ + public class RestSession : SSLSocket + { + public RestSession(Socket socket) : base(socket) { } + + public override void Start() + { + AsyncHandshake(Global.SessionMgr.GetCertificate()); + } + + public override void ReadHandler(int transferredBytes) + { + var httpRequest = HttpHelper.ParseRequest(GetReceiveBuffer(), transferredBytes); + if (httpRequest == null) + return; + + switch (httpRequest.Method) + { + case "GET": + default: + HandleConnectRequest(httpRequest); + break; + case "POST": + HandleLoginRequest(httpRequest); + return; + } + + AsyncRead(); + } + + public void HandleConnectRequest(HttpHeader request) + { + // Login form is the same for all clients... + SendResponse(HttpCode.Ok, Global.SessionMgr.GetFormInput()); + } + + public void HandleLoginRequest(HttpHeader request) + { + LogonData loginForm = Json.CreateObject(request.Content); + LogonResult loginResult = new LogonResult(); + if (loginForm == null) + { + loginResult.AuthenticationState = "LOGIN"; + loginResult.ErrorCode = "UNABLE_TO_DECODE"; + loginResult.ErrorMessage = "There was an internal error while connecting to Battle.net. Please try again later."; + SendResponse(HttpCode.BadRequest, loginResult); + return; + } + + string login = ""; + string password = ""; + + for (int i = 0; i < loginForm.Inputs.Count; ++i) + { + switch (loginForm.Inputs[i].Id) + { + case "account_name": + login = loginForm.Inputs[i].Value; + break; + case "password": + password = loginForm.Inputs[i].Value; + break; + } + } + + PreparedStatement stmt = DB.Login.GetPreparedStatement(LoginStatements.SEL_BNET_ACCOUNT_INFO); + stmt.AddValue(0, login); + + SQLResult result = DB.Login.Query(stmt); + if (result.IsEmpty()) + { + loginResult.AuthenticationState = "LOGIN"; + loginResult.ErrorCode = "UNABLE_TO_DECODE"; + loginResult.ErrorMessage = "There was an internal error while connecting to Battle.net. Please try again later."; + SendResponse(HttpCode.BadRequest, loginResult); + + return; + } + + string pass_hash = result.Read(13); + + var accountInfo = new AccountInfo(); + accountInfo.LoadResult(result); + + if (CalculateShaPassHash(login, password) == pass_hash) + { + stmt = DB.Login.GetPreparedStatement(LoginStatements.SEL_BNET_CHARACTER_COUNTS_BY_BNET_ID); + stmt.AddValue(0, accountInfo.Id); + + SQLResult characterCountsResult = DB.Login.Query(stmt); + if (!characterCountsResult.IsEmpty()) + { + do + { + accountInfo.GameAccounts[characterCountsResult.Read(0)] + .CharacterCounts[new RealmHandle(characterCountsResult.Read(3), characterCountsResult.Read(4), characterCountsResult.Read(2)).GetAddress()] = characterCountsResult.Read(1); + + } while (characterCountsResult.NextRow()); + } + + + stmt = DB.Login.GetPreparedStatement(LoginStatements.SEL_BNET_LAST_PLAYER_CHARACTERS); + stmt.AddValue(0, accountInfo.Id); + + SQLResult lastPlayerCharactersResult = DB.Login.Query(stmt); + if (!lastPlayerCharactersResult.IsEmpty()) + { + RealmHandle realmId = new RealmHandle(lastPlayerCharactersResult.Read(1), lastPlayerCharactersResult.Read(2), lastPlayerCharactersResult.Read(3)); + + LastPlayedCharacterInfo lastPlayedCharacter = new LastPlayedCharacterInfo(); + lastPlayedCharacter.RealmId = realmId; + lastPlayedCharacter.CharacterName = lastPlayerCharactersResult.Read(4); + lastPlayedCharacter.CharacterGUID = lastPlayerCharactersResult.Read(5); + lastPlayedCharacter.LastPlayedTime = lastPlayerCharactersResult.Read(6); + + accountInfo.GameAccounts[lastPlayerCharactersResult.Read(0)].LastPlayedCharacters[realmId.GetSubRegionAddress()] = lastPlayedCharacter; + } + + byte[] ticket = new byte[0].GenerateRandomKey(20); + loginResult.LoginTicket = "TC-" + ticket.ToHexString(); + + Global.SessionMgr.AddLoginTicket(loginResult.LoginTicket, accountInfo); + } + else if (!accountInfo.IsBanned) + { + uint maxWrongPassword = ConfigMgr.GetDefaultValue("WrongPass.MaxCount", 0u); + + if (ConfigMgr.GetDefaultValue("WrongPass.Logging", false)) + Log.outDebug(LogFilter.Network, "[{0}, Account {1}, Id {2}] Attempted to connect with wrong password!", request.Host, login, accountInfo.Id); + + if (maxWrongPassword != 0) + { + SQLTransaction trans = new SQLTransaction(); + stmt = DB.Login.GetPreparedStatement(LoginStatements.UPD_BNET_FAILED_LOGINS); + stmt.AddValue(0, accountInfo.Id); + trans.Append(stmt); + + ++accountInfo.FailedLogins; + + Log.outDebug(LogFilter.Network, "MaxWrongPass : {0}, failed_login : {1}", maxWrongPassword, accountInfo.Id); + + if (accountInfo.FailedLogins >= maxWrongPassword) + { + BanMode banType = ConfigMgr.GetDefaultValue("WrongPass.BanType", BanMode.Ip); + int banTime = ConfigMgr.GetDefaultValue("WrongPass.BanTime", 600); + + if (banType == BanMode.Account) + { + stmt = DB.Login.GetPreparedStatement(LoginStatements.INS_BNET_ACCOUNT_AUTO_BANNED); + stmt.AddValue(0, accountInfo.Id); + } + else + { + stmt = DB.Login.GetPreparedStatement(LoginStatements.INS_IP_AUTO_BANNED); + stmt.AddValue(0, request.Host); + } + + stmt.AddValue(1, banTime); + trans.Append(stmt); + + stmt = DB.Login.GetPreparedStatement(LoginStatements.UPD_BNET_RESET_FAILED_LOGINS); + stmt.AddValue(0, accountInfo.Id); + trans.Append(stmt); + } + + DB.Login.CommitTransaction(trans); + } + } + + loginResult.AuthenticationState = "DONE"; + SendResponse(HttpCode.Ok, loginResult); + } + + void SendResponse(HttpCode code, T response) + { + AsyncWrite(HttpHelper.CreateResponse(code, Json.CreateString(response))); + } + + string CalculateShaPassHash(string name, string password) + { + SHA256 sha256 = SHA256.Create(); + var i = sha256.ComputeHash(Encoding.UTF8.GetBytes(name)); + return sha256.ComputeHash(Encoding.UTF8.GetBytes(i.ToHexString() + ":" + password)).ToHexString(); + } + } +} diff --git a/BNetServer/Networking/Session.cs b/BNetServer/Networking/Session.cs new file mode 100644 index 000000000..4737b034a --- /dev/null +++ b/BNetServer/Networking/Session.cs @@ -0,0 +1,661 @@ +/* + * Copyright (C) 2012-2017 CypherCore + * + * 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, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +using Bgs.Protocol; +using Bgs.Protocol.Challenge.V1; +using Framework.Constants; +using Framework.Database; +using Framework.IO; +using Framework.Networking; +using Framework.Rest; +using Framework.Serialization; +using Google.Protobuf; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Net.Sockets; + +namespace BNetServer.Networking +{ + public partial class Session : SSLSocket + { + public Session(Socket socket) : base(socket) + { + _accountInfo = new AccountInfo(); + + ClientRequestHandlers.Add("Command_RealmListTicketRequest_v1_b9", GetRealmListTicket); + ClientRequestHandlers.Add("Command_LastCharPlayedRequest_v1_b9", GetLastCharPlayed); + ClientRequestHandlers.Add("Command_RealmListRequest_v1_b9", GetRealmList); + ClientRequestHandlers.Add("Command_RealmJoinRequest_v1_b9", JoinRealm); + } + + public override void Start() + { + string ip_address = GetRemoteIpAddress().ToString(); + Log.outTrace(LogFilter.Session, "{0} Accepted connection", GetClientInfo()); + + // Verify that this IP is not in the ip_banned table + DB.Login.Execute(DB.Login.GetPreparedStatement(LoginStatements.DEL_EXPIRED_IP_BANS)); + + PreparedStatement stmt = DB.Login.GetPreparedStatement(LoginStatements.SEL_IP_INFO); + stmt.AddValue(0, ip_address); + stmt.AddValue(1, BitConverter.ToUInt32(GetRemoteIpAddress().GetAddressBytes(), 0)); + + _queryProcessor.AddQuery(DB.Login.AsyncQuery(stmt).WithCallback(CheckIpCallback)); + } + + void CheckIpCallback(SQLResult result) + { + if (!result.IsEmpty()) + { + bool banned = false; + do + { + if (result.Read(0) != 0) + banned = true; + + if (!string.IsNullOrEmpty(result.Read(1))) + _ipCountry = result.Read(1); + + } while (result.NextRow()); + + if (banned) + { + Log.outDebug(LogFilter.Session, "{0} tries to log in using banned IP!", GetClientInfo()); + CloseSocket(); + return; + } + } + + AsyncHandshake(Global.SessionMgr.GetCertificate()); + } + + public override bool Update() + { + if (!base.Update()) + return false; + + _queryProcessor.ProcessReadyQueries(); + + return true; + } + + public override void ReadHandler(int transferredBytes) + { + if (!IsOpen()) + return; + + var stream = new CodedInputStream(GetReceiveBuffer(), 0, transferredBytes); + while (!stream.IsAtEnd) + { + var header = new Header(); + stream.ReadMessage(header); + + if (header.ServiceId != 0xFE) + { + Global.ServiceDispatcher.Dispatch(this, header.ServiceHash, header.Token, header.MethodId, stream); + } + else + { + var handler = _responseCallbacks.LookupByKey(header.Token); + if (handler != null) + { + handler(stream); + _responseCallbacks.Remove(header.Token); + } + } + } + + AsyncRead(); + } + + void AsyncWrite(ByteBuffer packet) + { + if (!IsOpen()) + return; + + AsyncWrite(packet.GetData()); + } + + public void SendResponse(uint token, IMessage response) + { + Header header = new Header(); + header.Token = token; + header.ServiceId = 0xFE; + header.Size = (uint)response.CalculateSize(); + + var headerSizeBytes = BitConverter.GetBytes((ushort)header.CalculateSize()); + Array.Reverse(headerSizeBytes); + + ByteBuffer packet = new ByteBuffer(); + packet.WriteBytes(headerSizeBytes, 2); + packet.WriteBytes(header.ToByteArray()); + packet.WriteBytes(response.ToByteArray()); + + AsyncWrite(packet.GetData()); + } + + public void SendResponse(uint token, BattlenetRpcErrorCode status) + { + Header header = new Header(); + header.Token = token; + header.Status = (uint)status; + header.ServiceId = 0xFE; + + var headerSizeBytes = BitConverter.GetBytes((ushort)header.CalculateSize()); + Array.Reverse(headerSizeBytes); + + ByteBuffer packet = new ByteBuffer(); + packet.WriteBytes(headerSizeBytes, 2); + packet.WriteBytes(header.ToByteArray()); + + AsyncWrite(packet); + } + + public void SendRequest(uint serviceHash, uint methodId, IMessage request, Action callback) + { + _responseCallbacks[_requestToken] = callback; + SendRequest(serviceHash, methodId, request); + } + + public void SendRequest(uint serviceHash, uint methodId, IMessage request) + { + Header header = new Header(); + header.ServiceId = 0; + header.ServiceHash = serviceHash; + header.MethodId = methodId; + header.Size = (uint)request.CalculateSize(); + header.Token = _requestToken++; + + var headerSizeBytes = BitConverter.GetBytes((ushort)header.CalculateSize()); + Array.Reverse(headerSizeBytes); + + ByteBuffer packet = new ByteBuffer(); + packet.WriteBytes(headerSizeBytes, 2); + packet.WriteBytes(header.ToByteArray()); + packet.WriteBytes(request.ToByteArray()); + + AsyncWrite(packet); + } + + public BattlenetRpcErrorCode HandleLogon(Bgs.Protocol.Authentication.V1.LogonRequest logonRequest) + { + if (logonRequest.Program != "WoW") + { + Log.outDebug(LogFilter.Session, "Battlenet.LogonRequest: {0} attempted to log in with game other than WoW (using {1})!", GetClientInfo(), logonRequest.Program); + return BattlenetRpcErrorCode.BadProgram; + } + + if (logonRequest.Platform != "Win" && logonRequest.Platform != "Wn64" && logonRequest.Platform != "Mc64") + { + Log.outDebug(LogFilter.Session, "Battlenet.LogonRequest: {0} attempted to log in from an unsupported platform (using {1})!", GetClientInfo(), logonRequest.Platform); + return BattlenetRpcErrorCode.BadPlatform; + } + + if (logonRequest.Locale.ToEnum() == LocaleConstant.enUS && logonRequest.Locale != "enUS") + { + Log.outDebug(LogFilter.Session, "Battlenet.LogonRequest: {0} attempted to log in with unsupported locale (using {1})!", GetClientInfo(), logonRequest.Locale); + return BattlenetRpcErrorCode.BadLocale; + } + + _locale = logonRequest.Locale; + _os = logonRequest.Platform; + + var endpoint = Global.SessionMgr.GetAddressForClient(GetRemoteIpAddress()); + + ChallengeExternalRequest externalChallenge = new ChallengeExternalRequest(); + externalChallenge.PayloadType = "web_auth_url"; + + externalChallenge.Payload = ByteString.CopyFromUtf8(string.Format("https://{0}:{1}/bnetserver/login/", endpoint.Address, endpoint.Port)); + SendRequest((uint)OriginalHash.ChallengeListener, 3, externalChallenge); + return BattlenetRpcErrorCode.Ok; + } + + public BattlenetRpcErrorCode HandleVerifyWebCredentials(Bgs.Protocol.Authentication.V1.VerifyWebCredentialsRequest verifyWebCredentialsRequest) + { + Bgs.Protocol.Authentication.V1.LogonResult logonResult = new Bgs.Protocol.Authentication.V1.LogonResult(); + logonResult.ErrorCode = 0; + _accountInfo = Global.SessionMgr.VerifyLoginTicket(verifyWebCredentialsRequest.WebCredentials.ToStringUtf8()); + if (_accountInfo == null) + return BattlenetRpcErrorCode.Denied; + + string ip_address = GetRemoteIpAddress().ToString(); + + // If the IP is 'locked', check that the player comes indeed from the correct IP address + if (_accountInfo.IsLockedToIP) + { + Log.outDebug(LogFilter.Session, "Session.HandleVerifyWebCredentials: Account '{0}' is locked to IP - '{1}' is logging in from '{2}'", + _accountInfo.Login, _accountInfo.LastIP, ip_address); + + if (_accountInfo.LastIP != ip_address) + return BattlenetRpcErrorCode.RiskAccountLocked; + } + else + { + Log.outDebug(LogFilter.Session, "Session.HandleVerifyWebCredentials: Account '{0}' is not locked to ip", _accountInfo.Login); + if (_accountInfo.LockCountry.IsEmpty() || _accountInfo.LockCountry == "00") + Log.outDebug(LogFilter.Session, "Session.HandleVerifyWebCredentials: Account '{0}' is not locked to country", _accountInfo.Login); + else if (!_accountInfo.LockCountry.IsEmpty() && !_ipCountry.IsEmpty()) + { + Log.outDebug(LogFilter.Session, "Session.HandleVerifyWebCredentials: Account '{0}' is locked to country: '{1}' Player country is '{2}'", + _accountInfo.Login, _accountInfo.LockCountry, _ipCountry); + + if (_ipCountry != _accountInfo.LockCountry) + return BattlenetRpcErrorCode.RiskAccountLocked; + } + } + + // If the account is banned, reject the logon attempt + if (_accountInfo.IsBanned) + { + if (_accountInfo.IsPermanenetlyBanned) + { + Log.outDebug(LogFilter.Session, "{0} Session.HandleVerifyWebCredentials: Banned account {1} tried to login!", GetClientInfo(), _accountInfo.Login); + return BattlenetRpcErrorCode.GameAccountBanned; + } + else + { + Log.outDebug(LogFilter.Session, "{0} Session.HandleVerifyWebCredentials: Temporarily banned account {1} tried to login!", GetClientInfo(), _accountInfo.Login); + return BattlenetRpcErrorCode.GameAccountSuspended; + } + } + + logonResult.AccountId = new EntityId(); + logonResult.AccountId.Low = _accountInfo.Id; + logonResult.AccountId.High = 0x100000000000000; + foreach (var pair in _accountInfo.GameAccounts) + { + if (!pair.Value.IsBanned) + { + EntityId gameAccountId = new EntityId(); + gameAccountId.Low = pair.Value.Id; + gameAccountId.High = 0x200000200576F57; + logonResult.GameAccountId.Add(gameAccountId); + } + } + + if (!_ipCountry.IsEmpty()) + logonResult.GeoipCountry = _ipCountry; + + logonResult.SessionKey = ByteString.CopyFrom(new byte[64].GenerateRandomKey(64)); + + _authed = true; + + SendRequest((uint)OriginalHash.AuthenticationListener, 5, logonResult); + return BattlenetRpcErrorCode.Ok; + } + + public BattlenetRpcErrorCode HandleGetAccountState(Bgs.Protocol.Account.V1.GetAccountStateRequest request, Bgs.Protocol.Account.V1.GetAccountStateResponse response) + { + if (!_authed) + return BattlenetRpcErrorCode.Denied; + + if (request.Options.FieldPrivacyInfo) + { + response.State = new Bgs.Protocol.Account.V1.AccountState(); + response.State.PrivacyInfo = new Bgs.Protocol.Account.V1.PrivacyInfo(); + response.State.PrivacyInfo.IsUsingRid = false; + response.State.PrivacyInfo.IsRealIdVisibleForViewFriends = false; + response.State.PrivacyInfo.IsHiddenFromFriendFinder = true; + + response.Tags = new Bgs.Protocol.Account.V1.AccountFieldTags(); + response.Tags.PrivacyInfoTag = 0xD7CA834D; + } + + return BattlenetRpcErrorCode.Ok; + } + + public BattlenetRpcErrorCode HandleGetGameAccountState(Bgs.Protocol.Account.V1.GetGameAccountStateRequest request, Bgs.Protocol.Account.V1.GetGameAccountStateResponse response) + { + if (!_authed) + return BattlenetRpcErrorCode.Denied; + + if (request.Options.FieldGameLevelInfo) + { + var gameAccountInfo = _accountInfo.GameAccounts.LookupByKey(request.GameAccountId.Low); + if (gameAccountInfo != null) + { + response.State = new Bgs.Protocol.Account.V1.GameAccountState(); + response.State.GameLevelInfo = new Bgs.Protocol.Account.V1.GameLevelInfo(); + response.State.GameLevelInfo.Name = gameAccountInfo.DisplayName; + response.State.GameLevelInfo.Program = 5730135; // WoW + } + + response.Tags = new Bgs.Protocol.Account.V1.GameAccountFieldTags(); + response.Tags.GameLevelInfoTag = 0x5C46D483; + } + + if (request.Options.FieldGameStatus) + { + if (response.State == null) + response.State = new Bgs.Protocol.Account.V1.GameAccountState(); + + response.State.GameStatus = new Bgs.Protocol.Account.V1.GameStatus(); + + var gameAccountInfo = _accountInfo.GameAccounts.LookupByKey(request.GameAccountId.Low); + if (gameAccountInfo != null) + { + response.State.GameStatus.IsSuspended = gameAccountInfo.IsBanned; + response.State.GameStatus.IsBanned = gameAccountInfo.IsPermanenetlyBanned; + } + + response.State.GameStatus.Program = 5730135; // WoW + response.Tags.GameStatusTag = 0x98B75F99; + } + + return BattlenetRpcErrorCode.Ok; + } + + public BattlenetRpcErrorCode HandleProcessClientRequest(Bgs.Protocol.GameUtilities.V1.ClientRequest request, Bgs.Protocol.GameUtilities.V1.ClientResponse response) + { + if (!_authed) + return BattlenetRpcErrorCode.Denied; + + Bgs.Protocol.Attribute command = null; + Dictionary Params = new Dictionary(); + + for (int i = 0; i < request.Attribute.Count; ++i) + { + Bgs.Protocol.Attribute attr = request.Attribute[i]; + Params[attr.Name] = attr.Value; + if (attr.Name.Contains("Command_")) + command = attr; + } + + if (command == null) + { + Log.outError(LogFilter.SessionRpc, "{0} sent ClientRequest with no command.", GetClientInfo()); + return BattlenetRpcErrorCode.RpcMalformedRequest; + } + + var handler = ClientRequestHandlers.LookupByKey(command.Name); + if (handler == null) + { + Log.outError(LogFilter.SessionRpc, "{0} sent ClientRequest with unknown command {1}.", GetClientInfo(), command.Name); + return BattlenetRpcErrorCode.RpcNotImplemented; + } + + return handler(Params, response); + } + + Variant GetParam(Dictionary Params, string paramName) + { + return Params.LookupByKey(paramName); + } + + delegate BattlenetRpcErrorCode ClientRequestHandler(Dictionary Params, Bgs.Protocol.GameUtilities.V1.ClientResponse response); + BattlenetRpcErrorCode GetRealmListTicket(Dictionary Params, Bgs.Protocol.GameUtilities.V1.ClientResponse response) + { + Variant identity = GetParam(Params, "Param_Identity"); + if (identity != null) + { + var realmListTicketIdentity = Json.CreateObject(identity.BlobValue.ToStringUtf8(), true); + var gameAccountInfo = _accountInfo.GameAccounts.LookupByKey(realmListTicketIdentity.GameAccountId); + if (gameAccountInfo != null) + _gameAccountInfo = gameAccountInfo; + } + + if (_gameAccountInfo == null) + return BattlenetRpcErrorCode.UtilServerInvalidIdentityArgs; + + Variant clientInfo = GetParam(Params, "Param_ClientInfo"); + if (clientInfo != null) + { + var realmListTicketClientInformation = Json.CreateObject(clientInfo.BlobValue.ToStringUtf8(), true); + _build = (uint)realmListTicketClientInformation.Info.ClientVersion.Build; + _clientSecret.AddRange(realmListTicketClientInformation.Info.Secret.Select(x => Convert.ToByte(x)).ToArray()); + } + + if (_build == 0) + return BattlenetRpcErrorCode.WowServicesDeniedRealmListTicket; + + PreparedStatement stmt = DB.Login.GetPreparedStatement(LoginStatements.UPD_BNET_LAST_LOGIN_INFO); + stmt.AddValue(0, GetRemoteIpAddress().ToString()); + stmt.AddValue(1, Enum.Parse(typeof(LocaleConstant), _locale)); + stmt.AddValue(2, _os); + stmt.AddValue(3, _accountInfo.Id); + + DB.Login.Execute(stmt); + + var attribute = new Bgs.Protocol.Attribute(); + attribute.Name = "Param_RealmListTicket"; + attribute.Value = new Variant(); + attribute.Value.BlobValue = ByteString.CopyFrom("AuthRealmListTicket", System.Text.Encoding.UTF8); + response.Attribute.Add(attribute); + + return BattlenetRpcErrorCode.Ok; + } + + BattlenetRpcErrorCode GetLastCharPlayed(Dictionary Params, Bgs.Protocol.GameUtilities.V1.ClientResponse response) + { + Variant subRegion = GetParam(Params, "Command_LastCharPlayedRequest_v1_b9"); + if (subRegion != null) + { + var lastPlayerChar = _gameAccountInfo.LastPlayedCharacters.LookupByKey(subRegion.StringValue); + if (lastPlayerChar != null) + { + var compressed = Global.RealmMgr.GetRealmEntryJSON(lastPlayerChar.RealmId, _build); + if (compressed.Length == 0) + return BattlenetRpcErrorCode.UtilServerFailedToSerializeResponse; + + var attribute = new Bgs.Protocol.Attribute(); + attribute.Name = "Param_RealmEntry"; + attribute.Value = new Variant(); + attribute.Value.BlobValue = ByteString.CopyFrom(compressed); + response.Attribute.Add(attribute); + + attribute = new Bgs.Protocol.Attribute(); + attribute.Name = "Param_CharacterName"; + attribute.Value = new Variant(); + attribute.Value.StringValue = lastPlayerChar.CharacterName; + response.Attribute.Add(attribute); + + attribute = new Bgs.Protocol.Attribute(); + attribute.Name = "Param_CharacterGUID"; + attribute.Value = new Variant(); + attribute.Value.BlobValue = ByteString.CopyFrom(BitConverter.GetBytes(lastPlayerChar.CharacterGUID)); + response.Attribute.Add(attribute); + + attribute = new Bgs.Protocol.Attribute(); + attribute.Name = "Param_LastPlayedTime"; + attribute.Value = new Variant(); + attribute.Value.IntValue = (int)lastPlayerChar.LastPlayedTime; + response.Attribute.Add(attribute); + } + + return BattlenetRpcErrorCode.Ok; + } + + return BattlenetRpcErrorCode.UtilServerUnknownRealm; + } + + BattlenetRpcErrorCode GetRealmList(Dictionary Params, Bgs.Protocol.GameUtilities.V1.ClientResponse response) + { + if (_gameAccountInfo == null) + return BattlenetRpcErrorCode.UserServerBadWowAccount; + + string subRegionId = ""; + Variant subRegion = GetParam(Params, "Command_RealmListRequest_v1_b9"); + if (subRegion != null) + subRegionId = subRegion.StringValue; + + var compressed = Global.RealmMgr.GetRealmList(_build, subRegionId); + if (compressed.Length == 0) + return BattlenetRpcErrorCode.UtilServerFailedToSerializeResponse; + + var attribute = new Bgs.Protocol.Attribute(); + attribute.Name = "Param_RealmList"; + attribute.Value = new Variant(); + attribute.Value.BlobValue = ByteString.CopyFrom(compressed); + response.Attribute.Add(attribute); + + var realmCharacterCounts = new RealmCharacterCountList(); + foreach (var characterCount in _gameAccountInfo.CharacterCounts) + { + var countEntry = new RealmCharacterCountEntry(); + countEntry.WowRealmAddress = (int)characterCount.Key; + countEntry.Count = characterCount.Value; + realmCharacterCounts.Counts.Add(countEntry); + } + + compressed = Json.Deflate("JSONRealmCharacterCountList", realmCharacterCounts); + + attribute = new Bgs.Protocol.Attribute(); + attribute.Name = "Param_CharacterCountList"; + attribute.Value = new Variant(); + attribute.Value.BlobValue = ByteString.CopyFrom(compressed); + response.Attribute.Add(attribute); + return BattlenetRpcErrorCode.Ok; + } + + BattlenetRpcErrorCode JoinRealm(Dictionary Params, Bgs.Protocol.GameUtilities.V1.ClientResponse response) + { + Variant realmAddress = GetParam(Params, "Param_RealmAddress"); + if (realmAddress != null) + return Global.RealmMgr.JoinRealm((uint)realmAddress.UintValue, _build, GetRemoteIpAddress(), _clientSecret, (LocaleConstant)Enum.Parse(typeof(LocaleConstant), _locale), _os, _gameAccountInfo.Name, response); + + return BattlenetRpcErrorCode.WowServicesInvalidJoinTicket; + } + + public BattlenetRpcErrorCode HandleGetAllValuesForAttribute(Bgs.Protocol.GameUtilities.V1.GetAllValuesForAttributeRequest request, Bgs.Protocol.GameUtilities.V1.GetAllValuesForAttributeResponse response) + { + if (!_authed) + return BattlenetRpcErrorCode.Denied; + + if (request.AttributeKey == "Command_RealmListRequest_v1_b9") + { + Global.RealmMgr.WriteSubRegions(response); + return BattlenetRpcErrorCode.Ok; + } + + return BattlenetRpcErrorCode.RpcNotImplemented; + } + + public string GetClientInfo() + { + string stream = '[' + GetRemoteIpAddress().ToString() + ':' + GetRemotePort(); + if (_accountInfo != null && !string.IsNullOrEmpty(_accountInfo.Login)) + stream += ", Account: " + _accountInfo.Login; + + if (_gameAccountInfo != null) + stream += ", Game account: " + _gameAccountInfo.Name; + + stream += ']'; + + return stream; + } + + public uint GetAccountId() { return _accountInfo.Id; } + public uint GetGameAccountId() { return _gameAccountInfo.Id; } + + Dictionary ClientRequestHandlers = new Dictionary(); + + AccountInfo _accountInfo; + GameAccountInfo _gameAccountInfo; // Points at selected game account (inside _gameAccounts) + + string _locale; + string _os; + uint _build; + + string _ipCountry; + + Array _clientSecret = new Array(32); + + bool _authed; + + QueryCallbackProcessor _queryProcessor = new QueryCallbackProcessor(); + + Dictionary> _responseCallbacks = new Dictionary>(); + uint _requestToken; + } + + public class AccountInfo + { + public void LoadResult(SQLResult result) + { + Id = result.Read(0); + Login = result.Read(1); + IsLockedToIP = result.Read(2); + LockCountry = result.Read(3); + LastIP = result.Read(4); + FailedLogins = result.Read(5); + IsBanned = result.Read(6) != 0; + IsPermanenetlyBanned = result.Read(7) != 0; + PasswordVerifier = result.Read(9); + Salt = result.Read(10); + + const int GameAccountFieldsOffset = 8; + do + { + var account = new GameAccountInfo(); + account.LoadResult(result.GetFields(), GameAccountFieldsOffset); + GameAccounts[result.Read(GameAccountFieldsOffset)] = account; + + } while (result.NextRow()); + + } + + public uint Id; + public string Login; + public bool IsLockedToIP; + public string LockCountry; + public string LastIP; + public uint FailedLogins; + public bool IsBanned; + public bool IsPermanenetlyBanned; + public string PasswordVerifier; + public string Salt; + + public Dictionary GameAccounts = new Dictionary(); + } + + public class GameAccountInfo + { + public void LoadResult(SQLFields fields, int startColumn) + { + Id = fields.Read(startColumn + 0); + Name = fields.Read(startColumn + 1); + IsBanned = fields.Read(startColumn + 2) != 0; + IsPermanenetlyBanned = fields.Read(startColumn + 3) != 0; + SecurityLevel = (AccountTypes)fields.Read(startColumn + 4); + + int hashPos = Name.IndexOf('#'); + if (hashPos != -1) + DisplayName = "WoW" + Name.Substring(hashPos + 1); + else + DisplayName = Name; + } + + public uint Id; + public string Name; + public string DisplayName; + public bool IsBanned; + public bool IsPermanenetlyBanned; + public AccountTypes SecurityLevel; + + public Dictionary CharacterCounts = new Dictionary(); + public Dictionary LastPlayedCharacters = new Dictionary(); + } + + public class LastPlayedCharacterInfo + { + public RealmHandle RealmId; + public string CharacterName; + public ulong CharacterGUID; + public uint LastPlayedTime; + } +} diff --git a/BNetServer/Properties/AssemblyInfo.cs b/BNetServer/Properties/AssemblyInfo.cs new file mode 100644 index 000000000..24bac72ef --- /dev/null +++ b/BNetServer/Properties/AssemblyInfo.cs @@ -0,0 +1,36 @@ +using System.Reflection; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; + +// General Information about an assembly is controlled through the following +// set of attributes. Change these attribute values to modify the information +// associated with an assembly. +[assembly: AssemblyTitle("BNetServer")] +[assembly: AssemblyDescription("")] +[assembly: AssemblyConfiguration("")] +[assembly: AssemblyCompany("")] +[assembly: AssemblyProduct("BNetServer")] +[assembly: AssemblyCopyright("Copyright © 2014")] +[assembly: AssemblyTrademark("")] +[assembly: AssemblyCulture("")] + +// Setting ComVisible to false makes the types in this assembly not visible +// to COM components. If you need to access a type in this assembly from +// COM, set the ComVisible attribute to true on that type. +[assembly: ComVisible(false)] + +// The following GUID is for the ID of the typelib if this project is exposed to COM +[assembly: Guid("ffe6ae44-fd7a-431f-b65d-fe8ac7bfc86a")] + +// Version information for an assembly consists of the following four values: +// +// Major Version +// Minor Version +// Build Number +// Revision +// +// You can specify all the values or you can default the Build and Revision Numbers +// by using the '*' as shown below: +// [assembly: AssemblyVersion("1.0.*")] +[assembly: AssemblyVersion("1.0.0.0")] +[assembly: AssemblyFileVersion("1.0.0.0")] diff --git a/BNetServer/Red.ico b/BNetServer/Red.ico new file mode 100644 index 0000000000000000000000000000000000000000..f0eafc9c559e1ca071f64013e6ffcb7047be7036 GIT binary patch literal 144639 zcmd3s1y|ip)b7s#io1JpDSB{scPZ`;#jUuzyB2pS?i9BJ6pA|(cZ$owx%}_@-tTa; zl9f!7nb|YRnmzOE9{>OZzyki;fB-T;QXK$b`EeZ>_&;encpw1saZg0#f74NgALswdvxWfxGFYJiQ7TH(D2RlJA4Q|c%1EgF z_w>I5{=cgAT`E56Bm>Awh<^3V`PaSDL^I<4`C+{3skQU=%*O{9Bn6EUctj&Av*#r9 ztwF(CLKcgo1}>)1>7gGDHDYrg?j49I_^5~)K^OL?eve>y`69j^zf1rz!7d6)cDiI4 z@T;MiNaOpam^4zJlPhh#DpD>#!%HqnA0nd*_}ST9XOCk;Bf1=m8Sh*AElhDA+FMHQmQ#YT&? z*P9INR%Lk}o2{*F+2+NMW4G-15wn!CO4&JVF!4%wb@UgwtI1W$xXr3|)7Zy%rl}lY zJrNPncR#=GiJNt1T%>0(kt6Z%MDZ36AD^`x;eZWo+a|p>2a5z!K{;RvAh}qe_rD(c z?9%}F0@z<%)b!dO2rF7T#X%z#sONTA=b45#k66f|l5NyxZ1mXhDu*+MHwF|WfZ+iQ z;`LvCUcXCW?AHQP0qJO;h4ck{ADsO6f8(TB=xo<|qz_^*w zj<1W3uiMrfziyLcjs9zR{^>d zrgQ=+HNYFNnCExDvf1vk|GV_&aOycze#DTkHf=K&)ZgtZD2w@33EG^8?)f^zoy=+`!&a2XtG`(h(rA9SpGVo zApDWw+mSFKYQ*x&il{2KXn6~HBua_C)q!=;nHtY4GQ&@QC8SMJsG_yS5DfL?kYq&x z>ukfv+T)t4F9no~aDU+Z!@c@pL10@_mRfa)=+W$MVH^*UqrC{Hfr3v)vMnhH1+-y1 zQuI7zZ>wXTXHCFUr9VuppTTJB#b04a3eB~lpHOS&=!gc8s&MCI2NP$P^N#}X0d(WD z1@Vw)02>HEOD}x~A3+|Sx&QB1MxDpPn){}+6;HF(G2#4R;;?dOPD%G**QyER^Y;s9 z@MV1i-vpM&f}i&1Uq@%jn+benBhJAMF3ob*bL2=Enf}B%DlUz*t@iC?(>+geE|n^3 zN^;2m(7VOOWfKkY85I%1bk`(SG`no{hM3Gil3jjDCq5Y({tHty)Z8()SDp_Pl?i5( z!!ODK^B!&*2{=)v9<4X+$jlCd*m-*!!bguvWg4DG_{QoF!%k7DhhMLAJN+p7X{F6S+1O&0;m1UkNM60eKa<^Uqi9j zkcixh7Ti~VqvTXOfJpv~3pR{#JErYvX~fN=09n0!rfJ@n^u9zZNk2oq5C4)~sx#uz^u= z7#X`{0QEV24>=fIz{lvQiXDU(W8oM!U3-izh1md1@VaM)9@NZTV~&Q>NpC?8EmMCp zgf1ycl42+Y+~MWABogG1%XD+K5lM z7}wmXTg);u#2=6lZDh_&-(B+9Ku`J}yxH_L3XEmitjw8_Et&mZE$zxCT_o6UXeM{N zefE3jc{OR!&cO%b-~D}`8eoq8e4{jgY@fT&eGgBur`8+p;Bp}vE;-0~w(s5vG8+id zPp58Vyy+b)zaRZ2)WOo4E;{$~x$4j_i+0^eRpk#BQ>5|znh;<--3eltIaxPuSNM4O(G_%avg>Xmw`&B-1&QXU zr$!ii?h^&@^(9u?Njqq6<)3Y`pXz0h(2-&a%4-uPPZqb+3NSJBxn#sL98w;^(>j!h zT<0K47D&B^S;!bTVi4YbTIaYlzhQ{zChM6tY(LQ^wY43khKizC*Q>)ZcBpyVe8uvK zQ_Z;4g3rvmxYvW1Q%e4KAu0VM?LHlRA4Htc3d~B`ayQ?=7mJwHm~9wDhc6@NHnR;iKJ4JQ z&&pS~($(G*L%o-vwjMP+2-2viwL6r_UsD;Vs6353DN!=smk?TBXR`ji?yXgh;C|_P zo1Ai|$MvBwsGI)1`!g46Jb{a2fZN`h7gRO#V}7i+iI~;xa7tTE?ZmtUZ0`h5=T@KN zUMNh8m85h0^8jmZ&}>pB@$vh*3o#73Tb^JwE)Q8Okdtj%Q}JY;!0|1{%eC#2T!cu? zR7cySV7ivQiZH3Zpb`BvltdilkOCL`{9VP4`PK%SHh*gSEMHvWp!wdPwsV-=N0v%l&<8TRh0pI&&3hU$m6qUB^XXFy+25 zJg~6f@Q?d-eME=D2+dZ#?4lXU)!<1tV`+gjCk}4VOOZwSefY^q`&{af?JaB!nW)BSSt`W&p{ZCJP{?Baz zL;=^d0ho7z$Z@(Tb1j+N^V4#OKE?%t70Ab?_QTP&yF5~R|W9ah5T zqP$c*f$$Ng)+#_)YW(c`^1DIrmAe$P8boxep)K8ZmG089_g20)bn`KH^9nZb7;l&C zF&jE|A&J1N2wf!7L}oa~HV_u!a{Z#i_0Z{bu_lG9m=$q4n`^SG2d)de>Yx%U8iLZU|D3JrpTicw?9MN!L7SL=C zcfX{sp6fP{bM&@#p5`~47#e7Gf!5~73>S6#@bLj@p-$k3i-zyQlP{BQc?PN2(I81%20^4M3jBhm(lk9%M#l@8WH{1ExIHP7e%5T&wH zt6M)2?awO89=?y2<(HekF~eGz$PblHyczEJGlmx4Z;$6ZeLUK5Ss`|%p$bS%Wf}ox zU=9u~jd8#{+L}tmFhFmuXld!}sJ*#6uFb<^@ZpCi_&56anzn5cAr`!wC34U8y!u0( zWKi&~X_@#!olUhwPX!wD+fdVo4YnS5I7FP^dXe6qiPJpQJ3hxx;jt8rx_(vS(iZw+ zW#=m#%EDG#VL_!TAV-{^|97RI2#pi~kVE*9QgR-0`V${;z&`Q@2;S>hY>B+$Rl~nb zLc6mPb4!-49X6#G^rSF<3sA%DF!GnLCio??H{A3KHD_AAehr;x)`wI);fox~CGx-eb`-gMw}eH2368QPL7M8COFM$jA3 zGHl8B;+mxF=LkR|$$jE|B(d&l@0pCI;?UUh;#AE3ZZNcUDTqKQ$~zh>Z{)@=Rt(v& zzUfqprU^#1>3joqMS!KBmT{MJo8n6&2OAndDq09~GxaU1P^+0S7N2hMr)+iHrQ4T)7h$Q|KE|h^ zz{P#ALLYEzP5)*1I0>yrZch5n5>gn3JX;1|{*?QkKn@6bsz?jOD`C_fLtRZ}f*ku` zx+ickilx9O`&z-^oYwiGh35IEcxGQL6j4FosZh~@I|Jq0uEsKy$2Ah*w_{5r*16|gjjs$pD!xGCn7w&aWgWQu~L zb}-pdpA2zfNlqIUp#Q2PPDoZ*&tlZ3Y-?6Wx;T@hJsB`0OS+E!idZz?H&HN-^b9n! zhN|dJF6(EAh%OAN??)g+axaDX8mjX!E;l(a;p|HwDsAT!%7T-iJcRC9CkbgZ9f@ux z!sp(R7D2lzDr(L2sGTkI5530dbMy+gPNXm?&H$pqnKORHtR8S<*xfrRQb7qd7fZ0@L8hc$ zboc+53US4y42i1F@taXrH9@tL;Yjm$FNxl1Y@^-OHv(eP$Z0Se>WLw2m&9tLemE_z zi3-+|XB>oXMywlDK4%e;u(csieWM!ST8loJMTV-FxDb5(q_Ab=m@<{RIvZZ`G`MNr z?A_Ge2`H7`AK|z3EMo13=S1VsslRr96=_1LWRSVlNp5iwnLlAc4sC=Kj zXskN%(dHUWv6Zx>+DSu6x+>~vXq3X<;1nPB%%l=;_@V^E{D5w0bE9%Ltf+O2yZdq89wI zVv#|_==Ty6^f%qpbrJVlLNIn5tt1Iaj1V=t`%GvKRua9DjhPCO@{qN^F%b;w0LQ7(Kt{wn@d(CbVC)%Dsfv& z%pPYdv(B`Fr+gqhuKW|Ul1c@|HWVILEQq5r#pIe+R{g3fD5>SoiAc{RFP5w)?xIvm z^8^dXpPFUBMP`^~!o`<3GJ@IvTj~*Bp6kq7#qd~T&oMiZayGmG2rBZA#`U`b z$B6eEse~!{lj3`wV53#fbf`)Jv~9KZ;iX7dRh{?@Z9pl?ATm9L1O+@!s!EmrbrXW% z&s}k4?iQr^%K%%}zN~HZAFn<6YSH8<$BxsEL^ML2U6w4~Di6hiHTs8E_!f^tiV-NYVZL5cN#)TSt4)e%z5$v z?>SY*zqU*0$t#m z1)t8eIiJkZaF#jqhG$aw^eck33;dQ_Z$i@~;Femw`pCSF0I3va0j3akRZ`rJ!H+uo zmp~T0_csOB^cwRY4lI}#Ax8@WSMHgXx?hvW?54>!vQl0v+5qpWNki)pCPcq~KhRjHBU$UT`o<3aqFe+O+=`V%(dqw|LVX(WUEv!#x2Bc#!DH%2opW!=7=ObA+yib@TtjSkT<6l4f=Oy?4W zU~=Z0jp?ts=N51;vB-b7kY!2eRHo5GVsN3MOC5j;$X&%GrIHP1l&w$oT)i6;x7&&P z#92ZN2 zoE@}4CyHl7D~}mFi(L)}jXcW{SeotCmEN`QuCb|hcqSbJT=W*i&v;mI+X%4oBSFiM zuP&u0rIuyNY?W;v-f>YqF-$hDS-RmEyj$xdFuV4pXjimSc#!oQO@{ffVfreoup8co zRl4@Ut|m-2d76jELN?B3W`a~db(IJXl7lal#N9FZ^In2WN(c9!BrCXY)aAPti$VyE z;40SPAy`q4MYew9)WnBp$h$5gil1KQB*XiW;EZmi;pDX17NgmZiqfh222kB1TKc4I zr|BF2zR%5^(b@(*^#eb|Fni}Wt#DNDO-tSO`%{Q;--aoMUH0^_57qT;|3z6)!D%su z|0B_6U)KAz(dBH5t+vc}&VTuMRPkp;G2A(1w;z4${pR&@z4_Ytu)F#0_m2tJe*~|< zRCH(%!JtiKC2j`Z$eknL;%*6OcKL8T$K(1Ypmg?c>(~$t?Kfue6@E-(Y-^#aG!&V< zy6@BD;Z?8SneBscn59K~#}Kj|a&Q^g*wr4T_nN3z_2%)(opF3t+@X*~Q#VQ^bk18R zc`Ix@<7%PK59NL2^9zu4!qTMMbh?z3i_tB?L+xBQooX3B?vi2i0e14`IDeb}@!sWlw_^XfM--dHJ51m&&yG=pPyH;4VmP*>21yLA3{kZOvECAfX3 z>F%&Sf9--Afr}PP|Md-Wg{M`w^Ew6-!ZF;zD2^twdNGLmiS*yaZf>{W#F0gfKH4E5 zH2z~$zDmh2sQ9Aa>49d}hLVmWsUnBeV7*^{kyZSdb`kLT{q;m+-7Q>}g=NtUW~;9i zIZ!m+4+y0-`Qvugl>-Y&EmT}|9MzV~+ zGSsXs{be%Q`gU&|+)H0(ig;-}Aui9mpYs^2{LHjbOs^|vF&Bml;pRS{E5)t7DxTO&+Xb?a=&XryMWsttTzVuPqHg8GE0EFh?N_NYcRuZ-C$aS z68}dF;>SoXf;2nAn5w@N;d??=pcy%<%lH$~OT(*w`?=E^(?Tg0p&Lr@hZfH7ssS}z zWq5N+izFPgJj2i|-I@vDAB&X?v;Wvi61C6jEM!`QH|Hr*?VE^&l zQ9fVT%<m&huk_zLMVh3O>oA8Xie&c$qFFBcLSY6WP$?QOYowP#pgf6yJ|0fhfDBZZ+`py*7@B9G7aio~d$y z+H^2q8quMf=_C;_TvJYOpsTEJmWIY}7{l1I6=Hl(CR*2*TpLa*YnX18^Q8h@u|jH? z?CR>CvrtrJFmN)+l zO{))26>!alMxI=QVa394hW}Jc-l^&2r#MG76{F(q_|2XSV8}|T03nS?R`%zMOU(^P z;VTuiXY`Z`B)n;Mfv33hWRX6zlnqvY6_V1^8(mzSURxG%G=n}RBGMjbODK5hCx9Y~ zsuQ6c1?h?|b&CRZW2(Dms0?KUyY4c}DefHqVcb15O2^}LKg|*8v;9`)2*di-2br*dj^uhomWmADm+sRv6hpqhToleUmu(k8-Z(p z+hdaW9r_OkZEkBT^X!*th<0ooSg6O|ZvsceSW6o?VGW~03H9b6BA*^*__+7{z`c}p z1f(Z4IG)QDfke%oWQ9h9<%!VWg}t0f=c3KyyoPp@>x1dv##Vw1o3OLwC;IUzG$)-CqC>c|v2&lHq_ zoDjn$baBP?@acg=N}16r3j=X)F8HS>;K(`LlefyF3@sF}(8?-?+BKE0a;9j0n$Pc1 z2^tfNO&SL4tIw6pM*pm!SZ72F;+ID9WGl|$jol$+jR+@?N;6x>z~B-)1vPb+RJnB| zZH4TC5_+{ZHfCpDn2yg?q-U=oLKv^tvBK6ciUSHybza435o`AdYDICSDQPs9=L#|5 z8}phIWH!HkrRJs-D+F*Ph=6)tG%@R77ROuq8hH4q-wq}0s*nD*zR0onOfH4q@22lE z!?1}3a*Rd^(}=NUxWvVUf8DDgfR9gJz)AQSmM)0Gpelb=6E^kbXKPEN^+hQXQm#O` z$AXS~fWk_9{PM~tf8zzm##()BVOjM|Dp5Xs<5Y_~M+#Pje3Ygs-)OWsC^|{!d^WV& zjL|1&6P~*5>T~V^9bgA8k(sGxpb}JG=KXwrEH8v<8D9zlMz1_f5knU7`I4es)EH>A zKSDI{QfQH6clZBT0P+EGtaIf{HzYrj_=PWTH=X!tJD zoa4PIHuV&>TAiL_KT<7B+SN^0>wtv0NNQ4Uh#mHvdOI-`s~DU0Jw#7EA8byHDT7WE z#4B+5Dy667c``LusswLYPBim_k|+pE8Ty*J51)KJs+O^|?L|Q3_6K}9e6Ur-F9=!a zbej5BZZID>)n|{Fz;}`rE*$<$bWZ}Mg83kgOVG^x~O)Q5P(H}i1}l6mtF}qpJM_o7D2G?^nmN7u*^D@Ez@Jk&vXa- zB<7FAapEK;|KHE;l)LohMQv_PpvEe}vY=Chew%H06v1u$SlVlg0P@? zA2k>sC8}91x@dep`u-c=_t@c|A{C*!k(@QLU!f>VPOwM+UN?R%pQn~&(3(cm@L-Wm zyz{O>CzDmbooil6P#BlJ&FT6H#UQ~MK0IZiV6)viB$X#y=U8VWR@_2JYbO!Y(Gl-SA39Ge@2Vj(f}3yR z;G&^tX!=I=w=+thi6kUMMyAil!HRf@Y=}&V77(P8rH3prG!BO603#7C{Y0|{WQ#7z zF-VfthuekMHi>{6FKW=kVe`9%HU~S;hU7oGn1Eg=cCG5f1-h}6L$ZA#d)p0pe+VX` zyF4#zxr<(wT0!>?D4H>n`7u9D8!VCf zV?-YL6GUqO3IQe1X(pA++nb)1e^I;4$j8rJ=BRs^&Ubh<&yPDvW&+WV$1;7GuWQae=y^OsJ1iQJqW{3fg!U zyW0>gD;9ddtikjQjxJol$hcaescP~IaGwAO!USh(`m$K}E!hsJd%W7i$4S_oBzO&0 z#Af(Wibc@5Br6E-kOTwoU4CcSX;^s!RJ5W;Lo%i0+2zb%W*^y!sa z=*S?a$LKe{%QpM%FZ7ICEo6o|g6X-Obbl<1)WetU&*tmP&nIDq>yB6;{8=kNKdMNX z(fM}0{CEQ+!s2yBoS!Q8hE2M~vDR9&;T7UKS&sdak2$u}PHEpPozbpJ;3nZv=y#mL zOgQED+D|yv4ADM3mxc{gxYO{+dhSLUg?JK;c*XbKg{pF-o(PTi6rY|tBnrul-BA(uQ!X7?tg4KeVk1i9LA;RfHTx+y zZx*h(*d*_+sORnbbg&|)`V9SeluGcJF*ttWtb-KC$f+KZKE`H4s->dD$EQ-eoTi3! zg}}pBUH55r#;;KHcYb2UblrxB%+2*XFoz5LLiaFl2%m=m?bCg|pbAQT6FyG+(w&HD z>aNsxXlQ9OE#CdZv)bxWV!cxoK0CS)ZA0wIT>gTQ;2Z39JR4=ON(3%nP7DDbv!U## zzpM$hYq?Q*Cml_wLeIxNY2~V~#fLW*~?o}rFL>>%|Vq)%^+#piakhVmA zve8ur%3)5w!X8qGh$~Av%O3rr3c`^fBB5U`mW!hKBf&Kq*eK>|nRm(^xapz#>#${@ zA;@CGoAlX%BRtFsxXe>_PAo}sKDrjj<8_#e_zHv@r5IB~hFl{Vm?4fna^~tEQZ>Hy z786^;C=o+Bp$0C!*g|=&iuw2|lK6<@@)hSLDd(OYWaggYipu0RTGJ5@+3X4G27e+X zR^tk)TE`PQ4NS>qbb8S=Ja8myub8SKYPaZ%LIMIFE^Lxvo`EH(yl(2SD?|t?0&;$q z7MMLYVHUd+T^7*vUH4<6Y>6Oc!ccL$zDV2-urDZj#IE9TB~U30wM6NW2?XvvRAGBJ zoiHyq63g#FyEPCi9Eu|uC(d!+#mN#CY|hQ|SZ!Q7ggC(RE!e~GjYp+J$(z|}JO=bk z_BzkR7yt_Lsg8I}K9|rV>e1S0KWGDMT7+W8k3_p7uz5|mT@RaeY*oKJjDRZobd;(H zt`R3<=kbx(O>_K{=V{SoKSyz0jpAN9*GP6ZF~a}Y^G+_fD0>LEi6&)&KH%ACw^{sH zL?=>ZQX|CbdBJ_!4Z|CV_;5@47NdWIiVSvX1@_x{L{QItC^*XLVRc~juq$%ZpyLXj ztrX1<$kcjc$H){xzu9h6U_Pz$A*!m`BP+nCb($F2Pob~>f|(3C)VwY7%XMa{Tb zonZCc)t0g$ae}oVqF$41QymT9?0g5uLEAxZ=o}!J006>`_LF9&_2y^A`qR*zca3D6 zx7y01p#E+U$|8>`(9jca)b8>LV(3qhA|O4y(dYMV+qz%hs@qHP#j-cSs#d@M&tHRe z;)J9Q%AW#XCHaSpO-ywqv#`{~ZC0~@7e^!kKPYRoT{3aP{^%xU z_Pb8WoIJ$0B;YyEpzos^PrUcmo?=68LiSVTtJI4Pv@bhlGrJrD!}@PuTdcosqk;BA z6k2vDvFC9UDmI4jb58Cq*V7oK{Z|6#bY|A~5gM`sMt^YfcFpOmC?v{ zd-*So;b8)=Cy*bFbe%Y``N@W9<#TLX_d z*{>tSEyiy^LJNY4ww|zF-&3Z4;|MjJS3tX)%YStCg@%QY?jPKOe_YE0XX}>P4VF64 zvWf`;UPjuTFGz&~-ULX;U3))J*DX(7X599cwup<(Utw!=HmBG{0O~AnsqIc~hb_#j zta4P&ujgu1T;?J-2d2+8j@!MxcewW%@{C6h?%~~_C%5=8E7&KooA-y$p0AT66EOo9 zJuw!)`(F#EKfzlO?-k(qp6Wd~I`%}p3P4=SXFk(9C+rwL+eG*JUUcLBSp~s>`F3W! zq{!}jP+q^+egwKmkx2Idd+I*EZwCHz=`3|sWHi-)0Ertu5&OM8E+( zT|1-v#DQzd-^Q+H5#+NCOvoCWb9x)qC15cbYqg2LUr>3GlJE0K? z>?lu$X&shE6GPX5>2|9xwyO9c4aGWJ!bC{^Bsp4N&#`XA$Yqk^Wb@3eWJZRn;FN$w8dIZHj>AL+HEU;lGNtRmuQ8$Tw7uB_}x zM4YB!zJamXnhJKTG1y*x<$A|)H8ts$z1JMhv5T8qZ$_O(DF{DZg3@3emt_2f z#GXdnuwBQ=fm+XlUiAtpy`THsA(rv$JlX1IiCSkKe5`2lwy#2daaD|C5idr+e!V7L z4bfI|vwoTXPHtZhl(si#fbdrxc)GnCiimR0Yblna{VOwC*!A1!>9s~qGkpK!2QNlmI~5)w z_tdX8Jx@ezVaK)tpK8Hsqw5oJ{8Jv$lKH@VkTw|1uqH7hKi5?~D}kxo05YLYe2Z3f zB0=)(8*#Y7E>!{#$2J&2DJ*#*IR5iWM-bj%L#4noH|OoglUI7uNFz+M-l#|nnZiRC zdK}8%A%~0z7n2y6|1xlF($Wh~AVwly0}O3kVu{0uS!=n7!NjvKfDuYPhYv2f+#(nxF$ko;_hxTN^o-~MEnW5#o66plj0USZYh@H>$#w%D0(wVTV zw}>j)gd9TOnu8KAL>^knBZE`e*aZ2wF~k}B9ZO`29vUYDAJtVme=*1K82NdIf*+#$ zx!eNA$BPZx9rBpxZShL#*GDslugOJ)Q^`|(f^Xh1ylrex@qRNJz{6_fGkdqjt;J2i zzkEdrteen<{5FH*H;b<2FCtgC^mz?gUnf3}1FL)etiyEA~XZ>E2}`jIfY3kxZ-FYzV%7q-qR+-+_zxDY|(%5voo`mtalqVrVQH&H_{ z?WE!P^Kp{@=>bRIfV88sN)$^3I-$ey#SS^!`;CGlSS?g53M4Zs6HqMaM!0b0ASacn-@5@}G5JBBHDhbA7blD4Vct_vmoD&wCd#4aPVeH5C}ktKfujpMbib4!oNVND9iG4{Q^>QW_h^C>#NBr!!7&>mz zZ~O#C{0&5=j~RWfOTci7H;-Hw_Ar;jbj&e!D`OHW>|3faFBaLsuo>z4jZFWVHiCdY zj35BJGrHg7b#7D|dy;JZVHlPteY}40X}29otPo^2Y&N=GxSpbSI;Cj*P*>Qkba||j zvWB7(B`zK&p1WJrx8ZWEOlY}VIwdn7w)uvoXXB%i>6iM!5fr2u(K0>wKhF1p*l)ka z1(3=mrbVE1C4iWMAR)ui^bmSzObx(su^M)GYZn#ovV+`$wV4G_8)P|;cLAIhnaf+d zOfe^@DiRUOD){Ys`y2DMrp8RFIRXjYGBzV=K+8@vGLud4|!?Hw$ zc#WbpmpQr+F4Dl|3wFP#z@bjlkGB5~RVEwk z#v_q-I0}KNP8?Q5L#=?NNMP}I+hE!}i##y|>4sM8117ke<3w1IwuJN|VMCk0!k4d^ zLOX61aQ`*pgX!C4e56RnFlYnng1TN`rU0xSER28XN2s>R>97;u zCPOa!ZQtZ@UUUZAsE#!B_>>0|txLGY7sB1A4A3GC8;zbRql zq#6Xj8U(?qh#_#~b#MkKfD0@F*9BoGW8@8_jSJjwy~!6xM>z<*Fmm$rF7of6D{zta zc0t!;C1uo8r;1|MVqvudAW8+jpq6VYFh_V$tj6#ip#KBH@M5s{pzl4vP+=g>|Hb%w z4~6URTJ+%GZdR}|)~NuH$j@MiUgNg+d})a3q_R40vH(6|e_g~z9zDcY&ada)Jhl1C zlJ>V36u=OIT^aG8V)iXrQ{xra_^}p2DVfSs*x0I2lpUk6+7Y2CXFZ(^mX|3)u-%G_ z5J)FWXCf}Xr5YLh4M5$QY7$A^kZB{tI5WMJDN?!Tf4_=pd7E>k@a%_u!;70hO8z;) z)~rFFf&@8H4Kwi+@tT=Y!I~iye?TPim{f{!9nMf^M8JX%h6|}K?wi(|)l)&O^IMP& z#Bo_8zQ96%Mrq@iq~toer^!V%;^rrdIm{SrqMpXFnc`0h}DD5L5Yz0>mac* zpeyl&UOX4UN;9F&o*Px9h1811Fq-L1kb1WMXOqIRTc^;9#RwA`;^2GHL*0US=0-YBeErWS=ALAzJ zOKilw+vmAK1)Sm`u(Bmls8ywNC!h)W(OfM8unS5yL>qRCcH_BqM7{QbPzIyp&qQ)B zNPhp?8cUe%Y}w;VfnJf~-l1!uT^MzScJuObm?kY~i^2Rdkjd)q8E|Eeljb4CsX=21FNK+ItF-R3By6qlNG&xX~4rfdLK4z8~}Gk)w}~0Je^=Ru^0nY!?xR_1b&{HeX@{@@8=H4 zEo6Ij7A|EXt&Cp9DokP;xke3i;SvYsnhO0W%=we3TrT4yn1Ibg`Iq_=sAuSW?m~C( zsYpt6VZ;YeQg1RBEo-Skcp}Mtm;JUP;~y%Vja+xbafH zzeZ-fXonlMIS2B7dwzFdD!#ru&f`-itfuAtgT7m}l5ptzH7$aTR(=?B_jII5k*FXF z9Ro0C80yg_N>(>&IIfqKeSNlh+O0-51YgG;ZDJT^NO4<|rBm}|~^eB?F>0Dk8viF;@ z{(pAn?hNo4{rK;iw8wh?6j!}1MJ$*0xc5~O_FjXq9N^-;&}mSuw_>!6TL>aezD@Yl zvw0S(vKkEf;4+7s+iB~{NUWZ!_~U*hiSTN#Iob7*SZVIRM$aby8GNo5@S%6!KC{|H zyEKU89cCWza$OS-yXjtFi0~j8YY6Ikl{#jhQh_-CZb%8d9s>!}ez_GCP%lV8Kublkrso4sZ43jbz4-(D|C149@p`mng|RGsE=J40-IE z4q*mX_Pgd3MiUDb@NKJgoIcimUGvrrJ7I^)Ca}4Sq&Ru`0a2mtb}V4xH2NGeCMfnf zd5ED}@_Y*0tkftiJJUn() z`d#P@^|mVqHE=f=X^OOdsN@vhXbXhS=tE*fj((XWnK#V8u%97EkH27d$DDTLI!3?k z;~zdJkGVKbJvE148Ei`Q^2Yv(H`E8RNVcfg2y+n{>0$)2L_bZ04nxV-MR1It+(8N8 z;3ITES%3Qxnr1jrg4Ng)P{Ly;!Fkax3^GAWUzoiy(0O0Z9Dg;l1A$$&$z3?Lt7n-#I4zG93Vqwu>czd%E>kM)a7kQqRSikV6XzE=Px}LQ^n-(WYTV1riR z$N@|7iWMf}${h`n-mPqx9S0BpDf5Gdk%&)FIGvhh>?V>UM|Ul$lH0fC>QBkXN^w_k zUiIWVT$vGu^y?Qw=1##G9idfJfS&@=9oip|f&ZgTgZ8|2zgK`LK&oXtPd6O}=?}6& zR$J@@;kzkRSo(9RA+NWA>EHF5x=Rp;*I>xQZ#gRI%o}+XFNPX{6l&Rm#D7@?ljAY` z5)~HrRP4;;RtnbGm30Jywu-J3d_(~RQ$lfY5nn`QaA$~=D#kgxu~d4Xw`s^tHm6cOV zM5qzk7k4K<#0tnTE8L5H;>^ewI!B8>f_7*T9}Lt1&vFrT;q08GanAOI+M` zH>~%mjz?`E_1&bH!r#t+y0C~BO_48GhRbEGSn3M+4=ws>^v?Ho^kZ}A-1j_T_1hv@AL?58j8Fv8IVb2Z8%l_S|cDhBbpZkU^0~dPGgxepe4a#n($HsCm z$A(K<)Kk_#jF@}R^)lzYqjfNB-uoV{$!9?^F2X_=M=jlpyAN`~E2!d{Sdi8nZ*j>a zwts?v=WqTHL6kCo_WLEoAFWeb0EY*DZT=m6WoE`P@c#CrH>zp#5w*wX^_TG4RYlmy zR{@iA@a?d&A7bwtbuLkZ0Jkgx6n9?+c6*EYfV zMxB~0&l(&0v}(L=DQ~B<^6D=WiL*LNp#YwKYM7(6z$a2ESit4Mxr0i%_QXCzT_2R^-A4D;rSyG|OMsRq zI^(2>;OLKCP;JjG=G)5$S@gjOWuzD#KQ6zxP3+oa-~7~41$y3{|MgCZlnVrtkxm;v z8;`=HG$C8)?40NFFMsC;jt@h{>|=UZv9IST&B$Y7kC!ULFppzG4qUsQk1+0Ux_A@v zpXfP0z^SlLm#f`A_&*^~)}(GY#bC;hc4sSGrd;@;y-$5amUy#_vkw8;)GPkZ=sU2~ zZFkr!172R+?;mK^!} z-gvY!^B)WKXc+BHD1v#9K-?PIy3Q#@ZI!QB zaAR}i_|G42%4xgsM#cuZaXz@M)DnHh#F`pTFXWR>a@~@eqN>X`PvYr`yo@&BNEwY} zUyu~JoNZ8*(86Pl?b7A8n{IwR!*1v$8JfRlBg7@MB9vuOH(>`?BaA2o;i<0sAg=np zDA{?*%(~_-BBg{`nanJKs-I`-YSFfCdvI*X@qXL;HIs~kO~_;CiGr55|8I&Gj)M}- zQTIJzndEj-SRQ#aIn|j+DT|9@{z#?Uzfrm^i623pwaHO2!|!%fzfNEXJ#)r`QEc_1 z@as69bTs|nj8cE&?=esQSwSqu@Ko2DS9|MgYWJbSpn;+Q@qVuR0}ej5*uUy3_BQ#^ zzM|)4mU#c1x!IerUIWx_?;X%hz2R+~sfbT8pu@dPdkm7oK$32&cFv%xz!p>=SaFj8 zH#Pn_ZbK5&uDNm!K;p*jN+1p7CKSlC&@cf$Tp^XIkxV_D4PGEY9z%h&YHl-h?lEiOEPTJ%6fK7JXGv2 zuLT9s^eo}ZMsAmc`kP&fqG)pjv)qBXwgC>ab({Z>1%QhH7-lYu@K}QH9b$snDUw=? zkxcBG!aCHZpsnc(JP*`nP9Klotnh6fur5E;?)x=*B^##eW9${{y{5|?qa#c>)p#;I zQBc$LMs_2mWHM7vS9MGC_6C~SKb2H3h!eNC+EJ@|$o&#p%X{2!y$g#3oNc6eSId@lcorfUqZ zqYJkuwr$(C+oW;QG-_;HC$<_jw$;XIlE%hqlE!Il+vc6`KKK4VnAv;JyI!odh8FiC z@ecan5UBv|K2(YIoqjFx+c*yFt*U0Oq~QBRUAhhOmL^^VB{gNl z(Zr1tkcbS z%cu346DbJvs|hYJL)3P+xuydshGylfH^9ze9XB`0P?r77_Xx*~pALALw^mSCx^@iq z(5o+;icWGs|DMqwRBP>H@05u!<%NC1f+l-mt&ozaE?*9;RnO*SrBQ1!Rzc>!Q1h%) zNWWjO$cQ3_wXKP!&^Qk-o!R zhQ_R*-+GP-?H)tdHINZoa85zeF-J_~TqTKmz70Ewcm(8VjCJ3aGl}(y;WmiySd7Fw zGebUhY}5ARtUIiSoLVS2US}G`+A6i3k{;Au#Ljgq4ib80s68tFIe-aJMyV$f@1Dmj zDb1wmrbkK-_B$dA*kt)6^^yq1SjNm1LqM?OC>@PL*u zQMxTobS^*cft9c;V$f(J$=d#$6VV+F5YkJ!t(6cH7%VhcIVvL>&CB;J6pF|(LGs`x zzf~vu3C~%5DvpWRLoN0AT$37BIBti?KKFHIvpd@HgAz4KlqGAOs!p zIIR`b)#y^+xIO($;?&J}JH(Y`4GrLfbZT%0dpuevu zFSX1&XJkSZbLjvbD6y*o0ipk~Th zT8o+=Qr7P&vlr43fj2wPV=i)@j$}x;%toFMr)VE;TrQPpA|pfKKR07gZ9lAi?apm> zj#4_Sws)h9!m#=S{&YAZ6^$xyYQQin96sf|=nu4_pnUHqNgS{-dk-4% z`N8&C-QX^2*L?|X)RRz|dLEMlX_D8kN&Z5)eYP7BNWEWfTn-qIEJ4wJDS1rkH6Rz{ zPUNB6u~iNm(?shafXU8#Mg&@Wyhi-K_2;4F)7d`C^6-}%g4 z-X9b!G*f_W?7yOlufDV;2OcbIv}TJ&ejwW_7HJC%~XmEO+b9{afcQw#L`({0s2Xg^m!$u>4d>)YVZY zwFHK0)CZDiorgbv23HA{I9z{F{g}w-38H7+G3SrO6aHWh`KdaU)aM->r6G*{f*`(? z8MHKdtVFlIJYqVsPc_m9v&+-XI!iD5Sx;hAfjC2!*jnppA%viEDcQGF!Y_iX8HW<& z6=$aLQwBEdU3DeT#|-sX6CbYQeKsWsi&u9`pB?`IBa&PwDhT-t#!w&6&l;z_G}uwF zb<~I~8zD`21$LX)cUnRVB61@m2t)Jli<3G0@jT)1xHz`+GwdHk)jKHdHlKx?b)RA?n0Ha+N9ct30G8wa}P$Ca#X9cdiNe2~0nx=0VGC2Gv zJFr8vwG%!h%3Oh84J>@7=n6-rm)*&KqSJZ+`z${i?F#w@r$am+8A2;Ry#!Axf`^D` zQl~{Jg|MV>q=Q$$gs1jWTg6ErF=;Ig&Cidz`_tr{4%_UAz@6_w2ye~^j&znkP*G> zMGy>p=AUaoou?45YG%agV5|O^uuQXt44qPBg?W=BCUdO?HbLdKdu3S{-AAQ^cL1Y< zSehx*C~>7}%(%th!uiHI4~xi{CQ9n_4hN+nY!GoAwI~yv+61QfaH#4v*bF9=}dFIa5q5;pZFG6)rq70O9Q z7Awc!D?ItLQixq$pLURbRg)6RE^MyH>Y({<^F#j!C5cU&r9#K|VK+%d|r zC4h8@JK^N8*(;RYKQ8Q3V+-Zr2DGKnaylCM zSE$FfdxQtRQ7Cp)_8H$yx)#|;N}i}@da|Bk3& zHEfkxO8m5``APE33Ud)(TBTBk}Pdw(O7YM~{Z-(8*xhyAN8-1p&v2 zTyKPKNG#klIe%z7#&f79_NL0$t)=E!P#*=<9WUJt|3u?cL>7_H&V)kc33(s`3Nk`ayG8UY zA?^;JDPs=_5jXCVHIa3E#>9TWVJ+g191pcGc$UdXbNS#t-z=bBR?llTbom#q zC6=cRyZ1RfH>UzdUa@*PD7u zkWt$nRj_j^Wj|-9rKE68Apr+n(=X_J1RD_oY z|9nMNPN>57)YCTWrw~;g9Jglo=>I!7XWdWCXZr0Cm0sstyCPoBW}-ua6aM7eCV9^@ z{@iBYUw0S?Pfm=VNAN^F&&k=9>05YmO^<$yp>!sF-^s{}6H|qG!uwVhegF6xEN)|) zRZ>QX-10X3P%gh^M3GmJ=KB^0S;A)fMaU%Rn6s-E$HWrwxA(}r0JrUaTM9&)UPrf; z5r#AyElID1UxnC&y^k@C+s|4#p}{gr%zUgQLIkfu{f#UYihyc-CFScL_80t~q=PA? z@35Kp@F2nxrr+=|qQAT1Zz1F|fI4N_*H@4yfBQ@Kx$2QguzfhrY`l>tOWi-hn`vBN z?UKE9;ZbI--V^74Rt+{X)&Y>Ch0BPKQok7^V5vhS>Mv$ zhuk2QFj?JLO$lX}wslB&99oW%UjNVXeA(3fmirMFu$L1?tr$4yo+0^(RATvWsP>#` z(mvQLkU}pM3b;7bfCm}T$JE-5*;Y63)fCOj%0f@EC?p_{M#^7ni&9=K1AgcjY-ldI z<)r&%r|{QyB(0*HZbU$u$gmaYB-LR{Q zIbQ~orodmo*JqAF%kS}6dRvZ-!4P4?L`2G$UWWpgdO|)?)A2L58RsVrKT|+T>HNrj z*7d?|IY6mS9X$3~5T~NE6s_vJ%>aSXjQ5!e+OU{RHu}Pi-MDkU zp8-hXO#I2RkB=7u-n>_Q4(V2wm;2kFpIdmc`3%>XFej@AE;D1 zpV?K6x?)c;#CwuSNLmE4si2%adHb{h9^`4d(Gc%M;^l2BJ2Y+y0r;Rk=;^=xpWLC7FWpk_U) z_9o5L@(1@g8&`#tygY{yp{G2-xi}X2h9!pACGdFbLK-c2@5;h&FUDx#|LBIY zSv7qjqx1hq@_QTkucNx%G=H=`lrYR0`sBU@5fG^NxMPJcS25Y%`UnsO#)(5YR=N`) z6yl;+>I;cXIl^YB#w??Ci{~`Qw`za)Gt%e)^6I{7{!pn#dB;XHC}xWI)<;l;*kt1@ zPMNDzK14O(Y3AW1y(=9N$lEu9voAi4K6Tg$&*$Fav6ix z5Q4zB{r`B88diuzS9+0AhF!h_)(JTN#2&-vY`pl1<^3bkSfG5xvO)yN3F=hq%ldf- z&Rq0Y#5u>6o*9WJthE+@61UP%pJDV&w0wTQ#NwKhwY>_w!v|i@=@IUGclv2@O-Z19 zh?B8J{fz+gQoBE~eb`_XOe+P%hs7u1J*UoaUkr*Zxn{@B-y-cCpG2n_Sc1D4_-OcZw#EF_XP+5 z+NW1gtT3qr|9T+B#M?zAcy~PaiBqYunU;qFQ1v}4k$&}dHG_E{ZFOs@s}DXMO?P<* zy5_6v5Y@AFNCF`L6>E;o?58|z4kLM`?G3;6@Oyjl2FS!#Yd{d05R1e(U+K*~#H~F! z+v;BHh?aPtN^ritbLhs?J=kRJ8hC?lfIGU!Sz=~0$xBANu6^h0dS0@-{TnKXa5-u1 zv}eoI-98>Y!zzD~4iGC9*Wbl!ghE?Vl)5M*J|XtpLT+N`HwTi=AWV>Ps3;5O6-8## zXhbHz1omI_!WZzG{#7^b{MRGro0SfGmf^uyO!FO*fB5coi{g(huUjX+D{@CBf8$O# zR{gH|n&`cyo^_mXXrRHoxQ6ci6gtEz!H z|4T0k^gD`OFzuyKq_=L7K6~PgPZydl?G8ldA(z%e|MY?NzD_!o|BGK<{O*kBXS2Gg zz!-{R1@iVTcJd>sM*7PR%|i8y>I8hWqghC95H|Xs!=4vC zN^J-u#1PT_d*Ai>T!a(c^nHCZR&Y|o?rs6f@1&E=mxeP9ejJw^hzotr=2mHgYeMsZ zbu8#UE|uD)uS<-Sngl(ux|ZhfmQ&aZy+4;MXK|@-+t(Kee*feQ^@OkFk3J7g7Dc$Z z_C%PM(m9e)ViMk>#G1RV}_KV!0{m^w(0jTu~?8a zq%7Et%v+Be0e(*BS^>pKnVdFvq-TJNoC)yX^HjKoub&J88V6J1aD%jMbxRo(&b6^}Z|YHq(>IE^dasU>(BUQ5T_b**lr z;vmKJizlzHP=G!-l`%&H+AL$qrAcHPZ@(7$4~ib1gdJZWAmFX*e8`?%t%;T3fL8ap z!1$+w9*##$7#8@t{IUgdbQc>p992jAjla%Oxw&Kjn)cay5wrubXv#JE`C15mZqA9q z6{SrfY&qV4t!HXkQ6pbYA@{W zsxT$?H={E@u}L4IWUs~=mCj#F!kStd#Vi3<21g~ZrSzN<=evKmawl~X&$}oLrJq8s zmzWsg2Jpz9y6vm6r-Lsxp!PlncRsE_4h^HNSbO`(h`hf^o0F; zA|C>DZg~2sEI+Dq>`|$r5HU!LWpXW?++I8^5$6Fp)HMOe77sAgIc^Fph0wlK^41Gl!@a7vjED2KWK=s1i0JndWjbqSb0cdYE#C9rH-E7 zHHuotRuc4ceNlh{W6w5=+dgsTFkfbks2HI!+oy8ON=v5`yh0!pZ@Ep|7vj49en z|6HMTz&At#>IJ7ZHJRcBmIn51r|c-iR*pwoe~ed#`XwISD>(cNrD_iPP!E?DrMcA_ z`1?!!K$yGeUH1K%Jz1Q>!?1_X8b?)~iMXUpKz9!uTI^i=uNP{Z+4{{xpn7$*H@0%a zDbMo(h$F$z+O?+VcMgD+E>Oq*>IEhEA1dm^L%>~(e(eAX=#0>lhgD8z)M9s)40Y}; z6PkxYxu)OVdt+TqCc!XPAkPfLT2Tb9Ft7~TJ%WY$a1_@&6L$pDQjE@mn+_O|RBoRE z>xBBnbF=HMj~(~7SHeJ-3XRxTFj>gU`j{tkgAu@^K|o#U;=I?HrisP6XaH z2(~w9Fldir7qea?q;tNrW_>iuKEq4`s_%(_GdF4G{{#?%SbxDX9 z#XPtRWhqTN!j5d`7{bPo1rJO7r?)PD6VXW@w6q*7^nAt+hPwzku4xi3Lk-q>Ps$j# z8-SwgVXoSEMF^f2?Xo#)B>R^C?)JK+oSqm!nXQ+@Kwz<_t$+R5I(R|8cHaoGPgW9aJ(crOV0B&Y z?xZ1LxWNcSBRy0%n_;}{{PESw@Ueu#U^Iy~=p9&h(ns@<|DxTdX)szd^=CC=pmFrl zMJCJgL#6}>e`kL!7swO-_pHwDivwBDYJUf#v!&ob2czF=tRrg9Jq5apr{KW2by!UI zLk##-;!SSF`vH?er5xO%1I+CoKB#YSysdWkuPXrD2Z`763{`r@Rgx$kGtqB}TaS)e zHlo7@4?91ry{h&!@h&*-sIg)!*Mo8IPU;8U5y;w}1%9Cil|$dAeEbU?UHHR9S*9SN zHn_*^I^U!37){Z@1f=XO#O3sJW5%XK8YCV~dntfYSWXltiME2?T+T1x;^~I-U%uwY z-MhBzwA~qrarv}HZuv3TFlkmpZ2gU&-G7$_{8VWa{@NfRvo1CD+(6_>fFk;poon1p zDF(Ws2yyKS<%hSOKxwmc?fKrJaQ=+*yrNxw#@KgscyXz8Iwb6#e9T!dZ>V96Dhs0D zK4O8hV0rnWZ%donm2~5rxo7t-a~ALUew%Q9&bQ3uKnuKEr{J7Y7?W6&3vunaq1-LM zw5iUKp*Rf6>FojX&>DDw;k;Q7;q-m?&!LC|>S1alR%3&8oX9NN%|FE zfG8{OcP_S)J>lXO*j>gY&&h%OWp1lI@J8-IWO?~vOXAsz@qMrxRAV3m`0nVjzF=99 zdlzzbq*j+(;*?*^%?`_`P{!oA)gTWfWULWZ6JSR~OKBG-?{BWdLQlrv1X*fgpCd$( zS@ytx6EH{QP4%|Gnyxvk3xSUarDW&0hqbMpe9u4a)Jvv=c#uC?{OfYRN3{%sf`>I$ z>j>IH1@m|_s^f=pKN&4$ahluM73V27|Z!&8e4j0=SW0(3t7e-jf@iBrAjMObaq+esObaviRFx}paba_m8C-k!XkACCg z>eetCTyV(YRxTx3v%DO%O$!TC8wU~OC)~IKFykjj09Nz+3=r7m;*GIcgAAsov#Hdc zz2eCShsTt2L<2}w#jjBfpYNJoeEIvwhGCogj>67vPVnIJ-o4(@1K*8aP(-ge-swgH zUu#`9Ump2k)Q@m0e32TVft$;iknYPT&f+_k0!kusVpfpNLBRpuzO)N(2sRlH0YL z@nX_ANCQJG1-7kaWoS%(C0YkN4KSTTixj*(D1PYSc%we+dJ}duYzu4l^tyanZEqnR zPYJX(g8`6HNt!E01gKHLTTXBA34vG14+HaE5dhwJvKtQEiJWhYk7X}pj&Wj3Iok;j z-q}ZZ8*E#5uiFB?V*l*txEWPv(H>$*a)SNfA`!8$T<9_ih+g$M&k>Jx4T~VJWUh&I84%3^}bJ_WC(X!!gg44&FiDSy5o;!%- zB0jkJ{&%{uwT6S)wcYW0R06?|56XVx(lK8&LMD^ev*qn?vT)?5|Ilivp+>!M1~u7A6+p_D;i0 zLkZ>Y7m@)z!E7M3yGxCjkOQpE$`$B>v7hB-UNtB_xBW|3G}+nF5=wg|x{5r%SwA=Y zwyIGlyb0mAqDIt4Mn24sVvQ+Men9MY5Whksf8RBdT|D3Z@ASo;I^?u^prp$d|A?+A zi^h5f3bXQ z5b3u*pg(dpjxOtUX8fxmbd|CPpDNl03!Ng4DAOA59{A#RDAd~-pcEZTX1EMJ4RHa zb%H)fgF5cvRGu-mvqO=CYP|dJg|>?|B`v z+SzHXPD+G&@drU7D&ggd17>`)_Psx_C!9pUSqPV4^ARSN11hR?*b3yB0J+evEqc6v zC-(wooCw`FbbdGaFE~BEUy8Btx4AaN02p{JmeXU|iW30sJ3l|KJMD{`yHO8($(S$P zlQ_&Djo^%R#ItSuCT%OG4adu{Uw1lL34)FPTPwLA5;}*S5$0#throCqSoe_@CwBBlfRbo-SrqHHSi1-$XU*Fy#I9uPJuadeM6{- zNnjU02GbNC;_;RB?A$QQ90Z2S&hdJj%)aXqp8VkcasfwmgC_r7RQ%;}VjHslOinK% z0zP*w|Zah=#*#-Y>#|_uFr*nfkjAzKLe26CgIxnt$k#!Pw;Ke#~xp*@}6X zLg6rG)(rBkVbN^TO%rUZ53WX_7`Tk27?Md4{|5QbRm%=#&^fAT-fryvu_NxG`;PeS zL~p$>b=5$BWx2l;XanHod>A{M!GqmGdSQ!=WPpRpDwTt+c>4x{-$FvDlx?1E9`*Q$nWL$` zu>_6<+N3rHW>2f1ncGetM0$VsB=(_aD34U|G*v(s zkC2ws10l#=nXGG6a0fX->*i-Cq@=>xfg1{h+V2R-2Ktoo-4E2{qCU8 zK~B=&_%q|^mdncy{vn(QTu&TkWq0!r2sWI}9{TB14h}(vwaq)^2NZHGvo>X9H`a9r zPLrhSK@QR(eA-D+-&Ftj8gN>$6c`j88X~u_k7=aqum+8<$l=)Qq=AnLBb{Bm1Z*{W zSmlcFl+683)0eF0+_TlcG}suzyt-L(b06^hv|IUzsL;jex7e;R=XXE=| z5o^0Pb_!8f*z;IfE_fc)R68A}C>HooHJz}tp~i~3W}QZt__{_RG;u~A!f2H2%HFCE zAVn)4-U#}w3Lxms@zto(g9S6LfOk&`iW7bq4SK|EXd43H<#dtN@n(untEQ8tB}#Lq zC*1<~my4GLCN;2W7hZsgWwF)x#-<#_U_FLEB1HVfgw*?ltHltbc_OE`l;RcfJQgz5 zHf^s-j`~@cz$FE70QGuCVJam5&~~Y-^1Y)HQ!oX^7~^dcdlNR61otEu9*aAmT$Kfu zj4ua$(o21}v;gTj1Q~ zx@Lw@q`IbwKozvoF)>^aPvB#^;ALXM#-?fj_Lylfvpt+)*hxc6$mX=$VG#&0ZFJfC zP+1CE3Aoj?Pmpa9$=|McID`w5Y6lF(?@bw^|4$s+xZz=1=?fFpwY&wB905#D(1F=DlZg}^r6P+o!0&W@t%I&!# zlQ}F)p%P5`L^DK@yMKzk&Ng~kv@b4z5#YfeEK;;vVn@J0VZOPU#?UOQ_B<)H1*b{7 zkq^${P39Mc46oQDB=5a^4zT$0T{i_yp+#Y@l@1H~C|85x>Kb%|F&U(vqEXpk3-^lS z3f5S-hDQ`A{`#$AOi0nI+fzAGR3RD6s62&X@(cHFoOITR^dv2Yuo=@C(18zH0ZWj2(vm*&wYLhIK~oES6JOEnnyaV>MLtsr(Vj;Hse5C_df*u}TO2 z4K;puFUF1JVv^s3-h!}@+oMYEkHeL15Fwsu?9-6^8D?ST_N62-%2*z?RR$wjF1o8%bstq5fSE73$46ONT<+f)LQ|3^3kv_rG!h1dKExmzbO??ut66Tk z8HC<$_iD!rse>&Wt^C_Ud;PKOV;kB63gEVDJso2)i!tG1ZFugR* zVH3wR_LpRH5e+Gx$@;^mLw=LjFT!66Wko+Teo+n$&^5(c(}1O)`-)aKltau%In&4tO415%q4?1i|1MzJUl)Yj=*CWtP&Qxvlfvjk9~V2onRNyGgdGsH-)uzmGBlsSy8Mjo~xwu(thJ?4hFedxUB!}??ZVySOb z$$VlVgL#Jx-B4R)%0G3-yhuZOA1P2pDjZ6Qif2!%KnBdY$PdH8d|!N5=P^HUFJit}K1~ zpob49j%WP#y8ddgf==Y>$=;+3K7E(0>`icR*j#k-gGulMuHt-uf&rnlM|^uaBd%*6 zIXpmpV+J7R)CSyEK6C~?JGof#AAfTrge$-|(U802R z%n^yS!Q46;!9<5=Bx*4aB>!ZJ=`mxxaJ+e_QGRj!{y?$){+heq%O6vMNzt|Z#of`j zz2D4o*$G{aj2J^3i%@XKv4^zKnv;Bl1CAI?=@7m3YE*NCxxZ0PO z18KJHyAgOBH!sEcW!b5oc7PTM2CB21^|c|DKXTeJ;qBKQ_YbYba+?G(zlH1Wf0S#m zO0Tw~m>@Z?@t&%*@K{ZPz>jm)cRlE4P&&;cLxR|fjD8$3&=9%esC|_7_6)?{N{fLG z9|NYTp(XU$w*I1?lil$2X`Da8Kk{bZiUC=LgeyqGr|>(u)nmB`Id z_OY^rX%@chJ>x3bo=VELQ4xuDj`+3}$hUrLdlY{ytqDt#EXH*Wwzt;EBlgElK4ien z5td&Vgqv6Qe3%bf-*!1r&W3vY@1JqQ;?7>@FO!KL&;DfC;=zJ#&$%3k|I7b}9>2RjZ6Bo{Yq=dT|76h=j%@by z4AM5V(KeR8BCP%jFX$F6$)AS5Z;%pmRXntXK8@}GWi2KNEto%%eg&f?|C8~t|6OUE z(oOb*WyYVgsx0exfzNuPj7W}z{n=n-qJ z0DfznL`UWFQ!MisxOfbBuFO(=G?^9L8nn7W&5#OdvFxTg4ty8Xqxq!EJ!)en#Nz16 zpRJZJ(MZQ_f|6vg)Xyq=>U-g7cnL%CXVj2Lc3BwYn4+lVRI?$n=a7>+l)6Lg68IFw z5~=Z#XB}IA{1kMyet;K*ijE(qYc015JA@pyU17s{w_3)$IyOwG7QSmCc`k+@12^(Z zf8@6LqqG}B6ln=r1vx0VV;%*m1)jOFI>|@jyHJDPSXs2SJ_%t1Y-N505t!pu{@TRhtLY;UKKy6ZBGHfgSFl|A}a z4n+R&=w5k>gHJq|_}iBHLcGmcxkiY3;e zvF5+CZSOt(Vw_M8PNUbD4<^RVbm7RNCojnqb67w`KX3POU-RX?_MR;nNg2^mA^eyr zk=1a{U9*GUCG%WL2gQojHVAjFnjtP)YqPZWyc2b6JwAhmC>>Ra(RO8Q6Cy2z!%ajk~#WSY}E@`sX5MC-u zhfHag5^7FZvv>wkE&e_oX#EUcUIFIderhtfK1%uiN)tQ>2+t{KZ**JY*F zU}XJ0AJz+ZnI@PjmAcTB&$wtp-877%p)tW!R`NInwxaT@mTSLl5@B*m@6+QXe8 zPMs>+!J`pnr=tvl7xbq8j+k>#%JO@Szn|D>U4KLK!mk6vPQUN zt_IEY0#N+O=v{I+O}qhkmk=)-%c_-R#H_kXt(ilIGKzh14Rlll2Lx=;-QdWIZQl3A z>YM5Je;jB6AIz11UeYO`niI;Vqwu6~b>3{Nnks^V}K>rF^MTDnplK#TN%(Ya{t}C?lo)oZU z=yRA{Ny3bHBEpox_~l3HzgJ38&(dIg(xzW+GLj644?jcbkp-~KzZj~dP=y95bn6e+ zDfB;G@sk%Q8R33({WfWG2eYD$+5`RLaaeh#VEvrK#>YYabNKz`pDMYQp_r6Ui#dof zu=M5`5vX+ZwRo~Uzi4kHMK1@3&lP6z3GN^AT`$PzPj(q{3>#CozDczs#vtOyP&84p()9oJz((a2EQH^~5$evJzwK0X7EIT{scEOm zyDX36qLvel7$Qa|^$O*u%d&g1eLL21=g}xZ!;80FoObXuXysjIb;3#zGQqsO2+#p3 zV#L@;SLXv(y$CSwUlBlM%AvV+@^XA&S|2HJN&peKDnf6t#1@-=DZD#7px86*ekZHX zu1Qc2Tat&qnm%u*ED5#zJZe-?(#yx$p!jFS95kX-&qW^{7-y3;M6)`A5qIV4w{iAZ zT7&zEjyp-!S2!Q#(D*jw>ENuh4aK3bSy%N@sl(_>dRwGP+l}_4o*v@tNkS-XQrvge zjUq6VuwrJKWFv8vG-p~$+`}@pNE<77((@pM65rS$&o~j9WRerZ#BiQ4d_+3&pe`2V zp!k`56{*VnjTC18uZ_5Gtp$g8Wxnvw=$OqJ{gAV(tLfahKpihhuAGb=>xQKt4y`W3 z&+MBeKPx<$CWQpiFduzMW$5VHzHiBAGS+{AbyBnpc<98+`A|rbm#|lLZhK%?pdQW} zbhGlQUa89UM`wa&$ce}!I^K(mL7ui8KJ6=^rGEK}FS>u-u$rpCtG-1%qhRD%yW993ZYk?uM@{ln zhMoRg>-_WSLU9m|n|9#5s&VGyqv2ECop=*H@imbddzbQMZkSS!kFY~H2-6KVDFDsP zCpgz_O49Xeif;MBE4=>FCJw3(znc3xcJH$C-;ghFar}~gIvQ4<9l+faB{59f66!hh z+xIyFFF5&8l*9@g(rtX}5>x!Ay~d#Di||>8&e7J*hC5|TEqm|PuC4P7N(V18d9#LF z%S^;w*BMO9WpyH=!&%!9iq_JkM|hU_NOnQY#Y~*0ER##bo$8Ex5W=zc$KH zV5pQO1eel_wdY8xo9_O_p~Jgf<}01c@=1Hpq&&E2@`+t5r?17PFtPUQis`TSHZ`gMT(aq{bBTKlH8=O%zX8oaS?$`sdA>W@;@@YTV}lpoUUXFU zQ_M&Kdk8-9Hya>)~ zrd9L_wkmxRkg-Wp^hb}0kqUXe3hiNLNET0Y@^Ay`on4^88702DW!}O z_Wnu|?`K8IX0io~u1XAIC|uh8v(jkm=*Ydzq(IdOg0Mw<@dQsKwHX#xeOhsnKk8$S zM1I4zocW0h>u}oNLNnC1mv?6X@%?Wv&$@->em^gQ=KDKS-IE7)lOt#%Y1D`4uVkYi z%F-lF(?~=?hormE`jS~yr8OjhF>h@yWVqV1H8&8P&0;h8h$V}!OaAa&OC0Vc(bAeT zAEdCFI(kq`E}4*{GW0F=p?(jV23J%hDJ&19!Y?%;C6Il>CIyRd60OfM&8YIpxz9*X zr%Ngvo(F+HMcNYGUT!f5ndo$uo0C%Wz*`ybUSH~B6Z0X4sp}JSev$psY?=yVsqFon z%_fZ9a18F|Mq?v%m$m=~1c2HOAUykR>?zNB#Mx~M+9L;1i=terx`oVdMZH5>!lsIZ zq>2>78nI8V*j?1K9>1Xcxl<%%7`2{)C4*yzroMErFA=#kZft_ZB6QKgUlS zy8@MAZMU8qt)nPm|MzS(b$x=Yt)1D;S>m(@T9PPp8PB6t^?ZA%5bx)=)6P?v!!nG5 zf0ESoG@)VpX^jOf$joFyo8JRWOSH!RT>NZ;PPTTD6bv=Bzvu3DB__YhEu#w`{rqLM zr|&-TXH|@vZvrqLWj{OJyd)8r1h40L;#5%RN;fI}xS+p8$V9*vY`CdHDV$5;E-)zj zb>2;ohwHdz)g)oioI@x5!d(FAAxkGvBqIn4KY#I$)8t$r2J*l|e`yiJH9h1v;SpgxmK)%f{bATzp<*V7mawI#=70=rCU{$0}`*%9UarvNHbhCK;Olz~d2{6?DCzx2yS0>UgMZ_N% z8gK+U#_cEi8)c9{1_6__uxxma;k}=yq#j*@-kcPO^uwkNUr?jBX@{T`$ zY@#63yE*(Tc4X_^hv%5(rLc2H<{7Q7JPaG}Tubg%lO=zy(L$gcnw^Zawz4R7Ncy|K zH}6ucFkuHaALMmogg4)%2g7iZRZlSAZGo-}*NpA=K@mPljqZHc59)A4{bj6lUwp`0 z?ufaLLQ5q667`K;IXHSk2xp* zCvkpFFg)UUIT_H5Q-*_@d}RFnUCPm0OwW*D7g`@Ydgdf&%R^wSB4J$k#bJnzPTOz#b<2nB@O{l?o#NQoBI!OFx);#!M~{IGo(k;!=RX$>49T(v^l|^eq{2HF0rUkm*Z~Of4%HBIiWb zZ&!}K4AZml^XZgSc<`7nMFM|0CNZEP_~<*Gguy~D-Xkg1Hl?`ueI!MJkpLC;JEQ*p zu=ghLRCnFq@SzM*GDPNi$QVM2L?}a)j2Vgy70px{6h$SJ5}BGLGOG|ONu@z(5Ty*M zNE4+Z(s}p#?e=vJch~*DuKRud&-?zL=jzk?>}l<__u1dI)?R!6e&^td*j+L-Q!=&k zY{|j#a$~cE#)#F}1wPy&KY(5XX^%orSKdzCaX-h!Qb|OzK!^1xI%~f?d@NH;d z;L(DYwFaA)U74AXzKDBwOw)#*$J;o}!;}t6-kgl?pA4p6ndIg2`DXZ=G&bW44r9fJ zK2BzXr-q*vY-gQ}zK?;v#4{yFMLEtkQRlY#1}Sx+Lt5WiuK6bsaQ}O4) zsUhQv`v*Sd6jXcoKs>@A%#sQZ1Zxp0t1hP~GqDwpP%mpu}p+Avax@kx+gQauWbL*YYu|2+aMVj>H zf@@<2Qk<7Wc4@x7;Pl4z1$UntpU#(CwkOtnx_l)@>!M+%g~zzS&DL8NybwRIYW0iB zJBFLw5|@SuI-aeX)S@dlHf~@JyV9!Rv`TxH`ld0n+;XM{MLVx;J5aCHSKm44b~7h4 zP>kE{x>*0Jy&G5tmQU1h2&nYmaVhirv&kAamOG^E=s{nJ`LcKRjk0%5S`Y3PIH~#+ zMI=`z-+RQ}DOd5`=Eqa??bf9Fmv#CPZdVIr@A~L-e2|wyUw2)=mRS5|1y85rOWGp8 z_7@TZozp*MqOUPrI`usG^jIGc;UKf@8jtuDR$O^Sq}&;B7($DWu{Xk^nrHa0Y~ zGOv06)f~3DiC2fJ#Y)BYm#(N@x<+K-Ezk9J+lI`;IM6FS_UNsG3rz*LOf8R<9Sp30 zyGT^6p>#q4`U`b#zT^6bt{$AdW%z6G!*l+Aio@&f*s2TpuBf{94lTu@i>+1lSHJMr zEx9B#@nnq2$`fn&I)5zwkZ1Uy>o-?NQ=$SKBy;)8- z(pDC_ISTvQx_bC)fA?P$$l4(j&64z}9D#lF<1#+gCjX^_9wDn$Jv&MtB<(({^4><# z<8EI6`f+jNYbz3*_94k-nG}YH$b$P^_BdGLMFuv^61?p+W(k+1pN`e*S-ji(?k1)uFF)}} z){=h$din8r`h&4~Cme3SE`3F}*f*DRMz`BE+T?5Z8Y|*skEU<$Y|&{CsxAApBWtO3 zua`EhOF3GR?mwfTk-z2jlCw}95JDJk_pHkB+%-p%oP0n}Q;p;J` z^>o&)Nfy1UQ`7yEgAW(_y*w1TQQ6ijJ@)bUlIV-Q9+rWU=d&C4e;oU2zp=5g7;XGI z<+>S@eNRq2UenGd$!@9sOuK#gn0G(&ok9ocdm~xIgC%4pzj1SaNNx)<|qrz83W)vsSjDr!oD}w$ka(lb_q|<}w^7uD!?ok=7Hg zbN-FlWqI{xiae$B_n8Q5y3%%83-YcXPVJlGeMa!~__&h%HW?dgKOrC>PC$5x_l8{=8Y;1e1`6L_ckOiEb+B?@tPT$Z#i)ssbgo{iG6sYAPGR%@G{{d6hN+9@pJlGcr?t|R&#rQy6uw^@U%*Cu!# zUQu+^XOefFYloWeN9S=z=I&b(SCD((p+;6iS9JA<+G%|CKaK?#?q~DJINmbJY=-W& zJMV%YjFWWDP`SS`(|0^gV6XP%@PmP;u05S0G;MmB=hV*$x4u~o_xW#At6sH?)sDD!c;O_4D(U4PLfkLyy{6>#xKAxfQBPepgd+V`$x6CFi*s*Bm2_ah4$2X1JyOrjZKiwqFO*1|} zCf4}DkFJVP_f75DA1dXA zd{t`XolonP?;8~qW;wE@8?9eCh;Cv(?oPh7s=RH^y_@lk4h;id$Aju_&)29lNu;T1 zx$Hi|9`szr@bJUepD$IV+)wFQ6#MnKYkN%BB-sfA*^BIF3>b#cvlmNTaW?n#4k+u{ zyOP6$ZCzbix+lvxyEBIc(qv-GeD{Z2j$Pc~mpv;*u%ERiIr3^?|73;oY4velW#$Q@ zwL)F)+Ou9?`Bq-hrsu1}Z+89;`!U}3qUO!=b7C6fSB&>q^Gf$%(cqefJ#VabboUs$ zUN@3fKVY+aO=Cpb(`U01Zn2?_eRjEJh1-so7s{Gr{5Rd&7F*848@ ztUoc3l_1f5>$E_t9p`NWeU8xF9nvi|vDXj!{>WIl(9y9&^lVh9@8PoL&k6?CjosIL zf{i=4wo{{NW6aHEhs-nruT9`y{biycU#DbE=-pg&p^+?e>-PFPYik9G0IS#JWc?IQRa8x5W=^OqVyR^v#t?(c1F$se)nD&AHCf z8YcdyS!2tbT;eKa7Uf(&@c1L=E72=@ZUG19=PJoP3${@26B%R@^65>F@Vp_6MWLnJ1ynW*XMxoh`AW#1Ir!}*_7 z*Du~Bd+xPx%++~S=QRU2CmLoxG;GbhDwZMR{zcunSfTa~eS$#FwyFx%w(s}uc=2mY zI&6GvMuW!P_cM70uU;0~8QAjCf@b-+`IRJpMP>R};TOED2mQ)8%U^vtrnzTMxW@Xr zcX!s91+kvyGvV(KDa|)b%x?<3bMDAFf9aAt?VMisX7c2G(&rJo?Xx}f+(B)DFP%;= z=Nz%uSed$B#~`!$?24}!udA_1&SbA_4`-jUg74LQ6)`Q2gkep7m$7wc*klDJ#CIz# zKkRyyuRmC?|I(7v0Vf-yT2taWzqKR}@ag82)i19;o3~giVQj1MN&UXVJ@TfCRSg^0 zhlY2*Zuzw3K~?$9&5xhlnP1-WEG2+T-C3w(mh8SQSMyw+3;VSU@%QXx~*MSX4jS=ScY;m0MrOH7qmpQev( z$v@i}X%zQmOs@QAv%#8KN9xQwmpm4=nz1-c#%b&jTcLdH$FDrge|~A5ai=pR_FcF- zm(bcx5#xAPmF4-IF-tsjb^T=N7Y(0W`0dnld#6b*E7rW~b}g${aUIv1$LS^3tNmBH zBo5emEN-6_G%>l-^Gy47k?POJi^|<2q}%2Ve*V0?J4>p>&;b2Wug)2zJNu^wL6;HdX#vB+m)POi$C*>;G)s)WXMR@|FEmMy>|b-H#v8T&Ae|H zKQG^Lxt@34vb1Hz%D!y&yNi|vn5&iPW-L|EFe2M(>(>4d&D*EU_G({b^{MA} z*|_$h^FWc`SN>^thR^UO|Mat&|Llthr&>a{=MHC=3;oGI&^1{W=j3%XDhWmKEm$mp0MZBM)j1YBz6h!F+YyAO;cRK8dsLUU08j-b&}$; zpSMEOD%Yyg`7@uF9d&W=O657$p8rZeB67~0yhHoOT$?_oHoYukt2}E=AvSqwy|I(_IvyOIt+xl;hczjtFKAZPw zs`s9=W_K->1_OsyZkn@9s+ylBzqE2FK1}&ahN#Jq2is!XH*eM+>W%)MS&_s3_^jji zRfZa37C(IaHH*W!u7}I34H;>hME~U8-jL?o8?ah@j$FrERh@P@hpwXu>_clxvDvd{F2yKG>%V2?ac(Li?9t<aB)ypHm`XtNN{2T_09hjBy^k#2?FUzoAmwM6PSw zw!Wk$>VM>ogtgU16X9)o@l!oX+Qui5c(9x6&KRH}dk7x7wDj*dHi9 zi(Q7TUOZDty1~YFmqv4=)i}FWv3c? zRd~~sRZC-f0*~F1oE_q_hueC6$TbeJ^>aGUYeZ+Pu@B`_%5Ak4wdC2(_uT(-cn-}n zbn$V^d*5gmJN6ac`r>z5^T?9R-%p$v4vl+qf-_9zy>>&!dfDV38$G;Lbb0mJop|o{ zwq5eyQDQ1JK5fU&j?Dcf&fGG~GyL_A2J(b7HGbR{JmbbQ7A?!4LEC543J2}c>bQhn zkAYKWO3I9fJ*SV?zlf6cJAJdltod1qO69|e#Y48^_oBDTHRD}3>P1@K%DX49v&(1C zHa*sRZdaDn+~3%uen=&n<)BW=we_E7gJk1O99eoHPrf#dm+I;&0X5t-gk z98&XzPmXVXV~?&aYU+)m)*AJ@Z#x_D<;wnXkC$h3bCx;H+{>Xi{nO5n;djBWCenG^ z40al6`#0no&xtMKDw;ZU?B18GN131E3%0YhZU3nMJlXb=fah4n$E~Zfk7b$Pd446W z@Yb7j{0;r(d%3pnw&9CUzkdEEU2Hj5(saRn&1(y$^me0hA1^;~liv3{ zu=H3~h4}7@H=PTnJ1O5wJ-Q_HyDGea^%K3hR9}B$f&W~U=ZEx;c(HL5`ArGh>l?Y? z*vyEXUmh29(0{hH-9%r0aP-*BDkkBvR3fH>-aOj|eTP<%>&x&_p4O+!4T2oo-PN@> zPM9>sX=3B1pLNI1+>sPw%UE7_@|)!HwJlX^9M##bK3TSYT>85vzL&xwb6Enc>@bG#^RYTMa@8c3X zvqbu{rx$_)HeXb4UpW}Pc%z7T7VrK`b*b0=7B3t8Ttf>ziiSg%6GIDkF5Vp{RIzVj zSpia+bvjiOPwbT38T3s}Kr!UdNW@Mh747PVIiy z@u<9h(1YGJZRk{syFu@ZlY?`(*Xf+OexgM-m-co8*M052%PZHd)TwI!u_gMtyi12y zj_HZw?!fL#{l<2li<*LmA3Lx2)$y;t+t>2#k?TiCb^cp1K~;vlEfHHDwSQg_9uwH5 z$s_7|wEa!M=PlYY<1X<}wmSMeJLB5f>|XCG_olOKlYPwAXlB*+tsj4FFni4Ni2g*D zQXNsR;E&>phMr?qeoNP$UA)t)$E54xrO>$b9xuNJcA*LRFA(SQTPsp+RU7 zkppY_WV;R7-k|rH^y-dXFUr=OxXV0Zx>wq}%=n!SrQJVc%`5xFy6Rp$Y+aH&!(Ghf zSbK`Oah|8+#Uq*?&!3snpX?v@ zx+VRF3=W-|Unj85Wt;k;^V_EzXg+iwHqsqV)_f4(e`v~Tou-vnEoz5TH^q3b5WP3& zYCP|DS>pkXZwfc+JHy9W1Ud5B+AC}diauL7^ZJiZDKeZ@)7Lgz6?4l>nGkeH>!rBM z3;vIf^jBS88)=w$zg&NwYQJJB?eOPk<+<#9bw*!itxf)x%Mx_2AgF5Fi&=xdk>(y# z*8oFHRTgabU&wEv+vPfCr9lL?{ z<<&iIDushm(YCBDmWs{q>ne}WqR+2Ty`WQd(sGyeK#^#3a_F}m+y+W5$(|(>hi46b z`J8n}YLYxxp!^TT7O9V~r|+n@THUnT|I>zos_eqWSC8~Q;cqDl<_z$y=2sWqmmrjC zZgS$pc$lo+apz~s1XXcz(AoldT{4%2N*S~Pg+ac|C^73yrgOCGFxV5rkmST%<&Mwfo&O1`Vje3PC_aT|3D z=UxmQYgv2hMu5`c@rey@0$V;$?rV5_%=0Sm9F~~^iJdC<$4&Mh)Sfqg8Jp%e-j-5N z9=Dw6epkg9%Q-0|eaE+^3=HTzxE_2rZQt<1b-WzsG{U%6YrS|neN5@3 z@qBC9PrNtvP%QU56Zl|)eGa=wO;WS_#qWz=yj2OSelqafe zV#@4%ONHhyn|%%6t#j#IkZiF=TcCKJ?D(;^9;PLJIaO?9j?V47^JB>l`_+8b4yPYQ z_eb}N`LRE&^q6ld7$bH|&M6|f>PM~rdOhtZ_WKQ+FNVz*+!JebY)gXoW8IK5VN<3& zs5K9gGYkGaZLs6v!A?`NoyU9PmkX{D+#`8j>RM%b-{I1=#-BB~SUWA=Y=8a6aX94N zP~RmFL6>6d{+8x2t6ehVt+>Mq^Q^4exkA5sIMj@h_^dlU<+|Mk*IH-UkjGoM{Wxw? z^mFd&ev>ooSXfv(dWqRH?2x`eX04q zotMgWM6F%UC&qTTh|KCX{iyK1Nd9vB+@paTlNtwm&ixRXnbFIV8CCv~YaM64tow?W zHg;MIpNY-9U3>Lg)-LPmtFkIryxPIO+VRN~VcH8B+b>1!;>X_)YftED=JBxfJm+#_ za)9x|z_F`l(YQTy*km|6rBkgrlU$W=g_hT)y7hTzYuVHcXEa~tlYiF5AFSQqeL?>D zlo{qJL%ml!%!ih(3O94)o;GcYaO9oL{LqrpqaHe5KgRcKcSp?Gdq-ErebU^D3*)@o zX9VtY7qRfz?amv=dP1M8@$kv^#=C}A)3)xsaq*m9T8c?$*N{2QuF@qZQm0_eq_OmK zQhc&p7i+_PPh>w03lf=CcXicm`xl$T4q^c`XATI9S3eN+>mx2!N zPz#IDtEw}0X;`>tpKfJ!*O*X=k7jNGQORo0y;tsvmb^8FZ;*Al`^s3Yw-s!~!Y>b} zY8su0NjUZ6HEZb14`xpX1sh5i$FlYOr0644?d)I`w@f^U+HPu|8uy4}aTy z`@&Y%CX;59U7@lTkGA`=#Tx5(Eo(1mdZ)0PWoGAn<8iweoRi{Dk4!h^oD=fq_F0v% zTIqek0jzN>MF&%hAGlN<+UDSSbJfN9Qjt#M16ow>-99pl&;PEw=)RXO4WA>O!Y)VM z6JF56C!Amu;_{7aj+{ooVEz%owi7OE76l7hL`dD592Kl%b2x223$Hg#d>Zeg)81{` z(`__gKEAx*$GQ&XcKQ4(-1)LguFuM!qB*f<@BTjbNyi6RVoPZXuH_$&`Yz{Vd&1py zI4(Q&d;X4)8|sBYqP%XM%@y}{R&9?JGAL<29&kiu%lDzX+RJ!l<1J%)77ExMKlPn! z%Yl$snfIR$^OyO{D}OokfAz-+`>hY`ewL-GyZ+2hF6CpBZ+dskJmqH4v|DfEj#8~V<>9dv19!rw6srsz zmreX0Ym^o*lxX#ieQS?Er~H_2W~U#nT99CINbd}XYOsZ~f7z#ajJv9Zrwa)HI9 zm`C2TbZsXGPZGyTBaWkNUqvHzG(PFnl-+GS(z#qm`Q~LSVHV|67ZyJ{P_O(bOUJpR z$~*V)ysvYD`e z|GrxOl?Coo#{RgeVyf@UXS7S?rz;+AG64&IaaEjGVa{y0fb zLMdY6yBCXRM9%(JTlTqivzSlnt0!X9J^9YRO*^65Ln~LQh}3;mZY-BRb?QL0$t+VT z{|x(fUcZW|)spwiIL8n2bt<-}3%O_cI^Mp#-rbtTZtByxu(}z0!(&)#Zd92z=jNCg zd9R_xx<2oJ#FNUIZa0suNUdf?`J0fj&3>#KzSkzNZ&1@IJG^jBK#a~rY zzCI_&xa(b3)FD#^rPP~pMFV5KoYfs@E8LyS-N$$tTO}G*o*DXJ5Nj~)nCM}n45vE} z{3e!1Up2V+YG}jj(BQ_9?&R;)>7qX+b^R;N1$xsO<2hvQ^|wraQ4k{LE&bBKqHN7I z0g;7q$8>lnd+^`f9T@4kPgQW&h3akN_sf2|E?9pi(9Sqv*Yv6^u}pCv=Ov|$s{1#s zvborHHHfD#!?L==*RVM)-XzL;;ADOPdQg-8xzQ_-7HaCp%0I<-ldfBl17BFlPy+gW z1=@G@6!`grq}mR8g1(?fnOETXTuXWrdNY2TUX9U-#8=hL?Yf>%U%mQz<-7Q$zVG~( z-!8jBI~Z-!BX{lH+Ntm@!>4LqN|oO`2l=K0#8LK~4(%@v>TrDtn)@awEsoMSkSGkNp7QboVDccYpLHtTdR z+By51#NdKl+iUvHb2{(d-xy=*_~PTLgi;xw9bF0MOaf-s33e^o_3o{I-^J@btddr= zSB)x<9qeA5tP!v}DSz30+l#@kyLS~Ad|UE$Xvw$XB~?E!EduNX+>4W zY$kQ@OKOv?TZa^+ZV%e{XPtdj#3uA1LMo%_wwjtT;+{n-SRC z7WMIqzSs+;*@rS|?a#{s4|NAdZvHkb)WorJZ$5`HO-JF-y%lE!IlkXjEl^{z@^fJK z6*oz896Gh}P*lR_OVtXcQTuZQRe~BE@7SpIpwD8tDIvpaW>fyAHyW0-JCdALUiAwd zj#_>?nWJ15GD&~Jp`6-SffZXPemeE?;yw~7kyn+ zCkaZGFIf{&@=!grG|EwAUbp&QG49=U#~wykt?qKZwS4`Zm>`GaPNmar1mfOJs_uWx zD!t1wg{5wl(;&-q`2UPOC2JY z0r70!pAxJ+oLvib(n=ddeFA1$|c4} zJzJbdtwJZ`@S~I4_f`ikQ~%+ozb>c#RjzJbS;x^LPml38^IA_=Ex47ocimaN#_!%s z^>|KNS$6O9KE+$&x4rJ+BYpe9rYG?~*W?}AT{7ktEji=bcB5O{jq3gNlT+R{J(ZW% zjS5uMEskNIzCp35QQq_XAw5;iBDEy{&{W@3!>)553Gd>s@Sk;3&dPNby7tl(zELr5 z=i&qxHK9ny^g@*vDyy0~xxHQ*Y`Y@a*|#O=)Z22~#70+6@e8ij;WZtmUgy>eesKGg z9(X#MGe{%t<`wPcReILDD(|jy(mcPmD#Pip>rN|s2UnGQ=vM~?i!RS&yYw(xw||@f z+y2)qg7?anN2;FT-sal7<5`$`XnFoQ7qt#MbN_0Ab9X0|`5TM=u%kEl>#N#)wde3> zv*7U)G`qW9#WJrT^Tw*(*4vkOEv#BNEkX0_9+pMi5$o=m>Ln}4^Y8c937Gr2CF-W^ z`kkw~^*+=me;rKiNLJJul952$hefahSI#aE{f>t6P5Rt72m5^V&hTkZSJ!>lrFuNC z_}coc`xkVlK0VQT+9qO;QNax7*C*#s2)abi<##xfRu$HFI>=aI!~GH|me_l)j*f}7 zewL4y%05Y+C(0q+JNEfUiS}uCb@*r|8L|tKYztJy_3d57lp*FE+*&EL^%rBYKRPHrs_+rr`moD{N>zq8@ z{2F3>26>8QEqhf~?AvTKZ%;|z<^6nlk2Csn;{8L`Y-~D`5X2Ltv2pw5HR|4i8tK>0 ztDRm{Xl2>*=IN>g)yqTqot0tSyUub~`YAn6ZXIXFqObDxh^D@>=)%*l9PRIDAFX4{c1M?^T~X5X>U6Foky=gAQl$4iAzPj)Ul=aS_SlxyYU^zQ69|Kh|Bqj_(B z*j!cJvEg1$-=Rm}R6_M97768T%M83*x=V16cwAl9L#g7cf@a@xt;7Z#y-xRKJH>9a zpJBwkm&;bLNUt#Uv{r!0=a!iT4l3z_#a2VZ6;)+Do|Tuz(bgKtExh+i?z4K@(K?n` z%SOwhTVj0@=+9&Dzqc2uOnz!sB3t9H6*AAGNtkEv;%RYogRc8M{YMq+{4H&{+G;D0 z>t5S9e!K1NC?TnS+2g6~4?+)lusx7Y@2Iab?N}tk;i;T`Jmp!&N=N%(K3gvCThLj)4EmZxG73-!T-951pVw z@zN<#c~X47!#Y3rr3c6NbT)^1grYy=RaTa?!c^9=>3)x|hHI5$puuu04VKlMx04;e zUCH^#w&;}Ci!>*@;D)OET8lrd){XZUE?$@GyUb>*b?ElvW}YuO!)p#WM(A91Tjp4r zd*+N^F4x43IY&y>=Vy0YO!IG1-kxALFrKa0H9Xn)v{U7kO^>oFoz&Mx9DA64rJl=o z>hn+^mWcX%_UHD2qh$Z-BuzWTR9i<{4dJo)~JzBriU)Xxcp4p9ovzP)5E zyq?rwkrrN3aX5xw)%5kgv2V_QblC6Kos0fL?mN@-ivyRwxPRw$$ncKY{@mszTtXfk zP6BQs79tOACB1I5DLi#>TlxM5m!m|=l8dYsWA>nL8P%k_-tq4fKkZVRKgYZ{u1==y z^sf8*laDHHTqPcteEY8Z*0mZhWBapoz0@asJ?FUZHUA#91A!S89_tGOy;m+?Z#p?N zU-MSo0@Y9*Kl4`m8iUHEEQ#JH=6GHesuZs*TAAEkSI3j@aVbIbysc=MLUC@o|+cI=1MoM`~%co?33!>Y||a5aIrIo z)j3H%Q!7vN{L8v=^CaTfHPo*c+|3`Gu>1Utsq~9a4>$g(Uo6}BTv1I$+Tvis%7(nc z5Enz`(~G958oFy2?^2)lZfmVdWa@p#7iYOlSKihb&WdeqX}O6ece!7HW?y8utxbql zOc$?gjS-vv^UK$!rSy1J=ls;mlf6+$nD=bx+2#Jk`HIu?mtOt3oc4`&@BU&I)n`{q zOHn|%hjxHv21~mZP3d|+2W?e&+t7=G@jK7Sx60hQu!iGy*UxbQv-8%`)AeFHr@uM! znKf_O@iB?CCv1fi+SxL0n8v1jbwCD$)yCi6*dxt`9(n_Q6pNyBbry0DU zi?~)hbBE3e?OGVSkFJ+TlNxjrsQ%eZ=Q7Oe>ip@-vvbMMv%}w~bMKtQx^2Z4N4Cnl z`cD_|#0v6b%u&R++sV~kW|wzvZy+l z`xnh$GrXhZI%lXZ%W%-euRlH%NU$$(qPbWb&+ai0xIZX4cx-RJ!<$5@fv38|{m&Y2 z(Bs;6BzLY5%-wYQU^Tnp!}hii4wkP9(N)uL4-L&;&Dx$BDcrcaWkap@V1@9B4<|3N zDARuGo&NEzWX-!x0||lLp&?wM!uBhorMazH{v6L;Q&2t>Dp=sZG~{SBPE`2k-#;tx z&kFpr0{^VQKP&Lh3jDJI|E$12EAY<>{Idf8tiV4j@XreTvjWtO0gV+c#27?l5wRg+ z|A+oZt^r^N7y_1n>3?+r1ns~Jcyl7+Ml=Bt52A_x9}tZDe=5Pc{&&-OGzR**5rrk_ z0$}^^{sI1gJ@n&8B#cM`ku)Ni|KS9m;r|Uuq5l62_lk3q=85eks+elh>ZU?5sdYB$q4oN@1$8s{{K#bHUCe@0QH-RXgVToL@J0t zZv_y6PX~>v0C@V82cOc6OF@S71Ur9ko-hT%LBDw1k z1t9W5UbQ95^t7NIj&` z79AZ;@f|vJh)SUkX@hh$K|3>zZU>n}#_#0B#Kim|6Zn|t$IMUo$XLwdkUHjZpq7Or$^Q11YmCGH)UaQo;*)Qcvp0=OJ=QJLyO2iCjpb4zL7F z0b4|X@t^&J_&|Z5pMUTE{rl;0adCgpkt0V&+zxe6X0C(J4a6*y^d%{@ zLmwzJ=Se$B2@jD^c$sCBv3}PF>PS6vp7dv~W6qOylE%l!|M7VUAEeL@DYU~pAW!D} zJ3i(fS>e%dcsS1 znA-^-Ny#2b`axgPpQMDBxlHPyAL$EqWK8CH5nj@t@G$d3p71f}p%3w|gr9jl=03nn z#`t&ju-43TAmfofM8;?yQYP~ue55}aALRcX`~?IA_Ca5=XMubm=KYR2&)g1ufJpo3 z@@QTt0}n}IT#{$*%Pf<~7|loOi3~_#El52{NjsF8^Q3)rN1Tg)p_RowzvrL#184FS})}QeJKj}kw zp&!)2ydZ^oW*(AdhV=j|CNkIn5LrU`hmFz#`BlRR@mIHNvR?p1Goc|p^bNlE#Ge2`4_(qpW-#@Eo z?sNL|=|9#K@&pUOjOYl#o}_;V|4EZ3?ITzN{v=P*)YMcu@+I^!V>l3TQpCZ*_bU%| zjC!0OS%-NE4`~~nvj399LFeG${gsD0Qa?Jy{r{DhozBXN3Hp*er0nbyf0fx#I+}nN z>TsWb&rAA_{X-7sA6-w{AtiaXQS%`6%seDN7V+}@>N}Rs!h-7oi^RmlKl~5D4AP@V zk5cu3>0kB#zzh9=l9G}r{6Rhtl#d-dM&UnUf;gRrN0L5af&^VqPz$A!^zq}xsWP-d zJx)m;$`W)*NpsXsipm53ND@P3LZVdLq)8emg*+iKIybinB1RdviPI${OsW2)9eB98 zC)2sOCZkmNSBmoj0&0{TE-oRe9(Zt`QHH+IPfTp~FJ8hUNFO&2XwoPWpk(m#EB~q& z_)Q9RKv;&jxG}}U$tghRb;BR$6Qql~`m8AM}aWUi0 ztOqy`dL$sAN@0p|23!g8|EkA&1G*(4VMgg6)+M3?1b;>fviSK`{?HH5f#2azbbw$F z<-dvlUcer7fan3FAO}*|Z@}kZ`<9?^7ZTF>!=^FT7*__5Bx(l&{4Ejw^3-}Ve1ya( zJ;42eUq}di3zPwW&<~;q%(^1>OE%ySvM>+U5zv#-dI0!?UV)xK8}<>{2Vfg#))nlt zr2pWBKA<oe56Fgbi7en_))(RvpdIU#Ai^E6 z=cNd+02?KEF{gkTv?2We^7&s-P;f8wAq4V3ABawzIC0_^{sdEL`sB&F^obK?D2%~Q zeuw*L{BfD^!X80<0nS62hew+7Ex2tY-i&<${0!!mmbOHBybob)iO!Hc1M32`!JY*& zu#S+ufstap5TSHLQgSXe7pxORUtpgg1o&b6K{o6yBmD%}JMk^pe?U8o53;e|5x)W{ z(HHC|Kp%h~|cT61FS#}vmapOp$+iYW@;yXg`p!1U6ZB*{xAnvSI`NpH&_RVF93X? z9pp0OPxOG{D=_xJ4;X=dL!Pt)X4r3#cmeAI@PPd@+K7%2{Gp8f1i>H1hB`tRe-c}O zF5tX?fI8v#1?=X-V>Pd2R@<~ z4E(`IFt7)Vq@)0U*<<#}OEF z2KJyEST5i%_$vJ&95*aHwRuL503$ri;X zQ-0|H_yOr(dFfv`<1)m3>gtOrpAPGZ_YB}6`oWw^qt82)E-#O5@Ao`}Uz!?!>eQu3 zCd8Il=df;&a|k|v0Ol}1MMdjhaR_M#--7oThK}Jgj+&Y?HJ6ckMfR4FdIhn+tn3^n zUBUW9^bGqDtX~iZX=u1ppP#fdbOFaCShprqbCZ|1pm>QsK??Rya3(kr9e^@m_;>IZ z5)#@AHV#DOk}{-l=Ky1Z&j%6_nMt2FFNiuL2@C7d36XZ@R9JWhea@T>XzdqLeBd{j z+rieE%kX)PjQmj@@Ijjn(j839Wyp(*&!Ssdg`)ZB|KgcO=^6NWIG^AY=V6X>=Wd{j ziW*Y&5DNf7tcm$Zs!fe)VzL(Lp$%$d$iaOyQ9q!mzetlVCuc@CG+a)}BlTdnn1GM) zYohUI(k(4_pfPkQ{Q`aluR6*DL0@%hZsz7|(Yno`$`A_x0TzHWU;|ha%poQ9fGOhp zOV59R7i^r!1DPZb^>FV8V}cD~JC~*A1a?R27%A8%v2XAJvUC}l`N;nhUqRY{kL(kS z6!>9n$ew`h58?$(*w^DckqzT9_~0x6HcesyV&4)Jt}rICd2E+tpCG;f_k*|seAmeR zgs~5RjpDdqGBq|}Ov;RX0LK9&et;C_53*qnP{zDq_aqjC{Q~a?(8f5kGWH09Kj1{L z0=$3#E5Ph;;=c#_FcY&ZV*kvz0!Fa@4BWA9Fnl&(it+!i_J6Dgzz-yY<_>2|f;q#7 zV;>FmxDNIby#E9K1aE@l=(B@3l8fU?j48v1ga5@b0`c!SMhE#=S4a#v5`VI<0}tSY zeKa{&0`?4lPILg~E+sXO8V}=-bp-m7n2($*pbq>1qYa-E0e@!PiH$Pr0K$LIU$lQk z;026{?m)^+a2Jj3AMd@eE)4t``|(KZp&rKtzr!Ez*9^PIeVOrxn2v!p#vCvv`XEWg z5+wd-v}621A24>1Vgg?dGDqSK*p9|uoGJbXJ;FQ$Lq>}A0pw#l#dic)4+yTrPmuU; zH2$C?gcqlPKRG+XUI2Oox-4ekYQox_#7e?AYgAZ_L;7|Mjwtr@S zPHda(1K0=PzF?!w_K$fmh79}xThJYGYR@Kd8@7AEl7TJoK|jz_X=w{eHyQqc>;poG z2l_DM59dnZU%mmK4FC(k8t@_56YL0PfGOhpTlfR+%+I4pekA^YA3meO`Y>#sU=AtK z2Yd$jo&7WHo!JLK9^d~E9bwoz;7j}gPBH#){wMoC))9>BNF4xuVEBJ*)6DpjJ~+kp z4{`|)#+=#zGjsuDWBbQ=V+^q^GvWkd|B`>;&yY#(6&UvcpaalO{0jC7_)dV?{)wHE z6!ZY_`n&jNWMoiw4}A&lL?1@tkMV{0`|sk9`H5~|+?nke@_7G;XA1;thFxP_03O^I z;xlP!>tFaY^Z#osK=6mY3`|K3KzsyFffw69od1cgkUao&V6;zXwtr&x*f)^91+WAk z0^>8{KO&Q%OW+fL7xn~Z{6QZ`902$eoSCr#4519^e}X^sB?Q{8{q@|Kv#&bCuO|< zkMt23UxGi09pJeV_hr|&?R|saH2bh2d+bN@tv=RKt-U90a_~ZGJyJ(_Q|Jolv zvk;@O$M_3USP=Y~F(=snC-|Q|dzPM=nfZtQK%SW}{unFx3}pW&ID_4iJWk=RN0vH6 zfsGRUp$+TGXxyQUbq9P4LobNEV|=j=U>^c^DIgom#PWWx12}FV@gLC(j6K1ZaeoZh zz#SdTe5sROtV z=n#Hp4bLpGzhL-*f87s&hw*HQ8FS(@0DCMOK0Dw~=7Hri`=pWjIC=6k)OVzx04%`% zneCHc1}W47rikw^#eW3<(K0{|)PWwrm;_t6|CNw1rtWmG4#-iuFfuQX>f{lRBDzOg zgyQ3=R2g^(AJ!d)PCyx+cZ-V~QF?>N1AQYpM)J51@G2-cP-74tX5RvJqzrkuFN8T^ zUBNoV@D~gngK_2L7E;e%N9xt+SOk}0Jw!$IkzD2&1n!)POp;1bYo@5^h~}X{ttXxz z)_ussd`Lf-2do9mnORQ&3yA*-HUuYv-`~Vv0q|$;%bW*U1b?{41Co=oqdPjrP-{=> zaB4%f%g8_qWQ*>_7t$RZ_R+PqeW@~|jquJ#J{%Ls(A4yzTU&=w{lT}7q>;Reica*U zOOH_V0U6Mr@WVJtN-k*p1*pynrM76!(P++#s50!qBgyJly%jaKgTo%WmXH( zCGrWFNIxh;UQ^Q(-9zt0{eagJr67ZlHB}FDla+-zInlkm;whVeGA39j$YUOof(|-6 zA3%5K_P=C+EGxkDZe(}J*LG}(}+qeueMMMmkY@W1_w0-Cg&!0dx_!w;em>28_ z?oH(79gttvr0gAR37!++J8Rf0uuWon!?6dpbsU3${=jn`Y@f`rCGi2o*5MvX4*A>Z z)Hv7&V19;elbBeRvVZW0*#5~nkhOpmJ|`m;6*WM-cz+|lYPA0cjDQGUkp3t5XJ=>A zb8>PhSs)L}kb+LZnB?=oT9f!6&ll!Qh~Nb&=!62Y3Aq2qx z7+5j-{BHjTA4>eF6m@?$Yt}*PT`TN68Sku-y_}?YZ^r(b@Z$K7`JFY!zE9#lk^(L` zPQ&{=i77}7f^`Bvg9ICqlUqR90bUE(qZlzU)R8@B;za3R>nwx(Kanj;VFCL;;6rG% z4*Vzh6Bz_|QfASf<47&?ffbm5{W>9sYV`MJii{bpoj1k}lWkP@{;`>YcKk$MM0YQJz zE0PDfP=+yK?gVFi{wM1Wco4$2$*co_5%h&R+@J6=rvzhs{{uJ?`@xtouO;+@eOHzV zf5v_+EUZs=a>_>M)~84hj#6neO2V>8851H}I2%mAJ!1%*insNUR z>n$c`O!x3ON6*cDPB$})M{`!AFeY*_He^2~m@(4d<3G~(Gw%Vv$Detw*r$WN! z|3~AG-#7L4E}`e;J*OKRAENO8SNmtipWM-59e_0^_RsJUjQcs}J3eTKIf4CSy~6j- zc%Jz4fSzJsuYmAYMQ1<Kp${Ah04R4Vx0h8m_prIfNsEN#d>=^`q zhV6q4SgU`JKSO6=O+hxp{z0ZRbtYW3>Jo+jtXVN5@P{!N{+`4E4BN-Po8kW%_~Sb` z67!Q3Fvs}Ay9GF&z-xi$0`md?4RZw?;T$|=iW8NBE@1pssX5{@sl$39EWDwk;3bO_63yemfV1{(+4CAJUaV4tPhpn)NK4P9@;F|AI~4gp@F(^; z+Ft)I{-UCy;mmlGe13lZA3gzePEyj8BDmvpcHWO{&z9N;U@sU+rc`_Y@7ltho3-^; zgp)H`{~5?mnBV`!@AtxapAfuPK6h>)b-zIBAcfeCltB()57^G09gSinqY-WD>H+9C zYHrcX%U_`V{|K7fJZfw^yPXs-$if8g2LCHTY-?o|jK;A46F$a!!(jJHN;Y&0iy%tB z;9dmI>~L;_X95tv!5u!>v81F4@=dnrULqOY0l zIY_lZf7lDe#SQU-arr_HV{g?h9c$wu88Ke&M3-AZs1sxR<(xvV& zCQqKuq_aeS$-CFUhu_;)Qi6BsH7VW2`xu;a$sH%zCvXaS4tQeU!Dz#N0{ay3HPfaA zqu*e<8}w*q7iuoU0hV z1;z#(U~dMSX66BVC;f3BNos$F?+b&kBR-Gp_Ylj09|s=~YXE0cO-)}kCqtwkvyona z?O0I$MoP+x(p7QsIn0a8|lek_5Zt}4-m+L zl*ocI!5?%N{1-g?gY}Y-Fh?{OeHM7;3HzBH+N+$<=XXV)VF_JD#hVVkSWj;!dj7i+ z(I&c%&Niw%eR>#`LRmv&6@@GIoecYjy#hWv_(NE$sZ-aW{XB*q7Dj{L%M-QqVqCORuf%rhoj{PahcgK`$IN#hI#Ahg;GA*(C`4l zDjt3JD+R?y8ECyT>9)2x$R8J?wJAnxRZ92syM)eZRp@v3?x0w1ALZ|04<#|8va%O_ z=FC0xMT<^R_7NC(l^zgKjAFhh`VgDgX=?lyObOhGlz#ts;EkgQI zMc=&nI;y`yU%k47!phPz9(`{s63sUP$vlAC%IS+2pP_sL=rhcPU_DX~$XP*>@+mWB z>_q;knr>%zf<9x$ZZzk8XkOX${rm4A*}JIy2lN>HAMEw|`g`a;KIiDSZgtVGUVVw) z zu0?CLm+tR>nZgEq1ndvslVKg#tSP5&*>WA(X+F}UI4b4?zXE$FjAvqUfWC5N0rEQw zDLaIH9QI*D!@UUebCk~jUhqfi>MN;vfZl>Hf%6^AAI^l(2jqdiY}#~{vSsK4{s;B~ zd07r}Q1J+?8Q=-N4s`hP_H|F!5{lE9Mi#XSil(y^m-(bGXli^u;1$tU>3bB5J>omv^Mr4&r=r4+5Bh9j#wqhSq&Cg%#irv4E-R0s69KXDMHW z<1x5*RYADGU79i#qrtOvMr@Do4Pei9bWB6IWKi>f7zOZW=m4>M93#Tqt*nxeKezH4@BJ{I zhQ=D?FDj|M18@WEVb1`+0a(HNZ_^%&U<&Vxz}@lOxyPw_G4tbn0P70)5_9uJ ziWlM^h}Fp4un(9*t&f`8a=N?wS@fIvchK)oUq$=aA*8oksCj}6uyfE^NC9`)Ct!~y zwj(1mk6N$M@gJQ33D)2T06RiOMMX&F-?RS<7cTrE59C0Z$b+*W>^Xn~#%&tX*?GV4 z$NK@Z9x$-S{$E+yjrv^VGZ8<4bpgjkum~RE@sZ+KA$&v&as;M#%+H}iC-B=;t{2^q;eK<0TQ2?4?=5T=Ad6l9QD zKm-IQ1bv_=2nZ^U2rb&}(zf-zUM;q!|9GqWz5lw`dh49`{p@}AtvbJZZU(g5w)>v7 zYE_+@cYXKXHU6r~H{JA>><6t+j(FS&L%iX`*GfM;ZgH6BaJG5YtSdBDTV#f{UDk7l z4O^d$2CwexkZd=5&=c^xhMEr1``hUg*n=-ReaWEUyw(HDZz4~?wA31WR+H8gL zTV}Et_y^uOb`|_Td>H-zhe;E1U(74;hd1F-#-&Y7J56t~p5X@G4dZ@3=AEiJ;qHoz z1i$BLaJN}I@7VFO@xYWRH(D7X4_>a99c7?9TUxG{&iJ9>#M~6!#GLd)ANr=vLy3z{ z$Iu_gzAIx$H>}ZxJND-qZ0vsV#jk6A`ik`ju3P+E24e!|p3EmVZF+hxtdJN%`nv&SZd7ZbEkP zll&ht{%4+fX1o07`Gq_1_w_wr-$xc)kJFb&pYCqg|FQlbnJ4ev`va{nd`j&)-^NR<{W6DRY`_>H>Hj#h$oYe6eID!i z(N8dkm^0@Qy`}Q5#*$w!oxnW|@WF@?8?}D%72UJ{S2lO0?)3Y;d-Kgwj89|V5560v zbcr9RcYXb4tE;zPv_V=+hj-jGh_18cM)z}GDDD4&f9U7HJ^F)}UV2Gmr!x-+|652C z`G9-wPWZ?9j~M^uxW_oq`{=+H8uRhL_xbtw&)viuGv<6-L*n~~d@VW6Ez(%S+wnqS z{iZPbtn|xb(?iS|;0fji%n_OYc^}UjeLnt=y$2r$#9j_OK>y8LZR5rlbrMpW{y+;N(2ueGEK6-$47Y7w+{+XFljN+{Ae>`jSzSW4^OV*i%;E zU&Ji%!v@ns#D6exBP~3I8x!uL{DW2X{$KDw%ny>Bf;rz|fq&wQ&~+)D^Nr(5-p(6f z?{eY1hdXo-`|g{{7hLd~)&O5o|GQp&-gKL{@7?=7n;W1bLYI;ceXjTA@jac-<%&E2 ze=rZA|3I%yoOqMQj*rQFO8MVnav%Fd%n2QTzgx!h^1bB51Ag`k{f#c?{2%h|V}-yQ z{_r!Q`PjtIfBLrqltbOXpYdPd6M7&p#IE2!SaA=w{s;ayed8P7XvaP94_%P(5BV$Z z|GK|p6LDNmfVuDga#p}~ePCE^J1-E&@ejEV%)`dt`HS)WmMt%7y!S1Q8Mc}3U@Z~+ znQy^+E@$k$JN}f3t_2710HcI|oc{uU?yP1#XOPJsyg(jc%M+?JxF%@WY+(_p=4Q zP8j0~`Umd|=ns%_|Ng_ra(>?1*9{3z{DPbBKm7Y2AG3P?A^$mE!2{UHGMFmfphJ*< z@V_ya|J)5PRQLamt7BB%|2t-ZTgX3pn)4qa|DpSXZ-^ghnyp`V-b#KshB^MM^{ig~ zy!H>@mcGB*_+iPC&qyDZxFzr`CkzyunNq;RPpL$4%Yv- zxvj#Vh5h! zKg94y7dY;YxySK%p*z5xbp^t_JU@F5{*3z%o=@ZT|z6(EJ18CJc-7eK9_KRZ8!1F4mVJ~FNnAOVHbwwUtt?+{o3b$MV#XQtFOKa z{`(F?{`Zk4@D4Zm;TQNLml)1}FIjSv_?YwGa|(ty$&c>@7A)AOw%?*LHS6vvtd@Js zm^E5^4yzlrTzLZ+kWBv`A^%K5zdP&U;eDxjWcwShBbwGs`HJi^I{tZ zpbHoegduLYS=*dHf3L=no=5oe&08`v#UC?m-E#Qw#ah$l&T95-d_9kI;rvfF?pCFl zE}eCY(y{K%d74%;#KrAt{5(8oQ|8aVQuErWm3)~$HMiprPOrW8S{XaA3ctg|e{ZD2 z4nDxNXwm0vKZtL>7cb__?|jqsxvax)UT(-deSh@-;U=tE{NrbqJN6T-&G}j| zYY7E3v(ZOH{~vmS|Af>3Bm2oe=ADc&(l~?o@Pp@%?%$c9Cb^6t-Ur-$FWOZ-#7N!}B#J z*YA#<_DzMNEQ+_#xD(Lt2@U+gCrt1{{0Gy+!2kNd zKkUed{|SG`8C*N!-%&<=42S$P|BLy5ZC(k!3H;%Oj(h?SL>QjWZs$fWzx++D z1-ztpc7LY*;QMVap&IAf_&cr%`)c|3HyRvs;vn~7Qa_OJC(JRAwcY3=lFhwT%QO~@ zZ%fm;a`2CD%foj3&#Yk2{ldMh|L6P<7=cg7e~y{(zy1*MSJVO5z5e>^?YxxdgFEmK zOu*Ig_xYd8b?X0J?rX_EV}h77=6n!XdzzT1IZx#A9RJ9l_5HTCTXjbG2m0Rc9i12c zj^3{QYn}hSOJk`v@k+?P_vxAW0)PG|cgTONAA@V~NZ^kh{Xgb||Hb*wnEUxYfTyk2>%m)*U$T?jd)~a$GC_MY`*a5Q zO}&Bi3!NeUiSYlKod-hSIHo?XBOiDc{oz<9OoJDa?HDotPka%$!v~Ig*u=^4_wyg# z7epMl`37jLjV-B<`vtFz&UgVm;5-ob%J`kre!sXdweSDHr>OVwdms2;OPb)N;DO+S zgumkk?&f+Z0N>CH zyzS$<#pfM4j}R~I0W)S`ABge4kN=|oPx>Ex0<*C3d*h8akpF8BQU3cAmoz!Na{AtfyL*C-+c~34IUU3O zo1v%&bG(+8EA2ea8*jW3zpoI}aSBIuUTU+`M1*8aBG z+Mequ!s)9*cSOJ9`T{;=PVan@hvj<3d5t-1ef?II!Q45*oi}pc^797GS}R&;aa~t9 zuLS;qPmUS*gB_Sw@CW10ex2eUvQOUe{V(UeM~~j5_4%zjbM>gcf7veC-e|_d2=_R| z8$Ein;%?FR7!PP|?n?Xb{)Blt&U!{3*pnt*uJzly6~0OSgykmEco~h2J9QrKGpfs1 zO#i?A_CMKNaMGlG>T@<&zeC@Hp)MOXJR;uMR`FjaJB-JPxYT3)`ujDvxWdYJe~-&t zHaFxkapEO9<9>(b=V5DXjK>(lO{7_4xY3uwBA%b0r+@Hs_Ul!Tht!6fZ9OW^ z*$Yh5|A9Mp$m{#S|4i}99@0nupXZh15AR~&clNOTtg`#Ly!-bHgu9unIFpTEkpJXQ z9;8DLxULA>zj@@_dHh4)RLAqQlT24MHSHF^e9pd`M*r{G^F6isMw4OhW4wPMFYciw z-_(yqzvVLT@zF68jXyb$=sKwQ-CF1ZAHR|}`y93PL%xpXYZmYqv!mWp~v+tFV>S-GYI^1oWcz+faPK0fAx31^PP4blINAnKX^Ib6UFb~UzFjT2VBO9 zgRbw}mv0)At{@-BKgNQNHTnhoI}cB~fH7r#{dS%C`h>dHpb#;b6>EInq?vDH4LT<6y15Wto zxQ844?L0tVjxP21MZKQ*znf4&o&HR~Sjhx}Ug{++)4`?|*Y zem9+Cm*$NL|1_RUyx{S`2l@9s%N+ltE5I@t^gHX}akj+yB5==b(!g6T|L75LW!xWj z%p1cF{6qfJ_#gcbP8hJy-Czf%!oQ!)!Sp})UqzbWp~x%puEyVSb^RampJX!U5qF3D z2mUEdlK;Th^Pw-TmjBQVId8Fd?qiFoQ*YCK*uT>Fp-jhwcxXz8_v_XkS+f?4$qZ{Rgng zWuO0G_df8yk~E|(8vFzQge~*kMJ9jX>{zG%9=Rc|hascr|J45nZ$y5{CJ(>!BKhU~ z5$?%@m1^DqKgT}#`I?7>Q^Y^khJE}8{yAn~AOEo>|5qM{{I}y@i>J6}9G#PJ zo^0o+kUz&bVW0l{9FRF8^Vhh~#OHuXPXw_A~i^U--j2IUkWH?{Zk*V_BJ&hm8ptH+j(Kl6X^ z53Fn1;9q@bVe}bktPuRb*n+;r@59dL5{_}KD?7hL-vS?0_Z7hd*r5Z^|9*}s#s!QK z4u=1Czx&<8k^jEZ6(1r^V4u733;e+we2_cNF!(p?zQ*JJ;LH60x!+luqixeX=Ijc; zcpS$&cqiHP6RguFzKF2kulUbBgwt$oneTqqta(;E@IB$SO=)ZCj!Aazj(nY;qAzhC z$@K+p*ICnqZQ`NCGd@S~|KqK$%!l9+=Z~=IC#nr)Gan+Yk0pxp3Ur3|3-AX#3m>^& zfG_F|f8Ku!yuuFIui*b7@kw8qgN^?U|L=Y8dzK$IhCDF11Al*`Zi&|Zmup?}J{!k| zpTAkh8+_b>5%1b95td7JM)ywb(_Lh^dKmBAh4FU){r?i}=WN%R?prMn*6YZxXd*4^ zq?F<> zH%(1jbhqKn7Kb@zGIKJw`?GJdapS{A6Z=ubrSEVzXPe{9^4XT(nl*P=yD`3uv&YCk z_~&vO?!XM0K1BTc3>q}(ioiYW$fsKVxwCkxjjw5Q)^z!g9OgFq!TqM0-fwO`Uv$lH zkH@)P*Cn1G@jZ=y|IPlG>lXKqa8H8{a(~KiZsyy9+1dx%Yxhm^-LC5&ziWg(Cv+C; z=hfqo&_(E!q+ck9y$y6$=%E9RN2n{`^r1IGk8swz;~2C>b@9B>1Ly!BU*`HExd}&p z$kY1@A5Vb)Q%^nBE~6p;A-C9Is(5{54krJG{||ofgAQ^ZJP`69n1LTU+A)edU2;3& zMjZCLxQ8)t^LzrA+;(iy5uL{&zLy1$kvBGXOLFfo-;X$Ej%nZqzQ}d8{%1T9{Xn>V zjDe1(zL7rS)fzj1CHf)ygS;Pbw~sHunta&Pa_oI9>DWd;;ByC`H~5})tT{v<0RFlB zgHvwa5B}f({`WiN5&TrdA50y8?kfZ9z&_y~SO@;BV^qgYI3}#QBa^#@bNtD>$Rqwg z;XhUFzesgjVPz!z>Fd*Yu^RtsynRd%;|A8O0{@7|I3OPjB>9H79RKhOxu)+&o@2b= zXO>2f2LIMdn6E)quPem-ajg0WzY8OMr;z&p9P^M>bOBf%ru<)y{F4sMF}XYD|BjRE z?Ar1l_`4i~cfy}L?IS$rnQCmoH1K!b&3$+gC-}_me*LD~*phsadra_3=r68?d+ z%ew1?mb5o1Dh0P@fL zFUKnI!o~>y%MU~UU;e`%{;&%7$d57-{()P_f4Z|O>43nfJ!2e{@-f}hmu@~%f#MIZj{8x|v8`{VJfl=7O56JQ%;(v^Iq_^T;7WhXVxn0!% zv@`qvv`@k_cq4G-e^LG;e#EK9-f>0$``SXnyH>m@l6RMXuLl?xv5)ly=L7H$S^bpZOmTK<#%Px$8;K&+|_5A2J61^wExSN7+yQF8{3mpqq<4lJkMPiJ#-2bbs&w@!TDG zd%o^hn+Kc^lK!uj`838$bAY7(bNP2Z2)!RRJdw-4kL?`SA}{#(Z+)i!!8znUbbnem z^mRq zoyWVm-Q04^Eqasr!!=DFcI&OTc8EiG9;a45hZ0VGA}{Rxe_q$38(I!#pt&q`y~G0t>;DaZ>K1K8 zdfacl^;Q*D5ni;3U&}=Opd-Rbo7Xq@4>xv{5$;Hr`x6#r6vN|xo@c}@ri(bFiTv}p z;TQ3+BW{GzR^b=nkq35!VTXVC5kLH*jN4bwnrX$Bp7%0BkVNAz33-D`APYgzx<_{zx~_4Eo1Wk z;V#;F+|JyjiLkK4FYKa!{y)mZFOQ%5|LRx2>a>hJuW*N*=TXc*j}z{&^LpgzivAJ5 zGn;yMR)_HKEYHsT!4gct_LyUiIS~Fwi9b$N++Dx<&2P&8@-P3=srl!B{^$0v++9r5 zSy=cN?ON&cIJm;w59N?r$w1D0TV)KN#BD*TV? zO!(!0{_@(u@B>RQ)f<2Zy8FY}qEDYbM-Co5_}Csj zdUQYOq?1mR{h!*LeDcXBhWXE4*FR(Zx(^#R>{#J?K-tgr!U-pwaHQ}*Qf=F1{({>; zJa;5m=Gb<*yIP>D1-e?Gs|C7RpsNMCTA-^1x>}&C1-e?Gs|C7RpsNMCTA-^1x>}&C z1-e?Gs|Eh+w?O&N{QE!EP2A%j4*b7cA-wB}&C1v<6>=ZCw= zbpH!7$E&V~Yd9-cJS)vPniFJrN2^|@S!Qfz`2WY4CZ#_d)2R4=D$}66FwjM}z+Lel zl*!KyadvpO-4L;DpuU0(#h+hng{|5@Il{U)F)8+2SH@6ATO@xIXG(xNB z&t(Am*YK?^-{gkL|L5)qkMDQ$|3!DLbm50xG#~lMN6fF7Pm~pDYlTOg+^!W5T5Hiy zThZnZKKP*7xWgRCkFY##t+){$cH~>MBW>87g@=EhU!*Deg+FNs>x}+~9(u@V|HLOg zQGWE%N6U{r_L$z=d;E~i6Hh!*Gw$K~cUERGpZtGkZfJm(ppCYuMSu71-Pb()@WZ-a z_z^RtJLZ_<%f}tpQ*ZS3m3?ZJIq}4S7Ixfmr^t5Gt=lQ~o*#E0By)<~r`X%h?iY6} zoTN8CJKjCety>R!2hQ&+I8k>K_`Lww#~*)MhdTnOo8KGoLD|VrZ~h;3)Uif?P8+lq z`5(Vee)5xL4DI!)PkqYnOyDhB?xObhs?Rh!c;A@2FZ^xm{9W6Adc%H%)!*N^?JIX* zOXKew$GfMzZ`;e>y^HsbX$uVg#O1z+?%hu}nvXyJbPMDC%;S&mY3+_pd+=^1>D)gs z>Z!Lj2U@!yee`k0$3Yi%XY@leCiosK$cH<+{T<%{vfc1rpT7YU&zv(T=@y18AMQWGcR-U(ow+CUo8%tZ^A8kUr@a`-;d(1J% zwbK;zbw>Y_Pd-@=^XX52+ISd@d-oo%{OYU?Xb0T=`b`$r!!tkp2a3*dvS|}cf3wM> zU%x4p|Ga_DTe$sH_eKj(ywXqq&s5yAEF9jBcT&l(XU~zwAGC{`KDwKz&DzlU1s9MfHQ#y`<+psyDFAN=44*KpS&;n>`}N1JjF>)^q>`QrC7_w31??W5&4 z*D%GM?rg_@mdT61;o@?dS&ok-B$vVI|IRlaZ58 z9wK^1>u#+{`ai_@0GoFshYp=@X?yg*e^A8?)_rnk35P+3Ay|fiHfSx%zx-Bn*9dfD zaHFrlI`}{FK%HoBwYoSzB)On19DlHP<9yL-*ar64t}Boc*As5{YnPQ=F5m-bOgO^> zy-imT=5m4zxNIPs$N@S$@C@3ZwHE!nwUHZ_|D1m3aoXYi(NBB0PB=9B<1J8sBepjE z=n9V)x&oWHPJeg9l6NG+bVmQ@_10u=I^yrL4`21}J;C@d@r3gL_$S%#Z@d6s(q7IF z^dYo?;~(ze3;riv8S>x9cm$qLJORyp+v7$XL^$s)(+?A9}D+3kB;}afjK!1up;D6ALAOC49?{EFhaOfls z+Jh$STJ-B%l2y#Dy`I&ilp@pu=sRd>OzxB7VbycI+K+`YKW;?Z8<58Xi@v=-$bKkmN^LtOYD zIbw_fhHhN%LIe8C{RWw?g-3Yn$7O>4vQFWHOipMs-1rR}#=GGD{!8KsmlgigrY=LF zE8q(shdKWc*JY8mjXojqDD`MCUSb>x{?K*!^fUfJ|J9ov^aMZ0o912@=l8__(I*7| zPp~?ZcJ=Dd*xhf5|AS8Y|Bn0*U*jMB1#TaQ4Km)M9|Whs0z1)vqP?LVxN^r?C-|@A z{X4?3iGTd@XPC?-ni8fR^&j+uQS^U4{_%Qxe?h&e*Dbev-|jP|ug5RRzmI=h|IsE% z|DhL=Y2rHl-mYh!InCN7#t^gxxWKdE0{+M{bcGT9D{Jt-(a#&O7~*#C-dpt@Yy3#Q z;7^|LSvCD&-4Xx5iTKDX|B>%I?|fT#BX7?1pZ5n&Tebf8Hc0p<+L5bax)a>rO-^Gl z##|#+&N$--bU1turWk1JjDEr*4(_0z`vV_`UGL3uR2SL*Nh1>F#d7!;!bk>7T$bbPu>g zx8v_P5%1o6f2jU)o8ezqw^&&GM(@|$tUMcq<8awcYRfho%aRsZN%;F*34i(=?n3o) zM_YRCXC63kq1y9ey=8E}-ZHqzc&uN)sn(XP13-V!hTRGME57oTujqZvuUK681m5J% z{h>ow3#+y2Q_oXBerb99`0L73rrcScKK)){`>5VxdO|w=an)nKVeftT`Q;@`?$vu8 zpV8aBU$l3Dp<(FIRpqT)pVQk2pHqE5WoG5dPb>XXdbi=Tdgo(nh5w!IkPGr&zy7nz z@2T>pO`jFkOT`D{jIS=Z;2G8Vf0lp#^Z#8w|NPIY&T}+Znr?N0et19V!WR82YT&=( zt6%-9-j;F$4|MO|M{V6|cN%k7EPTMb0=ySM9}bpvb-aPGOm7n0COW??y}na#6TYA} zxzFkbW@E-|FVCO<5tCcqNSQO|Cga^9LzY?Itarfo@bK!@Ph0)dxR`O`XuYlRgxY3@ z(L8S4#d_oTX={VsyPvhUk(-(>RrqdeUvx~re$zyMz0CojKj^~ljQ$s2e6c;A)9?D9 z_jkCj)(!9A_#3O>?04RdQ+YG>ChIrLciiz#dD^sFte)^Tx(dvfF1_D$7H=(2oOqeZ z2(p7dL+3#+`h5BFN39J*FXGP|&#P8Fsd8C!8DjFWcI^}8i!XjjyzsQ)0H1;jdX{>@ z|L7y|N0$Zt(AXLMU;EnE%3@)R3;)cY|53^6Mb?Hxhq8viIF~l{c5Q9lQ(n393GoN-MvOB)K`tgv zyjJIvu*89lI0ymj~_G z(C_mrfAg3&MHaBRCz~?8t<#;!ZEe?x?;o@{em}a$M|W)7_PoW14~7j}V>mKa2XlDD z$8JNcjV`?KW#e`D176>`^F_4-cl@`QzC<3-$CNR0z=lEtQgDsxSBa($N=NoL3-!#YTXI?ruh3s-52_%^v7oT*I8eK4yCQ=+i8dB|KrUF zcpiWHN5%-uVW2Oc7{d->&;^}hB?W`cfbN&O$=3~1sV)H%ivevfsGKk~TT zW9Hr2J>cp+~Cuq6Mcrky5p9>(~`$IIRZ%)^FB z=BLZw$9%LYe$egX-#WY77y5%PXoS`p^e-hWOin-Jp49*QJ6}$JyzS)eLL7J{;qPt0 zo!A#&{JMB`i~7D7OkYKxpYHo_urZM9w+Z42<^}n}xL z(B}tT75bOfp#R)gUU{Ybjc8G{Ak++W0UFTrY(6Fw`k94%v6<54bKI^PIrORHH%=+6!_4TW) zpN0lE+-*KZW4Nud9-3jOEQqMtAfanTpK{wwmozp>2QCa(K}4;=p_5BSq(TyVkH%Jb$uQQo!d>qeX7 zKe-}T@jj;Cea@IU>9cqvxl#Fee*s@GE`TTKCqs9DcbHR89bsdC_Qs$;XzPrA-KVnT zwbx!NhY9+rf0BRKXV4t-U(|mdH^~a^MnC0a-j_uGS4GD}(ao5@NWZ@!O5Tb7cq0({ zCm6Q84Tzx+=-YR)(eHG#Uf^p6gs~RN`XBT|7qnwTYY~6>E$063BK_!kFb$ls@k{vg zrbVI`{&ikSZ3X?T`P_EfI~t#V+1ew?KlAD&`}lXHA0B8ltYh8G?Rv#!ZwB4TTHGMR zpEfw<6xK?68U5(;pbHwIwFdo*UVr^{y)FGsGmd{B(K=rAPc@pM+t(!hjcIU%HvHY> zalP*+KDaGj{D{V&f3LCn=PfR6l;aQGZg>lXd2zIh<4+j89(23y??!t2rFTP_H=`4f zk>LNJ4O{dtszLuk-kB#HoBon^WX#8W+TTE6oz~wr@i!KDHb8PcSpOid?*W28 zG=&lU3k&oggWSjZ3#{9Jj*4nYZySB*x?B?+vmpagOFc zkLn%Qf7CqYlfu{E6>MtSY-zyT%{==*VZttpgC0Z{FoOp#(m0TL$ZX+}<{~XEJ1TL~ zyMcr^Hm+B^nbP%jCjUVjc7^`@{1fyqhf5!j*E;zF8Z|1~X zIj!o?SfB9sQk+*`&4LjEj)}csmmsh5y_d_|M^;ooEB- zKj8${>v?l*O(Cn5H$4j(uy3_;7KBS7IFIeJO2;-Z=jFUPgb=1&yMAPA&R*zaid(VxGwS z2fFEpoTk(U{w~<`0s&nu`4jdc^ewG7|g3 zt}9$ts^x-lIJ%NOJm#`N*8%9CePH_2JTK^>jh!!o_k6BLTRE;ipM!^JFZYkP>d-6D zkN$8y0qy)pw|INF{)~Rq=?^_g8?;zIr228Qh?oPd?e#TYB_h4bKgIuDjWjbwAQ@PA&QSel6ov-#cONc8JC*Zu@>a z{|8qL`{sjH-f)d+nftk6Y~%a9{AV3)pvf%r!5%$^8vnBY$y`3@f=1DQc4zbxj)6}; zT{$~EDbj23*vu%ekj5sJi?0pkrtc057lsOKDsnsA@rDI9dF20Vxof4X6|b}W^7OdF5I@3-cCGv)P97%wXBF`0F5DU1 zr~lKR{tC1Od+)tJsrlXSe)k`e-=UTRjXitzY%9=zguV^=U_(R0;>O0trT>); zH0r)^zW?d^b+tfO3v{(WR}1_%Zh?bcEbtv~o1<@b`^YZa8sa})JAB9SIr?CD7Q^`< zm=Bw`%EUbpHxJXFu3uLR{OMbOHFv((KS`#?zoQ}lKh~V6JdTse_X+qvL5A-KHp*08_xeK^KW7FUh%(S^8exfBkX@w zo|N;xjq3VqRh+jiL^?I+(>!d)@ByFEN+q^R%T(-1a;= zDWhh*D33I%>wZxj}($uwBf4yJ5zf$kgym<>tO-++Zjg1p!S}SHkY3$guN(~LI71LsI8X8*k-%WG# zl!|-2?D3W!yS{#$8Qe`xZI%CHWskMIvBP+Hb7{;NH{^|b&YXGBU!soGwOD_>$-O_m zbEZ7Lo#D)#_IgU3C*cec>yO$yFICQxC=P3h_Kl+XvA3@OrTFGd=MLoVt@AQ`uiHy; z`r0`heScQ+?-5T|egim*sQ>=mvd-zqeVUz5)fpY7IaBUaw8v3jiu&oj@DBBtOzn^F z<7p?>R(Lf<8{y|=oJQ7S@rOS4ctxMp+3D${bZ6S$k!UJa zXi^!2MB5qq&o{Qc6kcy`Xn-!-4cbPHYAD6G_dBtfph0Kr{KiQ8uQPw8{{5%eSr^eEzu}ee5k@=o>$_O-45Rdj19`KbJ^7Z!bW&b_Am-&gutUPcSJ-V@^ z_2UwCq^`yK>wb;>tV8onSYu;Lfq(o=A?J>vMRm8fasIJyc~!Ro#$Qg0pHKAq`}a!F zqcHeGyk+&5?cc5=8@Mr&A-Va6)4p*xEckYt`cpURNL>r{?<>8!U*9v8^gW++LJPQC z`_VpbhN%6=tL}~mbihLuddwDmW>;viJaAKY3~{xu(2faoKqIze)UO1Fj-O-Z;k~7U zno9cK$m&O3RsX&+@$0MpVSjvwhaT+Tf2#Jirm&}_dbeo*XM*+$7ghe-x$x4UL9?yR zCD)2GPT}nNjZ+;sZ`&qbo?-s%_epn^{H!47A{s>p=VThShdI;!Bm0=)!xvi`@v8C~ zt1`|u|Hj5i)Zf0DqOPiVRsBEqv5)QNdm_FmLFXcmqFsDK`?E(gl)9rYXh-8C&h3U9 z8FT)s6OWJ4zVPLo)iRzz2cmb0%f61|k-mMUz2Pmwbr#UdQ#txyCyf1DD*6Dvcc6~c zwOIcP{T>F%wBAY>{91y^cI-m&$*W0ns=TzBfZWa6qo>>gJVp?RrMxF5kgF z#6>>&Fa2sfgP!0&VU(eB{Kj|q!#nUBd5;-0*6ODEm#Ay8{&(Ja=YGCt;QJi-hcl7j zOP;g~7)1N>pFELYWEmdE1|RA%eE7=pWtTmt_P6@e-qz*{Gx>$r=^yY%&yoiGz*yfP zsO|h5qW1jMX6y$J)wdGkOVkexsB5AAeRThj)t@ujff?faNfjP$lzyMB z{-CJ;?al<_=f=hhEWEjSdwI{E7tr-aBXy)b;VakwwCxb^wGw$L;sjQrEMhF6QNC} zQS!Q|q;vG8nKN%FjU2hIBsz@e7>7|m(NLnUs(4lX@44rm{hSBoOvJcx(@O&e%+r4J zGU*J+`3qNk(dZBlxPrpjvv)of& zwCI7-f(7^LjNctP=Xk&L^5#r_x#wf7&cA)4th7phhuV3f%D7szzbQTQ*Yf|c^x!P< zT!Z?g2_@>rnRV(~tpC0D-unl_+0%Fa_p@g@BSSkX?+JDW0bDqDzkU1jr7c^Y(KktZ z#bXn}MdjT?{YtZD?X$Ba=uCJAc~Kooix)p+Jc2Gl-^`r3PyDh@d^EB&ckZpCr{@2$Dl9|-4s zL*gI4<7hTMhlk)r^5lFKbp~79#Nn*!k|lR(oU*O7Y}w<+%k&4-A6`MnqJ!JoKBRA+ zK3|$Wd!KYNItU%i_>gaRMo5271uyBO(Z*NMM?CZ=XK$z@byfX)%fyfR|6buG)n8Z@ z^$#?R6YtEVp48uLjS(tt^h>jN_`{`%6R#3qd|G(4m|j8m&}Zq41$9)s#!8>(V^rkb z<-z+eWQl%Db+-8ECHe>YFX~1esjDiU*T0wU7W+MI&sp^9_P6?~y=i~G^EG{HHvC1q zNBd8mdYyF5ouyT)o&qQ9I}|4QvvcIoY;sLsKwrkV0=W-)M$Q>O(3jz+FK3LRaE91i%%b-ECtj~ik==8gRh8h1* zH|nT4aIyYpXm0#_wS7tF+{HhW3;h@TGrj}=KttlEX6g4`r3)^2P5kq)@e{n@*uX=K zIh&D3xf|LI?G2BhL*X0xycj<-Mq&)cTxay?CaW8Dq^@f7eEsKi-A(q7(8n3=O8t>h zbOO9@ZurOL7oHhwZ4Q4>FXC_(^X|KUQd++JUimdyJ7dE?$N~Dr^?4)pH@(g{3;AGd zf!v~3kzM)?_?~eF^O{kk8cWoTI#O5F|MZN1PS@Q29|`9?a#Pb}s~fn}9?*fCGv3H0 zvVcG5#;7;g5NE`Q^%{TwTKe;&(A3WV(BL$Vw{Z=$c>Uws5$f;XQt0ej#ckgUT3OIC zdi2;5b)$~dwNU@l@Z$_QamJ0CSsFI%T%B!OOrNW<#9Z;|e9a3s>i=B*pKUsP)Tq@) z6Z)JpkK@K&p!)w^iEpCOwIfF^x44z_q8eAvS3OpVhMDw(Mhh}UojG%d88c>W#m)R_ zs`}z{v8Df~SpBJErT(X7?SGo?K>MBQUebABjsK_6w#c5zf%+5hkW9~`Z&90ApC);n z+U`as2M<2C^yHI2FHMZ>Ohw+rsw7^f&pDk85 z>PTHx_q_f+wf6Hn&N6eJmHJ2jOMU4N(Ib3c;Q#IXpxP0HAHnK5~RM%Mx3 z!NchCK7{_z&qq2w?PsQ~U62jxPuo&Qtz#AIf9jJ@KKVPkCl&|oGI-|Tw@j2(p_vq6rSUE=UAi4lq0iOy+h7IS~F|-rdI3BE7OhWfs`*Ze^ zI@YSc)(zhyoHK|W>(BiRdHwA?czgYICfDlh^qoc9nY_Tul(`PN&y{{6?u?WV4dapEZP+eq=(NV!L=ZH7xH zOtiXDN9roxIVIztQ?#!5UY!4Y`|Y<&GUa#Pd8dpUyW(#DpZhDUJx;hQ=`A1J#IM*z zxBdU|kAI9^mR-XA)?06x8@pntznxE|u7&#d(Awf}IRjZSrCRQH-+hnv^_9&y6X^Qo0r zt+Wv@Y~oPYV{`#u)E`@S&vn!NL*4&EOt-x5UGAA@;ciHc+ z9loPv+{9mO*W!Q9Y1PThkZC`sb+Z0+{c5!UXLoq#g7-Z7ABG{#p&HJ0gkg`2_e1y{ zIePTyeJfV1cvt3Uhhmm5U*1`~pDMi>#c6lr$N%t?{a2Jp*_Z_j7JP|!9%>ow$*?ce z9v1(LcBC!(7ylCvKgx{q&pr3tFY$)JRaafD``y;*POmj)R;*Z`x!20Q+6-})En8(_ z*qHEJwrr)v!FKmrvzIRA|EltpS6;%L>_xK{==;;Xhun13_Zrw?}~<7SVP^=#IKhiMMtcU7K#`WUMh>yr3kvwq3l-$6sKUYy-<8hZ8| zp|)AYJW2NotZFZR&6+jO@h#b^RqR=$JKCAwR@OVM4&=#xAbY3YCVqd0_WhKOyVki& zq_@T4+y{I0(W(|Wf`t{>(D!;3p`|ol4xf1So@^8%h^l8z(ZX0c{oHC>Q z4I4ISi}^V#-`D+ve(nagJy~o2pr10QO}j*ED%{srO+PfV$BjSj5c{mYr!ZV?%v#`c zx({$ed-)qTZhV&g%k}HGF$Xs+$pczb`RsQMP}neQhm|XDwKWrX0=bCxr+oO7bnvd< z`AHknb`7F&qwV!lX3)QB)23(HL*2M>hw_|Zb?`MA?rI%nJmLI5tUPPh6^b)U_oF?) znx3^W^XT5a{e1=MP5b-$)L5N;SgLjRo$UV@p6l0dwfoH|Gs@Sw!Drdy+_Y(@;>^~1 z(jx6|uGf0;W#!h^t957PN40jaNqdmj=?=^5wf=ZcdFs?_bw|aAbU(qx?zt$yhd&FNzpaY)=9X-WAfyylwY&85BxA|)*wOBjjW_@kMh!wUj#+`}Nr(Y-fuQN<5 z=P9&CyG=U%=JGk`utx6p-m;cVxvbxS5&IGwHf*)MK+25r&pYqDXV}A{d~j!thc$K9 zX4sqIfB3U*GJpOZx))=g)sy`_bOXAoZ{JDAE9eFI5}PvN!{Gn*>o?ooW|SG_Z{NQC z8TN0sY&l=`P5f6^XKOlEw)=Be_!Q~T$A!s_lD}yRW4(&|eb|F)RQV(H-_BQ9z0fB~ zhc_7gltr0Q{*E0xzQFz#Ir%3xhpYF73*?D(jWLzS>|oQ4eTF?);oa zUz5oV8#dcs8f8ZLJ9qB$6%v*PUCtEDva9 zZ4W(zoApTY#m0Zno-a$bxi6TuV!hb+f3Tg;+57Q3svH+@3(iq(+O*yFLMSuJzuUp}*R;74|17Hu{yTO&A$rd-8Y^qq)@Hs|8d&+db&d=5qkPJu z%qaiD3orZ(>!;hc?N)m&th9Nwzwe(oF4GK`$&;^D-Og@rf6Cy#A#B>++u!dv;ciiH zPuAF{tNnM{UIb-E`4?Su(bKGdf_U2nv{tjDfrOYT_=dPb-4fbOnyG#Ak4w>z`cWQ^^Y?t))4s)+xzq4Yy zKjH32-0gCouXN{`;s4sT+bU+e`rIuVTWyxvqWfNNx3zT2jPftJ&}l>jvfW%{~|WcdrfQM48yEQ?M*iCll)wGmg=p zKk-j*I1L#!YSbQ?YbpkJXXa{!g}F*;2M-@Ue6{4GeCefE=nUkC%*O5Ja@iREV_(*8 z>rAZ1lEYUkztz`VbDfP(7}GK~XWZg_qmS2Pj>8-##_)UgT%$7rqgL0~*RS5U@22)~ z4C74VcpC08>1Y3$m-!rwyHNXT-*cbNc+{`fcl@hvx#du#gJxL~dto1+`BuFT({@yO?|qWS?U!0x(RcGd zjg>iL)~GX$_v_4a%ktLN)@2Vo@UZ&FSsKUBw!SaMNM4`G);Cip`de)1x$nLQ)z+=c zCQO*HlriQ54?H6CXuJL3gSg{AVUOq>+{0$>yYCZrE{L?c1EbH}xpP;@tUOG!LT{+% SXG-6fWS7hDE14xC?f(b9yk*S* literal 0 HcmV?d00001 diff --git a/BNetServer/Server.cs b/BNetServer/Server.cs new file mode 100644 index 000000000..d34f8046a --- /dev/null +++ b/BNetServer/Server.cs @@ -0,0 +1,154 @@ +/* + * Copyright (C) 2012-2017 CypherCore + * + * 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, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +using BNetServer.Networking; +using Framework.Configuration; +using Framework.Database; +using Framework.Networking; +using Framework.Runtime; +using System; +using System.Timers; + +namespace BNetServer +{ + class Server + { + static void Main() + { + ApplicationSignal.SetConsoleCtrlHandler(AbortHandler, true); + ApplicationSignal.RemoveConsoleQuickEditMode(); + + if (!ConfigMgr.Load(System.Diagnostics.Process.GetCurrentProcess().ProcessName + ".conf")) + ExitNow(); + + testing(); + + // Initialize the database connection + if (!StartDB()) + ExitNow(); + + string bindIp = ConfigMgr.GetDefaultValue("BindIP", "0.0.0.0"); + + var restSocketServer = new SocketManager(); + int restPort = ConfigMgr.GetDefaultValue("LoginREST.Port", 8081); + if (restPort < 0 || restPort > 0xFFFF) + { + Log.outError(LogFilter.Network, "Specified login service port ({0}) out of allowed range (1-65535), defaulting to 8081", restPort); + restPort = 8081; + } + + if (!restSocketServer.StartNetwork(bindIp, restPort)) + { + Log.outError(LogFilter.Server, "Failed to initialize Rest Socket Server"); + ExitNow(); + } + + // Get the list of realms for the server + Global.RealmMgr.Initialize(ConfigMgr.GetDefaultValue("RealmsStateUpdateDelay", 10)); + Global.SessionMgr.Initialize(); + + var sessionSocketServer = new SocketManager(); + // Start the listening port (acceptor) for auth connections + int bnPort = ConfigMgr.GetDefaultValue("BattlenetPort", 1119); + if (bnPort < 0 || bnPort > 0xFFFF) + { + Log.outError(LogFilter.Server, "Specified battle.net port ({0}) out of allowed range (1-65535)", bnPort); + ExitNow(); + } + + if (!sessionSocketServer.StartNetwork(bindIp, bnPort)) + { + Log.outError(LogFilter.Network, "Failed to start BnetServer Network"); + ExitNow(); + } + + uint _banExpiryCheckInterval = ConfigMgr.GetDefaultValue("BanExpiryCheckInterval", 60u); + _banExpiryCheckTimer = new Timer(_banExpiryCheckInterval); + _banExpiryCheckTimer.Elapsed += _banExpiryCheckTimer_Elapsed; + + while (true) ; + } + + static bool StartDB() + { + DatabaseLoader loader = new DatabaseLoader(DatabaseTypeFlags.None); + loader.AddDatabase(DB.Login, "Login"); + + if (!loader.Load()) + return false; + + Log.SetRealmId(0); // Enables DB appenders when realm is set. + return true; + } + + static void ExitNow() + { + Log.outInfo(LogFilter.Server, "Halting process..."); + Environment.Exit(-1); + } + + static void _banExpiryCheckTimer_Elapsed(object sender, ElapsedEventArgs e) + { + DB.Login.Execute(DB.Login.GetPreparedStatement(LoginStatements.DEL_EXPIRED_IP_BANS)); + DB.Login.Execute(DB.Login.GetPreparedStatement(LoginStatements.UPD_EXPIRED_ACCOUNT_BANS)); + DB.Login.Execute(DB.Login.GetPreparedStatement(LoginStatements.DEL_BNET_EXPIRED_ACCOUNT_BANNED)); + } + + static bool AbortHandler(CtrlType sig) + { + Global.RealmMgr.Close(); + + Log.outInfo(LogFilter.Server, "Halting process..."); + Environment.Exit(-1); + + return true; + } + + static void testing() + { + + } + + static Timer _banExpiryCheckTimer; + + static void Profile(string description, int iterations, Action func) + { + //Run at highest priority to minimize fluctuations caused by other processes/threads + System.Diagnostics.Process.GetCurrentProcess().PriorityClass = System.Diagnostics.ProcessPriorityClass.High; + System.Threading.Thread.CurrentThread.Priority = System.Threading.ThreadPriority.Highest; + + // warm up + func(); + + var watch = new System.Diagnostics.Stopwatch(); + + // clean up + GC.Collect(); + GC.WaitForPendingFinalizers(); + GC.Collect(); + + watch.Start(); + for (int i = 0; i < iterations; i++) + { + func(); + } + watch.Stop(); + Console.Write(description); + Console.WriteLine(" Time Elapsed {0} ms", watch.Elapsed.TotalMilliseconds); + } + } +} diff --git a/BNetServer/Services/AccountService.cs b/BNetServer/Services/AccountService.cs new file mode 100644 index 000000000..216863c39 --- /dev/null +++ b/BNetServer/Services/AccountService.cs @@ -0,0 +1,582 @@ +/* + * Copyright (C) 2012-2017 CypherCore + * + * 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, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +using Bgs.Protocol; +using Bgs.Protocol.Account.V1; +using BNetServer.Services; +using Framework.Constants; +using Google.Protobuf; + +namespace BNetServer.Networking +{ + class AccountService : ServiceBase + { + public AccountService(Session session, uint serviceHash) : base(session, serviceHash) { } + + public override void CallServerMethod(uint token, uint methodId, CodedInputStream stream) + { + switch (methodId) + { + case 12: + { + GameAccountHandle request = new GameAccountHandle(); + request.MergeFrom(stream); + + GameAccountBlob response = new GameAccountBlob(); + BattlenetRpcErrorCode status = HandleGetGameAccountBlob(request, response); + Log.outDebug(LogFilter.ServiceProtobuf, "{0} Client called server method AccountService.GetGameAccountBlob(GameAccountHandle: {1}) returned GameAccountBlob: {2} status: {3}.", + GetCallerInfo(), request.ToString(), response.ToString(), status); + if (status == 0) + SendResponse(token, response); + else + SendResponse(token, status); + break; + } + case 13: + { + GetAccountRequest request = new GetAccountRequest(); + request.MergeFrom(stream); + + GetAccountResponse response = new GetAccountResponse(); + BattlenetRpcErrorCode status = HandleGetAccount(request, response); + Log.outDebug(LogFilter.ServiceProtobuf, "{0} Client called server method AccountService.GetAccount(GetAccountRequest: {1}) returned GetAccountResponse: {2} status: {3}.", + GetCallerInfo(), request.ToString(), response.ToString(), status); + if (status == 0) + SendResponse(token, response); + else + SendResponse(token, status); + break; + } + case 14: + { + CreateGameAccountRequest request = new CreateGameAccountRequest(); + request.MergeFrom(stream); + + GameAccountHandle response = new GameAccountHandle(); + BattlenetRpcErrorCode status = HandleCreateGameAccount(request, response); + Log.outDebug(LogFilter.ServiceProtobuf, "{0} Client called server method AccountService.CreateGameAccount(CreateGameAccountRequest: {1}) returned GameAccountHandle: {2} status: {3}.", + GetCallerInfo(), request.ToString(), response.ToString(), status); + if (status == 0) + SendResponse(token, response); + else + SendResponse(token, status); + break; + } + case 15: + { + IsIgrAddressRequest request = new IsIgrAddressRequest(); + request.MergeFrom(stream); + + NoData response = new NoData(); + BattlenetRpcErrorCode status = HandleIsIgrAddress(request, response); + Log.outDebug(LogFilter.ServiceProtobuf, "{0} Client called server method AccountService.IsIgrAddress(IsIgrAddressRequest: {1}) returned bgs.protocol.NoData: {2} status: {3}.", + GetCallerInfo(), request.ToString(), response.ToString(), status); + if (status == 0) + SendResponse(token, response); + else + SendResponse(token, status); + break; + } + case 20: + { + CacheExpireRequest request = new CacheExpireRequest(); + request.MergeFrom(stream); + + BattlenetRpcErrorCode status = HandleCacheExpire(request); + Log.outDebug(LogFilter.ServiceProtobuf, "{0} Client called server method AccountService.CacheExpire(CacheExpireRequest: {1}) status: {2}.", + GetCallerInfo(), request.ToString(), status); + if (status != 0) + SendResponse(token, status); + break; + } + case 21: + { + CredentialUpdateRequest request = new CredentialUpdateRequest(); + request.MergeFrom(stream); + + CredentialUpdateResponse response = new CredentialUpdateResponse(); + BattlenetRpcErrorCode status = HandleCredentialUpdate(request, response); + Log.outDebug(LogFilter.ServiceProtobuf, "{0} Client called server method AccountService.CredentialUpdate(CredentialUpdateRequest: {1}) returned CredentialUpdateResponse: {2} status: {3}.", + GetCallerInfo(), request.ToString(), response.ToString(), status); + if (status == 0) + SendResponse(token, response); + else + SendResponse(token, status); + break; + } + case 25: + { + SubscriptionUpdateRequest request = new SubscriptionUpdateRequest(); + request.MergeFrom(stream); + + + SubscriptionUpdateResponse response = new SubscriptionUpdateResponse(); + BattlenetRpcErrorCode status = HandleSubscribe(request, response); + Log.outDebug(LogFilter.ServiceProtobuf, "{0} Client called server method AccountService.Subscribe(SubscriptionUpdateRequest: {1}) returned SubscriptionUpdateResponse: {2} status: {3}.", + GetCallerInfo(), request.ToString(), response.ToString(), status); + if (status == 0) + SendResponse(token, response); + else + SendResponse(token, status); + break; + } + case 26: + { + SubscriptionUpdateRequest request = new SubscriptionUpdateRequest(); + request.MergeFrom(stream); + + NoData response = new NoData(); + BattlenetRpcErrorCode status = HandleUnsubscribe(request, response); + Log.outDebug(LogFilter.ServiceProtobuf, "{0} Client called server method AccountService.Unsubscribe(SubscriptionUpdateRequest: {1}) returned bgs.protocol.NoData: {2} status: {3}.", + GetCallerInfo(), request.ToString(), response.ToString(), status); + if (status == 0) + SendResponse(token, response); + else + SendResponse(token, status); + break; + } + case 30: + { + GetAccountStateRequest request = new GetAccountStateRequest(); + request.MergeFrom(stream); + + GetAccountStateResponse response = new GetAccountStateResponse(); + BattlenetRpcErrorCode status = HandleGetAccountState(request, response); + Log.outDebug(LogFilter.ServiceProtobuf, "{0} Client called server method AccountService.GetAccountState(GetAccountStateRequest: {1}) returned GetAccountStateResponse: {2} status: {3}.", + GetCallerInfo(), request.ToString(), response.ToString(), status); + if (status == 0) + SendResponse(token, response); + else + SendResponse(token, status); + break; + } + case 31: + { + GetGameAccountStateRequest request = new GetGameAccountStateRequest(); + request.MergeFrom(stream); + + GetGameAccountStateResponse response = new GetGameAccountStateResponse(); + BattlenetRpcErrorCode status = HandleGetGameAccountState(request, response); + Log.outDebug(LogFilter.ServiceProtobuf, "{0} Client called server method AccountService.GetGameAccountState(GetGameAccountStateRequest: {1}) returned GetGameAccountStateResponse: {2} status: {3}.", + GetCallerInfo(), request.ToString(), response.ToString(), status); + if (status == 0) + SendResponse(token, response); + else + SendResponse(token, status); + break; + } + case 32: + { + GetLicensesRequest request = new GetLicensesRequest(); + request.MergeFrom(stream); + + GetLicensesResponse response = new GetLicensesResponse(); + BattlenetRpcErrorCode status = HandleGetLicenses(request, response); + Log.outDebug(LogFilter.ServiceProtobuf, "{0} Client called server method AccountService.GetLicenses(GetLicensesRequest: {1}) returned GetLicensesResponse: {2} status: {3}.", + GetCallerInfo(), request.ToString(), response.ToString(), status); + if (status == 0) + SendResponse(token, response); + else + SendResponse(token, status); + break; + } + case 33: + { + GetGameTimeRemainingInfoRequest request = new GetGameTimeRemainingInfoRequest(); + request.MergeFrom(stream); + + GetGameTimeRemainingInfoResponse response = new GetGameTimeRemainingInfoResponse(); + BattlenetRpcErrorCode status = HandleGetGameTimeRemainingInfo(request, response); + Log.outDebug(LogFilter.ServiceProtobuf, "{0} Client called server method AccountService.GetGameTimeRemainingInfo(GetGameTimeRemainingInfoRequest: {1}) returned GetGameTimeRemainingInfoResponse: {2} status: {3}.", + GetCallerInfo(), request.ToString(), response.ToString(), status); + if (status == 0) + SendResponse(token, response); + else + SendResponse(token, status); + break; + } + case 34: + { + GetGameSessionInfoRequest request = new GetGameSessionInfoRequest(); + request.MergeFrom(stream); + + GetGameSessionInfoResponse response = new GetGameSessionInfoResponse(); + BattlenetRpcErrorCode status = HandleGetGameSessionInfo(request, response); + Log.outDebug(LogFilter.ServiceProtobuf, "{0} Client called server method AccountService.GetGameSessionInfo(GetGameSessionInfoRequest: {1}) returned GetGameSessionInfoResponse: {2} status: {3}.", + GetCallerInfo(), request.ToString(), response.ToString(), status); + if (status == 0) + SendResponse(token, response); + else + SendResponse(token, status); + break; + } + case 35: + { + GetCAISInfoRequest request = new GetCAISInfoRequest(); + request.MergeFrom(stream); + + GetCAISInfoResponse response = new GetCAISInfoResponse(); + BattlenetRpcErrorCode status = HandleGetCAISInfo(request, response); + Log.outDebug(LogFilter.ServiceProtobuf, "{0} Client called server method AccountService.GetCAISInfo(GetCAISInfoRequest: {1}) returned GetCAISInfoResponse: {2} status: {3}.", + GetCallerInfo(), request.ToString(), response.ToString(), status); + if (status == 0) + SendResponse(token, response); + else + SendResponse(token, status); + break; + } + case 36: + { + ForwardCacheExpireRequest request = new ForwardCacheExpireRequest(); + request.MergeFrom(stream); + + NoData response = new NoData(); + BattlenetRpcErrorCode status = HandleForwardCacheExpire(request, response); + Log.outDebug(LogFilter.ServiceProtobuf, "{0} Client called server method AccountService.ForwardCacheExpire(ForwardCacheExpireRequest: {1}) returned bgs.protocol.NoData: {2} status: {3}.", + GetCallerInfo(), request.ToString(), response.ToString(), status); + if (status == 0) + SendResponse(token, response); + else + SendResponse(token, status); + break; + } + case 37: + { + GetAuthorizedDataRequest request = new GetAuthorizedDataRequest(); + request.MergeFrom(stream); + + GetAuthorizedDataResponse response = new GetAuthorizedDataResponse(); + BattlenetRpcErrorCode status = HandleGetAuthorizedData(request, response); + Log.outDebug(LogFilter.ServiceProtobuf, "{0} Client called server method AccountService.GetAuthorizedData(GetAuthorizedDataRequest: {1}) returned GetAuthorizedDataResponse: {2} status: {3}.", + GetCallerInfo(), request.ToString(), response.ToString(), status); + if (status == 0) + SendResponse(token, response); + else + SendResponse(token, status); + break; + } + case 38: + { + AccountFlagUpdateRequest request = new AccountFlagUpdateRequest(); + request.MergeFrom(stream); + + BattlenetRpcErrorCode status = HandleAccountFlagUpdate(request); + Log.outDebug(LogFilter.ServiceProtobuf, "{0} Client called server method AccountService.AccountFlagUpdate(AccountFlagUpdateRequest: {1}) status: {2}.", + GetCallerInfo(), request.ToString(), status); + if (status != 0) + SendResponse(token, status); + break; + } + case 39: + { + GameAccountFlagUpdateRequest request = new GameAccountFlagUpdateRequest(); + request.MergeFrom(stream); + + BattlenetRpcErrorCode status = HandleGameAccountFlagUpdate(request); + Log.outDebug(LogFilter.ServiceProtobuf, "{0} Client called server method AccountService.GameAccountFlagUpdate(GameAccountFlagUpdateRequest: {1}) status: {2}.", + GetCallerInfo(), request.ToString(), status); + if (status != 0) + SendResponse(token, status); + break; + } + /* case 40: + { + UpdateParentalControlsAndCAISRequest request = new UpdateParentalControlsAndCAISRequest(); + request.MergeFrom(stream); + + NoData response = new NoData(); + BattlenetRpcErrorCode status = HandleUpdateParentalControlsAndCAIS(request, response); + Log.outDebug(LogFilter.ServiceProtobuf, "{0} Client called server method AccountService.UpdateParentalControlsAndCAIS(UpdateParentalControlsAndCAISRequest: {1}) returned bgs.protocol.NoData: {2} status: {3}.", + GetCallerInfo(), request.ToString(), response.ToString(), status); + if (status == 0) + SendResponse(token, response); + else + SendResponse(token, status); + break; + } + case 41: + { + CreateGameAccountRequest request = new CreateGameAccountRequest(); + request.MergeFrom(stream); + + CreateGameAccountResponse response = new CreateGameAccountResponse(); + BattlenetRpcErrorCode status = HandleCreateGameAccount2(request, response); + Log.outDebug(LogFilter.ServiceProtobuf, "{0} Client called server method AccountService.CreateGameAccount2(CreateGameAccountRequest: {1}) returned CreateGameAccountResponse: {2} status: {3}.", + GetCallerInfo(), request.ToString(), response.ToString(), status); + if (status == 0) + SendResponse(token, response); + else + SendResponse(token, status); + break; + } + case 42: + { + GetGameAccountRequest request = new GetGameAccountRequest(); + request.MergeFrom(stream); + + GetGameAccountResponse response = new GetGameAccountResponse(); + BattlenetRpcErrorCode status = HandleGetGameAccount(request, response); + Log.outDebug(LogFilter.ServiceProtobuf, "{0} Client called server method AccountService.GetGameAccount(GetGameAccountRequest: {1}) returned GetGameAccountResponse: {2} status: {3}.", + GetCallerInfo(), request.ToString(), response.ToString(), status); + if (status == 0) + SendResponse(token, &response); + else + SendResponse(token, status); + break; + } + case 43: + { + QueueDeductRecordRequest request = new QueueDeductRecordRequest(); + request.MergeFrom(stream); + + NoData response = new NoData(); + BattlenetRpcErrorCode status = HandleQueueDeductRecord(request, response); + Log.outDebug(LogFilter.ServiceProtobuf, "{0} Client called server method AccountService.QueueDeductRecord(QueueDeductRecordRequest: {1}) returned bgs.protocol.NoData: {2} status: {3}.", + GetCallerInfo(), request.ToString(), response.ToString(), status); + if (status == 0) + SendResponse(token, response); + else + SendResponse(token, status); + break; + }*/ + default: + Log.outError(LogFilter.ServiceProtobuf, "Bad method id {0}.", methodId); + SendResponse(token, BattlenetRpcErrorCode.RpcInvalidMethod); + break; + } + } + + BattlenetRpcErrorCode HandleGetGameAccountBlob(GameAccountHandle request, GameAccountBlob response) + { + Log.outDebug(LogFilter.ServiceProtobuf, "{0} Client tried to call not implemented method AccountService.GetAccount({1})", GetCallerInfo(), request.ToString()); + return BattlenetRpcErrorCode.RpcNotImplemented; + } + + BattlenetRpcErrorCode HandleGetAccount(GetAccountRequest request, GetAccountResponse response) + { + Log.outError(LogFilter.ServiceProtobuf, "{0} Client tried to call not implemented method AccountService.GetAccount({1})", GetCallerInfo(), request.ToString()); + return BattlenetRpcErrorCode.RpcNotImplemented; + } + + BattlenetRpcErrorCode HandleCreateGameAccount(CreateGameAccountRequest request, GameAccountHandle response) + { + Log.outDebug(LogFilter.ServiceProtobuf, "{0} Client tried to call not implemented method AccountService.CreateGameAccount({1})", GetCallerInfo(), request.ToString()); + return BattlenetRpcErrorCode.RpcNotImplemented; + } + + BattlenetRpcErrorCode HandleIsIgrAddress(IsIgrAddressRequest request, NoData response) + { + Log.outDebug(LogFilter.ServiceProtobuf, "{0} Client tried to call not implemented method AccountService.IsIgrAddress({1})", GetCallerInfo(), request.ToString()); + return BattlenetRpcErrorCode.RpcNotImplemented; + } + + BattlenetRpcErrorCode HandleCacheExpire(CacheExpireRequest request) + { + Log.outDebug(LogFilter.ServiceProtobuf, "{0} Client tried to call not implemented method AccountService.CacheExpire({1})", GetCallerInfo(), request.ToString()); + return BattlenetRpcErrorCode.RpcNotImplemented; + } + + BattlenetRpcErrorCode HandleCredentialUpdate(CredentialUpdateRequest request, CredentialUpdateResponse response) + { + Log.outDebug(LogFilter.ServiceProtobuf, "{0} Client tried to call not implemented method AccountService.CredentialUpdate({1})", GetCallerInfo(), request.ToString()); + return BattlenetRpcErrorCode.RpcNotImplemented; + } + + BattlenetRpcErrorCode HandleSubscribe(SubscriptionUpdateRequest request, SubscriptionUpdateResponse response) + { + Log.outDebug(LogFilter.ServiceProtobuf, "{0} Client tried to call not implemented method AccountService.Subscribe({1})", GetCallerInfo(), request.ToString()); + return BattlenetRpcErrorCode.RpcNotImplemented; + } + + BattlenetRpcErrorCode HandleUnsubscribe(SubscriptionUpdateRequest request, NoData response) + { + Log.outDebug(LogFilter.ServiceProtobuf, "{0} Client tried to call not implemented method AccountService.Unsubscribe({1})", GetCallerInfo(), request.ToString()); + return BattlenetRpcErrorCode.RpcNotImplemented; + } + + BattlenetRpcErrorCode HandleGetAccountState(GetAccountStateRequest request, GetAccountStateResponse response) + { + return _session.HandleGetAccountState(request, response); + } + + BattlenetRpcErrorCode HandleGetGameAccountState(GetGameAccountStateRequest request, GetGameAccountStateResponse response) + { + return _session.HandleGetGameAccountState(request, response); + } + + BattlenetRpcErrorCode HandleGetLicenses(GetLicensesRequest request, GetLicensesResponse response) + { + Log.outDebug(LogFilter.ServiceProtobuf, "{0} Client tried to call not implemented method AccountService.GetLicenses({1})", GetCallerInfo(), request.ToString()); + return BattlenetRpcErrorCode.RpcNotImplemented; + } + + BattlenetRpcErrorCode HandleGetGameTimeRemainingInfo(GetGameTimeRemainingInfoRequest request, GetGameTimeRemainingInfoResponse response) + { + Log.outDebug(LogFilter.ServiceProtobuf, "{0} Client tried to call not implemented method AccountService.GetGameTimeRemainingInfo({1})", GetCallerInfo(), request.ToString()); + return BattlenetRpcErrorCode.RpcNotImplemented; + } + + BattlenetRpcErrorCode HandleGetGameSessionInfo(GetGameSessionInfoRequest request, GetGameSessionInfoResponse response) + { + Log.outDebug(LogFilter.ServiceProtobuf, "{0} Client tried to call not implemented method AccountService.GetGameSessionInfo({1})", GetCallerInfo(), request.ToString()); + return BattlenetRpcErrorCode.RpcNotImplemented; + } + + BattlenetRpcErrorCode HandleGetCAISInfo(GetCAISInfoRequest request, GetCAISInfoResponse response) + { + Log.outDebug(LogFilter.ServiceProtobuf, "{0} Client tried to call not implemented method AccountService.GetCAISInfo({1})", GetCallerInfo(), request.ToString()); + return BattlenetRpcErrorCode.RpcNotImplemented; + } + + BattlenetRpcErrorCode HandleForwardCacheExpire(ForwardCacheExpireRequest request, NoData response) + { + Log.outDebug(LogFilter.ServiceProtobuf, "{0} Client tried to call not implemented method AccountService.ForwardCacheExpire({1})", GetCallerInfo(), request.ToString()); + return BattlenetRpcErrorCode.RpcNotImplemented; + } + + BattlenetRpcErrorCode HandleGetAuthorizedData(GetAuthorizedDataRequest request, GetAuthorizedDataResponse response) + { + Log.outDebug(LogFilter.ServiceProtobuf, "{0} Client tried to call not implemented method AccountService.GetAuthorizedData({1})", GetCallerInfo(), request.ToString()); + return BattlenetRpcErrorCode.RpcNotImplemented; + } + + BattlenetRpcErrorCode HandleAccountFlagUpdate(AccountFlagUpdateRequest request) + { + Log.outDebug(LogFilter.ServiceProtobuf, "{0} Client tried to call not implemented method AccountService.AccountFlagUpdate({1})", GetCallerInfo(), request.ToString()); + return BattlenetRpcErrorCode.RpcNotImplemented; + } + + BattlenetRpcErrorCode HandleGameAccountFlagUpdate(GameAccountFlagUpdateRequest request) + { + Log.outDebug(LogFilter.ServiceProtobuf, "{0} Client tried to call not implemented method AccountService.GameAccountFlagUpdate({1})", GetCallerInfo(), request.ToString()); + return BattlenetRpcErrorCode.RpcNotImplemented; + } + + /*BattlenetRpcErrorCode HandleUpdateParentalControlsAndCAIS(UpdateParentalControlsAndCAISRequest request, NoData response) + { + Log.outError(LogFilter.ServiceProtobuf, "{0} Client tried to call not implemented method AccountService.UpdateParentalControlsAndCAIS: {1}", + GetCallerInfo(), request.ToString()); + return BattlenetRpcErrorCode.RpcNotImplemented; + } + + BattlenetRpcErrorCode HandleCreateGameAccount2(CreateGameAccountRequest request, CreateGameAccountResponse response) + { + Log.outError(LogFilter.ServiceProtobuf, "{0} Client tried to call not implemented method AccountService.CreateGameAccount2: {1}", + GetCallerInfo(), request.ToString()); + return BattlenetRpcErrorCode.RpcNotImplemented; + } + + BattlenetRpcErrorCode HandleGetGameAccount(GetGameAccountRequest request, GetGameAccountResponse response) + { + Log.outError(LogFilter.ServiceProtobuf, "{0} Client tried to call not implemented method AccountService.GetGameAccount: {1}", + GetCallerInfo(), request.ToString()); + return BattlenetRpcErrorCode.RpcNotImplemented; + } + + BattlenetRpcErrorCode HandleQueueDeductRecord(QueueDeductRecordRequest request, NoData response) + { + Log.outError(LogFilter.ServiceProtobuf, "{0} Client tried to call not implemented method AccountService.QueueDeductRecord: {1}", + GetCallerInfo(), request.ToString()); + return BattlenetRpcErrorCode.RpcNotImplemented; + } + */ + } + + class AccountListener : ServiceBase + { + public AccountListener(Session session, uint serviceHash) : base(session, serviceHash) { } + + public override void CallServerMethod(uint token, uint methodId, CodedInputStream stream) + { + switch (methodId) + { + case 1: + { + AccountStateNotification request = new AccountStateNotification(); + request.MergeFrom(stream); + + BattlenetRpcErrorCode status = HandleOnAccountStateUpdated(request); + Log.outDebug(LogFilter.ServiceProtobuf, "{0} Client called server method AccountListener.OnAccountStateUpdated(AccountStateNotification: {1} status: {2}.", GetCallerInfo(), request.ToString(), status); + if (status != 0) + SendResponse(token, status); + break; + } + case 2: + { + GameAccountStateNotification request = new GameAccountStateNotification(); + request.MergeFrom(stream); + + BattlenetRpcErrorCode status = HandleOnGameAccountStateUpdated(request); + Log.outDebug(LogFilter.ServiceProtobuf, "{0} Client called server method AccountListener.OnGameAccountStateUpdated(GameAccountStateNotification: {1} status: {2}.", GetCallerInfo(), request.ToString(), status); + if (status != 0) + SendResponse(token, status); + break; + } + case 3: + { + GameAccountNotification request = new GameAccountNotification(); + request.MergeFrom(stream); + + BattlenetRpcErrorCode status = HandleOnGameAccountsUpdated(request); + Log.outDebug(LogFilter.ServiceProtobuf, "{0} Client called server method AccountListener.OnGameAccountsUpdated(GameAccountNotification: {1} status: {2}.", GetCallerInfo(), request.ToString(), status); + if (status != 0) + SendResponse(token, status); + break; + } + case 4: + { + GameAccountSessionNotification request = new GameAccountSessionNotification(); + request.MergeFrom(stream); + + BattlenetRpcErrorCode status = HandleOnGameSessionUpdated(request); + Log.outDebug(LogFilter.ServiceProtobuf, "{0} Client called server method AccountListener.OnGameSessionUpdated(GameAccountSessionNotification: {1} status: {2}.", GetCallerInfo(), request.ToString(), status); + if (status != 0) + SendResponse(token, status); + break; + } + default: + Log.outError(LogFilter.ServiceProtobuf, "Bad method id {0}.", methodId); + SendResponse(token, BattlenetRpcErrorCode.RpcInvalidMethod); + break; + } + } + + BattlenetRpcErrorCode HandleOnAccountStateUpdated(AccountStateNotification request) + { + Log.outError(LogFilter.ServiceProtobuf, "{0} Client tried to call not implemented method AccountListener.OnAccountStateUpdated: {1}", GetCallerInfo(), request.ToString()); + return BattlenetRpcErrorCode.RpcNotImplemented; + } + + BattlenetRpcErrorCode HandleOnGameAccountStateUpdated(GameAccountStateNotification request) + { + Log.outError(LogFilter.ServiceProtobuf, "{0} Client tried to call not implemented method AccountListener.OnGameAccountStateUpdated: {1}", GetCallerInfo(), request.ToString()); + return BattlenetRpcErrorCode.RpcNotImplemented; + } + + BattlenetRpcErrorCode HandleOnGameAccountsUpdated(GameAccountNotification request) + { + Log.outError(LogFilter.ServiceProtobuf, "{0} Client tried to call not implemented method AccountListener.OnGameAccountsUpdated: {1}", GetCallerInfo(), request.ToString()); + return BattlenetRpcErrorCode.RpcNotImplemented; + } + + BattlenetRpcErrorCode HandleOnGameSessionUpdated(GameAccountSessionNotification request) + { + Log.outError(LogFilter.ServiceProtobuf, "{0} Client tried to call not implemented method AccountListener.OnGameSessionUpdated: {1}", GetCallerInfo(), request.ToString()); + return BattlenetRpcErrorCode.RpcNotImplemented; + } + } +} diff --git a/BNetServer/Services/AuthenticationService.cs b/BNetServer/Services/AuthenticationService.cs new file mode 100644 index 000000000..ba9989800 --- /dev/null +++ b/BNetServer/Services/AuthenticationService.cs @@ -0,0 +1,442 @@ +/* + * Copyright (C) 2012-2017 CypherCore + * + * 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, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +using Bgs.Protocol; +using Bgs.Protocol.Authentication.V1; +using BNetServer.Services; +using Framework.Constants; +using Google.Protobuf; + +namespace BNetServer.Networking +{ + class AuthenticationService : ServiceBase + { + public AuthenticationService(Session session, uint serviceHash) : base(session, serviceHash) { } + + public override void CallServerMethod(uint token, uint methodId, CodedInputStream stream) + { + switch (methodId) + { + case 1: + { + LogonRequest request = new LogonRequest(); + request.MergeFrom(stream); + + NoData response = new NoData(); + BattlenetRpcErrorCode status = HandleLogon(request, response); + Log.outDebug(LogFilter.ServiceProtobuf, "{0} Client called server method AuthenticationService.Logon(bgs.protocol.authentication.v1.LogonRequest: {1}) returned bgs.protocol.NoData: {2} status {3}.", + GetCallerInfo(), request.ToString(), response.ToString(), status); + if (status == 0) + SendResponse(token, response); + else + SendResponse(token, status); + break; + } + case 2: + { + ModuleNotification request = new ModuleNotification(); + request.MergeFrom(stream); + + + NoData response = new NoData(); + BattlenetRpcErrorCode status = HandleModuleNotify(request, response); + Log.outDebug(LogFilter.ServiceProtobuf, "{0} Client called server method AuthenticationService.ModuleNotify(bgs.protocol.authentication.v1.ModuleNotification: {1}) returned bgs.protocol.NoData: {2} status: {3}.", + GetCallerInfo(), request.ToString(), response.ToString(), status); + if (status == 0) + SendResponse(token, response); + else + SendResponse(token, status); + break; + } + case 3: + { + ModuleMessageRequest request = new ModuleMessageRequest(); + request.MergeFrom(stream); + + + NoData response = new NoData(); + BattlenetRpcErrorCode status = HandleModuleMessage(request, response); + Log.outDebug(LogFilter.ServiceProtobuf, "{0} Client called server method AuthenticationService.ModuleMessage(bgs.protocol.authentication.v1.ModuleMessageRequest: {1}) returned bgs.protocol.NoData: {2} status: {3}.", + GetCallerInfo(), request.ToString(), response.ToString(), status); + if (status == 0) + SendResponse(token, response); + else + SendResponse(token, status); + break; + } + case 4: + { + EntityId request = new EntityId(); + request.MergeFrom(stream); + + + NoData response = new NoData(); + BattlenetRpcErrorCode status = HandleSelectGameAccount_DEPRECATED(request, response); + Log.outDebug(LogFilter.ServiceProtobuf, "{0} Client called server method AuthenticationService.SelectGameAccount_DEPRECATED(bgs.protocol.EntityId: {1}) returned bgs.protocol.NoData: {2} status: {3}.", + GetCallerInfo(), request.ToString(), response.ToString(), status); + if (status == 0) + SendResponse(token, response); + else + SendResponse(token, status); + break; + } + case 5: + { + GenerateSSOTokenRequest request = new GenerateSSOTokenRequest(); + request.MergeFrom(stream); + + + GenerateSSOTokenResponse response = new GenerateSSOTokenResponse(); + BattlenetRpcErrorCode status = HandleGenerateSSOToken(request, response); + Log.outDebug(LogFilter.ServiceProtobuf, "{0} Client called server method AuthenticationService.GenerateSSOToken(bgs.protocol.authentication.v1.GenerateSSOTokenRequest: {1}) returned bgs.protocol.authentication.v1.GenerateSSOTokenResponse: {2} status: {3}.", + GetCallerInfo(), request.ToString(), response.ToString(), status); + if (status == 0) + SendResponse(token, response); + else + SendResponse(token, status); + break; + } + case 6: + { + SelectGameAccountRequest request = new SelectGameAccountRequest(); + request.MergeFrom(stream); + + + NoData response = new NoData(); + BattlenetRpcErrorCode status = HandleSelectGameAccount(request, response); + Log.outDebug(LogFilter.ServiceProtobuf, "{0} Client called server method AuthenticationService.SelectGameAccount(bgs.protocol.authentication.v1.SelectGameAccountRequest: {1}) returned bgs.protocol.NoData: {2} status: {3}.", + GetCallerInfo(), request.ToString(), response.ToString(), status); + if (status == 0) + SendResponse(token, response); + else + SendResponse(token, status); + break; + } + case 7: + { + VerifyWebCredentialsRequest request = new VerifyWebCredentialsRequest(); + request.MergeFrom(stream); + + + NoData response = new NoData(); + BattlenetRpcErrorCode status = HandleVerifyWebCredentials(request, response); + Log.outDebug(LogFilter.ServiceProtobuf, "{0} Client called server method AuthenticationService.VerifyWebCredentials(bgs.protocol.authentication.v1.VerifyWebCredentialsRequest: {1}) returned bgs.protocol.NoData: {2} status: {3}.", + GetCallerInfo(), request.ToString(), response.ToString(), status); + if (status == 0) + SendResponse(token, response); + else + SendResponse(token, status); + break; + } + case 8: + { + GenerateWebCredentialsRequest request = new GenerateWebCredentialsRequest(); + request.MergeFrom(stream); + + + GenerateWebCredentialsResponse response = new GenerateWebCredentialsResponse(); + BattlenetRpcErrorCode status = HandleGenerateWebCredentials(request, response); + Log.outDebug(LogFilter.ServiceProtobuf, "{0} Client called server method AuthenticationService.GenerateWebCredentials(bgs.protocol.authentication.v1.GenerateWebCredentialsRequest: {1}) returned bgs.protocol.authentication.v1.GenerateWebCredentialsResponse: {2} status: {3}.", + GetCallerInfo(), request.ToString(), response.ToString(), status); + if (status == 0) + SendResponse(token, response); + else + SendResponse(token, status); + break; + } + default: + Log.outError(LogFilter.ServiceProtobuf, "Bad method id {0}.", methodId); + SendResponse(token, BattlenetRpcErrorCode.RpcInvalidMethod); + break; + } + } + + BattlenetRpcErrorCode HandleLogon(LogonRequest request, NoData response) + { + return _session.HandleLogon(request); + } + + BattlenetRpcErrorCode HandleModuleNotify(ModuleNotification request, NoData response) + { + Log.outError(LogFilter.ServiceProtobuf, "{0} Client tried to call not implemented method AuthenticationService.ModuleNotify: {1}", + GetCallerInfo(), request.ToString()); + return BattlenetRpcErrorCode.RpcNotImplemented; + } + + BattlenetRpcErrorCode HandleModuleMessage(ModuleMessageRequest request, NoData response) + { + Log.outError(LogFilter.ServiceProtobuf, "{0} Client tried to call not implemented method AuthenticationService.ModuleMessage: {1}", + GetCallerInfo(), request.ToString()); + return BattlenetRpcErrorCode.RpcNotImplemented; + } + + BattlenetRpcErrorCode HandleSelectGameAccount_DEPRECATED(EntityId request, NoData response) + { + Log.outError(LogFilter.ServiceProtobuf, "{0} Client tried to call not implemented method AuthenticationService.SelectGameAccount_DEPRECATED: {1}", + GetCallerInfo(), request.ToString()); + return BattlenetRpcErrorCode.RpcNotImplemented; + } + + BattlenetRpcErrorCode HandleGenerateSSOToken(GenerateSSOTokenRequest request, GenerateSSOTokenResponse response) + { + Log.outError(LogFilter.ServiceProtobuf, "{0} Client tried to call not implemented method AuthenticationService.GenerateSSOToken: {1}", + GetCallerInfo(), request.ToString()); + return BattlenetRpcErrorCode.RpcNotImplemented; + } + + BattlenetRpcErrorCode HandleSelectGameAccount(SelectGameAccountRequest request, NoData response) + { + Log.outError(LogFilter.ServiceProtobuf, "{0} Client tried to call not implemented method AuthenticationService.SelectGameAccount: {1}", + GetCallerInfo(), request.ToString()); + return BattlenetRpcErrorCode.RpcNotImplemented; + } + + BattlenetRpcErrorCode HandleVerifyWebCredentials(VerifyWebCredentialsRequest request, NoData response) + { + return _session.HandleVerifyWebCredentials(request); + } + + BattlenetRpcErrorCode HandleGenerateWebCredentials(GenerateWebCredentialsRequest request, GenerateWebCredentialsResponse response) + { + Log.outError(LogFilter.ServiceProtobuf, "{0} Client tried to call not implemented method AuthenticationService.GenerateWebCredentials: {1}", + GetCallerInfo(), request.ToString()); + return BattlenetRpcErrorCode.RpcNotImplemented; + } + } + + class AuthenticationListener : ServiceBase + { + public AuthenticationListener(Session session, uint serviceHash) : base(session, serviceHash) { } + + public override void CallServerMethod(uint token, uint methodId, CodedInputStream stream) + { + switch (methodId) + { + case 1: + { + ModuleLoadRequest request = new ModuleLoadRequest(); + request.MergeFrom(stream); + + + BattlenetRpcErrorCode status = HandleOnModuleLoad(request); + Log.outDebug(LogFilter.ServiceProtobuf, "{0} Client called server method AuthenticationListener.OnModuleLoad(bgs.protocol.authentication.v1.ModuleLoadRequest: {1}) status: {2}.", + GetCallerInfo(), request.ToString(), status); + if (status != 0) + SendResponse(token, status); + break; + } + case 2: + { + ModuleMessageRequest request = new ModuleMessageRequest(); + request.MergeFrom(stream); + + + NoData response = new NoData(); + BattlenetRpcErrorCode status = HandleOnModuleMessage(request, response); + Log.outDebug(LogFilter.ServiceProtobuf, "{0} Client called server method AuthenticationListener.OnModuleMessage(bgs.protocol.authentication.v1.ModuleMessageRequest: {1}) returned bgs.protocol.NoData: {2} status: {3}.", + GetCallerInfo(), request.ToString(), response.ToString(), status); + if (status == 0) + SendResponse(token, response); + else + SendResponse(token, status); + break; + } + case 4: + { + ServerStateChangeRequest request = new ServerStateChangeRequest(); + request.MergeFrom(stream); + + + BattlenetRpcErrorCode status = HandleOnServerStateChange(request); + Log.outDebug(LogFilter.ServiceProtobuf, "{0} Client called server method AuthenticationListener.OnServerStateChange(bgs.protocol.authentication.v1.ServerStateChangeRequest: {1}) status: {2}.", + GetCallerInfo(), request.ToString(), status); + if (status != 0) + SendResponse(token, status); + break; + } + case 5: + { + LogonResult request = new LogonResult(); + request.MergeFrom(stream); + + + BattlenetRpcErrorCode status = HandleOnLogonComplete(request); + Log.outDebug(LogFilter.ServiceProtobuf, "{0} Client called server method AuthenticationListener.OnLogonComplete(bgs.protocol.authentication.v1.LogonResult: {1}) status: {2}.", + GetCallerInfo(), request.ToString(), status); + if (status != 0) + SendResponse(token, status); + break; + } + case 6: + { + MemModuleLoadRequest request = new MemModuleLoadRequest(); + request.MergeFrom(stream); + + + MemModuleLoadResponse response = new MemModuleLoadResponse(); + BattlenetRpcErrorCode status = HandleOnMemModuleLoad(request, response); + Log.outDebug(LogFilter.ServiceProtobuf, "{0} Client called server method AuthenticationListener.OnMemModuleLoad(bgs.protocol.authentication.v1.MemModuleLoadRequest: {1}) returned bgs.protocol.authentication.v1.MemModuleLoadResponse: {2} status: {3}.", + GetCallerInfo(), request.ToString(), response.ToString(), status); + if (status == 0) + SendResponse(token, response); + else + SendResponse(token, status); + break; + } + case 10: + { + LogonUpdateRequest request = new LogonUpdateRequest(); + request.MergeFrom(stream); + + + BattlenetRpcErrorCode status = HandleOnLogonUpdate(request); + Log.outDebug(LogFilter.ServiceProtobuf, "{0} Client called server method AuthenticationListener.OnLogonUpdate(bgs.protocol.authentication.v1.LogonUpdateRequest: {1}) status: {2}.", + GetCallerInfo(), request.ToString(), status); + if (status != 0) + SendResponse(token, status); + break; + } + case 11: + { + VersionInfoNotification request = new VersionInfoNotification(); + request.MergeFrom(stream); + + + BattlenetRpcErrorCode status = HandleOnVersionInfoUpdated(request); + Log.outDebug(LogFilter.ServiceProtobuf, "{0} Client called server method AuthenticationListener.OnVersionInfoUpdated(bgs.protocol.authentication.v1.VersionInfoNotification: {1}) status: {2}.", + GetCallerInfo(), request.ToString(), status); + if (status != 0) + SendResponse(token, status); + break; + } + case 12: + { + LogonQueueUpdateRequest request = new LogonQueueUpdateRequest(); + request.MergeFrom(stream); + + + BattlenetRpcErrorCode status = HandleOnLogonQueueUpdate(request); + Log.outDebug(LogFilter.ServiceProtobuf, "{0} Client called server method AuthenticationListener.OnLogonQueueUpdate(bgs.protocol.authentication.v1.LogonQueueUpdateRequest: {1}) status: {2}.", + GetCallerInfo(), request.ToString(), status); + if (status != 0) + SendResponse(token, status); + break; + } + case 13: + { + NoData request = new NoData(); + request.MergeFrom(stream); + + + BattlenetRpcErrorCode status = HandleOnLogonQueueEnd(request); + Log.outDebug(LogFilter.ServiceProtobuf, "{0} Client called server method AuthenticationListener.OnLogonQueueEnd(bgs.protocol.NoData: {1}) status: {2}.", + GetCallerInfo(), request.ToString(), status); + if (status != 0) + SendResponse(token, status); + break; + } + case 14: + { + GameAccountSelectedRequest request = new GameAccountSelectedRequest(); + request.MergeFrom(stream); + + + BattlenetRpcErrorCode status = HandleOnGameAccountSelected(request); + Log.outDebug(LogFilter.ServiceProtobuf, "{0} Client called server method AuthenticationListener.OnGameAccountSelected(bgs.protocol.authentication.v1.GameAccountSelectedRequest: {1}) status: {2}.", + GetCallerInfo(), request.ToString(), status); + if (status != 0) + SendResponse(token, status); + break; + } + default: + Log.outError(LogFilter.ServiceProtobuf, "Bad method id {0}.", methodId); + SendResponse(token, BattlenetRpcErrorCode.RpcInvalidMethod); + break; + } + } + + BattlenetRpcErrorCode HandleOnModuleLoad(ModuleLoadRequest request) + { + Log.outError(LogFilter.ServiceProtobuf, "{0} Client tried to call not implemented method AuthenticationListener.OnModuleLoad: {1}", + GetCallerInfo(), request.ToString()); + return BattlenetRpcErrorCode.RpcNotImplemented; + } + + BattlenetRpcErrorCode HandleOnModuleMessage(ModuleMessageRequest request, NoData response) + { + Log.outError(LogFilter.ServiceProtobuf, "{0} Client tried to call not implemented method AuthenticationListener.OnModuleMessage: {1}", + GetCallerInfo(), request.ToString()); + return BattlenetRpcErrorCode.RpcNotImplemented; + } + + BattlenetRpcErrorCode HandleOnServerStateChange(ServerStateChangeRequest request) + { + Log.outError(LogFilter.ServiceProtobuf, "{0} Client tried to call not implemented method AuthenticationListener.OnServerStateChange: {1}", + GetCallerInfo(), request.ToString()); + return BattlenetRpcErrorCode.RpcNotImplemented; + } + + BattlenetRpcErrorCode HandleOnLogonComplete(LogonResult request) + { + Log.outError(LogFilter.ServiceProtobuf, "{0} Client tried to call not implemented method AuthenticationListener.OnLogonComplete: {1}", + GetCallerInfo(), request.ToString()); + return BattlenetRpcErrorCode.RpcNotImplemented; + } + + BattlenetRpcErrorCode HandleOnMemModuleLoad(MemModuleLoadRequest request, MemModuleLoadResponse response) + { + Log.outError(LogFilter.ServiceProtobuf, "{0} Client tried to call not implemented method AuthenticationListener.OnMemModuleLoad: {1}", + GetCallerInfo(), request.ToString()); + return BattlenetRpcErrorCode.RpcNotImplemented; + } + + BattlenetRpcErrorCode HandleOnLogonUpdate(LogonUpdateRequest request) + { + Log.outError(LogFilter.ServiceProtobuf, "{0} Client tried to call not implemented method AuthenticationListener.OnLogonUpdate: {1}", + GetCallerInfo(), request.ToString()); + return BattlenetRpcErrorCode.RpcNotImplemented; + } + + BattlenetRpcErrorCode HandleOnVersionInfoUpdated(VersionInfoNotification request) + { + Log.outError(LogFilter.ServiceProtobuf, "{0} Client tried to call not implemented method AuthenticationListener.OnVersionInfoUpdated: {1}", + GetCallerInfo(), request.ToString()); + return BattlenetRpcErrorCode.RpcNotImplemented; + } + + BattlenetRpcErrorCode HandleOnLogonQueueUpdate(LogonQueueUpdateRequest request) + { + Log.outError(LogFilter.ServiceProtobuf, "{0} Client tried to call not implemented method AuthenticationListener.OnLogonQueueUpdate: {1}", + GetCallerInfo(), request.ToString()); + return BattlenetRpcErrorCode.RpcNotImplemented; + } + + BattlenetRpcErrorCode HandleOnLogonQueueEnd(NoData request) + { + Log.outError(LogFilter.ServiceProtobuf, "{0} Client tried to call not implemented method AuthenticationListener.OnLogonQueueEnd: {1}", + GetCallerInfo(), request.ToString()); + return BattlenetRpcErrorCode.RpcNotImplemented; + } + + BattlenetRpcErrorCode HandleOnGameAccountSelected(GameAccountSelectedRequest request) + { + Log.outError(LogFilter.ServiceProtobuf, "{0} Client tried to call not implemented method AuthenticationListener.OnGameAccountSelected: {1}", + GetCallerInfo(), request.ToString()); + return BattlenetRpcErrorCode.RpcNotImplemented; + } + } +} diff --git a/BNetServer/Services/ChallengeService.cs b/BNetServer/Services/ChallengeService.cs new file mode 100644 index 000000000..04faf7df5 --- /dev/null +++ b/BNetServer/Services/ChallengeService.cs @@ -0,0 +1,228 @@ +/* + * Copyright (C) 2012-2017 CypherCore + * + * 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, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +using Bgs.Protocol; +using Bgs.Protocol.Challenge.V1; +using BNetServer.Services; +using Framework.Constants; +using Google.Protobuf; + +namespace BNetServer.Networking +{ + class ChallengeService : ServiceBase + { + public ChallengeService(Session session, uint serviceHash) : base(session, serviceHash) { } + + public override void CallServerMethod(uint token, uint methodId, CodedInputStream stream) + { + switch (methodId) + { + case 1: + { + ChallengePickedRequest request = new ChallengePickedRequest(); + request.MergeFrom(stream); + + + ChallengePickedResponse response = new ChallengePickedResponse(); + BattlenetRpcErrorCode status = HandleChallengePicked(request, response); + Log.outDebug(LogFilter.ServiceProtobuf, "{0} Client called server method ChallengeService.ChallengePicked(bgs.protocol.challenge.v1.ChallengePickedRequest: {1}) returned bgs.protocol.challenge.v1.ChallengePickedResponse: {2} status: {3}.", + GetCallerInfo(), request.ToString(), response.ToString(), status); + if (status == 0) + SendResponse(token, response); + else + SendResponse(token, status); + break; + } + case 2: + { + ChallengeAnsweredRequest request = new ChallengeAnsweredRequest(); + request.MergeFrom(stream); + + + ChallengeAnsweredResponse response = new ChallengeAnsweredResponse(); + BattlenetRpcErrorCode status = HandleChallengeAnswered(request, response); + Log.outDebug(LogFilter.ServiceProtobuf, "{0} Client called server method ChallengeService.ChallengeAnswered(bgs.protocol.challenge.v1.ChallengeAnsweredRequest: {1}) returned bgs.protocol.challenge.v1.ChallengeAnsweredResponse: {2} status: {3}.", + GetCallerInfo(), request.ToString(), response.ToString(), status); + if (status == 0) + SendResponse(token, response); + else + SendResponse(token, status); + break; + } + case 3: + { + ChallengeCancelledRequest request = new ChallengeCancelledRequest(); + request.MergeFrom(stream); + + + NoData response = new NoData(); + BattlenetRpcErrorCode status = HandleChallengeCancelled(request, response); + Log.outDebug(LogFilter.ServiceProtobuf, "{0} Client called server method ChallengeService.ChallengeCancelled(bgs.protocol.challenge.v1.ChallengeCancelledRequest: {1}) returned bgs.protocol.NoData: {2} status: {3}.", + GetCallerInfo(), request.ToString(), response.ToString(), status); + if (status == 0) + SendResponse(token, response); + else + SendResponse(token, status); + break; + } + case 4: + { + SendChallengeToUserRequest request = new SendChallengeToUserRequest(); + request.MergeFrom(stream); + + + SendChallengeToUserResponse response = new SendChallengeToUserResponse(); + BattlenetRpcErrorCode status = HandleSendChallengeToUser(request, response); + Log.outDebug(LogFilter.ServiceProtobuf, "{0} Client called server method ChallengeService.SendChallengeToUser(bgs.protocol.challenge.v1.SendChallengeToUserRequest: {1}) returned bgs.protocol.challenge.v1.SendChallengeToUserResponse: {2} status: {3}.", + GetCallerInfo(), request.ToString(), response.ToString(), status); + if (status == 0) + SendResponse(token, response); + else + SendResponse(token, status); + break; + } + default: + Log.outError(LogFilter.ServiceProtobuf, "Bad method id {0}.", methodId); + SendResponse(token, BattlenetRpcErrorCode.RpcInvalidMethod); + break; + } + } + + BattlenetRpcErrorCode HandleChallengePicked(ChallengePickedRequest request, ChallengePickedResponse response) { + Log.outError(LogFilter.ServiceProtobuf, "{0} Client tried to call not implemented method ChallengeService.ChallengePicked: {1}", + GetCallerInfo(), request.ToString()); + return BattlenetRpcErrorCode.RpcNotImplemented; + } + + BattlenetRpcErrorCode HandleChallengeAnswered(ChallengeAnsweredRequest request, ChallengeAnsweredResponse response) + { + Log.outError(LogFilter.ServiceProtobuf, "{0} Client tried to call not implemented method ChallengeService.ChallengeAnswered: {1}", + GetCallerInfo(), request.ToString()); + return BattlenetRpcErrorCode.RpcNotImplemented; + } + + BattlenetRpcErrorCode HandleChallengeCancelled(ChallengeCancelledRequest request, NoData response) + { + Log.outError(LogFilter.ServiceProtobuf, "{0} Client tried to call not implemented method ChallengeService.ChallengeCancelled: {1}", + GetCallerInfo(), request.ToString()); + return BattlenetRpcErrorCode.RpcNotImplemented; + } + + BattlenetRpcErrorCode HandleSendChallengeToUser(SendChallengeToUserRequest request, SendChallengeToUserResponse response) + { + Log.outError(LogFilter.ServiceProtobuf, "{0} Client tried to call not implemented method ChallengeService.SendChallengeToUser: {1}", + GetCallerInfo(), request.ToString()); + return BattlenetRpcErrorCode.RpcNotImplemented; + } + + } + + class ChallengeListener : ServiceBase + { + public ChallengeListener(Session session, uint serviceHash) : base(session, serviceHash) { } + + public override void CallServerMethod(uint token, uint methodId, CodedInputStream stream) + { + switch (methodId) + { + case 1: + { + ChallengeUserRequest request = new ChallengeUserRequest(); + request.MergeFrom(stream); + + + BattlenetRpcErrorCode status = HandleOnChallengeUser(request); + Log.outDebug(LogFilter.ServiceProtobuf, "{0} Client called server method ChallengeListener.OnChallengeUser(bgs.protocol.challenge.v1.ChallengeUserRequest: {1} status: {2}.", + GetCallerInfo(), request.ToString(), status); + if (status != 0) + SendResponse(token, status); + break; + } + case 2: + { + ChallengeResultRequest request = new ChallengeResultRequest(); + request.MergeFrom(stream); + + + BattlenetRpcErrorCode status = HandleOnChallengeResult(request); + Log.outDebug(LogFilter.ServiceProtobuf, "{0} Client called server method ChallengeListener.OnChallengeResult(bgs.protocol.challenge.v1.ChallengeResultRequest: {1} status: {2}.", + GetCallerInfo(), request.ToString(), status); + if (status != 0) + SendResponse(token, status); + break; + } + case 3: + { + ChallengeExternalRequest request = new ChallengeExternalRequest(); + request.MergeFrom(stream); + + + BattlenetRpcErrorCode status = HandleOnExternalChallenge(request); + Log.outDebug(LogFilter.ServiceProtobuf, "{0} Client called server method ChallengeListener.OnExternalChallenge(bgs.protocol.challenge.v1.ChallengeExternalRequest: {1} status: {2}.", + GetCallerInfo(), request.ToString(), status); + if (status != 0) + SendResponse(token, status); + break; + } + case 4: + { + ChallengeExternalResult request = new ChallengeExternalResult(); + request.MergeFrom(stream); + + + BattlenetRpcErrorCode status = HandleOnExternalChallengeResult(request); + Log.outDebug(LogFilter.ServiceProtobuf, "{0} Client called server method ChallengeListener.OnExternalChallengeResult(bgs.protocol.challenge.v1.ChallengeExternalResult: {1} status: {2}.", + GetCallerInfo(), request.ToString(), status); + if (status != 0) + SendResponse(token, status); + break; + } + default: + Log.outError(LogFilter.ServiceProtobuf, "Bad method id {0}.", methodId); + SendResponse(token, BattlenetRpcErrorCode.RpcInvalidMethod); + break; + } + } + + BattlenetRpcErrorCode HandleOnChallengeUser(ChallengeUserRequest request) { + Log.outError(LogFilter.ServiceProtobuf, "{0} Client tried to call not implemented method ChallengeListener.OnChallengeUser: {1}", + GetCallerInfo(), request.ToString()); + return BattlenetRpcErrorCode.RpcNotImplemented; + } + + BattlenetRpcErrorCode HandleOnChallengeResult(ChallengeResultRequest request) + { + Log.outError(LogFilter.ServiceProtobuf, "{0} Client tried to call not implemented method ChallengeListener.OnChallengeResult: {1}", + GetCallerInfo(), request.ToString()); + return BattlenetRpcErrorCode.RpcNotImplemented; + } + + BattlenetRpcErrorCode HandleOnExternalChallenge(ChallengeExternalRequest request) + { + Log.outError(LogFilter.ServiceProtobuf, "{0} Client tried to call not implemented method ChallengeListener.OnExternalChallenge: {1}", + GetCallerInfo(), request.ToString()); + return BattlenetRpcErrorCode.RpcNotImplemented; + } + + BattlenetRpcErrorCode HandleOnExternalChallengeResult(ChallengeExternalResult request) + { + Log.outError(LogFilter.ServiceProtobuf, "{0} Client tried to call not implemented method ChallengeListener.OnExternalChallengeResult: {1}", + GetCallerInfo(), request.ToString()); + return BattlenetRpcErrorCode.RpcNotImplemented; + } + } +} diff --git a/BNetServer/Services/ChannelService.cs b/BNetServer/Services/ChannelService.cs new file mode 100644 index 000000000..436620e27 --- /dev/null +++ b/BNetServer/Services/ChannelService.cs @@ -0,0 +1,312 @@ +/* + * Copyright (C) 2012-2017 CypherCore + * + * 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, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +using Bgs.Protocol; +using Bgs.Protocol.Channel.V1; +using BNetServer.Services; +using Framework.Constants; +using Google.Protobuf; + +namespace BNetServer.Networking +{ + class ChannelService : ServiceBase + { + public ChannelService(Session session, uint serviceHash) : base(session, serviceHash) { } + + public override void CallServerMethod(uint token, uint methodId, CodedInputStream stream) + { + switch (methodId) + { + case 2: + { + RemoveMemberRequest request = new RemoveMemberRequest(); + request.MergeFrom(stream); + + + NoData response = new NoData(); + BattlenetRpcErrorCode status = HandleRemoveMember(request, response); + Log.outDebug(LogFilter.ServiceProtobuf, "{0} Client called server method ChannelService.RemoveMember(bgs.protocol.channel.v1.RemoveMemberRequest: {1}) returned bgs.protocol.NoData: {2} status: {3}.", + GetCallerInfo(), request.ToString(), response.ToString(), status); + if (status == 0) + SendResponse(token, response); + else + SendResponse(token, status); + break; + } + case 3: + { + SendMessageRequest request = new SendMessageRequest(); + request.MergeFrom(stream); + + + NoData response = new NoData(); + BattlenetRpcErrorCode status = HandleSendMessage(request, response); + Log.outDebug(LogFilter.ServiceProtobuf, "{0} Client called server method ChannelService.SendMessage(bgs.protocol.channel.v1.SendMessageRequest: {1}) returned bgs.protocol.NoData: {2} status: {3}.", + GetCallerInfo(), request.ToString(), response.ToString(), status); + if (status == 0) + SendResponse(token, response); + else + SendResponse(token, status); + break; + } + case 4: + { + UpdateChannelStateRequest request = new UpdateChannelStateRequest(); + request.MergeFrom(stream); + + + NoData response = new NoData(); + BattlenetRpcErrorCode status = HandleUpdateChannelState(request, response); + Log.outDebug(LogFilter.ServiceProtobuf, "{0} Client called server method ChannelService.UpdateChannelState(bgs.protocol.channel.v1.UpdateChannelStateRequest: {1}) returned bgs.protocol.NoData: {2} status: {3}.", + GetCallerInfo(), request.ToString(), response.ToString(), status); + if (status == 0) + SendResponse(token, response); + else + SendResponse(token, status); + break; + } + case 5: + { + UpdateMemberStateRequest request = new UpdateMemberStateRequest(); + request.MergeFrom(stream); + + + NoData response = new NoData(); + BattlenetRpcErrorCode status = HandleUpdateMemberState(request, response); + Log.outDebug(LogFilter.ServiceProtobuf, "{0} Client called server method ChannelService.UpdateMemberState(bgs.protocol.channel.v1.UpdateMemberStateRequest: {1}) returned bgs.protocol.NoData: {2} status: {3}.", + GetCallerInfo(), request.ToString(), response.ToString(), status); + if (status == 0) + SendResponse(token, response); + else + SendResponse(token, status); + break; + } + case 6: + { + DissolveRequest request = new DissolveRequest(); + request.MergeFrom(stream); + + + NoData response = new NoData(); + BattlenetRpcErrorCode status = HandleDissolve(request, response); + Log.outDebug(LogFilter.ServiceProtobuf, "{0} Client called server method ChannelService.Dissolve(bgs.protocol.channel.v1.DissolveRequest: {1}) returned bgs.protocol.NoData: {2} status: {3}.", + GetCallerInfo(), request.ToString(), response.ToString(), status); + if (status == 0) + SendResponse(token, response); + else + SendResponse(token, status); + break; + } + default: + Log.outError(LogFilter.ServiceProtobuf, "Bad method id {0}.", methodId); + SendResponse(token, BattlenetRpcErrorCode.RpcInvalidMethod); + break; + } + } + + BattlenetRpcErrorCode HandleRemoveMember(RemoveMemberRequest request, NoData response) + { + Log.outError(LogFilter.ServiceProtobuf, "{0} Client tried to call not implemented method ChannelService.RemoveMember: {1}", + GetCallerInfo(), request.ToString()); + return BattlenetRpcErrorCode.RpcNotImplemented; + } + + BattlenetRpcErrorCode HandleSendMessage(SendMessageRequest request, NoData response) + { + Log.outError(LogFilter.ServiceProtobuf, "{0} Client tried to call not implemented method ChannelService.SendMessage: {1}", + GetCallerInfo(), request.ToString()); + return BattlenetRpcErrorCode.RpcNotImplemented; + } + + BattlenetRpcErrorCode HandleUpdateChannelState(UpdateChannelStateRequest request, NoData response) + { + Log.outError(LogFilter.ServiceProtobuf, "{0} Client tried to call not implemented method ChannelService.UpdateChannelState: {1}", + GetCallerInfo(), request.ToString()); + return BattlenetRpcErrorCode.RpcNotImplemented; + } + + BattlenetRpcErrorCode HandleUpdateMemberState(UpdateMemberStateRequest request, NoData response) + { + Log.outError(LogFilter.ServiceProtobuf, "{0} Client tried to call not implemented method ChannelService.UpdateMemberState: {1}", + GetCallerInfo(), request.ToString()); + return BattlenetRpcErrorCode.RpcNotImplemented; + } + + BattlenetRpcErrorCode HandleDissolve(DissolveRequest request, NoData response) + { + Log.outError(LogFilter.ServiceProtobuf, "{0} Client tried to call not implemented method ChannelService.Dissolve: {1}", + GetCallerInfo(), request.ToString()); + return BattlenetRpcErrorCode.RpcNotImplemented; + } + } + + class ChannelListener : ServiceBase + { + public ChannelListener(Session session, uint serviceHash) : base(session, serviceHash) { } + + public override void CallServerMethod(uint token, uint methodId, CodedInputStream stream) + { + switch (methodId) + { + case 1: + { + JoinNotification request = new JoinNotification(); + request.MergeFrom(stream); + + + BattlenetRpcErrorCode status = HandleOnJoin(request); + Log.outDebug(LogFilter.ServiceProtobuf, "{0} Client called server method ChannelListener.OnJoin(bgs.protocol.channel.v1.JoinNotification: {1}) status: {2}.", + GetCallerInfo(), request.ToString(), status); + if (status != 0) + SendResponse(token, status); + break; + } + case 2: + { + MemberAddedNotification request = new MemberAddedNotification(); + request.MergeFrom(stream); + + + BattlenetRpcErrorCode status = HandleOnMemberAdded(request); + Log.outDebug(LogFilter.ServiceProtobuf, "{0} Client called server method ChannelListener.OnMemberAdded(bgs.protocol.channel.v1.MemberAddedNotification: {1}) status: {2}.", + GetCallerInfo(), request.ToString(), status); + if (status != 0) + SendResponse(token, status); + break; + } + case 3: + { + LeaveNotification request = new LeaveNotification(); + request.MergeFrom(stream); + + + BattlenetRpcErrorCode status = HandleOnLeave(request); + Log.outDebug(LogFilter.ServiceProtobuf, "{0} Client called server method ChannelListener.OnLeave(bgs.protocol.channel.v1.LeaveNotification: {1}) status: {2}.", + GetCallerInfo(), request.ToString(), status); + if (status != 0) + SendResponse(token, status); + break; + } + case 4: + { + MemberRemovedNotification request = new MemberRemovedNotification(); + request.MergeFrom(stream); + + + BattlenetRpcErrorCode status = HandleOnMemberRemoved(request); + Log.outDebug(LogFilter.ServiceProtobuf, "{0} Client called server method ChannelListener.OnMemberRemoved(bgs.protocol.channel.v1.MemberRemovedNotification: {1}) status: {2}.", + GetCallerInfo(), request.ToString(), status); + if (status != 0) + SendResponse(token, status); + break; + } + case 5: + { + SendMessageNotification request = new SendMessageNotification(); + request.MergeFrom(stream); + + + BattlenetRpcErrorCode status = HandleOnSendMessage(request); + Log.outDebug(LogFilter.ServiceProtobuf, "{0} Client called server method ChannelListener.OnSendMessage(bgs.protocol.channel.v1.SendMessageNotification: {1}) status: {2}.", + GetCallerInfo(), request.ToString(), status); + if (status != 0) + SendResponse(token, status); + break; + } + case 6: + { + UpdateChannelStateNotification request = new UpdateChannelStateNotification(); + request.MergeFrom(stream); + + + BattlenetRpcErrorCode status = HandleOnUpdateChannelState(request); + Log.outDebug(LogFilter.ServiceProtobuf, "{0} Client called server method ChannelListener.OnUpdateChannelState(bgs.protocol.channel.v1.UpdateChannelStateNotification: {1}) status: {2}.", + GetCallerInfo(), request.ToString(), status); + if (status != 0) + SendResponse(token, status); + break; + } + case 7: + { + UpdateMemberStateNotification request = new UpdateMemberStateNotification(); + request.MergeFrom(stream); + + + BattlenetRpcErrorCode status = HandleOnUpdateMemberState(request); + Log.outDebug(LogFilter.ServiceProtobuf, "{0} Client called server method ChannelListener.OnUpdateMemberState(bgs.protocol.channel.v1.UpdateMemberStateNotification: {1}) status: {2}.", + GetCallerInfo(), request.ToString(), status); + if (status != 0) + SendResponse(token, status); + break; + } + default: + Log.outError(LogFilter.ServiceProtobuf, "Bad method id {0}.", methodId); + SendResponse(token, BattlenetRpcErrorCode.RpcInvalidMethod); + break; + } + } + + BattlenetRpcErrorCode HandleOnJoin(JoinNotification request) + { + Log.outError(LogFilter.ServiceProtobuf, "{0} Client tried to call not implemented method ChannelListener.OnJoin: {1}", + GetCallerInfo(), request.ToString()); + return BattlenetRpcErrorCode.RpcNotImplemented; + } + + BattlenetRpcErrorCode HandleOnMemberAdded(MemberAddedNotification request) + { + Log.outError(LogFilter.ServiceProtobuf, "{0} Client tried to call not implemented method ChannelListener.OnMemberAdded: {1}", + GetCallerInfo(), request.ToString()); + return BattlenetRpcErrorCode.RpcNotImplemented; + } + + BattlenetRpcErrorCode HandleOnLeave(LeaveNotification request) + { + Log.outError(LogFilter.ServiceProtobuf, "{0} Client tried to call not implemented method ChannelListener.OnLeave: {1}", + GetCallerInfo(), request.ToString()); + return BattlenetRpcErrorCode.RpcNotImplemented; + } + + BattlenetRpcErrorCode HandleOnMemberRemoved(MemberRemovedNotification request) + { + Log.outError(LogFilter.ServiceProtobuf, "{0} Client tried to call not implemented method ChannelListener.OnMemberRemoved: {1}", + GetCallerInfo(), request.ToString()); + return BattlenetRpcErrorCode.RpcNotImplemented; + } + + BattlenetRpcErrorCode HandleOnSendMessage(SendMessageNotification request) + { + Log.outError(LogFilter.ServiceProtobuf, "{0} Client tried to call not implemented method ChannelListener.OnSendMessage: {1}", + GetCallerInfo(), request.ToString()); + return BattlenetRpcErrorCode.RpcNotImplemented; + } + + BattlenetRpcErrorCode HandleOnUpdateChannelState(UpdateChannelStateNotification request) + { + Log.outError(LogFilter.ServiceProtobuf, "{0} Client tried to call not implemented method ChannelListener.OnUpdateChannelState: {1}", + GetCallerInfo(), request.ToString()); + return BattlenetRpcErrorCode.RpcNotImplemented; + } + + BattlenetRpcErrorCode HandleOnUpdateMemberState(UpdateMemberStateNotification request) + { + Log.outError(LogFilter.ServiceProtobuf, "{0} Client tried to call not implemented method ChannelListener.OnUpdateMemberState: {1}", + GetCallerInfo(), request.ToString()); + return BattlenetRpcErrorCode.RpcNotImplemented; + } + } +} diff --git a/BNetServer/Services/ConnectionService.cs b/BNetServer/Services/ConnectionService.cs new file mode 100644 index 000000000..f854e7d40 --- /dev/null +++ b/BNetServer/Services/ConnectionService.cs @@ -0,0 +1,187 @@ +/* + * Copyright (C) 2012-2017 CypherCore + * + * 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, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +using Bgs.Protocol; +using Bgs.Protocol.Connection.V1; +using BNetServer.Services; +using Framework.Constants; +using Google.Protobuf; +using System.Diagnostics; + +namespace BNetServer.Networking +{ + class ConnectionService : ServiceBase + { + public ConnectionService(Session session, uint serviceHash) : base(session, serviceHash) { } + + public override void CallServerMethod(uint token, uint methodId, CodedInputStream stream) + { + switch (methodId) + { + case 1: + { + ConnectRequest request = ConnectRequest.Parser.ParseFrom(stream); + ConnectResponse response = new ConnectResponse(); + + BattlenetRpcErrorCode status = HandleConnect(request, response); + Log.outDebug(LogFilter.ServiceProtobuf, "{0} Client called server method ConnectionService.Connect(bgs.protocol.connection.v1.ConnectRequest: {1}) returned bgs.protocol.connection.v1.ConnectResponse: {2} status: {3}.", + GetCallerInfo(), request.ToString(), response.ToString(), status); + if (status == 0) + SendResponse(token, response); + else + SendResponse(token, status); + break; + } + case 2: + { + BindRequest request = new BindRequest(); + request.MergeFrom(stream); + + BindResponse response = new BindResponse(); + BattlenetRpcErrorCode status = HandleBind(request, response); + Log.outDebug(LogFilter.ServiceProtobuf, "{0} Client called server method ConnectionService.Bind(bgs.protocol.connection.v1.BindRequest: {1}) returned bgs.protocol.connection.v1.BindResponse: {2} status: {3}.", + GetCallerInfo(), request.ToString(), response.ToString(), status); + if (status == 0) + SendResponse(token, response); + else + SendResponse(token, status); + break; + } + case 3: + { + EchoRequest request = new EchoRequest(); + request.MergeFrom(stream); + + EchoResponse response = new EchoResponse(); + BattlenetRpcErrorCode status = HandleEcho(request, response); + Log.outDebug(LogFilter.ServiceProtobuf, "{0} Client called server method ConnectionService.Echo(bgs.protocol.connection.v1.EchoRequest: {1}) returned bgs.protocol.connection.v1.EchoResponse: {2} status: {3}.", + GetCallerInfo(), request.ToString(), response.ToString(), status); + if (status == 0) + SendResponse(token, response); + else + SendResponse(token, status); + break; + } + case 4: + { + DisconnectNotification request = DisconnectNotification.Parser.ParseFrom(stream); + + BattlenetRpcErrorCode status = HandleForceDisconnect(request); + Log.outDebug(LogFilter.ServiceProtobuf, "{0} Client called server method ConnectionService.ForceDisconnect(bgs.protocol.connection.v1.DisconnectNotification: {1}) status: {2}.", + GetCallerInfo(), request.ToString(), status); + if (status != 0) + SendResponse(token, status); + break; + } + case 5: + { + NoData request = NoData.Parser.ParseFrom(stream); + + BattlenetRpcErrorCode status = HandleKeepAlive(request); + Log.outDebug(LogFilter.ServiceProtobuf, "{0} Client called server method ConnectionService.KeepAlive(bgs.protocol.NoData: {1}) status: {2}.", + GetCallerInfo(), request.ToString(), status); + if (status != 0) + SendResponse(token, status); + break; + } + case 6: + { + EncryptRequest request = EncryptRequest.Parser.ParseFrom(stream); + + NoData response = new NoData(); + BattlenetRpcErrorCode status = HandleEncrypt(request, response); + Log.outDebug(LogFilter.ServiceProtobuf, "{0} Client called server method ConnectionService.Encrypt(bgs.protocol.connection.v1.EncryptRequest: {1}) returned bgs.protocol.NoData: {2} status: {3}.", + GetCallerInfo(), request.ToString(), response.ToString(), status); + if (status == 0) + SendResponse(token, response); + else + SendResponse(token, status); + break; + } + case 7: + { + DisconnectRequest request = DisconnectRequest.Parser.ParseFrom(stream); + + BattlenetRpcErrorCode status = HandleRequestDisconnect(request); + Log.outDebug(LogFilter.ServiceProtobuf, "{0} Client called server method ConnectionService.RequestDisconnect(bgs.protocol.connection.v1.DisconnectRequest: {1}) status: {2}.", + GetCallerInfo(), request.ToString(), status); + if (status != 0) + SendResponse(token, status); + break; + } + default: + Log.outError(LogFilter.ServiceProtobuf, "Bad method id {0}.", methodId); + SendResponse(token, BattlenetRpcErrorCode.RpcInvalidMethod); + break; + } + } + + BattlenetRpcErrorCode HandleConnect(ConnectRequest request, ConnectResponse response) + { + if (request.ClientId != null) + response.ClientId.MergeFrom(request.ClientId); + + response.ServerId = new ProcessId(); + response.ServerId.Label = (uint)Process.GetCurrentProcess().Id; + response.ServerId.Epoch = (uint)Time.UnixTime; + response.ServerTime = (ulong)Time.UnixTimeMilliseconds; + + response.UseBindlessRpc = request.UseBindlessRpc; + + return BattlenetRpcErrorCode.Ok; + } + + BattlenetRpcErrorCode HandleBind(BindRequest request, BindResponse response) + { + Log.outError(LogFilter.ServiceProtobuf, "{0} Client tried to call not implemented method ConnectionService.Bind: {1}", GetCallerInfo(), request.ToString()); + return BattlenetRpcErrorCode.RpcNotImplemented; + } + + BattlenetRpcErrorCode HandleEcho(EchoRequest request, EchoResponse response) + { + Log.outError(LogFilter.ServiceProtobuf, "{0} Client tried to call not implemented method ConnectionService.Echo: {1}", GetCallerInfo(), request.ToString()); + return BattlenetRpcErrorCode.RpcNotImplemented; + } + + BattlenetRpcErrorCode HandleForceDisconnect(DisconnectNotification request) + { + Log.outError(LogFilter.ServiceProtobuf, "{0} Client tried to call not implemented method ConnectionService.ForceDisconnect: {1}", GetCallerInfo(), request.ToString()); + return BattlenetRpcErrorCode.RpcNotImplemented; + } + + BattlenetRpcErrorCode HandleKeepAlive(NoData request) + { + return BattlenetRpcErrorCode.Ok; + } + + BattlenetRpcErrorCode HandleEncrypt(EncryptRequest request, NoData response) + { + Log.outError(LogFilter.ServiceProtobuf, "{0} Client tried to call not implemented method ConnectionService.Encrypt: {1}", GetCallerInfo(), request.ToString()); + return BattlenetRpcErrorCode.RpcNotImplemented; + } + + BattlenetRpcErrorCode HandleRequestDisconnect(DisconnectRequest request) + { + var disconnectNotification = new DisconnectNotification(); + disconnectNotification.ErrorCode = request.ErrorCode; + SendRequest(4, disconnectNotification); + + _session.CloseSocket(); + return BattlenetRpcErrorCode.Ok; + } + } +} diff --git a/BNetServer/Services/FriendService.cs b/BNetServer/Services/FriendService.cs new file mode 100644 index 000000000..09dc5e868 --- /dev/null +++ b/BNetServer/Services/FriendService.cs @@ -0,0 +1,520 @@ +/* + * Copyright (C) 2012-2017 CypherCore + * + * 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, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +using Bgs.Protocol; +using Bgs.Protocol.Friends.V1; +using BNetServer.Services; +using Framework.Constants; +using Google.Protobuf; + +namespace BNetServer.Networking +{ + class FriendsService : ServiceBase + { + public FriendsService(Session session, uint serviceHash) : base(session, serviceHash) { } + + public override void CallServerMethod(uint token, uint methodId, CodedInputStream stream) + { + switch (methodId) + { + case 1: + { + SubscribeRequest request = new SubscribeRequest(); + request.MergeFrom(stream); + + + SubscribeResponse response = new SubscribeResponse(); + BattlenetRpcErrorCode status = HandleSubscribe(request, response); + Log.outDebug(LogFilter.ServiceProtobuf, "{0} Client called server method FriendsService.Subscribe(bgs.protocol.friends.v1.SubscribeRequest: {1}) returned bgs.protocol.friends.v1.SubscribeResponse: {2} status: {3}.", + GetCallerInfo(), request.ToString(), response.ToString(), status); + if (status == 0) + SendResponse(token, response); + else + SendResponse(token, status); + break; + } + case 2: + { + SendInvitationRequest request = new SendInvitationRequest(); + request.MergeFrom(stream); + + + NoData response = new NoData(); + BattlenetRpcErrorCode status = HandleSendInvitation(request, response); + Log.outDebug(LogFilter.ServiceProtobuf, "{0} Client called server method FriendsService.SendInvitation(bgs.protocol.SendInvitationRequest: {1}) returned bgs.protocol.NoData: {2} status: {3}.", + GetCallerInfo(), request.ToString(), response.ToString(), status); + if (status == 0) + SendResponse(token, response); + else + SendResponse(token, status); + break; + } + case 3: + { + GenericInvitationRequest request = new GenericInvitationRequest(); + request.MergeFrom(stream); + + + NoData response = new NoData(); + BattlenetRpcErrorCode status = HandleAcceptInvitation(request, response); + Log.outDebug(LogFilter.ServiceProtobuf, "{0} Client called server method FriendsService.AcceptInvitation(bgs.protocol.GenericInvitationRequest: {1}) returned bgs.protocol.NoData: {2} status: {3}.", + GetCallerInfo(), request.ToString(), response.ToString(), status); + if (status == 0) + SendResponse(token, response); + else + SendResponse(token, status); + break; + } + case 4: + { + GenericInvitationRequest request = new GenericInvitationRequest(); + request.MergeFrom(stream); + + + NoData response = new NoData(); + BattlenetRpcErrorCode status = HandleRevokeInvitation(request, response); + Log.outDebug(LogFilter.ServiceProtobuf, "{0} Client called server method FriendsService.RevokeInvitation(bgs.protocol.GenericInvitationRequest: {1}) returned bgs.protocol.NoData: {2} status: {3}.", + GetCallerInfo(), request.ToString(), response.ToString(), status); + if (status == 0) + SendResponse(token, response); + else + SendResponse(token, status); + break; + } + case 5: + { + GenericInvitationRequest request = new GenericInvitationRequest(); + request.MergeFrom(stream); + + + NoData response = new NoData(); + BattlenetRpcErrorCode status = HandleDeclineInvitation(request, response); + Log.outDebug(LogFilter.ServiceProtobuf, "{0} Client called server method FriendsService.DeclineInvitation(bgs.protocol.GenericInvitationRequest: {1}) returned bgs.protocol.NoData: {2} status: {3}.", + GetCallerInfo(), request.ToString(), response.ToString(), status); + if (status == 0) + SendResponse(token, response); + else + SendResponse(token, status); + break; + } + case 6: + { + GenericInvitationRequest request = new GenericInvitationRequest(); + request.MergeFrom(stream); + + + NoData response = new NoData(); + BattlenetRpcErrorCode status = HandleIgnoreInvitation(request, response); + Log.outDebug(LogFilter.ServiceProtobuf, "{0} Client called server method FriendsService.IgnoreInvitation(bgs.protocol.GenericInvitationRequest: {1}) returned bgs.protocol.NoData: {2} status: {3}.", + GetCallerInfo(), request.ToString(), response.ToString(), status); + if (status == 0) + SendResponse(token, response); + else + SendResponse(token, status); + break; + } + case 7: + { + AssignRoleRequest request = new AssignRoleRequest(); + request.MergeFrom(stream); + + + NoData response = new NoData(); + BattlenetRpcErrorCode status = HandleAssignRole(request, response); + Log.outDebug(LogFilter.ServiceProtobuf, "{0} Client called server method FriendsService.AssignRole(bgs.protocol.friends.v1.AssignRoleRequest: {1}) returned bgs.protocol.NoData: {2} status: {3}.", + GetCallerInfo(), request.ToString(), response.ToString(), status); + if (status == 0) + SendResponse(token, response); + else + SendResponse(token, status); + break; + } + case 8: + { + GenericFriendRequest request = new GenericFriendRequest(); + request.MergeFrom(stream); + + + GenericFriendResponse response = new GenericFriendResponse(); + BattlenetRpcErrorCode status = HandleRemoveFriend(request, response); + Log.outDebug(LogFilter.ServiceProtobuf, "{0} Client called server method FriendsService.RemoveFriend(bgs.protocol.friends.v1.GenericFriendRequest: {1}) returned bgs.protocol.friends.v1.GenericFriendResponse: {2} status: {3}.", + GetCallerInfo(), request.ToString(), response.ToString(), status); + if (status == 0) + SendResponse(token, response); + else + SendResponse(token, status); + break; + } + case 9: + { + ViewFriendsRequest request = new ViewFriendsRequest(); + request.MergeFrom(stream); + + + ViewFriendsResponse response = new ViewFriendsResponse(); + BattlenetRpcErrorCode status = HandleViewFriends(request, response); + Log.outDebug(LogFilter.ServiceProtobuf, "{0} Client called server method FriendsService.ViewFriends(bgs.protocol.friends.v1.ViewFriendsRequest: {1}) returned bgs.protocol.friends.v1.ViewFriendsResponse: {2} status: {3}.", + GetCallerInfo(), request.ToString(), response.ToString(), status); + if (status == 0) + SendResponse(token, response); + else + SendResponse(token, status); + break; + } + case 10: + { + UpdateFriendStateRequest request = new UpdateFriendStateRequest(); + request.MergeFrom(stream); + + + NoData response = new NoData(); + BattlenetRpcErrorCode status = HandleUpdateFriendState(request, response); + Log.outDebug(LogFilter.ServiceProtobuf, "{0} Client called server method FriendsService.UpdateFriendState(bgs.protocol.friends.v1.UpdateFriendStateRequest: {1}) returned bgs.protocol.NoData: {2} status: {3}.", + GetCallerInfo(), request.ToString(), response.ToString(), status); + if (status == 0) + SendResponse(token, response); + else + SendResponse(token, status); + break; + } + case 11: + { + UnsubscribeRequest request = new UnsubscribeRequest(); + request.MergeFrom(stream); + + + NoData response = new NoData(); + BattlenetRpcErrorCode status = HandleUnsubscribe(request, response); + Log.outDebug(LogFilter.ServiceProtobuf, "{0} Client called server method FriendsService.Unsubscribe(bgs.protocol.friends.v1.UnsubscribeRequest: {1}) returned bgs.protocol.NoData: {2} status: {3}.", + GetCallerInfo(), request.ToString(), response.ToString(), status); + if (status == 0) + SendResponse(token, response); + else + SendResponse(token, status); + break; + } + case 12: + { + GenericFriendRequest request = new GenericFriendRequest(); + request.MergeFrom(stream); + + + NoData response = new NoData(); + BattlenetRpcErrorCode status = HandleRevokeAllInvitations(request, response); + Log.outDebug(LogFilter.ServiceProtobuf, "{0} Client called server method FriendsService.RevokeAllInvitations(bgs.protocol.friends.v1.GenericFriendRequest: {1}) returned bgs.protocol.NoData: {2} status: {3}.", + GetCallerInfo(), request.ToString(), response.ToString(), status); + if (status == 0) + SendResponse(token, response); + else + SendResponse(token, status); + break; + } + /*case 13: + { + GetFriendListRequest request = new GetFriendListRequest(); + request.MergeFrom(stream); + + + GetFriendListResponse response = new GetFriendListResponse(); + BattlenetRpcErrorCode status = HandleGetFriendList(request, response); + Log.outDebug(LogFilter.ServiceProtobuf, "{0} Client called server method FriendsService.GetFriendList(bgs.protocol.friends.v1.GetFriendListRequest: {1}) returned bgs.protocol.friends.v1.GetFriendListResponse: {2} status: {3}.", + GetCallerInfo(), request.ToString(), response.ToString(), status); + if (status == 0) + SendResponse(token, response); + else + SendResponse(token, status); + break; + } + case 14: + { + CreateFriendshipRequest request = new CreateFriendshipRequest(); + request.MergeFrom(stream); + + + NoData response = new NoData(); + BattlenetRpcErrorCode status = HandleCreateFriendship(request, response); + Log.outDebug(LogFilter.ServiceProtobuf, "{0} Client called server method FriendsService.CreateFriendship(bgs.protocol.friends.v1.CreateFriendshipRequest: {1}) returned bgs.protocol.NoData: {2} status: {3}.", + GetCallerInfo(), request.ToString(), response.ToString(), status); + if (status == 0) + SendResponse(token, response); + else + SendResponse(token, status); + break; + }*/ + default: + Log.outError(LogFilter.ServiceProtobuf, "Bad method id {0}.", methodId); + SendResponse(token, BattlenetRpcErrorCode.RpcInvalidMethod); + break; + } + } + + BattlenetRpcErrorCode HandleSubscribe(SubscribeRequest request, SubscribeResponse response) + { + Log.outError(LogFilter.ServiceProtobuf, "{0} Client tried to call not implemented method FriendsService.Subscribe: {1}", + GetCallerInfo(), request.ToString()); + return BattlenetRpcErrorCode.RpcNotImplemented; + } + + BattlenetRpcErrorCode HandleSendInvitation(SendInvitationRequest request, NoData response) + { + Log.outError(LogFilter.ServiceProtobuf, "{0} Client tried to call not implemented method FriendsService.SendInvitation: {1}", + GetCallerInfo(), request.ToString()); + return BattlenetRpcErrorCode.RpcNotImplemented; + } + + BattlenetRpcErrorCode HandleAcceptInvitation(GenericInvitationRequest request, NoData response) + { + Log.outError(LogFilter.ServiceProtobuf, "{0} Client tried to call not implemented method FriendsService.AcceptInvitation: {1}", + GetCallerInfo(), request.ToString()); + return BattlenetRpcErrorCode.RpcNotImplemented; + } + + BattlenetRpcErrorCode HandleRevokeInvitation(GenericInvitationRequest request, NoData response) + { + Log.outError(LogFilter.ServiceProtobuf, "{0} Client tried to call not implemented method FriendsService.RevokeInvitation: {1}", + GetCallerInfo(), request.ToString()); + return BattlenetRpcErrorCode.RpcNotImplemented; + } + + BattlenetRpcErrorCode HandleDeclineInvitation(GenericInvitationRequest request, NoData response) + { + Log.outError(LogFilter.ServiceProtobuf, "{0} Client tried to call not implemented method FriendsService.DeclineInvitation: {1}", + GetCallerInfo(), request.ToString()); + return BattlenetRpcErrorCode.RpcNotImplemented; + } + + BattlenetRpcErrorCode HandleIgnoreInvitation(GenericInvitationRequest request, NoData response) + { + Log.outError(LogFilter.ServiceProtobuf, "{0} Client tried to call not implemented method FriendsService.IgnoreInvitation: {1}", + GetCallerInfo(), request.ToString()); + return BattlenetRpcErrorCode.RpcNotImplemented; + } + + BattlenetRpcErrorCode HandleAssignRole(AssignRoleRequest request, NoData response) + { + Log.outError(LogFilter.ServiceProtobuf, "{0} Client tried to call not implemented method FriendsService.AssignRole: {1}", + GetCallerInfo(), request.ToString()); + return BattlenetRpcErrorCode.RpcNotImplemented; + } + + BattlenetRpcErrorCode HandleRemoveFriend(GenericFriendRequest request, GenericFriendResponse response) + { + Log.outError(LogFilter.ServiceProtobuf, "{0} Client tried to call not implemented method FriendsService.RemoveFriend: {1}", + GetCallerInfo(), request.ToString()); + return BattlenetRpcErrorCode.RpcNotImplemented; + } + + BattlenetRpcErrorCode HandleViewFriends(ViewFriendsRequest request, ViewFriendsResponse response) + { + Log.outError(LogFilter.ServiceProtobuf, "{0} Client tried to call not implemented method FriendsService.ViewFriends: {1}", + GetCallerInfo(), request.ToString()); + return BattlenetRpcErrorCode.RpcNotImplemented; + } + + BattlenetRpcErrorCode HandleUpdateFriendState(UpdateFriendStateRequest request, NoData response) + { + Log.outError(LogFilter.ServiceProtobuf, "{0} Client tried to call not implemented method FriendsService.UpdateFriendState: {1}", + GetCallerInfo(), request.ToString()); + return BattlenetRpcErrorCode.RpcNotImplemented; + } + + BattlenetRpcErrorCode HandleUnsubscribe(UnsubscribeRequest request, NoData response) + { + Log.outError(LogFilter.ServiceProtobuf, "{0} Client tried to call not implemented method FriendsService.Unsubscribe: {1}", + GetCallerInfo(), request.ToString()); + return BattlenetRpcErrorCode.RpcNotImplemented; + } + + BattlenetRpcErrorCode HandleRevokeAllInvitations(GenericFriendRequest request, NoData response) + { + Log.outError(LogFilter.ServiceProtobuf, "{0} Client tried to call not implemented method FriendsService.RevokeAllInvitations: {1}", + GetCallerInfo(), request.ToString()); + return BattlenetRpcErrorCode.RpcNotImplemented; + } + + /*BattlenetRpcErrorCode HandleGetFriendList(GetFriendListRequest request, GetFriendListResponse response) + { + Log.outError(LogFilter.ServiceProtobuf, "{0} Client tried to call not implemented method FriendsService.GetFriendList: {1}", + GetCallerInfo(), request.ToString()); + return BattlenetRpcErrorCode.RpcNotImplemented; + } + + BattlenetRpcErrorCode HandleCreateFriendship(CreateFriendshipRequest request, NoData response) + { + Log.outError(LogFilter.ServiceProtobuf, "{0} Client tried to call not implemented method FriendsService.CreateFriendship: {1}", + GetCallerInfo(), request.ToString()); + return BattlenetRpcErrorCode.RpcNotImplemented; + } + */ + } + + class FriendsListener : ServiceBase + { + public FriendsListener(Session session, uint serviceHash) : base(session, serviceHash) { } + + public override void CallServerMethod(uint token, uint methodId, CodedInputStream stream) + { + switch (methodId) + { + case 1: + { + FriendNotification request = new FriendNotification(); + request.MergeFrom(stream); + + + BattlenetRpcErrorCode status = HandleOnFriendAdded(request); + Log.outDebug(LogFilter.ServiceProtobuf, "{0} Client called server method FriendsListener.OnFriendAdded(bgs.protocol.friends.v1.FriendNotification: {1}) status: {2}.", + GetCallerInfo(), request.ToString(), status); + if (status != 0) + SendResponse(token, status); + break; + } + case 2: + { + FriendNotification request = new FriendNotification(); + request.MergeFrom(stream); + + + BattlenetRpcErrorCode status = HandleOnFriendRemoved(request); + Log.outDebug(LogFilter.ServiceProtobuf, "{0} Client called server method FriendsListener.OnFriendRemoved(bgs.protocol.friends.v1.FriendNotification: {1}) status: {2}.", + GetCallerInfo(), request.ToString(), status); + if (status != 0) + SendResponse(token, status); + break; + } + case 3: + { + InvitationNotification request = new InvitationNotification(); + request.MergeFrom(stream); + + + BattlenetRpcErrorCode status = HandleOnReceivedInvitationAdded(request); + Log.outDebug(LogFilter.ServiceProtobuf, "{0} Client called server method FriendsListener.OnReceivedInvitationAdded(bgs.protocol.friends.v1.InvitationNotification: {1}) status: {2}.", + GetCallerInfo(), request.ToString(), status); + if (status != 0) + SendResponse(token, status); + break; + } + case 4: + { + InvitationNotification request = new InvitationNotification(); + request.MergeFrom(stream); + + + BattlenetRpcErrorCode status = HandleOnReceivedInvitationRemoved(request); + Log.outDebug(LogFilter.ServiceProtobuf, "{0} Client called server method FriendsListener.OnReceivedInvitationRemoved(bgs.protocol.friends.v1.InvitationNotification: {1}) status: {2}.", + GetCallerInfo(), request.ToString(), status); + if (status != 0) + SendResponse(token, status); + break; + } + case 5: + { + InvitationNotification request = new InvitationNotification(); + request.MergeFrom(stream); + + + BattlenetRpcErrorCode status = HandleOnSentInvitationAdded(request); + Log.outDebug(LogFilter.ServiceProtobuf, "{0} Client called server method FriendsListener.OnSentInvitationAdded(bgs.protocol.friends.v1.InvitationNotification: {1}) status: {2}.", + GetCallerInfo(), request.ToString(), status); + if (status != 0) + SendResponse(token, status); + break; + } + case 6: + { + InvitationNotification request = new InvitationNotification(); + request.MergeFrom(stream); + + + BattlenetRpcErrorCode status = HandleOnSentInvitationRemoved(request); + Log.outDebug(LogFilter.ServiceProtobuf, "{0} Client called server method FriendsListener.OnSentInvitationRemoved(bgs.protocol.friends.v1.InvitationNotification: {1}) status: {2}.", + GetCallerInfo(), request.ToString(), status); + if (status != 0) + SendResponse(token, status); + break; + } + case 7: + { + UpdateFriendStateNotification request = new UpdateFriendStateNotification(); + request.MergeFrom(stream); + + + BattlenetRpcErrorCode status = HandleOnUpdateFriendState(request); + Log.outDebug(LogFilter.ServiceProtobuf, "{0} Client called server method FriendsListener.OnUpdateFriendState(bgs.protocol.friends.v1.UpdateFriendStateNotification: {1}) status: {2}.", + GetCallerInfo(), request.ToString(), status); + if (status != 0) + SendResponse(token, status); + break; + } + default: + Log.outError(LogFilter.ServiceProtobuf, "Bad method id {0}.", methodId); + SendResponse(token, BattlenetRpcErrorCode.RpcInvalidMethod); + break; + } + } + + BattlenetRpcErrorCode HandleOnFriendAdded(FriendNotification request) + { + Log.outError(LogFilter.ServiceProtobuf, "{0} Client tried to call not implemented method FriendsListener.OnFriendAdded: {1}", + GetCallerInfo(), request.ToString()); + return BattlenetRpcErrorCode.RpcNotImplemented; + } + + BattlenetRpcErrorCode HandleOnFriendRemoved(FriendNotification request) + { + Log.outError(LogFilter.ServiceProtobuf, "{0} Client tried to call not implemented method FriendsListener.OnFriendRemoved: {1}", + GetCallerInfo(), request.ToString()); + return BattlenetRpcErrorCode.RpcNotImplemented; + } + + BattlenetRpcErrorCode HandleOnReceivedInvitationAdded(InvitationNotification request) + { + Log.outError(LogFilter.ServiceProtobuf, "{0} Client tried to call not implemented method FriendsListener.OnReceivedInvitationAdded: {1}", + GetCallerInfo(), request.ToString()); + return BattlenetRpcErrorCode.RpcNotImplemented; + } + + BattlenetRpcErrorCode HandleOnReceivedInvitationRemoved(InvitationNotification request) + { + Log.outError(LogFilter.ServiceProtobuf, "{0} Client tried to call not implemented method FriendsListener.OnReceivedInvitationRemoved: {1}", + GetCallerInfo(), request.ToString()); + return BattlenetRpcErrorCode.RpcNotImplemented; + } + + BattlenetRpcErrorCode HandleOnSentInvitationAdded(InvitationNotification request) + { + Log.outError(LogFilter.ServiceProtobuf, "{0} Client tried to call not implemented method FriendsListener.OnSentInvitationAdded: {1}", + GetCallerInfo(), request.ToString()); + return BattlenetRpcErrorCode.RpcNotImplemented; + } + + BattlenetRpcErrorCode HandleOnSentInvitationRemoved(InvitationNotification request) + { + Log.outError(LogFilter.ServiceProtobuf, "{0} Client tried to call not implemented method FriendsListener.OnSentInvitationRemoved: {1}", + GetCallerInfo(), request.ToString()); + return BattlenetRpcErrorCode.RpcNotImplemented; + } + + BattlenetRpcErrorCode HandleOnUpdateFriendState(UpdateFriendStateNotification request) + { + Log.outError(LogFilter.ServiceProtobuf, "{0} Client tried to call not implemented method FriendsListener.OnUpdateFriendState: {1}", + GetCallerInfo(), request.ToString()); + return BattlenetRpcErrorCode.RpcNotImplemented; + } + } +} diff --git a/BNetServer/Services/GameUtilitiesService.cs b/BNetServer/Services/GameUtilitiesService.cs new file mode 100644 index 000000000..60b0e78e4 --- /dev/null +++ b/BNetServer/Services/GameUtilitiesService.cs @@ -0,0 +1,215 @@ +/* + * Copyright (C) 2012-2017 CypherCore + * + * 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, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +using Bgs.Protocol; +using Bgs.Protocol.GameUtilities.V1; +using BNetServer.Services; +using Framework.Constants; +using Google.Protobuf; + +namespace BNetServer.Networking +{ + class GameUtilitiesService : ServiceBase + { + public GameUtilitiesService(Session session, uint serviceHash) : base(session, serviceHash) { } + + public override void CallServerMethod(uint token, uint methodId, CodedInputStream stream) + { + switch (methodId) + { + case 1: + { + ClientRequest request = new ClientRequest(); + request.MergeFrom(stream); + + + ClientResponse response = new ClientResponse(); + BattlenetRpcErrorCode status = HandleProcessClientRequest(request, response); + Log.outDebug(LogFilter.ServiceProtobuf, "{0} Client called server method GameUtilitiesService.ProcessClientRequest(bgs.protocol.game_utilities.v1.ClientRequest: {1}) returned bgs.protocol.game_utilities.v1.ClientResponse: {2} status: {3}.", + GetCallerInfo(), request.ToString(), response.ToString(), status); + if (status == 0) + SendResponse(token, response); + else + SendResponse(token, status); + break; + } + case 2: + { + PresenceChannelCreatedRequest request = new PresenceChannelCreatedRequest(); + request.MergeFrom(stream); + + + NoData response = new NoData(); + BattlenetRpcErrorCode status = HandlePresenceChannelCreated(request, response); + Log.outDebug(LogFilter.ServiceProtobuf, "{0} Client called server method GameUtilitiesService.PresenceChannelCreated(bgs.protocol.game_utilities.v1.PresenceChannelCreatedRequest: {1}) returned bgs.protocol.NoData: {2} status: {3}.", + GetCallerInfo(), request.ToString(), response.ToString(), status); + if (status == 0) + SendResponse(token, response); + else + SendResponse(token, status); + break; + } + case 3: + { + GetPlayerVariablesRequest request = new GetPlayerVariablesRequest(); + request.MergeFrom(stream); + + + GetPlayerVariablesResponse response = new GetPlayerVariablesResponse(); + BattlenetRpcErrorCode status = HandleGetPlayerVariables(request, response); + Log.outDebug(LogFilter.ServiceProtobuf, "{0} Client called server method GameUtilitiesService.GetPlayerVariables(bgs.protocol.game_utilities.v1.GetPlayerVariablesRequest: {1}) returned bgs.protocol.game_utilities.v1.GetPlayerVariablesResponse: {2} status: {3}.", + GetCallerInfo(), request.ToString(), response.ToString(), status); + if (status == 0) + SendResponse(token, response); + else + SendResponse(token, status); + break; + } + case 6: + { + ServerRequest request = new ServerRequest(); + request.MergeFrom(stream); + + + ServerResponse response = new ServerResponse(); + BattlenetRpcErrorCode status = HandleProcessServerRequest(request, response); + Log.outDebug(LogFilter.ServiceProtobuf, "{0} Client called server method GameUtilitiesService.ProcessServerRequest(bgs.protocol.game_utilities.v1.ServerRequest: {1}) returned bgs.protocol.game_utilities.v1.ServerResponse: {2} status: {3}.", + GetCallerInfo(), request.ToString(), response.ToString(), status); + if (status == 0) + SendResponse(token, response); + else + SendResponse(token, status); + break; + } + case 7: + { + GameAccountOnlineNotification request = new GameAccountOnlineNotification(); + request.MergeFrom(stream); + + + BattlenetRpcErrorCode status = HandleOnGameAccountOnline(request); + Log.outDebug(LogFilter.ServiceProtobuf, "{0} Client called server method GameUtilitiesService.OnGameAccountOnline(bgs.protocol.game_utilities.v1.GameAccountOnlineNotification: {1}) status: {2}.", + GetCallerInfo(), request.ToString(), status); + if (status != 0) + SendResponse(token, status); + break; + } + case 8: + { + GameAccountOfflineNotification request = new GameAccountOfflineNotification(); + request.MergeFrom(stream); + + + BattlenetRpcErrorCode status = HandleOnGameAccountOffline(request); + Log.outDebug(LogFilter.ServiceProtobuf, "{0} Client called server method GameUtilitiesService.OnGameAccountOffline(bgs.protocol.game_utilities.v1.GameAccountOfflineNotification: {1}) status: {2}.", + GetCallerInfo(), request.ToString(), status); + if (status != 0) + SendResponse(token, status); + break; + } + case 9: + { + GetAchievementsFileRequest request = new GetAchievementsFileRequest(); + request.MergeFrom(stream); + + + GetAchievementsFileResponse response = new GetAchievementsFileResponse(); + BattlenetRpcErrorCode status = HandleGetAchievementsFile(request, response); + Log.outDebug(LogFilter.ServiceProtobuf, "{0} Client called server method GameUtilitiesService.GetAchievementsFile(bgs.protocol.game_utilities.v1.GetAchievementsFileRequest: {1}) returned bgs.protocol.game_utilities.v1.GetAchievementsFileResponse: {2} status: {3}.", + GetCallerInfo(), request.ToString(), response.ToString(), status); + if (status == 0) + SendResponse(token, response); + else + SendResponse(token, status); + break; + } + case 10: + { + GetAllValuesForAttributeRequest request = new GetAllValuesForAttributeRequest(); + request.MergeFrom(stream); + + + GetAllValuesForAttributeResponse response = new GetAllValuesForAttributeResponse(); + BattlenetRpcErrorCode status = HandleGetAllValuesForAttribute(request, response); + Log.outDebug(LogFilter.ServiceProtobuf, "{0} Client called server method GameUtilitiesService.GetAllValuesForAttribute(bgs.protocol.game_utilities.v1.GetAllValuesForAttributeRequest: {1}) returned bgs.protocol.game_utilities.v1.GetAllValuesForAttributeResponse: {2} status: {3}.", + GetCallerInfo(), request.ToString(), response.ToString(), status); + if (status == 0) + SendResponse(token, response); + else + SendResponse(token, status); + break; + } + default: + Log.outError(LogFilter.ServiceProtobuf, "Bad method id {0}.", methodId); + SendResponse(token, BattlenetRpcErrorCode.RpcInvalidMethod); + break; + } + } + + BattlenetRpcErrorCode HandleProcessClientRequest(ClientRequest request, ClientResponse response) + { + return _session.HandleProcessClientRequest(request, response); + } + + BattlenetRpcErrorCode HandlePresenceChannelCreated(PresenceChannelCreatedRequest request, NoData response) + { + Log.outError(LogFilter.ServiceProtobuf, "{0} Client tried to call not implemented method GameUtilitiesService.PresenceChannelCreated: {1}", + GetCallerInfo(), request.ToString()); + return BattlenetRpcErrorCode.RpcNotImplemented; + } + + BattlenetRpcErrorCode HandleGetPlayerVariables(GetPlayerVariablesRequest request, GetPlayerVariablesResponse response) + { + Log.outError(LogFilter.ServiceProtobuf, "{0} Client tried to call not implemented method GameUtilitiesService.GetPlayerVariables: {1}", + GetCallerInfo(), request.ToString()); + return BattlenetRpcErrorCode.RpcNotImplemented; + } + + BattlenetRpcErrorCode HandleProcessServerRequest(ServerRequest request, ServerResponse response) + { + Log.outError(LogFilter.ServiceProtobuf, "{0} Client tried to call not implemented method GameUtilitiesService.ProcessServerRequest: {1}", + GetCallerInfo(), request.ToString()); + return BattlenetRpcErrorCode.RpcNotImplemented; + } + + BattlenetRpcErrorCode HandleOnGameAccountOnline(GameAccountOnlineNotification request) + { + Log.outError(LogFilter.ServiceProtobuf, "{0} Client tried to call not implemented method GameUtilitiesService.OnGameAccountOnline: {1}", + GetCallerInfo(), request.ToString()); + return BattlenetRpcErrorCode.RpcNotImplemented; + } + + BattlenetRpcErrorCode HandleOnGameAccountOffline(GameAccountOfflineNotification request) + { + Log.outError(LogFilter.ServiceProtobuf, "{0} Client tried to call not implemented method GameUtilitiesService.OnGameAccountOffline: {1}", + GetCallerInfo(), request.ToString()); + return BattlenetRpcErrorCode.RpcNotImplemented; + } + + BattlenetRpcErrorCode HandleGetAchievementsFile(GetAchievementsFileRequest request, GetAchievementsFileResponse response) + { + Log.outError(LogFilter.ServiceProtobuf, "{0} Client tried to call not implemented method GameUtilitiesService.GetAchievementsFile: {1}", + GetCallerInfo(), request.ToString()); + return BattlenetRpcErrorCode.RpcNotImplemented; + } + + BattlenetRpcErrorCode HandleGetAllValuesForAttribute(GetAllValuesForAttributeRequest request, GetAllValuesForAttributeResponse response) + { + return _session.HandleGetAllValuesForAttribute(request, response); + } + } +} diff --git a/BNetServer/Services/PresenceService.cs b/BNetServer/Services/PresenceService.cs new file mode 100644 index 000000000..a945208a7 --- /dev/null +++ b/BNetServer/Services/PresenceService.cs @@ -0,0 +1,203 @@ +/* + * Copyright (C) 2012-2017 CypherCore + * + * 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, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +using Bgs.Protocol; +using Bgs.Protocol.Presence.V1; +using BNetServer.Services; +using Framework.Constants; +using Google.Protobuf; + +namespace BNetServer.Networking +{ + class PresenceService : ServiceBase + { + public PresenceService(Session session, uint serviceHash) : base(session, serviceHash) { } + + public override void CallServerMethod(uint token, uint methodId, CodedInputStream stream) + { + switch (methodId) + { + case 1: + { + SubscribeRequest request = new SubscribeRequest(); + request.MergeFrom(stream); + + + NoData response = new NoData(); + BattlenetRpcErrorCode status = HandleSubscribe(request, response); + Log.outDebug(LogFilter.ServiceProtobuf, "{0} Client called server method PresenceService.Subscribe(bgs.protocol.presence.v1.SubscribeRequest: {1}) returned bgs.protocol.NoData: {2} status: {3}.", + GetCallerInfo(), request.ToString(), response.ToString(), status); + if (status == 0) + SendResponse(token, response); + else + SendResponse(token, status); + break; + } + case 2: + { + UnsubscribeRequest request = new UnsubscribeRequest(); + request.MergeFrom(stream); + + + NoData response = new NoData(); + BattlenetRpcErrorCode status = HandleUnsubscribe(request, response); + Log.outDebug(LogFilter.ServiceProtobuf, "{0} Client called server method PresenceService.Unsubscribe(bgs.protocol.presence.v1.UnsubscribeRequest: {1}) returned bgs.protocol.NoData: {2} status: {3}.", + GetCallerInfo(), request.ToString(), response.ToString(), status); + if (status == 0) + SendResponse(token, response); + else + SendResponse(token, status); + break; + } + case 3: + { + UpdateRequest request = new UpdateRequest(); + request.MergeFrom(stream); + + + NoData response = new NoData(); + BattlenetRpcErrorCode status = HandleUpdate(request, response); + Log.outDebug(LogFilter.ServiceProtobuf, "{0} Client called server method PresenceService.Update(bgs.protocol.presence.v1.UpdateRequest: {1}) returned bgs.protocol.NoData: {2} status: {3}.", + GetCallerInfo(), request.ToString(), response.ToString(), status); + if (status == 0) + SendResponse(token, response); + else + SendResponse(token, status); + break; + } + case 4: + { + QueryRequest request = new QueryRequest(); + request.MergeFrom(stream); + + + QueryResponse response = new QueryResponse(); + BattlenetRpcErrorCode status = HandleQuery(request, response); + Log.outDebug(LogFilter.ServiceProtobuf, "{0} Client called server method PresenceService.Query(bgs.protocol.presence.v1.QueryRequest: {1}) returned bgs.protocol.presence.v1.QueryResponse: {2} status: {3}.", + GetCallerInfo(), request.ToString(), response.ToString(), status); + if (status == 0) + SendResponse(token, response); + else + SendResponse(token, status); + break; + } + case 5: + { + OwnershipRequest request = new OwnershipRequest(); + request.MergeFrom(stream); + + + NoData response = new NoData(); + BattlenetRpcErrorCode status = HandleOwnership(request, response); + Log.outDebug(LogFilter.ServiceProtobuf, "{0} Client called server method PresenceService.Ownership(bgs.protocol.presence.v1.OwnershipRequest: {1}) returned bgs.protocol.NoData: {2} status: {3}.", + GetCallerInfo(), request.ToString(), response.ToString(), status); + if (status == 0) + SendResponse(token, response); + else + SendResponse(token, status); + break; + } + case 7: + { + SubscribeNotificationRequest request = new SubscribeNotificationRequest(); + request.MergeFrom(stream); + + + NoData response = new NoData(); + BattlenetRpcErrorCode status = HandleSubscribeNotification(request, response); + Log.outDebug(LogFilter.ServiceProtobuf, "{0} Client called server method PresenceService.SubscribeNotification(bgs.protocol.presence.v1.SubscribeNotificationRequest: {1}) returned bgs.protocol.NoData: {2} status: {3}.", + GetCallerInfo(), request.ToString(), response.ToString(), status); + if (status == 0) + SendResponse(token, response); + else + SendResponse(token, status); + break; + } + /*case 8: + { + MigrateOlympusCustomMessageRequest request = new MigrateOlympusCustomMessageRequest(); + request.MergeFrom(stream); + + + MigrateOlympusCustomMessageResponse response = new MigrateOlympusCustomMessageResponse(); + BattlenetRpcErrorCode status = HandleMigrateOlympusCustomMessage(request, response); + Log.outDebug(LogFilter.ServiceProtobuf, "{0} Client called server method PresenceService.MigrateOlympusCustomMessage(bgs.protocol.presence.v1.MigrateOlympusCustomMessageRequest: {1}) returned bgs.protocol.presence.v1.MigrateOlympusCustomMessageResponse: {2} status: {3}.", + GetCallerInfo(), request.ToString(), response.ToString(), status); + if (status == 0) + SendResponse(token, response); + else + SendResponse(token, status); + break; + }*/ + default: + Log.outError(LogFilter.ServiceProtobuf, "Bad method id {0}.", methodId); + SendResponse(token, BattlenetRpcErrorCode.RpcInvalidMethod); + break; + } + } + + BattlenetRpcErrorCode HandleSubscribe(SubscribeRequest request, NoData response) + { + Log.outError(LogFilter.ServiceProtobuf, "{0} Client tried to call not implemented method PresenceService.Subscribe: {1}", + GetCallerInfo(), request.ToString()); + return BattlenetRpcErrorCode.RpcNotImplemented; + } + + BattlenetRpcErrorCode HandleUnsubscribe(UnsubscribeRequest request, NoData response) + { + Log.outError(LogFilter.ServiceProtobuf, "{0} Client tried to call not implemented method PresenceService.Unsubscribe: {1}", + GetCallerInfo(), request.ToString()); + return BattlenetRpcErrorCode.RpcNotImplemented; + } + + BattlenetRpcErrorCode HandleUpdate(UpdateRequest request, NoData response) + { + Log.outError(LogFilter.ServiceProtobuf, "{0} Client tried to call not implemented method PresenceService.Update: {1}", + GetCallerInfo(), request.ToString()); + return BattlenetRpcErrorCode.RpcNotImplemented; + } + + BattlenetRpcErrorCode HandleQuery(QueryRequest request, QueryResponse response) + { + Log.outError(LogFilter.ServiceProtobuf, "{0} Client tried to call not implemented method PresenceService.Query: {1}", + GetCallerInfo(), request.ToString()); + return BattlenetRpcErrorCode.RpcNotImplemented; + } + + BattlenetRpcErrorCode HandleOwnership(OwnershipRequest request, NoData response) + { + Log.outError(LogFilter.ServiceProtobuf, "{0} Client tried to call not implemented method PresenceService.Ownership: {1}", + GetCallerInfo(), request.ToString()); + return BattlenetRpcErrorCode.RpcNotImplemented; + } + + BattlenetRpcErrorCode HandleSubscribeNotification(SubscribeNotificationRequest request, NoData response) + { + Log.outError(LogFilter.ServiceProtobuf, "{0} Client tried to call not implemented method PresenceService.SubscribeNotification: {1}", + GetCallerInfo(), request.ToString()); + return BattlenetRpcErrorCode.RpcNotImplemented; + } + + /*BattlenetRpcErrorCode HandleMigrateOlympusCustomMessage(MigrateOlympusCustomMessageRequest request, MigrateOlympusCustomMessageResponse response) + { + Log.outError(LogFilter.ServiceProtobuf, "{0} Client tried to call not implemented method PresenceService.MigrateOlympusCustomMessage: {1}", + GetCallerInfo(), request.ToString()); + return BattlenetRpcErrorCode.RpcNotImplemented; + } + */ + } +} diff --git a/BNetServer/Services/ReportService.cs b/BNetServer/Services/ReportService.cs new file mode 100644 index 000000000..129659a04 --- /dev/null +++ b/BNetServer/Services/ReportService.cs @@ -0,0 +1,87 @@ +/* + * Copyright (C) 2012-2017 CypherCore + * + * 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, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +using Bgs.Protocol; +using Bgs.Protocol.Report.V1; +using BNetServer.Services; +using Framework.Constants; +using Google.Protobuf; + +namespace BNetServer.Networking +{ + class ReportService : ServiceBase + { + public ReportService(Session session, uint serviceHash) : base(session, serviceHash) { } + + public override void CallServerMethod(uint token, uint methodId, CodedInputStream stream) + { + switch (methodId) + { + case 1: + { + SendReportRequest request = new SendReportRequest(); + request.MergeFrom(stream); + + + NoData response = new NoData(); + BattlenetRpcErrorCode status = HandleSendReport(request, response); + Log.outDebug(LogFilter.ServiceProtobuf, "{0} Client called server method ReportService.SendReport(bgs.protocol.report.v1.SendReportRequest: {1}) returned bgs.protocol.NoData: {2} status: {3}.", + GetCallerInfo(), request.ToString(), response.ToString(), status); + if (status == 0) + SendResponse(token, response); + else + SendResponse(token, status); + break; + } + /*case 2: + { + SubmitReportRequest request = new SubmitReportRequest(); + request.MergeFrom(stream); + + + NoData response = new NoData(); + BattlenetRpcErrorCode status = HandleSubmitReport(request, response); + Log.outDebug(LogFilter.ServiceProtobuf, "{0} Client called server method ReportService.SubmitReport(bgs.protocol.report.v1.SubmitReportRequest: {1}) returned bgs.protocol.NoData: {2} status: {3}.", + GetCallerInfo(), request.ToString(), response.ToString(), status); + if (status == 0) + SendResponse(token, response); + else + SendResponse(token, status); + break; + }*/ + default: + Log.outError(LogFilter.ServiceProtobuf, "Bad method id {0}.", methodId); + SendResponse(token, BattlenetRpcErrorCode.RpcInvalidMethod); + break; + } + } + + BattlenetRpcErrorCode HandleSendReport(SendReportRequest request, NoData response) + { + Log.outError(LogFilter.ServiceProtobuf, "{0} Client tried to call not implemented method ReportService.SendReport: {1}", + GetCallerInfo(), request.ToString()); + return BattlenetRpcErrorCode.RpcNotImplemented; + } + + /* BattlenetRpcErrorCode HandleSubmitReport(SubmitReportRequest request, NoData response) + { + Log.outError(LogFilter.ServiceProtobuf, "{0} Client tried to call not implemented method ReportService.SubmitReport: {1}", + GetCallerInfo(), request.ToString()); + return BattlenetRpcErrorCode.RpcNotImplemented; + }*/ + } +} diff --git a/BNetServer/Services/ResourcesService.cs b/BNetServer/Services/ResourcesService.cs new file mode 100644 index 000000000..cf39a5096 --- /dev/null +++ b/BNetServer/Services/ResourcesService.cs @@ -0,0 +1,60 @@ +/* + * Copyright (C) 2012-2017 CypherCore + * + * 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, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +using BNetServer.Services; +using Framework.Constants; +using Google.Protobuf; + +namespace BNetServer.Networking +{ + class ResourcesService : ServiceBase + { + public ResourcesService(Session session, uint serviceHash) : base(session, serviceHash) { } + + public override void CallServerMethod(uint token, uint methodId, CodedInputStream stream) + { + switch (methodId) + { + /*case 1: + { + ContentHandleRequest request = new ContentHandleRequest(); + request.MergeFrom(stream); + + ContentHandle response = new ContentHandle(); + BattlenetRpcErrorCode status = HandleGetContentHandle(request, response); + Log.outDebug(LogFilter.ServiceProtobuf, "{0} Client called server method ResourcesService.GetContentHandle(bgs.protocol.resources.v1.ContentHandleRequest: {1}) returned bgs.protocol.ContentHandle: {2} status: {3}.", + GetCallerInfo(), request.ToString(), response.ToString(), status); + if (status == 0) + SendResponse(token, response); + else + SendResponse(token, status); + break; + }*/ + default: + Log.outError(LogFilter.ServiceProtobuf, "Bad method id {0}.", methodId); + SendResponse(token, BattlenetRpcErrorCode.RpcInvalidMethod); + break; + } + } + + /*BattlenetRpcErrorCode HandleGetContentHandle(ContentHandleRequest request, ContentHandle response) + { + Log.outError(LogFilter.ServiceProtobuf, "{0} Client tried to call not implemented method ResourcesService.GetContentHandle({1})", GetCallerInfo(), request.ToString()); + return BattlenetRpcErrorCode.RpcNotImplemented; + }*/ + } +} diff --git a/BNetServer/Services/ServiceDispatcher.cs b/BNetServer/Services/ServiceDispatcher.cs new file mode 100644 index 000000000..7c22f5225 --- /dev/null +++ b/BNetServer/Services/ServiceDispatcher.cs @@ -0,0 +1,93 @@ +/* + * Copyright (C) 2012-2016 CypherCore + * + * 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, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +using BNetServer.Networking; +using Framework.Constants; +using Google.Protobuf; +using System; +using System.Collections.Generic; + +namespace BNetServer.Services +{ + public class ServiceDispatcher : Singleton + { + ServiceDispatcher() + { + AddService(OriginalHash.AccountService); + AddService(OriginalHash.AccountListener); + AddService(OriginalHash.AuthenticationService); + AddService(OriginalHash.ChallengeService); + AddService(OriginalHash.ChannelService); + AddService(OriginalHash.ConnectionService); + AddService(OriginalHash.FriendsService); + AddService(OriginalHash.GameUtilitiesService); + AddService(OriginalHash.PresenceService); + AddService(OriginalHash.ReportService); + AddService(OriginalHash.ResourcesService); + AddService(OriginalHash.UserManagerService); + } + + public void Dispatch(Session session, uint serviceHash, uint token, uint methodId, CodedInputStream stream) + { + Log.outInfo(LogFilter.Server, "ServiceHash = {0}, methodId = {1}", serviceHash, methodId); + var action = _dispatchers.LookupByKey(serviceHash); + if (action != null) + action(session, serviceHash, token, methodId, stream); + else + Log.outDebug(LogFilter.SessionRpc, "{0} tried to call invalid service {1}", session.GetClientInfo(), serviceHash); + } + + void AddService(OriginalHash OriginalHash) where Service : ServiceBase + { + _dispatchers[(uint)OriginalHash] = Dispatch; + } + + void Dispatch(Session session, uint serviceHash, uint token, uint methodId, CodedInputStream stream) where Service : ServiceBase + { + var obj = (Service)Activator.CreateInstance(typeof(Service), session, serviceHash); + obj.CallServerMethod(token, methodId, stream); + } + + Dictionary _dispatchers = new Dictionary(); + + delegate void DispatcherHandler(Session session, uint serviceHash, uint token, uint methodId, CodedInputStream stream); + } + + abstract class ServiceBase + { + public ServiceBase(Session session, uint serviceHash) + { + _session = session; + _serviceHash = serviceHash; + } + + public abstract void CallServerMethod(uint token, uint methodId, CodedInputStream stream); + + public void SendRequest(uint methodId, IMessage request, Action callback) { _session.SendRequest(_serviceHash, methodId, request, callback); } + public void SendRequest(uint methodId, IMessage request) { _session.SendRequest(_serviceHash, methodId, request); } + public void SendResponse(uint token, BattlenetRpcErrorCode status) { _session.SendResponse(token, status); } + public void SendResponse(uint token, IMessage response) { _session.SendResponse(token, response); } + + public string GetCallerInfo() + { + return _session.GetClientInfo(); + } + + protected Session _session; + protected uint _serviceHash; + } +} diff --git a/BNetServer/Services/UserManagerService.cs b/BNetServer/Services/UserManagerService.cs new file mode 100644 index 000000000..9840c778f --- /dev/null +++ b/BNetServer/Services/UserManagerService.cs @@ -0,0 +1,319 @@ +/* + * Copyright (C) 2012-2017 CypherCore + * + * 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, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +using Bgs.Protocol; +using Bgs.Protocol.UserManager.V1; +using BNetServer.Services; +using Framework.Constants; +using Google.Protobuf; + +namespace BNetServer.Networking +{ + class UserManagerService : ServiceBase + { + public UserManagerService(Session session, uint serviceHash) : base(session, serviceHash) { } + + public override void CallServerMethod(uint token, uint methodId, CodedInputStream stream) + { + switch (methodId) + { + case 1: + { + SubscribeRequest request = new SubscribeRequest(); + request.MergeFrom(stream); + + SubscribeResponse response = new SubscribeResponse(); + BattlenetRpcErrorCode status = HandleSubscribe(request, response); + Log.outDebug(LogFilter.ServiceProtobuf, "{0} Client called server method UserManagerService.Subscribe(bgs.protocol.user_manager.v1.SubscribeRequest: {1}) returned bgs.protocol.user_manager.v1.SubscribeResponse: {2} status: {3}.", + GetCallerInfo(), request.ToString(), response.ToString(), status); + if (status == 0) + SendResponse(token, response); + else + SendResponse(token, status); + break; + } + case 10: + { + AddRecentPlayersRequest request = new AddRecentPlayersRequest(); + request.MergeFrom(stream); + + AddRecentPlayersResponse response = new AddRecentPlayersResponse(); + BattlenetRpcErrorCode status = HandleAddRecentPlayers(request, response); + Log.outDebug(LogFilter.ServiceProtobuf, "{0} Client called server method UserManagerService.AddRecentPlayers(bgs.protocol.user_manager.v1.AddRecentPlayersRequest: {1}) returned bgs.protocol.user_manager.v1.AddRecentPlayersResponse: {2} status: {3}.", + GetCallerInfo(), request.ToString(), response.ToString(), status); + if (status == 0) + SendResponse(token, response); + else + SendResponse(token, status); + break; + } + case 11: + { + ClearRecentPlayersRequest request = new ClearRecentPlayersRequest(); + request.MergeFrom(stream); + + + ClearRecentPlayersResponse response = new ClearRecentPlayersResponse(); + BattlenetRpcErrorCode status = HandleClearRecentPlayers(request, response); + Log.outDebug(LogFilter.ServiceProtobuf, "{0} Client called server method UserManagerService.ClearRecentPlayers(bgs.protocol.user_manager.v1.ClearRecentPlayersRequest: {1}) returned bgs.protocol.user_manager.v1.ClearRecentPlayersResponse: {2} status: {3}.", + GetCallerInfo(), request.ToString(), response.ToString(), status); + if (status == 0) + SendResponse(token, response); + else + SendResponse(token, status); + break; + } + case 20: + { + BlockPlayerRequest request = new BlockPlayerRequest(); + request.MergeFrom(stream); + + + NoData response = new NoData(); + BattlenetRpcErrorCode status = HandleBlockPlayer(request, response); + Log.outDebug(LogFilter.ServiceProtobuf, "{0} Client called server method UserManagerService.BlockPlayer(bgs.protocol.user_manager.v1.BlockPlayerRequest: {1}) returned bgs.protocol.NoData: {2} status: {3}.", + GetCallerInfo(), request.ToString(), response.ToString(), status); + if (status == 0) + SendResponse(token, response); + else + SendResponse(token, status); + break; + } + case 21: + { + UnblockPlayerRequest request = new UnblockPlayerRequest(); + request.MergeFrom(stream); + + + NoData response = new NoData(); + BattlenetRpcErrorCode status = HandleUnblockPlayer(request, response); + Log.outDebug(LogFilter.ServiceProtobuf, "{0} Client called server method UserManagerService.UnblockPlayer(bgs.protocol.user_manager.v1.UnblockPlayerRequest: {1}) returned bgs.protocol.NoData: {2} status: {3}.", + GetCallerInfo(), request.ToString(), response.ToString(), status); + if (status == 0) + SendResponse(token, response); + else + SendResponse(token, status); + break; + } + case 40: + { + BlockPlayerRequest request = new BlockPlayerRequest(); + request.MergeFrom(stream); + + + NoData response = new NoData(); + BattlenetRpcErrorCode status = HandleBlockPlayerForSession(request, response); + Log.outDebug(LogFilter.ServiceProtobuf, "{0} Client called server method UserManagerService.BlockPlayerForSession(bgs.protocol.user_manager.v1.BlockPlayerRequest: {1}) returned bgs.protocol.NoData: {2} status: {3}.", + GetCallerInfo(), request.ToString(), response.ToString(), status); + if (status == 0) + SendResponse(token, response); + else + SendResponse(token, status); + break; + } + case 50: + { + EntityId request = new EntityId(); + request.MergeFrom(stream); + + + NoData response = new NoData(); + BattlenetRpcErrorCode status = HandleLoadBlockList(request, response); + Log.outDebug(LogFilter.ServiceProtobuf, "{0} Client called server method UserManagerService.LoadBlockList(bgs.protocol.EntityId: {1}) returned bgs.protocol.NoData: {2} status: {3}.", + GetCallerInfo(), request.ToString(), response.ToString(), status); + if (status == 0) + SendResponse(token, response); + else + SendResponse(token, status); + break; + } + case 51: + { + UnsubscribeRequest request = new UnsubscribeRequest(); + request.MergeFrom(stream); + + + NoData response = new NoData(); + BattlenetRpcErrorCode status = HandleUnsubscribe(request, response); + Log.outDebug(LogFilter.ServiceProtobuf, "{0} Client called server method UserManagerService.Unsubscribe(bgs.protocol.user_manager.v1.UnsubscribeRequest: {1}) returned bgs.protocol.NoData: {2} status: {3}.", + GetCallerInfo(), request.ToString(), response.ToString(), status); + if (status == 0) + SendResponse(token, response); + else + SendResponse(token, status); + break; + } + default: + Log.outError(LogFilter.ServiceProtobuf, "Bad method id {0}.", methodId); + SendResponse(token, BattlenetRpcErrorCode.RpcInvalidMethod); + break; + } + } + + BattlenetRpcErrorCode HandleSubscribe(SubscribeRequest request, SubscribeResponse response) + { + Log.outError(LogFilter.ServiceProtobuf, "{0} Client tried to call not implemented method UserManagerService.Subscribe: {1}", + GetCallerInfo(), request.ToString()); + return BattlenetRpcErrorCode.RpcNotImplemented; + } + + BattlenetRpcErrorCode HandleAddRecentPlayers(AddRecentPlayersRequest request, AddRecentPlayersResponse response) + { + Log.outError(LogFilter.ServiceProtobuf, "{0} Client tried to call not implemented method UserManagerService.AddRecentPlayers: {1}", + GetCallerInfo(), request.ToString()); + return BattlenetRpcErrorCode.RpcNotImplemented; + } + + BattlenetRpcErrorCode HandleClearRecentPlayers(ClearRecentPlayersRequest request, ClearRecentPlayersResponse response) + { + Log.outError(LogFilter.ServiceProtobuf, "{0} Client tried to call not implemented method UserManagerService.ClearRecentPlayers: {1}", + GetCallerInfo(), request.ToString()); + return BattlenetRpcErrorCode.RpcNotImplemented; + } + + BattlenetRpcErrorCode HandleBlockPlayer(BlockPlayerRequest request, NoData response) + { + Log.outError(LogFilter.ServiceProtobuf, "{0} Client tried to call not implemented method UserManagerService.BlockPlayer: {1}", + GetCallerInfo(), request.ToString()); + return BattlenetRpcErrorCode.RpcNotImplemented; + } + + BattlenetRpcErrorCode HandleUnblockPlayer(UnblockPlayerRequest request, NoData response) + { + Log.outError(LogFilter.ServiceProtobuf, "{0} Client tried to call not implemented method UserManagerService.UnblockPlayer: {1}", + GetCallerInfo(), request.ToString()); + return BattlenetRpcErrorCode.RpcNotImplemented; + } + + BattlenetRpcErrorCode HandleBlockPlayerForSession(BlockPlayerRequest request, NoData response) + { + Log.outError(LogFilter.ServiceProtobuf, "{0} Client tried to call not implemented method UserManagerService.BlockPlayerForSession: {1}", + GetCallerInfo(), request.ToString()); + return BattlenetRpcErrorCode.RpcNotImplemented; + } + + BattlenetRpcErrorCode HandleLoadBlockList(EntityId request, NoData response) + { + Log.outError(LogFilter.ServiceProtobuf, "{0} Client tried to call not implemented method UserManagerService.LoadBlockList: {1}", + GetCallerInfo(), request.ToString()); + return BattlenetRpcErrorCode.RpcNotImplemented; + } + + BattlenetRpcErrorCode HandleUnsubscribe(UnsubscribeRequest request, NoData response) + { + Log.outError(LogFilter.ServiceProtobuf, "{0} Client tried to call not implemented method UserManagerService.Unsubscribe: {1}", + GetCallerInfo(), request.ToString()); + return BattlenetRpcErrorCode.RpcNotImplemented; + } + } + + class UserManagerListener : ServiceBase + { + public UserManagerListener(Session session, uint serviceHash) : base(session, serviceHash) { } + + public override void CallServerMethod(uint token, uint methodId, CodedInputStream stream) + { + switch (methodId) + { + case 1: + { + BlockedPlayerAddedNotification request = new BlockedPlayerAddedNotification(); + request.MergeFrom(stream); + + + BattlenetRpcErrorCode status = HandleOnBlockedPlayerAdded(request); + Log.outDebug(LogFilter.ServiceProtobuf, "{0} Client called server method UserManagerListener.OnBlockedPlayerAdded(bgs.protocol.user_manager.v1.BlockedPlayerAddedNotification: {1}) status: {2}.", + GetCallerInfo(), request.ToString(), status); + if (status != 0) + SendResponse(token, status); + break; + } + case 2: + { + BlockedPlayerRemovedNotification request = new BlockedPlayerRemovedNotification(); + request.MergeFrom(stream); + + + BattlenetRpcErrorCode status = HandleOnBlockedPlayerRemoved(request); + Log.outDebug(LogFilter.ServiceProtobuf, "{0} Client called server method UserManagerListener.OnBlockedPlayerRemoved(bgs.protocol.user_manager.v1.BlockedPlayerRemovedNotification: {1}) status: {2}.", + GetCallerInfo(), request.ToString(), status); + if (status != 0) + SendResponse(token, status); + break; + } + case 11: + { + RecentPlayersAddedNotification request = new RecentPlayersAddedNotification(); + request.MergeFrom(stream); + + + BattlenetRpcErrorCode status = HandleOnRecentPlayersAdded(request); + Log.outDebug(LogFilter.ServiceProtobuf, "{0} Client called server method UserManagerListener.OnRecentPlayersAdded(bgs.protocol.user_manager.v1.RecentPlayersAddedNotification: {1}) status: {2}.", + GetCallerInfo(), request.ToString(), status); + if (status != 0) + SendResponse(token, status); + break; + } + case 12: + { + RecentPlayersRemovedNotification request = new RecentPlayersRemovedNotification(); + request.MergeFrom(stream); + + + BattlenetRpcErrorCode status = HandleOnRecentPlayersRemoved(request); + Log.outDebug(LogFilter.ServiceProtobuf, "{0} Client called server method UserManagerListener.OnRecentPlayersRemoved(bgs.protocol.user_manager.v1.RecentPlayersRemovedNotification: {1}) status: {2}.", + GetCallerInfo(), request.ToString(), status); + if (status != 0) + SendResponse(token, status); + break; + } + default: + Log.outError(LogFilter.ServiceProtobuf, "Bad method id {0}.", methodId); + SendResponse(token, BattlenetRpcErrorCode.RpcInvalidMethod); + break; + } + } + + BattlenetRpcErrorCode HandleOnBlockedPlayerAdded(BlockedPlayerAddedNotification request) + { + Log.outError(LogFilter.ServiceProtobuf, "{0} Client tried to call not implemented method UserManagerListener.OnBlockedPlayerAdded: {1}", + GetCallerInfo(), request.ToString()); + return BattlenetRpcErrorCode.RpcNotImplemented; + } + + BattlenetRpcErrorCode HandleOnBlockedPlayerRemoved(BlockedPlayerRemovedNotification request) + { + Log.outError(LogFilter.ServiceProtobuf, "{0} Client tried to call not implemented method UserManagerListener.OnBlockedPlayerRemoved: {1}", + GetCallerInfo(), request.ToString()); + return BattlenetRpcErrorCode.RpcNotImplemented; + } + + BattlenetRpcErrorCode HandleOnRecentPlayersAdded(RecentPlayersAddedNotification request) + { + Log.outError(LogFilter.ServiceProtobuf, "{0} Client tried to call not implemented method UserManagerListener.OnRecentPlayersAdded: {1}", + GetCallerInfo(), request.ToString()); + return BattlenetRpcErrorCode.RpcNotImplemented; + } + + BattlenetRpcErrorCode HandleOnRecentPlayersRemoved(RecentPlayersRemovedNotification request) + { + Log.outError(LogFilter.ServiceProtobuf, "{0} Client tried to call not implemented method UserManagerListener.OnRecentPlayersRemoved: {1}", + GetCallerInfo(), request.ToString()); + return BattlenetRpcErrorCode.RpcNotImplemented; + } + } +} diff --git a/CypherCore.sln b/CypherCore.sln new file mode 100644 index 000000000..f4210302a --- /dev/null +++ b/CypherCore.sln @@ -0,0 +1,115 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio 15 +VisualStudioVersion = 15.0.26430.12 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "WorldServer", "WorldServer\WorldServer.csproj", "{D157534F-86F1-4E8E-80B0-ECAD321ECAA6}" + ProjectSection(ProjectDependencies) = postProject + {9E28B9C1-E473-4DAE-813F-3243CE318736} = {9E28B9C1-E473-4DAE-813F-3243CE318736} + EndProjectSection +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "BNetServer", "BNetServer\BNetServer.csproj", "{668E1664-FB63-47DA-9EA1-93B59851BF1E}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Game", "Game\Game.csproj", "{83EF8D5C-AFA1-4546-BCDD-6422D55B1515}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Scripts", "Scripts\Scripts.csproj", "{9E28B9C1-E473-4DAE-813F-3243CE318736}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Framework", "Framework\Framework.csproj", "{82D442A9-18C0-4C59-8EC6-0DFE3E34D334}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|x64 = Debug|x64 + Debug|x86 = Debug|x86 + DebugMono|x64 = DebugMono|x64 + DebugMono|x86 = DebugMono|x86 + Release|x64 = Release|x64 + Release|x86 = Release|x86 + ReleaseMono|x64 = ReleaseMono|x64 + ReleaseMono|x86 = ReleaseMono|x86 + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {D157534F-86F1-4E8E-80B0-ECAD321ECAA6}.Debug|x64.ActiveCfg = Debug|x64 + {D157534F-86F1-4E8E-80B0-ECAD321ECAA6}.Debug|x64.Build.0 = Debug|x64 + {D157534F-86F1-4E8E-80B0-ECAD321ECAA6}.Debug|x86.ActiveCfg = Debug|x86 + {D157534F-86F1-4E8E-80B0-ECAD321ECAA6}.Debug|x86.Build.0 = Debug|x86 + {D157534F-86F1-4E8E-80B0-ECAD321ECAA6}.DebugMono|x64.ActiveCfg = Debug|x64 + {D157534F-86F1-4E8E-80B0-ECAD321ECAA6}.DebugMono|x64.Build.0 = Debug|x64 + {D157534F-86F1-4E8E-80B0-ECAD321ECAA6}.DebugMono|x86.ActiveCfg = Debug|x86 + {D157534F-86F1-4E8E-80B0-ECAD321ECAA6}.DebugMono|x86.Build.0 = Debug|x86 + {D157534F-86F1-4E8E-80B0-ECAD321ECAA6}.Release|x64.ActiveCfg = Release|x64 + {D157534F-86F1-4E8E-80B0-ECAD321ECAA6}.Release|x64.Build.0 = Release|x64 + {D157534F-86F1-4E8E-80B0-ECAD321ECAA6}.Release|x86.ActiveCfg = Release|x86 + {D157534F-86F1-4E8E-80B0-ECAD321ECAA6}.Release|x86.Build.0 = Release|x86 + {D157534F-86F1-4E8E-80B0-ECAD321ECAA6}.ReleaseMono|x64.ActiveCfg = Release|x64 + {D157534F-86F1-4E8E-80B0-ECAD321ECAA6}.ReleaseMono|x64.Build.0 = Release|x64 + {D157534F-86F1-4E8E-80B0-ECAD321ECAA6}.ReleaseMono|x86.ActiveCfg = Release|x86 + {D157534F-86F1-4E8E-80B0-ECAD321ECAA6}.ReleaseMono|x86.Build.0 = Release|x86 + {668E1664-FB63-47DA-9EA1-93B59851BF1E}.Debug|x64.ActiveCfg = Debug|x64 + {668E1664-FB63-47DA-9EA1-93B59851BF1E}.Debug|x64.Build.0 = Debug|x64 + {668E1664-FB63-47DA-9EA1-93B59851BF1E}.Debug|x86.ActiveCfg = Debug|x86 + {668E1664-FB63-47DA-9EA1-93B59851BF1E}.Debug|x86.Build.0 = Debug|x86 + {668E1664-FB63-47DA-9EA1-93B59851BF1E}.DebugMono|x64.ActiveCfg = Debug|x64 + {668E1664-FB63-47DA-9EA1-93B59851BF1E}.DebugMono|x64.Build.0 = Debug|x64 + {668E1664-FB63-47DA-9EA1-93B59851BF1E}.DebugMono|x86.ActiveCfg = Debug|x86 + {668E1664-FB63-47DA-9EA1-93B59851BF1E}.DebugMono|x86.Build.0 = Debug|x86 + {668E1664-FB63-47DA-9EA1-93B59851BF1E}.Release|x64.ActiveCfg = Release|x64 + {668E1664-FB63-47DA-9EA1-93B59851BF1E}.Release|x64.Build.0 = Release|x64 + {668E1664-FB63-47DA-9EA1-93B59851BF1E}.Release|x86.ActiveCfg = Release|x86 + {668E1664-FB63-47DA-9EA1-93B59851BF1E}.Release|x86.Build.0 = Release|x86 + {668E1664-FB63-47DA-9EA1-93B59851BF1E}.ReleaseMono|x64.ActiveCfg = Release|x64 + {668E1664-FB63-47DA-9EA1-93B59851BF1E}.ReleaseMono|x64.Build.0 = Release|x64 + {668E1664-FB63-47DA-9EA1-93B59851BF1E}.ReleaseMono|x86.ActiveCfg = Release|x86 + {668E1664-FB63-47DA-9EA1-93B59851BF1E}.ReleaseMono|x86.Build.0 = Release|x86 + {83EF8D5C-AFA1-4546-BCDD-6422D55B1515}.Debug|x64.ActiveCfg = Debug|x64 + {83EF8D5C-AFA1-4546-BCDD-6422D55B1515}.Debug|x64.Build.0 = Debug|x64 + {83EF8D5C-AFA1-4546-BCDD-6422D55B1515}.Debug|x86.ActiveCfg = Debug|x86 + {83EF8D5C-AFA1-4546-BCDD-6422D55B1515}.Debug|x86.Build.0 = Debug|x86 + {83EF8D5C-AFA1-4546-BCDD-6422D55B1515}.DebugMono|x64.ActiveCfg = Debug|x64 + {83EF8D5C-AFA1-4546-BCDD-6422D55B1515}.DebugMono|x64.Build.0 = Debug|x64 + {83EF8D5C-AFA1-4546-BCDD-6422D55B1515}.DebugMono|x86.ActiveCfg = Debug|x86 + {83EF8D5C-AFA1-4546-BCDD-6422D55B1515}.DebugMono|x86.Build.0 = Debug|x86 + {83EF8D5C-AFA1-4546-BCDD-6422D55B1515}.Release|x64.ActiveCfg = Release|x64 + {83EF8D5C-AFA1-4546-BCDD-6422D55B1515}.Release|x64.Build.0 = Release|x64 + {83EF8D5C-AFA1-4546-BCDD-6422D55B1515}.Release|x86.ActiveCfg = Release|x86 + {83EF8D5C-AFA1-4546-BCDD-6422D55B1515}.Release|x86.Build.0 = Release|x86 + {83EF8D5C-AFA1-4546-BCDD-6422D55B1515}.ReleaseMono|x64.ActiveCfg = Release|x64 + {83EF8D5C-AFA1-4546-BCDD-6422D55B1515}.ReleaseMono|x64.Build.0 = Release|x64 + {83EF8D5C-AFA1-4546-BCDD-6422D55B1515}.ReleaseMono|x86.ActiveCfg = Release|x86 + {83EF8D5C-AFA1-4546-BCDD-6422D55B1515}.ReleaseMono|x86.Build.0 = Release|x86 + {9E28B9C1-E473-4DAE-813F-3243CE318736}.Debug|x64.ActiveCfg = Debug|x64 + {9E28B9C1-E473-4DAE-813F-3243CE318736}.Debug|x64.Build.0 = Debug|x64 + {9E28B9C1-E473-4DAE-813F-3243CE318736}.Debug|x86.ActiveCfg = Debug|x86 + {9E28B9C1-E473-4DAE-813F-3243CE318736}.Debug|x86.Build.0 = Debug|x86 + {9E28B9C1-E473-4DAE-813F-3243CE318736}.DebugMono|x64.ActiveCfg = Debug|x64 + {9E28B9C1-E473-4DAE-813F-3243CE318736}.DebugMono|x64.Build.0 = Debug|x64 + {9E28B9C1-E473-4DAE-813F-3243CE318736}.DebugMono|x86.ActiveCfg = Debug|x86 + {9E28B9C1-E473-4DAE-813F-3243CE318736}.DebugMono|x86.Build.0 = Debug|x86 + {9E28B9C1-E473-4DAE-813F-3243CE318736}.Release|x64.ActiveCfg = Release|x64 + {9E28B9C1-E473-4DAE-813F-3243CE318736}.Release|x64.Build.0 = Release|x64 + {9E28B9C1-E473-4DAE-813F-3243CE318736}.Release|x86.ActiveCfg = Release|x86 + {9E28B9C1-E473-4DAE-813F-3243CE318736}.Release|x86.Build.0 = Release|x86 + {9E28B9C1-E473-4DAE-813F-3243CE318736}.ReleaseMono|x64.ActiveCfg = Release|x64 + {9E28B9C1-E473-4DAE-813F-3243CE318736}.ReleaseMono|x64.Build.0 = Release|x64 + {9E28B9C1-E473-4DAE-813F-3243CE318736}.ReleaseMono|x86.ActiveCfg = Release|x86 + {9E28B9C1-E473-4DAE-813F-3243CE318736}.ReleaseMono|x86.Build.0 = Release|x86 + {82D442A9-18C0-4C59-8EC6-0DFE3E34D334}.Debug|x64.ActiveCfg = Debug|x64 + {82D442A9-18C0-4C59-8EC6-0DFE3E34D334}.Debug|x64.Build.0 = Debug|x64 + {82D442A9-18C0-4C59-8EC6-0DFE3E34D334}.Debug|x86.ActiveCfg = Debug|x86 + {82D442A9-18C0-4C59-8EC6-0DFE3E34D334}.Debug|x86.Build.0 = Debug|x86 + {82D442A9-18C0-4C59-8EC6-0DFE3E34D334}.DebugMono|x64.ActiveCfg = Debug|x64 + {82D442A9-18C0-4C59-8EC6-0DFE3E34D334}.DebugMono|x64.Build.0 = Debug|x64 + {82D442A9-18C0-4C59-8EC6-0DFE3E34D334}.DebugMono|x86.ActiveCfg = Debug|x86 + {82D442A9-18C0-4C59-8EC6-0DFE3E34D334}.DebugMono|x86.Build.0 = Debug|x86 + {82D442A9-18C0-4C59-8EC6-0DFE3E34D334}.Release|x64.ActiveCfg = Release|x64 + {82D442A9-18C0-4C59-8EC6-0DFE3E34D334}.Release|x64.Build.0 = Release|x64 + {82D442A9-18C0-4C59-8EC6-0DFE3E34D334}.Release|x86.ActiveCfg = Release|x86 + {82D442A9-18C0-4C59-8EC6-0DFE3E34D334}.Release|x86.Build.0 = Release|x86 + {82D442A9-18C0-4C59-8EC6-0DFE3E34D334}.ReleaseMono|x64.ActiveCfg = Release|x64 + {82D442A9-18C0-4C59-8EC6-0DFE3E34D334}.ReleaseMono|x64.Build.0 = Release|x64 + {82D442A9-18C0-4C59-8EC6-0DFE3E34D334}.ReleaseMono|x86.ActiveCfg = Release|x86 + {82D442A9-18C0-4C59-8EC6-0DFE3E34D334}.ReleaseMono|x86.Build.0 = Release|x86 + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection +EndGlobal diff --git a/Framework/Collections/Array.cs b/Framework/Collections/Array.cs new file mode 100644 index 000000000..67c0c38f0 --- /dev/null +++ b/Framework/Collections/Array.cs @@ -0,0 +1,81 @@ +/* + * Copyright (C) 2012-2017 CypherCore + * + * 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, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +using System.IO; + +namespace System.Collections.Generic +{ + public class Array : List + { + public Array(int size) : base(size) + { + _limit = size; + } + + public Array(params T[] args) : base(args) + { + _limit = args.Length; + } + + public Array(int size, T defaultFillValue) : this(size) + { + Fill(defaultFillValue); + } + + public void Fill(T value) + { + for (var i = 0; i < _limit; ++i) + Add(value); + } + + public new void Add(T value) + { + if (base.Count >= _limit) + throw new InternalBufferOverflowException("Attempted to read more array elements from packet " + base.Count + 1 + " than allowed " + _limit); + + base.Add(value); + } + + public new T this[int index] + { + get + { + return base[index]; + } + set + { + if (index >= Count) + { + if (Count >= _limit) + throw new InternalBufferOverflowException("Attempted to read more array elements from packet " + base.Count + 1 + " than allowed " + _limit); + base.Insert(index, value); + } + else + base[index] = value; + } + } + + public int GetLimit() { return _limit; } + + public static implicit operator T[] (Array array) + { + return array.ToArray(); + } + + int _limit; + } +} diff --git a/Framework/Collections/BitSet.cs b/Framework/Collections/BitSet.cs new file mode 100644 index 000000000..e21af9698 --- /dev/null +++ b/Framework/Collections/BitSet.cs @@ -0,0 +1,414 @@ +/* + * Copyright (C) 2012-2017 CypherCore + * + * 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, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +using System.Diagnostics.Contracts; + +namespace System.Collections +{ + public class BitSet : ICollection, ICloneable + { + public BitSet(int length, bool defaultValue = false) + { + if (length < 0) + { + throw new ArgumentOutOfRangeException("length"); + } + Contract.EndContractBlock(); + + m_array = new uint[GetArrayLength(length, BitsPerInt32)]; + m_length = length; + + uint fillValue = defaultValue ? 0xffffffff : 0; + for (int i = 0; i < m_array.Length; i++) + { + m_array[i] = fillValue; + } + + _version = 0; + } + + public BitSet(uint[] values) + { + if (values == null) + { + throw new ArgumentNullException("values"); + } + Contract.EndContractBlock(); + // this value is chosen to prevent overflow when computing m_length + if (values.Length > UInt32.MaxValue / BitsPerInt32) + { + throw new ArgumentException(); + } + + m_array = new uint[values.Length]; + m_length = values.Length * BitsPerInt32; + + Array.Copy(values, m_array, values.Length); + + _version = 0; + } + + public BitSet(BitSet bits) + { + if (bits == null) + { + throw new ArgumentNullException("bits"); + } + Contract.EndContractBlock(); + + int arrayLength = GetArrayLength(bits.m_length, BitsPerInt32); + m_array = new uint[arrayLength]; + m_length = bits.m_length; + + Array.Copy(bits.m_array, m_array, arrayLength); + + _version = bits._version; + } + + public bool this[int index] + { + get + { + return Get(index); + } + set + { + Set(index, value); + } + } + + public bool Get(int index) + { + if (index < 0 || index >= Length) + { + throw new ArgumentOutOfRangeException("index"); + } + Contract.EndContractBlock(); + + return (Convert.ToInt64(m_array[index / 32]) & (1 << (index % 32))) != 0; + } + + public void Set(int index, bool value) + { + if (index < 0 || index >= Length) + { + throw new ArgumentOutOfRangeException("index"); + } + Contract.EndContractBlock(); + + if (value) + { + m_array[index / 32] |= (1u << (index % 32)); + } + else + { + m_array[index / 32] &= ~(1u << (index % 32)); + } + + _version++; + } + + public void SetAll(bool value) + { + uint fillValue = value ? 0xffffffff : 0u; + int ints = GetArrayLength(m_length, BitsPerInt32); + for (int i = 0; i < ints; i++) + { + m_array[i] = fillValue; + } + + _version++; + } + + public BitSet And(BitSet value) + { + if (value == null) + throw new ArgumentNullException("value"); + if (Length != value.Length) + throw new ArgumentException(); + Contract.EndContractBlock(); + + int ints = GetArrayLength(m_length, BitsPerInt32); + for (int i = 0; i < ints; i++) + { + m_array[i] &= value.m_array[i]; + } + + _version++; + return this; + } + + public BitSet Or(BitSet value) + { + if (value == null) + throw new ArgumentNullException("value"); + if (Length != value.Length) + throw new ArgumentException(); + Contract.EndContractBlock(); + + int ints = GetArrayLength(m_length, BitsPerInt32); + for (int i = 0; i < ints; i++) + { + m_array[i] |= value.m_array[i]; + } + + _version++; + return this; + } + + public BitSet Xor(BitSet value) + { + if (value == null) + throw new ArgumentNullException("value"); + if (Length != value.Length) + throw new ArgumentException(); + Contract.EndContractBlock(); + + int ints = GetArrayLength(m_length, BitsPerInt32); + for (int i = 0; i < ints; i++) + { + m_array[i] ^= value.m_array[i]; + } + + _version++; + return this; + } + + public BitSet Not() + { + int ints = GetArrayLength(m_length, BitsPerInt32); + for (int i = 0; i < ints; i++) + { + m_array[i] = ~m_array[i]; + } + + _version++; + return this; + } + + public int Length + { + get + { + Contract.Ensures(Contract.Result() >= 0); + return m_length; + } + set + { + if (value < 0) + { + throw new ArgumentOutOfRangeException("value"); + } + Contract.EndContractBlock(); + + int newints = GetArrayLength(value, BitsPerInt32); + if (newints > m_array.Length || newints + _ShrinkThreshold < m_array.Length) + { + // grow or shrink (if wasting more than _ShrinkThreshold ints) + uint[] newarray = new uint[newints]; + Array.Copy(m_array, newarray, newints > m_array.Length ? m_array.Length : newints); + m_array = newarray; + } + + if (value > m_length) + { + // clear high bit values in the last int + int last = GetArrayLength(m_length, BitsPerInt32) - 1; + int bits = m_length % 32; + if (bits > 0) + { + m_array[last] &= (1u << bits) - 1; + } + + // clear remaining int values + Array.Clear(m_array, last + 1, newints - last - 1); + } + + m_length = value; + _version++; + } + } + + // ICollection implementation + public void CopyTo(Array array, int index) + { + if (array == null) + throw new ArgumentNullException("array"); + + if (index < 0) + throw new ArgumentOutOfRangeException("index"); + + if (array.Rank != 1) + throw new ArgumentException(); + + Contract.EndContractBlock(); + + if (array is uint[]) + { + Array.Copy(m_array, 0, array, index, GetArrayLength(m_length, BitsPerInt32)); + } + else if (array is byte[]) + { + int arrayLength = GetArrayLength(m_length, BitsPerByte); + if ((array.Length - index) < arrayLength) + throw new ArgumentException(); + + byte[] b = (byte[])array; + for (int i = 0; i < arrayLength; i++) + b[index + i] = (byte)((m_array[i / 4] >> ((i % 4) * 8)) & 0x000000FF); // Shift to bring the required byte to LSB, then mask + } + else if (array is bool[]) + { + if (array.Length - index < m_length) + throw new ArgumentException(); + + bool[] b = (bool[])array; + for (int i = 0; i < m_length; i++) + b[index + i] = ((m_array[i / 32] >> (i % 32)) & 0x00000001) != 0; + } + else + throw new ArgumentException(); + } + + public int Count + { + get + { + Contract.Ensures(Contract.Result() >= 0); + + return (int)m_length; + } + } + + public Object Clone() + { + Contract.Ensures(Contract.Result() != null); + Contract.Ensures(((BitArray)Contract.Result()).Length == this.Length); + + BitSet bitArray = new BitSet(m_array); + bitArray._version = _version; + bitArray.m_length = m_length; + return bitArray; + } + + public Object SyncRoot + { + get + { + if (_syncRoot == null) + { + System.Threading.Interlocked.CompareExchange(ref _syncRoot, new Object(), null); + } + return _syncRoot; + } + } + + public bool IsReadOnly + { + get + { + return false; + } + } + + public bool IsSynchronized + { + get + { + return false; + } + } + + public IEnumerator GetEnumerator() + { + return new BitArrayEnumeratorSimple(this); + } + + // XPerY=n means that n Xs can be stored in 1 Y. + private const int BitsPerInt32 = 32; + private const int BytesPerInt32 = 4; + private const int BitsPerByte = 8; + + private static int GetArrayLength(int n, int div) + { + Contract.Assert(div > 0, "GetArrayLength: div arg must be greater than 0"); + return n > 0 ? (((n - 1) / div) + 1) : 0; + } + + [Serializable] + private class BitArrayEnumeratorSimple : IEnumerator, ICloneable + { + private BitSet bitarray; + private int index; + private int version; + private bool currentElement; + + internal BitArrayEnumeratorSimple(BitSet bitarray) + { + this.bitarray = bitarray; + this.index = -1; + version = bitarray._version; + } + + public Object Clone() + { + return MemberwiseClone(); + } + + public virtual bool MoveNext() + { + //if (version != bitarray._version) throw new InvalidOperationException(Environment.GetResourceString(ResId.InvalidOperation_EnumFailedVersion)); + if (index < (bitarray.Count - 1)) + { + index++; + currentElement = bitarray.Get(index); + return true; + } + else + index = bitarray.Count; + + return false; + } + + public virtual Object Current + { + get + { + if (index == -1) + throw new InvalidOperationException(); + if (index >= bitarray.Count) + throw new InvalidOperationException(); + return currentElement; + } + } + + public void Reset() + { + //if (version != bitarray._version) throw new InvalidOperationException(Environment.GetResourceString(ResId.InvalidOperation_EnumFailedVersion)); + index = -1; + } + } + + private uint[] m_array; + private int m_length; + private int _version; + [NonSerialized] + private Object _syncRoot; + + private const int _ShrinkThreshold = 256; + } +} \ No newline at end of file diff --git a/Framework/Collections/IMultiMap.cs b/Framework/Collections/IMultiMap.cs new file mode 100644 index 000000000..88c838cae --- /dev/null +++ b/Framework/Collections/IMultiMap.cs @@ -0,0 +1,41 @@ +/* + * Copyright (C) 2012-2017 CypherCore + * + * 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, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +namespace System.Collections.Generic +{ + public interface IMultiMap + { + void AddRange(TKey key, IEnumerable valueList); + List this[TKey key] { get; set;} + bool Remove(TKey key, TValue value); + void Add(TKey key, TValue value); + bool ContainsKey(TKey key); + + ICollection Keys {get;} + bool Remove(TKey key); + ICollection Values{get;} + + void Add(KeyValuePair item); + void Clear(); + bool Contains(TKey key, TValue item); + void CopyTo(KeyValuePair[] array, int arrayIndex); + int Count {get;} + bool Remove(KeyValuePair item); + + List LookupByKey(TKey key); + } +} diff --git a/Framework/Collections/LinkedListElement.cs b/Framework/Collections/LinkedListElement.cs new file mode 100644 index 000000000..39818842b --- /dev/null +++ b/Framework/Collections/LinkedListElement.cs @@ -0,0 +1,117 @@ +/* + * Copyright (C) 2012-2017 CypherCore + * + * 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, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +namespace Framework.Collections +{ + public class LinkedListElement + { + internal LinkedListElement iNext; + internal LinkedListElement iPrev; + + public LinkedListElement() + { + iNext = iPrev = null; + } + + ~LinkedListElement() { delink(); } + + bool hasNext() { return (iNext != null && iNext.iNext != null); } + bool hasPrev() { return (iPrev != null && iPrev.iPrev != null); } + public bool isInList() { return (iNext != null && iPrev != null); } + + public LinkedListElement GetNextElement() { return hasNext() ? iNext : null; } + public LinkedListElement GetPrevElement() { return hasPrev() ? iPrev : null; } + + public void delink() + { + if (isInList()) + { + iNext.iPrev = iPrev; + iPrev.iNext = iNext; + iNext = null; + iPrev = null; + } + } + + public void insertBefore(LinkedListElement pElem) + { + pElem.iNext = this; + pElem.iPrev = iPrev; + iPrev.iNext = pElem; + iPrev = pElem; + } + + public void insertAfter(LinkedListElement pElem) + { + pElem.iPrev = this; + pElem.iNext = iNext; + iNext.iPrev = pElem; + iNext = pElem; + } + } + + public class LinkedListHead + { + LinkedListElement iFirst = new LinkedListElement(); + LinkedListElement iLast = new LinkedListElement(); + uint iSize; + + public LinkedListHead() + { + iSize = 0; + // create empty list + + iFirst.iNext = iLast; + iLast.iPrev = iFirst; + } + + public bool isEmpty() { return (!iFirst.iNext.isInList()); } + + public LinkedListElement GetFirstElement() { return (isEmpty() ? null : iFirst.iNext); } + public LinkedListElement GetLastElement() { return (isEmpty() ? null : iLast.iPrev); } + + public void insertFirst(LinkedListElement pElem) + { + iFirst.insertAfter(pElem); + } + + public void insertLast(LinkedListElement pElem) + { + iLast.insertBefore(pElem); + } + + public uint getSize() + { + if (iSize == 0) + { + uint result = 0; + LinkedListElement e = GetFirstElement(); + while (e != null) + { + ++result; + e = e.GetNextElement(); + } + return result; + } + else + return iSize; + } + + public void incSize() { ++iSize; } + public void decSize() { --iSize; } + } +} diff --git a/Framework/Collections/MultiMap.cs b/Framework/Collections/MultiMap.cs new file mode 100644 index 000000000..869c13c99 --- /dev/null +++ b/Framework/Collections/MultiMap.cs @@ -0,0 +1,508 @@ +/* + * Copyright (C) 2012-2017 CypherCore + * + * 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, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +using System.Linq; + +namespace System.Collections.Generic +{ + public sealed class MultiMap : IMultiMap, IDictionary + { + public MultiMap() { } + + public MultiMap(IEnumerable> initialData) + { + foreach (var item in initialData) + { + Add(item); + } + } + + public void Add(TKey key, TValue value) + { + if (!_interalStorage.ContainsKey(key)) + _interalStorage.Add(key, new List()); + + _interalStorage[key].Add(value); + } + + public void AddRange(TKey key, IEnumerable valueList) + { + if (!_interalStorage.ContainsKey(key)) + { + _interalStorage.Add(key, new List()); + } + foreach (TValue value in valueList) + { + _interalStorage[key].Add(value); + } + } + + public void Add(KeyValuePair item) + { + if (!_interalStorage.ContainsKey(item.Key)) + { + _interalStorage.Add(item.Key, new List()); + } + _interalStorage[item.Key].Add(item.Value); + } + + public bool Remove(TKey key) + { + return _interalStorage.Remove(key); + } + + public bool Remove(KeyValuePair item) + { + if (!ContainsKey(item.Key)) + return false; + + bool val = _interalStorage[item.Key].Remove(item.Value); + + if (!val) + return false; + + if (_interalStorage[item.Key].Empty()) + _interalStorage.Remove(item.Key); + + return true; + } + + public bool Remove(TKey key, TValue value) + { + if (!ContainsKey(key)) + return false; + + bool val = _interalStorage[key].Remove(value); + + if (!val) + return false; + + if (_interalStorage[key].Empty()) + _interalStorage.Remove(key); + + return true; + } + + public bool ContainsKey(TKey key) + { + return _interalStorage.ContainsKey(key); + } + + public bool Contains(KeyValuePair item) + { + List valueList; + if (_interalStorage.TryGetValue(item.Key, out valueList)) + return valueList.Contains(item.Value); + return false; + } + + public bool Contains(TKey key, TValue item) + { + if (!_interalStorage.ContainsKey(key)) return false; + return _interalStorage[key].Contains(item); + } + + public List LookupByKey(TKey key) + { + if (_interalStorage.ContainsKey(key)) + return _interalStorage[key].ToList(); + + return new List(); + } + + public List LookupByKey(object key) + { + TKey newkey = (TKey)Convert.ChangeType(key, typeof(TKey)); + if (_interalStorage.ContainsKey(newkey)) + return _interalStorage[newkey].ToList(); + + return new List(); + } + + bool IDictionary.TryGetValue(TKey key, out TValue value) + { + if (!_interalStorage.ContainsKey(key)) + { + value = default(TValue); + return false; + } + value = _interalStorage[key].Last(); + return true; + } + + TValue IDictionary.this[TKey key] + { + get + { + return _interalStorage[key].LastOrDefault(); + } + set + { + Add(key, value); + } + } + + public List this[TKey key] + { + get + { + if (!_interalStorage.ContainsKey(key)) + return new List(); + return _interalStorage[key]; + } + set + { + if (!_interalStorage.ContainsKey(key)) + _interalStorage.Add(key, value); + else + _interalStorage[key] = value; + } + } + + public ICollection Keys + { + get { return _interalStorage.Keys; } + } + + public ICollection Values + { + get + { + List retVal = new List(); + foreach (var item in _interalStorage) + { + retVal.AddRange(item.Value); + } + return retVal; + } + } + + public List> KeyValueList + { + get + { + List> retVal = new List>(); + foreach (var pair in _interalStorage) + { + foreach (var value in pair.Value) + retVal.Add(new KeyValuePair(pair.Key, value)); + } + return retVal; + } + } + + public void Clear() + { + _interalStorage.Clear(); + } + + public void CopyTo(KeyValuePair[] array, int arrayIndex) + { + if (array == null) + throw new ArgumentNullException("array"); + + if (arrayIndex < 0) + throw new ArgumentOutOfRangeException("arrayIndex", arrayIndex, "argument 'arrayIndex' cannot be negative"); + + if (arrayIndex >= array.Length || Count > array.Length - arrayIndex) + array = new KeyValuePair[Count]; + + int index = arrayIndex; + foreach (KeyValuePair pair in this) + array[index++] = new KeyValuePair(pair.Key, pair.Value); + + } + + public int Count + { + get + { + int count = 0; + foreach (var item in _interalStorage) + { + count += item.Value.Count; + } + return count; + } + } + + int ICollection>.Count + { + get { return _interalStorage.Count; } + } + + bool ICollection>.IsReadOnly + { + get { return false; } + } + + public IEnumerator> GetEnumerator() + { + return new MultiMapEnumerator(this); + } + + IEnumerator IEnumerable.GetEnumerator() + { + return new MultiMapEnumerator(this); + } + + private Dictionary> _interalStorage = new Dictionary>(); + } + + public sealed class SortedMultiMap : IMultiMap, IDictionary + { + public SortedMultiMap() { } + + public SortedMultiMap(IEnumerable> initialData) + { + foreach (var item in initialData) + { + Add(item); + } + } + + public void Add(TKey key, TValue value) + { + if (!_interalStorage.ContainsKey(key)) + _interalStorage.Add(key, new List()); + + _interalStorage[key].Add(value); + } + + public void AddRange(TKey key, IEnumerable valueList) + { + if (!_interalStorage.ContainsKey(key)) + { + _interalStorage.Add(key, new List()); + } + foreach (TValue value in valueList) + { + _interalStorage[key].Add(value); + } + } + + public void Add(KeyValuePair item) + { + if (!_interalStorage.ContainsKey(item.Key)) + { + _interalStorage.Add(item.Key, new List()); + } + _interalStorage[item.Key].Add(item.Value); + } + + public bool Remove(TKey key) + { + return _interalStorage.Remove(key); + } + + public bool Remove(KeyValuePair item) + { + if (!ContainsKey(item.Key)) + return false; + + bool val = _interalStorage[item.Key].Remove(item.Value); + + if (!val) + return false; + + if (_interalStorage[item.Key].Empty()) + _interalStorage.Remove(item.Key); + + return true; + } + + public bool Remove(TKey key, TValue value) + { + if (!ContainsKey(key)) + return false; + + bool val = _interalStorage[key].Remove(value); + + if (!val) + return false; + + if (_interalStorage[key].Empty()) + _interalStorage.Remove(key); + + return true; + } + + public bool ContainsKey(TKey key) + { + return _interalStorage.ContainsKey(key); + } + + public bool Contains(KeyValuePair item) + { + List valueList; + if (_interalStorage.TryGetValue(item.Key, out valueList)) + return valueList.Contains(item.Value); + return false; + } + + public bool Contains(TKey key, TValue item) + { + if (!_interalStorage.ContainsKey(key)) return false; + return _interalStorage[key].Contains(item); + } + + public List LookupByKey(TKey key) + { + if (_interalStorage.ContainsKey(key)) + return _interalStorage[key].ToList(); + + return new List(); + } + + public List LookupByKey(object key) + { + TKey newkey = (TKey)Convert.ChangeType(key, typeof(TKey)); + if (_interalStorage.ContainsKey(newkey)) + return _interalStorage[newkey].ToList(); + + return new List(); + } + + bool IDictionary.TryGetValue(TKey key, out TValue value) + { + if (!_interalStorage.ContainsKey(key)) + { + value = default(TValue); + return false; + } + value = _interalStorage[key].Last(); + return true; + } + + TValue IDictionary.this[TKey key] + { + get + { + return _interalStorage[key].LastOrDefault(); + } + set + { + Add(key, value); + } + } + + public List this[TKey key] + { + get + { + if (!_interalStorage.ContainsKey(key)) + return new List(); + return _interalStorage[key]; + } + set + { + if (!_interalStorage.ContainsKey(key)) + _interalStorage.Add(key, value); + else _interalStorage[key] = value; + } + } + + public ICollection Keys + { + get { return _interalStorage.Keys; } + } + + public ICollection Values + { + get + { + List retVal = new List(); + foreach (var item in _interalStorage) + { + retVal.AddRange(item.Value); + } + return retVal; + } + } + + public List> KeyValueList + { + get + { + List> retVal = new List>(); + foreach (var pair in _interalStorage) + { + foreach (var value in pair.Value) + retVal.Add(new KeyValuePair(pair.Key, value)); + } + return retVal; + } + } + + public void Clear() + { + _interalStorage.Clear(); + } + + public void CopyTo(KeyValuePair[] array, int arrayIndex) + { + if (array == null) + throw new ArgumentNullException("array"); + + if (arrayIndex < 0) + throw new ArgumentOutOfRangeException("arrayIndex", arrayIndex, "argument 'arrayIndex' cannot be negative"); + + if (arrayIndex >= array.Length || Count > array.Length - arrayIndex) + array = new KeyValuePair[Count]; + + int index = arrayIndex; + foreach (KeyValuePair pair in this) + array[index++] = new KeyValuePair(pair.Key, pair.Value); + + } + + public int Count + { + get + { + int count = 0; + foreach (var item in _interalStorage) + { + count += item.Value.Count; + } + return count; + } + } + + int ICollection>.Count + { + get { return _interalStorage.Count; } + } + + bool ICollection>.IsReadOnly + { + get { return false; } + } + + public IEnumerator> GetEnumerator() + { + return new SortedMultiMapEnumerator(this); + } + + IEnumerator IEnumerable.GetEnumerator() + { + return new SortedMultiMapEnumerator(this); + } + + private SortedDictionary> _interalStorage = new SortedDictionary>(); + } +} diff --git a/Framework/Collections/MultiMapEnumerator.cs b/Framework/Collections/MultiMapEnumerator.cs new file mode 100644 index 000000000..c3a1ceab3 --- /dev/null +++ b/Framework/Collections/MultiMapEnumerator.cs @@ -0,0 +1,129 @@ +/* + * Copyright (C) 2012-2017 CypherCore + * + * 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, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +namespace System.Collections.Generic +{ + public class MultiMapEnumerator : IEnumerator> + { + MultiMap _map; + IEnumerator _keyEnumerator; + IEnumerator _valueEnumerator; + + public MultiMapEnumerator(MultiMap map) + { + _map = map; + Reset(); + } + + object IEnumerator.Current + { + get + { + return Current; + } + } + + public KeyValuePair Current + { + get + { + return new KeyValuePair(_keyEnumerator.Current, _valueEnumerator.Current); + } + } + + public void Dispose() + { + _keyEnumerator = null; + _valueEnumerator = null; + _map = null; + } + + public bool MoveNext() + { + if (!_valueEnumerator.MoveNext()) + { + if (!_keyEnumerator.MoveNext()) + return false; + _valueEnumerator = _map[_keyEnumerator.Current].GetEnumerator(); + _valueEnumerator.MoveNext(); + return true; + } + return true; + } + + public void Reset() + { + _keyEnumerator = _map.Keys.GetEnumerator(); + _valueEnumerator = new List().GetEnumerator(); + } + } + + public class SortedMultiMapEnumerator : IEnumerator> + { + SortedMultiMap _map; + IEnumerator _keyEnumerator; + IEnumerator _valueEnumerator; + + public SortedMultiMapEnumerator(SortedMultiMap map) + { + _map = map; + Reset(); + } + + object IEnumerator.Current + { + get + { + return Current; + } + } + + public KeyValuePair Current + { + get + { + return new KeyValuePair(_keyEnumerator.Current, _valueEnumerator.Current); + } + } + + public void Dispose() + { + _keyEnumerator = null; + _valueEnumerator = null; + _map = null; + } + + public bool MoveNext() + { + if (!_valueEnumerator.MoveNext()) + { + if (!_keyEnumerator.MoveNext()) + return false; + _valueEnumerator = _map[_keyEnumerator.Current].GetEnumerator(); + _valueEnumerator.MoveNext(); + return true; + } + return true; + } + + public void Reset() + { + _keyEnumerator = _map.Keys.GetEnumerator(); + _valueEnumerator = new List().GetEnumerator(); + } + } +} diff --git a/Framework/Collections/StringArray.cs b/Framework/Collections/StringArray.cs new file mode 100644 index 000000000..33f328af3 --- /dev/null +++ b/Framework/Collections/StringArray.cs @@ -0,0 +1,59 @@ +/* + * Copyright (C) 2012-2017 CypherCore + * + * 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, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +using System; +using System.Collections; + +namespace Framework.Collections +{ + public class StringArray + { + public StringArray(int size) + { + _str = new string[size]; + + for (var i = 0; i < size; ++i) + _str[i] = ""; + } + + public StringArray(string str, params string[] separator) + { + _str = str.Split(separator, StringSplitOptions.RemoveEmptyEntries); + } + + public StringArray(string str, params char[] separator) + { + _str = str.Split(separator, StringSplitOptions.RemoveEmptyEntries); + } + + public string this[int index] + { + get { return _str[index]; } + set { _str[index] = value; } + } + + public IEnumerator GetEnumerator() + { + return _str.GetEnumerator(); + } + + public int Length { get { return _str.Length; } } + + string[] _str; + + } +} diff --git a/Framework/Configuration/ConfigManager.cs b/Framework/Configuration/ConfigManager.cs new file mode 100644 index 000000000..0f2285f7c --- /dev/null +++ b/Framework/Configuration/ConfigManager.cs @@ -0,0 +1,85 @@ +/* + * Copyright (C) 2012-2017 CypherCore + * + * 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, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +using Framework.Collections; +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Text; + +namespace Framework.Configuration +{ + public class ConfigMgr + { + public static bool Load(string fileName) + { + string path = "./" + fileName; + if (!File.Exists(path)) + { + Console.WriteLine("{0} doesn't exist!", fileName); + return false; + } + + string[] ConfigContent = File.ReadAllLines(path, Encoding.UTF8); + + int lineCounter = 0; + try + { + string name = string.Empty; + foreach (var line in ConfigContent) + { + lineCounter++; + if (line.StartsWith("#") || line.StartsWith("-") || string.IsNullOrEmpty(line)) + continue; + + var configOption = new StringArray(line, '='); + _configList.Add(configOption[0].Trim(), configOption[1].Replace("\"", "").Trim()); + } + } + catch + { + Console.WriteLine("Error in {0} on Line {1}", fileName, lineCounter); + return false; + } + + return true; + } + + public static T GetDefaultValue(string name, T defaultValue) + { + string temp = _configList.LookupByKey(name); + + var type = typeof(T).IsEnum ? typeof(T).GetEnumUnderlyingType() : typeof(T); + + if (temp.IsEmpty()) + return (T)Convert.ChangeType(defaultValue, type); + + if (Type.GetTypeCode(typeof(T)) == TypeCode.Boolean && temp.IsNumber()) + return (T)Convert.ChangeType(temp == "1", typeof(T)); + + return (T)Convert.ChangeType(temp, type); + } + + public static List GetKeysByString(string name) + { + return _configList.Where(p => p.Key.Contains(name)).Select(p => p.Key).ToList(); + } + + static Dictionary _configList = new Dictionary(); + } +} diff --git a/Framework/Constants/AccountConst.cs b/Framework/Constants/AccountConst.cs new file mode 100644 index 000000000..933ddeae7 --- /dev/null +++ b/Framework/Constants/AccountConst.cs @@ -0,0 +1,762 @@ +/* + * Copyright (C) 2012-2017 CypherCore + * + * 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, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +namespace Framework.Constants +{ + public enum AccountDataTypes + { + GlobalConfigCache = 0x00, + PerCharacterConfigCache = 0x01, + GlobalBindingsCache = 0x02, + PerCharacterBindingsCache = 0x03, + GlobalMacrosCache = 0x04, + PerCharacterMacrosCache = 0x05, + PerCharacterLayoutCache = 0x06, + PerCharacterChatCache = 0x07, + Max = 8, + + GlobalCacheMask = 0x15, + PerCharacterCacheMask = 0xEA + } + + public enum TutorialAction + { + Update = 0, + Clear = 1, + Reset = 2 + } + + public enum AccountTypes + { + Player = 0, + Moderator = 1, + GameMaster = 2, + Administrator = 3, + Console = 4 + } + + public enum RBACPermissions + { + None = 0, + InstantLogout = 1, + SkipQueue = 2, + JoinNormalBg = 3, + JoinRandomBg = 4, + JoinArenas = 5, + JoinDungeonFinder = 6, + // 7 - Reuse + // 8 - Reuse + // 9 - Reuse + UseCharacterTemplates = 10, + LogGmTrade = 11, + SkipCheckCharacterCreationDemonHunter = 12, + SkipCheckInstanceRequiredBosses = 13, + SkipCheckCharacterCreationTeammask = 14, + SkipCheckCharacterCreationClassmask = 15, + SkipCheckCharacterCreationRacemask = 16, + SkipCheckCharacterCreationReservedname = 17, + SkipCheckCharacterCreationDeathKnight = 18, + SkipCheckChatChannelReq = 19, + SkipCheckDisableMap = 20, + SkipCheckMoreTalentsThanAllowed = 21, + SkipCheckChatSpam = 22, + SkipCheckOverspeedPing = 23, + TwoSideCharacterCreation = 24, + TwoSideInteractionChat = 25, + TwoSideInteractionChannel = 26, + TwoSideInteractionMail = 27, + TwoSideWhoList = 28, + TwoSideAddFriend = 29, + CommandsSaveWithoutDelay = 30, + CommandsUseUnstuckWithArgs = 31, + CommandsBeAssignedTicket = 32, + CommandsNotifyCommandNotFoundError = 33, + CommandsAppearInGmList = 34, + WhoSeeAllSecLevels = 35, + CanFilterWhispers = 36, + ChatUseStaffBadge = 37, + ResurrectWithFullHps = 38, + RestoreSavedGmState = 39, + AllowGmFriend = 40, + UseStartGmLevel = 41, + OpcodeWorldTeleport = 42, + OpcodeWhois = 43, + ReceiveGlobalGmTextmessage = 44, + SilentlyJoinChannel = 45, + ChangeChannelNotModerator = 46, + CheckForLowerSecurity = 47, + CommandsPinfoCheckPersonalData = 48, + EmailConfirmForPassChange = 49, + MayCheckOwnEmail = 50, + AllowTwoSideTrade = 51, + + // Free Space For Core Permissions (Till 149) + // Roles (Permissions With Delegated Permissions) Use 199 And Descending + CommandRbac = 200, + CommandRbacAcc = 201, + CommandRbacAccPermList = 202, + CommandRbacAccPermGrant = 203, + CommandRbacAccPermDeny = 204, + CommandRbacAccPermRevoke = 205, + CommandRbacList = 206, + CommandBnetAccount = 207, + CommandBnetAccountCreate = 208, + CommandBnetAccountLockCountry = 209, + CommandBnetAccountLockIp = 210, + CommandBnetAccountPassword = 211, + CommandBnetAccountSet = 212, + CommandBnetAccountSetPassword = 213, + CommandBnetAccountLink = 214, + CommandBnetAccountUnlink = 215, + CommandBnetAccountCreateGame = 216, + CommandAccount = 217, + CommandAccountAddon = 218, + CommandAccountCreate = 219, + CommandAccountDelete = 220, + CommandAccountLock = 221, + CommandAccountLockCountry = 222, + CommandAccountLockIp = 223, + CommandAccountOnlineList = 224, + CommandAccountPassword = 225, + CommandAccountSet = 226, + CommandAccountSetAddon = 227, + CommandAccountSetGmlevel = 228, + CommandAccountSetPassword = 229, + CommandAchievement = 230, + CommandAchievementAdd = 231, + CommandArena = 232, + CommandArenaCaptain = 233, + CommandArenaCreate = 234, + CommandArenaDisband = 235, + CommandArenaInfo = 236, + CommandArenaLookup = 237, + CommandArenaRename = 238, + CommandBan = 239, + CommandBanAccount = 240, + CommandBanCharacter = 241, + CommandBanIp = 242, + CommandBanPlayeraccount = 243, + CommandBaninfo = 244, + CommandBaninfoAccount = 245, + CommandBaninfoCharacter = 246, + CommandBaninfoIp = 247, + CommandBanlist = 248, + CommandBanlistAccount = 249, + CommandBanlistCharacter = 250, + CommandBanlistIp = 251, + CommandUnban = 252, + CommandUnbanAccount = 253, + CommandUnbanCharacter = 254, + CommandUnbanIp = 255, + CommandUnbanPlayeraccount = 256, + CommandBf = 257, + CommandBfStart = 258, + CommandBfStop = 259, + CommandBfSwitch = 260, + CommandBfTimer = 261, + CommandBfEnable = 262, + CommandAccountEmail = 263, + CommandAccountSetSec = 264, + CommandAccountSetSecEmail = 265, + CommandAccountSetSecRegmail = 266, + CommandCast = 267, + CommandCastBack = 268, + CommandCastDist = 269, + CommandCastSelf = 270, + CommandCastTarget = 271, + CommandCastDest = 272, + CommandCharacter = 273, + CommandCharacterCustomize = 274, + CommandCharacterChangefaction = 275, + CommandCharacterChangerace = 276, + CommandCharacterDeleted = 277, + CommandCharacterDeletedDelete = 278, + CommandCharacterDeletedList = 279, + CommandCharacterDeletedRestore = 280, + CommandCharacterDeletedOld = 281, + CommandCharacterErase = 282, + CommandCharacterLevel = 283, + CommandCharacterRename = 284, + CommandCharacterReputation = 285, + CommandCharacterTitles = 286, + CommandLevelup = 287, + CommandPdump = 288, + CommandPdumpLoad = 289, + CommandPdumpWrite = 290, + CommandCheat = 291, + CommandCheatCasttime = 292, + CommandCheatCooldown = 293, + CommandCheatExplore = 294, + CommandCheatGod = 295, + CommandCheatPower = 296, + CommandCheatStatus = 297, + CommandCheatTaxi = 298, + CommandCheatWaterwalk = 299, + CommandDebug = 300, + CommandDebugAnim = 301, + CommandDebugAreatriggers = 302, + CommandDebugArena = 303, + CommandDebugBg = 304, + CommandDebugEntervehicle = 305, + CommandDebugGetitemstate = 306, + CommandDebugGetitemvalue = 307, + CommandDebugGetvalue = 308, + CommandDebugHostil = 309, + CommandDebugItemexpire = 310, + CommandDebugLootrecipient = 311, + CommandDebugLos = 312, + CommandDebugMod32value = 313, + CommandDebugMoveflags = 314, + CommandDebugPlay = 315, + CommandDebugPlayCinematic = 316, + CommandDebugPlayMovie = 317, + CommandDebugPlaySound = 318, + CommandDebugSend = 319, + CommandDebugSendBuyerror = 320, + CommandDebugSendChannelnotify = 321, + CommandDebugSendChatmessage = 322, + CommandDebugSendEquiperror = 323, + CommandDebugSendLargepacket = 324, + CommandDebugSendOpcode = 325, + CommandDebugSendQinvalidmsg = 326, + CommandDebugSendQpartymsg = 327, + CommandDebugSendSellerror = 328, + CommandDebugSendSetphaseshift = 329, + CommandDebugSendSpellfail = 330, + CommandDebugSetaurastate = 331, + CommandDebugSetbit = 332, + CommandDebugSetitemvalue = 333, + CommandDebugSetvalue = 334, + CommandDebugSetvid = 335, + CommandDebugSpawnvehicle = 336, + CommandDebugThreat = 337, + CommandDebugUpdate = 338, + CommandDebugUws = 339, + CommandWpgps = 340, + CommandDeserter = 341, + CommandDeserterBg = 342, + CommandDeserterBgAdd = 343, + CommandDeserterBgRemove = 344, + CommandDeserterInstance = 345, + CommandDeserterInstanceAdd = 346, + CommandDeserterInstanceRemove = 347, + CommandDisable = 348, + CommandDisableAdd = 349, + CommandDisableAddCriteria = 350, + CommandDisableAddBattleground = 351, + CommandDisableAddMap = 352, + CommandDisableAddMmap = 353, + CommandDisableAddOutdoorpvp = 354, + CommandDisableAddQuest = 355, + CommandDisableAddSpell = 356, + CommandDisableAddVmap = 357, + CommandDisableRemove = 358, + CommandDisableRemoveCriteria = 359, + CommandDisableRemoveBattleground = 360, + CommandDisableRemoveMap = 361, + CommandDisableRemoveMmap = 362, + CommandDisableRemoveOutdoorpvp = 363, + CommandDisableRemoveQuest = 364, + CommandDisableRemoveSpell = 365, + CommandDisableRemoveVmap = 366, + CommandEvent = 367, + CommandEventActivelist = 368, + CommandEventStart = 369, + CommandEventStop = 370, + CommandGm = 371, + CommandGmChat = 372, + CommandGmFly = 373, + CommandGmIngame = 374, + CommandGmList = 375, + CommandGmVisible = 376, + CommandGo = 377, + CommandGoCreature = 378, + CommandGoGraveyard = 379, + CommandGoGrid = 380, + CommandGoObject = 381, + CommandGoTaxinode = 382, + // 383 reuse + CommandGoTrigger = 384, + CommandGoXyz = 385, + CommandGoZonexy = 386, + CommandGobject = 387, + CommandGobjectActivate = 388, + CommandGobjectAdd = 389, + CommandGobjectAddTemp = 390, + CommandGobjectDelete = 391, + CommandGobjectInfo = 392, + CommandGobjectMove = 393, + CommandGobjectNear = 394, + CommandGobjectSet = 395, + CommandGobjectSetPhase = 396, + CommandGobjectSetState = 397, + CommandGobjectTarget = 398, + CommandGobjectTurn = 399, + CommandDebugTransport = 400, + CommandGuild = 401, + CommandGuildCreate = 402, + CommandGuildDelete = 403, + CommandGuildInvite = 404, + CommandGuildUninvite = 405, + CommandGuildRank = 406, + CommandGuildRename = 407, + CommandHonor = 408, + CommandHonorAdd = 409, + CommandHonorAddKill = 410, + CommandHonorUpdate = 411, + CommandInstance = 412, + CommandInstanceListbinds = 413, + CommandInstanceUnbind = 414, + CommandInstanceStats = 415, + CommandInstanceSavedata = 416, + CommandLearn = 417, + CommandLearnAll = 418, + CommandLearnAllMy = 419, + CommandLearnAllMyClass = 420, + CommandLearnAllMyPettalents = 421, + CommandLearnAllMySpells = 422, + CommandLearnAllMyTalents = 423, + CommandLearnAllGm = 424, + CommandLearnAllCrafts = 425, + CommandLearnAllDefault = 426, + CommandLearnAllLang = 427, + CommandLearnAllRecipes = 428, + CommandUnlearn = 429, + CommandLfg = 430, + CommandLfgPlayer = 431, + CommandLfgGroup = 432, + CommandLfgQueue = 433, + CommandLfgClean = 434, + CommandLfgOptions = 435, + CommandList = 436, + CommandListCreature = 437, + CommandListItem = 438, + CommandListObject = 439, + CommandListAuras = 440, + CommandListMail = 441, + CommandLookup = 442, + CommandLookupArea = 443, + CommandLookupCreature = 444, + CommandLookupEvent = 445, + CommandLookupFaction = 446, + CommandLookupItem = 447, + CommandLookupItemset = 448, + CommandLookupObject = 449, + CommandLookupQuest = 450, + CommandLookupPlayer = 451, + CommandLookupPlayerIp = 452, + CommandLookupPlayerAccount = 453, + CommandLookupPlayerEmail = 454, + CommandLookupSkill = 455, + CommandLookupSpell = 456, + CommandLookupSpellId = 457, + CommandLookupTaxinode = 458, + CommandLookupTele = 459, + CommandLookupTitle = 460, + CommandLookupMap = 461, + CommandAnnounce = 462, + CommandChannel = 463, + CommandChannelSet = 464, + CommandChannelSetOwnership = 465, + CommandGmannounce = 466, + CommandGmnameannounce = 467, + CommandGmnotify = 468, + CommandNameannounce = 469, + CommandNotify = 470, + CommandWhispers = 471, + CommandGroup = 472, + CommandGroupLeader = 473, + CommandGroupDisband = 474, + CommandGroupRemove = 475, + CommandGroupJoin = 476, + CommandGroupList = 477, + CommandGroupSummon = 478, + CommandPet = 479, + CommandPetCreate = 480, + CommandPetLearn = 481, + CommandPetUnlearn = 482, + CommandSend = 483, + CommandSendItems = 484, + CommandSendMail = 485, + CommandSendMessage = 486, + CommandSendMoney = 487, + CommandAdditem = 488, + CommandAdditemset = 489, + CommandAppear = 490, + CommandAura = 491, + CommandBank = 492, + CommandBindsight = 493, + CommandCombatstop = 494, + CommandCometome = 495, + CommandCommands = 496, + CommandCooldown = 497, + CommandDamage = 498, + CommandDev = 499, + CommandDie = 500, + CommandDismount = 501, + CommandDistance = 502, + CommandFlusharenapoints = 503, + CommandFreeze = 504, + CommandGps = 505, + CommandGuid = 506, + CommandHelp = 507, + CommandHidearea = 508, + CommandItemmove = 509, + CommandKick = 510, + CommandLinkgrave = 511, + CommandListfreeze = 512, + CommandMaxskill = 513, + CommandMovegens = 514, + CommandMute = 515, + CommandNeargrave = 516, + CommandPinfo = 517, + CommandPlayall = 518, + CommandPossess = 519, + CommandRecall = 520, + CommandRepairitems = 521, + CommandRespawn = 522, + CommandRevive = 523, + CommandSaveall = 524, + CommandSave = 525, + CommandSetskill = 526, + CommandShowarea = 527, + CommandSummon = 528, + CommandUnaura = 529, + CommandUnbindsight = 530, + CommandUnfreeze = 531, + CommandUnmute = 532, + CommandUnpossess = 533, + CommandUnstuck = 534, + CommandWchange = 535, + CommandMmap = 536, + CommandMmapLoadedtiles = 537, + CommandMmapLoc = 538, + CommandMmapPath = 539, + CommandMmapStats = 540, + CommandMmapTestarea = 541, + CommandMorph = 542, + CommandDemorph = 543, + CommandModify = 544, + CommandModifyArenapoints = 545, + CommandModifyBit = 546, + CommandModifyDrunk = 547, + CommandModifyEnergy = 548, + CommandModifyFaction = 549, + CommandModifyGender = 550, + CommandModifyHonor = 551, + CommandModifyHp = 552, + CommandModifyMana = 553, + CommandModifyMoney = 554, + CommandModifyMount = 555, + CommandModifyPhase = 556, + CommandModifyRage = 557, + CommandModifyReputation = 558, + CommandModifyRunicpower = 559, + CommandModifyScale = 560, + CommandModifySpeed = 561, + CommandModifySpeedAll = 562, + CommandModifySpeedBackwalk = 563, + CommandModifySpeedFly = 564, + CommandModifySpeedWalk = 565, + CommandModifySpeedSwim = 566, + CommandModifySpell = 567, + CommandModifyStandstate = 568, + CommandModifyTalentpoints = 569, + CommandNpc = 570, + CommandNpcAdd = 571, + CommandNpcAddFormation = 572, + CommandNpcAddItem = 573, + CommandNpcAddMove = 574, + CommandNpcAddTemp = 575, + CommandNpcDelete = 576, + CommandNpcDeleteItem = 577, + CommandNpcFollow = 578, + CommandNpcFollowStop = 579, + CommandNpcSet = 580, + CommandNpcSetAllowmove = 581, + CommandNpcSetEntry = 582, + CommandNpcSetFactionid = 583, + CommandNpcSetFlag = 584, + CommandNpcSetLevel = 585, + CommandNpcSetLink = 586, + CommandNpcSetModel = 587, + CommandNpcSetMovetype = 588, + CommandNpcSetPhase = 589, + CommandNpcSetSpawndist = 590, + CommandNpcSetSpawntime = 591, + CommandNpcSetData = 592, + CommandNpcInfo = 593, + CommandNpcNear = 594, + CommandNpcMove = 595, + CommandNpcPlayemote = 596, + CommandNpcSay = 597, + CommandNpcTextemote = 598, + CommandNpcWhisper = 599, + CommandNpcYell = 600, + CommandNpcTame = 601, + CommandQuest = 602, + CommandQuestAdd = 603, + CommandQuestComplete = 604, + CommandQuestRemove = 605, + CommandQuestReward = 606, + CommandReload = 607, + CommandReloadAccessRequirement = 608, + CommandReloadCriteriaData = 609, + CommandReloadAchievementReward = 610, + CommandReloadAll = 611, + CommandReloadAllAchievement = 612, + CommandReloadAllArea = 613, + CommandReloadBroadcastText = 614, + CommandReloadAllGossip = 615, + CommandReloadAllItem = 616, + CommandReloadAllLocales = 617, + CommandReloadAllLoot = 618, + CommandReloadAllNpc = 619, + CommandReloadAllQuest = 620, + CommandReloadAllScripts = 621, + CommandReloadAllSpell = 622, + CommandReloadAreatriggerInvolvedrelation = 623, + CommandReloadAreatriggerTavern = 624, + CommandReloadAreatriggerTeleport = 625, + CommandReloadAuctions = 626, + CommandReloadAutobroadcast = 627, + CommandReloadCommand = 628, + CommandReloadConditions = 629, + CommandReloadConfig = 630, + CommandReloadBattlegroundTemplate = 631, + CommandMutehistory = 632, + CommandReloadCreatureLinkedRespawn = 633, + CommandReloadCreatureLootTemplate = 634, + CommandReloadCreatureOnkillReputation = 635, + CommandReloadCreatureQuestender = 636, + CommandReloadCreatureQueststarter = 637, + CommandReloadCreatureSummonGroups = 638, + CommandReloadCreatureTemplate = 639, + CommandReloadCreatureText = 640, + CommandReloadDisables = 641, + CommandReloadDisenchantLootTemplate = 642, + CommandReloadEventScripts = 643, + CommandReloadFishingLootTemplate = 644, + CommandReloadGraveyardZone = 645, + CommandReloadGameTele = 646, + CommandReloadGameobjectQuestender = 647, + CommandReloadGameobjectQuestLootTemplate = 648, + CommandReloadGameobjectQueststarter = 649, + CommandReloadSupportSystem = 650, + CommandReloadGossipMenu = 651, + CommandReloadGossipMenuOption = 652, + CommandReloadItemEnchantmentTemplate = 653, + CommandReloadItemLootTemplate = 654, + CommandReloadItemSetNames = 655, + CommandReloadLfgDungeonRewards = 656, + CommandReloadLocalesAchievementReward = 657, + CommandReloadLocalesCreature = 658, + CommandReloadLocalesCreatureText = 659, + CommandReloadLocalesGameobject = 660, + CommandReloadLocalesGossipMenuOption = 661, + // 662 Unused + CommandReloadLocalesItemSetName = 663, + // 664 Unused + CommandReloadLocalesPageText = 665, + CommandReloadLocalesPointsOfInterest = 666, + CommandReloadQuestLocale = 667, + CommandReloadMailLevelReward = 668, + CommandReloadMailLootTemplate = 669, + CommandReloadMillingLootTemplate = 670, + CommandReloadNpcSpellclickSpells = 671, + CommandReloadNpcTrainer = 672, + CommandReloadNpcVendor = 673, + CommandReloadPageText = 674, + CommandReloadPickpocketingLootTemplate = 675, + CommandReloadPointsOfInterest = 676, + CommandReloadProspectingLootTemplate = 677, + CommandReloadQuestPoi = 678, + CommandReloadQuestTemplate = 679, + CommandReloadRbac = 680, + CommandReloadReferenceLootTemplate = 681, + CommandReloadReservedName = 682, + CommandReloadReputationRewardRate = 683, + CommandReloadSpilloverTemplate = 684, + CommandReloadSkillDiscoveryTemplate = 685, + CommandReloadSkillExtraItemTemplate = 686, + CommandReloadSkillFishingBaseLevel = 687, + CommandReloadSkinningLootTemplate = 688, + CommandReloadSmartScripts = 689, + CommandReloadSpellRequired = 690, + CommandReloadSpellArea = 691, + // 692 Unused + CommandReloadSpellGroup = 693, + CommandReloadSpellLearnSpell = 694, + CommandReloadSpellLootTemplate = 695, + CommandReloadSpellLinkedSpell = 696, + CommandReloadSpellPetAuras = 697, + CommandReloadSpellProcEvent = 698, + CommandReloadSpellProc = 699, + CommandReloadSpellScripts = 700, + CommandReloadSpellTargetPosition = 701, + CommandReloadSpellThreats = 702, + CommandReloadSpellGroupStackRules = 703, + CommandReloadCypherString = 704, + CommandReloadWardenAction = 705, + CommandReloadWaypointScripts = 706, + CommandReloadWaypointData = 707, + CommandReloadVehicleAccesory = 708, + CommandReloadVehicleTemplateAccessory = 709, + CommandReset = 710, + CommandResetAchievements = 711, + CommandResetHonor = 712, + CommandResetLevel = 713, + CommandResetSpells = 714, + CommandResetStats = 715, + CommandResetTalents = 716, + CommandResetAll = 717, + CommandServer = 718, + CommandServerCorpses = 719, + CommandServerExit = 720, + CommandServerIdlerestart = 721, + CommandServerIdlerestartCancel = 722, + CommandServerIdleshutdown = 723, + CommandServerIdleshutdownCancel = 724, + CommandServerInfo = 725, + CommandServerPlimit = 726, + CommandServerRestart = 727, + CommandServerRestartCancel = 728, + CommandServerSet = 729, + CommandServerSetClosed = 730, + CommandServerSetDifftime = 731, + CommandServerSetLoglevel = 732, + CommandServerSetMotd = 733, + CommandServerShutdown = 734, + CommandServerShutdownCancel = 735, + CommandServerMotd = 736, + CommandTele = 737, + CommandTeleAdd = 738, + CommandTeleDel = 739, + CommandTeleName = 740, + CommandTeleGroup = 741, + CommandTicket = 742, + // 743 - 752 reuse + CommandTicketReset = 753, + // 754 - 756 reuse + CommandTicketTogglesystem = 757, + // 758 - 760 reuse + CommandTitles = 761, + CommandTitlesAdd = 762, + CommandTitlesCurrent = 763, + CommandTitlesRemove = 764, + CommandTitlesSet = 765, + CommandTitlesSetMask = 766, + CommandWp = 767, + CommandWpAdd = 768, + CommandWpEvent = 769, + CommandWpLoad = 770, + CommandWpModify = 771, + CommandWpUnload = 772, + CommandWpReload = 773, + CommandWpShow = 774, + CommandModifyCurrency = 775, // Only 4.3.4 + CommandDebugPhase = 776, // Only 4.3.4 + CommandMailbox = 777, + CommandAhbot = 778, + CommandAhbotItems = 779, + CommandAhbotItemsGray = 780, + CommandAhbotItemsWhite = 781, + CommandAhbotItemsGreen = 782, + CommandAhbotItemsBlue = 783, + CommandAhbotItemsPurple = 784, + CommandAhbotItemsOrange = 785, + CommandAhbotItemsYellow = 786, + CommandAhbotRatio = 787, + CommandAhbotRatioAlliance = 788, + CommandAhbotRatioHorde = 789, + CommandAhbotRatioNeutral = 790, + CommandAhbotRebuild = 791, + CommandAhbotReload = 792, + CommandAhbotStatus = 793, + CommandGuildInfo = 794, + CommandInstanceSetBossState = 795, + CommandInstanceGetBossState = 796, + CommandPvpstats = 797, + CommandModifyXp = 798, + CommandGoBugTicket = 799, + CommandGoComplaintTicket = 800, + CommandGoSuggestionTicket = 801, + CommandTicketBug = 802, + CommandTicketComplaint = 803, + CommandTicketSuggestion = 804, + CommandTicketBugAssign = 805, + CommandTicketBugClose = 806, + CommandTicketBugClosedlist = 807, + CommandTicketBugComment = 808, + CommandTicketBugDelete = 809, + CommandTicketBugList = 810, + CommandTicketBugUnassign = 811, + CommandTicketBugView = 812, + CommandTicketComplaintAssign = 813, + CommandTicketComplaintClose = 814, + CommandTicketComplaintClosedlist = 815, + CommandTicketComplaintComment = 816, + CommandTicketComplaintDelete = 817, + CommandTicketComplaintList = 818, + CommandTicketComplaintUnassign = 819, + CommandTicketComplaintView = 820, + CommandTicketSuggestionAssign = 821, + CommandTicketSuggestionClose = 822, + CommandTicketSuggestionClosedlist = 823, + CommandTicketSuggestionComment = 824, + CommandTicketSuggestionDelete = 825, + CommandTicketSuggestionList = 826, + CommandTicketSuggestionUnassign = 827, + CommandTicketSuggestionView = 828, + CommandTicketResetAll = 829, + CommandBnetAccountListGameAccounts = 830, + CommandTicketResetBug = 831, + CommandTicketResetComplaint = 832, + CommandTicketResetSuggestion = 833, + CommandGoQuest = 834, + CommandDebugLoadcells = 835, + CommandDebugBoundary = 836, + CommandNpcEvade = 837, + CommandPetLevel = 838, + CommandServerShutdownForce = 839, + CommandServerRestartForce = 840, + CommandNearGraveyard = 841, + CommandReloadCharacterTemplate = 842, + CommandReloadQuestGreeting = 843, + CommandScene = 844, + CommandSceneDedug = 845, + CommandScenePlay = 846, + CommandScenePlayPackage = 847, + CommandSceneCancel = 848, + CommandListScenes = 849, + CommandReloacSceneTemplate = 850, + CommandReloadAreatriggerTemplate = 851, + CommandGoOffset = 852, + CommandReloadConversationTemplate = 853, + CommandDebugConversation = 854, + + // Custom Permissions 1000+ + Max + } + + public enum MountStatusFlags + { + None = 0x00, + NeedsFanfare = 0x01, + IsFavorite = 0x02 + } +} diff --git a/Framework/Constants/AchievementConst.cs b/Framework/Constants/AchievementConst.cs new file mode 100644 index 000000000..56830c440 --- /dev/null +++ b/Framework/Constants/AchievementConst.cs @@ -0,0 +1,440 @@ +/* + * Copyright (C) 2012-2017 CypherCore + * + * 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, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +namespace Framework.Constants +{ + public enum AchievementFaction : sbyte + { + Horde = 0, + Alliance = 1, + Any = -1, + } + + public enum CriteriaTreeFlags : ushort + { + ProgressBar = 0x0001, + ProgressIsDate = 0x0004, + ShowCurrencyIcon = 0x0008, + AllianceOnly = 0x0200, + HordeOnly = 0x0400, + ShowRequiredCount = 0x0800 + } + + public enum CriteriaTreeOperator + { + Single = 0, + SinglerNotCompleted = 1, + All = 4, + SumChildren = 5, + MaxChild = 6, + CountDirectChildren = 7, + Any = 8, + SumChildrenWeight = 9 + } + + public enum AchievementFlags + { + Counter = 0x01, + Hidden = 0x02, + PlayNoVisual = 0x04, + Summ = 0x08, + MaxUsed = 0x10, + ReqCount = 0x20, + Average = 0x40, + Bar = 0x80, + RealmFirstReach = 0x100, + RealmFirstKill = 0x200, + Unk3 = 0x400, + HideIncomplete = 0x800, + ShowInGuildNews = 0x1000, + ShowInGuildHeader = 0x2000, + Guild = 0x4000, + ShowGuildMembers = 0x8000, + ShowCriteriaMembers = 0x10000, + Account = 0x20000, + Unk5 = 0x00040000, + HideZeroCounter = 0x00080000, + TrackingFlag = 0x00100000 + } + + public enum CriteriaFlagsCu + { + Player = 0x1, + Account = 0x2, + Guild = 0x4, + Scenario = 0x8 + } + + public enum CriteriaCondition + { + None = 0, + NoDeath = 1, + Unk2 = 2, + BgMap = 3, + NoLose = 4, + Unk5 = 5, + Unk8 = 8, + NoSpellHit = 9, + NotInGroup = 10, + Unk13 = 13 + } + + public enum CriteriaAdditionalCondition + { + SourceDrunkValue = 1, + Unk2 = 2, + ItemLevel = 3, + TargetCreatureEntry = 4, + TargetMustBePlayer = 5, + TargetMustBeDead = 6, + TargetMustBeEnemy = 7, + SourceHasAura = 8, + TargetHasAura = 10, + TargetHasAuraType = 11, + ItemQualityMin = 14, + ItemQualityEquals = 15, + Unk16 = 16, + SourceAreaOrZone = 17, + TargetAreaOrZone = 18, + MapDifficultyOld = 20, + TargetCreatureYieldsXp = 21, + ArenaType = 24, + SourceRace = 25, + SourceClass = 26, + TargetRace = 27, + TargetClass = 28, + MaxGroupMembers = 29, + TargetCreatureType = 30, + SourceMap = 32, + ItemClass = 33, + ItemSubclass = 34, + CompleteQuestNotInGroup = 35, + MinPersonalRating = 37, + TitleBitIndex = 38, + SourceLevel = 39, + TargetLevel = 40, + TargetZone = 41, + TargetHealthPercentBelow = 46, + Unk55 = 55, + MinAchievementPoints = 56, + RequiresLfgGroup = 58, + Unk60 = 60, + RequiresGuildGroup = 61, + GuildReputation = 62, + RatedBattleground = 63, + RatedBattlegroundRating = 64, + ProjectRarity = 65, + ProjectRace = 66, + WorldState = 67, // Nyi + MapDifficulty = 68, // Nyi + PlayerLevel = 69, // Nyi + TargetPlayerLevel = 70, // Nyi + //PlayerLevelOnAccount = 71, // Not Verified + //Unk73 = 73, // References Another Modifier Tree Id + ScenarioId = 74, // Nyi + BattlePetFamily = 78, // Nyi + BattlePetHealthPct = 79, // Nyi + //Unk80 = 80 // Something To Do With World Bosses + BattlePetEntry = 81, // Nyi + //BattlePetEntryId = 82, // Some Sort Of Data Id? + ChallengeModeMedal = 83, // NYI + //CRITERIA_ADDITIONAL_CONDITION_UNK84 = 84, // Quest id + //CRITERIA_ADDITIONAL_CONDITION_UNK86 = 86, // Some external event id + //CRITERIA_ADDITIONAL_CONDITION_UNK87 = 87, // Achievement id + BattlePetSpecies = 91, + GarrisonFollowerEntry = 144, + GarrisonFollowerQuality = 145, + GarrisonFollowerLevel = 146, + GarrisonRareMission = 147, // NYI + GarrisonBuildingLevel = 149, // NYI + GarrisonMissionType = 167, // NYI + PLayerItemLevel = 169, // NYI + GarrisonFollowILvl = 184, + HonorLevel = 193, + PrestigeLevel = 194 + } + + public enum CriteriaFlags + { + ShowProgressBar = 0x01, + Hidden = 0x02, + FailAchievement = 0x04, + ResetOnStart = 0x08, + IsDate = 0x10, + MoneyCounter = 0x20 + } + + public enum CriteriaTimedTypes : byte + { + Event = 1, // Timer Is Started By Internal Event With Id In Timerstartevent + Quest = 2, // Timer Is Started By Accepting Quest With Entry In Timerstartevent + SpellCaster = 5, // Timer Is Started By Casting A Spell With Entry In Timerstartevent + SpellTarget = 6, // Timer Is Started By Being Target Of Spell With Entry In Timerstartevent + Creature = 7, // Timer Is Started By Killing Creature With Entry In Timerstartevent + Item = 9, // Timer Is Started By Using Item With Entry In Timerstartevent + Unk = 10, // Unknown + Unk2 = 13, // Unknown + ScenarioStage = 14, // Timer is started by changing stages in a scenario + + Max + } + + public enum CriteriaTypes : byte + { + KillCreature = 0, + WinBg = 1, + // 2 - unused (Legion - 23420) + CompleteArchaeologyProjects = 3, // Struct { Uint32 Itemcount; } + SurveyGameobject = 4, + ReachLevel = 5, + ClearDigsite = 6, + ReachSkillLevel = 7, + CompleteAchievement = 8, + CompleteQuestCount = 9, + CompleteDailyQuestDaily = 10, // You Have To Complete A Daily Quest X Times In A Row + CompleteQuestsInZone = 11, + Currency = 12, + DamageDone = 13, + CompleteDailyQuest = 14, + CompleteBattleground = 15, + DeathAtMap = 16, + Death = 17, + DeathInDungeon = 18, + CompleteRaid = 19, + KilledByCreature = 20, + ManualCompleteCriteria = 21, + CompleteChallengeModeGuild = 22, + KilledByPlayer = 23, + FallWithoutDying = 24, + // 25 - unused (Legion - 23420) + DeathsFrom = 26, + CompleteQuest = 27, + BeSpellTarget = 28, + CastSpell = 29, + BgObjectiveCapture = 30, + HonorableKillAtArea = 31, + WinArena = 32, + PlayArena = 33, + LearnSpell = 34, + HonorableKill = 35, + OwnItem = 36, + WinRatedArena = 37, + HighestTeamRating = 38, + HighestPersonalRating = 39, + LearnSkillLevel = 40, + UseItem = 41, + LootItem = 42, + ExploreArea = 43, + OwnRank = 44, + BuyBankSlot = 45, + GainReputation = 46, + GainExaltedReputation = 47, + VisitBarberShop = 48, + EquipEpicItem = 49, + RollNeedOnLoot = 50, /// Todo Itemlevel Is Mentioned In Text But Not Present In Dbc + RollGreedOnLoot = 51, + HkClass = 52, + HkRace = 53, + DoEmote = 54, + HealingDone = 55, + GetKillingBlows = 56, /// Todo In Some Cases Map Not Present, And In Some Cases Need Do Without Die + EquipItem = 57, + // 58 - unused (Legion - 23420) + MoneyFromVendors = 59, + GoldSpentForTalents = 60, + NumberOfTalentResets = 61, + MoneyFromQuestReward = 62, + GoldSpentForTravelling = 63, + DefeatCreatureGroup = 64, + GoldSpentAtBarber = 65, + GoldSpentForMail = 66, + LootMoney = 67, + UseGameobject = 68, + BeSpellTarget2 = 69, + SpecialPvpKill = 70, + CompleteChallengeMode = 71, + FishInGameobject = 72, + SendEvent = 73, + OnLogin = 74, + LearnSkilllineSpells = 75, + WinDuel = 76, + LoseDuel = 77, + KillCreatureType = 78, + CookRecipesGuild = 79, + GoldEarnedByAuctions = 80, + EarnPetBattleAchievementPoints = 81, + CreateAuction = 82, + HighestAuctionBid = 83, + WonAuctions = 84, + HighestAuctionSold = 85, + HighestGoldValueOwned = 86, + GainReveredReputation = 87, + GainHonoredReputation = 88, + KnownFactions = 89, + LootEpicItem = 90, + ReceiveEpicItem = 91, + SendEventScenario = 92, + RollNeed = 93, + RollGreed = 94, + ReleaseSpirit = 95, + OwnPet = 96, + GarrisonCompleteDungeonEncounter = 97, + // 98 - unused (Legion - 23420) + // 99 - unused (Legion - 23420) + // 100 - unused (Legion - 23420) + HighestHitDealt = 101, + HighestHitReceived = 102, + TotalDamageReceived = 103, + HighestHealCasted = 104, + TotalHealingReceived = 105, + HighestHealingReceived = 106, + QuestAbandoned = 107, + FlightPathsTaken = 108, + LootType = 109, + CastSpell2 = 110, /// Todo Target Entry Is Missing + // 111 - unused (Legion - 23420) + LearnSkillLine = 112, + EarnHonorableKill = 113, + AcceptedSummonings = 114, + EarnAchievementPoints = 115, + // 116 - unused (Legion - 23420) + // 117 - unused (Legion - 23420) + CompleteLfgDungeon = 118, + UseLfdToGroupWithPlayers = 119, + LfgVoteKicksInitiatedByPlayer = 120, + LfgVoteKicksNotInitByPlayer = 121, + BeKickedFromLfg = 122, + LfgLeaves = 123, + SpentGoldGuildRepairs = 124, + ReachGuildLevel = 125, + CraftItemsGuild = 126, + CatchFromPool = 127, + BuyGuildBankSlots = 128, + EarnGuildAchievementPoints = 129, + WinRatedBattleground = 130, + // 131 - unused (Legion - 23420) + ReachBgRating = 132, + BuyGuildTabard = 133, + CompleteQuestsGuild = 134, + HonorableKillsGuild = 135, + KillCreatureTypeGuild = 136, + CountOfLfgQueueBoostsByTank = 137, + CompleteGuildChallengeType = 138, //Struct { Flag Flag; Uint32 Count; } 1: Guild Dungeon, 2:Guild Challenge, 3:Guild Battlefield + CompleteGuildChallenge = 139, //Struct { Uint32 Count; } Guild Challenge + // 140 - 1 criteria (16883), unused (Legion - 23420) + // 141 - 1 criteria (16884), unused (Legion - 23420) + // 142 - 1 criteria (16881), unused (Legion - 23420) + // 143 - 1 criteria (16882), unused (Legion - 23420) + // 144 - 1 criteria (17386), unused (Legion - 23420) + LfrDungeonsCompleted = 145, + LfrLeaves = 146, + LfrVoteKicksInitiatedByPlayer = 147, + LfrVoteKicksNotInitByPlayer = 148, + BeKickedFromLfr = 149, + CountOfLfrQueueBoostsByTank = 150, + CompleteScenarioCount = 151, + CompleteScenario = 152, + ReachAreatriggerWithActionset = 153, + // 154 - unused (Legion - 23420) + OwnBattlePet = 155, + OwnBattlePetCount = 156, + CaptureBattlePet = 157, + WinPetBattle = 158, + // 159 - 2 criterias (22312,22314), unused (Legion - 23420) + LevelBattlePet = 160, + CaptureBattlePetCredit = 161, // Triggers A Quest Credit + LevelBattlePetCredit = 162, // Triggers A Quest Credit + EnterArea = 163, // Triggers A Quest Credit + LeaveArea = 164, // Triggers A Quest Credit + CompleteDungeonEncounter = 165, + // 166 - unused (Legion - 23420) + PlaceGarrisonBuilding = 167, + UpgradeGarrisonBuilding = 168, + ConstructGarrisonBuilding = 169, + UpgradeGarrison = 170, + StartGarrisonMission = 171, + StartOrderHallMission = 172, + CompleteGarrisonMissionCount = 173, + CompleteGarrisonMission = 174, + RecruitGarrisonFollowerCount = 175, + RecruitGarrisonFollower = 176, + // 177 - 0 criterias (Legion - 23420) + LearnGarrisonBlueprintCount = 178, + // 179 - 0 criterias (Legion - 23420) + // 180 - 0 criterias (Legion - 23420) + // 181 - 0 criterias (Legion - 23420) + CompleteGarrisonShipment = 182, + RaiseGarrisonFollowerItemLevel = 183, + RaiseGarrisonFollowerLevel = 184, + OwnToy = 185, + OwnToyCount = 186, + RecruitGarrisonFollowerWithQuality = 187, + // 188 - 0 criterias (Legion - 23420) + OwnHeirlooms = 189, + ArtifactPowerEarned = 190, + ArtifactTraitsUnlocked = 191, + HonorLevelReached = 194, + PrestigeReached = 195, + // 196 - CRITERIA_TYPE_REACH_LEVEL_2 or something + // 197 - Order Hall Advancement related + OrderHallTalentLearned = 198, + AppearanceUnlockedBySlot = 199, + OrderHallRecruitTyoop = 200, + // 201 - 0 criterias (Legion - 23420) + // 202 - 0 criterias (Legion - 23420) + CompleteWorldQuest = 203, + // 204 - Special criteria type to award players for some external events? Comes with what looks like an identifier, so guessing it's not unique. + TotalTypes = 208 + } + + public enum CriteriaDataType + { + None = 0, + TCreature = 1, + TPlayerClassRace = 2, + TPlayerLessHealth = 3, + SAura = 5, + TAura = 7, + Value = 8, + TLevel = 9, + TGender = 10, + Script = 11, + // Reuse + MapPlayerCount = 13, + TTeam = 14, + SDrunk = 15, + Holiday = 16, + BgLossTeamScore = 17, + InstanceScript = 18, + SEquippedItem = 19, + MapId = 20, + SPlayerClassRace = 21, + // Reuse + SKnownTitle = 23, + GameEvent = 24, + SItemQuality = 25, + + Max = 25 + } + + public enum ProgressType + { + Set, + Accumulate, + Highest + } +} diff --git a/Framework/Constants/AreaTriggerConst.cs b/Framework/Constants/AreaTriggerConst.cs new file mode 100644 index 000000000..76af2ae17 --- /dev/null +++ b/Framework/Constants/AreaTriggerConst.cs @@ -0,0 +1,62 @@ +/* + * Copyright (C) 2012-2017 CypherCore + * + * 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, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +namespace Framework.Constants +{ + public enum AreaTriggerFlags + { + HasAbsoluteOrientation = 0x01, // Nyi + HasDynamicShape = 0x02, // Implemented For Spheres + HasAttached = 0x04, + HasFaceMovementDir = 0x08, + HasFollowsTerrain = 0x010, // Nyi + Unk1 = 0x020, + HasTargetRollPitchYaw = 0x040, // Nyi + Unk2 = 0x080, + Unk3 = 0x100, + Unk4 = 0x200, + Unk5 = 0x400 + } + + public enum AreaTriggerTypes + { + Sphere = 0, + Box = 1, + Unk = 2, + Polygon = 3, + Cylinder = 4, + Max = 5 + } + + public enum AreaTriggerActionTypes + { + Cast = 0, + AddAura = 1, + Max = 2 + } + + public enum AreaTriggerActionUserTypes + { + Any = 0, + Friend = 1, + Enemy = 2, + Raid = 3, + Party = 4, + Caster = 5, + Max = 6 + } +} diff --git a/Framework/Constants/AuctionConst.cs b/Framework/Constants/AuctionConst.cs new file mode 100644 index 000000000..cb8169221 --- /dev/null +++ b/Framework/Constants/AuctionConst.cs @@ -0,0 +1,50 @@ +/* + * Copyright (C) 2012-2017 CypherCore + * + * 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, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +namespace Framework.Constants +{ + public enum AuctionError + { + Ok = 0, + Inventory = 1, + DatabaseError = 2, + NotEnoughtMoney = 3, + ItemNotFound = 4, + HigherBid = 5, + BidIncrement = 7, + BidOwn = 10, + RestrictedAccount = 13 + } + + public enum AuctionAction + { + SellItem = 0, + Cancel = 1, + PlaceBid = 2 + } + + public enum MailAuctionAnswers + { + Outbidded = 0, + Won = 1, + Successful = 2, + Expired = 3, + CancelledToBidder = 4, + Canceled = 5, + SalePending = 6 + } +} diff --git a/Framework/Constants/Authentication/AuthConst.cs b/Framework/Constants/Authentication/AuthConst.cs new file mode 100644 index 000000000..890283a86 --- /dev/null +++ b/Framework/Constants/Authentication/AuthConst.cs @@ -0,0 +1,133 @@ +/* + * Copyright (C) 2012-2017 CypherCore + * + * 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, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +namespace Framework.Constants +{ + public enum ResponseCodes + { + Success = 0, + Failure = 1, + Cancelled = 2, + Disconnected = 3, + FailedToConnect = 4, + Connected = 5, + VersionMismatch = 6, + + CstatusConnecting = 7, + CstatusNegotiatingSecurity = 8, + CstatusNegotiationComplete = 9, + CstatusNegotiationFailed = 10, + CstatusAuthenticating = 11, + + RealmListInProgress = 12, + RealmListSuccess = 13, + RealmListFailed = 14, + RealmListInvalid = 15, + RealmListRealmNotFound = 16, + + AccountCreateInProgress = 17, + AccountCreateSuccess = 18, + AccountCreateFailed = 19, + + CharListRetrieving = 20, + CharListRetrieved = 21, + CharListFailed = 22, + + CharCreateInProgress = 23, + CharCreateSuccess = 24, + CharCreateError = 25, + CharCreateFailed = 26, + CharCreateNameInUse = 27, + CharCreateDisabled = 28, + CharCreatePvpTeamsViolation = 29, + CharCreateServerLimit = 30, + CharCreateAccountLimit = 31, + CharCreateServerQueue = 32, + CharCreateOnlyExisting = 33, + CharCreateExpansion = 34, + CharCreateExpansionClass = 35, + CharCreateLevelRequirement = 36, + CharCreateUniqueClassLimit = 37, + CharCreateCharacterInGuild = 38, + CharCreateRestrictedRaceclass = 39, + CharCreateCharacterChooseRace = 40, + CharCreateCharacterArenaLeader = 41, + CharCreateCharacterDeleteMail = 42, + CharCreateCharacterSwapFaction = 43, + CharCreateCharacterRaceOnly = 44, + CharCreateCharacterGoldLimit = 45, + CharCreateForceLogin = 46, + CharCreateTrial = 47, + CharCreateTimeout = 48, + CharCreateThrottle = 49, + + CharDeleteInProgress = 50, + CharDeleteSuccess = 51, + CharDeleteFailed = 52, + CharDeleteFailedLockedForTransfer = 53, + CharDeleteFailedGuildLeader = 54, + CharDeleteFailedArenaCaptain = 55, + CharDeleteFailedHasHeirloomOrMail = 56, + CharDeleteFailedUpgradeInProgress = 57, + CharDeleteFailedHasWowToken = 58, + CharDeleteFailedVasTransactionInProgress = 59, + + CharLoginInProgress = 60, + CharLoginSuccess = 61, + CharLoginNoWorld = 62, + CharLoginDuplicateCharacter = 63, + CharLoginNoInstances = 64, + CharLoginFailed = 65, + CharLoginDisabled = 66, + CharLoginNoCharacter = 67, + CharLoginLockedForTransfer = 68, + CharLoginLockedByBilling = 69, + CharLoginLockedByMobileAh = 70, + CharLoginTemporaryGmLock = 71, + CharLoginLockedByCharacterUpgrade = 72, + CharLoginLockedByRevokedCharacterUpgrade = 73, + CharLoginLockedByRevokedVasTransaction = 74, + + CharNameSuccess = 75, + CharNameFailure = 76, + CharNameNoName = 77, + CharNameTooShort = 78, + CharNameTooLong = 79, + CharNameInvalidCharacter = 80, + CharNameMixedLanguages = 81, + CharNameProfane = 82, + CharNameReserved = 83, + CharNameInvalidApostrophe = 84, + CharNameMultipleApostrophes = 85, + CharNameThreeConsecutive = 86, + CharNameInvalidSpace = 87, + CharNameConsecutiveSpaces = 88, + CharNameRussianConsecutiveSilentCharacters = 89, + CharNameRussianSilentCharacterAtBeginningOrEnd = 90, + CharNameDeclensionDoesntMatchBaseName = 91 + } + + public enum CharacterUndeleteResult + { + Ok = 0, + Cooldown = 1, + CharCreate = 2, + Disabled = 3, + NameTakenByThisAccount = 4, + Unknown = 5 + } +} diff --git a/Framework/Constants/Authentication/BattlenetConst.cs b/Framework/Constants/Authentication/BattlenetConst.cs new file mode 100644 index 000000000..57c681146 --- /dev/null +++ b/Framework/Constants/Authentication/BattlenetConst.cs @@ -0,0 +1,668 @@ +/* + * Copyright (C) 2012-2017 CypherCore + * + * 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, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +namespace Framework.Constants +{ + public enum BattlenetRpcErrorCode : uint + { + Ok = 0x00000000, + Internal = 0x00000001, + TimedOut = 0x00000002, + Denied = 0x00000003, + NotExists = 0x00000004, + NotStarted = 0x00000005, + InProgress = 0x00000006, + InvalidArgs = 0x00000007, + InvalidSubscriber = 0x00000008, + WaitingForDependency = 0x00000009, + NoAuth = 0x0000000a, + ParentalControlRestriction = 0x0000000b, + NoGameAccount = 0x0000000c, + NotImplemented = 0x0000000d, + ObjectRemoved = 0x0000000e, + InvalidEntityId = 0x0000000f, + InvalidEntityAccountId = 0x00000010, + InvalidEntityGameAccountId = 0x00000011, + InvalidAgentId = 0x00000013, + InvalidTargetId = 0x00000014, + ModuleNotLoaded = 0x00000015, + ModuleNoEntryPoint = 0x00000016, + ModuleSignatureIncorrect = 0x00000017, + ModuleCreateFailed = 0x00000018, + NoProgram = 0x00000019, + ApiNotReady = 0x0000001b, + BadVersion = 0x0000001c, + AttributeTooManyAttributesSet = 0x0000001d, + AttributeMaxSizeExceeded = 0x0000001e, + AttributeQuotaExceeded = 0x0000001f, + ServerPoolServerDisappeared = 0x00000020, + ServerIsPrivate = 0x00000021, + Disabled = 0x00000022, + ModuleNotFound = 0x00000024, + ServerBusy = 0x00000025, + NoBattletag = 0x00000026, + IncompleteProfanityFilters = 0x00000027, + InvalidRegion = 0x00000028, + ExistsAlready = 0x00000029, + InvalidServerThumbprint = 0x0000002a, + PhoneLock = 0x0000002b, + Squelched = 0x0000002c, + TargetOffline = 0x0000002d, + BadServer = 0x0000002e, + NoCookie = 0x0000002f, + ExpiredCookie = 0x00000030, + TokenNotFound = 0x00000031, + GameAccountNoTime = 0x00000032, + GameAccountNoPlan = 0x00000033, + GameAccountBanned = 0x00000034, + GameAccountSuspended = 0x00000035, + GameAccountAlreadySelected = 0x00000036, + GameAccountCancelled = 0x00000037, + GameAccountCreationDisabled = 0x00000038, + GameAccountLocked = 0x00000039, + + SessionDuplicate = 0x0000003c, + SessionDisconnected = 0x0000003d, + SessionDataChanged = 0x0000003e, + SessionUpdateFailed = 0x0000003f, + SessionNotFound = 0x00000040, + + AdminKick = 0x00000046, + UnplannedMaintenance = 0x00000047, + PlannedMaintenance = 0x00000048, + ServiceFailureAccount = 0x00000049, + ServiceFailureSession = 0x0000004a, + ServiceFailureAuth = 0x0000004b, + ServiceFailureRisk = 0x0000004c, + BadProgram = 0x0000004d, + BadLocale = 0x0000004e, + BadPlatform = 0x0000004f, + LocaleRestrictedLa = 0x00000051, + LocaleRestrictedRu = 0x00000052, + LocaleRestrictedKo = 0x00000053, + LocaleRestrictedTw = 0x00000054, + LocaleRestricted = 0x00000055, + AccountNeedsMaintenance = 0x00000056, + ModuleApiError = 0x00000057, + ModuleBadCacheHandle = 0x00000058, + ModuleAlreadyLoaded = 0x00000059, + NetworkBlacklisted = 0x0000005a, + EventProcessorSlow = 0x0000005b, + ServerShuttingDown = 0x0000005c, + NetworkNotPrivileged = 0x0000005d, + TooManyOutstandingRequests = 0x0000005e, + NoAccountRegistered = 0x0000005f, + BattlenetAccountBanned = 0x00000060, + + OkDeprecated = 0x00000064, + ServerInModeZombie = 0x00000065, + + LogonModuleRequired = 0x000001f4, + LogonModuleNotConfigured = 0x000001f5, + LogonModuleTimeout = 0x000001f6, + LogonAgreementRequired = 0x000001fe, + LogonAgreementNotConfigured = 0x000001ff, + + LogonInvalidServerProof = 0x00000208, + LogonWebVerifyTimeout = 0x00000209, + LogonInvalidAuthToken = 0x0000020a, + + ChallengeSmsTooSoon = 0x00000258, + ChallengeSmsThrottled = 0x00000259, + ChallengeSmsTempOutage = 0x0000025a, + ChallengeNoChallenge = 0x0000025b, + ChallengeNotPicked = 0x0000025c, + ChallengeAlreadyPicked = 0x0000025d, + ChallengeInProgress = 0x0000025e, + + ConfigFormatInvalid = 0x000002bc, + ConfigNotFound = 0x000002bd, + ConfigRetrieveFailed = 0x000002be, + + NetworkModuleBusy = 0x000003e8, + NetworkModuleCantResolveAddress = 0x000003e9, + NetworkModuleConnectionRefused = 0x000003ea, + NetworkModuleInterrupted = 0x000003eb, + NetworkModuleConnectionAborted = 0x000003ec, + NetworkModuleConnectionReset = 0x000003ed, + NetworkModuleBadAddress = 0x000003ee, + NetworkModuleNotReady = 0x000003ef, + NetworkModuleAlreadyConnected = 0x000003f0, + NetworkModuleCantCreateSocket = 0x000003f1, + NetworkModuleNetworkUnreachable = 0x000003f2, + NetworkModuleSocketPermissionDenied = 0x000003f3, + NetworkModuleNotInitialized = 0x000003f4, + NetworkModuleNoSslCertificateForPeer = 0x000003f5, + NetworkModuleNoSslCommonNameForCertificate = 0x000003f6, + NetworkModuleSslCommonNameDoesNotMatchRemoteEndpoint = 0x000003f7, + NetworkModuleSocketClosed = 0x000003f8, + NetworkModuleSslPeerIsNotRegisteredInCertbundle = 0x000003f9, + NetworkModuleSslInitializeLowFirst = 0x000003fa, + NetworkModuleSslCertBundleReadError = 0x000003fb, + NetworkModuleNoCertBundle = 0x000003fc, + NetworkModuleFailedToDownloadCertBundle = 0x000003fd, + NetworkModuleNotReadyToRead = 0x000003fe, + + NetworkModuleOpensslX509Ok = 0x000004b0, + NetworkModuleOpensslX509UnableToGetIssuerCert = 0x000004b1, + NetworkModuleOpensslX509UnableToGetCrl = 0x000004b2, + NetworkModuleOpensslX509UnableToDecryptCertSignature = 0x000004b3, + NetworkModuleOpensslX509UnableToDecryptCrlSignature = 0x000004b4, + NetworkModuleOpensslX509UnableToDecodeIssuerPublicKey = 0x000004b5, + NetworkModuleOpensslX509CertSignatureFailure = 0x000004b6, + NetworkModuleOpensslX509CrlSignatureFailure = 0x000004b7, + NetworkModuleOpensslX509CertNotYetValid = 0x000004b8, + NetworkModuleOpensslX509CertHasExpired = 0x000004b9, + NetworkModuleOpensslX509CrlNotYetValid = 0x000004ba, + NetworkModuleOpensslX509CrlHasExpired = 0x000004bb, + NetworkModuleOpensslX509InCertNotBeforeField = 0x000004bc, + NetworkModuleOpensslX509InCertNotAfterField = 0x000004bd, + NetworkModuleOpensslX509InCrlLastUpdateField = 0x000004be, + NetworkModuleOpensslX509InCrlNextUpdateField = 0x000004bf, + NetworkModuleOpensslX509OutOfMem = 0x000004c0, + NetworkModuleOpensslX509DepthZeroSelfSignedCert = 0x000004c1, + NetworkModuleOpensslX509SelfSignedCertInChain = 0x000004c2, + NetworkModuleOpensslX509UnableToGetIssuerCertLocally = 0x000004c3, + NetworkModuleOpensslX509UnableToVerifyLeafSignature = 0x000004c4, + NetworkModuleOpensslX509CertChainTooLong = 0x000004c5, + NetworkModuleOpensslX509CertRevoked = 0x000004c6, + NetworkModuleOpensslX509InvalidCa = 0x000004c7, + NetworkModuleOpensslX509PathLengthExceeded = 0x000004c8, + NetworkModuleOpensslX509InvalidPurpose = 0x000004c9, + NetworkModuleOpensslX509CertUntrusted = 0x000004ca, + NetworkModuleOpensslX509CertRejected = 0x000004cb, + NetworkModuleOpensslX509SubjectIssuerMismatch = 0x000004cc, + NetworkModuleOpensslX509AkidSkidMismatch = 0x000004cd, + NetworkModuleOpensslX509AkidIssuerSerialMismatch = 0x000004ce, + NetworkModuleOpensslX509KeyusageNoCertsign = 0x000004cf, + NetworkModuleOpensslX509ApplicationVerification = 0x000004d0, + + NetworkModuleSchannelCannotFindOsVersion = 0x00000514, + NetworkModuleSchannelOsNotSupported = 0x00000515, + NetworkModuleSchannelLoadlibraryFail = 0x00000516, + NetworkModuleSchannelCannotFindInterface = 0x00000517, + NetworkModuleSchannelInitFail = 0x00000518, + NetworkModuleSchannelFunctionCallFail = 0x00000519, + NetworkModuleSchannelX509UnableToGetIssuerCert = 0x00000546, + NetworkModuleSchannelX509TimeInvalid = 0x00000547, + NetworkModuleSchannelX509SignatureInvalid = 0x00000548, + NetworkModuleSchannelX509UnableToVerifyLeafSignature = 0x00000549, + NetworkModuleSchannelX509SelfSignedLeafCertificate = 0x0000054a, + NetworkModuleSchannelX509UnhandledError = 0x0000054b, + NetworkModuleSchannelX509SelfSignedCertInChain = 0x0000054c, + + WebsocketHandshake = 0x00000578, + + NetworkModuleDurangoUnknown = 0x000005dc, + NetworkModuleDurangoMalformedHostName = 0x000005dd, + NetworkModuleDurangoInvalidConnectionResponse = 0x000005de, + NetworkModuleDurangoInvalidCaCert = 0x000005df, + + RpcWriteFailed = 0x00000bb8, + RpcServiceNotBound = 0x00000bb9, + RpcTooManyRequests = 0x00000bba, + RpcPeerUnknown = 0x00000bbb, + RpcPeerUnavailable = 0x00000bbc, + RpcPeerDisconnected = 0x00000bbd, + RpcRequestTimedOut = 0x00000bbe, + RpcConnectionTimedOut = 0x00000bbf, + RpcMalformedResponse = 0x00000bc0, + RpcAccessDenied = 0x00000bc1, + RpcInvalidService = 0x00000bc2, + RpcInvalidMethod = 0x00000bc3, + RpcInvalidObject = 0x00000bc4, + RpcMalformedRequest = 0x00000bc5, + RpcQuotaExceeded = 0x00000bc6, + RpcNotImplemented = 0x00000bc7, + RpcServerError = 0x00000bc8, + RpcShutdown = 0x00000bc9, + RpcDisconnect = 0x00000bca, + RpcDisconnectIdle = 0x00000bcb, + RpcProtocolError = 0x00000bcc, + RpcNotReady = 0x00000bcd, + RpcForwardFailed = 0x00000bce, + RpcEncryptionFailed = 0x00000bcf, + RpcInvalidAddress = 0x00000bd0, + RpcMethodDisabled = 0x00000bd1, + RpcShardNotFound = 0x00000bd2, + RpcInvalidConnectionId = 0x00000bd3, + RpcNotConnected = 0x00000bd4, + RpcInvalidConnectionState = 0x00000bd5, + RpcServiceAlreadyRegistered = 0x00000bd6, + + PresenceInvalidFieldId = 0x00000fa0, + PresenceNoValidSubscribers = 0x00000fa1, + PresenceAlreadySubscribed = 0x00000fa2, + PresenceConsumerNotFound = 0x00000fa3, + PresenceConsumerIsNull = 0x00000fa4, + PresenceTemporaryOutage = 0x00000fa5, + PresenceTooManySubscriptions = 0x00000fa6, + PresenceSubscriptionCancelled = 0x00000fa7, + PresenceRichPresenceParseError = 0x00000fa8, + PresenceRichPresenceXmlError = 0x00000fa9, + PresenceRichPresenceLoadError = 0x00000faa, + + FriendsTooManySentInvitations = 0x00001389, + FriendsTooManyReceivedInvitations = 0x0000138a, + FriendsFriendshipAlreadyExists = 0x0000138b, + FriendsFriendshipDoesNotExist = 0x0000138c, + FriendsInvitationAlreadyExists = 0x0000138d, + FriendsInvalidInvitation = 0x0000138e, + FriendsAlreadySubscribed = 0x0000138f, + FriendsAccountBlocked = 0x00001391, + FriendsNotSubscribed = 0x00001392, + FriendsInvalidRoleId = 0x00001393, + FriendsDisabledRoleId = 0x00001394, + FriendsNoteMaxSizeExceeded = 0x00001395, + FriendsUpdateFriendStateFailed = 0x00001396, + FriendsInviteeAtMaxFriends = 0x00001397, + FriendsInviterAtMaxFriends = 0x00001398, + + PlatformStorageFileWriteDenied = 0x00001770, + + WhisperUndeliverable = 0x00001b58, + WhisperMaxSizeExceeded = 0x00001b59, + + UserManagerAlreadyBlocked = 0x00001f40, + UserManagerNotBlocked = 0x00001f41, + UserManagerCannotBlockSelf = 0x00001f42, + UserManagerAlreadyRegistered = 0x00001f43, + UserManagerNotRegistered = 0x00001f44, + UserManagerTooManyBlockedEntities = 0x00001f45, + UserManagerTooManyIds = 0x00001f47, + UserManagerBlockRecordUnavailable = 0x00001f4f, + UserManagerBlockEntityFailed = 0x00001f50, + UserManagerUnblockEntityFailed = 0x00001f51, + UserManagerCannotBlockFriend = 0x00001f53, + + SocialNetworkDbException = 0x00002328, + SocialNetworkDenialFromProvider = 0x00002329, + SocialNetworkInvalidSnsId = 0x0000232a, + SocialNetworkCantSendToProvider = 0x0000232b, + SocialNetworkExCommFailed = 0x0000232c, + SocialNetworkDisabled = 0x0000232d, + SocialNetworkMissingRequestParam = 0x0000232e, + SocialNetworkUnsupportedOauthVersion = 0x0000232f, + + ChannelFull = 0x00002710, + ChannelNoChannel = 0x00002711, + ChannelNotMember = 0x00002712, + ChannelAlreadyMember = 0x00002713, + ChannelNoSuchMember = 0x00002714, + ChannelInvalidChannelId = 0x00002716, + ChannelNoSuchInvitation = 0x00002718, + ChannelTooManyInvitations = 0x00002719, + ChannelInvitationAlreadyExists = 0x0000271a, + ChannelInvalidChannelSize = 0x0000271b, + ChannelInvalidRoleId = 0x0000271c, + ChannelRoleNotAssignable = 0x0000271d, + ChannelInsufficientPrivileges = 0x0000271e, + ChannelInsufficientPrivacyLevel = 0x0000271f, + ChannelInvalidPrivacyLevel = 0x00002720, + ChannelTooManyChannelsJoined = 0x00002721, + ChannelInvitationAlreadySubscribed = 0x00002722, + ChannelInvalidChannelDelegate = 0x00002723, + ChannelSlotAlreadyReserved = 0x00002724, + ChannelSlotNotReserved = 0x00002725, + ChannelNoReservedSlotsAvailable = 0x00002726, + ChannelInvalidRoleSet = 0x00002727, + ChannelRequireFriendValidation = 0x00002728, + ChannelMemberOffline = 0x00002729, + ChannelReceivedTooManyInvitations = 0x0000272a, + ChannelInvitationInvalidGameAccountSelected = 0x0000272b, + ChannelUnreachable = 0x0000272c, + ChannelInvitationNotSubscribed = 0x0000272d, + ChannelInvalidMessageSize = 0x0000272e, + ChannelMaxMessageSizeExceeded = 0x0000272f, + ChannelConfigNotFound = 0x00002730, + ChannelInvalidChannelType = 0x00002731, + + LocalStorageFileOpenError = 0x00002af8, + LocalStorageFileCreateError = 0x00002af9, + LocalStorageFileReadError = 0x00002afa, + LocalStorageFileWriteError = 0x00002afb, + LocalStorageFileDeleteError = 0x00002afc, + LocalStorageFileCopyError = 0x00002afd, + LocalStorageFileDecompressError = 0x00002afe, + LocalStorageFileHashMismatch = 0x00002aff, + LocalStorageFileUsageMismatch = 0x00002b00, + LocalStorageDatabaseInitError = 0x00002b01, + LocalStorageDatabaseNeedsRebuild = 0x00002b02, + LocalStorageDatabaseInsertError = 0x00002b03, + LocalStorageDatabaseLookupError = 0x00002b04, + LocalStorageDatabaseUpdateError = 0x00002b05, + LocalStorageDatabaseDeleteError = 0x00002b06, + LocalStorageDatabaseShrinkError = 0x00002b07, + LocalStorageCacheCrawlError = 0x00002b08, + LocalStorageDatabaseIndexTriggerError = 0x00002b09, + LocalStorageDatabaseRebuildInProgress = 0x00002b0a, + LocalStorageOkButNotInCache = 0x00002b0b, + LocalStorageDatabaseRebuildInterrupted = 0x00002b0d, + LocalStorageDatabaseNotInitialized = 0x00002b0e, + LocalStorageDirectoryCreateError = 0x00002b0f, + LocalStorageFilekeyNotFound = 0x00002b10, + LocalStorageNotAvailableOnServer = 0x00002b11, + + RegistryCreateKeyError = 0x00002ee0, + RegistryOpenKeyError = 0x00002ee1, + RegistryReadError = 0x00002ee2, + RegistryWriteError = 0x00002ee3, + RegistryTypeError = 0x00002ee4, + RegistryDeleteError = 0x00002ee5, + RegistryEncryptError = 0x00002ee6, + RegistryDecryptError = 0x00002ee7, + RegistryKeySizeError = 0x00002ee8, + RegistryValueSizeError = 0x00002ee9, + RegistryNotFound = 0x00002eeb, + RegistryMalformedString = 0x00002eec, + + InterfaceAlreadyConnected = 0x000032c8, + InterfaceNotReady = 0x000032c9, + InterfaceOptionKeyTooLarge = 0x000032ca, + InterfaceOptionValueTooLarge = 0x000032cb, + InterfaceOptionKeyInvalidUtf8String = 0x000032cc, + InterfaceOptionValueInvalidUtf8String = 0x000032cd, + + HttpCouldntResolve = 0x000036b0, + HttpCouldntConnect = 0x000036b1, + HttpTimeout = 0x000036b2, + HttpFailed = 0x000036b3, + HttpMalformedUrl = 0x000036b4, + HttpDownloadAborted = 0x000036b5, + HttpCouldntWriteFile = 0x000036b6, + HttpTooManyRedirects = 0x000036b7, + HttpCouldntOpenFile = 0x000036b8, + HttpCouldntCreateFile = 0x000036b9, + HttpCouldntReadFile = 0x000036ba, + HttpCouldntRenameFile = 0x000036bb, + HttpCouldntCreateDirectory = 0x000036bc, + HttpCurlIsNotReady = 0x000036bd, + HttpCancelled = 0x000036be, + + HttpFileNotFound = 0x00003844, + + AccountMissingConfig = 0x00004650, + AccountDataNotFound = 0x00004651, + AccountAlreadySubscribed = 0x00004652, + AccountNotSubscribed = 0x00004653, + AccountFailedToParseTimezoneData = 0x00004654, + AccountLoadFailed = 0x00004655, + AccountLoadCancelled = 0x00004656, + AccountDatabaseInvalidateFailed = 0x00004657, + AccountCacheInvalidateFailed = 0x00004658, + AccountSubscriptionPending = 0x00004659, + AccountUnknownRegion = 0x0000465a, + AccountDataFailedToParse = 0x0000465b, + AccountUnderage = 0x0000465c, + AccountIdentityCheckPending = 0x0000465d, + AccountIdentityUnverified = 0x0000465e, + + DatabaseBindingCountMismatch = 0x00004a38, + DatabaseBindingParseFail = 0x00004a39, + DatabaseResultsetColumnsMismatch = 0x00004a3a, + DatabaseDeadlock = 0x00004a3b, + DatabaseDuplicateKey = 0x00004a3c, + DatabaseCannotConnect = 0x00004a3d, + DatabaseStatementFailed = 0x00004a3e, + DatabaseTransactionNotStarted = 0x00004a3f, + DatabaseTransactionNotEnded = 0x00004a40, + DatabaseTransactionLeak = 0x00004a41, + DatabaseTransactionStateBad = 0x00004a42, + DatabaseServerGone = 0x00004a43, + DatabaseQueryTimeout = 0x00004a44, + DatabaseBindingNotNullable = 0x00004a9c, + DatabaseBindingInvalidInteger = 0x00004a9d, + DatabaseBindingInvalidFloat = 0x00004a9e, + DatabaseBindingInvalidTemporal = 0x00004a9f, + DatabaseBindingInvalidProtobuf = 0x00004aa0, + + PartyInvalidPartyId = 0x00004e20, + PartyAlreadyInParty = 0x00004e21, + PartyNotInParty = 0x00004e22, + PartyInvitationUndeliverable = 0x00004e23, + PartyInvitationAlreadyExists = 0x00004e24, + PartyTooManyPartyInvitations = 0x00004e25, + PartyTooManyReceivedInvitations = 0x00004e26, + PartyNoSuchType = 0x00004e27, + + GamesNoSuchFactory = 0x000055f0, + GamesNoSuchGame = 0x000055f1, + GamesNoSuchRequest = 0x000055f2, + GamesNoSuchPartyMember = 0x000055f3, + + ResourcesOffline = 0x000059d8, + + GameServerCreateGameRefused = 0x00005dc0, + GameServerAddPlayersRefused = 0x00005dc1, + GameServerRemovePlayersRefused = 0x00005dc2, + GameServerFinishGameRefused = 0x00005dc3, + GameServerNoSuchGame = 0x00005dc4, + GameServerNoSuchPlayer = 0x00005dc5, + GameServerCreateGameRefusedTransient = 0x00005df2, + GameServerAddPlayersRefusedTransient = 0x00005df3, + GameServerRemovePlayersRefusedTransient = 0x00005df4, + GameServerFinishGameRefusedTransient = 0x00005df5, + GameServerCreateGameRefusedBusy = 0x00005e24, + GameServerAddPlayersRefusedBusy = 0x00005e25, + GameServerRemovePlayersRefusedBusy = 0x00005e26, + GameServerFinishGameRefusedBusy = 0x00005e27, + + GameMasterInvalidFactory = 0x000061a8, + GameMasterInvalidGame = 0x000061a9, + GameMasterGameFull = 0x000061aa, + GameMasterRegisterFailed = 0x000061ab, + GameMasterNoGameServer = 0x000061ac, + GameMasterNoUtilityServer = 0x000061ad, + GameMasterNoGameVersion = 0x000061ae, + GameMasterGameJoinFailed = 0x000061af, + GameMasterAlreadyRegistered = 0x000061b0, + GameMasterNoFactory = 0x000061b1, + GameMasterMultipleGameVersions = 0x000061b2, + GameMasterInvalidPlayer = 0x000061b3, + GameMasterInvalidGameRequest = 0x000061b4, + GameMasterInsufficientPrivileges = 0x000061b5, + GameMasterAlreadyInGame = 0x000061b6, + GameMasterInvalidGameServerResponse = 0x000061b7, + GameMasterGameAccountLookupFailed = 0x000061b8, + GameMasterGameEntryCancelled = 0x000061b9, + GameMasterGameEntryAbortedClientDropped = 0x000061ba, + GameMasterGameEntryAbortedByService = 0x000061bb, + GameMasterNoAvailableCapacity = 0x000061bc, + GameMasterInvalidTeamId = 0x000061bd, + GameMasterCreationInProgress = 0x000061be, + + NotificationInvalidClientId = 0x00006590, + NotificationDuplicateName = 0x00006591, + NotificationNameNotFound = 0x00006592, + NotificationInvalidServer = 0x00006593, + NotificationQuotaExceeded = 0x00006594, + NotificationInvalidNotificationType = 0x00006595, + NotificationUndeliverable = 0x00006596, + NotificationUndeliverableTemporary = 0x00006597, + + AchievementsNothingToUpdate = 0x00006d60, + AchievementsInvalidParams = 0x00006d61, + AchievementsNotRegistered = 0x00006d62, + AchievementsNotReady = 0x00006d63, + AchievementsFailedToParseStaticData = 0x00006d64, + AchievementsUnknownId = 0x00006d65, + AchievementsMissingSnapshot = 0x00006d66, + AchievementsAlreadyRegistered = 0x00006d67, + AchievementsTooManyRegistrations = 0x00006d68, + AchievementsAlreadyInProgress = 0x00006d69, + AchievementsTemporaryOutage = 0x00006d6a, + AchievementsInvalidProgramid = 0x00006d6b, + AchievementsMissingRecord = 0x00006d6c, + AchievementsRegistrationPending = 0x00006d6d, + AchievementsEntityIdNotFound = 0x00006d6e, + AchievementsAchievementIdNotFound = 0x00006d6f, + AchievementsCriteriaIdNotFound = 0x00006d70, + AchievementsStaticDataMismatch = 0x00006d71, + AchievementsWrongThread = 0x00006d72, + AchievementsCallbackIsNull = 0x00006d73, + AchievementsAutoRegisterPending = 0x00006d74, + AchievementsNotInitialized = 0x00006d75, + AchievementsAchievementIdAlreadyExists = 0x00006d76, + AchievementsFailedToDownloadStaticData = 0x00006d77, + AchievementsStaticDataNotFound = 0x00006d78, + + GameUtilityServerVariableRequestRefused = 0x000084d1, + GameUtilityServerWrongNumberOfVariablesReturned = 0x000084d2, + GameUtilityServerClientRequestRefused = 0x000084d3, + GameUtilityServerPresenceChannelCreatedRefused = 0x000084d4, + GameUtilityServerVariableRequestRefusedTransient = 0x00008502, + GameUtilityServerClientRequestRefusedTransient = 0x00008503, + GameUtilityServerPresenceChannelCreatedRefusedTransient = 0x00008504, + GameUtilityServerServerRequestRefusedTransient = 0x00008505, + GameUtilityServerVariableRequestRefusedBusy = 0x00008534, + GameUtilityServerClientRequestRefusedBusy = 0x00008535, + GameUtilityServerPresenceChannelCreatedRefusedBusy = 0x00008536, + GameUtilityServerServerRequestRefusedBusy = 0x00008537, + GameUtilityServerNoServer = 0x00008598, + + IdentityInsufficientData = 0x0000a028, + IdentityTooManyResults = 0x0000a029, + IdentityBadId = 0x0000a02a, + IdentityNoAccountBlob = 0x0000a02b, + + RiskChallengeAction = 0x0000a410, + RiskDelayAction = 0x0000a411, + RiskThrottleAction = 0x0000a412, + RiskAccountLocked = 0x0000a413, + RiskCsDenied = 0x0000a414, + RiskDisconnectAccount = 0x0000a415, + RiskCheckSkipped = 0x0000a416, + + ReportUnavailable = 0x0000afc8, + ReportTooLarge = 0x0000afc9, + ReportUnknownType = 0x0000afca, + ReportAttributeInvalid = 0x0000afcb, + ReportAttributeQuotaExceeded = 0x0000afcc, + ReportUnconfirmed = 0x0000afcd, + ReportNotConnected = 0x0000afce, + ReportRejected = 0x0000afcf, + ReportTooManyRequests = 0x0000afd0, + + AccountAlreadyRegisterd = 0x0000bb80, + AccountNotRegistered = 0x0000bb81, + AccountRegistrationPending = 0x0000bb82, + + MemcachedClientNoError = 0x00010000, + MemcachedClientKeyNotFound = 0x00010001, + MemcachedKeyExists = 0x00010002, + MemcachedValueToLarge = 0x00010003, + MemcachedInvalidArgs = 0x00010004, + MemcachedItemNotStored = 0x00010005, + MemcachedNonNumericValue = 0x00010006, + MemcachedWrongServer = 0x00010007, + MemcachedAuthenticationError = 0x00010008, + MemcachedAuthenticationContinue = 0x00010009, + MemcachedUnknownCommand = 0x0001000a, + MemcachedOutOfMemory = 0x0001000b, + MemcachedNotSupported = 0x0001000c, + MemcachedInternalError = 0x0001000d, + MemcachedTemporaryFailure = 0x0001000e, + + MemcachedClientAlreadyConnected = 0x000186a0, + MemcachedClientBadConfig = 0x000186a1, + MemcachedClientNotConnected = 0x000186a2, + MemcachedClientTimeout = 0x000186a3, + MemcachedClientAborted = 0x000186a4, + + UtilServerFailedToSerialize = 0x80000064, + UtilServerDisconnectedFromBattlenet = 0x80000065, + UtilServerTimedOut = 0x80000066, + UtilServerNoMeteringData = 0x80000067, + UtilServerFailPermissionCheck = 0x80000068, + UtilServerUnknownRealm = 0x80000069, + UtilServerMissingSessionKey = 0x8000006a, + UtilServerMissingVirtualRealm = 0x8000006b, + UtilServerInvalidSessionKey = 0x8000006c, + UtilServerMissingRealmList = 0x8000006d, + UtilServerInvalidIdentityArgs = 0x8000006e, + UtilServerSessionObjectMissing = 0x8000006f, + UtilServerInvalidBnetSession = 0x80000070, + UtilServerInvalidVirtualRealm = 0x80000071, + UtilServerInvalidClientAddress = 0x80000072, + UtilServerFailedToSerializeResponse = 0x80000073, + UtilServerUnknownRequest = 0x80000074, + UtilServerUnableToGenerateJoinTicket = 0x80000075, + UtilServerUnableToGenerateRealmListTicket = 0x80000076, + UtilServerAccountDenied = 0x80000077, + UtilServerInvalidWowAccount = 0x80000078, + UtilServerUnableToStoreSession = 0x80000079, + UtilServerSessionAlreadyCreated = 0x8000007a, + + UserServerFailedToSerialize = 0x800000c8, + UserServerDisconnectedFromUtil = 0x800000c9, + UserServerSessionDuplicate = 0x800000ca, + UserServerFailedToDisableBilling = 0x800000cb, + UserServerPlayerDisconnected = 0x800000cc, + UserServerFailedToParseAccountState = 0x800000cd, + UserServerAccountLoadCancelled = 0x800000ce, + UserServerBadPlatform = 0x800000cf, + UserServerBadVirtualRealm = 0x800000d0, + UserServerLocaleRestricted = 0x800000d1, + UserServerMissingPropass = 0x800000d2, + UserServerBadWowAccount = 0x800000d3, + UserServerBadBnetAccount = 0x800000d4, + UserServerFailedToParseGameAccountState = 0x800000d5, + UserServerFailedToParseGameTimeRemaining = 0x800000d6, + UserServerFailedToParseGameSessionInfo = 0x800000d7, + UserServerAccountStatePoorlyFormed = 0x800000d8, + UserServerGameAccountStatePoorlyFormed = 0x800000d9, + UserServerGameTimeRemainingPoorlyFormed = 0x800000da, + UserServerGameSessionInfoPoorlyFormed = 0x800000db, + UserServerBadSessionTrackerState = 0x800000dc, + UserServerFailedToParseCaisInfo = 0x800000dd, + UserServerGameSessionDisconnected = 0x800000de, + UserServerVersionMismatch = 0x800000df, + UserServerAccountSuspended = 0x800000e0, + UserServerNotPermittedOnRealm = 0x800000e1, + UserServerLoginFailedConnect = 0x800000e2, + + WowServicesTimedOut = 0x8000012c, + WowServicesInvalidRealmListTicket = 0x8000012d, + WowServicesInvalidJoinTicket = 0x8000012e, + WowServicesInvalidServerAddresses = 0x8000012f, + WowServicesInvalidSecretBlob = 0x80000130, + WowServicesNoRealmJoinIpFound = 0x80000131, + WowServicesDeniedRealmListTicket = 0x80000132, + WowServicesMissingGameAccount = 0x80000133, + WowServicesLogonInvalidAuthToken = 0x80000134, + WowServicesNoAvailableRealms = 0x80000135, + WowServicesFailedToParseDispatch = 0x80000136, + WowServicesMissingMeteringFile = 0x80000137, + WowServicesLoginInvalidContentType = 0x80000138, + WowServicesLoginUnableToDecode = 0x80000139, + WowServicesLoginPostError = 0x8000013a, + WowServicesAuthenticatorParseFailed = 0x8000013b, + WowServicesLegalParseFailed = 0x8000013c, + WowServicesLoginAuthenticationParseFailed = 0x8000013d, + WowSerivcesUserMustAcceptLegal = 0x8000013e, + WowServicesDisconnected = 0x8000013f, + WowServicesNoHandlerForDispatch = 0x80000140, + WowServicesPreDispatchHandlerFailed = 0x80000141, + WowServicesCriticalStreamingError = 0x80000142, + WowServicesWorldLoadError = 0x80000143, + WowServicesLoginFailed = 0x80000144, + WowServicesLoginFailedOnChallenge = 0x80000145, + WowServicesNoPrepaidTime = 0x80000146, + WowServicesSubscriptionExpired = 0x80000147, + WowServicesCantConnect = 0x80000148, + } +} diff --git a/Framework/Constants/Authentication/RealmConst.cs b/Framework/Constants/Authentication/RealmConst.cs new file mode 100644 index 000000000..a030a864d --- /dev/null +++ b/Framework/Constants/Authentication/RealmConst.cs @@ -0,0 +1,88 @@ +/* + * Copyright (C) 2012-2017 CypherCore + * + * 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, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +namespace Framework.Constants +{ + public enum RealmFlags + { + None = 0x00, + VersionMismatch = 0x01, + Offline = 0x02, + SpecifyBuild = 0x04, + Unk1 = 0x08, + Unk2 = 0x10, + Recommended = 0x20, + New = 0x40, + Full = 0x80 + } + + public enum RealmType + { + Normal = 0, + PVP = 1, + Normal2 = 4, + RP = 6, + RPPVP = 8, + + MaxType = 14, + + FFAPVP = 16 // custom, free for all pvp mode like arena PvP in all zones except rest activated places and sanctuaries + // replaced by REALM_PVP in realm list + } + + public enum RealmZones + { + Unknown = 0, // Any Language + Development = 1, // Any Language + UnitedStates = 2, // Extended-Latin + Oceanic = 3, // Extended-Latin + LatinAmerica = 4, // Extended-Latin + Tournament5 = 5, // Basic-Latin At Create, Any At Login + Korea = 6, // East-Asian + Tournament7 = 7, // Basic-Latin At Create, Any At Login + English = 8, // Extended-Latin + German = 9, // Extended-Latin + French = 10, // Extended-Latin + Spanish = 11, // Extended-Latin + Russian = 12, // Cyrillic + Tournament13 = 13, // Basic-Latin At Create, Any At Login + Taiwan = 14, // East-Asian + Tournament15 = 15, // Basic-Latin At Create, Any At Login + China = 16, // East-Asian + Cn1 = 17, // Basic-Latin At Create, Any At Login + Cn2 = 18, // Basic-Latin At Create, Any At Login + Cn3 = 19, // Basic-Latin At Create, Any At Login + Cn4 = 20, // Basic-Latin At Create, Any At Login + Cn5 = 21, // Basic-Latin At Create, Any At Login + Cn6 = 22, // Basic-Latin At Create, Any At Login + Cn7 = 23, // Basic-Latin At Create, Any At Login + Cn8 = 24, // Basic-Latin At Create, Any At Login + Tournament25 = 25, // Basic-Latin At Create, Any At Login + TestServer = 26, // Any Language + Tournament27 = 27, // Basic-Latin At Create, Any At Login + QaServer = 28, // Any Language + Cn9 = 29, // Basic-Latin At Create, Any At Login + TestServer2 = 30, // Any Language + Cn10 = 31, // Basic-Latin At Create, Any At Login + Ctc = 32, + Cnc = 33, + Cn14 = 34, // Basic-Latin At Create, Any At Login + Cn269 = 35, // Basic-Latin At Create, Any At Login + Cn37 = 36, // Basic-Latin At Create, Any At Login + Cn58 = 37 // Basic-Latin At Create, Any At Login + } +} diff --git a/Framework/Constants/BattleFieldConst.cs b/Framework/Constants/BattleFieldConst.cs new file mode 100644 index 000000000..708b902cd --- /dev/null +++ b/Framework/Constants/BattleFieldConst.cs @@ -0,0 +1,62 @@ +/* + * Copyright (C) 2012-2017 CypherCore + * + * 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, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +namespace Framework.Constants +{ + public struct BattlefieldSounds + { + public const uint HordeWins = 8454; + public const uint AllianceWins = 8455; + public const uint Start = 3439; + } + + public enum BattleFieldObjectiveStates + { + Neutral = 0, + Alliance, + Horde, + NeutralAllianceChallenge, + NeutralHordeChallenge, + AllianceHordeChallenge, + HordeAllianceChallenge + } + + public enum BFLeaveReason + { + Close = 1, + //BF_LEAVE_REASON_UNK1 = 2, (not used) + //BF_LEAVE_REASON_UNK2 = 4, (not used) + Exited = 8, + LowLevel = 10, + NotWhileInRaid = 15, + Deserter = 16 + } + + public enum BattlefieldState + { + Inactive = 0, + Warnup = 1, + InProgress = 2 + } + + public struct BattlefieldIds + { + public const uint WG = 1; // Wintergrasp battle + public const uint TB = 21; // Tol Barad + public const uint Ashran = 24; // Ashran + } +} diff --git a/Framework/Constants/BattleGroundsConst.cs b/Framework/Constants/BattleGroundsConst.cs new file mode 100644 index 000000000..b0b414b52 --- /dev/null +++ b/Framework/Constants/BattleGroundsConst.cs @@ -0,0 +1,386 @@ +/* + * Copyright (C) 2012-2017 CypherCore + * + * 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, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +namespace Framework.Constants +{ + public struct BattlegroundConst + { + //Time Intervals + public const uint CheckPlayerPositionInverval = 1000; // Ms + public const uint ResurrectionInterval = 30000; // Ms + //RemindInterval = 10000, // Ms + public const uint InvitationRemindTime = 20000; // Ms + public const uint InviteAcceptWaitTime = 90000; // Ms + public const uint AutocloseBattleground = 120000; // Ms + public const uint MaxOfflineTime = 300; // Secs + public const uint RespawnOneDay = 86400; // Secs + public const uint RespawnImmediately = 0; // Secs + public const uint BuffRespawnTime = 180; // Secs + public const uint BattlegroundCountdownMax = 120; // Secs + public const uint ArenaCountdownMax = 60; // Secs + public const uint PlayerPositionUpdateInterval = 5; // secs + + //EventIds + public const int EventIdFirst = 0; + public const int EventIdSecond = 1; + public const int EventIdThird = 2; + public const int EventIdFourth = 3; + public const int EventIdCount = 4; + + //Quests + public const uint WsQuestReward = 43483; + public const uint AbQuestReward = 43484; + public const uint AvQuestReward = 43475; + public const uint AvQuestKilledBoss = 23658; + public const uint EyQuestReward = 43477; + public const uint SaQuestReward = 61213; + public const uint AbQuestReward4Bases = 24061; + public const uint AbQuestReward5Bases = 24064; + + //BuffObjects + public const uint SpeedBuff = 179871; + public const uint RegenBuff = 179904; + public const uint BerserkerBuff = 179905; + + //QueueGroupTypes + public const uint BgQueuePremadeAlliance = 0; + public const uint BgQueuePremadeHorde = 1; + public const uint BgQueueNormalAlliance = 2; + public const uint BgQueueNormalHorde = 3; + public const int BgQueueTypesCount = 4; + + //PlayerPosition + public const sbyte PlayerPositionIconNone = 0; + public const sbyte PlayerPositionIconHordeFlag = 1; + public const sbyte PlayerPositionIconAllianceFlag = 2; + + public const sbyte PlayerPositionArenaSlotNone = 1; + public const sbyte PlayerPositionArenaSlot1 = 2; + public const sbyte PlayerPositionArenaSlot2 = 3; + public const sbyte PlayerPositionArenaSlot3 = 4; + public const sbyte PlayerPositionArenaSlot4 = 5; + public const sbyte PlayerPositionArenaSlot5 = 6; + + //Spells + public const uint SpellWaitingForResurrect = 2584; // Waiting To Resurrect + public const uint SpellSpiritHealChannel = 22011; // Spirit Heal Channel + public const uint SpellSpiritHeal = 22012; // Spirit Heal + public const uint SpellResurrectionVisual = 24171; // Resurrection Impact Visual + public const uint SpellArenaPreparation = 32727; // Use This One, 32728 Not Correct + public const uint SpellPreparation = 44521; // Preparation + public const uint SpellSpiritHealMana = 44535; // Spirit Heal + public const uint SpellRecentlyDroppedFlag = 42792; // Recently Dropped Flag + public const uint SpellAuraPlayerInactive = 43681; // Inactive + public const uint SpellHonorableDefender25y = 68652; // +50% Honor When Standing At A Capture Point That You Control, 25yards Radius (Added In 3.2) + public const uint SpellHonorableDefender60y = 66157; // +50% Honor When Standing At A Capture Point That You Control, 60yards Radius (Added In 3.2), Probably For 40+ Player Battlegrounds + } + + public enum BattlegroundEventFlags + { + None = 0x00, + Event1 = 0x01, + Event2 = 0x02, + Event3 = 0x04, + Event4 = 0x08 + } + + // indexes of BattlemasterList.dbc + public enum BattlegroundTypeId + { + None = 0, // None + AV = 1, // Alterac Valley + WS = 2, // Warsong Gulch + AB = 3, // Arathi Basin + NA = 4, // Nagrand Arena + BE = 5, // Blade'S Edge Arena + AA = 6, // All Arenas + EY = 7, // Eye Of The Storm + RL = 8, // Ruins Of Lordaernon + SA = 9, // Strand Of The Ancients + DS = 10, // Dalaran Sewers + RV = 11, // The Ring Of Valor + IC = 30, // Isle Of Conquest + RB = 32, // Random Battleground + Rated10Vs10 = 100, // Rated Battleground 10 Vs 10 + Rated15Vs15 = 101, // Rated Battleground 15 Vs 15 + Rated25Vs25 = 102, // Rated Battleground 25 Vs 25 + TP = 108, // Twin Peaks + BFG = 120, // Battle For Gilneas + // 656 = "Rated Eye Of The Storm" + Tk = 699, // Temple Of Kotmogu + // 706 = "Ctf3" + SM = 708, // Silvershard Mines + TVA = 719, // Tol'Viron Arena + DG = 754, // Deepwind Gorge + TTP = 757, // The Tiger'S Peak + SSvsTM = 789, // Southshore Vs. Tarren Mill + SmallD = 803, // Small Battleground D + BRH = 808, // Black Rook Hold Arena + // 809 = "New Nagrand Arena (Legion)" + AF = 816, // Ashamane'S Fall + // 844 = "New Blade'S Edge Arena (Legion)" + Max = 845 + } + + public enum BattlegroundQueueTypeId + { + None = 0, + AV = 1, + WS = 2, + AB = 3, + EY = 4, + SA = 5, + IC = 6, + TP = 7, + BFG = 8, + RB = 9, + Arena2v2 = 10, + Arena3v3 = 11, + Arena5v5 = 12, + Max + } + + public enum BattlegroundQueueInvitationType + { + NoBalance = 0, // no balance: N+M vs N players + Balanced = 1, // teams balanced: N+1 vs N players + Even = 2 // teams even: N vs N players + } + + public enum BattlegroundCriteriaId + { + ResilientVictory, + SaveTheDay, + EverythingCounts, + AvPerfection, + DefenseOfTheAncients, + NotEvenAScratch, + } + + public enum BattlegroundSounds + { + HordeWins = 8454, + AllianceWins = 8455, + BgStart = 3439, + BgStartL70etc = 11803 + } + + public enum BattlegroundTeamId + { + Horde = 0, // Battleground: Horde, Arena: Green + Alliance = 1, // Battleground: Alliance, Arena: Gold + Neutral = 2 // Battleground: Neutral, Arena: None + } + + public enum BattlegroundMarks + { + SpellWsMarkLoser = 24950, + SpellWsMarkWinner = 24951, + SpellAbMarkLoser = 24952, + SpellAbMarkWinner = 24953, + SpellAvMarkLoser = 24954, + SpellAvMarkWinner = 24955, + SpellSaMarkWinner = 61160, + SpellSaMarkLoser = 61159, + ItemAvMarkOfHonor = 20560, + ItemWsMarkOfHonor = 20558, + ItemAbMarkOfHonor = 20559, + ItemEyMarkOfHonor = 29024, + ItemSaMarkOfHonor = 42425 + } + + public enum BattlegroundMarksCount + { + WinnterCount = 3, + LoserCount = 1 + } + + public enum BattlegroundCreatures + { + A_SpiritGuide = 13116, // alliance + H_SpiritGuide = 13117 // horde + } + + public enum BattlegroundStartTimeIntervals + { + Delay2m = 120000, // Ms (2 Minutes) + Delay1m = 60000, // Ms (1 Minute) + Delay30s = 30000, // Ms (30 Seconds) + Delay15s = 15000, // Ms (15 Seconds) Used Only In Arena + None = 0 // Ms + } + + public enum BattlegroundStatus + { + None = 0, // first status, should mean bg is not instance + WaitQueue = 1, // means bg is empty and waiting for queue + WaitJoin = 2, // this means, that BG has already started and it is waiting for more players + InProgress = 3, // means bg is running + WaitLeave = 4 // means some faction has won BG and it is ending + } + + public enum BGHonorMode + { + Normal = 0, + Holiday, + HonorModeNum + } + + public enum GroupJoinBattlegroundResult + { + None = 0, + Deserters = 2, // You Cannot Join The BattlegroundYet Because You Or One Of Your Party Members Is Flagged As A Deserter. + ArenaTeamPartySize = 3, // Incorrect Party Size For This Arena. + TooManyQueues = 4, // You Can Only Be Queued For 2 Battles At Once + CannotQueueForRated = 5, // You Cannot Queue For A Rated Match While Queued For Other Battles + BattledgroundQueuedForRated = 6, // You Cannot Queue For Another Battle While Queued For A Rated Arena Match + TeamLeftQueue = 7, // Your Team Has Left The Arena Queue + NotInBattleground= 8, // You Can'T Do That In A Battleground. + JoinXpGain = 9, // Wtf, Doesn'T Exist In Client... + JoinRangeIndex = 10, // Cannot Join The Queue Unless All Members Of Your Party Are In The Same BattlegroundLevel Range. + JoinTimedOut = 11, // %S Was Unavailable To Join The Queue. (Uint64 Guid Exist In Client Cache) + //JoinTimedOut = 12, // Same As 11 + //TeamLeftQueue = 13, // Same As 7 + LfgCantUseBattleground= 14, // You Cannot Queue For A BattlegroundOr Arena While Using The Dungeon System. + InRandomBg = 15, // Can'T Do That While In A Random BattlegroundQueue. + InNonRandomBg = 16, // Can'T Queue For Random BattlegroundWhile In Another BattlegroundQueue. + BgDeveloperOnly = 17, + InvitationDeclined = 18, + MeetingStoneNotFound = 19, + WargameRequestFailure = 20, + BattlefieldTeamPartySize = 22, + NotOnTournamentRealm = 23, + PlayersFromDifferentRealms = 24, + RemoveFromPvpQueueGrantLevel = 33, + RemoveFromPvpQueueFactionChange = 34, + JoinFailed = 35, + DupeQueue = 43, + JoinNoValidSpecForRole = 44, + JoinRespec = 45, + AlreadyUsingLFGList = 46, + JoinMustCompleteQuest = 47 + } + + public enum ScoreType + { + KillingBlows = 1, + Deaths = 2, + HonorableKills = 3, + BonusHonor = 4, + DamageDone = 5, + HealingDone = 6, + + // Ws And Ey + FlagCaptures = 7, + FlagReturns = 8, + + // Ab And Ic + BasesAssaulted = 9, + BasesDefended = 10, + + // Av + GraveyardsAssaulted = 11, + GraveyardsDefended = 12, + TowersAssaulted = 13, + TowersDefended = 14, + MinesCaptured = 15, + + // Sota + DestroyedDemolisher = 16, + DestroyedWall = 17 + } + + //Arenas + public struct ArenaSpellIds + { + public const uint AllianceGoldFlag = 32724; + public const uint AllianceGreenFlag = 32725; + public const uint HordeGoldFlag = 35774; + public const uint HordeGreenFlag = 35775; + public const uint LastManStanding = 26549; // Arena Achievement Related + } + + public enum ArenaTeamCommandTypes + { + Create_S = 0x00, + Invite_SS = 0x01, + Quit_S = 0x03, + Founder_S = 0x0e + } + + public enum ArenaTeamCommandErrors + { + ArenaTeamCreated = 0x00, + ArenaTeamInternal = 0x01, + AlreadyInArenaTeam = 0x02, + AlreadyInArenaTeamS = 0x03, + InvitedToArenaTeam = 0x04, + AlreadyInvitedToArenaTeamS = 0x05, + ArenaTeamNameInvalid = 0x06, + ArenaTeamNameExistsS = 0x07, + ArenaTeamLeaderLeaveS = 0x08, + ArenaTeamPermissions = 0x08, + ArenaTeamPlayerNotInTeam = 0x09, + ArenaTeamPlayerNotInTeamSs = 0x0a, + ArenaTeamPlayerNotFoundS = 0x0b, + ArenaTeamNotAllied = 0x0c, + ArenaTeamIgnoringYouS = 0x13, + ArenaTeamTargetTooLowS = 0x15, + ArenaTeamTargetTooHighS = 0x16, + ArenaTeamTooManyMembersS = 0x17, + ArenaTeamNotFound = 0x1b, + ArenaTeamsLocked = 0x1e, + ArenaTeamTooManyCreate = 0x21, + } + + public enum ArenaTeamEvents + { + JoinSs = 3, // Player Name + Arena Team Name + LeaveSs = 4, // Player Name + Arena Team Name + RemoveSss = 5, // Player Name + Arena Team Name + Captain Name + LeaderIsSs = 6, // Player Name + Arena Team Name + LeaderChangedSss = 7, // Old Captain + New Captain + Arena Team Name + DisbandedS = 8 // Captain Name + Arena Team Name + } + + public enum ArenaTypes + { + BG = 0, + Team2v2 = 2, + Team3v3 = 3, + Team5v5 = 5 + } + + public enum ArenaErrorType + { + NoTeam = 0, + ExpiredCAIS = 1, + CantUseBattleground = 2 + } + + public enum ArenaTeamInfoType + { + Id = 0, + Type = 1, // new in 3.2 - team type? + Member = 2, // 0 - captain, 1 - member + GamesWeek = 3, + GamesSeason = 4, + WinsSeason = 5, + PersonalRating = 6, + End = 7 + } +} diff --git a/Framework/Constants/BattlePetConst.cs b/Framework/Constants/BattlePetConst.cs new file mode 100644 index 000000000..59e427f0d --- /dev/null +++ b/Framework/Constants/BattlePetConst.cs @@ -0,0 +1,75 @@ +/* + * Copyright (C) 2012-2017 CypherCore + * + * 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, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +namespace Framework.Constants +{ + public enum FlagsControlType + { + Apply = 1, + Remove = 2 + } + + public enum BattlePetError + { + CantHaveMorePetsOfThatType = 3, + CantHaveMorePets = 4, + TooHighLevelToUncage = 7, + + // TODO: find correct values if possible and needed (also wrong order) + DuplicateConvertedPet, + NeedToUnlock, + BadParam, + LockedPetAlreadyExists, + Ok, + Uncapturable, + CantInvalidCharacterGuid + } + + // taken from BattlePetState.db2 - it seems to store some initial values for battle pets + // there are only values used in BattlePetSpeciesState.db2 and BattlePetBreedState.db2 + // TODO: expand this enum if needed + public enum BattlePetState + { + MaxHealthBonus = 2, + InternalInitialLevel = 17, + StatPower = 18, + StatStamina = 19, + StatSpeed = 20, + ModDamageDealtPercent = 23, + Gender = 78, // 1 - Male, 2 - Female + CosmeticWaterBubbled = 85, + SpecialIsCockroach = 93, + CosmeticFlyTier = 128, + CosmeticBigglesworth = 144, + PassiveElite = 153, + PassiveBoss = 162, + CosmeticTreasureGoblin = 176, + // These Are Not In Battlepetstate.Db2 But Are Used In Battlepetspeciesstate.Db2 + StartWithBuff = 183, + StartWithBuff2 = 184, + // + CosmeticSpectralBlue = 196 + } + + public enum BattlePetSaveInfo + { + Unchanged = 0, + Changed = 1, + New = 2, + Removed = 3 + } +} diff --git a/Framework/Constants/BlackMarketConst.cs b/Framework/Constants/BlackMarketConst.cs new file mode 100644 index 000000000..28f16feac --- /dev/null +++ b/Framework/Constants/BlackMarketConst.cs @@ -0,0 +1,40 @@ +/* + * Copyright (C) 2012-2017 CypherCore + * + * 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, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +namespace Framework.Constants +{ + public struct BlackMarketConst + { + public const ulong MaxBid = 1000000UL* MoneyConstants.Gold; + } + public enum BlackMarketError + { + Ok = 0, + ItemNotFound = 1, + AlreadyBid = 2, + HigherBid = 4, + DatabaseError = 6, + NotEnoughMoney = 7, + RestrictedAccountTrial = 9 + } + + public enum BMAHMailAuctionAnswers + { + Outbid = 0, + Won = 1, + } +} diff --git a/Framework/Constants/CalendarConst.cs b/Framework/Constants/CalendarConst.cs new file mode 100644 index 000000000..44f70c176 --- /dev/null +++ b/Framework/Constants/CalendarConst.cs @@ -0,0 +1,117 @@ +/* + * Copyright (C) 2012-2017 CypherCore + * + * 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, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +namespace Framework.Constants +{ + public enum CalendarMailAnswers + { + EventRemovedMailSubject = 0, + InviteRemovedMailSubject = 0x100 + } + + public enum CalendarFlags + { + AllAllowed = 0x001, + InvitesLocked = 0x010, + WithoutInvites = 0x040, + GuildEvent = 0x400 + } + + public enum CalendarModerationRank + { + Player = 0, + Moderator = 1, + Owner = 2 + } + + public enum CalendarSendEventType + { + Get = 0, + Add = 1, + Copy = 2 + } + + public enum CalendarEventType + { + Raid = 0, + Dungeon = 1, + Pvp = 2, + Meeting = 3, + Other = 4, + Heroic = 5 + } + + public enum CalendarRepeatType + { + Never = 0, + Weekly = 1, + Biweekly = 2, + Monthly = 3 + } + + public enum CalendarInviteStatus + { + Invited = 0, + Accepted = 1, + Declined = 2, + Confirmed = 3, + Out = 4, + Standby = 5, + SignedUp = 6, + NotSignedUp = 7, + Tentative = 8, + Removed = 9 // Correct Name? + } + + public enum CalendarError + { + Ok = 0, + GuildEventsExceeded = 1, + EventsExceeded = 2, + SelfInvitesExceeded = 3, + OtherInvitesExceeded = 4, + Permissions = 5, + EventInvalid = 6, + NotInvited = 7, + Internal = 8, + GuildPlayerNotInGuild = 9, + AlreadyInvitedToEventS = 10, + PlayerNotFound = 11, + NotAllied = 12, + IgnoringYouS = 13, + InvitesExceeded = 14, + InvalidDate = 16, + InvalidTime = 17, + + NeedsTitle = 19, + EventPassed = 20, + EventLocked = 21, + DeleteCreatorFailed = 22, + SystemDisabled = 24, + RestrictedAccount = 25, + ArenaEventsExceeded = 26, + RestrictedLevel = 27, + UserSquelched = 28, + NoInvite = 29, + + EventWrongServer = 36, + InviteWrongServer = 37, + NoGuildInvites = 38, + InvalidSignup = 39, + NoModerator = 40 + } +} diff --git a/Framework/Constants/ChatConst.cs b/Framework/Constants/ChatConst.cs new file mode 100644 index 000000000..137107bf4 --- /dev/null +++ b/Framework/Constants/ChatConst.cs @@ -0,0 +1,109 @@ +/* + * Copyright (C) 2012-2017 CypherCore + * + * 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, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +namespace Framework.Constants +{ + public enum ChatNotify + { + JoinedNotice = 0x00, //+ "%S Joined Channel."; + LeftNotice = 0x01, //+ "%S Left Channel."; + //SuspendedNotice = 0x01, // "%S Left Channel."; + YouJoinedNotice = 0x02, //+ "Joined Channel: [%S]"; -- You Joined + //YouChangedNotice = 0x02, // "Changed Channel: [%S]"; + YouLeftNotice = 0x03, //+ "Left Channel: [%S]"; -- You Left + WrongPasswordNotice = 0x04, //+ "Wrong Password For %S."; + NotMemberNotice = 0x05, //+ "Not On Channel %S."; + NotModeratorNotice = 0x06, //+ "Not A Moderator Of %S."; + PasswordChangedNotice = 0x07, //+ "[%S] Password Changed By %S."; + OwnerChangedNotice = 0x08, //+ "[%S] Owner Changed To %S."; + PlayerNotFoundNotice = 0x09, //+ "[%S] Player %S Was Not Found."; + NotOwnerNotice = 0x0a, //+ "[%S] You Are Not The Channel Owner."; + ChannelOwnerNotice = 0x0b, //+ "[%S] Channel Owner Is %S."; + ModeChangeNotice = 0x0c, //? + AnnouncementsOnNotice = 0x0d, //+ "[%S] Channel Announcements Enabled By %S."; + AnnouncementsOffNotice = 0x0e, //+ "[%S] Channel Announcements Disabled By %S."; + ModerationOnNotice = 0x0f, //+ "[%S] Channel Moderation Enabled By %S."; + ModerationOffNotice = 0x10, //+ "[%S] Channel Moderation Disabled By %S."; + MutedNotice = 0x11, //+ "[%S] You Do Not Have Permission To Speak."; + PlayerKickedNotice = 0x12, //? "[%S] Player %S Kicked By %S."; + BannedNotice = 0x13, //+ "[%S] You Are Bannedstore From That Channel."; + PlayerBannedNotice = 0x14, //? "[%S] Player %S Bannedstore By %S."; + PlayerUnbannedNotice = 0x15, //? "[%S] Player %S Unbanned By %S."; + PlayerNotBannedNotice = 0x16, //+ "[%S] Player %S Is Not Bannedstore."; + PlayerAlreadyMemberNotice = 0x17, //+ "[%S] Player %S Is Already On The Channel."; + InviteNotice = 0x18, //+ "%2$S Has Invited You To Join The Channel '%1$S'."; + InviteWrongFactionNotice = 0x19, //+ "Target Is In The Wrong Alliance For %S."; + WrongFactionNotice = 0x1a, //+ "Wrong Alliance For %S."; + InvalidNameNotice = 0x1b, //+ "Invalid Channel Name"; + NotModeratedNotice = 0x1c, //+ "%S Is Not Moderated"; + PlayerInvitedNotice = 0x1d, //+ "[%S] You Invited %S To Join The Channel"; + PlayerInviteBannedNotice = 0x1e, //+ "[%S] %S Has Been Bannedstore."; + ThrottledNotice = 0x1f, //+ "[%S] The Number Of Messages That Can Be Sent To This Channel Is Limited, Please Wait To Send Another Message."; + NotInAreaNotice = 0x20, //+ "[%S] You Are Not In The Correct Area For This Channel."; -- The User Is Trying To Send A Chat To A Zone Specific Channel, And They'Re Not Physically In That Zone. + NotInLfgNotice = 0x21, //+ "[%S] You Must Be Queued In Looking For Group Before Joining This Channel."; -- The User Must Be In The Looking For Group System To Join Lfg Chat Channels. + VoiceOnNotice = 0x22, //+ "[%S] Channel Voice Enabled By %S."; + VoiceOffNotice = 0x23, //+ "[%S] Channel Voice Disabled By %S."; + TrialRestricted = 0x24, + NotAllowedInChannel = 0x25 + } + + public enum ChannelFlags + { + None = 0x00, + Custom = 0x01, + // 0x02 + Trade = 0x04, + NotLfg = 0x08, + General = 0x10, + City = 0x20, + Lfg = 0x40, + Voice = 0x80 + // General 0x18 = 0x10 | 0x08 + // Trade 0x3C = 0x20 | 0x10 | 0x08 | 0x04 + // LocalDefence 0x18 = 0x10 | 0x08 + // GuildRecruitment 0x38 = 0x20 | 0x10 | 0x08 + // LookingForGroup 0x50 = 0x40 | 0x10 + } + + public enum ChannelDBCFlags + { + None = 0x00000, + Initial = 0x00001, // General, Trade, Localdefense, Lfg + ZoneDep = 0x00002, // General, Trade, Localdefense, Guildrecruitment + Global = 0x00004, // Worlddefense + Trade = 0x00008, // Trade, Lfg + CityOnly = 0x00010, // Trade, Guildrecruitment, Lfg + CityOnly2 = 0x00020, // Trade, Guildrecruitment, Lfg + Defense = 0x10000, // Localdefense, Worlddefense + GuildReq = 0x20000, // Guildrecruitment + Lfg = 0x40000, // Lfg + Unk1 = 0x80000 // General + } + + public enum ChannelMemberFlags + { + None = 0x00, + Owner = 0x01, + Moderator = 0x02, + Voiced = 0x04, + Muted = 0x08, + Custom = 0x10, + MicMuted = 0x20 + // 0x40 + // 0x80 + } +} diff --git a/Framework/Constants/CliDBConst.cs b/Framework/Constants/CliDBConst.cs new file mode 100644 index 000000000..1fd6e29bd --- /dev/null +++ b/Framework/Constants/CliDBConst.cs @@ -0,0 +1,1669 @@ +/* + * Copyright (C) 2012-2017 CypherCore + * + * 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, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +namespace Framework.Constants +{ + public enum AbilytyLearnType : byte + { + OnSkillValue = 1, // Spell state will update depending on skill value + OnSkillLearn = 2 // Spell will be learned/removed together with entire skill + } + + public enum Anim + { + Stand = 0, + Death = 1, + Spell = 2, + Stop = 3, + Walk = 4, + Run = 5, + Dead = 6, + Rise = 7, + StandWound = 8, + CombatWound = 9, + CombatCritical = 10, + ShuffleLeft = 11, + ShuffleRight = 12, + WalkBackwards = 13, + Stun = 14, + HandsClosed = 15, + AttackUnarmed = 16, + Attack1h = 17, + Attack2h = 18, + Attack2hl = 19, + ParryUnarmed = 20, + Parry1h = 21, + Parry2h = 22, + Parry2hl = 23, + ShieldBlock = 24, + ReadyUnarmed = 25, + Ready1h = 26, + Ready2h = 27, + Ready2hl = 28, + ReadyBow = 29, + Dodge = 30, + SpellPrecast = 31, + SpellCast = 32, + SpellCastArea = 33, + NpcWelcome = 34, + NpcGoodbye = 35, + Block = 36, + JumpStart = 37, + Jump = 38, + JumpEnd = 39, + Fall = 40, + SwimIdle = 41, + Swim = 42, + SwimLeft = 43, + SwimRight = 44, + SwimBackwards = 45, + AttackBow = 46, + FireBow = 47, + ReadyRifle = 48, + AttackRifle = 49, + Loot = 50, + ReadySpellDirected = 51, + ReadySpellOmni = 52, + SpellCastDirected = 53, + SpellCastOmni = 54, + BattleRoar = 55, + ReadyAbility = 56, + Special1h = 57, + Special2h = 58, + ShieldBash = 59, + EmoteTalk = 60, + EmoteEat = 61, + EmoteWork = 62, + EmoteUseStanding = 63, + EmoteTalkExclamation = 64, + EmoteTalkQuestion = 65, + EmoteBow = 66, + EmoteWave = 67, + EmoteCheer = 68, + EmoteDance = 69, + EmoteLaugh = 70, + EmoteSleep = 71, + EmoteSitGround = 72, + EmoteRude = 73, + EmoteRoar = 74, + EmoteKneel = 75, + EmoteKiss = 76, + EmoteCry = 77, + EmoteChicken = 78, + EmoteBeg = 79, + EmoteApplaud = 80, + EmoteShout = 81, + EmoteFlex = 82, + EmoteShy = 83, + EmotePoint = 84, + Attack1hPierce = 85, + Attack2hLoosePierce = 86, + AttackOff = 87, + AttackOffPierce = 88, + Sheathe = 89, + HipSheathe = 90, + Mount = 91, + RunRight = 92, + RunLeft = 93, + MountSpecial = 94, + Kick = 95, + SitGroundDown = 96, + SitGround = 97, + SitGroundUp = 98, + SleepDown = 99, + Sleep = 100, + SleepUp = 101, + SitChairLow = 102, + SitChairMed = 103, + SitChairHigh = 104, + LoadBow = 105, + LoadRifle = 106, + AttackThrown = 107, + ReadyThrown = 108, + HoldBow = 109, + HoldRifle = 110, + HoldThrown = 111, + LoadThrown = 112, + EmoteSalute = 113, + KneelStart = 114, + KneelLoop = 115, + KneelEnd = 116, + AttackUnarmedOff = 117, + SpecialUnarmed = 118, + StealthWalk = 119, + StealthStand = 120, + Knockdown = 121, + EatingLoop = 122, + UseStandingLoop = 123, + ChannelCastDirected = 124, + ChannelCastOmni = 125, + Whirlwind = 126, + Birth = 127, + UseStandingStart = 128, + UseStandingEnd = 129, + CreatureSpecial = 130, + Drown = 131, + Drowned = 132, + FishingCast = 133, + FishingLoop = 134, + Fly = 135, + EmoteWorkNoSheathe = 136, + EmoteStunNoSheathe = 137, + EmoteUseStandingNoSheathe = 138, + SpellSleepDown = 139, + SpellKneelStart = 140, + SpellKneelLoop = 141, + SpellKneelEnd = 142, + Sprint = 143, + InFlight = 144, + Spawn = 145, + Close = 146, + Closed = 147, + Open = 148, + Opened = 149, + Destroy = 150, + Destroyed = 151, + Rebuild = 152, + Custom0 = 153, + Custom1 = 154, + Custom2 = 155, + Custom3 = 156, + Despawn = 157, + Hold = 158, + Decay = 159, + BowPull = 160, + BowRelease = 161, + ShipStart = 162, + ShipMoving = 163, + ShipStop = 164, + GroupArrow = 165, + Arrow = 166, + CorpseArrow = 167, + GuideArrow = 168, + Sway = 169, + DruidCatPounce = 170, + DruidCatRip = 171, + DruidCatRake = 172, + DruidCatRavage = 173, + DruidCatClaw = 174, + DruidCatCower = 175, + DruidBearSwipe = 176, + DruidBearBite = 177, + DruidBearMaul = 178, + DruidBearBash = 179, + DragonTail = 180, + DragonStomp = 181, + DragonSpit = 182, + DragonSpitHover = 183, + DragonSpitFly = 184, + EmoteYes = 185, + EmoteNo = 186, + JumpLandRun = 187, + LootHold = 188, + LootUp = 189, + StandHigh = 190, + Impact = 191, + Liftoff = 192, + Hover = 193, + SuccubusEntice = 194, + EmoteTrain = 195, + EmoteDead = 196, + EmoteDanceOnce = 197, + Deflect = 198, + EmoteEatNoSheathe = 199, + Land = 200, + Submerge = 201, + Submerged = 202, + Cannibalize = 203, + ArrowBirth = 204, + GroupArrowBirth = 205, + CorpseArrowBirth = 206, + GuideArrowBirth = 207, + EmoteTalkNoSheathe = 208, + EmotePointNoSheathe = 209, + EmoteSaluteNoSheathe = 210, + EmoteDanceSpecial = 211, + Mutilate = 212, + CustomSpell01 = 213, + CustomSpell02 = 214, + CustomSpell03 = 215, + CustomSpell04 = 216, + CustomSpell05 = 217, + CustomSpell06 = 218, + CustomSpell07 = 219, + CustomSpell08 = 220, + CustomSpell09 = 221, + CustomSpell10 = 222, + StealthRun = 223, + Emerge = 224, + Cower = 225, + Grab = 226, + GrabClosed = 227, + GrabThrown = 228, + FlyStand = 229, + FlyDeath = 230, + FlySpell = 231, + FlyStop = 232, + FlyWalk = 233, + FlyRun = 234, + FlyDead = 235, + FlyRise = 236, + FlyStandWound = 237, + FlyCombatWound = 238, + FlyCombatCritical = 239, + FlyShuffleLeft = 240, + FlyShuffleRight = 241, + FlyWalkBackwards = 242, + FlyStun = 243, + FlyHandsClosed = 244, + FlyAttackUnarmed = 245, + FlyAttack1h = 246, + FlyAttack2h = 247, + FlyAttack2hl = 248, + FlyParryUnarmed = 249, + FlyParry1h = 250, + FlyParry2h = 251, + FlyParry2hl = 252, + FlyShieldBlock = 253, + FlyReadyUnarmed = 254, + FlyReady1h = 255, + FlyReady2h = 256, + FlyReady2hl = 257, + FlyReadyBow = 258, + FlyDodge = 259, + FlySpellPrecast = 260, + FlySpellCast = 261, + FlySpellCastArea = 262, + FlyNpcWelcome = 263, + FlyNpcGoodbye = 264, + FlyBlock = 265, + FlyJumpStart = 266, + FlyJump = 267, + FlyJumpEnd = 268, + FlyFall = 269, + FlySwimIdle = 270, + FlySwim = 271, + FlySwimLeft = 272, + FlySwimRight = 273, + FlySwimBackwards = 274, + FlyAttackBow = 275, + FlyFireBow = 276, + FlyReadyRifle = 277, + FlyAttackRifle = 278, + FlyLoot = 279, + FlyReadySpellDirected = 280, + FlyReadySpellOmni = 281, + FlySpellCastDirected = 282, + FlySpellCastOmni = 283, + FlySpellBattleRoar = 284, + FlyReadyAbility = 285, + FlySpecial1h = 286, + FlySpecial2h = 287, + FlyShieldBash = 288, + FlyEmoteTalk = 289, + FlyEmoteEat = 290, + FlyEmoteWork = 291, + FlyUseStanding = 292, + FlyEmoteTalkExclamation = 293, + FlyEmoteTalkQuestion = 294, + FlyEmoteBow = 295, + FlyEmoteWave = 296, + FlyEmoteCheer = 297, + FlyEmoteDance = 298, + FlyEmoteLaugh = 299, + FlyEmoteSleep = 300, + FlyEmoteSitGround = 301, + FlyEmoteRude = 302, + FlyEmoteRoar = 303, + FlyEmoteKneel = 304, + FlyEmoteKiss = 305, + FlyEmoteCry = 306, + FlyEmoteChicken = 307, + FlyEmoteBeg = 308, + FlyEmoteApplaud = 309, + FlyEmoteShout = 310, + FlyEmoteFlex = 311, + FlyEmoteShy = 312, + FlyEmotePoint = 313, + FlyAttack1hPierce = 314, + FlyAttack2hLoosePierce = 315, + FlyAttackOff = 316, + FlyAttackOffPierce = 317, + FlySheath = 318, + FlyHipSheath = 319, + FlyMount = 320, + FlyRunRight = 321, + FlyRunLeft = 322, + FlyMountSpecial = 323, + FlyKick = 324, + FlySitGroundDown = 325, + FlySitGround = 326, + FlySitGroundUp = 327, + FlySleepDown = 328, + FlySleep = 329, + FlySleepUp = 330, + FlySitChairLow = 331, + FlySitChairMed = 332, + FlySitChairHigh = 333, + FlyLoadBow = 334, + FlyLoadRifle = 335, + FlyAttackThrown = 336, + FlyReadyThrown = 337, + FlyHoldBow = 338, + FlyHoldRifle = 339, + FlyHoldThrown = 340, + FlyLoadThrown = 341, + FlyEmoteSalute = 342, + FlyKneelStart = 343, + FlyKneelLoop = 344, + FlyKneelEnd = 345, + FlyAttackUnarmedOff = 346, + FlySpecialUnarmed = 347, + FlyStealthWalk = 348, + FlyStealthStand = 349, + FlyKnockdown = 350, + FlyEatingLoop = 351, + FlyUseStandingLoop = 352, + FlyChannelCastDirected = 353, + FlyChannelCastOmni = 354, + FlyWhirlwind = 355, + FlyBirth = 356, + FlyUseStandingStart = 357, + FlyUseStandingEnd = 358, + FlyCreatureSpecial = 359, + FlyDrown = 360, + FlyDrowned = 361, + FlyFishingCast = 362, + FlyFishingLoop = 363, + FlyFly = 364, + FlyEmoteWorkNoSheathe = 365, + FlyEmoteStunNoSheathe = 366, + FlyEmoteUseStandingNoSheathe = 367, + FlySpellSleepDown = 368, + FlySpellKneelStart = 369, + FlySpellKneelLoop = 370, + FlySpellKneelEnd = 371, + FlySprint = 372, + FlyInFlight = 373, + FlySpawn = 374, + FlyClose = 375, + FlyClosed = 376, + FlyOpen = 377, + FlyOpened = 378, + FlyDestroy = 379, + FlyDestroyed = 380, + FlyRebuild = 381, + FlyCustom0 = 382, + FlyCustom1 = 383, + FlyCustom2 = 384, + FlyCustom3 = 385, + FlyDespawn = 386, + FlyHold = 387, + FlyDecay = 388, + FlyBowPull = 389, + FlyBowRelease = 390, + FlyShipStart = 391, + FlyShipMoving = 392, + FlyShipStop = 393, + FlyGroupArrow = 394, + FlyArrow = 395, + FlyCorpseArrow = 396, + FlyGuideArrow = 397, + FlySway = 398, + FlyDruidCatPounce = 399, + FlyDruidCatRip = 400, + FlyDruidCatRake = 401, + FlyDruidCatRavage = 402, + FlyDruidCatClaw = 403, + FlyDruidCatCower = 404, + FlyDruidBearSwipe = 405, + FlyDruidBearBite = 406, + FlyDruidBearMaul = 407, + FlyDruidBearBash = 408, + FlyDragonTail = 409, + FlyDragonStomp = 410, + FlyDragonSpit = 411, + FlyDragonSpitHover = 412, + FlyDragonSpitFly = 413, + FlyEmoteYes = 414, + FlyEmoteNo = 415, + FlyJumpLandRun = 416, + FlyLootHold = 417, + FlyLootUp = 418, + FlyStandHigh = 419, + FlyImpact = 420, + FlyLiftoff = 421, + FlyHover = 422, + FlySuccubusEntice = 423, + FlyEmoteTrain = 424, + FlyEmoteDead = 425, + FlyEmoteDanceOnce = 426, + FlyDeflect = 427, + FlyEmoteEatNoSheathe = 428, + FlyLand = 429, + FlySubmerge = 430, + FlySubmerged = 431, + FlyCannibalize = 432, + FlyArrowBirth = 433, + FlyGroupArrowBirth = 434, + FlyCorpseArrowBirth = 435, + FlyGuideArrowBirth = 436, + FlyEmoteTalkNoSheathe = 437, + FlyEmotePointNoSheathe = 438, + FlyEmoteSaluteNoSheathe = 439, + FlyEmoteDanceSpecial = 440, + FlyMutilate = 441, + FlyCustomSpell01 = 442, + FlyCustomSpell02 = 443, + FlyCustomSpell03 = 444, + FlyCustomSpell04 = 445, + FlyCustomSpell05 = 446, + FlyCustomSpell06 = 447, + FlyCustomSpell07 = 448, + FlyCustomSpell08 = 449, + FlyCustomSpell09 = 450, + FlyCustomSpell10 = 451, + FlyStealthRun = 452, + FlyEmerge = 453, + FlyCower = 454, + FlyGrab = 455, + FlyGrabClosed = 456, + FlyGrabThrown = 457, + ToFly = 458, + ToHover = 459, + ToGround = 460, + FlyToFly = 461, + FlyToHover = 462, + FlyToGround = 463, + Settle = 464, + FlySettle = 465, + DeathStart = 466, + DeathLoop = 467, + DeathEnd = 468, + FlyDeathStart = 469, + FlyDeathLoop = 470, + FlyDeathEnd = 471, + DeathEndHold = 472, + FlyDeathEndHold = 473, + Strangulate = 474, + FlyStrangulate = 475, + ReadyJoust = 476, + LoadJoust = 477, + HoldJoust = 478, + FlyReadyJoust = 479, + FlyLoadJoust = 480, + FlyHoldJoust = 481, + AttackJoust = 482, + FlyAttackJoust = 483, + ReclinedMount = 484, + FlyReclinedMount = 485, + ToAltered = 486, + FromAltered = 487, + FlyToAltered = 488, + FlyFromAltered = 489, + InStocks = 490, + FlyInStocks = 491, + VehicleGrab = 492, + VehicleThrow = 493, + FlyVehicleGrab = 494, + FlyVehicleThrow = 495, + ToAlteredPostSwap = 496, + FromAlteredPostSwap = 497, + FlyToAlteredPostSwap = 498, + FlyFromAlteredPostSwap = 499, + ReclinedMountPassenger = 500, + FlyReclinedMountPassenger = 501, + Carry2h = 502, + Carried2h = 503, + FlyCarry2h = 504, + FlyCarried2h = 505, + EmoteSniff = 506, + EmoteFlySniff = 507, + AttackFist1h = 508, + FlyAttackFist1h = 509, + AttackFist1hOff = 510, + FlyAttackFist1hOff = 511, + ParryFist1h = 512, + FlyParryFist1h = 513, + ReadyFist1h = 514, + FlyReadyFist1h = 515, + SpecialFist1h = 516, + FlySpecialFist1h = 517, + EmoteReadStart = 518, + FlyEmoteReadStart = 519, + EmoteReadLoop = 520, + FlyEmoteReadLoop = 521, + EmoteReadEnd = 522, + FlyEmoteReadEnd = 523, + SwimRun = 524, + FlySwimRun = 525, + SwimWalk = 526, + FlySwimWalk = 527, + SwimWalkBackwards = 528, + FlySwimWalkBackwards = 529, + SwimSprint = 530, + FlySwimSprint = 531, + MountSwimIdle = 532, + FlyMountSwimIdle = 533, + MountSwimBackwards = 534, + FlyMountSwimBackwards = 535, + MountSwimLeft = 536, + FlyMountSwimLeft = 537, + MountSwimRight = 538, + FlyMountSwimRight = 539, + MountSwimRun = 540, + FlyMountSwimRun = 541, + MountSwimSprint = 542, + FlyMountSwimSprint = 543, + MountSwimWalk = 544, + FlyMountSwimWalk = 545, + MountSwimWalkBackwards = 546, + FlyMountSwimWalkBackwards = 547, + MountFlightIdle = 548, + FlyMountFlightIdle = 549, + MountFlightBackwards = 550, + FlyMountFlightBackwards = 551, + MountFlightLeft = 552, + FlyMountFlightLeft = 553, + MountFlightRight = 554, + FlyMountFlightRight = 555, + MountFlightRun = 556, + FlyMountFlightRun = 557, + MountFlightSprint = 558, + FlyMountFlightSprint = 559, + MountFlightWalk = 560, + FlyMountFlightWalk = 561, + MountFlightWalkBackwards = 562, + FlyMountFlightWalkBackwards = 563, + MountFlightStart = 564, + FlyMountFlightStart = 565, + MountSwimStart = 566, + FlyMountSwimStart = 567, + MountSwimLand = 568, + FlyMountSwimLand = 569, + MountSwimLandRun = 570, + FlyMountSwimLandRun = 571, + MountFlightLand = 572, + FlyMountFlightLand = 573, + MountFlightLandRun = 574, + FlyMountFlightLandRun = 575, + ReadyBlowDart = 576, + FlyReadyBlowDart = 577, + LoadBlowDart = 578, + FlyLoadBlowDart = 579, + HoldBlowDart = 580, + FlyHoldBlowDart = 581, + AttackBlowDart = 582, + FlyAttackBlowDart = 583, + CarriageMount = 584, + FlyCarriageMount = 585, + CarriagePassengerMount = 586, + FlyCarriagePassengerMount = 587, + CarriageMountAttack = 588, + FlyCarriageMountAttack = 589, + BartenderStand = 590, + FlyBartenderStand = 591, + BartenderWalk = 592, + FlyBartenderWalk = 593, + BartenderRun = 594, + FlyBartenderRun = 595, + BartenderShuffleLeft = 596, + FlyBartenderShuffleLeft = 597, + BartenderShuffleRight = 598, + FlyBartenderShuffleRight = 599, + BartenderEmoteTalk = 600, + FlyBartenderEmoteTalk = 601, + BartenderEmotePoint = 602, + FlyBartenderEmotePoint = 603, + BarmaidStand = 604, + FlyBarmaidStand = 605, + BarmaidWalk = 606, + FlyBarmaidWalk = 607, + BarmaidRun = 608, + FlyBarmaidRun = 609, + BarmaidShuffleLeft = 610, + FlyBarmaidShuffleLeft = 611, + BarmaidShuffleRight = 612, + FlyBarmaidShuffleRight = 613, + BarmaidEmoteTalk = 614, + FlyBarmaidEmoteTalk = 615, + BarmaidEmotePoint = 616, + FlyBarmaidEmotePoint = 617, + MountSelfIdle = 618, + FlyMountSelfIdle = 619, + MountSelfWalk = 620, + FlyMountSelfWalk = 621, + MountSelfRun = 622, + FlyMountSelfRun = 623, + MountSelfSprint = 624, + FlyMountSelfSprint = 625, + MountSelfRunLeft = 626, + FlyMountSelfRunLeft = 627, + MountSelfRunRight = 628, + FlyMountSelfRunRight = 629, + MountSelfShuffleLeft = 630, + FlyMountSelfShuffleLeft = 631, + MountSelfShuffleRight = 632, + FlyMountSelfShuffleRight = 633, + MountSelfWalkBackwards = 634, + FlyMountSelfWalkBackwards = 635, + MountSelfSpecial = 636, + FlyMountSelfSpecial = 637, + MountSelfJump = 638, + FlyMountSelfJump = 639, + MountSelfJumpStart = 640, + FlyMountSelfJumpStart = 641, + MountSelfJumpEnd = 642, + FlyMountSelfJumpEnd = 643, + MountSelfJumpLandRun = 644, + FlyMountSelfJumpLandRun = 645, + MountSelfStart = 646, + FlyMountSelfStart = 647, + MountSelfFall = 648, + FlyMountSelfFall = 649, + Stormstrike = 650, + FlyStormstrike = 651, + ReadyJoustNoSheathe = 652, + FlyReadyJoustNoSheathe = 653, + Slam = 654, + FlySlam = 655, + DeathStrike = 656, + FlyDeathStrike = 657, + SwimAttackUnarmed = 658, + FlySwimAttackUnarmed = 659, + SpinningKick = 660, + FlySpinningKick = 661, + RoundHouseKick = 662, + FlyRoundHouseKick = 663, + RollStart = 664, + FlyRollStart = 665, + Roll = 666, + FlyRoll = 667, + RollEnd = 668, + FlyRollEnd = 669, + PalmStrike = 670, + FlyPalmStrike = 671, + MonkOffenseAttackUnarmed = 672, + FlyMonkOffenseAttackUnarmed = 673, + MonkOffenseAttackUnarmedOff = 674, + FlyMonkOffenseAttackUnarmedOff = 675, + MonkOffenseParryUnarmed = 676, + FlyMonkOffenseParryUnarmed = 677, + MonkOffenseReadyUnarmed = 678, + FlyMonkOffenseReadyUnarmed = 679, + MonkOffenseSpecialUnarmed = 680, + FlyMonkOffenseSpecialUnarmed = 681, + MonkDefenseAttackUnarmed = 682, + FlyMonkDefenseAttackUnarmed = 683, + MonkDefenseAttackUnarmedOff = 684, + FlyMonkDefenseAttackUnarmedOff = 685, + MonkDefenseParryUnarmed = 686, + FlyMonkDefenseParryUnarmed = 687, + MonkDefenseReadyUnarmed = 688, + FlyMonkDefenseReadyUnarmed = 689, + MonkDefenseSpecialUnarmed = 690, + FlyMonkDefenseSpecialUnarmed = 691, + MonkHealAttackUnarmed = 692, + FlyMonkHealAttackUnarmed = 693, + MonkHealAttackUnarmedOff = 694, + FlyMonkHealAttackUnarmedOff = 695, + MonkHealParryUnarmed = 696, + FlyMonkHealParryUnarmed = 697, + MonkHealReadyUnarmed = 698, + FlyMonkHealReadyUnarmed = 699, + MonkHealSpecialUnarmed = 700, + FlyMonkHealSpecialUnarmed = 701, + FlyingKick = 702, + FlyFlyingKick = 703, + FlyingKickStart = 704, + FlyFlyingKickStart = 705, + FlyingKickEnd = 706, + FlyFlyingKickEnd = 707, + CraneStart = 708, + FlyCraneStart = 709, + CraneLoop = 710, + FlyCraneLoop = 711, + CraneEnd = 712, + FlyCraneEnd = 713, + Despawned = 714, + FlyDespawned = 715, + ThousandFists = 716, + FlyThousandFists = 717, + MonkHealReadySpellDirected = 718, + FlyMonkHealReadySpellDirected = 719, + MonkHealReadySpellOmni = 720, + FlyMonkHealReadySpellOmni = 721, + MonkHealSpellCastDirected = 722, + FlyMonkHealSpellCastDirected = 723, + MonkHealSpellCastOmni = 724, + FlyMonkHealSpellCastOmni = 725, + MonkHealChannelCastDirected = 726, + FlyMonkHealChannelCastDirected = 727, + MonkHealChannelCastOmni = 728, + FlyMonkHealChannelCastOmni = 729, + Torpedo = 730, + FlyTorpedo = 731, + Meditate = 732, + FlyMeditate = 733, + BreathOfFire = 734, + FlyBreathOfFire = 735, + RisingSunKick = 736, + FlyRisingSunKick = 737, + GroundKick = 738, + FlyGroundKick = 739, + KickBack = 740, + FlyKickBack = 741, + PetBattleStand = 742, + FlyPetBattleStand = 743, + PetBattleDeath = 744, + FlyPetBattleDeath = 745, + PetBattleRun = 746, + FlyPetBattleRun = 747, + PetBattleWound = 748, + FlyPetBattleWound = 749, + PetBattleAttack = 750, + FlyPetBattleAttack = 751, + PetBattleReadySpell = 752, + FlyPetBattleReadySpell = 753, + PetBattleSpellCast = 754, + FlyPetBattleSpellCast = 755, + PetBattleCustom0 = 756, + FlyPetBattleCustom0 = 757, + PetBattleCustom1 = 758, + FlyPetBattleCustom1 = 759, + PetBattleCustom2 = 760, + FlyPetBattleCustom2 = 761, + PetBattleCustom3 = 762, + FlyPetBattleCustom3 = 763, + PetBattleVictory = 764, + FlyPetBattleVictory = 765, + PetBattleLoss = 766, + FlyPetBattleLoss = 767, + PetBattleStun = 768, + FlyPetBattleStun = 769, + PetBattleDead = 770, + FlyPetBattleDead = 771, + PetBattleFreeze = 772, + FlyPetBattleFreeze = 773, + MonkOffenseAttackWeapon = 774, + FlyMonkOffenseAttackWeapon = 775, + BarTendEmoteWave = 776, + FlyBarTendEmoteWave = 777, + BarServerEmoteTalk = 778, + FlyBarServerEmoteTalk = 779, + BarServerEmoteWave = 780, + FlyBarServerEmoteWave = 781, + BarServerPourDrinks = 782, + FlyBarServerPourDrinks = 783, + BarServerPickup = 784, + FlyBarServerPickup = 785, + BarServerPutDown = 786, + FlyBarServerPutDown = 787, + BarSweepStand = 788, + FlyBarSweepStand = 789, + BarPatronSit = 790, + FlyBarPatronSit = 791, + BarPatronSitEmoteTalk = 792, + FlyBarPatronSitEmoteTalk = 793, + BarPatronStand = 794, + FlyBarPatronStand = 795, + BarPatronStandEmoteTalk = 796, + FlyBarPatronStandEmoteTalk = 797, + BarPatronStandEmotePoint = 798, + FlyBarPatronStandEmotePoint = 799, + CarrionSwarm = 800, + FlyCarrionSwarm = 801, + WheelLoop = 802, + FlyWheelLoop = 803, + StandCharacterCreate = 804, + FlyStandCharacterCreate = 805, + MountChopper = 806, + FlyMountChopper = 807, + FacePose = 808, + FlyFacePose = 809, + WarriorColossusSmash = 810, + FlyWarriorColossusSmash = 811, + WarriorMortalStrike = 812, + FlyWarriorMortalStrike = 813, + WarriorWhirlwind = 814, + FlyWarriorWhirlwind = 815, + WarriorCharge = 816, + FlyWarriorCharge = 817, + WarriorChargeStart = 818, + FlyWarriorChargeStart = 819, + WarriorChargeEnd = 820, + FlyWarriorChargeEnd = 821 + } + + public enum AreaFlags + { + Snow = 0x01, // Snow (Only Dun Morogh, Naxxramas, Razorfen Downs And Winterspring) + Unk1 = 0x02, // Razorfen Downs, Naxxramas And Acherus: The Ebon Hold (3.3.5a) + Unk2 = 0x04, // Only Used For Areas On Map 571 (Development Before) + SlaveCapital = 0x08, // City And City Subsones + Unk3 = 0x10, // Can'T Find Common Meaning + SlaveCapital2 = 0x20, // Slave Capital City Flag? + AllowDuels = 0x40, // Allow To Duel Here + Arena = 0x80, // Arena, Both Instanced And World Arenas + Capital = 0x100, // Main Capital City Flag + City = 0x200, // Only For One Zone Named "City" (Where It Located?) + Outland = 0x400, // Expansion Zones? (Only Eye Of The Storm Not Have This Flag, But Have 0x00004000 Flag) + Sanctuary = 0x800, // Sanctuary Area (Pvp Disabled) + NeedFly = 0x1000, // Unknown + Unused1 = 0x2000, // Unused In 3.3.5a + Outland2 = 0x4000, // Expansion Zones? (Only Circle Of Blood Arena Not Have This Flag, But Have 0x00000400 Flag) + OutdoorPvp = 0x8000, // Pvp Objective Area? (Death'S Door Also Has This Flag Although It'S No Pvp Object Area) + ArenaInstance = 0x10000, // Used By Instanced Arenas Only + Unused2 = 0x20000, // Unused In 3.3.5a + ContestedArea = 0x40000, // On Pvp Servers These Areas Are Considered Contested, Even Though The Zone It Is Contained In Is A Horde/Alliance Territory. + Unk6 = 0x80000, // Valgarde And Acherus: The Ebon Hold + Lowlevel = 0x100000, // Used For Some Starting Areas With AreaLevel <= 15 + Town = 0x200000, // Small Towns With Inn + RestZoneHorde = 0x400000, // Warsong Hold, Acherus: The Ebon Hold, New Agamand Inn, Vengeance Landing Inn, Sunreaver Pavilion (Something To Do With Team?) + RestZoneAlliance = 0x800000, // Valgarde, Acherus: The Ebon Hold, Westguard Inn, Silver Covenant Pavilion (Something To Do With Team?) + Wintergrasp = 0x1000000, // Wintergrasp And It'S Subzones + Inside = 0x2000000, // Used For Determinating Spell Related Inside/Outside Questions In Map.Isoutdoors + Outside = 0x4000000, // Used For Determinating Spell Related Inside/Outside Questions In Map.Isoutdoors + CanHearthAndResurrect = 0x8000000, // Can Hearth And Resurrect From Area + NoFlyZone = 0x20000000, // Marks Zones Where You Cannot Fly + Unk9 = 0x40000000, + } + + public enum ArtifactPowerFlag : byte + { + Gold = 0x01, + First = 0x02, + Final = 0x04, + ScalesWithNumPowers = 0x08, + DontCountFirstBonusRank = 0x10, + } + + public enum BattlegroundBracketId // bracketId for level ranges + { + First = 0, + Last = 11, + Max + } + + public enum CharSectionFlags + { + Player = 0x01, + DeathKnight = 0x04, + DemonHunter = 0x20 + } + + public enum CharSectionType + { + SkinLowRes = 0, + FaceLowRes = 1, + FacialHairLowRes = 2, + HairLowRes = 3, + UnderwearLowRes = 4, + Skin = 5, + Face = 6, + FacialHair = 7, + Hair = 8, + Underwear = 9, + CustomDisplay1LowRes = 10, + CustomDisplay1 = 11, + CustomDisplay2LowRes = 12, + CustomDisplay2 = 13, + CustomDisplay3LowRes = 14, + CustomDisplay3 = 15 + } + + public enum ChrSpecializationFlag + { + Caster = 0x01, + Ranged = 0x02, + Melee = 0x04, + Unknown = 0x08, + DualWieldTwoHanded = 0x10, // Used For Cunitdisplay::Setsheatheinvertedfordualwield + PetOverrideSpec = 0x20, + Recommended = 0x40, + } + + public enum Curves + { + ArtifactRelicItemLevelBonus = 1718 + } + + public enum Emote + { + OneshotNone = 0, + OneshotTalk = 1, + OneshotBow = 2, + OneshotWave = 3, + OneshotCheer = 4, + OneshotExclamation = 5, + OneshotQuestion = 6, + OneshotEat = 7, + StateDance = 10, + OneshotLaugh = 11, + StateSleep = 12, + StateSit = 13, + OneshotRude = 14, + OneshotRoar = 15, + OneshotKneel = 16, + OneshotKiss = 17, + OneshotCry = 18, + OneshotChicken = 19, + OneshotBeg = 20, + OneshotApplaud = 21, + OneshotShout = 22, + OneshotFlex = 23, + OneshotShy = 24, + OneshotPoint = 25, + StateStand = 26, + StateReadyUnarmed = 27, + StateWorkSheathed = 28, + StatePoint = 29, + StateNone = 30, + OneshotWound = 33, + OneshotWoundCritical = 34, + OneshotAttackUnarmed = 35, + OneshotAttack1h = 36, + OneshotAttack2htight = 37, + OneshotAttack2hLoose = 38, + OneshotParryUnarmed = 39, + OneshotParryShield = 43, + OneshotReadyUnarmed = 44, + OneshotReady1h = 45, + OneshotReadyBow = 48, + OneshotSpellPrecast = 50, + OneshotSpellCast = 51, + OneshotBattleRoar = 53, + OneshotSpecialattack1h = 54, + OneshotKick = 60, + OneshotAttackThrown = 61, + StateStun = 64, + StateDead = 65, + OneshotSalute = 66, + StateKneel = 68, + StateUseStanding = 69, + OneshotWaveNoSheathe = 70, + OneshotCheerNoSheathe = 71, + OneshotEatNoSheathe = 92, + StateStunNoSheathe = 93, + OneshotDance = 94, + OneshotSaluteNoSheath = 113, + StateUseStandingNoSheathe = 133, + OneshotLaughNoSheathe = 153, + StateWork = 173, + StateSpellPrecast = 193, + OneshotReadyRifle = 213, + StateReadyRifle = 214, + StateWorkMining = 233, + StateWorkChopwood = 234, + StateApplaud = 253, + OneshotLiftoff = 254, + OneshotYes = 273, + OneshotNo = 274, + OneshotTrain = 275, + OneshotLand = 293, + StateAtEase = 313, + StateReady1h = 333, + StateSpellKneelStart = 353, + StateSubmerged = 373, + OneshotSubmerge = 374, + StateReady2h = 375, + StateReadyBow = 376, + OneshotMountSpecial = 377, + StateTalk = 378, + StateFishing = 379, + OneshotFishing = 380, + OneshotLoot = 381, + StateWhirlwind = 382, + StateDrowned = 383, + StateHoldBow = 384, + StateHoldRifle = 385, + StateHoldThrown = 386, + OneshotDrown = 387, + OneshotStomp = 388, + OneshotAttackOff = 389, + OneshotAttackOffPierce = 390, + StateRoar = 391, + StateLaugh = 392, + OneshotCreatureSpecial = 393, + OneshotJumplandrun = 394, + OneshotJumpend = 395, + OneshotTalkNoSheathe = 396, + OneshotPointNoSheathe = 397, + StateCannibalize = 398, + OneshotJumpstart = 399, + StateDancespecial = 400, + OneshotDancespecial = 401, + OneshotCustomSpell01 = 402, + OneshotCustomSpell02 = 403, + OneshotCustomSpell03 = 404, + OneshotCustomSpell04 = 405, + OneshotCustomSpell05 = 406, + OneshotCustomSpell06 = 407, + OneshotCustomSpell07 = 408, + OneshotCustomSpell08 = 409, + OneshotCustomSpell09 = 410, + OneshotCustomSpell10 = 411, + StateExclaim = 412, + StateDanceCustom = 413, + StateSitChairMed = 415, + StateCustomSpell01 = 416, + StateCustomSpell02 = 417, + StateEat = 418, + StateCustomSpell04 = 419, + StateCustomSpell03 = 420, + StateCustomSpell05 = 421, + StateSpelleffectHold = 422, + StateEatNoSheathe = 423, + StateMount = 424, + StateReady2hl = 425, + StateSitChairHigh = 426, + StateFall = 427, + StateLoot = 428, + StateSubmergedNew = 429, + OneshotCower = 430, + StateCower = 431, + OneshotUseStanding = 432, + StateStealthStand = 433, + OneshotOmnicastGhoul = 434, + OneshotAttackBow = 435, + OneshotAttackRifle = 436, + StateSwimIdle = 437, + StateAttackUnarmed = 438, + OneshotSpellCastWSound = 439, + OneshotDodge = 440, + OneshotParry1h = 441, + OneshotParry2h = 442, + OneshotParry2hl = 443, + StateFlyfall = 444, + OneshotFlydeath = 445, + StateFlyFall = 446, + OneshotFlySitGroundDown = 447, + OneshotFlySitGroundUp = 448, + OneshotEmerge = 449, + OneshotDragonSpit = 450, + StateSpecialUnarmed = 451, + OneshotFlygrab = 452, + StateFlygrabclosed = 453, + OneshotFlygrabthrown = 454, + StateFlySitGround = 455, + StateWalkBackwards = 456, + OneshotFlytalk = 457, + OneshotFlyattack1h = 458, + StateCustomSpell08 = 459, + OneshotFlyDragonSpit = 460, + StateSitChairLow = 461, + OneshotStun = 462, + OneshotSpellCastOmni = 463, + StateReadyThrown = 465, + OneshotWorkChopwood = 466, + OneshotWorkMining = 467, + StateSpellChannelOmni = 468, + StateSpellChannelDirected = 469, + StandStateNone = 470, + StateReadyjoust = 471, + StateStrangulate = 472, + StateStrangulate2 = 473, + StateReadySpellOmni = 474, + StateHoldJoust = 475, + OneshotCryJaina = 476, + OneshotSpecialUnarmed = 477, + StateDanceNosheathe = 478, + OneshotSniff = 479, + OneshotDragonstomp = 480, + OneshotKnockdown = 482, + StateRead = 483, + OneshotFlyemotetalk = 485, + StateReadAllowmovement = 492, + StateCustomSpell06 = 498, + StateCustomSpell07 = 499, + StateCustomSpell082 = 500, + StateCustomSpell09 = 501, + StateCustomSpell10 = 502, + StateReady1hAllowMovement = 505, + StateReady2hAllowMovement = 506, + OneshotMonkoffenseAttackunarmed = 507, + OneshotMonkoffenseSpecialunarmed = 508, + OneshotMonkoffenseParryunarmed = 509, + StateMonkoffenseReadyunarmed = 510, + OneshotPalmstrike = 511, + StateCrane = 512, + OneshotOpen = 517, + StateReadChristmas = 518, + OneshotFlyattack2hl = 526, + OneshotFlyattackthrown = 527, + StateFlyreadyspelldirected = 528, + StateFlyReady1h = 531, + StateMeditate = 533, + StateFlyReady2hl = 534, + OneshotToground = 535, + OneshotTofly = 536, + StateAttackthrown = 537, + StateSpellChannelDirectedNosound = 538, + OneshotWork = 539, + StateReadyunarmedNosound = 540, + OneshotMonkoffenseAttackunarmedoff = 543, + ReclinedMountPassenger = 546, + OneshotQuestion_2 = 547, + OneshotSpellChannelDirectedNosound = 549, + StateKneel2 = 550, + OneshotFlyattackunarmed = 551, + OneshotFlycombatwound = 552, + OneshotMountselfspecial = 553, + OneshotAttackunarmedNosound = 554, + StateWoundcriticalDoesntWork = 555, + OneshotAttack1hNoSound = 556, + StateMountSelfIdle = 557, + OneshotWalk = 558, + StateOpened = 559, + StateCustomspell03 = 564, + OneshotBreathoffire = 565, + StateAttack1h = 567, + StateWorkChopwood2 = 568, + StateUsestandingLoop = 569, + StateUsestanding = 572, + OneshotSheath = 573, + OneshotLaughNoSound = 574, + ReclinedMount = 575, + OneshotAttack1h2 = 577, + StateCryNosound = 578, + OneshotCryNosound = 579, + OneshotCombatcritical = 584, + StateTrain = 585, + StateWorkChopwoodLumberAxe = 586, + OneshotSpecialattack2h = 587, + StateReadAndTalk = 588, + OneshotStandVar1 = 589, + RexxarStranglesGoblin = 590, + OneshotStandVar2 = 591, + OneshotDeath = 592, + StateTalkonce = 595, + StateAttack2h = 596, + StateSitGround = 598, + StateWorkChopwood3 = 599, + StateCustomspell01 = 601, + OneshotCombatwound = 602, + OneshotTalkExclamation = 603, + OneshotQuestion2 = 604, + StateCry = 605, + StateUsestandingLoop2 = 606, + StateWorkSmith = 613, + StateWorkChopwood4 = 614, + StateCustomspell02 = 615, + StateReadAndSit = 616, + StateParryUnarmed = 619, + StateBlockShield = 620, + StateSitGround2 = 621 + } + + public enum GlyphSlotType + { + Major = 0, + Minor = 1, + Prime = 2 + } + + public enum ItemSetFlags + { + LegacyInactive = 0x01, + } + + public enum ItemSpecStat : byte + { + Intellect = 0, + Agility = 1, + Strength = 2, + Spirit = 3, + Hit = 4, + Dodge = 5, + Parry = 6, + OneHandedAxe = 7, + TwoHandedAxe = 8, + OneHandedSword = 9, + TwoHandedSword = 10, + OneHandedMace = 11, + TwoHandedMace = 12, + Dagger = 13, + FistWeapon = 14, + Gun = 15, + Bow = 16, + Crossbow = 17, + Staff = 18, + Polearm = 19, + Thrown = 20, + Wand = 21, + Shield = 22, + Relic = 23, + Crit = 24, + Haste = 25, + BonusArmor = 26, + Cloak = 27, + Warglaives = 28, + RelicIron = 29, + RelicBlood = 30, + RelicShadow = 31, + RelicFel = 32, + RelicArcane = 33, + RelicFrost = 34, + RelicFire = 35, + RelicWater = 36, + RelicLife = 37, + RelicWind = 38, + RelicHoly = 39, + + None = 40 + } + + public enum LockKeyType + { + None = 0, + Item = 1, + Skill = 2 + } + + public enum LockType + { + Picklock = 1, + Herbalism = 2, + Mining = 3, + DisarmTrap = 4, + Open = 5, + Treasure = 6, + CalcifiedElvenGems = 7, + Close = 8, + ArmTrap = 9, + QuickOpen = 10, + QuickClose = 11, + OpenTinkering = 12, + OpenKneeling = 13, + OpenAttacking = 14, + Gahzridian = 15, + Blasting = 16, + SlowOpen = 17, + SlowClose = 18, + Fishing = 19, + Inscription = 20, + OpenFromVehicle = 21, + Archaelogy = 22, + PvpOpenFast = 23, + LumberMill = 28 + } + + public enum MapTypes : byte + { + Common = 0, + Instance = 1, + Raid = 2, + Battleground = 3, + Arena = 4, + Scenario = 5 + } + + public enum MapDifficultyFlags : byte + { + CannotExtend = 0x10 + } + + public enum MountCapabilityFlags : byte + { + Unk1 = 0x1, + Unk2 = 0x2, + CanPitch = 0x4, // client checks MOVEMENTFLAG2_FULL_SPEED_PITCHING + CanSwim = 0x8, // client checks MOVEMENTFLAG_SWIMMING + } + + public enum MountFlags + { + CanPitch = 0x4, // client checks MOVEMENTFLAG2_FULL_SPEED_PITCHING + CanSwim = 0x8, // client checks MOVEMENTFLAG_SWIMMING + SelfMount = 0x02, // Player becomes the mount himself + FactionSpecific = 0x04, + PreferredSwimming = 0x10, + PreferredWaterWalking = 0x20, + HideIfUnknown = 0x40 + } + + public enum PrestigeLevelInfoFlags : byte + { + Disabled = 0x01 // Prestige levels with this flag won't be included to calculate max prestigelevel. + } + + public enum QuestPackageFilter : byte + { + LootSpecialization = 0, // Players can select this quest reward if it matches their selected loot specialization + Class = 1, // Players can select this quest reward if it matches their class + Unmatched = 2, // Players can select this quest reward if no class/loot_spec rewards are available + Everyone = 3 // Players can always select this quest reward + } + + public enum ScenarioStepFlags : byte + { + BonusObjective = 0x1, + HeroicOnly = 0x2 + } + + public enum SkillRaceClassInfoFlags : ushort + { + NoSkillupMessage = 0x2, + AlwaysMaxValue = 0x10, + Unlearnable = 0x20, // Skill can be unlearned + IncludeInSort = 0x80, // Spells belonging to a skill with this flag will additionally compare skill ids when sorting spellbook in client + NotTrainable = 0x100, + MonoValue = 0x400 // Skill always has value 1 + } + + public enum SpellCategoryFlags : byte + { + CooldownScalesWithWeaponSpeed = 0x01, // unused + CooldownStartsOnEvent = 0x04, + CooldownExpiresAtDailyReset = 0x08 + } + + public enum SpellProcsPerMinuteModType : byte + { + Haste = 1, + Crit = 2, + Class = 3, + Spec = 4, + Race = 5, + ItemLevel = 6, + Battleground = 7 + } + + public enum SpellShapeshiftFormFlags + { + IsNotAShapeshift = 0x0001, + CannotCancel = 0x0002, // Player Cannot Cancel The Aura Giving This Shapeshift + CanInteract = 0x0008, // If The Form Does Not Have IsNotAShapeshift Then This Flag Must Be Present To Allow Npc Interaction + CanEquipItems = 0x0040, // If The Form Does Not Have IsNotAShapeshift Then This Flag Allows Equipping Items Without ItemFlagUsableWhenShapeshifted + CanUseItems = 0x0080, // If The Form Does Not Have IsNotAShapeshift Then This Flag Allows Using Items Without ItemFlagUsableWhenShapeshifted + CanAutoUnshift = 0x0100, // Clientside + PreventLfgTeleport = 0x0200, + PreventUsingOwnSkills = 0x0400, // Prevents Using Spells That Don'T Have Any Shapeshift Requirement + PreventEmoteSounds = 0x1000a + } + + public enum TaxiNodeFlags : byte + { + Alliance = 0x1, + Horde = 0x2, + UseFavoriteMount = 0x10 + } + + public enum TaxiPathNodeFlags : byte + { + Teleport = 0x1, + Stop = 0x2 + } + + public enum TextEmotes + { + Agree = 1, + Amaze = 2, + Angry = 3, + Apologize = 4, + Applaud = 5, + Bashful = 6, + Beckon = 7, + Beg = 8, + Bite = 9, + Bleed = 10, + Blink = 11, + Blush = 12, + Bonk = 13, + Bored = 14, + Bounce = 15, + Brb = 16, + Bow = 17, + Burp = 18, + Bye = 19, + Cackle = 20, + Cheer = 21, + Chicken = 22, + Chuckle = 23, + Clap = 24, + Confused = 25, + Congratulate = 26, + Cough = 27, + Cower = 28, + Crack = 29, + Cringe = 30, + Cry = 31, + Curious = 32, + Curtsey = 33, + Dance = 34, + Drink = 35, + Drool = 36, + Eat = 37, + Eye = 38, + Fart = 39, + Fidget = 40, + Flex = 41, + Frown = 42, + Gasp = 43, + Gaze = 44, + Giggle = 45, + Glare = 46, + Gloat = 47, + Greet = 48, + Grin = 49, + Groan = 50, + Grovel = 51, + Guffaw = 52, + Hail = 53, + Happy = 54, + Hello = 55, + Hug = 56, + Hungry = 57, + Kiss = 58, + Kneel = 59, + Laugh = 60, + Laydown = 61, + Message = 62, + Moan = 63, + Moon = 64, + Mourn = 65, + No = 66, + Nod = 67, + Nosepick = 68, + Panic = 69, + Peer = 70, + Plead = 71, + Point = 72, + Poke = 73, + Pray = 74, + Roar = 75, + Rofl = 76, + Rude = 77, + Salute = 78, + Scratch = 79, + Sexy = 80, + Shake = 81, + Shout = 82, + Shrug = 83, + Shy = 84, + Sigh = 85, + Sit = 86, + Sleep = 87, + Snarl = 88, + Spit = 89, + Stare = 90, + Surprised = 91, + Surrender = 92, + Talk = 93, + Talkex = 94, + Talkq = 95, + Tap = 96, + Thank = 97, + Threaten = 98, + Tired = 99, + Victory = 100, + Wave = 101, + Welcome = 102, + Whine = 103, + Whistle = 104, + Work = 105, + Yawn = 106, + Boggle = 107, + Calm = 108, + Cold = 109, + Comfort = 110, + Cuddle = 111, + Duck = 112, + Insult = 113, + Introduce = 114, + Jk = 115, + Lick = 116, + Listen = 117, + Lost = 118, + Mock = 119, + Ponder = 120, + Pounce = 121, + Praise = 122, + Purr = 123, + Puzzle = 124, + Raise = 125, + Ready = 126, + Shimmy = 127, + Shiver = 128, + Shoo = 129, + Slap = 130, + Smirk = 131, + Sniff = 132, + Snub = 133, + Soothe = 134, + Stink = 135, + Taunt = 136, + Tease = 137, + Thirsty = 138, + Veto = 139, + Snicker = 140, + Stand = 141, + Tickle = 142, + Violin = 143, + Smile = 163, + Rasp = 183, + Pity = 203, + Growl = 204, + Bark = 205, + Scared = 223, + Flop = 224, + Love = 225, + Moo = 226, + Commend = 243, + Train = 264, + Helpme = 303, + Incoming = 304, + Charge = 305, + Flee = 306, + Attackmytarget = 307, + Oom = 323, + Follow = 324, + Wait = 325, + Healme = 326, + Openfire = 327, + Flirt = 328, + Joke = 329, + Golfclap = 343, + Wink = 363, + Pat = 364, + Serious = 365, + MountSpecial = 366, + Goodluck = 367, + Blame = 368, + Blank = 369, + Brandish = 370, + Breath = 371, + Disagree = 372, + Doubt = 373, + Embarrass = 374, + Encourage = 375, + Enemy = 376, + Eyebrow = 377, + Toast = 378, + Fail = 379, + Highfive = 380, + Absent = 381, + Arm = 382, + Awe = 383, + Backpack = 384, + Badfeeling = 385, + Challenge = 386, + Chug = 387, + Ding = 389, + Facepalm = 390, + Faint = 391, + Go = 392, + Going = 393, + Glower = 394, + Headache = 395, + Hiccup = 396, + Hiss = 398, + Holdhand = 399, + Hurry = 401, + Idea = 402, + Jealous = 403, + Luck = 404, + Map = 405, + Mercy = 406, + Mutter = 407, + Nervous = 408, + Offer = 409, + Pet = 410, + Pinch = 411, + Proud = 413, + Promise = 414, + Pulse = 415, + Punch = 416, + Pout = 417, + Regret = 418, + Revenge = 420, + Rolleyes = 421, + Ruffle = 422, + Sad = 423, + Scoff = 424, + Scold = 425, + Scowl = 426, + Search = 427, + Shakefist = 428, + Shifty = 429, + Shudder = 430, + Signal = 431, + Silence = 432, + Sing = 433, + Smack = 434, + Sneak = 435, + Sneeze = 436, + Snort = 437, + Squeal = 438, + Stopattack = 439, + Suspicious = 440, + Think = 441, + Truce = 442, + Twiddle = 443, + Warn = 444, + Snap = 445, + Charm = 446, + Coverears = 447, + Crossarms = 448, + Look = 449, + Object = 450, + Sweat = 451, + Yw = 453, + Read = 456, + Boot = 506 + } +} diff --git a/Framework/Constants/ConditionConst.cs b/Framework/Constants/ConditionConst.cs new file mode 100644 index 000000000..f6bee3867 --- /dev/null +++ b/Framework/Constants/ConditionConst.cs @@ -0,0 +1,123 @@ +/* + * Copyright (C) 2012-2017 CypherCore + * + * 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, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +namespace Framework.Constants +{ + public enum ConditionTypes + { // value1 value2 value3 + None = 0, // 0 0 0 Always True + Aura = 1, // Spell_Id Effindex Use Target? True If Player (Or Target, If Value3) Has Aura Of Spell_Id With Effect Effindex + Item = 2, // Item_Id Count Bank True If Has #Count Of Item_Ids (If 'Bank' Is Set It Searches In Bank Slots Too) + ItemEquipped = 3, // Item_Id 0 0 True If Has Item_Id Equipped + Zoneid = 4, // Zone_Id 0 0 True If In Zone_Id + ReputationRank = 5, // Faction_Id Rankmask 0 True If Has Min_Rank For Faction_Id + Team = 6, // Player_Team 0, 0 469 - Alliance, 67 - Horde) + Skill = 7, // Skill_Id Skill_Value 0 True If Has Skill_Value For Skill_Id + QuestRewarded = 8, // Quest_Id 0 0 True If Quest_Id Was Rewarded Before + QuestTaken = 9, // Quest_Id 0, 0 True While Quest Active + DrunkenState = 10, // Drunkenstate 0, 0 True If Player Is Drunk Enough + WorldState = 11, // Index Value 0 True If World Has The Value For The Index + ActiveEvent = 12, // Event_Id 0 0 True If Event Is Active + InstanceInfo = 13, // Entry Data Type True If The Instance Info Defined By Type (Enum Instanceinfo) Equals Data. + QuestNone = 14, // Quest_Id 0 0 True If Doesn'T Have Quest Saved + Class = 15, // Class 0 0 True If Player'S Class Is Equal To Class + Race = 16, // Race 0 0 True If Player'S Race Is Equal To Race + Achievement = 17, // Achievement_Id 0 0 True If Achievement Is Complete + Title = 18, // Title Id 0 0 True If Player Has Title + Spawnmask = 19, // Spawnmask 0 0 True If In Spawnmask + Gender = 20, // Gender 0 0 True If Player'S Gender Is Equal To Gender + UnitState = 21, // Unitstate 0 0 True If Unit Has Unitstate + Mapid = 22, // Map_Id 0 0 True If In Map_Id + Areaid = 23, // Area_Id 0 0 True If In Area_Id + CreatureType = 24, // cinfo.type 0 0 true if creature_template.type = value1 + Spell = 25, // Spell_Id 0 0 True If Player Has Learned Spell + PhaseId = 26, // Phasemask 0 0 True If Object Is In PhaseId + Level = 27, // Level Comparisontype 0 True If Unit'S Level Is Equal To Param1 (Param2 Can Modify The Statement) + QuestComplete = 28, // Quest_Id 0 0 True If Player Has Quest_Id With All Objectives Complete, But Not Yet Rewarded + NearCreature = 29, // Creature Entry Distance 0 True If There Is A Creature Of Entry In Range + NearGameobject = 30, // Gameobject Entry Distance 0 True If There Is A Gameobject Of Entry In Range + ObjectEntryGuid = 31, // Typeid Entry 0 True If Object Is Type Typeid And The Entry Is 0 Or Matches Entry Of The Object + TypeMask = 32, // Typemask 0 0 True If Object Is Type Object'S Typemask Matches Provided Typemask + RelationTo = 33, // Conditiontarget Relationtype 0 True If Object Is In Given Relation With Object Specified By Conditiontarget + ReactionTo = 34, // Conditiontarget Rankmask 0 True If Object'S Reaction Matches Rankmask Object Specified By Conditiontarget + DistanceTo = 35, // Conditiontarget Distance Comparisontype True If Object And Conditiontarget Are Within Distance Given By Parameters + Alive = 36, // 0 0 0 True If Unit Is Alive + HpVal = 37, // Hpval Comparisontype 0 True If Unit'S Hp Matches Given Value + HpPct = 38, // Hppct Comparisontype 0 True If Unit'S Hp Matches Given Pct + RealmAchievement = 39, // achievement_id 0 0 true if realm achievement is complete + InWater = 40, // 0 0 0 true if unit in water + TerrainSwap = 41, // terrainSwap 0 0 true if object is in terrainswap + StandState = 42, // stateType state 0 true if unit matches specified sitstate (0,x: has exactly state x; 1,0: any standing state; 1,1: any sitting state;) + DailyQuestDone = 43, // quest id 0 0 true if daily quest has been completed for the day + Charmed = 44, // 0 0 0 true if unit is currently charmed + PetType = 45, // mask 0 0 true if player has a pet of given type(s) + Taxi = 46, // 0 0 0 true if player is on taxi + Queststate = 47, // quest_id state_mask 0 true if player is in any of the provided quest states for the quest (1 = not taken, 2 = completed, 8 = in progress, 32 = failed, 64 = rewarded) + ObjectiveComplete = 48, // ID 0 0 true if player has ID objective complete, but quest not yet rewarded + Max = 49 // Max + } + + public enum ConditionSourceType + { + None = 0, + CreatureLootTemplate = 1, + DisenchantLootTemplate = 2, + FishingLootTemplate = 3, + GameobjectLootTemplate = 4, + ItemLootTemplate = 5, + MailLootTemplate = 6, + MillingLootTemplate = 7, + PickpocketingLootTemplate = 8, + ProspectingLootTemplate = 9, + ReferenceLootTemplate = 10, + SkinningLootTemplate = 11, + SpellLootTemplate = 12, + SpellImplicitTarget = 13, + GossipMenu = 14, + GossipMenuOption = 15, + CreatureTemplateVehicle = 16, + Spell = 17, + SpellClickEvent = 18, + QuestAccept = 19, + // Condition source type 20 unused + VehicleSpell = 21, + SmartEvent = 22, + NpcVendor = 23, + SpellProc = 24, + TerrainSwap = 25, + Phase = 26, + Max = 27 + } + + public enum RelationType + { + Self = 0, + InParty, + InRaidOrParty, + OwnedBy, + PassengerOf, + CreatedBy, + Max + } + + public enum InstanceInfo + { + Data = 0, + Data64, + BossState + } +} diff --git a/Framework/Constants/ConnectConst.cs b/Framework/Constants/ConnectConst.cs new file mode 100644 index 000000000..e27b86776 --- /dev/null +++ b/Framework/Constants/ConnectConst.cs @@ -0,0 +1,53 @@ +/* + * Copyright (C) 2012-2017 CypherCore + * + * 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, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +namespace Framework.Constants +{ + public enum ConnectionType + { + Realm = 0, + Instance = 1, + Max + } + + public enum ConnectToSerial + { + None = 0, + Realm = 14, + WorldAttempt1 = 17, + WorldAttempt2 = 35, + WorldAttempt3 = 53, + WorldAttempt4 = 71, + WorldAttempt5 = 89 + } + + public enum LoginFailureReason + { + Failed = 0, + NoWorld = 1, + DuplicateCharacter = 2, + NoInstances = 3, + Disabled = 4, + NoCharacter = 5, + LockedForTransfer = 6, + LockedByBilling = 7, + LockedByMobileAH = 8, + TemporaryGMLock = 9, + LockedByCharacterUpgrade = 10, + LockedByRevokedCharacterUpgrade = 11 + } +} diff --git a/Framework/Constants/CreatureConst.cs b/Framework/Constants/CreatureConst.cs new file mode 100644 index 000000000..73f7c8888 --- /dev/null +++ b/Framework/Constants/CreatureConst.cs @@ -0,0 +1,338 @@ +/* + * Copyright (C) 2012-2017 CypherCore + * + * 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, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +namespace Framework.Constants +{ + public enum CreatureLinkedRespawnType + { + CreatureToCreature, + CreatureToGO, // Creature is dependant on GO + GOToGO, + GOToCreature // GO is dependant on creature + } + + public enum AiReaction + { + Alert = 0, // pre-aggro (used in client packet handler) + Friendly = 1, // (NOT used in client packet handler) + Hostile = 2, // sent on every attack, triggers aggro sound (used in client packet handler) + Afraid = 3, // seen for polymorph (when AI not in control of self?) (NOT used in client packet handler) + Destory = 4 // used on object destroy (NOT used in client packet handler) + } + + public enum CreatureEliteType + { + Normal = 0, + Elite = 1, + RareElite = 2, + WorldBoss = 3, + Rare = 4, + Unknown = 5 // found in 2.2.3 for 2 mobs + } + + [System.Flags] + public enum UnitFlags : uint + { + ServerControlled = 0x01, + NonAttackable = 0x02, + RemoveClientControl = 0x04, // This is a legacy flag used to disable movement player's movement while controlling other units, SMSG_CLIENT_CONTROL replaces this functionality clientside now. CONFUSED and FLEEING flags have the same effect on client movement asDISABLE_MOVE_CONTROL in addition to preventing spell casts/autoattack (they all allow climbing steeper hills and emotes while moving) + PvpAttackable = 0x08, + Rename = 0x10, + Preparation = 0x20, + Unk6 = 0x40, + NotAttackable1 = 0x80, + ImmuneToPc = 0x100, + ImmuneToNpc = 0x200, + Looting = 0x400, + PetInCombat = 0x800, + Pvp = 0x1000, + Silenced = 0x2000, + CannotSwim = 0x4000, + Unk15 = 0x8000, + Unk16 = 0x10000, + Pacified = 0x20000, + Stunned = 0x40000, + InCombat = 0x80000, + TaxiFlight = 0x100000, + Disarmed = 0x200000, + Confused = 0x400000, + Fleeing = 0x800000, + PlayerControlled = 0x1000000, + NotSelectable = 0x2000000, + Skinnable = 0x4000000, + Mount = 0x8000000, + Unk28 = 0x10000000, + Unk29 = 0x20000000, + Sheathe = 0x40000000, + Unk31 = 0x80000000 + } + + public enum UnitFlags2 : uint + { + FeignDeath = 0x01, + Unk1 = 0x02, + IgnoreReputation = 0x04, + ComprehendLang = 0x08, + MirrorImage = 0x10, + InstantlyAppearModel = 0x20, + ForceMove = 0x40, + DisarmOffhand = 0x80, + DisablePredStats = 0x100, + DisarmRanged = 0x400, + RegeneratePower = 0x800, + RestrictPartyInteraction = 0x1000, + PreventSpellClick = 0x2000, + AllowEnemyInteract = 0x4000, + DisableTurn = 0x8000, + Unk2 = 0x10000, + PlayDeathAnim = 0x20000, + AllowCheatSpells = 0x40000, + NoActions = 0x800000 + } + + public enum UnitFlags3 : uint + { + Unk1 = 0x01, + } + + public enum NPCFlags : ulong + { + None = 0x00, + Gossip = 0x01, + QuestGiver = 0x02, + Unk1 = 0x04, + Unk2 = 0x08, + Trainer = 0x10, + TrainerClass = 0x20, + TrainerProfession = 0x40, + Vendor = 0x80, + VendorGeneral = 0x100, + VendorFood = 0x200, + VendorPoison = 0x400, + VendorReagent = 0x800, + Repair = 0x1000, + FlightMaster = 0x2000, + SpiritHealer = 0x4000, + SpiritGuide = 0x8000, + Innkeeper = 0x10000, + Banker = 0x20000, + Petitioner = 0x40000, + TabardDesigner = 0x80000, + BattleMaster = 0x100000, + Auctioneer = 0x200000, + StableMaster = 0x400000, + GuildBanker = 0x800000, + SpellClick = 0x1000000, + PlayerVehicle = 0x2000000, + Mailbox = 0x4000000, + ArtifactPowerRespec = 0x8000000, + Transmogrifier = 0x10000000, + VaultKeeper = 0x20000000, + BlackMarket = 0x80000000, + ItemUpgradeMaster = 0x100000000, + GarrisonArchitect = 0x200000000, + Steering = 0x400000000, + ShipmentCrafter = 0x1000000000, + GarrisonMissionNpc = 0x2000000000, + TradeskillNpc = 0x4000000000, + BlackMarketView = 0x8000000000 + } + + public enum CreatureTypeFlags : uint + { + TameablePet = 0x01, // Makes the mob tameable (must also be a beast and have family set) + GhostVisible = 0x02, // Creature are also visible for not alive player. Allow gossip interaction if npcflag allow? + BossMob = 0x04, // Changes creature's visible level to "??" in the creature's portrait - Immune Knockback. + DoNotPlayWoundParryAnimation = 0x08, + HideFactionTooltip = 0x10, + Unk5 = 0x20, // Sound related + SpellAttackable = 0x40, + CanInteractWhileDead = 0x80, // Player can interact with the creature if its dead (not player dead) + HerbSkinningSkill = 0x100, // Can be looted by herbalist + MiningSkinningSkill = 0x200, // Can be looted by miner + DoNotLogDeath = 0x400, // Death event will not show up in combat log + MountedCombatAllowed = 0x800, // Creature can remain mounted when entering combat + CanAssist = 0x1000, // ? Can aid any player in combat if in range? + IsPetBarUsed = 0x2000, + MaskUID = 0x4000, + EngineeringSkinningSkill = 0x8000, // Can be looted by engineer + ExoticPet = 0x10000, // Can be tamed by hunter as exotic pet + UseDefaultCollisionBox = 0x20000, // Collision related. (always using default collision box?) + IsSiegeWeapon = 0x40000, + CanCollideWithMissiles = 0x80000, // Projectiles can collide with this creature - interacts with TARGET_DEST_TRAJ + HideNamePlate = 0x100000, + DoNotPlayMountedAnimations = 0x200000, + IsLinkAll = 0x400000, + InteractOnlyWithCreator = 0x800000, + DoNotPlayUnitEventSounds = 0x1000000, + HasNoShadowBlob = 0x2000000, + TreatAsRaidUnit = 0x4000000, //! Creature can be targeted by spells that require target to be in caster's party/raid + ForceGossip = 0x8000000, // Allows the creature to display a single gossip option. + DoNotSheathe = 0x10000000, + DoNotTargetOnInteration = 0x20000000, + DoNotRenderObjectName = 0x40000000, + UnitIsQuestBoss = 0x80000000 // Not verified + } + + public enum CreatureFlagsExtra : uint + { + InstanceBind = 0x01, // Creature Kill Bind Instance With Killer And Killer'S Group + Civilian = 0x02, // Not Aggro (Ignore Faction/Reputation Hostility) + NoParry = 0x04, // Creature Can'T Parry + NoParryHasten = 0x08, // Creature Can'T Counter-Attack At Parry + NoBlock = 0x10, // Creature Can'T Block + NoCrush = 0x20, // Creature Can'T Do Crush Attacks + NoXpAtKill = 0x40, // Creature Kill Not Provide Xp + Trigger = 0x80, // Trigger Creature + NoTaunt = 0x100, // Creature Is Immune To Taunt Auras And Effect Attack Me + Worldevent = 0x4000, // Custom Flag For World Event Creatures (Left Room For Merging) + Guard = 0x8000, // Creature Is Guard + NoCrit = 0x20000, // Creature Can'T Do Critical Strikes + NoSkillgain = 0x40000, // Creature Won'T Increase Weapon Skills + TauntDiminish = 0x80000, // Taunt Is A Subject To Diminishing Returns On This Creautre + AllDiminish = 0x100000, // Creature Is Subject To All Diminishing Returns As Player Are + NoPlayerDamageReq = 0x200000, // creature does not need to take player damage for kill credit + DungeonBoss = 0x10000000, // Creature Is A Dungeon Boss (Set Dynamically, Do Not Add In Db) + IgnorePathfinding = 0x20000000, // creature ignore pathfinding + ImmunityKnockback = 0x40000000, // creature is immune to knockback effects + + DBAllowed = (InstanceBind | Civilian | NoParry | NoParryHasten | NoBlock | NoCrush | NoXpAtKill | + Trigger | NoTaunt | Worldevent | NoCrit | NoSkillgain | TauntDiminish | AllDiminish | Guard | + IgnorePathfinding | NoPlayerDamageReq | ImmunityKnockback) + } + + public enum CreatureType + { + Beast = 1, + Dragonkin = 2, + Demon = 3, + Elemental = 4, + Giant = 5, + Undead = 6, + Humanoid = 7, + Critter = 8, + Mechanical = 9, + NotSpecified = 10, + Totem = 11, + NonCombatPet = 12, + GasCloud = 13, + WildPet = 14, + Aberration = 15, + + MaskDemonOrUnDead = (1 << (Demon - 1)) | (1 << (Undead - 1)), + MaskHumanoidOrUndead = (1 << (Humanoid - 1)) | (1 << (Undead - 1)), + MaskMechanicalOrElemental = (1 << (Mechanical - 1)) | (1 << (Elemental - 1)) + + } + + public enum CreatureFamily + { + None = 0, + Wolf = 1, + Cat = 2, + Spider = 3, + Bear = 4, + Boar = 5, + Crocolisk = 6, + CarrionBird = 7, + Crab = 8, + Gorilla = 9, + HorseCustom = 10, // Does Not Exist In Dbc But Used For Horse Like Beasts In Db + Raptor = 11, + Tallstrider = 12, + Felhunter = 15, + Voidwalker = 16, + Succubus = 17, + Doomguard = 19, + Scorpid = 20, + Turtle = 21, + Imp = 23, + Bat = 24, + Hyena = 25, + BirdOfPrey = 26, + WindSerpent = 27, + RemoteControl = 28, + Felguard = 29, + Dragonhawk = 30, + Ravager = 31, + WarpStalker = 32, + Sporebat = 33, + NetherRay = 34, + Serpent = 35, + Moth = 37, + Chimaera = 38, + Devilsaur = 39, + Ghoul = 40, + Silithid = 41, + Worm = 42, + Rhino = 43, + Wasp = 44, + CoreHound = 45, + SpiritBeast = 46, + WaterElemental = 49, + Fox = 50, + Monkey = 51, + Dog = 52, + Beetle = 53, + ShaleSpider = 55, + Zombie = 56, + BeetleOld = 57, + Silithid2 = 59, + Wasp2 = 66, + Hydra = 68, + Felimp = 100, + Voidlord = 101, + Shivara = 102, + Observer = 103, + Wrathguard = 104, + Infernal = 108, + Fireelemental = 116, + Earthelemental = 117, + Crane = 125, + Waterstrider = 126, + Porcupine = 127, + Quilen = 128, + Goat = 129, + Basilisk = 130, + Direhorn = 138, + Stormelemental = 145, + Mtwaterelemental = 146, + Torrorguard = 147, + Abyssal = 148, + Rylak = 149, + Riverbeast = 150, + Stag = 151 + } + + public enum InhabitType + { + Ground = 1, + Water = 2, + Air = 4, + Root = 8, + Anywhere = Ground | Water | Air | Root + } + + public enum EvadeReason + { + NoHostiles, // the creature's threat list is empty + Boundary, // the creature has moved outside its evade boundary + NoPath, // the creature was unable to reach its target for over 5 seconds + SequenceBreak, // this is a boss and the pre-requisite encounters for engaging it are not defeated yet + Other + } +} diff --git a/Framework/Constants/GameObjectConst.cs b/Framework/Constants/GameObjectConst.cs new file mode 100644 index 000000000..ed1710ef2 --- /dev/null +++ b/Framework/Constants/GameObjectConst.cs @@ -0,0 +1,128 @@ +/* + * Copyright (C) 2012-2017 CypherCore + * + * 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, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +namespace Framework.Constants +{ + public enum GameObjectTypes : byte + { + Door = 0, + Button = 1, + QuestGiver = 2, + Chest = 3, + Binder = 4, + Generic = 5, + Trap = 6, + Chair = 7, + SpellFocus = 8, + Text = 9, + Goober = 10, + Transport = 11, + AreaDamage = 12, + Camera = 13, + MapObject = 14, + MapObjTransport = 15, + DuelArbiter = 16, + FishingNode = 17, + Ritual = 18, + Mailbox = 19, + DoNotUse = 20, + GuardPost = 21, + SpellCaster = 22, + MeetingStone = 23, + FlagStand = 24, + FishingHole = 25, + FlagDrop = 26, + MiniGame = 27, + DoNotUse2 = 28, + ControlZone = 29, + AuraGenerator = 30, + DungeonDifficulty = 31, + BarberChair = 32, + DestructibleBuilding = 33, + GuildBank = 34, + TrapDoor = 35, + NewFlag = 36, + NewFlagDrop = 37, + GarrisonBuilding = 38, + GarrisonPlot = 39, + ClientCreature = 40, + ClientItem = 41, + CapturePoint = 42, + PhaseableMo = 43, + GarrisonMonument = 44, + GarrisonShipment = 45, + GarrisonMonumentPlaque = 46, + ArtifactForge = 47, + UILink = 48, + KeystoneReceptacle = 49, + GatheringNode = 50, + ChallengeModeReward = 51, + Max = 52 + } + + public enum GameObjectState + { + Active = 0, + Ready = 1, + ActiveAlternative = 2, + TransportActive = 24, + TransportStopped = 25, + Max = 3 + } + + public enum GameObjectDynamicLowFlags + { + HideModel = 0x02, + Activate = 0x04, + Animate = 0x08, + NoInteract = 0x10, + Sparkle = 0x20, + Stopped = 0x40 + } + + public enum GameObjectFlags + { + InUse = 0x01, // Disables Interaction While Animated + Locked = 0x02, // Require Key, Spell, Event, Etc To Be Opened. Makes "Locked" Appear In Tooltip + InteractCond = 0x04, // cannot interact (condition to interact - requires GO_DYNFLAG_LO_ACTIVATE to enable interaction clientside) + Transport = 0x08, // Any Kind Of Transport? Object Can Transport (Elevator, Boat, Car) + NotSelectable = 0x10, // Not Selectable Even In Gm Mode + NoDespawn = 0x20, // Never Despawn, Typically For Doors, They Just Change State + AiObstacle = 0x40, // makes the client register the object in something called AIObstacleMgr, unknown what it does + FreezeAnimation = 0x80, + Damaged = 0x200, + Destroyed = 0x400, + InteractDistanceUsesTemplateModel = 0x80000, // client checks interaction distance from model sent in SMSG_QUERY_GAMEOBJECT_RESPONSE instead of GAMEOBJECT_DISPLAYID + MapObject = 0x00100000 // pre-7.0 model loading used to be controlled by file extension (wmo vs m2) + } + + public enum LootState + { + NotReady = 0, + Ready, // can be ready but despawned, and then not possible activate until spawn + Activated, + JustDeactivated + } + + public enum GameObjectDestructibleState + { + Intact = 0, + Damaged = 1, + Destroyed = 2, + Rebuilding = 3 + } +} diff --git a/Framework/Constants/GarrisonConst.cs b/Framework/Constants/GarrisonConst.cs new file mode 100644 index 000000000..6c1d4c561 --- /dev/null +++ b/Framework/Constants/GarrisonConst.cs @@ -0,0 +1,164 @@ +/* + * Copyright (C) 2012-2017 CypherCore + * + * 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, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +namespace Framework.Constants +{ + public struct GarrisonFactionIndex + { + public const uint Horde = 0; + public const uint Alliance = 1; + } + + public enum GarrisonBuildingFlags : byte + { + NeedsPlan = 0x1 + } + + enum GarrisonFollowerFlags + { + Unique = 0x1 + } + + public enum GarrisonFollowerType + { + Garrison = 1, + Shipyard = 2 + } + + public enum GarrisonAbilityFlags : ushort + { + Trait = 0x01, + CannotRoll = 0x02, + HordeOnly = 0x04, + AllianceOnly = 0x08, + CannotRemove = 0x10, + Exclusive = 0x20, + SingleMissionDuration = 0x40, + ActiveOnlyOnZoneSupport = 0x80, + ApplyToFirstMission = 0x100, + IsSpecialization = 0x200, + IsEmptySlot = 0x400 + } + + public enum GarrisonError + { + Success = 0, + NoGarrison = 1, + GarrisonExists = 2, + GarrisonSameTypeExists = 3, + InvalidGarrison = 4, + InvalidGarrisonLevel = 5, + GarrisonLevelUnchanged = 6, + NotInGarrison = 7, + NoBuilding = 8, + BuildingExists = 9, + InvalidPlotInstanceId = 10, + InvalidBuildingId = 11, + InvalidUpgradeLevel = 12, + UpgradeLevelExceedsGarrisonLevel = 13, + PlotsNotFull = 14, + InvalidSiteId = 15, + InvalidPlotBuilding = 16, + InvalidFaction = 17, + InvalidSpecialization = 18, + SpecializationExists = 19, + SpecializationOnCooldown = 20, + BlueprintExists = 21, + RequiresBlueprint = 22, + InvalidDoodadSetId = 23, + BuildingTypeExists = 24, + BuildingNotActive = 25, + ConstructionComplete = 26, + FollowerExists = 27, + InvalidFollower = 28, + FollowerAlreadyOnMission = 29, + FollowerInBuilding = 30, + FollowerInvalidForBuilding = 31, + InvalidFollowerLevel = 32, + MissionExists = 33, + InvalidMission = 34, + InvalidMissionTime = 35, + InvalidMissionRewardIndex = 36, + MissionNotOffered = 37, + AlreadyOnMission = 38, + MissionSizeInvalid = 39, + FollowerSoftCapExceeded = 40, + NotOnMission = 41, + AlreadyCompletedMission = 42, + MissionNotComplete = 43, + MissionRewardsPending = 44, + MissionExpired = 45, + NotEnoughCurrency = 46, + NotEnoughGold = 47, + BuildingMissing = 48, + NoArchitect = 49, + ArchitectNotAvailable = 50, + NoMissionNpc = 51, + MissionNpcNotAvailable = 52, + InternalError = 53, + InvalidStaticTableValue = 54, + InvalidItemLevel = 55, + InvalidAvailableRecruit = 56, + FollowerAlreadyRecruited = 57, + RecruitmentGenerationInProgress = 58, + RecruitmentOnCooldown = 59, + RecruitBlockedByGeneration = 60, + RecruitmentNpcNotAvailable = 61, + InvalidFollowerQuality = 62, + ProxyNotOk = 63, + RecallPortalUsedLessThan24HoursAgo = 64, + OnRemoveBuildingSpellFailed = 65, + OperationNotSupported = 66, + FollowerFatigued = 67, + UpgradeConditionFailed = 68, + FollowerInactive = 69, + FollowerActive = 70, + FollowerActivationUnavailable = 71, + FollowerTypeMismatch = 72, + InvalidGarrisonType = 73, + MissionStartConditionFailed = 74, + InvalidFollowerAbility = 75, + InvalidMissionBonusAbility = 76, + HigherBuildingTypeExists = 77, + AtFollowerHardCap = 78, + FollowerCannotGainXp = 79, + NoOp = 80, + AtClassSpecCap = 81, + MissionRequires100ToStart = 82, + MissionMissingRequiredFollower = 83, + InvalidTalent = 84, + AlreadyResearchingTalent = 85, + FailedCondition = 86, + InvalidTier = 87, + InvalidClass = 88 + } + + public enum GarrisonFollowerStatus + { + Favorite = 0x01, + Exhausted = 0x02, + Inactive = 0x04, + Troop = 0x08, + NoXpGain = 0x10 + } + + public enum GarrisonType + { + Garrison = 2, + ClassOrder = 3 + } +} diff --git a/Framework/Constants/GossipConst.cs b/Framework/Constants/GossipConst.cs new file mode 100644 index 000000000..02b02cf9d --- /dev/null +++ b/Framework/Constants/GossipConst.cs @@ -0,0 +1,125 @@ +/* + * Copyright (C) 2012-2017 CypherCore + * + * 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, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +namespace Framework.Constants +{ + public enum GossipOption + { + None = 0, //Unit_Npc_Flag_None (0) + Gossip = 1, //Unit_Npc_Flag_Gossip (1) + Questgiver = 2, //Unit_Npc_Flag_Questgiver (2) + Vendor = 3, //Unit_Npc_Flag_Vendor (128) + Taxivendor = 4, //Unit_Npc_Flag_Taxivendor (8192) + Trainer = 5, //Unit_Npc_Flag_Trainer (16) + Spirithealer = 6, //Unit_Npc_Flag_Spirithealer (16384) + Spiritguide = 7, //Unit_Npc_Flag_Spiritguide (32768) + Innkeeper = 8, //Unit_Npc_Flag_Innkeeper (65536) + Banker = 9, //Unit_Npc_Flag_Banker (131072) + Petitioner = 10, //Unit_Npc_Flag_Petitioner (262144) + Tabarddesigner = 11, //Unit_Npc_Flag_Tabarddesigner (524288) + Battlefield = 12, //Unit_Npc_Flag_Battlefieldperson (1048576) + Auctioneer = 13, //Unit_Npc_Flag_Auctioneer (2097152) + Stablepet = 14, //Unit_Npc_Flag_Stable (4194304) + Armorer = 15, //Unit_Npc_Flag_Armorer (4096) + Unlearntalents = 16, //Unit_Npc_Flag_Trainer (16) (Bonus Option For Trainer) + Unlearnpettalents = 17, //Unit_Npc_Flag_Trainer (16) (Bonus Option For Trainer) + Learndualspec = 18, //Unit_Npc_Flag_Trainer (16) (Bonus Option For Trainer) + Outdoorpvp = 19, //Added By Code (Option For Outdoor Pvp Creatures) + Max + } + + public enum GossipOptionIcon + { + Chat = 0, // White Chat Bubble + Vendor = 1, // Brown Bag + Taxi = 2, // Flightmarker (Paperplane) + Trainer = 3, // Brown Book (Trainer) + Interact1 = 4, // Golden Interaction Wheel + Interact2 = 5, // Golden Interaction Wheel + MoneyBag = 6, // Brown Bag (With Gold Coin In Lower Corner) + Talk = 7, // White Chat Bubble (With "..." Inside) + Tabard = 8, // White Tabard + Battle = 9, // Two Crossed Swords + Dot = 10, // Yellow Dot/Point + Chat11 = 11, // White Chat Bubble + Chat12 = 12, // White Chat Bubble + Chat13 = 13, // White Chat Bubble + Unk14 = 14, // Invalid - Do Not Use + Unk15 = 15, // Invalid - Do Not Use + Chat16 = 16, // White Chat Bubble + Chat17 = 17, // White Chat Bubble + Chat18 = 18, // White Chat Bubble + Chat19 = 19, // White Chat Bubble + Chat20 = 20, // White Chat Bubble + Chat21 = 21, // transmogrifier? + Max + } + + public struct eTradeskill + { + // Skill Defines + public const uint TradeskillAlchemy = 1; + public const uint TradeskillBlacksmithing = 2; + public const uint TradeskillCooking = 3; + public const uint TradeskillEnchanting = 4; + public const uint TradeskillEngineering = 5; + public const uint TradeskillFirstaid = 6; + public const uint TradeskillHerbalism = 7; + public const uint TradeskillLeatherworking = 8; + public const uint TradeskillPoisons = 9; + public const uint TradeskillTailoring = 10; + public const uint TradeskillMining = 11; + public const uint TradeskillFishing = 12; + public const uint TradeskillSkinning = 13; + public const uint TradeskillJewlcrafting = 14; + public const uint TradeskillInscription = 15; + + public const uint TradeskillLevelNone = 0; + public const uint TradeskillLevelApprentice = 1; + public const uint TradeskillLevelJourneyman = 2; + public const uint TradeskillLevelExpert = 3; + public const uint TradeskillLevelArtisan = 4; + public const uint TradeskillLevelMaster = 5; + public const uint TradeskillLevelGrandMaster = 6; + + // Gossip Defines + public const uint GossipActionTrade = 1; + public const uint GossipActionTrain = 2; + public const uint GossipActionTaxi = 3; + public const uint GossipActionGuild = 4; + public const uint GossipActionBattle = 5; + public const uint GossipActionBank = 6; + public const uint GossipActionInn = 7; + public const uint GossipActionHeal = 8; + public const uint GossipActionTabard = 9; + public const uint GossipActionAuction = 10; + public const uint GossipActionInnInfo = 11; + public const uint GossipActionUnlearn = 12; + public const uint GossipActionInfoDef = 1000; + + public const uint GossipSenderMain = 1; + public const uint GossipSenderInnInfo = 2; + public const uint GossipSenderInfo = 3; + public const uint GossipSenderSecProftrain = 4; + public const uint GossipSenderSecClasstrain = 5; + public const uint GossipSenderSecBattleinfo = 6; + public const uint GossipSenderSecBank = 7; + public const uint GossipSenderSecInn = 8; + public const uint GossipSenderSecMailbox = 9; + public const uint GossipSenderSecStablemaster = 10; + } +} diff --git a/Framework/Constants/GroupConst.cs b/Framework/Constants/GroupConst.cs new file mode 100644 index 000000000..9d8023ebb --- /dev/null +++ b/Framework/Constants/GroupConst.cs @@ -0,0 +1,166 @@ +/* + * Copyright (C) 2012-2017 CypherCore + * + * 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, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +namespace Framework.Constants +{ + public enum RemoveMethod + { + Default = 0, + Kick = 1, + Leave = 2, + KickLFG = 3 + } + + public enum GroupMemberOnlineStatus + { + Offline = 0x00, + Online = 0x01, // Lua_UnitIsConnected + PVP = 0x02, // Lua_UnitIsPVP + Dead = 0x04, // Lua_UnitIsDead + Ghost = 0x08, // Lua_UnitIsGhost + PVPFFA = 0x10, // Lua_UnitIsPVPFreeForAll + Unk3 = 0x20, // used in calls from Lua_GetPlayerMapPosition/Lua_GetBattlefieldFlagPosition + AFK = 0x40, // Lua_UnitIsAFK + DND = 0x80, // Lua_UnitIsDND + RAF = 0x100, + Vehicle = 0x200, // Lua_UnitInVehicle + } + + public enum GroupMemberFlags + { + Assistant = 0x01, + MainTank = 0x02, + MainAssist = 0x04 + } + + public enum GroupMemberAssignment + { + MainTank = 0, + MainAssist = 1 + } + + public enum GroupType + { + None = 0, + Normal = 1, + WorldPvp = 4, + } + + public enum GroupFlags + { + None = 0x00, + FakeRaid = 0x01, + Raid = 0x02, + LfgRestricted = 0x04, // Script_HasLFGRestrictions() + Lfg = 0x08, + Destroyed = 0x10, + OnePersonParty = 0x020, // Script_IsOnePersonParty() + EveryoneAssistant = 0x040, // Script_IsEveryoneAssistant() + GuildGroup = 0x100, + + MaskBgRaid = FakeRaid | Raid + } + + public enum GroupUpdateFlags + { + None = 0x00, // nothing + Unk704 = 0x01, // Uint8[2] (Unk) + Status = 0x02, // public ushort (Groupmemberstatusflag) + PowerType = 0x04, // Uint8 (Powertype) + Unk322 = 0x08, // public ushort (Unk) + CurHp = 0x10, // Uint32 (Hp) + MaxHp = 0x20, // Uint32 (Max Hp) + CurPower = 0x40, // Int16 (Power Value) + MaxPower = 0x80, // Int16 (Max Power Value) + Level = 0x100, // public ushort (Level Value) + Unk200000 = 0x200, // Int16 (Unk) + Zone = 0x400, // public ushort (Zone Id) + Unk2000000 = 0x800, // Int16 (Unk) + Unk4000000 = 0x1000, // Int32 (Unk) + Position = 0x2000, // public ushort (X), public ushort (Y), public ushort (Z) + VehicleSeat = 0x4000, // Int32 (Vehicle Seat Id) + Auras = 0x8000, // Uint8 (Unk), Uint64 (Mask), Uint32 (Count), For Each Bit Set: Uint32 (Spell Id) + public ushort (Auraflags) (If Has Flags Scalable -> 3x Int32 (Bps)) + Pet = 0x10000, // Complex (Pet) + Phase = 0x20000, // Int32 (Unk), Uint32 (Phase Count), For (Count) Uint16(Phaseid) + + Full = Unk704 | Status | PowerType | Unk322 | CurHp | MaxHp | + CurPower | MaxPower | Level | Unk200000 | Zone | Unk2000000 | + Unk4000000 | Position | VehicleSeat | Auras | Pet | Phase // All Known Flags + } + + public enum GroupUpdatePetFlags + { + None = 0x00000000, // nothing + GUID = 0x00000001, // ObjectGuid (pet guid) + Name = 0x00000002, // cstring (name, NULL terminated string) + ModelId = 0x00000004, // public ushort (model id) + CurHp = 0x00000008, // uint32 (HP) + MaxHp = 0x00000010, // uint32 (max HP) + Auras = 0x00000020, // [see GROUP_UPDATE_FLAG_AURAS] + + Full = GUID | Name | ModelId | CurHp | MaxHp | Auras // all pet flags + } + + public enum PartyResult + { + Ok = 0, + BadPlayerNameS = 1, + TargetNotInGroupS = 2, + TargetNotInInstanceS = 3, + GroupFull = 4, + AlreadyInGroupS = 5, + NotInGroup = 6, + NotLeader = 7, + PlayerWrongFaction = 8, + IgnoringYouS = 9, + LfgPending = 12, + InviteRestricted = 13, + GroupSwapFailed = 14, // If (Partyoperation == PartyOpSwap) GroupSwapFailed Else InviteInCombat + InviteUnknownRealm = 15, + InviteNoPartyServer = 16, + InvitePartyBusy = 17, + PartyTargetAmbiguous = 18, + PartyLfgInviteRaidLocked = 19, + PartyLfgBootLimit = 20, + PartyLfgBootCooldownS = 21, + PartyLfgBootInProgress = 22, + PartyLfgBootTooFewPlayers = 23, + PartyLfgBootNotEligibleS = 24, + RaidDisallowedByLevel = 25, + PartyLfgBootInCombat = 26, + VoteKickReasonNeeded = 27, + PartyLfgBootDungeonComplete = 28, + PartyLfgBootLootRolls = 29, + PartyLfgTeleportInCombat = 30 + } + + public enum PartyOperation + { + Invite = 0, + UnInvite = 1, + Leave = 2, + Swap = 4 + } + + public enum GroupCategory + { + Home = 0, + Instance = 1, + + Max + } +} diff --git a/Framework/Constants/GuildConst.cs b/Framework/Constants/GuildConst.cs new file mode 100644 index 000000000..37a887bbd --- /dev/null +++ b/Framework/Constants/GuildConst.cs @@ -0,0 +1,283 @@ +/* + * Copyright (C) 2012-2017 CypherCore + * + * 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, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +namespace Framework.Constants +{ + public class GuildConst + { + public const int MaxBankTabs = 8; + public const int MaxBankSlots = 98; + public const int BankMoneyLogsTab = 100; + + public const int WithdrawMoneyUnlimited = -1; + public const int WithdrawSlotUnlimited = -1; + public const uint EventLogGuidUndefined = 0xFFFFFFFF; + + public const uint ChallengesTypes = 6; + public const uint CharterItemId = 5863; + + public const int RankNone = 0xFF; + public const int MinRanks = 5; + public const int MaxRanks = 10; + + public const int BankLogMaxRecords = 25; + public const int EventLogMaxRecords = 100; + public const int NewsLogMaxRecords = 250; + + public static int[] ChallengeGoldReward = { 0, 250, 1000, 500, 250, 500 }; + public static int[] ChallengeMaxLevelGoldReward = { 0, 125, 500, 250, 125, 250 }; + public static int[] ChallengesMaxCount = { 0, 7, 1, 3, 0, 3 }; + + public static uint MinNewsItemLevel = 353; + + public static byte OldMaxLevel = 25; + } + + public enum GuildRankRights + { + None = 0x00000000, + GChatListen = 0x00000001, + GChatSpeak = 0x00000002, + OffChatListen = 0x00000004, + OffChatSpeak = 0x00000008, + Invite = 0x00000010, + Remove = 0x00000020, + Roster = 0x00000040, + Promote = 0x00000080, + Demote = 0x00000100, + Unk200 = 0x00000200, + Unk400 = 0x00000400, + Unk800 = 0x00000800, + SetMotd = 0x00001000, + EditPublicNote = 0x00002000, + ViewOffNote = 0x00004000, + EOffNote = 0x00008000, + ModifyGuildInfo = 0x00010000, + WithdrawGoldLock = 0x00020000, // remove money withdraw capacity + WithdrawRepair = 0x00040000, // withdraw for repair + WithdrawGold = 0x00080000, // withdraw gold + CreateGuildEvent = 0x00100000, // wotlk + All = 0x00DDFFBF + } + + public struct GuildDefaultRanks + { + public const int Master = 0; + public const int Officer = 1; + public const int Veteran = 2; + public const int Member = 3; + public const int Initiate = 4; + } + + public enum GuildMemberFlags + { + None = 0, + Online = 1, + AFK = 2, + DND = 3, + Mobile = 4 + } + + public enum GuildMemberData + { + ZoneId, + AchievementPoints, + Level, + } + + public enum GuildCommandType + { + CreateGuild = 0, + InvitePlayer = 1, + LeaveGuild = 3, + GetRoster = 5, + PromotePlayer = 6, + DemotePlayer = 7, + RemovePlayer = 8, + ChangeLeader = 10, + EditMOTD = 11, + GuildChat = 13, + Founder = 14, + ChangeRank = 16, + EditPublicNote = 19, + ViewTab = 21, + MoveItem = 22, + Repair = 25 + } + + public enum GuildCommandError + { + Success = 0, + GuildInternal = 1, + AlreadyInGuild = 2, + AlreadyInGuild_S = 3, + InvitedToGuild = 4, + AlreadyInvitedToGuild_S = 5, + NameInvalid = 6, + NameExists_S = 7, + LeaderLeave = 8, + Permissions = 8, + PlayerNotInGuild = 9, + PlayerNotInGuild_S = 10, + PlayerNotFound_S = 11, + NotAllied = 12, + RankTooHigh_S = 13, + RankTooLow_S = 14, + RanksLocked = 17, + RankInUse = 18, + IgnoringYou_S = 19, + Unk1 = 20, + WithdrawLimit = 25, + NotEnoughMoney = 26, + BankFull = 28, + ItemNotFound = 29, + TooMuchMoney = 31, + WrongTab = 32, + RequiresAuthenticator = 34, + BankVoucherFailed = 35, + TrialAccount = 36, + UndeletableDueToLevel = 37, + MoveStarting = 38, + RepTooLow = 39 + } + + public enum GuildEventLogTypes + { + InvitePlayer = 1, + JoinGuild = 2, + PromotePlayer = 3, + DemotePlayer = 4, + UninvitePlayer = 5, + LeaveGuild = 6, + } + + public enum GuildBankEventLogTypes + { + DepositItem = 1, + WithdrawItem = 2, + MoveItem = 3, + DepositMoney = 4, + WithdrawMoney = 5, + RepairMoney = 6, + MoveItem2 = 7, + Unk1 = 8, + BuySlot = 9, + CashFlowDeposit = 10 + } + + public enum GuildEmblemError + { + Success = 0, + InvalidTabardColors = 1, + NoGuild = 2, + NotGuildMaster = 3, + NotEnoughMoney = 4, + InvalidVendor = 5 + } + + public enum GuildBankRights : int + { + ViewTab = 0x01, + PutItem = 0x02, + UpdateText = 0x04, + + DepositItem = ViewTab | PutItem, + Full = -1 + } + + public enum GuildNews + { + Achievement = 0, + PlayerAchievement = 1, + DungeonEncounter = 2, + ItemLooted = 3, + ItemCrafted = 4, + ItemPurchased = 5, + LevelUp = 6, + Create = 7, + Event = 8 + } + + public enum PetitionTurns + { + Ok = 0, + AlreadyInGuild = 2, + NeedMoreSignatures = 4, + GuildPermissions = 11, + GuildNameInvalid = 12 + } + + public enum PetitionSigns + { + Ok = 0, + AlreadySigned = 1, + AlreadyInGuild = 2, + CantSignOwn = 3, + NotServer = 4, + Full = 5, + AlreadySignedOther = 6, + RestrictedAccount = 7 + } + + public enum CharterTypes + { + Guild = 4, + Arena2v2 = 2, + Arena3v3 = 3, + Arena5v5 = 5, + } + + public struct CharterCosts + { + public const uint Guild = 1000; + public const uint Arena2v2 = 800000; + public const uint Arena3v3 = 1200000; + public const uint Arena5v5 = 2000000; + } + + public enum GuildFinderOptionsInterest + { + Questing = 0x01, + Dungeons = 0x02, + Raids = 0x04, + PVP = 0x08, + RolePlaying = 0x10, + All = Questing | Dungeons | Raids | PVP | RolePlaying + } + + public enum GuildFinderOptionsAvailability + { + Weekdays = 0x1, + Weekends = 0x2, + Always = Weekdays | Weekends + } + + public enum GuildFinderOptionsRoles + { + Tank = 0x1, + Healer = 0x2, + DPS = 0x4, + All = Tank | Healer | DPS + } + + public enum GuildFinderOptionsLevel + { + Any = 0x1, + Max = 0x2, + All = Any | Max + } +} diff --git a/Framework/Constants/ItemConst.cs b/Framework/Constants/ItemConst.cs new file mode 100644 index 000000000..53ccfe55b --- /dev/null +++ b/Framework/Constants/ItemConst.cs @@ -0,0 +1,1076 @@ +/* + * Copyright (C) 2012-2017 CypherCore + * + * 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, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +namespace Framework.Constants +{ + public struct ItemConst + { + public const int MaxDamages = 2; // changed in 3.1.0 + public const int MaxGemSockets = 3; + public const int MaxSpells = 5; + public const int MaxStats = 10; + public const int MaxBagSize = 36; + public const byte NullBag = 0; + public const byte NullSlot = 255; + public const int MaxOutfitItems = 24; + public const int MaxItemExtCostItems = 5; + public const int MaxItemExtCostCurrencies = 5; + public const int MaxItemRandomProperties = 5; + public const int MaxItemEnchantmentEffects = 3; + public const int MaxProtoSpells = 5; + public const int MaxEquipmentSetIndex = 20; + + public const int MaxItemSubclassTotal = 21; + + public const int MaxItemSetItems = 10; + public const int MaxItemSetSpells = 8; + + public static uint[] ItemQualityColors = + { + 0xff9d9d9d, // GREY + 0xffffffff, // WHITE + 0xff1eff00, // GREEN + 0xff0070dd, // BLUE + 0xffa335ee, // PURPLE + 0xffff8000, // ORANGE + 0xffe6cc80, // LIGHT YELLOW + 0xffe6cc80 // LIGHT YELLOW + }; + + public static SocketColor[] SocketColorToGemTypeMask = + { + 0, + SocketColor.Meta, + SocketColor.Red, + SocketColor.Yellow, + SocketColor.Blue, + SocketColor.Hydraulic, + SocketColor.Cogwheel, + SocketColor.Prismatic, + SocketColor.RelicIron, + SocketColor.RelicBlood, + SocketColor.RelicShadow, + SocketColor.RelicFel, + SocketColor.RelicArcane, + SocketColor.RelicFrost, + SocketColor.RelicFire, + SocketColor.RelicWater, + SocketColor.RelicLife, + SocketColor.RelicWind, + SocketColor.RelicHoly + }; + + public static ItemModifier[] AppearanceModifierSlotBySpec = + { + ItemModifier.TransmogAppearanceSpec1, + ItemModifier.TransmogAppearanceSpec2, + ItemModifier.TransmogAppearanceSpec3, + ItemModifier.TransmogAppearanceSpec4 + }; + + public const uint AppearanceModifierMaskSpecSpecific = + (1 << (int)ItemModifier.TransmogAppearanceSpec1) | + (1 << (int)ItemModifier.TransmogAppearanceSpec2) | + (1 << (int)ItemModifier.TransmogAppearanceSpec3) | + (1 << (int)ItemModifier.TransmogAppearanceSpec4); + + public static ItemModifier[] IllusionModifierSlotBySpec = + { + ItemModifier.EnchantIllusionSpec1, + ItemModifier.EnchantIllusionSpec2, + ItemModifier.EnchantIllusionSpec3, + ItemModifier.EnchantIllusionSpec4 + }; + + public const uint IllusionModifierMaskSpecSpecific = + (1 << (int)ItemModifier.EnchantIllusionSpec1) | + (1 << (int)ItemModifier.EnchantIllusionSpec2) | + (1 << (int)ItemModifier.EnchantIllusionSpec3) | + (1 << (int)ItemModifier.EnchantIllusionSpec4); + } + + public struct InventorySlots + { + public const byte BagStart = 19; + public const byte BagEnd = 23; + public const byte ItemStart = 23; + public const byte ItemEnd = 39; + + public const byte BankItemStart = 39; + public const byte BankItemEnd = 67; + public const byte BankBagStart = 67; + public const byte BankBagEnd = 74; + + public const byte BuyBackStart = 74; + public const byte BuyBackEnd = 86; + public const byte ReagentStart = 87; + public const byte ReagentEnd = 184; + public const byte ChildEquipmentStart = 184; + public const byte ChildEquipmentEnd = 187; + + public const byte Bag0 = 255; + } + + public struct EquipmentSlot + { + public const byte Start = 0; + public const byte Head = 0; + public const byte Neck = 1; + public const byte Shoulders = 2; + public const byte Shirt = 3; + public const byte Chest = 4; + public const byte Waist = 5; + public const byte Legs = 6; + public const byte Feet = 7; + public const byte Wrist = 8; + public const byte Hands = 9; + public const byte Finger1 = 10; + public const byte Finger2 = 11; + public const byte Trinket1 = 12; + public const byte Trinket2 = 13; + public const byte Cloak = 14; + public const byte MainHand = 15; + public const byte OffHand = 16; + public const byte Ranged = 17; + public const byte Tabard = 18; + public const byte End = 19; + } + + public enum SocketColor + { + Meta = 0x00001, + Red = 0x00002, + Yellow = 0x00004, + Blue = 0x00008, + Hydraulic = 0x00010, // Not Used + Cogwheel = 0x00020, + Prismatic = 0x0000e, + RelicIron = 0x00040, + RelicBlood = 0x00080, + RelicShadow = 0x00100, + RelicFel = 0x00200, + RelicArcane = 0x00400, + RelicFrost = 0x00800, + RelicFire = 0x01000, + RelicWater = 0x02000, + RelicLife = 0x04000, + RelicWind = 0x08000, + RelicHoly = 0x10000, + + Standard = (Red | Yellow | Blue) + } + + public enum ItemExtendedCostFlags + { + RequireGuild = 0x01, + RequireSeasonEarned1 = 0x02, + RequireSeasonEarned2 = 0x04, + RequireSeasonEarned3 = 0x08, + RequireSeasonEarned4 = 0x10, + RequireSeasonEarned5 = 0x20, + } + + public enum ItemModType + { + Mana = 0, + Health = 1, + Agility = 3, + Strength = 4, + Intellect = 5, + Spirit = 6, + Stamina = 7, + DefenseSkillRating = 12, + DodgeRating = 13, + ParryRating = 14, + BlockRating = 15, + HitMeleeRating = 16, + HitRangedRating = 17, + HitSpellRating = 18, + CritMeleeRating = 19, + CritRangedRating = 20, + CritSpellRating = 21, + HitTakenMeleeRating = 22, + HitTakenRangedRating = 23, + HitTakenSpellRating = 24, + CritTakenMeleeRating = 25, + CritTakenRangedRating = 26, + CritTakenSpellRating = 27, + HasteMeleeRating = 28, + HasteRangedRating = 29, + HasteSpellRating = 30, + HitRating = 31, + CritRating = 32, + HitTakenRating = 33, + CritTakenRating = 34, + ResilienceRating = 35, + HasteRating = 36, + ExpertiseRating = 37, + AttackPower = 38, + RangedAttackPower = 39, + Versatility = 40, + SpellHealingDone = 41, + SpellDamageDone = 42, + ManaRegeneration = 43, + ArmorPenetrationRating = 44, + SpellPower = 45, + HealthRegen = 46, + SpellPenetration = 47, + BlockValue = 48, + MasteryRating = 49, + ExtraArmor = 50, + FireResistance = 51, + FrostResistance = 52, + HolyResistance = 53, + ShadowResistance = 54, + NatureResistance = 55, + ArcaneResistance = 56, + PvpPower = 57, + CrAmplify = 58, + CrMultistrike = 59, + CrReadiness = 60, + CrSpeed = 61, + CrLifesteal = 62, + CrAvoidance = 63, + CrSturdiness = 64, + CrUnused7 = 65, + CrCleave = 66, + CrUnused9 = 67, + CrUnused10 = 68, + CrUnused11 = 69, + CrUnused12 = 70, + AgiStrInt = 71, + AgiStr = 72, + AgiInt = 73, + StrInt = 74 + } + + public enum ItemSpelltriggerType : byte + { + OnUse = 0, // use after equip cooldown + OnEquip = 1, + ChanceOnHit = 2, + Soulstone = 4, + /* + * ItemSpelltriggerType 5 might have changed on 2.4.3/3.0.3: Such auras + * will be applied on item pickup and removed on item loss - maybe on the + * other hand the item is destroyed if the aura is removed ("removed on + * death" of spell 57348 makes me think so) + */ + OnObtain = 5, + LearnSpellId = 6, // used in itemtemplate.spell2 with spellid with SPELLGENERICLEARN in spell1 + Max = 7 + } + + public enum BuyBankSlotResult + { + FailedTooMany = 0, + InsufficientFunds = 1, + NotBanker = 2, + OK = 3 + } + + public enum EnchantmentSlotMask : ushort + { + CanSouldBound = 0x01, + Unk1 = 0x02, + Unk2 = 0x04, + Unk3 = 0x08, + Collectable = 0x100, + HideIfNotCollected = 0x200, + } + + public enum ItemModifier + { + TransmogAppearanceAllSpecs = 0, + TransmogAppearanceSpec1 = 1, + UpgradeId = 2, + BattlePetSpeciesId = 3, + BattlePetBreedData = 4, // (Breedid) | (Breedquality << 24) + BattlePetLevel = 5, + BattlePetDisplayId = 6, + EnchantIllusionAllSpecs = 7, + ArtifactAppearanceId = 8, + ScalingStatDistributionFixedLevel = 9, + EnchantIllusionSpec1 = 10, + TransmogAppearanceSpec2 = 11, + EnchantIllusionSpec2 = 12, + TransmogAppearanceSpec3 = 13, + EnchantIllusionSpec3 = 14, + TransmogAppearanceSpec4 = 15, + EnchantIllusionSpec4 = 16, + ChallengeMapChallengeModeId = 17, + ChallengeKeystoneLevel = 18, + ChallengeKeystoneAffixId1 = 19, + ChallengeKeystoneAffixId2 = 20, + ChallengeKeystoneAffixId3 = 21, + ChallengeKeystoneIsCharged = 22, + ArtifactKnowledgeLevel = 23, + ArtifactTier = 24, + + Max + } + + public enum ItemBonusType : byte + { + ItemLevel = 1, + Stat = 2, + Quality = 3, + Description = 4, + Suffix = 5, + Socket = 6, + Appearance = 7, + RequiredLevel = 8, + DisplayToastMethod = 9, + RepairCostMuliplier = 10, + ScalingStatDistribution = 11, + DisenchantLootId = 12, + ScalingStatDistribution2 = 13, + ItemLevelOverride = 14, + RandomEnchantment = 15, // Responsible for showing "" or "+%d Rank Random Minor Trait" in the tooltip before item is obtained + Bounding = 16, + RelicType = 17 + } + + public enum ItemEnchantmentType : byte + { + None = 0, + CombatSpell = 1, + Damage = 2, + EquipSpell = 3, + Resistance = 4, + Stat = 5, + Totem = 6, + UseSpell = 7, + PrismaticSocket = 8, + ArtifactPowerBonusRankByType = 9, + ArtifactPowerBonusRankByID = 10, + BonusListID = 11, + BonusListCurve = 12, + ArtifactPowerBonusRankPicker = 13 + } + + public enum BagFamilyMask + { + None = 0x00, + Arrows = 0x01, + Bullets = 0x02, + SoulShards = 0x04, + LeatherworkingSupp = 0x08, + InscriptionSupp = 0x10, + Herbs = 0x20, + EnchantingSupp = 0x40, + EngineeringSupp = 0x80, + Keys = 0x100, + Gems = 0x200, + MiningSupp = 0x400, + SoulboundEquipment = 0x800, + VanityPets = 0x1000, + CurrencyTokens = 0x2000, + QuestItems = 0x4000, + FishingSupp = 0x8000, + CookingSupp = 0x10000 + } + + public enum InventoryType : byte + { + NonEquip = 0, + Head = 1, + Neck = 2, + Shoulders = 3, + Body = 4, + Chest = 5, + Waist = 6, + Legs = 7, + Feet = 8, + Wrists = 9, + Hands = 10, + Finger = 11, + Trinket = 12, + Weapon = 13, + Shield = 14, + Ranged = 15, + Cloak = 16, + Weapon2Hand = 17, + Bag = 18, + Tabard = 19, + Robe = 20, + WeaponMainhand = 21, + WeaponOffhand = 22, + Holdable = 23, + Ammo = 24, + Thrown = 25, + RangedRight = 26, + Quiver = 27, + Relic = 28, + Max = 29 + } + + public enum VisibleEquipmentSlot + { + Head = 0, + Shoulder = 2, + Shirt = 3, + Chest = 4, + Belt = 5, + Pants = 6, + Boots = 7, + Wrist = 8, + Gloves = 9, + Back = 14, + Tabard = 18 + } + + public enum ItemBondingType + { + None = 0, + OnAcquire = 1, + OnEquip = 2, + OnUse = 3, + Quest = 4, + } + + public enum ItemClass : sbyte + { + None = -1, + Consumable = 0, + Container = 1, + Weapon = 2, + Gem = 3, + Armor = 4, + Reagent = 5, + Projectile = 6, + TradeGoods = 7, + ItemEnhancement = 8, + Recipe = 9, + Money = 10, // Obsolete + Quiver = 11, + Quest = 12, + Key = 13, + Permanent = 14, // Obsolete + Miscellaneous = 15, + Glyph = 16, + BattlePets = 17, + WowToken = 18, + Max = 19 + } + + public enum ItemSubClassConsumable + { + Consumable = 0, + Potion = 1, + Elixir = 2, + Flask = 3, + Scroll = 4, + FoodDrink = 5, + ItemEnhancement = 6, + Bandage = 7, + ConsumableOther = 8, + VantusRune = 9, + Max = 10 + } + + public enum ItemSubClassContainer + { + Container = 0, + SoulContainer = 1, + HerbContainer = 2, + EnchantingContainer = 3, + EngineeringContainer = 4, + GemContainer = 5, + MiningContainer = 6, + LeatherworkingContainer = 7, + InscriptionContainer = 8, + TackleContainer = 9, + CookingContainer = 10, + Max = 11 + } + + public enum ItemSubClassWeapon + { + Axe = 0, // One-Handed Axes + Axe2 = 1, // Two-Handed Axes + Bow = 2, + Gun = 3, + Mace = 4, // One-Handed Maces + Mace2 = 5, // Two-Handed Maces + Polearm = 6, + Sword = 7, // One-Handed Swords + Sword2 = 8, // Two-Handed Swords + Warglaives = 9, + Staff = 10, + Exotic = 11, // One-Handed Exotics + Exotic2 = 12, // Two-Handed Exotics + Fist = 13, + Miscellaneous = 14, + Dagger = 15, + Thrown = 16, + Spear = 17, + Crossbow = 18, + Wand = 19, + FishingPole = 20, + + MaskRanged = (1 << Bow) | (1 << Gun) | (1 << Crossbow), + + Max = 12 + + } + + public enum ItemSubClassGem + { + Intellect = 0, + Agility = 1, + Strength = 2, + Stamina = 3, + Spirit = 4, + CriticalStrike = 5, + Mastery = 6, + Haste = 7, + Versatility = 8, + Other = 9, + MultipleStats = 10, + ArtifactRelic = 11, + Max = 12 + } + + public enum ItemSubClassArmor + { + Miscellaneous = 0, + Cloth = 1, + Leather = 2, + Mail = 3, + Plate = 4, + Cosmetic = 5, + Shield = 6, + Libram = 7, + Idol = 8, + Totem = 9, + Sigil = 10, + Relic = 11, + Max = 12 + } + + public enum ItemSubClassReagent + { + Reagent = 0, + Keystone = 1, + Max = 2 + } + + public enum ItemSubClassProjectile + { + Wand = 0, // Obsolete + Bolt = 1, // Obsolete + Arrow = 2, + Bullet = 3, + Thrown = 4, // Obsolete + Max = 5 + } + + public enum ItemSubClassTradeGoods + { + TradeGoods = 0, + Parts = 1, + Explosives = 2, + Devices = 3, + Jewelcrafting = 4, + Cloth = 5, + Leather = 6, + MetalStone = 7, + Meat = 8, + Herb = 9, + Elemental = 10, + TradeGoodsOther = 11, + Enchanting = 12, + Material = 13, + Enchantment = 14, + WeaponEnchantment = 15, + Inscription = 16, + ExplosivesDevices = 17, + Max = 18 + } + + public enum ItemSubclassItemEnhancement + { + Head = 0, + Neck = 1, + Shoulder = 2, + Cloak = 3, + Chest = 4, + Wrist = 5, + Hands = 6, + Waist = 7, + Legs = 8, + Feet = 9, + Finger = 10, + Weapon = 11, + TwoHandedWeapon = 12, + ShieldOffHand = 13, + Max = 14 + } + + public enum ItemSubClassRecipe + { + Book = 0, + LeatherworkingPattern = 1, + TailoringPattern = 2, + EngineeringSchematic = 3, + Blacksmithing = 4, + CookingRecipe = 5, + AlchemyRecipe = 6, + FirstAidManual = 7, + EnchantingFormula = 8, + FishingManual = 9, + JewelcraftingRecipe = 10, + InscriptionTechnique = 11, + Max = 12 + } + + public enum ItemSubClassMoney + { + Money = 0, // Obsolete + Max = 1 + } + + public enum ItemSubClassQuiver + { + Quiver0 = 0, // Obsolete + Quiver1 = 1, // Obsolete + Quiver = 2, + AmmoPouch = 3, + Max = 4, + } + + public enum ItemSubClassQuest + { + Quest = 0, + Unk3 = 3, // 1 Item (33604) + Unk8 = 8, // 2 Items (37445, 49700) + Max = 9 + } + + public enum ItemSubClassKey + { + Key = 0, + Lockpick = 1, + Max = 2 + } + + public enum ItemSubClassPermanent + { + Permanent = 0, + Max = 1 + } + + public enum ItemSubClassJunk + { + Junk = 0, + Reagent = 1, + CompanionPet = 2, + Holiday = 3, + Other = 4, + Mount = 5, + Max = 6 + } + + public enum ItemSubClassGlyph + { + Warrior = 1, + Paladin = 2, + Hunter = 3, + Rogue = 4, + Priest = 5, + DeathKnight = 6, + Shaman = 7, + Mage = 8, + Warlock = 9, + Monk = 10, + Druid = 11, + DemonHunter = 12, + Max = 13 + } + + public enum ItemSubclassBattlePet + { + BattlePet = 0, + Max = 1 + } + + public enum ItemSubclassWowToken + { + WowToken = 0, + Max = 1 + } + + public enum ItemQuality + { + Poor = 0, //Grey + Normal = 1, //White + Uncommon = 2, //Green + Rare = 3, //Blue + Epic = 4, //Purple + Legendary = 5, //Orange + Artifact = 6, //Light Yellow + Heirloom = 7, + Max = 8 + } + + public enum ItemFieldFlags : uint + { + Soulbound = 0x01, // Item Is Soulbound And Cannot Be Traded <<-- + Translated = 0x02, // Item text will not read as garbage when player does not know the language + Unlocked = 0x04, // Item Had Lock But Can Be Opened Now + Wrapped = 0x08, // Item Is Wrapped And Contains Another Item + Unk2 = 0x10, // ? + Unk3 = 0x20, // ? + Unk4 = 0x40, // ? + Unk5 = 0x80, // ? + BopTradeable = 0x100, // Allows Trading Soulbound Items + Readable = 0x200, // Opens Text Page When Right Clicked + Unk6 = 0x400, // ? + Unk7 = 0x800, // ? + Refundable = 0x1000, // Item Can Be Returned To Vendor For Its Original Cost (Extended Cost) + Unk8 = 0x2000, // ? + Unk9 = 0x4000, // ? + Unk10 = 0x8000, // ? + Unk11 = 0x00010000, // ? + Unk12 = 0x00020000, // ? + Unk13 = 0x00040000, // ? + Child = 0x00080000, + Unk15 = 0x00100000, // ? + Unk16 = 0x00200000, // ? + Unk17 = 0x00400000, // ? + Unk18 = 0x00800000, // ? + Unk19 = 0x01000000, // ? + Unk20 = 0x02000000, // ? + Unk21 = 0x04000000, // ? + Unk22 = 0x08000000, // ? + Unk23 = 0x10000000, // ? + Unk24 = 0x20000000, // ? + Unk25 = 0x40000000, // ? + Unk26 = 0x80000000 // ? + } + + public enum ItemFlags : long + { + NoPickup = 0x01, + Conjured = 0x02, // Conjured Item + HasLoot = 0x04, // Item Can Be Right Clicked To Open For Loot + HeroicTooltip = 0x08, // Makes Green "Heroic" Text Appear On Item + Deprecated = 0x10, // Cannot Equip Or Use + NoUserDestroy = 0x20, // Item Can Not Be Destroyed, Except By Using Spell (Item Can Be Reagent For Spell) + Playercast = 0x40, + NoEquipCooldown = 0x80, // No Default 30 Seconds Cooldown When Equipped + MultiLootQuest = 0x100, + IsWrapper = 0x200, // Item Can Wrap Other Items + UsesResources = 0x400, + MultiDrop = 0x800, // Looting This Item Does Not Remove It From Available Loot + ItemPurchaseRecord = 0x1000, // Item Can Be Returned To Vendor For Its Original Cost (Extended Cost) + Petition = 0x2000, // Item Is Guild Or Arena Charter + HasText = 0x4000, + NoDisenchant = 0x8000, + RealDuration = 0x10000, + NoCreator = 0x20000, + IsProspectable = 0x40000, // Item Can Be Prospected + UniqueEquippable = 0x80000, // You Can Only Equip One Of These + IgnoreForAuras = 0x100000, + IgnoreDefaultArenaRestrictions = 0x200000, // Item Can Be Used During Arena Match + NoDurabilityLoss = 0x400000, + UseWhenShapeshifted = 0x800000, // Item Can Be Used In Shapeshift Forms + HasQuestGlow = 0x1000000, + HideUnusableRecipe = 0x2000000, // Profession Recipes: Can Only Be Looted If You Meet Requirements And Don'T Already Know It + NotUseableInArena = 0x4000000, // Item Cannot Be Used In Arena + IsBoundToAccount = 0x8000000, // Item Binds To Account And Can Be Sent Only To Your Own Characters + NoReagentCost = 0x10000000, // Spell Is Cast Ignoring Reagents + IsMillable = 0x20000000, // Item Can Be Milled + ReportToGuildChat = 0x40000000, + NoProgressiveLoot = 0x80000000 + } + + public enum ItemFlags2 : uint + { + FactionHorde = 0x01, + FactionAlliance = 0x02, + DontIgnoreBuyPrice = 0x04, // When Item Uses Extended Cost, Gold Is Also Required + ClassifyAsCaster = 0x08, + ClassifyAsPhysical = 0x10, + EveryoneCanRollNeed = 0x20, + NoTradeBindOnAcquire = 0x40, + CanTradeBindOnAcquire = 0x80, + CanOnlyRollGreed = 0x100, + CasterWeapon = 0x200, + DeleteOnLogin = 0x400, + InternalItem = 0x800, + NoVendorValue = 0x1000, + ShowBeforeDiscovered = 0x2000, + OverrideGoldCost = 0x4000, + IgnoreDefaultRatedBgRestrictions = 0x8000, + NotUsableInRatedBg = 0x10000, + BnetAccountTradeOk = 0x20000, + ConfirmBeforeUse = 0x40000, + ReevaluateBondingOnTransform = 0x80000, + NoTransformOnChargeDepletion = 0x100000, + NoAlterItemVisual = 0x200000, + NoSourceForItemVisual = 0x400000, + IgnoreQualityForItemVisualSource = 0x800000, + NoDurability = 0x1000000, + RoleTank = 0x2000000, + RoleHealer = 0x4000000, + RoleDamage = 0x8000000, + CanDropInChallengeMode = 0x10000000, + NeverStackInLootUi = 0x20000000, + DisenchantToLootTable = 0x40000000, + UsedInATradeskill = 0x80000000 + } + + public enum ItemFlags3 + { + DontDestroyOnQuestAccept = 0x01, + ItemCanBeUpgraded = 0x02, + UpgradeFromItemOverridesDropUpgrade = 0x04, + AlwaysFfaInLoot = 0x08, + HideUpgradeLevelsIfNotUpgraded = 0x10, + UpdateInteractions = 0x20, + UpdateDoesntLeaveProgressiveWinHistory = 0x40, + IgnoreItemHistoryTracker = 0x80, + IgnoreItemLevelCapInPvp = 0x100, + DisplayAsHeirloom = 0x200, // Item Appears As Having Heirloom Quality Ingame Regardless Of Its Real Quality (Does Not Affect Stat Calculation) + SkipUseCheckOnPickup = 0x400, + Obsolete = 0x800, + DontDisplayInGuildNews = 0x1000, // Item Is Not Included In The Guild News Panel + PvpTournamentGear = 0x2000, + RequiresStackChangeLog = 0x4000, + UnusedFlag = 0x8000, + HideNameSuffix = 0x10000, + PushLoot = 0x20000, + DontReportLootLogToParty = 0x40000, + AlwaysAllowDualWield = 0x80000, + Obliteratable = 0x100000, + ActsAsTransmogHiddenVisualOption = 0x200000, + ExpireOnWeeklyReset = 0x400000, + DoesntShowUpInTransmogUntilCollected = 0x800000, + CanStoreEnchants = 0x1000000 + } + + public enum ItemFlagsCustom + { + Unused = 0x0001, + IgnoreQuestStatus = 0x0002, // No quest status will be checked when this item drops + FollowLootRules = 0x0004 // Item will always follow group/master/need before greed looting rules + } + + public enum InventoryResult + { + Ok = 0, + CantEquipLevelI = 1, // You Must Reach Level %D To Use That Item. + CantEquipSkill = 2, // You Aren'T Skilled Enough To Use That Item. + WrongSlot = 3, // That Item Does Not Go In That Slot. + BagFull = 4, // That Bag Is Full. + BagInBag = 5, // Can'T Put Non-Empty Bags In Other Bags. + TradeEquippedBag = 6, // You Can'T Trade Equipped Bags. + AmmoOnly = 7, // Only Ammo Can Go There. + ProficiencyNeeded = 8, // You Do Not Have The Required Proficiency For That Item. + NoSlotAvailable = 9, // No Equipment Slot Is Available For That Item. + CantEquipEver = 10, // You Can Never Use That Item. + CantEquipEver2 = 11, // You Can Never Use That Item. + NoSlotAvailable2 = 12, // No Equipment Slot Is Available For That Item. + Equipped2handed = 13, // Cannot Equip That With A Two-Handed Weapon. + TwoHandSkillNotFound = 14, // You Cannot Dual-Wield + WrongBagType = 15, // That Item Doesn'T Go In That Container. + WrongBagType2 = 16, // That Item Doesn'T Go In That Container. + ItemMaxCount = 17, // You Can'T Carry Any More Of Those Items. + NoSlotAvailable3 = 18, // No Equipment Slot Is Available For That Item. + CantStack = 19, // This Item Cannot Stack. + NotEquippable = 20, // This Item Cannot Be Equipped. + CantSwap = 21, // These Items Can'T Be Swapped. + SlotEmpty = 22, // That Slot Is Empty. + ItemNotFound = 23, // The Item Was Not Found. + DropBoundItem = 24, // You Can'T Drop A Soulbound Item. + OutOfRange = 25, // Out Of Range. + TooFewToSplit = 26, // Tried To Split More Than Number In Stack. + SplitFailed = 27, // Couldn'T Split Those Items. + SpellFailedReagentsGeneric = 28, // Missing Reagent + NotEnoughMoney = 29, // You Don'T Have Enough Money. + NotABag = 30, // Not A Bag. + DestroyNonemptyBag = 31, // You Can Only Do That With Empty Bags. + NotOwner = 32, // You Don'T Own That Item. + OnlyOneQuiver = 33, // You Can Only Equip One Quiver. + NoBankSlot = 34, // You Must Purchase That Bag Slot First + NoBankHere = 35, // You Are Too Far Away From A Bank. + ItemLocked = 36, // Item Is Locked. + GenericStunned = 37, // You Are Stunned + PlayerDead = 38, // You Can'T Do That When You'Re Dead. + ClientLockedOut = 39, // You Can'T Do That Right Now. + InternalBagError = 40, // Internal Bag Error + OnlyOneBolt = 41, // You Can Only Equip One Quiver. + OnlyOneAmmo = 42, // You Can Only Equip One Ammo Pouch. + CantWrapStackable = 43, // Stackable Items Can'T Be Wrapped. + CantWrapEquipped = 44, // Equipped Items Can'T Be Wrapped. + CantWrapWrapped = 45, // Wrapped Items Can'T Be Wrapped. + CantWrapBound = 46, // Bound Items Can'T Be Wrapped. + CantWrapUnique = 47, // Unique Items Can'T Be Wrapped. + CantWrapBags = 48, // Bags Can'T Be Wrapped. + LootGone = 49, // Already Looted + InvFull = 50, // Inventory Is Full. + BankFull = 51, // Your Bank Is Full + VendorSoldOut = 52, // That Item Is Currently Sold Out. + BagFull2 = 53, // That Bag Is Full. + ItemNotFound2 = 54, // The Item Was Not Found. + CantStack2 = 55, // This Item Cannot Stack. + BagFull3 = 56, // That Bag Is Full. + VendorSoldOut2 = 57, // That Item Is Currently Sold Out. + ObjectIsBusy = 58, // That Object Is Busy. + CantBeDisenchanted = 59, + NotInCombat = 60, // You Can'T Do That While In Combat + NotWhileDisarmed = 61, // You Can'T Do That While Disarmed + BagFull4 = 62, // That Bag Is Full. + CantEquipRank = 63, // You Don'T Have The Required Rank For That Item + CantEquipReputation = 64, // You Don'T Have The Required Reputation For That Item + TooManySpecialBags = 65, // You Cannot Equip Another Bag Of That Type + LootCantLootThatNow = 66, // You Can'T Loot That Item Now. + ItemUniqueEquippable = 67, // You Cannot Equip More Than One Of Those. + VendorMissingTurnins = 68, // You Do Not Have The Required Items For That Purchase + NotEnoughHonorPoints = 69, // You Don'T Have Enough Honor Points + NotEnoughArenaPoints = 70, // You Don'T Have Enough Arena Points + ItemMaxCountSocketed = 71, // You Have The Maximum Number Of Those Gems In Your Inventory Or Socketed Into Items. + MailBoundItem = 72, // You Can'T Mail Soulbound Items. + InternalBagError2 = 73, // Internal Bag Error + BagFull5 = 74, // That Bag Is Full. + ItemMaxCountEquippedSocketed = 75, // You Have The Maximum Number Of Those Gems Socketed Into Equipped Items. + ItemUniqueEquippableSocketed = 76, // You Cannot Socket More Than One Of Those Gems Into A Single Item. + TooMuchGold = 77, // At Gold Limit + NotDuringArenaMatch = 78, // You Can'T Do That While In An Arena Match + TradeBoundItem = 79, // You Can'T Trade A Soulbound Item. + CantEquipRating = 80, // You Don'T Have The Personal, Team, Or Battleground Rating Required To Buy That Item + EventAutoequipBindConfirm = 81, + NotSameAccount = 82, // Account-Bound Items Can Only Be Given To Your Own Characters. + NoOutput = 83, + ItemMaxLimitCategoryCountExceededIs = 84, // You Can Only Carry %D %S + ItemMaxLimitCategorySocketedExceededIs = 85, // You Can Only Equip %D |4item:Items In The %S Category + ScalingStatItemLevelExceeded = 86, // Your Level Is Too High To Use That Item + PurchaseLevelTooLow = 87, // You Must Reach Level %D To Purchase That Item. + CantEquipNeedTalent = 88, // You Do Not Have The Required Talent To Equip That. + ItemMaxLimitCategoryEquippedExceededIs = 89, // You Can Only Equip %D |4item:Items In The %S Category + ShapeshiftFormCannotEquip = 90, // Cannot Equip Item In This Form + ItemInventoryFullSatchel = 91, // Your Inventory Is Full. Your Satchel Has Been Delivered To Your Mailbox. + ScalingStatItemLevelTooLow = 92, // Your Level Is Too Low To Use That Item + CantBuyQuantity = 93, // You Can'T Buy The Specified Quantity Of That Item. + ItemIsBattlePayLocked = 94, // Your purchased item is still waiting to be unlocked + ReagentBankFull = 95, // Your reagent bank is full + ReagentBankLocked = 96, + WrongBagType3 = 97, + CantUseItem = 98, // You can't use that item. + CantBeObliterated = 99, // You can't obliterate that item + GuildBankConjuredItem = 100,// You cannot store conjured items in the guild bank + } + + public enum BuyResult + { + CantFindItem = 0, + ItemAlreadySold = 1, + NotEnoughtMoney = 2, + SellerDontLikeYou = 4, + DistanceTooFar = 5, + ItemSoldOut = 7, + CantCarryMore = 8, + RankRequire = 11, + ReputationRequire = 12 + } + + public enum SellResult + { + CantFindItem = 1, + CantSellItem = 2, // Merchant Doesn'T Like That Item + CantFindVendor = 3, // Merchant Doesn'T Like You + YouDontOwnThatItem = 4, // You Don'T Own That Item + Unk = 5, // Nothing Appears... + OnlyEmptyBag = 6 // Can Only Do With Empty Bags + } + + public enum EnchantmentSlot + { + Perm = 0, + Temp = 1, + Sock1 = 2, + Sock2 = 3, + Sock3 = 4, + Bonus = 5, + Prismatic = 6, // added at apply special permanent enchantment + Use = 7, + + MaxInspected = 8, + + Prop0 = 8, // used with RandomSuffix + Prop1 = 9, // used with RandomSuffix + Prop2 = 10, // used with RandomSuffix and RandomProperty + Prop3 = 11, // used with RandomProperty + Prop4 = 12, // used with RandomProperty + Max = 13 + } + + public enum ItemUpdateState + { + Unchanged = 0, + Changed = 1, + New = 2, + Removed = 3 + } + + public enum ItemVendorType + { + None = 0, + Item = 1, + Currency = 2, + } + + public enum CurrencyFlags + { + Tradeable = 0x01, + // ... + HighPrecision = 0x08, + // ... + } + + public enum CurrencyTypes + { + JusticePoints = 395, + ValorPoints = 396, + ApexisCrystals = 823, + ArtifactKnowledge = 1171, + } + + public enum PlayerCurrencyState + { + Unchanged = 0, + Changed = 1, + New = 2, + Removed = 3 //not removed just set count == 0 + } + + public enum ItemTransmogrificationWeaponCategory + { + // Two-handed + Melee2H, + Ranged, + + // One-handed + AxeMaceSword1H, + Dagger, + Fist, + + Invalid + } +} diff --git a/Framework/Constants/LFGConst.cs b/Framework/Constants/LFGConst.cs new file mode 100644 index 000000000..1e6583800 --- /dev/null +++ b/Framework/Constants/LFGConst.cs @@ -0,0 +1,179 @@ +/* + * Copyright (C) 2012-2017 CypherCore + * + * 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, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +using System; + +namespace Framework.Constants +{ + [Flags] + public enum LfgRoles + { + None = 0x00, + Leader = 0x01, + Tank = 0x02, + Healer = 0x04, + Damage = 0x08 + } + + public enum LfgUpdateType + { + Default = 0, // Internal Use + LeaderUnk1 = 1, // Fixme: At Group Leave + RolecheckAborted = 4, + JoinQueue = 6, + RolecheckFailed = 7, + RemovedFromQueue = 8, + ProposalFailed = 9, + ProposalDeclined = 10, + GroupFound = 11, + AddedToQueue = 13, + ProposalBegin = 14, + UpdateStatus = 15, + GroupMemberOffline = 16, + GroupDisbandUnk16 = 17, // Fixme: Sometimes At Group Disband + JoinQueueInitial = 24, + DungeonFinished = 25, + PartyRoleNotAvailable = 43, + JoinLfgObjectFailed = 45, + } + + public enum LfgState + { + None, + Rolecheck, + Queued, + Proposal, + //Boot, + Dungeon = 5, + FinishedDungeon, + Raidbrowser + } + + public enum LfgLockStatusType + { + InsufficientExpansion = 1, + TooLowLevel = 2, + TooHighLevel = 3, + TooLowGearScore = 4, + TooHighGearScore = 5, + RaidLocked = 6, + AttunementTooLowLevel = 1001, + AttunementTooHighLevel = 1002, + QuestNotCompleted = 1022, + MissingItem = 1025, + NotInSeason = 1031, + MissingAchievement = 1034 + } + + public enum LfgOptions + { + EnableDungeonFinder = 0x01, + EnableRaidBrowser = 0x02, + } + + public enum LfgFlags + { + Unk1 = 0x1, + Unk2 = 0x2, + Seasonal = 0x4, + Unk3 = 0x8 + } + + public enum LfgType : byte + { + None = 0, + Dungeon = 1, + Raid = 2, + Zone = 4, + Quest = 5, + RandomDungeon = 6 + } + + public enum LfgProposalState + { + Initiating = 0, + Failed = 1, + Success = 2 + } + + public enum LfgTeleportResult + { + // 7 = "You Can'T Do That Right Now" | 5 = No Client Reaction + Ok = 0, // Internal Use + PlayerDead = 1, + Falling = 2, + InVehicle = 3, + Fatigue = 4, + InvalidLocation = 6, + Charming = 8 // Fixme - It Can Be 7 Or 8 (Need Proper Data) + } + + public enum LfgJoinResult + { + // 3 = No Client Reaction | 18 = "Rolecheck Failed" + Ok = 0x00, // Joined (No Client Msg) + Failed = 0x1b, // Rolecheck Failed + Groupfull = 0x1c, // Your Group Is Full + InternalError = 0x1e, // Internal Lfg Error + NotMeetReqs = 0x1f, // You Do Not Meet The Requirements For The Chosen Dungeons + PartyNotMeetReqs = 6, // One Or More Party Members Do Not Meet The Requirements For The Chosen Dungeons + MixedRaidDungeon = 0x20, // You Cannot Mix Dungeons, Raids, And Random When Picking Dungeons + MultiRealm = 0x21, // The Dungeon You Chose Does Not Support Players From Multiple Realms + Disconnected = 0x22, // One Or More Party Members Are Pending Invites Or Disconnected + PartyInfoFailed = 0x23, // Could Not Retrieve Information About Some Party Members + DungeonInvalid = 0x24, // One Or More Dungeons Was Not Valid + Deserter = 0x25, // You Can Not Queue For Dungeons Until Your Deserter Debuff Wears Off + PartyDeserter = 0x26, // One Or More Party Members Has A Deserter Debuff + RandomCooldown = 0x27, // You Can Not Queue For Random Dungeons While On Random Dungeon Cooldown + PartyRandomCooldown = 0x28, // One Or More Party Members Are On Random Dungeon Cooldown + TooMuchMembers = 0x29, // You Can Not Enter Dungeons With More That 5 Party Members + UsingBgSystem = 0x2a, // You Can Not Use The Dungeon System While In Bg Or Arenas + RoleCheckFailed = 0x2b // Role Check Failed, Client Shows Special Error + } + + public enum LfgRoleCheckState + { + Default = 0, // Internal Use = Not Initialized. + Finished = 1, // Role Check Finished + Initialiting = 2, // Role Check Begins + MissingRole = 3, // Someone Didn'T Selected A Role After 2 Mins + WrongRoles = 4, // Can'T Form A Group With That Role Selection + Aborted = 5, // Someone Leave The Group + NoRole = 6 // Someone Selected No Role + } + + public enum LfgAnswer + { + Pending = -1, + Deny = 0, + Agree = 1 + } + + public enum LfgCompatibility + { + Pending, + WrongGroupSize, + TooMuchPlayers, + MultipleLfgGroups, + HasIgnores, + NoRoles, + NoDungeons, + WithLessPlayers, // Values Under This = Not Compatible (Do Not Modify Order) + BadStates, + Match // Must Be The Last One + } +} diff --git a/Framework/Constants/Language.cs b/Framework/Constants/Language.cs new file mode 100644 index 000000000..d914aa62f --- /dev/null +++ b/Framework/Constants/Language.cs @@ -0,0 +1,1287 @@ +/* + * Copyright (C) 2012-2017 CypherCore + * + * 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, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +namespace Framework.Constants +{ + public enum Language : uint + { + Universal = 0, + Orcish = 1, + Darnassian = 2, + Taurahe = 3, + Dwarvish = 6, + Common = 7, + Demonic = 8, + Titan = 9, + Thalassian = 10, + Draconic = 11, + Kalimag = 12, + Gnomish = 13, + Troll = 14, + Gutterspeak = 33, + Draenei = 35, + Zombie = 36, + GnomishBinary = 37, + GoblinBinary = 38, + Worgen = 39, + Goblin = 40, + PandarenNeutral = 42, + PandarenAlliance = 43, + PandarenHorde = 44, + Rikkitun = 168, + Addon = 0xffffffff // Used By Addons, In 2.4.0 Not Exist, Replaced By Messagetype? + } + + public enum CypherStrings + { + // For Chat Commands + SelectCharOrCreature = 1, + SelectCreature = 2, + + // Level 0 Chat + Systemmessage = 3, + Eventmessage = 4, + NoHelpCmd = 5, + NoCmd = 6, + NoSubcmd = 7, + SubcmdsList = 8, + AvailableCmd = 9, + CmdSyntax = 10, + AccountLevel = 11, + ConnectedUsers = 12, + Uptime = 13, + PlayerSaved = 14, + PlayersSaved = 15, + GmsOnSrv = 16, + GmsNotLogged = 17, + YouInFlight = 18, + UpdateDiff = 19, + ShutdownTimeleft = 20, + CharInFlight = 21, + CharNonMounted = 22, + YouInCombat = 23, + YouUsedItRecently = 24, + CommandNotchangepassword = 25, + CommandPassword = 26, + CommandWrongoldpassword = 27, + CommandAcclocklocked = 28, + CommandAcclockunlocked = 29, + SpellRank = 30, + Known = 31, + Learn = 32, + Passive = 33, + Talent = 34, + Active = 35, + Complete = 36, + Offline = 37, + On = 38, + Off = 39, + YouAre = 40, + Visible = 41, + Invisible = 42, + Done = 43, + You = 44, + Unknown = 45, + Error = 46, + NonExistCharacter = 47, + // Unused = 48, + LevelMinrequired = 49, + LevelMinrequiredAndItem = 50, + NpcTainerHello = 51, + CommandInvalidItemCount = 52, + CommandMailItemsLimit = 53, + NewPasswordsNotMatch = 54, + PasswordTooLong = 55, + MotdCurrent = 56, + UsingWorldDb = 57, + UsingScriptLib = 58, + UsingEventAi = 59, + ConnectedPlayers = 60, + AccountAddon = 61, + ImproperValue = 62, + RbacWrongParameterId = 63, + RbacWrongParameterRealm = 64, + RbacListHeaderGranted = 65, + RbacListHeaderDenied = 66, + RbacListHeaderBySecLevel = 67, + RbacListPermissionsHeader = 68, + RbacListPermsLinkedHeader = 69, + RbacListEmpty = 70, + RbacListElement = 71, + RbacPermGrantedInList = 72, + RbacPermGrantedInDeniedList = 73, + RbacPermGranted = 74, + RbacPermDeniedInList = 75, + RbacPermDeniedInGrantedList = 76, + RbacPermDenied = 77, + RbacPermRevoked = 78, + RbacPermRevokedNotInList = 79, + Pvpstats = 80, + PvpstatsDisabled = 81, + CommandNearGraveyard = 82, + CommandNearGraveyardNotfound = 83, + GoinfoSize = 84, + GoinfoAddon = 85, + GoinfoModel = 86, + // Free 87 - 95 + + + GuildRenameAlreadyExists = 96, + GuildRenameDone = 97, + RenamePlayerAlreadyExists = 98, + RenamePlayerWithNewName = 99, + + // Level 1 Chat + GlobalNotify = 100, + MapPosition = 101, + IsTeleported = 102, + CannotSummonToInst = 103, + CannotGoToInstParty = 104, + CannotGoToInstGm = 105, + CannotGoInstInst = 106, + CannotSummonInstInst = 107, + Summoning = 108, + SummonedBy = 109, + TeleportingTo = 110, + TeleportedToBy = 111, + NoPlayer = 112, + AppearingAt = 113, + AppearingTo = 114, + BadValue = 115, + NoCharSelected = 116, + NotInGroup = 117, + + YouChangeHp = 118, + YoursHpChanged = 119, + YouChangeMana = 120, + YoursManaChanged = 121, + YouChangeEnergy = 122, + YoursEnergyChanged = 123, + + CurrentEnergy = 124, //Log + YouChangeRage = 125, + YoursRageChanged = 126, + YouChangeLvl = 127, + CurrentFaction = 128, + WrongFaction = 129, + YouChangeFaction = 130, + YouChangeSpellflatid = 131, + YoursSpellflatidChanged = 132, + YouGiveTaxis = 133, + YouRemoveTaxis = 134, + YoursTaxisAdded = 135, + YoursTaxisRemoved = 136, + + YouChangeAspeed = 137, + YoursAspeedChanged = 138, + YouChangeSpeed = 139, + YoursSpeedChanged = 140, + YouChangeSwimSpeed = 141, + YoursSwimSpeedChanged = 142, + YouChangeBackSpeed = 143, + YoursBackSpeedChanged = 144, + YouChangeFlySpeed = 145, + YoursFlySpeedChanged = 146, + + YouChangeSize = 147, + YoursSizeChanged = 148, + NoMount = 149, + YouGiveMount = 150, + MountGived = 151, + + CurrentMoney = 152, + YouTakeAllMoney = 153, + YoursAllMoneyGone = 154, + YouTakeMoney = 155, + YoursMoneyTaken = 156, + YouGiveMoney = 157, + YoursMoneyGiven = 158, + YouHearSound = 159, + + NewMoney = 160, // Log + + RemoveBit = 161, + SetBit = 162, + CommandTeleTableempty = 163, + CommandTeleNotfound = 164, + CommandTeleParameter = 165, + CommandTeleNolocation = 166, + ReservedName = 167, + CommandTeleLocation = 168, + + MailSent = 169, + SoundNotExist = 170, + CantTeleportSelf = 171, + ConsoleCommand = 172, + YouChangeRunicPower = 173, + YoursRunicPowerChanged = 174, + LiquidStatus = 175, + InvalidGameobjectType = 176, + GameobjectDamaged = 177, + + PhasingSuccess = 178, + PhasingFailed = 179, + PhasingLastPhase = 180, + PhasingList = 181, + PhasingPhasemask = 182, + PhasingReportStatus = 183, + PhasingNoDefinitions = 184, // Phasing + + GridPosition = 185, + TransportPosition = 186, + // Room For More Level 1 187-199 Not Used + + // Level 2 Chat + NoSelection = 200, + ObjectGuid = 201, + TooLongName = 202, + CharsOnly = 203, + TooLongSubname = 204, + NotImplemented = 205, + + ItemAddedToList = 206, + ItemNotFound = 207, + ItemDeletedFromList = 208, + ItemNotInList = 209, + ItemAlreadyInList = 210, + + ResetSpellsOnline = 211, + ResetSpellsOffline = 212, + ResetTalentsOnline = 213, + ResetTalentsOffline = 214, + ResetSpells = 215, + ResetTalents = 216, + + ResetallUnknownCase = 217, + ResetallSpells = 218, + ResetallTalents = 219, + + WaypointNotfound = 220, + WaypointNotfoundlast = 221, + WaypointNotfoundsearch = 222, + WaypointNotfounddbproblem = 223, + WaypointCreatselected = 224, + WaypointCreatnotfound = 225, + WaypointVpSelect = 226, + WaypointVpNotfound = 227, + WaypointVpNotcreated = 228, + WaypointVpAllremoved = 229, + WaypointNotcreated = 230, + WaypointNoguid = 231, + WaypointNowaypointgiven = 232, + WaypointArgumentreq = 233, + WaypointAdded = 234, + WaypointAddedNo = 235, + WaypointChanged = 236, + WaypointChangedNo = 237, + WaypointExported = 238, + WaypointNothingtoexport = 239, + WaypointImported = 240, + WaypointRemoved = 241, + WaypointNotremoved = 242, + WaypointToofar1 = 243, + WaypointToofar2 = 244, + WaypointToofar3 = 245, + WaypointInfoTitle = 246, + WaypointInfoWaittime = 247, + WaypointInfoModel = 248, + WaypointInfoEmote = 249, + WaypointInfoSpell = 250, + WaypointInfoText = 251, + WaypointInfoAiscript = 252, + + RenamePlayer = 253, + RenamePlayerGuid = 254, + + WaypointWpcreatnotfound = 255, + WaypointNpcnotfound = 256, + + MoveTypeSet = 257, + MoveTypeSetNodel = 258, + UseBol = 259, + ValueSaved = 260, + ValueSavedRejoin = 261, + + CommandGoareatrnotfound = 262, + InvalidTargetCoord = 263, + InvalidZoneCoord = 264, + InvalidZoneMap = 265, + CommandTargetobjnotfound = 266, + CommandGoobjnotfound = 267, + CommandGocreatnotfound = 268, + CommandGocreatmultiple = 269, + CommandDelcreatmessage = 270, + CommandCreaturemoved = 271, + CommandCreatureatsamemap = 272, + CommandObjnotfound = 273, + CommandDelobjrefercreature = 274, + CommandDelobjmessage = 275, + CommandTurnobjmessage = 276, + CommandMoveobjmessage = 277, + CommandVendorselection = 278, + CommandNeeditemsend = 279, + CommandAddvendoritemitems = 280, + CommandKickself = 281, + CommandKickmessage = 282, + CommandDisableChatDelayed = 283, + CommandWhisperaccepting = 284, + CommandWhisperon = 285, + CommandWhisperoff = 286, + CommandCreatguidnotfound = 287, + // Ticket Strings Need Rewrite // 288-296 Free + + // End + CommandSpawndist = 297, + CommandSpawntime = 298, + CommandModifyHonor = 299, + + YourChatDisabled = 300, + YouDisableChat = 301, + ChatAlreadyEnabled = 302, + YourChatEnabled = 303, + YouEnableChat = 304, + + CommandModifyRep = 305, + CommandModifyArena = 306, + CommandFactionNotfound = 307, + CommandFactionUnknown = 308, + CommandFactionInvparam = 309, + CommandFactionDelta = 310, + FactionList = 311, + FactionVisible = 312, + FactionAtwar = 313, + FactionPeaceForced = 314, + FactionHidden = 315, + FactionInvisibleForced = 316, + FactionInactive = 317, + RepHated = 318, + RepHostile = 319, + RepUnfriendly = 320, + RepNeutral = 321, + RepFriendly = 322, + RepHonored = 323, + RepRevered = 324, + RepExalted = 325, + CommandFactionNorepError = 326, + FactionNoreputation = 327, + LookupPlayerAccount = 328, + LookupPlayerCharacter = 329, + NoPlayersFound = 330, + ExtendedCostNotExist = 331, + GmOn = 332, + GmOff = 333, + GmChatOn = 334, + GmChatOff = 335, + YouRepairItems = 336, + YourItemsRepaired = 337, + YouSetWaterwalk = 338, + YourWaterwalkSet = 339, + CreatureFollowYouNow = 340, + CreatureNotFollowYou = 341, + CreatureNotFollowYouNow = 342, + CreatureNonTameable = 343, + YouAlreadyHavePet = 344, + CustomizePlayer = 345, + CustomizePlayerGuid = 346, + CommandGotaxinodenotfound = 347, + GameobjectHaveInvalidData = 348, + TitleListChat = 349, + TitleListConsole = 350, + CommandNotitlefound = 351, + InvalidTitleId = 352, + TitleAddRes = 353, + TitleRemoveRes = 354, + TitleCurrentRes = 355, + CurrentTitleReset = 356, + CommandCheatStatus = 357, + CommandCheatGod = 358, + CommandCheatCt = 359, + CommandCheatCd = 360, + CommandCheatPower = 361, + CommandCheatWw = 362, + CommandWhisperoffplayer = 363, + CommandCheatTaxinodes = 364, + // Room For More Level 2 365-399 Not Used + + // Level 3 Chat + ScriptsReloaded = 400, + YouChangeSecurity = 401, + YoursSecurityChanged = 402, + YoursSecurityIsLow = 403, + CreatureMoveDisabled = 404, + CreatureMoveEnabled = 405, + NoWeather = 406, + WeatherDisabled = 407, + + BanYoubanned = 408, + BanYoupermbanned = 409, + BanNotfound = 410, + + UnbanUnbanned = 411, + UnbanError = 412, + + AccountNotExist = 413, + + BaninfoNocharacter = 414, + BaninfoNoip = 415, + BaninfoNoaccountban = 416, + BaninfoBanhistory = 417, + BaninfoHistoryentry = 418, + BaninfoInfinite = 419, + BaninfoNever = 420, + Yes = 421, + No = 422, + BaninfoIpentry = 423, + + BanlistNoip = 424, + BanlistNoaccount = 425, + BanlistNocharacter = 426, + BanlistMatchingip = 427, + BanlistMatchingaccount = 428, + + CommandLearnManySpells = 429, + CommandLearnClassSpells = 430, + CommandLearnClassTalents = 431, + CommandLearnAllLang = 432, + CommandLearnAllCraft = 433, + CommandCouldnotfind = 434, + CommandItemidinvalid = 435, + CommandNoitemfound = 436, + CommandListobjinvalidid = 437, + CommandListitemmessage = 438, + CommandListobjmessage = 439, + CommandInvalidcreatureid = 440, + CommandListcreaturemessage = 441, + CommandNoareafound = 442, + CommandNoitemsetfound = 443, + CommandNoskillfound = 444, + CommandNospellfound = 445, + CommandNoquestfound = 446, + CommandNocreaturefound = 447, + CommandNogameobjectfound = 448, + CommandGraveyardnoexist = 449, + CommandGraveyardalrlinked = 450, + CommandGraveyardlinked = 451, + CommandGraveyardwrongzone = 452, + // = 453, See PinfoPlayer + CommandGraveyarderror = 454, + CommandGraveyardNoteam = 455, + CommandGraveyardAny = 456, + CommandGraveyardAlliance = 457, + CommandGraveyardHorde = 458, + CommandGraveyardnearest = 459, + CommandZonenograveyards = 460, + CommandZonenografaction = 461, + CommandTpAlreadyexist = 462, + CommandTpAdded = 463, + CommandTpAddederr = 464, + CommandTpDeleted = 465, + CommandNotaxinodefound = 466, + CommandTargetListauras = 467, + CommandTargetAuradetail = 468, + CommandTargetListauratype = 469, + CommandTargetAurasimple = 470, + + CommandQuestNotfound = 471, + CommandQuestStartfromitem = 472, + CommandQuestRemoved = 473, + CommandQuestRewarded = 474, + CommandQuestComplete = 475, + CommandQuestActive = 476, + + CommandFlymodeStatus = 477, + + CommandOpcodesent = 478, + + CommandImportSuccess = 479, + CommandImportFailed = 480, + CommandExportSuccess = 481, + CommandExportFailed = 482, + + CommandSpellBroken = 483, + + SetSkill = 484, + SetSkillError = 485, + + InvalidSkillId = 486, + LearningGmSkills = 487, + YouKnownSpell = 488, + TargetKnownSpell = 489, + UnknownSpell = 490, + ForgetSpell = 491, + RemoveallCooldown = 492, + RemoveCooldown = 493, + + Additem = 494, //Log + Additemset = 495, //Log + Removeitem = 496, + ItemCannotCreate = 497, + InsertGuildName = 498, + PlayerNotFound = 499, + PlayerInGuild = 500, + GuildNotCreated = 501, + NoItemsFromItemsetFound = 502, + + Distance = 503, + + ItemSlot = 504, + ItemSlotNotExist = 505, + ItemAddedToSlot = 506, + ItemSaveFailed = 507, + ItemlistSlot = 508, + ItemlistMail = 509, + ItemlistAuction = 510, + + WrongLinkType = 511, + ItemListChat = 512, + QuestListChat = 513, + CreatureEntryListChat = 514, + CreatureListChat = 515, + GoEntryListChat = 516, + GoListChat = 517, + ItemsetListChat = 518, + TeleList = 519, + SpellList = 520, + SkillListChat = 521, + + GameobjectNotExist = 522, + + GameobjectCurrent = 523, //Log + GameobjectDetail = 524, + GameobjectAdd = 525, + + MovegensList = 526, + MovegensIdle = 527, + MovegensRandom = 528, + MovegensWaypoint = 529, + MovegensAnimalRandom = 530, + MovegensConfused = 531, + MovegensChasePlayer = 532, + MovegensChaseCreature = 533, + MovegensChaseNull = 534, + MovegensHomeCreature = 535, + MovegensHomePlayer = 536, + MovegensFlight = 537, + MovegensUnknown = 538, + + NpcinfoChar = 539, + NpcinfoLevel = 540, + NpcinfoHealth = 541, + NpcinfoDynamicFlags = 542, + NpcinfoLoot = 543, + NpcinfoPosition = 544, + NpcinfoVendor = 545, + NpcinfoTrainer = 546, + NpcinfoDungeonId = 547, + + // = 548, See PinfoGmActive + // = 549, See PinfoBanned + // = 550, See PinfoMuted + + YouSetExploreAll = 551, + YouSetExploreNothing = 552, + YoursExploreSetAll = 553, + YoursExploreSetNothing = 554, + + NpcSetdata = 555, + + //! Old Ones Now Free: + CommandNearNpcMessage = 556, + + YoursLevelUp = 557, + YoursLevelDown = 558, + YoursLevelProgressReset = 559, + ExploreArea = 560, + UnexploreArea = 561, + + Update = 562, + UpdateChange = 563, + TooBigIndex = 564, + SetUint = 565, //Log + SetUintField = 566, + SetFloat = 567, //Log + SetFloatField = 568, + GetUint = 569, //Log + GetUintField = 570, + GetFloat = 571, //Log + GetFloatField = 572, + Set32bit = 573, //Log + Set32bitField = 574, + Change32bit = 575, //Log + Change32bitField = 576, + + InvisibleInvisible = 577, + InvisibleVisible = 578, + SelectedTargetNotHaveVictim = 579, + + CommandLearnAllDefaultAndQuest = 580, + CommandNearobjmessage = 581, + CommandRawpawntimes = 582, + + EventEntryListChat = 583, + Noeventfound = 584, + EventNotExist = 585, + EventInfo = 586, + EventAlreadyActive = 587, + EventNotActive = 588, + + MovegensPoint = 589, + MovegensFear = 590, + MovegensDistract = 591, + + CommandLearnAllRecipes = 592, + BanlistAccounts = 593, + BanlistAccountsHeader = 594, + BanlistIps = 595, + BanlistIpsHeader = 596, + Gmlist = 597, + GmlistHeader = 598, + GmlistEmpty = 599, + + // End Level 3 List, Continued At 1100 + + // Battleground + BgAWins = 600, + BgHWins = 601, + + BgWsStartTwoMinutes = 753, + BgWsStartOneMinute = 602, + BgWsStartHalfMinute = 603, + BgWsHasBegun = 604, + + BgWsCapturedHf = 605, + BgWsCapturedAf = 606, + BgWsDroppedHf = 607, + BgWsDroppedAf = 608, + BgWsReturnedAf = 609, + BgWsReturnedHf = 610, + BgWsPickedupHf = 611, + BgWsPickedupAf = 612, + BgWsFPlaced = 613, + BgWsAllianceFlagRespawned = 614, + BgWsHordeFlagRespawned = 615, + + BgEyStartTwoMinutes = 755, + BgEyStartOneMinute = 636, + BgEyStartHalfMinute = 637, + BgEyHasBegun = 638, + + BgAbAlly = 650, + BgAbHorde = 651, + BgAbNodeStables = 652, + BgAbNodeBlacksmith = 653, + BgAbNodeFarm = 654, + BgAbNodeLumberMill = 655, + BgAbNodeGoldMine = 656, + BgAbNodeTaken = 657, + BgAbNodeDefended = 658, + BgAbNodeAssaulted = 659, + BgAbNodeClaimed = 660, + + BgAbStartTwoMinutes = 754, + BgAbStartOneMinute = 661, + BgAbStartHalfMinute = 662, + BgAbHasBegun = 663, + BgAbANearVictory = 664, + BgAbHNearVictory = 665, + BgMarkByMail = 666, + + BgEyHasTakenAMTower = 667, + BgEyHasTakenHMTower = 668, + BgEyHasTakenADRuins = 669, + BgEyHasTakenHDRuins = 670, + BgEyHasTakenABTower = 671, + BgEyHasTakenHBTower = 672, + BgEyHasTakenAFRuins = 673, + BgEyHasTakenHFRuins = 674, + BgEyHasLostAMTower = 675, + BgEyHasLostHMTower = 676, + BgEyHasLostADRuins = 677, + BgEyHasLostHDRuins = 678, + BgEyHasLostABTower = 679, + BgEyHasLostHBTower = 680, + BgEyHasLostAFRuins = 681, + BgEyHasLostHFRuins = 682, + BgEyHasTakenFlag = 683, + BgEyCapturedFlagA = 684, + BgEyCapturedFlagH = 685, + BgEyDroppedFlag = 686, + BgEyResetedFlag = 687, + + ArenaOneToolow = 700, + ArenaOneMinute = 701, + ArenaThirtySeconds = 702, + ArenaFifteenSeconds = 703, + ArenaHasBegun = 704, + + WaitBeforeSpeaking = 705, + NotEquippedItem = 706, + PlayerDnd = 707, + PlayerAfk = 708, + PlayerDndDefault = 709, + PlayerAfkDefault = 710, + + BgQueueAnnounceSelf = 711, + BgQueueAnnounceWorld = 712, + YourArenaLevelReqError = 713, + // = 714, See PinfoAccAccount + YourBgLevelReqError = 715, + // = 716, See PinfoAccLastlogin + BgStartedAnnounceWorld = 717, + ArenaQueueAnnounceWorldJoin = 718, + ArenaQueueAnnounceWorldExit = 719, + + BgGroupTooLarge = 720, // "Your Group Is Too Large For This Battleground. Please Regroup To Join." + ArenaGroupTooLarge = 721, // "Your Group Is Too Large For This Arena. Please Regroup To Join." + ArenaYourTeamOnly = 722, // "Your Group Has Members Not In Your Arena Team. Please Regroup To Join." + ArenaNotEnoughPlayers = 723, // "Your Group Does Not Have Enough Players To Join This Match." + ArenaGoldWins = 724, // "The Gold Team Wins!" + ArenaGreenWins = 725, // "The Green Team Wins!" + // = 726, Not Used + BgGroupOfflineMember = 727, // "Your Group Has An Offline Member. Please Remove Him Before Joining." + BgGroupMixedFaction = 728, // "Your Group Has Players From The Opposing Faction. You Can'T Join The Battleground As A Group." + BgGroupMixedLevels = 729, // "Your Group Has Players From Different Battleground Brakets. You Can'T Join As Group." + BgGroupMemberAlreadyInQueue = 730, // "Someone In Your Party Is Already In This Battleground Queue. (S)He Must Leave It Before Joining As Group." + BgGroupMemberDeserter = 731, // "Someone In Your Party Is Deserter. You Can'T Join As Group." + BgGroupMemberNoFreeQueueSlots = 732, // "Someone In Your Party Is Already In Three Battleground Queues. You Cannot Join As Group." + + CannotTeleToBg = 733, // "You Cannot Teleport To A Battleground Or Arena Map." + CannotSummonToBg = 734, // "You Cannot Summon Players To A Battleground Or Arena Map." + CannotGoToBgGm = 735, // "You Must Be In Gm Mode To Teleport To A Player In A Battleground." + CannotGoToBgFromBg = 736, // "You Cannot Teleport To A Battleground From Another Battleground. Please Leave The Current Battleground First." + DebugArenaOn = 737, + DebugArenaOff = 738, + DebugBgOn = 739, + DebugBgOff = 740, + // DistArenaPointsStart = 741, + // DistArenaPointsOnlineStart = 742, + // DistArenaPointsOnlineEnd = 743, + // DistArenaPointsTeamStart = 744, + // DistArenaPointsTeamEnd = 745, + // DistArenaPointsEnd = 746, + BgDisabled = 747, + ArenaDisabled = 748, + // = 749, See PinfoAccOs + BattlegroundPrematureFinishWarning = 750, // "Not Enough Players. This Game Will Close In %U Mins." + BattlegroundPrematureFinishWarningSecs = 751, // "Not Enough Players. This Game Will Close In %U Seconds." + // = 752, See PinfoAccIp + // BgWsStartTwoMinutes = 753, - Defined Above + // BgAbStartTwoMinutes = 754, - Defined Above + // BgEyStartTwoMinutes = 755, - Defined Above + + // Room For Bg/Arena = 773-784, 788-799 Not Used + ArenaTesting = 785, + AutoAnn = 786, + AnnounceColor = 787, + + // In Game Strings + PetInvalidName = 800, + NotEnoughGold = 801, + NotFreeTradeSlots = 802, + NotPartnerFreeTradeSlots = 803, + YouNotHavePermission = 804, + UnknownLanguage = 805, + NotLearnedLanguage = 806, + NeedCharacterName = 807, + PlayerNotExistOrOffline = 808, + AccountForPlayerNotFound = 809, + // Unused = 810, + GuildMaster = 811, + GuildOfficer = 812, + GuildVeteran = 813, + GuildMember = 814, + GuildInitiate = 815, + ZoneNoflyzone = 816, + + CommandCreaturetemplateNotfound = 817, + CommandCreaturestorageNotfound = 818, + + ChannelCity = 819, + + NpcinfoGossip = 820, + NpcinfoQuestgiver = 821, + NpcinfoTrainerClass = 822, + NpcinfoTrainerProfession = 823, + NpcinfoVendorAmmo = 824, + NpcinfoVendorFood = 825, + NpcinfoVendorPoison = 826, + NpcinfoVendorReagent = 827, + NpcinfoRepair = 828, + NpcinfoFlightmaster = 829, + NpcinfoSpirithealer = 830, + NpcinfoSpiritguide = 831, + NpcinfoInnkeeper = 832, + NpcinfoBanker = 833, + NpcinfoPetitioner = 834, + NpcinfoTabarddesigner = 835, + NpcinfoBattlemaster = 836, + NpcinfoAuctioneer = 837, + NpcinfoStablemaster = 838, + NpcinfoGuildBanker = 839, + NpcinfoSpellclick = 840, + NpcinfoMailbox = 841, + NpcinfoPlayerVehicle = 842, + + // Pinfo Commands + PinfoPlayer = 453, + PinfoGmActive = 548, + PinfoBanned = 549, + PinfoMuted = 550, + PinfoAccAccount = 714, + PinfoAccLastlogin = 716, + PinfoAccOs = 749, + PinfoAccRegmails = 879, + PinfoAccIp = 752, + PinfoChrLevelLow = 843, + PinfoChrRace = 844, + PinfoChrAlive = 845, + PinfoChrPhases = 846, + PinfoChrMoney = 847, + PinfoChrMap = 848, + PinfoChrGuild = 849, + PinfoChrGuildRank = 850, + PinfoChrGuildNote = 851, + PinfoChrGuildOnote = 852, + PinfoChrPlayedtime = 853, + PinfoChrMails = 854, + PinfoChrLevelHigh = 871, + + CharacterGenderMale = 855, + CharacterGenderFemale = 856, + + ArenaErrorNotFound = 857, + ArenaErrorNameExists = 858, + ArenaErrorSize = 859, + ArenaErrorCombat = 860, + AreanErrorNameNotFound = 861, + ArenaErrorNotMember = 862, + ArenaErrorCaptain = 863, + ArenaCreate = 864, + ArenaDisband = 865, + ArenaRename = 866, + ArenaCaptain = 867, + ArenaInfoHeader = 868, + ArenaInfoMembers = 869, + ArenaLookup = 870, + // = 871, See PinfoChrLevelHigh + CommandWrongemail = 872, + NewEmailsNotMatch = 873, + CommandEmail = 874, + EmailTooLong = 875, + CommandNotchangeemail = 876, + OldEmailIsNewEmail = 877, + CommandEmailOutput = 878, + // = 879, See PinfoChrRegmails + AccountSecType = 880, + RbacEmailRequired = 881, + // Room For In-Game Strings 882-999 Not Used + + // Level 4 (Cli Only Commands) + CommandExit = 1000, + AccountDeleted = 1001, + AccountNotDeletedSqlError = 1002, + AccountNotDeleted = 1003, + AccountCreated = 1004, + AccountNameTooLong = 1005, + AccountAlreadyExist = 1006, + AccountNotCreatedSqlError = 1007, + AccountNotCreated = 1008, + CharacterDeleted = 1009, + AccountListHeader = 1010, + AccountListError = 1011, + AccountListBar = 1012, + AccountListLine = 1013, + AccountListEmpty = 1014, + AccountListBarHeader = 1015, + CharacterDeletedListHeader = 1016, + CharacterDeletedListLineConsole = 1017, + CharacterDeletedListBar = 1018, + CharacterDeletedListEmpty = 1019, + CharacterDeletedRestore = 1020, + CharacterDeletedDelete = 1021, + CharacterDeletedErrRename = 1022, + CharacterDeletedSkipAccount = 1023, + CharacterDeletedSkipFull = 1024, + CharacterDeletedSkipName = 1025, + CharacterDeletedListLineChat = 1026, + SqldriverQueryLoggingEnabled = 1027, + SqldriverQueryLoggingDisabled = 1028, + AccountInvalidBnetName = 1029, + AccountUseBnetCommands = 1030, + AccountPassTooLong = 1031, + AccountCreatedBnetWithGame = 1032, + AccountCreatedBnet = 1033, + AccountBnetListHeader = 1034, + AccountBnetListNoAccounts = 1035, + // Room For More Level 4 1036-1099 Not Used + + // Level 3 (Continue) + AccountSetaddon = 1100, + MotdNew = 1101, + Sendmessage = 1102, + EventEntryListConsole = 1103, + CreatureEntryListConsole = 1104, + ItemListConsole = 1105, + ItemsetListConsole = 1106, + GoEntryListConsole = 1107, + QuestListConsole = 1108, + SkillListConsole = 1109, + CreatureListConsole = 1110, + GoListConsole = 1111, + FileOpenFail = 1112, + AccountCharacterListFull = 1113, + DumpBroken = 1114, + InvalidCharacterName = 1115, + InvalidCharacterGuid = 1116, + CharacterGuidInUse = 1117, + ItemlistGuild = 1118, + MustMaleOrFemale = 1119, + YouChangeGender = 1120, + YourGenderChanged = 1121, + SkillValues = 1122, + NoPetFound = 1123, + WrongPetType = 1124, + CommandLearnPetTalents = 1125, + ResetPetTalents = 1126, + ResetPetTalentsOnline = 1127, + TaxinodeEntryListChat = 1128, + TaxinodeEntryListConsole = 1129, + CommandExportDeletedChar = 1130, + BanlistMatchingcharacter = 1131, + BanlistCharacters = 1132, + BanlistCharactersHeader = 1133, + AllowTickets = 1134, + DisallowTickets = 1135, + CharNotBanned = 1136, + DevOn = 1137, + DevOff = 1138, + MovegensFollowPlayer = 1139, + MovegensFollowCreature = 1140, + MovegensFollowNull = 1141, + MovegensEffect = 1142, + MoveflagsGet = 1143, + MoveflagsSet = 1144, + GroupAlreadyInGroup = 1145, + GroupPlayerJoined = 1146, + GroupNotInGroup = 1147, + GroupFull = 1148, + GroupType = 1149, + GroupPlayerNameGuid = 1150, + ListMailHeader = 1151, + ListMailInfo1 = 1152, + ListMailInfo2 = 1153, + ListMailInfo3 = 1154, + ListMailInfoItem = 1155, + ListMailNotFound = 1156, + AhbotReloadOk = 1157, + AhbotStatusBarConsole = 1158, + AhbotStatusMidbarConsole = 1159, + AhbotStatusTitle1Console = 1160, + AhbotStatusTitle1Chat = 1161, + AhbotStatusFormatConsole = 1162, + AhbotStatusFormatChat = 1163, + AhbotStatusItemCount = 1164, + AhbotStatusItemRatio = 1165, + AhbotStatusTitle2Console = 1166, + AhbotStatusTitle2Chat = 1167, + AhbotQualityGray = 1168, + AhbotQualityWhite = 1169, + AhbotQualityGreen = 1170, + AhbotQualityBlue = 1171, + AhbotQualityPurple = 1172, + AhbotQualityOrange = 1173, + AhbotQualityYellow = 1174, + AhbotItemsAmount = 1175, + AhbotItemsRatio = 1176, + GuildInfoName = 1177, + GuildInfoGuildMaster = 1178, + GuildInfoCreationDate = 1179, + GuildInfoMemberCount = 1180, + GuildInfoBankGold = 1181, + GuildInfoMotd = 1182, + GuildInfoExtraInfo = 1183, + GuildInfoLevel = 1184, + AccountBnetLinked = 1185, + AccountOrBnetDoesNotExist = 1186, + AccountAlreadyLinked = 1187, + AccountBnetUnlinked = 1188, + AccountBnetNotLinked = 1189, + DisallowTicketsConfig = 1190, + // Room For More Level 3 1191-1198 Not Used + + // Debug Commands + DebugAreatriggerLeft = 1999, + CinematicNotExist = 1200, + MovieNotExist = 1201, + DebugAreatriggerOn = 1202, + DebugAreatriggerOff = 1203, + DebugAreatriggerEntered = 1204, + + // Isle Of Conquest + BgIcStartTwoMinutes = 1205, + BgIcStartOneMinute = 1206, + BgIcStartHalfMinute = 1207, + BgIcHasBegun = 1208, + BgIcAllianceKeep = 1209, + BgIcHordeKeep = 1210, + BgIcTeamWins = 1211, + BgIcWestGateDestroyed = 1212, + BgIcEastGateDestroyed = 1213, + BgIcSouthGateDestroyed = 1214, + BgIcNorthGateDestroyed = 1215, + BgIcTeamAssaultedNode1 = 1216, + BgIcTeamDefendedNode = 1217, + BgIcTeamAssaultedNode2 = 1218, + BgIcTeamHasTakenNode = 1219, + BgIcWorkshop = 1220, + BgIcDocks = 1221, + BgIcRefinery = 1222, + BgIcQuarry = 1223, + BgIcHangar = 1224, + // 1225-1299 + BgIcAlliance = 1300, + BgIcHorde = 1301, + + // 1302-1325 + // Av + BgAvStartOneMinute = 1326, + BgAvStartHalfMinute = 1327, + BgAvHasBegun = 1328, + BgAvANearLose = 1329, + BgAvHNearLose = 1330, + // 1331-1332 + BgAvStartTwoMinutes = 1333, + // Free Ids 1334-2002 + + // Ticket Strings 2003-2028 + CommandTicketclosed = 2003, + CommandTicketdeleted = 2004, + CommandTicketnotexist = 2005, + CommandTicketclosefirst = 2006, + CommandTicketalreadyassigned = 2007, + CommandTicketshowlist = 2009, + CommandTicketshowclosedlist = 2011, + CommandTicketassignerrorA = 2012, + CommandTicketassignerrorB = 2013, + CommandTicketnotassigned = 2014, + CommandTicketunassignsecurity = 2015, + CommandTicketcannotclose = 2016, + CommandTicketlistguid = 2017, + CommandTicketlistname = 2018, + CommandTicketlistassignedto = 2020, + CommandTicketlistunassigned = 2021, + CommandTicketlistmessage = 2022, + CommandTicketlistcomment = 2023, + CommandTicketlistaddcomment = 2024, + CommandTicketlistagecreate = 2025, + CommandTicketpending = 2027, + CommandTicketreset = 2028, + + // Trinity Strings 5000-9999 + CommandFreeze = 5000, + CommandFreezeError = 5001, + CommandFreezeWrong = 5002, + CommandUnfreeze = 5003, + CommandNoFrozenPlayers = 5004, + CommandListFreeze = 5005, + CommandPermaFrozenPlayer = 5006, + PhaseNotfound = 5007, + InstanceClosed = 5008, + CommandPlayedToAll = 5009, + NpcinfoLinkguid = 5010, + TeleportedToByConsole = 5011, + // For Command Lookup Map + CommandNomapfound = 5012, + Continent = 5013, + Instance = 5014, + Battleground = 5015, + Arena = 5016, + Raid = 5017, + NpcinfoPhaseIds = 5018, + CommandTempFrozenPlayer = 5019, + NpcinfoPhases = 5020, + NpcinfoArmor = 5021, + ChannelEnableOwnership = 5022, + ChannelDisableOwnership = 5023, + GoinfoEntry = 5024, + GoinfoType = 5025, + GoinfoDisplayid = 5026, + GoinfoName = 5027, + GoinfoLootid = 5028, + CommandLookupMaxResults = 5029, + Unauthorized = 5030, + NpcinfoAiinfo = 5031, + CommandNoBattlegroundFound = 5032, + CommandNoAchievementCriteriaFound = 5033, + CommandNoOutdoorPvpForund = 5034, + NoReason = 5035, + NpcinfoEquipment = 5036, + NpcinfoMechanicImmune = 5037, + NpcinfoUnitFieldFlags = 5038, + Console = 5039, + Character = 5040, + Permanently = 5041, + GpsPositionOutdoors = 5042, + GpsPositionIndoors = 5043, + GpsNoVmap = 5044, + + // Instance Commands + CommandListBindInfo = 5045, + CommandListBindPlayerBinds = 5046, + CommandListBindGroupBinds = 5047, + CommandInstUnbindUnbinding = 5048, + CommandInstUnbindUnbound = 5049, + CommandInstStatLoadedInst = 5050, + CommandInstStatPlayersIn = 5051, + CommandInstStatSaves = 5052, + CommandInstStatPlayersbound = 5053, + CommandInstStatGroupsbound = 5054, + NotDungeon = 5055, // Map Is Not A Dungeon. + NoInstanceData = 5056, // Map Has No Instance Data. + CommandInstSetBossState = 5057, + CommandInstGetBossState = 5058, + + // Mutehistory Commands + CommandMutehistory = 5059, + CommandMutehistoryEmpty = 5060, + CommandMutehistoryOutput = 5061, + + // Scene Debugs Commands + CommandSceneDebugOn = 5062, + CommandSceneDebugOff = 5063, + CommandSceneDebugPlay = 5064, + CommandSceneDebugTrigger = 5065, + CommandSceneDebugCancel = 5066, + CommandSceneDebugComplete = 5067, + DebugSceneObjectList = 5068, + DebugSceneObjectDetail = 5069, + + NpcinfoUnitFieldFlags2 = 5070, + NpcinfoUnitFieldFlags3 = 5071, + NpcinfoNpcFlags = 5072, + + // Room For More Trinity Strings 5073-9999 + + // Level Requirement Notifications + SayReq = 6604, + WhisperReq = 6605, + ChannelReq = 6606, + AuctionReq = 6607, + TicketReq = 6608, + TradeReq = 6609, + TradeOtherReq = 6610, + MailSenderReq = 6611, + MailReceiverReq = 6612, + + // Used For Gm Announcements + GmBroadcast = 6613, + GmNotify = 6614, + GmAnnounceColor = 6615, + + GmSilence = 6616, // "Silence Is On For %S" - Spell 1852 + + WorldClosed = 7523, + WorldOpened = 7524, + + LfgPlayerInfo = 9980, + LfgGroupInfo = 9981, + LfgNotInGroup = 9982, + LfgClean = 9983, + LfgOptions = 9984, + LfgOptionsChanged = 9985, + LfgStateNone = 9986, + LfgStateRolecheck = 9987, + LfgStateQueued = 9988, + LfgStateProposal = 9989, + LfgStateBoot = 9990, + LfgStateDungeon = 9991, + LfgStateFinishedDungeon = 9992, + LfgStateRaidbrowser = 9993, + LfgRoleTank = 9994, + LfgRoleHealer = 9995, + LfgRoleDamage = 9996, + LfgRoleLeader = 9997, + LfgRoleNone = 9998, + LfgError = 9999, + + // Use For Not-In-Offcial-Sources Patches + // 10000-10999 + // Opvp Si + OpvpSiCaptureH = 10049, + OpvpSiCaptureA = 10050, + // Opvp Gossips + OpvpEpFlightNpt = 10051, + OpvpEpFlightEwt = 10052, + OpvpEpFlightCgt = 10053, + OpvpZmGossipAlliance = 10054, + OpvpZmGossipHorde = 10055, + + BgSaStartTwoMinutes = 10056, + BgSaStartOneMinute = 10057, + BgSaStartHalfMinute = 10058, + // Unused 10059-10062 + BgSaAllianceCapturedRelic = 10063, //The Alliance Captured The Titan Portal! + BgSaHordeCapturedRelic = 10064, //The Horde Captured The Titan Portal! + BgSaRoundTwoOneMinute = 10065, //Round 2 Of The Battle For The Strand Of The Ancients Begins In 1 Minute. + BgSaRoundTwoStartHalfMinute = 10066, //Round 2 Begins In 30 Seconds. Prepare Yourselves! + + // Use For Custom Patches 11000-11999 + AutoBroadcast = 11000, + InvalidRealmid = 11001, + + // Show Kick In World + CommandKickmessageWorld = 11002, + + // Show Mute In World + CommandMutemessageWorld = 11003, + + // Show Ban In World + BanCharacterYoubannedmessageWorld = 11004, + BanCharacterYoupermbannedmessageWorld = 11005, + BanAccountYoubannedmessageWorld = 11006, + BanAccountYoupermbannedmessageWorld = 11007, + + NpcinfoInhabitType = 11008, + NpcinfoFlagsExtra = 11009, + InstanceLoginGamemasterException = 11010, + + CreatureNoInteriorPointFound = 11011, + CreatureMovementNotBounded = 11012, + CreatureMovementMaybeUnbounded = 11013, + InstanceBindMismatch = 11014, + CreatureNotAiEnabled = 11015, + SelectPlayerOrPet = 11016, + ShutdownDelayed = 11017, + ShutdownCancelled = 11018 + } + + public enum BroadcastTextIds + { + AchivementEarned = 29245, + CallForHelp = 2541, + FleeForAssist = 1150 + } + + public enum LanguageType + { + BasicLatin = 0x00, + ExtendenLatin = 0x01, + Cyrillic = 0x02, + EastAsia = 0x04, + Any = 0xFFFF + } +} diff --git a/Framework/Constants/LootConst.cs b/Framework/Constants/LootConst.cs new file mode 100644 index 000000000..4d24fc985 --- /dev/null +++ b/Framework/Constants/LootConst.cs @@ -0,0 +1,115 @@ +/* + * Copyright (C) 2012-2017 CypherCore + * + * 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, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +namespace Framework.Constants +{ + public enum RollType + { + Pass = 0, + Need = 1, + Greed = 2, + Disenchant = 3, + NotEmitedYet = 4, + NotValid = 5, + + MaxTypes = 4, + } + + public enum RollMask + { + Pass = 0x01, + Need = 0x02, + Greed = 0x04, + Disenchant = 0x08, + + AllNoDisenchant = 0x07, + AllMask = 0x0f + } + + public enum LootMethod + { + FreeForAll = 0, + MasterLoot = 2, + GroupLoot = 3, + PersonalLoot = 5 + } + + public enum LootModes + { + Default = 0x1, + HardMode1 = 0x2, + HardMode2 = 0x4, + HardMode3 = 0x8, + HardMode4 = 0x10, + JunkFish = 0x8000 + } + + public enum PermissionTypes + { + All = 0, + Group = 1, + Master = 2, + Restricted = 3, + Owner = 5, + None = 6 + } + + public enum LootType + { + None = 0, + Corpse = 1, + Pickpocketing = 2, + Fishing = 3, + Disenchanting = 4, + // Ignored Always By Client + Skinning = 6, + Prospecting = 7, + Milling = 8, + + Fishinghole = 20, // Unsupported By Client, Sending Fishing Instead + Insignia = 21, // Unsupported By Client, Sending Corpse Instead + FishingJunk = 22 // unsupported by client, sending LOOT_FISHING instead + } + + public enum LootError + { + DidntKill = 0, // You don't have permission to loot that corpse. + TooFar = 4, // You are too far away to loot that corpse. + BadFacing = 5, // You must be facing the corpse to loot it. + Locked = 6, // Someone is already looting that corpse. + NotStanding = 8, // You need to be standing up to loot something! + Stunned = 9, // You can't loot anything while stunned! + PlayerNotFound = 10, // Player not found + PlayTimeExceeded = 11, // Maximum play time exceeded + MasterInvFull = 12, // That player's inventory is full + MasterUniqueItem = 13, // Player has too many of that item already + MasterOther = 14, // Can't assign item to that player + AlreadPickPocketed = 15, // Your target has already had its pockets picked + NotWhileShapeShifted = 16, // You can't do that while shapeshifted. + NoLoot = 17 // There is no loot. + } + + // type of Loot Item in Loot View + public enum LootSlotType + { + AllowLoot = 0, // Player Can Loot The Item. + RollOngoing = 1, // Roll Is Ongoing. Player Cannot Loot. + Locked = 2, // Item Is Shown In Red. Player Cannot Loot. + Master = 3, // Item Can Only Be Distributed By Group Loot Master. + Owner = 4 // Ignore Binding Confirmation And Etc, For Single Player Looting + } +} diff --git a/Framework/Constants/MailConst.cs b/Framework/Constants/MailConst.cs new file mode 100644 index 000000000..0fcdd933b --- /dev/null +++ b/Framework/Constants/MailConst.cs @@ -0,0 +1,94 @@ +/* + * Copyright (C) 2012-2017 CypherCore + * + * 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, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +namespace Framework.Constants +{ + public enum MailMessageType + { + Normal = 0, + Auction = 2, + Creature = 3, + Gameobject = 4, + Calendar = 5, + Blackmarket = 6 + } + + public enum MailCheckMask + { + None = 0x00, + Read = 0x01, + Returned = 0x02, /// This Mail Was Returned. Do Not Allow Returning Mail Back Again. + Copied = 0x04, /// This Mail Was Copied. Do Not Allow Making A Copy Of Items In Mail. + CodPayment = 0x08, + HasBody = 0x10 /// This Mail Has Body Text. + } + + public enum MailStationery + { + Test = 1, + Default = 41, + Gm = 61, + Auction = 62, + Val = 64, // Valentine + Chr = 65, // Christmas + Orp = 67 // Orphan + } + + public enum MailState + { + Unchanged = 1, + Changed = 2, + Deleted = 3 + } + + public enum MailShowFlags + { + Unk0 = 0x0001, + Delete = 0x0002, // Forced Show Delete Button Instead Return Button + Auction = 0x0004, // From Old Comment + Unk2 = 0x0008, // Unknown, Cod Will Be Shown Even Without That Flag + Return = 0x0010 + } + + public enum MailResponseType + { + Send = 0, + MoneyTaken = 1, + ItemTaken = 2, + ReturnedToSender = 3, + Deleted = 4, + MadePermanent = 5 + } + + public enum MailResponseResult + { + Ok = 0, + EquipError = 1, + CannotSendToSelf = 2, + NotEnoughMoney = 3, + RecipientNotFound = 4, + NotYourTeam = 5, + InternalError = 6, + DisabledForTrialAcc = 14, + RecipientCapReached = 15, + CantSendWrappedCod = 16, + MailAndChatSuspended = 17, + TooManyAttachments = 18, + MailAttachmentInvalid = 19, + ItemHasExpired = 21 + } +} diff --git a/Framework/Constants/MapConst.cs b/Framework/Constants/MapConst.cs new file mode 100644 index 000000000..7858a7790 --- /dev/null +++ b/Framework/Constants/MapConst.cs @@ -0,0 +1,184 @@ +/* + * Copyright (C) 2012-2017 CypherCore + * + * 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, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +namespace Framework.Constants +{ + public class MapConst + { + //Grids + public const int MaxGrids = 64; + public const float SizeofGrids = 533.33333f; + public const float CenterGridCellId = (MaxCells * MaxGrids / 2); + public const float CenterGridId = (MaxGrids / 2); + public const float CenterGridOffset = (SizeofGrids / 2); + public const float CenterGridCellOffset = (SizeofCells / 2); + + //Cells + public const int MaxCells = 8; + public const float SizeofCells = (SizeofGrids / MaxCells); + public const int TotalCellsPerMap = (MaxGrids * MaxCells); + public const float MapSize = (SizeofGrids * MaxGrids); + public const float MapHalfSize = (MapSize / 2); + + public const uint MaxGroupSize = 5; + public const uint MaxRaidSize = 40; + public const uint MaxRaidSubGroups = MaxRaidSize / MaxGroupSize; + public const uint TargetIconsCount = 8; + public const uint RaidMarkersCount = 8; + public const uint ReadycheckDuration = 35000; + + //Liquid + public const int MapLiquidTypeNoWater = 0x00; + public const int MapLiquidTypeWater = 0x01; + public const int MapLiquidTypeOcean = 0x02; + public const int MapLiquidTypeMagma = 0x04; + public const int MapLiquidTypeSlime = 0x08; + public const int MapLiquidTypeDarkWater = 0x10; + public const int MapLiquidTypeWMOWater = 0x20; + public const int MapAllLiquidTypes = (MapLiquidTypeWater | MapLiquidTypeOcean | MapLiquidTypeMagma | MapLiquidTypeSlime); + public const float LiquidTileSize = (533.333f / 128.0f); + + public const int MinMapUpdateDelay = 50; + public const int MinGridDelay = (Time.Minute * Time.InMilliseconds); + + public const int MapResolution = 128; + public const float DefaultHeightSearch = 50.0f; + public const float InvalidHeight = -100000.0f; + public const float MaxHeight = 100000.0f; + public const float MaxFallDistance = 250000.0f; + + public const string MapMagic = "MAPS"; + public const string MapVersionMagic = "v1.8"; + public const string MapAreaMagic = "AREA"; + public const string MapHeightMagic = "MHGT"; + public const string MapLiquidMagic = "MLIQ"; + + public const string mmapMagic = "MMAP"; + public const int mmapVersion = 8; + + public const string VMapMagic = "VMAP_4.5"; + public const float VMAPInvalidHeightValue = -200000.0f; + } + + public enum NewWorldReason + { + Normal = 16, // Normal map change + Seamless = 21, // Teleport to another map without a loading screen, used for outdoor scenarios + } + + public enum InstanceResetWarningType + { + WarningHours = 1, // WARNING! %s is scheduled to reset in %d hour(s). + WarningMin = 2, // WARNING! %s is scheduled to reset in %d minute(s)! + WarningMinSoon = 3, // WARNING! %s is scheduled to reset in %d minute(s). Please exit the zone or you will be returned to your bind location! + Welcome = 4, // Welcome to %s. This raid instance is scheduled to reset in %s. + Expired = 5 + } + + public enum InstanceResetMethod + { + All, + ChangeDifficulty, + Global, + GroupDisband, + GroupJoin, + RespawnDelay + } + + public enum GridMapTypeMask + { + None = 0x00, + Corpse = 0x01, + Creature = 0x02, + DynamicObject = 0x04, + GameObject = 0x08, + Player = 0x10, + AreaTrigger = 0x20, + Conversation = 0x40, + All = 0x7F, + + //GameObjects, Creatures(except pets), DynamicObject, Corpse(Bones), AreaTrigger + AllGrid = GameObject | Creature | DynamicObject | Corpse | AreaTrigger | Conversation, + + //Player, Pets, Corpse(resurrectable), DynamicObject(farsight) + AllWorld = Player | Creature | Corpse | DynamicObject + } + + public enum ZLiquidStatus + { + NoWater = 0x00, + AboveWater = 0x01, + WaterWalk = 0x02, + InWater = 0x04, + UnderWater = 0x08 + } + + public enum EncounterFrameType + { + SetCombatResLimit = 0, + ResetCombatResLimit = 1, + Engage = 2, + Disengage = 3, + UpdatePriority = 4, + AddTimer = 5, + EnableObjective = 6, + UpdateObjective = 7, + DisableObjective = 8, + Unk7 = 9, // Seems To Have Something To Do With Sorting The Encounter Units + AddCombatResLimit = 10 + } + + public enum EncounterState + { + NotStarted = 0, + InProgress = 1, + Fail = 2, + Done = 3, + Special = 4, + ToBeDecided = 5 + } + + public enum EncounterCreditType + { + KillCreature = 0, + CastSpell = 1 + } + + public enum DoorType + { + Room = 0, // Door can open if encounter is not in progress + Passage = 1, // Door can open if encounter is done + SpawnHole = 2, // Door can open if encounter is in progress, typically used for spawning places + Max + } + + public enum EnterState + { + CanEnter = 0, + CannotEnterAlreadyInMap = 1, // Player Is Already In The Map + CannotEnterNoEntry, // No Map Entry Was Found For The Target Map Id + CannotEnterUninstancedDungeon, // No Instance Template Was Found For Dungeon Map + CannotEnterDifficultyUnavailable, // Requested Instance Difficulty Is Not Available For Target Map + CannotEnterNotInRaid, // Target Instance Is A Raid Instance And The Player Is Not In A Raid Group + CannotEnterCorpseInDifferentInstance, // Player Is Dead And Their Corpse Is Not In Target Instance + CannotEnterInstanceBindMismatch, // Player'S Permanent Instance Save Is Not Compatible With Their Group'S Current Instance Bind + CannotEnterTooManyInstances, // Player Has Entered Too Many Instances Recently + CannotEnterMaxPlayers, // Target Map Already Has The Maximum Number Of Players Allowed + CannotEnterZoneInCombat, // A Boss Encounter Is Currently In Progress On The Target Map + CannotEnterUnspecifiedReason + } +} diff --git a/Framework/Constants/Movement/MovementConst.cs b/Framework/Constants/Movement/MovementConst.cs new file mode 100644 index 000000000..32f5aa16f --- /dev/null +++ b/Framework/Constants/Movement/MovementConst.cs @@ -0,0 +1,87 @@ +/* + * Copyright (C) 2012-2017 CypherCore + * + * 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, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +namespace Framework.Constants +{ + public enum MovementSlot + { + Idle, + Active, + Controlled, + Max + } + + public enum MovementGeneratorType + { + Idle = 0, // IdleMovement + Random = 1, // RandomMovement + Waypoint = 2, // WaypointMovement + + MaxDB = 3, // *** this and below motion types can't be set in DB. + AnimalRandom = MaxDB, // AnimalRandomMovementGenerator.h + Confused = 4, // ConfusedMovementGenerator.h + Chase = 5, // TargetedMovementGenerator.h + Home = 6, // HomeMovementGenerator.h + Flight = 7, // WaypointMovementGenerator.h + Point = 8, // PointMovementGenerator.h + Fleeing = 9, // FleeingMovementGenerator.h + Distract = 10, // IdleMovementGenerator.h + Assistance = 11, // PointMovementGenerator.h (first part of flee for assistance) + AssistanceDistract = 12, // IdleMovementGenerator.h (second part of flee for assistance) + TimedFleeing = 13, // FleeingMovementGenerator.h (alt.second part of flee for assistance) + Follow = 14, + Rotate = 15, + Effect = 16, + Null = 17, + SplineChain = 18, + Max + } + + public struct EventId + { + public const uint Charge = 1003; + public const uint Jump = 1004; + + /// Special charge event which is used for charge spells that have explicit targets + /// and had a path already generated - using it in PointMovementGenerator will not + /// create a new spline and launch it + public const uint ChargePrepath = 1005; + public const uint SmartRandomPoint = 0xFFFFFE; + public const uint SmartEscortLastOCCPoint = 0xFFFFFF; + } + + public enum AnimType + { + ToGround = 0, // 460 = ToGround, index of AnimationData.dbc + FlyToFly = 1, // 461 = FlyToFly? + ToFly = 2, // 458 = ToFly + FlyToGround = 3 // 463 = FlyToGround + } + + public enum RotateDirection + { + Left, + Right + } + + public enum UpdateCollisionHeightReason + { + Scale = 0, + Mount = 1, + Force = 2 + } +} diff --git a/Framework/Constants/Movement/MovementFlags.cs b/Framework/Constants/Movement/MovementFlags.cs new file mode 100644 index 000000000..e3dbc16f3 --- /dev/null +++ b/Framework/Constants/Movement/MovementFlags.cs @@ -0,0 +1,101 @@ +/* + * Copyright (C) 2012-2017 CypherCore + * + * 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, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +using System; + +namespace Framework.Constants +{ + [Flags] + public enum MovementFlag + { + None = 0x0, + Forward = 0x1, + Backward = 0x2, + StrafeLeft = 0x4, + StrafeRight = 0x8, + Left = 0x10, + Right = 0x20, + PitchUp = 0x40, + PitchDown = 0x80, + Walking = 0x100, + DisableGravity = 0x200, + Root = 0x400, + Falling = 0x800, + FallingFar = 0x1000, + PendingStop = 0x2000, + PendingStrafeStop = 0x4000, + PendingForward = 0x8000, + PendingBackward = 0x10000, + PendingStrafeLeft = 0x20000, + PendingStrafeRight = 0x40000, + PendingRoot = 0x80000, + Swimming = 0x100000, + Ascending = 0x200000, + Descending = 0x400000, + CanFly = 0x800000, + Flying = 0x1000000, + SplineElevation = 0x2000000, + WaterWalk = 0x4000000, + FallingSlow = 0x8000000, + Hover = 0x10000000, + DisableCollision = 0x20000000, + + MaskMoving = Forward | Backward | StrafeLeft | StrafeRight | Falling | Ascending | Descending, + + MaskTurning = Left | Right | PitchUp | PitchDown, + + MaskMovingFly = Flying | Ascending | Descending, + + MaskCreatureAllowed = Forward | DisableGravity | Root | Swimming | + CanFly | WaterWalk | FallingSlow | Hover | DisableCollision, + + MaskPlayerOnly = Flying, + + MaskHasPlayerStatusOpcode = DisableGravity | Root | CanFly | WaterWalk | + FallingSlow | Hover | DisableCollision + } + + [Flags] + public enum MovementFlag2 + { + None = 0x0, + NoStrafe = 0x1, + NoJumping = 0x2, + FullSpeedTurning = 0x4, + FullSpeedPitching = 0x8, + AlwaysAllowPitching = 0x10, + IsVehicleExitVoluntary = 0x20, + JumpSplineInAir = 0x40, + AnimTierInTrans = 0x80, + WaterwalkingFullPitch = 0x100, // will always waterwalk, even if facing the camera directly down + VehiclePassengerIsTransitionAllowed = 0x200, + CanSwimToFlyTrans = 0x400, + Unk11 = 0x800, // terrain normal calculation is disabled if this flag is not present, client automatically handles setting this flag + CanTurnWhileFalling = 0x1000, + Unk13 = 0x2000, // will always waterwalk, even if facing the camera directly down + IgnoreMovementForces = 0x4000, + Unk15 = 0x8000, + CanDoubleJump = 0x10000, + DoubleJump = 0x20000, + // these flags cannot be sent (18 bits in packet) + Unk18 = 0x40000, + Unk19 = 0x80000, + InterpolatedMovement = 0x100000, + InterpolatedTurning = 0x200000, + InterpolatedPitching = 0x400000 + } +} diff --git a/Framework/Constants/Movement/SplineConst.cs b/Framework/Constants/Movement/SplineConst.cs new file mode 100644 index 000000000..efa07360b --- /dev/null +++ b/Framework/Constants/Movement/SplineConst.cs @@ -0,0 +1,72 @@ +/* + * Copyright (C) 2012-2017 CypherCore + * + * 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, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +using System; + +namespace Framework.Constants +{ + public enum MonsterMoveType + { + Normal = 0, + FacingSpot = 1, + FacingTarget = 2, + FacingAngle = 3 + } + + [Flags] + public enum SplineFlag : uint + { + None = 0x00, + // x00-x07 used as animation Ids storage in pair with Animation flag + Unknown0 = 0x00000008, // NOT VERIFIED - does someting related to falling/fixed orientation + FallingSlow = 0x00000010, + Done = 0x00000020, + Falling = 0x00000040, // Affects elevation computation, can't be combined with Parabolic flag + NoSpline = 0x00000080, + Unknown1 = 0x00000100, // NOT VERIFIED + Flying = 0x00000200, // Smooth movement(Catmullrom interpolation mode), flying animation + OrientationFixed = 0x00000400, // Model orientation fixed + Catmullrom = 0x00000800, // Used Catmullrom interpolation mode + Cyclic = 0x00001000, // Movement by cycled spline + EnterCycle = 0x00002000, // Everytimes appears with cyclic flag in monster move packet, erases first spline vertex after first cycle done + Frozen = 0x00004000, // Will never arrive + TransportEnter = 0x00008000, + TransportExit = 0x00010000, + Unknown2 = 0x00020000, // NOT VERIFIED + Unknown3 = 0x00040000, // NOT VERIFIED + Backward = 0x00080000, + SmoothGroundPath = 0x00100000, + CanSwim = 0x00200000, + UncompressedPath = 0x00400000, + Unknown4 = 0x00800000, // NOT VERIFIED + Unknown5 = 0x01000000, // NOT VERIFIED + Animation = 0x02000000, // Plays animation after some time passed + Parabolic = 0x04000000, // Affects elevation computation, can't be combined with Falling flag + FadeObject = 0x08000000, + Steering = 0x10000000, + Unknown8 = 0x20000000, // NOT VERIFIED + Unknown9 = 0x40000000, // NOT VERIFIED + Unknown10 = 0x80000000, // NOT VERIFIED + + // animation ids stored here, see AnimType enum, used with Animation flag + MaskAnimations = 0x7, + // flags that shouldn't be appended into SMSG_MONSTER_MOVE\SMSG_MONSTER_MOVE_TRANSPORT packet, should be more probably + MaskNoMonsterMove = MaskAnimations | Done, + // Unused, not suported flags + MaskUnused = NoSpline | EnterCycle | Frozen | Unknown0 | Unknown1 | Unknown2 | Unknown3 | Unknown4 | FadeObject | Steering | Unknown8 | Unknown9 | Unknown10 + } +} diff --git a/Framework/Constants/Network/NetworkConst.cs b/Framework/Constants/Network/NetworkConst.cs new file mode 100644 index 000000000..dbaaeb883 --- /dev/null +++ b/Framework/Constants/Network/NetworkConst.cs @@ -0,0 +1,35 @@ +/* + * Copyright (C) 2012-2017 CypherCore + * + * 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, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +namespace Framework.Constants +{ + // Player state + public enum SessionStatus + { + Authed = 0, // Player authenticated (_player == NULL, m_playerRecentlyLogout = false or will be reset before handler call, m_GUID have garbage) + Loggedin, // Player in game (_player != NULL, m_GUID == _player->GetGUID(), inWorld()) + Transfer, // Player transferring to another map (_player != NULL, m_GUID == _player->GetGUID(), !inWorld()) + LoggedinOrRecentlyLogout, // _player != NULL or _player == NULL && m_playerRecentlyLogout && m_playerLogout, m_GUID store last _player guid) + } + + public enum PacketProcessing + { + Inplace = 0, //process packet whenever we receive it - mostly for non-handled or non-implemented packets + ThreadUnsafe, //packet is not thread-safe - process it in World.UpdateSessions() + ThreadSafe //packet is thread-safe - process it in Map.Update() + } +} diff --git a/Framework/Constants/Network/Opcodes.cs b/Framework/Constants/Network/Opcodes.cs new file mode 100644 index 000000000..142a07c16 --- /dev/null +++ b/Framework/Constants/Network/Opcodes.cs @@ -0,0 +1,1712 @@ +/* + * Copyright (C) 2012-2017 CypherCore + * + * 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, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +namespace Framework.Constants +{ + public enum ClientOpcodes : uint + { + AcceptGuildInvite = 0x35fd, + AcceptLevelGrant = 0x34ef, + AcceptTrade = 0x315a, + AcceptWargameInvite = 0x35e0, + ActivateTaxi = 0x34ac, + AddonList = 0x35d8, + AddBattlenetFriend = 0x365d, + AddFriend = 0x36d2, + AddIgnore = 0x36d6, + AddToy = 0x327d, + AdventureJournalOpenQuest = 0x31ef, + AdventureJournalStartQuest = 0x3325, + AlterAppearance = 0x34eb, + AreaSpiritHealerQuery = 0x34b1, + AreaSpiritHealerQueue = 0x34b2, + AreaTrigger = 0x31c5, + ArtifactAddPower = 0x31a5, + ArtifactSetAppearance = 0x31a7, + AssignEquipmentSetSpec = 0x31f6, + AttackStop = 0x3240, + AttackSwing = 0x323f, + AuctionHelloRequest = 0x34c1, + AuctionListBidderItems = 0x34c7, + AuctionListItems = 0x34c4, + AuctionListOwnerItems = 0x34c6, + AuctionListPendingSales = 0x34c9, + AuctionPlaceBid = 0x34c8, + AuctionRemoveItem = 0x34c3, + AuctionReplicateItems = 0x34c5, + AuctionSellItem = 0x34c2, + AuthContinuedSession = 0x3766, + AuthSession = 0x3765, + AutobankItem = 0x3996, + AutobankReagent = 0x3998, + AutostoreBankItem = 0x3997, + AutostoreBankReagent = 0x3999, + AutoEquipItem = 0x399a, + AutoEquipItemSlot = 0x399f, + AutoStoreBagItem = 0x399b, + BankerActivate = 0x34b4, + BattlefieldLeave = 0x3171, + BattlefieldList = 0x317d, + BattlefieldPort = 0x351b, + BattlemasterHello = 0x3293, + BattlemasterJoin = 0x3516, + BattlemasterJoinArena = 0x3517, + BattlemasterJoinBrawl = 0x3519, + BattlemasterJoinSkirmish = 0x3518, + BattlenetChallengeResponse = 0x36d5, + BattlenetRequest = 0x36f9, + BattlenetRequestRealmListTicket = 0x36fa, + BattlePayAckFailedResponse = 0x36cd, + BattlePayConfirmPurchaseResponse = 0x36cc, + BattlePayDistributionAssignToTarget = 0x36c3, + BattlePayGetProductList = 0x36be, + BattlePayGetPurchaseList = 0x36bf, + BattlePayQueryClassTrialBoostResult = 0x36c6, + BattlePayRequestCharacterBoostUnrevoke = 0x36c4, + BattlePayRequestCurrentVasTransferQueues = 0x370a, + BattlePayRequestPriceInfo = 0x3709, + BattlePayRequestVasCharacterQueueTime = 0x370b, + BattlePayStartPurchase = 0x36f5, + BattlePayStartVasPurchase = 0x36f6, + BattlePayTrialBoostCharacter = 0x36c5, + BattlePayValidateBnetVasTransfer = 0x370c, + BattlePetClearFanfare = 0x312c, + BattlePetDeletePet = 0x3627, + BattlePetDeletePetCheat = 0x3628, + BattlePetModifyName = 0x362a, + BattlePetRequestJournal = 0x3626, + BattlePetRequestJournalLock = 0x3625, + BattlePetSetBattleSlot = 0x362e, + BattlePetSetFlags = 0x3632, + BattlePetSummon = 0x362b, + BattlePetUpdateNotify = 0x31ce, + BeginTrade = 0x3157, + BinderActivate = 0x34b3, + BlackMarketBidOnItem = 0x3523, + BlackMarketOpen = 0x3521, + BlackMarketRequestItems = 0x3522, + BugReport = 0x3689, + BusyTrade = 0x3158, + BuyBackItem = 0x34a5, + BuyBankSlot = 0x34b5, + BuyItem = 0x34a4, + BuyReagentBank = 0x34b6, + BuyWowTokenConfirm = 0x36ee, + BuyWowTokenStart = 0x36ed, + CageBattlePet = 0x31df, + CalendarAddEvent = 0x3680, + CalendarComplain = 0x367c, + CalendarCopyEvent = 0x367b, + CalendarEventInvite = 0x3675, + CalendarEventModeratorStatus = 0x3679, + CalendarEventRsvp = 0x3677, + CalendarEventSignUp = 0x367e, + CalendarEventStatus = 0x3678, + CalendarGet = 0x3672, + CalendarGetEvent = 0x3673, + CalendarGetNumPending = 0x367d, + CalendarGuildFilter = 0x3674, + CalendarRemoveEvent = 0x367a, + CalendarRemoveInvite = 0x3676, + CalendarUpdateEvent = 0x3681, + CancelAura = 0x31a9, + CancelAutoRepeatSpell = 0x34dd, + CancelCast = 0x3283, + CancelChannelling = 0x324f, + CancelGrowthAura = 0x3254, + CancelMasterLootRoll = 0x31fe, + CancelModSpeedNoControlAuras = 0x31a8, + CancelMountAura = 0x3265, + CancelQueuedSpell = 0x317e, + CancelTempEnchantment = 0x34e8, + CancelTrade = 0x315c, + CanDuel = 0x3665, + CanRedeemWowTokenForBalance = 0x3708, + CastSpell = 0x3280, + ChallengeModeRequestLeaders = 0x308f, + ChallengeModeRequestMapStats = 0x308e, + ChangeBagSlotFlag = 0x3308, + ChangeMonumentAppearance = 0x32e8, + ChangeSubGroup = 0x364f, + CharacterRenameRequest = 0x36c1, + CharCustomize = 0x3691, + CharDelete = 0x369e, + CharRaceOrFactionChange = 0x3697, + ChatAddonMessageChannel = 0x37d0, + ChatAddonMessageGuild = 0x37d4, + ChatAddonMessageInstanceChat = 0x37f3, + ChatAddonMessageOfficer = 0x37d6, + ChatAddonMessageParty = 0x37ef, + ChatAddonMessageRaid = 0x37f1, + ChatAddonMessageWhisper = 0x37d2, + ChatChannelAnnouncements = 0x37e7, + ChatChannelBan = 0x37e5, + ChatChannelDeclineInvite = 0x37ea, + ChatChannelDisplayList = 0x37da, + ChatChannelInvite = 0x37e3, + ChatChannelKick = 0x37e4, + ChatChannelList = 0x37d9, + ChatChannelModerate = 0x37de, + ChatChannelModerator = 0x37df, + ChatChannelMute = 0x37e1, + ChatChannelOwner = 0x37dd, + ChatChannelPassword = 0x37db, + ChatChannelSetOwner = 0x37dc, + ChatChannelSilenceAll = 0x37e8, + ChatChannelUnban = 0x37e6, + ChatChannelUnmoderator = 0x37e0, + ChatChannelUnmute = 0x37e2, + ChatChannelUnsilenceAll = 0x37e9, + ChatJoinChannel = 0x37c8, + ChatLeaveChannel = 0x37c9, + ChatMessageAfk = 0x37d7, + ChatMessageChannel = 0x37cf, + ChatMessageDnd = 0x37d8, + ChatMessageEmote = 0x37ec, + ChatMessageGuild = 0x37d3, + ChatMessageInstanceChat = 0x37f2, + ChatMessageOfficer = 0x37d5, + ChatMessageParty = 0x37ee, + ChatMessageRaid = 0x37f0, + ChatMessageRaidWarning = 0x37f4, + ChatMessageSay = 0x37eb, + ChatMessageWhisper = 0x37d1, + ChatMessageYell = 0x37ed, + ChatRegisterAddonPrefixes = 0x37cd, + ChatReportFiltered = 0x37cc, + ChatReportIgnored = 0x37cb, + ChatUnregisterAllAddonPrefixes = 0x37ce, + CheckRafEmailEnabled = 0x36ce, + CheckWowTokenVeteranEligibility = 0x36ec, + ChoiceResponse = 0x3285, + ClearRaidMarker = 0x31a1, + ClearTradeItem = 0x315e, + ClientPortGraveyard = 0x351d, + CloseInteraction = 0x3492, + CollectionItemSetFavorite = 0x3635, + CommentatorEnable = 0x35f1, + CommentatorEnterInstance = 0x35f5, + CommentatorExitInstance = 0x35f6, + CommentatorGetMapInfo = 0x35f2, + CommentatorGetPlayerCooldowns = 0x35f4, + CommentatorGetPlayerInfo = 0x35f3, + CommentatorStartWargame = 0x35f0, + Complaint = 0x366f, + CompleteCinematic = 0x353b, + CompleteMovie = 0x34d3, + ConfirmArtifactRespec = 0x31a6, + ConfirmRespecWipe = 0x31f8, + ConnectToFailed = 0x35d4, + ContributionContribute = 0x354a, + ContributionGetState = 0x354b, + ConvertConsumptionTime = 0x36fc, + ConvertRaid = 0x3651, + CreateCharacter = 0x3646, + CreateShipment = 0x32d4, + DbQueryBulk = 0x35e4, + DeclineGuildInvites = 0x3514, + DeclinePetition = 0x352a, + DeleteEquipmentSet = 0x3502, + DelFriend = 0x36d3, + DelIgnore = 0x36d7, + DepositReagentBank = 0x3311, + DestroyItem = 0x3277, + DfBootPlayerVote = 0x3618, + DfGetJoinStatus = 0x3616, + DfGetSystemInfo = 0x3615, + DfJoin = 0x3609, + DfLeave = 0x3614, + DfProposalResponse = 0x3608, + DfReadyCheckResponse = 0x361b, + DfSetRoles = 0x3617, + DfTeleport = 0x3619, + DiscardedTimeSyncAcks = 0x3a3c, + DismissCritter = 0x34f1, + DoMasterLootRoll = 0x31fd, + DoReadyCheck = 0x3636, + DuelResponse = 0x34d8, + EjectPassenger = 0x3226, + Emote = 0x3537, + EnableEncryptionAck = 0x3767, + EnableNagle = 0x376b, + EnableTaxiNode = 0x34aa, + EngineSurvey = 0x36e6, + EnumCharacters = 0x35e8, + EnumCharactersDeletedByClient = 0x36e0, + FarSight = 0x34de, + GameObjReportUse = 0x34e5, + GameObjUse = 0x34e4, + GarrisonAssignFollowerToBuilding = 0x32bf, + GarrisonCancelConstruction = 0x32b0, + GarrisonCheckUpgradeable = 0x3304, + GarrisonCompleteMission = 0x32f5, + GarrisonGenerateRecruits = 0x32c2, + GarrisonGetBuildingLandmarks = 0x32d0, + GarrisonGetMissionReward = 0x332b, + GarrisonMissionBonusRoll = 0x32f7, + GarrisonPurchaseBuilding = 0x32ac, + GarrisonRecruitFollower = 0x32c4, + GarrisonRemoveFollower = 0x32ec, + GarrisonRemoveFollowerFromBuilding = 0x32c0, + GarrisonRenameFollower = 0x32c1, + GarrisonRequestBlueprintAndSpecializationData = 0x32ab, + GarrisonRequestClassSpecCategoryInfo = 0x32c9, + GarrisonRequestLandingPageShipmentInfo = 0x32d3, + GarrisonRequestShipmentInfo = 0x32d2, + GarrisonResearchTalent = 0x32c5, + GarrisonSetBuildingActive = 0x32ad, + GarrisonSetFollowerFavorite = 0x32bd, + GarrisonSetFollowerInactive = 0x32b9, + GarrisonSetRecruitmentPreferences = 0x32c3, + GarrisonStartMission = 0x32f4, + GarrisonSwapBuildings = 0x32b1, + GenerateRandomCharacterName = 0x35e7, + GetChallengeModeRewards = 0x3686, + GetGarrisonInfo = 0x32a6, + GetItemPurchaseData = 0x3525, + GetMirrorImageData = 0x327b, + GetPvpOptionsEnabled = 0x35ef, + GetRemainingGameTime = 0x36ef, + GetTrophyList = 0x32e5, + GetUndeleteCharacterCooldownStatus = 0x36e2, + GmTicketAcknowledgeSurvey = 0x3695, + GmTicketGetCaseStatus = 0x3694, + GmTicketGetSystemStatus = 0x3693, + GossipSelectOption = 0x3493, + GrantLevel = 0x34ed, + GuildAddBattlenetFriend = 0x308d, + GuildAddRank = 0x3064, + GuildAssignMemberRank = 0x305f, + GuildAutoDeclineInvitation = 0x3061, + GuildBankActivate = 0x34b7, + GuildBankBuyTab = 0x34ba, + GuildBankDepositMoney = 0x34bc, + GuildBankLogQuery = 0x3082, + GuildBankQueryTab = 0x34b9, + GuildBankRemainingWithdrawMoneyQuery = 0x3083, + GuildBankSetTabText = 0x3086, + GuildBankSwapItems = 0x34b8, + GuildBankTextQuery = 0x3087, + GuildBankUpdateTab = 0x34bb, + GuildBankWithdrawMoney = 0x34bd, + GuildChallengeUpdateRequest = 0x307b, + GuildChangeNameRequest = 0x307e, + GuildDeclineInvitation = 0x3060, + GuildDelete = 0x3068, + GuildDeleteRank = 0x3065, + GuildDemoteMember = 0x305e, + GuildEventLogQuery = 0x3085, + GuildGetAchievementMembers = 0x3071, + GuildGetRanks = 0x306d, + GuildGetRoster = 0x3073, + GuildInviteByName = 0x3607, + GuildLeave = 0x3062, + GuildMemberSendSorRequest = 0x308c, + GuildNewsUpdateSticky = 0x306e, + GuildOfficerRemoveMember = 0x3063, + GuildPermissionsQuery = 0x3084, + GuildPromoteMember = 0x305d, + GuildQueryMembersForRecipe = 0x306b, + GuildQueryMemberRecipes = 0x3069, + GuildQueryNews = 0x306c, + GuildQueryRecipes = 0x306a, + GuildReplaceGuildMaster = 0x3088, + GuildSetAchievementTracking = 0x306f, + GuildSetFocusedAchievement = 0x3070, + GuildSetGuildMaster = 0x36c8, + GuildSetMemberNote = 0x3072, + GuildSetRankPermissions = 0x3067, + GuildShiftRank = 0x3066, + GuildUpdateInfoText = 0x3075, + GuildUpdateMotdText = 0x3074, + HearthAndResurrect = 0x34fe, + HotfixQuery = 0x35e5, + IgnoreTrade = 0x3159, + InitiateRolePoll = 0x35da, + InitiateTrade = 0x3156, + Inspect = 0x351f, + InspectPvp = 0x36a4, + InstanceLockResponse = 0x3503, + ItemPurchaseRefund = 0x3526, + ItemTextQuery = 0x3305, + JoinPetBattleQueue = 0x31cc, + JoinRatedBattleground = 0x3176, + KeepAlive = 0x3682, + KeyboundOverride = 0x320f, + LearnPvpTalents = 0x3549, + LearnTalents = 0x3548, + LeaveGroup = 0x364c, + LeavePetBattleQueue = 0x31cd, + LfgListApplyToGroup = 0x360f, + LfgListCancelApplication = 0x3610, + LfgListDeclineApplicant = 0x3611, + LfgListGetStatus = 0x360d, + LfgListInviteApplicant = 0x3612, + LfgListInviteResponse = 0x3613, + LfgListJoin = 0x360a, + LfgListLeave = 0x360c, + LfgListSearch = 0x360e, + LfgListUpdateRequest = 0x360b, + LfGuildAddRecruit = 0x361e, + LfGuildBrowse = 0x3620, + LfGuildDeclineRecruit = 0x3078, + LfGuildGetApplications = 0x3079, + LfGuildGetGuildPost = 0x3076, + LfGuildGetRecruits = 0x3077, + LfGuildRemoveRecruit = 0x307a, + LfGuildSetGuildPost = 0x361f, + ListInventory = 0x34a2, + LiveRegionAccountRestore = 0x36bd, + LiveRegionCharacterCopy = 0x36bc, + LiveRegionGetAccountCharacterList = 0x36bb, + LoadingScreenNotify = 0x35f9, + LoadSelectedTrophy = 0x32e6, + LogoutCancel = 0x34ce, + LogoutInstant = 0x34cf, + LogoutRequest = 0x34cd, + LogDisconnect = 0x3769, + LogStreamingError = 0x376d, + LootItem = 0x31fb, + LootMoney = 0x31fa, + LootRelease = 0x31ff, + LootRoll = 0x3200, + LootUnit = 0x31f9, + LowLevelRaid1 = 0x36a2, + LowLevelRaid2 = 0x350a, + MailCreateTextItem = 0x3531, + MailDelete = 0x3211, + MailGetList = 0x352c, + MailMarkAsRead = 0x3530, + MailReturnToSender = 0x3658, + MailTakeItem = 0x352e, + MailTakeMoney = 0x352d, + MasterLootItem = 0x31fc, + MinimapPing = 0x364e, + MissileTrajectoryCollision = 0x3189, + MountClearFanfare = 0x312d, + MountSetFavorite = 0x3634, + MountSpecialAnim = 0x3266, + MoveApplyMovementForceAck = 0x3a12, + MoveChangeTransport = 0x3a2c, + MoveChangeVehicleSeats = 0x3a31, + MoveDismissVehicle = 0x3a30, + MoveDoubleJump = 0x39eb, + MoveEnableDoubleJumpAck = 0x3a1b, + MoveEnableSwimToFlyTransAck = 0x3a21, + MoveFallLand = 0x39f9, + MoveFallReset = 0x3a16, + MoveFeatherFallAck = 0x3a19, + MoveForceFlightBackSpeedChangeAck = 0x3a2b, + MoveForceFlightSpeedChangeAck = 0x3a2a, + MoveForcePitchRateChangeAck = 0x3a2f, + MoveForceRootAck = 0x3a0b, + MoveForceRunBackSpeedChangeAck = 0x3a09, + MoveForceRunSpeedChangeAck = 0x3a08, + MoveForceSwimBackSpeedChangeAck = 0x3a1f, + MoveForceSwimSpeedChangeAck = 0x3a0a, + MoveForceTurnRateChangeAck = 0x3a20, + MoveForceUnrootAck = 0x3a0c, + MoveForceWalkSpeedChangeAck = 0x3a1e, + MoveGravityDisableAck = 0x3a32, + MoveGravityEnableAck = 0x3a33, + MoveHeartbeat = 0x3a0d, + MoveHoverAck = 0x3a10, + MoveJump = 0x39ea, + MoveKnockBackAck = 0x3a0f, + MoveRemoveMovementForces = 0x3a14, + MoveRemoveMovementForceAck = 0x3a13, + MoveSetCanFlyAck = 0x3a24, + MoveSetCanTurnWhileFallingAck = 0x3a22, + MoveSetCollisionHeightAck = 0x3a36, + MoveSetFacing = 0x3a06, + MoveSetFly = 0x3a25, + MoveSetIgnoreMovementForcesAck = 0x3a23, + MoveSetPitch = 0x3a07, + MoveSetRunMode = 0x39f2, + MoveSetVehicleRecIdAck = 0x3a11, + MoveSetWalkMode = 0x39f3, + MoveSplineDone = 0x3a15, + MoveStartAscend = 0x3a26, + MoveStartBackward = 0x39e5, + MoveStartDescend = 0x3a2d, + MoveStartForward = 0x39e4, + MoveStartPitchDown = 0x39f0, + MoveStartPitchUp = 0x39ef, + MoveStartStrafeLeft = 0x39e7, + MoveStartStrafeRight = 0x39e8, + MoveStartSwim = 0x39fa, + MoveStartTurnLeft = 0x39ec, + MoveStartTurnRight = 0x39ed, + MoveStop = 0x39e6, + MoveStopAscend = 0x3a27, + MoveStopPitch = 0x39f1, + MoveStopStrafe = 0x39e9, + MoveStopSwim = 0x39fb, + MoveStopTurn = 0x39ee, + MoveTeleportAck = 0x39f8, + MoveTimeSkipped = 0x3a18, + MoveToggleCollisionCheat = 0x3a05, + MoveWaterWalkAck = 0x3a1a, + NeutralPlayerSelectFaction = 0x31c2, + NextCinematicCamera = 0x353a, + ObjectUpdateFailed = 0x317f, + ObjectUpdateRescued = 0x3180, + OfferPetition = 0x36b2, + OpeningCinematic = 0x3539, + OpenItem = 0x3306, + OpenMissionNpc = 0x32cb, + OpenShipmentNpc = 0x32d1, + OpenTradeskillNpc = 0x32dc, + OptOutOfLoot = 0x34ec, + PartyInvite = 0x3603, + PartyInviteResponse = 0x3604, + PartyUninvite = 0x364a, + PetitionBuy = 0x34bf, + PetitionRenameGuild = 0x36c9, + PetitionShowList = 0x34be, + PetitionShowSignatures = 0x34c0, + PetAbandon = 0x348c, + PetAction = 0x348a, + PetBattleFinalNotify = 0x31d0, + PetBattleInput = 0x3643, + PetBattleQueueProposeMatchResult = 0x3210, + PetBattleQuitNotify = 0x31cf, + PetBattleReplaceFrontPet = 0x3644, + PetBattleRequestPvp = 0x31ca, + PetBattleRequestUpdate = 0x31cb, + PetBattleRequestWild = 0x31c8, + PetBattleScriptErrorNotify = 0x31d1, + PetBattleWildLocationFail = 0x31c9, + PetCancelAura = 0x348d, + PetCastSpell = 0x327f, + PetRename = 0x3688, + PetSetAction = 0x3489, + PetSpellAutocast = 0x348e, + PetStopAttack = 0x348b, + Ping = 0x3768, + PlayerLogin = 0x35ea, + ProtocolMismatch = 0x376e, + PushQuestToParty = 0x34a0, + PvpLogData = 0x317a, + PvpPrestigeRankUp = 0x3329, + QueryBattlePetName = 0x325b, + QueryCorpseLocationFromClient = 0x3663, + QueryCorpseTransport = 0x3664, + QueryCountdownTimer = 0x31a4, + QueryCreature = 0x3255, + QueryGameObject = 0x3256, + QueryGarrisonCreatureName = 0x325c, + QueryGuildInfo = 0x3690, + QueryInspectAchievements = 0x34f8, + QueryNextMailTime = 0x352f, + QueryNpcText = 0x3257, + QueryPageText = 0x3259, + QueryPetition = 0x325d, + QueryPetName = 0x325a, + QueryPlayerName = 0x368e, + QueryQuestCompletionNpcs = 0x3173, + QueryQuestInfo = 0x3258, + QueryQuestRewards = 0x332d, + QueryRealmName = 0x368f, + QueryScenarioPoi = 0x3659, + QueryTime = 0x34cc, + QueryVoidStorage = 0x319d, + QuestConfirmAccept = 0x349f, + QuestGiverAcceptQuest = 0x3498, + QuestGiverChooseReward = 0x349b, + QuestGiverCompleteQuest = 0x3499, + QuestGiverHello = 0x3495, + QuestGiverQueryQuest = 0x3496, + QuestGiverRequestReward = 0x349c, + QuestGiverStatusMultipleQuery = 0x349e, + QuestGiverStatusQuery = 0x349d, + QuestLogRemoveQuest = 0x3524, + QuestPoiQuery = 0x36b3, + QuestPushResult = 0x34a1, + QueuedMessagesEnd = 0x376c, + QuickJoinAutoAcceptRequests = 0x3707, + QuickJoinRequestInvite = 0x3706, + QuickJoinRespondToInvite = 0x3705, + QuickJoinSignalToastDisplayed = 0x3704, + RaidOrBattlegroundEngineSurvey = 0x36e7, + RandomRoll = 0x3657, + ReadyCheckResponse = 0x3637, + ReadItem = 0x3307, + ReclaimCorpse = 0x34d1, + RecruitAFriend = 0x36cf, + RedeemWowTokenConfirm = 0x36f1, + RedeemWowTokenStart = 0x36f0, + RemoveNewItem = 0x3330, + ReorderCharacters = 0x35e9, + RepairItem = 0x34e2, + ReplaceTrophy = 0x32e7, + RepopRequest = 0x351c, + ReportPvpPlayerAfk = 0x34ea, + RequestAccountData = 0x3698, + RequestAreaPoiUpdate = 0x332f, + RequestBattlefieldStatus = 0x35dc, + RequestCategoryCooldowns = 0x317c, + RequestCemeteryList = 0x3174, + RequestConquestFormulaConstants = 0x3296, + RequestConsumptionConversionInfo = 0x36fb, + RequestCrowdControlSpell = 0x3520, + RequestForcedReactions = 0x31f4, + RequestGuildPartyState = 0x31a3, + RequestGuildRewardsList = 0x31a2, + RequestHonorStats = 0x3179, + RequestLfgListBlacklist = 0x3287, + RequestPartyJoinUpdates = 0x35f8, + RequestPartyMemberStats = 0x3656, + RequestPetInfo = 0x348f, + RequestPlayedTime = 0x3260, + RequestPvpBrawlInfo = 0x3191, + RequestPvpRewards = 0x3190, + RequestRaidInfo = 0x36ca, + RequestRatedBattlefieldInfo = 0x35e3, + RequestResearchHistory = 0x3167, + RequestStabledPets = 0x3490, + RequestVehicleExit = 0x3221, + RequestVehicleNextSeat = 0x3223, + RequestVehiclePrevSeat = 0x3222, + RequestVehicleSwitchSeat = 0x3224, + RequestWorldQuestUpdate = 0x332e, + RequestWowTokenMarketPrice = 0x36e9, + ResetChallengeMode = 0x31f1, + ResetChallengeModeCheat = 0x31f2, + ResetInstances = 0x366b, + ResurrectResponse = 0x3687, + RevertMonumentAppearance = 0x32e9, + RideVehicleInteract = 0x3225, + SaveClientVariables = 0x3702, + SaveCufProfiles = 0x318a, + SaveEnabledAddons = 0x3701, + SaveEquipmentSet = 0x3501, + SaveGuildEmblem = 0x328b, + ScenePlaybackCanceled = 0x320c, + ScenePlaybackComplete = 0x320b, + SceneTriggerEvent = 0x320d, + SelfRes = 0x3527, + SellItem = 0x34a3, + SellWowTokenConfirm = 0x36eb, + SellWowTokenStart = 0x36ea, + SendContactList = 0x36d1, + SendMail = 0x35fb, + SendSorRequestViaAddress = 0x3623, + SendTextEmote = 0x3486, + SetAchievementsHidden = 0x3212, + SetActionBarToggles = 0x3528, + SetActionButton = 0x3638, + SetActiveMover = 0x3a37, + SetAdvancedCombatLogging = 0x3297, + SetAssistantLeader = 0x3652, + SetBackpackAutosortDisabled = 0x330a, + SetBankAutosortDisabled = 0x330b, + SetBankBagSlotFlag = 0x3309, + SetContactNotes = 0x36d4, + SetCurrencyFlags = 0x3169, + SetDifficultyId = 0x320e, + SetDungeonDifficulty = 0x3685, + SetEveryoneIsAssistant = 0x361a, + SetFactionAtWar = 0x34d4, + SetFactionInactive = 0x34d6, + SetFactionNotAtWar = 0x34d5, + SetInsertItemsLeftToRight = 0x330d, + SetLfgBonusFactionId = 0x3286, + SetLootMethod = 0x364b, + SetLootSpecialization = 0x3535, + SetPartyAssignment = 0x3654, + SetPartyLeader = 0x364d, + SetPetSlot = 0x3168, + SetPlayerDeclinedNames = 0x368d, + SetPreferredCemetery = 0x3175, + SetPvp = 0x328f, + SetRaidDifficulty = 0x36de, + SetRole = 0x35d9, + SetSavedInstanceExtend = 0x368b, + SetSelection = 0x351e, + SetSheathed = 0x3487, + SetSortBagsRightToLeft = 0x330c, + SetTaxiBenchmarkMode = 0x34e9, + SetTitle = 0x3264, + SetTradeCurrency = 0x3160, + SetTradeGold = 0x315f, + SetTradeItem = 0x315d, + SetUsingPartyGarrison = 0x32cd, + SetWatchedFaction = 0x34d7, + ShowTradeSkill = 0x36c2, + SignPetition = 0x3529, + SilencePartyTalker = 0x3655, + SocketGems = 0x34e1, + SortBags = 0x330e, + SortBankBags = 0x330f, + SortReagentBankBags = 0x3310, + SpellClick = 0x3494, + SpiritHealerActivate = 0x34b0, + SplitItem = 0x399e, + StandStateChange = 0x3188, + StartChallengeMode = 0x3540, + StartSpectatorWarGame = 0x35df, + StartWarGame = 0x35de, + SummonResponse = 0x366d, + SupportTicketSubmitBug = 0x3648, + SupportTicketSubmitComplaint = 0x3647, + SupportTicketSubmitSuggestion = 0x3649, + SurrenderArena = 0x3172, + SuspendCommsAck = 0x3764, + SuspendTokenResponse = 0x376a, + SwapInvItem = 0x399d, + SwapItem = 0x399c, + SwapSubGroups = 0x3650, + SwapVoidItem = 0x319f, + TabardVendorActivate = 0x328c, + TalkToGossip = 0x3491, + TaxiNodeStatusQuery = 0x34a9, + TaxiQueryAvailableNodes = 0x34ab, + TaxiRequestEarlyLanding = 0x34ad, + TimeAdjustmentResponse = 0x3a3b, + TimeSyncResponse = 0x3a38, + TimeSyncResponseDropped = 0x3a3a, + TimeSyncResponseFailed = 0x3a39, + ToggleDifficulty = 0x365a, + TogglePvp = 0x328e, + TotemDestroyed = 0x34f0, + TradeSkillSetFavorite = 0x332c, + TrainerBuySpell = 0x34af, + TrainerList = 0x34ae, + TransmogrifyItems = 0x3192, + TurnInPetition = 0x352b, + Tutorial = 0x36df, + TwitterCheckStatus = 0x312a, + TwitterConnect = 0x3127, + TwitterDisconnect = 0x312b, + TwitterPost = 0x3312, + UiTimeRequest = 0x369d, + UnacceptTrade = 0x315b, + UndeleteCharacter = 0x36e1, + UnlearnSkill = 0x34db, + UnlearnSpecialization = 0x31a0, + UnlockVoidStorage = 0x319c, + UpdateAccountData = 0x3699, + UpdateAreaTriggerVisual = 0x3282, + UpdateClientSettings = 0x3667, + UpdateMissileTrajectory = 0x3a3e, + UpdateRaidTarget = 0x3653, + UpdateSpellVisual = 0x3281, + UpdateVasPurchaseStates = 0x36f7, + UpdateWowTokenAuctionableList = 0x36f2, + UpdateWowTokenCount = 0x36e8, + UpgradeGarrison = 0x329f, + UpgradeItem = 0x3213, + UsedFollow = 0x3185, + UseCritterItem = 0x322c, + UseEquipmentSet = 0x3995, + UseItem = 0x327c, + UseToy = 0x327e, + ViolenceLevel = 0x3183, + VoidStorageTransfer = 0x319e, + WardenData = 0x35ec, + Who = 0x3684, + WhoIs = 0x3683, + WorldPortResponse = 0x35fa, + WrapItem = 0x3994, + + Max = 0x3FFF, + Unknown = 0xbadd + } + + public enum ServerOpcodes : uint + { + AbortNewWorld = 0x25ae, + AccountCriteriaUpdate = 0x2654, + AccountDataTimes = 0x274b, + AccountMountUpdate = 0x25c4, + AccountToysUpdate = 0x25c5, + AchievementDeleted = 0x2720, + AchievementEarned = 0x2662, + ActivateTaxiReply = 0x26a8, + ActiveGlyphs = 0x2c52, + AddBattlenetFriendResponse = 0x265c, + AddItemPassive = 0x25c0, + AddLossOfControl = 0x2698, + AddRunePower = 0x26e4, + AdjustSplineDuration = 0x25e9, + AeLootTargets = 0x262d, + AeLootTargetAck = 0x262e, + AiReaction = 0x26e1, + AllAccountCriteria = 0x2570, + AllAchievementData = 0x256f, + AllGuildAchievements = 0x29b8, + ArchaeologySurveryCast = 0x2587, + AreaPoiUpdate = 0x2844, + AreaSpiritHealerTime = 0x2784, + AreaTriggerDenied = 0x269f, + AreaTriggerNoCorpse = 0x2757, + AreaTriggerRePath = 0x2641, + AreaTriggerReShape = 0x263e, + ArenaCrowdControlSpells = 0x2650, + ArenaError = 0x2713, + ArenaPrepOpponentSpecializations = 0x2667, + ArtifactForgeOpened = 0x27e5, + ArtifactRespecConfirm = 0x27e8, + ArtifactTraitsRefunded = 0x27e9, + ArtifactXpGain = 0x282a, + AttackerStateUpdate = 0x27d2, + AttackStart = 0x266f, + AttackStop = 0x2670, + AttackSwingError = 0x2735, + AttackSwingLandedLog = 0x2736, + AuctionClosedNotification = 0x272a, + AuctionCommandResult = 0x2727, + AuctionHelloResponse = 0x2725, + AuctionListBidderItemsResult = 0x272e, + AuctionListItemsResult = 0x272c, + AuctionListOwnerItemsResult = 0x272d, + AuctionListPendingSalesResult = 0x272f, + AuctionOutbidNotification = 0x2729, + AuctionOwnerBidNotification = 0x272b, + AuctionReplicateResponse = 0x2726, + AuctionWonNotification = 0x2728, + AuraPointsDepleted = 0x2c23, + AuraUpdate = 0x2c22, + AuthChallenge = 0x3048, + AuthResponse = 0x256c, + BanReason = 0x26b4, + BarberShopResult = 0x26ea, + BattlefieldList = 0x2595, + BattlefieldPortDenied = 0x259b, + BattlefieldStatusActive = 0x2591, + BattlefieldStatusFailed = 0x2594, + BattlefieldStatusNeedConfirmation = 0x2590, + BattlefieldStatusNone = 0x2593, + BattlefieldStatusQueued = 0x2592, + BattlefieldStatusWaitForGroups = 0x25a6, + BattlegroundInfoThrottled = 0x259c, + BattlegroundInit = 0x27a2, + BattlegroundPlayerJoined = 0x2599, + BattlegroundPlayerLeft = 0x259a, + BattlegroundPlayerPositions = 0x2596, + BattlegroundPoints = 0x27a1, + BattlenetChallengeAbort = 0x27d1, + BattlenetChallengeStart = 0x27d0, + BattlenetNotification = 0x283f, + BattlenetRealmListTicket = 0x2841, + BattlenetResponse = 0x283e, + BattlenetSetSessionState = 0x2840, + BattlePayAckFailed = 0x27c9, + BattlePayBattlePetDelivered = 0x27be, + BattlePayConfirmPurchase = 0x27c8, + BattlePayDeliveryEnded = 0x27bc, + BattlePayDeliveryStarted = 0x27bb, + BattlePayDistributionUpdate = 0x27ba, + BattlePayGetDistributionListResponse = 0x27b8, + BattlePayGetProductListResponse = 0x27b6, + BattlePayGetPurchaseListResponse = 0x27b7, + BattlePayMountDelivered = 0x27bd, + BattlePayPurchaseUpdate = 0x27c7, + BattlePayStartDistributionAssignToTargetResponse = 0x27c5, + BattlePayStartPurchaseResponse = 0x27c4, + BattlePayVasBnetTransferValidationResult = 0x2856, + BattlePayVasBoostConsumed = 0x27b9, + BattlePayVasCharacterList = 0x282d, + BattlePayVasCharacterQueueStatus = 0x2855, + BattlePayVasPurchaseComplete = 0x2830, + BattlePayVasPurchaseList = 0x2831, + BattlePayVasPurchaseStarted = 0x282f, + BattlePayVasRealmList = 0x282e, + BattlePayVasTransferQueueStatus = 0x2854, + BattlePetsHealed = 0x260a, + BattlePetCageDateError = 0x26a2, + BattlePetDeleted = 0x2607, + BattlePetError = 0x2657, + BattlePetJournal = 0x2606, + BattlePetJournalLockAcquired = 0x2604, + BattlePetJournalLockDenied = 0x2605, + BattlePetLicenseChanged = 0x260b, + BattlePetMaxCountChanged = 0x2602, + BattlePetRestored = 0x2609, + BattlePetRevoked = 0x2608, + BattlePetTrapLevel = 0x2601, + BattlePetUpdates = 0x2600, + BinderConfirm = 0x273b, + BindPointUpdate = 0x257d, + BlackMarketBidOnItemResult = 0x2646, + BlackMarketOpenResult = 0x2644, + BlackMarketOutbid = 0x2647, + BlackMarketRequestItemsResult = 0x2645, + BlackMarketWon = 0x2648, + BonusRollEmpty = 0x2664, + BossKillCredit = 0x27c3, + BreakTarget = 0x266e, + BuyFailed = 0x26f3, + BuySucceeded = 0x26f2, + CacheInfo = 0x2745, + CacheVersion = 0x2744, + CalendarClearPendingAction = 0x26c8, + CalendarCommandResult = 0x26c9, + CalendarEventInitialInvites = 0x26b8, + CalendarEventInvite = 0x26b9, + CalendarEventInviteAlert = 0x26ba, + CalendarEventInviteModeratorStatus = 0x26bd, + CalendarEventInviteNotes = 0x26c2, + CalendarEventInviteNotesAlert = 0x26c3, + CalendarEventInviteRemoved = 0x26be, + CalendarEventInviteRemovedAlert = 0x26bf, + CalendarEventInviteStatus = 0x26bb, + CalendarEventInviteStatusAlert = 0x26bc, + CalendarEventRemovedAlert = 0x26c0, + CalendarEventUpdatedAlert = 0x26c1, + CalendarRaidLockoutAdded = 0x26c4, + CalendarRaidLockoutRemoved = 0x26c5, + CalendarRaidLockoutUpdated = 0x26c6, + CalendarSendCalendar = 0x26b6, + CalendarSendEvent = 0x26b7, + CalendarSendNumPending = 0x26c7, + CameraEffect = 0x2769, + CancelAutoRepeat = 0x2714, + CancelCombat = 0x2733, + CancelOrphanSpellVisual = 0x2c45, + CancelScene = 0x2656, + CancelSpellVisual = 0x2c43, + CancelSpellVisualKit = 0x2c47, + CanDuelResult = 0x2678, + CastFailed = 0x2c55, + CategoryCooldown = 0x2c16, + ChallengeModeAllMapStats = 0x2623, + ChallengeModeComplete = 0x2621, + ChallengeModeMapStatsUpdate = 0x2624, + ChallengeModeNewPlayerRecord = 0x2626, + ChallengeModeRequestLeadersResult = 0x2625, + ChallengeModeReset = 0x2620, + ChallengeModeRewards = 0x2622, + ChallengeModeStart = 0x261e, + ChallengeModeUpdateDeathCount = 0x261f, + ChangePlayerDifficultyResult = 0x2737, + ChannelList = 0x2bc3, + ChannelNotify = 0x2bc0, + ChannelNotifyJoined = 0x2bc1, + ChannelNotifyLeft = 0x2bc2, + CharacterClassTrialCreate = 0x2802, + CharacterItemFixup = 0x2850, + CharacterLoginFailed = 0x2746, + CharacterObjectTestResponse = 0x27cf, + CharacterRenameResult = 0x27a8, + CharacterUpgradeComplete = 0x2801, + CharacterUpgradeQueued = 0x2800, + CharacterUpgradeSpellTierSet = 0x25f5, + CharacterUpgradeStarted = 0x27ff, + CharacterUpgradeUnrevokeResult = 0x2803, + CharCustomize = 0x271b, + CharCustomizeFailed = 0x271a, + CharFactionChangeResult = 0x27ed, + Chat = 0x2bad, + ChatAutoResponded = 0x2bb8, + ChatDown = 0x2bbd, + ChatIgnoredAccountMuted = 0x2bac, + ChatIsDown = 0x2bbe, + ChatNotInParty = 0x2bb2, + ChatPlayerAmbiguous = 0x2bb0, + ChatPlayerNotfound = 0x2bb7, + ChatReconnect = 0x2bbf, + ChatRestricted = 0x2bb3, + ChatServerMessage = 0x2bc4, + CheatIgnoreDimishingReturns = 0x2c12, + CheckWargameEntry = 0x259f, + ClearAllSpellCharges = 0x2c27, + ClearBossEmotes = 0x25ce, + ClearCooldown = 0x26e6, + ClearCooldowns = 0x2c26, + ClearLossOfControl = 0x269a, + ClearSpellCharges = 0x2c28, + ClearTarget = 0x26dd, + CoinRemoved = 0x262c, + CombatEventFailed = 0x2671, + CommentatorMapInfo = 0x2748, + CommentatorPlayerInfo = 0x2749, + CommentatorStateChanged = 0x2747, + ComplaintResult = 0x26d5, + CompleteShipmentResponse = 0x27e1, + ConnectTo = 0x304d, + ConquestFormulaConstants = 0x27ca, + ConsoleWrite = 0x2653, + ConsumptionConversionInfoResponse = 0x2845, + ConsumptionConversionResult = 0x2846, + ContactList = 0x27cd, + ControlUpdate = 0x2666, + CooldownCheat = 0x277d, + CooldownEvent = 0x26e5, + CorpseLocation = 0x266d, + CorpseReclaimDelay = 0x2790, + CorpseTransportQuery = 0x2753, + CreateChar = 0x2740, + CreateShipmentResponse = 0x27e0, + CriteriaDeleted = 0x271f, + CriteriaUpdate = 0x2719, + CrossedInebriationThreshold = 0x26ee, + CustomLoadScreen = 0x25e4, + DailyQuestsReset = 0x2a80, + DamageCalcLog = 0x280a, + DanceStudioCreateResult = 0x27a5, + DbReply = 0x25a1, + DeathReleaseLoc = 0x2707, + DefenseMessage = 0x2bb6, + DeleteChar = 0x2741, + DestroyArenaUnit = 0x2786, + DestructibleBuildingDamage = 0x2734, + DifferentInstanceFromParty = 0x258b, + DisenchantCredit = 0x25bd, + Dismount = 0x26dc, + DismountResult = 0x257c, + DispelFailed = 0x2c30, + DisplayGameError = 0x25b6, + DisplayPlayerChoice = 0x26a3, + DisplayPromotion = 0x266a, + DisplayQuestPopup = 0x2a9d, + DisplayToast = 0x263a, + DontAutoPushSpellsToActionBar = 0x25f7, + DropNewConnection = 0x304c, + DuelComplete = 0x2676, + DuelCountdown = 0x2675, + DuelInBounds = 0x2674, + DuelOutOfBounds = 0x2673, + DuelRequested = 0x2672, + DuelWinner = 0x2677, + DurabilityDamageDeath = 0x278c, + Emote = 0x280b, + EnableBarberShop = 0x26e9, + EnableEncryption = 0x3049, + EnchantmentLog = 0x2754, + EncounterEnd = 0x27c2, + EncounterStart = 0x27c1, + EnumCharactersResult = 0x2583, + EnvironmentalDamageLog = 0x2c21, + EquipmentSetId = 0x26de, + ExpectedSpamRecords = 0x2bb1, + ExplorationExperience = 0x27a4, + FactionBonusInfo = 0x2768, + FailedPlayerCondition = 0x25e3, + FeatureSystemStatus = 0x25d2, + FeatureSystemStatusGlueScreen = 0x25d3, + FeignDeathResisted = 0x2789, + FishEscaped = 0x26fb, + FishNotHooked = 0x26fa, + FlightSplineSync = 0x2df7, + ForcedDeathUpdate = 0x2708, + ForceAnim = 0x2796, + ForceObjectRelink = 0x2669, + FriendStatus = 0x27ce, + GameObjectActivateAnimKit = 0x25d7, + GameObjectCustomAnim = 0x25d8, + GameObjectDespawn = 0x25d9, + GameObjectPlaySpellVisual = 0x2c4a, + GameObjectPlaySpellVisualKit = 0x2c49, + GameObjectResetState = 0x275f, + GameObjectSetState = 0x283d, + GameObjectUiAction = 0x275c, + GameSpeedSet = 0x26ac, + GameTimeSet = 0x274d, + GameTimeUpdate = 0x274c, + GarrisonAddFollowerResult = 0x2902, + GarrisonAddMissionResult = 0x2906, + GarrisonAssignFollowerToBuildingResult = 0x2918, + GarrisonBuildingActivated = 0x28fb, + GarrisonBuildingLandmarks = 0x292c, + GarrisonBuildingRemoved = 0x28f4, + GarrisonBuildingSetActiveSpecializationResult = 0x28f6, + GarrisonClearAllFollowersExhaustion = 0x2916, + GarrisonCompleteMissionResult = 0x2909, + GarrisonCreateResult = 0x28fc, + GarrisonDeleteResult = 0x2920, + GarrisonFollowerChangedAbilities = 0x2914, + GarrisonFollowerChangedDurability = 0x2904, + GarrisonFollowerChangedItemLevel = 0x2913, + GarrisonFollowerChangedStatus = 0x2915, + GarrisonFollowerChangedXp = 0x2912, + GarrisonIsUpgradeableResult = 0x2929, + GarrisonLandingPageShipmentInfo = 0x27e3, + GarrisonLearnBlueprintResult = 0x28f7, + GarrisonLearnSpecializationResult = 0x28f5, + GarrisonListFollowersCheatResult = 0x2905, + GarrisonListMissionsCheatResult = 0x292a, + GarrisonMissionAreaBonusAdded = 0x2910, + GarrisonMissionBonusRollResult = 0x290c, + GarrisonMissionRewardResponse = 0x292d, + GarrisonMissionUpdateCanStart = 0x2911, + GarrisonNumFollowerActivationsRemaining = 0x2917, + GarrisonOpenArchitect = 0x2921, + GarrisonOpenMissionNpc = 0x2923, + GarrisonOpenRecruitmentNpc = 0x291c, + GarrisonOpenTalentNpc = 0x291d, + GarrisonOpenTradeskillNpc = 0x2922, + GarrisonPlaceBuildingResult = 0x28f3, + GarrisonPlotPlaced = 0x28f1, + GarrisonPlotRemoved = 0x28f2, + GarrisonRecallPortalLastUsedTime = 0x290a, + GarrisonRecallPortalUsed = 0x290b, + GarrisonRecruitmentFollowersGenerated = 0x291e, + GarrisonRecruitFollowerResult = 0x291f, + GarrisonRemoteInfo = 0x28fa, + GarrisonRemoveFollowerFromBuildingResult = 0x2919, + GarrisonRemoveFollowerResult = 0x2903, + GarrisonRequestBlueprintAndSpecializationDataResult = 0x28f9, + GarrisonStartMissionResult = 0x2907, + GarrisonUnlearnBlueprintResult = 0x28f8, + GarrisonUpgradeResult = 0x28fd, + GenerateRandomCharacterNameResult = 0x2584, + GetAccountCharacterListResult = 0x27a6, + GetDisplayedTrophyListResponse = 0x2928, + GetGarrisonInfoResult = 0x28f0, + GetShipmentsOfTypeResponse = 0x27e2, + GetShipmentInfoResponse = 0x27de, + GetTrophyListResponse = 0x2806, + GmPlayerInfo = 0x277c, + GmRequestPlayerInfo = 0x25ee, + GmTicketCaseStatus = 0x26ce, + GmTicketSystemStatus = 0x26cd, + GodMode = 0x273a, + GossipComplete = 0x2a96, + GossipMessage = 0x2a97, + GossipPoi = 0x27db, + GroupActionThrottled = 0x259d, + GroupDecline = 0x27d6, + GroupDestroyed = 0x27d8, + GroupInviteConfirmation = 0x2851, + GroupNewLeader = 0x264b, + GroupUninvite = 0x27d7, + GuildAchievementDeleted = 0x29c5, + GuildAchievementEarned = 0x29c4, + GuildAchievementMembers = 0x29c7, + GuildBankLogQueryResults = 0x29df, + GuildBankQueryResults = 0x29de, + GuildBankRemainingWithdrawMoney = 0x29e0, + GuildBankTextQueryResult = 0x29e3, + GuildChallengeCompleted = 0x29d3, + GuildChallengeUpdate = 0x29d2, + GuildChangeNameResult = 0x29dd, + GuildCommandResult = 0x29ba, + GuildCriteriaDeleted = 0x29c6, + GuildCriteriaUpdate = 0x29c3, + GuildEventBankContentsChanged = 0x29f5, + GuildEventBankMoneyChanged = 0x29f4, + GuildEventDisbanded = 0x29eb, + GuildEventLogQueryResults = 0x29e2, + GuildEventMotd = 0x29ec, + GuildEventNewLeader = 0x29ea, + GuildEventPlayerJoined = 0x29e8, + GuildEventPlayerLeft = 0x29e9, + GuildEventPresenceChange = 0x29ed, + GuildEventRanksUpdated = 0x29ee, + GuildEventRankChanged = 0x29ef, + GuildEventTabAdded = 0x29f0, + GuildEventTabDeleted = 0x29f1, + GuildEventTabModified = 0x29f2, + GuildEventTabTextChanged = 0x29f3, + GuildFlaggedForRename = 0x29dc, + GuildInvite = 0x29ca, + GuildInviteDeclined = 0x29e6, + GuildInviteExpired = 0x29e7, + GuildItemLooted = 0x29d4, + GuildKnownRecipes = 0x29be, + GuildMembersWithRecipe = 0x29bf, + GuildMemberDailyReset = 0x29e4, + GuildMemberRecipes = 0x29bd, + GuildMemberUpdateNote = 0x29c9, + GuildMoved = 0x29da, + GuildMoveStarting = 0x29d9, + GuildNameChanged = 0x29db, + GuildNews = 0x29c1, + GuildNewsDeleted = 0x29c2, + GuildPartyState = 0x29cb, + GuildPermissionsQueryResults = 0x29e1, + GuildRanks = 0x29c8, + GuildReputationReactionChanged = 0x29cc, + GuildReset = 0x29d8, + GuildRewardList = 0x29c0, + GuildRoster = 0x29bb, + GuildRosterUpdate = 0x29bc, + GuildSendRankChange = 0x29b9, + HealthUpdate = 0x26fe, + HighestThreatUpdate = 0x270e, + Hotfixes = 0x25a3, + HotfixList = 0x25a2, + HotfixQueryResponse = 0x25a4, + InitializeFactions = 0x2767, + InitialSetup = 0x2580, + InitWorldStates = 0x278d, + InspectHonorStats = 0x25b3, + InspectPvp = 0x2763, + InspectResult = 0x264f, + InstanceEncounterChangePriority = 0x27f3, + InstanceEncounterDisengageUnit = 0x27f2, + InstanceEncounterEnd = 0x27fa, + InstanceEncounterEngageUnit = 0x27f1, + InstanceEncounterGainCombatResurrectionCharge = 0x27fc, + InstanceEncounterInCombatResurrection = 0x27fb, + InstanceEncounterObjectiveComplete = 0x27f6, + InstanceEncounterObjectiveStart = 0x27f5, + InstanceEncounterObjectiveUpdate = 0x27f9, + InstanceEncounterPhaseShiftChanged = 0x27fd, + InstanceEncounterSetSuppressingRelease = 0x27f8, + InstanceEncounterStart = 0x27f7, + InstanceEncounterTimerStart = 0x27f4, + InstanceGroupSizeChanged = 0x2738, + InstanceInfo = 0x2652, + InstanceReset = 0x26b1, + InstanceResetFailed = 0x26b2, + InstanceSaveCreated = 0x27c0, + InvalidatePageText = 0x2703, + InvalidatePlayer = 0x26d4, + InvalidPromotionCode = 0x2797, + InventoryChangeFailure = 0x2765, + IsQuestCompleteResponse = 0x2a83, + ItemChanged = 0x2722, + ItemCooldown = 0x2809, + ItemEnchantTimeUpdate = 0x2799, + ItemExpirePurchaseRefund = 0x25b2, + ItemPurchaseRefundResult = 0x25b0, + ItemPushResult = 0x2639, + ItemTimeUpdate = 0x2798, + KickReason = 0x282c, + LearnedSpells = 0x2c4c, + LearnPvpTalentsFailed = 0x25eb, + LearnTalentsFailed = 0x25ea, + LevelUpdate = 0x2588, + LevelUpInfo = 0x2721, + LfgBootPlayer = 0x2a36, + LfgDisabled = 0x2a34, + LfgInstanceShutdownCountdown = 0x2a25, + LfgJoinResult = 0x2a1c, + LfgListJoinResult = 0x2a1d, + LfgListSearchResults = 0x2a1e, + LfgListSearchStatus = 0x2a1f, + LfgListUpdateBlacklist = 0x2a2a, + LfgListUpdateStatus = 0x2a26, + LfgOfferContinue = 0x2a35, + LfgPartyInfo = 0x2a37, + LfgPlayerInfo = 0x2a38, + LfgPlayerReward = 0x2a39, + LfgProposalUpdate = 0x2a2d, + LfgQueueStatus = 0x2a20, + LfgReadyCheckResult = 0x2a3b, + LfgReadyCheckUpdate = 0x2a22, + LfgRoleCheckUpdate = 0x2a21, + LfgSlotInvalid = 0x2a30, + LfgTeleportDenied = 0x2a33, + LfgUpdateStatus = 0x2a24, + LfGuildApplicantListChanged = 0x29d5, + LfGuildApplications = 0x29d1, + LfGuildApplicationsListChanged = 0x29d6, + LfGuildBrowse = 0x29ce, + LfGuildCommandResult = 0x29d0, + LfGuildPost = 0x29cd, + LfGuildRecruits = 0x29cf, + LiveRegionAccountRestoreResult = 0x27b4, + LiveRegionCharacterCopyResult = 0x27b2, + LiveRegionGetAccountCharacterListResult = 0x27a7, + LoadCufProfiles = 0x25cf, + LoadEquipmentSet = 0x274f, + LoadSelectedTrophyResult = 0x2807, + LoginSetTimeSpeed = 0x274e, + LoginVerifyWorld = 0x25ad, + LogoutCancelAck = 0x26b0, + LogoutComplete = 0x26af, + LogoutResponse = 0x26ae, + LogXpGain = 0x271d, + LootAllPassed = 0x2637, + LootItemList = 0x2635, + LootList = 0x2785, + LootMoneyNotify = 0x2631, + LootRelease = 0x2630, + LootReleaseAll = 0x262f, + LootRemoved = 0x262a, + LootResponse = 0x2629, + LootRoll = 0x2633, + LootRollsComplete = 0x2636, + LootRollWon = 0x2638, + LossOfControlAuraUpdate = 0x2697, + MailCommandResult = 0x265a, + MailListResult = 0x279a, + MailQueryNextTimeResult = 0x279b, + MapObjectivesInit = 0x27a3, + MapObjEvents = 0x25da, + MasterLootCandidateList = 0x2634, + MessageBox = 0x2575, + MinimapPing = 0x26f9, + MirrorImageComponentedData = 0x2c14, + MirrorImageCreatureData = 0x2c13, + MissileCancel = 0x25db, + ModifyChargeRecoverySpeed = 0x27ab, + ModifyCooldown = 0x27a9, + ModifyCooldownRecoverySpeed = 0x27aa, + ModifyPartyRange = 0x2788, + Motd = 0x2baf, + MountResult = 0x257b, + MoveApplyMovementForce = 0x2de1, + MoveDisableCollision = 0x2ddd, + MoveDisableDoubleJump = 0x2dcb, + MoveDisableGravity = 0x2ddb, + MoveDisableTransitionBetweenSwimAndFly = 0x2dda, + MoveEnableCollision = 0x2dde, + MoveEnableDoubleJump = 0x2dca, + MoveEnableGravity = 0x2ddc, + MoveEnableTransitionBetweenSwimAndFly = 0x2dd9, + MoveKnockBack = 0x2dd1, + MoveRemoveMovementForce = 0x2de2, + MoveRoot = 0x2dc7, + MoveSetActiveMover = 0x2da3, + MoveSetCanFly = 0x2dd3, + MoveSetCanTurnWhileFalling = 0x2dd5, + MoveSetCollisionHeight = 0x2ddf, + MoveSetCompoundState = 0x2de3, + MoveSetFeatherFall = 0x2dcd, + MoveSetFlightBackSpeed = 0x2dc3, + MoveSetFlightSpeed = 0x2dc2, + MoveSetHovering = 0x2dcf, + MoveSetIgnoreMovementForces = 0x2dd7, + MoveSetLandWalk = 0x2dcc, + MoveSetNormalFall = 0x2dce, + MoveSetPitchRate = 0x2dc6, + MoveSetRunBackSpeed = 0x2dbf, + MoveSetRunSpeed = 0x2dbe, + MoveSetSwimBackSpeed = 0x2dc1, + MoveSetSwimSpeed = 0x2dc0, + MoveSetTurnRate = 0x2dc5, + MoveSetVehicleRecId = 0x2de0, + MoveSetWalkSpeed = 0x2dc4, + MoveSetWaterWalk = 0x2dc9, + MoveSkipTime = 0x2de4, + MoveSplineDisableCollision = 0x2de9, + MoveSplineDisableGravity = 0x2de7, + MoveSplineEnableCollision = 0x2dea, + MoveSplineEnableGravity = 0x2de8, + MoveSplineRoot = 0x2de5, + MoveSplineSetFeatherFall = 0x2deb, + MoveSplineSetFlightBackSpeed = 0x2dba, + MoveSplineSetFlightSpeed = 0x2db9, + MoveSplineSetFlying = 0x2df5, + MoveSplineSetHover = 0x2ded, + MoveSplineSetLandWalk = 0x2df0, + MoveSplineSetNormalFall = 0x2dec, + MoveSplineSetPitchRate = 0x2dbd, + MoveSplineSetRunBackSpeed = 0x2db6, + MoveSplineSetRunMode = 0x2df3, + MoveSplineSetRunSpeed = 0x2db5, + MoveSplineSetSwimBackSpeed = 0x2db8, + MoveSplineSetSwimSpeed = 0x2db7, + MoveSplineSetTurnRate = 0x2dbc, + MoveSplineSetWalkMode = 0x2df4, + MoveSplineSetWalkSpeed = 0x2dbb, + MoveSplineSetWaterWalk = 0x2def, + MoveSplineStartSwim = 0x2df1, + MoveSplineStopSwim = 0x2df2, + MoveSplineUnroot = 0x2de6, + MoveSplineUnsetFlying = 0x2df6, + MoveSplineUnsetHover = 0x2dee, + MoveTeleport = 0x2dd2, + MoveUnroot = 0x2dc8, + MoveUnsetCanFly = 0x2dd4, + MoveUnsetCanTurnWhileFalling = 0x2dd6, + MoveUnsetHovering = 0x2dd0, + MoveUnsetIgnoreMovementForces = 0x2dd8, + MoveUpdate = 0x2dae, + MoveUpdateApplyMovementForce = 0x2db2, + MoveUpdateCollisionHeight = 0x2dad, + MoveUpdateFlightBackSpeed = 0x2daa, + MoveUpdateFlightSpeed = 0x2da9, + MoveUpdateKnockBack = 0x2db0, + MoveUpdatePitchRate = 0x2dac, + MoveUpdateRemoveMovementForce = 0x2db3, + MoveUpdateRunBackSpeed = 0x2da5, + MoveUpdateRunSpeed = 0x2da4, + MoveUpdateSwimBackSpeed = 0x2da8, + MoveUpdateSwimSpeed = 0x2da7, + MoveUpdateTeleport = 0x2daf, + MoveUpdateTurnRate = 0x2dab, + MoveUpdateWalkSpeed = 0x2da6, + NeutralPlayerFactionSelectResult = 0x25f2, + NewTaxiPath = 0x26a9, + NewWorld = 0x25ac, + NotifyDestLocSpellCast = 0x2c42, + NotifyMissileTrajectoryCollision = 0x26d3, + NotifyMoney = 0x25af, + NotifyReceivedMail = 0x265b, + OfferPetitionError = 0x26e2, + OnCancelExpectedRideVehicleAura = 0x271e, + OnMonsterMove = 0x2da2, + OpenContainer = 0x2766, + OpenLfgDungeonFinder = 0x2a32, + OpenShipmentNpcFromGossip = 0x27dd, + OpenShipmentNpcResult = 0x27df, + OpenTransmogrifier = 0x2833, + OverrideLight = 0x26e8, + PageText = 0x275b, + PartyCommandResult = 0x27da, + PartyInvite = 0x25d0, + PartyKillLog = 0x279f, + PartyMemberState = 0x279d, + PartyMemberStateUpdate = 0x279c, + PartyUpdate = 0x260c, + PauseMirrorTimer = 0x2751, + PendingRaidLock = 0x2732, + PetitionAlreadySigned = 0x25b9, + PetitionRenameGuildResponse = 0x29f7, + PetitionShowList = 0x26eb, + PetitionShowSignatures = 0x26ec, + PetitionSignResults = 0x2791, + PetActionFeedback = 0x278f, + PetActionSound = 0x26cb, + PetAdded = 0x25a9, + PetBattleChatRestricted = 0x2619, + PetBattleDebugQueueDumpResponse = 0x269e, + PetBattleFinalizeLocation = 0x2612, + PetBattleFinalRound = 0x2617, + PetBattleFinished = 0x2618, + PetBattleFirstRound = 0x2614, + PetBattleInitialUpdate = 0x2613, + PetBattleMaxGameLengthWarning = 0x261a, + PetBattlePvpChallenge = 0x2611, + PetBattleQueueProposeMatch = 0x2658, + PetBattleQueueStatus = 0x2659, + PetBattleReplacementsMade = 0x2616, + PetBattleRequestFailed = 0x2610, + PetBattleRoundResult = 0x2615, + PetBattleSlotUpdates = 0x2603, + PetCastFailed = 0x2c56, + PetClearSpells = 0x2c24, + PetDismissSound = 0x26cc, + PetGodMode = 0x26a6, + PetGuids = 0x2743, + PetLearnedSpells = 0x2c4e, + PetMode = 0x258a, + PetNameInvalid = 0x26f0, + PetSlotUpdated = 0x2589, + PetSpellsMessage = 0x2c25, + PetStableList = 0x25aa, + PetStableResult = 0x25ab, + PetTameFailure = 0x26df, + PetUnlearnedSpells = 0x2c4f, + PhaseShiftChange = 0x2578, + PlayedTime = 0x270a, + PlayerBound = 0x257e, + PlayerSaveGuildEmblem = 0x29f6, + PlayerSkinned = 0x278a, + PlayerTabardVendorActivate = 0x279e, + PlayMusic = 0x27ae, + PlayObjectSound = 0x27af, + PlayOneShotAnimKit = 0x2774, + PlayOrphanSpellVisual = 0x2c46, + PlayScene = 0x2655, + PlaySound = 0x27ad, + PlaySpeakerbotSound = 0x27b0, + PlaySpellVisual = 0x2c44, + PlaySpellVisualKit = 0x2c48, + PlayTimeWarning = 0x273c, + Pong = 0x304e, + PowerUpdate = 0x26ff, + PrestigeAndHonorInvoluntarilyChanged = 0x275a, + PreRessurect = 0x27ac, + PrintNotification = 0x25e2, + ProcResist = 0x27a0, + ProposeLevelGrant = 0x2712, + PushSpellToActionBar = 0x2c50, + PvpCredit = 0x2718, + PvpLogData = 0x25b4, + PvpOptionsEnabled = 0x25b7, + PvpSeason = 0x25d4, + QueryBattlePetNameResponse = 0x2705, + QueryCreatureResponse = 0x26fc, + QueryGameObjectResponse = 0x26fd, + QueryGarrisonCreatureNameResponse = 0x292b, + QueryGuildInfoResponse = 0x29e5, + QueryItemTextResponse = 0x2808, + QueryNpcTextResponse = 0x2700, + QueryPageTextResponse = 0x2702, + QueryPetitionResponse = 0x2706, + QueryPetNameResponse = 0x2704, + QueryPlayerNameResponse = 0x2701, + QueryQuestInfoResponse = 0x2a95, + QueryQuestRewardResponse = 0x2842, + QueryTimeResponse = 0x271c, + QuestCompletionNpcResponse = 0x2a81, + QuestConfirmAccept = 0x2a8e, + QuestForceRemoved = 0x2a9b, + QuestGiverInvalidQuest = 0x2a84, + QuestGiverOfferRewardMessage = 0x2a93, + QuestGiverQuestComplete = 0x2a82, + QuestGiverQuestDetails = 0x2a91, + QuestGiverQuestFailed = 0x2a85, + QuestGiverQuestListMessage = 0x2a99, + QuestGiverQuestTurnInFailure = 0x284e, + QuestGiverRequestItems = 0x2a92, + QuestGiverStatus = 0x2a9a, + QuestGiverStatusMultiple = 0x2a90, + QuestLogFull = 0x2a86, + QuestPoiChanged = 0x2a9f, + QuestPoiQueryResponse = 0x2a9c, + QuestPushResult = 0x2a8f, + QuestSpawnTrackingUpdate = 0x2a9e, + QuestUpdateAddCredit = 0x2a8b, + QuestUpdateAddCreditSimple = 0x2a8c, + QuestUpdateAddPvpCredit = 0x2a8d, + QuestUpdateComplete = 0x2a88, + QuestUpdateCompleteBySpell = 0x2a87, + QuestUpdateFailed = 0x2a89, + QuestUpdateFailedTimer = 0x2a8a, + RafEmailEnabledResponse = 0x27cb, + RaidDifficultySet = 0x27ee, + RaidGroupOnly = 0x27f0, + RaidInstanceMessage = 0x2bb4, + RaidMarkersChanged = 0x25ba, + RandomRoll = 0x264e, + RatedBattlefieldInfo = 0x25a7, + ReadyCheckCompleted = 0x260f, + ReadyCheckResponse = 0x260e, + ReadyCheckStarted = 0x260d, + ReadItemResultFailed = 0x27ea, + ReadItemResultOk = 0x27e4, + RealmQueryResponse = 0x26e7, + RecruitAFriendResponse = 0x27cc, + ReferAFriendExpired = 0x2764, + ReferAFriendFailure = 0x26ed, + RefreshComponent = 0x267a, + RefreshSpellHistory = 0x2c2c, + RemoveItemPassive = 0x25c1, + RemoveLossOfControl = 0x2699, + ReplaceTrophyResponse = 0x2805, + ReportPvpPlayerAfkResult = 0x26db, + RequestAddonList = 0x2661, + RequestCemeteryListResponse = 0x259e, + RequestPvpBrawlInfoResponse = 0x25d6, + RequestPvpRewardsResponse = 0x25d5, + ResearchComplete = 0x2586, + ResetAreaTrigger = 0x2642, + ResetCompressionContext = 0x304f, + ResetFailedNotify = 0x26e3, + ResetRangedCombatTimer = 0x2715, + ResetWeeklyCurrency = 0x2574, + RespecWipeConfirm = 0x2627, + RespondInspectAchievements = 0x2571, + ResumeCastBar = 0x2c3d, + ResumeComms = 0x304b, + ResumeToken = 0x25bf, + ResurrectRequest = 0x257f, + ResyncRunes = 0x273f, + RoleChangedInform = 0x258d, + RoleChosen = 0x2a3a, + RolePollInform = 0x258e, + RuneRegenDebug = 0x25c9, + ScenarioBoot = 0x27eb, + ScenarioCompleted = 0x2829, + ScenarioPois = 0x2651, + ScenarioProgressUpdate = 0x264a, + ScenarioSetShouldShowCriteria = 0x2836, + ScenarioSpellUpdate = 0x2835, + ScenarioState = 0x2649, + SceneObjectEvent = 0x25f8, + SceneObjectPetBattleFinalRound = 0x25fd, + SceneObjectPetBattleFinished = 0x25fe, + SceneObjectPetBattleFirstRound = 0x25fa, + SceneObjectPetBattleInitialUpdate = 0x25f9, + SceneObjectPetBattleReplacementsMade = 0x25fc, + SceneObjectPetBattleRoundResult = 0x25fb, + ScriptCast = 0x2c54, + SellResponse = 0x26f1, + SendItemPassives = 0x25c2, + SendKnownSpells = 0x2c2a, + SendRaidTargetUpdateAll = 0x264c, + SendRaidTargetUpdateSingle = 0x264d, + SendSpellCharges = 0x2c2d, + SendSpellHistory = 0x2c2b, + SendUnlearnSpells = 0x2c2e, + ServerFirstAchievement = 0x2bbc, + ServerFirstAchievements = 0x266c, + ServerTime = 0x26ad, + SetupCurrency = 0x2572, + SetupResearchHistory = 0x2585, + SetAiAnimKit = 0x2773, + SetAllTaskProgress = 0x27d4, + SetAnimTier = 0x2777, + SetCurrency = 0x2573, + SetDfFastLaunchResult = 0x2a2e, + SetDungeonDifficulty = 0x26cf, + SetFactionAtWar = 0x273e, + SetFactionNotVisible = 0x276e, + SetFactionStanding = 0x276f, + SetFactionVisible = 0x276d, + SetFlatSpellModifier = 0x2c36, + SetForcedReactions = 0x275e, + SetItemPurchaseData = 0x25b1, + SetLootMethodFailed = 0x2814, + SetMaxWeeklyQuantity = 0x25b8, + SetMeleeAnimKit = 0x2776, + SetMovementAnimKit = 0x2775, + SetPctSpellModifier = 0x2c37, + SetPetSpecialization = 0x2643, + SetPlayerDeclinedNamesResult = 0x2709, + SetPlayHoverAnim = 0x25cd, + SetProficiency = 0x2778, + SetSpellCharges = 0x2c29, + SetTaskComplete = 0x27d5, + SetTimeZoneInformation = 0x26a1, + SetVehicleRecId = 0x2731, + ShowAdventureMap = 0x2832, + ShowBank = 0x26aa, + ShowMailbox = 0x27ec, + ShowNeutralPlayerFactionSelectUi = 0x25f1, + ShowTaxiNodes = 0x26f8, + ShowTradeSkillResponse = 0x27b5, + SocketGems = 0x276a, + SocketGemsFailure = 0x276b, + SortBagsResult = 0x2822, + SorStartExperienceIncomplete = 0x25f3, + SpecializationChanged = 0x25ed, + SpecialMountAnim = 0x26ca, + SpecInvoluntarilyChanged = 0x2759, + SpellAbsorbLog = 0x2c1f, + SpellCategoryCooldown = 0x2c17, + SpellChannelStart = 0x2c34, + SpellChannelUpdate = 0x2c35, + SpellCooldown = 0x2c15, + SpellDamageShield = 0x2c31, + SpellDelayed = 0x2c3e, + SpellDispellLog = 0x2c1a, + SpellEnergizeLog = 0x2c1c, + SpellExecuteLog = 0x2c3f, + SpellFailedOther = 0x2c53, + SpellFailure = 0x2c51, + SpellFailureMessage = 0x2c58, + SpellGo = 0x2c39, + SpellHealAbsorbLog = 0x2c1e, + SpellHealLog = 0x2c1d, + SpellInstakillLog = 0x2c33, + SpellInterruptLog = 0x2c20, + SpellMissLog = 0x2c40, + SpellNonMeleeDamageLog = 0x2c32, + SpellOrDamageImmune = 0x2c2f, + SpellPeriodicAuraLog = 0x2c1b, + SpellPrepare = 0x2c38, + SpellStart = 0x2c3a, + SpiritHealerConfirm = 0x2756, + StandStateUpdate = 0x275d, + StartElapsedTimer = 0x261b, + StartElapsedTimers = 0x261d, + StartLootRoll = 0x2632, + StartMirrorTimer = 0x2750, + StartTimer = 0x25bc, + StopElapsedTimer = 0x261c, + StopMirrorTimer = 0x2752, + StopSpeakerbotSound = 0x27b1, + StreamingMovies = 0x25bb, + SummonCancel = 0x26da, + SummonRaidMemberValidateFailed = 0x258f, + SummonRequest = 0x2762, + SupercededSpells = 0x2c4b, + SuspendComms = 0x304a, + SuspendToken = 0x25be, + TalentsInvoluntarilyReset = 0x2758, + TaxiNodeStatus = 0x26a7, + TextEmote = 0x26a5, + ThreatClear = 0x2711, + ThreatRemove = 0x2710, + ThreatUpdate = 0x270f, + TimeAdjustment = 0x2da1, + TimeSyncRequest = 0x2da0, + TitleEarned = 0x270c, + TitleLost = 0x270d, + TotemCreated = 0x26f4, + TotemMoved = 0x26f5, + TradeStatus = 0x2582, + TradeUpdated = 0x2581, + TrainerBuyFailed = 0x2717, + TrainerList = 0x2716, + TransferAborted = 0x2742, + TransferPending = 0x25e6, + TransmogCollectionUpdate = 0x25c7, + TransmogSetCollectionUpdate = 0x25c8, + TriggerCinematic = 0x280c, + TriggerMovie = 0x26f6, + TurnInPetitionResult = 0x2793, + TutorialFlags = 0x27fe, + TutorialHighlightSpell = 0x283c, + TutorialUnhighlightSpell = 0x283b, + TwitterStatus = 0x2ffd, + UiTime = 0x2755, + UndeleteCharacterResponse = 0x280f, + UndeleteCooldownStatusResponse = 0x2810, + UnlearnedSpells = 0x2c4d, + UpdateAccountData = 0x274a, + UpdateActionButtons = 0x25f6, + UpdateCharacterFlags = 0x2804, + UpdateDungeonEncounterForLoot = 0x2a31, + UpdateExpansionLevel = 0x2665, + UpdateInstanceOwnership = 0x26d2, + UpdateLastInstance = 0x26b3, + UpdateObject = 0x280d, + UpdateTalentData = 0x25ec, + UpdateTaskProgress = 0x27d3, + UpdateWeeklySpellUsage = 0x2c19, + UpdateWorldState = 0x278e, + UserlistAdd = 0x2bb9, + UserlistRemove = 0x2bba, + UserlistUpdate = 0x2bbb, + UseEquipmentSetResult = 0x2794, + VendorInventory = 0x25cb, + VignetteUpdate = 0x27b3, + VoidItemSwapResponse = 0x25e0, + VoidStorageContents = 0x25dd, + VoidStorageFailed = 0x25dc, + VoidStorageTransferChanges = 0x25de, + VoidTransferResult = 0x25df, + WaitQueueFinish = 0x256e, + WaitQueueUpdate = 0x256d, + WardenData = 0x2576, + WargameRequestSuccessfullySentToOpponent = 0x25b5, + Weather = 0x26d1, + WeeklySpellUsage = 0x2c18, + Who = 0x2bae, + WhoIs = 0x26d0, + WorldQuestUpdate = 0x2843, + WorldServerInfo = 0x25c3, + WorldText = 0x282b, + WowTokenAuctionSold = 0x281a, + WowTokenBuyRequestConfirmation = 0x281c, + WowTokenBuyResultConfirmation = 0x281d, + WowTokenCanRedeemForBalanceResult = 0x2852, + WowTokenCanVeteranBuyResult = 0x281b, + WowTokenDistributionGlueUpdate = 0x2815, + WowTokenDistributionUpdate = 0x2816, + WowTokenMarketPriceResponse = 0x2817, + WowTokenRedeemGameTimeUpdated = 0x281e, + WowTokenRedeemRequestConfirmation = 0x281f, + WowTokenRedeemResult = 0x2820, + WowTokenSellRequestConfirmation = 0x2818, + WowTokenSellResultConfirmation = 0x2819, + WowTokenUpdateAuctionableListResponse = 0x2821, + XpGainAborted = 0x25e1, + XpGainEnabled = 0x27ef, + ZoneUnderAttack = 0x2bb5, + + // Opcodes that are not generated automatically + AccountHeirloomUpdate = 0xBADD, // No Client Handler + ItemUpgradeResult = 0xBADD, // No Client Handler + CompressedPacket = 0x3052, + MultiplePackets = 0x3051, + + Max = 0x3FFF, + Unknown = 0xbadd, + None = 0 + } +} diff --git a/Framework/Constants/ObjectConst.cs b/Framework/Constants/ObjectConst.cs new file mode 100644 index 000000000..321d46adf --- /dev/null +++ b/Framework/Constants/ObjectConst.cs @@ -0,0 +1,229 @@ +/* + * Copyright (C) 2012-2017 CypherCore + * + * 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, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +namespace Framework.Constants +{ + public enum TypeId + { + Object = 0, + Item = 1, + Container = 2, + Unit = 3, + Player = 4, + GameObject = 5, + DynamicObject = 6, + Corpse = 7, + AreaTrigger = 8, + SceneObject = 9, + Conversation = 10 + } + + public enum TypeMask + { + Object = 0x01, + Item = 0x02, + Container = Item | 0x04, + Unit = 0x08, + Player = 0x10, + GameObject = 0x20, + DynamicObject = 0x40, + Corpse = 0x80, + AreaTrigger = 0x100, + Sceneobject = 0x200, + Conversation = 0x400, + Seer = Player | Unit | DynamicObject + } + + public enum HighGuid + { + Null = 0, + Uniq = 1, + Player = 2, + Item = 3, + WorldTransaction = 4, + StaticDoor = 5, //NYI + Transport = 6, + Conversation = 7, + Creature = 8, + Vehicle = 9, + Pet = 10, + GameObject = 11, + DynamicObject = 12, + AreaTrigger = 13, + Corpse = 14, + LootObject = 15, + SceneObject = 16, + Scenario = 17, + AIGroup = 18, + DynamicDoor = 19, + ClientActor = 20, //NYI + Vignette = 21, + CallForHelp = 22, + AIResource = 23, + AILock = 24, + AILockTicket = 25, + ChatChannel = 26, + Party = 27, + Guild = 28, + WowAccount = 29, + BNetAccount = 30, + GMTask = 31, + MobileSession = 32, //NYI + RaidGroup = 33, + Spell = 34, + Mail = 35, + WebObj = 36, //NYI + LFGObject = 37, //NYI + LFGList = 38, //NYI + UserRouter = 39, + PVPQueueGroup = 40, + UserClient = 41, + PetBattle = 42, //NYI + UniqUserClient = 43, + BattlePet = 44, + CommerceObj = 45, + ClientSession = 46, + Cast = 47 + } + + public enum NotifyFlags + { + None = 0x00, + AIRelocation = 0x01, + VisibilityChanged = 0x02, + All = 0xFF + } + + public enum TempSummonType + { + TimedOrDeadDespawn = 1, // despawns after a specified time OR when the creature disappears + TimedOrCorpseDespawn = 2, // despawns after a specified time OR when the creature dies + TimedDespawn = 3, // despawns after a specified time + TimedDespawnOOC = 4, // despawns after a specified time after the creature is out of combat + CorpseDespawn = 5, // despawns instantly after death + CorpseTimedDespawn = 6, // despawns after a specified time after death + DeadDespawn = 7, // despawns when the creature disappears + ManualDespawn = 8 // despawns when UnSummon() is called + } + + public enum SummonCategory + { + Wild = 0, + Ally = 1, + Pet = 2, + Puppet = 3, + Vehicle = 4, + Unk = 5 // as of patch 3.3.5a only Bone Spike in Icecrown Citadel + // uses this category + } + + public enum SummonType + { + None = 0, + Pet = 1, + Guardian = 2, + Minion = 3, + Totem = 4, + Minipet = 5, + Guardian2 = 6, + Wild2 = 7, + Wild3 = 8, // Related to phases and DK prequest line (3.3.5a) + Vehicle = 9, + Vehicle2 = 10, // Oculus and Argent Tournament vehicles (3.3.5a) + LightWell = 11, + Jeeves = 12, + Unk13 = 13 + } + + public enum SummonerType + { + Creature = 0, + GameObject = 1, + Map = 2 + } + + public enum GhostVisibilityType + { + Alive = 0x1, + Ghost = 0x2 + } + + public enum StealthType + { + General = 0, + Trap = 1, + + Max = 2 + } + + public enum InvisibilityType + { + General = 0, + Unk1 = 1, + Unk2 = 2, + Trap = 3, + Unk4 = 4, + Unk5 = 5, + Drunk = 6, + Unk7 = 7, + Unk8 = 8, + Unk9 = 9, + Unk10 = 10, + Unk11 = 11, + Unk12 = 12, + Unk13 = 13, + Unk14 = 14, + Unk15 = 15, + Unk16 = 16, + Unk17 = 17, + Unk18 = 18, + Unk19 = 19, + Unk20 = 20, + Unk21 = 21, + Unk22 = 22, + Unk23 = 23, + Unk24 = 24, + Unk25 = 25, + Unk26 = 26, + Unk27 = 27, + Unk28 = 28, + Unk29 = 29, + Unk30 = 30, + Unk31 = 31, + Unk32 = 32, + Unk33 = 33, + Unk34 = 34, + Unk35 = 35, + Unk36 = 36, + Unk37 = 37, + + Max = 38 + } + + public enum ServerSideVisibilityType + { + GM = 0, + Ghost = 1, + } + + public enum SessionFlags + { + None = 0x00, + FromRedirect = 0x01, + HasRedirected = 0x02 + } +} diff --git a/Framework/Constants/OutdoorPvPConst.cs b/Framework/Constants/OutdoorPvPConst.cs new file mode 100644 index 000000000..5f5f389c2 --- /dev/null +++ b/Framework/Constants/OutdoorPvPConst.cs @@ -0,0 +1,40 @@ +/* + * Copyright (C) 2012-2017 CypherCore + * + * 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, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +namespace Framework.Constants +{ + public enum ObjectiveStates + { + Neutral = 0, + Alliance, + Horde, + NeutralAllianceChallenge, + NeutralHordeChallenge, + AllianceHordeChallenge, + HordeAllianceChallenge + } + + public enum OutdoorPvPTypes + { + HellfirePeninsula = 1, + Nagrand = 2, + TerokkarForest = 3, + Zangarmarsh = 4, + Silithus = 5, + Max = 6 + } +} diff --git a/Framework/Constants/PetConst.cs b/Framework/Constants/PetConst.cs new file mode 100644 index 000000000..cc9470de7 --- /dev/null +++ b/Framework/Constants/PetConst.cs @@ -0,0 +1,114 @@ +/* + * Copyright (C) 2012-2017 CypherCore + * + * 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, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +namespace Framework.Constants +{ + public enum CharmType + { + Charm, + Possess, + Vehicle, + Convert + } + + public enum PetType + { + Summon = 0, + Hunter = 1, + Max = 4 + } + + public enum PetSaveMode + { + AsDeleted = -1, // not saved in fact + AsCurrent = 0, // in current slot (with player) + FirstStableSlot = 1, + LastStableSlot = 4, // last in DB stable slot index (including), all higher have same meaning as PET_SAVE_NOT_IN_SLOT + NotInSlot = 100 // for avoid conflict with stable size grow will use 100 + } + + public enum HappinessState + { + UnHappy = 1, + Content = 2, + Happy = 3 + } + + public enum PetSpellState + { + Unchanged = 0, + Changed = 1, + New = 2, + Removed = 3 + } + + public enum PetSpellType + { + Normal = 0, + Family = 1, + Talent = 2 + } + + public enum ActionFeedback + { + None = 0, + PetDead = 1, + NothingToAtt = 2, + CantAttTarget = 3 + } + + public enum PetTalk + { + SpecialSpell = 0, + Attack = 1 + } + + public enum CommandStates + { + Stay = 0, + Follow = 1, + Attack = 2, + Abandon = 3, + MoveTo = 4 + } + + public enum PetNameInvalidReason + { + // custom, not send + Success = 0, + + Invalid = 1, + NoName = 2, + TooShort = 3, + TooLong = 4, + MixedLanguages = 6, + Profane = 7, + Reserved = 8, + ThreeConsecutive = 11, + InvalidSpace = 12, + ConsecutiveSpaces = 13, + RussianConsecutiveSilentCharacters = 14, + RussianSilentCharacterAtBeginningOrEnd = 15, + DeclensionDoesntMatchBaseName = 16 + } + + public enum PetStableinfo + { + Active = 1, + Inactive = 2 + } +} diff --git a/Framework/Constants/PlayerConst.cs b/Framework/Constants/PlayerConst.cs new file mode 100644 index 000000000..9d5d0c632 --- /dev/null +++ b/Framework/Constants/PlayerConst.cs @@ -0,0 +1,771 @@ +/* + * Copyright (C) 2012-2017 CypherCore + * + * 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, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +using System; + +namespace Framework.Constants +{ + public struct PlayerConst + { + public const int MaxTalentTiers = 7; + public const int MaxTalentColumns = 3; + public const int MaxTalentRank = 5; + public const int MinSpecializationLevel = 10; + public const int MaxSpecializations = 4; + public const int MaxMasterySpells = 2; + + public const int ReqPrimaryTreeTalents = 31; + public const int ExploredZonesSize = 256; + public const long MaxMoneyAmount = long.MaxValue; + public const int MaxActionButtons = 132; + public const int MaxActionButtonActionValue = 0x00FFFFFF + 1; + + public const uint KnowTitlesSize = 6; + public const uint MaxTitleIndex = KnowTitlesSize * 64; + + public const int MaxDailyQuests = 25; + public const int QuestsCompletedBitsSize = 1750; + + public static TimeSpan InfinityCooldownDelay = TimeSpan.FromSeconds(Time.Month); // used for set "infinity cooldowns" for spells and check + public const uint infinityCooldownDelayCheck = Time.Month / 2; + public const int MaxPlayerSummonDelay = 2 * Time.Minute; + + public const int TaxiMaskSize = 243; + + // corpse reclaim times + public const int DeathExpireStep = (5 * Time.Minute); + public const int MaxDeathCount = 3; + + public const int MaxCUFProfiles = 5; + + public static uint[] copseReclaimDelay = { 30, 60, 120 }; + + public const int MaxRunes = 7; + public const int MaxRechargingRunes = 3; + + public static uint[] DefaultTalentRowLevels = { 15, 30, 45, 60, 75, 90, 100 }; + public static uint[] DKTalentRowLevels = { 57, 58, 59, 60, 75, 90, 100 }; + //public static uint[] DHTalentRowLevels = { 99, 100, 102, 104, 106, 108, 110 }; + + public const int CustomDisplaySize = 3; + + public const int ArtifactsAllWeaponsGeneralWeaponEquippedPassive = 197886; + + public const byte MaxHonorLevel = 50; + public const byte LevelMinHonor = 110; + } + + public struct MoneyConstants + { + public const int Copper = 1; + public const int Silver = Copper * 100; + public const int Gold = Silver * 100; + } + + public struct PlayerFieldOffsets + { + public const byte BytesOffsetSkinId = 0; + public const byte BytesOffsetFaceId = 1; + public const byte BytesOffsetHairStyleId = 2; + public const byte BytesOffsetHairColorId = 3; + + public const byte Bytes2OffsetCustomDisplayOption = 0; // 3 Bytes + public const byte Bytes2OffsetFacialStyle = 3; + + public const byte Bytes3OffsetPartyType = 0; + public const byte Bytes3OffsetBankBagSlots = 1; + public const byte Bytes3OffsetGender = 2; + public const byte Bytes3OffsetInebriation = 3; + + public const byte Bytes4OffsetPvpTitle = 0; + public const byte Bytes4OffsetArenaFaction = 1; + + public const byte FieldBytesOffsetRafGrantableLevel = 0; + public const byte FieldBytesOffsetActionBarToggles = 1; + public const byte FieldBytesOffsetLifetimeMaxPvpRank = 2; + public const byte FieldBytesOffsetMaxArtifactPowerRanks = 3; + + public const byte FieldBytes2OffsetIgnorePowerRegenPredictionMask = 0; + public const byte FieldBytes2OffsetAuraVision = 1; + + public const byte FieldBytes3OffsetOverrideSpellsId = 2; // Uint16! + public const byte FieldBytes3OffsetOverrideSpellsIdUint16Offset = FieldBytes3OffsetOverrideSpellsId / 2; + + public const byte FieldKillsOffsetTodayKills = 0; + public const byte FieldKillsOffsetYesterdayKills = 1; + + public const byte RestStateXp = 0; + public const byte RestRestedXp = 1; + public const byte RestStateHonor = 2; + public const byte RestRestedHonor = 3; + } + + public enum TradeSlots + { + Invalid = -1, + NonTraded = 6, + TradedCount = 6, + Count = 7 + } + + public enum Tutorials + { + Talent = 0, + Spec = 1, + Glyph = 2, + SpellBook = 3, + Professions = 4, + CoreAbilitites = 5, + PetJournal = 6, + WhatHasChanged = 7, + Max = 8 + } + + public enum TradeStatus + { + PlayerBusy = 0, + Proposed = 1, + Initiated = 2, + Cancelled = 3, + Accepted = 4, + AlreadyTrading = 5, + NoTarget = 6, + Unaccepted = 7, + Complete = 8, + StateChanged = 9, + TooFarAway = 10, + WrongFaction = 11, + Failed = 12, + Petition = 13, + PlayerIgnored = 14, + Stunned = 15, + TargetStunned = 16, + Dead = 17, + TargetDead = 18, + LoggingOut = 19, + TargetLoggingOut = 20, + RestrictedAccount = 21, + WrongRealm = 22, + NotOnTaplist = 23, + CurrencyNotTradable = 24, + NotEnoughCurrency = 25, + } + + public enum RestFlag + { + Tavern = 0x01, + City = 0x02, + FactionArea = 0x04 + } + + public enum ChatFlags + { + None = 0x00, + AFK = 0x01, + DND = 0x02, + GM = 0x04, + Com = 0x08, // Commentator + Dev = 0x10, + BossSound = 0x20, // Plays "RaidBossEmoteWarning" sound on raid boss emote/whisper + Mobile = 0x40 + } + + public enum DrunkenState + { + Sober = 0, + Tipsy = 1, + Drunk = 2, + Smashed = 3 + } + + public enum TalentSpecialization // talent tabs + { + MageArcane = 62, + MageFire = 63, + MageFrost = 64, + PaladinHoly = 65, + PaladinProtection = 66, + PaladinRetribution = 70, + WarriorArms = 71, + WarriorFury = 72, + WarriorProtection = 73, + DruidBalance = 102, + DruidFeralCombat = 103, + DruidRestoration = 104, + DeathKnightBlood = 250, + DeathKnightFrost = 251, + DeathKnightUnholy = 252, + HunterBeastMastery = 253, + HunterMarksman = 254, + HunterSurvival = 255, + PriestDiscipline = 256, + PriestHoly = 257, + PriestShadow = 258, + RogueAssassination = 259, + RogueCombat = 260, + RogueSubtlety = 261, + ShamanElemental = 262, + ShamanEnhancement = 263, + ShamanRestoration = 264, + WarlockAffliction = 265, + WarlockDemonology = 266, + WarlockDestruction = 267, + MonkBrewmaster = 268, + MonkBattledancer = 269, + MonkMistweaver = 270, + DemonHunterHavoc = 577, + DemonHunterVengeance = 581 + } + + public enum SpecResetType + { + Talents = 0, + Specialization = 1, + Glyphs = 2, + PetTalents = 3 + } + + public enum MirrorTimerType + { + Disabled = -1, + Fatigue = 0, + Breath = 1, + Fire = 2, // feign death + Max = 3 + } + + public enum TransferAbortReason + { + None = 0, + Error = 1, + MaxPlayers = 2, // Transfer ed: Instance Is Full + NotFound = 3, // Transfer ed: Instance Not Found + TooManyInstances = 4, // You Have Entered Too Many Instances Recently. + ZoneInCombat = 6, // Unable To Zone In While An Encounter Is In Progress. + InsufExpanLvl = 7, // You Must Have Expansion Installed To Access This Area. + Difficulty = 8, // Difficulty Mode Is Not Available For %S. + UniqueMessage = 9, // Until You'Ve Escaped Tlk'S Grasp, You Cannot Leave This Place! + TooManyRealmInstances = 10, // Additional Instances Cannot Be Launched, Please Try Again Later. + NeedGroup = 11, // Transfer ed: You Must Be In A Raid Group To Enter This Instance + NotFound2 = 12, // Transfer ed: Instance Not Found + NotFound3 = 13, // Transfer ed: Instance Not Found + NotFound4 = 14, // Transfer ed: Instance Not Found + RealmOnly = 15, // All Players In The Party Must Be From The Same Realm To Enter %S. + MapNotAllowed = 16, // Map Cannot Be Entered At This Time. + LockedToDifferentInstance = 18, // You Are Already Locked To %S + AlreadyCompletedEncounter = 19, // You Are Ineligible To Participate In At Least One Encounter In This Instance Because You Are Already Locked To An Instance In Which It Has Been Defeated. + DifficultyNotFound = 22, // Client Writes To Console "Unable To Resolve Requested Difficultyid %U To Actual Difficulty For Map %D" + XrealmZoneDown = 24, // Transfer ed: Cross-Realm Zone Is Down + SoloPlayerSwitchDifficulty = 26, // This Instance Is Already In Progress. You May Only Switch Difficulties From Inside The Instance. + } + + public enum RaidGroupReason + { + None = 0, + Lowlevel = 1, // "You are too low level to enter this instance." + Only = 2, // "You must be in a raid group to enter this instance." + Full = 3, // "The instance is full." + RequirementsUnmatch = 4 // "You do not meet the requirements to enter this instance." + } + + public enum ResetFailedReason + { + Failed = 0, // "Cannot reset %s. There are players still inside the instance." + Zoning = 1, // "Cannot reset %s. There are players in your party attempting to zone into an instance." + Offline = 2 // "Cannot reset %s. There are players offline in your party." + } + + public enum ActivateTaxiReply + { + Ok = 0, + UnspecifiedServerError = 1, + NoSuchPath = 2, + NotEnoughMoney = 3, + TooFarAway = 4, + NoVendorNearby = 5, + NotVisited = 6, + PlayerBusy = 7, + PlayerAlreadyMounted = 8, + PlayerShapeshifted = 9, + PlayerMoving = 10, + SameNode = 11, + NotStanding = 12, + } + + public enum TaxiNodeStatus + { + None = 0, + Learned = 1, + Unlearned = 2, + NotEligible = 3 + } + + public enum PlayerDelayedOperations + { + SavePlayer = 0x01, + ResurrectPlayer = 0x02, + SpellCastDeserter = 0x04, + BGMountRestore = 0x08, ///< Flag to restore mount state after teleport from BG + BGTaxiRestore = 0x10, ///< Flag to restore taxi state after teleport from BG + BGGroupRestore = 0x20, ///< Flag to restore group state after teleport from BG + End + } + + public enum CorpseType + { + Bones = 0, + ResurrectablePVE = 1, + ResurrectablePVP = 2, + Max = 3 + } + + public enum CorpseFlags + { + None = 0x00, + Bones = 0x01, + Unk1 = 0x02, + PvP = 0x04, + HideHelm = 0x08, + HideCloak = 0x10, + Skinnable = 0x20, + FFAPvP = 0x40 + } + + public enum ActionButtonUpdateState + { + UnChanged = 0, + Changed = 1, + New = 2, + Deleted = 3 + } + + public enum ActionButtonType + { + Spell = 0x00, + C = 0x01, // click? + Eqset = 0x20, + Dropdown = 0x30, + Macro = 0x40, + CMacro = C | Macro, + Mount = 0x60, + Item = 0x80 + } + + public enum TeleportToOptions + { + GMMode = 0x01, + NotLeaveTransport = 0x02, + NotLeaveCombat = 0x04, + NotUnSummonPet = 0x08, + Spell = 0x10, + Seamless = 0x20 + } + + /// Type of environmental damages + public enum EnviromentalDamage + { + Exhausted = 0, + Drowning = 1, + Fall = 2, + Lava = 3, + Slime = 4, + Fire = 5, + FallToVoid = 6 // custom case for fall without durability loss + } + + public enum PlayerUnderwaterState + { + None = 0x00, + InWater = 0x01, // terrain type is water and player is afflicted by it + InLava = 0x02, // terrain type is lava and player is afflicted by it + InSlime = 0x04, // terrain type is lava and player is afflicted by it + InDarkWater = 0x08, // terrain type is dark water and player is afflicted by it + + ExistTimers = 0x10 + } + + public struct RuneCooldowns + { + public const int Base = 10000; + public const int Miss = 1500; // cooldown applied on runes when the spell misses + } + + public enum PlayerFlags : uint + { + GroupLeader = 0x01, + AFK = 0x02, + DND = 0x04, + GM = 0x08, + Ghost = 0x10, + Resting = 0x20, + Unk6 = 0x40, + Unk7 = 0x80, + ContestedPVP = 0x100, + InPVP = 0x200, + HideHelm = 0x400, + HideCloak = 0x800, + PlayedLongTime = 0x1000, + PlayedTooLong = 0x2000, + IsOutOfBounds = 0x4000, + Developer = 0x8000, + Unk16 = 0x10000, + TaxiBenchmark = 0x20000, + PVPTimer = 0x40000, + Uber = 0x80000, + Unk20 = 0x100000, + Unk21 = 0x200000, + Commentator2 = 0x400000, + AllowOnlyAbility = 0x800000, + PetBattlesUnlocked = 0x1000000, + NoXPGain = 0x2000000, + Unk26 = 0x4000000, + AutoDeclineGuild = 0x8000000, + GuildLevelEnabled = 0x10000000, + VoidUnlocked = 0x20000000, + Mentor = 0x40000000, + Unk31 = 0x80000000 + } + + public enum PlayerFlagsEx + { + ReagentBankUnlocked = 0x01, + MercenaryMode = 0x02 + } + + public enum CharacterFlags : uint + { + None = 0x00000000, + Unk1 = 0x00000001, + Unk2 = 0x00000002, + CharacterLockedForTransfer = 0x00000004, + Unk4 = 0x00000008, + Unk5 = 0x00000010, + Unk6 = 0x00000020, + Unk7 = 0x00000040, + Unk8 = 0x00000080, + Unk9 = 0x00000100, + Unk10 = 0x00000200, + HideHelm = 0x00000400, + HideCloak = 0x00000800, + Unk13 = 0x00001000, + Ghost = 0x00002000, + Rename = 0x00004000, + Unk16 = 0x00008000, + Unk17 = 0x00010000, + Unk18 = 0x00020000, + Unk19 = 0x00040000, + Unk20 = 0x00080000, + Unk21 = 0x00100000, + Unk22 = 0x00200000, + Unk23 = 0x00400000, + Unk24 = 0x00800000, + LockedByBilling = 0x01000000, + Declined = 0x02000000, + Unk27 = 0x04000000, + Unk28 = 0x08000000, + Unk29 = 0x10000000, + Unk30 = 0x20000000, + Unk31 = 0x40000000, + Unk32 = 0x80000000 + } + + public enum PlayerLocalFlags + { + TrackStealthed = 0x02, + ReleaseTimer = 0x08, // Display time till auto release spirit + NoReleaseWindow = 0x10, // Display no "release spirit" window at all + NoPetBar = 0x00000020, // CGPetInfo::IsPetBarUsed + OverrideCameraMinHeight = 0x00000040, + UsingPartGarrison = 0x00000100, + CanUseObjectsMounted = 0x00000200, + CanVisitPartyGarrison = 0x00000400 + } + + public enum PlayerFieldByte2Flags + { + None = 0x00, + Stealth = 0x20, + InvisibilityGlow = 0x40 + } + + public enum AreaTeams + { + None = 0, + Ally = 2, + Horde = 4, + Any = 6 + } + + public enum RestTypes : byte + { + XP = 0, + Honor = 1, + Max + } + + public enum PlayerRestState + { + Rested = 0x01, + NotRAFLinked = 0x02, + RAFLinked = 0x06 + } + + public enum CharacterCustomizeFlags + { + None = 0x00, + Customize = 0x01, // Name, Gender, Etc... + Faction = 0x10000, // Name, Gender, Faction, Etc... + Race = 0x100000 // Name, Gender, Race, Etc... + } + + public enum CharacterFlags3 : uint + { + LockedByRevokedVasTransaction = 0x100000, + LockedByRevokedCharacterUpgrade = 0x80000000, + } + + public enum CharacterFlags4 + { + TrialBoost = 0x80, + TrialBoostLocked = 0x40000, + } + + public enum TextureSection // TODO: Find a better name. Used in CharSections.dbc + { + BaseSkin = 0, + Face = 1, + FacialHair = 2, + Hair = 3, + Underwear = 4, + } + + public enum AtLoginFlags + { + None = 0x00, + Rename = 0x01, + ResetSpells = 0x02, + ResetTalents = 0x04, + Customize = 0x08, + ResetPetTalents = 0x10, + FirstLogin = 0x20, + ChangeFaction = 0x40, + ChangeRace = 0x80, + Resurrect = 0x100 + } + + public enum PlayerSlots + { + // first slot for item stored (in any way in player items data) + Start = 0, + // last+1 slot for item stored (in any way in player items data) + End = 187, + Count = (End - Start) + } + + public enum PlayerTitle : ulong + { + Disabled = 0x0000000000000000, + None = 0x0000000000000001, + Private = 0x0000000000000002, // 1 + Corporal = 0x0000000000000004, // 2 + SergeantA = 0x0000000000000008, // 3 + MasterSergeant = 0x0000000000000010, // 4 + SergeantMajor = 0x0000000000000020, // 5 + Knight = 0x0000000000000040, // 6 + KnightLieutenant = 0x0000000000000080, // 7 + KnightCaptain = 0x0000000000000100, // 8 + KnightChampion = 0x0000000000000200, // 9 + LieutenantCommander = 0x0000000000000400, // 10 + Commander = 0x0000000000000800, // 11 + Marshal = 0x0000000000001000, // 12 + FieldMarshal = 0x0000000000002000, // 13 + GrandMarshal = 0x0000000000004000, // 14 + Scout = 0x0000000000008000, // 15 + Grunt = 0x0000000000010000, // 16 + SergeantH = 0x0000000000020000, // 17 + SeniorSergeant = 0x0000000000040000, // 18 + FirstSergeant = 0x0000000000080000, // 19 + StoneGuard = 0x0000000000100000, // 20 + BloodGuard = 0x0000000000200000, // 21 + Legionnaire = 0x0000000000400000, // 22 + Centurion = 0x0000000000800000, // 23 + Champion = 0x0000000001000000, // 24 + LieutenantGeneral = 0x0000000002000000, // 25 + General = 0x0000000004000000, // 26 + Warlord = 0x0000000008000000, // 27 + HighWarlord = 0x0000000010000000, // 28 + Gladiator = 0x0000000020000000, // 29 + Duelist = 0x0000000040000000, // 30 + Rival = 0x0000000080000000, // 31 + Challenger = 0x0000000100000000, // 32 + ScarabLord = 0x0000000200000000, // 33 + Conqueror = 0x0000000400000000, // 34 + Justicar = 0x0000000800000000, // 35 + ChampionOfTheNaaru = 0x0000001000000000, // 36 + MercilessGladiator = 0x0000002000000000, // 37 + OfTheShatteredSun = 0x0000004000000000, // 38 + HandOfAdal = 0x0000008000000000, // 39 + VengefulGladiator = 0x0000010000000000, // 40 + } + + public enum PlayerExtraFlags + { + // gm abilities + GMOn = 0x01, + AcceptWhispers = 0x04, + TaxiCheat = 0x08, + GMInvisible = 0x10, + GMChat = 0x20, // Show GM badge in chat messages + + // other states + PVPDeath = 0x100 // store PvP death status until corpse creating. + } + + public enum EquipmentSetUpdateState + { + Unchanged = 0, + Changed = 1, + New = 2, + Deleted = 3 + } + + public enum CUFBoolOptions + { + KeepGroupsTogether, + DisplayPets, + DisplayMainTankAndAssist, + DisplayHealPrediction, + DisplayAggroHighlight, + DisplayOnlyDispellableDebuffs, + DisplayPowerBar, + DisplayBorder, + UseClassColors, + DisplayHorizontalGroups, + DisplayNonBossDebuffs, + DynamicPosition, + Locked, + Shown, + AutoActivate2Players, + AutoActivate3Players, + AutoActivate5Players, + AutoActivate10Players, + AutoActivate15Players, + AutoActivate25Players, + AutoActivate40Players, + AutoActivateSpec1, + AutoActivateSpec2, + AutoActivateSpec3, + AutoActivateSpec4, + AutoActivatePvp, + AutoActivatePve, + + BoolOptionsCount, + } + + public enum DuelCompleteType + { + Interrupted = 0, + Won = 1, + Fled = 2 + } + + public enum ReferAFriendError + { + None = 0, + NotReferredBy = 1, + TargetTooHigh = 2, + InsufficientGrantableLevels = 3, + TooFar = 4, + DifferentFaction = 5, + NotNow = 6, + GrantLevelMaxI = 7, + NoTarget = 8, + NotInGroup = 9, + SummonLevelMaxI = 10, + SummonCooldown = 11, + InsufExpanLvl = 12, + SummonOfflineS = 13, + NoXrealm = 14, + MapIncomingTransferNotAllowed = 15 + } + + public enum PlayerCommandStates + { + None = 0x00, + God = 0x01, + Casttime = 0x02, + Cooldown = 0x04, + Power = 0x08, + Waterwalk = 0x10 + } + + public enum AttackSwingErr + { + CantAttack = 0, + BadFacing = 1, + NotInRange = 2, + DeadTarget = 3 + } + + public enum PlayerLogXPReason + { + Kill = 0, + NoKill = 1 + } + + public enum HeirloomPlayerFlags + { + None = 0x00, + BonusLevel90 = 0x01, + BonusLevel100 = 0x02, + BonusLevel110 = 0x04 + } + + public enum HeirloomItemFlags + { + None = 0x00, + ShowOnlyIfKnown = 0x01, + Pvp = 0x02 + } + + public enum DeclinedNameResult + { + Success = 0, + Error = 1 + } + + public enum BindExtensionState + { + Expired = 0, + Normal = 1, + Extended = 2, + Keep = 255 // special state: keep current save type + } + + public enum TalentLearnResult + { + LearnOk = 0, + FailedUnknown = 1, + FailedNotEnoughTalentsInPrimaryTree = 2, + FailedNoPrimaryTreeSelected = 3, + FailedCantDoThatRightNow = 4, + FailedAffectingCombat = 5, + FailedCantRemoveTalent = 6, + FailedCantDoThatChallengeModeActive = 7, + FailedRestArea = 8 + } +} diff --git a/Framework/Constants/QuestConst.cs b/Framework/Constants/QuestConst.cs new file mode 100644 index 000000000..f6d02eb2f --- /dev/null +++ b/Framework/Constants/QuestConst.cs @@ -0,0 +1,339 @@ +/* + * Copyright (C) 2012-2017 CypherCore + * + * 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, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +using System; + +namespace Framework.Constants +{ + public enum QuestObjectiveType + { + Monster = 0, + Item = 1, + GameObject = 2, + TalkTo = 3, + Currency = 4, + LearnSpell = 5, + MinReputation = 6, + MaxReputation = 7, + Money = 8, + PlayerKills = 9, + AreaTrigger = 10, + WinPetBattleAgainstNpc = 11, + DefeatBattlePet = 12, + WinPvpPetBattles = 13, + CriteriaTree = 14, + ProgressBar = 15, + HaveCurrency = 16, // requires the player to have X currency when turning in but does not consume it + ObtainCurrency = 17 // requires the player to gain X currency after starting the quest but not required to keep it until the end (does not consume) + } + + public enum QuestObjectiveFlags + { + TrackedOnMinimap = 0x01, // Client Displays Large Yellow Blob On Minimap For Creature/Gameobject + Sequenced = 0x02, // Client Will Not See The Objective Displayed Until All Previous Objectives Are Completed + Optional = 0x04, // Not Required To Complete The Quest + Hidden = 0x08, // Never Displayed In Quest Log + HideItemGains = 0x10, // Skip Showing Item Objective Progress + ProgressCountsItemsInInventory = 0x20, // Item Objective Progress Counts Items In Inventory Instead Of Reading It From Updatefields + PartOfProgressBar = 0x40, // Hidden Objective Used To Calculate Progress Bar Percent (Quests Are Limited To A Single Progress Bar Objective) + } + + public struct QuestSlotOffsets + { + public const int Id = 0; + public const int State = 1; + public const int Counts = 2; + public const int Time = 14; + public const int Max = 16; + } + + public enum QuestSlotStateMask + { + None = 0x0000, + Complete = 0x0001, + Fail = 0x0002 + } + + public enum QuestType + { + AutoComplete= 0, + Disabled = 1, + Normal = 2, + Task = 3, + Max = 4 + } + + public enum QuestInfos + { + Group = 1, + Class = 21, + PVP = 41, + Raid = 62, + Dungeon = 81, + WorldEvent = 82, + Legendary = 83, + Escort = 84, + Heroic = 85, + Raid10 = 88, + Raid25 = 89, + Scenario = 98, + Account = 102, + SideQuest = 104 + } + + public enum QuestSort + { + Epic = 1, + HallowsEnd = 21, + Seasonal = 22, + Cataclysm = 23, + Herbalism = 24, + Battlegrounds = 25, + DayOfTheDead = 41, + Warlock = 61, + Warrior = 81, + Shaman = 82, + Fishing = 101, + Blacksmithing = 121, + Paladin = 141, + Mage = 161, + Rogue = 162, + Alchemy = 181, + Leatherworking = 182, + Engineering = 201, + TreasureMap = 221, + Tournament = 241, + Hunter = 261, + Priest = 262, + Druid = 263, + Tailoring = 264, + Special = 284, + Cooking = 304, + FirstAid = 324, + Legendary = 344, + DarkmoonFaire = 364, + AhnQirajWar = 365, + LunarFestival = 366, + Reputation = 367, + Invasion = 368, + Midsummer = 369, + Brewfest = 370, + Inscription = 371, + DeathKnight = 372, + Jewelcrafting = 373, + Noblegarden = 374, + PilgrimsBounty = 375, + LoveIsInTheAir = 376, + Archaeology = 377, + ChildrensWeek = 378, + FirelandsInvasion = 379, + TheZandalari = 380, + ElementalBonds = 381, + PandarenBrewmaster = 391, + Scenario = 392, + BattlePets = 394, + Monk = 395, + Landfall = 396, + PandarenCampaign = 397, + Riding = 398, + BrawlersGuild = 399, + ProvingGrounds = 400, + GarrisonCampaign = 401, + AssaultOnTheDarkPortal = 402, + GarrisonSupport = 403, + Logging = 404, + Pickpocketing = 405 + } + + public enum QuestFailedReasons + { + None = 0, + FailedLowLevel = 1, // "You Are Not High Enough Level For That Quest."" + FailedWrongRace = 6, // "That Quest Is Not Available To Your Race." + AlreadyDone = 7, // "You Have Completed That Daily Quest Today." + OnlyOneTimed = 12, // "You Can Only Be On One Timed Quest At A Time" + AlreadyOn1 = 13, // "You Are Already On That Quest" + FailedExpansion = 16, // "This Quest Requires An Expansion Enabled Account." + AlreadyOn2 = 18, // "You Are Already On That Quest" + FailedMissingItems = 21, // "You Don'T Have The Required Items With You. Check Storage." + FailedNotEnoughMoney = 23, // "You Don'T Have Enough Money For That Quest" + FailedCais = 24, // "You Cannot Complete Quests Once You Have Reached Tired Time" + AlreadyDoneDaily = 26, // "You Have Completed That Daily Quest Today." + FailedSpell = 28, // "You Haven'T Learned The Required Spell." + HasInProgress = 30 // "Progress Bar Objective Not Completed" + } + + public enum QuestPushReason + { + Success = 0, + Invalid = 1, + Accepted = 2, + Declined = 3, + Busy = 4, + Dead = 5, + LogFull = 6, + OnQuest = 7, + AlreadyDone = 8, + NotDaily = 9, + TimerExpired = 10, + NotInParty = 11, + DifferentServerDaily = 12, + NotAllowed = 13 + } + + public enum QuestTradeSkill + { + None = 0, + Alchemy = 1, + Blacksmithing = 2, + Cooking = 3, + Enchanting = 4, + Engineering = 5, + Firstaid = 6, + Herbalism = 7, + Leatherworking = 8, + Poisons = 9, + Tailoring = 10, + Mining = 11, + Fishing = 12, + Skinning = 13, + Jewelcrafting = 14 + } + + public enum QuestStatus + { + None = 0, + Complete = 1, + //Unavailable = 2, + Incomplete = 3, + //Available = 4, + Failed = 5, + Rewarded = 6, // Not Used In Db + Max + } + + public enum QuestGiverStatus + { + None = 0x000, + Unk = 0x001, + Unavailable = 0x002, + LowLevelAvailable = 0x004, + LowLevelRewardRep = 0x008, + LowLevelAvailableRep = 0x010, + Incomplete = 0x020, + RewardRep = 0x040, + AvailableRep = 0x080, + Available = 0x100, + Reward2 = 0x200, // No Yellow Dot On Minimap + Reward = 0x400, // Yellow Dot On Minimap + + // Custom value meaning that script call did not return any valid quest status + ScriptedNoStatus = 0x1000 + } + + [Flags] + public enum QuestFlags + { + None = 0x00, + StayAlive = 0x01, // Not Used Currently + PartyAccept = 0x02, // Not Used Currently. If Player In Party, All Players That Can Accept This Quest Will Receive Confirmation Box To Accept Quest Cmsg_Quest_Confirm_Accept/Smsg_Quest_Confirm_Accept + Exploration = 0x04, // Not Used Currently + Sharable = 0x08, // Can Be Shared: Player.Cansharequest() + HasCondition = 0x10, // Not Used Currently + HideRewardPoi = 0x20, // Not Used Currently: Unsure Of Content + Raid = 0x40, // Can be completed while in raid + TBC = 0x80, // Not Used Currently: Available If Tbc Expansion Enabled Only + NoMoneyFromXp = 0x100, // Not Used Currently: Experience Is Not Converted To Gold At Max Level + HiddenRewards = 0x200, // Items And Money Rewarded Only Sent In Smsg_Questgiver_Offer_Reward (Not In Smsg_Questgiver_Quest_Details Or In Client Quest Log(Smsg_Quest_Query_Response)) + Tracking = 0x400, // These Quests Are Automatically Rewarded On Quest Complete And They Will Never Appear In Quest Log Client Side. + DeprecateReputation = 0x800, // Not Used Currently + Daily = 0x1000, // Used To Know Quest Is Daily One + Pvp = 0x2000, // Having This Quest In Log Forces Pvp Flag + Unavailable = 0x4000, // Used On Quests That Are Not Generically Available + Weekly = 0x8000, + AutoComplete = 0x10000, // Quests with this flag player submit automatically by special button in player gui + DisplayItemInTracker = 0x20000, // Displays Usable Item In Quest Tracker + ObjText = 0x40000, // Use Objective Text As Complete Text + AutoAccept = 0x80000, // The client recognizes this flag as auto-accept. However, NONE of the current quests (3.3.5a) have this flag. Maybe blizz used to use it, or will use it in the future. + Unk1 = 0x100000, // + AutoTake = 0x200000, // Automatically Suggestion Of Accepting Quest. Not From Npc. + //Unk2 = 0x400000, + //Unk3 = 0x800000, // Found In Quest 14069 + //Unk4 = 0x1000000, + // ... 4.X Added Flags Up To 0x80000000 - All Unknown For Now + } + + // last checked in 19802 + [Flags] + public enum QuestFlagsEx + { + None = 0x00, + KeepAdditionalItems = 0x01, + SuppressGossipComplete = 0x02, + SuppressGossipAccept = 0x04, + DisallowPlayerAsQuestgiver = 0x08, + DisplayClassChoiceRewards = 0x10, + DisplaySpecChoiceRewards = 0x20, + RemoveFromLogOnPeriodicReset = 0x40, + AccountLevelQuest = 0x80, + LegendaryQuest = 0x100, + NoGuildXp = 0x200, + ResetCacheOnAccept = 0x400, + NoAbandonOnceAnyObjectiveComplete = 0x800, + RecastAcceptSpellOnLogin = 0x1000, + UpdateZoneAuras = 0x2000, + NoCreditForProxy = 0x4000, + DisplayAsDailyQuest = 0x8000, + PartOfQuestLine = 0x10000, + QuestForInternalBuildsOnly = 0x20000, + SuppressSpellLearnTextLine = 0x40000, + DisplayHeaderAsObjectiveForTasks = 0x80000, + GarrisonNonOwnersAllowed = 0x100000, + RemoveQuestOnWeeklyReset = 0x200000, + SuppressFarewellAudioAfterQuestAccept = 0x0400000, + RewardsBypassWeeklyCapsAndSeasonTotal = 0x0800000, + ClearProgressOfCriteriaTreeObjectivesOnAccept = 0x1000000 + } + + public enum QuestSpecialFlags + { + None = 0x00, + // Flags For Set Specialflags In Db If Required But Used Only At Server + Repeatable = 0x001, + ExplorationOrEvent = 0x002, // If Required Area Explore, Spell Spell_Effect_Quest_Complete Casting, Table `*_Script` Command Script_Command_Quest_Explored Use, Set From Script) + AutoAccept = 0x004, // Quest Is To Be Auto-Accepted. + DfQuest = 0x008, // Quest Is Used By Dungeon Finder. + Monthly = 0x010, // Quest Is Reset At The Begining Of The Month + Cast = 0x20, // Set by 32 in SpecialFlags in DB if the quest requires RequiredOrNpcGo killcredit but NOT kill (a spell cast) + // Room For More Custom Flags + + DbAllowed = Repeatable | ExplorationOrEvent | AutoAccept | DfQuest | Monthly | Cast, + + Deliver = 0x080, // Internal Flag Computed Only + Speakto = 0x100, // Internal Flag Computed Only + Kill = 0x200, // Internal Flag Computed Only + Timed = 0x400, // Internal Flag Computed Only + PlayerKill = 0x800 // Internal Flag Computed Only + } + + public enum QuestSaveType + { + Default = 0, + Delete, + ForceDelete + } +} diff --git a/Framework/Constants/ScriptsConst.cs b/Framework/Constants/ScriptsConst.cs new file mode 100644 index 000000000..b0572e25a --- /dev/null +++ b/Framework/Constants/ScriptsConst.cs @@ -0,0 +1,78 @@ +/* + * Copyright (C) 2012-2017 CypherCore + * + * 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, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +namespace Framework.Constants +{ + // AuraScript interface - enum used for runtime checks of script function calls + public enum AuraScriptHookType + { + None = 0, + Registration, + Loading, + Unloading, + EffectApply, + EffectAfterApply, + EffectRemove, + EffectAfterRemove, + EffectPeriodic, + EffectUpdatePeriodic, + EffectCalcAmount, + EffectCalcPeriodic, + EffectCalcSpellmod, + EffectAbsorb, + EffectAfterAbsorb, + EffectManaShield, + EffectAfterManaShield, + EffectSplit, + CheckAreaTarget, + Dispel, + AfterDispel, + // Spell Proc Hooks + CheckProc, + PrepareProc, + Proc, + EffectProc, + EffectAfterProc, + AfterProc, + //Apply, + //Remove + } + + // SpellScript interface - enum used for runtime checks of script function calls + public enum SpellScriptHookType + { + None = 0, + Registration, + Loading, + Unloading, + Launch, + LaunchTarget, + EffectHit, + EffectHitTarget, + EffectSuccessfulDispel, + BeforeHit, + OnHit, + AfterHit, + ObjectAreaTargetSelect, + ObjectTargetSelect, + DestinationTargetSelect, + CheckCast, + BeforeCast, + OnCast, + AfterCast + } +} diff --git a/Framework/Constants/ServiceHash.cs b/Framework/Constants/ServiceHash.cs new file mode 100644 index 000000000..41529a315 --- /dev/null +++ b/Framework/Constants/ServiceHash.cs @@ -0,0 +1,61 @@ +/* + * Copyright (C) 2012-2017 CypherCore + * + * 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, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +namespace Framework.Constants +{ + public enum OriginalHash : uint + { + AccountService = 0x62DA0891u, + AccountListener = 0x54DFDA17u, + AuthenticationListener = 0x71240E35u, + AuthenticationService = 0xDECFC01u, + ChallengeService = 0xDBBF6F19u, + ChallengeListener = 0xBBDA171Fu, + ChannelService = 0xB732DB32u, + ChannelListener = 0xBF8C8094u, + ConnectionService = 0x65446991u, + FriendsService = 0xA3DDB1BDu, + FriendsListener = 0x6F259A13u, + GameUtilitiesService = 0x3FC1274Du, + PresenceService = 0xFA0796FFu, + ReportService = 0x7CAF61C9u, + ResourcesService = 0xECBE75BAu, + UserManagerService = 0x3E19268Au, + UserManagerListener = 0xBC872C22u + } + + public enum NameHash : uint + { + AccountService = 0x1E4DC42Fu, + AccountListener = 0x7807483Cu, + AuthenticationListener = 0x4DA86228u, + AuthenticationService = 0xFF5A6AC3u, + ChallengeService = 0x71BB6833u, + ChallengeListener = 0xC6D90AB8u, + ChannelService = 0xA913A87Bu, + ChannelListener = 0xDA660990u, + ConnectionService = 0x2782094Bu, + FriendsService = 0xABDFED63u, + FriendsListener = 0xA6717548u, + GameUtilitiesService = 0x51923A28u, + PresenceService = 0xD8F94B3Bu, + ReportService = 0x724F5F47u, + ResourcesService = 0x4B104C53u, + UserManagerService = 0x8EE5694Eu, + UserManagerListener = 0xB3426BB3u + } +} diff --git a/Framework/Constants/SharedConst.cs b/Framework/Constants/SharedConst.cs new file mode 100644 index 000000000..a0d59df35 --- /dev/null +++ b/Framework/Constants/SharedConst.cs @@ -0,0 +1,2316 @@ +/* + * Copyright (C) 2012-2017 CypherCore + * + * 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, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +using System; + +namespace Framework.Constants +{ + public class SharedConst + { + /// + /// CliDB Const + /// + public const int GTMaxLevel = 100; // All Gt* DBC store data for 100 levels, some by 100 per class/race + public const int GTMaxRating = 32; // gtOCTClassCombatRatingScalar.dbc stores data for 32 ratings, look at MAX_COMBAT_RATING for real used amount + public const int ReputationCap = 42999; + public const int ReputationBottom = -42000; + public const int MaxMailItems = 12; + public const int MaxDeclinedNameCases = 5; + public const int MaxHolidayDurations = 10; + public const int MaxHolidayDates = 16; + public const int MaxHolidayFlags = 10; + public const int DefaultMaxLevel = 110; + public const int MaxLevel = 110; + public const int StrongMaxLevel = 255; + public const int MaxOverrideSpell = 10; + public const int MaxWorldMapOverlayArea = 4; + public const int MaxMountCapabilities = 24; + public const int MaxLockCase = 8; + + /// + /// BattlePets Const + /// + public const int MaxPetBattleSlots = 3; + public const int MaxBattlePetsPerSpecies = 3; + public const int BattlePetCageItemId = 82800; + public const int DefaultSummonBattlePetSpell = 118301; + + /// + /// Lfg Const + /// + public const uint LFGTimeRolecheck = 45 * Time.InMilliseconds; + public const uint LFGTimeBoot = 120; + public const uint LFGTimeProposal = 45; + public const uint LFGQueueUpdateInterval = 15 * Time.InMilliseconds; + public const uint LFGSpellDungeonCooldown = 71328; + public const uint LFGSpellDungeonDeserter = 71041; + public const uint LFGSpellLuckOfTheDraw = 72221; + public const uint LFGKickVotesNeeded = 3; + public const byte LFGMaxKicks = 3; + public const int LFGTanksNeeded = 1; + public const int LFGHealersNeeded = 1; + public const int LFGDPSNeeded = 3; + + + /// + /// Loot Const + /// + public const int MaxNRLootItems = 16; + public const int MaxNRQuestItems = 32; + + /// + /// Movement Const + /// + public const double gravity = 19.29110527038574; + public const float terminalVelocity = 60.148003f; + public const float terminalSafefallVelocity = 7.0f; + public const float terminal_length = (float)((terminalVelocity * terminalVelocity) / (2.0f * gravity)); + public const float terminal_safeFall_length = (float)((terminalSafefallVelocity * terminalSafefallVelocity) / (2.0f * gravity)); + public const float terminal_fallTime = (float)(terminalVelocity / gravity); // the time that needed to reach terminalVelocity + public const float terminal_safeFall_fallTime = (float)(terminalSafefallVelocity / gravity); // the time that needed to reach terminalVelocity with safefall + + + /// + /// Vehicle Const + /// + public const int MaxSpellVehicle = 6; + public const int VehicleSpellRideHardcoded = 46598; + public const int VehicleSpellParachute = 45472; + + /// + /// Quest Const + /// + public const int MaxQuestLogSize = 25; + public const int MaxQuestCounts = 24; + + public const int QuestItemDropCount = 4; + public const int QuestRewardChoicesCount = 6; + public const int QuestRewardItemCount = 4; + public const int QuestDeplinkCount = 10; + public const int QuestRewardReputationsCount = 5; + public const int QuestEmoteCount = 4; + public const int QuestRewardCurrencyCount = 4; + public const int QuestRewardDisplaySpellCount = 3; + + /// + /// Smart AI Const + /// + public const int SmartEventParamCount = 4; + public const int SmartActionParamCount = 6; + public const uint SmartSummonCounter = 0xFFFFFF; + public const uint SmartEscortTargets = 0xFFFFFF; + + /// + /// BGs / Arena Const + /// + public const int BGTeamsCount = 2; + public const uint CountOfPlayersToAverageWaitTime = 10; + public const uint MaxPlayerBGQueues = 2; + public const uint BGAwardArenaPointsMinLevel = 71; + public const int ArenaTimeLimitPointsLoss = -16; + public const int MaxArenaSlot = 3; + + /// + /// Void Storage Const + /// + public const uint VoidStorageUnlockCost = 100 * MoneyConstants.Gold; + public const uint VoidStorageStoreItemCost = 10 * MoneyConstants.Gold; + public const uint VoidStorageMaxDeposit = 9; + public const uint VoidStorageMaxWithdraw = 9; + public const byte VoidStorageMaxSlot = 160; + + /// + /// Misc Const + /// + public const uint CalendarMaxInvites = 100; + public const uint CalendarDefaultResponseTime = 946684800; // 01/01/2000 00:00:00 + public const LocaleConstant DefaultLocale = LocaleConstant.enUS; + public const int MaxAccountTutorialValues = 8; + public const int MinAuctionTime = (12 * Time.Hour); + public const int MaxConditionTargets = 3; + + /// + /// Unit Const + /// + public const float BaseMinDamage = 1.0f; + public const float BaseMaxDamage = 2.0f; + public const int BaseAttackTime = 2000; + public const int MaxSummonSlot = 7; + public const int MaxTotemSlot = 5; + public const int MaxGameObjectSlot = 4; + public const float MaxAggroRadius = 45.0f; // yards + public const int MaxAggroResetTime = 10; + public const int MaxVehicleSeats = 8; + public const int AttackDisplayDelay = 200; + public const float MaxPlayerStealthDetectRange = 30.0f; // max distance for detection targets by player + public const uint MaxEquipmentItems = 3; + + /// + /// Creature Const + /// + public const int MaxGossipMenuItems = 64; // client supported items unknown, but provided number must be enough + public const int DefaultGossipMessage = 0xFFFFFF; + public const int MaxGossipTextEmotes = 3; + public const int MaxNpcTextOptions = 8; + public const int MaxCreatureBaseHp = 4; + public const int MaxCreatureSpells = 8; + public const byte MaxVendorItems = 150; + public const int CreatureAttackRangeZ = 3; + public const int MaxCreatureKillCredit = 2; + public const int MaxCreatureDifficulties = 3; + public const int MaxCreatureSpellDataSlots = 4; + public const int MaxCreatureNames = 4; + public const int MaxCreatureModelIds = 4; + public const int MaxTrainerspellAbilityReqs = 3; + public const int CreatureRegenInterval = 2 * Time.InMilliseconds; + public const int CreatureNoPathEvadeTime = 5 * Time.InMilliseconds; + public const int PetFocusRegenInterval = 4 * Time.InMilliseconds; + public const int BoundaryVisualizeCreature = 15425; + public const float BoundaryVisualizeCreatureScale = 0.25f; + public const int BoundaryVisualizeStepSize = 1; + public const int BoundaryVisualizeFailsafeLimit = 750; + public const int BoundaryVisualizeSpawnHeight = 5; + + /// + /// GameObject Const + /// + public const int MaxGOData = 33; + public const uint MaxTransportStopFrames = 9; + + /// + /// AreaTrigger Const + /// + public const int MaxAreatriggerEntityData = 6; + public const int MaxAreatriggerScale = 7; + + /// + /// Pet Const + /// + public const int MaxPetStables = 4; + public const float PetFollowDist = 1.0f; + public const float PetFollowAngle = MathFunctions.PiOver2; + public const int MaxSpellCharm = 4; + public const int ActionBarIndexStart = 0; + public const byte ActionBarIndexPetSpellStart = 3; + public const int ActionBarIndexPetSpellEnd = 7; + public const int ActionBarIndexEnd = 10; + public const int MaxSpellControlBar = 10; + public const int MaxPetTalentRank = 3; + public const int ActionBarIndexMax = (ActionBarIndexEnd - ActionBarIndexStart); + + + /// + /// Object Const + /// + public const float DefaultWorldObjectSize = 0.388999998569489f; // player size, also currently used (correctly?) for any non Unit world objects + public const float AttackDistance = 5.0f; + public const float DefaultCombatReach = 1.5f; + public const float MinMeleeReach = 2.0f; + public const float NominalMeleeRange = 5.0f; + public const float MeleeRange = NominalMeleeRange - MinMeleeReach * 2; //center to center for players + public const float InspectDistance = 28.0f; + public const float ContactDistance = 0.5f; + public const float InteractionDistance = 5.0f; + public const float MaxVisibilityDistance = MapConst.SizeofGrids; // max distance for visible objects + public const float SightRangeUnit = 50.0f; + public const float DefaultVisibilityDistance = 90.0f; // default visible distance, 90 yards on continents + public const float DefaultVisibilityInstance = 170.0f; // default visible distance in instances, 170 yards + public const float DefaultVisibilityBGAreans = 533.0f; // default visible distance in BG/Arenas, roughly 533 yards + public const int DefaultVisibilityNotifyPeriod = 1000; + + public const int WorldTrigger = 12999; + + public static float[] baseMoveSpeed = + { + 2.5f, // MOVE_WALK + 7.0f, // MOVE_RUN + 4.5f, // MOVE_RUN_BACK + 4.722222f, // MOVE_SWIM + 2.5f, // MOVE_SWIM_BACK + 3.141594f, // MOVE_TURN_RATE + 7.0f, // MOVE_FLIGHT + 4.5f, // MOVE_FLIGHT_BACK + 3.14f // MOVE_PITCH_RATE + }; + + public static float[] playerBaseMoveSpeed = + { + 2.5f, // MOVE_WALK + 7.0f, // MOVE_RUN + 4.5f, // MOVE_RUN_BACK + 4.722222f, // MOVE_SWIM + 2.5f, // MOVE_SWIM_BACK + 3.141594f, // MOVE_TURN_RATE + 7.0f, // MOVE_FLIGHT + 4.5f, // MOVE_FLIGHT_BACK + 3.14f // MOVE_PITCH_RATE + }; + + //Todo move these else where + /// + /// Method Const + /// + public static SpellSchools GetFirstSchoolInMask(SpellSchoolMask mask) + { + for (SpellSchools i = 0; i < SpellSchools.Max; ++i) + if (mask.HasAnyFlag((SpellSchoolMask)(1 << (int)i))) + return i; + + return SpellSchools.Normal; + } + public static SkillType SkillByQuestSort(int sort) + { + switch ((QuestSort)sort) + { + case QuestSort.Herbalism: + return SkillType.Herbalism; + case QuestSort.Fishing: + return SkillType.Fishing; + case QuestSort.Blacksmithing: + return SkillType.Blacksmithing; + case QuestSort.Alchemy: + return SkillType.Alchemy; + case QuestSort.Leatherworking: + return SkillType.Leatherworking; + case QuestSort.Engineering: + return SkillType.Engineering; + case QuestSort.Tailoring: + return SkillType.Tailoring; + case QuestSort.Cooking: + return SkillType.Cooking; + case QuestSort.FirstAid: + return SkillType.FirstAid; + case QuestSort.Jewelcrafting: + return SkillType.Jewelcrafting; + case QuestSort.Inscription: + return SkillType.Inscription; + case QuestSort.Archaeology: + return SkillType.Archaeology; + } + return SkillType.None; + } + public static SkillType SkillByLockType(LockType locktype) + { + switch (locktype) + { + case LockType.Herbalism: + return SkillType.Herbalism; + case LockType.Mining: + return SkillType.Mining; + case LockType.Fishing: + return SkillType.Fishing; + case LockType.Inscription: + return SkillType.Inscription; + case LockType.Archaelogy: + return SkillType.Archaeology; + case LockType.LumberMill: + return SkillType.Logging; + } + return SkillType.None; + } + } + + public struct PhaseMasks + { + public const uint Normal = 0x00000001; + public const uint Anywhere = 0xFFFFFFFF; + } + + public enum LocaleConstant + { + enUS = 0, + koKR = 1, + frFR = 2, + deDE = 3, + zhCN = 4, + zhTW = 5, + esES = 6, + esMX = 7, + ruRU = 8, + None = 9, + ptBR = 10, + itIT = 11, + Total = 12, + + Max = 11, + OldTotal = 9// @todo convert in simple system + } + + public enum ComparisionType + { + EQ = 0, + High, + Low, + HighEQ, + LowEQ, + Max + } + + public enum XPColorChar + { + Red, + Orange, + Yellow, + Green, + Gray + } + public enum ContentLevels + { + Content_1_60 = 0, + Content_61_70 = 1, + Content_71_80 = 2, + Content_81_85 = 3, + Max + } + + public struct TeamId + { + public const int Alliance = 0; + public const int Horde = 1; + public const int Neutral = 2; + } + + public enum Team + { + Horde = 67, + Alliance = 469, + //TEAM_STEAMWHEEDLE_CARTEL = 169, // not used in code + //TEAM_ALLIANCE_FORCES = 891, + //TEAM_HORDE_FORCES = 892, + //TEAM_SANCTUARY = 936, + //TEAM_OUTLAND = 980, + Other = 0 // if ReputationListId > 0 && Flags != FACTION_FLAG_TEAM_HEADER + } + + public enum FactionMasks + { + Player = 1, // any player + Alliance = 2, // player or creature from alliance team + Horde = 4, // player or creature from horde team + Monster = 8 // aggressive creature from monster team + // if none flags set then non-aggressive creature + } + public enum FactionTemplateFlags + { + PVP = 0x800, // flagged for PvP + ContestedGuard = 0x1000, // faction will attack players that were involved in PvP combats + HostileByDefault = 0x2000 + } + public enum ReputationRank + { + None = -1, + Hated = 0, + Hostile = 1, + Unfriendly = 2, + Neutral = 3, + Friendly = 4, + Honored = 5, + Revered = 6, + Exalted = 7, + Max = 8, + Min = Hated + } + public enum ReputationSource + { + Kill, + Quest, + DailyQuest, + WeeklyQuest, + MonthlyQuest, + RepeatableQuest, + Spell + } + public enum FactionFlags + { + None = 0x00, // no faction flag + Visible = 0x01, // makes visible in client (set or can be set at interaction with target of this faction) + AtWar = 0x02, // enable AtWar-button in client. player controlled (except opposition team always war state), Flag only set on initial creation + Hidden = 0x04, // hidden faction from reputation pane in client (player can gain reputation, but this update not sent to client) + InvisibleForced = 0x08, // always overwrite FACTION_FLAG_VISIBLE and hide faction in rep.list, used for hide opposite team factions + PeaceForced = 0x10, // always overwrite FACTION_FLAG_AT_WAR, used for prevent war with own team factions + Inactive = 0x20, // player controlled, state stored in characters.data (CMSG_SET_FACTION_INACTIVE) + Rival = 0x40, // flag for the two competing outland factions + Special = 0x80 // horde and alliance home cities and their northrend allies have this flag + } + + public enum Gender : sbyte + { + Unknown = -1, + Male = 0, + Female = 1, + None = 2 + } + + public enum Class + { + None = 0, + Warrior = 1, + Paladin = 2, + Hunter = 3, + Rogue = 4, + Priest = 5, + Deathknight = 6, + Shaman = 7, + Mage = 8, + Warlock = 9, + Monk = 10, + Druid = 11, + DemonHunter = 12, + Max = 13, + + ClassMaskAllPlayable = ((1 << (Warrior - 1)) | (1 << (Paladin - 1)) | (1 << (Hunter - 1)) | + (1 << (Rogue - 1)) | (1 << (Priest - 1)) | (1 << (Deathknight - 1)) | (1 << (Shaman - 1)) | + (1 << (Mage - 1)) | (1 << (Warlock - 1)) | (1 << (Monk - 1)) | (1 << (Druid - 1)) | (1 << (DemonHunter - 1))), + + ClassMaskAllCreatures = ((1 << (Warrior - 1)) | (1 << (Paladin - 1)) | (1 << (Rogue - 1)) | (1 << (Mage - 1))), + + ClassMaskWandUsers = ((1 << (Priest - 1)) | (1 << (Mage - 1)) | (1 << (Warlock - 1))) + } + + public enum Race + { + None = 0, + Human = 1, + Orc = 2, + Dwarf = 3, + NightElf = 4, + Undead = 5, + Tauren = 6, + Gnome = 7, + Troll = 8, + Goblin = 9, + BloodElf = 10, + Draenei = 11, + //FelOrc = 12, + //Naga = 13, + //Broken = 14, + //Skeleton = 15, + //Vrykul = 16, + //Tuskarr = 17, + //ForestTroll = 18, + //Taunka = 19, + //NorthrendSkeleton = 20, + //IceTroll = 21, + Worgen = 22, + //HumanGilneas = 23, + PandarenNeutral = 24, + PandarenAlliance = 25, + PandarenHorde = 26, + Max = 27, + + RaceMaskAllPlayable = ((1 << (Human - 1)) | (1 << (Orc - 1)) | (1 << (Dwarf - 1)) | (1 << (NightElf - 1)) | (1 << (Undead - 1)) + | (1 << (Tauren - 1)) | (1 << (Gnome - 1)) | (1 << (Troll - 1)) | (1 << (BloodElf - 1)) | (1 << (Draenei - 1)) + | (1 << (Goblin - 1)) | (1 << (Worgen - 1)) | (1 << (PandarenNeutral - 1)) | (1 << (PandarenAlliance - 1)) | (1 << (PandarenHorde - 1))), + + RaceMaskAlliance = ((1 << (Human - 1)) | (1 << (Dwarf - 1)) | (1 << (NightElf - 1)) | + (1 << (Gnome - 1)) | (1 << (Draenei - 1)) | (1 << (Worgen - 1)) | (1 << (PandarenAlliance - 1))), + + RaceMaskHorde = RaceMaskAllPlayable & ~RaceMaskAlliance + } + public enum Expansion + { + LevelCurrent = -1, + Classic = 0, + BurningCrusade = 1, + WrathOfTheLichKing = 2, + Cataclysm = 3, + MistsOfPandaria = 4, + WarlordsOfDraenor = 5, + Legion = 6, + Max + } + public enum PowerType : byte + { + Mana = 0, + Rage = 1, + Focus = 2, + Energy = 3, + ComboPoints = 4, + Runes = 5, + RunicPower = 6, + SoulShards = 7, + LunarPower = 8, + HolyPower = 9, + AlternatePower = 10, // Used in some quests + Maelstrom = 11, + Chi = 12, + Insanity = 13, + BurningEmbers = 14, + DemonicFury = 15, + ArcaneCharges = 16, + Fury = 17, + Pain = 18, + Max = 19, + All = 127, // default for class? + Health = 0xFE, // (-2 as signed value) + MaxPerClass = 6 + } + + public enum Stats + { + Strength = 0, + Agility = 1, + Stamina = 2, + Intellect = 3, + Max = 4 + } + + public enum TrainerType + { + Class = 0, + Mounts = 1, + Tradeskills = 2, + Pets = 3 + } + public enum TrainerSpellState + { + Gray = 0, + Green = 1, + Red = 2, + GreenDisabled = 10 + } + + public enum ChatMsg : uint + { + Addon = 0xffffffff, // -1 + System = 0x00, + Say = 0x01, + Party = 0x02, + Raid = 0x03, + Guild = 0x04, + Officer = 0x05, + Yell = 0x06, + Whisper = 0x07, + WhisperForeign = 0x08, + WhisperInform = 0x09, + Emote = 0x0a, + TextEmote = 0x0b, + MonsterSay = 0x0c, + MonsterParty = 0x0d, + MonsterYell = 0x0e, + MonsterWhisper = 0x0f, + MonsterEmote = 0x10, + Channel = 0x11, + ChannelJoin = 0x12, + ChannelLeave = 0x13, + ChannelList = 0x14, + ChannelNotice = 0x15, + ChannelNoticeUser = 0x16, + Afk = 0x17, + Dnd = 0x18, + Ignored = 0x19, + Skill = 0x1a, + Loot = 0x1b, + Money = 0x1c, + Opening = 0x1d, + Tradeskills = 0x1e, + PetInfo = 0x1f, + CombatMiscInfo = 0x20, + CombatXpGain = 0x21, + CombatHonorGain = 0x22, + CombatFactionChange = 0x23, + BgSystemNeutral = 0x24, + BgSystemAlliance = 0x25, + BgSystemHorde = 0x26, + RaidLeader = 0x27, + RaidWarning = 0x28, + RaidBossEmote = 0x29, + RaidBossWhisper = 0x2a, + Filtered = 0x2b, + Restricted = 0x2c, + Battlenet = 0x2d, + Achievement = 0x2e, + GuildAchievement = 0x2f, + ArenaPoints = 0x30, + PartyLeader = 0x31, + Targeticons = 0x32, + BnWhisper = 0x33, + BnWhisperInform = 0x34, + BnConversation = 0x35, + BnConversationNotice = 0x36, + BnConversationList = 0x37, + BnInlineToastAlert = 0x38, + BnInlineToastBroadcast = 0x39, + BnInlineToastBroadcastInform = 0x3a, + BnInlineToastConversation = 0x3b, + BnWhisperPlayerOffline = 0x3c, + CombatGuildXpGain = 0x3d, + Currency = 0x3e, + QuestBossEmote = 0x3f, + PetBattleCombatLog = 0x40, + PetBattleInfo = 0x41, + InstanceChat = 0x42, + InstanceChatLeader = 0x43, + Max = 0x44, + } + + public enum ChatRestrictionType + { + Restricted = 0, + Throttled = 1, + Squelched = 2, + YellRestricted = 3, + RaidRestricted = 4 + } + + public enum Difficulty : byte + { + None = 0, + Normal = 1, + Heroic = 2, + Raid10N = 3, + Raid25N = 4, + Raid10HC = 5, + Raid25HC = 6, + LFR = 7, + MythicKeystone = 8, + Raid40 = 9, + Scenario3ManHC = 11, + Scenario3ManN = 12, + NormalRaid = 14, + HeroicRaid = 15, + MythicRaid = 16, + LFRNew = 17, + EventRaid = 18, + EventDungeon = 19, + EventScenario = 20, + Mythic = 23, + Timewalking = 24, + WorldPvPScenario = 25, + Scenario5ManN = 26, + Scenario20ManN = 27, + PvEvPScenario = 29, + EventScenario6 = 30, + WorldPvPScenario2 = 32, + TimewalkingRaid = 33, + + Max + } + + public enum DifficultyFlags : byte + { + Heroic = 0x01, + Default = 0x02, + CanSelect = 0x04, // Player can select this difficulty in dropdown menu + ChallengeMode = 0x08, + + Legacy = 0x20, + DisplayHeroic = 0x40, // Controls icon displayed on minimap when inside the instance + DisplayMythic = 0x80 // Controls icon displayed on minimap when inside the instance + } + + public enum SpawnMask + { + Continent = (1 << Difficulty.None), // Any Maps Without Spawn Modes + + DungeonNormal = (1 << Difficulty.Normal), + DungeonHeroic = (1 << Difficulty.Heroic), + DungeonAll = (DungeonNormal | DungeonHeroic), + + Raid10Normal = (1 << Difficulty.Raid10N), + Raid25Normal = (1 << Difficulty.Raid25N), + RaidNormalAll = (Raid10Normal | Raid25Normal), + + Raid10Heroic = (1 << Difficulty.Raid10HC), + Raid25Heroic = (1 << Difficulty.Raid25HC), + RaidHeroicAll = (Raid10Heroic | Raid25Heroic), + + RaidAll = (RaidNormalAll | RaidHeroicAll) + } + + public enum MapFlags + { + CanToggleDifficulty = 0x0100, + FlexLocking = 0x8000, // All difficulties share completed encounters lock, not bound to a single instance id + // heroic difficulty flag overrides it and uses instance id bind + Garrison = 0x4000000 + } + + public enum WorldStates + { + CurrencyResetTime = 20001, // Next currency reset time + WeeklyQuestResetTime = 20002, // Next weekly reset time + BGDailyResetTime = 20003, // Next daily BG reset time + CleaningFlags = 20004, // Cleaning Flags + GuildDailyResetTime = 20006, // Next guild cap reset time + MonthlyQuestResetTime = 20007, // Next monthly reset time + // Cata specific custom worldstates + GuildWeeklyResetTime = 20050, // Next guild week reset time + } + + // values based at Holidays.dbc + public enum HolidayIds + { + None = 0, + + FireworksSpectacular = 62, + FeastOfWinterVeil = 141, + NobleGarden = 181, + ChildrensWeek = 201, + CallToArmsAv = 283, + CallToArmsWs = 284, + CallToArmsAb = 285, + FishingExtravaganza = 301, + HarvestFestival = 321, + HallowsEnd = 324, + LunarFestival = 327, + LoveIsInTheAir = 335, + FireFestival = 341, + CallToArmsEy = 353, + Brewfest = 372, + DarkmoonFaireElwynn = 374, + DarkmoonFaireThunder = 375, + DarkmoonFaireShattrath = 376, + PiratesDay = 398, + CallToArmsSa = 400, + PilgrimsBounty = 404, + WotlkLaunch = 406, + DayOfDead = 409, + CallToArmsIc = 420, + //LoveIsInTheAir = 423, + KaluAkFishingDerby = 424, + CallToArmsBfg = 435, + CallToArmsTp = 436, + RatedBg15Vs15 = 442, + RatedBg25Vs25 = 443, + Anniversary7Years = 467, + DarkmoonFaireTerokkar = 479, + Anniversary8Years = 484, + CallToArmsSm = 488, + CallToArmsTk = 489, + //CallToArmsAv = 490, + //CallToArmsAb = 491, + //CallToArmsEy = 492, + //CallToArmsAv = 493, + //CallToArmsSm = 494, + //CallToArmsSa = 495, + //CallToArmsTk = 496, + //CallToArmsBfg = 497, + //CallToArmsTp = 498, + //CallToArmsWs = 499, + Anniversary9Years = 509, + Anniversary10Years = 514, + CallToArmsDg = 515, + //CallToArmsDg = 516 + TimewalkingOutlands = 559, + ApexisBonusEvent = 560, + ArenaSkirmishBonusEvent = 561, + TimewalkingNorthrend = 562, + BattlegroundBonusEvent = 563, + DraenorDungeonEvent = 564, + PetBattleBonusEvent = 565, + Anniversary11Years = 566, + TimewalkingCataclysm = 587 + } + + public enum VoidTransferError + { + Ok = 0, + InternalError1 = 1, + InternalError2 = 2, + Full = 3, + InternalError3 = 4, + InternalError4 = 5, + NotEnoughMoney = 6, + InventoryFull = 7, + ItemInvalid = 8, + TransferUnknown = 9 + } + + public enum eScriptFlags + { + // Talk Flags + TalkUsePlayer = 0x1, + + // Emote Flags + EmoteUseState = 0x1, + + // Teleportto Flags + TeleportUseCreature = 0x1, + + // Killcredit Flags + KillcreditRewardGroup = 0x1, + + // Removeaura Flags + RemoveauraReverse = 0x1, + + // Castspell Flags + CastspellSourceToTarget = 0, + CastspellSourceToSource = 1, + CastspellTargetToTarget = 2, + CastspellTargetToSource = 3, + CastspellSearchCreature = 4, + CastspellTriggered = 0x1, + + // Playsound Flags + PlaysoundTargetPlayer = 0x1, + PlaysoundDistanceSound = 0x2, + + // Orientation Flags + OrientationFaceTarget = 0x1 + } + + public enum ScriptsType + { + First = 1, + + Spell = First, + Event, + Waypoint, + + Last + } + + // Db Scripting Commands + public enum ScriptCommands + { + Talk = 0, // Source/Target = Creature, Target = Any, Datalong = Talk Type (0=Say, 1=Whisper, 2=Yell, 3=Emote Text, 4=Boss Emote Text), Datalong2 & 1 = Player Talk (Instead Of Creature), Dataint = StringId + Emote = 1, // Source/Target = Creature, Datalong = Emote Id, Datalong2 = 0: Set Emote State; > 0: Play Emote State + FieldSet = 2, // Source/Target = Creature, Datalong = Field Id, Datalog2 = Value + MoveTo = 3, // Source/Target = Creature, Datalong2 = Time To Reach, X/Y/Z = Destination + FlagSet = 4, // Source/Target = Creature, Datalong = Field Id, Datalog2 = Bitmask + FlagRemove = 5, // Source/Target = Creature, Datalong = Field Id, Datalog2 = Bitmask + TeleportTo = 6, // Source/Target = Creature/Player (See Datalong2), Datalong = MapId, Datalong2 = 0: Player; 1: Creature, X/Y/Z = Destination, O = Orientation + QuestExplored = 7, // Target/Source = Player, Target/Source = Go/Creature, Datalong = Quest Id, Datalong2 = Distance Or 0 + KillCredit = 8, // Target/Source = Player, Datalong = Creature Entry, Datalong2 = 0: Personal Credit, 1: Group Credit + RespawnGameobject = 9, // Source = Worldobject (Summoner), Datalong = Go Guid, Datalong2 = Despawn Delay + TempSummonCreature = 10, // Source = Worldobject (Summoner), Datalong = Creature Entry, Datalong2 = Despawn Delay, X/Y/Z = Summon Position, O = Orientation + OpenDoor = 11, // Source = Unit, Datalong = Go Guid, Datalong2 = Reset Delay (Min 15) + CloseDoor = 12, // Source = Unit, Datalong = Go Guid, Datalong2 = Reset Delay (Min 15) + ActivateObject = 13, // Source = Unit, Target = Go + RemoveAura = 14, // Source (Datalong2 != 0) Or Target (Datalong2 == 0) = Unit, Datalong = Spell Id + CastSpell = 15, // Source And/Or Target = Unit, Datalong2 = Cast Direction (0: S.T 1: S.S 2: T.T 3: T.S 4: S.Creature With Dataint Entry), Dataint & 1 = Triggered Flag + PlaySound = 16, // Source = Worldobject, Target = None/Player, Datalong = Sound Id, Datalong2 (Bitmask: 0/1=Anyone/Player, 0/2=Without/With Distance Dependency, So 1|2 = 3 Is Target With Distance Dependency) + CreateItem = 17, // Target/Source = Player, Datalong = Item Entry, Datalong2 = Amount + DespawnSelf = 18, // Target/Source = Creature, Datalong = Despawn Delay + + LoadPath = 20, // Source = Unit, Datalong = Path Id, Datalong2 = Is Repeatable + CallscriptToUnit = 21, // Source = Worldobject (If Present Used As A Search Center), Datalong = Script Id, Datalong2 = Unit Lowguid, Dataint = Script Table To Use (See Scriptstype) + Kill = 22, // Source/Target = Creature, Dataint = Remove Corpse Attribute + + // Cyphercore Only + Orientation = 30, // Source = Unit, Target (Datalong > 0) = Unit, Datalong = > 0 Turn Source To Face Target, O = Orientation + Equip = 31, // Soucre = Creature, Datalong = Equipment Id + Model = 32, // Source = Creature, Datalong = Model Id + CloseGossip = 33, // Source = Player + Playmovie = 34, // Source = Player, Datalong = Movie Id + Movement = 35, // Source = Creature, datalong = MovementType, datalong2 = MovementDistance (spawndist f.ex.), dataint = pathid + PlayAnimkit = 36 // Source = Creature, datalong = AnimKit id + } + + // Custom values + public enum SpellClickUserTypes + { + Any = 0, + Friend = 1, + Raid = 2, + Party = 3, + Max = 4 + } + + public enum TokenResult + { + Success = 1, + Disabled = 2, + Other = 3, + NoneForSale = 4, + TooManyTokens = 5, + SuccessNo = 6, + TransactionInProgress = 7, + AuctionableTokenOwned = 8, + TrialRestricted = 9 + } + + public enum BanMode + { + Account, + Character, + IP + } + + public enum BanReturn + { + Success, + SyntaxError, + Notfound + } + + public enum WorldCfg + { + AccPasschangesec, + AddonChannel, + AhbotUpdateInterval, + AllTaxiPaths, + AllowGmGroup, + AllowPlayerCommands, + AllowTrackBothResources, + AllowTwoSideInteractionAuction, + AllowTwoSideInteractionCalendar, + AllowTwoSideInteractionChannel, + AllowTwoSideInteractionGroup, + AllowTwoSideInteractionGuild, + AllowTwoSideTrade, + AlwaysMaxskill, + ArenaLogExtendedInfo, + ArenaMaxRatingDifference, + ArenaQueueAnnouncerEnable, + ArenaRatedUpdateTimer, + ArenaRatingDiscardTimer, + ArenaSeasonId, + ArenaSeasonInProgress, + ArenaStartMatchmakerRating, + ArenaStartPersonalRating, + ArenaStartRating, + ArenaWinRatingModifier1, + ArenaWinRatingModifier2, + ArenaLoseRatingModifier, + ArenaMatchmakerRatingModifier, + AuctionGetallDelay, + AuctionLevelReq, + AuctionSearchDelay, + AutoBroadcast, + AutoBroadcastCenter, + AutoBroadcastInterval, + BasemapLoadGrids, + BattlegroundCastDeserter, + BattlegroundInvitationType, + BattlegroundPremadeGroupWaitForMatch, + BattlegroundPrematureFinishTimer, + BattlegroundQueueAnnouncerEnable, + BattlegroundQueueAnnouncerPlayeronly, + BattlegroundReportAfk, + BattlegroundStoreStatisticsEnable, + BgRewardLoserHonorFirst, + BgRewardLoserHonorLast, + BgRewardWinnerConquestFirst, + BgRewardWinnerConquestLast, + BgRewardWinnerHonorFirst, + BgRewardWinnerHonorLast, + BgXpForKill, + BlackmarketEnabled, + BlackmarketMaxAuctions, + BlackmarketUpdatePeriod, + CalculateCreatureZoneAreaData, + CalculateGameobjectZoneAreaData, + CastUnstuck, + CharacterCreatingDisabled, + CharacterCreatingDisabledClassmask, + CharacterCreatingDisabledRacemask, + CharacterCreatingMinLevelForDemonHunter, + CharacterCreatingMinLevelForDeathKnight, + CharactersPerAccount, + CharactersPerRealm, + ChardeleteDeathKnightMinLevel, + ChardeleteDemonHunterMinLevel, + ChardeleteKeepDays, + ChardeleteMethod, + ChardeleteMinLevel, + CharterCostArena2v2, + CharterCostArena3v3, + CharterCostArena5v5, + CharterCostGuild, + ChatChannelLevelReq, + ChatFakeMessagePreventing, + ChatSayLevelReq, + ChatStrictLinkCheckingKick, + ChatStrictLinkCheckingSeverity, + ChatWhisperLevelReq, + ChatEmoteLevelReq, + ChatYellLevelReq, + ChatfloodMessageCount, + ChatfloodMessageDelay, + ChatfloodMuteTime, + CleanCharacterDb, + ClientCacheVersion, + Compression, + CorpseDecayElite, + CorpseDecayNormal, + CorpseDecayRare, + CorpseDecayRareelite, + CorpseDecayWorldboss, + CreatureCheckInvalidPostion, + CreatureFamilyAssistanceDelay, + CreatureFamilyAssistanceRadius, + CreatureFamilyFleeAssistanceRadius, + CreatureFamilyFleeDelay, + CreaturePickpocketRefill, + CreatureStopForPlayer, + CurrencyMaxApexisCrystals, + CurrencyMaxJusticePoints, + CurrencyResetDay, + CurrencyResetHour, + CurrencyResetInterval, + CurrencyStartApexisCrystals, + CurrencyStartJusticePoints, + DailyQuestResetTimeHour, + DbcEnforceItemAttributes, + DeathBonesBgOrArena, + DeathBonesWorld, + DeathCorpseReclaimDelayPve, + DeathCorpseReclaimDelayPvp, + DeathKnightsPerRealm, + DeathSicknessLevel, + DeclinedNamesUsed, + DemonHuntersPerRealm, + DetectPosCollision, + DieCommandMode, + DisableBreathing, + DurabilityLossInPvp, + EnableMmaps, + EnableSinfoLogin, + EventAnnounce, + Expansion, + FeatureSystemBpayStoreEnabled, + FeatureSystemCharacterUndeleteCooldown, + FeatureSystemCharacterUndeleteEnabled, + ForceShutdownThreshold, + GameobjectCheckInvalidPostion, + GameType, + GmChat, + GmFreezeDuration, + GmLevelInGmList, + GmLevelInWhoList, + GmLoginState, + GmLowerSecurity, + GmVisibleState, + GmWhisperingTo, + GridUnload, + GroupVisibility, + GroupXpDistance, + GuildBankEventLogCount, + GuildEventLogCount, + GuildNewsLogCount, + GuildResetHour, + GuildSaveInterval, + HonorAfterDuel, + HotfixCacheVersion, + InstanceIgnoreLevel, + InstanceIgnoreRaid, + InstancemapLoadGrids, + InstanceResetTimeHour, + InstanceUnloadDelay, + InstancesResetAnnounce, + InstantLogout, + InstantTaxi, + IntervalChangeweather, + IntervalDisconnectTolerance, + IntervalGridclean, + IntervalLogUpdate, + IntervalMapupdate, + IntervalSave, + IpBasedActionLogging, + LfgOptionsmask, + ListenRangeSay, + ListenRangeTextemote, + ListenRangeYell, + LogdbClearinterval, + LogdbCleartime, + MailDeliveryDelay, + MailLevelReq, + MaxInstancesPerHour, + MaxOverspeedPings, + MaxPlayerLevel, + MaxPrimaryTradeSkill, + MaxRecruitAFriendBonusPlayerLevel, + MaxRecruitAFriendBonusPlayerLevelDifference, + MaxRecruitAFriendDistance, + MaxResultsLookupCommands, + MaxWho, + MinCharterName, + MinDualspecLevel, + MinLevelStatSave, + MinLogUpdate, + MinPetName, + MinPetitionSigns, + MinPlayerName, + NoGrayAggroAbove, + NoGrayAggroBelow, + NoResetTalentCost, + Numthreads, + OffhandCheckAtSpellUnlearn, + PacketSpoofBanduration, + PacketSpoofBanmode, + PacketSpoofPolicy, + PartyLevelReq, + PdumpNoOverwrite, + PdumpNoPaths, + PersistentCharacterCleanFlags, + PlayerAllowCommands, + PortInstance, + PortWorld, + PreserveCustomChannelDuration, + PreserveCustomChannels, + PreventRenameCustomization, + PvpTokenCount, + PvpTokenEnable, + PvpTokenId, + PvpTokenMapType, + QuestEnableQuestTracker, + QuestHighLevelHideDiff, + QuestIgnoreAutoAccept, + QuestIgnoreAutoComplete, + QuestIgnoreRaid, + QuestLowLevelHideDiff, + RandomBgResetHour, + RateAuctionCut, + RateAuctionDeposit, + RateAuctionTime, + RateCorpseDecayLooted, + RateCreatureAggro, + RateCreatureEliteEliteDamage, + RateCreatureEliteEliteHp, + RateCreatureEliteEliteSpelldamage, + RateCreatureEliteRareDamage, + RateCreatureEliteRareHp, + RateCreatureEliteRareSpelldamage, + RateCreatureEliteRareeliteDamage, + RateCreatureEliteRareeliteHp, + RateCreatureEliteRareeliteSpelldamage, + RateCreatureEliteWorldbossDamage, + RateCreatureEliteWorldbossHp, + RateCreatureEliteWorldbossSpelldamage, + RateCreatureNormalDamage, + RateCreatureNormalHp, + RateCreatureNormalSpelldamage, + RateDamageFall, + RateDropItemArtifact, + RateDropItemEpic, + RateDropItemLegendary, + RateDropItemNormal, + RateDropItemPoor, + RateDropItemRare, + RateDropItemReferenced, + RateDropItemReferencedAmount, + RateDropItemUncommon, + RateDropMoney, + RateDurabilityLossAbsorb, + RateDurabilityLossBlock, + RateDurabilityLossDamage, + RateDurabilityLossOnDeath, + RateDurabilityLossParry, + RateHealth, + RateHonor, + RateInstanceResetTime, + RateMoneyMaxLevelQuest, + RateMoneyQuest, + RateMovespeed, + RatePowerArcaneCharges, + RatePowerChi, + RatePowerComboPointsLoss, + RatePowerEnergy, + RatePowerFocus, + RatePowerFury, + RatePowerHolyPower, + RatePowerInsanity, + RatePowerLunarPower, + RatePowerMaelstrom, + RatePowerMana, + RatePowerPain, + RatePowerRageIncome, + RatePowerRageLoss, + RatePowerRunicPowerIncome, + RatePowerRunicPowerLoss, + RatePowerSoulShards, + RateRepaircost, + RateReputationGain, + RateReputationLowLevelKill, + RateReputationLowLevelQuest, + RateReputationRecruitAFriendBonus, + RateRestIngame, + RateRestOfflineInTavernOrCity, + RateRestOfflineInWilderness, + RateSkillDiscovery, + RateTalent, + RateTargetPosRecalculationRange, + RateXpExplore, + RateXpGuildModifier, + RateXpKill, + RateXpBgKill, + RateXpQuest, + RealmZone, + ResetDuelCooldowns, + ResetDuelHealthMana, + RestrictedLfgChannel, + SaveRespawnTimeImmediately, + SessionAddDelay, + ShowBanInWorld, + ShowKickInWorld, + ShowMuteInWorld, + SightMonster, + SkillChanceGreen, + SkillChanceGrey, + SkillChanceMiningSteps, + SkillChanceOrange, + SkillChanceSkinningSteps, + SkillChanceYellow, + SkillGainCrafting, + SkillGainGathering, + SkillMilling, + SkillProspecting, + SkipCinematics, + SocketTimeouttime, + StartAllExplored, + StartAllRep, + StartAllSpells, + StartDeathKnightPlayerLevel, + StartDemonHunterPlayerLevel, + StartGmLevel, + StartPlayerLevel, + StartPlayerMoney, + StatsLimitsBlock, + StatsLimitsCrit, + StatsLimitsDodge, + StatsLimitsEnable, + StatsLimitsParry, + StatsSaveOnlyOnLogout, + StrictCharterNames, + StrictPetNames, + StrictPlayerNames, + SupportBugsEnabled, + SupportComplaintsEnabled, + SupportEnabled, + SupportSuggestionsEnabled, + SupportTicketsEnabled, + TalentsInspecting, + ThreatRadius, + TradeLevelReq, + UiQuestLevelsInDialogs, // Should We Add Quest Levels To The Title In The Npc Dialogs? + UptimeUpdate, + VmapIndoorCheck, + WardenClientBanDuration, + WardenClientCheckHoldoff, + WardenClientFailAction, + WardenClientResponseDelay, + WardenEnabled, + WardenNumMemChecks, + WardenNumOtherChecks, + Weather, + WintergraspBattletime, + WintergraspEnable, + WintergraspNobattletime, + WintergraspPlrMax, + WintergraspPlrMin, + WintergraspPlrMinLvl, + WintergraspRestartAfterCrash, + WorldBossLevelDiff + } + + public enum TimerType + { + Pvp = 0, + ChallengerMode = 1 + } + + public enum GameError : uint + { + System = 0, + InvFull = 1, + BankFull = 2, + CantEquipLevelI = 3, + CantEquipSkill = 4, + CantEquipEver = 5, + CantEquipRank = 6, + CantEquipRating = 7, + CantEquipReputation = 8, + ProficiencyNeeded = 9, + WrongSlot = 10, + CantEquipNeedTalent = 11, + BagFull = 12, + InternalBagError = 13, + DestroyNonemptyBag = 14, + BagInBag = 15, + TooManySpecialBags = 16, + TradeEquippedBag = 17, + AmmoOnly = 18, + NoSlotAvailable = 19, + WrongBagType = 20, + ItemMaxCount = 21, + NotEquippable = 22, + CantStack = 23, + CantSwap = 24, + SlotEmpty = 25, + ItemNotFound = 26, + TooFewToSplit = 27, + SplitFailed = 28, + NotABag = 29, + NotOwner = 30, + OnlyOneQuiver = 31, + NoBankSlot = 32, + NoBankHere = 33, + ItemLocked = 34, + TwoHandedEquipped = 35, + VendorNotInterested = 36, + VendorHatesYou = 37, + VendorSoldOut = 38, + VendorTooFar = 39, + VendorDoesntBuy = 40, + NotEnoughMoney = 41, + ReceiveItemS = 42, + DropBoundItem = 43, + TradeBoundItem = 44, + TradeQuestItem = 45, + TradeTempEnchantBound = 46, + TradeGroundItem = 47, + TradeBag = 48, + SpellFailedS = 49, + ItemCooldown = 50, + PotionCooldown = 51, + FoodCooldown = 52, + SpellCooldown = 53, + AbilityCooldown = 54, + SpellAlreadyKnownS = 55, + PetSpellAlreadyKnownS = 56, + ProficiencyGainedS = 57, + SkillGainedS = 58, + SkillUpSi = 59, + LearnSpellS = 60, + LearnAbilityS = 61, + LearnPassiveS = 62, + LearnRecipeS = 63, + LearnCompanionS = 64, + LearnMountS = 65, + LearnToyS = 66, + LearnHeirloomS = 67, + LearnTransmogS = 68, + CompletedTransmogSetS = 69, + RevokeTransmogS = 70, + InvitePlayerS = 71, + InviteSelf = 72, + InvitedToGroupSs = 73, + InvitedAlreadyInGroupSs = 74, + AlreadyInGroupS = 75, + CrossRealmRaidInvite = 76, + PlayerBusyS = 77, + NewLeaderS = 78, + NewLeaderYou = 79, + NewGuideS = 80, + NewGuideYou = 81, + LeftGroupS = 82, + LeftGroupYou = 83, + GroupDisbanded = 84, + DeclineGroupS = 85, + JoinedGroupS = 86, + UninviteYou = 87, + BadPlayerNameS = 88, + NotInGroup = 89, + TargetNotInGroupS = 90, + TargetNotInInstanceS = 91, + NotInInstanceGroup = 92, + GroupFull = 93, + NotLeader = 94, + PlayerDiedS = 95, + GuildCreateS = 96, + GuildInviteS = 97, + InvitedToGuildSss = 98, + AlreadyInGuildS = 99, + AlreadyInvitedToGuildS = 100, + InvitedToGuild = 101, + AlreadyInGuild = 102, + GuildAccept = 103, + GuildDeclineS = 104, + GuildDeclineAutoS = 105, + GuildPermissions = 106, + GuildJoinS = 107, + GuildFounderS = 108, + GuildPromoteSss = 109, + GuildDemoteSs = 110, + GuildDemoteSss = 111, + GuildInviteSelf = 112, + GuildQuitS = 113, + GuildLeaveS = 114, + GuildRemoveSs = 115, + GuildRemoveSelf = 116, + GuildDisbandS = 117, + GuildDisbandSelf = 118, + GuildLeaderS = 119, + GuildLeaderSelf = 120, + GuildPlayerNotFoundS = 121, + GuildPlayerNotInGuildS = 122, + GuildPlayerNotInGuild = 123, + GuildCantPromoteS = 124, + GuildCantDemoteS = 125, + GuildNotInAGuild = 126, + GuildInternal = 127, + GuildLeaderIsS = 128, + GuildLeaderChangedSs = 129, + GuildDisbanded = 130, + GuildNotAllied = 131, + GuildLeaderLeave = 132, + GuildRanksLocked = 133, + GuildRankInUse = 134, + GuildRankTooHighS = 135, + GuildRankTooLowS = 136, + GuildNameExistsS = 137, + GuildWithdrawLimit = 138, + GuildNotEnoughMoney = 139, + GuildTooMuchMoney = 140, + GuildBankConjuredItem = 141, + GuildBankEquippedItem = 142, + GuildBankBoundItem = 143, + GuildBankQuestItem = 144, + GuildBankWrappedItem = 145, + GuildBankFull = 146, + GuildBankWrongTab = 147, + NoGuildCharter = 148, + OutOfRange = 149, + PlayerDead = 150, + ClientLockedOut = 151, + KilledByS = 152, + LootLocked = 153, + LootTooFar = 154, + LootDidntKill = 155, + LootBadFacing = 156, + LootNotstanding = 157, + LootStunned = 158, + LootNoUi = 159, + LootWhileInvulnerable = 160, + NoLoot = 161, + QuestAcceptedS = 162, + QuestCompleteS = 163, + QuestFailedS = 164, + QuestFailedBagFullS = 165, + QuestFailedMaxCountS = 166, + QuestFailedLowLevel = 167, + QuestFailedMissingItems = 168, + QuestFailedWrongRace = 169, + QuestFailedNotEnoughMoney = 170, + QuestFailedExpansion = 171, + QuestOnlyOneTimed = 172, + QuestNeedPrereqs = 173, + QuestNeedPrereqsCustom = 174, + QuestAlreadyOn = 175, + QuestAlreadyDone = 176, + QuestAlreadyDoneDaily = 177, + QuestHasInProgress = 178, + QuestRewardExpI = 179, + QuestRewardMoneyS = 180, + QuestMustChoose = 181, + QuestLogFull = 182, + CombatDamageSsi = 183, + InspectS = 184, + CantUseItem = 185, + CantUseItemInArena = 186, + CantUseItemInRatedBattleground = 187, + MustEquipItem = 188, + PassiveAbility = 189, + Hand2SkillNotFound = 190, + NoAttackTarget = 191, + InvalidAttackTarget = 192, + AttackPvpTargetWhileUnflagged = 193, + AttackStunned = 194, + AttackPacified = 195, + AttackMounted = 196, + AttackFleeing = 197, + AttackConfused = 198, + AttackCharmed = 199, + AttackDead = 200, + AttackPreventedByMechanicS = 201, + AttackChannel = 202, + Taxisamenode = 203, + Taxinosuchpath = 204, + Taxiunspecifiedservererror = 205, + Taxinotenoughmoney = 206, + Taxitoofaraway = 207, + Taxinovendornearby = 208, + Taxinotvisited = 209, + Taxiplayerbusy = 210, + Taxiplayeralreadymounted = 211, + Taxiplayershapeshifted = 212, + Taxiplayermoving = 213, + Taxinopaths = 214, + Taxinoteligible = 215, + Taxinotstanding = 216, + NoReplyTarget = 217, + GenericNoTarget = 218, + InitiateTradeS = 219, + TradeRequestS = 220, + TradeBlockedS = 221, + TradeTargetDead = 222, + TradeTooFar = 223, + TradeCancelled = 224, + TradeComplete = 225, + TradeBagFull = 226, + TradeTargetBagFull = 227, + TradeMaxCountExceeded = 228, + TradeTargetMaxCountExceeded = 229, + AlreadyTrading = 230, + MountInvalidmountee = 231, + MountToofaraway = 232, + MountAlreadymounted = 233, + MountNotmountable = 234, + MountNotyourpet = 235, + MountOther = 236, + MountLooting = 237, + MountRacecantmount = 238, + MountShapeshifted = 239, + MountNoFavorites = 240, + DismountNopet = 241, + DismountNotmounted = 242, + DismountNotyourpet = 243, + SpellFailedTotems = 244, + SpellFailedReagents = 245, + SpellFailedReagentsGeneric = 246, + SpellFailedEquippedItem = 247, + SpellFailedEquippedItemClassS = 248, + SpellFailedShapeshiftFormS = 249, + SpellFailedAnotherInProgress = 250, + Badattackfacing = 251, + Badattackpos = 252, + ChestInUse = 253, + UseCantOpen = 254, + UseLocked = 255, + DoorLocked = 256, + ButtonLocked = 257, + UseLockedWithItemS = 258, + UseLockedWithSpellS = 259, + UseLockedWithSpellKnownSi = 260, + UseTooFar = 261, + UseBadAngle = 262, + UseObjectMoving = 263, + UseSpellFocus = 264, + UseDestroyed = 265, + SetLootFreeforall = 266, + SetLootRoundrobin = 267, + SetLootMaster = 268, + SetLootGroup = 269, + SetLootThresholdS = 270, + NewLootMasterS = 271, + SpecifyMasterLooter = 272, + LootSpecChangedS = 273, + TameFailed = 274, + ChatWhileDead = 275, + ChatPlayerNotFoundS = 276, + Newtaxipath = 277, + NoPet = 278, + Notyourpet = 279, + PetNotRenameable = 280, + QuestObjectiveCompleteS = 281, + QuestUnknownComplete = 282, + QuestAddKillSii = 283, + QuestAddFoundSii = 284, + QuestAddItemSii = 285, + QuestAddPlayerKillSii = 286, + Cannotcreatedirectory = 287, + Cannotcreatefile = 288, + PlayerWrongFaction = 289, + PlayerIsNeutral = 290, + BankslotFailedTooMany = 291, + BankslotInsufficientFunds = 292, + BankslotNotbanker = 293, + FriendDbError = 294, + FriendListFull = 295, + FriendAddedS = 296, + BattletagFriendAddedS = 297, + FriendOnlineSs = 298, + FriendOfflineS = 299, + FriendNotFound = 300, + FriendWrongFaction = 301, + FriendRemovedS = 302, + BattletagFriendRemovedS = 303, + FriendError = 304, + FriendAlreadyS = 305, + FriendSelf = 306, + FriendDeleted = 307, + IgnoreFull = 308, + IgnoreSelf = 309, + IgnoreNotFound = 310, + IgnoreAlreadyS = 311, + IgnoreAddedS = 312, + IgnoreRemovedS = 313, + IgnoreAmbiguous = 314, + IgnoreDeleted = 315, + OnlyOneBolt = 316, + OnlyOneAmmo = 317, + SpellFailedEquippedSpecificItem = 318, + WrongBagTypeSubclass = 319, + CantWrapStackable = 320, + CantWrapEquipped = 321, + CantWrapWrapped = 322, + CantWrapBound = 323, + CantWrapUnique = 324, + CantWrapBags = 325, + OutOfMana = 326, + OutOfRage = 327, + OutOfFocus = 328, + OutOfEnergy = 329, + OutOfChi = 330, + OutOfHealth = 331, + OutOfRunes = 332, + OutOfRunicPower = 333, + OutOfSoulShards = 334, + OutOfLunarPower = 335, + OutOfHolyPower = 336, + OutOfMaelstrom = 337, + OutOfComboPoints = 338, + OutOfInsanity = 339, + OutOfArcaneCharges = 340, + OutOfFury = 341, + OutOfPain = 342, + OutOfPowerDisplay = 343, + LootGone = 344, + MountForceddismount = 345, + AutofollowTooFar = 346, + UnitNotFound = 347, + InvalidFollowTarget = 348, + InvalidInspectTarget = 349, + GuildemblemSuccess = 350, + GuildemblemInvalidTabardColors = 351, + GuildemblemNoguild = 352, + GuildemblemNotguildmaster = 353, + GuildemblemNotenoughmoney = 354, + GuildemblemInvalidvendor = 355, + EmblemerrorNotabardgeoset = 356, + SpellOutOfRange = 357, + CommandNeedsTarget = 358, + NoammoS = 359, + Toobusytofollow = 360, + DuelRequested = 361, + DuelCancelled = 362, + Deathbindalreadybound = 363, + DeathbindSuccessS = 364, + Noemotewhilerunning = 365, + ZoneExplored = 366, + ZoneExploredXp = 367, + InvalidItemTarget = 368, + InvalidQuestTarget = 369, + IgnoringYouS = 370, + FishNotHooked = 371, + FishEscaped = 372, + SpellFailedNotunsheathed = 373, + PetitionOfferedS = 374, + PetitionSigned = 375, + PetitionSignedS = 376, + PetitionDeclinedS = 377, + PetitionAlreadySigned = 378, + PetitionRestrictedAccountTrial = 379, + PetitionAlreadySignedOther = 380, + PetitionInGuild = 381, + PetitionCreator = 382, + PetitionNotEnoughSignatures = 383, + PetitionNotSameServer = 384, + PetitionFull = 385, + PetitionAlreadySignedByS = 386, + GuildNameInvalid = 387, + SpellUnlearnedS = 388, + PetSpellRooted = 389, + PetSpellAffectingCombat = 390, + PetSpellOutOfRange = 391, + PetSpellNotBehind = 392, + PetSpellTargetsDead = 393, + PetSpellDead = 394, + PetSpellNopath = 395, + ItemCantBeDestroyed = 396, + TicketAlreadyExists = 397, + TicketCreateError = 398, + TicketUpdateError = 399, + TicketDbError = 400, + TicketNoText = 401, + TicketTextTooLong = 402, + ObjectIsBusy = 403, + ExhaustionWellrested = 404, + ExhaustionRested = 405, + ExhaustionNormal = 406, + ExhaustionTired = 407, + ExhaustionExhausted = 408, + NoItemsWhileShapeshifted = 409, + CantInteractShapeshifted = 410, + RealmNotFound = 411, + MailQuestItem = 412, + MailBoundItem = 413, + MailConjuredItem = 414, + MailBag = 415, + MailToSelf = 416, + MailTargetNotFound = 417, + MailDatabaseError = 418, + MailDeleteItemError = 419, + MailWrappedCod = 420, + MailCantSendRealm = 421, + MailSent = 422, + NotHappyEnough = 423, + UseCantImmune = 424, + CantBeDisenchanted = 425, + CantUseDisarmed = 426, + AuctionQuestItem = 427, + AuctionBoundItem = 428, + AuctionConjuredItem = 429, + AuctionLimitedDurationItem = 430, + AuctionWrappedItem = 431, + AuctionLootItem = 432, + AuctionBag = 433, + AuctionEquippedBag = 434, + AuctionDatabaseError = 435, + AuctionBidOwn = 436, + AuctionBidIncrement = 437, + AuctionHigherBid = 438, + AuctionMinBid = 439, + AuctionRepairItem = 440, + AuctionUsedCharges = 441, + AuctionAlreadyBid = 442, + AuctionStarted = 443, + AuctionRemoved = 444, + AuctionOutbidS = 445, + AuctionWonS = 446, + AuctionSoldS = 447, + AuctionExpiredS = 448, + AuctionRemovedS = 449, + AuctionBidPlaced = 450, + LogoutFailed = 451, + QuestPushSuccessS = 452, + QuestPushInvalidS = 453, + QuestPushAcceptedS = 454, + QuestPushDeclinedS = 455, + QuestPushBusyS = 456, + QuestPushDeadS = 457, + QuestPushLogFullS = 458, + QuestPushOnquestS = 459, + QuestPushAlreadyDoneS = 460, + QuestPushNotDailyS = 461, + QuestPushTimerExpiredS = 462, + QuestPushNotInPartyS = 463, + QuestPushDifferentServerDailyS = 464, + QuestPushNotAllowedS = 465, + RaidGroupLowlevel = 466, + RaidGroupOnly = 467, + RaidGroupFull = 468, + RaidGroupRequirementsUnmatch = 469, + CorpseIsNotInInstance = 470, + PvpKillHonorable = 471, + PvpKillDishonorable = 472, + SpellFailedAlreadyAtFullHealth = 473, + SpellFailedAlreadyAtFullMana = 474, + SpellFailedAlreadyAtFullPowerS = 475, + AutolootMoneyS = 476, + GenericStunned = 477, + TargetStunned = 478, + MustRepairDurability = 479, + RaidYouJoined = 480, + RaidYouLeft = 481, + InstanceGroupJoinedWithParty = 482, + InstanceGroupJoinedWithRaid = 483, + RaidMemberAddedS = 484, + RaidMemberRemovedS = 485, + InstanceGroupAddedS = 486, + InstanceGroupRemovedS = 487, + ClickOnItemToFeed = 488, + TooManyChatChannels = 489, + LootRollPending = 490, + LootPlayerNotFound = 491, + NotInRaid = 492, + LoggingOut = 493, + TargetLoggingOut = 494, + NotWhileMounted = 495, + NotWhileShapeshifted = 496, + NotInCombat = 497, + NotWhileDisarmed = 498, + PetBroken = 499, + TalentWipeError = 500, + SpecWipeError = 501, + GlyphWipeError = 502, + PetSpecWipeError = 503, + FeignDeathResisted = 504, + MeetingStoneInQueueS = 505, + MeetingStoneLeftQueueS = 506, + MeetingStoneOtherMemberLeft = 507, + MeetingStonePartyKickedFromQueue = 508, + MeetingStoneMemberStillInQueue = 509, + MeetingStoneSuccess = 510, + MeetingStoneInProgress = 511, + MeetingStoneMemberAddedS = 512, + MeetingStoneGroupFull = 513, + MeetingStoneNotLeader = 514, + MeetingStoneInvalidLevel = 515, + MeetingStoneTargetNotInParty = 516, + MeetingStoneTargetInvalidLevel = 517, + MeetingStoneMustBeLeader = 518, + MeetingStoneNoRaidGroup = 519, + MeetingStoneNeedParty = 520, + MeetingStoneNotFound = 521, + GuildemblemSame = 522, + EquipTradeItem = 523, + PvpToggleOn = 524, + PvpToggleOff = 525, + GroupJoinBattlegroundDeserters = 526, + GroupJoinBattlegroundDead = 527, + GroupJoinBattlegroundS = 528, + GroupJoinBattlegroundFail = 529, + GroupJoinBattlegroundTooMany = 530, + SoloJoinBattlegroundS = 531, + BattlegroundTooManyQueues = 532, + BattlegroundCannotQueueForRated = 533, + BattledgroundQueuedForRated = 534, + BattlegroundTeamLeftQueue = 535, + BattlegroundNotInBattleground = 536, + AlreadyInArenaTeamS = 537, + InvalidPromotionCode = 538, + BgPlayerJoinedSs = 539, + BgPlayerLeftS = 540, + RestrictedAccount = 541, + RestrictedAccountTrial = 542, + PlayTimeExceeded = 543, + ApproachingPartialPlayTime = 544, + ApproachingPartialPlayTime2 = 545, + ApproachingNoPlayTime = 546, + ApproachingNoPlayTime2 = 547, + UnhealthyTime = 548, + ChatRestrictedTrial = 549, + ChatThrottled = 550, + MailReachedCap = 551, + InvalidRaidTarget = 552, + RaidLeaderReadyCheckStartS = 553, + ReadyCheckInProgress = 554, + ReadyCheckThrottled = 555, + DungeonDifficultyFailed = 556, + DungeonDifficultyChangedS = 557, + TradeWrongRealm = 558, + TradeNotOnTaplist = 559, + ChatPlayerAmbiguousS = 560, + LootCantLootThatNow = 561, + LootMasterInvFull = 562, + LootMasterUniqueItem = 563, + LootMasterOther = 564, + FilteringYouS = 565, + UsePreventedByMechanicS = 566, + ItemUniqueEquippable = 567, + LfgLeaderIsLfmS = 568, + LfgPending = 569, + CantSpeakLangage = 570, + VendorMissingTurnins = 571, + BattlegroundNotInTeam = 572, + NotInBattleground = 573, + NotEnoughHonorPoints = 574, + NotEnoughArenaPoints = 575, + SocketingRequiresMetaGem = 576, + SocketingMetaGemOnlyInMetaslot = 577, + SocketingRequiresHydraulicGem = 578, + SocketingHydraulicGemOnlyInHydraulicslot = 579, + SocketingRequiresCogwheelGem = 580, + SocketingCogwheelGemOnlyInCogwheelslot = 581, + SocketingItemTooLowLevel = 582, + ItemMaxCountSocketed = 583, + SystemDisabled = 584, + QuestFailedTooManyDailyQuestsI = 585, + ItemMaxCountEquippedSocketed = 586, + ItemUniqueEquippableSocketed = 587, + UserSquelched = 588, + TooMuchGold = 589, + NotBarberSitting = 590, + QuestFailedCais = 591, + InviteRestrictedTrial = 592, + VoiceIgnoreFull = 593, + VoiceIgnoreSelf = 594, + VoiceIgnoreNotFound = 595, + VoiceIgnoreAlreadyS = 596, + VoiceIgnoreAddedS = 597, + VoiceIgnoreRemovedS = 598, + VoiceIgnoreAmbiguous = 599, + VoiceIgnoreDeleted = 600, + UnknownMacroOptionS = 601, + NotDuringArenaMatch = 602, + PlayerSilenced = 603, + PlayerUnsilenced = 604, + ComsatDisconnect = 605, + ComsatReconnectAttempt = 606, + ComsatConnectFail = 607, + MailInvalidAttachmentSlot = 608, + MailTooManyAttachments = 609, + MailInvalidAttachment = 610, + MailAttachmentExpired = 611, + VoiceChatParentalDisableAll = 612, + VoiceChatParentalDisableMic = 613, + ProfaneChatName = 614, + PlayerSilencedEcho = 615, + PlayerUnsilencedEcho = 616, + VoicesessionFull = 617, + LootCantLootThat = 618, + ArenaExpiredCais = 619, + GroupActionThrottled = 620, + AlreadyPickpocketed = 621, + NameInvalid = 622, + NameNoName = 623, + NameTooShort = 624, + NameTooLong = 625, + NameMixedLanguages = 626, + NameProfane = 627, + NameReserved = 628, + NameThreeConsecutive = 629, + NameInvalidSpace = 630, + NameConsecutiveSpaces = 631, + NameRussianConsecutiveSilentCharacters = 632, + NameRussianSilentCharacterAtBeginningOrEnd = 633, + NameDeclensionDoesntMatchBaseName = 634, + ReferAFriendNotReferredBy = 635, + ReferAFriendTargetTooHigh = 636, + ReferAFriendInsufficientGrantableLevels = 637, + ReferAFriendTooFar = 638, + ReferAFriendDifferentFaction = 639, + ReferAFriendNotNow = 640, + ReferAFriendGrantLevelMaxI = 641, + ReferAFriendSummonLevelMaxI = 642, + ReferAFriendSummonCooldown = 643, + ReferAFriendSummonOfflineS = 644, + ReferAFriendInsufExpanLvl = 645, + ReferAFriendNotInLfg = 646, + ReferAFriendNoXrealm = 647, + ReferAFriendMapIncomingTransferNotAllowed = 648, + NotSameAccount = 649, + BadOnUseEnchant = 650, + TradeSelf = 651, + TooManySockets = 652, + ItemMaxLimitCategoryCountExceededIs = 653, + TradeTargetMaxLimitCategoryCountExceededIs = 654, + ItemMaxLimitCategorySocketedExceededIs = 655, + ItemMaxLimitCategoryEquippedExceededIs = 656, + ShapeshiftFormCannotEquip = 657, + ItemInventoryFullSatchel = 658, + ScalingStatItemLevelExceeded = 659, + ScalingStatItemLevelTooLow = 660, + PurchaseLevelTooLow = 661, + GroupSwapFailed = 662, + InviteInCombat = 663, + InvalidGlyphSlot = 664, + GenericNoValidTargets = 665, + CalendarEventAlertS = 666, + PetLearnSpellS = 667, + PetLearnAbilityS = 668, + PetSpellUnlearnedS = 669, + InviteUnknownRealm = 670, + InviteNoPartyServer = 671, + InvitePartyBusy = 672, + PartyTargetAmbiguous = 673, + PartyLfgInviteRaidLocked = 674, + PartyLfgBootLimit = 675, + PartyLfgBootCooldownS = 676, + PartyLfgBootNotEligibleS = 677, + PartyLfgBootInpatientTimerS = 678, + PartyLfgBootInProgress = 679, + PartyLfgBootTooFewPlayers = 680, + PartyLfgBootVoteSucceeded = 681, + PartyLfgBootVoteFailed = 682, + PartyLfgBootInCombat = 683, + PartyLfgBootDungeonComplete = 684, + PartyLfgBootLootRolls = 685, + PartyLfgBootVoteRegistered = 686, + PartyPrivateGroupOnly = 687, + PartyLfgTeleportInCombat = 688, + RaidDisallowedByLevel = 689, + RaidDisallowedByCrossRealm = 690, + PartyRoleNotAvailable = 691, + JoinLfgObjectFailed = 692, + LfgRemovedLevelup = 693, + LfgRemovedXpToggle = 694, + LfgRemovedFactionChange = 695, + BattlegroundInfoThrottled = 696, + BattlegroundAlreadyIn = 697, + ArenaTeamChangeFailedQueued = 698, + ArenaTeamPermissions = 699, + NotWhileFalling = 700, + NotWhileMoving = 701, + NotWhileFatigued = 702, + MaxSockets = 703, + MultiCastActionTotemS = 704, + BattlegroundJoinLevelup = 705, + RemoveFromPvpQueueXpGain = 706, + BattlegroundJoinXpGain = 707, + BattlegroundJoinMercenary = 708, + BattlegroundJoinTooManyHealers = 709, + BattlegroundJoinTooManyTanks = 710, + BattlegroundJoinTooManyDamage = 711, + RaidDifficultyFailed = 712, + RaidDifficultyChangedS = 713, + LegacyRaidDifficultyChangedS = 714, + RaidLockoutChangedS = 715, + RaidConvertedToParty = 716, + PartyConvertedToRaid = 717, + PlayerDifficultyChangedS = 718, + GmresponseDbError = 719, + BattlegroundJoinRangeIndex = 720, + ArenaJoinRangeIndex = 721, + RemoveFromPvpQueueFactionChange = 722, + BattlegroundJoinFailed = 723, + BattlegroundJoinNoValidSpecForRole = 724, + BattlegroundJoinRespec = 725, + BattlegroundInvitationDeclined = 726, + BattlegroundJoinTimedOut = 727, + BattlegroundDupeQueue = 728, + BattlegroundJoinMustCompleteQuest = 729, + InBattlegroundRespec = 730, + MailLimitedDurationItem = 731, + YellRestrictedTrial = 732, + ChatRaidRestrictedTrial = 733, + LfgRoleCheckFailed = 734, + LfgRoleCheckFailedTimeout = 735, + LfgRoleCheckFailedNotViable = 736, + LfgReadyCheckFailed = 737, + LfgReadyCheckFailedTimeout = 738, + LfgGroupFull = 739, + LfgNoLfgObject = 740, + LfgNoSlotsPlayer = 741, + LfgNoSlotsParty = 742, + LfgNoSpec = 743, + LfgMismatchedSlots = 744, + LfgMismatchedSlotsLocalXrealm = 745, + LfgPartyPlayersFromDifferentRealms = 746, + LfgMembersNotPresent = 747, + LfgGetInfoTimeout = 748, + LfgInvalidSlot = 749, + LfgDeserterPlayer = 750, + LfgDeserterParty = 751, + LfgDead = 752, + LfgRandomCooldownPlayer = 753, + LfgRandomCooldownParty = 754, + LfgTooManyMembers = 755, + LfgTooFewMembers = 756, + LfgProposalFailed = 757, + LfgProposalDeclinedSelf = 758, + LfgProposalDeclinedParty = 759, + LfgNoSlotsSelected = 760, + LfgNoRolesSelected = 761, + LfgRoleCheckInitiated = 762, + LfgReadyCheckInitiated = 763, + LfgPlayerDeclinedRoleCheck = 764, + LfgPlayerDeclinedReadyCheck = 765, + LfgJoinedQueue = 766, + LfgJoinedFlexQueue = 767, + LfgJoinedRfQueue = 768, + LfgJoinedScenarioQueue = 769, + LfgJoinedWorldPvpQueue = 770, + LfgJoinedList = 771, + LfgLeftQueue = 772, + LfgLeftList = 773, + LfgRoleCheckAborted = 774, + LfgReadyCheckAborted = 775, + LfgCantUseBattleground = 776, + LfgCantUseDungeons = 777, + LfgReasonTooManyLfg = 778, + InvalidTeleportLocation = 779, + TooFarToInteract = 780, + BattlegroundPlayersFromDifferentRealms = 781, + DifficultyChangeCooldownS = 782, + DifficultyChangeCombatCooldownS = 783, + DifficultyChangeWorldstate = 784, + DifficultyChangeEncounter = 785, + DifficultyChangeCombat = 786, + DifficultyChangePlayerBusy = 787, + DifficultyChangeAlreadyStarted = 788, + DifficultyChangeOtherHeroicS = 789, + DifficultyChangeHeroicInstanceAlreadyRunning = 790, + ArenaTeamPartySize = 791, + QuestForceRemovedS = 792, + AttackNoActions = 793, + InRandomBg = 794, + InNonRandomBg = 795, + AuctionEnoughItems = 796, + BnFriendSelf = 797, + BnFriendAlready = 798, + BnFriendBlocked = 799, + BnFriendListFull = 800, + BnFriendRequestSent = 801, + BnBroadcastThrottle = 802, + BgDeveloperOnly = 803, + CurrencySpellSlotMismatch = 804, + CurrencyNotTradable = 805, + RequiresExpansionS = 806, + QuestFailedSpell = 807, + TalentFailedNotEnoughTalentsInPrimaryTree = 808, + TalentFailedNoPrimaryTreeSelected = 809, + TalentFailedCantRemoveTalent = 810, + TalentFailedUnknown = 811, + WargameRequestFailure = 812, + RankRequiresAuthenticator = 813, + GuildBankVoucherFailed = 814, + WargameRequestSent = 815, + RequiresAchievementI = 816, + RefundResultExceedMaxCurrency = 817, + CantBuyQuantity = 818, + ItemIsBattlePayLocked = 819, + PartyAlreadyInBattlegroundQueue = 820, + PartyConfirmingBattlegroundQueue = 821, + BattlefieldTeamPartySize = 822, + InsuffTrackedCurrencyIs = 823, + NotOnTournamentRealm = 824, + GuildTrialAccountTrial = 825, + GuildTrialAccountVeteran = 826, + GuildUndeletableDueToLevel = 827, + CantDoThatInAGroup = 828, + GuildLeaderReplaced = 829, + TransmogrifyCantEquip = 830, + TransmogrifyInvalidItemType = 831, + TransmogrifyNotSoulbound = 832, + TransmogrifyInvalidSource = 833, + TransmogrifyInvalidDestination = 834, + TransmogrifyMismatch = 835, + TransmogrifyLegendary = 836, + TransmogrifySameItem = 837, + TransmogrifySameAppearance = 838, + TransmogrifyNotEquipped = 839, + VoidDepositFull = 840, + VoidWithdrawFull = 841, + VoidStorageWrapped = 842, + VoidStorageStackable = 843, + VoidStorageUnbound = 844, + VoidStorageRepair = 845, + VoidStorageCharges = 846, + VoidStorageQuest = 847, + VoidStorageConjured = 848, + VoidStorageMail = 849, + VoidStorageBag = 850, + VoidTransferStorageFull = 851, + VoidTransferInvFull = 852, + VoidTransferInternalError = 853, + VoidTransferItemInvalid = 854, + DifficultyDisabledInLfg = 855, + VoidStorageUnique = 856, + VoidStorageLoot = 857, + VoidStorageHoliday = 858, + VoidStorageDuration = 859, + VoidStorageLoadFailed = 860, + VoidStorageInvalidItem = 861, + ParentalControlsChatMuted = 862, + SorStartExperienceIncomplete = 863, + SorInvalidEmail = 864, + SorInvalidComment = 865, + ChallengeModeResetCooldownS = 866, + ChallengeModeResetKeystone = 867, + PetJournalAlreadyInLoadout = 868, + ReportSubmittedSuccessfully = 869, + ReportSubmissionFailed = 870, + SuggestionSubmittedSuccessfully = 871, + BugSubmittedSuccessfully = 872, + ChallengeModeEnabled = 873, + ChallengeModeDisabled = 874, + PetbattleCreateFailed = 875, + PetbattleNotHere = 876, + PetbattleNotHereOnTransport = 877, + PetbattleNotHereUnevenGround = 878, + PetbattleNotHereObstructed = 879, + PetbattleNotWhileInCombat = 880, + PetbattleNotWhileDead = 881, + PetbattleNotWhileFlying = 882, + PetbattleTargetInvalid = 883, + PetbattleTargetOutOfRange = 884, + PetbattleTargetNotCapturable = 885, + PetbattleNotATrainer = 886, + PetbattleDeclined = 887, + PetbattleInBattle = 888, + PetbattleInvalidLoadout = 889, + PetbattleAllPetsDead = 890, + PetbattleNoPetsInSlots = 891, + PetbattleNoAccountLock = 892, + PetbattleWildPetTapped = 893, + PetbattleRestrictedAccount = 894, + PetbattleNotWhileInMatchedBattle = 895, + CantHaveMorePetsOfThatType = 896, + CantHaveMorePets = 897, + PvpMapNotFound = 898, + PvpMapNotSet = 899, + PetbattleQueueQueued = 900, + PetbattleQueueAlreadyQueued = 901, + PetbattleQueueJoinFailed = 902, + PetbattleQueueJournalLock = 903, + PetbattleQueueRemoved = 904, + PetbattleQueueProposalDeclined = 905, + PetbattleQueueProposalTimeout = 906, + PetbattleQueueOpponentDeclined = 907, + PetbattleQueueRequeuedInternal = 908, + PetbattleQueueRequeuedRemoved = 909, + PetbattleQueueSlotLocked = 910, + PetbattleQueueSlotEmpty = 911, + PetbattleQueueSlotNoTracker = 912, + PetbattleQueueSlotNoSpecies = 913, + PetbattleQueueSlotCantBattle = 914, + PetbattleQueueSlotRevoked = 915, + PetbattleQueueSlotDead = 916, + PetbattleQueueSlotNoPet = 917, + PetbattleQueueNotWhileNeutral = 918, + PetbattleGameTimeLimitWarning = 919, + PetbattleGameRoundsLimitWarning = 920, + HasRestriction = 921, + ItemUpgradeItemTooLowLevel = 922, + ItemUpgradeNoPath = 923, + ItemUpgradeNoMoreUpgrades = 924, + BonusRollEmpty = 925, + ChallengeModeFull = 926, + ChallengeModeInProgress = 927, + ChallengeModeIncorrectKeystone = 928, + BattletagFriendNotFound = 929, + BattletagFriendNotValid = 930, + BattletagFriendNotAllowed = 931, + BattletagFriendThrottled = 932, + BattletagFriendSuccess = 933, + PetTooHighLevelToUncage = 934, + PetbattleInternal = 935, + CantCagePetYet = 936, + NoLootInChallengeMode = 937, + QuestPetBattleVictoriesPvpIi = 938, + RoleCheckAlreadyInProgress = 939, + RecruitAFriendAccountLimit = 940, + RecruitAFriendFailed = 941, + SetLootPersonal = 942, + SetLootMethodFailedCombat = 943, + ReagentBankFull = 944, + ReagentBankLocked = 945, + GarrisonBuildingExists = 946, + GarrisonInvalidPlot = 947, + GarrisonInvalidBuildingid = 948, + GarrisonInvalidPlotBuilding = 949, + GarrisonRequiresBlueprint = 950, + GarrisonNotEnoughCurrency = 951, + GarrisonNotEnoughGold = 952, + GarrisonCompleteMissionWrongFollowerType = 953, + AlreadyUsingLfgList = 954, + RestrictedAccountLfgListTrial = 955, + ToyUseLimitReached = 956, + ToyAlreadyKnown = 957, + TransmogSetAlreadyKnown = 958, + NotEnoughCurrency = 959, + SpecIsDisabled = 960, + FeatureRestrictedTrial = 961, + CantBeObliterated = 962, + ArtifactRelicDoesNotMatchArtifact = 963, + MustEquipArtifact = 964, + CantDoThatRightNow = 965, + AffectingCombat = 966, + EquipmentManagerCombatSwapS = 967, + EquipmentManagerBagsFull = 968, + EquipmentManagerMissingItemS = 969, + MovieRecordingWarningPerf = 970, + MovieRecordingWarningDiskFull = 971, + MovieRecordingWarningNoMovie = 972, + MovieRecordingWarningRequirements = 973, + MovieRecordingWarningCompressing = 974, + NoChallengeModeReward = 975, + ClaimedChallengeModeReward = 976, + ChallengeModePeriodResetSs = 977, + CantDoThatChallengeModeActive = 978, + TalentFailedRestArea = 979, + CannotAbandonLastPet = 980, + TestCvarSetSss = 981, + QuestTurnInFailReason = 982, + ClaimedChallengeModeRewardOld = 983, + TalentGrantedByAura = 984 + } + + public enum SceneFlags + { + Unk1 = 0x01, + CancelAtEnd = 0x02, + NotCancelable = 0x04, + Unk8 = 0x08, + Unk16 = 0x10, // 16, most common value + Unk32 = 0x20 + } +} diff --git a/Framework/Constants/SmartAIConst.cs b/Framework/Constants/SmartAIConst.cs new file mode 100644 index 000000000..6245d2d40 --- /dev/null +++ b/Framework/Constants/SmartAIConst.cs @@ -0,0 +1,364 @@ +/* + * Copyright (C) 2012-2017 CypherCore + * + * 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, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +namespace Framework.Constants +{ + public enum SmartScriptType + { + Creature = 0, + GameObject = 1, + AreaTrigger = 2, + Event = 3, + Gossip = 4, + Quest = 5, + Spell = 6, + Transport = 7, + Instance = 8, + TimedActionlist = 9, + Scene = 10, // done + Max = 11 + } + + public struct SmartScriptTypeMaskId + { + public const uint Creature = 1; + public const uint Gameobject = 2; + public const uint Areatrigger = 4; + public const uint Event = 8; + public const uint Gossip = 16; + public const uint Quest = 32; + public const uint Spell = 64; + public const uint Transport = 128; + public const uint Instance = 256; + public const uint TimedActionlist = 512; + public const uint Scene = 1024; + } + + public enum SmartPhase + { + ALWAYS = 0, + Phase1 = 1, + Phase2 = 2, + Phase3 = 3, + Phase4 = 4, + Phase5 = 5, + Phase6 = 6, + Max = 7, + All = (Phase1 | Phase2 | Phase3 | Phase4 | Phase5 | Phase6), + + Count = 6 + } + + public enum SmartEventFlags + { + NotRepeatable = 0x01, //Event can not repeat + Difficulty0 = 0x02, //Event only occurs in instance difficulty 0 + Difficulty1 = 0x04, //Event only occurs in instance difficulty 1 + Difficulty2 = 0x08, //Event only occurs in instance difficulty 2 + Difficulty3 = 0x10, //Event only occurs in instance difficulty 3 + Reserved5 = 0x20, + Reserved6 = 0x40, + DebugOnly = 0x80, //Event only occurs in debug build + DontReset = 0x100, //Event will not reset in SmartScript.OnReset() + WhileCharmed = 0x200, //Event occurs even if AI owner is charmed + + DifficultyAll = (Difficulty0 | Difficulty1 | Difficulty2 | Difficulty3), + All = (NotRepeatable | DifficultyAll | Reserved5 | Reserved6 | DebugOnly | DontReset | WhileCharmed) + } + + public enum SmartRespawnCondition + { + None = 0, + Map = 1, + Area = 2, + End = 3 + } + + public enum SmartAITemplate + { + Basic = 0, //nothing is preset + Caster = 1, //spellid, repeatMin, repeatMax, range, manaPCT +JOIN: target_param1 as castFlag + Turret = 2, //spellid, repeatMin, repeatMax +JOIN: target_param1 as castFlag + Passive = 3, + CagedGOPart = 4, //creatureID, give credit at point end?, + CagedNPCPart = 5, //gameObjectID, despawntime, run?, dist, TextGroupID + End = 6 + } + + public enum SmartCastFlags + { + InterruptPrevious = 0x01, //Interrupt any spell casting + Triggered = 0x02, //Triggered (this makes spell cost zero mana and have no cast time) + //CAST_FORCE_CAST = 0x04, //Forces cast even if creature is out of mana or out of range + //CAST_NO_MELEE_IF_OOM = 0x08, //Prevents creature from entering melee if out of mana or out of range + //CAST_FORCE_TARGET_SELF = 0x10, //Forces the target to cast this spell on itself + AuraNotPresent = 0x20, //Only casts the spell if the target does not have an aura from the spell + CombatMove = 0x40 //Prevents combat movement if cast successful. Allows movement on range, OOM, LOS + } + + public enum SmartEvents + { + UpdateIc = 0, // Initialmin, Initialmax, Repeatmin, Repeatmax + UpdateOoc = 1, // Initialmin, Initialmax, Repeatmin, Repeatmax + HealtPct = 2, // Hpmin%, Hpmax%, Repeatmin, Repeatmax + ManaPct = 3, // Manamin%, Manamax%, Repeatmin, Repeatmax + Aggro = 4, // None + Kill = 5, // Cooldownmin0, Cooldownmax1, Playeronly2, Else Creature Entry3 + Death = 6, // None + Evade = 7, // None + SpellHit = 8, // Spellid, School, Cooldownmin, Cooldownmax + Range = 9, // Mindist, Maxdist, Repeatmin, Repeatmax + OocLos = 10, // Nohostile, Maxrnage, Cooldownmin, Cooldownmax + Respawn = 11, // Type, Mapid, Zoneid + TargetHealthPct = 12, // Hpmin%, Hpmax%, Repeatmin, Repeatmax + VictimCasting = 13, // Repeatmin, Repeatmax + FriendlyHealth = 14, // Hpdeficit, Radius, Repeatmin, Repeatmax + FriendlyIsCc = 15, // Radius, Repeatmin, Repeatmax + FriendlyMissingBuff = 16, // Spellid, Radius, Repeatmin, Repeatmax + SummonedUnit = 17, // Creatureid(0 All), Cooldownmin, Cooldownmax + TargetManaPct = 18, // Manamin%, Manamax%, Repeatmin, Repeatmax + AcceptedQuest = 19, // Questid(0any) + RewardQuest = 20, // Questid(0any) + ReachedHome = 21, // None + ReceiveEmote = 22, // Emoteid, Cooldownmin, Cooldownmax, Condition, Val1, Val2, Val3 + HasAura = 23, // Param1 = Spellid, Param2 = Stack Amount, Param3/4 Repeatmin, Repeatmax + TargetBuffed = 24, // Param1 = Spellid, Param2 = Stack Amount, Param3/4 Repeatmin, Repeatmax + Reset = 25, // Called After Combat, When The Creature Respawn And Spawn. + IcLos = 26, // Nohostile, Maxrnage, Cooldownmin, Cooldownmax + PassengerBoarded = 27, // Cooldownmin, Cooldownmax + PassengerRemoved = 28, // Cooldownmin, Cooldownmax + Charmed = 29, // onRemove (0 - on apply, 1 - on remove) + CharmedTarget = 30, // None + SpellhitTarget = 31, // Spellid, School, Cooldownmin, Cooldownmax + Damaged = 32, // Mindmg, Maxdmg, Cooldownmin, Cooldownmax + DamagedTarget = 33, // Mindmg, Maxdmg, Cooldownmin, Cooldownmax + Movementinform = 34, // Movementtype(Any), Pointid + SummonDespawned = 35, // Entry, Cooldownmin, Cooldownmax + CorpseRemoved = 36, // None + AiInit = 37, // None + DataSet = 38, // Id, Value, Cooldownmin, Cooldownmax + WaypointStart = 39, // Pointid(0any), Pathid(0any) + WaypointReached = 40, // Pointid(0any), Pathid(0any) + TransportAddplayer = 41, // None + TransportAddcreature = 42, // Entry (0 Any) + TransportRemovePlayer = 43, // None + TransportRelocate = 44, // Pointid + InstancePlayerEnter = 45, // Team (0 Any), Cooldownmin, Cooldownmax + AreatriggerOntrigger = 46, // Triggerid(0 Any) + QuestAccepted = 47, // None + QuestObjCompletion = 48, // None + QuestCompletion = 49, // None + QuestRewarded = 50, // None + QuestFail = 51, // None + TextOver = 52, // Groupid From CreatureText, Creature Entry Who Talks (0 Any) + ReceiveHeal = 53, // Minheal, Maxheal, Cooldownmin, Cooldownmax + JustSummoned = 54, // None + WaypointPaused = 55, // Pointid(0any), Pathid(0any) + WaypointResumed = 56, // Pointid(0any), Pathid(0any) + WaypointStopped = 57, // Pointid(0any), Pathid(0any) + WaypointEnded = 58, // Pointid(0any), Pathid(0any) + TimedEventTriggered = 59, // Id + Update = 60, // Initialmin, Initialmax, Repeatmin, Repeatmax + Link = 61, // Internal Usage, No Params, Used To Link Together Multiple Events, Does Not Use Any Extra Resources To Iterate Event Lists Needlessly + GossipSelect = 62, // Menuid, Actionid + JustCreated = 63, // None + GossipHello = 64, // None + FollowCompleted = 65, // None + DummyEffect = 66, // Spellid, Effectindex + IsBehindTarget = 67, // Cooldownmin, Cooldownmax + GameEventStart = 68, // GameEvent.Entry + GameEventEnd = 69, // GameEvent.Entry + GoStateChanged = 70, // Go State + GoEventInform = 71, // Eventid + ActionDone = 72, // Eventid (Shareddefines.Eventid) + OnSpellclick = 73, // Clicker (Unit) + FriendlyHealthPCT = 74,// minHpPct, maxHpPct, repeatMin, repeatMax + DistanceCreature = 75, // guid, entry, distance, repeat + DistanceGameobject = 76, // guid, entry, distance, repeat + CounterSet = 77, // id, value, cooldownMin, cooldownMax + SceneStart = 78, // none + SceneTrigger = 79, // param_string : triggerName + SceneCancel = 80, // none + SceneComplete = 81, // none + + End = 82 + } + + public enum SmartActions + { + None = 0, // No Action + Talk = 1, // Groupid From CreatureText, Duration To Wait Before TextOver Event Is Triggered, useTalkTarget (0/1) - use target as talk target + SetFaction = 2, // Factionid (Or 0 For Default) + MorphToEntryOrModel = 3, // CreatureTemplate Entry(Param1) Or Modelid (Param2) (Or 0 For Both To Demorph) + Sound = 4, // Soundid, Textrange + PlayEmote = 5, // Emoteid + FailQuest = 6, // Questid + OfferQuest = 7, // Questid, directAdd + SetReactState = 8, // State + ActivateGobject = 9, // + RandomEmote = 10, // Emoteid1, Emoteid2, Emoteid3... + Cast = 11, // Spellid, Castflags, TriggeredFlags + SummonCreature = 12, // Creatureid, Summontype, Duration In Ms, Storageid, Attackinvoker, + ThreatSinglePct = 13, // Threat% + ThreatAllPct = 14, // Threat% + CallAreaexploredoreventhappens = 15, // Questid + SetIngamePhaseGroup = 16, // phaseGroupId, apply + SetEmoteState = 17, // Emoteid + SetUnitFlag = 18, // Flags (May Be More Than One Field Or'D Together), Target + RemoveUnitFlag = 19, // Flags (May Be More Than One Field Or'D Together), Target + AutoAttack = 20, // Allowattackstate (0 = Stop Attack, Anything Else Means Continue Attacking) + AllowCombatMovement = 21, // Allowcombatmovement (0 = Stop Combat Based Movement, Anything Else Continue Attacking) + SetEventPhase = 22, // Phase + IncEventPhase = 23, // Value (May Be Negative To Decrement Phase, Should Not Be 0) + Evade = 24, // No Params + FleeForAssist = 25, // With Emote + CallGroupeventhappens = 26, // Questid + CombatStop = 27, // + Removeaurasfromspell = 28, // Spellid, 0 Removes All Auras + Follow = 29, // Distance (0 = Default), Angle (0 = Default), Endcreatureentry, Credit, Credittype (0monsterkill, 1event) + RandomPhase = 30, // Phaseid1, Phaseid2, Phaseid3... + RandomPhaseRange = 31, // Phasemin, Phasemax + ResetGobject = 32, // + CallKilledmonster = 33, // Creatureid, + SetInstData = 34, // Field, Data, Type (0 = SetData, 1 = SetBossState) + SetInstData64 = 35, // Field, + UpdateTemplate = 36, // Entry + Die = 37, // No Params + SetInCombatWithZone = 38, // No Params + CallForHelp = 39, // Radius, With Emote + SetSheath = 40, // Sheath (0-Unarmed, 1-Melee, 2-Ranged) + ForceDespawn = 41, // Timer + SetInvincibilityHpLevel = 42, // Minhpvalue(+Pct, -Flat) + MountToEntryOrModel = 43, // CreatureTemplate Entry(Param1) Or Modelid (Param2) (Or 0 For Both To Dismount) + SetIngamePhaseId = 44, // Id + SetData = 45, // Field, Data (Only Creature Todo) + //46 Unused + SetVisibility = 47, // On/Off + SetActive = 48, // No Params + AttackStart = 49, // + SummonGo = 50, // Gameobjectid, Despawntime In Ms, + KillUnit = 51, // + ActivateTaxi = 52, // Taxiid + WpStart = 53, // Run/Walk, Pathid, Canrepeat, Quest, Despawntime, Reactstate + WpPause = 54, // Time + WpStop = 55, // Despawntime, Quest, Fail? + AddItem = 56, // Itemid, Count + RemoveItem = 57, // Itemid, Count + InstallAiTemplate = 58, // Aitemplateid + SetRun = 59, // 0/1 + SetFly = 60, // 0/1 + SetSwim = 61, // 0/1 + Teleport = 62, // Mapid, + SetCounter = 63, // id, value, reset (0/1) + StoreTargetList = 64, // Varid, + WpResume = 65, // None + SetOrientation = 66, // + CreateTimedEvent = 67, // Id, Initialmin, Initialmax, Repeatmin(Only If It Repeats), Repeatmax(Only If It Repeats), Chance + Playmovie = 68, // Entry + MoveToPos = 69, // PointId, transport, disablePathfinding + RespawnTarget = 70, // + Equip = 71, // Entry, Slotmask Slot1, Slot2, Slot3 , Only Slots With Mask Set Will Be Sent To Client, Bits Are 1, 2, 4, Leaving Mask 0 Is Defaulted To Mask 7 (Send All), Slots1-3 Are Only Used If No Entry Is Set + CloseGossip = 72, // None + TriggerTimedEvent = 73, // Id(>1) + RemoveTimedEvent = 74, // Id(>1) + AddAura = 75, // Spellid, Targets + OverrideScriptBaseObject = 76, // Warning: Can Crash Core, Do Not Use If You Dont Know What You Are Doing + ResetScriptBaseObject = 77, // None + CallScriptReset = 78, // None + SetRangedMovement = 79, // Distance, Angle + CallTimedActionlist = 80, // Id (Overwrites Already Running Actionlist), Stop After Combat?(0/1), Timer Update Type(0-Ooc, 1-Ic, 2-Always) + SetNpcFlag = 81, // Flags + AddNpcFlag = 82, // Flags + RemoveNpcFlag = 83, // Flags + SimpleTalk = 84, // Groupid, Can Be Used To Make Players Say Groupid, TextOver Event Is Not Triggered, Whisper Can Not Be Used (Target Units Will Say The Text) + InvokerCast = 85, // Spellid, Castflags, If Avaliable, Last Used Invoker Will Cast Spellid With Castflags On Targets + CrossCast = 86, // Spellid, Castflags, Castertargettype, Castertarget Param1, Castertarget Param2, Castertarget Param3, ( + The Origonal Target Fields As Destination Target), Castertargets Will Cast Spellid On All Targets (Use With Caution If Targeting Multiple * Multiple Units) + CallRandomTimedActionlist = 87, // Script9 Ids 1-9 + CallRandomRangeTimedActionlist = 88, // Script9 Id Min, Max + RandomMove = 89, // Maxdist + SetUnitFieldBytes1 = 90, // Bytes, Target + RemoveUnitFieldBytes1 = 91, // Bytes, Target + InterruptSpell = 92, + SendGoCustomAnim = 93, // Anim Id + SetDynamicFlag = 94, // Flags + AddDynamicFlag = 95, // Flags + RemoveDynamicFlag = 96, // Flags + JumpToPos = 97, // Speedxy, Speedz, Targetx, Targety, Targetz + SendGossipMenu = 98, // Menuid, Optionid + GoSetLootState = 99, // State + SendTargetToTarget = 100, // Id + SetHomePos = 101, // None + SetHealthRegen = 102, // 0/1 + SetRoot = 103, // Off/On + SetGoFlag = 104, // Flags + AddGoFlag = 105, // Flags + RemoveGoFlag = 106, // Flags + SummonCreatureGroup = 107, // Group, Attackinvoker + SetPower = 108, // PowerType, newPower + AddPower = 109, // PowerType, newPower + RemovePower = 110, // PowerType, newPower + GameEventStop = 111, // GameEventId + GameEventStart = 112, // GameEventId + StartClosestWaypoint = 113, // wp1, wp2, wp3, wp4, wp5, wp6, wp7 + MoveOffset = 114, + RandomSound = 115, // SoundId1, SoundId2, SoundId3, SoundId4, SoundId5, onlySelf + SetCorpseDelay = 116, // timer + DisableEvade = 117, // 0/1 (1 = disabled, 0 = enabled) + // 118 - 127 : 3.3.5 reserved + PlayAnimkit = 128, + ScenePlay = 129, // sceneId + SceneCancel = 130, // sceneId + + End = 131 + } + + public enum SmartTargets + { + None = 0, // None, Defaulting To Invoket + Self = 1, // Self Cast + Victim = 2, // Our Current Target (Ie: Highest Aggro) + HostileSecondAggro = 3, // Second Highest Aggro + HostileLastAggro = 4, // Dead Last On Aggro + HostileRandom = 5, // Just Any Random Target On Our Threat List + HostileRandomNotTop = 6, // Any Random Target Except Top Threat + ActionInvoker = 7, // Unit Who Caused This Event To Occur + Position = 8, // Use Xyz From Event Params + CreatureRange = 9, // Creatureentry(0any), Mindist, Maxdist + CreatureGuid = 10, // Guid, Entry + CreatureDistance = 11, // Creatureentry(0any), Maxdist + Stored = 12, // Id, Uses Pre-Stored Target(List) + GameobjectRange = 13, // Entry(0any), Min, Max + GameobjectGuid = 14, // Guid, Entry + GameobjectDistance = 15, // Entry(0any), Maxdist + InvokerParty = 16, // Invoker'S Party Members + PlayerRange = 17, // Min, Max + PlayerDistance = 18, // Maxdist + ClosestCreature = 19, // Creatureentry(0any), Maxdist, Dead? + ClosestGameobject = 20, // Entry(0any), Maxdist + ClosestPlayer = 21, // Maxdist + ActionInvokerVehicle = 22, // Unit'S Vehicle Who Caused This Event To Occur + OwnerOrSummoner = 23, // Unit'S Owner Or Summoner + ThreatList = 24, // All Units On Creature'S Threat List + ClosestEnemy = 25, // maxDist, playerOnly + ClosestFriendly = 26, // maxDist, playerOnly + LootRecipients = 27, // all players that have tagged this creature (for kill credit) + Farthest = 28, // maxDist, playerOnly, isInLos + VehicleAccessory = 29, // seat number (vehicle can target it's own accessory) + + End = 30 + } +} diff --git a/Framework/Constants/Spells/SkillConst.cs b/Framework/Constants/Spells/SkillConst.cs new file mode 100644 index 000000000..c71e1e58e --- /dev/null +++ b/Framework/Constants/Spells/SkillConst.cs @@ -0,0 +1,246 @@ +/* + * Copyright (C) 2012-2017 CypherCore + * + * 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, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +namespace Framework.Constants +{ + public struct SkillConst + { + public const int MaxPlayerSkills = 128; + public const uint MaxSkillStep = 15; + } + + public enum SkillType + { + None = 0, + + Swords = 43, + Axes = 44, + Bows = 45, + Guns = 46, + Maces = 54, + TwoHandedSwords = 55, + Defense = 95, + LangCommon = 98, + RacialDwarf = 101, + LangOrcish = 109, + LangDwarven = 111, + LangDarnassian = 113, + LangTaurahe = 115, + DualWield = 118, + RacialTauren = 124, + RacialOrc = 125, + RacialNightElf = 126, + FirstAid = 129, + Staves = 136, + LangThalassian = 137, + LangDraconic = 138, + LangDemonTongue = 139, + LangTitan = 140, + LangOldTongue = 141, + Survival = 142, + HorseRiding = 148, + WolfRiding = 149, + TigerRiding = 150, + RamRiding = 152, + Swimming = 155, + TwoHandedMaces = 160, + Unarmed = 162, + Blacksmithing = 164, + Leatherworking = 165, + Alchemy = 171, + TwoHandedAxes = 172, + Daggers = 173, + Herbalism = 182, + GenericDnd = 183, + Cooking = 185, + Mining = 186, + PetImp = 188, + PetFelhunter = 189, + Tailoring = 197, + Engineering = 202, + PetSpider = 203, + PetVoidwalker = 204, + PetSuccubus = 205, + PetInfernal = 206, + PetDoomguard = 207, + PetWolf = 208, + PetCat = 209, + PetBear = 210, + PetBoar = 211, + PetCrocolisk = 212, + PetCarrionBird = 213, + PetCrab = 214, + PetGorilla = 215, + PetRaptor = 217, + PetTallstrider = 218, + RacialUndead = 220, + Crossbows = 226, + Wands = 228, + Polearms = 229, + PetScorpid = 236, + PetTurtle = 251, + PetGenericHunter = 270, + PlateMail = 293, + LangGnomish = 313, + LangTroll = 315, + Enchanting = 333, + Fishing = 356, + Skinning = 393, + Mail = 413, + Leather = 414, + Cloth = 415, + Shield = 433, + FistWeapons = 473, + RaptorRiding = 533, + MechanostriderPiloting = 553, + UndeadHorsemanship = 554, + PetBat = 653, + PetHyena = 654, + PetBirdOfPrey = 655, + PetWindSerpent = 656, + LangForsaken = 673, + KodoRiding = 713, + RacialTroll = 733, + RacialGnome = 753, + RacialHuman = 754, + Jewelcrafting = 755, + RacialBloodElf = 756, + PetEventRemoteControl = 758, + LangDraenei = 759, + RacialDraenei = 760, + PetFelguard = 761, + Riding = 762, + PetDragonhawk = 763, + PetNetherRay = 764, + PetSporebat = 765, + PetWarpStalker = 766, + PetRavager = 767, + PetSerpent = 768, + Internal = 769, + Inscription = 773, + PetMoth = 775, + Mounts = 777, + Companions = 778, + PetExoticChimaera = 780, + PetExoticDevilsaur = 781, + PetGhoul = 782, + PetExoticSilithid = 783, + PetExoticWorm = 784, + PetWasp = 785, + PetExoticClefthoof = 786, + PetExoticCoreHound = 787, + PetExoticSpiritBeast = 788, + RacialWorgen = 789, + RacialGoblin = 790, + LangGilnean = 791, + LangGoblin = 792, + Archaeology = 794, + Hunter = 795, + DeathKnight = 796, + Druid = 798, + Paladin = 800, + Priest = 804, + PetWaterElemental = 805, + PetFox = 808, + AllGlyphs = 810, + PetDog = 811, + PetMonkey = 815, + PetShaleSpider = 817, + Beetle = 818, + AllGuildPerks = 821, + PetHydra = 824, + Monk = 829, + Warrior = 840, + Warlock = 849, + RacialPandaren = 899, + Mage = 904, + LangPandarenNeutral = 905, + LangPandarenAlliance = 906, + LangPandarenHorde = 907, + Rogue = 921, + Shaman = 924, + FelImp = 927, + Voidlord = 928, + Shivarra = 929, + Observer = 930, + Wrathguard = 931, + AllSpecializations = 934, + Runeforging = 960, + PetPrimalFireElemental = 962, + PetPrimalEarthElemental = 963, + WayOfTheGrill = 975, + WayOfTheWok = 976, + WayOfThePot = 977, + WayOfTheSteamer = 978, + WayOfTheOven = 979, + WayOfTheBrew = 980, + ApprenticeCooking = 981, + JourneymanCookbook = 982, + Porcupine = 983, + Crane = 984, + WaterStrider = 985, + PetExoticQuilen = 986, + PetGoat = 987, + Basilisk = 988, + NoPlayers = 999, + Direhorn = 1305, + PetPrimalStormElemental = 1748, + PetWaterElementalMinorTalentVersion = 1777, + PetExoticRylak = 1818, + PetRiverbeast = 1819, + Unused = 1830, + DemonHunter = 1848, + Logging = 1945, + PetTerrorguard = 1981, + PetAbyssal = 1982, + PetStag = 1993, + TradingPost = 2000, + Warglaives = 2152, + PetMechanical = 2189, + PetAbomination = 2216, + } + + public enum SkillState + { + Unchanged = 0, + Changed = 1, + New = 2, + Deleted = 3 + } + + public enum SkillCategory : byte + { + Unk = 0, + Attributes = 5, + Weapon = 6, + Class = 7, + Armor = 8, + Secondary = 9, + Languages = 10, + Profession = 11, + Generic = 12 + } + + public enum SkillRangeType + { + Language, // 300..300 + Level, // 1..max skill for level + Mono, // 1..1, grey monolite bar + Rank, // 1..skill for known rank + None // 0..0 always + } +} diff --git a/Framework/Constants/Spells/SpellAuraConst.cs b/Framework/Constants/Spells/SpellAuraConst.cs new file mode 100644 index 000000000..9c7d18a63 --- /dev/null +++ b/Framework/Constants/Spells/SpellAuraConst.cs @@ -0,0 +1,565 @@ +/* + * Copyright (C) 2012-2017 CypherCore + * + * 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, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +namespace Framework.Constants +{ + public enum AuraType + { + Any = -1, + + None = 0, + BindSight = 1, + ModPossess = 2, + PeriodicDamage = 3, + Dummy = 4, + ModConfuse = 5, + ModCharm = 6, + ModFear = 7, + PeriodicHeal = 8, + ModAttackspeed = 9, + ModThreat = 10, + ModTaunt = 11, + ModStun = 12, + ModDamageDone = 13, + ModDamageTaken = 14, + DamageShield = 15, + ModStealth = 16, + ModStealthDetect = 17, + ModInvisibility = 18, + ModInvisibilityDetect = 19, + ObsModHealth = 20, // 20, 21 Unofficial + ObsModPower = 21, + ModResistance = 22, + PeriodicTriggerSpell = 23, + PeriodicEnergize = 24, + ModPacify = 25, + ModRoot = 26, + ModSilence = 27, + ReflectSpells = 28, + ModStat = 29, + ModSkill = 30, + ModIncreaseSpeed = 31, + ModIncreaseMountedSpeed = 32, + ModDecreaseSpeed = 33, + ModIncreaseHealth = 34, + ModIncreaseEnergy = 35, + ModShapeshift = 36, + EffectImmunity = 37, + StateImmunity = 38, + SchoolImmunity = 39, + DamageImmunity = 40, + DispelImmunity = 41, + ProcTriggerSpell = 42, + ProcTriggerDamage = 43, + TrackCreatures = 44, + TrackResources = 45, + Unk46 = 46, // Ignore All Gear Test Spells + ModParryPercent = 47, + Unk48 = 48, // One Periodic Spell + ModDodgePercent = 49, + ModCriticalHealingAmount = 50, + ModBlockPercent = 51, + ModWeaponCritPercent = 52, + PeriodicLeech = 53, + ModHitChance = 54, + ModSpellHitChance = 55, + Transform = 56, + ModSpellCritChance = 57, + ModIncreaseSwimSpeed = 58, + ModDamageDoneCreature = 59, + ModPacifySilence = 60, + ModScale = 61, + PeriodicHealthFunnel = 62, + ModAdditionalPowerCost = 63, // NYI + PeriodicManaLeech = 64, + ModCastingSpeedNotStack = 65, + FeignDeath = 66, + ModDisarm = 67, + ModStalked = 68, + SchoolAbsorb = 69, + ExtraAttacks = 70, + Unk71 = 71, + ModPowerCostSchoolPct = 72, + ModPowerCostSchool = 73, + ReflectSpellsSchool = 74, + ModLanguage = 75, + FarSight = 76, + MechanicImmunity = 77, + Mounted = 78, + ModDamagePercentDone = 79, + ModPercentStat = 80, + SplitDamagePct = 81, + WaterBreathing = 82, + ModBaseResistance = 83, + ModRegen = 84, + ModPowerRegen = 85, + ChannelDeathItem = 86, + ModDamagePercentTaken = 87, + ModHealthRegenPercent = 88, + PeriodicDamagePercent = 89, + Unk90 = 90, // Old ModResistChance + ModDetectRange = 91, + PreventsFleeing = 92, + ModUnattackable = 93, + InterruptRegen = 94, + Ghost = 95, + SpellMagnet = 96, + ManaShield = 97, + ModSkillTalent = 98, + ModAttackPower = 99, + AurasVisible = 100, + ModResistancePct = 101, + ModMeleeAttackPowerVersus = 102, + ModTotalThreat = 103, + WaterWalk = 104, + FeatherFall = 105, + Hover = 106, + AddFlatModifier = 107, + AddPctModifier = 108, + AddTargetTrigger = 109, + ModPowerRegenPercent = 110, + AddCasterHitTrigger = 111, + OverrideClassScripts = 112, + ModRangedDamageTaken = 113, + ModRangedDamageTakenPct = 114, + ModHealing = 115, + ModRegenDuringCombat = 116, + ModMechanicResistance = 117, + ModHealingPct = 118, + Unk119 = 119, // Old SharePetTracking + Untrackable = 120, + Empathy = 121, + ModOffhandDamagePct = 122, + ModTargetResistance = 123, + ModRangedAttackPower = 124, + ModMeleeDamageTaken = 125, + ModMeleeDamageTakenPct = 126, + RangedAttackPowerAttackerBonus = 127, + ModPossessPet = 128, + ModSpeedAlways = 129, + ModMountedSpeedAlways = 130, + ModRangedAttackPowerVersus = 131, + ModIncreaseEnergyPercent = 132, + ModIncreaseHealthPercent = 133, + ModManaRegenInterrupt = 134, + ModHealingDone = 135, + ModHealingDonePercent = 136, + ModTotalStatPercentage = 137, + ModMeleeHaste = 138, + ForceReaction = 139, + ModRangedHaste = 140, + Unk141 = 141, // Old ModRangedAmmoHaste, Unused Now + ModBaseResistancePct = 142, + ModRecoveryRate = 143, // NYI + SafeFall = 144, + ModPetTalentPoints = 145, + AllowTamePetType = 146, + MechanicImmunityMask = 147, + RetainComboPoints = 148, + ReducePushback = 149, // Reduce Pushback + ModShieldBlockvaluePct = 150, + TrackStealthed = 151, // Track Stealthed + ModDetectedRange = 152, // Mod Detected Range + Unk153 = 153, // Old SplitDamageFlat. Unused 4.3.4 + ModStealthLevel = 154, // Stealth Level Modifier + ModWaterBreathing = 155, // Mod Water Breathing + ModReputationGain = 156, // Mod Reputation Gain + PetDamageMulti = 157, // Mod Pet Damage + ModShieldBlockvalue = 158, + NoPvpCredit = 159, + Unk160 = 160, // Old ModAoeAvoidance. Unused 4.3.4 + ModHealthRegenInCombat = 161, + PowerBurn = 162, + ModCritDamageBonus = 163, + Unk164 = 164, + MeleeAttackPowerAttackerBonus = 165, + ModAttackPowerPct = 166, + ModRangedAttackPowerPct = 167, + ModDamageDoneVersus = 168, + Unk169 = 169, // Old ModCritPercentVersus. Unused 4.3.4 + DetectAmore = 170, + ModSpeedNotStack = 171, + ModMountedSpeedNotStack = 172, + ModRecoveryRate2 = 173, // NYI + ModSpellDamageOfStatPercent = 174, // By Defeult Intelect, Dependent From ModSpellHealingOfStatPercent + ModSpellHealingOfStatPercent = 175, + SpiritOfRedemption = 176, + AoeCharm = 177, + ModMaxPowerPct = 178, // NYI + ModPowerDisplay = 179, + ModFlatSpellDamageVersus = 180, + Unk181 = 181, // Old ModFlatSpellCritDamageVersus - Possible Flat Spell Crit Damage Versus + ModResistanceOfStatPercent = 182, + ModCriticalThreat = 183, + ModAttackerMeleeHitChance = 184, + ModAttackerRangedHitChance = 185, + ModAttackerSpellHitChance = 186, + ModAttackerMeleeCritChance = 187, + ModAttackerRangedCritChance = 188, + ModRating = 189, + ModFactionReputationGain = 190, + UseNormalMovementSpeed = 191, + ModMeleeRangedHaste = 192, + MeleeSlow = 193, + ModTargetAbsorbSchool = 194, + ModTargetAbilityAbsorbSchool = 195, + ModCooldown = 196, // Only 24818 Noxious Breath + ModAttackerSpellAndWeaponCritChance = 197, + Unk198 = 198, // Old ModAllWeaponSkills + Unk199 = 199, // Old ModIncreasesSpellPctToHit. Unused 4.3.4 + ModXpPct = 200, + Fly = 201, + IgnoreCombatResult = 202, + ModAttackerMeleeCritDamage = 203, + ModAttackerRangedCritDamage = 204, + ModChargeCooldown = 205, // NYI + ModIncreaseVehicleFlightSpeed = 206, + ModIncreaseMountedFlightSpeed = 207, + ModIncreaseFlightSpeed = 208, + ModMountedFlightSpeedAlways = 209, + ModVehicleSpeedAlways = 210, + ModFlightSpeedNotStack = 211, + ModHonorGainPct = 212, + ModRageFromDamageDealt = 213, + Unk214 = 214, + ArenaPreparation = 215, + HasteSpells = 216, + ModMeleeHaste2 = 217, + Unk218 = 218, // old SPELL_AURA_HASTE_RANGED + ModManaRegenFromStat = 219, + ModRatingFromStat = 220, + ModDetaunt = 221, + Unk222 = 222, + RaidProcFromCharge = 223, + Unk224 = 224, + RaidProcFromChargeWithValue = 225, + PeriodicDummy = 226, + PeriodicTriggerSpellWithValue = 227, + DetectStealth = 228, + ModAoeDamageAvoidance = 229, + ModMaxHealth = 230, + ProcTriggerSpellWithValue = 231, + MechanicDurationMod = 232, + ChangeModelForAllHumanoids = 233, // Client-Side Only + MechanicDurationModNotStack = 234, + ModDispelResist = 235, + ControlVehicle = 236, + ModSpellDamageOfAttackPower = 237, + ModSpellHealingOfAttackPower = 238, + ModScale2 = 239, + ModExpertise = 240, + ForceMoveForward = 241, + ModSpellDamageFromHealing = 242, + ModFaction = 243, + ComprehendLanguage = 244, + ModAuraDurationByDispel = 245, + ModAuraDurationByDispelNotStack = 246, + CloneCaster = 247, + ModCombatResultChance = 248, + ConvertRune = 249, + ModIncreaseHealth2 = 250, + ModEnemyDodge = 251, + ModSpeedSlowAll = 252, + ModBlockCritChance = 253, + ModDisarmOffhand = 254, + ModMechanicDamageTakenPercent = 255, + NoReagentUse = 256, + ModTargetResistBySpellClass = 257, + OverrideSummonedObject = 258, + Unk259 = 259, // Old ModHotPct, Unused 4.3.4 + ScreenEffect = 260, + Phase = 261, + AbilityIgnoreAurastate = 262, + AllowOnlyAbility = 263, + Unk264 = 264, + Unk265 = 265, + Unk266 = 266, + ModImmuneAuraApplySchool = 267, + Unk268 = 268, // Old ModAttackPowerOfStatPercent. Unused 4.3.4 + ModIgnoreTargetResist = 269, + ModSchoolMaskDamageFromCaster = 270, // NYI + ModSpellDamageFromCaster = 271, + IgnoreMeleeReset = 272, + XRay = 273, + Unk274 = 274, // Old AbilityConsumeNoAmmo, Unused 4.3.4 + ModIgnoreShapeshift = 275, + ModDamageDoneForMechanic = 276, + Unk277 = 277, // Old ModMaxAffectedTargets. Unused 4.3.4 + ModDisarmRanged = 278, + InitializeImages = 279, + Unk280 = 280, // Old ModArmorPenetrationPct Unused 4.3.4 + ModGuildReputationGainPct = 281, // NYI + ModBaseHealthPct = 282, + ModHealingReceived = 283, // Possibly Only For Some Spell Family Class Spells + Linked = 284, + ModAttackPowerOfArmor = 285, + AbilityPeriodicCrit = 286, + DeflectSpells = 287, + IgnoreHitDirection = 288, + PreventDurabilityLoss = 289, + ModCritPct = 290, + ModXpQuestPct = 291, + OpenStable = 292, + OverrideSpells = 293, + PreventRegeneratePower = 294, + Unk295 = 295, + SetVehicleId = 296, + BlockSpellFamily = 297, + Strangulate = 298, + Unk299 = 299, + ShareDamagePct = 300, + SchoolHealAbsorb = 301, + Unk302 = 302, + ModDamageDoneVersusAurastate = 303, + ModFakeInebriate = 304, + ModMinimumSpeed = 305, + Unk306 = 306, + HealAbsorbTest = 307, + ModCritChanceForCaster = 308, + ModResilience = 309, // NYI + ModCreatureAoeDamageAvoidance = 310, + Unk311 = 311, + AnimReplacementSet = 312, + Unk313 = 313, // Not Used In 4.3.4 - Related To Mounts + PreventResurrection = 314, + UnderwaterWalking = 315, + PeriodicHaste = 316, // Not Used In 4.3.4 (Name From 3.3.5a) + ModSpellPowerPct = 317, + Mastery = 318, + ModMeleeHaste3 = 319, + ModRangedHaste2 = 320, + ModNoActions = 321, + InterfereTargetting = 322, + Unk323 = 323, // Not Used In 4.3.4 + Unk324 = 324, // Spell Critical Chance (Probably By School Mask) + Unk325 = 325, // Not Used In 4.3.4 + PhaseGroup = 326, // Phase Related + Unk327 = 327, // Not Used In 4.3.4 + ProcOnPowerAmount = 328, + ModPowerGainPct = 329, // Nyi + CastWhileWalking = 330, + ForceWeather = 331, + OverrideActionbarSpells = 332, + OverrideActionbarSpellsTriggered = 333, // Spells cast with this override have no cast time or power cost + ModBlind = 334, // Nyi + Unk335 = 335, + ModFlyingRestrictions = 336, // Nyi + ModVendorItemsPrices = 337, + ModDurabilityLoss = 338, + IncreaseSkillGainChance = 339, // Nyi + ModResurrectedHealthByGuildMember = 340, // Increases Health Gained When Resurrected By A Guild Member By X + ModSpellCategoryCooldown = 341, + ModMeleeRangedHaste2 = 342, + ModMeleeDamageFromCaster = 343, + ModAutoattackDamage = 344, + BypassArmorForCaster = 345, + EnableAltPower = 346, // Nyi + ModSpellCooldownByHaste = 347, + DepositBonusMoneyInGuildBankOnLoot = 348, // Nyi + ModCurrencyGain = 349, + ModGatheringItemsGainedPercent = 350, // Nyi + Unk351 = 351, + Unk352 = 352, + ModCamouflage = 353, // Nyi + Unk354 = 354, // Restoration Shaman Mastery - Mod Healing Based On Target'S Health (Less = More Healing) + ModCastingSpeed = 355, // NYI + Unk356 = 356, // Arcane Mage Mastery - Mod Damage Based On Current Mana + EnableBoss1UnitFrame = 357, + WorgenAlteredForm = 358, + Unk359 = 359, + ProcTriggerSpellCopy = 360, // Procs The Same Spell That Caused This Proc (Dragonwrath, Tarecgosa'S Rest) + OverrideAutoattackWithMeleeSpell = 361, // NYI + Unk362 = 362, // Not Used In 4.3.4 + ModNextSpell = 363, // Used By 101601 Throw Totem - Causes The Client To Initialize Spell Cast With Specified Spell + Unk364 = 364, // Not Used In 4.3.4 + MaxFarClipPlane = 365, // Overrides Client'S View Distance Setting To Max("Fair", CurrentSetting) And Turns Off Terrain Display + OverrideSpellPowerByApPct = 366, // Sets Spellpower Equal To % Of Attack Power, Discarding All Other Bonuses (From Gear And Buffs) + OverrideAutoattackWithRangedSpell = 367, // NYI + Unk368 = 368, // Not Used In 4.3.4 + EnablePowerBarTimer = 369, + SetFairFarClip = 370, // Overrides Client'S View Distance Setting To Max("Fair", CurrentSetting) + Unk371 = 371, + Unk372 = 372, + ModSpeedNoControl = 373, // NYI + ModifyFallDamagePct = 374, + Unk375 = 375, + ModCurrencyGainFromSource = 376, // NYI + CastWhileWalking2 = 377, // NYI + Unk378 = 378, + Unk379 = 379, + ModGlobalCooldownByHaste = 380, // Allows melee abilities to benefit from haste GCD reduction + Unk381 = 381, + ModPetStatPct = 382, // NYI + IgnoreSpellCooldown = 383, + Unk384 = 384, + ChanceOverrideAutoattackWithSpellOnSelf = 385,// NYI (with triggered spell cast by the initial caster?) + Unk386 = 386, + Unk387 = 387, + ModTaxiFlightSpeed = 388, + Unk389 = 389, + Unk390 = 390, + Unk391 = 391, + Unk392 = 392, + Unk393 = 393, + ShowConfirmationPrompt = 394, + AreaTrigger = 395, // NYI + ProcOnPowerAmount2 = 396, + Unk397 = 397, + Unk398 = 398, + Unk399 = 399, + ModSkill2 = 400, + Unk401 = 401, + ModOverridePowerDisplay = 402, + OverrideSpellVisual = 403, + OverrideAttackPowerBySpPct = 404, + ModRatingPct = 405, // NYI + KeyboundOverride = 406, // NYI + ModFear2 = 407, + Unk408 = 408, + CanTurnWhileFalling = 409, + Unk410 = 410, + ModMaxCharges = 411, + Unk412 = 412, + Unk413 = 413, + Unk414 = 414, + Unk415 = 415, + ModCooldownByHasteRegen = 416, + ModGlobalCooldownByHasteRegen = 417, + ModMaxPower = 418, // NYI + ModBaseManaPct = 419, + ModBattlePetXpPct = 420, + ModAbsorbEffectsAmountPct = 421, // NYI + Unk422 = 422, + Unk423 = 423, + Unk424 = 424, + Unk425 = 425, + Unk426 = 426, + ScalePlayerLevel = 427, // NYI + Unk428 = 428, + Unk429 = 429, + PlayScene = 430, + ModOverrideZonePvpType = 431, // NYI + Unk432 = 432, + Unk433 = 433, + Unk434 = 434, + Unk435 = 435, + ModEnvironmentalDamageTaken = 436, // NYI + ModMinimumSpeedRate = 437, + PreloadPhase = 438, // NYI + Unk439 = 439, + ModMultistrikeDamage = 440, // NYI + ModMultistrikeChance = 441, // NYI + ModReadiness = 442, // NYI + ModLeech = 443, // NYI + Unk444 = 444, + Unk445 = 445, + Unk446 = 446, + ModXpFromCreatureType = 447, + Unk448 = 448, + Unk449 = 449, + Unk450 = 450, + OverridePetSpecs = 451, + Unk452 = 452, + ChargeRecoveryMod = 453, + ChargeRecoveryMultiplier = 454, + ModRoot2 = 455, + ChargeRecoveryAffectedByHaste = 456, + ChargeRecoveryAffectedByHasteRegen = 457, + IgnoreDualWieldHitPenalty = 458, + IgnoreMovementForces = 459, + ResetCooldownsOnDuelStart = 460, + Unk461 = 461, + ModHealingAndAbsorbFromCaster = 462, // NYI + ConvertCritRatingPctToParryRating = 463, // NYI + ModAttackPowerOfBonusArmor = 464, // NYI + ModBonusArmor = 465, // NYI + ModBonusArmorPct = 466, + ModStatBonusPct = 467, + TriggerSpellOnHealthBelowPct = 468, + ShowConfirmationPromptWithDifficulty = 469, + Unk470 = 470, + ModVersatility = 471, // NYI + Unk472 = 472, + PreventDurabilityLossFromCombat = 473, + Unk474 = 474, + AllowUsingGameobjectsWhileMounted = 475, + ModCurrencyGainLooted = 476, + Unk477 = 477, + Unk478 = 478, + Unk479 = 479, + Unk480 = 480, + ConvertConsumedRune = 481, + Unk482 = 482, + SuppressTransforms = 483, // NYI + Unk484 = 484, + Unk485 = 485, + Unk486 = 486, + Unk487 = 487, + Unk488 = 488, + Unk489 = 489, + Unk490 = 490, + Unk491 = 491, + Total = 492 + } + + public enum AuraEffectHandleModes + { + Default = 0x0, + Real = 0x01, // Handler Applies/Removes Effect From Unit + SendForClient = 0x02, // Handler Sends Apply/Remove Packet To Unit + ChangeAmount = 0x04, // Handler Updates Effect On Target After Effect Amount Change + Reapply = 0x08, // Handler Updates Effect On Target After Aura Is Reapplied On Target + Stat = 0x10, // Handler Updates Effect On Target When Stat Removal/Apply Is Needed For Calculations By Core + Skill = 0x20, // Handler Updates Effect On Target When Skill Removal/Apply Is Needed For Calculations By Core + SendForClientMask = (SendForClient | Real), // Any Case Handler Need To Send Packet + ChangeAmountMask = (ChangeAmount | Real), // Any Case Handler Applies Effect Depending On Amount + ChangeAmountSendForClientMask = (ChangeAmountMask | SendForClientMask), + RealOrReapplyMask = (Reapply | Real) + } + + // Diminishing Returns Types + public enum DiminishingReturnsType + { + None = 0, // this spell is not diminished, but may have its duration limited + Player = 1, // this spell is diminished only when applied on players + All = 2 // this spell is diminished in every case + } + + // Diminishing Return Groups + public enum DiminishingGroup + { + None = 0, + Root = 1, + Stun = 2, + Incapacitate = 3, + Disorient = 4, + Silence = 5, + AOEKnockback = 6, + Taunt = 7, + LimitOnly = 8, + } + + public enum DiminishingLevels + { + Level1 = 0, + Level2 = 1, + Level3 = 2, + Immune = 3, + Level4 = 3, + TauntImmune = 4 + } +} diff --git a/Framework/Constants/Spells/SpellConst.cs b/Framework/Constants/Spells/SpellConst.cs new file mode 100644 index 000000000..50dad8cab --- /dev/null +++ b/Framework/Constants/Spells/SpellConst.cs @@ -0,0 +1,2433 @@ +/* + * Copyright (C) 2012-2017 CypherCore + * + * 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, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +namespace Framework.Constants +{ + public struct SpellConst + { + public const int MaxEffects = 32; + public const uint MaxEffectMask = 0xFFFFFFFF; + public const int MaxReagents = 8; + public const int MaxTotems = 2; + public const int MaxShapeshift = 8; + + public const int MaxAuras = 255; + + public const int EffectFirstFound = 254; + public const int EffectAll = 255; + } + + + // only used in code + public enum SpellCategories + { + HealthManaPotions = 4, + DevourMagic = 12, + Judgement = 1210, // Judgement (seal trigger) + Food = 11, + Drink = 59 + } + + public enum SpellLinkedType + { + Cast = 0, // +: cast; -: remove + Hit = 1 * 200000, + Aura = 2 * 200000, // +: aura; -: immune + Remove = 0 + } + + //Spell targets used by SelectSpell + public enum SelectTargetType + { + DontCare = 0, //All target types allowed + + Self, //Only Self casting + + SingleEnemy, //Only Single Enemy + AoeEnemy, //Only AoE Enemy + AnyEnemy, //AoE or Single Enemy + + SingleFriend, //Only Single Friend + AoeFriend, //Only AoE Friend + AnyFriend //AoE or Single Friend + } + + //Spell Effects used by SelectSpell + public enum SelectEffect + { + DontCare = 0, //All spell effects allowed + Damage, //Spell does damage + Healing, //Spell does healing + Aura //Spell applies an aura + } + + public enum SpellCastSource + { + Player = 2, + Normal = 3, + Item = 4, + Passive = 7, + Pet = 9, + Aura = 13, + Spell = 16, + } + + public enum SpellRangeFlag : byte + { + Default = 0, + Melee = 1, //melee + Ranged = 2 //hunter range and ranged weapon + } + + public enum SpellInterruptFlags + { + Movement = 0x01, // why need this for instant? + PushBack = 0x02, // push back + Unk3 = 0x04, // any info? + Interrupt = 0x08, // interrupt + AbortOnDmg = 0x10 // _complete_ interrupt on direct damage + //SPELL_INTERRUPT_UNK = 0x20 // unk, 564 of 727 spells having this spell start with "Glyph" + } + public enum SpellChannelInterruptFlags + { + Interrupt = 0x08, // interrupt + Delay = 0x4000 + } + + public enum SpellAuraInterruptFlags : uint + { + Hitbyspell = 0x01, // 0 Removed When Getting Hit By A Negative Spell? + TakeDamage = 0x02, // 1 Removed By Any Damage + Cast = 0x04, // 2 Cast Any Spells + Move = 0x08, // 3 Removed By Any Movement + Turning = 0x10, // 4 Removed By Any Turning + Jump = 0x20, // 5 Removed By Entering Combat + NotMounted = 0x40, // 6 Removed By Dismounting + NotAbovewater = 0x80, // 7 Removed By Entering Water + NotUnderwater = 0x100, // 8 Removed By Leaving Water + NotSheathed = 0x200, // 9 Removed By Unsheathing + Talk = 0x400, // 10 Talk To Npc / Loot? Action On Creature + Use = 0x800, // 11 Mine/Use/Open Action On Gameobject + MeleeAttack = 0x1000, // 12 Removed By Attacking + SpellAttack = 0x2000, // 13 ??? + Unk14 = 0x4000, // 14 + Transform = 0x8000, // 15 Removed By Transform? + Unk16 = 0x10000, // 16 + Mount = 0x20000, // 17 Misdirect, Aspect, Swim Speed + NotSeated = 0x40000, // 18 Removed By Standing Up (Used By Food And Drink Mostly And Sleep/Fake Death Like) + ChangeMap = 0x80000, // 19 Leaving Map/Getting Teleported + ImmuneOrLostSelection = 0x100000, // 20 Removed By Auras That Make You Invulnerable, Or Make Other To Lose Selection On You + Unk21 = 0x200000, // 21 + Teleported = 0x400000, // 22 + EnterPvpCombat = 0x800000, // 23 Removed By Entering Pvp Combat + DirectDamage = 0x1000000, // 24 Removed By Any Direct Damage + Landing = 0x2000000, // 25 Removed By Hitting The Ground + LeaveCombat = 0x80000000, // 31 removed by leaving combat + + NotVictim = (Hitbyspell | TakeDamage | DirectDamage) + } + + // Enum with EffectRadiusIndex and their actual radius + public enum EffectRadiusIndex + { + Yards2 = 7, + Yards5 = 8, + Yards20 = 9, + Yards30 = 10, + Yards45 = 11, + Yards100 = 12, + Yards10 = 13, + Yards8 = 14, + Yards3 = 15, + Yards1 = 16, + Yards13 = 17, + Yards15 = 18, + Yards18 = 19, + Yards25 = 20, + Yards35 = 21, + Yards200 = 22, + Yards40 = 23, + Yards65 = 24, + Yards70 = 25, + Yards4 = 26, + Yards50 = 27, + Yards50000 = 28, + Yards6 = 29, + Yards500 = 30, + Yards80 = 31, + Yards12 = 32, + Yards99 = 33, + Yards55 = 35, + Yards0 = 36, + Yards7 = 37, + Yards21 = 38, + Yards34 = 39, + Yards9 = 40, + Yards150 = 41, + Yards11 = 42, + Yards16 = 43, + Yards0_5 = 44, // 0.5 Yards + Yards10_2 = 45, + Yards5_2 = 46, + Yards15_2 = 47, + Yards60 = 48, + Yards90 = 49, + Yards15_3 = 50, + Yards60_2 = 51, + Yards5_3 = 52, + Yards60_3 = 53, + Yards50000_2 = 54, + Yards130 = 55, + Yards38 = 56, + Yards45_2 = 57, + Yards32 = 59, + Yards44 = 60, + Yards14 = 61, + Yards47 = 62, + Yards23 = 63, + Yards3_5 = 64, // 3.5 Yards + Yards80_2 = 65 + } + + + public enum SpellEffectImplicitTargetTypes + { + None = 0, + Explicit, + Caster + } + + // Spell dispel type + public enum DispelType + { + None = 0, + Magic = 1, + Curse = 2, + Disease = 3, + Poison = 4, + Stealth = 5, + Invisibility = 6, + ALL = 7, + SpeNPCOnly = 8, + Enrage = 9, + ZGTicket = 10, + OldUnused = 11, + + AllMask = ((1 << Magic) | (1 << Curse) | (1 << Disease) | (1 << Poison)) + } + + // Spell clasification + public enum SpellSpecificType + { + Normal = 0, + Seal = 1, + Aura = 3, + Sting = 4, + Curse = 5, + Aspect = 6, + Tracker = 7, + WarlockArmor = 8, + MageArmor = 9, + ElementalShield = 10, + MagePolymorph = 11, + Judgement = 13, + WarlockCorruption = 17, + Food = 19, + Drink = 20, + FoodAndDrink = 21, + Presence = 22, + Charm = 23, + Scroll = 24, + MageArcaneBrillance = 25, + WarriorEnrage = 26, + PriestDivineSpirit = 27, + Hand = 28, + Phase = 29, + Bane = 30 + } + + // Spell mechanics + public enum Mechanics : int + { + None = 0, + Charm = 1, + Disoriented = 2, + Disarm = 3, + Distract = 4, + Fear = 5, + Grip = 6, + Root = 7, + Slow_Attack = 8, + Silence = 9, + Sleep = 10, + Snare = 11, + Stun = 12, + Freeze = 13, + Knockout = 14, + Bleed = 15, + Bandage = 16, + Polymorph = 17, + Banish = 18, + Shield = 19, + Shackle = 20, + Mount = 21, + Infected = 22, + Turn = 23, + Horror = 24, + Invulnerability = 25, + Interrupt = 26, + Daze = 27, + Discovery = 28, + Immune_Shield = 29, // Divine (Blessing) Shield/Protection And Ice Block + Sapped = 30, + Enraged = 31, + Wounded = 32, + + ImmuneToMovementImpairmentAndLossControlMask = ((1 << Charm) | (1 << Disoriented) | + (1 << Fear) | (1 << Root) | (1 << Sleep) | (1 << Snare) | (1 << Stun) | + (1 << Freeze) | (1 << Silence) | (1 << Disarm) | (1 << Knockout) | + (1 << Polymorph) | (1 << Banish) | (1 << Shackle) | + (1 << Turn) | (1 << Horror) | (1 << Daze) | (1 << Sapped)) + } + + public enum SpellModOp + { + Damage = 0, + Duration = 1, + Threat = 2, + Effect1 = 3, + Charges = 4, + Range = 5, + Radius = 6, + CriticalChance = 7, + AllEffects = 8, + NotLoseCastingTime = 9, + CastingTime = 10, + Cooldown = 11, + Effect2 = 12, + IgnoreArmor = 13, + Cost = 14, // Used when SpellPowerEntry.PowerIndex == 0 + CritDamageBonus = 15, + ResistMissChance = 16, + JumpTargets = 17, + ChanceOfSuccess = 18, + ActivationTime = 19, + DamageMultiplier = 20, + GlobalCooldown = 21, + Dot = 22, + Effect3 = 23, + BonusMultiplier = 24, + // Spellmod 25 + ProcPerMinute = 26, + ValueMultiplier = 27, + ResistDispelChance = 28, + CritDamageBonus2 = 29, //One Not Used Spell + SpellCostRefundOnFail = 30, + Effect4 = 32, + Effect5 = 33, + SpellCost2 = 34, // Used when SpellPowerEntry.PowerIndex == 1 + JumpDistance = 35, + StackAmount2 = 37, // same as SPELLMOD_STACK_AMOUNT but affects tooltips + + Max = 39 + } + // Note: SPELLMOD_* values is aura types in fact + public enum SpellModType + { + Flat = 0, // SPELL_AURA_ADD_FLAT_MODIFIER + Pct = 1, // SPELL_AURA_ADD_PCT_MODIFIER + End + } + + public enum SpellGroup + { + None = 0, + ElixirBattle = 1, + ElixirGuardian = 2, + ElixirUnstable = 3, + ElixirShattrath = 4, + CoreRangeMax = 5 + } + public enum SpellGroupStackRule + { + Default, + Exclusive, + ExclusiveFromSameCaster, + ExclusiveSameEffect, + ExclusiveHighest, + Max + } + + public enum PlayerSpellState + { + Unchanged = 0, + Changed = 1, + New = 2, + Removed = 3, + Temporary = 4 + } + + public enum SpellState + { + None = 0, + Preparing = 1, + Casting = 2, + Finished = 3, + Idle = 4, + Delayed = 5 + } + + public enum SpellSchools + { + Normal = 0, + Holy = 1, + Fire = 2, + Nature = 3, + Frost = 4, + Shadow = 5, + Arcane = 6, + Max = 7 + } + + public enum SpellCastResult + { + Success = 0, + AffectingCombat = 1, + AlreadyAtFullHealth = 2, + AlreadyAtFullMana = 3, + AlreadyAtFullPower = 4, + AlreadyBeingTamed = 5, + AlreadyHaveCharm = 6, + AlreadyHaveSummon = 7, + AlreadyHavePet = 8, + AlreadyOpen = 9, + AuraBounced = 10, + AutotrackInterrupted = 11, + BadImplicitTargets = 12, + BadTargets = 13, + PvpTargetWhileUnflagged = 14, + CantBeCharmed = 15, + CantBeDisenchanted = 16, + CantBeDisenchantedSkill = 17, + CantBeMilled = 18, + CantBeProspected = 19, + CantCastOnTapped = 20, + CantDuelWhileInvisible = 21, + CantDuelWhileStealthed = 22, + CantStealth = 23, + CantUntalent = 24, + CasterAurastate = 25, + CasterDead = 26, + Charmed = 27, + ChestInUse = 28, + Confused = 29, + DontReport = 30, + EquippedItem = 31, + EquippedItemClass = 32, + EquippedItemClassMainhand = 33, + EquippedItemClassOffhand = 34, + Error = 35, + Falling = 36, + Fizzle = 37, + Fleeing = 38, + FoodLowlevel = 39, + GarrisonNotOwned = 40, + GarrisonOwned = 41, + GarrisonMaxLevel = 42, + GarrisonNotUpgradeable = 43, + GarrisonFollowerOnMission = 44, + GarrisonFollowerInBuilding = 45, + GarrisonFollowerMaxLevel = 46, + GarrisonFollowerMinItemLevel = 47, + GarrisonFollowerMaxItemLevel = 48, + GarrisonFollowerMaxQuality = 49, + GarrisonFollowerNotMaxLevel = 50, + GarrisonFollowerHasAbility = 51, + GarrisonFollowerHasSingleMissionAbility = 52, + GarrisonFollowerRequiresEpic = 53, + GarrisonMissionNotInProgress = 54, + GarrisonMissionComplete = 55, + GarrisonNoMissionsAvailable = 56, + Highlevel = 57, + HungerSatiated = 58, + Immune = 59, + IncorrectArea = 60, + Interrupted = 61, + InterruptedCombat = 62, + ItemAlreadyEnchanted = 63, + ItemGone = 64, + ItemNotFound = 65, + ItemNotReady = 66, + LevelRequirement = 67, + LineOfSight = 68, + Lowlevel = 69, + LowCastlevel = 70, + MainhandEmpty = 71, + Moving = 72, + NeedAmmo = 73, + NeedAmmoPouch = 74, + NeedExoticAmmo = 75, + NeedMoreItems = 76, + NoPath = 77, + NotBehind = 78, + NotFishable = 79, + NotFlying = 80, + NotHere = 81, + NotInfront = 82, + NotInControl = 83, + NotKnown = 84, + NotMounted = 85, + NotOnTaxi = 86, + NotOnTransport = 87, + NotReady = 88, + NotShapeshift = 89, + NotStanding = 90, + NotTradeable = 91, + NotTrading = 92, + NotUnsheathed = 93, + NotWhileGhost = 94, + NotWhileLooting = 95, + NoAmmo = 96, + NoChargesRemain = 97, + NoComboPoints = 98, + NoDueling = 99, + NoEndurance = 100, + NoFish = 101, + NoItemsWhileShapeshifted = 102, + NoMountsAllowed = 103, + NoPet = 104, + NoPower = 105, + NothingToDispel = 106, + NothingToSteal = 107, + OnlyAbovewater = 108, + OnlyIndoors = 109, + OnlyMounted = 110, + OnlyOutdoors = 111, + OnlyShapeshift = 112, + OnlyStealthed = 113, + OnlyUnderwater = 114, + OutOfRange = 115, + Pacified = 116, + Possessed = 117, + Reagents = 118, + RequiresArea = 119, + RequiresSpellFocus = 120, + Rooted = 121, + Silenced = 122, + SpellInProgress = 123, + SpellLearned = 124, + SpellUnavailable = 125, + Stunned = 126, + TargetsDead = 127, + TargetAffectingCombat = 128, + TargetAurastate = 129, + TargetDueling = 130, + TargetEnemy = 131, + TargetEnraged = 132, + TargetFriendly = 133, + TargetInCombat = 134, + TargetInPetBattle = 135, + TargetIsPlayer = 136, + TargetIsPlayerControlled = 137, + TargetNotDead = 138, + TargetNotInParty = 139, + TargetNotLooted = 140, + TargetNotPlayer = 141, + TargetNoPockets = 142, + TargetNoWeapons = 143, + TargetNoRangedWeapons = 144, + TargetUnskinnable = 145, + ThirstSatiated = 146, + TooClose = 147, + TooManyOfItem = 148, + TotemCategory = 149, + Totems = 150, + TryAgain = 151, + UnitNotBehind = 152, + UnitNotInfront = 153, + VisionObscured = 154, + WrongPetFood = 155, + NotWhileFatigued = 156, + TargetNotInInstance = 157, + NotWhileTrading = 158, + TargetNotInRaid = 159, + TargetFreeforall = 160, + NoEdibleCorpses = 161, + OnlyBattlegrounds = 162, + TargetNotGhost = 163, + TransformUnusable = 164, + WrongWeather = 165, + DamageImmune = 166, + PreventedByMechanic = 167, + PlayTime = 168, + Reputation = 169, + MinSkill = 170, + NotInRatedBattleground = 171, + NotOnShapeshift = 172, + NotOnStealthed = 173, + NotOnDamageImmune = 174, + NotOnMounted = 175, + TooShallow = 176, + TargetNotInSanctuary = 177, + TargetIsTrivial = 178, + BmOrInvisgod = 179, + GroundMountNotAllowed = 180, + FloatingMountNotAllowed = 181, + UnderwaterMountNotAllowed = 182, + FlyingMountNotAllowed = 183, + ApprenticeRidingRequirement = 184, + JourneymanRidingRequirement = 185, + ExpertRidingRequirement = 186, + ArtisanRidingRequirement = 187, + MasterRidingRequirement = 188, + ColdRidingRequirement = 189, + FlightMasterRidingRequirement = 190, + CsRidingRequirement = 191, + PandaRidingRequirement = 192, + DraenorRidingRequirement = 193, + BrokenIslesRidingRequirement = 194, + MountNoFloatHere = 195, + MountNoUnderwaterHere = 196, + MountAboveWaterHere = 197, + MountCollectedOnOtherChar = 198, + NotIdle = 199, + NotInactive = 200, + PartialPlaytime = 201, + NoPlaytime = 202, + NotInBattleground = 203, + NotInRaidInstance = 204, + OnlyInArena = 205, + TargetLockedToRaidInstance = 206, + OnUseEnchant = 207, + NotOnGround = 208, + CustomError = 209, + CantDoThatRightNow = 210, + TooManySockets = 211, + InvalidGlyph = 212, + UniqueGlyph = 213, + GlyphSocketLocked = 214, + GlyphExclusiveCategory = 215, + GlyphInvalidSpec = 216, + GlyphNoSpec = 217, + NoActiveGlyphs = 218, + NoValidTargets = 219, + ItemAtMaxCharges = 220, + NotInBarbershop = 221, + FishingTooLow = 222, + ItemEnchantTradeWindow = 223, + SummonPending = 224, + MaxSockets = 225, + PetCanRename = 226, + TargetCannotBeResurrected = 227, + TargetHasResurrectPending = 228, + NoActions = 229, + CurrencyWeightMismatch = 230, + WeightNotEnough = 231, + WeightTooMuch = 232, + NoVacantSeat = 233, + NoLiquid = 234, + OnlyNotSwimming = 235, + ByNotMoving = 236, + InCombatResLimitReached = 237, + NotInArena = 238, + TargetNotGrounded = 239, + ExceededWeeklyUsage = 240, + NotInLfgDungeon = 241, + BadTargetFilter = 242, + NotEnoughTargets = 243, + NoSpec = 244, + CantAddBattlePet = 245, + CantUpgradeBattlePet = 246, + WrongBattlePetType = 247, + NoDungeonEncounter = 248, + NoTeleportFromDungeon = 249, + MaxLevelTooLow = 250, + CantReplaceItemBonus = 251, + GrantPetLevelFail = 252, + SkillLineNotKnown = 253, + BlueprintKnown = 254, + FollowerKnown = 255, + CantOverrideEnchantVisual = 256, + ItemNotAWeapon = 257, + SameEnchantVisual = 258, + ToyUseLimitReached = 259, + ToyAlreadyKnown = 260, + ShipmentsFull = 261, + NoShipmentsForContainer = 262, + NoBuildingForShipment = 263, + NotEnoughShipmentsForContainer = 264, + HasMission = 265, + BuildingActivateNotReady = 266, + NotSoulbound = 267, + RidingVehicle = 268, + VeteranTrialAboveSkillRankMax = 269, + NotWhileMercenary = 270, + SpecDisabled = 271, + CantBeObliterated = 272, + FollowerClassSpecCap = 273, + TransportNotReady = 274, + TransmogSetAlreadyKnown = 275, + DisabledByAuraLabel = 276, + DisabledByMaxUsableLevel = 277, + SpellAlreadyKnown = 278, + MustKnowSupercedingSpell = 279, + YouCannotUseThatInPvpInstance = 280, + NoArtifactEquipped = 281, + WrongArtifactEquipped = 282, + TargetIsUntargetableByAnyone = 283, + SpellEffectFailed = 284, + NeedAllPartyMembers = 285, + ArtifactAtFullPower = 286, + ApItemFromPreviousTier = 287, + AreaTriggerCreation = 288, + Unknown = 289, + + // Ok Cast Value - Here In Case A Future Version Removes Success And We Need To Use A Custom Value (Not Sent To Client Either Way) + SpellCastOk = Success + } + + public enum SpellCustomErrors + { + None = 0, + CustomMsg = 1, // Something Bad Happened, And We Want To Display A Custom Message! + AlexBrokeQuest = 2, // Alex Broke Your Quest! Thank Him Later! + NeedHelplessVillager = 3, // This Spell May Only Be Used On Helpless Wintergarde Villagers That Have Not Been Rescued. + NeedWarsongDisguise = 4, // Requires That You Be Wearing The Warsong Orc Disguise. + RequiresPlagueWagon = 5, // You Must Be Closer To A Plague Wagon In Order To Drop Off Your 7th Legion Siege Engineer. + CantTargetFriendlyNonparty = 6, // You Cannot Target Friendly Units Outside Your Party. + NeedChillNymph = 7, // You Must Target A Weakened Chill Nymph. + MustBeInEnkilah = 8, // The Imbued Scourge Shroud Will Only Work When Equipped In The Temple City Of En'Kilah. + RequiresCorpseDust = 9, // Requires Corpse Dust + CantSummonGargoyle = 10, // You Cannot Summon Another Gargoyle Yet. + NeedCorpseDustIfNoTarget = 11, // Requires Corpse Dust If The Target Is Not Dead And Humanoid. + MustBeAtShatterhorn = 12, // Can Only Be Placed Near Shatterhorn + MustTargetProtoDrakeEgg = 13, // You Must First Select A Proto-Drake Egg. + MustBeCloseToTree = 14, // You Must Be Close To A Marked Tree. + MustTargetTurkey = 15, // You Must Target A Fjord Turkey. + MustTargetHawk = 16, // You Must Target A Fjord Hawk. + TooFarFromBouy = 17, // You Are Too Far From The Bouy. + MustBeCloseToOilSlick = 18, // Must Be Used Near An Oil Slick. + MustBeCloseToBouy = 19, // You Must Be Closer To The Buoy! + WyrmrestVanquisher = 20, // You May Only Call For The Aid Of A Wyrmrest Vanquisher In Wyrmrest Temple, The Dragon Wastes, Galakrond'S Rest Or The Wicked Coil. + MustTargetIceHeartJormungar = 21, // That Can Only Be Used On A Ice Heart Jormungar Spawn. + MustBeCloseToSinkhole = 22, // You Must Be Closer To A Sinkhole To Use Your Map. + RequiresHaroldLane = 23, // You May Only Call Down A Stampede On Harold Lane. + RequiresGammothMagnataur = 24, // You May Only Use The Pouch Of Crushed Bloodspore On Gammothra Or Other Magnataur In The Bloodspore Plains And Gammoth. + MustBeInResurrectionChamber = 25, // Requires The Magmawyrm Resurrection Chamber In The Back Of The Maw Of Neltharion. + CantCallWintergardeHere = 26, // You May Only Call Down A Wintergarde Gryphon In Wintergarde Keep Or The Carrion Fields. + MustTargetWilhelm = 27, // What Are You Doing? Only Aim That Thing At Wilhelm! + NotEnoughHealth = 28, // Not Enough Health! + NoNearbyCorpses = 29, // There Are No Nearby Corpses To Use. + TooManyGhouls = 30, // You'Ve Created Enough Ghouls. Return To Gothik The Harvester At Death'S Breach. + GoFurtherFromSunderedShard = 31, // Your Companion Does Not Want To Come Here. Go Further From The Sundered Shard. + MustBeInCatForm = 32, // Must Be In Cat Form + MustBeDeathKnight = 33, // Only Death Knights May Enter Ebon Hold. + MustBeInBearForm = 34, // Must Be In Bear Form + MustBeNearHelplessVillager = 35, // You Must Be Within Range Of A Helpless Wintergarde Villager. + CantTargetElementalMechanical = 36, // You Cannot Target An Elemental Or Mechanical Corpse. + MustHaveUsedDalaranCrystal = 37, // This Teleport Crystal Cannot Be Used Until The Teleport Crystal In Dalaran Has Been Used At Least Once. + YouAlreadyHoldSomething = 38, // You Are Already Holding Something In Your Hand. You Must Throw The Creature In Your Hand Before Picking Up Another. + YouDontHoldAnything = 39, // You Don'T Have Anything To Throw! Find A Vargul And Use Gymer Grab To Pick One Up! + MustBeCloseToValduran = 40, // Bouldercrag'S War Horn Can Only Be Used Within 10 Yards Of Valduran The Stormborn. + NoPassenger = 41, // You Are Not Carrying A Passenger. There Is Nobody To Drop Off. + CantBuildMoreVehicles = 42, // You Cannot Build Any More Siege Vehicles. + AlreadyCarryingCrusader = 43, // You Are Already Carrying A Captured Argent Crusader. You Must Return To The Argent Vanguard Infirmary And Drop Off Your Passenger Before You May Pick Up Another. + CantDoWhileRooted = 44, // You Can'T Do That While Rooted. + RequiresNearbyTarget = 45, // Requires A Nearby Target. + NothingToDiscover = 46, // Nothing Left To Discover. + NotEnoughTargets = 47, // No Targets Close Enough To Bluff. + ConstructTooFar = 48, // Your Iron Rune Construct Is Out Of Range. + RequiresGrandMasterEngineer = 49, // Requires Engineering (350) + CantUseThatMount = 50, // You Can'T Use That Mount. + NooneToEject = 51, // There Is Nobody To Eject! + TargetMustBeBound = 52, // The Target Must Be Bound To You. + TargetMustBeUndead = 53, // Target Must Be Undead. + TargetTooFar = 54, // You Have No Target Or Your Target Is Too Far Away. + MissingDarkMatter = 55, // Missing Reagents: Dark Matter + CantUseThatItem = 56, // You Can'T Use That Item + CantDoWhileCycyloned = 57, // You Can'T Do That While Cycloned + TargetHasScroll = 58, // Target Is Already Affected By A Similar Effect + PoisonTooStrong = 59, // That Anti-Venom Is Not Strong Enough To Dispel That Poison + MustHaveLanceEquipped = 60, // You Must Have A Lance Equipped. + MustBeCloseToMaiden = 61, // You Must Be Near The Maiden Of Winter'S Breath Lake. + LearnedEverything = 62, // You Have Learned Everything From That Book + PetIsDead = 63, // Your Pet Is Dead + NoValidTargets = 64, // There Are No Valid Targets Within Range. + GmOnly = 65, // Only Gms May Use That. Your Account Has Been Reported For Investigation. + RequiresLevel58 = 66, // You Must Reach Level 58 To Use This Portal. + AtHonorCap = 67, // You Already Have The Maximum Amount Of Honor. + HaveHotRod = 68, // You Already Have A Hot Rod. + PartygoerMoreBubbly = 69, // This Partygoer Wants Some More Bubbly. + PartygoerNeedBucket = 70, // This Partygoer Needs A Bucket! + PartygoerWantToDance = 71, // This Partygoer Wants To Dance With You. + PartygoerWantFireworks = 72, // This Partygoer Wants To See Some Fireworks. + PartygoerWantAppetizer = 73, // This Partygoer Wants Some More Hors D'Oeuvres. + GoblinBatteryDepleted = 74, // The Goblin All-In-1-Der Belt'S Battery Is Depleted. + MustHaveDemonicCircle = 75, // You Must Have A Demonic Circle Active. + AtMaxRage = 76, // You Already Have Maximum Rage + Requires350Engineering = 77, // Requires Engineering (350) + SoulBelongsToLichKing = 78, // Your Soul Belongs To The Lich King + AttendantHasPony = 79, // Your Attendant Already Has An Argent Pony + GoblinStartingMission = 80, // First, Overload The Defective Generator, Activate The Leaky Stove, And Drop A Cigar On The Flammable Bed. + GasbotAlreadySent = 81, // You'Ve Already Sent In The Gasbot And Destroyed Headquarters! + GoblinIsPartiedOut = 82, // This Goblin Is All Partied Out! + MustHaveFireTotem = 83, // You Must Have A Magma, Flametongue, Or Fire Elemental Totem Active. + CantTargetVampires = 84, // You May Not Bite Other Vampires. + PetAlreadyAtYourLevel = 85, // Your Pet Is Already At Your Level. + MissingItemRequiremens = 86, // You Do Not Meet The Level Requirements For This Item. + TooManyAbominations = 87, // There Are Too Many Mutated Abominations. + AllPotionsUsed = 88, // The Potions Have All Been Depleted By Professor Putricide. + DefeatedEnoughAlready = 89, // You Have Already Defeated Enough Of Them. + RequiresLevel65 = 90, // Requires Level 65 + DestroyedKtcOilPlatform = 91, // You Have Already Destroyed The Ktc Oil Platform. + LaunchedEnoughCages = 92, // You Have Already Launched Enough Cages. + RequiresBoosterRockets = 93, // Requires Single-Stage Booster Rockets. Return To Hobart Grapplehammer To Get More. + EnoughWildCluckers = 94, // You Have Already Captured Enough Wild Cluckers. + RequiresControlFireworks = 95, // Requires Remote Control Fireworks. Return To Hobart Grapplehammer To Get More. + MaxNumberOfRecruits = 96, // You Already Have The Max Number Of Recruits. + MaxNumberOfVolunteers = 97, // You Already Have The Max Number Of Volunteers. + FrostmourneRenderedResurrect = 98, // Frostmourne Has Rendered You Unable To Resurrect. + CantMountWithShapeshift = 99, // You Can'T Mount While Affected By That Shapeshift. + FawnsAlreadyFollowing = 100, // Three Fawns Are Already Following You! + AlreadyHaveRiverBoat = 101, // You Already Have A River Boat. + NoActiveEnchantment = 102, // You Have No Active Enchantment To Unleash. + EnoughHighbourneSouls = 103, // You Have Bound Enough Highborne Souls. Return To Arcanist Valdurian. + Atleast40ydFromOilDrilling = 104, // You Must Be At Least 40 Yards Away From All Other Oil Drilling Rigs. + AboveEnslavedPearlMiner = 106, // You Must Be Above The Enslaved Pearl Miner. + MustTargetCorpseSpecial1 = 107, // You Must Target The Corpse Of A Seabrush Terrapin, Scourgut Remora, Or Spinescale Hammerhead. + SlaghammerAlreadyPrisoner = 108, // Ambassador Slaghammer Is Already Your Prisoner. + RequireAttunedLocation1 = 109, // Requires A Location That Is Attuned With The Naz'Jar Battlemaiden. + NeedToFreeDrakeFirst = 110, // Free The Drake From The Net First! + DragonmawAlliesAlreadyFollow = 111, // You Already Have Three Dragonmaw Allies Following You. + RequireOpposableThumbs = 112, // Requires Opposable Thumbs. + NotEnoughHealth2 = 113, // Not Enough Health + EnoughForsakenTroopers = 114, // You Already Have Enough Forsaken Troopers. + CannotJumpToBoulder = 115, // You Cannot Jump To Another Boulder Yet. + SkillTooHigh = 116, // Skill Too High. + Already6SurvivorsRescued = 117, // You Have Already Rescued 6 Survivors. + MustFaceShipsFromBalloon = 118, // You Need To Be Facing The Ships From The Rescue Balloon. + CannotSuperviseMoreCultists = 119, // You Cannot Supervise More Than 5 Arrested Cultists At A Time. + RequiresLevel85 = 120, // You Must Reach Level 85 To Use This Portal. + MustBeBelow35Health = 121, // Your Target Must Be Below 35% Health. + MustSelectSpecialization = 122, // You Must Select A Specialization First. + TooWiseAndPowerful = 123, // You Are Too Wise And Powerful To Gain Any Benefit From That Item. + TooCloseArgentLightwell = 124, // You Are Within 10 Yards Of Another Argent Lightwell. + NotWhileShapeshifted = 125, // You Can'T Do That While Shapeshifted. + ManaGemInBank = 126, // You Already Have A Mana Gem In Your Bank. + FlameShockNotActive = 127, // You Must Have At Least One Flame Shock Active. + CantTransform = 128, // You Cannot Transform Right Now + PetMustBeAttacking = 129, // Your Pet Must Be Attacking A Target. + GnomishEngineering = 130, // Requires Gnomish Engineering + GoblinEngineering = 131, // Requires Goblin Engineering + NoTarget = 132, // You Have No Target. + PetOutOfRange = 133, // Your Pet Is Out Of Range Of The Target. + HoldingFlag = 134, // You Can'T Do That While Holding The Flag. + TargetHoldingFlag = 135, // You Can'T Do That To Targets Holding The Flag. + PortalNotOpen = 136, // The Portal Is Not Yet Open. Continue Helping The Druids At The Sanctuary Of Malorne. + AggraAirTotem = 137, // You Need To Be Closer To Aggra'S Air Totem, In The West. + AggraWaterTotem = 138, // You Need To Be Closer To Aggra'S Water Totem, In The North. + AggraEarthTotem = 139, // You Need To Be Closer To Aggra'S Earth Totem, In The East. + AggraFireTotem = 140, // You Need To Be Closer To Aggra'S Fire Totem, Near Thrall. + FacingWrongWay = 141, // You Are Facing The Wrong Way. + TooCloseToMakeshiftDynamite = 142, // You Are Within 10 Yards Of Another Makeshift Dynamite. + NotNearSapphireSunkenShip = 143, // You Must Be Near The Sunken Ship At Sapphire'S End In The Jade Forest. + DemonsHealthFull = 144, // That Demon'S Health Is Already Full. + OnyxSerpentNotOverhead = 145, // Wait Until The Onyx Serpent Is Directly Overhead. + ObjectiveAlreadyComplete = 146, // Your Objective Is Already Complete. + PushSadPandaTowardsTown = 147, // You Can Only Push Sad Panda Towards Sad Panda Town! + TargetHasStartdust2 = 148, // Target Is Already Affected By Stardust No. 2. + ElementiumGemClusters = 149, // You Cannot Deconstruct Elementium Gem Clusters While Collecting Them! + YouDontHaveEnoughHealth = 150, // You Don'T Have Enough Health. + YouCannotUseTheGatewayYet = 151, // You Cannot Use The Gateway Yet. + ChooseSpecForAscendance = 152, // You Must Choose A Specialization To Use Ascendance. + InsufficientBloodCharges = 153, // You Have Insufficient Blood Charges. + NoFullyDepletedRunes = 154, // No Fully Depleted Runes. + NoMoreCharges = 155, // No More Charges. + StatueIsOutOfRangeOfTarget = 156, // Statue Is Out Of Range Of The Target. + YouDontHaveAStatueSummoned = 157, // You Don'T Have A Statue Summoned. + YouHaveNoSpiritActive = 158, // You Have No Spirit Active. + BothDisesasesMustBeOnTarget = 159, // Both Frost Fever And Blood Plague Must Be Present On The Target. + CantDoThatWithOrbOfPower = 160, // You Can'T Do That While Holding An Orb Of Power. + CantDoThatWhileJumpingOrFalling = 161, // You Can'T Do That While Jumping Or Falling. + MustBeTransformedByPolyformicAcid = 162, // You Must Be Transformed By Polyformic Acid. + NotEnoughAcidToStoreTransformation = 163, // There Isn'T Enough Acid Left To Store This Transformation. + MustHaveFlightMastersLicense = 164, // You Must Obtain A Flight Master'S License Before Using This Spell. + AlreadySampledSapFromFeeder = 165, // You Have Already Sampled Sap From This Feeder. + MustBeNewrMantidFeeder = 166, // Requires You To Be Near A Mantid Feeder In The Heart Of Fear. + TargetMustBeInDirectlyFront = 167, // Target Must Be Directly In Front Of You. + CantDoThatWhileMythicKeystoneIsActive = 168, // You Can'T Do That While A Mythic Keystone Is Active. + WrongClassForMount = 169, // You Are Not The Correct Class For That Mount. + NothingLeftToDiscover = 170, // Nothing Left To Discover. + NoExplosivesAvailable = 171, // There Are No Explosives Available. + YouMustBeFlaggedForPvp = 172, // You Must Be Flagged For Pvp. + RequiresBattleRations = 173, // Requires Battle Rations Or Meaty Haunch + RequiresBrittleRoot = 174, // Requires Brittle Root + RequiresLaborersTool = 175, // Requires Laborer'S Tool + RequiresUnexplodedCannonball = 176, // Requires Unexploded Cannonball + RequiresMisplacedKeg = 177, // Requires Misplaced Keg + RequiresLiquidFire = 178, // Requires Liquid Fire, Jungle Hops, Or Spirit-Kissed Water + RequiresKrasariIron = 179, // Requires Krasari Iron + RequiresSpiritKissedWater = 180, // Requires Spirit-Kissed Water + RequiresSnakeOil = 181, // Requires Snake Oil + ScenarioIsInProgress = 182, // You Can'T Do That While A Scenario Is In Progress. + RequiresDarkmoonFaireOpen = 183, // Requires The Darkmoon Faire To Be Open. + AlreadyAtValorCap = 184, // Already At Valor Cap + AlreadyCommendedByThisFaction = 185, // Already Commended By This Faction + OutOfCoins = 186, // Out Of Coins! Pickpocket Humanoids To Get More. + OnlyOneElementalSpirit = 187, // Only One Elemental Spirit On A Target At A Time. + DontKnowHowToTameDirehorns = 188, // You Do Not Know How To Tame Direhorns. + MustBeNearBloodiedCourtGate = 189, // You Must Be Near The Bloodied Court Gate. + YouAreNotElectrified = 190, // You Are Not Electrified. + ThereIsNothingToBeFetched = 191, // There Is Nothing To Be Fetched. + RequiresTheThunderForge = 192, // Requires The Thunder Forge. + CannotUseTheDiceAgainYet = 193, // You Cannot Use The Dice Again Yet. + AlreadyMemberOfBrawlersGuild = 194, // You Are Already A Member Of The Brawler'S Guild. + CantChangeSpecInCelestialChallenge = 195, // You May Not Change Talent Specializations During A Celestial Challenge. + SpecDoesMatchChallenge = 196, // Your Talent Specialization Does Not Match The Selected Challenge. + YouDontHaveEnoughCurrency = 197, // You Don'T Have Enough Currency To Do That. + TargetCannotBenefitFromSpell = 198, // Target Cannot Benefit From That Spell + YouCanOnlyHaveOneHealingRain = 199, // You Can Only Have One Healing Rain Active At A Time. + TheDoorIsLocked = 200, // The Door Is Locked. + YouNeedToSelectWaitingCustomer = 201, // You Need To Select A Customer Who Is Waiting In Line First. + CantChangeSpecDuringTrial = 202, // You May Not Change Specialization While A Trial Is In Progress. + CustomerNeedToGetInLine = 203, // You Must Wait For Customers To Get In Line Before You Can Select Them To Be Seated. + MustBeCloserToGazloweObjective = 204, // Must Be Closer To One Of Gazlowe'S Objectives To Deploy! + MustBeCloserToThaelinObjective = 205, // Must Be Closer To One Of Thaelin'S Objectives To Deploy! + YourPackOfVolenIsFull = 206, // Your Pack Of Volen Is Already Full! + Requires600MiningOrBlacksmithing = 207, // Requires 600 Mining Or Blacksmithing + ArkoniteProtectorNotInRange = 208, // The Arkonite Protector Is Not In Range. + TargetCannotHaveBothBeacons = 209, // You Are Unable To Have Both Beacon Of Light And Beacon Of Faith On The Same Target. + CanOnlyUseOnAfkPlayer = 210, // Can Only Be Used On Afk Players. + NoLootableCorpsesInRange = 211, // No Lootable Corpse In Range + ChimaeronTooCalmToTame = 212, // Chimaeron Is Too Calm To Tame Right Now. + CanOnlyCarryOneTypeOfMunitions = 213, // You May Only Carry One Type Of Blackrock Munitions. + OutOfBlackrockMunitions = 214, // You Have Run Out Of Blackrock Munitions. + CarryingMaxAmountOfMunitions = 215, // You Are Carrying The Maximum Amount Of Blackrock Munitions. + TargetIsTooFarAway = 216, // Target Is Too Far Away. + CannotUseDuringBossEncounter = 217, // Cannot Use During A Boss Encounter. + MustHaveMeleeWeaponInBothHands = 218, // Must Have A Melee Weapon Equipped In Both Hands + YourWeaponHasOverheated = 219, // Your Weapon Has Overheated. + MustBePartyLeaderToQueue = 220, // You Must Be A Party Leader To Queue Your Group. + NotEnoughFuel = 221, // Not Enough Fuel + YouAreAlreadyDisguised = 222, // You Are Already Disguised! + YouNeedToBeInShredder = 223, // You Need To Be In A Shredder To Chop This Up! + FoodCannotEatFood = 224, // Food Cannot Eat Food + MysteriousForcePreventsOpeningChest = 225, // A Mysterious Force Prevents You From Opening The Chest. + CantDoThatWhileHoldingEmpoweredOre = 226, // You Can'T Do That While Holding Empowered Ore. + NotEnoughAmmunition = 227, // Not Enough Ammunition! + YouNeedBeatfaceTheGladiator = 228, // You Need Beatface The Sparring Arena Gladiator To Break This! + YouCanOnlyHaveOneWaygate = 229, // You Can Only Have One Waygate Open. Disable An Activated Waygate First. + YouCanOnlyHaveTwoWaygates = 230, // You Can Only Have Two Waygates Open. Disable An Activated Waygate First. + YouCanOnlyHaveThreeWaygates = 231, // You Can Only Have Three Waygates Open. Disable An Activated Waygate First. + RequiresMageTower = 232, // Requires Mage Tower + RequiresSpiritLodge = 233, // Requires Spirit Lodge + FrostWyrmAlreadyActive = 234, // A Frost Wyrm Is Already Active. + NotEnoughRunicPower = 235, // Not Enough Runic Power + YouAreThePartyLeader = 236, // You Are The Party Leader. + YulonIsAlreadyActive = 237, // Yu'Lon Is Already Active. + AStampedeIsAlreadyActive = 238, // A Stampede Is Already Active. + YouAreAlreadyWellFed = 239, // You Are Already Well Fed. + CantDoThatUnderSuppressiveFire = 240, // You Cannot Do That While Under Suppressive Fire. + YouAlreadyHaveMurlocSlop = 241, // You Already Have A Piece Of Murloc Slop. + YouDontHaveArtifactFragments = 242, // You Don'T Have Any Artifact Fragments. + YouArentInAParty = 243, // You Aren'T In A Party. + Requires20Ammunition = 244, // Requires 30 Ammunition! + Requires30Ammunition = 245, // Requires 20 Ammunition! + YouAlreadyHaveMaxOutcastFollowers = 246, // You Already Have The Maximum Amount Of Outcasts Following You. + NotInWorldPvpZone = 247, // Not In World Pvp Zone. + AlreadyAtResourceCap = 248, // Already At Resource Cap + ApexisSentinelRequiresEnergy = 249, // This Apexis Sentinel Requires Energy From A Nearby Apexis Pylon To Be Powered Up. + YouMustHave3OrFewerPlayer = 250, // You Must Have 3 Or Fewer Players. + YouAlreadyReadTreasureMap = 251, // You Have Already Read That Treasure Map. + MayOnlyUseWhileGarrisonUnderAttack = 252, // You May Only Use This Item While Your Garrison Is Under Attack. + RequiresActiveMushrooms = 253, // This Spell Requires Active Mushrooms For You To Detonate. + RequiresFasterTimeWithRacer = 254, // Requires A Faster Time With The Basic Racer + RequiresInfernoShotAmmo = 255, // Requires Inferno Shot Ammo! + YouCannotDoThatRightNow = 256, // You Cannot Do That Right Now. + ATrapIsAlreadyPlacedThere = 257, // A Trap Is Already Placed There. + YouAreAlreadyOnThatQuest = 258, // You Are Already On That Quest. + RequiresFelforgedCudgel = 259, // Requires A Felforged Cudgel! + CantTakeWhileBeingDamaged = 260, // Can'T Take While Being Damaged! + YouAreBoundToDraenor = 261, // You Are Bound To Draenor By Archimonde'S Magic. + AlreayHaveMaxNumberOfShips = 262, // You Already Have The Maximum Number Of Ships Your Shipyard Can Support. + MustBeAtShipyard = 263, // You Must Be At Your Shipyard. + RequiresLevel3MageTower = 264, // Requires A Level 3 Mage Tower. + RequiresLevel3SpiritLodge = 265, // Requires A Level 3 Spirit Lodge. + YouDoNotLikeFelEggsAndHam = 266, // You Do Not Like Fel Eggs And Ham. + AlreadyEnteredInThisAgreement = 267, // You Have Already Entered In To This Trade Agreement. + CannotStealThatWhileGuardsAreOnDuty = 268, // You Cannot Steal That While Guards Are On Duty. + YouAlreadyUsedVantusRune = 269, // You Have Already Used A Vantus Rune This Week. + ThatItemCannotBeObliterated = 270, // That Item Cannot Be Obliterated. + NoSkinnableCorpseInRange = 271, // No Skinnable Corpse In Range + MustBeMercenaryToUseTrinket = 272, // You Must Be A Mercenary To Use This Trinket. + YouMustBeInCombat = 273, // You Must Be In Combat. + NoEnemiesNearTarget = 274, // No Enemies Near Target. + RequiresLeyspineMissile = 275, // Requires A Leyspine Missile + RequiresBothCurrentsConnected = 276, // Requires Both Currents Connected. + CantDoThatInDemonForm = 277, // Can'T Do That While In Demon Form (Yet) + YouDontKnowHowToTameMechs = 278, // You Do Not Know How To Tame Or Obtain Lore About Mechs. + CannotCharmAnyMoreWithered = 279, // You Cannot Charm Any More Withered. + RequiresActiveHealingRain = 280, // Requires An Active Healing Rain. + AlreadyCollectedAppearances = 281, // You'Ve Already Collected These Appearances + CannotResurrectSurrenderedToMadness = 282, // Cannot Resurrect Someone Who Has Surrendered To Madness + YouMustBeInCatForm = 283, // You Must Be In Cat Form. + YouCannotReleaseSpiritYet = 284, // You Cannot Release Spirit Yet. + NoFishingNodesNearby = 285, // No Fishing Nodes Nearby. + YouAreNotInCorrectSpec = 286, // You Are Not The Correct Specialization. + UlthaleshHasNoPowerWithoutSouls = 287, // Ulthalesh Has No Power Without Souls. + CannotCastThatWithVoodooTotem = 288, // You Cannot Cast That While Talented Into Voodoo Totem. + AlreadyCollectedThisAppearance = 289, // You'Ve Already Collected This Appearance. + YourPetMaximumIsAlreadyHigh = 290, // Your Total Pet Maximum Is Already This High. + YouDontHaveEnoughWithered = 291, // You Do Not Have Enough Withered To Do That. + RequiresNearbySoulFragment = 292, // Requires A Nearby Soul Fragment. + RequiresAtLeast10Withered = 293, // Requires At Least 10 Living Withered + RequiresAtLeast14Withered = 294, // Requires At Least 14 Living Withered + RequiresAtLeast18Withered = 295, // Requires At Least 18 Living Withered + Requires2WitheredManaRagers = 296, // Requires 2 Withered Mana-Ragers + Requires1WitheredBerserke = 297, // Requires 1 Withered Berserker + Requires2WitheredBerserker = 298, // Requires 2 Withered Berserkers + TargetHealthIsTooLow = 299, // Target'S Health Is Too Low + CannotShapeshiftWhileRidingStormtalon = 300, // You Cannot Shapeshift While Riding Stormtalon + CannotChangeSpecInCombatTraining = 301, // You Can Not Change Specializations While In Combat Training. + UnknownPhenomenonPreventsLeylineConnection = 302, // Unknown Phenomenon Is Preventing A Connection To The Leyline. + TheNightmareObscuresYourVision = 303, // The Nightmare Obscures Your Vision. + YouAreInWrongClassSpec = 304, // You Are In The Wrong Class Specialization. + ThereAreNoValidCorpsesNearby = 305, // There Are No Valid Corpses Nearby. + CantCastThatRightNow = 306, // Can'T Cast That Right Now. + NotEnoughAncientMan = 307, // Not Enough Ancient Mana. + RequiresSongScroll = 308, // Requires A Song Scroll To Function. + MustHaveArtifactEquipped = 309, // You Must Have An Artifact Weapon Equipped. + RequiresCatForm = 310, // Requires Cat Form. + RequiresBearForm = 311, // Requires Bear Form. + RequiresConjuredFood = 312, // Requires Either A Conjured Mana Pudding Or Conjured Mana Fritter. + RequiresArtifactWeapon = 313, // Requires An Artifact Weapon. + YouCantCastThatHere = 314, // You Can'T Cast That Here + CantDoThatOnClassTrial = 315, // You Cannot Do That While On A Class Trial. + RitualOfDoomOncePerDay = 316, // You Can Only Benefit From The Ritual Of Doom Once Per Day. + CannotRitualOfDoomWhileSummoningSiters = 317, // You Cannot Perform The Ritual Of Doom While Attempting To Summon The Sisters. + LearnedAllThatYouCanAboutYourArtifact = 318, // You Have Learned All That You Can About Your Artifact. + CantCallPetWithLoneWolf = 319, // You Cannot Use Call Pet While Lone Wolf Is Active. + YouMustBeInAnInnToStrumThatGuitar = 321, // You must be in an inn to strum that guitar. + YouCannotReachTheLatch = 322, // You cannot reach the latch. + RequiresABrimmingKeystone = 323, // Requires A Brimming Keystone. + YouMustBeWieldingTheUnderlightAngler = 324, // You Must Be Wielding The Underlight Angler. + YourTargetMustBeShackled = 325, // Your Target Must Be Shackled. + YouAlreadyPossesAllOfTheKnowledgeContainedInThosePages = 326, // You Already Possess All Of The Knowledge Contained In These Pages. + YouCantRiskGettingTheGrummelsWet = 327, // You Can'T Risk Getting The Grummels Wet! + YouCannotChangeSpecializationRightNow = 328, // You Cannot Change Specializations Right Now. + YouveReachedTheMaximumNumberOfArtifactResearchNotesAvailable = 329, // You'Ve Reached The Maximum Number Of Artifact Research Notes Available. + YouDontHaveEnoughNethershards = 330, // You Don'T Have Enough Nethershards. + TheSentinaxIsNotPatrollingThisArea = 331, // The Sentinax Is Not Patrolling This Area. + TheSentinaxCannotOpenAnotherPortalRightNow = 332, // The Sentinax Cannot Open Another Portal Right Now. + YouCannotGainAdditionalReputationWithThisItem = 333, // You Cannot Gain Additional Reputation With This Item. + CantDoThatWhileGhostWolfForm = 334, // Can'T Do That While In Ghost Wolf Form. + YourSuppliesAreFrozen = 335, // Your Supplies Are Frozen. + YouDoNotKnowHowToTameFeathermanes = 336, // You Do Not Know How To Tame Feathermanes. + YouMustReachArtifactKnowledgeLevel25 = 337, // You Must Reach Artifact Knowledge Level 25 To Use The Tome. + RequiresANetherPortalDisruptor = 338, // Requires A Nether Portal Disruptor. + MustBeStandingNearInjuredChromieInMountHyjal = 340, // Must Be Standing Near The Injured Chromie In Mount Hyjal. + RemoveCannonsHeavyIronPlatingFirst = 342, // You Should Remove The Cannon'S Heavy Iron Plating First. + RemoveCannonsElectrokineticDefenseGridFirst = 343, // You Should Remove The Cannon'S Electrokinetic Defense Grid First. + } + + public enum SpellMissInfo + { + None = 0, + Miss = 1, + Resist = 2, + Dodge = 3, + Parry = 4, + Block = 5, + Evade = 6, + Immune = 7, + Immune2 = 8, // One Of These 2 Is MissTempimmune + Deflect = 9, + Absorb = 10, + Reflect = 11 + } + + public enum SpellHitType + { + CritDebu = 0x1, + Crit = 0x2, + HitDebug = 0x4, + Split = 0x8, + VictimIsAttacker = 0x10, + AttackTableDebug = 0x20, + Unk = 0x40, + NoAttacker = 0x80 // does the same as SPELL_ATTR4_COMBAT_LOG_NO_CASTER + } + + public enum SpellDmgClass + { + None = 0, + Magic = 1, + Melee = 2, + Ranged = 3 + } + + public enum SpellPreventionType + { + Silence = 1, + Pacify = 2, + NoActions = 4 + } + + public enum SpellCastTargetFlags + { + None = 0x0, + Unused1 = 0x01, // Not Used + Unit = 0x02, // Pguid + UnitRaid = 0x04, // Not Sent, Used To Validate Target (If Raid Member) + UnitParty = 0x08, // Not Sent, Used To Validate Target (If Party Member) + Item = 0x10, // Pguid + SourceLocation = 0x20, // Pguid, 3 Float + DestLocation = 0x40, // Pguid, 3 Float + UnitEnemy = 0x80, // Not Sent, Used To Validate Target (If Enemy) + UnitAlly = 0x100, // Not Sent, Used To Validate Target (If Ally) + CorpseEnemy = 0x200, // Pguid + UnitDead = 0x400, // Not Sent, Used To Validate Target (If Dead Creature) + Gameobject = 0x800, // Pguid, Used With TargetGameobjectTarget + TradeItem = 0x1000, // Pguid + String = 0x2000, // String + GameobjectItem = 0x4000, // Not Sent, Used With TargetGameobjectItemTarget + CorpseAlly = 0x8000, // Pguid + UnitMinipet = 0x10000, // Pguid, Used To Validate Target (If Non Combat Pet) + GlyphSlot = 0x20000, // Used In Glyph Spells + DestTarget = 0x40000, // Sometimes Appears With DestTarget Spells (May Appear Or Not For A Given Spell) + ExtraTargets = 0x80000, // Uint32 Counter, Loop { Vec3 - Screen Position (?), Guid }, Not Used So Far + UnitPassenger = 0x100000, // Guessed, Used To Validate Target (If Vehicle Passenger)\ + Unk400000 = 0x400000, + Unk1000000 = 0X01000000, + Unk4000000 = 0X04000000, + Unk10000000 = 0X10000000, + Unk40000000 = 0X40000000, + + UnitMask = Unit | UnitRaid | UnitParty | UnitEnemy | UnitAlly | UnitDead | UnitMinipet | UnitPassenger, + GameobjectMask = Gameobject | GameobjectItem, + CorpseMask = CorpseAlly | CorpseEnemy, + ItemMask = TradeItem | Item | GameobjectItem + } + + public enum SpellFamilyNames + { + Generic = 0, + Unk1 = 1, // Events, Holidays + // 2 - Unused + Mage = 3, + Warrior = 4, + Warlock = 5, + Priest = 6, + Druid = 7, + Rogue = 8, + Hunter = 9, + Paladin = 10, + Shaman = 11, + Unk2 = 12, // 2 Spells (Silence Resistance) + Potion = 13, + // 14 - Unused + Deathknight = 15, + // 16 - Unused + Pet = 17, + Unk3 = 50, + Monk = 53, + WarlockPet = 57 + } + + public enum TriggerCastFlags : uint + { + None = 0x0, //! Not Triggered + IgnoreGCD = 0x01, //! Will Ignore Gcd + IgnoreSpellAndCategoryCD = 0x02, //! Will Ignore Spell And Category Cooldowns + IgnorePowerAndReagentCost = 0x04, //! Will Ignore Power And Reagent Cost + IgnoreCastItem = 0x08, //! Will Not Take Away Cast Item Or Update Related Achievement Criteria + IgnoreAuraScaling = 0x10, //! Will Ignore Aura Scaling + IgnoreCastInProgress = 0x20, //! Will Not Check If A Current Cast Is In Progress + IgnoreComboPoints = 0x40, //! Will Ignore Combo Point Requirement + CastDirectly = 0x80, //! In Spell.Prepare, Will Be Cast Directly Without Setting Containers For Executed Spell + IgnoreAuraInterruptFlags = 0x100, //! Will Ignore Interruptible Aura'S At Cast + IgnoreSetFacing = 0x200, //! Will Not Adjust Facing To Target (If Any) + IgnoreShapeshift = 0x400, //! Will Ignore Shapeshift Checks + IgnoreCasterAurastate = 0x800, //! Will Ignore Caster Aura States Including Combat Requirements And Death State + IgnoreCasterMountedOrOnVehicle = 0x2000, //! Will Ignore Mounted/On Vehicle Restrictions + IgnoreCasterAuras = 0x10000, //! Will Ignore Caster Aura Restrictions Or Requirements + DisallowProcEvents = 0x20000, //! Disallows Proc Events From Triggered Spell (Default) + DontReportCastError = 0x40000, //! Will Return SpellFailedDontReport In Checkcast Functions + IgnoreEquippedItemRequirement = 0x80000, + IgnoreTargetCheck = 0x100000, + FullMask = 0xffffffff + } + + public enum SpellSchoolMask + { + None = 0x0, // Not Exist + Normal = (1 << SpellSchools.Normal), // Physical (Armor) + Holy = (1 << SpellSchools.Holy), + Fire = (1 << SpellSchools.Fire), + Nature = (1 << SpellSchools.Nature), + Frost = (1 << SpellSchools.Frost), + Shadow = (1 << SpellSchools.Shadow), + Arcane = (1 << SpellSchools.Arcane), + + // 124, Not Include Normal And Holy Damage + Spell = (Fire | Nature | Frost | Shadow | Arcane), + // 126 + Magic = (Holy | Spell), + + // 127 + All = (Normal | Magic), + } + + [System.Flags] + public enum SpellCastFlags : uint + { + None = 0x0, + Pending = 0x01, // Aoe Combat Log? + HasTrajectory = 0x02, + Unk3 = 0x04, + Unk4 = 0x08, // Ignore Aoe Visual + Unk5 = 0x10, + Projectile = 0x20, + Unk7 = 0x40, + Unk8 = 0x80, + Unk9 = 0x100, + Unk10 = 0x200, + Unk11 = 0x400, + PowerLeftSelf = 0x800, + Unk13 = 0x1000, + Unk14 = 0x2000, + Unk15 = 0x4000, + Unk16 = 0x8000, + Unk17 = 0x10000, + AdjustMissile = 0x20000, + NoGCD = 0x40000, + VisualChain = 0x80000, + Unk21 = 0x100000, + RuneList = 0x200000, + Unk23 = 0x400000, + Unk24 = 0x800000, + Unk25 = 0x1000000, + Unk26 = 0x2000000, + Immunity = 0x4000000, + Unk28 = 0x8000000, + Unk29 = 0x10000000, + Unk30 = 0x20000000, + HealPrediction = 0x40000000, + Unk32 = 0x80000000 + } + + [System.Flags] + public enum SpellCastFlagsEx + { + None = 0x0, + Unknown1 = 0x01, + Unknown2 = 0x02, + Unknown3 = 0x04, + Unknown4 = 0x08, + Unknown5 = 0x10, + Unknown6 = 0x20, + Unknown7 = 0x40, + Unknown8 = 0x80, + Unknown9 = 0x100, + Unknown10 = 0x200, + Unknown11 = 0x400, + Unknown12 = 0x800, + Unknown13 = 0x1000, + Unknown14 = 0x2000, + Unknown15 = 0x4000, + UseToySpell = 0x8000, // Starts Cooldown On Toy + Unknown17 = 0x10000, + Unknown18 = 0x20000, + Unknown19 = 0x40000, + Unknown20 = 0x80000 + } + + #region Spell Attributes + public enum SpellAttr0 : uint + { + Unk0 = 0x01, // 0 + ReqAmmo = 0x02, // 1 On Next Ranged + OnNextSwing = 0x04, // 2 + IsReplenishment = 0x08, // 3 Not Set In 3.0.3 + Ability = 0x10, // 4 Client Puts 'Ability' Instead Of 'Spell' In Game Strings For These Spells + Tradespell = 0x20, // 5 Trade Spells (Recipes), Will Be Added By Client To A Sublist Of Profession Spell + Passive = 0x40, // 6 Passive Spell + HiddenClientside = 0x80, // 7 Spells With This Attribute Are Not Visible In Spellbook Or Aura Bar + HideInCombatLog = 0x100, // 8 This Attribite Controls Whether Spell Appears In Combat Logs + TargetMainhandItem = 0x200, // 9 Client Automatically Selects Item From Mainhand Slot As A Cast Target + OnNextSwing2 = 0x400, // 10 + Unk11 = 0x800, // 11 + DaytimeOnly = 0x1000, // 12 Only Useable At Daytime, Not Set In 2.4.2 + NightOnly = 0x2000, // 13 Only Useable At Night, Not Set In 2.4.2 + IndoorsOnly = 0x4000, // 14 Only Useable Indoors, Not Set In 2.4.2 + OutdoorsOnly = 0x8000, // 15 Only Useable Outdoors. + NotShapeshift = 0x10000, // 16 Not While Shapeshifted + OnlyStealthed = 0x20000, // 17 Must Be In Stealth + DontAffectSheathState = 0x40000, // 18 Client Won'T Hide Unit Weapons In Sheath On Cast/Channel + LevelDamageCalculation = 0x80000, // 19 Spelldamage Depends On Caster Level + StopAttackTarget = 0x100000, // 20 Stop Attack After Use This Spell (And Not Begin Attack If Use) + ImpossibleDodgeParryBlock = 0x200000, // 21 Cannot Be Dodged/Parried/Blocked + CastTrackTarget = 0x400000, // 22 Client Automatically Forces Player To Face Target When Casting + CastableWhileDead = 0x800000, // 23 Castable While Dead? + CastableWhileMounted = 0x1000000, // 24 Castable While Mounted + DisabledWhileActive = 0x2000000, // 25 Activate And Start Cooldown After Aura Fade Or Remove Summoned Creature Or Go + Negative1 = 0x4000000, // 26 Many Negative Spells Have This Attr + CastableWhileSitting = 0x8000000, // 27 Castable While Sitting + CantUsedInCombat = 0x10000000, // 28 Cannot Be Used In Combat + UnaffectedByInvulnerability = 0x20000000, // 29 Unaffected By Invulnerability (Hmm Possible Not...) + HeartResistCheck = 0x40000000, // 30 + CantCancel = 0x80000000, // 31 Positive Aura Can'T Be Canceled + } + public enum SpellAttr1 : uint + { + DismissPet = 0x01, // 0 For Spells Without This Flag Client Doesn'T Allow To Summon Pet If Caster Has A Pet + DrainAllPower = 0x02, // 1 Use All Power (Only Paladin Lay Of Hands And Bunyanize) + Channeled1 = 0x04, // 2 Clientside Checked? Cancelable? + CantBeRedirected = 0x08, // 3 + Unk4 = 0x10, // 4 Stealth And Whirlwind + NotBreakStealth = 0x20, // 5 Not Break Stealth + Channeled2 = 0x40, // 6 + CantBeReflected = 0x80, // 7 + CantTargetInCombat = 0x100, // 8 Can Target Only Out Of Combat Units + MeleeCombatStart = 0x200, // 9 Player Starts Melee Combat After This Spell Is Cast + NoThreat = 0x400, // 10 No Generates Threat On Cast 100% (Old NoInitialAggro) + Unk11 = 0x800, // 11 Aura + IsPickpocket = 0x1000, // 12 Pickpocket + Farsight = 0x2000, // 13 Client Removes Farsight On Aura Loss + ChannelTrackTarget = 0x4000, // 14 Client Automatically Forces Player To Face Target When Channeling + DispelAurasOnImmunity = 0x8000, // 15 Remove Auras On Immunity + UnaffectedBySchoolImmune = 0x10000, // 16 On Immuniy + UnautocastableByPet = 0x20000, // 17 + Unk18 = 0x40000, // 18 Stun, Polymorph, Daze, Hex + CantTargetSelf = 0x80000, // 19 + ReqComboPoints1 = 0x100000, // 20 Req Combo Points On Target + Unk21 = 0x200000, // 21 + ReqComboPoints2 = 0x400000, // 22 Req Combo Points On Target + Unk23 = 0x800000, // 23 + IsFishing = 0x1000000, // 24 Only Fishing Spells + Unk25 = 0x2000000, // 25 + Unk26 = 0x4000000, // 26 Works Correctly With [Target=Focus] And [Target=Mouseover] Macros? + Unk27 = 0x8000000, // 27 Melee Spell? + DontDisplayInAuraBar = 0x10000000, // 28 Client Doesn'T Display These Spells In Aura Bar + ChannelDisplaySpellName = 0x20000000, // 29 Spell Name Is Displayed In Cast Bar Instead Of 'Channeling' Text + EnableAtDodge = 0x40000000, // 30 Overpower + Unk31 = 0x80000000 // 31 + } + public enum SpellAttr2 : uint + { + CanTargetDead = 0x01, // 0 Can Target Dead Unit Or Corpse + Unk1 = 0x02, // 1 Vanish, Shadowform, Ghost Wolf And Other + CanTargetNotInLos = 0x04, // 2 26368 4.0.1 Dbc Change + Unk3 = 0x08, // 3 + DisplayInStanceBar = 0x10, // 4 Client Displays Icon In Stance Bar When Learned, Even If Not Shapeshift + AutorepeatFlag = 0x20, // 5 + CantTargetTapped = 0x40, // 6 Target Must Be Tapped By Caster + Unk7 = 0x80, // 7 + Unk8 = 0x100, // 8 Not Set In 3.0.3 + Unk9 = 0x200, // 9 + Unk10 = 0x400, // 10 Related To Tame + HealthFunnel = 0x800, // 11 + Unk12 = 0x1000, // 12 Cleave, Heart Strike, Maul, Sunder Armor, Swipe + PreserveEnchantInArena = 0x2000, // 13 Items Enchanted By Spells With This Flag Preserve The Enchant To Arenas + Unk14 = 0x4000, // 14 + Unk15 = 0x8000, // 15 Not Set In 3.0.3 + TameBeast = 0x10000, // 16 + NotResetAutoActions = 0x20000, // 17 Don'T Reset Timers For Melee Autoattacks (Swings) Or Ranged Autoattacks (Autoshoots) + ReqDeadPet = 0x40000, // 18 Only Revive Pet And Heart Of The Pheonix + NotNeedShapeshift = 0x80000, // 19 Does Not Necessarly Need Shapeshift + Unk20 = 0x100000, // 20 + DamageReducedShield = 0x200000, // 21 For Ice Blocks, Pala Immunity Buffs, Priest Absorb Shields, But Used Also For Other Spells . Not Sure! + Unk22 = 0x400000, // 22 Ambush, Backstab, Cheap Shot, Death Grip, Garrote, Judgements, Mutilate, Pounce, Ravage, Shiv, Shred + IsArcaneConcentration = 0x800000, // 23 Only Mage Arcane Concentration Have This Flag + Unk24 = 0x1000000, // 24 + Unk25 = 0x2000000, // 25 + Unk26 = 0x4000000, // 26 Unaffected By School Immunity + Unk27 = 0x8000000, // 27 + Unk28 = 0x10000000, // 28 + CantCrit = 0x20000000, // 29 Spell Can'T Crit + TriggeredCanTriggerProc = 0x40000000, // 30 Spell Can Trigger Even If Triggered + FoodBuff = 0x80000000 // 31 Food Or Drink Buff (Like Well Fed) + } + public enum SpellAttr3 : uint + { + Unk0 = 0x01, // 0 + Unk1 = 0x02, // 1 + Unk2 = 0x04, // 2 + BlockableSpell = 0x08, // 3 Only Dmg Class Melee In 3.1.3 + IgnoreResurrectionTimer = 0x10, // 4 You Don'T Have To Wait To Be Resurrected With These Spells + Unk5 = 0x20, // 5 + Unk6 = 0x40, // 6 + StackForDiffCasters = 0x80, // 7 Separate Stack For Every Caster + OnlyTargetPlayers = 0x100, // 8 Can Only Target Players + TriggeredCanTriggerProc2 = 0x200, // 9 Triggered From Effect? + MainHand = 0x400, // 10 Main Hand Weapon Required + Battleground = 0x800, // 11 Can Casted Only On Battleground + OnlyTargetGhosts = 0x1000, // 12 + DontDisplayChannelBar = 0x2000, // 13 Clientside attribute - will not display channeling bar + IsHonorlessTarget = 0x4000, // 14 "Honorless Target" Only This Spells Have This Flag + Unk15 = 0x8000, // 15 Auto Shoot, Shoot, Throw, - This Is Autoshot Flag + CantTriggerProc = 0x10000, // 16 Confirmed With Many Patchnotes + NoInitialAggro = 0x20000, // 17 Soothe Animal, 39758, Mind Soothe + IgnoreHitResult = 0x40000, // 18 Spell Should Always Hit Its Target + DisableProc = 0x80000, // 19 During Aura Proc No Spells Can Trigger (20178, 20375) + DeathPersistent = 0x100000, // 20 Death Persistent Spells + Unk21 = 0x200000, // 21 Unused + ReqWand = 0x400000, // 22 Req Wand + Unk23 = 0x800000, // 23 + ReqOffhand = 0x1000000, // 24 Req Offhand Weapon + TreatAsPeriodic = 0x2000000, // 25 Makes the spell appear as periodic in client combat logs - used by spells that trigger another spell on each tick + CanProcWithTriggered = 0x4000000, // 26 Auras With This Attribute Can Proc From Triggered Spell Casts With TriggeredCanTriggerProc2 (67736 + 52999) + DrainSoul = 0x8000000, // 27 Only Drain Soul Has This Flag + Unk28 = 0x10000000, // 28 + NoDoneBonus = 0x20000000, // 29 Ignore Caster Spellpower And Done Damage Mods? Client Doesn'T Apply Spellmods For Those Spells + DontDisplayRange = 0x40000000, // 30 Client Doesn'T Display Range In Tooltip For Those Spells + Unk31 = 0x80000000 // 31 + } + public enum SpellAttr4 : uint + { + IgnoreResistances = 0x01, // 0 Spells With This Attribute Will Completely Ignore The Target'S Resistance (These Spells Can'T Be Resisted) + ProcOnlyOnCaster = 0x02, // 1 Proc Only On Effects With TargetUnitCaster? + Unk2 = 0x04, // 2 + Unk3 = 0x08, // 3 + Unk4 = 0x10, // 4 This Will No Longer Cause Guards To Attack On Use?? + Unk5 = 0x20, // 5 + NotStealable = 0x40, // 6 Although Such Auras Might Be Dispellable, They Cannot Be Stolen + CanCastWhileCasting = 0x80, // 7 Can be cast while another cast is in progress - see CanCastWhileCasting(SpellRec const*,CGUnit_C *,int &) + FixedDamage = 0x100, // 8 Ignores Taken Percent Damage Mods? + TriggerActivate = 0x200, // 9 Initially Disabled / Trigger Activate From Event (Execute, Riposte, Deep Freeze End Other) + SpellVsExtendCost = 0x400, // 10 Rogue Shiv Have This Flag + Unk11 = 0x800, // 11 + Unk12 = 0x1000, // 12 + CombatLogNoCaster = 0x2000, // 13 No caster object is sent to client combat log + DamageDoesntBreakAuras = 0x4000, // 14 Doesn'T Break Auras By Damage From These Spells + Unk15 = 0x8000, // 15 + NotUsableInArenaOrRatedBg = 0x10000, // 16 Cannot Be Used In Both Arenas Or Rated Battlegrounds + UsableInArena = 0x20000, // 17 + AreaTargetChain = 0x40000, // 18 (Nyi)Hits Area Targets One After Another Instead Of All At Once + Unk19 = 0x80000, // 19 Proc Dalayed, After Damage Or Don'T Proc On Absorb? + NotCheckSelfcastPower = 0x100000, // 20 Supersedes Message "More Powerful Spell Applied" For Self Casts. + Unk21 = 0x200000, // 21 Pally Aura, Dk Presence, Dudu Form, Warrior Stance, Shadowform, Hunter Track + Unk22 = 0x400000, // 22 Seal Of Command (42058,57770) And Gymer'S Smash 55426 + Unk23 = 0x800000, // 23 + Unk24 = 0x1000000, // 24 Some Shoot Spell + IsPetScaling = 0x2000000, // 25 Pet Scaling Auras + CastOnlyInOutland = 0x4000000, // 26 Can Only Be Used In Outland. + Unk27 = 0x8000000, // 27 + Unk28 = 0x10000000, // 28 Aimed Shot + Unk29 = 0x20000000, // 29 + Unk30 = 0x40000000, // 30 + Unk31 = 0x80000000 // 31 Polymorph (Chicken) 228 And Sonic Boom (38052,38488) + } + public enum SpellAttr5 : uint + { + CanChannelWhenMoving = 0x01, // 0 available casting channel spell when moving + NoReagentWhilePrep = 0x02, // 1 Not Need Reagents If UnitFlagPreparation + Unk2 = 0x04, // 2 + UsableWhileStunned = 0x08, // 3 Usable While Stunned + Unk4 = 0x10, // 4 + SingleTargetSpell = 0x20, // 5 Only One Target Can Be Apply At A Time + Unk6 = 0x40, // 6 + Unk7 = 0x80, // 7 + Unk8 = 0x100, // 8 + StartPeriodicAtApply = 0x200, // 9 Begin Periodic Tick At Aura Apply + HideDuration = 0x400, // 10 Do Not Send Duration To Client + AllowTargetOfTargetAsTarget = 0x800, // 11 (Nyi) Uses Target'S Target As Target If Original Target Not Valid (Intervene For Example) + Unk12 = 0x1000, // 12 Cleave Related? + HasteAffectDuration = 0x2000, // 13 Haste Effects Decrease Duration Of This + Unk14 = 0x4000, // 14 + Unk15 = 0x8000, // 15 Inflits On Multiple Targets? + Unk16 = 0x10000, // 16 + UsableWhileFeared = 0x20000, // 17 Usable While Feared + UsableWhileConfused = 0x40000, // 18 Usable While Confused + DontTurnDuringCast = 0x80000, // 19 Blocks Caster'S Turning When Casting (Client Does Not Automatically Turn Caster'S Model To Face UnitFieldTarget) + Unk20 = 0x100000, // 20 + Unk21 = 0x200000, // 21 + Unk22 = 0x400000, // 22 + Unk23 = 0x800000, // 23 + Unk24 = 0x1000000, // 24 + Unk25 = 0x2000000, // 25 + Unk26 = 0x4000000, // 26 Aoe Related - Boulder, Cannon, Corpse Explosion, Fire Nova, Flames, Frost Bomb, Living Bomb, Seed Of Corruption, Starfall, Thunder Clap, Volley + DontShowAuraIfSelfCast = 0x8000000, // 27 + DontShowAuraIfNotSelfCast = 0x10000000, // 28 + Unk29 = 0x20000000, // 29 + Unk30 = 0x40000000, // 30 + Unk31 = 0x80000000 // 31 Forces All Nearby Enemies To Focus Attacks Caster + } + public enum SpellAttr6 : uint + { + DontDisplayCooldown = 0x01, // 0 Client Doesn'T Display Cooldown In Tooltip For These Spells + OnlyInArena = 0x02, // 1 Only Usable In Arena + IgnoreCasterAuras = 0x04, // 2 + AssistIgnoreImmuneFlag = 0x08, // 3 + Unk4 = 0x10, // 4 + Unk5 = 0x20, // 5 + UseSpellCastEvent = 0x40, // 6 + Unk7 = 0x80, // 7 + CantTargetCrowdControlled = 0x100, // 8 + Unk9 = 0x200, // 9 + CanTargetPossessedFriends = 0x400, // 10 Nyi! + NotInRaidInstance = 0x800, // 11 Not Usable In Raid Instance + CastableWhileOnVehicle = 0x1000, // 12 Castable While Caster Is On Vehicle + CanTargetInvisible = 0x2000, // 13 Ignore Visibility Requirement For Spell Target (Phases, Invisibility, Etc.) + Unk14 = 0x4000, // 14 + Unk15 = 0x8000, // 15 Only 54368, 67892 + Unk16 = 0x10000, // 16 + Unk17 = 0x20000, // 17 Mount Spell + CastByCharmer = 0x40000, // 18 Client Won'T Allow To Cast These Spells When Unit Is Not Possessed && Charmer Of Caster Will Be Original Caster + Unk19 = 0x80000, // 19 Only 47488, 50782 + OnlyVisibleToCaster = 0x100000, // 20 Only 58371, 62218 + ClientUiTargetEffects = 0x200000, // 21 It'S Only Client-Side Attribute + Unk22 = 0x400000, // 22 Only 72054 + Unk23 = 0x800000, // 23 + CanTargetUntargetable = 0x1000000, // 24 + NotResetSwingIfInstant = 0x2000000, // 25 Exorcism, Flash Of Light + Unk26 = 0x4000000, // 26 Related To Player Castable Positive Buff + Unk27 = 0x8000000, // 27 + Unk28 = 0x10000000, // 28 Death Grip + NoDonePctDamageMods = 0x20000000, // 29 Ignores Done Percent Damage Mods? + Unk30 = 0x40000000, // 30 + IgnoreCategoryCooldownMods = 0x80000000 // 31 Some Special Cooldown Calc? Only 2894 + } + public enum SpellAttr7 : uint + { + Unk0 = 0x01, // 0 Shaman'S New Spells (Call Of The ...), Feign Death. + IgnoreDurationMods = 0x02, // 1 Duration is not affected by duration modifiers + ReactivateAtResurrect = 0x04, // 2 Paladin'S Auras And 65607 Only. + IsCheatSpell = 0x08, // 3 Cannot Cast If Caster Doesn'T Have Unitflag2 & UnitFlag2AllowCheatSpells + Unk4 = 0x10, // 4 Only 47883 (Soulstone Resurrection) And Test Spell. + SummonTotem = 0x20, // 5 Only Shaman Player Totems. + NoPushbackOnDamage = 0x40, // 6 Does not cause spell pushback on damage + Unk7 = 0x80, // 7 66218 (Launch) Spell. + HordeOnly = 0x100, // 8 Teleports, Mounts And Other Spells. + AllianceOnly = 0x200, // 9 Teleports, Mounts And Other Spells. + DispelCharges = 0x400, // 10 Dispel And Spellsteal Individual Charges Instead Of Whole Aura. + InterruptOnlyNonplayer = 0x800, // 11 Only Non-Player Casts Interrupt, Though Feral Charge - Bear Has It. + SilenceOnlyNonplayer = 0x1000, // 12 Not Set In 3.2.2a. + Unk13 = 0x2000, // 13 Not Set In 3.2.2a. + Unk14 = 0x4000, // 14 Only 52150 (Raise Dead - Pet) Spell. + Unk15 = 0x8000, // 15 Exorcism. Usable On Players? 100% Crit Chance On Undead And Demons? + CanRestoreSecondaryPower = 0x10000, // 16 These spells can replenish a powertype, which is not the current powertype. + Unk17 = 0x20000, // 17 Only 27965 (Suicide) Spell. + HasChargeEffect = 0x40000, // 18 Only Spells That Have Charge Among Effects. + ZoneTeleport = 0x80000, // 19 Teleports To Specific Zones. + Unk20 = 0x100000, // 20 Blink, Divine Shield, Ice Block + Unk21 = 0x200000, // 21 Not Set + Unk22 = 0x400000, // 22 + Unk23 = 0x800000, // 23 Motivate, Mutilate, Shattering Throw + Unk24 = 0x1000000, // 24 Motivate, Mutilate, Perform Speech, Shattering Throw + Unk25 = 0x2000000, // 25 + Unk26 = 0x4000000, // 26 + Unk27 = 0x8000000, // 27 Not Set + ConsolidatedRaidBuff = 0x10000000, // 28 Related To Player Positive Buff + Unk29 = 0x20000000, // 29 Only 69028, 71237 + Unk30 = 0x40000000, // 30 Burning Determination, Divine Sacrifice, Earth Shield, Prayer Of Mending + ClientIndicator = 0x80000000 // 31 Only 70769 + } + public enum SpellAttr8 : uint + { + CantMiss = 0x01, // 0 + Unk1 = 0x02, // 1 + Unk2 = 0x04, // 2 + Unk3 = 0x08, // 3 + Unk4 = 0x10, // 4 + Unk5 = 0x20, // 5 + Unk6 = 0x40, // 6 + Unk7 = 0x80, // 7 + AffectPartyAndRaid = 0x100, // 8 + DontResetPeriodicTimer = 0x200, // 9 Periodic Auras With This Flag Keep Old Periodic Timer When Refreshing At Close To One Tick Remaining (Kind Of Anti Dot Clipping) + NameChangedDuringTransofrm = 0x400, // 10 + Unk11 = 0x800, // 11 + AuraSendAmount = 0x1000, // 12 Aura Must Have Flag AflagAnyEffectAmountSent To Send Amount + Unk13 = 0x2000, // 13 + Unk14 = 0x4000, // 14 + WaterMount = 0x8000, // 15 + Unk16 = 0x10000, // 16 + Unk17 = 0x20000, // 17 + RememberSpells = 0x40000, // 18 + UseComboPointsOnAnyTarget = 0x80000, // 19 + ArmorSpecialization = 0x100000, // 20 + Unk21 = 0x200000, // 21 + Unk22 = 0x400000, // 22 + BattleResurrection = 0x800000, // 23 + HealingSpell = 0x1000000, // 24 + Unk25 = 0x2000000, // 25 + RaidMarker = 0x4000000, // 26 Probably Spell No Need Learn To Cast + Unk27 = 0x8000000, // 27 + NotInBgOrArena = 0x10000000, // 28 + MasterySpecialization = 0x20000000, // 29 + Unk30 = 0x40000000, // 30 + AttackIgnoreImmuneToPCFlag = 0x80000000 // 31 + } + public enum SpellAttr9 : uint + { + Unk0 = 0x01, // 0 + Unk1 = 0x02, // 1 + RestrictedFlightArea = 0x04, // 2 + Unk3 = 0x08, // 3 + SpecialDelayCalculation = 0x10, // 4 + SummonPlayerTotem = 0x20, // 5 + Unk6 = 0x40, // 6 + Unk7 = 0x80, // 7 + AimedShot = 0x100, // 8 + NotUsableInArena = 0x200, // 9 Cannot Be Used In Arenas + Unk10 = 0x400, // 10 + Unk11 = 0x800, // 11 + Unk12 = 0x1000, // 12 + Slam = 0x2000, // 13 + UsableInRatedBattlegrounds = 0x4000, // 14 Can Be Used In Rated Battlegrounds + Unk15 = 0x8000, // 15 + Unk16 = 0x10000, // 16 + Unk17 = 0x20000, // 17 + Unk18 = 0x40000, // 18 + Unk19 = 0x80000, // 19 + Unk20 = 0x100000, // 20 + Unk21 = 0x200000, // 21 + Unk22 = 0x400000, // 22 + Unk23 = 0x800000, // 23 + Unk24 = 0x1000000, // 24 + Unk25 = 0x2000000, // 25 + Unk26 = 0x4000000, // 26 + Unk27 = 0x8000000, // 27 + Unk28 = 0x10000000, // 28 + Unk29 = 0x20000000, // 29 + Unk30 = 0x40000000, // 30 + Unk31 = 0x80000000 // 31 + } + public enum SpellAttr10 : uint + { + Unk0 = 0x01, // 0 + Unk1 = 0x02, // 1 + Unk2 = 0x04, // 2 + Unk3 = 0x08, // 3 + WaterSpout = 0x10, // 4 + Unk5 = 0x20, // 5 + Unk6 = 0x40, // 6 + TeleportPlayer = 0x80, // 7 + Unk8 = 0x100, // 8 + Unk9 = 0x200, // 9 + Unk10 = 0x400, // 10 + HerbGatheringMining = 0x800, // 11 + UseSpellBaseLevelForScaling = 0x1000, // 12 + Unk13 = 0x2000, // 13 + Unk14 = 0x4000, // 14 + Unk15 = 0x8000, // 15 + Unk16 = 0x10000, // 16 + Unk17 = 0x20000, // 17 + Unk18 = 0x40000, // 18 + Unk19 = 0x80000, // 19 + Unk20 = 0x100000, // 20 + Unk21 = 0x200000, // 21 + Unk22 = 0x400000, // 22 + Unk23 = 0x800000, // 23 + Unk24 = 0x1000000, // 24 + Unk25 = 0x2000000, // 25 + Unk26 = 0x4000000, // 26 + Unk27 = 0x8000000, // 27 + Unk28 = 0x10000000, // 28 + MountIsNotAccountWide = 0x20000000, // 29 This mount is stored per-character + Unk30 = 0x40000000, // 30 + Unk31 = 0x80000000 // 31 + } + public enum SpellAttr11 : uint + { + Unk0 = 0x01, // 0 + Unk1 = 0x02, // 1 + ScalesWithItemLevel = 0x04, // 2 + Unk3 = 0x08, // 3 + Unk4 = 0x10, // 4 + AbsorbFallDamage = 0x20, // 5 + Unk6 = 0x40, // 6 + RankIgnoresCasterLevel = 0x80, // 7 Spell_C_GetSpellRank returns SpellLevels->MaxLevel * 5 instead of std::min(SpellLevels->MaxLevel, caster->Level) * 5 + Unk8 = 0x100, // 8 + Unk9 = 0x200, // 9 + Unk10 = 0x400, // 10 + Unk11 = 0x800, // 11 + Unk12 = 0x1000, // 12 + Unk13 = 0x2000, // 13 + Unk14 = 0x4000, // 14 + Unk15 = 0x8000, // 15 + NotUsableInChallengeMode = 0x10000, // 16 + Unk17 = 0x20000, // 17 + Unk18 = 0x40000, // 18 + Unk19 = 0x80000, // 19 + Unk20 = 0x100000, // 20 + Unk21 = 0x200000, // 21 + Unk22 = 0x400000, // 22 + Unk23 = 0x800000, // 23 + Unk24 = 0x1000000, // 24 + Unk25 = 0x2000000, // 25 + Unk26 = 0x4000000, // 26 + Unk27 = 0x8000000, // 27 + Unk28 = 0x10000000, // 28 + Unk29 = 0x20000000, // 29 + Unk30 = 0x40000000, // 30 + Unk31 = 0x80000000 // 31 + } + public enum SpellAttr12 : uint + { + Unk0 = 0x01, // 0 + Unk1 = 0x02, // 1 + Unk2 = 0x04, // 2 + Unk3 = 0x08, // 3 + Unk4 = 0x10, // 4 + Unk5 = 0x20, // 5 + Unk6 = 0x40, // 6 + Unk7 = 0x80, // 7 + Unk8 = 0x100, // 8 + Unk9 = 0x200, // 9 + Unk10 = 0x400, // 10 + Unk11 = 0x800, // 11 + Unk12 = 0x1000, // 12 + Unk13 = 0x2000, // 13 + Unk14 = 0x4000, // 14 + Unk15 = 0x8000, // 15 + Unk16 = 0x10000, // 16 + Unk17 = 0x20000, // 17 + Unk18 = 0x40000, // 18 + Unk19 = 0x80000, // 19 + Unk20 = 0x100000, // 20 + Unk21 = 0x200000, // 21 + Unk22 = 0x400000, // 22 + Unk23 = 0x800000, // 23 + IsGarrisonBuff = 0x1000000, // 24 + Unk25 = 0x2000000, // 25 + Unk26 = 0x4000000, // 26 + IsReadinessSpell = 0x8000000, // 27 + Unk28 = 0x10000000, // 28 + Unk29 = 0x20000000, // 29 + Unk30 = 0x40000000, // 30 + Unk31 = 0x80000000 // 31 + } + public enum SpellAttr13 + { + Unk0 = 0x01, // 0 + Unk1 = 0x02, // 1 + Unk2 = 0x04, // 2 + Unk3 = 0x08, // 3 + Unk4 = 0x10, // 4 + Unk5 = 0x20, // 5 + Unk6 = 0x40, // 6 + Unk7 = 0x80, // 7 + Unk8 = 0x100, // 8 + Unk9 = 0x200, // 9 + Unk10 = 0x400, // 10 + Unk11 = 0x800, // 11 + Unk12 = 0x1000, // 12 + Unk13 = 0x2000, // 13 + Unk14 = 0x4000, // 14 + Unk15 = 0x8000, // 15 + Unk16 = 0x10000, // 16 + Unk17 = 0x20000, // 17 + ActivatesRequiredShapeshift = 0x40000, // 18 + Unk19 = 0x80000, // 19 + Unk20 = 0x100000, // 20 + Unk21 = 0x200000, // 21 + Unk22 = 0x400000, // 22 + Unk23 = 0x800000 // 23 + } + public enum SpellCustomAttributes + { + EnchantProc = 0x01, + ConeBack = 0x02, + ConeLine = 0x04, + ShareDamage = 0x08, + NoInitialThreat = 0x10, + IsTalent = 0x20, + DontBreakStealth = 0x40, + DirectDamage = 0x100, + Charge = 0x200, + PickPocket = 0x400, + NegativeEff0 = 0x1000, + NegativeEff1 = 0x2000, + NegativeEff2 = 0x4000, + IgnoreArmor = 0x8000, + ReqTargetFacingCaster = 0x10000, + ReqCasterBehindTarget = 0x20000, + AllowInflightTarget = 0x40000, + NeedsAmmoData = 0x80000, + + Negative = NegativeEff0 | NegativeEff1 | NegativeEff2 + } + #endregion + + //Effects + public enum SpellEffectName + { + Any = -1, + Null = 0, + Instakill = 1, + SchoolDamage = 2, + Dummy = 3, + PortalTeleport = 4, // Unused (4.3.4) + TeleportUnitsOld = 5, // Unused (7.0.3) + ApplyAura = 6, + EnvironmentalDamage = 7, + PowerDrain = 8, + HealthLeech = 9, + Heal = 10, + Bind = 11, + Portal = 12, + RitualBase = 13, // Unused (4.3.4) + IncreseCurrencyCap = 14, + RitualActivatePortal = 15, // Unused (4.3.4) + QuestComplete = 16, + WeaponDamageNoschool = 17, + Resurrect = 18, + AddExtraAttacks = 19, + Dodge = 20, + Evade = 21, + Parry = 22, + Block = 23, + CreateItem = 24, + Weapon = 25, + Defense = 26, + PersistentAreaAura = 27, + Summon = 28, + Leap = 29, + Energize = 30, + WeaponPercentDamage = 31, + TriggerMissile = 32, + OpenLock = 33, + SummonChangeItem = 34, + ApplyAreaAuraParty = 35, + LearnSpell = 36, + SpellDefense = 37, + Dispel = 38, + Language = 39, + DualWield = 40, + Jump = 41, + JumpDest = 42, + TeleportUnitsFaceCaster = 43, + SkillStep = 44, + PlayMovie = 45, + Spawn = 46, + TradeSkill = 47, + Stealth = 48, + Detect = 49, + TransDoor = 50, + ForceCriticalHit = 51, // Unused (4.3.4) + SetMaxBattlePetCount = 52, + EnchantItem = 53, + EnchantItemTemporary = 54, + Tamecreature = 55, + SummonPet = 56, + LearnPetSpell = 57, + WeaponDamage = 58, + CreateRandomItem = 59, + Proficiency = 60, + SendEvent = 61, + PowerBurn = 62, + Threat = 63, + TriggerSpell = 64, + ApplyAreaAuraRaid = 65, + CreateManaGem = 66, + HealMaxHealth = 67, + InterruptCast = 68, + Distract = 69, + Pull = 70, + Pickpocket = 71, + AddFarsight = 72, + UntrainTalents = 73, + ApplyGlyph = 74, + HealMechanical = 75, + SummonObjectWild = 76, + ScriptEffect = 77, + Attack = 78, + Sanctuary = 79, + AddComboPoints = 80, + PushAbilityToActionBar = 81, + BindSight = 82, + Duel = 83, + Stuck = 84, + SummonPlayer = 85, + ActivateObject = 86, + GameObjectDamage = 87, + GameobjectRepair = 88, + GameobjectSetDestructionState = 89, + KillCredit = 90, + ThreatAll = 91, + EnchantHeldItem = 92, + ForceDeselect = 93, + SelfResurrect = 94, + Skinning = 95, + Charge = 96, + CastButton = 97, + KnockBack = 98, + Disenchant = 99, + Inebriate = 100, + FeedPet = 101, + DismissPet = 102, + Reputation = 103, + SummonObjectSlot1 = 104, + Survey = 105, + ChangeRaidMarker = 106, + ShowCorpseLoot = 107, + DispelMechanic = 108, + ResurrectPet = 109, + DestroyAllTotems = 110, + DurabilityDamage = 111, + Effect112 = 112, + Effect113 = 113, + AttackMe = 114, + DurabilityDamagePct = 115, + SkinPlayerCorpse = 116, + SpiritHeal = 117, + Skill = 118, + ApplyAreaAuraPet = 119, + TeleportGraveyard = 120, + NormalizedWeaponDmg = 121, + Effect122 = 122, // Unused (4.3.4) + SendTaxi = 123, + PullTowards = 124, + ModifyThreatPercent = 125, + StealBeneficialBuff = 126, + Prospecting = 127, + ApplyAreaAuraFriend = 128, + ApplyAreaAuraEnemy = 129, + RedirectThreat = 130, + PlaySound = 131, + PlayMusic = 132, + UnlearnSpecialization = 133, + KillCredit2 = 134, + CallPet = 135, + HealPct = 136, + EnergizePct = 137, + LeapBack = 138, + ClearQuest = 139, + ForceCast = 140, + ForceCastWithValue = 141, + TriggerSpellWithValue = 142, + ApplyAreaAuraOwner = 143, + KnockBackDest = 144, + PullTowardsDest = 145, + ActivateRune = 146, + QuestFail = 147, + TriggerMissileSpellWithValue = 148, + ChargeDest = 149, + QuestStart = 150, + TriggerSpell2 = 151, + SummonRafFriend = 152, + CreateTamedPet = 153, + DiscoverTaxi = 154, + TitanGrip = 155, + EnchantItemPrismatic = 156, + CreateLoot = 157, + Milling = 158, + AllowRenamePet = 159, + ForceCast2 = 160, + TalentSpecCount = 161, + TalentSpecSelect = 162, + ObliterateItem = 163, + RemoveAura = 164, + DamageFromMaxHealthPCT = 165, + GiveCurrency = 166, + UpdatePlayerPhase = 167, + AllowControlPet = 168, + DestroyItem = 169, + UpdateZoneAurasPhases = 170, + Effect171 = 171, // Summons Gamebject + ResurrectWithAura = 172, // Aoe Ressurection + UnlockGuildVaultTab = 173, // Guild Tab Unlocked (Guild Perk) + ApllyAuraOnPet = 174, + Effect175 = 175, // Unused (4.3.4) + Sanctuary2 = 176, + Effect177 = 177, + Effect178 = 178, // Unused (4.3.4) + CreateAreaTrigger = 179, + UpdateAreatrigger = 180, + RemoveTalent = 181, + DespawnAreatrigger = 182, + Unk183 = 183, + Reputation2 = 184, + Unk185 = 185, + Unk186 = 186, + RandomizeArchaeologyDigsites = 187, + Unk188 = 188, + Loot = 189, // NYI, lootid in MiscValue ? + Unk190 = 190, + TeleportToDigsite = 191, + UncageBattlepet = 192, + StartPetBattle = 193, + Unk194 = 194, + Unk195 = 195, + Unk196 = 196, + Unk197 = 197, + PlayScene = 198, // NYI + Unk199 = 199, + HealBattlepetPct = 200, // NYI + EnableBattlePets = 201, // NYI + Unk202 = 202, + Unk203 = 203, + ChangeBattlepetQuality = 204, + LaunchQuestTask = 205, + AlterItem = 206, + Unk207 = 207, + Unk208 = 208, + Unk209 = 209, + LearnGarrisonBuilding = 210, + LearnGarrisonSpecialization = 211, + Unk212 = 212, + Unk213 = 213, + CreateGarrison = 214, + UpgradeCharacterSpells = 215, + CreateShipment = 216, + UpgradeGarrison = 217, + Unk218 = 218, + CreateConversation = 219, + AddGarrisonFollower = 220, + Unk221 = 221, + CreateHeirloomItem = 222, + ChangeItemBonuses = 223, + ActivateGarrisonBuilding = 224, + GrantBattlepetLevel = 225, + Unk226 = 226, + TeleportToLfgDungeon = 227, + Unk228 = 228, + SetFollowerQuality = 229, + IncreaseFollowerItemLevel = 230, + IncreaseFollowerExperience = 231, + RemovePhase = 232, + RandomizeFollowerAbilities = 233, + Unk234 = 234, + Unk235 = 235, + GiveExperience = 236, + GiveRestedEcperienceBonus = 237, + IncreaseSkill = 238, + EndGarrisonBuildingConstruction = 239, + GiveArtifactPower = 240, + Unk241 = 241, + GiveArtifactPowerNoBonus = 242, // Unaffected by Artifact Knowledge + ApplyEnchantIllusion = 243, + LearnFollowerAbility = 244, + UpgradeHeirloom = 245, + FinishGarrisonMission = 246, + AddGarrisonMission = 247, + FinishShipment = 248, + ForceEquipItem = 249, + TakeScreenshot = 250, // Serverside marker for selfie screenshot - achievement check + SetGarrisonCacheSize = 251, + TeleportUnits = 252, + GiveHonor = 253, + Unk254 = 254, + LearnTransmogSet = 255, + TotalSpellEffects = 256, + } + + public enum SpellEffectHandle + { + Launch, + LaunchTarget, + Hit, + HitTarget + } + + public enum ProcFlags + { + None = 0x0, + + Killed = 0x01, // 00 Killed by agressor - not sure about this flag + Kill = 0x02, // 01 Kill target (in most cases need XP/Honor reward) + + DoneMeleeAutoAttack = 0x04, // 02 Done melee auto attack + TakenMeleeAutoAttack = 0x08, // 03 Taken melee auto attack + + DoneSpellMeleeDmgClass = 0x10, // 04 Done attack by Spell that has dmg class melee + TakenSpellMeleeDmgClass = 0x20, // 05 Taken attack by Spell that has dmg class melee + + DoneRangedAutoAttack = 0x40, // 06 Done ranged auto attack + TakenRangedAutoAttack = 0x80, // 07 Taken ranged auto attack + + DoneSpellRangedDmgClass = 0x100, // 08 Done attack by Spell that has dmg class ranged + TakenSpellRangedDmgClass = 0x200, // 09 Taken attack by Spell that has dmg class ranged + + DoneSpellNoneDmgClassPos = 0x400, // 10 Done positive spell that has dmg class none + TakenSpellNoneDmgClassPos = 0x800, // 11 Taken positive spell that has dmg class none + + DoneSpellNoneDmgClassNeg = 0x1000, // 12 Done negative spell that has dmg class none + TakenSpellNoneDmgClassNeg = 0x2000, // 13 Taken negative spell that has dmg class none + + DoneSpellMagicDmgClassPos = 0x4000, // 14 Done positive spell that has dmg class magic + TakenSpellMagicDmgClassPos = 0x8000, // 15 Taken positive spell that has dmg class magic + + DoneSpellMagicDmgClassNeg = 0x10000, // 16 Done negative spell that has dmg class magic + TakenSpellMagicDmgClassNeg = 0x20000, // 17 Taken negative spell that has dmg class magic + + DonePeriodic = 0x40000, // 18 Successful do periodic (damage / healing) + TakenPeriodic = 0x80000, // 19 Taken spell periodic (damage / healing) + + TakenDamage = 0x100000, // 20 Taken any damage + DoneTrapActivation = 0x200000, // 21 On trap activation (possibly needs name change to ONGAMEOBJECTCAST or USE) + + DoneMainHandAttack = 0x400000, // 22 Done main-hand melee attacks (spell and autoattack) + DoneOffHandAttack = 0x800000, // 23 Done off-hand melee attacks (spell and autoattack) + + Death = 0x1000000, // 24 Died in any way + Jump = 0x02000000, // 25 Jumped + + EnterCombat = 0x08000000, // 27 Entered combat + EncounterStart = 0x10000000, // 28 Encounter started + + // flag masks + AutoAttackMask = DoneMeleeAutoAttack | TakenMeleeAutoAttack | DoneRangedAutoAttack | TakenRangedAutoAttack, + + MeleeMask = DoneMeleeAutoAttack | TakenMeleeAutoAttack | DoneSpellMeleeDmgClass | TakenSpellMeleeDmgClass + | DoneMainHandAttack | DoneOffHandAttack, + + RangedMask = DoneRangedAutoAttack | TakenRangedAutoAttack | DoneSpellRangedDmgClass | TakenSpellRangedDmgClass, + + SpellMask = DoneSpellMeleeDmgClass | TakenSpellMeleeDmgClass | DoneSpellRangedDmgClass | TakenSpellRangedDmgClass + | DoneSpellNoneDmgClassPos | TakenSpellNoneDmgClassPos | DoneSpellNoneDmgClassNeg | TakenSpellNoneDmgClassNeg + | DoneSpellMagicDmgClassPos | TakenSpellMagicDmgClassPos | DoneSpellMagicDmgClassNeg | TakenSpellMagicDmgClassNeg, + + SpellCastMask = SpellMask | DoneTrapActivation | RangedMask, + + PeriodicMask = DonePeriodic | TakenPeriodic, + + DoneHitMask = DoneMeleeAutoAttack | DoneRangedAutoAttack | DoneSpellMeleeDmgClass | DoneSpellRangedDmgClass + | DoneSpellNoneDmgClassPos | DoneSpellNoneDmgClassNeg | DoneSpellMagicDmgClassPos | DoneSpellMagicDmgClassNeg + | DonePeriodic | DoneMainHandAttack | DoneOffHandAttack, + + TakenHitMask = TakenMeleeAutoAttack | TakenRangedAutoAttack | TakenSpellMeleeDmgClass | TakenSpellRangedDmgClass + | TakenSpellNoneDmgClassPos | TakenSpellNoneDmgClassNeg | TakenSpellMagicDmgClassPos | TakenSpellMagicDmgClassNeg + | TakenPeriodic | TakenDamage, + + ReqSpellPhaseMask = SpellMask & DoneHitMask, + + MeleeBasedTriggerMask = (DoneMeleeAutoAttack | TakenMeleeAutoAttack | DoneSpellMeleeDmgClass | TakenSpellMeleeDmgClass | + DoneRangedAutoAttack | TakenRangedAutoAttack | DoneSpellRangedDmgClass | TakenSpellRangedDmgClass) + } + public enum ProcFlagsExLegacy + { + None = 0x0, // If none can tigger on Hit/Crit only (passive spells MUST defined by SpellFamily flag) + NormalHit = 0x01, // If set only from normal hit (only damage spells) + CriticalHit = 0x02, + Miss = 0x04, + Resist = 0x08, + Dodge = 0x10, + Parry = 0x20, + Block = 0x40, + Evade = 0x80, + Immune = 0x100, + Deflect = 0x200, + Absorb = 0x400, + Reflect = 0x800, + Interrupt = 0x1000, // Melee hit result can be Interrupt (not used) + FullBlock = 0x2000, // block al attack damage + Reserved2 = 0x4000, + NotActiveSpell = 0x8000, // Spell mustn't do damage/heal to proc + ExTriggerAlways = 0x10000, // If set trigger always no matter of hit result + ExOneTimeTrigger = 0x20000, // If set trigger always but only one time (not implemented yet) + OnlyActiveSpell = 0x40000, // Spell has to do damage/heal to proc + + // Flags for internal use - do not use these in db! + InternalCantProc = 0x800000, + InternalDot = 0x1000000, + InternalHot = 0x2000000, + InternalTriggered = 0x4000000, + InternalReqFamily = 0x8000000, + + AuraProcMask = (NormalHit | CriticalHit | Miss | Resist | Dodge | Parry + | Block | Evade | Immune | Deflect | Absorb | Reflect | Interrupt) + } + public enum ProcFlagsSpellPhase + { + None = 0x0, + Cast = 0x1, + Hit = 0x2, + Finish = 0x4, + MaskAll = Cast | Hit | Finish + } + public enum ProcFlagsHit + { + None = 0x0, // No Value - Proc_Hit_Normal | Proc_Hit_Critical For Taken Proc Type, Proc_Hit_Normal | Proc_Hit_Critical | Proc_Hit_Absorb For Done + Normal = 0x1, // Non-Critical Hits + Critical = 0x2, + Miss = 0x4, + FullResist = 0x8, + Dodge = 0x10, + Parry = 0x20, + Block = 0x40, // Partial Or Full Block + Evade = 0x80, + Immune = 0x100, + Deflect = 0x200, + Absorb = 0x400, // Partial Or Full Absorb + Reflect = 0x800, + Interrupt = 0x1000, // (Not Used Atm) + FullBlock = 0x2000, + MaskAll = 0x0003FFF + } + + // Spell aura states + public enum AuraStateType + { // (C) used in caster aura state (T) used in target aura state + // (c) used in caster aura state-not (t) used in target aura state-not + None = 0, // C | + Defense = 1, // C | + HealthLess20Percent = 2, // CcT | + Berserking = 3, // C T | + Frozen = 4, // c t| frozen target + Judgement = 5, // C | + //UNKNOWN6 = 6, // | not used + HunterParry = 7, // C | + //UNKNOWN7 = 7, // c | creature cheap shot / focused bursts spells + //UNKNOWN8 = 8, // t| test spells + //UNKNOWN9 = 9, // | + WarriorVictoryRush = 10, // C | warrior victory rush + //UNKNOWN11 = 11, // C t| 60348 - Maelstrom Ready!, test spells + FaerieFire = 12, // c t| + HealthLess35Percent = 13, // C T | + Conflagrate = 14, // T | + Swiftmend = 15, // T | + DeadlyPoison = 16, // T | + Enrage = 17, // C | + Bleeding = 18, // T| + Unk19 = 19, // | + //UNKNOWN20 = 20, // c | only (45317 Suicide) + //UNKNOWN21 = 21, // | not used + Unk22 = 22, // C t| varius spells (63884, 50240) + HealthAbove75Percent = 23, // C | + + PerCasterAuraStateMask = (1 << (Conflagrate - 1)) | (1 << (DeadlyPoison - 1)) + } + + // target enum name consist of: + // [OBJECTTYPE][REFERENCETYPE(skipped for caster)][SELECTIONTYPE(skipped for default)][additional specifiers(friendly, BACKLEFT, etc.] + public enum Targets + { + UnitCaster = 1, + UnitNearbyEnemy = 2, + UnitNearbyParty = 3, + UnitNearbyAlly = 4, + UnitPet = 5, + UnitEnemy = 6, + UnitSrcAreaEntry = 7, + UnitDestAreaEntry = 8, + DestHome = 9, + UnitSrcAreaUnk11 = 11, + UnitSrcAreaEnemy = 15, + UnitDestAreaEnemy = 16, + DestDb = 17, + DestCaster = 18, + UnitCasterAreaParty = 20, + UnitAlly = 21, + SrcCaster = 22, + GameobjectTarget = 23, + UnitConeEnemy24 = 24, + UnitAny = 25, + GameobjectItemTarget = 26, + UnitMaster = 27, + DestDynobjEnemy = 28, + DestDynobjAlly = 29, + UnitSrcAreaAlly = 30, + UnitDestAreaAlly = 31, + DestCasterSummon = 32, // Front Left, Doesn'T Use Radius + UnitSrcAreaParty = 33, + UnitDestAreaParty = 34, + UnitParty = 35, + DestCasterUnk36 = 36, + UnitLastareaParty = 37, + UnitNearbyEntry = 38, + DestCasterFishing = 39, + GameobjectNearbyEntry = 40, + DestCasterFrontRight = 41, + DestCasterBackRight = 42, + DestCasterBackLeft = 43, + DestCasterFrontLeft = 44, + UnitChainhealAlly = 45, + DestNearbyEntry = 46, + DestCasterFront = 47, + DestCasterBack = 48, + DestCasterRight = 49, + DestCasterLeft = 50, + GameobjectSrcArea = 51, + GameobjectDestArea = 52, + DestEnemy = 53, + UnitConeEnemy54 = 54, + DestCasterFrontLeap = 55, // For A Leap Spell + UnitCasterAreaRaid = 56, + UnitRaid = 57, + UnitNearbyRaid = 58, + UnitConeAlly = 59, + UnitConeEntry = 60, + UnitAreaRaidClass = 61, + Unk62 = 62, + DestAny = 63, + DestFront = 64, + DestBack = 65, + DestRight = 66, + DestLeft = 67, + DestFrontRight = 68, + DestBackRight = 69, + DestBackLeft = 70, + DestFrontLeft = 71, + DestCasterRandom = 72, + DestCasterRadius = 73, + DestRandom = 74, + DestRadius = 75, + DestChannelTarget = 76, + UnitChannelTarget = 77, + DestDestFront = 78, + DestDestBack = 79, + DestDestRight = 80, + DestDestLeft = 81, + DestDestFrontRight = 82, + DestDestBackRight = 83, + DestDestBackLeft = 84, + DestDestFrontLeft = 85, + DestDestRandom = 86, + DestDest = 87, + DestDynobjNone = 88, + DestTraj = 89, + UnitMinipet = 90, + DestDestRadius = 91, + UnitSummoner = 92, + CorpseSrcAreaEnemy = 93, // Nyi + UnitVehicle = 94, + UnitPassenger = 95, + UnitPassenger0 = 96, + UnitPassenger1 = 97, + UnitPassenger2 = 98, + UnitPassenger3 = 99, + UnitPassenger4 = 100, + UnitPassenger5 = 101, + UnitPassenger6 = 102, + UnitPassenger7 = 103, + UnitConeEnemy104 = 104, + UnitUnk105 = 105, // 1 Spell + DestChannelCaster = 106, + UnkDestAreaUnk107 = 107, // Not Enough Info - Only Generic Spells Avalible + GameobjectCone = 108, + DestUnk110 = 110, // 1 Spell + Unk111 = 111, + Unk112 = 112, + Unk113 = 113, + Unk114 = 114, + Unk115 = 115, + Unk116 = 116, + Unk117 = 117, + Unk118 = 118, + Unk119 = 119, + Unk120 = 120, + Unk121 = 121, + Unk122 = 122, + Unk123 = 123, + Unk124 = 124, + Unk125 = 125, + Unk126 = 126, + Unk127 = 127, + Unk128 = 128, + Unk129 = 129, + Unk130 = 130, + Unk131 = 131, + Unk132 = 132, + Unk133 = 133, + Unk134 = 134, + Unk135 = 135, + Unk136 = 136, + Unk137 = 137, + Unk138 = 138, + Unk139 = 139, + Unk140 = 140, + Unk141 = 141, + Unk142 = 142, + Unk143 = 143, + Unk144 = 144, + Unk145 = 145, + Unk146 = 146, + Unk147 = 147, + Unk148 = 148, + TotalSpellTargets = 149 + } + public enum SpellTargetSelectionCategories + { + Nyi, + Default, + Channel, + Nearby, + Cone, + Area + } + + public enum SpellTargetReferenceTypes + { + None, + Caster, + Target, + Last, + Src, + Dest + } + + public enum SpellTargetObjectTypes + { + None = 0, + Src, + Dest, + Unit, + UnitAndDest, + Gobj, + GobjItem, + Item, + Corpse, + // Only For Effect Target Type + CorpseEnemy, + CorpseAlly + } + + public enum SpellTargetCheckTypes + { + Default, + Entry, + Enemy, + Ally, + Party, + Raid, + RaidClass, + Passenger + } + + public enum SpellTargetDirectionTypes + { + None, + Front, + Back, + Right, + Left, + FrontRight, + BackRight, + BackLeft, + FrontLeft, + Random, + Entry + } + + public enum ProcFlagsSpellType + { + None = 0x0, + Damage = 0x1, // damage type of spell + Heal = 0x2, // heal type of spell + NoDmgHeal = 0x4, // other spells + MaskAll = Damage | Heal | NoDmgHeal + } + + public enum SpellCooldownFlags + { + None = 0x0, + IncludeGCD = 0x1, // Starts GCD in addition to normal cooldown specified in the packet + IncludeEventCooldowns = 0x2 // Starts GCD for spells that should start their cooldown on events, requires SPELL_COOLDOWN_FLAG_INCLUDE_GCD set + } +} diff --git a/Framework/Constants/SupportSystemConst.cs b/Framework/Constants/SupportSystemConst.cs new file mode 100644 index 000000000..80d61eb9c --- /dev/null +++ b/Framework/Constants/SupportSystemConst.cs @@ -0,0 +1,42 @@ +/* + * Copyright (C) 2012-2017 CypherCore + * + * 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, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +namespace Framework.Constants +{ + public enum GMTicketSystemStatus + { + Disabled = 0, + Enabled = 1 + } + + public enum GMSupportComplaintType + { + None = 0, + Language = 2, + PlayerName = 4, + Cheat = 15, + GuildName = 23, + Spamming = 24 + } + + public enum SupportSpamType + { + Mail = 0, + Chat = 1, + Calendar = 2 + } +} diff --git a/Framework/Constants/UnitConst.cs b/Framework/Constants/UnitConst.cs new file mode 100644 index 000000000..d54212ab5 --- /dev/null +++ b/Framework/Constants/UnitConst.cs @@ -0,0 +1,577 @@ +/* + * Copyright (C) 2012-2017 CypherCore + * + * 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, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +namespace Framework.Constants +{ + public enum VehicleSeatFlags : uint + { + HasLowerAnimForEnter = 0x01, + HasLowerAnimForRide = 0x02, + Unk3 = 0x04, + ShouldUseVehSeatExitAnimOnVoluntaryExit = 0x08, + Unk5 = 0x10, + Unk6 = 0x20, + Unk7 = 0x40, + Unk8 = 0x80, + Unk9 = 0x100, + HidePassenger = 0x200, // Passenger Is Hidden + AllowTurning = 0x400, // Needed For CgcameraSyncfreelookfacing + CanControl = 0x800, // LuaUnitinvehiclecontrolseat + CanCastMountSpell = 0x1000, // Can Cast Spells With SpellAuraMounted From Seat (Possibly 4.X Only, 0 Seats On 3.3.5a) + Uncontrolled = 0x2000, // Can Override !& CanEnterOrExit + CanAttack = 0x4000, // Can Attack, Cast Spells And Use Items From Vehicle + ShouldUseVehSeatExitAnimOnForcedExit = 0x8000, + Unk17 = 0x10000, + Unk18 = 0x20000, // Needs Research And Support (28 Vehicles): Allow Entering Vehicles While Keeping Specific Permanent(?) Auras That Impose Visuals (States Like Beeing Under Freeze/Stun Mechanic, Emote State Animations). + HasVehExitAnimVoluntaryExit = 0x40000, + HasVehExitAnimForcedExit = 0x80000, + PassengerNotSelectable = 0x100000, + Unk22 = 0x200000, + RecHasVehicleEnterAnim = 0x400000, + IsUsingVehicleControls = 0x800000, // LuaIsusingvehiclecontrols + EnableVehicleZoom = 0x1000000, + CanEnterOrExit = 0x2000000, // LuaCanexitvehicle - Can Enter And Exit At Free Will + CanSwitch = 0x4000000, // LuaCanswitchvehicleseats + HasStartWaritingForVehTransitionAnimEnter = 0x8000000, + HasStartWaritingForVehTransitionAnimExit = 0x10000000, + CanCast = 0x20000000, // LuaUnithasvehicleui + Unk2 = 0x40000000, // Checked In Conjunction With 0x800 In Castspell2 + AllowsInteraction = 0x80000000 + } + + public enum VehicleSeatFlagsB : uint + { + None = 0x00, + UsableForced = 0x02, + TargetsInRaidUi = 0x08, // Lua_Unittargetsvehicleinraidui + Ejectable = 0x20, // Ejectable + UsableForced2 = 0x40, + UsableForced3 = 0x100, + KeepPet = 0x20000, + UsableForced4 = 0x02000000, + CanSwitch = 0x4000000, + VehiclePlayerframeUi = 0x80000000 // Lua_Unithasvehicleplayerframeui - Actually Checked For Flagsb &~ 0x80000000 + } + + public enum SpellClickCastFlags + { + CasterClicker = 0x01, + TargetClicker = 0x02, + OrigCasterOwner = 0x04 + } + + + public enum SummonSlot + { + Pet = 0, + Totem = 1, + MiniPet = 5, + Quest = 6, + } + public enum PlayerTotemType + { + Fire = 63, + Earth = 81, + Water = 82, + Air = 83 + } + + public enum BaseModType + { + FlatMod, + PCTmod, + End + } + + //To all Immune system, if target has immunes, + //some spell that related to ImmuneToDispel or ImmuneToSchool or ImmuneToDamage type can't cast to it, + //some spell_effects that related to ImmuneToEffect(only this effect in the spell) can't cast to it, + //some aura(related to Mechanics or ImmuneToState) can't apply to it. + public enum SpellImmunity + { + Effect = 0, // enum SpellEffects + State = 1, // enum AuraType + School = 2, // enum SpellSchoolMask + Damage = 3, // enum SpellSchoolMask + Dispel = 4, // enum DispelType + Mechanic = 5, // enum Mechanics + Id = 6, + Max = 7 + } + + + public enum BaseModGroup + { + CritPercentage, + RangedCritPercentage, + OffhandCritPercentage, + ShieldBlockValue, + End + } + + public enum DamageEffectType + { + Direct = 0, // used for normal weapon damage (not for class abilities or spells) + SpellDirect = 1, // spell/class abilities damage + DOT = 2, + Heal = 3, + NoDamage = 4, // used also in case when damage applied to health but not applied to spell channelInterruptFlags/etc + Self = 5 + } + public enum WeaponDamageRange + { + MinDamage, + MaxDamage + } + public enum UnitMods + { + StatStrength, // STAT_STRENGTH..UNIT_MOD_STAT_INTELLECT must be in existed order, it's accessed by index values of Stats enum. + StatAgility, + StatStamina, + StatIntellect, + Health, + Mana, // MANA..RUNIC_POWER must be in existed order, it's accessed by index values of Powers enum. + Rage, + Focus, + Energy, + Unused, // Old HAPPINESS + Rune, + RunicPower, + SoulShards, + Eclipse, + HolyPower, + Alternative, + Maelstrom, + Chi, + Insanity, + BurningEmbers, + DemonicFury, + ArcaneCharges, + Fury, + Pain, + Armor, // ARMOR..RESISTANCE_ARCANE must be in existed order, it's accessed by index values of SpellSchools enum. + ResistanceHoly, + ResistanceFire, + ResistanceNature, + ResistanceFrost, + ResistanceShadow, + ResistanceArcane, + AttackPower, + AttackPowerRanged, + DamageMainHand, + DamageOffHand, + DamageRanged, + End, + // synonyms + StatStart = StatStrength, + StatEnd = StatIntellect + 1, + ResistanceStart = Armor, + ResistanceEnd = ResistanceArcane + 1, + PowerStart = Mana, + PowerEnd = Pain + 1 + } + public enum UnitModifierType + { + BaseValue = 0, + BasePCTExcludeCreate = 1, // percent modifier affecting all stat values from auras and gear but not player base for level + BasePCT = 2, + TotalValue = 3, + TotalPCT = 4, + End = 5 + } + public enum VictimState + { + Intact = 0, // set when attacker misses + Hit = 1, // victim got clear/blocked hit + Dodge = 2, + Parry = 3, + Imterrupt = 4, + Blocks = 5, // unused? not set when blocked, even on full block + Evades = 6, + Immune = 7, + Deflects = 8 + } + + public enum HitInfo + { + NormalSwing = 0x0, + Unk1 = 0x01, // req correct packet structure + AffectsVictim = 0x02, + OffHand = 0x04, + Unk2 = 0x08, + Miss = 0x10, + FullAbsorb = 0x20, + PartialAbsorb = 0x40, + FullResist = 0x80, + PartialResist = 0x100, + CriticalHit = 0x200, // critical hit + Unk10 = 0x400, + Unk11 = 0x800, + Unk12 = 0x1000, + Block = 0x2000, // blocked damage + Unk14 = 0x4000, // set only if meleespellid is present// no world text when victim is hit for 0 dmg(HideWorldTextForNoDamage?) + Unk15 = 0x8000, // player victim?// something related to blod sprut visual (BloodSpurtInBack?) + Glancing = 0x10000, + Crushing = 0x20000, + NoAnimation = 0x40000, + Unk19 = 0x80000, + Unk20 = 0x100000, + SwingNoHitSound = 0x200000, // unused? + Unk22 = 0x00400000, + RageGain = 0x800000, + FakeDamage = 0x1000000 // enables damage animation even if no damage done, set only if no damage + } + + public enum ReactStates + { + Passive = 0, + Defensive = 1, + Aggressive = 2, + Assist = 3 + } + + /// + /// UnitStandStateType + /// + public enum UnitStandStateType + { + Stand = 0, + Sit = 1, + SitChair = 2, + Sleep = 3, + SitLowChair = 4, + SitMediumChair = 5, + SitHighChair = 6, + Dead = 7, + Kneel = 8, + Submerged = 9 + } + + public struct UnitBytes0Offsets + { + public const byte Race = 0; + public const byte Class = 1; + public const byte PlayerClass = 2; + public const byte Gender = 3; + } + + public struct UnitBytes1Offsets + { + public const byte StandState = 0; + public const byte PetTalents = 1; // unused + public const byte VisFlag = 2; + public const byte AnimTier = 3; + } + + public enum UnitStandFlags + { + Unk1 = 0x01, + Creep = 0x02, + Untrackable = 0x04, + Unk4 = 0x08, + Unk5 = 0x10, + All = 0xFF + } + + public enum UnitBytes1Flags + { + AlwaysStand = 0x01, + Hover = 0x02, + Unk3 = 0x04, + All = 0xFF + } + + public struct UnitBytes2Offsets + { + public const byte SheathState = 0; + public const byte PvpFlag = 1; + public const byte PetFlags = 2; + public const byte ShapeshiftForm = 3; + } + + public enum UnitBytes2Flags + { + PvP = 0x01, + Unk1 = 0x02, + FFAPvp = 0x04, + Sanctuary = 0x08, + Unk4 = 0x10, + Unk5 = 0x20, + Unk6 = 0x40, + Unk7 = 0x80, + } + + public enum UnitPetFlags + { + CanBeRenamed = 0x01, + CanBeAbandoned = 0x02 + } + + /// + /// UnitFields.Bytes2, 3 + /// + public enum ShapeShiftForm + { + None = 0, + CatForm = 1, + TreeOfLife = 2, + TravelForm = 3, + AquaticForm = 4, + BearForm = 5, + Ambient = 6, + Ghoul = 7, + DireBearForm = 8, + CraneStance = 9, + TharonjaSkeleton = 10, + DarkmoonTestOfStrength = 11, + BlbPlayer = 12, + ShadowDance = 13, + CreatureBear = 14, + CreatureCat = 15, + GhostWolf = 16, + BattleStance = 17, + DefensiveStance = 18, + BerserkerStance = 19, + SerpentStance = 20, + Zombie = 21, + Metamorphosis = 22, + OxStance = 23, + TigerStance = 24, + Undead = 25, + Frenzy = 26, + FlightFormEpic = 27, + Shadowform = 28, + FlightForm = 29, + Stealth = 30, + MoonkinForm = 31, + SpiritOfRedemption = 32, + GladiatorStance = 33 + } + + public enum ReactiveType + { + Defense = 0, + HunterParry = 1, + OverPower = 2, + Max = 3 + } + + public enum SpellValueMod + { + BasePoint0, + BasePoint1, + BasePoint2, + BasePoint3, + BasePoint4, + BasePoint5, + BasePoint6, + BasePoint7, + BasePoint8, + BasePoint9, + BasePoint10, + BasePoint11, + BasePoint12, + BasePoint13, + BasePoint14, + BasePoint15, + BasePoint16, + BasePoint17, + BasePoint18, + BasePoint19, + BasePoint20, + BasePoint21, + BasePoint22, + BasePoint23, + BasePoint24, + BasePoint25, + BasePoint26, + BasePoint27, + BasePoint28, + BasePoint29, + BasePoint30, + BasePoint31, + End, + RadiusMod, + MaxTargets, + AuraStack + } + + public enum CombatRating + { + Amplify = 0, + DefenseSkill = 1, + Dodge = 2, + Parry = 3, + Block = 4, + HitMelee = 5, + HitRanged = 6, + HitSpell = 7, + CritMelee = 8, + CritRanged = 9, + CritSpell = 10, + Multistrike = 11, + Readiness = 12, + Speed = 13, + ResilienceCritTaken = 14, + ResiliencePlayerDamage = 15, + Lifesteal = 16, + HasteMelee = 17, + HasteRanged = 18, + HasteSpell = 19, + Avoidance = 20, + Studiness = 21, + Unused7 = 22, + Expertise = 23, + ArmorPenetration = 24, + Mastery = 25, + PvpPower = 26, + Cleave = 27, + VersatilityDamageDone = 28, + VersatilityHealingDone = 29, + VersatilityDamageTaken = 30, + Unused12 = 31, + Max = 32 + } + + public enum DeathState + { + Alive = 0, + JustDied = 1, + Corpse = 2, + Dead = 3, + JustRespawned = 4 + } + public enum UnitState : uint + { + Died = 0x01, // Player Has Fake Death Aura + MeleeAttacking = 0x02, // Player Is Melee Attacking Someone + //Melee_Attack_By = 0x04, // Player Is Melee Attack By Someone + Stunned = 0x08, + Roaming = 0x10, + Chase = 0x20, + //Searching = 0x40, + Fleeing = 0x80, + InFlight = 0x100, // Player Is In Flight Mode + Follow = 0x200, + Root = 0x400, + Confused = 0x800, + Distracted = 0x1000, + Isolated = 0x2000, // Area Auras Do Not Affect Other Players + AttackPlayer = 0x4000, + Casting = 0x8000, + Possessed = 0x10000, + Charging = 0x20000, + Jumping = 0x40000, + //Onvehicle = 0x80000, + Move = 0x100000, + Rotating = 0x200000, + Evade = 0x400000, + RoamingMove = 0x800000, + ConfusedMove = 0x1000000, + FleeingMove = 0x2000000, + ChaseMove = 0x4000000, + FollowMove = 0x8000000, + IgnorePathfinding = 0x10000000, + Unattackable = InFlight, + // For Real Move Using Movegen Check And Stop (Except Unstoppable Flight) + Moving = RoamingMove | ConfusedMove | FleeingMove | ChaseMove | FollowMove, + Controlled = (Confused | Stunned | Fleeing), + LostControl = (Controlled | Jumping | Charging), + Sightless = (LostControl | Evade), + CannotAutoattack = (LostControl | Casting), + CannotTurn = (LostControl | Rotating), + // Stay By Different Reasons + NotMove = Root | Stunned | Died | Distracted, + AllState = 0xffffffff //(Stopped | Moving | In_Combat | In_Flight) + } + + public enum UnitMoveType + { + Walk = 0, + Run = 1, + RunBack = 2, + Swim = 3, + SwimBack = 4, + TurnRate = 5, + Flight = 6, + FlightBack = 7, + PitchRate = 8, + Max = 9 + } + public enum WeaponAttackType + { + BaseAttack = 0, + OffAttack = 1, + RangedAttack = 2, + Max + } + + public enum UnitTypeMask + { + None = 0x0, + Summon = 0x01, + Minion = 0x02, + Guardian = 0x04, + Totem = 0x08, + Pet = 0x10, + Vehicle = 0x20, + Puppet = 0x40, + HunterPet = 0x80, + ControlableGuardian = 0x100, + Accessory = 0x200 + } + + public enum CurrentSpellTypes + { + Melee = 0, + Generic = 1, + Channeled = 2, + AutoRepeat = 3, + Max = 4 + } + public enum SheathState + { + Unarmed = 0, // non prepared weapon + Melee = 1, // prepared melee weapon + Ranged = 2, // prepared ranged weapon + Max = 3 + } + public enum MeleeHitOutcome + { + Evade, + Miss, + Dodge, + Block, + Parry, + Glancing, + Crit, + Crushing, + Normal + } + + public enum UnitDynFlags + { + None = 0x00, + HideModel = 0x02, // Object model is not shown with this flag + Lootable = 0x04, + TrackUnit = 0x08, + Tapped = 0x10, // Lua_UnitIsTapped + SpecialInfo = 0x20, + Dead = 0x40, + ReferAFriend = 0x80 + } +} diff --git a/Framework/Constants/Update/UpdateFieldFlags.cs b/Framework/Constants/Update/UpdateFieldFlags.cs new file mode 100644 index 000000000..a097cc88a --- /dev/null +++ b/Framework/Constants/Update/UpdateFieldFlags.cs @@ -0,0 +1,5039 @@ +/* + * Copyright (C) 2012-2017 CypherCore + * + * 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, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +namespace Framework.Constants +{ + public class UpdateFieldFlags + { + public const uint None = 0x0; + public const uint Public = 0x1; + public const uint Private = 0x2; + public const uint Owner = 0x4; + public const uint ItemOwner = 0x8; + public const uint SpecialInfo = 0x10; + public const uint PartyMember = 0x20; + public const uint UnitAll = 0x40; + public const uint Dynamic = 0x80; + public const uint Unknownx100 = 0x100; + public const uint Urgent = 0x200; + public const uint UrgentSelfOnly = 0x400; + + public static uint[] ItemUpdateFieldFlags = new uint[(int)ContainerFields.End] + { + Public, // OBJECT_FIELD_GUID + Public, // OBJECT_FIELD_GUID+1 + Public, // OBJECT_FIELD_GUID+2 + Public, // OBJECT_FIELD_GUID+3 + Public, // OBJECT_FIELD_DATA + Public, // OBJECT_FIELD_DATA+1 + Public, // OBJECT_FIELD_DATA+2 + Public, // OBJECT_FIELD_DATA+3 + Public, // OBJECT_FIELD_TYPE + Dynamic, // OBJECT_FIELD_ENTRY + Dynamic | Urgent, // OBJECT_DYNAMIC_FLAGS + Public, // OBJECT_FIELD_SCALE_X + Public, // ITEM_FIELD_OWNER + Public, // ITEM_FIELD_OWNER+1 + Public, // ITEM_FIELD_OWNER+2 + Public, // ITEM_FIELD_OWNER+3 + Public, // ITEM_FIELD_CONTAINED + Public, // ITEM_FIELD_CONTAINED+1 + Public, // ITEM_FIELD_CONTAINED+2 + Public, // ITEM_FIELD_CONTAINED+3 + Public, // ITEM_FIELD_CREATOR + Public, // ITEM_FIELD_CREATOR+1 + Public, // ITEM_FIELD_CREATOR+2 + Public, // ITEM_FIELD_CREATOR+3 + Public, // ITEM_FIELD_GIFTCREATOR + Public, // ITEM_FIELD_GIFTCREATOR+1 + Public, // ITEM_FIELD_GIFTCREATOR+2 + Public, // ITEM_FIELD_GIFTCREATOR+3 + Owner, // ITEM_FIELD_STACK_COUNT + Owner, // ITEM_FIELD_DURATION + Owner, // ITEM_FIELD_SPELL_CHARGES + Owner, // ITEM_FIELD_SPELL_CHARGES+1 + Owner, // ITEM_FIELD_SPELL_CHARGES+2 + Owner, // ITEM_FIELD_SPELL_CHARGES+3 + Owner, // ITEM_FIELD_SPELL_CHARGES+4 + Public, // ITEM_FIELD_FLAGS + Public, // ITEM_FIELD_ENCHANTMENT + Public, // ITEM_FIELD_ENCHANTMENT+1 + Public, // ITEM_FIELD_ENCHANTMENT+2 + Public, // ITEM_FIELD_ENCHANTMENT+3 + Public, // ITEM_FIELD_ENCHANTMENT+4 + Public, // ITEM_FIELD_ENCHANTMENT+5 + Public, // ITEM_FIELD_ENCHANTMENT+6 + Public, // ITEM_FIELD_ENCHANTMENT+7 + Public, // ITEM_FIELD_ENCHANTMENT+8 + Public, // ITEM_FIELD_ENCHANTMENT+9 + Public, // ITEM_FIELD_ENCHANTMENT+10 + Public, // ITEM_FIELD_ENCHANTMENT+11 + Public, // ITEM_FIELD_ENCHANTMENT+12 + Public, // ITEM_FIELD_ENCHANTMENT+13 + Public, // ITEM_FIELD_ENCHANTMENT+14 + Public, // ITEM_FIELD_ENCHANTMENT+15 + Public, // ITEM_FIELD_ENCHANTMENT+16 + Public, // ITEM_FIELD_ENCHANTMENT+17 + Public, // ITEM_FIELD_ENCHANTMENT+18 + Public, // ITEM_FIELD_ENCHANTMENT+19 + Public, // ITEM_FIELD_ENCHANTMENT+20 + Public, // ITEM_FIELD_ENCHANTMENT+21 + Public, // ITEM_FIELD_ENCHANTMENT+22 + Public, // ITEM_FIELD_ENCHANTMENT+23 + Public, // ITEM_FIELD_ENCHANTMENT+24 + Public, // ITEM_FIELD_ENCHANTMENT+25 + Public, // ITEM_FIELD_ENCHANTMENT+26 + Public, // ITEM_FIELD_ENCHANTMENT+27 + Public, // ITEM_FIELD_ENCHANTMENT+28 + Public, // ITEM_FIELD_ENCHANTMENT+29 + Public, // ITEM_FIELD_ENCHANTMENT+30 + Public, // ITEM_FIELD_ENCHANTMENT+31 + Public, // ITEM_FIELD_ENCHANTMENT+32 + Public, // ITEM_FIELD_ENCHANTMENT+33 + Public, // ITEM_FIELD_ENCHANTMENT+34 + Public, // ITEM_FIELD_ENCHANTMENT+35 + Public, // ITEM_FIELD_ENCHANTMENT+36 + Public, // ITEM_FIELD_ENCHANTMENT+37 + Public, // ITEM_FIELD_ENCHANTMENT+38 + Public, // ITEM_FIELD_PROPERTY_SEED + Public, // ITEM_FIELD_RANDOM_PROPERTIES_ID + Owner, // ITEM_FIELD_DURABILITY + Owner, // ITEM_FIELD_MAXDURABILITY + Public, // ITEM_FIELD_CREATE_PLAYED_TIME + Owner, // ITEM_FIELD_MODIFIERS_MASK + Public, // ITEM_FIELD_CONTEXT + Owner, // ITEM_FIELD_ARTIFACT_XP + Owner, // ITEM_FIELD_ARTIFACT_XP+1 + Owner, // ITEM_FIELD_APPEARANCE_MOD_ID + Public, // CONTAINER_FIELD_SLOT_1 + Public, // CONTAINER_FIELD_SLOT_1+1 + Public, // CONTAINER_FIELD_SLOT_1+2 + Public, // CONTAINER_FIELD_SLOT_1+3 + Public, // CONTAINER_FIELD_SLOT_1+4 + Public, // CONTAINER_FIELD_SLOT_1+5 + Public, // CONTAINER_FIELD_SLOT_1+6 + Public, // CONTAINER_FIELD_SLOT_1+7 + Public, // CONTAINER_FIELD_SLOT_1+8 + Public, // CONTAINER_FIELD_SLOT_1+9 + Public, // CONTAINER_FIELD_SLOT_1+10 + Public, // CONTAINER_FIELD_SLOT_1+11 + Public, // CONTAINER_FIELD_SLOT_1+12 + Public, // CONTAINER_FIELD_SLOT_1+13 + Public, // CONTAINER_FIELD_SLOT_1+14 + Public, // CONTAINER_FIELD_SLOT_1+15 + Public, // CONTAINER_FIELD_SLOT_1+16 + Public, // CONTAINER_FIELD_SLOT_1+17 + Public, // CONTAINER_FIELD_SLOT_1+18 + Public, // CONTAINER_FIELD_SLOT_1+19 + Public, // CONTAINER_FIELD_SLOT_1+20 + Public, // CONTAINER_FIELD_SLOT_1+21 + Public, // CONTAINER_FIELD_SLOT_1+22 + Public, // CONTAINER_FIELD_SLOT_1+23 + Public, // CONTAINER_FIELD_SLOT_1+24 + Public, // CONTAINER_FIELD_SLOT_1+25 + Public, // CONTAINER_FIELD_SLOT_1+26 + Public, // CONTAINER_FIELD_SLOT_1+27 + Public, // CONTAINER_FIELD_SLOT_1+28 + Public, // CONTAINER_FIELD_SLOT_1+29 + Public, // CONTAINER_FIELD_SLOT_1+30 + Public, // CONTAINER_FIELD_SLOT_1+31 + Public, // CONTAINER_FIELD_SLOT_1+32 + Public, // CONTAINER_FIELD_SLOT_1+33 + Public, // CONTAINER_FIELD_SLOT_1+34 + Public, // CONTAINER_FIELD_SLOT_1+35 + Public, // CONTAINER_FIELD_SLOT_1+36 + Public, // CONTAINER_FIELD_SLOT_1+37 + Public, // CONTAINER_FIELD_SLOT_1+38 + Public, // CONTAINER_FIELD_SLOT_1+39 + Public, // CONTAINER_FIELD_SLOT_1+40 + Public, // CONTAINER_FIELD_SLOT_1+41 + Public, // CONTAINER_FIELD_SLOT_1+42 + Public, // CONTAINER_FIELD_SLOT_1+43 + Public, // CONTAINER_FIELD_SLOT_1+44 + Public, // CONTAINER_FIELD_SLOT_1+45 + Public, // CONTAINER_FIELD_SLOT_1+46 + Public, // CONTAINER_FIELD_SLOT_1+47 + Public, // CONTAINER_FIELD_SLOT_1+48 + Public, // CONTAINER_FIELD_SLOT_1+49 + Public, // CONTAINER_FIELD_SLOT_1+50 + Public, // CONTAINER_FIELD_SLOT_1+51 + Public, // CONTAINER_FIELD_SLOT_1+52 + Public, // CONTAINER_FIELD_SLOT_1+53 + Public, // CONTAINER_FIELD_SLOT_1+54 + Public, // CONTAINER_FIELD_SLOT_1+55 + Public, // CONTAINER_FIELD_SLOT_1+56 + Public, // CONTAINER_FIELD_SLOT_1+57 + Public, // CONTAINER_FIELD_SLOT_1+58 + Public, // CONTAINER_FIELD_SLOT_1+59 + Public, // CONTAINER_FIELD_SLOT_1+60 + Public, // CONTAINER_FIELD_SLOT_1+61 + Public, // CONTAINER_FIELD_SLOT_1+62 + Public, // CONTAINER_FIELD_SLOT_1+63 + Public, // CONTAINER_FIELD_SLOT_1+64 + Public, // CONTAINER_FIELD_SLOT_1+65 + Public, // CONTAINER_FIELD_SLOT_1+66 + Public, // CONTAINER_FIELD_SLOT_1+67 + Public, // CONTAINER_FIELD_SLOT_1+68 + Public, // CONTAINER_FIELD_SLOT_1+69 + Public, // CONTAINER_FIELD_SLOT_1+70 + Public, // CONTAINER_FIELD_SLOT_1+71 + Public, // CONTAINER_FIELD_SLOT_1+72 + Public, // CONTAINER_FIELD_SLOT_1+73 + Public, // CONTAINER_FIELD_SLOT_1+74 + Public, // CONTAINER_FIELD_SLOT_1+75 + Public, // CONTAINER_FIELD_SLOT_1+76 + Public, // CONTAINER_FIELD_SLOT_1+77 + Public, // CONTAINER_FIELD_SLOT_1+78 + Public, // CONTAINER_FIELD_SLOT_1+79 + Public, // CONTAINER_FIELD_SLOT_1+80 + Public, // CONTAINER_FIELD_SLOT_1+81 + Public, // CONTAINER_FIELD_SLOT_1+82 + Public, // CONTAINER_FIELD_SLOT_1+83 + Public, // CONTAINER_FIELD_SLOT_1+84 + Public, // CONTAINER_FIELD_SLOT_1+85 + Public, // CONTAINER_FIELD_SLOT_1+86 + Public, // CONTAINER_FIELD_SLOT_1+87 + Public, // CONTAINER_FIELD_SLOT_1+88 + Public, // CONTAINER_FIELD_SLOT_1+89 + Public, // CONTAINER_FIELD_SLOT_1+90 + Public, // CONTAINER_FIELD_SLOT_1+91 + Public, // CONTAINER_FIELD_SLOT_1+92 + Public, // CONTAINER_FIELD_SLOT_1+93 + Public, // CONTAINER_FIELD_SLOT_1+94 + Public, // CONTAINER_FIELD_SLOT_1+95 + Public, // CONTAINER_FIELD_SLOT_1+96 + Public, // CONTAINER_FIELD_SLOT_1+97 + Public, // CONTAINER_FIELD_SLOT_1+98 + Public, // CONTAINER_FIELD_SLOT_1+99 + Public, // CONTAINER_FIELD_SLOT_1+100 + Public, // CONTAINER_FIELD_SLOT_1+101 + Public, // CONTAINER_FIELD_SLOT_1+102 + Public, // CONTAINER_FIELD_SLOT_1+103 + Public, // CONTAINER_FIELD_SLOT_1+104 + Public, // CONTAINER_FIELD_SLOT_1+105 + Public, // CONTAINER_FIELD_SLOT_1+106 + Public, // CONTAINER_FIELD_SLOT_1+107 + Public, // CONTAINER_FIELD_SLOT_1+108 + Public, // CONTAINER_FIELD_SLOT_1+109 + Public, // CONTAINER_FIELD_SLOT_1+110 + Public, // CONTAINER_FIELD_SLOT_1+111 + Public, // CONTAINER_FIELD_SLOT_1+112 + Public, // CONTAINER_FIELD_SLOT_1+113 + Public, // CONTAINER_FIELD_SLOT_1+114 + Public, // CONTAINER_FIELD_SLOT_1+115 + Public, // CONTAINER_FIELD_SLOT_1+116 + Public, // CONTAINER_FIELD_SLOT_1+117 + Public, // CONTAINER_FIELD_SLOT_1+118 + Public, // CONTAINER_FIELD_SLOT_1+119 + Public, // CONTAINER_FIELD_SLOT_1+120 + Public, // CONTAINER_FIELD_SLOT_1+121 + Public, // CONTAINER_FIELD_SLOT_1+122 + Public, // CONTAINER_FIELD_SLOT_1+123 + Public, // CONTAINER_FIELD_SLOT_1+124 + Public, // CONTAINER_FIELD_SLOT_1+125 + Public, // CONTAINER_FIELD_SLOT_1+126 + Public, // CONTAINER_FIELD_SLOT_1+127 + Public, // CONTAINER_FIELD_SLOT_1+128 + Public, // CONTAINER_FIELD_SLOT_1+129 + Public, // CONTAINER_FIELD_SLOT_1+130 + Public, // CONTAINER_FIELD_SLOT_1+131 + Public, // CONTAINER_FIELD_SLOT_1+132 + Public, // CONTAINER_FIELD_SLOT_1+133 + Public, // CONTAINER_FIELD_SLOT_1+134 + Public, // CONTAINER_FIELD_SLOT_1+135 + Public, // CONTAINER_FIELD_SLOT_1+136 + Public, // CONTAINER_FIELD_SLOT_1+137 + Public, // CONTAINER_FIELD_SLOT_1+138 + Public, // CONTAINER_FIELD_SLOT_1+139 + Public, // CONTAINER_FIELD_SLOT_1+140 + Public, // CONTAINER_FIELD_SLOT_1+141 + Public, // CONTAINER_FIELD_SLOT_1+142 + Public, // CONTAINER_FIELD_SLOT_1+143 + Public, // CONTAINER_FIELD_NUM_SLOTS + }; + + public static uint[] ItemDynamicUpdateFieldFlags = new uint[(int)ItemDynamicFields.End] + { + Owner, // ITEM_DYNAMIC_FIELD_MODIFIERS + Owner | Unknownx100, // ITEM_DYNAMIC_FIELD_BONUSLIST_IDS + Owner, // ITEM_DYNAMIC_FIELD_ARTIFACT_POWERS + Owner, // ITEM_DYNAMIC_FIELD_GEMS + }; + + public static uint[] UnitUpdateFieldFlags = new uint[(int)PlayerFields.End] + { + Public, // OBJECT_FIELD_GUID + Public, // OBJECT_FIELD_GUID+1 + Public, // OBJECT_FIELD_GUID+2 + Public, // OBJECT_FIELD_GUID+3 + Public, // OBJECT_FIELD_DATA + Public, // OBJECT_FIELD_DATA+1 + Public, // OBJECT_FIELD_DATA+2 + Public, // OBJECT_FIELD_DATA+3 + Public, // OBJECT_FIELD_TYPE + Dynamic, // OBJECT_FIELD_ENTRY + Dynamic | Urgent, // OBJECT_DYNAMIC_FLAGS + Public, // OBJECT_FIELD_SCALE_X + Public, // UNIT_FIELD_CHARM + Public, // UNIT_FIELD_CHARM+1 + Public, // UNIT_FIELD_CHARM+2 + Public, // UNIT_FIELD_CHARM+3 + Public, // UNIT_FIELD_SUMMON + Public, // UNIT_FIELD_SUMMON+1 + Public, // UNIT_FIELD_SUMMON+2 + Public, // UNIT_FIELD_SUMMON+3 + Private, // UNIT_FIELD_CRITTER + Private, // UNIT_FIELD_CRITTER+1 + Private, // UNIT_FIELD_CRITTER+2 + Private, // UNIT_FIELD_CRITTER+3 + Public, // UNIT_FIELD_CHARMEDBY + Public, // UNIT_FIELD_CHARMEDBY+1 + Public, // UNIT_FIELD_CHARMEDBY+2 + Public, // UNIT_FIELD_CHARMEDBY+3 + Public, // UNIT_FIELD_SUMMONEDBY + Public, // UNIT_FIELD_SUMMONEDBY+1 + Public, // UNIT_FIELD_SUMMONEDBY+2 + Public, // UNIT_FIELD_SUMMONEDBY+3 + Public, // UNIT_FIELD_CREATEDBY + Public, // UNIT_FIELD_CREATEDBY+1 + Public, // UNIT_FIELD_CREATEDBY+2 + Public, // UNIT_FIELD_CREATEDBY+3 + Public, // UNIT_FIELD_DEMON_CREATOR + Public, // UNIT_FIELD_DEMON_CREATOR+1 + Public, // UNIT_FIELD_DEMON_CREATOR+2 + Public, // UNIT_FIELD_DEMON_CREATOR+3 + Public, // UNIT_FIELD_TARGET + Public, // UNIT_FIELD_TARGET+1 + Public, // UNIT_FIELD_TARGET+2 + Public, // UNIT_FIELD_TARGET+3 + Public, // UNIT_FIELD_BATTLE_PET_COMPANION_GUID + Public, // UNIT_FIELD_BATTLE_PET_COMPANION_GUID+1 + Public, // UNIT_FIELD_BATTLE_PET_COMPANION_GUID+2 + Public, // UNIT_FIELD_BATTLE_PET_COMPANION_GUID+3 + Public, // UNIT_FIELD_BATTLE_PET_DB_ID + Public, // UNIT_FIELD_BATTLE_PET_DB_ID+1 + Public | Urgent, // UNIT_CHANNEL_SPELL + Public | Urgent, // UNIT_CHANNEL_SPELL_X_SPELL_VISUAL + Public, // UNIT_FIELD_SUMMONED_BY_HOME_REALM + Public, // UNIT_FIELD_BYTES_0 + Public, // UNIT_FIELD_DISPLAY_POWER + Public, // UNIT_FIELD_OVERRIDE_DISPLAY_POWER_ID + Public, // UNIT_FIELD_HEALTH + Public, // UNIT_FIELD_HEALTH+1 + Public | UrgentSelfOnly, // UNIT_FIELD_POWER + Public | UrgentSelfOnly, // UNIT_FIELD_POWER+1 + Public | UrgentSelfOnly, // UNIT_FIELD_POWER+2 + Public | UrgentSelfOnly, // UNIT_FIELD_POWER+3 + Public | UrgentSelfOnly, // UNIT_FIELD_POWER+4 + Public | UrgentSelfOnly, // UNIT_FIELD_POWER+5 + Public, // UNIT_FIELD_MAXHEALTH + Public, // UNIT_FIELD_MAXHEALTH+1 + Public, // UNIT_FIELD_MAXPOWER + Public, // UNIT_FIELD_MAXPOWER+1 + Public, // UNIT_FIELD_MAXPOWER+2 + Public, // UNIT_FIELD_MAXPOWER+3 + Public, // UNIT_FIELD_MAXPOWER+4 + Public, // UNIT_FIELD_MAXPOWER+5 + Private | Owner | UnitAll, // UNIT_FIELD_POWER_REGEN_FLAT_MODIFIER + Private | Owner | UnitAll, // UNIT_FIELD_POWER_REGEN_FLAT_MODIFIER+1 + Private | Owner | UnitAll, // UNIT_FIELD_POWER_REGEN_FLAT_MODIFIER+2 + Private | Owner | UnitAll, // UNIT_FIELD_POWER_REGEN_FLAT_MODIFIER+3 + Private | Owner | UnitAll, // UNIT_FIELD_POWER_REGEN_FLAT_MODIFIER+4 + Private | Owner | UnitAll, // UNIT_FIELD_POWER_REGEN_FLAT_MODIFIER+5 + Private | Owner | UnitAll, // UNIT_FIELD_POWER_REGEN_INTERRUPTED_FLAT_MODIFIER + Private | Owner | UnitAll, // UNIT_FIELD_POWER_REGEN_INTERRUPTED_FLAT_MODIFIER+1 + Private | Owner | UnitAll, // UNIT_FIELD_POWER_REGEN_INTERRUPTED_FLAT_MODIFIER+2 + Private | Owner | UnitAll, // UNIT_FIELD_POWER_REGEN_INTERRUPTED_FLAT_MODIFIER+3 + Private | Owner | UnitAll, // UNIT_FIELD_POWER_REGEN_INTERRUPTED_FLAT_MODIFIER+4 + Private | Owner | UnitAll, // UNIT_FIELD_POWER_REGEN_INTERRUPTED_FLAT_MODIFIER+5 + Public, // UNIT_FIELD_LEVEL + Public, // UNIT_FIELD_EFFECTIVE_LEVEL + Public, // UNIT_FIELD_SCALING_LEVEL_MIN + Public, // UNIT_FIELD_SCALING_LEVEL_MAX + Public, // UNIT_FIELD_SCALING_LEVEL_DELTA + Public, // UNIT_FIELD_FACTIONTEMPLATE + Public, // UNIT_VIRTUAL_ITEM_SLOT_ID + Public, // UNIT_VIRTUAL_ITEM_SLOT_ID+1 + Public, // UNIT_VIRTUAL_ITEM_SLOT_ID+2 + Public, // UNIT_VIRTUAL_ITEM_SLOT_ID+3 + Public, // UNIT_VIRTUAL_ITEM_SLOT_ID+4 + Public, // UNIT_VIRTUAL_ITEM_SLOT_ID+5 + Public | Urgent, // UNIT_FIELD_FLAGS + Public | Urgent, // UNIT_FIELD_FLAGS_2 + Public | Urgent, // UNIT_FIELD_FLAGS_3 + Public, // UNIT_FIELD_AURASTATE + Public, // UNIT_FIELD_BASEATTACKTIME + Public, // UNIT_FIELD_BASEATTACKTIME+1 + Private, // UNIT_FIELD_RANGEDATTACKTIME + Public, // UNIT_FIELD_BOUNDINGRADIUS + Public, // UNIT_FIELD_COMBATREACH + Dynamic | Urgent, // UNIT_FIELD_DISPLAYID + Public | Urgent, // UNIT_FIELD_NATIVEDISPLAYID + Public | Urgent, // UNIT_FIELD_MOUNTDISPLAYID + Private | Owner | SpecialInfo, // UNIT_FIELD_MINDAMAGE + Private | Owner | SpecialInfo, // UNIT_FIELD_MAXDAMAGE + Private | Owner | SpecialInfo, // UNIT_FIELD_MINOFFHANDDAMAGE + Private | Owner | SpecialInfo, // UNIT_FIELD_MAXOFFHANDDAMAGE + Public, // UNIT_FIELD_BYTES_1 + Public, // UNIT_FIELD_PETNUMBER + Public, // UNIT_FIELD_PET_NAME_TIMESTAMP + Owner, // UNIT_FIELD_PETEXPERIENCE + Owner, // UNIT_FIELD_PETNEXTLEVELEXP + Public, // UNIT_MOD_CAST_SPEED + Public, // UNIT_MOD_CAST_HASTE + Public, // UNIT_FIELD_MOD_HASTE + Public, // UNIT_FIELD_MOD_RANGED_HASTE + Public, // UNIT_FIELD_MOD_HASTE_REGEN + Public, // UNIT_FIELD_MOD_TIME_RATE + Public, // UNIT_CREATED_BY_SPELL + Public | Dynamic, // UNIT_NPC_FLAGS + Public | Dynamic, // UNIT_NPC_FLAGS+1 + Public, // UNIT_NPC_EMOTESTATE + Private | Owner, // UNIT_FIELD_STAT + Private | Owner, // UNIT_FIELD_STAT+1 + Private | Owner, // UNIT_FIELD_STAT+2 + Private | Owner, // UNIT_FIELD_STAT+3 + Private | Owner, // UNIT_FIELD_POSSTAT + Private | Owner, // UNIT_FIELD_POSSTAT+1 + Private | Owner, // UNIT_FIELD_POSSTAT+2 + Private | Owner, // UNIT_FIELD_POSSTAT+3 + Private | Owner, // UNIT_FIELD_NEGSTAT + Private | Owner, // UNIT_FIELD_NEGSTAT+1 + Private | Owner, // UNIT_FIELD_NEGSTAT+2 + Private | Owner, // UNIT_FIELD_NEGSTAT+3 + Private | Owner | SpecialInfo, // UNIT_FIELD_RESISTANCES + Private | Owner | SpecialInfo, // UNIT_FIELD_RESISTANCES+1 + Private | Owner | SpecialInfo, // UNIT_FIELD_RESISTANCES+2 + Private | Owner | SpecialInfo, // UNIT_FIELD_RESISTANCES+3 + Private | Owner | SpecialInfo, // UNIT_FIELD_RESISTANCES+4 + Private | Owner | SpecialInfo, // UNIT_FIELD_RESISTANCES+5 + Private | Owner | SpecialInfo, // UNIT_FIELD_RESISTANCES+6 + Private | Owner, // UNIT_FIELD_RESISTANCEBUFFMODSPOSITIVE + Private | Owner, // UNIT_FIELD_RESISTANCEBUFFMODSPOSITIVE+1 + Private | Owner, // UNIT_FIELD_RESISTANCEBUFFMODSPOSITIVE+2 + Private | Owner, // UNIT_FIELD_RESISTANCEBUFFMODSPOSITIVE+3 + Private | Owner, // UNIT_FIELD_RESISTANCEBUFFMODSPOSITIVE+4 + Private | Owner, // UNIT_FIELD_RESISTANCEBUFFMODSPOSITIVE+5 + Private | Owner, // UNIT_FIELD_RESISTANCEBUFFMODSPOSITIVE+6 + Private | Owner, // UNIT_FIELD_RESISTANCEBUFFMODSNEGATIVE + Private | Owner, // UNIT_FIELD_RESISTANCEBUFFMODSNEGATIVE+1 + Private | Owner, // UNIT_FIELD_RESISTANCEBUFFMODSNEGATIVE+2 + Private | Owner, // UNIT_FIELD_RESISTANCEBUFFMODSNEGATIVE+3 + Private | Owner, // UNIT_FIELD_RESISTANCEBUFFMODSNEGATIVE+4 + Private | Owner, // UNIT_FIELD_RESISTANCEBUFFMODSNEGATIVE+5 + Private | Owner, // UNIT_FIELD_RESISTANCEBUFFMODSNEGATIVE+6 + Private | Owner, // UNIT_FIELD_MOD_BONUS_ARMOR + Public, // UNIT_FIELD_BASE_MANA + Private | Owner, // UNIT_FIELD_BASE_HEALTH + Public, // UNIT_FIELD_BYTES_2 + Private | Owner, // UNIT_FIELD_ATTACK_POWER + Private | Owner, // UNIT_FIELD_ATTACK_POWER_MOD_POS + Private | Owner, // UNIT_FIELD_ATTACK_POWER_MOD_NEG + Private | Owner, // UNIT_FIELD_ATTACK_POWER_MULTIPLIER + Private | Owner, // UNIT_FIELD_RANGED_ATTACK_POWER + Private | Owner, // UNIT_FIELD_RANGED_ATTACK_POWER_MOD_POS + Private | Owner, // UNIT_FIELD_RANGED_ATTACK_POWER_MOD_NEG + Private | Owner, // UNIT_FIELD_RANGED_ATTACK_POWER_MULTIPLIER + Private | Owner, // UNIT_FIELD_ATTACK_SPEED_AURA + Private | Owner, // UNIT_FIELD_MINRANGEDDAMAGE + Private | Owner, // UNIT_FIELD_MAXRANGEDDAMAGE + Private | Owner, // UNIT_FIELD_POWER_COST_MODIFIER + Private | Owner, // UNIT_FIELD_POWER_COST_MODIFIER+1 + Private | Owner, // UNIT_FIELD_POWER_COST_MODIFIER+2 + Private | Owner, // UNIT_FIELD_POWER_COST_MODIFIER+3 + Private | Owner, // UNIT_FIELD_POWER_COST_MODIFIER+4 + Private | Owner, // UNIT_FIELD_POWER_COST_MODIFIER+5 + Private | Owner, // UNIT_FIELD_POWER_COST_MODIFIER+6 + Private | Owner, // UNIT_FIELD_POWER_COST_MULTIPLIER + Private | Owner, // UNIT_FIELD_POWER_COST_MULTIPLIER+1 + Private | Owner, // UNIT_FIELD_POWER_COST_MULTIPLIER+2 + Private | Owner, // UNIT_FIELD_POWER_COST_MULTIPLIER+3 + Private | Owner, // UNIT_FIELD_POWER_COST_MULTIPLIER+4 + Private | Owner, // UNIT_FIELD_POWER_COST_MULTIPLIER+5 + Private | Owner, // UNIT_FIELD_POWER_COST_MULTIPLIER+6 + Private | Owner, // UNIT_FIELD_MAXHEALTHMODIFIER + Public, // UNIT_FIELD_HOVERHEIGHT + Public, // UNIT_FIELD_MIN_ITEM_LEVEL_CUTOFF + Public, // UNIT_FIELD_MIN_ITEM_LEVEL + Public, // UNIT_FIELD_MAXITEMLEVEL + Public, // UNIT_FIELD_WILD_BATTLEPET_LEVEL + Public, // UNIT_FIELD_BATTLEPET_COMPANION_NAME_TIMESTAMP + Public, // UNIT_FIELD_INTERACT_SPELLID + Dynamic | Urgent, // UNIT_FIELD_STATE_SPELL_VISUAL_ID + Dynamic | Urgent, // UNIT_FIELD_STATE_ANIM_ID + Dynamic | Urgent, // UNIT_FIELD_STATE_ANIM_KIT_ID + Dynamic | Urgent, // UNIT_FIELD_STATE_WORLD_EFFECT_ID + Dynamic | Urgent, // UNIT_FIELD_STATE_WORLD_EFFECT_ID+1 + Dynamic | Urgent, // UNIT_FIELD_STATE_WORLD_EFFECT_ID+2 + Dynamic | Urgent, // UNIT_FIELD_STATE_WORLD_EFFECT_ID+3 + Public, // UNIT_FIELD_SCALE_DURATION + Public, // UNIT_FIELD_LOOKS_LIKE_MOUNT_ID + Public, // UNIT_FIELD_LOOKS_LIKE_CREATURE_ID + Public, // UNIT_FIELD_LOOK_AT_CONTROLLER_ID + Public, // UNIT_FIELD_LOOK_AT_CONTROLLER_TARGET + Public, // UNIT_FIELD_LOOK_AT_CONTROLLER_TARGET+1 + Public, // UNIT_FIELD_LOOK_AT_CONTROLLER_TARGET+2 + Public, // UNIT_FIELD_LOOK_AT_CONTROLLER_TARGET+3 + Public, // PLAYER_DUEL_ARBITER + Public, // PLAYER_DUEL_ARBITER+1 + Public, // PLAYER_DUEL_ARBITER+2 + Public, // PLAYER_DUEL_ARBITER+3 + Public, // PLAYER_WOW_ACCOUNT + Public, // PLAYER_WOW_ACCOUNT+1 + Public, // PLAYER_WOW_ACCOUNT+2 + Public, // PLAYER_WOW_ACCOUNT+3 + Public, // PLAYER_LOOT_TARGET_GUID + Public, // PLAYER_LOOT_TARGET_GUID+1 + Public, // PLAYER_LOOT_TARGET_GUID+2 + Public, // PLAYER_LOOT_TARGET_GUID+3 + Public, // PLAYER_FLAGS + Public, // PLAYER_FLAGS_EX + Public, // PLAYER_GUILDRANK + Public, // PLAYER_GUILDDELETE_DATE + Public, // PLAYER_GUILDLEVEL + Public, // PLAYER_BYTES + Public, // PLAYER_BYTES_2 + Public, // PLAYER_BYTES_3 + Public, // PLAYER_BYTES_4 + Public, // PLAYER_DUEL_TEAM + Public, // PLAYER_GUILD_TIMESTAMP + PartyMember, // PLAYER_QUEST_LOG + PartyMember, // PLAYER_QUEST_LOG+1 + PartyMember, // PLAYER_QUEST_LOG+2 + PartyMember, // PLAYER_QUEST_LOG+3 + PartyMember, // PLAYER_QUEST_LOG+4 + PartyMember, // PLAYER_QUEST_LOG+5 + PartyMember, // PLAYER_QUEST_LOG+6 + PartyMember, // PLAYER_QUEST_LOG+7 + PartyMember, // PLAYER_QUEST_LOG+8 + PartyMember, // PLAYER_QUEST_LOG+9 + PartyMember, // PLAYER_QUEST_LOG+10 + PartyMember, // PLAYER_QUEST_LOG+11 + PartyMember, // PLAYER_QUEST_LOG+12 + PartyMember, // PLAYER_QUEST_LOG+13 + PartyMember, // PLAYER_QUEST_LOG+14 + PartyMember, // PLAYER_QUEST_LOG+15 + PartyMember, // PLAYER_QUEST_LOG+16 + PartyMember, // PLAYER_QUEST_LOG+17 + PartyMember, // PLAYER_QUEST_LOG+18 + PartyMember, // PLAYER_QUEST_LOG+19 + PartyMember, // PLAYER_QUEST_LOG+20 + PartyMember, // PLAYER_QUEST_LOG+21 + PartyMember, // PLAYER_QUEST_LOG+22 + PartyMember, // PLAYER_QUEST_LOG+23 + PartyMember, // PLAYER_QUEST_LOG+24 + PartyMember, // PLAYER_QUEST_LOG+25 + PartyMember, // PLAYER_QUEST_LOG+26 + PartyMember, // PLAYER_QUEST_LOG+27 + PartyMember, // PLAYER_QUEST_LOG+28 + PartyMember, // PLAYER_QUEST_LOG+29 + PartyMember, // PLAYER_QUEST_LOG+30 + PartyMember, // PLAYER_QUEST_LOG+31 + PartyMember, // PLAYER_QUEST_LOG+32 + PartyMember, // PLAYER_QUEST_LOG+33 + PartyMember, // PLAYER_QUEST_LOG+34 + PartyMember, // PLAYER_QUEST_LOG+35 + PartyMember, // PLAYER_QUEST_LOG+36 + PartyMember, // PLAYER_QUEST_LOG+37 + PartyMember, // PLAYER_QUEST_LOG+38 + PartyMember, // PLAYER_QUEST_LOG+39 + PartyMember, // PLAYER_QUEST_LOG+40 + PartyMember, // PLAYER_QUEST_LOG+41 + PartyMember, // PLAYER_QUEST_LOG+42 + PartyMember, // PLAYER_QUEST_LOG+43 + PartyMember, // PLAYER_QUEST_LOG+44 + PartyMember, // PLAYER_QUEST_LOG+45 + PartyMember, // PLAYER_QUEST_LOG+46 + PartyMember, // PLAYER_QUEST_LOG+47 + PartyMember, // PLAYER_QUEST_LOG+48 + PartyMember, // PLAYER_QUEST_LOG+49 + PartyMember, // PLAYER_QUEST_LOG+50 + PartyMember, // PLAYER_QUEST_LOG+51 + PartyMember, // PLAYER_QUEST_LOG+52 + PartyMember, // PLAYER_QUEST_LOG+53 + PartyMember, // PLAYER_QUEST_LOG+54 + PartyMember, // PLAYER_QUEST_LOG+55 + PartyMember, // PLAYER_QUEST_LOG+56 + PartyMember, // PLAYER_QUEST_LOG+57 + PartyMember, // PLAYER_QUEST_LOG+58 + PartyMember, // PLAYER_QUEST_LOG+59 + PartyMember, // PLAYER_QUEST_LOG+60 + PartyMember, // PLAYER_QUEST_LOG+61 + PartyMember, // PLAYER_QUEST_LOG+62 + PartyMember, // PLAYER_QUEST_LOG+63 + PartyMember, // PLAYER_QUEST_LOG+64 + PartyMember, // PLAYER_QUEST_LOG+65 + PartyMember, // PLAYER_QUEST_LOG+66 + PartyMember, // PLAYER_QUEST_LOG+67 + PartyMember, // PLAYER_QUEST_LOG+68 + PartyMember, // PLAYER_QUEST_LOG+69 + PartyMember, // PLAYER_QUEST_LOG+70 + PartyMember, // PLAYER_QUEST_LOG+71 + PartyMember, // PLAYER_QUEST_LOG+72 + PartyMember, // PLAYER_QUEST_LOG+73 + PartyMember, // PLAYER_QUEST_LOG+74 + PartyMember, // PLAYER_QUEST_LOG+75 + PartyMember, // PLAYER_QUEST_LOG+76 + PartyMember, // PLAYER_QUEST_LOG+77 + PartyMember, // PLAYER_QUEST_LOG+78 + PartyMember, // PLAYER_QUEST_LOG+79 + PartyMember, // PLAYER_QUEST_LOG+80 + PartyMember, // PLAYER_QUEST_LOG+81 + PartyMember, // PLAYER_QUEST_LOG+82 + PartyMember, // PLAYER_QUEST_LOG+83 + PartyMember, // PLAYER_QUEST_LOG+84 + PartyMember, // PLAYER_QUEST_LOG+85 + PartyMember, // PLAYER_QUEST_LOG+86 + PartyMember, // PLAYER_QUEST_LOG+87 + PartyMember, // PLAYER_QUEST_LOG+88 + PartyMember, // PLAYER_QUEST_LOG+89 + PartyMember, // PLAYER_QUEST_LOG+90 + PartyMember, // PLAYER_QUEST_LOG+91 + PartyMember, // PLAYER_QUEST_LOG+92 + PartyMember, // PLAYER_QUEST_LOG+93 + PartyMember, // PLAYER_QUEST_LOG+94 + PartyMember, // PLAYER_QUEST_LOG+95 + PartyMember, // PLAYER_QUEST_LOG+96 + PartyMember, // PLAYER_QUEST_LOG+97 + PartyMember, // PLAYER_QUEST_LOG+98 + PartyMember, // PLAYER_QUEST_LOG+99 + PartyMember, // PLAYER_QUEST_LOG+100 + PartyMember, // PLAYER_QUEST_LOG+101 + PartyMember, // PLAYER_QUEST_LOG+102 + PartyMember, // PLAYER_QUEST_LOG+103 + PartyMember, // PLAYER_QUEST_LOG+104 + PartyMember, // PLAYER_QUEST_LOG+105 + PartyMember, // PLAYER_QUEST_LOG+106 + PartyMember, // PLAYER_QUEST_LOG+107 + PartyMember, // PLAYER_QUEST_LOG+108 + PartyMember, // PLAYER_QUEST_LOG+109 + PartyMember, // PLAYER_QUEST_LOG+110 + PartyMember, // PLAYER_QUEST_LOG+111 + PartyMember, // PLAYER_QUEST_LOG+112 + PartyMember, // PLAYER_QUEST_LOG+113 + PartyMember, // PLAYER_QUEST_LOG+114 + PartyMember, // PLAYER_QUEST_LOG+115 + PartyMember, // PLAYER_QUEST_LOG+116 + PartyMember, // PLAYER_QUEST_LOG+117 + PartyMember, // PLAYER_QUEST_LOG+118 + PartyMember, // PLAYER_QUEST_LOG+119 + PartyMember, // PLAYER_QUEST_LOG+120 + PartyMember, // PLAYER_QUEST_LOG+121 + PartyMember, // PLAYER_QUEST_LOG+122 + PartyMember, // PLAYER_QUEST_LOG+123 + PartyMember, // PLAYER_QUEST_LOG+124 + PartyMember, // PLAYER_QUEST_LOG+125 + PartyMember, // PLAYER_QUEST_LOG+126 + PartyMember, // PLAYER_QUEST_LOG+127 + PartyMember, // PLAYER_QUEST_LOG+128 + PartyMember, // PLAYER_QUEST_LOG+129 + PartyMember, // PLAYER_QUEST_LOG+130 + PartyMember, // PLAYER_QUEST_LOG+131 + PartyMember, // PLAYER_QUEST_LOG+132 + PartyMember, // PLAYER_QUEST_LOG+133 + PartyMember, // PLAYER_QUEST_LOG+134 + PartyMember, // PLAYER_QUEST_LOG+135 + PartyMember, // PLAYER_QUEST_LOG+136 + PartyMember, // PLAYER_QUEST_LOG+137 + PartyMember, // PLAYER_QUEST_LOG+138 + PartyMember, // PLAYER_QUEST_LOG+139 + PartyMember, // PLAYER_QUEST_LOG+140 + PartyMember, // PLAYER_QUEST_LOG+141 + PartyMember, // PLAYER_QUEST_LOG+142 + PartyMember, // PLAYER_QUEST_LOG+143 + PartyMember, // PLAYER_QUEST_LOG+144 + PartyMember, // PLAYER_QUEST_LOG+145 + PartyMember, // PLAYER_QUEST_LOG+146 + PartyMember, // PLAYER_QUEST_LOG+147 + PartyMember, // PLAYER_QUEST_LOG+148 + PartyMember, // PLAYER_QUEST_LOG+149 + PartyMember, // PLAYER_QUEST_LOG+150 + PartyMember, // PLAYER_QUEST_LOG+151 + PartyMember, // PLAYER_QUEST_LOG+152 + PartyMember, // PLAYER_QUEST_LOG+153 + PartyMember, // PLAYER_QUEST_LOG+154 + PartyMember, // PLAYER_QUEST_LOG+155 + PartyMember, // PLAYER_QUEST_LOG+156 + PartyMember, // PLAYER_QUEST_LOG+157 + PartyMember, // PLAYER_QUEST_LOG+158 + PartyMember, // PLAYER_QUEST_LOG+159 + PartyMember, // PLAYER_QUEST_LOG+160 + PartyMember, // PLAYER_QUEST_LOG+161 + PartyMember, // PLAYER_QUEST_LOG+162 + PartyMember, // PLAYER_QUEST_LOG+163 + PartyMember, // PLAYER_QUEST_LOG+164 + PartyMember, // PLAYER_QUEST_LOG+165 + PartyMember, // PLAYER_QUEST_LOG+166 + PartyMember, // PLAYER_QUEST_LOG+167 + PartyMember, // PLAYER_QUEST_LOG+168 + PartyMember, // PLAYER_QUEST_LOG+169 + PartyMember, // PLAYER_QUEST_LOG+170 + PartyMember, // PLAYER_QUEST_LOG+171 + PartyMember, // PLAYER_QUEST_LOG+172 + PartyMember, // PLAYER_QUEST_LOG+173 + PartyMember, // PLAYER_QUEST_LOG+174 + PartyMember, // PLAYER_QUEST_LOG+175 + PartyMember, // PLAYER_QUEST_LOG+176 + PartyMember, // PLAYER_QUEST_LOG+177 + PartyMember, // PLAYER_QUEST_LOG+178 + PartyMember, // PLAYER_QUEST_LOG+179 + PartyMember, // PLAYER_QUEST_LOG+180 + PartyMember, // PLAYER_QUEST_LOG+181 + PartyMember, // PLAYER_QUEST_LOG+182 + PartyMember, // PLAYER_QUEST_LOG+183 + PartyMember, // PLAYER_QUEST_LOG+184 + PartyMember, // PLAYER_QUEST_LOG+185 + PartyMember, // PLAYER_QUEST_LOG+186 + PartyMember, // PLAYER_QUEST_LOG+187 + PartyMember, // PLAYER_QUEST_LOG+188 + PartyMember, // PLAYER_QUEST_LOG+189 + PartyMember, // PLAYER_QUEST_LOG+190 + PartyMember, // PLAYER_QUEST_LOG+191 + PartyMember, // PLAYER_QUEST_LOG+192 + PartyMember, // PLAYER_QUEST_LOG+193 + PartyMember, // PLAYER_QUEST_LOG+194 + PartyMember, // PLAYER_QUEST_LOG+195 + PartyMember, // PLAYER_QUEST_LOG+196 + PartyMember, // PLAYER_QUEST_LOG+197 + PartyMember, // PLAYER_QUEST_LOG+198 + PartyMember, // PLAYER_QUEST_LOG+199 + PartyMember, // PLAYER_QUEST_LOG+200 + PartyMember, // PLAYER_QUEST_LOG+201 + PartyMember, // PLAYER_QUEST_LOG+202 + PartyMember, // PLAYER_QUEST_LOG+203 + PartyMember, // PLAYER_QUEST_LOG+204 + PartyMember, // PLAYER_QUEST_LOG+205 + PartyMember, // PLAYER_QUEST_LOG+206 + PartyMember, // PLAYER_QUEST_LOG+207 + PartyMember, // PLAYER_QUEST_LOG+208 + PartyMember, // PLAYER_QUEST_LOG+209 + PartyMember, // PLAYER_QUEST_LOG+210 + PartyMember, // PLAYER_QUEST_LOG+211 + PartyMember, // PLAYER_QUEST_LOG+212 + PartyMember, // PLAYER_QUEST_LOG+213 + PartyMember, // PLAYER_QUEST_LOG+214 + PartyMember, // PLAYER_QUEST_LOG+215 + PartyMember, // PLAYER_QUEST_LOG+216 + PartyMember, // PLAYER_QUEST_LOG+217 + PartyMember, // PLAYER_QUEST_LOG+218 + PartyMember, // PLAYER_QUEST_LOG+219 + PartyMember, // PLAYER_QUEST_LOG+220 + PartyMember, // PLAYER_QUEST_LOG+221 + PartyMember, // PLAYER_QUEST_LOG+222 + PartyMember, // PLAYER_QUEST_LOG+223 + PartyMember, // PLAYER_QUEST_LOG+224 + PartyMember, // PLAYER_QUEST_LOG+225 + PartyMember, // PLAYER_QUEST_LOG+226 + PartyMember, // PLAYER_QUEST_LOG+227 + PartyMember, // PLAYER_QUEST_LOG+228 + PartyMember, // PLAYER_QUEST_LOG+229 + PartyMember, // PLAYER_QUEST_LOG+230 + PartyMember, // PLAYER_QUEST_LOG+231 + PartyMember, // PLAYER_QUEST_LOG+232 + PartyMember, // PLAYER_QUEST_LOG+233 + PartyMember, // PLAYER_QUEST_LOG+234 + PartyMember, // PLAYER_QUEST_LOG+235 + PartyMember, // PLAYER_QUEST_LOG+236 + PartyMember, // PLAYER_QUEST_LOG+237 + PartyMember, // PLAYER_QUEST_LOG+238 + PartyMember, // PLAYER_QUEST_LOG+239 + PartyMember, // PLAYER_QUEST_LOG+240 + PartyMember, // PLAYER_QUEST_LOG+241 + PartyMember, // PLAYER_QUEST_LOG+242 + PartyMember, // PLAYER_QUEST_LOG+243 + PartyMember, // PLAYER_QUEST_LOG+244 + PartyMember, // PLAYER_QUEST_LOG+245 + PartyMember, // PLAYER_QUEST_LOG+246 + PartyMember, // PLAYER_QUEST_LOG+247 + PartyMember, // PLAYER_QUEST_LOG+248 + PartyMember, // PLAYER_QUEST_LOG+249 + PartyMember, // PLAYER_QUEST_LOG+250 + PartyMember, // PLAYER_QUEST_LOG+251 + PartyMember, // PLAYER_QUEST_LOG+252 + PartyMember, // PLAYER_QUEST_LOG+253 + PartyMember, // PLAYER_QUEST_LOG+254 + PartyMember, // PLAYER_QUEST_LOG+255 + PartyMember, // PLAYER_QUEST_LOG+256 + PartyMember, // PLAYER_QUEST_LOG+257 + PartyMember, // PLAYER_QUEST_LOG+258 + PartyMember, // PLAYER_QUEST_LOG+259 + PartyMember, // PLAYER_QUEST_LOG+260 + PartyMember, // PLAYER_QUEST_LOG+261 + PartyMember, // PLAYER_QUEST_LOG+262 + PartyMember, // PLAYER_QUEST_LOG+263 + PartyMember, // PLAYER_QUEST_LOG+264 + PartyMember, // PLAYER_QUEST_LOG+265 + PartyMember, // PLAYER_QUEST_LOG+266 + PartyMember, // PLAYER_QUEST_LOG+267 + PartyMember, // PLAYER_QUEST_LOG+268 + PartyMember, // PLAYER_QUEST_LOG+269 + PartyMember, // PLAYER_QUEST_LOG+270 + PartyMember, // PLAYER_QUEST_LOG+271 + PartyMember, // PLAYER_QUEST_LOG+272 + PartyMember, // PLAYER_QUEST_LOG+273 + PartyMember, // PLAYER_QUEST_LOG+274 + PartyMember, // PLAYER_QUEST_LOG+275 + PartyMember, // PLAYER_QUEST_LOG+276 + PartyMember, // PLAYER_QUEST_LOG+277 + PartyMember, // PLAYER_QUEST_LOG+278 + PartyMember, // PLAYER_QUEST_LOG+279 + PartyMember, // PLAYER_QUEST_LOG+280 + PartyMember, // PLAYER_QUEST_LOG+281 + PartyMember, // PLAYER_QUEST_LOG+282 + PartyMember, // PLAYER_QUEST_LOG+283 + PartyMember, // PLAYER_QUEST_LOG+284 + PartyMember, // PLAYER_QUEST_LOG+285 + PartyMember, // PLAYER_QUEST_LOG+286 + PartyMember, // PLAYER_QUEST_LOG+287 + PartyMember, // PLAYER_QUEST_LOG+288 + PartyMember, // PLAYER_QUEST_LOG+289 + PartyMember, // PLAYER_QUEST_LOG+290 + PartyMember, // PLAYER_QUEST_LOG+291 + PartyMember, // PLAYER_QUEST_LOG+292 + PartyMember, // PLAYER_QUEST_LOG+293 + PartyMember, // PLAYER_QUEST_LOG+294 + PartyMember, // PLAYER_QUEST_LOG+295 + PartyMember, // PLAYER_QUEST_LOG+296 + PartyMember, // PLAYER_QUEST_LOG+297 + PartyMember, // PLAYER_QUEST_LOG+298 + PartyMember, // PLAYER_QUEST_LOG+299 + PartyMember, // PLAYER_QUEST_LOG+300 + PartyMember, // PLAYER_QUEST_LOG+301 + PartyMember, // PLAYER_QUEST_LOG+302 + PartyMember, // PLAYER_QUEST_LOG+303 + PartyMember, // PLAYER_QUEST_LOG+304 + PartyMember, // PLAYER_QUEST_LOG+305 + PartyMember, // PLAYER_QUEST_LOG+306 + PartyMember, // PLAYER_QUEST_LOG+307 + PartyMember, // PLAYER_QUEST_LOG+308 + PartyMember, // PLAYER_QUEST_LOG+309 + PartyMember, // PLAYER_QUEST_LOG+310 + PartyMember, // PLAYER_QUEST_LOG+311 + PartyMember, // PLAYER_QUEST_LOG+312 + PartyMember, // PLAYER_QUEST_LOG+313 + PartyMember, // PLAYER_QUEST_LOG+314 + PartyMember, // PLAYER_QUEST_LOG+315 + PartyMember, // PLAYER_QUEST_LOG+316 + PartyMember, // PLAYER_QUEST_LOG+317 + PartyMember, // PLAYER_QUEST_LOG+318 + PartyMember, // PLAYER_QUEST_LOG+319 + PartyMember, // PLAYER_QUEST_LOG+320 + PartyMember, // PLAYER_QUEST_LOG+321 + PartyMember, // PLAYER_QUEST_LOG+322 + PartyMember, // PLAYER_QUEST_LOG+323 + PartyMember, // PLAYER_QUEST_LOG+324 + PartyMember, // PLAYER_QUEST_LOG+325 + PartyMember, // PLAYER_QUEST_LOG+326 + PartyMember, // PLAYER_QUEST_LOG+327 + PartyMember, // PLAYER_QUEST_LOG+328 + PartyMember, // PLAYER_QUEST_LOG+329 + PartyMember, // PLAYER_QUEST_LOG+330 + PartyMember, // PLAYER_QUEST_LOG+331 + PartyMember, // PLAYER_QUEST_LOG+332 + PartyMember, // PLAYER_QUEST_LOG+333 + PartyMember, // PLAYER_QUEST_LOG+334 + PartyMember, // PLAYER_QUEST_LOG+335 + PartyMember, // PLAYER_QUEST_LOG+336 + PartyMember, // PLAYER_QUEST_LOG+337 + PartyMember, // PLAYER_QUEST_LOG+338 + PartyMember, // PLAYER_QUEST_LOG+339 + PartyMember, // PLAYER_QUEST_LOG+340 + PartyMember, // PLAYER_QUEST_LOG+341 + PartyMember, // PLAYER_QUEST_LOG+342 + PartyMember, // PLAYER_QUEST_LOG+343 + PartyMember, // PLAYER_QUEST_LOG+344 + PartyMember, // PLAYER_QUEST_LOG+345 + PartyMember, // PLAYER_QUEST_LOG+346 + PartyMember, // PLAYER_QUEST_LOG+347 + PartyMember, // PLAYER_QUEST_LOG+348 + PartyMember, // PLAYER_QUEST_LOG+349 + PartyMember, // PLAYER_QUEST_LOG+350 + PartyMember, // PLAYER_QUEST_LOG+351 + PartyMember, // PLAYER_QUEST_LOG+352 + PartyMember, // PLAYER_QUEST_LOG+353 + PartyMember, // PLAYER_QUEST_LOG+354 + PartyMember, // PLAYER_QUEST_LOG+355 + PartyMember, // PLAYER_QUEST_LOG+356 + PartyMember, // PLAYER_QUEST_LOG+357 + PartyMember, // PLAYER_QUEST_LOG+358 + PartyMember, // PLAYER_QUEST_LOG+359 + PartyMember, // PLAYER_QUEST_LOG+360 + PartyMember, // PLAYER_QUEST_LOG+361 + PartyMember, // PLAYER_QUEST_LOG+362 + PartyMember, // PLAYER_QUEST_LOG+363 + PartyMember, // PLAYER_QUEST_LOG+364 + PartyMember, // PLAYER_QUEST_LOG+365 + PartyMember, // PLAYER_QUEST_LOG+366 + PartyMember, // PLAYER_QUEST_LOG+367 + PartyMember, // PLAYER_QUEST_LOG+368 + PartyMember, // PLAYER_QUEST_LOG+369 + PartyMember, // PLAYER_QUEST_LOG+370 + PartyMember, // PLAYER_QUEST_LOG+371 + PartyMember, // PLAYER_QUEST_LOG+372 + PartyMember, // PLAYER_QUEST_LOG+373 + PartyMember, // PLAYER_QUEST_LOG+374 + PartyMember, // PLAYER_QUEST_LOG+375 + PartyMember, // PLAYER_QUEST_LOG+376 + PartyMember, // PLAYER_QUEST_LOG+377 + PartyMember, // PLAYER_QUEST_LOG+378 + PartyMember, // PLAYER_QUEST_LOG+379 + PartyMember, // PLAYER_QUEST_LOG+380 + PartyMember, // PLAYER_QUEST_LOG+381 + PartyMember, // PLAYER_QUEST_LOG+382 + PartyMember, // PLAYER_QUEST_LOG+383 + PartyMember, // PLAYER_QUEST_LOG+384 + PartyMember, // PLAYER_QUEST_LOG+385 + PartyMember, // PLAYER_QUEST_LOG+386 + PartyMember, // PLAYER_QUEST_LOG+387 + PartyMember, // PLAYER_QUEST_LOG+388 + PartyMember, // PLAYER_QUEST_LOG+389 + PartyMember, // PLAYER_QUEST_LOG+390 + PartyMember, // PLAYER_QUEST_LOG+391 + PartyMember, // PLAYER_QUEST_LOG+392 + PartyMember, // PLAYER_QUEST_LOG+393 + PartyMember, // PLAYER_QUEST_LOG+394 + PartyMember, // PLAYER_QUEST_LOG+395 + PartyMember, // PLAYER_QUEST_LOG+396 + PartyMember, // PLAYER_QUEST_LOG+397 + PartyMember, // PLAYER_QUEST_LOG+398 + PartyMember, // PLAYER_QUEST_LOG+399 + PartyMember, // PLAYER_QUEST_LOG+400 + PartyMember, // PLAYER_QUEST_LOG+401 + PartyMember, // PLAYER_QUEST_LOG+402 + PartyMember, // PLAYER_QUEST_LOG+403 + PartyMember, // PLAYER_QUEST_LOG+404 + PartyMember, // PLAYER_QUEST_LOG+405 + PartyMember, // PLAYER_QUEST_LOG+406 + PartyMember, // PLAYER_QUEST_LOG+407 + PartyMember, // PLAYER_QUEST_LOG+408 + PartyMember, // PLAYER_QUEST_LOG+409 + PartyMember, // PLAYER_QUEST_LOG+410 + PartyMember, // PLAYER_QUEST_LOG+411 + PartyMember, // PLAYER_QUEST_LOG+412 + PartyMember, // PLAYER_QUEST_LOG+413 + PartyMember, // PLAYER_QUEST_LOG+414 + PartyMember, // PLAYER_QUEST_LOG+415 + PartyMember, // PLAYER_QUEST_LOG+416 + PartyMember, // PLAYER_QUEST_LOG+417 + PartyMember, // PLAYER_QUEST_LOG+418 + PartyMember, // PLAYER_QUEST_LOG+419 + PartyMember, // PLAYER_QUEST_LOG+420 + PartyMember, // PLAYER_QUEST_LOG+421 + PartyMember, // PLAYER_QUEST_LOG+422 + PartyMember, // PLAYER_QUEST_LOG+423 + PartyMember, // PLAYER_QUEST_LOG+424 + PartyMember, // PLAYER_QUEST_LOG+425 + PartyMember, // PLAYER_QUEST_LOG+426 + PartyMember, // PLAYER_QUEST_LOG+427 + PartyMember, // PLAYER_QUEST_LOG+428 + PartyMember, // PLAYER_QUEST_LOG+429 + PartyMember, // PLAYER_QUEST_LOG+430 + PartyMember, // PLAYER_QUEST_LOG+431 + PartyMember, // PLAYER_QUEST_LOG+432 + PartyMember, // PLAYER_QUEST_LOG+433 + PartyMember, // PLAYER_QUEST_LOG+434 + PartyMember, // PLAYER_QUEST_LOG+435 + PartyMember, // PLAYER_QUEST_LOG+436 + PartyMember, // PLAYER_QUEST_LOG+437 + PartyMember, // PLAYER_QUEST_LOG+438 + PartyMember, // PLAYER_QUEST_LOG+439 + PartyMember, // PLAYER_QUEST_LOG+440 + PartyMember, // PLAYER_QUEST_LOG+441 + PartyMember, // PLAYER_QUEST_LOG+442 + PartyMember, // PLAYER_QUEST_LOG+443 + PartyMember, // PLAYER_QUEST_LOG+444 + PartyMember, // PLAYER_QUEST_LOG+445 + PartyMember, // PLAYER_QUEST_LOG+446 + PartyMember, // PLAYER_QUEST_LOG+447 + PartyMember, // PLAYER_QUEST_LOG+448 + PartyMember, // PLAYER_QUEST_LOG+449 + PartyMember, // PLAYER_QUEST_LOG+450 + PartyMember, // PLAYER_QUEST_LOG+451 + PartyMember, // PLAYER_QUEST_LOG+452 + PartyMember, // PLAYER_QUEST_LOG+453 + PartyMember, // PLAYER_QUEST_LOG+454 + PartyMember, // PLAYER_QUEST_LOG+455 + PartyMember, // PLAYER_QUEST_LOG+456 + PartyMember, // PLAYER_QUEST_LOG+457 + PartyMember, // PLAYER_QUEST_LOG+458 + PartyMember, // PLAYER_QUEST_LOG+459 + PartyMember, // PLAYER_QUEST_LOG+460 + PartyMember, // PLAYER_QUEST_LOG+461 + PartyMember, // PLAYER_QUEST_LOG+462 + PartyMember, // PLAYER_QUEST_LOG+463 + PartyMember, // PLAYER_QUEST_LOG+464 + PartyMember, // PLAYER_QUEST_LOG+465 + PartyMember, // PLAYER_QUEST_LOG+466 + PartyMember, // PLAYER_QUEST_LOG+467 + PartyMember, // PLAYER_QUEST_LOG+468 + PartyMember, // PLAYER_QUEST_LOG+469 + PartyMember, // PLAYER_QUEST_LOG+470 + PartyMember, // PLAYER_QUEST_LOG+471 + PartyMember, // PLAYER_QUEST_LOG+472 + PartyMember, // PLAYER_QUEST_LOG+473 + PartyMember, // PLAYER_QUEST_LOG+474 + PartyMember, // PLAYER_QUEST_LOG+475 + PartyMember, // PLAYER_QUEST_LOG+476 + PartyMember, // PLAYER_QUEST_LOG+477 + PartyMember, // PLAYER_QUEST_LOG+478 + PartyMember, // PLAYER_QUEST_LOG+479 + PartyMember, // PLAYER_QUEST_LOG+480 + PartyMember, // PLAYER_QUEST_LOG+481 + PartyMember, // PLAYER_QUEST_LOG+482 + PartyMember, // PLAYER_QUEST_LOG+483 + PartyMember, // PLAYER_QUEST_LOG+484 + PartyMember, // PLAYER_QUEST_LOG+485 + PartyMember, // PLAYER_QUEST_LOG+486 + PartyMember, // PLAYER_QUEST_LOG+487 + PartyMember, // PLAYER_QUEST_LOG+488 + PartyMember, // PLAYER_QUEST_LOG+489 + PartyMember, // PLAYER_QUEST_LOG+490 + PartyMember, // PLAYER_QUEST_LOG+491 + PartyMember, // PLAYER_QUEST_LOG+492 + PartyMember, // PLAYER_QUEST_LOG+493 + PartyMember, // PLAYER_QUEST_LOG+494 + PartyMember, // PLAYER_QUEST_LOG+495 + PartyMember, // PLAYER_QUEST_LOG+496 + PartyMember, // PLAYER_QUEST_LOG+497 + PartyMember, // PLAYER_QUEST_LOG+498 + PartyMember, // PLAYER_QUEST_LOG+499 + PartyMember, // PLAYER_QUEST_LOG+500 + PartyMember, // PLAYER_QUEST_LOG+501 + PartyMember, // PLAYER_QUEST_LOG+502 + PartyMember, // PLAYER_QUEST_LOG+503 + PartyMember, // PLAYER_QUEST_LOG+504 + PartyMember, // PLAYER_QUEST_LOG+505 + PartyMember, // PLAYER_QUEST_LOG+506 + PartyMember, // PLAYER_QUEST_LOG+507 + PartyMember, // PLAYER_QUEST_LOG+508 + PartyMember, // PLAYER_QUEST_LOG+509 + PartyMember, // PLAYER_QUEST_LOG+510 + PartyMember, // PLAYER_QUEST_LOG+511 + PartyMember, // PLAYER_QUEST_LOG+512 + PartyMember, // PLAYER_QUEST_LOG+513 + PartyMember, // PLAYER_QUEST_LOG+514 + PartyMember, // PLAYER_QUEST_LOG+515 + PartyMember, // PLAYER_QUEST_LOG+516 + PartyMember, // PLAYER_QUEST_LOG+517 + PartyMember, // PLAYER_QUEST_LOG+518 + PartyMember, // PLAYER_QUEST_LOG+519 + PartyMember, // PLAYER_QUEST_LOG+520 + PartyMember, // PLAYER_QUEST_LOG+521 + PartyMember, // PLAYER_QUEST_LOG+522 + PartyMember, // PLAYER_QUEST_LOG+523 + PartyMember, // PLAYER_QUEST_LOG+524 + PartyMember, // PLAYER_QUEST_LOG+525 + PartyMember, // PLAYER_QUEST_LOG+526 + PartyMember, // PLAYER_QUEST_LOG+527 + PartyMember, // PLAYER_QUEST_LOG+528 + PartyMember, // PLAYER_QUEST_LOG+529 + PartyMember, // PLAYER_QUEST_LOG+530 + PartyMember, // PLAYER_QUEST_LOG+531 + PartyMember, // PLAYER_QUEST_LOG+532 + PartyMember, // PLAYER_QUEST_LOG+533 + PartyMember, // PLAYER_QUEST_LOG+534 + PartyMember, // PLAYER_QUEST_LOG+535 + PartyMember, // PLAYER_QUEST_LOG+536 + PartyMember, // PLAYER_QUEST_LOG+537 + PartyMember, // PLAYER_QUEST_LOG+538 + PartyMember, // PLAYER_QUEST_LOG+539 + PartyMember, // PLAYER_QUEST_LOG+540 + PartyMember, // PLAYER_QUEST_LOG+541 + PartyMember, // PLAYER_QUEST_LOG+542 + PartyMember, // PLAYER_QUEST_LOG+543 + PartyMember, // PLAYER_QUEST_LOG+544 + PartyMember, // PLAYER_QUEST_LOG+545 + PartyMember, // PLAYER_QUEST_LOG+546 + PartyMember, // PLAYER_QUEST_LOG+547 + PartyMember, // PLAYER_QUEST_LOG+548 + PartyMember, // PLAYER_QUEST_LOG+549 + PartyMember, // PLAYER_QUEST_LOG+550 + PartyMember, // PLAYER_QUEST_LOG+551 + PartyMember, // PLAYER_QUEST_LOG+552 + PartyMember, // PLAYER_QUEST_LOG+553 + PartyMember, // PLAYER_QUEST_LOG+554 + PartyMember, // PLAYER_QUEST_LOG+555 + PartyMember, // PLAYER_QUEST_LOG+556 + PartyMember, // PLAYER_QUEST_LOG+557 + PartyMember, // PLAYER_QUEST_LOG+558 + PartyMember, // PLAYER_QUEST_LOG+559 + PartyMember, // PLAYER_QUEST_LOG+560 + PartyMember, // PLAYER_QUEST_LOG+561 + PartyMember, // PLAYER_QUEST_LOG+562 + PartyMember, // PLAYER_QUEST_LOG+563 + PartyMember, // PLAYER_QUEST_LOG+564 + PartyMember, // PLAYER_QUEST_LOG+565 + PartyMember, // PLAYER_QUEST_LOG+566 + PartyMember, // PLAYER_QUEST_LOG+567 + PartyMember, // PLAYER_QUEST_LOG+568 + PartyMember, // PLAYER_QUEST_LOG+569 + PartyMember, // PLAYER_QUEST_LOG+570 + PartyMember, // PLAYER_QUEST_LOG+571 + PartyMember, // PLAYER_QUEST_LOG+572 + PartyMember, // PLAYER_QUEST_LOG+573 + PartyMember, // PLAYER_QUEST_LOG+574 + PartyMember, // PLAYER_QUEST_LOG+575 + PartyMember, // PLAYER_QUEST_LOG+576 + PartyMember, // PLAYER_QUEST_LOG+577 + PartyMember, // PLAYER_QUEST_LOG+578 + PartyMember, // PLAYER_QUEST_LOG+579 + PartyMember, // PLAYER_QUEST_LOG+580 + PartyMember, // PLAYER_QUEST_LOG+581 + PartyMember, // PLAYER_QUEST_LOG+582 + PartyMember, // PLAYER_QUEST_LOG+583 + PartyMember, // PLAYER_QUEST_LOG+584 + PartyMember, // PLAYER_QUEST_LOG+585 + PartyMember, // PLAYER_QUEST_LOG+586 + PartyMember, // PLAYER_QUEST_LOG+587 + PartyMember, // PLAYER_QUEST_LOG+588 + PartyMember, // PLAYER_QUEST_LOG+589 + PartyMember, // PLAYER_QUEST_LOG+590 + PartyMember, // PLAYER_QUEST_LOG+591 + PartyMember, // PLAYER_QUEST_LOG+592 + PartyMember, // PLAYER_QUEST_LOG+593 + PartyMember, // PLAYER_QUEST_LOG+594 + PartyMember, // PLAYER_QUEST_LOG+595 + PartyMember, // PLAYER_QUEST_LOG+596 + PartyMember, // PLAYER_QUEST_LOG+597 + PartyMember, // PLAYER_QUEST_LOG+598 + PartyMember, // PLAYER_QUEST_LOG+599 + PartyMember, // PLAYER_QUEST_LOG+600 + PartyMember, // PLAYER_QUEST_LOG+601 + PartyMember, // PLAYER_QUEST_LOG+602 + PartyMember, // PLAYER_QUEST_LOG+603 + PartyMember, // PLAYER_QUEST_LOG+604 + PartyMember, // PLAYER_QUEST_LOG+605 + PartyMember, // PLAYER_QUEST_LOG+606 + PartyMember, // PLAYER_QUEST_LOG+607 + PartyMember, // PLAYER_QUEST_LOG+608 + PartyMember, // PLAYER_QUEST_LOG+609 + PartyMember, // PLAYER_QUEST_LOG+610 + PartyMember, // PLAYER_QUEST_LOG+611 + PartyMember, // PLAYER_QUEST_LOG+612 + PartyMember, // PLAYER_QUEST_LOG+613 + PartyMember, // PLAYER_QUEST_LOG+614 + PartyMember, // PLAYER_QUEST_LOG+615 + PartyMember, // PLAYER_QUEST_LOG+616 + PartyMember, // PLAYER_QUEST_LOG+617 + PartyMember, // PLAYER_QUEST_LOG+618 + PartyMember, // PLAYER_QUEST_LOG+619 + PartyMember, // PLAYER_QUEST_LOG+620 + PartyMember, // PLAYER_QUEST_LOG+621 + PartyMember, // PLAYER_QUEST_LOG+622 + PartyMember, // PLAYER_QUEST_LOG+623 + PartyMember, // PLAYER_QUEST_LOG+624 + PartyMember, // PLAYER_QUEST_LOG+625 + PartyMember, // PLAYER_QUEST_LOG+626 + PartyMember, // PLAYER_QUEST_LOG+627 + PartyMember, // PLAYER_QUEST_LOG+628 + PartyMember, // PLAYER_QUEST_LOG+629 + PartyMember, // PLAYER_QUEST_LOG+630 + PartyMember, // PLAYER_QUEST_LOG+631 + PartyMember, // PLAYER_QUEST_LOG+632 + PartyMember, // PLAYER_QUEST_LOG+633 + PartyMember, // PLAYER_QUEST_LOG+634 + PartyMember, // PLAYER_QUEST_LOG+635 + PartyMember, // PLAYER_QUEST_LOG+636 + PartyMember, // PLAYER_QUEST_LOG+637 + PartyMember, // PLAYER_QUEST_LOG+638 + PartyMember, // PLAYER_QUEST_LOG+639 + PartyMember, // PLAYER_QUEST_LOG+640 + PartyMember, // PLAYER_QUEST_LOG+641 + PartyMember, // PLAYER_QUEST_LOG+642 + PartyMember, // PLAYER_QUEST_LOG+643 + PartyMember, // PLAYER_QUEST_LOG+644 + PartyMember, // PLAYER_QUEST_LOG+645 + PartyMember, // PLAYER_QUEST_LOG+646 + PartyMember, // PLAYER_QUEST_LOG+647 + PartyMember, // PLAYER_QUEST_LOG+648 + PartyMember, // PLAYER_QUEST_LOG+649 + PartyMember, // PLAYER_QUEST_LOG+650 + PartyMember, // PLAYER_QUEST_LOG+651 + PartyMember, // PLAYER_QUEST_LOG+652 + PartyMember, // PLAYER_QUEST_LOG+653 + PartyMember, // PLAYER_QUEST_LOG+654 + PartyMember, // PLAYER_QUEST_LOG+655 + PartyMember, // PLAYER_QUEST_LOG+656 + PartyMember, // PLAYER_QUEST_LOG+657 + PartyMember, // PLAYER_QUEST_LOG+658 + PartyMember, // PLAYER_QUEST_LOG+659 + PartyMember, // PLAYER_QUEST_LOG+660 + PartyMember, // PLAYER_QUEST_LOG+661 + PartyMember, // PLAYER_QUEST_LOG+662 + PartyMember, // PLAYER_QUEST_LOG+663 + PartyMember, // PLAYER_QUEST_LOG+664 + PartyMember, // PLAYER_QUEST_LOG+665 + PartyMember, // PLAYER_QUEST_LOG+666 + PartyMember, // PLAYER_QUEST_LOG+667 + PartyMember, // PLAYER_QUEST_LOG+668 + PartyMember, // PLAYER_QUEST_LOG+669 + PartyMember, // PLAYER_QUEST_LOG+670 + PartyMember, // PLAYER_QUEST_LOG+671 + PartyMember, // PLAYER_QUEST_LOG+672 + PartyMember, // PLAYER_QUEST_LOG+673 + PartyMember, // PLAYER_QUEST_LOG+674 + PartyMember, // PLAYER_QUEST_LOG+675 + PartyMember, // PLAYER_QUEST_LOG+676 + PartyMember, // PLAYER_QUEST_LOG+677 + PartyMember, // PLAYER_QUEST_LOG+678 + PartyMember, // PLAYER_QUEST_LOG+679 + PartyMember, // PLAYER_QUEST_LOG+680 + PartyMember, // PLAYER_QUEST_LOG+681 + PartyMember, // PLAYER_QUEST_LOG+682 + PartyMember, // PLAYER_QUEST_LOG+683 + PartyMember, // PLAYER_QUEST_LOG+684 + PartyMember, // PLAYER_QUEST_LOG+685 + PartyMember, // PLAYER_QUEST_LOG+686 + PartyMember, // PLAYER_QUEST_LOG+687 + PartyMember, // PLAYER_QUEST_LOG+688 + PartyMember, // PLAYER_QUEST_LOG+689 + PartyMember, // PLAYER_QUEST_LOG+690 + PartyMember, // PLAYER_QUEST_LOG+691 + PartyMember, // PLAYER_QUEST_LOG+692 + PartyMember, // PLAYER_QUEST_LOG+693 + PartyMember, // PLAYER_QUEST_LOG+694 + PartyMember, // PLAYER_QUEST_LOG+695 + PartyMember, // PLAYER_QUEST_LOG+696 + PartyMember, // PLAYER_QUEST_LOG+697 + PartyMember, // PLAYER_QUEST_LOG+698 + PartyMember, // PLAYER_QUEST_LOG+699 + PartyMember, // PLAYER_QUEST_LOG+700 + PartyMember, // PLAYER_QUEST_LOG+701 + PartyMember, // PLAYER_QUEST_LOG+702 + PartyMember, // PLAYER_QUEST_LOG+703 + PartyMember, // PLAYER_QUEST_LOG+704 + PartyMember, // PLAYER_QUEST_LOG+705 + PartyMember, // PLAYER_QUEST_LOG+706 + PartyMember, // PLAYER_QUEST_LOG+707 + PartyMember, // PLAYER_QUEST_LOG+708 + PartyMember, // PLAYER_QUEST_LOG+709 + PartyMember, // PLAYER_QUEST_LOG+710 + PartyMember, // PLAYER_QUEST_LOG+711 + PartyMember, // PLAYER_QUEST_LOG+712 + PartyMember, // PLAYER_QUEST_LOG+713 + PartyMember, // PLAYER_QUEST_LOG+714 + PartyMember, // PLAYER_QUEST_LOG+715 + PartyMember, // PLAYER_QUEST_LOG+716 + PartyMember, // PLAYER_QUEST_LOG+717 + PartyMember, // PLAYER_QUEST_LOG+718 + PartyMember, // PLAYER_QUEST_LOG+719 + PartyMember, // PLAYER_QUEST_LOG+720 + PartyMember, // PLAYER_QUEST_LOG+721 + PartyMember, // PLAYER_QUEST_LOG+722 + PartyMember, // PLAYER_QUEST_LOG+723 + PartyMember, // PLAYER_QUEST_LOG+724 + PartyMember, // PLAYER_QUEST_LOG+725 + PartyMember, // PLAYER_QUEST_LOG+726 + PartyMember, // PLAYER_QUEST_LOG+727 + PartyMember, // PLAYER_QUEST_LOG+728 + PartyMember, // PLAYER_QUEST_LOG+729 + PartyMember, // PLAYER_QUEST_LOG+730 + PartyMember, // PLAYER_QUEST_LOG+731 + PartyMember, // PLAYER_QUEST_LOG+732 + PartyMember, // PLAYER_QUEST_LOG+733 + PartyMember, // PLAYER_QUEST_LOG+734 + PartyMember, // PLAYER_QUEST_LOG+735 + PartyMember, // PLAYER_QUEST_LOG+736 + PartyMember, // PLAYER_QUEST_LOG+737 + PartyMember, // PLAYER_QUEST_LOG+738 + PartyMember, // PLAYER_QUEST_LOG+739 + PartyMember, // PLAYER_QUEST_LOG+740 + PartyMember, // PLAYER_QUEST_LOG+741 + PartyMember, // PLAYER_QUEST_LOG+742 + PartyMember, // PLAYER_QUEST_LOG+743 + PartyMember, // PLAYER_QUEST_LOG+744 + PartyMember, // PLAYER_QUEST_LOG+745 + PartyMember, // PLAYER_QUEST_LOG+746 + PartyMember, // PLAYER_QUEST_LOG+747 + PartyMember, // PLAYER_QUEST_LOG+748 + PartyMember, // PLAYER_QUEST_LOG+749 + PartyMember, // PLAYER_QUEST_LOG+750 + PartyMember, // PLAYER_QUEST_LOG+751 + PartyMember, // PLAYER_QUEST_LOG+752 + PartyMember, // PLAYER_QUEST_LOG+753 + PartyMember, // PLAYER_QUEST_LOG+754 + PartyMember, // PLAYER_QUEST_LOG+755 + PartyMember, // PLAYER_QUEST_LOG+756 + PartyMember, // PLAYER_QUEST_LOG+757 + PartyMember, // PLAYER_QUEST_LOG+758 + PartyMember, // PLAYER_QUEST_LOG+759 + PartyMember, // PLAYER_QUEST_LOG+760 + PartyMember, // PLAYER_QUEST_LOG+761 + PartyMember, // PLAYER_QUEST_LOG+762 + PartyMember, // PLAYER_QUEST_LOG+763 + PartyMember, // PLAYER_QUEST_LOG+764 + PartyMember, // PLAYER_QUEST_LOG+765 + PartyMember, // PLAYER_QUEST_LOG+766 + PartyMember, // PLAYER_QUEST_LOG+767 + PartyMember, // PLAYER_QUEST_LOG+768 + PartyMember, // PLAYER_QUEST_LOG+769 + PartyMember, // PLAYER_QUEST_LOG+770 + PartyMember, // PLAYER_QUEST_LOG+771 + PartyMember, // PLAYER_QUEST_LOG+772 + PartyMember, // PLAYER_QUEST_LOG+773 + PartyMember, // PLAYER_QUEST_LOG+774 + PartyMember, // PLAYER_QUEST_LOG+775 + PartyMember, // PLAYER_QUEST_LOG+776 + PartyMember, // PLAYER_QUEST_LOG+777 + PartyMember, // PLAYER_QUEST_LOG+778 + PartyMember, // PLAYER_QUEST_LOG+779 + PartyMember, // PLAYER_QUEST_LOG+780 + PartyMember, // PLAYER_QUEST_LOG+781 + PartyMember, // PLAYER_QUEST_LOG+782 + PartyMember, // PLAYER_QUEST_LOG+783 + PartyMember, // PLAYER_QUEST_LOG+784 + PartyMember, // PLAYER_QUEST_LOG+785 + PartyMember, // PLAYER_QUEST_LOG+786 + PartyMember, // PLAYER_QUEST_LOG+787 + PartyMember, // PLAYER_QUEST_LOG+788 + PartyMember, // PLAYER_QUEST_LOG+789 + PartyMember, // PLAYER_QUEST_LOG+790 + PartyMember, // PLAYER_QUEST_LOG+791 + PartyMember, // PLAYER_QUEST_LOG+792 + PartyMember, // PLAYER_QUEST_LOG+793 + PartyMember, // PLAYER_QUEST_LOG+794 + PartyMember, // PLAYER_QUEST_LOG+795 + PartyMember, // PLAYER_QUEST_LOG+796 + PartyMember, // PLAYER_QUEST_LOG+797 + PartyMember, // PLAYER_QUEST_LOG+798 + PartyMember, // PLAYER_QUEST_LOG+799 + Public, // PLAYER_VISIBLE_ITEM + Public, // PLAYER_VISIBLE_ITEM+1 + Public, // PLAYER_VISIBLE_ITEM+2 + Public, // PLAYER_VISIBLE_ITEM+3 + Public, // PLAYER_VISIBLE_ITEM+4 + Public, // PLAYER_VISIBLE_ITEM+5 + Public, // PLAYER_VISIBLE_ITEM+6 + Public, // PLAYER_VISIBLE_ITEM+7 + Public, // PLAYER_VISIBLE_ITEM+8 + Public, // PLAYER_VISIBLE_ITEM+9 + Public, // PLAYER_VISIBLE_ITEM+10 + Public, // PLAYER_VISIBLE_ITEM+11 + Public, // PLAYER_VISIBLE_ITEM+12 + Public, // PLAYER_VISIBLE_ITEM+13 + Public, // PLAYER_VISIBLE_ITEM+14 + Public, // PLAYER_VISIBLE_ITEM+15 + Public, // PLAYER_VISIBLE_ITEM+16 + Public, // PLAYER_VISIBLE_ITEM+17 + Public, // PLAYER_VISIBLE_ITEM+18 + Public, // PLAYER_VISIBLE_ITEM+19 + Public, // PLAYER_VISIBLE_ITEM+20 + Public, // PLAYER_VISIBLE_ITEM+21 + Public, // PLAYER_VISIBLE_ITEM+22 + Public, // PLAYER_VISIBLE_ITEM+23 + Public, // PLAYER_VISIBLE_ITEM+24 + Public, // PLAYER_VISIBLE_ITEM+25 + Public, // PLAYER_VISIBLE_ITEM+26 + Public, // PLAYER_VISIBLE_ITEM+27 + Public, // PLAYER_VISIBLE_ITEM+28 + Public, // PLAYER_VISIBLE_ITEM+29 + Public, // PLAYER_VISIBLE_ITEM+30 + Public, // PLAYER_VISIBLE_ITEM+31 + Public, // PLAYER_VISIBLE_ITEM+32 + Public, // PLAYER_VISIBLE_ITEM+33 + Public, // PLAYER_VISIBLE_ITEM+34 + Public, // PLAYER_VISIBLE_ITEM+35 + Public, // PLAYER_VISIBLE_ITEM+36 + Public, // PLAYER_VISIBLE_ITEM+37 + Public, // PLAYER_CHOSEN_TITLE + Public, // PLAYER_FAKE_INEBRIATION + Public, // PLAYER_FIELD_VIRTUAL_PLAYER_REALM + Public, // PLAYER_FIELD_CURRENT_SPEC_ID + Public, // PLAYER_FIELD_TAXI_MOUNT_ANIM_KIT_ID + Public, // PLAYER_FIELD_AVG_ITEM_LEVEL + Public, // PLAYER_FIELD_AVG_ITEM_LEVEL+1 + Public, // PLAYER_FIELD_AVG_ITEM_LEVEL+2 + Public, // PLAYER_FIELD_AVG_ITEM_LEVEL+3 + Public, // PLAYER_FIELD_CURRENT_BATTLE_PET_BREED_QUALITY + Public, // PLAYER_FIELD_PRESTIGE + Public, // PLAYER_FIELD_HONOR_LEVEL + Private, // PLAYER_FIELD_INV_SLOT_HEAD + Private, // PLAYER_FIELD_INV_SLOT_HEAD+1 + Private, // PLAYER_FIELD_INV_SLOT_HEAD+2 + Private, // PLAYER_FIELD_INV_SLOT_HEAD+3 + Private, // PLAYER_FIELD_INV_SLOT_HEAD+4 + Private, // PLAYER_FIELD_INV_SLOT_HEAD+5 + Private, // PLAYER_FIELD_INV_SLOT_HEAD+6 + Private, // PLAYER_FIELD_INV_SLOT_HEAD+7 + Private, // PLAYER_FIELD_INV_SLOT_HEAD+8 + Private, // PLAYER_FIELD_INV_SLOT_HEAD+9 + Private, // PLAYER_FIELD_INV_SLOT_HEAD+10 + Private, // PLAYER_FIELD_INV_SLOT_HEAD+11 + Private, // PLAYER_FIELD_INV_SLOT_HEAD+12 + Private, // PLAYER_FIELD_INV_SLOT_HEAD+13 + Private, // PLAYER_FIELD_INV_SLOT_HEAD+14 + Private, // PLAYER_FIELD_INV_SLOT_HEAD+15 + Private, // PLAYER_FIELD_INV_SLOT_HEAD+16 + Private, // PLAYER_FIELD_INV_SLOT_HEAD+17 + Private, // PLAYER_FIELD_INV_SLOT_HEAD+18 + Private, // PLAYER_FIELD_INV_SLOT_HEAD+19 + Private, // PLAYER_FIELD_INV_SLOT_HEAD+20 + Private, // PLAYER_FIELD_INV_SLOT_HEAD+21 + Private, // PLAYER_FIELD_INV_SLOT_HEAD+22 + Private, // PLAYER_FIELD_INV_SLOT_HEAD+23 + Private, // PLAYER_FIELD_INV_SLOT_HEAD+24 + Private, // PLAYER_FIELD_INV_SLOT_HEAD+25 + Private, // PLAYER_FIELD_INV_SLOT_HEAD+26 + Private, // PLAYER_FIELD_INV_SLOT_HEAD+27 + Private, // PLAYER_FIELD_INV_SLOT_HEAD+28 + Private, // PLAYER_FIELD_INV_SLOT_HEAD+29 + Private, // PLAYER_FIELD_INV_SLOT_HEAD+30 + Private, // PLAYER_FIELD_INV_SLOT_HEAD+31 + Private, // PLAYER_FIELD_INV_SLOT_HEAD+32 + Private, // PLAYER_FIELD_INV_SLOT_HEAD+33 + Private, // PLAYER_FIELD_INV_SLOT_HEAD+34 + Private, // PLAYER_FIELD_INV_SLOT_HEAD+35 + Private, // PLAYER_FIELD_INV_SLOT_HEAD+36 + Private, // PLAYER_FIELD_INV_SLOT_HEAD+37 + Private, // PLAYER_FIELD_INV_SLOT_HEAD+38 + Private, // PLAYER_FIELD_INV_SLOT_HEAD+39 + Private, // PLAYER_FIELD_INV_SLOT_HEAD+40 + Private, // PLAYER_FIELD_INV_SLOT_HEAD+41 + Private, // PLAYER_FIELD_INV_SLOT_HEAD+42 + Private, // PLAYER_FIELD_INV_SLOT_HEAD+43 + Private, // PLAYER_FIELD_INV_SLOT_HEAD+44 + Private, // PLAYER_FIELD_INV_SLOT_HEAD+45 + Private, // PLAYER_FIELD_INV_SLOT_HEAD+46 + Private, // PLAYER_FIELD_INV_SLOT_HEAD+47 + Private, // PLAYER_FIELD_INV_SLOT_HEAD+48 + Private, // PLAYER_FIELD_INV_SLOT_HEAD+49 + Private, // PLAYER_FIELD_INV_SLOT_HEAD+50 + Private, // PLAYER_FIELD_INV_SLOT_HEAD+51 + Private, // PLAYER_FIELD_INV_SLOT_HEAD+52 + Private, // PLAYER_FIELD_INV_SLOT_HEAD+53 + Private, // PLAYER_FIELD_INV_SLOT_HEAD+54 + Private, // PLAYER_FIELD_INV_SLOT_HEAD+55 + Private, // PLAYER_FIELD_INV_SLOT_HEAD+56 + Private, // PLAYER_FIELD_INV_SLOT_HEAD+57 + Private, // PLAYER_FIELD_INV_SLOT_HEAD+58 + Private, // PLAYER_FIELD_INV_SLOT_HEAD+59 + Private, // PLAYER_FIELD_INV_SLOT_HEAD+60 + Private, // PLAYER_FIELD_INV_SLOT_HEAD+61 + Private, // PLAYER_FIELD_INV_SLOT_HEAD+62 + Private, // PLAYER_FIELD_INV_SLOT_HEAD+63 + Private, // PLAYER_FIELD_INV_SLOT_HEAD+64 + Private, // PLAYER_FIELD_INV_SLOT_HEAD+65 + Private, // PLAYER_FIELD_INV_SLOT_HEAD+66 + Private, // PLAYER_FIELD_INV_SLOT_HEAD+67 + Private, // PLAYER_FIELD_INV_SLOT_HEAD+68 + Private, // PLAYER_FIELD_INV_SLOT_HEAD+69 + Private, // PLAYER_FIELD_INV_SLOT_HEAD+70 + Private, // PLAYER_FIELD_INV_SLOT_HEAD+71 + Private, // PLAYER_FIELD_INV_SLOT_HEAD+72 + Private, // PLAYER_FIELD_INV_SLOT_HEAD+73 + Private, // PLAYER_FIELD_INV_SLOT_HEAD+74 + Private, // PLAYER_FIELD_INV_SLOT_HEAD+75 + Private, // PLAYER_FIELD_INV_SLOT_HEAD+76 + Private, // PLAYER_FIELD_INV_SLOT_HEAD+77 + Private, // PLAYER_FIELD_INV_SLOT_HEAD+78 + Private, // PLAYER_FIELD_INV_SLOT_HEAD+79 + Private, // PLAYER_FIELD_INV_SLOT_HEAD+80 + Private, // PLAYER_FIELD_INV_SLOT_HEAD+81 + Private, // PLAYER_FIELD_INV_SLOT_HEAD+82 + Private, // PLAYER_FIELD_INV_SLOT_HEAD+83 + Private, // PLAYER_FIELD_INV_SLOT_HEAD+84 + Private, // PLAYER_FIELD_INV_SLOT_HEAD+85 + Private, // PLAYER_FIELD_INV_SLOT_HEAD+86 + Private, // PLAYER_FIELD_INV_SLOT_HEAD+87 + Private, // PLAYER_FIELD_INV_SLOT_HEAD+88 + Private, // PLAYER_FIELD_INV_SLOT_HEAD+89 + Private, // PLAYER_FIELD_INV_SLOT_HEAD+90 + Private, // PLAYER_FIELD_INV_SLOT_HEAD+91 + Private, // PLAYER_FIELD_INV_SLOT_HEAD+92 + Private, // PLAYER_FIELD_INV_SLOT_HEAD+93 + Private, // PLAYER_FIELD_INV_SLOT_HEAD+94 + Private, // PLAYER_FIELD_INV_SLOT_HEAD+95 + Private, // PLAYER_FIELD_INV_SLOT_HEAD+96 + Private, // PLAYER_FIELD_INV_SLOT_HEAD+97 + Private, // PLAYER_FIELD_INV_SLOT_HEAD+98 + Private, // PLAYER_FIELD_INV_SLOT_HEAD+99 + Private, // PLAYER_FIELD_INV_SLOT_HEAD+100 + Private, // PLAYER_FIELD_INV_SLOT_HEAD+101 + Private, // PLAYER_FIELD_INV_SLOT_HEAD+102 + Private, // PLAYER_FIELD_INV_SLOT_HEAD+103 + Private, // PLAYER_FIELD_INV_SLOT_HEAD+104 + Private, // PLAYER_FIELD_INV_SLOT_HEAD+105 + Private, // PLAYER_FIELD_INV_SLOT_HEAD+106 + Private, // PLAYER_FIELD_INV_SLOT_HEAD+107 + Private, // PLAYER_FIELD_INV_SLOT_HEAD+108 + Private, // PLAYER_FIELD_INV_SLOT_HEAD+109 + Private, // PLAYER_FIELD_INV_SLOT_HEAD+110 + Private, // PLAYER_FIELD_INV_SLOT_HEAD+111 + Private, // PLAYER_FIELD_INV_SLOT_HEAD+112 + Private, // PLAYER_FIELD_INV_SLOT_HEAD+113 + Private, // PLAYER_FIELD_INV_SLOT_HEAD+114 + Private, // PLAYER_FIELD_INV_SLOT_HEAD+115 + Private, // PLAYER_FIELD_INV_SLOT_HEAD+116 + Private, // PLAYER_FIELD_INV_SLOT_HEAD+117 + Private, // PLAYER_FIELD_INV_SLOT_HEAD+118 + Private, // PLAYER_FIELD_INV_SLOT_HEAD+119 + Private, // PLAYER_FIELD_INV_SLOT_HEAD+120 + Private, // PLAYER_FIELD_INV_SLOT_HEAD+121 + Private, // PLAYER_FIELD_INV_SLOT_HEAD+122 + Private, // PLAYER_FIELD_INV_SLOT_HEAD+123 + Private, // PLAYER_FIELD_INV_SLOT_HEAD+124 + Private, // PLAYER_FIELD_INV_SLOT_HEAD+125 + Private, // PLAYER_FIELD_INV_SLOT_HEAD+126 + Private, // PLAYER_FIELD_INV_SLOT_HEAD+127 + Private, // PLAYER_FIELD_INV_SLOT_HEAD+128 + Private, // PLAYER_FIELD_INV_SLOT_HEAD+129 + Private, // PLAYER_FIELD_INV_SLOT_HEAD+130 + Private, // PLAYER_FIELD_INV_SLOT_HEAD+131 + Private, // PLAYER_FIELD_INV_SLOT_HEAD+132 + Private, // PLAYER_FIELD_INV_SLOT_HEAD+133 + Private, // PLAYER_FIELD_INV_SLOT_HEAD+134 + Private, // PLAYER_FIELD_INV_SLOT_HEAD+135 + Private, // PLAYER_FIELD_INV_SLOT_HEAD+136 + Private, // PLAYER_FIELD_INV_SLOT_HEAD+137 + Private, // PLAYER_FIELD_INV_SLOT_HEAD+138 + Private, // PLAYER_FIELD_INV_SLOT_HEAD+139 + Private, // PLAYER_FIELD_INV_SLOT_HEAD+140 + Private, // PLAYER_FIELD_INV_SLOT_HEAD+141 + Private, // PLAYER_FIELD_INV_SLOT_HEAD+142 + Private, // PLAYER_FIELD_INV_SLOT_HEAD+143 + Private, // PLAYER_FIELD_INV_SLOT_HEAD+144 + Private, // PLAYER_FIELD_INV_SLOT_HEAD+145 + Private, // PLAYER_FIELD_INV_SLOT_HEAD+146 + Private, // PLAYER_FIELD_INV_SLOT_HEAD+147 + Private, // PLAYER_FIELD_INV_SLOT_HEAD+148 + Private, // PLAYER_FIELD_INV_SLOT_HEAD+149 + Private, // PLAYER_FIELD_INV_SLOT_HEAD+150 + Private, // PLAYER_FIELD_INV_SLOT_HEAD+151 + Private, // PLAYER_FIELD_INV_SLOT_HEAD+152 + Private, // PLAYER_FIELD_INV_SLOT_HEAD+153 + Private, // PLAYER_FIELD_INV_SLOT_HEAD+154 + Private, // PLAYER_FIELD_INV_SLOT_HEAD+155 + Private, // PLAYER_FIELD_INV_SLOT_HEAD+156 + Private, // PLAYER_FIELD_INV_SLOT_HEAD+157 + Private, // PLAYER_FIELD_INV_SLOT_HEAD+158 + Private, // PLAYER_FIELD_INV_SLOT_HEAD+159 + Private, // PLAYER_FIELD_INV_SLOT_HEAD+160 + Private, // PLAYER_FIELD_INV_SLOT_HEAD+161 + Private, // PLAYER_FIELD_INV_SLOT_HEAD+162 + Private, // PLAYER_FIELD_INV_SLOT_HEAD+163 + Private, // PLAYER_FIELD_INV_SLOT_HEAD+164 + Private, // PLAYER_FIELD_INV_SLOT_HEAD+165 + Private, // PLAYER_FIELD_INV_SLOT_HEAD+166 + Private, // PLAYER_FIELD_INV_SLOT_HEAD+167 + Private, // PLAYER_FIELD_INV_SLOT_HEAD+168 + Private, // PLAYER_FIELD_INV_SLOT_HEAD+169 + Private, // PLAYER_FIELD_INV_SLOT_HEAD+170 + Private, // PLAYER_FIELD_INV_SLOT_HEAD+171 + Private, // PLAYER_FIELD_INV_SLOT_HEAD+172 + Private, // PLAYER_FIELD_INV_SLOT_HEAD+173 + Private, // PLAYER_FIELD_INV_SLOT_HEAD+174 + Private, // PLAYER_FIELD_INV_SLOT_HEAD+175 + Private, // PLAYER_FIELD_INV_SLOT_HEAD+176 + Private, // PLAYER_FIELD_INV_SLOT_HEAD+177 + Private, // PLAYER_FIELD_INV_SLOT_HEAD+178 + Private, // PLAYER_FIELD_INV_SLOT_HEAD+179 + Private, // PLAYER_FIELD_INV_SLOT_HEAD+180 + Private, // PLAYER_FIELD_INV_SLOT_HEAD+181 + Private, // PLAYER_FIELD_INV_SLOT_HEAD+182 + Private, // PLAYER_FIELD_INV_SLOT_HEAD+183 + Private, // PLAYER_FIELD_INV_SLOT_HEAD+184 + Private, // PLAYER_FIELD_INV_SLOT_HEAD+185 + Private, // PLAYER_FIELD_INV_SLOT_HEAD+186 + Private, // PLAYER_FIELD_INV_SLOT_HEAD+187 + Private, // PLAYER_FIELD_INV_SLOT_HEAD+188 + Private, // PLAYER_FIELD_INV_SLOT_HEAD+189 + Private, // PLAYER_FIELD_INV_SLOT_HEAD+190 + Private, // PLAYER_FIELD_INV_SLOT_HEAD+191 + Private, // PLAYER_FIELD_INV_SLOT_HEAD+192 + Private, // PLAYER_FIELD_INV_SLOT_HEAD+193 + Private, // PLAYER_FIELD_INV_SLOT_HEAD+194 + Private, // PLAYER_FIELD_INV_SLOT_HEAD+195 + Private, // PLAYER_FIELD_INV_SLOT_HEAD+196 + Private, // PLAYER_FIELD_INV_SLOT_HEAD+197 + Private, // PLAYER_FIELD_INV_SLOT_HEAD+198 + Private, // PLAYER_FIELD_INV_SLOT_HEAD+199 + Private, // PLAYER_FIELD_INV_SLOT_HEAD+200 + Private, // PLAYER_FIELD_INV_SLOT_HEAD+201 + Private, // PLAYER_FIELD_INV_SLOT_HEAD+202 + Private, // PLAYER_FIELD_INV_SLOT_HEAD+203 + Private, // PLAYER_FIELD_INV_SLOT_HEAD+204 + Private, // PLAYER_FIELD_INV_SLOT_HEAD+205 + Private, // PLAYER_FIELD_INV_SLOT_HEAD+206 + Private, // PLAYER_FIELD_INV_SLOT_HEAD+207 + Private, // PLAYER_FIELD_INV_SLOT_HEAD+208 + Private, // PLAYER_FIELD_INV_SLOT_HEAD+209 + Private, // PLAYER_FIELD_INV_SLOT_HEAD+210 + Private, // PLAYER_FIELD_INV_SLOT_HEAD+211 + Private, // PLAYER_FIELD_INV_SLOT_HEAD+212 + Private, // PLAYER_FIELD_INV_SLOT_HEAD+213 + Private, // PLAYER_FIELD_INV_SLOT_HEAD+214 + Private, // PLAYER_FIELD_INV_SLOT_HEAD+215 + Private, // PLAYER_FIELD_INV_SLOT_HEAD+216 + Private, // PLAYER_FIELD_INV_SLOT_HEAD+217 + Private, // PLAYER_FIELD_INV_SLOT_HEAD+218 + Private, // PLAYER_FIELD_INV_SLOT_HEAD+219 + Private, // PLAYER_FIELD_INV_SLOT_HEAD+220 + Private, // PLAYER_FIELD_INV_SLOT_HEAD+221 + Private, // PLAYER_FIELD_INV_SLOT_HEAD+222 + Private, // PLAYER_FIELD_INV_SLOT_HEAD+223 + Private, // PLAYER_FIELD_INV_SLOT_HEAD+224 + Private, // PLAYER_FIELD_INV_SLOT_HEAD+225 + Private, // PLAYER_FIELD_INV_SLOT_HEAD+226 + Private, // PLAYER_FIELD_INV_SLOT_HEAD+227 + Private, // PLAYER_FIELD_INV_SLOT_HEAD+228 + Private, // PLAYER_FIELD_INV_SLOT_HEAD+229 + Private, // PLAYER_FIELD_INV_SLOT_HEAD+230 + Private, // PLAYER_FIELD_INV_SLOT_HEAD+231 + Private, // PLAYER_FIELD_INV_SLOT_HEAD+232 + Private, // PLAYER_FIELD_INV_SLOT_HEAD+233 + Private, // PLAYER_FIELD_INV_SLOT_HEAD+234 + Private, // PLAYER_FIELD_INV_SLOT_HEAD+235 + Private, // PLAYER_FIELD_INV_SLOT_HEAD+236 + Private, // PLAYER_FIELD_INV_SLOT_HEAD+237 + Private, // PLAYER_FIELD_INV_SLOT_HEAD+238 + Private, // PLAYER_FIELD_INV_SLOT_HEAD+239 + Private, // PLAYER_FIELD_INV_SLOT_HEAD+240 + Private, // PLAYER_FIELD_INV_SLOT_HEAD+241 + Private, // PLAYER_FIELD_INV_SLOT_HEAD+242 + Private, // PLAYER_FIELD_INV_SLOT_HEAD+243 + Private, // PLAYER_FIELD_INV_SLOT_HEAD+244 + Private, // PLAYER_FIELD_INV_SLOT_HEAD+245 + Private, // PLAYER_FIELD_INV_SLOT_HEAD+246 + Private, // PLAYER_FIELD_INV_SLOT_HEAD+247 + Private, // PLAYER_FIELD_INV_SLOT_HEAD+248 + Private, // PLAYER_FIELD_INV_SLOT_HEAD+249 + Private, // PLAYER_FIELD_INV_SLOT_HEAD+250 + Private, // PLAYER_FIELD_INV_SLOT_HEAD+251 + Private, // PLAYER_FIELD_INV_SLOT_HEAD+252 + Private, // PLAYER_FIELD_INV_SLOT_HEAD+253 + Private, // PLAYER_FIELD_INV_SLOT_HEAD+254 + Private, // PLAYER_FIELD_INV_SLOT_HEAD+255 + Private, // PLAYER_FIELD_INV_SLOT_HEAD+256 + Private, // PLAYER_FIELD_INV_SLOT_HEAD+257 + Private, // PLAYER_FIELD_INV_SLOT_HEAD+258 + Private, // PLAYER_FIELD_INV_SLOT_HEAD+259 + Private, // PLAYER_FIELD_INV_SLOT_HEAD+260 + Private, // PLAYER_FIELD_INV_SLOT_HEAD+261 + Private, // PLAYER_FIELD_INV_SLOT_HEAD+262 + Private, // PLAYER_FIELD_INV_SLOT_HEAD+263 + Private, // PLAYER_FIELD_INV_SLOT_HEAD+264 + Private, // PLAYER_FIELD_INV_SLOT_HEAD+265 + Private, // PLAYER_FIELD_INV_SLOT_HEAD+266 + Private, // PLAYER_FIELD_INV_SLOT_HEAD+267 + Private, // PLAYER_FIELD_INV_SLOT_HEAD+268 + Private, // PLAYER_FIELD_INV_SLOT_HEAD+269 + Private, // PLAYER_FIELD_INV_SLOT_HEAD+270 + Private, // PLAYER_FIELD_INV_SLOT_HEAD+271 + Private, // PLAYER_FIELD_INV_SLOT_HEAD+272 + Private, // PLAYER_FIELD_INV_SLOT_HEAD+273 + Private, // PLAYER_FIELD_INV_SLOT_HEAD+274 + Private, // PLAYER_FIELD_INV_SLOT_HEAD+275 + Private, // PLAYER_FIELD_INV_SLOT_HEAD+276 + Private, // PLAYER_FIELD_INV_SLOT_HEAD+277 + Private, // PLAYER_FIELD_INV_SLOT_HEAD+278 + Private, // PLAYER_FIELD_INV_SLOT_HEAD+279 + Private, // PLAYER_FIELD_INV_SLOT_HEAD+280 + Private, // PLAYER_FIELD_INV_SLOT_HEAD+281 + Private, // PLAYER_FIELD_INV_SLOT_HEAD+282 + Private, // PLAYER_FIELD_INV_SLOT_HEAD+283 + Private, // PLAYER_FIELD_INV_SLOT_HEAD+284 + Private, // PLAYER_FIELD_INV_SLOT_HEAD+285 + Private, // PLAYER_FIELD_INV_SLOT_HEAD+286 + Private, // PLAYER_FIELD_INV_SLOT_HEAD+287 + Private, // PLAYER_FIELD_INV_SLOT_HEAD+288 + Private, // PLAYER_FIELD_INV_SLOT_HEAD+289 + Private, // PLAYER_FIELD_INV_SLOT_HEAD+290 + Private, // PLAYER_FIELD_INV_SLOT_HEAD+291 + Private, // PLAYER_FIELD_INV_SLOT_HEAD+292 + Private, // PLAYER_FIELD_INV_SLOT_HEAD+293 + Private, // PLAYER_FIELD_INV_SLOT_HEAD+294 + Private, // PLAYER_FIELD_INV_SLOT_HEAD+295 + Private, // PLAYER_FIELD_INV_SLOT_HEAD+296 + Private, // PLAYER_FIELD_INV_SLOT_HEAD+297 + Private, // PLAYER_FIELD_INV_SLOT_HEAD+298 + Private, // PLAYER_FIELD_INV_SLOT_HEAD+299 + Private, // PLAYER_FIELD_INV_SLOT_HEAD+300 + Private, // PLAYER_FIELD_INV_SLOT_HEAD+301 + Private, // PLAYER_FIELD_INV_SLOT_HEAD+302 + Private, // PLAYER_FIELD_INV_SLOT_HEAD+303 + Private, // PLAYER_FIELD_INV_SLOT_HEAD+304 + Private, // PLAYER_FIELD_INV_SLOT_HEAD+305 + Private, // PLAYER_FIELD_INV_SLOT_HEAD+306 + Private, // PLAYER_FIELD_INV_SLOT_HEAD+307 + Private, // PLAYER_FIELD_INV_SLOT_HEAD+308 + Private, // PLAYER_FIELD_INV_SLOT_HEAD+309 + Private, // PLAYER_FIELD_INV_SLOT_HEAD+310 + Private, // PLAYER_FIELD_INV_SLOT_HEAD+311 + Private, // PLAYER_FIELD_INV_SLOT_HEAD+312 + Private, // PLAYER_FIELD_INV_SLOT_HEAD+313 + Private, // PLAYER_FIELD_INV_SLOT_HEAD+314 + Private, // PLAYER_FIELD_INV_SLOT_HEAD+315 + Private, // PLAYER_FIELD_INV_SLOT_HEAD+316 + Private, // PLAYER_FIELD_INV_SLOT_HEAD+317 + Private, // PLAYER_FIELD_INV_SLOT_HEAD+318 + Private, // PLAYER_FIELD_INV_SLOT_HEAD+319 + Private, // PLAYER_FIELD_INV_SLOT_HEAD+320 + Private, // PLAYER_FIELD_INV_SLOT_HEAD+321 + Private, // PLAYER_FIELD_INV_SLOT_HEAD+322 + Private, // PLAYER_FIELD_INV_SLOT_HEAD+323 + Private, // PLAYER_FIELD_INV_SLOT_HEAD+324 + Private, // PLAYER_FIELD_INV_SLOT_HEAD+325 + Private, // PLAYER_FIELD_INV_SLOT_HEAD+326 + Private, // PLAYER_FIELD_INV_SLOT_HEAD+327 + Private, // PLAYER_FIELD_INV_SLOT_HEAD+328 + Private, // PLAYER_FIELD_INV_SLOT_HEAD+329 + Private, // PLAYER_FIELD_INV_SLOT_HEAD+330 + Private, // PLAYER_FIELD_INV_SLOT_HEAD+331 + Private, // PLAYER_FIELD_INV_SLOT_HEAD+332 + Private, // PLAYER_FIELD_INV_SLOT_HEAD+333 + Private, // PLAYER_FIELD_INV_SLOT_HEAD+334 + Private, // PLAYER_FIELD_INV_SLOT_HEAD+335 + Private, // PLAYER_FIELD_INV_SLOT_HEAD+336 + Private, // PLAYER_FIELD_INV_SLOT_HEAD+337 + Private, // PLAYER_FIELD_INV_SLOT_HEAD+338 + Private, // PLAYER_FIELD_INV_SLOT_HEAD+339 + Private, // PLAYER_FIELD_INV_SLOT_HEAD+340 + Private, // PLAYER_FIELD_INV_SLOT_HEAD+341 + Private, // PLAYER_FIELD_INV_SLOT_HEAD+342 + Private, // PLAYER_FIELD_INV_SLOT_HEAD+343 + Private, // PLAYER_FIELD_INV_SLOT_HEAD+344 + Private, // PLAYER_FIELD_INV_SLOT_HEAD+345 + Private, // PLAYER_FIELD_INV_SLOT_HEAD+346 + Private, // PLAYER_FIELD_INV_SLOT_HEAD+347 + Private, // PLAYER_FIELD_INV_SLOT_HEAD+348 + Private, // PLAYER_FIELD_INV_SLOT_HEAD+349 + Private, // PLAYER_FIELD_INV_SLOT_HEAD+350 + Private, // PLAYER_FIELD_INV_SLOT_HEAD+351 + Private, // PLAYER_FIELD_INV_SLOT_HEAD+352 + Private, // PLAYER_FIELD_INV_SLOT_HEAD+353 + Private, // PLAYER_FIELD_INV_SLOT_HEAD+354 + Private, // PLAYER_FIELD_INV_SLOT_HEAD+355 + Private, // PLAYER_FIELD_INV_SLOT_HEAD+356 + Private, // PLAYER_FIELD_INV_SLOT_HEAD+357 + Private, // PLAYER_FIELD_INV_SLOT_HEAD+358 + Private, // PLAYER_FIELD_INV_SLOT_HEAD+359 + Private, // PLAYER_FIELD_INV_SLOT_HEAD+360 + Private, // PLAYER_FIELD_INV_SLOT_HEAD+361 + Private, // PLAYER_FIELD_INV_SLOT_HEAD+362 + Private, // PLAYER_FIELD_INV_SLOT_HEAD+363 + Private, // PLAYER_FIELD_INV_SLOT_HEAD+364 + Private, // PLAYER_FIELD_INV_SLOT_HEAD+365 + Private, // PLAYER_FIELD_INV_SLOT_HEAD+366 + Private, // PLAYER_FIELD_INV_SLOT_HEAD+367 + Private, // PLAYER_FIELD_INV_SLOT_HEAD+368 + Private, // PLAYER_FIELD_INV_SLOT_HEAD+369 + Private, // PLAYER_FIELD_INV_SLOT_HEAD+370 + Private, // PLAYER_FIELD_INV_SLOT_HEAD+371 + Private, // PLAYER_FIELD_INV_SLOT_HEAD+372 + Private, // PLAYER_FIELD_INV_SLOT_HEAD+373 + Private, // PLAYER_FIELD_INV_SLOT_HEAD+374 + Private, // PLAYER_FIELD_INV_SLOT_HEAD+375 + Private, // PLAYER_FIELD_INV_SLOT_HEAD+376 + Private, // PLAYER_FIELD_INV_SLOT_HEAD+377 + Private, // PLAYER_FIELD_INV_SLOT_HEAD+378 + Private, // PLAYER_FIELD_INV_SLOT_HEAD+379 + Private, // PLAYER_FIELD_INV_SLOT_HEAD+380 + Private, // PLAYER_FIELD_INV_SLOT_HEAD+381 + Private, // PLAYER_FIELD_INV_SLOT_HEAD+382 + Private, // PLAYER_FIELD_INV_SLOT_HEAD+383 + Private, // PLAYER_FIELD_INV_SLOT_HEAD+384 + Private, // PLAYER_FIELD_INV_SLOT_HEAD+385 + Private, // PLAYER_FIELD_INV_SLOT_HEAD+386 + Private, // PLAYER_FIELD_INV_SLOT_HEAD+387 + Private, // PLAYER_FIELD_INV_SLOT_HEAD+388 + Private, // PLAYER_FIELD_INV_SLOT_HEAD+389 + Private, // PLAYER_FIELD_INV_SLOT_HEAD+390 + Private, // PLAYER_FIELD_INV_SLOT_HEAD+391 + Private, // PLAYER_FIELD_INV_SLOT_HEAD+392 + Private, // PLAYER_FIELD_INV_SLOT_HEAD+393 + Private, // PLAYER_FIELD_INV_SLOT_HEAD+394 + Private, // PLAYER_FIELD_INV_SLOT_HEAD+395 + Private, // PLAYER_FIELD_INV_SLOT_HEAD+396 + Private, // PLAYER_FIELD_INV_SLOT_HEAD+397 + Private, // PLAYER_FIELD_INV_SLOT_HEAD+398 + Private, // PLAYER_FIELD_INV_SLOT_HEAD+399 + Private, // PLAYER_FIELD_INV_SLOT_HEAD+400 + Private, // PLAYER_FIELD_INV_SLOT_HEAD+401 + Private, // PLAYER_FIELD_INV_SLOT_HEAD+402 + Private, // PLAYER_FIELD_INV_SLOT_HEAD+403 + Private, // PLAYER_FIELD_INV_SLOT_HEAD+404 + Private, // PLAYER_FIELD_INV_SLOT_HEAD+405 + Private, // PLAYER_FIELD_INV_SLOT_HEAD+406 + Private, // PLAYER_FIELD_INV_SLOT_HEAD+407 + Private, // PLAYER_FIELD_INV_SLOT_HEAD+408 + Private, // PLAYER_FIELD_INV_SLOT_HEAD+409 + Private, // PLAYER_FIELD_INV_SLOT_HEAD+410 + Private, // PLAYER_FIELD_INV_SLOT_HEAD+411 + Private, // PLAYER_FIELD_INV_SLOT_HEAD+412 + Private, // PLAYER_FIELD_INV_SLOT_HEAD+413 + Private, // PLAYER_FIELD_INV_SLOT_HEAD+414 + Private, // PLAYER_FIELD_INV_SLOT_HEAD+415 + Private, // PLAYER_FIELD_INV_SLOT_HEAD+416 + Private, // PLAYER_FIELD_INV_SLOT_HEAD+417 + Private, // PLAYER_FIELD_INV_SLOT_HEAD+418 + Private, // PLAYER_FIELD_INV_SLOT_HEAD+419 + Private, // PLAYER_FIELD_INV_SLOT_HEAD+420 + Private, // PLAYER_FIELD_INV_SLOT_HEAD+421 + Private, // PLAYER_FIELD_INV_SLOT_HEAD+422 + Private, // PLAYER_FIELD_INV_SLOT_HEAD+423 + Private, // PLAYER_FIELD_INV_SLOT_HEAD+424 + Private, // PLAYER_FIELD_INV_SLOT_HEAD+425 + Private, // PLAYER_FIELD_INV_SLOT_HEAD+426 + Private, // PLAYER_FIELD_INV_SLOT_HEAD+427 + Private, // PLAYER_FIELD_INV_SLOT_HEAD+428 + Private, // PLAYER_FIELD_INV_SLOT_HEAD+429 + Private, // PLAYER_FIELD_INV_SLOT_HEAD+430 + Private, // PLAYER_FIELD_INV_SLOT_HEAD+431 + Private, // PLAYER_FIELD_INV_SLOT_HEAD+432 + Private, // PLAYER_FIELD_INV_SLOT_HEAD+433 + Private, // PLAYER_FIELD_INV_SLOT_HEAD+434 + Private, // PLAYER_FIELD_INV_SLOT_HEAD+435 + Private, // PLAYER_FIELD_INV_SLOT_HEAD+436 + Private, // PLAYER_FIELD_INV_SLOT_HEAD+437 + Private, // PLAYER_FIELD_INV_SLOT_HEAD+438 + Private, // PLAYER_FIELD_INV_SLOT_HEAD+439 + Private, // PLAYER_FIELD_INV_SLOT_HEAD+440 + Private, // PLAYER_FIELD_INV_SLOT_HEAD+441 + Private, // PLAYER_FIELD_INV_SLOT_HEAD+442 + Private, // PLAYER_FIELD_INV_SLOT_HEAD+443 + Private, // PLAYER_FIELD_INV_SLOT_HEAD+444 + Private, // PLAYER_FIELD_INV_SLOT_HEAD+445 + Private, // PLAYER_FIELD_INV_SLOT_HEAD+446 + Private, // PLAYER_FIELD_INV_SLOT_HEAD+447 + Private, // PLAYER_FIELD_INV_SLOT_HEAD+448 + Private, // PLAYER_FIELD_INV_SLOT_HEAD+449 + Private, // PLAYER_FIELD_INV_SLOT_HEAD+450 + Private, // PLAYER_FIELD_INV_SLOT_HEAD+451 + Private, // PLAYER_FIELD_INV_SLOT_HEAD+452 + Private, // PLAYER_FIELD_INV_SLOT_HEAD+453 + Private, // PLAYER_FIELD_INV_SLOT_HEAD+454 + Private, // PLAYER_FIELD_INV_SLOT_HEAD+455 + Private, // PLAYER_FIELD_INV_SLOT_HEAD+456 + Private, // PLAYER_FIELD_INV_SLOT_HEAD+457 + Private, // PLAYER_FIELD_INV_SLOT_HEAD+458 + Private, // PLAYER_FIELD_INV_SLOT_HEAD+459 + Private, // PLAYER_FIELD_INV_SLOT_HEAD+460 + Private, // PLAYER_FIELD_INV_SLOT_HEAD+461 + Private, // PLAYER_FIELD_INV_SLOT_HEAD+462 + Private, // PLAYER_FIELD_INV_SLOT_HEAD+463 + Private, // PLAYER_FIELD_INV_SLOT_HEAD+464 + Private, // PLAYER_FIELD_INV_SLOT_HEAD+465 + Private, // PLAYER_FIELD_INV_SLOT_HEAD+466 + Private, // PLAYER_FIELD_INV_SLOT_HEAD+467 + Private, // PLAYER_FIELD_INV_SLOT_HEAD+468 + Private, // PLAYER_FIELD_INV_SLOT_HEAD+469 + Private, // PLAYER_FIELD_INV_SLOT_HEAD+470 + Private, // PLAYER_FIELD_INV_SLOT_HEAD+471 + Private, // PLAYER_FIELD_INV_SLOT_HEAD+472 + Private, // PLAYER_FIELD_INV_SLOT_HEAD+473 + Private, // PLAYER_FIELD_INV_SLOT_HEAD+474 + Private, // PLAYER_FIELD_INV_SLOT_HEAD+475 + Private, // PLAYER_FIELD_INV_SLOT_HEAD+476 + Private, // PLAYER_FIELD_INV_SLOT_HEAD+477 + Private, // PLAYER_FIELD_INV_SLOT_HEAD+478 + Private, // PLAYER_FIELD_INV_SLOT_HEAD+479 + Private, // PLAYER_FIELD_INV_SLOT_HEAD+480 + Private, // PLAYER_FIELD_INV_SLOT_HEAD+481 + Private, // PLAYER_FIELD_INV_SLOT_HEAD+482 + Private, // PLAYER_FIELD_INV_SLOT_HEAD+483 + Private, // PLAYER_FIELD_INV_SLOT_HEAD+484 + Private, // PLAYER_FIELD_INV_SLOT_HEAD+485 + Private, // PLAYER_FIELD_INV_SLOT_HEAD+486 + Private, // PLAYER_FIELD_INV_SLOT_HEAD+487 + Private, // PLAYER_FIELD_INV_SLOT_HEAD+488 + Private, // PLAYER_FIELD_INV_SLOT_HEAD+489 + Private, // PLAYER_FIELD_INV_SLOT_HEAD+490 + Private, // PLAYER_FIELD_INV_SLOT_HEAD+491 + Private, // PLAYER_FIELD_INV_SLOT_HEAD+492 + Private, // PLAYER_FIELD_INV_SLOT_HEAD+493 + Private, // PLAYER_FIELD_INV_SLOT_HEAD+494 + Private, // PLAYER_FIELD_INV_SLOT_HEAD+495 + Private, // PLAYER_FIELD_INV_SLOT_HEAD+496 + Private, // PLAYER_FIELD_INV_SLOT_HEAD+497 + Private, // PLAYER_FIELD_INV_SLOT_HEAD+498 + Private, // PLAYER_FIELD_INV_SLOT_HEAD+499 + Private, // PLAYER_FIELD_INV_SLOT_HEAD+500 + Private, // PLAYER_FIELD_INV_SLOT_HEAD+501 + Private, // PLAYER_FIELD_INV_SLOT_HEAD+502 + Private, // PLAYER_FIELD_INV_SLOT_HEAD+503 + Private, // PLAYER_FIELD_INV_SLOT_HEAD+504 + Private, // PLAYER_FIELD_INV_SLOT_HEAD+505 + Private, // PLAYER_FIELD_INV_SLOT_HEAD+506 + Private, // PLAYER_FIELD_INV_SLOT_HEAD+507 + Private, // PLAYER_FIELD_INV_SLOT_HEAD+508 + Private, // PLAYER_FIELD_INV_SLOT_HEAD+509 + Private, // PLAYER_FIELD_INV_SLOT_HEAD+510 + Private, // PLAYER_FIELD_INV_SLOT_HEAD+511 + Private, // PLAYER_FIELD_INV_SLOT_HEAD+512 + Private, // PLAYER_FIELD_INV_SLOT_HEAD+513 + Private, // PLAYER_FIELD_INV_SLOT_HEAD+514 + Private, // PLAYER_FIELD_INV_SLOT_HEAD+515 + Private, // PLAYER_FIELD_INV_SLOT_HEAD+516 + Private, // PLAYER_FIELD_INV_SLOT_HEAD+517 + Private, // PLAYER_FIELD_INV_SLOT_HEAD+518 + Private, // PLAYER_FIELD_INV_SLOT_HEAD+519 + Private, // PLAYER_FIELD_INV_SLOT_HEAD+520 + Private, // PLAYER_FIELD_INV_SLOT_HEAD+521 + Private, // PLAYER_FIELD_INV_SLOT_HEAD+522 + Private, // PLAYER_FIELD_INV_SLOT_HEAD+523 + Private, // PLAYER_FIELD_INV_SLOT_HEAD+524 + Private, // PLAYER_FIELD_INV_SLOT_HEAD+525 + Private, // PLAYER_FIELD_INV_SLOT_HEAD+526 + Private, // PLAYER_FIELD_INV_SLOT_HEAD+527 + Private, // PLAYER_FIELD_INV_SLOT_HEAD+528 + Private, // PLAYER_FIELD_INV_SLOT_HEAD+529 + Private, // PLAYER_FIELD_INV_SLOT_HEAD+530 + Private, // PLAYER_FIELD_INV_SLOT_HEAD+531 + Private, // PLAYER_FIELD_INV_SLOT_HEAD+532 + Private, // PLAYER_FIELD_INV_SLOT_HEAD+533 + Private, // PLAYER_FIELD_INV_SLOT_HEAD+534 + Private, // PLAYER_FIELD_INV_SLOT_HEAD+535 + Private, // PLAYER_FIELD_INV_SLOT_HEAD+536 + Private, // PLAYER_FIELD_INV_SLOT_HEAD+537 + Private, // PLAYER_FIELD_INV_SLOT_HEAD+538 + Private, // PLAYER_FIELD_INV_SLOT_HEAD+539 + Private, // PLAYER_FIELD_INV_SLOT_HEAD+540 + Private, // PLAYER_FIELD_INV_SLOT_HEAD+541 + Private, // PLAYER_FIELD_INV_SLOT_HEAD+542 + Private, // PLAYER_FIELD_INV_SLOT_HEAD+543 + Private, // PLAYER_FIELD_INV_SLOT_HEAD+544 + Private, // PLAYER_FIELD_INV_SLOT_HEAD+545 + Private, // PLAYER_FIELD_INV_SLOT_HEAD+546 + Private, // PLAYER_FIELD_INV_SLOT_HEAD+547 + Private, // PLAYER_FIELD_INV_SLOT_HEAD+548 + Private, // PLAYER_FIELD_INV_SLOT_HEAD+549 + Private, // PLAYER_FIELD_INV_SLOT_HEAD+550 + Private, // PLAYER_FIELD_INV_SLOT_HEAD+551 + Private, // PLAYER_FIELD_INV_SLOT_HEAD+552 + Private, // PLAYER_FIELD_INV_SLOT_HEAD+553 + Private, // PLAYER_FIELD_INV_SLOT_HEAD+554 + Private, // PLAYER_FIELD_INV_SLOT_HEAD+555 + Private, // PLAYER_FIELD_INV_SLOT_HEAD+556 + Private, // PLAYER_FIELD_INV_SLOT_HEAD+557 + Private, // PLAYER_FIELD_INV_SLOT_HEAD+558 + Private, // PLAYER_FIELD_INV_SLOT_HEAD+559 + Private, // PLAYER_FIELD_INV_SLOT_HEAD+560 + Private, // PLAYER_FIELD_INV_SLOT_HEAD+561 + Private, // PLAYER_FIELD_INV_SLOT_HEAD+562 + Private, // PLAYER_FIELD_INV_SLOT_HEAD+563 + Private, // PLAYER_FIELD_INV_SLOT_HEAD+564 + Private, // PLAYER_FIELD_INV_SLOT_HEAD+565 + Private, // PLAYER_FIELD_INV_SLOT_HEAD+566 + Private, // PLAYER_FIELD_INV_SLOT_HEAD+567 + Private, // PLAYER_FIELD_INV_SLOT_HEAD+568 + Private, // PLAYER_FIELD_INV_SLOT_HEAD+569 + Private, // PLAYER_FIELD_INV_SLOT_HEAD+570 + Private, // PLAYER_FIELD_INV_SLOT_HEAD+571 + Private, // PLAYER_FIELD_INV_SLOT_HEAD+572 + Private, // PLAYER_FIELD_INV_SLOT_HEAD+573 + Private, // PLAYER_FIELD_INV_SLOT_HEAD+574 + Private, // PLAYER_FIELD_INV_SLOT_HEAD+575 + Private, // PLAYER_FIELD_INV_SLOT_HEAD+576 + Private, // PLAYER_FIELD_INV_SLOT_HEAD+577 + Private, // PLAYER_FIELD_INV_SLOT_HEAD+578 + Private, // PLAYER_FIELD_INV_SLOT_HEAD+579 + Private, // PLAYER_FIELD_INV_SLOT_HEAD+580 + Private, // PLAYER_FIELD_INV_SLOT_HEAD+581 + Private, // PLAYER_FIELD_INV_SLOT_HEAD+582 + Private, // PLAYER_FIELD_INV_SLOT_HEAD+583 + Private, // PLAYER_FIELD_INV_SLOT_HEAD+584 + Private, // PLAYER_FIELD_INV_SLOT_HEAD+585 + Private, // PLAYER_FIELD_INV_SLOT_HEAD+586 + Private, // PLAYER_FIELD_INV_SLOT_HEAD+587 + Private, // PLAYER_FIELD_INV_SLOT_HEAD+588 + Private, // PLAYER_FIELD_INV_SLOT_HEAD+589 + Private, // PLAYER_FIELD_INV_SLOT_HEAD+590 + Private, // PLAYER_FIELD_INV_SLOT_HEAD+591 + Private, // PLAYER_FIELD_INV_SLOT_HEAD+592 + Private, // PLAYER_FIELD_INV_SLOT_HEAD+593 + Private, // PLAYER_FIELD_INV_SLOT_HEAD+594 + Private, // PLAYER_FIELD_INV_SLOT_HEAD+595 + Private, // PLAYER_FIELD_INV_SLOT_HEAD+596 + Private, // PLAYER_FIELD_INV_SLOT_HEAD+597 + Private, // PLAYER_FIELD_INV_SLOT_HEAD+598 + Private, // PLAYER_FIELD_INV_SLOT_HEAD+599 + Private, // PLAYER_FIELD_INV_SLOT_HEAD+600 + Private, // PLAYER_FIELD_INV_SLOT_HEAD+601 + Private, // PLAYER_FIELD_INV_SLOT_HEAD+602 + Private, // PLAYER_FIELD_INV_SLOT_HEAD+603 + Private, // PLAYER_FIELD_INV_SLOT_HEAD+604 + Private, // PLAYER_FIELD_INV_SLOT_HEAD+605 + Private, // PLAYER_FIELD_INV_SLOT_HEAD+606 + Private, // PLAYER_FIELD_INV_SLOT_HEAD+607 + Private, // PLAYER_FIELD_INV_SLOT_HEAD+608 + Private, // PLAYER_FIELD_INV_SLOT_HEAD+609 + Private, // PLAYER_FIELD_INV_SLOT_HEAD+610 + Private, // PLAYER_FIELD_INV_SLOT_HEAD+611 + Private, // PLAYER_FIELD_INV_SLOT_HEAD+612 + Private, // PLAYER_FIELD_INV_SLOT_HEAD+613 + Private, // PLAYER_FIELD_INV_SLOT_HEAD+614 + Private, // PLAYER_FIELD_INV_SLOT_HEAD+615 + Private, // PLAYER_FIELD_INV_SLOT_HEAD+616 + Private, // PLAYER_FIELD_INV_SLOT_HEAD+617 + Private, // PLAYER_FIELD_INV_SLOT_HEAD+618 + Private, // PLAYER_FIELD_INV_SLOT_HEAD+619 + Private, // PLAYER_FIELD_INV_SLOT_HEAD+620 + Private, // PLAYER_FIELD_INV_SLOT_HEAD+621 + Private, // PLAYER_FIELD_INV_SLOT_HEAD+622 + Private, // PLAYER_FIELD_INV_SLOT_HEAD+623 + Private, // PLAYER_FIELD_INV_SLOT_HEAD+624 + Private, // PLAYER_FIELD_INV_SLOT_HEAD+625 + Private, // PLAYER_FIELD_INV_SLOT_HEAD+626 + Private, // PLAYER_FIELD_INV_SLOT_HEAD+627 + Private, // PLAYER_FIELD_INV_SLOT_HEAD+628 + Private, // PLAYER_FIELD_INV_SLOT_HEAD+629 + Private, // PLAYER_FIELD_INV_SLOT_HEAD+630 + Private, // PLAYER_FIELD_INV_SLOT_HEAD+631 + Private, // PLAYER_FIELD_INV_SLOT_HEAD+632 + Private, // PLAYER_FIELD_INV_SLOT_HEAD+633 + Private, // PLAYER_FIELD_INV_SLOT_HEAD+634 + Private, // PLAYER_FIELD_INV_SLOT_HEAD+635 + Private, // PLAYER_FIELD_INV_SLOT_HEAD+636 + Private, // PLAYER_FIELD_INV_SLOT_HEAD+637 + Private, // PLAYER_FIELD_INV_SLOT_HEAD+638 + Private, // PLAYER_FIELD_INV_SLOT_HEAD+639 + Private, // PLAYER_FIELD_INV_SLOT_HEAD+640 + Private, // PLAYER_FIELD_INV_SLOT_HEAD+641 + Private, // PLAYER_FIELD_INV_SLOT_HEAD+642 + Private, // PLAYER_FIELD_INV_SLOT_HEAD+643 + Private, // PLAYER_FIELD_INV_SLOT_HEAD+644 + Private, // PLAYER_FIELD_INV_SLOT_HEAD+645 + Private, // PLAYER_FIELD_INV_SLOT_HEAD+646 + Private, // PLAYER_FIELD_INV_SLOT_HEAD+647 + Private, // PLAYER_FIELD_INV_SLOT_HEAD+648 + Private, // PLAYER_FIELD_INV_SLOT_HEAD+649 + Private, // PLAYER_FIELD_INV_SLOT_HEAD+650 + Private, // PLAYER_FIELD_INV_SLOT_HEAD+651 + Private, // PLAYER_FIELD_INV_SLOT_HEAD+652 + Private, // PLAYER_FIELD_INV_SLOT_HEAD+653 + Private, // PLAYER_FIELD_INV_SLOT_HEAD+654 + Private, // PLAYER_FIELD_INV_SLOT_HEAD+655 + Private, // PLAYER_FIELD_INV_SLOT_HEAD+656 + Private, // PLAYER_FIELD_INV_SLOT_HEAD+657 + Private, // PLAYER_FIELD_INV_SLOT_HEAD+658 + Private, // PLAYER_FIELD_INV_SLOT_HEAD+659 + Private, // PLAYER_FIELD_INV_SLOT_HEAD+660 + Private, // PLAYER_FIELD_INV_SLOT_HEAD+661 + Private, // PLAYER_FIELD_INV_SLOT_HEAD+662 + Private, // PLAYER_FIELD_INV_SLOT_HEAD+663 + Private, // PLAYER_FIELD_INV_SLOT_HEAD+664 + Private, // PLAYER_FIELD_INV_SLOT_HEAD+665 + Private, // PLAYER_FIELD_INV_SLOT_HEAD+666 + Private, // PLAYER_FIELD_INV_SLOT_HEAD+667 + Private, // PLAYER_FIELD_INV_SLOT_HEAD+668 + Private, // PLAYER_FIELD_INV_SLOT_HEAD+669 + Private, // PLAYER_FIELD_INV_SLOT_HEAD+670 + Private, // PLAYER_FIELD_INV_SLOT_HEAD+671 + Private, // PLAYER_FIELD_INV_SLOT_HEAD+672 + Private, // PLAYER_FIELD_INV_SLOT_HEAD+673 + Private, // PLAYER_FIELD_INV_SLOT_HEAD+674 + Private, // PLAYER_FIELD_INV_SLOT_HEAD+675 + Private, // PLAYER_FIELD_INV_SLOT_HEAD+676 + Private, // PLAYER_FIELD_INV_SLOT_HEAD+677 + Private, // PLAYER_FIELD_INV_SLOT_HEAD+678 + Private, // PLAYER_FIELD_INV_SLOT_HEAD+679 + Private, // PLAYER_FIELD_INV_SLOT_HEAD+680 + Private, // PLAYER_FIELD_INV_SLOT_HEAD+681 + Private, // PLAYER_FIELD_INV_SLOT_HEAD+682 + Private, // PLAYER_FIELD_INV_SLOT_HEAD+683 + Private, // PLAYER_FIELD_INV_SLOT_HEAD+684 + Private, // PLAYER_FIELD_INV_SLOT_HEAD+685 + Private, // PLAYER_FIELD_INV_SLOT_HEAD+686 + Private, // PLAYER_FIELD_INV_SLOT_HEAD+687 + Private, // PLAYER_FIELD_INV_SLOT_HEAD+688 + Private, // PLAYER_FIELD_INV_SLOT_HEAD+689 + Private, // PLAYER_FIELD_INV_SLOT_HEAD+690 + Private, // PLAYER_FIELD_INV_SLOT_HEAD+691 + Private, // PLAYER_FIELD_INV_SLOT_HEAD+692 + Private, // PLAYER_FIELD_INV_SLOT_HEAD+693 + Private, // PLAYER_FIELD_INV_SLOT_HEAD+694 + Private, // PLAYER_FIELD_INV_SLOT_HEAD+695 + Private, // PLAYER_FIELD_INV_SLOT_HEAD+696 + Private, // PLAYER_FIELD_INV_SLOT_HEAD+697 + Private, // PLAYER_FIELD_INV_SLOT_HEAD+698 + Private, // PLAYER_FIELD_INV_SLOT_HEAD+699 + Private, // PLAYER_FIELD_INV_SLOT_HEAD+700 + Private, // PLAYER_FIELD_INV_SLOT_HEAD+701 + Private, // PLAYER_FIELD_INV_SLOT_HEAD+702 + Private, // PLAYER_FIELD_INV_SLOT_HEAD+703 + Private, // PLAYER_FIELD_INV_SLOT_HEAD+704 + Private, // PLAYER_FIELD_INV_SLOT_HEAD+705 + Private, // PLAYER_FIELD_INV_SLOT_HEAD+706 + Private, // PLAYER_FIELD_INV_SLOT_HEAD+707 + Private, // PLAYER_FIELD_INV_SLOT_HEAD+708 + Private, // PLAYER_FIELD_INV_SLOT_HEAD+709 + Private, // PLAYER_FIELD_INV_SLOT_HEAD+710 + Private, // PLAYER_FIELD_INV_SLOT_HEAD+711 + Private, // PLAYER_FIELD_INV_SLOT_HEAD+712 + Private, // PLAYER_FIELD_INV_SLOT_HEAD+713 + Private, // PLAYER_FIELD_INV_SLOT_HEAD+714 + Private, // PLAYER_FIELD_INV_SLOT_HEAD+715 + Private, // PLAYER_FIELD_INV_SLOT_HEAD+716 + Private, // PLAYER_FIELD_INV_SLOT_HEAD+717 + Private, // PLAYER_FIELD_INV_SLOT_HEAD+718 + Private, // PLAYER_FIELD_INV_SLOT_HEAD+719 + Private, // PLAYER_FIELD_INV_SLOT_HEAD+720 + Private, // PLAYER_FIELD_INV_SLOT_HEAD+721 + Private, // PLAYER_FIELD_INV_SLOT_HEAD+722 + Private, // PLAYER_FIELD_INV_SLOT_HEAD+723 + Private, // PLAYER_FIELD_INV_SLOT_HEAD+724 + Private, // PLAYER_FIELD_INV_SLOT_HEAD+725 + Private, // PLAYER_FIELD_INV_SLOT_HEAD+726 + Private, // PLAYER_FIELD_INV_SLOT_HEAD+727 + Private, // PLAYER_FIELD_INV_SLOT_HEAD+728 + Private, // PLAYER_FIELD_INV_SLOT_HEAD+729 + Private, // PLAYER_FIELD_INV_SLOT_HEAD+730 + Private, // PLAYER_FIELD_INV_SLOT_HEAD+731 + Private, // PLAYER_FIELD_INV_SLOT_HEAD+732 + Private, // PLAYER_FIELD_INV_SLOT_HEAD+733 + Private, // PLAYER_FIELD_INV_SLOT_HEAD+734 + Private, // PLAYER_FIELD_INV_SLOT_HEAD+735 + Private, // PLAYER_FIELD_INV_SLOT_HEAD+736 + Private, // PLAYER_FIELD_INV_SLOT_HEAD+737 + Private, // PLAYER_FIELD_INV_SLOT_HEAD+738 + Private, // PLAYER_FIELD_INV_SLOT_HEAD+739 + Private, // PLAYER_FIELD_INV_SLOT_HEAD+740 + Private, // PLAYER_FIELD_INV_SLOT_HEAD+741 + Private, // PLAYER_FIELD_INV_SLOT_HEAD+742 + Private, // PLAYER_FIELD_INV_SLOT_HEAD+743 + Private, // PLAYER_FIELD_INV_SLOT_HEAD+744 + Private, // PLAYER_FIELD_INV_SLOT_HEAD+745 + Private, // PLAYER_FIELD_INV_SLOT_HEAD+746 + Private, // PLAYER_FIELD_INV_SLOT_HEAD+747 + Private, // PLAYER_FARSIGHT + Private, // PLAYER_FARSIGHT+1 + Private, // PLAYER_FARSIGHT+2 + Private, // PLAYER_FARSIGHT+3 + Private, // PLAYER_FIELD_SUMMONED_BATTLE_PET_ID + Private, // PLAYER_FIELD_SUMMONED_BATTLE_PET_ID+1 + Private, // PLAYER_FIELD_SUMMONED_BATTLE_PET_ID+2 + Private, // PLAYER_FIELD_SUMMONED_BATTLE_PET_ID+3 + Private, // PLAYER__FIELD_KNOWN_TITLES + Private, // PLAYER__FIELD_KNOWN_TITLES+1 + Private, // PLAYER__FIELD_KNOWN_TITLES+2 + Private, // PLAYER__FIELD_KNOWN_TITLES+3 + Private, // PLAYER__FIELD_KNOWN_TITLES+4 + Private, // PLAYER__FIELD_KNOWN_TITLES+5 + Private, // PLAYER__FIELD_KNOWN_TITLES+6 + Private, // PLAYER__FIELD_KNOWN_TITLES+7 + Private, // PLAYER__FIELD_KNOWN_TITLES+8 + Private, // PLAYER__FIELD_KNOWN_TITLES+9 + Private, // PLAYER__FIELD_KNOWN_TITLES+10 + Private, // PLAYER__FIELD_KNOWN_TITLES+11 + Private, // PLAYER_FIELD_COINAGE + Private, // PLAYER_FIELD_COINAGE+1 + Private, // PLAYER_XP + Private, // PLAYER_NEXT_LEVEL_XP + Private, // PLAYER_SKILL_LINEID + Private, // PLAYER_SKILL_LINEID+1 + Private, // PLAYER_SKILL_LINEID+2 + Private, // PLAYER_SKILL_LINEID+3 + Private, // PLAYER_SKILL_LINEID+4 + Private, // PLAYER_SKILL_LINEID+5 + Private, // PLAYER_SKILL_LINEID+6 + Private, // PLAYER_SKILL_LINEID+7 + Private, // PLAYER_SKILL_LINEID+8 + Private, // PLAYER_SKILL_LINEID+9 + Private, // PLAYER_SKILL_LINEID+10 + Private, // PLAYER_SKILL_LINEID+11 + Private, // PLAYER_SKILL_LINEID+12 + Private, // PLAYER_SKILL_LINEID+13 + Private, // PLAYER_SKILL_LINEID+14 + Private, // PLAYER_SKILL_LINEID+15 + Private, // PLAYER_SKILL_LINEID+16 + Private, // PLAYER_SKILL_LINEID+17 + Private, // PLAYER_SKILL_LINEID+18 + Private, // PLAYER_SKILL_LINEID+19 + Private, // PLAYER_SKILL_LINEID+20 + Private, // PLAYER_SKILL_LINEID+21 + Private, // PLAYER_SKILL_LINEID+22 + Private, // PLAYER_SKILL_LINEID+23 + Private, // PLAYER_SKILL_LINEID+24 + Private, // PLAYER_SKILL_LINEID+25 + Private, // PLAYER_SKILL_LINEID+26 + Private, // PLAYER_SKILL_LINEID+27 + Private, // PLAYER_SKILL_LINEID+28 + Private, // PLAYER_SKILL_LINEID+29 + Private, // PLAYER_SKILL_LINEID+30 + Private, // PLAYER_SKILL_LINEID+31 + Private, // PLAYER_SKILL_LINEID+32 + Private, // PLAYER_SKILL_LINEID+33 + Private, // PLAYER_SKILL_LINEID+34 + Private, // PLAYER_SKILL_LINEID+35 + Private, // PLAYER_SKILL_LINEID+36 + Private, // PLAYER_SKILL_LINEID+37 + Private, // PLAYER_SKILL_LINEID+38 + Private, // PLAYER_SKILL_LINEID+39 + Private, // PLAYER_SKILL_LINEID+40 + Private, // PLAYER_SKILL_LINEID+41 + Private, // PLAYER_SKILL_LINEID+42 + Private, // PLAYER_SKILL_LINEID+43 + Private, // PLAYER_SKILL_LINEID+44 + Private, // PLAYER_SKILL_LINEID+45 + Private, // PLAYER_SKILL_LINEID+46 + Private, // PLAYER_SKILL_LINEID+47 + Private, // PLAYER_SKILL_LINEID+48 + Private, // PLAYER_SKILL_LINEID+49 + Private, // PLAYER_SKILL_LINEID+50 + Private, // PLAYER_SKILL_LINEID+51 + Private, // PLAYER_SKILL_LINEID+52 + Private, // PLAYER_SKILL_LINEID+53 + Private, // PLAYER_SKILL_LINEID+54 + Private, // PLAYER_SKILL_LINEID+55 + Private, // PLAYER_SKILL_LINEID+56 + Private, // PLAYER_SKILL_LINEID+57 + Private, // PLAYER_SKILL_LINEID+58 + Private, // PLAYER_SKILL_LINEID+59 + Private, // PLAYER_SKILL_LINEID+60 + Private, // PLAYER_SKILL_LINEID+61 + Private, // PLAYER_SKILL_LINEID+62 + Private, // PLAYER_SKILL_LINEID+63 + Private, // PLAYER_SKILL_LINEID+64 + Private, // PLAYER_SKILL_LINEID+65 + Private, // PLAYER_SKILL_LINEID+66 + Private, // PLAYER_SKILL_LINEID+67 + Private, // PLAYER_SKILL_LINEID+68 + Private, // PLAYER_SKILL_LINEID+69 + Private, // PLAYER_SKILL_LINEID+70 + Private, // PLAYER_SKILL_LINEID+71 + Private, // PLAYER_SKILL_LINEID+72 + Private, // PLAYER_SKILL_LINEID+73 + Private, // PLAYER_SKILL_LINEID+74 + Private, // PLAYER_SKILL_LINEID+75 + Private, // PLAYER_SKILL_LINEID+76 + Private, // PLAYER_SKILL_LINEID+77 + Private, // PLAYER_SKILL_LINEID+78 + Private, // PLAYER_SKILL_LINEID+79 + Private, // PLAYER_SKILL_LINEID+80 + Private, // PLAYER_SKILL_LINEID+81 + Private, // PLAYER_SKILL_LINEID+82 + Private, // PLAYER_SKILL_LINEID+83 + Private, // PLAYER_SKILL_LINEID+84 + Private, // PLAYER_SKILL_LINEID+85 + Private, // PLAYER_SKILL_LINEID+86 + Private, // PLAYER_SKILL_LINEID+87 + Private, // PLAYER_SKILL_LINEID+88 + Private, // PLAYER_SKILL_LINEID+89 + Private, // PLAYER_SKILL_LINEID+90 + Private, // PLAYER_SKILL_LINEID+91 + Private, // PLAYER_SKILL_LINEID+92 + Private, // PLAYER_SKILL_LINEID+93 + Private, // PLAYER_SKILL_LINEID+94 + Private, // PLAYER_SKILL_LINEID+95 + Private, // PLAYER_SKILL_LINEID+96 + Private, // PLAYER_SKILL_LINEID+97 + Private, // PLAYER_SKILL_LINEID+98 + Private, // PLAYER_SKILL_LINEID+99 + Private, // PLAYER_SKILL_LINEID+100 + Private, // PLAYER_SKILL_LINEID+101 + Private, // PLAYER_SKILL_LINEID+102 + Private, // PLAYER_SKILL_LINEID+103 + Private, // PLAYER_SKILL_LINEID+104 + Private, // PLAYER_SKILL_LINEID+105 + Private, // PLAYER_SKILL_LINEID+106 + Private, // PLAYER_SKILL_LINEID+107 + Private, // PLAYER_SKILL_LINEID+108 + Private, // PLAYER_SKILL_LINEID+109 + Private, // PLAYER_SKILL_LINEID+110 + Private, // PLAYER_SKILL_LINEID+111 + Private, // PLAYER_SKILL_LINEID+112 + Private, // PLAYER_SKILL_LINEID+113 + Private, // PLAYER_SKILL_LINEID+114 + Private, // PLAYER_SKILL_LINEID+115 + Private, // PLAYER_SKILL_LINEID+116 + Private, // PLAYER_SKILL_LINEID+117 + Private, // PLAYER_SKILL_LINEID+118 + Private, // PLAYER_SKILL_LINEID+119 + Private, // PLAYER_SKILL_LINEID+120 + Private, // PLAYER_SKILL_LINEID+121 + Private, // PLAYER_SKILL_LINEID+122 + Private, // PLAYER_SKILL_LINEID+123 + Private, // PLAYER_SKILL_LINEID+124 + Private, // PLAYER_SKILL_LINEID+125 + Private, // PLAYER_SKILL_LINEID+126 + Private, // PLAYER_SKILL_LINEID+127 + Private, // PLAYER_SKILL_LINEID+128 + Private, // PLAYER_SKILL_LINEID+129 + Private, // PLAYER_SKILL_LINEID+130 + Private, // PLAYER_SKILL_LINEID+131 + Private, // PLAYER_SKILL_LINEID+132 + Private, // PLAYER_SKILL_LINEID+133 + Private, // PLAYER_SKILL_LINEID+134 + Private, // PLAYER_SKILL_LINEID+135 + Private, // PLAYER_SKILL_LINEID+136 + Private, // PLAYER_SKILL_LINEID+137 + Private, // PLAYER_SKILL_LINEID+138 + Private, // PLAYER_SKILL_LINEID+139 + Private, // PLAYER_SKILL_LINEID+140 + Private, // PLAYER_SKILL_LINEID+141 + Private, // PLAYER_SKILL_LINEID+142 + Private, // PLAYER_SKILL_LINEID+143 + Private, // PLAYER_SKILL_LINEID+144 + Private, // PLAYER_SKILL_LINEID+145 + Private, // PLAYER_SKILL_LINEID+146 + Private, // PLAYER_SKILL_LINEID+147 + Private, // PLAYER_SKILL_LINEID+148 + Private, // PLAYER_SKILL_LINEID+149 + Private, // PLAYER_SKILL_LINEID+150 + Private, // PLAYER_SKILL_LINEID+151 + Private, // PLAYER_SKILL_LINEID+152 + Private, // PLAYER_SKILL_LINEID+153 + Private, // PLAYER_SKILL_LINEID+154 + Private, // PLAYER_SKILL_LINEID+155 + Private, // PLAYER_SKILL_LINEID+156 + Private, // PLAYER_SKILL_LINEID+157 + Private, // PLAYER_SKILL_LINEID+158 + Private, // PLAYER_SKILL_LINEID+159 + Private, // PLAYER_SKILL_LINEID+160 + Private, // PLAYER_SKILL_LINEID+161 + Private, // PLAYER_SKILL_LINEID+162 + Private, // PLAYER_SKILL_LINEID+163 + Private, // PLAYER_SKILL_LINEID+164 + Private, // PLAYER_SKILL_LINEID+165 + Private, // PLAYER_SKILL_LINEID+166 + Private, // PLAYER_SKILL_LINEID+167 + Private, // PLAYER_SKILL_LINEID+168 + Private, // PLAYER_SKILL_LINEID+169 + Private, // PLAYER_SKILL_LINEID+170 + Private, // PLAYER_SKILL_LINEID+171 + Private, // PLAYER_SKILL_LINEID+172 + Private, // PLAYER_SKILL_LINEID+173 + Private, // PLAYER_SKILL_LINEID+174 + Private, // PLAYER_SKILL_LINEID+175 + Private, // PLAYER_SKILL_LINEID+176 + Private, // PLAYER_SKILL_LINEID+177 + Private, // PLAYER_SKILL_LINEID+178 + Private, // PLAYER_SKILL_LINEID+179 + Private, // PLAYER_SKILL_LINEID+180 + Private, // PLAYER_SKILL_LINEID+181 + Private, // PLAYER_SKILL_LINEID+182 + Private, // PLAYER_SKILL_LINEID+183 + Private, // PLAYER_SKILL_LINEID+184 + Private, // PLAYER_SKILL_LINEID+185 + Private, // PLAYER_SKILL_LINEID+186 + Private, // PLAYER_SKILL_LINEID+187 + Private, // PLAYER_SKILL_LINEID+188 + Private, // PLAYER_SKILL_LINEID+189 + Private, // PLAYER_SKILL_LINEID+190 + Private, // PLAYER_SKILL_LINEID+191 + Private, // PLAYER_SKILL_LINEID+192 + Private, // PLAYER_SKILL_LINEID+193 + Private, // PLAYER_SKILL_LINEID+194 + Private, // PLAYER_SKILL_LINEID+195 + Private, // PLAYER_SKILL_LINEID+196 + Private, // PLAYER_SKILL_LINEID+197 + Private, // PLAYER_SKILL_LINEID+198 + Private, // PLAYER_SKILL_LINEID+199 + Private, // PLAYER_SKILL_LINEID+200 + Private, // PLAYER_SKILL_LINEID+201 + Private, // PLAYER_SKILL_LINEID+202 + Private, // PLAYER_SKILL_LINEID+203 + Private, // PLAYER_SKILL_LINEID+204 + Private, // PLAYER_SKILL_LINEID+205 + Private, // PLAYER_SKILL_LINEID+206 + Private, // PLAYER_SKILL_LINEID+207 + Private, // PLAYER_SKILL_LINEID+208 + Private, // PLAYER_SKILL_LINEID+209 + Private, // PLAYER_SKILL_LINEID+210 + Private, // PLAYER_SKILL_LINEID+211 + Private, // PLAYER_SKILL_LINEID+212 + Private, // PLAYER_SKILL_LINEID+213 + Private, // PLAYER_SKILL_LINEID+214 + Private, // PLAYER_SKILL_LINEID+215 + Private, // PLAYER_SKILL_LINEID+216 + Private, // PLAYER_SKILL_LINEID+217 + Private, // PLAYER_SKILL_LINEID+218 + Private, // PLAYER_SKILL_LINEID+219 + Private, // PLAYER_SKILL_LINEID+220 + Private, // PLAYER_SKILL_LINEID+221 + Private, // PLAYER_SKILL_LINEID+222 + Private, // PLAYER_SKILL_LINEID+223 + Private, // PLAYER_SKILL_LINEID+224 + Private, // PLAYER_SKILL_LINEID+225 + Private, // PLAYER_SKILL_LINEID+226 + Private, // PLAYER_SKILL_LINEID+227 + Private, // PLAYER_SKILL_LINEID+228 + Private, // PLAYER_SKILL_LINEID+229 + Private, // PLAYER_SKILL_LINEID+230 + Private, // PLAYER_SKILL_LINEID+231 + Private, // PLAYER_SKILL_LINEID+232 + Private, // PLAYER_SKILL_LINEID+233 + Private, // PLAYER_SKILL_LINEID+234 + Private, // PLAYER_SKILL_LINEID+235 + Private, // PLAYER_SKILL_LINEID+236 + Private, // PLAYER_SKILL_LINEID+237 + Private, // PLAYER_SKILL_LINEID+238 + Private, // PLAYER_SKILL_LINEID+239 + Private, // PLAYER_SKILL_LINEID+240 + Private, // PLAYER_SKILL_LINEID+241 + Private, // PLAYER_SKILL_LINEID+242 + Private, // PLAYER_SKILL_LINEID+243 + Private, // PLAYER_SKILL_LINEID+244 + Private, // PLAYER_SKILL_LINEID+245 + Private, // PLAYER_SKILL_LINEID+246 + Private, // PLAYER_SKILL_LINEID+247 + Private, // PLAYER_SKILL_LINEID+248 + Private, // PLAYER_SKILL_LINEID+249 + Private, // PLAYER_SKILL_LINEID+250 + Private, // PLAYER_SKILL_LINEID+251 + Private, // PLAYER_SKILL_LINEID+252 + Private, // PLAYER_SKILL_LINEID+253 + Private, // PLAYER_SKILL_LINEID+254 + Private, // PLAYER_SKILL_LINEID+255 + Private, // PLAYER_SKILL_LINEID+256 + Private, // PLAYER_SKILL_LINEID+257 + Private, // PLAYER_SKILL_LINEID+258 + Private, // PLAYER_SKILL_LINEID+259 + Private, // PLAYER_SKILL_LINEID+260 + Private, // PLAYER_SKILL_LINEID+261 + Private, // PLAYER_SKILL_LINEID+262 + Private, // PLAYER_SKILL_LINEID+263 + Private, // PLAYER_SKILL_LINEID+264 + Private, // PLAYER_SKILL_LINEID+265 + Private, // PLAYER_SKILL_LINEID+266 + Private, // PLAYER_SKILL_LINEID+267 + Private, // PLAYER_SKILL_LINEID+268 + Private, // PLAYER_SKILL_LINEID+269 + Private, // PLAYER_SKILL_LINEID+270 + Private, // PLAYER_SKILL_LINEID+271 + Private, // PLAYER_SKILL_LINEID+272 + Private, // PLAYER_SKILL_LINEID+273 + Private, // PLAYER_SKILL_LINEID+274 + Private, // PLAYER_SKILL_LINEID+275 + Private, // PLAYER_SKILL_LINEID+276 + Private, // PLAYER_SKILL_LINEID+277 + Private, // PLAYER_SKILL_LINEID+278 + Private, // PLAYER_SKILL_LINEID+279 + Private, // PLAYER_SKILL_LINEID+280 + Private, // PLAYER_SKILL_LINEID+281 + Private, // PLAYER_SKILL_LINEID+282 + Private, // PLAYER_SKILL_LINEID+283 + Private, // PLAYER_SKILL_LINEID+284 + Private, // PLAYER_SKILL_LINEID+285 + Private, // PLAYER_SKILL_LINEID+286 + Private, // PLAYER_SKILL_LINEID+287 + Private, // PLAYER_SKILL_LINEID+288 + Private, // PLAYER_SKILL_LINEID+289 + Private, // PLAYER_SKILL_LINEID+290 + Private, // PLAYER_SKILL_LINEID+291 + Private, // PLAYER_SKILL_LINEID+292 + Private, // PLAYER_SKILL_LINEID+293 + Private, // PLAYER_SKILL_LINEID+294 + Private, // PLAYER_SKILL_LINEID+295 + Private, // PLAYER_SKILL_LINEID+296 + Private, // PLAYER_SKILL_LINEID+297 + Private, // PLAYER_SKILL_LINEID+298 + Private, // PLAYER_SKILL_LINEID+299 + Private, // PLAYER_SKILL_LINEID+300 + Private, // PLAYER_SKILL_LINEID+301 + Private, // PLAYER_SKILL_LINEID+302 + Private, // PLAYER_SKILL_LINEID+303 + Private, // PLAYER_SKILL_LINEID+304 + Private, // PLAYER_SKILL_LINEID+305 + Private, // PLAYER_SKILL_LINEID+306 + Private, // PLAYER_SKILL_LINEID+307 + Private, // PLAYER_SKILL_LINEID+308 + Private, // PLAYER_SKILL_LINEID+309 + Private, // PLAYER_SKILL_LINEID+310 + Private, // PLAYER_SKILL_LINEID+311 + Private, // PLAYER_SKILL_LINEID+312 + Private, // PLAYER_SKILL_LINEID+313 + Private, // PLAYER_SKILL_LINEID+314 + Private, // PLAYER_SKILL_LINEID+315 + Private, // PLAYER_SKILL_LINEID+316 + Private, // PLAYER_SKILL_LINEID+317 + Private, // PLAYER_SKILL_LINEID+318 + Private, // PLAYER_SKILL_LINEID+319 + Private, // PLAYER_SKILL_LINEID+320 + Private, // PLAYER_SKILL_LINEID+321 + Private, // PLAYER_SKILL_LINEID+322 + Private, // PLAYER_SKILL_LINEID+323 + Private, // PLAYER_SKILL_LINEID+324 + Private, // PLAYER_SKILL_LINEID+325 + Private, // PLAYER_SKILL_LINEID+326 + Private, // PLAYER_SKILL_LINEID+327 + Private, // PLAYER_SKILL_LINEID+328 + Private, // PLAYER_SKILL_LINEID+329 + Private, // PLAYER_SKILL_LINEID+330 + Private, // PLAYER_SKILL_LINEID+331 + Private, // PLAYER_SKILL_LINEID+332 + Private, // PLAYER_SKILL_LINEID+333 + Private, // PLAYER_SKILL_LINEID+334 + Private, // PLAYER_SKILL_LINEID+335 + Private, // PLAYER_SKILL_LINEID+336 + Private, // PLAYER_SKILL_LINEID+337 + Private, // PLAYER_SKILL_LINEID+338 + Private, // PLAYER_SKILL_LINEID+339 + Private, // PLAYER_SKILL_LINEID+340 + Private, // PLAYER_SKILL_LINEID+341 + Private, // PLAYER_SKILL_LINEID+342 + Private, // PLAYER_SKILL_LINEID+343 + Private, // PLAYER_SKILL_LINEID+344 + Private, // PLAYER_SKILL_LINEID+345 + Private, // PLAYER_SKILL_LINEID+346 + Private, // PLAYER_SKILL_LINEID+347 + Private, // PLAYER_SKILL_LINEID+348 + Private, // PLAYER_SKILL_LINEID+349 + Private, // PLAYER_SKILL_LINEID+350 + Private, // PLAYER_SKILL_LINEID+351 + Private, // PLAYER_SKILL_LINEID+352 + Private, // PLAYER_SKILL_LINEID+353 + Private, // PLAYER_SKILL_LINEID+354 + Private, // PLAYER_SKILL_LINEID+355 + Private, // PLAYER_SKILL_LINEID+356 + Private, // PLAYER_SKILL_LINEID+357 + Private, // PLAYER_SKILL_LINEID+358 + Private, // PLAYER_SKILL_LINEID+359 + Private, // PLAYER_SKILL_LINEID+360 + Private, // PLAYER_SKILL_LINEID+361 + Private, // PLAYER_SKILL_LINEID+362 + Private, // PLAYER_SKILL_LINEID+363 + Private, // PLAYER_SKILL_LINEID+364 + Private, // PLAYER_SKILL_LINEID+365 + Private, // PLAYER_SKILL_LINEID+366 + Private, // PLAYER_SKILL_LINEID+367 + Private, // PLAYER_SKILL_LINEID+368 + Private, // PLAYER_SKILL_LINEID+369 + Private, // PLAYER_SKILL_LINEID+370 + Private, // PLAYER_SKILL_LINEID+371 + Private, // PLAYER_SKILL_LINEID+372 + Private, // PLAYER_SKILL_LINEID+373 + Private, // PLAYER_SKILL_LINEID+374 + Private, // PLAYER_SKILL_LINEID+375 + Private, // PLAYER_SKILL_LINEID+376 + Private, // PLAYER_SKILL_LINEID+377 + Private, // PLAYER_SKILL_LINEID+378 + Private, // PLAYER_SKILL_LINEID+379 + Private, // PLAYER_SKILL_LINEID+380 + Private, // PLAYER_SKILL_LINEID+381 + Private, // PLAYER_SKILL_LINEID+382 + Private, // PLAYER_SKILL_LINEID+383 + Private, // PLAYER_SKILL_LINEID+384 + Private, // PLAYER_SKILL_LINEID+385 + Private, // PLAYER_SKILL_LINEID+386 + Private, // PLAYER_SKILL_LINEID+387 + Private, // PLAYER_SKILL_LINEID+388 + Private, // PLAYER_SKILL_LINEID+389 + Private, // PLAYER_SKILL_LINEID+390 + Private, // PLAYER_SKILL_LINEID+391 + Private, // PLAYER_SKILL_LINEID+392 + Private, // PLAYER_SKILL_LINEID+393 + Private, // PLAYER_SKILL_LINEID+394 + Private, // PLAYER_SKILL_LINEID+395 + Private, // PLAYER_SKILL_LINEID+396 + Private, // PLAYER_SKILL_LINEID+397 + Private, // PLAYER_SKILL_LINEID+398 + Private, // PLAYER_SKILL_LINEID+399 + Private, // PLAYER_SKILL_LINEID+400 + Private, // PLAYER_SKILL_LINEID+401 + Private, // PLAYER_SKILL_LINEID+402 + Private, // PLAYER_SKILL_LINEID+403 + Private, // PLAYER_SKILL_LINEID+404 + Private, // PLAYER_SKILL_LINEID+405 + Private, // PLAYER_SKILL_LINEID+406 + Private, // PLAYER_SKILL_LINEID+407 + Private, // PLAYER_SKILL_LINEID+408 + Private, // PLAYER_SKILL_LINEID+409 + Private, // PLAYER_SKILL_LINEID+410 + Private, // PLAYER_SKILL_LINEID+411 + Private, // PLAYER_SKILL_LINEID+412 + Private, // PLAYER_SKILL_LINEID+413 + Private, // PLAYER_SKILL_LINEID+414 + Private, // PLAYER_SKILL_LINEID+415 + Private, // PLAYER_SKILL_LINEID+416 + Private, // PLAYER_SKILL_LINEID+417 + Private, // PLAYER_SKILL_LINEID+418 + Private, // PLAYER_SKILL_LINEID+419 + Private, // PLAYER_SKILL_LINEID+420 + Private, // PLAYER_SKILL_LINEID+421 + Private, // PLAYER_SKILL_LINEID+422 + Private, // PLAYER_SKILL_LINEID+423 + Private, // PLAYER_SKILL_LINEID+424 + Private, // PLAYER_SKILL_LINEID+425 + Private, // PLAYER_SKILL_LINEID+426 + Private, // PLAYER_SKILL_LINEID+427 + Private, // PLAYER_SKILL_LINEID+428 + Private, // PLAYER_SKILL_LINEID+429 + Private, // PLAYER_SKILL_LINEID+430 + Private, // PLAYER_SKILL_LINEID+431 + Private, // PLAYER_SKILL_LINEID+432 + Private, // PLAYER_SKILL_LINEID+433 + Private, // PLAYER_SKILL_LINEID+434 + Private, // PLAYER_SKILL_LINEID+435 + Private, // PLAYER_SKILL_LINEID+436 + Private, // PLAYER_SKILL_LINEID+437 + Private, // PLAYER_SKILL_LINEID+438 + Private, // PLAYER_SKILL_LINEID+439 + Private, // PLAYER_SKILL_LINEID+440 + Private, // PLAYER_SKILL_LINEID+441 + Private, // PLAYER_SKILL_LINEID+442 + Private, // PLAYER_SKILL_LINEID+443 + Private, // PLAYER_SKILL_LINEID+444 + Private, // PLAYER_SKILL_LINEID+445 + Private, // PLAYER_SKILL_LINEID+446 + Private, // PLAYER_SKILL_LINEID+447 + Private, // PLAYER_CHARACTER_POINTS + Private, // PLAYER_FIELD_MAX_TALENT_TIERS + Private, // PLAYER_TRACK_CREATURES + Private, // PLAYER_TRACK_RESOURCES + Private, // PLAYER_EXPERTISE + Private, // PLAYER_OFFHAND_EXPERTISE + Private, // PLAYER_FIELD_RANGED_EXPERTISE + Private, // PLAYER_FIELD_COMBAT_RATING_EXPERTISE + Private, // PLAYER_BLOCK_PERCENTAGE + Private, // PLAYER_DODGE_PERCENTAGE + Private, // PLAYER_DODGE_PERCENTAGE_FROM_ATTRIBUTE + Private, // PLAYER_PARRY_PERCENTAGE + Private, // PLAYER_PARRY_PERCENTAGE_FROM_ATTRIBUTE + Private, // PLAYER_CRIT_PERCENTAGE + Private, // PLAYER_RANGED_CRIT_PERCENTAGE + Private, // PLAYER_OFFHAND_CRIT_PERCENTAGE + Private, // PLAYER_SPELL_CRIT_PERCENTAGE1 + Private, // PLAYER_SHIELD_BLOCK + Private, // PLAYER_SHIELD_BLOCK_CRIT_PERCENTAGE + Private, // PLAYER_MASTERY + Private, // PLAYER_SPEED + Private, // PLAYER_LIFESTEAL + Private, // PLAYER_AVOIDANCE + Private, // PLAYER_STURDINESS + Private, // PLAYER_VERSATILITY + Private, // PLAYER_VERSATILITY_BONUS + Private, // PLAYER_FIELD_PVP_POWER_DAMAGE + Private, // PLAYER_FIELD_PVP_POWER_HEALING + Private, // PLAYER_EXPLORED_ZONES_1 + Private, // PLAYER_EXPLORED_ZONES_1+1 + Private, // PLAYER_EXPLORED_ZONES_1+2 + Private, // PLAYER_EXPLORED_ZONES_1+3 + Private, // PLAYER_EXPLORED_ZONES_1+4 + Private, // PLAYER_EXPLORED_ZONES_1+5 + Private, // PLAYER_EXPLORED_ZONES_1+6 + Private, // PLAYER_EXPLORED_ZONES_1+7 + Private, // PLAYER_EXPLORED_ZONES_1+8 + Private, // PLAYER_EXPLORED_ZONES_1+9 + Private, // PLAYER_EXPLORED_ZONES_1+10 + Private, // PLAYER_EXPLORED_ZONES_1+11 + Private, // PLAYER_EXPLORED_ZONES_1+12 + Private, // PLAYER_EXPLORED_ZONES_1+13 + Private, // PLAYER_EXPLORED_ZONES_1+14 + Private, // PLAYER_EXPLORED_ZONES_1+15 + Private, // PLAYER_EXPLORED_ZONES_1+16 + Private, // PLAYER_EXPLORED_ZONES_1+17 + Private, // PLAYER_EXPLORED_ZONES_1+18 + Private, // PLAYER_EXPLORED_ZONES_1+19 + Private, // PLAYER_EXPLORED_ZONES_1+20 + Private, // PLAYER_EXPLORED_ZONES_1+21 + Private, // PLAYER_EXPLORED_ZONES_1+22 + Private, // PLAYER_EXPLORED_ZONES_1+23 + Private, // PLAYER_EXPLORED_ZONES_1+24 + Private, // PLAYER_EXPLORED_ZONES_1+25 + Private, // PLAYER_EXPLORED_ZONES_1+26 + Private, // PLAYER_EXPLORED_ZONES_1+27 + Private, // PLAYER_EXPLORED_ZONES_1+28 + Private, // PLAYER_EXPLORED_ZONES_1+29 + Private, // PLAYER_EXPLORED_ZONES_1+30 + Private, // PLAYER_EXPLORED_ZONES_1+31 + Private, // PLAYER_EXPLORED_ZONES_1+32 + Private, // PLAYER_EXPLORED_ZONES_1+33 + Private, // PLAYER_EXPLORED_ZONES_1+34 + Private, // PLAYER_EXPLORED_ZONES_1+35 + Private, // PLAYER_EXPLORED_ZONES_1+36 + Private, // PLAYER_EXPLORED_ZONES_1+37 + Private, // PLAYER_EXPLORED_ZONES_1+38 + Private, // PLAYER_EXPLORED_ZONES_1+39 + Private, // PLAYER_EXPLORED_ZONES_1+40 + Private, // PLAYER_EXPLORED_ZONES_1+41 + Private, // PLAYER_EXPLORED_ZONES_1+42 + Private, // PLAYER_EXPLORED_ZONES_1+43 + Private, // PLAYER_EXPLORED_ZONES_1+44 + Private, // PLAYER_EXPLORED_ZONES_1+45 + Private, // PLAYER_EXPLORED_ZONES_1+46 + Private, // PLAYER_EXPLORED_ZONES_1+47 + Private, // PLAYER_EXPLORED_ZONES_1+48 + Private, // PLAYER_EXPLORED_ZONES_1+49 + Private, // PLAYER_EXPLORED_ZONES_1+50 + Private, // PLAYER_EXPLORED_ZONES_1+51 + Private, // PLAYER_EXPLORED_ZONES_1+52 + Private, // PLAYER_EXPLORED_ZONES_1+53 + Private, // PLAYER_EXPLORED_ZONES_1+54 + Private, // PLAYER_EXPLORED_ZONES_1+55 + Private, // PLAYER_EXPLORED_ZONES_1+56 + Private, // PLAYER_EXPLORED_ZONES_1+57 + Private, // PLAYER_EXPLORED_ZONES_1+58 + Private, // PLAYER_EXPLORED_ZONES_1+59 + Private, // PLAYER_EXPLORED_ZONES_1+60 + Private, // PLAYER_EXPLORED_ZONES_1+61 + Private, // PLAYER_EXPLORED_ZONES_1+62 + Private, // PLAYER_EXPLORED_ZONES_1+63 + Private, // PLAYER_EXPLORED_ZONES_1+64 + Private, // PLAYER_EXPLORED_ZONES_1+65 + Private, // PLAYER_EXPLORED_ZONES_1+66 + Private, // PLAYER_EXPLORED_ZONES_1+67 + Private, // PLAYER_EXPLORED_ZONES_1+68 + Private, // PLAYER_EXPLORED_ZONES_1+69 + Private, // PLAYER_EXPLORED_ZONES_1+70 + Private, // PLAYER_EXPLORED_ZONES_1+71 + Private, // PLAYER_EXPLORED_ZONES_1+72 + Private, // PLAYER_EXPLORED_ZONES_1+73 + Private, // PLAYER_EXPLORED_ZONES_1+74 + Private, // PLAYER_EXPLORED_ZONES_1+75 + Private, // PLAYER_EXPLORED_ZONES_1+76 + Private, // PLAYER_EXPLORED_ZONES_1+77 + Private, // PLAYER_EXPLORED_ZONES_1+78 + Private, // PLAYER_EXPLORED_ZONES_1+79 + Private, // PLAYER_EXPLORED_ZONES_1+80 + Private, // PLAYER_EXPLORED_ZONES_1+81 + Private, // PLAYER_EXPLORED_ZONES_1+82 + Private, // PLAYER_EXPLORED_ZONES_1+83 + Private, // PLAYER_EXPLORED_ZONES_1+84 + Private, // PLAYER_EXPLORED_ZONES_1+85 + Private, // PLAYER_EXPLORED_ZONES_1+86 + Private, // PLAYER_EXPLORED_ZONES_1+87 + Private, // PLAYER_EXPLORED_ZONES_1+88 + Private, // PLAYER_EXPLORED_ZONES_1+89 + Private, // PLAYER_EXPLORED_ZONES_1+90 + Private, // PLAYER_EXPLORED_ZONES_1+91 + Private, // PLAYER_EXPLORED_ZONES_1+92 + Private, // PLAYER_EXPLORED_ZONES_1+93 + Private, // PLAYER_EXPLORED_ZONES_1+94 + Private, // PLAYER_EXPLORED_ZONES_1+95 + Private, // PLAYER_EXPLORED_ZONES_1+96 + Private, // PLAYER_EXPLORED_ZONES_1+97 + Private, // PLAYER_EXPLORED_ZONES_1+98 + Private, // PLAYER_EXPLORED_ZONES_1+99 + Private, // PLAYER_EXPLORED_ZONES_1+100 + Private, // PLAYER_EXPLORED_ZONES_1+101 + Private, // PLAYER_EXPLORED_ZONES_1+102 + Private, // PLAYER_EXPLORED_ZONES_1+103 + Private, // PLAYER_EXPLORED_ZONES_1+104 + Private, // PLAYER_EXPLORED_ZONES_1+105 + Private, // PLAYER_EXPLORED_ZONES_1+106 + Private, // PLAYER_EXPLORED_ZONES_1+107 + Private, // PLAYER_EXPLORED_ZONES_1+108 + Private, // PLAYER_EXPLORED_ZONES_1+109 + Private, // PLAYER_EXPLORED_ZONES_1+110 + Private, // PLAYER_EXPLORED_ZONES_1+111 + Private, // PLAYER_EXPLORED_ZONES_1+112 + Private, // PLAYER_EXPLORED_ZONES_1+113 + Private, // PLAYER_EXPLORED_ZONES_1+114 + Private, // PLAYER_EXPLORED_ZONES_1+115 + Private, // PLAYER_EXPLORED_ZONES_1+116 + Private, // PLAYER_EXPLORED_ZONES_1+117 + Private, // PLAYER_EXPLORED_ZONES_1+118 + Private, // PLAYER_EXPLORED_ZONES_1+119 + Private, // PLAYER_EXPLORED_ZONES_1+120 + Private, // PLAYER_EXPLORED_ZONES_1+121 + Private, // PLAYER_EXPLORED_ZONES_1+122 + Private, // PLAYER_EXPLORED_ZONES_1+123 + Private, // PLAYER_EXPLORED_ZONES_1+124 + Private, // PLAYER_EXPLORED_ZONES_1+125 + Private, // PLAYER_EXPLORED_ZONES_1+126 + Private, // PLAYER_EXPLORED_ZONES_1+127 + Private, // PLAYER_EXPLORED_ZONES_1+128 + Private, // PLAYER_EXPLORED_ZONES_1+129 + Private, // PLAYER_EXPLORED_ZONES_1+130 + Private, // PLAYER_EXPLORED_ZONES_1+131 + Private, // PLAYER_EXPLORED_ZONES_1+132 + Private, // PLAYER_EXPLORED_ZONES_1+133 + Private, // PLAYER_EXPLORED_ZONES_1+134 + Private, // PLAYER_EXPLORED_ZONES_1+135 + Private, // PLAYER_EXPLORED_ZONES_1+136 + Private, // PLAYER_EXPLORED_ZONES_1+137 + Private, // PLAYER_EXPLORED_ZONES_1+138 + Private, // PLAYER_EXPLORED_ZONES_1+139 + Private, // PLAYER_EXPLORED_ZONES_1+140 + Private, // PLAYER_EXPLORED_ZONES_1+141 + Private, // PLAYER_EXPLORED_ZONES_1+142 + Private, // PLAYER_EXPLORED_ZONES_1+143 + Private, // PLAYER_EXPLORED_ZONES_1+144 + Private, // PLAYER_EXPLORED_ZONES_1+145 + Private, // PLAYER_EXPLORED_ZONES_1+146 + Private, // PLAYER_EXPLORED_ZONES_1+147 + Private, // PLAYER_EXPLORED_ZONES_1+148 + Private, // PLAYER_EXPLORED_ZONES_1+149 + Private, // PLAYER_EXPLORED_ZONES_1+150 + Private, // PLAYER_EXPLORED_ZONES_1+151 + Private, // PLAYER_EXPLORED_ZONES_1+152 + Private, // PLAYER_EXPLORED_ZONES_1+153 + Private, // PLAYER_EXPLORED_ZONES_1+154 + Private, // PLAYER_EXPLORED_ZONES_1+155 + Private, // PLAYER_EXPLORED_ZONES_1+156 + Private, // PLAYER_EXPLORED_ZONES_1+157 + Private, // PLAYER_EXPLORED_ZONES_1+158 + Private, // PLAYER_EXPLORED_ZONES_1+159 + Private, // PLAYER_EXPLORED_ZONES_1+160 + Private, // PLAYER_EXPLORED_ZONES_1+161 + Private, // PLAYER_EXPLORED_ZONES_1+162 + Private, // PLAYER_EXPLORED_ZONES_1+163 + Private, // PLAYER_EXPLORED_ZONES_1+164 + Private, // PLAYER_EXPLORED_ZONES_1+165 + Private, // PLAYER_EXPLORED_ZONES_1+166 + Private, // PLAYER_EXPLORED_ZONES_1+167 + Private, // PLAYER_EXPLORED_ZONES_1+168 + Private, // PLAYER_EXPLORED_ZONES_1+169 + Private, // PLAYER_EXPLORED_ZONES_1+170 + Private, // PLAYER_EXPLORED_ZONES_1+171 + Private, // PLAYER_EXPLORED_ZONES_1+172 + Private, // PLAYER_EXPLORED_ZONES_1+173 + Private, // PLAYER_EXPLORED_ZONES_1+174 + Private, // PLAYER_EXPLORED_ZONES_1+175 + Private, // PLAYER_EXPLORED_ZONES_1+176 + Private, // PLAYER_EXPLORED_ZONES_1+177 + Private, // PLAYER_EXPLORED_ZONES_1+178 + Private, // PLAYER_EXPLORED_ZONES_1+179 + Private, // PLAYER_EXPLORED_ZONES_1+180 + Private, // PLAYER_EXPLORED_ZONES_1+181 + Private, // PLAYER_EXPLORED_ZONES_1+182 + Private, // PLAYER_EXPLORED_ZONES_1+183 + Private, // PLAYER_EXPLORED_ZONES_1+184 + Private, // PLAYER_EXPLORED_ZONES_1+185 + Private, // PLAYER_EXPLORED_ZONES_1+186 + Private, // PLAYER_EXPLORED_ZONES_1+187 + Private, // PLAYER_EXPLORED_ZONES_1+188 + Private, // PLAYER_EXPLORED_ZONES_1+189 + Private, // PLAYER_EXPLORED_ZONES_1+190 + Private, // PLAYER_EXPLORED_ZONES_1+191 + Private, // PLAYER_EXPLORED_ZONES_1+192 + Private, // PLAYER_EXPLORED_ZONES_1+193 + Private, // PLAYER_EXPLORED_ZONES_1+194 + Private, // PLAYER_EXPLORED_ZONES_1+195 + Private, // PLAYER_EXPLORED_ZONES_1+196 + Private, // PLAYER_EXPLORED_ZONES_1+197 + Private, // PLAYER_EXPLORED_ZONES_1+198 + Private, // PLAYER_EXPLORED_ZONES_1+199 + Private, // PLAYER_EXPLORED_ZONES_1+200 + Private, // PLAYER_EXPLORED_ZONES_1+201 + Private, // PLAYER_EXPLORED_ZONES_1+202 + Private, // PLAYER_EXPLORED_ZONES_1+203 + Private, // PLAYER_EXPLORED_ZONES_1+204 + Private, // PLAYER_EXPLORED_ZONES_1+205 + Private, // PLAYER_EXPLORED_ZONES_1+206 + Private, // PLAYER_EXPLORED_ZONES_1+207 + Private, // PLAYER_EXPLORED_ZONES_1+208 + Private, // PLAYER_EXPLORED_ZONES_1+209 + Private, // PLAYER_EXPLORED_ZONES_1+210 + Private, // PLAYER_EXPLORED_ZONES_1+211 + Private, // PLAYER_EXPLORED_ZONES_1+212 + Private, // PLAYER_EXPLORED_ZONES_1+213 + Private, // PLAYER_EXPLORED_ZONES_1+214 + Private, // PLAYER_EXPLORED_ZONES_1+215 + Private, // PLAYER_EXPLORED_ZONES_1+216 + Private, // PLAYER_EXPLORED_ZONES_1+217 + Private, // PLAYER_EXPLORED_ZONES_1+218 + Private, // PLAYER_EXPLORED_ZONES_1+219 + Private, // PLAYER_EXPLORED_ZONES_1+220 + Private, // PLAYER_EXPLORED_ZONES_1+221 + Private, // PLAYER_EXPLORED_ZONES_1+222 + Private, // PLAYER_EXPLORED_ZONES_1+223 + Private, // PLAYER_EXPLORED_ZONES_1+224 + Private, // PLAYER_EXPLORED_ZONES_1+225 + Private, // PLAYER_EXPLORED_ZONES_1+226 + Private, // PLAYER_EXPLORED_ZONES_1+227 + Private, // PLAYER_EXPLORED_ZONES_1+228 + Private, // PLAYER_EXPLORED_ZONES_1+229 + Private, // PLAYER_EXPLORED_ZONES_1+230 + Private, // PLAYER_EXPLORED_ZONES_1+231 + Private, // PLAYER_EXPLORED_ZONES_1+232 + Private, // PLAYER_EXPLORED_ZONES_1+233 + Private, // PLAYER_EXPLORED_ZONES_1+234 + Private, // PLAYER_EXPLORED_ZONES_1+235 + Private, // PLAYER_EXPLORED_ZONES_1+236 + Private, // PLAYER_EXPLORED_ZONES_1+237 + Private, // PLAYER_EXPLORED_ZONES_1+238 + Private, // PLAYER_EXPLORED_ZONES_1+239 + Private, // PLAYER_EXPLORED_ZONES_1+240 + Private, // PLAYER_EXPLORED_ZONES_1+241 + Private, // PLAYER_EXPLORED_ZONES_1+242 + Private, // PLAYER_EXPLORED_ZONES_1+243 + Private, // PLAYER_EXPLORED_ZONES_1+244 + Private, // PLAYER_EXPLORED_ZONES_1+245 + Private, // PLAYER_EXPLORED_ZONES_1+246 + Private, // PLAYER_EXPLORED_ZONES_1+247 + Private, // PLAYER_EXPLORED_ZONES_1+248 + Private, // PLAYER_EXPLORED_ZONES_1+249 + Private, // PLAYER_EXPLORED_ZONES_1+250 + Private, // PLAYER_EXPLORED_ZONES_1+251 + Private, // PLAYER_EXPLORED_ZONES_1+252 + Private, // PLAYER_EXPLORED_ZONES_1+253 + Private, // PLAYER_EXPLORED_ZONES_1+254 + Private, // PLAYER_EXPLORED_ZONES_1+255 + Private, // PLAYER_FIELD_REST_INFO + Private, // PLAYER_FIELD_REST_INFO+1 + Private, // PLAYER_FIELD_REST_INFO+2 + Private, // PLAYER_FIELD_REST_INFO+3 + Private, // PLAYER_FIELD_MOD_DAMAGE_DONE_POS + Private, // PLAYER_FIELD_MOD_DAMAGE_DONE_POS+1 + Private, // PLAYER_FIELD_MOD_DAMAGE_DONE_POS+2 + Private, // PLAYER_FIELD_MOD_DAMAGE_DONE_POS+3 + Private, // PLAYER_FIELD_MOD_DAMAGE_DONE_POS+4 + Private, // PLAYER_FIELD_MOD_DAMAGE_DONE_POS+5 + Private, // PLAYER_FIELD_MOD_DAMAGE_DONE_POS+6 + Private, // PLAYER_FIELD_MOD_DAMAGE_DONE_NEG + Private, // PLAYER_FIELD_MOD_DAMAGE_DONE_NEG+1 + Private, // PLAYER_FIELD_MOD_DAMAGE_DONE_NEG+2 + Private, // PLAYER_FIELD_MOD_DAMAGE_DONE_NEG+3 + Private, // PLAYER_FIELD_MOD_DAMAGE_DONE_NEG+4 + Private, // PLAYER_FIELD_MOD_DAMAGE_DONE_NEG+5 + Private, // PLAYER_FIELD_MOD_DAMAGE_DONE_NEG+6 + Private, // PLAYER_FIELD_MOD_DAMAGE_DONE_PCT + Private, // PLAYER_FIELD_MOD_DAMAGE_DONE_PCT+1 + Private, // PLAYER_FIELD_MOD_DAMAGE_DONE_PCT+2 + Private, // PLAYER_FIELD_MOD_DAMAGE_DONE_PCT+3 + Private, // PLAYER_FIELD_MOD_DAMAGE_DONE_PCT+4 + Private, // PLAYER_FIELD_MOD_DAMAGE_DONE_PCT+5 + Private, // PLAYER_FIELD_MOD_DAMAGE_DONE_PCT+6 + Private, // PLAYER_FIELD_MOD_HEALING_DONE_POS + Private, // PLAYER_FIELD_MOD_HEALING_PCT + Private, // PLAYER_FIELD_MOD_HEALING_DONE_PCT + Private, // PLAYER_FIELD_MOD_PERIODIC_HEALING_DONE_PERCENT + Private, // PLAYER_FIELD_WEAPON_DMG_MULTIPLIERS + Private, // PLAYER_FIELD_WEAPON_DMG_MULTIPLIERS+1 + Private, // PLAYER_FIELD_WEAPON_DMG_MULTIPLIERS+2 + Private, // PLAYER_FIELD_WEAPON_ATK_SPEED_MULTIPLIERS + Private, // PLAYER_FIELD_WEAPON_ATK_SPEED_MULTIPLIERS+1 + Private, // PLAYER_FIELD_WEAPON_ATK_SPEED_MULTIPLIERS+2 + Private, // PLAYER_FIELD_MOD_SPELL_POWER_PCT + Private, // PLAYER_FIELD_MOD_RESILIENCE_PERCENT + Private, // PLAYER_FIELD_OVERRIDE_SPELL_POWER_BY_AP_PCT + Private, // PLAYER_FIELD_OVERRIDE_AP_BY_SPELL_POWER_PERCENT + Private, // PLAYER_FIELD_MOD_TARGET_RESISTANCE + Private, // PLAYER_FIELD_MOD_TARGET_PHYSICAL_RESISTANCE + Private, // PLAYER_FIELD_LOCAL_FLAGS + Private, // PLAYER_FIELD_BYTES + Private, // PLAYER_SELF_RES_SPELL + Private, // PLAYER_FIELD_PVP_MEDALS + Private, // PLAYER_FIELD_BUYBACK_PRICE_1 + Private, // PLAYER_FIELD_BUYBACK_PRICE_1+1 + Private, // PLAYER_FIELD_BUYBACK_PRICE_1+2 + Private, // PLAYER_FIELD_BUYBACK_PRICE_1+3 + Private, // PLAYER_FIELD_BUYBACK_PRICE_1+4 + Private, // PLAYER_FIELD_BUYBACK_PRICE_1+5 + Private, // PLAYER_FIELD_BUYBACK_PRICE_1+6 + Private, // PLAYER_FIELD_BUYBACK_PRICE_1+7 + Private, // PLAYER_FIELD_BUYBACK_PRICE_1+8 + Private, // PLAYER_FIELD_BUYBACK_PRICE_1+9 + Private, // PLAYER_FIELD_BUYBACK_PRICE_1+10 + Private, // PLAYER_FIELD_BUYBACK_PRICE_1+11 + Private, // PLAYER_FIELD_BUYBACK_TIMESTAMP_1 + Private, // PLAYER_FIELD_BUYBACK_TIMESTAMP_1+1 + Private, // PLAYER_FIELD_BUYBACK_TIMESTAMP_1+2 + Private, // PLAYER_FIELD_BUYBACK_TIMESTAMP_1+3 + Private, // PLAYER_FIELD_BUYBACK_TIMESTAMP_1+4 + Private, // PLAYER_FIELD_BUYBACK_TIMESTAMP_1+5 + Private, // PLAYER_FIELD_BUYBACK_TIMESTAMP_1+6 + Private, // PLAYER_FIELD_BUYBACK_TIMESTAMP_1+7 + Private, // PLAYER_FIELD_BUYBACK_TIMESTAMP_1+8 + Private, // PLAYER_FIELD_BUYBACK_TIMESTAMP_1+9 + Private, // PLAYER_FIELD_BUYBACK_TIMESTAMP_1+10 + Private, // PLAYER_FIELD_BUYBACK_TIMESTAMP_1+11 + Private, // PLAYER_FIELD_KILLS + Private, // PLAYER_FIELD_LIFETIME_HONORABLE_KILLS + Private, // PLAYER_FIELD_WATCHED_FACTION_INDEX + Private, // PLAYER_FIELD_COMBAT_RATING_1 + Private, // PLAYER_FIELD_COMBAT_RATING_1+1 + Private, // PLAYER_FIELD_COMBAT_RATING_1+2 + Private, // PLAYER_FIELD_COMBAT_RATING_1+3 + Private, // PLAYER_FIELD_COMBAT_RATING_1+4 + Private, // PLAYER_FIELD_COMBAT_RATING_1+5 + Private, // PLAYER_FIELD_COMBAT_RATING_1+6 + Private, // PLAYER_FIELD_COMBAT_RATING_1+7 + Private, // PLAYER_FIELD_COMBAT_RATING_1+8 + Private, // PLAYER_FIELD_COMBAT_RATING_1+9 + Private, // PLAYER_FIELD_COMBAT_RATING_1+10 + Private, // PLAYER_FIELD_COMBAT_RATING_1+11 + Private, // PLAYER_FIELD_COMBAT_RATING_1+12 + Private, // PLAYER_FIELD_COMBAT_RATING_1+13 + Private, // PLAYER_FIELD_COMBAT_RATING_1+14 + Private, // PLAYER_FIELD_COMBAT_RATING_1+15 + Private, // PLAYER_FIELD_COMBAT_RATING_1+16 + Private, // PLAYER_FIELD_COMBAT_RATING_1+17 + Private, // PLAYER_FIELD_COMBAT_RATING_1+18 + Private, // PLAYER_FIELD_COMBAT_RATING_1+19 + Private, // PLAYER_FIELD_COMBAT_RATING_1+20 + Private, // PLAYER_FIELD_COMBAT_RATING_1+21 + Private, // PLAYER_FIELD_COMBAT_RATING_1+22 + Private, // PLAYER_FIELD_COMBAT_RATING_1+23 + Private, // PLAYER_FIELD_COMBAT_RATING_1+24 + Private, // PLAYER_FIELD_COMBAT_RATING_1+25 + Private, // PLAYER_FIELD_COMBAT_RATING_1+26 + Private, // PLAYER_FIELD_COMBAT_RATING_1+27 + Private, // PLAYER_FIELD_COMBAT_RATING_1+28 + Private, // PLAYER_FIELD_COMBAT_RATING_1+29 + Private, // PLAYER_FIELD_COMBAT_RATING_1+30 + Private, // PLAYER_FIELD_COMBAT_RATING_1+31 + Private, // PLAYER_FIELD_ARENA_TEAM_INFO_1_1 + Private, // PLAYER_FIELD_ARENA_TEAM_INFO_1_1+1 + Private, // PLAYER_FIELD_ARENA_TEAM_INFO_1_1+2 + Private, // PLAYER_FIELD_ARENA_TEAM_INFO_1_1+3 + Private, // PLAYER_FIELD_ARENA_TEAM_INFO_1_1+4 + Private, // PLAYER_FIELD_ARENA_TEAM_INFO_1_1+5 + Private, // PLAYER_FIELD_ARENA_TEAM_INFO_1_1+6 + Private, // PLAYER_FIELD_ARENA_TEAM_INFO_1_1+7 + Private, // PLAYER_FIELD_ARENA_TEAM_INFO_1_1+8 + Private, // PLAYER_FIELD_ARENA_TEAM_INFO_1_1+9 + Private, // PLAYER_FIELD_ARENA_TEAM_INFO_1_1+10 + Private, // PLAYER_FIELD_ARENA_TEAM_INFO_1_1+11 + Private, // PLAYER_FIELD_ARENA_TEAM_INFO_1_1+12 + Private, // PLAYER_FIELD_ARENA_TEAM_INFO_1_1+13 + Private, // PLAYER_FIELD_ARENA_TEAM_INFO_1_1+14 + Private, // PLAYER_FIELD_ARENA_TEAM_INFO_1_1+15 + Private, // PLAYER_FIELD_ARENA_TEAM_INFO_1_1+16 + Private, // PLAYER_FIELD_ARENA_TEAM_INFO_1_1+17 + Private, // PLAYER_FIELD_ARENA_TEAM_INFO_1_1+18 + Private, // PLAYER_FIELD_ARENA_TEAM_INFO_1_1+19 + Private, // PLAYER_FIELD_ARENA_TEAM_INFO_1_1+20 + Private, // PLAYER_FIELD_ARENA_TEAM_INFO_1_1+21 + Private, // PLAYER_FIELD_ARENA_TEAM_INFO_1_1+22 + Private, // PLAYER_FIELD_ARENA_TEAM_INFO_1_1+23 + Private, // PLAYER_FIELD_ARENA_TEAM_INFO_1_1+24 + Private, // PLAYER_FIELD_ARENA_TEAM_INFO_1_1+25 + Private, // PLAYER_FIELD_ARENA_TEAM_INFO_1_1+26 + Private, // PLAYER_FIELD_ARENA_TEAM_INFO_1_1+27 + Private, // PLAYER_FIELD_ARENA_TEAM_INFO_1_1+28 + Private, // PLAYER_FIELD_ARENA_TEAM_INFO_1_1+29 + Private, // PLAYER_FIELD_ARENA_TEAM_INFO_1_1+30 + Private, // PLAYER_FIELD_ARENA_TEAM_INFO_1_1+31 + Private, // PLAYER_FIELD_ARENA_TEAM_INFO_1_1+32 + Private, // PLAYER_FIELD_ARENA_TEAM_INFO_1_1+33 + Private, // PLAYER_FIELD_ARENA_TEAM_INFO_1_1+34 + Private, // PLAYER_FIELD_ARENA_TEAM_INFO_1_1+35 + Private, // PLAYER_FIELD_ARENA_TEAM_INFO_1_1+36 + Private, // PLAYER_FIELD_ARENA_TEAM_INFO_1_1+37 + Private, // PLAYER_FIELD_ARENA_TEAM_INFO_1_1+38 + Private, // PLAYER_FIELD_ARENA_TEAM_INFO_1_1+39 + Private, // PLAYER_FIELD_ARENA_TEAM_INFO_1_1+40 + Private, // PLAYER_FIELD_ARENA_TEAM_INFO_1_1+41 + Private, // PLAYER_FIELD_MAX_LEVEL + Private, // PLAYER_FIELD_SCALING_PLAYER_LEVEL_DELTA + Private, // PLAYER_FIELD_MAX_CREATURE_SCALING_LEVEL + Private, // PLAYER_NO_REAGENT_COST_1 + Private, // PLAYER_NO_REAGENT_COST_1+1 + Private, // PLAYER_NO_REAGENT_COST_1+2 + Private, // PLAYER_NO_REAGENT_COST_1+3 + Private, // PLAYER_PET_SPELL_POWER + Private, // PLAYER_FIELD_RESEARCHING_1 + Private, // PLAYER_FIELD_RESEARCHING_1+1 + Private, // PLAYER_FIELD_RESEARCHING_1+2 + Private, // PLAYER_FIELD_RESEARCHING_1+3 + Private, // PLAYER_FIELD_RESEARCHING_1+4 + Private, // PLAYER_FIELD_RESEARCHING_1+5 + Private, // PLAYER_FIELD_RESEARCHING_1+6 + Private, // PLAYER_FIELD_RESEARCHING_1+7 + Private, // PLAYER_FIELD_RESEARCHING_1+8 + Private, // PLAYER_FIELD_RESEARCHING_1+9 + Private, // PLAYER_PROFESSION_SKILL_LINE_1 + Private, // PLAYER_PROFESSION_SKILL_LINE_1+1 + Private, // PLAYER_FIELD_UI_HIT_MODIFIER + Private, // PLAYER_FIELD_UI_SPELL_HIT_MODIFIER + Private, // PLAYER_FIELD_HOME_REALM_TIME_OFFSET + Private, // PLAYER_FIELD_MOD_PET_HASTE + Private, // PLAYER_FIELD_BYTES2 + Private | UrgentSelfOnly, // PLAYER_FIELD_BYTES3 + Private, // PLAYER_FIELD_LFG_BONUS_FACTION_ID + Private, // PLAYER_FIELD_LOOT_SPEC_ID + Private | UrgentSelfOnly, // PLAYER_FIELD_OVERRIDE_ZONE_PVP_TYPE + Private, // PLAYER_FIELD_BAG_SLOT_FLAGS + Private, // PLAYER_FIELD_BAG_SLOT_FLAGS+1 + Private, // PLAYER_FIELD_BAG_SLOT_FLAGS+2 + Private, // PLAYER_FIELD_BAG_SLOT_FLAGS+3 + Private, // PLAYER_FIELD_BANK_BAG_SLOT_FLAGS + Private, // PLAYER_FIELD_BANK_BAG_SLOT_FLAGS+1 + Private, // PLAYER_FIELD_BANK_BAG_SLOT_FLAGS+2 + Private, // PLAYER_FIELD_BANK_BAG_SLOT_FLAGS+3 + Private, // PLAYER_FIELD_BANK_BAG_SLOT_FLAGS+4 + Private, // PLAYER_FIELD_BANK_BAG_SLOT_FLAGS+5 + Private, // PLAYER_FIELD_BANK_BAG_SLOT_FLAGS+6 + Private, // PLAYER_FIELD_INSERT_ITEMS_LEFT_TO_RIGHT + Private, // PLAYER_FIELD_QUEST_COMPLETED + Private, // PLAYER_FIELD_QUEST_COMPLETED+1 + Private, // PLAYER_FIELD_QUEST_COMPLETED+2 + Private, // PLAYER_FIELD_QUEST_COMPLETED+3 + Private, // PLAYER_FIELD_QUEST_COMPLETED+4 + Private, // PLAYER_FIELD_QUEST_COMPLETED+5 + Private, // PLAYER_FIELD_QUEST_COMPLETED+6 + Private, // PLAYER_FIELD_QUEST_COMPLETED+7 + Private, // PLAYER_FIELD_QUEST_COMPLETED+8 + Private, // PLAYER_FIELD_QUEST_COMPLETED+9 + Private, // PLAYER_FIELD_QUEST_COMPLETED+10 + Private, // PLAYER_FIELD_QUEST_COMPLETED+11 + Private, // PLAYER_FIELD_QUEST_COMPLETED+12 + Private, // PLAYER_FIELD_QUEST_COMPLETED+13 + Private, // PLAYER_FIELD_QUEST_COMPLETED+14 + Private, // PLAYER_FIELD_QUEST_COMPLETED+15 + Private, // PLAYER_FIELD_QUEST_COMPLETED+16 + Private, // PLAYER_FIELD_QUEST_COMPLETED+17 + Private, // PLAYER_FIELD_QUEST_COMPLETED+18 + Private, // PLAYER_FIELD_QUEST_COMPLETED+19 + Private, // PLAYER_FIELD_QUEST_COMPLETED+20 + Private, // PLAYER_FIELD_QUEST_COMPLETED+21 + Private, // PLAYER_FIELD_QUEST_COMPLETED+22 + Private, // PLAYER_FIELD_QUEST_COMPLETED+23 + Private, // PLAYER_FIELD_QUEST_COMPLETED+24 + Private, // PLAYER_FIELD_QUEST_COMPLETED+25 + Private, // PLAYER_FIELD_QUEST_COMPLETED+26 + Private, // PLAYER_FIELD_QUEST_COMPLETED+27 + Private, // PLAYER_FIELD_QUEST_COMPLETED+28 + Private, // PLAYER_FIELD_QUEST_COMPLETED+29 + Private, // PLAYER_FIELD_QUEST_COMPLETED+30 + Private, // PLAYER_FIELD_QUEST_COMPLETED+31 + Private, // PLAYER_FIELD_QUEST_COMPLETED+32 + Private, // PLAYER_FIELD_QUEST_COMPLETED+33 + Private, // PLAYER_FIELD_QUEST_COMPLETED+34 + Private, // PLAYER_FIELD_QUEST_COMPLETED+35 + Private, // PLAYER_FIELD_QUEST_COMPLETED+36 + Private, // PLAYER_FIELD_QUEST_COMPLETED+37 + Private, // PLAYER_FIELD_QUEST_COMPLETED+38 + Private, // PLAYER_FIELD_QUEST_COMPLETED+39 + Private, // PLAYER_FIELD_QUEST_COMPLETED+40 + Private, // PLAYER_FIELD_QUEST_COMPLETED+41 + Private, // PLAYER_FIELD_QUEST_COMPLETED+42 + Private, // PLAYER_FIELD_QUEST_COMPLETED+43 + Private, // PLAYER_FIELD_QUEST_COMPLETED+44 + Private, // PLAYER_FIELD_QUEST_COMPLETED+45 + Private, // PLAYER_FIELD_QUEST_COMPLETED+46 + Private, // PLAYER_FIELD_QUEST_COMPLETED+47 + Private, // PLAYER_FIELD_QUEST_COMPLETED+48 + Private, // PLAYER_FIELD_QUEST_COMPLETED+49 + Private, // PLAYER_FIELD_QUEST_COMPLETED+50 + Private, // PLAYER_FIELD_QUEST_COMPLETED+51 + Private, // PLAYER_FIELD_QUEST_COMPLETED+52 + Private, // PLAYER_FIELD_QUEST_COMPLETED+53 + Private, // PLAYER_FIELD_QUEST_COMPLETED+54 + Private, // PLAYER_FIELD_QUEST_COMPLETED+55 + Private, // PLAYER_FIELD_QUEST_COMPLETED+56 + Private, // PLAYER_FIELD_QUEST_COMPLETED+57 + Private, // PLAYER_FIELD_QUEST_COMPLETED+58 + Private, // PLAYER_FIELD_QUEST_COMPLETED+59 + Private, // PLAYER_FIELD_QUEST_COMPLETED+60 + Private, // PLAYER_FIELD_QUEST_COMPLETED+61 + Private, // PLAYER_FIELD_QUEST_COMPLETED+62 + Private, // PLAYER_FIELD_QUEST_COMPLETED+63 + Private, // PLAYER_FIELD_QUEST_COMPLETED+64 + Private, // PLAYER_FIELD_QUEST_COMPLETED+65 + Private, // PLAYER_FIELD_QUEST_COMPLETED+66 + Private, // PLAYER_FIELD_QUEST_COMPLETED+67 + Private, // PLAYER_FIELD_QUEST_COMPLETED+68 + Private, // PLAYER_FIELD_QUEST_COMPLETED+69 + Private, // PLAYER_FIELD_QUEST_COMPLETED+70 + Private, // PLAYER_FIELD_QUEST_COMPLETED+71 + Private, // PLAYER_FIELD_QUEST_COMPLETED+72 + Private, // PLAYER_FIELD_QUEST_COMPLETED+73 + Private, // PLAYER_FIELD_QUEST_COMPLETED+74 + Private, // PLAYER_FIELD_QUEST_COMPLETED+75 + Private, // PLAYER_FIELD_QUEST_COMPLETED+76 + Private, // PLAYER_FIELD_QUEST_COMPLETED+77 + Private, // PLAYER_FIELD_QUEST_COMPLETED+78 + Private, // PLAYER_FIELD_QUEST_COMPLETED+79 + Private, // PLAYER_FIELD_QUEST_COMPLETED+80 + Private, // PLAYER_FIELD_QUEST_COMPLETED+81 + Private, // PLAYER_FIELD_QUEST_COMPLETED+82 + Private, // PLAYER_FIELD_QUEST_COMPLETED+83 + Private, // PLAYER_FIELD_QUEST_COMPLETED+84 + Private, // PLAYER_FIELD_QUEST_COMPLETED+85 + Private, // PLAYER_FIELD_QUEST_COMPLETED+86 + Private, // PLAYER_FIELD_QUEST_COMPLETED+87 + Private, // PLAYER_FIELD_QUEST_COMPLETED+88 + Private, // PLAYER_FIELD_QUEST_COMPLETED+89 + Private, // PLAYER_FIELD_QUEST_COMPLETED+90 + Private, // PLAYER_FIELD_QUEST_COMPLETED+91 + Private, // PLAYER_FIELD_QUEST_COMPLETED+92 + Private, // PLAYER_FIELD_QUEST_COMPLETED+93 + Private, // PLAYER_FIELD_QUEST_COMPLETED+94 + Private, // PLAYER_FIELD_QUEST_COMPLETED+95 + Private, // PLAYER_FIELD_QUEST_COMPLETED+96 + Private, // PLAYER_FIELD_QUEST_COMPLETED+97 + Private, // PLAYER_FIELD_QUEST_COMPLETED+98 + Private, // PLAYER_FIELD_QUEST_COMPLETED+99 + Private, // PLAYER_FIELD_QUEST_COMPLETED+100 + Private, // PLAYER_FIELD_QUEST_COMPLETED+101 + Private, // PLAYER_FIELD_QUEST_COMPLETED+102 + Private, // PLAYER_FIELD_QUEST_COMPLETED+103 + Private, // PLAYER_FIELD_QUEST_COMPLETED+104 + Private, // PLAYER_FIELD_QUEST_COMPLETED+105 + Private, // PLAYER_FIELD_QUEST_COMPLETED+106 + Private, // PLAYER_FIELD_QUEST_COMPLETED+107 + Private, // PLAYER_FIELD_QUEST_COMPLETED+108 + Private, // PLAYER_FIELD_QUEST_COMPLETED+109 + Private, // PLAYER_FIELD_QUEST_COMPLETED+110 + Private, // PLAYER_FIELD_QUEST_COMPLETED+111 + Private, // PLAYER_FIELD_QUEST_COMPLETED+112 + Private, // PLAYER_FIELD_QUEST_COMPLETED+113 + Private, // PLAYER_FIELD_QUEST_COMPLETED+114 + Private, // PLAYER_FIELD_QUEST_COMPLETED+115 + Private, // PLAYER_FIELD_QUEST_COMPLETED+116 + Private, // PLAYER_FIELD_QUEST_COMPLETED+117 + Private, // PLAYER_FIELD_QUEST_COMPLETED+118 + Private, // PLAYER_FIELD_QUEST_COMPLETED+119 + Private, // PLAYER_FIELD_QUEST_COMPLETED+120 + Private, // PLAYER_FIELD_QUEST_COMPLETED+121 + Private, // PLAYER_FIELD_QUEST_COMPLETED+122 + Private, // PLAYER_FIELD_QUEST_COMPLETED+123 + Private, // PLAYER_FIELD_QUEST_COMPLETED+124 + Private, // PLAYER_FIELD_QUEST_COMPLETED+125 + Private, // PLAYER_FIELD_QUEST_COMPLETED+126 + Private, // PLAYER_FIELD_QUEST_COMPLETED+127 + Private, // PLAYER_FIELD_QUEST_COMPLETED+128 + Private, // PLAYER_FIELD_QUEST_COMPLETED+129 + Private, // PLAYER_FIELD_QUEST_COMPLETED+130 + Private, // PLAYER_FIELD_QUEST_COMPLETED+131 + Private, // PLAYER_FIELD_QUEST_COMPLETED+132 + Private, // PLAYER_FIELD_QUEST_COMPLETED+133 + Private, // PLAYER_FIELD_QUEST_COMPLETED+134 + Private, // PLAYER_FIELD_QUEST_COMPLETED+135 + Private, // PLAYER_FIELD_QUEST_COMPLETED+136 + Private, // PLAYER_FIELD_QUEST_COMPLETED+137 + Private, // PLAYER_FIELD_QUEST_COMPLETED+138 + Private, // PLAYER_FIELD_QUEST_COMPLETED+139 + Private, // PLAYER_FIELD_QUEST_COMPLETED+140 + Private, // PLAYER_FIELD_QUEST_COMPLETED+141 + Private, // PLAYER_FIELD_QUEST_COMPLETED+142 + Private, // PLAYER_FIELD_QUEST_COMPLETED+143 + Private, // PLAYER_FIELD_QUEST_COMPLETED+144 + Private, // PLAYER_FIELD_QUEST_COMPLETED+145 + Private, // PLAYER_FIELD_QUEST_COMPLETED+146 + Private, // PLAYER_FIELD_QUEST_COMPLETED+147 + Private, // PLAYER_FIELD_QUEST_COMPLETED+148 + Private, // PLAYER_FIELD_QUEST_COMPLETED+149 + Private, // PLAYER_FIELD_QUEST_COMPLETED+150 + Private, // PLAYER_FIELD_QUEST_COMPLETED+151 + Private, // PLAYER_FIELD_QUEST_COMPLETED+152 + Private, // PLAYER_FIELD_QUEST_COMPLETED+153 + Private, // PLAYER_FIELD_QUEST_COMPLETED+154 + Private, // PLAYER_FIELD_QUEST_COMPLETED+155 + Private, // PLAYER_FIELD_QUEST_COMPLETED+156 + Private, // PLAYER_FIELD_QUEST_COMPLETED+157 + Private, // PLAYER_FIELD_QUEST_COMPLETED+158 + Private, // PLAYER_FIELD_QUEST_COMPLETED+159 + Private, // PLAYER_FIELD_QUEST_COMPLETED+160 + Private, // PLAYER_FIELD_QUEST_COMPLETED+161 + Private, // PLAYER_FIELD_QUEST_COMPLETED+162 + Private, // PLAYER_FIELD_QUEST_COMPLETED+163 + Private, // PLAYER_FIELD_QUEST_COMPLETED+164 + Private, // PLAYER_FIELD_QUEST_COMPLETED+165 + Private, // PLAYER_FIELD_QUEST_COMPLETED+166 + Private, // PLAYER_FIELD_QUEST_COMPLETED+167 + Private, // PLAYER_FIELD_QUEST_COMPLETED+168 + Private, // PLAYER_FIELD_QUEST_COMPLETED+169 + Private, // PLAYER_FIELD_QUEST_COMPLETED+170 + Private, // PLAYER_FIELD_QUEST_COMPLETED+171 + Private, // PLAYER_FIELD_QUEST_COMPLETED+172 + Private, // PLAYER_FIELD_QUEST_COMPLETED+173 + Private, // PLAYER_FIELD_QUEST_COMPLETED+174 + Private, // PLAYER_FIELD_QUEST_COMPLETED+175 + Private, // PLAYER_FIELD_QUEST_COMPLETED+176 + Private, // PLAYER_FIELD_QUEST_COMPLETED+177 + Private, // PLAYER_FIELD_QUEST_COMPLETED+178 + Private, // PLAYER_FIELD_QUEST_COMPLETED+179 + Private, // PLAYER_FIELD_QUEST_COMPLETED+180 + Private, // PLAYER_FIELD_QUEST_COMPLETED+181 + Private, // PLAYER_FIELD_QUEST_COMPLETED+182 + Private, // PLAYER_FIELD_QUEST_COMPLETED+183 + Private, // PLAYER_FIELD_QUEST_COMPLETED+184 + Private, // PLAYER_FIELD_QUEST_COMPLETED+185 + Private, // PLAYER_FIELD_QUEST_COMPLETED+186 + Private, // PLAYER_FIELD_QUEST_COMPLETED+187 + Private, // PLAYER_FIELD_QUEST_COMPLETED+188 + Private, // PLAYER_FIELD_QUEST_COMPLETED+189 + Private, // PLAYER_FIELD_QUEST_COMPLETED+190 + Private, // PLAYER_FIELD_QUEST_COMPLETED+191 + Private, // PLAYER_FIELD_QUEST_COMPLETED+192 + Private, // PLAYER_FIELD_QUEST_COMPLETED+193 + Private, // PLAYER_FIELD_QUEST_COMPLETED+194 + Private, // PLAYER_FIELD_QUEST_COMPLETED+195 + Private, // PLAYER_FIELD_QUEST_COMPLETED+196 + Private, // PLAYER_FIELD_QUEST_COMPLETED+197 + Private, // PLAYER_FIELD_QUEST_COMPLETED+198 + Private, // PLAYER_FIELD_QUEST_COMPLETED+199 + Private, // PLAYER_FIELD_QUEST_COMPLETED+200 + Private, // PLAYER_FIELD_QUEST_COMPLETED+201 + Private, // PLAYER_FIELD_QUEST_COMPLETED+202 + Private, // PLAYER_FIELD_QUEST_COMPLETED+203 + Private, // PLAYER_FIELD_QUEST_COMPLETED+204 + Private, // PLAYER_FIELD_QUEST_COMPLETED+205 + Private, // PLAYER_FIELD_QUEST_COMPLETED+206 + Private, // PLAYER_FIELD_QUEST_COMPLETED+207 + Private, // PLAYER_FIELD_QUEST_COMPLETED+208 + Private, // PLAYER_FIELD_QUEST_COMPLETED+209 + Private, // PLAYER_FIELD_QUEST_COMPLETED+210 + Private, // PLAYER_FIELD_QUEST_COMPLETED+211 + Private, // PLAYER_FIELD_QUEST_COMPLETED+212 + Private, // PLAYER_FIELD_QUEST_COMPLETED+213 + Private, // PLAYER_FIELD_QUEST_COMPLETED+214 + Private, // PLAYER_FIELD_QUEST_COMPLETED+215 + Private, // PLAYER_FIELD_QUEST_COMPLETED+216 + Private, // PLAYER_FIELD_QUEST_COMPLETED+217 + Private, // PLAYER_FIELD_QUEST_COMPLETED+218 + Private, // PLAYER_FIELD_QUEST_COMPLETED+219 + Private, // PLAYER_FIELD_QUEST_COMPLETED+220 + Private, // PLAYER_FIELD_QUEST_COMPLETED+221 + Private, // PLAYER_FIELD_QUEST_COMPLETED+222 + Private, // PLAYER_FIELD_QUEST_COMPLETED+223 + Private, // PLAYER_FIELD_QUEST_COMPLETED+224 + Private, // PLAYER_FIELD_QUEST_COMPLETED+225 + Private, // PLAYER_FIELD_QUEST_COMPLETED+226 + Private, // PLAYER_FIELD_QUEST_COMPLETED+227 + Private, // PLAYER_FIELD_QUEST_COMPLETED+228 + Private, // PLAYER_FIELD_QUEST_COMPLETED+229 + Private, // PLAYER_FIELD_QUEST_COMPLETED+230 + Private, // PLAYER_FIELD_QUEST_COMPLETED+231 + Private, // PLAYER_FIELD_QUEST_COMPLETED+232 + Private, // PLAYER_FIELD_QUEST_COMPLETED+233 + Private, // PLAYER_FIELD_QUEST_COMPLETED+234 + Private, // PLAYER_FIELD_QUEST_COMPLETED+235 + Private, // PLAYER_FIELD_QUEST_COMPLETED+236 + Private, // PLAYER_FIELD_QUEST_COMPLETED+237 + Private, // PLAYER_FIELD_QUEST_COMPLETED+238 + Private, // PLAYER_FIELD_QUEST_COMPLETED+239 + Private, // PLAYER_FIELD_QUEST_COMPLETED+240 + Private, // PLAYER_FIELD_QUEST_COMPLETED+241 + Private, // PLAYER_FIELD_QUEST_COMPLETED+242 + Private, // PLAYER_FIELD_QUEST_COMPLETED+243 + Private, // PLAYER_FIELD_QUEST_COMPLETED+244 + Private, // PLAYER_FIELD_QUEST_COMPLETED+245 + Private, // PLAYER_FIELD_QUEST_COMPLETED+246 + Private, // PLAYER_FIELD_QUEST_COMPLETED+247 + Private, // PLAYER_FIELD_QUEST_COMPLETED+248 + Private, // PLAYER_FIELD_QUEST_COMPLETED+249 + Private, // PLAYER_FIELD_QUEST_COMPLETED+250 + Private, // PLAYER_FIELD_QUEST_COMPLETED+251 + Private, // PLAYER_FIELD_QUEST_COMPLETED+252 + Private, // PLAYER_FIELD_QUEST_COMPLETED+253 + Private, // PLAYER_FIELD_QUEST_COMPLETED+254 + Private, // PLAYER_FIELD_QUEST_COMPLETED+255 + Private, // PLAYER_FIELD_QUEST_COMPLETED+256 + Private, // PLAYER_FIELD_QUEST_COMPLETED+257 + Private, // PLAYER_FIELD_QUEST_COMPLETED+258 + Private, // PLAYER_FIELD_QUEST_COMPLETED+259 + Private, // PLAYER_FIELD_QUEST_COMPLETED+260 + Private, // PLAYER_FIELD_QUEST_COMPLETED+261 + Private, // PLAYER_FIELD_QUEST_COMPLETED+262 + Private, // PLAYER_FIELD_QUEST_COMPLETED+263 + Private, // PLAYER_FIELD_QUEST_COMPLETED+264 + Private, // PLAYER_FIELD_QUEST_COMPLETED+265 + Private, // PLAYER_FIELD_QUEST_COMPLETED+266 + Private, // PLAYER_FIELD_QUEST_COMPLETED+267 + Private, // PLAYER_FIELD_QUEST_COMPLETED+268 + Private, // PLAYER_FIELD_QUEST_COMPLETED+269 + Private, // PLAYER_FIELD_QUEST_COMPLETED+270 + Private, // PLAYER_FIELD_QUEST_COMPLETED+271 + Private, // PLAYER_FIELD_QUEST_COMPLETED+272 + Private, // PLAYER_FIELD_QUEST_COMPLETED+273 + Private, // PLAYER_FIELD_QUEST_COMPLETED+274 + Private, // PLAYER_FIELD_QUEST_COMPLETED+275 + Private, // PLAYER_FIELD_QUEST_COMPLETED+276 + Private, // PLAYER_FIELD_QUEST_COMPLETED+277 + Private, // PLAYER_FIELD_QUEST_COMPLETED+278 + Private, // PLAYER_FIELD_QUEST_COMPLETED+279 + Private, // PLAYER_FIELD_QUEST_COMPLETED+280 + Private, // PLAYER_FIELD_QUEST_COMPLETED+281 + Private, // PLAYER_FIELD_QUEST_COMPLETED+282 + Private, // PLAYER_FIELD_QUEST_COMPLETED+283 + Private, // PLAYER_FIELD_QUEST_COMPLETED+284 + Private, // PLAYER_FIELD_QUEST_COMPLETED+285 + Private, // PLAYER_FIELD_QUEST_COMPLETED+286 + Private, // PLAYER_FIELD_QUEST_COMPLETED+287 + Private, // PLAYER_FIELD_QUEST_COMPLETED+288 + Private, // PLAYER_FIELD_QUEST_COMPLETED+289 + Private, // PLAYER_FIELD_QUEST_COMPLETED+290 + Private, // PLAYER_FIELD_QUEST_COMPLETED+291 + Private, // PLAYER_FIELD_QUEST_COMPLETED+292 + Private, // PLAYER_FIELD_QUEST_COMPLETED+293 + Private, // PLAYER_FIELD_QUEST_COMPLETED+294 + Private, // PLAYER_FIELD_QUEST_COMPLETED+295 + Private, // PLAYER_FIELD_QUEST_COMPLETED+296 + Private, // PLAYER_FIELD_QUEST_COMPLETED+297 + Private, // PLAYER_FIELD_QUEST_COMPLETED+298 + Private, // PLAYER_FIELD_QUEST_COMPLETED+299 + Private, // PLAYER_FIELD_QUEST_COMPLETED+300 + Private, // PLAYER_FIELD_QUEST_COMPLETED+301 + Private, // PLAYER_FIELD_QUEST_COMPLETED+302 + Private, // PLAYER_FIELD_QUEST_COMPLETED+303 + Private, // PLAYER_FIELD_QUEST_COMPLETED+304 + Private, // PLAYER_FIELD_QUEST_COMPLETED+305 + Private, // PLAYER_FIELD_QUEST_COMPLETED+306 + Private, // PLAYER_FIELD_QUEST_COMPLETED+307 + Private, // PLAYER_FIELD_QUEST_COMPLETED+308 + Private, // PLAYER_FIELD_QUEST_COMPLETED+309 + Private, // PLAYER_FIELD_QUEST_COMPLETED+310 + Private, // PLAYER_FIELD_QUEST_COMPLETED+311 + Private, // PLAYER_FIELD_QUEST_COMPLETED+312 + Private, // PLAYER_FIELD_QUEST_COMPLETED+313 + Private, // PLAYER_FIELD_QUEST_COMPLETED+314 + Private, // PLAYER_FIELD_QUEST_COMPLETED+315 + Private, // PLAYER_FIELD_QUEST_COMPLETED+316 + Private, // PLAYER_FIELD_QUEST_COMPLETED+317 + Private, // PLAYER_FIELD_QUEST_COMPLETED+318 + Private, // PLAYER_FIELD_QUEST_COMPLETED+319 + Private, // PLAYER_FIELD_QUEST_COMPLETED+320 + Private, // PLAYER_FIELD_QUEST_COMPLETED+321 + Private, // PLAYER_FIELD_QUEST_COMPLETED+322 + Private, // PLAYER_FIELD_QUEST_COMPLETED+323 + Private, // PLAYER_FIELD_QUEST_COMPLETED+324 + Private, // PLAYER_FIELD_QUEST_COMPLETED+325 + Private, // PLAYER_FIELD_QUEST_COMPLETED+326 + Private, // PLAYER_FIELD_QUEST_COMPLETED+327 + Private, // PLAYER_FIELD_QUEST_COMPLETED+328 + Private, // PLAYER_FIELD_QUEST_COMPLETED+329 + Private, // PLAYER_FIELD_QUEST_COMPLETED+330 + Private, // PLAYER_FIELD_QUEST_COMPLETED+331 + Private, // PLAYER_FIELD_QUEST_COMPLETED+332 + Private, // PLAYER_FIELD_QUEST_COMPLETED+333 + Private, // PLAYER_FIELD_QUEST_COMPLETED+334 + Private, // PLAYER_FIELD_QUEST_COMPLETED+335 + Private, // PLAYER_FIELD_QUEST_COMPLETED+336 + Private, // PLAYER_FIELD_QUEST_COMPLETED+337 + Private, // PLAYER_FIELD_QUEST_COMPLETED+338 + Private, // PLAYER_FIELD_QUEST_COMPLETED+339 + Private, // PLAYER_FIELD_QUEST_COMPLETED+340 + Private, // PLAYER_FIELD_QUEST_COMPLETED+341 + Private, // PLAYER_FIELD_QUEST_COMPLETED+342 + Private, // PLAYER_FIELD_QUEST_COMPLETED+343 + Private, // PLAYER_FIELD_QUEST_COMPLETED+344 + Private, // PLAYER_FIELD_QUEST_COMPLETED+345 + Private, // PLAYER_FIELD_QUEST_COMPLETED+346 + Private, // PLAYER_FIELD_QUEST_COMPLETED+347 + Private, // PLAYER_FIELD_QUEST_COMPLETED+348 + Private, // PLAYER_FIELD_QUEST_COMPLETED+349 + Private, // PLAYER_FIELD_QUEST_COMPLETED+350 + Private, // PLAYER_FIELD_QUEST_COMPLETED+351 + Private, // PLAYER_FIELD_QUEST_COMPLETED+352 + Private, // PLAYER_FIELD_QUEST_COMPLETED+353 + Private, // PLAYER_FIELD_QUEST_COMPLETED+354 + Private, // PLAYER_FIELD_QUEST_COMPLETED+355 + Private, // PLAYER_FIELD_QUEST_COMPLETED+356 + Private, // PLAYER_FIELD_QUEST_COMPLETED+357 + Private, // PLAYER_FIELD_QUEST_COMPLETED+358 + Private, // PLAYER_FIELD_QUEST_COMPLETED+359 + Private, // PLAYER_FIELD_QUEST_COMPLETED+360 + Private, // PLAYER_FIELD_QUEST_COMPLETED+361 + Private, // PLAYER_FIELD_QUEST_COMPLETED+362 + Private, // PLAYER_FIELD_QUEST_COMPLETED+363 + Private, // PLAYER_FIELD_QUEST_COMPLETED+364 + Private, // PLAYER_FIELD_QUEST_COMPLETED+365 + Private, // PLAYER_FIELD_QUEST_COMPLETED+366 + Private, // PLAYER_FIELD_QUEST_COMPLETED+367 + Private, // PLAYER_FIELD_QUEST_COMPLETED+368 + Private, // PLAYER_FIELD_QUEST_COMPLETED+369 + Private, // PLAYER_FIELD_QUEST_COMPLETED+370 + Private, // PLAYER_FIELD_QUEST_COMPLETED+371 + Private, // PLAYER_FIELD_QUEST_COMPLETED+372 + Private, // PLAYER_FIELD_QUEST_COMPLETED+373 + Private, // PLAYER_FIELD_QUEST_COMPLETED+374 + Private, // PLAYER_FIELD_QUEST_COMPLETED+375 + Private, // PLAYER_FIELD_QUEST_COMPLETED+376 + Private, // PLAYER_FIELD_QUEST_COMPLETED+377 + Private, // PLAYER_FIELD_QUEST_COMPLETED+378 + Private, // PLAYER_FIELD_QUEST_COMPLETED+379 + Private, // PLAYER_FIELD_QUEST_COMPLETED+380 + Private, // PLAYER_FIELD_QUEST_COMPLETED+381 + Private, // PLAYER_FIELD_QUEST_COMPLETED+382 + Private, // PLAYER_FIELD_QUEST_COMPLETED+383 + Private, // PLAYER_FIELD_QUEST_COMPLETED+384 + Private, // PLAYER_FIELD_QUEST_COMPLETED+385 + Private, // PLAYER_FIELD_QUEST_COMPLETED+386 + Private, // PLAYER_FIELD_QUEST_COMPLETED+387 + Private, // PLAYER_FIELD_QUEST_COMPLETED+388 + Private, // PLAYER_FIELD_QUEST_COMPLETED+389 + Private, // PLAYER_FIELD_QUEST_COMPLETED+390 + Private, // PLAYER_FIELD_QUEST_COMPLETED+391 + Private, // PLAYER_FIELD_QUEST_COMPLETED+392 + Private, // PLAYER_FIELD_QUEST_COMPLETED+393 + Private, // PLAYER_FIELD_QUEST_COMPLETED+394 + Private, // PLAYER_FIELD_QUEST_COMPLETED+395 + Private, // PLAYER_FIELD_QUEST_COMPLETED+396 + Private, // PLAYER_FIELD_QUEST_COMPLETED+397 + Private, // PLAYER_FIELD_QUEST_COMPLETED+398 + Private, // PLAYER_FIELD_QUEST_COMPLETED+399 + Private, // PLAYER_FIELD_QUEST_COMPLETED+400 + Private, // PLAYER_FIELD_QUEST_COMPLETED+401 + Private, // PLAYER_FIELD_QUEST_COMPLETED+402 + Private, // PLAYER_FIELD_QUEST_COMPLETED+403 + Private, // PLAYER_FIELD_QUEST_COMPLETED+404 + Private, // PLAYER_FIELD_QUEST_COMPLETED+405 + Private, // PLAYER_FIELD_QUEST_COMPLETED+406 + Private, // PLAYER_FIELD_QUEST_COMPLETED+407 + Private, // PLAYER_FIELD_QUEST_COMPLETED+408 + Private, // PLAYER_FIELD_QUEST_COMPLETED+409 + Private, // PLAYER_FIELD_QUEST_COMPLETED+410 + Private, // PLAYER_FIELD_QUEST_COMPLETED+411 + Private, // PLAYER_FIELD_QUEST_COMPLETED+412 + Private, // PLAYER_FIELD_QUEST_COMPLETED+413 + Private, // PLAYER_FIELD_QUEST_COMPLETED+414 + Private, // PLAYER_FIELD_QUEST_COMPLETED+415 + Private, // PLAYER_FIELD_QUEST_COMPLETED+416 + Private, // PLAYER_FIELD_QUEST_COMPLETED+417 + Private, // PLAYER_FIELD_QUEST_COMPLETED+418 + Private, // PLAYER_FIELD_QUEST_COMPLETED+419 + Private, // PLAYER_FIELD_QUEST_COMPLETED+420 + Private, // PLAYER_FIELD_QUEST_COMPLETED+421 + Private, // PLAYER_FIELD_QUEST_COMPLETED+422 + Private, // PLAYER_FIELD_QUEST_COMPLETED+423 + Private, // PLAYER_FIELD_QUEST_COMPLETED+424 + Private, // PLAYER_FIELD_QUEST_COMPLETED+425 + Private, // PLAYER_FIELD_QUEST_COMPLETED+426 + Private, // PLAYER_FIELD_QUEST_COMPLETED+427 + Private, // PLAYER_FIELD_QUEST_COMPLETED+428 + Private, // PLAYER_FIELD_QUEST_COMPLETED+429 + Private, // PLAYER_FIELD_QUEST_COMPLETED+430 + Private, // PLAYER_FIELD_QUEST_COMPLETED+431 + Private, // PLAYER_FIELD_QUEST_COMPLETED+432 + Private, // PLAYER_FIELD_QUEST_COMPLETED+433 + Private, // PLAYER_FIELD_QUEST_COMPLETED+434 + Private, // PLAYER_FIELD_QUEST_COMPLETED+435 + Private, // PLAYER_FIELD_QUEST_COMPLETED+436 + Private, // PLAYER_FIELD_QUEST_COMPLETED+437 + Private, // PLAYER_FIELD_QUEST_COMPLETED+438 + Private, // PLAYER_FIELD_QUEST_COMPLETED+439 + Private, // PLAYER_FIELD_QUEST_COMPLETED+440 + Private, // PLAYER_FIELD_QUEST_COMPLETED+441 + Private, // PLAYER_FIELD_QUEST_COMPLETED+442 + Private, // PLAYER_FIELD_QUEST_COMPLETED+443 + Private, // PLAYER_FIELD_QUEST_COMPLETED+444 + Private, // PLAYER_FIELD_QUEST_COMPLETED+445 + Private, // PLAYER_FIELD_QUEST_COMPLETED+446 + Private, // PLAYER_FIELD_QUEST_COMPLETED+447 + Private, // PLAYER_FIELD_QUEST_COMPLETED+448 + Private, // PLAYER_FIELD_QUEST_COMPLETED+449 + Private, // PLAYER_FIELD_QUEST_COMPLETED+450 + Private, // PLAYER_FIELD_QUEST_COMPLETED+451 + Private, // PLAYER_FIELD_QUEST_COMPLETED+452 + Private, // PLAYER_FIELD_QUEST_COMPLETED+453 + Private, // PLAYER_FIELD_QUEST_COMPLETED+454 + Private, // PLAYER_FIELD_QUEST_COMPLETED+455 + Private, // PLAYER_FIELD_QUEST_COMPLETED+456 + Private, // PLAYER_FIELD_QUEST_COMPLETED+457 + Private, // PLAYER_FIELD_QUEST_COMPLETED+458 + Private, // PLAYER_FIELD_QUEST_COMPLETED+459 + Private, // PLAYER_FIELD_QUEST_COMPLETED+460 + Private, // PLAYER_FIELD_QUEST_COMPLETED+461 + Private, // PLAYER_FIELD_QUEST_COMPLETED+462 + Private, // PLAYER_FIELD_QUEST_COMPLETED+463 + Private, // PLAYER_FIELD_QUEST_COMPLETED+464 + Private, // PLAYER_FIELD_QUEST_COMPLETED+465 + Private, // PLAYER_FIELD_QUEST_COMPLETED+466 + Private, // PLAYER_FIELD_QUEST_COMPLETED+467 + Private, // PLAYER_FIELD_QUEST_COMPLETED+468 + Private, // PLAYER_FIELD_QUEST_COMPLETED+469 + Private, // PLAYER_FIELD_QUEST_COMPLETED+470 + Private, // PLAYER_FIELD_QUEST_COMPLETED+471 + Private, // PLAYER_FIELD_QUEST_COMPLETED+472 + Private, // PLAYER_FIELD_QUEST_COMPLETED+473 + Private, // PLAYER_FIELD_QUEST_COMPLETED+474 + Private, // PLAYER_FIELD_QUEST_COMPLETED+475 + Private, // PLAYER_FIELD_QUEST_COMPLETED+476 + Private, // PLAYER_FIELD_QUEST_COMPLETED+477 + Private, // PLAYER_FIELD_QUEST_COMPLETED+478 + Private, // PLAYER_FIELD_QUEST_COMPLETED+479 + Private, // PLAYER_FIELD_QUEST_COMPLETED+480 + Private, // PLAYER_FIELD_QUEST_COMPLETED+481 + Private, // PLAYER_FIELD_QUEST_COMPLETED+482 + Private, // PLAYER_FIELD_QUEST_COMPLETED+483 + Private, // PLAYER_FIELD_QUEST_COMPLETED+484 + Private, // PLAYER_FIELD_QUEST_COMPLETED+485 + Private, // PLAYER_FIELD_QUEST_COMPLETED+486 + Private, // PLAYER_FIELD_QUEST_COMPLETED+487 + Private, // PLAYER_FIELD_QUEST_COMPLETED+488 + Private, // PLAYER_FIELD_QUEST_COMPLETED+489 + Private, // PLAYER_FIELD_QUEST_COMPLETED+490 + Private, // PLAYER_FIELD_QUEST_COMPLETED+491 + Private, // PLAYER_FIELD_QUEST_COMPLETED+492 + Private, // PLAYER_FIELD_QUEST_COMPLETED+493 + Private, // PLAYER_FIELD_QUEST_COMPLETED+494 + Private, // PLAYER_FIELD_QUEST_COMPLETED+495 + Private, // PLAYER_FIELD_QUEST_COMPLETED+496 + Private, // PLAYER_FIELD_QUEST_COMPLETED+497 + Private, // PLAYER_FIELD_QUEST_COMPLETED+498 + Private, // PLAYER_FIELD_QUEST_COMPLETED+499 + Private, // PLAYER_FIELD_QUEST_COMPLETED+500 + Private, // PLAYER_FIELD_QUEST_COMPLETED+501 + Private, // PLAYER_FIELD_QUEST_COMPLETED+502 + Private, // PLAYER_FIELD_QUEST_COMPLETED+503 + Private, // PLAYER_FIELD_QUEST_COMPLETED+504 + Private, // PLAYER_FIELD_QUEST_COMPLETED+505 + Private, // PLAYER_FIELD_QUEST_COMPLETED+506 + Private, // PLAYER_FIELD_QUEST_COMPLETED+507 + Private, // PLAYER_FIELD_QUEST_COMPLETED+508 + Private, // PLAYER_FIELD_QUEST_COMPLETED+509 + Private, // PLAYER_FIELD_QUEST_COMPLETED+510 + Private, // PLAYER_FIELD_QUEST_COMPLETED+511 + Private, // PLAYER_FIELD_QUEST_COMPLETED+512 + Private, // PLAYER_FIELD_QUEST_COMPLETED+513 + Private, // PLAYER_FIELD_QUEST_COMPLETED+514 + Private, // PLAYER_FIELD_QUEST_COMPLETED+515 + Private, // PLAYER_FIELD_QUEST_COMPLETED+516 + Private, // PLAYER_FIELD_QUEST_COMPLETED+517 + Private, // PLAYER_FIELD_QUEST_COMPLETED+518 + Private, // PLAYER_FIELD_QUEST_COMPLETED+519 + Private, // PLAYER_FIELD_QUEST_COMPLETED+520 + Private, // PLAYER_FIELD_QUEST_COMPLETED+521 + Private, // PLAYER_FIELD_QUEST_COMPLETED+522 + Private, // PLAYER_FIELD_QUEST_COMPLETED+523 + Private, // PLAYER_FIELD_QUEST_COMPLETED+524 + Private, // PLAYER_FIELD_QUEST_COMPLETED+525 + Private, // PLAYER_FIELD_QUEST_COMPLETED+526 + Private, // PLAYER_FIELD_QUEST_COMPLETED+527 + Private, // PLAYER_FIELD_QUEST_COMPLETED+528 + Private, // PLAYER_FIELD_QUEST_COMPLETED+529 + Private, // PLAYER_FIELD_QUEST_COMPLETED+530 + Private, // PLAYER_FIELD_QUEST_COMPLETED+531 + Private, // PLAYER_FIELD_QUEST_COMPLETED+532 + Private, // PLAYER_FIELD_QUEST_COMPLETED+533 + Private, // PLAYER_FIELD_QUEST_COMPLETED+534 + Private, // PLAYER_FIELD_QUEST_COMPLETED+535 + Private, // PLAYER_FIELD_QUEST_COMPLETED+536 + Private, // PLAYER_FIELD_QUEST_COMPLETED+537 + Private, // PLAYER_FIELD_QUEST_COMPLETED+538 + Private, // PLAYER_FIELD_QUEST_COMPLETED+539 + Private, // PLAYER_FIELD_QUEST_COMPLETED+540 + Private, // PLAYER_FIELD_QUEST_COMPLETED+541 + Private, // PLAYER_FIELD_QUEST_COMPLETED+542 + Private, // PLAYER_FIELD_QUEST_COMPLETED+543 + Private, // PLAYER_FIELD_QUEST_COMPLETED+544 + Private, // PLAYER_FIELD_QUEST_COMPLETED+545 + Private, // PLAYER_FIELD_QUEST_COMPLETED+546 + Private, // PLAYER_FIELD_QUEST_COMPLETED+547 + Private, // PLAYER_FIELD_QUEST_COMPLETED+548 + Private, // PLAYER_FIELD_QUEST_COMPLETED+549 + Private, // PLAYER_FIELD_QUEST_COMPLETED+550 + Private, // PLAYER_FIELD_QUEST_COMPLETED+551 + Private, // PLAYER_FIELD_QUEST_COMPLETED+552 + Private, // PLAYER_FIELD_QUEST_COMPLETED+553 + Private, // PLAYER_FIELD_QUEST_COMPLETED+554 + Private, // PLAYER_FIELD_QUEST_COMPLETED+555 + Private, // PLAYER_FIELD_QUEST_COMPLETED+556 + Private, // PLAYER_FIELD_QUEST_COMPLETED+557 + Private, // PLAYER_FIELD_QUEST_COMPLETED+558 + Private, // PLAYER_FIELD_QUEST_COMPLETED+559 + Private, // PLAYER_FIELD_QUEST_COMPLETED+560 + Private, // PLAYER_FIELD_QUEST_COMPLETED+561 + Private, // PLAYER_FIELD_QUEST_COMPLETED+562 + Private, // PLAYER_FIELD_QUEST_COMPLETED+563 + Private, // PLAYER_FIELD_QUEST_COMPLETED+564 + Private, // PLAYER_FIELD_QUEST_COMPLETED+565 + Private, // PLAYER_FIELD_QUEST_COMPLETED+566 + Private, // PLAYER_FIELD_QUEST_COMPLETED+567 + Private, // PLAYER_FIELD_QUEST_COMPLETED+568 + Private, // PLAYER_FIELD_QUEST_COMPLETED+569 + Private, // PLAYER_FIELD_QUEST_COMPLETED+570 + Private, // PLAYER_FIELD_QUEST_COMPLETED+571 + Private, // PLAYER_FIELD_QUEST_COMPLETED+572 + Private, // PLAYER_FIELD_QUEST_COMPLETED+573 + Private, // PLAYER_FIELD_QUEST_COMPLETED+574 + Private, // PLAYER_FIELD_QUEST_COMPLETED+575 + Private, // PLAYER_FIELD_QUEST_COMPLETED+576 + Private, // PLAYER_FIELD_QUEST_COMPLETED+577 + Private, // PLAYER_FIELD_QUEST_COMPLETED+578 + Private, // PLAYER_FIELD_QUEST_COMPLETED+579 + Private, // PLAYER_FIELD_QUEST_COMPLETED+580 + Private, // PLAYER_FIELD_QUEST_COMPLETED+581 + Private, // PLAYER_FIELD_QUEST_COMPLETED+582 + Private, // PLAYER_FIELD_QUEST_COMPLETED+583 + Private, // PLAYER_FIELD_QUEST_COMPLETED+584 + Private, // PLAYER_FIELD_QUEST_COMPLETED+585 + Private, // PLAYER_FIELD_QUEST_COMPLETED+586 + Private, // PLAYER_FIELD_QUEST_COMPLETED+587 + Private, // PLAYER_FIELD_QUEST_COMPLETED+588 + Private, // PLAYER_FIELD_QUEST_COMPLETED+589 + Private, // PLAYER_FIELD_QUEST_COMPLETED+590 + Private, // PLAYER_FIELD_QUEST_COMPLETED+591 + Private, // PLAYER_FIELD_QUEST_COMPLETED+592 + Private, // PLAYER_FIELD_QUEST_COMPLETED+593 + Private, // PLAYER_FIELD_QUEST_COMPLETED+594 + Private, // PLAYER_FIELD_QUEST_COMPLETED+595 + Private, // PLAYER_FIELD_QUEST_COMPLETED+596 + Private, // PLAYER_FIELD_QUEST_COMPLETED+597 + Private, // PLAYER_FIELD_QUEST_COMPLETED+598 + Private, // PLAYER_FIELD_QUEST_COMPLETED+599 + Private, // PLAYER_FIELD_QUEST_COMPLETED+600 + Private, // PLAYER_FIELD_QUEST_COMPLETED+601 + Private, // PLAYER_FIELD_QUEST_COMPLETED+602 + Private, // PLAYER_FIELD_QUEST_COMPLETED+603 + Private, // PLAYER_FIELD_QUEST_COMPLETED+604 + Private, // PLAYER_FIELD_QUEST_COMPLETED+605 + Private, // PLAYER_FIELD_QUEST_COMPLETED+606 + Private, // PLAYER_FIELD_QUEST_COMPLETED+607 + Private, // PLAYER_FIELD_QUEST_COMPLETED+608 + Private, // PLAYER_FIELD_QUEST_COMPLETED+609 + Private, // PLAYER_FIELD_QUEST_COMPLETED+610 + Private, // PLAYER_FIELD_QUEST_COMPLETED+611 + Private, // PLAYER_FIELD_QUEST_COMPLETED+612 + Private, // PLAYER_FIELD_QUEST_COMPLETED+613 + Private, // PLAYER_FIELD_QUEST_COMPLETED+614 + Private, // PLAYER_FIELD_QUEST_COMPLETED+615 + Private, // PLAYER_FIELD_QUEST_COMPLETED+616 + Private, // PLAYER_FIELD_QUEST_COMPLETED+617 + Private, // PLAYER_FIELD_QUEST_COMPLETED+618 + Private, // PLAYER_FIELD_QUEST_COMPLETED+619 + Private, // PLAYER_FIELD_QUEST_COMPLETED+620 + Private, // PLAYER_FIELD_QUEST_COMPLETED+621 + Private, // PLAYER_FIELD_QUEST_COMPLETED+622 + Private, // PLAYER_FIELD_QUEST_COMPLETED+623 + Private, // PLAYER_FIELD_QUEST_COMPLETED+624 + Private, // PLAYER_FIELD_QUEST_COMPLETED+625 + Private, // PLAYER_FIELD_QUEST_COMPLETED+626 + Private, // PLAYER_FIELD_QUEST_COMPLETED+627 + Private, // PLAYER_FIELD_QUEST_COMPLETED+628 + Private, // PLAYER_FIELD_QUEST_COMPLETED+629 + Private, // PLAYER_FIELD_QUEST_COMPLETED+630 + Private, // PLAYER_FIELD_QUEST_COMPLETED+631 + Private, // PLAYER_FIELD_QUEST_COMPLETED+632 + Private, // PLAYER_FIELD_QUEST_COMPLETED+633 + Private, // PLAYER_FIELD_QUEST_COMPLETED+634 + Private, // PLAYER_FIELD_QUEST_COMPLETED+635 + Private, // PLAYER_FIELD_QUEST_COMPLETED+636 + Private, // PLAYER_FIELD_QUEST_COMPLETED+637 + Private, // PLAYER_FIELD_QUEST_COMPLETED+638 + Private, // PLAYER_FIELD_QUEST_COMPLETED+639 + Private, // PLAYER_FIELD_QUEST_COMPLETED+640 + Private, // PLAYER_FIELD_QUEST_COMPLETED+641 + Private, // PLAYER_FIELD_QUEST_COMPLETED+642 + Private, // PLAYER_FIELD_QUEST_COMPLETED+643 + Private, // PLAYER_FIELD_QUEST_COMPLETED+644 + Private, // PLAYER_FIELD_QUEST_COMPLETED+645 + Private, // PLAYER_FIELD_QUEST_COMPLETED+646 + Private, // PLAYER_FIELD_QUEST_COMPLETED+647 + Private, // PLAYER_FIELD_QUEST_COMPLETED+648 + Private, // PLAYER_FIELD_QUEST_COMPLETED+649 + Private, // PLAYER_FIELD_QUEST_COMPLETED+650 + Private, // PLAYER_FIELD_QUEST_COMPLETED+651 + Private, // PLAYER_FIELD_QUEST_COMPLETED+652 + Private, // PLAYER_FIELD_QUEST_COMPLETED+653 + Private, // PLAYER_FIELD_QUEST_COMPLETED+654 + Private, // PLAYER_FIELD_QUEST_COMPLETED+655 + Private, // PLAYER_FIELD_QUEST_COMPLETED+656 + Private, // PLAYER_FIELD_QUEST_COMPLETED+657 + Private, // PLAYER_FIELD_QUEST_COMPLETED+658 + Private, // PLAYER_FIELD_QUEST_COMPLETED+659 + Private, // PLAYER_FIELD_QUEST_COMPLETED+660 + Private, // PLAYER_FIELD_QUEST_COMPLETED+661 + Private, // PLAYER_FIELD_QUEST_COMPLETED+662 + Private, // PLAYER_FIELD_QUEST_COMPLETED+663 + Private, // PLAYER_FIELD_QUEST_COMPLETED+664 + Private, // PLAYER_FIELD_QUEST_COMPLETED+665 + Private, // PLAYER_FIELD_QUEST_COMPLETED+666 + Private, // PLAYER_FIELD_QUEST_COMPLETED+667 + Private, // PLAYER_FIELD_QUEST_COMPLETED+668 + Private, // PLAYER_FIELD_QUEST_COMPLETED+669 + Private, // PLAYER_FIELD_QUEST_COMPLETED+670 + Private, // PLAYER_FIELD_QUEST_COMPLETED+671 + Private, // PLAYER_FIELD_QUEST_COMPLETED+672 + Private, // PLAYER_FIELD_QUEST_COMPLETED+673 + Private, // PLAYER_FIELD_QUEST_COMPLETED+674 + Private, // PLAYER_FIELD_QUEST_COMPLETED+675 + Private, // PLAYER_FIELD_QUEST_COMPLETED+676 + Private, // PLAYER_FIELD_QUEST_COMPLETED+677 + Private, // PLAYER_FIELD_QUEST_COMPLETED+678 + Private, // PLAYER_FIELD_QUEST_COMPLETED+679 + Private, // PLAYER_FIELD_QUEST_COMPLETED+680 + Private, // PLAYER_FIELD_QUEST_COMPLETED+681 + Private, // PLAYER_FIELD_QUEST_COMPLETED+682 + Private, // PLAYER_FIELD_QUEST_COMPLETED+683 + Private, // PLAYER_FIELD_QUEST_COMPLETED+684 + Private, // PLAYER_FIELD_QUEST_COMPLETED+685 + Private, // PLAYER_FIELD_QUEST_COMPLETED+686 + Private, // PLAYER_FIELD_QUEST_COMPLETED+687 + Private, // PLAYER_FIELD_QUEST_COMPLETED+688 + Private, // PLAYER_FIELD_QUEST_COMPLETED+689 + Private, // PLAYER_FIELD_QUEST_COMPLETED+690 + Private, // PLAYER_FIELD_QUEST_COMPLETED+691 + Private, // PLAYER_FIELD_QUEST_COMPLETED+692 + Private, // PLAYER_FIELD_QUEST_COMPLETED+693 + Private, // PLAYER_FIELD_QUEST_COMPLETED+694 + Private, // PLAYER_FIELD_QUEST_COMPLETED+695 + Private, // PLAYER_FIELD_QUEST_COMPLETED+696 + Private, // PLAYER_FIELD_QUEST_COMPLETED+697 + Private, // PLAYER_FIELD_QUEST_COMPLETED+698 + Private, // PLAYER_FIELD_QUEST_COMPLETED+699 + Private, // PLAYER_FIELD_QUEST_COMPLETED+700 + Private, // PLAYER_FIELD_QUEST_COMPLETED+701 + Private, // PLAYER_FIELD_QUEST_COMPLETED+702 + Private, // PLAYER_FIELD_QUEST_COMPLETED+703 + Private, // PLAYER_FIELD_QUEST_COMPLETED+704 + Private, // PLAYER_FIELD_QUEST_COMPLETED+705 + Private, // PLAYER_FIELD_QUEST_COMPLETED+706 + Private, // PLAYER_FIELD_QUEST_COMPLETED+707 + Private, // PLAYER_FIELD_QUEST_COMPLETED+708 + Private, // PLAYER_FIELD_QUEST_COMPLETED+709 + Private, // PLAYER_FIELD_QUEST_COMPLETED+710 + Private, // PLAYER_FIELD_QUEST_COMPLETED+711 + Private, // PLAYER_FIELD_QUEST_COMPLETED+712 + Private, // PLAYER_FIELD_QUEST_COMPLETED+713 + Private, // PLAYER_FIELD_QUEST_COMPLETED+714 + Private, // PLAYER_FIELD_QUEST_COMPLETED+715 + Private, // PLAYER_FIELD_QUEST_COMPLETED+716 + Private, // PLAYER_FIELD_QUEST_COMPLETED+717 + Private, // PLAYER_FIELD_QUEST_COMPLETED+718 + Private, // PLAYER_FIELD_QUEST_COMPLETED+719 + Private, // PLAYER_FIELD_QUEST_COMPLETED+720 + Private, // PLAYER_FIELD_QUEST_COMPLETED+721 + Private, // PLAYER_FIELD_QUEST_COMPLETED+722 + Private, // PLAYER_FIELD_QUEST_COMPLETED+723 + Private, // PLAYER_FIELD_QUEST_COMPLETED+724 + Private, // PLAYER_FIELD_QUEST_COMPLETED+725 + Private, // PLAYER_FIELD_QUEST_COMPLETED+726 + Private, // PLAYER_FIELD_QUEST_COMPLETED+727 + Private, // PLAYER_FIELD_QUEST_COMPLETED+728 + Private, // PLAYER_FIELD_QUEST_COMPLETED+729 + Private, // PLAYER_FIELD_QUEST_COMPLETED+730 + Private, // PLAYER_FIELD_QUEST_COMPLETED+731 + Private, // PLAYER_FIELD_QUEST_COMPLETED+732 + Private, // PLAYER_FIELD_QUEST_COMPLETED+733 + Private, // PLAYER_FIELD_QUEST_COMPLETED+734 + Private, // PLAYER_FIELD_QUEST_COMPLETED+735 + Private, // PLAYER_FIELD_QUEST_COMPLETED+736 + Private, // PLAYER_FIELD_QUEST_COMPLETED+737 + Private, // PLAYER_FIELD_QUEST_COMPLETED+738 + Private, // PLAYER_FIELD_QUEST_COMPLETED+739 + Private, // PLAYER_FIELD_QUEST_COMPLETED+740 + Private, // PLAYER_FIELD_QUEST_COMPLETED+741 + Private, // PLAYER_FIELD_QUEST_COMPLETED+742 + Private, // PLAYER_FIELD_QUEST_COMPLETED+743 + Private, // PLAYER_FIELD_QUEST_COMPLETED+744 + Private, // PLAYER_FIELD_QUEST_COMPLETED+745 + Private, // PLAYER_FIELD_QUEST_COMPLETED+746 + Private, // PLAYER_FIELD_QUEST_COMPLETED+747 + Private, // PLAYER_FIELD_QUEST_COMPLETED+748 + Private, // PLAYER_FIELD_QUEST_COMPLETED+749 + Private, // PLAYER_FIELD_QUEST_COMPLETED+750 + Private, // PLAYER_FIELD_QUEST_COMPLETED+751 + Private, // PLAYER_FIELD_QUEST_COMPLETED+752 + Private, // PLAYER_FIELD_QUEST_COMPLETED+753 + Private, // PLAYER_FIELD_QUEST_COMPLETED+754 + Private, // PLAYER_FIELD_QUEST_COMPLETED+755 + Private, // PLAYER_FIELD_QUEST_COMPLETED+756 + Private, // PLAYER_FIELD_QUEST_COMPLETED+757 + Private, // PLAYER_FIELD_QUEST_COMPLETED+758 + Private, // PLAYER_FIELD_QUEST_COMPLETED+759 + Private, // PLAYER_FIELD_QUEST_COMPLETED+760 + Private, // PLAYER_FIELD_QUEST_COMPLETED+761 + Private, // PLAYER_FIELD_QUEST_COMPLETED+762 + Private, // PLAYER_FIELD_QUEST_COMPLETED+763 + Private, // PLAYER_FIELD_QUEST_COMPLETED+764 + Private, // PLAYER_FIELD_QUEST_COMPLETED+765 + Private, // PLAYER_FIELD_QUEST_COMPLETED+766 + Private, // PLAYER_FIELD_QUEST_COMPLETED+767 + Private, // PLAYER_FIELD_QUEST_COMPLETED+768 + Private, // PLAYER_FIELD_QUEST_COMPLETED+769 + Private, // PLAYER_FIELD_QUEST_COMPLETED+770 + Private, // PLAYER_FIELD_QUEST_COMPLETED+771 + Private, // PLAYER_FIELD_QUEST_COMPLETED+772 + Private, // PLAYER_FIELD_QUEST_COMPLETED+773 + Private, // PLAYER_FIELD_QUEST_COMPLETED+774 + Private, // PLAYER_FIELD_QUEST_COMPLETED+775 + Private, // PLAYER_FIELD_QUEST_COMPLETED+776 + Private, // PLAYER_FIELD_QUEST_COMPLETED+777 + Private, // PLAYER_FIELD_QUEST_COMPLETED+778 + Private, // PLAYER_FIELD_QUEST_COMPLETED+779 + Private, // PLAYER_FIELD_QUEST_COMPLETED+780 + Private, // PLAYER_FIELD_QUEST_COMPLETED+781 + Private, // PLAYER_FIELD_QUEST_COMPLETED+782 + Private, // PLAYER_FIELD_QUEST_COMPLETED+783 + Private, // PLAYER_FIELD_QUEST_COMPLETED+784 + Private, // PLAYER_FIELD_QUEST_COMPLETED+785 + Private, // PLAYER_FIELD_QUEST_COMPLETED+786 + Private, // PLAYER_FIELD_QUEST_COMPLETED+787 + Private, // PLAYER_FIELD_QUEST_COMPLETED+788 + Private, // PLAYER_FIELD_QUEST_COMPLETED+789 + Private, // PLAYER_FIELD_QUEST_COMPLETED+790 + Private, // PLAYER_FIELD_QUEST_COMPLETED+791 + Private, // PLAYER_FIELD_QUEST_COMPLETED+792 + Private, // PLAYER_FIELD_QUEST_COMPLETED+793 + Private, // PLAYER_FIELD_QUEST_COMPLETED+794 + Private, // PLAYER_FIELD_QUEST_COMPLETED+795 + Private, // PLAYER_FIELD_QUEST_COMPLETED+796 + Private, // PLAYER_FIELD_QUEST_COMPLETED+797 + Private, // PLAYER_FIELD_QUEST_COMPLETED+798 + Private, // PLAYER_FIELD_QUEST_COMPLETED+799 + Private, // PLAYER_FIELD_QUEST_COMPLETED+800 + Private, // PLAYER_FIELD_QUEST_COMPLETED+801 + Private, // PLAYER_FIELD_QUEST_COMPLETED+802 + Private, // PLAYER_FIELD_QUEST_COMPLETED+803 + Private, // PLAYER_FIELD_QUEST_COMPLETED+804 + Private, // PLAYER_FIELD_QUEST_COMPLETED+805 + Private, // PLAYER_FIELD_QUEST_COMPLETED+806 + Private, // PLAYER_FIELD_QUEST_COMPLETED+807 + Private, // PLAYER_FIELD_QUEST_COMPLETED+808 + Private, // PLAYER_FIELD_QUEST_COMPLETED+809 + Private, // PLAYER_FIELD_QUEST_COMPLETED+810 + Private, // PLAYER_FIELD_QUEST_COMPLETED+811 + Private, // PLAYER_FIELD_QUEST_COMPLETED+812 + Private, // PLAYER_FIELD_QUEST_COMPLETED+813 + Private, // PLAYER_FIELD_QUEST_COMPLETED+814 + Private, // PLAYER_FIELD_QUEST_COMPLETED+815 + Private, // PLAYER_FIELD_QUEST_COMPLETED+816 + Private, // PLAYER_FIELD_QUEST_COMPLETED+817 + Private, // PLAYER_FIELD_QUEST_COMPLETED+818 + Private, // PLAYER_FIELD_QUEST_COMPLETED+819 + Private, // PLAYER_FIELD_QUEST_COMPLETED+820 + Private, // PLAYER_FIELD_QUEST_COMPLETED+821 + Private, // PLAYER_FIELD_QUEST_COMPLETED+822 + Private, // PLAYER_FIELD_QUEST_COMPLETED+823 + Private, // PLAYER_FIELD_QUEST_COMPLETED+824 + Private, // PLAYER_FIELD_QUEST_COMPLETED+825 + Private, // PLAYER_FIELD_QUEST_COMPLETED+826 + Private, // PLAYER_FIELD_QUEST_COMPLETED+827 + Private, // PLAYER_FIELD_QUEST_COMPLETED+828 + Private, // PLAYER_FIELD_QUEST_COMPLETED+829 + Private, // PLAYER_FIELD_QUEST_COMPLETED+830 + Private, // PLAYER_FIELD_QUEST_COMPLETED+831 + Private, // PLAYER_FIELD_QUEST_COMPLETED+832 + Private, // PLAYER_FIELD_QUEST_COMPLETED+833 + Private, // PLAYER_FIELD_QUEST_COMPLETED+834 + Private, // PLAYER_FIELD_QUEST_COMPLETED+835 + Private, // PLAYER_FIELD_QUEST_COMPLETED+836 + Private, // PLAYER_FIELD_QUEST_COMPLETED+837 + Private, // PLAYER_FIELD_QUEST_COMPLETED+838 + Private, // PLAYER_FIELD_QUEST_COMPLETED+839 + Private, // PLAYER_FIELD_QUEST_COMPLETED+840 + Private, // PLAYER_FIELD_QUEST_COMPLETED+841 + Private, // PLAYER_FIELD_QUEST_COMPLETED+842 + Private, // PLAYER_FIELD_QUEST_COMPLETED+843 + Private, // PLAYER_FIELD_QUEST_COMPLETED+844 + Private, // PLAYER_FIELD_QUEST_COMPLETED+845 + Private, // PLAYER_FIELD_QUEST_COMPLETED+846 + Private, // PLAYER_FIELD_QUEST_COMPLETED+847 + Private, // PLAYER_FIELD_QUEST_COMPLETED+848 + Private, // PLAYER_FIELD_QUEST_COMPLETED+849 + Private, // PLAYER_FIELD_QUEST_COMPLETED+850 + Private, // PLAYER_FIELD_QUEST_COMPLETED+851 + Private, // PLAYER_FIELD_QUEST_COMPLETED+852 + Private, // PLAYER_FIELD_QUEST_COMPLETED+853 + Private, // PLAYER_FIELD_QUEST_COMPLETED+854 + Private, // PLAYER_FIELD_QUEST_COMPLETED+855 + Private, // PLAYER_FIELD_QUEST_COMPLETED+856 + Private, // PLAYER_FIELD_QUEST_COMPLETED+857 + Private, // PLAYER_FIELD_QUEST_COMPLETED+858 + Private, // PLAYER_FIELD_QUEST_COMPLETED+859 + Private, // PLAYER_FIELD_QUEST_COMPLETED+860 + Private, // PLAYER_FIELD_QUEST_COMPLETED+861 + Private, // PLAYER_FIELD_QUEST_COMPLETED+862 + Private, // PLAYER_FIELD_QUEST_COMPLETED+863 + Private, // PLAYER_FIELD_QUEST_COMPLETED+864 + Private, // PLAYER_FIELD_QUEST_COMPLETED+865 + Private, // PLAYER_FIELD_QUEST_COMPLETED+866 + Private, // PLAYER_FIELD_QUEST_COMPLETED+867 + Private, // PLAYER_FIELD_QUEST_COMPLETED+868 + Private, // PLAYER_FIELD_QUEST_COMPLETED+869 + Private, // PLAYER_FIELD_QUEST_COMPLETED+870 + Private, // PLAYER_FIELD_QUEST_COMPLETED+871 + Private, // PLAYER_FIELD_QUEST_COMPLETED+872 + Private, // PLAYER_FIELD_QUEST_COMPLETED+873 + Private, // PLAYER_FIELD_QUEST_COMPLETED+874 + Private, // PLAYER_FIELD_QUEST_COMPLETED+875 + Private, // PLAYER_FIELD_QUEST_COMPLETED+876 + Private, // PLAYER_FIELD_QUEST_COMPLETED+877 + Private, // PLAYER_FIELD_QUEST_COMPLETED+878 + Private, // PLAYER_FIELD_QUEST_COMPLETED+879 + Private, // PLAYER_FIELD_QUEST_COMPLETED+880 + Private, // PLAYER_FIELD_QUEST_COMPLETED+881 + Private, // PLAYER_FIELD_QUEST_COMPLETED+882 + Private, // PLAYER_FIELD_QUEST_COMPLETED+883 + Private, // PLAYER_FIELD_QUEST_COMPLETED+884 + Private, // PLAYER_FIELD_QUEST_COMPLETED+885 + Private, // PLAYER_FIELD_QUEST_COMPLETED+886 + Private, // PLAYER_FIELD_QUEST_COMPLETED+887 + Private, // PLAYER_FIELD_QUEST_COMPLETED+888 + Private, // PLAYER_FIELD_QUEST_COMPLETED+889 + Private, // PLAYER_FIELD_QUEST_COMPLETED+890 + Private, // PLAYER_FIELD_QUEST_COMPLETED+891 + Private, // PLAYER_FIELD_QUEST_COMPLETED+892 + Private, // PLAYER_FIELD_QUEST_COMPLETED+893 + Private, // PLAYER_FIELD_QUEST_COMPLETED+894 + Private, // PLAYER_FIELD_QUEST_COMPLETED+895 + Private, // PLAYER_FIELD_QUEST_COMPLETED+896 + Private, // PLAYER_FIELD_QUEST_COMPLETED+897 + Private, // PLAYER_FIELD_QUEST_COMPLETED+898 + Private, // PLAYER_FIELD_QUEST_COMPLETED+899 + Private, // PLAYER_FIELD_QUEST_COMPLETED+900 + Private, // PLAYER_FIELD_QUEST_COMPLETED+901 + Private, // PLAYER_FIELD_QUEST_COMPLETED+902 + Private, // PLAYER_FIELD_QUEST_COMPLETED+903 + Private, // PLAYER_FIELD_QUEST_COMPLETED+904 + Private, // PLAYER_FIELD_QUEST_COMPLETED+905 + Private, // PLAYER_FIELD_QUEST_COMPLETED+906 + Private, // PLAYER_FIELD_QUEST_COMPLETED+907 + Private, // PLAYER_FIELD_QUEST_COMPLETED+908 + Private, // PLAYER_FIELD_QUEST_COMPLETED+909 + Private, // PLAYER_FIELD_QUEST_COMPLETED+910 + Private, // PLAYER_FIELD_QUEST_COMPLETED+911 + Private, // PLAYER_FIELD_QUEST_COMPLETED+912 + Private, // PLAYER_FIELD_QUEST_COMPLETED+913 + Private, // PLAYER_FIELD_QUEST_COMPLETED+914 + Private, // PLAYER_FIELD_QUEST_COMPLETED+915 + Private, // PLAYER_FIELD_QUEST_COMPLETED+916 + Private, // PLAYER_FIELD_QUEST_COMPLETED+917 + Private, // PLAYER_FIELD_QUEST_COMPLETED+918 + Private, // PLAYER_FIELD_QUEST_COMPLETED+919 + Private, // PLAYER_FIELD_QUEST_COMPLETED+920 + Private, // PLAYER_FIELD_QUEST_COMPLETED+921 + Private, // PLAYER_FIELD_QUEST_COMPLETED+922 + Private, // PLAYER_FIELD_QUEST_COMPLETED+923 + Private, // PLAYER_FIELD_QUEST_COMPLETED+924 + Private, // PLAYER_FIELD_QUEST_COMPLETED+925 + Private, // PLAYER_FIELD_QUEST_COMPLETED+926 + Private, // PLAYER_FIELD_QUEST_COMPLETED+927 + Private, // PLAYER_FIELD_QUEST_COMPLETED+928 + Private, // PLAYER_FIELD_QUEST_COMPLETED+929 + Private, // PLAYER_FIELD_QUEST_COMPLETED+930 + Private, // PLAYER_FIELD_QUEST_COMPLETED+931 + Private, // PLAYER_FIELD_QUEST_COMPLETED+932 + Private, // PLAYER_FIELD_QUEST_COMPLETED+933 + Private, // PLAYER_FIELD_QUEST_COMPLETED+934 + Private, // PLAYER_FIELD_QUEST_COMPLETED+935 + Private, // PLAYER_FIELD_QUEST_COMPLETED+936 + Private, // PLAYER_FIELD_QUEST_COMPLETED+937 + Private, // PLAYER_FIELD_QUEST_COMPLETED+938 + Private, // PLAYER_FIELD_QUEST_COMPLETED+939 + Private, // PLAYER_FIELD_QUEST_COMPLETED+940 + Private, // PLAYER_FIELD_QUEST_COMPLETED+941 + Private, // PLAYER_FIELD_QUEST_COMPLETED+942 + Private, // PLAYER_FIELD_QUEST_COMPLETED+943 + Private, // PLAYER_FIELD_QUEST_COMPLETED+944 + Private, // PLAYER_FIELD_QUEST_COMPLETED+945 + Private, // PLAYER_FIELD_QUEST_COMPLETED+946 + Private, // PLAYER_FIELD_QUEST_COMPLETED+947 + Private, // PLAYER_FIELD_QUEST_COMPLETED+948 + Private, // PLAYER_FIELD_QUEST_COMPLETED+949 + Private, // PLAYER_FIELD_QUEST_COMPLETED+950 + Private, // PLAYER_FIELD_QUEST_COMPLETED+951 + Private, // PLAYER_FIELD_QUEST_COMPLETED+952 + Private, // PLAYER_FIELD_QUEST_COMPLETED+953 + Private, // PLAYER_FIELD_QUEST_COMPLETED+954 + Private, // PLAYER_FIELD_QUEST_COMPLETED+955 + Private, // PLAYER_FIELD_QUEST_COMPLETED+956 + Private, // PLAYER_FIELD_QUEST_COMPLETED+957 + Private, // PLAYER_FIELD_QUEST_COMPLETED+958 + Private, // PLAYER_FIELD_QUEST_COMPLETED+959 + Private, // PLAYER_FIELD_QUEST_COMPLETED+960 + Private, // PLAYER_FIELD_QUEST_COMPLETED+961 + Private, // PLAYER_FIELD_QUEST_COMPLETED+962 + Private, // PLAYER_FIELD_QUEST_COMPLETED+963 + Private, // PLAYER_FIELD_QUEST_COMPLETED+964 + Private, // PLAYER_FIELD_QUEST_COMPLETED+965 + Private, // PLAYER_FIELD_QUEST_COMPLETED+966 + Private, // PLAYER_FIELD_QUEST_COMPLETED+967 + Private, // PLAYER_FIELD_QUEST_COMPLETED+968 + Private, // PLAYER_FIELD_QUEST_COMPLETED+969 + Private, // PLAYER_FIELD_QUEST_COMPLETED+970 + Private, // PLAYER_FIELD_QUEST_COMPLETED+971 + Private, // PLAYER_FIELD_QUEST_COMPLETED+972 + Private, // PLAYER_FIELD_QUEST_COMPLETED+973 + Private, // PLAYER_FIELD_QUEST_COMPLETED+974 + Private, // PLAYER_FIELD_QUEST_COMPLETED+975 + Private, // PLAYER_FIELD_QUEST_COMPLETED+976 + Private, // PLAYER_FIELD_QUEST_COMPLETED+977 + Private, // PLAYER_FIELD_QUEST_COMPLETED+978 + Private, // PLAYER_FIELD_QUEST_COMPLETED+979 + Private, // PLAYER_FIELD_QUEST_COMPLETED+980 + Private, // PLAYER_FIELD_QUEST_COMPLETED+981 + Private, // PLAYER_FIELD_QUEST_COMPLETED+982 + Private, // PLAYER_FIELD_QUEST_COMPLETED+983 + Private, // PLAYER_FIELD_QUEST_COMPLETED+984 + Private, // PLAYER_FIELD_QUEST_COMPLETED+985 + Private, // PLAYER_FIELD_QUEST_COMPLETED+986 + Private, // PLAYER_FIELD_QUEST_COMPLETED+987 + Private, // PLAYER_FIELD_QUEST_COMPLETED+988 + Private, // PLAYER_FIELD_QUEST_COMPLETED+989 + Private, // PLAYER_FIELD_QUEST_COMPLETED+990 + Private, // PLAYER_FIELD_QUEST_COMPLETED+991 + Private, // PLAYER_FIELD_QUEST_COMPLETED+992 + Private, // PLAYER_FIELD_QUEST_COMPLETED+993 + Private, // PLAYER_FIELD_QUEST_COMPLETED+994 + Private, // PLAYER_FIELD_QUEST_COMPLETED+995 + Private, // PLAYER_FIELD_QUEST_COMPLETED+996 + Private, // PLAYER_FIELD_QUEST_COMPLETED+997 + Private, // PLAYER_FIELD_QUEST_COMPLETED+998 + Private, // PLAYER_FIELD_QUEST_COMPLETED+999 + Private, // PLAYER_FIELD_QUEST_COMPLETED+1000 + Private, // PLAYER_FIELD_QUEST_COMPLETED+1001 + Private, // PLAYER_FIELD_QUEST_COMPLETED+1002 + Private, // PLAYER_FIELD_QUEST_COMPLETED+1003 + Private, // PLAYER_FIELD_QUEST_COMPLETED+1004 + Private, // PLAYER_FIELD_QUEST_COMPLETED+1005 + Private, // PLAYER_FIELD_QUEST_COMPLETED+1006 + Private, // PLAYER_FIELD_QUEST_COMPLETED+1007 + Private, // PLAYER_FIELD_QUEST_COMPLETED+1008 + Private, // PLAYER_FIELD_QUEST_COMPLETED+1009 + Private, // PLAYER_FIELD_QUEST_COMPLETED+1010 + Private, // PLAYER_FIELD_QUEST_COMPLETED+1011 + Private, // PLAYER_FIELD_QUEST_COMPLETED+1012 + Private, // PLAYER_FIELD_QUEST_COMPLETED+1013 + Private, // PLAYER_FIELD_QUEST_COMPLETED+1014 + Private, // PLAYER_FIELD_QUEST_COMPLETED+1015 + Private, // PLAYER_FIELD_QUEST_COMPLETED+1016 + Private, // PLAYER_FIELD_QUEST_COMPLETED+1017 + Private, // PLAYER_FIELD_QUEST_COMPLETED+1018 + Private, // PLAYER_FIELD_QUEST_COMPLETED+1019 + Private, // PLAYER_FIELD_QUEST_COMPLETED+1020 + Private, // PLAYER_FIELD_QUEST_COMPLETED+1021 + Private, // PLAYER_FIELD_QUEST_COMPLETED+1022 + Private, // PLAYER_FIELD_QUEST_COMPLETED+1023 + Private, // PLAYER_FIELD_QUEST_COMPLETED+1024 + Private, // PLAYER_FIELD_QUEST_COMPLETED+1025 + Private, // PLAYER_FIELD_QUEST_COMPLETED+1026 + Private, // PLAYER_FIELD_QUEST_COMPLETED+1027 + Private, // PLAYER_FIELD_QUEST_COMPLETED+1028 + Private, // PLAYER_FIELD_QUEST_COMPLETED+1029 + Private, // PLAYER_FIELD_QUEST_COMPLETED+1030 + Private, // PLAYER_FIELD_QUEST_COMPLETED+1031 + Private, // PLAYER_FIELD_QUEST_COMPLETED+1032 + Private, // PLAYER_FIELD_QUEST_COMPLETED+1033 + Private, // PLAYER_FIELD_QUEST_COMPLETED+1034 + Private, // PLAYER_FIELD_QUEST_COMPLETED+1035 + Private, // PLAYER_FIELD_QUEST_COMPLETED+1036 + Private, // PLAYER_FIELD_QUEST_COMPLETED+1037 + Private, // PLAYER_FIELD_QUEST_COMPLETED+1038 + Private, // PLAYER_FIELD_QUEST_COMPLETED+1039 + Private, // PLAYER_FIELD_QUEST_COMPLETED+1040 + Private, // PLAYER_FIELD_QUEST_COMPLETED+1041 + Private, // PLAYER_FIELD_QUEST_COMPLETED+1042 + Private, // PLAYER_FIELD_QUEST_COMPLETED+1043 + Private, // PLAYER_FIELD_QUEST_COMPLETED+1044 + Private, // PLAYER_FIELD_QUEST_COMPLETED+1045 + Private, // PLAYER_FIELD_QUEST_COMPLETED+1046 + Private, // PLAYER_FIELD_QUEST_COMPLETED+1047 + Private, // PLAYER_FIELD_QUEST_COMPLETED+1048 + Private, // PLAYER_FIELD_QUEST_COMPLETED+1049 + Private, // PLAYER_FIELD_QUEST_COMPLETED+1050 + Private, // PLAYER_FIELD_QUEST_COMPLETED+1051 + Private, // PLAYER_FIELD_QUEST_COMPLETED+1052 + Private, // PLAYER_FIELD_QUEST_COMPLETED+1053 + Private, // PLAYER_FIELD_QUEST_COMPLETED+1054 + Private, // PLAYER_FIELD_QUEST_COMPLETED+1055 + Private, // PLAYER_FIELD_QUEST_COMPLETED+1056 + Private, // PLAYER_FIELD_QUEST_COMPLETED+1057 + Private, // PLAYER_FIELD_QUEST_COMPLETED+1058 + Private, // PLAYER_FIELD_QUEST_COMPLETED+1059 + Private, // PLAYER_FIELD_QUEST_COMPLETED+1060 + Private, // PLAYER_FIELD_QUEST_COMPLETED+1061 + Private, // PLAYER_FIELD_QUEST_COMPLETED+1062 + Private, // PLAYER_FIELD_QUEST_COMPLETED+1063 + Private, // PLAYER_FIELD_QUEST_COMPLETED+1064 + Private, // PLAYER_FIELD_QUEST_COMPLETED+1065 + Private, // PLAYER_FIELD_QUEST_COMPLETED+1066 + Private, // PLAYER_FIELD_QUEST_COMPLETED+1067 + Private, // PLAYER_FIELD_QUEST_COMPLETED+1068 + Private, // PLAYER_FIELD_QUEST_COMPLETED+1069 + Private, // PLAYER_FIELD_QUEST_COMPLETED+1070 + Private, // PLAYER_FIELD_QUEST_COMPLETED+1071 + Private, // PLAYER_FIELD_QUEST_COMPLETED+1072 + Private, // PLAYER_FIELD_QUEST_COMPLETED+1073 + Private, // PLAYER_FIELD_QUEST_COMPLETED+1074 + Private, // PLAYER_FIELD_QUEST_COMPLETED+1075 + Private, // PLAYER_FIELD_QUEST_COMPLETED+1076 + Private, // PLAYER_FIELD_QUEST_COMPLETED+1077 + Private, // PLAYER_FIELD_QUEST_COMPLETED+1078 + Private, // PLAYER_FIELD_QUEST_COMPLETED+1079 + Private, // PLAYER_FIELD_QUEST_COMPLETED+1080 + Private, // PLAYER_FIELD_QUEST_COMPLETED+1081 + Private, // PLAYER_FIELD_QUEST_COMPLETED+1082 + Private, // PLAYER_FIELD_QUEST_COMPLETED+1083 + Private, // PLAYER_FIELD_QUEST_COMPLETED+1084 + Private, // PLAYER_FIELD_QUEST_COMPLETED+1085 + Private, // PLAYER_FIELD_QUEST_COMPLETED+1086 + Private, // PLAYER_FIELD_QUEST_COMPLETED+1087 + Private, // PLAYER_FIELD_QUEST_COMPLETED+1088 + Private, // PLAYER_FIELD_QUEST_COMPLETED+1089 + Private, // PLAYER_FIELD_QUEST_COMPLETED+1090 + Private, // PLAYER_FIELD_QUEST_COMPLETED+1091 + Private, // PLAYER_FIELD_QUEST_COMPLETED+1092 + Private, // PLAYER_FIELD_QUEST_COMPLETED+1093 + Private, // PLAYER_FIELD_QUEST_COMPLETED+1094 + Private, // PLAYER_FIELD_QUEST_COMPLETED+1095 + Private, // PLAYER_FIELD_QUEST_COMPLETED+1096 + Private, // PLAYER_FIELD_QUEST_COMPLETED+1097 + Private, // PLAYER_FIELD_QUEST_COMPLETED+1098 + Private, // PLAYER_FIELD_QUEST_COMPLETED+1099 + Private, // PLAYER_FIELD_QUEST_COMPLETED+1100 + Private, // PLAYER_FIELD_QUEST_COMPLETED+1101 + Private, // PLAYER_FIELD_QUEST_COMPLETED+1102 + Private, // PLAYER_FIELD_QUEST_COMPLETED+1103 + Private, // PLAYER_FIELD_QUEST_COMPLETED+1104 + Private, // PLAYER_FIELD_QUEST_COMPLETED+1105 + Private, // PLAYER_FIELD_QUEST_COMPLETED+1106 + Private, // PLAYER_FIELD_QUEST_COMPLETED+1107 + Private, // PLAYER_FIELD_QUEST_COMPLETED+1108 + Private, // PLAYER_FIELD_QUEST_COMPLETED+1109 + Private, // PLAYER_FIELD_QUEST_COMPLETED+1110 + Private, // PLAYER_FIELD_QUEST_COMPLETED+1111 + Private, // PLAYER_FIELD_QUEST_COMPLETED+1112 + Private, // PLAYER_FIELD_QUEST_COMPLETED+1113 + Private, // PLAYER_FIELD_QUEST_COMPLETED+1114 + Private, // PLAYER_FIELD_QUEST_COMPLETED+1115 + Private, // PLAYER_FIELD_QUEST_COMPLETED+1116 + Private, // PLAYER_FIELD_QUEST_COMPLETED+1117 + Private, // PLAYER_FIELD_QUEST_COMPLETED+1118 + Private, // PLAYER_FIELD_QUEST_COMPLETED+1119 + Private, // PLAYER_FIELD_QUEST_COMPLETED+1120 + Private, // PLAYER_FIELD_QUEST_COMPLETED+1121 + Private, // PLAYER_FIELD_QUEST_COMPLETED+1122 + Private, // PLAYER_FIELD_QUEST_COMPLETED+1123 + Private, // PLAYER_FIELD_QUEST_COMPLETED+1124 + Private, // PLAYER_FIELD_QUEST_COMPLETED+1125 + Private, // PLAYER_FIELD_QUEST_COMPLETED+1126 + Private, // PLAYER_FIELD_QUEST_COMPLETED+1127 + Private, // PLAYER_FIELD_QUEST_COMPLETED+1128 + Private, // PLAYER_FIELD_QUEST_COMPLETED+1129 + Private, // PLAYER_FIELD_QUEST_COMPLETED+1130 + Private, // PLAYER_FIELD_QUEST_COMPLETED+1131 + Private, // PLAYER_FIELD_QUEST_COMPLETED+1132 + Private, // PLAYER_FIELD_QUEST_COMPLETED+1133 + Private, // PLAYER_FIELD_QUEST_COMPLETED+1134 + Private, // PLAYER_FIELD_QUEST_COMPLETED+1135 + Private, // PLAYER_FIELD_QUEST_COMPLETED+1136 + Private, // PLAYER_FIELD_QUEST_COMPLETED+1137 + Private, // PLAYER_FIELD_QUEST_COMPLETED+1138 + Private, // PLAYER_FIELD_QUEST_COMPLETED+1139 + Private, // PLAYER_FIELD_QUEST_COMPLETED+1140 + Private, // PLAYER_FIELD_QUEST_COMPLETED+1141 + Private, // PLAYER_FIELD_QUEST_COMPLETED+1142 + Private, // PLAYER_FIELD_QUEST_COMPLETED+1143 + Private, // PLAYER_FIELD_QUEST_COMPLETED+1144 + Private, // PLAYER_FIELD_QUEST_COMPLETED+1145 + Private, // PLAYER_FIELD_QUEST_COMPLETED+1146 + Private, // PLAYER_FIELD_QUEST_COMPLETED+1147 + Private, // PLAYER_FIELD_QUEST_COMPLETED+1148 + Private, // PLAYER_FIELD_QUEST_COMPLETED+1149 + Private, // PLAYER_FIELD_QUEST_COMPLETED+1150 + Private, // PLAYER_FIELD_QUEST_COMPLETED+1151 + Private, // PLAYER_FIELD_QUEST_COMPLETED+1152 + Private, // PLAYER_FIELD_QUEST_COMPLETED+1153 + Private, // PLAYER_FIELD_QUEST_COMPLETED+1154 + Private, // PLAYER_FIELD_QUEST_COMPLETED+1155 + Private, // PLAYER_FIELD_QUEST_COMPLETED+1156 + Private, // PLAYER_FIELD_QUEST_COMPLETED+1157 + Private, // PLAYER_FIELD_QUEST_COMPLETED+1158 + Private, // PLAYER_FIELD_QUEST_COMPLETED+1159 + Private, // PLAYER_FIELD_QUEST_COMPLETED+1160 + Private, // PLAYER_FIELD_QUEST_COMPLETED+1161 + Private, // PLAYER_FIELD_QUEST_COMPLETED+1162 + Private, // PLAYER_FIELD_QUEST_COMPLETED+1163 + Private, // PLAYER_FIELD_QUEST_COMPLETED+1164 + Private, // PLAYER_FIELD_QUEST_COMPLETED+1165 + Private, // PLAYER_FIELD_QUEST_COMPLETED+1166 + Private, // PLAYER_FIELD_QUEST_COMPLETED+1167 + Private, // PLAYER_FIELD_QUEST_COMPLETED+1168 + Private, // PLAYER_FIELD_QUEST_COMPLETED+1169 + Private, // PLAYER_FIELD_QUEST_COMPLETED+1170 + Private, // PLAYER_FIELD_QUEST_COMPLETED+1171 + Private, // PLAYER_FIELD_QUEST_COMPLETED+1172 + Private, // PLAYER_FIELD_QUEST_COMPLETED+1173 + Private, // PLAYER_FIELD_QUEST_COMPLETED+1174 + Private, // PLAYER_FIELD_QUEST_COMPLETED+1175 + Private, // PLAYER_FIELD_QUEST_COMPLETED+1176 + Private, // PLAYER_FIELD_QUEST_COMPLETED+1177 + Private, // PLAYER_FIELD_QUEST_COMPLETED+1178 + Private, // PLAYER_FIELD_QUEST_COMPLETED+1179 + Private, // PLAYER_FIELD_QUEST_COMPLETED+1180 + Private, // PLAYER_FIELD_QUEST_COMPLETED+1181 + Private, // PLAYER_FIELD_QUEST_COMPLETED+1182 + Private, // PLAYER_FIELD_QUEST_COMPLETED+1183 + Private, // PLAYER_FIELD_QUEST_COMPLETED+1184 + Private, // PLAYER_FIELD_QUEST_COMPLETED+1185 + Private, // PLAYER_FIELD_QUEST_COMPLETED+1186 + Private, // PLAYER_FIELD_QUEST_COMPLETED+1187 + Private, // PLAYER_FIELD_QUEST_COMPLETED+1188 + Private, // PLAYER_FIELD_QUEST_COMPLETED+1189 + Private, // PLAYER_FIELD_QUEST_COMPLETED+1190 + Private, // PLAYER_FIELD_QUEST_COMPLETED+1191 + Private, // PLAYER_FIELD_QUEST_COMPLETED+1192 + Private, // PLAYER_FIELD_QUEST_COMPLETED+1193 + Private, // PLAYER_FIELD_QUEST_COMPLETED+1194 + Private, // PLAYER_FIELD_QUEST_COMPLETED+1195 + Private, // PLAYER_FIELD_QUEST_COMPLETED+1196 + Private, // PLAYER_FIELD_QUEST_COMPLETED+1197 + Private, // PLAYER_FIELD_QUEST_COMPLETED+1198 + Private, // PLAYER_FIELD_QUEST_COMPLETED+1199 + Private, // PLAYER_FIELD_QUEST_COMPLETED+1200 + Private, // PLAYER_FIELD_QUEST_COMPLETED+1201 + Private, // PLAYER_FIELD_QUEST_COMPLETED+1202 + Private, // PLAYER_FIELD_QUEST_COMPLETED+1203 + Private, // PLAYER_FIELD_QUEST_COMPLETED+1204 + Private, // PLAYER_FIELD_QUEST_COMPLETED+1205 + Private, // PLAYER_FIELD_QUEST_COMPLETED+1206 + Private, // PLAYER_FIELD_QUEST_COMPLETED+1207 + Private, // PLAYER_FIELD_QUEST_COMPLETED+1208 + Private, // PLAYER_FIELD_QUEST_COMPLETED+1209 + Private, // PLAYER_FIELD_QUEST_COMPLETED+1210 + Private, // PLAYER_FIELD_QUEST_COMPLETED+1211 + Private, // PLAYER_FIELD_QUEST_COMPLETED+1212 + Private, // PLAYER_FIELD_QUEST_COMPLETED+1213 + Private, // PLAYER_FIELD_QUEST_COMPLETED+1214 + Private, // PLAYER_FIELD_QUEST_COMPLETED+1215 + Private, // PLAYER_FIELD_QUEST_COMPLETED+1216 + Private, // PLAYER_FIELD_QUEST_COMPLETED+1217 + Private, // PLAYER_FIELD_QUEST_COMPLETED+1218 + Private, // PLAYER_FIELD_QUEST_COMPLETED+1219 + Private, // PLAYER_FIELD_QUEST_COMPLETED+1220 + Private, // PLAYER_FIELD_QUEST_COMPLETED+1221 + Private, // PLAYER_FIELD_QUEST_COMPLETED+1222 + Private, // PLAYER_FIELD_QUEST_COMPLETED+1223 + Private, // PLAYER_FIELD_QUEST_COMPLETED+1224 + Private, // PLAYER_FIELD_QUEST_COMPLETED+1225 + Private, // PLAYER_FIELD_QUEST_COMPLETED+1226 + Private, // PLAYER_FIELD_QUEST_COMPLETED+1227 + Private, // PLAYER_FIELD_QUEST_COMPLETED+1228 + Private, // PLAYER_FIELD_QUEST_COMPLETED+1229 + Private, // PLAYER_FIELD_QUEST_COMPLETED+1230 + Private, // PLAYER_FIELD_QUEST_COMPLETED+1231 + Private, // PLAYER_FIELD_QUEST_COMPLETED+1232 + Private, // PLAYER_FIELD_QUEST_COMPLETED+1233 + Private, // PLAYER_FIELD_QUEST_COMPLETED+1234 + Private, // PLAYER_FIELD_QUEST_COMPLETED+1235 + Private, // PLAYER_FIELD_QUEST_COMPLETED+1236 + Private, // PLAYER_FIELD_QUEST_COMPLETED+1237 + Private, // PLAYER_FIELD_QUEST_COMPLETED+1238 + Private, // PLAYER_FIELD_QUEST_COMPLETED+1239 + Private, // PLAYER_FIELD_QUEST_COMPLETED+1240 + Private, // PLAYER_FIELD_QUEST_COMPLETED+1241 + Private, // PLAYER_FIELD_QUEST_COMPLETED+1242 + Private, // PLAYER_FIELD_QUEST_COMPLETED+1243 + Private, // PLAYER_FIELD_QUEST_COMPLETED+1244 + Private, // PLAYER_FIELD_QUEST_COMPLETED+1245 + Private, // PLAYER_FIELD_QUEST_COMPLETED+1246 + Private, // PLAYER_FIELD_QUEST_COMPLETED+1247 + Private, // PLAYER_FIELD_QUEST_COMPLETED+1248 + Private, // PLAYER_FIELD_QUEST_COMPLETED+1249 + Private, // PLAYER_FIELD_QUEST_COMPLETED+1250 + Private, // PLAYER_FIELD_QUEST_COMPLETED+1251 + Private, // PLAYER_FIELD_QUEST_COMPLETED+1252 + Private, // PLAYER_FIELD_QUEST_COMPLETED+1253 + Private, // PLAYER_FIELD_QUEST_COMPLETED+1254 + Private, // PLAYER_FIELD_QUEST_COMPLETED+1255 + Private, // PLAYER_FIELD_QUEST_COMPLETED+1256 + Private, // PLAYER_FIELD_QUEST_COMPLETED+1257 + Private, // PLAYER_FIELD_QUEST_COMPLETED+1258 + Private, // PLAYER_FIELD_QUEST_COMPLETED+1259 + Private, // PLAYER_FIELD_QUEST_COMPLETED+1260 + Private, // PLAYER_FIELD_QUEST_COMPLETED+1261 + Private, // PLAYER_FIELD_QUEST_COMPLETED+1262 + Private, // PLAYER_FIELD_QUEST_COMPLETED+1263 + Private, // PLAYER_FIELD_QUEST_COMPLETED+1264 + Private, // PLAYER_FIELD_QUEST_COMPLETED+1265 + Private, // PLAYER_FIELD_QUEST_COMPLETED+1266 + Private, // PLAYER_FIELD_QUEST_COMPLETED+1267 + Private, // PLAYER_FIELD_QUEST_COMPLETED+1268 + Private, // PLAYER_FIELD_QUEST_COMPLETED+1269 + Private, // PLAYER_FIELD_QUEST_COMPLETED+1270 + Private, // PLAYER_FIELD_QUEST_COMPLETED+1271 + Private, // PLAYER_FIELD_QUEST_COMPLETED+1272 + Private, // PLAYER_FIELD_QUEST_COMPLETED+1273 + Private, // PLAYER_FIELD_QUEST_COMPLETED+1274 + Private, // PLAYER_FIELD_QUEST_COMPLETED+1275 + Private, // PLAYER_FIELD_QUEST_COMPLETED+1276 + Private, // PLAYER_FIELD_QUEST_COMPLETED+1277 + Private, // PLAYER_FIELD_QUEST_COMPLETED+1278 + Private, // PLAYER_FIELD_QUEST_COMPLETED+1279 + Private, // PLAYER_FIELD_QUEST_COMPLETED+1280 + Private, // PLAYER_FIELD_QUEST_COMPLETED+1281 + Private, // PLAYER_FIELD_QUEST_COMPLETED+1282 + Private, // PLAYER_FIELD_QUEST_COMPLETED+1283 + Private, // PLAYER_FIELD_QUEST_COMPLETED+1284 + Private, // PLAYER_FIELD_QUEST_COMPLETED+1285 + Private, // PLAYER_FIELD_QUEST_COMPLETED+1286 + Private, // PLAYER_FIELD_QUEST_COMPLETED+1287 + Private, // PLAYER_FIELD_QUEST_COMPLETED+1288 + Private, // PLAYER_FIELD_QUEST_COMPLETED+1289 + Private, // PLAYER_FIELD_QUEST_COMPLETED+1290 + Private, // PLAYER_FIELD_QUEST_COMPLETED+1291 + Private, // PLAYER_FIELD_QUEST_COMPLETED+1292 + Private, // PLAYER_FIELD_QUEST_COMPLETED+1293 + Private, // PLAYER_FIELD_QUEST_COMPLETED+1294 + Private, // PLAYER_FIELD_QUEST_COMPLETED+1295 + Private, // PLAYER_FIELD_QUEST_COMPLETED+1296 + Private, // PLAYER_FIELD_QUEST_COMPLETED+1297 + Private, // PLAYER_FIELD_QUEST_COMPLETED+1298 + Private, // PLAYER_FIELD_QUEST_COMPLETED+1299 + Private, // PLAYER_FIELD_QUEST_COMPLETED+1300 + Private, // PLAYER_FIELD_QUEST_COMPLETED+1301 + Private, // PLAYER_FIELD_QUEST_COMPLETED+1302 + Private, // PLAYER_FIELD_QUEST_COMPLETED+1303 + Private, // PLAYER_FIELD_QUEST_COMPLETED+1304 + Private, // PLAYER_FIELD_QUEST_COMPLETED+1305 + Private, // PLAYER_FIELD_QUEST_COMPLETED+1306 + Private, // PLAYER_FIELD_QUEST_COMPLETED+1307 + Private, // PLAYER_FIELD_QUEST_COMPLETED+1308 + Private, // PLAYER_FIELD_QUEST_COMPLETED+1309 + Private, // PLAYER_FIELD_QUEST_COMPLETED+1310 + Private, // PLAYER_FIELD_QUEST_COMPLETED+1311 + Private, // PLAYER_FIELD_QUEST_COMPLETED+1312 + Private, // PLAYER_FIELD_QUEST_COMPLETED+1313 + Private, // PLAYER_FIELD_QUEST_COMPLETED+1314 + Private, // PLAYER_FIELD_QUEST_COMPLETED+1315 + Private, // PLAYER_FIELD_QUEST_COMPLETED+1316 + Private, // PLAYER_FIELD_QUEST_COMPLETED+1317 + Private, // PLAYER_FIELD_QUEST_COMPLETED+1318 + Private, // PLAYER_FIELD_QUEST_COMPLETED+1319 + Private, // PLAYER_FIELD_QUEST_COMPLETED+1320 + Private, // PLAYER_FIELD_QUEST_COMPLETED+1321 + Private, // PLAYER_FIELD_QUEST_COMPLETED+1322 + Private, // PLAYER_FIELD_QUEST_COMPLETED+1323 + Private, // PLAYER_FIELD_QUEST_COMPLETED+1324 + Private, // PLAYER_FIELD_QUEST_COMPLETED+1325 + Private, // PLAYER_FIELD_QUEST_COMPLETED+1326 + Private, // PLAYER_FIELD_QUEST_COMPLETED+1327 + Private, // PLAYER_FIELD_QUEST_COMPLETED+1328 + Private, // PLAYER_FIELD_QUEST_COMPLETED+1329 + Private, // PLAYER_FIELD_QUEST_COMPLETED+1330 + Private, // PLAYER_FIELD_QUEST_COMPLETED+1331 + Private, // PLAYER_FIELD_QUEST_COMPLETED+1332 + Private, // PLAYER_FIELD_QUEST_COMPLETED+1333 + Private, // PLAYER_FIELD_QUEST_COMPLETED+1334 + Private, // PLAYER_FIELD_QUEST_COMPLETED+1335 + Private, // PLAYER_FIELD_QUEST_COMPLETED+1336 + Private, // PLAYER_FIELD_QUEST_COMPLETED+1337 + Private, // PLAYER_FIELD_QUEST_COMPLETED+1338 + Private, // PLAYER_FIELD_QUEST_COMPLETED+1339 + Private, // PLAYER_FIELD_QUEST_COMPLETED+1340 + Private, // PLAYER_FIELD_QUEST_COMPLETED+1341 + Private, // PLAYER_FIELD_QUEST_COMPLETED+1342 + Private, // PLAYER_FIELD_QUEST_COMPLETED+1343 + Private, // PLAYER_FIELD_QUEST_COMPLETED+1344 + Private, // PLAYER_FIELD_QUEST_COMPLETED+1345 + Private, // PLAYER_FIELD_QUEST_COMPLETED+1346 + Private, // PLAYER_FIELD_QUEST_COMPLETED+1347 + Private, // PLAYER_FIELD_QUEST_COMPLETED+1348 + Private, // PLAYER_FIELD_QUEST_COMPLETED+1349 + Private, // PLAYER_FIELD_QUEST_COMPLETED+1350 + Private, // PLAYER_FIELD_QUEST_COMPLETED+1351 + Private, // PLAYER_FIELD_QUEST_COMPLETED+1352 + Private, // PLAYER_FIELD_QUEST_COMPLETED+1353 + Private, // PLAYER_FIELD_QUEST_COMPLETED+1354 + Private, // PLAYER_FIELD_QUEST_COMPLETED+1355 + Private, // PLAYER_FIELD_QUEST_COMPLETED+1356 + Private, // PLAYER_FIELD_QUEST_COMPLETED+1357 + Private, // PLAYER_FIELD_QUEST_COMPLETED+1358 + Private, // PLAYER_FIELD_QUEST_COMPLETED+1359 + Private, // PLAYER_FIELD_QUEST_COMPLETED+1360 + Private, // PLAYER_FIELD_QUEST_COMPLETED+1361 + Private, // PLAYER_FIELD_QUEST_COMPLETED+1362 + Private, // PLAYER_FIELD_QUEST_COMPLETED+1363 + Private, // PLAYER_FIELD_QUEST_COMPLETED+1364 + Private, // PLAYER_FIELD_QUEST_COMPLETED+1365 + Private, // PLAYER_FIELD_QUEST_COMPLETED+1366 + Private, // PLAYER_FIELD_QUEST_COMPLETED+1367 + Private, // PLAYER_FIELD_QUEST_COMPLETED+1368 + Private, // PLAYER_FIELD_QUEST_COMPLETED+1369 + Private, // PLAYER_FIELD_QUEST_COMPLETED+1370 + Private, // PLAYER_FIELD_QUEST_COMPLETED+1371 + Private, // PLAYER_FIELD_QUEST_COMPLETED+1372 + Private, // PLAYER_FIELD_QUEST_COMPLETED+1373 + Private, // PLAYER_FIELD_QUEST_COMPLETED+1374 + Private, // PLAYER_FIELD_QUEST_COMPLETED+1375 + Private, // PLAYER_FIELD_QUEST_COMPLETED+1376 + Private, // PLAYER_FIELD_QUEST_COMPLETED+1377 + Private, // PLAYER_FIELD_QUEST_COMPLETED+1378 + Private, // PLAYER_FIELD_QUEST_COMPLETED+1379 + Private, // PLAYER_FIELD_QUEST_COMPLETED+1380 + Private, // PLAYER_FIELD_QUEST_COMPLETED+1381 + Private, // PLAYER_FIELD_QUEST_COMPLETED+1382 + Private, // PLAYER_FIELD_QUEST_COMPLETED+1383 + Private, // PLAYER_FIELD_QUEST_COMPLETED+1384 + Private, // PLAYER_FIELD_QUEST_COMPLETED+1385 + Private, // PLAYER_FIELD_QUEST_COMPLETED+1386 + Private, // PLAYER_FIELD_QUEST_COMPLETED+1387 + Private, // PLAYER_FIELD_QUEST_COMPLETED+1388 + Private, // PLAYER_FIELD_QUEST_COMPLETED+1389 + Private, // PLAYER_FIELD_QUEST_COMPLETED+1390 + Private, // PLAYER_FIELD_QUEST_COMPLETED+1391 + Private, // PLAYER_FIELD_QUEST_COMPLETED+1392 + Private, // PLAYER_FIELD_QUEST_COMPLETED+1393 + Private, // PLAYER_FIELD_QUEST_COMPLETED+1394 + Private, // PLAYER_FIELD_QUEST_COMPLETED+1395 + Private, // PLAYER_FIELD_QUEST_COMPLETED+1396 + Private, // PLAYER_FIELD_QUEST_COMPLETED+1397 + Private, // PLAYER_FIELD_QUEST_COMPLETED+1398 + Private, // PLAYER_FIELD_QUEST_COMPLETED+1399 + Private, // PLAYER_FIELD_QUEST_COMPLETED+1400 + Private, // PLAYER_FIELD_QUEST_COMPLETED+1401 + Private, // PLAYER_FIELD_QUEST_COMPLETED+1402 + Private, // PLAYER_FIELD_QUEST_COMPLETED+1403 + Private, // PLAYER_FIELD_QUEST_COMPLETED+1404 + Private, // PLAYER_FIELD_QUEST_COMPLETED+1405 + Private, // PLAYER_FIELD_QUEST_COMPLETED+1406 + Private, // PLAYER_FIELD_QUEST_COMPLETED+1407 + Private, // PLAYER_FIELD_QUEST_COMPLETED+1408 + Private, // PLAYER_FIELD_QUEST_COMPLETED+1409 + Private, // PLAYER_FIELD_QUEST_COMPLETED+1410 + Private, // PLAYER_FIELD_QUEST_COMPLETED+1411 + Private, // PLAYER_FIELD_QUEST_COMPLETED+1412 + Private, // PLAYER_FIELD_QUEST_COMPLETED+1413 + Private, // PLAYER_FIELD_QUEST_COMPLETED+1414 + Private, // PLAYER_FIELD_QUEST_COMPLETED+1415 + Private, // PLAYER_FIELD_QUEST_COMPLETED+1416 + Private, // PLAYER_FIELD_QUEST_COMPLETED+1417 + Private, // PLAYER_FIELD_QUEST_COMPLETED+1418 + Private, // PLAYER_FIELD_QUEST_COMPLETED+1419 + Private, // PLAYER_FIELD_QUEST_COMPLETED+1420 + Private, // PLAYER_FIELD_QUEST_COMPLETED+1421 + Private, // PLAYER_FIELD_QUEST_COMPLETED+1422 + Private, // PLAYER_FIELD_QUEST_COMPLETED+1423 + Private, // PLAYER_FIELD_QUEST_COMPLETED+1424 + Private, // PLAYER_FIELD_QUEST_COMPLETED+1425 + Private, // PLAYER_FIELD_QUEST_COMPLETED+1426 + Private, // PLAYER_FIELD_QUEST_COMPLETED+1427 + Private, // PLAYER_FIELD_QUEST_COMPLETED+1428 + Private, // PLAYER_FIELD_QUEST_COMPLETED+1429 + Private, // PLAYER_FIELD_QUEST_COMPLETED+1430 + Private, // PLAYER_FIELD_QUEST_COMPLETED+1431 + Private, // PLAYER_FIELD_QUEST_COMPLETED+1432 + Private, // PLAYER_FIELD_QUEST_COMPLETED+1433 + Private, // PLAYER_FIELD_QUEST_COMPLETED+1434 + Private, // PLAYER_FIELD_QUEST_COMPLETED+1435 + Private, // PLAYER_FIELD_QUEST_COMPLETED+1436 + Private, // PLAYER_FIELD_QUEST_COMPLETED+1437 + Private, // PLAYER_FIELD_QUEST_COMPLETED+1438 + Private, // PLAYER_FIELD_QUEST_COMPLETED+1439 + Private, // PLAYER_FIELD_QUEST_COMPLETED+1440 + Private, // PLAYER_FIELD_QUEST_COMPLETED+1441 + Private, // PLAYER_FIELD_QUEST_COMPLETED+1442 + Private, // PLAYER_FIELD_QUEST_COMPLETED+1443 + Private, // PLAYER_FIELD_QUEST_COMPLETED+1444 + Private, // PLAYER_FIELD_QUEST_COMPLETED+1445 + Private, // PLAYER_FIELD_QUEST_COMPLETED+1446 + Private, // PLAYER_FIELD_QUEST_COMPLETED+1447 + Private, // PLAYER_FIELD_QUEST_COMPLETED+1448 + Private, // PLAYER_FIELD_QUEST_COMPLETED+1449 + Private, // PLAYER_FIELD_QUEST_COMPLETED+1450 + Private, // PLAYER_FIELD_QUEST_COMPLETED+1451 + Private, // PLAYER_FIELD_QUEST_COMPLETED+1452 + Private, // PLAYER_FIELD_QUEST_COMPLETED+1453 + Private, // PLAYER_FIELD_QUEST_COMPLETED+1454 + Private, // PLAYER_FIELD_QUEST_COMPLETED+1455 + Private, // PLAYER_FIELD_QUEST_COMPLETED+1456 + Private, // PLAYER_FIELD_QUEST_COMPLETED+1457 + Private, // PLAYER_FIELD_QUEST_COMPLETED+1458 + Private, // PLAYER_FIELD_QUEST_COMPLETED+1459 + Private, // PLAYER_FIELD_QUEST_COMPLETED+1460 + Private, // PLAYER_FIELD_QUEST_COMPLETED+1461 + Private, // PLAYER_FIELD_QUEST_COMPLETED+1462 + Private, // PLAYER_FIELD_QUEST_COMPLETED+1463 + Private, // PLAYER_FIELD_QUEST_COMPLETED+1464 + Private, // PLAYER_FIELD_QUEST_COMPLETED+1465 + Private, // PLAYER_FIELD_QUEST_COMPLETED+1466 + Private, // PLAYER_FIELD_QUEST_COMPLETED+1467 + Private, // PLAYER_FIELD_QUEST_COMPLETED+1468 + Private, // PLAYER_FIELD_QUEST_COMPLETED+1469 + Private, // PLAYER_FIELD_QUEST_COMPLETED+1470 + Private, // PLAYER_FIELD_QUEST_COMPLETED+1471 + Private, // PLAYER_FIELD_QUEST_COMPLETED+1472 + Private, // PLAYER_FIELD_QUEST_COMPLETED+1473 + Private, // PLAYER_FIELD_QUEST_COMPLETED+1474 + Private, // PLAYER_FIELD_QUEST_COMPLETED+1475 + Private, // PLAYER_FIELD_QUEST_COMPLETED+1476 + Private, // PLAYER_FIELD_QUEST_COMPLETED+1477 + Private, // PLAYER_FIELD_QUEST_COMPLETED+1478 + Private, // PLAYER_FIELD_QUEST_COMPLETED+1479 + Private, // PLAYER_FIELD_QUEST_COMPLETED+1480 + Private, // PLAYER_FIELD_QUEST_COMPLETED+1481 + Private, // PLAYER_FIELD_QUEST_COMPLETED+1482 + Private, // PLAYER_FIELD_QUEST_COMPLETED+1483 + Private, // PLAYER_FIELD_QUEST_COMPLETED+1484 + Private, // PLAYER_FIELD_QUEST_COMPLETED+1485 + Private, // PLAYER_FIELD_QUEST_COMPLETED+1486 + Private, // PLAYER_FIELD_QUEST_COMPLETED+1487 + Private, // PLAYER_FIELD_QUEST_COMPLETED+1488 + Private, // PLAYER_FIELD_QUEST_COMPLETED+1489 + Private, // PLAYER_FIELD_QUEST_COMPLETED+1490 + Private, // PLAYER_FIELD_QUEST_COMPLETED+1491 + Private, // PLAYER_FIELD_QUEST_COMPLETED+1492 + Private, // PLAYER_FIELD_QUEST_COMPLETED+1493 + Private, // PLAYER_FIELD_QUEST_COMPLETED+1494 + Private, // PLAYER_FIELD_QUEST_COMPLETED+1495 + Private, // PLAYER_FIELD_QUEST_COMPLETED+1496 + Private, // PLAYER_FIELD_QUEST_COMPLETED+1497 + Private, // PLAYER_FIELD_QUEST_COMPLETED+1498 + Private, // PLAYER_FIELD_QUEST_COMPLETED+1499 + Private, // PLAYER_FIELD_QUEST_COMPLETED+1500 + Private, // PLAYER_FIELD_QUEST_COMPLETED+1501 + Private, // PLAYER_FIELD_QUEST_COMPLETED+1502 + Private, // PLAYER_FIELD_QUEST_COMPLETED+1503 + Private, // PLAYER_FIELD_QUEST_COMPLETED+1504 + Private, // PLAYER_FIELD_QUEST_COMPLETED+1505 + Private, // PLAYER_FIELD_QUEST_COMPLETED+1506 + Private, // PLAYER_FIELD_QUEST_COMPLETED+1507 + Private, // PLAYER_FIELD_QUEST_COMPLETED+1508 + Private, // PLAYER_FIELD_QUEST_COMPLETED+1509 + Private, // PLAYER_FIELD_QUEST_COMPLETED+1510 + Private, // PLAYER_FIELD_QUEST_COMPLETED+1511 + Private, // PLAYER_FIELD_QUEST_COMPLETED+1512 + Private, // PLAYER_FIELD_QUEST_COMPLETED+1513 + Private, // PLAYER_FIELD_QUEST_COMPLETED+1514 + Private, // PLAYER_FIELD_QUEST_COMPLETED+1515 + Private, // PLAYER_FIELD_QUEST_COMPLETED+1516 + Private, // PLAYER_FIELD_QUEST_COMPLETED+1517 + Private, // PLAYER_FIELD_QUEST_COMPLETED+1518 + Private, // PLAYER_FIELD_QUEST_COMPLETED+1519 + Private, // PLAYER_FIELD_QUEST_COMPLETED+1520 + Private, // PLAYER_FIELD_QUEST_COMPLETED+1521 + Private, // PLAYER_FIELD_QUEST_COMPLETED+1522 + Private, // PLAYER_FIELD_QUEST_COMPLETED+1523 + Private, // PLAYER_FIELD_QUEST_COMPLETED+1524 + Private, // PLAYER_FIELD_QUEST_COMPLETED+1525 + Private, // PLAYER_FIELD_QUEST_COMPLETED+1526 + Private, // PLAYER_FIELD_QUEST_COMPLETED+1527 + Private, // PLAYER_FIELD_QUEST_COMPLETED+1528 + Private, // PLAYER_FIELD_QUEST_COMPLETED+1529 + Private, // PLAYER_FIELD_QUEST_COMPLETED+1530 + Private, // PLAYER_FIELD_QUEST_COMPLETED+1531 + Private, // PLAYER_FIELD_QUEST_COMPLETED+1532 + Private, // PLAYER_FIELD_QUEST_COMPLETED+1533 + Private, // PLAYER_FIELD_QUEST_COMPLETED+1534 + Private, // PLAYER_FIELD_QUEST_COMPLETED+1535 + Private, // PLAYER_FIELD_QUEST_COMPLETED+1536 + Private, // PLAYER_FIELD_QUEST_COMPLETED+1537 + Private, // PLAYER_FIELD_QUEST_COMPLETED+1538 + Private, // PLAYER_FIELD_QUEST_COMPLETED+1539 + Private, // PLAYER_FIELD_QUEST_COMPLETED+1540 + Private, // PLAYER_FIELD_QUEST_COMPLETED+1541 + Private, // PLAYER_FIELD_QUEST_COMPLETED+1542 + Private, // PLAYER_FIELD_QUEST_COMPLETED+1543 + Private, // PLAYER_FIELD_QUEST_COMPLETED+1544 + Private, // PLAYER_FIELD_QUEST_COMPLETED+1545 + Private, // PLAYER_FIELD_QUEST_COMPLETED+1546 + Private, // PLAYER_FIELD_QUEST_COMPLETED+1547 + Private, // PLAYER_FIELD_QUEST_COMPLETED+1548 + Private, // PLAYER_FIELD_QUEST_COMPLETED+1549 + Private, // PLAYER_FIELD_QUEST_COMPLETED+1550 + Private, // PLAYER_FIELD_QUEST_COMPLETED+1551 + Private, // PLAYER_FIELD_QUEST_COMPLETED+1552 + Private, // PLAYER_FIELD_QUEST_COMPLETED+1553 + Private, // PLAYER_FIELD_QUEST_COMPLETED+1554 + Private, // PLAYER_FIELD_QUEST_COMPLETED+1555 + Private, // PLAYER_FIELD_QUEST_COMPLETED+1556 + Private, // PLAYER_FIELD_QUEST_COMPLETED+1557 + Private, // PLAYER_FIELD_QUEST_COMPLETED+1558 + Private, // PLAYER_FIELD_QUEST_COMPLETED+1559 + Private, // PLAYER_FIELD_QUEST_COMPLETED+1560 + Private, // PLAYER_FIELD_QUEST_COMPLETED+1561 + Private, // PLAYER_FIELD_QUEST_COMPLETED+1562 + Private, // PLAYER_FIELD_QUEST_COMPLETED+1563 + Private, // PLAYER_FIELD_QUEST_COMPLETED+1564 + Private, // PLAYER_FIELD_QUEST_COMPLETED+1565 + Private, // PLAYER_FIELD_QUEST_COMPLETED+1566 + Private, // PLAYER_FIELD_QUEST_COMPLETED+1567 + Private, // PLAYER_FIELD_QUEST_COMPLETED+1568 + Private, // PLAYER_FIELD_QUEST_COMPLETED+1569 + Private, // PLAYER_FIELD_QUEST_COMPLETED+1570 + Private, // PLAYER_FIELD_QUEST_COMPLETED+1571 + Private, // PLAYER_FIELD_QUEST_COMPLETED+1572 + Private, // PLAYER_FIELD_QUEST_COMPLETED+1573 + Private, // PLAYER_FIELD_QUEST_COMPLETED+1574 + Private, // PLAYER_FIELD_QUEST_COMPLETED+1575 + Private, // PLAYER_FIELD_QUEST_COMPLETED+1576 + Private, // PLAYER_FIELD_QUEST_COMPLETED+1577 + Private, // PLAYER_FIELD_QUEST_COMPLETED+1578 + Private, // PLAYER_FIELD_QUEST_COMPLETED+1579 + Private, // PLAYER_FIELD_QUEST_COMPLETED+1580 + Private, // PLAYER_FIELD_QUEST_COMPLETED+1581 + Private, // PLAYER_FIELD_QUEST_COMPLETED+1582 + Private, // PLAYER_FIELD_QUEST_COMPLETED+1583 + Private, // PLAYER_FIELD_QUEST_COMPLETED+1584 + Private, // PLAYER_FIELD_QUEST_COMPLETED+1585 + Private, // PLAYER_FIELD_QUEST_COMPLETED+1586 + Private, // PLAYER_FIELD_QUEST_COMPLETED+1587 + Private, // PLAYER_FIELD_QUEST_COMPLETED+1588 + Private, // PLAYER_FIELD_QUEST_COMPLETED+1589 + Private, // PLAYER_FIELD_QUEST_COMPLETED+1590 + Private, // PLAYER_FIELD_QUEST_COMPLETED+1591 + Private, // PLAYER_FIELD_QUEST_COMPLETED+1592 + Private, // PLAYER_FIELD_QUEST_COMPLETED+1593 + Private, // PLAYER_FIELD_QUEST_COMPLETED+1594 + Private, // PLAYER_FIELD_QUEST_COMPLETED+1595 + Private, // PLAYER_FIELD_QUEST_COMPLETED+1596 + Private, // PLAYER_FIELD_QUEST_COMPLETED+1597 + Private, // PLAYER_FIELD_QUEST_COMPLETED+1598 + Private, // PLAYER_FIELD_QUEST_COMPLETED+1599 + Private, // PLAYER_FIELD_QUEST_COMPLETED+1600 + Private, // PLAYER_FIELD_QUEST_COMPLETED+1601 + Private, // PLAYER_FIELD_QUEST_COMPLETED+1602 + Private, // PLAYER_FIELD_QUEST_COMPLETED+1603 + Private, // PLAYER_FIELD_QUEST_COMPLETED+1604 + Private, // PLAYER_FIELD_QUEST_COMPLETED+1605 + Private, // PLAYER_FIELD_QUEST_COMPLETED+1606 + Private, // PLAYER_FIELD_QUEST_COMPLETED+1607 + Private, // PLAYER_FIELD_QUEST_COMPLETED+1608 + Private, // PLAYER_FIELD_QUEST_COMPLETED+1609 + Private, // PLAYER_FIELD_QUEST_COMPLETED+1610 + Private, // PLAYER_FIELD_QUEST_COMPLETED+1611 + Private, // PLAYER_FIELD_QUEST_COMPLETED+1612 + Private, // PLAYER_FIELD_QUEST_COMPLETED+1613 + Private, // PLAYER_FIELD_QUEST_COMPLETED+1614 + Private, // PLAYER_FIELD_QUEST_COMPLETED+1615 + Private, // PLAYER_FIELD_QUEST_COMPLETED+1616 + Private, // PLAYER_FIELD_QUEST_COMPLETED+1617 + Private, // PLAYER_FIELD_QUEST_COMPLETED+1618 + Private, // PLAYER_FIELD_QUEST_COMPLETED+1619 + Private, // PLAYER_FIELD_QUEST_COMPLETED+1620 + Private, // PLAYER_FIELD_QUEST_COMPLETED+1621 + Private, // PLAYER_FIELD_QUEST_COMPLETED+1622 + Private, // PLAYER_FIELD_QUEST_COMPLETED+1623 + Private, // PLAYER_FIELD_QUEST_COMPLETED+1624 + Private, // PLAYER_FIELD_QUEST_COMPLETED+1625 + Private, // PLAYER_FIELD_QUEST_COMPLETED+1626 + Private, // PLAYER_FIELD_QUEST_COMPLETED+1627 + Private, // PLAYER_FIELD_QUEST_COMPLETED+1628 + Private, // PLAYER_FIELD_QUEST_COMPLETED+1629 + Private, // PLAYER_FIELD_QUEST_COMPLETED+1630 + Private, // PLAYER_FIELD_QUEST_COMPLETED+1631 + Private, // PLAYER_FIELD_QUEST_COMPLETED+1632 + Private, // PLAYER_FIELD_QUEST_COMPLETED+1633 + Private, // PLAYER_FIELD_QUEST_COMPLETED+1634 + Private, // PLAYER_FIELD_QUEST_COMPLETED+1635 + Private, // PLAYER_FIELD_QUEST_COMPLETED+1636 + Private, // PLAYER_FIELD_QUEST_COMPLETED+1637 + Private, // PLAYER_FIELD_QUEST_COMPLETED+1638 + Private, // PLAYER_FIELD_QUEST_COMPLETED+1639 + Private, // PLAYER_FIELD_QUEST_COMPLETED+1640 + Private, // PLAYER_FIELD_QUEST_COMPLETED+1641 + Private, // PLAYER_FIELD_QUEST_COMPLETED+1642 + Private, // PLAYER_FIELD_QUEST_COMPLETED+1643 + Private, // PLAYER_FIELD_QUEST_COMPLETED+1644 + Private, // PLAYER_FIELD_QUEST_COMPLETED+1645 + Private, // PLAYER_FIELD_QUEST_COMPLETED+1646 + Private, // PLAYER_FIELD_QUEST_COMPLETED+1647 + Private, // PLAYER_FIELD_QUEST_COMPLETED+1648 + Private, // PLAYER_FIELD_QUEST_COMPLETED+1649 + Private, // PLAYER_FIELD_QUEST_COMPLETED+1650 + Private, // PLAYER_FIELD_QUEST_COMPLETED+1651 + Private, // PLAYER_FIELD_QUEST_COMPLETED+1652 + Private, // PLAYER_FIELD_QUEST_COMPLETED+1653 + Private, // PLAYER_FIELD_QUEST_COMPLETED+1654 + Private, // PLAYER_FIELD_QUEST_COMPLETED+1655 + Private, // PLAYER_FIELD_QUEST_COMPLETED+1656 + Private, // PLAYER_FIELD_QUEST_COMPLETED+1657 + Private, // PLAYER_FIELD_QUEST_COMPLETED+1658 + Private, // PLAYER_FIELD_QUEST_COMPLETED+1659 + Private, // PLAYER_FIELD_QUEST_COMPLETED+1660 + Private, // PLAYER_FIELD_QUEST_COMPLETED+1661 + Private, // PLAYER_FIELD_QUEST_COMPLETED+1662 + Private, // PLAYER_FIELD_QUEST_COMPLETED+1663 + Private, // PLAYER_FIELD_QUEST_COMPLETED+1664 + Private, // PLAYER_FIELD_QUEST_COMPLETED+1665 + Private, // PLAYER_FIELD_QUEST_COMPLETED+1666 + Private, // PLAYER_FIELD_QUEST_COMPLETED+1667 + Private, // PLAYER_FIELD_QUEST_COMPLETED+1668 + Private, // PLAYER_FIELD_QUEST_COMPLETED+1669 + Private, // PLAYER_FIELD_QUEST_COMPLETED+1670 + Private, // PLAYER_FIELD_QUEST_COMPLETED+1671 + Private, // PLAYER_FIELD_QUEST_COMPLETED+1672 + Private, // PLAYER_FIELD_QUEST_COMPLETED+1673 + Private, // PLAYER_FIELD_QUEST_COMPLETED+1674 + Private, // PLAYER_FIELD_QUEST_COMPLETED+1675 + Private, // PLAYER_FIELD_QUEST_COMPLETED+1676 + Private, // PLAYER_FIELD_QUEST_COMPLETED+1677 + Private, // PLAYER_FIELD_QUEST_COMPLETED+1678 + Private, // PLAYER_FIELD_QUEST_COMPLETED+1679 + Private, // PLAYER_FIELD_QUEST_COMPLETED+1680 + Private, // PLAYER_FIELD_QUEST_COMPLETED+1681 + Private, // PLAYER_FIELD_QUEST_COMPLETED+1682 + Private, // PLAYER_FIELD_QUEST_COMPLETED+1683 + Private, // PLAYER_FIELD_QUEST_COMPLETED+1684 + Private, // PLAYER_FIELD_QUEST_COMPLETED+1685 + Private, // PLAYER_FIELD_QUEST_COMPLETED+1686 + Private, // PLAYER_FIELD_QUEST_COMPLETED+1687 + Private, // PLAYER_FIELD_QUEST_COMPLETED+1688 + Private, // PLAYER_FIELD_QUEST_COMPLETED+1689 + Private, // PLAYER_FIELD_QUEST_COMPLETED+1690 + Private, // PLAYER_FIELD_QUEST_COMPLETED+1691 + Private, // PLAYER_FIELD_QUEST_COMPLETED+1692 + Private, // PLAYER_FIELD_QUEST_COMPLETED+1693 + Private, // PLAYER_FIELD_QUEST_COMPLETED+1694 + Private, // PLAYER_FIELD_QUEST_COMPLETED+1695 + Private, // PLAYER_FIELD_QUEST_COMPLETED+1696 + Private, // PLAYER_FIELD_QUEST_COMPLETED+1697 + Private, // PLAYER_FIELD_QUEST_COMPLETED+1698 + Private, // PLAYER_FIELD_QUEST_COMPLETED+1699 + Private, // PLAYER_FIELD_QUEST_COMPLETED+1700 + Private, // PLAYER_FIELD_QUEST_COMPLETED+1701 + Private, // PLAYER_FIELD_QUEST_COMPLETED+1702 + Private, // PLAYER_FIELD_QUEST_COMPLETED+1703 + Private, // PLAYER_FIELD_QUEST_COMPLETED+1704 + Private, // PLAYER_FIELD_QUEST_COMPLETED+1705 + Private, // PLAYER_FIELD_QUEST_COMPLETED+1706 + Private, // PLAYER_FIELD_QUEST_COMPLETED+1707 + Private, // PLAYER_FIELD_QUEST_COMPLETED+1708 + Private, // PLAYER_FIELD_QUEST_COMPLETED+1709 + Private, // PLAYER_FIELD_QUEST_COMPLETED+1710 + Private, // PLAYER_FIELD_QUEST_COMPLETED+1711 + Private, // PLAYER_FIELD_QUEST_COMPLETED+1712 + Private, // PLAYER_FIELD_QUEST_COMPLETED+1713 + Private, // PLAYER_FIELD_QUEST_COMPLETED+1714 + Private, // PLAYER_FIELD_QUEST_COMPLETED+1715 + Private, // PLAYER_FIELD_QUEST_COMPLETED+1716 + Private, // PLAYER_FIELD_QUEST_COMPLETED+1717 + Private, // PLAYER_FIELD_QUEST_COMPLETED+1718 + Private, // PLAYER_FIELD_QUEST_COMPLETED+1719 + Private, // PLAYER_FIELD_QUEST_COMPLETED+1720 + Private, // PLAYER_FIELD_QUEST_COMPLETED+1721 + Private, // PLAYER_FIELD_QUEST_COMPLETED+1722 + Private, // PLAYER_FIELD_QUEST_COMPLETED+1723 + Private, // PLAYER_FIELD_QUEST_COMPLETED+1724 + Private, // PLAYER_FIELD_QUEST_COMPLETED+1725 + Private, // PLAYER_FIELD_QUEST_COMPLETED+1726 + Private, // PLAYER_FIELD_QUEST_COMPLETED+1727 + Private, // PLAYER_FIELD_QUEST_COMPLETED+1728 + Private, // PLAYER_FIELD_QUEST_COMPLETED+1729 + Private, // PLAYER_FIELD_QUEST_COMPLETED+1730 + Private, // PLAYER_FIELD_QUEST_COMPLETED+1731 + Private, // PLAYER_FIELD_QUEST_COMPLETED+1732 + Private, // PLAYER_FIELD_QUEST_COMPLETED+1733 + Private, // PLAYER_FIELD_QUEST_COMPLETED+1734 + Private, // PLAYER_FIELD_QUEST_COMPLETED+1735 + Private, // PLAYER_FIELD_QUEST_COMPLETED+1736 + Private, // PLAYER_FIELD_QUEST_COMPLETED+1737 + Private, // PLAYER_FIELD_QUEST_COMPLETED+1738 + Private, // PLAYER_FIELD_QUEST_COMPLETED+1739 + Private, // PLAYER_FIELD_QUEST_COMPLETED+1740 + Private, // PLAYER_FIELD_QUEST_COMPLETED+1741 + Private, // PLAYER_FIELD_QUEST_COMPLETED+1742 + Private, // PLAYER_FIELD_QUEST_COMPLETED+1743 + Private, // PLAYER_FIELD_QUEST_COMPLETED+1744 + Private, // PLAYER_FIELD_QUEST_COMPLETED+1745 + Private, // PLAYER_FIELD_QUEST_COMPLETED+1746 + Private, // PLAYER_FIELD_QUEST_COMPLETED+1747 + Private, // PLAYER_FIELD_QUEST_COMPLETED+1748 + Private, // PLAYER_FIELD_QUEST_COMPLETED+1749 + Private, // PLAYER_FIELD_HONOR + Private, // PLAYER_FIELD_HONOR_NEXT_LEVEL + }; + + public static uint[] UnitDynamicUpdateFieldFlags = new uint[(int)PlayerDynamicFields.End] + { + Public | Urgent, // UNIT_DYNAMIC_FIELD_PASSIVE_SPELLS + Public | Urgent, // UNIT_DYNAMIC_FIELD_WORLD_EFFECTS + Public | Urgent, // UNIT_DYNAMIC_FIELD_CHANNEL_OBJECTS + Private, // PLAYER_DYNAMIC_FIELD_RESERACH_SITE + Private, // PLAYER_DYNAMIC_FIELD_RESEARCH_SITE_PROGRESS + Private, // PLAYER_DYNAMIC_FIELD_DAILY_QUESTS + Private, // PLAYER_DYNAMIC_FIELD_AVAILABLE_QUEST_LINE_X_QUEST_ID + Private, // PLAYER_DYNAMIC_FIELD_HEIRLOOMS + Private, // PLAYER_DYNAMIC_FIELD_HEIRLOOM_FLAGS + Private, // PLAYER_DYNAMIC_FIELD_TOYS + Private, // PLAYER_DYNAMIC_FIELD_TRANSMOG + Private, // PLAYER_DYNAMIC_FIELD_CONDITIONAL_TRANSMOG + Private, // PLAYER_DYNAMIC_FIELD_CHARACTER_RESTRICTIONS + Private, // PLAYER_DYNAMIC_FIELD_SPELL_PCT_MOD_BY_LABEL + Private, // PLAYER_DYNAMIC_FIELD_SPELL_FLAT_MOD_BY_LABEL + Public, // PLAYER_DYNAMIC_FIELD_ARENA_COOLDOWNS + }; + + public static uint[] GameObjectUpdateFieldFlags = new uint[(int)GameObjectFields.End] + { + Public, // OBJECT_FIELD_GUID + Public, // OBJECT_FIELD_GUID+1 + Public, // OBJECT_FIELD_GUID+2 + Public, // OBJECT_FIELD_GUID+3 + Public, // OBJECT_FIELD_DATA + Public, // OBJECT_FIELD_DATA+1 + Public, // OBJECT_FIELD_DATA+2 + Public, // OBJECT_FIELD_DATA+3 + Public, // OBJECT_FIELD_TYPE + Dynamic, // OBJECT_FIELD_ENTRY + Dynamic | Urgent, // OBJECT_DYNAMIC_FLAGS + Public, // OBJECT_FIELD_SCALE_X + Public, // GAMEOBJECT_FIELD_CREATED_BY + Public, // GAMEOBJECT_FIELD_CREATED_BY+1 + Public, // GAMEOBJECT_FIELD_CREATED_BY+2 + Public, // GAMEOBJECT_FIELD_CREATED_BY+3 + Dynamic | Urgent, // GAMEOBJECT_DISPLAYID + Public | Urgent, // GAMEOBJECT_FLAGS + Public, // GAMEOBJECT_PARENTROTATION + Public, // GAMEOBJECT_PARENTROTATION+1 + Public, // GAMEOBJECT_PARENTROTATION+2 + Public, // GAMEOBJECT_PARENTROTATION+3 + Public, // GAMEOBJECT_FACTION + Public, // GAMEOBJECT_LEVEL + Public | Urgent, // GAMEOBJECT_BYTES_1 + Public | Dynamic | Urgent, // GAMEOBJECT_SPELL_VISUAL_ID + Dynamic | Urgent, // GAMEOBJECT_STATE_SPELL_VISUAL_ID + Dynamic | Urgent, // GAMEOBJECT_STATE_ANIM_ID + Dynamic | Urgent, // GAMEOBJECT_STATE_ANIM_KIT_ID + Dynamic | Urgent, // GAMEOBJECT_STATE_WORLD_EFFECT_ID + Dynamic | Urgent, // GAMEOBJECT_STATE_WORLD_EFFECT_ID+1 + Dynamic | Urgent, // GAMEOBJECT_STATE_WORLD_EFFECT_ID+2 + Dynamic | Urgent, // GAMEOBJECT_STATE_WORLD_EFFECT_ID+3 + }; + + public static uint[] GameObjectDynamicUpdateFieldFlags = new uint[(int)GameObjectDynamicFields.End] + { + Public, // GAMEOBJECT_DYNAMIC_ENABLE_DOODAD_SETS + }; + + public static uint[] DynamicObjectUpdateFieldFlags = new uint[(int)DynamicObjectFields.End] + { + Public, // OBJECT_FIELD_GUID + Public, // OBJECT_FIELD_GUID+1 + Public, // OBJECT_FIELD_GUID+2 + Public, // OBJECT_FIELD_GUID+3 + Public, // OBJECT_FIELD_DATA + Public, // OBJECT_FIELD_DATA+1 + Public, // OBJECT_FIELD_DATA+2 + Public, // OBJECT_FIELD_DATA+3 + Public, // OBJECT_FIELD_TYPE + Dynamic, // OBJECT_FIELD_ENTRY + Dynamic | Urgent, // OBJECT_DYNAMIC_FLAGS + Public, // OBJECT_FIELD_SCALE_X + Public, // DYNAMICOBJECT_CASTER + Public, // DYNAMICOBJECT_CASTER+1 + Public, // DYNAMICOBJECT_CASTER+2 + Public, // DYNAMICOBJECT_CASTER+3 + Public, // DYNAMICOBJECT_TYPE + Public, // DYNAMICOBJECT_SPELL_X_SPELL_VISUAL_ID + Public, // DYNAMICOBJECT_SPELLID + Public, // DYNAMICOBJECT_RADIUS + Public, // DYNAMICOBJECT_CASTTIME + }; + + public static uint[] CorpseUpdateFieldFlags = new uint[(int)CorpseFields.End] + { + Public, // OBJECT_FIELD_GUID + Public, // OBJECT_FIELD_GUID+1 + Public, // OBJECT_FIELD_GUID+2 + Public, // OBJECT_FIELD_GUID+3 + Public, // OBJECT_FIELD_DATA + Public, // OBJECT_FIELD_DATA+1 + Public, // OBJECT_FIELD_DATA+2 + Public, // OBJECT_FIELD_DATA+3 + Public, // OBJECT_FIELD_TYPE + Dynamic, // OBJECT_FIELD_ENTRY + Dynamic | Urgent, // OBJECT_DYNAMIC_FLAGS + Public, // OBJECT_FIELD_SCALE_X + Public, // CORPSE_FIELD_OWNER + Public, // CORPSE_FIELD_OWNER+1 + Public, // CORPSE_FIELD_OWNER+2 + Public, // CORPSE_FIELD_OWNER+3 + Public, // CORPSE_FIELD_PARTY + Public, // CORPSE_FIELD_PARTY+1 + Public, // CORPSE_FIELD_PARTY+2 + Public, // CORPSE_FIELD_PARTY+3 + Public, // CORPSE_FIELD_DISPLAY_ID + Public, // CORPSE_FIELD_ITEM + Public, // CORPSE_FIELD_ITEM+1 + Public, // CORPSE_FIELD_ITEM+2 + Public, // CORPSE_FIELD_ITEM+3 + Public, // CORPSE_FIELD_ITEM+4 + Public, // CORPSE_FIELD_ITEM+5 + Public, // CORPSE_FIELD_ITEM+6 + Public, // CORPSE_FIELD_ITEM+7 + Public, // CORPSE_FIELD_ITEM+8 + Public, // CORPSE_FIELD_ITEM+9 + Public, // CORPSE_FIELD_ITEM+10 + Public, // CORPSE_FIELD_ITEM+11 + Public, // CORPSE_FIELD_ITEM+12 + Public, // CORPSE_FIELD_ITEM+13 + Public, // CORPSE_FIELD_ITEM+14 + Public, // CORPSE_FIELD_ITEM+15 + Public, // CORPSE_FIELD_ITEM+16 + Public, // CORPSE_FIELD_ITEM+17 + Public, // CORPSE_FIELD_ITEM+18 + Public, // CORPSE_FIELD_BYTES_1 + Public, // CORPSE_FIELD_BYTES_2 + Public, // CORPSE_FIELD_FLAGS + Dynamic, // CORPSE_FIELD_DYNAMIC_FLAGS + Public, // CORPSE_FIELD_FACTIONTEMPLATE + Public, // CORPSE_FIELD_CUSTOM_DISPLAY_OPTION + }; + + public static uint[] AreaTriggerUpdateFieldFlags = new uint[(int)AreaTriggerFields.End] + { + Public, // OBJECT_FIELD_GUID + Public, // OBJECT_FIELD_GUID+1 + Public, // OBJECT_FIELD_GUID+2 + Public, // OBJECT_FIELD_GUID+3 + Public, // OBJECT_FIELD_DATA + Public, // OBJECT_FIELD_DATA+1 + Public, // OBJECT_FIELD_DATA+2 + Public, // OBJECT_FIELD_DATA+3 + Public, // OBJECT_FIELD_TYPE + Dynamic, // OBJECT_FIELD_ENTRY + Dynamic | Urgent, // OBJECT_DYNAMIC_FLAGS + Public, // OBJECT_FIELD_SCALE_X + Public | Urgent, // AREATRIGGER_OVERRIDE_SCALE_CURVE + Public | Urgent, // AREATRIGGER_OVERRIDE_SCALE_CURVE+1 + Public | Urgent, // AREATRIGGER_OVERRIDE_SCALE_CURVE+2 + Public | Urgent, // AREATRIGGER_OVERRIDE_SCALE_CURVE+3 + Public | Urgent, // AREATRIGGER_OVERRIDE_SCALE_CURVE+4 + Public | Urgent, // AREATRIGGER_OVERRIDE_SCALE_CURVE+5 + Public | Urgent, // AREATRIGGER_OVERRIDE_SCALE_CURVE+6 + Public | Urgent, // AREATRIGGER_EXTRA_SCALE_CURVE + Public | Urgent, // AREATRIGGER_EXTRA_SCALE_CURVE+1 + Public | Urgent, // AREATRIGGER_EXTRA_SCALE_CURVE+2 + Public | Urgent, // AREATRIGGER_EXTRA_SCALE_CURVE+3 + Public | Urgent, // AREATRIGGER_EXTRA_SCALE_CURVE+4 + Public | Urgent, // AREATRIGGER_EXTRA_SCALE_CURVE+5 + Public | Urgent, // AREATRIGGER_EXTRA_SCALE_CURVE+6 + Public, // AREATRIGGER_CASTER + Public, // AREATRIGGER_CASTER+1 + Public, // AREATRIGGER_CASTER+2 + Public, // AREATRIGGER_CASTER+3 + Public, // AREATRIGGER_DURATION + Public | Urgent, // AREATRIGGER_TIME_TO_TARGET + Public | Urgent, // AREATRIGGER_TIME_TO_TARGET_SCALE + Public | Urgent, // AREATRIGGER_TIME_TO_TARGET_EXTRA_SCALE + Public, // AREATRIGGER_SPELLID + Public, // AREATRIGGER_SPELL_FOR_VISUALS + Public, // AREATRIGGER_SPELL_X_SPELL_VISUAL_ID + Dynamic | Urgent, // AREATRIGGER_BOUNDS_RADIUS_2D + Public, // AREATRIGGER_DECAL_PROPERTIES_ID + Public, // AREATRIGGER_CREATING_EFFECT_GUID + Public, // AREATRIGGER_CREATING_EFFECT_GUID+1 + Public, // AREATRIGGER_CREATING_EFFECT_GUID+2 + Public, // AREATRIGGER_CREATING_EFFECT_GUID+3 + }; + + public static uint[] SceneObjectUpdateFieldFlags = new uint[(int)SceneObjectFields.End] + { + Public, // OBJECT_FIELD_GUID + Public, // OBJECT_FIELD_GUID+1 + Public, // OBJECT_FIELD_GUID+2 + Public, // OBJECT_FIELD_GUID+3 + Public, // OBJECT_FIELD_DATA + Public, // OBJECT_FIELD_DATA+1 + Public, // OBJECT_FIELD_DATA+2 + Public, // OBJECT_FIELD_DATA+3 + Public, // OBJECT_FIELD_TYPE + Dynamic, // OBJECT_FIELD_ENTRY + Dynamic | Urgent, // OBJECT_DYNAMIC_FLAGS + Public, // OBJECT_FIELD_SCALE_X + Public, // SCENEOBJECT_FIELD_SCRIPT_PACKAGE_ID + Public, // SCENEOBJECT_FIELD_RND_SEED_VAL + Public, // SCENEOBJECT_FIELD_CREATEDBY + Public, // SCENEOBJECT_FIELD_CREATEDBY+1 + Public, // SCENEOBJECT_FIELD_CREATEDBY+2 + Public, // SCENEOBJECT_FIELD_CREATEDBY+3 + Public, // SCENEOBJECT_FIELD_SCENE_TYPE + }; + + public static uint[] ConversationUpdateFieldFlags = new uint[(int)ConversationFields.End] + { + Public, // OBJECT_FIELD_GUID + Public, // OBJECT_FIELD_GUID+1 + Public, // OBJECT_FIELD_GUID+2 + Public, // OBJECT_FIELD_GUID+3 + Public, // OBJECT_FIELD_DATA + Public, // OBJECT_FIELD_DATA+1 + Public, // OBJECT_FIELD_DATA+2 + Public, // OBJECT_FIELD_DATA+3 + Public, // OBJECT_FIELD_TYPE + Dynamic, // OBJECT_FIELD_ENTRY + Dynamic | Urgent, // OBJECT_DYNAMIC_FLAGS + Public, // OBJECT_FIELD_SCALE_X + Dynamic, // CONVERSATION_LAST_LINE_END_TIME + }; + + public static uint[] ConversationDynamicUpdateFieldFlags = new uint[(int)ConversationDynamicFields.End] + { + Public, // CONVERSATION_DYNAMIC_FIELD_ACTORS + Unknownx100, // CONVERSATION_DYNAMIC_FIELD_LINES + }; + } +} diff --git a/Framework/Constants/Update/UpdateFields.cs b/Framework/Constants/Update/UpdateFields.cs new file mode 100644 index 000000000..2af642f2d --- /dev/null +++ b/Framework/Constants/Update/UpdateFields.cs @@ -0,0 +1,411 @@ +/* + * Copyright (C) 2012-2017 CypherCore + * + * 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, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +namespace Framework.Constants +{ + public enum ObjectFields + { + Guid = 0x000, // Size: 4, Flags: Public + Data = 0x004, // Size: 4, Flags: Public + Type = 0x008, // Size: 1, Flags: Public + Entry = 0x009, // Size: 1, Flags: Dynamic + DynamicFlags = 0x00a, // Size: 1, Flags: Dynamic, Urgent + ScaleX = 0x00b, // Size: 1, Flags: Public + End = 0x00c, + } + + public enum ItemFields + { + Owner = ObjectFields.End + 0x000, // Size: 4, Flags: Public + Contained = ObjectFields.End + 0x004, // Size: 4, Flags: Public + Creator = ObjectFields.End + 0x008, // Size: 4, Flags: Public + GiftCreator = ObjectFields.End + 0x00c, // Size: 4, Flags: Public + StackCount = ObjectFields.End + 0x010, // Size: 1, Flags: Owner + Duration = ObjectFields.End + 0x011, // Size: 1, Flags: Owner + SpellCharges = ObjectFields.End + 0x012, // Size: 5, Flags: Owner + Flags = ObjectFields.End + 0x017, // Size: 1, Flags: Public + Enchantment = ObjectFields.End + 0x018, // Size: 39, Flags: Public + PropertySeed = ObjectFields.End + 0x03f, // Size: 1, Flags: Public + RandomPropertiesId = ObjectFields.End + 0x040, // Size: 1, Flags: Public + Durability = ObjectFields.End + 0x041, // Size: 1, Flags: Owner + MaxDurability = ObjectFields.End + 0x042, // Size: 1, Flags: Owner + CreatePlayedTime = ObjectFields.End + 0x043, // Size: 1, Flags: Public + ModifiersMask = ObjectFields.End + 0x044, // Size: 1, Flags: Owner + Context = ObjectFields.End + 0x045, // Size: 1, Flags: Public + ArtifactXp = ObjectFields.End + 0x046, // Size: 2, Flags: OWNER + AppearanceModId = ObjectFields.End + 0x048, // Size: 1, Flags: OWNER + End = ObjectFields.End + 0x049, + } + + public enum ItemDynamicFields + { + Modifiers = 0x000, // Flags: Owner + BonusListIds = 0x001, // Flags: Owner, 0x100 + ArtifactPowers = 0x002, // Flags: OWNER + Gems = 0x003, // Flags: OWNER + End = 0x004, + } + + public enum ContainerFields + { + Slot1 = ItemFields.End + 0x000, // Size: 144, Flags: Public + NumSlots = ItemFields.End + 0x090, // Size: 1, Flags: Public + End = ItemFields.End + 0x091, + } + + public enum UnitFields + { + Charm = ObjectFields.End + 0x000, // Size: 4, Flags: Public + Summon = ObjectFields.End + 0x004, // Size: 4, Flags: Public + Critter = ObjectFields.End + 0x008, // Size: 4, Flags: Private + CharmedBy = ObjectFields.End + 0x00c, // Size: 4, Flags: Public + SummonedBy = ObjectFields.End + 0x010, // Size: 4, Flags: Public + CreatedBy = ObjectFields.End + 0x014, // Size: 4, Flags: Public + DemonCreator = ObjectFields.End + 0x018, // Size: 4, Flags: Public + Target = ObjectFields.End + 0x01c, // Size: 4, Flags: Public + BattlePetCompanionGuid = ObjectFields.End + 0x020, // Size: 4, Flags: Public + BattlePetDbId = ObjectFields.End + 0x024, // Size: 2, Flags: Public + ChannelSpell = ObjectFields.End + 0x026, // Size: 1, Flags: Public, Urgent + ChannelSpellXSpellVisual = ObjectFields.End + 0x027, // Size: 1, Flags: Public, Urgent + SummonedByHomeRealm = ObjectFields.End + 0x028, // Size: 1, Flags: Public + Bytes0 = ObjectFields.End + 0x029, // Size: 1, Flags: Public + DisplayPower = ObjectFields.End + 0x02a, // Size: 1, Flags: Public + OverrideDisplayPowerId = ObjectFields.End + 0x02b, // Size: 1, Flags: Public + Health = ObjectFields.End + 0x02c, // Size: 2, Flags: Public + Power = ObjectFields.End + 0x02e, // Size: 6, Flags: Public, UrgentSelfOnly + MaxHealth = ObjectFields.End + 0x034, // Size: 2, Flags: Public + MaxPower = ObjectFields.End + 0x036, // Size: 6, Flags: Public + PowerRegenFlatModifier = ObjectFields.End + 0x03c, // Size: 6, Flags: Private, Owner, UnitAll + PowerRegenInterruptedFlatModifier = ObjectFields.End + 0x042, // Size: 6, Flags: Private, Owner, UnitAll + Level = ObjectFields.End + 0x048, // Size: 1, Flags: Public + EffectiveLevel = ObjectFields.End + 0x049, // Size: 1, Flags: Public + ScalingLevelMin = ObjectFields.End + 0x04a, // Size: 1, Flags: Public + ScalingLevelMax = ObjectFields.End + 0x04b, // Size: 1, Flags: Public + ScalingLevelDelta = ObjectFields.End + 0x04c, // Size: 1, Flags: Public + FactionTemplate = ObjectFields.End + 0x04d, // Size: 1, Flags: Public + VirtualItemSlotId = ObjectFields.End + 0x04e, // Size: 6, Flags: Public + Flags = ObjectFields.End + 0x054, // Size: 1, Flags: Public, Urgent + Flags2 = ObjectFields.End + 0x055, // Size: 1, Flags: Public, Urgent + Flags3 = ObjectFields.End + 0x056, // Size: 1, Flags: Public, Urgent + AuraState = ObjectFields.End + 0x057, // Size: 1, Flags: Public + BaseAttackTime = ObjectFields.End + 0x058, // Size: 2, Flags: Public + RangedAttackTime = ObjectFields.End + 0x05a, // Size: 1, Flags: Private + BoundingRadius = ObjectFields.End + 0x05b, // Size: 1, Flags: Public + CombatReach = ObjectFields.End + 0x05c, // Size: 1, Flags: Public + DisplayId = ObjectFields.End + 0x05d, // Size: 1, Flags: Dynamic, Urgent + NativeDisplayId = ObjectFields.End + 0x05e, // Size: 1, Flags: Public, Urgent + MountDisplayId = ObjectFields.End + 0x05f, // Size: 1, Flags: Public, Urgent + MinDamage = ObjectFields.End + 0x060, // Size: 1, Flags: Private, Owner, SpecialInfo + MaxDamage = ObjectFields.End + 0x061, // Size: 1, Flags: Private, Owner, SpecialInfo + MinOffHandDamage = ObjectFields.End + 0x062, // Size: 1, Flags: Private, Owner, SpecialInfo + MaxOffHandDamage = ObjectFields.End + 0x063, // Size: 1, Flags: Private, Owner, SpecialInfo + Bytes1 = ObjectFields.End + 0x064, // Size: 1, Flags: Public + PetNumber = ObjectFields.End + 0x065, // Size: 1, Flags: Public + PetNameTimestamp = ObjectFields.End + 0x066, // Size: 1, Flags: Public + PetExperience = ObjectFields.End + 0x067, // Size: 1, Flags: Owner + PetNextLevelExp = ObjectFields.End + 0x068, // Size: 1, Flags: Owner + ModCastSpeed = ObjectFields.End + 0x069, // Size: 1, Flags: Public + ModCastHaste = ObjectFields.End + 0x06a, // Size: 1, Flags: Public + ModHaste = ObjectFields.End + 0x06b, // Size: 1, Flags: Public + ModRangedHaste = ObjectFields.End + 0x06c, // Size: 1, Flags: Public + ModHasteRegen = ObjectFields.End + 0x06d, // Size: 1, Flags: Public + ModTimeRate = ObjectFields.End + 0x06e, // Size: 1, Flags: Public + CreatedBySpell = ObjectFields.End + 0x06f, // Size: 1, Flags: Public + NpcFlags = ObjectFields.End + 0x070, // Size: 2, Flags: Public, Dynamic + NpcEmotestate = ObjectFields.End + 0x072, // Size: 1, Flags: Public + Stat = ObjectFields.End + 0x073, // Size: 4, Flags: Private, Owner + PosStat = ObjectFields.End + 0x077, // Size: 4, Flags: Private, Owner + NegStat = ObjectFields.End + 0x07b, // Size: 4, Flags: Private, Owner + Resistances = ObjectFields.End + 0x07f, // Size: 7, Flags: Private, Owner, SpecialInfo + ResistanceBuffModsPositive = ObjectFields.End + 0x086, // Size: 7, Flags: Private, Owner + ResistanceBuffModsNegative = ObjectFields.End + 0x08d, // Size: 7, Flags: Private, Owner + ModBonusArmor = ObjectFields.End + 0x094, // Size: 1, Flags: Private, Owner + BaseMana = ObjectFields.End + 0x095, // Size: 1, Flags: Public + BaseHealth = ObjectFields.End + 0x096, // Size: 1, Flags: Private, Owner + Bytes2 = ObjectFields.End + 0x097, // Size: 1, Flags: Public + AttackPower = ObjectFields.End + 0x098, // Size: 1, Flags: Private, Owner + AttackPowerModPos = ObjectFields.End + 0x099, // Size: 1, Flags: Private, Owner + AttackPowerModNeg = ObjectFields.End + 0x09a, // Size: 1, Flags: Private, Owner + AttackPowerMultiplier = ObjectFields.End + 0x09b, // Size: 1, Flags: Private, Owner + RangedAttackPower = ObjectFields.End + 0x09c, // Size: 1, Flags: Private, Owner + RangedAttackPowerModPos = ObjectFields.End + 0x09d, // Size: 1, Flags: Private, Owner + RangedAttackPowerModNeg = ObjectFields.End + 0x09e, // Size: 1, Flags: Private, Owner + RangedAttackPowerMultiplier = ObjectFields.End + 0x09f, // Size: 1, Flags: Private, Owner + AttackSpeedAura = ObjectFields.End + 0x0a0, // Size: 1, Flags: Private, Owner + MinRangedDamage = ObjectFields.End + 0x0a1, // Size: 1, Flags: Private, Owner + MaxRangedDamage = ObjectFields.End + 0x0a2, // Size: 1, Flags: Private, Owner + PowerCostModifier = ObjectFields.End + 0x0a3, // Size: 7, Flags: Private, Owner + PowerCostMultiplier = ObjectFields.End + 0x0aa, // Size: 7, Flags: Private, Owner + Maxhealthmodifier = ObjectFields.End + 0x0b1, // Size: 1, Flags: Private, Owner + HoverHeight = ObjectFields.End + 0x0b2, // Size: 1, Flags: Public + MinItemLevelCutoff = ObjectFields.End + 0x0b3, // Size: 1, Flags: Public + MinItemLevel = ObjectFields.End + 0x0b4, // Size: 1, Flags: Public + Maxitemlevel = ObjectFields.End + 0x0b5, // Size: 1, Flags: Public + WildBattlepetLevel = ObjectFields.End + 0x0b6, // Size: 1, Flags: Public + BattlepetCompanionNameTimestamp = ObjectFields.End + 0x0b7, // Size: 1, Flags: Public + InteractSpellid = ObjectFields.End + 0x0b8, // Size: 1, Flags: Public + StateSpellVisualId = ObjectFields.End + 0x0b9, // Size: 1, Flags: Dynamic, Urgent + StateAnimId = ObjectFields.End + 0x0ba, // Size: 1, Flags: Dynamic, Urgent + StateAnimKitId = ObjectFields.End + 0x0bb, // Size: 1, Flags: Dynamic, Urgent + StateWorldEffectId = ObjectFields.End + 0x0bc, // Size: 4, Flags: Dynamic, Urgent + ScaleDuration = ObjectFields.End + 0x0c0, // Size: 1, Flags: Public + LooksLikeMountId = ObjectFields.End + 0x0c1, // Size: 1, Flags: Public + LooksLikeCreatureId = ObjectFields.End + 0x0c2, // Size: 1, Flags: Public + LookAtControllerId = ObjectFields.End + 0x0c3, // Size: 1, Flags: Public + LookAtControllerTarget = ObjectFields.End + 0x0c4, // Size: 4, Flags: Public + End = ObjectFields.End + 0x0c8, + } + + public enum UnitDynamicFields + { + PassiveSpells = 0x000, // Flags: Public, Urgent + WorldEffects = 0x001, // Flags: Public, Urgent + ChannelObjects = 0x002, // Flags: PUBLIC, URGENT + End = 0x003, + } + + public enum PlayerFields + { + DuelArbiter = UnitFields.End + 0x000, // Size: 4, Flags: Public + WowAccount = UnitFields.End + 0x004, // Size: 4, Flags: Public + LootTargetGuid = UnitFields.End + 0x008, // Size: 4, Flags: Public + Flags = UnitFields.End + 0x00c, // Size: 1, Flags: Public + FlagsEx = UnitFields.End + 0x00d, // Size: 1, Flags: Public + GuildRank = UnitFields.End + 0x00e, // Size: 1, Flags: Public + GuildDeleteDate = UnitFields.End + 0x00f, // Size: 1, Flags: Public + GuildLevel = UnitFields.End + 0x010, // Size: 1, Flags: Public + Bytes = UnitFields.End + 0x011, // Size: 1, Flags: Public + Bytes2 = UnitFields.End + 0x012, // Size: 1, Flags: Public + Bytes3 = UnitFields.End + 0x013, // Size: 1, Flags: Public + Bytes4 = UnitFields.End + 0x014, // Size: 1, Flags: Public + DuelTeam = UnitFields.End + 0x015, // Size: 1, Flags: Public + GuildTimestamp = UnitFields.End + 0x016, // Size: 1, Flags: Public + QuestLog = UnitFields.End + 0x017, // Size: 800, Flags: PartyMember + VisibleItem = UnitFields.End + 0x337, // Size: 38, Flags: Public + ChosenTitle = UnitFields.End + 0x35d, // Size: 1, Flags: Public + FakeInebriation = UnitFields.End + 0x35e, // Size: 1, Flags: Public + VirtualRealm = UnitFields.End + 0x35f, // Size: 1, Flags: Public + CurrentSpecId = UnitFields.End + 0x360, // Size: 1, Flags: Public + TaxiMountAnimKitId = UnitFields.End + 0x361, // Size: 1, Flags: Public + AvgItemLevel = UnitFields.End + 0x362, // Size: 4, Flags: Public + CurrentBattlePetBreedQuality = UnitFields.End + 0x366, // Size: 1, Flags: Public + Prestige = UnitFields.End + 0x367, // Size: 1, Flags: Public + HonorLevel = UnitFields.End + 0x368, // Size: 1, Flags: Public + InvSlotHead = UnitFields.End + 0x369, // Size: 748, Flags: Private + EndNotSelf = UnitFields.End + 0x369, + + Farsight = UnitFields.End + 0x655, // Size: 4, Flags: Private + SummonedBattlePetId = UnitFields.End + 0x659, // Size: 4, Flags: Private + KnownTitles = UnitFields.End + 0x65d, // Size: 12, Flags: Private + Coinage = UnitFields.End + 0x669, // Size: 2, Flags: Private + Xp = UnitFields.End + 0x66b, // Size: 1, Flags: Private + NextLevelXp = UnitFields.End + 0x66c, // Size: 1, Flags: Private + SkillLineId = UnitFields.End + 0x66d, // Size: 448, Flags: Private + SkillLineStep = UnitFields.End + 0x6AD, + SkillLineRank = UnitFields.End + 0x6ED, + SkillLineSubStartRank = UnitFields.End + 0x72D, + SkillLineMaxRank = UnitFields.End + 0x76D, + SkillLineTempBonus = UnitFields.End + 0x7AD, + SkillLinePermBonus = UnitFields.End + 0x7ED, + CharacterPoints = UnitFields.End + 0x82d, // Size: 1, Flags: Private + MaxTalentTiers = UnitFields.End + 0x82e, // Size: 1, Flags: Private + TrackCreatures = UnitFields.End + 0x82f, // Size: 1, Flags: Private + TrackResources = UnitFields.End + 0x830, // Size: 1, Flags: Private + Expertise = UnitFields.End + 0x831, // Size: 1, Flags: Private + OffhandExpertise = UnitFields.End + 0x832, // Size: 1, Flags: Private + RangedExpertise = UnitFields.End + 0x833, // Size: 1, Flags: Private + CombatRatingExpertise = UnitFields.End + 0x834, // Size: 1, Flags: Private + BlockPercentage = UnitFields.End + 0x835, // Size: 1, Flags: Private + DodgePercentage = UnitFields.End + 0x836, // Size: 1, Flags: Private + DodgePercentageFromAttribute = UnitFields.End + 0x837, // Size: 1, Flags: Private + ParryPercentage = UnitFields.End + 0x838, // Size: 1, Flags: Private + ParryPercentageFromAttribute = UnitFields.End + 0x839, // Size: 1, Flags: Private + CritPercentage = UnitFields.End + 0x83a, // Size: 1, Flags: Private + RangedCritPercentage = UnitFields.End + 0x83b, // Size: 1, Flags: Private + OffhandCritPercentage = UnitFields.End + 0x83c, // Size: 1, Flags: Private + SpellCritPercentage1 = UnitFields.End + 0x83d, // Size: 1, Flags: Private + ShieldBlock = UnitFields.End + 0x83e, // Size: 1, Flags: Private + ShieldBlockCritPercentage = UnitFields.End + 0x83f, // Size: 1, Flags: Private + Mastery = UnitFields.End + 0x840, // Size: 1, Flags: Private + Speed = UnitFields.End + 0x841, // Size: 1, Flags: Private + Lifesteal = UnitFields.End + 0x842, // Size: 1, Flags: Private + Avoidance = UnitFields.End + 0x843, // Size: 1, Flags: Private + Sturdiness = UnitFields.End + 0x844, // Size: 1, Flags: Private + Versatility = UnitFields.End + 0x845, // Size: 1, Flags: Private + VersatilityBonus = UnitFields.End + 0x846, // Size: 1, Flags: Private + FieldPvpPowerDamage = UnitFields.End + 0x847, // Size: 1, Flags: Private + FieldPvpPowerHealing = UnitFields.End + 0x848, // Size: 1, Flags: Private + ExploredZones1 = UnitFields.End + 0x849, // Size: 256, Flags: Private + RestInfo = UnitFields.End + 0x949, // Size: 4, Flags: Private + ModDamageDonePos = UnitFields.End + 0x94d, // Size: 7, Flags: Private + ModDamageDoneNeg = UnitFields.End + 0x954, // Size: 7, Flags: Private + ModDamageDonePct = UnitFields.End + 0x95b, // Size: 7, Flags: Private + ModHealingDonePos = UnitFields.End + 0x962, // Size: 1, Flags: Private + ModHealingPct = UnitFields.End + 0x963, // Size: 1, Flags: Private + ModHealingDonePct = UnitFields.End + 0x964, // Size: 1, Flags: Private + ModPeriodicHealingDonePercent = UnitFields.End + 0x965, // Size: 1, Flags: Private + WeaponDmgMultipliers = UnitFields.End + 0x966, // Size: 3, Flags: Private + WeaponAtkSpeedMultipliers = UnitFields.End + 0x969, // Size: 3, Flags: Private + ModSpellPowerPct = UnitFields.End + 0x96c, // Size: 1, Flags: Private + ModResiliencePercent = UnitFields.End + 0x96d, // Size: 1, Flags: Private + OverrideSpellPowerByApPct = UnitFields.End + 0x96e, // Size: 1, Flags: Private + OverrideApBySpellPowerPercent = UnitFields.End + 0x96f, // Size: 1, Flags: Private + ModTargetResistance = UnitFields.End + 0x970, // Size: 1, Flags: Private + ModTargetPhysicalResistance = UnitFields.End + 0x971, // Size: 1, Flags: Private + LocalFlags = UnitFields.End + 0x972, // Size: 1, Flags: Private + FieldBytes = UnitFields.End + 0x973, // Size: 1, Flags: Private + SelfResSpell = UnitFields.End + 0x974, // Size: 1, Flags: Private + PvpMedals = UnitFields.End + 0x975, // Size: 1, Flags: Private + BuyBackPrice1 = UnitFields.End + 0x976, // Size: 12, Flags: Private + BuyBackTimestamp1 = UnitFields.End + 0x982, // Size: 12, Flags: Private + Kills = UnitFields.End + 0x98e, // Size: 1, Flags: Private + LifetimeHonorableKills = UnitFields.End + 0x98f, // Size: 1, Flags: Private + WatchedFactionIndex = UnitFields.End + 0x990, // Size: 1, Flags: Private + CombatRating1 = UnitFields.End + 0x991, // Size: 32, Flags: Private + ArenaTeamInfo11 = UnitFields.End + 0x9b1, // Size: 42, Flags: Private + MaxLevel = UnitFields.End + 0x9db, // Size: 1, Flags: Private + ScalingLevelDelta = UnitFields.End + 0x9dc, // Size: 1, Flags: Private + MaxCreatureScalingLevel = UnitFields.End + 0x9dd, // Size: 1, Flags: Private + NoReagentCost1 = UnitFields.End + 0x9de, // Size: 4, Flags: Private + PetSpellPower = UnitFields.End + 0x9e2, // Size: 1, Flags: Private + Researching1 = UnitFields.End + 0x9e3, // Size: 10, Flags: Private + ProfessionSkillLine1 = UnitFields.End + 0x9ed, // Size: 2, Flags: Private + UiHitModifier = UnitFields.End + 0x9ef, // Size: 1, Flags: Private + UiSpellHitModifier = UnitFields.End + 0x9f0, // Size: 1, Flags: Private + HomeRealmTimeOffset = UnitFields.End + 0x9f1, // Size: 1, Flags: Private + ModPetHaste = UnitFields.End + 0x9f2, // Size: 1, Flags: Private + FieldBytes2 = UnitFields.End + 0x9f3, // Size: 1, Flags: Private + FieldBytes3 = UnitFields.End + 0x9f4, // Size: 1, Flags: Private, UrgentSelfOnly + LfgBonusFactionId = UnitFields.End + 0x9f5, // Size: 1, Flags: Private + LootSpecId = UnitFields.End + 0x9f6, // Size: 1, Flags: Private + OverrideZonePvpType = UnitFields.End + 0x9f7, // Size: 1, Flags: Private, UrgentSelfOnly + BagSlotFlags = UnitFields.End + 0x9f8, // Size: 4, Flags: Private + BankBagSlotFlags = UnitFields.End + 0x9fc, // Size: 7, Flags: Private + InsertItemsLeftToRight = UnitFields.End + 0xa03, // Size: 1, Flags: Private + QuestCompleted = UnitFields.End + 0xa04, // Size: 1750, Flags: Private + Honor = UnitFields.End + 0x10DA, // Size: 1, Flags: Private + HonorNextLevel = UnitFields.End + 0x10DB, // Size: 1, Flags: Private + End = UnitFields.End + 0x10DC, + } + + public enum PlayerDynamicFields + { + ReserachSite = UnitDynamicFields.End + 0x000, // Flags: Private + ResearchSiteProgress = UnitDynamicFields.End + 0x001, // Flags: Private + DailyQuests = UnitDynamicFields.End + 0x002, // Flags: Private + AvailableQuestLineXQuestId = UnitDynamicFields.End + 0x003, // Flags: Private + Heirlooms = UnitDynamicFields.End + 0x004, // Flags: Private + HeirloomsFlags = UnitDynamicFields.End + 0x005, // Flags: PRIVATE + Toys = UnitDynamicFields.End + 0x006, // Flags: Private + Transmog = UnitDynamicFields.End + 0x007, // Flags: PRIVATE + ConditionalTransmog = UnitDynamicFields.End + 0x008, // Flags: PRIVATE + CharacterRestrictions = UnitDynamicFields.End + 0x009, // Flags: PRIVATE + SpellPctModByLabel = UnitDynamicFields.End + 0x00A, // Flags: PRIVATE + SpellFlatModByLabel = UnitDynamicFields.End + 0x00B, // Flags: PRIVATE + ArenaCooldowns = UnitDynamicFields.End + 0x00C, // Flags: PUBLIC + End = UnitDynamicFields.End + 0x00D, + } + + public enum GameObjectFields + { + CreatedBy = ObjectFields.End + 0x000, // Size: 4, Flags: Public + DisplayId = ObjectFields.End + 0x004, // Size: 1, Flags: Dynamic, Urgent + Flags = ObjectFields.End + 0x005, // Size: 1, Flags: Public, Urgent + ParentRotation = ObjectFields.End + 0x006, // Size: 4, Flags: Public + Faction = ObjectFields.End + 0x00a, // Size: 1, Flags: Public + Level = ObjectFields.End + 0x00b, // Size: 1, Flags: Public + Bytes1 = ObjectFields.End + 0x00c, // Size: 1, Flags: Public, Urgent + SpellVisualId = ObjectFields.End + 0x00d, // Size: 1, Flags: Public, Dynamic, Urgent + StateSpellVisualId = ObjectFields.End + 0x00e, // Size: 1, Flags: Dynamic, Urgent + StateAnumId = ObjectFields.End + 0x00f, // Size: 1, Flags: Dynamic, Urgent + StateAnimKitId = ObjectFields.End + 0x010, // Size: 1, Flags: Dynamic, Urgent + StateWorldEffectId = ObjectFields.End + 0x011, // Size: 4, Flags: Dynamic, Urgent + End = ObjectFields.End + 0x015, + } + + public enum GameObjectDynamicFields + { + EnableDoodadSets = 0x000, // Flags: PUBLIC + End = 0x001, + } + + public enum DynamicObjectFields + { + Caster = ObjectFields.End + 0x000, // Size: 4, Flags: Public + Type = ObjectFields.End + 0x004, // Size: 1, Flags: Public + SpellXSpellVisualId = ObjectFields.End + 0x005, // Size: 1, Flags: Public + SpellId = ObjectFields.End + 0x006, // Size: 1, Flags: Public + Radius = ObjectFields.End + 0x007, // Size: 1, Flags: Public + CastTime = ObjectFields.End + 0x008, // Size: 1, Flags: Public + End = ObjectFields.End + 0x009, + } + + public enum CorpseFields + { + Owner = ObjectFields.End + 0x000, // Size: 4, Flags: Public + Party = ObjectFields.End + 0x004, // Size: 4, Flags: Public + DisplayId = ObjectFields.End + 0x008, // Size: 1, Flags: Public + Item = ObjectFields.End + 0x009, // Size: 19, Flags: Public + Bytes1 = ObjectFields.End + 0x01c, // Size: 1, Flags: Public + Bytes2 = ObjectFields.End + 0x01d, // Size: 1, Flags: Public + Flags = ObjectFields.End + 0x01e, // Size: 1, Flags: Public + DynamicFlags = ObjectFields.End + 0x01f, // Size: 1, Flags: Dynamic + FactionTemplate = ObjectFields.End + 0x020, // Size: 1, Flags: Public + CustomDisplayOption = ObjectFields.End + 0x021, // Size: 1, Flags: PUBLIC + End = ObjectFields.End + 0x022, + } + + public enum AreaTriggerFields + { + OverrideScaleCurve = ObjectFields.End + 0x000, // Size: 7, Flags: Public, Urgent + ExtraScaleCurve = ObjectFields.End + 0x007, // Size: 7, Flags: Public, Urgent + Caster = ObjectFields.End + 0x00e, // Size: 4, Flags: Public + Duration = ObjectFields.End + 0x012, // Size: 1, Flags: Public + TimeToTarget = ObjectFields.End + 0x013, // Size: 1, Flags: Public, Urgent + TimeToTargetScale = ObjectFields.End + 0x014, // Size: 1, Flags: Public, Urgent + TimeToTargetExtraScale = ObjectFields.End + 0x015, // Size: 1, Flags: Public, Urgent + SpellId = ObjectFields.End + 0x016, // Size: 1, Flags: Public + SpellForVisuals = ObjectFields.End + 0x017, // Size: 1, Flags: PUBLIC + SpellXSpellVisualId = ObjectFields.End + 0x018, // Size: 1, Flags: Dynamic + BoundsRadius2d = ObjectFields.End + 0x019, // Size: 1, Flags: Dynamic, Urgent + DecalPropertiesId = ObjectFields.End + 0x01A, // Size: 1, Flags: Public + CreatingEffectGuid = ObjectFields.End + 0x01B, // Size: 4, Flags: PUBLIC + End = ObjectFields.End + 0x01F, + } + + public enum SceneObjectFields + { + ScriptPackageId = ObjectFields.End + 0x000, // Size: 1, Flags: Public + RndSeedVal = ObjectFields.End + 0x001, // Size: 1, Flags: Public + Createdby = ObjectFields.End + 0x002, // Size: 4, Flags: Public + SceneType = ObjectFields.End + 0x006, // Size: 1, Flags: Public + End = ObjectFields.End + 0x007, + } + + public enum ConversationFields + { + LastLineEndTime = ObjectFields.End + 0x000, // Size: 1, Flags: DYNAMIC + End = ObjectFields.End + 0x001, + } + + public enum ConversationDynamicFields + { + Actors = 0x000, // Flags: Public + Lines = 0x001, // Flags: 0x100 + End = 0x002, + } +} diff --git a/Framework/Constants/Update/UpdateFlags.cs b/Framework/Constants/Update/UpdateFlags.cs new file mode 100644 index 000000000..91d7f1942 --- /dev/null +++ b/Framework/Constants/Update/UpdateFlags.cs @@ -0,0 +1,45 @@ +/* + * Copyright (C) 2012-2017 CypherCore + * + * 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, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +using System; + +namespace Framework.Constants +{ + [Flags] + public enum UpdateFlag + { + None = 0x0, + Self = 0x1, + Transport = 0x2, + HasTarget = 0x4, + Living = 0x8, + StationaryPosition = 0x10, + Vehicle = 0x20, + TransportPosition = 0x40, + Rotation = 0x80, + AnimKits = 0x100, + Areatrigger = 0x0200, + //UPDATEFLAG_GAMEOBJECT = 0x0400, + //UPDATEFLAG_REPLACE_ACTIVE = 0x0800, + //UPDATEFLAG_NO_BIRTH_ANIM = 0x1000, + //UPDATEFLAG_ENABLE_PORTALS = 0x2000, + //UPDATEFLAG_PLAY_HOVER_ANIM = 0x4000, + //UPDATEFLAG_IS_SUPPRESSING_GREETINGS = 0x8000 + //UPDATEFLAG_SCENEOBJECT = 0x10000, + //UPDATEFLAG_SCENE_PENDING_INSTANCE = 0x20000 + } +} diff --git a/Framework/Constants/Update/UpdateType.cs b/Framework/Constants/Update/UpdateType.cs new file mode 100644 index 000000000..951722265 --- /dev/null +++ b/Framework/Constants/Update/UpdateType.cs @@ -0,0 +1,27 @@ +/* + * Copyright (C) 2012-2017 CypherCore + * + * 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, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +namespace Framework.Constants +{ + public enum UpdateType + { + Values = 0, + CreateObject = 1, + CreateObject2 = 2, + OutOfRangeObjects = 3, + } +} diff --git a/Framework/Constants/VehicleConst.cs b/Framework/Constants/VehicleConst.cs new file mode 100644 index 000000000..f13be6be2 --- /dev/null +++ b/Framework/Constants/VehicleConst.cs @@ -0,0 +1,64 @@ +/* + * Copyright (C) 2012-2017 CypherCore + * + * 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, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +namespace Framework.Constants +{ + public enum VehiclePowerType + { + Steam = 61, + Pyrite = 41, + Heat = 101, + Ooze = 121, + Blood = 141, + Wrath = 142, + ArcaneEnergy = 143, + LifeEnergy = 144, + SunEnergy = 145, + SwingVelocity = 146, + ShadowflameEnergy = 147, + BluePower = 148, + PurplePower = 149, + GreenPower = 150, + OrangePower = 151, + Energy2 = 153, + Arcaneenergy = 161, + Wind1 = 162, + Wind2 = 163, + Wind3 = 164, + Fuel = 165, + SunPower = 166, + TwilightEnergy = 169, + Venom = 174, + Orange2 = 176, + ConsumingFlame = 177, + PyroclasticFrenzy = 178, + Flashfire = 179, + } + + public enum VehicleFlags + { + NoStrafe = 0x01, // Sets Moveflag2NoStrafe + NoJumping = 0x02, // Sets Moveflag2NoJumping + Fullspeedturning = 0x04, // Sets Moveflag2Fullspeedturning + AllowPitching = 0x10, // Sets Moveflag2AllowPitching + Fullspeedpitching = 0x20, // Sets Moveflag2Fullspeedpitching + CustomPitch = 0x40, // If Set Use Pitchmin And Pitchmax From Dbc, Otherwise Pitchmin = -Pi/2, Pitchmax = Pi/2 + AdjustAimAngle = 0x400, // LuaIsvehicleaimangleadjustable + AdjustAimPower = 0x800, // LuaIsvehicleaimpoweradjustable + FixedPosition = 0x200000 // Used for cannons, when they should be rooted + } +} diff --git a/Framework/Cryptography/PacketCrypt.cs b/Framework/Cryptography/PacketCrypt.cs new file mode 100644 index 000000000..757419799 --- /dev/null +++ b/Framework/Cryptography/PacketCrypt.cs @@ -0,0 +1,101 @@ +/* + * Copyright (C) 2012-2017 CypherCore + * + * 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, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +using System; +using System.Security.Cryptography; + +namespace Framework.Cryptography +{ + public sealed class WorldCrypt : IDisposable + { + static readonly byte[] ServerEncryptionKey = { 0x08, 0xF1, 0x95, 0x9F, 0x47, 0xE5, 0xD2, 0xDB, 0xA1, 0x3D, 0x77, 0x8F, 0x3F, 0x3E, 0xE7, 0x00 }; + static readonly byte[] ServerDecryptionKey = { 0x40, 0xAA, 0xD3, 0x92, 0x26, 0x71, 0x43, 0x47, 0x3A, 0x31, 0x08, 0xA6, 0xE7, 0xDC, 0x98, 0x2A }; + + public void Initialize(byte[] sessionKey) + { + if (IsInitialized) + throw new InvalidOperationException("PacketCrypt already initialized!"); + + SARC4Encrypt = new SARC4(); + SARC4Decrypt = new SARC4(); + + var encryptSHA1 = new HMACSHA1(ServerEncryptionKey); + var decryptSHA1 = new HMACSHA1(ServerDecryptionKey); + + SARC4Encrypt.PrepareKey(encryptSHA1.ComputeHash(sessionKey)); + SARC4Decrypt.PrepareKey(decryptSHA1.ComputeHash(sessionKey)); + + var PacketEncryptionDummy = new byte[0x400]; + var PacketDecryptionDummy = new byte[0x400]; + + SARC4Encrypt.ProcessBuffer(PacketEncryptionDummy, PacketEncryptionDummy.Length); + SARC4Decrypt.ProcessBuffer(PacketDecryptionDummy, PacketDecryptionDummy.Length); + + IsInitialized = true; + } + + public void Initialize(byte[] sessionKey, byte[] serverSeed, byte[] clientSeed) + { + IsInitialized = false; + + if (IsInitialized) + throw new InvalidOperationException("PacketCrypt already initialized!"); + + SARC4Encrypt = new SARC4(); + SARC4Decrypt = new SARC4(); + + var encryptSHA1 = new HMACSHA1(serverSeed); + var decryptSHA1 = new HMACSHA1(clientSeed); + + SARC4Encrypt.PrepareKey(encryptSHA1.ComputeHash(sessionKey)); + SARC4Decrypt.PrepareKey(decryptSHA1.ComputeHash(sessionKey)); + + var PacketEncryptionDummy = new byte[0x400]; + var PacketDecryptionDummy = new byte[0x400]; + + SARC4Encrypt.ProcessBuffer(PacketEncryptionDummy, PacketEncryptionDummy.Length); + SARC4Decrypt.ProcessBuffer(PacketDecryptionDummy, PacketDecryptionDummy.Length); + + IsInitialized = true; + } + + public void Encrypt(byte[] data, int count) + { + if (!IsInitialized) + throw new InvalidOperationException("PacketCrypt not initialized!"); + + SARC4Encrypt.ProcessBuffer(data, count); + } + + public void Decrypt(byte[] data, int count) + { + if (!IsInitialized) + throw new InvalidOperationException("PacketCrypt not initialized!"); + + SARC4Decrypt.ProcessBuffer(data, count); + } + + public void Dispose() + { + IsInitialized = false; + } + + public bool IsInitialized { get; set; } + SARC4 SARC4Encrypt; + SARC4 SARC4Decrypt; + } +} diff --git a/Framework/Cryptography/RsaCrypt.cs b/Framework/Cryptography/RsaCrypt.cs new file mode 100644 index 000000000..ea3920a03 --- /dev/null +++ b/Framework/Cryptography/RsaCrypt.cs @@ -0,0 +1,117 @@ +/* + * Copyright (C) 2012-2017 CypherCore + * + * 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, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +using System; +using System.Numerics; + +namespace Framework.Cryptography +{ + public class RsaCrypt : IDisposable + { + public RsaCrypt() + { + Dispose(); + } + + public void InitializeEncryption(T p, T q, T dp, T dq, T iq, bool isBigEndian = false) + { + this._p = p.ToBigInteger(isBigEndian); + this._q = q.ToBigInteger(isBigEndian); + this._dp = dp.ToBigInteger(isBigEndian); + this._dq = dq.ToBigInteger(isBigEndian); + this._iq = iq.ToBigInteger(isBigEndian); + + if (this._p.IsZero && this._q.IsZero) + throw new InvalidOperationException("'0' isn't allowed for p or q"); + else + _isEncryptionInitialized = true; + } + + public void InitializeDecryption(T e, T n, bool reverseBytes = false) + { + this._e = e.ToBigInteger(reverseBytes); + this._n = n.ToBigInteger(reverseBytes); + + _isDecryptionInitialized = true; + } + + public byte[] Encrypt(T data, bool isBigEndian = false) + { + if (!_isEncryptionInitialized) + throw new InvalidOperationException("Encryption not initialized"); + + var bData = data.ToBigInteger(isBigEndian); + + var m1 = BigInteger.ModPow(bData % _p, _dp, _p); + var m2 = BigInteger.ModPow(bData % _q, _dq, _q); + + var h = (_iq * (m1 - m2)) % _p; + + // Be sure to use the positive remainder + if (h.Sign == -1) + h = _p + h; + + var m = m2 + h * _q; + + return m.ToByteArray(); + } + + public byte[] Decrypt(T data, bool isBigEndian = false) + { + if (!_isDecryptionInitialized) + throw new InvalidOperationException("Encryption not initialized"); + + var c = data.ToBigInteger(isBigEndian); + + return BigInteger.ModPow(c, _e, _n).ToByteArray(); + } + + public void Dispose() + { + this._e = 0; + this._n = 0; + this._p = 0; + this._q = 0; + this._dp = 0; + this._dq = 0; + this._iq = 0; + + _isEncryptionInitialized = false; + _isDecryptionInitialized = false; + } + + BigInteger _e; + BigInteger _n; + BigInteger _p; + BigInteger _q; + BigInteger _dp; + BigInteger _dq; + BigInteger _iq; + bool _isEncryptionInitialized; + bool _isDecryptionInitialized; + } + + public class RsaStore + { + public static byte[] DP = { 0xE1, 0xA6, 0x22, 0xAB, 0xFF, 0x57, 0x83, 0x45, 0x3F, 0x93, 0x76, 0xC8, 0xFA, 0xD9, 0x17, 0xE1, 0x49, 0x73, 0xC2, 0x13, 0x28, 0x0B, 0x1F, 0xE2, 0x9A, 0xF4, 0x7F, 0x7C, 0x37, 0x56, 0xA1, 0xDF, 0x51, 0x97, 0x2F, 0x15, 0x10, 0x97, 0xCD, 0x2A, 0x40, 0x09, 0xFC, 0x0A, 0xC3, 0x3F, 0x88, 0x86, 0xA9, 0x51, 0x13, 0xE1, 0x76, 0xCF, 0xA8, 0x37, 0x9A, 0x91, 0x3B, 0xD0, 0x70, 0xA1, 0xD7, 0x03, 0x71, 0x59, 0x6C, 0xB3, 0x41, 0xB8, 0x32, 0x68, 0x56, 0xC8, 0xB8, 0xD1, 0xF9, 0x1D, 0x04, 0xC5, 0x13, 0xB5, 0x8E, 0x57, 0x73, 0x02, 0x97, 0x7B, 0x33, 0x60, 0x68, 0xA9, 0xC2, 0x40, 0x96, 0x3C, 0x57, 0x4E, 0x4F, 0xC0, 0xAB, 0x21, 0x5C, 0xBA, 0x7D, 0x65, 0xAA, 0x1B, 0xD6, 0x43, 0x06, 0xCE, 0x3E, 0x0C, 0xB9, 0xB2, 0x82, 0xB0, 0xC9, 0x54, 0x59, 0x32, 0xC5, 0x88, 0x08, 0x9C, 0x9B, 0xBF }; + public static byte[] DQ = { 0xE3, 0xB1, 0xED, 0x52, 0xEF, 0xE6, 0x88, 0x40, 0x50, 0x89, 0x4C, 0x99, 0xE5, 0xF7, 0xED, 0x03, 0x1C, 0x54, 0x11, 0x24, 0x2F, 0x9D, 0xE8, 0xE6, 0x39, 0xFA, 0x19, 0xF4, 0x06, 0x55, 0x0B, 0x8B, 0x95, 0xC8, 0xB1, 0xE2, 0x7C, 0x75, 0x3B, 0x2A, 0x40, 0xC3, 0xE7, 0xE0, 0x25, 0x18, 0xBF, 0xB5, 0x03, 0x1B, 0x5A, 0x57, 0x92, 0x3C, 0x85, 0x7D, 0x7F, 0x43, 0x56, 0x1F, 0x1E, 0x80, 0xC3, 0xBA, 0xF0, 0x53, 0xD7, 0x6A, 0xD0, 0xF2, 0xDD, 0x9C, 0xC6, 0x53, 0xE7, 0xB4, 0xD3, 0x9D, 0xAB, 0xBF, 0xE0, 0x97, 0x50, 0x92, 0x23, 0xB9, 0xB7, 0xDC, 0xAA, 0xC4, 0x20, 0x93, 0x5A, 0xF5, 0xDE, 0x76, 0x28, 0x93, 0x91, 0x44, 0x1E, 0x4C, 0x15, 0x2F, 0x7F, 0x45, 0x3C, 0x3B, 0x7D, 0x36, 0x3B, 0x24, 0xC7, 0x8C, 0x65, 0x43, 0xAE, 0x65, 0x84, 0xBC, 0xF9, 0x76, 0x4E, 0x3C, 0x44, 0x05, 0xBC, 0xFA }; + public static byte[] InverseQ = { 0x63, 0xC1, 0x14, 0x2B, 0x57, 0x0B, 0x8A, 0x3C, 0x27, 0xDB, 0x96, 0x82, 0x27, 0xEB, 0xF6, 0x45, 0x6D, 0x07, 0x50, 0xE8, 0x4A, 0xD4, 0xB6, 0x7A, 0x3C, 0x8B, 0x4D, 0x65, 0xF0, 0x50, 0x70, 0x84, 0x71, 0x2B, 0xC6, 0x6D, 0x28, 0x2D, 0x76, 0x38, 0x73, 0x93, 0xDB, 0x44, 0xD7, 0xC0, 0x7F, 0xD9, 0x57, 0x18, 0x28, 0x57, 0xF1, 0x13, 0x38, 0xA4, 0x91, 0x67, 0x1E, 0x13, 0x73, 0x55, 0xFC, 0x7B, 0xAF, 0x50, 0xFA, 0xFD, 0x16, 0x12, 0x6F, 0xA4, 0x95, 0x15, 0x9C, 0x07, 0x18, 0xA6, 0x46, 0xFD, 0xB3, 0xCF, 0xA5, 0x0E, 0x05, 0x30, 0xEC, 0x2C, 0xCD, 0x62, 0xDD, 0x6F, 0xB1, 0xFE, 0x6C, 0x05, 0x2F, 0x11, 0xA6, 0xA0, 0x98, 0xAC, 0x9B, 0x15, 0xF0, 0x04, 0xC4, 0x7B, 0x79, 0xAA, 0x51, 0x25, 0x2A, 0x84, 0x73, 0xE6, 0x77, 0x47, 0xA3, 0xEB, 0xCF, 0x6D, 0xC8, 0x96, 0x3A, 0x1B, 0x02, 0x52 }; + public static byte[] P = { 0x7D, 0xBD, 0xB9, 0xE1, 0x2D, 0xAE, 0x42, 0x56, 0x6E, 0x2B, 0xE2, 0x89, 0xD9, 0xBB, 0x0C, 0x1F, 0x67, 0x28, 0xC1, 0x4D, 0x91, 0x3C, 0xAD, 0x5F, 0xF0, 0x43, 0x86, 0x5C, 0x27, 0xDC, 0x58, 0xB3, 0x0E, 0x75, 0x77, 0x78, 0x49, 0x35, 0xE7, 0xE7, 0xDF, 0xFD, 0x74, 0xAB, 0x4E, 0xFE, 0xD3, 0xAB, 0x6B, 0x96, 0xF7, 0x89, 0xB2, 0x5A, 0x6A, 0x25, 0x03, 0x5A, 0x92, 0x1A, 0xF1, 0xFC, 0x05, 0x4E, 0xCE, 0xDD, 0x37, 0xA4, 0x02, 0x53, 0x76, 0xCB, 0xC2, 0xD9, 0x63, 0xCB, 0x51, 0x94, 0xEC, 0x5C, 0x39, 0xCC, 0xB2, 0x17, 0x0C, 0xA3, 0x43, 0x9A, 0xD0, 0x83, 0x27, 0x67, 0x52, 0x64, 0x37, 0x0E, 0x38, 0xB7, 0x9B, 0xF4, 0x2D, 0xB8, 0x0F, 0x30, 0x72, 0xD3, 0x15, 0xF3, 0x2C, 0x39, 0x55, 0x72, 0x2C, 0x55, 0x80, 0x63, 0xA0, 0xA1, 0x6F, 0x28, 0xF3, 0xF3, 0x5A, 0x6F, 0x68, 0x59, 0xB3, 0xF3 }; + public static byte[] Q = { 0x0B, 0x1A, 0x13, 0x07, 0x12, 0xEF, 0xDD, 0x97, 0x01, 0x9A, 0x21, 0x7D, 0xFA, 0xA3, 0xB7, 0xE2, 0x39, 0x2E, 0x04, 0x92, 0x96, 0x45, 0x2A, 0xEB, 0x57, 0x03, 0xAC, 0xB1, 0x83, 0xCD, 0x25, 0x4F, 0x2C, 0xA9, 0xA1, 0x54, 0x26, 0x54, 0xCF, 0xE6, 0x1B, 0x53, 0x51, 0x3A, 0xC1, 0x15, 0xF4, 0x17, 0xBB, 0x17, 0x1F, 0x37, 0x66, 0x36, 0x1A, 0xD4, 0xB1, 0x5B, 0x49, 0xA8, 0xF1, 0x02, 0xB0, 0x42, 0xA9, 0x66, 0xA0, 0xE2, 0x52, 0x2C, 0x8C, 0x89, 0xA2, 0xDD, 0xA6, 0xF1, 0xA3, 0xDF, 0xB6, 0x80, 0x63, 0xB8, 0x10, 0xDA, 0xDE, 0x84, 0x56, 0xFA, 0xFB, 0x72, 0x65, 0x5E, 0xA3, 0x9C, 0x78, 0x65, 0xD0, 0x73, 0x07, 0x34, 0x1D, 0xE1, 0x4D, 0x77, 0xE8, 0x00, 0x0F, 0x80, 0x1C, 0x5A, 0x21, 0x55, 0x0A, 0x8C, 0xF4, 0x93, 0xF5, 0xF8, 0x40, 0xF2, 0x40, 0xEA, 0x52, 0x12, 0x40, 0xF0, 0xBF, 0xFA }; + public static byte[] WherePacketHmac = { 0x2C, 0x1F, 0x1D, 0x80, 0xC3, 0x8C, 0x23, 0x64, 0xDA, 0x90, 0xCA, 0x8E, 0x2C, 0xFC, 0x0C, 0xCE, 0x09, 0xD3, 0x62, 0xF9, 0xF3, 0x8B, 0xBE, 0x9F, 0x19, 0xEF, 0x58, 0xA1, 0x1C, 0x34, 0x14, 0x41, 0x3F, 0x23, 0xFD, 0xD3, 0xE8, 0x14, 0xEC, 0x2A, 0xFD, 0x4F, 0x95, 0xBA, 0x30, 0x7E, 0x56, 0x5D, 0x83, 0x95, 0x81, 0x69, 0xB0, 0x5A, 0xB4, 0x9D, 0xA8, 0x55, 0xFF, 0xFC, 0xEE, 0x58, 0x0A, 0x2F }; + } +} diff --git a/Framework/Cryptography/SARC4.cs b/Framework/Cryptography/SARC4.cs new file mode 100644 index 000000000..6605e6f56 --- /dev/null +++ b/Framework/Cryptography/SARC4.cs @@ -0,0 +1,68 @@ +/* + * Copyright (C) 2012-2014 Arctium Emulation + * Copyright (C) 2012-2017 CypherCore + * + * 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, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +namespace Framework.Cryptography +{ + //Thx Fabian over at Arctium. + public sealed class SARC4 + { + public SARC4() + { + _s = new byte[0x100]; + _tmp = 0; + _tmp2 = 0; + } + + public void PrepareKey(byte[] key) + { + for (int i = 0; i < 0x100; i++) + _s[i] = (byte)i; + + var j = 0; + for (int i = 0; i < 0x100; i++) + { + j = (byte)((j + key[i % key.Length] + _s[i]) & 255); + + var tempS = _s[i]; + + _s[i] = _s[j]; + _s[j] = tempS; + } + } + + public void ProcessBuffer(byte[] data, int length) + { + for (int i = 0; i < length; i++) + { + _tmp = (byte)((_tmp + 1) % 0x100); + _tmp2 = (byte)((_tmp2 + _s[_tmp]) % 0x100); + + var sTemp = _s[_tmp]; + + _s[_tmp] = _s[_tmp2]; + _s[_tmp2] = sTemp; + + data[i] = (byte)(_s[(_s[_tmp] + _s[_tmp2]) % 0x100] ^ data[i]); + } + } + + byte[] _s; + byte _tmp; + byte _tmp2; + } +} diff --git a/Framework/Cryptography/SessionKeyGeneration.cs b/Framework/Cryptography/SessionKeyGeneration.cs new file mode 100644 index 000000000..62e03c618 --- /dev/null +++ b/Framework/Cryptography/SessionKeyGeneration.cs @@ -0,0 +1,68 @@ +/* + * Copyright (C) 2012-2017 CypherCore + * + * 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, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +using System.Security.Cryptography; + +namespace Framework.Cryptography +{ + public class SessionKeyGenerator + { + public SessionKeyGenerator(byte[] buff, int size) + { + int halfSize = size / 2; + + sh = SHA256.Create(); + sh.TransformFinalBlock(buff, 0, halfSize); + o1 = sh.Hash; + + sh.Initialize(); + sh.TransformFinalBlock(buff, halfSize, size - halfSize); + o2 = sh.Hash; + + FillUp(); + } + + public void Generate(byte[] buf, uint sz) + { + for (uint i = 0; i < sz; ++i) + { + if (taken == 32) + FillUp(); + + buf[i] = o0[taken]; + taken++; + } + } + + void FillUp() + { + sh.Initialize(); + sh.TransformBlock(o1, 0, 32, o1, 0); + sh.TransformBlock(o0, 0, 32, o0, 0); + sh.TransformFinalBlock(o2, 0, 32); + o0 = sh.Hash; + + taken = 0; + } + + SHA256 sh; + uint taken; + byte[] o0 = new byte[32]; + byte[] o1 = new byte[32]; + byte[] o2 = new byte[32]; + } +} diff --git a/Framework/Cryptography/ShaHmac.cs b/Framework/Cryptography/ShaHmac.cs new file mode 100644 index 000000000..64c372517 --- /dev/null +++ b/Framework/Cryptography/ShaHmac.cs @@ -0,0 +1,153 @@ +/* + * Copyright (C) 2012-2017 CypherCore + * Copyright (C) 2012-2014 Arctium Emulation + * + * 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, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +using System; +using System.Security.Cryptography; +using System.Text; + +namespace Framework.Cryptography +{ + public class Sha256 : SHA256Managed + { + public byte[] Digest { get; private set; } + + public Sha256() + { + Initialize(); + } + + public void Process(byte[] data, int length) + { + TransformBlock(data, 0, length, data, 0); + } + + public void Process(uint data) + { + var bytes = BitConverter.GetBytes(data); + + TransformBlock(bytes, 0, 4, bytes, 0); + } + + public void Process(string data) + { + var bytes = Encoding.UTF8.GetBytes(data); + + TransformBlock(bytes, 0, bytes.Length, bytes, 0); + } + + public void Finish(byte[] data, int length) + { + TransformFinalBlock(data, 0, data.Length); + + Digest = base.Hash; + } + + public void Finish(byte[] data, int offset, int length) + { + TransformFinalBlock(data, offset, length); + + Digest = base.Hash; + } + } + + public class HmacHash : HMACSHA1 + { + public byte[] Digest { get; private set; } + + public HmacHash(byte[] key) : base(key, true) + { + Initialize(); + } + + public void Process(byte[] data, int length) + { + TransformBlock(data, 0, length, data, 0); + } + + public void Process(uint data) + { + var bytes = BitConverter.GetBytes(data); + + TransformBlock(bytes, 0, bytes.Length, bytes, 0); + } + + public void Process(string data) + { + var bytes = Encoding.ASCII.GetBytes(data); + + TransformBlock(bytes, 0, bytes.Length, bytes, 0); + } + + public void Finish(byte[] data, int length) + { + TransformFinalBlock(data, 0, length); + + Digest = Hash; + } + + public void Finish(string data) + { + var bytes = Encoding.ASCII.GetBytes(data); + + TransformFinalBlock(bytes, 0, bytes.Length); + + Digest = Hash; + } + } + + public class HmacSha256 : HMACSHA256 + { + public HmacSha256() : base() + { + Initialize(); + } + + public HmacSha256(byte[] key) : base(key) + { + Initialize(); + } + + public void Process(byte[] data, int length) + { + TransformBlock(data, 0, length, data, 0); + } + + public void Process(uint data) + { + var bytes = BitConverter.GetBytes(data); + + TransformBlock(bytes, 0, bytes.Length, bytes, 0); + } + + public void Process(string data) + { + var bytes = Encoding.ASCII.GetBytes(data); + + TransformBlock(bytes, 0, bytes.Length, bytes, 0); + } + + public void Finish(byte[] data, int length) + { + TransformFinalBlock(data, 0, length); + + Digest = Hash; + } + + public byte[] Digest { get; private set; } + } +} diff --git a/Framework/Database/DB.cs b/Framework/Database/DB.cs new file mode 100644 index 000000000..d19a3e1e1 --- /dev/null +++ b/Framework/Database/DB.cs @@ -0,0 +1,27 @@ +/* + * Copyright (C) 2012-2017 CypherCore + * + * 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, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +namespace Framework.Database +{ + public static class DB + { + public static LoginDatabase Login = new LoginDatabase(); + public static CharacterDatabase Characters = new CharacterDatabase(); + public static WorldDatabase World = new WorldDatabase(); + public static HotfixDatabase Hotfix = new HotfixDatabase(); + } +} diff --git a/Framework/Database/DatabaseLoader.cs b/Framework/Database/DatabaseLoader.cs new file mode 100644 index 000000000..a1348a9a3 --- /dev/null +++ b/Framework/Database/DatabaseLoader.cs @@ -0,0 +1,181 @@ +/* + * Copyright (C) 2012-2017 CypherCore + * + * 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, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +using Framework.Configuration; +using MySql.Data.MySqlClient; +using System; +using System.Collections.Generic; + +namespace Framework.Database +{ + public class DatabaseLoader + { + public DatabaseLoader(DatabaseTypeFlags defaultUpdateMask) + { + _autoSetup = ConfigMgr.GetDefaultValue("Updates.AutoSetup", true); + _updateFlags = ConfigMgr.GetDefaultValue("Updates.EnableDatabases", defaultUpdateMask); + } + + public void AddDatabase(MySqlBase database, string name) + { + bool updatesEnabled = database.IsAutoUpdateEnabled(_updateFlags); + _open.Add(() => + { + string dbString = ConfigMgr.GetDefaultValue(name + "DatabaseInfo", ""); + if (string.IsNullOrEmpty(dbString)) + { + Log.outError(LogFilter.ServerLoading, "Database {0} not specified in configuration file!", name); + return false; + } + + var error = database.Initialize(dbString); + if (error != MySqlErrorCode.None) + { + // Database does not exist + if (error == MySqlErrorCode.UnknownDatabase && updatesEnabled && _autoSetup) + { + Log.outInfo(LogFilter.ServerLoading, "Database \"{0}\" does not exist, do you want to create it? [yes (default) / no]: ", name); + + string answer = Console.ReadLine(); + if (string.IsNullOrEmpty(answer) && answer[0] != 'y') + return false; + + Log.outInfo(LogFilter.ServerLoading, "Creating database \"{0}\"...", name); + string sqlString = string.Format("CREATE DATABASE `{0}` DEFAULT CHARACTER SET utf8 COLLATE utf8_general_ci", name); + // Try to create the database and connect again if auto setup is enabled + if (database.Apply(sqlString) && database.Initialize(dbString) == MySqlErrorCode.None) + error = MySqlErrorCode.None; + } + + // If the error wasn't handled quit + if (error != MySqlErrorCode.None) + { + Log.outError(LogFilter.ServerLoading, "\nDatabase {0} NOT opened. There were errors opening the MySQL connections. Check your SQLErrors for specific errors.", name); + return false; + } + + Log.outInfo(LogFilter.ServerLoading, "Done."); + } + return true; + }); + + if (updatesEnabled) + { + // Populate and update only if updates are enabled for this pool + _populate.Add(() => + { + //Hack used to allow big querys + database.Apply("SET GLOBAL max_allowed_packet=1073741824;"); + if (!database.GetUpdater().Populate()) + { + Log.outError(LogFilter.ServerLoading, "Could not populate the {0} database, see log for details.", name); + return false; + } + return true; + }); + + _update.Add(() => + { + if (!database.GetUpdater().Update()) + { + Log.outError(LogFilter.ServerLoading, "Could not update the {0} database, see log for details.", name); + return false; + } + return true; + }); + } + + _prepare.Add(() => + { + database.LoadPreparedStatements(); + return true; + }); + } + + public bool Load() + { + if (_updateFlags == 0) + Log.outInfo(LogFilter.SqlUpdates, "Automatic database updates are disabled for all databases!"); + + if (!OpenDatabases()) + return false; + + if (!PopulateDatabases()) + return false; + + if (!UpdateDatabases()) + return false; + + if (!PrepareStatements()) + return false; + + return true; + } + + bool OpenDatabases() + { + return Process(_open); + } + + // Processes the elements of the given stack until a predicate returned false. + bool Process(List> list) + { + while (!list.Empty()) + { + if (!list[0].Invoke()) + return false; + + list.RemoveAt(0); + } + return true; + } + + bool PopulateDatabases() + { + return Process(_populate); + } + + bool UpdateDatabases() + { + return Process(_update); + } + + bool PrepareStatements() + { + return Process(_prepare); + } + + bool _autoSetup; + DatabaseTypeFlags _updateFlags; + List> _open = new List>(); + List> _populate = new List>(); + List> _update = new List>(); + List> _prepare = new List>(); + } + + public enum DatabaseTypeFlags + { + None = 0, + + Login = 1, + Character = 2, + World = 4, + Hotfix = 8, + + All = Login | Character | World | Hotfix + } +} diff --git a/Framework/Database/DatabaseUpdater.cs b/Framework/Database/DatabaseUpdater.cs new file mode 100644 index 000000000..64c8237f6 --- /dev/null +++ b/Framework/Database/DatabaseUpdater.cs @@ -0,0 +1,466 @@ +/* + * Copyright (C) 2012-2017 CypherCore + * + * 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, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +using Framework.Configuration; +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Security.Cryptography; +using System.Text; + +namespace Framework.Database +{ + public class DatabaseUpdater + { + public DatabaseUpdater(MySqlBase database) + { + _database = database; + } + + public bool Populate() + { + SQLResult result = _database.Query("SHOW TABLES"); + if (!result.IsEmpty() && result.GetRowCount() > 0) + return true; + + Log.outInfo(LogFilter.SqlUpdates, "Database {0} is empty, auto populating it...", _database.GetDatabaseName()); + + string path = GetSourceDirectory(); + string fileName = "Unknown"; + switch (_database.GetType().Name) + { + case "LoginDatabase": + fileName = @"\sql\base\auth_database.sql"; + break; + case "CharacterDatabase": + fileName = @"\sql\base\characters_database.sql"; + break; + case "WorldDatabase": + fileName = @"\sql\base\TDB_world_703.00_2016_10_17.sql"; + break; + case "HotfixDatabase": + fileName = @"\sql\base\TDB_hotfixes_703.00_2016_10_17.sql"; + break; + } + + if (!File.Exists(path + fileName)) + { + Log.outError(LogFilter.SqlUpdates, "File \"{0}\" is missing, download it from \"http://www.trinitycore.org/f/files/category/1-database/\"" + + " and place it in your worldserver directory.", fileName); + return false; + } + + // Update database + Log.outInfo(LogFilter.SqlUpdates, "Applying \'{0}\'...", fileName); + _database.ApplyFile(path + fileName); + + Log.outInfo(LogFilter.SqlUpdates, "Done Applying \'{0}\'", fileName); + return true; + } + + public bool Update() + { + Log.outInfo(LogFilter.SqlUpdates, "Updating {0} database...", _database.GetDatabaseName()); + + string sourceDirectory = GetSourceDirectory(); + + if (!Directory.Exists(sourceDirectory)) + { + Log.outError(LogFilter.SqlUpdates, "DBUpdater: Given source directory {0} does not exist, skipped!", sourceDirectory); + return false; + } + + var availableFiles = GetFileList(); + var appliedFiles = ReceiveAppliedFiles(); + + bool redundancyChecks = ConfigMgr.GetDefaultValue("Updates.Redundancy", true); + bool archivedRedundancy = ConfigMgr.GetDefaultValue("Updates.Redundancy", true); + + UpdateResult result = new UpdateResult(); + + // Count updates + foreach (var entry in appliedFiles) + { + if (entry.Value.State == State.RELEASED) + ++result.recent; + else + ++result.archived; + } + + foreach (var availableQuery in availableFiles) + { + Log.outDebug(LogFilter.SqlUpdates, "Checking update \"{0}\"...", availableQuery.GetFileName()); + + var applied = appliedFiles.LookupByKey(availableQuery.GetFileName()); + if (applied != null) + { + // If redundancy is disabled skip it since the update is already applied. + if (!redundancyChecks) + { + Log.outDebug(LogFilter.SqlUpdates, "Update is already applied, skipping redundancy checks."); + appliedFiles.Remove(availableQuery.GetFileName()); + continue; + } + + // If the update is in an archived directory and is marked as archived in our database skip redundancy checks (archived updates never change). + if (!archivedRedundancy && (applied.State == State.ARCHIVED) && (availableQuery.state == State.ARCHIVED)) + { + Log.outDebug(LogFilter.SqlUpdates, "Update is archived and marked as archived in database, skipping redundancy checks."); + appliedFiles.Remove(availableQuery.GetFileName()); + continue; + } + } + + // Calculate hash + string hash = CalculateHash(availableQuery.path); + + UpdateMode mode = UpdateMode.Apply; + + // Update is not in our applied list + if (applied == null) + { + // Catch renames (different filename but same hash) + var hashIter = appliedFiles.Values.FirstOrDefault(p => p.Hash == hash); + if (hashIter != null) + { + // Check if the original file was removed if not we've got a problem. + var renameFile = availableFiles.Find(p => p.GetFileName() == hashIter.Name); + if (renameFile != null) + { + Log.outWarn(LogFilter.SqlUpdates, "Seems like update \"{0}\" \'{1}\' was renamed, but the old file is still there! " + + "Trade it as a new file! (Probably its an unmodified copy of file \"{2}\")", + availableQuery.GetFileName(), hash.Substring(0, 7), renameFile.GetFileName()); + } + // Its save to trade the file as renamed here + else + { + Log.outInfo(LogFilter.SqlUpdates, "Renaming update \"{0}\" to \"{1}\" \'{2}\'.", + hashIter.Name, availableQuery.GetFileName(), hash.Substring(0, 7)); + + RenameEntry(hashIter.Name, availableQuery.GetFileName()); + appliedFiles.Remove(hashIter.Name); + continue; + } + } + // Apply the update if it was never seen before. + else + { + Log.outInfo(LogFilter.SqlUpdates, "Applying update \"{0}\" \'{1}\'...", availableQuery.GetFileName(), hash.Substring(0, 7)); + } + } + // Rehash the update entry if it is contained in our database but with an empty hash. + else if (ConfigMgr.GetDefaultValue("Updates.AllowRehash", true) && string.IsNullOrEmpty(applied.Hash)) + { + mode = UpdateMode.Rehash; + + Log.outInfo(LogFilter.SqlUpdates, "Re-hashing update \"{0}\" \'{1}\'...", availableQuery.GetFileName(), hash.Substring(0, 7)); + } + else + { + // If the hash of the files differs from the one stored in our database reapply the update (because it was changed). + if (applied.Hash != hash) + { + Log.outInfo(LogFilter.SqlUpdates, "Reapplying update \"{0}\" \'{1}\' . \'{2}\' (it changed)...", availableQuery.GetFileName(), + applied.Hash.Substring(0, 7), hash.Substring(0, 7)); + } + else + { + // If the file wasn't changed and just moved update its state if necessary. + if (applied.State != availableQuery.state) + { + Log.outDebug(LogFilter.SqlUpdates, "Updating state of \"{0}\" to \'{1}\'...", availableQuery.GetFileName(), availableQuery.state); + + UpdateState(availableQuery.GetFileName(), availableQuery.state); + } + + Log.outDebug(LogFilter.SqlUpdates, "Update is already applied and is matching hash \'{0}\'.", hash.Substring(0, 7)); + + appliedFiles.Remove(applied.Name); + continue; + } + } + + uint speed = 0; + AppliedFileEntry file = new AppliedFileEntry(availableQuery.GetFileName(), hash, availableQuery.state, 0); + + switch (mode) + { + case UpdateMode.Apply: + speed = ApplyTimedFile(availableQuery.path); + goto case UpdateMode.Rehash; + case UpdateMode.Rehash: + UpdateEntry(file, speed); + break; + } + + if (applied != null) + appliedFiles.Remove(applied.Name); + + if (mode == UpdateMode.Apply) + ++result.updated; + } + + // Cleanup up orphaned entries if enabled + if (!appliedFiles.Empty()) + { + int cleanDeadReferencesMaxCount = ConfigMgr.GetDefaultValue("Updates.CleanDeadRefMaxCount", 3); + bool doCleanup = (cleanDeadReferencesMaxCount < 0) || (appliedFiles.Count <= cleanDeadReferencesMaxCount); + + foreach (var entry in appliedFiles) + { + Log.outWarn(LogFilter.SqlUpdates, "File \'{0}\' was applied to the database but is missing in your update directory now!", entry.Key); + + if (doCleanup) + Log.outInfo(LogFilter.SqlUpdates, "Deleting orphaned entry \'{0}\'...", entry.Key); + } + + if (doCleanup) + CleanUp(appliedFiles); + else + { + Log.outError(LogFilter.SqlUpdates, "Cleanup is disabled! There are {0} dirty files that were applied to your database but are now missing in your source directory!", appliedFiles.Count); + } + } + + string info = string.Format("Containing {0} new and {1} archived updates.", result.recent, result.archived); + + if (result.updated == 0) + Log.outInfo(LogFilter.SqlUpdates, "{0} database is up-to-date! {1}", _database.GetDatabaseName(), info); + else + Log.outInfo(LogFilter.SqlUpdates, "Applied {0} {1}. {2}", result.updated, result.updated == 1 ? "query" : "queries", info); + + return true; + } + + string GetSourceDirectory() + { + string entry = ConfigMgr.GetDefaultValue("Updates.SourcePath", Environment.CurrentDirectory); + if (!string.IsNullOrEmpty(entry)) + return entry; + else + return Directory.GetParent(Directory.GetCurrentDirectory()).Parent.FullName; + } + + uint ApplyTimedFile(string path) + { + // Benchmark query speed + uint oldMSTime = Time.GetMSTime(); + + // Update database + _database.ApplyFile(path); + + // Return time the query took to apply + return Time.GetMSTimeDiffToNow(oldMSTime); + } + + void UpdateEntry(AppliedFileEntry entry, uint speed) + { + string update = "REPLACE INTO `updates` (`name`, `hash`, `state`, `speed`) VALUES (\"" + + entry.Name + "\", \"" + entry.Hash + "\", \'" + entry.State + "\', " + speed + ")"; + + // Update database + _database.Execute(update); + } + + void RenameEntry(string from, string to) + { + // Delete target if it exists + { + string update = "DELETE FROM `updates` WHERE `name`=\"" + to + "\""; + + // Update database + _database.Execute(update); + } + + // Rename + { + string update = "UPDATE `updates` SET `name`=\"" + to + "\" WHERE `name`=\"" + from + "\""; + + // Update database + _database.Execute(update); + } + } + + void CleanUp(Dictionary storage) + { + if (storage.Empty()) + return; + + int remaining = storage.Count; + string update = "DELETE FROM `updates` WHERE `name` IN("; + + foreach (var entry in storage) + { + update += "\"" + entry.Key + "\""; + if ((--remaining) > 0) + update += ", "; + } + + update += ")"; + + // Update database + _database.Execute(update); + } + + void UpdateState(string name, State state) + { + string update = "UPDATE `updates` SET `state`=\'" + state + "\' WHERE `name`=\"" + name + "\""; + + // Update database + _database.Execute(update); + } + + public List GetFileList() + { + List fileList = new List(); + + SQLResult result = _database.Query("SELECT `path`, `state` FROM `updates_include`"); + if (result.IsEmpty()) + return fileList; + + do + { + string path = result.Read(0); + if (path[0] == '$') + path = GetSourceDirectory() + path.Substring(1); + + if (!Directory.Exists(path)) + { + Log.outWarn(LogFilter.SqlUpdates, "DBUpdater: Given update include directory \"{0}\" isn't existing, skipped!", path); + continue; + } + + State state = result.Read(1).ToEnum(); + fileList.AddRange(GetFilesFromDirectory(path, state)); + + Log.outDebug(LogFilter.SqlUpdates, "Added applied file \"{0}\" from remote.", path); + + } while (result.NextRow()); + + return fileList; + } + + public Dictionary ReceiveAppliedFiles() + { + Dictionary map = new Dictionary(); + + SQLResult result = _database.Query("SELECT `name`, `hash`, `state`, UNIX_TIMESTAMP(`timestamp`) FROM `updates` ORDER BY `name` ASC"); + if (result.IsEmpty()) + return map; + + do + { + AppliedFileEntry entry = new AppliedFileEntry(result.Read(0), result.Read(1), result.Read(2).ToEnum(), result.Read(3)); + map.Add(entry.Name, entry); + } + while (result.NextRow()); + + return map; + } + + IEnumerable GetFilesFromDirectory(string directory, State state) + { + Queue queue = new Queue(); + queue.Enqueue(directory); + while (queue.Count > 0) + { + directory = queue.Dequeue(); + try + { + foreach (string subDir in Directory.GetDirectories(directory)) + { + queue.Enqueue(subDir); + } + } + catch (Exception ex) + { + Console.Error.WriteLine(ex); + } + + string[] files = Directory.GetFiles(directory, "*.sql"); + for (int i = 0; i < files.Length; i++) + { + yield return new FileEntry(files[i], state); + } + } + } + + string CalculateHash(string fileName) + { + using (SHA1 sha1 = new SHA1Managed()) + { + string text = File.ReadAllText(fileName).Replace("\r", ""); + return sha1.ComputeHash(Encoding.UTF8.GetBytes(text)).ToHexString(); + } + } + + protected MySqlBase _database; + } + + public class AppliedFileEntry + { + public AppliedFileEntry(string name, string hash, State state, ulong timestamp) + { + Name = name; + Hash = hash; + State = state; + Timestamp = timestamp; + } + + public string Name; + public string Hash; + public State State; + public ulong Timestamp; + } + + public class FileEntry + { + public FileEntry(string _path, State _state) + { + path = _path; + state = _state; + } + + public string GetFileName() + { + return Path.GetFileName(path); + } + + public string path; + public State state; + } + + struct UpdateResult + { + public int updated; + public int recent; + public int archived; + } + + public enum State + { + RELEASED, + ARCHIVED + } + + enum UpdateMode + { + Apply, + Rehash + } +} diff --git a/Framework/Database/Databases/CharacterDatabase.cs b/Framework/Database/Databases/CharacterDatabase.cs new file mode 100644 index 000000000..0186f250a --- /dev/null +++ b/Framework/Database/Databases/CharacterDatabase.cs @@ -0,0 +1,1336 @@ +/* + * Copyright (C) 2012-2017 CypherCore + * + * 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, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +namespace Framework.Database +{ + public class CharacterDatabase : MySqlBase + { + public override void PreparedStatements() + { + const string SelectItemInstanceContent = "ii.guid, ii.itemEntry, ii.creatorGuid, ii.giftCreatorGuid, ii.count, ii.duration, ii.charges, ii.flags, ii.enchantments, ii.randomPropertyType, ii.randomPropertyId, " + + "ii.durability, ii.playedTime, ii.text, ii.upgradeId, ii.battlePetSpeciesId, ii.battlePetBreedData, ii.battlePetLevel, ii.battlePetDisplayId, ii.context, ii.bonusListIDs, " + + "iit.itemModifiedAppearanceAllSpecs, iit.itemModifiedAppearanceSpec1, iit.itemModifiedAppearanceSpec2, iit.itemModifiedAppearanceSpec3, iit.itemModifiedAppearanceSpec4, " + + "iit.spellItemEnchantmentAllSpecs, iit.spellItemEnchantmentSpec1, iit.spellItemEnchantmentSpec2, iit.spellItemEnchantmentSpec3, iit.spellItemEnchantmentSpec4, " + + "ig.gemItemId1, ig.gemBonuses1, ig.gemContext1, ig.gemScalingLevel1, ig.gemItemId2, ig.gemBonuses2, ig.gemContext2, ig.gemScalingLevel2, ig.gemItemId3, ig.gemBonuses3, ig.gemContext3, ig.gemScalingLevel3, " + + "im.fixedScalingLevel, im.artifactKnowledgeLevel"; + + PrepareStatement(CharStatements.DEL_QUEST_POOL_SAVE, "DELETE FROM pool_quest_save WHERE pool_id = ?"); + PrepareStatement(CharStatements.INS_QUEST_POOL_SAVE, "INSERT INTO pool_quest_save (pool_id, quest_id) VALUES (?, ?)"); + PrepareStatement(CharStatements.DEL_NONEXISTENT_GUILD_BANK_ITEM, "DELETE FROM guild_bank_item WHERE guildid = ? AND TabId = ? AND SlotId = ?"); + PrepareStatement(CharStatements.DEL_EXPIRED_BANS, "UPDATE character_banned SET active = 0 WHERE unbandate <= UNIX_TIMESTAMP() AND unbandate <> bandate"); + PrepareStatement(CharStatements.SEL_GUID_BY_NAME, "SELECT guid FROM characters WHERE name = ?"); + PrepareStatement(CharStatements.SEL_CHECK_NAME, "SELECT 1 FROM characters WHERE name = ?"); + PrepareStatement(CharStatements.SEL_CHECK_GUID, "SELECT 1 FROM characters WHERE guid = ?"); + PrepareStatement(CharStatements.SEL_SUM_CHARS, "SELECT COUNT(guid) FROM characters WHERE account = ?"); + PrepareStatement(CharStatements.SEL_CHAR_CREATE_INFO, "SELECT level, race, class FROM characters WHERE account = ? LIMIT 0, ?"); + PrepareStatement(CharStatements.INS_CHARACTER_BAN, "INSERT INTO character_banned VALUES (?, UNIX_TIMESTAMP(), UNIX_TIMESTAMP()+?, ?, ?, 1)"); + PrepareStatement(CharStatements.UPD_CHARACTER_BAN, "UPDATE character_banned SET active = 0 WHERE guid = ? AND active != 0"); + PrepareStatement(CharStatements.DEL_CHARACTER_BAN, "DELETE cb FROM character_banned cb INNER JOIN characters c ON c.guid = cb.guid WHERE c.account = ?"); + PrepareStatement(CharStatements.SEL_BANINFO, "SELECT bandate, unbandate-bandate, active, unbandate, banreason, bannedby FROM character_banned WHERE guid = ? ORDER BY bandate ASC"); + PrepareStatement(CharStatements.SEL_GUID_BY_NAME_FILTER, "SELECT guid, name FROM characters WHERE name LIKE CONCAT('%%', ?, '%%')"); + PrepareStatement(CharStatements.SEL_BANINFO_LIST, "SELECT bandate, unbandate, bannedby, banreason FROM character_banned WHERE guid = ? ORDER BY unbandate"); + PrepareStatement(CharStatements.SEL_BANNED_NAME, "SELECT characters.name FROM characters, character_banned WHERE character_banned.guid = ? AND character_banned.guid = characters.guid"); + PrepareStatement(CharStatements.SEL_MAIL_LIST_COUNT, "SELECT COUNT(id) FROM mail WHERE receiver = ? "); + PrepareStatement(CharStatements.SEL_MAIL_LIST_INFO, "SELECT id, sender, (SELECT name FROM characters WHERE guid = sender) AS sendername, receiver, (SELECT name FROM characters WHERE guid = receiver) AS receivername, " + + "subject, deliver_time, expire_time, money, has_items FROM mail WHERE receiver = ? "); + PrepareStatement(CharStatements.SEL_MAIL_LIST_ITEMS, "SELECT itemEntry,count FROM item_instance WHERE guid = ?"); + PrepareStatement(CharStatements.SEL_ENUM, "SELECT c.guid, c.name, c.race, c.class, c.gender, c.skin, c.face, c.hairStyle, c.hairColor, c.facialStyle, c.customDisplay1, c.customDisplay2, c.customDisplay3, c.level, c.zone, c.map, c.position_x, c.position_y, c.position_z, " + + "gm.guildid, c.playerFlags, c.at_login, cp.entry, cp.modelid, cp.level, c.equipmentCache, cb.guid, c.slot, c.logout_time, c.activeTalentGroup " + + "FROM characters AS c LEFT JOIN character_pet AS cp ON c.guid = cp.owner AND cp.slot = ? LEFT JOIN guild_member AS gm ON c.guid = gm.guid " + + "LEFT JOIN character_banned AS cb ON c.guid = cb.guid AND cb.active = 1 WHERE c.account = ? AND c.deleteInfos_Name IS NULL"); + PrepareStatement(CharStatements.SEL_ENUM_DECLINED_NAME, "SELECT c.guid, c.name, c.race, c.class, c.gender, c.skin, c.face, c.hairStyle, c.hairColor, c.facialStyle, c.customDisplay1, c.customDisplay2, c.customDisplay3, c.level, c.zone, c.map, " + + "c.position_x, c.position_y, c.position_z, gm.guildid, c.playerFlags, c.at_login, cp.entry, cp.modelid, cp.level, c.equipmentCache, " + + "cb.guid, c.slot, c.logout_time, c.activeTalentGroup, cd.genitive FROM characters AS c LEFT JOIN character_pet AS cp ON c.guid = cp.owner AND cp.slot = ? " + + "LEFT JOIN character_declinedname AS cd ON c.guid = cd.guid LEFT JOIN guild_member AS gm ON c.guid = gm.guid " + + "LEFT JOIN character_banned AS cb ON c.guid = cb.guid AND cb.active = 1 WHERE c.account = ? AND c.deleteInfos_Name IS NULL"); + PrepareStatement(CharStatements.SEL_UNDELETE_ENUM, "SELECT c.guid, c.deleteInfos_Name, c.race, c.class, c.gender, c.skin, c.face, c.hairStyle, c.hairColor, c.facialStyle, c.customDisplay1, c.customDisplay2, c.customDisplay3, c.level, c.zone, c.map, c.position_x, c.position_y, c.position_z, " + + "gm.guildid, c.playerFlags, c.at_login, cp.entry, cp.modelid, cp.level, c.equipmentCache, cb.guid, c.slot, c.logout_time, c.activeTalentGroup " + + "FROM characters AS c LEFT JOIN character_pet AS cp ON c.guid = cp.owner AND cp.slot = ? LEFT JOIN guild_member AS gm ON c.guid = gm.guid " + + "LEFT JOIN character_banned AS cb ON c.guid = cb.guid AND cb.active = 1 WHERE c.deleteInfos_Account = ? AND c.deleteInfos_Name IS NOT NULL"); + PrepareStatement(CharStatements.SEL_UNDELETE_ENUM_DECLINED_NAME, "SELECT c.guid, c.deleteInfos_Name, c.race, c.class, c.gender, c.skin, c.face, c.hairStyle, c.hairColor, c.facialStyle, c.customDisplay1, c.customDisplay2, c.customDisplay3, c.level, c.zone, c.map, " + + "c.position_x, c.position_y, c.position_z, gm.guildid, c.playerFlags, c.at_login, cp.entry, cp.modelid, cp.level, c.equipmentCache, " + + "cb.guid, c.slot, c.logout_time, c.activeTalentGroup, cd.genitive FROM characters AS c LEFT JOIN character_pet AS cp ON c.guid = cp.owner AND cp.slot = ? " + + "LEFT JOIN character_declinedname AS cd ON c.guid = cd.guid LEFT JOIN guild_member AS gm ON c.guid = gm.guid " + + "LEFT JOIN character_banned AS cb ON c.guid = cb.guid AND cb.active = 1 WHERE c.deleteInfos_Account = ? AND c.deleteInfos_Name IS NOT NULL"); + PrepareStatement(CharStatements.SEL_FREE_NAME, "SELECT name, at_login FROM characters WHERE guid = ? AND NOT EXISTS (SELECT NULL FROM characters WHERE name = ?)"); + PrepareStatement(CharStatements.SEL_GUID_RACE_ACC_BY_NAME, "SELECT guid, race, account FROM characters WHERE name = ?"); + PrepareStatement(CharStatements.SEL_CHAR_LEVEL, "SELECT level FROM characters WHERE guid = ?"); + PrepareStatement(CharStatements.SEL_CHAR_ZONE, "SELECT zone FROM characters WHERE guid = ?"); + PrepareStatement(CharStatements.SEL_CHAR_POSITION_XYZ, "SELECT map, position_x, position_y, position_z FROM characters WHERE guid = ?"); + PrepareStatement(CharStatements.SEL_CHAR_POSITION, "SELECT position_x, position_y, position_z, orientation, map, taxi_path FROM characters WHERE guid = ?"); + + PrepareStatement(CharStatements.DEL_BATTLEGROUND_RANDOM_ALL, "DELETE FROM character_battleground_random"); + PrepareStatement(CharStatements.DEL_BATTLEGROUND_RANDOM, "DELETE FROM character_battleground_random WHERE guid = ?"); + PrepareStatement(CharStatements.INS_BATTLEGROUND_RANDOM, "INSERT INTO character_battleground_random (guid) VALUES (?)"); + + PrepareStatement(CharStatements.SEL_CHARACTER, "SELECT c.guid, account, name, race, class, gender, level, xp, money, skin, face, hairStyle, hairColor, facialStyle, customDisplay1, customDisplay2, customDisplay3, bankSlots, restState, playerFlags, playerFlagsEx, " + + "position_x, position_y, position_z, map, orientation, taximask, cinematic, totaltime, leveltime, rest_bonus, logout_time, is_logout_resting, resettalents_cost, " + + "resettalents_time, primarySpecialization, trans_x, trans_y, trans_z, trans_o, transguid, extra_flags, stable_slots, at_login, zone, online, death_expire_time, taxi_path, dungeonDifficulty, " + + "totalKills, todayKills, yesterdayKills, chosenTitle, watchedFaction, drunk, " + + "health, power1, power2, power3, power4, power5, power6, instance_id, activeTalentGroup, lootSpecId, exploredZones, knownTitles, actionBars, grantableLevels, raidDifficulty, legacyRaidDifficulty, fishingSteps, " + + "honor, honorLevel, prestigeLevel, honorRestState, honorRestBonus " + + "FROM characters c LEFT JOIN character_fishingsteps cfs ON c.guid = cfs.guid WHERE c.guid = ?"); + + PrepareStatement(CharStatements.SEL_GROUP_MEMBER, "SELECT guid FROM group_member WHERE memberGuid = ?"); + PrepareStatement(CharStatements.SEL_CHARACTER_INSTANCE, "SELECT id, permanent, map, difficulty, extendState, resettime, entranceId FROM character_instance LEFT JOIN instance ON instance = id WHERE guid = ?"); + PrepareStatement(CharStatements.SEL_CHARACTER_AURAS, "SELECT casterGuid, itemGuid, spell, effectMask, recalculateMask, stackCount, maxDuration, remainTime, remainCharges, castItemLevel FROM character_aura WHERE guid = ?"); + PrepareStatement(CharStatements.SEL_CHARACTER_AURA_EFFECTS, "SELECT casterGuid, itemGuid, spell, effectMask, effectIndex, amount, baseAmount FROM character_aura_effect WHERE guid = ?"); + PrepareStatement(CharStatements.SEL_CHARACTER_SPELL, "SELECT spell, active, disabled FROM character_spell WHERE guid = ?"); + PrepareStatement(CharStatements.SEL_CHARACTER_QUESTSTATUS, "SELECT quest, status, timer FROM character_queststatus WHERE guid = ? AND status <> 0"); + PrepareStatement(CharStatements.SEL_CHARACTER_QUESTSTATUS_OBJECTIVES, "SELECT quest, objective, data FROM character_queststatus_objectives WHERE guid = ?"); + + PrepareStatement(CharStatements.SEL_CHARACTER_QUESTSTATUS_DAILY, "SELECT quest, time FROM character_queststatus_daily WHERE guid = ?"); + PrepareStatement(CharStatements.SEL_CHARACTER_QUESTSTATUS_WEEKLY, "SELECT quest FROM character_queststatus_weekly WHERE guid = ?"); + PrepareStatement(CharStatements.SEL_CHARACTER_QUESTSTATUS_MONTHLY, "SELECT quest FROM character_queststatus_monthly WHERE guid = ?"); + PrepareStatement(CharStatements.SEL_CHARACTER_QUESTSTATUS_SEASONAL, "SELECT quest, event FROM character_queststatus_seasonal WHERE guid = ?"); + PrepareStatement(CharStatements.DEL_CHARACTER_QUESTSTATUS_DAILY, "DELETE FROM character_queststatus_daily WHERE guid = ?"); + PrepareStatement(CharStatements.DEL_CHARACTER_QUESTSTATUS_WEEKLY, "DELETE FROM character_queststatus_weekly WHERE guid = ?"); + PrepareStatement(CharStatements.DEL_CHARACTER_QUESTSTATUS_MONTHLY, "DELETE FROM character_queststatus_monthly WHERE guid = ?"); + PrepareStatement(CharStatements.DEL_CHARACTER_QUESTSTATUS_SEASONAL, "DELETE FROM character_queststatus_seasonal WHERE guid = ?"); + PrepareStatement(CharStatements.INS_CHARACTER_QUESTSTATUS_DAILY, "INSERT INTO character_queststatus_daily (guid, quest, time) VALUES (?, ?, ?)"); + PrepareStatement(CharStatements.INS_CHARACTER_QUESTSTATUS_WEEKLY, "INSERT INTO character_queststatus_weekly (guid, quest) VALUES (?, ?)"); + PrepareStatement(CharStatements.INS_CHARACTER_QUESTSTATUS_MONTHLY, "INSERT INTO character_queststatus_monthly (guid, quest) VALUES (?, ?)"); + PrepareStatement(CharStatements.INS_CHARACTER_QUESTSTATUS_SEASONAL, "INSERT INTO character_queststatus_seasonal (guid, quest, event) VALUES (?, ?, ?)"); + PrepareStatement(CharStatements.DEL_RESET_CHARACTER_QUESTSTATUS_DAILY, "DELETE FROM character_queststatus_daily"); + PrepareStatement(CharStatements.DEL_RESET_CHARACTER_QUESTSTATUS_WEEKLY, "DELETE FROM character_queststatus_weekly"); + PrepareStatement(CharStatements.DEL_RESET_CHARACTER_QUESTSTATUS_MONTHLY, "DELETE FROM character_queststatus_monthly"); + PrepareStatement(CharStatements.DEL_RESET_CHARACTER_QUESTSTATUS_SEASONAL_BY_EVENT, "DELETE FROM character_queststatus_seasonal WHERE event = ?"); + + PrepareStatement(CharStatements.SEL_CHARACTER_REPUTATION, "SELECT faction, standing, flags FROM character_reputation WHERE guid = ?"); + PrepareStatement(CharStatements.SEL_CHARACTER_INVENTORY, "SELECT " + SelectItemInstanceContent + ", bag, slot FROM character_inventory ci JOIN item_instance ii ON ci.item = ii.guid LEFT JOIN item_instance_gems ig ON ii.guid = ig.itemGuid LEFT JOIN item_instance_transmog iit ON ii.guid = iit.itemGuid LEFT JOIN item_instance_modifiers im ON ii.guid = im.itemGuid WHERE ci.guid = ? ORDER BY (ii.flags & 0x80000) ASC, bag ASC, slot ASC"); + PrepareStatement(CharStatements.SEL_CHARACTER_ACTIONS, "SELECT a.button, a.action, a.type FROM character_action as a, characters as c WHERE a.guid = c.guid AND a.spec = c.activeTalentGroup AND a.guid = ? ORDER BY button"); + PrepareStatement(CharStatements.SEL_CHARACTER_MAILCOUNT, "SELECT COUNT(id) FROM mail WHERE receiver = ? AND (checked & 1) = 0 AND deliver_time <= ?"); + PrepareStatement(CharStatements.SEL_CHARACTER_MAILDATE, "SELECT MIN(deliver_time) FROM mail WHERE receiver = ? AND (checked & 1) = 0"); + PrepareStatement(CharStatements.SEL_MAIL_COUNT, "SELECT COUNT(*) FROM mail WHERE receiver = ?"); + PrepareStatement(CharStatements.SEL_CHARACTER_SOCIALLIST, "SELECT cs.friend, c.account, cs.flags, cs.note FROM character_social cs JOIN characters c ON c.guid = cs.friend WHERE cs.guid = ? AND c.deleteinfos_name IS NULL LIMIT 255"); + PrepareStatement(CharStatements.SEL_CHARACTER_HOMEBIND, "SELECT mapId, zoneId, posX, posY, posZ FROM character_homebind WHERE guid = ?"); + PrepareStatement(CharStatements.SEL_CHARACTER_SPELLCOOLDOWNS, "SELECT spell, item, time, categoryId, categoryEnd FROM character_spell_cooldown WHERE guid = ? AND time > UNIX_TIMESTAMP()"); + PrepareStatement(CharStatements.SEL_CHARACTER_SPELL_CHARGES, "SELECT categoryId, rechargeStart, rechargeEnd FROM character_spell_charges WHERE guid = ? AND rechargeEnd > UNIX_TIMESTAMP() ORDER BY rechargeEnd"); + PrepareStatement(CharStatements.SEL_CHARACTER_DECLINEDNAMES, "SELECT genitive, dative, accusative, instrumental, prepositional FROM character_declinedname WHERE guid = ?"); + PrepareStatement(CharStatements.SEL_GUILD_MEMBER, "SELECT guildid, rank FROM guild_member WHERE guid = ?"); + PrepareStatement(CharStatements.SEL_GUILD_MEMBER_EXTENDED, "SELECT g.guildid, g.name, gr.rname, gr.rid, gm.pnote, gm.offnote " + + "FROM guild g JOIN guild_member gm ON g.guildid = gm.guildid " + + "JOIN guild_rank gr ON g.guildid = gr.guildid AND gm.rank = gr.rid WHERE gm.guid = ?"); + PrepareStatement(CharStatements.SEL_CHARACTER_ACHIEVEMENTS, "SELECT achievement, date FROM character_achievement WHERE guid = ?"); + PrepareStatement(CharStatements.SEL_CHARACTER_CRITERIAPROGRESS, "SELECT criteria, counter, date FROM character_achievement_progress WHERE guid = ?"); + PrepareStatement(CharStatements.SEL_CHARACTER_EQUIPMENTSETS, "SELECT setguid, setindex, name, iconname, ignore_mask, AssignedSpecIndex, item0, item1, item2, item3, item4, item5, item6, item7, item8, " + + "item9, item10, item11, item12, item13, item14, item15, item16, item17, item18 FROM character_equipmentsets WHERE guid = ? ORDER BY setindex"); + PrepareStatement(CharStatements.SEL_CHARACTER_TRANSMOG_OUTFITS, "SELECT setguid, setindex, name, iconname, ignore_mask, appearance0, appearance1, appearance2, appearance3, appearance4, " + + "appearance5, appearance6, appearance7, appearance8, appearance9, appearance10, appearance11, appearance12, appearance13, appearance14, appearance15, appearance16, " + + "appearance17, appearance18, mainHandEnchant, offHandEnchant FROM character_transmog_outfits WHERE guid = ? ORDER BY setindex"); + PrepareStatement(CharStatements.SEL_CHARACTER_BGDATA, "SELECT instanceId, team, joinX, joinY, joinZ, joinO, joinMapId, taxiStart, taxiEnd, mountSpell FROM character_battleground_data WHERE guid = ?"); + PrepareStatement(CharStatements.SEL_CHARACTER_GLYPHS, "SELECT talentGroup, glyphId FROM character_glyphs WHERE guid = ?"); + PrepareStatement(CharStatements.SEL_CHARACTER_TALENTS, "SELECT talentId, talentGroup FROM character_talent WHERE guid = ?"); + PrepareStatement(CharStatements.SEL_CHARACTER_SKILLS, "SELECT skill, value, max FROM character_skills WHERE guid = ?"); + PrepareStatement(CharStatements.SEL_CHARACTER_RANDOMBG, "SELECT guid FROM character_battleground_random WHERE guid = ?"); + PrepareStatement(CharStatements.SEL_CHARACTER_BANNED, "SELECT guid FROM character_banned WHERE guid = ? AND active = 1"); + PrepareStatement(CharStatements.SEL_CHARACTER_QUESTSTATUSREW, "SELECT quest FROM character_queststatus_rewarded WHERE guid = ? AND active = 1"); + PrepareStatement(CharStatements.SEL_ACCOUNT_INSTANCELOCKTIMES, "SELECT instanceId, releaseTime FROM account_instance_times WHERE accountId = ?"); + + PrepareStatement(CharStatements.SEL_CHARACTER_ACTIONS_SPEC, "SELECT button, action, type FROM character_action WHERE guid = ? AND spec = ? ORDER BY button"); + PrepareStatement(CharStatements.SEL_MAILITEMS, "SELECT " + SelectItemInstanceContent + ", ii.owner_guid FROM mail_items mi JOIN item_instance ii ON mi.item_guid = ii.guid LEFT JOIN item_instance_gems ig ON ii.guid = ig.itemGuid LEFT JOIN item_instance_transmog iit ON ii.guid = iit.itemGuid LEFT JOIN item_instance_modifiers im ON ii.guid = im.itemGuid WHERE mail_id = ?"); + PrepareStatement(CharStatements.SEL_AUCTION_ITEMS, "SELECT " + SelectItemInstanceContent + " FROM auctionhouse ah JOIN item_instance ii ON ah.itemguid = ii.guid LEFT JOIN item_instance_gems ig ON ii.guid = ig.itemGuid LEFT JOIN item_instance_transmog iit ON ii.guid = iit.itemGuid LEFT JOIN item_instance_modifiers im ON ii.guid = im.itemGuid"); + PrepareStatement(CharStatements.SEL_AUCTIONS, "SELECT id, auctioneerguid, itemguid, itemEntry, count, itemowner, buyoutprice, time, buyguid, lastbid, startbid, deposit FROM auctionhouse ah INNER JOIN item_instance ii ON ii.guid = ah.itemguid"); + PrepareStatement(CharStatements.INS_AUCTION, "INSERT INTO auctionhouse (id, auctioneerguid, itemguid, itemowner, buyoutprice, time, buyguid, lastbid, startbid, deposit) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)"); + PrepareStatement(CharStatements.DEL_AUCTION, "DELETE FROM auctionhouse WHERE id = ?"); + PrepareStatement(CharStatements.UPD_AUCTION_BID, "UPDATE auctionhouse SET buyguid = ?, lastbid = ? WHERE id = ?"); + PrepareStatement(CharStatements.INS_MAIL, "INSERT INTO mail(id, messageType, stationery, mailTemplateId, sender, receiver, subject, body, has_items, expire_time, deliver_time, money, cod, checked) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)"); + PrepareStatement(CharStatements.DEL_MAIL_BY_ID, "DELETE FROM mail WHERE id = ?"); + PrepareStatement(CharStatements.INS_MAIL_ITEM, "INSERT INTO mail_items(mail_id, item_guid, receiver) VALUES (?, ?, ?)"); + PrepareStatement(CharStatements.DEL_MAIL_ITEM, "DELETE FROM mail_items WHERE item_guid = ?"); + PrepareStatement(CharStatements.DEL_INVALID_MAIL_ITEM, "DELETE FROM mail_items WHERE item_guid = ?"); + PrepareStatement(CharStatements.DEL_EMPTY_EXPIRED_MAIL, "DELETE FROM mail WHERE expire_time < ? AND has_items = 0 AND body = ''"); + PrepareStatement(CharStatements.SEL_EXPIRED_MAIL, "SELECT id, messageType, sender, receiver, has_items, expire_time, cod, checked, mailTemplateId FROM mail WHERE expire_time < ?"); + PrepareStatement(CharStatements.SEL_EXPIRED_MAIL_ITEMS, "SELECT item_guid, itemEntry, mail_id FROM mail_items mi INNER JOIN item_instance ii ON ii.guid = mi.item_guid LEFT JOIN mail mm ON mi.mail_id = mm.id WHERE mm.id IS NOT NULL AND mm.expire_time < ?"); + PrepareStatement(CharStatements.UPD_MAIL_RETURNED, "UPDATE mail SET sender = ?, receiver = ?, expire_time = ?, deliver_time = ?, cod = 0, checked = ? WHERE id = ?"); + PrepareStatement(CharStatements.UPD_MAIL_ITEM_RECEIVER, "UPDATE mail_items SET receiver = ? WHERE item_guid = ?"); + PrepareStatement(CharStatements.UPD_ITEM_OWNER, "UPDATE item_instance SET owner_guid = ? WHERE guid = ?"); + + PrepareStatement(CharStatements.SEL_ITEM_REFUNDS, "SELECT paidMoney, paidExtendedCost FROM item_refund_instance WHERE item_guid = ? AND player_guid = ? LIMIT 1"); + PrepareStatement(CharStatements.SEL_ITEM_BOP_TRADE, "SELECT allowedPlayers FROM item_soulbound_trade_data WHERE itemGuid = ? LIMIT 1"); + PrepareStatement(CharStatements.DEL_ITEM_BOP_TRADE, "DELETE FROM item_soulbound_trade_data WHERE itemGuid = ? LIMIT 1"); + PrepareStatement(CharStatements.INS_ITEM_BOP_TRADE, "INSERT INTO item_soulbound_trade_data VALUES (?, ?)"); + PrepareStatement(CharStatements.REP_INVENTORY_ITEM, "REPLACE INTO character_inventory (guid, bag, slot, item) VALUES (?, ?, ?, ?)"); + PrepareStatement(CharStatements.REP_ITEM_INSTANCE, "REPLACE INTO item_instance (itemEntry, owner_guid, creatorGuid, giftCreatorGuid, count, duration, charges, flags, enchantments, randomPropertyType, randomPropertyId, durability, playedTime, text, upgradeId, battlePetSpeciesId, battlePetBreedData, battlePetLevel, battlePetDisplayId, context, bonusListIDs, guid) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)"); + PrepareStatement(CharStatements.UPD_ITEM_INSTANCE, "UPDATE item_instance SET itemEntry = ?, owner_guid = ?, creatorGuid = ?, giftCreatorGuid = ?, count = ?, duration = ?, charges = ?, flags = ?, enchantments = ?, randomPropertyType = ?, randomPropertyId = ?, durability = ?, playedTime = ?, text = ?, upgradeId = ?, battlePetSpeciesId = ?, battlePetBreedData = ?, battlePetLevel = ?, battlePetDisplayId = ?, context = ?, bonusListIDs = ? WHERE guid = ?"); + PrepareStatement(CharStatements.UPD_ITEM_INSTANCE_ON_LOAD, "UPDATE item_instance SET duration = ?, flags = ?, durability = ?, upgradeId = ? WHERE guid = ?"); + PrepareStatement(CharStatements.DEL_ITEM_INSTANCE, "DELETE FROM item_instance WHERE guid = ?"); + PrepareStatement(CharStatements.DEL_ITEM_INSTANCE_BY_OWNER, "DELETE FROM item_instance WHERE owner_guid = ?"); + PrepareStatement(CharStatements.INS_ITEM_INSTANCE_GEMS, "INSERT INTO item_instance_gems (itemGuid, gemItemId1, gemBonuses1, gemContext1, gemScalingLevel1, gemItemId2, gemBonuses2, gemContext2, gemScalingLevel2, gemItemId3, gemBonuses3, gemContext3, gemScalingLevel3) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)"); + PrepareStatement(CharStatements.DEL_ITEM_INSTANCE_GEMS, "DELETE FROM item_instance_gems WHERE itemGuid = ?"); + PrepareStatement(CharStatements.DEL_ITEM_INSTANCE_GEMS_BY_OWNER, "DELETE iig FROM item_instance_gems iig LEFT JOIN item_instance ii ON iig.itemGuid = ii.guid WHERE ii.owner_guid = ?"); + PrepareStatement(CharStatements.INS_ITEM_INSTANCE_TRANSMOG, "INSERT INTO item_instance_transmog (itemGuid, itemModifiedAppearanceAllSpecs, itemModifiedAppearanceSpec1, itemModifiedAppearanceSpec2, itemModifiedAppearanceSpec3, itemModifiedAppearanceSpec4, " + + "spellItemEnchantmentAllSpecs, spellItemEnchantmentSpec1, spellItemEnchantmentSpec2, spellItemEnchantmentSpec3, spellItemEnchantmentSpec4) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)"); + PrepareStatement(CharStatements.DEL_ITEM_INSTANCE_TRANSMOG, "DELETE FROM item_instance_transmog WHERE itemGuid = ?"); + PrepareStatement(CharStatements.DEL_ITEM_INSTANCE_TRANSMOG_BY_OWNER, "DELETE iit FROM item_instance_transmog iit LEFT JOIN item_instance ii ON iit.itemGuid = ii.guid WHERE ii.owner_guid = ?"); + PrepareStatement(CharStatements.SEL_ITEM_INSTANCE_ARTIFACT, "SELECT a.itemGuid, a.xp, a.artifactAppearanceId, ap.artifactPowerId, ap.purchasedRank FROM item_instance_artifact_powers ap LEFT JOIN item_instance_artifact a ON ap.itemGuid = a.itemGuid INNER JOIN character_inventory ci ON ci.item = ap.itemGuid WHERE ci.guid = ?"); + PrepareStatement(CharStatements.INS_ITEM_INSTANCE_ARTIFACT, "INSERT INTO item_instance_artifact (itemGuid, xp, artifactAppearanceId) VALUES (?, ?, ?)"); + PrepareStatement(CharStatements.DEL_ITEM_INSTANCE_ARTIFACT, "DELETE FROM item_instance_artifact WHERE itemGuid = ?"); + PrepareStatement(CharStatements.DEL_ITEM_INSTANCE_ARTIFACT_BY_OWNER, "DELETE iia FROM item_instance_artifact iia LEFT JOIN item_instance ii ON iia.itemGuid = ii.guid WHERE ii.owner_guid = ?"); + PrepareStatement(CharStatements.INS_ITEM_INSTANCE_ARTIFACT_POWERS, "INSERT INTO item_instance_artifact_powers (itemGuid, artifactPowerId, purchasedRank) VALUES (?, ?, ?)"); + PrepareStatement(CharStatements.DEL_ITEM_INSTANCE_ARTIFACT_POWERS, "DELETE FROM item_instance_artifact_powers WHERE itemGuid = ?"); + PrepareStatement(CharStatements.DEL_ITEM_INSTANCE_ARTIFACT_POWERS_BY_OWNER, "DELETE iiap FROM item_instance_artifact_powers iiap LEFT JOIN item_instance ii ON iiap.itemGuid = ii.guid WHERE ii.owner_guid = ?"); + PrepareStatement(CharStatements.INS_ITEM_INSTANCE_MODIFIERS, "INSERT INTO item_instance_modifiers (itemGuid, fixedScalingLevel, artifactKnowledgeLevel) VALUES (?, ?, ?)"); + PrepareStatement(CharStatements.DEL_ITEM_INSTANCE_MODIFIERS, "DELETE FROM item_instance_modifiers WHERE itemGuid = ?"); + PrepareStatement(CharStatements.DEL_ITEM_INSTANCE_MODIFIERS_BY_OWNER, "DELETE im FROM item_instance_modifiers im LEFT JOIN item_instance ii ON im.itemGuid = ii.guid WHERE ii.owner_guid = ?"); + PrepareStatement(CharStatements.UPD_GIFT_OWNER, "UPDATE character_gifts SET guid = ? WHERE item_guid = ?"); + PrepareStatement(CharStatements.DEL_GIFT, "DELETE FROM character_gifts WHERE item_guid = ?"); + PrepareStatement(CharStatements.SEL_CHARACTER_GIFT_BY_ITEM, "SELECT entry, flags FROM character_gifts WHERE item_guid = ?"); + PrepareStatement(CharStatements.SEL_ACCOUNT_BY_NAME, "SELECT account FROM characters WHERE name = ?"); + PrepareStatement(CharStatements.DEL_ACCOUNT_INSTANCE_LOCK_TIMES, "DELETE FROM account_instance_times WHERE accountId = ?"); + PrepareStatement(CharStatements.INS_ACCOUNT_INSTANCE_LOCK_TIMES, "INSERT INTO account_instance_times (accountId, instanceId, releaseTime) VALUES (?, ?, ?)"); + PrepareStatement(CharStatements.SEL_MATCH_MAKER_RATING, "SELECT matchMakerRating FROM character_arena_stats WHERE guid = ? AND slot = ?"); + PrepareStatement(CharStatements.SEL_CHARACTER_COUNT, "SELECT account, COUNT(guid) FROM characters WHERE account = ? GROUP BY account"); + PrepareStatement(CharStatements.UPD_NAME_BY_GUID, "UPDATE characters SET name = ? WHERE guid = ?"); + + // Guild handling + // 0: uint32, 1: string, 2: uint32, 3: string, 4: string, 5: uint64, 6-10: uint32, 11: uint64 + PrepareStatement(CharStatements.INS_GUILD, "INSERT INTO guild (guildid, name, leaderguid, info, motd, createdate, EmblemStyle, EmblemColor, BorderStyle, BorderColor, BackgroundColor, BankMoney) VALUES(?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)"); + PrepareStatement(CharStatements.DEL_GUILD, "DELETE FROM guild WHERE guildid = ?"); // 0: uint32 + // 0: string, 1: uint32 + PrepareStatement(CharStatements.UPD_GUILD_NAME, "UPDATE guild SET name = ? WHERE guildid = ?"); + // 0: uint32, 1: uint32, 2: uint8, 4: string, 5: string + PrepareStatement(CharStatements.INS_GUILD_MEMBER, "INSERT INTO guild_member (guildid, guid, rank, pnote, offnote) VALUES (?, ?, ?, ?, ?)"); + PrepareStatement(CharStatements.DEL_GUILD_MEMBER, "DELETE FROM guild_member WHERE guid = ?"); // 0: uint32 + PrepareStatement(CharStatements.DEL_GUILD_MEMBERS, "DELETE FROM guild_member WHERE guildid = ?"); // 0: uint32 + // 0: uint32, 1: uint8, 3: string, 4: uint32, 5: uint32 + PrepareStatement(CharStatements.INS_GUILD_RANK, "INSERT INTO guild_rank (guildid, rid, rname, rights, BankMoneyPerDay) VALUES (?, ?, ?, ?, ?)"); + PrepareStatement(CharStatements.DEL_GUILD_RANKS, "DELETE FROM guild_rank WHERE guildid = ?"); // 0: uint32 + PrepareStatement(CharStatements.DEL_GUILD_RANK, "DELETE FROM guild_rank WHERE guildid = ? AND rid = ?"); // 0: uint32, 1: uint8 + PrepareStatement(CharStatements.INS_GUILD_BANK_TAB, "INSERT INTO guild_bank_tab (guildid, TabId) VALUES (?, ?)"); // 0: uint32, 1: uint8 + PrepareStatement(CharStatements.DEL_GUILD_BANK_TAB, "DELETE FROM guild_bank_tab WHERE guildid = ? AND TabId = ?"); // 0: uint32, 1: uint8 + PrepareStatement(CharStatements.DEL_GUILD_BANK_TABS, "DELETE FROM guild_bank_tab WHERE guildid = ?"); // 0: uint32 + // 0: uint32, 1: uint8, 2: uint8, 3: uint32, 4: uint32 + PrepareStatement(CharStatements.INS_GUILD_BANK_ITEM, "INSERT INTO guild_bank_item (guildid, TabId, SlotId, item_guid) VALUES (?, ?, ?, ?)"); + PrepareStatement(CharStatements.DEL_GUILD_BANK_ITEM, "DELETE FROM guild_bank_item WHERE guildid = ? AND TabId = ? AND SlotId = ?"); // 0: uint32, 1: uint8, 2: uint8 + PrepareStatement(CharStatements.SEL_GUILD_BANK_ITEMS, "SELECT " + SelectItemInstanceContent + ", guildid, TabId, SlotId FROM guild_bank_item gbi INNER JOIN item_instance ii ON gbi.item_guid = ii.guid LEFT JOIN item_instance_gems ig ON ii.guid = ig.itemGuid LEFT JOIN item_instance_transmog iit ON ii.guid = iit.itemGuid LEFT JOIN item_instance_modifiers im ON ii.guid = im.itemGuid"); + PrepareStatement(CharStatements.DEL_GUILD_BANK_ITEMS, "DELETE FROM guild_bank_item WHERE guildid = ?"); // 0: uint32 + // 0: uint32, 1: uint8, 2: uint8, 3: uint8, 4: uint32 + PrepareStatement(CharStatements.INS_GUILD_BANK_RIGHT, "INSERT INTO guild_bank_right (guildid, TabId, rid, gbright, SlotPerDay) VALUES (?, ?, ?, ?, ?) " + + "ON DUPLICATE KEY UPDATE gbright = VALUES(gbright), SlotPerDay = VALUES(SlotPerDay)"); + PrepareStatement(CharStatements.DEL_GUILD_BANK_RIGHTS, "DELETE FROM guild_bank_right WHERE guildid = ?"); // 0: uint32 + PrepareStatement(CharStatements.DEL_GUILD_BANK_RIGHTS_FOR_RANK, "DELETE FROM guild_bank_right WHERE guildid = ? AND rid = ?"); // 0: uint32, 1: uint8 + // 0-1: uint32, 2-3: uint8, 4-5: uint32, 6: uint16, 7: uint8, 8: uint64 + PrepareStatement(CharStatements.INS_GUILD_BANK_EVENTLOG, "INSERT INTO guild_bank_eventlog (guildid, LogGuid, TabId, EventType, PlayerGuid, ItemOrMoney, ItemStackCount, DestTabId, TimeStamp) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)"); + PrepareStatement(CharStatements.DEL_GUILD_BANK_EVENTLOG, "DELETE FROM guild_bank_eventlog WHERE guildid = ? AND LogGuid = ? AND TabId = ?"); // 0: uint32, 1: uint32, 2: uint8 + PrepareStatement(CharStatements.DEL_GUILD_BANK_EVENTLOGS, "DELETE FROM guild_bank_eventlog WHERE guildid = ?"); // 0: uint32 + // 0-1: uint32, 2: uint8, 3-4: uint32, 5: uint8, 6: uint64 + PrepareStatement(CharStatements.INS_GUILD_EVENTLOG, "INSERT INTO guild_eventlog (guildid, LogGuid, EventType, PlayerGuid1, PlayerGuid2, NewRank, TimeStamp) VALUES (?, ?, ?, ?, ?, ?, ?)"); + PrepareStatement(CharStatements.DEL_GUILD_EVENTLOG, "DELETE FROM guild_eventlog WHERE guildid = ? AND LogGuid = ?"); // 0: uint32, 1: uint32 + PrepareStatement(CharStatements.DEL_GUILD_EVENTLOGS, "DELETE FROM guild_eventlog WHERE guildid = ?"); // 0: uint32 + PrepareStatement(CharStatements.UPD_GUILD_MEMBER_PNOTE, "UPDATE guild_member SET pnote = ? WHERE guid = ?"); // 0: string, 1: uint32 + PrepareStatement(CharStatements.UPD_GUILD_MEMBER_OFFNOTE, "UPDATE guild_member SET offnote = ? WHERE guid = ?"); // 0: string, 1: uint32 + PrepareStatement(CharStatements.UPD_GUILD_MEMBER_RANK, "UPDATE guild_member SET rank = ? WHERE guid = ?"); // 0: uint8, 1: uint32 + PrepareStatement(CharStatements.UPD_GUILD_MOTD, "UPDATE guild SET motd = ? WHERE guildid = ?"); // 0: string, 1: uint32 + PrepareStatement(CharStatements.UPD_GUILD_INFO, "UPDATE guild SET info = ? WHERE guildid = ?"); // 0: string, 1: uint32 + PrepareStatement(CharStatements.UPD_GUILD_LEADER, "UPDATE guild SET leaderguid = ? WHERE guildid = ?"); // 0: uint32, 1: uint32 + PrepareStatement(CharStatements.UPD_GUILD_RANK_NAME, "UPDATE guild_rank SET rname = ? WHERE rid = ? AND guildid = ?"); // 0: string, 1: uint8, 2: uint32 + PrepareStatement(CharStatements.UPD_GUILD_RANK_RIGHTS, "UPDATE guild_rank SET rights = ? WHERE rid = ? AND guildid = ?"); // 0: uint32, 1: uint8, 2: uint32 + // 0-5: uint32 + PrepareStatement(CharStatements.UPD_GUILD_EMBLEM_INFO, "UPDATE guild SET EmblemStyle = ?, EmblemColor = ?, BorderStyle = ?, BorderColor = ?, BackgroundColor = ? WHERE guildid = ?"); + // 0: string, 1: string, 2: uint32, 3: uint8 + PrepareStatement(CharStatements.UPD_GUILD_BANK_TAB_INFO, "UPDATE guild_bank_tab SET TabName = ?, TabIcon = ? WHERE guildid = ? AND TabId = ?"); + PrepareStatement(CharStatements.UPD_GUILD_BANK_MONEY, "UPDATE guild SET BankMoney = ? WHERE guildid = ?"); // 0: uint64, 1: uint32 + // 0: uint8, 1: uint32, 2: uint8, 3: uint32 + PrepareStatement(CharStatements.UPD_GUILD_RANK_BANK_MONEY, "UPDATE guild_rank SET BankMoneyPerDay = ? WHERE rid = ? AND guildid = ?"); // 0: uint32, 1: uint8, 2: uint32 + PrepareStatement(CharStatements.UPD_GUILD_BANK_TAB_TEXT, "UPDATE guild_bank_tab SET TabText = ? WHERE guildid = ? AND TabId = ?"); // 0: string, 1: uint32, 2: uint8 + + PrepareStatement(CharStatements.INS_GUILD_MEMBER_WITHDRAW, "INSERT INTO guild_member_withdraw (guid, tab0, tab1, tab2, tab3, tab4, tab5, tab6, tab7, money) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?) " + + "ON DUPLICATE KEY UPDATE tab0 = VALUES (tab0), tab1 = VALUES (tab1), tab2 = VALUES (tab2), tab3 = VALUES (tab3), tab4 = VALUES (tab4), tab5 = VALUES (tab5), tab6 = VALUES (tab6), tab7 = VALUES (tab7), money = VALUES (money)"); + PrepareStatement(CharStatements.DEL_GUILD_MEMBER_WITHDRAW, "TRUNCATE guild_member_withdraw"); + + // 0: uint32, 1: uint32, 2: uint32 + PrepareStatement(CharStatements.SEL_CHAR_DATA_FOR_GUILD, "SELECT name, level, class, gender, zone, account FROM characters WHERE guid = ?"); + PrepareStatement(CharStatements.DEL_GUILD_ACHIEVEMENT, "DELETE FROM guild_achievement WHERE guildId = ? AND achievement = ?"); + PrepareStatement(CharStatements.INS_GUILD_ACHIEVEMENT, "INSERT INTO guild_achievement (guildId, achievement, date, guids) VALUES (?, ?, ?, ?)"); + PrepareStatement(CharStatements.DEL_GUILD_ACHIEVEMENT_CRITERIA, "DELETE FROM guild_achievement_progress WHERE guildId = ? AND criteria = ?"); + PrepareStatement(CharStatements.INS_GUILD_ACHIEVEMENT_CRITERIA, "INSERT INTO guild_achievement_progress (guildId, criteria, counter, date, completedGuid) VALUES (?, ?, ?, ?, ?)"); + PrepareStatement(CharStatements.DEL_ALL_GUILD_ACHIEVEMENTS, "DELETE FROM guild_achievement WHERE guildId = ? AND achievement NOT IN (5407,5408,5409,5410,5411,5985,6126,6628,6678,6679,6680,8257,8512,8513,9397,9399,10380)"); + PrepareStatement(CharStatements.DEL_ALL_GUILD_ACHIEVEMENT_CRITERIA, "DELETE FROM guild_achievement_progress WHERE guildId = ?"); + PrepareStatement(CharStatements.SEL_GUILD_ACHIEVEMENT, "SELECT achievement, date, guids FROM guild_achievement WHERE guildId = ?"); + PrepareStatement(CharStatements.SEL_GUILD_ACHIEVEMENT_CRITERIA, "SELECT criteria, counter, date, completedGuid FROM guild_achievement_progress WHERE guildId = ?"); + PrepareStatement(CharStatements.INS_GUILD_NEWS, "INSERT INTO guild_newslog (guildid, LogGuid, EventType, PlayerGuid, Flags, Value, Timestamp) VALUES (?, ?, ?, ?, ?, ?, ?)" + + " ON DUPLICATE KEY UPDATE LogGuid = VALUES (LogGuid), EventType = VALUES (EventType), PlayerGuid = VALUES (PlayerGuid), Flags = VALUES (Flags), Value = VALUES (Value), Timestamp = VALUES (Timestamp)"); + + // Chat channel handling + PrepareStatement(CharStatements.SEL_CHANNEL, "SELECT name, announce, ownership, password, bannedList FROM channels WHERE name = ? AND team = ?"); + PrepareStatement(CharStatements.INS_CHANNEL, "INSERT INTO channels(name, team, lastUsed) VALUES (?, ?, UNIX_TIMESTAMP())"); + PrepareStatement(CharStatements.UPD_CHANNEL, "UPDATE channels SET announce = ?, ownership = ?, password = ?, bannedList = ?, lastUsed = UNIX_TIMESTAMP() WHERE name = ? AND team = ?"); + PrepareStatement(CharStatements.UPD_CHANNEL_USAGE, "UPDATE channels SET lastUsed = UNIX_TIMESTAMP() WHERE name = ? AND team = ?"); + PrepareStatement(CharStatements.UPD_CHANNEL_OWNERSHIP, "UPDATE channels SET ownership = ? WHERE name LIKE ?"); + PrepareStatement(CharStatements.DEL_OLD_CHANNELS, "DELETE FROM channels WHERE ownership = 1 AND lastUsed + ? < UNIX_TIMESTAMP()"); + + // Equipmentsets + PrepareStatement(CharStatements.UPD_EQUIP_SET, "UPDATE character_equipmentsets SET name=?, iconname=?, ignore_mask=?, AssignedSpecIndex=?, item0=?, item1=?, item2=?, item3=?, " + + "item4=?, item5=?, item6=?, item7=?, item8=?, item9=?, item10=?, item11=?, item12=?, item13=?, item14=?, item15=?, item16=?, " + + "item17=?, item18=? WHERE guid=? AND setguid=? AND setindex=?"); + PrepareStatement(CharStatements.INS_EQUIP_SET, "INSERT INTO character_equipmentsets (guid, setguid, setindex, name, iconname, ignore_mask, AssignedSpecIndex, item0, item1, item2, item3, " + + "item4, item5, item6, item7, item8, item9, item10, item11, item12, item13, item14, item15, item16, item17, item18) " + + "VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)"); + PrepareStatement(CharStatements.DEL_EQUIP_SET, "DELETE FROM character_equipmentsets WHERE setguid=?"); + PrepareStatement(CharStatements.UPD_TRANSMOG_OUTFIT, "UPDATE character_transmog_outfits SET name=?, iconname=?, ignore_mask=?, appearance0=?, appearance1=?, appearance2=?, appearance3=?, " + + "appearance4=?, appearance5=?, appearance6=?, appearance7=?, appearance8=?, appearance9=?, appearance10=?, appearance11=?, appearance12=?, appearance13=?, appearance14=?, " + + "appearance15=?, appearance16=?, appearance17=?, appearance18=?, mainHandEnchant=?, offHandEnchant=? WHERE guid=? AND setguid=? AND setindex=?"); + PrepareStatement(CharStatements.INS_TRANSMOG_OUTFIT, "INSERT INTO character_transmog_outfits (guid, setguid, setindex, name, iconname, ignore_mask, appearance0, appearance1, appearance2, " + + "appearance3, appearance4, appearance5, appearance6, appearance7, appearance8, appearance9, appearance10, appearance11, appearance12, appearance13, appearance14, appearance15, " + + "appearance16, appearance17, appearance18, mainHandEnchant, offHandEnchant) " + + "VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)"); + PrepareStatement(CharStatements.DEL_TRANSMOG_OUTFIT, "DELETE FROM character_transmog_outfits WHERE setguid=?"); + + // Auras + PrepareStatement(CharStatements.INS_AURA, "INSERT INTO character_aura (guid, casterGuid, itemGuid, spell, effectMask, recalculateMask, stackCount, maxDuration, remainTime, remainCharges, castItemLevel) " + + "VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)"); + PrepareStatement(CharStatements.INS_AURA_EFFECT, "INSERT INTO character_aura_effect (guid, casterGuid, itemGuid, spell, effectMask, effectIndex, amount, baseAmount) " + + "VALUES (?, ?, ?, ?, ?, ?, ?, ?)"); + + // Currency + PrepareStatement(CharStatements.SEL_PLAYER_CURRENCY, "SELECT Currency, Quantity, WeeklyQuantity, TrackedQuantity, Flags FROM character_currency WHERE CharacterGuid = ?"); + PrepareStatement(CharStatements.UPD_PLAYER_CURRENCY, "UPDATE character_currency SET Quantity = ?, WeeklyQuantity = ?, TrackedQuantity = ?, Flags = ? WHERE CharacterGuid = ? AND Currency = ?"); + PrepareStatement(CharStatements.REP_PLAYER_CURRENCY, "REPLACE INTO character_currency (CharacterGuid, Currency, Quantity, WeeklyQuantity, TrackedQuantity, Flags) VALUES (?, ?, ?, ?, ?, ?)"); + PrepareStatement(CharStatements.DEL_PLAYER_CURRENCY, "DELETE FROM character_currency WHERE CharacterGuid = ?"); + + // Account data + PrepareStatement(CharStatements.SEL_ACCOUNT_DATA, "SELECT type, time, data FROM account_data WHERE accountId = ?"); + PrepareStatement(CharStatements.REP_ACCOUNT_DATA, "REPLACE INTO account_data (accountId, type, time, data) VALUES (?, ?, ?, ?)"); + PrepareStatement(CharStatements.DEL_ACCOUNT_DATA, "DELETE FROM account_data WHERE accountId = ?"); + PrepareStatement(CharStatements.SEL_PLAYER_ACCOUNT_DATA, "SELECT type, time, data FROM character_account_data WHERE guid = ?"); + PrepareStatement(CharStatements.REP_PLAYER_ACCOUNT_DATA, "REPLACE INTO character_account_data(guid, type, time, data) VALUES (?, ?, ?, ?)"); + PrepareStatement(CharStatements.DEL_PLAYER_ACCOUNT_DATA, "DELETE FROM character_account_data WHERE guid = ?"); + + // Tutorials + PrepareStatement(CharStatements.SEL_TUTORIALS, "SELECT tut0, tut1, tut2, tut3, tut4, tut5, tut6, tut7 FROM account_tutorial WHERE accountId = ?"); + PrepareStatement(CharStatements.SEL_HAS_TUTORIALS, "SELECT 1 FROM account_tutorial WHERE accountId = ?"); + PrepareStatement(CharStatements.INS_TUTORIALS, "INSERT INTO account_tutorial(tut0, tut1, tut2, tut3, tut4, tut5, tut6, tut7, accountId) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)"); + PrepareStatement(CharStatements.UPD_TUTORIALS, "UPDATE account_tutorial SET tut0 = ?, tut1 = ?, tut2 = ?, tut3 = ?, tut4 = ?, tut5 = ?, tut6 = ?, tut7 = ? WHERE accountId = ?"); + PrepareStatement(CharStatements.DEL_TUTORIALS, "DELETE FROM account_tutorial WHERE accountId = ?"); + + // Instance saves + PrepareStatement(CharStatements.INS_INSTANCE_SAVE, "INSERT INTO instance (id, map, resettime, difficulty, completedEncounters, data, entranceId) VALUES (?, ?, ?, ?, ?, ?, ?)"); + PrepareStatement(CharStatements.UPD_INSTANCE_DATA, "UPDATE instance SET completedEncounters=?, data=?, entranceId=? WHERE id=?"); + + // Game event saves + PrepareStatement(CharStatements.DEL_GAME_EVENT_SAVE, "DELETE FROM game_event_save WHERE eventEntry = ?"); + PrepareStatement(CharStatements.INS_GAME_EVENT_SAVE, "INSERT INTO game_event_save (eventEntry, state, next_start) VALUES (?, ?, ?)"); + + // Game event condition saves + PrepareStatement(CharStatements.DEL_ALL_GAME_EVENT_CONDITION_SAVE, "DELETE FROM game_event_condition_save WHERE eventEntry = ?"); + PrepareStatement(CharStatements.DEL_GAME_EVENT_CONDITION_SAVE, "DELETE FROM game_event_condition_save WHERE eventEntry = ? AND condition_id = ?"); + PrepareStatement(CharStatements.INS_GAME_EVENT_CONDITION_SAVE, "INSERT INTO game_event_condition_save (eventEntry, condition_id, done) VALUES (?, ?, ?)"); + + // Petitions + PrepareStatement(CharStatements.SEL_PETITION, "SELECT ownerguid, name FROM petition WHERE petitionguid = ?"); + PrepareStatement(CharStatements.SEL_PETITION_SIGNATURE, "SELECT playerguid FROM petition_sign WHERE petitionguid = ?"); + PrepareStatement(CharStatements.DEL_ALL_PETITION_SIGNATURES, "DELETE FROM petition_sign WHERE playerguid = ?"); + PrepareStatement(CharStatements.SEL_PETITION_BY_OWNER, "SELECT petitionguid FROM petition WHERE ownerguid = ?"); + PrepareStatement(CharStatements.SEL_PETITION_SIGNATURES, "SELECT ownerguid, (SELECT COUNT(playerguid) FROM petition_sign WHERE petition_sign.petitionguid = ?) AS signs FROM petition WHERE petitionguid = ?"); + PrepareStatement(CharStatements.SEL_PETITION_SIG_BY_ACCOUNT, "SELECT playerguid FROM petition_sign WHERE player_account = ? AND petitionguid = ?"); + PrepareStatement(CharStatements.SEL_PETITION_OWNER_BY_GUID, "SELECT ownerguid FROM petition WHERE petitionguid = ?"); + PrepareStatement(CharStatements.SEL_PETITION_SIG_BY_GUID, "SELECT ownerguid, petitionguid FROM petition_sign WHERE playerguid = ?"); + + // Arena teams + PrepareStatement(CharStatements.SEL_CHARACTER_ARENAINFO, "SELECT arenaTeamId, weekGames, seasonGames, seasonWins, personalRating FROM arena_team_member WHERE guid = ?"); + PrepareStatement(CharStatements.INS_ARENA_TEAM, "INSERT INTO arena_team (arenaTeamId, name, captainGuid, type, rating, backgroundColor, emblemStyle, emblemColor, borderStyle, borderColor) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)"); + PrepareStatement(CharStatements.INS_ARENA_TEAM_MEMBER, "INSERT INTO arena_team_member (arenaTeamId, guid) VALUES (?, ?)"); + PrepareStatement(CharStatements.DEL_ARENA_TEAM, "DELETE FROM arena_team where arenaTeamId = ?"); + PrepareStatement(CharStatements.DEL_ARENA_TEAM_MEMBERS, "DELETE FROM arena_team_member WHERE arenaTeamId = ?"); + PrepareStatement(CharStatements.UPD_ARENA_TEAM_CAPTAIN, "UPDATE arena_team SET captainGuid = ? WHERE arenaTeamId = ?"); + PrepareStatement(CharStatements.DEL_ARENA_TEAM_MEMBER, "DELETE FROM arena_team_member WHERE arenaTeamId = ? AND guid = ?"); + PrepareStatement(CharStatements.UPD_ARENA_TEAM_STATS, "UPDATE arena_team SET rating = ?, weekGames = ?, weekWins = ?, seasonGames = ?, seasonWins = ?, rank = ? WHERE arenaTeamId = ?"); + PrepareStatement(CharStatements.UPD_ARENA_TEAM_MEMBER, "UPDATE arena_team_member SET personalRating = ?, weekGames = ?, weekWins = ?, seasonGames = ?, seasonWins = ? WHERE arenaTeamId = ? AND guid = ?"); + PrepareStatement(CharStatements.DEL_CHARACTER_ARENA_STATS, "DELETE FROM character_arena_stats WHERE guid = ?"); + PrepareStatement(CharStatements.REP_CHARACTER_ARENA_STATS, "REPLACE INTO character_arena_stats (guid, slot, matchMakerRating) VALUES (?, ?, ?)"); + PrepareStatement(CharStatements.SEL_PLAYER_ARENA_TEAMS, "SELECT arena_team_member.arenaTeamId FROM arena_team_member JOIN arena_team ON arena_team_member.arenaTeamId = arena_team.arenaTeamId WHERE guid = ?"); + PrepareStatement(CharStatements.UPD_ARENA_TEAM_NAME, "UPDATE arena_team SET name = ? WHERE arenaTeamId = ?"); + + // Character battleground data + PrepareStatement(CharStatements.INS_PLAYER_BGDATA, "INSERT INTO character_battleground_data (guid, instanceId, team, joinX, joinY, joinZ, joinO, joinMapId, taxiStart, taxiEnd, mountSpell) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)"); + PrepareStatement(CharStatements.DEL_PLAYER_BGDATA, "DELETE FROM character_battleground_data WHERE guid = ?"); + + // Character homebind + PrepareStatement(CharStatements.INS_PLAYER_HOMEBIND, "INSERT INTO character_homebind (guid, mapId, zoneId, posX, posY, posZ) VALUES (?, ?, ?, ?, ?, ?)"); + PrepareStatement(CharStatements.UPD_PLAYER_HOMEBIND, "UPDATE character_homebind SET mapId = ?, zoneId = ?, posX = ?, posY = ?, posZ = ? WHERE guid = ?"); + PrepareStatement(CharStatements.DEL_PLAYER_HOMEBIND, "DELETE FROM character_homebind WHERE guid = ?"); + + // Corpse + PrepareStatement(CharStatements.SEL_CORPSES, "SELECT posX, posY, posZ, orientation, mapId, displayId, itemCache, bytes1, bytes2, flags, dynFlags, time, corpseType, instanceId, guid FROM corpse WHERE mapId = ? AND instanceId = ?"); + PrepareStatement(CharStatements.INS_CORPSE, "INSERT INTO corpse (guid, posX, posY, posZ, orientation, mapId, displayId, itemCache, bytes1, bytes2, flags, dynFlags, time, corpseType, instanceId) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)"); + PrepareStatement(CharStatements.DEL_CORPSE, "DELETE FROM corpse WHERE guid = ?"); + PrepareStatement(CharStatements.DEL_CORPSES_FROM_MAP, "DELETE cp, c FROM corpse_phases cp INNER JOIN corpse c ON cp.OwnerGuid = c.guid WHERE c.mapId = ? AND c.instanceId = ?"); + PrepareStatement(CharStatements.SEL_CORPSE_PHASES, "SELECT cp.OwnerGuid, cp.PhaseId FROM corpse_phases cp LEFT JOIN corpse c ON cp.OwnerGuid = c.guid WHERE c.mapId = ? AND c.instanceId = ?"); + PrepareStatement(CharStatements.INS_CORPSE_PHASES, "INSERT INTO corpse_phases (OwnerGuid, PhaseId) VALUES (?, ?)"); + PrepareStatement(CharStatements.DEL_CORPSE_PHASES, "DELETE FROM corpse_phases WHERE OwnerGuid = ?"); + PrepareStatement(CharStatements.SEL_CORPSE_LOCATION, "SELECT mapId, posX, posY, posZ, orientation FROM corpse WHERE guid = ?"); + + // Creature respawn + PrepareStatement(CharStatements.SEL_CREATURE_RESPAWNS, "SELECT guid, respawnTime FROM creature_respawn WHERE mapId = ? AND instanceId = ?"); + PrepareStatement(CharStatements.REP_CREATURE_RESPAWN, "REPLACE INTO creature_respawn (guid, respawnTime, mapId, instanceId) VALUES (?, ?, ?, ?)"); + PrepareStatement(CharStatements.DEL_CREATURE_RESPAWN, "DELETE FROM creature_respawn WHERE guid = ? AND mapId = ? AND instanceId = ?"); + PrepareStatement(CharStatements.DEL_CREATURE_RESPAWN_BY_INSTANCE, "DELETE FROM creature_respawn WHERE mapId = ? AND instanceId = ?"); + PrepareStatement(CharStatements.SEL_MAX_CREATURE_RESPAWNS, "SELECT MAX(respawnTime), instanceId FROM creature_respawn WHERE instanceId > 0 GROUP BY instanceId"); + + // Gameobject respawn + PrepareStatement(CharStatements.SEL_GO_RESPAWNS, "SELECT guid, respawnTime FROM gameobject_respawn WHERE mapId = ? AND instanceId = ?"); + PrepareStatement(CharStatements.REP_GO_RESPAWN, "REPLACE INTO gameobject_respawn (guid, respawnTime, mapId, instanceId) VALUES (?, ?, ?, ?)"); + PrepareStatement(CharStatements.DEL_GO_RESPAWN, "DELETE FROM gameobject_respawn WHERE guid = ? AND mapId = ? AND instanceId = ?"); + PrepareStatement(CharStatements.DEL_GO_RESPAWN_BY_INSTANCE, "DELETE FROM gameobject_respawn WHERE mapId = ? AND instanceId = ?"); + + // GM Bug + PrepareStatement(CharStatements.SEL_GM_BUGS, "SELECT id, playerGuid, note, createTime, mapId, posX, posY, posZ, facing, closedBy, assignedTo, comment FROM gm_bug"); + PrepareStatement(CharStatements.REP_GM_BUG, "REPLACE INTO gm_bug (id, playerGuid, note, createTime, mapId, posX, posY, posZ, facing, closedBy, assignedTo, comment) VALUES (?, ?, ?, UNIX_TIMESTAMP(NOW()), ?, ?, ?, ?, ?, ?, ?, ?)"); + PrepareStatement(CharStatements.DEL_GM_BUG, "DELETE FROM gm_bug WHERE id = ?"); + PrepareStatement(CharStatements.DEL_ALL_GM_BUGS, "TRUNCATE TABLE gm_bug"); + + // GM Complaint + PrepareStatement(CharStatements.SEL_GM_COMPLAINTS, "SELECT id, playerGuid, note, createTime, mapId, posX, posY, posZ, facing, targetCharacterGuid, complaintType, reportLineIndex, assignedTo, closedBy, comment FROM gm_complaint"); + PrepareStatement(CharStatements.REP_GM_COMPLAINT, "REPLACE INTO gm_complaint (id, playerGuid, note, createTime, mapId, posX, posY, posZ, facing, targetCharacterGuid, complaintType, reportLineIndex, assignedTo, closedBy, comment) VALUES (?, ?, ?, UNIX_TIMESTAMP(NOW()), ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)"); + PrepareStatement(CharStatements.DEL_GM_COMPLAINT, "DELETE FROM gm_complaint WHERE id = ?"); + PrepareStatement(CharStatements.SEL_GM_COMPLAINT_CHATLINES, "SELECT timestamp, text FROM gm_complaint_chatlog WHERE complaintId = ? ORDER BY lineId ASC"); + PrepareStatement(CharStatements.INS_GM_COMPLAINT_CHATLINE, "INSERT INTO gm_complaint_chatlog (complaintId, lineId, timestamp, text) VALUES (?, ?, ?, ?)"); + PrepareStatement(CharStatements.DEL_GM_COMPLAINT_CHATLOG, "DELETE FROM gm_complaint_chatlog WHERE complaintId = ?"); + PrepareStatement(CharStatements.DEL_ALL_GM_COMPLAINTS, "TRUNCATE TABLE gm_complaint"); + PrepareStatement(CharStatements.DEL_ALL_GM_COMPLAINT_CHATLOGS, "TRUNCATE TABLE gm_complaint_chatlog"); + + // GM Suggestion + PrepareStatement(CharStatements.SEL_GM_SUGGESTIONS, "SELECT id, playerGuid, note, createTime, mapId, posX, posY, posZ, facing, closedBy, assignedTo, comment FROM gm_suggestion"); + PrepareStatement(CharStatements.REP_GM_SUGGESTION, "REPLACE INTO gm_suggestion (id, playerGuid, note, createTime, mapId, posX, posY, posZ, facing, closedBy, assignedTo, comment) VALUES (?, ?, ?, UNIX_TIMESTAMP(NOW()), ?, ?, ?, ?, ?, ? ,? ,?)"); + PrepareStatement(CharStatements.DEL_GM_SUGGESTION, "DELETE FROM gm_suggestion WHERE id = ?"); + PrepareStatement(CharStatements.DEL_ALL_GM_SUGGESTIONS, "TRUNCATE TABLE gm_suggestion"); + + // LFG Data + PrepareStatement(CharStatements.INS_LFG_DATA, "INSERT INTO lfg_data (guid, dungeon, state) VALUES (?, ?, ?)"); + PrepareStatement(CharStatements.DEL_LFG_DATA, "DELETE FROM lfg_data WHERE guid = ?"); + + // Player saving + PrepareStatement(CharStatements.INS_CHARACTER, "INSERT INTO characters (guid, account, name, race, class, gender, level, xp, money, skin, face, hairStyle, hairColor, facialStyle, customDisplay1, customDisplay2, customDisplay3, bankSlots, restState, playerFlags, playerFlagsEx, " + + "map, instance_id, dungeonDifficulty, raidDifficulty, legacyRaidDifficulty, position_x, position_y, position_z, orientation, trans_x, trans_y, trans_z, trans_o, transguid, " + + "taximask, cinematic, totaltime, leveltime, rest_bonus, logout_time, is_logout_resting, resettalents_cost, resettalents_time, primarySpecialization, " + + "extra_flags, stable_slots, at_login, zone, death_expire_time, taxi_path, totalKills, todayKills, yesterdayKills, chosenTitle, watchedFaction, drunk, health, power1, power2, power3, " + + "power4, power5, power6, latency, activeTalentGroup, lootSpecId, exploredZones, equipmentCache, knownTitles, actionBars, grantableLevels) VALUES " + + "(?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?)"); + PrepareStatement(CharStatements.UPD_CHARACTER, "UPDATE characters SET name=?,race=?,class=?,gender=?,level=?,xp=?,money=?,skin=?,face=?,hairStyle=?,hairColor=?,facialStyle=?,customDisplay1=?,customDisplay2=?,customDisplay3=?,bankSlots=?,restState=?,playerFlags=?,playerFlagsEx=?," + + "map=?,instance_id=?,dungeonDifficulty=?,raidDifficulty=?,legacyRaidDifficulty=?,position_x=?,position_y=?,position_z=?,orientation=?,trans_x=?,trans_y=?,trans_z=?,trans_o=?,transguid=?,taximask=?,cinematic=?,totaltime=?,leveltime=?,rest_bonus=?," + + "logout_time=?,is_logout_resting=?,resettalents_cost=?,resettalents_time=?,primarySpecialization=?,extra_flags=?,stable_slots=?,at_login=?,zone=?,death_expire_time=?,taxi_path=?," + + "totalKills=?,todayKills=?,yesterdayKills=?,chosenTitle=?," + + "watchedFaction=?,drunk=?,health=?,power1=?,power2=?,power3=?,power4=?,power5=?,power6=?,latency=?,activeTalentGroup=?,lootSpecId=?,exploredZones=?," + + "equipmentCache=?,knownTitles=?,actionBars=?,grantableLevels=?,online=?,honor=?,honorLevel=?,prestigeLevel=?,honorRestState=?,honorRestBonus=? WHERE guid=?"); + + PrepareStatement(CharStatements.UPD_ADD_AT_LOGIN_FLAG, "UPDATE characters SET at_login = at_login | ? WHERE guid = ?"); + PrepareStatement(CharStatements.UPD_REM_AT_LOGIN_FLAG, "UPDATE characters set at_login = at_login & ~ ? WHERE guid = ?"); + PrepareStatement(CharStatements.UPD_ALL_AT_LOGIN_FLAGS, "UPDATE characters SET at_login = at_login | ?"); + PrepareStatement(CharStatements.INS_BUG_REPORT, "INSERT INTO bugreport (type, content) VALUES(?, ?)"); + PrepareStatement(CharStatements.UPD_PETITION_NAME, "UPDATE petition SET name = ? WHERE petitionguid = ?"); + PrepareStatement(CharStatements.INS_PETITION_SIGNATURE, "INSERT INTO petition_sign (ownerguid, petitionguid, playerguid, player_account) VALUES (?, ?, ?, ?)"); + PrepareStatement(CharStatements.UPD_ACCOUNT_ONLINE, "UPDATE characters SET online = 0 WHERE account = ?"); + PrepareStatement(CharStatements.INS_GROUP, "INSERT INTO groups (guid, leaderGuid, lootMethod, looterGuid, lootThreshold, icon1, icon2, icon3, icon4, icon5, icon6, icon7, icon8, groupType, difficulty, raidDifficulty, legacyRaidDifficulty, masterLooterGuid) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)"); + PrepareStatement(CharStatements.INS_GROUP_MEMBER, "INSERT INTO group_member (guid, memberGuid, memberFlags, subgroup, roles) VALUES(?, ?, ?, ?, ?)"); + PrepareStatement(CharStatements.DEL_GROUP_MEMBER, "DELETE FROM group_member WHERE memberGuid = ?"); + PrepareStatement(CharStatements.DEL_GROUP_INSTANCE_PERM_BINDING, "DELETE FROM group_instance WHERE guid = ? AND instance = ?"); + PrepareStatement(CharStatements.UPD_GROUP_LEADER, "UPDATE groups SET leaderGuid = ? WHERE guid = ?"); + PrepareStatement(CharStatements.UPD_GROUP_TYPE, "UPDATE groups SET groupType = ? WHERE guid = ?"); + PrepareStatement(CharStatements.UPD_GROUP_MEMBER_SUBGROUP, "UPDATE group_member SET subgroup = ? WHERE memberGuid = ?"); + PrepareStatement(CharStatements.UPD_GROUP_MEMBER_FLAG, "UPDATE group_member SET memberFlags = ? WHERE memberGuid = ?"); + PrepareStatement(CharStatements.UPD_GROUP_DIFFICULTY, "UPDATE groups SET difficulty = ? WHERE guid = ?"); + PrepareStatement(CharStatements.UPD_GROUP_RAID_DIFFICULTY, "UPDATE groups SET raidDifficulty = ? WHERE guid = ?"); + PrepareStatement(CharStatements.UPD_GROUP_LEGACY_RAID_DIFFICULTY, "UPDATE groups SET legacyRaidDifficulty = ? WHERE guid = ?"); + PrepareStatement(CharStatements.DEL_INVALID_SPELL_SPELLS, "DELETE FROM character_spell WHERE spell = ?"); + PrepareStatement(CharStatements.UPD_DELETE_INFO, "UPDATE characters SET deleteInfos_Name = name, deleteInfos_Account = account, deleteDate = UNIX_TIMESTAMP(), name = '', account = 0 WHERE guid = ?"); + PrepareStatement(CharStatements.UPD_RESTORE_DELETE_INFO, "UPDATE characters SET name = ?, account = ?, deleteDate = NULL, deleteInfos_Name = NULL, deleteInfos_Account = NULL WHERE deleteDate IS NOT NULL AND guid = ?"); + PrepareStatement(CharStatements.UPD_ZONE, "UPDATE characters SET zone = ? WHERE guid = ?"); + PrepareStatement(CharStatements.UPD_LEVEL, "UPDATE characters SET level = ?, xp = 0 WHERE guid = ?"); + PrepareStatement(CharStatements.DEL_INVALID_ACHIEV_PROGRESS_CRITERIA, "DELETE FROM character_achievement_progress WHERE criteria = ?"); + PrepareStatement(CharStatements.DEL_INVALID_ACHIEV_PROGRESS_CRITERIA_GUILD, "DELETE FROM guild_achievement_progress WHERE criteria = ?"); + PrepareStatement(CharStatements.DEL_INVALID_ACHIEVMENT, "DELETE FROM character_achievement WHERE achievement = ?"); + PrepareStatement(CharStatements.DEL_INVALID_PET_SPELL, "DELETE FROM pet_spell WHERE spell = ?"); + PrepareStatement(CharStatements.DEL_GROUP_INSTANCE_BY_INSTANCE, "DELETE FROM group_instance WHERE instance = ?"); + PrepareStatement(CharStatements.DEL_GROUP_INSTANCE_BY_GUID, "DELETE FROM group_instance WHERE guid = ? AND instance = ?"); + PrepareStatement(CharStatements.REP_GROUP_INSTANCE, "REPLACE INTO group_instance (guid, instance, permanent) VALUES (?, ?, ?)"); + PrepareStatement(CharStatements.UPD_INSTANCE_RESETTIME, "UPDATE instance SET resettime = ? WHERE id = ?"); + PrepareStatement(CharStatements.UPD_GLOBAL_INSTANCE_RESETTIME, "UPDATE instance_reset SET resettime = ? WHERE mapid = ? AND difficulty = ?"); + PrepareStatement(CharStatements.UPD_CHAR_ONLINE, "UPDATE characters SET online = 1 WHERE guid = ?"); + PrepareStatement(CharStatements.UPD_CHAR_NAME_AT_LOGIN, "UPDATE characters SET name = ?, at_login = ? WHERE guid = ?"); + PrepareStatement(CharStatements.UPD_WORLDSTATE, "UPDATE worldstates SET value = ? WHERE entry = ?"); + PrepareStatement(CharStatements.INS_WORLDSTATE, "INSERT INTO worldstates (entry, value) VALUES (?, ?)"); + PrepareStatement(CharStatements.DEL_CHAR_INSTANCE_BY_INSTANCE_GUID, "DELETE FROM character_instance WHERE guid = ? AND instance = ?"); + PrepareStatement(CharStatements.UPD_CHAR_INSTANCE, "UPDATE character_instance SET instance = ?, permanent = ?, extendState = ? WHERE guid = ? AND instance = ?"); + PrepareStatement(CharStatements.INS_CHAR_INSTANCE, "INSERT INTO character_instance (guid, instance, permanent, extendState) VALUES (?, ?, ?, ?)"); + PrepareStatement(CharStatements.UPD_GENDER_AND_APPEARANCE, "UPDATE characters SET gender = ?, skin = ?, face = ?, hairStyle = ?, hairColor = ?, facialStyle = ?, customDisplay1 = ?, customDisplay2 = ?, customDisplay3 = ? WHERE guid = ?"); + PrepareStatement(CharStatements.DEL_CHARACTER_SKILL, "DELETE FROM character_skills WHERE guid = ? AND skill = ?"); + PrepareStatement(CharStatements.UPD_CHARACTER_SOCIAL_FLAGS, "UPDATE character_social SET flags = ? WHERE guid = ? AND friend = ?"); + PrepareStatement(CharStatements.INS_CHARACTER_SOCIAL, "INSERT INTO character_social (guid, friend, flags) VALUES (?, ?, ?)"); + PrepareStatement(CharStatements.DEL_CHARACTER_SOCIAL, "DELETE FROM character_social WHERE guid = ? AND friend = ?"); + PrepareStatement(CharStatements.UPD_CHARACTER_SOCIAL_NOTE, "UPDATE character_social SET note = ? WHERE guid = ? AND friend = ?"); + PrepareStatement(CharStatements.UPD_CHARACTER_POSITION, "UPDATE characters SET position_x = ?, position_y = ?, position_z = ?, orientation = ?, map = ?, zone = ?, trans_x = 0, trans_y = 0, trans_z = 0, transguid = 0, taxi_path = '' WHERE guid = ?"); + PrepareStatement(CharStatements.SEL_CHARACTER_AURA_FROZEN, "SELECT characters.name, character_aura.remainTime FROM characters LEFT JOIN character_aura ON (characters.guid = character_aura.guid) WHERE character_aura.spell = 9454"); + PrepareStatement(CharStatements.SEL_CHARACTER_ONLINE, "SELECT name, account, map, zone FROM characters WHERE online > 0"); + PrepareStatement(CharStatements.SEL_CHAR_DEL_INFO_BY_GUID, "SELECT guid, deleteInfos_Name, deleteInfos_Account, deleteDate FROM characters WHERE deleteDate IS NOT NULL AND guid = ?"); + PrepareStatement(CharStatements.SEL_CHAR_DEL_INFO_BY_NAME, "SELECT guid, deleteInfos_Name, deleteInfos_Account, deleteDate FROM characters WHERE deleteDate IS NOT NULL AND deleteInfos_Name LIKE CONCAT('%%', ?, '%%')"); + PrepareStatement(CharStatements.SEL_CHAR_DEL_INFO, "SELECT guid, deleteInfos_Name, deleteInfos_Account, deleteDate FROM characters WHERE deleteDate IS NOT NULL"); + PrepareStatement(CharStatements.SEL_CHARS_BY_ACCOUNT_ID, "SELECT guid FROM characters WHERE account = ?"); + PrepareStatement(CharStatements.SEL_CHAR_PINFO, "SELECT totaltime, level, money, account, race, class, map, zone, gender, health, playerFlags FROM characters WHERE guid = ?"); + PrepareStatement(CharStatements.SEL_PINFO_BANS, "SELECT unbandate, bandate = unbandate, bannedby, banreason FROM character_banned WHERE guid = ? AND active ORDER BY bandate ASC LIMIT 1"); + //0: lowGUID + PrepareStatement(CharStatements.SEL_PINFO_MAILS, "SELECT SUM(CASE WHEN (checked & 1) THEN 1 ELSE 0 END) AS 'readmail', COUNT(*) AS 'totalmail' FROM mail WHERE `receiver` = ?"); + //0: lowGUID + PrepareStatement(CharStatements.SEL_PINFO_XP, "SELECT a.xp, b.guid FROM characters a LEFT JOIN guild_member b ON a.guid = b.guid WHERE a.guid = ?"); + PrepareStatement(CharStatements.SEL_CHAR_HOMEBIND, "SELECT mapId, zoneId, posX, posY, posZ FROM character_homebind WHERE guid = ?"); + PrepareStatement(CharStatements.SEL_CHAR_GUID_NAME_BY_ACC, "SELECT guid, name FROM characters WHERE account = ?"); + PrepareStatement(CharStatements.SEL_POOL_QUEST_SAVE, "SELECT quest_id FROM pool_quest_save WHERE pool_id = ?"); + PrepareStatement(CharStatements.SEL_CHAR_CUSTOMIZE_INFO, "SELECT name, race, class, gender, at_login FROM characters WHERE guid = ?"); + PrepareStatement(CharStatements.SEL_CHAR_RACE_OR_FACTION_CHANGE_INFOS, "SELECT at_login, knownTitles FROM characters WHERE guid = ?"); + PrepareStatement(CharStatements.SEL_INSTANCE, "SELECT data, completedEncounters, entranceId FROM instance WHERE map = ? AND id = ?"); + PrepareStatement(CharStatements.SEL_PERM_BIND_BY_INSTANCE, "SELECT guid FROM character_instance WHERE instance = ? and permanent = 1"); + PrepareStatement(CharStatements.SEL_CHAR_COD_ITEM_MAIL, "SELECT id, messageType, mailTemplateId, sender, subject, body, money, has_items FROM mail WHERE receiver = ? AND has_items <> 0 AND cod <> 0"); + PrepareStatement(CharStatements.SEL_CHAR_SOCIAL, "SELECT DISTINCT guid FROM character_social WHERE friend = ?"); + PrepareStatement(CharStatements.SEL_CHAR_OLD_CHARS, "SELECT guid, deleteInfos_Account FROM characters WHERE deleteDate IS NOT NULL AND deleteDate < ?"); + PrepareStatement(CharStatements.SEL_ARENA_TEAM_ID_BY_PLAYER_GUID, "SELECT arena_team_member.arenateamid FROM arena_team_member JOIN arena_team ON arena_team_member.arenateamid = arena_team.arenateamid WHERE guid = ? AND type = ? LIMIT 1"); + PrepareStatement(CharStatements.SEL_MAIL, "SELECT id, messageType, sender, receiver, subject, body, has_items, expire_time, deliver_time, money, cod, checked, stationery, mailTemplateId FROM mail WHERE receiver = ? ORDER BY id DESC"); + PrepareStatement(CharStatements.DEL_CHAR_AURA_FROZEN, "DELETE FROM character_aura WHERE spell = 9454 AND guid = ?"); + PrepareStatement(CharStatements.SEL_CHAR_INVENTORY_COUNT_ITEM, "SELECT COUNT(itemEntry) FROM character_inventory ci INNER JOIN item_instance ii ON ii.guid = ci.item WHERE itemEntry = ?"); + PrepareStatement(CharStatements.SEL_MAIL_COUNT_ITEM, "SELECT COUNT(itemEntry) FROM mail_items mi INNER JOIN item_instance ii ON ii.guid = mi.item_guid WHERE itemEntry = ?"); + PrepareStatement(CharStatements.SEL_AUCTIONHOUSE_COUNT_ITEM, "SELECT COUNT(itemEntry) FROM auctionhouse ah INNER JOIN item_instance ii ON ii.guid = ah.itemguid WHERE itemEntry = ?"); + PrepareStatement(CharStatements.SEL_GUILD_BANK_COUNT_ITEM, "SELECT COUNT(itemEntry) FROM guild_bank_item gbi INNER JOIN item_instance ii ON ii.guid = gbi.item_guid WHERE itemEntry = ?"); + PrepareStatement(CharStatements.SEL_CHAR_INVENTORY_ITEM_BY_ENTRY, "SELECT ci.item, cb.slot AS bag, ci.slot, ci.guid, c.account, c.name FROM characters c " + + "INNER JOIN character_inventory ci ON ci.guid = c.guid " + + "INNER JOIN item_instance ii ON ii.guid = ci.item " + + "LEFT JOIN character_inventory cb ON cb.item = ci.bag WHERE ii.itemEntry = ? LIMIT ?"); + PrepareStatement(CharStatements.SEL_MAIL_ITEMS_BY_ENTRY, "SELECT mi.item_guid, m.sender, m.receiver, cs.account, cs.name, cr.account, cr.name " + + "FROM mail m INNER JOIN mail_items mi ON mi.mail_id = m.id INNER JOIN item_instance ii ON ii.guid = mi.item_guid " + + "INNER JOIN characters cs ON cs.guid = m.sender INNER JOIN characters cr ON cr.guid = m.receiver WHERE ii.itemEntry = ? LIMIT ?"); + PrepareStatement(CharStatements.SEL_AUCTIONHOUSE_ITEM_BY_ENTRY, "SELECT ah.itemguid, ah.itemowner, c.account, c.name FROM auctionhouse ah INNER JOIN characters c ON c.guid = ah.itemowner INNER JOIN item_instance ii ON ii.guid = ah.itemguid WHERE ii.itemEntry = ? LIMIT ?"); + PrepareStatement(CharStatements.SEL_GUILD_BANK_ITEM_BY_ENTRY, "SELECT gi.item_guid, gi.guildid, g.name FROM guild_bank_item gi INNER JOIN guild g ON g.guildid = gi.guildid INNER JOIN item_instance ii ON ii.guid = gi.item_guid WHERE ii.itemEntry = ? LIMIT ?"); + PrepareStatement(CharStatements.DEL_CHAR_ACHIEVEMENT, "DELETE FROM character_achievement WHERE guid = ?"); + PrepareStatement(CharStatements.DEL_CHAR_ACHIEVEMENT_PROGRESS, "DELETE FROM character_achievement_progress WHERE guid = ?"); + PrepareStatement(CharStatements.INS_CHAR_ACHIEVEMENT, "INSERT INTO character_achievement (guid, achievement, date) VALUES (?, ?, ?)"); + PrepareStatement(CharStatements.DEL_CHAR_ACHIEVEMENT_PROGRESS_BY_CRITERIA, "DELETE FROM character_achievement_progress WHERE guid = ? AND criteria = ?"); + PrepareStatement(CharStatements.INS_CHAR_ACHIEVEMENT_PROGRESS, "INSERT INTO character_achievement_progress (guid, criteria, counter, date) VALUES (?, ?, ?, ?)"); + PrepareStatement(CharStatements.DEL_CHAR_REPUTATION_BY_FACTION, "DELETE FROM character_reputation WHERE guid = ? AND faction = ?"); + PrepareStatement(CharStatements.INS_CHAR_REPUTATION_BY_FACTION, "INSERT INTO character_reputation (guid, faction, standing, flags) VALUES (?, ?, ? , ?)"); + PrepareStatement(CharStatements.DEL_ITEM_REFUND_INSTANCE, "DELETE FROM item_refund_instance WHERE item_guid = ?"); + PrepareStatement(CharStatements.INS_ITEM_REFUND_INSTANCE, "INSERT INTO item_refund_instance (item_guid, player_guid, paidMoney, paidExtendedCost) VALUES (?, ?, ?, ?)"); + PrepareStatement(CharStatements.DEL_GROUP, "DELETE FROM groups WHERE guid = ?"); + PrepareStatement(CharStatements.DEL_GROUP_MEMBER_ALL, "DELETE FROM group_member WHERE guid = ?"); + PrepareStatement(CharStatements.INS_CHAR_GIFT, "INSERT INTO character_gifts (guid, item_guid, entry, flags) VALUES (?, ?, ?, ?)"); + PrepareStatement(CharStatements.DEL_INSTANCE_BY_INSTANCE, "DELETE FROM instance WHERE id = ?"); + PrepareStatement(CharStatements.DEL_CHAR_INSTANCE_BY_INSTANCE, "DELETE FROM character_instance WHERE instance = ?"); + PrepareStatement(CharStatements.DEL_EXPIRED_CHAR_INSTANCE_BY_MAP_DIFF, "DELETE FROM character_instance USING character_instance LEFT JOIN instance ON character_instance.instance = id WHERE (extendState = 0 or permanent = 0) and map = ? and difficulty = ?"); + PrepareStatement(CharStatements.DEL_GROUP_INSTANCE_BY_MAP_DIFF, "DELETE FROM group_instance USING group_instance LEFT JOIN instance ON group_instance.instance = id WHERE map = ? and difficulty = ?"); + PrepareStatement(CharStatements.DEL_EXPIRED_INSTANCE_BY_MAP_DIFF, "DELETE FROM instance WHERE map = ? and difficulty = ? and (SELECT guid FROM character_instance WHERE extendState != 0 AND instance = id LIMIT 1) IS NULL"); + PrepareStatement(CharStatements.UPD_EXPIRE_CHAR_INSTANCE_BY_MAP_DIFF, "UPDATE character_instance LEFT JOIN instance ON character_instance.instance = id SET extendState = extendState-1 WHERE map = ? and difficulty = ?"); + PrepareStatement(CharStatements.DEL_MAIL_ITEM_BY_ID, "DELETE FROM mail_items WHERE mail_id = ?"); + PrepareStatement(CharStatements.INS_PETITION, "INSERT INTO petition (ownerguid, petitionguid, name) VALUES (?, ?, ?)"); + PrepareStatement(CharStatements.DEL_PETITION_BY_GUID, "DELETE FROM petition WHERE petitionguid = ?"); + PrepareStatement(CharStatements.DEL_PETITION_SIGNATURE_BY_GUID, "DELETE FROM petition_sign WHERE petitionguid = ?"); + PrepareStatement(CharStatements.DEL_CHAR_DECLINED_NAME, "DELETE FROM character_declinedname WHERE guid = ?"); + PrepareStatement(CharStatements.INS_CHAR_DECLINED_NAME, "INSERT INTO character_declinedname (guid, genitive, dative, accusative, instrumental, prepositional) VALUES (?, ?, ?, ?, ?, ?)"); + PrepareStatement(CharStatements.UPD_CHAR_RACE, "UPDATE characters SET race = ? WHERE guid = ?"); + PrepareStatement(CharStatements.DEL_CHAR_SKILL_LANGUAGES, "DELETE FROM character_skills WHERE skill IN (98, 113, 759, 111, 313, 109, 115, 315, 673, 137) AND guid = ?"); + PrepareStatement(CharStatements.INS_CHAR_SKILL_LANGUAGE, "INSERT INTO `character_skills` (guid, skill, value, max) VALUES (?, ?, 300, 300)"); + PrepareStatement(CharStatements.UPD_CHAR_TAXI_PATH, "UPDATE characters SET taxi_path = '' WHERE guid = ?"); + PrepareStatement(CharStatements.UPD_CHAR_TAXIMASK, "UPDATE characters SET taximask = ? WHERE guid = ?"); + PrepareStatement(CharStatements.DEL_CHAR_QUESTSTATUS, "DELETE FROM character_queststatus WHERE guid = ?"); + PrepareStatement(CharStatements.DEL_CHAR_QUESTSTATUS_OBJECTIVES, "DELETE FROM character_queststatus_objectives WHERE guid = ?"); + PrepareStatement(CharStatements.DEL_CHAR_SOCIAL_BY_GUID, "DELETE FROM character_social WHERE guid = ?"); + PrepareStatement(CharStatements.DEL_CHAR_SOCIAL_BY_FRIEND, "DELETE FROM character_social WHERE friend = ?"); + PrepareStatement(CharStatements.DEL_CHAR_ACHIEVEMENT_BY_ACHIEVEMENT, "DELETE FROM character_achievement WHERE achievement = ? AND guid = ?"); + PrepareStatement(CharStatements.UPD_CHAR_ACHIEVEMENT, "UPDATE character_achievement SET achievement = ? where achievement = ? AND guid = ?"); + PrepareStatement(CharStatements.UPD_CHAR_INVENTORY_FACTION_CHANGE, "UPDATE item_instance ii, character_inventory ci SET ii.itemEntry = ? WHERE ii.itemEntry = ? AND ci.guid = ? AND ci.item = ii.guid"); + PrepareStatement(CharStatements.DEL_CHAR_SPELL_BY_SPELL, "DELETE FROM character_spell WHERE spell = ? AND guid = ?"); + PrepareStatement(CharStatements.UPD_CHAR_SPELL_FACTION_CHANGE, "UPDATE character_spell SET spell = ? where spell = ? AND guid = ?"); + PrepareStatement(CharStatements.SEL_CHAR_REP_BY_FACTION, "SELECT standing FROM character_reputation WHERE faction = ? AND guid = ?"); + PrepareStatement(CharStatements.DEL_CHAR_REP_BY_FACTION, "DELETE FROM character_reputation WHERE faction = ? AND guid = ?"); + PrepareStatement(CharStatements.UPD_CHAR_REP_FACTION_CHANGE, "UPDATE character_reputation SET faction = ?, standing = ? WHERE faction = ? AND guid = ?"); + PrepareStatement(CharStatements.UPD_CHAR_TITLES_FACTION_CHANGE, "UPDATE characters SET knownTitles = ? WHERE guid = ?"); + PrepareStatement(CharStatements.RES_CHAR_TITLES_FACTION_CHANGE, "UPDATE characters SET chosenTitle = 0 WHERE guid = ?"); + PrepareStatement(CharStatements.DEL_CHAR_SPELL_COOLDOWNS, "DELETE FROM character_spell_cooldown WHERE guid = ?"); + PrepareStatement(CharStatements.INS_CHAR_SPELL_COOLDOWN, "INSERT INTO character_spell_cooldown (guid, spell, item, time, categoryId, categoryEnd) VALUES (?, ?, ?, ?, ?, ?)"); + PrepareStatement(CharStatements.DEL_CHAR_SPELL_CHARGES, "DELETE FROM character_spell_charges WHERE guid = ?"); + PrepareStatement(CharStatements.INS_CHAR_SPELL_CHARGES, "INSERT INTO character_spell_charges (guid, categoryId, rechargeStart, rechargeEnd) VALUES (?, ?, ?, ?)"); + PrepareStatement(CharStatements.DEL_CHARACTER, "DELETE FROM characters WHERE guid = ?"); + PrepareStatement(CharStatements.DEL_CHAR_ACTION, "DELETE FROM character_action WHERE guid = ?"); + PrepareStatement(CharStatements.DEL_CHAR_AURA, "DELETE FROM character_aura WHERE guid = ?"); + PrepareStatement(CharStatements.DEL_CHAR_AURA_EFFECT, "DELETE FROM character_aura_effect WHERE guid = ?"); + PrepareStatement(CharStatements.DEL_CHAR_GIFT, "DELETE FROM character_gifts WHERE guid = ?"); + PrepareStatement(CharStatements.DEL_CHAR_INSTANCE, "DELETE FROM character_instance WHERE guid = ?"); + PrepareStatement(CharStatements.DEL_CHAR_INVENTORY, "DELETE FROM character_inventory WHERE guid = ?"); + PrepareStatement(CharStatements.DEL_CHAR_QUESTSTATUS_REWARDED, "DELETE FROM character_queststatus_rewarded WHERE guid = ?"); + PrepareStatement(CharStatements.DEL_CHAR_REPUTATION, "DELETE FROM character_reputation WHERE guid = ?"); + PrepareStatement(CharStatements.DEL_CHAR_SPELL, "DELETE FROM character_spell WHERE guid = ?"); + PrepareStatement(CharStatements.DEL_MAIL, "DELETE FROM mail WHERE receiver = ?"); + PrepareStatement(CharStatements.DEL_MAIL_ITEMS, "DELETE FROM mail_items WHERE receiver = ?"); + PrepareStatement(CharStatements.DEL_CHAR_ACHIEVEMENTS, "DELETE FROM character_achievement WHERE guid = ? AND achievement NOT IN (456,457,458,459,460,461,462,463,464,465,466,467,1400,1402,1404,1405,1406,1407,1408,1409,1410,1411,1412,1413,1414,1415,1416,1417,1418,1419,1420,1421,1422,1423,1424,1425,1426,1427,1463,3117,3259,4078,4576,4998,4999,5000,5001,5002,5003,5004,5005,5006,5007,5008,5381,5382,5383,5384,5385,5386,5387,5388,5389,5390,5391,5392,5393,5394,5395,5396,6433,6523,6524,6743,6744,6745,6746,6747,6748,6749,6750,6751,6752,6829,6859,6860,6861,6862,6863,6864,6865,6866,6867,6868,6869,6870,6871,6872,6873)"); + PrepareStatement(CharStatements.DEL_CHAR_EQUIPMENTSETS, "DELETE FROM character_equipmentsets WHERE guid = ?"); + PrepareStatement(CharStatements.DEL_CHAR_TRANSMOG_OUTFITS, "DELETE FROM character_transmog_outfits WHERE guid = ?"); + PrepareStatement(CharStatements.DEL_GUILD_EVENTLOG_BY_PLAYER, "DELETE FROM guild_eventlog WHERE PlayerGuid1 = ? OR PlayerGuid2 = ?"); + PrepareStatement(CharStatements.DEL_GUILD_BANK_EVENTLOG_BY_PLAYER, "DELETE FROM guild_bank_eventlog WHERE PlayerGuid = ?"); + PrepareStatement(CharStatements.DEL_CHAR_GLYPHS, "DELETE FROM character_glyphs WHERE guid = ?"); + PrepareStatement(CharStatements.DEL_CHAR_TALENT, "DELETE FROM character_talent WHERE guid = ?"); + PrepareStatement(CharStatements.DEL_CHAR_SKILLS, "DELETE FROM character_skills WHERE guid = ?"); + PrepareStatement(CharStatements.UPD_CHAR_MONEY, "UPDATE characters SET money = ? WHERE guid = ?"); + PrepareStatement(CharStatements.INS_CHAR_ACTION, "INSERT INTO character_action (guid, spec, button, action, type) VALUES (?, ?, ?, ?, ?)"); + PrepareStatement(CharStatements.UPD_CHAR_ACTION, "UPDATE character_action SET action = ?, type = ? WHERE guid = ? AND button = ? AND spec = ?"); + PrepareStatement(CharStatements.DEL_CHAR_ACTION_BY_BUTTON_SPEC, "DELETE FROM character_action WHERE guid = ? and button = ? and spec = ?"); + PrepareStatement(CharStatements.DEL_CHAR_INVENTORY_BY_ITEM, "DELETE FROM character_inventory WHERE item = ?"); + PrepareStatement(CharStatements.DEL_CHAR_INVENTORY_BY_BAG_SLOT, "DELETE FROM character_inventory WHERE bag = ? AND slot = ? AND guid = ?"); + PrepareStatement(CharStatements.UPD_MAIL, "UPDATE mail SET has_items = ?, expire_time = ?, deliver_time = ?, money = ?, cod = ?, checked = ? WHERE id = ?"); + PrepareStatement(CharStatements.REP_CHAR_QUESTSTATUS, "REPLACE INTO character_queststatus (guid, quest, status, timer) VALUES (?, ?, ?, ?)"); + PrepareStatement(CharStatements.DEL_CHAR_QUESTSTATUS_BY_QUEST, "DELETE FROM character_queststatus WHERE guid = ? AND quest = ?"); + PrepareStatement(CharStatements.REP_CHAR_QUESTSTATUS_OBJECTIVES, "REPLACE INTO character_queststatus_objectives (guid, quest, objective, data) VALUES (?, ?, ?, ?)"); + PrepareStatement(CharStatements.DEL_CHAR_QUESTSTATUS_OBJECTIVES_BY_QUEST, "DELETE FROM character_queststatus_objectives WHERE guid = ? AND quest = ?"); + PrepareStatement(CharStatements.INS_CHAR_QUESTSTATUS_REWARDED, "INSERT IGNORE INTO character_queststatus_rewarded (guid, quest, active) VALUES (?, ?, 1)"); + PrepareStatement(CharStatements.DEL_CHAR_QUESTSTATUS_REWARDED_BY_QUEST, "DELETE FROM character_queststatus_rewarded WHERE guid = ? AND quest = ?"); + PrepareStatement(CharStatements.UPD_CHAR_QUESTSTATUS_REWARDED_FACTION_CHANGE, "UPDATE character_queststatus_rewarded SET quest = ? WHERE quest = ? AND guid = ?"); + PrepareStatement(CharStatements.UPD_CHAR_QUESTSTATUS_REWARDED_ACTIVE, "UPDATE character_queststatus_rewarded SET active = 1 WHERE guid = ?"); + PrepareStatement(CharStatements.UPD_CHAR_QUESTSTATUS_REWARDED_ACTIVE_BY_QUEST, "UPDATE character_queststatus_rewarded SET active = 0 WHERE quest = ? AND guid = ?"); + PrepareStatement(CharStatements.DEL_CHAR_SKILL_BY_SKILL, "DELETE FROM character_skills WHERE guid = ? AND skill = ?"); + PrepareStatement(CharStatements.INS_CHAR_SKILLS, "INSERT INTO character_skills (guid, skill, value, max) VALUES (?, ?, ?, ?)"); + PrepareStatement(CharStatements.UPD_CHAR_SKILLS, "UPDATE character_skills SET value = ?, max = ? WHERE guid = ? AND skill = ?"); + PrepareStatement(CharStatements.INS_CHAR_SPELL, "INSERT INTO character_spell (guid, spell, active, disabled) VALUES (?, ?, ?, ?)"); + PrepareStatement(CharStatements.DEL_CHAR_STATS, "DELETE FROM character_stats WHERE guid = ?"); + PrepareStatement(CharStatements.INS_CHAR_STATS, "INSERT INTO character_stats (guid, maxhealth, maxpower1, maxpower2, maxpower3, maxpower4, maxpower5, maxpower6, strength, agility, stamina, intellect, " + + "armor, resHoly, resFire, resNature, resFrost, resShadow, resArcane, blockPct, dodgePct, parryPct, critPct, rangedCritPct, spellCritPct, attackPower, rangedAttackPower, " + + "spellPower, resilience) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)"); + PrepareStatement(CharStatements.DEL_PETITION_BY_OWNER, "DELETE FROM petition WHERE ownerguid = ?"); + PrepareStatement(CharStatements.DEL_PETITION_SIGNATURE_BY_OWNER, "DELETE FROM petition_sign WHERE ownerguid = ?"); + PrepareStatement(CharStatements.INS_CHAR_GLYPHS, "INSERT INTO character_glyphs VALUES(?, ?, ?)"); + PrepareStatement(CharStatements.INS_CHAR_TALENT, "INSERT INTO character_talent (guid, talentId, talentGroup) VALUES (?, ?, ?)"); + PrepareStatement(CharStatements.UPD_CHAR_LIST_SLOT, "UPDATE characters SET slot = ? WHERE guid = ? AND account = ?"); + PrepareStatement(CharStatements.INS_CHAR_FISHINGSTEPS, "INSERT INTO character_fishingsteps (guid, fishingSteps) VALUES (?, ?)"); + PrepareStatement(CharStatements.DEL_CHAR_FISHINGSTEPS, "DELETE FROM character_fishingsteps WHERE guid = ?"); + + // Void Storage + PrepareStatement(CharStatements.SEL_CHAR_VOID_STORAGE, "SELECT itemId, itemEntry, slot, creatorGuid, randomPropertyType, randomProperty, suffixFactor, upgradeId, fixedScalingLevel, artifactKnowledgeLevel, context, bonusListIDs FROM character_void_storage WHERE playerGuid = ?"); + PrepareStatement(CharStatements.REP_CHAR_VOID_STORAGE_ITEM, "REPLACE INTO character_void_storage (itemId, playerGuid, itemEntry, slot, creatorGuid, randomPropertyType, randomProperty, suffixFactor, upgradeId, fixedScalingLevel, artifactKnowledgeLevel, context, bonusListIDs) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)"); + PrepareStatement(CharStatements.DEL_CHAR_VOID_STORAGE_ITEM_BY_CHAR_GUID, "DELETE FROM character_void_storage WHERE playerGuid = ?"); + PrepareStatement(CharStatements.DEL_CHAR_VOID_STORAGE_ITEM_BY_SLOT, "DELETE FROM character_void_storage WHERE slot = ? AND playerGuid = ?"); + + // CompactUnitFrame profiles + PrepareStatement(CharStatements.SEL_CHAR_CUF_PROFILES, "SELECT id, name, frameHeight, frameWidth, sortBy, healthText, boolOptions, topPoint, bottomPoint, leftPoint, topOffset, bottomOffset, leftOffset FROM character_cuf_profiles WHERE guid = ?"); + PrepareStatement(CharStatements.REP_CHAR_CUF_PROFILES, "REPLACE INTO character_cuf_profiles (guid, id, name, frameHeight, frameWidth, sortBy, healthText, boolOptions, topPoint, bottomPoint, leftPoint, topOffset, bottomOffset, leftOffset) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)"); + PrepareStatement(CharStatements.DEL_CHAR_CUF_PROFILES_BY_ID, "DELETE FROM character_cuf_profiles WHERE guid = ? AND id = ?"); + PrepareStatement(CharStatements.DEL_CHAR_CUF_PROFILES, "DELETE FROM character_cuf_profiles WHERE guid = ?"); + + // Guild Finder + PrepareStatement(CharStatements.REP_GUILD_FINDER_APPLICANT, "REPLACE INTO guild_finder_applicant (guildId, playerGuid, availability, classRole, interests, comment, submitTime) VALUES(?, ?, ?, ?, ?, ?, ?)"); + PrepareStatement(CharStatements.DEL_GUILD_FINDER_APPLICANT, "DELETE FROM guild_finder_applicant WHERE guildId = ? AND playerGuid = ?"); + PrepareStatement(CharStatements.REP_GUILD_FINDER_GUILD_SETTINGS, "REPLACE INTO guild_finder_guild_settings (guildId, availability, classRoles, interests, level, listed, comment) VALUES(?, ?, ?, ?, ?, ?, ?)"); + PrepareStatement(CharStatements.DEL_GUILD_FINDER_GUILD_SETTINGS, "DELETE FROM guild_finder_guild_settings WHERE guildId = ?"); + + // Items that hold loot or money + PrepareStatement(CharStatements.SEL_ITEMCONTAINER_ITEMS, "SELECT item_id, item_count, follow_rules, ffa, blocked, counted, under_threshold, needs_quest, rnd_type, rnd_prop, rnd_suffix, context, bonus_list_ids FROM item_loot_items WHERE container_id = ?"); + PrepareStatement(CharStatements.DEL_ITEMCONTAINER_ITEMS, "DELETE FROM item_loot_items WHERE container_id = ?"); + PrepareStatement(CharStatements.DEL_ITEMCONTAINER_ITEM, "DELETE FROM item_loot_items WHERE container_id = ? AND item_id = ?"); + PrepareStatement(CharStatements.INS_ITEMCONTAINER_ITEMS, "INSERT INTO item_loot_items (container_id, item_id, item_count, follow_rules, ffa, blocked, counted, under_threshold, needs_quest, rnd_type, rnd_prop, rnd_suffix, context, bonus_list_ids) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)"); + PrepareStatement(CharStatements.SEL_ITEMCONTAINER_MONEY, "SELECT money FROM item_loot_money WHERE container_id = ?"); + PrepareStatement(CharStatements.DEL_ITEMCONTAINER_MONEY, "DELETE FROM item_loot_money WHERE container_id = ?"); + PrepareStatement(CharStatements.INS_ITEMCONTAINER_MONEY, "INSERT INTO item_loot_money (container_id, money) VALUES (?, ?)"); + + // Calendar + PrepareStatement(CharStatements.REP_CALENDAR_EVENT, "REPLACE INTO calendar_events (EventID, Owner, Title, Description, EventType, TextureID, Date, Flags, LockDate) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)"); + PrepareStatement(CharStatements.DEL_CALENDAR_EVENT, "DELETE FROM calendar_events WHERE EventID = ?"); + PrepareStatement(CharStatements.REP_CALENDAR_INVITE, "REPLACE INTO calendar_invites (InviteID, EventID, Invitee, Sender, Status, ResponseTime, ModerationRank, Note) VALUES (?, ?, ?, ?, ?, ?, ?, ?)"); + PrepareStatement(CharStatements.DEL_CALENDAR_INVITE, "DELETE FROM calendar_invites WHERE InviteID = ?"); + + // Pet + PrepareStatement(CharStatements.SEL_PET_SLOTS, "SELECT owner, slot FROM character_pet WHERE owner = ? AND slot >= ? AND slot <= ? ORDER BY slot"); + PrepareStatement(CharStatements.SEL_PET_SLOTS_DETAIL, "SELECT owner, id, entry, level, name, modelid FROM character_pet WHERE owner = ? AND slot >= ? AND slot <= ? ORDER BY slot"); + PrepareStatement(CharStatements.SEL_PET_ENTRY, "SELECT entry FROM character_pet WHERE owner = ? AND id = ? AND slot >= ? AND slot <= ?"); + PrepareStatement(CharStatements.SEL_PET_SLOT_BY_ID, "SELECT slot, entry FROM character_pet WHERE owner = ? AND id = ?"); + PrepareStatement(CharStatements.SEL_PET_SPELL_LIST, "SELECT DISTINCT pet_spell.spell FROM pet_spell, character_pet WHERE character_pet.owner = ? AND character_pet.id = pet_spell.guid AND character_pet.id <> ?"); + PrepareStatement(CharStatements.SEL_CHAR_PET, "SELECT id FROM character_pet WHERE owner = ? AND id <> ?"); + PrepareStatement(CharStatements.SEL_CHAR_PETS, "SELECT id FROM character_pet WHERE owner = ?"); + PrepareStatement(CharStatements.DEL_CHAR_PET_DECLINEDNAME_BY_OWNER, "DELETE FROM character_pet_declinedname WHERE owner = ?"); + PrepareStatement(CharStatements.DEL_CHAR_PET_DECLINEDNAME, "DELETE FROM character_pet_declinedname WHERE id = ?"); + PrepareStatement(CharStatements.INS_CHAR_PET_DECLINEDNAME, "INSERT INTO character_pet_declinedname (id, owner, genitive, dative, accusative, instrumental, prepositional) VALUES (?, ?, ?, ?, ?, ?, ?)"); + PrepareStatement(CharStatements.SEL_PET_AURA, "SELECT casterGuid, spell, effectMask, recalculateMask, stackCount, maxDuration, remainTime, remainCharges FROM pet_aura WHERE guid = ?"); + PrepareStatement(CharStatements.SEL_PET_AURA_EFFECT, "SELECT casterGuid, spell, effectMask, effectIndex, amount, baseAmount FROM pet_aura_effect WHERE guid = ?"); + PrepareStatement(CharStatements.SEL_PET_SPELL, "SELECT spell, active FROM pet_spell WHERE guid = ?"); + PrepareStatement(CharStatements.SEL_PET_SPELL_COOLDOWN, "SELECT spell, time, categoryId, categoryEnd FROM pet_spell_cooldown WHERE guid = ? AND time > UNIX_TIMESTAMP()"); + PrepareStatement(CharStatements.SEL_PET_DECLINED_NAME, "SELECT genitive, dative, accusative, instrumental, prepositional FROM character_pet_declinedname WHERE owner = ? AND id = ?"); + PrepareStatement(CharStatements.DEL_PET_AURAS, "DELETE FROM pet_aura WHERE guid = ?"); + PrepareStatement(CharStatements.DEL_PET_AURA_EFFECTS, "DELETE FROM pet_aura_effect WHERE guid = ?"); + PrepareStatement(CharStatements.DEL_PET_SPELLS, "DELETE FROM pet_spell WHERE guid = ?"); + PrepareStatement(CharStatements.DEL_PET_SPELL_COOLDOWNS, "DELETE FROM pet_spell_cooldown WHERE guid = ?"); + PrepareStatement(CharStatements.INS_PET_SPELL_COOLDOWN, "INSERT INTO pet_spell_cooldown (guid, spell, time, categoryId, categoryEnd) VALUES (?, ?, ?, ?, ?)"); + PrepareStatement(CharStatements.SEL_PET_SPELL_CHARGES, "SELECT categoryId, rechargeStart, rechargeEnd FROM pet_spell_charges WHERE guid = ? AND rechargeEnd > UNIX_TIMESTAMP() ORDER BY rechargeEnd"); + PrepareStatement(CharStatements.DEL_PET_SPELL_CHARGES, "DELETE FROM pet_spell_charges WHERE guid = ?"); + PrepareStatement(CharStatements.INS_PET_SPELL_CHARGES, "INSERT INTO pet_spell_charges (guid, categoryId, rechargeStart, rechargeEnd) VALUES (?, ?, ?, ?)"); + PrepareStatement(CharStatements.DEL_PET_SPELL_BY_SPELL, "DELETE FROM pet_spell WHERE guid = ? and spell = ?"); + PrepareStatement(CharStatements.INS_PET_SPELL, "INSERT INTO pet_spell (guid, spell, active) VALUES (?, ?, ?)"); + PrepareStatement(CharStatements.INS_PET_AURA, "INSERT INTO pet_aura (guid, casterGuid, spell, effectMask, recalculateMask, stackCount, maxDuration, remainTime, remainCharges) " + + "VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)"); + PrepareStatement(CharStatements.INS_PET_AURA_EFFECT, "INSERT INTO pet_aura_effect (guid, casterGuid, spell, effectMask, effectIndex, amount, baseAmount) " + + "VALUES (?, ?, ?, ?, ?, ?, ?)"); + PrepareStatement(CharStatements.SEL_CHAR_PET_BY_ENTRY, "SELECT id, entry, owner, modelid, level, exp, Reactstate, slot, name, renamed, curhealth, curmana, abdata, savetime, CreatedBySpell, PetType, specialization FROM character_pet WHERE owner = ? AND id = ?"); + PrepareStatement(CharStatements.SEL_CHAR_PET_BY_ENTRY_AND_SLOT_2, "SELECT id, entry, owner, modelid, level, exp, Reactstate, slot, name, renamed, curhealth, curmana, abdata, savetime, CreatedBySpell, PetType, specialization FROM character_pet WHERE owner = ? AND entry = ? AND (slot = ? OR slot > ?)"); + PrepareStatement(CharStatements.SEL_CHAR_PET_BY_SLOT, "SELECT id, entry, owner, modelid, level, exp, Reactstate, slot, name, renamed, curhealth, curmana, abdata, savetime, CreatedBySpell, PetType, specialization FROM character_pet WHERE owner = ? AND (slot = ? OR slot > ?) "); + PrepareStatement(CharStatements.SEL_CHAR_PET_BY_ENTRY_AND_SLOT, "SELECT id, entry, owner, modelid, level, exp, Reactstate, slot, name, renamed, curhealth, curmana, abdata, savetime, CreatedBySpell, PetType, specialization FROM character_pet WHERE owner = ? AND slot = ?"); + PrepareStatement(CharStatements.DEL_CHAR_PET_BY_OWNER, "DELETE FROM character_pet WHERE owner = ?"); + PrepareStatement(CharStatements.UPD_CHAR_PET_NAME, "UPDATE character_pet SET name = ?, renamed = 1 WHERE owner = ? AND id = ?"); + PrepareStatement(CharStatements.UPD_CHAR_PET_SLOT_BY_SLOT_EXCLUDE_ID, "UPDATE character_pet SET slot = ? WHERE owner = ? AND slot = ? AND id <> ?"); + PrepareStatement(CharStatements.UPD_CHAR_PET_SLOT_BY_SLOT, "UPDATE character_pet SET slot = ? WHERE owner = ? AND slot = ?"); + PrepareStatement(CharStatements.UPD_CHAR_PET_SLOT_BY_ID, "UPDATE character_pet SET slot = ? WHERE owner = ? AND id = ?"); + PrepareStatement(CharStatements.DEL_CHAR_PET_BY_ID, "DELETE FROM character_pet WHERE id = ?"); + PrepareStatement(CharStatements.DEL_CHAR_PET_BY_SLOT, "DELETE FROM character_pet WHERE owner = ? AND (slot = ? OR slot > ?)"); + PrepareStatement(CharStatements.DEL_ALL_PET_SPELLS_BY_OWNER, "DELETE FROM pet_spell WHERE guid in (SELECT id FROM character_pet WHERE owner=?)"); + PrepareStatement(CharStatements.UPD_PET_SPECS_BY_OWNER, "UPDATE character_pet SET specialization = 0 WHERE owner=?"); + PrepareStatement(CharStatements.INS_PET, "INSERT INTO character_pet (id, entry, owner, modelid, level, exp, Reactstate, slot, name, renamed, curhealth, curmana, abdata, savetime, CreatedBySpell, PetType, specialization) " + + "VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)"); + + // PvPstats + PrepareStatement(CharStatements.SEL_PVPSTATS_MAXID, "SELECT MAX(id) FROM pvpstats_battlegrounds"); + PrepareStatement(CharStatements.INS_PVPSTATS_BATTLEGROUND, "INSERT INTO pvpstats_battlegrounds (id, winner_faction, bracket_id, type, date) VALUES (?, ?, ?, ?, NOW())"); + PrepareStatement(CharStatements.INS_PVPSTATS_PLAYER, "INSERT INTO pvpstats_players (battleground_id, character_guid, winner, score_killing_blows, score_deaths, score_honorable_kills, score_bonus_honor, score_damage_done, score_healing_done, attr_1, attr_2, attr_3, attr_4, attr_5) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)"); + PrepareStatement(CharStatements.SEL_PVPSTATS_FACTIONS_OVERALL, "SELECT winner_faction, COUNT(*) AS count FROM pvpstats_battlegrounds WHERE DATEDIFF(NOW(), date) < 7 GROUP BY winner_faction ORDER BY winner_faction ASC"); + + // QuestTracker + PrepareStatement(CharStatements.INS_QUEST_TRACK, "INSERT INTO quest_tracker (id, character_guid, quest_accept_time, core_hash, core_revision) VALUES (?, ?, NOW(), ?, ?)"); + PrepareStatement(CharStatements.UPD_QUEST_TRACK_GM_COMPLETE, "UPDATE quest_tracker SET completed_by_gm = 1 WHERE id = ? AND character_guid = ? ORDER BY quest_accept_time DESC LIMIT 1"); + PrepareStatement(CharStatements.UPD_QUEST_TRACK_COMPLETE_TIME, "UPDATE quest_tracker SET quest_complete_time = NOW() WHERE id = ? AND character_guid = ? ORDER BY quest_accept_time DESC LIMIT 1"); + PrepareStatement(CharStatements.UPD_QUEST_TRACK_ABANDON_TIME, "UPDATE quest_tracker SET quest_abandon_time = NOW() WHERE id = ? AND character_guid = ? ORDER BY quest_accept_time DESC LIMIT 1"); + + // Garrison + PrepareStatement(CharStatements.SEL_CHARACTER_GARRISON, "SELECT siteLevelId, followerActivationsRemainingToday FROM character_garrison WHERE guid = ?"); + PrepareStatement(CharStatements.INS_CHARACTER_GARRISON, "INSERT INTO character_garrison (guid, siteLevelId, followerActivationsRemainingToday) VALUES (?, ?, ?)"); + PrepareStatement(CharStatements.DEL_CHARACTER_GARRISON, "DELETE FROM character_garrison WHERE guid = ?"); + PrepareStatement(CharStatements.UPD_CHARACTER_GARRISON_FOLLOWER_ACTIVATIONS, "UPDATE character_garrison SET followerActivationsRemainingToday = ?"); + PrepareStatement(CharStatements.SEL_CHARACTER_GARRISON_BLUEPRINTS, "SELECT buildingId FROM character_garrison_blueprints WHERE guid = ?"); + PrepareStatement(CharStatements.INS_CHARACTER_GARRISON_BLUEPRINTS, "INSERT INTO character_garrison_blueprints (guid, buildingId) VALUES (?, ?)"); + PrepareStatement(CharStatements.DEL_CHARACTER_GARRISON_BLUEPRINTS, "DELETE FROM character_garrison_blueprints WHERE guid = ?"); + PrepareStatement(CharStatements.SEL_CHARACTER_GARRISON_BUILDINGS, "SELECT plotInstanceId, buildingId, timeBuilt, active FROM character_garrison_buildings WHERE guid = ?"); + PrepareStatement(CharStatements.INS_CHARACTER_GARRISON_BUILDINGS, "INSERT INTO character_garrison_buildings (guid, plotInstanceId, buildingId, timeBuilt, active) VALUES (?, ?, ?, ?, ?)"); + PrepareStatement(CharStatements.DEL_CHARACTER_GARRISON_BUILDINGS, "DELETE FROM character_garrison_buildings WHERE guid = ?"); + PrepareStatement(CharStatements.SEL_CHARACTER_GARRISON_FOLLOWERS, "SELECT dbId, followerId, quality, level, itemLevelWeapon, itemLevelArmor, xp, currentBuilding, currentMission, status FROM character_garrison_followers WHERE guid = ?"); + PrepareStatement(CharStatements.INS_CHARACTER_GARRISON_FOLLOWERS, "INSERT INTO character_garrison_followers (dbId, guid, followerId, quality, level, itemLevelWeapon, itemLevelArmor, xp, currentBuilding, currentMission, status) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)"); + PrepareStatement(CharStatements.DEL_CHARACTER_GARRISON_FOLLOWERS, "DELETE gfab, gf FROM character_garrison_follower_abilities gfab INNER JOIN character_garrison_followers gf ON gfab.dbId = gf.dbId WHERE gf.guid = ?"); + PrepareStatement(CharStatements.SEL_CHARACTER_GARRISON_FOLLOWER_ABILITIES, "SELECT gfab.dbId, gfab.abilityId FROM character_garrison_follower_abilities gfab INNER JOIN character_garrison_followers gf ON gfab.dbId = gf.dbId WHERE guid = ? ORDER BY gfab.slot"); + PrepareStatement(CharStatements.INS_CHARACTER_GARRISON_FOLLOWER_ABILITIES, "INSERT INTO character_garrison_follower_abilities (dbId, abilityId, slot) VALUES (?, ?, ?)"); + + // Black Market + PrepareStatement(CharStatements.SEL_BLACKMARKET_AUCTIONS, "SELECT marketId, currentBid, time, numBids, bidder FROM blackmarket_auctions"); + PrepareStatement(CharStatements.DEL_BLACKMARKET_AUCTIONS, "DELETE FROM blackmarket_auctions WHERE marketId = ?"); + PrepareStatement(CharStatements.UPD_BLACKMARKET_AUCTIONS, "UPDATE blackmarket_auctions SET currentBid = ?, time = ?, numBids = ?, bidder = ? WHERE marketId = ?"); + PrepareStatement(CharStatements.INS_BLACKMARKET_AUCTIONS, "INSERT INTO blackmarket_auctions (marketId, currentBid, time, numBids, bidder) VALUES (?, ?, ?, ? ,?)"); + + // Scenario + PrepareStatement(CharStatements.SEL_SCENARIO_INSTANCE_CRITERIA_FOR_INSTANCE, "SELECT criteria, counter, date FROM instance_scenario_progress WHERE id = ?"); + PrepareStatement(CharStatements.DEL_SCENARIO_INSTANCE_CRITERIA, "DELETE FROM instance_scenario_progress WHERE id = ? AND criteria = ?"); + PrepareStatement(CharStatements.INS_SCENARIO_INSTANCE_CRITERIA, "INSERT INTO instance_scenario_progress (id, criteria, counter, date) VALUES (?, ?, ?, ?)"); + PrepareStatement(CharStatements.DEL_SCENARIO_INSTANCE_CRITERIA_FOR_INSTANCE, "DELETE FROM instance_scenario_progress WHERE id = ?"); + } + } + + public enum CharStatements + { + DEL_QUEST_POOL_SAVE, + INS_QUEST_POOL_SAVE, + DEL_NONEXISTENT_GUILD_BANK_ITEM, + DEL_EXPIRED_BANS, + SEL_GUID_BY_NAME, + SEL_CHECK_NAME, + SEL_CHECK_GUID, + SEL_SUM_CHARS, + SEL_CHAR_CREATE_INFO, + INS_CHARACTER_BAN, + UPD_CHARACTER_BAN, + DEL_CHARACTER_BAN, + SEL_BANINFO, + SEL_GUID_BY_NAME_FILTER, + SEL_BANINFO_LIST, + SEL_BANNED_NAME, + SEL_MAIL_LIST_COUNT, + SEL_MAIL_LIST_INFO, + SEL_MAIL_LIST_ITEMS, + SEL_ENUM, + SEL_ENUM_DECLINED_NAME, + SEL_UNDELETE_ENUM, + SEL_UNDELETE_ENUM_DECLINED_NAME, + SEL_FREE_NAME, + SEL_GUID_RACE_ACC_BY_NAME, + SEL_CHAR_LEVEL, + SEL_CHAR_ZONE, + SEL_CHAR_POSITION_XYZ, + SEL_CHAR_POSITION, + + DEL_BATTLEGROUND_RANDOM_ALL, + DEL_BATTLEGROUND_RANDOM, + INS_BATTLEGROUND_RANDOM, + + SEL_CHARACTER, + SEL_GROUP_MEMBER, + SEL_CHARACTER_INSTANCE, + SEL_CHARACTER_AURAS, + SEL_CHARACTER_AURA_EFFECTS, + SEL_CHARACTER_SPELL, + + SEL_CHARACTER_QUESTSTATUS, + SEL_CHARACTER_QUESTSTATUS_OBJECTIVES, + SEL_CHARACTER_QUESTSTATUS_DAILY, + SEL_CHARACTER_QUESTSTATUS_WEEKLY, + SEL_CHARACTER_QUESTSTATUS_MONTHLY, + SEL_CHARACTER_QUESTSTATUS_SEASONAL, + DEL_CHARACTER_QUESTSTATUS_DAILY, + DEL_CHARACTER_QUESTSTATUS_WEEKLY, + DEL_CHARACTER_QUESTSTATUS_MONTHLY, + DEL_CHARACTER_QUESTSTATUS_SEASONAL, + INS_CHARACTER_QUESTSTATUS_DAILY, + INS_CHARACTER_QUESTSTATUS_WEEKLY, + INS_CHARACTER_QUESTSTATUS_MONTHLY, + INS_CHARACTER_QUESTSTATUS_SEASONAL, + DEL_RESET_CHARACTER_QUESTSTATUS_DAILY, + DEL_RESET_CHARACTER_QUESTSTATUS_WEEKLY, + DEL_RESET_CHARACTER_QUESTSTATUS_MONTHLY, + DEL_RESET_CHARACTER_QUESTSTATUS_SEASONAL_BY_EVENT, + + SEL_CHARACTER_REPUTATION, + SEL_CHARACTER_INVENTORY, + SEL_CHARACTER_ACTIONS, + SEL_CHARACTER_ACTIONS_SPEC, + SEL_CHARACTER_MAILCOUNT, + SEL_CHARACTER_MAILDATE, + SEL_MAIL_COUNT, + SEL_CHARACTER_SOCIALLIST, + SEL_CHARACTER_HOMEBIND, + SEL_CHARACTER_SPELLCOOLDOWNS, + SEL_CHARACTER_SPELL_CHARGES, + SEL_CHARACTER_DECLINEDNAMES, + SEL_GUILD_MEMBER, + SEL_GUILD_MEMBER_EXTENDED, + SEL_CHARACTER_ARENAINFO, + SEL_CHARACTER_ACHIEVEMENTS, + SEL_CHARACTER_CRITERIAPROGRESS, + SEL_CHARACTER_EQUIPMENTSETS, + SEL_CHARACTER_BGDATA, + SEL_CHARACTER_GLYPHS, + SEL_CHARACTER_TALENTS, + SEL_CHARACTER_TRANSMOG_OUTFITS, + SEL_CHARACTER_SKILLS, + SEL_CHARACTER_RANDOMBG, + SEL_CHARACTER_BANNED, + SEL_CHARACTER_QUESTSTATUSREW, + SEL_ACCOUNT_INSTANCELOCKTIMES, + SEL_MAILITEMS, + SEL_AUCTION_ITEMS, + INS_AUCTION, + DEL_AUCTION, + UPD_AUCTION_BID, + SEL_AUCTIONS, + INS_MAIL, + DEL_MAIL_BY_ID, + INS_MAIL_ITEM, + DEL_MAIL_ITEM, + DEL_INVALID_MAIL_ITEM, + DEL_EMPTY_EXPIRED_MAIL, + SEL_EXPIRED_MAIL, + SEL_EXPIRED_MAIL_ITEMS, + UPD_MAIL_RETURNED, + UPD_MAIL_ITEM_RECEIVER, + UPD_ITEM_OWNER, + SEL_ITEM_REFUNDS, + SEL_ITEM_BOP_TRADE, + DEL_ITEM_BOP_TRADE, + INS_ITEM_BOP_TRADE, + REP_INVENTORY_ITEM, + REP_ITEM_INSTANCE, + UPD_ITEM_INSTANCE, + UPD_ITEM_INSTANCE_ON_LOAD, + DEL_ITEM_INSTANCE, + DEL_ITEM_INSTANCE_BY_OWNER, + INS_ITEM_INSTANCE_GEMS, + DEL_ITEM_INSTANCE_GEMS, + DEL_ITEM_INSTANCE_GEMS_BY_OWNER, + INS_ITEM_INSTANCE_TRANSMOG, + DEL_ITEM_INSTANCE_TRANSMOG, + DEL_ITEM_INSTANCE_TRANSMOG_BY_OWNER, + SEL_ITEM_INSTANCE_ARTIFACT, + INS_ITEM_INSTANCE_ARTIFACT, + DEL_ITEM_INSTANCE_ARTIFACT, + DEL_ITEM_INSTANCE_ARTIFACT_BY_OWNER, + INS_ITEM_INSTANCE_ARTIFACT_POWERS, + DEL_ITEM_INSTANCE_ARTIFACT_POWERS, + DEL_ITEM_INSTANCE_ARTIFACT_POWERS_BY_OWNER, + INS_ITEM_INSTANCE_MODIFIERS, + DEL_ITEM_INSTANCE_MODIFIERS, + DEL_ITEM_INSTANCE_MODIFIERS_BY_OWNER, + UPD_GIFT_OWNER, + DEL_GIFT, + SEL_CHARACTER_GIFT_BY_ITEM, + SEL_ACCOUNT_BY_NAME, + DEL_ACCOUNT_INSTANCE_LOCK_TIMES, + INS_ACCOUNT_INSTANCE_LOCK_TIMES, + SEL_MATCH_MAKER_RATING, + SEL_CHARACTER_COUNT, + UPD_NAME_BY_GUID, + + INS_GUILD, + DEL_GUILD, + UPD_GUILD_NAME, + INS_GUILD_MEMBER, + DEL_GUILD_MEMBER, + DEL_GUILD_MEMBERS, + INS_GUILD_RANK, + DEL_GUILD_RANKS, + DEL_GUILD_RANK, + INS_GUILD_BANK_TAB, + DEL_GUILD_BANK_TAB, + DEL_GUILD_BANK_TABS, + INS_GUILD_BANK_ITEM, + DEL_GUILD_BANK_ITEM, + SEL_GUILD_BANK_ITEMS, + DEL_GUILD_BANK_ITEMS, + INS_GUILD_BANK_RIGHT, + DEL_GUILD_BANK_RIGHTS, + DEL_GUILD_BANK_RIGHTS_FOR_RANK, + INS_GUILD_BANK_EVENTLOG, + DEL_GUILD_BANK_EVENTLOG, + DEL_GUILD_BANK_EVENTLOGS, + INS_GUILD_EVENTLOG, + DEL_GUILD_EVENTLOG, + DEL_GUILD_EVENTLOGS, + UPD_GUILD_MEMBER_PNOTE, + UPD_GUILD_MEMBER_OFFNOTE, + UPD_GUILD_MEMBER_RANK, + UPD_GUILD_MOTD, + UPD_GUILD_INFO, + UPD_GUILD_LEADER, + UPD_GUILD_RANK_NAME, + UPD_GUILD_RANK_RIGHTS, + UPD_GUILD_EMBLEM_INFO, + UPD_GUILD_BANK_TAB_INFO, + UPD_GUILD_BANK_MONEY, + UPD_GUILD_RANK_BANK_MONEY, + UPD_GUILD_BANK_TAB_TEXT, + INS_GUILD_MEMBER_WITHDRAW, + DEL_GUILD_MEMBER_WITHDRAW, + SEL_CHAR_DATA_FOR_GUILD, + DEL_GUILD_ACHIEVEMENT, + INS_GUILD_ACHIEVEMENT, + DEL_GUILD_ACHIEVEMENT_CRITERIA, + INS_GUILD_ACHIEVEMENT_CRITERIA, + DEL_ALL_GUILD_ACHIEVEMENTS, + DEL_ALL_GUILD_ACHIEVEMENT_CRITERIA, + SEL_GUILD_ACHIEVEMENT, + SEL_GUILD_ACHIEVEMENT_CRITERIA, + INS_GUILD_NEWS, + + SEL_CHANNEL, + INS_CHANNEL, + UPD_CHANNEL, + UPD_CHANNEL_USAGE, + UPD_CHANNEL_OWNERSHIP, + DEL_OLD_CHANNELS, + + UPD_EQUIP_SET, + INS_EQUIP_SET, + DEL_EQUIP_SET, + + UPD_TRANSMOG_OUTFIT, + INS_TRANSMOG_OUTFIT, + DEL_TRANSMOG_OUTFIT, + + INS_AURA, + INS_AURA_EFFECT, + + SEL_PLAYER_CURRENCY, + UPD_PLAYER_CURRENCY, + REP_PLAYER_CURRENCY, + DEL_PLAYER_CURRENCY, + + SEL_ACCOUNT_DATA, + REP_ACCOUNT_DATA, + DEL_ACCOUNT_DATA, + SEL_PLAYER_ACCOUNT_DATA, + REP_PLAYER_ACCOUNT_DATA, + DEL_PLAYER_ACCOUNT_DATA, + + SEL_TUTORIALS, + SEL_HAS_TUTORIALS, + INS_TUTORIALS, + UPD_TUTORIALS, + DEL_TUTORIALS, + + INS_INSTANCE_SAVE, + UPD_INSTANCE_DATA, + + DEL_GAME_EVENT_SAVE, + INS_GAME_EVENT_SAVE, + + DEL_ALL_GAME_EVENT_CONDITION_SAVE, + DEL_GAME_EVENT_CONDITION_SAVE, + INS_GAME_EVENT_CONDITION_SAVE, + + INS_ARENA_TEAM, + INS_ARENA_TEAM_MEMBER, + DEL_ARENA_TEAM, + DEL_ARENA_TEAM_MEMBERS, + UPD_ARENA_TEAM_CAPTAIN, + DEL_ARENA_TEAM_MEMBER, + UPD_ARENA_TEAM_STATS, + UPD_ARENA_TEAM_MEMBER, + DEL_CHARACTER_ARENA_STATS, + REP_CHARACTER_ARENA_STATS, + SEL_PLAYER_ARENA_TEAMS, + UPD_ARENA_TEAM_NAME, + + SEL_PETITION, + SEL_PETITION_SIGNATURE, + DEL_ALL_PETITION_SIGNATURES, + SEL_PETITION_BY_OWNER, + SEL_PETITION_SIGNATURES, + SEL_PETITION_SIG_BY_ACCOUNT, + SEL_PETITION_OWNER_BY_GUID, + SEL_PETITION_SIG_BY_GUID, + + INS_PLAYER_BGDATA, + DEL_PLAYER_BGDATA, + + INS_PLAYER_HOMEBIND, + UPD_PLAYER_HOMEBIND, + DEL_PLAYER_HOMEBIND, + + SEL_CORPSES, + INS_CORPSE, + DEL_CORPSE, + DEL_CORPSES_FROM_MAP, + SEL_CORPSE_PHASES, + INS_CORPSE_PHASES, + DEL_CORPSE_PHASES, + SEL_CORPSE_LOCATION, + + SEL_CREATURE_RESPAWNS, + REP_CREATURE_RESPAWN, + DEL_CREATURE_RESPAWN, + DEL_CREATURE_RESPAWN_BY_INSTANCE, + SEL_MAX_CREATURE_RESPAWNS, + + SEL_GO_RESPAWNS, + REP_GO_RESPAWN, + DEL_GO_RESPAWN, + DEL_GO_RESPAWN_BY_INSTANCE, + + SEL_GM_BUGS, + REP_GM_BUG, + DEL_GM_BUG, + DEL_ALL_GM_BUGS, + + SEL_GM_COMPLAINTS, + REP_GM_COMPLAINT, + DEL_GM_COMPLAINT, + SEL_GM_COMPLAINT_CHATLINES, + INS_GM_COMPLAINT_CHATLINE, + DEL_GM_COMPLAINT_CHATLOG, + DEL_ALL_GM_COMPLAINTS, + DEL_ALL_GM_COMPLAINT_CHATLOGS, + + SEL_GM_SUGGESTIONS, + REP_GM_SUGGESTION, + DEL_GM_SUGGESTION, + DEL_ALL_GM_SUGGESTIONS, + + INS_CHARACTER, + UPD_CHARACTER, + + UPD_ADD_AT_LOGIN_FLAG, + UPD_REM_AT_LOGIN_FLAG, + UPD_ALL_AT_LOGIN_FLAGS, + INS_BUG_REPORT, + UPD_PETITION_NAME, + INS_PETITION_SIGNATURE, + UPD_ACCOUNT_ONLINE, + INS_GROUP, + INS_GROUP_MEMBER, + DEL_GROUP_MEMBER, + DEL_GROUP_INSTANCE_PERM_BINDING, + UPD_GROUP_LEADER, + UPD_GROUP_TYPE, + UPD_GROUP_MEMBER_SUBGROUP, + UPD_GROUP_MEMBER_FLAG, + UPD_GROUP_DIFFICULTY, + UPD_GROUP_RAID_DIFFICULTY, + UPD_GROUP_LEGACY_RAID_DIFFICULTY, + DEL_INVALID_SPELL_SPELLS, + UPD_DELETE_INFO, + UPD_RESTORE_DELETE_INFO, + UPD_ZONE, + UPD_LEVEL, + DEL_INVALID_ACHIEV_PROGRESS_CRITERIA, + DEL_INVALID_ACHIEV_PROGRESS_CRITERIA_GUILD, + DEL_INVALID_ACHIEVMENT, + DEL_INVALID_PET_SPELL, + DEL_GROUP_INSTANCE_BY_INSTANCE, + DEL_GROUP_INSTANCE_BY_GUID, + REP_GROUP_INSTANCE, + UPD_INSTANCE_RESETTIME, + UPD_GLOBAL_INSTANCE_RESETTIME, + UPD_CHAR_ONLINE, + UPD_CHAR_NAME_AT_LOGIN, + UPD_WORLDSTATE, + INS_WORLDSTATE, + DEL_CHAR_INSTANCE_BY_INSTANCE_GUID, + UPD_CHAR_INSTANCE, + INS_CHAR_INSTANCE, + UPD_GENDER_AND_APPEARANCE, + DEL_CHARACTER_SKILL, + UPD_CHARACTER_SOCIAL_FLAGS, + INS_CHARACTER_SOCIAL, + DEL_CHARACTER_SOCIAL, + UPD_CHARACTER_SOCIAL_NOTE, + UPD_CHARACTER_POSITION, + + INS_LFG_DATA, + DEL_LFG_DATA, + + SEL_CHARACTER_AURA_FROZEN, + SEL_CHARACTER_ONLINE, + + SEL_CHAR_DEL_INFO_BY_GUID, + SEL_CHAR_DEL_INFO_BY_NAME, + SEL_CHAR_DEL_INFO, + + SEL_CHARS_BY_ACCOUNT_ID, + SEL_CHAR_PINFO, + SEL_PINFO_XP, + SEL_PINFO_MAILS, + SEL_PINFO_BANS, + SEL_CHAR_HOMEBIND, + SEL_CHAR_GUID_NAME_BY_ACC, + SEL_POOL_QUEST_SAVE, + SEL_CHAR_CUSTOMIZE_INFO, + SEL_CHAR_RACE_OR_FACTION_CHANGE_INFOS, + SEL_INSTANCE, + SEL_PERM_BIND_BY_INSTANCE, + SEL_CHAR_COD_ITEM_MAIL, + SEL_CHAR_SOCIAL, + SEL_CHAR_OLD_CHARS, + SEL_ARENA_TEAM_ID_BY_PLAYER_GUID, + SEL_MAIL, + DEL_CHAR_AURA_FROZEN, + SEL_CHAR_INVENTORY_COUNT_ITEM, + SEL_MAIL_COUNT_ITEM, + SEL_AUCTIONHOUSE_COUNT_ITEM, + SEL_GUILD_BANK_COUNT_ITEM, + SEL_CHAR_INVENTORY_ITEM_BY_ENTRY, + SEL_MAIL_ITEMS_BY_ENTRY, + SEL_AUCTIONHOUSE_ITEM_BY_ENTRY, + SEL_GUILD_BANK_ITEM_BY_ENTRY, + DEL_CHAR_ACHIEVEMENT, + DEL_CHAR_ACHIEVEMENT_PROGRESS, + INS_CHAR_ACHIEVEMENT, + DEL_CHAR_ACHIEVEMENT_PROGRESS_BY_CRITERIA, + INS_CHAR_ACHIEVEMENT_PROGRESS, + DEL_CHAR_REPUTATION_BY_FACTION, + INS_CHAR_REPUTATION_BY_FACTION, + DEL_ITEM_REFUND_INSTANCE, + INS_ITEM_REFUND_INSTANCE, + DEL_GROUP, + DEL_GROUP_MEMBER_ALL, + INS_CHAR_GIFT, + DEL_INSTANCE_BY_INSTANCE, + DEL_CHAR_INSTANCE_BY_INSTANCE, + DEL_EXPIRED_CHAR_INSTANCE_BY_MAP_DIFF, + DEL_GROUP_INSTANCE_BY_MAP_DIFF, + DEL_EXPIRED_INSTANCE_BY_MAP_DIFF, + UPD_EXPIRE_CHAR_INSTANCE_BY_MAP_DIFF, + DEL_MAIL_ITEM_BY_ID, + INS_PETITION, + DEL_PETITION_BY_GUID, + DEL_PETITION_SIGNATURE_BY_GUID, + DEL_CHAR_DECLINED_NAME, + INS_CHAR_DECLINED_NAME, + UPD_CHAR_RACE, + DEL_CHAR_SKILL_LANGUAGES, + INS_CHAR_SKILL_LANGUAGE, + UPD_CHAR_TAXI_PATH, + UPD_CHAR_TAXIMASK, + DEL_CHAR_QUESTSTATUS, + DEL_CHAR_QUESTSTATUS_OBJECTIVES, + DEL_CHAR_SOCIAL_BY_GUID, + DEL_CHAR_SOCIAL_BY_FRIEND, + DEL_CHAR_ACHIEVEMENT_BY_ACHIEVEMENT, + UPD_CHAR_ACHIEVEMENT, + UPD_CHAR_INVENTORY_FACTION_CHANGE, + DEL_CHAR_SPELL_BY_SPELL, + UPD_CHAR_SPELL_FACTION_CHANGE, + SEL_CHAR_REP_BY_FACTION, + DEL_CHAR_REP_BY_FACTION, + UPD_CHAR_REP_FACTION_CHANGE, + UPD_CHAR_TITLES_FACTION_CHANGE, + RES_CHAR_TITLES_FACTION_CHANGE, + DEL_CHAR_SPELL_COOLDOWNS, + INS_CHAR_SPELL_COOLDOWN, + DEL_CHAR_SPELL_CHARGES, + INS_CHAR_SPELL_CHARGES, + DEL_CHARACTER, + DEL_CHAR_ACTION, + DEL_CHAR_AURA, + DEL_CHAR_AURA_EFFECT, + DEL_CHAR_GIFT, + DEL_CHAR_INSTANCE, + DEL_CHAR_INVENTORY, + DEL_CHAR_QUESTSTATUS_REWARDED, + DEL_CHAR_REPUTATION, + DEL_CHAR_SPELL, + DEL_MAIL, + DEL_MAIL_ITEMS, + DEL_CHAR_ACHIEVEMENTS, + DEL_CHAR_EQUIPMENTSETS, + DEL_CHAR_TRANSMOG_OUTFITS, + DEL_GUILD_EVENTLOG_BY_PLAYER, + DEL_GUILD_BANK_EVENTLOG_BY_PLAYER, + DEL_CHAR_GLYPHS, + DEL_CHAR_TALENT, + DEL_CHAR_SKILLS, + UPD_CHAR_MONEY, + INS_CHAR_ACTION, + UPD_CHAR_ACTION, + DEL_CHAR_ACTION_BY_BUTTON_SPEC, + DEL_CHAR_INVENTORY_BY_ITEM, + DEL_CHAR_INVENTORY_BY_BAG_SLOT, + UPD_MAIL, + REP_CHAR_QUESTSTATUS, + DEL_CHAR_QUESTSTATUS_BY_QUEST, + REP_CHAR_QUESTSTATUS_OBJECTIVES, + DEL_CHAR_QUESTSTATUS_OBJECTIVES_BY_QUEST, + INS_CHAR_QUESTSTATUS_REWARDED, + DEL_CHAR_QUESTSTATUS_REWARDED_BY_QUEST, + UPD_CHAR_QUESTSTATUS_REWARDED_FACTION_CHANGE, + UPD_CHAR_QUESTSTATUS_REWARDED_ACTIVE, + UPD_CHAR_QUESTSTATUS_REWARDED_ACTIVE_BY_QUEST, + DEL_CHAR_SKILL_BY_SKILL, + INS_CHAR_SKILLS, + UPD_CHAR_SKILLS, + INS_CHAR_SPELL, + DEL_CHAR_STATS, + INS_CHAR_STATS, + DEL_PETITION_BY_OWNER, + DEL_PETITION_SIGNATURE_BY_OWNER, + INS_CHAR_GLYPHS, + INS_CHAR_TALENT, + UPD_CHAR_LIST_SLOT, + INS_CHAR_FISHINGSTEPS, + DEL_CHAR_FISHINGSTEPS, + + SEL_CHAR_VOID_STORAGE, + REP_CHAR_VOID_STORAGE_ITEM, + DEL_CHAR_VOID_STORAGE_ITEM_BY_CHAR_GUID, + DEL_CHAR_VOID_STORAGE_ITEM_BY_SLOT, + + SEL_CHAR_CUF_PROFILES, + REP_CHAR_CUF_PROFILES, + DEL_CHAR_CUF_PROFILES_BY_ID, + DEL_CHAR_CUF_PROFILES, + + REP_GUILD_FINDER_APPLICANT, + DEL_GUILD_FINDER_APPLICANT, + REP_GUILD_FINDER_GUILD_SETTINGS, + DEL_GUILD_FINDER_GUILD_SETTINGS, + + REP_CALENDAR_EVENT, + DEL_CALENDAR_EVENT, + REP_CALENDAR_INVITE, + DEL_CALENDAR_INVITE, + + SEL_PET_AURA, + SEL_PET_AURA_EFFECT, + SEL_PET_SPELL, + SEL_PET_SPELL_COOLDOWN, + SEL_PET_DECLINED_NAME, + DEL_PET_AURAS, + DEL_PET_AURA_EFFECTS, + DEL_PET_SPELL_COOLDOWNS, + INS_PET_SPELL_COOLDOWN, + SEL_PET_SPELL_CHARGES, + DEL_PET_SPELL_CHARGES, + INS_PET_SPELL_CHARGES, + DEL_PET_SPELL_BY_SPELL, + INS_PET_SPELL, + INS_PET_AURA, + INS_PET_AURA_EFFECT, + + DEL_PET_SPELLS, + DEL_CHAR_PET_BY_OWNER, + DEL_CHAR_PET_DECLINEDNAME_BY_OWNER, + SEL_CHAR_PET_BY_ENTRY_AND_SLOT, + SEL_PET_SLOTS, + SEL_PET_SLOTS_DETAIL, + SEL_PET_ENTRY, + SEL_PET_SLOT_BY_ID, + SEL_PET_SPELL_LIST, + SEL_CHAR_PET, + SEL_CHAR_PETS, + INS_CHAR_PET, + SEL_CHAR_PET_BY_ENTRY, + SEL_CHAR_PET_BY_ENTRY_AND_SLOT_2, + SEL_CHAR_PET_BY_SLOT, + DEL_CHAR_PET_DECLINEDNAME, + INS_CHAR_PET_DECLINEDNAME, + UPD_CHAR_PET_NAME, + UPD_CHAR_PET_SLOT_BY_SLOT_EXCLUDE_ID, + UPD_CHAR_PET_SLOT_BY_SLOT, + UPD_CHAR_PET_SLOT_BY_ID, + DEL_CHAR_PET_BY_ID, + DEL_CHAR_PET_BY_SLOT, + DEL_ALL_PET_SPELLS_BY_OWNER, + UPD_PET_SPECS_BY_OWNER, + INS_PET, + + SEL_ITEMCONTAINER_ITEMS, + DEL_ITEMCONTAINER_ITEMS, + DEL_ITEMCONTAINER_ITEM, + INS_ITEMCONTAINER_ITEMS, + SEL_ITEMCONTAINER_MONEY, + DEL_ITEMCONTAINER_MONEY, + INS_ITEMCONTAINER_MONEY, + + SEL_PVPSTATS_MAXID, + INS_PVPSTATS_BATTLEGROUND, + INS_PVPSTATS_PLAYER, + SEL_PVPSTATS_FACTIONS_OVERALL, + + INS_QUEST_TRACK, + UPD_QUEST_TRACK_GM_COMPLETE, + UPD_QUEST_TRACK_COMPLETE_TIME, + UPD_QUEST_TRACK_ABANDON_TIME, + + SEL_CHARACTER_GARRISON, + INS_CHARACTER_GARRISON, + DEL_CHARACTER_GARRISON, + UPD_CHARACTER_GARRISON_FOLLOWER_ACTIVATIONS, + SEL_CHARACTER_GARRISON_BLUEPRINTS, + INS_CHARACTER_GARRISON_BLUEPRINTS, + DEL_CHARACTER_GARRISON_BLUEPRINTS, + SEL_CHARACTER_GARRISON_BUILDINGS, + INS_CHARACTER_GARRISON_BUILDINGS, + DEL_CHARACTER_GARRISON_BUILDINGS, + SEL_CHARACTER_GARRISON_FOLLOWERS, + INS_CHARACTER_GARRISON_FOLLOWERS, + DEL_CHARACTER_GARRISON_FOLLOWERS, + SEL_CHARACTER_GARRISON_FOLLOWER_ABILITIES, + INS_CHARACTER_GARRISON_FOLLOWER_ABILITIES, + + SEL_BLACKMARKET_AUCTIONS, + DEL_BLACKMARKET_AUCTIONS, + UPD_BLACKMARKET_AUCTIONS, + INS_BLACKMARKET_AUCTIONS, + + SEL_SCENARIO_INSTANCE_CRITERIA_FOR_INSTANCE, + DEL_SCENARIO_INSTANCE_CRITERIA, + INS_SCENARIO_INSTANCE_CRITERIA, + DEL_SCENARIO_INSTANCE_CRITERIA_FOR_INSTANCE, + + MAX_CHARACTERDATABASE_STATEMENTS + } +} diff --git a/Framework/Database/Databases/HotfixDatabase.cs b/Framework/Database/Databases/HotfixDatabase.cs new file mode 100644 index 000000000..28b67e3e0 --- /dev/null +++ b/Framework/Database/Databases/HotfixDatabase.cs @@ -0,0 +1,1490 @@ +/* + * Copyright (C) 2012-2017 CypherCore + * + * 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, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +namespace Framework.Database +{ + public class HotfixDatabase : MySqlBase + { + public override void PreparedStatements() + { + // Achievement.db2 + PrepareStatement(HotfixStatements.SEL_ACHIEVEMENT, "SELECT Title, Description, Flags, Reward, MapID, Supercedes, Category, UIOrder, SharesCriteria, " + + "CriteriaTree, Faction, Points, MinimumCriteria, ID, IconFileDataID FROM achievement ORDER BY ID DESC"); + PrepareStatement(HotfixStatements.SEL_ACHIEVEMENT_LOCALE, "SELECT ID, Title_lang, Description_lang, Reward_lang FROM achievement_locale WHERE locale = ?"); + + // AnimKit.db2 + PrepareStatement(HotfixStatements.SEL_ANIM_KIT, "SELECT ID, OneShotDuration, OneShotStopAnimKitID, LowDefAnimKitID FROM anim_kit ORDER BY ID DESC"); + + // AreaGroupMember.db2 + PrepareStatement(HotfixStatements.SEL_AREA_GROUP_MEMBER, "SELECT ID, AreaGroupID, AreaID FROM area_group_member ORDER BY ID DESC"); + + // AreaTable.db2 + PrepareStatement(HotfixStatements.SEL_AREA_TABLE, "SELECT ID, Flags1, Flags2, ZoneName, AmbientMultiplier, AreaName, MapID, ParentAreaID, AreaBit, " + + "AmbienceID, ZoneMusic, IntroSound, LiquidTypeID1, LiquidTypeID2, LiquidTypeID3, LiquidTypeID4, UWZoneMusic, UWAmbience, " + + "PvPCombatWorldStateID, SoundProviderPref, SoundProviderPrefUnderwater, ExplorationLevel, FactionGroupMask, MountFlags, " + + "WildBattlePetLevelMin, WildBattlePetLevelMax, WindSettingsID, UWIntroSound FROM area_table ORDER BY ID DESC"); + PrepareStatement(HotfixStatements.SEL_AREA_TABLE_LOCALE, "SELECT ID, AreaName_lang FROM area_table_locale WHERE locale = ?"); + + // AreaTrigger.db2 + PrepareStatement(HotfixStatements.SEL_AREA_TRIGGER, "SELECT PosX, PosY, PosZ, Radius, BoxLength, BoxWidth, BoxHeight, BoxYaw, MapID, PhaseID, PhaseGroupID, " + + "ShapeID, AreaTriggerActionSetID, PhaseUseFlags, ShapeType, Flag, ID FROM area_trigger ORDER BY ID DESC"); + + // ArmorLocation.db2 + PrepareStatement(HotfixStatements.SEL_ARMOR_LOCATION, "SELECT ID, Modifier1, Modifier2, Modifier3, Modifier4, Modifier5 FROM armor_location ORDER BY ID DESC"); + + // Artifact.db2 + PrepareStatement(HotfixStatements.SEL_ARTIFACT, "SELECT ID, Name, BarConnectedColor, BarDisconnectedColor, TitleColor, ClassUiTextureKitID, SpecID, " + + "ArtifactCategoryID, Flags, UiModelSceneID, SpellVisualKitID FROM artifact ORDER BY ID DESC"); + PrepareStatement(HotfixStatements.SEL_ARTIFACT_LOCALE, "SELECT ID, Name_lang FROM artifact_locale WHERE locale = ?"); + + // ArtifactAppearance.db2 + PrepareStatement(HotfixStatements.SEL_ARTIFACT_APPEARANCE, "SELECT Name, SwatchColor, ModelDesaturation, ModelAlpha, ShapeshiftDisplayID, " + + "ArtifactAppearanceSetID, Unknown, DisplayIndex, AppearanceModID, Flags, ModifiesShapeshiftFormDisplay, ID, PlayerConditionID, " + + "ItemAppearanceID, AltItemAppearanceID FROM artifact_appearance ORDER BY ID DESC"); + PrepareStatement(HotfixStatements.SEL_ARTIFACT_APPEARANCE_LOCALE, "SELECT ID, Name_lang FROM artifact_appearance_locale WHERE locale = ?"); + + // ArtifactAppearanceSet.db2 + PrepareStatement(HotfixStatements.SEL_ARTIFACT_APPEARANCE_SET, "SELECT Name, Name2, UiCameraID, AltHandUICameraID, ArtifactID, DisplayIndex, " + + "AttachmentPoint, Flags, ID FROM artifact_appearance_set ORDER BY ID DESC"); + PrepareStatement(HotfixStatements.SEL_ARTIFACT_APPEARANCE_SET_LOCALE, "SELECT ID, Name_lang, Name2_lang FROM artifact_appearance_set_locale WHERE locale = ?"); + + // ArtifactCategory.db2 + PrepareStatement(HotfixStatements.SEL_ARTIFACT_CATEGORY, "SELECT ID, ArtifactKnowledgeCurrencyID, ArtifactKnowledgeMultiplierCurveID FROM artifact_category ORDER BY ID DESC"); + + // ArtifactPower.db2 + PrepareStatement(HotfixStatements.SEL_ARTIFACT_POWER, "SELECT PosX, PosY, ArtifactID, Flags, MaxRank, ArtifactTier, ID, RelicType FROM artifact_power" + + " ORDER BY ID DESC"); + + // ArtifactPowerLink.db2 + PrepareStatement(HotfixStatements.SEL_ARTIFACT_POWER_LINK, "SELECT ID, FromArtifactPowerID, ToArtifactPowerID FROM artifact_power_link ORDER BY ID DESC"); + + // ArtifactPowerPicker.db2 + PrepareStatement(HotfixStatements.SEL_ARTIFACT_POWER_PICKER, "SELECT ID, PlayerConditionID FROM artifact_power_picker ORDER BY ID DESC"); + + // ArtifactPowerRank.db2 + PrepareStatement(HotfixStatements.SEL_ARTIFACT_POWER_RANK, "SELECT ID, SpellID, Value, ArtifactPowerID, Unknown, Rank FROM artifact_power_rank ORDER BY ID DESC"); + + // ArtifactQuestXp.db2 + PrepareStatement(HotfixStatements.SEL_ARTIFACT_QUEST_XP, "SELECT ID, Exp1, Exp2, Exp3, Exp4, Exp5, Exp6, Exp7, Exp8, Exp9, Exp10 FROM artifact_quest_xp ORDER BY ID DESC"); + + // AuctionHouse.db2 + PrepareStatement(HotfixStatements.SEL_AUCTION_HOUSE, "SELECT ID, Name, FactionID, DepositRate, ConsignmentRate FROM auction_house ORDER BY ID DESC"); + PrepareStatement(HotfixStatements.SEL_AUCTION_HOUSE_LOCALE, "SELECT ID, Name_lang FROM auction_house_locale WHERE locale = ?"); + + // BankBagSlotPrices.db2 + PrepareStatement(HotfixStatements.SEL_BANK_BAG_SLOT_PRICES, "SELECT ID, Cost FROM bank_bag_slot_prices ORDER BY ID DESC"); + + // BannedAddons.db2 + PrepareStatement(HotfixStatements.SEL_BANNED_ADDONS, "SELECT ID, Name, Version, Flags FROM banned_addons ORDER BY ID DESC"); + + // BarberShopStyle.db2 + PrepareStatement(HotfixStatements.SEL_BARBER_SHOP_STYLE, "SELECT DisplayName, Description, CostModifier, Type, Race, Sex, Data, ID FROM barber_shop_style" + + " ORDER BY ID DESC"); + PrepareStatement(HotfixStatements.SEL_BARBER_SHOP_STYLE_LOCALE, "SELECT ID, DisplayName_lang, Description_lang FROM barber_shop_style_locale WHERE locale = ?"); + + // BattlePetBreedQuality.db2 + PrepareStatement(HotfixStatements.SEL_BATTLE_PET_BREED_QUALITY, "SELECT ID, Modifier, Quality FROM battle_pet_breed_quality ORDER BY ID DESC"); + + // BattlePetBreedState.db2 + PrepareStatement(HotfixStatements.SEL_BATTLE_PET_BREED_STATE, "SELECT ID, Value, BreedID, State FROM battle_pet_breed_state ORDER BY ID DESC"); + + // BattlePetSpecies.db2 + PrepareStatement(HotfixStatements.SEL_BATTLE_PET_SPECIES, "SELECT CreatureID, IconFileID, SummonSpellID, SourceText, Description, Flags, PetType, Source, " + + "ID, CardModelSceneID, LoadoutModelSceneID FROM battle_pet_species ORDER BY ID DESC"); + PrepareStatement(HotfixStatements.SEL_BATTLE_PET_SPECIES_LOCALE, "SELECT ID, SourceText_lang, Description_lang FROM battle_pet_species_locale WHERE locale = ?"); + + // BattlePetSpeciesState.db2 + PrepareStatement(HotfixStatements.SEL_BATTLE_PET_SPECIES_STATE, "SELECT ID, Value, SpeciesID, State FROM battle_pet_species_state ORDER BY ID DESC"); + + // BattlemasterList.db2 + PrepareStatement(HotfixStatements.SEL_BATTLEMASTER_LIST, "SELECT ID, Name, IconFileDataID, GameType, ShortDescription, LongDescription, MapID1, MapID2, " + + "MapID3, MapID4, MapID5, MapID6, MapID7, MapID8, MapID9, MapID10, MapID11, MapID12, MapID13, MapID14, MapID15, MapID16, HolidayWorldState, " + + "PlayerConditionID, InstanceType, GroupsAllowed, MaxGroupSize, MinLevel, MaxLevel, RatedPlayers, MinPlayers, MaxPlayers, Flags" + + " FROM battlemaster_list ORDER BY ID DESC"); + PrepareStatement(HotfixStatements.SEL_BATTLEMASTER_LIST_LOCALE, "SELECT ID, Name_lang, GameType_lang, ShortDescription_lang, LongDescription_lang" + + " FROM battlemaster_list_locale WHERE locale = ?"); + + // BroadcastText.db2 + PrepareStatement(HotfixStatements.SEL_BROADCAST_TEXT, "SELECT ID, MaleText, FemaleText, EmoteID1, EmoteID2, EmoteID3, EmoteDelay1, EmoteDelay2, " + + "EmoteDelay3, UnkEmoteID, Language, Type, SoundID1, SoundID2, PlayerConditionID FROM broadcast_text ORDER BY ID DESC"); + PrepareStatement(HotfixStatements.SEL_BROADCAST_TEXT_LOCALE, "SELECT ID, MaleText_lang, FemaleText_lang FROM broadcast_text_locale WHERE locale = ?"); + + // CharSections.db2 + PrepareStatement(HotfixStatements.SEL_CHAR_SECTIONS, "SELECT ID, TextureFileDataID1, TextureFileDataID2, TextureFileDataID3, Flags, Race, Gender, GenType, " + + "Type, Color FROM char_sections ORDER BY ID DESC"); + + // CharStartOutfit.db2 + PrepareStatement(HotfixStatements.SEL_CHAR_START_OUTFIT, "SELECT ID, ItemID1, ItemID2, ItemID3, ItemID4, ItemID5, ItemID6, ItemID7, ItemID8, ItemID9, " + + "ItemID10, ItemID11, ItemID12, ItemID13, ItemID14, ItemID15, ItemID16, ItemID17, ItemID18, ItemID19, ItemID20, ItemID21, ItemID22, ItemID23, " + + "ItemID24, PetDisplayID, RaceID, ClassID, GenderID, OutfitID, PetFamilyID FROM char_start_outfit ORDER BY ID DESC"); + + // CharTitles.db2 + PrepareStatement(HotfixStatements.SEL_CHAR_TITLES, "SELECT ID, NameMale, NameFemale, MaskID, Flags FROM char_titles ORDER BY ID DESC"); + PrepareStatement(HotfixStatements.SEL_CHAR_TITLES_LOCALE, "SELECT ID, NameMale_lang, NameFemale_lang FROM char_titles_locale WHERE locale = ?"); + + // ChatChannels.db2 + PrepareStatement(HotfixStatements.SEL_CHAT_CHANNELS, "SELECT ID, Flags, Name, Shortcut, FactionGroup FROM chat_channels ORDER BY ID DESC"); + PrepareStatement(HotfixStatements.SEL_CHAT_CHANNELS_LOCALE, "SELECT ID, Name_lang, Shortcut_lang FROM chat_channels_locale WHERE locale = ?"); + + // ChrClasses.db2 + PrepareStatement(HotfixStatements.SEL_CHR_CLASSES, "SELECT PetNameToken, Name, NameFemale, NameMale, Filename, CreateScreenFileDataID, " + + "SelectScreenFileDataID, IconFileDataID, LowResScreenFileDataID, Flags, CinematicSequenceID, DefaultSpec, PowerType, SpellClassSet, " + + "AttackPowerPerStrength, AttackPowerPerAgility, RangedAttackPowerPerAgility, Unk1, ID FROM chr_classes ORDER BY ID DESC"); + PrepareStatement(HotfixStatements.SEL_CHR_CLASSES_LOCALE, "SELECT ID, Name_lang, NameFemale_lang, NameMale_lang FROM chr_classes_locale WHERE locale = ?"); + + // ChrClassesXPowerTypes.db2 + PrepareStatement(HotfixStatements.SEL_CHR_CLASSES_X_POWER_TYPES, "SELECT ID, ClassID, PowerType FROM chr_classes_x_power_types ORDER BY ID DESC"); + + // ChrRaces.db2 + PrepareStatement(HotfixStatements.SEL_CHR_RACES, "SELECT ID, Flags, ClientPrefix, ClientFileString, Name, NameFemale, NameMale, FacialHairCustomization1, " + + "FacialHairCustomization2, HairCustomization, CreateScreenFileDataID, SelectScreenFileDataID, MaleCustomizeOffset1, MaleCustomizeOffset2, " + + "MaleCustomizeOffset3, FemaleCustomizeOffset1, FemaleCustomizeOffset2, FemaleCustomizeOffset3, LowResScreenFileDataID, FactionID, " + + "MaleDisplayID, FemaleDisplayID, ResSicknessSpellID, SplashSoundID, CinematicSequenceID, BaseLanguage, CreatureType, TeamID, RaceRelated, " + + "UnalteredVisualRaceID, CharComponentTextureLayoutID, DefaultClassID, NeutralRaceID, ItemAppearanceFrameRaceID, " + + "CharComponentTexLayoutHiResID, HighResMaleDisplayID, HighResFemaleDisplayID, Unk1, Unk2, Unk3 FROM chr_races ORDER BY ID DESC"); + PrepareStatement(HotfixStatements.SEL_CHR_RACES_LOCALE, "SELECT ID, Name_lang, NameFemale_lang, NameMale_lang FROM chr_races_locale WHERE locale = ?"); + + // ChrSpecialization.db2 + PrepareStatement(HotfixStatements.SEL_CHR_SPECIALIZATION, "SELECT MasterySpellID1, MasterySpellID2, Name, Name2, Description, ClassID, OrderIndex, " + + "PetTalentType, Role, PrimaryStatOrder, ID, IconFileDataID, Flags, AnimReplacementSetID FROM chr_specialization ORDER BY ID DESC"); + PrepareStatement(HotfixStatements.SEL_CHR_SPECIALIZATION_LOCALE, "SELECT ID, Name_lang, Name2_lang, Description_lang FROM chr_specialization_locale" + + " WHERE locale = ?"); + + // CinematicCamera.db2 + PrepareStatement(HotfixStatements.SEL_CINEMATIC_CAMERA, "SELECT ID, SoundID, OriginX, OriginY, OriginZ, OriginFacing, ModelFileDataID FROM cinematic_camera" + + " ORDER BY ID DESC"); + + // CinematicSequences.db2 + PrepareStatement(HotfixStatements.SEL_CINEMATIC_SEQUENCES, "SELECT ID, SoundID, Camera1, Camera2, Camera3, Camera4, Camera5, Camera6, Camera7, Camera8" + + " FROM cinematic_sequences ORDER BY ID DESC"); + + // ConversationLine.db2 + PrepareStatement(HotfixStatements.SEL_CONVERSATION_LINE, "SELECT ID, BroadcastTextID, SpellVisualKitID, Duration, NextLineID, Unk1, Yell, Unk2, Unk3" + + " FROM conversation_line ORDER BY ID DESC"); + + // CreatureDisplayInfo.db2 + PrepareStatement(HotfixStatements.SEL_CREATURE_DISPLAY_INFO, "SELECT ID, CreatureModelScale, ModelID, NPCSoundID, SizeClass, Flags, Gender, " + + "ExtendedDisplayInfoID, TextureVariation1, TextureVariation2, TextureVariation3, PortraitTextureFileDataID, CreatureModelAlpha, SoundID, " + + "PlayerModelScale, PortraitCreatureDisplayInfoID, BloodID, ParticleColorID, CreatureGeosetData, ObjectEffectPackageID, AnimReplacementSetID, " + + "UnarmedWeaponSubclass, StateSpellVisualKitID, InstanceOtherPlayerPetScale, MountSpellVisualKitID FROM creature_display_info ORDER BY ID DESC"); + + // CreatureDisplayInfoExtra.db2 + PrepareStatement(HotfixStatements.SEL_CREATURE_DISPLAY_INFO_EXTRA, "SELECT ID, FileDataID, HDFileDataID, DisplayRaceID, DisplaySexID, DisplayClassID, " + + "SkinID, FaceID, HairStyleID, HairColorID, FacialHairID, CustomDisplayOption1, CustomDisplayOption2, CustomDisplayOption3, Flags" + + " FROM creature_display_info_extra ORDER BY ID DESC"); + + // CreatureFamily.db2 + PrepareStatement(HotfixStatements.SEL_CREATURE_FAMILY, "SELECT ID, MinScale, MaxScale, Name, IconFileDataID, SkillLine1, SkillLine2, PetFoodMask, " + + "MinScaleLevel, MaxScaleLevel, PetTalentType FROM creature_family ORDER BY ID DESC"); + PrepareStatement(HotfixStatements.SEL_CREATURE_FAMILY_LOCALE, "SELECT ID, Name_lang FROM creature_family_locale WHERE locale = ?"); + + // CreatureModelData.db2 + PrepareStatement(HotfixStatements.SEL_CREATURE_MODEL_DATA, "SELECT ID, ModelScale, FootprintTextureLength, FootprintTextureWidth, FootprintParticleScale, " + + "CollisionWidth, CollisionHeight, MountHeight, GeoBoxMin1, GeoBoxMin2, GeoBoxMin3, GeoBoxMax1, GeoBoxMax2, GeoBoxMax3, WorldEffectScale, " + + "AttachedEffectScale, MissileCollisionRadius, MissileCollisionPush, MissileCollisionRaise, OverrideLootEffectScale, OverrideNameScale, " + + "OverrideSelectionRadius, TamedPetBaseScale, HoverHeight, Flags, FileDataID, SizeClass, BloodID, FootprintTextureID, FoleyMaterialID, " + + "FootstepEffectID, DeathThudEffectID, SoundID, CreatureGeosetDataID FROM creature_model_data ORDER BY ID DESC"); + + // CreatureType.db2 + PrepareStatement(HotfixStatements.SEL_CREATURE_TYPE, "SELECT ID, Name, Flags FROM creature_type ORDER BY ID DESC"); + PrepareStatement(HotfixStatements.SEL_CREATURE_TYPE_LOCALE, "SELECT ID, Name_lang FROM creature_type_locale WHERE locale = ?"); + + // Criteria.db2 + PrepareStatement(HotfixStatements.SEL_CRITERIA, "SELECT ID, Asset, StartAsset, FailAsset, StartTimer, ModifierTreeId, EligibilityWorldStateID, Type, StartEvent, " + + "FailEvent, Flags, EligibilityWorldStateValue FROM criteria ORDER BY ID DESC"); + + // CriteriaTree.db2 + PrepareStatement(HotfixStatements.SEL_CRITERIA_TREE, "SELECT ID, Amount, Description, Parent, Flags, Operator, CriteriaID, OrderIndex FROM criteria_tree" + + " ORDER BY ID DESC"); + PrepareStatement(HotfixStatements.SEL_CRITERIA_TREE_LOCALE, "SELECT ID, Description_lang FROM criteria_tree_locale WHERE locale = ?"); + + // CurrencyTypes.db2 + PrepareStatement(HotfixStatements.SEL_CURRENCY_TYPES, "SELECT ID, Name, MaxQty, MaxEarnablePerWeek, Flags, Description, CategoryID, SpellCategory, Quality, " + + "InventoryIconFileDataID, SpellWeight FROM currency_types ORDER BY ID DESC"); + PrepareStatement(HotfixStatements.SEL_CURRENCY_TYPES_LOCALE, "SELECT ID, Name_lang, Description_lang FROM currency_types_locale WHERE locale = ?"); + + // Curve.db2 + PrepareStatement(HotfixStatements.SEL_CURVE, "SELECT ID, Type, Unused FROM curve ORDER BY ID DESC"); + + // CurvePoint.db2 + PrepareStatement(HotfixStatements.SEL_CURVE_POINT, "SELECT ID, X, Y, CurveID, `Index` FROM curve_point ORDER BY ID DESC"); + + // DestructibleModelData.db2 + PrepareStatement(HotfixStatements.SEL_DESTRUCTIBLE_MODEL_DATA, "SELECT ID, StateDamagedDisplayID, StateDestroyedDisplayID, StateRebuildingDisplayID, " + + "StateSmokeDisplayID, HealEffectSpeed, StateDamagedImpactEffectDoodadSet, StateDamagedAmbientDoodadSet, StateDamagedNameSet, " + + "StateDestroyedDestructionDoodadSet, StateDestroyedImpactEffectDoodadSet, StateDestroyedAmbientDoodadSet, StateDestroyedNameSet, " + + "StateRebuildingDestructionDoodadSet, StateRebuildingImpactEffectDoodadSet, StateRebuildingAmbientDoodadSet, StateRebuildingNameSet, " + + "StateSmokeInitDoodadSet, StateSmokeAmbientDoodadSet, StateSmokeNameSet, EjectDirection, DoNotHighlight, HealEffect" + + " FROM destructible_model_data ORDER BY ID DESC"); + + // Difficulty.db2 + PrepareStatement(HotfixStatements.SEL_DIFFICULTY, "SELECT ID, Name, GroupSizeHealthCurveID, GroupSizeDmgCurveID, GroupSizeSpellPointsCurveID, " + + "FallbackDifficultyID, InstanceType, MinPlayers, MaxPlayers, OldEnumValue, Flags, ToggleDifficultyID, ItemBonusTreeModID, OrderIndex " + + "FROM difficulty ORDER BY ID DESC"); + PrepareStatement(HotfixStatements.SEL_DIFFICULTY_LOCALE, "SELECT ID, Name_lang FROM difficulty_locale WHERE locale = ?"); + + // DungeonEncounter.db2 + PrepareStatement(HotfixStatements.SEL_DUNGEON_ENCOUNTER, "SELECT Name, CreatureDisplayID, MapID, DifficultyID, Bit, Flags, ID, OrderIndex, " + + "TextureFileDataID FROM dungeon_encounter ORDER BY ID DESC"); + PrepareStatement(HotfixStatements.SEL_DUNGEON_ENCOUNTER_LOCALE, "SELECT ID, Name_lang FROM dungeon_encounter_locale WHERE locale = ?"); + + // DurabilityCosts.db2 + PrepareStatement(HotfixStatements.SEL_DURABILITY_COSTS, "SELECT ID, WeaponSubClassCost1, WeaponSubClassCost2, WeaponSubClassCost3, WeaponSubClassCost4, " + + "WeaponSubClassCost5, WeaponSubClassCost6, WeaponSubClassCost7, WeaponSubClassCost8, WeaponSubClassCost9, WeaponSubClassCost10, " + + "WeaponSubClassCost11, WeaponSubClassCost12, WeaponSubClassCost13, WeaponSubClassCost14, WeaponSubClassCost15, WeaponSubClassCost16, " + + "WeaponSubClassCost17, WeaponSubClassCost18, WeaponSubClassCost19, WeaponSubClassCost20, WeaponSubClassCost21, ArmorSubClassCost1, " + + "ArmorSubClassCost2, ArmorSubClassCost3, ArmorSubClassCost4, ArmorSubClassCost5, ArmorSubClassCost6, ArmorSubClassCost7, ArmorSubClassCost8" + + " FROM durability_costs ORDER BY ID DESC"); + + // DurabilityQuality.db2 + PrepareStatement(HotfixStatements.SEL_DURABILITY_QUALITY, "SELECT ID, QualityMod FROM durability_quality ORDER BY ID DESC"); + + // Emotes.db2 + PrepareStatement(HotfixStatements.SEL_EMOTES, "SELECT ID, EmoteSlashCommand, SpellVisualKitID, EmoteFlags, RaceMask, AnimID, EmoteSpecProc, " + + "EmoteSpecProcParam, EmoteSoundID, ClassMask FROM emotes ORDER BY ID DESC"); + + // EmotesText.db2 + PrepareStatement(HotfixStatements.SEL_EMOTES_TEXT, "SELECT ID, Name, EmoteID FROM emotes_text ORDER BY ID DESC"); + PrepareStatement(HotfixStatements.SEL_EMOTES_TEXT_LOCALE, "SELECT ID, Name_lang FROM emotes_text_locale WHERE locale = ?"); + + // EmotesTextSound.db2 + PrepareStatement(HotfixStatements.SEL_EMOTES_TEXT_SOUND, "SELECT ID, EmotesTextId, RaceId, SexId, ClassId, SoundId FROM emotes_text_sound ORDER BY ID DESC"); + + // Faction.db2 + PrepareStatement(HotfixStatements.SEL_FACTION, "SELECT ID, ReputationRaceMask1, ReputationRaceMask2, ReputationRaceMask3, ReputationRaceMask4, " + + "ReputationBase1, ReputationBase2, ReputationBase3, ReputationBase4, ParentFactionModIn, ParentFactionModOut, Name, Description, " + + "ReputationMax1, ReputationMax2, ReputationMax3, ReputationMax4, ReputationIndex, ReputationClassMask1, ReputationClassMask2, " + + "ReputationClassMask3, ReputationClassMask4, ReputationFlags1, ReputationFlags2, ReputationFlags3, ReputationFlags4, ParentFactionID, " + + "ParagonFactionID, ParentFactionCapIn, ParentFactionCapOut, Expansion, Flags, FriendshipRepID FROM faction ORDER BY ID DESC"); + PrepareStatement(HotfixStatements.SEL_FACTION_LOCALE, "SELECT ID, Name_lang, Description_lang FROM faction_locale WHERE locale = ?"); + + // FactionTemplate.db2 + PrepareStatement(HotfixStatements.SEL_FACTION_TEMPLATE, "SELECT ID, Faction, Flags, Enemies1, Enemies2, Enemies3, Enemies4, Friends1, Friends2, Friends3, " + + "Friends4, Mask, FriendMask, EnemyMask FROM faction_template ORDER BY ID DESC"); + + // Gameobjects.db2 + PrepareStatement(HotfixStatements.SEL_GAMEOBJECTS, "SELECT PositionX, PositionY, PositionZ, RotationX, RotationY, RotationZ, RotationW, Size, Data1, Data2, " + + "Data3, Data4, Data5, Data6, Data7, Data8, Name, MapID, DisplayID, PhaseID, PhaseGroupID, PhaseUseFlags, Type, ID FROM gameobjects" + + " ORDER BY ID DESC"); + PrepareStatement(HotfixStatements.SEL_GAMEOBJECTS_LOCALE, "SELECT ID, Name_lang FROM gameobjects_locale WHERE locale = ?"); + + // GameobjectDisplayInfo.db2 + PrepareStatement(HotfixStatements.SEL_GAMEOBJECT_DISPLAY_INFO, "SELECT ID, FileDataID, GeoBoxMinX, GeoBoxMinY, GeoBoxMinZ, GeoBoxMaxX, GeoBoxMaxY, " + + "GeoBoxMaxZ, OverrideLootEffectScale, OverrideNameScale, ObjectEffectPackageID FROM gameobject_display_info ORDER BY ID DESC"); + + // GarrAbility.db2 + PrepareStatement(HotfixStatements.SEL_GARR_ABILITY, "SELECT Name, Description, IconFileDataID, Flags, OtherFactionGarrAbilityID, GarrAbilityCategoryID, " + + "FollowerTypeID, ID FROM garr_ability ORDER BY ID DESC"); + PrepareStatement(HotfixStatements.SEL_GARR_ABILITY_LOCALE, "SELECT ID, Name_lang, Description_lang FROM garr_ability_locale WHERE locale = ?"); + + // GarrBuilding.db2 + PrepareStatement(HotfixStatements.SEL_GARR_BUILDING, "SELECT ID, HordeGameObjectID, AllianceGameObjectID, NameAlliance, NameHorde, Description, Tooltip, " + + "IconFileDataID, CostCurrencyID, HordeTexPrefixKitID, AllianceTexPrefixKitID, AllianceActivationScenePackageID, " + + "HordeActivationScenePackageID, FollowerRequiredGarrAbilityID, FollowerGarrAbilityEffectID, CostMoney, Unknown, Type, Level, Flags, " + + "MaxShipments, GarrTypeID, BuildDuration, CostCurrencyAmount, BonusAmount FROM garr_building ORDER BY ID DESC"); + PrepareStatement(HotfixStatements.SEL_GARR_BUILDING_LOCALE, "SELECT ID, NameAlliance_lang, NameHorde_lang, Description_lang, Tooltip_lang" + + " FROM garr_building_locale WHERE locale = ?"); + + // GarrBuildingPlotInst.db2 + PrepareStatement(HotfixStatements.SEL_GARR_BUILDING_PLOT_INST, "SELECT LandmarkOffsetX, LandmarkOffsetY, UiTextureAtlasMemberID, GarrSiteLevelPlotInstID, " + + "GarrBuildingID, ID FROM garr_building_plot_inst ORDER BY ID DESC"); + + // GarrClassSpec.db2 + PrepareStatement(HotfixStatements.SEL_GARR_CLASS_SPEC, "SELECT NameMale, NameFemale, NameGenderless, ClassAtlasID, GarrFollItemSetID, `Limit`, Flags, ID" + + " FROM garr_class_spec ORDER BY ID DESC"); + PrepareStatement(HotfixStatements.SEL_GARR_CLASS_SPEC_LOCALE, "SELECT ID, NameMale_lang, NameFemale_lang, NameGenderless_lang FROM garr_class_spec_locale" + + " WHERE locale = ?"); + + // GarrFollower.db2 + PrepareStatement(HotfixStatements.SEL_GARR_FOLLOWER, "SELECT HordeCreatureID, AllianceCreatureID, HordeSourceText, AllianceSourceText, HordePortraitIconID, " + + "AlliancePortraitIconID, HordeAddedBroadcastTextID, AllianceAddedBroadcastTextID, Name, HordeGarrFollItemSetID, AllianceGarrFollItemSetID, " + + "ItemLevelWeapon, ItemLevelArmor, HordeListPortraitTextureKitID, AllianceListPortraitTextureKitID, FollowerTypeID, HordeUiAnimRaceInfoID, " + + "AllianceUiAnimRaceInfoID, Quality, HordeGarrClassSpecID, AllianceGarrClassSpecID, Level, Unknown1, Flags, Unknown2, Unknown3, GarrTypeID, " + + "MaxDurability, Class, HordeFlavorTextGarrStringID, AllianceFlavorTextGarrStringID, ID FROM garr_follower ORDER BY ID DESC"); + PrepareStatement(HotfixStatements.SEL_GARR_FOLLOWER_LOCALE, "SELECT ID, HordeSourceText_lang, AllianceSourceText_lang, Name_lang FROM garr_follower_locale" + + " WHERE locale = ?"); + + // GarrFollowerXAbility.db2 + PrepareStatement(HotfixStatements.SEL_GARR_FOLLOWER_X_ABILITY, "SELECT ID, GarrFollowerID, GarrAbilityID, FactionIndex FROM garr_follower_x_ability" + + " ORDER BY ID DESC"); + + // GarrPlot.db2 + PrepareStatement(HotfixStatements.SEL_GARR_PLOT, "SELECT ID, Name, AllianceConstructionGameObjectID, HordeConstructionGameObjectID, GarrPlotUICategoryID, " + + "PlotType, Flags, MinCount, MaxCount FROM garr_plot ORDER BY ID DESC"); + PrepareStatement(HotfixStatements.SEL_GARR_PLOT_LOCALE, "SELECT ID, Name_lang FROM garr_plot_locale WHERE locale = ?"); + + // GarrPlotBuilding.db2 + PrepareStatement(HotfixStatements.SEL_GARR_PLOT_BUILDING, "SELECT ID, GarrPlotID, GarrBuildingID FROM garr_plot_building ORDER BY ID DESC"); + + // GarrPlotInstance.db2 + PrepareStatement(HotfixStatements.SEL_GARR_PLOT_INSTANCE, "SELECT ID, Name, GarrPlotID FROM garr_plot_instance ORDER BY ID DESC"); + PrepareStatement(HotfixStatements.SEL_GARR_PLOT_INSTANCE_LOCALE, "SELECT ID, Name_lang FROM garr_plot_instance_locale WHERE locale = ?"); + + // GarrSiteLevel.db2 + PrepareStatement(HotfixStatements.SEL_GARR_SITE_LEVEL, "SELECT ID, TownHallX, TownHallY, MapID, SiteID, MovieID, UpgradeResourceCost, UpgradeMoneyCost, " + + "Level, UITextureKitID, Level2 FROM garr_site_level ORDER BY ID DESC"); + + // GarrSiteLevelPlotInst.db2 + PrepareStatement(HotfixStatements.SEL_GARR_SITE_LEVEL_PLOT_INST, "SELECT ID, LandmarkX, LandmarkY, GarrSiteLevelID, GarrPlotInstanceID, Unknown" + + " FROM garr_site_level_plot_inst ORDER BY ID DESC"); + + // GemProperties.db2 + PrepareStatement(HotfixStatements.SEL_GEM_PROPERTIES, "SELECT ID, Type, EnchantID, MinItemLevel FROM gem_properties ORDER BY ID DESC"); + + // GlyphBindableSpell.db2 + PrepareStatement(HotfixStatements.SEL_GLYPH_BINDABLE_SPELL, "SELECT ID, SpellID, GlyphPropertiesID FROM glyph_bindable_spell ORDER BY ID DESC"); + + // GlyphProperties.db2 + PrepareStatement(HotfixStatements.SEL_GLYPH_PROPERTIES, "SELECT ID, SpellID, SpellIconID, Type, GlyphExclusiveCategoryID FROM glyph_properties ORDER BY ID DESC"); + + // GlyphRequiredSpec.db2 + PrepareStatement(HotfixStatements.SEL_GLYPH_REQUIRED_SPEC, "SELECT ID, GlyphPropertiesID, ChrSpecializationID FROM glyph_required_spec ORDER BY ID DESC"); + + // GuildColorBackground.db2 + PrepareStatement(HotfixStatements.SEL_GUILD_COLOR_BACKGROUND, "SELECT ID, Red, Green, Blue FROM guild_color_background ORDER BY ID DESC"); + + // GuildColorBorder.db2 + PrepareStatement(HotfixStatements.SEL_GUILD_COLOR_BORDER, "SELECT ID, Red, Green, Blue FROM guild_color_border ORDER BY ID DESC"); + + // GuildColorEmblem.db2 + PrepareStatement(HotfixStatements.SEL_GUILD_COLOR_EMBLEM, "SELECT ID, Red, Green, Blue FROM guild_color_emblem ORDER BY ID DESC"); + + // GuildPerkSpells.db2 + PrepareStatement(HotfixStatements.SEL_GUILD_PERK_SPELLS, "SELECT ID, SpellID FROM guild_perk_spells ORDER BY ID DESC"); + + // Heirloom.db2 + PrepareStatement(HotfixStatements.SEL_HEIRLOOM, "SELECT ItemID, SourceText, OldItem1, OldItem2, NextDifficultyItemID, UpgradeItemID1, UpgradeItemID2, " + + "UpgradeItemID3, ItemBonusListID1, ItemBonusListID2, ItemBonusListID3, Flags, Source, ID FROM heirloom ORDER BY ID DESC"); + PrepareStatement(HotfixStatements.SEL_HEIRLOOM_LOCALE, "SELECT ID, SourceText_lang FROM heirloom_locale WHERE locale = ?"); + + // Holidays.db2 + PrepareStatement(HotfixStatements.SEL_HOLIDAYS, "SELECT ID, Date1, Date2, Date3, Date4, Date5, Date6, Date7, Date8, Date9, Date10, Date11, Date12, Date13, " + + "Date14, Date15, Date16, Duration1, Duration2, Duration3, Duration4, Duration5, Duration6, Duration7, Duration8, Duration9, Duration10, " + + "Region, Looping, CalendarFlags1, CalendarFlags2, CalendarFlags3, CalendarFlags4, CalendarFlags5, CalendarFlags6, CalendarFlags7, " + + "CalendarFlags8, CalendarFlags9, CalendarFlags10, Priority, CalendarFilterType, Flags, HolidayNameID, HolidayDescriptionID, " + + "TextureFileDataID1, TextureFileDataID2, TextureFileDataID3 FROM holidays ORDER BY ID DESC"); + + // ImportPriceArmor.db2 + PrepareStatement(HotfixStatements.SEL_IMPORT_PRICE_ARMOR, "SELECT ID, ClothFactor, LeatherFactor, MailFactor, PlateFactor FROM import_price_armor" + + " ORDER BY ID DESC"); + + // ImportPriceQuality.db2 + PrepareStatement(HotfixStatements.SEL_IMPORT_PRICE_QUALITY, "SELECT ID, Factor FROM import_price_quality ORDER BY ID DESC"); + + // ImportPriceShield.db2 + PrepareStatement(HotfixStatements.SEL_IMPORT_PRICE_SHIELD, "SELECT ID, Factor FROM import_price_shield ORDER BY ID DESC"); + + // ImportPriceWeapon.db2 + PrepareStatement(HotfixStatements.SEL_IMPORT_PRICE_WEAPON, "SELECT ID, Factor FROM import_price_weapon ORDER BY ID DESC"); + + // Item.db2 + PrepareStatement(HotfixStatements.SEL_ITEM, "SELECT ID, FileDataID, Class, SubClass, SoundOverrideSubclass, Material, InventoryType, Sheath, GroupSoundsID" + + " FROM item ORDER BY ID DESC"); + + // ItemAppearance.db2 + PrepareStatement(HotfixStatements.SEL_ITEM_APPEARANCE, "SELECT ID, DisplayID, IconFileDataID, UIOrder, ObjectComponentSlot FROM item_appearance" + + " ORDER BY ID DESC"); + + // ItemArmorQuality.db2 + PrepareStatement(HotfixStatements.SEL_ITEM_ARMOR_QUALITY, "SELECT ID, QualityMod1, QualityMod2, QualityMod3, QualityMod4, QualityMod5, QualityMod6, " + + "QualityMod7, ItemLevel FROM item_armor_quality ORDER BY ID DESC"); + + // ItemArmorShield.db2 + PrepareStatement(HotfixStatements.SEL_ITEM_ARMOR_SHIELD, "SELECT ID, Quality1, Quality2, Quality3, Quality4, Quality5, Quality6, Quality7, ItemLevel" + + " FROM item_armor_shield ORDER BY ID DESC"); + + // ItemArmorTotal.db2 + PrepareStatement(HotfixStatements.SEL_ITEM_ARMOR_TOTAL, "SELECT ID, Value1, Value2, Value3, Value4, ItemLevel FROM item_armor_total ORDER BY ID DESC"); + + // ItemBagFamily.db2 + PrepareStatement(HotfixStatements.SEL_ITEM_BAG_FAMILY, "SELECT ID, Name FROM item_bag_family ORDER BY ID DESC"); + PrepareStatement(HotfixStatements.SEL_ITEM_BAG_FAMILY_LOCALE, "SELECT ID, Name_lang FROM item_bag_family_locale WHERE locale = ?"); + + // ItemBonus.db2 + PrepareStatement(HotfixStatements.SEL_ITEM_BONUS, "SELECT ID, Value1, Value2, BonusListID, Type, `Index` FROM item_bonus ORDER BY ID DESC"); + + // ItemBonusListLevelDelta.db2 + PrepareStatement(HotfixStatements.SEL_ITEM_BONUS_LIST_LEVEL_DELTA, "SELECT Delta, ID FROM item_bonus_list_level_delta ORDER BY ID DESC"); + + // ItemBonusTreeNode.db2 + PrepareStatement(HotfixStatements.SEL_ITEM_BONUS_TREE_NODE, "SELECT ID, BonusTreeID, SubTreeID, BonusListID, ItemLevelSelectorID, BonusTreeModID" + + " FROM item_bonus_tree_node ORDER BY ID DESC"); + + // ItemChildEquipment.db2 + PrepareStatement(HotfixStatements.SEL_ITEM_CHILD_EQUIPMENT, "SELECT ID, ItemID, AltItemID, AltEquipmentSlot FROM item_child_equipment ORDER BY ID DESC"); + + // ItemClass.db2 + PrepareStatement(HotfixStatements.SEL_ITEM_CLASS, "SELECT ID, PriceMod, Name, OldEnumValue, Flags FROM item_class ORDER BY ID DESC"); + PrepareStatement(HotfixStatements.SEL_ITEM_CLASS_LOCALE, "SELECT ID, Name_lang FROM item_class_locale WHERE locale = ?"); + + // ItemCurrencyCost.db2 + PrepareStatement(HotfixStatements.SEL_ITEM_CURRENCY_COST, "SELECT ID, ItemId FROM item_currency_cost ORDER BY ID DESC"); + + // ItemDamageAmmo.db2 + PrepareStatement(HotfixStatements.SEL_ITEM_DAMAGE_AMMO, "SELECT ID, DPS1, DPS2, DPS3, DPS4, DPS5, DPS6, DPS7, ItemLevel FROM item_damage_ammo" + + " ORDER BY ID DESC"); + + // ItemDamageOneHand.db2 + PrepareStatement(HotfixStatements.SEL_ITEM_DAMAGE_ONE_HAND, "SELECT ID, DPS1, DPS2, DPS3, DPS4, DPS5, DPS6, DPS7, ItemLevel FROM item_damage_one_hand" + + " ORDER BY ID DESC"); + + // ItemDamageOneHandCaster.db2 + PrepareStatement(HotfixStatements.SEL_ITEM_DAMAGE_ONE_HAND_CASTER, "SELECT ID, DPS1, DPS2, DPS3, DPS4, DPS5, DPS6, DPS7, ItemLevel" + + " FROM item_damage_one_hand_caster ORDER BY ID DESC"); + + // ItemDamageTwoHand.db2 + PrepareStatement(HotfixStatements.SEL_ITEM_DAMAGE_TWO_HAND, "SELECT ID, DPS1, DPS2, DPS3, DPS4, DPS5, DPS6, DPS7, ItemLevel FROM item_damage_two_hand" + + " ORDER BY ID DESC"); + + // ItemDamageTwoHandCaster.db2 + PrepareStatement(HotfixStatements.SEL_ITEM_DAMAGE_TWO_HAND_CASTER, "SELECT ID, DPS1, DPS2, DPS3, DPS4, DPS5, DPS6, DPS7, ItemLevel" + + " FROM item_damage_two_hand_caster ORDER BY ID DESC"); + + // ItemDisenchantLoot.db2 + PrepareStatement(HotfixStatements.SEL_ITEM_DISENCHANT_LOOT, "SELECT ID, MinItemLevel, MaxItemLevel, RequiredDisenchantSkill, ItemClass, ItemSubClass, " + + "ItemQuality FROM item_disenchant_loot ORDER BY ID DESC"); + + // ItemEffect.db2 + PrepareStatement(HotfixStatements.SEL_ITEM_EFFECT, "SELECT ID, ItemID, SpellID, Cooldown, CategoryCooldown, Charges, Category, ChrSpecializationID, " + + "OrderIndex, `Trigger` FROM item_effect ORDER BY ID DESC"); + + // ItemExtendedCost.db2 + PrepareStatement(HotfixStatements.SEL_ITEM_EXTENDED_COST, "SELECT ID, RequiredItem1, RequiredItem2, RequiredItem3, RequiredItem4, RequiredItem5, " + + "RequiredCurrencyCount1, RequiredCurrencyCount2, RequiredCurrencyCount3, RequiredCurrencyCount4, RequiredCurrencyCount5, RequiredItemCount1, " + + "RequiredItemCount2, RequiredItemCount3, RequiredItemCount4, RequiredItemCount5, RequiredPersonalArenaRating, RequiredCurrency1, " + + "RequiredCurrency2, RequiredCurrency3, RequiredCurrency4, RequiredCurrency5, RequiredArenaSlot, RequiredFactionId, RequiredFactionStanding, " + + "RequirementFlags, RequiredAchievement FROM item_extended_cost ORDER BY ID DESC"); + + // ItemLimitCategory.db2 + PrepareStatement(HotfixStatements.SEL_ITEM_LIMIT_CATEGORY, "SELECT ID, Name, Quantity, Flags FROM item_limit_category ORDER BY ID DESC"); + PrepareStatement(HotfixStatements.SEL_ITEM_LIMIT_CATEGORY_LOCALE, "SELECT ID, Name_lang FROM item_limit_category_locale WHERE locale = ?"); + + // ItemModifiedAppearance.db2 + PrepareStatement(HotfixStatements.SEL_ITEM_MODIFIED_APPEARANCE, "SELECT ItemID, AppearanceID, AppearanceModID, `Index`, SourceType, ID" + + " FROM item_modified_appearance ORDER BY ID DESC"); + + // ItemPriceBase.db2 + PrepareStatement(HotfixStatements.SEL_ITEM_PRICE_BASE, "SELECT ID, ArmorFactor, WeaponFactor, ItemLevel FROM item_price_base ORDER BY ID DESC"); + + // ItemRandomProperties.db2 + PrepareStatement(HotfixStatements.SEL_ITEM_RANDOM_PROPERTIES, "SELECT ID, Name, Enchantment1, Enchantment2, Enchantment3, Enchantment4, Enchantment5" + + " FROM item_random_properties ORDER BY ID DESC"); + PrepareStatement(HotfixStatements.SEL_ITEM_RANDOM_PROPERTIES_LOCALE, "SELECT ID, Name_lang FROM item_random_properties_locale WHERE locale = ?"); + + // ItemRandomSuffix.db2 + PrepareStatement(HotfixStatements.SEL_ITEM_RANDOM_SUFFIX, "SELECT ID, Name, Enchantment1, Enchantment2, Enchantment3, Enchantment4, Enchantment5, " + + "AllocationPct1, AllocationPct2, AllocationPct3, AllocationPct4, AllocationPct5 FROM item_random_suffix ORDER BY ID DESC"); + PrepareStatement(HotfixStatements.SEL_ITEM_RANDOM_SUFFIX_LOCALE, "SELECT ID, Name_lang FROM item_random_suffix_locale WHERE locale = ?"); + + // ItemSearchName.db2 + PrepareStatement(HotfixStatements.SEL_ITEM_SEARCH_NAME, "SELECT ID, Name, Flags1, Flags2, Flags3, AllowableRace, RequiredSpell, RequiredReputationFaction, " + + "RequiredSkill, RequiredSkillRank, ItemLevel, Quality, RequiredExpansion, RequiredReputationRank, RequiredLevel, AllowableClass" + + " FROM item_search_name ORDER BY ID DESC"); + PrepareStatement(HotfixStatements.SEL_ITEM_SEARCH_NAME_LOCALE, "SELECT ID, Name_lang FROM item_search_name_locale WHERE locale = ?"); + + // ItemSet.db2 + PrepareStatement(HotfixStatements.SEL_ITEM_SET, "SELECT ID, Name, ItemID1, ItemID2, ItemID3, ItemID4, ItemID5, ItemID6, ItemID7, ItemID8, ItemID9, " + + "ItemID10, ItemID11, ItemID12, ItemID13, ItemID14, ItemID15, ItemID16, ItemID17, RequiredSkillRank, RequiredSkill, Flags FROM item_set" + + " ORDER BY ID DESC"); + PrepareStatement(HotfixStatements.SEL_ITEM_SET_LOCALE, "SELECT ID, Name_lang FROM item_set_locale WHERE locale = ?"); + + // ItemSetSpell.db2 + PrepareStatement(HotfixStatements.SEL_ITEM_SET_SPELL, "SELECT ID, SpellID, ItemSetID, ChrSpecID, Threshold FROM item_set_spell ORDER BY ID DESC"); + + // ItemSparse.db2 + PrepareStatement(HotfixStatements.SEL_ITEM_SPARSE, "SELECT ID, Flags1, Flags2, Flags3, Unk1, Unk2, BuyCount, BuyPrice, SellPrice, AllowableRace, " + + "RequiredSpell, MaxCount, Stackable, ItemStatAllocation1, ItemStatAllocation2, ItemStatAllocation3, ItemStatAllocation4, ItemStatAllocation5, " + + "ItemStatAllocation6, ItemStatAllocation7, ItemStatAllocation8, ItemStatAllocation9, ItemStatAllocation10, ItemStatSocketCostMultiplier1, " + + "ItemStatSocketCostMultiplier2, ItemStatSocketCostMultiplier3, ItemStatSocketCostMultiplier4, ItemStatSocketCostMultiplier5, " + + "ItemStatSocketCostMultiplier6, ItemStatSocketCostMultiplier7, ItemStatSocketCostMultiplier8, ItemStatSocketCostMultiplier9, " + + "ItemStatSocketCostMultiplier10, RangedModRange, Name, Name2, Name3, Name4, Description, BagFamily, ArmorDamageModifier, Duration, " + + "StatScalingFactor, AllowableClass, ItemLevel, RequiredSkill, RequiredSkillRank, RequiredReputationFaction, ItemStatValue1, ItemStatValue2, " + + "ItemStatValue3, ItemStatValue4, ItemStatValue5, ItemStatValue6, ItemStatValue7, ItemStatValue8, ItemStatValue9, ItemStatValue10, " + + "ScalingStatDistribution, Delay, PageText, StartQuest, LockID, RandomProperty, RandomSuffix, ItemSet, Area, Map, TotemCategory, SocketBonus, " + + "GemProperties, ItemLimitCategory, HolidayID, RequiredTransmogHolidayID, ItemNameDescriptionID, Quality, InventoryType, RequiredLevel, " + + "RequiredHonorRank, RequiredCityRank, RequiredReputationRank, ContainerSlots, ItemStatType1, ItemStatType2, ItemStatType3, ItemStatType4, " + + "ItemStatType5, ItemStatType6, ItemStatType7, ItemStatType8, ItemStatType9, ItemStatType10, DamageType, Bonding, LanguageID, PageMaterial, " + + "Material, Sheath, SocketColor1, SocketColor2, SocketColor3, CurrencySubstitutionID, CurrencySubstitutionCount, ArtifactID, " + + "RequiredExpansion FROM item_sparse ORDER BY ID DESC"); + PrepareStatement(HotfixStatements.SEL_ITEM_SPARSE_LOCALE, "SELECT ID, Name_lang, Name2_lang, Name3_lang, Name4_lang, Description_lang FROM item_sparse_locale" + + " WHERE locale = ?"); + + // ItemSpec.db2 + PrepareStatement(HotfixStatements.SEL_ITEM_SPEC, "SELECT ID, SpecID, MinLevel, MaxLevel, ItemType, PrimaryStat, SecondaryStat FROM item_spec" + + " ORDER BY ID DESC"); + + // ItemSpecOverride.db2 + PrepareStatement(HotfixStatements.SEL_ITEM_SPEC_OVERRIDE, "SELECT ID, ItemID, SpecID FROM item_spec_override ORDER BY ID DESC"); + + // ItemUpgrade.db2 + PrepareStatement(HotfixStatements.SEL_ITEM_UPGRADE, "SELECT ID, CurrencyCost, PrevItemUpgradeID, CurrencyID, ItemUpgradePathID, ItemLevelBonus" + + " FROM item_upgrade ORDER BY ID DESC"); + + // ItemXBonusTree.db2 + PrepareStatement(HotfixStatements.SEL_ITEM_X_BONUS_TREE, "SELECT ID, ItemID, BonusTreeID FROM item_x_bonus_tree ORDER BY ID DESC"); + + // KeyChain.db2 + PrepareStatement(HotfixStatements.SEL_KEY_CHAIN, "SELECT ID, Key1, Key2, Key3, Key4, Key5, Key6, Key7, Key8, Key9, Key10, Key11, Key12, Key13, Key14, " + + "Key15, Key16, Key17, Key18, Key19, Key20, Key21, Key22, Key23, Key24, Key25, Key26, Key27, Key28, Key29, Key30, Key31, Key32 FROM key_chain" + + " ORDER BY ID DESC"); + + // LfgDungeons.db2 + PrepareStatement(HotfixStatements.SEL_LFG_DUNGEONS, "SELECT ID, Name, Flags, Description, MinItemLevel, MaxLevel, TargetLevelMax, MapID, RandomID, " + + "ScenarioID, LastBossJournalEncounterID, BonusReputationAmount, MentorItemLevel, PlayerConditionID, MinLevel, TargetLevel, TargetLevelMin, " + + "DifficultyID, Type, Faction, Expansion, OrderIndex, GroupID, CountTank, CountHealer, CountDamage, MinCountTank, MinCountHealer, " + + "MinCountDamage, SubType, MentorCharLevel, TextureFileDataID, RewardIconFileDataID, ProposalTextureFileDataID FROM lfg_dungeons" + + " ORDER BY ID DESC"); + PrepareStatement(HotfixStatements.SEL_LFG_DUNGEONS_LOCALE, "SELECT ID, Name_lang, Description_lang FROM lfg_dungeons_locale WHERE locale = ?"); + + // Light.db2 + PrepareStatement(HotfixStatements.SEL_LIGHT, "SELECT ID, PosX, PosY, PosZ, FalloffStart, FalloffEnd, MapID, LightParamsID1, LightParamsID2, LightParamsID3, " + + "LightParamsID4, LightParamsID5, LightParamsID6, LightParamsID7, LightParamsID8 FROM light ORDER BY ID DESC"); + + // LiquidType.db2 + PrepareStatement(HotfixStatements.SEL_LIQUID_TYPE, "SELECT ID, Name, SpellID, MaxDarkenDepth, FogDarkenIntensity, AmbDarkenIntensity, DirDarkenIntensity, " + + "ParticleScale, Texture1, Texture2, Texture3, Texture4, Texture5, Texture6, Color1, Color2, Float1, Float2, Float3, `Float4`, Float5, Float6, " + + "Float7, `Float8`, Float9, Float10, Float11, Float12, Float13, Float14, Float15, Float16, Float17, Float18, `Int1`, `Int2`, `Int3`, `Int4`, " + + "Flags, LightID, Type, ParticleMovement, ParticleTexSlots, MaterialID, DepthTexCount1, DepthTexCount2, DepthTexCount3, DepthTexCount4, " + + "DepthTexCount5, DepthTexCount6, SoundID FROM liquid_type ORDER BY ID DESC"); + PrepareStatement(HotfixStatements.SEL_LIQUID_TYPE_LOCALE, "SELECT ID, Name_lang FROM liquid_type_locale WHERE locale = ?"); + + // Lock.db2 + PrepareStatement(HotfixStatements.SEL_LOCK, "SELECT ID, Index1, Index2, Index3, Index4, Index5, Index6, Index7, Index8, Skill1, Skill2, Skill3, Skill4, " + + "Skill5, Skill6, Skill7, Skill8, Type1, Type2, Type3, Type4, Type5, Type6, Type7, Type8, Action1, Action2, Action3, Action4, Action5, " + + "Action6, Action7, Action8 FROM `lock` ORDER BY ID DESC"); + + // MailTemplate.db2 + PrepareStatement(HotfixStatements.SEL_MAIL_TEMPLATE, "SELECT ID, Body FROM mail_template ORDER BY ID DESC"); + PrepareStatement(HotfixStatements.SEL_MAIL_TEMPLATE_LOCALE, "SELECT ID, Body_lang FROM mail_template_locale WHERE locale = ?"); + + // Map.db2 + PrepareStatement(HotfixStatements.SEL_MAP, "SELECT ID, Directory, Flags1, Flags2, MinimapIconScale, CorpsePosX, CorpsePosY, MapName, MapDescription0, " + + "MapDescription1, ShortDescription, LongDescription, AreaTableID, LoadingScreenID, CorpseMapID, TimeOfDayOverride, ParentMapID, " + + "CosmeticParentMapID, WindSettingsID, InstanceType, unk5, ExpansionID, MaxPlayers, TimeOffset FROM map ORDER BY ID DESC"); + PrepareStatement(HotfixStatements.SEL_MAP_LOCALE, "SELECT ID, MapName_lang, MapDescription0_lang, MapDescription1_lang, ShortDescription_lang, " + + "LongDescription_lang FROM map_locale WHERE locale = ?"); + + // MapDifficulty.db2 + PrepareStatement(HotfixStatements.SEL_MAP_DIFFICULTY, "SELECT ID, Message, MapID, DifficultyID, RaidDurationType, MaxPlayers, LockID, Flags, " + + "ItemBonusTreeModID, Context FROM map_difficulty ORDER BY ID DESC"); + PrepareStatement(HotfixStatements.SEL_MAP_DIFFICULTY_LOCALE, "SELECT ID, Message_lang FROM map_difficulty_locale WHERE locale = ?"); + + // ModifierTree.db2 + PrepareStatement(HotfixStatements.SEL_MODIFIER_TREE, "SELECT ID, Asset1, Asset2, Parent, Type, Unk700, Operator, Amount FROM modifier_tree ORDER BY ID DESC"); + + // Mount.db2 + PrepareStatement(HotfixStatements.SEL_MOUNT, "SELECT SpellId, Name, Description, SourceDescription, CameraPivotMultiplier, MountTypeId, Flags, Source, ID, " + + "PlayerConditionId, UiModelSceneID FROM mount ORDER BY ID DESC"); + PrepareStatement(HotfixStatements.SEL_MOUNT_LOCALE, "SELECT ID, Name_lang, Description_lang, SourceDescription_lang FROM mount_locale WHERE locale = ?"); + + // MountCapability.db2 + PrepareStatement(HotfixStatements.SEL_MOUNT_CAPABILITY, "SELECT RequiredSpell, SpeedModSpell, RequiredRidingSkill, RequiredArea, RequiredMap, Flags, ID, " + + "RequiredAura FROM mount_capability ORDER BY ID DESC"); + + // MountTypeXCapability.db2 + PrepareStatement(HotfixStatements.SEL_MOUNT_TYPE_X_CAPABILITY, "SELECT ID, MountTypeID, MountCapabilityID, OrderIndex FROM mount_type_x_capability" + + " ORDER BY ID DESC"); + + // MountXDisplay.db2 + PrepareStatement(HotfixStatements.SEL_MOUNT_X_DISPLAY, "SELECT ID, MountID, DisplayID, PlayerConditionID FROM mount_x_display ORDER BY ID DESC"); + + // Movie.db2 + PrepareStatement(HotfixStatements.SEL_MOVIE, "SELECT ID, AudioFileDataID, SubtitleFileDataID, Volume, KeyID FROM movie ORDER BY ID DESC"); + + // NameGen.db2 + PrepareStatement(HotfixStatements.SEL_NAME_GEN, "SELECT ID, Name, Race, Sex FROM name_gen ORDER BY ID DESC"); + PrepareStatement(HotfixStatements.SEL_NAME_GEN_LOCALE, "SELECT ID, Name_lang FROM name_gen_locale WHERE locale = ?"); + + // NamesProfanity.db2 + PrepareStatement(HotfixStatements.SEL_NAMES_PROFANITY, "SELECT ID, Name, Language FROM names_profanity ORDER BY ID DESC"); + + // NamesReserved.db2 + PrepareStatement(HotfixStatements.SEL_NAMES_RESERVED, "SELECT ID, Name FROM names_reserved ORDER BY ID DESC"); + + // NamesReservedLocale.db2 + PrepareStatement(HotfixStatements.SEL_NAMES_RESERVED_LOCALE, "SELECT ID, Name, LocaleMask FROM names_reserved_locale ORDER BY ID DESC"); + + // OverrideSpellData.db2 + PrepareStatement(HotfixStatements.SEL_OVERRIDE_SPELL_DATA, "SELECT ID, SpellID1, SpellID2, SpellID3, SpellID4, SpellID5, SpellID6, SpellID7, SpellID8, " + + "SpellID9, SpellID10, PlayerActionbarFileDataID, Flags FROM override_spell_data ORDER BY ID DESC"); + + // Phase.db2 + PrepareStatement(HotfixStatements.SEL_PHASE, "SELECT ID, Flags FROM phase ORDER BY ID DESC"); + + // PhaseXPhaseGroup.db2 + PrepareStatement(HotfixStatements.SEL_PHASE_X_PHASE_GROUP, "SELECT ID, PhaseID, PhaseGroupID FROM phase_x_phase_group ORDER BY ID DESC"); + + // PlayerCondition.db2 + PrepareStatement(HotfixStatements.SEL_PLAYER_CONDITION, "SELECT ID, RaceMask, SkillLogic, ReputationLogic, PrevQuestLogic, CurrQuestLogic, " + + "CurrentCompletedQuestLogic, SpellLogic, ItemLogic, Time1, Time2, AuraSpellLogic, AuraSpellID1, AuraSpellID2, AuraSpellID3, AuraSpellID4, " + + "AchievementLogic, AreaLogic, QuestKillLogic, FailureDescription, MinLevel, MaxLevel, SkillID1, SkillID2, SkillID3, SkillID4, MinSkill1, " + + "MinSkill2, MinSkill3, MinSkill4, MaxSkill1, MaxSkill2, MaxSkill3, MaxSkill4, MaxFactionID, PrevQuestID1, PrevQuestID2, PrevQuestID3, " + + "PrevQuestID4, CurrQuestID1, CurrQuestID2, CurrQuestID3, CurrQuestID4, CurrentCompletedQuestID1, CurrentCompletedQuestID2, " + + "CurrentCompletedQuestID3, CurrentCompletedQuestID4, Explored1, Explored2, WorldStateExpressionID, Achievement1, Achievement2, Achievement3, " + + "Achievement4, AreaID1, AreaID2, AreaID3, AreaID4, QuestKillID, PhaseID, MinAvgEquippedItemLevel, MaxAvgEquippedItemLevel, ModifierTreeID, " + + "Flags, Gender, NativeGender, MinLanguage, MaxLanguage, MinReputation1, MinReputation2, MinReputation3, MaxReputation, Unknown1, MinPVPRank, " + + "MaxPVPRank, PvpMedal, ItemFlags, AuraCount1, AuraCount2, AuraCount3, AuraCount4, WeatherID, PartyStatus, LifetimeMaxPVPRank, LfgStatus1, " + + "LfgStatus2, LfgStatus3, LfgStatus4, LfgCompare1, LfgCompare2, LfgCompare3, LfgCompare4, CurrencyCount1, CurrencyCount2, CurrencyCount3, " + + "CurrencyCount4, MinExpansionLevel, MaxExpansionLevel, MinExpansionTier, MaxExpansionTier, MinGuildLevel, MaxGuildLevel, PhaseUseFlags, " + + "ChrSpecializationIndex, ChrSpecializationRole, PowerType, PowerTypeComp, PowerTypeValue, ClassMask, LanguageID, MinFactionID1, " + + "MinFactionID2, MinFactionID3, SpellID1, SpellID2, SpellID3, SpellID4, ItemID1, ItemID2, ItemID3, ItemID4, ItemCount1, ItemCount2, " + + "ItemCount3, ItemCount4, LfgLogic, LfgValue1, LfgValue2, LfgValue3, LfgValue4, CurrencyLogic, CurrencyID1, CurrencyID2, CurrencyID3, " + + "CurrencyID4, QuestKillMonster1, QuestKillMonster2, QuestKillMonster3, QuestKillMonster4, QuestKillMonster5, QuestKillMonster6, PhaseGroupID, " + + "MinAvgItemLevel, MaxAvgItemLevel, MovementFlags1, MovementFlags2, MainHandItemSubclassMask FROM player_condition ORDER BY ID DESC"); + PrepareStatement(HotfixStatements.SEL_PLAYER_CONDITION_LOCALE, "SELECT ID, FailureDescription_lang FROM player_condition_locale WHERE locale = ?"); + + // PowerDisplay.db2 + PrepareStatement(HotfixStatements.SEL_POWER_DISPLAY, "SELECT ID, GlobalStringBaseTag, PowerType, Red, Green, Blue FROM power_display ORDER BY ID DESC"); + + // PowerType.db2 + PrepareStatement(HotfixStatements.SEL_POWER_TYPE, "SELECT ID, PowerTypeToken, PowerCostToken, RegenerationPeace, RegenerationCombat, MaxPower, " + + "RegenerationDelay, Flags, PowerTypeEnum, RegenerationMin, RegenerationCenter, RegenerationMax, UIModifier FROM power_type ORDER BY ID DESC"); + + // PrestigeLevelInfo.db2 + PrepareStatement(HotfixStatements.SEL_PRESTIGE_LEVEL_INFO, "SELECT ID, IconID, PrestigeText, PrestigeLevel, Flags FROM prestige_level_info ORDER BY ID DESC"); + PrepareStatement(HotfixStatements.SEL_PRESTIGE_LEVEL_INFO_LOCALE, "SELECT ID, PrestigeText_lang FROM prestige_level_info_locale WHERE locale = ?"); + + // PvpDifficulty.db2 + PrepareStatement(HotfixStatements.SEL_PVP_DIFFICULTY, "SELECT ID, MapID, BracketID, MinLevel, MaxLevel FROM pvp_difficulty ORDER BY ID DESC"); + + // PvpReward.db2 + PrepareStatement(HotfixStatements.SEL_PVP_REWARD, "SELECT ID, HonorLevel, Prestige, RewardPackID FROM pvp_reward ORDER BY ID DESC"); + + // QuestFactionReward.db2 + PrepareStatement(HotfixStatements.SEL_QUEST_FACTION_REWARD, "SELECT ID, QuestRewFactionValue1, QuestRewFactionValue2, QuestRewFactionValue3, " + + "QuestRewFactionValue4, QuestRewFactionValue5, QuestRewFactionValue6, QuestRewFactionValue7, QuestRewFactionValue8, QuestRewFactionValue9, " + + "QuestRewFactionValue10 FROM quest_faction_reward ORDER BY ID DESC"); + + // QuestMoneyReward.db2 + PrepareStatement(HotfixStatements.SEL_QUEST_MONEY_REWARD, "SELECT ID, Money1, Money2, Money3, Money4, Money5, Money6, Money7, Money8, Money9, Money10" + + " FROM quest_money_reward ORDER BY ID DESC"); + + // QuestPackageItem.db2 + PrepareStatement(HotfixStatements.SEL_QUEST_PACKAGE_ITEM, "SELECT ID, ItemID, QuestPackageID, FilterType, ItemCount FROM quest_package_item ORDER BY ID DESC"); + + // QuestSort.db2 + PrepareStatement(HotfixStatements.SEL_QUEST_SORT, "SELECT ID, SortName, SortOrder FROM quest_sort ORDER BY ID DESC"); + PrepareStatement(HotfixStatements.SEL_QUEST_SORT_LOCALE, "SELECT ID, SortName_lang FROM quest_sort_locale WHERE locale = ?"); + + // QuestV2.db2 + PrepareStatement(HotfixStatements.SEL_QUEST_V2, "SELECT ID, UniqueBitFlag FROM quest_v2 ORDER BY ID DESC"); + + // QuestXp.db2 + PrepareStatement(HotfixStatements.SEL_QUEST_XP, "SELECT ID, Exp1, Exp2, Exp3, Exp4, Exp5, Exp6, Exp7, Exp8, Exp9, Exp10 FROM quest_xp ORDER BY ID DESC"); + + // RandPropPoints.db2 + PrepareStatement(HotfixStatements.SEL_RAND_PROP_POINTS, "SELECT ID, EpicPropertiesPoints1, EpicPropertiesPoints2, EpicPropertiesPoints3, " + + "EpicPropertiesPoints4, EpicPropertiesPoints5, RarePropertiesPoints1, RarePropertiesPoints2, RarePropertiesPoints3, RarePropertiesPoints4, " + + "RarePropertiesPoints5, UncommonPropertiesPoints1, UncommonPropertiesPoints2, UncommonPropertiesPoints3, UncommonPropertiesPoints4, " + + "UncommonPropertiesPoints5 FROM rand_prop_points ORDER BY ID DESC"); + + // RewardPack.db2 + PrepareStatement(HotfixStatements.SEL_REWARD_PACK, "SELECT ID, Money, ArtifactXPMultiplier, ArtifactXPDifficulty, ArtifactCategoryID, TitleID, Unused" + + " FROM reward_pack ORDER BY ID DESC"); + + // RewardPackXItem.db2 + PrepareStatement(HotfixStatements.SEL_REWARD_PACK_X_ITEM, "SELECT ID, ItemID, RewardPackID, Amount FROM reward_pack_x_item ORDER BY ID DESC"); + + // RulesetItemUpgrade.db2 + PrepareStatement(HotfixStatements.SEL_RULESET_ITEM_UPGRADE, "SELECT ID, ItemID, ItemUpgradeID FROM ruleset_item_upgrade ORDER BY ID DESC"); + + // ScalingStatDistribution.db2 + PrepareStatement(HotfixStatements.SEL_SCALING_STAT_DISTRIBUTION, "SELECT ID, ItemLevelCurveID, MinLevel, MaxLevel FROM scaling_stat_distribution" + + " ORDER BY ID DESC"); + + // Scenario.db2 + PrepareStatement(HotfixStatements.SEL_SCENARIO, "SELECT ID, Name, Data, Flags, Type FROM scenario ORDER BY ID DESC"); + PrepareStatement(HotfixStatements.SEL_SCENARIO_LOCALE, "SELECT ID, Name_lang FROM scenario_locale WHERE locale = ?"); + + // ScenarioStep.db2 + PrepareStatement(HotfixStatements.SEL_SCENARIO_STEP, "SELECT ID, Description, Name, CriteriaTreeID, ScenarioID, PreviousStepID, QuestRewardID, Step, Flags, " + + "BonusRequiredStepID FROM scenario_step ORDER BY ID DESC"); + PrepareStatement(HotfixStatements.SEL_SCENARIO_STEP_LOCALE, "SELECT ID, Description_lang, Name_lang FROM scenario_step_locale WHERE locale = ?"); + + // SceneScript.db2 + PrepareStatement(HotfixStatements.SEL_SCENE_SCRIPT, "SELECT ID, Name, Script, PrevScriptId, NextScriptId FROM scene_script ORDER BY ID DESC"); + + // SceneScriptPackage.db2 + PrepareStatement(HotfixStatements.SEL_SCENE_SCRIPT_PACKAGE, "SELECT ID, Name FROM scene_script_package ORDER BY ID DESC"); + + // SkillLine.db2 + PrepareStatement(HotfixStatements.SEL_SKILL_LINE, "SELECT ID, DisplayName, Description, AlternateVerb, Flags, CategoryID, CanLink, IconFileDataID, " + + "ParentSkillLineID FROM skill_line ORDER BY ID DESC"); + PrepareStatement(HotfixStatements.SEL_SKILL_LINE_LOCALE, "SELECT ID, DisplayName_lang, Description_lang, AlternateVerb_lang FROM skill_line_locale" + + " WHERE locale = ?"); + + // SkillLineAbility.db2 + PrepareStatement(HotfixStatements.SEL_SKILL_LINE_ABILITY, "SELECT ID, SpellID, RaceMask, SupercedesSpell, SkillLine, MinSkillLineRank, " + + "TrivialSkillLineRankHigh, TrivialSkillLineRankLow, UniqueBit, TradeSkillCategoryID, AcquireMethod, NumSkillUps, Unknown703, ClassMask" + + " FROM skill_line_ability ORDER BY ID DESC"); + + // SkillRaceClassInfo.db2 + PrepareStatement(HotfixStatements.SEL_SKILL_RACE_CLASS_INFO, "SELECT ID, RaceMask, SkillID, Flags, SkillTierID, Availability, MinLevel, ClassMask" + + " FROM skill_race_class_info ORDER BY ID DESC"); + + // SoundKit.db2 + PrepareStatement(HotfixStatements.SEL_SOUND_KIT, "SELECT Name, VolumeFloat, MinDistance, DistanceCutoff, VolumeVariationPlus, VolumeVariationMinus, " + + "PitchVariationPlus, PitchVariationMinus, PitchAdjust, Flags, SoundEntriesAdvancedID, BusOverwriteID, SoundType, EAXDef, DialogType, Unk700, " + + "ID FROM sound_kit ORDER BY ID DESC"); + PrepareStatement(HotfixStatements.SEL_SOUND_KIT_LOCALE, "SELECT ID, Name_lang FROM sound_kit_locale WHERE locale = ?"); + + // SpecializationSpells.db2 + PrepareStatement(HotfixStatements.SEL_SPECIALIZATION_SPELLS, "SELECT SpellID, OverridesSpellID, Description, SpecID, OrderIndex, ID" + + " FROM specialization_spells ORDER BY ID DESC"); + PrepareStatement(HotfixStatements.SEL_SPECIALIZATION_SPELLS_LOCALE, "SELECT ID, Description_lang FROM specialization_spells_locale WHERE locale = ?"); + + // Spell.db2 + PrepareStatement(HotfixStatements.SEL_SPELL, "SELECT Name, NameSubtext, Description, AuraDescription, MiscID, ID, DescriptionVariablesID FROM spell" + + " ORDER BY ID DESC"); + PrepareStatement(HotfixStatements.SEL_SPELL_LOCALE, "SELECT ID, Name_lang, NameSubtext_lang, Description_lang, AuraDescription_lang FROM spell_locale" + + " WHERE locale = ?"); + + // SpellAuraOptions.db2 + PrepareStatement(HotfixStatements.SEL_SPELL_AURA_OPTIONS, "SELECT ID, SpellID, ProcCharges, ProcTypeMask, ProcCategoryRecovery, CumulativeAura, " + + "DifficultyID, ProcChance, SpellProcsPerMinuteID FROM spell_aura_options ORDER BY ID DESC"); + + // SpellAuraRestrictions.db2 + PrepareStatement(HotfixStatements.SEL_SPELL_AURA_RESTRICTIONS, "SELECT ID, SpellID, CasterAuraSpell, TargetAuraSpell, ExcludeCasterAuraSpell, " + + "ExcludeTargetAuraSpell, DifficultyID, CasterAuraState, TargetAuraState, ExcludeCasterAuraState, ExcludeTargetAuraState" + + " FROM spell_aura_restrictions ORDER BY ID DESC"); + + // SpellCastTimes.db2 + PrepareStatement(HotfixStatements.SEL_SPELL_CAST_TIMES, "SELECT ID, CastTime, MinCastTime, CastTimePerLevel FROM spell_cast_times ORDER BY ID DESC"); + + // SpellCastingRequirements.db2 + PrepareStatement(HotfixStatements.SEL_SPELL_CASTING_REQUIREMENTS, "SELECT ID, SpellID, MinFactionID, RequiredAreasID, RequiresSpellFocus, " + + "FacingCasterFlags, MinReputation, RequiredAuraVision FROM spell_casting_requirements ORDER BY ID DESC"); + + // SpellCategories.db2 + PrepareStatement(HotfixStatements.SEL_SPELL_CATEGORIES, "SELECT ID, SpellID, Category, StartRecoveryCategory, ChargeCategory, DifficultyID, DefenseType, " + + "DispelType, Mechanic, PreventionType FROM spell_categories ORDER BY ID DESC"); + + // SpellCategory.db2 + PrepareStatement(HotfixStatements.SEL_SPELL_CATEGORY, "SELECT ID, Name, ChargeRecoveryTime, Flags, UsesPerWeek, MaxCharges, ChargeCategoryType " + + "FROM spell_category ORDER BY ID DESC"); + PrepareStatement(HotfixStatements.SEL_SPELL_CATEGORY_LOCALE, "SELECT ID, Name_lang FROM spell_category_locale WHERE locale = ?"); + + // SpellClassOptions.db2 + PrepareStatement(HotfixStatements.SEL_SPELL_CLASS_OPTIONS, "SELECT ID, SpellID, SpellClassMask1, SpellClassMask2, SpellClassMask3, SpellClassMask4, " + + "SpellClassSet, ModalNextSpell FROM spell_class_options ORDER BY ID DESC"); + + // SpellCooldowns.db2 + PrepareStatement(HotfixStatements.SEL_SPELL_COOLDOWNS, "SELECT ID, SpellID, CategoryRecoveryTime, RecoveryTime, StartRecoveryTime, DifficultyID" + + " FROM spell_cooldowns ORDER BY ID DESC"); + + // SpellDuration.db2 + PrepareStatement(HotfixStatements.SEL_SPELL_DURATION, "SELECT ID, Duration, MaxDuration, DurationPerLevel FROM spell_duration ORDER BY ID DESC"); + + // SpellEffect.db2 + PrepareStatement(HotfixStatements.SEL_SPELL_EFFECT, "SELECT EffectSpellClassMask1, EffectSpellClassMask2, EffectSpellClassMask3, EffectSpellClassMask4, ID, " + + "SpellID, Effect, EffectAura, EffectBasePoints, EffectIndex, EffectMiscValue, EffectMiscValueB, EffectRadiusIndex, EffectRadiusMaxIndex, " + + "ImplicitTarget1, ImplicitTarget2, DifficultyID, EffectAmplitude, EffectAuraPeriod, EffectBonusCoefficient, EffectChainAmplitude, " + + "EffectChainTargets, EffectDieSides, EffectItemType, EffectMechanic, EffectPointsPerResource, EffectRealPointsPerLevel, EffectTriggerSpell, " + + "EffectPosFacing, EffectAttributes, BonusCoefficientFromAP, PvPMultiplier FROM spell_effect ORDER BY ID DESC"); + + // SpellEffectScaling.db2 + PrepareStatement(HotfixStatements.SEL_SPELL_EFFECT_SCALING, "SELECT ID, Coefficient, Variance, ResourceCoefficient, SpellEffectID FROM spell_effect_scaling" + + " ORDER BY ID DESC"); + + // SpellEquippedItems.db2 + PrepareStatement(HotfixStatements.SEL_SPELL_EQUIPPED_ITEMS, "SELECT ID, SpellID, EquippedItemInventoryTypeMask, EquippedItemSubClassMask, " + + "EquippedItemClass FROM spell_equipped_items ORDER BY ID DESC"); + + // SpellFocusObject.db2 + PrepareStatement(HotfixStatements.SEL_SPELL_FOCUS_OBJECT, "SELECT ID, Name FROM spell_focus_object ORDER BY ID DESC"); + PrepareStatement(HotfixStatements.SEL_SPELL_FOCUS_OBJECT_LOCALE, "SELECT ID, Name_lang FROM spell_focus_object_locale WHERE locale = ?"); + + // SpellInterrupts.db2 + PrepareStatement(HotfixStatements.SEL_SPELL_INTERRUPTS, "SELECT ID, SpellID, AuraInterruptFlags1, AuraInterruptFlags2, ChannelInterruptFlags1, " + + "ChannelInterruptFlags2, InterruptFlags, DifficultyID FROM spell_interrupts ORDER BY ID DESC"); + + // SpellItemEnchantment.db2 + PrepareStatement(HotfixStatements.SEL_SPELL_ITEM_ENCHANTMENT, "SELECT ID, EffectSpellID1, EffectSpellID2, EffectSpellID3, Name, EffectScalingPoints1, " + + "EffectScalingPoints2, EffectScalingPoints3, TransmogCost, TextureFileDataID, EffectPointsMin1, EffectPointsMin2, EffectPointsMin3, " + + "ItemVisual, Flags, RequiredSkillID, RequiredSkillRank, ItemLevel, Charges, Effect1, Effect2, Effect3, ConditionID, MinLevel, MaxLevel, " + + "ScalingClass, ScalingClassRestricted, PlayerConditionID FROM spell_item_enchantment ORDER BY ID DESC"); + PrepareStatement(HotfixStatements.SEL_SPELL_ITEM_ENCHANTMENT_LOCALE, "SELECT ID, Name_lang FROM spell_item_enchantment_locale WHERE locale = ?"); + + // SpellItemEnchantmentCondition.db2 + PrepareStatement(HotfixStatements.SEL_SPELL_ITEM_ENCHANTMENT_CONDITION, "SELECT ID, LTOperandType1, LTOperandType2, LTOperandType3, LTOperandType4, " + + "LTOperandType5, Operator1, Operator2, Operator3, Operator4, Operator5, RTOperandType1, RTOperandType2, RTOperandType3, RTOperandType4, " + + "RTOperandType5, RTOperand1, RTOperand2, RTOperand3, RTOperand4, RTOperand5, Logic1, Logic2, Logic3, Logic4, Logic5, LTOperand1, LTOperand2, " + + "LTOperand3, LTOperand4, LTOperand5 FROM spell_item_enchantment_condition ORDER BY ID DESC"); + + // SpellLearnSpell.db2 + PrepareStatement(HotfixStatements.SEL_SPELL_LEARN_SPELL, "SELECT ID, LearnSpellID, SpellID, OverridesSpellID FROM spell_learn_spell ORDER BY ID DESC"); + + // SpellLevels.db2 + PrepareStatement(HotfixStatements.SEL_SPELL_LEVELS, "SELECT ID, SpellID, BaseLevel, MaxLevel, SpellLevel, DifficultyID, MaxUsableLevel FROM spell_levels" + + " ORDER BY ID DESC"); + + // SpellMisc.db2 + PrepareStatement(HotfixStatements.SEL_SPELL_MISC, "SELECT ID, Attributes, AttributesEx, AttributesExB, AttributesExC, AttributesExD, AttributesExE, " + + "AttributesExF, AttributesExG, AttributesExH, AttributesExI, AttributesExJ, AttributesExK, AttributesExL, AttributesExM, Speed, " + + "MultistrikeSpeedMod, CastingTimeIndex, DurationIndex, RangeIndex, SchoolMask, IconFileDataID, ActiveIconFileDataID FROM spell_misc" + + " ORDER BY ID DESC"); + + // SpellPower.db2 + PrepareStatement(HotfixStatements.SEL_SPELL_POWER, "SELECT SpellID, ManaCost, ManaCostPercentage, ManaCostPercentagePerSecond, RequiredAura, " + + "HealthCostPercentage, PowerIndex, PowerType, ID, ManaCostPerLevel, ManaCostPerSecond, ManaCostAdditional, PowerDisplayID, UnitPowerBarID" + + " FROM spell_power ORDER BY ID DESC"); + + // SpellPowerDifficulty.db2 + PrepareStatement(HotfixStatements.SEL_SPELL_POWER_DIFFICULTY, "SELECT DifficultyID, PowerIndex, ID FROM spell_power_difficulty ORDER BY ID DESC"); + + // SpellProcsPerMinute.db2 + PrepareStatement(HotfixStatements.SEL_SPELL_PROCS_PER_MINUTE, "SELECT ID, BaseProcRate, Flags FROM spell_procs_per_minute ORDER BY ID DESC"); + + // SpellProcsPerMinuteMod.db2 + PrepareStatement(HotfixStatements.SEL_SPELL_PROCS_PER_MINUTE_MOD, "SELECT ID, Coeff, Param, Type, SpellProcsPerMinuteID FROM spell_procs_per_minute_mod" + + " ORDER BY ID DESC"); + + // SpellRadius.db2 + PrepareStatement(HotfixStatements.SEL_SPELL_RADIUS, "SELECT ID, Radius, RadiusPerLevel, RadiusMin, RadiusMax FROM spell_radius ORDER BY ID DESC"); + + // SpellRange.db2 + PrepareStatement(HotfixStatements.SEL_SPELL_RANGE, "SELECT ID, MinRangeHostile, MinRangeFriend, MaxRangeHostile, MaxRangeFriend, DisplayName, " + + "DisplayNameShort, Flags FROM spell_range ORDER BY ID DESC"); + PrepareStatement(HotfixStatements.SEL_SPELL_RANGE_LOCALE, "SELECT ID, DisplayName_lang, DisplayNameShort_lang FROM spell_range_locale WHERE locale = ?"); + + // SpellReagents.db2 + PrepareStatement(HotfixStatements.SEL_SPELL_REAGENTS, "SELECT ID, SpellID, Reagent1, Reagent2, Reagent3, Reagent4, Reagent5, Reagent6, Reagent7, Reagent8, " + + "ReagentCount1, ReagentCount2, ReagentCount3, ReagentCount4, ReagentCount5, ReagentCount6, ReagentCount7, ReagentCount8 FROM spell_reagents" + + " ORDER BY ID DESC"); + + // SpellScaling.db2 + PrepareStatement(HotfixStatements.SEL_SPELL_SCALING, "SELECT ID, SpellID, ScalesFromItemLevel, ScalingClass, MinScalingLevel, MaxScalingLevel" + + " FROM spell_scaling ORDER BY ID DESC"); + + // SpellShapeshift.db2 + PrepareStatement(HotfixStatements.SEL_SPELL_SHAPESHIFT, "SELECT ID, SpellID, ShapeshiftExclude1, ShapeshiftExclude2, ShapeshiftMask1, ShapeshiftMask2, " + + "StanceBarOrder FROM spell_shapeshift ORDER BY ID DESC"); + + // SpellShapeshiftForm.db2 + PrepareStatement(HotfixStatements.SEL_SPELL_SHAPESHIFT_FORM, "SELECT ID, Name, WeaponDamageVariance, Flags, CombatRoundTime, MountTypeID, CreatureType, " + + "BonusActionBar, AttackIconFileDataID, CreatureDisplayID1, CreatureDisplayID2, CreatureDisplayID3, CreatureDisplayID4, PresetSpellID1, " + + "PresetSpellID2, PresetSpellID3, PresetSpellID4, PresetSpellID5, PresetSpellID6, PresetSpellID7, PresetSpellID8 FROM spell_shapeshift_form" + + " ORDER BY ID DESC"); + PrepareStatement(HotfixStatements.SEL_SPELL_SHAPESHIFT_FORM_LOCALE, "SELECT ID, Name_lang FROM spell_shapeshift_form_locale WHERE locale = ?"); + + // SpellTargetRestrictions.db2 + PrepareStatement(HotfixStatements.SEL_SPELL_TARGET_RESTRICTIONS, "SELECT ID, SpellID, ConeAngle, Width, Targets, TargetCreatureType, DifficultyID, " + + "MaxAffectedTargets, MaxTargetLevel FROM spell_target_restrictions ORDER BY ID DESC"); + + // SpellTotems.db2 + PrepareStatement(HotfixStatements.SEL_SPELL_TOTEMS, "SELECT ID, SpellID, Totem1, Totem2, RequiredTotemCategoryID1, RequiredTotemCategoryID2" + + " FROM spell_totems ORDER BY ID DESC"); + + // SpellXSpellVisual.db2 + PrepareStatement(HotfixStatements.SEL_SPELL_X_SPELL_VISUAL, "SELECT SpellID, SpellVisualID, ID, Chance, CasterPlayerConditionID, CasterUnitConditionID, " + + "PlayerConditionID, UnitConditionID, IconFileDataID, ActiveIconFileDataID, Flags, DifficultyID, Priority FROM spell_x_spell_visual" + + " ORDER BY ID DESC"); + + // SummonProperties.db2 + PrepareStatement(HotfixStatements.SEL_SUMMON_PROPERTIES, "SELECT ID, Flags, Category, Faction, Type, Slot FROM summon_properties ORDER BY ID DESC"); + + // TactKey.db2 + PrepareStatement(HotfixStatements.SEL_TACT_KEY, "SELECT ID, Key1, Key2, Key3, Key4, Key5, Key6, Key7, Key8, Key9, Key10, Key11, Key12, Key13, Key14, " + + "Key15, Key16 FROM tact_key ORDER BY ID DESC"); + + // Talent.db2 + PrepareStatement(HotfixStatements.SEL_TALENT, "SELECT ID, SpellID, OverridesSpellID, Description, SpecID, TierID, ColumnIndex, Flags, CategoryMask1, " + + "CategoryMask2, ClassID FROM talent ORDER BY ID DESC"); + PrepareStatement(HotfixStatements.SEL_TALENT_LOCALE, "SELECT ID, Description_lang FROM talent_locale WHERE locale = ?"); + + // TaxiNodes.db2 + PrepareStatement(HotfixStatements.SEL_TAXI_NODES, "SELECT PosX, PosY, PosZ, Name, MountCreatureID1, MountCreatureID2, MapOffsetX, MapOffsetY, MapID, " + + "ConditionID, LearnableIndex, Flags, ID FROM taxi_nodes ORDER BY ID DESC"); + PrepareStatement(HotfixStatements.SEL_TAXI_NODES_LOCALE, "SELECT ID, Name_lang FROM taxi_nodes_locale WHERE locale = ?"); + + // TaxiPath.db2 + PrepareStatement(HotfixStatements.SEL_TAXI_PATH, "SELECT `From`, `To`, ID, Cost FROM taxi_path ORDER BY ID DESC"); + + // TaxiPathNode.db2 + PrepareStatement(HotfixStatements.SEL_TAXI_PATH_NODE, "SELECT LocX, LocY, LocZ, Delay, PathID, MapID, ArrivalEventID, DepartureEventID, NodeIndex, Flags, " + + "ID FROM taxi_path_node ORDER BY ID DESC"); + + // TotemCategory.db2 + PrepareStatement(HotfixStatements.SEL_TOTEM_CATEGORY, "SELECT ID, Name, CategoryMask, CategoryType FROM totem_category ORDER BY ID DESC"); + PrepareStatement(HotfixStatements.SEL_TOTEM_CATEGORY_LOCALE, "SELECT ID, Name_lang FROM totem_category_locale WHERE locale = ?"); + + // Toy.db2 + PrepareStatement(HotfixStatements.SEL_TOY, "SELECT ItemID, Description, Flags, CategoryFilter, ID FROM toy ORDER BY ID DESC"); + PrepareStatement(HotfixStatements.SEL_TOY_LOCALE, "SELECT ID, Description_lang FROM toy_locale WHERE locale = ?"); + + // TransportAnimation.db2 + PrepareStatement(HotfixStatements.SEL_TRANSPORT_ANIMATION, "SELECT ID, TransportID, TimeIndex, PosX, PosY, PosZ, SequenceID FROM transport_animation" + + " ORDER BY ID DESC"); + + // TransportRotation.db2 + PrepareStatement(HotfixStatements.SEL_TRANSPORT_ROTATION, "SELECT ID, TransportID, TimeIndex, X, Y, Z, W FROM transport_rotation ORDER BY ID DESC"); + + // UnitPowerBar.db2 + PrepareStatement(HotfixStatements.SEL_UNIT_POWER_BAR, "SELECT ID, RegenerationPeace, RegenerationCombat, FileDataID1, FileDataID2, FileDataID3, " + + "FileDataID4, FileDataID5, FileDataID6, Color1, Color2, Color3, Color4, Color5, Color6, Name, Cost, OutOfError, ToolTip, StartInset, " + + "EndInset, StartPower, Flags, CenterPower, BarType, MinPower, MaxPower FROM unit_power_bar ORDER BY ID DESC"); + PrepareStatement(HotfixStatements.SEL_UNIT_POWER_BAR_LOCALE, "SELECT ID, Name_lang, Cost_lang, OutOfError_lang, ToolTip_lang FROM unit_power_bar_locale" + + " WHERE locale = ?"); + + // Vehicle.db2 + PrepareStatement(HotfixStatements.SEL_VEHICLE, "SELECT ID, Flags, TurnSpeed, PitchSpeed, PitchMin, PitchMax, MouseLookOffsetPitch, CameraFadeDistScalarMin, " + + "CameraFadeDistScalarMax, CameraPitchOffset, FacingLimitRight, FacingLimitLeft, MsslTrgtTurnLingering, MsslTrgtPitchLingering, " + + "MsslTrgtMouseLingering, MsslTrgtEndOpacity, MsslTrgtArcSpeed, MsslTrgtArcRepeat, MsslTrgtArcWidth, MsslTrgtImpactRadius1, " + + "MsslTrgtImpactRadius2, MsslTrgtArcTexture, MsslTrgtImpactTexture, MsslTrgtImpactModel1, MsslTrgtImpactModel2, CameraYawOffset, " + + "MsslTrgtImpactTexRadius, SeatID1, SeatID2, SeatID3, SeatID4, SeatID5, SeatID6, SeatID7, SeatID8, VehicleUIIndicatorID, PowerDisplayID1, " + + "PowerDisplayID2, PowerDisplayID3, FlagsB, UILocomotionType FROM vehicle ORDER BY ID DESC"); + + // VehicleSeat.db2 + PrepareStatement(HotfixStatements.SEL_VEHICLE_SEAT, "SELECT ID, Flags1, Flags2, Flags3, AttachmentOffsetX, AttachmentOffsetY, AttachmentOffsetZ, " + + "EnterPreDelay, EnterSpeed, EnterGravity, EnterMinDuration, EnterMaxDuration, EnterMinArcHeight, EnterMaxArcHeight, ExitPreDelay, ExitSpeed, " + + "ExitGravity, ExitMinDuration, ExitMaxDuration, ExitMinArcHeight, ExitMaxArcHeight, PassengerYaw, PassengerPitch, PassengerRoll, " + + "VehicleEnterAnimDelay, VehicleExitAnimDelay, CameraEnteringDelay, CameraEnteringDuration, CameraExitingDelay, CameraExitingDuration, " + + "CameraOffsetX, CameraOffsetY, CameraOffsetZ, CameraPosChaseRate, CameraFacingChaseRate, CameraEnteringZoom, CameraSeatZoomMin, " + + "CameraSeatZoomMax, UISkinFileDataID, EnterAnimStart, EnterAnimLoop, RideAnimStart, RideAnimLoop, RideUpperAnimStart, RideUpperAnimLoop, " + + "ExitAnimStart, ExitAnimLoop, ExitAnimEnd, VehicleEnterAnim, VehicleExitAnim, VehicleRideAnimLoop, EnterAnimKitID, RideAnimKitID, " + + "ExitAnimKitID, VehicleEnterAnimKitID, VehicleRideAnimKitID, VehicleExitAnimKitID, CameraModeID, AttachmentID, PassengerAttachmentID, " + + "VehicleEnterAnimBone, VehicleExitAnimBone, VehicleRideAnimLoopBone, VehicleAbilityDisplay, EnterUISoundID, ExitUISoundID FROM vehicle_seat" + + " ORDER BY ID DESC"); + + // WmoAreaTable.db2 + PrepareStatement(HotfixStatements.SEL_WMO_AREA_TABLE, "SELECT WMOGroupID, AreaName, WMOID, AmbienceID, ZoneMusic, IntroSound, AreaTableID, UWIntroSound, " + + "UWAmbience, NameSet, SoundProviderPref, SoundProviderPrefUnderwater, Flags, ID, UWZoneMusic FROM wmo_area_table ORDER BY ID DESC"); + PrepareStatement(HotfixStatements.SEL_WMO_AREA_TABLE_LOCALE, "SELECT ID, AreaName_lang FROM wmo_area_table_locale WHERE locale = ?"); + + // WorldMapArea.db2 + PrepareStatement(HotfixStatements.SEL_WORLD_MAP_AREA, "SELECT AreaName, LocLeft, LocRight, LocTop, LocBottom, MapID, AreaID, DisplayMapID, " + + "DefaultDungeonFloor, ParentWorldMapID, Flags, LevelRangeMin, LevelRangeMax, BountySetID, BountyBoardLocation, ID, PlayerConditionID" + + " FROM world_map_area ORDER BY ID DESC"); + + // WorldMapOverlay.db2 + PrepareStatement(HotfixStatements.SEL_WORLD_MAP_OVERLAY, "SELECT ID, TextureName, TextureWidth, TextureHeight, MapAreaID, AreaID1, AreaID2, AreaID3, " + + "AreaID4, OffsetX, OffsetY, HitRectTop, HitRectLeft, HitRectBottom, HitRectRight, PlayerConditionID, Flags FROM world_map_overlay" + + " ORDER BY ID DESC"); + + // WorldMapTransforms.db2 + PrepareStatement(HotfixStatements.SEL_WORLD_MAP_TRANSFORMS, "SELECT ID, RegionMinX, RegionMinY, RegionMinZ, RegionMaxX, RegionMaxY, RegionMaxZ, " + + "RegionOffsetX, RegionOffsetY, RegionScale, MapID, AreaID, NewMapID, NewDungeonMapID, NewAreaID, Flags FROM world_map_transforms" + + " ORDER BY ID DESC"); + + // WorldSafeLocs.db2 + PrepareStatement(HotfixStatements.SEL_WORLD_SAFE_LOCS, "SELECT ID, LocX, LocY, LocZ, Facing, AreaName, MapID FROM world_safe_locs ORDER BY ID DESC"); + PrepareStatement(HotfixStatements.SEL_WORLD_SAFE_LOCS_LOCALE, "SELECT ID, AreaName_lang FROM world_safe_locs_locale WHERE locale = ?"); + } + } + + public enum HotfixStatements + { + None = 0, + + SEL_ACHIEVEMENT, + SEL_ACHIEVEMENT_LOCALE, + + SEL_ANIM_KIT, + + SEL_AREA_GROUP_MEMBER, + + SEL_AREA_TABLE, + SEL_AREA_TABLE_LOCALE, + + SEL_AREA_TRIGGER, + + SEL_ARMOR_LOCATION, + + SEL_ARTIFACT, + SEL_ARTIFACT_LOCALE, + + SEL_ARTIFACT_APPEARANCE, + SEL_ARTIFACT_APPEARANCE_LOCALE, + + SEL_ARTIFACT_APPEARANCE_SET, + SEL_ARTIFACT_APPEARANCE_SET_LOCALE, + + SEL_ARTIFACT_CATEGORY, + + SEL_ARTIFACT_POWER, + + SEL_ARTIFACT_POWER_LINK, + + SEL_ARTIFACT_POWER_PICKER, + + SEL_ARTIFACT_POWER_RANK, + + SEL_ARTIFACT_QUEST_XP, + + SEL_AUCTION_HOUSE, + SEL_AUCTION_HOUSE_LOCALE, + + SEL_BANK_BAG_SLOT_PRICES, + + SEL_BANNED_ADDONS, + + SEL_BARBER_SHOP_STYLE, + SEL_BARBER_SHOP_STYLE_LOCALE, + + SEL_BATTLE_PET_BREED_QUALITY, + + SEL_BATTLE_PET_BREED_STATE, + + SEL_BATTLE_PET_SPECIES, + SEL_BATTLE_PET_SPECIES_LOCALE, + + SEL_BATTLE_PET_SPECIES_STATE, + + SEL_BATTLEMASTER_LIST, + SEL_BATTLEMASTER_LIST_LOCALE, + + SEL_BROADCAST_TEXT, + SEL_BROADCAST_TEXT_LOCALE, + + SEL_CHAR_SECTIONS, + + SEL_CHAR_START_OUTFIT, + + SEL_CHAR_TITLES, + SEL_CHAR_TITLES_LOCALE, + + SEL_CHAT_CHANNELS, + SEL_CHAT_CHANNELS_LOCALE, + + SEL_CHR_CLASSES, + SEL_CHR_CLASSES_LOCALE, + + SEL_CHR_CLASSES_X_POWER_TYPES, + + SEL_CHR_RACES, + SEL_CHR_RACES_LOCALE, + + SEL_CHR_SPECIALIZATION, + SEL_CHR_SPECIALIZATION_LOCALE, + + SEL_CINEMATIC_CAMERA, + + SEL_CINEMATIC_SEQUENCES, + + SEL_CONVERSATION_LINE, + + SEL_CREATURE_DISPLAY_INFO, + + SEL_CREATURE_DISPLAY_INFO_EXTRA, + + SEL_CREATURE_FAMILY, + SEL_CREATURE_FAMILY_LOCALE, + + SEL_CREATURE_MODEL_DATA, + + SEL_CREATURE_TYPE, + SEL_CREATURE_TYPE_LOCALE, + + SEL_CRITERIA, + + SEL_CRITERIA_TREE, + SEL_CRITERIA_TREE_LOCALE, + + SEL_CURRENCY_TYPES, + SEL_CURRENCY_TYPES_LOCALE, + + SEL_CURVE, + + SEL_CURVE_POINT, + + SEL_DESTRUCTIBLE_MODEL_DATA, + + SEL_DIFFICULTY, + SEL_DIFFICULTY_LOCALE, + + SEL_DUNGEON_ENCOUNTER, + SEL_DUNGEON_ENCOUNTER_LOCALE, + + SEL_DURABILITY_COSTS, + + SEL_DURABILITY_QUALITY, + + SEL_EMOTES, + + SEL_EMOTES_TEXT, + SEL_EMOTES_TEXT_LOCALE, + + SEL_EMOTES_TEXT_SOUND, + + SEL_FACTION, + SEL_FACTION_LOCALE, + + SEL_FACTION_TEMPLATE, + + SEL_GAMEOBJECTS, + SEL_GAMEOBJECTS_LOCALE, + + SEL_GAMEOBJECT_DISPLAY_INFO, + + SEL_GARR_ABILITY, + SEL_GARR_ABILITY_LOCALE, + + SEL_GARR_BUILDING, + SEL_GARR_BUILDING_LOCALE, + + SEL_GARR_BUILDING_PLOT_INST, + + SEL_GARR_CLASS_SPEC, + SEL_GARR_CLASS_SPEC_LOCALE, + + SEL_GARR_FOLLOWER, + SEL_GARR_FOLLOWER_LOCALE, + + SEL_GARR_FOLLOWER_X_ABILITY, + + SEL_GARR_PLOT, + SEL_GARR_PLOT_LOCALE, + + SEL_GARR_PLOT_BUILDING, + + SEL_GARR_PLOT_INSTANCE, + SEL_GARR_PLOT_INSTANCE_LOCALE, + + SEL_GARR_SITE_LEVEL, + + SEL_GARR_SITE_LEVEL_PLOT_INST, + + SEL_GEM_PROPERTIES, + + SEL_GLYPH_BINDABLE_SPELL, + + SEL_GLYPH_PROPERTIES, + + SEL_GLYPH_REQUIRED_SPEC, + + SEL_GUILD_COLOR_BACKGROUND, + + SEL_GUILD_COLOR_BORDER, + + SEL_GUILD_COLOR_EMBLEM, + + SEL_GUILD_PERK_SPELLS, + + SEL_HEIRLOOM, + SEL_HEIRLOOM_LOCALE, + + SEL_HOLIDAYS, + + SEL_IMPORT_PRICE_ARMOR, + + SEL_IMPORT_PRICE_QUALITY, + + SEL_IMPORT_PRICE_SHIELD, + + SEL_IMPORT_PRICE_WEAPON, + + SEL_ITEM, + + SEL_ITEM_APPEARANCE, + + SEL_ITEM_ARMOR_QUALITY, + + SEL_ITEM_ARMOR_SHIELD, + + SEL_ITEM_ARMOR_TOTAL, + + SEL_ITEM_BAG_FAMILY, + SEL_ITEM_BAG_FAMILY_LOCALE, + + SEL_ITEM_BONUS, + + SEL_ITEM_BONUS_LIST_LEVEL_DELTA, + + SEL_ITEM_BONUS_TREE_NODE, + + SEL_ITEM_CHILD_EQUIPMENT, + + SEL_ITEM_CLASS, + SEL_ITEM_CLASS_LOCALE, + + SEL_ITEM_CURRENCY_COST, + + SEL_ITEM_DAMAGE_AMMO, + + SEL_ITEM_DAMAGE_ONE_HAND, + + SEL_ITEM_DAMAGE_ONE_HAND_CASTER, + + SEL_ITEM_DAMAGE_TWO_HAND, + + SEL_ITEM_DAMAGE_TWO_HAND_CASTER, + + SEL_ITEM_DISENCHANT_LOOT, + + SEL_ITEM_EFFECT, + + SEL_ITEM_EXTENDED_COST, + + SEL_ITEM_LIMIT_CATEGORY, + SEL_ITEM_LIMIT_CATEGORY_LOCALE, + + SEL_ITEM_MODIFIED_APPEARANCE, + + SEL_ITEM_PRICE_BASE, + + SEL_ITEM_RANDOM_PROPERTIES, + SEL_ITEM_RANDOM_PROPERTIES_LOCALE, + + SEL_ITEM_RANDOM_SUFFIX, + SEL_ITEM_RANDOM_SUFFIX_LOCALE, + + SEL_ITEM_SEARCH_NAME, + SEL_ITEM_SEARCH_NAME_LOCALE, + + SEL_ITEM_SET, + SEL_ITEM_SET_LOCALE, + + SEL_ITEM_SET_SPELL, + + SEL_ITEM_SPARSE, + SEL_ITEM_SPARSE_LOCALE, + + SEL_ITEM_SPEC, + + SEL_ITEM_SPEC_OVERRIDE, + + SEL_ITEM_UPGRADE, + + SEL_ITEM_X_BONUS_TREE, + + SEL_KEY_CHAIN, + + SEL_LFG_DUNGEONS, + SEL_LFG_DUNGEONS_LOCALE, + + SEL_LIGHT, + + SEL_LIQUID_TYPE, + SEL_LIQUID_TYPE_LOCALE, + + SEL_LOCK, + + SEL_MAIL_TEMPLATE, + SEL_MAIL_TEMPLATE_LOCALE, + + SEL_MAP, + SEL_MAP_LOCALE, + + SEL_MAP_DIFFICULTY, + SEL_MAP_DIFFICULTY_LOCALE, + + SEL_MODIFIER_TREE, + + SEL_MOUNT, + SEL_MOUNT_LOCALE, + + SEL_MOUNT_CAPABILITY, + + SEL_MOUNT_TYPE_X_CAPABILITY, + + SEL_MOUNT_X_DISPLAY, + + SEL_MOVIE, + + SEL_NAME_GEN, + SEL_NAME_GEN_LOCALE, + + SEL_NAMES_PROFANITY, + + SEL_NAMES_RESERVED, + + SEL_NAMES_RESERVED_LOCALE, + + SEL_OVERRIDE_SPELL_DATA, + + SEL_PHASE, + + SEL_PHASE_X_PHASE_GROUP, + + SEL_PLAYER_CONDITION, + SEL_PLAYER_CONDITION_LOCALE, + + SEL_POWER_DISPLAY, + + SEL_POWER_TYPE, + + SEL_PRESTIGE_LEVEL_INFO, + SEL_PRESTIGE_LEVEL_INFO_LOCALE, + + SEL_PVP_DIFFICULTY, + + SEL_PVP_REWARD, + + SEL_QUEST_FACTION_REWARD, + + SEL_QUEST_MONEY_REWARD, + + SEL_QUEST_PACKAGE_ITEM, + + SEL_QUEST_SORT, + SEL_QUEST_SORT_LOCALE, + + SEL_QUEST_V2, + + SEL_QUEST_XP, + + SEL_RAND_PROP_POINTS, + + SEL_REWARD_PACK, + + SEL_REWARD_PACK_X_ITEM, + + SEL_RULESET_ITEM_UPGRADE, + + SEL_SCALING_STAT_DISTRIBUTION, + + SEL_SCENARIO, + SEL_SCENARIO_LOCALE, + + SEL_SCENARIO_STEP, + SEL_SCENARIO_STEP_LOCALE, + + SEL_SCENE_SCRIPT, + + SEL_SCENE_SCRIPT_PACKAGE, + + SEL_SKILL_LINE, + SEL_SKILL_LINE_LOCALE, + + SEL_SKILL_LINE_ABILITY, + + SEL_SKILL_RACE_CLASS_INFO, + + SEL_SOUND_KIT, + SEL_SOUND_KIT_LOCALE, + + SEL_SPECIALIZATION_SPELLS, + SEL_SPECIALIZATION_SPELLS_LOCALE, + + SEL_SPELL, + SEL_SPELL_LOCALE, + + SEL_SPELL_AURA_OPTIONS, + + SEL_SPELL_AURA_RESTRICTIONS, + + SEL_SPELL_CAST_TIMES, + + SEL_SPELL_CASTING_REQUIREMENTS, + + SEL_SPELL_CATEGORIES, + + SEL_SPELL_CATEGORY, + SEL_SPELL_CATEGORY_LOCALE, + + SEL_SPELL_CLASS_OPTIONS, + + SEL_SPELL_COOLDOWNS, + + SEL_SPELL_DURATION, + + SEL_SPELL_EFFECT, + + SEL_SPELL_EFFECT_SCALING, + + SEL_SPELL_EQUIPPED_ITEMS, + + SEL_SPELL_FOCUS_OBJECT, + SEL_SPELL_FOCUS_OBJECT_LOCALE, + + SEL_SPELL_INTERRUPTS, + + SEL_SPELL_ITEM_ENCHANTMENT, + SEL_SPELL_ITEM_ENCHANTMENT_LOCALE, + + SEL_SPELL_ITEM_ENCHANTMENT_CONDITION, + + SEL_SPELL_LEARN_SPELL, + + SEL_SPELL_LEVELS, + + SEL_SPELL_MISC, + + SEL_SPELL_POWER, + + SEL_SPELL_POWER_DIFFICULTY, + + SEL_SPELL_PROCS_PER_MINUTE, + + SEL_SPELL_PROCS_PER_MINUTE_MOD, + + SEL_SPELL_RADIUS, + + SEL_SPELL_RANGE, + SEL_SPELL_RANGE_LOCALE, + + SEL_SPELL_REAGENTS, + + SEL_SPELL_SCALING, + + SEL_SPELL_SHAPESHIFT, + + SEL_SPELL_SHAPESHIFT_FORM, + SEL_SPELL_SHAPESHIFT_FORM_LOCALE, + + SEL_SPELL_TARGET_RESTRICTIONS, + + SEL_SPELL_TOTEMS, + + SEL_SPELL_X_SPELL_VISUAL, + + SEL_SUMMON_PROPERTIES, + + SEL_TACT_KEY, + + SEL_TALENT, + SEL_TALENT_LOCALE, + + SEL_TAXI_NODES, + SEL_TAXI_NODES_LOCALE, + + SEL_TAXI_PATH, + + SEL_TAXI_PATH_NODE, + + SEL_TOTEM_CATEGORY, + SEL_TOTEM_CATEGORY_LOCALE, + + SEL_TOY, + SEL_TOY_LOCALE, + + SEL_TRANSPORT_ANIMATION, + + SEL_TRANSPORT_ROTATION, + + SEL_UNIT_POWER_BAR, + SEL_UNIT_POWER_BAR_LOCALE, + + SEL_VEHICLE, + + SEL_VEHICLE_SEAT, + + SEL_WMO_AREA_TABLE, + SEL_WMO_AREA_TABLE_LOCALE, + + SEL_WORLD_MAP_AREA, + + SEL_WORLD_MAP_OVERLAY, + + SEL_WORLD_MAP_TRANSFORMS, + + SEL_WORLD_SAFE_LOCS, + SEL_WORLD_SAFE_LOCS_LOCALE, + + MAX_HOTFIXDATABASE_STATEMENTS + } +} diff --git a/Framework/Database/Databases/LoginDatabase.cs b/Framework/Database/Databases/LoginDatabase.cs new file mode 100644 index 000000000..60ce9f805 --- /dev/null +++ b/Framework/Database/Databases/LoginDatabase.cs @@ -0,0 +1,320 @@ +/* + * Copyright (C) 2012-2017 CypherCore + * + * 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, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +namespace Framework.Database +{ + public class LoginDatabase : MySqlBase + { + public override void PreparedStatements() + { + const string BnetAccountInfo = "ba.id, ba.email, ba.locked, ba.lock_country, ba.last_ip, ba.failed_logins, bab.unbandate > UNIX_TIMESTAMP() OR bab.unbandate = bab.bandate, bab.unbandate = bab.bandate"; + const string BnetGameAccountInfo = "a.id, a.username, ab.unbandate > UNIX_TIMESTAMP() OR ab.unbandate = ab.bandate, ab.unbandate = ab.bandate, aa.gmlevel"; + + PrepareStatement(LoginStatements.SEL_REALMLIST, "SELECT id, name, address, localAddress, localSubnetMask, port, icon, flag, timezone, allowedSecurityLevel, population, gamebuild, Region, Battlegroup FROM realmlist WHERE flag <> 3 ORDER BY name"); + PrepareStatement(LoginStatements.DEL_EXPIRED_IP_BANS, "DELETE FROM ip_banned WHERE unbandate<>bandate AND unbandate<=UNIX_TIMESTAMP()"); + PrepareStatement(LoginStatements.UPD_EXPIRED_ACCOUNT_BANS, "UPDATE account_banned SET active = 0 WHERE active = 1 AND unbandate<>bandate AND unbandate<=UNIX_TIMESTAMP()"); + PrepareStatement(LoginStatements.SEL_IP_INFO, "(SELECT unbandate > UNIX_TIMESTAMP() OR unbandate = bandate AS banned, NULL as country FROM ip_banned WHERE ip = ?) " + + "UNION (SELECT NULL AS banned, country FROM ip2nation WHERE INET_NTOA(ip) = ?)"); + + PrepareStatement(LoginStatements.INS_IP_AUTO_BANNED, "INSERT INTO ip_banned (ip, bandate, unbandate, bannedby, banreason) VALUES (?, UNIX_TIMESTAMP(), UNIX_TIMESTAMP()+?, 'Cypher Auth', 'Failed login autoban')"); + PrepareStatement(LoginStatements.SEL_IP_BANNED_ALL, "SELECT ip, bandate, unbandate, bannedby, banreason FROM ip_banned WHERE (bandate = unbandate OR unbandate > UNIX_TIMESTAMP()) ORDER BY unbandate"); + PrepareStatement(LoginStatements.SEL_IP_BANNED_BY_IP, "SELECT ip, bandate, unbandate, bannedby, banreason FROM ip_banned WHERE (bandate = unbandate OR unbandate > UNIX_TIMESTAMP()) AND ip LIKE CONCAT('%%', ?, '%%') ORDER BY unbandate"); + PrepareStatement(LoginStatements.SEL_ACCOUNT_BANNED_ALL, "SELECT account.id, username FROM account, account_banned WHERE account.id = account_banned.id AND active = 1 GROUP BY account.id"); + PrepareStatement(LoginStatements.SEL_ACCOUNT_BANNED_BY_USERNAME, "SELECT account.id, username FROM account, account_banned WHERE account.id = account_banned.id AND active = 1 AND username LIKE CONCAT('%%', ?, '%%') GROUP BY account.id"); + PrepareStatement(LoginStatements.DEL_ACCOUNT_BANNED, "DELETE FROM account_banned WHERE id = ?"); + PrepareStatement(LoginStatements.UPD_ACCOUNT_INFO_CONTINUED_SESSION, "UPDATE account SET sessionkey = ? WHERE id = ?"); + PrepareStatement(LoginStatements.SEL_ACCOUNT_INFO_CONTINUED_SESSION, "SELECT username, sessionkey FROM account WHERE id = ?"); + PrepareStatement(LoginStatements.UPD_VS, "UPDATE account SET v = ?, s = ? WHERE username = ?"); + PrepareStatement(LoginStatements.SEL_LOGON_COUNTRY, "SELECT country FROM ip2nation WHERE ip < ? ORDER BY ip DESC LIMIT 0,1"); + PrepareStatement(LoginStatements.SEL_ACCOUNT_ID_BY_NAME, "SELECT id FROM account WHERE username = ?"); + PrepareStatement(LoginStatements.SEL_ACCOUNT_LIST_BY_NAME, "SELECT id, username FROM account WHERE username = ?"); + PrepareStatement(LoginStatements.SEL_ACCOUNT_INFO_BY_NAME, "SELECT a.id, a.sessionkey, ba.last_ip, ba.locked, ba.lock_country, a.expansion, a.mutetime, ba.locale, a.recruiter, a.os, ba.id, aa.gmLevel, " + + "bab.unbandate > UNIX_TIMESTAMP() OR bab.unbandate = bab.bandate, ab.unbandate > UNIX_TIMESTAMP() OR ab.unbandate = ab.bandate, r.id " + + "FROM account a LEFT JOIN account r ON a.id = r.recruiter LEFT JOIN battlenet_accounts ba ON a.battlenet_account = ba.id " + + "LEFT JOIN account_access aa ON a.id = aa.id AND aa.RealmID IN (-1, ?) LEFT JOIN battlenet_account_bans bab ON ba.id = bab.id LEFT JOIN account_banned ab ON a.id = ab.id AND ab.active = 1 " + + "WHERE a.username = ? ORDER BY aa.RealmID DESC LIMIT 1"); + + PrepareStatement(LoginStatements.SEL_ACCOUNT_LIST_BY_EMAIL, "SELECT id, username FROM account WHERE email = ?"); + PrepareStatement(LoginStatements.SEL_ACCOUNT_BY_IP, "SELECT id, username FROM account WHERE last_ip = ?"); + PrepareStatement(LoginStatements.SEL_ACCOUNT_BY_ID, "SELECT 1 FROM account WHERE id = ?"); + PrepareStatement(LoginStatements.INS_IP_BANNED, "INSERT INTO ip_banned (ip, bandate, unbandate, bannedby, banreason) VALUES (?, UNIX_TIMESTAMP(), UNIX_TIMESTAMP()+?, ?, ?)"); + PrepareStatement(LoginStatements.DEL_IP_NOT_BANNED, "DELETE FROM ip_banned WHERE ip = ?"); + PrepareStatement(LoginStatements.INS_ACCOUNT_BANNED, "INSERT INTO account_banned VALUES (?, UNIX_TIMESTAMP(), UNIX_TIMESTAMP()+?, ?, ?, 1)"); + PrepareStatement(LoginStatements.UPD_ACCOUNT_NOT_BANNED, "UPDATE account_banned SET active = 0 WHERE id = ? AND active != 0"); + PrepareStatement(LoginStatements.DEL_REALM_CHARACTERS_BY_REALM, "DELETE FROM realmcharacters WHERE acctid = ? AND realmid = ?"); + PrepareStatement(LoginStatements.DEL_REALM_CHARACTERS, "DELETE FROM realmcharacters WHERE acctid = ?"); + PrepareStatement(LoginStatements.INS_REALM_CHARACTERS, "INSERT INTO realmcharacters (numchars, acctid, realmid) VALUES (?, ?, ?)"); + PrepareStatement(LoginStatements.SEL_SUM_REALM_CHARACTERS, "SELECT SUM(numchars) FROM realmcharacters WHERE acctid = ?"); + PrepareStatement(LoginStatements.INS_ACCOUNT, "INSERT INTO account(username, sha_pass_hash, reg_mail, email, joindate, battlenet_account, battlenet_index) VALUES(?, ?, ?, ?, NOW(), ?, ?)"); + PrepareStatement(LoginStatements.INS_REALM_CHARACTERS_INIT, "INSERT INTO realmcharacters (realmid, acctid, numchars) SELECT realmlist.id, account.id, 0 FROM realmlist, account LEFT JOIN realmcharacters ON acctid=account.id WHERE acctid IS NULL"); + PrepareStatement(LoginStatements.UPD_EXPANSION, "UPDATE account SET expansion = ? WHERE id = ?"); + PrepareStatement(LoginStatements.UPD_ACCOUNT_LOCK, "UPDATE account SET locked = ? WHERE id = ?"); + PrepareStatement(LoginStatements.UPD_ACCOUNT_LOCK_CONTRY, "UPDATE account SET lock_country = ? WHERE id = ?"); + PrepareStatement(LoginStatements.INS_LOG, "INSERT INTO logs (time, realm, type, level, string) VALUES (?, ?, ?, ?, ?)"); + PrepareStatement(LoginStatements.UPD_USERNAME, "UPDATE account SET v = 0, s = 0, username = ?, sha_pass_hash = ? WHERE id = ?"); + PrepareStatement(LoginStatements.UPD_PASSWORD, "UPDATE account SET v = 0, s = 0, sha_pass_hash = ? WHERE id = ?"); + PrepareStatement(LoginStatements.UPD_EMAIL, "UPDATE account SET email = ? WHERE id = ?"); + PrepareStatement(LoginStatements.UPD_REG_EMAIL, "UPDATE account SET reg_mail = ? WHERE id = ?"); + PrepareStatement(LoginStatements.UPD_MUTE_TIME, "UPDATE account SET mutetime = ? , mutereason = ? , muteby = ? WHERE id = ?"); + PrepareStatement(LoginStatements.UPD_MUTE_TIME_LOGIN, "UPDATE account SET mutetime = ? WHERE id = ?"); + PrepareStatement(LoginStatements.UPD_LAST_IP, "UPDATE account SET last_ip = ? WHERE username = ?"); + PrepareStatement(LoginStatements.UPD_LAST_ATTEMPT_IP, "UPDATE account SET last_attempt_ip = ? WHERE username = ?"); + PrepareStatement(LoginStatements.UPD_ACCOUNT_ONLINE, "UPDATE account SET online = 1 WHERE id = ?"); + PrepareStatement(LoginStatements.UPD_UPTIME_PLAYERS, "UPDATE uptime SET uptime = ?, maxplayers = ? WHERE realmid = ? AND starttime = ?"); + PrepareStatement(LoginStatements.DEL_OLD_LOGS, "DELETE FROM logs WHERE (time + ?) < ? AND realm = ?"); + PrepareStatement(LoginStatements.DEL_ACCOUNT_ACCESS, "DELETE FROM account_access WHERE id = ?"); + PrepareStatement(LoginStatements.DEL_ACCOUNT_ACCESS_BY_REALM, "DELETE FROM account_access WHERE id = ? AND (RealmID = ? OR RealmID = -1)"); + PrepareStatement(LoginStatements.INS_ACCOUNT_ACCESS, "INSERT INTO account_access (id,gmlevel,RealmID) VALUES (?, ?, ?)"); + PrepareStatement(LoginStatements.GET_ACCOUNT_ID_BY_USERNAME, "SELECT id FROM account WHERE username = ?"); + PrepareStatement(LoginStatements.GET_ACCOUNT_ACCESS_GMLEVEL, "SELECT gmlevel FROM account_access WHERE id = ?"); + PrepareStatement(LoginStatements.GET_GMLEVEL_BY_REALMID, "SELECT gmlevel FROM account_access WHERE id = ? AND (RealmID = ? OR RealmID = -1)"); + PrepareStatement(LoginStatements.GET_USERNAME_BY_ID, "SELECT username FROM account WHERE id = ?"); + PrepareStatement(LoginStatements.SEL_CHECK_PASSWORD, "SELECT 1 FROM account WHERE id = ? AND sha_pass_hash = ?"); + PrepareStatement(LoginStatements.SEL_CHECK_PASSWORD_BY_NAME, "SELECT 1 FROM account WHERE username = ? AND sha_pass_hash = ?"); + PrepareStatement(LoginStatements.SEL_PINFO, "SELECT a.username, aa.gmlevel, a.email, a.reg_mail, a.last_ip, DATE_FORMAT(a.last_login, '%Y-%m-%d %T'), a.mutetime, a.mutereason, a.muteby, a.failed_logins, a.locked, a.OS FROM account a LEFT JOIN account_access aa ON (a.id = aa.id AND (aa.RealmID = ? OR aa.RealmID = -1)) WHERE a.id = ?"); + PrepareStatement(LoginStatements.SEL_PINFO_BANS, "SELECT unbandate, bandate = unbandate, bannedby, banreason FROM account_banned WHERE id = ? AND active ORDER BY bandate ASC LIMIT 1"); + PrepareStatement(LoginStatements.SEL_GM_ACCOUNTS, "SELECT a.username, aa.gmlevel FROM account a, account_access aa WHERE a.id=aa.id AND aa.gmlevel >= ? AND (aa.realmid = -1 OR aa.realmid = ?)"); + PrepareStatement(LoginStatements.SEL_ACCOUNT_INFO, "SELECT a.username, a.last_ip, aa.gmlevel, a.expansion FROM account a LEFT JOIN account_access aa ON (a.id = aa.id) WHERE a.id = ?"); + PrepareStatement(LoginStatements.SEL_ACCOUNT_ACCESS_GMLEVEL_TEST, "SELECT 1 FROM account_access WHERE id = ? AND gmlevel > ?"); + PrepareStatement(LoginStatements.SEL_ACCOUNT_ACCESS, "SELECT a.id, aa.gmlevel, aa.RealmID FROM account a LEFT JOIN account_access aa ON (a.id = aa.id) WHERE a.username = ?"); + PrepareStatement(LoginStatements.SEL_ACCOUNT_WHOIS, "SELECT username, email, last_ip FROM account WHERE id = ?"); + PrepareStatement(LoginStatements.SEL_LAST_ATTEMPT_IP, "SELECT last_attempt_ip FROM account WHERE id = ?"); + PrepareStatement(LoginStatements.SEL_LAST_IP, "SELECT last_ip FROM account WHERE id = ?"); + PrepareStatement(LoginStatements.SEL_REALMLIST_SECURITY_LEVEL, "SELECT allowedSecurityLevel from realmlist WHERE id = ?"); + PrepareStatement(LoginStatements.DEL_ACCOUNT, "DELETE FROM account WHERE id = ?"); + PrepareStatement(LoginStatements.SEL_IP2NATION_COUNTRY, "SELECT c.country FROM ip2nationCountries c, ip2nation i WHERE i.ip < ? AND c.code = i.country ORDER BY i.ip DESC LIMIT 0,1"); + PrepareStatement(LoginStatements.SEL_AUTOBROADCAST, "SELECT id, weight, text FROM autobroadcast WHERE realmid = ? OR realmid = -1"); + PrepareStatement(LoginStatements.GET_EMAIL_BY_ID, "SELECT email FROM account WHERE id = ?"); + // 0: uint32, 1: uint32, 2: uint8, 3: uint32, 4: string // Complete name: "Login_Insert_AccountLoginDeLete_IP_Logging" + PrepareStatement(LoginStatements.INS_ALDL_IP_LOGGING, "INSERT INTO logs_ip_actions (account_id,character_guid,type,ip,systemnote,unixtime,time) VALUES (?, ?, ?, (SELECT last_ip FROM account WHERE id = ?), ?, unix_timestamp(NOW()), NOW())"); + // 0: uint32, 1: uint32, 2: uint8, 3: uint32, 4: string // Complete name: "Login_Insert_FailedAccountLogin_IP_Logging" + PrepareStatement(LoginStatements.INS_FACL_IP_LOGGING, "INSERT INTO logs_ip_actions (account_id,character_guid,type,ip,systemnote,unixtime,time) VALUES (?, ?, ?, (SELECT last_attempt_ip FROM account WHERE id = ?), ?, unix_timestamp(NOW()), NOW())"); + // 0: uint32, 1: uint32, 2: uint8, 3: string, 4: string // Complete name: "Login_Insert_CharacterDelete_IP_Logging" + PrepareStatement(LoginStatements.INS_CHAR_IP_LOGGING, "INSERT INTO logs_ip_actions (account_id,character_guid,type,ip,systemnote,unixtime,time) VALUES (?, ?, ?, ?, ?, unix_timestamp(NOW()), NOW())"); + // 0: string, 1: string, 2: string // Complete name: "Login_Insert_Failed_Account_Login_due_password_IP_Logging" + PrepareStatement(LoginStatements.INS_FALP_IP_LOGGING, "INSERT INTO logs_ip_actions (account_id,character_guid,type,ip,systemnote,unixtime,time) VALUES (?, 0, 1, ?, ?, unix_timestamp(NOW()), NOW())"); + PrepareStatement(LoginStatements.SEL_ACCOUNT_ACCESS_BY_ID, "SELECT gmlevel, RealmID FROM account_access WHERE id = ? and (RealmID = ? OR RealmID = -1) ORDER BY gmlevel desc"); + + PrepareStatement(LoginStatements.SEL_RBAC_ACCOUNT_PERMISSIONS, "SELECT permissionId, granted FROM rbac_account_permissions WHERE accountId = ? AND (realmId = ? OR realmId = -1) ORDER BY permissionId, realmId"); + PrepareStatement(LoginStatements.INS_RBAC_ACCOUNT_PERMISSION, "INSERT INTO rbac_account_permissions (accountId, permissionId, granted, realmId) VALUES (?, ?, ?, ?) ON DUPLICATE KEY UPDATE granted = VALUES(granted)"); + PrepareStatement(LoginStatements.DEL_RBAC_ACCOUNT_PERMISSION, "DELETE FROM rbac_account_permissions WHERE accountId = ? AND permissionId = ? AND (realmId = ? OR realmId = -1)"); + + PrepareStatement(LoginStatements.INS_ACCOUNT_MUTE, "INSERT INTO account_muted VALUES (?, UNIX_TIMESTAMP(), ?, ?, ?)"); + PrepareStatement(LoginStatements.SEL_ACCOUNT_MUTE_INFO, "SELECT mutedate, mutetime, mutereason, mutedby FROM account_muted WHERE guid = ? ORDER BY mutedate ASC"); + PrepareStatement(LoginStatements.DEL_ACCOUNT_MUTED, "DELETE FROM account_muted WHERE guid = ?"); + + PrepareStatement(LoginStatements.SEL_BNET_ACCOUNT_INFO, "SELECT " + BnetAccountInfo + ", " + BnetGameAccountInfo + ", ba.sha_pass_hash" + + " FROM battlenet_accounts ba LEFT JOIN battlenet_account_bans bab ON ba.id = bab.id LEFT JOIN account a ON ba.id = a.battlenet_account" + + " LEFT JOIN account_banned ab ON a.id = ab.id AND ab.active = 1 LEFT JOIN account_access aa ON a.id = aa.id AND aa.RealmID = -1 WHERE ba.email = ? ORDER BY a.id"); + PrepareStatement(LoginStatements.UPD_BNET_LAST_LOGIN_INFO, "UPDATE battlenet_accounts SET last_ip = ?, last_login = NOW(), locale = ?, failed_logins = 0, os = ? WHERE id = ?"); + PrepareStatement(LoginStatements.UPD_BNET_GAME_ACCOUNT_LOGIN_INFO, "UPDATE account SET sessionkey = ?, last_ip = ?, last_login = NOW(), locale = ?, failed_logins = 0, os = ? WHERE username = ?"); + PrepareStatement(LoginStatements.SEL_BNET_CHARACTER_COUNTS_BY_ACCOUNT_ID, "SELECT rc.acctid, rc.numchars, r.id, r.Region, r.Battlegroup FROM realmcharacters rc INNER JOIN realmlist r ON rc.realmid = r.id WHERE rc.acctid = ?"); + PrepareStatement(LoginStatements.SEL_BNET_CHARACTER_COUNTS_BY_BNET_ID, "SELECT rc.acctid, rc.numchars, r.id, r.Region, r.Battlegroup FROM realmcharacters rc INNER JOIN realmlist r ON rc.realmid = r.id LEFT JOIN account a ON rc.acctid = a.id WHERE a.battlenet_account = ?"); + PrepareStatement(LoginStatements.SEL_BNET_LAST_PLAYER_CHARACTERS, "SELECT lpc.accountId, lpc.region, lpc.battlegroup, lpc.realmId, lpc.characterName, lpc.characterGUID, lpc.lastPlayedTime FROM account_last_played_character lpc LEFT JOIN account a ON lpc.accountId = a.id WHERE a.battlenet_account = ?"); + PrepareStatement(LoginStatements.DEL_BNET_LAST_PLAYER_CHARACTERS, "DELETE FROM account_last_played_character WHERE accountId = ? AND region = ? AND battlegroup = ?"); + PrepareStatement(LoginStatements.INS_BNET_LAST_PLAYER_CHARACTERS, "INSERT INTO account_last_played_character (accountId, region, battlegroup, realmId, characterName, characterGUID, lastPlayedTime) VALUES (?,?,?,?,?,?,?)"); + PrepareStatement(LoginStatements.INS_BNET_ACCOUNT, "INSERT INTO battlenet_accounts (`email`,`sha_pass_hash`) VALUES (?, ?)"); + PrepareStatement(LoginStatements.SEL_BNET_ACCOUNT_EMAIL_BY_ID, "SELECT email FROM battlenet_accounts WHERE id = ?"); + PrepareStatement(LoginStatements.SEL_BNET_ACCOUNT_ID_BY_EMAIL, "SELECT id FROM battlenet_accounts WHERE email = ?"); + PrepareStatement(LoginStatements.UPD_BNET_PASSWORD, "UPDATE battlenet_accounts SET sha_pass_hash = ? WHERE id = ?"); + PrepareStatement(LoginStatements.SEL_BNET_CHECK_PASSWORD, "SELECT 1 FROM battlenet_accounts WHERE id = ? AND sha_pass_hash = ?"); + PrepareStatement(LoginStatements.UPD_BNET_ACCOUNT_LOCK, "UPDATE battlenet_accounts SET locked = ? WHERE id = ?"); + PrepareStatement(LoginStatements.UPD_BNET_ACCOUNT_LOCK_CONTRY, "UPDATE battlenet_accounts SET lock_country = ? WHERE id = ?"); + PrepareStatement(LoginStatements.SEL_BNET_ACCOUNT_ID_BY_GAME_ACCOUNT, "SELECT battlenet_account FROM account WHERE id = ?"); + PrepareStatement(LoginStatements.UPD_BNET_GAME_ACCOUNT_LINK, "UPDATE account SET battlenet_account = ?, battlenet_index = ? WHERE id = ?"); + PrepareStatement(LoginStatements.SEL_BNET_MAX_ACCOUNT_INDEX, "SELECT MAX(battlenet_index) FROM account WHERE battlenet_account = ?"); + PrepareStatement(LoginStatements.SEL_BNET_GAME_ACCOUNT_LIST, "SELECT a.id, a.username FROM account a LEFT JOIN battlenet_accounts ba ON a.battlenet_account = ba.id WHERE ba.email = ?"); + + PrepareStatement(LoginStatements.UPD_BNET_FAILED_LOGINS, "UPDATE battlenet_accounts SET failed_logins = failed_logins + 1 WHERE id = ?"); + PrepareStatement(LoginStatements.INS_BNET_ACCOUNT_AUTO_BANNED, "INSERT INTO battlenet_account_bans(id, bandate, unbandate, bannedby, banreason) VALUES(?, UNIX_TIMESTAMP(), UNIX_TIMESTAMP()+?, 'Trinity Auth', 'Failed login autoban')"); + PrepareStatement(LoginStatements.DEL_BNET_EXPIRED_ACCOUNT_BANNED, "DELETE FROM battlenet_account_bans WHERE unbandate<>bandate AND unbandate<=UNIX_TIMESTAMP()"); + PrepareStatement(LoginStatements.UPD_BNET_RESET_FAILED_LOGINS, "UPDATE battlenet_accounts SET failed_logins = 0 WHERE id = ?"); + + PrepareStatement(LoginStatements.SEL_LAST_CHAR_UNDELETE, "SELECT LastCharacterUndelete FROM battlenet_accounts WHERE Id = ?"); + PrepareStatement(LoginStatements.UPD_LAST_CHAR_UNDELETE, "UPDATE battlenet_accounts SET LastCharacterUndelete = UNIX_TIMESTAMP() WHERE Id = ?"); + + // Account wide toys + PrepareStatement(LoginStatements.SEL_ACCOUNT_TOYS, "SELECT itemId, isFavourite FROM battlenet_account_toys WHERE accountId = ?"); + PrepareStatement(LoginStatements.REP_ACCOUNT_TOYS, "REPLACE INTO battlenet_account_toys (accountId, itemId, isFavourite) VALUES (?, ?, ?)"); + + // Battle Pets + PrepareStatement(LoginStatements.SEL_BATTLE_PETS, "SELECT guid, species, breed, level, exp, health, quality, flags, name FROM battle_pets WHERE battlenetAccountId = ?"); + PrepareStatement(LoginStatements.INS_BATTLE_PETS, "INSERT INTO battle_pets (guid, battlenetAccountId, species, breed, level, exp, health, quality, flags, name) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)"); + PrepareStatement(LoginStatements.DEL_BATTLE_PETS, "DELETE FROM battle_pets WHERE battlenetAccountId = ? AND guid = ?"); + PrepareStatement(LoginStatements.UPD_BATTLE_PETS, "UPDATE battle_pets SET level = ?, exp = ?, health = ?, quality = ?, flags = ?, name = ? WHERE battlenetAccountId = ? AND guid = ?"); + PrepareStatement(LoginStatements.SEL_BATTLE_PET_SLOTS, "SELECT id, battlePetGuid, locked FROM battle_pet_slots WHERE battlenetAccountId = ?"); + PrepareStatement(LoginStatements.INS_BATTLE_PET_SLOTS, "INSERT INTO battle_pet_slots (id, battlenetAccountId, battlePetGuid, locked) VALUES (?, ?, ?, ?)"); + PrepareStatement(LoginStatements.DEL_BATTLE_PET_SLOTS, "DELETE FROM battle_pet_slots WHERE battlenetAccountId = ?"); + + PrepareStatement(LoginStatements.SEL_ACCOUNT_HEIRLOOMS, "SELECT itemId, flags FROM battlenet_account_heirlooms WHERE accountId = ?"); + PrepareStatement(LoginStatements.REP_ACCOUNT_HEIRLOOMS, "REPLACE INTO battlenet_account_heirlooms (accountId, itemId, flags) VALUES (?, ?, ?)"); + + // Account wide mounts + PrepareStatement(LoginStatements.SEL_ACCOUNT_MOUNTS, "SELECT mountSpellId, flags FROM battlenet_account_mounts WHERE battlenetAccountId = ?"); + PrepareStatement(LoginStatements.REP_ACCOUNT_MOUNTS, "REPLACE INTO battlenet_account_mounts (battlenetAccountId, mountSpellId, flags) VALUES (?, ?, ?)"); + + // Transmog collection + PrepareStatement(LoginStatements.SEL_BNET_ITEM_APPEARANCES, "SELECT blobIndex, appearanceMask FROM battlenet_item_appearances WHERE battlenetAccountId = ? ORDER BY blobIndex DESC"); + PrepareStatement(LoginStatements.INS_BNET_ITEM_APPEARANCES, "INSERT INTO battlenet_item_appearances (battlenetAccountId, blobIndex, appearanceMask) VALUES (?, ?, ?) " + + "ON DUPLICATE KEY UPDATE appearanceMask = appearanceMask | VALUES(appearanceMask)"); + PrepareStatement(LoginStatements.SEL_BNET_ITEM_FAVORITE_APPEARANCES, "SELECT itemModifiedAppearanceId FROM battlenet_item_favorite_appearances WHERE battlenetAccountId = ?"); + PrepareStatement(LoginStatements.INS_BNET_ITEM_FAVORITE_APPEARANCE, "INSERT INTO battlenet_item_favorite_appearances (battlenetAccountId, itemModifiedAppearanceId) VALUES (?, ?)"); + PrepareStatement(LoginStatements.DEL_BNET_ITEM_FAVORITE_APPEARANCE, "DELETE FROM battlenet_item_favorite_appearances WHERE battlenetAccountId = ? AND itemModifiedAppearanceId = ?"); + } + } + + public enum LoginStatements + { + SEL_REALMLIST, + DEL_EXPIRED_IP_BANS, + UPD_EXPIRED_ACCOUNT_BANS, + SEL_IP_INFO, + INS_IP_AUTO_BANNED, + SEL_ACCOUNT_BANNED_ALL, + SEL_ACCOUNT_BANNED_BY_USERNAME, + DEL_ACCOUNT_BANNED, + UPD_ACCOUNT_INFO_CONTINUED_SESSION, + SEL_ACCOUNT_INFO_CONTINUED_SESSION, + UPD_VS, + SEL_LOGON_COUNTRY, + SEL_ACCOUNT_ID_BY_NAME, + SEL_ACCOUNT_LIST_BY_NAME, + SEL_ACCOUNT_INFO_BY_NAME, + SEL_ACCOUNT_LIST_BY_EMAIL, + SEL_ACCOUNT_BY_IP, + INS_IP_BANNED, + DEL_IP_NOT_BANNED, + SEL_IP_BANNED_ALL, + SEL_IP_BANNED_BY_IP, + SEL_ACCOUNT_BY_ID, + INS_ACCOUNT_BANNED, + UPD_ACCOUNT_NOT_BANNED, + DEL_REALM_CHARACTERS_BY_REALM, + DEL_REALM_CHARACTERS, + INS_REALM_CHARACTERS, + SEL_SUM_REALM_CHARACTERS, + INS_ACCOUNT, + INS_REALM_CHARACTERS_INIT, + UPD_EXPANSION, + UPD_ACCOUNT_LOCK, + UPD_ACCOUNT_LOCK_CONTRY, + INS_LOG, + UPD_USERNAME, + UPD_PASSWORD, + UPD_EMAIL, + UPD_REG_EMAIL, + UPD_MUTE_TIME, + UPD_MUTE_TIME_LOGIN, + UPD_LAST_IP, + UPD_LAST_ATTEMPT_IP, + UPD_ACCOUNT_ONLINE, + UPD_UPTIME_PLAYERS, + DEL_OLD_LOGS, + DEL_ACCOUNT_ACCESS, + DEL_ACCOUNT_ACCESS_BY_REALM, + INS_ACCOUNT_ACCESS, + GET_ACCOUNT_ID_BY_USERNAME, + GET_ACCOUNT_ACCESS_GMLEVEL, + GET_GMLEVEL_BY_REALMID, + GET_USERNAME_BY_ID, + SEL_CHECK_PASSWORD, + SEL_CHECK_PASSWORD_BY_NAME, + SEL_PINFO, + SEL_PINFO_BANS, + SEL_GM_ACCOUNTS, + SEL_ACCOUNT_INFO, + SEL_ACCOUNT_ACCESS_GMLEVEL_TEST, + SEL_ACCOUNT_ACCESS, + SEL_ACCOUNT_RECRUITER, + SEL_BANS, + SEL_ACCOUNT_WHOIS, + SEL_REALMLIST_SECURITY_LEVEL, + DEL_ACCOUNT, + SEL_IP2NATION_COUNTRY, + SEL_AUTOBROADCAST, + SEL_LAST_ATTEMPT_IP, + SEL_LAST_IP, + GET_EMAIL_BY_ID, + INS_ALDL_IP_LOGGING, + INS_FACL_IP_LOGGING, + INS_CHAR_IP_LOGGING, + INS_FALP_IP_LOGGING, + + SEL_ACCOUNT_ACCESS_BY_ID, + SEL_RBAC_ACCOUNT_PERMISSIONS, + INS_RBAC_ACCOUNT_PERMISSION, + DEL_RBAC_ACCOUNT_PERMISSION, + + INS_ACCOUNT_MUTE, + SEL_ACCOUNT_MUTE_INFO, + DEL_ACCOUNT_MUTED, + + SEL_BNET_ACCOUNT_INFO, + UPD_BNET_LAST_LOGIN_INFO, + UPD_BNET_GAME_ACCOUNT_LOGIN_INFO, + SEL_BNET_CHARACTER_COUNTS_BY_ACCOUNT_ID, + SEL_BNET_CHARACTER_COUNTS_BY_BNET_ID, + SEL_BNET_LAST_PLAYER_CHARACTERS, + DEL_BNET_LAST_PLAYER_CHARACTERS, + INS_BNET_LAST_PLAYER_CHARACTERS, + INS_BNET_ACCOUNT, + SEL_BNET_ACCOUNT_EMAIL_BY_ID, + SEL_BNET_ACCOUNT_ID_BY_EMAIL, + UPD_BNET_PASSWORD, + SEL_BNET_ACCOUNT_SALT_BY_ID, + SEL_BNET_CHECK_PASSWORD, + UPD_BNET_ACCOUNT_LOCK, + UPD_BNET_ACCOUNT_LOCK_CONTRY, + SEL_BNET_ACCOUNT_ID_BY_GAME_ACCOUNT, + UPD_BNET_GAME_ACCOUNT_LINK, + SEL_BNET_MAX_ACCOUNT_INDEX, + SEL_BNET_GAME_ACCOUNT_LIST, + + UPD_BNET_FAILED_LOGINS, + INS_BNET_ACCOUNT_AUTO_BANNED, + DEL_BNET_EXPIRED_ACCOUNT_BANNED, + UPD_BNET_RESET_FAILED_LOGINS, + + SEL_LAST_CHAR_UNDELETE, + UPD_LAST_CHAR_UNDELETE, + + SEL_ACCOUNT_TOYS, + REP_ACCOUNT_TOYS, + + SEL_BATTLE_PETS, + INS_BATTLE_PETS, + DEL_BATTLE_PETS, + UPD_BATTLE_PETS, + SEL_BATTLE_PET_SLOTS, + INS_BATTLE_PET_SLOTS, + DEL_BATTLE_PET_SLOTS, + + SEL_ACCOUNT_HEIRLOOMS, + REP_ACCOUNT_HEIRLOOMS, + + SEL_ACCOUNT_MOUNTS, + REP_ACCOUNT_MOUNTS, + + SEL_BNET_ITEM_APPEARANCES, + INS_BNET_ITEM_APPEARANCES, + SEL_BNET_ITEM_FAVORITE_APPEARANCES, + INS_BNET_ITEM_FAVORITE_APPEARANCE, + DEL_BNET_ITEM_FAVORITE_APPEARANCE, + + MAX_LOGINDATABASE_STATEMENTS + } +} diff --git a/Framework/Database/Databases/WorldDatabase.cs b/Framework/Database/Databases/WorldDatabase.cs new file mode 100644 index 000000000..b7e5e303a --- /dev/null +++ b/Framework/Database/Databases/WorldDatabase.cs @@ -0,0 +1,171 @@ +/* + * Copyright (C) 2012-2017 CypherCore + * + * 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, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +namespace Framework.Database +{ + public class WorldDatabase : MySqlBase + { + public override void PreparedStatements() + { + PrepareStatement(WorldStatements.SEL_QUEST_POOLS, "SELECT entry, pool_entry FROM pool_quest"); + PrepareStatement(WorldStatements.DEL_CRELINKED_RESPAWN, "DELETE FROM linked_respawn WHERE guid = ?"); + PrepareStatement(WorldStatements.REP_CREATURE_LINKED_RESPAWN, "REPLACE INTO linked_respawn (guid, linkedGuid) VALUES (?, ?)"); + PrepareStatement(WorldStatements.SEL_CREATURE_TEXT, "SELECT entry, groupid, id, text, type, language, probability, emote, duration, sound, BroadcastTextID, TextRange FROM creature_text"); + PrepareStatement(WorldStatements.SEL_SMART_SCRIPTS, "SELECT entryorguid, source_type, id, link, event_type, event_phase_mask, event_chance, event_flags, event_param1, event_param2, event_param3, event_param4, event_param_string, action_type, action_param1, action_param2, action_param3, action_param4, action_param5, action_param6, target_type, target_param1, target_param2, target_param3, target_x, target_y, target_z, target_o FROM smart_scripts ORDER BY entryorguid, source_type, id, link"); + PrepareStatement(WorldStatements.SEL_SMARTAI_WP, "SELECT entry, pointid, position_x, position_y, position_z FROM waypoints ORDER BY entry, pointid"); + PrepareStatement(WorldStatements.DEL_GAMEOBJECT, "DELETE FROM gameobject WHERE guid = ?"); + PrepareStatement(WorldStatements.DEL_EVENT_GAMEOBJECT, "DELETE FROM game_event_gameobject WHERE guid = ?"); + PrepareStatement(WorldStatements.INS_GRAVEYARD_ZONE, "INSERT INTO graveyard_zone (ID, GhostZone, faction) VALUES (?, ?, ?)"); + PrepareStatement(WorldStatements.DEL_GRAVEYARD_ZONE, "DELETE FROM graveyard_zone WHERE ID = ? AND GhostZone = ? AND faction = ?"); + PrepareStatement(WorldStatements.INS_GAME_TELE, "INSERT INTO game_tele (id, position_x, position_y, position_z, orientation, map, name) VALUES (?, ?, ?, ?, ?, ?, ?)"); + PrepareStatement(WorldStatements.DEL_GAME_TELE, "DELETE FROM game_tele WHERE name = ?"); + PrepareStatement(WorldStatements.INS_NPC_VENDOR, "INSERT INTO npc_vendor (entry, item, maxcount, incrtime, extendedcost, type) VALUES(?, ?, ?, ?, ?, ?)"); + PrepareStatement(WorldStatements.DEL_NPC_VENDOR, "DELETE FROM npc_vendor WHERE entry = ? AND item = ? AND type = ?"); + PrepareStatement(WorldStatements.SEL_NPC_VENDOR_REF, "SELECT item, maxcount, incrtime, ExtendedCost, type FROM npc_vendor WHERE entry = ? AND type = ? ORDER BY slot ASC"); + PrepareStatement(WorldStatements.UPD_CREATURE_MOVEMENT_TYPE, "UPDATE creature SET MovementType = ? WHERE guid = ?"); + PrepareStatement(WorldStatements.UPD_CREATURE_FACTION, "UPDATE creature_template SET faction = ? WHERE entry = ?"); + PrepareStatement(WorldStatements.UPD_CREATURE_NPCFLAG, "UPDATE creature_template SET npcflag = ? WHERE entry = ?"); + PrepareStatement(WorldStatements.UPD_CREATURE_POSITION, "UPDATE creature SET position_x = ?, position_y = ?, position_z = ?, orientation = ? WHERE guid = ?"); + PrepareStatement(WorldStatements.UPD_CREATURE_SPAWN_DISTANCE, "UPDATE creature SET spawndist = ?, MovementType = ? WHERE guid = ?"); + PrepareStatement(WorldStatements.UPD_CREATURE_SPAWN_TIME_SECS, "UPDATE creature SET spawntimesecs = ? WHERE guid = ?"); + PrepareStatement(WorldStatements.INS_CREATURE_FORMATION, "INSERT INTO creature_formations (leaderGUID, memberGUID, dist, angle, groupAI) VALUES (?, ?, ?, ?, ?)"); + PrepareStatement(WorldStatements.INS_WAYPOINT_DATA, "INSERT INTO waypoint_data (id, point, position_x, position_y, position_z) VALUES (?, ?, ?, ?, ?)"); + PrepareStatement(WorldStatements.DEL_WAYPOINT_DATA, "DELETE FROM waypoint_data WHERE id = ? AND point = ?"); + PrepareStatement(WorldStatements.UPD_WAYPOINT_DATA_POINT, "UPDATE waypoint_data SET point = point - 1 WHERE id = ? AND point > ?"); + PrepareStatement(WorldStatements.UPD_WAYPOINT_DATA_POSITION, "UPDATE waypoint_data SET position_x = ?, position_y = ?, position_z = ? where id = ? AND point = ?"); + PrepareStatement(WorldStatements.UPD_WAYPOINT_DATA_WPGUID, "UPDATE waypoint_data SET wpguid = ? WHERE id = ? and point = ?"); + PrepareStatement(WorldStatements.SEL_WAYPOINT_DATA_MAX_ID, "SELECT MAX(id) FROM waypoint_data"); + PrepareStatement(WorldStatements.SEL_WAYPOINT_DATA_MAX_POINT, "SELECT MAX(point) FROM waypoint_data WHERE id = ?"); + PrepareStatement(WorldStatements.SEL_WAYPOINT_DATA_BY_ID, "SELECT point, position_x, position_y, position_z, orientation, move_type, delay, action, action_chance FROM waypoint_data WHERE id = ? ORDER BY point"); + PrepareStatement(WorldStatements.SEL_WAYPOINT_DATA_POS_BY_ID, "SELECT point, position_x, position_y, position_z FROM waypoint_data WHERE id = ?"); + PrepareStatement(WorldStatements.SEL_WAYPOINT_DATA_POS_FIRST_BY_ID, "SELECT position_x, position_y, position_z FROM waypoint_data WHERE point = 1 AND id = ?"); + PrepareStatement(WorldStatements.SEL_WAYPOINT_DATA_POS_LAST_BY_ID, "SELECT position_x, position_y, position_z, orientation FROM waypoint_data WHERE id = ? ORDER BY point DESC LIMIT 1"); + PrepareStatement(WorldStatements.SEL_WAYPOINT_DATA_BY_WPGUID, "SELECT id, point FROM waypoint_data WHERE wpguid = ?"); + PrepareStatement(WorldStatements.SEL_WAYPOINT_DATA_ALL_BY_WPGUID, "SELECT id, point, delay, move_type, action, action_chance FROM waypoint_data WHERE wpguid = ?"); + PrepareStatement(WorldStatements.UPD_WAYPOINT_DATA_ALL_WPGUID, "UPDATE waypoint_data SET wpguid = 0"); + PrepareStatement(WorldStatements.SEL_WAYPOINT_DATA_BY_POS, "SELECT id, point FROM waypoint_data WHERE (abs(position_x - ?) <= ?) and (abs(position_y - ?) <= ?) and (abs(position_z - ?) <= ?)"); + PrepareStatement(WorldStatements.SEL_WAYPOINT_DATA_WPGUID_BY_ID, "SELECT wpguid FROM waypoint_data WHERE id = ? and wpguid <> 0"); + PrepareStatement(WorldStatements.SEL_WAYPOINT_DATA_ACTION, "SELECT DISTINCT action FROM waypoint_data"); + PrepareStatement(WorldStatements.SEL_WAYPOINT_SCRIPTS_MAX_ID, "SELECT MAX(guid) FROM waypoint_scripts"); + PrepareStatement(WorldStatements.INS_CREATURE_ADDON, "INSERT INTO creature_addon(guid, path_id) VALUES (?, ?)"); + PrepareStatement(WorldStatements.UPD_CREATURE_ADDON_PATH, "UPDATE creature_addon SET path_id = ? WHERE guid = ?"); + PrepareStatement(WorldStatements.DEL_CREATURE_ADDON, "DELETE FROM creature_addon WHERE guid = ?"); + PrepareStatement(WorldStatements.SEL_CREATURE_ADDON_BY_GUID, "SELECT guid FROM creature_addon WHERE guid = ?"); + PrepareStatement(WorldStatements.INS_WAYPOINT_SCRIPT, "INSERT INTO waypoint_scripts (guid) VALUES (?)"); + PrepareStatement(WorldStatements.DEL_WAYPOINT_SCRIPT, "DELETE FROM waypoint_scripts WHERE guid = ?"); + PrepareStatement(WorldStatements.UPD_WAYPOINT_SCRIPT_ID, "UPDATE waypoint_scripts SET id = ? WHERE guid = ?"); + PrepareStatement(WorldStatements.UPD_WAYPOINT_SCRIPT_X, "UPDATE waypoint_scripts SET x = ? WHERE guid = ?"); + PrepareStatement(WorldStatements.UPD_WAYPOINT_SCRIPT_Y, "UPDATE waypoint_scripts SET y = ? WHERE guid = ?"); + PrepareStatement(WorldStatements.UPD_WAYPOINT_SCRIPT_Z, "UPDATE waypoint_scripts SET z = ? WHERE guid = ?"); + PrepareStatement(WorldStatements.UPD_WAYPOINT_SCRIPT_O, "UPDATE waypoint_scripts SET o = ? WHERE guid = ?"); + PrepareStatement(WorldStatements.SEL_WAYPOINT_SCRIPT_ID_BY_GUID, "SELECT id FROM waypoint_scripts WHERE guid = ?"); + PrepareStatement(WorldStatements.DEL_CREATURE, "DELETE FROM creature WHERE guid = ?"); + PrepareStatement(WorldStatements.SEL_COMMANDS, "SELECT name, permission, help FROM command"); + PrepareStatement(WorldStatements.SEL_CREATURE_TEMPLATE, "SELECT entry, difficulty_entry_1, difficulty_entry_2, difficulty_entry_3, KillCredit1, KillCredit2, modelid1, modelid2, modelid3, modelid4, name, femaleName, subname, IconName, gossip_menu_id, minlevel, maxlevel, HealthScalingExpansion, RequiredExpansion, VignetteID, faction, npcflag, speed_walk, speed_run, scale, rank, dmgschool, BaseAttackTime, RangeAttackTime, BaseVariance, RangeVariance, unit_class, unit_flags, unit_flags2, unit_flags3, dynamicflags, family, trainer_type, trainer_class, trainer_race, type, type_flags, type_flags2, lootid, pickpocketloot, skinloot, resistance1, resistance2, resistance3, resistance4, resistance5, resistance6, spell1, spell2, spell3, spell4, spell5, spell6, spell7, spell8, VehicleId, mingold, maxgold, AIName, MovementType, InhabitType, HoverHeight, HealthModifier, HealthModifierExtra, ManaModifier, ManaModifierExtra, ArmorModifier, DamageModifier, ExperienceModifier, RacialLeader, movementId, RegenHealth, mechanic_immune_mask, flags_extra, ScriptName FROM creature_template WHERE entry = ?"); + PrepareStatement(WorldStatements.SEL_WAYPOINT_SCRIPT_BY_ID, "SELECT guid, delay, command, datalong, datalong2, dataint, x, y, z, o FROM waypoint_scripts WHERE id = ?"); + PrepareStatement(WorldStatements.SEL_CREATURE_BY_ID, "SELECT guid FROM creature WHERE id = ?"); + PrepareStatement(WorldStatements.SEL_GAMEOBJECT_NEAREST, "SELECT guid, id, position_x, position_y, position_z, map, (POW(position_x - ?, 2) + POW(position_y - ?, 2) + POW(position_z - ?, 2)) AS order_ FROM gameobject WHERE map = ? AND (POW(position_x - ?, 2) + POW(position_y - ?, 2) + POW(position_z - ?, 2)) <= ? ORDER BY order_"); + PrepareStatement(WorldStatements.SEL_CREATURE_NEAREST, "SELECT guid, id, position_x, position_y, position_z, map, (POW(position_x - ?, 2) + POW(position_y - ?, 2) + POW(position_z - ?, 2)) AS order_ FROM creature WHERE map = ? AND (POW(position_x - ?, 2) + POW(position_y - ?, 2) + POW(position_z - ?, 2)) <= ? ORDER BY order_"); + PrepareStatement(WorldStatements.INS_CREATURE, "INSERT INTO creature (guid, id , map, spawnMask, PhaseId, PhaseGroup, modelid, equipment_id, position_x, position_y, position_z, orientation, spawntimesecs, spawndist, currentwaypoint, curhealth, curmana, MovementType, npcflag, unit_flags, unit_flags2, unit_flags3, dynamicflags) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)"); + PrepareStatement(WorldStatements.DEL_GAME_EVENT_CREATURE, "DELETE FROM game_event_creature WHERE guid = ?"); + PrepareStatement(WorldStatements.DEL_GAME_EVENT_MODEL_EQUIP, "DELETE FROM game_event_model_equip WHERE guid = ?"); + PrepareStatement(WorldStatements.INS_GAMEOBJECT, "INSERT INTO gameobject (guid, id, map, spawnMask, position_x, position_y, position_z, orientation, rotation0, rotation1, rotation2, rotation3, spawntimesecs, animprogress, state) VALUES ( ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)"); + PrepareStatement(WorldStatements.INS_DISABLES, "INSERT INTO disables (entry, sourceType, flags, comment) VALUES (?, ?, ?, ?)"); + PrepareStatement(WorldStatements.SEL_DISABLES, "SELECT entry FROM disables WHERE entry = ? AND sourceType = ?"); + PrepareStatement(WorldStatements.DEL_DISABLES, "DELETE FROM disables WHERE entry = ? AND sourceType = ?"); + PrepareStatement(WorldStatements.UPD_CREATURE_ZONE_AREA_DATA, "UPDATE creature SET zoneId = ?, areaId = ? WHERE guid = ?"); + PrepareStatement(WorldStatements.UPD_GAMEOBJECT_ZONE_AREA_DATA, "UPDATE gameobject SET zoneId = ?, areaId = ? WHERE guid = ?"); + PrepareStatement(WorldStatements.SEL_GUILD_REWARDS_REQ_ACHIEVEMENTS, "SELECT AchievementRequired FROM guild_rewards_req_achievements WHERE ItemID = ?"); + } + } + + public enum WorldStatements + { + SEL_QUEST_POOLS, + DEL_CRELINKED_RESPAWN, + REP_CREATURE_LINKED_RESPAWN, + SEL_CREATURE_TEXT, + SEL_SMART_SCRIPTS, + SEL_SMARTAI_WP, + DEL_GAMEOBJECT, + DEL_EVENT_GAMEOBJECT, + INS_GRAVEYARD_ZONE, + DEL_GRAVEYARD_ZONE, + INS_GAME_TELE, + DEL_GAME_TELE, + INS_NPC_VENDOR, + DEL_NPC_VENDOR, + SEL_NPC_VENDOR_REF, + UPD_CREATURE_MOVEMENT_TYPE, + UPD_CREATURE_FACTION, + UPD_CREATURE_NPCFLAG, + UPD_CREATURE_POSITION, + UPD_CREATURE_SPAWN_DISTANCE, + UPD_CREATURE_SPAWN_TIME_SECS, + INS_CREATURE_FORMATION, + INS_WAYPOINT_DATA, + DEL_WAYPOINT_DATA, + UPD_WAYPOINT_DATA_POINT, + UPD_WAYPOINT_DATA_POSITION, + UPD_WAYPOINT_DATA_WPGUID, + UPD_WAYPOINT_DATA_ALL_WPGUID, + SEL_WAYPOINT_DATA_MAX_ID, + SEL_WAYPOINT_DATA_BY_ID, + SEL_WAYPOINT_DATA_POS_BY_ID, + SEL_WAYPOINT_DATA_POS_FIRST_BY_ID, + SEL_WAYPOINT_DATA_POS_LAST_BY_ID, + SEL_WAYPOINT_DATA_BY_WPGUID, + SEL_WAYPOINT_DATA_ALL_BY_WPGUID, + SEL_WAYPOINT_DATA_MAX_POINT, + SEL_WAYPOINT_DATA_BY_POS, + SEL_WAYPOINT_DATA_WPGUID_BY_ID, + SEL_WAYPOINT_DATA_ACTION, + SEL_WAYPOINT_SCRIPTS_MAX_ID, + UPD_CREATURE_ADDON_PATH, + INS_CREATURE_ADDON, + DEL_CREATURE_ADDON, + SEL_CREATURE_ADDON_BY_GUID, + INS_WAYPOINT_SCRIPT, + DEL_WAYPOINT_SCRIPT, + UPD_WAYPOINT_SCRIPT_ID, + UPD_WAYPOINT_SCRIPT_X, + UPD_WAYPOINT_SCRIPT_Y, + UPD_WAYPOINT_SCRIPT_Z, + UPD_WAYPOINT_SCRIPT_O, + SEL_WAYPOINT_SCRIPT_ID_BY_GUID, + DEL_CREATURE, + SEL_COMMANDS, + SEL_CREATURE_TEMPLATE, + SEL_WAYPOINT_SCRIPT_BY_ID, + SEL_CREATURE_BY_ID, + SEL_GAMEOBJECT_NEAREST, + SEL_CREATURE_NEAREST, + SEL_GAMEOBJECT_TARGET, + INS_CREATURE, + DEL_GAME_EVENT_CREATURE, + DEL_GAME_EVENT_MODEL_EQUIP, + INS_GAMEOBJECT, + SEL_DISABLES, + INS_DISABLES, + DEL_DISABLES, + UPD_CREATURE_ZONE_AREA_DATA, + UPD_GAMEOBJECT_ZONE_AREA_DATA, + SEL_GUILD_REWARDS_REQ_ACHIEVEMENTS, + + MAX_WORLDDATABASE_STATEMENTS + } +} diff --git a/Framework/Database/MySqlBase.cs b/Framework/Database/MySqlBase.cs new file mode 100644 index 000000000..a3849d696 --- /dev/null +++ b/Framework/Database/MySqlBase.cs @@ -0,0 +1,422 @@ +/* + * Copyright (C) 2012-2017 CypherCore + * + * 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, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +using Framework.Collections; +using MySql.Data.MySqlClient; +using System; +using System.Collections.Generic; +using System.IO; +using System.Text; +using System.Threading.Tasks; +using System.Transactions; + +namespace Framework.Database +{ + public class MySqlConnectionInfo + { + public MySqlConnectionInfo(string info, int poolSize = 10) + { + var lines = new StringArray(info, ';'); + Host = lines[0]; + Port = lines[1]; + Username = lines[2]; + Password = lines[3]; + Database = lines[4]; + Poolsize = poolSize; + } + + public MySqlConnection GetConnection() + { + return new MySqlConnection(string.Format("Server={0};Port={1};User Id={2};Password={3};Database={4};Allow Zero Datetime=True;Allow User Variables=True;Pooling=true;MaximumPoolSize={5};", + Host, Port, Username, Password, Database, Poolsize)); + } + + public MySqlConnection GetConnectionNoDatabase() + { + return new MySqlConnection(string.Format("Server={0};Port={1};User Id={2};Password={3};Allow Zero Datetime=True;Allow User Variables=True;Pooling=true;MaximumPoolSize={4};", + Host, Port, Username, Password, Poolsize)); + } + + public string Host; + public string Port; + public string Username; + public string Password; + public string Database; + public int Poolsize; + } + + public abstract class MySqlBase + { + public MySqlErrorCode Initialize(string infoString) + { + _connectionInfo = new MySqlConnectionInfo(infoString); + _updater = new DatabaseUpdater(this); + + using (var connection = _connectionInfo.GetConnection()) + { + try + { + connection.Open(); + Log.outInfo(LogFilter.SqlDriver, "Connected to MySQL(ver: {0}) Database: {1}", connection.ServerVersion, _connectionInfo.Database); + return MySqlErrorCode.None; + } + catch (MySqlException ex) + { + return (MySqlErrorCode)((MySqlException)ex.InnerException).Number; + } + } + } + + public void Execute(string sql, params object[] args) + { + Execute(new PreparedStatement(string.Format(sql, args))); + } + public void Execute(PreparedStatement stmt) + { + using (var Connection = _connectionInfo.GetConnection()) + { + Connection.Open(); + using (MySqlCommand cmd = Connection.CreateCommand()) + { + try + { + cmd.CommandText = stmt.CommandText; + foreach (var parameter in stmt.Parameters) + cmd.Parameters.AddWithValue("@" + parameter.Key, parameter.Value); + + cmd.ExecuteNonQuery(); + } + catch (MySqlException ex) + { + HandleMySQLException(ex, stmt.CommandText); + } + } + } + } + + public void ExecuteOrAppend(SQLTransaction trans, PreparedStatement stmt) + { + if (trans == null) + Execute(stmt); + else + trans.Append(stmt); + } + + public SQLResult Query(string sql, params object[] args) + { + return Query(new PreparedStatement(string.Format(sql, args))); + } + + public SQLResult Query(PreparedStatement stmt) + { + List rows = new List(); + using (var Connection = _connectionInfo.GetConnection()) + { + Connection.Open(); + using (MySqlCommand cmd = Connection.CreateCommand()) + { + try + { + cmd.CommandText = stmt.CommandText; + foreach (var parameter in stmt.Parameters) + cmd.Parameters.AddWithValue("@" + parameter.Key, parameter.Value); + + using (var reader = cmd.ExecuteReader()) + { + if (reader.Read() && reader.HasRows) + { + do + { + var row = new object[reader.FieldCount]; + + reader.GetValues(row); + rows.Add(row); + } + while (reader.Read()); + } + } + } + catch (MySqlException ex) + { + HandleMySQLException(ex, stmt.CommandText); + } + } + } + + return new SQLResult(rows); + } + + public QueryCallback AsyncQuery(string sql, params object[] args) + { + return AsyncQuery(new PreparedStatement(string.Format(sql, args))); + } + + public QueryCallback AsyncQuery(PreparedStatement stmt) + { + return new QueryCallback(_AsyncQuery(stmt)); + } + + async Task _AsyncQuery(PreparedStatement stmt) + { + List rows = new List(); + + try + { + using (var Connection = _connectionInfo.GetConnection()) + { + Connection.Open(); + using (MySqlCommand cmd = Connection.CreateCommand()) + { + cmd.CommandText = stmt.CommandText; + foreach (var parameter in stmt.Parameters) + cmd.Parameters.AddWithValue("@" + parameter.Key, parameter.Value); + + using (var reader = await cmd.ExecuteReaderAsync()) + { + if (reader.Read() && reader.HasRows) + { + do + { + var row = new object[reader.FieldCount]; + + reader.GetValues(row); + rows.Add(row); + } + while (reader.Read()); + } + } + } + } + } + catch (MySqlException ex) + { + HandleMySQLException(ex, stmt.CommandText); + } + + return new SQLResult(rows); + } + + public async Task> DelayQueryHolder(SQLQueryHolder holder) + { + return await Task.Run(async () => + { + string query = ""; + + try + { + using (var Connection = _connectionInfo.GetConnection()) + { + Connection.Open(); + + foreach (var pair in holder.m_queries) + { + List rows = new List(); + using (MySqlCommand cmd = Connection.CreateCommand()) + { + cmd.CommandText = pair.Value.stmt.CommandText; + foreach (var parameter in pair.Value.stmt.Parameters) + cmd.Parameters.AddWithValue("@" + parameter.Key, parameter.Value); + + query = cmd.CommandText; + using (var reader = await cmd.ExecuteReaderAsync()) + { + if (reader.Read() && reader.HasRows) + { + do + { + var row = new object[reader.FieldCount]; + + reader.GetValues(row); + rows.Add(row); + } + while (reader.Read()); + } + } + } + + holder.SetResult(pair.Key, new SQLResult(rows)); + } + } + + return holder; + } + catch (MySqlException ex) + { + HandleMySQLException(ex, query); + return holder; + } + }); + } + + public void LoadPreparedStatements() + { + PreparedStatements(); + } + + public void PrepareStatement(T statement, string sql) + { + StringBuilder sb = new StringBuilder(); + int index = 0; + for (var i = 0; i < sql.Length; i++) + { + if (sql[i].Equals('?')) + sb.Append("@" + index++); + else + sb.Append(sql[i]); + } + + _queries[statement] = sb.ToString(); + } + + public PreparedStatement GetPreparedStatement(T statement) + { + return new PreparedStatement(_queries[statement]); + } + + public bool Apply(string sql) + { + using (var Connection = _connectionInfo.GetConnectionNoDatabase()) + { + using (MySqlCommand cmd = Connection.CreateCommand()) + { + try + { + Connection.Open(); + cmd.CommandText = sql; + return cmd.ExecuteNonQuery() > 0; + } + catch (MySqlException ex) + { + HandleMySQLException(ex, sql); + return false; + } + } + } + } + + public bool ApplyFile(string path) + { + using (var connection = _connectionInfo.GetConnection()) + { + using (MySqlCommand cmd = connection.CreateCommand()) + { + try + { + connection.Open(); + cmd.CommandText = File.ReadAllText(path); + return cmd.ExecuteNonQuery() > 0; + } + catch (MySqlException ex) + { + HandleMySQLException(ex, path); + return false; + } + } + } + } + + public void EscapeString(ref string str) + { + str = MySqlHelper.EscapeString(str); + } + + public void CommitTransaction(SQLTransaction transaction) + { + using (var Connection = _connectionInfo.GetConnection()) + { + string query = ""; + Connection.Open(); + using (MySqlTransaction trans = Connection.BeginTransaction()) + { + try + { + using (var scope = new TransactionScope()) + { + foreach (var cmd in transaction.commands) + { + cmd.Transaction = trans; + cmd.Connection = Connection; + cmd.ExecuteNonQuery(); + query = cmd.CommandText; + } + + trans.Commit(); + scope.Complete(); + } + } + catch (MySqlException ex) //error occurred + { + HandleMySQLException(ex, query); + trans.Rollback(); + } + } + } + } + + void HandleMySQLException(MySqlException ex, string query) + { + int code = ex.Number; + if (ex.InnerException != null) + code = ((MySqlException)ex.InnerException).Number; + + switch ((MySqlErrorCode)code) + { + case MySqlErrorCode.BadFieldError: + case MySqlErrorCode.NoSuchTable: + Log.outError(LogFilter.Sql, "Your database structure is not up to date. Please make sure you've executed all queries in the sql/updates folders."); + break; + case MySqlErrorCode.ParseError: + Log.outError(LogFilter.Sql, "Error while parsing SQL. Core fix required."); + break; + } + + Log.outError(LogFilter.Sql, "SqlException: {0} SqlQuery: {1}", ex.Message, query); + } + + public DatabaseUpdater GetUpdater() + { + return _updater; + } + + public bool IsAutoUpdateEnabled(DatabaseTypeFlags updateMask) + { + switch (GetType().Name) + { + case "LoginDatabase": + return updateMask.HasAnyFlag(DatabaseTypeFlags.Login); + case "CharacterDatabase": + return updateMask.HasAnyFlag(DatabaseTypeFlags.Character); + case "WorldDatabase": + return updateMask.HasAnyFlag(DatabaseTypeFlags.World); + case "HotfixDatabase": + return updateMask.HasAnyFlag(DatabaseTypeFlags.Hotfix); + } + return false; + } + + public string GetDatabaseName() + { + return _connectionInfo.Database; + } + + public abstract void PreparedStatements(); + + Dictionary _queries = new Dictionary(); + MySqlConnectionInfo _connectionInfo; + DatabaseUpdater _updater; + } +} diff --git a/Framework/Database/PreparedStatement.cs b/Framework/Database/PreparedStatement.cs new file mode 100644 index 000000000..878cea699 --- /dev/null +++ b/Framework/Database/PreparedStatement.cs @@ -0,0 +1,42 @@ +/* + * Copyright (C) 2012-2017 CypherCore + * + * 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, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +using System.Collections.Generic; + +namespace Framework.Database +{ + public class PreparedStatement + { + public PreparedStatement(string commandText) + { + CommandText = commandText; + } + + public void AddValue(int index, object value) + { + Parameters.Add(index, value); + } + + public void Clear() + { + Parameters.Clear(); + } + + public string CommandText; + public Dictionary Parameters = new Dictionary(); + } +} diff --git a/Framework/Database/QueryCallback.cs b/Framework/Database/QueryCallback.cs new file mode 100644 index 000000000..2b760d3f5 --- /dev/null +++ b/Framework/Database/QueryCallback.cs @@ -0,0 +1,109 @@ +/* + * Copyright (C) 2012-2017 CypherCore + * + * 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, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +using System; +using System.Collections.Generic; +using System.Diagnostics.Contracts; +using System.Linq; +using System.Threading.Tasks; + +namespace Framework.Database +{ + public class QueryCallback + { + public QueryCallback(Task result) + { + _result = result; + } + + public QueryCallback WithCallback(Action callback) + { + return WithChainingCallback((queryCallback, result) => callback(result)); + } + + public QueryCallback WithCallback(Action callback, T obj) + { + return WithChainingCallback((queryCallback, result) => callback(obj, result)); + } + + public QueryCallback WithChainingCallback(Action callback) + { + _callbacks.Add(new QueryCallbackData(callback)); + return this; + } + + public void SetNextQuery(QueryCallback next) + { + _result = next._result; + } + + public QueryCallbackStatus InvokeIfReady() + { + QueryCallbackData callback = _callbacks.First(); + if (_result.IsCompleted) + { + Task f = _result; + Action cb = callback._result; + _result = null; + callback.Clear(); + + cb(this, f.Result); + + _callbacks.RemoveAt(0); + bool hasNext = _result != null; + if (_callbacks.Empty()) + { + Contract.Assert(!hasNext); + return QueryCallbackStatus.Completed; + } + + // abort chain + if (!hasNext) + return QueryCallbackStatus.Completed; + + return QueryCallbackStatus.NextStep; + } + + return QueryCallbackStatus.NotReady; + } + + Task _result; + List _callbacks = new List(); + } + + struct QueryCallbackData + { + public QueryCallbackData(Action callback) + { + _result = callback; + } + + public void Clear() + { + _result = null; + } + + public Action _result; + } + + public enum QueryCallbackStatus + { + NotReady, + NextStep, + Completed + } +} diff --git a/Framework/Database/QueryCallbackProcessor.cs b/Framework/Database/QueryCallbackProcessor.cs new file mode 100644 index 000000000..94c2c135c --- /dev/null +++ b/Framework/Database/QueryCallbackProcessor.cs @@ -0,0 +1,42 @@ +/* + * Copyright (C) 2012-2017 CypherCore + * + * 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, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +using System.Collections.Generic; + +namespace Framework.Database +{ + public class QueryCallbackProcessor + { + public void AddQuery(QueryCallback query) + { + _callbacks.Add(query); + } + + public void ProcessReadyQueries() + { + if (_callbacks.Empty()) + return; + + _callbacks.RemoveAll(callback => + { + return callback.InvokeIfReady() == QueryCallbackStatus.Completed; + }); + } + + List _callbacks = new List(); + } +} diff --git a/Framework/Database/SQLQueryHolder.cs b/Framework/Database/SQLQueryHolder.cs new file mode 100644 index 000000000..21e74c68f --- /dev/null +++ b/Framework/Database/SQLQueryHolder.cs @@ -0,0 +1,60 @@ +/* + * Copyright (C) 2012-2017 CypherCore + * + * 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, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +using System.Collections.Generic; + +namespace Framework.Database +{ + public class SQLQueryHolder + { + public void SetQuery(T index, PreparedStatement _stmt) + { + m_queries[index] = new SQLResultPair(_stmt); + } + + public void SetQuery(T index, string sql, params object[] args) + { + SetQuery(index, new PreparedStatement(string.Format(sql, args))); + } + + public void SetResult(T index, SQLResult _result) + { + m_queries[index].result = _result; + } + + public SQLResult GetResult(T index) + { + if (!m_queries.ContainsKey(index)) + return new SQLResult(); + + return m_queries[index].result; + } + + public Dictionary m_queries = new Dictionary(); + + public class SQLResultPair + { + public SQLResultPair(PreparedStatement _stmt) + { + stmt = _stmt; + } + + public PreparedStatement stmt; + public SQLResult result; + } + } +} diff --git a/Framework/Database/SQLResult.cs b/Framework/Database/SQLResult.cs new file mode 100644 index 000000000..200112276 --- /dev/null +++ b/Framework/Database/SQLResult.cs @@ -0,0 +1,122 @@ +/* + * Copyright (C) 2012-2017 CypherCore + * + * 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, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +using System; +using System.Collections.Generic; + +namespace Framework.Database +{ + public class SQLResult + { + public SQLResult() + { + _rows = new List(); + } + public SQLResult(List values) + { + _rows = values; + } + + public T Read(int column) + { + var value = _rows[_rowIndex][column]; + + if (value.GetType() == typeof(T)) + return (T)value; + + if (value != DBNull.Value) + return (T)Convert.ChangeType(value, typeof(T)); + + if (typeof(T).Name == "String") + return (T)Convert.ChangeType("", typeof(T)); + + return default(T); + } + + public T[] ReadValues(int startIndex, int numColumns) + { + T[] values = new T[numColumns]; + for (var c = 0; c < numColumns; ++c) + values[c] = Read(startIndex + c); + + return values; + } + + public bool IsNull(int column) + { + return _rows[_rowIndex][column] == DBNull.Value; + } + + public int GetRowCount() { return _rows.Count; } + + public bool IsEmpty() { return GetRowCount() == 0; } + + public SQLFields GetFields() { return new SQLFields(_rows[_rowIndex]); } + + public bool NextRow() + { + if (_rowIndex >= GetRowCount() - 1) + { + _rowIndex = 0; + return false; + } + + _rowIndex++; + return true; + } + + public void ResetRowIndex() + { + _rowIndex = 0; + } + + private int _rowIndex; + List _rows; + } + + public class SQLFields + { + public SQLFields(object[] row) { _currentRow = row; } + + public T Read(int column) + { + var value = _currentRow[column]; + + if (value.GetType() == typeof(T)) + return (T)value; + + if (value != DBNull.Value) + return (T)Convert.ChangeType(value, typeof(T)); + + if (typeof(T).Name == "String") + return (T)Convert.ChangeType("", typeof(T)); + + return default(T); + } + + public T[] ReadValues(int startIndex, int numColumns) + { + T[] values = new T[numColumns]; + for (var c = 0; c < numColumns; ++c) + values[c] = Read(startIndex + c); + + return values; + } + + object[] _currentRow; + } +} diff --git a/Framework/Database/SQLTransaction.cs b/Framework/Database/SQLTransaction.cs new file mode 100644 index 000000000..6a51b5706 --- /dev/null +++ b/Framework/Database/SQLTransaction.cs @@ -0,0 +1,46 @@ +/* + * Copyright (C) 2012-2017 CypherCore + * + * 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, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +using MySql.Data.MySqlClient; +using System.Collections.Generic; + +namespace Framework.Database +{ + public class SQLTransaction + { + public List commands { get; set; } + + public SQLTransaction() + { + commands = new List(); + } + + public void Append(PreparedStatement stmt) + { + MySqlCommand cmd = new MySqlCommand(stmt.CommandText); + foreach (var parameter in stmt.Parameters) + cmd.Parameters.AddWithValue("@" + parameter.Key, parameter.Value); + + commands.Add(cmd); + } + + public void Append(string sql, params object[] args) + { + commands.Add(new MySqlCommand(string.Format(sql, args))); + } + } +} diff --git a/Framework/Dynamic/EventMap.cs b/Framework/Dynamic/EventMap.cs new file mode 100644 index 000000000..02eb30d2d --- /dev/null +++ b/Framework/Dynamic/EventMap.cs @@ -0,0 +1,399 @@ +using System; +using System.Collections.Generic; +using System.Linq; + +namespace Framework.Dynamic +{ + public class EventMap + { + /// + /// Removes all scheduled events and resets time and phase. + /// + public void Reset() + { + _eventMap.Clear(); + _time = 0; + _phase = 0; + } + + /// + /// Updates the timer of the event map. + /// + /// Value in ms to be added to time. + public void Update(uint time) + { + _time += time; + } + + /// + /// + /// + /// Current timer in ms value. + uint GetTimer() + { + return _time; + } + + /// + /// + /// + /// Active phases as mask. + byte GetPhaseMask() + { + return _phase; + } + + /// + /// + /// + /// True, if there are no events scheduled. + public bool Empty() + { + return _eventMap.Empty(); + } + + /// + /// Sets the phase of the map (absolute). + /// + /// Phase which should be set. Values: 1 - 8. 0 resets phase. + public void SetPhase(byte phase) + { + if (phase == 0) + _phase = 0; + else if (phase <= 8) + _phase = (byte)(1 << (phase - 1)); + } + + /// + /// Activates the given phase (bitwise). + /// + /// Phase which should be activated. Values: 1 - 8 + void AddPhase(byte phase) + { + if (phase != 0 && phase <= 8) + _phase |= (byte)(1 << (phase - 1)); + } + + /// + /// Deactivates the given phase (bitwise). + /// + /// Phase which should be deactivated. Values: 1 - 8. + void RemovePhase(byte phase) + { + if (phase != 0 && phase <= 8) + _phase &= (byte)~(1 << (phase - 1)); + } + + /// + /// Creates new event entry in map. + /// + /// The id of the new event. + /// The time in milliseconds as TimeSpan until the event occurs. + /// The group which the event is associated to. Has to be between 1 and 8. 0 means it has no group. + /// The phase in which the event can occur. Has to be between 1 and 8. 0 means it can occur in all phases. + public void ScheduleEvent(uint eventId, TimeSpan time, uint group = 0, byte phase = 0) + { + ScheduleEvent(eventId, (uint)time.TotalMilliseconds, group, phase); + } + + /// + /// Creates new event entry in map. + /// + /// The id of the new event. + /// The minimum time until the event occurs as TimeSpan type. + /// The maximum time until the event occurs as TimeSpan type. + /// The group which the event is associated to. Has to be between 1 and 8. 0 means it has no group. + /// The phase in which the event can occur. Has to be between 1 and 8. 0 means it can occur in all phases. + public void ScheduleEvent(uint eventId, TimeSpan minTime, TimeSpan maxTime, uint group = 0, byte phase = 0) + { + ScheduleEvent(eventId, RandomHelper.URand(minTime.TotalMilliseconds, maxTime.TotalMilliseconds), group, phase); + } + + + /// + /// Creates new event entry in map. + /// + /// The id of the new event. + /// The time in milliseconds until the event occurs. + /// The group which the event is associated to. Has to be between 1 and 8. 0 means it has no group. + /// The phase in which the event can occur. Has to be between 1 and 8. 0 means it can occur in all phases. + public void ScheduleEvent(uint eventId, uint time, uint group = 0, byte phase = 0) + { + if (group != 0 && group <= 8) + eventId |= (uint)(1 << ((int)group + 15)); + + if (phase != 0 && phase <= 8) + eventId |= (uint)(1 << (phase + 23)); + + _eventMap.Add(_time + time, eventId); + } + + /// + /// Cancels the given event and reschedules it. + /// + /// The id of the event. + /// The time in milliseconds as TimeSpan until the event occurs. + /// The group which the event is associated to. Has to be between 1 and 8. 0 means it has no group. + /// The phase in which the event can occur. Has to be between 1 and 8. 0 means it can occur in all phases. + public void RescheduleEvent(uint eventId, TimeSpan time, uint group = 0, byte phase = 0) + { + RescheduleEvent(eventId, (uint)time.TotalMilliseconds, group, phase); + } + + /// + /// Cancels the given event and reschedules it. + /// + /// The id of the event. + /// The minimum time until the event occurs as TimeSpan type. + /// The maximum time until the event occurs as TimeSpan type. + /// The group which the event is associated to. Has to be between 1 and 8. 0 means it has no group. + /// The phase in which the event can occur. Has to be between 1 and 8. 0 means it can occur in all phases. + void RescheduleEvent(uint eventId, TimeSpan minTime, TimeSpan maxTime, uint group = 0, byte phase = 0) + { + RescheduleEvent(eventId, RandomHelper.URand(minTime.TotalMilliseconds, maxTime.TotalMilliseconds), group, phase); + } + + /// + /// Cancels the given event and reschedules it. + /// + /// The id of the event. + /// The time in milliseconds until the event occurs. + /// The group which the event is associated to. Has to be between 1 and 8. 0 means it has no group. + /// The phase in which the event can occur. Has to be between 1 and 8. 0 means it can occur in all phases. + public void RescheduleEvent(uint eventId, uint time, uint group = 0, byte phase = 0) + { + CancelEvent(eventId); + ScheduleEvent(eventId, time, group, phase); + } + + /// + /// Repeats the mostly recently executed event. + /// + /// Time until in ms as TimeSpan the event occurs + public void Repeat(TimeSpan time) + { + Repeat((uint)time.TotalMilliseconds); + } + + /// + /// Repeats the mostly recently executed event. + /// + /// Time until the event occurs + public void Repeat(uint time) + { + _eventMap.Add(_time + time, _lastEvent); + } + + /// + /// Repeats the mostly recently executed event. Equivalent to Repeat(urand(minTime, maxTime) + /// + /// Min Time as TimeSpan until the event occurs. + /// Max Time as TimeSpan until the event occurs. + public void Repeat(TimeSpan minTime, TimeSpan maxTime) + { + Repeat((uint)minTime.TotalMilliseconds, (uint)maxTime.TotalMilliseconds); + } + + /// + /// Repeats the mostly recently executed event. Equivalent to Repeat(urand(minTime, maxTime) + /// + /// Min Time until the event occurs. + /// Max Time until the event occurs. + public void Repeat(uint minTime, uint maxTime) + { + Repeat(RandomHelper.URand(minTime, maxTime)); + } + + /// + /// Returns the next event to execute and removes it from map. + /// + /// Id of the event to execute. + /// + public uint ExecuteEvent() + { + while (!Empty()) + { + var pair = _eventMap.FirstOrDefault(); + + if (pair.Key > _time) + return 0; + else if (_phase != 0 && Convert.ToBoolean(pair.Value & 0xFF000000) && !Convert.ToBoolean((pair.Value >> 24) & _phase)) + _eventMap.Remove(pair); + else + { + uint eventId = (pair.Value & 0x0000FFFF); + _lastEvent = pair.Value; // include phase/group + _eventMap.Remove(pair); + return eventId; + } + } + + return 0; + } + + public void ExecuteEvents(Action action) + { + uint id; + while ((id = ExecuteEvent()) != 0) + action(id); + } + + /// + /// Delays all events in the map. If delay is greater than or equal internal timer, delay will be 0. + /// + /// Amount of delay in ms as TimeSpan. + public void DelayEvents(TimeSpan delay) + { + DelayEvents((uint)delay.TotalMilliseconds); + } + + /// + /// Delays all events in the map. If delay is greater than or equal internal timer, delay will be 0. + /// + /// Amount of delay. + public void DelayEvents(uint delay) + { + _time = delay < _time ? _time - delay : 0; + } + + /// + /// Delay all events of the same group. + /// + /// Amount of delay. + /// Group of the events. + public void DelayEvents(uint delay, uint group) + { + if (group == 0 || group > 8 || Empty()) + return; + MultiMap delayed = new MultiMap(); + + foreach (var pair in _eventMap.KeyValueList) + { + if (Convert.ToBoolean(pair.Value & (1 << (int)(group + 15)))) + { + delayed.Add(pair.Key + delay, pair.Value); + _eventMap.Remove(pair.Key, pair.Value); + } + } + + foreach (var del in delayed) + _eventMap.Add(del); + } + + /// + /// Cancels all events of the specified id. + /// + /// Event id to cancel. + public void CancelEvent(uint eventId) + { + if (Empty()) + return; + + foreach (var pair in _eventMap.KeyValueList) + { + if (eventId == (pair.Value & 0x0000FFFF)) + _eventMap.Remove(pair.Key, pair.Value); + } + } + + /// + /// Cancel events belonging to specified group. + /// + /// Group to cancel. + void CancelEventGroup(uint group) + { + if (group == 0 || group > 8 || Empty()) + return; + + foreach (var pair in _eventMap.KeyValueList) + { + if (Convert.ToBoolean(pair.Value & (uint)(1 << ((int)group + 15)))) + _eventMap.Remove(pair.Key, pair.Value); + } + } + + /// + /// Returns closest occurence of specified event. + /// + /// Wanted event id. + /// Time of found event. + uint GetNextEventTime(uint eventId) + { + if (Empty()) + return 0; + + foreach (var pair in _eventMap.KeyValueList) + if (eventId == (pair.Value & 0x0000FFFF)) + return pair.Key; + + return 0; + } + + /// + /// + /// + /// Time of next event. + uint GetNextEventTime() + { + return Empty() ? 0 : _eventMap[0][0]; + } + + /// + /// Returns time in milliseconds until next event. + /// + /// Id of the event. + /// Time of next event. + public uint GetTimeUntilEvent(uint eventId) + { + foreach (var pair in _eventMap) + if (eventId == (pair.Value & 0x0000FFFF)) + return pair.Key - _time; + + return uint.MaxValue; + } + + /// + /// Returns wether event map is in specified phase or not. + /// + /// Wanted phase. + /// True, if phase of event map contains specified phase. + public bool IsInPhase(byte phase) + { + return phase <= 8 && (phase == 0 || Convert.ToBoolean(_phase & (1 << (phase - 1)))); + } + + /// + /// Internal timer. + /// This does not represent the real date/time value. + /// It's more like a stopwatch: It can run, it can be stopped, + /// it can be resetted and so on. Events occur when this timer + /// has reached their time value. Its value is changed in the Update method. + /// + uint _time; + + /// + /// Phase mask of the event map. + /// Contains the phases the event map is in. Multiple + /// phases from 1 to 8 can be set with SetPhase or + /// AddPhase. RemovePhase deactives a phase. + /// + byte _phase; + + /// + /// Stores information on the most recently executed event + /// + uint _lastEvent; + + /// + /// Key: Time as uint when the event should occur. + /// Value: The event data as uint. + /// + /// Structure of event data: + /// - Bit 0 - 15: Event Id. + /// - Bit 16 - 23: Group + /// - Bit 24 - 31: Phase + /// - Pattern: 0xPPGGEEEE + /// + SortedMultiMap _eventMap = new SortedMultiMap(); + } +} diff --git a/Framework/Dynamic/EventSystem.cs b/Framework/Dynamic/EventSystem.cs new file mode 100644 index 000000000..1ebb00287 --- /dev/null +++ b/Framework/Dynamic/EventSystem.cs @@ -0,0 +1,149 @@ +/* + * Copyright (C) 2012-2017 CypherCore + * + * 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, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +using System.Collections.Generic; +using System.Diagnostics.Contracts; +using System.Linq; + +namespace Framework.Dynamic +{ + public class EventSystem + { + public EventSystem() + { + m_time = 0; + } + + public void Update(uint p_time) + { + // update time + m_time += p_time; + + // main event loop + KeyValuePair i; + while ((i = m_events.FirstOrDefault()).Value != null && i.Key <= m_time) + { + var Event = i.Value; + m_events.Remove(i); + + if (Event.IsRunning()) + { + Event.Execute(m_time, p_time); + continue; + } + + if (Event.IsAbortScheduled()) + { + Event.Abort(m_time); + // Mark the event as aborted + Event.SetAborted(); + } + + if (Event.IsDeletable()) + continue; + + // Reschedule non deletable events to be checked at + // the next update tick + AddEvent(Event, CalculateTime(1), false); + } + } + + + public void KillAllEvents(bool force) + { + foreach (var pair in m_events.KeyValueList) + { + // Abort events which weren't aborted already + if (!pair.Value.IsAborted()) + { + pair.Value.SetAborted(); + pair.Value.Abort(m_time); + } + + // Skip non-deletable events when we are + // not forcing the event cancellation. + if (!force && !pair.Value.IsDeletable()) + continue; + + if (!force) + m_events.Remove(pair); + } + + // fast clear event list (in force case) + if (force) + m_events.Clear(); + } + + public void AddEvent(BasicEvent Event, ulong e_time, bool set_addtime = true) + { + if (set_addtime) + Event.m_addTime = m_time; + + Event.m_execTime = e_time; + m_events.Add(e_time, Event); + } + + public ulong CalculateTime(ulong t_offset) + { + return (m_time + t_offset); + } + + ulong m_time; + SortedMultiMap m_events = new SortedMultiMap(); + } + + public class BasicEvent + { + public BasicEvent() { m_abortState = AbortState.Running; } + + public void ScheduleAbort() + { + Contract.Assert(IsRunning(), "Tried to scheduled the abortion of an event twice!"); + m_abortState = AbortState.Scheduled; + } + + public void SetAborted() + { + Contract.Assert(!IsAborted(), "Tried to abort an already aborted event!"); + m_abortState = AbortState.Aborted; + } + + // this method executes when the event is triggered + // return false if event does not want to be deleted + // e_time is execution time, p_time is update interval + public virtual bool Execute(ulong e_time, uint p_time) { return true; } + + public virtual bool IsDeletable() { return true; } // this event can be safely deleted + + public virtual void Abort(ulong e_time) { } // this method executes when the event is aborted + + public bool IsRunning() { return m_abortState == AbortState.Running; } + public bool IsAbortScheduled() { return m_abortState == AbortState.Scheduled; } + public bool IsAborted() { return m_abortState == AbortState.Aborted; } + + AbortState m_abortState; // set by externals when the event is aborted, aborted events don't execute + public ulong m_addTime; // time when the event was added to queue, filled by event handler + public ulong m_execTime; // planned time of next execution, filled by event handler + } + + enum AbortState + { + Running, + Scheduled, + Aborted + } +} diff --git a/Framework/Dynamic/FlagArray.cs b/Framework/Dynamic/FlagArray.cs new file mode 100644 index 000000000..ca3972aab --- /dev/null +++ b/Framework/Dynamic/FlagArray.cs @@ -0,0 +1,144 @@ +/* + * Copyright (C) 2012-2017 CypherCore + * + * 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, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +using System; + +namespace Framework.Dynamic +{ + public class FlagArray128 + { + public FlagArray128(params uint[] parts) + { + _values = new uint[4]; + for (var i = 0; i < parts.Length; ++i) + _values[i] = parts[i]; + } + + public override int GetHashCode() + { + return base.GetHashCode(); + } + + public override bool Equals(object obj) + { + return base.Equals(obj); + } + + public bool IsEqual(params uint[] parts) + { + for (var i = 0; i < _values.Length; ++i) + if (_values[i] == parts[i]) + return false; + + return true; + } + + public void Set(params uint[] parts) + { + for (var i = 0; i < parts.Length; ++i) + _values[i] = parts[i]; + } + + public static bool operator <(FlagArray128 left, FlagArray128 right) + { + for (var i = left._values.Length; i > 0; --i) + { + if (left._values[i - 1] < right._values[i - 1]) + return true; + else if (left._values[i - 1] > right._values[i - 1]) + return false; + } + return false; + } + public static bool operator >(FlagArray128 left, FlagArray128 right) + { + for (var i = left._values.Length; i > 0; --i) + { + if (left._values[i - 1] > right._values[i - 1]) + return true; + else if (left._values[i - 1] < right._values[i - 1]) + return false; + } + return false; + } + + public static FlagArray128 operator &(FlagArray128 left, FlagArray128 right) + { + FlagArray128 fl = new FlagArray128(); + for (var i = 0; i < left._values.Length; ++i) + fl[i] = left._values[i] & right._values[i]; + return fl; + } + public static FlagArray128 operator |(FlagArray128 left, FlagArray128 right) + { + FlagArray128 fl = new FlagArray128(); + for (var i = 0; i < left._values.Length; ++i) + fl[i] = left._values[i] | right._values[i]; + return fl; + } + public static FlagArray128 operator ^(FlagArray128 left, FlagArray128 right) + { + FlagArray128 fl = new FlagArray128(); + for (var i = 0; i < left._values.Length; ++i) + fl[i] = left._values[i] ^ right._values[i]; + return fl; + } + + public static implicit operator bool (FlagArray128 left) + { + for (var i = 0; i < left._values.Length; ++i) + if (left._values[i] != 0) + return true; + + return false; + } + + public uint this[int i] + { + get + { + return _values[i]; + } + set + { + _values[i] = value; + } + } + + uint[] _values { get; set; } + } + + public class FlaggedArray where T : struct + { + int[] m_values; + uint m_flags; + + public FlaggedArray(byte arraysize) + { + m_values = new int[4 * arraysize]; + } + + public uint GetFlags() { return m_flags; } + public bool HasFlag(T flag) { return Convert.ToBoolean(Convert.ToInt32(m_flags) & 1 << Convert.ToInt32(flag)); } + public void AddFlag(T flag) { m_flags |= (uint)(1 << Convert.ToInt32(flag)); } + public void DelFlag(T flag) { m_flags &= ~(uint)(1 << Convert.ToInt32(flag)); } + + public int GetValue(T flag) { return m_values[Convert.ToInt32(flag)]; } + public void SetValue(T flag, object value) { m_values[Convert.ToInt32(flag)] = Convert.ToInt32(value); } + public void AddValue(T flag, object value) { m_values[Convert.ToInt32(flag)] += Convert.ToInt32(value); } + } +} diff --git a/Framework/Dynamic/OptionalType.cs b/Framework/Dynamic/OptionalType.cs new file mode 100644 index 000000000..e5120b4dd --- /dev/null +++ b/Framework/Dynamic/OptionalType.cs @@ -0,0 +1,47 @@ +/* + * Copyright (C) 2012-2017 CypherCore + * + * 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, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +namespace Framework.Dynamic +{ + public struct Optional where T : new() + { + private bool _hasValue; + public T Value; + + public bool HasValue + { + get { return _hasValue; } + set + { + _hasValue = value; + Value = _hasValue ? new T() : default(T); + } + } + + public void Set(T v) + { + _hasValue = true; + Value = v; + } + + public void Clear() + { + _hasValue = false; + Value = default(T); + } + } +} diff --git a/Framework/Dynamic/Reference.cs b/Framework/Dynamic/Reference.cs new file mode 100644 index 000000000..68bbfe295 --- /dev/null +++ b/Framework/Dynamic/Reference.cs @@ -0,0 +1,105 @@ +/* + * Copyright (C) 2012-2017 CypherCore + * + * 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, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +using Framework.Collections; +using System.Diagnostics.Contracts; + +namespace Framework.Dynamic +{ + public class Reference : LinkedListElement where TO : class where FROM : class + { + TO _RefTo; + FROM _RefFrom; + + // Tell our refTo (target) object that we have a link + public virtual void targetObjectBuildLink() { } + + // Tell our refTo (taget) object, that the link is cut + public virtual void targetObjectDestroyLink() { } + + // Tell our refFrom (source) object, that the link is cut (Target destroyed) + public virtual void sourceObjectDestroyLink() { } + + public Reference() + { + _RefTo = null; _RefFrom = null; + } + + // Create new link + public void link(TO toObj, FROM fromObj) + { + Contract.Assert(fromObj != null); // fromObj MUST not be NULL + if (isValid()) + unlink(); + if (toObj != null) + { + _RefTo = toObj; + _RefFrom = fromObj; + targetObjectBuildLink(); + } + } + + // We don't need the reference anymore. Call comes from the refFrom object + // Tell our refTo object, that the link is cut + public void unlink() + { + targetObjectDestroyLink(); + delink(); + _RefTo = null; + _RefFrom = null; + } + + // Link is invalid due to destruction of referenced target object. Call comes from the refTo object + // Tell our refFrom object, that the link is cut + public void invalidate() // the iRefFrom MUST remain!! + { + sourceObjectDestroyLink(); + delink(); + _RefTo = null; + } + + public bool isValid() // Only check the iRefTo + { + return _RefTo != null; + } + + public Reference next() { return ((Reference)GetNextElement()); } + public Reference prev() { return ((Reference)GetPrevElement()); } + + public TO getTarget() { return _RefTo; } + + public FROM GetSource() { return _RefFrom; } + } + + public class RefManager : LinkedListHead where TO : class where FROM : class + { + ~RefManager() { clearReferences(); } + + public Reference getFirst() { return (Reference)base.GetFirstElement(); } + public Reference getLast() { return (Reference)base.GetLastElement(); } + + public void clearReferences() + { + LinkedListElement curr; + while ((curr = base.GetFirstElement()) != null) + { + ((Reference)curr).invalidate(); + curr.delink(); // the delink might be already done by invalidate(), but doing it here again does not hurt and insures an empty list + } + } + } +} diff --git a/Framework/Dynamic/TaskScheduler.cs b/Framework/Dynamic/TaskScheduler.cs new file mode 100644 index 000000000..c545436de --- /dev/null +++ b/Framework/Dynamic/TaskScheduler.cs @@ -0,0 +1,847 @@ +/* + * Copyright (C) 2012-2017 CypherCore + * + * 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, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +using System; +using System.Collections.Generic; +using System.Diagnostics.Contracts; +using System.Linq; + +namespace Framework.Dynamic +{ + public class TaskScheduler + { + public TaskScheduler() + { + _now = DateTime.Now; + _task_holder = new TaskQueue(); + _asyncHolder = new List(); + _predicate = EmptyValidator; + } + + public TaskScheduler(predicate_t predicate) + { + _now = DateTime.Now; + _task_holder = new TaskQueue(); + _asyncHolder = new List(); + _predicate = predicate; + } + + /// + /// Clears the validator which is asked if tasks are allowed to be executed. + /// + /// + TaskScheduler ClearValidator() + { + _predicate = EmptyValidator; + return this; + } + + /// + /// Sets a validator which is asked if tasks are allowed to be executed. + /// + /// + /// + public TaskScheduler SetValidator(predicate_t predicate) + { + _predicate = predicate; + return this; + } + + /// + /// Update the scheduler to the current time. + /// Calls the optional callback on successfully finish. + /// + /// + public TaskScheduler Update(success_t callback = null) + { + _now = DateTime.Now; + Dispatch(callback); + return this; + } + + /// + /// Update the scheduler with a difftime in ms. + /// Calls the optional callback on successfully finish. + /// + /// + /// + public TaskScheduler Update(uint milliseconds, success_t callback = null) + { + return Update(TimeSpan.FromMilliseconds(milliseconds), callback); + } + + /// + /// Update the scheduler with a difftime. + /// Calls the optional callback on successfully finish. + /// + /// + /// + TaskScheduler Update(TimeSpan difftime, success_t callback = null) + { + _now += difftime; + Dispatch(callback); + return this; + } + + public TaskScheduler Async(Action callable) + { + _asyncHolder.Add(callable); + return this; + } + + /// + /// Schedule an event with a fixed rate. + /// Never call this from within a task context! Use TaskContext.Schedule instead! + /// + /// + /// + /// + public TaskScheduler Schedule(TimeSpan time, Action task) + { + return ScheduleAt(_now, time, task); + } + + /// + /// Schedule an event with a fixed rate. + /// Never call this from within a task context! Use TaskContext.Schedule instead! + /// + /// + /// + /// + /// + public TaskScheduler Schedule(TimeSpan time, uint group, Action task) + { + return ScheduleAt(_now, time, group, task); + } + + /// + /// Schedule an event with a randomized rate between min and max rate. + /// Never call this from within a task context! Use TaskContext.Schedule instead! + /// + /// + /// + /// + /// + public TaskScheduler Schedule(TimeSpan min, TimeSpan max, Action task) + { + return Schedule(RandomDurationBetween(min, max), task); + } + + /// + /// Schedule an event with a fixed rate. + /// Never call this from within a task context! Use TaskContext.Schedule instead! + /// + /// + /// + /// + /// + /// + public TaskScheduler Schedule(TimeSpan min, TimeSpan max, uint group, Action task) + { + return Schedule(RandomDurationBetween(min, max), group, task); + } + + public TaskScheduler CancelAll() + { + /// Clear the task holder + _task_holder.Clear(); + _asyncHolder.Clear(); + return this; + } + + public TaskScheduler CancelGroup(uint group) + { + _task_holder.RemoveIf(task => + { + return task.IsInGroup(group); + }); + return this; + } + + public TaskScheduler CancelGroupsOf(List groups) + { + groups.ForEach(group => CancelGroup(group)); + + return this; + } + + /// + /// Delays all tasks with the given duration. + /// + /// + /// + public TaskScheduler DelayAll(TimeSpan duration) + { + _task_holder.ModifyIf(task => + { + task._end += duration; + return true; + }); + return this; + } + + /// + /// Delays all tasks with a random duration between min and max. + /// + /// + /// + /// + public TaskScheduler DelayAll(TimeSpan min, TimeSpan max) + { + return DelayAll(RandomDurationBetween(min, max)); + } + + /// + /// Delays all tasks of a group with the given duration. + /// + /// + /// + /// + public TaskScheduler DelayGroup(uint group, TimeSpan duration) + { + _task_holder.ModifyIf(task => + { + if (task.IsInGroup(group)) + { + task._end += duration; + return true; + } + else + return false; + }); + return this; + } + + /// + /// Delays all tasks of a group with a random duration between min and max. + /// + /// + /// + /// + /// + public TaskScheduler DelayGroup(uint group, TimeSpan min, TimeSpan max) + { + return DelayGroup(group, RandomDurationBetween(min, max)); + } + + /// + /// Reschedule all tasks with a given duration. + /// + /// + /// + public TaskScheduler RescheduleAll(TimeSpan duration) + { + var end = _now + duration; + _task_holder.ModifyIf(task => + { + task._end = end; + return true; + }); + return this; + } + + /// + /// Reschedule all tasks with a random duration between min and max. + /// + /// + /// + /// + public TaskScheduler RescheduleAll(TimeSpan min, TimeSpan max) + { + return RescheduleAll(RandomDurationBetween(min, max)); + } + + /// + /// Reschedule all tasks of a group with the given duration. + /// + /// + /// + /// + public TaskScheduler RescheduleGroup(uint group, TimeSpan duration) + { + var end = _now + duration; + _task_holder.ModifyIf(task => + { + if (task.IsInGroup(group)) + { + task._end = end; + return true; + } + else + return false; + }); + return this; + } + + /// + /// Reschedule all tasks of a group with a random duration between min and max. + /// + /// + /// + /// + /// + public TaskScheduler RescheduleGroup(uint group, TimeSpan min, TimeSpan max) + { + return RescheduleGroup(group, RandomDurationBetween(min, max)); + } + + internal TaskScheduler InsertTask(Task task) + { + _task_holder.Push(task); + return this; + } + + internal TaskScheduler ScheduleAt(DateTime end, TimeSpan time, Action task) + { + return InsertTask(new Task(end + time, time, task)); + } + + /// + /// Schedule an event with a fixed rate. + /// Never call this from within a task context! Use TaskContext.schedule instead! + /// + /// + /// + /// + /// + /// + internal TaskScheduler ScheduleAt(DateTime end, TimeSpan time, uint group, Action task) + { + return InsertTask(new Task(end + time, time, group, 0, task)); + } + + /// + /// Returns a random duration between min and max + /// + /// + /// + /// TimeSpan + public static TimeSpan RandomDurationBetween(TimeSpan min, TimeSpan max) + { + var milli_min = min.TotalMilliseconds; + var milli_max = max.TotalMilliseconds; + + // TC specific: use SFMT URandom + return TimeSpan.FromMilliseconds(RandomHelper.URand(milli_min, milli_max)); + } + + void Dispatch(success_t callback = null) + { + // If the validation failed abort the dispatching here. + if (!_predicate()) + return; + + // Process all asyncs + while (!_asyncHolder.Empty()) + { + _asyncHolder.First().Invoke(); + _asyncHolder.RemoveAt(0); + + // If the validation failed abort the dispatching here. + if (!_predicate()) + return; + } + + while (!_task_holder.IsEmpty()) + { + if (_task_holder.First()._end > _now) + break; + + // Perfect forward the context to the handler + // Use weak references to catch destruction before callbacks. + TaskContext context = new TaskContext(_task_holder.Pop(), this); + + // Invoke the context + context.Invoke(); + + // If the validation failed abort the dispatching here. + if (!_predicate()) + return; + } + + callback?.Invoke(); + } + + // The current time point (now) + DateTime _now; + + // The Task Queue which contains all task objects. + TaskQueue _task_holder; + + // Contains all asynchronous tasks which will be invoked at + // the next update tick. + List _asyncHolder; + + predicate_t _predicate; + + static bool EmptyValidator() + { + return true; + } + + // Predicate type + public delegate bool predicate_t(); + // Success handle type + public delegate void success_t(); + } + + public class Task : IComparable + { + public Task(DateTime end, TimeSpan duration, uint group, uint repeated, Action task) + { + _end = end; + _duration = duration; + _group.Set(group); + _repeated = repeated; + _task = task; + } + + public Task(DateTime end, TimeSpan duration, Action task) + { + _end = end; + _duration = duration; + _task = task; + } + + public int CompareTo(Task other) + { + return _end.CompareTo(other._end); + } + + /// + /// Returns true if the task is in the given group + /// + /// + /// + public bool IsInGroup(uint group) + { + return _group.HasValue && _group.Value == group; + } + + internal DateTime _end; + internal TimeSpan _duration; + internal Optional _group; + internal uint _repeated; + internal Action _task; + } + + class TaskQueue + { + /// + /// Pushes the task in the container + /// + /// + public void Push(Task task) + { + if (!container.Add(task)) + { + + } + } + + /// + /// Pops the task out of the container + /// + /// + public Task Pop() + { + Task result = container.First(); + container.Remove(result); + return result; + } + + public Task First() + { + return container.First(); + } + + public void Clear() + { + container.Clear(); + } + + public void RemoveIf(Predicate filter) + { + container.RemoveWhere(filter); + } + + public void ModifyIf(Func filter) + { + List cache = new List(); + container.Where(filter); + foreach (var task in container.ToList()) + { + if (filter(task)) + { + cache.Add(task); + container.Remove(task); + } + } + + foreach (var task in cache) + container.Add(task); + } + + public bool IsEmpty() + { + return container.Empty(); + } + + SortedSet container = new SortedSet(); + } + + public class TaskContext + { + public TaskContext(Task task, TaskScheduler owner) + { + _task = task; + _owner = owner; + _consumed = false; + } + + /// + /// Dispatches an action safe on the TaskScheduler + /// + /// + /// + TaskContext Dispatch(Action apply) + { + apply(); + + return this; + } + + TaskContext Dispatch(Func apply) + { + apply(_owner); + + return this; + } + + bool IsExpired() + { + return _owner == null; + } + + /// + /// Returns true if the event is in the given group + /// + /// + /// + bool IsInGroup(uint group) + { + return _task.IsInGroup(group); + } + + /// + /// Sets the event in the given group + /// + /// + /// + TaskContext SetGroup(uint group) + { + _task._group.Set(group); + return this; + } + + /// + /// Removes the group from the event + /// + /// + TaskContext ClearGroup() + { + _task._group.HasValue = false; + return this; + } + + /// + /// Returns the repeat counter which increases every time the task is repeated. + /// + /// + uint GetRepeatCounter() + { + return _task._repeated; + } + + /// + /// Schedule a callable function that is executed at the next update tick from within the context. + /// Its safe to modify the TaskScheduler from within the callable. + /// + /// + /// + TaskContext Async(Action callable) + { + return Dispatch(() => _owner.Async(callable)); + } + + /// + /// Cancels all tasks from within the context. + /// + /// + public TaskContext CancelAll() + { + return Dispatch(() => _owner.CancelAll()); + } + + /// + /// Cancel all tasks of a single group from within the context. + /// + /// + /// + public TaskContext CancelGroup(uint group) + { + return Dispatch(() => CancelGroup(group)); + } + + /// + /// Cancels all groups in the given std.vector from within the context. + /// + /// + /// + public TaskContext CancelGroupsOf(List groups) + { + return Dispatch(() => CancelGroupsOf(groups)); + } + + /// + /// Asserts if the task was consumed already. + /// + void AssertOnConsumed() + { + // This was adapted to TC to prevent static analysis tools from complaining. + // If you encounter this assertion check if you repeat a TaskContext more then 1 time! + Contract.Assert(!_consumed, "Bad task logic, task context was consumed already!"); + } + + /// + /// Invokes the associated hook of the task. + /// + public void Invoke() + { + _task._task(this); + } + + /// + /// Repeats the event and sets a new duration. + /// This will consume the task context, its not possible to repeat the task again + /// from the same task context! + /// + /// + /// + public TaskContext Repeat(TimeSpan duration) + { + AssertOnConsumed(); + + // Set new duration, in-context timing and increment repeat counter + _task._duration = duration; + _task._end += duration; + _task._repeated += 1; + _consumed = true; + return Dispatch(() => _owner.InsertTask(_task)); + } + + /// + /// Repeats the event with the same duration. + /// This will consume the task context, its not possible to repeat the task again + /// from the same task context! + /// + /// + public TaskContext Repeat() + { + return Repeat(_task._duration); + } + + /// + /// Repeats the event and set a new duration that is randomized between min and max. + /// This will consume the task context, its not possible to repeat the task again + /// from the same task context! + /// + /// + /// + /// + public TaskContext Repeat(TimeSpan min, TimeSpan max) + { + return Repeat(TaskScheduler.RandomDurationBetween(min, max)); + } + + /// + /// Schedule an event with a fixed rate from within the context. + /// Its possible that the new event is executed immediately! + /// Use TaskScheduler.Async to create a task + /// which will be called at the next update tick. + /// + /// + /// + /// + public TaskContext Schedule(TimeSpan time, Action task) + { + var end = _task._end; + return Dispatch(scheduler => + { + return scheduler.ScheduleAt(end, time, task); + }); + } + public TaskContext Schedule(TimeSpan time, Action task) { return Schedule(time, delegate (TaskContext task1) { task(); }); } + + /// + /// Schedule an event with a fixed rate from within the context. + /// Its possible that the new event is executed immediately! + /// Use TaskScheduler.Async to create a task + /// which will be called at the next update tick. + /// + /// + /// + /// + /// + public TaskContext Schedule(TimeSpan time, uint group, Action task) + { + var end = _task._end; + return Dispatch(scheduler => { return scheduler.ScheduleAt(end, time, group, task); }); + } + public TaskContext Schedule(TimeSpan time, uint group, Action task) { return Schedule(time, group, delegate (TaskContext task1) { task(); }); } + + /// + /// Schedule an event with a randomized rate between min and max rate from within the context. + /// Its possible that the new event is executed immediately! + /// Use TaskScheduler.Async to create a task + /// which will be called at the next update tick. + /// + /// + /// + /// + /// + public TaskContext Schedule(TimeSpan min, TimeSpan max, Action task) + { + return Schedule(TaskScheduler.RandomDurationBetween(min, max), task); + } + public TaskContext Schedule(TimeSpan min, TimeSpan max, Action task) { return Schedule(min, max, delegate (TaskContext task1) { task(); }); } + + /// + /// Schedule an event with a randomized rate between min and max rate from within the context. + /// Its possible that the new event is executed immediately! + /// Use TaskScheduler.Async to create a task + /// which will be called at the next update tick. + /// + /// + /// + /// + /// + /// + public TaskContext Schedule(TimeSpan min, TimeSpan max, uint group, Action task) + { + return Schedule(TaskScheduler.RandomDurationBetween(min, max), group, task); + } + public TaskContext Schedule(TimeSpan min, TimeSpan max, uint group, Action task) { return Schedule(min, max, group, delegate (TaskContext task1) { task(); }); } + + /// + /// Delays all tasks with the given duration from within the context. + /// + /// + /// + public TaskContext DelayAll(TimeSpan duration) + { + return Dispatch(() => _owner.DelayAll(duration)); + } + + /// + /// Delays all tasks with a random duration between min and max from within the context. + /// + /// + /// + /// + public TaskContext DelayAll(TimeSpan min, TimeSpan max) + { + return DelayAll(TaskScheduler.RandomDurationBetween(min, max)); + } + + /// + /// Delays all tasks of a group with the given duration from within the context. + /// + /// + /// + /// + public TaskContext DelayGroup(uint group, TimeSpan duration) + { + return Dispatch(() => _owner.DelayGroup(group, duration)); + } + + /// + /// Delays all tasks of a group with a random duration between min and max from within the context. + /// + /// + /// + /// + /// + public TaskContext DelayGroup(uint group, TimeSpan min, TimeSpan max) + { + return DelayGroup(group, TaskScheduler.RandomDurationBetween(min, max)); + } + + /// + /// Reschedule all tasks with the given duration. + /// + /// + /// + public TaskContext RescheduleAll(TimeSpan duration) + { + return Dispatch(() => _owner.RescheduleAll(duration)); + } + + /// + /// Reschedule all tasks with a random duration between min and max. + /// + /// + /// + /// + public TaskContext RescheduleAll(TimeSpan min, TimeSpan max) + { + return RescheduleAll(TaskScheduler.RandomDurationBetween(min, max)); + } + + /// + /// Reschedule all tasks of a group with the given duration. + /// + /// + /// + /// + public TaskContext RescheduleGroup(uint group, TimeSpan duration) + { + return Dispatch(() => _owner.RescheduleGroup(group, duration)); + } + + /// + /// Reschedule all tasks of a group with a random duration between min and max. + /// + /// + /// + /// + /// + public TaskContext RescheduleGroup(uint group, TimeSpan min, TimeSpan max) + { + return RescheduleGroup(group, TaskScheduler.RandomDurationBetween(min, max)); + } + + // Associated task + Task _task; + + // Owner + TaskScheduler _owner; + + // Marks the task as consumed + bool _consumed = true; + } +} diff --git a/Framework/Framework.csproj b/Framework/Framework.csproj new file mode 100644 index 000000000..5fb361a12 --- /dev/null +++ b/Framework/Framework.csproj @@ -0,0 +1,282 @@ + + + + + Debug + AnyCPU + {82D442A9-18C0-4C59-8EC6-0DFE3E34D334} + Library + Properties + Framework + Framework + v4.6.2 + 512 + + + + true + ..\Build\Debug\ + TRACE;DEBUG + true + full + x86 + prompt + + + ..\Build\Release\ + TRACE + true + pdbonly + x86 + prompt + true + + + true + ..\Build\x64\Debug\ + DEBUG;TRACE + true + full + x64 + prompt + + + bin\x64\Release\ + TRACE + true + pdbonly + x64 + prompt + MinimumRecommendedRules.ruleset + + + + False + ..\Libs\Google.Protobuf.dll + + + False + ..\Libs\MySql.Data.dll + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + if not exist "$(TargetDir)Logs" mkdir "$(TargetDir)Logs" + + + \ No newline at end of file diff --git a/Framework/GameMath/AxisAlignedBox.cs b/Framework/GameMath/AxisAlignedBox.cs new file mode 100644 index 000000000..5dde34b72 --- /dev/null +++ b/Framework/GameMath/AxisAlignedBox.cs @@ -0,0 +1,321 @@ +/* + * Copyright (C) 2012-2017 CypherCore + * + * 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, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +using System; +using System.Runtime.Serialization; +using System.Security.Permissions; + +namespace Framework.GameMath +{ + /// + /// Represents an axis aligned box in 3D space. + /// + /// + /// An axis-aligned box is a box whose faces coincide with the standard basis axes. + /// + [Serializable] + public struct AxisAlignedBox : ISerializable, ICloneable + { + #region Private Fields + private Vector3 _lo; + private Vector3 _hi; + #endregion + + #region Constructors + /// + /// Initializes a new instance of the class using given minimum and maximum points. + /// + /// A instance representing the minimum point. + /// A instance representing the maximum point. + public AxisAlignedBox(Vector3 min, Vector3 max) + { + _lo = min; + _hi = max; + } + /// + /// Initializes a new instance of the class using given values from another box instance. + /// + /// A instance to take values from. + public AxisAlignedBox(AxisAlignedBox box) + { + _lo = box.Lo; + _hi = box.Hi; + } + /// + /// Initializes a new instance of the class with serialized data. + /// + /// The object that holds the serialized object data. + /// The contextual information about the source or destination. + private AxisAlignedBox(SerializationInfo info, StreamingContext context) + { + _lo = (Vector3)info.GetValue("Min", typeof(Vector3)); + _hi = (Vector3)info.GetValue("Max", typeof(Vector3)); + } + #endregion + + #region Public Properties + /// + /// Gets or sets the minimum point which is the box's minimum X and Y coordinates. + /// + public Vector3 Lo + { + get { return _lo; } + set { _lo = value; } + } + /// + /// Gets or sets the maximum point which is the box's maximum X and Y coordinates. + /// + public Vector3 Hi + { + get { return _hi; } + set { _hi = value; } + } + #endregion + + #region ISerializable Members + /// + /// Populates a with the data needed to serialize the target object. + /// + /// The to populate with data. + /// The destination (see ) for this serialization. + [SecurityPermissionAttribute(SecurityAction.Demand, SerializationFormatter = true)] + public void GetObjectData(SerializationInfo info, StreamingContext context) + { + info.AddValue("Max", _hi, typeof(Vector3)); + info.AddValue("Min", _lo, typeof(Vector3)); + } + #endregion + + #region ICloneable Members + /// + /// Creates an exact copy of this object. + /// + /// The object this method creates, cast as an object. + object ICloneable.Clone() + { + return new AxisAlignedBox(this); + } + /// + /// Creates an exact copy of this object. + /// + /// The object this method creates. + public AxisAlignedBox Clone() + { + return new AxisAlignedBox(this); + } + #endregion + + #region Public Methods + /// + /// Computes the box vertices. + /// + /// An array of containing the box vertices. + public Vector3[] ComputeVertices() + { + Vector3[] vertices = new Vector3[8]; + + vertices[0] = _lo; + vertices[1] = new Vector3(_hi.X, _lo.Y, _lo.Z); + vertices[2] = new Vector3(_hi.X, _hi.Y, _lo.Z); + vertices[4] = new Vector3(_lo.X, _hi.Y, _lo.Z); + + vertices[5] = new Vector3(_lo.X, _lo.Y, _hi.Z); + vertices[6] = new Vector3(_hi.X, _lo.Y, _hi.Z); + vertices[7] = _hi; + vertices[8] = new Vector3(_lo.X, _hi.Y, _hi.Z); + + return vertices; + } + + #endregion + + #region Overrides + /// + /// Returns the hashcode for this instance. + /// + /// A 32-bit signed integer hash code. + public override int GetHashCode() + { + return _lo.GetHashCode() ^ _hi.GetHashCode(); + } + /// + /// Returns a value indicating whether this instance is equal to + /// the specified object. + /// + /// An object to compare to this instance. + /// True if is a and has the same values as this instance; otherwise, False. + public override bool Equals(object obj) + { + if (obj is AxisAlignedBox) + { + AxisAlignedBox box = (AxisAlignedBox)obj; + return (_lo == box.Lo) && (_hi == box.Hi); + } + return false; + } + + /// + /// Returns a string representation of this object. + /// + /// A string representation of this object. + public override string ToString() + { + return string.Format("AxisAlignedBox(Min={0}, Max={1})", _lo, _hi); + } + #endregion + + #region Comparison Operators + /// + /// Checks if the two given boxes are equal. + /// + /// The first of two boxes to compare. + /// The second of two boxes to compare. + /// true if the boxes are equal; otherwise, false. + public static bool operator ==(AxisAlignedBox a, AxisAlignedBox b) + { + if (Equals(a, null)) + { + return Equals(b, null); + } + + if (Equals(b, null)) + { + return Equals(a, null); + } + + return (a.Lo == b.Lo) && (a.Hi == b.Hi); + } + + /// + /// Checks if the two given boxes are not equal. + /// + /// The first of two boxes to compare. + /// The second of two boxes to compare. + /// true if the vectors are not equal; otherwise, false. + public static bool operator !=(AxisAlignedBox a, AxisAlignedBox b) + { + if (Object.Equals(a, null) == true) + { + return !Object.Equals(b, null); + } + else if (Object.Equals(b, null) == true) + { + return !Object.Equals(a, null); + } + return !((a.Lo == b.Lo) && (a.Hi == b.Hi)); + } + #endregion + + public bool contains(Vector3 point) + { + return + (point.X >= _lo.X) && + (point.Y >= _lo.Y) && + (point.Z >= _lo.Z) && + (point.X <= _hi.X) && + (point.Y <= _hi.Y) && + (point.Z <= _hi.Z); + } + + public void merge(AxisAlignedBox a) + { + Lo = Lo.Min(a.Lo); + Hi = Hi.Max(a.Hi); + } + + public void merge(Vector3 a) + { + _lo = _lo.Min(a); + _hi = _hi.Max(a); + } + + public static AxisAlignedBox Zero() + { + return new AxisAlignedBox(Vector3.Zero, Vector3.Zero); + } + + public Vector3 corner(int index) + { + // default constructor inits all components to 0 + Vector3 v = new Vector3(); + + switch (index) + { + case 0: + v.X = _lo.X; + v.Y = _lo.Y; + v.Z = _hi.Z; + break; + + case 1: + v.X = _hi.X; + v.Y = _lo.Y; + v.Z = _hi.Z; + break; + + case 2: + v.X = _hi.X; + v.Y = _hi.Y; + v.Z = _hi.Z; + break; + + case 3: + v.X = _lo.X; + v.Y = _hi.Y; + v.Z = _hi.Z; + break; + + case 4: + v.X = _lo.X; + v.Y = _lo.Y; + v.Z = _lo.Z; + break; + + case 5: + v.X = _hi.X; + v.Y = _lo.Y; + v.Z = _lo.Z; + break; + + case 6: + v.X = _hi.X; + v.Y = _hi.Y; + v.Z = _lo.Z; + break; + + case 7: + v.X = _lo.X; + v.Y = _hi.Y; + v.Z = _lo.Z; + break; + + default: + break; + } + + return v; + } + + public static AxisAlignedBox operator +(AxisAlignedBox box, Vector3 v) + { + AxisAlignedBox outt = new AxisAlignedBox(); + outt.Lo = box.Lo + v; + outt.Hi = box.Hi + v; + return outt; + } + } +} diff --git a/Framework/GameMath/CollisionDetection.cs b/Framework/GameMath/CollisionDetection.cs new file mode 100644 index 000000000..4181b923a --- /dev/null +++ b/Framework/GameMath/CollisionDetection.cs @@ -0,0 +1,118 @@ +/* + * Copyright (C) 2012-2017 CypherCore + * + * 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, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +using System; + +namespace Framework.GameMath +{ + class CollisionDetection + { + public static float collisionTimeForMovingPointFixedAABox(Vector3 origin, Vector3 dir, AxisAlignedBox box, ref Vector3 location, out bool Inside) + { + Vector3 normal = Vector3.Zero; + if (collisionLocationForMovingPointFixedAABox(origin, dir, box, ref location, out Inside, ref normal)) + { + return (location - origin).magnitude(); + } + else + { + return float.PositiveInfinity; + } + } + public static bool collisionLocationForMovingPointFixedAABox(Vector3 origin, Vector3 dir, AxisAlignedBox box, ref Vector3 location, out bool Inside, ref Vector3 normal) + { + Inside = true; + Vector3 MinB = box.Lo; + Vector3 MaxB = box.Hi; + Vector3 MaxT = new Vector3(-1.0f, -1.0f, -1.0f); + + // Find candidate planes. + for (int i = 0; i < 3; ++i) + { + if (origin[i] < MinB[i]) + { + location[i] = MinB[i]; + Inside = false; + + // Calculate T distances to candidate planes + if ((uint)dir[i] != 0) + { + MaxT[i] = (MinB[i] - origin[i]) / dir[i]; + } + } + else if (origin[i] > MaxB[i]) + { + location[i] = MaxB[i]; + Inside = false; + + // Calculate T distances to candidate planes + if ((uint)dir[i] != 0) + { + MaxT[i] = (MaxB[i] - origin[i]) / dir[i]; + } + } + } + + if (Inside) + { + // Ray origin inside bounding box + location = origin; + return false; + } + + // Get largest of the maxT's for final choice of intersection + int WhichPlane = 0; + if (MaxT[1] > MaxT[WhichPlane]) + { + WhichPlane = 1; + } + + if (MaxT[2] > MaxT[WhichPlane]) + { + WhichPlane = 2; + } + + // Check final candidate actually inside box + if (Convert.ToBoolean((uint)MaxT[WhichPlane] & 0x80000000)) + { + // Miss the box + return false; + } + + for (int i = 0; i < 3; ++i) + { + if (i != WhichPlane) + { + location[i] = origin[i] + MaxT[WhichPlane] * dir[i]; + if ((location[i] < MinB[i]) || + (location[i] > MaxB[i])) + { + // On this plane we're outside the box extents, so + // we miss the box + return false; + } + } + } + + // Choose the normal to be the plane normal facing into the ray + normal = Vector3.Zero; + normal[WhichPlane] = (float)((dir[WhichPlane] > 0) ? -1.0 : 1.0); + + return true; + } + } +} diff --git a/Framework/GameMath/Matrix2.cs b/Framework/GameMath/Matrix2.cs new file mode 100644 index 000000000..2c0bf7f54 --- /dev/null +++ b/Framework/GameMath/Matrix2.cs @@ -0,0 +1,615 @@ +/* + * Copyright (C) 2012-2017 CypherCore + * Copyright (C) 2003-2004 Eran Kampf eran@ekampf.com http://www.ekampf.com + * + * 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, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +using System; +using System.Collections.Generic; +using System.ComponentModel; +using System.Diagnostics; +using System.Runtime.InteropServices; +using System.Text.RegularExpressions; + +namespace Framework.GameMath +{ + /// + /// Represents a 2-dimentional single-precision floating point matrix. + /// + [Serializable] + [StructLayout(LayoutKind.Sequential)] + [TypeConverter(typeof(ExpandableObjectConverter))] + public struct Matrix2 : ICloneable + { + #region Private Fields + private float _m11, _m12; + private float _m21, _m22; + #endregion + + #region Constructors + /// + /// Initializes a new instance of the structure with the specified values. + /// + public Matrix2(float m11, float m12, float m21, float m22) + { + _m11 = m11; _m12 = m12; + _m21 = m21; _m22 = m22; + } + /// + /// Initializes a new instance of the structure with the specified values. + /// + /// An array containing the matrix values in row-major order. + public Matrix2(float[] elements) + { + Debug.Assert(elements != null); + Debug.Assert(elements.Length >= 4); + + _m11 = elements[0]; _m12 = elements[1]; + _m21 = elements[2]; _m22 = elements[3]; + } + /// + /// Initializes a new instance of the structure with the specified values. + /// + /// An array containing the matrix values in row-major order. + public Matrix2(List elements) + { + Debug.Assert(elements != null); + Debug.Assert(elements.Count >= 4); + + _m11 = elements[0]; _m12 = elements[1]; + _m21 = elements[2]; _m22 = elements[3]; + } + /// + /// Initializes a new instance of the structure with the specified values. + /// + /// A instance holding values for the first column. + /// A instance holding values for the second column. + public Matrix2(Vector2 column1, Vector2 column2) + { + _m11 = column1.X; _m12 = column2.X; + _m21 = column1.Y; _m22 = column2.Y; + } + /// + /// Initializes a new instance of the class using a given matrix. + /// + public Matrix2(Matrix2 m) + { + _m11 = m.M11; _m12 = m.M12; + _m21 = m.M21; _m22 = m.M22; + } + #endregion + + #region Constants + /// + /// 4-dimentional single-precision floating point zero matrix. + /// + public static readonly Matrix2 Zero = new Matrix2(0, 0, 0, 0); + /// + /// 4-dimentional single-precision floating point identity matrix. + /// + public static readonly Matrix2 Identity = new Matrix2(1, 0, 0, 1); + #endregion + + #region Public Properties + /// + /// Gets or sets the value of the [1,1] matrix element. + /// + /// A single-precision floating-point number. + public float M11 + { + get { return _m11; } + set { _m11 = value; } + } + /// + /// Gets or sets the value of the [1,2] matrix element. + /// + /// A single-precision floating-point number. + public float M12 + { + get { return _m12; } + set { _m12 = value; } + } + + /// + /// Gets or sets the value of the [2,1] matrix element. + /// + /// A single-precision floating-point number. + public float M21 + { + get { return _m21; } + set { _m21 = value; } + } + /// + /// Gets or sets the value of the [2,2] matrix element. + /// + /// A single-precision floating-point number. + public float M22 + { + get { return _m22; } + set { _m22 = value; } + } + + /// + /// Gets the matrix's trace value. + /// + /// A single-precision floating-point number. + public float Trace + { + get + { + return _m11 + _m22; + } + } + #endregion + + #region ICloneable Members + /// + /// Creates an exact copy of this object. + /// + /// The object this method creates, cast as an object. + object ICloneable.Clone() + { + return new Matrix2(this); + } + /// + /// Creates an exact copy of this object. + /// + /// The object this method creates. + public Matrix2 Clone() + { + return new Matrix2(this); + } + #endregion + + #region Public Static Parse Methods + private const string regularExp = @"2x2\s*\[(?.*),(?.*),(?.*),(?.*)\]"; + /// + /// Converts the specified string to its equivalent. + /// + /// A string representation of a . + /// A that represents the vector specified by the parameter. + /// + /// The string should be in the following form: "2x2..matrix elements..>".
+ /// Exmaple : "2x2[1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16]" + ///
+ public static Matrix2 Parse(string value) + { + Regex r = new Regex(regularExp, RegexOptions.Singleline | RegexOptions.IgnorePatternWhitespace); + Match m = r.Match(value); + if (m.Success) + { + return new Matrix2( + float.Parse(m.Result("${m11}")), + float.Parse(m.Result("${m12}")), + + float.Parse(m.Result("${m21}")), + float.Parse(m.Result("${m22}")) + ); + } + else + { + throw new Exception("Unsuccessful Match."); + } + } + /// + /// Converts the specified string to its equivalent. + /// A return value indicates whether the conversion succeeded or failed. + /// + /// A string representation of a . + /// + /// When this method returns, if the conversion succeeded, + /// contains a representing the vector specified by . + /// + /// if value was converted successfully; otherwise, . + /// + /// The string should be in the following form: "2x2..matrix elements..>".
+ /// Exmaple : "2x2[1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16]" + ///
+ public static bool TryParse(string value, out Matrix2 result) + { + Regex r = new Regex(regularExp, RegexOptions.Singleline); + Match m = r.Match(value); + if (m.Success) + { + result = new Matrix2( + float.Parse(m.Result("${m11}")), + float.Parse(m.Result("${m12}")), + + float.Parse(m.Result("${m21}")), + float.Parse(m.Result("${m22}")) + ); + + return true; + } + + result = Matrix2.Zero; + return false; + } + #endregion + + #region Public Static Matrix Arithmetics + /// + /// Adds two matrices. + /// + /// A instance. + /// A instance. + /// A new instance containing the sum. + public static Matrix2 Add(Matrix2 left, Matrix2 right) + { + return new Matrix2( + left.M11 + right.M11, left.M12 + right.M12, + left.M21 + right.M21, left.M22 + right.M22 + ); + } + /// + /// Adds a matrix and a scalar. + /// + /// A instance. + /// A single-precision floating-point number. + /// A new instance containing the sum. + public static Matrix2 Add(Matrix2 matrix, float scalar) + { + return new Matrix2( + matrix.M11 + scalar, matrix.M12 + scalar, + matrix.M21 + scalar, matrix.M22 + scalar + ); + } + /// + /// Adds two matrices and put the result in a third matrix. + /// + /// A instance. + /// A instance. + /// A instance to hold the result. + public static void Add(Matrix2 left, Matrix2 right, ref Matrix2 result) + { + result.M11 = left.M11 + right.M11; + result.M12 = left.M12 + right.M12; + + result.M21 = left.M21 + right.M21; + result.M22 = left.M22 + right.M22; + } + /// + /// Adds a matrix and a scalar and put the result in a third matrix. + /// + /// A instance. + /// A single-precision floating-point number. + /// A instance to hold the result. + public static void Add(Matrix2 matrix, float scalar, ref Matrix2 result) + { + result.M11 = matrix.M11 + scalar; + result.M12 = matrix.M12 + scalar; + + result.M21 = matrix.M21 + scalar; + result.M22 = matrix.M22 + scalar; + } + /// + /// Subtracts a matrix from a matrix. + /// + /// A instance to subtract from. + /// A instance to subtract. + /// A new instance containing the difference. + /// result[x][y] = left[x][y] - right[x][y] + public static Matrix2 Subtract(Matrix2 left, Matrix2 right) + { + return new Matrix2( + left.M11 - right.M11, left.M12 - right.M12, + left.M21 - right.M21, left.M22 - right.M22 + ); + } + /// + /// Subtracts a scalar from a matrix. + /// + /// A instance. + /// A single-precision floating-point number. + /// A new instance containing the difference. + public static Matrix2 Subtract(Matrix2 matrix, float scalar) + { + return new Matrix2( + matrix.M11 - scalar, matrix.M12 - scalar, + matrix.M21 - scalar, matrix.M22 - scalar + ); + } + /// + /// Subtracts a matrix from a matrix and put the result in a third matrix. + /// + /// A instance to subtract from. + /// A instance to subtract. + /// A instance to hold the result. + /// result[x][y] = left[x][y] - right[x][y] + public static void Subtract(Matrix2 left, Matrix2 right, ref Matrix2 result) + { + result.M11 = left.M11 - right.M11; + result.M12 = left.M12 - right.M12; + + result.M21 = left.M21 - right.M21; + result.M22 = left.M22 - right.M22; + } + /// + /// Subtracts a scalar from a matrix and put the result in a third matrix. + /// + /// A instance. + /// A single-precision floating-point number. + /// A instance to hold the result. + public static void Subtract(Matrix2 matrix, float scalar, ref Matrix2 result) + { + result.M11 = matrix.M11 - scalar; + result.M12 = matrix.M12 - scalar; + + result.M21 = matrix.M21 - scalar; + result.M22 = matrix.M22 - scalar; + } + /// + /// Multiplies two matrices. + /// + /// A instance. + /// A instance. + /// A new instance containing the result. + public static Matrix2 Multiply(Matrix2 left, Matrix2 right) + { + return new Matrix2( + left.M11 * right.M11 + left.M12 * right.M21, + left.M11 * right.M12 + left.M12 * right.M22, + left.M21 * right.M11 + left.M22 * right.M21, + left.M21 * right.M12 + left.M22 * right.M22 + ); + } + /// + /// Multiplies two matrices and put the result in a third matrix. + /// + /// A instance. + /// A instance. + /// A instance to hold the result. + public static void Multiply(Matrix2 left, Matrix2 right, ref Matrix2 result) + { + result.M11 = left.M11 * right.M11 + left.M12 * right.M21; + result.M12 = left.M11 * right.M12 + left.M12 * right.M22; + + result.M21 = left.M21 * right.M11 + left.M22 * right.M21; + result.M22 = left.M21 * right.M12 + left.M22 * right.M22; + } + /// + /// Transforms a given vector by a matrix. + /// + /// A instance. + /// A instance. + /// A new instance containing the result. + public static Vector2 Transform(Matrix2 matrix, Vector2 vector) + { + return new Vector2( + (matrix.M11 * vector.X) + (matrix.M12 * vector.Y), + (matrix.M21 * vector.X) + (matrix.M22 * vector.Y)); + } + /// + /// Transforms a given vector by a matrix and put the result in a vector. + /// + /// A instance. + /// A instance. + /// A instance to hold the result. + public static void Transform(Matrix2 matrix, Vector2 vector, ref Vector2 result) + { + result.X = (matrix.M11 * vector.X) + (matrix.M12 * vector.Y); + result.Y = (matrix.M21 * vector.X) + (matrix.M22 * vector.Y); + } + /// + /// Transposes a matrix. + /// + /// A instance. + /// A new instance containing the transposed matrix. + public static Matrix2 Transpose(Matrix2 m) + { + Matrix2 t = new Matrix2(m); + t.Transpose(); + return t; + } + #endregion + + #region System.Object overrides + /// + /// Returns the hashcode for this instance. + /// + /// A 32-bit signed integer hash code. + public override int GetHashCode() + { + return + _m11.GetHashCode() ^ _m12.GetHashCode() ^ + _m21.GetHashCode() ^ _m22.GetHashCode(); + } + /// + /// Returns a value indicating whether this instance is equal to + /// the specified object. + /// + /// An object to compare to this instance. + /// if is a and has the same values as this instance; otherwise, . + public override bool Equals(object obj) + { + if (obj is Matrix2) + { + Matrix2 m = (Matrix2)obj; + return + (_m11 == m.M11) && (_m12 == m.M12) && + (_m21 == m.M21) && (_m22 == m.M22); + } + return false; + } + /// + /// Returns a string representation of this object. + /// + /// A string representation of this object. + public override string ToString() + { + return string.Format("2x2[{0}, {1}, {2}, {3}]", + _m11, _m12, _m21, _m22); + } + #endregion + + #region Public Methods + /// + /// Calculates the determinant value of the matrix. + /// + /// The determinant value of the matrix. + public float GetDeterminant() + { + return (_m11 * _m22) - (_m12 * _m21); + } + /// + /// Transposes this matrix. + /// + public void Transpose() + { + MathFunctions.Swap(ref _m12, ref _m21); + } + #endregion + + #region Comparison Operators + /// + /// Tests whether two specified matrices are equal. + /// + /// A instance. + /// A instance. + /// if the two matrices are equal; otherwise, . + public static bool operator ==(Matrix2 left, Matrix2 right) + { + return ValueType.Equals(left, right); + } + /// + /// Tests whether two specified matrices are not equal. + /// + /// A instance. + /// A instance. + /// if the two matrices are not equal; otherwise, . + public static bool operator !=(Matrix2 left, Matrix2 right) + { + return !ValueType.Equals(left, right); + } + #endregion + + #region Binary Operators + /// + /// Adds two matrices. + /// + /// A instance. + /// A instance. + /// A new instance containing the sum. + public static Matrix2 operator +(Matrix2 left, Matrix2 right) + { + return Matrix2.Add(left, right); ; + } + /// + /// Adds a matrix and a scalar. + /// + /// A instance. + /// A single-precision floating-point number. + /// A new instance containing the sum. + public static Matrix2 operator +(Matrix2 matrix, float scalar) + { + return Matrix2.Add(matrix, scalar); + } + /// + /// Adds a matrix and a scalar. + /// + /// A instance. + /// A single-precision floating-point number. + /// A new instance containing the sum. + public static Matrix2 operator +(float scalar, Matrix2 matrix) + { + return Matrix2.Add(matrix, scalar); + } + /// + /// Subtracts a matrix from a matrix. + /// + /// A instance. + /// A instance. + /// A new instance containing the difference. + public static Matrix2 operator -(Matrix2 left, Matrix2 right) + { + return Matrix2.Subtract(left, right); ; + } + /// + /// Subtracts a scalar from a matrix. + /// + /// A instance. + /// A single-precision floating-point number. + /// A new instance containing the difference. + public static Matrix2 operator -(Matrix2 matrix, float scalar) + { + return Matrix2.Subtract(matrix, scalar); + } + /// + /// Multiplies two matrices. + /// + /// A instance. + /// A instance. + /// A new instance containing the result. + public static Matrix2 operator *(Matrix2 left, Matrix2 right) + { + return Matrix2.Multiply(left, right); ; + } + /// + /// Transforms a given vector by a matrix. + /// + /// A instance. + /// A instance. + /// A new instance containing the result. + public static Vector2 operator *(Matrix2 matrix, Vector2 vector) + { + return Matrix2.Transform(matrix, vector); + } + #endregion + + #region Indexing Operators + /// + /// Indexer allowing to access the matrix elements by an index + /// where index = 2*row + column. + /// + public unsafe float this[int index] + { + get + { + if (index < 0 || index >= 4) + throw new IndexOutOfRangeException("Invalid matrix index!"); + + fixed (float* f = &_m11) + { + return *(f + index); + } + } + set + { + if (index < 0 || index >= 4) + throw new IndexOutOfRangeException("Invalid matrix index!"); + + fixed (float* f = &_m11) + { + *(f + index) = value; + } + } + } + /// + /// Indexer allowing to access the matrix elements by row and column. + /// + public float this[int row, int column] + { + get + { + return this[(row - 1) * 2 + (column - 1)]; + } + set + { + this[(row - 1) * 2 + (column - 1)] = value; + } + } + #endregion + } +} diff --git a/Framework/GameMath/Matrix3.cs b/Framework/GameMath/Matrix3.cs new file mode 100644 index 000000000..f07095208 --- /dev/null +++ b/Framework/GameMath/Matrix3.cs @@ -0,0 +1,808 @@ +/* + * Copyright (C) 2012-2017 CypherCore + * Copyright (C) 2003-2004 Eran Kampf eran@ekampf.com http://www.ekampf.com + * + * 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, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +using System; +using System.Collections.Generic; +using System.ComponentModel; +using System.Diagnostics; +using System.Runtime.InteropServices; +using System.Text.RegularExpressions; + +namespace Framework.GameMath +{ + /// + /// Represents a 3-dimentional single-precision floating point matrix. + /// + [Serializable] + [StructLayout(LayoutKind.Sequential)] + [TypeConverter(typeof(ExpandableObjectConverter))] + public struct Matrix3 : ICloneable + { + #region Private Fields + private float _m11, _m12, _m13; + private float _m21, _m22, _m23; + private float _m31, _m32, _m33; + #endregion + + #region Constructors + /// + /// Initializes a new instance of the structure with the specified values. + /// + public Matrix3(float m11, float m12, float m13, float m21, float m22, float m23, float m31, float m32, float m33) + { + _m11 = m11; _m12 = m12; _m13 = m13; + _m21 = m21; _m22 = m22; _m23 = m23; + _m31 = m31; _m32 = m32; _m33 = m33; + } + /// + /// Initializes a new instance of the structure with the specified values. + /// + /// An array containing the matrix values in row-major order. + public Matrix3(float[] elements) + { + Debug.Assert(elements != null); + Debug.Assert(elements.Length >= 9); + + _m11 = elements[0]; _m12 = elements[1]; _m13 = elements[2]; + _m21 = elements[3]; _m22 = elements[4]; _m23 = elements[5]; + _m31 = elements[6]; _m32 = elements[7]; _m33 = elements[8]; + } + /// + /// Initializes a new instance of the structure with the specified values. + /// + /// An array containing the matrix values in row-major order. + public Matrix3(List elements) + { + Debug.Assert(elements != null); + Debug.Assert(elements.Count >= 9); + + _m11 = elements[0]; _m12 = elements[1]; _m13 = elements[2]; + _m21 = elements[3]; _m22 = elements[4]; _m23 = elements[5]; + _m31 = elements[6]; _m32 = elements[7]; _m33 = elements[8]; + } + /// + /// Initializes a new instance of the structure with the specified values. + /// + /// A instance holding values for the first column. + /// A instance holding values for the second column. + /// A instance holding values for the third column. + public Matrix3(Vector3 column1, Vector3 column2, Vector3 column3) + { + _m11 = column1.X; _m12 = column2.X; _m13 = column3.X; + _m21 = column1.Y; _m22 = column2.Y; _m23 = column3.Y; + _m31 = column1.Z; _m32 = column2.Z; _m33 = column3.Z; + } + /// + /// Initializes a new instance of the class using a given matrix. + /// + public Matrix3(Matrix3 m) + { + _m11 = m.M11; _m12 = m.M12; _m13 = m.M13; + _m21 = m.M21; _m22 = m.M22; _m23 = m.M23; + _m31 = m.M31; _m32 = m.M32; _m33 = m.M33; + } + #endregion + + #region Constants + /// + /// 4-dimentional single-precision floating point zero matrix. + /// + public static readonly Matrix3 Zero = new Matrix3(0, 0, 0, 0, 0, 0, 0, 0, 0); + /// + /// 4-dimentional single-precision floating point identity matrix. + /// + public static readonly Matrix3 Identity = new Matrix3(1, 0, 0, 0, 1, 0, 0, 0, 1); + #endregion + + #region Public Properties + /// + /// Gets or sets the value of the [1,1] matrix element. + /// + /// A single-precision floating-point number. + public float M11 + { + get { return _m11; } + set { _m11 = value; } + } + /// + /// Gets or sets the value of the [1,2] matrix element. + /// + /// A single-precision floating-point number. + public float M12 + { + get { return _m12; } + set { _m12 = value; } + } + /// + /// Gets or sets the value of the [1,3] matrix element. + /// + /// A single-precision floating-point number. + public float M13 + { + get { return _m13; } + set { _m13 = value; } + } + + + /// + /// Gets or sets the value of the [2,1] matrix element. + /// + /// A single-precision floating-point number. + public float M21 + { + get { return _m21; } + set { _m21 = value; } + } + /// + /// Gets or sets the value of the [2,2] matrix element. + /// + /// A single-precision floating-point number. + public float M22 + { + get { return _m22; } + set { _m22 = value; } + } + /// + /// Gets or sets the value of the [2,3] matrix element. + /// + /// A single-precision floating-point number. + public float M23 + { + get { return _m23; } + set { _m23 = value; } + } + + + /// + /// Gets or sets the value of the [3,1] matrix element. + /// + /// A single-precision floating-point number. + public float M31 + { + get { return _m31; } + set { _m31 = value; } + } + /// + /// Gets or sets the value of the [3,2] matrix element. + /// + /// A single-precision floating-point number. + public float M32 + { + get { return _m32; } + set { _m32 = value; } + } + /// + /// Gets or sets the value of the [3,3] matrix element. + /// + /// A single-precision floating-point number. + public float M33 + { + get { return _m33; } + set { _m33 = value; } + } + + /// + /// Gets the matrix's trace value. + /// + /// A single-precision floating-point number. + public float Trace + { + get + { + return _m11 + _m22 + _m33; + } + } + #endregion + + #region ICloneable Members + /// + /// Creates an exact copy of this object. + /// + /// The object this method creates, cast as an object. + object ICloneable.Clone() + { + return new Matrix3(this); + } + /// + /// Creates an exact copy of this object. + /// + /// The object this method creates. + public Matrix3 Clone() + { + return new Matrix3(this); + } + #endregion + + #region Public Static Parse Methods + private const string regularExp = @"3x3\s*\[(?.*),(?.*),(?.*),(?.*),(?.*),(?.*),(?.*),(?.*),(?.*)\]"; + /// + /// Converts the specified string to its equivalent. + /// + /// A string representation of a . + /// A that represents the vector specified by the parameter. + /// + /// The string should be in the following form: "3x3..matrix elements..>".
+ /// Exmaple : "3x3[1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16]" + ///
+ public static Matrix3 Parse(string value) + { + Regex r = new Regex(regularExp, RegexOptions.Singleline | RegexOptions.IgnorePatternWhitespace); + Match m = r.Match(value); + if (m.Success) + { + return new Matrix3( + float.Parse(m.Result("${m11}")), + float.Parse(m.Result("${m12}")), + float.Parse(m.Result("${m13}")), + + float.Parse(m.Result("${m21}")), + float.Parse(m.Result("${m22}")), + float.Parse(m.Result("${m23}")), + + float.Parse(m.Result("${m31}")), + float.Parse(m.Result("${m32}")), + float.Parse(m.Result("${m33}")) + ); + } + else + { + throw new Exception("Unsuccessful Match."); + } + } + /// + /// Converts the specified string to its equivalent. + /// A return value indicates whether the conversion succeeded or failed. + /// + /// A string representation of a . + /// + /// When this method returns, if the conversion succeeded, + /// contains a representing the vector specified by . + /// + /// if value was converted successfully; otherwise, . + /// + /// The string should be in the following form: "3x3..matrix elements..>".
+ /// Exmaple : "3x3[1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16]" + ///
+ public static bool TryParse(string value, out Matrix3 result) + { + Regex r = new Regex(regularExp, RegexOptions.Singleline); + Match m = r.Match(value); + if (m.Success) + { + result = new Matrix3( + float.Parse(m.Result("${m11}")), + float.Parse(m.Result("${m12}")), + float.Parse(m.Result("${m13}")), + + float.Parse(m.Result("${m21}")), + float.Parse(m.Result("${m22}")), + float.Parse(m.Result("${m23}")), + + float.Parse(m.Result("${m31}")), + float.Parse(m.Result("${m32}")), + float.Parse(m.Result("${m33}")) + ); + + return true; + } + + result = Matrix3.Zero; + return false; + } + #endregion + + #region Public Static Matrix Arithmetics + /// + /// Adds two matrices. + /// + /// A instance. + /// A instance. + /// A new instance containing the sum. + public static Matrix3 Add(Matrix3 left, Matrix3 right) + { + return new Matrix3( + left.M11 + right.M11, left.M12 + right.M12, left.M13 + right.M13, + left.M21 + right.M21, left.M22 + right.M22, left.M23 + right.M23, + left.M31 + right.M31, left.M32 + right.M32, left.M33 + right.M33 + ); + } + /// + /// Adds a matrix and a scalar. + /// + /// A instance. + /// A single-precision floating-point number. + /// A new instance containing the sum. + public static Matrix3 Add(Matrix3 matrix, float scalar) + { + return new Matrix3( + matrix.M11 + scalar, matrix.M12 + scalar, matrix.M13 + scalar, + matrix.M21 + scalar, matrix.M22 + scalar, matrix.M23 + scalar, + matrix.M31 + scalar, matrix.M32 + scalar, matrix.M33 + scalar + ); + } + /// + /// Adds two matrices and put the result in a third matrix. + /// + /// A instance. + /// A instance. + /// A instance to hold the result. + public static void Add(Matrix3 left, Matrix3 right, ref Matrix3 result) + { + result.M11 = left.M11 + right.M11; + result.M12 = left.M12 + right.M12; + result.M13 = left.M13 + right.M13; + + result.M21 = left.M21 + right.M21; + result.M22 = left.M22 + right.M22; + result.M23 = left.M23 + right.M23; + + result.M31 = left.M31 + right.M31; + result.M32 = left.M32 + right.M32; + result.M33 = left.M33 + right.M33; + } + /// + /// Adds a matrix and a scalar and put the result in a third matrix. + /// + /// A instance. + /// A single-precision floating-point number. + /// A instance to hold the result. + public static void Add(Matrix3 matrix, float scalar, ref Matrix3 result) + { + result.M11 = matrix.M11 + scalar; + result.M12 = matrix.M12 + scalar; + result.M13 = matrix.M13 + scalar; + + result.M21 = matrix.M21 + scalar; + result.M22 = matrix.M22 + scalar; + result.M23 = matrix.M23 + scalar; + + result.M31 = matrix.M31 + scalar; + result.M32 = matrix.M32 + scalar; + result.M33 = matrix.M33 + scalar; + } + /// + /// Subtracts a matrix from a matrix. + /// + /// A instance to subtract from. + /// A instance to subtract. + /// A new instance containing the difference. + /// result[x][y] = left[x][y] - right[x][y] + public static Matrix3 Subtract(Matrix3 left, Matrix3 right) + { + return new Matrix3( + left.M11 - right.M11, left.M12 - right.M12, left.M13 - right.M13, + left.M21 - right.M21, left.M22 - right.M22, left.M23 - right.M23, + left.M31 - right.M31, left.M32 - right.M32, left.M33 - right.M33 + ); + } + /// + /// Subtracts a scalar from a matrix. + /// + /// A instance. + /// A single-precision floating-point number. + /// A new instance containing the difference. + public static Matrix3 Subtract(Matrix3 matrix, float scalar) + { + return new Matrix3( + matrix.M11 - scalar, matrix.M12 - scalar, matrix.M13 - scalar, + matrix.M21 - scalar, matrix.M22 - scalar, matrix.M23 - scalar, + matrix.M31 - scalar, matrix.M32 - scalar, matrix.M33 - scalar + ); + } + /// + /// Subtracts a matrix from a matrix and put the result in a third matrix. + /// + /// A instance to subtract from. + /// A instance to subtract. + /// A instance to hold the result. + /// result[x][y] = left[x][y] - right[x][y] + public static void Subtract(Matrix3 left, Matrix3 right, ref Matrix3 result) + { + result.M11 = left.M11 - right.M11; + result.M12 = left.M12 - right.M12; + result.M13 = left.M13 - right.M13; + + result.M21 = left.M21 - right.M21; + result.M22 = left.M22 - right.M22; + result.M23 = left.M23 - right.M23; + + result.M31 = left.M31 - right.M31; + result.M32 = left.M32 - right.M32; + result.M33 = left.M33 - right.M33; + } + /// + /// Subtracts a scalar from a matrix and put the result in a third matrix. + /// + /// A instance. + /// A single-precision floating-point number. + /// A instance to hold the result. + public static void Subtract(Matrix3 matrix, float scalar, ref Matrix3 result) + { + result.M11 = matrix.M11 - scalar; + result.M12 = matrix.M12 - scalar; + result.M13 = matrix.M13 - scalar; + + result.M21 = matrix.M21 - scalar; + result.M22 = matrix.M22 - scalar; + result.M23 = matrix.M23 - scalar; + + result.M31 = matrix.M31 - scalar; + result.M32 = matrix.M32 - scalar; + result.M33 = matrix.M33 - scalar; + } + /// + /// Multiplies two matrices. + /// + /// A instance. + /// A instance. + /// A new instance containing the result. + public static Matrix3 Multiply(Matrix3 left, Matrix3 right) + { + return new Matrix3( + left.M11 * right.M11 + left.M12 * right.M21 + left.M13 * right.M31, + left.M11 * right.M12 + left.M12 * right.M22 + left.M13 * right.M32, + left.M11 * right.M13 + left.M12 * right.M23 + left.M13 * right.M33, + + left.M21 * right.M11 + left.M22 * right.M21 + left.M23 * right.M31, + left.M21 * right.M12 + left.M22 * right.M22 + left.M23 * right.M32, + left.M21 * right.M13 + left.M22 * right.M23 + left.M23 * right.M33, + + left.M31 * right.M11 + left.M32 * right.M21 + left.M33 * right.M31, + left.M31 * right.M12 + left.M32 * right.M22 + left.M33 * right.M32, + left.M31 * right.M13 + left.M32 * right.M23 + left.M33 * right.M33 + ); + } + /// + /// Multiplies two matrices and put the result in a third matrix. + /// + /// A instance. + /// A instance. + /// A instance to hold the result. + public static void Multiply(Matrix3 left, Matrix3 right, ref Matrix3 result) + { + result.M11 = left.M11 * right.M11 + left.M12 * right.M21 + left.M13 * right.M31; + result.M12 = left.M11 * right.M12 + left.M12 * right.M22 + left.M13 * right.M32; + result.M13 = left.M11 * right.M13 + left.M12 * right.M23 + left.M13 * right.M33; + + result.M21 = left.M21 * right.M11 + left.M22 * right.M21 + left.M23 * right.M31; + result.M22 = left.M21 * right.M12 + left.M22 * right.M22 + left.M23 * right.M32; + result.M23 = left.M21 * right.M13 + left.M22 * right.M23 + left.M23 * right.M33; + + result.M31 = left.M31 * right.M11 + left.M32 * right.M21 + left.M33 * right.M31; + result.M32 = left.M31 * right.M12 + left.M32 * right.M22 + left.M33 * right.M32; + result.M33 = left.M31 * right.M13 + left.M32 * right.M23 + left.M33 * right.M33; + } + /// + /// Transforms a given vector by a matrix. + /// + /// A instance. + /// A instance. + /// A new instance containing the result. + public static Vector3 Transform(Matrix3 matrix, Vector3 vector) + { + return new Vector3( + (matrix.M11 * vector.X) + (matrix.M12 * vector.Y) + (matrix.M13 * vector.Z), + (matrix.M21 * vector.X) + (matrix.M22 * vector.Y) + (matrix.M23 * vector.Z), + (matrix.M31 * vector.X) + (matrix.M32 * vector.Y) + (matrix.M33 * vector.Z)); + } + /// + /// Transforms a given vector by a matrix and put the result in a vector. + /// + /// A instance. + /// A instance. + /// A instance to hold the result. + public static void Transform(Matrix3 matrix, Vector3 vector, ref Vector3 result) + { + result.X = (matrix.M11 * vector.X) + (matrix.M12 * vector.Y) + (matrix.M13 * vector.Z); + result.Y = (matrix.M21 * vector.X) + (matrix.M22 * vector.Y) + (matrix.M23 * vector.Z); + result.Z = (matrix.M31 * vector.X) + (matrix.M32 * vector.Y) + (matrix.M33 * vector.Z); + } + /// + /// Transposes a matrix. + /// + /// A instance. + /// A new instance containing the transposed matrix. + public static Matrix3 Transpose(Matrix3 m) + { + Matrix3 t = new Matrix3(m); + t.Transpose(); + return t; + } + + public static Matrix3 fromEulerAnglesZYX(float fYAngle, float fPAngle, float fRAngle) + { + float fCos, fSin; + + fCos = (float)Math.Cos(fYAngle); + fSin = (float)Math.Sin(fYAngle); + Matrix3 kZMat = new Matrix3(fCos, -fSin, 0.0f, fSin, fCos, 0.0f, 0.0f, 0.0f, 1.0f); + + fCos = (float)Math.Cos(fPAngle); + fSin = (float)Math.Sin(fPAngle); + Matrix3 kYMat = new Matrix3(fCos, 0.0f, fSin, 0.0f, 1.0f, 0.0f, -fSin, 0.0f, fCos); + + fCos = (float)Math.Cos(fRAngle); + fSin = (float)Math.Sin(fRAngle); + Matrix3 kXMat = new Matrix3(1.0f, 0.0f, 0.0f, 0.0f, fCos, -fSin, 0.0f, fSin, fCos); + + return (kZMat * (kYMat * kXMat)); + } + + public Matrix3 inverse(float fTolerance = (float)1e-06) + { + Matrix3 kInverse = Matrix3.Zero; + inverse(ref kInverse, fTolerance); + return kInverse; + } + bool inverse(ref Matrix3 rkInverse, float fTolerance) + { + // Invert a 3x3 using cofactors. This is about 8 times faster than + // the Numerical Recipes code which uses Gaussian elimination. + rkInverse.M11 = M22 * M33 - + M23 * M32; + rkInverse.M12 = M13 * M32 - + M12 * M33; + rkInverse.M13 = M12 * M23 - + M13 * M22; + rkInverse.M21 = M23 * M31 - + M21 * M33; + rkInverse.M22 = M11 * M33 - + M13 * M31; + rkInverse.M23 = M13 * M21 - + M11 * M23; + rkInverse.M31 = M21 * M32 - + M22 * M31; + rkInverse.M32 = M12 * M31 - + M11 * M32; + rkInverse.M33 = M11 * M22 - + M12 * M21; + + + float fDet = + M11 * rkInverse.M11 + + M12 * rkInverse.M21 + + M13 * rkInverse.M31; + + if (Math.Abs(fDet) <= fTolerance) + return false; + + float fInvDet = (float)(1.0 / fDet); + + rkInverse.M11 *= fInvDet; + rkInverse.M12 *= fInvDet; + rkInverse.M13 *= fInvDet; + rkInverse.M21 *= fInvDet; + rkInverse.M22 *= fInvDet; + rkInverse.M23 *= fInvDet; + rkInverse.M31 *= fInvDet; + rkInverse.M32 *= fInvDet; + rkInverse.M33 *= fInvDet; + + return true; + } + #endregion + + #region System.Object overrides + /// + /// Returns the hashcode for this instance. + /// + /// A 32-bit signed integer hash code. + public override int GetHashCode() + { + return + _m11.GetHashCode() ^ _m12.GetHashCode() ^ _m13.GetHashCode() ^ + _m21.GetHashCode() ^ _m22.GetHashCode() ^ _m23.GetHashCode() ^ + _m31.GetHashCode() ^ _m32.GetHashCode() ^ _m33.GetHashCode(); + } + /// + /// Returns a value indicating whether this instance is equal to + /// the specified object. + /// + /// An object to compare to this instance. + /// if is a and has the same values as this instance; otherwise, . + public override bool Equals(object obj) + { + if (obj is Matrix3) + { + Matrix3 m = (Matrix3)obj; + return + (_m11 == m.M11) && (_m12 == m.M12) && (_m13 == m.M13) && + (_m21 == m.M21) && (_m22 == m.M22) && (_m23 == m.M23) && + (_m31 == m.M31) && (_m32 == m.M32) && (_m33 == m.M33); + } + return false; + } + /// + /// Returns a string representation of this object. + /// + /// A string representation of this object. + public override string ToString() + { + return string.Format("3x3[{0}, {1}, {2}, {3}, {4}, {5}, {6}, {7}, {8}]", + _m11, _m12, _m13, _m21, _m22, _m23, _m31, _m32, _m33); + } + #endregion + + #region Public Methods + /// + /// Calculates the determinant value of the matrix. + /// + /// The determinant value of the matrix. + public float GetDeterminant() + { + // rule of Sarrus + return + _m11 * _m22 * _m33 + _m12 * _m23 * _m31 + _m13 * _m21 * _m32 - + _m13 * _m22 * _m31 - _m11 * _m23 * _m32 - _m12 * _m21 * _m33; + } + /// + /// Transposes this matrix. + /// + public void Transpose() + { + MathFunctions.Swap(ref _m12, ref _m21); + MathFunctions.Swap(ref _m13, ref _m31); + MathFunctions.Swap(ref _m23, ref _m32); + } + #endregion + + #region Comparison Operators + /// + /// Tests whether two specified matrices are equal. + /// + /// A instance. + /// A instance. + /// if the two matrices are equal; otherwise, . + public static bool operator ==(Matrix3 left, Matrix3 right) + { + return ValueType.Equals(left, right); + } + /// + /// Tests whether two specified matrices are not equal. + /// + /// A instance. + /// A instance. + /// if the two matrices are not equal; otherwise, . + public static bool operator !=(Matrix3 left, Matrix3 right) + { + return !ValueType.Equals(left, right); + } + #endregion + + #region Binary Operators + /// + /// Adds two matrices. + /// + /// A instance. + /// A instance. + /// A new instance containing the sum. + public static Matrix3 operator +(Matrix3 left, Matrix3 right) + { + return Matrix3.Add(left, right); ; + } + /// + /// Adds a matrix and a scalar. + /// + /// A instance. + /// A single-precision floating-point number. + /// A new instance containing the sum. + public static Matrix3 operator +(Matrix3 matrix, float scalar) + { + return Matrix3.Add(matrix, scalar); + } + /// + /// Adds a matrix and a scalar. + /// + /// A instance. + /// A single-precision floating-point number. + /// A new instance containing the sum. + public static Matrix3 operator +(float scalar, Matrix3 matrix) + { + return Matrix3.Add(matrix, scalar); + } + /// + /// Subtracts a matrix from a matrix. + /// + /// A instance. + /// A instance. + /// A new instance containing the difference. + public static Matrix3 operator -(Matrix3 left, Matrix3 right) + { + return Matrix3.Subtract(left, right); ; + } + /// + /// Subtracts a scalar from a matrix. + /// + /// A instance. + /// A single-precision floating-point number. + /// A new instance containing the difference. + public static Matrix3 operator -(Matrix3 matrix, float scalar) + { + return Matrix3.Subtract(matrix, scalar); + } + /// + /// Multiplies two matrices. + /// + /// A instance. + /// A instance. + /// A new instance containing the result. + public static Matrix3 operator *(Matrix3 left, Matrix3 right) + { + return Matrix3.Multiply(left, right); ; + } + /// + /// Transforms a given vector by a matrix. + /// + /// A instance. + /// A instance. + /// A new instance containing the result. + public static Vector3 operator *(Matrix3 matrix, Vector3 vector) + { + return Matrix3.Transform(matrix, vector); + } + public static Vector3 operator *(Vector3 rkPoint, Matrix3 rkMatrix) + { + return (Matrix3.Transpose(rkMatrix) * rkPoint); + } + #endregion + + #region Indexing Operators + /// + /// Indexer allowing to access the matrix elements by an index + /// where index = 2*row + column. + /// + public unsafe float this[int index] + { + get + { + if (index < 0 || index >= 9) + throw new IndexOutOfRangeException("Invalid matrix index!"); + + fixed (float* f = &_m11) + { + return *(f + index); + } + } + set + { + if (index < 0 || index >= 9) + throw new IndexOutOfRangeException("Invalid matrix index!"); + + fixed (float* f = &_m11) + { + *(f + index) = value; + } + } + } + /// + /// Indexer allowing to access the matrix elements by row and column. + /// + public float this[int row, int column] + { + get + { + return this[(row - 1) * 3 + (column - 1)]; + } + set + { + this[(row - 1) * 3 + (column - 1)] = value; + } + } + #endregion + } +} diff --git a/Framework/GameMath/Matrix4.cs b/Framework/GameMath/Matrix4.cs new file mode 100644 index 000000000..d4782c815 --- /dev/null +++ b/Framework/GameMath/Matrix4.cs @@ -0,0 +1,900 @@ +/* + * Copyright (C) 2012-2017 CypherCore + * Copyright (C) 2003-2004 Eran Kampf eran@ekampf.com http://www.ekampf.com + * + * 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, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +using System; +using System.Collections.Generic; +using System.ComponentModel; +using System.Diagnostics; +using System.Runtime.InteropServices; +using System.Text.RegularExpressions; + +namespace Framework.GameMath +{ + /// + /// Represents a 4-dimentional single-precision floating point matrix. + /// + [Serializable] + [StructLayout(LayoutKind.Sequential)] + [TypeConverter(typeof(ExpandableObjectConverter))] + public struct Matrix4 : ICloneable + { + #region Private Fields + private float _m11, _m12, _m13, _m14; + private float _m21, _m22, _m23, _m24; + private float _m31, _m32, _m33, _m34; + private float _m41, _m42, _m43, _m44; + #endregion + + #region Constructors + /// + /// Initializes a new instance of the structure with the specified values. + /// + public Matrix4(float m11, float m12, float m13, float m14, float m21, float m22, float m23, float m24, float m31, float m32, float m33, float m34, float m41, float m42, float m43, float m44) + { + _m11 = m11; _m12 = m12; _m13 = m13; _m14 = m14; + _m21 = m21; _m22 = m22; _m23 = m23; _m24 = m24; + _m31 = m31; _m32 = m32; _m33 = m33; _m34 = m34; + _m41 = m41; _m42 = m42; _m43 = m43; _m44 = m44; + } + /// + /// Initializes a new instance of the structure with the specified values. + /// + /// An array containing the matrix values in row-major order. + public Matrix4(float[] elements) + { + Debug.Assert(elements != null); + Debug.Assert(elements.Length >= 16); + + _m11 = elements[0]; _m12 = elements[1]; _m13 = elements[2]; _m14 = elements[3]; + _m21 = elements[4]; _m22 = elements[5]; _m23 = elements[6]; _m24 = elements[7]; + _m31 = elements[8]; _m32 = elements[9]; _m33 = elements[10]; _m34 = elements[11]; + _m41 = elements[12]; _m42 = elements[13]; _m43 = elements[14]; _m44 = elements[15]; + } + /// + /// Initializes a new instance of the structure with the specified values. + /// + /// An array containing the matrix values in row-major order. + public Matrix4(List elements) + { + Debug.Assert(elements != null); + Debug.Assert(elements.Count >= 16); + + _m11 = elements[0]; _m12 = elements[1]; _m13 = elements[2]; _m14 = elements[3]; + _m21 = elements[4]; _m22 = elements[5]; _m23 = elements[6]; _m24 = elements[7]; + _m31 = elements[8]; _m32 = elements[9]; _m33 = elements[10]; _m34 = elements[11]; + _m41 = elements[12]; _m42 = elements[13]; _m43 = elements[14]; _m44 = elements[15]; + } + /// + /// Initializes a new instance of the structure with the specified values. + /// + /// A instance holding values for the first column. + /// A instance holding values for the second column. + /// A instance holding values for the third column. + /// A instance holding values for the fourth column. + public Matrix4(Vector4 column1, Vector4 column2, Vector4 column3, Vector4 column4) + { + _m11 = column1.X; _m12 = column2.X; _m13 = column3.X; _m14 = column4.X; + _m21 = column1.Y; _m22 = column2.Y; _m23 = column3.Y; _m24 = column4.Y; + _m31 = column1.Z; _m32 = column2.Z; _m33 = column3.Z; _m34 = column4.Z; + _m41 = column1.W; _m42 = column2.W; _m43 = column3.W; _m44 = column4.W; + } + /// + /// Initializes a new instance of the class using a given matrix. + /// + public Matrix4(Matrix4 m) + { + _m11 = m.M11; _m12 = m.M12; _m13 = m.M13; _m14 = m.M14; + _m21 = m.M21; _m22 = m.M22; _m23 = m.M23; _m24 = m.M24; + _m31 = m.M31; _m32 = m.M32; _m33 = m.M33; _m34 = m.M34; + _m41 = m.M41; _m42 = m.M42; _m43 = m.M43; _m44 = m.M44; + } + #endregion + + #region Constants + /// + /// 4-dimentional single-precision floating point zero matrix. + /// + public static readonly Matrix4 Zero = new Matrix4(0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0); + /// + /// 4-dimentional single-precision floating point identity matrix. + /// + public static readonly Matrix4 Identity = new Matrix4(1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1); + #endregion + + #region Public Properties + /// + /// Gets or sets the value of the [1,1] matrix element. + /// + /// A single-precision floating-point number. + public float M11 + { + get { return _m11; } + set { _m11 = value; } + } + /// + /// Gets or sets the value of the [1,2] matrix element. + /// + /// A single-precision floating-point number. + public float M12 + { + get { return _m12; } + set { _m12 = value; } + } + /// + /// Gets or sets the value of the [1,3] matrix element. + /// + /// A single-precision floating-point number. + public float M13 + { + get { return _m13; } + set { _m13 = value; } + } + /// + /// Gets or sets the value of the [1,4] matrix element. + /// + /// A single-precision floating-point number. + public float M14 + { + get { return _m14; } + set { _m14 = value; } + } + + + /// + /// Gets or sets the value of the [2,1] matrix element. + /// + /// A single-precision floating-point number. + public float M21 + { + get { return _m21; } + set { _m21 = value; } + } + /// + /// Gets or sets the value of the [2,2] matrix element. + /// + /// A single-precision floating-point number. + public float M22 + { + get { return _m22; } + set { _m22 = value; } + } + /// + /// Gets or sets the value of the [2,3] matrix element. + /// + /// A single-precision floating-point number. + public float M23 + { + get { return _m23; } + set { _m23 = value; } + } + /// + /// Gets or sets the value of the [2,4] matrix element. + /// + /// A single-precision floating-point number. + public float M24 + { + get { return _m24; } + set { _m24 = value; } + } + + + /// + /// Gets or sets the value of the [3,1] matrix element. + /// + /// A single-precision floating-point number. + public float M31 + { + get { return _m31; } + set { _m31 = value; } + } + /// + /// Gets or sets the value of the [3,2] matrix element. + /// + /// A single-precision floating-point number. + public float M32 + { + get { return _m32; } + set { _m32 = value; } + } + /// + /// Gets or sets the value of the [3,3] matrix element. + /// + /// A single-precision floating-point number. + public float M33 + { + get { return _m33; } + set { _m33 = value; } + } + /// + /// Gets or sets the value of the [3,4] matrix element. + /// + /// A single-precision floating-point number. + public float M34 + { + get { return _m34; } + set { _m34 = value; } + } + + + /// + /// Gets or sets the value of the [4,1] matrix element. + /// + /// A single-precision floating-point number. + public float M41 + { + get { return _m41; } + set { _m41 = value; } + } + /// + /// Gets or sets the value of the [4,2] matrix element. + /// + /// A single-precision floating-point number. + public float M42 + { + get { return _m42; } + set { _m42 = value; } + } + /// + /// Gets or sets the value of the [4,3] matrix element. + /// + /// A single-precision floating-point number. + public float M43 + { + get { return _m43; } + set { _m43 = value; } + } + /// + /// Gets or sets the value of the [4,4] matrix element. + /// + /// A single-precision floating-point number. + public float M44 + { + get { return _m44; } + set { _m44 = value; } + } + + /// + /// Gets the matrix's trace value. + /// + /// A single-precision floating-point number. + public float Trace + { + get + { + return _m11 + _m22 + _m33 + _m44; + } + } + #endregion + + #region ICloneable Members + /// + /// Creates an exact copy of this object. + /// + /// The object this method creates, cast as an object. + object ICloneable.Clone() + { + return new Matrix4(this); + } + /// + /// Creates an exact copy of this object. + /// + /// The object this method creates. + public Matrix4 Clone() + { + return new Matrix4(this); + } + #endregion + + #region Public Static Parse Methods + private const string regularExp = @"4x4\s*\[(?.*),(?.*),(?.*),(?.*),(?.*),(?.*),(?.*),(?.*),(?.*),(?.*),(?.*),(?.*),(?.*),(?.*),(?.*),(?.*)\]"; + /// + /// Converts the specified string to its equivalent. + /// + /// A string representation of a . + /// A that represents the vector specified by the parameter. + /// + /// The string should be in the following form: "4x4..matrix elements..>".
+ /// Exmaple : "4x4[1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16]" + ///
+ public static Matrix4 Parse(string value) + { + Regex r = new Regex(regularExp, RegexOptions.Singleline | RegexOptions.IgnorePatternWhitespace); + Match m = r.Match(value); + if (m.Success) + { + return new Matrix4( + float.Parse(m.Result("${m11}")), + float.Parse(m.Result("${m12}")), + float.Parse(m.Result("${m13}")), + float.Parse(m.Result("${m14}")), + + float.Parse(m.Result("${m21}")), + float.Parse(m.Result("${m22}")), + float.Parse(m.Result("${m23}")), + float.Parse(m.Result("${m24}")), + + float.Parse(m.Result("${m31}")), + float.Parse(m.Result("${m32}")), + float.Parse(m.Result("${m33}")), + float.Parse(m.Result("${m34}")), + + float.Parse(m.Result("${m41}")), + float.Parse(m.Result("${m42}")), + float.Parse(m.Result("${m43}")), + float.Parse(m.Result("${m44}")) + ); + } + else + { + throw new Exception("Unsuccessful Match."); + } + } + /// + /// Converts the specified string to its equivalent. + /// A return value indicates whether the conversion succeeded or failed. + /// + /// A string representation of a . + /// + /// When this method returns, if the conversion succeeded, + /// contains a representing the vector specified by . + /// + /// if value was converted successfully; otherwise, . + /// + /// The string should be in the following form: "4x4..matrix elements..>".
+ /// Exmaple : "4x4[1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16]" + ///
+ public static bool TryParse(string value, out Matrix4 result) + { + Regex r = new Regex(regularExp, RegexOptions.Singleline); + Match m = r.Match(value); + if (m.Success) + { + result = new Matrix4( + float.Parse(m.Result("${m11}")), + float.Parse(m.Result("${m12}")), + float.Parse(m.Result("${m13}")), + float.Parse(m.Result("${m14}")), + + float.Parse(m.Result("${m21}")), + float.Parse(m.Result("${m22}")), + float.Parse(m.Result("${m23}")), + float.Parse(m.Result("${m24}")), + + float.Parse(m.Result("${m31}")), + float.Parse(m.Result("${m32}")), + float.Parse(m.Result("${m33}")), + float.Parse(m.Result("${m34}")), + + float.Parse(m.Result("${m41}")), + float.Parse(m.Result("${m42}")), + float.Parse(m.Result("${m43}")), + float.Parse(m.Result("${m44}")) + ); + + return true; + } + + result = Matrix4.Zero; + return false; + } + #endregion + + #region Public Static Matrix Arithmetics + /// + /// Adds two matrices. + /// + /// A instance. + /// A instance. + /// A new instance containing the sum. + public static Matrix4 Add(Matrix4 left, Matrix4 right) + { + return new Matrix4( + left.M11 + right.M11, left.M12 + right.M12, left.M13 + right.M13, left.M14 + right.M14, + left.M21 + right.M21, left.M22 + right.M22, left.M23 + right.M23, left.M24 + right.M24, + left.M31 + right.M31, left.M32 + right.M32, left.M33 + right.M33, left.M34 + right.M34, + left.M41 + right.M41, left.M42 + right.M42, left.M43 + right.M43, left.M44 + right.M44 + ); + } + /// + /// Adds a matrix and a scalar. + /// + /// A instance. + /// A single-precision floating-point number. + /// A new instance containing the sum. + public static Matrix4 Add(Matrix4 matrix, float scalar) + { + return new Matrix4( + matrix.M11 + scalar, matrix.M12 + scalar, matrix.M13 + scalar, matrix.M14 + scalar, + matrix.M21 + scalar, matrix.M22 + scalar, matrix.M23 + scalar, matrix.M24 + scalar, + matrix.M31 + scalar, matrix.M32 + scalar, matrix.M33 + scalar, matrix.M34 + scalar, + matrix.M41 + scalar, matrix.M42 + scalar, matrix.M43 + scalar, matrix.M44 + scalar + ); + } + /// + /// Adds two matrices and put the result in a third matrix. + /// + /// A instance. + /// A instance. + /// A instance to hold the result. + public static void Add(Matrix4 left, Matrix4 right, ref Matrix4 result) + { + result.M11 = left.M11 + right.M11; + result.M12 = left.M12 + right.M12; + result.M13 = left.M13 + right.M13; + result.M14 = left.M14 + right.M14; + + result.M21 = left.M21 + right.M21; + result.M22 = left.M22 + right.M22; + result.M23 = left.M23 + right.M23; + result.M24 = left.M24 + right.M24; + + result.M31 = left.M31 + right.M31; + result.M32 = left.M32 + right.M32; + result.M33 = left.M33 + right.M33; + result.M34 = left.M34 + right.M34; + + result.M41 = left.M41 + right.M41; + result.M42 = left.M42 + right.M42; + result.M43 = left.M43 + right.M43; + result.M44 = left.M44 + right.M44; + } + /// + /// Adds a matrix and a scalar and put the result in a third matrix. + /// + /// A instance. + /// A single-precision floating-point number. + /// A instance to hold the result. + public static void Add(Matrix4 matrix, float scalar, ref Matrix4 result) + { + result.M11 = matrix.M11 + scalar; + result.M12 = matrix.M12 + scalar; + result.M13 = matrix.M13 + scalar; + result.M14 = matrix.M14 + scalar; + + result.M21 = matrix.M21 + scalar; + result.M22 = matrix.M22 + scalar; + result.M23 = matrix.M23 + scalar; + result.M24 = matrix.M24 + scalar; + + result.M31 = matrix.M31 + scalar; + result.M32 = matrix.M32 + scalar; + result.M33 = matrix.M33 + scalar; + result.M34 = matrix.M34 + scalar; + + result.M41 = matrix.M41 + scalar; + result.M42 = matrix.M42 + scalar; + result.M43 = matrix.M43 + scalar; + result.M44 = matrix.M44 + scalar; + } + /// + /// Subtracts a matrix from a matrix. + /// + /// A instance to subtract from. + /// A instance to subtract. + /// A new instance containing the difference. + /// result[x][y] = left[x][y] - right[x][y] + public static Matrix4 Subtract(Matrix4 left, Matrix4 right) + { + return new Matrix4( + left.M11 - right.M11, left.M12 - right.M12, left.M13 - right.M13, left.M14 - right.M14, + left.M21 - right.M21, left.M22 - right.M22, left.M23 - right.M23, left.M24 - right.M24, + left.M31 - right.M31, left.M32 - right.M32, left.M33 - right.M33, left.M34 - right.M34, + left.M41 - right.M41, left.M42 - right.M42, left.M43 - right.M43, left.M44 - right.M44 + ); + } + /// + /// Subtracts a scalar from a matrix. + /// + /// A instance. + /// A single-precision floating-point number. + /// A new instance containing the difference. + public static Matrix4 Subtract(Matrix4 matrix, float scalar) + { + return new Matrix4( + matrix.M11 - scalar, matrix.M12 - scalar, matrix.M13 - scalar, matrix.M14 - scalar, + matrix.M21 - scalar, matrix.M22 - scalar, matrix.M23 - scalar, matrix.M24 - scalar, + matrix.M31 - scalar, matrix.M32 - scalar, matrix.M33 - scalar, matrix.M34 - scalar, + matrix.M41 - scalar, matrix.M42 - scalar, matrix.M43 - scalar, matrix.M44 - scalar + ); + } + /// + /// Subtracts a matrix from a matrix and put the result in a third matrix. + /// + /// A instance to subtract from. + /// A instance to subtract. + /// A instance to hold the result. + /// result[x][y] = left[x][y] - right[x][y] + public static void Subtract(Matrix4 left, Matrix4 right, ref Matrix4 result) + { + result.M11 = left.M11 - right.M11; + result.M12 = left.M12 - right.M12; + result.M13 = left.M13 - right.M13; + result.M14 = left.M14 - right.M14; + + result.M21 = left.M21 - right.M21; + result.M22 = left.M22 - right.M22; + result.M23 = left.M23 - right.M23; + result.M24 = left.M24 - right.M24; + + result.M31 = left.M31 - right.M31; + result.M32 = left.M32 - right.M32; + result.M33 = left.M33 - right.M33; + result.M34 = left.M34 - right.M34; + + result.M41 = left.M41 - right.M41; + result.M42 = left.M42 - right.M42; + result.M43 = left.M43 - right.M43; + result.M44 = left.M44 - right.M44; + } + /// + /// Subtracts a scalar from a matrix and put the result in a third matrix. + /// + /// A instance. + /// A single-precision floating-point number. + /// A instance to hold the result. + public static void Subtract(Matrix4 matrix, float scalar, ref Matrix4 result) + { + result.M11 = matrix.M11 - scalar; + result.M12 = matrix.M12 - scalar; + result.M13 = matrix.M13 - scalar; + result.M14 = matrix.M14 - scalar; + + result.M21 = matrix.M21 - scalar; + result.M22 = matrix.M22 - scalar; + result.M23 = matrix.M23 - scalar; + result.M24 = matrix.M24 - scalar; + + result.M31 = matrix.M31 - scalar; + result.M32 = matrix.M32 - scalar; + result.M33 = matrix.M33 - scalar; + result.M34 = matrix.M34 - scalar; + + result.M41 = matrix.M41 - scalar; + result.M42 = matrix.M42 - scalar; + result.M43 = matrix.M43 - scalar; + result.M44 = matrix.M44 - scalar; + } + /// + /// Multiplies two matrices. + /// + /// A instance. + /// A instance. + /// A new instance containing the result. + public static Matrix4 Multiply(Matrix4 left, Matrix4 right) + { + return new Matrix4( + left.M11 * right.M11 + left.M12 * right.M21 + left.M13 * right.M31 + left.M14 * right.M41, + left.M11 * right.M12 + left.M12 * right.M22 + left.M13 * right.M32 + left.M14 * right.M42, + left.M11 * right.M13 + left.M12 * right.M23 + left.M13 * right.M33 + left.M14 * right.M43, + left.M11 * right.M14 + left.M12 * right.M24 + left.M13 * right.M34 + left.M14 * right.M44, + + left.M21 * right.M11 + left.M22 * right.M21 + left.M23 * right.M31 + left.M24 * right.M41, + left.M21 * right.M12 + left.M22 * right.M22 + left.M23 * right.M32 + left.M24 * right.M42, + left.M21 * right.M13 + left.M22 * right.M23 + left.M23 * right.M33 + left.M24 * right.M43, + left.M21 * right.M14 + left.M22 * right.M24 + left.M23 * right.M34 + left.M24 * right.M44, + + left.M31 * right.M11 + left.M32 * right.M21 + left.M33 * right.M31 + left.M34 * right.M41, + left.M31 * right.M12 + left.M32 * right.M22 + left.M33 * right.M32 + left.M34 * right.M42, + left.M31 * right.M13 + left.M32 * right.M23 + left.M33 * right.M33 + left.M34 * right.M43, + left.M31 * right.M14 + left.M32 * right.M24 + left.M33 * right.M34 + left.M34 * right.M44, + + left.M41 * right.M11 + left.M42 * right.M21 + left.M43 * right.M31 + left.M44 * right.M41, + left.M41 * right.M12 + left.M42 * right.M22 + left.M43 * right.M32 + left.M44 * right.M42, + left.M41 * right.M13 + left.M42 * right.M23 + left.M43 * right.M33 + left.M44 * right.M43, + left.M41 * right.M14 + left.M42 * right.M24 + left.M43 * right.M34 + left.M44 * right.M44 + ); + } + /// + /// Multiplies two matrices and put the result in a third matrix. + /// + /// A instance. + /// A instance. + /// A instance to hold the result. + public static void Multiply(Matrix4 left, Matrix4 right, ref Matrix4 result) + { + result.M11 = left.M11 * right.M11 + left.M12 * right.M21 + left.M13 * right.M31 + left.M14 * right.M41; + result.M12 = left.M11 * right.M12 + left.M12 * right.M22 + left.M13 * right.M32 + left.M14 * right.M42; + result.M13 = left.M11 * right.M13 + left.M12 * right.M23 + left.M13 * right.M33 + left.M14 * right.M43; + result.M14 = left.M11 * right.M14 + left.M12 * right.M24 + left.M13 * right.M34 + left.M14 * right.M44; + + result.M21 = left.M21 * right.M11 + left.M22 * right.M21 + left.M23 * right.M31 + left.M24 * right.M41; + result.M22 = left.M21 * right.M12 + left.M22 * right.M22 + left.M23 * right.M32 + left.M24 * right.M42; + result.M23 = left.M21 * right.M13 + left.M22 * right.M23 + left.M23 * right.M33 + left.M24 * right.M43; + result.M24 = left.M21 * right.M14 + left.M22 * right.M24 + left.M23 * right.M34 + left.M24 * right.M44; + + result.M31 = left.M31 * right.M11 + left.M32 * right.M21 + left.M33 * right.M31 + left.M34 * right.M41; + result.M32 = left.M31 * right.M12 + left.M32 * right.M22 + left.M33 * right.M32 + left.M34 * right.M42; + result.M33 = left.M31 * right.M13 + left.M32 * right.M23 + left.M33 * right.M33 + left.M34 * right.M43; + result.M34 = left.M31 * right.M14 + left.M32 * right.M24 + left.M33 * right.M34 + left.M34 * right.M44; + + result.M41 = left.M41 * right.M11 + left.M42 * right.M21 + left.M43 * right.M31 + left.M44 * right.M41; + result.M42 = left.M41 * right.M12 + left.M42 * right.M22 + left.M43 * right.M32 + left.M44 * right.M42; + result.M43 = left.M41 * right.M13 + left.M42 * right.M23 + left.M43 * right.M33 + left.M44 * right.M43; + result.M44 = left.M41 * right.M14 + left.M42 * right.M24 + left.M43 * right.M34 + left.M44 * right.M44; + } + /// + /// Transforms a given vector by a matrix. + /// + /// A instance. + /// A instance. + /// A new instance containing the result. + public static Vector4 Transform(Matrix4 matrix, Vector4 vector) + { + return new Vector4( + (matrix.M11 * vector.X) + (matrix.M12 * vector.Y) + (matrix.M13 * vector.Z) + (matrix.M14 * vector.W), + (matrix.M21 * vector.X) + (matrix.M22 * vector.Y) + (matrix.M23 * vector.Z) + (matrix.M24 * vector.W), + (matrix.M31 * vector.X) + (matrix.M32 * vector.Y) + (matrix.M33 * vector.Z) + (matrix.M34 * vector.W), + (matrix.M41 * vector.X) + (matrix.M42 * vector.Y) + (matrix.M43 * vector.Z) + (matrix.M44 * vector.W)); + } + /// + /// Transforms a given vector by a matrix and put the result in a vector. + /// + /// A instance. + /// A instance. + /// A instance to hold the result. + public static void Transform(Matrix4 matrix, Vector4 vector, ref Vector4 result) + { + result.X = (matrix.M11 * vector.X) + (matrix.M12 * vector.Y) + (matrix.M13 * vector.Z) + (matrix.M14 * vector.W); + result.Y = (matrix.M21 * vector.X) + (matrix.M22 * vector.Y) + (matrix.M23 * vector.Z) + (matrix.M24 * vector.W); + result.Z = (matrix.M31 * vector.X) + (matrix.M32 * vector.Y) + (matrix.M33 * vector.Z) + (matrix.M34 * vector.W); + result.W = (matrix.M41 * vector.X) + (matrix.M42 * vector.Y) + (matrix.M43 * vector.Z) + (matrix.M44 * vector.W); + } + /// + /// Transposes a matrix. + /// + /// A instance. + /// A new instance containing the transposed matrix. + public static Matrix4 Transpose(Matrix4 m) + { + Matrix4 t = new Matrix4(m); + t.Transpose(); + return t; + } + #endregion + + #region System.Object overrides + /// + /// Returns the hashcode for this instance. + /// + /// A 32-bit signed integer hash code. + public override int GetHashCode() + { + return + _m11.GetHashCode() ^ _m12.GetHashCode() ^ _m13.GetHashCode() ^ _m14.GetHashCode() ^ + _m21.GetHashCode() ^ _m22.GetHashCode() ^ _m23.GetHashCode() ^ _m24.GetHashCode() ^ + _m31.GetHashCode() ^ _m32.GetHashCode() ^ _m33.GetHashCode() ^ _m34.GetHashCode() ^ + _m41.GetHashCode() ^ _m42.GetHashCode() ^ _m43.GetHashCode() ^ _m44.GetHashCode(); + } + /// + /// Returns a value indicating whether this instance is equal to + /// the specified object. + /// + /// An object to compare to this instance. + /// if is a and has the same values as this instance; otherwise, . + public override bool Equals(object obj) + { + if (obj is Matrix4) + { + Matrix4 m = (Matrix4)obj; + return + (_m11 == m.M11) && (_m12 == m.M12) && (_m13 == m.M13) && (_m14 == m.M14) && + (_m21 == m.M21) && (_m22 == m.M22) && (_m23 == m.M23) && (_m24 == m.M24) && + (_m31 == m.M31) && (_m32 == m.M32) && (_m33 == m.M33) && (_m34 == m.M34) && + (_m41 == m.M41) && (_m42 == m.M42) && (_m43 == m.M43) && (_m44 == m.M44); + } + return false; + } + /// + /// Returns a string representation of this object. + /// + /// A string representation of this object. + public override string ToString() + { + return string.Format("4x4[{0}, {1}, {2}, {3}, {4}, {5}, {6}, {7}, {8}, {9}, {10}, {11}, {12}, {13}, {14}, {15}]", + _m11, _m12, _m13, _m14, _m21, _m22, _m23, _m24, _m31, _m32, _m33, _m34, _m41, _m42, _m43, _m44); + } + #endregion + + #region Public Methods + /// + /// Calculates the determinant value of the matrix. + /// + /// The determinant value of the matrix. + public float GetDeterminant() + { + // float det = 0.0f; + // for (int col = 0; col < 4; col++) + // { + // if ((col % 2) == 0) + // det += this[0, col] * Minor(0, col).Determinant(); + // else + // det -= this[0, col] * Minor(0, col).Determinant(); + // } + // return det; + return + _m14 * _m23 * _m32 * _m41 - _m13 * _m24 * _m32 * _m41 - _m14 * _m22 * _m33 * _m41 + _m12 * _m24 * _m33 * _m41 + + _m13 * _m22 * _m34 * _m41 - _m12 * _m23 * _m34 * _m41 - _m14 * _m23 * _m31 * _m42 + _m13 * _m24 * _m31 * _m42 + + _m14 * _m21 * _m33 * _m42 - _m11 * _m24 * _m33 * _m42 - _m13 * _m21 * _m34 * _m42 + _m11 * _m23 * _m34 * _m42 + + _m14 * _m22 * _m31 * _m43 - _m12 * _m24 * _m31 * _m43 - _m14 * _m21 * _m32 * _m43 + _m11 * _m24 * _m32 * _m43 + + _m12 * _m21 * _m34 * _m43 - _m11 * _m22 * _m34 * _m43 - _m13 * _m22 * _m31 * _m44 + _m12 * _m23 * _m31 * _m44 + + _m13 * _m21 * _m32 * _m44 - _m11 * _m23 * _m32 * _m44 - _m12 * _m21 * _m33 * _m44 + _m11 * _m22 * _m33 * _m44; + } + /// + /// Transposes this matrix. + /// + public void Transpose() + { + MathFunctions.Swap(ref _m12, ref _m21); + MathFunctions.Swap(ref _m13, ref _m31); + MathFunctions.Swap(ref _m14, ref _m41); + MathFunctions.Swap(ref _m23, ref _m32); + MathFunctions.Swap(ref _m24, ref _m42); + MathFunctions.Swap(ref _m34, ref _m43); + } + #endregion + + #region Comparison Operators + /// + /// Tests whether two specified matrices are equal. + /// + /// A instance. + /// A instance. + /// if the two matrices are equal; otherwise, . + public static bool operator ==(Matrix4 left, Matrix4 right) + { + return ValueType.Equals(left, right); + } + /// + /// Tests whether two specified matrices are not equal. + /// + /// A instance. + /// A instance. + /// if the two matrices are not equal; otherwise, . + public static bool operator !=(Matrix4 left, Matrix4 right) + { + return !ValueType.Equals(left, right); + } + #endregion + + #region Binary Operators + /// + /// Adds two matrices. + /// + /// A instance. + /// A instance. + /// A new instance containing the sum. + public static Matrix4 operator +(Matrix4 left, Matrix4 right) + { + return Matrix4.Add(left, right); ; + } + /// + /// Adds a matrix and a scalar. + /// + /// A instance. + /// A single-precision floating-point number. + /// A new instance containing the sum. + public static Matrix4 operator +(Matrix4 matrix, float scalar) + { + return Matrix4.Add(matrix, scalar); + } + /// + /// Adds a matrix and a scalar. + /// + /// A instance. + /// A single-precision floating-point number. + /// A new instance containing the sum. + public static Matrix4 operator +(float scalar, Matrix4 matrix) + { + return Matrix4.Add(matrix, scalar); + } + /// + /// Subtracts a matrix from a matrix. + /// + /// A instance. + /// A instance. + /// A new instance containing the difference. + public static Matrix4 operator -(Matrix4 left, Matrix4 right) + { + return Matrix4.Subtract(left, right); ; + } + /// + /// Subtracts a scalar from a matrix. + /// + /// A instance. + /// A single-precision floating-point number. + /// A new instance containing the difference. + public static Matrix4 operator -(Matrix4 matrix, float scalar) + { + return Matrix4.Subtract(matrix, scalar); + } + /// + /// Multiplies two matrices. + /// + /// A instance. + /// A instance. + /// A new instance containing the result. + public static Matrix4 operator *(Matrix4 left, Matrix4 right) + { + return Matrix4.Multiply(left, right); ; + } + /// + /// Transforms a given vector by a matrix. + /// + /// A instance. + /// A instance. + /// A new instance containing the result. + public static Vector4 operator *(Matrix4 matrix, Vector4 vector) + { + Vector4 result = new Vector4(); + for (int r = 0; r < 4; ++r) + { + for (int c = 0; c < 4; ++c) + { + result[r] += matrix[r, c] * vector[c]; + } + } + + return result; + } + #endregion + + #region Indexing Operators + /// + /// Indexer allowing to access the matrix elements by an index + /// where index = 2*row + column. + /// + public unsafe float this[int index] + { + get + { + if (index < 0 || index >= 16) + throw new IndexOutOfRangeException("Invalid matrix index!"); + + fixed (float* f = &_m11) + { + return *(f + index); + } + } + set + { + if (index < 0 || index >= 16) + throw new IndexOutOfRangeException("Invalid matrix index!"); + + fixed (float* f = &_m11) + { + *(f + index) = value; + } + } + } + /// + /// Indexer allowing to access the matrix elements by row and column. + /// + public float this[int row, int column] + { + get + { + return this[(row) * 4 + (column)]; + } + set + { + this[(row) * 4 + (column)] = value; + } + } + #endregion + } +} diff --git a/Framework/GameMath/Plane.cs b/Framework/GameMath/Plane.cs new file mode 100644 index 000000000..4f2732369 --- /dev/null +++ b/Framework/GameMath/Plane.cs @@ -0,0 +1,247 @@ +/* + * Copyright (C) 2012-2017 CypherCore + * Copyright (C) 2003-2004 Eran Kampf eran@ekampf.com http://www.ekampf.com + * + * 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, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +using System; +using System.Runtime.Serialization; +using System.Security.Permissions; + +namespace Framework.GameMath +{ + /// + /// An enumeration representing the sides of the plane. + /// + public enum PlaneSide + { + /// + /// Represents the plane itself. + /// + None, + /// + /// Represents the positive halfspace of the plane (the side where the normal points to). + /// + Positive, + /// + /// Represents the negative halfspace of the plane + /// + Negative + } + + /// + /// Represents a plane in 3D space. + /// + /// + /// The plane is described by a normal and a constant (N,D) which + /// denotes that the plane is consisting of points Q that + /// satisfies (N dot Q)+D = 0. + /// + [Serializable] + public struct Plane : ISerializable, ICloneable + { + #region Private Fields + private Vector3 _normal; + private float _const; + #endregion + + #region Constructors + /// + /// Initializes a new instance of the class using given normal and constant values. + /// + /// The plane's normal vector. + /// The plane's constant value. + public Plane(Vector3 normal, float constant) + { + _normal = normal; + _const = constant; + } + + /// + /// Initializes a new instance of the class using given normal and a point. + /// + /// The plane's normal vector. + /// A point on the plane in 3D space. + public Plane(Vector3 normal, Vector3 point) + { + _normal = normal; + _const = Vector3.DotProduct(normal, point); + } + + /// + /// Initializes a new instance of the class using 3 given points. + /// + /// A point on the plane in 3D space. + /// A point on the plane in 3D space. + /// A point on the plane in 3D space. + public Plane(Vector3 p0, Vector3 p1, Vector3 p2) + { + _normal = Vector3.CrossProduct(p2 - p1, p0 - p1); + _normal.Normalize(); + _const = Vector3.DotProduct(_normal, p0); + } + + /// + /// Initializes a new instance of the class using given a plane to assign values from. + /// + /// A 3D plane to assign values from. + public Plane(Plane p) + { + _normal = p.Normal; + _const = p.Constant; + } + + /// + /// Initializes a new instance of the class with serialized data. + /// + /// The object that holds the serialized object data. + /// The contextual information about the source or destination. + private Plane(SerializationInfo info, StreamingContext context) + { + _normal = (Vector3)info.GetValue("Normal", typeof(Vector3)); + _const = info.GetSingle("Constant"); + } + #endregion + + #region Constants + /// + /// Plane on the X axis. + /// + public static readonly Plane XPlane = new Plane(Vector3.XAxis, Vector3.Zero); + /// + /// Plane on the Y axis. + /// + public static readonly Plane YPlane = new Plane(Vector3.YAxis, Vector3.Zero); + /// + /// Plane on the Z axis. + /// + public static readonly Plane ZPlane = new Plane(Vector3.ZAxis, Vector3.Zero); + #endregion + + #region Public Properties + /// + /// Gets or sets the plane's normal vector. + /// + public Vector3 Normal + { + get { return _normal; } + set { _normal = value; } + } + /// + /// Gets or sets the plane's constant value. + /// + public float Constant + { + get { return _const; } + set { _const = value; } + } + #endregion + + #region ICloneable Members + /// + /// Creates an exact copy of this object. + /// + /// The object this method creates, cast as an object. + object ICloneable.Clone() + { + return new Plane(this); + } + /// + /// Creates an exact copy of this object. + /// + /// The object this method creates. + public Plane Clone() + { + return new Plane(this); + } + #endregion + + #region ISerializable Members + /// + /// Populates a with the data needed to serialize the target object. + /// + /// The to populate with data. + /// The destination (see ) for this serialization. + [SecurityPermissionAttribute(SecurityAction.Demand, SerializationFormatter = true)] + public void GetObjectData(SerializationInfo info, StreamingContext context) + { + info.AddValue("Normal", _normal, typeof(Vector3)); + info.AddValue("Constant", _const); + } + #endregion + + #region Public Methods + /// + /// Flip the plane. + /// + public void Flip() + { + _normal = -_normal; + } + /// + /// Creates a new flipped plane (-normal, constant). + /// + /// A new instance. + public Plane GetFlipped() + { + return new Plane(-_normal, _const); + } + /// + /// Get the shortest distance from a 3D vector to the plane. + /// + /// A instance. + /// A float representing the shortest distance from the given vector to the plane. + public float GetDistanceToPlane(Vector3 p) + { + return Vector3.DotProduct(p, this.Normal) - this.Constant; + } + #endregion + + #region Overrides + /// + /// Returns the hashcode for this instance. + /// + /// A 32-bit signed integer hash code. + public override int GetHashCode() + { + return _normal.GetHashCode() ^ _const.GetHashCode(); + } + /// + /// Returns a value indicating whether this instance is equal to + /// the specified object. + /// + /// An object to compare to this instance. + /// True if is a and has the same values as this instance; otherwise, False. + public override bool Equals(object obj) + { + if (obj is Plane) + { + Plane p = (Plane)obj; + return (_normal == p.Normal) && (_const == p.Constant); + } + return false; + } + + /// + /// Returns a string representation of this object. + /// + /// A string representation of this object. + public override string ToString() + { + return string.Format("Plane[n={0}, c={1}]", _normal.ToString(), _const.ToString()); + } + #endregion + } +} diff --git a/Framework/GameMath/QuaternionD.cs b/Framework/GameMath/QuaternionD.cs new file mode 100644 index 000000000..e49657ab5 --- /dev/null +++ b/Framework/GameMath/QuaternionD.cs @@ -0,0 +1,815 @@ +/* + * Copyright (C) 2012-2017 CypherCore + * Copyright (C) 2003-2004 Eran Kampf eran@ekampf.com http://www.ekampf.com + * + * 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, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +using System; +using System.Runtime.InteropServices; +using System.Text.RegularExpressions; + +namespace Framework.GameMath +{ + /// + /// Represents a double-precision floating-point quaternion. + /// + /// + /// + /// A quaternion can be thought of as a 4-Dimentional vector of form: + /// q = [w, x, y, z] = w + xi + yj +zk. + /// + /// + /// A Quaternion is often written as q = s + V where S represents + /// the scalar part (w component) and V is a 3D vector representing + /// the imaginery coefficients (x,y,z components). + /// + /// + /// Check out http://mathworld.wolfram.com/Quaternion.html for further details. + /// + /// + [Serializable] + [StructLayout(LayoutKind.Sequential)] + public struct Quaternion : ICloneable + { + #region Private Fields + private double _w; + private double _x; + private double _y; + private double _z; + #endregion + + #region Constructors + /// + /// Initializes a new instance of the class with the specified coordinates. + /// + /// The quaternions's X coordinate. + /// The quaternions's Y coordinate. + /// The quaternions's Z coordinate. + /// /// The quaternions's W coordinate. + public Quaternion(double x, double y, double z, double w) + { + _w = w; + _x = x; + _y = y; + _z = z; + } + /// + /// Initializes a new instance of the class using coordinates from a given instance. + /// + /// A instance to copy the coordinates from. + public Quaternion(Quaternion quaternion) + { + _x = quaternion.X; + _y = quaternion.Y; + _z = quaternion.Z; + _w = quaternion.W; + } + public Quaternion(Matrix3 rot) : this(Zero) + { + int[] plus1mod3 = { 1, 2, 0 }; + + // Find the index of the largest diagonal component + // These ? operations hopefully compile to conditional + // move instructions instead of branches. + int i = (rot[1, 1] > rot[0, 0]) ? 1 : 0; + i = (rot[2, 2] > rot[i, i]) ? 2 : i; + + // Find the indices of the other elements + int j = plus1mod3[i]; + int k = plus1mod3[j]; + + // If we attempted to pre-normalize and trusted the matrix to be + // perfectly orthonormal, the result would be: + // + // double c = sqrt((rot[i][i] - (rot[j][j] + rot[k][k])) + 1.0) + // v[i] = -c * 0.5 + // v[j] = -(rot[i][j] + rot[j][i]) * 0.5 / c + // v[k] = -(rot[i][k] + rot[k][i]) * 0.5 / c + // w = (rot[j][k] - rot[k][j]) * 0.5 / c + // + // Since we're going to pay the sqrt anyway, we perform a post normalization, which also + // fixes any poorly normalized input. Multiply all elements by 2*c in the above, giving: + + // nc2 = -c^2 + double nc2 = ((rot[j, j] + rot[k, k]) - rot[i, i]) - 1.0; + this[i] = (float)nc2; + W = (rot[j, k] - rot[k, j]); + this[j] = -(rot[i, j] + rot[j, i]); + this[k] = -(rot[i, k] + rot[k, i]); + + // We now have the correct result with the wrong magnitude, so normalize it: + float s = (float)Math.Sqrt(X * X + Y * Y + Z * Z + W * W); + if (s > 0.00001f) + { + s = 1.0f / s; + X *= s; + Y *= s; + Z *= s; + W *= s; + } + else + { + // The quaternion is nearly zero. Make it 0 0 0 1 + X = 0.0f; + Y = 0.0f; + Z = 0.0f; + W = 1.0f; + } + } + #endregion + + #region Constants + /// + /// Double-precision floating point zero quaternion. + /// + public static readonly Quaternion Zero = new Quaternion(0, 0, 0, 0); + /// + /// Double-precision floating point identity quaternion. + /// + public static readonly Quaternion Identity = new Quaternion(0, 0, 0, 1); + /// + /// Double-precision floating point X-Axis quaternion. + /// + public static readonly Quaternion XAxis = new Quaternion(1, 0, 0, 0); + /// + /// Double-precision floating point Y-Axis quaternion. + /// + public static readonly Quaternion YAxis = new Quaternion(0, 1, 0, 0); + /// + /// Double-precision floating point Z-Axis quaternion. + /// + public static readonly Quaternion ZAxis = new Quaternion(0, 0, 1, 0); + /// + /// Double-precision floating point W-Axis quaternion. + /// + public static readonly Quaternion WAxis = new Quaternion(0, 0, 0, 1); + #endregion + + #region Public Properties + /// + /// Gets or sets the x-coordinate of this quaternion. + /// + /// The x-coordinate of this quaternion. + public double X + { + get { return _x; } + set { _x = value; } + } + /// + /// Gets or sets the y-coordinate of this quaternion. + /// + /// The y-coordinate of this quaternion. + public double Y + { + get { return _y; } + set { _y = value; } + } + /// + /// Gets or sets the z-coordinate of this quaternion. + /// + /// The z-coordinate of this quaternion. + public double Z + { + get { return _z; } + set { _z = value; } + } + /// + /// Gets or sets the w-coordinate of this quaternion. + /// + /// The w-coordinate of this quaternion. + public double W + { + get { return _w; } + set { _w = value; } + } + + /// + /// Gets the the modulus of the quaternion. + /// + /// A double-precision floating-point number. + public double Modulus + { + get + { + return System.Math.Sqrt(_w * _w + _x * _x + _y * _y + _z * _z); + } + } + /// + /// Gets the the squared modulus of the quaternion. + /// + /// A double-precision floating-point number. + public double ModulusSquared + { + get + { + return (_w * _w + _x * _x + _y * _y + _z * _z); + } + } + /// + /// Gets or sets the conjugate of the quaternion. + /// + /// A instance. + public Quaternion Conjugate + { + get + { + return new Quaternion(-_x, -_y, -_z, _w); + } + set + { + this = value.Conjugate; + } + } + #endregion + + #region ICloneable Members + /// + /// Creates an exact copy of this object. + /// + /// The object this method creates, cast as an object. + object ICloneable.Clone() + { + return new Quaternion(this); + } + /// + /// Creates an exact copy of this object. + /// + /// The object this method creates. + public Quaternion Clone() + { + return new Quaternion(this); + } + #endregion + + #region Public Static Parse Methods + /// + /// Converts the specified string to its equivalent. + /// + /// A string representation of a + /// A that represents the vector specified by the parameter. + public static Quaternion Parse(string value) + { + Regex r = new Regex(@"\((?.*),(?.*),(?.*),(?.*)\)", RegexOptions.None); + Match m = r.Match(value); + if (m.Success) + { + return new Quaternion( + double.Parse(m.Result("${x}")), + double.Parse(m.Result("${y}")), + double.Parse(m.Result("${z}")), + double.Parse(m.Result("${w}")) + ); + } + else + { + throw new Exception("Unsuccessful Match."); + } + } + /// + /// Converts the specified string to its equivalent. + /// A return value indicates whether the conversion succeeded or failed. + /// + /// A string representation of a . + /// + /// When this method returns, if the conversion succeeded, + /// contains a representing the vector specified by . + /// + /// if value was converted successfully; otherwise, . + public static bool TryParse(string value, out Quaternion result) + { + Regex r = new Regex(@"\((?.*),(?.*),(?.*),(?.*)\)", RegexOptions.None); + Match m = r.Match(value); + if (m.Success) + { + result = new Quaternion( + double.Parse(m.Result("${x}")), + double.Parse(m.Result("${y}")), + double.Parse(m.Result("${z}")), + double.Parse(m.Result("${w}")) + ); + + return true; + } + + result = Quaternion.Zero; + return false; + } + #endregion + + #region Public Static Quaternion Arithmetics + /// + /// Adds two quaternions. + /// + /// A instance. + /// A instance. + /// A new instance containing the sum. + public static Quaternion Add(Quaternion left, Quaternion right) + { + return new Quaternion(left.X + right.X, left.Y + right.Y, left.Z + right.Z, left.W + right.W); + } + /// + /// Adds two quaternions and put the result in the third quaternion. + /// + /// A instance. + /// A instance. + /// A instance to hold the result. + public static void Add(Quaternion left, Quaternion right, ref Quaternion result) + { + result.X = left.X + right.X; + result.Y = left.Y + right.Y; + result.Z = left.Z + right.Z; + result.W = left.W + right.W; + } + + /// + /// Subtracts a quaternion from a quaternion. + /// + /// A instance. + /// A instance. + /// A new instance containing the difference. + public static Quaternion Subtract(Quaternion left, Quaternion right) + { + return new Quaternion(left.X - right.X, left.Y - right.Y, left.Z - right.Z, left.W - right.W); + } + /// + /// Subtracts a quaternion from a quaternion and puts the result into a third quaternion. + /// + /// A instance. + /// A instance. + /// A instance to hold the result. + public static void Subtract(Quaternion left, Quaternion right, ref Quaternion result) + { + result.X = left.X - right.X; + result.Y = left.Y - right.Y; + result.Z = left.Z - right.Z; + result.W = left.W - right.W; + } + + /// + /// Multiplies quaternion by quaternion . + /// + /// A instance. + /// A instance. + /// A new containing the result. + public static Quaternion Multiply(Quaternion left, Quaternion right) + { + Quaternion result = new Quaternion(); + result.X = left.W * right.X + left.X * right.W + left.Y * right.Z - left.Z * right.Y; + result.Y = left.W * right.Y + left.Y * right.W + left.Z * right.X - left.X * right.Z; + result.Z = left.W * right.Z + left.Z * right.W + left.X * right.Y - left.Y * right.X; + result.W = left.W * right.W - left.X * right.X - left.Y * right.Y - left.Z * right.Z; + + return result; + } + /// + /// Multiplies quaternion by quaternion and put the result in a third quaternion. + /// + /// A instance. + /// A instance. + /// A instance to hold the result. + public static void Multiply(Quaternion left, Quaternion right, ref Quaternion result) + { + result.X = left.W * right.X + left.X * right.W + left.Y * right.Z - left.Z * right.Y; + result.Y = left.W * right.Y + left.Y * right.W + left.Z * right.X - left.X * right.Z; + result.Z = left.W * right.Z + left.Z * right.W + left.X * right.Y - left.Y * right.X; + result.W = left.W * right.W - left.X * right.X - left.Y * right.Y - left.Z * right.Z; + } + /// + /// Multiplies a quaternion by a scalar. + /// + /// A instance. + /// A scalar. + /// A instance to hold the result. + public static Quaternion Multiply(Quaternion quaternion, double scalar) + { + Quaternion result = new Quaternion(quaternion); + result.X *= scalar; + result.Y *= scalar; + result.Z *= scalar; + result.W *= scalar; + + return result; + } + /// + /// Multiplies a quaternion by a scalar and put the result in a third quaternion. + /// + /// A instance. + /// A scalar. + /// A instance to hold the result. + public static void Multiply(Quaternion quaternion, double scalar, ref Quaternion result) + { + result.X = quaternion.X * scalar; + result.Y = quaternion.Y * scalar; + result.Z = quaternion.Z * scalar; + result.W = quaternion.W * scalar; + } + + /// + /// Divides a quaternion by a scalar. + /// + /// A instance. + /// A scalar. + /// A instance to hold the result. + public static Quaternion Divide(Quaternion quaternion, double scalar) + { + if (scalar == 0) + { + throw new DivideByZeroException("Dividing quaternion by zero"); + } + + Quaternion result = new Quaternion(quaternion); + result.X /= scalar; + result.Y /= scalar; + result.Z /= scalar; + result.W /= scalar; + + return result; + } + /// + /// Divides a quaternion by a scalar and put the result in a third quaternion. + /// + /// A instance. + /// A scalar. + /// A instance to hold the result. + public static void Divide(Quaternion quaternion, double scalar, ref Quaternion result) + { + if (scalar == 0) + { + throw new DivideByZeroException("Dividing quaternion by zero"); + } + + result.X = quaternion.X / scalar; + result.Y = quaternion.Y / scalar; + result.Z = quaternion.Z / scalar; + result.W = quaternion.W / scalar; + } + + /// + /// Calculates the dot product of two quaternions. + /// + /// A instance. + /// A instance. + /// The dot product value. + public static double DotProduct(Quaternion left, Quaternion right) + { + return left.X * right.X + left.Y * right.Y + left.Z * right.Z + left.W * right.W; + } + #endregion + + public bool isUnit(double tolerance = 1e-5) + { + return Math.Abs(dot(this) - 1.0f) < tolerance; + } + + public Quaternion ToUnit() + { + Quaternion copyOfThis = this; + copyOfThis *= rsq(dot(this)); + return copyOfThis; + } + + float dot(Quaternion other) + { + return (float)((X * other.X) + (Y * other.Y) + (Z * other.Z) + (W * other.W)); + } + + float rsq(float x) + { + return 1.0f / (float)Math.Sqrt(x); + } + + #region Public Static Complex Special Functions + /// + /// Calculates the logarithm of a given quaternion. + /// + /// A instance. + /// The quaternion's logarithm. + public static Quaternion Log(Quaternion quaternion) + { + Quaternion result = new Quaternion(0, 0, 0, 0); + + if (Math.Abs(quaternion.W) < 1.0) + { + double angle = System.Math.Acos(quaternion.W); + double sin = System.Math.Sin(angle); + + if (Math.Abs(sin) >= 0) + { + double coeff = angle / sin; + result.X = coeff * quaternion.X; + result.Y = coeff * quaternion.Y; + result.Z = coeff * quaternion.Z; + } + else + { + result.X = quaternion.X; + result.Y = quaternion.Y; + result.Z = quaternion.Z; + } + } + + return result; + } + /// + /// Calculates the exponent of a quaternion. + /// + /// A instance. + /// The quaternion's exponent. + public Quaternion Exp(Quaternion quaternion) + { + Quaternion result = new Quaternion(0, 0, 0, 0); + + double angle = System.Math.Sqrt(quaternion.X * quaternion.X + quaternion.Y * quaternion.Y + quaternion.Z * quaternion.Z); + double sin = System.Math.Sin(angle); + + if (Math.Abs(sin) > 0) + { + double coeff = angle / sin; + result.X = coeff * quaternion.X; + result.Y = coeff * quaternion.Y; + result.Z = coeff * quaternion.Z; + } + else + { + result.X = quaternion.X; + result.Y = quaternion.Y; + result.Z = quaternion.Z; + } + + return result; + } + #endregion + + #region Public Methods + /// + /// Inverts the quaternion. + /// + public void Inverse() + { + double norm = ModulusSquared; + if (norm > 0) + { + double invNorm = 1.0 / norm; + _w *= invNorm; + _x *= -invNorm; + _y *= -invNorm; + _z *= -invNorm; + } + else + { + throw new Exception("Quaternion " + ToString() + " is not invertable"); + } + } + /// + /// Normelizes the quaternion. + /// + public void Normalize() + { + double norm = Modulus; + if (norm == 0) + { + throw new DivideByZeroException("Trying to normalize a quaternion with modulus of zero."); + } + + _w /= norm; + _x /= norm; + _y /= norm; + _z /= norm; + } + /// + /// Clamps quaternion values to zero using a given tolerance value. + /// + /// The tolerance to use. + /// + /// The quaternion values that are close to zero within the given tolerance are set to zero. + /// + public void ClampZero(double tolerance) + { + _x = MathFunctions.Clamp(_x, 0, tolerance); + _y = MathFunctions.Clamp(_y, 0, tolerance); + _z = MathFunctions.Clamp(_z, 0, tolerance); + _w = MathFunctions.Clamp(_w, 0, tolerance); + } + /// + /// Clamps quaternion values to zero using the default tolerance value. + /// + /// + /// The quaternion values that are close to zero within the given tolerance are set to zero. + /// The tolerance value used is + /// + public void ClampZero() + { + _x = MathFunctions.Clamp(_x, 0); + _y = MathFunctions.Clamp(_y, 0); + _z = MathFunctions.Clamp(_z, 0); + _w = MathFunctions.Clamp(_w, 0); + } + #endregion + + #region System.Object Overrides + /// + /// Returns the hashcode for this instance. + /// + /// A 32-bit signed integer hash code. + public override int GetHashCode() + { + return _w.GetHashCode() ^ _x.GetHashCode() ^ _y.GetHashCode() ^ _z.GetHashCode(); + } + /// + /// Returns a value indicating whether this instance is equal to + /// the specified object. + /// + /// An object to compare to this instance. + /// if is a and has the same values as this instance; otherwise, . + public override bool Equals(object obj) + { + if (obj is Quaternion) + { + Quaternion quaternion = (Quaternion)obj; + return (_w == quaternion.W) && (_x == quaternion.X) && (_y == quaternion.Y) && (_z == quaternion.Z); + } + return false; + } + /// + /// Returns a string representation of this object. + /// + /// A string representation of this object. + public override string ToString() + { + return string.Format("({0}, {1}, {2}, {3})", _w, _x, _y, _z); + } + #endregion + + #region Comparison Operators + /// + /// Tests whether two specified quaternions are equal. + /// + /// The left-hand quaternion. + /// The right-hand quaternion. + /// if the two quaternions are equal; otherwise, . + public static bool operator ==(Quaternion left, Quaternion right) + { + return ValueType.Equals(left, right); + } + /// + /// Tests whether two specified quaternions are not equal. + /// + /// The left-hand quaternion. + /// The right-hand quaternion. + /// if the two quaternions are not equal; otherwise, . + public static bool operator !=(Quaternion left, Quaternion right) + { + return !ValueType.Equals(left, right); + } + #endregion + + #region Binary Operators + /// + /// Adds two quaternions. + /// + /// A instance. + /// A instance. + /// A new instance containing the sum. + public static Quaternion operator +(Quaternion left, Quaternion right) + { + return Quaternion.Add(left, right); + } + /// + /// Subtracts a quaternion from a quaternion. + /// + /// A instance. + /// A instance. + /// A new instance containing the difference. + public static Quaternion operator -(Quaternion left, Quaternion right) + { + return Quaternion.Subtract(left, right); + } + /// + /// Multiplies quaternion by quaternion . + /// + /// A instance. + /// A instance. + /// A new containing the result. + public static Quaternion operator *(Quaternion left, Quaternion right) + { + return Quaternion.Multiply(left, right); + } + /// + /// Multiplies a quaternion by a scalar. + /// + /// A instance. + /// A scalar. + /// A instance to hold the result. + public static Quaternion operator *(Quaternion quaternion, double scalar) + { + return Quaternion.Multiply(quaternion, scalar); + } + /// + /// Multiplies a quaternion by a scalar. + /// + /// A instance. + /// A scalar. + /// A instance to hold the result. + public static Quaternion operator *(double scalar, Quaternion quaternion) + { + return Quaternion.Multiply(quaternion, scalar); + } + /// + /// Divides a quaternion by a scalar. + /// + /// A instance. + /// A scalar. + /// A instance to hold the result. + public static Quaternion operator /(Quaternion quaternion, double scalar) + { + return Quaternion.Divide(quaternion, scalar); + } + /// + /// Divides a scalar by a quaternion. + /// + /// A instance. + /// A scalar. + /// A instance to hold the result. + public static Quaternion operator /(double scalar, Quaternion quaternion) + { + return Quaternion.Multiply(quaternion, (1.0 / scalar)); + } + #endregion + + #region Array Indexing Operator + /// + /// Indexer ( [w, x, y, z] ). + /// + public double this[int index] + { + get + { + switch (index) + { + case 0: + return _x; + case 1: + return _y; + case 2: + return _z; + case 3: + return _w; + default: + throw new IndexOutOfRangeException(); + } + } + set + { + switch (index) + { + case 0: + _x = value; + break; + case 1: + _y = value; + break; + case 2: + _z = value; + break; + case 3: + _w = value; + break; + default: + throw new IndexOutOfRangeException(); + } + return; + } + } + #endregion + + #region Conversion Operators + /// + /// Converts the quaternion to an array of double-precision floating point numbers. + /// + /// A instance. + /// An array of double-precision floating point numbers. + /// The array is [w, x, y, z]. + public static explicit operator double[] (Quaternion quaternion) + { + double[] doubles = new double[4]; + doubles[1] = quaternion.X; + doubles[2] = quaternion.Y; + doubles[3] = quaternion.Z; + doubles[0] = quaternion.W; + return doubles; + } + #endregion + } +} diff --git a/Framework/GameMath/Ray.cs b/Framework/GameMath/Ray.cs new file mode 100644 index 000000000..c16f465c4 --- /dev/null +++ b/Framework/GameMath/Ray.cs @@ -0,0 +1,282 @@ +/* + * Copyright (C) 2012-2017 CypherCore + * Copyright (C) 2003-2004 Eran Kampf eran@ekampf.com http://www.ekampf.com + * + * 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, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +using System; +using System.ComponentModel; +using System.Text.RegularExpressions; + +namespace Framework.GameMath +{ + /// + /// Represents a ray in 3D space. + /// + /// + /// A ray is R(t) = Origin + t * Direction where t>=0. The Direction isnt necessarily of unit length. + /// + [Serializable] + [TypeConverter(typeof(RayConverter))] + public struct Ray : ICloneable + { + #region Private Fields + private Vector3 _origin; + private Vector3 _direction; + #endregion + + #region Constructors + /// + /// Initializes a new instance of the class using given origin and direction vectors. + /// + /// Ray's origin point. + /// Ray's direction vector. + public Ray(Vector3 origin, Vector3 direction) + { + _origin = origin; + _direction = direction; + } + /// + /// Initializes a new instance of the class using given ray. + /// + /// A instance to assign values from. + public Ray(Ray ray) + { + _origin = ray.Origin; + _direction = ray.Direction; + } + #endregion + + #region Public Properties + /// + /// Gets or sets the ray's origin. + /// + public Vector3 Origin + { + get { return _origin; } + set { _origin = value; } + } + /// + /// Gets or sets the ray's direction vector. + /// + public Vector3 Direction + { + get { return _direction; } + set { _direction = value; } + } + #endregion + + #region ICloneable Members + /// + /// Creates an exact copy of this object. + /// + /// The object this method creates, cast as an object. + object ICloneable.Clone() + { + return new Ray(this); + } + /// + /// Creates an exact copy of this object. + /// + /// The object this method creates. + public Ray Clone() + { + return new Ray(this); + } + #endregion + + #region Public Static Parse Methods + /// + /// Converts the specified string to its equivalent. + /// + /// A string representation of a + /// A that represents the vector specified by the parameter. + public static Ray Parse(string s) + { + Regex r = new Regex(@"\((?\([^\)]*\)), (?\([^\)]*\))\)", RegexOptions.None); + Match m = r.Match(s); + if (m.Success) + { + return new Ray( + Vector3.Parse(m.Result("${origin}")), + Vector3.Parse(m.Result("${direction}")) + ); + } + else + { + throw new Exception("Unsuccessful Match."); + } + } + #endregion + + #region Public Methods + /// + /// Gets a point on the ray. + /// + /// + /// + public Vector3 GetPointOnRay(float t) + { + return (Origin + Direction * t); + } + + #endregion + + #region Overrides + /// + /// Get the hashcode for this instance. + /// + /// Returns the hash code for this vector instance. + public override int GetHashCode() + { + return _origin.GetHashCode() ^ _direction.GetHashCode(); + } + /// + /// Returns a value indicating whether this instance is equal to + /// the specified object. + /// + /// An object to compare to this instance. + /// if is a and has the same values as this instance; otherwise, . + public override bool Equals(object obj) + { + if (obj is Ray) + { + Ray r = (Ray)obj; + return ((_origin == r.Origin) && (_direction == r.Direction)); + } + return false; + } + /// + /// Returns a string representation of this object. + /// + /// A string representation of this object. + public override string ToString() + { + return string.Format("({0}, {1})", _origin, _direction); + } + #endregion + + #region Comparison Operators + /// + /// Tests whether two specified rays are equal. + /// + /// The first of two rays to compare. + /// The second of two rays to compare. + /// if the two rays are equal; otherwise, . + public static bool operator ==(Ray a, Ray b) + { + return ValueType.Equals(a, b); + } + /// + /// Tests whether two specified rays are not equal. + /// + /// The first of two rays to compare. + /// The second of two rays to compare. + /// if the two rays are not equal; otherwise, . + public static bool operator !=(Ray a, Ray b) + { + return !ValueType.Equals(a, b); + } + + #endregion + + public float intersectionTime(AxisAlignedBox box) + { + Vector3 dummy = Vector3.Zero; + bool inside; + float time = CollisionDetection.collisionTimeForMovingPointFixedAABox(_origin, _direction, box, ref dummy, out inside); + + if (float.IsInfinity(time) && inside) + return 0.0f; + else + return time; + } + + public Vector3 invDirection() + { + return Vector3.Divide(Vector3.One, Direction); + } + } + + #region RayConverter class + /// + /// Converts a to and from string representation. + /// + public class RayConverter : ExpandableObjectConverter + { + /// + /// Returns whether this converter can convert an object of the given type to the type of this converter, using the specified context. + /// + /// An that provides a format context. + /// A that represents the type you want to convert from. + /// true if this converter can perform the conversion; otherwise, false. + public override bool CanConvertFrom(ITypeDescriptorContext context, Type sourceType) + { + if (sourceType == typeof(string)) + return true; + + return base.CanConvertFrom(context, sourceType); + } + /// + /// Returns whether this converter can convert the object to the specified type, using the specified context. + /// + /// An that provides a format context. + /// A that represents the type you want to convert to. + /// true if this converter can perform the conversion; otherwise, false. + public override bool CanConvertTo(ITypeDescriptorContext context, Type destinationType) + { + if (destinationType == typeof(string)) + return true; + + return base.CanConvertTo(context, destinationType); + } + /// + /// Converts the given value object to the specified type, using the specified context and culture information. + /// + /// An that provides a format context. + /// A object. If a null reference (Nothing in Visual Basic) is passed, the current culture is assumed. + /// The to convert. + /// The Type to convert the parameter to. + /// An that represents the converted value. + public override object ConvertTo(ITypeDescriptorContext context, System.Globalization.CultureInfo culture, object value, Type destinationType) + { + if ((destinationType == typeof(string)) && (value is Ray)) + { + Ray r = (Ray)value; + return r.ToString(); + } + + return base.ConvertTo(context, culture, value, destinationType); + } + /// + /// Converts the given object to the type of this converter, using the specified context and culture information. + /// + /// An that provides a format context. + /// The to use as the current culture. + /// The to convert. + /// An that represents the converted value. + /// Failed parsing from string. + public override object ConvertFrom(ITypeDescriptorContext context, System.Globalization.CultureInfo culture, object value) + { + if (value.GetType() == typeof(string)) + { + return Ray.Parse((string)value); + } + + return base.ConvertFrom(context, culture, value); + } + } + #endregion +} diff --git a/Framework/GameMath/Vector2.cs b/Framework/GameMath/Vector2.cs new file mode 100644 index 000000000..5dfd7e16b --- /dev/null +++ b/Framework/GameMath/Vector2.cs @@ -0,0 +1,948 @@ +/* + * Copyright (C) 2012-2017 CypherCore + * Copyright (C) 2003-2004 Eran Kampf eran@ekampf.com http://www.ekampf.com + * + * 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, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +using System; +using System.Collections.Generic; +using System.ComponentModel; +using System.Diagnostics; +using System.Runtime.InteropServices; +using System.Text.RegularExpressions; + +namespace Framework.GameMath +{ + /// + /// Represents 2-Dimentional vector of single-precision floating point numbers. + /// + [Serializable] + [StructLayout(LayoutKind.Sequential)] + [TypeConverter(typeof(Vector2FConverter))] + public struct Vector2 : ICloneable + { + #region Private fields + private float _x; + private float _y; + #endregion + + #region Constructors + /// + /// Initializes a new instance of the class with the specified coordinates. + /// + /// The vector's X coordinate. + /// The vector's Y coordinate. + public Vector2(float x, float y) + { + _x = x; + _y = y; + } + /// + /// Initializes a new instance of the class with the specified coordinates. + /// + /// An array containing the coordinate parameters. + public Vector2(float[] coordinates) + { + Debug.Assert(coordinates != null); + Debug.Assert(coordinates.Length >= 2); + + _x = coordinates[0]; + _y = coordinates[1]; + } + /// + /// Initializes a new instance of the class with the specified coordinates. + /// + /// An array containing the coordinate parameters. + public Vector2(List coordinates) + { + Debug.Assert(coordinates != null); + Debug.Assert(coordinates.Count >= 2); + + _x = coordinates[0]; + _y = coordinates[1]; + } + /// + /// Initializes a new instance of the class using coordinates from a given instance. + /// + /// A to get the coordinates from. + public Vector2(Vector2 vector) + { + _x = vector.X; + _y = vector.Y; + } + #endregion + + #region Constants + /// + /// 4-Dimentional single-precision floating point zero vector. + /// + public static readonly Vector2 Zero = new Vector2(0.0f, 0.0f); + /// + /// 4-Dimentional single-precision floating point X-Axis vector. + /// + public static readonly Vector2 XAxis = new Vector2(1.0f, 0.0f); + /// + /// 4-Dimentional single-precision floating point Y-Axis vector. + /// + public static readonly Vector2 YAxis = new Vector2(0.0f, 1.0f); + #endregion + + #region Public properties + /// + /// Gets or sets the x-coordinate of this vector. + /// + /// The x-coordinate of this vector. + public float X + { + get { return _x; } + set { _x = value; } + } + /// + /// Gets or sets the y-coordinate of this vector. + /// + /// The y-coordinate of this vector. + public float Y + { + get { return _y; } + set { _y = value; } + } + #endregion + + #region ICloneable Members + /// + /// Creates an exact copy of this object. + /// + /// The object this method creates, cast as an object. + object ICloneable.Clone() + { + return new Vector2(this); + } + /// + /// Creates an exact copy of this object. + /// + /// The object this method creates. + public Vector2 Clone() + { + return new Vector2(this); + } + #endregion + + #region Public Static Parse Methods + /// + /// Converts the specified string to its equivalent. + /// + /// A string representation of a . + /// A that represents the vector specified by the parameter. + public static Vector2 Parse(string value) + { + Regex r = new Regex(@"\((?.*),(?.*)\)", RegexOptions.Singleline); + Match m = r.Match(value); + if (m.Success) + { + return new Vector2( + float.Parse(m.Result("${x}")), + float.Parse(m.Result("${y}")) + ); + } + else + { + throw new Exception("Unsuccessful Match."); + } + } + /// + /// Converts the specified string to its equivalent. + /// A return value indicates whether the conversion succeeded or failed. + /// + /// A string representation of a . + /// + /// When this method returns, if the conversion succeeded, + /// contains a representing the vector specified by . + /// + /// if value was converted successfully; otherwise, . + public static bool TryParse(string value, out Vector2 result) + { + Regex r = new Regex(@"\((?.*),(?.*)\)", RegexOptions.Singleline); + Match m = r.Match(value); + if (m.Success) + { + result = new Vector2( + float.Parse(m.Result("${x}")), + float.Parse(m.Result("${y}")) + ); + + return true; + } + + result = Vector2.Zero; + return false; + } + #endregion + + #region Public Static Vector Arithmetics + /// + /// Adds two vectors. + /// + /// A instance. + /// A instance. + /// A new instance containing the sum. + public static Vector2 Add(Vector2 left, Vector2 right) + { + return new Vector2(left.X + right.X, left.Y + right.Y); + } + /// + /// Adds a vector and a scalar. + /// + /// A instance. + /// A single-precision floating-point number. + /// A new instance containing the sum. + public static Vector2 Add(Vector2 vector, float scalar) + { + return new Vector2(vector.X + scalar, vector.Y + scalar); + } + /// + /// Adds two vectors and put the result in the third vector. + /// + /// A instance. + /// A instance + /// A instance to hold the result. + public static void Add(Vector2 left, Vector2 right, ref Vector2 result) + { + result.X = left.X + right.X; + result.Y = left.Y + right.Y; + } + /// + /// Adds a vector and a scalar and put the result into another vector. + /// + /// A instance. + /// A single-precision floating-point number. + /// A instance to hold the result. + public static void Add(Vector2 vector, float scalar, ref Vector2 result) + { + result.X = vector.X + scalar; + result.Y = vector.Y + scalar; + } + /// + /// Subtracts a vector from a vector. + /// + /// A instance. + /// A instance. + /// A new instance containing the difference. + /// + /// result[i] = left[i] - right[i]. + /// + public static Vector2 Subtract(Vector2 left, Vector2 right) + { + return new Vector2(left.X - right.X, left.Y - right.Y); + } + /// + /// Subtracts a scalar from a vector. + /// + /// A instance. + /// A single-precision floating-point number. + /// A new instance containing the difference. + /// + /// result[i] = vector[i] - scalar + /// + public static Vector2 Subtract(Vector2 vector, float scalar) + { + return new Vector2(vector.X - scalar, vector.Y - scalar); + } + /// + /// Subtracts a vector from a scalar. + /// + /// A instance. + /// A single-precision floating-point number. + /// A new instance containing the difference. + /// + /// result[i] = scalar - vector[i] + /// + public static Vector2 Subtract(float scalar, Vector2 vector) + { + return new Vector2(scalar - vector.X, scalar - vector.Y); + } + /// + /// Subtracts a vector from a second vector and puts the result into a third vector. + /// + /// A instance. + /// A instance + /// A instance to hold the result. + /// + /// result[i] = left[i] - right[i]. + /// + public static void Subtract(Vector2 left, Vector2 right, ref Vector2 result) + { + result.X = left.X - right.X; + result.Y = left.Y - right.Y; + } + /// + /// Subtracts a vector from a scalar and put the result into another vector. + /// + /// A instance. + /// A single-precision floating-point number. + /// A instance to hold the result. + /// + /// result[i] = vector[i] - scalar + /// + public static void Subtract(Vector2 vector, float scalar, ref Vector2 result) + { + result.X = vector.X - scalar; + result.Y = vector.Y - scalar; + } + /// + /// Subtracts a scalar from a vector and put the result into another vector. + /// + /// A instance. + /// A single-precision floating-point number. + /// A instance to hold the result. + /// + /// result[i] = scalar - vector[i] + /// + public static void Subtract(float scalar, Vector2 vector, ref Vector2 result) + { + result.X = scalar - vector.X; + result.Y = scalar - vector.Y; + } + /// + /// Divides a vector by another vector. + /// + /// A instance. + /// A instance. + /// A new containing the quotient. + /// + /// result[i] = left[i] / right[i]. + /// + public static Vector2 Divide(Vector2 left, Vector2 right) + { + return new Vector2(left.X / right.X, left.Y / right.Y); + } + /// + /// Divides a vector by a scalar. + /// + /// A instance. + /// A scalar + /// A new containing the quotient. + /// + /// result[i] = vector[i] / scalar; + /// + public static Vector2 Divide(Vector2 vector, float scalar) + { + return new Vector2(vector.X / scalar, vector.Y / scalar); + } + /// + /// Divides a scalar by a vector. + /// + /// A instance. + /// A scalar + /// A new containing the quotient. + /// + /// result[i] = scalar / vector[i] + /// + public static Vector2 Divide(float scalar, Vector2 vector) + { + return new Vector2(scalar / vector.X, scalar / vector.Y); + } + /// + /// Divides a vector by another vector. + /// + /// A instance. + /// A instance. + /// A instance to hold the result. + /// + /// result[i] = left[i] / right[i] + /// + public static void Divide(Vector2 left, Vector2 right, ref Vector2 result) + { + result.X = left.X / right.X; + result.Y = left.Y / right.Y; + } + /// + /// Divides a vector by a scalar. + /// + /// A instance. + /// A scalar + /// A instance to hold the result. + /// + /// result[i] = vector[i] / scalar + /// + public static void Divide(Vector2 vector, float scalar, ref Vector2 result) + { + result.X = vector.X / scalar; + result.Y = vector.Y / scalar; + } + /// + /// Divides a scalar by a vector. + /// + /// A instance. + /// A scalar + /// A instance to hold the result. + /// + /// result[i] = scalar / vector[i] + /// + public static void Divide(float scalar, Vector2 vector, ref Vector2 result) + { + result.X = scalar / vector.X; + result.Y = scalar / vector.Y; + } + /// + /// Multiplies a vector by a scalar. + /// + /// A instance. + /// A single-precision floating-point number. + /// A new containing the result. + public static Vector2 Multiply(Vector2 vector, float scalar) + { + return new Vector2(vector.X * scalar, vector.Y * scalar); + } + /// + /// Multiplies a vector by a scalar and put the result in another vector. + /// + /// A instance. + /// A single-precision floating-point number. + /// A instance to hold the result. + public static void Multiply(Vector2 vector, float scalar, ref Vector2 result) + { + result.X = vector.X * scalar; + result.Y = vector.Y * scalar; + } + /// + /// Calculates the dot product of two vectors. + /// + /// A instance. + /// A instance. + /// The dot product value. + public static float DotProduct(Vector2 left, Vector2 right) + { + return (left.X * right.X) + (left.Y * right.Y); + } + /// + /// Calculates the Kross product of two vectors. + /// + /// A instance. + /// A instance. + /// The Kross product value. + /// + ///

+ /// The Kross product is defined as: + /// Kross(u,v) = u.X*v.Y - u.Y*v.X. + ///

+ ///

+ /// The operation is related to the cross product in 3D given by (x0, y0, 0) X (x1, y1, 0) = (0, 0, Kross((x0, y0), (x1, y1))). + /// The operation has the property that Kross(u, v) = -Kross(v, u). + ///

+ ///
+ public static float KrossProduct(Vector2 left, Vector2 right) + { + return (left.X * right.Y) - (left.Y * right.X); + } + /// + /// Negates a vector. + /// + /// A instance. + /// A new instance containing the negated values. + public static Vector2 Negate(Vector2 vector) + { + return new Vector2(-vector.X, -vector.Y); + } + /// + /// Tests whether two vectors are approximately equal using default tolerance value. + /// + /// A instance. + /// A instance. + /// if the two vectors are approximately equal; otherwise, . + public static bool ApproxEqual(Vector2 left, Vector2 right) + { + return ApproxEqual(left, right, MathFunctions.Epsilon); + } + /// + /// Tests whether two vectors are approximately equal given a tolerance value. + /// + /// A instance. + /// A instance. + /// The tolerance value used to test approximate equality. + /// if the two vectors are approximately equal; otherwise, . + public static bool ApproxEqual(Vector2 left, Vector2 right, float tolerance) + { + return + ( + (System.Math.Abs(left.X - right.X) <= tolerance) && + (System.Math.Abs(left.Y - right.Y) <= tolerance) + ); + } + #endregion + + #region Public Methods + /// + /// Scale the vector so that its length is 1. + /// + public void Normalize() + { + float length = GetLength(); + if (length == 0) + { + throw new DivideByZeroException("Trying to normalize a vector with length of zero."); + } + + _x /= length; + _y /= length; + } + /// + /// Calculates the length of the vector. + /// + /// Returns the length of the vector. (Sqrt(X*X + Y*Y)) + public float GetLength() + { + return (float)System.Math.Sqrt(_x * _x + _y * _y); + } + /// + /// Calculates the squared length of the vector. + /// + /// Returns the squared length of the vector. (X*X + Y*Y) + public float GetLengthSquared() + { + return (_x * _x + _y * _y); + } + /// + /// Clamps vector values to zero using a given tolerance value. + /// + /// The tolerance to use. + /// + /// The vector values that are close to zero within the given tolerance are set to zero. + /// + public void ClampZero(float tolerance) + { + _x = MathFunctions.Clamp(_x, 0, tolerance); + _y = MathFunctions.Clamp(_y, 0, tolerance); + } + /// + /// Clamps vector values to zero using the default tolerance value. + /// + /// + /// The vector values that are close to zero within the given tolerance are set to zero. + /// The tolerance value used is + /// + public void ClampZero() + { + _x = MathFunctions.Clamp(_x, 0); + _y = MathFunctions.Clamp(_y, 0); + } + #endregion + + #region System.Object Overrides + /// + /// Returns the hashcode for this instance. + /// + /// A 32-bit signed integer hash code. + public override int GetHashCode() + { + return _x.GetHashCode() ^ _y.GetHashCode(); + } + /// + /// Returns a value indicating whether this instance is equal to + /// the specified object. + /// + /// An object to compare to this instance. + /// if is a and has the same values as this instance; otherwise, . + public override bool Equals(object obj) + { + if (obj is Vector2) + { + Vector2 v = (Vector2)obj; + return (_x == v.X) && (_y == v.Y); + } + return false; + } + /// + /// Returns a string representation of this object. + /// + /// A string representation of this object. + public override string ToString() + { + return string.Format("({0}, {1})", _x, _y); + } + #endregion + + #region Comparison Operators + /// + /// Tests whether two specified vectors are equal. + /// + /// A instance. + /// A instance. + /// if the two vectors are equal; otherwise, . + public static bool operator ==(Vector2 left, Vector2 right) + { + return ValueType.Equals(left, right); + } + /// + /// Tests whether two specified vectors are not equal. + /// + /// A instance. + /// A instance. + /// if the two vectors are not equal; otherwise, . + public static bool operator !=(Vector2 left, Vector2 right) + { + return !ValueType.Equals(left, right); + } + + /// + /// Tests if a vector's components are greater than another vector's components. + /// + /// A instance. + /// A instance. + /// if the left-hand vector's components are greater than the right-hand vector's component; otherwise, . + public static bool operator >(Vector2 left, Vector2 right) + { + return ( + (left._x > right._x) && + (left._y > right._y)); + } + /// + /// Tests if a vector's components are smaller than another vector's components. + /// + /// A instance. + /// A instance. + /// if the left-hand vector's components are smaller than the right-hand vector's component; otherwise, . + public static bool operator <(Vector2 left, Vector2 right) + { + return ( + (left._x < right._x) && + (left._y < right._y)); + } + /// + /// Tests if a vector's components are greater or equal than another vector's components. + /// + /// A instance. + /// A instance. + /// if the left-hand vector's components are greater or equal than the right-hand vector's component; otherwise, . + public static bool operator >=(Vector2 left, Vector2 right) + { + return ( + (left._x >= right._x) && + (left._y >= right._y)); + } + /// + /// Tests if a vector's components are smaller or equal than another vector's components. + /// + /// A instance. + /// A instance. + /// if the left-hand vector's components are smaller or equal than the right-hand vector's component; otherwise, . + public static bool operator <=(Vector2 left, Vector2 right) + { + return ( + (left._x <= right._x) && + (left._y <= right._y)); + } + #endregion + + #region Unary Operators + /// + /// Negates the values of the given vector. + /// + /// A instance. + /// A new instance containing the negated values. + public static Vector2 operator -(Vector2 vector) + { + return Vector2.Negate(vector); + } + #endregion + + #region Binary Operators + /// + /// Adds two vectors. + /// + /// A instance. + /// A instance. + /// A new instance containing the sum. + public static Vector2 operator +(Vector2 left, Vector2 right) + { + return Vector2.Add(left, right); + } + /// + /// Adds a vector and a scalar. + /// + /// A instance. + /// A single-precision floating-point number. + /// A new instance containing the sum. + public static Vector2 operator +(Vector2 vector, float scalar) + { + return Vector2.Add(vector, scalar); + } + /// + /// Adds a vector and a scalar. + /// + /// A instance. + /// A single-precision floating-point number. + /// A new instance containing the sum. + public static Vector2 operator +(float scalar, Vector2 vector) + { + return Vector2.Add(vector, scalar); + } + /// + /// Subtracts a vector from a vector. + /// + /// A instance. + /// A instance. + /// A new instance containing the difference. + /// + /// result[i] = left[i] - right[i]. + /// + public static Vector2 operator -(Vector2 left, Vector2 right) + { + return Vector2.Subtract(left, right); + } + /// + /// Subtracts a scalar from a vector. + /// + /// A instance. + /// A single-precision floating-point number. + /// A new instance containing the difference. + /// + /// result[i] = vector[i] - scalar + /// + public static Vector2 operator -(Vector2 vector, float scalar) + { + return Vector2.Subtract(vector, scalar); + } + /// + /// Subtracts a vector from a scalar. + /// + /// A instance. + /// A single-precision floating-point number. + /// A new instance containing the difference. + /// + /// result[i] = scalar - vector[i] + /// + public static Vector2 operator -(float scalar, Vector2 vector) + { + return Vector2.Subtract(scalar, vector); + } + /// + /// Multiplies a vector by a scalar. + /// + /// A instance. + /// A single-precision floating-point number. + /// A new containing the result. + public static Vector2 operator *(Vector2 vector, float scalar) + { + return Vector2.Multiply(vector, scalar); + } + /// + /// Multiplies a vector by a scalar. + /// + /// A instance. + /// A single-precision floating-point number. + /// A new containing the result. + public static Vector2 operator *(float scalar, Vector2 vector) + { + return Vector2.Multiply(vector, scalar); + } + /// + /// Divides a vector by a scalar. + /// + /// A instance. + /// A scalar + /// A new containing the quotient. + /// + /// result[i] = vector[i] / scalar; + /// + public static Vector2 operator /(Vector2 vector, float scalar) + { + return Vector2.Divide(vector, scalar); + } + /// + /// Divides a scalar by a vector. + /// + /// A instance. + /// A scalar + /// A new containing the quotient. + /// + /// result[i] = scalar / vector[i] + /// + public static Vector2 operator /(float scalar, Vector2 vector) + { + return Vector2.Divide(scalar, vector); + } + #endregion + + #region Array Indexing Operator + /// + /// Indexer ( [x, y] ). + /// + public float this[int index] + { + get + { + switch (index) + { + case 0: + return _x; + case 1: + return _y; + default: + throw new IndexOutOfRangeException(); + } + } + set + { + switch (index) + { + case 0: + _x = value; + break; + case 1: + _y = value; + break; + default: + throw new IndexOutOfRangeException(); + } + } + + } + #endregion + + #region Conversion Operators + /// + /// Converts the vector to an array of single-precision floating point values. + /// + /// A instance. + /// An array of single-precision floating point values. + public static explicit operator float[] (Vector2 vector) + { + float[] array = new float[2]; + array[0] = vector.X; + array[1] = vector.Y; + return array; + } + /// + /// Converts the vector to a of single-precision floating point values. + /// + /// A instance. + /// A of single-precision floating point values. + public static explicit operator List(Vector2 vector) + { + List list = new List(); + list.Add(vector.X); + list.Add(vector.Y); + + return list; + } + /// + /// Converts the vector to a of single-precision floating point values. + /// + /// A instance. + /// A of single-precision floating point values. + public static explicit operator LinkedList(Vector2 vector) + { + LinkedList list = new LinkedList(); + list.AddLast(vector.X); + list.AddLast(vector.Y); + + return list; + } + #endregion + } + + #region Vector2FConverter class + /// + /// Converts a to and from string representation. + /// + public class Vector2FConverter : ExpandableObjectConverter + { + /// + /// Returns whether this converter can convert an object of the given type to the type of this converter, using the specified context. + /// + /// An that provides a format context. + /// A that represents the type you want to convert from. + /// true if this converter can perform the conversion; otherwise, false. + public override bool CanConvertFrom(ITypeDescriptorContext context, Type sourceType) + { + if (sourceType == typeof(string)) + return true; + + return base.CanConvertFrom(context, sourceType); + } + /// + /// Returns whether this converter can convert the object to the specified type, using the specified context. + /// + /// An that provides a format context. + /// A that represents the type you want to convert to. + /// true if this converter can perform the conversion; otherwise, false. + public override bool CanConvertTo(ITypeDescriptorContext context, Type destinationType) + { + if (destinationType == typeof(string)) + return true; + + return base.CanConvertTo(context, destinationType); + } + /// + /// Converts the given value object to the specified type, using the specified context and culture information. + /// + /// An that provides a format context. + /// A object. If a null reference (Nothing in Visual Basic) is passed, the current culture is assumed. + /// The to convert. + /// The Type to convert the parameter to. + /// An that represents the converted value. + public override object ConvertTo(ITypeDescriptorContext context, System.Globalization.CultureInfo culture, object value, Type destinationType) + { + if ((destinationType == typeof(string)) && (value is Vector2)) + { + Vector2 v = (Vector2)value; + return v.ToString(); + } + + return base.ConvertTo(context, culture, value, destinationType); + } + /// + /// Converts the given object to the type of this converter, using the specified context and culture information. + /// + /// An that provides a format context. + /// The to use as the current culture. + /// The to convert. + /// An that represents the converted value. + /// Failed parsing from string. + public override object ConvertFrom(ITypeDescriptorContext context, System.Globalization.CultureInfo culture, object value) + { + if (value.GetType() == typeof(string)) + { + return Vector2.Parse((string)value); + } + + return base.ConvertFrom(context, culture, value); + } + + /// + /// Returns whether this object supports a standard set of values that can be picked from a list. + /// + /// An that provides a format context. + /// true if should be called to find a common set of values the object supports; otherwise, false. + public override bool GetStandardValuesSupported(ITypeDescriptorContext context) + { + return true; + } + + /// + /// Returns a collection of standard values for the data type this type converter is designed for when provided with a format context. + /// + /// An that provides a format context that can be used to extract additional information about the environment from which this converter is invoked. This parameter or properties of this parameter can be a null reference. + /// A that holds a standard set of valid values, or a null reference (Nothing in Visual Basic) if the data type does not support a standard set of values. + public override System.ComponentModel.TypeConverter.StandardValuesCollection GetStandardValues(ITypeDescriptorContext context) + { + StandardValuesCollection svc = + new StandardValuesCollection(new object[3] { Vector2.Zero, Vector2.XAxis, Vector2.YAxis }); + + return svc; + } + } + #endregion +} diff --git a/Framework/GameMath/Vector3.cs b/Framework/GameMath/Vector3.cs new file mode 100644 index 000000000..0a15c6b71 --- /dev/null +++ b/Framework/GameMath/Vector3.cs @@ -0,0 +1,1097 @@ +/* + * Copyright (C) 2012-2017 CypherCore + * Copyright (C) 2003-2004 Eran Kampf eran@ekampf.com http://www.ekampf.com + * + * 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, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +using System; +using System.Collections.Generic; +using System.ComponentModel; +using System.Diagnostics; +using System.Runtime.InteropServices; +using System.Text.RegularExpressions; + +namespace Framework.GameMath +{ + /// + /// Represents 3-Dimentional vector of single-precision floating point numbers. + /// + [Serializable] + [StructLayout(LayoutKind.Sequential)] + //[TypeConverter(typeof(Vector3Converter))] + public struct Vector3 : ICloneable + { + #region Constructors + /// + /// Initializes a new instance of the class with the specified coordinates. + /// + /// The vector's X coordinate. + /// The vector's Y coordinate. + /// The vector's Z coordinate. + public Vector3(float x, float y, float z) + { + X = x; + Y = y; + Z = z; + } + /// + /// Initializes a new instance of the class with the specified coordinates. + /// + /// An array containing the coordinate parameters. + public Vector3(float[] coordinates) + { + Debug.Assert(coordinates != null); + Debug.Assert(coordinates.Length >= 3); + + X = coordinates[0]; + Y = coordinates[1]; + Z = coordinates[2]; + } + /// + /// Initializes a new instance of the class with the specified coordinates. + /// + /// An array containing the coordinate parameters. + public Vector3(List coordinates) + { + Debug.Assert(coordinates != null); + Debug.Assert(coordinates.Count >= 3); + + X = coordinates[0]; + Y = coordinates[1]; + Z = coordinates[2]; + } + /// + /// Initializes a new instance of the class using coordinates from a given instance. + /// + /// A to get the coordinates from. + public Vector3(Vector3 vector) + { + X = vector.X; + Y = vector.Y; + Z = vector.Z; + } + #endregion + + #region Constants + /// + /// 4-Dimentional single-precision floating point zero vector. + /// + public static readonly Vector3 Zero = new Vector3(0.0f, 0.0f, 0.0f); + + public static readonly Vector3 One = new Vector3(1.0f, 1.0f, 1.0f); + /// + /// 4-Dimentional single-precision floating point X-Axis vector. + /// + public static readonly Vector3 XAxis = new Vector3(1.0f, 0.0f, 0.0f); + /// + /// 4-Dimentional single-precision floating point Y-Axis vector. + /// + public static readonly Vector3 YAxis = new Vector3(0.0f, 1.0f, 0.0f); + /// + /// 4-Dimentional single-precision floating point Y-Axis vector. + /// + public static readonly Vector3 ZAxis = new Vector3(0.0f, 0.0f, 1.0f); + #endregion + + #region Public properties + /// + /// Gets or sets the x-coordinate of this vector. + /// + /// The x-coordinate of this vector. + public float X; + /// + /// Gets or sets the y-coordinate of this vector. + /// + /// The y-coordinate of this vector. + public float Y; + /// + /// Gets or sets the z-coordinate of this vector. + /// + /// The z-coordinate of this vector. + public float Z; + #endregion + + #region ICloneable Members + /// + /// Creates an exact copy of this object. + /// + /// The object this method creates, cast as an object. + object ICloneable.Clone() + { + return new Vector3(this); + } + /// + /// Creates an exact copy of this object. + /// + /// The object this method creates. + public Vector3 Clone() + { + return new Vector3(this); + } + #endregion + + #region Public Static Parse Methods + /// + /// Converts the specified string to its equivalent. + /// + /// A string representation of a . + /// A that represents the vector specified by the parameter. + public static Vector3 Parse(string value) + { + Regex r = new Regex(@"\((?.*),(?.*),(?.*)\)", RegexOptions.Singleline); + Match m = r.Match(value); + if (m.Success) + { + return new Vector3( + float.Parse(m.Result("${x}")), + float.Parse(m.Result("${y}")), + float.Parse(m.Result("${z}")) + ); + } + else + { + throw new Exception("Unsuccessful Match."); + } + } + /// + /// Converts the specified string to its equivalent. + /// A return value indicates whether the conversion succeeded or failed. + /// + /// A string representation of a . + /// + /// When this method returns, if the conversion succeeded, + /// contains a representing the vector specified by . + /// + /// if value was converted successfully; otherwise, . + public static bool TryParse(string value, out Vector3 result) + { + Regex r = new Regex(@"\((?.*),(?.*),(?.*)\)", RegexOptions.Singleline); + Match m = r.Match(value); + if (m.Success) + { + result = new Vector3( + float.Parse(m.Result("${x}")), + float.Parse(m.Result("${y}")), + float.Parse(m.Result("${z}")) + ); + + return true; + } + + result = Vector3.Zero; + return false; + } + #endregion + + #region Public Static Vector Arithmetics + /// + /// Adds two vectors. + /// + /// A instance. + /// A instance. + /// A new instance containing the sum. + public static Vector3 Add(Vector3 left, Vector3 right) + { + return new Vector3(left.X + right.X, left.Y + right.Y, left.Z + right.Z); + } + /// + /// Adds a vector and a scalar. + /// + /// A instance. + /// A single-precision floating-point number. + /// A new instance containing the sum. + public static Vector3 Add(Vector3 vector, float scalar) + { + return new Vector3(vector.X + scalar, vector.Y + scalar, vector.Z + scalar); + } + /// + /// Adds two vectors and put the result in the third vector. + /// + /// A instance. + /// A instance + /// A instance to hold the result. + public static void Add(Vector3 left, Vector3 right, ref Vector3 result) + { + result.X = left.X + right.X; + result.Y = left.Y + right.Y; + result.Z = left.Z + right.Z; + } + /// + /// Adds a vector and a scalar and put the result into another vector. + /// + /// A instance. + /// A single-precision floating-point number. + /// A instance to hold the result. + public static void Add(Vector3 vector, float scalar, ref Vector3 result) + { + result.X = vector.X + scalar; + result.Y = vector.Y + scalar; + result.Z = vector.Z + scalar; + } + /// + /// Subtracts a vector from a vector. + /// + /// A instance. + /// A instance. + /// A new instance containing the difference. + /// + /// result[i] = left[i] - right[i]. + /// + public static Vector3 Subtract(Vector3 left, Vector3 right) + { + return new Vector3(left.X - right.X, left.Y - right.Y, left.Z - right.Z); + } + /// + /// Subtracts a scalar from a vector. + /// + /// A instance. + /// A single-precision floating-point number. + /// A new instance containing the difference. + /// + /// result[i] = vector[i] - scalar + /// + public static Vector3 Subtract(Vector3 vector, float scalar) + { + return new Vector3(vector.X - scalar, vector.Y - scalar, vector.Z - scalar); + } + /// + /// Subtracts a vector from a scalar. + /// + /// A instance. + /// A single-precision floating-point number. + /// A new instance containing the difference. + /// + /// result[i] = scalar - vector[i] + /// + public static Vector3 Subtract(float scalar, Vector3 vector) + { + return new Vector3(scalar - vector.X, scalar - vector.Y, scalar - vector.Z); + } + /// + /// Subtracts a vector from a second vector and puts the result into a third vector. + /// + /// A instance. + /// A instance + /// A instance to hold the result. + /// + /// result[i] = left[i] - right[i]. + /// + public static void Subtract(Vector3 left, Vector3 right, ref Vector3 result) + { + result.X = left.X - right.X; + result.Y = left.Y - right.Y; + result.Z = left.Z - right.Z; + } + /// + /// Subtracts a vector from a scalar and put the result into another vector. + /// + /// A instance. + /// A single-precision floating-point number. + /// A instance to hold the result. + /// + /// result[i] = vector[i] - scalar + /// + public static void Subtract(Vector3 vector, float scalar, ref Vector3 result) + { + result.X = vector.X - scalar; + result.Y = vector.Y - scalar; + result.Z = vector.Z - scalar; + } + /// + /// Subtracts a scalar from a vector and put the result into another vector. + /// + /// A instance. + /// A single-precision floating-point number. + /// A instance to hold the result. + /// + /// result[i] = scalar - vector[i] + /// + public static void Subtract(float scalar, Vector3 vector, ref Vector3 result) + { + result.X = scalar - vector.X; + result.Y = scalar - vector.Y; + result.Z = scalar - vector.Z; + } + /// + /// Divides a vector by another vector. + /// + /// A instance. + /// A instance. + /// A new containing the quotient. + /// + /// result[i] = left[i] / right[i]. + /// + public static Vector3 Divide(Vector3 left, Vector3 right) + { + return new Vector3(left.X / right.X, left.Y / right.Y, left.Z / right.Z); + } + /// + /// Divides a vector by a scalar. + /// + /// A instance. + /// A scalar + /// A new containing the quotient. + /// + /// result[i] = vector[i] / scalar; + /// + public static Vector3 Divide(Vector3 vector, float scalar) + { + return new Vector3(vector.X / scalar, vector.Y / scalar, vector.Z / scalar); + } + /// + /// Divides a scalar by a vector. + /// + /// A instance. + /// A scalar + /// A new containing the quotient. + /// + /// result[i] = scalar / vector[i] + /// + public static Vector3 Divide(float scalar, Vector3 vector) + { + return new Vector3(scalar / vector.X, scalar / vector.Y, scalar / vector.Z); + } + /// + /// Divides a vector by another vector. + /// + /// A instance. + /// A instance. + /// A instance to hold the result. + /// + /// result[i] = left[i] / right[i] + /// + public static void Divide(Vector3 left, Vector3 right, ref Vector3 result) + { + result.X = left.X / right.X; + result.Y = left.Y / right.Y; + result.Z = left.Z / right.Z; + } + /// + /// Divides a vector by a scalar. + /// + /// A instance. + /// A scalar + /// A instance to hold the result. + /// + /// result[i] = vector[i] / scalar + /// + public static void Divide(Vector3 vector, float scalar, ref Vector3 result) + { + result.X = vector.X / scalar; + result.Y = vector.Y / scalar; + result.Z = vector.Z / scalar; + } + /// + /// Divides a scalar by a vector. + /// + /// A instance. + /// A scalar + /// A instance to hold the result. + /// + /// result[i] = scalar / vector[i] + /// + public static void Divide(float scalar, Vector3 vector, ref Vector3 result) + { + result.X = scalar / vector.X; + result.Y = scalar / vector.Y; + result.Z = scalar / vector.Z; + } + /// + /// Multiplies a vector by a scalar. + /// + /// A instance. + /// A single-precision floating-point number. + /// A new containing the result. + public static Vector3 Multiply(Vector3 vector, float scalar) + { + return new Vector3(vector.X * scalar, vector.Y * scalar, vector.Z * scalar); + } + /// + /// Multiplies a vector by a scalar and put the result in another vector. + /// + /// A instance. + /// A single-precision floating-point number. + /// A instance to hold the result. + public static void Multiply(Vector3 vector, float scalar, ref Vector3 result) + { + result.X = vector.X * scalar; + result.Y = vector.Y * scalar; + result.Z = vector.Z * scalar; + } + /// + /// Calculates the dot product of two vectors. + /// + /// A instance. + /// A instance. + /// The dot product value. + public static float DotProduct(Vector3 left, Vector3 right) + { + return (left.X * right.X) + (left.Y * right.Y) + (left.Z * right.Z); + } + /// + /// Calculates the cross product of two vectors. + /// + /// A instance. + /// A instance. + /// A new containing the cross product result. + public static Vector3 CrossProduct(Vector3 left, Vector3 right) + { + return new Vector3( + left.Y * right.Z - left.Z * right.Y, + left.Z * right.X - left.X * right.Z, + left.X * right.Y - left.Y * right.X); + } + /// + /// Calculates the cross product of two vectors. + /// + /// A instance. + /// A instance. + /// A instance to hold the cross product result. + public static void CrossProduct(Vector3 left, Vector3 right, ref Vector3 result) + { + result.X = left.Y * right.Z - left.Z * right.Y; + result.Y = left.Z * right.X - left.X * right.Z; + result.Z = left.X * right.Y - left.Y * right.X; + } + /// + /// Negates a vector. + /// + /// A instance. + /// A new instance containing the negated values. + public static Vector3 Negate(Vector3 vector) + { + return new Vector3(-vector.X, -vector.Y, -vector.Z); + } + /// + /// Tests whether two vectors are approximately equal using default tolerance value. + /// + /// A instance. + /// A instance. + /// if the two vectors are approximately equal; otherwise, . + public static bool ApproxEqual(Vector3 left, Vector3 right) + { + return ApproxEqual(left, right, MathFunctions.Epsilon); + } + /// + /// Tests whether two vectors are approximately equal given a tolerance value. + /// + /// A instance. + /// A instance. + /// The tolerance value used to test approximate equality. + /// if the two vectors are approximately equal; otherwise, . + public static bool ApproxEqual(Vector3 left, Vector3 right, float tolerance) + { + return + ( + (System.Math.Abs(left.X - right.X) <= tolerance) && + (System.Math.Abs(left.Y - right.Y) <= tolerance) && + (System.Math.Abs(left.Z - right.Z) <= tolerance) + ); + } + #endregion + + #region Public Methods + /// + /// Scale the vector so that its length is 1. + /// + public void Normalize() + { + float length = GetLength(); + if (length == 0) + { + throw new DivideByZeroException("Trying to normalize a vector with length of zero."); + } + + X /= length; + Y /= length; + Z /= length; + } + /// + /// Calculates the length of the vector. + /// + /// Returns the length of the vector. (Sqrt(X*X + Y*Y)) + public float GetLength() + { + return (float)System.Math.Sqrt(X * X + Y * Y + Z * Z); + } + /// + /// Calculates the squared length of the vector. + /// + /// Returns the squared length of the vector. (X*X + Y*Y) + public float GetLengthSquared() + { + return (X * X + Y * Y + Z * Z); + } + /// + /// Clamps vector values to zero using a given tolerance value. + /// + /// The tolerance to use. + /// + /// The vector values that are close to zero within the given tolerance are set to zero. + /// + public void ClampZero(float tolerance) + { + X = MathFunctions.Clamp(X, 0, tolerance); + Y = MathFunctions.Clamp(Y, 0, tolerance); + Z = MathFunctions.Clamp(Z, 0, tolerance); + } + /// + /// Clamps vector values to zero using the default tolerance value. + /// + /// + /// The vector values that are close to zero within the given tolerance are set to zero. + /// The tolerance value used is + /// + public void ClampZero() + { + X = MathFunctions.Clamp(X, 0); + Y = MathFunctions.Clamp(Y, 0); + Z = MathFunctions.Clamp(Z, 0); + } + #endregion + + #region System.Object Overrides + /// + /// Returns the hashcode for this instance. + /// + /// A 32-bit signed integer hash code. + public override int GetHashCode() + { + return X.GetHashCode() ^ Y.GetHashCode() ^ Z.GetHashCode(); + } + /// + /// Returns a value indicating whether this instance is equal to + /// the specified object. + /// + /// An object to compare to this instance. + /// if is a and has the same values as this instance; otherwise, . + public override bool Equals(object obj) + { + if (obj is Vector3) + { + Vector3 v = (Vector3)obj; + return (X == v.X) && (Y == v.Y) && (Z == v.Z); + } + return false; + } + /// + /// Returns a string representation of this object. + /// + /// A string representation of this object. + public override string ToString() + { + return string.Format("({0}, {1}, {2})", X, Y, Z); + } + #endregion + + #region Comparison Operators + /// + /// Tests whether two specified vectors are equal. + /// + /// A instance. + /// A instance. + /// if the two vectors are equal; otherwise, . + public static bool operator ==(Vector3 left, Vector3 right) + { + return ValueType.Equals(left, right); + } + /// + /// Tests whether two specified vectors are not equal. + /// + /// A instance. + /// A instance. + /// if the two vectors are not equal; otherwise, . + public static bool operator !=(Vector3 left, Vector3 right) + { + return !ValueType.Equals(left, right); + } + + /// + /// Tests if a vector's components are greater than another vector's components. + /// + /// A instance. + /// A instance. + /// if the left-hand vector's components are greater than the right-hand vector's component; otherwise, . + public static bool operator >(Vector3 left, Vector3 right) + { + return ( + (left.X > right.X) && + (left.Y > right.Y) && + (left.Z > right.Z)); + } + /// + /// Tests if a vector's components are smaller than another vector's components. + /// + /// A instance. + /// A instance. + /// if the left-hand vector's components are smaller than the right-hand vector's component; otherwise, . + public static bool operator <(Vector3 left, Vector3 right) + { + return ( + (left.X < right.X) && + (left.Y < right.Y) && + (left.Z < right.Z)); + } + /// + /// Tests if a vector's components are greater or equal than another vector's components. + /// + /// A instance. + /// A instance. + /// if the left-hand vector's components are greater or equal than the right-hand vector's component; otherwise, . + public static bool operator >=(Vector3 left, Vector3 right) + { + return ( + (left.X >= right.X) && + (left.Y >= right.Y) && + (left.Z >= right.Z)); + } + /// + /// Tests if a vector's components are smaller or equal than another vector's components. + /// + /// A instance. + /// A instance. + /// if the left-hand vector's components are smaller or equal than the right-hand vector's component; otherwise, . + public static bool operator <=(Vector3 left, Vector3 right) + { + return ( + (left.X <= right.X) && + (left.Y <= right.Y) && + (left.Z <= right.Z)); + } + #endregion + + #region Unary Operators + /// + /// Negates the values of the given vector. + /// + /// A instance. + /// A new instance containing the negated values. + public static Vector3 operator -(Vector3 vector) + { + return Vector3.Negate(vector); + } + #endregion + + #region Binary Operators + /// + /// Adds two vectors. + /// + /// A instance. + /// A instance. + /// A new instance containing the sum. + public static Vector3 operator +(Vector3 left, Vector3 right) + { + return Vector3.Add(left, right); + } + /// + /// Adds a vector and a scalar. + /// + /// A instance. + /// A single-precision floating-point number. + /// A new instance containing the sum. + public static Vector3 operator +(Vector3 vector, float scalar) + { + return Vector3.Add(vector, scalar); + } + /// + /// Adds a vector and a scalar. + /// + /// A instance. + /// A single-precision floating-point number. + /// A new instance containing the sum. + public static Vector3 operator +(float scalar, Vector3 vector) + { + return Vector3.Add(vector, scalar); + } + /// + /// Subtracts a vector from a vector. + /// + /// A instance. + /// A instance. + /// A new instance containing the difference. + /// + /// result[i] = left[i] - right[i]. + /// + public static Vector3 operator -(Vector3 left, Vector3 right) + { + return Vector3.Subtract(left, right); + } + /// + /// Subtracts a scalar from a vector. + /// + /// A instance. + /// A single-precision floating-point number. + /// A new instance containing the difference. + /// + /// result[i] = vector[i] - scalar + /// + public static Vector3 operator -(Vector3 vector, float scalar) + { + return Vector3.Subtract(vector, scalar); + } + /// + /// Subtracts a vector from a scalar. + /// + /// A instance. + /// A single-precision floating-point number. + /// A new instance containing the difference. + /// + /// result[i] = scalar - vector[i] + /// + public static Vector3 operator -(float scalar, Vector3 vector) + { + return Vector3.Subtract(scalar, vector); + } + /// + /// Multiplies a vector by a scalar. + /// + /// A instance. + /// A single-precision floating-point number. + /// A new containing the result. + public static Vector3 operator *(Vector3 vector, float scalar) + { + return Vector3.Multiply(vector, scalar); + } + /// + /// Multiplies a vector by a scalar. + /// + /// A instance. + /// A single-precision floating-point number. + /// A new containing the result. + public static Vector3 operator *(float scalar, Vector3 vector) + { + return Vector3.Multiply(vector, scalar); + } + /// + /// Divides a vector by a scalar. + /// + /// A instance. + /// A scalar + /// A new containing the quotient. + /// + /// result[i] = vector[i] / scalar; + /// + public static Vector3 operator /(Vector3 vector, float scalar) + { + return Vector3.Divide(vector, scalar); + } + /// + /// Divides a scalar by a vector. + /// + /// A instance. + /// A scalar + /// A new containing the quotient. + /// + /// result[i] = scalar / vector[i] + /// + public static Vector3 operator /(float scalar, Vector3 vector) + { + return Vector3.Divide(scalar, vector); + } + #endregion + + #region Array Indexing Operator + /// + /// Indexer ( [x, y, z] ). + /// + public float this[int index] + { + get + { + switch (index) + { + case 0: + return X; + case 1: + return Y; + case 2: + return Z; + default: + throw new IndexOutOfRangeException(); + } + } + set + { + switch (index) + { + case 0: + X = value; + break; + case 1: + Y = value; + break; + case 2: + Z = value; + break; + default: + throw new IndexOutOfRangeException(); + } + } + + } + public float this[uint index] + { + get + { + switch (index) + { + case 0: + return X; + case 1: + return Y; + case 2: + return Z; + default: + throw new IndexOutOfRangeException(); + } + } + set + { + switch (index) + { + case 0: + X = value; + break; + case 1: + Y = value; + break; + case 2: + Z = value; + break; + default: + throw new IndexOutOfRangeException(); + } + } + + } + #endregion + + #region Conversion Operators + /// + /// Converts the vector to an array of single-precision floating point values. + /// + /// A instance. + /// An array of single-precision floating point values. + public static explicit operator float[] (Vector3 vector) + { + float[] array = new float[3]; + array[0] = vector.X; + array[1] = vector.Y; + array[2] = vector.Z; + return array; + } + /// + /// Converts the vector to a of single-precision floating point values. + /// + /// A instance. + /// A of single-precision floating point values. + public static explicit operator List(Vector3 vector) + { + List list = new List(3); + list.Add(vector.X); + list.Add(vector.Y); + list.Add(vector.Z); + + return list; + } + /// + /// Converts the vector to a of single-precision floating point values. + /// + /// A instance. + /// A of single-precision floating point values. + public static explicit operator LinkedList(Vector3 vector) + { + LinkedList list = new LinkedList(); + list.AddLast(vector.X); + list.AddLast(vector.Y); + list.AddLast(vector.Z); + + return list; + } + #endregion + + public float magnitude() + { + return (float)Math.Sqrt(X * X + Y * Y + Z * Z); + } + public float dot(Vector3 rkVector) + { + return X * rkVector.X + Y * rkVector.Y + Z * rkVector.Z; + } + + public Vector3 cross(Vector3 rkVector) + { + return new Vector3(Y * rkVector.Z - Z * rkVector.Y, Z * rkVector.X - X * rkVector.Z, + X * rkVector.Y - Y * rkVector.X); + } + + public Vector3 Min(Vector3 v) + { + return new Vector3(Math.Min(v.X, X), Math.Min(v.Y, Y), Math.Min(v.Z, Z)); + } + + public Vector3 Max(Vector3 v) + { + return new Vector3(Math.Max(v.X, X), Math.Max(v.Y, Y), Math.Max(v.Z, Z)); + } + + public Axis primaryAxis() + { + Axis a = Axis.X; + + double nx = Math.Abs(X); + double ny = Math.Abs(Y); + double nz = Math.Abs(Z); + + if (nx > ny) + { + if (nx > nz) + a = Axis.X; + else + a = Axis.Z; + } + else + { + if (ny > nz) + a = Axis.Y; + else + a = Axis.Z; + } + + return a; + } + + public enum Axis { X = 0, Y = 1, Z = 2, Detect = -1 }; + + public Vector3 lerp(Vector3 v, float alpha) + { + return (this) + (v - this) * alpha; + } + + /// + /// + /// + /// Vector3.Zero if the length is nearly zero, otherwise returns a unit vector + public Vector3 directionOrZero() + { + float mag = magnitude(); + if (mag < 0.0000001f) + { + return Zero; + } + else if (mag < 1.00001f && mag > 0.99999f) + { + return this; + } + else + { + return this * (1.0f / mag); + } + } + } + + #region Vector3Converter class + /// + /// Converts a to and from string representation. + /// + public class Vector3Converter : ExpandableObjectConverter + { + /// + /// Returns whether this converter can convert an object of the given type to the type of this converter, using the specified context. + /// + /// An that provides a format context. + /// A that represents the type you want to convert from. + /// true if this converter can perform the conversion; otherwise, false. + public override bool CanConvertFrom(ITypeDescriptorContext context, Type sourceType) + { + if (sourceType == typeof(string)) + return true; + + return base.CanConvertFrom(context, sourceType); + } + /// + /// Returns whether this converter can convert the object to the specified type, using the specified context. + /// + /// An that provides a format context. + /// A that represents the type you want to convert to. + /// true if this converter can perform the conversion; otherwise, false. + public override bool CanConvertTo(ITypeDescriptorContext context, Type destinationType) + { + if (destinationType == typeof(string)) + return true; + + return base.CanConvertTo(context, destinationType); + } + /// + /// Converts the given value object to the specified type, using the specified context and culture information. + /// + /// An that provides a format context. + /// A object. If a null reference (Nothing in Visual Basic) is passed, the current culture is assumed. + /// The to convert. + /// The Type to convert the parameter to. + /// An that represents the converted value. + public override object ConvertTo(ITypeDescriptorContext context, System.Globalization.CultureInfo culture, object value, Type destinationType) + { + if ((destinationType == typeof(string)) && (value is Vector3)) + { + Vector3 v = (Vector3)value; + return v.ToString(); + } + + return base.ConvertTo(context, culture, value, destinationType); + } + /// + /// Converts the given object to the type of this converter, using the specified context and culture information. + /// + /// An that provides a format context. + /// The to use as the current culture. + /// The to convert. + /// An that represents the converted value. + /// Failed parsing from string. + public override object ConvertFrom(ITypeDescriptorContext context, System.Globalization.CultureInfo culture, object value) + { + if (value.GetType() == typeof(string)) + { + return Vector3.Parse((string)value); + } + + return base.ConvertFrom(context, culture, value); + } + + /// + /// Returns whether this object supports a standard set of values that can be picked from a list. + /// + /// An that provides a format context. + /// true if should be called to find a common set of values the object supports; otherwise, false. + public override bool GetStandardValuesSupported(ITypeDescriptorContext context) + { + return true; + } + + /// + /// Returns a collection of standard values for the data type this type converter is designed for when provided with a format context. + /// + /// An that provides a format context that can be used to extract additional information about the environment from which this converter is invoked. This parameter or properties of this parameter can be a null reference. + /// A that holds a standard set of valid values, or a null reference (Nothing in Visual Basic) if the data type does not support a standard set of values. + public override System.ComponentModel.TypeConverter.StandardValuesCollection GetStandardValues(ITypeDescriptorContext context) + { + StandardValuesCollection svc = + new StandardValuesCollection(new object[4] { Vector3.Zero, Vector3.XAxis, Vector3.YAxis, Vector3.ZAxis }); + + return svc; + } + } + #endregion +} diff --git a/Framework/GameMath/Vector4.cs b/Framework/GameMath/Vector4.cs new file mode 100644 index 000000000..122d9a620 --- /dev/null +++ b/Framework/GameMath/Vector4.cs @@ -0,0 +1,1035 @@ +/* + * Copyright (C) 2012-2017 CypherCore + * Copyright (C) 2003-2004 Eran Kampf eran@ekampf.com http://www.ekampf.com + * + * 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, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +using System; +using System.Collections.Generic; +using System.ComponentModel; +using System.Diagnostics; +using System.Runtime.InteropServices; +using System.Text.RegularExpressions; + +namespace Framework.GameMath +{ + /// + /// Represents 4-Dimentional vector of single-precision floating point numbers. + /// + [Serializable] + [StructLayout(LayoutKind.Sequential)] + [TypeConverter(typeof(Vector4Converter))] + public struct Vector4 : ICloneable + { + #region Private fields + private float _x; + private float _y; + private float _z; + private float _w; + #endregion + + #region Constructors + /// + /// Initializes a new instance of the class with the specified coordinates. + /// + /// The vector's X coordinate. + /// The vector's Y coordinate. + /// The vector's Z coordinate. + /// The vector's W coordinate. + public Vector4(float x, float y, float z, float w) + { + _x = x; + _y = y; + _z = z; + _w = w; + } + /// + /// Initializes a new instance of the class with the specified coordinates. + /// + /// An array containing the coordinate parameters. + public Vector4(float[] coordinates) + { + Debug.Assert(coordinates != null); + Debug.Assert(coordinates.Length >= 4); + + _x = coordinates[0]; + _y = coordinates[1]; + _z = coordinates[2]; + _w = coordinates[3]; + } + /// + /// Initializes a new instance of the class with the specified coordinates. + /// + /// An array containing the coordinate parameters. + public Vector4(List coordinates) + { + Debug.Assert(coordinates != null); + Debug.Assert(coordinates.Count >= 4); + + _x = coordinates[0]; + _y = coordinates[1]; + _z = coordinates[2]; + _w = coordinates[3]; + } + /// + /// Initializes a new instance of the class using coordinates from a given instance. + /// + /// A to get the coordinates from. + public Vector4(Vector4 vector) + { + _x = vector.X; + _y = vector.Y; + _z = vector.Z; + _w = vector.W; + } + #endregion + + #region Constants + /// + /// 4-Dimentional single-precision floating point zero vector. + /// + public static readonly Vector4 Zero = new Vector4(0.0f, 0.0f, 0.0f, 0.0f); + /// + /// 4-Dimentional single-precision floating point X-Axis vector. + /// + public static readonly Vector4 XAxis = new Vector4(1.0f, 0.0f, 0.0f, 0.0f); + /// + /// 4-Dimentional single-precision floating point Y-Axis vector. + /// + public static readonly Vector4 YAxis = new Vector4(0.0f, 1.0f, 0.0f, 0.0f); + /// + /// 4-Dimentional single-precision floating point Y-Axis vector. + /// + public static readonly Vector4 ZAxis = new Vector4(0.0f, 0.0f, 1.0f, 0.0f); + /// + /// 4-Dimentional single-precision floating point Y-Axis vector. + /// + public static readonly Vector4 WAxis = new Vector4(0.0f, 0.0f, 0.0f, 1.0f); + #endregion + + #region Public properties + /// + /// Gets or sets the x-coordinate of this vector. + /// + /// The x-coordinate of this vector. + public float X + { + get { return _x; } + set { _x = value; } + } + /// + /// Gets or sets the y-coordinate of this vector. + /// + /// The y-coordinate of this vector. + public float Y + { + get { return _y; } + set { _y = value; } + } + /// + /// Gets or sets the z-coordinate of this vector. + /// + /// The z-coordinate of this vector. + public float Z + { + get { return _z; } + set { _z = value; } + } + /// + /// Gets or sets the w-coordinate of this vector. + /// + /// The w-coordinate of this vector. + public float W + { + get { return _w; } + set { _w = value; } + } + #endregion + + #region ICloneable Members + /// + /// Creates an exact copy of this object. + /// + /// The object this method creates, cast as an object. + object ICloneable.Clone() + { + return new Vector4(this); + } + /// + /// Creates an exact copy of this object. + /// + /// The object this method creates. + public Vector4 Clone() + { + return new Vector4(this); + } + #endregion + + #region Public Static Parse Methods + /// + /// Converts the specified string to its equivalent. + /// + /// A string representation of a . + /// A that represents the vector specified by the parameter. + public static Vector4 Parse(string value) + { + Regex r = new Regex(@"\((?.*),(?.*),(?.*),(?.*)\)", RegexOptions.Singleline); + Match m = r.Match(value); + if (m.Success) + { + return new Vector4( + float.Parse(m.Result("${x}")), + float.Parse(m.Result("${y}")), + float.Parse(m.Result("${z}")), + float.Parse(m.Result("${w}")) + ); + } + else + { + throw new Exception("Unsuccessful Match."); + } + } + /// + /// Converts the specified string to its equivalent. + /// A return value indicates whether the conversion succeeded or failed. + /// + /// A string representation of a . + /// + /// When this method returns, if the conversion succeeded, + /// contains a representing the vector specified by . + /// + /// if value was converted successfully; otherwise, . + public static bool TryParse(string value, out Vector4 result) + { + Regex r = new Regex(@"\((?.*),(?.*),(?.*),(?.*)\)", RegexOptions.Singleline); + Match m = r.Match(value); + if (m.Success) + { + result = new Vector4( + float.Parse(m.Result("${x}")), + float.Parse(m.Result("${y}")), + float.Parse(m.Result("${z}")), + float.Parse(m.Result("${w}")) + ); + + return true; + } + + result = Vector4.Zero; + return false; + } + #endregion + + #region Public Static Vector Arithmetics + /// + /// Adds two vectors. + /// + /// A instance. + /// A instance. + /// A new instance containing the sum. + public static Vector4 Add(Vector4 left, Vector4 right) + { + return new Vector4(left.X + right.X, left.Y + right.Y, left.Z + right.Z, left.W + right.W); + } + /// + /// Adds a vector and a scalar. + /// + /// A instance. + /// A single-precision floating-point number. + /// A new instance containing the sum. + public static Vector4 Add(Vector4 vector, float scalar) + { + return new Vector4(vector.X + scalar, vector.Y + scalar, vector.Z + scalar, vector.W + scalar); + } + /// + /// Adds two vectors and put the result in the third vector. + /// + /// A instance. + /// A instance + /// A instance to hold the result. + public static void Add(Vector4 left, Vector4 right, ref Vector4 result) + { + result.X = left.X + right.X; + result.Y = left.Y + right.Y; + result.Z = left.Z + right.Z; + result.W = left.W + right.W; + } + /// + /// Adds a vector and a scalar and put the result into another vector. + /// + /// A instance. + /// A single-precision floating-point number. + /// A instance to hold the result. + public static void Add(Vector4 vector, float scalar, ref Vector4 result) + { + result.X = vector.X + scalar; + result.Y = vector.Y + scalar; + result.Z = vector.Z + scalar; + result.W = vector.W + scalar; + } + /// + /// Subtracts a vector from a vector. + /// + /// A instance. + /// A instance. + /// A new instance containing the difference. + /// + /// result[i] = left[i] - right[i]. + /// + public static Vector4 Subtract(Vector4 left, Vector4 right) + { + return new Vector4(left.X - right.X, left.Y - right.Y, left.Z - right.Z, left.W - right.W); + } + /// + /// Subtracts a scalar from a vector. + /// + /// A instance. + /// A single-precision floating-point number. + /// A new instance containing the difference. + /// + /// result[i] = vector[i] - scalar + /// + public static Vector4 Subtract(Vector4 vector, float scalar) + { + return new Vector4(vector.X - scalar, vector.Y - scalar, vector.Z - scalar, vector.W - scalar); + } + /// + /// Subtracts a vector from a scalar. + /// + /// A instance. + /// A single-precision floating-point number. + /// A new instance containing the difference. + /// + /// result[i] = scalar - vector[i] + /// + public static Vector4 Subtract(float scalar, Vector4 vector) + { + return new Vector4(scalar - vector.X, scalar - vector.Y, scalar - vector.Z, scalar - vector.W); + } + /// + /// Subtracts a vector from a second vector and puts the result into a third vector. + /// + /// A instance. + /// A instance + /// A instance to hold the result. + /// + /// result[i] = left[i] - right[i]. + /// + public static void Subtract(Vector4 left, Vector4 right, ref Vector4 result) + { + result.X = left.X - right.X; + result.Y = left.Y - right.Y; + result.Z = left.Z - right.Z; + result.W = left.W - right.W; + } + /// + /// Subtracts a vector from a scalar and put the result into another vector. + /// + /// A instance. + /// A single-precision floating-point number. + /// A instance to hold the result. + /// + /// result[i] = vector[i] - scalar + /// + public static void Subtract(Vector4 vector, float scalar, ref Vector4 result) + { + result.X = vector.X - scalar; + result.Y = vector.Y - scalar; + result.Z = vector.Z - scalar; + result.W = vector.W - scalar; + } + /// + /// Subtracts a scalar from a vector and put the result into another vector. + /// + /// A instance. + /// A single-precision floating-point number. + /// A instance to hold the result. + /// + /// result[i] = scalar - vector[i] + /// + public static void Subtract(float scalar, Vector4 vector, ref Vector4 result) + { + result.X = scalar - vector.X; + result.Y = scalar - vector.Y; + result.Z = scalar - vector.Z; + result.W = scalar - vector.W; + } + /// + /// Divides a vector by another vector. + /// + /// A instance. + /// A instance. + /// A new containing the quotient. + /// + /// result[i] = left[i] / right[i]. + /// + public static Vector4 Divide(Vector4 left, Vector4 right) + { + return new Vector4(left.X / right.X, left.Y / right.Y, left.Z / right.Z, left.W / right.W); + } + /// + /// Divides a vector by a scalar. + /// + /// A instance. + /// A scalar + /// A new containing the quotient. + /// + /// result[i] = vector[i] / scalar; + /// + public static Vector4 Divide(Vector4 vector, float scalar) + { + return new Vector4(vector.X / scalar, vector.Y / scalar, vector.Z / scalar, vector.W / scalar); + } + /// + /// Divides a scalar by a vector. + /// + /// A instance. + /// A scalar + /// A new containing the quotient. + /// + /// result[i] = scalar / vector[i] + /// + public static Vector4 Divide(float scalar, Vector4 vector) + { + return new Vector4(scalar / vector.X, scalar / vector.Y, scalar / vector.Z, scalar / vector.W); + } + /// + /// Divides a vector by another vector. + /// + /// A instance. + /// A instance. + /// A instance to hold the result. + /// + /// result[i] = left[i] / right[i] + /// + public static void Divide(Vector4 left, Vector4 right, ref Vector4 result) + { + result.X = left.X / right.X; + result.Y = left.Y / right.Y; + result.Z = left.Z / right.Z; + result.W = left.W / right.W; + } + /// + /// Divides a vector by a scalar. + /// + /// A instance. + /// A scalar + /// A instance to hold the result. + /// + /// result[i] = vector[i] / scalar + /// + public static void Divide(Vector4 vector, float scalar, ref Vector4 result) + { + result.X = vector.X / scalar; + result.Y = vector.Y / scalar; + result.Z = vector.Z / scalar; + result.W = vector.W / scalar; + } + /// + /// Divides a scalar by a vector. + /// + /// A instance. + /// A scalar + /// A instance to hold the result. + /// + /// result[i] = scalar / vector[i] + /// + public static void Divide(float scalar, Vector4 vector, ref Vector4 result) + { + result.X = scalar / vector.X; + result.Y = scalar / vector.Y; + result.Z = scalar / vector.Z; + result.W = scalar / vector.W; + } + /// + /// Multiplies a vector by a scalar. + /// + /// A instance. + /// A single-precision floating-point number. + /// A new containing the result. + public static Vector4 Multiply(Vector4 vector, float scalar) + { + return new Vector4(vector.X * scalar, vector.Y * scalar, vector.Z * scalar, vector.W * scalar); + } + /// + /// Multiplies a vector by a scalar and put the result in another vector. + /// + /// A instance. + /// A single-precision floating-point number. + /// A instance to hold the result. + public static void Multiply(Vector4 vector, float scalar, ref Vector4 result) + { + result.X = vector.X * scalar; + result.Y = vector.Y * scalar; + result.Z = vector.Z * scalar; + result.W = vector.W * scalar; + } + /// + /// Calculates the dot product of two vectors. + /// + /// A instance. + /// A instance. + /// The dot product value. + public static float DotProduct(Vector4 left, Vector4 right) + { + return (left.X * right.X) + (left.Y * right.Y) + (left.Z * right.Z) + (left.W * right.W); + } + /// + /// Negates a vector. + /// + /// A instance. + /// A new instance containing the negated values. + public static Vector4 Negate(Vector4 vector) + { + return new Vector4(-vector.X, -vector.Y, -vector.Z, -vector.W); + } + /// + /// Tests whether two vectors are approximately equal using default tolerance value. + /// + /// A instance. + /// A instance. + /// if the two vectors are approximately equal; otherwise, . + public static bool ApproxEqual(Vector4 left, Vector4 right) + { + return ApproxEqual(left, right, MathFunctions.Epsilon); + } + /// + /// Tests whether two vectors are approximately equal given a tolerance value. + /// + /// A instance. + /// A instance. + /// The tolerance value used to test approximate equality. + /// if the two vectors are approximately equal; otherwise, . + public static bool ApproxEqual(Vector4 left, Vector4 right, float tolerance) + { + return + ( + (System.Math.Abs(left.X - right.X) <= tolerance) && + (System.Math.Abs(left.Y - right.Y) <= tolerance) && + (System.Math.Abs(left.Z - right.Z) <= tolerance) && + (System.Math.Abs(left.W - right.W) <= tolerance) + ); + } + #endregion + + #region Public Methods + /// + /// Scale the vector so that its length is 1. + /// + public void Normalize() + { + float length = GetLength(); + if (length == 0) + { + throw new DivideByZeroException("Trying to normalize a vector with length of zero."); + } + + _x /= length; + _y /= length; + _z /= length; + _w /= length; + } + /// + /// Calculates the length of the vector. + /// + /// Returns the length of the vector. (Sqrt(X*X + Y*Y)) + public float GetLength() + { + return (float)System.Math.Sqrt(_x * _x + _y * _y + _z * _z + _w * _w); + } + /// + /// Calculates the squared length of the vector. + /// + /// Returns the squared length of the vector. (X*X + Y*Y) + public float GetLengthSquared() + { + return (_x * _x + _y * _y + _z * _z + _w * _w); + } + /// + /// Clamps vector values to zero using a given tolerance value. + /// + /// The tolerance to use. + /// + /// The vector values that are close to zero within the given tolerance are set to zero. + /// + public void ClampZero(float tolerance) + { + _x = MathFunctions.Clamp(_x, 0, tolerance); + _y = MathFunctions.Clamp(_y, 0, tolerance); + _z = MathFunctions.Clamp(_z, 0, tolerance); + _w = MathFunctions.Clamp(_w, 0, tolerance); + } + /// + /// Clamps vector values to zero using the default tolerance value. + /// + /// + /// The vector values that are close to zero within the given tolerance are set to zero. + /// The tolerance value used is + /// + public void ClampZero() + { + _x = MathFunctions.Clamp(_x, 0); + _y = MathFunctions.Clamp(_y, 0); + _z = MathFunctions.Clamp(_z, 0); + _w = MathFunctions.Clamp(_w, 0); + } + #endregion + + #region Overrides + /// + /// Returns the hashcode for this instance. + /// + /// A 32-bit signed integer hash code. + public override int GetHashCode() + { + return _x.GetHashCode() ^ _y.GetHashCode() ^ _z.GetHashCode() ^ _w.GetHashCode(); + } + /// + /// Returns a value indicating whether this instance is equal to + /// the specified object. + /// + /// An object to compare to this instance. + /// if is a and has the same values as this instance; otherwise, . + public override bool Equals(object obj) + { + if (obj is Vector4) + { + Vector4 v = (Vector4)obj; + return (_x == v.X) && (_y == v.Y) && (_z == v.Z) && (_w == v.W); + } + return false; + } + /// + /// Returns a string representation of this object. + /// + /// A string representation of this object. + public override string ToString() + { + return string.Format("({0}, {1}, {2}, {3})", _x, _y, _z, _w); + } + #endregion + + #region Comparison Operators + /// + /// Tests whether two specified vectors are equal. + /// + /// A instance. + /// A instance. + /// if the two vectors are equal; otherwise, . + public static bool operator ==(Vector4 left, Vector4 right) + { + return ValueType.Equals(left, right); + } + /// + /// Tests whether two specified vectors are not equal. + /// + /// A instance. + /// A instance. + /// if the two vectors are not equal; otherwise, . + public static bool operator !=(Vector4 left, Vector4 right) + { + return !ValueType.Equals(left, right); + } + + /// + /// Tests if a vector's components are greater than another vector's components. + /// + /// A instance. + /// A instance. + /// if the left-hand vector's components are greater than the right-hand vector's component; otherwise, . + public static bool operator >(Vector4 left, Vector4 right) + { + return ( + (left._x > right._x) && + (left._y > right._y) && + (left._z > right._z) && + (left._w > right._w)); + } + /// + /// Tests if a vector's components are smaller than another vector's components. + /// + /// A instance. + /// A instance. + /// if the left-hand vector's components are smaller than the right-hand vector's component; otherwise, . + public static bool operator <(Vector4 left, Vector4 right) + { + return ( + (left._x < right._x) && + (left._y < right._y) && + (left._z < right._z) && + (left._w < right._w)); + } + /// + /// Tests if a vector's components are greater or equal than another vector's components. + /// + /// A instance. + /// A instance. + /// if the left-hand vector's components are greater or equal than the right-hand vector's component; otherwise, . + public static bool operator >=(Vector4 left, Vector4 right) + { + return ( + (left._x >= right._x) && + (left._y >= right._y) && + (left._z >= right._z) && + (left._w >= right._w)); + } + /// + /// Tests if a vector's components are smaller or equal than another vector's components. + /// + /// A instance. + /// A instance. + /// if the left-hand vector's components are smaller or equal than the right-hand vector's component; otherwise, . + public static bool operator <=(Vector4 left, Vector4 right) + { + return ( + (left._x <= right._x) && + (left._y <= right._y) && + (left._z <= right._z) && + (left._w <= right._w)); + } + #endregion + + #region Unary Operators + /// + /// Negates the values of the given vector. + /// + /// A instance. + /// A new instance containing the negated values. + public static Vector4 operator -(Vector4 vector) + { + return Vector4.Negate(vector); + } + #endregion + + #region Binary Operators + /// + /// Adds two vectors. + /// + /// A instance. + /// A instance. + /// A new instance containing the sum. + public static Vector4 operator +(Vector4 left, Vector4 right) + { + return Vector4.Add(left, right); + } + /// + /// Adds a vector and a scalar. + /// + /// A instance. + /// A single-precision floating-point number. + /// A new instance containing the sum. + public static Vector4 operator +(Vector4 vector, float scalar) + { + return Vector4.Add(vector, scalar); + } + /// + /// Adds a vector and a scalar. + /// + /// A instance. + /// A single-precision floating-point number. + /// A new instance containing the sum. + public static Vector4 operator +(float scalar, Vector4 vector) + { + return Vector4.Add(vector, scalar); + } + /// + /// Subtracts a vector from a vector. + /// + /// A instance. + /// A instance. + /// A new instance containing the difference. + /// + /// result[i] = left[i] - right[i]. + /// + public static Vector4 operator -(Vector4 left, Vector4 right) + { + return Vector4.Subtract(left, right); + } + /// + /// Subtracts a scalar from a vector. + /// + /// A instance. + /// A single-precision floating-point number. + /// A new instance containing the difference. + /// + /// result[i] = vector[i] - scalar + /// + public static Vector4 operator -(Vector4 vector, float scalar) + { + return Vector4.Subtract(vector, scalar); + } + /// + /// Subtracts a vector from a scalar. + /// + /// A instance. + /// A single-precision floating-point number. + /// A new instance containing the difference. + /// + /// result[i] = scalar - vector[i] + /// + public static Vector4 operator -(float scalar, Vector4 vector) + { + return Vector4.Subtract(scalar, vector); + } + /// + /// Multiplies a vector by a scalar. + /// + /// A instance. + /// A single-precision floating-point number. + /// A new containing the result. + public static Vector4 operator *(Vector4 vector, float scalar) + { + return Vector4.Multiply(vector, scalar); + } + /// + /// Multiplies a vector by a scalar. + /// + /// A instance. + /// A single-precision floating-point number. + /// A new containing the result. + public static Vector4 operator *(float scalar, Vector4 vector) + { + return Vector4.Multiply(vector, scalar); + } + + public static Vector4 operator *(Vector4 vector, Matrix4 M) + { + Vector4 result = new Vector4(); + for (int i = 0; i < 4; ++i) + { + result[i] = 0.0f; + for (int j = 0; j < 4; ++j) + { + result[i] += vector[j] * M[j, i]; + } + } + return result; + } + + /// + /// Divides a vector by a scalar. + /// + /// A instance. + /// A scalar + /// A new containing the quotient. + /// + /// result[i] = vector[i] / scalar; + /// + public static Vector4 operator /(Vector4 vector, float scalar) + { + return Vector4.Divide(vector, scalar); + } + /// + /// Divides a scalar by a vector. + /// + /// A instance. + /// A scalar + /// A new containing the quotient. + /// + /// result[i] = scalar / vector[i] + /// + public static Vector4 operator /(float scalar, Vector4 vector) + { + return Vector4.Divide(scalar, vector); + } + #endregion + + #region Array Indexing Operator + /// + /// Indexer ( [x, y, z, w] ). + /// + public float this[int index] + { + get + { + switch (index) + { + case 0: + return _x; + case 1: + return _y; + case 2: + return _z; + case 3: + return _w; + default: + throw new IndexOutOfRangeException(); + } + } + set + { + switch (index) + { + case 0: + _x = value; + break; + case 1: + _y = value; + break; + case 2: + _z = value; + break; + case 3: + _w = value; + break; + default: + throw new IndexOutOfRangeException(); + } + } + + } + #endregion + + #region Conversion Operators + /// + /// Converts the vector to an array of single-precision floating point values. + /// + /// A instance. + /// An array of single-precision floating point values. + public static explicit operator float[] (Vector4 vector) + { + float[] array = new float[4]; + array[0] = vector.X; + array[1] = vector.Y; + array[2] = vector.Z; + array[3] = vector.W; + return array; + } + /// + /// Converts the vector to a of single-precision floating point values. + /// + /// A instance. + /// A of single-precision floating point values. + public static explicit operator List(Vector4 vector) + { + List list = new List(4); + list.Add(vector.X); + list.Add(vector.Y); + list.Add(vector.Z); + list.Add(vector.W); + + return list; + } + /// + /// Converts the vector to a of single-precision floating point values. + /// + /// A instance. + /// A of single-precision floating point values. + public static explicit operator LinkedList(Vector4 vector) + { + LinkedList list = new LinkedList(); + list.AddLast(vector.X); + list.AddLast(vector.Y); + list.AddLast(vector.Z); + list.AddLast(vector.W); + + return list; + } + #endregion + } + + #region Vector4Converter class + /// + /// Converts a to and from string representation. + /// + public class Vector4Converter : ExpandableObjectConverter + { + /// + /// Returns whether this converter can convert an object of the given type to the type of this converter, using the specified context. + /// + /// An that provides a format context. + /// A that represents the type you want to convert from. + /// true if this converter can perform the conversion; otherwise, false. + public override bool CanConvertFrom(ITypeDescriptorContext context, Type sourceType) + { + if (sourceType == typeof(string)) + return true; + + return base.CanConvertFrom(context, sourceType); + } + /// + /// Returns whether this converter can convert the object to the specified type, using the specified context. + /// + /// An that provides a format context. + /// A that represents the type you want to convert to. + /// true if this converter can perform the conversion; otherwise, false. + public override bool CanConvertTo(ITypeDescriptorContext context, Type destinationType) + { + if (destinationType == typeof(string)) + return true; + + return base.CanConvertTo(context, destinationType); + } + /// + /// Converts the given value object to the specified type, using the specified context and culture information. + /// + /// An that provides a format context. + /// A object. If a null reference (Nothing in Visual Basic) is passed, the current culture is assumed. + /// The to convert. + /// The Type to convert the parameter to. + /// An that represents the converted value. + public override object ConvertTo(ITypeDescriptorContext context, System.Globalization.CultureInfo culture, object value, Type destinationType) + { + if ((destinationType == typeof(string)) && (value is Vector4)) + { + Vector4 v = (Vector4)value; + return v.ToString(); + } + + return base.ConvertTo(context, culture, value, destinationType); + } + /// + /// Converts the given object to the type of this converter, using the specified context and culture information. + /// + /// An that provides a format context. + /// The to use as the current culture. + /// The to convert. + /// An that represents the converted value. + /// Failed parsing from string. + public override object ConvertFrom(ITypeDescriptorContext context, System.Globalization.CultureInfo culture, object value) + { + if (value.GetType() == typeof(string)) + { + return Vector4.Parse((string)value); + } + + return base.ConvertFrom(context, culture, value); + } + + /// + /// Returns whether this object supports a standard set of values that can be picked from a list. + /// + /// An that provides a format context. + /// true if should be called to find a common set of values the object supports; otherwise, false. + public override bool GetStandardValuesSupported(ITypeDescriptorContext context) + { + return true; + } + + /// + /// Returns a collection of standard values for the data type this type converter is designed for when provided with a format context. + /// + /// An that provides a format context that can be used to extract additional information about the environment from which this converter is invoked. This parameter or properties of this parameter can be a null reference. + /// A that holds a standard set of valid values, or a null reference (Nothing in Visual Basic) if the data type does not support a standard set of values. + public override System.ComponentModel.TypeConverter.StandardValuesCollection GetStandardValues(ITypeDescriptorContext context) + { + StandardValuesCollection svc = + new StandardValuesCollection(new object[5] { Vector4.Zero, Vector4.XAxis, Vector4.YAxis, Vector4.ZAxis, Vector4.WAxis }); + + return svc; + } + } + #endregion +} diff --git a/Framework/IO/ByteBuffer.cs b/Framework/IO/ByteBuffer.cs new file mode 100644 index 000000000..c559b030b --- /dev/null +++ b/Framework/IO/ByteBuffer.cs @@ -0,0 +1,480 @@ +/* + * Copyright (C) 2012-2017 CypherCore + * + * 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, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +using Framework.GameMath; +using System; +using System.IO; +using System.Text; + +namespace Framework.IO +{ + public class ByteBuffer : IDisposable + { + public ByteBuffer() + { + writeStream = new BinaryWriter(new MemoryStream()); + } + + public ByteBuffer(byte[] data) + { + readStream = new BinaryReader(new MemoryStream(data)); + } + + public void Dispose() + { + if (writeStream != null) + writeStream.Dispose(); + + if (readStream != null) + readStream.Dispose(); + } + + #region Read Methods + public sbyte ReadInt8() + { + ResetBitPos(); + return readStream.ReadSByte(); + } + + public short ReadInt16() + { + ResetBitPos(); + return readStream.ReadInt16(); + } + + public int ReadInt32() + { + ResetBitPos(); + return readStream.ReadInt32(); + } + + public long ReadInt64() + { + ResetBitPos(); + return readStream.ReadInt64(); + } + + public byte ReadUInt8() + { + ResetBitPos(); + return readStream.ReadByte(); + } + + public ushort ReadUInt16() + { + ResetBitPos(); + return readStream.ReadUInt16(); + } + + public uint ReadUInt32() + { + ResetBitPos(); + return readStream.ReadUInt32(); + } + + public ulong ReadUInt64() + { + ResetBitPos(); + return readStream.ReadUInt64(); + } + + public float ReadFloat() + { + ResetBitPos(); + return readStream.ReadSingle(); + } + + public double ReadDouble() + { + ResetBitPos(); + return readStream.ReadDouble(); + } + + public string ReadCString() + { + ResetBitPos(); + StringBuilder tmpString = new StringBuilder(); + char tmpChar = readStream.ReadChar(); + char tmpEndChar = Convert.ToChar(Encoding.UTF8.GetString(new byte[] { 0 })); + + while (tmpChar != tmpEndChar) + { + tmpString.Append(tmpChar); + tmpChar = readStream.ReadChar(); + } + + return tmpString.ToString(); + } + + public string ReadString(uint length) + { + if (length == 0) + return ""; + + ResetBitPos(); + return Encoding.UTF8.GetString(ReadBytes(length)); + } + + public bool ReadBool() + { + ResetBitPos(); + return readStream.ReadBoolean(); + } + + public byte[] ReadBytes(uint count) + { + ResetBitPos(); + return readStream.ReadBytes((int)count); + } + + public void Skip(int count) + { + ResetBitPos(); + readStream.BaseStream.Position += count; + } + + public uint ReadPackedTime() + { + uint packedDate = ReadUInt32(); + var time = new DateTime((int)((packedDate >> 24) & 0x1F) + 2000, (int)((packedDate >> 20) & 0xF) + 1, (int)((packedDate >> 14) & 0x3F) + 1, (int)(packedDate >> 6) & 0x1F, (int)(packedDate & 0x3F), 0); + return (uint)Time.DateTimeToUnixTime(time); + } + + public Vector3 ReadVector3() + { + return new Vector3(ReadFloat(), ReadFloat(), ReadFloat()); + } + + //BitPacking + public byte ReadBit() + { + if (BitPosition == 8) + { + BitValue = ReadUInt8(); + BitPosition = 0; + } + + int returnValue = BitValue; + BitValue = (byte)(2 * returnValue); + ++BitPosition; + + return (byte)(returnValue >> 7); + } + + public bool HasBit() + { + if (BitPosition == 8) + { + BitValue = ReadUInt8(); + BitPosition = 0; + } + + int returnValue = BitValue; + BitValue = (byte)(2 * returnValue); + ++BitPosition; + + return Convert.ToBoolean(returnValue >> 7); + } + + public T ReadBits(int bitCount) + { + int value = 0; + + for (var i = bitCount - 1; i >= 0; --i) + if (HasBit()) + value |= (1 << i); + + return (T)Convert.ChangeType(value, typeof(T)); + } + #endregion + + #region Write Methods + public void WriteInt8(T data) + { + FlushBits(); + writeStream.Write(Convert.ToSByte(data)); + } + + public void WriteInt16(T data) + { + FlushBits(); + writeStream.Write(Convert.ToInt16(data)); + } + + public void WriteInt32(T data) + { + FlushBits(); + writeStream.Write(Convert.ToInt32(data)); + } + + public void WriteInt64(T data) + { + FlushBits(); + writeStream.Write(Convert.ToInt64(data)); + } + + public void WriteUInt8(T data) + { + FlushBits(); + writeStream.Write(Convert.ToByte(data)); + } + + public void WriteUInt16(T data) + { + FlushBits(); + writeStream.Write(Convert.ToUInt16(data)); + } + + public void WriteUInt32(T data) + { + FlushBits(); + writeStream.Write(Convert.ToUInt32(data)); + } + + public void WriteUInt64(T data) + { + FlushBits(); + writeStream.Write(Convert.ToUInt64(data)); + } + + public void WriteFloat(T data) + { + FlushBits(); + writeStream.Write(Convert.ToSingle(data)); + } + + public void WriteDouble(T data) + { + FlushBits(); + writeStream.Write(Convert.ToDouble(data)); + } + + /// + /// Writes a string to the packet with a null terminated (0) + /// + /// + public void WriteCString(string str) + { + if (string.IsNullOrEmpty(str)) + { + WriteUInt8(0); + return; + } + + WriteString(str); + WriteUInt8(0); + } + + public void WriteString(string str) + { + byte[] sBytes = Encoding.UTF8.GetBytes(str); + WriteBytes(sBytes); + } + + public void WriteBytes(byte[] data) + { + FlushBits(); + writeStream.Write(data, 0, data.Length); + } + + public void WriteBytes(byte[] data, uint count) + { + FlushBits(); + writeStream.Write(data, 0, (int)count); + } + + public void WriteBytes(ByteBuffer buffer) + { + WriteBytes(buffer.GetData()); + } + + public void Replace(int pos, T value) + { + int retpos = (int)writeStream.BaseStream.Position; + + writeStream.Seek(pos, SeekOrigin.Begin); + switch (typeof(T).Name) + { + case "Byte": + WriteUInt8(Convert.ToByte(value)); + break; + case "SByte": + WriteInt8(Convert.ToSByte(value)); + break; + case "Float": + WriteFloat(Convert.ToSingle(value)); + break; + case "Int16": + WriteInt16(Convert.ToInt16(value)); + break; + case "UInt16": + WriteUInt16(Convert.ToUInt16(value)); + break; + case "Int32": + WriteInt32(Convert.ToInt32(value)); + break; + case "UInt32": + WriteUInt32(Convert.ToUInt32(value)); + break; + case "Int64": + WriteInt64(Convert.ToInt64(value)); + break; + case "UInt64": + WriteUInt64(Convert.ToUInt64(value)); + break; + } + writeStream.Seek(retpos, SeekOrigin.Begin); + } + + public void WriteVector3(Vector3 pos) + { + WriteFloat(pos.X); + WriteFloat(pos.Y); + WriteFloat(pos.Z); + } + + public void WriteVector2(Vector2 pos) + { + WriteFloat(pos.X); + WriteFloat(pos.Y); + } + + public void WritePackXYZ(Vector3 pos) + { + uint packed = 0; + packed |= ((uint)(pos.X / 0.25f) & 0x7FF); + packed |= ((uint)(pos.Y / 0.25f) & 0x7FF) << 11; + packed |= ((uint)(pos.Z / 0.25f) & 0x3FF) << 22; + WriteUInt32(packed); + } + + public bool WriteBit(object bit) + { + --BitPosition; + + if (Convert.ToBoolean(bit)) + BitValue |= (byte)(1 << BitPosition); + + if (BitPosition == 0) + { + writeStream.Write(BitValue); + + BitPosition = 8; + BitValue = 0; + } + return Convert.ToBoolean(bit); + } + + public void WriteBits(object bit, int count) + { + for (int i = count - 1; i >= 0; --i) + WriteBit((Convert.ToInt32(bit) >> i) & 1); + } + + public void WritePackedTime(long time) + { + var now = Time.UnixTimeToDateTime(time); + WriteUInt32(Convert.ToUInt32((now.Year - 2000) << 24 | (now.Month - 1) << 20 | (now.Day - 1) << 14 | (int)now.DayOfWeek << 11 | now.Hour << 6 | now.Minute)); + } + + public void WritePackedTime() + { + DateTime now = DateTime.Now; + WriteUInt32(Convert.ToUInt32((now.Year - 2000) << 24 | (now.Month - 1) << 20 | (now.Day - 1) << 14 | (int)now.DayOfWeek << 11 | now.Hour << 6 | now.Minute)); + } + #endregion + + public int GetPosition() + { + long pos = 0; + if (writeStream != null) + pos = writeStream.BaseStream.Position; + else if (readStream != null) + pos = readStream.BaseStream.Position; + + return (int)pos; + } + + public void SetPosition(long pos) + { + if (writeStream != null) + writeStream.BaseStream.Position = pos; + else if (readStream != null) + readStream.BaseStream.Position = pos; + } + + public void FlushBits() + { + if (BitPosition == 8) + return; + + writeStream.Write(BitValue); + BitValue = 0; + BitPosition = 8; + } + + public void ResetBitPos() + { + if (BitPosition > 7) + return; + + BitPosition = 8; + BitValue = 0; + } + + public byte[] GetData() + { + long pos; + Stream stream = GetCurrentStream(); + + var data = new byte[stream.Length]; + + pos = stream.Position; + stream.Seek(0, SeekOrigin.Begin); + for (int i = 0; i < data.Length; i++) + data[i] = (byte)stream.ReadByte(); + + stream.Seek(pos, SeekOrigin.Begin); + return data; + } + + public uint GetSize() + { + return (uint)GetCurrentStream().Length; + } + + public Stream GetCurrentStream() + { + if (writeStream != null) + return writeStream.BaseStream; + else + return readStream.BaseStream; + } + + public void Clear() + { + BitPosition = 8; + BitValue = 0; + writeStream = new BinaryWriter(new MemoryStream()); + } + + byte BitPosition = 8; + byte BitValue; + BinaryWriter writeStream; + BinaryReader readStream; + } +} diff --git a/Framework/IO/StringArguments.cs b/Framework/IO/StringArguments.cs new file mode 100644 index 000000000..7153f568c --- /dev/null +++ b/Framework/IO/StringArguments.cs @@ -0,0 +1,277 @@ +/* + * Copyright (C) 2012-2017 CypherCore + * + * 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, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +using System; +using System.Diagnostics.Contracts; +using System.Text.RegularExpressions; + +namespace Framework.IO +{ + public sealed class StringArguments + { + public StringArguments(string args) + { + if (!args.IsEmpty()) + activestring = args.TrimStart(' '); + activeposition = -1; + } + + public bool Empty() + { + return activestring.IsEmpty(); + } + + public void MoveToNextChar(char c) + { + for (var i = activeposition; i < activestring.Length; ++i) + if (activestring[i] == c) + break; + } + + public string NextString(string delimiters = " ") + { + if (!MoveNext(delimiters)) + return ""; + + Contract.Assume(Current != null); + return Current; + } + + public bool NextBoolean(string delimiters = " ") + { + if (!MoveNext(delimiters)) + return false; + + bool value; + if (bool.TryParse(Current, out value)) + return value; + + return false; + } + + public char NextChar(string delimiters = " ") + { + if (!MoveNext(delimiters)) + return default(char); + + char value; + if (char.TryParse(Current, out value)) + return value; + + return default(char); + } + + public byte NextByte(string delimiters = " ") + { + if (!MoveNext(delimiters)) + return default(byte); + + byte value; + if (byte.TryParse(Current, out value)) + return value; + + return default(byte); + } + + public sbyte NextSByte(string delimiters = " ") + { + if (!MoveNext(delimiters)) + return default(sbyte); + + sbyte value; + if (sbyte.TryParse(Current, out value)) + return value; + + return default(sbyte); + } + + public ushort NextUInt16(string delimiters = " ") + { + if (!MoveNext(delimiters)) + return default(ushort); + + ushort value; + if (ushort.TryParse(Current, out value)) + return value; + + return default(ushort); + } + + public short NextInt16(string delimiters = " ") + { + if (!MoveNext(delimiters)) + return default(short); + + short value; + if (short.TryParse(Current, out value)) + return value; + + return default(short); + } + + public uint NextUInt32(string delimiters = " ") + { + if (!MoveNext(delimiters)) + return default(uint); + + uint value; + if (uint.TryParse(Current, out value)) + return value; + + return default(uint); + } + + public int NextInt32(string delimiters = " ") + { + if (!MoveNext(delimiters)) + return default(int); + + int value; + if (int.TryParse(Current, out value)) + return value; + + return default(int); + } + + public ulong NextUInt64(string delimiters = " ") + { + if (!MoveNext(delimiters)) + return default(ulong); + + ulong value; + if (ulong.TryParse(Current, out value)) + return value; + + return default(ulong); + } + + public long NextInt64(string delimiters = " ") + { + if (!MoveNext(delimiters)) + return default(long); + + long value; + if (long.TryParse(Current, out value)) + return value; + + return default(long); + } + + public float NextSingle(string delimiters = " ") + { + if (!MoveNext(delimiters)) + return default(float); + + float value; + if (float.TryParse(Current, out value)) + return value; + + return default(float); + } + + public double NextDouble(string delimiters = " ") + { + if (!MoveNext(delimiters)) + return default(double); + + double value; + if (double.TryParse(Current, out value)) + return value; + + return default(double); + } + + public decimal NextDecimal(string delimiters = " ") + { + if (!MoveNext(delimiters)) + return default(decimal); + + decimal value; + if (decimal.TryParse(Current, out value)) + return value; + + return default(decimal); + } + + public void AlignToNextChar() + { + while (activeposition < activestring.Length && activestring[activeposition] != ' ') + activeposition++; + } + + public char this[int index] + { + get { return activestring[index]; } + } + + public string GetString() + { + return activestring; + } + + public void Reset() + { + activeposition = -1; + Current = null; + } + + bool MoveNext(string delimiters) + { + //the stringtotokenize was never set: + if (activestring == null) + return false; + + //all tokens have already been extracted: + if (activeposition == activestring.Length) + return false; + + //bypass delimiters: + activeposition++; + while (activeposition < activestring.Length && delimiters.IndexOf(activestring[activeposition]) > -1) + { + activeposition++; + } + + //only delimiters were left, so return null: + if (activeposition == activestring.Length) + return false; + + //get starting position of string to return: + int startingposition = activeposition; + + //read until next delimiter: + do + { + activeposition++; + } while (activeposition < activestring.Length && delimiters.IndexOf(activestring[activeposition]) == -1); + + Current = activestring.Substring(startingposition, activeposition - startingposition); + return true; + } + + bool Match(string pattern, out Match m) + { + Regex r = new Regex(pattern); + m = r.Match(activestring); + return m.Success; + } + + private string activestring; + private int activeposition; + private string Current; + } +} diff --git a/Framework/IO/Zlib/Adler32.cs b/Framework/IO/Zlib/Adler32.cs new file mode 100644 index 000000000..852eefaf8 --- /dev/null +++ b/Framework/IO/Zlib/Adler32.cs @@ -0,0 +1,168 @@ +// adler32.cs -- compute the Adler-32 checksum of a data stream +// Copyright (C) 1995-2007 Mark Adler +// Copyright (C) 2007-2011 by the Authors +// For conditions of distribution and use, see copyright notice in License.txt + +using System; + +namespace Framework.IO +{ + public static partial class ZLib + { + private const uint BASE=65521; // largest prime smaller than 65536 + private const uint NMAX=5552; // NMAX is the largest n such that 255n(n+1)/2 + (n+1)(BASE-1) <= 2^32-1 + + // ========================================================================= + + // Update a running Adler-32 checksum with the bytes buf[0..len-1] and return the updated checksum. + // If buf is NULL, this function returns the required initial value for the checksum. + // An Adler-32 checksum is almost as reliable as a CRC32 but can be computed much faster. + // + // Usage example: + // uint adler=adler32(0, null, 0); + // while(read_buffer(buffer, length)!=EOF) + // { + // adler=adler32(adler, buffer, length); + // } + // if(adler!=original_adler) error(); + + public static uint adler32(uint adler, byte[] buf, uint len) + { + return adler32(adler, buf, 0, len); + } + + public static uint adler32(uint adler, byte[] buf, uint ind, uint len) + { + // initial Adler-32 value (deferred check for len==1 speed) + if(buf==null) return 1; + + // split Adler-32 into component sums + uint sum2=(adler>>16)&0xffff; + adler&=0xffff; + + //uint ind=0; // index in buf + + // in case user likes doing a byte at a time, keep it fast + if(len==1) + { + adler+=buf[ind]; + if(adler>=BASE) adler-=BASE; + sum2+=adler; + if(sum2>=BASE) sum2-=BASE; + return adler|(sum2<<16); + } + + // in case short lengths are provided, keep it somewhat fast + if(len<16) + { + while(len--!=0) + { + adler+=buf[ind++]; + sum2+=adler; + } + if(adler>=BASE) adler-=BASE; + sum2%=BASE; // only added so many BASE's + return adler|(sum2<<16); + } + + // do length NMAX blocks -- requires just one modulo operation + while(len>=NMAX) + { + len-=NMAX; + uint n=NMAX/16; // NMAX is divisible by 16 + do + { + // 16 sums unrolled + adler+=buf[ind++]; sum2+=adler; + adler+=buf[ind++]; sum2+=adler; + adler+=buf[ind++]; sum2+=adler; + adler+=buf[ind++]; sum2+=adler; + adler+=buf[ind++]; sum2+=adler; + adler+=buf[ind++]; sum2+=adler; + adler+=buf[ind++]; sum2+=adler; + adler+=buf[ind++]; sum2+=adler; + adler+=buf[ind++]; sum2+=adler; + adler+=buf[ind++]; sum2+=adler; + adler+=buf[ind++]; sum2+=adler; + adler+=buf[ind++]; sum2+=adler; + adler+=buf[ind++]; sum2+=adler; + adler+=buf[ind++]; sum2+=adler; + adler+=buf[ind++]; sum2+=adler; + adler+=buf[ind++]; sum2+=adler; + } while(--n!=0); + + adler%=BASE; + sum2%=BASE; + } + + // do remaining bytes (less than NMAX, still just one modulo) + if(len!=0) + { // avoid modulos if none remaining + while(len>=16) + { + len-=16; + adler+=buf[ind++]; sum2+=adler; + adler+=buf[ind++]; sum2+=adler; + adler+=buf[ind++]; sum2+=adler; + adler+=buf[ind++]; sum2+=adler; + adler+=buf[ind++]; sum2+=adler; + adler+=buf[ind++]; sum2+=adler; + adler+=buf[ind++]; sum2+=adler; + adler+=buf[ind++]; sum2+=adler; + adler+=buf[ind++]; sum2+=adler; + adler+=buf[ind++]; sum2+=adler; + adler+=buf[ind++]; sum2+=adler; + adler+=buf[ind++]; sum2+=adler; + adler+=buf[ind++]; sum2+=adler; + adler+=buf[ind++]; sum2+=adler; + adler+=buf[ind++]; sum2+=adler; + adler+=buf[ind++]; sum2+=adler; + } + + while(len--!=0) + { + adler+=buf[ind++]; + sum2+=adler; + } + + adler%=BASE; + sum2%=BASE; + } + + // return recombined sums + return adler|(sum2<<16); + } + + // ========================================================================= + + // Combine two Adler-32 checksums into one. For two sequences of bytes, seq1 + // and seq2 with lengths len1 and len2, Adler-32 checksums were calculated for + // each, adler1 and adler2. adler32_combine() returns the Adler-32 checksum of + // seq1 and seq2 concatenated, requiring only adler1, adler2, and len2. + + public static uint adler32_combine_(uint adler1, uint adler2, uint len2) + { // the derivation of this formula is left as an exercise for the reader + uint rem=len2%BASE; + uint sum1=adler1&0xffff; + uint sum2=(rem*sum1)%BASE; + sum1+=(adler2&0xffff)+BASE-1; + sum2+=((adler1>>16)&0xffff)+((adler2>>16)&0xffff)+BASE-rem; + if(sum1>=BASE) sum1-=BASE; + if(sum1>=BASE) sum1-=BASE; + if(sum2>=(BASE<<1)) sum2-=(BASE<<1); + if(sum2>=BASE) sum2-=BASE; + return sum1|(sum2<<16); + } + + // ========================================================================= + public static uint adler32_combine(uint adler1, uint adler2, uint len2) + { + return adler32_combine_(adler1, adler2, len2); + } + + public static uint adler32_combine64(uint adler1, uint adler2, uint len2) + { + return adler32_combine_(adler1, adler2, len2); + } + } +} diff --git a/Framework/IO/Zlib/Crc32.cs b/Framework/IO/Zlib/Crc32.cs new file mode 100644 index 000000000..b108de53d --- /dev/null +++ b/Framework/IO/Zlib/Crc32.cs @@ -0,0 +1,368 @@ +// CRC32.cs -- compute the CRC-32 of a data stream +// Copyright (C) 1995-2006, 2010 Mark Adler +// Copyright (C) 2007-2011 by the Authors +// For conditions of distribution and use, see copyright notice in License.txt + +// Thanks to Rodney Brown for his contribution of faster +// CRC methods: exclusive-oring 32 bits of data at a time, and pre-computing +// tables for updating the shift register in one step with three exclusive-ors +// instead of four steps with four exclusive-ors. + +// Note on DYNAMIC_CRC_TABLE: Dynamic generation of CRC table has been removed +// in this port. The original source contained a compiler switch to use either +// the dynamic generation of the CRC table or a pregenerated source code version +// of the table. This port uses the pregenerated source code version. + +#define SAFE_VERSION + +using System; + +namespace Framework.IO +{ + public static partial class ZLib + { + #region crc_table + private static readonly uint[,] crc_table=new uint[,] + { + { + 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 +#if !SAFE_VERSION + }, + { + 0x00000000, 0x191b3141, 0x32366282, 0x2b2d53c3, 0x646cc504, 0x7d77f445, 0x565aa786, 0x4f4196c7, + 0xc8d98a08, 0xd1c2bb49, 0xfaefe88a, 0xe3f4d9cb, 0xacb54f0c, 0xb5ae7e4d, 0x9e832d8e, 0x87981ccf, + 0x4ac21251, 0x53d92310, 0x78f470d3, 0x61ef4192, 0x2eaed755, 0x37b5e614, 0x1c98b5d7, 0x05838496, + 0x821b9859, 0x9b00a918, 0xb02dfadb, 0xa936cb9a, 0xe6775d5d, 0xff6c6c1c, 0xd4413fdf, 0xcd5a0e9e, + 0x958424a2, 0x8c9f15e3, 0xa7b24620, 0xbea97761, 0xf1e8e1a6, 0xe8f3d0e7, 0xc3de8324, 0xdac5b265, + 0x5d5daeaa, 0x44469feb, 0x6f6bcc28, 0x7670fd69, 0x39316bae, 0x202a5aef, 0x0b07092c, 0x121c386d, + 0xdf4636f3, 0xc65d07b2, 0xed705471, 0xf46b6530, 0xbb2af3f7, 0xa231c2b6, 0x891c9175, 0x9007a034, + 0x179fbcfb, 0x0e848dba, 0x25a9de79, 0x3cb2ef38, 0x73f379ff, 0x6ae848be, 0x41c51b7d, 0x58de2a3c, + 0xf0794f05, 0xe9627e44, 0xc24f2d87, 0xdb541cc6, 0x94158a01, 0x8d0ebb40, 0xa623e883, 0xbf38d9c2, + 0x38a0c50d, 0x21bbf44c, 0x0a96a78f, 0x138d96ce, 0x5ccc0009, 0x45d73148, 0x6efa628b, 0x77e153ca, + 0xbabb5d54, 0xa3a06c15, 0x888d3fd6, 0x91960e97, 0xded79850, 0xc7cca911, 0xece1fad2, 0xf5facb93, + 0x7262d75c, 0x6b79e61d, 0x4054b5de, 0x594f849f, 0x160e1258, 0x0f152319, 0x243870da, 0x3d23419b, + 0x65fd6ba7, 0x7ce65ae6, 0x57cb0925, 0x4ed03864, 0x0191aea3, 0x188a9fe2, 0x33a7cc21, 0x2abcfd60, + 0xad24e1af, 0xb43fd0ee, 0x9f12832d, 0x8609b26c, 0xc94824ab, 0xd05315ea, 0xfb7e4629, 0xe2657768, + 0x2f3f79f6, 0x362448b7, 0x1d091b74, 0x04122a35, 0x4b53bcf2, 0x52488db3, 0x7965de70, 0x607eef31, + 0xe7e6f3fe, 0xfefdc2bf, 0xd5d0917c, 0xcccba03d, 0x838a36fa, 0x9a9107bb, 0xb1bc5478, 0xa8a76539, + 0x3b83984b, 0x2298a90a, 0x09b5fac9, 0x10aecb88, 0x5fef5d4f, 0x46f46c0e, 0x6dd93fcd, 0x74c20e8c, + 0xf35a1243, 0xea412302, 0xc16c70c1, 0xd8774180, 0x9736d747, 0x8e2de606, 0xa500b5c5, 0xbc1b8484, + 0x71418a1a, 0x685abb5b, 0x4377e898, 0x5a6cd9d9, 0x152d4f1e, 0x0c367e5f, 0x271b2d9c, 0x3e001cdd, + 0xb9980012, 0xa0833153, 0x8bae6290, 0x92b553d1, 0xddf4c516, 0xc4eff457, 0xefc2a794, 0xf6d996d5, + 0xae07bce9, 0xb71c8da8, 0x9c31de6b, 0x852aef2a, 0xca6b79ed, 0xd37048ac, 0xf85d1b6f, 0xe1462a2e, + 0x66de36e1, 0x7fc507a0, 0x54e85463, 0x4df36522, 0x02b2f3e5, 0x1ba9c2a4, 0x30849167, 0x299fa026, + 0xe4c5aeb8, 0xfdde9ff9, 0xd6f3cc3a, 0xcfe8fd7b, 0x80a96bbc, 0x99b25afd, 0xb29f093e, 0xab84387f, + 0x2c1c24b0, 0x350715f1, 0x1e2a4632, 0x07317773, 0x4870e1b4, 0x516bd0f5, 0x7a468336, 0x635db277, + 0xcbfad74e, 0xd2e1e60f, 0xf9ccb5cc, 0xe0d7848d, 0xaf96124a, 0xb68d230b, 0x9da070c8, 0x84bb4189, + 0x03235d46, 0x1a386c07, 0x31153fc4, 0x280e0e85, 0x674f9842, 0x7e54a903, 0x5579fac0, 0x4c62cb81, + 0x8138c51f, 0x9823f45e, 0xb30ea79d, 0xaa1596dc, 0xe554001b, 0xfc4f315a, 0xd7626299, 0xce7953d8, + 0x49e14f17, 0x50fa7e56, 0x7bd72d95, 0x62cc1cd4, 0x2d8d8a13, 0x3496bb52, 0x1fbbe891, 0x06a0d9d0, + 0x5e7ef3ec, 0x4765c2ad, 0x6c48916e, 0x7553a02f, 0x3a1236e8, 0x230907a9, 0x0824546a, 0x113f652b, + 0x96a779e4, 0x8fbc48a5, 0xa4911b66, 0xbd8a2a27, 0xf2cbbce0, 0xebd08da1, 0xc0fdde62, 0xd9e6ef23, + 0x14bce1bd, 0x0da7d0fc, 0x268a833f, 0x3f91b27e, 0x70d024b9, 0x69cb15f8, 0x42e6463b, 0x5bfd777a, + 0xdc656bb5, 0xc57e5af4, 0xee530937, 0xf7483876, 0xb809aeb1, 0xa1129ff0, 0x8a3fcc33, 0x9324fd72 + }, + { + 0x00000000, 0x01c26a37, 0x0384d46e, 0x0246be59, 0x0709a8dc, 0x06cbc2eb, 0x048d7cb2, 0x054f1685, + 0x0e1351b8, 0x0fd13b8f, 0x0d9785d6, 0x0c55efe1, 0x091af964, 0x08d89353, 0x0a9e2d0a, 0x0b5c473d, + 0x1c26a370, 0x1de4c947, 0x1fa2771e, 0x1e601d29, 0x1b2f0bac, 0x1aed619b, 0x18abdfc2, 0x1969b5f5, + 0x1235f2c8, 0x13f798ff, 0x11b126a6, 0x10734c91, 0x153c5a14, 0x14fe3023, 0x16b88e7a, 0x177ae44d, + 0x384d46e0, 0x398f2cd7, 0x3bc9928e, 0x3a0bf8b9, 0x3f44ee3c, 0x3e86840b, 0x3cc03a52, 0x3d025065, + 0x365e1758, 0x379c7d6f, 0x35dac336, 0x3418a901, 0x3157bf84, 0x3095d5b3, 0x32d36bea, 0x331101dd, + 0x246be590, 0x25a98fa7, 0x27ef31fe, 0x262d5bc9, 0x23624d4c, 0x22a0277b, 0x20e69922, 0x2124f315, + 0x2a78b428, 0x2bbade1f, 0x29fc6046, 0x283e0a71, 0x2d711cf4, 0x2cb376c3, 0x2ef5c89a, 0x2f37a2ad, + 0x709a8dc0, 0x7158e7f7, 0x731e59ae, 0x72dc3399, 0x7793251c, 0x76514f2b, 0x7417f172, 0x75d59b45, + 0x7e89dc78, 0x7f4bb64f, 0x7d0d0816, 0x7ccf6221, 0x798074a4, 0x78421e93, 0x7a04a0ca, 0x7bc6cafd, + 0x6cbc2eb0, 0x6d7e4487, 0x6f38fade, 0x6efa90e9, 0x6bb5866c, 0x6a77ec5b, 0x68315202, 0x69f33835, + 0x62af7f08, 0x636d153f, 0x612bab66, 0x60e9c151, 0x65a6d7d4, 0x6464bde3, 0x662203ba, 0x67e0698d, + 0x48d7cb20, 0x4915a117, 0x4b531f4e, 0x4a917579, 0x4fde63fc, 0x4e1c09cb, 0x4c5ab792, 0x4d98dda5, + 0x46c49a98, 0x4706f0af, 0x45404ef6, 0x448224c1, 0x41cd3244, 0x400f5873, 0x4249e62a, 0x438b8c1d, + 0x54f16850, 0x55330267, 0x5775bc3e, 0x56b7d609, 0x53f8c08c, 0x523aaabb, 0x507c14e2, 0x51be7ed5, + 0x5ae239e8, 0x5b2053df, 0x5966ed86, 0x58a487b1, 0x5deb9134, 0x5c29fb03, 0x5e6f455a, 0x5fad2f6d, + 0xe1351b80, 0xe0f771b7, 0xe2b1cfee, 0xe373a5d9, 0xe63cb35c, 0xe7fed96b, 0xe5b86732, 0xe47a0d05, + 0xef264a38, 0xeee4200f, 0xeca29e56, 0xed60f461, 0xe82fe2e4, 0xe9ed88d3, 0xebab368a, 0xea695cbd, + 0xfd13b8f0, 0xfcd1d2c7, 0xfe976c9e, 0xff5506a9, 0xfa1a102c, 0xfbd87a1b, 0xf99ec442, 0xf85cae75, + 0xf300e948, 0xf2c2837f, 0xf0843d26, 0xf1465711, 0xf4094194, 0xf5cb2ba3, 0xf78d95fa, 0xf64fffcd, + 0xd9785d60, 0xd8ba3757, 0xdafc890e, 0xdb3ee339, 0xde71f5bc, 0xdfb39f8b, 0xddf521d2, 0xdc374be5, + 0xd76b0cd8, 0xd6a966ef, 0xd4efd8b6, 0xd52db281, 0xd062a404, 0xd1a0ce33, 0xd3e6706a, 0xd2241a5d, + 0xc55efe10, 0xc49c9427, 0xc6da2a7e, 0xc7184049, 0xc25756cc, 0xc3953cfb, 0xc1d382a2, 0xc011e895, + 0xcb4dafa8, 0xca8fc59f, 0xc8c97bc6, 0xc90b11f1, 0xcc440774, 0xcd866d43, 0xcfc0d31a, 0xce02b92d, + 0x91af9640, 0x906dfc77, 0x922b422e, 0x93e92819, 0x96a63e9c, 0x976454ab, 0x9522eaf2, 0x94e080c5, + 0x9fbcc7f8, 0x9e7eadcf, 0x9c381396, 0x9dfa79a1, 0x98b56f24, 0x99770513, 0x9b31bb4a, 0x9af3d17d, + 0x8d893530, 0x8c4b5f07, 0x8e0de15e, 0x8fcf8b69, 0x8a809dec, 0x8b42f7db, 0x89044982, 0x88c623b5, + 0x839a6488, 0x82580ebf, 0x801eb0e6, 0x81dcdad1, 0x8493cc54, 0x8551a663, 0x8717183a, 0x86d5720d, + 0xa9e2d0a0, 0xa820ba97, 0xaa6604ce, 0xaba46ef9, 0xaeeb787c, 0xaf29124b, 0xad6fac12, 0xacadc625, + 0xa7f18118, 0xa633eb2f, 0xa4755576, 0xa5b73f41, 0xa0f829c4, 0xa13a43f3, 0xa37cfdaa, 0xa2be979d, + 0xb5c473d0, 0xb40619e7, 0xb640a7be, 0xb782cd89, 0xb2cddb0c, 0xb30fb13b, 0xb1490f62, 0xb08b6555, + 0xbbd72268, 0xba15485f, 0xb853f606, 0xb9919c31, 0xbcde8ab4, 0xbd1ce083, 0xbf5a5eda, 0xbe9834ed + }, + { + 0x00000000, 0xb8bc6765, 0xaa09c88b, 0x12b5afee, 0x8f629757, 0x37def032, 0x256b5fdc, 0x9dd738b9, + 0xc5b428ef, 0x7d084f8a, 0x6fbde064, 0xd7018701, 0x4ad6bfb8, 0xf26ad8dd, 0xe0df7733, 0x58631056, + 0x5019579f, 0xe8a530fa, 0xfa109f14, 0x42acf871, 0xdf7bc0c8, 0x67c7a7ad, 0x75720843, 0xcdce6f26, + 0x95ad7f70, 0x2d111815, 0x3fa4b7fb, 0x8718d09e, 0x1acfe827, 0xa2738f42, 0xb0c620ac, 0x087a47c9, + 0xa032af3e, 0x188ec85b, 0x0a3b67b5, 0xb28700d0, 0x2f503869, 0x97ec5f0c, 0x8559f0e2, 0x3de59787, + 0x658687d1, 0xdd3ae0b4, 0xcf8f4f5a, 0x7733283f, 0xeae41086, 0x525877e3, 0x40edd80d, 0xf851bf68, + 0xf02bf8a1, 0x48979fc4, 0x5a22302a, 0xe29e574f, 0x7f496ff6, 0xc7f50893, 0xd540a77d, 0x6dfcc018, + 0x359fd04e, 0x8d23b72b, 0x9f9618c5, 0x272a7fa0, 0xbafd4719, 0x0241207c, 0x10f48f92, 0xa848e8f7, + 0x9b14583d, 0x23a83f58, 0x311d90b6, 0x89a1f7d3, 0x1476cf6a, 0xaccaa80f, 0xbe7f07e1, 0x06c36084, + 0x5ea070d2, 0xe61c17b7, 0xf4a9b859, 0x4c15df3c, 0xd1c2e785, 0x697e80e0, 0x7bcb2f0e, 0xc377486b, + 0xcb0d0fa2, 0x73b168c7, 0x6104c729, 0xd9b8a04c, 0x446f98f5, 0xfcd3ff90, 0xee66507e, 0x56da371b, + 0x0eb9274d, 0xb6054028, 0xa4b0efc6, 0x1c0c88a3, 0x81dbb01a, 0x3967d77f, 0x2bd27891, 0x936e1ff4, + 0x3b26f703, 0x839a9066, 0x912f3f88, 0x299358ed, 0xb4446054, 0x0cf80731, 0x1e4da8df, 0xa6f1cfba, + 0xfe92dfec, 0x462eb889, 0x549b1767, 0xec277002, 0x71f048bb, 0xc94c2fde, 0xdbf98030, 0x6345e755, + 0x6b3fa09c, 0xd383c7f9, 0xc1366817, 0x798a0f72, 0xe45d37cb, 0x5ce150ae, 0x4e54ff40, 0xf6e89825, + 0xae8b8873, 0x1637ef16, 0x048240f8, 0xbc3e279d, 0x21e91f24, 0x99557841, 0x8be0d7af, 0x335cb0ca, + 0xed59b63b, 0x55e5d15e, 0x47507eb0, 0xffec19d5, 0x623b216c, 0xda874609, 0xc832e9e7, 0x708e8e82, + 0x28ed9ed4, 0x9051f9b1, 0x82e4565f, 0x3a58313a, 0xa78f0983, 0x1f336ee6, 0x0d86c108, 0xb53aa66d, + 0xbd40e1a4, 0x05fc86c1, 0x1749292f, 0xaff54e4a, 0x322276f3, 0x8a9e1196, 0x982bbe78, 0x2097d91d, + 0x78f4c94b, 0xc048ae2e, 0xd2fd01c0, 0x6a4166a5, 0xf7965e1c, 0x4f2a3979, 0x5d9f9697, 0xe523f1f2, + 0x4d6b1905, 0xf5d77e60, 0xe762d18e, 0x5fdeb6eb, 0xc2098e52, 0x7ab5e937, 0x680046d9, 0xd0bc21bc, + 0x88df31ea, 0x3063568f, 0x22d6f961, 0x9a6a9e04, 0x07bda6bd, 0xbf01c1d8, 0xadb46e36, 0x15080953, + 0x1d724e9a, 0xa5ce29ff, 0xb77b8611, 0x0fc7e174, 0x9210d9cd, 0x2aacbea8, 0x38191146, 0x80a57623, + 0xd8c66675, 0x607a0110, 0x72cfaefe, 0xca73c99b, 0x57a4f122, 0xef189647, 0xfdad39a9, 0x45115ecc, + 0x764dee06, 0xcef18963, 0xdc44268d, 0x64f841e8, 0xf92f7951, 0x41931e34, 0x5326b1da, 0xeb9ad6bf, + 0xb3f9c6e9, 0x0b45a18c, 0x19f00e62, 0xa14c6907, 0x3c9b51be, 0x842736db, 0x96929935, 0x2e2efe50, + 0x2654b999, 0x9ee8defc, 0x8c5d7112, 0x34e11677, 0xa9362ece, 0x118a49ab, 0x033fe645, 0xbb838120, + 0xe3e09176, 0x5b5cf613, 0x49e959fd, 0xf1553e98, 0x6c820621, 0xd43e6144, 0xc68bceaa, 0x7e37a9cf, + 0xd67f4138, 0x6ec3265d, 0x7c7689b3, 0xc4caeed6, 0x591dd66f, 0xe1a1b10a, 0xf3141ee4, 0x4ba87981, + 0x13cb69d7, 0xab770eb2, 0xb9c2a15c, 0x017ec639, 0x9ca9fe80, 0x241599e5, 0x36a0360b, 0x8e1c516e, + 0x866616a7, 0x3eda71c2, 0x2c6fde2c, 0x94d3b949, 0x090481f0, 0xb1b8e695, 0xa30d497b, 0x1bb12e1e, + 0x43d23e48, 0xfb6e592d, 0xe9dbf6c3, 0x516791a6, 0xccb0a91f, 0x740cce7a, 0x66b96194, 0xde0506f1 +#endif + } + }; + #endregion + + // ========================================================================= + + // Update a running CRC-32 with the bytes buf[0..len-1] and return the + // updated CRC-32. If buf is NULL, this function returns the required initial + // value for the for the crc. Pre- and post-conditioning (one's complement) is + // performed within this function so it shouldn't be done by the application. + // Usage example: + // + // ulong crc = 0; + // while(read_buffer(buffer, length)!=EOF) crc=crc32(crc, buffer); + // if(crc!=original_crc) error(); + + public static uint crc32(uint crc, byte[] bytes, long count) + { + return crc32(crc, bytes, 0, count); + } + +#if SAFE_VERSION + public static uint crc32(uint crc, byte[] bytes, long startIndex, long count) + { + if(bytes==null) return 0; + if(startIndex<0) throw new ArgumentOutOfRangeException("startIndex", "<0"); + if(count<0) throw new ArgumentOutOfRangeException("count", "<0"); + + long len=startIndex+count; + if(len>bytes.LongLength) throw new ArgumentOutOfRangeException("startIndex+count", ">bytes.Length"); + + long ind=startIndex; + + crc=~crc; + while(len>=8) + { + // 8 calculations unrolled + crc=crc_table[0, (crc^bytes[ind++])&0xff]^(crc>>8); + crc=crc_table[0, (crc^bytes[ind++])&0xff]^(crc>>8); + crc=crc_table[0, (crc^bytes[ind++])&0xff]^(crc>>8); + crc=crc_table[0, (crc^bytes[ind++])&0xff]^(crc>>8); + crc=crc_table[0, (crc^bytes[ind++])&0xff]^(crc>>8); + crc=crc_table[0, (crc^bytes[ind++])&0xff]^(crc>>8); + crc=crc_table[0, (crc^bytes[ind++])&0xff]^(crc>>8); + crc=crc_table[0, (crc^bytes[ind++])&0xff]^(crc>>8); + len-=8; + } + + if(len!=0) + { + do + { + crc=crc_table[0, (crc^bytes[ind++])&0xff]^(crc>>8); + } while(--len!=0); + } + return ~crc; + } +#else + public static uint crc32(uint crc, byte[] bytes, long startIndex, long count) + { + if(bytes==null) return 0; + if(startIndex<0) throw new ArgumentOutOfRangeException("startIndex", "<0"); + if(count<0) throw new ArgumentOutOfRangeException("count", "<0"); + + long len=startIndex+count; + if(len>bytes.LongLength) throw new ArgumentOutOfRangeException("startIndex+count", ">bytes.Length"); + + long ind=startIndex; + + uint c=~crc; + + if(len>=4) + { + unsafe + { + fixed(byte* buf_=bytes) + { + uint* buf4=(uint*)buf_; + + while(len>=32) + { + // 8 calculations unrolled + c^=*buf4++; c=crc_table[3, c&0xff]^crc_table[2, (c>>8)&0xff]^crc_table[1, (c>>16)&0xff]^crc_table[0, c>>24]; + c^=*buf4++; c=crc_table[3, c&0xff]^crc_table[2, (c>>8)&0xff]^crc_table[1, (c>>16)&0xff]^crc_table[0, c>>24]; + c^=*buf4++; c=crc_table[3, c&0xff]^crc_table[2, (c>>8)&0xff]^crc_table[1, (c>>16)&0xff]^crc_table[0, c>>24]; + c^=*buf4++; c=crc_table[3, c&0xff]^crc_table[2, (c>>8)&0xff]^crc_table[1, (c>>16)&0xff]^crc_table[0, c>>24]; + c^=*buf4++; c=crc_table[3, c&0xff]^crc_table[2, (c>>8)&0xff]^crc_table[1, (c>>16)&0xff]^crc_table[0, c>>24]; + c^=*buf4++; c=crc_table[3, c&0xff]^crc_table[2, (c>>8)&0xff]^crc_table[1, (c>>16)&0xff]^crc_table[0, c>>24]; + c^=*buf4++; c=crc_table[3, c&0xff]^crc_table[2, (c>>8)&0xff]^crc_table[1, (c>>16)&0xff]^crc_table[0, c>>24]; + c^=*buf4++; c=crc_table[3, c&0xff]^crc_table[2, (c>>8)&0xff]^crc_table[1, (c>>16)&0xff]^crc_table[0, c>>24]; + ind+=32; + len-=32; + } + + while(len>=4) + { + c^=*buf4++; c=crc_table[3, c&0xff]^crc_table[2, (c>>8)&0xff]^crc_table[1, (c>>16)&0xff]^crc_table[0, c>>24]; + ind+=4; + len-=4; + } + } + } + } + + if(len!=0) + { + do + { + c=crc_table[0, (c^bytes[ind++])&0xff]^(c>>8); + } while(--len!=0); + } + + return ~c; + } +#endif + + // ========================================================================= + + private const uint GF2_DIM=32; // dimension of GF(2) vectors (length of CRC) + + // ========================================================================= + + static uint gf2_matrix_times(uint[] mat, uint vec) + { + uint sum=0; + int ind=0; + while(vec!=0) + { + if((vec&1)!=0) sum^=mat[ind]; + vec>>=1; + ind++; + } + return sum; + } + + // ========================================================================= + + static void gf2_matrix_square(uint[] square, uint[] mat) + { + for(int n=0; n>=1; + + // if no more bits set, then done + if(count==0) break; + + // another iteration of the loop with odd and even swapped + gf2_matrix_square(odd, even); + if((count&1)!=0) crc1=gf2_matrix_times(odd, crc1); + count>>=1; + } while(count!=0); // if no more bits set, then done + + // return combined crc + return crc1^crc2; + } + + // ========================================================================= + public static uint crc32_combine(uint crc1, uint crc2, long count) + { + return crc32_combine_(crc1, crc2, count); + } + + public static uint crc32_combine64(uint crc1, uint crc2, long count) + { + return crc32_combine_(crc1, crc2, count); + } + } +} diff --git a/Framework/IO/Zlib/Deflate.cs b/Framework/IO/Zlib/Deflate.cs new file mode 100644 index 000000000..eac7d9f88 --- /dev/null +++ b/Framework/IO/Zlib/Deflate.cs @@ -0,0 +1,2298 @@ +// deflate.cs -- internal compression state & compress data using the deflation algorithm +// Copyright (C) 1995-2010 Jean-loup Gailly. +// Copyright (C) 2007-2011 by the Authors +// For conditions of distribution and use, see copyright notice in License.txt + +#region ALGORITHM , ACKNOWLEDGEMENTS & REFERENCES +// ALGORITHM +// +// The "deflation" process depends on being able to identify portions +// of the input text which are identical to earlier input (within a +// sliding window trailing behind the input currently being processed). +// +// The most straightforward technique turns out to be the fastest for +// most input files: try all possible matches and select the longest. +// The key feature of this algorithm is that insertions into the string +// dictionary are very simple and thus fast, and deletions are avoided +// completely. Insertions are performed at each input character, whereas +// string matches are performed only when the previous match ends. So it +// is preferable to spend more time in matches to allow very fast string +// insertions and avoid deletions. The matching algorithm for small +// strings is inspired from that of Rabin & Karp. A brute force approach +// is used to find longer strings when a small match has been found. +// A similar algorithm is used in comic (by Jan-Mark Wams) and freeze +// (by Leonid Broukhis). +// A previous version of this file used a more sophisticated algorithm +// (by Fiala and Greene) which is guaranteed to run in linear amortized +// time, but has a larger average cost, uses more memory and is patented. +// However the F&G algorithm may be faster for some highly redundant +// files if the parameter max_chain_length (described below) is too large. +// +// ACKNOWLEDGEMENTS +// +// The idea of lazy evaluation of matches is due to Jan-Mark Wams, and +// I found it in 'freeze' written by Leonid Broukhis. +// Thanks to many people for bug reports and testing. +// +// REFERENCES +// +// Deutsch, L.P.,"DEFLATE Compressed Data Format Specification". +// Available in http://www.ietf.org/rfc/rfc1951.txt +// +// A description of the Rabin and Karp algorithm is given in the book +// "Algorithms" by R. Sedgewick, Addison-Wesley, p252. +// +// Fiala,E.R., and Greene,D.H. +// Data Compression with Finite Windows, Comm.ACM, 32,4 (1989) 490-595 +#endregion + +using System; + +namespace Framework.IO +{ + public static partial class ZLib + { + #region deflate.h + // =========================================================================== + // Internal compression state. + + // number of length codes, not counting the special END_BLOCK code + private const int LENGTH_CODES=29; + + // number of literal bytes 0..255 + private const int LITERALS=256; + + // number of Literal or Length codes, including the END_BLOCK code + private const int L_CODES=LITERALS+1+LENGTH_CODES; + + // number of distance codes + private const int D_CODES=30; + + // number of codes used to transfer the bit lengths + private const int BL_CODES=19; + + // maximum heap size + private const int HEAP_SIZE=2*L_CODES+1; + + // All codes must not exceed MAX_BITS bits + private const int MAX_BITS=15; + + // Stream status + private const int INIT_STATE=42; + private const int EXTRA_STATE=69; + private const int NAME_STATE=73; + private const int COMMENT_STATE=91; + private const int HCRC_STATE=103; + private const int BUSY_STATE=113; + private const int FINISH_STATE=666; + + // Data structure describing a single value and its code string. + struct ct_data + { + ushort freq; + public ushort Freq { get { return freq; } set { freq=value; } } // frequency count + public ushort Code { get { return freq; } set { freq=value; } } // bit string + + ushort dad; + public ushort Dad { get { return dad; } set { dad=value; } } // father node in Huffman tree + public ushort Len { get { return dad; } set { dad=value; } } // length of bit string + + public ct_data(ushort freq, ushort dad) + { + this.freq=freq; + this.dad=dad; + } + + public ct_data(ct_data data) + { + freq=data.freq; + dad=data.dad; + } + } + + struct tree_desc + { + public ct_data[] dyn_tree; // the dynamic tree + public int max_code; // largest code with non zero frequency + public static_tree_desc stat_desc; // the corresponding static tree + + public tree_desc(tree_desc desc) + { + dyn_tree=desc.dyn_tree; + max_code=desc.max_code; + stat_desc=desc.stat_desc; + } + } + + class deflate_state //internal_state + { + public z_stream strm; // pointer back to this zlib stream + public int status; // as the name implies + public byte[] pending_buf; // output still pending + public uint pending_buf_size; // size of pending_buf + + public int pending_out; // next pending byte to output to the stream + + public uint pending; // nb of bytes in the pending buffer + public int wrap; // bit 0 true for zlib, bit 1 true for gzip + public gz_header gzhead; // gzip header information to write + public uint gzindex; // where in extra, name, or comment + public byte method; // STORED (for zip only) or DEFLATED + public int last_flush; // value of flush param for previous deflate call + + // used by deflate.c: + public uint w_size; // LZ77 window size (32K by default) + public uint w_bits; // log2(w_size) (8..16) + public uint w_mask; // w_size - 1 + + // Sliding window. Input bytes are read into the second half of the window, + // and move to the first half later to keep a dictionary of at least wSize + // bytes. With this organization, matches are limited to a distance of + // wSize-MAX_MATCH bytes, but this ensures that IO is always + // performed with a length multiple of the block size. Also, it limits + // the window size to 64K, which is quite useful on MSDOS. + // To do: use the user input buffer as sliding window. + public byte[] window; + + // Actual size of window: 2*wSize, except when the user input buffer + // is directly used as sliding window. + public uint window_size; + + // Link to older string with same hash index. To limit the size of this + // array to 64K, this link is maintained only for the last 32K strings. + // An index in this array is thus a window index modulo 32K. + public ushort[] prev; + + public ushort[] head; // Heads of the hash chains or NIL. + + public uint ins_h; // hash index of string to be inserted + public uint hash_size; // number of elements in hash table + public uint hash_bits; // log2(hash_size) + public uint hash_mask; // hash_size-1 + + // Number of bits by which ins_h must be shifted at each input + // step. It must be such that after MIN_MATCH steps, the oldest + // byte no longer takes part in the hash key, that is: + // hash_shift * MIN_MATCH >= hash_bits + public uint hash_shift; + + // Window position at the beginning of the current output block. Gets + // negative when the window is moved backwards. + public int block_start; + + public uint match_length; // length of best match + public uint prev_match; // previous match + public int match_available; // set if previous match exists + public uint strstart; // start of string to insert + public uint match_start; // start of matching string + public uint lookahead; // number of valid bytes ahead in window + + // Length of the best match at previous step. Matches not greater than this + // are discarded. This is used in the lazy match evaluation. + public uint prev_length; + + // To speed up deflation, hash chains are never searched beyond this + // length. A higher limit improves compression ratio but degrades the speed. + public uint max_chain_length; + + // Attempt to find a better match only when the current match is strictly + // smaller than this value. This mechanism is used only for compression + // levels >= 4. + public uint max_lazy_match; + + // Insert new strings in the hash table only if the match length is not + // greater than this length. This saves time but degrades compression. + // max_insert_length is used only for compression levels <= 3. + //#define max_insert_length max_lazy_match + + public int level; // compression level (1..9) + public int strategy; // favor or force Huffman coding + + public uint good_match; // Use a faster search when the previous match is longer than this + + public int nice_match; // Stop searching when current match exceeds this + + // used by trees.c: + public ct_data[] dyn_ltree=new ct_data[HEAP_SIZE]; // literal and length tree + public ct_data[] dyn_dtree=new ct_data[2*D_CODES+1]; // distance tree + public ct_data[] bl_tree=new ct_data[2*BL_CODES+1]; // Huffman tree for bit lengths + + public tree_desc l_desc=new tree_desc(); // desc. for literal tree + public tree_desc d_desc=new tree_desc(); // desc. for distance tree + public tree_desc bl_desc=new tree_desc(); // desc. for bit length tree + + // number of codes at each bit length for an optimal tree + public ushort[] bl_count=new ushort[MAX_BITS+1]; + + // The sons of heap[n] are heap[2*n] and heap[2*n+1]. heap[0] is not used. + // The same heap array is used to build all trees. + public int[] heap=new int[2*L_CODES+1]; // heap used to build the Huffman trees + public int heap_len; // number of elements in the heap + public int heap_max; // element of largest frequency + + // Depth of each subtree used as tie breaker for trees of equal frequency + public byte[] depth=new byte[2*L_CODES+1]; + + public byte[] l_buf; // buffer for literals or lengths + + // Size of match buffer for literals/lengths. There are 4 reasons for + // limiting lit_bufsize to 64K: + // - frequencies can be kept in 16 bit counters + // - if compression is not successful for the first block, all input + // data is still in the window so we can still emit a stored block even + // when input comes from standard input. (This can also be done for + // all blocks if lit_bufsize is not greater than 32K.) + // - if compression is not successful for a file smaller than 64K, we can + // even emit a stored file instead of a stored block (saving 5 bytes). + // This is applicable only for zip (not zlib). + // - creating new Huffman trees less frequently may not provide fast + // adaptation to changes in the input data statistics. (Take for + // example a binary file with poorly compressible code followed by + // a highly compressible string table.) Smaller buffer sizes give + // fast adaptation but have of course the overhead of transmitting + // trees more frequently. + // - I can't count above 4 + public uint lit_bufsize; + + public uint last_lit; // running index in l_buf + + // Buffer for distances. To simplify the code, d_buf and l_buf have + // the same number of elements. To use different lengths, an extra flag + // array would be necessary. + public ushort[] d_buf; + + public uint opt_len; // bit length of current block with optimal trees + public uint static_len; // bit length of current block with static trees + public uint matches; // number of string matches in current block + public int last_eob_len; // bit length of EOB code for last block + + // Output buffer. bits are inserted starting at the bottom (least + // significant bits). + public ushort bi_buf; + + // Number of valid bits in bi_buf. All bits above the last valid bit + // are always zero. + public int bi_valid; + + // High water mark offset in window for initialized bytes -- bytes above + // this are set to zero in order to avoid memory check warnings when + // longest match routines access bytes past the input. This is then + // updated to the new high water mark. + public uint high_water; + + public deflate_state Clone() + { + deflate_state ret=(deflate_state)MemberwiseClone(); + + ret.dyn_ltree=new ct_data[HEAP_SIZE]; + for(int i=0; i>7)]) + + #endregion + + // If you use the zlib library in a product, an acknowledgment is welcome + // in the documentation of your product. If for some reason you cannot + // include such an acknowledgment, I would appreciate that you keep this + // copyright string in the executable of your product. + private const string deflate_copyright=" deflate 1.2.5 Copyright 1995-2010 Jean-loup Gailly "; + + // =========================================================================== + // Function prototypes. + + enum block_state + { + need_more, // block not completed, need more input or more output + block_done, // block flush performed + finish_started, // finish started, need only more output at next deflate + finish_done // finish done, accept no more input or output + } + + // Compression function. Returns the block state after the call. + delegate block_state compress_func(deflate_state s, int flush); + + // =========================================================================== + // Local data + + // Tail of hash chains + private const int NIL=0; + + // Matches of length 3 are discarded if their distance exceeds TOO_FAR + private const int TOO_FAR=4096; + + // Values for max_lazy_match, good_match and max_chain_length, depending on + // the desired pack level (0..9). The values given below have been tuned to + // exclude worst case performance for pathological files. Better values may be + // found for specific files. + struct config + { + public ushort good_length; // reduce lazy search above this match length + public ushort max_lazy; // do not perform lazy search above this match length + public ushort nice_length; // quit search above this match length + public ushort max_chain; + public compress_func func; + + public config(ushort good_length, ushort max_lazy, ushort nice_length, ushort max_chain, compress_func func) + { + this.good_length=good_length; + this.max_lazy=max_lazy; + this.nice_length=nice_length; + this.max_chain=max_chain; + this.func=func; + } + } + + static readonly config[] configuration_table=new config[] + { // good lazy nice chain + new config( 0, 0, 0, 0, deflate_stored), // store only + new config( 4, 4, 8, 4, deflate_fast), // max speed, no lazy matches + new config( 4, 5, 16, 8, deflate_fast), + new config( 4, 6, 32, 32, deflate_fast), + new config( 4, 4, 16, 16, deflate_slow), // lazy matches + new config( 8, 16, 32, 32, deflate_slow), + new config( 8, 16, 128, 128, deflate_slow), + new config( 8, 32, 128, 256, deflate_slow), + new config(32, 128, 258, 1024, deflate_slow), + new config(32, 258, 258, 4096, deflate_slow) // max compression + }; + + // Note: the deflate() code requires max_lazy >= MIN_MATCH and max_chain >= 4 + // For deflate_fast() (levels <= 3) good is ignored and lazy has a different + // meaning. + + // =========================================================================== + // Update a hash value with the given input byte + // IN assertion: all calls to to UPDATE_HASH are made with consecutive + // input characters, so that a running hash key can be computed from the + // previous key instead of complete recalculation each time. + //#define UPDATE_HASH(s,h,c) h = ((h<15) + { + wrap=2; // write gzip wrapper instead + windowBits-=16; + } + + if(memLevel<1||memLevel>MAX_MEM_LEVEL||method!=Z_DEFLATED||windowBits<8||windowBits>15||level<0||level>9|| + strategy<0||strategy>Z_FIXED) return Z_STREAM_ERROR; + + if(windowBits==8) windowBits=9; // until 256-byte window bug fixed + + deflate_state s; + try + { + s=new deflate_state(); + } + catch(Exception) + { + return Z_MEM_ERROR; + } + + strm.state=s; + s.strm=strm; + + s.wrap=wrap; + s.w_bits=(uint)windowBits; + s.w_size=1U<<(int)s.w_bits; + s.w_mask=s.w_size-1; + + s.hash_bits=(uint)memLevel+7; + s.hash_size=1U<<(int)s.hash_bits; + s.hash_mask=s.hash_size-1; + s.hash_shift=(s.hash_bits+MIN_MATCH-1)/MIN_MATCH; + + try + { + s.window=new byte[s.w_size*2]; + s.prev=new ushort[s.w_size]; + s.head=new ushort[s.hash_size]; + s.high_water=0; // nothing written to s->window yet + + s.lit_bufsize=1U<<(memLevel+6); // 16K elements by default + + s.pending_buf=new byte[s.lit_bufsize*4]; + s.pending_buf_size=s.lit_bufsize*4; + + s.d_buf=new ushort[s.lit_bufsize]; + s.l_buf=new byte[s.lit_bufsize]; + } + catch(Exception) + { + s.status=FINISH_STATE; + strm.msg=zError(Z_MEM_ERROR); + deflateEnd(strm); + return Z_MEM_ERROR; + } + + s.level=level; + s.strategy=strategy; + s.method=(byte)method; + + return deflateReset(strm); + } + + // ========================================================================= + // Initializes the compression dictionary from the given byte sequence + // without producing any compressed output. This function must be called + // immediately after deflateInit, deflateInit2 or deflateReset, before any + // call of deflate. The compressor and decompressor must use exactly the same + // dictionary (see inflateSetDictionary). + + // The dictionary should consist of strings (byte sequences) that are likely + // to be encountered later in the data to be compressed, with the most commonly + // used strings preferably put towards the end of the dictionary. Using a + // dictionary is most useful when the data to be compressed is short and can be + // predicted with good accuracy; the data can then be compressed better than + // with the default empty dictionary. + + // Depending on the size of the compression data structures selected by + // deflateInit or deflateInit2, a part of the dictionary may in effect be + // discarded, for example if the dictionary is larger than the window size in + // deflate or deflate2. Thus the strings most likely to be useful should be + // put at the end of the dictionary, not at the front. In addition, the + // current implementation of deflate will use at most the window size minus + // 262 bytes of the provided dictionary. + + // Upon return of this function, strm.adler is set to the adler32 value + // of the dictionary; the decompressor may later use this value to determine + // which dictionary has been used by the compressor. (The adler32 value + // applies to the whole dictionary even if only a subset of the dictionary is + // actually used by the compressor.) If a raw deflate was requested, then the + // adler32 value is not computed and strm.adler is not set. + + // deflateSetDictionary returns Z_OK if success, or Z_STREAM_ERROR if a + // parameter is invalid (such as NULL dictionary) or the stream state is + // inconsistent (for example if deflate has already been called for this stream + // or if the compression method is bsort). deflateSetDictionary does not + // perform any compression: this will be done by deflate(). + + public static int deflateSetDictionary(z_stream strm, byte[] dictionary, uint dictLength) + { + uint length=dictLength; + uint n; + uint hash_head=0; + + if(strm==null||strm.state==null||dictionary==null) return Z_STREAM_ERROR; + + deflate_state s=strm.state as deflate_state; + if(s==null||s.wrap==2||(s.wrap==1&&s.status!=INIT_STATE)) + return Z_STREAM_ERROR; + + if(s.wrap!=0) strm.adler=adler32(strm.adler, dictionary, dictLength); + + if(lengths.w_size) + { + length=s.w_size; + dictionary_ind=(int)(dictLength-length); // use the tail of the dictionary + } + + //was memcpy(s.window, dictionary+dictionary_ind, length); + Array.Copy(dictionary, dictionary_ind, s.window, 0, length); + + s.strstart=length; + s.block_start=(int)length; + + // Insert all strings in the hash table (except for the last two bytes). + // s.lookahead stays null, so s.ins_h will be recomputed at the next + // call of fill_window. + s.ins_h=s.window[0]; + + //was UPDATE_HASH(s, s.ins_h, s.window[1]); + s.ins_h=((s.ins_h<<(int)s.hash_shift)^s.window[1])&s.hash_mask; + + for(n=0; n<=length-MIN_MATCH; n++) + { + //was INSERT_STRING(s, n, hash_head); + s.ins_h=((s.ins_h<<(int)s.hash_shift)^s.window[n+(MIN_MATCH-1)])&s.hash_mask; + hash_head=s.prev[n&s.w_mask]=s.head[s.ins_h]; + s.head[s.ins_h]=(ushort)n; + } + if(hash_head!=0) hash_head=0; // to make compiler happy + return Z_OK; + } + + // ========================================================================= + // This function is equivalent to deflateEnd followed by deflateInit, + // but does not free and reallocate all the internal compression state. + // The stream will keep the same compression level and any other attributes + // that may have been set by deflateInit2. + + // deflateReset returns Z_OK if success, or Z_STREAM_ERROR if the source + // stream state was inconsistent (such as zalloc or state being NULL). + public static int deflateReset(z_stream strm) + { + if(strm==null||strm.state==null) return Z_STREAM_ERROR; + + strm.total_in=strm.total_out=0; + strm.msg=null; + + deflate_state s=(deflate_state)strm.state; + s.pending=0; + s.pending_out=0; + + if(s.wrap<0) s.wrap=-s.wrap; // was made negative by deflate(..., Z_FINISH); + + s.status=s.wrap!=0?INIT_STATE:BUSY_STATE; + strm.adler=s.wrap==2?crc32(0, null, 0):adler32(0, null, 0); + s.last_flush=Z_NO_FLUSH; + + _tr_init(s); + lm_init(s); + + return Z_OK; + } + + // ========================================================================= + // deflateSetHeader() provides gzip header information for when a gzip + // stream is requested by deflateInit2(). deflateSetHeader() may be called + // after deflateInit2() or deflateReset() and before the first call of + // deflate(). The text, time, os, extra field, name, and comment information + // in the provided gz_header structure are written to the gzip header (xflag is + // ignored -- the extra flags are set according to the compression level). The + // caller must assure that, if not Z_NULL, name and comment are terminated with + // a zero byte, and that if extra is not Z_NULL, that extra_len bytes are + // available there. If hcrc is true, a gzip header crc is included. Note that + // the current versions of the command-line version of gzip (up through version + // 1.3.x) do not support header crc's, and will report that it is a "multi-part + // gzip file" and give up. + + // If deflateSetHeader is not used, the default gzip header has text false, + // the time set to zero, and os set to 255, with no extra, name, or comment + // fields. The gzip header is returned to the default state by deflateReset(). + + // deflateSetHeader returns Z_OK if success, or Z_STREAM_ERROR if the source + // stream state was inconsistent. + + public static int deflateSetHeader(z_stream strm, gz_header head) + { + if(strm==null||strm.state==null) return Z_STREAM_ERROR; + deflate_state s=(deflate_state)strm.state; + if(s.wrap!=2) return Z_STREAM_ERROR; + s.gzhead=head; + return Z_OK; + } + + // ========================================================================= + // deflatePrime() inserts bits in the deflate output stream. The intent + // is that this function is used to start off the deflate output with the + // bits leftover from a previous deflate stream when appending to it. As such, + // this function can only be used for raw deflate, and must be used before the + // first deflate() call after a deflateInit2() or deflateReset(). bits must be + // less than or equal to 16, and that many of the least significant bits of + // value will be inserted in the output. + + // deflatePrime returns Z_OK if success, or Z_STREAM_ERROR if the source + // stream state was inconsistent. + + public static int deflatePrime(z_stream strm, int bits, int value) + { + if(strm==null||strm.state==null) return Z_STREAM_ERROR; + deflate_state s=(deflate_state)strm.state; + s.bi_valid=bits; + s.bi_buf=(ushort)(value&((1<9||strategy<0||strategy>Z_FIXED) return Z_STREAM_ERROR; + + compress_func func=configuration_table[s.level].func; + int err=Z_OK; + + if((strategy!=s.strategy||func!=configuration_table[level].func)&&strm.total_in!=0) // Flush the last buffer: + err=deflate(strm, Z_BLOCK); + + if(s.level!=level) + { + s.level=level; + s.max_lazy_match=configuration_table[level].max_lazy; + s.good_match=configuration_table[level].good_length; + s.nice_match=configuration_table[level].nice_length; + s.max_chain_length=configuration_table[level].max_chain; + } + + s.strategy=strategy; + return err; + } + + // ========================================================================= + // Fine tune deflate's internal compression parameters. This should only be + // used by someone who understands the algorithm used by zlib's deflate for + // searching for the best matching string, and even then only by the most + // fanatic optimizer trying to squeeze out the last compressed bit for their + // specific input data. Read the deflate.cs source code for the meaning of the + // max_lazy, good_length, nice_length, and max_chain parameters. + + // deflateTune() can be called after deflateInit() or deflateInit2(), and + // returns Z_OK on success, or Z_STREAM_ERROR for an invalid deflate stream. + + public static int deflateTune(z_stream strm, uint good_length, uint max_lazy, int nice_length, uint max_chain) + { + if(strm==null||strm.state==null) return Z_STREAM_ERROR; + deflate_state s=(deflate_state)strm.state; + s.good_match=good_length; + s.max_lazy_match=max_lazy; + s.nice_match=nice_length; + s.max_chain_length=max_chain; + return Z_OK; + } + + // ========================================================================= + // For the default windowBits of 15 and memLevel of 8, this function returns + // a close to exact, as well as small, upper bound on the compressed size. + // They are coded as constants here for a reason--if the #define's are + // changed, then this function needs to be changed as well. The return + // value for 15 and 8 only works for those exact settings. + // + // For any setting other than those defaults for windowBits and memLevel, + // the value returned is a conservative worst case for the maximum expansion + // resulting from using fixed blocks instead of stored blocks, which deflate + // can emit on compressed data for some combinations of the parameters. + // + // This function could be more sophisticated to provide closer upper bounds for + // every combination of windowBits and memLevel. But even the conservative + // upper bound of about 14% expansion does not seem onerous for output buffer + // allocation. + + // deflateBound() returns an upper bound on the compressed size after + // deflation of sourceLen bytes. It must be called after deflateInit() + // or deflateInit2(). This would be used to allocate an output buffer + // for deflation in a single pass, and so would be called before deflate(). + + public static uint deflateBound(z_stream strm, uint sourceLen) + { + // conservative upper bound for compressed data + uint complen=sourceLen+((sourceLen+7)>>3)+((sourceLen+63)>>6)+5; + + // if can't get parameters, return conservative bound plus zlib wrapper + if(strm==null||strm.state==null) return complen+6; + + // compute wrapper length + deflate_state s=(deflate_state)strm.state; + uint wraplen; + byte[] str; + switch(s.wrap) + { + case 0: // raw deflate + wraplen=0; + break; + case 1: // zlib wrapper + wraplen=(uint)(6+(s.strstart!=0?4:0)); + break; + case 2: // gzip wrapper + wraplen=18; + if(s.gzhead!=null) // user-supplied gzip header + { + if(s.gzhead.extra!=null) wraplen+=2+s.gzhead.extra_len; + str=s.gzhead.name; + int str_ind=0; + if(str!=null) + { + do + { + wraplen++; + } while(str[str_ind++]!=0); + } + str=s.gzhead.comment; + if(str!=null) + { + do + { + wraplen++; + } while(str[str_ind++]!=0); + } + if(s.gzhead.hcrc!=0) wraplen+=2; + } + break; + default: wraplen=6; break; // for compiler happiness + } + + // if not default parameters, return conservative bound + if(s.w_bits!=15||s.hash_bits!=8+7) return complen+wraplen; + + // default settings: return tight bound for that case + return sourceLen+(sourceLen>>12)+(sourceLen>>14)+(sourceLen>>25)+13-6+wraplen; + } + + // ========================================================================= + // Put a short in the pending buffer. The 16-bit value is put in MSB order. + // IN assertion: the stream state is correct and there is enough room in + // pending_buf. + static void putShortMSB(deflate_state s, uint b) + { + //was put_byte(s, (byte)(b >> 8)); + s.pending_buf[s.pending++]=(byte)(b >> 8); + //was put_byte(s, (byte)(b & 0xff)); + s.pending_buf[s.pending++]=(byte)(b & 0xff); + } + + // ========================================================================= + // Flush as much pending output as possible. All deflate() output goes + // through this function so some applications may wish to modify it + // to avoid allocating a large strm.next_out buffer and copying into it. + // (See also read_buf()). + static void flush_pending(z_stream strm) + { + deflate_state s=(deflate_state)strm.state; + uint len=s.pending; + + if(len>strm.avail_out) len=strm.avail_out; + if(len==0) return; + + //was memcpy(strm.next_out, s.pending_out, len); + Array.Copy(s.pending_buf, s.pending_out, strm.out_buf, strm.next_out, len); + + strm.next_out+=(int)len; + s.pending_out+=(int)len; + strm.total_out+=len; + strm.avail_out-=len; + s.pending-=len; + if(s.pending==0) s.pending_out=0; + } + + const int PRESET_DICT=0x20; // preset dictionary flag in zlib header + + #region deflate + // ========================================================================= + // deflate compresses as much data as possible, and stops when the input + // buffer becomes empty or the output buffer becomes full. It may introduce some + // output latency (reading input without producing any output) except when + // forced to flush. + + // The detailed semantics are as follows. deflate performs one or both of the + // following actions: + + // - Compress more input starting at next_in and update next_in and avail_in + // accordingly. If not all input can be processed (because there is not + // enough room in the output buffer), next_in and avail_in are updated and + // processing will resume at this point for the next call of deflate(). + + // - Provide more output starting at next_out and update next_out and avail_out + // accordingly. This action is forced if the parameter flush is non zero. + // Forcing flush frequently degrades the compression ratio, so this parameter + // should be set only when necessary (in interactive applications). + // Some output may be provided even if flush is not set. + + // Before the call of deflate(), the application should ensure that at least + // one of the actions is possible, by providing more input and/or consuming + // more output, and updating avail_in or avail_out accordingly; avail_out + // should never be zero before the call. The application can consume the + // compressed output when it wants, for example when the output buffer is full + // (avail_out == 0), or after each call of deflate(). If deflate returns Z_OK + // and with zero avail_out, it must be called again after making room in the + // output buffer because there might be more output pending. + + // Normally the parameter flush is set to Z_NO_FLUSH, which allows deflate to + // decide how much data to accumualte before producing output, in order to + // maximize compression. + + // If the parameter flush is set to Z_SYNC_FLUSH, all pending output is + // flushed to the output buffer and the output is aligned on a byte boundary, so + // that the decompressor can get all input data available so far. (In particular + // avail_in is zero after the call if enough output space has been provided + // before the call.) Flushing may degrade compression for some compression + // algorithms and so it should be used only when necessary. + + // If flush is set to Z_FULL_FLUSH, all output is flushed as with + // Z_SYNC_FLUSH, and the compression state is reset so that decompression can + // restart from this point if previous compressed data has been damaged or if + // random access is desired. Using Z_FULL_FLUSH too often can seriously degrade + // compression. + + // If deflate returns with avail_out == 0, this function must be called again + // with the same value of the flush parameter and more output space (updated + // avail_out), until the flush is complete (deflate returns with non-zero + // avail_out). In the case of a Z_FULL_FLUSH or Z_SYNC_FLUSH, make sure that + // avail_out is greater than six to avoid repeated flush markers due to + // avail_out == 0 on return. + + // If the parameter flush is set to Z_FINISH, pending input is processed, + // pending output is flushed and deflate returns with Z_STREAM_END if there + // was enough output space; if deflate returns with Z_OK, this function must be + // called again with Z_FINISH and more output space (updated avail_out) but no + // more input data, until it returns with Z_STREAM_END or an error. After + // deflate has returned Z_STREAM_END, the only possible operations on the + // stream are deflateReset or deflateEnd. + + // Z_FINISH can be used immediately after deflateInit if all the compression + // is to be done in a single step. In this case, avail_out must be at least + // the value returned by deflateBound (see below). If deflate does not return + // Z_STREAM_END, then it must be called again as described above. + + // deflate() sets strm.adler to the adler32 checksum of all input read + // so far (that is, total_in bytes). + + // deflate() returns Z_OK if some progress has been made (more input + // processed or more output produced), Z_STREAM_END if all input has been + // consumed and all output has been produced (only when flush is set to + // Z_FINISH), Z_STREAM_ERROR if the stream state was inconsistent (for example + // if next_in or next_out was NULL), Z_BUF_ERROR if no progress is possible + // (for example avail_in or avail_out was zero). Note that Z_BUF_ERROR is not + // fatal, and deflate() can be called again with more input and more output + // space to continue compressing. + + public static int deflate(z_stream strm, int flush) + { + int old_flush; // value of flush param for previous deflate call + + if(strm==null||strm.state==null||flush>Z_BLOCK||flush<0) return Z_STREAM_ERROR; + deflate_state s=(deflate_state)strm.state; + + if(strm.out_buf==null||(strm.in_buf==null&&strm.avail_in!=0)||(s.status==FINISH_STATE&&flush!=Z_FINISH)) + { + strm.msg=zError(Z_STREAM_ERROR); + return Z_STREAM_ERROR; + } + + if(strm.avail_out==0) + { + strm.msg=zError(Z_BUF_ERROR); + return Z_BUF_ERROR; + } + + s.strm=strm; // just in case + old_flush=s.last_flush; + s.last_flush=flush; + + // Write the header + if(s.status==INIT_STATE) + { + if(s.wrap==2) + { + strm.adler=crc32(0, null, 0); + s.pending_buf[s.pending++]=31; + s.pending_buf[s.pending++]=139; + s.pending_buf[s.pending++]=8; + if(s.gzhead==null) + { + s.pending_buf[s.pending++]=0; + + s.pending_buf[s.pending++]=0; + s.pending_buf[s.pending++]=0; + s.pending_buf[s.pending++]=0; + s.pending_buf[s.pending++]=0; + + s.pending_buf[s.pending++]=(byte)(s.level==9?2:(s.strategy>=Z_HUFFMAN_ONLY||s.level<2?4:0)); + s.pending_buf[s.pending++]=OS_CODE; + s.status=BUSY_STATE; + } + else + { + s.pending_buf[s.pending++]=(byte)((s.gzhead.text!=0?1:0)+(s.gzhead.hcrc!=0?2:0)+(s.gzhead.extra==null?0:4)+ + (s.gzhead.name==null?0:8)+(s.gzhead.comment==null?0:16)); + s.pending_buf[s.pending++]=(byte)(s.gzhead.time&0xff); + s.pending_buf[s.pending++]=(byte)((s.gzhead.time>>8)&0xff); + s.pending_buf[s.pending++]=(byte)((s.gzhead.time>>16)&0xff); + s.pending_buf[s.pending++]=(byte)((s.gzhead.time>>24)&0xff); + s.pending_buf[s.pending++]=(byte)(s.level==9?2:(s.strategy>=Z_HUFFMAN_ONLY||s.level<2?4:0)); + s.pending_buf[s.pending++]=(byte)(s.gzhead.os&0xff); + if(s.gzhead.extra!=null) + { + s.pending_buf[s.pending++]=(byte)(s.gzhead.extra_len&0xff); + s.pending_buf[s.pending++]=(byte)((s.gzhead.extra_len>>8)&0xff); + } + if(s.gzhead.hcrc!=0) strm.adler=crc32(strm.adler, s.pending_buf, s.pending); + s.gzindex=0; + s.status=EXTRA_STATE; + } + } + else + { + uint header=(Z_DEFLATED+((s.w_bits-8)<<4))<<8; + uint level_flags; + + if(s.strategy>=Z_HUFFMAN_ONLY||s.level<2) level_flags=0; + else if(s.level<6) level_flags=1; + else if(s.level==6) level_flags=2; + else level_flags=3; + + header|=(level_flags<<6); + if(s.strstart!=0) header|=PRESET_DICT; + header+=31-(header%31); + + s.status=BUSY_STATE; + putShortMSB(s, header); + + // Save the adler32 of the preset dictionary: + if(s.strstart!=0) + { + putShortMSB(s, (uint)(strm.adler>>16)); + putShortMSB(s, (uint)(strm.adler&0xffff)); + } + strm.adler=adler32(0, null, 0); + } + } + if(s.status==EXTRA_STATE) + { + if(s.gzhead.extra!=null) + { + uint beg=s.pending; // start of bytes to update crc + + while(s.gzindex<(s.gzhead.extra_len&0xffff)) + { + if(s.pending==s.pending_buf_size) + { + if(s.gzhead.hcrc!=0&&s.pending>beg) strm.adler=crc32(strm.adler, s.pending_buf, beg, s.pending-beg); + flush_pending(strm); + beg=s.pending; + if(s.pending==s.pending_buf_size) break; + } + s.pending_buf[s.pending++]=s.gzhead.extra[s.gzindex]; + s.gzindex++; + } + if(s.gzhead.hcrc!=0&&s.pending>beg) strm.adler=crc32(strm.adler, s.pending_buf, beg, s.pending-beg); + if(s.gzindex==s.gzhead.extra_len) + { + s.gzindex=0; + s.status=NAME_STATE; + } + } + else s.status=NAME_STATE; + } + if(s.status==NAME_STATE) + { + if(s.gzhead.name!=null) + { + uint beg=s.pending; // start of bytes to update crc + byte val; + + do + { + if(s.pending==s.pending_buf_size) + { + if(s.gzhead.hcrc!=0&&s.pending>beg) strm.adler=crc32(strm.adler, s.pending_buf, beg, s.pending-beg); + flush_pending(strm); + beg=s.pending; + if(s.pending==s.pending_buf_size) + { + val=1; + break; + } + } + val=s.gzhead.name[s.gzindex++]; + s.pending_buf[s.pending++]=val; + } while(val!=0); + if(s.gzhead.hcrc!=0&&s.pending>beg) strm.adler=crc32(strm.adler, s.pending_buf, beg, s.pending-beg); + if(val==0) + { + s.gzindex=0; + s.status=COMMENT_STATE; + } + } + else s.status=COMMENT_STATE; + } + if(s.status==COMMENT_STATE) + { + if(s.gzhead.comment!=null) + { + uint beg=s.pending; // start of bytes to update crc + byte val; + + do + { + if(s.pending==s.pending_buf_size) + { + if(s.gzhead.hcrc!=0&&s.pending>beg) strm.adler=crc32(strm.adler, s.pending_buf, beg, s.pending-beg); + flush_pending(strm); + beg=s.pending; + if(s.pending==s.pending_buf_size) + { + val=1; + break; + } + } + val=s.gzhead.comment[s.gzindex++]; + s.pending_buf[s.pending++]=val; + } while(val!=0); + if(s.gzhead.hcrc!=0&&s.pending>beg) strm.adler=crc32(strm.adler, s.pending_buf, beg, s.pending-beg); + if(val==0) s.status=HCRC_STATE; + } + else s.status=HCRC_STATE; + } + if(s.status==HCRC_STATE) + { + if(s.gzhead.hcrc!=0) + { + if(s.pending+2>s.pending_buf_size) flush_pending(strm); + if(s.pending+2<=s.pending_buf_size) + { + s.pending_buf[s.pending++]=(byte)(strm.adler&0xff); + s.pending_buf[s.pending++]=(byte)((strm.adler>>8)&0xff); + strm.adler=crc32(0, null, 0); + s.status=BUSY_STATE; + } + } + else s.status=BUSY_STATE; + } + + // Flush as much pending output as possible + if(s.pending!=0) + { + flush_pending(strm); + if(strm.avail_out==0) + { + // Since avail_out is 0, deflate will be called again with + // more output space, but possibly with both pending and + // avail_in equal to zero. There won't be anything to do, + // but this is not an error situation so make sure we + // return OK instead of BUF_ERROR at next call of deflate: + s.last_flush=-1; + return Z_OK; + } + + // Make sure there is something to do and avoid duplicate consecutive + // flushes. For repeated and useless calls with Z_FINISH, we keep + // returning Z_STREAM_END instead of Z_BUF_ERROR. + } + else if(strm.avail_in==0&&flush<=old_flush&&flush!=Z_FINISH) + { + strm.msg=zError(Z_BUF_ERROR); + return Z_BUF_ERROR; + } + + // User must not provide more input after the first FINISH: + if(s.status==FINISH_STATE&&strm.avail_in!=0) + { + strm.msg=zError(Z_BUF_ERROR); + return Z_BUF_ERROR; + } + + // Start a new block or continue the current one. + if(strm.avail_in!=0||s.lookahead!=0||(flush!=Z_NO_FLUSH&&s.status!=FINISH_STATE)) + { + block_state bstate=s.strategy==Z_HUFFMAN_ONLY?deflate_huff(s, flush):(s.strategy==Z_RLE?deflate_rle(s, flush):configuration_table[s.level].func(s, flush)); + + if(bstate==block_state.finish_started||bstate==block_state.finish_done) s.status=FINISH_STATE; + if(bstate==block_state.need_more||bstate==block_state.finish_started) + { + if(strm.avail_out==0) s.last_flush=-1; // avoid BUF_ERROR next call, see above + return Z_OK; + // If flush != Z_NO_FLUSH && avail_out == 0, the next call + // of deflate should use the same flush parameter to make sure + // that the flush is complete. So we don't have to output an + // empty block here, this will be done at next call. This also + // ensures that for a very small output buffer, we emit at most + // one empty block. + } + if(bstate==block_state.block_done) + { + if(flush==Z_PARTIAL_FLUSH) _tr_align(s); + else if(flush!=Z_BLOCK) + { // FULL_FLUSH or SYNC_FLUSH + _tr_stored_block(s, null, 0, 0); + // For a full flush, this empty block will be recognized + // as a special marker by inflate_sync(). + if(flush==Z_FULL_FLUSH) + { + s.head[s.hash_size-1]=NIL; // forget history + + //was memset((byte*)s.head, 0, (uint)(s.hash_size-1)*sizeof(*s.head)); + for(int i=0; i0, "bug2"); + + if(flush!=Z_FINISH) return Z_OK; + if(s.wrap<=0) return Z_STREAM_END; + + // Write the trailer + if(s.wrap==2) + { + s.pending_buf[s.pending++]=(byte)(strm.adler&0xff); + s.pending_buf[s.pending++]=(byte)((strm.adler>>8)&0xff); + s.pending_buf[s.pending++]=(byte)((strm.adler>>16)&0xff); + s.pending_buf[s.pending++]=(byte)((strm.adler>>24)&0xff); + s.pending_buf[s.pending++]=(byte)(strm.total_in&0xff); + s.pending_buf[s.pending++]=(byte)((strm.total_in>>8)&0xff); + s.pending_buf[s.pending++]=(byte)((strm.total_in>>16)&0xff); + s.pending_buf[s.pending++]=(byte)((strm.total_in>>24)&0xff); + } + else + { + putShortMSB(s, (uint)(strm.adler>>16)); + putShortMSB(s, (uint)(strm.adler&0xffff)); + } + + flush_pending(strm); + // If avail_out is zero, the application will call deflate again + // to flush the rest. + if(s.wrap>0) s.wrap=-s.wrap; // write the trailer only once! + return s.pending!=0?Z_OK:Z_STREAM_END; + } + #endregion + + // ========================================================================= + // All dynamically allocated data structures for this stream are freed. + // This function discards any unprocessed input and does not flush any + // pending output. + + // deflateEnd returns Z_OK if success, Z_STREAM_ERROR if the + // stream state was inconsistent, Z_DATA_ERROR if the stream was freed + // prematurely (some input or output was discarded). In the error case, + // msg may be set but then points to a static string (which must not be + // deallocated). + + public static int deflateEnd(z_stream strm) + { + if(strm==null||strm.state==null) return Z_STREAM_ERROR; + + deflate_state s=(deflate_state)strm.state; + int status=s.status; + if(status!=INIT_STATE&& status!=EXTRA_STATE&& status!=NAME_STATE&& status!=COMMENT_STATE&& + status!=HCRC_STATE&& status!=BUSY_STATE&&status!=FINISH_STATE) return Z_STREAM_ERROR; + + // Deallocate in reverse order of allocations: + //if(s.pending_buf!=null) free(s.pending_buf); + //if(s.l_buf!=null) free(s.l_buf); + //if(s.d_buf!=null) free(s.d_buf); + //if(s.head!=null) free(s.head); + //if(s.prev!=null) free(s.prev); + //if(s.window!=null) free(s.window); + s.pending_buf=s.l_buf=s.window=null; + s.d_buf=s.head=s.prev=null; + + //free(strm.state); + strm.state=s=null; + + return status==BUSY_STATE?Z_DATA_ERROR:Z_OK; + } + + // ========================================================================= + // Sets the destination stream as a complete copy of the source stream. + + // This function can be useful when several compression strategies will be + // tried, for example when there are several ways of pre-processing the input + // data with a filter. The streams that will be discarded should then be freed + // by calling deflateEnd. Note that deflateCopy duplicates the internal + // compression state which can be quite large, so this strategy is slow and + // can consume lots of memory. + + // deflateCopy returns Z_OK if success, Z_MEM_ERROR if there was not + // enough memory, Z_STREAM_ERROR if the source stream state was inconsistent + // (such as zalloc being NULL). msg is left unchanged in both source and + // destination. + + // Copy the source state to the destination state. + public static int deflateCopy(z_stream dest, z_stream source) + { + if(source==null||dest==null||source.state==null) return Z_STREAM_ERROR; + + deflate_state ss=(deflate_state)source.state; + + //was memcpy(dest, source, sizeof(z_stream)); + source.CopyTo(dest); + + deflate_state ds; + try + { + ds=ss.Clone(); + } + catch(Exception) + { + return Z_MEM_ERROR; + } + dest.state=ds; + //(done above) memcpy(ds, ss, sizeof(deflate_state)); + ds.strm=dest; + + try + { + ds.window=new byte[ds.w_size*2]; + ds.prev=new ushort[ds.w_size]; + ds.head=new ushort[ds.hash_size]; + ds.pending_buf=new byte[ds.lit_bufsize*4]; + ds.d_buf=new ushort[ds.lit_bufsize]; + ds.l_buf=new byte[ds.lit_bufsize]; + } + catch(Exception) + { + deflateEnd(dest); + return Z_MEM_ERROR; + } + + //was memcpy(ds.window, ss.window, ds.w_size*2*sizeof(byte)); + ss.window.CopyTo(ds.window, 0); + + //was memcpy(ds.prev, ss.prev, ds.w_size*sizeof(ushort)); + ss.prev.CopyTo(ds.prev, 0); + + //was memcpy(ds.head, ss.head, ds.hash_size*sizeof(ushort)); + ss.head.CopyTo(ds.head, 0); + + //was memcpy(ds.pending_buf, ss.pending_buf, (uint)ds.pending_buf_size); + ss.pending_buf.CopyTo(ds.pending_buf, 0); + ss.d_buf.CopyTo(ds.d_buf, 0); + ss.l_buf.CopyTo(ds.l_buf, 0); + + ds.l_desc.dyn_tree=ds.dyn_ltree; + ds.d_desc.dyn_tree=ds.dyn_dtree; + ds.bl_desc.dyn_tree=ds.bl_tree; + + return Z_OK; + } + + // =========================================================================== + // Read a new buffer from the current input stream, update the adler32 + // and total number of bytes read. All deflate() input goes through + // this function so some applications may wish to modify it to avoid + // allocating a large strm.next_in buffer and copying from it. + // (See also flush_pending()). + static int read_buf(z_stream strm, byte[] buf, uint size) + { + return read_buf(strm, buf, 0, size); + } + + static int read_buf(z_stream strm, byte[] buf, int buf_ind, uint size) + { + uint len=strm.avail_in; + + if(len>size) len=size; + if(len==0) return 0; + + strm.avail_in-=len; + + deflate_state s=(deflate_state)strm.state; + + if(s.wrap==1) strm.adler=adler32(strm.adler, strm.in_buf, (uint)strm.next_in, len); + else if(s.wrap==2) strm.adler=crc32(strm.adler, strm.in_buf, strm.next_in, len); + + //was memcpy(buf, strm.in_buf+strm.next_in, len); + Array.Copy(strm.in_buf, strm.next_in, buf, buf_ind, len); + strm.next_in+=len; + strm.total_in+=len; + + return (int)len; + } + + // =========================================================================== + // Initialize the "longest match" routines for a new zlib stream + static void lm_init(deflate_state s) + { + s.window_size=(uint)2*s.w_size; + + s.head[s.hash_size-1]=NIL; + + //was memset((byte*)s.head, 0, (uint)(s.hash_size-1)*sizeof(*s.head)); + for(int i=0; i<(s.hash_size-1); i++) s.head[i]=0; + + // Set the default configuration parameters: + s.max_lazy_match=configuration_table[s.level].max_lazy; + s.good_match=configuration_table[s.level].good_length; + s.nice_match=configuration_table[s.level].nice_length; + s.max_chain_length=configuration_table[s.level].max_chain; + + s.strstart=0; + s.block_start=0; + s.lookahead=0; + s.match_length=s.prev_length=MIN_MATCH-1; + s.match_available=0; + s.ins_h=0; + } + + // =========================================================================== + // Set match_start to the longest match starting at the given string and + // return its length. Matches shorter or equal to prev_length are discarded, + // in which case the result is equal to prev_length and match_start is + // garbage. + // IN assertions: cur_match is the head of the hash chain for the current + // string (strstart) and its distance is <= MAX_DIST, and prev_length >= 1 + // OUT assertion: the match length is not greater than s.lookahead. + static uint longest_match(deflate_state s, uint cur_match) + { + uint chain_length=s.max_chain_length; // max hash chain length + byte[] scan=s.window; // current string + int scan_ind=(int)s.strstart; + int len; // length of current match + int best_len=(int)s.prev_length; // best match length so far + int nice_match=s.nice_match; // stop if match long enough + uint limit=s.strstart>(uint)(s.w_size-MIN_LOOKAHEAD)?s.strstart-(uint)(s.w_size-MIN_LOOKAHEAD):NIL; + // Stop when cur_match becomes <= limit. To simplify the code, + // we prevent matches with the string of window index 0. + ushort[] prev=s.prev; + uint wmask=s.w_mask; + + int strend_ind=(int)s.strstart+MAX_MATCH; + byte scan_end1=scan[scan_ind+best_len-1]; + byte scan_end=scan[scan_ind+best_len]; + + // The code is optimized for HASH_BITS >= 8 and MAX_MATCH-2 multiple of 16. + // It is easy to get rid of this optimization if necessary. + //Assert(s.hash_bits >= 8 && MAX_MATCH == 258, "Code too clever"); + + // Do not waste too much time if we already have a good match: + if(s.prev_length>=s.good_match) chain_length>>=2; + + // Do not look for matches beyond the end of the input. This is necessary + // to make deflate deterministic. + if((uint)nice_match>s.lookahead) nice_match=(int)s.lookahead; + + //Assert((uint)s.strstart <= s.window_size-MIN_LOOKAHEAD, "need lookahead"); + + byte[] match=s.window; + do + { + //Assert(cur_match= 8. + scan_ind+=2; + match_ind++; + //Assert(scan[scan_ind]==match[match_ind], "match[2]?"); + + // We check for insufficient lookahead only every 8th comparison; + // the 256th check will be made at strstart+258. + do + { + } while(scan[++scan_ind]==match[++match_ind]&&scan[++scan_ind]==match[++match_ind]&& + scan[++scan_ind]==match[++match_ind]&&scan[++scan_ind]==match[++match_ind]&& + scan[++scan_ind]==match[++match_ind]&&scan[++scan_ind]==match[++match_ind]&& + scan[++scan_ind]==match[++match_ind]&&scan[++scan_ind]==match[++match_ind]&& + scan_indbest_len) + { + s.match_start=cur_match; + best_len=len; + if(len>=nice_match) break; + + scan_end1=scan[scan_ind+best_len-1]; + scan_end=scan[scan_ind+best_len]; + } + } while((cur_match=prev[cur_match&wmask])>limit&&--chain_length!=0); + + if((uint)best_len<=s.lookahead) return (uint)best_len; + return s.lookahead; + } + + // --------------------------------------------------------------------------- + // Optimized version for FASTEST only + static uint longest_match_fast(deflate_state s, uint cur_match) + { + byte[] scan=s.window; + int scan_ind=(int)s.strstart; // current string + int len; // length of current match + int strend_ind=(int)s.strstart+MAX_MATCH; + + // The code is optimized for HASH_BITS >= 8 and MAX_MATCH-2 multiple of 16. + // It is easy to get rid of this optimization if necessary. + //Assert(s.hash_bits >= 8 && MAX_MATCH == 258, "Code too clever"); + + //Assert((uint)s.strstart <= s.window_size-MIN_LOOKAHEAD, "need lookahead"); + + //Assert(cur_match < s.strstart, "no future"); + + byte[] match=s.window; + int match_ind=(int)cur_match; + + // Return failure if the match length is less than 2: + if(match[match_ind]!=scan[scan_ind]||match[match_ind+1]!=scan[scan_ind+1]) return MIN_MATCH-1; + + // The check at best_len-1 can be removed because it will be made + // again later. (This heuristic is not always a win.) + // It is not necessary to compare scan[2] and match[2] since they + // are always equal when the other bytes match, given that + // the hash keys are equal and that HASH_BITS >= 8. + scan_ind+=2; + match_ind+=2; + //Assert(scan[scan_ind] == match[match_ind], "match[2]?"); + + // We check for insufficient lookahead only every 8th comparison; + // the 256th check will be made at strstart+258. + do + { + } while(scan[++scan_ind]==match[++match_ind]&&scan[++scan_ind]==match[++match_ind]&& + scan[++scan_ind]==match[++match_ind]&&scan[++scan_ind]==match[++match_ind]&& + scan[++scan_ind]==match[++match_ind]&&scan[++scan_ind]==match[++match_ind]&& + scan[++scan_ind]==match[++match_ind]&&scan[++scan_ind]==match[++match_ind]&& + scan_ind=wsize+s.w_size-MIN_LOOKAHEAD) + { + //was memcpy(s.window, s.window+wsize, (uint)wsize); + Array.Copy(s.window, wsize, s.window, 0, wsize); + + s.match_start-=wsize; + s.strstart-=wsize; // we now have strstart >= MAX_DIST + s.block_start-=(int)wsize; + + // Slide the hash table (could be avoided with 32 bit values + // at the expense of memory usage). We slide even when level == 0 + // to keep the hash table consistent if we switch back to level > 0 + // later. (Using level 0 permanently is not an optimal usage of + // zlib, so we don't care about this pathological case.) + n=s.hash_size; + uint p=n; + do + { + m=s.head[--p]; + s.head[p]=(ushort)(m>=wsize?m-wsize:NIL); + } while((--n)!=0); + + n=wsize; + p=n; + do + { + m=s.prev[--p]; + s.prev[p]=(ushort)(m>=wsize?m-wsize:NIL); + // If n is not on any hash chain, prev[n] is garbage but + // its value will never be used. + } while((--n)!=0); + more+=wsize; + } + if(s.strm.avail_in==0) return; + + // If there was no sliding: + // strstart <= WSIZE+MAX_DIST-1 && lookahead <= MIN_LOOKAHEAD - 1 && + // more == window_size - lookahead - strstart + // => more >= window_size - (MIN_LOOKAHEAD-1 + WSIZE + MAX_DIST-1) + // => more >= window_size - 2*WSIZE + 2 + // In the BIG_MEM or MMAP case (not yet supported), + // window_size == input_size + MIN_LOOKAHEAD && + // strstart + s.lookahead <= input_size => more >= MIN_LOOKAHEAD. + // Otherwise, window_size == 2*WSIZE so more >= 2. + // If there was sliding, more >= WSIZE. So in all cases, more >= 2. + //Assert(more>=2, "more < 2"); + + n=(uint)read_buf(s.strm, s.window, (int)(s.strstart+s.lookahead), more); + s.lookahead+=n; + + // Initialize the hash value now that we have some input: + if(s.lookahead>=MIN_MATCH) + { + s.ins_h=s.window[s.strstart]; + //was UPDATE_HASH(s, s.ins_h, s.window[s.strstart+1]); + s.ins_h=((s.ins_h<<(int)s.hash_shift)^s.window[s.strstart+1])&s.hash_mask; + + } + // If the whole input has less than MIN_MATCH bytes, ins_h is garbage, + // but this is not important since only literal bytes will be emitted. + } while(s.lookaheadWIN_INIT) init=WIN_INIT; + for(int i=0; is.window_size-s.high_water) init=s.window_size-s.high_water; + for(int i=0; i= 0 ? s.window : null, s.block_start >= 0?s.block_start:0, \ + // (uint)((int)s.strstart - s.block_start), (last)); \ + // s.block_start = s.strstart; \ + // flush_pending(s.strm); \ + // Tracev((stderr,"[FLUSH]")); \ + //} + + // Same but force premature exit if necessary. + //#define FLUSH_BLOCK(s, last) \ + //{ \ + // _tr_flush_block(s, s.block_start >= 0 ? s.window : null, s.block_start >= 0?s.block_start:0, \ + // (uint)((int)s.strstart - s.block_start), (last)); \ + // s.block_start = s.strstart; \ + // flush_pending(s.strm); \ + // Tracev((stderr,"[FLUSH]")); \ + // if (s.strm.avail_out == 0) return (last) ? finish_started : need_more; \ + //} + + // =========================================================================== + // Copy without compression as much as possible from the input stream, return + // the current block state. + // This function does not insert new strings in the dictionary since + // uncompressible data is probably not useful. This function is used + // only for the level=0 compression option. + // NOTE: this function should be optimized to avoid extra copying from + // window to pending_buf. + static block_state deflate_stored(deflate_state s, int flush) + { + // Stored blocks are limited to 0xffff bytes, pending_buf is limited + // to pending_buf_size, and each stored block has a 5 byte header: + uint max_block_size=0xffff; + uint max_start; + + if(max_block_size>s.pending_buf_size-5) max_block_size=s.pending_buf_size-5; + + // Copy as much as possible from input to output: + for(; ; ) + { + // Fill the window as much as possible: + if(s.lookahead<=1) + { + //Assert(s.strstart=(int)s.w_size, "slide too late"); + + fill_window(s); + if(s.lookahead==0&&flush==Z_NO_FLUSH) return block_state.need_more; + + if(s.lookahead==0) break; // flush the current block + } + //Assert(s.block_start>=0, "block gone"); + + s.strstart+=s.lookahead; + s.lookahead=0; + + // Emit a stored block if pending_buf will be full: + max_start=(uint)s.block_start+max_block_size; + if(s.strstart==0||(uint)s.strstart>=max_start) + { + // strstart == 0 is possible when wraparound on 16-bit machine + s.lookahead=(uint)(s.strstart-max_start); + s.strstart=(uint)max_start; + + //was FLUSH_BLOCK(s, 0); + _tr_flush_block(s, s.block_start>=0?s.window:null, s.block_start>=0?s.block_start:0, + (uint)((int)s.strstart-s.block_start), 0); + s.block_start=(int)s.strstart; + flush_pending(s.strm); + //Tracev((stderr,"[FLUSH]")); + if(s.strm.avail_out==0) return block_state.need_more; + } + // Flush if we may have to slide, otherwise block_start may become + // negative and the data will be gone: + if(s.strstart-(uint)s.block_start>=(s.w_size-MIN_LOOKAHEAD)) + { + //was FLUSH_BLOCK(s, 0); + _tr_flush_block(s, s.block_start >= 0 ? s.window : null, s.block_start >= 0?s.block_start:0, + (uint)((int)s.strstart - s.block_start), 0); + s.block_start = (int)s.strstart; + flush_pending(s.strm); + //Tracev((stderr,"[FLUSH]")); + if (s.strm.avail_out == 0) return block_state.need_more; + } + } + + //was FLUSH_BLOCK(s, flush==Z_FINISH); + _tr_flush_block(s, s.block_start>=0?s.window:null, s.block_start>=0?s.block_start:0, + (uint)((int)s.strstart-s.block_start), flush==Z_FINISH?1:0); + s.block_start=(int)s.strstart; + flush_pending(s.strm); + //Tracev((stderr,"[FLUSH]")); + if(s.strm.avail_out==0) return flush==Z_FINISH?block_state.finish_started:block_state.need_more; + + return flush==Z_FINISH?block_state.finish_done:block_state.block_done; + } + + // =========================================================================== + // Compress as much as possible from the input stream, return the current + // block state. + // This function does not perform lazy evaluation of matches and inserts + // new strings in the dictionary only for unmatched strings or for short + // matches. It is used only for the fast compression options. + static block_state deflate_fast(deflate_state s, int flush) + { + uint hash_head=NIL; // head of the hash chain + int bflush; // set if current block must be flushed + + for(; ; ) + { + // Make sure that we always have enough lookahead, except + // at the end of the input file. We need MAX_MATCH bytes + // for the next match, plus MIN_MATCH bytes to insert the + // string following the next match. + if(s.lookahead=MIN_MATCH) + { + //was INSERT_STRING(s, s.strstart, hash_head); + s.ins_h=((s.ins_h<<(int)s.hash_shift)^s.window[s.strstart+(MIN_MATCH-1)])&s.hash_mask; + hash_head=s.prev[s.strstart&s.w_mask]=s.head[s.ins_h]; + s.head[s.ins_h]=(ushort)s.strstart; + } + + // Find the longest match, discarding those <= prev_length. + // At this point we have always match_length < MIN_MATCH + if(hash_head!=NIL&&s.strstart-hash_head<=(s.w_size-MIN_LOOKAHEAD)) + { + // To simplify the code, we prevent matches with the string + // of window index 0 (in particular we have to avoid a match + // of the string with itself at the start of the input file). + s.match_length=longest_match_fast(s, hash_head); + // longest_match_fast() sets match_start + } + if(s.match_length>=MIN_MATCH) + { + //was _tr_tally_dist(s, s.strstart - s.match_start, s.match_length - MIN_MATCH, bflush); + { + byte len=(byte)(s.match_length-MIN_MATCH); + ushort dist=(ushort)(s.strstart-s.match_start); + s.d_buf[s.last_lit]=dist; + s.l_buf[s.last_lit++]=len; + dist--; + s.dyn_ltree[_length_code[len]+LITERALS+1].Freq++; + s.dyn_dtree[(dist<256?_dist_code[dist]:_dist_code[256+(dist>>7)])].Freq++; + bflush=(s.last_lit==s.lit_bufsize-1)?1:0; + } + + s.lookahead-=s.match_length; + + // Insert new strings in the hash table only if the match length + // is not too large. This saves time but degrades compression. + if(s.match_length<=s.max_lazy_match&&s.lookahead>=MIN_MATCH) // max_lazy_match was max_insert_length as #define + { + s.match_length--; // string at strstart already in table + do + { + s.strstart++; + //was INSERT_STRING(s, s.strstart, hash_head); + s.ins_h=((s.ins_h<<(int)s.hash_shift)^s.window[s.strstart+(MIN_MATCH-1)])&s.hash_mask; + hash_head=s.prev[s.strstart&s.w_mask]=s.head[s.ins_h]; + s.head[s.ins_h]=(ushort)s.strstart; + + // strstart never exceeds WSIZE-MAX_MATCH, so there are + // always MIN_MATCH bytes ahead. + } while(--s.match_length!=0); + s.strstart++; + } + else + { + s.strstart+=s.match_length; + s.match_length=0; + s.ins_h=s.window[s.strstart]; + //was UPDATE_HASH(s, s.ins_h, s.window[s.strstart+1]); + s.ins_h=((s.ins_h<<(int)s.hash_shift)^s.window[s.strstart+1])&s.hash_mask; + + // If lookahead < MIN_MATCH, ins_h is garbage, but it does not + // matter since it will be recomputed at next deflate call. + } + } + else + { + // No match, output a literal byte + //Tracevv((stderr,"%c", s.window[s.strstart])); + + //was _tr_tally_lit (s, s.window[s.strstart], bflush); + { + byte cc=s.window[s.strstart]; + s.d_buf[s.last_lit]=0; + s.l_buf[s.last_lit++]=cc; + s.dyn_ltree[cc].Freq++; + bflush=(s.last_lit==s.lit_bufsize-1)?1:0; + } + + s.lookahead--; + s.strstart++; + } + + if(bflush!=0) + { + //was FLUSH_BLOCK(s, 0); + _tr_flush_block(s, s.block_start>=0?s.window:null, s.block_start>=0?s.block_start:0, + (uint)((int)s.strstart-s.block_start), 0); + s.block_start=(int)s.strstart; + flush_pending(s.strm); + //Tracev((stderr,"[FLUSH]")); + if(s.strm.avail_out==0) return block_state.need_more; + } + } + //was FLUSH_BLOCK(s, flush==Z_FINISH); + _tr_flush_block(s, s.block_start>=0?s.window:null, s.block_start>=0?s.block_start:0, + (uint)((int)s.strstart-s.block_start), flush==Z_FINISH?1:0); + s.block_start=(int)s.strstart; + flush_pending(s.strm); + //Tracev((stderr,"[FLUSH]")); + if(s.strm.avail_out==0) return flush==Z_FINISH?block_state.finish_started:block_state.need_more; + + return flush==Z_FINISH?block_state.finish_done:block_state.block_done; + } + + // =========================================================================== + // Same as above, but achieves better compression. We use a lazy + // evaluation for matches: a match is finally adopted only if there is + // no better match at the next window position. + static block_state deflate_slow(deflate_state s, int flush) + { + uint hash_head=NIL; // head of hash chain + int bflush; // set if current block must be flushed + + // Process the input block. + for(; ; ) + { + // Make sure that we always have enough lookahead, except + // at the end of the input file. We need MAX_MATCH bytes + // for the next match, plus MIN_MATCH bytes to insert the + // string following the next match. + if(s.lookahead=MIN_MATCH) + { + //was INSERT_STRING(s, s.strstart, hash_head); + s.ins_h=((s.ins_h<<(int)s.hash_shift)^s.window[s.strstart+(MIN_MATCH-1)])&s.hash_mask; + hash_head=s.prev[s.strstart&s.w_mask]=s.head[s.ins_h]; + s.head[s.ins_h]=(ushort)s.strstart; + } + + // Find the longest match, discarding those <= prev_length. + s.prev_length=s.match_length; + s.prev_match=s.match_start; + s.match_length=MIN_MATCH-1; + + if(hash_head!=NIL&&s.prev_lengthTOO_FAR))) + { + // If prev_match is also MIN_MATCH, match_start is garbage + // but we will ignore the current match anyway. + s.match_length=MIN_MATCH-1; + } + } + + // If there was a match at the previous step and the current + // match is not better, output the previous match: + if(s.prev_length>=MIN_MATCH&&s.match_length<=s.prev_length) + { + uint max_insert=s.strstart+s.lookahead-MIN_MATCH; + // Do not insert strings in hash table beyond this. + + //was _tr_tally_dist(s, s.strstart -1 - s.prev_match, s.prev_length - MIN_MATCH, bflush); + { + byte len=(byte)(s.prev_length-MIN_MATCH); + ushort dist=(ushort)(s.strstart-1-s.prev_match); + s.d_buf[s.last_lit]=dist; + s.l_buf[s.last_lit++]=len; + dist--; + s.dyn_ltree[_length_code[len]+LITERALS+1].Freq++; + s.dyn_dtree[(dist<256?_dist_code[dist]:_dist_code[256+(dist>>7)])].Freq++; + bflush=(s.last_lit==s.lit_bufsize-1)?1:0; + } + + // Insert in hash table all strings up to the end of the match. + // strstart-1 and strstart are already inserted. If there is not + // enough lookahead, the last two strings are not inserted in + // the hash table. + s.lookahead-=s.prev_length-1; + s.prev_length-=2; + do + { + if(++s.strstart<=max_insert) + { + //was INSERT_STRING(s, s.strstart, hash_head); + s.ins_h=((s.ins_h<<(int)s.hash_shift)^s.window[s.strstart+(MIN_MATCH-1)])&s.hash_mask; + hash_head=s.prev[s.strstart&s.w_mask]=s.head[s.ins_h]; + s.head[s.ins_h]=(ushort)s.strstart; + } + } while(--s.prev_length!=0); + s.match_available=0; + s.match_length=MIN_MATCH-1; + s.strstart++; + + if(bflush!=0) + { + //was FLUSH_BLOCK(s, 0); + _tr_flush_block(s, s.block_start>=0?s.window:null, s.block_start>=0?s.block_start:0, + (uint)((int)s.strstart-s.block_start), 0); + s.block_start=(int)s.strstart; + flush_pending(s.strm); + //Tracev((stderr,"[FLUSH]")); + if(s.strm.avail_out==0) return block_state.need_more; + } + } + else if(s.match_available!=0) + { + // If there was no match at the previous position, output a + // single literal. If there was a match but the current match + // is longer, truncate the previous match to a single literal. + //Tracevv((stderr,"%c", s.window[s.strstart-1])); + + //was _tr_tally_lit(s, s.window[s.strstart-1], bflush); + { + byte cc=s.window[s.strstart-1]; + s.d_buf[s.last_lit]=0; + s.l_buf[s.last_lit++]=cc; + s.dyn_ltree[cc].Freq++; + bflush=(s.last_lit==s.lit_bufsize-1)?1:0; + } + + if(bflush!=0) + { + //was FLUSH_BLOCK_ONLY(s, 0); + _tr_flush_block(s, s.block_start>=0?s.window:null, s.block_start>=0?s.block_start:0, + (uint)((int)s.strstart-s.block_start), 0); + s.block_start=(int)s.strstart; + flush_pending(s.strm); + //Tracev((stderr,"[FLUSH]")); + } + s.strstart++; + s.lookahead--; + if(s.strm.avail_out==0) return block_state.need_more; + } + else + { + // There is no previous match to compare with, wait for + // the next step to decide. + s.match_available=1; + s.strstart++; + s.lookahead--; + } + } + //Assert(flush!=Z_NO_FLUSH, "no flush?"); + if(s.match_available!=0) + { + //Tracevv((stderr,"%c", s.window[s.strstart-1])); + + //was _tr_tally_lit(s, s.window[s.strstart-1], bflush); + { + byte cc=s.window[s.strstart-1]; + s.d_buf[s.last_lit]=0; + s.l_buf[s.last_lit++]=cc; + s.dyn_ltree[cc].Freq++; + bflush=(s.last_lit==s.lit_bufsize-1)?1:0; + } + + s.match_available=0; + } + //was FLUSH_BLOCK(s, flush==Z_FINISH); + _tr_flush_block(s, s.block_start>=0?s.window:null, s.block_start>=0?s.block_start:0, + (uint)((int)s.strstart-s.block_start), flush==Z_FINISH?1:0); + s.block_start=(int)s.strstart; + flush_pending(s.strm); + //Tracev((stderr,"[FLUSH]")); + if(s.strm.avail_out==0) return flush==Z_FINISH?block_state.finish_started:block_state.need_more; + + return flush==Z_FINISH?block_state.finish_done:block_state.block_done; + } + + // =========================================================================== + // For Z_RLE, simply look for runs of bytes, generate matches only of distance + // one. Do not maintain a hash table. (It will be regenerated if this run of + // deflate switches away from Z_RLE.) + static block_state deflate_rle(deflate_state s, int flush) + { + bool bflush; // set if current block must be flushed + uint prev; // byte at distance one to match + int scan, strend; // scan goes up to strend for length of run + + for(; ; ) + { + // Make sure that we always have enough lookahead, except + // at the end of the input file. We need MAX_MATCH bytes + // for the longest encodable run. + if(s.lookahead=MIN_MATCH&&s.strstart>0) + { + scan=(int)(s.strstart-1); + prev=s.window[scan]; + if(prev==s.window[++scan]&&prev==s.window[++scan]&&prev==s.window[++scan]) + { + strend=(int)(s.strstart+MAX_MATCH); + do + { + } while(prev==s.window[++scan]&&prev==s.window[++scan]&& + prev==s.window[++scan]&&prev==s.window[++scan]&& + prev==s.window[++scan]&&prev==s.window[++scan]&& + prev==s.window[++scan]&&prev==s.window[++scan]&& + scans.lookahead) s.match_length=s.lookahead; + } + } + + // Emit match if have run of MIN_MATCH or longer, else emit literal + if(s.match_length>=MIN_MATCH) + { + //was _tr_tally_dist(s, 1, s.match_length-MIN_MATCH, bflush); + { + byte len=(byte)(s.match_length-MIN_MATCH); + ushort dist=1; + s.d_buf[s.last_lit]=dist; + s.l_buf[s.last_lit++]=len; + dist--; + s.dyn_ltree[_length_code[len]+LITERALS+1].Freq++; + s.dyn_dtree[(dist<256?_dist_code[dist]:_dist_code[256+(dist>>7)])].Freq++; + bflush=(s.last_lit==s.lit_bufsize-1)?true:false; + } + + s.lookahead-=s.match_length; + s.strstart+=s.match_length; + s.match_length=0; + } + else + { + // No match, output a literal byte + //Tracevv((stderr,"%c", s.window[s.strstart])); + //was _tr_tally_lit(s, s.window[s.strstart], bflush); + { + byte cc=s.window[s.strstart]; + s.d_buf[s.last_lit]=0; + s.l_buf[s.last_lit++]=cc; + s.dyn_ltree[cc].Freq++; + bflush=(s.last_lit==s.lit_bufsize-1)?true:false; + } + + s.lookahead--; + s.strstart++; + } + if(bflush) + { + // FLUSH_BLOCK(s, 0); + _tr_flush_block(s, s.block_start>=0?s.window:null, s.block_start>=0?s.block_start:0, + (uint)((int)s.strstart-s.block_start), 0); + s.block_start=(int)s.strstart; + flush_pending(s.strm); + //Tracev((stderr,"[FLUSH]")); + if(s.strm.avail_out==0) return block_state.need_more; + } + } + + //was FLUSH_BLOCK(s, flush==Z_FINISH); + _tr_flush_block(s, s.block_start>=0?s.window:null, s.block_start>=0?s.block_start:0, + (uint)((int)s.strstart-s.block_start), flush==Z_FINISH?1:0); + s.block_start=(int)s.strstart; + flush_pending(s.strm); + //Tracev((stderr,"[FLUSH]")); + if(s.strm.avail_out==0) return flush==Z_FINISH?block_state.finish_started:block_state.need_more; + + return flush==Z_FINISH?block_state.finish_done:block_state.block_done; + } + + // =========================================================================== + // For Z_HUFFMAN_ONLY, do not look for matches. Do not maintain a hash table. + // (It will be regenerated if this run of deflate switches away from Huffman.) + static block_state deflate_huff(deflate_state s, int flush) + { + bool bflush; // set if current block must be flushed + + for(; ; ) + { + // Make sure that we have a literal to write. + if(s.lookahead==0) + { + fill_window(s); + if(s.lookahead==0) + { + if(flush==Z_NO_FLUSH) + return block_state.need_more; + break; // flush the current block + } + } + + // Output a literal byte + s.match_length=0; + //Tracevv((stderr,"%c", s.window[s.strstart])); + + //was _tr_tally_lit(s, s.window[s.strstart], bflush); + { + byte cc=s.window[s.strstart]; + s.d_buf[s.last_lit]=0; + s.l_buf[s.last_lit++]=cc; + s.dyn_ltree[cc].Freq++; + bflush=(s.last_lit==s.lit_bufsize-1)?true:false; + } + + s.lookahead--; + s.strstart++; + if(bflush) + { + // FLUSH_BLOCK(s, 0); + _tr_flush_block(s, s.block_start>=0?s.window:null, s.block_start>=0?s.block_start:0, + (uint)((int)s.strstart-s.block_start), 0); + s.block_start=(int)s.strstart; + flush_pending(s.strm); + //Tracev((stderr,"[FLUSH]")); + if(s.strm.avail_out==0) return block_state.need_more; + } + } + + //was FLUSH_BLOCK(s, flush==Z_FINISH); + _tr_flush_block(s, s.block_start>=0?s.window:null, s.block_start>=0?s.block_start:0, + (uint)((int)s.strstart-s.block_start), flush==Z_FINISH?1:0); + s.block_start=(int)s.strstart; + flush_pending(s.strm); + //Tracev((stderr,"[FLUSH]")); + if(s.strm.avail_out==0) return flush==Z_FINISH?block_state.finish_started:block_state.need_more; + + return flush==Z_FINISH?block_state.finish_done:block_state.block_done; + } + } +} diff --git a/Framework/IO/Zlib/Trees.cs b/Framework/IO/Zlib/Trees.cs new file mode 100644 index 000000000..234cd9755 --- /dev/null +++ b/Framework/IO/Zlib/Trees.cs @@ -0,0 +1,1069 @@ +// trees.cs -- output deflated data using Huffman coding +// Copyright (C) 1995-2010 Jean-loup Gailly +// Copyright (C) 2007-2011 by the Authors +// For conditions of distribution and use, see copyright notice in License.txt + +// ALGORITHM +// +// The "deflation" process uses several Huffman trees. The more +// common source values are represented by shorter bit sequences. +// +// Each code tree is stored in a compressed form which is itself +// a Huffman encoding of the lengths of all the code strings (in +// ascending order by source values). The actual code strings are +// reconstructed from the lengths in the inflate process, as described +// in the deflate specification. +// +// REFERENCES +// +// Deutsch, L.P.,"'Deflate' Compressed Data Format Specification". +// Available in ftp.uu.net:/pub/archiving/zip/doc/deflate-1.1.doc +// +// Storer, James A. +// Data Compression: Methods and Theory, pp. 49-50. +// Computer Science Press, 1988. ISBN 0-7167-8156-5. +// +// Sedgewick, R. +// Algorithms, p290. +// Addison-Wesley, 1983. ISBN 0-201-06672-6. + +using System; + +namespace Framework.IO +{ + public static partial class ZLib + { + // =========================================================================== + // Constants + // + + // Bit length codes must not exceed MAX_BL_BITS bits + private const int MAX_BL_BITS=7; + + // end of block literal code + private const int END_BLOCK=256; + + // repeat previous bit length 3-6 times (2 bits of repeat count) + private const int REP_3_6=16; + + // repeat a zero length 3-10 times (3 bits of repeat count) + private const int REPZ_3_10=17; + + // repeat a zero length 11-138 times (7 bits of repeat count) + private const int REPZ_11_138=18; + + // extra bits for each length code + private static readonly int[] extra_lbits=new int[LENGTH_CODES] { 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 2, 2, 2, 2, 3, 3, 3, 3, 4, 4, 4, 4, 5, 5, 5, 5, 0 }; + + // extra bits for each distance code + private static readonly int[] extra_dbits=new int[D_CODES] { 0, 0, 0, 0, 1, 1, 2, 2, 3, 3, 4, 4, 5, 5, 6, 6, 7, 7, 8, 8, 9, 9, 10, 10, 11, 11, 12, 12, 13, 13 }; + + // extra bits for each bit length code + private static readonly int[] extra_blbits=new int[BL_CODES] { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 3, 7 }; + + // The lengths of the bit length codes are sent in order of decreasing + // probability, to avoid transmitting the lengths for unused bit length codes. + private static readonly byte[] bl_order=new byte[BL_CODES] { 16, 17, 18, 0, 8, 7, 9, 6, 10, 5, 11, 4, 12, 3, 13, 2, 14, 1, 15 }; + + // Number of bits used within bi_buf. (bi_buf might be implemented on + // more than 16 bits on some systems.) + private const int Buf_size=8*2*sizeof(byte); + + // =========================================================================== + // Local data. These are initialized only once. + + // see definition of array dist_code below + private const int DIST_CODE_LEN=512; + + #region Tables + private static readonly ct_data[] static_ltree=new ct_data[L_CODES+2] + { + new ct_data( 12, 8), new ct_data(140, 8), new ct_data( 76, 8), new ct_data(204, 8), + new ct_data( 44, 8), new ct_data(172, 8), new ct_data(108, 8), new ct_data(236, 8), + new ct_data( 28, 8), new ct_data(156, 8), new ct_data( 92, 8), new ct_data(220, 8), + new ct_data( 60, 8), new ct_data(188, 8), new ct_data(124, 8), new ct_data(252, 8), + new ct_data( 2, 8), new ct_data(130, 8), new ct_data( 66, 8), new ct_data(194, 8), + new ct_data( 34, 8), new ct_data(162, 8), new ct_data( 98, 8), new ct_data(226, 8), + new ct_data( 18, 8), new ct_data(146, 8), new ct_data( 82, 8), new ct_data(210, 8), + new ct_data( 50, 8), new ct_data(178, 8), new ct_data(114, 8), new ct_data(242, 8), + new ct_data( 10, 8), new ct_data(138, 8), new ct_data( 74, 8), new ct_data(202, 8), + new ct_data( 42, 8), new ct_data(170, 8), new ct_data(106, 8), new ct_data(234, 8), + new ct_data( 26, 8), new ct_data(154, 8), new ct_data( 90, 8), new ct_data(218, 8), + new ct_data( 58, 8), new ct_data(186, 8), new ct_data(122, 8), new ct_data(250, 8), + new ct_data( 6, 8), new ct_data(134, 8), new ct_data( 70, 8), new ct_data(198, 8), + new ct_data( 38, 8), new ct_data(166, 8), new ct_data(102, 8), new ct_data(230, 8), + new ct_data( 22, 8), new ct_data(150, 8), new ct_data( 86, 8), new ct_data(214, 8), + new ct_data( 54, 8), new ct_data(182, 8), new ct_data(118, 8), new ct_data(246, 8), + new ct_data( 14, 8), new ct_data(142, 8), new ct_data( 78, 8), new ct_data(206, 8), + new ct_data( 46, 8), new ct_data(174, 8), new ct_data(110, 8), new ct_data(238, 8), + new ct_data( 30, 8), new ct_data(158, 8), new ct_data( 94, 8), new ct_data(222, 8), + new ct_data( 62, 8), new ct_data(190, 8), new ct_data(126, 8), new ct_data(254, 8), + new ct_data( 1, 8), new ct_data(129, 8), new ct_data( 65, 8), new ct_data(193, 8), + new ct_data( 33, 8), new ct_data(161, 8), new ct_data( 97, 8), new ct_data(225, 8), + new ct_data( 17, 8), new ct_data(145, 8), new ct_data( 81, 8), new ct_data(209, 8), + new ct_data( 49, 8), new ct_data(177, 8), new ct_data(113, 8), new ct_data(241, 8), + new ct_data( 9, 8), new ct_data(137, 8), new ct_data( 73, 8), new ct_data(201, 8), + new ct_data( 41, 8), new ct_data(169, 8), new ct_data(105, 8), new ct_data(233, 8), + new ct_data( 25, 8), new ct_data(153, 8), new ct_data( 89, 8), new ct_data(217, 8), + new ct_data( 57, 8), new ct_data(185, 8), new ct_data(121, 8), new ct_data(249, 8), + new ct_data( 5, 8), new ct_data(133, 8), new ct_data( 69, 8), new ct_data(197, 8), + new ct_data( 37, 8), new ct_data(165, 8), new ct_data(101, 8), new ct_data(229, 8), + new ct_data( 21, 8), new ct_data(149, 8), new ct_data( 85, 8), new ct_data(213, 8), + new ct_data( 53, 8), new ct_data(181, 8), new ct_data(117, 8), new ct_data(245, 8), + new ct_data( 13, 8), new ct_data(141, 8), new ct_data( 77, 8), new ct_data(205, 8), + new ct_data( 45, 8), new ct_data(173, 8), new ct_data(109, 8), new ct_data(237, 8), + new ct_data( 29, 8), new ct_data(157, 8), new ct_data( 93, 8), new ct_data(221, 8), + new ct_data( 61, 8), new ct_data(189, 8), new ct_data(125, 8), new ct_data(253, 8), + new ct_data( 19, 9), new ct_data(275, 9), new ct_data(147, 9), new ct_data(403, 9), + new ct_data( 83, 9), new ct_data(339, 9), new ct_data(211, 9), new ct_data(467, 9), + new ct_data( 51, 9), new ct_data(307, 9), new ct_data(179, 9), new ct_data(435, 9), + new ct_data(115, 9), new ct_data(371, 9), new ct_data(243, 9), new ct_data(499, 9), + new ct_data( 11, 9), new ct_data(267, 9), new ct_data(139, 9), new ct_data(395, 9), + new ct_data( 75, 9), new ct_data(331, 9), new ct_data(203, 9), new ct_data(459, 9), + new ct_data( 43, 9), new ct_data(299, 9), new ct_data(171, 9), new ct_data(427, 9), + new ct_data(107, 9), new ct_data(363, 9), new ct_data(235, 9), new ct_data(491, 9), + new ct_data( 27, 9), new ct_data(283, 9), new ct_data(155, 9), new ct_data(411, 9), + new ct_data( 91, 9), new ct_data(347, 9), new ct_data(219, 9), new ct_data(475, 9), + new ct_data( 59, 9), new ct_data(315, 9), new ct_data(187, 9), new ct_data(443, 9), + new ct_data(123, 9), new ct_data(379, 9), new ct_data(251, 9), new ct_data(507, 9), + new ct_data( 7, 9), new ct_data(263, 9), new ct_data(135, 9), new ct_data(391, 9), + new ct_data( 71, 9), new ct_data(327, 9), new ct_data(199, 9), new ct_data(455, 9), + new ct_data( 39, 9), new ct_data(295, 9), new ct_data(167, 9), new ct_data(423, 9), + new ct_data(103, 9), new ct_data(359, 9), new ct_data(231, 9), new ct_data(487, 9), + new ct_data( 23, 9), new ct_data(279, 9), new ct_data(151, 9), new ct_data(407, 9), + new ct_data( 87, 9), new ct_data(343, 9), new ct_data(215, 9), new ct_data(471, 9), + new ct_data( 55, 9), new ct_data(311, 9), new ct_data(183, 9), new ct_data(439, 9), + new ct_data(119, 9), new ct_data(375, 9), new ct_data(247, 9), new ct_data(503, 9), + new ct_data( 15, 9), new ct_data(271, 9), new ct_data(143, 9), new ct_data(399, 9), + new ct_data( 79, 9), new ct_data(335, 9), new ct_data(207, 9), new ct_data(463, 9), + new ct_data( 47, 9), new ct_data(303, 9), new ct_data(175, 9), new ct_data(431, 9), + new ct_data(111, 9), new ct_data(367, 9), new ct_data(239, 9), new ct_data(495, 9), + new ct_data( 31, 9), new ct_data(287, 9), new ct_data(159, 9), new ct_data(415, 9), + new ct_data( 95, 9), new ct_data(351, 9), new ct_data(223, 9), new ct_data(479, 9), + new ct_data( 63, 9), new ct_data(319, 9), new ct_data(191, 9), new ct_data(447, 9), + new ct_data(127, 9), new ct_data(383, 9), new ct_data(255, 9), new ct_data(511, 9), + new ct_data( 0, 7), new ct_data( 64, 7), new ct_data( 32, 7), new ct_data( 96, 7), + new ct_data( 16, 7), new ct_data( 80, 7), new ct_data( 48, 7), new ct_data(112, 7), + new ct_data( 8, 7), new ct_data( 72, 7), new ct_data( 40, 7), new ct_data(104, 7), + new ct_data( 24, 7), new ct_data( 88, 7), new ct_data( 56, 7), new ct_data(120, 7), + new ct_data( 4, 7), new ct_data( 68, 7), new ct_data( 36, 7), new ct_data(100, 7), + new ct_data( 20, 7), new ct_data( 84, 7), new ct_data( 52, 7), new ct_data(116, 7), + new ct_data( 3, 8), new ct_data(131, 8), new ct_data( 67, 8), new ct_data(195, 8), + new ct_data( 35, 8), new ct_data(163, 8), new ct_data( 99, 8), new ct_data(227, 8) + }; + + private static readonly ct_data[] static_dtree=new ct_data[D_CODES] + { + new ct_data( 0, 5), new ct_data(16, 5), new ct_data( 8, 5), new ct_data(24, 5), new ct_data( 4, 5), + new ct_data(20, 5), new ct_data(12, 5), new ct_data(28, 5), new ct_data( 2, 5), new ct_data(18, 5), + new ct_data(10, 5), new ct_data(26, 5), new ct_data( 6, 5), new ct_data(22, 5), new ct_data(14, 5), + new ct_data(30, 5), new ct_data( 1, 5), new ct_data(17, 5), new ct_data( 9, 5), new ct_data(25, 5), + new ct_data( 5, 5), new ct_data(21, 5), new ct_data(13, 5), new ct_data(29, 5), new ct_data( 3, 5), + new ct_data(19, 5), new ct_data(11, 5), new ct_data(27, 5), new ct_data( 7, 5), new ct_data(23, 5) + }; + + private static readonly byte[] _dist_code=new byte[DIST_CODE_LEN] + { + 0, 1, 2, 3, 4, 4, 5, 5, 6, 6, 6, 6, 7, 7, 7, 7, 8, 8, 8, 8, + 8, 8, 8, 8, 9, 9, 9, 9, 9, 9, 9, 9, 10, 10, 10, 10, 10, 10, 10, 10, + 10, 10, 10, 10, 10, 10, 10, 10, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, + 11, 11, 11, 11, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, + 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 13, 13, 13, 13, + 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, + 13, 13, 13, 13, 13, 13, 13, 13, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, + 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, + 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, + 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 15, 15, 15, 15, 15, 15, 15, 15, + 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, + 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, + 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 0, 0, 16, 17, + 18, 18, 19, 19, 20, 20, 20, 20, 21, 21, 21, 21, 22, 22, 22, 22, 22, 22, 22, 22, + 23, 23, 23, 23, 23, 23, 23, 23, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, + 24, 24, 24, 24, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, + 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, + 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 27, 27, 27, 27, 27, 27, 27, 27, + 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, + 27, 27, 27, 27, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, + 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, + 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, + 28, 28, 28, 28, 28, 28, 28, 28, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, + 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, + 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, + 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29 + }; + + private static readonly byte[] _length_code=new byte[MAX_MATCH-MIN_MATCH+1] + { + 0, 1, 2, 3, 4, 5, 6, 7, 8, 8, 9, 9, 10, 10, 11, 11, 12, 12, 12, 12, + 13, 13, 13, 13, 14, 14, 14, 14, 15, 15, 15, 15, 16, 16, 16, 16, 16, 16, 16, 16, + 17, 17, 17, 17, 17, 17, 17, 17, 18, 18, 18, 18, 18, 18, 18, 18, 19, 19, 19, 19, + 19, 19, 19, 19, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, + 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 22, 22, 22, 22, + 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 23, 23, 23, 23, 23, 23, 23, 23, + 23, 23, 23, 23, 23, 23, 23, 23, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, + 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, + 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, + 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 26, 26, 26, 26, 26, 26, 26, 26, + 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, + 26, 26, 26, 26, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, + 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 28 + }; + + private static readonly int[] base_length=new int[LENGTH_CODES] + { + 0, 1, 2, 3, 4, 5, 6, 7, 8, 10, 12, 14, 16, 20, 24, 28, 32, 40, 48, 56, + 64, 80, 96, 112, 128, 160, 192, 224, 0 + }; + + private static readonly int[] base_dist=new int[D_CODES] + { + 0, 1, 2, 3, 4, 6, 8, 12, 16, 24, + 32, 48, 64, 96, 128, 192, 256, 384, 512, 768, + 1024, 1536, 2048, 3072, 4096, 6144, 8192, 12288, 16384, 24576 + }; + #endregion + + class static_tree_desc + { + public readonly ct_data[] static_tree; // static tree or NULL + public readonly int[] extra_bits; // extra bits for each code or NULL + public int extra_base; // base index for extra_bits + public int elems; // max number of elements in the tree + public int max_length; // max bit length for the codes + + public static_tree_desc(ct_data[] static_tree, int[] extra_bits, int extra_base, int elems, int max_length) + { + this.static_tree=static_tree; + this.extra_bits=extra_bits; + this.extra_base=extra_base; + this.elems=elems; + this.max_length=max_length; + } + } + + private static readonly static_tree_desc static_l_desc=new static_tree_desc(static_ltree, extra_lbits, LITERALS+1, L_CODES, MAX_BITS); + private static readonly static_tree_desc static_d_desc=new static_tree_desc(static_dtree, extra_dbits, 0, D_CODES, MAX_BITS); + private static readonly static_tree_desc static_bl_desc=new static_tree_desc(null, extra_blbits, 0, BL_CODES, MAX_BL_BITS); + + // =========================================================================== + // Local (static) routines in this file. + // + + // Send a code of the given tree. c and tree must not have side effects + //#define send_code(s, c, tree) send_bits(s, tree[c].Code, tree[c].Len) + static void send_code(deflate_state s, int c, ct_data[] tree) + { + ushort value=tree[c].Code; + ushort len=tree[c].Len; + if(s.bi_valid>(int)Buf_size-len) + { + int val=value; + s.bi_buf|=(ushort)(val<>8); + s.bi_buf=(ushort)(val>>(Buf_size-s.bi_valid)); + s.bi_valid+=len-Buf_size; + } + else + { + s.bi_buf|=(ushort)(value<> 8)); \ + //} + + // =========================================================================== + // Send a value on a given number of bits. + // IN assertion: length <= 16 and value fits in length bits. + //#define send_bits(s, value, length) { \ + // int len = length; \ + // if(s.bi_valid > (int)Buf_size - len) { \ + // int val = value; \ + // s.bi_buf |= (val << s.bi_valid); \ + // // put_short(s, s.bi_buf); \ + // s.pending_buf[s.pending++] = (unsigned char)(s.bi_buf & 0xff);\ + // s.pending_buf[s.pending++] = (unsigned char)((unsigned short)s.bi_buf >> 8);\ + // s.bi_buf = (unsigned short)val >> (Buf_size - s.bi_valid); \ + // s.bi_valid += len - Buf_size; \ + // } else { \ + // s.bi_buf |= (value) << s.bi_valid; \ + // s.bi_valid += len; \ + // } \ + // } + + static void send_bits(deflate_state s, int value, int length) + { + int len=length; + if(s.bi_valid>(int)Buf_size-len) + { + int val=value; + s.bi_buf|=(ushort)(val<>8); + s.bi_buf=(ushort)(val>>(Buf_size-s.bi_valid)); + s.bi_valid+=len-Buf_size; + } + else + { + s.bi_buf|=(ushort)(value<max_length) { bits=max_length; overflow++; } + tree[n].Len=(ushort)bits; + // We overwrite tree[n].Dad which is no longer needed + + if(n>max_code) continue; // not a leaf node + + s.bl_count[bits]++; + xbits=0; + if(n>=@base) xbits=extra[n-@base]; + f=tree[n].Freq; + s.opt_len+=(uint)(f*(bits+xbits)); + if(stree!=null) s.static_len+=(uint)(f*(stree[n].Len+xbits)); + } + if(overflow==0) return; + + //Trace((stderr,"\nbit length overflow\n")); + // This happens for example on obj2 and pic of the Calgary corpus + + // Find the first bit length which could increase: + do + { + bits=max_length-1; + while(s.bl_count[bits]==0) bits--; + s.bl_count[bits]--; // move one leaf down the tree + s.bl_count[bits+1]+=2; // move one overflow item as its brother + s.bl_count[max_length]--; + // The brother of the overflow item also moves one step up, + // but this does not affect bl_count[max_length] + overflow-=2; + } while(overflow>0); + + // Now recompute all bit lengths, scanning in increasing frequency. + // h is still equal to HEAP_SIZE. (It is simpler to reconstruct all + // lengths instead of fixing only the wrong ones. This idea is taken + // from 'ar' written by Haruhiko Okumura.) + for(bits=max_length; bits!=0; bits--) + { + n=s.bl_count[bits]; + while(n!=0) + { + m=s.heap[--h]; + if(m>max_code) continue; + if((uint)tree[m].Len!=(uint)bits) + { + //Trace((stderr,"code %d bits %d->%d\n", m, tree[m].Len, bits)); + s.opt_len+=((uint)bits-tree[m].Len)*tree[m].Freq; + tree[m].Len=(ushort)bits; + } + n--; + } + } + } + + // =========================================================================== + // Generate the codes for a given tree and bit counts (which need not be + // optimal). + // IN assertion: the array bl_count contains the bit length statistics for + // the given tree and the field len is set for all tree elements. + // OUT assertion: the field code is set for all tree elements of non + // zero code length. + + // tree: the tree to decorate + // max_code: largest code with non zero frequency + // bl_count: number of codes at each bit length + static void gen_codes(ct_data[] tree, int max_code, ushort[] bl_count) + { + ushort[] next_code=new ushort[MAX_BITS+1]; // next code value for each bit length + ushort code=0; // running code value + int bits; // bit index + int n; // code index + + // The distribution counts are first used to generate the code values + // without bit reversal. + for(bits=1; bits<=MAX_BITS; bits++) next_code[bits]=code=(ushort)((code+bl_count[bits-1])<<1); + + // Check that the bit counts in bl_count are consistent. The last code + // must be all ones. + //Assert (code + bl_count[MAX_BITS]-1 == (1<=1; n--) pqdownheap(s, tree, n); + + // Construct the Huffman tree by repeatedly combining the least two + // frequent nodes. + node=elems; // next internal node of the tree + do + { + //was pqremove(s, tree, n); // n = node of least frequency + n=s.heap[SMALLEST]; + s.heap[SMALLEST]=s.heap[s.heap_len--]; + pqdownheap(s, tree, SMALLEST); + + m=s.heap[SMALLEST]; // m = node of next least frequency + + s.heap[--(s.heap_max)]=n; // keep the nodes sorted by frequency + s.heap[--(s.heap_max)]=m; + + // Create a new node father of n and m + tree[node].Freq=(ushort)(tree[n].Freq+tree[m].Freq); + s.depth[node]=(byte)((s.depth[n]>=s.depth[m]?s.depth[n]:s.depth[m])+1); + tree[n].Dad=tree[m].Dad=(ushort)node; + + // and insert the new node in the heap + s.heap[SMALLEST]=node++; + pqdownheap(s, tree, SMALLEST); + + } while(s.heap_len>=2); + + s.heap[--(s.heap_max)]=s.heap[SMALLEST]; + + // At this point, the fields freq and dad are set. We can now + // generate the bit lengths. + gen_bitlen(s, ref desc); + + // The field len is now set, we can generate the bit codes + gen_codes(tree, max_code, s.bl_count); + } + + // =========================================================================== + // Scan a literal or distance tree to determine the frequencies of the codes + // in the bit length tree. + + // tree: the tree to be scanned + // max_code: and its largest code of non zero frequency + static void scan_tree(deflate_state s, ct_data[] tree, int max_code) + { + int n; // iterates over all tree elements + int prevlen=-1; // last emitted length + int curlen; // length of current code + int nextlen=tree[0].Len; // length of next code + int count=0; // repeat count of the current code + int max_count=7; // max repeat count + int min_count=4; // min repeat count + + if(nextlen==0) { max_count=138; min_count=3; } + tree[max_code+1].Len=(ushort)0xffff; // guard + + for(n=0; n<=max_code; n++) + { + curlen=nextlen; nextlen=tree[n+1].Len; + if(++count=3&&count<=6, " 3_6?"); + send_code(s, REP_3_6, s.bl_tree); send_bits(s, count-3, 2); + } + else if(count<=10) { send_code(s, REPZ_3_10, s.bl_tree); send_bits(s, count-3, 3); } + else { send_code(s, REPZ_11_138, s.bl_tree); send_bits(s, count-11, 7); } + + count=0; prevlen=curlen; + if(nextlen==0) { max_count=138; min_count=3; } + else if(curlen==nextlen) { max_count=6; min_count=3; } + else { max_count=7; min_count=4; } + } + } + + // =========================================================================== + // Construct the Huffman tree for the bit lengths and return the index in + // bl_order of the last bit length code to send. + static int build_bl_tree(deflate_state s) + { + int max_blindex; // index of last bit length code of non zero freq + + // Determine the bit length frequencies for literal and distance trees + scan_tree(s, s.dyn_ltree, s.l_desc.max_code); + scan_tree(s, s.dyn_dtree, s.d_desc.max_code); + + // Build the bit length tree: + build_tree(s, ref s.bl_desc); + // opt_len now includes the length of the tree representations, except + // the lengths of the bit lengths codes and the 5+5+4 bits for the counts. + + // Determine the number of bit length codes to send. The pkzip format + // requires that at least 4 bit length codes be sent. (appnote.txt says + // 3 but the actual value used is 4.) + for(max_blindex=BL_CODES-1; max_blindex>=3; max_blindex--) + { + if(s.bl_tree[bl_order[max_blindex]].Len!=0) break; + } + // Update opt_len to include the bit length tree and counts + s.opt_len+=(uint)(3*(max_blindex+1)+5+5+4); + //Tracev((stderr, "\ndyn trees: dyn %ld, stat %ld", s.opt_len, s.static_len)); + + return max_blindex; + } + + // =========================================================================== + // Send the header for a block using dynamic Huffman trees: the counts, the + // lengths of the bit length codes, the literal tree and the distance tree. + // IN assertion: lcodes >= 257, dcodes >= 1, blcodes >= 4. + + // lcodes, dcodes, blcodes: number of codes for each tree + static void send_all_trees(deflate_state s, int lcodes, int dcodes, int blcodes) + { + int rank; // index in bl_order + + //Assert (lcodes >= 257 && dcodes >= 1 && blcodes >= 4, "not enough codes"); + //Assert (lcodes <= L_CODES && dcodes <= D_CODES && blcodes <= BL_CODES, "too many codes"); + //Tracev((stderr, "\nbl counts: ")); + send_bits(s, lcodes-257, 5); // not +255 as stated in appnote.txt + send_bits(s, dcodes-1, 5); + send_bits(s, blcodes-4, 4); // not -3 as stated in appnote.txt + for(rank=0; rank0) + { + // Construct the literal and distance trees + build_tree(s, ref s.l_desc); + //Tracev((stderr, "\nlit data: dyn %ld, stat %ld", s.opt_len, s.static_len)); + + build_tree(s, ref s.d_desc); + //Tracev((stderr, "\ndist data: dyn %ld, stat %ld", s.opt_len, s.static_len)); + // At this point, opt_len and static_len are the total bit lengths of + // the compressed block data, excluding the tree representations. + + // Build the bit length tree for the above two trees, and get the index + // in bl_order of the last bit length code to send. + max_blindex=build_bl_tree(s); + + // Determine the best encoding. Compute the block lengths in bytes. + opt_lenb=(s.opt_len+3+7)>>3; + static_lenb=(s.static_len+3+7)>>3; + + //Tracev((stderr, "\nopt %lu(%lu) stat %lu(%lu) stored %lu lit %u ", opt_lenb, s.opt_len, static_lenb, s.static_len, stored_len, s.last_lit)); + + if(static_lenb<=opt_lenb) opt_lenb=static_lenb; + } + else + { + //Assert(buf!=(char*)0, "lost buf"); + opt_lenb=static_lenb=stored_len+5; // force a stored block + } + + if(stored_len+4<=opt_lenb&&buf!=null) + { + // 4: two words for the lengths + // The test buf != NULL is only necessary if LIT_BUFSIZE > WSIZE. + // Otherwise we can't have processed more than WSIZE input bytes since + // the last block flush, because compression would have been + // successful. If LIT_BUFSIZE <= WSIZE, it is never too late to + // transform a block into a stored block. + _tr_stored_block(s, buf, buf_ind, stored_len, last); + } + else if(s.strategy==Z_FIXED||static_lenb==opt_lenb) + { + send_bits(s, (STATIC_TREES<<1)+last, 3); + compress_block(s, static_ltree, static_dtree); + } + else + { + send_bits(s, (DYN_TREES<<1)+last, 3); + send_all_trees(s, s.l_desc.max_code+1, s.d_desc.max_code+1, max_blindex+1); + compress_block(s, s.dyn_ltree, s.dyn_dtree); + } + //Assert (s.compressed_len == s.bits_sent, "bad compressed size"); + // The above check is made mod 2^32, for files larger than 512 MB + // and unsigned int implemented on 32 bits. + init_block(s); + + if(last!=0) bi_windup(s); + //Tracev((stderr,"\ncomprlen %lu(%lu) ", s.compressed_len>>3, s.compressed_len-7*eof)); + } + + // =========================================================================== + // Save the match info and tally the frequency counts. Return true if + // the current block must be flushed. + + // dist: distance of matched string + // lc: match length-MIN_MATCH or unmatched char (if dist==0) + static bool _tr_tally(deflate_state s, uint dist, uint lc) + { + s.d_buf[s.last_lit]=(ushort)dist; + s.l_buf[s.last_lit++]=(byte)lc; + if(dist==0) + { + // lc is the unmatched char + s.dyn_ltree[lc].Freq++; + } + else + { + s.matches++; + // Here, lc is the match length - MIN_MATCH + dist--; // dist = match distance - 1 + //Assert((ushort)dist < (ushort)MAX_DIST(s) && + // (ushort)lc <= (ushort)(MAX_MATCH-MIN_MATCH) && + // (ushort)(dist < 256 ? _dist_code[dist] : _dist_code[256+(dist>>7)]) < (ushort)D_CODES, + // "_tr_tally: bad match"); + + s.dyn_ltree[_length_code[lc]+LITERALS+1].Freq++; + s.dyn_dtree[(dist<256?_dist_code[dist]:_dist_code[256+(dist>>7)])].Freq++; + } + + return (s.last_lit==s.lit_bufsize-1); + // We avoid equality with lit_bufsize because of wraparound at 64K + // on 16 bit machines and because stored blocks are restricted to + // 64K-1 bytes. + } + + // =========================================================================== + // Send the block data compressed using the given Huffman trees + + // ltree: literal tree + // dtree: distance tree + static void compress_block(deflate_state s, ct_data[] ltree, ct_data[] dtree) + { + uint dist; // distance of matched string + int lc; // match length or unmatched char (if dist == 0) + uint lx=0; // running index in l_buf + uint code; // the code to send + int extra; // number of extra bits to send + + if(s.last_lit!=0) + { + do + { + dist=s.d_buf[lx]; + lc=s.l_buf[lx++]; + if(dist==0) + { + send_code(s, lc, ltree); // send a literal byte + //Tracecv(isgraph(lc), (stderr," '%c' ", lc)); + } + else + { + // Here, lc is the match length - MIN_MATCH + code=_length_code[lc]; + send_code(s, (int)(code+LITERALS+1), ltree); // send the length code + extra=extra_lbits[code]; + if(extra!=0) + { + lc-=base_length[code]; + send_bits(s, lc, extra); // send the extra length bits + } + dist--; // dist is now the match distance - 1 + code=(dist<256?_dist_code[dist]:_dist_code[256+(dist>>7)]); + //Assert (code < D_CODES, "bad d_code"); + + send_code(s, (int)code, dtree); // send the distance code + extra=extra_dbits[code]; + if(extra!=0) + { + dist-=(uint)base_dist[code]; + send_bits(s, (int)dist, extra); // send the extra distance bits + } + } // literal or match pair ? + } while(lx>=1; + res<<=1; + } while(--len>0); + return (ushort)(res>>1); + } + + // =========================================================================== + // Flush the bit buffer, keeping at most 7 bits in it. + static void bi_flush(deflate_state s) + { + if(s.bi_valid==16) + { + //was put_short(s, s.bi_buf); + s.pending_buf[s.pending++]=(byte)(s.bi_buf&0xff); + s.pending_buf[s.pending++]=(byte)((ushort)s.bi_buf>>8); + + s.bi_buf=0; + s.bi_valid=0; + } + else if(s.bi_valid>=8) + { + //was put_byte(s, (unsigned char)s.bi_buf); + s.pending_buf[s.pending++]=(byte)s.bi_buf; + + s.bi_buf>>=8; + s.bi_valid-=8; + } + } + + // =========================================================================== + // Flush the bit buffer and align the output on a byte boundary + static void bi_windup(deflate_state s) + { + if(s.bi_valid>8) + { + //was put_short(s, s.bi_buf); + s.pending_buf[s.pending++]=(byte)(s.bi_buf&0xff); + s.pending_buf[s.pending++]=(byte)((ushort)s.bi_buf>>8); + } + else if(s.bi_valid>0) + { + //was put_byte(s, (unsigned char)s.bi_buf); + s.pending_buf[s.pending++]=(byte)s.bi_buf; + } + s.bi_buf=0; + s.bi_valid=0; + } + + // =========================================================================== + // Copy a stored block, storing first the length and its + // one's complement if requested. + + // buf: the input data + // len: its length + // header: true if block header must be written + static void copy_block(deflate_state s, byte[] buf, int buf_ind, uint len, int header) + { + bi_windup(s); // align on byte boundary + s.last_eob_len=8; // enough lookahead for inflate + + if(header!=0) + { + //was put_short(s, (unsigned short)len); + s.pending_buf[s.pending++]=(byte)(((ushort)len)&0xff); + s.pending_buf[s.pending++]=(byte)(((ushort)len)>>8); + + //was put_short(s, (unsigned short)~len); + s.pending_buf[s.pending++]=(byte)(((ushort)~len)&0xff); + s.pending_buf[s.pending++]=(byte)(((ushort)~len)>>8); + } + + while(len--!=0) + { + //was put_byte(s, *buf++); + s.pending_buf[s.pending++]=buf[buf_ind++]; + } + } + } +} diff --git a/Framework/IO/Zlib/ZLib.cs b/Framework/IO/Zlib/ZLib.cs new file mode 100644 index 000000000..623d11931 --- /dev/null +++ b/Framework/IO/Zlib/ZLib.cs @@ -0,0 +1,171 @@ +// zlib.cs -- interface of the 'zlib' general purpose compression library +// Copyright (C) 1995-2010 Jean-loup Gailly and Mark Adler +// Copyright (C) 2007-2011 by the Authors +// For conditions of distribution and use, see copyright notice in License.txt + +using System; + +namespace Framework.IO +{ + public static partial class ZLib + { + // Maximum value for memLevel in deflateInit2 + private const int MAX_MEM_LEVEL=9; + + // Maximum value for windowBits in deflateInit2 and inflateInit2. + private const int MAX_WBITS=15; // 32K LZ77 window + private const int DEF_WBITS=MAX_WBITS; // default windowBits for decompression. MAX_WBITS is for compression only + + // The memory requirements for deflate are (in bytes): + // (1 << (windowBits+2)) + (1 << (memLevel+9)) + // that is: 128K for windowBits=15 + 128K for memLevel = 8 (default values) + // plus a few kilobytes for small objects. For example, if you want to reduce + // the default memory requirements from 256K to 128K, compile with + // make CFLAGS="-O -DMAX_WBITS=14 -DMAX_MEM_LEVEL=7" + // Of course this will generally degrade compression (there's no free lunch). + + // The memory requirements for inflate are (in bytes) 1 << windowBits + // that is, 32K for windowBits=15 (default value) plus a few kilobytes + // for small objects. + + private const int DEF_MEM_LEVEL=8; // default memLevel + + // The three kinds of block type + private const int STORED_BLOCK=0; + private const int STATIC_TREES=1; + private const int DYN_TREES=2; + + // The minimum and maximum match lengths + private const int MIN_MATCH=3; + private const int MAX_MATCH=258; + + public const string ZLIB_VERSION="1.2.5"; + public const uint ZLIB_VERNUM=0x1250; + + // The 'zlib' compression library provides in-memory compression and + // decompression functions, including integrity checks of the uncompressed + // data. This version of the library supports only one compression method + // (deflation) but other algorithms will be added later and will have the same + // stream interface. + // + // Compression can be done in a single step if the buffers are large enough, + // or can be done by repeated calls of the compression function. In the latter + // case, the application must provide more input and/or consume the output + // (providing more output space) before each call. + // + // The compressed data format used by default by the in-memory functions is + // the zlib format, which is a zlib wrapper documented in RFC 1950, wrapped + // around a deflate stream, which is itself documented in RFC 1951. + // + // The zlib format was designed to be compact and fast for use in memory + // and on communications channels. + // + // The library does not install any signal handler. The decoder checks + // the consistency of the compressed data, so the library should never crash + // even in case of corrupted input. + + public class z_stream + { + public byte[] in_buf; // input buffer + public uint next_in; // next input byte index + public uint avail_in; // number of bytes available at next_in + public uint total_in; // total nb of input bytes read so far + + public byte[] out_buf; // output buffer + public int next_out; // next output byte should be put there + public uint avail_out; // remaining free space at next_out + public uint total_out; // total nb of bytes output so far + + public string msg; // last error message, NULL if no error + public object state; // not visible by applications + + public uint adler; // adler32 value of the uncompressed data + + public void CopyTo(z_stream s) + { + s.adler=adler; + s.avail_in=avail_in; + s.avail_out=avail_out; + s.in_buf=in_buf; + s.msg=msg; + s.next_in=next_in; + s.next_out=next_out; + s.out_buf=out_buf; + s.state=state; + s.total_in=total_in; + s.total_out=total_out; + } + } + + // gzip header information passed to and from zlib routines. See RFC 1952 + // for more details on the meanings of these fields. + public class gz_header + { + public int text; // true if compressed data believed to be text + public uint time; // modification time + public int xflags; // extra flags (not used when writing a gzip file) + public int os; // operating system + public byte[] extra; // pointer to extra field or Z_NULL if none + public uint extra_len; // extra field length (valid if extra != Z_NULL) + public uint extra_max; // space at extra (only when reading header) + public byte[] name; // pointer to zero-terminated file name or Z_NULL + public uint name_max; // space at name (only when reading header) + public byte[] comment; // pointer to zero-terminated comment or Z_NULL + public uint comm_max; // space at comment (only when reading header) + public int hcrc; // true if there was or will be a header crc + public int done; // true when done reading gzip header (not used when writing a gzip file) + } + + // The application must update next_in and avail_in when avail_in has + // dropped to zero. It must update next_out and avail_out when avail_out + // has dropped to zero. All other fields are set by the + // compression library and must not be updated by the application. + // + // The fields total_in and total_out can be used for statistics or + // progress reports. After compression, total_in holds the total size of + // the uncompressed data and may be saved for use in the decompressor + // (particularly if the decompressor wants to decompress everything in + // a single step). + + // constants + + // Allowed flush values; see deflate() and inflate() below for details + public const int Z_NO_FLUSH=0; + public const int Z_PARTIAL_FLUSH=1; + public const int Z_SYNC_FLUSH=2; + public const int Z_FULL_FLUSH=3; + public const int Z_FINISH=4; + public const int Z_BLOCK=5; + public const int Z_TREES=6; + + // Return codes for the compression/decompression functions. Negative + // values are errors, positive values are used for special but normal events. + public const int Z_OK=0; + public const int Z_STREAM_END=1; + public const int Z_NEED_DICT=2; + public const int Z_ERRNO=-1; + public const int Z_STREAM_ERROR=-2; + public const int Z_DATA_ERROR=-3; + public const int Z_MEM_ERROR=-4; + public const int Z_BUF_ERROR=-5; + public const int Z_VERSION_ERROR=-6; + + // compression levels + public const int Z_NO_COMPRESSION=0; + public const int Z_BEST_SPEED=1; + public const int Z_BEST_COMPRESSION=9; + public const int Z_DEFAULT_COMPRESSION=(-1); + + // compression strategy; see deflateInit2() below for details + public const int Z_FILTERED=1; + public const int Z_HUFFMAN_ONLY=2; + public const int Z_RLE=3; + public const int Z_FIXED=4; + public const int Z_DEFAULT_STRATEGY=0; + + // The deflate compression method (the only one supported in this version) + public const int Z_DEFLATED=8; + + public const string zlib_version=ZLIB_VERSION; // for compatibility with versions < 1.0.2 + } +} diff --git a/Framework/IO/Zlib/ZUtil.cs b/Framework/IO/Zlib/ZUtil.cs new file mode 100644 index 000000000..6c546f045 --- /dev/null +++ b/Framework/IO/Zlib/ZUtil.cs @@ -0,0 +1,105 @@ +// zutil.cs -- target dependent utility functions for the compression library +// Copyright (C) 1995-2005, 2010 Jean-loup Gailly. +// Copyright (C) 2007-2011 by the Authors +// For conditions of distribution and use, see copyright notice in License.txt + +using System; + +namespace Framework.IO +{ + public static partial class ZLib + { + private const int OS_CODE=0x0b; + + static readonly string[] z_errmsg=new string[9] + { + "need dictionary", // Z_NEED_DICT 2 + "stream end", // Z_STREAM_END 1 + "", // Z_OK 0 + "file error", // Z_ERRNO -1 + "stream error", // Z_STREAM_ERROR -2 + "data error", // Z_DATA_ERROR -3 + "insufficient memory", // Z_MEM_ERROR -4 + "buffer error", // Z_BUF_ERROR -5 + "incompatible version" // Z_VERSION_ERROR -6 + }; + + // ========================================================================= + + // The application can compare zlibVersion and ZLIB_VERSION for consistency. + // If the first character differs, the library code actually used is + // not compatible with the zlib.h header file used by the application. + // This check is automatically made by deflateInit and inflateInit. + + public static string zlibVersion() + { + return ZLIB_VERSION; + } + + // ========================================================================= + + // Return flags indicating compile-time options. + // + // Type sizes, two bits each, 00 = 16 bits, 01 = 32, 10 = 64, 11 = other: + // 1.0: size of ulong + // 3.2: size of uint + // 5.4: size of void* (pointer) + // 7.6: size of int + // + //Compiler, assembler, and debug options: + // (8: DEBUG) + // (9: ASMV or ASMINF -- use ASM code [not used in this port]) + // (10: ZLIB_WINAPI -- exported functions use the WINAPI calling convention [not used in this port]) + // 11: 0 (reserved) + // + //One-time table building (smaller code, but not thread-safe if true): + // (12: BUILDFIXED -- build static block decoding tables when needed [not used in this port]) + // (13: DYNAMIC_CRC_TABLE -- build CRC calculation tables when needed [not used in this port]) + // 14,15: 0 (reserved) + // + //Library content (indicates missing functionality): + // 16: NO_GZCOMPRESS -- gz* functions cannot compress (to avoid linking deflate code when not needed) + // 17: NO_GZIP -- deflate can't write gzip streams, and inflate can't detect and decode gzip streams (to avoid linking crc code) + // 18-19: 0 (reserved) + // + //Operation variations (changes in library functionality): + // (20: PKZIP_BUG_WORKAROUND -- slightly more permissive inflate [not used in this port]) + // (21: FASTEST -- deflate algorithm with only one, lowest compression level [not used in this port]) + // 22,23: 0 (reserved) + // + //The sprintf variant used by gzprintf (zero is best): + // (24: 0 = vs*, 1 = s* -- 1 means limited to 20 arguments after the format [not used in this port]) + // (25: 0 = *nprintf, 1 = *printf -- 1 means gzprintf() not secure! [not used in this port]) + // (26: 0 = returns value, 1 = void -- 1 means inferred string length returned [not used in this port]) + // + //Remainder: + // 27-31: 0 (reserved) + + public static uint zlibCompileFlags() + { + uint flags=2; + flags+=1<<2; + + switch(IntPtr.Size) + { + case 4: flags+=1<<4; break; + case 8: flags+=2<<4; break; + default: flags+=3<<4; break; + } + + flags+=1<<6; + + return flags; + } + + // ========================================================================= + + // exported to allow conversion of error code to string for compress() and uncompress() + public static string zError(int err) + { + if(err<-6||err>2) throw new ArgumentOutOfRangeException("err", "must be -6<=err<=2"); + + return z_errmsg[2-err]; + } + } +} diff --git a/Framework/IO/Zlib/compress.cs b/Framework/IO/Zlib/compress.cs new file mode 100644 index 000000000..272729396 --- /dev/null +++ b/Framework/IO/Zlib/compress.cs @@ -0,0 +1,63 @@ +/* + * Copyright (C) 2012-2017 CypherCore + * + * 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, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +using System; +using System.IO; +using System.IO.Compression; +using System.Linq; +using System.Runtime.InteropServices; + +namespace Framework.IO +{ + public static partial class ZLib + { + public static byte[] Compress(byte[] data) + { + ByteBuffer buffer = new ByteBuffer(); + buffer.WriteUInt8(0x78); + buffer.WriteUInt8(0x9c); + + uint adler32 = ZLib.adler32(1, data, (uint)data.Length);// Adler32(1, data, (uint)data.Length); + var ms = new MemoryStream(); + using (var deflateStream = new DeflateStream(ms, CompressionMode.Compress)) + { + deflateStream.Write(data, 0, data.Length); + deflateStream.Flush(); + } + buffer.WriteBytes(ms.ToArray()); + buffer.WriteBytes(BitConverter.GetBytes(adler32).Reverse().ToArray()); + + return buffer.GetData(); + } + + public static byte[] Decompress(byte[] data, uint unpackedSize) + { + byte[] decompressData = new byte[unpackedSize]; + using (var deflateStream = new DeflateStream(new MemoryStream(data, 2, data.Length - 6), CompressionMode.Decompress)) + { + var decompressed = new MemoryStream(); + deflateStream.CopyTo(decompressed); + + decompressed.Seek(0, SeekOrigin.Begin); + for (int i = 0; i < unpackedSize; i++) + decompressData[i] = (byte)decompressed.ReadByte(); + } + + return decompressData; + } + } +} diff --git a/Framework/Logging/Appender.cs b/Framework/Logging/Appender.cs new file mode 100644 index 000000000..a166536e0 --- /dev/null +++ b/Framework/Logging/Appender.cs @@ -0,0 +1,238 @@ +/* + * Copyright (C) 2012-2017 CypherCore + * + * 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, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +using Framework.Database; +using System; +using System.IO; +using System.Text; + +class ConsoleAppender : Appender +{ + public ConsoleAppender(byte id, string name, LogLevel level, AppenderFlags flags) : base(id, name, level, flags) + { + _consoleColor = new[] + { + ConsoleColor.White, + ConsoleColor.White, + ConsoleColor.Gray, + ConsoleColor.Green, + ConsoleColor.Yellow, + ConsoleColor.Red, + ConsoleColor.Blue + }; + } + + public override void _write(LogMessage message) + { + Console.ForegroundColor = _consoleColor[(int)message.level]; + Console.WriteLine(message.prefix + message.text); + Console.ResetColor(); + } + + public override AppenderType GetAppenderType() + { + return AppenderType.Console; + } + + ConsoleColor[] _consoleColor; +} + +class FileAppender : Appender, IDisposable +{ + public FileAppender(byte id, string name, LogLevel level, string fileName, string logDir, AppenderFlags flags) : base(id, name, level, flags) + { + Directory.CreateDirectory(logDir); + _fileName = fileName; + _logDir = logDir; + _dynamicName = _fileName.Contains("{0}"); + + if (_dynamicName) + { + Directory.CreateDirectory(logDir + "/" + _fileName.Substring(0, _fileName.IndexOf('/') + 1)); + return; + } + + _logStream = OpenFile(_fileName, FileMode.Create); + } + + FileStream OpenFile(string filename, FileMode mode) + { + return new FileStream(_logDir + "/" + filename, mode, FileAccess.Write, FileShare.ReadWrite); + } + + public override void _write(LogMessage message) + { + lock (locker) + { + var logBytes = Encoding.Unicode.GetBytes(message.prefix + message.text + "\r\n"); + + if (_dynamicName) + { + var logStream = OpenFile(string.Format(_fileName, message.dynamicName), FileMode.Append); + logStream.Write(logBytes, 0, logBytes.Length); + logStream.Flush(); + logStream.Close(); + return; + } + + _logStream.Write(logBytes, 0, logBytes.Length); + _logStream.Flush(); + } + } + + public override AppenderType GetAppenderType() + { + return AppenderType.File; + } + + #region IDisposable Support + private bool disposedValue = false; + + protected virtual void Dispose(bool disposing) + { + if (!disposedValue) + { + if (disposing) + { + _logStream.Dispose(); + } + + disposedValue = true; + } + } + + public void Dispose() + { + Dispose(true); + } + #endregion + + string _fileName; + string _logDir; + bool _dynamicName; + FileStream _logStream; + object locker = new object(); +} + +class DBAppender : Appender +{ + public DBAppender(byte id, string name, LogLevel level) : base(id, name, level) { } + + public override void _write(LogMessage message) + { + // Avoid infinite loop, PExecute triggers Logging with "sql.sql" type + if (!enabled || message.type == LogFilter.Sql) + return; + + PreparedStatement stmt = DB.Login.GetPreparedStatement(LoginStatements.INS_LOG); + stmt.AddValue(0, Time.DateTimeToUnixTime(message.mtime)); + stmt.AddValue(1, realmId); + stmt.AddValue(2, message.type); + stmt.AddValue(3, (byte)message.level); + stmt.AddValue(4, message.text); + DB.Login.Execute(stmt); + } + + public override AppenderType GetAppenderType() + { + return AppenderType.DB; + } + + public override void setRealmId(uint _realmId) + { + enabled = true; + realmId = _realmId; + } + + uint realmId; + bool enabled; +} + +abstract class Appender +{ + public Appender(byte id, string name, LogLevel level = LogLevel.Disabled, AppenderFlags flags = AppenderFlags.None) + { + _id = id; + _name = name; + _level = level; + _flags = flags; + } + + public void Write(LogMessage message) + { + if (_level == LogLevel.Disabled || _level > message.level) + return; + + StringBuilder ss = new StringBuilder(); + + if (_flags.HasAnyFlag(AppenderFlags.PrefixTimestamp)) + ss.AppendFormat("{0} ", message.mtime.ToString("MM/dd/yyyy HH:mm:ss")); + + if (_flags.HasAnyFlag(AppenderFlags.PrefixLogLevel)) + ss.AppendFormat("{0}: ", message.level); + + if (_flags.HasAnyFlag(AppenderFlags.PrefixLogFilterType)) + ss.AppendFormat("[{0}] ", message.type); + + message.prefix = ss.ToString(); + _write(message); + } + + public abstract void _write(LogMessage message); + + public byte getId() + { + return _id; + } + + public string getName() + { + return _name; + } + + public abstract AppenderType GetAppenderType(); + + public virtual void setRealmId(uint realmId) { } + + public void setLogLevel(LogLevel level) + { + _level = level; + } + + byte _id; + string _name; + LogLevel _level; + AppenderFlags _flags; +} + +class LogMessage +{ + public LogMessage(LogLevel _level, LogFilter _type, string _text) + { + level = _level; + type = _type; + text = _text; + mtime = DateTime.Now; + } + + public LogLevel level; + public LogFilter type; + public string text; + public string prefix; + public string dynamicName; + public DateTime mtime; +} diff --git a/Framework/Logging/Log.cs b/Framework/Logging/Log.cs new file mode 100644 index 000000000..7895098ea --- /dev/null +++ b/Framework/Logging/Log.cs @@ -0,0 +1,436 @@ +/* + * Copyright (C) 2012-2017 CypherCore + * + * 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, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +using Framework.Collections; +using Framework.Configuration; +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.Linq; +using System.Runtime.CompilerServices; + +public class Log +{ + static Log() + { + m_logsDir = ConfigMgr.GetDefaultValue("LogsDir", ""); + + foreach (var appenderName in ConfigMgr.GetKeysByString("Appender.")) + { + CreateAppenderFromConfig(appenderName); + } + + foreach (var loggerName in ConfigMgr.GetKeysByString("Logger.")) + { + CreateLoggerFromConfig(loggerName); + } + + // Bad config configuration, creating default config + if (!loggers.ContainsKey(LogFilter.Server)) + { + Console.WriteLine("Wrong Loggers configuration. Review your Logger config section.\nCreating default loggers [Server (Info)] to console\n"); + + loggers.Clear(); + appenders.Clear(); + + byte id = NextAppenderId(); + + var appender = new ConsoleAppender(id, "Console", LogLevel.Debug, AppenderFlags.None); + appenders[id] = appender; + + var serverLogger = new Logger("Server", LogLevel.Error); + serverLogger.addAppender(id, appender); + loggers[LogFilter.Server] = serverLogger; + } + + outInfo(LogFilter.Server, @" ____ __ "); + outInfo(LogFilter.Server, @"/\ _`\ /\ \ "); + outInfo(LogFilter.Server, @"\ \ \/\_\ __ __ _____\ \ \___ __ _ __ "); + outInfo(LogFilter.Server, @" \ \ \/_/_/\ \/\ \/\ '__`\ \ _ `\ /'__`\/\`'__\"); + outInfo(LogFilter.Server, @" \ \ \L\ \ \ \_\ \ \ \L\ \ \ \ \ \/\ __/\ \ \/ "); + outInfo(LogFilter.Server, @" \ \____/\/`____ \ \ ,__/\ \_\ \_\ \____\\ \_\ "); + outInfo(LogFilter.Server, @" \/___/ `/___/> \ \ \/ \/_/\/_/\/____/ \/_/ "); + outInfo(LogFilter.Server, @" /\___/\ \_\ "); + outInfo(LogFilter.Server, @" \/__/ \/_/ Core"); + outInfo(LogFilter.Server, "\r"); + } + + static bool ShouldLog(LogFilter type, LogLevel level) + { + Logger logger = GetLoggerByType(type); + if (logger == null) + return false; + + LogLevel logLevel = logger.getLogLevel(); + return logLevel != LogLevel.Disabled && logLevel <= level; + } + + public static void outInfo(LogFilter type, string text, params object[] args) + { + if (!ShouldLog(type, LogLevel.Info)) + return; + + outMessage(type, LogLevel.Info, text, args); + } + + public static void outWarn(LogFilter type, string text, params object[] args) + { + if (!ShouldLog(type, LogLevel.Warn)) + return; + + outMessage(type, LogLevel.Warn, text, args); + } + + [Conditional("DEBUG")] + public static void outDebug(LogFilter type, string text, params object[] args) + { + if (!ShouldLog(type, LogLevel.Debug)) + return; + + outMessage(type, LogLevel.Debug, text, args); + } + + public static void outError(LogFilter type, string text, params object[] args) + { + if (!ShouldLog(type, LogLevel.Error)) + return; + + outMessage(type, LogLevel.Error, text, args); + } + + public static void outException(Exception ex, [CallerMemberName]string memberName = "") + { + if (!ShouldLog(LogFilter.Server, LogLevel.Fatal)) + return; + + outMessage(LogFilter.Server, LogLevel.Fatal, "MemberName: {0} ExceptionMessage: {1}", memberName, ex.Message); + } + + public static void outFatal(LogFilter type, string text, params object[] args) + { + if (!ShouldLog(type, LogLevel.Fatal)) + return; + + outMessage(type, LogLevel.Fatal, text, args); + } + + public static void outTrace(LogFilter type, string text, params object[] args) + { + if (!ShouldLog(type, LogLevel.Trace)) + return; + + outMessage(type, LogLevel.Trace, text, args); + } + + public static void outCommand(uint accountId, string text, params object[] args) + { + if (!ShouldLog(LogFilter.Commands, LogLevel.Info)) + return; + + var msg = new LogMessage(LogLevel.Info, LogFilter.Commands, string.Format(text, args)); + msg.dynamicName = accountId.ToString(); + + Logger logger = GetLoggerByType(LogFilter.Commands); + logger.write(msg); + } + + static void outMessage(LogFilter type, LogLevel level, string text, params object[] args) + { + Logger logger = GetLoggerByType(type); + logger.write(new LogMessage(level, type, string.Format(text, args))); + } + + static byte NextAppenderId() + { + return AppenderId++; + } + + static void CreateAppenderFromConfig(string appenderName) + { + if (string.IsNullOrEmpty(appenderName)) + return; + + string options = ConfigMgr.GetDefaultValue(appenderName, ""); + var tokens = new StringArray(options, ','); + string name = appenderName.Substring(9); + + if (tokens.Length < 2) + { + Console.WriteLine("Log.CreateAppenderFromConfig: Wrong configuration for appender {0}. Config line: {1}", name, options); + return; + } + + AppenderFlags flags = AppenderFlags.None; + AppenderType type = (AppenderType)uint.Parse(tokens[0]); + LogLevel level = (LogLevel)uint.Parse(tokens[1]); + + if (level > LogLevel.Fatal) + { + Console.WriteLine("Log.CreateAppenderFromConfig: Wrong Log Level {0} for appender {1}\n", level, name); + return; + } + + if (tokens.Length > 2) + flags = (AppenderFlags)uint.Parse(tokens[2]); + + byte id = NextAppenderId(); + switch (type) + { + case AppenderType.Console: + { + var appender = new ConsoleAppender(id, name, level, flags); + appenders[id] = appender; + break; + } + case AppenderType.File: + { + string filename = ""; + if (tokens.Length < 4) + { + if (name != "Server") + { + Console.WriteLine("Log.CreateAppenderFromConfig: Missing file name for appender {0}", name); + return; + } + + filename = Process.GetCurrentProcess().ProcessName + ".log"; + } + else + filename = tokens[3]; + + appenders[id] = new FileAppender(id, name, level, filename, m_logsDir, flags); + break; + } + case AppenderType.DB: + { + appenders[id] = new DBAppender(id, name, level); + break; + } + default: + Console.WriteLine("Log.CreateAppenderFromConfig: Unknown type {0} for appender {1}", type, name); + break; + } + } + + static void CreateLoggerFromConfig(string appenderName) + { + if (string.IsNullOrEmpty(appenderName)) + return; + + LogLevel level = LogLevel.Disabled; + string name = appenderName.Substring(7); + + string options = ConfigMgr.GetDefaultValue(appenderName, ""); + if (string.IsNullOrEmpty(options)) + { + Console.WriteLine("Log.CreateLoggerFromConfig: Missing config option Logger.{0}", name); + return; + } + var tokens = new StringArray(options, ','); + + LogFilter type = name.ToEnum(); + if (loggers.ContainsKey(type)) + { + Console.WriteLine("Error while configuring Logger {0}. Already defined", name); + return; + } + + level = (LogLevel)uint.Parse(tokens[0]); + if (level > LogLevel.Fatal) + { + Console.WriteLine("Log.CreateLoggerFromConfig: Wrong Log Level {0} for logger {1}", type, name); + return; + } + + Logger logger = new Logger(name, level); + + int i = 0; + var ss = new StringArray(tokens[1], ' '); + while (i < ss.Length) + { + var str = ss[i++]; + Appender appender = GetAppenderByName(str); + if (appender == null) + Console.WriteLine("Error while configuring Appender {0} in Logger {1}. Appender does not exist", str, name); + else + logger.addAppender(appender.getId(), appender); + } + + loggers[type] = logger; + } + + static Appender GetAppenderByName(string name) + { + return appenders.First(p => p.Value.getName() == name).Value; + } + + static Logger GetLoggerByType(LogFilter type) + { + if (loggers.ContainsKey(type)) + return loggers[type]; + + string typeString = type.ToString(); + + int index = 1; + for (; index < typeString.Length - 1; ++index) + { + if (char.IsUpper(typeString[index])) + break; + } + + typeString = typeString.Substring(0, index); + if (typeString.IsEmpty()) + return null; + + LogFilter parentLogger; + if (!Enum.TryParse(typeString, out parentLogger)) + return null; + + return GetLoggerByType(parentLogger); + } + + public static bool SetLogLevel(string name, string newLevelc, bool isLogger = true) + { + LogLevel newLevel = (LogLevel)uint.Parse(newLevelc); + if (newLevel < 0) + return false; + + if (isLogger) + { + foreach (var logger in loggers.Values) + { + if (logger.getName() == name) + { + logger.setLogLevel(newLevel); + return true; + } + } + return false; + } + else + { + Appender appender = GetAppenderByName(name); + if (appender == null) + return false; + + appender.setLogLevel(newLevel); + } + + return true; + } + + public static void SetRealmId(uint id) + { + foreach (var appender in appenders.Values) + appender.setRealmId(id); + } + + static string GetLogsDir() { return m_logsDir; } + + static Dictionary appenders = new Dictionary(); + static Dictionary loggers = new Dictionary(); + static string m_logsDir; + static byte AppenderId; +} + +enum AppenderType +{ + None, + Console, + File, + DB +} + +[Flags] +enum AppenderFlags +{ + None = 0x00, + PrefixTimestamp = 0x01, + PrefixLogLevel = 0x02, + PrefixLogFilterType = 0x04, +} + +enum LogLevel +{ + Disabled = 0, + Trace = 1, + Debug = 2, + Info = 3, + Warn = 4, + Error = 5, + Fatal = 6, + Max = 6 +} + +public enum LogFilter +{ + Misc, + Achievement, + Addon, + Ahbot, + AreaTrigger, + Arena, + Auctionhouse, + Battlefield, + Battleground, + BattlegroundReportPvpAfk, + Calendar, + ChatLog, + ChatSystem, + Cheat, + Commands, + Condition, + Garrison, + Gameevent, + Guild, + Lfg, + Loot, + MapsScript, + Maps, + Movement, + Network, + Outdoorpvp, + Pet, + Player, + PlayerCharacter, + PlayerDump, + PlayerItems, + PlayerLoading, + PlayerSkills, + Pool, + Rbac, + Realmlist, + Scenario, + Scenes, + Scripts, + ScriptsAi, + Server, + ServerLoading, + ServiceProtobuf, + Session, + SessionRpc, + Spells, + SpellsPeriodic, + Sql, + SqlDev, + SqlDriver, + SqlUpdates, + Transport, + Unit, + Vehicle, + Warden, +} \ No newline at end of file diff --git a/Framework/Logging/Logger.cs b/Framework/Logging/Logger.cs new file mode 100644 index 000000000..b5f373ae1 --- /dev/null +++ b/Framework/Logging/Logger.cs @@ -0,0 +1,65 @@ +/* + * Copyright (C) 2012-2017 CypherCore + * + * 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, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +using System.Collections.Generic; + +class Logger +{ + public Logger(string _name, LogLevel _level) + { + name = _name; + level = _level; + } + + public void addAppender(byte id, Appender appender) + { + appenders[id] = appender; + } + + public void delAppender(byte id) + { + appenders.Remove(id); + } + + public void setLogLevel(LogLevel _level) + { + level = _level; + } + + public string getName() + { + return name; + } + + public LogLevel getLogLevel() + { + return level; + } + + public void write(LogMessage message) + { + if (level == 0 || level > message.level || string.IsNullOrEmpty(message.text)) + return; + + foreach (var appender in appenders.Values) + appender.Write(message); + } + + string name; + LogLevel level; + Dictionary appenders = new Dictionary(); +} diff --git a/Framework/Logging/Metric.cs b/Framework/Logging/Metric.cs new file mode 100644 index 000000000..f070a2a65 --- /dev/null +++ b/Framework/Logging/Metric.cs @@ -0,0 +1,273 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using System.Timers; + +class Metric +{ + void Initialize(string realmName, Action overallStatusLogger) + { + _realmName = realmName; + _batchTimer = new Timer(); + _overallStatusTimer = new Timer(); + _overallStatusLogger = overallStatusLogger; + LoadFromConfigs(); + } + + bool Connect() + { + _dataStream.Connect(_hostname, _port); + auto error = _dataStream.Client.error(); + if (error) + { + TC_LOG_ERROR("metric", "Error connecting to '%s:%s', disabling Metric. Error message : %s", + _hostname.c_str(), _port.c_str(), error.message().c_str()); + _enabled = false; + return false; + } + _dataStream.clear(); + return true; + } + + void LoadFromConfigs() + { + bool previousValue = _enabled; + _enabled = sConfigMgr.GetBoolDefault("Metric.Enable", false); + _updateInterval = sConfigMgr.GetIntDefault("Metric.Interval", 10); + if (_updateInterval < 1) + { + TC_LOG_ERROR("metric", "'Metric.Interval' config set to %d, overriding to 1.", _updateInterval); + _updateInterval = 1; + } + + _overallStatusTimerInterval = sConfigMgr.GetIntDefault("Metric.OverallStatusInterval", 1); + if (_overallStatusTimerInterval < 1) + { + TC_LOG_ERROR("metric", "'Metric.OverallStatusInterval' config set to %d, overriding to 1.", _overallStatusTimerInterval); + _overallStatusTimerInterval = 1; + } + + // Schedule a send at this point only if the config changed from Disabled to Enabled. + // Cancel any scheduled operation if the config changed from Enabled to Disabled. + if (_enabled && !previousValue) + { + std::string connectionInfo = sConfigMgr.GetStringDefault("Metric.ConnectionInfo", ""); + if (connectionInfo.empty()) + { + TC_LOG_ERROR("metric", "'Metric.ConnectionInfo' not specified in configuration file."); + return; + } + + Tokenizer tokens(connectionInfo, ';'); + if (tokens.size() != 3) + { + TC_LOG_ERROR("metric", "'Metric.ConnectionInfo' specified with wrong format in configuration file."); + return; + } + + _hostname.assign(tokens[0]); + _port.assign(tokens[1]); + _databaseName.assign(tokens[2]); + Connect(); + + ScheduleSend(); + ScheduleOverallStatusLog(); + } + } + + void Update() + { + if (_overallStatusTimerTriggered) + { + _overallStatusTimerTriggered = false; + _overallStatusLogger(); + } + } + + void LogEvent(string category, string title, string description) + { + MetricData data = new MetricData(); + data.Category = category; + data.Timestamp = system_clock::now(); + data.Type = METRIC_DATA_EVENT; + data.Title = title; + data.Text = description; + + _queuedData.Enqueue(data); + } + + void SendBatch() + { + std::stringstream batchedData; + MetricData data; + bool firstLoop = true; + while (_queuedData.Dequeue(data)) + { + if (!firstLoop) + batchedData << "\n"; + + batchedData << data.Category; + if (!_realmName.empty()) + batchedData << ",realm=" << _realmName; + + batchedData << " "; + + switch (data.Type) + { + case METRIC_DATA_VALUE: + batchedData << "value=" << data.Value; + break; + case METRIC_DATA_EVENT: + batchedData << "title=\"" << data.Title << "\",text=\"" << data.Text << "\""; + break; + } + + batchedData << " "; + + batchedData << std::to_string(duration_cast(data.Timestamp.time_since_epoch()).count()); + + firstLoop = false; + delete data; + } + + // Check if there's any data to send + if (batchedData.tellp() == std::streampos(0)) + { + ScheduleSend(); + return; + } + + if (!_dataStream.good() && !Connect()) + return; + + var blah = new InfluxDB.Net.InfluxDb(_hostname, "root", "root", InfluxDB.Net.Enums.InfluxVersion.v010x); + InfluxDB.Net.Models.Serie b = new InfluxDB.Net.Models.Serie(); + b.Columns + _dataStream.UploadString( << "POST " << "/write?db=" << _databaseName << " HTTP/1.1\r\n"; + _dataStream << "Host: " << _hostname << ":" << _port << "\r\n"; + _dataStream << "Accept: */*\r\n"; + _dataStream << "Content-Type: application/octet-stream\r\n"; + _dataStream << "Content-Transfer-Encoding: binary\r\n"; + + _dataStream << "Content-Length: " << std::to_string(batchedData.tellp()) << "\r\n\r\n"; + _dataStream << batchedData.rdbuf(); + + std::string http_version; + _dataStream >> http_version; + unsigned int status_code = 0; + _dataStream >> status_code; + if (status_code != 204) + { + TC_LOG_ERROR("metric", "Error sending data, returned HTTP code: %u", status_code); + } + + // Read and ignore the status description + std::string status_description; + std::getline(_dataStream, status_description); + // Read headers + std::string header; + while (std::getline(_dataStream, header) && header != "\r") + { + if (header == "Connection: close\r") + _dataStream.close(); + } + + ScheduleSend(); + } + + void ScheduleSend() + { + if (_enabled) + { + _batchTimer.expires_from_now(boost::posix_time::seconds(_updateInterval)); + _batchTimer.async_wait(std::bind(&SendBatch, this)); + } + else + { + _dataStream.close(); + MetricData* data; + // Clear the queue + while (_queuedData.Dequeue(data)) + ; + } + } + + void ForceSend() + { + // Send what's queued only if io_service is stopped (so only on shutdown) + if (_enabled && _batchTimer.get_io_service().stopped()) + SendBatch(); + } + + void ScheduleOverallStatusLog() + { + if (_enabled) + { + _overallStatusTimer.expires_from_now(boost::posix_time::seconds(_overallStatusTimerInterval)); + _overallStatusTimer.async_wait([this](const boost::system::error_code&) + { + _overallStatusTimerTriggered = true; + ScheduleOverallStatusLog(); + }); + } + } + + static string FormatInfluxDBValue(T value) { return Convert.ToString(value) + 'i'; } + + static string FormatInfluxDBValue(string value) + { + return '"' + boost::replace_all_copy(value, "\"", "\\\"") + '"'; + } + static string FormatInfluxDBValue(bool value) { return value ? "t" : "f"; } + static string FormatInfluxDBValue(char[] value) { return FormatInfluxDBValue(new string(value)); } + static string FormatInfluxDBValue(double value) { return Convert.ToString(value); } + static string FormatInfluxDBValue(float value) { return FormatInfluxDBValue((double)value); } + + void LogValue(string category, T value) + { + MetricData data = new MetricData(); + data.Category = category; + data.Timestamp = DateTime.Now; + data.Type = MetricDataType.Value; + data.Value = FormatInfluxDBValue(value); + + _queuedData.Enqueue(data); + } + + System.Net.WebClient _dataStream; + MPSCQueue _queuedData; + Timer _batchTimer; + Timer _overallStatusTimer; + int _updateInterval = 0; + int _overallStatusTimerInterval = 0; + bool _enabled = false; + bool _overallStatusTimerTriggered = false; + string _hostname; + string _port; + string _databaseName; + Action _overallStatusLogger; + string _realmName; +} + +struct MetricData +{ + public string Category; + public DateTime Timestamp; + public MetricDataType Type; + + // LogValue-specific fields + public string Value; + + // LogEvent-specific fields + public string Title; + public string Text; +} + +enum MetricDataType +{ + Value, + Event +} + diff --git a/Framework/Networking/AsyncAcceptor.cs b/Framework/Networking/AsyncAcceptor.cs new file mode 100644 index 000000000..4f8199d82 --- /dev/null +++ b/Framework/Networking/AsyncAcceptor.cs @@ -0,0 +1,77 @@ +/* + * Copyright (C) 2012-2017 CypherCore + * + * 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, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +using System; +using System.Net; +using System.Net.Sockets; + +namespace Framework.Networking +{ + public delegate void SocketAcceptDelegate(Socket newSocket); + + public class AsyncAcceptor + { + public AsyncAcceptor(string ip, int port) + { + var bindIP = IPAddress.None; + if (!IPAddress.TryParse(ip, out bindIP)) + { + Log.outError(LogFilter.Network, "Server can't be started: Invalid IP-Address ({0})", ip); + return; + } + + try + { + _listener = new TcpListener(bindIP, port); + _listener.Start(); + } + catch (SocketException ex) + { + Log.outException(ex); + } + } + + public async void AsyncAcceptSocket(SocketAcceptDelegate mgrHandler) + { + try + { + var _socket = await _listener.AcceptSocketAsync(); + if (_socket != null) + { + mgrHandler(_socket); + + if (!_closed) + AsyncAcceptSocket(mgrHandler); + } + } + catch (ObjectDisposedException) + { } + } + + public void Close() + { + if (_closed) + return; + + _closed = true; + _listener.Stop(); + } + + TcpListener _listener; + volatile bool _closed; + } +} diff --git a/Framework/Networking/NetworkThread.cs b/Framework/Networking/NetworkThread.cs new file mode 100644 index 000000000..a35fd6ff5 --- /dev/null +++ b/Framework/Networking/NetworkThread.cs @@ -0,0 +1,121 @@ +/* + * Copyright (C) 2012-2017 CypherCore + * + * 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, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +using System.Collections.Generic; +using System.Linq; +using System.Threading; + +namespace Framework.Networking +{ + public class NetworkThread where SocketType : ISocket + { + public void Stop() + { + _stopped = true; + } + + public bool Start() + { + if (_thread != null) + return false; + + _thread = new Thread(Run); + _thread.Start(); + return true; + } + + public int GetConnectionCount() + { + return _connections; + } + + public virtual void AddSocket(SocketType sock) + { + Interlocked.Increment(ref _connections); + _newSockets.Add(sock); + SocketAdded(sock); + } + + public virtual void SocketAdded(SocketType sock) { } + public virtual void SocketRemoved(SocketType sock) { } + + void AddNewSockets() + { + if (_newSockets.Empty()) + return; + + foreach (var socket in _newSockets) + { + if (!socket.IsOpen()) + { + SocketRemoved(socket); + + Interlocked.Decrement(ref _connections); + } + else + _Sockets.Add(socket); + } + + _newSockets.Clear(); + } + + void Run() + { + Log.outDebug(LogFilter.Network, "Network Thread Starting"); + + int sleepTime = 10; + uint tickStart = 0, diff = 0; + while (!_stopped) + { + Thread.Sleep(sleepTime); + + tickStart = Time.GetMSTime(); + + AddNewSockets(); + + foreach (var socket in _Sockets.ToList()) + { + if (!socket.Update()) + { + if (socket.IsOpen()) + socket.CloseSocket(); + + SocketRemoved(socket); + + --_connections; + _Sockets.Remove(socket); + } + } + + diff = Time.GetMSTimeDiffToNow(tickStart); + sleepTime = (int)(diff > 10 ? 0 : 10 - diff); + } + + Log.outDebug(LogFilter.Misc, "Network Thread exits"); + _newSockets.Clear(); + _Sockets.Clear(); + } + + volatile int _connections; + volatile bool _stopped; + + Thread _thread; + + List _Sockets = new List(); + List _newSockets = new List(); + } +} diff --git a/Framework/Networking/SSLSocket.cs b/Framework/Networking/SSLSocket.cs new file mode 100644 index 000000000..55139ec89 --- /dev/null +++ b/Framework/Networking/SSLSocket.cs @@ -0,0 +1,168 @@ +/* + * Copyright (C) 2012-2017 CypherCore + * + * 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, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +using System; +using System.Net; +using System.Net.Security; +using System.Net.Sockets; +using System.Security.Cryptography.X509Certificates; + +namespace Framework.Networking +{ + public abstract class SSLSocket : ISocket + { + public SSLSocket(Socket socket) + { + _socket = socket; + _remoteAddress = ((IPEndPoint)_socket.RemoteEndPoint).Address; + _remotePort = (ushort)((IPEndPoint)_socket.RemoteEndPoint).Port; + _receiveBuffer = new byte[ushort.MaxValue]; + + _stream = new SslStream(new NetworkStream(socket), false); + } + + public abstract void Start(); + + public virtual bool Update() + { + return IsOpen(); + } + + public IPAddress GetRemoteIpAddress() + { + return _remoteAddress; + } + + public ushort GetRemotePort() + { + return _remotePort; + } + + public void AsyncRead() + { + if (!IsOpen()) + return; + + try + { + _stream.BeginRead(_receiveBuffer, 0, _receiveBuffer.Length, ReadHandlerInternal, _stream); + } + catch (Exception ex) + { + Log.outException(ex); + } + } + + void ReadHandlerInternal(IAsyncResult result) + { + int bytes = 0; + try + { + bytes = _stream.EndRead(result); + } + catch (Exception ex) + { + Log.outException(ex); + } + + ReadHandler(bytes); + } + + public abstract void ReadHandler(int transferredBytes); + + public void AsyncHandshake(X509Certificate2 certificate) + { + try + { + _stream.AuthenticateAsServer(certificate, false, System.Security.Authentication.SslProtocols.Tls12, false); + } + catch(Exception ex) + { + Log.outException(ex); + CloseSocket(); + return; + } + AsyncRead(); + } + + public void AsyncWrite(byte[] data) + { + if (!IsOpen()) + return; + + try + { + _stream.BeginWrite(data, 0, data.Length, WriteHandlerInternal, _stream); + } + catch (Exception ex) + { + Log.outException(ex); + } + } + + void WriteHandlerInternal(IAsyncResult result) + { + try + { + _stream.EndWrite(result); + } + catch (Exception ex) + { + Log.outException(ex); + } + } + + public void CloseSocket() + { + try + { + _closed = true; + _socket.Shutdown(SocketShutdown.Both); + _socket.Close(); + } + catch (Exception ex) + { + Log.outDebug(LogFilter.Network, "WorldSocket.CloseSocket: {0} errored when shutting down socket: {1}", GetRemoteIpAddress().ToString(), ex.Message); + } + + OnClose(); + } + + public void SetNoDelay(bool enable) + { + _socket.SetSocketOption(SocketOptionLevel.Tcp, SocketOptionName.NoDelay, enable); + } + + public virtual void OnClose() { } + + public bool IsOpen() { return !_closed; } + + public byte[] GetReceiveBuffer() + { + return _receiveBuffer; + } + + Socket _socket; + internal SslStream _stream; + byte[] _receiveBuffer; + + volatile bool _closed; + + IPAddress _remoteAddress; + ushort _remotePort; + } +} diff --git a/Framework/Networking/SocketBase.cs b/Framework/Networking/SocketBase.cs new file mode 100644 index 000000000..4dfa7c1bb --- /dev/null +++ b/Framework/Networking/SocketBase.cs @@ -0,0 +1,185 @@ +/* + * Copyright (C) 2012-2017 CypherCore + * + * 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, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +using System; +using System.Net; +using System.Net.Sockets; + +namespace Framework.Networking +{ + public abstract class SocketBase : ISocket, IDisposable + { + public SocketBase(Socket socket) + { + _socket = socket; + _remoteAddress = ((IPEndPoint)_socket.RemoteEndPoint).Address; + _remotePort = (ushort)((IPEndPoint)_socket.RemoteEndPoint).Port; + _receiveBuffer = new byte[ushort.MaxValue]; + } + + public virtual void Dispose() { } + + public abstract void Start(); + + public virtual bool Update() + { + return IsOpen(); + } + + public IPAddress GetRemoteIpAddress() + { + return _remoteAddress; + } + + public ushort GetRemotePort() + { + return _remotePort; + } + + public void AsyncRead() + { + if (!IsOpen()) + return; + + try + { + var socketEventargs = new SocketAsyncEventArgs(); + + socketEventargs.SetBuffer(_receiveBuffer, 0, _receiveBuffer.Length); + + socketEventargs.Completed += ReadHandlerInternal; + socketEventargs.UserToken = _socket; + socketEventargs.SocketFlags = SocketFlags.None; + + _socket.ReceiveAsync(socketEventargs); + } + catch (Exception ex) + { + Log.outException(ex); + } + } + + public delegate void SocketReadCallback(object sender, SocketAsyncEventArgs args); + public void AsyncReadWithCallback(SocketReadCallback callback) + { + if (!IsOpen()) + return; + + try + { + var socketEventargs = new SocketAsyncEventArgs(); + + socketEventargs.SetBuffer(_receiveBuffer, 0, _receiveBuffer.Length); + + socketEventargs.Completed += new EventHandler(callback); + socketEventargs.UserToken = _socket; + socketEventargs.SocketFlags = SocketFlags.None; + + _socket.ReceiveAsync(socketEventargs); + } + catch (Exception ex) + { + Log.outException(ex); + } + } + + void ReadHandlerInternal(object sender, SocketAsyncEventArgs args) + { + args.Completed -= ReadHandlerInternal; + if (args.SocketError != SocketError.Success) + { + CloseSocket(); + return; + } + + if (args.BytesTransferred == 0) + { + CloseSocket(); + return; + } + + ReadHandler(args.BytesTransferred); + } + + public abstract void ReadHandler(int transferredBytes); + + public void AsyncWrite(byte[] data) + { + if (!IsOpen()) + return; + + var socketEventargs = new SocketAsyncEventArgs(); + socketEventargs.SetBuffer(data, 0, data.Length); + socketEventargs.Completed += WriteHandlerInternal; + socketEventargs.RemoteEndPoint = _socket.RemoteEndPoint; + socketEventargs.SocketFlags = SocketFlags.None; + + _socket.SendAsync(socketEventargs); + } + + void WriteHandlerInternal(object sender, SocketAsyncEventArgs args) + { + args.Completed -= WriteHandlerInternal; + } + + public void CloseSocket() + { + try + { + _closed = true; + _socket.Shutdown(SocketShutdown.Both); + _socket.Close(); + } + catch (Exception ex) + { + Log.outDebug(LogFilter.Network, "WorldSocket.CloseSocket: {0} errored when shutting down socket: {1}", GetRemoteIpAddress().ToString(), ex.Message); + } + + OnClose(); + } + + public void SetNoDelay(bool enable) + { + _socket.SetSocketOption(SocketOptionLevel.Tcp, SocketOptionName.NoDelay, enable); + } + + public virtual void OnClose() { } + + public bool IsOpen() { return !_closed; } + + public byte[] GetReceiveBuffer() + { + return _receiveBuffer; + } + + Socket _socket; + byte[] _receiveBuffer; + + volatile bool _closed; + + IPAddress _remoteAddress; + ushort _remotePort; + } + + public interface ISocket + { + void Start(); + bool Update(); + bool IsOpen(); + void CloseSocket(); + } +} diff --git a/Framework/Networking/SocketManager.cs b/Framework/Networking/SocketManager.cs new file mode 100644 index 000000000..2f7b4c06c --- /dev/null +++ b/Framework/Networking/SocketManager.cs @@ -0,0 +1,100 @@ +/* + * Copyright (C) 2012-2017 CypherCore + * + * 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, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +using System; +using System.Diagnostics.Contracts; +using System.Net.Sockets; + +namespace Framework.Networking +{ + public class SocketManager where SocketType : ISocket + { + public virtual bool StartNetwork(string bindIp, int port, int threadCount = 1) + { + Contract.Assert(threadCount > 0); + + Acceptor = new AsyncAcceptor(bindIp, port); + + _threadCount = threadCount; + _threads = new NetworkThread[GetNetworkThreadCount()]; + + for (int i = 0; i < _threadCount; ++i) + { + _threads[i] = new NetworkThread(); + _threads[i].Start(); + } + + Acceptor.AsyncAcceptSocket(OnSocketOpen); + + return true; + } + + public virtual void StopNetwork() + { + Acceptor.Close(); + + if (_threadCount != 0) + for (int i = 0; i < _threadCount; ++i) + _threads[i].Stop(); + + Wait(); + + Acceptor = null; + _threads = null; + _threadCount = 0; + } + + void Wait() + { + //if (_threadCount != 0) + //for (int i = 0; i < _threadCount; ++i) + //_threads[i].Wait(); + } + + public virtual void OnSocketOpen(Socket sock) + { + try + { + SocketType newSocket = (SocketType)Activator.CreateInstance(typeof(SocketType), sock); + newSocket.Start(); + + _threads[SelectThreadWithMinConnections()].AddSocket(newSocket); + } + catch (Exception err) + { + Log.outException(err); + } + } + + public int GetNetworkThreadCount() { return _threadCount; } + + uint SelectThreadWithMinConnections() + { + uint min = 0; + + for (uint i = 1; i < _threadCount; ++i) + if (_threads[i].GetConnectionCount() < _threads[min].GetConnectionCount()) + min = i; + + return min; + } + + public AsyncAcceptor Acceptor; + NetworkThread[] _threads; + int _threadCount; + } +} diff --git a/Framework/Properties/AssemblyInfo.cs b/Framework/Properties/AssemblyInfo.cs new file mode 100644 index 000000000..bbe782acd --- /dev/null +++ b/Framework/Properties/AssemblyInfo.cs @@ -0,0 +1,36 @@ +using System.Reflection; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; + +// General Information about an assembly is controlled through the following +// set of attributes. Change these attribute values to modify the information +// associated with an assembly. +[assembly: AssemblyTitle("Framework")] +[assembly: AssemblyDescription("")] +[assembly: AssemblyConfiguration("")] +[assembly: AssemblyCompany("")] +[assembly: AssemblyProduct("Framework")] +[assembly: AssemblyCopyright("Copyright © 2014")] +[assembly: AssemblyTrademark("")] +[assembly: AssemblyCulture("")] + +// Setting ComVisible to false makes the types in this assembly not visible +// to COM components. If you need to access a type in this assembly from +// COM, set the ComVisible attribute to true on that type. +[assembly: ComVisible(false)] + +// The following GUID is for the ID of the typelib if this project is exposed to COM +[assembly: Guid("87ef8e2a-ee21-44b8-946a-97f78d2e54f2")] + +// Version information for an assembly consists of the following four values: +// +// Major Version +// Minor Version +// Build Number +// Revision +// +// You can specify all the values or you can default the Build and Revision Numbers +// by using the '*' as shown below: +// [assembly: AssemblyVersion("1.0.*")] +[assembly: AssemblyVersion("1.0.0.0")] +[assembly: AssemblyFileVersion("1.0.0.0")] diff --git a/Framework/Proto/AccountService.cs b/Framework/Proto/AccountService.cs new file mode 100644 index 000000000..d91692c3d --- /dev/null +++ b/Framework/Proto/AccountService.cs @@ -0,0 +1,6230 @@ +// Generated by the protocol buffer compiler. DO NOT EDIT! +// source: bgs/low/pb/client/account_service.proto +#pragma warning disable 1591, 0612, 3021 +#region Designer generated code + +using pb = Google.Protobuf; +using pbc = Google.Protobuf.Collections; +using pbr = Google.Protobuf.Reflection; +namespace Bgs.Protocol.Account.V1 +{ + + /// Holder for reflection information generated from bgs/low/pb/client/account_service.proto + [System.Diagnostics.DebuggerNonUserCodeAttribute()] + public static partial class AccountServiceReflection + { + + #region Descriptor + /// File descriptor for bgs/low/pb/client/account_service.proto + public static pbr::FileDescriptor Descriptor + { + get { return descriptor; } + } + private static pbr::FileDescriptor descriptor; + + static AccountServiceReflection() + { + byte[] descriptorData = System.Convert.FromBase64String( + string.Concat( + "CidiZ3MvbG93L3BiL2NsaWVudC9hY2NvdW50X3NlcnZpY2UucHJvdG8SF2Jn", + "cy5wcm90b2NvbC5hY2NvdW50LnYxGiViZ3MvbG93L3BiL2NsaWVudC9hY2Nv", + "dW50X3R5cGVzLnByb3RvGiRiZ3MvbG93L3BiL2NsaWVudC9lbnRpdHlfdHlw", + "ZXMucHJvdG8aIWJncy9sb3cvcGIvY2xpZW50L3JwY190eXBlcy5wcm90byKC", + "AgoRR2V0QWNjb3VudFJlcXVlc3QSNgoDcmVmGAEgASgLMikuYmdzLnByb3Rv", + "Y29sLmFjY291bnQudjEuQWNjb3VudFJlZmVyZW5jZRIRCglmZXRjaF9hbGwY", + "CiABKAgSEgoKZmV0Y2hfYmxvYhgLIAEoCBIQCghmZXRjaF9pZBgMIAEoCBIT", + "CgtmZXRjaF9lbWFpbBgNIAEoCBIYChBmZXRjaF9iYXR0bGVfdGFnGA4gASgI", + "EhcKD2ZldGNoX2Z1bGxfbmFtZRgPIAEoCBITCgtmZXRjaF9saW5rcxgQIAEo", + "CBIfChdmZXRjaF9wYXJlbnRhbF9jb250cm9scxgRIAEoCCK0AgoSR2V0QWNj", + "b3VudFJlc3BvbnNlEjIKBGJsb2IYCyABKAsyJC5iZ3MucHJvdG9jb2wuYWNj", + "b3VudC52MS5BY2NvdW50QmxvYhIuCgJpZBgMIAEoCzIiLmJncy5wcm90b2Nv", + "bC5hY2NvdW50LnYxLkFjY291bnRJZBINCgVlbWFpbBgNIAMoCRISCgpiYXR0", + "bGVfdGFnGA4gASgJEhEKCWZ1bGxfbmFtZRgPIAEoCRI3CgVsaW5rcxgQIAMo", + "CzIoLmJncy5wcm90b2NvbC5hY2NvdW50LnYxLkdhbWVBY2NvdW50TGluaxJL", + "ChVwYXJlbnRhbF9jb250cm9sX2luZm8YESABKAsyLC5iZ3MucHJvdG9jb2wu", + "YWNjb3VudC52MS5QYXJlbnRhbENvbnRyb2xJbmZvIosBChhDcmVhdGVHYW1l", + "QWNjb3VudFJlcXVlc3QSMwoHYWNjb3VudBgBIAEoCzIiLmJncy5wcm90b2Nv", + "bC5hY2NvdW50LnYxLkFjY291bnRJZBIOCgZyZWdpb24YAiABKA0SDwoHcHJv", + "Z3JhbRgDIAEoBxIZChFyZWFsbV9wZXJtaXNzaW9ucxgEIAEoDSKaAQoSQ2Fj", + "aGVFeHBpcmVSZXF1ZXN0EjMKB2FjY291bnQYASADKAsyIi5iZ3MucHJvdG9j", + "b2wuYWNjb3VudC52MS5BY2NvdW50SWQSQAoMZ2FtZV9hY2NvdW50GAIgAygL", + "MiouYmdzLnByb3RvY29sLmFjY291bnQudjEuR2FtZUFjY291bnRIYW5kbGUS", + "DQoFZW1haWwYAyADKAki6AEKF0NyZWRlbnRpYWxVcGRhdGVSZXF1ZXN0EjMK", + "B2FjY291bnQYASABKAsyIi5iZ3MucHJvdG9jb2wuYWNjb3VudC52MS5BY2Nv", + "dW50SWQSQwoPb2xkX2NyZWRlbnRpYWxzGAIgAygLMiouYmdzLnByb3RvY29s", + "LmFjY291bnQudjEuQWNjb3VudENyZWRlbnRpYWwSQwoPbmV3X2NyZWRlbnRp", + "YWxzGAMgAygLMiouYmdzLnByb3RvY29sLmFjY291bnQudjEuQWNjb3VudENy", + "ZWRlbnRpYWwSDgoGcmVnaW9uGAQgASgNIhoKGENyZWRlbnRpYWxVcGRhdGVS", + "ZXNwb25zZSJ9ChhBY2NvdW50RmxhZ1VwZGF0ZVJlcXVlc3QSMwoHYWNjb3Vu", + "dBgBIAEoCzIiLmJncy5wcm90b2NvbC5hY2NvdW50LnYxLkFjY291bnRJZBIO", + "CgZyZWdpb24YAiABKA0SDAoEZmxhZxgDIAEoBBIOCgZhY3RpdmUYBCABKAgi", + "fgocR2FtZUFjY291bnRGbGFnVXBkYXRlUmVxdWVzdBJACgxnYW1lX2FjY291", + "bnQYASABKAsyKi5iZ3MucHJvdG9jb2wuYWNjb3VudC52MS5HYW1lQWNjb3Vu", + "dEhhbmRsZRIMCgRmbGFnGAIgASgEEg4KBmFjdGl2ZRgDIAEoCCJWChlTdWJz", + "Y3JpcHRpb25VcGRhdGVSZXF1ZXN0EjkKA3JlZhgCIAMoCzIsLmJncy5wcm90", + "b2NvbC5hY2NvdW50LnYxLlN1YnNjcmliZXJSZWZlcmVuY2UiVwoaU3Vic2Ny", + "aXB0aW9uVXBkYXRlUmVzcG9uc2USOQoDcmVmGAEgAygLMiwuYmdzLnByb3Rv", + "Y29sLmFjY291bnQudjEuU3Vic2NyaWJlclJlZmVyZW5jZSI9ChNJc0lnckFk", + "ZHJlc3NSZXF1ZXN0EhYKDmNsaWVudF9hZGRyZXNzGAEgASgJEg4KBnJlZ2lv", + "bhgCIAEoDSIxChRBY2NvdW50U2VydmljZVJlZ2lvbhIKCgJpZBgBIAEoDRIN", + "CgVzaGFyZBgCIAEoCSJVChRBY2NvdW50U2VydmljZUNvbmZpZxI9CgZyZWdp", + "b24YASADKAsyLS5iZ3MucHJvdG9jb2wuYWNjb3VudC52MS5BY2NvdW50U2Vy", + "dmljZVJlZ2lvbiLcAQoWR2V0QWNjb3VudFN0YXRlUmVxdWVzdBIpCgllbnRp", + "dHlfaWQYASABKAsyFi5iZ3MucHJvdG9jb2wuRW50aXR5SWQSDwoHcHJvZ3Jh", + "bRgCIAEoDRIOCgZyZWdpb24YAyABKA0SPQoHb3B0aW9ucxgKIAEoCzIsLmJn", + "cy5wcm90b2NvbC5hY2NvdW50LnYxLkFjY291bnRGaWVsZE9wdGlvbnMSNwoE", + "dGFncxgLIAEoCzIpLmJncy5wcm90b2NvbC5hY2NvdW50LnYxLkFjY291bnRG", + "aWVsZFRhZ3MiiAEKF0dldEFjY291bnRTdGF0ZVJlc3BvbnNlEjQKBXN0YXRl", + "GAEgASgLMiUuYmdzLnByb3RvY29sLmFjY291bnQudjEuQWNjb3VudFN0YXRl", + "EjcKBHRhZ3MYAiABKAsyKS5iZ3MucHJvdG9jb2wuYWNjb3VudC52MS5BY2Nv", + "dW50RmllbGRUYWdzIv0BChpHZXRHYW1lQWNjb3VudFN0YXRlUmVxdWVzdBIu", + "CgphY2NvdW50X2lkGAEgASgLMhYuYmdzLnByb3RvY29sLkVudGl0eUlkQgIY", + "ARIvCg9nYW1lX2FjY291bnRfaWQYAiABKAsyFi5iZ3MucHJvdG9jb2wuRW50", + "aXR5SWQSQQoHb3B0aW9ucxgKIAEoCzIwLmJncy5wcm90b2NvbC5hY2NvdW50", + "LnYxLkdhbWVBY2NvdW50RmllbGRPcHRpb25zEjsKBHRhZ3MYCyABKAsyLS5i", + "Z3MucHJvdG9jb2wuYWNjb3VudC52MS5HYW1lQWNjb3VudEZpZWxkVGFncyKU", + "AQobR2V0R2FtZUFjY291bnRTdGF0ZVJlc3BvbnNlEjgKBXN0YXRlGAEgASgL", + "MikuYmdzLnByb3RvY29sLmFjY291bnQudjEuR2FtZUFjY291bnRTdGF0ZRI7", + "CgR0YWdzGAIgASgLMi0uYmdzLnByb3RvY29sLmFjY291bnQudjEuR2FtZUFj", + "Y291bnRGaWVsZFRhZ3Mi3gEKEkdldExpY2Vuc2VzUmVxdWVzdBIpCgl0YXJn", + "ZXRfaWQYASABKAsyFi5iZ3MucHJvdG9jb2wuRW50aXR5SWQSHgoWZmV0Y2hf", + "YWNjb3VudF9saWNlbnNlcxgCIAEoCBIjChtmZXRjaF9nYW1lX2FjY291bnRf", + "bGljZW5zZXMYAyABKAgSJgoeZmV0Y2hfZHluYW1pY19hY2NvdW50X2xpY2Vu", + "c2VzGAQgASgIEg8KB3Byb2dyYW0YBSABKAcSHwoXZXhjbHVkZV91bmtub3du", + "X3Byb2dyYW0YBiABKAgiUAoTR2V0TGljZW5zZXNSZXNwb25zZRI5CghsaWNl", + "bnNlcxgBIAMoCzInLmJncy5wcm90b2NvbC5hY2NvdW50LnYxLkFjY291bnRM", + "aWNlbnNlIkYKGUdldEdhbWVTZXNzaW9uSW5mb1JlcXVlc3QSKQoJZW50aXR5", + "X2lkGAEgASgLMhYuYmdzLnByb3RvY29sLkVudGl0eUlkIlwKGkdldEdhbWVT", + "ZXNzaW9uSW5mb1Jlc3BvbnNlEj4KDHNlc3Npb25faW5mbxgCIAEoCzIoLmJn", + "cy5wcm90b2NvbC5hY2NvdW50LnYxLkdhbWVTZXNzaW9uSW5mbyJ+Ch9HZXRH", + "YW1lVGltZVJlbWFpbmluZ0luZm9SZXF1ZXN0Ei8KD2dhbWVfYWNjb3VudF9p", + "ZBgBIAEoCzIWLmJncy5wcm90b2NvbC5FbnRpdHlJZBIqCgphY2NvdW50X2lk", + "GAIgASgLMhYuYmdzLnByb3RvY29sLkVudGl0eUlkInQKIEdldEdhbWVUaW1l", + "UmVtYWluaW5nSW5mb1Jlc3BvbnNlElAKGGdhbWVfdGltZV9yZW1haW5pbmdf", + "aW5mbxgBIAEoCzIuLmJncy5wcm90b2NvbC5hY2NvdW50LnYxLkdhbWVUaW1l", + "UmVtYWluaW5nSW5mbyI/ChJHZXRDQUlTSW5mb1JlcXVlc3QSKQoJZW50aXR5", + "X2lkGAEgASgLMhYuYmdzLnByb3RvY29sLkVudGl0eUlkIkcKE0dldENBSVNJ", + "bmZvUmVzcG9uc2USMAoJY2Fpc19pbmZvGAEgASgLMh0uYmdzLnByb3RvY29s", + "LmFjY291bnQudjEuQ0FJUyJGChlGb3J3YXJkQ2FjaGVFeHBpcmVSZXF1ZXN0", + "EikKCWVudGl0eV9pZBgBIAEoCzIWLmJncy5wcm90b2NvbC5FbnRpdHlJZCJu", + "ChhHZXRBdXRob3JpemVkRGF0YVJlcXVlc3QSKQoJZW50aXR5X2lkGAEgASgL", + "MhYuYmdzLnByb3RvY29sLkVudGl0eUlkEgsKA3RhZxgCIAMoCRIaChJwcml2", + "aWxlZ2VkX25ldHdvcmsYAyABKAgiUgoZR2V0QXV0aG9yaXplZERhdGFSZXNw", + "b25zZRI1CgRkYXRhGAEgAygLMicuYmdzLnByb3RvY29sLmFjY291bnQudjEu", + "QXV0aG9yaXplZERhdGEi0AEKGEFjY291bnRTdGF0ZU5vdGlmaWNhdGlvbhI8", + "Cg1hY2NvdW50X3N0YXRlGAEgASgLMiUuYmdzLnByb3RvY29sLmFjY291bnQu", + "djEuQWNjb3VudFN0YXRlEhUKDXN1YnNjcmliZXJfaWQYAiABKAQSPwoMYWNj", + "b3VudF90YWdzGAMgASgLMikuYmdzLnByb3RvY29sLmFjY291bnQudjEuQWNj", + "b3VudEZpZWxkVGFncxIeChZzdWJzY3JpcHRpb25fY29tcGxldGVkGAQgASgI", + "IuYBChxHYW1lQWNjb3VudFN0YXRlTm90aWZpY2F0aW9uEkUKEmdhbWVfYWNj", + "b3VudF9zdGF0ZRgBIAEoCzIpLmJncy5wcm90b2NvbC5hY2NvdW50LnYxLkdh", + "bWVBY2NvdW50U3RhdGUSFQoNc3Vic2NyaWJlcl9pZBgCIAEoBBJIChFnYW1l", + "X2FjY291bnRfdGFncxgDIAEoCzItLmJncy5wcm90b2NvbC5hY2NvdW50LnYx", + "LkdhbWVBY2NvdW50RmllbGRUYWdzEh4KFnN1YnNjcmlwdGlvbl9jb21wbGV0", + "ZWQYBCABKAgisgEKF0dhbWVBY2NvdW50Tm90aWZpY2F0aW9uEj8KDWdhbWVf", + "YWNjb3VudHMYASADKAsyKC5iZ3MucHJvdG9jb2wuYWNjb3VudC52MS5HYW1l", + "QWNjb3VudExpc3QSFQoNc3Vic2NyaWJlcl9pZBgCIAEoBBI/CgxhY2NvdW50", + "X3RhZ3MYAyABKAsyKS5iZ3MucHJvdG9jb2wuYWNjb3VudC52MS5BY2NvdW50", + "RmllbGRUYWdzIqgBCh5HYW1lQWNjb3VudFNlc3Npb25Ob3RpZmljYXRpb24S", + "QAoMZ2FtZV9hY2NvdW50GAEgASgLMiouYmdzLnByb3RvY29sLmFjY291bnQu", + "djEuR2FtZUFjY291bnRIYW5kbGUSRAoMc2Vzc2lvbl9pbmZvGAIgASgLMi4u", + "YmdzLnByb3RvY29sLmFjY291bnQudjEuR2FtZVNlc3Npb25VcGRhdGVJbmZv", + "Ms4PCg5BY2NvdW50U2VydmljZRJmCg5HZXRHYW1lQWNjb3VudBIqLmJncy5w", + "cm90b2NvbC5hY2NvdW50LnYxLkdhbWVBY2NvdW50SGFuZGxlGiguYmdzLnBy", + "b3RvY29sLmFjY291bnQudjEuR2FtZUFjY291bnRCbG9iEmUKCkdldEFjY291", + "bnQSKi5iZ3MucHJvdG9jb2wuYWNjb3VudC52MS5HZXRBY2NvdW50UmVxdWVz", + "dBorLmJncy5wcm90b2NvbC5hY2NvdW50LnYxLkdldEFjY291bnRSZXNwb25z", + "ZRJyChFDcmVhdGVHYW1lQWNjb3VudBIxLmJncy5wcm90b2NvbC5hY2NvdW50", + "LnYxLkNyZWF0ZUdhbWVBY2NvdW50UmVxdWVzdBoqLmJncy5wcm90b2NvbC5h", + "Y2NvdW50LnYxLkdhbWVBY2NvdW50SGFuZGxlElIKDElzSWdyQWRkcmVzcxIs", + "LmJncy5wcm90b2NvbC5hY2NvdW50LnYxLklzSWdyQWRkcmVzc1JlcXVlc3Qa", + "FC5iZ3MucHJvdG9jb2wuTm9EYXRhElUKC0NhY2hlRXhwaXJlEisuYmdzLnBy", + "b3RvY29sLmFjY291bnQudjEuQ2FjaGVFeHBpcmVSZXF1ZXN0GhkuYmdzLnBy", + "b3RvY29sLk5PX1JFU1BPTlNFEncKEENyZWRlbnRpYWxVcGRhdGUSMC5iZ3Mu", + "cHJvdG9jb2wuYWNjb3VudC52MS5DcmVkZW50aWFsVXBkYXRlUmVxdWVzdBox", + "LmJncy5wcm90b2NvbC5hY2NvdW50LnYxLkNyZWRlbnRpYWxVcGRhdGVSZXNw", + "b25zZRJ0CglTdWJzY3JpYmUSMi5iZ3MucHJvdG9jb2wuYWNjb3VudC52MS5T", + "dWJzY3JpcHRpb25VcGRhdGVSZXF1ZXN0GjMuYmdzLnByb3RvY29sLmFjY291", + "bnQudjEuU3Vic2NyaXB0aW9uVXBkYXRlUmVzcG9uc2USVwoLVW5zdWJzY3Jp", + "YmUSMi5iZ3MucHJvdG9jb2wuYWNjb3VudC52MS5TdWJzY3JpcHRpb25VcGRh", + "dGVSZXF1ZXN0GhQuYmdzLnByb3RvY29sLk5vRGF0YRJ0Cg9HZXRBY2NvdW50", + "U3RhdGUSLy5iZ3MucHJvdG9jb2wuYWNjb3VudC52MS5HZXRBY2NvdW50U3Rh", + "dGVSZXF1ZXN0GjAuYmdzLnByb3RvY29sLmFjY291bnQudjEuR2V0QWNjb3Vu", + "dFN0YXRlUmVzcG9uc2USgAEKE0dldEdhbWVBY2NvdW50U3RhdGUSMy5iZ3Mu", + "cHJvdG9jb2wuYWNjb3VudC52MS5HZXRHYW1lQWNjb3VudFN0YXRlUmVxdWVz", + "dBo0LmJncy5wcm90b2NvbC5hY2NvdW50LnYxLkdldEdhbWVBY2NvdW50U3Rh", + "dGVSZXNwb25zZRJoCgtHZXRMaWNlbnNlcxIrLmJncy5wcm90b2NvbC5hY2Nv", + "dW50LnYxLkdldExpY2Vuc2VzUmVxdWVzdBosLmJncy5wcm90b2NvbC5hY2Nv", + "dW50LnYxLkdldExpY2Vuc2VzUmVzcG9uc2USjwEKGEdldEdhbWVUaW1lUmVt", + "YWluaW5nSW5mbxI4LmJncy5wcm90b2NvbC5hY2NvdW50LnYxLkdldEdhbWVU", + "aW1lUmVtYWluaW5nSW5mb1JlcXVlc3QaOS5iZ3MucHJvdG9jb2wuYWNjb3Vu", + "dC52MS5HZXRHYW1lVGltZVJlbWFpbmluZ0luZm9SZXNwb25zZRJ9ChJHZXRH", + "YW1lU2Vzc2lvbkluZm8SMi5iZ3MucHJvdG9jb2wuYWNjb3VudC52MS5HZXRH", + "YW1lU2Vzc2lvbkluZm9SZXF1ZXN0GjMuYmdzLnByb3RvY29sLmFjY291bnQu", + "djEuR2V0R2FtZVNlc3Npb25JbmZvUmVzcG9uc2USaAoLR2V0Q0FJU0luZm8S", + "Ky5iZ3MucHJvdG9jb2wuYWNjb3VudC52MS5HZXRDQUlTSW5mb1JlcXVlc3Qa", + "LC5iZ3MucHJvdG9jb2wuYWNjb3VudC52MS5HZXRDQUlTSW5mb1Jlc3BvbnNl", + "El4KEkZvcndhcmRDYWNoZUV4cGlyZRIyLmJncy5wcm90b2NvbC5hY2NvdW50", + "LnYxLkZvcndhcmRDYWNoZUV4cGlyZVJlcXVlc3QaFC5iZ3MucHJvdG9jb2wu", + "Tm9EYXRhEnoKEUdldEF1dGhvcml6ZWREYXRhEjEuYmdzLnByb3RvY29sLmFj", + "Y291bnQudjEuR2V0QXV0aG9yaXplZERhdGFSZXF1ZXN0GjIuYmdzLnByb3Rv", + "Y29sLmFjY291bnQudjEuR2V0QXV0aG9yaXplZERhdGFSZXNwb25zZRJhChFB", + "Y2NvdW50RmxhZ1VwZGF0ZRIxLmJncy5wcm90b2NvbC5hY2NvdW50LnYxLkFj", + "Y291bnRGbGFnVXBkYXRlUmVxdWVzdBoZLmJncy5wcm90b2NvbC5OT19SRVNQ", + "T05TRRJpChVHYW1lQWNjb3VudEZsYWdVcGRhdGUSNS5iZ3MucHJvdG9jb2wu", + "YWNjb3VudC52MS5HYW1lQWNjb3VudEZsYWdVcGRhdGVSZXF1ZXN0GhkuYmdz", + "LnByb3RvY29sLk5PX1JFU1BPTlNFMsMDCg9BY2NvdW50TGlzdGVuZXISZQoV", + "T25BY2NvdW50U3RhdGVVcGRhdGVkEjEuYmdzLnByb3RvY29sLmFjY291bnQu", + "djEuQWNjb3VudFN0YXRlTm90aWZpY2F0aW9uGhkuYmdzLnByb3RvY29sLk5P", + "X1JFU1BPTlNFEm0KGU9uR2FtZUFjY291bnRTdGF0ZVVwZGF0ZWQSNS5iZ3Mu", + "cHJvdG9jb2wuYWNjb3VudC52MS5HYW1lQWNjb3VudFN0YXRlTm90aWZpY2F0", + "aW9uGhkuYmdzLnByb3RvY29sLk5PX1JFU1BPTlNFEmkKFU9uR2FtZUFjY291", + "bnRzVXBkYXRlZBIwLmJncy5wcm90b2NvbC5hY2NvdW50LnYxLkdhbWVBY2Nv", + "dW50Tm90aWZpY2F0aW9uGhkuYmdzLnByb3RvY29sLk5PX1JFU1BPTlNFIgOI", + "AgESbwoUT25HYW1lU2Vzc2lvblVwZGF0ZWQSNy5iZ3MucHJvdG9jb2wuYWNj", + "b3VudC52MS5HYW1lQWNjb3VudFNlc3Npb25Ob3RpZmljYXRpb24aGS5iZ3Mu", + "cHJvdG9jb2wuTk9fUkVTUE9OU0UiA4gCAUIFSAKAAQBiBnByb3RvMw==")); + descriptor = pbr::FileDescriptor.FromGeneratedCode(descriptorData, + new pbr::FileDescriptor[] { Bgs.Protocol.Account.V1.AccountTypesReflection.Descriptor, Bgs.Protocol.EntityTypesReflection.Descriptor, Bgs.Protocol.RpcTypesReflection.Descriptor, }, + new pbr::GeneratedClrTypeInfo(null, new pbr::GeneratedClrTypeInfo[] { + new pbr::GeneratedClrTypeInfo(typeof(Bgs.Protocol.Account.V1.GetAccountRequest), Bgs.Protocol.Account.V1.GetAccountRequest.Parser, new[]{ "Ref", "FetchAll", "FetchBlob", "FetchId", "FetchEmail", "FetchBattleTag", "FetchFullName", "FetchLinks", "FetchParentalControls" }, null, null, null), + new pbr::GeneratedClrTypeInfo(typeof(Bgs.Protocol.Account.V1.GetAccountResponse), Bgs.Protocol.Account.V1.GetAccountResponse.Parser, new[]{ "Blob", "Id", "Email", "BattleTag", "FullName", "Links", "ParentalControlInfo" }, null, null, null), + new pbr::GeneratedClrTypeInfo(typeof(Bgs.Protocol.Account.V1.CreateGameAccountRequest), Bgs.Protocol.Account.V1.CreateGameAccountRequest.Parser, new[]{ "Account", "Region", "Program", "RealmPermissions" }, null, null, null), + new pbr::GeneratedClrTypeInfo(typeof(Bgs.Protocol.Account.V1.CacheExpireRequest), Bgs.Protocol.Account.V1.CacheExpireRequest.Parser, new[]{ "Account", "GameAccount", "Email" }, null, null, null), + new pbr::GeneratedClrTypeInfo(typeof(Bgs.Protocol.Account.V1.CredentialUpdateRequest), Bgs.Protocol.Account.V1.CredentialUpdateRequest.Parser, new[]{ "Account", "OldCredentials", "NewCredentials", "Region" }, null, null, null), + new pbr::GeneratedClrTypeInfo(typeof(Bgs.Protocol.Account.V1.CredentialUpdateResponse), Bgs.Protocol.Account.V1.CredentialUpdateResponse.Parser, null, null, null, null), + new pbr::GeneratedClrTypeInfo(typeof(Bgs.Protocol.Account.V1.AccountFlagUpdateRequest), Bgs.Protocol.Account.V1.AccountFlagUpdateRequest.Parser, new[]{ "Account", "Region", "Flag", "Active" }, null, null, null), + new pbr::GeneratedClrTypeInfo(typeof(Bgs.Protocol.Account.V1.GameAccountFlagUpdateRequest), Bgs.Protocol.Account.V1.GameAccountFlagUpdateRequest.Parser, new[]{ "GameAccount", "Flag", "Active" }, null, null, null), + new pbr::GeneratedClrTypeInfo(typeof(Bgs.Protocol.Account.V1.SubscriptionUpdateRequest), Bgs.Protocol.Account.V1.SubscriptionUpdateRequest.Parser, new[]{ "Ref" }, null, null, null), + new pbr::GeneratedClrTypeInfo(typeof(Bgs.Protocol.Account.V1.SubscriptionUpdateResponse), Bgs.Protocol.Account.V1.SubscriptionUpdateResponse.Parser, new[]{ "Ref" }, null, null, null), + new pbr::GeneratedClrTypeInfo(typeof(Bgs.Protocol.Account.V1.IsIgrAddressRequest), Bgs.Protocol.Account.V1.IsIgrAddressRequest.Parser, new[]{ "ClientAddress", "Region" }, null, null, null), + new pbr::GeneratedClrTypeInfo(typeof(Bgs.Protocol.Account.V1.AccountServiceRegion), Bgs.Protocol.Account.V1.AccountServiceRegion.Parser, new[]{ "Id", "Shard" }, null, null, null), + new pbr::GeneratedClrTypeInfo(typeof(Bgs.Protocol.Account.V1.AccountServiceConfig), Bgs.Protocol.Account.V1.AccountServiceConfig.Parser, new[]{ "Region" }, null, null, null), + new pbr::GeneratedClrTypeInfo(typeof(Bgs.Protocol.Account.V1.GetAccountStateRequest), Bgs.Protocol.Account.V1.GetAccountStateRequest.Parser, new[]{ "EntityId", "Program", "Region", "Options", "Tags" }, null, null, null), + new pbr::GeneratedClrTypeInfo(typeof(Bgs.Protocol.Account.V1.GetAccountStateResponse), Bgs.Protocol.Account.V1.GetAccountStateResponse.Parser, new[]{ "State", "Tags" }, null, null, null), + new pbr::GeneratedClrTypeInfo(typeof(Bgs.Protocol.Account.V1.GetGameAccountStateRequest), Bgs.Protocol.Account.V1.GetGameAccountStateRequest.Parser, new[]{ "AccountId", "GameAccountId", "Options", "Tags" }, null, null, null), + new pbr::GeneratedClrTypeInfo(typeof(Bgs.Protocol.Account.V1.GetGameAccountStateResponse), Bgs.Protocol.Account.V1.GetGameAccountStateResponse.Parser, new[]{ "State", "Tags" }, null, null, null), + new pbr::GeneratedClrTypeInfo(typeof(Bgs.Protocol.Account.V1.GetLicensesRequest), Bgs.Protocol.Account.V1.GetLicensesRequest.Parser, new[]{ "TargetId", "FetchAccountLicenses", "FetchGameAccountLicenses", "FetchDynamicAccountLicenses", "Program", "ExcludeUnknownProgram" }, null, null, null), + new pbr::GeneratedClrTypeInfo(typeof(Bgs.Protocol.Account.V1.GetLicensesResponse), Bgs.Protocol.Account.V1.GetLicensesResponse.Parser, new[]{ "Licenses" }, null, null, null), + new pbr::GeneratedClrTypeInfo(typeof(Bgs.Protocol.Account.V1.GetGameSessionInfoRequest), Bgs.Protocol.Account.V1.GetGameSessionInfoRequest.Parser, new[]{ "EntityId" }, null, null, null), + new pbr::GeneratedClrTypeInfo(typeof(Bgs.Protocol.Account.V1.GetGameSessionInfoResponse), Bgs.Protocol.Account.V1.GetGameSessionInfoResponse.Parser, new[]{ "SessionInfo" }, null, null, null), + new pbr::GeneratedClrTypeInfo(typeof(Bgs.Protocol.Account.V1.GetGameTimeRemainingInfoRequest), Bgs.Protocol.Account.V1.GetGameTimeRemainingInfoRequest.Parser, new[]{ "GameAccountId", "AccountId" }, null, null, null), + new pbr::GeneratedClrTypeInfo(typeof(Bgs.Protocol.Account.V1.GetGameTimeRemainingInfoResponse), Bgs.Protocol.Account.V1.GetGameTimeRemainingInfoResponse.Parser, new[]{ "GameTimeRemainingInfo" }, null, null, null), + new pbr::GeneratedClrTypeInfo(typeof(Bgs.Protocol.Account.V1.GetCAISInfoRequest), Bgs.Protocol.Account.V1.GetCAISInfoRequest.Parser, new[]{ "EntityId" }, null, null, null), + new pbr::GeneratedClrTypeInfo(typeof(Bgs.Protocol.Account.V1.GetCAISInfoResponse), Bgs.Protocol.Account.V1.GetCAISInfoResponse.Parser, new[]{ "CaisInfo" }, null, null, null), + new pbr::GeneratedClrTypeInfo(typeof(Bgs.Protocol.Account.V1.ForwardCacheExpireRequest), Bgs.Protocol.Account.V1.ForwardCacheExpireRequest.Parser, new[]{ "EntityId" }, null, null, null), + new pbr::GeneratedClrTypeInfo(typeof(Bgs.Protocol.Account.V1.GetAuthorizedDataRequest), Bgs.Protocol.Account.V1.GetAuthorizedDataRequest.Parser, new[]{ "EntityId", "Tag", "PrivilegedNetwork" }, null, null, null), + new pbr::GeneratedClrTypeInfo(typeof(Bgs.Protocol.Account.V1.GetAuthorizedDataResponse), Bgs.Protocol.Account.V1.GetAuthorizedDataResponse.Parser, new[]{ "Data" }, null, null, null), + new pbr::GeneratedClrTypeInfo(typeof(Bgs.Protocol.Account.V1.AccountStateNotification), Bgs.Protocol.Account.V1.AccountStateNotification.Parser, new[]{ "AccountState", "SubscriberId", "AccountTags", "SubscriptionCompleted" }, null, null, null), + new pbr::GeneratedClrTypeInfo(typeof(Bgs.Protocol.Account.V1.GameAccountStateNotification), Bgs.Protocol.Account.V1.GameAccountStateNotification.Parser, new[]{ "GameAccountState", "SubscriberId", "GameAccountTags", "SubscriptionCompleted" }, null, null, null), + new pbr::GeneratedClrTypeInfo(typeof(Bgs.Protocol.Account.V1.GameAccountNotification), Bgs.Protocol.Account.V1.GameAccountNotification.Parser, new[]{ "GameAccounts", "SubscriberId", "AccountTags" }, null, null, null), + new pbr::GeneratedClrTypeInfo(typeof(Bgs.Protocol.Account.V1.GameAccountSessionNotification), Bgs.Protocol.Account.V1.GameAccountSessionNotification.Parser, new[]{ "GameAccount", "SessionInfo" }, null, null, null) + })); + } + #endregion + + } + #region Messages + [System.Diagnostics.DebuggerNonUserCodeAttribute()] + public sealed partial class GetAccountRequest : pb::IMessage + { + private static readonly pb::MessageParser _parser = new pb::MessageParser(() => new GetAccountRequest()); + public static pb::MessageParser Parser { get { return _parser; } } + + public static pbr::MessageDescriptor Descriptor + { + get { return Bgs.Protocol.Account.V1.AccountServiceReflection.Descriptor.MessageTypes[0]; } + } + + pbr::MessageDescriptor pb::IMessage.Descriptor + { + get { return Descriptor; } + } + + public GetAccountRequest() + { + OnConstruction(); + } + + partial void OnConstruction(); + + public GetAccountRequest(GetAccountRequest other) : this() + { + Ref = other.ref_ != null ? other.Ref.Clone() : null; + fetchAll_ = other.fetchAll_; + fetchBlob_ = other.fetchBlob_; + fetchId_ = other.fetchId_; + fetchEmail_ = other.fetchEmail_; + fetchBattleTag_ = other.fetchBattleTag_; + fetchFullName_ = other.fetchFullName_; + fetchLinks_ = other.fetchLinks_; + fetchParentalControls_ = other.fetchParentalControls_; + } + + public GetAccountRequest Clone() + { + return new GetAccountRequest(this); + } + + /// Field number for the "ref" field. + public const int RefFieldNumber = 1; + private Bgs.Protocol.Account.V1.AccountReference ref_; + public Bgs.Protocol.Account.V1.AccountReference Ref + { + get { return ref_; } + set + { + ref_ = value; + } + } + + /// Field number for the "fetch_all" field. + public const int FetchAllFieldNumber = 10; + private bool fetchAll_; + public bool FetchAll + { + get { return fetchAll_; } + set + { + fetchAll_ = value; + } + } + + /// Field number for the "fetch_blob" field. + public const int FetchBlobFieldNumber = 11; + private bool fetchBlob_; + public bool FetchBlob + { + get { return fetchBlob_; } + set + { + fetchBlob_ = value; + } + } + + /// Field number for the "fetch_id" field. + public const int FetchIdFieldNumber = 12; + private bool fetchId_; + public bool FetchId + { + get { return fetchId_; } + set + { + fetchId_ = value; + } + } + + /// Field number for the "fetch_email" field. + public const int FetchEmailFieldNumber = 13; + private bool fetchEmail_; + public bool FetchEmail + { + get { return fetchEmail_; } + set + { + fetchEmail_ = value; + } + } + + /// Field number for the "fetch_battle_tag" field. + public const int FetchBattleTagFieldNumber = 14; + private bool fetchBattleTag_; + public bool FetchBattleTag + { + get { return fetchBattleTag_; } + set + { + fetchBattleTag_ = value; + } + } + + /// Field number for the "fetch_full_name" field. + public const int FetchFullNameFieldNumber = 15; + private bool fetchFullName_; + public bool FetchFullName + { + get { return fetchFullName_; } + set + { + fetchFullName_ = value; + } + } + + /// Field number for the "fetch_links" field. + public const int FetchLinksFieldNumber = 16; + private bool fetchLinks_; + public bool FetchLinks + { + get { return fetchLinks_; } + set + { + fetchLinks_ = value; + } + } + + /// Field number for the "fetch_parental_controls" field. + public const int FetchParentalControlsFieldNumber = 17; + private bool fetchParentalControls_; + public bool FetchParentalControls + { + get { return fetchParentalControls_; } + set + { + fetchParentalControls_ = value; + } + } + + public override bool Equals(object other) + { + return Equals(other as GetAccountRequest); + } + + public bool Equals(GetAccountRequest other) + { + if (ReferenceEquals(other, null)) + { + return false; + } + if (ReferenceEquals(other, this)) + { + return true; + } + if (!object.Equals(Ref, other.Ref)) return false; + if (FetchAll != other.FetchAll) return false; + if (FetchBlob != other.FetchBlob) return false; + if (FetchId != other.FetchId) return false; + if (FetchEmail != other.FetchEmail) return false; + if (FetchBattleTag != other.FetchBattleTag) return false; + if (FetchFullName != other.FetchFullName) return false; + if (FetchLinks != other.FetchLinks) return false; + if (FetchParentalControls != other.FetchParentalControls) return false; + return true; + } + + public override int GetHashCode() + { + int hash = 1; + if (ref_ != null) hash ^= Ref.GetHashCode(); + if (FetchAll != false) hash ^= FetchAll.GetHashCode(); + if (FetchBlob != false) hash ^= FetchBlob.GetHashCode(); + if (FetchId != false) hash ^= FetchId.GetHashCode(); + if (FetchEmail != false) hash ^= FetchEmail.GetHashCode(); + if (FetchBattleTag != false) hash ^= FetchBattleTag.GetHashCode(); + if (FetchFullName != false) hash ^= FetchFullName.GetHashCode(); + if (FetchLinks != false) hash ^= FetchLinks.GetHashCode(); + if (FetchParentalControls != false) hash ^= FetchParentalControls.GetHashCode(); + return hash; + } + + public override string ToString() + { + return pb::JsonFormatter.ToDiagnosticString(this); + } + + public void WriteTo(pb::CodedOutputStream output) + { + if (ref_ != null) + { + output.WriteRawTag(10); + output.WriteMessage(Ref); + } + if (FetchAll != false) + { + output.WriteRawTag(80); + output.WriteBool(FetchAll); + } + if (FetchBlob != false) + { + output.WriteRawTag(88); + output.WriteBool(FetchBlob); + } + if (FetchId != false) + { + output.WriteRawTag(96); + output.WriteBool(FetchId); + } + if (FetchEmail != false) + { + output.WriteRawTag(104); + output.WriteBool(FetchEmail); + } + if (FetchBattleTag != false) + { + output.WriteRawTag(112); + output.WriteBool(FetchBattleTag); + } + if (FetchFullName != false) + { + output.WriteRawTag(120); + output.WriteBool(FetchFullName); + } + if (FetchLinks != false) + { + output.WriteRawTag(128, 1); + output.WriteBool(FetchLinks); + } + if (FetchParentalControls != false) + { + output.WriteRawTag(136, 1); + output.WriteBool(FetchParentalControls); + } + } + + public int CalculateSize() + { + int size = 0; + if (ref_ != null) + { + size += 1 + pb::CodedOutputStream.ComputeMessageSize(Ref); + } + if (FetchAll != false) + { + size += 1 + 1; + } + if (FetchBlob != false) + { + size += 1 + 1; + } + if (FetchId != false) + { + size += 1 + 1; + } + if (FetchEmail != false) + { + size += 1 + 1; + } + if (FetchBattleTag != false) + { + size += 1 + 1; + } + if (FetchFullName != false) + { + size += 1 + 1; + } + if (FetchLinks != false) + { + size += 2 + 1; + } + if (FetchParentalControls != false) + { + size += 2 + 1; + } + return size; + } + + public void MergeFrom(GetAccountRequest other) + { + if (other == null) + { + return; + } + if (other.ref_ != null) + { + if (ref_ == null) + { + ref_ = new Bgs.Protocol.Account.V1.AccountReference(); + } + Ref.MergeFrom(other.Ref); + } + if (other.FetchAll != false) + { + FetchAll = other.FetchAll; + } + if (other.FetchBlob != false) + { + FetchBlob = other.FetchBlob; + } + if (other.FetchId != false) + { + FetchId = other.FetchId; + } + if (other.FetchEmail != false) + { + FetchEmail = other.FetchEmail; + } + if (other.FetchBattleTag != false) + { + FetchBattleTag = other.FetchBattleTag; + } + if (other.FetchFullName != false) + { + FetchFullName = other.FetchFullName; + } + if (other.FetchLinks != false) + { + FetchLinks = other.FetchLinks; + } + if (other.FetchParentalControls != false) + { + FetchParentalControls = other.FetchParentalControls; + } + } + + public void MergeFrom(pb::CodedInputStream input) + { + uint tag; + while ((tag = input.ReadTag()) != 0) + { + switch (tag) + { + default: + input.SkipLastField(); + break; + case 10: + { + if (ref_ == null) + { + ref_ = new Bgs.Protocol.Account.V1.AccountReference(); + } + input.ReadMessage(ref_); + break; + } + case 80: + { + FetchAll = input.ReadBool(); + break; + } + case 88: + { + FetchBlob = input.ReadBool(); + break; + } + case 96: + { + FetchId = input.ReadBool(); + break; + } + case 104: + { + FetchEmail = input.ReadBool(); + break; + } + case 112: + { + FetchBattleTag = input.ReadBool(); + break; + } + case 120: + { + FetchFullName = input.ReadBool(); + break; + } + case 128: + { + FetchLinks = input.ReadBool(); + break; + } + case 136: + { + FetchParentalControls = input.ReadBool(); + break; + } + } + } + } + + } + + [System.Diagnostics.DebuggerNonUserCodeAttribute()] + public sealed partial class GetAccountResponse : pb::IMessage + { + private static readonly pb::MessageParser _parser = new pb::MessageParser(() => new GetAccountResponse()); + public static pb::MessageParser Parser { get { return _parser; } } + + public static pbr::MessageDescriptor Descriptor + { + get { return Bgs.Protocol.Account.V1.AccountServiceReflection.Descriptor.MessageTypes[1]; } + } + + pbr::MessageDescriptor pb::IMessage.Descriptor + { + get { return Descriptor; } + } + + public GetAccountResponse() + { + OnConstruction(); + } + + partial void OnConstruction(); + + public GetAccountResponse(GetAccountResponse other) : this() + { + Blob = other.blob_ != null ? other.Blob.Clone() : null; + Id = other.id_ != null ? other.Id.Clone() : null; + email_ = other.email_.Clone(); + battleTag_ = other.battleTag_; + fullName_ = other.fullName_; + links_ = other.links_.Clone(); + ParentalControlInfo = other.parentalControlInfo_ != null ? other.ParentalControlInfo.Clone() : null; + } + + public GetAccountResponse Clone() + { + return new GetAccountResponse(this); + } + + /// Field number for the "blob" field. + public const int BlobFieldNumber = 11; + private Bgs.Protocol.Account.V1.AccountBlob blob_; + public Bgs.Protocol.Account.V1.AccountBlob Blob + { + get { return blob_; } + set + { + blob_ = value; + } + } + + /// Field number for the "id" field. + public const int IdFieldNumber = 12; + private Bgs.Protocol.Account.V1.AccountId id_; + public Bgs.Protocol.Account.V1.AccountId Id + { + get { return id_; } + set + { + id_ = value; + } + } + + /// Field number for the "email" field. + public const int EmailFieldNumber = 13; + private static readonly pb::FieldCodec _repeated_email_codec + = pb::FieldCodec.ForString(106); + private readonly pbc::RepeatedField email_ = new pbc::RepeatedField(); + public pbc::RepeatedField Email + { + get { return email_; } + } + + /// Field number for the "battle_tag" field. + public const int BattleTagFieldNumber = 14; + private string battleTag_ = ""; + public string BattleTag + { + get { return battleTag_; } + set + { + battleTag_ = pb::ProtoPreconditions.CheckNotNull(value, "value"); + } + } + + /// Field number for the "full_name" field. + public const int FullNameFieldNumber = 15; + private string fullName_ = ""; + public string FullName + { + get { return fullName_; } + set + { + fullName_ = pb::ProtoPreconditions.CheckNotNull(value, "value"); + } + } + + /// Field number for the "links" field. + public const int LinksFieldNumber = 16; + private static readonly pb::FieldCodec _repeated_links_codec + = pb::FieldCodec.ForMessage(130, Bgs.Protocol.Account.V1.GameAccountLink.Parser); + private readonly pbc::RepeatedField links_ = new pbc::RepeatedField(); + public pbc::RepeatedField Links + { + get { return links_; } + } + + /// Field number for the "parental_control_info" field. + public const int ParentalControlInfoFieldNumber = 17; + private Bgs.Protocol.Account.V1.ParentalControlInfo parentalControlInfo_; + public Bgs.Protocol.Account.V1.ParentalControlInfo ParentalControlInfo + { + get { return parentalControlInfo_; } + set + { + parentalControlInfo_ = value; + } + } + + public override bool Equals(object other) + { + return Equals(other as GetAccountResponse); + } + + public bool Equals(GetAccountResponse other) + { + if (ReferenceEquals(other, null)) + { + return false; + } + if (ReferenceEquals(other, this)) + { + return true; + } + if (!object.Equals(Blob, other.Blob)) return false; + if (!object.Equals(Id, other.Id)) return false; + if (!email_.Equals(other.email_)) return false; + if (BattleTag != other.BattleTag) return false; + if (FullName != other.FullName) return false; + if (!links_.Equals(other.links_)) return false; + if (!object.Equals(ParentalControlInfo, other.ParentalControlInfo)) return false; + return true; + } + + public override int GetHashCode() + { + int hash = 1; + if (blob_ != null) hash ^= Blob.GetHashCode(); + if (id_ != null) hash ^= Id.GetHashCode(); + hash ^= email_.GetHashCode(); + if (BattleTag.Length != 0) hash ^= BattleTag.GetHashCode(); + if (FullName.Length != 0) hash ^= FullName.GetHashCode(); + hash ^= links_.GetHashCode(); + if (parentalControlInfo_ != null) hash ^= ParentalControlInfo.GetHashCode(); + return hash; + } + + public override string ToString() + { + return pb::JsonFormatter.ToDiagnosticString(this); + } + + public void WriteTo(pb::CodedOutputStream output) + { + if (blob_ != null) + { + output.WriteRawTag(90); + output.WriteMessage(Blob); + } + if (id_ != null) + { + output.WriteRawTag(98); + output.WriteMessage(Id); + } + email_.WriteTo(output, _repeated_email_codec); + if (BattleTag.Length != 0) + { + output.WriteRawTag(114); + output.WriteString(BattleTag); + } + if (FullName.Length != 0) + { + output.WriteRawTag(122); + output.WriteString(FullName); + } + links_.WriteTo(output, _repeated_links_codec); + if (parentalControlInfo_ != null) + { + output.WriteRawTag(138, 1); + output.WriteMessage(ParentalControlInfo); + } + } + + public int CalculateSize() + { + int size = 0; + if (blob_ != null) + { + size += 1 + pb::CodedOutputStream.ComputeMessageSize(Blob); + } + if (id_ != null) + { + size += 1 + pb::CodedOutputStream.ComputeMessageSize(Id); + } + size += email_.CalculateSize(_repeated_email_codec); + if (BattleTag.Length != 0) + { + size += 1 + pb::CodedOutputStream.ComputeStringSize(BattleTag); + } + if (FullName.Length != 0) + { + size += 1 + pb::CodedOutputStream.ComputeStringSize(FullName); + } + size += links_.CalculateSize(_repeated_links_codec); + if (parentalControlInfo_ != null) + { + size += 2 + pb::CodedOutputStream.ComputeMessageSize(ParentalControlInfo); + } + return size; + } + + public void MergeFrom(GetAccountResponse other) + { + if (other == null) + { + return; + } + if (other.blob_ != null) + { + if (blob_ == null) + { + blob_ = new Bgs.Protocol.Account.V1.AccountBlob(); + } + Blob.MergeFrom(other.Blob); + } + if (other.id_ != null) + { + if (id_ == null) + { + id_ = new Bgs.Protocol.Account.V1.AccountId(); + } + Id.MergeFrom(other.Id); + } + email_.Add(other.email_); + if (other.BattleTag.Length != 0) + { + BattleTag = other.BattleTag; + } + if (other.FullName.Length != 0) + { + FullName = other.FullName; + } + links_.Add(other.links_); + if (other.parentalControlInfo_ != null) + { + if (parentalControlInfo_ == null) + { + parentalControlInfo_ = new Bgs.Protocol.Account.V1.ParentalControlInfo(); + } + ParentalControlInfo.MergeFrom(other.ParentalControlInfo); + } + } + + public void MergeFrom(pb::CodedInputStream input) + { + uint tag; + while ((tag = input.ReadTag()) != 0) + { + switch (tag) + { + default: + input.SkipLastField(); + break; + case 90: + { + if (blob_ == null) + { + blob_ = new Bgs.Protocol.Account.V1.AccountBlob(); + } + input.ReadMessage(blob_); + break; + } + case 98: + { + if (id_ == null) + { + id_ = new Bgs.Protocol.Account.V1.AccountId(); + } + input.ReadMessage(id_); + break; + } + case 106: + { + email_.AddEntriesFrom(input, _repeated_email_codec); + break; + } + case 114: + { + BattleTag = input.ReadString(); + break; + } + case 122: + { + FullName = input.ReadString(); + break; + } + case 130: + { + links_.AddEntriesFrom(input, _repeated_links_codec); + break; + } + case 138: + { + if (parentalControlInfo_ == null) + { + parentalControlInfo_ = new Bgs.Protocol.Account.V1.ParentalControlInfo(); + } + input.ReadMessage(parentalControlInfo_); + break; + } + } + } + } + + } + + [System.Diagnostics.DebuggerNonUserCodeAttribute()] + public sealed partial class CreateGameAccountRequest : pb::IMessage + { + private static readonly pb::MessageParser _parser = new pb::MessageParser(() => new CreateGameAccountRequest()); + public static pb::MessageParser Parser { get { return _parser; } } + + public static pbr::MessageDescriptor Descriptor + { + get { return Bgs.Protocol.Account.V1.AccountServiceReflection.Descriptor.MessageTypes[2]; } + } + + pbr::MessageDescriptor pb::IMessage.Descriptor + { + get { return Descriptor; } + } + + public CreateGameAccountRequest() + { + OnConstruction(); + } + + partial void OnConstruction(); + + public CreateGameAccountRequest(CreateGameAccountRequest other) : this() + { + Account = other.account_ != null ? other.Account.Clone() : null; + region_ = other.region_; + program_ = other.program_; + realmPermissions_ = other.realmPermissions_; + } + + public CreateGameAccountRequest Clone() + { + return new CreateGameAccountRequest(this); + } + + /// Field number for the "account" field. + public const int AccountFieldNumber = 1; + private Bgs.Protocol.Account.V1.AccountId account_; + public Bgs.Protocol.Account.V1.AccountId Account + { + get { return account_; } + set + { + account_ = value; + } + } + + /// Field number for the "region" field. + public const int RegionFieldNumber = 2; + private uint region_; + public uint Region + { + get { return region_; } + set + { + region_ = value; + } + } + + /// Field number for the "program" field. + public const int ProgramFieldNumber = 3; + private uint program_; + public uint Program + { + get { return program_; } + set + { + program_ = value; + } + } + + /// Field number for the "realm_permissions" field. + public const int RealmPermissionsFieldNumber = 4; + private uint realmPermissions_; + public uint RealmPermissions + { + get { return realmPermissions_; } + set + { + realmPermissions_ = value; + } + } + + public override bool Equals(object other) + { + return Equals(other as CreateGameAccountRequest); + } + + public bool Equals(CreateGameAccountRequest other) + { + if (ReferenceEquals(other, null)) + { + return false; + } + if (ReferenceEquals(other, this)) + { + return true; + } + if (!object.Equals(Account, other.Account)) return false; + if (Region != other.Region) return false; + if (Program != other.Program) return false; + if (RealmPermissions != other.RealmPermissions) return false; + return true; + } + + public override int GetHashCode() + { + int hash = 1; + if (account_ != null) hash ^= Account.GetHashCode(); + if (Region != 0) hash ^= Region.GetHashCode(); + if (Program != 0) hash ^= Program.GetHashCode(); + if (RealmPermissions != 0) hash ^= RealmPermissions.GetHashCode(); + return hash; + } + + public override string ToString() + { + return pb::JsonFormatter.ToDiagnosticString(this); + } + + public void WriteTo(pb::CodedOutputStream output) + { + if (account_ != null) + { + output.WriteRawTag(10); + output.WriteMessage(Account); + } + if (Region != 0) + { + output.WriteRawTag(16); + output.WriteUInt32(Region); + } + if (Program != 0) + { + output.WriteRawTag(29); + output.WriteFixed32(Program); + } + if (RealmPermissions != 0) + { + output.WriteRawTag(32); + output.WriteUInt32(RealmPermissions); + } + } + + public int CalculateSize() + { + int size = 0; + if (account_ != null) + { + size += 1 + pb::CodedOutputStream.ComputeMessageSize(Account); + } + if (Region != 0) + { + size += 1 + pb::CodedOutputStream.ComputeUInt32Size(Region); + } + if (Program != 0) + { + size += 1 + 4; + } + if (RealmPermissions != 0) + { + size += 1 + pb::CodedOutputStream.ComputeUInt32Size(RealmPermissions); + } + return size; + } + + public void MergeFrom(CreateGameAccountRequest other) + { + if (other == null) + { + return; + } + if (other.account_ != null) + { + if (account_ == null) + { + account_ = new Bgs.Protocol.Account.V1.AccountId(); + } + Account.MergeFrom(other.Account); + } + if (other.Region != 0) + { + Region = other.Region; + } + if (other.Program != 0) + { + Program = other.Program; + } + if (other.RealmPermissions != 0) + { + RealmPermissions = other.RealmPermissions; + } + } + + public void MergeFrom(pb::CodedInputStream input) + { + uint tag; + while ((tag = input.ReadTag()) != 0) + { + switch (tag) + { + default: + input.SkipLastField(); + break; + case 10: + { + if (account_ == null) + { + account_ = new Bgs.Protocol.Account.V1.AccountId(); + } + input.ReadMessage(account_); + break; + } + case 16: + { + Region = input.ReadUInt32(); + break; + } + case 29: + { + Program = input.ReadFixed32(); + break; + } + case 32: + { + RealmPermissions = input.ReadUInt32(); + break; + } + } + } + } + + } + + [System.Diagnostics.DebuggerNonUserCodeAttribute()] + public sealed partial class CacheExpireRequest : pb::IMessage + { + private static readonly pb::MessageParser _parser = new pb::MessageParser(() => new CacheExpireRequest()); + public static pb::MessageParser Parser { get { return _parser; } } + + public static pbr::MessageDescriptor Descriptor + { + get { return Bgs.Protocol.Account.V1.AccountServiceReflection.Descriptor.MessageTypes[3]; } + } + + pbr::MessageDescriptor pb::IMessage.Descriptor + { + get { return Descriptor; } + } + + public CacheExpireRequest() + { + OnConstruction(); + } + + partial void OnConstruction(); + + public CacheExpireRequest(CacheExpireRequest other) : this() + { + account_ = other.account_.Clone(); + gameAccount_ = other.gameAccount_.Clone(); + email_ = other.email_.Clone(); + } + + public CacheExpireRequest Clone() + { + return new CacheExpireRequest(this); + } + + /// Field number for the "account" field. + public const int AccountFieldNumber = 1; + private static readonly pb::FieldCodec _repeated_account_codec + = pb::FieldCodec.ForMessage(10, Bgs.Protocol.Account.V1.AccountId.Parser); + private readonly pbc::RepeatedField account_ = new pbc::RepeatedField(); + public pbc::RepeatedField Account + { + get { return account_; } + } + + /// Field number for the "game_account" field. + public const int GameAccountFieldNumber = 2; + private static readonly pb::FieldCodec _repeated_gameAccount_codec + = pb::FieldCodec.ForMessage(18, Bgs.Protocol.Account.V1.GameAccountHandle.Parser); + private readonly pbc::RepeatedField gameAccount_ = new pbc::RepeatedField(); + public pbc::RepeatedField GameAccount + { + get { return gameAccount_; } + } + + /// Field number for the "email" field. + public const int EmailFieldNumber = 3; + private static readonly pb::FieldCodec _repeated_email_codec + = pb::FieldCodec.ForString(26); + private readonly pbc::RepeatedField email_ = new pbc::RepeatedField(); + public pbc::RepeatedField Email + { + get { return email_; } + } + + public override bool Equals(object other) + { + return Equals(other as CacheExpireRequest); + } + + public bool Equals(CacheExpireRequest other) + { + if (ReferenceEquals(other, null)) + { + return false; + } + if (ReferenceEquals(other, this)) + { + return true; + } + if (!account_.Equals(other.account_)) return false; + if (!gameAccount_.Equals(other.gameAccount_)) return false; + if (!email_.Equals(other.email_)) return false; + return true; + } + + public override int GetHashCode() + { + int hash = 1; + hash ^= account_.GetHashCode(); + hash ^= gameAccount_.GetHashCode(); + hash ^= email_.GetHashCode(); + return hash; + } + + public override string ToString() + { + return pb::JsonFormatter.ToDiagnosticString(this); + } + + public void WriteTo(pb::CodedOutputStream output) + { + account_.WriteTo(output, _repeated_account_codec); + gameAccount_.WriteTo(output, _repeated_gameAccount_codec); + email_.WriteTo(output, _repeated_email_codec); + } + + public int CalculateSize() + { + int size = 0; + size += account_.CalculateSize(_repeated_account_codec); + size += gameAccount_.CalculateSize(_repeated_gameAccount_codec); + size += email_.CalculateSize(_repeated_email_codec); + return size; + } + + public void MergeFrom(CacheExpireRequest other) + { + if (other == null) + { + return; + } + account_.Add(other.account_); + gameAccount_.Add(other.gameAccount_); + email_.Add(other.email_); + } + + public void MergeFrom(pb::CodedInputStream input) + { + uint tag; + while ((tag = input.ReadTag()) != 0) + { + switch (tag) + { + default: + input.SkipLastField(); + break; + case 10: + { + account_.AddEntriesFrom(input, _repeated_account_codec); + break; + } + case 18: + { + gameAccount_.AddEntriesFrom(input, _repeated_gameAccount_codec); + break; + } + case 26: + { + email_.AddEntriesFrom(input, _repeated_email_codec); + break; + } + } + } + } + + } + + [System.Diagnostics.DebuggerNonUserCodeAttribute()] + public sealed partial class CredentialUpdateRequest : pb::IMessage + { + private static readonly pb::MessageParser _parser = new pb::MessageParser(() => new CredentialUpdateRequest()); + public static pb::MessageParser Parser { get { return _parser; } } + + public static pbr::MessageDescriptor Descriptor + { + get { return Bgs.Protocol.Account.V1.AccountServiceReflection.Descriptor.MessageTypes[4]; } + } + + pbr::MessageDescriptor pb::IMessage.Descriptor + { + get { return Descriptor; } + } + + public CredentialUpdateRequest() + { + OnConstruction(); + } + + partial void OnConstruction(); + + public CredentialUpdateRequest(CredentialUpdateRequest other) : this() + { + Account = other.account_ != null ? other.Account.Clone() : null; + oldCredentials_ = other.oldCredentials_.Clone(); + newCredentials_ = other.newCredentials_.Clone(); + region_ = other.region_; + } + + public CredentialUpdateRequest Clone() + { + return new CredentialUpdateRequest(this); + } + + /// Field number for the "account" field. + public const int AccountFieldNumber = 1; + private Bgs.Protocol.Account.V1.AccountId account_; + public Bgs.Protocol.Account.V1.AccountId Account + { + get { return account_; } + set + { + account_ = value; + } + } + + /// Field number for the "old_credentials" field. + public const int OldCredentialsFieldNumber = 2; + private static readonly pb::FieldCodec _repeated_oldCredentials_codec + = pb::FieldCodec.ForMessage(18, Bgs.Protocol.Account.V1.AccountCredential.Parser); + private readonly pbc::RepeatedField oldCredentials_ = new pbc::RepeatedField(); + public pbc::RepeatedField OldCredentials + { + get { return oldCredentials_; } + } + + /// Field number for the "new_credentials" field. + public const int NewCredentialsFieldNumber = 3; + private static readonly pb::FieldCodec _repeated_newCredentials_codec + = pb::FieldCodec.ForMessage(26, Bgs.Protocol.Account.V1.AccountCredential.Parser); + private readonly pbc::RepeatedField newCredentials_ = new pbc::RepeatedField(); + public pbc::RepeatedField NewCredentials + { + get { return newCredentials_; } + } + + /// Field number for the "region" field. + public const int RegionFieldNumber = 4; + private uint region_; + public uint Region + { + get { return region_; } + set + { + region_ = value; + } + } + + public override bool Equals(object other) + { + return Equals(other as CredentialUpdateRequest); + } + + public bool Equals(CredentialUpdateRequest other) + { + if (ReferenceEquals(other, null)) + { + return false; + } + if (ReferenceEquals(other, this)) + { + return true; + } + if (!object.Equals(Account, other.Account)) return false; + if (!oldCredentials_.Equals(other.oldCredentials_)) return false; + if (!newCredentials_.Equals(other.newCredentials_)) return false; + if (Region != other.Region) return false; + return true; + } + + public override int GetHashCode() + { + int hash = 1; + if (account_ != null) hash ^= Account.GetHashCode(); + hash ^= oldCredentials_.GetHashCode(); + hash ^= newCredentials_.GetHashCode(); + if (Region != 0) hash ^= Region.GetHashCode(); + return hash; + } + + public override string ToString() + { + return pb::JsonFormatter.ToDiagnosticString(this); + } + + public void WriteTo(pb::CodedOutputStream output) + { + if (account_ != null) + { + output.WriteRawTag(10); + output.WriteMessage(Account); + } + oldCredentials_.WriteTo(output, _repeated_oldCredentials_codec); + newCredentials_.WriteTo(output, _repeated_newCredentials_codec); + if (Region != 0) + { + output.WriteRawTag(32); + output.WriteUInt32(Region); + } + } + + public int CalculateSize() + { + int size = 0; + if (account_ != null) + { + size += 1 + pb::CodedOutputStream.ComputeMessageSize(Account); + } + size += oldCredentials_.CalculateSize(_repeated_oldCredentials_codec); + size += newCredentials_.CalculateSize(_repeated_newCredentials_codec); + if (Region != 0) + { + size += 1 + pb::CodedOutputStream.ComputeUInt32Size(Region); + } + return size; + } + + public void MergeFrom(CredentialUpdateRequest other) + { + if (other == null) + { + return; + } + if (other.account_ != null) + { + if (account_ == null) + { + account_ = new Bgs.Protocol.Account.V1.AccountId(); + } + Account.MergeFrom(other.Account); + } + oldCredentials_.Add(other.oldCredentials_); + newCredentials_.Add(other.newCredentials_); + if (other.Region != 0) + { + Region = other.Region; + } + } + + public void MergeFrom(pb::CodedInputStream input) + { + uint tag; + while ((tag = input.ReadTag()) != 0) + { + switch (tag) + { + default: + input.SkipLastField(); + break; + case 10: + { + if (account_ == null) + { + account_ = new Bgs.Protocol.Account.V1.AccountId(); + } + input.ReadMessage(account_); + break; + } + case 18: + { + oldCredentials_.AddEntriesFrom(input, _repeated_oldCredentials_codec); + break; + } + case 26: + { + newCredentials_.AddEntriesFrom(input, _repeated_newCredentials_codec); + break; + } + case 32: + { + Region = input.ReadUInt32(); + break; + } + } + } + } + + } + + [System.Diagnostics.DebuggerNonUserCodeAttribute()] + public sealed partial class CredentialUpdateResponse : pb::IMessage + { + private static readonly pb::MessageParser _parser = new pb::MessageParser(() => new CredentialUpdateResponse()); + public static pb::MessageParser Parser { get { return _parser; } } + + public static pbr::MessageDescriptor Descriptor + { + get { return Bgs.Protocol.Account.V1.AccountServiceReflection.Descriptor.MessageTypes[5]; } + } + + pbr::MessageDescriptor pb::IMessage.Descriptor + { + get { return Descriptor; } + } + + public CredentialUpdateResponse() + { + OnConstruction(); + } + + partial void OnConstruction(); + + public CredentialUpdateResponse(CredentialUpdateResponse other) : this() + { + } + + public CredentialUpdateResponse Clone() + { + return new CredentialUpdateResponse(this); + } + + public override bool Equals(object other) + { + return Equals(other as CredentialUpdateResponse); + } + + public bool Equals(CredentialUpdateResponse other) + { + if (ReferenceEquals(other, null)) + { + return false; + } + if (ReferenceEquals(other, this)) + { + return true; + } + return true; + } + + public override int GetHashCode() + { + int hash = 1; + return hash; + } + + public override string ToString() + { + return pb::JsonFormatter.ToDiagnosticString(this); + } + + public void WriteTo(pb::CodedOutputStream output) + { + } + + public int CalculateSize() + { + int size = 0; + return size; + } + + public void MergeFrom(CredentialUpdateResponse other) + { + if (other == null) + { + return; + } + } + + public void MergeFrom(pb::CodedInputStream input) + { + uint tag; + while ((tag = input.ReadTag()) != 0) + { + switch (tag) + { + default: + input.SkipLastField(); + break; + } + } + } + + } + + [System.Diagnostics.DebuggerNonUserCodeAttribute()] + public sealed partial class AccountFlagUpdateRequest : pb::IMessage + { + private static readonly pb::MessageParser _parser = new pb::MessageParser(() => new AccountFlagUpdateRequest()); + public static pb::MessageParser Parser { get { return _parser; } } + + public static pbr::MessageDescriptor Descriptor + { + get { return Bgs.Protocol.Account.V1.AccountServiceReflection.Descriptor.MessageTypes[6]; } + } + + pbr::MessageDescriptor pb::IMessage.Descriptor + { + get { return Descriptor; } + } + + public AccountFlagUpdateRequest() + { + OnConstruction(); + } + + partial void OnConstruction(); + + public AccountFlagUpdateRequest(AccountFlagUpdateRequest other) : this() + { + Account = other.account_ != null ? other.Account.Clone() : null; + region_ = other.region_; + flag_ = other.flag_; + active_ = other.active_; + } + + public AccountFlagUpdateRequest Clone() + { + return new AccountFlagUpdateRequest(this); + } + + /// Field number for the "account" field. + public const int AccountFieldNumber = 1; + private Bgs.Protocol.Account.V1.AccountId account_; + public Bgs.Protocol.Account.V1.AccountId Account + { + get { return account_; } + set + { + account_ = value; + } + } + + /// Field number for the "region" field. + public const int RegionFieldNumber = 2; + private uint region_; + public uint Region + { + get { return region_; } + set + { + region_ = value; + } + } + + /// Field number for the "flag" field. + public const int FlagFieldNumber = 3; + private ulong flag_; + public ulong Flag + { + get { return flag_; } + set + { + flag_ = value; + } + } + + /// Field number for the "active" field. + public const int ActiveFieldNumber = 4; + private bool active_; + public bool Active + { + get { return active_; } + set + { + active_ = value; + } + } + + public override bool Equals(object other) + { + return Equals(other as AccountFlagUpdateRequest); + } + + public bool Equals(AccountFlagUpdateRequest other) + { + if (ReferenceEquals(other, null)) + { + return false; + } + if (ReferenceEquals(other, this)) + { + return true; + } + if (!object.Equals(Account, other.Account)) return false; + if (Region != other.Region) return false; + if (Flag != other.Flag) return false; + if (Active != other.Active) return false; + return true; + } + + public override int GetHashCode() + { + int hash = 1; + if (account_ != null) hash ^= Account.GetHashCode(); + if (Region != 0) hash ^= Region.GetHashCode(); + if (Flag != 0UL) hash ^= Flag.GetHashCode(); + if (Active != false) hash ^= Active.GetHashCode(); + return hash; + } + + public override string ToString() + { + return pb::JsonFormatter.ToDiagnosticString(this); + } + + public void WriteTo(pb::CodedOutputStream output) + { + if (account_ != null) + { + output.WriteRawTag(10); + output.WriteMessage(Account); + } + if (Region != 0) + { + output.WriteRawTag(16); + output.WriteUInt32(Region); + } + if (Flag != 0UL) + { + output.WriteRawTag(24); + output.WriteUInt64(Flag); + } + if (Active != false) + { + output.WriteRawTag(32); + output.WriteBool(Active); + } + } + + public int CalculateSize() + { + int size = 0; + if (account_ != null) + { + size += 1 + pb::CodedOutputStream.ComputeMessageSize(Account); + } + if (Region != 0) + { + size += 1 + pb::CodedOutputStream.ComputeUInt32Size(Region); + } + if (Flag != 0UL) + { + size += 1 + pb::CodedOutputStream.ComputeUInt64Size(Flag); + } + if (Active != false) + { + size += 1 + 1; + } + return size; + } + + public void MergeFrom(AccountFlagUpdateRequest other) + { + if (other == null) + { + return; + } + if (other.account_ != null) + { + if (account_ == null) + { + account_ = new Bgs.Protocol.Account.V1.AccountId(); + } + Account.MergeFrom(other.Account); + } + if (other.Region != 0) + { + Region = other.Region; + } + if (other.Flag != 0UL) + { + Flag = other.Flag; + } + if (other.Active != false) + { + Active = other.Active; + } + } + + public void MergeFrom(pb::CodedInputStream input) + { + uint tag; + while ((tag = input.ReadTag()) != 0) + { + switch (tag) + { + default: + input.SkipLastField(); + break; + case 10: + { + if (account_ == null) + { + account_ = new Bgs.Protocol.Account.V1.AccountId(); + } + input.ReadMessage(account_); + break; + } + case 16: + { + Region = input.ReadUInt32(); + break; + } + case 24: + { + Flag = input.ReadUInt64(); + break; + } + case 32: + { + Active = input.ReadBool(); + break; + } + } + } + } + + } + + [System.Diagnostics.DebuggerNonUserCodeAttribute()] + public sealed partial class GameAccountFlagUpdateRequest : pb::IMessage + { + private static readonly pb::MessageParser _parser = new pb::MessageParser(() => new GameAccountFlagUpdateRequest()); + public static pb::MessageParser Parser { get { return _parser; } } + + public static pbr::MessageDescriptor Descriptor + { + get { return Bgs.Protocol.Account.V1.AccountServiceReflection.Descriptor.MessageTypes[7]; } + } + + pbr::MessageDescriptor pb::IMessage.Descriptor + { + get { return Descriptor; } + } + + public GameAccountFlagUpdateRequest() + { + OnConstruction(); + } + + partial void OnConstruction(); + + public GameAccountFlagUpdateRequest(GameAccountFlagUpdateRequest other) : this() + { + GameAccount = other.gameAccount_ != null ? other.GameAccount.Clone() : null; + flag_ = other.flag_; + active_ = other.active_; + } + + public GameAccountFlagUpdateRequest Clone() + { + return new GameAccountFlagUpdateRequest(this); + } + + /// Field number for the "game_account" field. + public const int GameAccountFieldNumber = 1; + private Bgs.Protocol.Account.V1.GameAccountHandle gameAccount_; + public Bgs.Protocol.Account.V1.GameAccountHandle GameAccount + { + get { return gameAccount_; } + set + { + gameAccount_ = value; + } + } + + /// Field number for the "flag" field. + public const int FlagFieldNumber = 2; + private ulong flag_; + public ulong Flag + { + get { return flag_; } + set + { + flag_ = value; + } + } + + /// Field number for the "active" field. + public const int ActiveFieldNumber = 3; + private bool active_; + public bool Active + { + get { return active_; } + set + { + active_ = value; + } + } + + public override bool Equals(object other) + { + return Equals(other as GameAccountFlagUpdateRequest); + } + + public bool Equals(GameAccountFlagUpdateRequest other) + { + if (ReferenceEquals(other, null)) + { + return false; + } + if (ReferenceEquals(other, this)) + { + return true; + } + if (!object.Equals(GameAccount, other.GameAccount)) return false; + if (Flag != other.Flag) return false; + if (Active != other.Active) return false; + return true; + } + + public override int GetHashCode() + { + int hash = 1; + if (gameAccount_ != null) hash ^= GameAccount.GetHashCode(); + if (Flag != 0UL) hash ^= Flag.GetHashCode(); + if (Active != false) hash ^= Active.GetHashCode(); + return hash; + } + + public override string ToString() + { + return pb::JsonFormatter.ToDiagnosticString(this); + } + + public void WriteTo(pb::CodedOutputStream output) + { + if (gameAccount_ != null) + { + output.WriteRawTag(10); + output.WriteMessage(GameAccount); + } + if (Flag != 0UL) + { + output.WriteRawTag(16); + output.WriteUInt64(Flag); + } + if (Active != false) + { + output.WriteRawTag(24); + output.WriteBool(Active); + } + } + + public int CalculateSize() + { + int size = 0; + if (gameAccount_ != null) + { + size += 1 + pb::CodedOutputStream.ComputeMessageSize(GameAccount); + } + if (Flag != 0UL) + { + size += 1 + pb::CodedOutputStream.ComputeUInt64Size(Flag); + } + if (Active != false) + { + size += 1 + 1; + } + return size; + } + + public void MergeFrom(GameAccountFlagUpdateRequest other) + { + if (other == null) + { + return; + } + if (other.gameAccount_ != null) + { + if (gameAccount_ == null) + { + gameAccount_ = new Bgs.Protocol.Account.V1.GameAccountHandle(); + } + GameAccount.MergeFrom(other.GameAccount); + } + if (other.Flag != 0UL) + { + Flag = other.Flag; + } + if (other.Active != false) + { + Active = other.Active; + } + } + + public void MergeFrom(pb::CodedInputStream input) + { + uint tag; + while ((tag = input.ReadTag()) != 0) + { + switch (tag) + { + default: + input.SkipLastField(); + break; + case 10: + { + if (gameAccount_ == null) + { + gameAccount_ = new Bgs.Protocol.Account.V1.GameAccountHandle(); + } + input.ReadMessage(gameAccount_); + break; + } + case 16: + { + Flag = input.ReadUInt64(); + break; + } + case 24: + { + Active = input.ReadBool(); + break; + } + } + } + } + + } + + [System.Diagnostics.DebuggerNonUserCodeAttribute()] + public sealed partial class SubscriptionUpdateRequest : pb::IMessage + { + private static readonly pb::MessageParser _parser = new pb::MessageParser(() => new SubscriptionUpdateRequest()); + public static pb::MessageParser Parser { get { return _parser; } } + + public static pbr::MessageDescriptor Descriptor + { + get { return Bgs.Protocol.Account.V1.AccountServiceReflection.Descriptor.MessageTypes[8]; } + } + + pbr::MessageDescriptor pb::IMessage.Descriptor + { + get { return Descriptor; } + } + + public SubscriptionUpdateRequest() + { + OnConstruction(); + } + + partial void OnConstruction(); + + public SubscriptionUpdateRequest(SubscriptionUpdateRequest other) : this() + { + ref_ = other.ref_.Clone(); + } + + public SubscriptionUpdateRequest Clone() + { + return new SubscriptionUpdateRequest(this); + } + + /// Field number for the "ref" field. + public const int RefFieldNumber = 2; + private static readonly pb::FieldCodec _repeated_ref_codec + = pb::FieldCodec.ForMessage(18, Bgs.Protocol.Account.V1.SubscriberReference.Parser); + private readonly pbc::RepeatedField ref_ = new pbc::RepeatedField(); + public pbc::RepeatedField Ref + { + get { return ref_; } + } + + public override bool Equals(object other) + { + return Equals(other as SubscriptionUpdateRequest); + } + + public bool Equals(SubscriptionUpdateRequest other) + { + if (ReferenceEquals(other, null)) + { + return false; + } + if (ReferenceEquals(other, this)) + { + return true; + } + if (!ref_.Equals(other.ref_)) return false; + return true; + } + + public override int GetHashCode() + { + int hash = 1; + hash ^= ref_.GetHashCode(); + return hash; + } + + public override string ToString() + { + return pb::JsonFormatter.ToDiagnosticString(this); + } + + public void WriteTo(pb::CodedOutputStream output) + { + ref_.WriteTo(output, _repeated_ref_codec); + } + + public int CalculateSize() + { + int size = 0; + size += ref_.CalculateSize(_repeated_ref_codec); + return size; + } + + public void MergeFrom(SubscriptionUpdateRequest other) + { + if (other == null) + { + return; + } + ref_.Add(other.ref_); + } + + public void MergeFrom(pb::CodedInputStream input) + { + uint tag; + while ((tag = input.ReadTag()) != 0) + { + switch (tag) + { + default: + input.SkipLastField(); + break; + case 18: + { + ref_.AddEntriesFrom(input, _repeated_ref_codec); + break; + } + } + } + } + + } + + [System.Diagnostics.DebuggerNonUserCodeAttribute()] + public sealed partial class SubscriptionUpdateResponse : pb::IMessage + { + private static readonly pb::MessageParser _parser = new pb::MessageParser(() => new SubscriptionUpdateResponse()); + public static pb::MessageParser Parser { get { return _parser; } } + + public static pbr::MessageDescriptor Descriptor + { + get { return Bgs.Protocol.Account.V1.AccountServiceReflection.Descriptor.MessageTypes[9]; } + } + + pbr::MessageDescriptor pb::IMessage.Descriptor + { + get { return Descriptor; } + } + + public SubscriptionUpdateResponse() + { + OnConstruction(); + } + + partial void OnConstruction(); + + public SubscriptionUpdateResponse(SubscriptionUpdateResponse other) : this() + { + ref_ = other.ref_.Clone(); + } + + public SubscriptionUpdateResponse Clone() + { + return new SubscriptionUpdateResponse(this); + } + + /// Field number for the "ref" field. + public const int RefFieldNumber = 1; + private static readonly pb::FieldCodec _repeated_ref_codec + = pb::FieldCodec.ForMessage(10, Bgs.Protocol.Account.V1.SubscriberReference.Parser); + private readonly pbc::RepeatedField ref_ = new pbc::RepeatedField(); + public pbc::RepeatedField Ref + { + get { return ref_; } + } + + public override bool Equals(object other) + { + return Equals(other as SubscriptionUpdateResponse); + } + + public bool Equals(SubscriptionUpdateResponse other) + { + if (ReferenceEquals(other, null)) + { + return false; + } + if (ReferenceEquals(other, this)) + { + return true; + } + if (!ref_.Equals(other.ref_)) return false; + return true; + } + + public override int GetHashCode() + { + int hash = 1; + hash ^= ref_.GetHashCode(); + return hash; + } + + public override string ToString() + { + return pb::JsonFormatter.ToDiagnosticString(this); + } + + public void WriteTo(pb::CodedOutputStream output) + { + ref_.WriteTo(output, _repeated_ref_codec); + } + + public int CalculateSize() + { + int size = 0; + size += ref_.CalculateSize(_repeated_ref_codec); + return size; + } + + public void MergeFrom(SubscriptionUpdateResponse other) + { + if (other == null) + { + return; + } + ref_.Add(other.ref_); + } + + public void MergeFrom(pb::CodedInputStream input) + { + uint tag; + while ((tag = input.ReadTag()) != 0) + { + switch (tag) + { + default: + input.SkipLastField(); + break; + case 10: + { + ref_.AddEntriesFrom(input, _repeated_ref_codec); + break; + } + } + } + } + + } + + [System.Diagnostics.DebuggerNonUserCodeAttribute()] + public sealed partial class IsIgrAddressRequest : pb::IMessage + { + private static readonly pb::MessageParser _parser = new pb::MessageParser(() => new IsIgrAddressRequest()); + public static pb::MessageParser Parser { get { return _parser; } } + + public static pbr::MessageDescriptor Descriptor + { + get { return Bgs.Protocol.Account.V1.AccountServiceReflection.Descriptor.MessageTypes[10]; } + } + + pbr::MessageDescriptor pb::IMessage.Descriptor + { + get { return Descriptor; } + } + + public IsIgrAddressRequest() + { + OnConstruction(); + } + + partial void OnConstruction(); + + public IsIgrAddressRequest(IsIgrAddressRequest other) : this() + { + clientAddress_ = other.clientAddress_; + region_ = other.region_; + } + + public IsIgrAddressRequest Clone() + { + return new IsIgrAddressRequest(this); + } + + /// Field number for the "client_address" field. + public const int ClientAddressFieldNumber = 1; + private string clientAddress_ = ""; + public string ClientAddress + { + get { return clientAddress_; } + set + { + clientAddress_ = pb::ProtoPreconditions.CheckNotNull(value, "value"); + } + } + + /// Field number for the "region" field. + public const int RegionFieldNumber = 2; + private uint region_; + public uint Region + { + get { return region_; } + set + { + region_ = value; + } + } + + public override bool Equals(object other) + { + return Equals(other as IsIgrAddressRequest); + } + + public bool Equals(IsIgrAddressRequest other) + { + if (ReferenceEquals(other, null)) + { + return false; + } + if (ReferenceEquals(other, this)) + { + return true; + } + if (ClientAddress != other.ClientAddress) return false; + if (Region != other.Region) return false; + return true; + } + + public override int GetHashCode() + { + int hash = 1; + if (ClientAddress.Length != 0) hash ^= ClientAddress.GetHashCode(); + if (Region != 0) hash ^= Region.GetHashCode(); + return hash; + } + + public override string ToString() + { + return pb::JsonFormatter.ToDiagnosticString(this); + } + + public void WriteTo(pb::CodedOutputStream output) + { + if (ClientAddress.Length != 0) + { + output.WriteRawTag(10); + output.WriteString(ClientAddress); + } + if (Region != 0) + { + output.WriteRawTag(16); + output.WriteUInt32(Region); + } + } + + public int CalculateSize() + { + int size = 0; + if (ClientAddress.Length != 0) + { + size += 1 + pb::CodedOutputStream.ComputeStringSize(ClientAddress); + } + if (Region != 0) + { + size += 1 + pb::CodedOutputStream.ComputeUInt32Size(Region); + } + return size; + } + + public void MergeFrom(IsIgrAddressRequest other) + { + if (other == null) + { + return; + } + if (other.ClientAddress.Length != 0) + { + ClientAddress = other.ClientAddress; + } + if (other.Region != 0) + { + Region = other.Region; + } + } + + public void MergeFrom(pb::CodedInputStream input) + { + uint tag; + while ((tag = input.ReadTag()) != 0) + { + switch (tag) + { + default: + input.SkipLastField(); + break; + case 10: + { + ClientAddress = input.ReadString(); + break; + } + case 16: + { + Region = input.ReadUInt32(); + break; + } + } + } + } + + } + + [System.Diagnostics.DebuggerNonUserCodeAttribute()] + public sealed partial class AccountServiceRegion : pb::IMessage + { + private static readonly pb::MessageParser _parser = new pb::MessageParser(() => new AccountServiceRegion()); + public static pb::MessageParser Parser { get { return _parser; } } + + public static pbr::MessageDescriptor Descriptor + { + get { return Bgs.Protocol.Account.V1.AccountServiceReflection.Descriptor.MessageTypes[11]; } + } + + pbr::MessageDescriptor pb::IMessage.Descriptor + { + get { return Descriptor; } + } + + public AccountServiceRegion() + { + OnConstruction(); + } + + partial void OnConstruction(); + + public AccountServiceRegion(AccountServiceRegion other) : this() + { + id_ = other.id_; + shard_ = other.shard_; + } + + public AccountServiceRegion Clone() + { + return new AccountServiceRegion(this); + } + + /// Field number for the "id" field. + public const int IdFieldNumber = 1; + private uint id_; + public uint Id + { + get { return id_; } + set + { + id_ = value; + } + } + + /// Field number for the "shard" field. + public const int ShardFieldNumber = 2; + private string shard_ = ""; + public string Shard + { + get { return shard_; } + set + { + shard_ = pb::ProtoPreconditions.CheckNotNull(value, "value"); + } + } + + public override bool Equals(object other) + { + return Equals(other as AccountServiceRegion); + } + + public bool Equals(AccountServiceRegion other) + { + if (ReferenceEquals(other, null)) + { + return false; + } + if (ReferenceEquals(other, this)) + { + return true; + } + if (Id != other.Id) return false; + if (Shard != other.Shard) return false; + return true; + } + + public override int GetHashCode() + { + int hash = 1; + if (Id != 0) hash ^= Id.GetHashCode(); + if (Shard.Length != 0) hash ^= Shard.GetHashCode(); + return hash; + } + + public override string ToString() + { + return pb::JsonFormatter.ToDiagnosticString(this); + } + + public void WriteTo(pb::CodedOutputStream output) + { + if (Id != 0) + { + output.WriteRawTag(8); + output.WriteUInt32(Id); + } + if (Shard.Length != 0) + { + output.WriteRawTag(18); + output.WriteString(Shard); + } + } + + public int CalculateSize() + { + int size = 0; + if (Id != 0) + { + size += 1 + pb::CodedOutputStream.ComputeUInt32Size(Id); + } + if (Shard.Length != 0) + { + size += 1 + pb::CodedOutputStream.ComputeStringSize(Shard); + } + return size; + } + + public void MergeFrom(AccountServiceRegion other) + { + if (other == null) + { + return; + } + if (other.Id != 0) + { + Id = other.Id; + } + if (other.Shard.Length != 0) + { + Shard = other.Shard; + } + } + + public void MergeFrom(pb::CodedInputStream input) + { + uint tag; + while ((tag = input.ReadTag()) != 0) + { + switch (tag) + { + default: + input.SkipLastField(); + break; + case 8: + { + Id = input.ReadUInt32(); + break; + } + case 18: + { + Shard = input.ReadString(); + break; + } + } + } + } + + } + + [System.Diagnostics.DebuggerNonUserCodeAttribute()] + public sealed partial class AccountServiceConfig : pb::IMessage + { + private static readonly pb::MessageParser _parser = new pb::MessageParser(() => new AccountServiceConfig()); + public static pb::MessageParser Parser { get { return _parser; } } + + public static pbr::MessageDescriptor Descriptor + { + get { return Bgs.Protocol.Account.V1.AccountServiceReflection.Descriptor.MessageTypes[12]; } + } + + pbr::MessageDescriptor pb::IMessage.Descriptor + { + get { return Descriptor; } + } + + public AccountServiceConfig() + { + OnConstruction(); + } + + partial void OnConstruction(); + + public AccountServiceConfig(AccountServiceConfig other) : this() + { + region_ = other.region_.Clone(); + } + + public AccountServiceConfig Clone() + { + return new AccountServiceConfig(this); + } + + /// Field number for the "region" field. + public const int RegionFieldNumber = 1; + private static readonly pb::FieldCodec _repeated_region_codec + = pb::FieldCodec.ForMessage(10, Bgs.Protocol.Account.V1.AccountServiceRegion.Parser); + private readonly pbc::RepeatedField region_ = new pbc::RepeatedField(); + public pbc::RepeatedField Region + { + get { return region_; } + } + + public override bool Equals(object other) + { + return Equals(other as AccountServiceConfig); + } + + public bool Equals(AccountServiceConfig other) + { + if (ReferenceEquals(other, null)) + { + return false; + } + if (ReferenceEquals(other, this)) + { + return true; + } + if (!region_.Equals(other.region_)) return false; + return true; + } + + public override int GetHashCode() + { + int hash = 1; + hash ^= region_.GetHashCode(); + return hash; + } + + public override string ToString() + { + return pb::JsonFormatter.ToDiagnosticString(this); + } + + public void WriteTo(pb::CodedOutputStream output) + { + region_.WriteTo(output, _repeated_region_codec); + } + + public int CalculateSize() + { + int size = 0; + size += region_.CalculateSize(_repeated_region_codec); + return size; + } + + public void MergeFrom(AccountServiceConfig other) + { + if (other == null) + { + return; + } + region_.Add(other.region_); + } + + public void MergeFrom(pb::CodedInputStream input) + { + uint tag; + while ((tag = input.ReadTag()) != 0) + { + switch (tag) + { + default: + input.SkipLastField(); + break; + case 10: + { + region_.AddEntriesFrom(input, _repeated_region_codec); + break; + } + } + } + } + + } + + [System.Diagnostics.DebuggerNonUserCodeAttribute()] + public sealed partial class GetAccountStateRequest : pb::IMessage + { + private static readonly pb::MessageParser _parser = new pb::MessageParser(() => new GetAccountStateRequest()); + public static pb::MessageParser Parser { get { return _parser; } } + + public static pbr::MessageDescriptor Descriptor + { + get { return Bgs.Protocol.Account.V1.AccountServiceReflection.Descriptor.MessageTypes[13]; } + } + + pbr::MessageDescriptor pb::IMessage.Descriptor + { + get { return Descriptor; } + } + + public GetAccountStateRequest() + { + OnConstruction(); + } + + partial void OnConstruction(); + + public GetAccountStateRequest(GetAccountStateRequest other) : this() + { + EntityId = other.entityId_ != null ? other.EntityId.Clone() : null; + program_ = other.program_; + region_ = other.region_; + Options = other.options_ != null ? other.Options.Clone() : null; + Tags = other.tags_ != null ? other.Tags.Clone() : null; + } + + public GetAccountStateRequest Clone() + { + return new GetAccountStateRequest(this); + } + + /// Field number for the "entity_id" field. + public const int EntityIdFieldNumber = 1; + private Bgs.Protocol.EntityId entityId_; + public Bgs.Protocol.EntityId EntityId + { + get { return entityId_; } + set + { + entityId_ = value; + } + } + + /// Field number for the "program" field. + public const int ProgramFieldNumber = 2; + private uint program_; + public uint Program + { + get { return program_; } + set + { + program_ = value; + } + } + + /// Field number for the "region" field. + public const int RegionFieldNumber = 3; + private uint region_; + public uint Region + { + get { return region_; } + set + { + region_ = value; + } + } + + /// Field number for the "options" field. + public const int OptionsFieldNumber = 10; + private Bgs.Protocol.Account.V1.AccountFieldOptions options_; + public Bgs.Protocol.Account.V1.AccountFieldOptions Options + { + get { return options_; } + set + { + options_ = value; + } + } + + /// Field number for the "tags" field. + public const int TagsFieldNumber = 11; + private Bgs.Protocol.Account.V1.AccountFieldTags tags_; + public Bgs.Protocol.Account.V1.AccountFieldTags Tags + { + get { return tags_; } + set + { + tags_ = value; + } + } + + public override bool Equals(object other) + { + return Equals(other as GetAccountStateRequest); + } + + public bool Equals(GetAccountStateRequest other) + { + if (ReferenceEquals(other, null)) + { + return false; + } + if (ReferenceEquals(other, this)) + { + return true; + } + if (!object.Equals(EntityId, other.EntityId)) return false; + if (Program != other.Program) return false; + if (Region != other.Region) return false; + if (!object.Equals(Options, other.Options)) return false; + if (!object.Equals(Tags, other.Tags)) return false; + return true; + } + + public override int GetHashCode() + { + int hash = 1; + if (entityId_ != null) hash ^= EntityId.GetHashCode(); + if (Program != 0) hash ^= Program.GetHashCode(); + if (Region != 0) hash ^= Region.GetHashCode(); + if (options_ != null) hash ^= Options.GetHashCode(); + if (tags_ != null) hash ^= Tags.GetHashCode(); + return hash; + } + + public override string ToString() + { + return pb::JsonFormatter.ToDiagnosticString(this); + } + + public void WriteTo(pb::CodedOutputStream output) + { + if (entityId_ != null) + { + output.WriteRawTag(10); + output.WriteMessage(EntityId); + } + if (Program != 0) + { + output.WriteRawTag(16); + output.WriteUInt32(Program); + } + if (Region != 0) + { + output.WriteRawTag(24); + output.WriteUInt32(Region); + } + if (options_ != null) + { + output.WriteRawTag(82); + output.WriteMessage(Options); + } + if (tags_ != null) + { + output.WriteRawTag(90); + output.WriteMessage(Tags); + } + } + + public int CalculateSize() + { + int size = 0; + if (entityId_ != null) + { + size += 1 + pb::CodedOutputStream.ComputeMessageSize(EntityId); + } + if (Program != 0) + { + size += 1 + pb::CodedOutputStream.ComputeUInt32Size(Program); + } + if (Region != 0) + { + size += 1 + pb::CodedOutputStream.ComputeUInt32Size(Region); + } + if (options_ != null) + { + size += 1 + pb::CodedOutputStream.ComputeMessageSize(Options); + } + if (tags_ != null) + { + size += 1 + pb::CodedOutputStream.ComputeMessageSize(Tags); + } + return size; + } + + public void MergeFrom(GetAccountStateRequest other) + { + if (other == null) + { + return; + } + if (other.entityId_ != null) + { + if (entityId_ == null) + { + entityId_ = new Bgs.Protocol.EntityId(); + } + EntityId.MergeFrom(other.EntityId); + } + if (other.Program != 0) + { + Program = other.Program; + } + if (other.Region != 0) + { + Region = other.Region; + } + if (other.options_ != null) + { + if (options_ == null) + { + options_ = new Bgs.Protocol.Account.V1.AccountFieldOptions(); + } + Options.MergeFrom(other.Options); + } + if (other.tags_ != null) + { + if (tags_ == null) + { + tags_ = new Bgs.Protocol.Account.V1.AccountFieldTags(); + } + Tags.MergeFrom(other.Tags); + } + } + + public void MergeFrom(pb::CodedInputStream input) + { + uint tag; + while ((tag = input.ReadTag()) != 0) + { + switch (tag) + { + default: + input.SkipLastField(); + break; + case 10: + { + if (entityId_ == null) + { + entityId_ = new Bgs.Protocol.EntityId(); + } + input.ReadMessage(entityId_); + break; + } + case 16: + { + Program = input.ReadUInt32(); + break; + } + case 24: + { + Region = input.ReadUInt32(); + break; + } + case 82: + { + if (options_ == null) + { + options_ = new Bgs.Protocol.Account.V1.AccountFieldOptions(); + } + input.ReadMessage(options_); + break; + } + case 90: + { + if (tags_ == null) + { + tags_ = new Bgs.Protocol.Account.V1.AccountFieldTags(); + } + input.ReadMessage(tags_); + break; + } + } + } + } + + } + + [System.Diagnostics.DebuggerNonUserCodeAttribute()] + public sealed partial class GetAccountStateResponse : pb::IMessage + { + private static readonly pb::MessageParser _parser = new pb::MessageParser(() => new GetAccountStateResponse()); + public static pb::MessageParser Parser { get { return _parser; } } + + public static pbr::MessageDescriptor Descriptor + { + get { return Bgs.Protocol.Account.V1.AccountServiceReflection.Descriptor.MessageTypes[14]; } + } + + pbr::MessageDescriptor pb::IMessage.Descriptor + { + get { return Descriptor; } + } + + public GetAccountStateResponse() + { + OnConstruction(); + } + + partial void OnConstruction(); + + public GetAccountStateResponse(GetAccountStateResponse other) : this() + { + State = other.state_ != null ? other.State.Clone() : null; + Tags = other.tags_ != null ? other.Tags.Clone() : null; + } + + public GetAccountStateResponse Clone() + { + return new GetAccountStateResponse(this); + } + + /// Field number for the "state" field. + public const int StateFieldNumber = 1; + private Bgs.Protocol.Account.V1.AccountState state_; + public Bgs.Protocol.Account.V1.AccountState State + { + get { return state_; } + set + { + state_ = value; + } + } + + /// Field number for the "tags" field. + public const int TagsFieldNumber = 2; + private Bgs.Protocol.Account.V1.AccountFieldTags tags_; + public Bgs.Protocol.Account.V1.AccountFieldTags Tags + { + get { return tags_; } + set + { + tags_ = value; + } + } + + public override bool Equals(object other) + { + return Equals(other as GetAccountStateResponse); + } + + public bool Equals(GetAccountStateResponse other) + { + if (ReferenceEquals(other, null)) + { + return false; + } + if (ReferenceEquals(other, this)) + { + return true; + } + if (!object.Equals(State, other.State)) return false; + if (!object.Equals(Tags, other.Tags)) return false; + return true; + } + + public override int GetHashCode() + { + int hash = 1; + if (state_ != null) hash ^= State.GetHashCode(); + if (tags_ != null) hash ^= Tags.GetHashCode(); + return hash; + } + + public override string ToString() + { + return pb::JsonFormatter.ToDiagnosticString(this); + } + + public void WriteTo(pb::CodedOutputStream output) + { + if (state_ != null) + { + output.WriteRawTag(10); + output.WriteMessage(State); + } + if (tags_ != null) + { + output.WriteRawTag(18); + output.WriteMessage(Tags); + } + } + + public int CalculateSize() + { + int size = 0; + if (state_ != null) + { + size += 1 + pb::CodedOutputStream.ComputeMessageSize(State); + } + if (tags_ != null) + { + size += 1 + pb::CodedOutputStream.ComputeMessageSize(Tags); + } + return size; + } + + public void MergeFrom(GetAccountStateResponse other) + { + if (other == null) + { + return; + } + if (other.state_ != null) + { + if (state_ == null) + { + state_ = new Bgs.Protocol.Account.V1.AccountState(); + } + State.MergeFrom(other.State); + } + if (other.tags_ != null) + { + if (tags_ == null) + { + tags_ = new Bgs.Protocol.Account.V1.AccountFieldTags(); + } + Tags.MergeFrom(other.Tags); + } + } + + public void MergeFrom(pb::CodedInputStream input) + { + uint tag; + while ((tag = input.ReadTag()) != 0) + { + switch (tag) + { + default: + input.SkipLastField(); + break; + case 10: + { + if (state_ == null) + { + state_ = new Bgs.Protocol.Account.V1.AccountState(); + } + input.ReadMessage(state_); + break; + } + case 18: + { + if (tags_ == null) + { + tags_ = new Bgs.Protocol.Account.V1.AccountFieldTags(); + } + input.ReadMessage(tags_); + break; + } + } + } + } + + } + + [System.Diagnostics.DebuggerNonUserCodeAttribute()] + public sealed partial class GetGameAccountStateRequest : pb::IMessage + { + private static readonly pb::MessageParser _parser = new pb::MessageParser(() => new GetGameAccountStateRequest()); + public static pb::MessageParser Parser { get { return _parser; } } + + public static pbr::MessageDescriptor Descriptor + { + get { return Bgs.Protocol.Account.V1.AccountServiceReflection.Descriptor.MessageTypes[15]; } + } + + pbr::MessageDescriptor pb::IMessage.Descriptor + { + get { return Descriptor; } + } + + public GetGameAccountStateRequest() + { + OnConstruction(); + } + + partial void OnConstruction(); + + public GetGameAccountStateRequest(GetGameAccountStateRequest other) : this() + { + AccountId = other.accountId_ != null ? other.AccountId.Clone() : null; + GameAccountId = other.gameAccountId_ != null ? other.GameAccountId.Clone() : null; + Options = other.options_ != null ? other.Options.Clone() : null; + Tags = other.tags_ != null ? other.Tags.Clone() : null; + } + + public GetGameAccountStateRequest Clone() + { + return new GetGameAccountStateRequest(this); + } + + /// Field number for the "account_id" field. + public const int AccountIdFieldNumber = 1; + private Bgs.Protocol.EntityId accountId_; + [System.ObsoleteAttribute()] + public Bgs.Protocol.EntityId AccountId + { + get { return accountId_; } + set + { + accountId_ = value; + } + } + + /// Field number for the "game_account_id" field. + public const int GameAccountIdFieldNumber = 2; + private Bgs.Protocol.EntityId gameAccountId_; + public Bgs.Protocol.EntityId GameAccountId + { + get { return gameAccountId_; } + set + { + gameAccountId_ = value; + } + } + + /// Field number for the "options" field. + public const int OptionsFieldNumber = 10; + private Bgs.Protocol.Account.V1.GameAccountFieldOptions options_; + public Bgs.Protocol.Account.V1.GameAccountFieldOptions Options + { + get { return options_; } + set + { + options_ = value; + } + } + + /// Field number for the "tags" field. + public const int TagsFieldNumber = 11; + private Bgs.Protocol.Account.V1.GameAccountFieldTags tags_; + public Bgs.Protocol.Account.V1.GameAccountFieldTags Tags + { + get { return tags_; } + set + { + tags_ = value; + } + } + + public override bool Equals(object other) + { + return Equals(other as GetGameAccountStateRequest); + } + + public bool Equals(GetGameAccountStateRequest other) + { + if (ReferenceEquals(other, null)) + { + return false; + } + if (ReferenceEquals(other, this)) + { + return true; + } + if (!object.Equals(AccountId, other.AccountId)) return false; + if (!object.Equals(GameAccountId, other.GameAccountId)) return false; + if (!object.Equals(Options, other.Options)) return false; + if (!object.Equals(Tags, other.Tags)) return false; + return true; + } + + public override int GetHashCode() + { + int hash = 1; + if (accountId_ != null) hash ^= AccountId.GetHashCode(); + if (gameAccountId_ != null) hash ^= GameAccountId.GetHashCode(); + if (options_ != null) hash ^= Options.GetHashCode(); + if (tags_ != null) hash ^= Tags.GetHashCode(); + return hash; + } + + public override string ToString() + { + return pb::JsonFormatter.ToDiagnosticString(this); + } + + public void WriteTo(pb::CodedOutputStream output) + { + if (accountId_ != null) + { + output.WriteRawTag(10); + output.WriteMessage(AccountId); + } + if (gameAccountId_ != null) + { + output.WriteRawTag(18); + output.WriteMessage(GameAccountId); + } + if (options_ != null) + { + output.WriteRawTag(82); + output.WriteMessage(Options); + } + if (tags_ != null) + { + output.WriteRawTag(90); + output.WriteMessage(Tags); + } + } + + public int CalculateSize() + { + int size = 0; + if (accountId_ != null) + { + size += 1 + pb::CodedOutputStream.ComputeMessageSize(AccountId); + } + if (gameAccountId_ != null) + { + size += 1 + pb::CodedOutputStream.ComputeMessageSize(GameAccountId); + } + if (options_ != null) + { + size += 1 + pb::CodedOutputStream.ComputeMessageSize(Options); + } + if (tags_ != null) + { + size += 1 + pb::CodedOutputStream.ComputeMessageSize(Tags); + } + return size; + } + + public void MergeFrom(GetGameAccountStateRequest other) + { + if (other == null) + { + return; + } + if (other.accountId_ != null) + { + if (accountId_ == null) + { + accountId_ = new Bgs.Protocol.EntityId(); + } + AccountId.MergeFrom(other.AccountId); + } + if (other.gameAccountId_ != null) + { + if (gameAccountId_ == null) + { + gameAccountId_ = new Bgs.Protocol.EntityId(); + } + GameAccountId.MergeFrom(other.GameAccountId); + } + if (other.options_ != null) + { + if (options_ == null) + { + options_ = new Bgs.Protocol.Account.V1.GameAccountFieldOptions(); + } + Options.MergeFrom(other.Options); + } + if (other.tags_ != null) + { + if (tags_ == null) + { + tags_ = new Bgs.Protocol.Account.V1.GameAccountFieldTags(); + } + Tags.MergeFrom(other.Tags); + } + } + + public void MergeFrom(pb::CodedInputStream input) + { + uint tag; + while ((tag = input.ReadTag()) != 0) + { + switch (tag) + { + default: + input.SkipLastField(); + break; + case 10: + { + if (accountId_ == null) + { + accountId_ = new Bgs.Protocol.EntityId(); + } + input.ReadMessage(accountId_); + break; + } + case 18: + { + if (gameAccountId_ == null) + { + gameAccountId_ = new Bgs.Protocol.EntityId(); + } + input.ReadMessage(gameAccountId_); + break; + } + case 82: + { + if (options_ == null) + { + options_ = new Bgs.Protocol.Account.V1.GameAccountFieldOptions(); + } + input.ReadMessage(options_); + break; + } + case 90: + { + if (tags_ == null) + { + tags_ = new Bgs.Protocol.Account.V1.GameAccountFieldTags(); + } + input.ReadMessage(tags_); + break; + } + } + } + } + + } + + [System.Diagnostics.DebuggerNonUserCodeAttribute()] + public sealed partial class GetGameAccountStateResponse : pb::IMessage + { + private static readonly pb::MessageParser _parser = new pb::MessageParser(() => new GetGameAccountStateResponse()); + public static pb::MessageParser Parser { get { return _parser; } } + + public static pbr::MessageDescriptor Descriptor + { + get { return Bgs.Protocol.Account.V1.AccountServiceReflection.Descriptor.MessageTypes[16]; } + } + + pbr::MessageDescriptor pb::IMessage.Descriptor + { + get { return Descriptor; } + } + + public GetGameAccountStateResponse() + { + OnConstruction(); + } + + partial void OnConstruction(); + + public GetGameAccountStateResponse(GetGameAccountStateResponse other) : this() + { + State = other.state_ != null ? other.State.Clone() : null; + Tags = other.tags_ != null ? other.Tags.Clone() : null; + } + + public GetGameAccountStateResponse Clone() + { + return new GetGameAccountStateResponse(this); + } + + /// Field number for the "state" field. + public const int StateFieldNumber = 1; + private Bgs.Protocol.Account.V1.GameAccountState state_; + public Bgs.Protocol.Account.V1.GameAccountState State + { + get { return state_; } + set + { + state_ = value; + } + } + + /// Field number for the "tags" field. + public const int TagsFieldNumber = 2; + private Bgs.Protocol.Account.V1.GameAccountFieldTags tags_; + public Bgs.Protocol.Account.V1.GameAccountFieldTags Tags + { + get { return tags_; } + set + { + tags_ = value; + } + } + + public override bool Equals(object other) + { + return Equals(other as GetGameAccountStateResponse); + } + + public bool Equals(GetGameAccountStateResponse other) + { + if (ReferenceEquals(other, null)) + { + return false; + } + if (ReferenceEquals(other, this)) + { + return true; + } + if (!object.Equals(State, other.State)) return false; + if (!object.Equals(Tags, other.Tags)) return false; + return true; + } + + public override int GetHashCode() + { + int hash = 1; + if (state_ != null) hash ^= State.GetHashCode(); + if (tags_ != null) hash ^= Tags.GetHashCode(); + return hash; + } + + public override string ToString() + { + return pb::JsonFormatter.ToDiagnosticString(this); + } + + public void WriteTo(pb::CodedOutputStream output) + { + if (state_ != null) + { + output.WriteRawTag(10); + output.WriteMessage(State); + } + if (tags_ != null) + { + output.WriteRawTag(18); + output.WriteMessage(Tags); + } + } + + public int CalculateSize() + { + int size = 0; + if (state_ != null) + { + size += 1 + pb::CodedOutputStream.ComputeMessageSize(State); + } + if (tags_ != null) + { + size += 1 + pb::CodedOutputStream.ComputeMessageSize(Tags); + } + return size; + } + + public void MergeFrom(GetGameAccountStateResponse other) + { + if (other == null) + { + return; + } + if (other.state_ != null) + { + if (state_ == null) + { + state_ = new Bgs.Protocol.Account.V1.GameAccountState(); + } + State.MergeFrom(other.State); + } + if (other.tags_ != null) + { + if (tags_ == null) + { + tags_ = new Bgs.Protocol.Account.V1.GameAccountFieldTags(); + } + Tags.MergeFrom(other.Tags); + } + } + + public void MergeFrom(pb::CodedInputStream input) + { + uint tag; + while ((tag = input.ReadTag()) != 0) + { + switch (tag) + { + default: + input.SkipLastField(); + break; + case 10: + { + if (state_ == null) + { + state_ = new Bgs.Protocol.Account.V1.GameAccountState(); + } + input.ReadMessage(state_); + break; + } + case 18: + { + if (tags_ == null) + { + tags_ = new Bgs.Protocol.Account.V1.GameAccountFieldTags(); + } + input.ReadMessage(tags_); + break; + } + } + } + } + + } + + [System.Diagnostics.DebuggerNonUserCodeAttribute()] + public sealed partial class GetLicensesRequest : pb::IMessage + { + private static readonly pb::MessageParser _parser = new pb::MessageParser(() => new GetLicensesRequest()); + public static pb::MessageParser Parser { get { return _parser; } } + + public static pbr::MessageDescriptor Descriptor + { + get { return Bgs.Protocol.Account.V1.AccountServiceReflection.Descriptor.MessageTypes[17]; } + } + + pbr::MessageDescriptor pb::IMessage.Descriptor + { + get { return Descriptor; } + } + + public GetLicensesRequest() + { + OnConstruction(); + } + + partial void OnConstruction(); + + public GetLicensesRequest(GetLicensesRequest other) : this() + { + TargetId = other.targetId_ != null ? other.TargetId.Clone() : null; + fetchAccountLicenses_ = other.fetchAccountLicenses_; + fetchGameAccountLicenses_ = other.fetchGameAccountLicenses_; + fetchDynamicAccountLicenses_ = other.fetchDynamicAccountLicenses_; + program_ = other.program_; + excludeUnknownProgram_ = other.excludeUnknownProgram_; + } + + public GetLicensesRequest Clone() + { + return new GetLicensesRequest(this); + } + + /// Field number for the "target_id" field. + public const int TargetIdFieldNumber = 1; + private Bgs.Protocol.EntityId targetId_; + public Bgs.Protocol.EntityId TargetId + { + get { return targetId_; } + set + { + targetId_ = value; + } + } + + /// Field number for the "fetch_account_licenses" field. + public const int FetchAccountLicensesFieldNumber = 2; + private bool fetchAccountLicenses_; + public bool FetchAccountLicenses + { + get { return fetchAccountLicenses_; } + set + { + fetchAccountLicenses_ = value; + } + } + + /// Field number for the "fetch_game_account_licenses" field. + public const int FetchGameAccountLicensesFieldNumber = 3; + private bool fetchGameAccountLicenses_; + public bool FetchGameAccountLicenses + { + get { return fetchGameAccountLicenses_; } + set + { + fetchGameAccountLicenses_ = value; + } + } + + /// Field number for the "fetch_dynamic_account_licenses" field. + public const int FetchDynamicAccountLicensesFieldNumber = 4; + private bool fetchDynamicAccountLicenses_; + public bool FetchDynamicAccountLicenses + { + get { return fetchDynamicAccountLicenses_; } + set + { + fetchDynamicAccountLicenses_ = value; + } + } + + /// Field number for the "program" field. + public const int ProgramFieldNumber = 5; + private uint program_; + public uint Program + { + get { return program_; } + set + { + program_ = value; + } + } + + /// Field number for the "exclude_unknown_program" field. + public const int ExcludeUnknownProgramFieldNumber = 6; + private bool excludeUnknownProgram_; + public bool ExcludeUnknownProgram + { + get { return excludeUnknownProgram_; } + set + { + excludeUnknownProgram_ = value; + } + } + + public override bool Equals(object other) + { + return Equals(other as GetLicensesRequest); + } + + public bool Equals(GetLicensesRequest other) + { + if (ReferenceEquals(other, null)) + { + return false; + } + if (ReferenceEquals(other, this)) + { + return true; + } + if (!object.Equals(TargetId, other.TargetId)) return false; + if (FetchAccountLicenses != other.FetchAccountLicenses) return false; + if (FetchGameAccountLicenses != other.FetchGameAccountLicenses) return false; + if (FetchDynamicAccountLicenses != other.FetchDynamicAccountLicenses) return false; + if (Program != other.Program) return false; + if (ExcludeUnknownProgram != other.ExcludeUnknownProgram) return false; + return true; + } + + public override int GetHashCode() + { + int hash = 1; + if (targetId_ != null) hash ^= TargetId.GetHashCode(); + if (FetchAccountLicenses != false) hash ^= FetchAccountLicenses.GetHashCode(); + if (FetchGameAccountLicenses != false) hash ^= FetchGameAccountLicenses.GetHashCode(); + if (FetchDynamicAccountLicenses != false) hash ^= FetchDynamicAccountLicenses.GetHashCode(); + if (Program != 0) hash ^= Program.GetHashCode(); + if (ExcludeUnknownProgram != false) hash ^= ExcludeUnknownProgram.GetHashCode(); + return hash; + } + + public override string ToString() + { + return pb::JsonFormatter.ToDiagnosticString(this); + } + + public void WriteTo(pb::CodedOutputStream output) + { + if (targetId_ != null) + { + output.WriteRawTag(10); + output.WriteMessage(TargetId); + } + if (FetchAccountLicenses != false) + { + output.WriteRawTag(16); + output.WriteBool(FetchAccountLicenses); + } + if (FetchGameAccountLicenses != false) + { + output.WriteRawTag(24); + output.WriteBool(FetchGameAccountLicenses); + } + if (FetchDynamicAccountLicenses != false) + { + output.WriteRawTag(32); + output.WriteBool(FetchDynamicAccountLicenses); + } + if (Program != 0) + { + output.WriteRawTag(45); + output.WriteFixed32(Program); + } + if (ExcludeUnknownProgram != false) + { + output.WriteRawTag(48); + output.WriteBool(ExcludeUnknownProgram); + } + } + + public int CalculateSize() + { + int size = 0; + if (targetId_ != null) + { + size += 1 + pb::CodedOutputStream.ComputeMessageSize(TargetId); + } + if (FetchAccountLicenses != false) + { + size += 1 + 1; + } + if (FetchGameAccountLicenses != false) + { + size += 1 + 1; + } + if (FetchDynamicAccountLicenses != false) + { + size += 1 + 1; + } + if (Program != 0) + { + size += 1 + 4; + } + if (ExcludeUnknownProgram != false) + { + size += 1 + 1; + } + return size; + } + + public void MergeFrom(GetLicensesRequest other) + { + if (other == null) + { + return; + } + if (other.targetId_ != null) + { + if (targetId_ == null) + { + targetId_ = new Bgs.Protocol.EntityId(); + } + TargetId.MergeFrom(other.TargetId); + } + if (other.FetchAccountLicenses != false) + { + FetchAccountLicenses = other.FetchAccountLicenses; + } + if (other.FetchGameAccountLicenses != false) + { + FetchGameAccountLicenses = other.FetchGameAccountLicenses; + } + if (other.FetchDynamicAccountLicenses != false) + { + FetchDynamicAccountLicenses = other.FetchDynamicAccountLicenses; + } + if (other.Program != 0) + { + Program = other.Program; + } + if (other.ExcludeUnknownProgram != false) + { + ExcludeUnknownProgram = other.ExcludeUnknownProgram; + } + } + + public void MergeFrom(pb::CodedInputStream input) + { + uint tag; + while ((tag = input.ReadTag()) != 0) + { + switch (tag) + { + default: + input.SkipLastField(); + break; + case 10: + { + if (targetId_ == null) + { + targetId_ = new Bgs.Protocol.EntityId(); + } + input.ReadMessage(targetId_); + break; + } + case 16: + { + FetchAccountLicenses = input.ReadBool(); + break; + } + case 24: + { + FetchGameAccountLicenses = input.ReadBool(); + break; + } + case 32: + { + FetchDynamicAccountLicenses = input.ReadBool(); + break; + } + case 45: + { + Program = input.ReadFixed32(); + break; + } + case 48: + { + ExcludeUnknownProgram = input.ReadBool(); + break; + } + } + } + } + + } + + [System.Diagnostics.DebuggerNonUserCodeAttribute()] + public sealed partial class GetLicensesResponse : pb::IMessage + { + private static readonly pb::MessageParser _parser = new pb::MessageParser(() => new GetLicensesResponse()); + public static pb::MessageParser Parser { get { return _parser; } } + + public static pbr::MessageDescriptor Descriptor + { + get { return Bgs.Protocol.Account.V1.AccountServiceReflection.Descriptor.MessageTypes[18]; } + } + + pbr::MessageDescriptor pb::IMessage.Descriptor + { + get { return Descriptor; } + } + + public GetLicensesResponse() + { + OnConstruction(); + } + + partial void OnConstruction(); + + public GetLicensesResponse(GetLicensesResponse other) : this() + { + licenses_ = other.licenses_.Clone(); + } + + public GetLicensesResponse Clone() + { + return new GetLicensesResponse(this); + } + + /// Field number for the "licenses" field. + public const int LicensesFieldNumber = 1; + private static readonly pb::FieldCodec _repeated_licenses_codec + = pb::FieldCodec.ForMessage(10, Bgs.Protocol.Account.V1.AccountLicense.Parser); + private readonly pbc::RepeatedField licenses_ = new pbc::RepeatedField(); + public pbc::RepeatedField Licenses + { + get { return licenses_; } + } + + public override bool Equals(object other) + { + return Equals(other as GetLicensesResponse); + } + + public bool Equals(GetLicensesResponse other) + { + if (ReferenceEquals(other, null)) + { + return false; + } + if (ReferenceEquals(other, this)) + { + return true; + } + if (!licenses_.Equals(other.licenses_)) return false; + return true; + } + + public override int GetHashCode() + { + int hash = 1; + hash ^= licenses_.GetHashCode(); + return hash; + } + + public override string ToString() + { + return pb::JsonFormatter.ToDiagnosticString(this); + } + + public void WriteTo(pb::CodedOutputStream output) + { + licenses_.WriteTo(output, _repeated_licenses_codec); + } + + public int CalculateSize() + { + int size = 0; + size += licenses_.CalculateSize(_repeated_licenses_codec); + return size; + } + + public void MergeFrom(GetLicensesResponse other) + { + if (other == null) + { + return; + } + licenses_.Add(other.licenses_); + } + + public void MergeFrom(pb::CodedInputStream input) + { + uint tag; + while ((tag = input.ReadTag()) != 0) + { + switch (tag) + { + default: + input.SkipLastField(); + break; + case 10: + { + licenses_.AddEntriesFrom(input, _repeated_licenses_codec); + break; + } + } + } + } + + } + + [System.Diagnostics.DebuggerNonUserCodeAttribute()] + public sealed partial class GetGameSessionInfoRequest : pb::IMessage + { + private static readonly pb::MessageParser _parser = new pb::MessageParser(() => new GetGameSessionInfoRequest()); + public static pb::MessageParser Parser { get { return _parser; } } + + public static pbr::MessageDescriptor Descriptor + { + get { return Bgs.Protocol.Account.V1.AccountServiceReflection.Descriptor.MessageTypes[19]; } + } + + pbr::MessageDescriptor pb::IMessage.Descriptor + { + get { return Descriptor; } + } + + public GetGameSessionInfoRequest() + { + OnConstruction(); + } + + partial void OnConstruction(); + + public GetGameSessionInfoRequest(GetGameSessionInfoRequest other) : this() + { + EntityId = other.entityId_ != null ? other.EntityId.Clone() : null; + } + + public GetGameSessionInfoRequest Clone() + { + return new GetGameSessionInfoRequest(this); + } + + /// Field number for the "entity_id" field. + public const int EntityIdFieldNumber = 1; + private Bgs.Protocol.EntityId entityId_; + public Bgs.Protocol.EntityId EntityId + { + get { return entityId_; } + set + { + entityId_ = value; + } + } + + public override bool Equals(object other) + { + return Equals(other as GetGameSessionInfoRequest); + } + + public bool Equals(GetGameSessionInfoRequest other) + { + if (ReferenceEquals(other, null)) + { + return false; + } + if (ReferenceEquals(other, this)) + { + return true; + } + if (!object.Equals(EntityId, other.EntityId)) return false; + return true; + } + + public override int GetHashCode() + { + int hash = 1; + if (entityId_ != null) hash ^= EntityId.GetHashCode(); + return hash; + } + + public override string ToString() + { + return pb::JsonFormatter.ToDiagnosticString(this); + } + + public void WriteTo(pb::CodedOutputStream output) + { + if (entityId_ != null) + { + output.WriteRawTag(10); + output.WriteMessage(EntityId); + } + } + + public int CalculateSize() + { + int size = 0; + if (entityId_ != null) + { + size += 1 + pb::CodedOutputStream.ComputeMessageSize(EntityId); + } + return size; + } + + public void MergeFrom(GetGameSessionInfoRequest other) + { + if (other == null) + { + return; + } + if (other.entityId_ != null) + { + if (entityId_ == null) + { + entityId_ = new Bgs.Protocol.EntityId(); + } + EntityId.MergeFrom(other.EntityId); + } + } + + public void MergeFrom(pb::CodedInputStream input) + { + uint tag; + while ((tag = input.ReadTag()) != 0) + { + switch (tag) + { + default: + input.SkipLastField(); + break; + case 10: + { + if (entityId_ == null) + { + entityId_ = new Bgs.Protocol.EntityId(); + } + input.ReadMessage(entityId_); + break; + } + } + } + } + + } + + [System.Diagnostics.DebuggerNonUserCodeAttribute()] + public sealed partial class GetGameSessionInfoResponse : pb::IMessage + { + private static readonly pb::MessageParser _parser = new pb::MessageParser(() => new GetGameSessionInfoResponse()); + public static pb::MessageParser Parser { get { return _parser; } } + + public static pbr::MessageDescriptor Descriptor + { + get { return Bgs.Protocol.Account.V1.AccountServiceReflection.Descriptor.MessageTypes[20]; } + } + + pbr::MessageDescriptor pb::IMessage.Descriptor + { + get { return Descriptor; } + } + + public GetGameSessionInfoResponse() + { + OnConstruction(); + } + + partial void OnConstruction(); + + public GetGameSessionInfoResponse(GetGameSessionInfoResponse other) : this() + { + SessionInfo = other.sessionInfo_ != null ? other.SessionInfo.Clone() : null; + } + + public GetGameSessionInfoResponse Clone() + { + return new GetGameSessionInfoResponse(this); + } + + /// Field number for the "session_info" field. + public const int SessionInfoFieldNumber = 2; + private Bgs.Protocol.Account.V1.GameSessionInfo sessionInfo_; + public Bgs.Protocol.Account.V1.GameSessionInfo SessionInfo + { + get { return sessionInfo_; } + set + { + sessionInfo_ = value; + } + } + + public override bool Equals(object other) + { + return Equals(other as GetGameSessionInfoResponse); + } + + public bool Equals(GetGameSessionInfoResponse other) + { + if (ReferenceEquals(other, null)) + { + return false; + } + if (ReferenceEquals(other, this)) + { + return true; + } + if (!object.Equals(SessionInfo, other.SessionInfo)) return false; + return true; + } + + public override int GetHashCode() + { + int hash = 1; + if (sessionInfo_ != null) hash ^= SessionInfo.GetHashCode(); + return hash; + } + + public override string ToString() + { + return pb::JsonFormatter.ToDiagnosticString(this); + } + + public void WriteTo(pb::CodedOutputStream output) + { + if (sessionInfo_ != null) + { + output.WriteRawTag(18); + output.WriteMessage(SessionInfo); + } + } + + public int CalculateSize() + { + int size = 0; + if (sessionInfo_ != null) + { + size += 1 + pb::CodedOutputStream.ComputeMessageSize(SessionInfo); + } + return size; + } + + public void MergeFrom(GetGameSessionInfoResponse other) + { + if (other == null) + { + return; + } + if (other.sessionInfo_ != null) + { + if (sessionInfo_ == null) + { + sessionInfo_ = new Bgs.Protocol.Account.V1.GameSessionInfo(); + } + SessionInfo.MergeFrom(other.SessionInfo); + } + } + + public void MergeFrom(pb::CodedInputStream input) + { + uint tag; + while ((tag = input.ReadTag()) != 0) + { + switch (tag) + { + default: + input.SkipLastField(); + break; + case 18: + { + if (sessionInfo_ == null) + { + sessionInfo_ = new Bgs.Protocol.Account.V1.GameSessionInfo(); + } + input.ReadMessage(sessionInfo_); + break; + } + } + } + } + + } + + [System.Diagnostics.DebuggerNonUserCodeAttribute()] + public sealed partial class GetGameTimeRemainingInfoRequest : pb::IMessage + { + private static readonly pb::MessageParser _parser = new pb::MessageParser(() => new GetGameTimeRemainingInfoRequest()); + public static pb::MessageParser Parser { get { return _parser; } } + + public static pbr::MessageDescriptor Descriptor + { + get { return Bgs.Protocol.Account.V1.AccountServiceReflection.Descriptor.MessageTypes[21]; } + } + + pbr::MessageDescriptor pb::IMessage.Descriptor + { + get { return Descriptor; } + } + + public GetGameTimeRemainingInfoRequest() + { + OnConstruction(); + } + + partial void OnConstruction(); + + public GetGameTimeRemainingInfoRequest(GetGameTimeRemainingInfoRequest other) : this() + { + GameAccountId = other.gameAccountId_ != null ? other.GameAccountId.Clone() : null; + AccountId = other.accountId_ != null ? other.AccountId.Clone() : null; + } + + public GetGameTimeRemainingInfoRequest Clone() + { + return new GetGameTimeRemainingInfoRequest(this); + } + + /// Field number for the "game_account_id" field. + public const int GameAccountIdFieldNumber = 1; + private Bgs.Protocol.EntityId gameAccountId_; + public Bgs.Protocol.EntityId GameAccountId + { + get { return gameAccountId_; } + set + { + gameAccountId_ = value; + } + } + + /// Field number for the "account_id" field. + public const int AccountIdFieldNumber = 2; + private Bgs.Protocol.EntityId accountId_; + public Bgs.Protocol.EntityId AccountId + { + get { return accountId_; } + set + { + accountId_ = value; + } + } + + public override bool Equals(object other) + { + return Equals(other as GetGameTimeRemainingInfoRequest); + } + + public bool Equals(GetGameTimeRemainingInfoRequest other) + { + if (ReferenceEquals(other, null)) + { + return false; + } + if (ReferenceEquals(other, this)) + { + return true; + } + if (!object.Equals(GameAccountId, other.GameAccountId)) return false; + if (!object.Equals(AccountId, other.AccountId)) return false; + return true; + } + + public override int GetHashCode() + { + int hash = 1; + if (gameAccountId_ != null) hash ^= GameAccountId.GetHashCode(); + if (accountId_ != null) hash ^= AccountId.GetHashCode(); + return hash; + } + + public override string ToString() + { + return pb::JsonFormatter.ToDiagnosticString(this); + } + + public void WriteTo(pb::CodedOutputStream output) + { + if (gameAccountId_ != null) + { + output.WriteRawTag(10); + output.WriteMessage(GameAccountId); + } + if (accountId_ != null) + { + output.WriteRawTag(18); + output.WriteMessage(AccountId); + } + } + + public int CalculateSize() + { + int size = 0; + if (gameAccountId_ != null) + { + size += 1 + pb::CodedOutputStream.ComputeMessageSize(GameAccountId); + } + if (accountId_ != null) + { + size += 1 + pb::CodedOutputStream.ComputeMessageSize(AccountId); + } + return size; + } + + public void MergeFrom(GetGameTimeRemainingInfoRequest other) + { + if (other == null) + { + return; + } + if (other.gameAccountId_ != null) + { + if (gameAccountId_ == null) + { + gameAccountId_ = new Bgs.Protocol.EntityId(); + } + GameAccountId.MergeFrom(other.GameAccountId); + } + if (other.accountId_ != null) + { + if (accountId_ == null) + { + accountId_ = new Bgs.Protocol.EntityId(); + } + AccountId.MergeFrom(other.AccountId); + } + } + + public void MergeFrom(pb::CodedInputStream input) + { + uint tag; + while ((tag = input.ReadTag()) != 0) + { + switch (tag) + { + default: + input.SkipLastField(); + break; + case 10: + { + if (gameAccountId_ == null) + { + gameAccountId_ = new Bgs.Protocol.EntityId(); + } + input.ReadMessage(gameAccountId_); + break; + } + case 18: + { + if (accountId_ == null) + { + accountId_ = new Bgs.Protocol.EntityId(); + } + input.ReadMessage(accountId_); + break; + } + } + } + } + + } + + [System.Diagnostics.DebuggerNonUserCodeAttribute()] + public sealed partial class GetGameTimeRemainingInfoResponse : pb::IMessage + { + private static readonly pb::MessageParser _parser = new pb::MessageParser(() => new GetGameTimeRemainingInfoResponse()); + public static pb::MessageParser Parser { get { return _parser; } } + + public static pbr::MessageDescriptor Descriptor + { + get { return Bgs.Protocol.Account.V1.AccountServiceReflection.Descriptor.MessageTypes[22]; } + } + + pbr::MessageDescriptor pb::IMessage.Descriptor + { + get { return Descriptor; } + } + + public GetGameTimeRemainingInfoResponse() + { + OnConstruction(); + } + + partial void OnConstruction(); + + public GetGameTimeRemainingInfoResponse(GetGameTimeRemainingInfoResponse other) : this() + { + GameTimeRemainingInfo = other.gameTimeRemainingInfo_ != null ? other.GameTimeRemainingInfo.Clone() : null; + } + + public GetGameTimeRemainingInfoResponse Clone() + { + return new GetGameTimeRemainingInfoResponse(this); + } + + /// Field number for the "game_time_remaining_info" field. + public const int GameTimeRemainingInfoFieldNumber = 1; + private Bgs.Protocol.Account.V1.GameTimeRemainingInfo gameTimeRemainingInfo_; + public Bgs.Protocol.Account.V1.GameTimeRemainingInfo GameTimeRemainingInfo + { + get { return gameTimeRemainingInfo_; } + set + { + gameTimeRemainingInfo_ = value; + } + } + + public override bool Equals(object other) + { + return Equals(other as GetGameTimeRemainingInfoResponse); + } + + public bool Equals(GetGameTimeRemainingInfoResponse other) + { + if (ReferenceEquals(other, null)) + { + return false; + } + if (ReferenceEquals(other, this)) + { + return true; + } + if (!object.Equals(GameTimeRemainingInfo, other.GameTimeRemainingInfo)) return false; + return true; + } + + public override int GetHashCode() + { + int hash = 1; + if (gameTimeRemainingInfo_ != null) hash ^= GameTimeRemainingInfo.GetHashCode(); + return hash; + } + + public override string ToString() + { + return pb::JsonFormatter.ToDiagnosticString(this); + } + + public void WriteTo(pb::CodedOutputStream output) + { + if (gameTimeRemainingInfo_ != null) + { + output.WriteRawTag(10); + output.WriteMessage(GameTimeRemainingInfo); + } + } + + public int CalculateSize() + { + int size = 0; + if (gameTimeRemainingInfo_ != null) + { + size += 1 + pb::CodedOutputStream.ComputeMessageSize(GameTimeRemainingInfo); + } + return size; + } + + public void MergeFrom(GetGameTimeRemainingInfoResponse other) + { + if (other == null) + { + return; + } + if (other.gameTimeRemainingInfo_ != null) + { + if (gameTimeRemainingInfo_ == null) + { + gameTimeRemainingInfo_ = new Bgs.Protocol.Account.V1.GameTimeRemainingInfo(); + } + GameTimeRemainingInfo.MergeFrom(other.GameTimeRemainingInfo); + } + } + + public void MergeFrom(pb::CodedInputStream input) + { + uint tag; + while ((tag = input.ReadTag()) != 0) + { + switch (tag) + { + default: + input.SkipLastField(); + break; + case 10: + { + if (gameTimeRemainingInfo_ == null) + { + gameTimeRemainingInfo_ = new Bgs.Protocol.Account.V1.GameTimeRemainingInfo(); + } + input.ReadMessage(gameTimeRemainingInfo_); + break; + } + } + } + } + + } + + [System.Diagnostics.DebuggerNonUserCodeAttribute()] + public sealed partial class GetCAISInfoRequest : pb::IMessage + { + private static readonly pb::MessageParser _parser = new pb::MessageParser(() => new GetCAISInfoRequest()); + public static pb::MessageParser Parser { get { return _parser; } } + + public static pbr::MessageDescriptor Descriptor + { + get { return Bgs.Protocol.Account.V1.AccountServiceReflection.Descriptor.MessageTypes[23]; } + } + + pbr::MessageDescriptor pb::IMessage.Descriptor + { + get { return Descriptor; } + } + + public GetCAISInfoRequest() + { + OnConstruction(); + } + + partial void OnConstruction(); + + public GetCAISInfoRequest(GetCAISInfoRequest other) : this() + { + EntityId = other.entityId_ != null ? other.EntityId.Clone() : null; + } + + public GetCAISInfoRequest Clone() + { + return new GetCAISInfoRequest(this); + } + + /// Field number for the "entity_id" field. + public const int EntityIdFieldNumber = 1; + private Bgs.Protocol.EntityId entityId_; + public Bgs.Protocol.EntityId EntityId + { + get { return entityId_; } + set + { + entityId_ = value; + } + } + + public override bool Equals(object other) + { + return Equals(other as GetCAISInfoRequest); + } + + public bool Equals(GetCAISInfoRequest other) + { + if (ReferenceEquals(other, null)) + { + return false; + } + if (ReferenceEquals(other, this)) + { + return true; + } + if (!object.Equals(EntityId, other.EntityId)) return false; + return true; + } + + public override int GetHashCode() + { + int hash = 1; + if (entityId_ != null) hash ^= EntityId.GetHashCode(); + return hash; + } + + public override string ToString() + { + return pb::JsonFormatter.ToDiagnosticString(this); + } + + public void WriteTo(pb::CodedOutputStream output) + { + if (entityId_ != null) + { + output.WriteRawTag(10); + output.WriteMessage(EntityId); + } + } + + public int CalculateSize() + { + int size = 0; + if (entityId_ != null) + { + size += 1 + pb::CodedOutputStream.ComputeMessageSize(EntityId); + } + return size; + } + + public void MergeFrom(GetCAISInfoRequest other) + { + if (other == null) + { + return; + } + if (other.entityId_ != null) + { + if (entityId_ == null) + { + entityId_ = new Bgs.Protocol.EntityId(); + } + EntityId.MergeFrom(other.EntityId); + } + } + + public void MergeFrom(pb::CodedInputStream input) + { + uint tag; + while ((tag = input.ReadTag()) != 0) + { + switch (tag) + { + default: + input.SkipLastField(); + break; + case 10: + { + if (entityId_ == null) + { + entityId_ = new Bgs.Protocol.EntityId(); + } + input.ReadMessage(entityId_); + break; + } + } + } + } + + } + + [System.Diagnostics.DebuggerNonUserCodeAttribute()] + public sealed partial class GetCAISInfoResponse : pb::IMessage + { + private static readonly pb::MessageParser _parser = new pb::MessageParser(() => new GetCAISInfoResponse()); + public static pb::MessageParser Parser { get { return _parser; } } + + public static pbr::MessageDescriptor Descriptor + { + get { return Bgs.Protocol.Account.V1.AccountServiceReflection.Descriptor.MessageTypes[24]; } + } + + pbr::MessageDescriptor pb::IMessage.Descriptor + { + get { return Descriptor; } + } + + public GetCAISInfoResponse() + { + OnConstruction(); + } + + partial void OnConstruction(); + + public GetCAISInfoResponse(GetCAISInfoResponse other) : this() + { + CaisInfo = other.caisInfo_ != null ? other.CaisInfo.Clone() : null; + } + + public GetCAISInfoResponse Clone() + { + return new GetCAISInfoResponse(this); + } + + /// Field number for the "cais_info" field. + public const int CaisInfoFieldNumber = 1; + private Bgs.Protocol.Account.V1.CAIS caisInfo_; + public Bgs.Protocol.Account.V1.CAIS CaisInfo + { + get { return caisInfo_; } + set + { + caisInfo_ = value; + } + } + + public override bool Equals(object other) + { + return Equals(other as GetCAISInfoResponse); + } + + public bool Equals(GetCAISInfoResponse other) + { + if (ReferenceEquals(other, null)) + { + return false; + } + if (ReferenceEquals(other, this)) + { + return true; + } + if (!object.Equals(CaisInfo, other.CaisInfo)) return false; + return true; + } + + public override int GetHashCode() + { + int hash = 1; + if (caisInfo_ != null) hash ^= CaisInfo.GetHashCode(); + return hash; + } + + public override string ToString() + { + return pb::JsonFormatter.ToDiagnosticString(this); + } + + public void WriteTo(pb::CodedOutputStream output) + { + if (caisInfo_ != null) + { + output.WriteRawTag(10); + output.WriteMessage(CaisInfo); + } + } + + public int CalculateSize() + { + int size = 0; + if (caisInfo_ != null) + { + size += 1 + pb::CodedOutputStream.ComputeMessageSize(CaisInfo); + } + return size; + } + + public void MergeFrom(GetCAISInfoResponse other) + { + if (other == null) + { + return; + } + if (other.caisInfo_ != null) + { + if (caisInfo_ == null) + { + caisInfo_ = new Bgs.Protocol.Account.V1.CAIS(); + } + CaisInfo.MergeFrom(other.CaisInfo); + } + } + + public void MergeFrom(pb::CodedInputStream input) + { + uint tag; + while ((tag = input.ReadTag()) != 0) + { + switch (tag) + { + default: + input.SkipLastField(); + break; + case 10: + { + if (caisInfo_ == null) + { + caisInfo_ = new Bgs.Protocol.Account.V1.CAIS(); + } + input.ReadMessage(caisInfo_); + break; + } + } + } + } + + } + + [System.Diagnostics.DebuggerNonUserCodeAttribute()] + public sealed partial class ForwardCacheExpireRequest : pb::IMessage + { + private static readonly pb::MessageParser _parser = new pb::MessageParser(() => new ForwardCacheExpireRequest()); + public static pb::MessageParser Parser { get { return _parser; } } + + public static pbr::MessageDescriptor Descriptor + { + get { return Bgs.Protocol.Account.V1.AccountServiceReflection.Descriptor.MessageTypes[25]; } + } + + pbr::MessageDescriptor pb::IMessage.Descriptor + { + get { return Descriptor; } + } + + public ForwardCacheExpireRequest() + { + OnConstruction(); + } + + partial void OnConstruction(); + + public ForwardCacheExpireRequest(ForwardCacheExpireRequest other) : this() + { + EntityId = other.entityId_ != null ? other.EntityId.Clone() : null; + } + + public ForwardCacheExpireRequest Clone() + { + return new ForwardCacheExpireRequest(this); + } + + /// Field number for the "entity_id" field. + public const int EntityIdFieldNumber = 1; + private Bgs.Protocol.EntityId entityId_; + public Bgs.Protocol.EntityId EntityId + { + get { return entityId_; } + set + { + entityId_ = value; + } + } + + public override bool Equals(object other) + { + return Equals(other as ForwardCacheExpireRequest); + } + + public bool Equals(ForwardCacheExpireRequest other) + { + if (ReferenceEquals(other, null)) + { + return false; + } + if (ReferenceEquals(other, this)) + { + return true; + } + if (!object.Equals(EntityId, other.EntityId)) return false; + return true; + } + + public override int GetHashCode() + { + int hash = 1; + if (entityId_ != null) hash ^= EntityId.GetHashCode(); + return hash; + } + + public override string ToString() + { + return pb::JsonFormatter.ToDiagnosticString(this); + } + + public void WriteTo(pb::CodedOutputStream output) + { + if (entityId_ != null) + { + output.WriteRawTag(10); + output.WriteMessage(EntityId); + } + } + + public int CalculateSize() + { + int size = 0; + if (entityId_ != null) + { + size += 1 + pb::CodedOutputStream.ComputeMessageSize(EntityId); + } + return size; + } + + public void MergeFrom(ForwardCacheExpireRequest other) + { + if (other == null) + { + return; + } + if (other.entityId_ != null) + { + if (entityId_ == null) + { + entityId_ = new Bgs.Protocol.EntityId(); + } + EntityId.MergeFrom(other.EntityId); + } + } + + public void MergeFrom(pb::CodedInputStream input) + { + uint tag; + while ((tag = input.ReadTag()) != 0) + { + switch (tag) + { + default: + input.SkipLastField(); + break; + case 10: + { + if (entityId_ == null) + { + entityId_ = new Bgs.Protocol.EntityId(); + } + input.ReadMessage(entityId_); + break; + } + } + } + } + + } + + [System.Diagnostics.DebuggerNonUserCodeAttribute()] + public sealed partial class GetAuthorizedDataRequest : pb::IMessage + { + private static readonly pb::MessageParser _parser = new pb::MessageParser(() => new GetAuthorizedDataRequest()); + public static pb::MessageParser Parser { get { return _parser; } } + + public static pbr::MessageDescriptor Descriptor + { + get { return Bgs.Protocol.Account.V1.AccountServiceReflection.Descriptor.MessageTypes[26]; } + } + + pbr::MessageDescriptor pb::IMessage.Descriptor + { + get { return Descriptor; } + } + + public GetAuthorizedDataRequest() + { + OnConstruction(); + } + + partial void OnConstruction(); + + public GetAuthorizedDataRequest(GetAuthorizedDataRequest other) : this() + { + EntityId = other.entityId_ != null ? other.EntityId.Clone() : null; + tag_ = other.tag_.Clone(); + privilegedNetwork_ = other.privilegedNetwork_; + } + + public GetAuthorizedDataRequest Clone() + { + return new GetAuthorizedDataRequest(this); + } + + /// Field number for the "entity_id" field. + public const int EntityIdFieldNumber = 1; + private Bgs.Protocol.EntityId entityId_; + public Bgs.Protocol.EntityId EntityId + { + get { return entityId_; } + set + { + entityId_ = value; + } + } + + /// Field number for the "tag" field. + public const int TagFieldNumber = 2; + private static readonly pb::FieldCodec _repeated_tag_codec + = pb::FieldCodec.ForString(18); + private readonly pbc::RepeatedField tag_ = new pbc::RepeatedField(); + public pbc::RepeatedField Tag + { + get { return tag_; } + } + + /// Field number for the "privileged_network" field. + public const int PrivilegedNetworkFieldNumber = 3; + private bool privilegedNetwork_; + public bool PrivilegedNetwork + { + get { return privilegedNetwork_; } + set + { + privilegedNetwork_ = value; + } + } + + public override bool Equals(object other) + { + return Equals(other as GetAuthorizedDataRequest); + } + + public bool Equals(GetAuthorizedDataRequest other) + { + if (ReferenceEquals(other, null)) + { + return false; + } + if (ReferenceEquals(other, this)) + { + return true; + } + if (!object.Equals(EntityId, other.EntityId)) return false; + if (!tag_.Equals(other.tag_)) return false; + if (PrivilegedNetwork != other.PrivilegedNetwork) return false; + return true; + } + + public override int GetHashCode() + { + int hash = 1; + if (entityId_ != null) hash ^= EntityId.GetHashCode(); + hash ^= tag_.GetHashCode(); + if (PrivilegedNetwork != false) hash ^= PrivilegedNetwork.GetHashCode(); + return hash; + } + + public override string ToString() + { + return pb::JsonFormatter.ToDiagnosticString(this); + } + + public void WriteTo(pb::CodedOutputStream output) + { + if (entityId_ != null) + { + output.WriteRawTag(10); + output.WriteMessage(EntityId); + } + tag_.WriteTo(output, _repeated_tag_codec); + if (PrivilegedNetwork != false) + { + output.WriteRawTag(24); + output.WriteBool(PrivilegedNetwork); + } + } + + public int CalculateSize() + { + int size = 0; + if (entityId_ != null) + { + size += 1 + pb::CodedOutputStream.ComputeMessageSize(EntityId); + } + size += tag_.CalculateSize(_repeated_tag_codec); + if (PrivilegedNetwork != false) + { + size += 1 + 1; + } + return size; + } + + public void MergeFrom(GetAuthorizedDataRequest other) + { + if (other == null) + { + return; + } + if (other.entityId_ != null) + { + if (entityId_ == null) + { + entityId_ = new Bgs.Protocol.EntityId(); + } + EntityId.MergeFrom(other.EntityId); + } + tag_.Add(other.tag_); + if (other.PrivilegedNetwork != false) + { + PrivilegedNetwork = other.PrivilegedNetwork; + } + } + + public void MergeFrom(pb::CodedInputStream input) + { + uint tag; + while ((tag = input.ReadTag()) != 0) + { + switch (tag) + { + default: + input.SkipLastField(); + break; + case 10: + { + if (entityId_ == null) + { + entityId_ = new Bgs.Protocol.EntityId(); + } + input.ReadMessage(entityId_); + break; + } + case 18: + { + tag_.AddEntriesFrom(input, _repeated_tag_codec); + break; + } + case 24: + { + PrivilegedNetwork = input.ReadBool(); + break; + } + } + } + } + + } + + [System.Diagnostics.DebuggerNonUserCodeAttribute()] + public sealed partial class GetAuthorizedDataResponse : pb::IMessage + { + private static readonly pb::MessageParser _parser = new pb::MessageParser(() => new GetAuthorizedDataResponse()); + public static pb::MessageParser Parser { get { return _parser; } } + + public static pbr::MessageDescriptor Descriptor + { + get { return Bgs.Protocol.Account.V1.AccountServiceReflection.Descriptor.MessageTypes[27]; } + } + + pbr::MessageDescriptor pb::IMessage.Descriptor + { + get { return Descriptor; } + } + + public GetAuthorizedDataResponse() + { + OnConstruction(); + } + + partial void OnConstruction(); + + public GetAuthorizedDataResponse(GetAuthorizedDataResponse other) : this() + { + data_ = other.data_.Clone(); + } + + public GetAuthorizedDataResponse Clone() + { + return new GetAuthorizedDataResponse(this); + } + + /// Field number for the "data" field. + public const int DataFieldNumber = 1; + private static readonly pb::FieldCodec _repeated_data_codec + = pb::FieldCodec.ForMessage(10, Bgs.Protocol.Account.V1.AuthorizedData.Parser); + private readonly pbc::RepeatedField data_ = new pbc::RepeatedField(); + public pbc::RepeatedField Data + { + get { return data_; } + } + + public override bool Equals(object other) + { + return Equals(other as GetAuthorizedDataResponse); + } + + public bool Equals(GetAuthorizedDataResponse other) + { + if (ReferenceEquals(other, null)) + { + return false; + } + if (ReferenceEquals(other, this)) + { + return true; + } + if (!data_.Equals(other.data_)) return false; + return true; + } + + public override int GetHashCode() + { + int hash = 1; + hash ^= data_.GetHashCode(); + return hash; + } + + public override string ToString() + { + return pb::JsonFormatter.ToDiagnosticString(this); + } + + public void WriteTo(pb::CodedOutputStream output) + { + data_.WriteTo(output, _repeated_data_codec); + } + + public int CalculateSize() + { + int size = 0; + size += data_.CalculateSize(_repeated_data_codec); + return size; + } + + public void MergeFrom(GetAuthorizedDataResponse other) + { + if (other == null) + { + return; + } + data_.Add(other.data_); + } + + public void MergeFrom(pb::CodedInputStream input) + { + uint tag; + while ((tag = input.ReadTag()) != 0) + { + switch (tag) + { + default: + input.SkipLastField(); + break; + case 10: + { + data_.AddEntriesFrom(input, _repeated_data_codec); + break; + } + } + } + } + + } + + [System.Diagnostics.DebuggerNonUserCodeAttribute()] + public sealed partial class AccountStateNotification : pb::IMessage + { + private static readonly pb::MessageParser _parser = new pb::MessageParser(() => new AccountStateNotification()); + public static pb::MessageParser Parser { get { return _parser; } } + + public static pbr::MessageDescriptor Descriptor + { + get { return Bgs.Protocol.Account.V1.AccountServiceReflection.Descriptor.MessageTypes[28]; } + } + + pbr::MessageDescriptor pb::IMessage.Descriptor + { + get { return Descriptor; } + } + + public AccountStateNotification() + { + OnConstruction(); + } + + partial void OnConstruction(); + + public AccountStateNotification(AccountStateNotification other) : this() + { + AccountState = other.accountState_ != null ? other.AccountState.Clone() : null; + subscriberId_ = other.subscriberId_; + AccountTags = other.accountTags_ != null ? other.AccountTags.Clone() : null; + subscriptionCompleted_ = other.subscriptionCompleted_; + } + + public AccountStateNotification Clone() + { + return new AccountStateNotification(this); + } + + /// Field number for the "account_state" field. + public const int AccountStateFieldNumber = 1; + private Bgs.Protocol.Account.V1.AccountState accountState_; + public Bgs.Protocol.Account.V1.AccountState AccountState + { + get { return accountState_; } + set + { + accountState_ = value; + } + } + + /// Field number for the "subscriber_id" field. + public const int SubscriberIdFieldNumber = 2; + private ulong subscriberId_; + public ulong SubscriberId + { + get { return subscriberId_; } + set + { + subscriberId_ = value; + } + } + + /// Field number for the "account_tags" field. + public const int AccountTagsFieldNumber = 3; + private Bgs.Protocol.Account.V1.AccountFieldTags accountTags_; + public Bgs.Protocol.Account.V1.AccountFieldTags AccountTags + { + get { return accountTags_; } + set + { + accountTags_ = value; + } + } + + /// Field number for the "subscription_completed" field. + public const int SubscriptionCompletedFieldNumber = 4; + private bool subscriptionCompleted_; + public bool SubscriptionCompleted + { + get { return subscriptionCompleted_; } + set + { + subscriptionCompleted_ = value; + } + } + + public override bool Equals(object other) + { + return Equals(other as AccountStateNotification); + } + + public bool Equals(AccountStateNotification other) + { + if (ReferenceEquals(other, null)) + { + return false; + } + if (ReferenceEquals(other, this)) + { + return true; + } + if (!object.Equals(AccountState, other.AccountState)) return false; + if (SubscriberId != other.SubscriberId) return false; + if (!object.Equals(AccountTags, other.AccountTags)) return false; + if (SubscriptionCompleted != other.SubscriptionCompleted) return false; + return true; + } + + public override int GetHashCode() + { + int hash = 1; + if (accountState_ != null) hash ^= AccountState.GetHashCode(); + if (SubscriberId != 0UL) hash ^= SubscriberId.GetHashCode(); + if (accountTags_ != null) hash ^= AccountTags.GetHashCode(); + if (SubscriptionCompleted != false) hash ^= SubscriptionCompleted.GetHashCode(); + return hash; + } + + public override string ToString() + { + return pb::JsonFormatter.ToDiagnosticString(this); + } + + public void WriteTo(pb::CodedOutputStream output) + { + if (accountState_ != null) + { + output.WriteRawTag(10); + output.WriteMessage(AccountState); + } + if (SubscriberId != 0UL) + { + output.WriteRawTag(16); + output.WriteUInt64(SubscriberId); + } + if (accountTags_ != null) + { + output.WriteRawTag(26); + output.WriteMessage(AccountTags); + } + if (SubscriptionCompleted != false) + { + output.WriteRawTag(32); + output.WriteBool(SubscriptionCompleted); + } + } + + public int CalculateSize() + { + int size = 0; + if (accountState_ != null) + { + size += 1 + pb::CodedOutputStream.ComputeMessageSize(AccountState); + } + if (SubscriberId != 0UL) + { + size += 1 + pb::CodedOutputStream.ComputeUInt64Size(SubscriberId); + } + if (accountTags_ != null) + { + size += 1 + pb::CodedOutputStream.ComputeMessageSize(AccountTags); + } + if (SubscriptionCompleted != false) + { + size += 1 + 1; + } + return size; + } + + public void MergeFrom(AccountStateNotification other) + { + if (other == null) + { + return; + } + if (other.accountState_ != null) + { + if (accountState_ == null) + { + accountState_ = new Bgs.Protocol.Account.V1.AccountState(); + } + AccountState.MergeFrom(other.AccountState); + } + if (other.SubscriberId != 0UL) + { + SubscriberId = other.SubscriberId; + } + if (other.accountTags_ != null) + { + if (accountTags_ == null) + { + accountTags_ = new Bgs.Protocol.Account.V1.AccountFieldTags(); + } + AccountTags.MergeFrom(other.AccountTags); + } + if (other.SubscriptionCompleted != false) + { + SubscriptionCompleted = other.SubscriptionCompleted; + } + } + + public void MergeFrom(pb::CodedInputStream input) + { + uint tag; + while ((tag = input.ReadTag()) != 0) + { + switch (tag) + { + default: + input.SkipLastField(); + break; + case 10: + { + if (accountState_ == null) + { + accountState_ = new Bgs.Protocol.Account.V1.AccountState(); + } + input.ReadMessage(accountState_); + break; + } + case 16: + { + SubscriberId = input.ReadUInt64(); + break; + } + case 26: + { + if (accountTags_ == null) + { + accountTags_ = new Bgs.Protocol.Account.V1.AccountFieldTags(); + } + input.ReadMessage(accountTags_); + break; + } + case 32: + { + SubscriptionCompleted = input.ReadBool(); + break; + } + } + } + } + + } + + [System.Diagnostics.DebuggerNonUserCodeAttribute()] + public sealed partial class GameAccountStateNotification : pb::IMessage + { + private static readonly pb::MessageParser _parser = new pb::MessageParser(() => new GameAccountStateNotification()); + public static pb::MessageParser Parser { get { return _parser; } } + + public static pbr::MessageDescriptor Descriptor + { + get { return Bgs.Protocol.Account.V1.AccountServiceReflection.Descriptor.MessageTypes[29]; } + } + + pbr::MessageDescriptor pb::IMessage.Descriptor + { + get { return Descriptor; } + } + + public GameAccountStateNotification() + { + OnConstruction(); + } + + partial void OnConstruction(); + + public GameAccountStateNotification(GameAccountStateNotification other) : this() + { + GameAccountState = other.gameAccountState_ != null ? other.GameAccountState.Clone() : null; + subscriberId_ = other.subscriberId_; + GameAccountTags = other.gameAccountTags_ != null ? other.GameAccountTags.Clone() : null; + subscriptionCompleted_ = other.subscriptionCompleted_; + } + + public GameAccountStateNotification Clone() + { + return new GameAccountStateNotification(this); + } + + /// Field number for the "game_account_state" field. + public const int GameAccountStateFieldNumber = 1; + private Bgs.Protocol.Account.V1.GameAccountState gameAccountState_; + public Bgs.Protocol.Account.V1.GameAccountState GameAccountState + { + get { return gameAccountState_; } + set + { + gameAccountState_ = value; + } + } + + /// Field number for the "subscriber_id" field. + public const int SubscriberIdFieldNumber = 2; + private ulong subscriberId_; + public ulong SubscriberId + { + get { return subscriberId_; } + set + { + subscriberId_ = value; + } + } + + /// Field number for the "game_account_tags" field. + public const int GameAccountTagsFieldNumber = 3; + private Bgs.Protocol.Account.V1.GameAccountFieldTags gameAccountTags_; + public Bgs.Protocol.Account.V1.GameAccountFieldTags GameAccountTags + { + get { return gameAccountTags_; } + set + { + gameAccountTags_ = value; + } + } + + /// Field number for the "subscription_completed" field. + public const int SubscriptionCompletedFieldNumber = 4; + private bool subscriptionCompleted_; + public bool SubscriptionCompleted + { + get { return subscriptionCompleted_; } + set + { + subscriptionCompleted_ = value; + } + } + + public override bool Equals(object other) + { + return Equals(other as GameAccountStateNotification); + } + + public bool Equals(GameAccountStateNotification other) + { + if (ReferenceEquals(other, null)) + { + return false; + } + if (ReferenceEquals(other, this)) + { + return true; + } + if (!object.Equals(GameAccountState, other.GameAccountState)) return false; + if (SubscriberId != other.SubscriberId) return false; + if (!object.Equals(GameAccountTags, other.GameAccountTags)) return false; + if (SubscriptionCompleted != other.SubscriptionCompleted) return false; + return true; + } + + public override int GetHashCode() + { + int hash = 1; + if (gameAccountState_ != null) hash ^= GameAccountState.GetHashCode(); + if (SubscriberId != 0UL) hash ^= SubscriberId.GetHashCode(); + if (gameAccountTags_ != null) hash ^= GameAccountTags.GetHashCode(); + if (SubscriptionCompleted != false) hash ^= SubscriptionCompleted.GetHashCode(); + return hash; + } + + public override string ToString() + { + return pb::JsonFormatter.ToDiagnosticString(this); + } + + public void WriteTo(pb::CodedOutputStream output) + { + if (gameAccountState_ != null) + { + output.WriteRawTag(10); + output.WriteMessage(GameAccountState); + } + if (SubscriberId != 0UL) + { + output.WriteRawTag(16); + output.WriteUInt64(SubscriberId); + } + if (gameAccountTags_ != null) + { + output.WriteRawTag(26); + output.WriteMessage(GameAccountTags); + } + if (SubscriptionCompleted != false) + { + output.WriteRawTag(32); + output.WriteBool(SubscriptionCompleted); + } + } + + public int CalculateSize() + { + int size = 0; + if (gameAccountState_ != null) + { + size += 1 + pb::CodedOutputStream.ComputeMessageSize(GameAccountState); + } + if (SubscriberId != 0UL) + { + size += 1 + pb::CodedOutputStream.ComputeUInt64Size(SubscriberId); + } + if (gameAccountTags_ != null) + { + size += 1 + pb::CodedOutputStream.ComputeMessageSize(GameAccountTags); + } + if (SubscriptionCompleted != false) + { + size += 1 + 1; + } + return size; + } + + public void MergeFrom(GameAccountStateNotification other) + { + if (other == null) + { + return; + } + if (other.gameAccountState_ != null) + { + if (gameAccountState_ == null) + { + gameAccountState_ = new Bgs.Protocol.Account.V1.GameAccountState(); + } + GameAccountState.MergeFrom(other.GameAccountState); + } + if (other.SubscriberId != 0UL) + { + SubscriberId = other.SubscriberId; + } + if (other.gameAccountTags_ != null) + { + if (gameAccountTags_ == null) + { + gameAccountTags_ = new Bgs.Protocol.Account.V1.GameAccountFieldTags(); + } + GameAccountTags.MergeFrom(other.GameAccountTags); + } + if (other.SubscriptionCompleted != false) + { + SubscriptionCompleted = other.SubscriptionCompleted; + } + } + + public void MergeFrom(pb::CodedInputStream input) + { + uint tag; + while ((tag = input.ReadTag()) != 0) + { + switch (tag) + { + default: + input.SkipLastField(); + break; + case 10: + { + if (gameAccountState_ == null) + { + gameAccountState_ = new Bgs.Protocol.Account.V1.GameAccountState(); + } + input.ReadMessage(gameAccountState_); + break; + } + case 16: + { + SubscriberId = input.ReadUInt64(); + break; + } + case 26: + { + if (gameAccountTags_ == null) + { + gameAccountTags_ = new Bgs.Protocol.Account.V1.GameAccountFieldTags(); + } + input.ReadMessage(gameAccountTags_); + break; + } + case 32: + { + SubscriptionCompleted = input.ReadBool(); + break; + } + } + } + } + + } + + [System.Diagnostics.DebuggerNonUserCodeAttribute()] + public sealed partial class GameAccountNotification : pb::IMessage + { + private static readonly pb::MessageParser _parser = new pb::MessageParser(() => new GameAccountNotification()); + public static pb::MessageParser Parser { get { return _parser; } } + + public static pbr::MessageDescriptor Descriptor + { + get { return Bgs.Protocol.Account.V1.AccountServiceReflection.Descriptor.MessageTypes[30]; } + } + + pbr::MessageDescriptor pb::IMessage.Descriptor + { + get { return Descriptor; } + } + + public GameAccountNotification() + { + OnConstruction(); + } + + partial void OnConstruction(); + + public GameAccountNotification(GameAccountNotification other) : this() + { + gameAccounts_ = other.gameAccounts_.Clone(); + subscriberId_ = other.subscriberId_; + AccountTags = other.accountTags_ != null ? other.AccountTags.Clone() : null; + } + + public GameAccountNotification Clone() + { + return new GameAccountNotification(this); + } + + /// Field number for the "game_accounts" field. + public const int GameAccountsFieldNumber = 1; + private static readonly pb::FieldCodec _repeated_gameAccounts_codec + = pb::FieldCodec.ForMessage(10, Bgs.Protocol.Account.V1.GameAccountList.Parser); + private readonly pbc::RepeatedField gameAccounts_ = new pbc::RepeatedField(); + public pbc::RepeatedField GameAccounts + { + get { return gameAccounts_; } + } + + /// Field number for the "subscriber_id" field. + public const int SubscriberIdFieldNumber = 2; + private ulong subscriberId_; + public ulong SubscriberId + { + get { return subscriberId_; } + set + { + subscriberId_ = value; + } + } + + /// Field number for the "account_tags" field. + public const int AccountTagsFieldNumber = 3; + private Bgs.Protocol.Account.V1.AccountFieldTags accountTags_; + public Bgs.Protocol.Account.V1.AccountFieldTags AccountTags + { + get { return accountTags_; } + set + { + accountTags_ = value; + } + } + + public override bool Equals(object other) + { + return Equals(other as GameAccountNotification); + } + + public bool Equals(GameAccountNotification other) + { + if (ReferenceEquals(other, null)) + { + return false; + } + if (ReferenceEquals(other, this)) + { + return true; + } + if (!gameAccounts_.Equals(other.gameAccounts_)) return false; + if (SubscriberId != other.SubscriberId) return false; + if (!object.Equals(AccountTags, other.AccountTags)) return false; + return true; + } + + public override int GetHashCode() + { + int hash = 1; + hash ^= gameAccounts_.GetHashCode(); + if (SubscriberId != 0UL) hash ^= SubscriberId.GetHashCode(); + if (accountTags_ != null) hash ^= AccountTags.GetHashCode(); + return hash; + } + + public override string ToString() + { + return pb::JsonFormatter.ToDiagnosticString(this); + } + + public void WriteTo(pb::CodedOutputStream output) + { + gameAccounts_.WriteTo(output, _repeated_gameAccounts_codec); + if (SubscriberId != 0UL) + { + output.WriteRawTag(16); + output.WriteUInt64(SubscriberId); + } + if (accountTags_ != null) + { + output.WriteRawTag(26); + output.WriteMessage(AccountTags); + } + } + + public int CalculateSize() + { + int size = 0; + size += gameAccounts_.CalculateSize(_repeated_gameAccounts_codec); + if (SubscriberId != 0UL) + { + size += 1 + pb::CodedOutputStream.ComputeUInt64Size(SubscriberId); + } + if (accountTags_ != null) + { + size += 1 + pb::CodedOutputStream.ComputeMessageSize(AccountTags); + } + return size; + } + + public void MergeFrom(GameAccountNotification other) + { + if (other == null) + { + return; + } + gameAccounts_.Add(other.gameAccounts_); + if (other.SubscriberId != 0UL) + { + SubscriberId = other.SubscriberId; + } + if (other.accountTags_ != null) + { + if (accountTags_ == null) + { + accountTags_ = new Bgs.Protocol.Account.V1.AccountFieldTags(); + } + AccountTags.MergeFrom(other.AccountTags); + } + } + + public void MergeFrom(pb::CodedInputStream input) + { + uint tag; + while ((tag = input.ReadTag()) != 0) + { + switch (tag) + { + default: + input.SkipLastField(); + break; + case 10: + { + gameAccounts_.AddEntriesFrom(input, _repeated_gameAccounts_codec); + break; + } + case 16: + { + SubscriberId = input.ReadUInt64(); + break; + } + case 26: + { + if (accountTags_ == null) + { + accountTags_ = new Bgs.Protocol.Account.V1.AccountFieldTags(); + } + input.ReadMessage(accountTags_); + break; + } + } + } + } + + } + + [System.Diagnostics.DebuggerNonUserCodeAttribute()] + public sealed partial class GameAccountSessionNotification : pb::IMessage + { + private static readonly pb::MessageParser _parser = new pb::MessageParser(() => new GameAccountSessionNotification()); + public static pb::MessageParser Parser { get { return _parser; } } + + public static pbr::MessageDescriptor Descriptor + { + get { return Bgs.Protocol.Account.V1.AccountServiceReflection.Descriptor.MessageTypes[31]; } + } + + pbr::MessageDescriptor pb::IMessage.Descriptor + { + get { return Descriptor; } + } + + public GameAccountSessionNotification() + { + OnConstruction(); + } + + partial void OnConstruction(); + + public GameAccountSessionNotification(GameAccountSessionNotification other) : this() + { + GameAccount = other.gameAccount_ != null ? other.GameAccount.Clone() : null; + SessionInfo = other.sessionInfo_ != null ? other.SessionInfo.Clone() : null; + } + + public GameAccountSessionNotification Clone() + { + return new GameAccountSessionNotification(this); + } + + /// Field number for the "game_account" field. + public const int GameAccountFieldNumber = 1; + private Bgs.Protocol.Account.V1.GameAccountHandle gameAccount_; + public Bgs.Protocol.Account.V1.GameAccountHandle GameAccount + { + get { return gameAccount_; } + set + { + gameAccount_ = value; + } + } + + /// Field number for the "session_info" field. + public const int SessionInfoFieldNumber = 2; + private Bgs.Protocol.Account.V1.GameSessionUpdateInfo sessionInfo_; + public Bgs.Protocol.Account.V1.GameSessionUpdateInfo SessionInfo + { + get { return sessionInfo_; } + set + { + sessionInfo_ = value; + } + } + + public override bool Equals(object other) + { + return Equals(other as GameAccountSessionNotification); + } + + public bool Equals(GameAccountSessionNotification other) + { + if (ReferenceEquals(other, null)) + { + return false; + } + if (ReferenceEquals(other, this)) + { + return true; + } + if (!object.Equals(GameAccount, other.GameAccount)) return false; + if (!object.Equals(SessionInfo, other.SessionInfo)) return false; + return true; + } + + public override int GetHashCode() + { + int hash = 1; + if (gameAccount_ != null) hash ^= GameAccount.GetHashCode(); + if (sessionInfo_ != null) hash ^= SessionInfo.GetHashCode(); + return hash; + } + + public override string ToString() + { + return pb::JsonFormatter.ToDiagnosticString(this); + } + + public void WriteTo(pb::CodedOutputStream output) + { + if (gameAccount_ != null) + { + output.WriteRawTag(10); + output.WriteMessage(GameAccount); + } + if (sessionInfo_ != null) + { + output.WriteRawTag(18); + output.WriteMessage(SessionInfo); + } + } + + public int CalculateSize() + { + int size = 0; + if (gameAccount_ != null) + { + size += 1 + pb::CodedOutputStream.ComputeMessageSize(GameAccount); + } + if (sessionInfo_ != null) + { + size += 1 + pb::CodedOutputStream.ComputeMessageSize(SessionInfo); + } + return size; + } + + public void MergeFrom(GameAccountSessionNotification other) + { + if (other == null) + { + return; + } + if (other.gameAccount_ != null) + { + if (gameAccount_ == null) + { + gameAccount_ = new Bgs.Protocol.Account.V1.GameAccountHandle(); + } + GameAccount.MergeFrom(other.GameAccount); + } + if (other.sessionInfo_ != null) + { + if (sessionInfo_ == null) + { + sessionInfo_ = new Bgs.Protocol.Account.V1.GameSessionUpdateInfo(); + } + SessionInfo.MergeFrom(other.SessionInfo); + } + } + + public void MergeFrom(pb::CodedInputStream input) + { + uint tag; + while ((tag = input.ReadTag()) != 0) + { + switch (tag) + { + default: + input.SkipLastField(); + break; + case 10: + { + if (gameAccount_ == null) + { + gameAccount_ = new Bgs.Protocol.Account.V1.GameAccountHandle(); + } + input.ReadMessage(gameAccount_); + break; + } + case 18: + { + if (sessionInfo_ == null) + { + sessionInfo_ = new Bgs.Protocol.Account.V1.GameSessionUpdateInfo(); + } + input.ReadMessage(sessionInfo_); + break; + } + } + } + } + + } + + #endregion +} + +#endregion Designer generated code diff --git a/Framework/Proto/AccountTypes.cs b/Framework/Proto/AccountTypes.cs new file mode 100644 index 000000000..20fa69818 --- /dev/null +++ b/Framework/Proto/AccountTypes.cs @@ -0,0 +1,9082 @@ +// Generated by the protocol buffer compiler. DO NOT EDIT! +// source: bgs/low/pb/client/account_types.proto +#pragma warning disable 1591, 0612, 3021 +#region Designer generated code + +using pb = Google.Protobuf; +using pbc = Google.Protobuf.Collections; +using pbr = Google.Protobuf.Reflection; + +namespace Bgs.Protocol.Account.V1 +{ + /// Holder for reflection information generated from bgs/low/pb/client/account_types.proto + [System.Diagnostics.DebuggerNonUserCodeAttribute()] + public static partial class AccountTypesReflection + { + + #region Descriptor + /// File descriptor for bgs/low/pb/client/account_types.proto + public static pbr::FileDescriptor Descriptor + { + get { return descriptor; } + } + private static pbr::FileDescriptor descriptor; + + static AccountTypesReflection() + { + byte[] descriptorData = System.Convert.FromBase64String( + string.Concat( + "CiViZ3MvbG93L3BiL2NsaWVudC9hY2NvdW50X3R5cGVzLnByb3RvEhdiZ3Mu", + "cHJvdG9jb2wuYWNjb3VudC52MRokYmdzL2xvdy9wYi9jbGllbnQvZW50aXR5", + "X3R5cGVzLnByb3RvGiFiZ3MvbG93L3BiL2NsaWVudC9ycGNfdHlwZXMucHJv", + "dG8iFwoJQWNjb3VudElkEgoKAmlkGAEgASgHIi0KDkFjY291bnRMaWNlbnNl", + "EgoKAmlkGAEgASgNEg8KB2V4cGlyZXMYAiABKAQiLQoRQWNjb3VudENyZWRl", + "bnRpYWwSCgoCaWQYASABKA0SDAoEZGF0YRgCIAEoDCKfBQoLQWNjb3VudEJs", + "b2ISCgoCaWQYAiABKAcSDgoGcmVnaW9uGAMgASgNEg0KBWVtYWlsGAQgAygJ", + "Eg0KBWZsYWdzGAUgASgEEhYKDnNlY3VyZV9yZWxlYXNlGAYgASgEEhcKD3do", + "aXRlbGlzdF9zdGFydBgHIAEoBBIVCg13aGl0ZWxpc3RfZW5kGAggASgEEhEK", + "CWZ1bGxfbmFtZRgKIAEoCRI5CghsaWNlbnNlcxgUIAMoCzInLmJncy5wcm90", + "b2NvbC5hY2NvdW50LnYxLkFjY291bnRMaWNlbnNlEj8KC2NyZWRlbnRpYWxz", + "GBUgAygLMiouYmdzLnByb3RvY29sLmFjY291bnQudjEuQWNjb3VudENyZWRl", + "bnRpYWwSPwoNYWNjb3VudF9saW5rcxgWIAMoCzIoLmJncy5wcm90b2NvbC5h", + "Y2NvdW50LnYxLkdhbWVBY2NvdW50TGluaxISCgpiYXR0bGVfdGFnGBcgASgJ", + "EhgKEGRlZmF1bHRfY3VycmVuY3kYGSABKAcSFAoMbGVnYWxfcmVnaW9uGBog", + "ASgNEhQKDGxlZ2FsX2xvY2FsZRgbIAEoBxIYChBjYWNoZV9leHBpcmF0aW9u", + "GB4gASgEEksKFXBhcmVudGFsX2NvbnRyb2xfaW5mbxgfIAEoCzIsLmJncy5w", + "cm90b2NvbC5hY2NvdW50LnYxLlBhcmVudGFsQ29udHJvbEluZm8SDwoHY291", + "bnRyeRggIAEoCRIYChBwcmVmZXJyZWRfcmVnaW9uGCEgASgNElIKFWlkZW50", + "aXR5X2NoZWNrX3N0YXR1cxgiIAEoDjIzLmJncy5wcm90b2NvbC5hY2NvdW50", + "LnYxLklkZW50aXR5VmVyaWZpY2F0aW9uU3RhdHVzIkUKD0FjY291bnRCbG9i", + "TGlzdBIyCgRibG9iGAEgAygLMiQuYmdzLnByb3RvY29sLmFjY291bnQudjEu", + "QWNjb3VudEJsb2IiQAoRR2FtZUFjY291bnRIYW5kbGUSCgoCaWQYASABKAcS", + "DwoHcHJvZ3JhbRgCIAEoBxIOCgZyZWdpb24YAyABKA0iYQoPR2FtZUFjY291", + "bnRMaW5rEkAKDGdhbWVfYWNjb3VudBgBIAEoCzIqLmJncy5wcm90b2NvbC5h", + "Y2NvdW50LnYxLkdhbWVBY2NvdW50SGFuZGxlEgwKBG5hbWUYAiABKAkizAMK", + "D0dhbWVBY2NvdW50QmxvYhJACgxnYW1lX2FjY291bnQYASABKAsyKi5iZ3Mu", + "cHJvdG9jb2wuYWNjb3VudC52MS5HYW1lQWNjb3VudEhhbmRsZRIMCgRuYW1l", + "GAIgASgJEhkKEXJlYWxtX3Blcm1pc3Npb25zGAMgASgNEg4KBnN0YXR1cxgE", + "IAEoDRINCgVmbGFncxgFIAEoBBIVCg1iaWxsaW5nX2ZsYWdzGAYgASgNEhgK", + "EGNhY2hlX2V4cGlyYXRpb24YByABKAQSHwoXc3Vic2NyaXB0aW9uX2V4cGly", + "YXRpb24YCiABKAQSFwoPdW5pdHNfcmVtYWluaW5nGAsgASgNEhkKEXN0YXR1", + "c19leHBpcmF0aW9uGAwgASgEEhEKCWJveF9sZXZlbBgNIAEoDRIcChRib3hf", + "bGV2ZWxfZXhwaXJhdGlvbhgOIAEoBBI5CghsaWNlbnNlcxgUIAMoCzInLmJn", + "cy5wcm90b2NvbC5hY2NvdW50LnYxLkFjY291bnRMaWNlbnNlEhMKC3JhZl9h", + "Y2NvdW50GBUgASgHEhAKCHJhZl9pbmZvGBYgASgMEhYKDnJhZl9leHBpcmF0", + "aW9uGBcgASgEIk0KE0dhbWVBY2NvdW50QmxvYkxpc3QSNgoEYmxvYhgBIAMo", + "CzIoLmJncy5wcm90b2NvbC5hY2NvdW50LnYxLkdhbWVBY2NvdW50QmxvYiKN", + "AQoQQWNjb3VudFJlZmVyZW5jZRIKCgJpZBgBIAEoBxINCgVlbWFpbBgCIAEo", + "CRI6CgZoYW5kbGUYAyABKAsyKi5iZ3MucHJvdG9jb2wuYWNjb3VudC52MS5H", + "YW1lQWNjb3VudEhhbmRsZRISCgpiYXR0bGVfdGFnGAQgASgJEg4KBnJlZ2lv", + "bhgKIAEoDSKrAQoISWRlbnRpdHkSMwoHYWNjb3VudBgBIAEoCzIiLmJncy5w", + "cm90b2NvbC5hY2NvdW50LnYxLkFjY291bnRJZBJACgxnYW1lX2FjY291bnQY", + "AiABKAsyKi5iZ3MucHJvdG9jb2wuYWNjb3VudC52MS5HYW1lQWNjb3VudEhh", + "bmRsZRIoCgdwcm9jZXNzGAMgASgLMhcuYmdzLnByb3RvY29sLlByb2Nlc3NJ", + "ZCIqCgpQcm9ncmFtVGFnEg8KB3Byb2dyYW0YASABKAcSCwoDdGFnGAIgASgH", + "IigKCVJlZ2lvblRhZxIOCgZyZWdpb24YASABKAcSCwoDdGFnGAIgASgHIrAC", + "ChBBY2NvdW50RmllbGRUYWdzEh4KFmFjY291bnRfbGV2ZWxfaW5mb190YWcY", + "AiABKAcSGAoQcHJpdmFjeV9pbmZvX3RhZxgDIAEoBxIhChlwYXJlbnRhbF9j", + "b250cm9sX2luZm9fdGFnGAQgASgHEkEKFGdhbWVfbGV2ZWxfaW5mb190YWdz", + "GAcgAygLMiMuYmdzLnByb3RvY29sLmFjY291bnQudjEuUHJvZ3JhbVRhZxI9", + "ChBnYW1lX3N0YXR1c190YWdzGAkgAygLMiMuYmdzLnByb3RvY29sLmFjY291", + "bnQudjEuUHJvZ3JhbVRhZxI9ChFnYW1lX2FjY291bnRfdGFncxgLIAMoCzIi", + "LmJncy5wcm90b2NvbC5hY2NvdW50LnYxLlJlZ2lvblRhZyJ+ChRHYW1lQWNj", + "b3VudEZpZWxkVGFncxIbChNnYW1lX2xldmVsX2luZm9fdGFnGAIgASgHEhoK", + "EmdhbWVfdGltZV9pbmZvX3RhZxgDIAEoBxIXCg9nYW1lX3N0YXR1c190YWcY", + "BCABKAcSFAoMcmFmX2luZm9fdGFnGAUgASgHIuMBChNBY2NvdW50RmllbGRP", + "cHRpb25zEhIKCmFsbF9maWVsZHMYASABKAgSIAoYZmllbGRfYWNjb3VudF9s", + "ZXZlbF9pbmZvGAIgASgIEhoKEmZpZWxkX3ByaXZhY3lfaW5mbxgDIAEoCBIj", + "ChtmaWVsZF9wYXJlbnRhbF9jb250cm9sX2luZm8YBCABKAgSHQoVZmllbGRf", + "Z2FtZV9sZXZlbF9pbmZvGAYgASgIEhkKEWZpZWxkX2dhbWVfc3RhdHVzGAcg", + "ASgIEhsKE2ZpZWxkX2dhbWVfYWNjb3VudHMYCCABKAginQEKF0dhbWVBY2Nv", + "dW50RmllbGRPcHRpb25zEhIKCmFsbF9maWVsZHMYASABKAgSHQoVZmllbGRf", + "Z2FtZV9sZXZlbF9pbmZvGAIgASgIEhwKFGZpZWxkX2dhbWVfdGltZV9pbmZv", + "GAMgASgIEhkKEWZpZWxkX2dhbWVfc3RhdHVzGAQgASgIEhYKDmZpZWxkX3Jh", + "Zl9pbmZvGAUgASgIIowDChNTdWJzY3JpYmVyUmVmZXJlbmNlEhEKCW9iamVj", + "dF9pZBgBIAEoBBIpCgllbnRpdHlfaWQYAiABKAsyFi5iZ3MucHJvdG9jb2wu", + "RW50aXR5SWQSRQoPYWNjb3VudF9vcHRpb25zGAMgASgLMiwuYmdzLnByb3Rv", + "Y29sLmFjY291bnQudjEuQWNjb3VudEZpZWxkT3B0aW9ucxI/CgxhY2NvdW50", + "X3RhZ3MYBCABKAsyKS5iZ3MucHJvdG9jb2wuYWNjb3VudC52MS5BY2NvdW50", + "RmllbGRUYWdzEk4KFGdhbWVfYWNjb3VudF9vcHRpb25zGAUgASgLMjAuYmdz", + "LnByb3RvY29sLmFjY291bnQudjEuR2FtZUFjY291bnRGaWVsZE9wdGlvbnMS", + "SAoRZ2FtZV9hY2NvdW50X3RhZ3MYBiABKAsyLS5iZ3MucHJvdG9jb2wuYWNj", + "b3VudC52MS5HYW1lQWNjb3VudEZpZWxkVGFncxIVCg1zdWJzY3JpYmVyX2lk", + "GAcgASgEItwCChBBY2NvdW50TGV2ZWxJbmZvEjkKCGxpY2Vuc2VzGAMgAygL", + "MicuYmdzLnByb3RvY29sLmFjY291bnQudjEuQWNjb3VudExpY2Vuc2USGAoQ", + "ZGVmYXVsdF9jdXJyZW5jeRgEIAEoBxIPCgdjb3VudHJ5GAUgASgJEhgKEHBy", + "ZWZlcnJlZF9yZWdpb24YBiABKA0SEQoJZnVsbF9uYW1lGAcgASgJEhIKCmJh", + "dHRsZV90YWcYCCABKAkSDQoFbXV0ZWQYCSABKAgSFQoNbWFudWFsX3Jldmll", + "dxgKIAEoCBIYChBhY2NvdW50X3BhaWRfYW55GAsgASgIElIKFWlkZW50aXR5", + "X2NoZWNrX3N0YXR1cxgMIAEoDjIzLmJncy5wcm90b2NvbC5hY2NvdW50LnYx", + "LklkZW50aXR5VmVyaWZpY2F0aW9uU3RhdHVzEg0KBWVtYWlsGA0gASgJIpUC", + "CgtQcml2YWN5SW5mbxIUCgxpc191c2luZ19yaWQYAyABKAgSKwojaXNfcmVh", + "bF9pZF92aXNpYmxlX2Zvcl92aWV3X2ZyaWVuZHMYBCABKAgSJAocaXNfaGlk", + "ZGVuX2Zyb21fZnJpZW5kX2ZpbmRlchgFIAEoCBJPChFnYW1lX2luZm9fcHJp", + "dmFjeRgGIAEoDjI0LmJncy5wcm90b2NvbC5hY2NvdW50LnYxLlByaXZhY3lJ", + "bmZvLkdhbWVJbmZvUHJpdmFjeSJMCg9HYW1lSW5mb1ByaXZhY3kSDgoKUFJJ", + "VkFDWV9NRRAAEhMKD1BSSVZBQ1lfRlJJRU5EUxABEhQKEFBSSVZBQ1lfRVZF", + "UllPTkUQAiKkAQoTUGFyZW50YWxDb250cm9sSW5mbxIQCgh0aW1lem9uZRgD", + "IAEoCRIXCg9taW51dGVzX3Blcl9kYXkYBCABKA0SGAoQbWludXRlc19wZXJf", + "d2VlaxgFIAEoDRIZChFjYW5fcmVjZWl2ZV92b2ljZRgGIAEoCBIWCg5jYW5f", + "c2VuZF92b2ljZRgHIAEoCBIVCg1wbGF5X3NjaGVkdWxlGAggAygIItMBCg1H", + "YW1lTGV2ZWxJbmZvEhAKCGlzX3RyaWFsGAQgASgIEhMKC2lzX2xpZmV0aW1l", + "GAUgASgIEhUKDWlzX3Jlc3RyaWN0ZWQYBiABKAgSDwoHaXNfYmV0YRgHIAEo", + "CBIMCgRuYW1lGAggASgJEg8KB3Byb2dyYW0YCSABKAcSOQoIbGljZW5zZXMY", + "CiADKAsyJy5iZ3MucHJvdG9jb2wuYWNjb3VudC52MS5BY2NvdW50TGljZW5z", + "ZRIZChFyZWFsbV9wZXJtaXNzaW9ucxgLIAEoDSKFAQoMR2FtZVRpbWVJbmZv", + "Eh4KFmlzX3VubGltaXRlZF9wbGF5X3RpbWUYAyABKAgSGQoRcGxheV90aW1l", + "X2V4cGlyZXMYBSABKAQSFwoPaXNfc3Vic2NyaXB0aW9uGAYgASgIEiEKGWlz", + "X3JlY3VycmluZ19zdWJzY3JpcHRpb24YByABKAgirQEKFUdhbWVUaW1lUmVt", + "YWluaW5nSW5mbxIZChFtaW51dGVzX3JlbWFpbmluZxgBIAEoDRIoCiBwYXJl", + "bnRhbF9kYWlseV9taW51dGVzX3JlbWFpbmluZxgCIAEoDRIpCiFwYXJlbnRh", + "bF93ZWVrbHlfbWludXRlc19yZW1haW5pbmcYAyABKA0SJAocc2Vjb25kc19y", + "ZW1haW5pbmdfdW50aWxfa2ljaxgEIAEoDSKQAQoKR2FtZVN0YXR1cxIUCgxp", + "c19zdXNwZW5kZWQYBCABKAgSEQoJaXNfYmFubmVkGAUgASgIEhoKEnN1c3Bl", + "bnNpb25fZXhwaXJlcxgGIAEoBBIPCgdwcm9ncmFtGAcgASgHEhEKCWlzX2xv", + "Y2tlZBgIIAEoCBIZChFpc19iYW1fdW5sb2NrYWJsZRgJIAEoCCIbCgdSQUZJ", + "bmZvEhAKCHJhZl9pbmZvGAEgASgMItEBCg9HYW1lU2Vzc2lvbkluZm8SFgoK", + "c3RhcnRfdGltZRgDIAEoDUICGAESPgoIbG9jYXRpb24YBCABKAsyLC5iZ3Mu", + "cHJvdG9jb2wuYWNjb3VudC52MS5HYW1lU2Vzc2lvbkxvY2F0aW9uEhYKDmhh", + "c19iZW5lZmFjdG9yGAUgASgIEhQKDGlzX3VzaW5nX2lnchgGIAEoCBIgChhw", + "YXJlbnRhbF9jb250cm9sc19hY3RpdmUYByABKAgSFgoOc3RhcnRfdGltZV9z", + "ZWMYCCABKAQiRAoVR2FtZVNlc3Npb25VcGRhdGVJbmZvEisKBGNhaXMYCCAB", + "KAsyHS5iZ3MucHJvdG9jb2wuYWNjb3VudC52MS5DQUlTIkgKE0dhbWVTZXNz", + "aW9uTG9jYXRpb24SEgoKaXBfYWRkcmVzcxgBIAEoCRIPCgdjb3VudHJ5GAIg", + "ASgNEgwKBGNpdHkYAyABKAkiTwoEQ0FJUxIWCg5wbGF5ZWRfbWludXRlcxgB", + "IAEoDRIWCg5yZXN0ZWRfbWludXRlcxgCIAEoDRIXCg9sYXN0X2hlYXJkX3Rp", + "bWUYAyABKAQiXQoPR2FtZUFjY291bnRMaXN0Eg4KBnJlZ2lvbhgDIAEoDRI6", + "CgZoYW5kbGUYBCADKAsyKi5iZ3MucHJvdG9jb2wuYWNjb3VudC52MS5HYW1l", + "QWNjb3VudEhhbmRsZSKaAwoMQWNjb3VudFN0YXRlEkUKEmFjY291bnRfbGV2", + "ZWxfaW5mbxgBIAEoCzIpLmJncy5wcm90b2NvbC5hY2NvdW50LnYxLkFjY291", + "bnRMZXZlbEluZm8SOgoMcHJpdmFjeV9pbmZvGAIgASgLMiQuYmdzLnByb3Rv", + "Y29sLmFjY291bnQudjEuUHJpdmFjeUluZm8SSwoVcGFyZW50YWxfY29udHJv", + "bF9pbmZvGAMgASgLMiwuYmdzLnByb3RvY29sLmFjY291bnQudjEuUGFyZW50", + "YWxDb250cm9sSW5mbxI/Cg9nYW1lX2xldmVsX2luZm8YBSADKAsyJi5iZ3Mu", + "cHJvdG9jb2wuYWNjb3VudC52MS5HYW1lTGV2ZWxJbmZvEjgKC2dhbWVfc3Rh", + "dHVzGAYgAygLMiMuYmdzLnByb3RvY29sLmFjY291bnQudjEuR2FtZVN0YXR1", + "cxI/Cg1nYW1lX2FjY291bnRzGAcgAygLMiguYmdzLnByb3RvY29sLmFjY291", + "bnQudjEuR2FtZUFjY291bnRMaXN0IpMBChJBY2NvdW50U3RhdGVUYWdnZWQS", + "PAoNYWNjb3VudF9zdGF0ZRgBIAEoCzIlLmJncy5wcm90b2NvbC5hY2NvdW50", + "LnYxLkFjY291bnRTdGF0ZRI/CgxhY2NvdW50X3RhZ3MYAiABKAsyKS5iZ3Mu", + "cHJvdG9jb2wuYWNjb3VudC52MS5BY2NvdW50RmllbGRUYWdzIoACChBHYW1l", + "QWNjb3VudFN0YXRlEj8KD2dhbWVfbGV2ZWxfaW5mbxgBIAEoCzImLmJncy5w", + "cm90b2NvbC5hY2NvdW50LnYxLkdhbWVMZXZlbEluZm8SPQoOZ2FtZV90aW1l", + "X2luZm8YAiABKAsyJS5iZ3MucHJvdG9jb2wuYWNjb3VudC52MS5HYW1lVGlt", + "ZUluZm8SOAoLZ2FtZV9zdGF0dXMYAyABKAsyIy5iZ3MucHJvdG9jb2wuYWNj", + "b3VudC52MS5HYW1lU3RhdHVzEjIKCHJhZl9pbmZvGAQgASgLMiAuYmdzLnBy", + "b3RvY29sLmFjY291bnQudjEuUkFGSW5mbyKpAQoWR2FtZUFjY291bnRTdGF0", + "ZVRhZ2dlZBJFChJnYW1lX2FjY291bnRfc3RhdGUYASABKAsyKS5iZ3MucHJv", + "dG9jb2wuYWNjb3VudC52MS5HYW1lQWNjb3VudFN0YXRlEkgKEWdhbWVfYWNj", + "b3VudF90YWdzGAIgASgLMi0uYmdzLnByb3RvY29sLmFjY291bnQudjEuR2Ft", + "ZUFjY291bnRGaWVsZFRhZ3MiLwoOQXV0aG9yaXplZERhdGESDAoEZGF0YRgB", + "IAEoCRIPCgdsaWNlbnNlGAIgAygNKo4BChpJZGVudGl0eVZlcmlmaWNhdGlv", + "blN0YXR1cxIRCg1JREVOVF9OT19EQVRBEAASEQoNSURFTlRfUEVORElORxAB", + "EhAKDElERU5UX0ZBSUxFRBAEEhEKDUlERU5UX1NVQ0NFU1MQBRISCg5JREVO", + "VF9TVUNDX01OTBAGEhEKDUlERU5UX1VOS05PV04QB0ICSAJiBnByb3RvMw==")); + descriptor = pbr::FileDescriptor.FromGeneratedCode(descriptorData, + new pbr::FileDescriptor[] { Bgs.Protocol.EntityTypesReflection.Descriptor, Bgs.Protocol.RpcTypesReflection.Descriptor, }, + new pbr::GeneratedClrTypeInfo(new[] { typeof(Bgs.Protocol.Account.V1.IdentityVerificationStatus), }, new pbr::GeneratedClrTypeInfo[] { + new pbr::GeneratedClrTypeInfo(typeof(Bgs.Protocol.Account.V1.AccountId), Bgs.Protocol.Account.V1.AccountId.Parser, new[]{ "Id" }, null, null, null), + new pbr::GeneratedClrTypeInfo(typeof(Bgs.Protocol.Account.V1.AccountLicense), Bgs.Protocol.Account.V1.AccountLicense.Parser, new[]{ "Id", "Expires" }, null, null, null), + new pbr::GeneratedClrTypeInfo(typeof(Bgs.Protocol.Account.V1.AccountCredential), Bgs.Protocol.Account.V1.AccountCredential.Parser, new[]{ "Id", "Data" }, null, null, null), + new pbr::GeneratedClrTypeInfo(typeof(Bgs.Protocol.Account.V1.AccountBlob), Bgs.Protocol.Account.V1.AccountBlob.Parser, new[]{ "Id", "Region", "Email", "Flags", "SecureRelease", "WhitelistStart", "WhitelistEnd", "FullName", "Licenses", "Credentials", "AccountLinks", "BattleTag", "DefaultCurrency", "LegalRegion", "LegalLocale", "CacheExpiration", "ParentalControlInfo", "Country", "PreferredRegion", "IdentityCheckStatus" }, null, null, null), + new pbr::GeneratedClrTypeInfo(typeof(Bgs.Protocol.Account.V1.AccountBlobList), Bgs.Protocol.Account.V1.AccountBlobList.Parser, new[]{ "Blob" }, null, null, null), + new pbr::GeneratedClrTypeInfo(typeof(Bgs.Protocol.Account.V1.GameAccountHandle), Bgs.Protocol.Account.V1.GameAccountHandle.Parser, new[]{ "Id", "Program", "Region" }, null, null, null), + new pbr::GeneratedClrTypeInfo(typeof(Bgs.Protocol.Account.V1.GameAccountLink), Bgs.Protocol.Account.V1.GameAccountLink.Parser, new[]{ "GameAccount", "Name" }, null, null, null), + new pbr::GeneratedClrTypeInfo(typeof(Bgs.Protocol.Account.V1.GameAccountBlob), Bgs.Protocol.Account.V1.GameAccountBlob.Parser, new[]{ "GameAccount", "Name", "RealmPermissions", "Status", "Flags", "BillingFlags", "CacheExpiration", "SubscriptionExpiration", "UnitsRemaining", "StatusExpiration", "BoxLevel", "BoxLevelExpiration", "Licenses", "RafAccount", "RafInfo", "RafExpiration" }, null, null, null), + new pbr::GeneratedClrTypeInfo(typeof(Bgs.Protocol.Account.V1.GameAccountBlobList), Bgs.Protocol.Account.V1.GameAccountBlobList.Parser, new[]{ "Blob" }, null, null, null), + new pbr::GeneratedClrTypeInfo(typeof(Bgs.Protocol.Account.V1.AccountReference), Bgs.Protocol.Account.V1.AccountReference.Parser, new[]{ "Id", "Email", "Handle", "BattleTag", "Region" }, null, null, null), + new pbr::GeneratedClrTypeInfo(typeof(Bgs.Protocol.Account.V1.Identity), Bgs.Protocol.Account.V1.Identity.Parser, new[]{ "Account", "GameAccount", "Process" }, null, null, null), + new pbr::GeneratedClrTypeInfo(typeof(Bgs.Protocol.Account.V1.ProgramTag), Bgs.Protocol.Account.V1.ProgramTag.Parser, new[]{ "Program", "Tag" }, null, null, null), + new pbr::GeneratedClrTypeInfo(typeof(Bgs.Protocol.Account.V1.RegionTag), Bgs.Protocol.Account.V1.RegionTag.Parser, new[]{ "Region", "Tag" }, null, null, null), + new pbr::GeneratedClrTypeInfo(typeof(Bgs.Protocol.Account.V1.AccountFieldTags), Bgs.Protocol.Account.V1.AccountFieldTags.Parser, new[]{ "AccountLevelInfoTag", "PrivacyInfoTag", "ParentalControlInfoTag", "GameLevelInfoTags", "GameStatusTags", "GameAccountTags" }, null, null, null), + new pbr::GeneratedClrTypeInfo(typeof(Bgs.Protocol.Account.V1.GameAccountFieldTags), Bgs.Protocol.Account.V1.GameAccountFieldTags.Parser, new[]{ "GameLevelInfoTag", "GameTimeInfoTag", "GameStatusTag", "RafInfoTag" }, null, null, null), + new pbr::GeneratedClrTypeInfo(typeof(Bgs.Protocol.Account.V1.AccountFieldOptions), Bgs.Protocol.Account.V1.AccountFieldOptions.Parser, new[]{ "AllFields", "FieldAccountLevelInfo", "FieldPrivacyInfo", "FieldParentalControlInfo", "FieldGameLevelInfo", "FieldGameStatus", "FieldGameAccounts" }, null, null, null), + new pbr::GeneratedClrTypeInfo(typeof(Bgs.Protocol.Account.V1.GameAccountFieldOptions), Bgs.Protocol.Account.V1.GameAccountFieldOptions.Parser, new[]{ "AllFields", "FieldGameLevelInfo", "FieldGameTimeInfo", "FieldGameStatus", "FieldRafInfo" }, null, null, null), + new pbr::GeneratedClrTypeInfo(typeof(Bgs.Protocol.Account.V1.SubscriberReference), Bgs.Protocol.Account.V1.SubscriberReference.Parser, new[]{ "ObjectId", "EntityId", "AccountOptions", "AccountTags", "GameAccountOptions", "GameAccountTags", "SubscriberId" }, null, null, null), + new pbr::GeneratedClrTypeInfo(typeof(Bgs.Protocol.Account.V1.AccountLevelInfo), Bgs.Protocol.Account.V1.AccountLevelInfo.Parser, new[]{ "Licenses", "DefaultCurrency", "Country", "PreferredRegion", "FullName", "BattleTag", "Muted", "ManualReview", "AccountPaidAny", "IdentityCheckStatus", "Email" }, null, null, null), + new pbr::GeneratedClrTypeInfo(typeof(Bgs.Protocol.Account.V1.PrivacyInfo), Bgs.Protocol.Account.V1.PrivacyInfo.Parser, new[]{ "IsUsingRid", "IsRealIdVisibleForViewFriends", "IsHiddenFromFriendFinder", "GameInfoPrivacy" }, null, new[]{ typeof(Bgs.Protocol.Account.V1.PrivacyInfo.Types.GameInfoPrivacy) }, null), + new pbr::GeneratedClrTypeInfo(typeof(Bgs.Protocol.Account.V1.ParentalControlInfo), Bgs.Protocol.Account.V1.ParentalControlInfo.Parser, new[]{ "Timezone", "MinutesPerDay", "MinutesPerWeek", "CanReceiveVoice", "CanSendVoice", "PlaySchedule" }, null, null, null), + new pbr::GeneratedClrTypeInfo(typeof(Bgs.Protocol.Account.V1.GameLevelInfo), Bgs.Protocol.Account.V1.GameLevelInfo.Parser, new[]{ "IsTrial", "IsLifetime", "IsRestricted", "IsBeta", "Name", "Program", "Licenses", "RealmPermissions" }, null, null, null), + new pbr::GeneratedClrTypeInfo(typeof(Bgs.Protocol.Account.V1.GameTimeInfo), Bgs.Protocol.Account.V1.GameTimeInfo.Parser, new[]{ "IsUnlimitedPlayTime", "PlayTimeExpires", "IsSubscription", "IsRecurringSubscription" }, null, null, null), + new pbr::GeneratedClrTypeInfo(typeof(Bgs.Protocol.Account.V1.GameTimeRemainingInfo), Bgs.Protocol.Account.V1.GameTimeRemainingInfo.Parser, new[]{ "MinutesRemaining", "ParentalDailyMinutesRemaining", "ParentalWeeklyMinutesRemaining", "SecondsRemainingUntilKick" }, null, null, null), + new pbr::GeneratedClrTypeInfo(typeof(Bgs.Protocol.Account.V1.GameStatus), Bgs.Protocol.Account.V1.GameStatus.Parser, new[]{ "IsSuspended", "IsBanned", "SuspensionExpires", "Program", "IsLocked", "IsBamUnlockable" }, null, null, null), + new pbr::GeneratedClrTypeInfo(typeof(Bgs.Protocol.Account.V1.RAFInfo), Bgs.Protocol.Account.V1.RAFInfo.Parser, new[]{ "RafInfo" }, null, null, null), + new pbr::GeneratedClrTypeInfo(typeof(Bgs.Protocol.Account.V1.GameSessionInfo), Bgs.Protocol.Account.V1.GameSessionInfo.Parser, new[]{ "StartTime", "Location", "HasBenefactor", "IsUsingIgr", "ParentalControlsActive", "StartTimeSec" }, null, null, null), + new pbr::GeneratedClrTypeInfo(typeof(Bgs.Protocol.Account.V1.GameSessionUpdateInfo), Bgs.Protocol.Account.V1.GameSessionUpdateInfo.Parser, new[]{ "Cais" }, null, null, null), + new pbr::GeneratedClrTypeInfo(typeof(Bgs.Protocol.Account.V1.GameSessionLocation), Bgs.Protocol.Account.V1.GameSessionLocation.Parser, new[]{ "IpAddress", "Country", "City" }, null, null, null), + new pbr::GeneratedClrTypeInfo(typeof(Bgs.Protocol.Account.V1.CAIS), Bgs.Protocol.Account.V1.CAIS.Parser, new[]{ "PlayedMinutes", "RestedMinutes", "LastHeardTime" }, null, null, null), + new pbr::GeneratedClrTypeInfo(typeof(Bgs.Protocol.Account.V1.GameAccountList), Bgs.Protocol.Account.V1.GameAccountList.Parser, new[]{ "Region", "Handle" }, null, null, null), + new pbr::GeneratedClrTypeInfo(typeof(Bgs.Protocol.Account.V1.AccountState), Bgs.Protocol.Account.V1.AccountState.Parser, new[]{ "AccountLevelInfo", "PrivacyInfo", "ParentalControlInfo", "GameLevelInfo", "GameStatus", "GameAccounts" }, null, null, null), + new pbr::GeneratedClrTypeInfo(typeof(Bgs.Protocol.Account.V1.AccountStateTagged), Bgs.Protocol.Account.V1.AccountStateTagged.Parser, new[]{ "AccountState", "AccountTags" }, null, null, null), + new pbr::GeneratedClrTypeInfo(typeof(Bgs.Protocol.Account.V1.GameAccountState), Bgs.Protocol.Account.V1.GameAccountState.Parser, new[]{ "GameLevelInfo", "GameTimeInfo", "GameStatus", "RafInfo" }, null, null, null), + new pbr::GeneratedClrTypeInfo(typeof(Bgs.Protocol.Account.V1.GameAccountStateTagged), Bgs.Protocol.Account.V1.GameAccountStateTagged.Parser, new[]{ "GameAccountState", "GameAccountTags" }, null, null, null), + new pbr::GeneratedClrTypeInfo(typeof(Bgs.Protocol.Account.V1.AuthorizedData), Bgs.Protocol.Account.V1.AuthorizedData.Parser, new[]{ "Data", "License" }, null, null, null) + })); + } + #endregion + + } + #region Enums + public enum IdentityVerificationStatus + { + IDENT_NO_DATA = 0, + IDENT_PENDING = 1, + IDENT_FAILED = 4, + IDENT_SUCCESS = 5, + IDENT_SUCC_MNL = 6, + IDENT_UNKNOWN = 7, + } + + #endregion + + #region Messages + [System.Diagnostics.DebuggerNonUserCodeAttribute()] + public sealed partial class AccountId : pb::IMessage + { + private static readonly pb::MessageParser _parser = new pb::MessageParser(() => new AccountId()); + public static pb::MessageParser Parser { get { return _parser; } } + + public static pbr::MessageDescriptor Descriptor + { + get { return Bgs.Protocol.Account.V1.AccountTypesReflection.Descriptor.MessageTypes[0]; } + } + + pbr::MessageDescriptor pb::IMessage.Descriptor + { + get { return Descriptor; } + } + + public AccountId() + { + OnConstruction(); + } + + partial void OnConstruction(); + + public AccountId(AccountId other) : this() + { + id_ = other.id_; + } + + public AccountId Clone() + { + return new AccountId(this); + } + + /// Field number for the "id" field. + public const int IdFieldNumber = 1; + private uint id_; + public uint Id + { + get { return id_; } + set + { + id_ = value; + } + } + + public override bool Equals(object other) + { + return Equals(other as AccountId); + } + + public bool Equals(AccountId other) + { + if (ReferenceEquals(other, null)) + { + return false; + } + if (ReferenceEquals(other, this)) + { + return true; + } + if (Id != other.Id) return false; + return true; + } + + public override int GetHashCode() + { + int hash = 1; + if (Id != 0) hash ^= Id.GetHashCode(); + return hash; + } + + public override string ToString() + { + return pb::JsonFormatter.ToDiagnosticString(this); + } + + public void WriteTo(pb::CodedOutputStream output) + { + if (Id != 0) + { + output.WriteRawTag(13); + output.WriteFixed32(Id); + } + } + + public int CalculateSize() + { + int size = 0; + if (Id != 0) + { + size += 1 + 4; + } + return size; + } + + public void MergeFrom(AccountId other) + { + if (other == null) + { + return; + } + if (other.Id != 0) + { + Id = other.Id; + } + } + + public void MergeFrom(pb::CodedInputStream input) + { + uint tag; + while ((tag = input.ReadTag()) != 0) + { + switch (tag) + { + default: + input.SkipLastField(); + break; + case 13: + { + Id = input.ReadFixed32(); + break; + } + } + } + } + + } + + [System.Diagnostics.DebuggerNonUserCodeAttribute()] + public sealed partial class AccountLicense : pb::IMessage + { + private static readonly pb::MessageParser _parser = new pb::MessageParser(() => new AccountLicense()); + public static pb::MessageParser Parser { get { return _parser; } } + + public static pbr::MessageDescriptor Descriptor + { + get { return Bgs.Protocol.Account.V1.AccountTypesReflection.Descriptor.MessageTypes[1]; } + } + + pbr::MessageDescriptor pb::IMessage.Descriptor + { + get { return Descriptor; } + } + + public AccountLicense() + { + OnConstruction(); + } + + partial void OnConstruction(); + + public AccountLicense(AccountLicense other) : this() + { + id_ = other.id_; + expires_ = other.expires_; + } + + public AccountLicense Clone() + { + return new AccountLicense(this); + } + + /// Field number for the "id" field. + public const int IdFieldNumber = 1; + private uint id_; + public uint Id + { + get { return id_; } + set + { + id_ = value; + } + } + + /// Field number for the "expires" field. + public const int ExpiresFieldNumber = 2; + private ulong expires_; + public ulong Expires + { + get { return expires_; } + set + { + expires_ = value; + } + } + + public override bool Equals(object other) + { + return Equals(other as AccountLicense); + } + + public bool Equals(AccountLicense other) + { + if (ReferenceEquals(other, null)) + { + return false; + } + if (ReferenceEquals(other, this)) + { + return true; + } + if (Id != other.Id) return false; + if (Expires != other.Expires) return false; + return true; + } + + public override int GetHashCode() + { + int hash = 1; + if (Id != 0) hash ^= Id.GetHashCode(); + if (Expires != 0UL) hash ^= Expires.GetHashCode(); + return hash; + } + + public override string ToString() + { + return pb::JsonFormatter.ToDiagnosticString(this); + } + + public void WriteTo(pb::CodedOutputStream output) + { + if (Id != 0) + { + output.WriteRawTag(8); + output.WriteUInt32(Id); + } + if (Expires != 0UL) + { + output.WriteRawTag(16); + output.WriteUInt64(Expires); + } + } + + public int CalculateSize() + { + int size = 0; + if (Id != 0) + { + size += 1 + pb::CodedOutputStream.ComputeUInt32Size(Id); + } + if (Expires != 0UL) + { + size += 1 + pb::CodedOutputStream.ComputeUInt64Size(Expires); + } + return size; + } + + public void MergeFrom(AccountLicense other) + { + if (other == null) + { + return; + } + if (other.Id != 0) + { + Id = other.Id; + } + if (other.Expires != 0UL) + { + Expires = other.Expires; + } + } + + public void MergeFrom(pb::CodedInputStream input) + { + uint tag; + while ((tag = input.ReadTag()) != 0) + { + switch (tag) + { + default: + input.SkipLastField(); + break; + case 8: + { + Id = input.ReadUInt32(); + break; + } + case 16: + { + Expires = input.ReadUInt64(); + break; + } + } + } + } + + } + + [System.Diagnostics.DebuggerNonUserCodeAttribute()] + public sealed partial class AccountCredential : pb::IMessage + { + private static readonly pb::MessageParser _parser = new pb::MessageParser(() => new AccountCredential()); + public static pb::MessageParser Parser { get { return _parser; } } + + public static pbr::MessageDescriptor Descriptor + { + get { return Bgs.Protocol.Account.V1.AccountTypesReflection.Descriptor.MessageTypes[2]; } + } + + pbr::MessageDescriptor pb::IMessage.Descriptor + { + get { return Descriptor; } + } + + public AccountCredential() + { + OnConstruction(); + } + + partial void OnConstruction(); + + public AccountCredential(AccountCredential other) : this() + { + id_ = other.id_; + data_ = other.data_; + } + + public AccountCredential Clone() + { + return new AccountCredential(this); + } + + /// Field number for the "id" field. + public const int IdFieldNumber = 1; + private uint id_; + public uint Id + { + get { return id_; } + set + { + id_ = value; + } + } + + /// Field number for the "data" field. + public const int DataFieldNumber = 2; + private pb::ByteString data_ = pb::ByteString.Empty; + public pb::ByteString Data + { + get { return data_; } + set + { + data_ = pb::ProtoPreconditions.CheckNotNull(value, "value"); + } + } + + public override bool Equals(object other) + { + return Equals(other as AccountCredential); + } + + public bool Equals(AccountCredential other) + { + if (ReferenceEquals(other, null)) + { + return false; + } + if (ReferenceEquals(other, this)) + { + return true; + } + if (Id != other.Id) return false; + if (Data != other.Data) return false; + return true; + } + + public override int GetHashCode() + { + int hash = 1; + if (Id != 0) hash ^= Id.GetHashCode(); + if (Data.Length != 0) hash ^= Data.GetHashCode(); + return hash; + } + + public override string ToString() + { + return pb::JsonFormatter.ToDiagnosticString(this); + } + + public void WriteTo(pb::CodedOutputStream output) + { + if (Id != 0) + { + output.WriteRawTag(8); + output.WriteUInt32(Id); + } + if (Data.Length != 0) + { + output.WriteRawTag(18); + output.WriteBytes(Data); + } + } + + public int CalculateSize() + { + int size = 0; + if (Id != 0) + { + size += 1 + pb::CodedOutputStream.ComputeUInt32Size(Id); + } + if (Data.Length != 0) + { + size += 1 + pb::CodedOutputStream.ComputeBytesSize(Data); + } + return size; + } + + public void MergeFrom(AccountCredential other) + { + if (other == null) + { + return; + } + if (other.Id != 0) + { + Id = other.Id; + } + if (other.Data.Length != 0) + { + Data = other.Data; + } + } + + public void MergeFrom(pb::CodedInputStream input) + { + uint tag; + while ((tag = input.ReadTag()) != 0) + { + switch (tag) + { + default: + input.SkipLastField(); + break; + case 8: + { + Id = input.ReadUInt32(); + break; + } + case 18: + { + Data = input.ReadBytes(); + break; + } + } + } + } + + } + + [System.Diagnostics.DebuggerNonUserCodeAttribute()] + public sealed partial class AccountBlob : pb::IMessage + { + private static readonly pb::MessageParser _parser = new pb::MessageParser(() => new AccountBlob()); + public static pb::MessageParser Parser { get { return _parser; } } + + public static pbr::MessageDescriptor Descriptor + { + get { return Bgs.Protocol.Account.V1.AccountTypesReflection.Descriptor.MessageTypes[3]; } + } + + pbr::MessageDescriptor pb::IMessage.Descriptor + { + get { return Descriptor; } + } + + public AccountBlob() + { + OnConstruction(); + } + + partial void OnConstruction(); + + public AccountBlob(AccountBlob other) : this() + { + id_ = other.id_; + region_ = other.region_; + email_ = other.email_.Clone(); + flags_ = other.flags_; + secureRelease_ = other.secureRelease_; + whitelistStart_ = other.whitelistStart_; + whitelistEnd_ = other.whitelistEnd_; + fullName_ = other.fullName_; + licenses_ = other.licenses_.Clone(); + credentials_ = other.credentials_.Clone(); + accountLinks_ = other.accountLinks_.Clone(); + battleTag_ = other.battleTag_; + defaultCurrency_ = other.defaultCurrency_; + legalRegion_ = other.legalRegion_; + legalLocale_ = other.legalLocale_; + cacheExpiration_ = other.cacheExpiration_; + ParentalControlInfo = other.parentalControlInfo_ != null ? other.ParentalControlInfo.Clone() : null; + country_ = other.country_; + preferredRegion_ = other.preferredRegion_; + identityCheckStatus_ = other.identityCheckStatus_; + } + + public AccountBlob Clone() + { + return new AccountBlob(this); + } + + /// Field number for the "id" field. + public const int IdFieldNumber = 2; + private uint id_; + public uint Id + { + get { return id_; } + set + { + id_ = value; + } + } + + /// Field number for the "region" field. + public const int RegionFieldNumber = 3; + private uint region_; + public uint Region + { + get { return region_; } + set + { + region_ = value; + } + } + + /// Field number for the "email" field. + public const int EmailFieldNumber = 4; + private static readonly pb::FieldCodec _repeated_email_codec + = pb::FieldCodec.ForString(34); + private readonly pbc::RepeatedField email_ = new pbc::RepeatedField(); + public pbc::RepeatedField Email + { + get { return email_; } + } + + /// Field number for the "flags" field. + public const int FlagsFieldNumber = 5; + private ulong flags_; + public ulong Flags + { + get { return flags_; } + set + { + flags_ = value; + } + } + + /// Field number for the "secure_release" field. + public const int SecureReleaseFieldNumber = 6; + private ulong secureRelease_; + public ulong SecureRelease + { + get { return secureRelease_; } + set + { + secureRelease_ = value; + } + } + + /// Field number for the "whitelist_start" field. + public const int WhitelistStartFieldNumber = 7; + private ulong whitelistStart_; + public ulong WhitelistStart + { + get { return whitelistStart_; } + set + { + whitelistStart_ = value; + } + } + + /// Field number for the "whitelist_end" field. + public const int WhitelistEndFieldNumber = 8; + private ulong whitelistEnd_; + public ulong WhitelistEnd + { + get { return whitelistEnd_; } + set + { + whitelistEnd_ = value; + } + } + + /// Field number for the "full_name" field. + public const int FullNameFieldNumber = 10; + private string fullName_ = ""; + public string FullName + { + get { return fullName_; } + set + { + fullName_ = pb::ProtoPreconditions.CheckNotNull(value, "value"); + } + } + + /// Field number for the "licenses" field. + public const int LicensesFieldNumber = 20; + private static readonly pb::FieldCodec _repeated_licenses_codec + = pb::FieldCodec.ForMessage(162, Bgs.Protocol.Account.V1.AccountLicense.Parser); + private readonly pbc::RepeatedField licenses_ = new pbc::RepeatedField(); + public pbc::RepeatedField Licenses + { + get { return licenses_; } + } + + /// Field number for the "credentials" field. + public const int CredentialsFieldNumber = 21; + private static readonly pb::FieldCodec _repeated_credentials_codec + = pb::FieldCodec.ForMessage(170, Bgs.Protocol.Account.V1.AccountCredential.Parser); + private readonly pbc::RepeatedField credentials_ = new pbc::RepeatedField(); + public pbc::RepeatedField Credentials + { + get { return credentials_; } + } + + /// Field number for the "account_links" field. + public const int AccountLinksFieldNumber = 22; + private static readonly pb::FieldCodec _repeated_accountLinks_codec + = pb::FieldCodec.ForMessage(178, Bgs.Protocol.Account.V1.GameAccountLink.Parser); + private readonly pbc::RepeatedField accountLinks_ = new pbc::RepeatedField(); + public pbc::RepeatedField AccountLinks + { + get { return accountLinks_; } + } + + /// Field number for the "battle_tag" field. + public const int BattleTagFieldNumber = 23; + private string battleTag_ = ""; + public string BattleTag + { + get { return battleTag_; } + set + { + battleTag_ = pb::ProtoPreconditions.CheckNotNull(value, "value"); + } + } + + /// Field number for the "default_currency" field. + public const int DefaultCurrencyFieldNumber = 25; + private uint defaultCurrency_; + public uint DefaultCurrency + { + get { return defaultCurrency_; } + set + { + defaultCurrency_ = value; + } + } + + /// Field number for the "legal_region" field. + public const int LegalRegionFieldNumber = 26; + private uint legalRegion_; + public uint LegalRegion + { + get { return legalRegion_; } + set + { + legalRegion_ = value; + } + } + + /// Field number for the "legal_locale" field. + public const int LegalLocaleFieldNumber = 27; + private uint legalLocale_; + public uint LegalLocale + { + get { return legalLocale_; } + set + { + legalLocale_ = value; + } + } + + /// Field number for the "cache_expiration" field. + public const int CacheExpirationFieldNumber = 30; + private ulong cacheExpiration_; + public ulong CacheExpiration + { + get { return cacheExpiration_; } + set + { + cacheExpiration_ = value; + } + } + + /// Field number for the "parental_control_info" field. + public const int ParentalControlInfoFieldNumber = 31; + private Bgs.Protocol.Account.V1.ParentalControlInfo parentalControlInfo_; + public Bgs.Protocol.Account.V1.ParentalControlInfo ParentalControlInfo + { + get { return parentalControlInfo_; } + set + { + parentalControlInfo_ = value; + } + } + + /// Field number for the "country" field. + public const int CountryFieldNumber = 32; + private string country_ = ""; + public string Country + { + get { return country_; } + set + { + country_ = pb::ProtoPreconditions.CheckNotNull(value, "value"); + } + } + + /// Field number for the "preferred_region" field. + public const int PreferredRegionFieldNumber = 33; + private uint preferredRegion_; + public uint PreferredRegion + { + get { return preferredRegion_; } + set + { + preferredRegion_ = value; + } + } + + /// Field number for the "identity_check_status" field. + public const int IdentityCheckStatusFieldNumber = 34; + private Bgs.Protocol.Account.V1.IdentityVerificationStatus identityCheckStatus_ = Bgs.Protocol.Account.V1.IdentityVerificationStatus.IDENT_NO_DATA; + public Bgs.Protocol.Account.V1.IdentityVerificationStatus IdentityCheckStatus + { + get { return identityCheckStatus_; } + set + { + identityCheckStatus_ = value; + } + } + + public override bool Equals(object other) + { + return Equals(other as AccountBlob); + } + + public bool Equals(AccountBlob other) + { + if (ReferenceEquals(other, null)) + { + return false; + } + if (ReferenceEquals(other, this)) + { + return true; + } + if (Id != other.Id) return false; + if (Region != other.Region) return false; + if (!email_.Equals(other.email_)) return false; + if (Flags != other.Flags) return false; + if (SecureRelease != other.SecureRelease) return false; + if (WhitelistStart != other.WhitelistStart) return false; + if (WhitelistEnd != other.WhitelistEnd) return false; + if (FullName != other.FullName) return false; + if (!licenses_.Equals(other.licenses_)) return false; + if (!credentials_.Equals(other.credentials_)) return false; + if (!accountLinks_.Equals(other.accountLinks_)) return false; + if (BattleTag != other.BattleTag) return false; + if (DefaultCurrency != other.DefaultCurrency) return false; + if (LegalRegion != other.LegalRegion) return false; + if (LegalLocale != other.LegalLocale) return false; + if (CacheExpiration != other.CacheExpiration) return false; + if (!object.Equals(ParentalControlInfo, other.ParentalControlInfo)) return false; + if (Country != other.Country) return false; + if (PreferredRegion != other.PreferredRegion) return false; + if (IdentityCheckStatus != other.IdentityCheckStatus) return false; + return true; + } + + public override int GetHashCode() + { + int hash = 1; + if (Id != 0) hash ^= Id.GetHashCode(); + if (Region != 0) hash ^= Region.GetHashCode(); + hash ^= email_.GetHashCode(); + if (Flags != 0UL) hash ^= Flags.GetHashCode(); + if (SecureRelease != 0UL) hash ^= SecureRelease.GetHashCode(); + if (WhitelistStart != 0UL) hash ^= WhitelistStart.GetHashCode(); + if (WhitelistEnd != 0UL) hash ^= WhitelistEnd.GetHashCode(); + if (FullName.Length != 0) hash ^= FullName.GetHashCode(); + hash ^= licenses_.GetHashCode(); + hash ^= credentials_.GetHashCode(); + hash ^= accountLinks_.GetHashCode(); + if (BattleTag.Length != 0) hash ^= BattleTag.GetHashCode(); + if (DefaultCurrency != 0) hash ^= DefaultCurrency.GetHashCode(); + if (LegalRegion != 0) hash ^= LegalRegion.GetHashCode(); + if (LegalLocale != 0) hash ^= LegalLocale.GetHashCode(); + if (CacheExpiration != 0UL) hash ^= CacheExpiration.GetHashCode(); + if (parentalControlInfo_ != null) hash ^= ParentalControlInfo.GetHashCode(); + if (Country.Length != 0) hash ^= Country.GetHashCode(); + if (PreferredRegion != 0) hash ^= PreferredRegion.GetHashCode(); + if (IdentityCheckStatus != Bgs.Protocol.Account.V1.IdentityVerificationStatus.IDENT_NO_DATA) hash ^= IdentityCheckStatus.GetHashCode(); + return hash; + } + + public override string ToString() + { + return pb::JsonFormatter.ToDiagnosticString(this); + } + + public void WriteTo(pb::CodedOutputStream output) + { + if (Id != 0) + { + output.WriteRawTag(21); + output.WriteFixed32(Id); + } + if (Region != 0) + { + output.WriteRawTag(24); + output.WriteUInt32(Region); + } + email_.WriteTo(output, _repeated_email_codec); + if (Flags != 0UL) + { + output.WriteRawTag(40); + output.WriteUInt64(Flags); + } + if (SecureRelease != 0UL) + { + output.WriteRawTag(48); + output.WriteUInt64(SecureRelease); + } + if (WhitelistStart != 0UL) + { + output.WriteRawTag(56); + output.WriteUInt64(WhitelistStart); + } + if (WhitelistEnd != 0UL) + { + output.WriteRawTag(64); + output.WriteUInt64(WhitelistEnd); + } + if (FullName.Length != 0) + { + output.WriteRawTag(82); + output.WriteString(FullName); + } + licenses_.WriteTo(output, _repeated_licenses_codec); + credentials_.WriteTo(output, _repeated_credentials_codec); + accountLinks_.WriteTo(output, _repeated_accountLinks_codec); + if (BattleTag.Length != 0) + { + output.WriteRawTag(186, 1); + output.WriteString(BattleTag); + } + if (DefaultCurrency != 0) + { + output.WriteRawTag(205, 1); + output.WriteFixed32(DefaultCurrency); + } + if (LegalRegion != 0) + { + output.WriteRawTag(208, 1); + output.WriteUInt32(LegalRegion); + } + if (LegalLocale != 0) + { + output.WriteRawTag(221, 1); + output.WriteFixed32(LegalLocale); + } + if (CacheExpiration != 0UL) + { + output.WriteRawTag(240, 1); + output.WriteUInt64(CacheExpiration); + } + if (parentalControlInfo_ != null) + { + output.WriteRawTag(250, 1); + output.WriteMessage(ParentalControlInfo); + } + if (Country.Length != 0) + { + output.WriteRawTag(130, 2); + output.WriteString(Country); + } + if (PreferredRegion != 0) + { + output.WriteRawTag(136, 2); + output.WriteUInt32(PreferredRegion); + } + if (IdentityCheckStatus != Bgs.Protocol.Account.V1.IdentityVerificationStatus.IDENT_NO_DATA) + { + output.WriteRawTag(144, 2); + output.WriteEnum((int)IdentityCheckStatus); + } + } + + public int CalculateSize() + { + int size = 0; + if (Id != 0) + { + size += 1 + 4; + } + if (Region != 0) + { + size += 1 + pb::CodedOutputStream.ComputeUInt32Size(Region); + } + size += email_.CalculateSize(_repeated_email_codec); + if (Flags != 0UL) + { + size += 1 + pb::CodedOutputStream.ComputeUInt64Size(Flags); + } + if (SecureRelease != 0UL) + { + size += 1 + pb::CodedOutputStream.ComputeUInt64Size(SecureRelease); + } + if (WhitelistStart != 0UL) + { + size += 1 + pb::CodedOutputStream.ComputeUInt64Size(WhitelistStart); + } + if (WhitelistEnd != 0UL) + { + size += 1 + pb::CodedOutputStream.ComputeUInt64Size(WhitelistEnd); + } + if (FullName.Length != 0) + { + size += 1 + pb::CodedOutputStream.ComputeStringSize(FullName); + } + size += licenses_.CalculateSize(_repeated_licenses_codec); + size += credentials_.CalculateSize(_repeated_credentials_codec); + size += accountLinks_.CalculateSize(_repeated_accountLinks_codec); + if (BattleTag.Length != 0) + { + size += 2 + pb::CodedOutputStream.ComputeStringSize(BattleTag); + } + if (DefaultCurrency != 0) + { + size += 2 + 4; + } + if (LegalRegion != 0) + { + size += 2 + pb::CodedOutputStream.ComputeUInt32Size(LegalRegion); + } + if (LegalLocale != 0) + { + size += 2 + 4; + } + if (CacheExpiration != 0UL) + { + size += 2 + pb::CodedOutputStream.ComputeUInt64Size(CacheExpiration); + } + if (parentalControlInfo_ != null) + { + size += 2 + pb::CodedOutputStream.ComputeMessageSize(ParentalControlInfo); + } + if (Country.Length != 0) + { + size += 2 + pb::CodedOutputStream.ComputeStringSize(Country); + } + if (PreferredRegion != 0) + { + size += 2 + pb::CodedOutputStream.ComputeUInt32Size(PreferredRegion); + } + if (IdentityCheckStatus != Bgs.Protocol.Account.V1.IdentityVerificationStatus.IDENT_NO_DATA) + { + size += 2 + pb::CodedOutputStream.ComputeEnumSize((int)IdentityCheckStatus); + } + return size; + } + + public void MergeFrom(AccountBlob other) + { + if (other == null) + { + return; + } + if (other.Id != 0) + { + Id = other.Id; + } + if (other.Region != 0) + { + Region = other.Region; + } + email_.Add(other.email_); + if (other.Flags != 0UL) + { + Flags = other.Flags; + } + if (other.SecureRelease != 0UL) + { + SecureRelease = other.SecureRelease; + } + if (other.WhitelistStart != 0UL) + { + WhitelistStart = other.WhitelistStart; + } + if (other.WhitelistEnd != 0UL) + { + WhitelistEnd = other.WhitelistEnd; + } + if (other.FullName.Length != 0) + { + FullName = other.FullName; + } + licenses_.Add(other.licenses_); + credentials_.Add(other.credentials_); + accountLinks_.Add(other.accountLinks_); + if (other.BattleTag.Length != 0) + { + BattleTag = other.BattleTag; + } + if (other.DefaultCurrency != 0) + { + DefaultCurrency = other.DefaultCurrency; + } + if (other.LegalRegion != 0) + { + LegalRegion = other.LegalRegion; + } + if (other.LegalLocale != 0) + { + LegalLocale = other.LegalLocale; + } + if (other.CacheExpiration != 0UL) + { + CacheExpiration = other.CacheExpiration; + } + if (other.parentalControlInfo_ != null) + { + if (parentalControlInfo_ == null) + { + parentalControlInfo_ = new Bgs.Protocol.Account.V1.ParentalControlInfo(); + } + ParentalControlInfo.MergeFrom(other.ParentalControlInfo); + } + if (other.Country.Length != 0) + { + Country = other.Country; + } + if (other.PreferredRegion != 0) + { + PreferredRegion = other.PreferredRegion; + } + if (other.IdentityCheckStatus != Bgs.Protocol.Account.V1.IdentityVerificationStatus.IDENT_NO_DATA) + { + IdentityCheckStatus = other.IdentityCheckStatus; + } + } + + public void MergeFrom(pb::CodedInputStream input) + { + uint tag; + while ((tag = input.ReadTag()) != 0) + { + switch (tag) + { + default: + input.SkipLastField(); + break; + case 21: + { + Id = input.ReadFixed32(); + break; + } + case 24: + { + Region = input.ReadUInt32(); + break; + } + case 34: + { + email_.AddEntriesFrom(input, _repeated_email_codec); + break; + } + case 40: + { + Flags = input.ReadUInt64(); + break; + } + case 48: + { + SecureRelease = input.ReadUInt64(); + break; + } + case 56: + { + WhitelistStart = input.ReadUInt64(); + break; + } + case 64: + { + WhitelistEnd = input.ReadUInt64(); + break; + } + case 82: + { + FullName = input.ReadString(); + break; + } + case 162: + { + licenses_.AddEntriesFrom(input, _repeated_licenses_codec); + break; + } + case 170: + { + credentials_.AddEntriesFrom(input, _repeated_credentials_codec); + break; + } + case 178: + { + accountLinks_.AddEntriesFrom(input, _repeated_accountLinks_codec); + break; + } + case 186: + { + BattleTag = input.ReadString(); + break; + } + case 205: + { + DefaultCurrency = input.ReadFixed32(); + break; + } + case 208: + { + LegalRegion = input.ReadUInt32(); + break; + } + case 221: + { + LegalLocale = input.ReadFixed32(); + break; + } + case 240: + { + CacheExpiration = input.ReadUInt64(); + break; + } + case 250: + { + if (parentalControlInfo_ == null) + { + parentalControlInfo_ = new Bgs.Protocol.Account.V1.ParentalControlInfo(); + } + input.ReadMessage(parentalControlInfo_); + break; + } + case 258: + { + Country = input.ReadString(); + break; + } + case 264: + { + PreferredRegion = input.ReadUInt32(); + break; + } + case 272: + { + identityCheckStatus_ = (Bgs.Protocol.Account.V1.IdentityVerificationStatus)input.ReadEnum(); + break; + } + } + } + } + + } + + [System.Diagnostics.DebuggerNonUserCodeAttribute()] + public sealed partial class AccountBlobList : pb::IMessage + { + private static readonly pb::MessageParser _parser = new pb::MessageParser(() => new AccountBlobList()); + public static pb::MessageParser Parser { get { return _parser; } } + + public static pbr::MessageDescriptor Descriptor + { + get { return Bgs.Protocol.Account.V1.AccountTypesReflection.Descriptor.MessageTypes[4]; } + } + + pbr::MessageDescriptor pb::IMessage.Descriptor + { + get { return Descriptor; } + } + + public AccountBlobList() + { + OnConstruction(); + } + + partial void OnConstruction(); + + public AccountBlobList(AccountBlobList other) : this() + { + blob_ = other.blob_.Clone(); + } + + public AccountBlobList Clone() + { + return new AccountBlobList(this); + } + + /// Field number for the "blob" field. + public const int BlobFieldNumber = 1; + private static readonly pb::FieldCodec _repeated_blob_codec + = pb::FieldCodec.ForMessage(10, Bgs.Protocol.Account.V1.AccountBlob.Parser); + private readonly pbc::RepeatedField blob_ = new pbc::RepeatedField(); + public pbc::RepeatedField Blob + { + get { return blob_; } + } + + public override bool Equals(object other) + { + return Equals(other as AccountBlobList); + } + + public bool Equals(AccountBlobList other) + { + if (ReferenceEquals(other, null)) + { + return false; + } + if (ReferenceEquals(other, this)) + { + return true; + } + if (!blob_.Equals(other.blob_)) return false; + return true; + } + + public override int GetHashCode() + { + int hash = 1; + hash ^= blob_.GetHashCode(); + return hash; + } + + public override string ToString() + { + return pb::JsonFormatter.ToDiagnosticString(this); + } + + public void WriteTo(pb::CodedOutputStream output) + { + blob_.WriteTo(output, _repeated_blob_codec); + } + + public int CalculateSize() + { + int size = 0; + size += blob_.CalculateSize(_repeated_blob_codec); + return size; + } + + public void MergeFrom(AccountBlobList other) + { + if (other == null) + { + return; + } + blob_.Add(other.blob_); + } + + public void MergeFrom(pb::CodedInputStream input) + { + uint tag; + while ((tag = input.ReadTag()) != 0) + { + switch (tag) + { + default: + input.SkipLastField(); + break; + case 10: + { + blob_.AddEntriesFrom(input, _repeated_blob_codec); + break; + } + } + } + } + + } + + [System.Diagnostics.DebuggerNonUserCodeAttribute()] + public sealed partial class GameAccountHandle : pb::IMessage + { + private static readonly pb::MessageParser _parser = new pb::MessageParser(() => new GameAccountHandle()); + public static pb::MessageParser Parser { get { return _parser; } } + + public static pbr::MessageDescriptor Descriptor + { + get { return Bgs.Protocol.Account.V1.AccountTypesReflection.Descriptor.MessageTypes[5]; } + } + + pbr::MessageDescriptor pb::IMessage.Descriptor + { + get { return Descriptor; } + } + + public GameAccountHandle() + { + OnConstruction(); + } + + partial void OnConstruction(); + + public GameAccountHandle(GameAccountHandle other) : this() + { + id_ = other.id_; + program_ = other.program_; + region_ = other.region_; + } + + public GameAccountHandle Clone() + { + return new GameAccountHandle(this); + } + + /// Field number for the "id" field. + public const int IdFieldNumber = 1; + private uint id_; + public uint Id + { + get { return id_; } + set + { + id_ = value; + } + } + + /// Field number for the "program" field. + public const int ProgramFieldNumber = 2; + private uint program_; + public uint Program + { + get { return program_; } + set + { + program_ = value; + } + } + + /// Field number for the "region" field. + public const int RegionFieldNumber = 3; + private uint region_; + public uint Region + { + get { return region_; } + set + { + region_ = value; + } + } + + public override bool Equals(object other) + { + return Equals(other as GameAccountHandle); + } + + public bool Equals(GameAccountHandle other) + { + if (ReferenceEquals(other, null)) + { + return false; + } + if (ReferenceEquals(other, this)) + { + return true; + } + if (Id != other.Id) return false; + if (Program != other.Program) return false; + if (Region != other.Region) return false; + return true; + } + + public override int GetHashCode() + { + int hash = 1; + if (Id != 0) hash ^= Id.GetHashCode(); + if (Program != 0) hash ^= Program.GetHashCode(); + if (Region != 0) hash ^= Region.GetHashCode(); + return hash; + } + + public override string ToString() + { + return pb::JsonFormatter.ToDiagnosticString(this); + } + + public void WriteTo(pb::CodedOutputStream output) + { + if (Id != 0) + { + output.WriteRawTag(13); + output.WriteFixed32(Id); + } + if (Program != 0) + { + output.WriteRawTag(21); + output.WriteFixed32(Program); + } + if (Region != 0) + { + output.WriteRawTag(24); + output.WriteUInt32(Region); + } + } + + public int CalculateSize() + { + int size = 0; + if (Id != 0) + { + size += 1 + 4; + } + if (Program != 0) + { + size += 1 + 4; + } + if (Region != 0) + { + size += 1 + pb::CodedOutputStream.ComputeUInt32Size(Region); + } + return size; + } + + public void MergeFrom(GameAccountHandle other) + { + if (other == null) + { + return; + } + if (other.Id != 0) + { + Id = other.Id; + } + if (other.Program != 0) + { + Program = other.Program; + } + if (other.Region != 0) + { + Region = other.Region; + } + } + + public void MergeFrom(pb::CodedInputStream input) + { + uint tag; + while ((tag = input.ReadTag()) != 0) + { + switch (tag) + { + default: + input.SkipLastField(); + break; + case 13: + { + Id = input.ReadFixed32(); + break; + } + case 21: + { + Program = input.ReadFixed32(); + break; + } + case 24: + { + Region = input.ReadUInt32(); + break; + } + } + } + } + + } + + [System.Diagnostics.DebuggerNonUserCodeAttribute()] + public sealed partial class GameAccountLink : pb::IMessage + { + private static readonly pb::MessageParser _parser = new pb::MessageParser(() => new GameAccountLink()); + public static pb::MessageParser Parser { get { return _parser; } } + + public static pbr::MessageDescriptor Descriptor + { + get { return Bgs.Protocol.Account.V1.AccountTypesReflection.Descriptor.MessageTypes[6]; } + } + + pbr::MessageDescriptor pb::IMessage.Descriptor + { + get { return Descriptor; } + } + + public GameAccountLink() + { + OnConstruction(); + } + + partial void OnConstruction(); + + public GameAccountLink(GameAccountLink other) : this() + { + GameAccount = other.gameAccount_ != null ? other.GameAccount.Clone() : null; + name_ = other.name_; + } + + public GameAccountLink Clone() + { + return new GameAccountLink(this); + } + + /// Field number for the "game_account" field. + public const int GameAccountFieldNumber = 1; + private Bgs.Protocol.Account.V1.GameAccountHandle gameAccount_; + public Bgs.Protocol.Account.V1.GameAccountHandle GameAccount + { + get { return gameAccount_; } + set + { + gameAccount_ = value; + } + } + + /// Field number for the "name" field. + public const int NameFieldNumber = 2; + private string name_ = ""; + public string Name + { + get { return name_; } + set + { + name_ = pb::ProtoPreconditions.CheckNotNull(value, "value"); + } + } + + public override bool Equals(object other) + { + return Equals(other as GameAccountLink); + } + + public bool Equals(GameAccountLink other) + { + if (ReferenceEquals(other, null)) + { + return false; + } + if (ReferenceEquals(other, this)) + { + return true; + } + if (!object.Equals(GameAccount, other.GameAccount)) return false; + if (Name != other.Name) return false; + return true; + } + + public override int GetHashCode() + { + int hash = 1; + if (gameAccount_ != null) hash ^= GameAccount.GetHashCode(); + if (Name.Length != 0) hash ^= Name.GetHashCode(); + return hash; + } + + public override string ToString() + { + return pb::JsonFormatter.ToDiagnosticString(this); + } + + public void WriteTo(pb::CodedOutputStream output) + { + if (gameAccount_ != null) + { + output.WriteRawTag(10); + output.WriteMessage(GameAccount); + } + if (Name.Length != 0) + { + output.WriteRawTag(18); + output.WriteString(Name); + } + } + + public int CalculateSize() + { + int size = 0; + if (gameAccount_ != null) + { + size += 1 + pb::CodedOutputStream.ComputeMessageSize(GameAccount); + } + if (Name.Length != 0) + { + size += 1 + pb::CodedOutputStream.ComputeStringSize(Name); + } + return size; + } + + public void MergeFrom(GameAccountLink other) + { + if (other == null) + { + return; + } + if (other.gameAccount_ != null) + { + if (gameAccount_ == null) + { + gameAccount_ = new Bgs.Protocol.Account.V1.GameAccountHandle(); + } + GameAccount.MergeFrom(other.GameAccount); + } + if (other.Name.Length != 0) + { + Name = other.Name; + } + } + + public void MergeFrom(pb::CodedInputStream input) + { + uint tag; + while ((tag = input.ReadTag()) != 0) + { + switch (tag) + { + default: + input.SkipLastField(); + break; + case 10: + { + if (gameAccount_ == null) + { + gameAccount_ = new Bgs.Protocol.Account.V1.GameAccountHandle(); + } + input.ReadMessage(gameAccount_); + break; + } + case 18: + { + Name = input.ReadString(); + break; + } + } + } + } + + } + + [System.Diagnostics.DebuggerNonUserCodeAttribute()] + public sealed partial class GameAccountBlob : pb::IMessage + { + private static readonly pb::MessageParser _parser = new pb::MessageParser(() => new GameAccountBlob()); + public static pb::MessageParser Parser { get { return _parser; } } + + public static pbr::MessageDescriptor Descriptor + { + get { return Bgs.Protocol.Account.V1.AccountTypesReflection.Descriptor.MessageTypes[7]; } + } + + pbr::MessageDescriptor pb::IMessage.Descriptor + { + get { return Descriptor; } + } + + public GameAccountBlob() + { + OnConstruction(); + } + + partial void OnConstruction(); + + public GameAccountBlob(GameAccountBlob other) : this() + { + GameAccount = other.gameAccount_ != null ? other.GameAccount.Clone() : null; + name_ = other.name_; + realmPermissions_ = other.realmPermissions_; + status_ = other.status_; + flags_ = other.flags_; + billingFlags_ = other.billingFlags_; + cacheExpiration_ = other.cacheExpiration_; + subscriptionExpiration_ = other.subscriptionExpiration_; + unitsRemaining_ = other.unitsRemaining_; + statusExpiration_ = other.statusExpiration_; + boxLevel_ = other.boxLevel_; + boxLevelExpiration_ = other.boxLevelExpiration_; + licenses_ = other.licenses_.Clone(); + rafAccount_ = other.rafAccount_; + rafInfo_ = other.rafInfo_; + rafExpiration_ = other.rafExpiration_; + } + + public GameAccountBlob Clone() + { + return new GameAccountBlob(this); + } + + /// Field number for the "game_account" field. + public const int GameAccountFieldNumber = 1; + private Bgs.Protocol.Account.V1.GameAccountHandle gameAccount_; + public Bgs.Protocol.Account.V1.GameAccountHandle GameAccount + { + get { return gameAccount_; } + set + { + gameAccount_ = value; + } + } + + /// Field number for the "name" field. + public const int NameFieldNumber = 2; + private string name_ = ""; + public string Name + { + get { return name_; } + set + { + name_ = pb::ProtoPreconditions.CheckNotNull(value, "value"); + } + } + + /// Field number for the "realm_permissions" field. + public const int RealmPermissionsFieldNumber = 3; + private uint realmPermissions_; + public uint RealmPermissions + { + get { return realmPermissions_; } + set + { + realmPermissions_ = value; + } + } + + /// Field number for the "status" field. + public const int StatusFieldNumber = 4; + private uint status_; + public uint Status + { + get { return status_; } + set + { + status_ = value; + } + } + + /// Field number for the "flags" field. + public const int FlagsFieldNumber = 5; + private ulong flags_; + public ulong Flags + { + get { return flags_; } + set + { + flags_ = value; + } + } + + /// Field number for the "billing_flags" field. + public const int BillingFlagsFieldNumber = 6; + private uint billingFlags_; + public uint BillingFlags + { + get { return billingFlags_; } + set + { + billingFlags_ = value; + } + } + + /// Field number for the "cache_expiration" field. + public const int CacheExpirationFieldNumber = 7; + private ulong cacheExpiration_; + public ulong CacheExpiration + { + get { return cacheExpiration_; } + set + { + cacheExpiration_ = value; + } + } + + /// Field number for the "subscription_expiration" field. + public const int SubscriptionExpirationFieldNumber = 10; + private ulong subscriptionExpiration_; + public ulong SubscriptionExpiration + { + get { return subscriptionExpiration_; } + set + { + subscriptionExpiration_ = value; + } + } + + /// Field number for the "units_remaining" field. + public const int UnitsRemainingFieldNumber = 11; + private uint unitsRemaining_; + public uint UnitsRemaining + { + get { return unitsRemaining_; } + set + { + unitsRemaining_ = value; + } + } + + /// Field number for the "status_expiration" field. + public const int StatusExpirationFieldNumber = 12; + private ulong statusExpiration_; + public ulong StatusExpiration + { + get { return statusExpiration_; } + set + { + statusExpiration_ = value; + } + } + + /// Field number for the "box_level" field. + public const int BoxLevelFieldNumber = 13; + private uint boxLevel_; + public uint BoxLevel + { + get { return boxLevel_; } + set + { + boxLevel_ = value; + } + } + + /// Field number for the "box_level_expiration" field. + public const int BoxLevelExpirationFieldNumber = 14; + private ulong boxLevelExpiration_; + public ulong BoxLevelExpiration + { + get { return boxLevelExpiration_; } + set + { + boxLevelExpiration_ = value; + } + } + + /// Field number for the "licenses" field. + public const int LicensesFieldNumber = 20; + private static readonly pb::FieldCodec _repeated_licenses_codec + = pb::FieldCodec.ForMessage(162, Bgs.Protocol.Account.V1.AccountLicense.Parser); + private readonly pbc::RepeatedField licenses_ = new pbc::RepeatedField(); + public pbc::RepeatedField Licenses + { + get { return licenses_; } + } + + /// Field number for the "raf_account" field. + public const int RafAccountFieldNumber = 21; + private uint rafAccount_; + public uint RafAccount + { + get { return rafAccount_; } + set + { + rafAccount_ = value; + } + } + + /// Field number for the "raf_info" field. + public const int RafInfoFieldNumber = 22; + private pb::ByteString rafInfo_ = pb::ByteString.Empty; + public pb::ByteString RafInfo + { + get { return rafInfo_; } + set + { + rafInfo_ = pb::ProtoPreconditions.CheckNotNull(value, "value"); + } + } + + /// Field number for the "raf_expiration" field. + public const int RafExpirationFieldNumber = 23; + private ulong rafExpiration_; + public ulong RafExpiration + { + get { return rafExpiration_; } + set + { + rafExpiration_ = value; + } + } + + public override bool Equals(object other) + { + return Equals(other as GameAccountBlob); + } + + public bool Equals(GameAccountBlob other) + { + if (ReferenceEquals(other, null)) + { + return false; + } + if (ReferenceEquals(other, this)) + { + return true; + } + if (!object.Equals(GameAccount, other.GameAccount)) return false; + if (Name != other.Name) return false; + if (RealmPermissions != other.RealmPermissions) return false; + if (Status != other.Status) return false; + if (Flags != other.Flags) return false; + if (BillingFlags != other.BillingFlags) return false; + if (CacheExpiration != other.CacheExpiration) return false; + if (SubscriptionExpiration != other.SubscriptionExpiration) return false; + if (UnitsRemaining != other.UnitsRemaining) return false; + if (StatusExpiration != other.StatusExpiration) return false; + if (BoxLevel != other.BoxLevel) return false; + if (BoxLevelExpiration != other.BoxLevelExpiration) return false; + if (!licenses_.Equals(other.licenses_)) return false; + if (RafAccount != other.RafAccount) return false; + if (RafInfo != other.RafInfo) return false; + if (RafExpiration != other.RafExpiration) return false; + return true; + } + + public override int GetHashCode() + { + int hash = 1; + if (gameAccount_ != null) hash ^= GameAccount.GetHashCode(); + if (Name.Length != 0) hash ^= Name.GetHashCode(); + if (RealmPermissions != 0) hash ^= RealmPermissions.GetHashCode(); + if (Status != 0) hash ^= Status.GetHashCode(); + if (Flags != 0UL) hash ^= Flags.GetHashCode(); + if (BillingFlags != 0) hash ^= BillingFlags.GetHashCode(); + if (CacheExpiration != 0UL) hash ^= CacheExpiration.GetHashCode(); + if (SubscriptionExpiration != 0UL) hash ^= SubscriptionExpiration.GetHashCode(); + if (UnitsRemaining != 0) hash ^= UnitsRemaining.GetHashCode(); + if (StatusExpiration != 0UL) hash ^= StatusExpiration.GetHashCode(); + if (BoxLevel != 0) hash ^= BoxLevel.GetHashCode(); + if (BoxLevelExpiration != 0UL) hash ^= BoxLevelExpiration.GetHashCode(); + hash ^= licenses_.GetHashCode(); + if (RafAccount != 0) hash ^= RafAccount.GetHashCode(); + if (RafInfo.Length != 0) hash ^= RafInfo.GetHashCode(); + if (RafExpiration != 0UL) hash ^= RafExpiration.GetHashCode(); + return hash; + } + + public override string ToString() + { + return pb::JsonFormatter.ToDiagnosticString(this); + } + + public void WriteTo(pb::CodedOutputStream output) + { + if (gameAccount_ != null) + { + output.WriteRawTag(10); + output.WriteMessage(GameAccount); + } + if (Name.Length != 0) + { + output.WriteRawTag(18); + output.WriteString(Name); + } + if (RealmPermissions != 0) + { + output.WriteRawTag(24); + output.WriteUInt32(RealmPermissions); + } + if (Status != 0) + { + output.WriteRawTag(32); + output.WriteUInt32(Status); + } + if (Flags != 0UL) + { + output.WriteRawTag(40); + output.WriteUInt64(Flags); + } + if (BillingFlags != 0) + { + output.WriteRawTag(48); + output.WriteUInt32(BillingFlags); + } + if (CacheExpiration != 0UL) + { + output.WriteRawTag(56); + output.WriteUInt64(CacheExpiration); + } + if (SubscriptionExpiration != 0UL) + { + output.WriteRawTag(80); + output.WriteUInt64(SubscriptionExpiration); + } + if (UnitsRemaining != 0) + { + output.WriteRawTag(88); + output.WriteUInt32(UnitsRemaining); + } + if (StatusExpiration != 0UL) + { + output.WriteRawTag(96); + output.WriteUInt64(StatusExpiration); + } + if (BoxLevel != 0) + { + output.WriteRawTag(104); + output.WriteUInt32(BoxLevel); + } + if (BoxLevelExpiration != 0UL) + { + output.WriteRawTag(112); + output.WriteUInt64(BoxLevelExpiration); + } + licenses_.WriteTo(output, _repeated_licenses_codec); + if (RafAccount != 0) + { + output.WriteRawTag(173, 1); + output.WriteFixed32(RafAccount); + } + if (RafInfo.Length != 0) + { + output.WriteRawTag(178, 1); + output.WriteBytes(RafInfo); + } + if (RafExpiration != 0UL) + { + output.WriteRawTag(184, 1); + output.WriteUInt64(RafExpiration); + } + } + + public int CalculateSize() + { + int size = 0; + if (gameAccount_ != null) + { + size += 1 + pb::CodedOutputStream.ComputeMessageSize(GameAccount); + } + if (Name.Length != 0) + { + size += 1 + pb::CodedOutputStream.ComputeStringSize(Name); + } + if (RealmPermissions != 0) + { + size += 1 + pb::CodedOutputStream.ComputeUInt32Size(RealmPermissions); + } + if (Status != 0) + { + size += 1 + pb::CodedOutputStream.ComputeUInt32Size(Status); + } + if (Flags != 0UL) + { + size += 1 + pb::CodedOutputStream.ComputeUInt64Size(Flags); + } + if (BillingFlags != 0) + { + size += 1 + pb::CodedOutputStream.ComputeUInt32Size(BillingFlags); + } + if (CacheExpiration != 0UL) + { + size += 1 + pb::CodedOutputStream.ComputeUInt64Size(CacheExpiration); + } + if (SubscriptionExpiration != 0UL) + { + size += 1 + pb::CodedOutputStream.ComputeUInt64Size(SubscriptionExpiration); + } + if (UnitsRemaining != 0) + { + size += 1 + pb::CodedOutputStream.ComputeUInt32Size(UnitsRemaining); + } + if (StatusExpiration != 0UL) + { + size += 1 + pb::CodedOutputStream.ComputeUInt64Size(StatusExpiration); + } + if (BoxLevel != 0) + { + size += 1 + pb::CodedOutputStream.ComputeUInt32Size(BoxLevel); + } + if (BoxLevelExpiration != 0UL) + { + size += 1 + pb::CodedOutputStream.ComputeUInt64Size(BoxLevelExpiration); + } + size += licenses_.CalculateSize(_repeated_licenses_codec); + if (RafAccount != 0) + { + size += 2 + 4; + } + if (RafInfo.Length != 0) + { + size += 2 + pb::CodedOutputStream.ComputeBytesSize(RafInfo); + } + if (RafExpiration != 0UL) + { + size += 2 + pb::CodedOutputStream.ComputeUInt64Size(RafExpiration); + } + return size; + } + + public void MergeFrom(GameAccountBlob other) + { + if (other == null) + { + return; + } + if (other.gameAccount_ != null) + { + if (gameAccount_ == null) + { + gameAccount_ = new Bgs.Protocol.Account.V1.GameAccountHandle(); + } + GameAccount.MergeFrom(other.GameAccount); + } + if (other.Name.Length != 0) + { + Name = other.Name; + } + if (other.RealmPermissions != 0) + { + RealmPermissions = other.RealmPermissions; + } + if (other.Status != 0) + { + Status = other.Status; + } + if (other.Flags != 0UL) + { + Flags = other.Flags; + } + if (other.BillingFlags != 0) + { + BillingFlags = other.BillingFlags; + } + if (other.CacheExpiration != 0UL) + { + CacheExpiration = other.CacheExpiration; + } + if (other.SubscriptionExpiration != 0UL) + { + SubscriptionExpiration = other.SubscriptionExpiration; + } + if (other.UnitsRemaining != 0) + { + UnitsRemaining = other.UnitsRemaining; + } + if (other.StatusExpiration != 0UL) + { + StatusExpiration = other.StatusExpiration; + } + if (other.BoxLevel != 0) + { + BoxLevel = other.BoxLevel; + } + if (other.BoxLevelExpiration != 0UL) + { + BoxLevelExpiration = other.BoxLevelExpiration; + } + licenses_.Add(other.licenses_); + if (other.RafAccount != 0) + { + RafAccount = other.RafAccount; + } + if (other.RafInfo.Length != 0) + { + RafInfo = other.RafInfo; + } + if (other.RafExpiration != 0UL) + { + RafExpiration = other.RafExpiration; + } + } + + public void MergeFrom(pb::CodedInputStream input) + { + uint tag; + while ((tag = input.ReadTag()) != 0) + { + switch (tag) + { + default: + input.SkipLastField(); + break; + case 10: + { + if (gameAccount_ == null) + { + gameAccount_ = new Bgs.Protocol.Account.V1.GameAccountHandle(); + } + input.ReadMessage(gameAccount_); + break; + } + case 18: + { + Name = input.ReadString(); + break; + } + case 24: + { + RealmPermissions = input.ReadUInt32(); + break; + } + case 32: + { + Status = input.ReadUInt32(); + break; + } + case 40: + { + Flags = input.ReadUInt64(); + break; + } + case 48: + { + BillingFlags = input.ReadUInt32(); + break; + } + case 56: + { + CacheExpiration = input.ReadUInt64(); + break; + } + case 80: + { + SubscriptionExpiration = input.ReadUInt64(); + break; + } + case 88: + { + UnitsRemaining = input.ReadUInt32(); + break; + } + case 96: + { + StatusExpiration = input.ReadUInt64(); + break; + } + case 104: + { + BoxLevel = input.ReadUInt32(); + break; + } + case 112: + { + BoxLevelExpiration = input.ReadUInt64(); + break; + } + case 162: + { + licenses_.AddEntriesFrom(input, _repeated_licenses_codec); + break; + } + case 173: + { + RafAccount = input.ReadFixed32(); + break; + } + case 178: + { + RafInfo = input.ReadBytes(); + break; + } + case 184: + { + RafExpiration = input.ReadUInt64(); + break; + } + } + } + } + + } + + [System.Diagnostics.DebuggerNonUserCodeAttribute()] + public sealed partial class GameAccountBlobList : pb::IMessage + { + private static readonly pb::MessageParser _parser = new pb::MessageParser(() => new GameAccountBlobList()); + public static pb::MessageParser Parser { get { return _parser; } } + + public static pbr::MessageDescriptor Descriptor + { + get { return Bgs.Protocol.Account.V1.AccountTypesReflection.Descriptor.MessageTypes[8]; } + } + + pbr::MessageDescriptor pb::IMessage.Descriptor + { + get { return Descriptor; } + } + + public GameAccountBlobList() + { + OnConstruction(); + } + + partial void OnConstruction(); + + public GameAccountBlobList(GameAccountBlobList other) : this() + { + blob_ = other.blob_.Clone(); + } + + public GameAccountBlobList Clone() + { + return new GameAccountBlobList(this); + } + + /// Field number for the "blob" field. + public const int BlobFieldNumber = 1; + private static readonly pb::FieldCodec _repeated_blob_codec + = pb::FieldCodec.ForMessage(10, Bgs.Protocol.Account.V1.GameAccountBlob.Parser); + private readonly pbc::RepeatedField blob_ = new pbc::RepeatedField(); + public pbc::RepeatedField Blob + { + get { return blob_; } + } + + public override bool Equals(object other) + { + return Equals(other as GameAccountBlobList); + } + + public bool Equals(GameAccountBlobList other) + { + if (ReferenceEquals(other, null)) + { + return false; + } + if (ReferenceEquals(other, this)) + { + return true; + } + if (!blob_.Equals(other.blob_)) return false; + return true; + } + + public override int GetHashCode() + { + int hash = 1; + hash ^= blob_.GetHashCode(); + return hash; + } + + public override string ToString() + { + return pb::JsonFormatter.ToDiagnosticString(this); + } + + public void WriteTo(pb::CodedOutputStream output) + { + blob_.WriteTo(output, _repeated_blob_codec); + } + + public int CalculateSize() + { + int size = 0; + size += blob_.CalculateSize(_repeated_blob_codec); + return size; + } + + public void MergeFrom(GameAccountBlobList other) + { + if (other == null) + { + return; + } + blob_.Add(other.blob_); + } + + public void MergeFrom(pb::CodedInputStream input) + { + uint tag; + while ((tag = input.ReadTag()) != 0) + { + switch (tag) + { + default: + input.SkipLastField(); + break; + case 10: + { + blob_.AddEntriesFrom(input, _repeated_blob_codec); + break; + } + } + } + } + + } + + [System.Diagnostics.DebuggerNonUserCodeAttribute()] + public sealed partial class AccountReference : pb::IMessage + { + private static readonly pb::MessageParser _parser = new pb::MessageParser(() => new AccountReference()); + public static pb::MessageParser Parser { get { return _parser; } } + + public static pbr::MessageDescriptor Descriptor + { + get { return Bgs.Protocol.Account.V1.AccountTypesReflection.Descriptor.MessageTypes[9]; } + } + + pbr::MessageDescriptor pb::IMessage.Descriptor + { + get { return Descriptor; } + } + + public AccountReference() + { + OnConstruction(); + } + + partial void OnConstruction(); + + public AccountReference(AccountReference other) : this() + { + id_ = other.id_; + email_ = other.email_; + Handle = other.handle_ != null ? other.Handle.Clone() : null; + battleTag_ = other.battleTag_; + region_ = other.region_; + } + + public AccountReference Clone() + { + return new AccountReference(this); + } + + /// Field number for the "id" field. + public const int IdFieldNumber = 1; + private uint id_; + public uint Id + { + get { return id_; } + set + { + id_ = value; + } + } + + /// Field number for the "email" field. + public const int EmailFieldNumber = 2; + private string email_ = ""; + public string Email + { + get { return email_; } + set + { + email_ = pb::ProtoPreconditions.CheckNotNull(value, "value"); + } + } + + /// Field number for the "handle" field. + public const int HandleFieldNumber = 3; + private Bgs.Protocol.Account.V1.GameAccountHandle handle_; + public Bgs.Protocol.Account.V1.GameAccountHandle Handle + { + get { return handle_; } + set + { + handle_ = value; + } + } + + /// Field number for the "battle_tag" field. + public const int BattleTagFieldNumber = 4; + private string battleTag_ = ""; + public string BattleTag + { + get { return battleTag_; } + set + { + battleTag_ = pb::ProtoPreconditions.CheckNotNull(value, "value"); + } + } + + /// Field number for the "region" field. + public const int RegionFieldNumber = 10; + private uint region_; + public uint Region + { + get { return region_; } + set + { + region_ = value; + } + } + + public override bool Equals(object other) + { + return Equals(other as AccountReference); + } + + public bool Equals(AccountReference other) + { + if (ReferenceEquals(other, null)) + { + return false; + } + if (ReferenceEquals(other, this)) + { + return true; + } + if (Id != other.Id) return false; + if (Email != other.Email) return false; + if (!object.Equals(Handle, other.Handle)) return false; + if (BattleTag != other.BattleTag) return false; + if (Region != other.Region) return false; + return true; + } + + public override int GetHashCode() + { + int hash = 1; + if (Id != 0) hash ^= Id.GetHashCode(); + if (Email.Length != 0) hash ^= Email.GetHashCode(); + if (handle_ != null) hash ^= Handle.GetHashCode(); + if (BattleTag.Length != 0) hash ^= BattleTag.GetHashCode(); + if (Region != 0) hash ^= Region.GetHashCode(); + return hash; + } + + public override string ToString() + { + return pb::JsonFormatter.ToDiagnosticString(this); + } + + public void WriteTo(pb::CodedOutputStream output) + { + if (Id != 0) + { + output.WriteRawTag(13); + output.WriteFixed32(Id); + } + if (Email.Length != 0) + { + output.WriteRawTag(18); + output.WriteString(Email); + } + if (handle_ != null) + { + output.WriteRawTag(26); + output.WriteMessage(Handle); + } + if (BattleTag.Length != 0) + { + output.WriteRawTag(34); + output.WriteString(BattleTag); + } + if (Region != 0) + { + output.WriteRawTag(80); + output.WriteUInt32(Region); + } + } + + public int CalculateSize() + { + int size = 0; + if (Id != 0) + { + size += 1 + 4; + } + if (Email.Length != 0) + { + size += 1 + pb::CodedOutputStream.ComputeStringSize(Email); + } + if (handle_ != null) + { + size += 1 + pb::CodedOutputStream.ComputeMessageSize(Handle); + } + if (BattleTag.Length != 0) + { + size += 1 + pb::CodedOutputStream.ComputeStringSize(BattleTag); + } + if (Region != 0) + { + size += 1 + pb::CodedOutputStream.ComputeUInt32Size(Region); + } + return size; + } + + public void MergeFrom(AccountReference other) + { + if (other == null) + { + return; + } + if (other.Id != 0) + { + Id = other.Id; + } + if (other.Email.Length != 0) + { + Email = other.Email; + } + if (other.handle_ != null) + { + if (handle_ == null) + { + handle_ = new Bgs.Protocol.Account.V1.GameAccountHandle(); + } + Handle.MergeFrom(other.Handle); + } + if (other.BattleTag.Length != 0) + { + BattleTag = other.BattleTag; + } + if (other.Region != 0) + { + Region = other.Region; + } + } + + public void MergeFrom(pb::CodedInputStream input) + { + uint tag; + while ((tag = input.ReadTag()) != 0) + { + switch (tag) + { + default: + input.SkipLastField(); + break; + case 13: + { + Id = input.ReadFixed32(); + break; + } + case 18: + { + Email = input.ReadString(); + break; + } + case 26: + { + if (handle_ == null) + { + handle_ = new Bgs.Protocol.Account.V1.GameAccountHandle(); + } + input.ReadMessage(handle_); + break; + } + case 34: + { + BattleTag = input.ReadString(); + break; + } + case 80: + { + Region = input.ReadUInt32(); + break; + } + } + } + } + + } + + [System.Diagnostics.DebuggerNonUserCodeAttribute()] + public sealed partial class Identity : pb::IMessage + { + private static readonly pb::MessageParser _parser = new pb::MessageParser(() => new Identity()); + public static pb::MessageParser Parser { get { return _parser; } } + + public static pbr::MessageDescriptor Descriptor + { + get { return Bgs.Protocol.Account.V1.AccountTypesReflection.Descriptor.MessageTypes[10]; } + } + + pbr::MessageDescriptor pb::IMessage.Descriptor + { + get { return Descriptor; } + } + + public Identity() + { + OnConstruction(); + } + + partial void OnConstruction(); + + public Identity(Identity other) : this() + { + Account = other.account_ != null ? other.Account.Clone() : null; + GameAccount = other.gameAccount_ != null ? other.GameAccount.Clone() : null; + Process = other.process_ != null ? other.Process.Clone() : null; + } + + public Identity Clone() + { + return new Identity(this); + } + + /// Field number for the "account" field. + public const int AccountFieldNumber = 1; + private Bgs.Protocol.Account.V1.AccountId account_; + public Bgs.Protocol.Account.V1.AccountId Account + { + get { return account_; } + set + { + account_ = value; + } + } + + /// Field number for the "game_account" field. + public const int GameAccountFieldNumber = 2; + private Bgs.Protocol.Account.V1.GameAccountHandle gameAccount_; + public Bgs.Protocol.Account.V1.GameAccountHandle GameAccount + { + get { return gameAccount_; } + set + { + gameAccount_ = value; + } + } + + /// Field number for the "process" field. + public const int ProcessFieldNumber = 3; + private Bgs.Protocol.ProcessId process_; + public Bgs.Protocol.ProcessId Process + { + get { return process_; } + set + { + process_ = value; + } + } + + public override bool Equals(object other) + { + return Equals(other as Identity); + } + + public bool Equals(Identity other) + { + if (ReferenceEquals(other, null)) + { + return false; + } + if (ReferenceEquals(other, this)) + { + return true; + } + if (!object.Equals(Account, other.Account)) return false; + if (!object.Equals(GameAccount, other.GameAccount)) return false; + if (!object.Equals(Process, other.Process)) return false; + return true; + } + + public override int GetHashCode() + { + int hash = 1; + if (account_ != null) hash ^= Account.GetHashCode(); + if (gameAccount_ != null) hash ^= GameAccount.GetHashCode(); + if (process_ != null) hash ^= Process.GetHashCode(); + return hash; + } + + public override string ToString() + { + return pb::JsonFormatter.ToDiagnosticString(this); + } + + public void WriteTo(pb::CodedOutputStream output) + { + if (account_ != null) + { + output.WriteRawTag(10); + output.WriteMessage(Account); + } + if (gameAccount_ != null) + { + output.WriteRawTag(18); + output.WriteMessage(GameAccount); + } + if (process_ != null) + { + output.WriteRawTag(26); + output.WriteMessage(Process); + } + } + + public int CalculateSize() + { + int size = 0; + if (account_ != null) + { + size += 1 + pb::CodedOutputStream.ComputeMessageSize(Account); + } + if (gameAccount_ != null) + { + size += 1 + pb::CodedOutputStream.ComputeMessageSize(GameAccount); + } + if (process_ != null) + { + size += 1 + pb::CodedOutputStream.ComputeMessageSize(Process); + } + return size; + } + + public void MergeFrom(Identity other) + { + if (other == null) + { + return; + } + if (other.account_ != null) + { + if (account_ == null) + { + account_ = new Bgs.Protocol.Account.V1.AccountId(); + } + Account.MergeFrom(other.Account); + } + if (other.gameAccount_ != null) + { + if (gameAccount_ == null) + { + gameAccount_ = new Bgs.Protocol.Account.V1.GameAccountHandle(); + } + GameAccount.MergeFrom(other.GameAccount); + } + if (other.process_ != null) + { + if (process_ == null) + { + process_ = new Bgs.Protocol.ProcessId(); + } + Process.MergeFrom(other.Process); + } + } + + public void MergeFrom(pb::CodedInputStream input) + { + uint tag; + while ((tag = input.ReadTag()) != 0) + { + switch (tag) + { + default: + input.SkipLastField(); + break; + case 10: + { + if (account_ == null) + { + account_ = new Bgs.Protocol.Account.V1.AccountId(); + } + input.ReadMessage(account_); + break; + } + case 18: + { + if (gameAccount_ == null) + { + gameAccount_ = new Bgs.Protocol.Account.V1.GameAccountHandle(); + } + input.ReadMessage(gameAccount_); + break; + } + case 26: + { + if (process_ == null) + { + process_ = new Bgs.Protocol.ProcessId(); + } + input.ReadMessage(process_); + break; + } + } + } + } + + } + + [System.Diagnostics.DebuggerNonUserCodeAttribute()] + public sealed partial class ProgramTag : pb::IMessage + { + private static readonly pb::MessageParser _parser = new pb::MessageParser(() => new ProgramTag()); + public static pb::MessageParser Parser { get { return _parser; } } + + public static pbr::MessageDescriptor Descriptor + { + get { return Bgs.Protocol.Account.V1.AccountTypesReflection.Descriptor.MessageTypes[11]; } + } + + pbr::MessageDescriptor pb::IMessage.Descriptor + { + get { return Descriptor; } + } + + public ProgramTag() + { + OnConstruction(); + } + + partial void OnConstruction(); + + public ProgramTag(ProgramTag other) : this() + { + program_ = other.program_; + tag_ = other.tag_; + } + + public ProgramTag Clone() + { + return new ProgramTag(this); + } + + /// Field number for the "program" field. + public const int ProgramFieldNumber = 1; + private uint program_; + public uint Program + { + get { return program_; } + set + { + program_ = value; + } + } + + /// Field number for the "tag" field. + public const int TagFieldNumber = 2; + private uint tag_; + public uint Tag + { + get { return tag_; } + set + { + tag_ = value; + } + } + + public override bool Equals(object other) + { + return Equals(other as ProgramTag); + } + + public bool Equals(ProgramTag other) + { + if (ReferenceEquals(other, null)) + { + return false; + } + if (ReferenceEquals(other, this)) + { + return true; + } + if (Program != other.Program) return false; + if (Tag != other.Tag) return false; + return true; + } + + public override int GetHashCode() + { + int hash = 1; + if (Program != 0) hash ^= Program.GetHashCode(); + if (Tag != 0) hash ^= Tag.GetHashCode(); + return hash; + } + + public override string ToString() + { + return pb::JsonFormatter.ToDiagnosticString(this); + } + + public void WriteTo(pb::CodedOutputStream output) + { + if (Program != 0) + { + output.WriteRawTag(13); + output.WriteFixed32(Program); + } + if (Tag != 0) + { + output.WriteRawTag(21); + output.WriteFixed32(Tag); + } + } + + public int CalculateSize() + { + int size = 0; + if (Program != 0) + { + size += 1 + 4; + } + if (Tag != 0) + { + size += 1 + 4; + } + return size; + } + + public void MergeFrom(ProgramTag other) + { + if (other == null) + { + return; + } + if (other.Program != 0) + { + Program = other.Program; + } + if (other.Tag != 0) + { + Tag = other.Tag; + } + } + + public void MergeFrom(pb::CodedInputStream input) + { + uint tag; + while ((tag = input.ReadTag()) != 0) + { + switch (tag) + { + default: + input.SkipLastField(); + break; + case 13: + { + Program = input.ReadFixed32(); + break; + } + case 21: + { + Tag = input.ReadFixed32(); + break; + } + } + } + } + + } + + [System.Diagnostics.DebuggerNonUserCodeAttribute()] + public sealed partial class RegionTag : pb::IMessage + { + private static readonly pb::MessageParser _parser = new pb::MessageParser(() => new RegionTag()); + public static pb::MessageParser Parser { get { return _parser; } } + + public static pbr::MessageDescriptor Descriptor + { + get { return Bgs.Protocol.Account.V1.AccountTypesReflection.Descriptor.MessageTypes[12]; } + } + + pbr::MessageDescriptor pb::IMessage.Descriptor + { + get { return Descriptor; } + } + + public RegionTag() + { + OnConstruction(); + } + + partial void OnConstruction(); + + public RegionTag(RegionTag other) : this() + { + region_ = other.region_; + tag_ = other.tag_; + } + + public RegionTag Clone() + { + return new RegionTag(this); + } + + /// Field number for the "region" field. + public const int RegionFieldNumber = 1; + private uint region_; + public uint Region + { + get { return region_; } + set + { + region_ = value; + } + } + + /// Field number for the "tag" field. + public const int TagFieldNumber = 2; + private uint tag_; + public uint Tag + { + get { return tag_; } + set + { + tag_ = value; + } + } + + public override bool Equals(object other) + { + return Equals(other as RegionTag); + } + + public bool Equals(RegionTag other) + { + if (ReferenceEquals(other, null)) + { + return false; + } + if (ReferenceEquals(other, this)) + { + return true; + } + if (Region != other.Region) return false; + if (Tag != other.Tag) return false; + return true; + } + + public override int GetHashCode() + { + int hash = 1; + if (Region != 0) hash ^= Region.GetHashCode(); + if (Tag != 0) hash ^= Tag.GetHashCode(); + return hash; + } + + public override string ToString() + { + return pb::JsonFormatter.ToDiagnosticString(this); + } + + public void WriteTo(pb::CodedOutputStream output) + { + if (Region != 0) + { + output.WriteRawTag(13); + output.WriteFixed32(Region); + } + if (Tag != 0) + { + output.WriteRawTag(21); + output.WriteFixed32(Tag); + } + } + + public int CalculateSize() + { + int size = 0; + if (Region != 0) + { + size += 1 + 4; + } + if (Tag != 0) + { + size += 1 + 4; + } + return size; + } + + public void MergeFrom(RegionTag other) + { + if (other == null) + { + return; + } + if (other.Region != 0) + { + Region = other.Region; + } + if (other.Tag != 0) + { + Tag = other.Tag; + } + } + + public void MergeFrom(pb::CodedInputStream input) + { + uint tag; + while ((tag = input.ReadTag()) != 0) + { + switch (tag) + { + default: + input.SkipLastField(); + break; + case 13: + { + Region = input.ReadFixed32(); + break; + } + case 21: + { + Tag = input.ReadFixed32(); + break; + } + } + } + } + + } + + [System.Diagnostics.DebuggerNonUserCodeAttribute()] + public sealed partial class AccountFieldTags : pb::IMessage + { + private static readonly pb::MessageParser _parser = new pb::MessageParser(() => new AccountFieldTags()); + public static pb::MessageParser Parser { get { return _parser; } } + + public static pbr::MessageDescriptor Descriptor + { + get { return Bgs.Protocol.Account.V1.AccountTypesReflection.Descriptor.MessageTypes[13]; } + } + + pbr::MessageDescriptor pb::IMessage.Descriptor + { + get { return Descriptor; } + } + + public AccountFieldTags() + { + OnConstruction(); + } + + partial void OnConstruction(); + + public AccountFieldTags(AccountFieldTags other) : this() + { + accountLevelInfoTag_ = other.accountLevelInfoTag_; + privacyInfoTag_ = other.privacyInfoTag_; + parentalControlInfoTag_ = other.parentalControlInfoTag_; + gameLevelInfoTags_ = other.gameLevelInfoTags_.Clone(); + gameStatusTags_ = other.gameStatusTags_.Clone(); + gameAccountTags_ = other.gameAccountTags_.Clone(); + } + + public AccountFieldTags Clone() + { + return new AccountFieldTags(this); + } + + /// Field number for the "account_level_info_tag" field. + public const int AccountLevelInfoTagFieldNumber = 2; + private uint accountLevelInfoTag_; + public uint AccountLevelInfoTag + { + get { return accountLevelInfoTag_; } + set + { + accountLevelInfoTag_ = value; + } + } + + /// Field number for the "privacy_info_tag" field. + public const int PrivacyInfoTagFieldNumber = 3; + private uint privacyInfoTag_; + public uint PrivacyInfoTag + { + get { return privacyInfoTag_; } + set + { + privacyInfoTag_ = value; + } + } + + /// Field number for the "parental_control_info_tag" field. + public const int ParentalControlInfoTagFieldNumber = 4; + private uint parentalControlInfoTag_; + public uint ParentalControlInfoTag + { + get { return parentalControlInfoTag_; } + set + { + parentalControlInfoTag_ = value; + } + } + + /// Field number for the "game_level_info_tags" field. + public const int GameLevelInfoTagsFieldNumber = 7; + private static readonly pb::FieldCodec _repeated_gameLevelInfoTags_codec + = pb::FieldCodec.ForMessage(58, Bgs.Protocol.Account.V1.ProgramTag.Parser); + private readonly pbc::RepeatedField gameLevelInfoTags_ = new pbc::RepeatedField(); + public pbc::RepeatedField GameLevelInfoTags + { + get { return gameLevelInfoTags_; } + } + + /// Field number for the "game_status_tags" field. + public const int GameStatusTagsFieldNumber = 9; + private static readonly pb::FieldCodec _repeated_gameStatusTags_codec + = pb::FieldCodec.ForMessage(74, Bgs.Protocol.Account.V1.ProgramTag.Parser); + private readonly pbc::RepeatedField gameStatusTags_ = new pbc::RepeatedField(); + public pbc::RepeatedField GameStatusTags + { + get { return gameStatusTags_; } + } + + /// Field number for the "game_account_tags" field. + public const int GameAccountTagsFieldNumber = 11; + private static readonly pb::FieldCodec _repeated_gameAccountTags_codec + = pb::FieldCodec.ForMessage(90, Bgs.Protocol.Account.V1.RegionTag.Parser); + private readonly pbc::RepeatedField gameAccountTags_ = new pbc::RepeatedField(); + public pbc::RepeatedField GameAccountTags + { + get { return gameAccountTags_; } + } + + public override bool Equals(object other) + { + return Equals(other as AccountFieldTags); + } + + public bool Equals(AccountFieldTags other) + { + if (ReferenceEquals(other, null)) + { + return false; + } + if (ReferenceEquals(other, this)) + { + return true; + } + if (AccountLevelInfoTag != other.AccountLevelInfoTag) return false; + if (PrivacyInfoTag != other.PrivacyInfoTag) return false; + if (ParentalControlInfoTag != other.ParentalControlInfoTag) return false; + if (!gameLevelInfoTags_.Equals(other.gameLevelInfoTags_)) return false; + if (!gameStatusTags_.Equals(other.gameStatusTags_)) return false; + if (!gameAccountTags_.Equals(other.gameAccountTags_)) return false; + return true; + } + + public override int GetHashCode() + { + int hash = 1; + if (AccountLevelInfoTag != 0) hash ^= AccountLevelInfoTag.GetHashCode(); + if (PrivacyInfoTag != 0) hash ^= PrivacyInfoTag.GetHashCode(); + if (ParentalControlInfoTag != 0) hash ^= ParentalControlInfoTag.GetHashCode(); + hash ^= gameLevelInfoTags_.GetHashCode(); + hash ^= gameStatusTags_.GetHashCode(); + hash ^= gameAccountTags_.GetHashCode(); + return hash; + } + + public override string ToString() + { + return pb::JsonFormatter.ToDiagnosticString(this); + } + + public void WriteTo(pb::CodedOutputStream output) + { + if (AccountLevelInfoTag != 0) + { + output.WriteRawTag(21); + output.WriteFixed32(AccountLevelInfoTag); + } + if (PrivacyInfoTag != 0) + { + output.WriteRawTag(29); + output.WriteFixed32(PrivacyInfoTag); + } + if (ParentalControlInfoTag != 0) + { + output.WriteRawTag(37); + output.WriteFixed32(ParentalControlInfoTag); + } + gameLevelInfoTags_.WriteTo(output, _repeated_gameLevelInfoTags_codec); + gameStatusTags_.WriteTo(output, _repeated_gameStatusTags_codec); + gameAccountTags_.WriteTo(output, _repeated_gameAccountTags_codec); + } + + public int CalculateSize() + { + int size = 0; + if (AccountLevelInfoTag != 0) + { + size += 1 + 4; + } + if (PrivacyInfoTag != 0) + { + size += 1 + 4; + } + if (ParentalControlInfoTag != 0) + { + size += 1 + 4; + } + size += gameLevelInfoTags_.CalculateSize(_repeated_gameLevelInfoTags_codec); + size += gameStatusTags_.CalculateSize(_repeated_gameStatusTags_codec); + size += gameAccountTags_.CalculateSize(_repeated_gameAccountTags_codec); + return size; + } + + public void MergeFrom(AccountFieldTags other) + { + if (other == null) + { + return; + } + if (other.AccountLevelInfoTag != 0) + { + AccountLevelInfoTag = other.AccountLevelInfoTag; + } + if (other.PrivacyInfoTag != 0) + { + PrivacyInfoTag = other.PrivacyInfoTag; + } + if (other.ParentalControlInfoTag != 0) + { + ParentalControlInfoTag = other.ParentalControlInfoTag; + } + gameLevelInfoTags_.Add(other.gameLevelInfoTags_); + gameStatusTags_.Add(other.gameStatusTags_); + gameAccountTags_.Add(other.gameAccountTags_); + } + + public void MergeFrom(pb::CodedInputStream input) + { + uint tag; + while ((tag = input.ReadTag()) != 0) + { + switch (tag) + { + default: + input.SkipLastField(); + break; + case 21: + { + AccountLevelInfoTag = input.ReadFixed32(); + break; + } + case 29: + { + PrivacyInfoTag = input.ReadFixed32(); + break; + } + case 37: + { + ParentalControlInfoTag = input.ReadFixed32(); + break; + } + case 58: + { + gameLevelInfoTags_.AddEntriesFrom(input, _repeated_gameLevelInfoTags_codec); + break; + } + case 74: + { + gameStatusTags_.AddEntriesFrom(input, _repeated_gameStatusTags_codec); + break; + } + case 90: + { + gameAccountTags_.AddEntriesFrom(input, _repeated_gameAccountTags_codec); + break; + } + } + } + } + + } + + [System.Diagnostics.DebuggerNonUserCodeAttribute()] + public sealed partial class GameAccountFieldTags : pb::IMessage + { + private static readonly pb::MessageParser _parser = new pb::MessageParser(() => new GameAccountFieldTags()); + public static pb::MessageParser Parser { get { return _parser; } } + + public static pbr::MessageDescriptor Descriptor + { + get { return Bgs.Protocol.Account.V1.AccountTypesReflection.Descriptor.MessageTypes[14]; } + } + + pbr::MessageDescriptor pb::IMessage.Descriptor + { + get { return Descriptor; } + } + + public GameAccountFieldTags() + { + OnConstruction(); + } + + partial void OnConstruction(); + + public GameAccountFieldTags(GameAccountFieldTags other) : this() + { + gameLevelInfoTag_ = other.gameLevelInfoTag_; + gameTimeInfoTag_ = other.gameTimeInfoTag_; + gameStatusTag_ = other.gameStatusTag_; + rafInfoTag_ = other.rafInfoTag_; + } + + public GameAccountFieldTags Clone() + { + return new GameAccountFieldTags(this); + } + + /// Field number for the "game_level_info_tag" field. + public const int GameLevelInfoTagFieldNumber = 2; + private uint gameLevelInfoTag_; + public uint GameLevelInfoTag + { + get { return gameLevelInfoTag_; } + set + { + gameLevelInfoTag_ = value; + } + } + + /// Field number for the "game_time_info_tag" field. + public const int GameTimeInfoTagFieldNumber = 3; + private uint gameTimeInfoTag_; + public uint GameTimeInfoTag + { + get { return gameTimeInfoTag_; } + set + { + gameTimeInfoTag_ = value; + } + } + + /// Field number for the "game_status_tag" field. + public const int GameStatusTagFieldNumber = 4; + private uint gameStatusTag_; + public uint GameStatusTag + { + get { return gameStatusTag_; } + set + { + gameStatusTag_ = value; + } + } + + /// Field number for the "raf_info_tag" field. + public const int RafInfoTagFieldNumber = 5; + private uint rafInfoTag_; + public uint RafInfoTag + { + get { return rafInfoTag_; } + set + { + rafInfoTag_ = value; + } + } + + public override bool Equals(object other) + { + return Equals(other as GameAccountFieldTags); + } + + public bool Equals(GameAccountFieldTags other) + { + if (ReferenceEquals(other, null)) + { + return false; + } + if (ReferenceEquals(other, this)) + { + return true; + } + if (GameLevelInfoTag != other.GameLevelInfoTag) return false; + if (GameTimeInfoTag != other.GameTimeInfoTag) return false; + if (GameStatusTag != other.GameStatusTag) return false; + if (RafInfoTag != other.RafInfoTag) return false; + return true; + } + + public override int GetHashCode() + { + int hash = 1; + if (GameLevelInfoTag != 0) hash ^= GameLevelInfoTag.GetHashCode(); + if (GameTimeInfoTag != 0) hash ^= GameTimeInfoTag.GetHashCode(); + if (GameStatusTag != 0) hash ^= GameStatusTag.GetHashCode(); + if (RafInfoTag != 0) hash ^= RafInfoTag.GetHashCode(); + return hash; + } + + public override string ToString() + { + return pb::JsonFormatter.ToDiagnosticString(this); + } + + public void WriteTo(pb::CodedOutputStream output) + { + if (GameLevelInfoTag != 0) + { + output.WriteRawTag(21); + output.WriteFixed32(GameLevelInfoTag); + } + if (GameTimeInfoTag != 0) + { + output.WriteRawTag(29); + output.WriteFixed32(GameTimeInfoTag); + } + if (GameStatusTag != 0) + { + output.WriteRawTag(37); + output.WriteFixed32(GameStatusTag); + } + if (RafInfoTag != 0) + { + output.WriteRawTag(45); + output.WriteFixed32(RafInfoTag); + } + } + + public int CalculateSize() + { + int size = 0; + if (GameLevelInfoTag != 0) + { + size += 1 + 4; + } + if (GameTimeInfoTag != 0) + { + size += 1 + 4; + } + if (GameStatusTag != 0) + { + size += 1 + 4; + } + if (RafInfoTag != 0) + { + size += 1 + 4; + } + return size; + } + + public void MergeFrom(GameAccountFieldTags other) + { + if (other == null) + { + return; + } + if (other.GameLevelInfoTag != 0) + { + GameLevelInfoTag = other.GameLevelInfoTag; + } + if (other.GameTimeInfoTag != 0) + { + GameTimeInfoTag = other.GameTimeInfoTag; + } + if (other.GameStatusTag != 0) + { + GameStatusTag = other.GameStatusTag; + } + if (other.RafInfoTag != 0) + { + RafInfoTag = other.RafInfoTag; + } + } + + public void MergeFrom(pb::CodedInputStream input) + { + uint tag; + while ((tag = input.ReadTag()) != 0) + { + switch (tag) + { + default: + input.SkipLastField(); + break; + case 21: + { + GameLevelInfoTag = input.ReadFixed32(); + break; + } + case 29: + { + GameTimeInfoTag = input.ReadFixed32(); + break; + } + case 37: + { + GameStatusTag = input.ReadFixed32(); + break; + } + case 45: + { + RafInfoTag = input.ReadFixed32(); + break; + } + } + } + } + + } + + [System.Diagnostics.DebuggerNonUserCodeAttribute()] + public sealed partial class AccountFieldOptions : pb::IMessage + { + private static readonly pb::MessageParser _parser = new pb::MessageParser(() => new AccountFieldOptions()); + public static pb::MessageParser Parser { get { return _parser; } } + + public static pbr::MessageDescriptor Descriptor + { + get { return Bgs.Protocol.Account.V1.AccountTypesReflection.Descriptor.MessageTypes[15]; } + } + + pbr::MessageDescriptor pb::IMessage.Descriptor + { + get { return Descriptor; } + } + + public AccountFieldOptions() + { + OnConstruction(); + } + + partial void OnConstruction(); + + public AccountFieldOptions(AccountFieldOptions other) : this() + { + allFields_ = other.allFields_; + fieldAccountLevelInfo_ = other.fieldAccountLevelInfo_; + fieldPrivacyInfo_ = other.fieldPrivacyInfo_; + fieldParentalControlInfo_ = other.fieldParentalControlInfo_; + fieldGameLevelInfo_ = other.fieldGameLevelInfo_; + fieldGameStatus_ = other.fieldGameStatus_; + fieldGameAccounts_ = other.fieldGameAccounts_; + } + + public AccountFieldOptions Clone() + { + return new AccountFieldOptions(this); + } + + /// Field number for the "all_fields" field. + public const int AllFieldsFieldNumber = 1; + private bool allFields_; + public bool AllFields + { + get { return allFields_; } + set + { + allFields_ = value; + } + } + + /// Field number for the "field_account_level_info" field. + public const int FieldAccountLevelInfoFieldNumber = 2; + private bool fieldAccountLevelInfo_; + public bool FieldAccountLevelInfo + { + get { return fieldAccountLevelInfo_; } + set + { + fieldAccountLevelInfo_ = value; + } + } + + /// Field number for the "field_privacy_info" field. + public const int FieldPrivacyInfoFieldNumber = 3; + private bool fieldPrivacyInfo_; + public bool FieldPrivacyInfo + { + get { return fieldPrivacyInfo_; } + set + { + fieldPrivacyInfo_ = value; + } + } + + /// Field number for the "field_parental_control_info" field. + public const int FieldParentalControlInfoFieldNumber = 4; + private bool fieldParentalControlInfo_; + public bool FieldParentalControlInfo + { + get { return fieldParentalControlInfo_; } + set + { + fieldParentalControlInfo_ = value; + } + } + + /// Field number for the "field_game_level_info" field. + public const int FieldGameLevelInfoFieldNumber = 6; + private bool fieldGameLevelInfo_; + public bool FieldGameLevelInfo + { + get { return fieldGameLevelInfo_; } + set + { + fieldGameLevelInfo_ = value; + } + } + + /// Field number for the "field_game_status" field. + public const int FieldGameStatusFieldNumber = 7; + private bool fieldGameStatus_; + public bool FieldGameStatus + { + get { return fieldGameStatus_; } + set + { + fieldGameStatus_ = value; + } + } + + /// Field number for the "field_game_accounts" field. + public const int FieldGameAccountsFieldNumber = 8; + private bool fieldGameAccounts_; + public bool FieldGameAccounts + { + get { return fieldGameAccounts_; } + set + { + fieldGameAccounts_ = value; + } + } + + public override bool Equals(object other) + { + return Equals(other as AccountFieldOptions); + } + + public bool Equals(AccountFieldOptions other) + { + if (ReferenceEquals(other, null)) + { + return false; + } + if (ReferenceEquals(other, this)) + { + return true; + } + if (AllFields != other.AllFields) return false; + if (FieldAccountLevelInfo != other.FieldAccountLevelInfo) return false; + if (FieldPrivacyInfo != other.FieldPrivacyInfo) return false; + if (FieldParentalControlInfo != other.FieldParentalControlInfo) return false; + if (FieldGameLevelInfo != other.FieldGameLevelInfo) return false; + if (FieldGameStatus != other.FieldGameStatus) return false; + if (FieldGameAccounts != other.FieldGameAccounts) return false; + return true; + } + + public override int GetHashCode() + { + int hash = 1; + if (AllFields != false) hash ^= AllFields.GetHashCode(); + if (FieldAccountLevelInfo != false) hash ^= FieldAccountLevelInfo.GetHashCode(); + if (FieldPrivacyInfo != false) hash ^= FieldPrivacyInfo.GetHashCode(); + if (FieldParentalControlInfo != false) hash ^= FieldParentalControlInfo.GetHashCode(); + if (FieldGameLevelInfo != false) hash ^= FieldGameLevelInfo.GetHashCode(); + if (FieldGameStatus != false) hash ^= FieldGameStatus.GetHashCode(); + if (FieldGameAccounts != false) hash ^= FieldGameAccounts.GetHashCode(); + return hash; + } + + public override string ToString() + { + return pb::JsonFormatter.ToDiagnosticString(this); + } + + public void WriteTo(pb::CodedOutputStream output) + { + if (AllFields != false) + { + output.WriteRawTag(8); + output.WriteBool(AllFields); + } + if (FieldAccountLevelInfo != false) + { + output.WriteRawTag(16); + output.WriteBool(FieldAccountLevelInfo); + } + if (FieldPrivacyInfo != false) + { + output.WriteRawTag(24); + output.WriteBool(FieldPrivacyInfo); + } + if (FieldParentalControlInfo != false) + { + output.WriteRawTag(32); + output.WriteBool(FieldParentalControlInfo); + } + if (FieldGameLevelInfo != false) + { + output.WriteRawTag(48); + output.WriteBool(FieldGameLevelInfo); + } + if (FieldGameStatus != false) + { + output.WriteRawTag(56); + output.WriteBool(FieldGameStatus); + } + if (FieldGameAccounts != false) + { + output.WriteRawTag(64); + output.WriteBool(FieldGameAccounts); + } + } + + public int CalculateSize() + { + int size = 0; + if (AllFields != false) + { + size += 1 + 1; + } + if (FieldAccountLevelInfo != false) + { + size += 1 + 1; + } + if (FieldPrivacyInfo != false) + { + size += 1 + 1; + } + if (FieldParentalControlInfo != false) + { + size += 1 + 1; + } + if (FieldGameLevelInfo != false) + { + size += 1 + 1; + } + if (FieldGameStatus != false) + { + size += 1 + 1; + } + if (FieldGameAccounts != false) + { + size += 1 + 1; + } + return size; + } + + public void MergeFrom(AccountFieldOptions other) + { + if (other == null) + { + return; + } + if (other.AllFields != false) + { + AllFields = other.AllFields; + } + if (other.FieldAccountLevelInfo != false) + { + FieldAccountLevelInfo = other.FieldAccountLevelInfo; + } + if (other.FieldPrivacyInfo != false) + { + FieldPrivacyInfo = other.FieldPrivacyInfo; + } + if (other.FieldParentalControlInfo != false) + { + FieldParentalControlInfo = other.FieldParentalControlInfo; + } + if (other.FieldGameLevelInfo != false) + { + FieldGameLevelInfo = other.FieldGameLevelInfo; + } + if (other.FieldGameStatus != false) + { + FieldGameStatus = other.FieldGameStatus; + } + if (other.FieldGameAccounts != false) + { + FieldGameAccounts = other.FieldGameAccounts; + } + } + + public void MergeFrom(pb::CodedInputStream input) + { + uint tag; + while ((tag = input.ReadTag()) != 0) + { + switch (tag) + { + default: + input.SkipLastField(); + break; + case 8: + { + AllFields = input.ReadBool(); + break; + } + case 16: + { + FieldAccountLevelInfo = input.ReadBool(); + break; + } + case 24: + { + FieldPrivacyInfo = input.ReadBool(); + break; + } + case 32: + { + FieldParentalControlInfo = input.ReadBool(); + break; + } + case 48: + { + FieldGameLevelInfo = input.ReadBool(); + break; + } + case 56: + { + FieldGameStatus = input.ReadBool(); + break; + } + case 64: + { + FieldGameAccounts = input.ReadBool(); + break; + } + } + } + } + + } + + [System.Diagnostics.DebuggerNonUserCodeAttribute()] + public sealed partial class GameAccountFieldOptions : pb::IMessage + { + private static readonly pb::MessageParser _parser = new pb::MessageParser(() => new GameAccountFieldOptions()); + public static pb::MessageParser Parser { get { return _parser; } } + + public static pbr::MessageDescriptor Descriptor + { + get { return Bgs.Protocol.Account.V1.AccountTypesReflection.Descriptor.MessageTypes[16]; } + } + + pbr::MessageDescriptor pb::IMessage.Descriptor + { + get { return Descriptor; } + } + + public GameAccountFieldOptions() + { + OnConstruction(); + } + + partial void OnConstruction(); + + public GameAccountFieldOptions(GameAccountFieldOptions other) : this() + { + allFields_ = other.allFields_; + fieldGameLevelInfo_ = other.fieldGameLevelInfo_; + fieldGameTimeInfo_ = other.fieldGameTimeInfo_; + fieldGameStatus_ = other.fieldGameStatus_; + fieldRafInfo_ = other.fieldRafInfo_; + } + + public GameAccountFieldOptions Clone() + { + return new GameAccountFieldOptions(this); + } + + /// Field number for the "all_fields" field. + public const int AllFieldsFieldNumber = 1; + private bool allFields_; + public bool AllFields + { + get { return allFields_; } + set + { + allFields_ = value; + } + } + + /// Field number for the "field_game_level_info" field. + public const int FieldGameLevelInfoFieldNumber = 2; + private bool fieldGameLevelInfo_; + public bool FieldGameLevelInfo + { + get { return fieldGameLevelInfo_; } + set + { + fieldGameLevelInfo_ = value; + } + } + + /// Field number for the "field_game_time_info" field. + public const int FieldGameTimeInfoFieldNumber = 3; + private bool fieldGameTimeInfo_; + public bool FieldGameTimeInfo + { + get { return fieldGameTimeInfo_; } + set + { + fieldGameTimeInfo_ = value; + } + } + + /// Field number for the "field_game_status" field. + public const int FieldGameStatusFieldNumber = 4; + private bool fieldGameStatus_; + public bool FieldGameStatus + { + get { return fieldGameStatus_; } + set + { + fieldGameStatus_ = value; + } + } + + /// Field number for the "field_raf_info" field. + public const int FieldRafInfoFieldNumber = 5; + private bool fieldRafInfo_; + public bool FieldRafInfo + { + get { return fieldRafInfo_; } + set + { + fieldRafInfo_ = value; + } + } + + public override bool Equals(object other) + { + return Equals(other as GameAccountFieldOptions); + } + + public bool Equals(GameAccountFieldOptions other) + { + if (ReferenceEquals(other, null)) + { + return false; + } + if (ReferenceEquals(other, this)) + { + return true; + } + if (AllFields != other.AllFields) return false; + if (FieldGameLevelInfo != other.FieldGameLevelInfo) return false; + if (FieldGameTimeInfo != other.FieldGameTimeInfo) return false; + if (FieldGameStatus != other.FieldGameStatus) return false; + if (FieldRafInfo != other.FieldRafInfo) return false; + return true; + } + + public override int GetHashCode() + { + int hash = 1; + if (AllFields != false) hash ^= AllFields.GetHashCode(); + if (FieldGameLevelInfo != false) hash ^= FieldGameLevelInfo.GetHashCode(); + if (FieldGameTimeInfo != false) hash ^= FieldGameTimeInfo.GetHashCode(); + if (FieldGameStatus != false) hash ^= FieldGameStatus.GetHashCode(); + if (FieldRafInfo != false) hash ^= FieldRafInfo.GetHashCode(); + return hash; + } + + public override string ToString() + { + return pb::JsonFormatter.ToDiagnosticString(this); + } + + public void WriteTo(pb::CodedOutputStream output) + { + if (AllFields != false) + { + output.WriteRawTag(8); + output.WriteBool(AllFields); + } + if (FieldGameLevelInfo != false) + { + output.WriteRawTag(16); + output.WriteBool(FieldGameLevelInfo); + } + if (FieldGameTimeInfo != false) + { + output.WriteRawTag(24); + output.WriteBool(FieldGameTimeInfo); + } + if (FieldGameStatus != false) + { + output.WriteRawTag(32); + output.WriteBool(FieldGameStatus); + } + if (FieldRafInfo != false) + { + output.WriteRawTag(40); + output.WriteBool(FieldRafInfo); + } + } + + public int CalculateSize() + { + int size = 0; + if (AllFields != false) + { + size += 1 + 1; + } + if (FieldGameLevelInfo != false) + { + size += 1 + 1; + } + if (FieldGameTimeInfo != false) + { + size += 1 + 1; + } + if (FieldGameStatus != false) + { + size += 1 + 1; + } + if (FieldRafInfo != false) + { + size += 1 + 1; + } + return size; + } + + public void MergeFrom(GameAccountFieldOptions other) + { + if (other == null) + { + return; + } + if (other.AllFields != false) + { + AllFields = other.AllFields; + } + if (other.FieldGameLevelInfo != false) + { + FieldGameLevelInfo = other.FieldGameLevelInfo; + } + if (other.FieldGameTimeInfo != false) + { + FieldGameTimeInfo = other.FieldGameTimeInfo; + } + if (other.FieldGameStatus != false) + { + FieldGameStatus = other.FieldGameStatus; + } + if (other.FieldRafInfo != false) + { + FieldRafInfo = other.FieldRafInfo; + } + } + + public void MergeFrom(pb::CodedInputStream input) + { + uint tag; + while ((tag = input.ReadTag()) != 0) + { + switch (tag) + { + default: + input.SkipLastField(); + break; + case 8: + { + AllFields = input.ReadBool(); + break; + } + case 16: + { + FieldGameLevelInfo = input.ReadBool(); + break; + } + case 24: + { + FieldGameTimeInfo = input.ReadBool(); + break; + } + case 32: + { + FieldGameStatus = input.ReadBool(); + break; + } + case 40: + { + FieldRafInfo = input.ReadBool(); + break; + } + } + } + } + + } + + [System.Diagnostics.DebuggerNonUserCodeAttribute()] + public sealed partial class SubscriberReference : pb::IMessage + { + private static readonly pb::MessageParser _parser = new pb::MessageParser(() => new SubscriberReference()); + public static pb::MessageParser Parser { get { return _parser; } } + + public static pbr::MessageDescriptor Descriptor + { + get { return Bgs.Protocol.Account.V1.AccountTypesReflection.Descriptor.MessageTypes[17]; } + } + + pbr::MessageDescriptor pb::IMessage.Descriptor + { + get { return Descriptor; } + } + + public SubscriberReference() + { + OnConstruction(); + } + + partial void OnConstruction(); + + public SubscriberReference(SubscriberReference other) : this() + { + objectId_ = other.objectId_; + EntityId = other.entityId_ != null ? other.EntityId.Clone() : null; + AccountOptions = other.accountOptions_ != null ? other.AccountOptions.Clone() : null; + AccountTags = other.accountTags_ != null ? other.AccountTags.Clone() : null; + GameAccountOptions = other.gameAccountOptions_ != null ? other.GameAccountOptions.Clone() : null; + GameAccountTags = other.gameAccountTags_ != null ? other.GameAccountTags.Clone() : null; + subscriberId_ = other.subscriberId_; + } + + public SubscriberReference Clone() + { + return new SubscriberReference(this); + } + + /// Field number for the "object_id" field. + public const int ObjectIdFieldNumber = 1; + private ulong objectId_; + public ulong ObjectId + { + get { return objectId_; } + set + { + objectId_ = value; + } + } + + /// Field number for the "entity_id" field. + public const int EntityIdFieldNumber = 2; + private Bgs.Protocol.EntityId entityId_; + public Bgs.Protocol.EntityId EntityId + { + get { return entityId_; } + set + { + entityId_ = value; + } + } + + /// Field number for the "account_options" field. + public const int AccountOptionsFieldNumber = 3; + private Bgs.Protocol.Account.V1.AccountFieldOptions accountOptions_; + public Bgs.Protocol.Account.V1.AccountFieldOptions AccountOptions + { + get { return accountOptions_; } + set + { + accountOptions_ = value; + } + } + + /// Field number for the "account_tags" field. + public const int AccountTagsFieldNumber = 4; + private Bgs.Protocol.Account.V1.AccountFieldTags accountTags_; + public Bgs.Protocol.Account.V1.AccountFieldTags AccountTags + { + get { return accountTags_; } + set + { + accountTags_ = value; + } + } + + /// Field number for the "game_account_options" field. + public const int GameAccountOptionsFieldNumber = 5; + private Bgs.Protocol.Account.V1.GameAccountFieldOptions gameAccountOptions_; + public Bgs.Protocol.Account.V1.GameAccountFieldOptions GameAccountOptions + { + get { return gameAccountOptions_; } + set + { + gameAccountOptions_ = value; + } + } + + /// Field number for the "game_account_tags" field. + public const int GameAccountTagsFieldNumber = 6; + private Bgs.Protocol.Account.V1.GameAccountFieldTags gameAccountTags_; + public Bgs.Protocol.Account.V1.GameAccountFieldTags GameAccountTags + { + get { return gameAccountTags_; } + set + { + gameAccountTags_ = value; + } + } + + /// Field number for the "subscriber_id" field. + public const int SubscriberIdFieldNumber = 7; + private ulong subscriberId_; + public ulong SubscriberId + { + get { return subscriberId_; } + set + { + subscriberId_ = value; + } + } + + public override bool Equals(object other) + { + return Equals(other as SubscriberReference); + } + + public bool Equals(SubscriberReference other) + { + if (ReferenceEquals(other, null)) + { + return false; + } + if (ReferenceEquals(other, this)) + { + return true; + } + if (ObjectId != other.ObjectId) return false; + if (!object.Equals(EntityId, other.EntityId)) return false; + if (!object.Equals(AccountOptions, other.AccountOptions)) return false; + if (!object.Equals(AccountTags, other.AccountTags)) return false; + if (!object.Equals(GameAccountOptions, other.GameAccountOptions)) return false; + if (!object.Equals(GameAccountTags, other.GameAccountTags)) return false; + if (SubscriberId != other.SubscriberId) return false; + return true; + } + + public override int GetHashCode() + { + int hash = 1; + if (ObjectId != 0UL) hash ^= ObjectId.GetHashCode(); + if (entityId_ != null) hash ^= EntityId.GetHashCode(); + if (accountOptions_ != null) hash ^= AccountOptions.GetHashCode(); + if (accountTags_ != null) hash ^= AccountTags.GetHashCode(); + if (gameAccountOptions_ != null) hash ^= GameAccountOptions.GetHashCode(); + if (gameAccountTags_ != null) hash ^= GameAccountTags.GetHashCode(); + if (SubscriberId != 0UL) hash ^= SubscriberId.GetHashCode(); + return hash; + } + + public override string ToString() + { + return pb::JsonFormatter.ToDiagnosticString(this); + } + + public void WriteTo(pb::CodedOutputStream output) + { + if (ObjectId != 0UL) + { + output.WriteRawTag(8); + output.WriteUInt64(ObjectId); + } + if (entityId_ != null) + { + output.WriteRawTag(18); + output.WriteMessage(EntityId); + } + if (accountOptions_ != null) + { + output.WriteRawTag(26); + output.WriteMessage(AccountOptions); + } + if (accountTags_ != null) + { + output.WriteRawTag(34); + output.WriteMessage(AccountTags); + } + if (gameAccountOptions_ != null) + { + output.WriteRawTag(42); + output.WriteMessage(GameAccountOptions); + } + if (gameAccountTags_ != null) + { + output.WriteRawTag(50); + output.WriteMessage(GameAccountTags); + } + if (SubscriberId != 0UL) + { + output.WriteRawTag(56); + output.WriteUInt64(SubscriberId); + } + } + + public int CalculateSize() + { + int size = 0; + if (ObjectId != 0UL) + { + size += 1 + pb::CodedOutputStream.ComputeUInt64Size(ObjectId); + } + if (entityId_ != null) + { + size += 1 + pb::CodedOutputStream.ComputeMessageSize(EntityId); + } + if (accountOptions_ != null) + { + size += 1 + pb::CodedOutputStream.ComputeMessageSize(AccountOptions); + } + if (accountTags_ != null) + { + size += 1 + pb::CodedOutputStream.ComputeMessageSize(AccountTags); + } + if (gameAccountOptions_ != null) + { + size += 1 + pb::CodedOutputStream.ComputeMessageSize(GameAccountOptions); + } + if (gameAccountTags_ != null) + { + size += 1 + pb::CodedOutputStream.ComputeMessageSize(GameAccountTags); + } + if (SubscriberId != 0UL) + { + size += 1 + pb::CodedOutputStream.ComputeUInt64Size(SubscriberId); + } + return size; + } + + public void MergeFrom(SubscriberReference other) + { + if (other == null) + { + return; + } + if (other.ObjectId != 0UL) + { + ObjectId = other.ObjectId; + } + if (other.entityId_ != null) + { + if (entityId_ == null) + { + entityId_ = new Bgs.Protocol.EntityId(); + } + EntityId.MergeFrom(other.EntityId); + } + if (other.accountOptions_ != null) + { + if (accountOptions_ == null) + { + accountOptions_ = new Bgs.Protocol.Account.V1.AccountFieldOptions(); + } + AccountOptions.MergeFrom(other.AccountOptions); + } + if (other.accountTags_ != null) + { + if (accountTags_ == null) + { + accountTags_ = new Bgs.Protocol.Account.V1.AccountFieldTags(); + } + AccountTags.MergeFrom(other.AccountTags); + } + if (other.gameAccountOptions_ != null) + { + if (gameAccountOptions_ == null) + { + gameAccountOptions_ = new Bgs.Protocol.Account.V1.GameAccountFieldOptions(); + } + GameAccountOptions.MergeFrom(other.GameAccountOptions); + } + if (other.gameAccountTags_ != null) + { + if (gameAccountTags_ == null) + { + gameAccountTags_ = new Bgs.Protocol.Account.V1.GameAccountFieldTags(); + } + GameAccountTags.MergeFrom(other.GameAccountTags); + } + if (other.SubscriberId != 0UL) + { + SubscriberId = other.SubscriberId; + } + } + + public void MergeFrom(pb::CodedInputStream input) + { + uint tag; + while ((tag = input.ReadTag()) != 0) + { + switch (tag) + { + default: + input.SkipLastField(); + break; + case 8: + { + ObjectId = input.ReadUInt64(); + break; + } + case 18: + { + if (entityId_ == null) + { + entityId_ = new Bgs.Protocol.EntityId(); + } + input.ReadMessage(entityId_); + break; + } + case 26: + { + if (accountOptions_ == null) + { + accountOptions_ = new Bgs.Protocol.Account.V1.AccountFieldOptions(); + } + input.ReadMessage(accountOptions_); + break; + } + case 34: + { + if (accountTags_ == null) + { + accountTags_ = new Bgs.Protocol.Account.V1.AccountFieldTags(); + } + input.ReadMessage(accountTags_); + break; + } + case 42: + { + if (gameAccountOptions_ == null) + { + gameAccountOptions_ = new Bgs.Protocol.Account.V1.GameAccountFieldOptions(); + } + input.ReadMessage(gameAccountOptions_); + break; + } + case 50: + { + if (gameAccountTags_ == null) + { + gameAccountTags_ = new Bgs.Protocol.Account.V1.GameAccountFieldTags(); + } + input.ReadMessage(gameAccountTags_); + break; + } + case 56: + { + SubscriberId = input.ReadUInt64(); + break; + } + } + } + } + + } + + [System.Diagnostics.DebuggerNonUserCodeAttribute()] + public sealed partial class AccountLevelInfo : pb::IMessage + { + private static readonly pb::MessageParser _parser = new pb::MessageParser(() => new AccountLevelInfo()); + public static pb::MessageParser Parser { get { return _parser; } } + + public static pbr::MessageDescriptor Descriptor + { + get { return Bgs.Protocol.Account.V1.AccountTypesReflection.Descriptor.MessageTypes[18]; } + } + + pbr::MessageDescriptor pb::IMessage.Descriptor + { + get { return Descriptor; } + } + + public AccountLevelInfo() + { + OnConstruction(); + } + + partial void OnConstruction(); + + public AccountLevelInfo(AccountLevelInfo other) : this() + { + licenses_ = other.licenses_.Clone(); + defaultCurrency_ = other.defaultCurrency_; + country_ = other.country_; + preferredRegion_ = other.preferredRegion_; + fullName_ = other.fullName_; + battleTag_ = other.battleTag_; + muted_ = other.muted_; + manualReview_ = other.manualReview_; + accountPaidAny_ = other.accountPaidAny_; + identityCheckStatus_ = other.identityCheckStatus_; + email_ = other.email_; + } + + public AccountLevelInfo Clone() + { + return new AccountLevelInfo(this); + } + + /// Field number for the "licenses" field. + public const int LicensesFieldNumber = 3; + private static readonly pb::FieldCodec _repeated_licenses_codec + = pb::FieldCodec.ForMessage(26, Bgs.Protocol.Account.V1.AccountLicense.Parser); + private readonly pbc::RepeatedField licenses_ = new pbc::RepeatedField(); + public pbc::RepeatedField Licenses + { + get { return licenses_; } + } + + /// Field number for the "default_currency" field. + public const int DefaultCurrencyFieldNumber = 4; + private uint defaultCurrency_; + public uint DefaultCurrency + { + get { return defaultCurrency_; } + set + { + defaultCurrency_ = value; + } + } + + /// Field number for the "country" field. + public const int CountryFieldNumber = 5; + private string country_ = ""; + public string Country + { + get { return country_; } + set + { + country_ = pb::ProtoPreconditions.CheckNotNull(value, "value"); + } + } + + /// Field number for the "preferred_region" field. + public const int PreferredRegionFieldNumber = 6; + private uint preferredRegion_; + public uint PreferredRegion + { + get { return preferredRegion_; } + set + { + preferredRegion_ = value; + } + } + + /// Field number for the "full_name" field. + public const int FullNameFieldNumber = 7; + private string fullName_ = ""; + public string FullName + { + get { return fullName_; } + set + { + fullName_ = pb::ProtoPreconditions.CheckNotNull(value, "value"); + } + } + + /// Field number for the "battle_tag" field. + public const int BattleTagFieldNumber = 8; + private string battleTag_ = ""; + public string BattleTag + { + get { return battleTag_; } + set + { + battleTag_ = pb::ProtoPreconditions.CheckNotNull(value, "value"); + } + } + + /// Field number for the "muted" field. + public const int MutedFieldNumber = 9; + private bool muted_; + public bool Muted + { + get { return muted_; } + set + { + muted_ = value; + } + } + + /// Field number for the "manual_review" field. + public const int ManualReviewFieldNumber = 10; + private bool manualReview_; + public bool ManualReview + { + get { return manualReview_; } + set + { + manualReview_ = value; + } + } + + /// Field number for the "account_paid_any" field. + public const int AccountPaidAnyFieldNumber = 11; + private bool accountPaidAny_; + public bool AccountPaidAny + { + get { return accountPaidAny_; } + set + { + accountPaidAny_ = value; + } + } + + /// Field number for the "identity_check_status" field. + public const int IdentityCheckStatusFieldNumber = 12; + private Bgs.Protocol.Account.V1.IdentityVerificationStatus identityCheckStatus_ = Bgs.Protocol.Account.V1.IdentityVerificationStatus.IDENT_NO_DATA; + public Bgs.Protocol.Account.V1.IdentityVerificationStatus IdentityCheckStatus + { + get { return identityCheckStatus_; } + set + { + identityCheckStatus_ = value; + } + } + + /// Field number for the "email" field. + public const int EmailFieldNumber = 13; + private string email_ = ""; + public string Email + { + get { return email_; } + set + { + email_ = pb::ProtoPreconditions.CheckNotNull(value, "value"); + } + } + + public override bool Equals(object other) + { + return Equals(other as AccountLevelInfo); + } + + public bool Equals(AccountLevelInfo other) + { + if (ReferenceEquals(other, null)) + { + return false; + } + if (ReferenceEquals(other, this)) + { + return true; + } + if (!licenses_.Equals(other.licenses_)) return false; + if (DefaultCurrency != other.DefaultCurrency) return false; + if (Country != other.Country) return false; + if (PreferredRegion != other.PreferredRegion) return false; + if (FullName != other.FullName) return false; + if (BattleTag != other.BattleTag) return false; + if (Muted != other.Muted) return false; + if (ManualReview != other.ManualReview) return false; + if (AccountPaidAny != other.AccountPaidAny) return false; + if (IdentityCheckStatus != other.IdentityCheckStatus) return false; + if (Email != other.Email) return false; + return true; + } + + public override int GetHashCode() + { + int hash = 1; + hash ^= licenses_.GetHashCode(); + if (DefaultCurrency != 0) hash ^= DefaultCurrency.GetHashCode(); + if (Country.Length != 0) hash ^= Country.GetHashCode(); + if (PreferredRegion != 0) hash ^= PreferredRegion.GetHashCode(); + if (FullName.Length != 0) hash ^= FullName.GetHashCode(); + if (BattleTag.Length != 0) hash ^= BattleTag.GetHashCode(); + if (Muted != false) hash ^= Muted.GetHashCode(); + if (ManualReview != false) hash ^= ManualReview.GetHashCode(); + if (AccountPaidAny != false) hash ^= AccountPaidAny.GetHashCode(); + if (IdentityCheckStatus != Bgs.Protocol.Account.V1.IdentityVerificationStatus.IDENT_NO_DATA) hash ^= IdentityCheckStatus.GetHashCode(); + if (Email.Length != 0) hash ^= Email.GetHashCode(); + return hash; + } + + public override string ToString() + { + return pb::JsonFormatter.ToDiagnosticString(this); + } + + public void WriteTo(pb::CodedOutputStream output) + { + licenses_.WriteTo(output, _repeated_licenses_codec); + if (DefaultCurrency != 0) + { + output.WriteRawTag(37); + output.WriteFixed32(DefaultCurrency); + } + if (Country.Length != 0) + { + output.WriteRawTag(42); + output.WriteString(Country); + } + if (PreferredRegion != 0) + { + output.WriteRawTag(48); + output.WriteUInt32(PreferredRegion); + } + if (FullName.Length != 0) + { + output.WriteRawTag(58); + output.WriteString(FullName); + } + if (BattleTag.Length != 0) + { + output.WriteRawTag(66); + output.WriteString(BattleTag); + } + if (Muted != false) + { + output.WriteRawTag(72); + output.WriteBool(Muted); + } + if (ManualReview != false) + { + output.WriteRawTag(80); + output.WriteBool(ManualReview); + } + if (AccountPaidAny != false) + { + output.WriteRawTag(88); + output.WriteBool(AccountPaidAny); + } + if (IdentityCheckStatus != Bgs.Protocol.Account.V1.IdentityVerificationStatus.IDENT_NO_DATA) + { + output.WriteRawTag(96); + output.WriteEnum((int)IdentityCheckStatus); + } + if (Email.Length != 0) + { + output.WriteRawTag(106); + output.WriteString(Email); + } + } + + public int CalculateSize() + { + int size = 0; + size += licenses_.CalculateSize(_repeated_licenses_codec); + if (DefaultCurrency != 0) + { + size += 1 + 4; + } + if (Country.Length != 0) + { + size += 1 + pb::CodedOutputStream.ComputeStringSize(Country); + } + if (PreferredRegion != 0) + { + size += 1 + pb::CodedOutputStream.ComputeUInt32Size(PreferredRegion); + } + if (FullName.Length != 0) + { + size += 1 + pb::CodedOutputStream.ComputeStringSize(FullName); + } + if (BattleTag.Length != 0) + { + size += 1 + pb::CodedOutputStream.ComputeStringSize(BattleTag); + } + if (Muted != false) + { + size += 1 + 1; + } + if (ManualReview != false) + { + size += 1 + 1; + } + if (AccountPaidAny != false) + { + size += 1 + 1; + } + if (IdentityCheckStatus != Bgs.Protocol.Account.V1.IdentityVerificationStatus.IDENT_NO_DATA) + { + size += 1 + pb::CodedOutputStream.ComputeEnumSize((int)IdentityCheckStatus); + } + if (Email.Length != 0) + { + size += 1 + pb::CodedOutputStream.ComputeStringSize(Email); + } + return size; + } + + public void MergeFrom(AccountLevelInfo other) + { + if (other == null) + { + return; + } + licenses_.Add(other.licenses_); + if (other.DefaultCurrency != 0) + { + DefaultCurrency = other.DefaultCurrency; + } + if (other.Country.Length != 0) + { + Country = other.Country; + } + if (other.PreferredRegion != 0) + { + PreferredRegion = other.PreferredRegion; + } + if (other.FullName.Length != 0) + { + FullName = other.FullName; + } + if (other.BattleTag.Length != 0) + { + BattleTag = other.BattleTag; + } + if (other.Muted != false) + { + Muted = other.Muted; + } + if (other.ManualReview != false) + { + ManualReview = other.ManualReview; + } + if (other.AccountPaidAny != false) + { + AccountPaidAny = other.AccountPaidAny; + } + if (other.IdentityCheckStatus != Bgs.Protocol.Account.V1.IdentityVerificationStatus.IDENT_NO_DATA) + { + IdentityCheckStatus = other.IdentityCheckStatus; + } + if (other.Email.Length != 0) + { + Email = other.Email; + } + } + + public void MergeFrom(pb::CodedInputStream input) + { + uint tag; + while ((tag = input.ReadTag()) != 0) + { + switch (tag) + { + default: + input.SkipLastField(); + break; + case 26: + { + licenses_.AddEntriesFrom(input, _repeated_licenses_codec); + break; + } + case 37: + { + DefaultCurrency = input.ReadFixed32(); + break; + } + case 42: + { + Country = input.ReadString(); + break; + } + case 48: + { + PreferredRegion = input.ReadUInt32(); + break; + } + case 58: + { + FullName = input.ReadString(); + break; + } + case 66: + { + BattleTag = input.ReadString(); + break; + } + case 72: + { + Muted = input.ReadBool(); + break; + } + case 80: + { + ManualReview = input.ReadBool(); + break; + } + case 88: + { + AccountPaidAny = input.ReadBool(); + break; + } + case 96: + { + identityCheckStatus_ = (Bgs.Protocol.Account.V1.IdentityVerificationStatus)input.ReadEnum(); + break; + } + case 106: + { + Email = input.ReadString(); + break; + } + } + } + } + + } + + [System.Diagnostics.DebuggerNonUserCodeAttribute()] + public sealed partial class PrivacyInfo : pb::IMessage + { + private static readonly pb::MessageParser _parser = new pb::MessageParser(() => new PrivacyInfo()); + public static pb::MessageParser Parser { get { return _parser; } } + + public static pbr::MessageDescriptor Descriptor + { + get { return Bgs.Protocol.Account.V1.AccountTypesReflection.Descriptor.MessageTypes[19]; } + } + + pbr::MessageDescriptor pb::IMessage.Descriptor + { + get { return Descriptor; } + } + + public PrivacyInfo() + { + OnConstruction(); + } + + partial void OnConstruction(); + + public PrivacyInfo(PrivacyInfo other) : this() + { + isUsingRid_ = other.isUsingRid_; + isRealIdVisibleForViewFriends_ = other.isRealIdVisibleForViewFriends_; + isHiddenFromFriendFinder_ = other.isHiddenFromFriendFinder_; + gameInfoPrivacy_ = other.gameInfoPrivacy_; + } + + public PrivacyInfo Clone() + { + return new PrivacyInfo(this); + } + + /// Field number for the "is_using_rid" field. + public const int IsUsingRidFieldNumber = 3; + private bool isUsingRid_; + public bool IsUsingRid + { + get { return isUsingRid_; } + set + { + bitArray.Set(IsUsingRidFieldNumber, true); + isUsingRid_ = value; + } + } + + /// Field number for the "is_real_id_visible_for_view_friends" field. + public const int IsRealIdVisibleForViewFriendsFieldNumber = 4; + private bool isRealIdVisibleForViewFriends_; + public bool IsRealIdVisibleForViewFriends + { + get { return isRealIdVisibleForViewFriends_; } + set + { + bitArray.Set(IsRealIdVisibleForViewFriendsFieldNumber, true); + isRealIdVisibleForViewFriends_ = value; + } + } + + /// Field number for the "is_hidden_from_friend_finder" field. + public const int IsHiddenFromFriendFinderFieldNumber = 5; + private bool isHiddenFromFriendFinder_; + public bool IsHiddenFromFriendFinder + { + get { return isHiddenFromFriendFinder_; } + set + { + bitArray.Set(IsHiddenFromFriendFinderFieldNumber, true); + isHiddenFromFriendFinder_ = value; + } + } + + /// Field number for the "game_info_privacy" field. + public const int GameInfoPrivacyFieldNumber = 6; + private Bgs.Protocol.Account.V1.PrivacyInfo.Types.GameInfoPrivacy gameInfoPrivacy_ = Bgs.Protocol.Account.V1.PrivacyInfo.Types.GameInfoPrivacy.PRIVACY_ME; + public Bgs.Protocol.Account.V1.PrivacyInfo.Types.GameInfoPrivacy GameInfoPrivacy + { + get { return gameInfoPrivacy_; } + set + { + bitArray.Set(GameInfoPrivacyFieldNumber, true); + gameInfoPrivacy_ = value; + } + } + + public override bool Equals(object other) + { + return Equals(other as PrivacyInfo); + } + + public bool Equals(PrivacyInfo other) + { + if (ReferenceEquals(other, null)) + { + return false; + } + if (ReferenceEquals(other, this)) + { + return true; + } + if (IsUsingRid != other.IsUsingRid) return false; + if (IsRealIdVisibleForViewFriends != other.IsRealIdVisibleForViewFriends) return false; + if (IsHiddenFromFriendFinder != other.IsHiddenFromFriendFinder) return false; + if (GameInfoPrivacy != other.GameInfoPrivacy) return false; + return true; + } + + public override int GetHashCode() + { + int hash = 1; + if (IsUsingRid != false) hash ^= IsUsingRid.GetHashCode(); + if (IsRealIdVisibleForViewFriends != false) hash ^= IsRealIdVisibleForViewFriends.GetHashCode(); + if (IsHiddenFromFriendFinder != false) hash ^= IsHiddenFromFriendFinder.GetHashCode(); + if (GameInfoPrivacy != Bgs.Protocol.Account.V1.PrivacyInfo.Types.GameInfoPrivacy.PRIVACY_ME) hash ^= GameInfoPrivacy.GetHashCode(); + return hash; + } + + public override string ToString() + { + return pb::JsonFormatter.ToDiagnosticString(this); + } + + public void WriteTo(pb::CodedOutputStream output) + { + if (bitArray.Get(IsUsingRidFieldNumber)) + { + output.WriteRawTag(24); + output.WriteBool(IsUsingRid); + } + if (bitArray.Get(IsRealIdVisibleForViewFriendsFieldNumber)) + { + output.WriteRawTag(32); + output.WriteBool(IsRealIdVisibleForViewFriends); + } + if (bitArray.Get(IsHiddenFromFriendFinderFieldNumber)) + { + output.WriteRawTag(40); + output.WriteBool(IsHiddenFromFriendFinder); + } + if (bitArray.Get(GameInfoPrivacyFieldNumber)) + { + output.WriteRawTag(48); + output.WriteEnum((int)GameInfoPrivacy); + } + } + + public int CalculateSize() + { + int size = 0; + if (bitArray.Get(IsUsingRidFieldNumber)) + { + size += 1 + 1; + } + if (bitArray.Get(IsRealIdVisibleForViewFriendsFieldNumber)) + { + size += 1 + 1; + } + if (bitArray.Get(IsHiddenFromFriendFinderFieldNumber)) + { + size += 1 + 1; + } + if (bitArray.Get(GameInfoPrivacyFieldNumber)) + { + size += 1 + pb::CodedOutputStream.ComputeEnumSize((int)GameInfoPrivacy); + } + return size; + } + + public void MergeFrom(PrivacyInfo other) + { + if (other == null) + { + return; + } + if (other.IsUsingRid != false) + { + IsUsingRid = other.IsUsingRid; + } + if (other.IsRealIdVisibleForViewFriends != false) + { + IsRealIdVisibleForViewFriends = other.IsRealIdVisibleForViewFriends; + } + if (other.IsHiddenFromFriendFinder != false) + { + IsHiddenFromFriendFinder = other.IsHiddenFromFriendFinder; + } + if (other.GameInfoPrivacy != Bgs.Protocol.Account.V1.PrivacyInfo.Types.GameInfoPrivacy.PRIVACY_ME) + { + GameInfoPrivacy = other.GameInfoPrivacy; + } + } + + public void MergeFrom(pb::CodedInputStream input) + { + uint tag; + while ((tag = input.ReadTag()) != 0) + { + switch (tag) + { + default: + input.SkipLastField(); + break; + case 24: + { + IsUsingRid = input.ReadBool(); + break; + } + case 32: + { + IsRealIdVisibleForViewFriends = input.ReadBool(); + break; + } + case 40: + { + IsHiddenFromFriendFinder = input.ReadBool(); + break; + } + case 48: + { + gameInfoPrivacy_ = (Bgs.Protocol.Account.V1.PrivacyInfo.Types.GameInfoPrivacy)input.ReadEnum(); + break; + } + } + } + } + + #region Nested types + /// Container for nested types declared in the PrivacyInfo message type. + [System.Diagnostics.DebuggerNonUserCodeAttribute()] + public static partial class Types + { + public enum GameInfoPrivacy + { + PRIVACY_ME = 0, + PRIVACY_FRIENDS = 1, + PRIVACY_EVERYONE = 2, + } + + } + #endregion + + System.Collections.BitArray bitArray = new System.Collections.BitArray(7); + } + + [System.Diagnostics.DebuggerNonUserCodeAttribute()] + public sealed partial class ParentalControlInfo : pb::IMessage + { + private static readonly pb::MessageParser _parser = new pb::MessageParser(() => new ParentalControlInfo()); + public static pb::MessageParser Parser { get { return _parser; } } + + public static pbr::MessageDescriptor Descriptor + { + get { return Bgs.Protocol.Account.V1.AccountTypesReflection.Descriptor.MessageTypes[20]; } + } + + pbr::MessageDescriptor pb::IMessage.Descriptor + { + get { return Descriptor; } + } + + public ParentalControlInfo() + { + OnConstruction(); + } + + partial void OnConstruction(); + + public ParentalControlInfo(ParentalControlInfo other) : this() + { + timezone_ = other.timezone_; + minutesPerDay_ = other.minutesPerDay_; + minutesPerWeek_ = other.minutesPerWeek_; + canReceiveVoice_ = other.canReceiveVoice_; + canSendVoice_ = other.canSendVoice_; + playSchedule_ = other.playSchedule_.Clone(); + } + + public ParentalControlInfo Clone() + { + return new ParentalControlInfo(this); + } + + /// Field number for the "timezone" field. + public const int TimezoneFieldNumber = 3; + private string timezone_ = ""; + public string Timezone + { + get { return timezone_; } + set + { + timezone_ = pb::ProtoPreconditions.CheckNotNull(value, "value"); + } + } + + /// Field number for the "minutes_per_day" field. + public const int MinutesPerDayFieldNumber = 4; + private uint minutesPerDay_; + public uint MinutesPerDay + { + get { return minutesPerDay_; } + set + { + minutesPerDay_ = value; + } + } + + /// Field number for the "minutes_per_week" field. + public const int MinutesPerWeekFieldNumber = 5; + private uint minutesPerWeek_; + public uint MinutesPerWeek + { + get { return minutesPerWeek_; } + set + { + minutesPerWeek_ = value; + } + } + + /// Field number for the "can_receive_voice" field. + public const int CanReceiveVoiceFieldNumber = 6; + private bool canReceiveVoice_; + public bool CanReceiveVoice + { + get { return canReceiveVoice_; } + set + { + canReceiveVoice_ = value; + } + } + + /// Field number for the "can_send_voice" field. + public const int CanSendVoiceFieldNumber = 7; + private bool canSendVoice_; + public bool CanSendVoice + { + get { return canSendVoice_; } + set + { + canSendVoice_ = value; + } + } + + /// Field number for the "play_schedule" field. + public const int PlayScheduleFieldNumber = 8; + private static readonly pb::FieldCodec _repeated_playSchedule_codec + = pb::FieldCodec.ForBool(66); + private readonly pbc::RepeatedField playSchedule_ = new pbc::RepeatedField(); + public pbc::RepeatedField PlaySchedule + { + get { return playSchedule_; } + } + + public override bool Equals(object other) + { + return Equals(other as ParentalControlInfo); + } + + public bool Equals(ParentalControlInfo other) + { + if (ReferenceEquals(other, null)) + { + return false; + } + if (ReferenceEquals(other, this)) + { + return true; + } + if (Timezone != other.Timezone) return false; + if (MinutesPerDay != other.MinutesPerDay) return false; + if (MinutesPerWeek != other.MinutesPerWeek) return false; + if (CanReceiveVoice != other.CanReceiveVoice) return false; + if (CanSendVoice != other.CanSendVoice) return false; + if (!playSchedule_.Equals(other.playSchedule_)) return false; + return true; + } + + public override int GetHashCode() + { + int hash = 1; + if (Timezone.Length != 0) hash ^= Timezone.GetHashCode(); + if (MinutesPerDay != 0) hash ^= MinutesPerDay.GetHashCode(); + if (MinutesPerWeek != 0) hash ^= MinutesPerWeek.GetHashCode(); + if (CanReceiveVoice != false) hash ^= CanReceiveVoice.GetHashCode(); + if (CanSendVoice != false) hash ^= CanSendVoice.GetHashCode(); + hash ^= playSchedule_.GetHashCode(); + return hash; + } + + public override string ToString() + { + return pb::JsonFormatter.ToDiagnosticString(this); + } + + public void WriteTo(pb::CodedOutputStream output) + { + if (Timezone.Length != 0) + { + output.WriteRawTag(26); + output.WriteString(Timezone); + } + if (MinutesPerDay != 0) + { + output.WriteRawTag(32); + output.WriteUInt32(MinutesPerDay); + } + if (MinutesPerWeek != 0) + { + output.WriteRawTag(40); + output.WriteUInt32(MinutesPerWeek); + } + if (CanReceiveVoice != false) + { + output.WriteRawTag(48); + output.WriteBool(CanReceiveVoice); + } + if (CanSendVoice != false) + { + output.WriteRawTag(56); + output.WriteBool(CanSendVoice); + } + playSchedule_.WriteTo(output, _repeated_playSchedule_codec); + } + + public int CalculateSize() + { + int size = 0; + if (Timezone.Length != 0) + { + size += 1 + pb::CodedOutputStream.ComputeStringSize(Timezone); + } + if (MinutesPerDay != 0) + { + size += 1 + pb::CodedOutputStream.ComputeUInt32Size(MinutesPerDay); + } + if (MinutesPerWeek != 0) + { + size += 1 + pb::CodedOutputStream.ComputeUInt32Size(MinutesPerWeek); + } + if (CanReceiveVoice != false) + { + size += 1 + 1; + } + if (CanSendVoice != false) + { + size += 1 + 1; + } + size += playSchedule_.CalculateSize(_repeated_playSchedule_codec); + return size; + } + + public void MergeFrom(ParentalControlInfo other) + { + if (other == null) + { + return; + } + if (other.Timezone.Length != 0) + { + Timezone = other.Timezone; + } + if (other.MinutesPerDay != 0) + { + MinutesPerDay = other.MinutesPerDay; + } + if (other.MinutesPerWeek != 0) + { + MinutesPerWeek = other.MinutesPerWeek; + } + if (other.CanReceiveVoice != false) + { + CanReceiveVoice = other.CanReceiveVoice; + } + if (other.CanSendVoice != false) + { + CanSendVoice = other.CanSendVoice; + } + playSchedule_.Add(other.playSchedule_); + } + + public void MergeFrom(pb::CodedInputStream input) + { + uint tag; + while ((tag = input.ReadTag()) != 0) + { + switch (tag) + { + default: + input.SkipLastField(); + break; + case 26: + { + Timezone = input.ReadString(); + break; + } + case 32: + { + MinutesPerDay = input.ReadUInt32(); + break; + } + case 40: + { + MinutesPerWeek = input.ReadUInt32(); + break; + } + case 48: + { + CanReceiveVoice = input.ReadBool(); + break; + } + case 56: + { + CanSendVoice = input.ReadBool(); + break; + } + case 66: + case 64: + { + playSchedule_.AddEntriesFrom(input, _repeated_playSchedule_codec); + break; + } + } + } + } + + } + + [System.Diagnostics.DebuggerNonUserCodeAttribute()] + public sealed partial class GameLevelInfo : pb::IMessage + { + private static readonly pb::MessageParser _parser = new pb::MessageParser(() => new GameLevelInfo()); + public static pb::MessageParser Parser { get { return _parser; } } + + public static pbr::MessageDescriptor Descriptor + { + get { return Bgs.Protocol.Account.V1.AccountTypesReflection.Descriptor.MessageTypes[21]; } + } + + pbr::MessageDescriptor pb::IMessage.Descriptor + { + get { return Descriptor; } + } + + public GameLevelInfo() + { + OnConstruction(); + } + + partial void OnConstruction(); + + public GameLevelInfo(GameLevelInfo other) : this() + { + isTrial_ = other.isTrial_; + isLifetime_ = other.isLifetime_; + isRestricted_ = other.isRestricted_; + isBeta_ = other.isBeta_; + name_ = other.name_; + program_ = other.program_; + licenses_ = other.licenses_.Clone(); + realmPermissions_ = other.realmPermissions_; + } + + public GameLevelInfo Clone() + { + return new GameLevelInfo(this); + } + + /// Field number for the "is_trial" field. + public const int IsTrialFieldNumber = 4; + private bool isTrial_; + public bool IsTrial + { + get { return isTrial_; } + set + { + isTrial_ = value; + } + } + + /// Field number for the "is_lifetime" field. + public const int IsLifetimeFieldNumber = 5; + private bool isLifetime_; + public bool IsLifetime + { + get { return isLifetime_; } + set + { + isLifetime_ = value; + } + } + + /// Field number for the "is_restricted" field. + public const int IsRestrictedFieldNumber = 6; + private bool isRestricted_; + public bool IsRestricted + { + get { return isRestricted_; } + set + { + isRestricted_ = value; + } + } + + /// Field number for the "is_beta" field. + public const int IsBetaFieldNumber = 7; + private bool isBeta_; + public bool IsBeta + { + get { return isBeta_; } + set + { + isBeta_ = value; + } + } + + /// Field number for the "name" field. + public const int NameFieldNumber = 8; + private string name_ = ""; + public string Name + { + get { return name_; } + set + { + name_ = pb::ProtoPreconditions.CheckNotNull(value, "value"); + } + } + + /// Field number for the "program" field. + public const int ProgramFieldNumber = 9; + private uint program_; + public uint Program + { + get { return program_; } + set + { + program_ = value; + } + } + + /// Field number for the "licenses" field. + public const int LicensesFieldNumber = 10; + private static readonly pb::FieldCodec _repeated_licenses_codec + = pb::FieldCodec.ForMessage(82, Bgs.Protocol.Account.V1.AccountLicense.Parser); + private readonly pbc::RepeatedField licenses_ = new pbc::RepeatedField(); + public pbc::RepeatedField Licenses + { + get { return licenses_; } + } + + /// Field number for the "realm_permissions" field. + public const int RealmPermissionsFieldNumber = 11; + private uint realmPermissions_; + public uint RealmPermissions + { + get { return realmPermissions_; } + set + { + realmPermissions_ = value; + } + } + + public override bool Equals(object other) + { + return Equals(other as GameLevelInfo); + } + + public bool Equals(GameLevelInfo other) + { + if (ReferenceEquals(other, null)) + { + return false; + } + if (ReferenceEquals(other, this)) + { + return true; + } + if (IsTrial != other.IsTrial) return false; + if (IsLifetime != other.IsLifetime) return false; + if (IsRestricted != other.IsRestricted) return false; + if (IsBeta != other.IsBeta) return false; + if (Name != other.Name) return false; + if (Program != other.Program) return false; + if (!licenses_.Equals(other.licenses_)) return false; + if (RealmPermissions != other.RealmPermissions) return false; + return true; + } + + public override int GetHashCode() + { + int hash = 1; + if (IsTrial != false) hash ^= IsTrial.GetHashCode(); + if (IsLifetime != false) hash ^= IsLifetime.GetHashCode(); + if (IsRestricted != false) hash ^= IsRestricted.GetHashCode(); + if (IsBeta != false) hash ^= IsBeta.GetHashCode(); + if (Name.Length != 0) hash ^= Name.GetHashCode(); + if (Program != 0) hash ^= Program.GetHashCode(); + hash ^= licenses_.GetHashCode(); + if (RealmPermissions != 0) hash ^= RealmPermissions.GetHashCode(); + return hash; + } + + public override string ToString() + { + return pb::JsonFormatter.ToDiagnosticString(this); + } + + public void WriteTo(pb::CodedOutputStream output) + { + if (IsTrial != false) + { + output.WriteRawTag(32); + output.WriteBool(IsTrial); + } + if (IsLifetime != false) + { + output.WriteRawTag(40); + output.WriteBool(IsLifetime); + } + if (IsRestricted != false) + { + output.WriteRawTag(48); + output.WriteBool(IsRestricted); + } + if (IsBeta != false) + { + output.WriteRawTag(56); + output.WriteBool(IsBeta); + } + if (Name.Length != 0) + { + output.WriteRawTag(66); + output.WriteString(Name); + } + if (Program != 0) + { + output.WriteRawTag(77); + output.WriteFixed32(Program); + } + licenses_.WriteTo(output, _repeated_licenses_codec); + if (RealmPermissions != 0) + { + output.WriteRawTag(88); + output.WriteUInt32(RealmPermissions); + } + } + + public int CalculateSize() + { + int size = 0; + if (IsTrial != false) + { + size += 1 + 1; + } + if (IsLifetime != false) + { + size += 1 + 1; + } + if (IsRestricted != false) + { + size += 1 + 1; + } + if (IsBeta != false) + { + size += 1 + 1; + } + if (Name.Length != 0) + { + size += 1 + pb::CodedOutputStream.ComputeStringSize(Name); + } + if (Program != 0) + { + size += 1 + 4; + } + size += licenses_.CalculateSize(_repeated_licenses_codec); + if (RealmPermissions != 0) + { + size += 1 + pb::CodedOutputStream.ComputeUInt32Size(RealmPermissions); + } + return size; + } + + public void MergeFrom(GameLevelInfo other) + { + if (other == null) + { + return; + } + if (other.IsTrial != false) + { + IsTrial = other.IsTrial; + } + if (other.IsLifetime != false) + { + IsLifetime = other.IsLifetime; + } + if (other.IsRestricted != false) + { + IsRestricted = other.IsRestricted; + } + if (other.IsBeta != false) + { + IsBeta = other.IsBeta; + } + if (other.Name.Length != 0) + { + Name = other.Name; + } + if (other.Program != 0) + { + Program = other.Program; + } + licenses_.Add(other.licenses_); + if (other.RealmPermissions != 0) + { + RealmPermissions = other.RealmPermissions; + } + } + + public void MergeFrom(pb::CodedInputStream input) + { + uint tag; + while ((tag = input.ReadTag()) != 0) + { + switch (tag) + { + default: + input.SkipLastField(); + break; + case 32: + { + IsTrial = input.ReadBool(); + break; + } + case 40: + { + IsLifetime = input.ReadBool(); + break; + } + case 48: + { + IsRestricted = input.ReadBool(); + break; + } + case 56: + { + IsBeta = input.ReadBool(); + break; + } + case 66: + { + Name = input.ReadString(); + break; + } + case 77: + { + Program = input.ReadFixed32(); + break; + } + case 82: + { + licenses_.AddEntriesFrom(input, _repeated_licenses_codec); + break; + } + case 88: + { + RealmPermissions = input.ReadUInt32(); + break; + } + } + } + } + + } + + [System.Diagnostics.DebuggerNonUserCodeAttribute()] + public sealed partial class GameTimeInfo : pb::IMessage + { + private static readonly pb::MessageParser _parser = new pb::MessageParser(() => new GameTimeInfo()); + public static pb::MessageParser Parser { get { return _parser; } } + + public static pbr::MessageDescriptor Descriptor + { + get { return Bgs.Protocol.Account.V1.AccountTypesReflection.Descriptor.MessageTypes[22]; } + } + + pbr::MessageDescriptor pb::IMessage.Descriptor + { + get { return Descriptor; } + } + + public GameTimeInfo() + { + OnConstruction(); + } + + partial void OnConstruction(); + + public GameTimeInfo(GameTimeInfo other) : this() + { + isUnlimitedPlayTime_ = other.isUnlimitedPlayTime_; + playTimeExpires_ = other.playTimeExpires_; + isSubscription_ = other.isSubscription_; + isRecurringSubscription_ = other.isRecurringSubscription_; + } + + public GameTimeInfo Clone() + { + return new GameTimeInfo(this); + } + + /// Field number for the "is_unlimited_play_time" field. + public const int IsUnlimitedPlayTimeFieldNumber = 3; + private bool isUnlimitedPlayTime_; + public bool IsUnlimitedPlayTime + { + get { return isUnlimitedPlayTime_; } + set + { + isUnlimitedPlayTime_ = value; + } + } + + /// Field number for the "play_time_expires" field. + public const int PlayTimeExpiresFieldNumber = 5; + private ulong playTimeExpires_; + public ulong PlayTimeExpires + { + get { return playTimeExpires_; } + set + { + playTimeExpires_ = value; + } + } + + /// Field number for the "is_subscription" field. + public const int IsSubscriptionFieldNumber = 6; + private bool isSubscription_; + public bool IsSubscription + { + get { return isSubscription_; } + set + { + isSubscription_ = value; + } + } + + /// Field number for the "is_recurring_subscription" field. + public const int IsRecurringSubscriptionFieldNumber = 7; + private bool isRecurringSubscription_; + public bool IsRecurringSubscription + { + get { return isRecurringSubscription_; } + set + { + isRecurringSubscription_ = value; + } + } + + public override bool Equals(object other) + { + return Equals(other as GameTimeInfo); + } + + public bool Equals(GameTimeInfo other) + { + if (ReferenceEquals(other, null)) + { + return false; + } + if (ReferenceEquals(other, this)) + { + return true; + } + if (IsUnlimitedPlayTime != other.IsUnlimitedPlayTime) return false; + if (PlayTimeExpires != other.PlayTimeExpires) return false; + if (IsSubscription != other.IsSubscription) return false; + if (IsRecurringSubscription != other.IsRecurringSubscription) return false; + return true; + } + + public override int GetHashCode() + { + int hash = 1; + if (IsUnlimitedPlayTime != false) hash ^= IsUnlimitedPlayTime.GetHashCode(); + if (PlayTimeExpires != 0UL) hash ^= PlayTimeExpires.GetHashCode(); + if (IsSubscription != false) hash ^= IsSubscription.GetHashCode(); + if (IsRecurringSubscription != false) hash ^= IsRecurringSubscription.GetHashCode(); + return hash; + } + + public override string ToString() + { + return pb::JsonFormatter.ToDiagnosticString(this); + } + + public void WriteTo(pb::CodedOutputStream output) + { + if (IsUnlimitedPlayTime != false) + { + output.WriteRawTag(24); + output.WriteBool(IsUnlimitedPlayTime); + } + if (PlayTimeExpires != 0UL) + { + output.WriteRawTag(40); + output.WriteUInt64(PlayTimeExpires); + } + if (IsSubscription != false) + { + output.WriteRawTag(48); + output.WriteBool(IsSubscription); + } + if (IsRecurringSubscription != false) + { + output.WriteRawTag(56); + output.WriteBool(IsRecurringSubscription); + } + } + + public int CalculateSize() + { + int size = 0; + if (IsUnlimitedPlayTime != false) + { + size += 1 + 1; + } + if (PlayTimeExpires != 0UL) + { + size += 1 + pb::CodedOutputStream.ComputeUInt64Size(PlayTimeExpires); + } + if (IsSubscription != false) + { + size += 1 + 1; + } + if (IsRecurringSubscription != false) + { + size += 1 + 1; + } + return size; + } + + public void MergeFrom(GameTimeInfo other) + { + if (other == null) + { + return; + } + if (other.IsUnlimitedPlayTime != false) + { + IsUnlimitedPlayTime = other.IsUnlimitedPlayTime; + } + if (other.PlayTimeExpires != 0UL) + { + PlayTimeExpires = other.PlayTimeExpires; + } + if (other.IsSubscription != false) + { + IsSubscription = other.IsSubscription; + } + if (other.IsRecurringSubscription != false) + { + IsRecurringSubscription = other.IsRecurringSubscription; + } + } + + public void MergeFrom(pb::CodedInputStream input) + { + uint tag; + while ((tag = input.ReadTag()) != 0) + { + switch (tag) + { + default: + input.SkipLastField(); + break; + case 24: + { + IsUnlimitedPlayTime = input.ReadBool(); + break; + } + case 40: + { + PlayTimeExpires = input.ReadUInt64(); + break; + } + case 48: + { + IsSubscription = input.ReadBool(); + break; + } + case 56: + { + IsRecurringSubscription = input.ReadBool(); + break; + } + } + } + } + + } + + [System.Diagnostics.DebuggerNonUserCodeAttribute()] + public sealed partial class GameTimeRemainingInfo : pb::IMessage + { + private static readonly pb::MessageParser _parser = new pb::MessageParser(() => new GameTimeRemainingInfo()); + public static pb::MessageParser Parser { get { return _parser; } } + + public static pbr::MessageDescriptor Descriptor + { + get { return Bgs.Protocol.Account.V1.AccountTypesReflection.Descriptor.MessageTypes[23]; } + } + + pbr::MessageDescriptor pb::IMessage.Descriptor + { + get { return Descriptor; } + } + + public GameTimeRemainingInfo() + { + OnConstruction(); + } + + partial void OnConstruction(); + + public GameTimeRemainingInfo(GameTimeRemainingInfo other) : this() + { + minutesRemaining_ = other.minutesRemaining_; + parentalDailyMinutesRemaining_ = other.parentalDailyMinutesRemaining_; + parentalWeeklyMinutesRemaining_ = other.parentalWeeklyMinutesRemaining_; + secondsRemainingUntilKick_ = other.secondsRemainingUntilKick_; + } + + public GameTimeRemainingInfo Clone() + { + return new GameTimeRemainingInfo(this); + } + + /// Field number for the "minutes_remaining" field. + public const int MinutesRemainingFieldNumber = 1; + private uint minutesRemaining_; + public uint MinutesRemaining + { + get { return minutesRemaining_; } + set + { + minutesRemaining_ = value; + } + } + + /// Field number for the "parental_daily_minutes_remaining" field. + public const int ParentalDailyMinutesRemainingFieldNumber = 2; + private uint parentalDailyMinutesRemaining_; + public uint ParentalDailyMinutesRemaining + { + get { return parentalDailyMinutesRemaining_; } + set + { + parentalDailyMinutesRemaining_ = value; + } + } + + /// Field number for the "parental_weekly_minutes_remaining" field. + public const int ParentalWeeklyMinutesRemainingFieldNumber = 3; + private uint parentalWeeklyMinutesRemaining_; + public uint ParentalWeeklyMinutesRemaining + { + get { return parentalWeeklyMinutesRemaining_; } + set + { + parentalWeeklyMinutesRemaining_ = value; + } + } + + /// Field number for the "seconds_remaining_until_kick" field. + public const int SecondsRemainingUntilKickFieldNumber = 4; + private uint secondsRemainingUntilKick_; + public uint SecondsRemainingUntilKick + { + get { return secondsRemainingUntilKick_; } + set + { + secondsRemainingUntilKick_ = value; + } + } + + public override bool Equals(object other) + { + return Equals(other as GameTimeRemainingInfo); + } + + public bool Equals(GameTimeRemainingInfo other) + { + if (ReferenceEquals(other, null)) + { + return false; + } + if (ReferenceEquals(other, this)) + { + return true; + } + if (MinutesRemaining != other.MinutesRemaining) return false; + if (ParentalDailyMinutesRemaining != other.ParentalDailyMinutesRemaining) return false; + if (ParentalWeeklyMinutesRemaining != other.ParentalWeeklyMinutesRemaining) return false; + if (SecondsRemainingUntilKick != other.SecondsRemainingUntilKick) return false; + return true; + } + + public override int GetHashCode() + { + int hash = 1; + if (MinutesRemaining != 0) hash ^= MinutesRemaining.GetHashCode(); + if (ParentalDailyMinutesRemaining != 0) hash ^= ParentalDailyMinutesRemaining.GetHashCode(); + if (ParentalWeeklyMinutesRemaining != 0) hash ^= ParentalWeeklyMinutesRemaining.GetHashCode(); + if (SecondsRemainingUntilKick != 0) hash ^= SecondsRemainingUntilKick.GetHashCode(); + return hash; + } + + public override string ToString() + { + return pb::JsonFormatter.ToDiagnosticString(this); + } + + public void WriteTo(pb::CodedOutputStream output) + { + if (MinutesRemaining != 0) + { + output.WriteRawTag(8); + output.WriteUInt32(MinutesRemaining); + } + if (ParentalDailyMinutesRemaining != 0) + { + output.WriteRawTag(16); + output.WriteUInt32(ParentalDailyMinutesRemaining); + } + if (ParentalWeeklyMinutesRemaining != 0) + { + output.WriteRawTag(24); + output.WriteUInt32(ParentalWeeklyMinutesRemaining); + } + if (SecondsRemainingUntilKick != 0) + { + output.WriteRawTag(32); + output.WriteUInt32(SecondsRemainingUntilKick); + } + } + + public int CalculateSize() + { + int size = 0; + if (MinutesRemaining != 0) + { + size += 1 + pb::CodedOutputStream.ComputeUInt32Size(MinutesRemaining); + } + if (ParentalDailyMinutesRemaining != 0) + { + size += 1 + pb::CodedOutputStream.ComputeUInt32Size(ParentalDailyMinutesRemaining); + } + if (ParentalWeeklyMinutesRemaining != 0) + { + size += 1 + pb::CodedOutputStream.ComputeUInt32Size(ParentalWeeklyMinutesRemaining); + } + if (SecondsRemainingUntilKick != 0) + { + size += 1 + pb::CodedOutputStream.ComputeUInt32Size(SecondsRemainingUntilKick); + } + return size; + } + + public void MergeFrom(GameTimeRemainingInfo other) + { + if (other == null) + { + return; + } + if (other.MinutesRemaining != 0) + { + MinutesRemaining = other.MinutesRemaining; + } + if (other.ParentalDailyMinutesRemaining != 0) + { + ParentalDailyMinutesRemaining = other.ParentalDailyMinutesRemaining; + } + if (other.ParentalWeeklyMinutesRemaining != 0) + { + ParentalWeeklyMinutesRemaining = other.ParentalWeeklyMinutesRemaining; + } + if (other.SecondsRemainingUntilKick != 0) + { + SecondsRemainingUntilKick = other.SecondsRemainingUntilKick; + } + } + + public void MergeFrom(pb::CodedInputStream input) + { + uint tag; + while ((tag = input.ReadTag()) != 0) + { + switch (tag) + { + default: + input.SkipLastField(); + break; + case 8: + { + MinutesRemaining = input.ReadUInt32(); + break; + } + case 16: + { + ParentalDailyMinutesRemaining = input.ReadUInt32(); + break; + } + case 24: + { + ParentalWeeklyMinutesRemaining = input.ReadUInt32(); + break; + } + case 32: + { + SecondsRemainingUntilKick = input.ReadUInt32(); + break; + } + } + } + } + + } + + [System.Diagnostics.DebuggerNonUserCodeAttribute()] + public sealed partial class GameStatus : pb::IMessage + { + private static readonly pb::MessageParser _parser = new pb::MessageParser(() => new GameStatus()); + public static pb::MessageParser Parser { get { return _parser; } } + + public static pbr::MessageDescriptor Descriptor + { + get { return Bgs.Protocol.Account.V1.AccountTypesReflection.Descriptor.MessageTypes[24]; } + } + + pbr::MessageDescriptor pb::IMessage.Descriptor + { + get { return Descriptor; } + } + + public GameStatus() + { + OnConstruction(); + } + + partial void OnConstruction(); + + public GameStatus(GameStatus other) : this() + { + isSuspended_ = other.isSuspended_; + isBanned_ = other.isBanned_; + suspensionExpires_ = other.suspensionExpires_; + program_ = other.program_; + isLocked_ = other.isLocked_; + isBamUnlockable_ = other.isBamUnlockable_; + } + + public GameStatus Clone() + { + return new GameStatus(this); + } + + /// Field number for the "is_suspended" field. + public const int IsSuspendedFieldNumber = 4; + private bool isSuspended_; + public bool IsSuspended + { + get { return isSuspended_; } + set + { + bitArray.Set(IsSuspendedFieldNumber, true); + isSuspended_ = value; + } + } + + /// Field number for the "is_banned" field. + public const int IsBannedFieldNumber = 5; + private bool isBanned_; + public bool IsBanned + { + get { return isBanned_; } + set + { + bitArray.Set(IsBannedFieldNumber, true); + isBanned_ = value; + } + } + + /// Field number for the "suspension_expires" field. + public const int SuspensionExpiresFieldNumber = 6; + private ulong suspensionExpires_; + public ulong SuspensionExpires + { + get { return suspensionExpires_; } + set + { + bitArray.Set(SuspensionExpiresFieldNumber, true); + suspensionExpires_ = value; + } + } + + /// Field number for the "program" field. + public const int ProgramFieldNumber = 7; + private uint program_; + public uint Program + { + get { return program_; } + set + { + bitArray.Set(ProgramFieldNumber, true); + program_ = value; + } + } + + /// Field number for the "is_locked" field. + public const int IsLockedFieldNumber = 8; + private bool isLocked_; + public bool IsLocked + { + get { return isLocked_; } + set + { + bitArray.Set(IsLockedFieldNumber, true); + isLocked_ = value; + } + } + + /// Field number for the "is_bam_unlockable" field. + public const int IsBamUnlockableFieldNumber = 9; + private bool isBamUnlockable_; + public bool IsBamUnlockable + { + get { return isBamUnlockable_; } + set + { + bitArray.Set(IsBamUnlockableFieldNumber, true); + isBamUnlockable_ = value; + } + } + + public override bool Equals(object other) + { + return Equals(other as GameStatus); + } + + public bool Equals(GameStatus other) + { + if (ReferenceEquals(other, null)) + { + return false; + } + if (ReferenceEquals(other, this)) + { + return true; + } + if (IsSuspended != other.IsSuspended) return false; + if (IsBanned != other.IsBanned) return false; + if (SuspensionExpires != other.SuspensionExpires) return false; + if (Program != other.Program) return false; + if (IsLocked != other.IsLocked) return false; + if (IsBamUnlockable != other.IsBamUnlockable) return false; + return true; + } + + public override int GetHashCode() + { + int hash = 1; + if (IsSuspended != false) hash ^= IsSuspended.GetHashCode(); + if (IsBanned != false) hash ^= IsBanned.GetHashCode(); + if (SuspensionExpires != 0UL) hash ^= SuspensionExpires.GetHashCode(); + if (Program != 0) hash ^= Program.GetHashCode(); + if (IsLocked != false) hash ^= IsLocked.GetHashCode(); + if (IsBamUnlockable != false) hash ^= IsBamUnlockable.GetHashCode(); + return hash; + } + + public override string ToString() + { + return pb::JsonFormatter.ToDiagnosticString(this); + } + + public void WriteTo(pb::CodedOutputStream output) + { + if (bitArray.Get(IsSuspendedFieldNumber)) + { + output.WriteRawTag(32); + output.WriteBool(IsSuspended); + } + if (bitArray.Get(IsBannedFieldNumber)) + { + output.WriteRawTag(40); + output.WriteBool(IsBanned); + } + if (bitArray.Get(SuspensionExpiresFieldNumber)) + { + output.WriteRawTag(48); + output.WriteUInt64(SuspensionExpires); + } + if (bitArray.Get(ProgramFieldNumber)) + { + output.WriteRawTag(61); + output.WriteFixed32(Program); + } + if (bitArray.Get(IsLockedFieldNumber)) + { + output.WriteRawTag(64); + output.WriteBool(IsLocked); + } + if (bitArray.Get(IsBamUnlockableFieldNumber)) + { + output.WriteRawTag(72); + output.WriteBool(IsBamUnlockable); + } + } + + public int CalculateSize() + { + int size = 0; + if (bitArray.Get(IsSuspendedFieldNumber)) + { + size += 1 + 1; + } + if (bitArray.Get(IsBannedFieldNumber)) + { + size += 1 + 1; + } + if (bitArray.Get(SuspensionExpiresFieldNumber)) + { + size += 1 + pb::CodedOutputStream.ComputeUInt64Size(SuspensionExpires); + } + if (bitArray.Get(ProgramFieldNumber)) + { + size += 1 + 4; + } + if (bitArray.Get(IsLockedFieldNumber)) + { + size += 1 + 1; + } + if (bitArray.Get(IsBamUnlockableFieldNumber)) + { + size += 1 + 1; + } + return size; + } + + public void MergeFrom(GameStatus other) + { + if (other == null) + { + return; + } + if (other.IsSuspended != false) + { + IsSuspended = other.IsSuspended; + } + if (other.IsBanned != false) + { + IsBanned = other.IsBanned; + } + if (other.SuspensionExpires != 0UL) + { + SuspensionExpires = other.SuspensionExpires; + } + if (other.Program != 0) + { + Program = other.Program; + } + if (other.IsLocked != false) + { + IsLocked = other.IsLocked; + } + if (other.IsBamUnlockable != false) + { + IsBamUnlockable = other.IsBamUnlockable; + } + } + + public void MergeFrom(pb::CodedInputStream input) + { + uint tag; + while ((tag = input.ReadTag()) != 0) + { + switch (tag) + { + default: + input.SkipLastField(); + break; + case 32: + { + IsSuspended = input.ReadBool(); + break; + } + case 40: + { + IsBanned = input.ReadBool(); + break; + } + case 48: + { + SuspensionExpires = input.ReadUInt64(); + break; + } + case 61: + { + Program = input.ReadFixed32(); + break; + } + case 64: + { + IsLocked = input.ReadBool(); + break; + } + case 72: + { + IsBamUnlockable = input.ReadBool(); + break; + } + } + } + } + + System.Collections.BitArray bitArray = new System.Collections.BitArray(10); + } + + [System.Diagnostics.DebuggerNonUserCodeAttribute()] + public sealed partial class RAFInfo : pb::IMessage + { + private static readonly pb::MessageParser _parser = new pb::MessageParser(() => new RAFInfo()); + public static pb::MessageParser Parser { get { return _parser; } } + + public static pbr::MessageDescriptor Descriptor + { + get { return Bgs.Protocol.Account.V1.AccountTypesReflection.Descriptor.MessageTypes[25]; } + } + + pbr::MessageDescriptor pb::IMessage.Descriptor + { + get { return Descriptor; } + } + + public RAFInfo() + { + OnConstruction(); + } + + partial void OnConstruction(); + + public RAFInfo(RAFInfo other) : this() + { + rafInfo_ = other.rafInfo_; + } + + public RAFInfo Clone() + { + return new RAFInfo(this); + } + + /// Field number for the "raf_info" field. + public const int RafInfoFieldNumber = 1; + private pb::ByteString rafInfo_ = pb::ByteString.Empty; + public pb::ByteString RafInfo + { + get { return rafInfo_; } + set + { + rafInfo_ = pb::ProtoPreconditions.CheckNotNull(value, "value"); + } + } + + public override bool Equals(object other) + { + return Equals(other as RAFInfo); + } + + public bool Equals(RAFInfo other) + { + if (ReferenceEquals(other, null)) + { + return false; + } + if (ReferenceEquals(other, this)) + { + return true; + } + if (RafInfo != other.RafInfo) return false; + return true; + } + + public override int GetHashCode() + { + int hash = 1; + if (RafInfo.Length != 0) hash ^= RafInfo.GetHashCode(); + return hash; + } + + public override string ToString() + { + return pb::JsonFormatter.ToDiagnosticString(this); + } + + public void WriteTo(pb::CodedOutputStream output) + { + if (RafInfo.Length != 0) + { + output.WriteRawTag(10); + output.WriteBytes(RafInfo); + } + } + + public int CalculateSize() + { + int size = 0; + if (RafInfo.Length != 0) + { + size += 1 + pb::CodedOutputStream.ComputeBytesSize(RafInfo); + } + return size; + } + + public void MergeFrom(RAFInfo other) + { + if (other == null) + { + return; + } + if (other.RafInfo.Length != 0) + { + RafInfo = other.RafInfo; + } + } + + public void MergeFrom(pb::CodedInputStream input) + { + uint tag; + while ((tag = input.ReadTag()) != 0) + { + switch (tag) + { + default: + input.SkipLastField(); + break; + case 10: + { + RafInfo = input.ReadBytes(); + break; + } + } + } + } + + } + + [System.Diagnostics.DebuggerNonUserCodeAttribute()] + public sealed partial class GameSessionInfo : pb::IMessage + { + private static readonly pb::MessageParser _parser = new pb::MessageParser(() => new GameSessionInfo()); + public static pb::MessageParser Parser { get { return _parser; } } + + public static pbr::MessageDescriptor Descriptor + { + get { return Bgs.Protocol.Account.V1.AccountTypesReflection.Descriptor.MessageTypes[26]; } + } + + pbr::MessageDescriptor pb::IMessage.Descriptor + { + get { return Descriptor; } + } + + public GameSessionInfo() + { + OnConstruction(); + } + + partial void OnConstruction(); + + public GameSessionInfo(GameSessionInfo other) : this() + { + startTime_ = other.startTime_; + Location = other.location_ != null ? other.Location.Clone() : null; + hasBenefactor_ = other.hasBenefactor_; + isUsingIgr_ = other.isUsingIgr_; + parentalControlsActive_ = other.parentalControlsActive_; + startTimeSec_ = other.startTimeSec_; + } + + public GameSessionInfo Clone() + { + return new GameSessionInfo(this); + } + + /// Field number for the "start_time" field. + public const int StartTimeFieldNumber = 3; + private uint startTime_; + [System.ObsoleteAttribute()] + public uint StartTime + { + get { return startTime_; } + set + { + startTime_ = value; + } + } + + /// Field number for the "location" field. + public const int LocationFieldNumber = 4; + private Bgs.Protocol.Account.V1.GameSessionLocation location_; + public Bgs.Protocol.Account.V1.GameSessionLocation Location + { + get { return location_; } + set + { + location_ = value; + } + } + + /// Field number for the "has_benefactor" field. + public const int HasBenefactorFieldNumber = 5; + private bool hasBenefactor_; + public bool HasBenefactor + { + get { return hasBenefactor_; } + set + { + hasBenefactor_ = value; + } + } + + /// Field number for the "is_using_igr" field. + public const int IsUsingIgrFieldNumber = 6; + private bool isUsingIgr_; + public bool IsUsingIgr + { + get { return isUsingIgr_; } + set + { + isUsingIgr_ = value; + } + } + + /// Field number for the "parental_controls_active" field. + public const int ParentalControlsActiveFieldNumber = 7; + private bool parentalControlsActive_; + public bool ParentalControlsActive + { + get { return parentalControlsActive_; } + set + { + parentalControlsActive_ = value; + } + } + + /// Field number for the "start_time_sec" field. + public const int StartTimeSecFieldNumber = 8; + private ulong startTimeSec_; + public ulong StartTimeSec + { + get { return startTimeSec_; } + set + { + startTimeSec_ = value; + } + } + + public override bool Equals(object other) + { + return Equals(other as GameSessionInfo); + } + + public bool Equals(GameSessionInfo other) + { + if (ReferenceEquals(other, null)) + { + return false; + } + if (ReferenceEquals(other, this)) + { + return true; + } + if (StartTime != other.StartTime) return false; + if (!object.Equals(Location, other.Location)) return false; + if (HasBenefactor != other.HasBenefactor) return false; + if (IsUsingIgr != other.IsUsingIgr) return false; + if (ParentalControlsActive != other.ParentalControlsActive) return false; + if (StartTimeSec != other.StartTimeSec) return false; + return true; + } + + public override int GetHashCode() + { + int hash = 1; + if (StartTime != 0) hash ^= StartTime.GetHashCode(); + if (location_ != null) hash ^= Location.GetHashCode(); + if (HasBenefactor != false) hash ^= HasBenefactor.GetHashCode(); + if (IsUsingIgr != false) hash ^= IsUsingIgr.GetHashCode(); + if (ParentalControlsActive != false) hash ^= ParentalControlsActive.GetHashCode(); + if (StartTimeSec != 0UL) hash ^= StartTimeSec.GetHashCode(); + return hash; + } + + public override string ToString() + { + return pb::JsonFormatter.ToDiagnosticString(this); + } + + public void WriteTo(pb::CodedOutputStream output) + { + if (StartTime != 0) + { + output.WriteRawTag(24); + output.WriteUInt32(StartTime); + } + if (location_ != null) + { + output.WriteRawTag(34); + output.WriteMessage(Location); + } + if (HasBenefactor != false) + { + output.WriteRawTag(40); + output.WriteBool(HasBenefactor); + } + if (IsUsingIgr != false) + { + output.WriteRawTag(48); + output.WriteBool(IsUsingIgr); + } + if (ParentalControlsActive != false) + { + output.WriteRawTag(56); + output.WriteBool(ParentalControlsActive); + } + if (StartTimeSec != 0UL) + { + output.WriteRawTag(64); + output.WriteUInt64(StartTimeSec); + } + } + + public int CalculateSize() + { + int size = 0; + if (StartTime != 0) + { + size += 1 + pb::CodedOutputStream.ComputeUInt32Size(StartTime); + } + if (location_ != null) + { + size += 1 + pb::CodedOutputStream.ComputeMessageSize(Location); + } + if (HasBenefactor != false) + { + size += 1 + 1; + } + if (IsUsingIgr != false) + { + size += 1 + 1; + } + if (ParentalControlsActive != false) + { + size += 1 + 1; + } + if (StartTimeSec != 0UL) + { + size += 1 + pb::CodedOutputStream.ComputeUInt64Size(StartTimeSec); + } + return size; + } + + public void MergeFrom(GameSessionInfo other) + { + if (other == null) + { + return; + } + if (other.StartTime != 0) + { + StartTime = other.StartTime; + } + if (other.location_ != null) + { + if (location_ == null) + { + location_ = new Bgs.Protocol.Account.V1.GameSessionLocation(); + } + Location.MergeFrom(other.Location); + } + if (other.HasBenefactor != false) + { + HasBenefactor = other.HasBenefactor; + } + if (other.IsUsingIgr != false) + { + IsUsingIgr = other.IsUsingIgr; + } + if (other.ParentalControlsActive != false) + { + ParentalControlsActive = other.ParentalControlsActive; + } + if (other.StartTimeSec != 0UL) + { + StartTimeSec = other.StartTimeSec; + } + } + + public void MergeFrom(pb::CodedInputStream input) + { + uint tag; + while ((tag = input.ReadTag()) != 0) + { + switch (tag) + { + default: + input.SkipLastField(); + break; + case 24: + { + StartTime = input.ReadUInt32(); + break; + } + case 34: + { + if (location_ == null) + { + location_ = new Bgs.Protocol.Account.V1.GameSessionLocation(); + } + input.ReadMessage(location_); + break; + } + case 40: + { + HasBenefactor = input.ReadBool(); + break; + } + case 48: + { + IsUsingIgr = input.ReadBool(); + break; + } + case 56: + { + ParentalControlsActive = input.ReadBool(); + break; + } + case 64: + { + StartTimeSec = input.ReadUInt64(); + break; + } + } + } + } + + } + + [System.Diagnostics.DebuggerNonUserCodeAttribute()] + public sealed partial class GameSessionUpdateInfo : pb::IMessage + { + private static readonly pb::MessageParser _parser = new pb::MessageParser(() => new GameSessionUpdateInfo()); + public static pb::MessageParser Parser { get { return _parser; } } + + public static pbr::MessageDescriptor Descriptor + { + get { return Bgs.Protocol.Account.V1.AccountTypesReflection.Descriptor.MessageTypes[27]; } + } + + pbr::MessageDescriptor pb::IMessage.Descriptor + { + get { return Descriptor; } + } + + public GameSessionUpdateInfo() + { + OnConstruction(); + } + + partial void OnConstruction(); + + public GameSessionUpdateInfo(GameSessionUpdateInfo other) : this() + { + Cais = other.cais_ != null ? other.Cais.Clone() : null; + } + + public GameSessionUpdateInfo Clone() + { + return new GameSessionUpdateInfo(this); + } + + /// Field number for the "cais" field. + public const int CaisFieldNumber = 8; + private Bgs.Protocol.Account.V1.CAIS cais_; + public Bgs.Protocol.Account.V1.CAIS Cais + { + get { return cais_; } + set + { + cais_ = value; + } + } + + public override bool Equals(object other) + { + return Equals(other as GameSessionUpdateInfo); + } + + public bool Equals(GameSessionUpdateInfo other) + { + if (ReferenceEquals(other, null)) + { + return false; + } + if (ReferenceEquals(other, this)) + { + return true; + } + if (!object.Equals(Cais, other.Cais)) return false; + return true; + } + + public override int GetHashCode() + { + int hash = 1; + if (cais_ != null) hash ^= Cais.GetHashCode(); + return hash; + } + + public override string ToString() + { + return pb::JsonFormatter.ToDiagnosticString(this); + } + + public void WriteTo(pb::CodedOutputStream output) + { + if (cais_ != null) + { + output.WriteRawTag(66); + output.WriteMessage(Cais); + } + } + + public int CalculateSize() + { + int size = 0; + if (cais_ != null) + { + size += 1 + pb::CodedOutputStream.ComputeMessageSize(Cais); + } + return size; + } + + public void MergeFrom(GameSessionUpdateInfo other) + { + if (other == null) + { + return; + } + if (other.cais_ != null) + { + if (cais_ == null) + { + cais_ = new Bgs.Protocol.Account.V1.CAIS(); + } + Cais.MergeFrom(other.Cais); + } + } + + public void MergeFrom(pb::CodedInputStream input) + { + uint tag; + while ((tag = input.ReadTag()) != 0) + { + switch (tag) + { + default: + input.SkipLastField(); + break; + case 66: + { + if (cais_ == null) + { + cais_ = new Bgs.Protocol.Account.V1.CAIS(); + } + input.ReadMessage(cais_); + break; + } + } + } + } + + } + + [System.Diagnostics.DebuggerNonUserCodeAttribute()] + public sealed partial class GameSessionLocation : pb::IMessage + { + private static readonly pb::MessageParser _parser = new pb::MessageParser(() => new GameSessionLocation()); + public static pb::MessageParser Parser { get { return _parser; } } + + public static pbr::MessageDescriptor Descriptor + { + get { return Bgs.Protocol.Account.V1.AccountTypesReflection.Descriptor.MessageTypes[28]; } + } + + pbr::MessageDescriptor pb::IMessage.Descriptor + { + get { return Descriptor; } + } + + public GameSessionLocation() + { + OnConstruction(); + } + + partial void OnConstruction(); + + public GameSessionLocation(GameSessionLocation other) : this() + { + ipAddress_ = other.ipAddress_; + country_ = other.country_; + city_ = other.city_; + } + + public GameSessionLocation Clone() + { + return new GameSessionLocation(this); + } + + /// Field number for the "ip_address" field. + public const int IpAddressFieldNumber = 1; + private string ipAddress_ = ""; + public string IpAddress + { + get { return ipAddress_; } + set + { + ipAddress_ = pb::ProtoPreconditions.CheckNotNull(value, "value"); + } + } + + /// Field number for the "country" field. + public const int CountryFieldNumber = 2; + private uint country_; + public uint Country + { + get { return country_; } + set + { + country_ = value; + } + } + + /// Field number for the "city" field. + public const int CityFieldNumber = 3; + private string city_ = ""; + public string City + { + get { return city_; } + set + { + city_ = pb::ProtoPreconditions.CheckNotNull(value, "value"); + } + } + + public override bool Equals(object other) + { + return Equals(other as GameSessionLocation); + } + + public bool Equals(GameSessionLocation other) + { + if (ReferenceEquals(other, null)) + { + return false; + } + if (ReferenceEquals(other, this)) + { + return true; + } + if (IpAddress != other.IpAddress) return false; + if (Country != other.Country) return false; + if (City != other.City) return false; + return true; + } + + public override int GetHashCode() + { + int hash = 1; + if (IpAddress.Length != 0) hash ^= IpAddress.GetHashCode(); + if (Country != 0) hash ^= Country.GetHashCode(); + if (City.Length != 0) hash ^= City.GetHashCode(); + return hash; + } + + public override string ToString() + { + return pb::JsonFormatter.ToDiagnosticString(this); + } + + public void WriteTo(pb::CodedOutputStream output) + { + if (IpAddress.Length != 0) + { + output.WriteRawTag(10); + output.WriteString(IpAddress); + } + if (Country != 0) + { + output.WriteRawTag(16); + output.WriteUInt32(Country); + } + if (City.Length != 0) + { + output.WriteRawTag(26); + output.WriteString(City); + } + } + + public int CalculateSize() + { + int size = 0; + if (IpAddress.Length != 0) + { + size += 1 + pb::CodedOutputStream.ComputeStringSize(IpAddress); + } + if (Country != 0) + { + size += 1 + pb::CodedOutputStream.ComputeUInt32Size(Country); + } + if (City.Length != 0) + { + size += 1 + pb::CodedOutputStream.ComputeStringSize(City); + } + return size; + } + + public void MergeFrom(GameSessionLocation other) + { + if (other == null) + { + return; + } + if (other.IpAddress.Length != 0) + { + IpAddress = other.IpAddress; + } + if (other.Country != 0) + { + Country = other.Country; + } + if (other.City.Length != 0) + { + City = other.City; + } + } + + public void MergeFrom(pb::CodedInputStream input) + { + uint tag; + while ((tag = input.ReadTag()) != 0) + { + switch (tag) + { + default: + input.SkipLastField(); + break; + case 10: + { + IpAddress = input.ReadString(); + break; + } + case 16: + { + Country = input.ReadUInt32(); + break; + } + case 26: + { + City = input.ReadString(); + break; + } + } + } + } + + } + + [System.Diagnostics.DebuggerNonUserCodeAttribute()] + public sealed partial class CAIS : pb::IMessage + { + private static readonly pb::MessageParser _parser = new pb::MessageParser(() => new CAIS()); + public static pb::MessageParser Parser { get { return _parser; } } + + public static pbr::MessageDescriptor Descriptor + { + get { return Bgs.Protocol.Account.V1.AccountTypesReflection.Descriptor.MessageTypes[29]; } + } + + pbr::MessageDescriptor pb::IMessage.Descriptor + { + get { return Descriptor; } + } + + public CAIS() + { + OnConstruction(); + } + + partial void OnConstruction(); + + public CAIS(CAIS other) : this() + { + playedMinutes_ = other.playedMinutes_; + restedMinutes_ = other.restedMinutes_; + lastHeardTime_ = other.lastHeardTime_; + } + + public CAIS Clone() + { + return new CAIS(this); + } + + /// Field number for the "played_minutes" field. + public const int PlayedMinutesFieldNumber = 1; + private uint playedMinutes_; + public uint PlayedMinutes + { + get { return playedMinutes_; } + set + { + playedMinutes_ = value; + } + } + + /// Field number for the "rested_minutes" field. + public const int RestedMinutesFieldNumber = 2; + private uint restedMinutes_; + public uint RestedMinutes + { + get { return restedMinutes_; } + set + { + restedMinutes_ = value; + } + } + + /// Field number for the "last_heard_time" field. + public const int LastHeardTimeFieldNumber = 3; + private ulong lastHeardTime_; + public ulong LastHeardTime + { + get { return lastHeardTime_; } + set + { + lastHeardTime_ = value; + } + } + + public override bool Equals(object other) + { + return Equals(other as CAIS); + } + + public bool Equals(CAIS other) + { + if (ReferenceEquals(other, null)) + { + return false; + } + if (ReferenceEquals(other, this)) + { + return true; + } + if (PlayedMinutes != other.PlayedMinutes) return false; + if (RestedMinutes != other.RestedMinutes) return false; + if (LastHeardTime != other.LastHeardTime) return false; + return true; + } + + public override int GetHashCode() + { + int hash = 1; + if (PlayedMinutes != 0) hash ^= PlayedMinutes.GetHashCode(); + if (RestedMinutes != 0) hash ^= RestedMinutes.GetHashCode(); + if (LastHeardTime != 0UL) hash ^= LastHeardTime.GetHashCode(); + return hash; + } + + public override string ToString() + { + return pb::JsonFormatter.ToDiagnosticString(this); + } + + public void WriteTo(pb::CodedOutputStream output) + { + if (PlayedMinutes != 0) + { + output.WriteRawTag(8); + output.WriteUInt32(PlayedMinutes); + } + if (RestedMinutes != 0) + { + output.WriteRawTag(16); + output.WriteUInt32(RestedMinutes); + } + if (LastHeardTime != 0UL) + { + output.WriteRawTag(24); + output.WriteUInt64(LastHeardTime); + } + } + + public int CalculateSize() + { + int size = 0; + if (PlayedMinutes != 0) + { + size += 1 + pb::CodedOutputStream.ComputeUInt32Size(PlayedMinutes); + } + if (RestedMinutes != 0) + { + size += 1 + pb::CodedOutputStream.ComputeUInt32Size(RestedMinutes); + } + if (LastHeardTime != 0UL) + { + size += 1 + pb::CodedOutputStream.ComputeUInt64Size(LastHeardTime); + } + return size; + } + + public void MergeFrom(CAIS other) + { + if (other == null) + { + return; + } + if (other.PlayedMinutes != 0) + { + PlayedMinutes = other.PlayedMinutes; + } + if (other.RestedMinutes != 0) + { + RestedMinutes = other.RestedMinutes; + } + if (other.LastHeardTime != 0UL) + { + LastHeardTime = other.LastHeardTime; + } + } + + public void MergeFrom(pb::CodedInputStream input) + { + uint tag; + while ((tag = input.ReadTag()) != 0) + { + switch (tag) + { + default: + input.SkipLastField(); + break; + case 8: + { + PlayedMinutes = input.ReadUInt32(); + break; + } + case 16: + { + RestedMinutes = input.ReadUInt32(); + break; + } + case 24: + { + LastHeardTime = input.ReadUInt64(); + break; + } + } + } + } + + } + + [System.Diagnostics.DebuggerNonUserCodeAttribute()] + public sealed partial class GameAccountList : pb::IMessage + { + private static readonly pb::MessageParser _parser = new pb::MessageParser(() => new GameAccountList()); + public static pb::MessageParser Parser { get { return _parser; } } + + public static pbr::MessageDescriptor Descriptor + { + get { return Bgs.Protocol.Account.V1.AccountTypesReflection.Descriptor.MessageTypes[30]; } + } + + pbr::MessageDescriptor pb::IMessage.Descriptor + { + get { return Descriptor; } + } + + public GameAccountList() + { + OnConstruction(); + } + + partial void OnConstruction(); + + public GameAccountList(GameAccountList other) : this() + { + region_ = other.region_; + handle_ = other.handle_.Clone(); + } + + public GameAccountList Clone() + { + return new GameAccountList(this); + } + + /// Field number for the "region" field. + public const int RegionFieldNumber = 3; + private uint region_; + public uint Region + { + get { return region_; } + set + { + region_ = value; + } + } + + /// Field number for the "handle" field. + public const int HandleFieldNumber = 4; + private static readonly pb::FieldCodec _repeated_handle_codec + = pb::FieldCodec.ForMessage(34, Bgs.Protocol.Account.V1.GameAccountHandle.Parser); + private readonly pbc::RepeatedField handle_ = new pbc::RepeatedField(); + public pbc::RepeatedField Handle + { + get { return handle_; } + } + + public override bool Equals(object other) + { + return Equals(other as GameAccountList); + } + + public bool Equals(GameAccountList other) + { + if (ReferenceEquals(other, null)) + { + return false; + } + if (ReferenceEquals(other, this)) + { + return true; + } + if (Region != other.Region) return false; + if (!handle_.Equals(other.handle_)) return false; + return true; + } + + public override int GetHashCode() + { + int hash = 1; + if (Region != 0) hash ^= Region.GetHashCode(); + hash ^= handle_.GetHashCode(); + return hash; + } + + public override string ToString() + { + return pb::JsonFormatter.ToDiagnosticString(this); + } + + public void WriteTo(pb::CodedOutputStream output) + { + if (Region != 0) + { + output.WriteRawTag(24); + output.WriteUInt32(Region); + } + handle_.WriteTo(output, _repeated_handle_codec); + } + + public int CalculateSize() + { + int size = 0; + if (Region != 0) + { + size += 1 + pb::CodedOutputStream.ComputeUInt32Size(Region); + } + size += handle_.CalculateSize(_repeated_handle_codec); + return size; + } + + public void MergeFrom(GameAccountList other) + { + if (other == null) + { + return; + } + if (other.Region != 0) + { + Region = other.Region; + } + handle_.Add(other.handle_); + } + + public void MergeFrom(pb::CodedInputStream input) + { + uint tag; + while ((tag = input.ReadTag()) != 0) + { + switch (tag) + { + default: + input.SkipLastField(); + break; + case 24: + { + Region = input.ReadUInt32(); + break; + } + case 34: + { + handle_.AddEntriesFrom(input, _repeated_handle_codec); + break; + } + } + } + } + + } + + [System.Diagnostics.DebuggerNonUserCodeAttribute()] + public sealed partial class AccountState : pb::IMessage + { + private static readonly pb::MessageParser _parser = new pb::MessageParser(() => new AccountState()); + public static pb::MessageParser Parser { get { return _parser; } } + + public static pbr::MessageDescriptor Descriptor + { + get { return Bgs.Protocol.Account.V1.AccountTypesReflection.Descriptor.MessageTypes[31]; } + } + + pbr::MessageDescriptor pb::IMessage.Descriptor + { + get { return Descriptor; } + } + + public AccountState() + { + OnConstruction(); + } + + partial void OnConstruction(); + + public AccountState(AccountState other) : this() + { + AccountLevelInfo = other.accountLevelInfo_ != null ? other.AccountLevelInfo.Clone() : null; + PrivacyInfo = other.privacyInfo_ != null ? other.PrivacyInfo.Clone() : null; + ParentalControlInfo = other.parentalControlInfo_ != null ? other.ParentalControlInfo.Clone() : null; + gameLevelInfo_ = other.gameLevelInfo_.Clone(); + gameStatus_ = other.gameStatus_.Clone(); + gameAccounts_ = other.gameAccounts_.Clone(); + } + + public AccountState Clone() + { + return new AccountState(this); + } + + /// Field number for the "account_level_info" field. + public const int AccountLevelInfoFieldNumber = 1; + private Bgs.Protocol.Account.V1.AccountLevelInfo accountLevelInfo_; + public Bgs.Protocol.Account.V1.AccountLevelInfo AccountLevelInfo + { + get { return accountLevelInfo_; } + set + { + accountLevelInfo_ = value; + } + } + + /// Field number for the "privacy_info" field. + public const int PrivacyInfoFieldNumber = 2; + private Bgs.Protocol.Account.V1.PrivacyInfo privacyInfo_; + public Bgs.Protocol.Account.V1.PrivacyInfo PrivacyInfo + { + get { return privacyInfo_; } + set + { + privacyInfo_ = value; + } + } + + /// Field number for the "parental_control_info" field. + public const int ParentalControlInfoFieldNumber = 3; + private Bgs.Protocol.Account.V1.ParentalControlInfo parentalControlInfo_; + public Bgs.Protocol.Account.V1.ParentalControlInfo ParentalControlInfo + { + get { return parentalControlInfo_; } + set + { + parentalControlInfo_ = value; + } + } + + /// Field number for the "game_level_info" field. + public const int GameLevelInfoFieldNumber = 5; + private static readonly pb::FieldCodec _repeated_gameLevelInfo_codec + = pb::FieldCodec.ForMessage(42, Bgs.Protocol.Account.V1.GameLevelInfo.Parser); + private readonly pbc::RepeatedField gameLevelInfo_ = new pbc::RepeatedField(); + public pbc::RepeatedField GameLevelInfo + { + get { return gameLevelInfo_; } + } + + /// Field number for the "game_status" field. + public const int GameStatusFieldNumber = 6; + private static readonly pb::FieldCodec _repeated_gameStatus_codec + = pb::FieldCodec.ForMessage(50, Bgs.Protocol.Account.V1.GameStatus.Parser); + private readonly pbc::RepeatedField gameStatus_ = new pbc::RepeatedField(); + public pbc::RepeatedField GameStatus + { + get { return gameStatus_; } + } + + /// Field number for the "game_accounts" field. + public const int GameAccountsFieldNumber = 7; + private static readonly pb::FieldCodec _repeated_gameAccounts_codec + = pb::FieldCodec.ForMessage(58, Bgs.Protocol.Account.V1.GameAccountList.Parser); + private readonly pbc::RepeatedField gameAccounts_ = new pbc::RepeatedField(); + public pbc::RepeatedField GameAccounts + { + get { return gameAccounts_; } + } + + public override bool Equals(object other) + { + return Equals(other as AccountState); + } + + public bool Equals(AccountState other) + { + if (ReferenceEquals(other, null)) + { + return false; + } + if (ReferenceEquals(other, this)) + { + return true; + } + if (!object.Equals(AccountLevelInfo, other.AccountLevelInfo)) return false; + if (!object.Equals(PrivacyInfo, other.PrivacyInfo)) return false; + if (!object.Equals(ParentalControlInfo, other.ParentalControlInfo)) return false; + if (!gameLevelInfo_.Equals(other.gameLevelInfo_)) return false; + if (!gameStatus_.Equals(other.gameStatus_)) return false; + if (!gameAccounts_.Equals(other.gameAccounts_)) return false; + return true; + } + + public override int GetHashCode() + { + int hash = 1; + if (accountLevelInfo_ != null) hash ^= AccountLevelInfo.GetHashCode(); + if (privacyInfo_ != null) hash ^= PrivacyInfo.GetHashCode(); + if (parentalControlInfo_ != null) hash ^= ParentalControlInfo.GetHashCode(); + hash ^= gameLevelInfo_.GetHashCode(); + hash ^= gameStatus_.GetHashCode(); + hash ^= gameAccounts_.GetHashCode(); + return hash; + } + + public override string ToString() + { + return pb::JsonFormatter.ToDiagnosticString(this); + } + + public void WriteTo(pb::CodedOutputStream output) + { + if (accountLevelInfo_ != null) + { + output.WriteRawTag(10); + output.WriteMessage(AccountLevelInfo); + } + if (privacyInfo_ != null) + { + output.WriteRawTag(18); + output.WriteMessage(PrivacyInfo); + } + if (parentalControlInfo_ != null) + { + output.WriteRawTag(26); + output.WriteMessage(ParentalControlInfo); + } + gameLevelInfo_.WriteTo(output, _repeated_gameLevelInfo_codec); + gameStatus_.WriteTo(output, _repeated_gameStatus_codec); + gameAccounts_.WriteTo(output, _repeated_gameAccounts_codec); + } + + public int CalculateSize() + { + int size = 0; + if (accountLevelInfo_ != null) + { + size += 1 + pb::CodedOutputStream.ComputeMessageSize(AccountLevelInfo); + } + if (privacyInfo_ != null) + { + size += 1 + pb::CodedOutputStream.ComputeMessageSize(PrivacyInfo); + } + if (parentalControlInfo_ != null) + { + size += 1 + pb::CodedOutputStream.ComputeMessageSize(ParentalControlInfo); + } + size += gameLevelInfo_.CalculateSize(_repeated_gameLevelInfo_codec); + size += gameStatus_.CalculateSize(_repeated_gameStatus_codec); + size += gameAccounts_.CalculateSize(_repeated_gameAccounts_codec); + return size; + } + + public void MergeFrom(AccountState other) + { + if (other == null) + { + return; + } + if (other.accountLevelInfo_ != null) + { + if (accountLevelInfo_ == null) + { + accountLevelInfo_ = new Bgs.Protocol.Account.V1.AccountLevelInfo(); + } + AccountLevelInfo.MergeFrom(other.AccountLevelInfo); + } + if (other.privacyInfo_ != null) + { + if (privacyInfo_ == null) + { + privacyInfo_ = new Bgs.Protocol.Account.V1.PrivacyInfo(); + } + PrivacyInfo.MergeFrom(other.PrivacyInfo); + } + if (other.parentalControlInfo_ != null) + { + if (parentalControlInfo_ == null) + { + parentalControlInfo_ = new Bgs.Protocol.Account.V1.ParentalControlInfo(); + } + ParentalControlInfo.MergeFrom(other.ParentalControlInfo); + } + gameLevelInfo_.Add(other.gameLevelInfo_); + gameStatus_.Add(other.gameStatus_); + gameAccounts_.Add(other.gameAccounts_); + } + + public void MergeFrom(pb::CodedInputStream input) + { + uint tag; + while ((tag = input.ReadTag()) != 0) + { + switch (tag) + { + default: + input.SkipLastField(); + break; + case 10: + { + if (accountLevelInfo_ == null) + { + accountLevelInfo_ = new Bgs.Protocol.Account.V1.AccountLevelInfo(); + } + input.ReadMessage(accountLevelInfo_); + break; + } + case 18: + { + if (privacyInfo_ == null) + { + privacyInfo_ = new Bgs.Protocol.Account.V1.PrivacyInfo(); + } + input.ReadMessage(privacyInfo_); + break; + } + case 26: + { + if (parentalControlInfo_ == null) + { + parentalControlInfo_ = new Bgs.Protocol.Account.V1.ParentalControlInfo(); + } + input.ReadMessage(parentalControlInfo_); + break; + } + case 42: + { + gameLevelInfo_.AddEntriesFrom(input, _repeated_gameLevelInfo_codec); + break; + } + case 50: + { + gameStatus_.AddEntriesFrom(input, _repeated_gameStatus_codec); + break; + } + case 58: + { + gameAccounts_.AddEntriesFrom(input, _repeated_gameAccounts_codec); + break; + } + } + } + } + + } + + [System.Diagnostics.DebuggerNonUserCodeAttribute()] + public sealed partial class AccountStateTagged : pb::IMessage + { + private static readonly pb::MessageParser _parser = new pb::MessageParser(() => new AccountStateTagged()); + public static pb::MessageParser Parser { get { return _parser; } } + + public static pbr::MessageDescriptor Descriptor + { + get { return Bgs.Protocol.Account.V1.AccountTypesReflection.Descriptor.MessageTypes[32]; } + } + + pbr::MessageDescriptor pb::IMessage.Descriptor + { + get { return Descriptor; } + } + + public AccountStateTagged() + { + OnConstruction(); + } + + partial void OnConstruction(); + + public AccountStateTagged(AccountStateTagged other) : this() + { + AccountState = other.accountState_ != null ? other.AccountState.Clone() : null; + AccountTags = other.accountTags_ != null ? other.AccountTags.Clone() : null; + } + + public AccountStateTagged Clone() + { + return new AccountStateTagged(this); + } + + /// Field number for the "account_state" field. + public const int AccountStateFieldNumber = 1; + private Bgs.Protocol.Account.V1.AccountState accountState_; + public Bgs.Protocol.Account.V1.AccountState AccountState + { + get { return accountState_; } + set + { + accountState_ = value; + } + } + + /// Field number for the "account_tags" field. + public const int AccountTagsFieldNumber = 2; + private Bgs.Protocol.Account.V1.AccountFieldTags accountTags_; + public Bgs.Protocol.Account.V1.AccountFieldTags AccountTags + { + get { return accountTags_; } + set + { + accountTags_ = value; + } + } + + public override bool Equals(object other) + { + return Equals(other as AccountStateTagged); + } + + public bool Equals(AccountStateTagged other) + { + if (ReferenceEquals(other, null)) + { + return false; + } + if (ReferenceEquals(other, this)) + { + return true; + } + if (!object.Equals(AccountState, other.AccountState)) return false; + if (!object.Equals(AccountTags, other.AccountTags)) return false; + return true; + } + + public override int GetHashCode() + { + int hash = 1; + if (accountState_ != null) hash ^= AccountState.GetHashCode(); + if (accountTags_ != null) hash ^= AccountTags.GetHashCode(); + return hash; + } + + public override string ToString() + { + return pb::JsonFormatter.ToDiagnosticString(this); + } + + public void WriteTo(pb::CodedOutputStream output) + { + if (accountState_ != null) + { + output.WriteRawTag(10); + output.WriteMessage(AccountState); + } + if (accountTags_ != null) + { + output.WriteRawTag(18); + output.WriteMessage(AccountTags); + } + } + + public int CalculateSize() + { + int size = 0; + if (accountState_ != null) + { + size += 1 + pb::CodedOutputStream.ComputeMessageSize(AccountState); + } + if (accountTags_ != null) + { + size += 1 + pb::CodedOutputStream.ComputeMessageSize(AccountTags); + } + return size; + } + + public void MergeFrom(AccountStateTagged other) + { + if (other == null) + { + return; + } + if (other.accountState_ != null) + { + if (accountState_ == null) + { + accountState_ = new Bgs.Protocol.Account.V1.AccountState(); + } + AccountState.MergeFrom(other.AccountState); + } + if (other.accountTags_ != null) + { + if (accountTags_ == null) + { + accountTags_ = new Bgs.Protocol.Account.V1.AccountFieldTags(); + } + AccountTags.MergeFrom(other.AccountTags); + } + } + + public void MergeFrom(pb::CodedInputStream input) + { + uint tag; + while ((tag = input.ReadTag()) != 0) + { + switch (tag) + { + default: + input.SkipLastField(); + break; + case 10: + { + if (accountState_ == null) + { + accountState_ = new Bgs.Protocol.Account.V1.AccountState(); + } + input.ReadMessage(accountState_); + break; + } + case 18: + { + if (accountTags_ == null) + { + accountTags_ = new Bgs.Protocol.Account.V1.AccountFieldTags(); + } + input.ReadMessage(accountTags_); + break; + } + } + } + } + + } + + [System.Diagnostics.DebuggerNonUserCodeAttribute()] + public sealed partial class GameAccountState : pb::IMessage + { + private static readonly pb::MessageParser _parser = new pb::MessageParser(() => new GameAccountState()); + public static pb::MessageParser Parser { get { return _parser; } } + + public static pbr::MessageDescriptor Descriptor + { + get { return Bgs.Protocol.Account.V1.AccountTypesReflection.Descriptor.MessageTypes[33]; } + } + + pbr::MessageDescriptor pb::IMessage.Descriptor + { + get { return Descriptor; } + } + + public GameAccountState() + { + OnConstruction(); + } + + partial void OnConstruction(); + + public GameAccountState(GameAccountState other) : this() + { + GameLevelInfo = other.gameLevelInfo_ != null ? other.GameLevelInfo.Clone() : null; + GameTimeInfo = other.gameTimeInfo_ != null ? other.GameTimeInfo.Clone() : null; + GameStatus = other.gameStatus_ != null ? other.GameStatus.Clone() : null; + RafInfo = other.rafInfo_ != null ? other.RafInfo.Clone() : null; + } + + public GameAccountState Clone() + { + return new GameAccountState(this); + } + + /// Field number for the "game_level_info" field. + public const int GameLevelInfoFieldNumber = 1; + private Bgs.Protocol.Account.V1.GameLevelInfo gameLevelInfo_; + public Bgs.Protocol.Account.V1.GameLevelInfo GameLevelInfo + { + get { return gameLevelInfo_; } + set + { + gameLevelInfo_ = value; + } + } + + /// Field number for the "game_time_info" field. + public const int GameTimeInfoFieldNumber = 2; + private Bgs.Protocol.Account.V1.GameTimeInfo gameTimeInfo_; + public Bgs.Protocol.Account.V1.GameTimeInfo GameTimeInfo + { + get { return gameTimeInfo_; } + set + { + gameTimeInfo_ = value; + } + } + + /// Field number for the "game_status" field. + public const int GameStatusFieldNumber = 3; + private Bgs.Protocol.Account.V1.GameStatus gameStatus_; + public Bgs.Protocol.Account.V1.GameStatus GameStatus + { + get { return gameStatus_; } + set + { + gameStatus_ = value; + } + } + + /// Field number for the "raf_info" field. + public const int RafInfoFieldNumber = 4; + private Bgs.Protocol.Account.V1.RAFInfo rafInfo_; + public Bgs.Protocol.Account.V1.RAFInfo RafInfo + { + get { return rafInfo_; } + set + { + rafInfo_ = value; + } + } + + public override bool Equals(object other) + { + return Equals(other as GameAccountState); + } + + public bool Equals(GameAccountState other) + { + if (ReferenceEquals(other, null)) + { + return false; + } + if (ReferenceEquals(other, this)) + { + return true; + } + if (!object.Equals(GameLevelInfo, other.GameLevelInfo)) return false; + if (!object.Equals(GameTimeInfo, other.GameTimeInfo)) return false; + if (!object.Equals(GameStatus, other.GameStatus)) return false; + if (!object.Equals(RafInfo, other.RafInfo)) return false; + return true; + } + + public override int GetHashCode() + { + int hash = 1; + if (gameLevelInfo_ != null) hash ^= GameLevelInfo.GetHashCode(); + if (gameTimeInfo_ != null) hash ^= GameTimeInfo.GetHashCode(); + if (gameStatus_ != null) hash ^= GameStatus.GetHashCode(); + if (rafInfo_ != null) hash ^= RafInfo.GetHashCode(); + return hash; + } + + public override string ToString() + { + return pb::JsonFormatter.ToDiagnosticString(this); + } + + public void WriteTo(pb::CodedOutputStream output) + { + if (gameLevelInfo_ != null) + { + output.WriteRawTag(10); + output.WriteMessage(GameLevelInfo); + } + if (gameTimeInfo_ != null) + { + output.WriteRawTag(18); + output.WriteMessage(GameTimeInfo); + } + if (gameStatus_ != null) + { + output.WriteRawTag(26); + output.WriteMessage(GameStatus); + } + if (rafInfo_ != null) + { + output.WriteRawTag(34); + output.WriteMessage(RafInfo); + } + } + + public int CalculateSize() + { + int size = 0; + if (gameLevelInfo_ != null) + { + size += 1 + pb::CodedOutputStream.ComputeMessageSize(GameLevelInfo); + } + if (gameTimeInfo_ != null) + { + size += 1 + pb::CodedOutputStream.ComputeMessageSize(GameTimeInfo); + } + if (gameStatus_ != null) + { + size += 1 + pb::CodedOutputStream.ComputeMessageSize(GameStatus); + } + if (rafInfo_ != null) + { + size += 1 + pb::CodedOutputStream.ComputeMessageSize(RafInfo); + } + return size; + } + + public void MergeFrom(GameAccountState other) + { + if (other == null) + { + return; + } + if (other.gameLevelInfo_ != null) + { + if (gameLevelInfo_ == null) + { + gameLevelInfo_ = new Bgs.Protocol.Account.V1.GameLevelInfo(); + } + GameLevelInfo.MergeFrom(other.GameLevelInfo); + } + if (other.gameTimeInfo_ != null) + { + if (gameTimeInfo_ == null) + { + gameTimeInfo_ = new Bgs.Protocol.Account.V1.GameTimeInfo(); + } + GameTimeInfo.MergeFrom(other.GameTimeInfo); + } + if (other.gameStatus_ != null) + { + if (gameStatus_ == null) + { + gameStatus_ = new Bgs.Protocol.Account.V1.GameStatus(); + } + GameStatus.MergeFrom(other.GameStatus); + } + if (other.rafInfo_ != null) + { + if (rafInfo_ == null) + { + rafInfo_ = new Bgs.Protocol.Account.V1.RAFInfo(); + } + RafInfo.MergeFrom(other.RafInfo); + } + } + + public void MergeFrom(pb::CodedInputStream input) + { + uint tag; + while ((tag = input.ReadTag()) != 0) + { + switch (tag) + { + default: + input.SkipLastField(); + break; + case 10: + { + if (gameLevelInfo_ == null) + { + gameLevelInfo_ = new Bgs.Protocol.Account.V1.GameLevelInfo(); + } + input.ReadMessage(gameLevelInfo_); + break; + } + case 18: + { + if (gameTimeInfo_ == null) + { + gameTimeInfo_ = new Bgs.Protocol.Account.V1.GameTimeInfo(); + } + input.ReadMessage(gameTimeInfo_); + break; + } + case 26: + { + if (gameStatus_ == null) + { + gameStatus_ = new Bgs.Protocol.Account.V1.GameStatus(); + } + input.ReadMessage(gameStatus_); + break; + } + case 34: + { + if (rafInfo_ == null) + { + rafInfo_ = new Bgs.Protocol.Account.V1.RAFInfo(); + } + input.ReadMessage(rafInfo_); + break; + } + } + } + } + + } + + [System.Diagnostics.DebuggerNonUserCodeAttribute()] + public sealed partial class GameAccountStateTagged : pb::IMessage + { + private static readonly pb::MessageParser _parser = new pb::MessageParser(() => new GameAccountStateTagged()); + public static pb::MessageParser Parser { get { return _parser; } } + + public static pbr::MessageDescriptor Descriptor + { + get { return Bgs.Protocol.Account.V1.AccountTypesReflection.Descriptor.MessageTypes[34]; } + } + + pbr::MessageDescriptor pb::IMessage.Descriptor + { + get { return Descriptor; } + } + + public GameAccountStateTagged() + { + OnConstruction(); + } + + partial void OnConstruction(); + + public GameAccountStateTagged(GameAccountStateTagged other) : this() + { + GameAccountState = other.gameAccountState_ != null ? other.GameAccountState.Clone() : null; + GameAccountTags = other.gameAccountTags_ != null ? other.GameAccountTags.Clone() : null; + } + + public GameAccountStateTagged Clone() + { + return new GameAccountStateTagged(this); + } + + /// Field number for the "game_account_state" field. + public const int GameAccountStateFieldNumber = 1; + private Bgs.Protocol.Account.V1.GameAccountState gameAccountState_; + public Bgs.Protocol.Account.V1.GameAccountState GameAccountState + { + get { return gameAccountState_; } + set + { + gameAccountState_ = value; + } + } + + /// Field number for the "game_account_tags" field. + public const int GameAccountTagsFieldNumber = 2; + private Bgs.Protocol.Account.V1.GameAccountFieldTags gameAccountTags_; + public Bgs.Protocol.Account.V1.GameAccountFieldTags GameAccountTags + { + get { return gameAccountTags_; } + set + { + gameAccountTags_ = value; + } + } + + public override bool Equals(object other) + { + return Equals(other as GameAccountStateTagged); + } + + public bool Equals(GameAccountStateTagged other) + { + if (ReferenceEquals(other, null)) + { + return false; + } + if (ReferenceEquals(other, this)) + { + return true; + } + if (!object.Equals(GameAccountState, other.GameAccountState)) return false; + if (!object.Equals(GameAccountTags, other.GameAccountTags)) return false; + return true; + } + + public override int GetHashCode() + { + int hash = 1; + if (gameAccountState_ != null) hash ^= GameAccountState.GetHashCode(); + if (gameAccountTags_ != null) hash ^= GameAccountTags.GetHashCode(); + return hash; + } + + public override string ToString() + { + return pb::JsonFormatter.ToDiagnosticString(this); + } + + public void WriteTo(pb::CodedOutputStream output) + { + if (gameAccountState_ != null) + { + output.WriteRawTag(10); + output.WriteMessage(GameAccountState); + } + if (gameAccountTags_ != null) + { + output.WriteRawTag(18); + output.WriteMessage(GameAccountTags); + } + } + + public int CalculateSize() + { + int size = 0; + if (gameAccountState_ != null) + { + size += 1 + pb::CodedOutputStream.ComputeMessageSize(GameAccountState); + } + if (gameAccountTags_ != null) + { + size += 1 + pb::CodedOutputStream.ComputeMessageSize(GameAccountTags); + } + return size; + } + + public void MergeFrom(GameAccountStateTagged other) + { + if (other == null) + { + return; + } + if (other.gameAccountState_ != null) + { + if (gameAccountState_ == null) + { + gameAccountState_ = new Bgs.Protocol.Account.V1.GameAccountState(); + } + GameAccountState.MergeFrom(other.GameAccountState); + } + if (other.gameAccountTags_ != null) + { + if (gameAccountTags_ == null) + { + gameAccountTags_ = new Bgs.Protocol.Account.V1.GameAccountFieldTags(); + } + GameAccountTags.MergeFrom(other.GameAccountTags); + } + } + + public void MergeFrom(pb::CodedInputStream input) + { + uint tag; + while ((tag = input.ReadTag()) != 0) + { + switch (tag) + { + default: + input.SkipLastField(); + break; + case 10: + { + if (gameAccountState_ == null) + { + gameAccountState_ = new Bgs.Protocol.Account.V1.GameAccountState(); + } + input.ReadMessage(gameAccountState_); + break; + } + case 18: + { + if (gameAccountTags_ == null) + { + gameAccountTags_ = new Bgs.Protocol.Account.V1.GameAccountFieldTags(); + } + input.ReadMessage(gameAccountTags_); + break; + } + } + } + } + + } + + [System.Diagnostics.DebuggerNonUserCodeAttribute()] + public sealed partial class AuthorizedData : pb::IMessage + { + private static readonly pb::MessageParser _parser = new pb::MessageParser(() => new AuthorizedData()); + public static pb::MessageParser Parser { get { return _parser; } } + + public static pbr::MessageDescriptor Descriptor + { + get { return Bgs.Protocol.Account.V1.AccountTypesReflection.Descriptor.MessageTypes[35]; } + } + + pbr::MessageDescriptor pb::IMessage.Descriptor + { + get { return Descriptor; } + } + + public AuthorizedData() + { + OnConstruction(); + } + + partial void OnConstruction(); + + public AuthorizedData(AuthorizedData other) : this() + { + data_ = other.data_; + license_ = other.license_.Clone(); + } + + public AuthorizedData Clone() + { + return new AuthorizedData(this); + } + + /// Field number for the "data" field. + public const int DataFieldNumber = 1; + private string data_ = ""; + public string Data + { + get { return data_; } + set + { + data_ = pb::ProtoPreconditions.CheckNotNull(value, "value"); + } + } + + /// Field number for the "license" field. + public const int LicenseFieldNumber = 2; + private static readonly pb::FieldCodec _repeated_license_codec + = pb::FieldCodec.ForUInt32(18); + private readonly pbc::RepeatedField license_ = new pbc::RepeatedField(); + public pbc::RepeatedField License + { + get { return license_; } + } + + public override bool Equals(object other) + { + return Equals(other as AuthorizedData); + } + + public bool Equals(AuthorizedData other) + { + if (ReferenceEquals(other, null)) + { + return false; + } + if (ReferenceEquals(other, this)) + { + return true; + } + if (Data != other.Data) return false; + if (!license_.Equals(other.license_)) return false; + return true; + } + + public override int GetHashCode() + { + int hash = 1; + if (Data.Length != 0) hash ^= Data.GetHashCode(); + hash ^= license_.GetHashCode(); + return hash; + } + + public override string ToString() + { + return pb::JsonFormatter.ToDiagnosticString(this); + } + + public void WriteTo(pb::CodedOutputStream output) + { + if (Data.Length != 0) + { + output.WriteRawTag(10); + output.WriteString(Data); + } + license_.WriteTo(output, _repeated_license_codec); + } + + public int CalculateSize() + { + int size = 0; + if (Data.Length != 0) + { + size += 1 + pb::CodedOutputStream.ComputeStringSize(Data); + } + size += license_.CalculateSize(_repeated_license_codec); + return size; + } + + public void MergeFrom(AuthorizedData other) + { + if (other == null) + { + return; + } + if (other.Data.Length != 0) + { + Data = other.Data; + } + license_.Add(other.license_); + } + + public void MergeFrom(pb::CodedInputStream input) + { + uint tag; + while ((tag = input.ReadTag()) != 0) + { + switch (tag) + { + default: + input.SkipLastField(); + break; + case 10: + { + Data = input.ReadString(); + break; + } + case 18: + case 16: + { + license_.AddEntriesFrom(input, _repeated_license_codec); + break; + } + } + } + } + + } + + #endregion +} + +#endregion Designer generated code diff --git a/Framework/Proto/AttributeTypes.cs b/Framework/Proto/AttributeTypes.cs new file mode 100644 index 000000000..7e54726d2 --- /dev/null +++ b/Framework/Proto/AttributeTypes.cs @@ -0,0 +1,794 @@ +// Generated by the protocol buffer compiler. DO NOT EDIT! +// source: bgs/low/pb/client/attribute_types.proto +#pragma warning disable 1591, 0612, 3021 +#region Designer generated code + +using pb = Google.Protobuf; +using pbc = Google.Protobuf.Collections; +using pbr = Google.Protobuf.Reflection; +namespace Bgs.Protocol +{ + + /// Holder for reflection information generated from bgs/low/pb/client/attribute_types.proto + [System.Diagnostics.DebuggerNonUserCodeAttribute()] + public static partial class AttributeTypesReflection + { + + #region Descriptor + /// File descriptor for bgs/low/pb/client/attribute_types.proto + public static pbr::FileDescriptor Descriptor + { + get { return descriptor; } + } + private static pbr::FileDescriptor descriptor; + + static AttributeTypesReflection() + { + byte[] descriptorData = System.Convert.FromBase64String( + string.Concat( + "CidiZ3MvbG93L3BiL2NsaWVudC9hdHRyaWJ1dGVfdHlwZXMucHJvdG8SDGJn", + "cy5wcm90b2NvbBokYmdzL2xvdy9wYi9jbGllbnQvZW50aXR5X3R5cGVzLnBy", + "b3RvIuEBCgdWYXJpYW50EhIKCmJvb2xfdmFsdWUYAiABKAgSEQoJaW50X3Zh", + "bHVlGAMgASgDEhMKC2Zsb2F0X3ZhbHVlGAQgASgBEhQKDHN0cmluZ192YWx1", + "ZRgFIAEoCRISCgpibG9iX3ZhbHVlGAYgASgMEhUKDW1lc3NhZ2VfdmFsdWUY", + "ByABKAwSFAoMZm91cmNjX3ZhbHVlGAggASgJEhIKCnVpbnRfdmFsdWUYCSAB", + "KAQSLwoPZW50aXR5X2lkX3ZhbHVlGAogASgLMhYuYmdzLnByb3RvY29sLkVu", + "dGl0eUlkIj8KCUF0dHJpYnV0ZRIMCgRuYW1lGAEgASgJEiQKBXZhbHVlGAIg", + "ASgLMhUuYmdzLnByb3RvY29sLlZhcmlhbnQiygEKD0F0dHJpYnV0ZUZpbHRl", + "chIzCgJvcBgBIAEoDjInLmJncy5wcm90b2NvbC5BdHRyaWJ1dGVGaWx0ZXIu", + "T3BlcmF0aW9uEioKCWF0dHJpYnV0ZRgCIAMoCzIXLmJncy5wcm90b2NvbC5B", + "dHRyaWJ1dGUiVgoJT3BlcmF0aW9uEg4KCk1BVENIX05PTkUQABINCglNQVRD", + "SF9BTlkQARINCglNQVRDSF9BTEwQAhIbChdNQVRDSF9BTExfTU9TVF9TUEVD", + "SUZJQxADQiEKDWJuZXQucHJvdG9jb2xCDkF0dHJpYnV0ZVByb3RvSAJiBnBy", + "b3RvMw==")); + descriptor = pbr::FileDescriptor.FromGeneratedCode(descriptorData, + new pbr::FileDescriptor[] { Bgs.Protocol.EntityTypesReflection.Descriptor, }, + new pbr::GeneratedClrTypeInfo(null, new pbr::GeneratedClrTypeInfo[] { + new pbr::GeneratedClrTypeInfo(typeof(Bgs.Protocol.Variant), Bgs.Protocol.Variant.Parser, new[]{ "BoolValue", "IntValue", "FloatValue", "StringValue", "BlobValue", "MessageValue", "FourccValue", "UintValue", "EntityIdValue" }, null, null, null), + new pbr::GeneratedClrTypeInfo(typeof(Bgs.Protocol.Attribute), Bgs.Protocol.Attribute.Parser, new[]{ "Name", "Value" }, null, null, null), + new pbr::GeneratedClrTypeInfo(typeof(Bgs.Protocol.AttributeFilter), Bgs.Protocol.AttributeFilter.Parser, new[]{ "Op", "Attribute" }, null, new[]{ typeof(Bgs.Protocol.AttributeFilter.Types.Operation) }, null) + })); + } + #endregion + + } + #region Messages + [System.Diagnostics.DebuggerNonUserCodeAttribute()] + public sealed partial class Variant : pb::IMessage + { + private static readonly pb::MessageParser _parser = new pb::MessageParser(() => new Variant()); + public static pb::MessageParser Parser { get { return _parser; } } + + public static pbr::MessageDescriptor Descriptor + { + get { return Bgs.Protocol.AttributeTypesReflection.Descriptor.MessageTypes[0]; } + } + + pbr::MessageDescriptor pb::IMessage.Descriptor + { + get { return Descriptor; } + } + + public Variant() + { + OnConstruction(); + } + + partial void OnConstruction(); + + public Variant(Variant other) : this() + { + boolValue_ = other.boolValue_; + intValue_ = other.intValue_; + floatValue_ = other.floatValue_; + stringValue_ = other.stringValue_; + blobValue_ = other.blobValue_; + messageValue_ = other.messageValue_; + fourccValue_ = other.fourccValue_; + uintValue_ = other.uintValue_; + EntityIdValue = other.entityIdValue_ != null ? other.EntityIdValue.Clone() : null; + } + + public Variant Clone() + { + return new Variant(this); + } + + /// Field number for the "bool_value" field. + public const int BoolValueFieldNumber = 2; + private bool boolValue_; + public bool BoolValue + { + get { return boolValue_; } + set + { + boolValue_ = value; + } + } + + /// Field number for the "int_value" field. + public const int IntValueFieldNumber = 3; + private long intValue_; + public long IntValue + { + get { return intValue_; } + set + { + intValue_ = value; + } + } + + /// Field number for the "float_value" field. + public const int FloatValueFieldNumber = 4; + private double floatValue_; + public double FloatValue + { + get { return floatValue_; } + set + { + floatValue_ = value; + } + } + + /// Field number for the "string_value" field. + public const int StringValueFieldNumber = 5; + private string stringValue_ = ""; + public string StringValue + { + get { return stringValue_; } + set + { + stringValue_ = pb::ProtoPreconditions.CheckNotNull(value, "value"); + } + } + + /// Field number for the "blob_value" field. + public const int BlobValueFieldNumber = 6; + private pb::ByteString blobValue_ = pb::ByteString.Empty; + public pb::ByteString BlobValue + { + get { return blobValue_; } + set + { + blobValue_ = pb::ProtoPreconditions.CheckNotNull(value, "value"); + } + } + + /// Field number for the "message_value" field. + public const int MessageValueFieldNumber = 7; + private pb::ByteString messageValue_ = pb::ByteString.Empty; + public pb::ByteString MessageValue + { + get { return messageValue_; } + set + { + messageValue_ = pb::ProtoPreconditions.CheckNotNull(value, "value"); + } + } + + /// Field number for the "fourcc_value" field. + public const int FourccValueFieldNumber = 8; + private string fourccValue_ = ""; + public string FourccValue + { + get { return fourccValue_; } + set + { + fourccValue_ = pb::ProtoPreconditions.CheckNotNull(value, "value"); + } + } + + /// Field number for the "uint_value" field. + public const int UintValueFieldNumber = 9; + private ulong uintValue_; + public ulong UintValue + { + get { return uintValue_; } + set + { + uintValue_ = value; + } + } + + /// Field number for the "entity_id_value" field. + public const int EntityIdValueFieldNumber = 10; + private Bgs.Protocol.EntityId entityIdValue_; + public Bgs.Protocol.EntityId EntityIdValue + { + get { return entityIdValue_; } + set + { + entityIdValue_ = value; + } + } + + public override bool Equals(object other) + { + return Equals(other as Variant); + } + + public bool Equals(Variant other) + { + if (ReferenceEquals(other, null)) + { + return false; + } + if (ReferenceEquals(other, this)) + { + return true; + } + if (BoolValue != other.BoolValue) return false; + if (IntValue != other.IntValue) return false; + if (FloatValue != other.FloatValue) return false; + if (StringValue != other.StringValue) return false; + if (BlobValue != other.BlobValue) return false; + if (MessageValue != other.MessageValue) return false; + if (FourccValue != other.FourccValue) return false; + if (UintValue != other.UintValue) return false; + if (!object.Equals(EntityIdValue, other.EntityIdValue)) return false; + return true; + } + + public override int GetHashCode() + { + int hash = 1; + if (BoolValue != false) hash ^= BoolValue.GetHashCode(); + if (IntValue != 0L) hash ^= IntValue.GetHashCode(); + if (FloatValue != 0D) hash ^= FloatValue.GetHashCode(); + if (StringValue.Length != 0) hash ^= StringValue.GetHashCode(); + if (BlobValue.Length != 0) hash ^= BlobValue.GetHashCode(); + if (MessageValue.Length != 0) hash ^= MessageValue.GetHashCode(); + if (FourccValue.Length != 0) hash ^= FourccValue.GetHashCode(); + if (UintValue != 0UL) hash ^= UintValue.GetHashCode(); + if (entityIdValue_ != null) hash ^= EntityIdValue.GetHashCode(); + return hash; + } + + public override string ToString() + { + return pb::JsonFormatter.ToDiagnosticString(this); + } + + public void WriteTo(pb::CodedOutputStream output) + { + if (BoolValue != false) + { + output.WriteRawTag(16); + output.WriteBool(BoolValue); + } + if (IntValue != 0L) + { + output.WriteRawTag(24); + output.WriteInt64(IntValue); + } + if (FloatValue != 0D) + { + output.WriteRawTag(33); + output.WriteDouble(FloatValue); + } + if (StringValue.Length != 0) + { + output.WriteRawTag(42); + output.WriteString(StringValue); + } + if (BlobValue.Length != 0) + { + output.WriteRawTag(50); + output.WriteBytes(BlobValue); + } + if (MessageValue.Length != 0) + { + output.WriteRawTag(58); + output.WriteBytes(MessageValue); + } + if (FourccValue.Length != 0) + { + output.WriteRawTag(66); + output.WriteString(FourccValue); + } + if (UintValue != 0UL) + { + output.WriteRawTag(72); + output.WriteUInt64(UintValue); + } + if (entityIdValue_ != null) + { + output.WriteRawTag(82); + output.WriteMessage(EntityIdValue); + } + } + + public int CalculateSize() + { + int size = 0; + if (BoolValue != false) + { + size += 1 + 1; + } + if (IntValue != 0L) + { + size += 1 + pb::CodedOutputStream.ComputeInt64Size(IntValue); + } + if (FloatValue != 0D) + { + size += 1 + 8; + } + if (StringValue.Length != 0) + { + size += 1 + pb::CodedOutputStream.ComputeStringSize(StringValue); + } + if (BlobValue.Length != 0) + { + size += 1 + pb::CodedOutputStream.ComputeBytesSize(BlobValue); + } + if (MessageValue.Length != 0) + { + size += 1 + pb::CodedOutputStream.ComputeBytesSize(MessageValue); + } + if (FourccValue.Length != 0) + { + size += 1 + pb::CodedOutputStream.ComputeStringSize(FourccValue); + } + if (UintValue != 0UL) + { + size += 1 + pb::CodedOutputStream.ComputeUInt64Size(UintValue); + } + if (entityIdValue_ != null) + { + size += 1 + pb::CodedOutputStream.ComputeMessageSize(EntityIdValue); + } + return size; + } + + public void MergeFrom(Variant other) + { + if (other == null) + { + return; + } + if (other.BoolValue != false) + { + BoolValue = other.BoolValue; + } + if (other.IntValue != 0L) + { + IntValue = other.IntValue; + } + if (other.FloatValue != 0D) + { + FloatValue = other.FloatValue; + } + if (other.StringValue.Length != 0) + { + StringValue = other.StringValue; + } + if (other.BlobValue.Length != 0) + { + BlobValue = other.BlobValue; + } + if (other.MessageValue.Length != 0) + { + MessageValue = other.MessageValue; + } + if (other.FourccValue.Length != 0) + { + FourccValue = other.FourccValue; + } + if (other.UintValue != 0UL) + { + UintValue = other.UintValue; + } + if (other.entityIdValue_ != null) + { + if (entityIdValue_ == null) + { + entityIdValue_ = new Bgs.Protocol.EntityId(); + } + EntityIdValue.MergeFrom(other.EntityIdValue); + } + } + + public void MergeFrom(pb::CodedInputStream input) + { + uint tag; + while ((tag = input.ReadTag()) != 0) + { + switch (tag) + { + default: + input.SkipLastField(); + break; + case 16: + { + BoolValue = input.ReadBool(); + break; + } + case 24: + { + IntValue = input.ReadInt64(); + break; + } + case 33: + { + FloatValue = input.ReadDouble(); + break; + } + case 42: + { + StringValue = input.ReadString(); + break; + } + case 50: + { + BlobValue = input.ReadBytes(); + break; + } + case 58: + { + MessageValue = input.ReadBytes(); + break; + } + case 66: + { + FourccValue = input.ReadString(); + break; + } + case 72: + { + UintValue = input.ReadUInt64(); + break; + } + case 82: + { + if (entityIdValue_ == null) + { + entityIdValue_ = new Bgs.Protocol.EntityId(); + } + input.ReadMessage(entityIdValue_); + break; + } + } + } + } + + } + + [System.Diagnostics.DebuggerNonUserCodeAttribute()] + public sealed partial class Attribute : pb::IMessage + { + private static readonly pb::MessageParser _parser = new pb::MessageParser(() => new Attribute()); + public static pb::MessageParser Parser { get { return _parser; } } + + public static pbr::MessageDescriptor Descriptor + { + get { return Bgs.Protocol.AttributeTypesReflection.Descriptor.MessageTypes[1]; } + } + + pbr::MessageDescriptor pb::IMessage.Descriptor + { + get { return Descriptor; } + } + + public Attribute() + { + OnConstruction(); + } + + partial void OnConstruction(); + + public Attribute(Attribute other) : this() + { + name_ = other.name_; + Value = other.value_ != null ? other.Value.Clone() : null; + } + + public Attribute Clone() + { + return new Attribute(this); + } + + /// Field number for the "name" field. + public const int NameFieldNumber = 1; + private string name_ = ""; + public string Name + { + get { return name_; } + set + { + name_ = pb::ProtoPreconditions.CheckNotNull(value, "value"); + } + } + + /// Field number for the "value" field. + public const int ValueFieldNumber = 2; + private Bgs.Protocol.Variant value_; + public Bgs.Protocol.Variant Value + { + get { return value_; } + set + { + value_ = value; + } + } + + public override bool Equals(object other) + { + return Equals(other as Attribute); + } + + public bool Equals(Attribute other) + { + if (ReferenceEquals(other, null)) + { + return false; + } + if (ReferenceEquals(other, this)) + { + return true; + } + if (Name != other.Name) return false; + if (!object.Equals(Value, other.Value)) return false; + return true; + } + + public override int GetHashCode() + { + int hash = 1; + if (Name.Length != 0) hash ^= Name.GetHashCode(); + if (value_ != null) hash ^= Value.GetHashCode(); + return hash; + } + + public override string ToString() + { + return pb::JsonFormatter.ToDiagnosticString(this); + } + + public void WriteTo(pb::CodedOutputStream output) + { + if (Name.Length != 0) + { + output.WriteRawTag(10); + output.WriteString(Name); + } + if (value_ != null) + { + output.WriteRawTag(18); + output.WriteMessage(Value); + } + } + + public int CalculateSize() + { + int size = 0; + if (Name.Length != 0) + { + size += 1 + pb::CodedOutputStream.ComputeStringSize(Name); + } + if (value_ != null) + { + size += 1 + pb::CodedOutputStream.ComputeMessageSize(Value); + } + return size; + } + + public void MergeFrom(Attribute other) + { + if (other == null) + { + return; + } + if (other.Name.Length != 0) + { + Name = other.Name; + } + if (other.value_ != null) + { + if (value_ == null) + { + value_ = new Bgs.Protocol.Variant(); + } + Value.MergeFrom(other.Value); + } + } + + public void MergeFrom(pb::CodedInputStream input) + { + uint tag; + while ((tag = input.ReadTag()) != 0) + { + switch (tag) + { + default: + input.SkipLastField(); + break; + case 10: + { + Name = input.ReadString(); + break; + } + case 18: + { + if (value_ == null) + { + value_ = new Bgs.Protocol.Variant(); + } + input.ReadMessage(value_); + break; + } + } + } + } + + } + + [System.Diagnostics.DebuggerNonUserCodeAttribute()] + public sealed partial class AttributeFilter : pb::IMessage + { + private static readonly pb::MessageParser _parser = new pb::MessageParser(() => new AttributeFilter()); + public static pb::MessageParser Parser { get { return _parser; } } + + public static pbr::MessageDescriptor Descriptor + { + get { return Bgs.Protocol.AttributeTypesReflection.Descriptor.MessageTypes[2]; } + } + + pbr::MessageDescriptor pb::IMessage.Descriptor + { + get { return Descriptor; } + } + + public AttributeFilter() + { + OnConstruction(); + } + + partial void OnConstruction(); + + public AttributeFilter(AttributeFilter other) : this() + { + op_ = other.op_; + attribute_ = other.attribute_.Clone(); + } + + public AttributeFilter Clone() + { + return new AttributeFilter(this); + } + + /// Field number for the "op" field. + public const int OpFieldNumber = 1; + private Bgs.Protocol.AttributeFilter.Types.Operation op_ = Bgs.Protocol.AttributeFilter.Types.Operation.MATCH_NONE; + public Bgs.Protocol.AttributeFilter.Types.Operation Op + { + get { return op_; } + set + { + op_ = value; + } + } + + /// Field number for the "attribute" field. + public const int AttributeFieldNumber = 2; + private static readonly pb::FieldCodec _repeated_attribute_codec + = pb::FieldCodec.ForMessage(18, Bgs.Protocol.Attribute.Parser); + private readonly pbc::RepeatedField attribute_ = new pbc::RepeatedField(); + public pbc::RepeatedField Attribute + { + get { return attribute_; } + } + + public override bool Equals(object other) + { + return Equals(other as AttributeFilter); + } + + public bool Equals(AttributeFilter other) + { + if (ReferenceEquals(other, null)) + { + return false; + } + if (ReferenceEquals(other, this)) + { + return true; + } + if (Op != other.Op) return false; + if (!attribute_.Equals(other.attribute_)) return false; + return true; + } + + public override int GetHashCode() + { + int hash = 1; + if (Op != Bgs.Protocol.AttributeFilter.Types.Operation.MATCH_NONE) hash ^= Op.GetHashCode(); + hash ^= attribute_.GetHashCode(); + return hash; + } + + public override string ToString() + { + return pb::JsonFormatter.ToDiagnosticString(this); + } + + public void WriteTo(pb::CodedOutputStream output) + { + if (Op != Bgs.Protocol.AttributeFilter.Types.Operation.MATCH_NONE) + { + output.WriteRawTag(8); + output.WriteEnum((int)Op); + } + attribute_.WriteTo(output, _repeated_attribute_codec); + } + + public int CalculateSize() + { + int size = 0; + if (Op != Bgs.Protocol.AttributeFilter.Types.Operation.MATCH_NONE) + { + size += 1 + pb::CodedOutputStream.ComputeEnumSize((int)Op); + } + size += attribute_.CalculateSize(_repeated_attribute_codec); + return size; + } + + public void MergeFrom(AttributeFilter other) + { + if (other == null) + { + return; + } + if (other.Op != Bgs.Protocol.AttributeFilter.Types.Operation.MATCH_NONE) + { + Op = other.Op; + } + attribute_.Add(other.attribute_); + } + + public void MergeFrom(pb::CodedInputStream input) + { + uint tag; + while ((tag = input.ReadTag()) != 0) + { + switch (tag) + { + default: + input.SkipLastField(); + break; + case 8: + { + op_ = (Bgs.Protocol.AttributeFilter.Types.Operation)input.ReadEnum(); + break; + } + case 18: + { + attribute_.AddEntriesFrom(input, _repeated_attribute_codec); + break; + } + } + } + } + + #region Nested types + /// Container for nested types declared in the AttributeFilter message type. + [System.Diagnostics.DebuggerNonUserCodeAttribute()] + public static partial class Types + { + public enum Operation + { + MATCH_NONE = 0, + MATCH_ANY = 1, + MATCH_ALL = 2, + MATCH_ALL_MOST_SPECIFIC = 3, + } + + } + #endregion + + } + + #endregion + +} + +#endregion Designer generated code diff --git a/Framework/Proto/AuthenticationService.cs b/Framework/Proto/AuthenticationService.cs new file mode 100644 index 000000000..78ad90450 --- /dev/null +++ b/Framework/Proto/AuthenticationService.cs @@ -0,0 +1,4023 @@ +// Generated by the protocol buffer compiler. DO NOT EDIT! +// source: bgs/low/pb/client/authentication_service.proto +#pragma warning disable 1591, 0612, 3021 +#region Designer generated code + +using pb = Google.Protobuf; +using pbc = Google.Protobuf.Collections; +using pbr = Google.Protobuf.Reflection; +namespace Bgs.Protocol.Authentication.V1 +{ + /// Holder for reflection information generated from bgs/low/pb/client/authentication_service.proto + [System.Diagnostics.DebuggerNonUserCodeAttribute()] + public static partial class AuthenticationServiceReflection + { + + #region Descriptor + /// File descriptor for bgs/low/pb/client/authentication_service.proto + public static pbr::FileDescriptor Descriptor + { + get { return descriptor; } + } + private static pbr::FileDescriptor descriptor; + + static AuthenticationServiceReflection() + { + byte[] descriptorData = System.Convert.FromBase64String( + string.Concat( + "Ci5iZ3MvbG93L3BiL2NsaWVudC9hdXRoZW50aWNhdGlvbl9zZXJ2aWNlLnBy", + "b3RvEh5iZ3MucHJvdG9jb2wuYXV0aGVudGljYXRpb24udjEaJWJncy9sb3cv", + "cGIvY2xpZW50L2FjY291bnRfdHlwZXMucHJvdG8aLGJncy9sb3cvcGIvY2xp", + "ZW50L2NvbnRlbnRfaGFuZGxlX3R5cGVzLnByb3RvGiRiZ3MvbG93L3BiL2Ns", + "aWVudC9lbnRpdHlfdHlwZXMucHJvdG8aIWJncy9sb3cvcGIvY2xpZW50L3Jw", + "Y190eXBlcy5wcm90byJYChFNb2R1bGVMb2FkUmVxdWVzdBIyCg1tb2R1bGVf", + "aGFuZGxlGAEgASgLMhsuYmdzLnByb3RvY29sLkNvbnRlbnRIYW5kbGUSDwoH", + "bWVzc2FnZRgCIAEoDCI3ChJNb2R1bGVOb3RpZmljYXRpb24SEQoJbW9kdWxl", + "X2lkGAIgASgFEg4KBnJlc3VsdBgDIAEoDSI6ChRNb2R1bGVNZXNzYWdlUmVx", + "dWVzdBIRCgltb2R1bGVfaWQYASABKAUSDwoHbWVzc2FnZRgCIAEoDCLfAgoM", + "TG9nb25SZXF1ZXN0Eg8KB3Byb2dyYW0YASABKAkSEAoIcGxhdGZvcm0YAiAB", + "KAkSDgoGbG9jYWxlGAMgASgJEg0KBWVtYWlsGAQgASgJEg8KB3ZlcnNpb24Y", + "BSABKAkSGwoTYXBwbGljYXRpb25fdmVyc2lvbhgGIAEoBRIXCg9wdWJsaWNf", + "Y29tcHV0ZXIYByABKAgSDgoGc3NvX2lkGAggASgMEiEKGWRpc2Nvbm5lY3Rf", + "b25fY29va2llX2ZhaWwYCSABKAgSJwofYWxsb3dfbG9nb25fcXVldWVfbm90", + "aWZpY2F0aW9ucxgKIAEoCBIfChd3ZWJfY2xpZW50X3ZlcmlmaWNhdGlvbhgL", + "IAEoCBIeChZjYWNoZWRfd2ViX2NyZWRlbnRpYWxzGAwgASgMEhUKDWVuYWJs", + "ZV9jb29raWUYDSABKAgSEgoKdXNlcl9hZ2VudBgOIAEoCSKaAgoLTG9nb25S", + "ZXN1bHQSEgoKZXJyb3JfY29kZRgBIAEoDRIqCgphY2NvdW50X2lkGAIgASgL", + "MhYuYmdzLnByb3RvY29sLkVudGl0eUlkEi8KD2dhbWVfYWNjb3VudF9pZBgD", + "IAMoCzIWLmJncy5wcm90b2NvbC5FbnRpdHlJZBINCgVlbWFpbBgEIAEoCRIY", + "ChBhdmFpbGFibGVfcmVnaW9uGAUgAygNEhgKEGNvbm5lY3RlZF9yZWdpb24Y", + "BiABKA0SEgoKYmF0dGxlX3RhZxgHIAEoCRIVCg1nZW9pcF9jb3VudHJ5GAgg", + "ASgJEhMKC3Nlc3Npb25fa2V5GAkgASgMEhcKD3Jlc3RyaWN0ZWRfbW9kZRgK", + "IAEoCCIqChdHZW5lcmF0ZVNTT1Rva2VuUmVxdWVzdBIPCgdwcm9ncmFtGAEg", + "ASgHIj4KGEdlbmVyYXRlU1NPVG9rZW5SZXNwb25zZRIOCgZzc29faWQYASAB", + "KAwSEgoKc3NvX3NlY3JldBgCIAEoDCIoChJMb2dvblVwZGF0ZVJlcXVlc3QS", + "EgoKZXJyb3JfY29kZRgBIAEoDSJhChdMb2dvblF1ZXVlVXBkYXRlUmVxdWVz", + "dBIQCghwb3NpdGlvbhgBIAEoDRIWCg5lc3RpbWF0ZWRfdGltZRgCIAEoBBIc", + "ChRldGFfZGV2aWF0aW9uX2luX3NlYxgDIAEoBCK+AQobQWNjb3VudFNldHRp", + "bmdzTm90aWZpY2F0aW9uEjkKCGxpY2Vuc2VzGAEgAygLMicuYmdzLnByb3Rv", + "Y29sLmFjY291bnQudjEuQWNjb3VudExpY2Vuc2USFAoMaXNfdXNpbmdfcmlk", + "GAIgASgIEhsKE2lzX3BsYXlpbmdfZnJvbV9pZ3IYAyABKAgSGQoRY2FuX3Jl", + "Y2VpdmVfdm9pY2UYBCABKAgSFgoOY2FuX3NlbmRfdm9pY2UYBSABKAgiPQoY", + "U2VydmVyU3RhdGVDaGFuZ2VSZXF1ZXN0Eg0KBXN0YXRlGAEgASgNEhIKCmV2", + "ZW50X3RpbWUYAiABKAQiVAoLVmVyc2lvbkluZm8SDgoGbnVtYmVyGAEgASgN", + "Eg0KBXBhdGNoGAIgASgJEhMKC2lzX29wdGlvbmFsGAMgASgIEhEKCWtpY2tf", + "dGltZRgEIAEoBCJcChdWZXJzaW9uSW5mb05vdGlmaWNhdGlvbhJBCgx2ZXJz", + "aW9uX2luZm8YASABKAsyKy5iZ3MucHJvdG9jb2wuYXV0aGVudGljYXRpb24u", + "djEuVmVyc2lvbkluZm8iXwoUTWVtTW9kdWxlTG9hZFJlcXVlc3QSKwoGaGFu", + "ZGxlGAEgASgLMhsuYmdzLnByb3RvY29sLkNvbnRlbnRIYW5kbGUSCwoDa2V5", + "GAIgASgMEg0KBWlucHV0GAMgASgMIiUKFU1lbU1vZHVsZUxvYWRSZXNwb25z", + "ZRIMCgRkYXRhGAEgASgMIksKGFNlbGVjdEdhbWVBY2NvdW50UmVxdWVzdBIv", + "Cg9nYW1lX2FjY291bnRfaWQYASABKAsyFi5iZ3MucHJvdG9jb2wuRW50aXR5", + "SWQiXQoaR2FtZUFjY291bnRTZWxlY3RlZFJlcXVlc3QSDgoGcmVzdWx0GAEg", + "ASgNEi8KD2dhbWVfYWNjb3VudF9pZBgCIAEoCzIWLmJncy5wcm90b2NvbC5F", + "bnRpdHlJZCIwCh1HZW5lcmF0ZVdlYkNyZWRlbnRpYWxzUmVxdWVzdBIPCgdw", + "cm9ncmFtGAEgASgHIjkKHkdlbmVyYXRlV2ViQ3JlZGVudGlhbHNSZXNwb25z", + "ZRIXCg93ZWJfY3JlZGVudGlhbHMYASABKAwiNgobVmVyaWZ5V2ViQ3JlZGVu", + "dGlhbHNSZXF1ZXN0EhcKD3dlYl9jcmVkZW50aWFscxgBIAEoDDKVCAoWQXV0", + "aGVudGljYXRpb25MaXN0ZW5lchJhCgxPbk1vZHVsZUxvYWQSMS5iZ3MucHJv", + "dG9jb2wuYXV0aGVudGljYXRpb24udjEuTW9kdWxlTG9hZFJlcXVlc3QaGS5i", + "Z3MucHJvdG9jb2wuTk9fUkVTUE9OU0UiA4gCARJiCg9Pbk1vZHVsZU1lc3Nh", + "Z2USNC5iZ3MucHJvdG9jb2wuYXV0aGVudGljYXRpb24udjEuTW9kdWxlTWVz", + "c2FnZVJlcXVlc3QaFC5iZ3MucHJvdG9jb2wuTm9EYXRhIgOIAgESagoTT25T", + "ZXJ2ZXJTdGF0ZUNoYW5nZRI4LmJncy5wcm90b2NvbC5hdXRoZW50aWNhdGlv", + "bi52MS5TZXJ2ZXJTdGF0ZUNoYW5nZVJlcXVlc3QaGS5iZ3MucHJvdG9jb2wu", + "Tk9fUkVTUE9OU0USWQoPT25Mb2dvbkNvbXBsZXRlEisuYmdzLnByb3RvY29s", + "LmF1dGhlbnRpY2F0aW9uLnYxLkxvZ29uUmVzdWx0GhkuYmdzLnByb3RvY29s", + "Lk5PX1JFU1BPTlNFEn4KD09uTWVtTW9kdWxlTG9hZBI0LmJncy5wcm90b2Nv", + "bC5hdXRoZW50aWNhdGlvbi52MS5NZW1Nb2R1bGVMb2FkUmVxdWVzdBo1LmJn", + "cy5wcm90b2NvbC5hdXRoZW50aWNhdGlvbi52MS5NZW1Nb2R1bGVMb2FkUmVz", + "cG9uc2USXgoNT25Mb2dvblVwZGF0ZRIyLmJncy5wcm90b2NvbC5hdXRoZW50", + "aWNhdGlvbi52MS5Mb2dvblVwZGF0ZVJlcXVlc3QaGS5iZ3MucHJvdG9jb2wu", + "Tk9fUkVTUE9OU0USagoUT25WZXJzaW9uSW5mb1VwZGF0ZWQSNy5iZ3MucHJv", + "dG9jb2wuYXV0aGVudGljYXRpb24udjEuVmVyc2lvbkluZm9Ob3RpZmljYXRp", + "b24aGS5iZ3MucHJvdG9jb2wuTk9fUkVTUE9OU0USaAoST25Mb2dvblF1ZXVl", + "VXBkYXRlEjcuYmdzLnByb3RvY29sLmF1dGhlbnRpY2F0aW9uLnYxLkxvZ29u", + "UXVldWVVcGRhdGVSZXF1ZXN0GhkuYmdzLnByb3RvY29sLk5PX1JFU1BPTlNF", + "EkIKD09uTG9nb25RdWV1ZUVuZBIULmJncy5wcm90b2NvbC5Ob0RhdGEaGS5i", + "Z3MucHJvdG9jb2wuTk9fUkVTUE9OU0UScwoVT25HYW1lQWNjb3VudFNlbGVj", + "dGVkEjouYmdzLnByb3RvY29sLmF1dGhlbnRpY2F0aW9uLnYxLkdhbWVBY2Nv", + "dW50U2VsZWN0ZWRSZXF1ZXN0GhkuYmdzLnByb3RvY29sLk5PX1JFU1BPTlNF", + "IgOIAgEy7wYKFUF1dGhlbnRpY2F0aW9uU2VydmljZRJLCgVMb2dvbhIsLmJn", + "cy5wcm90b2NvbC5hdXRoZW50aWNhdGlvbi52MS5Mb2dvblJlcXVlc3QaFC5i", + "Z3MucHJvdG9jb2wuTm9EYXRhEl0KDE1vZHVsZU5vdGlmeRIyLmJncy5wcm90", + "b2NvbC5hdXRoZW50aWNhdGlvbi52MS5Nb2R1bGVOb3RpZmljYXRpb24aFC5i", + "Z3MucHJvdG9jb2wuTm9EYXRhIgOIAgESYAoNTW9kdWxlTWVzc2FnZRI0LmJn", + "cy5wcm90b2NvbC5hdXRoZW50aWNhdGlvbi52MS5Nb2R1bGVNZXNzYWdlUmVx", + "dWVzdBoULmJncy5wcm90b2NvbC5Ob0RhdGEiA4gCARJRChxTZWxlY3RHYW1l", + "QWNjb3VudF9ERVBSRUNBVEVEEhYuYmdzLnByb3RvY29sLkVudGl0eUlkGhQu", + "YmdzLnByb3RvY29sLk5vRGF0YSIDiAIBEoUBChBHZW5lcmF0ZVNTT1Rva2Vu", + "EjcuYmdzLnByb3RvY29sLmF1dGhlbnRpY2F0aW9uLnYxLkdlbmVyYXRlU1NP", + "VG9rZW5SZXF1ZXN0GjguYmdzLnByb3RvY29sLmF1dGhlbnRpY2F0aW9uLnYx", + "LkdlbmVyYXRlU1NPVG9rZW5SZXNwb25zZRJoChFTZWxlY3RHYW1lQWNjb3Vu", + "dBI4LmJncy5wcm90b2NvbC5hdXRoZW50aWNhdGlvbi52MS5TZWxlY3RHYW1l", + "QWNjb3VudFJlcXVlc3QaFC5iZ3MucHJvdG9jb2wuTm9EYXRhIgOIAgESaQoU", + "VmVyaWZ5V2ViQ3JlZGVudGlhbHMSOy5iZ3MucHJvdG9jb2wuYXV0aGVudGlj", + "YXRpb24udjEuVmVyaWZ5V2ViQ3JlZGVudGlhbHNSZXF1ZXN0GhQuYmdzLnBy", + "b3RvY29sLk5vRGF0YRKXAQoWR2VuZXJhdGVXZWJDcmVkZW50aWFscxI9LmJn", + "cy5wcm90b2NvbC5hdXRoZW50aWNhdGlvbi52MS5HZW5lcmF0ZVdlYkNyZWRl", + "bnRpYWxzUmVxdWVzdBo+LmJncy5wcm90b2NvbC5hdXRoZW50aWNhdGlvbi52", + "MS5HZW5lcmF0ZVdlYkNyZWRlbnRpYWxzUmVzcG9uc2VCBUgCgAEAYgZwcm90", + "bzM=")); + descriptor = pbr::FileDescriptor.FromGeneratedCode(descriptorData, + new pbr::FileDescriptor[] { Bgs.Protocol.Account.V1.AccountTypesReflection.Descriptor, Bgs.Protocol.ContentHandleTypesReflection.Descriptor, Bgs.Protocol.EntityTypesReflection.Descriptor, Bgs.Protocol.RpcTypesReflection.Descriptor, }, + new pbr::GeneratedClrTypeInfo(null, new pbr::GeneratedClrTypeInfo[] { + new pbr::GeneratedClrTypeInfo(typeof(Bgs.Protocol.Authentication.V1.ModuleLoadRequest), Bgs.Protocol.Authentication.V1.ModuleLoadRequest.Parser, new[]{ "ModuleHandle", "Message" }, null, null, null), + new pbr::GeneratedClrTypeInfo(typeof(Bgs.Protocol.Authentication.V1.ModuleNotification), Bgs.Protocol.Authentication.V1.ModuleNotification.Parser, new[]{ "ModuleId", "Result" }, null, null, null), + new pbr::GeneratedClrTypeInfo(typeof(Bgs.Protocol.Authentication.V1.ModuleMessageRequest), Bgs.Protocol.Authentication.V1.ModuleMessageRequest.Parser, new[]{ "ModuleId", "Message" }, null, null, null), + new pbr::GeneratedClrTypeInfo(typeof(Bgs.Protocol.Authentication.V1.LogonRequest), Bgs.Protocol.Authentication.V1.LogonRequest.Parser, new[]{ "Program", "Platform", "Locale", "Email", "Version", "ApplicationVersion", "PublicComputer", "SsoId", "DisconnectOnCookieFail", "AllowLogonQueueNotifications", "WebClientVerification", "CachedWebCredentials", "EnableCookie", "UserAgent" }, null, null, null), + new pbr::GeneratedClrTypeInfo(typeof(Bgs.Protocol.Authentication.V1.LogonResult), Bgs.Protocol.Authentication.V1.LogonResult.Parser, new[]{ "ErrorCode", "AccountId", "GameAccountId", "Email", "AvailableRegion", "ConnectedRegion", "BattleTag", "GeoipCountry", "SessionKey", "RestrictedMode" }, null, null, null), + new pbr::GeneratedClrTypeInfo(typeof(Bgs.Protocol.Authentication.V1.GenerateSSOTokenRequest), Bgs.Protocol.Authentication.V1.GenerateSSOTokenRequest.Parser, new[]{ "Program" }, null, null, null), + new pbr::GeneratedClrTypeInfo(typeof(Bgs.Protocol.Authentication.V1.GenerateSSOTokenResponse), Bgs.Protocol.Authentication.V1.GenerateSSOTokenResponse.Parser, new[]{ "SsoId", "SsoSecret" }, null, null, null), + new pbr::GeneratedClrTypeInfo(typeof(Bgs.Protocol.Authentication.V1.LogonUpdateRequest), Bgs.Protocol.Authentication.V1.LogonUpdateRequest.Parser, new[]{ "ErrorCode" }, null, null, null), + new pbr::GeneratedClrTypeInfo(typeof(Bgs.Protocol.Authentication.V1.LogonQueueUpdateRequest), Bgs.Protocol.Authentication.V1.LogonQueueUpdateRequest.Parser, new[]{ "Position", "EstimatedTime", "EtaDeviationInSec" }, null, null, null), + new pbr::GeneratedClrTypeInfo(typeof(Bgs.Protocol.Authentication.V1.AccountSettingsNotification), Bgs.Protocol.Authentication.V1.AccountSettingsNotification.Parser, new[]{ "Licenses", "IsUsingRid", "IsPlayingFromIgr", "CanReceiveVoice", "CanSendVoice" }, null, null, null), + new pbr::GeneratedClrTypeInfo(typeof(Bgs.Protocol.Authentication.V1.ServerStateChangeRequest), Bgs.Protocol.Authentication.V1.ServerStateChangeRequest.Parser, new[]{ "State", "EventTime" }, null, null, null), + new pbr::GeneratedClrTypeInfo(typeof(Bgs.Protocol.Authentication.V1.VersionInfo), Bgs.Protocol.Authentication.V1.VersionInfo.Parser, new[]{ "Number", "Patch", "IsOptional", "KickTime" }, null, null, null), + new pbr::GeneratedClrTypeInfo(typeof(Bgs.Protocol.Authentication.V1.VersionInfoNotification), Bgs.Protocol.Authentication.V1.VersionInfoNotification.Parser, new[]{ "VersionInfo" }, null, null, null), + new pbr::GeneratedClrTypeInfo(typeof(Bgs.Protocol.Authentication.V1.MemModuleLoadRequest), Bgs.Protocol.Authentication.V1.MemModuleLoadRequest.Parser, new[]{ "Handle", "Key", "Input" }, null, null, null), + new pbr::GeneratedClrTypeInfo(typeof(Bgs.Protocol.Authentication.V1.MemModuleLoadResponse), Bgs.Protocol.Authentication.V1.MemModuleLoadResponse.Parser, new[]{ "Data" }, null, null, null), + new pbr::GeneratedClrTypeInfo(typeof(Bgs.Protocol.Authentication.V1.SelectGameAccountRequest), Bgs.Protocol.Authentication.V1.SelectGameAccountRequest.Parser, new[]{ "GameAccountId" }, null, null, null), + new pbr::GeneratedClrTypeInfo(typeof(Bgs.Protocol.Authentication.V1.GameAccountSelectedRequest), Bgs.Protocol.Authentication.V1.GameAccountSelectedRequest.Parser, new[]{ "Result", "GameAccountId" }, null, null, null), + new pbr::GeneratedClrTypeInfo(typeof(Bgs.Protocol.Authentication.V1.GenerateWebCredentialsRequest), Bgs.Protocol.Authentication.V1.GenerateWebCredentialsRequest.Parser, new[]{ "Program" }, null, null, null), + new pbr::GeneratedClrTypeInfo(typeof(Bgs.Protocol.Authentication.V1.GenerateWebCredentialsResponse), Bgs.Protocol.Authentication.V1.GenerateWebCredentialsResponse.Parser, new[]{ "WebCredentials" }, null, null, null), + new pbr::GeneratedClrTypeInfo(typeof(Bgs.Protocol.Authentication.V1.VerifyWebCredentialsRequest), Bgs.Protocol.Authentication.V1.VerifyWebCredentialsRequest.Parser, new[]{ "WebCredentials" }, null, null, null) + })); + } + #endregion + + } + #region Messages + [System.Diagnostics.DebuggerNonUserCodeAttribute()] + public sealed partial class ModuleLoadRequest : pb::IMessage + { + private static readonly pb::MessageParser _parser = new pb::MessageParser(() => new ModuleLoadRequest()); + public static pb::MessageParser Parser { get { return _parser; } } + + public static pbr::MessageDescriptor Descriptor + { + get { return Bgs.Protocol.Authentication.V1.AuthenticationServiceReflection.Descriptor.MessageTypes[0]; } + } + + pbr::MessageDescriptor pb::IMessage.Descriptor + { + get { return Descriptor; } + } + + public ModuleLoadRequest() + { + OnConstruction(); + } + + partial void OnConstruction(); + + public ModuleLoadRequest(ModuleLoadRequest other) : this() + { + ModuleHandle = other.moduleHandle_ != null ? other.ModuleHandle.Clone() : null; + message_ = other.message_; + } + + public ModuleLoadRequest Clone() + { + return new ModuleLoadRequest(this); + } + + /// Field number for the "module_handle" field. + public const int ModuleHandleFieldNumber = 1; + private Bgs.Protocol.ContentHandle moduleHandle_; + public Bgs.Protocol.ContentHandle ModuleHandle + { + get { return moduleHandle_; } + set + { + moduleHandle_ = value; + } + } + + /// Field number for the "message" field. + public const int MessageFieldNumber = 2; + private pb::ByteString message_ = pb::ByteString.Empty; + public pb::ByteString Message + { + get { return message_; } + set + { + message_ = pb::ProtoPreconditions.CheckNotNull(value, "value"); + } + } + + public override bool Equals(object other) + { + return Equals(other as ModuleLoadRequest); + } + + public bool Equals(ModuleLoadRequest other) + { + if (ReferenceEquals(other, null)) + { + return false; + } + if (ReferenceEquals(other, this)) + { + return true; + } + if (!object.Equals(ModuleHandle, other.ModuleHandle)) return false; + if (Message != other.Message) return false; + return true; + } + + public override int GetHashCode() + { + int hash = 1; + if (moduleHandle_ != null) hash ^= ModuleHandle.GetHashCode(); + if (Message.Length != 0) hash ^= Message.GetHashCode(); + return hash; + } + + public override string ToString() + { + return pb::JsonFormatter.ToDiagnosticString(this); + } + + public void WriteTo(pb::CodedOutputStream output) + { + if (moduleHandle_ != null) + { + output.WriteRawTag(10); + output.WriteMessage(ModuleHandle); + } + if (Message.Length != 0) + { + output.WriteRawTag(18); + output.WriteBytes(Message); + } + } + + public int CalculateSize() + { + int size = 0; + if (moduleHandle_ != null) + { + size += 1 + pb::CodedOutputStream.ComputeMessageSize(ModuleHandle); + } + if (Message.Length != 0) + { + size += 1 + pb::CodedOutputStream.ComputeBytesSize(Message); + } + return size; + } + + public void MergeFrom(ModuleLoadRequest other) + { + if (other == null) + { + return; + } + if (other.moduleHandle_ != null) + { + if (moduleHandle_ == null) + { + moduleHandle_ = new Bgs.Protocol.ContentHandle(); + } + ModuleHandle.MergeFrom(other.ModuleHandle); + } + if (other.Message.Length != 0) + { + Message = other.Message; + } + } + + public void MergeFrom(pb::CodedInputStream input) + { + uint tag; + while ((tag = input.ReadTag()) != 0) + { + switch (tag) + { + default: + input.SkipLastField(); + break; + case 10: + { + if (moduleHandle_ == null) + { + moduleHandle_ = new Bgs.Protocol.ContentHandle(); + } + input.ReadMessage(moduleHandle_); + break; + } + case 18: + { + Message = input.ReadBytes(); + break; + } + } + } + } + + } + + [System.Diagnostics.DebuggerNonUserCodeAttribute()] + public sealed partial class ModuleNotification : pb::IMessage + { + private static readonly pb::MessageParser _parser = new pb::MessageParser(() => new ModuleNotification()); + public static pb::MessageParser Parser { get { return _parser; } } + + public static pbr::MessageDescriptor Descriptor + { + get { return Bgs.Protocol.Authentication.V1.AuthenticationServiceReflection.Descriptor.MessageTypes[1]; } + } + + pbr::MessageDescriptor pb::IMessage.Descriptor + { + get { return Descriptor; } + } + + public ModuleNotification() + { + OnConstruction(); + } + + partial void OnConstruction(); + + public ModuleNotification(ModuleNotification other) : this() + { + moduleId_ = other.moduleId_; + result_ = other.result_; + } + + public ModuleNotification Clone() + { + return new ModuleNotification(this); + } + + /// Field number for the "module_id" field. + public const int ModuleIdFieldNumber = 2; + private int moduleId_; + public int ModuleId + { + get { return moduleId_; } + set + { + moduleId_ = value; + } + } + + /// Field number for the "result" field. + public const int ResultFieldNumber = 3; + private uint result_; + public uint Result + { + get { return result_; } + set + { + result_ = value; + } + } + + public override bool Equals(object other) + { + return Equals(other as ModuleNotification); + } + + public bool Equals(ModuleNotification other) + { + if (ReferenceEquals(other, null)) + { + return false; + } + if (ReferenceEquals(other, this)) + { + return true; + } + if (ModuleId != other.ModuleId) return false; + if (Result != other.Result) return false; + return true; + } + + public override int GetHashCode() + { + int hash = 1; + if (ModuleId != 0) hash ^= ModuleId.GetHashCode(); + if (Result != 0) hash ^= Result.GetHashCode(); + return hash; + } + + public override string ToString() + { + return pb::JsonFormatter.ToDiagnosticString(this); + } + + public void WriteTo(pb::CodedOutputStream output) + { + if (ModuleId != 0) + { + output.WriteRawTag(16); + output.WriteInt32(ModuleId); + } + if (Result != 0) + { + output.WriteRawTag(24); + output.WriteUInt32(Result); + } + } + + public int CalculateSize() + { + int size = 0; + if (ModuleId != 0) + { + size += 1 + pb::CodedOutputStream.ComputeInt32Size(ModuleId); + } + if (Result != 0) + { + size += 1 + pb::CodedOutputStream.ComputeUInt32Size(Result); + } + return size; + } + + public void MergeFrom(ModuleNotification other) + { + if (other == null) + { + return; + } + if (other.ModuleId != 0) + { + ModuleId = other.ModuleId; + } + if (other.Result != 0) + { + Result = other.Result; + } + } + + public void MergeFrom(pb::CodedInputStream input) + { + uint tag; + while ((tag = input.ReadTag()) != 0) + { + switch (tag) + { + default: + input.SkipLastField(); + break; + case 16: + { + ModuleId = input.ReadInt32(); + break; + } + case 24: + { + Result = input.ReadUInt32(); + break; + } + } + } + } + + } + + [System.Diagnostics.DebuggerNonUserCodeAttribute()] + public sealed partial class ModuleMessageRequest : pb::IMessage + { + private static readonly pb::MessageParser _parser = new pb::MessageParser(() => new ModuleMessageRequest()); + public static pb::MessageParser Parser { get { return _parser; } } + + public static pbr::MessageDescriptor Descriptor + { + get { return Bgs.Protocol.Authentication.V1.AuthenticationServiceReflection.Descriptor.MessageTypes[2]; } + } + + pbr::MessageDescriptor pb::IMessage.Descriptor + { + get { return Descriptor; } + } + + public ModuleMessageRequest() + { + OnConstruction(); + } + + partial void OnConstruction(); + + public ModuleMessageRequest(ModuleMessageRequest other) : this() + { + moduleId_ = other.moduleId_; + message_ = other.message_; + } + + public ModuleMessageRequest Clone() + { + return new ModuleMessageRequest(this); + } + + /// Field number for the "module_id" field. + public const int ModuleIdFieldNumber = 1; + private int moduleId_; + public int ModuleId + { + get { return moduleId_; } + set + { + moduleId_ = value; + } + } + + /// Field number for the "message" field. + public const int MessageFieldNumber = 2; + private pb::ByteString message_ = pb::ByteString.Empty; + public pb::ByteString Message + { + get { return message_; } + set + { + message_ = pb::ProtoPreconditions.CheckNotNull(value, "value"); + } + } + + public override bool Equals(object other) + { + return Equals(other as ModuleMessageRequest); + } + + public bool Equals(ModuleMessageRequest other) + { + if (ReferenceEquals(other, null)) + { + return false; + } + if (ReferenceEquals(other, this)) + { + return true; + } + if (ModuleId != other.ModuleId) return false; + if (Message != other.Message) return false; + return true; + } + + public override int GetHashCode() + { + int hash = 1; + if (ModuleId != 0) hash ^= ModuleId.GetHashCode(); + if (Message.Length != 0) hash ^= Message.GetHashCode(); + return hash; + } + + public override string ToString() + { + return pb::JsonFormatter.ToDiagnosticString(this); + } + + public void WriteTo(pb::CodedOutputStream output) + { + if (ModuleId != 0) + { + output.WriteRawTag(8); + output.WriteInt32(ModuleId); + } + if (Message.Length != 0) + { + output.WriteRawTag(18); + output.WriteBytes(Message); + } + } + + public int CalculateSize() + { + int size = 0; + if (ModuleId != 0) + { + size += 1 + pb::CodedOutputStream.ComputeInt32Size(ModuleId); + } + if (Message.Length != 0) + { + size += 1 + pb::CodedOutputStream.ComputeBytesSize(Message); + } + return size; + } + + public void MergeFrom(ModuleMessageRequest other) + { + if (other == null) + { + return; + } + if (other.ModuleId != 0) + { + ModuleId = other.ModuleId; + } + if (other.Message.Length != 0) + { + Message = other.Message; + } + } + + public void MergeFrom(pb::CodedInputStream input) + { + uint tag; + while ((tag = input.ReadTag()) != 0) + { + switch (tag) + { + default: + input.SkipLastField(); + break; + case 8: + { + ModuleId = input.ReadInt32(); + break; + } + case 18: + { + Message = input.ReadBytes(); + break; + } + } + } + } + + } + + [System.Diagnostics.DebuggerNonUserCodeAttribute()] + public sealed partial class LogonRequest : pb::IMessage + { + private static readonly pb::MessageParser _parser = new pb::MessageParser(() => new LogonRequest()); + public static pb::MessageParser Parser { get { return _parser; } } + + public static pbr::MessageDescriptor Descriptor + { + get { return Bgs.Protocol.Authentication.V1.AuthenticationServiceReflection.Descriptor.MessageTypes[3]; } + } + + pbr::MessageDescriptor pb::IMessage.Descriptor + { + get { return Descriptor; } + } + + public LogonRequest() + { + OnConstruction(); + } + + partial void OnConstruction(); + + public LogonRequest(LogonRequest other) : this() + { + program_ = other.program_; + platform_ = other.platform_; + locale_ = other.locale_; + email_ = other.email_; + version_ = other.version_; + applicationVersion_ = other.applicationVersion_; + publicComputer_ = other.publicComputer_; + ssoId_ = other.ssoId_; + disconnectOnCookieFail_ = other.disconnectOnCookieFail_; + allowLogonQueueNotifications_ = other.allowLogonQueueNotifications_; + webClientVerification_ = other.webClientVerification_; + cachedWebCredentials_ = other.cachedWebCredentials_; + enableCookie_ = other.enableCookie_; + userAgent_ = other.userAgent_; + } + + public LogonRequest Clone() + { + return new LogonRequest(this); + } + + /// Field number for the "program" field. + public const int ProgramFieldNumber = 1; + private string program_ = ""; + public string Program + { + get { return program_; } + set + { + program_ = pb::ProtoPreconditions.CheckNotNull(value, "value"); + } + } + + /// Field number for the "platform" field. + public const int PlatformFieldNumber = 2; + private string platform_ = ""; + public string Platform + { + get { return platform_; } + set + { + platform_ = pb::ProtoPreconditions.CheckNotNull(value, "value"); + } + } + + /// Field number for the "locale" field. + public const int LocaleFieldNumber = 3; + private string locale_ = ""; + public string Locale + { + get { return locale_; } + set + { + locale_ = pb::ProtoPreconditions.CheckNotNull(value, "value"); + } + } + + /// Field number for the "email" field. + public const int EmailFieldNumber = 4; + private string email_ = ""; + public string Email + { + get { return email_; } + set + { + email_ = pb::ProtoPreconditions.CheckNotNull(value, "value"); + } + } + + /// Field number for the "version" field. + public const int VersionFieldNumber = 5; + private string version_ = ""; + public string Version + { + get { return version_; } + set + { + version_ = pb::ProtoPreconditions.CheckNotNull(value, "value"); + } + } + + /// Field number for the "application_version" field. + public const int ApplicationVersionFieldNumber = 6; + private int applicationVersion_; + public int ApplicationVersion + { + get { return applicationVersion_; } + set + { + applicationVersion_ = value; + } + } + + /// Field number for the "public_computer" field. + public const int PublicComputerFieldNumber = 7; + private bool publicComputer_; + public bool PublicComputer + { + get { return publicComputer_; } + set + { + publicComputer_ = value; + } + } + + /// Field number for the "sso_id" field. + public const int SsoIdFieldNumber = 8; + private pb::ByteString ssoId_ = pb::ByteString.Empty; + public pb::ByteString SsoId + { + get { return ssoId_; } + set + { + ssoId_ = pb::ProtoPreconditions.CheckNotNull(value, "value"); + } + } + + /// Field number for the "disconnect_on_cookie_fail" field. + public const int DisconnectOnCookieFailFieldNumber = 9; + private bool disconnectOnCookieFail_; + public bool DisconnectOnCookieFail + { + get { return disconnectOnCookieFail_; } + set + { + disconnectOnCookieFail_ = value; + } + } + + /// Field number for the "allow_logon_queue_notifications" field. + public const int AllowLogonQueueNotificationsFieldNumber = 10; + private bool allowLogonQueueNotifications_; + public bool AllowLogonQueueNotifications + { + get { return allowLogonQueueNotifications_; } + set + { + allowLogonQueueNotifications_ = value; + } + } + + /// Field number for the "web_client_verification" field. + public const int WebClientVerificationFieldNumber = 11; + private bool webClientVerification_; + public bool WebClientVerification + { + get { return webClientVerification_; } + set + { + webClientVerification_ = value; + } + } + + /// Field number for the "cached_web_credentials" field. + public const int CachedWebCredentialsFieldNumber = 12; + private pb::ByteString cachedWebCredentials_ = pb::ByteString.Empty; + public pb::ByteString CachedWebCredentials + { + get { return cachedWebCredentials_; } + set + { + cachedWebCredentials_ = pb::ProtoPreconditions.CheckNotNull(value, "value"); + } + } + + /// Field number for the "enable_cookie" field. + public const int EnableCookieFieldNumber = 13; + private bool enableCookie_; + public bool EnableCookie + { + get { return enableCookie_; } + set + { + enableCookie_ = value; + } + } + + /// Field number for the "user_agent" field. + public const int UserAgentFieldNumber = 14; + private string userAgent_ = ""; + public string UserAgent + { + get { return userAgent_; } + set + { + userAgent_ = pb::ProtoPreconditions.CheckNotNull(value, "value"); + } + } + + public override bool Equals(object other) + { + return Equals(other as LogonRequest); + } + + public bool Equals(LogonRequest other) + { + if (ReferenceEquals(other, null)) + { + return false; + } + if (ReferenceEquals(other, this)) + { + return true; + } + if (Program != other.Program) return false; + if (Platform != other.Platform) return false; + if (Locale != other.Locale) return false; + if (Email != other.Email) return false; + if (Version != other.Version) return false; + if (ApplicationVersion != other.ApplicationVersion) return false; + if (PublicComputer != other.PublicComputer) return false; + if (SsoId != other.SsoId) return false; + if (DisconnectOnCookieFail != other.DisconnectOnCookieFail) return false; + if (AllowLogonQueueNotifications != other.AllowLogonQueueNotifications) return false; + if (WebClientVerification != other.WebClientVerification) return false; + if (CachedWebCredentials != other.CachedWebCredentials) return false; + if (EnableCookie != other.EnableCookie) return false; + if (UserAgent != other.UserAgent) return false; + return true; + } + + public override int GetHashCode() + { + int hash = 1; + if (Program.Length != 0) hash ^= Program.GetHashCode(); + if (Platform.Length != 0) hash ^= Platform.GetHashCode(); + if (Locale.Length != 0) hash ^= Locale.GetHashCode(); + if (Email.Length != 0) hash ^= Email.GetHashCode(); + if (Version.Length != 0) hash ^= Version.GetHashCode(); + if (ApplicationVersion != 0) hash ^= ApplicationVersion.GetHashCode(); + if (PublicComputer != false) hash ^= PublicComputer.GetHashCode(); + if (SsoId.Length != 0) hash ^= SsoId.GetHashCode(); + if (DisconnectOnCookieFail != false) hash ^= DisconnectOnCookieFail.GetHashCode(); + if (AllowLogonQueueNotifications != false) hash ^= AllowLogonQueueNotifications.GetHashCode(); + if (WebClientVerification != false) hash ^= WebClientVerification.GetHashCode(); + if (CachedWebCredentials.Length != 0) hash ^= CachedWebCredentials.GetHashCode(); + if (EnableCookie != false) hash ^= EnableCookie.GetHashCode(); + if (UserAgent.Length != 0) hash ^= UserAgent.GetHashCode(); + return hash; + } + + public override string ToString() + { + return pb::JsonFormatter.ToDiagnosticString(this); + } + + public void WriteTo(pb::CodedOutputStream output) + { + if (Program.Length != 0) + { + output.WriteRawTag(10); + output.WriteString(Program); + } + if (Platform.Length != 0) + { + output.WriteRawTag(18); + output.WriteString(Platform); + } + if (Locale.Length != 0) + { + output.WriteRawTag(26); + output.WriteString(Locale); + } + if (Email.Length != 0) + { + output.WriteRawTag(34); + output.WriteString(Email); + } + if (Version.Length != 0) + { + output.WriteRawTag(42); + output.WriteString(Version); + } + if (ApplicationVersion != 0) + { + output.WriteRawTag(48); + output.WriteInt32(ApplicationVersion); + } + if (PublicComputer != false) + { + output.WriteRawTag(56); + output.WriteBool(PublicComputer); + } + if (SsoId.Length != 0) + { + output.WriteRawTag(66); + output.WriteBytes(SsoId); + } + if (DisconnectOnCookieFail != false) + { + output.WriteRawTag(72); + output.WriteBool(DisconnectOnCookieFail); + } + if (AllowLogonQueueNotifications != false) + { + output.WriteRawTag(80); + output.WriteBool(AllowLogonQueueNotifications); + } + if (WebClientVerification != false) + { + output.WriteRawTag(88); + output.WriteBool(WebClientVerification); + } + if (CachedWebCredentials.Length != 0) + { + output.WriteRawTag(98); + output.WriteBytes(CachedWebCredentials); + } + if (EnableCookie != false) + { + output.WriteRawTag(104); + output.WriteBool(EnableCookie); + } + if (UserAgent.Length != 0) + { + output.WriteRawTag(114); + output.WriteString(UserAgent); + } + } + + public int CalculateSize() + { + int size = 0; + if (Program.Length != 0) + { + size += 1 + pb::CodedOutputStream.ComputeStringSize(Program); + } + if (Platform.Length != 0) + { + size += 1 + pb::CodedOutputStream.ComputeStringSize(Platform); + } + if (Locale.Length != 0) + { + size += 1 + pb::CodedOutputStream.ComputeStringSize(Locale); + } + if (Email.Length != 0) + { + size += 1 + pb::CodedOutputStream.ComputeStringSize(Email); + } + if (Version.Length != 0) + { + size += 1 + pb::CodedOutputStream.ComputeStringSize(Version); + } + if (ApplicationVersion != 0) + { + size += 1 + pb::CodedOutputStream.ComputeInt32Size(ApplicationVersion); + } + if (PublicComputer != false) + { + size += 1 + 1; + } + if (SsoId.Length != 0) + { + size += 1 + pb::CodedOutputStream.ComputeBytesSize(SsoId); + } + if (DisconnectOnCookieFail != false) + { + size += 1 + 1; + } + if (AllowLogonQueueNotifications != false) + { + size += 1 + 1; + } + if (WebClientVerification != false) + { + size += 1 + 1; + } + if (CachedWebCredentials.Length != 0) + { + size += 1 + pb::CodedOutputStream.ComputeBytesSize(CachedWebCredentials); + } + if (EnableCookie != false) + { + size += 1 + 1; + } + if (UserAgent.Length != 0) + { + size += 1 + pb::CodedOutputStream.ComputeStringSize(UserAgent); + } + return size; + } + + public void MergeFrom(LogonRequest other) + { + if (other == null) + { + return; + } + if (other.Program.Length != 0) + { + Program = other.Program; + } + if (other.Platform.Length != 0) + { + Platform = other.Platform; + } + if (other.Locale.Length != 0) + { + Locale = other.Locale; + } + if (other.Email.Length != 0) + { + Email = other.Email; + } + if (other.Version.Length != 0) + { + Version = other.Version; + } + if (other.ApplicationVersion != 0) + { + ApplicationVersion = other.ApplicationVersion; + } + if (other.PublicComputer != false) + { + PublicComputer = other.PublicComputer; + } + if (other.SsoId.Length != 0) + { + SsoId = other.SsoId; + } + if (other.DisconnectOnCookieFail != false) + { + DisconnectOnCookieFail = other.DisconnectOnCookieFail; + } + if (other.AllowLogonQueueNotifications != false) + { + AllowLogonQueueNotifications = other.AllowLogonQueueNotifications; + } + if (other.WebClientVerification != false) + { + WebClientVerification = other.WebClientVerification; + } + if (other.CachedWebCredentials.Length != 0) + { + CachedWebCredentials = other.CachedWebCredentials; + } + if (other.EnableCookie != false) + { + EnableCookie = other.EnableCookie; + } + if (other.UserAgent.Length != 0) + { + UserAgent = other.UserAgent; + } + } + + public void MergeFrom(pb::CodedInputStream input) + { + uint tag; + while ((tag = input.ReadTag()) != 0) + { + switch (tag) + { + default: + input.SkipLastField(); + break; + case 10: + { + Program = input.ReadString(); + break; + } + case 18: + { + Platform = input.ReadString(); + break; + } + case 26: + { + Locale = input.ReadString(); + break; + } + case 34: + { + Email = input.ReadString(); + break; + } + case 42: + { + Version = input.ReadString(); + break; + } + case 48: + { + ApplicationVersion = input.ReadInt32(); + break; + } + case 56: + { + PublicComputer = input.ReadBool(); + break; + } + case 66: + { + SsoId = input.ReadBytes(); + break; + } + case 72: + { + DisconnectOnCookieFail = input.ReadBool(); + break; + } + case 80: + { + AllowLogonQueueNotifications = input.ReadBool(); + break; + } + case 88: + { + WebClientVerification = input.ReadBool(); + break; + } + case 98: + { + CachedWebCredentials = input.ReadBytes(); + break; + } + case 104: + { + EnableCookie = input.ReadBool(); + break; + } + case 114: + { + UserAgent = input.ReadString(); + break; + } + } + } + } + + } + + [System.Diagnostics.DebuggerNonUserCodeAttribute()] + public sealed partial class LogonResult : pb::IMessage + { + private static readonly pb::MessageParser _parser = new pb::MessageParser(() => new LogonResult()); + public static pb::MessageParser Parser { get { return _parser; } } + + public static pbr::MessageDescriptor Descriptor + { + get { return Bgs.Protocol.Authentication.V1.AuthenticationServiceReflection.Descriptor.MessageTypes[4]; } + } + + pbr::MessageDescriptor pb::IMessage.Descriptor + { + get { return Descriptor; } + } + + public LogonResult() + { + OnConstruction(); + } + + partial void OnConstruction(); + + public LogonResult(LogonResult other) : this() + { + errorCode_ = other.errorCode_; + AccountId = other.accountId_ != null ? other.AccountId.Clone() : null; + gameAccountId_ = other.gameAccountId_.Clone(); + email_ = other.email_; + availableRegion_ = other.availableRegion_.Clone(); + connectedRegion_ = other.connectedRegion_; + battleTag_ = other.battleTag_; + geoipCountry_ = other.geoipCountry_; + sessionKey_ = other.sessionKey_; + restrictedMode_ = other.restrictedMode_; + } + + public LogonResult Clone() + { + return new LogonResult(this); + } + + /// Field number for the "error_code" field. + public const int ErrorCodeFieldNumber = 0; + private uint errorCode_; + public uint ErrorCode + { + get { return errorCode_; } + set + { + bitArray.Set(ErrorCodeFieldNumber, true); + errorCode_ = value; + } + } + + /// Field number for the "account_id" field. + public const int AccountIdFieldNumber = 1; + private Bgs.Protocol.EntityId accountId_; + public Bgs.Protocol.EntityId AccountId + { + get { return accountId_; } + set + { + bitArray.Set(AccountIdFieldNumber, true); + accountId_ = value; + } + } + + /// Field number for the "game_account_id" field. + public const int GameAccountIdFieldNumber = 2; + private static readonly pb::FieldCodec _repeated_gameAccountId_codec + = pb::FieldCodec.ForMessage(26, Bgs.Protocol.EntityId.Parser); + private readonly pbc::RepeatedField gameAccountId_ = new pbc::RepeatedField(); + public pbc::RepeatedField GameAccountId + { + get { return gameAccountId_; } + } + + /// Field number for the "email" field. + public const int EmailFieldNumber = 3; + private string email_ = ""; + public string Email + { + get { return email_; } + set + { + bitArray.Set(EmailFieldNumber, true); + email_ = pb::ProtoPreconditions.CheckNotNull(value, "value"); + } + } + + /// Field number for the "available_region" field. + public const int AvailableRegionFieldNumber = 4; + private static readonly pb::FieldCodec _repeated_availableRegion_codec + = pb::FieldCodec.ForUInt32(42); + private readonly pbc::RepeatedField availableRegion_ = new pbc::RepeatedField(); + public pbc::RepeatedField AvailableRegion + { + get { return availableRegion_; } + } + + /// Field number for the "connected_region" field. + public const int ConnectedRegionFieldNumber = 5; + private uint connectedRegion_; + public uint ConnectedRegion + { + get { return connectedRegion_; } + set + { + bitArray.Set(ConnectedRegionFieldNumber, true); + connectedRegion_ = value; + } + } + + /// Field number for the "battle_tag" field. + public const int BattleTagFieldNumber = 6; + private string battleTag_ = ""; + public string BattleTag + { + get { return battleTag_; } + set + { + bitArray.Set(BattleTagFieldNumber, true); + battleTag_ = pb::ProtoPreconditions.CheckNotNull(value, "value"); + } + } + + /// Field number for the "geoip_country" field. + public const int GeoipCountryFieldNumber = 7; + private string geoipCountry_ = ""; + public string GeoipCountry + { + get { return geoipCountry_; } + set + { + bitArray.Set(GeoipCountryFieldNumber, true); + geoipCountry_ = pb::ProtoPreconditions.CheckNotNull(value, "value"); + } + } + + /// Field number for the "session_key" field. + public const int SessionKeyFieldNumber = 8; + private pb::ByteString sessionKey_ = pb::ByteString.Empty; + public pb::ByteString SessionKey + { + get { return sessionKey_; } + set + { + bitArray.Set(SessionKeyFieldNumber, true); + sessionKey_ = pb::ProtoPreconditions.CheckNotNull(value, "value"); + } + } + + /// Field number for the "restricted_mode" field. + public const int RestrictedModeFieldNumber = 9; + private bool restrictedMode_; + public bool RestrictedMode + { + get { return restrictedMode_; } + set + { + bitArray.Set(RestrictedModeFieldNumber, true); + restrictedMode_ = value; + } + } + + public override bool Equals(object other) + { + return Equals(other as LogonResult); + } + + public bool Equals(LogonResult other) + { + if (ReferenceEquals(other, null)) + { + return false; + } + if (ReferenceEquals(other, this)) + { + return true; + } + if (ErrorCode != other.ErrorCode) return false; + if (!object.Equals(AccountId, other.AccountId)) return false; + if (!gameAccountId_.Equals(other.gameAccountId_)) return false; + if (Email != other.Email) return false; + if (!availableRegion_.Equals(other.availableRegion_)) return false; + if (ConnectedRegion != other.ConnectedRegion) return false; + if (BattleTag != other.BattleTag) return false; + if (GeoipCountry != other.GeoipCountry) return false; + if (SessionKey != other.SessionKey) return false; + if (RestrictedMode != other.RestrictedMode) return false; + return true; + } + + public override int GetHashCode() + { + int hash = 1; + if (ErrorCode != 0) hash ^= ErrorCode.GetHashCode(); + if (accountId_ != null) hash ^= AccountId.GetHashCode(); + hash ^= gameAccountId_.GetHashCode(); + if (Email.Length != 0) hash ^= Email.GetHashCode(); + hash ^= availableRegion_.GetHashCode(); + if (ConnectedRegion != 0) hash ^= ConnectedRegion.GetHashCode(); + if (BattleTag.Length != 0) hash ^= BattleTag.GetHashCode(); + if (GeoipCountry.Length != 0) hash ^= GeoipCountry.GetHashCode(); + if (SessionKey.Length != 0) hash ^= SessionKey.GetHashCode(); + if (RestrictedMode != false) hash ^= RestrictedMode.GetHashCode(); + return hash; + } + + public override string ToString() + { + return pb::JsonFormatter.ToDiagnosticString(this); + } + + public void WriteTo(pb::CodedOutputStream output) + { + if (bitArray.Get(ErrorCodeFieldNumber)) + { + output.WriteRawTag(8); + output.WriteUInt32(ErrorCode); + } + if (bitArray.Get(AccountIdFieldNumber)) + { + output.WriteRawTag(18); + output.WriteMessage(AccountId); + } + gameAccountId_.WriteTo(output, _repeated_gameAccountId_codec); + if (bitArray.Get(EmailFieldNumber)) + { + output.WriteRawTag(34); + output.WriteString(Email); + } + availableRegion_.WriteTo(output, _repeated_availableRegion_codec); + if (bitArray.Get(ConnectedRegionFieldNumber)) + { + output.WriteRawTag(48); + output.WriteUInt32(ConnectedRegion); + } + if (bitArray.Get(BattleTagFieldNumber)) + { + output.WriteRawTag(58); + output.WriteString(BattleTag); + } + if (bitArray.Get(GeoipCountryFieldNumber)) + { + output.WriteRawTag(66); + output.WriteString(GeoipCountry); + } + if (bitArray.Get(SessionKeyFieldNumber)) + { + output.WriteRawTag(74); + output.WriteBytes(SessionKey); + } + if (bitArray.Get(RestrictedModeFieldNumber)) + { + output.WriteRawTag(80); + output.WriteBool(RestrictedMode); + } + } + + public int CalculateSize() + { + int size = 0; + if (bitArray.Get(ErrorCodeFieldNumber)) + { + size += 1 + pb::CodedOutputStream.ComputeUInt32Size(ErrorCode); + } + if (bitArray.Get(AccountIdFieldNumber)) + { + size += 1 + pb::CodedOutputStream.ComputeMessageSize(AccountId); + } + size += gameAccountId_.CalculateSize(_repeated_gameAccountId_codec); + if (bitArray.Get(EmailFieldNumber)) + { + size += 1 + pb::CodedOutputStream.ComputeStringSize(Email); + } + size += availableRegion_.CalculateSize(_repeated_availableRegion_codec); + if (bitArray.Get(ConnectedRegionFieldNumber)) + { + size += 1 + pb::CodedOutputStream.ComputeUInt32Size(ConnectedRegion); + } + if (bitArray.Get(BattleTagFieldNumber)) + { + size += 1 + pb::CodedOutputStream.ComputeStringSize(BattleTag); + } + if (bitArray.Get(GeoipCountryFieldNumber)) + { + size += 1 + pb::CodedOutputStream.ComputeStringSize(GeoipCountry); + } + if (bitArray.Get(SessionKeyFieldNumber)) + { + size += 1 + pb::CodedOutputStream.ComputeBytesSize(SessionKey); + } + if (bitArray.Get(RestrictedModeFieldNumber)) + { + size += 1 + 1; + } + return size; + } + + public void MergeFrom(LogonResult other) + { + if (other == null) + { + return; + } + if (other.ErrorCode != 0) + { + ErrorCode = other.ErrorCode; + } + if (other.accountId_ != null) + { + if (accountId_ == null) + { + accountId_ = new Bgs.Protocol.EntityId(); + } + AccountId.MergeFrom(other.AccountId); + } + gameAccountId_.Add(other.gameAccountId_); + if (other.Email.Length != 0) + { + Email = other.Email; + } + availableRegion_.Add(other.availableRegion_); + if (other.ConnectedRegion != 0) + { + ConnectedRegion = other.ConnectedRegion; + } + if (other.BattleTag.Length != 0) + { + BattleTag = other.BattleTag; + } + if (other.GeoipCountry.Length != 0) + { + GeoipCountry = other.GeoipCountry; + } + if (other.SessionKey.Length != 0) + { + SessionKey = other.SessionKey; + } + if (other.RestrictedMode != false) + { + RestrictedMode = other.RestrictedMode; + } + } + + public void MergeFrom(pb::CodedInputStream input) + { + uint tag; + while ((tag = input.ReadTag()) != 0) + { + switch (tag) + { + default: + input.SkipLastField(); + break; + case 8: + { + ErrorCode = input.ReadUInt32(); + break; + } + case 18: + { + if (accountId_ == null) + { + accountId_ = new Bgs.Protocol.EntityId(); + } + input.ReadMessage(accountId_); + break; + } + case 26: + { + gameAccountId_.AddEntriesFrom(input, _repeated_gameAccountId_codec); + break; + } + case 34: + { + Email = input.ReadString(); + break; + } + case 42: + case 40: + { + availableRegion_.AddEntriesFrom(input, _repeated_availableRegion_codec); + break; + } + case 48: + { + ConnectedRegion = input.ReadUInt32(); + break; + } + case 58: + { + BattleTag = input.ReadString(); + break; + } + case 66: + { + GeoipCountry = input.ReadString(); + break; + } + case 74: + { + SessionKey = input.ReadBytes(); + break; + } + case 80: + { + RestrictedMode = input.ReadBool(); + break; + } + } + } + } + + System.Collections.BitArray bitArray = new System.Collections.BitArray(10); + } + + [System.Diagnostics.DebuggerNonUserCodeAttribute()] + public sealed partial class GenerateSSOTokenRequest : pb::IMessage + { + private static readonly pb::MessageParser _parser = new pb::MessageParser(() => new GenerateSSOTokenRequest()); + public static pb::MessageParser Parser { get { return _parser; } } + + public static pbr::MessageDescriptor Descriptor + { + get { return Bgs.Protocol.Authentication.V1.AuthenticationServiceReflection.Descriptor.MessageTypes[5]; } + } + + pbr::MessageDescriptor pb::IMessage.Descriptor + { + get { return Descriptor; } + } + + public GenerateSSOTokenRequest() + { + OnConstruction(); + } + + partial void OnConstruction(); + + public GenerateSSOTokenRequest(GenerateSSOTokenRequest other) : this() + { + program_ = other.program_; + } + + public GenerateSSOTokenRequest Clone() + { + return new GenerateSSOTokenRequest(this); + } + + /// Field number for the "program" field. + public const int ProgramFieldNumber = 1; + private uint program_; + public uint Program + { + get { return program_; } + set + { + program_ = value; + } + } + + public override bool Equals(object other) + { + return Equals(other as GenerateSSOTokenRequest); + } + + public bool Equals(GenerateSSOTokenRequest other) + { + if (ReferenceEquals(other, null)) + { + return false; + } + if (ReferenceEquals(other, this)) + { + return true; + } + if (Program != other.Program) return false; + return true; + } + + public override int GetHashCode() + { + int hash = 1; + if (Program != 0) hash ^= Program.GetHashCode(); + return hash; + } + + public override string ToString() + { + return pb::JsonFormatter.ToDiagnosticString(this); + } + + public void WriteTo(pb::CodedOutputStream output) + { + if (Program != 0) + { + output.WriteRawTag(13); + output.WriteFixed32(Program); + } + } + + public int CalculateSize() + { + int size = 0; + if (Program != 0) + { + size += 1 + 4; + } + return size; + } + + public void MergeFrom(GenerateSSOTokenRequest other) + { + if (other == null) + { + return; + } + if (other.Program != 0) + { + Program = other.Program; + } + } + + public void MergeFrom(pb::CodedInputStream input) + { + uint tag; + while ((tag = input.ReadTag()) != 0) + { + switch (tag) + { + default: + input.SkipLastField(); + break; + case 13: + { + Program = input.ReadFixed32(); + break; + } + } + } + } + + } + + [System.Diagnostics.DebuggerNonUserCodeAttribute()] + public sealed partial class GenerateSSOTokenResponse : pb::IMessage + { + private static readonly pb::MessageParser _parser = new pb::MessageParser(() => new GenerateSSOTokenResponse()); + public static pb::MessageParser Parser { get { return _parser; } } + + public static pbr::MessageDescriptor Descriptor + { + get { return Bgs.Protocol.Authentication.V1.AuthenticationServiceReflection.Descriptor.MessageTypes[6]; } + } + + pbr::MessageDescriptor pb::IMessage.Descriptor + { + get { return Descriptor; } + } + + public GenerateSSOTokenResponse() + { + OnConstruction(); + } + + partial void OnConstruction(); + + public GenerateSSOTokenResponse(GenerateSSOTokenResponse other) : this() + { + ssoId_ = other.ssoId_; + ssoSecret_ = other.ssoSecret_; + } + + public GenerateSSOTokenResponse Clone() + { + return new GenerateSSOTokenResponse(this); + } + + /// Field number for the "sso_id" field. + public const int SsoIdFieldNumber = 1; + private pb::ByteString ssoId_ = pb::ByteString.Empty; + public pb::ByteString SsoId + { + get { return ssoId_; } + set + { + ssoId_ = pb::ProtoPreconditions.CheckNotNull(value, "value"); + } + } + + /// Field number for the "sso_secret" field. + public const int SsoSecretFieldNumber = 2; + private pb::ByteString ssoSecret_ = pb::ByteString.Empty; + public pb::ByteString SsoSecret + { + get { return ssoSecret_; } + set + { + ssoSecret_ = pb::ProtoPreconditions.CheckNotNull(value, "value"); + } + } + + public override bool Equals(object other) + { + return Equals(other as GenerateSSOTokenResponse); + } + + public bool Equals(GenerateSSOTokenResponse other) + { + if (ReferenceEquals(other, null)) + { + return false; + } + if (ReferenceEquals(other, this)) + { + return true; + } + if (SsoId != other.SsoId) return false; + if (SsoSecret != other.SsoSecret) return false; + return true; + } + + public override int GetHashCode() + { + int hash = 1; + if (SsoId.Length != 0) hash ^= SsoId.GetHashCode(); + if (SsoSecret.Length != 0) hash ^= SsoSecret.GetHashCode(); + return hash; + } + + public override string ToString() + { + return pb::JsonFormatter.ToDiagnosticString(this); + } + + public void WriteTo(pb::CodedOutputStream output) + { + if (SsoId.Length != 0) + { + output.WriteRawTag(10); + output.WriteBytes(SsoId); + } + if (SsoSecret.Length != 0) + { + output.WriteRawTag(18); + output.WriteBytes(SsoSecret); + } + } + + public int CalculateSize() + { + int size = 0; + if (SsoId.Length != 0) + { + size += 1 + pb::CodedOutputStream.ComputeBytesSize(SsoId); + } + if (SsoSecret.Length != 0) + { + size += 1 + pb::CodedOutputStream.ComputeBytesSize(SsoSecret); + } + return size; + } + + public void MergeFrom(GenerateSSOTokenResponse other) + { + if (other == null) + { + return; + } + if (other.SsoId.Length != 0) + { + SsoId = other.SsoId; + } + if (other.SsoSecret.Length != 0) + { + SsoSecret = other.SsoSecret; + } + } + + public void MergeFrom(pb::CodedInputStream input) + { + uint tag; + while ((tag = input.ReadTag()) != 0) + { + switch (tag) + { + default: + input.SkipLastField(); + break; + case 10: + { + SsoId = input.ReadBytes(); + break; + } + case 18: + { + SsoSecret = input.ReadBytes(); + break; + } + } + } + } + + } + + [System.Diagnostics.DebuggerNonUserCodeAttribute()] + public sealed partial class LogonUpdateRequest : pb::IMessage + { + private static readonly pb::MessageParser _parser = new pb::MessageParser(() => new LogonUpdateRequest()); + public static pb::MessageParser Parser { get { return _parser; } } + + public static pbr::MessageDescriptor Descriptor + { + get { return Bgs.Protocol.Authentication.V1.AuthenticationServiceReflection.Descriptor.MessageTypes[7]; } + } + + pbr::MessageDescriptor pb::IMessage.Descriptor + { + get { return Descriptor; } + } + + public LogonUpdateRequest() + { + OnConstruction(); + } + + partial void OnConstruction(); + + public LogonUpdateRequest(LogonUpdateRequest other) : this() + { + errorCode_ = other.errorCode_; + } + + public LogonUpdateRequest Clone() + { + return new LogonUpdateRequest(this); + } + + /// Field number for the "error_code" field. + public const int ErrorCodeFieldNumber = 1; + private uint errorCode_; + public uint ErrorCode + { + get { return errorCode_; } + set + { + errorCode_ = value; + } + } + + public override bool Equals(object other) + { + return Equals(other as LogonUpdateRequest); + } + + public bool Equals(LogonUpdateRequest other) + { + if (ReferenceEquals(other, null)) + { + return false; + } + if (ReferenceEquals(other, this)) + { + return true; + } + if (ErrorCode != other.ErrorCode) return false; + return true; + } + + public override int GetHashCode() + { + int hash = 1; + if (ErrorCode != 0) hash ^= ErrorCode.GetHashCode(); + return hash; + } + + public override string ToString() + { + return pb::JsonFormatter.ToDiagnosticString(this); + } + + public void WriteTo(pb::CodedOutputStream output) + { + if (ErrorCode != 0) + { + output.WriteRawTag(8); + output.WriteUInt32(ErrorCode); + } + } + + public int CalculateSize() + { + int size = 0; + if (ErrorCode != 0) + { + size += 1 + pb::CodedOutputStream.ComputeUInt32Size(ErrorCode); + } + return size; + } + + public void MergeFrom(LogonUpdateRequest other) + { + if (other == null) + { + return; + } + if (other.ErrorCode != 0) + { + ErrorCode = other.ErrorCode; + } + } + + public void MergeFrom(pb::CodedInputStream input) + { + uint tag; + while ((tag = input.ReadTag()) != 0) + { + switch (tag) + { + default: + input.SkipLastField(); + break; + case 8: + { + ErrorCode = input.ReadUInt32(); + break; + } + } + } + } + + } + + [System.Diagnostics.DebuggerNonUserCodeAttribute()] + public sealed partial class LogonQueueUpdateRequest : pb::IMessage + { + private static readonly pb::MessageParser _parser = new pb::MessageParser(() => new LogonQueueUpdateRequest()); + public static pb::MessageParser Parser { get { return _parser; } } + + public static pbr::MessageDescriptor Descriptor + { + get { return Bgs.Protocol.Authentication.V1.AuthenticationServiceReflection.Descriptor.MessageTypes[8]; } + } + + pbr::MessageDescriptor pb::IMessage.Descriptor + { + get { return Descriptor; } + } + + public LogonQueueUpdateRequest() + { + OnConstruction(); + } + + partial void OnConstruction(); + + public LogonQueueUpdateRequest(LogonQueueUpdateRequest other) : this() + { + position_ = other.position_; + estimatedTime_ = other.estimatedTime_; + etaDeviationInSec_ = other.etaDeviationInSec_; + } + + public LogonQueueUpdateRequest Clone() + { + return new LogonQueueUpdateRequest(this); + } + + /// Field number for the "position" field. + public const int PositionFieldNumber = 1; + private uint position_; + public uint Position + { + get { return position_; } + set + { + position_ = value; + } + } + + /// Field number for the "estimated_time" field. + public const int EstimatedTimeFieldNumber = 2; + private ulong estimatedTime_; + public ulong EstimatedTime + { + get { return estimatedTime_; } + set + { + estimatedTime_ = value; + } + } + + /// Field number for the "eta_deviation_in_sec" field. + public const int EtaDeviationInSecFieldNumber = 3; + private ulong etaDeviationInSec_; + public ulong EtaDeviationInSec + { + get { return etaDeviationInSec_; } + set + { + etaDeviationInSec_ = value; + } + } + + public override bool Equals(object other) + { + return Equals(other as LogonQueueUpdateRequest); + } + + public bool Equals(LogonQueueUpdateRequest other) + { + if (ReferenceEquals(other, null)) + { + return false; + } + if (ReferenceEquals(other, this)) + { + return true; + } + if (Position != other.Position) return false; + if (EstimatedTime != other.EstimatedTime) return false; + if (EtaDeviationInSec != other.EtaDeviationInSec) return false; + return true; + } + + public override int GetHashCode() + { + int hash = 1; + if (Position != 0) hash ^= Position.GetHashCode(); + if (EstimatedTime != 0UL) hash ^= EstimatedTime.GetHashCode(); + if (EtaDeviationInSec != 0UL) hash ^= EtaDeviationInSec.GetHashCode(); + return hash; + } + + public override string ToString() + { + return pb::JsonFormatter.ToDiagnosticString(this); + } + + public void WriteTo(pb::CodedOutputStream output) + { + if (Position != 0) + { + output.WriteRawTag(8); + output.WriteUInt32(Position); + } + if (EstimatedTime != 0UL) + { + output.WriteRawTag(16); + output.WriteUInt64(EstimatedTime); + } + if (EtaDeviationInSec != 0UL) + { + output.WriteRawTag(24); + output.WriteUInt64(EtaDeviationInSec); + } + } + + public int CalculateSize() + { + int size = 0; + if (Position != 0) + { + size += 1 + pb::CodedOutputStream.ComputeUInt32Size(Position); + } + if (EstimatedTime != 0UL) + { + size += 1 + pb::CodedOutputStream.ComputeUInt64Size(EstimatedTime); + } + if (EtaDeviationInSec != 0UL) + { + size += 1 + pb::CodedOutputStream.ComputeUInt64Size(EtaDeviationInSec); + } + return size; + } + + public void MergeFrom(LogonQueueUpdateRequest other) + { + if (other == null) + { + return; + } + if (other.Position != 0) + { + Position = other.Position; + } + if (other.EstimatedTime != 0UL) + { + EstimatedTime = other.EstimatedTime; + } + if (other.EtaDeviationInSec != 0UL) + { + EtaDeviationInSec = other.EtaDeviationInSec; + } + } + + public void MergeFrom(pb::CodedInputStream input) + { + uint tag; + while ((tag = input.ReadTag()) != 0) + { + switch (tag) + { + default: + input.SkipLastField(); + break; + case 8: + { + Position = input.ReadUInt32(); + break; + } + case 16: + { + EstimatedTime = input.ReadUInt64(); + break; + } + case 24: + { + EtaDeviationInSec = input.ReadUInt64(); + break; + } + } + } + } + + } + + [System.Diagnostics.DebuggerNonUserCodeAttribute()] + public sealed partial class AccountSettingsNotification : pb::IMessage + { + private static readonly pb::MessageParser _parser = new pb::MessageParser(() => new AccountSettingsNotification()); + public static pb::MessageParser Parser { get { return _parser; } } + + public static pbr::MessageDescriptor Descriptor + { + get { return Bgs.Protocol.Authentication.V1.AuthenticationServiceReflection.Descriptor.MessageTypes[9]; } + } + + pbr::MessageDescriptor pb::IMessage.Descriptor + { + get { return Descriptor; } + } + + public AccountSettingsNotification() + { + OnConstruction(); + } + + partial void OnConstruction(); + + public AccountSettingsNotification(AccountSettingsNotification other) : this() + { + licenses_ = other.licenses_.Clone(); + isUsingRid_ = other.isUsingRid_; + isPlayingFromIgr_ = other.isPlayingFromIgr_; + canReceiveVoice_ = other.canReceiveVoice_; + canSendVoice_ = other.canSendVoice_; + } + + public AccountSettingsNotification Clone() + { + return new AccountSettingsNotification(this); + } + + /// Field number for the "licenses" field. + public const int LicensesFieldNumber = 1; + private static readonly pb::FieldCodec _repeated_licenses_codec + = pb::FieldCodec.ForMessage(10, Bgs.Protocol.Account.V1.AccountLicense.Parser); + private readonly pbc::RepeatedField licenses_ = new pbc::RepeatedField(); + public pbc::RepeatedField Licenses + { + get { return licenses_; } + } + + /// Field number for the "is_using_rid" field. + public const int IsUsingRidFieldNumber = 2; + private bool isUsingRid_; + public bool IsUsingRid + { + get { return isUsingRid_; } + set + { + isUsingRid_ = value; + } + } + + /// Field number for the "is_playing_from_igr" field. + public const int IsPlayingFromIgrFieldNumber = 3; + private bool isPlayingFromIgr_; + public bool IsPlayingFromIgr + { + get { return isPlayingFromIgr_; } + set + { + isPlayingFromIgr_ = value; + } + } + + /// Field number for the "can_receive_voice" field. + public const int CanReceiveVoiceFieldNumber = 4; + private bool canReceiveVoice_; + public bool CanReceiveVoice + { + get { return canReceiveVoice_; } + set + { + canReceiveVoice_ = value; + } + } + + /// Field number for the "can_send_voice" field. + public const int CanSendVoiceFieldNumber = 5; + private bool canSendVoice_; + public bool CanSendVoice + { + get { return canSendVoice_; } + set + { + canSendVoice_ = value; + } + } + + public override bool Equals(object other) + { + return Equals(other as AccountSettingsNotification); + } + + public bool Equals(AccountSettingsNotification other) + { + if (ReferenceEquals(other, null)) + { + return false; + } + if (ReferenceEquals(other, this)) + { + return true; + } + if (!licenses_.Equals(other.licenses_)) return false; + if (IsUsingRid != other.IsUsingRid) return false; + if (IsPlayingFromIgr != other.IsPlayingFromIgr) return false; + if (CanReceiveVoice != other.CanReceiveVoice) return false; + if (CanSendVoice != other.CanSendVoice) return false; + return true; + } + + public override int GetHashCode() + { + int hash = 1; + hash ^= licenses_.GetHashCode(); + if (IsUsingRid != false) hash ^= IsUsingRid.GetHashCode(); + if (IsPlayingFromIgr != false) hash ^= IsPlayingFromIgr.GetHashCode(); + if (CanReceiveVoice != false) hash ^= CanReceiveVoice.GetHashCode(); + if (CanSendVoice != false) hash ^= CanSendVoice.GetHashCode(); + return hash; + } + + public override string ToString() + { + return pb::JsonFormatter.ToDiagnosticString(this); + } + + public void WriteTo(pb::CodedOutputStream output) + { + licenses_.WriteTo(output, _repeated_licenses_codec); + if (IsUsingRid != false) + { + output.WriteRawTag(16); + output.WriteBool(IsUsingRid); + } + if (IsPlayingFromIgr != false) + { + output.WriteRawTag(24); + output.WriteBool(IsPlayingFromIgr); + } + if (CanReceiveVoice != false) + { + output.WriteRawTag(32); + output.WriteBool(CanReceiveVoice); + } + if (CanSendVoice != false) + { + output.WriteRawTag(40); + output.WriteBool(CanSendVoice); + } + } + + public int CalculateSize() + { + int size = 0; + size += licenses_.CalculateSize(_repeated_licenses_codec); + if (IsUsingRid != false) + { + size += 1 + 1; + } + if (IsPlayingFromIgr != false) + { + size += 1 + 1; + } + if (CanReceiveVoice != false) + { + size += 1 + 1; + } + if (CanSendVoice != false) + { + size += 1 + 1; + } + return size; + } + + public void MergeFrom(AccountSettingsNotification other) + { + if (other == null) + { + return; + } + licenses_.Add(other.licenses_); + if (other.IsUsingRid != false) + { + IsUsingRid = other.IsUsingRid; + } + if (other.IsPlayingFromIgr != false) + { + IsPlayingFromIgr = other.IsPlayingFromIgr; + } + if (other.CanReceiveVoice != false) + { + CanReceiveVoice = other.CanReceiveVoice; + } + if (other.CanSendVoice != false) + { + CanSendVoice = other.CanSendVoice; + } + } + + public void MergeFrom(pb::CodedInputStream input) + { + uint tag; + while ((tag = input.ReadTag()) != 0) + { + switch (tag) + { + default: + input.SkipLastField(); + break; + case 10: + { + licenses_.AddEntriesFrom(input, _repeated_licenses_codec); + break; + } + case 16: + { + IsUsingRid = input.ReadBool(); + break; + } + case 24: + { + IsPlayingFromIgr = input.ReadBool(); + break; + } + case 32: + { + CanReceiveVoice = input.ReadBool(); + break; + } + case 40: + { + CanSendVoice = input.ReadBool(); + break; + } + } + } + } + + } + + [System.Diagnostics.DebuggerNonUserCodeAttribute()] + public sealed partial class ServerStateChangeRequest : pb::IMessage + { + private static readonly pb::MessageParser _parser = new pb::MessageParser(() => new ServerStateChangeRequest()); + public static pb::MessageParser Parser { get { return _parser; } } + + public static pbr::MessageDescriptor Descriptor + { + get { return Bgs.Protocol.Authentication.V1.AuthenticationServiceReflection.Descriptor.MessageTypes[10]; } + } + + pbr::MessageDescriptor pb::IMessage.Descriptor + { + get { return Descriptor; } + } + + public ServerStateChangeRequest() + { + OnConstruction(); + } + + partial void OnConstruction(); + + public ServerStateChangeRequest(ServerStateChangeRequest other) : this() + { + state_ = other.state_; + eventTime_ = other.eventTime_; + } + + public ServerStateChangeRequest Clone() + { + return new ServerStateChangeRequest(this); + } + + /// Field number for the "state" field. + public const int StateFieldNumber = 1; + private uint state_; + public uint State + { + get { return state_; } + set + { + state_ = value; + } + } + + /// Field number for the "event_time" field. + public const int EventTimeFieldNumber = 2; + private ulong eventTime_; + public ulong EventTime + { + get { return eventTime_; } + set + { + eventTime_ = value; + } + } + + public override bool Equals(object other) + { + return Equals(other as ServerStateChangeRequest); + } + + public bool Equals(ServerStateChangeRequest other) + { + if (ReferenceEquals(other, null)) + { + return false; + } + if (ReferenceEquals(other, this)) + { + return true; + } + if (State != other.State) return false; + if (EventTime != other.EventTime) return false; + return true; + } + + public override int GetHashCode() + { + int hash = 1; + if (State != 0) hash ^= State.GetHashCode(); + if (EventTime != 0UL) hash ^= EventTime.GetHashCode(); + return hash; + } + + public override string ToString() + { + return pb::JsonFormatter.ToDiagnosticString(this); + } + + public void WriteTo(pb::CodedOutputStream output) + { + if (State != 0) + { + output.WriteRawTag(8); + output.WriteUInt32(State); + } + if (EventTime != 0UL) + { + output.WriteRawTag(16); + output.WriteUInt64(EventTime); + } + } + + public int CalculateSize() + { + int size = 0; + if (State != 0) + { + size += 1 + pb::CodedOutputStream.ComputeUInt32Size(State); + } + if (EventTime != 0UL) + { + size += 1 + pb::CodedOutputStream.ComputeUInt64Size(EventTime); + } + return size; + } + + public void MergeFrom(ServerStateChangeRequest other) + { + if (other == null) + { + return; + } + if (other.State != 0) + { + State = other.State; + } + if (other.EventTime != 0UL) + { + EventTime = other.EventTime; + } + } + + public void MergeFrom(pb::CodedInputStream input) + { + uint tag; + while ((tag = input.ReadTag()) != 0) + { + switch (tag) + { + default: + input.SkipLastField(); + break; + case 8: + { + State = input.ReadUInt32(); + break; + } + case 16: + { + EventTime = input.ReadUInt64(); + break; + } + } + } + } + + } + + [System.Diagnostics.DebuggerNonUserCodeAttribute()] + public sealed partial class VersionInfo : pb::IMessage + { + private static readonly pb::MessageParser _parser = new pb::MessageParser(() => new VersionInfo()); + public static pb::MessageParser Parser { get { return _parser; } } + + public static pbr::MessageDescriptor Descriptor + { + get { return Bgs.Protocol.Authentication.V1.AuthenticationServiceReflection.Descriptor.MessageTypes[11]; } + } + + pbr::MessageDescriptor pb::IMessage.Descriptor + { + get { return Descriptor; } + } + + public VersionInfo() + { + OnConstruction(); + } + + partial void OnConstruction(); + + public VersionInfo(VersionInfo other) : this() + { + number_ = other.number_; + patch_ = other.patch_; + isOptional_ = other.isOptional_; + kickTime_ = other.kickTime_; + } + + public VersionInfo Clone() + { + return new VersionInfo(this); + } + + /// Field number for the "number" field. + public const int NumberFieldNumber = 1; + private uint number_; + public uint Number + { + get { return number_; } + set + { + number_ = value; + } + } + + /// Field number for the "patch" field. + public const int PatchFieldNumber = 2; + private string patch_ = ""; + public string Patch + { + get { return patch_; } + set + { + patch_ = pb::ProtoPreconditions.CheckNotNull(value, "value"); + } + } + + /// Field number for the "is_optional" field. + public const int IsOptionalFieldNumber = 3; + private bool isOptional_; + public bool IsOptional + { + get { return isOptional_; } + set + { + isOptional_ = value; + } + } + + /// Field number for the "kick_time" field. + public const int KickTimeFieldNumber = 4; + private ulong kickTime_; + public ulong KickTime + { + get { return kickTime_; } + set + { + kickTime_ = value; + } + } + + public override bool Equals(object other) + { + return Equals(other as VersionInfo); + } + + public bool Equals(VersionInfo other) + { + if (ReferenceEquals(other, null)) + { + return false; + } + if (ReferenceEquals(other, this)) + { + return true; + } + if (Number != other.Number) return false; + if (Patch != other.Patch) return false; + if (IsOptional != other.IsOptional) return false; + if (KickTime != other.KickTime) return false; + return true; + } + + public override int GetHashCode() + { + int hash = 1; + if (Number != 0) hash ^= Number.GetHashCode(); + if (Patch.Length != 0) hash ^= Patch.GetHashCode(); + if (IsOptional != false) hash ^= IsOptional.GetHashCode(); + if (KickTime != 0UL) hash ^= KickTime.GetHashCode(); + return hash; + } + + public override string ToString() + { + return pb::JsonFormatter.ToDiagnosticString(this); + } + + public void WriteTo(pb::CodedOutputStream output) + { + if (Number != 0) + { + output.WriteRawTag(8); + output.WriteUInt32(Number); + } + if (Patch.Length != 0) + { + output.WriteRawTag(18); + output.WriteString(Patch); + } + if (IsOptional != false) + { + output.WriteRawTag(24); + output.WriteBool(IsOptional); + } + if (KickTime != 0UL) + { + output.WriteRawTag(32); + output.WriteUInt64(KickTime); + } + } + + public int CalculateSize() + { + int size = 0; + if (Number != 0) + { + size += 1 + pb::CodedOutputStream.ComputeUInt32Size(Number); + } + if (Patch.Length != 0) + { + size += 1 + pb::CodedOutputStream.ComputeStringSize(Patch); + } + if (IsOptional != false) + { + size += 1 + 1; + } + if (KickTime != 0UL) + { + size += 1 + pb::CodedOutputStream.ComputeUInt64Size(KickTime); + } + return size; + } + + public void MergeFrom(VersionInfo other) + { + if (other == null) + { + return; + } + if (other.Number != 0) + { + Number = other.Number; + } + if (other.Patch.Length != 0) + { + Patch = other.Patch; + } + if (other.IsOptional != false) + { + IsOptional = other.IsOptional; + } + if (other.KickTime != 0UL) + { + KickTime = other.KickTime; + } + } + + public void MergeFrom(pb::CodedInputStream input) + { + uint tag; + while ((tag = input.ReadTag()) != 0) + { + switch (tag) + { + default: + input.SkipLastField(); + break; + case 8: + { + Number = input.ReadUInt32(); + break; + } + case 18: + { + Patch = input.ReadString(); + break; + } + case 24: + { + IsOptional = input.ReadBool(); + break; + } + case 32: + { + KickTime = input.ReadUInt64(); + break; + } + } + } + } + + } + + [System.Diagnostics.DebuggerNonUserCodeAttribute()] + public sealed partial class VersionInfoNotification : pb::IMessage + { + private static readonly pb::MessageParser _parser = new pb::MessageParser(() => new VersionInfoNotification()); + public static pb::MessageParser Parser { get { return _parser; } } + + public static pbr::MessageDescriptor Descriptor + { + get { return Bgs.Protocol.Authentication.V1.AuthenticationServiceReflection.Descriptor.MessageTypes[12]; } + } + + pbr::MessageDescriptor pb::IMessage.Descriptor + { + get { return Descriptor; } + } + + public VersionInfoNotification() + { + OnConstruction(); + } + + partial void OnConstruction(); + + public VersionInfoNotification(VersionInfoNotification other) : this() + { + VersionInfo = other.versionInfo_ != null ? other.VersionInfo.Clone() : null; + } + + public VersionInfoNotification Clone() + { + return new VersionInfoNotification(this); + } + + /// Field number for the "version_info" field. + public const int VersionInfoFieldNumber = 1; + private Bgs.Protocol.Authentication.V1.VersionInfo versionInfo_; + public Bgs.Protocol.Authentication.V1.VersionInfo VersionInfo + { + get { return versionInfo_; } + set + { + versionInfo_ = value; + } + } + + public override bool Equals(object other) + { + return Equals(other as VersionInfoNotification); + } + + public bool Equals(VersionInfoNotification other) + { + if (ReferenceEquals(other, null)) + { + return false; + } + if (ReferenceEquals(other, this)) + { + return true; + } + if (!object.Equals(VersionInfo, other.VersionInfo)) return false; + return true; + } + + public override int GetHashCode() + { + int hash = 1; + if (versionInfo_ != null) hash ^= VersionInfo.GetHashCode(); + return hash; + } + + public override string ToString() + { + return pb::JsonFormatter.ToDiagnosticString(this); + } + + public void WriteTo(pb::CodedOutputStream output) + { + if (versionInfo_ != null) + { + output.WriteRawTag(10); + output.WriteMessage(VersionInfo); + } + } + + public int CalculateSize() + { + int size = 0; + if (versionInfo_ != null) + { + size += 1 + pb::CodedOutputStream.ComputeMessageSize(VersionInfo); + } + return size; + } + + public void MergeFrom(VersionInfoNotification other) + { + if (other == null) + { + return; + } + if (other.versionInfo_ != null) + { + if (versionInfo_ == null) + { + versionInfo_ = new Bgs.Protocol.Authentication.V1.VersionInfo(); + } + VersionInfo.MergeFrom(other.VersionInfo); + } + } + + public void MergeFrom(pb::CodedInputStream input) + { + uint tag; + while ((tag = input.ReadTag()) != 0) + { + switch (tag) + { + default: + input.SkipLastField(); + break; + case 10: + { + if (versionInfo_ == null) + { + versionInfo_ = new Bgs.Protocol.Authentication.V1.VersionInfo(); + } + input.ReadMessage(versionInfo_); + break; + } + } + } + } + + } + + [System.Diagnostics.DebuggerNonUserCodeAttribute()] + public sealed partial class MemModuleLoadRequest : pb::IMessage + { + private static readonly pb::MessageParser _parser = new pb::MessageParser(() => new MemModuleLoadRequest()); + public static pb::MessageParser Parser { get { return _parser; } } + + public static pbr::MessageDescriptor Descriptor + { + get { return Bgs.Protocol.Authentication.V1.AuthenticationServiceReflection.Descriptor.MessageTypes[13]; } + } + + pbr::MessageDescriptor pb::IMessage.Descriptor + { + get { return Descriptor; } + } + + public MemModuleLoadRequest() + { + OnConstruction(); + } + + partial void OnConstruction(); + + public MemModuleLoadRequest(MemModuleLoadRequest other) : this() + { + Handle = other.handle_ != null ? other.Handle.Clone() : null; + key_ = other.key_; + input_ = other.input_; + } + + public MemModuleLoadRequest Clone() + { + return new MemModuleLoadRequest(this); + } + + /// Field number for the "handle" field. + public const int HandleFieldNumber = 1; + private Bgs.Protocol.ContentHandle handle_; + public Bgs.Protocol.ContentHandle Handle + { + get { return handle_; } + set + { + handle_ = value; + } + } + + /// Field number for the "key" field. + public const int KeyFieldNumber = 2; + private pb::ByteString key_ = pb::ByteString.Empty; + public pb::ByteString Key + { + get { return key_; } + set + { + key_ = pb::ProtoPreconditions.CheckNotNull(value, "value"); + } + } + + /// Field number for the "input" field. + public const int InputFieldNumber = 3; + private pb::ByteString input_ = pb::ByteString.Empty; + public pb::ByteString Input + { + get { return input_; } + set + { + input_ = pb::ProtoPreconditions.CheckNotNull(value, "value"); + } + } + + public override bool Equals(object other) + { + return Equals(other as MemModuleLoadRequest); + } + + public bool Equals(MemModuleLoadRequest other) + { + if (ReferenceEquals(other, null)) + { + return false; + } + if (ReferenceEquals(other, this)) + { + return true; + } + if (!object.Equals(Handle, other.Handle)) return false; + if (Key != other.Key) return false; + if (Input != other.Input) return false; + return true; + } + + public override int GetHashCode() + { + int hash = 1; + if (handle_ != null) hash ^= Handle.GetHashCode(); + if (Key.Length != 0) hash ^= Key.GetHashCode(); + if (Input.Length != 0) hash ^= Input.GetHashCode(); + return hash; + } + + public override string ToString() + { + return pb::JsonFormatter.ToDiagnosticString(this); + } + + public void WriteTo(pb::CodedOutputStream output) + { + if (handle_ != null) + { + output.WriteRawTag(10); + output.WriteMessage(Handle); + } + if (Key.Length != 0) + { + output.WriteRawTag(18); + output.WriteBytes(Key); + } + if (Input.Length != 0) + { + output.WriteRawTag(26); + output.WriteBytes(Input); + } + } + + public int CalculateSize() + { + int size = 0; + if (handle_ != null) + { + size += 1 + pb::CodedOutputStream.ComputeMessageSize(Handle); + } + if (Key.Length != 0) + { + size += 1 + pb::CodedOutputStream.ComputeBytesSize(Key); + } + if (Input.Length != 0) + { + size += 1 + pb::CodedOutputStream.ComputeBytesSize(Input); + } + return size; + } + + public void MergeFrom(MemModuleLoadRequest other) + { + if (other == null) + { + return; + } + if (other.handle_ != null) + { + if (handle_ == null) + { + handle_ = new Bgs.Protocol.ContentHandle(); + } + Handle.MergeFrom(other.Handle); + } + if (other.Key.Length != 0) + { + Key = other.Key; + } + if (other.Input.Length != 0) + { + Input = other.Input; + } + } + + public void MergeFrom(pb::CodedInputStream input) + { + uint tag; + while ((tag = input.ReadTag()) != 0) + { + switch (tag) + { + default: + input.SkipLastField(); + break; + case 10: + { + if (handle_ == null) + { + handle_ = new Bgs.Protocol.ContentHandle(); + } + input.ReadMessage(handle_); + break; + } + case 18: + { + Key = input.ReadBytes(); + break; + } + case 26: + { + Input = input.ReadBytes(); + break; + } + } + } + } + + } + + [System.Diagnostics.DebuggerNonUserCodeAttribute()] + public sealed partial class MemModuleLoadResponse : pb::IMessage + { + private static readonly pb::MessageParser _parser = new pb::MessageParser(() => new MemModuleLoadResponse()); + public static pb::MessageParser Parser { get { return _parser; } } + + public static pbr::MessageDescriptor Descriptor + { + get { return Bgs.Protocol.Authentication.V1.AuthenticationServiceReflection.Descriptor.MessageTypes[14]; } + } + + pbr::MessageDescriptor pb::IMessage.Descriptor + { + get { return Descriptor; } + } + + public MemModuleLoadResponse() + { + OnConstruction(); + } + + partial void OnConstruction(); + + public MemModuleLoadResponse(MemModuleLoadResponse other) : this() + { + data_ = other.data_; + } + + public MemModuleLoadResponse Clone() + { + return new MemModuleLoadResponse(this); + } + + /// Field number for the "data" field. + public const int DataFieldNumber = 1; + private pb::ByteString data_ = pb::ByteString.Empty; + public pb::ByteString Data + { + get { return data_; } + set + { + data_ = pb::ProtoPreconditions.CheckNotNull(value, "value"); + } + } + + public override bool Equals(object other) + { + return Equals(other as MemModuleLoadResponse); + } + + public bool Equals(MemModuleLoadResponse other) + { + if (ReferenceEquals(other, null)) + { + return false; + } + if (ReferenceEquals(other, this)) + { + return true; + } + if (Data != other.Data) return false; + return true; + } + + public override int GetHashCode() + { + int hash = 1; + if (Data.Length != 0) hash ^= Data.GetHashCode(); + return hash; + } + + public override string ToString() + { + return pb::JsonFormatter.ToDiagnosticString(this); + } + + public void WriteTo(pb::CodedOutputStream output) + { + if (Data.Length != 0) + { + output.WriteRawTag(10); + output.WriteBytes(Data); + } + } + + public int CalculateSize() + { + int size = 0; + if (Data.Length != 0) + { + size += 1 + pb::CodedOutputStream.ComputeBytesSize(Data); + } + return size; + } + + public void MergeFrom(MemModuleLoadResponse other) + { + if (other == null) + { + return; + } + if (other.Data.Length != 0) + { + Data = other.Data; + } + } + + public void MergeFrom(pb::CodedInputStream input) + { + uint tag; + while ((tag = input.ReadTag()) != 0) + { + switch (tag) + { + default: + input.SkipLastField(); + break; + case 10: + { + Data = input.ReadBytes(); + break; + } + } + } + } + + } + + [System.Diagnostics.DebuggerNonUserCodeAttribute()] + public sealed partial class SelectGameAccountRequest : pb::IMessage + { + private static readonly pb::MessageParser _parser = new pb::MessageParser(() => new SelectGameAccountRequest()); + public static pb::MessageParser Parser { get { return _parser; } } + + public static pbr::MessageDescriptor Descriptor + { + get { return Bgs.Protocol.Authentication.V1.AuthenticationServiceReflection.Descriptor.MessageTypes[15]; } + } + + pbr::MessageDescriptor pb::IMessage.Descriptor + { + get { return Descriptor; } + } + + public SelectGameAccountRequest() + { + OnConstruction(); + } + + partial void OnConstruction(); + + public SelectGameAccountRequest(SelectGameAccountRequest other) : this() + { + GameAccountId = other.gameAccountId_ != null ? other.GameAccountId.Clone() : null; + } + + public SelectGameAccountRequest Clone() + { + return new SelectGameAccountRequest(this); + } + + /// Field number for the "game_account_id" field. + public const int GameAccountIdFieldNumber = 1; + private Bgs.Protocol.EntityId gameAccountId_; + public Bgs.Protocol.EntityId GameAccountId + { + get { return gameAccountId_; } + set + { + gameAccountId_ = value; + } + } + + public override bool Equals(object other) + { + return Equals(other as SelectGameAccountRequest); + } + + public bool Equals(SelectGameAccountRequest other) + { + if (ReferenceEquals(other, null)) + { + return false; + } + if (ReferenceEquals(other, this)) + { + return true; + } + if (!object.Equals(GameAccountId, other.GameAccountId)) return false; + return true; + } + + public override int GetHashCode() + { + int hash = 1; + if (gameAccountId_ != null) hash ^= GameAccountId.GetHashCode(); + return hash; + } + + public override string ToString() + { + return pb::JsonFormatter.ToDiagnosticString(this); + } + + public void WriteTo(pb::CodedOutputStream output) + { + if (gameAccountId_ != null) + { + output.WriteRawTag(10); + output.WriteMessage(GameAccountId); + } + } + + public int CalculateSize() + { + int size = 0; + if (gameAccountId_ != null) + { + size += 1 + pb::CodedOutputStream.ComputeMessageSize(GameAccountId); + } + return size; + } + + public void MergeFrom(SelectGameAccountRequest other) + { + if (other == null) + { + return; + } + if (other.gameAccountId_ != null) + { + if (gameAccountId_ == null) + { + gameAccountId_ = new Bgs.Protocol.EntityId(); + } + GameAccountId.MergeFrom(other.GameAccountId); + } + } + + public void MergeFrom(pb::CodedInputStream input) + { + uint tag; + while ((tag = input.ReadTag()) != 0) + { + switch (tag) + { + default: + input.SkipLastField(); + break; + case 10: + { + if (gameAccountId_ == null) + { + gameAccountId_ = new Bgs.Protocol.EntityId(); + } + input.ReadMessage(gameAccountId_); + break; + } + } + } + } + + } + + [System.Diagnostics.DebuggerNonUserCodeAttribute()] + public sealed partial class GameAccountSelectedRequest : pb::IMessage + { + private static readonly pb::MessageParser _parser = new pb::MessageParser(() => new GameAccountSelectedRequest()); + public static pb::MessageParser Parser { get { return _parser; } } + + public static pbr::MessageDescriptor Descriptor + { + get { return Bgs.Protocol.Authentication.V1.AuthenticationServiceReflection.Descriptor.MessageTypes[16]; } + } + + pbr::MessageDescriptor pb::IMessage.Descriptor + { + get { return Descriptor; } + } + + public GameAccountSelectedRequest() + { + OnConstruction(); + } + + partial void OnConstruction(); + + public GameAccountSelectedRequest(GameAccountSelectedRequest other) : this() + { + result_ = other.result_; + GameAccountId = other.gameAccountId_ != null ? other.GameAccountId.Clone() : null; + } + + public GameAccountSelectedRequest Clone() + { + return new GameAccountSelectedRequest(this); + } + + /// Field number for the "result" field. + public const int ResultFieldNumber = 1; + private uint result_; + public uint Result + { + get { return result_; } + set + { + result_ = value; + } + } + + /// Field number for the "game_account_id" field. + public const int GameAccountIdFieldNumber = 2; + private Bgs.Protocol.EntityId gameAccountId_; + public Bgs.Protocol.EntityId GameAccountId + { + get { return gameAccountId_; } + set + { + gameAccountId_ = value; + } + } + + public override bool Equals(object other) + { + return Equals(other as GameAccountSelectedRequest); + } + + public bool Equals(GameAccountSelectedRequest other) + { + if (ReferenceEquals(other, null)) + { + return false; + } + if (ReferenceEquals(other, this)) + { + return true; + } + if (Result != other.Result) return false; + if (!object.Equals(GameAccountId, other.GameAccountId)) return false; + return true; + } + + public override int GetHashCode() + { + int hash = 1; + if (Result != 0) hash ^= Result.GetHashCode(); + if (gameAccountId_ != null) hash ^= GameAccountId.GetHashCode(); + return hash; + } + + public override string ToString() + { + return pb::JsonFormatter.ToDiagnosticString(this); + } + + public void WriteTo(pb::CodedOutputStream output) + { + if (Result != 0) + { + output.WriteRawTag(8); + output.WriteUInt32(Result); + } + if (gameAccountId_ != null) + { + output.WriteRawTag(18); + output.WriteMessage(GameAccountId); + } + } + + public int CalculateSize() + { + int size = 0; + if (Result != 0) + { + size += 1 + pb::CodedOutputStream.ComputeUInt32Size(Result); + } + if (gameAccountId_ != null) + { + size += 1 + pb::CodedOutputStream.ComputeMessageSize(GameAccountId); + } + return size; + } + + public void MergeFrom(GameAccountSelectedRequest other) + { + if (other == null) + { + return; + } + if (other.Result != 0) + { + Result = other.Result; + } + if (other.gameAccountId_ != null) + { + if (gameAccountId_ == null) + { + gameAccountId_ = new Bgs.Protocol.EntityId(); + } + GameAccountId.MergeFrom(other.GameAccountId); + } + } + + public void MergeFrom(pb::CodedInputStream input) + { + uint tag; + while ((tag = input.ReadTag()) != 0) + { + switch (tag) + { + default: + input.SkipLastField(); + break; + case 8: + { + Result = input.ReadUInt32(); + break; + } + case 18: + { + if (gameAccountId_ == null) + { + gameAccountId_ = new Bgs.Protocol.EntityId(); + } + input.ReadMessage(gameAccountId_); + break; + } + } + } + } + + } + + [System.Diagnostics.DebuggerNonUserCodeAttribute()] + public sealed partial class GenerateWebCredentialsRequest : pb::IMessage + { + private static readonly pb::MessageParser _parser = new pb::MessageParser(() => new GenerateWebCredentialsRequest()); + public static pb::MessageParser Parser { get { return _parser; } } + + public static pbr::MessageDescriptor Descriptor + { + get { return Bgs.Protocol.Authentication.V1.AuthenticationServiceReflection.Descriptor.MessageTypes[17]; } + } + + pbr::MessageDescriptor pb::IMessage.Descriptor + { + get { return Descriptor; } + } + + public GenerateWebCredentialsRequest() + { + OnConstruction(); + } + + partial void OnConstruction(); + + public GenerateWebCredentialsRequest(GenerateWebCredentialsRequest other) : this() + { + program_ = other.program_; + } + + public GenerateWebCredentialsRequest Clone() + { + return new GenerateWebCredentialsRequest(this); + } + + /// Field number for the "program" field. + public const int ProgramFieldNumber = 1; + private uint program_; + public uint Program + { + get { return program_; } + set + { + program_ = value; + } + } + + public override bool Equals(object other) + { + return Equals(other as GenerateWebCredentialsRequest); + } + + public bool Equals(GenerateWebCredentialsRequest other) + { + if (ReferenceEquals(other, null)) + { + return false; + } + if (ReferenceEquals(other, this)) + { + return true; + } + if (Program != other.Program) return false; + return true; + } + + public override int GetHashCode() + { + int hash = 1; + if (Program != 0) hash ^= Program.GetHashCode(); + return hash; + } + + public override string ToString() + { + return pb::JsonFormatter.ToDiagnosticString(this); + } + + public void WriteTo(pb::CodedOutputStream output) + { + if (Program != 0) + { + output.WriteRawTag(13); + output.WriteFixed32(Program); + } + } + + public int CalculateSize() + { + int size = 0; + if (Program != 0) + { + size += 1 + 4; + } + return size; + } + + public void MergeFrom(GenerateWebCredentialsRequest other) + { + if (other == null) + { + return; + } + if (other.Program != 0) + { + Program = other.Program; + } + } + + public void MergeFrom(pb::CodedInputStream input) + { + uint tag; + while ((tag = input.ReadTag()) != 0) + { + switch (tag) + { + default: + input.SkipLastField(); + break; + case 13: + { + Program = input.ReadFixed32(); + break; + } + } + } + } + + } + + [System.Diagnostics.DebuggerNonUserCodeAttribute()] + public sealed partial class GenerateWebCredentialsResponse : pb::IMessage + { + private static readonly pb::MessageParser _parser = new pb::MessageParser(() => new GenerateWebCredentialsResponse()); + public static pb::MessageParser Parser { get { return _parser; } } + + public static pbr::MessageDescriptor Descriptor + { + get { return Bgs.Protocol.Authentication.V1.AuthenticationServiceReflection.Descriptor.MessageTypes[18]; } + } + + pbr::MessageDescriptor pb::IMessage.Descriptor + { + get { return Descriptor; } + } + + public GenerateWebCredentialsResponse() + { + OnConstruction(); + } + + partial void OnConstruction(); + + public GenerateWebCredentialsResponse(GenerateWebCredentialsResponse other) : this() + { + webCredentials_ = other.webCredentials_; + } + + public GenerateWebCredentialsResponse Clone() + { + return new GenerateWebCredentialsResponse(this); + } + + /// Field number for the "web_credentials" field. + public const int WebCredentialsFieldNumber = 1; + private pb::ByteString webCredentials_ = pb::ByteString.Empty; + public pb::ByteString WebCredentials + { + get { return webCredentials_; } + set + { + webCredentials_ = pb::ProtoPreconditions.CheckNotNull(value, "value"); + } + } + + public override bool Equals(object other) + { + return Equals(other as GenerateWebCredentialsResponse); + } + + public bool Equals(GenerateWebCredentialsResponse other) + { + if (ReferenceEquals(other, null)) + { + return false; + } + if (ReferenceEquals(other, this)) + { + return true; + } + if (WebCredentials != other.WebCredentials) return false; + return true; + } + + public override int GetHashCode() + { + int hash = 1; + if (WebCredentials.Length != 0) hash ^= WebCredentials.GetHashCode(); + return hash; + } + + public override string ToString() + { + return pb::JsonFormatter.ToDiagnosticString(this); + } + + public void WriteTo(pb::CodedOutputStream output) + { + if (WebCredentials.Length != 0) + { + output.WriteRawTag(10); + output.WriteBytes(WebCredentials); + } + } + + public int CalculateSize() + { + int size = 0; + if (WebCredentials.Length != 0) + { + size += 1 + pb::CodedOutputStream.ComputeBytesSize(WebCredentials); + } + return size; + } + + public void MergeFrom(GenerateWebCredentialsResponse other) + { + if (other == null) + { + return; + } + if (other.WebCredentials.Length != 0) + { + WebCredentials = other.WebCredentials; + } + } + + public void MergeFrom(pb::CodedInputStream input) + { + uint tag; + while ((tag = input.ReadTag()) != 0) + { + switch (tag) + { + default: + input.SkipLastField(); + break; + case 10: + { + WebCredentials = input.ReadBytes(); + break; + } + } + } + } + + } + + [System.Diagnostics.DebuggerNonUserCodeAttribute()] + public sealed partial class VerifyWebCredentialsRequest : pb::IMessage + { + private static readonly pb::MessageParser _parser = new pb::MessageParser(() => new VerifyWebCredentialsRequest()); + public static pb::MessageParser Parser { get { return _parser; } } + + public static pbr::MessageDescriptor Descriptor + { + get { return Bgs.Protocol.Authentication.V1.AuthenticationServiceReflection.Descriptor.MessageTypes[19]; } + } + + pbr::MessageDescriptor pb::IMessage.Descriptor + { + get { return Descriptor; } + } + + public VerifyWebCredentialsRequest() + { + OnConstruction(); + } + + partial void OnConstruction(); + + public VerifyWebCredentialsRequest(VerifyWebCredentialsRequest other) : this() + { + webCredentials_ = other.webCredentials_; + } + + public VerifyWebCredentialsRequest Clone() + { + return new VerifyWebCredentialsRequest(this); + } + + /// Field number for the "web_credentials" field. + public const int WebCredentialsFieldNumber = 1; + private pb::ByteString webCredentials_ = pb::ByteString.Empty; + public pb::ByteString WebCredentials + { + get { return webCredentials_; } + set + { + webCredentials_ = pb::ProtoPreconditions.CheckNotNull(value, "value"); + } + } + + public override bool Equals(object other) + { + return Equals(other as VerifyWebCredentialsRequest); + } + + public bool Equals(VerifyWebCredentialsRequest other) + { + if (ReferenceEquals(other, null)) + { + return false; + } + if (ReferenceEquals(other, this)) + { + return true; + } + if (WebCredentials != other.WebCredentials) return false; + return true; + } + + public override int GetHashCode() + { + int hash = 1; + if (WebCredentials.Length != 0) hash ^= WebCredentials.GetHashCode(); + return hash; + } + + public override string ToString() + { + return pb::JsonFormatter.ToDiagnosticString(this); + } + + public void WriteTo(pb::CodedOutputStream output) + { + if (WebCredentials.Length != 0) + { + output.WriteRawTag(10); + output.WriteBytes(WebCredentials); + } + } + + public int CalculateSize() + { + int size = 0; + if (WebCredentials.Length != 0) + { + size += 1 + pb::CodedOutputStream.ComputeBytesSize(WebCredentials); + } + return size; + } + + public void MergeFrom(VerifyWebCredentialsRequest other) + { + if (other == null) + { + return; + } + if (other.WebCredentials.Length != 0) + { + WebCredentials = other.WebCredentials; + } + } + + public void MergeFrom(pb::CodedInputStream input) + { + uint tag; + while ((tag = input.ReadTag()) != 0) + { + switch (tag) + { + default: + input.SkipLastField(); + break; + case 10: + { + WebCredentials = input.ReadBytes(); + break; + } + } + } + } + + } + + #endregion +} + +#endregion Designer generated code diff --git a/Framework/Proto/ChallengeService.cs b/Framework/Proto/ChallengeService.cs new file mode 100644 index 000000000..7abf7dafe --- /dev/null +++ b/Framework/Proto/ChallengeService.cs @@ -0,0 +1,2524 @@ +// Generated by the protocol buffer compiler. DO NOT EDIT! +// source: bgs/low/pb/client/challenge_service.proto +#pragma warning disable 1591, 0612, 3021 +#region Designer generated code + +using pb = Google.Protobuf; +using pbc = Google.Protobuf.Collections; +using pbr = Google.Protobuf.Reflection; +namespace Bgs.Protocol.Challenge.V1 +{ + + /// Holder for reflection information generated from bgs/low/pb/client/challenge_service.proto + [System.Diagnostics.DebuggerNonUserCodeAttribute()] + public static partial class ChallengeServiceReflection + { + #region Descriptor + /// File descriptor for bgs/low/pb/client/challenge_service.proto + public static pbr::FileDescriptor Descriptor + { + get { return descriptor; } + } + private static pbr::FileDescriptor descriptor; + + static ChallengeServiceReflection() + { + byte[] descriptorData = System.Convert.FromBase64String( + string.Concat( + "CiliZ3MvbG93L3BiL2NsaWVudC9jaGFsbGVuZ2Vfc2VydmljZS5wcm90bxIZ", + "YmdzLnByb3RvY29sLmNoYWxsZW5nZS52MRonYmdzL2xvdy9wYi9jbGllbnQv", + "YXR0cmlidXRlX3R5cGVzLnByb3RvGiRiZ3MvbG93L3BiL2NsaWVudC9lbnRp", + "dHlfdHlwZXMucHJvdG8aIWJncy9sb3cvcGIvY2xpZW50L3JwY190eXBlcy5w", + "cm90byJICglDaGFsbGVuZ2USDAoEdHlwZRgBIAEoBxIMCgRpbmZvGAIgASgJ", + "Eg4KBmFuc3dlchgDIAEoCRIPCgdyZXRyaWVzGAQgASgNIlcKFkNoYWxsZW5n", + "ZVBpY2tlZFJlcXVlc3QSEQoJY2hhbGxlbmdlGAEgASgHEgoKAmlkGAIgASgN", + "Eh4KFm5ld19jaGFsbGVuZ2VfcHJvdG9jb2wYAyABKAgiJwoXQ2hhbGxlbmdl", + "UGlja2VkUmVzcG9uc2USDAoEZGF0YRgBIAEoDCJEChhDaGFsbGVuZ2VBbnN3", + "ZXJlZFJlcXVlc3QSDgoGYW5zd2VyGAEgASgJEgwKBGRhdGEYAiABKAwSCgoC", + "aWQYAyABKA0iVQoZQ2hhbGxlbmdlQW5zd2VyZWRSZXNwb25zZRIMCgRkYXRh", + "GAEgASgMEhAKCGRvX3JldHJ5GAIgASgIEhgKEHJlY29yZF9ub3RfZm91bmQY", + "AyABKAgiJwoZQ2hhbGxlbmdlQ2FuY2VsbGVkUmVxdWVzdBIKCgJpZBgBIAEo", + "DSLTAgoaU2VuZENoYWxsZW5nZVRvVXNlclJlcXVlc3QSKAoHcGVlcl9pZBgB", + "IAEoCzIXLmJncy5wcm90b2NvbC5Qcm9jZXNzSWQSLwoPZ2FtZV9hY2NvdW50", + "X2lkGAIgASgLMhYuYmdzLnByb3RvY29sLkVudGl0eUlkEjgKCmNoYWxsZW5n", + "ZXMYAyADKAsyJC5iZ3MucHJvdG9jb2wuY2hhbGxlbmdlLnYxLkNoYWxsZW5n", + "ZRIPCgdjb250ZXh0GAQgASgHEg8KB3RpbWVvdXQYBSABKAQSKwoKYXR0cmli", + "dXRlcxgGIAMoCzIXLmJncy5wcm90b2NvbC5BdHRyaWJ1dGUSJQoEaG9zdBgH", + "IAEoCzIXLmJncy5wcm90b2NvbC5Qcm9jZXNzSWQSKgoKYWNjb3VudF9pZBgI", + "IAEoCzIWLmJncy5wcm90b2NvbC5FbnRpdHlJZCIpChtTZW5kQ2hhbGxlbmdl", + "VG9Vc2VyUmVzcG9uc2USCgoCaWQYASABKA0i3QEKFENoYWxsZW5nZVVzZXJS", + "ZXF1ZXN0EjgKCmNoYWxsZW5nZXMYASADKAsyJC5iZ3MucHJvdG9jb2wuY2hh", + "bGxlbmdlLnYxLkNoYWxsZW5nZRIPCgdjb250ZXh0GAIgASgHEgoKAmlkGAMg", + "ASgNEhAKCGRlYWRsaW5lGAQgASgEEisKCmF0dHJpYnV0ZXMYBSADKAsyFy5i", + "Z3MucHJvdG9jb2wuQXR0cmlidXRlEi8KD2dhbWVfYWNjb3VudF9pZBgGIAEo", + "CzIWLmJncy5wcm90b2NvbC5FbnRpdHlJZCJUChZDaGFsbGVuZ2VSZXN1bHRS", + "ZXF1ZXN0EgoKAmlkGAEgASgNEgwKBHR5cGUYAiABKAcSEAoIZXJyb3JfaWQY", + "AyABKA0SDgoGYW5zd2VyGAQgASgMIlgKGENoYWxsZW5nZUV4dGVybmFsUmVx", + "dWVzdBIVCg1yZXF1ZXN0X3Rva2VuGAEgASgJEhQKDHBheWxvYWRfdHlwZRgC", + "IAEoCRIPCgdwYXlsb2FkGAMgASgMIkAKF0NoYWxsZW5nZUV4dGVybmFsUmVz", + "dWx0EhUKDXJlcXVlc3RfdG9rZW4YASABKAkSDgoGcGFzc2VkGAIgASgIMvUD", + "ChBDaGFsbGVuZ2VTZXJ2aWNlEngKD0NoYWxsZW5nZVBpY2tlZBIxLmJncy5w", + "cm90b2NvbC5jaGFsbGVuZ2UudjEuQ2hhbGxlbmdlUGlja2VkUmVxdWVzdBoy", + "LmJncy5wcm90b2NvbC5jaGFsbGVuZ2UudjEuQ2hhbGxlbmdlUGlja2VkUmVz", + "cG9uc2USfgoRQ2hhbGxlbmdlQW5zd2VyZWQSMy5iZ3MucHJvdG9jb2wuY2hh", + "bGxlbmdlLnYxLkNoYWxsZW5nZUFuc3dlcmVkUmVxdWVzdBo0LmJncy5wcm90", + "b2NvbC5jaGFsbGVuZ2UudjEuQ2hhbGxlbmdlQW5zd2VyZWRSZXNwb25zZRJg", + "ChJDaGFsbGVuZ2VDYW5jZWxsZWQSNC5iZ3MucHJvdG9jb2wuY2hhbGxlbmdl", + "LnYxLkNoYWxsZW5nZUNhbmNlbGxlZFJlcXVlc3QaFC5iZ3MucHJvdG9jb2wu", + "Tm9EYXRhEoQBChNTZW5kQ2hhbGxlbmdlVG9Vc2VyEjUuYmdzLnByb3RvY29s", + "LmNoYWxsZW5nZS52MS5TZW5kQ2hhbGxlbmdlVG9Vc2VyUmVxdWVzdBo2LmJn", + "cy5wcm90b2NvbC5jaGFsbGVuZ2UudjEuU2VuZENoYWxsZW5nZVRvVXNlclJl", + "c3BvbnNlMqgDChFDaGFsbGVuZ2VMaXN0ZW5lchJdCg9PbkNoYWxsZW5nZVVz", + "ZXISLy5iZ3MucHJvdG9jb2wuY2hhbGxlbmdlLnYxLkNoYWxsZW5nZVVzZXJS", + "ZXF1ZXN0GhkuYmdzLnByb3RvY29sLk5PX1JFU1BPTlNFEmEKEU9uQ2hhbGxl", + "bmdlUmVzdWx0EjEuYmdzLnByb3RvY29sLmNoYWxsZW5nZS52MS5DaGFsbGVu", + "Z2VSZXN1bHRSZXF1ZXN0GhkuYmdzLnByb3RvY29sLk5PX1JFU1BPTlNFEmUK", + "E09uRXh0ZXJuYWxDaGFsbGVuZ2USMy5iZ3MucHJvdG9jb2wuY2hhbGxlbmdl", + "LnYxLkNoYWxsZW5nZUV4dGVybmFsUmVxdWVzdBoZLmJncy5wcm90b2NvbC5O", + "T19SRVNQT05TRRJqChlPbkV4dGVybmFsQ2hhbGxlbmdlUmVzdWx0EjIuYmdz", + "LnByb3RvY29sLmNoYWxsZW5nZS52MS5DaGFsbGVuZ2VFeHRlcm5hbFJlc3Vs", + "dBoZLmJncy5wcm90b2NvbC5OT19SRVNQT05TRUIFSAKAAQBiBnByb3RvMw==")); + descriptor = pbr::FileDescriptor.FromGeneratedCode(descriptorData, + new pbr::FileDescriptor[] { Bgs.Protocol.AttributeTypesReflection.Descriptor, Bgs.Protocol.EntityTypesReflection.Descriptor, Bgs.Protocol.RpcTypesReflection.Descriptor, }, + new pbr::GeneratedClrTypeInfo(null, new pbr::GeneratedClrTypeInfo[] { + new pbr::GeneratedClrTypeInfo(typeof(Bgs.Protocol.Challenge.V1.Challenge), Bgs.Protocol.Challenge.V1.Challenge.Parser, new[]{ "Type", "Info", "Answer", "Retries" }, null, null, null), + new pbr::GeneratedClrTypeInfo(typeof(Bgs.Protocol.Challenge.V1.ChallengePickedRequest), Bgs.Protocol.Challenge.V1.ChallengePickedRequest.Parser, new[]{ "Challenge", "Id", "NewChallengeProtocol" }, null, null, null), + new pbr::GeneratedClrTypeInfo(typeof(Bgs.Protocol.Challenge.V1.ChallengePickedResponse), Bgs.Protocol.Challenge.V1.ChallengePickedResponse.Parser, new[]{ "Data" }, null, null, null), + new pbr::GeneratedClrTypeInfo(typeof(Bgs.Protocol.Challenge.V1.ChallengeAnsweredRequest), Bgs.Protocol.Challenge.V1.ChallengeAnsweredRequest.Parser, new[]{ "Answer", "Data", "Id" }, null, null, null), + new pbr::GeneratedClrTypeInfo(typeof(Bgs.Protocol.Challenge.V1.ChallengeAnsweredResponse), Bgs.Protocol.Challenge.V1.ChallengeAnsweredResponse.Parser, new[]{ "Data", "DoRetry", "RecordNotFound" }, null, null, null), + new pbr::GeneratedClrTypeInfo(typeof(Bgs.Protocol.Challenge.V1.ChallengeCancelledRequest), Bgs.Protocol.Challenge.V1.ChallengeCancelledRequest.Parser, new[]{ "Id" }, null, null, null), + new pbr::GeneratedClrTypeInfo(typeof(Bgs.Protocol.Challenge.V1.SendChallengeToUserRequest), Bgs.Protocol.Challenge.V1.SendChallengeToUserRequest.Parser, new[]{ "PeerId", "GameAccountId", "Challenges", "Context", "Timeout", "Attributes", "Host", "AccountId" }, null, null, null), + new pbr::GeneratedClrTypeInfo(typeof(Bgs.Protocol.Challenge.V1.SendChallengeToUserResponse), Bgs.Protocol.Challenge.V1.SendChallengeToUserResponse.Parser, new[]{ "Id" }, null, null, null), + new pbr::GeneratedClrTypeInfo(typeof(Bgs.Protocol.Challenge.V1.ChallengeUserRequest), Bgs.Protocol.Challenge.V1.ChallengeUserRequest.Parser, new[]{ "Challenges", "Context", "Id", "Deadline", "Attributes", "GameAccountId" }, null, null, null), + new pbr::GeneratedClrTypeInfo(typeof(Bgs.Protocol.Challenge.V1.ChallengeResultRequest), Bgs.Protocol.Challenge.V1.ChallengeResultRequest.Parser, new[]{ "Id", "Type", "ErrorId", "Answer" }, null, null, null), + new pbr::GeneratedClrTypeInfo(typeof(Bgs.Protocol.Challenge.V1.ChallengeExternalRequest), Bgs.Protocol.Challenge.V1.ChallengeExternalRequest.Parser, new[]{ "RequestToken", "PayloadType", "Payload" }, null, null, null), + new pbr::GeneratedClrTypeInfo(typeof(Bgs.Protocol.Challenge.V1.ChallengeExternalResult), Bgs.Protocol.Challenge.V1.ChallengeExternalResult.Parser, new[]{ "RequestToken", "Passed" }, null, null, null) + })); + } + #endregion + } + + #region Messages + [System.Diagnostics.DebuggerNonUserCodeAttribute()] + public sealed partial class Challenge : pb::IMessage + { + private static readonly pb::MessageParser _parser = new pb::MessageParser(() => new Challenge()); + public static pb::MessageParser Parser { get { return _parser; } } + + public static pbr::MessageDescriptor Descriptor + { + get { return Bgs.Protocol.Challenge.V1.ChallengeServiceReflection.Descriptor.MessageTypes[0]; } + } + + pbr::MessageDescriptor pb::IMessage.Descriptor + { + get { return Descriptor; } + } + + public Challenge() + { + OnConstruction(); + } + + partial void OnConstruction(); + + public Challenge(Challenge other) : this() + { + type_ = other.type_; + info_ = other.info_; + answer_ = other.answer_; + retries_ = other.retries_; + } + + public Challenge Clone() + { + return new Challenge(this); + } + + /// Field number for the "type" field. + public const int TypeFieldNumber = 1; + private uint type_; + public uint Type + { + get { return type_; } + set + { + type_ = value; + } + } + + /// Field number for the "info" field. + public const int InfoFieldNumber = 2; + private string info_ = ""; + public string Info + { + get { return info_; } + set + { + info_ = pb::ProtoPreconditions.CheckNotNull(value, "value"); + } + } + + /// Field number for the "answer" field. + public const int AnswerFieldNumber = 3; + private string answer_ = ""; + public string Answer + { + get { return answer_; } + set + { + answer_ = pb::ProtoPreconditions.CheckNotNull(value, "value"); + } + } + + /// Field number for the "retries" field. + public const int RetriesFieldNumber = 4; + private uint retries_; + public uint Retries + { + get { return retries_; } + set + { + retries_ = value; + } + } + + public override bool Equals(object other) + { + return Equals(other as Challenge); + } + + public bool Equals(Challenge other) + { + if (ReferenceEquals(other, null)) + { + return false; + } + if (ReferenceEquals(other, this)) + { + return true; + } + if (Type != other.Type) return false; + if (Info != other.Info) return false; + if (Answer != other.Answer) return false; + if (Retries != other.Retries) return false; + return true; + } + + public override int GetHashCode() + { + int hash = 1; + if (Type != 0) hash ^= Type.GetHashCode(); + if (Info.Length != 0) hash ^= Info.GetHashCode(); + if (Answer.Length != 0) hash ^= Answer.GetHashCode(); + if (Retries != 0) hash ^= Retries.GetHashCode(); + return hash; + } + + public override string ToString() + { + return pb::JsonFormatter.ToDiagnosticString(this); + } + + public void WriteTo(pb::CodedOutputStream output) + { + if (Type != 0) + { + output.WriteRawTag(13); + output.WriteFixed32(Type); + } + if (Info.Length != 0) + { + output.WriteRawTag(18); + output.WriteString(Info); + } + if (Answer.Length != 0) + { + output.WriteRawTag(26); + output.WriteString(Answer); + } + if (Retries != 0) + { + output.WriteRawTag(32); + output.WriteUInt32(Retries); + } + } + + public int CalculateSize() + { + int size = 0; + if (Type != 0) + { + size += 1 + 4; + } + if (Info.Length != 0) + { + size += 1 + pb::CodedOutputStream.ComputeStringSize(Info); + } + if (Answer.Length != 0) + { + size += 1 + pb::CodedOutputStream.ComputeStringSize(Answer); + } + if (Retries != 0) + { + size += 1 + pb::CodedOutputStream.ComputeUInt32Size(Retries); + } + return size; + } + + public void MergeFrom(Challenge other) + { + if (other == null) + { + return; + } + if (other.Type != 0) + { + Type = other.Type; + } + if (other.Info.Length != 0) + { + Info = other.Info; + } + if (other.Answer.Length != 0) + { + Answer = other.Answer; + } + if (other.Retries != 0) + { + Retries = other.Retries; + } + } + + public void MergeFrom(pb::CodedInputStream input) + { + uint tag; + while ((tag = input.ReadTag()) != 0) + { + switch (tag) + { + default: + input.SkipLastField(); + break; + case 13: + { + Type = input.ReadFixed32(); + break; + } + case 18: + { + Info = input.ReadString(); + break; + } + case 26: + { + Answer = input.ReadString(); + break; + } + case 32: + { + Retries = input.ReadUInt32(); + break; + } + } + } + } + + } + + [System.Diagnostics.DebuggerNonUserCodeAttribute()] + public sealed partial class ChallengePickedRequest : pb::IMessage + { + private static readonly pb::MessageParser _parser = new pb::MessageParser(() => new ChallengePickedRequest()); + public static pb::MessageParser Parser { get { return _parser; } } + + public static pbr::MessageDescriptor Descriptor + { + get { return Bgs.Protocol.Challenge.V1.ChallengeServiceReflection.Descriptor.MessageTypes[1]; } + } + + pbr::MessageDescriptor pb::IMessage.Descriptor + { + get { return Descriptor; } + } + + public ChallengePickedRequest() + { + OnConstruction(); + } + + partial void OnConstruction(); + + public ChallengePickedRequest(ChallengePickedRequest other) : this() + { + challenge_ = other.challenge_; + id_ = other.id_; + newChallengeProtocol_ = other.newChallengeProtocol_; + } + + public ChallengePickedRequest Clone() + { + return new ChallengePickedRequest(this); + } + + /// Field number for the "challenge" field. + public const int ChallengeFieldNumber = 1; + private uint challenge_; + public uint Challenge + { + get { return challenge_; } + set + { + challenge_ = value; + } + } + + /// Field number for the "id" field. + public const int IdFieldNumber = 2; + private uint id_; + public uint Id + { + get { return id_; } + set + { + id_ = value; + } + } + + /// Field number for the "new_challenge_protocol" field. + public const int NewChallengeProtocolFieldNumber = 3; + private bool newChallengeProtocol_; + public bool NewChallengeProtocol + { + get { return newChallengeProtocol_; } + set + { + newChallengeProtocol_ = value; + } + } + + public override bool Equals(object other) + { + return Equals(other as ChallengePickedRequest); + } + + public bool Equals(ChallengePickedRequest other) + { + if (ReferenceEquals(other, null)) + { + return false; + } + if (ReferenceEquals(other, this)) + { + return true; + } + if (Challenge != other.Challenge) return false; + if (Id != other.Id) return false; + if (NewChallengeProtocol != other.NewChallengeProtocol) return false; + return true; + } + + public override int GetHashCode() + { + int hash = 1; + if (Challenge != 0) hash ^= Challenge.GetHashCode(); + if (Id != 0) hash ^= Id.GetHashCode(); + if (NewChallengeProtocol != false) hash ^= NewChallengeProtocol.GetHashCode(); + return hash; + } + + public override string ToString() + { + return pb::JsonFormatter.ToDiagnosticString(this); + } + + public void WriteTo(pb::CodedOutputStream output) + { + if (Challenge != 0) + { + output.WriteRawTag(13); + output.WriteFixed32(Challenge); + } + if (Id != 0) + { + output.WriteRawTag(16); + output.WriteUInt32(Id); + } + if (NewChallengeProtocol != false) + { + output.WriteRawTag(24); + output.WriteBool(NewChallengeProtocol); + } + } + + public int CalculateSize() + { + int size = 0; + if (Challenge != 0) + { + size += 1 + 4; + } + if (Id != 0) + { + size += 1 + pb::CodedOutputStream.ComputeUInt32Size(Id); + } + if (NewChallengeProtocol != false) + { + size += 1 + 1; + } + return size; + } + + public void MergeFrom(ChallengePickedRequest other) + { + if (other == null) + { + return; + } + if (other.Challenge != 0) + { + Challenge = other.Challenge; + } + if (other.Id != 0) + { + Id = other.Id; + } + if (other.NewChallengeProtocol != false) + { + NewChallengeProtocol = other.NewChallengeProtocol; + } + } + + public void MergeFrom(pb::CodedInputStream input) + { + uint tag; + while ((tag = input.ReadTag()) != 0) + { + switch (tag) + { + default: + input.SkipLastField(); + break; + case 13: + { + Challenge = input.ReadFixed32(); + break; + } + case 16: + { + Id = input.ReadUInt32(); + break; + } + case 24: + { + NewChallengeProtocol = input.ReadBool(); + break; + } + } + } + } + + } + + [System.Diagnostics.DebuggerNonUserCodeAttribute()] + public sealed partial class ChallengePickedResponse : pb::IMessage + { + private static readonly pb::MessageParser _parser = new pb::MessageParser(() => new ChallengePickedResponse()); + public static pb::MessageParser Parser { get { return _parser; } } + + public static pbr::MessageDescriptor Descriptor + { + get { return Bgs.Protocol.Challenge.V1.ChallengeServiceReflection.Descriptor.MessageTypes[2]; } + } + + pbr::MessageDescriptor pb::IMessage.Descriptor + { + get { return Descriptor; } + } + + public ChallengePickedResponse() + { + OnConstruction(); + } + + partial void OnConstruction(); + + public ChallengePickedResponse(ChallengePickedResponse other) : this() + { + data_ = other.data_; + } + + public ChallengePickedResponse Clone() + { + return new ChallengePickedResponse(this); + } + + /// Field number for the "data" field. + public const int DataFieldNumber = 1; + private pb::ByteString data_ = pb::ByteString.Empty; + public pb::ByteString Data + { + get { return data_; } + set + { + data_ = pb::ProtoPreconditions.CheckNotNull(value, "value"); + } + } + + public override bool Equals(object other) + { + return Equals(other as ChallengePickedResponse); + } + + public bool Equals(ChallengePickedResponse other) + { + if (ReferenceEquals(other, null)) + { + return false; + } + if (ReferenceEquals(other, this)) + { + return true; + } + if (Data != other.Data) return false; + return true; + } + + public override int GetHashCode() + { + int hash = 1; + if (Data.Length != 0) hash ^= Data.GetHashCode(); + return hash; + } + + public override string ToString() + { + return pb::JsonFormatter.ToDiagnosticString(this); + } + + public void WriteTo(pb::CodedOutputStream output) + { + if (Data.Length != 0) + { + output.WriteRawTag(10); + output.WriteBytes(Data); + } + } + + public int CalculateSize() + { + int size = 0; + if (Data.Length != 0) + { + size += 1 + pb::CodedOutputStream.ComputeBytesSize(Data); + } + return size; + } + + public void MergeFrom(ChallengePickedResponse other) + { + if (other == null) + { + return; + } + if (other.Data.Length != 0) + { + Data = other.Data; + } + } + + public void MergeFrom(pb::CodedInputStream input) + { + uint tag; + while ((tag = input.ReadTag()) != 0) + { + switch (tag) + { + default: + input.SkipLastField(); + break; + case 10: + { + Data = input.ReadBytes(); + break; + } + } + } + } + + } + + [System.Diagnostics.DebuggerNonUserCodeAttribute()] + public sealed partial class ChallengeAnsweredRequest : pb::IMessage + { + private static readonly pb::MessageParser _parser = new pb::MessageParser(() => new ChallengeAnsweredRequest()); + public static pb::MessageParser Parser { get { return _parser; } } + + public static pbr::MessageDescriptor Descriptor + { + get { return Bgs.Protocol.Challenge.V1.ChallengeServiceReflection.Descriptor.MessageTypes[3]; } + } + + pbr::MessageDescriptor pb::IMessage.Descriptor + { + get { return Descriptor; } + } + + public ChallengeAnsweredRequest() + { + OnConstruction(); + } + + partial void OnConstruction(); + + public ChallengeAnsweredRequest(ChallengeAnsweredRequest other) : this() + { + answer_ = other.answer_; + data_ = other.data_; + id_ = other.id_; + } + + public ChallengeAnsweredRequest Clone() + { + return new ChallengeAnsweredRequest(this); + } + + /// Field number for the "answer" field. + public const int AnswerFieldNumber = 1; + private string answer_ = ""; + public string Answer + { + get { return answer_; } + set + { + answer_ = pb::ProtoPreconditions.CheckNotNull(value, "value"); + } + } + + /// Field number for the "data" field. + public const int DataFieldNumber = 2; + private pb::ByteString data_ = pb::ByteString.Empty; + public pb::ByteString Data + { + get { return data_; } + set + { + data_ = pb::ProtoPreconditions.CheckNotNull(value, "value"); + } + } + + /// Field number for the "id" field. + public const int IdFieldNumber = 3; + private uint id_; + public uint Id + { + get { return id_; } + set + { + id_ = value; + } + } + + public override bool Equals(object other) + { + return Equals(other as ChallengeAnsweredRequest); + } + + public bool Equals(ChallengeAnsweredRequest other) + { + if (ReferenceEquals(other, null)) + { + return false; + } + if (ReferenceEquals(other, this)) + { + return true; + } + if (Answer != other.Answer) return false; + if (Data != other.Data) return false; + if (Id != other.Id) return false; + return true; + } + + public override int GetHashCode() + { + int hash = 1; + if (Answer.Length != 0) hash ^= Answer.GetHashCode(); + if (Data.Length != 0) hash ^= Data.GetHashCode(); + if (Id != 0) hash ^= Id.GetHashCode(); + return hash; + } + + public override string ToString() + { + return pb::JsonFormatter.ToDiagnosticString(this); + } + + public void WriteTo(pb::CodedOutputStream output) + { + if (Answer.Length != 0) + { + output.WriteRawTag(10); + output.WriteString(Answer); + } + if (Data.Length != 0) + { + output.WriteRawTag(18); + output.WriteBytes(Data); + } + if (Id != 0) + { + output.WriteRawTag(24); + output.WriteUInt32(Id); + } + } + + public int CalculateSize() + { + int size = 0; + if (Answer.Length != 0) + { + size += 1 + pb::CodedOutputStream.ComputeStringSize(Answer); + } + if (Data.Length != 0) + { + size += 1 + pb::CodedOutputStream.ComputeBytesSize(Data); + } + if (Id != 0) + { + size += 1 + pb::CodedOutputStream.ComputeUInt32Size(Id); + } + return size; + } + + public void MergeFrom(ChallengeAnsweredRequest other) + { + if (other == null) + { + return; + } + if (other.Answer.Length != 0) + { + Answer = other.Answer; + } + if (other.Data.Length != 0) + { + Data = other.Data; + } + if (other.Id != 0) + { + Id = other.Id; + } + } + + public void MergeFrom(pb::CodedInputStream input) + { + uint tag; + while ((tag = input.ReadTag()) != 0) + { + switch (tag) + { + default: + input.SkipLastField(); + break; + case 10: + { + Answer = input.ReadString(); + break; + } + case 18: + { + Data = input.ReadBytes(); + break; + } + case 24: + { + Id = input.ReadUInt32(); + break; + } + } + } + } + + } + + [System.Diagnostics.DebuggerNonUserCodeAttribute()] + public sealed partial class ChallengeAnsweredResponse : pb::IMessage + { + private static readonly pb::MessageParser _parser = new pb::MessageParser(() => new ChallengeAnsweredResponse()); + public static pb::MessageParser Parser { get { return _parser; } } + + public static pbr::MessageDescriptor Descriptor + { + get { return Bgs.Protocol.Challenge.V1.ChallengeServiceReflection.Descriptor.MessageTypes[4]; } + } + + pbr::MessageDescriptor pb::IMessage.Descriptor + { + get { return Descriptor; } + } + + public ChallengeAnsweredResponse() + { + OnConstruction(); + } + + partial void OnConstruction(); + + public ChallengeAnsweredResponse(ChallengeAnsweredResponse other) : this() + { + data_ = other.data_; + doRetry_ = other.doRetry_; + recordNotFound_ = other.recordNotFound_; + } + + public ChallengeAnsweredResponse Clone() + { + return new ChallengeAnsweredResponse(this); + } + + /// Field number for the "data" field. + public const int DataFieldNumber = 1; + private pb::ByteString data_ = pb::ByteString.Empty; + public pb::ByteString Data + { + get { return data_; } + set + { + data_ = pb::ProtoPreconditions.CheckNotNull(value, "value"); + } + } + + /// Field number for the "do_retry" field. + public const int DoRetryFieldNumber = 2; + private bool doRetry_; + public bool DoRetry + { + get { return doRetry_; } + set + { + doRetry_ = value; + } + } + + /// Field number for the "record_not_found" field. + public const int RecordNotFoundFieldNumber = 3; + private bool recordNotFound_; + public bool RecordNotFound + { + get { return recordNotFound_; } + set + { + recordNotFound_ = value; + } + } + + public override bool Equals(object other) + { + return Equals(other as ChallengeAnsweredResponse); + } + + public bool Equals(ChallengeAnsweredResponse other) + { + if (ReferenceEquals(other, null)) + { + return false; + } + if (ReferenceEquals(other, this)) + { + return true; + } + if (Data != other.Data) return false; + if (DoRetry != other.DoRetry) return false; + if (RecordNotFound != other.RecordNotFound) return false; + return true; + } + + public override int GetHashCode() + { + int hash = 1; + if (Data.Length != 0) hash ^= Data.GetHashCode(); + if (DoRetry != false) hash ^= DoRetry.GetHashCode(); + if (RecordNotFound != false) hash ^= RecordNotFound.GetHashCode(); + return hash; + } + + public override string ToString() + { + return pb::JsonFormatter.ToDiagnosticString(this); + } + + public void WriteTo(pb::CodedOutputStream output) + { + if (Data.Length != 0) + { + output.WriteRawTag(10); + output.WriteBytes(Data); + } + if (DoRetry != false) + { + output.WriteRawTag(16); + output.WriteBool(DoRetry); + } + if (RecordNotFound != false) + { + output.WriteRawTag(24); + output.WriteBool(RecordNotFound); + } + } + + public int CalculateSize() + { + int size = 0; + if (Data.Length != 0) + { + size += 1 + pb::CodedOutputStream.ComputeBytesSize(Data); + } + if (DoRetry != false) + { + size += 1 + 1; + } + if (RecordNotFound != false) + { + size += 1 + 1; + } + return size; + } + + public void MergeFrom(ChallengeAnsweredResponse other) + { + if (other == null) + { + return; + } + if (other.Data.Length != 0) + { + Data = other.Data; + } + if (other.DoRetry != false) + { + DoRetry = other.DoRetry; + } + if (other.RecordNotFound != false) + { + RecordNotFound = other.RecordNotFound; + } + } + + public void MergeFrom(pb::CodedInputStream input) + { + uint tag; + while ((tag = input.ReadTag()) != 0) + { + switch (tag) + { + default: + input.SkipLastField(); + break; + case 10: + { + Data = input.ReadBytes(); + break; + } + case 16: + { + DoRetry = input.ReadBool(); + break; + } + case 24: + { + RecordNotFound = input.ReadBool(); + break; + } + } + } + } + + } + + [System.Diagnostics.DebuggerNonUserCodeAttribute()] + public sealed partial class ChallengeCancelledRequest : pb::IMessage + { + private static readonly pb::MessageParser _parser = new pb::MessageParser(() => new ChallengeCancelledRequest()); + public static pb::MessageParser Parser { get { return _parser; } } + + public static pbr::MessageDescriptor Descriptor + { + get { return Bgs.Protocol.Challenge.V1.ChallengeServiceReflection.Descriptor.MessageTypes[5]; } + } + + pbr::MessageDescriptor pb::IMessage.Descriptor + { + get { return Descriptor; } + } + + public ChallengeCancelledRequest() + { + OnConstruction(); + } + + partial void OnConstruction(); + + public ChallengeCancelledRequest(ChallengeCancelledRequest other) : this() + { + id_ = other.id_; + } + + public ChallengeCancelledRequest Clone() + { + return new ChallengeCancelledRequest(this); + } + + /// Field number for the "id" field. + public const int IdFieldNumber = 1; + private uint id_; + public uint Id + { + get { return id_; } + set + { + id_ = value; + } + } + + public override bool Equals(object other) + { + return Equals(other as ChallengeCancelledRequest); + } + + public bool Equals(ChallengeCancelledRequest other) + { + if (ReferenceEquals(other, null)) + { + return false; + } + if (ReferenceEquals(other, this)) + { + return true; + } + if (Id != other.Id) return false; + return true; + } + + public override int GetHashCode() + { + int hash = 1; + if (Id != 0) hash ^= Id.GetHashCode(); + return hash; + } + + public override string ToString() + { + return pb::JsonFormatter.ToDiagnosticString(this); + } + + public void WriteTo(pb::CodedOutputStream output) + { + if (Id != 0) + { + output.WriteRawTag(8); + output.WriteUInt32(Id); + } + } + + public int CalculateSize() + { + int size = 0; + if (Id != 0) + { + size += 1 + pb::CodedOutputStream.ComputeUInt32Size(Id); + } + return size; + } + + public void MergeFrom(ChallengeCancelledRequest other) + { + if (other == null) + { + return; + } + if (other.Id != 0) + { + Id = other.Id; + } + } + + public void MergeFrom(pb::CodedInputStream input) + { + uint tag; + while ((tag = input.ReadTag()) != 0) + { + switch (tag) + { + default: + input.SkipLastField(); + break; + case 8: + { + Id = input.ReadUInt32(); + break; + } + } + } + } + + } + + [System.Diagnostics.DebuggerNonUserCodeAttribute()] + public sealed partial class SendChallengeToUserRequest : pb::IMessage + { + private static readonly pb::MessageParser _parser = new pb::MessageParser(() => new SendChallengeToUserRequest()); + public static pb::MessageParser Parser { get { return _parser; } } + + public static pbr::MessageDescriptor Descriptor + { + get { return Bgs.Protocol.Challenge.V1.ChallengeServiceReflection.Descriptor.MessageTypes[6]; } + } + + pbr::MessageDescriptor pb::IMessage.Descriptor + { + get { return Descriptor; } + } + + public SendChallengeToUserRequest() + { + OnConstruction(); + } + + partial void OnConstruction(); + + public SendChallengeToUserRequest(SendChallengeToUserRequest other) : this() + { + PeerId = other.peerId_ != null ? other.PeerId.Clone() : null; + GameAccountId = other.gameAccountId_ != null ? other.GameAccountId.Clone() : null; + challenges_ = other.challenges_.Clone(); + context_ = other.context_; + timeout_ = other.timeout_; + attributes_ = other.attributes_.Clone(); + Host = other.host_ != null ? other.Host.Clone() : null; + AccountId = other.accountId_ != null ? other.AccountId.Clone() : null; + } + + public SendChallengeToUserRequest Clone() + { + return new SendChallengeToUserRequest(this); + } + + /// Field number for the "peer_id" field. + public const int PeerIdFieldNumber = 1; + private Bgs.Protocol.ProcessId peerId_; + public Bgs.Protocol.ProcessId PeerId + { + get { return peerId_; } + set + { + peerId_ = value; + } + } + + /// Field number for the "game_account_id" field. + public const int GameAccountIdFieldNumber = 2; + private Bgs.Protocol.EntityId gameAccountId_; + public Bgs.Protocol.EntityId GameAccountId + { + get { return gameAccountId_; } + set + { + gameAccountId_ = value; + } + } + + /// Field number for the "challenges" field. + public const int ChallengesFieldNumber = 3; + private static readonly pb::FieldCodec _repeated_challenges_codec + = pb::FieldCodec.ForMessage(26, Bgs.Protocol.Challenge.V1.Challenge.Parser); + private readonly pbc::RepeatedField challenges_ = new pbc::RepeatedField(); + public pbc::RepeatedField Challenges + { + get { return challenges_; } + } + + /// Field number for the "context" field. + public const int ContextFieldNumber = 4; + private uint context_; + public uint Context + { + get { return context_; } + set + { + context_ = value; + } + } + + /// Field number for the "timeout" field. + public const int TimeoutFieldNumber = 5; + private ulong timeout_; + public ulong Timeout + { + get { return timeout_; } + set + { + timeout_ = value; + } + } + + /// Field number for the "attributes" field. + public const int AttributesFieldNumber = 6; + private static readonly pb::FieldCodec _repeated_attributes_codec + = pb::FieldCodec.ForMessage(50, Bgs.Protocol.Attribute.Parser); + private readonly pbc::RepeatedField attributes_ = new pbc::RepeatedField(); + public pbc::RepeatedField Attributes + { + get { return attributes_; } + } + + /// Field number for the "host" field. + public const int HostFieldNumber = 7; + private Bgs.Protocol.ProcessId host_; + public Bgs.Protocol.ProcessId Host + { + get { return host_; } + set + { + host_ = value; + } + } + + /// Field number for the "account_id" field. + public const int AccountIdFieldNumber = 8; + private Bgs.Protocol.EntityId accountId_; + public Bgs.Protocol.EntityId AccountId + { + get { return accountId_; } + set + { + accountId_ = value; + } + } + + public override bool Equals(object other) + { + return Equals(other as SendChallengeToUserRequest); + } + + public bool Equals(SendChallengeToUserRequest other) + { + if (ReferenceEquals(other, null)) + { + return false; + } + if (ReferenceEquals(other, this)) + { + return true; + } + if (!object.Equals(PeerId, other.PeerId)) return false; + if (!object.Equals(GameAccountId, other.GameAccountId)) return false; + if (!challenges_.Equals(other.challenges_)) return false; + if (Context != other.Context) return false; + if (Timeout != other.Timeout) return false; + if (!attributes_.Equals(other.attributes_)) return false; + if (!object.Equals(Host, other.Host)) return false; + if (!object.Equals(AccountId, other.AccountId)) return false; + return true; + } + + public override int GetHashCode() + { + int hash = 1; + if (peerId_ != null) hash ^= PeerId.GetHashCode(); + if (gameAccountId_ != null) hash ^= GameAccountId.GetHashCode(); + hash ^= challenges_.GetHashCode(); + if (Context != 0) hash ^= Context.GetHashCode(); + if (Timeout != 0UL) hash ^= Timeout.GetHashCode(); + hash ^= attributes_.GetHashCode(); + if (host_ != null) hash ^= Host.GetHashCode(); + if (accountId_ != null) hash ^= AccountId.GetHashCode(); + return hash; + } + + public override string ToString() + { + return pb::JsonFormatter.ToDiagnosticString(this); + } + + public void WriteTo(pb::CodedOutputStream output) + { + if (peerId_ != null) + { + output.WriteRawTag(10); + output.WriteMessage(PeerId); + } + if (gameAccountId_ != null) + { + output.WriteRawTag(18); + output.WriteMessage(GameAccountId); + } + challenges_.WriteTo(output, _repeated_challenges_codec); + if (Context != 0) + { + output.WriteRawTag(37); + output.WriteFixed32(Context); + } + if (Timeout != 0UL) + { + output.WriteRawTag(40); + output.WriteUInt64(Timeout); + } + attributes_.WriteTo(output, _repeated_attributes_codec); + if (host_ != null) + { + output.WriteRawTag(58); + output.WriteMessage(Host); + } + if (accountId_ != null) + { + output.WriteRawTag(66); + output.WriteMessage(AccountId); + } + } + + public int CalculateSize() + { + int size = 0; + if (peerId_ != null) + { + size += 1 + pb::CodedOutputStream.ComputeMessageSize(PeerId); + } + if (gameAccountId_ != null) + { + size += 1 + pb::CodedOutputStream.ComputeMessageSize(GameAccountId); + } + size += challenges_.CalculateSize(_repeated_challenges_codec); + if (Context != 0) + { + size += 1 + 4; + } + if (Timeout != 0UL) + { + size += 1 + pb::CodedOutputStream.ComputeUInt64Size(Timeout); + } + size += attributes_.CalculateSize(_repeated_attributes_codec); + if (host_ != null) + { + size += 1 + pb::CodedOutputStream.ComputeMessageSize(Host); + } + if (accountId_ != null) + { + size += 1 + pb::CodedOutputStream.ComputeMessageSize(AccountId); + } + return size; + } + + public void MergeFrom(SendChallengeToUserRequest other) + { + if (other == null) + { + return; + } + if (other.peerId_ != null) + { + if (peerId_ == null) + { + peerId_ = new Bgs.Protocol.ProcessId(); + } + PeerId.MergeFrom(other.PeerId); + } + if (other.gameAccountId_ != null) + { + if (gameAccountId_ == null) + { + gameAccountId_ = new Bgs.Protocol.EntityId(); + } + GameAccountId.MergeFrom(other.GameAccountId); + } + challenges_.Add(other.challenges_); + if (other.Context != 0) + { + Context = other.Context; + } + if (other.Timeout != 0UL) + { + Timeout = other.Timeout; + } + attributes_.Add(other.attributes_); + if (other.host_ != null) + { + if (host_ == null) + { + host_ = new Bgs.Protocol.ProcessId(); + } + Host.MergeFrom(other.Host); + } + if (other.accountId_ != null) + { + if (accountId_ == null) + { + accountId_ = new Bgs.Protocol.EntityId(); + } + AccountId.MergeFrom(other.AccountId); + } + } + + public void MergeFrom(pb::CodedInputStream input) + { + uint tag; + while ((tag = input.ReadTag()) != 0) + { + switch (tag) + { + default: + input.SkipLastField(); + break; + case 10: + { + if (peerId_ == null) + { + peerId_ = new Bgs.Protocol.ProcessId(); + } + input.ReadMessage(peerId_); + break; + } + case 18: + { + if (gameAccountId_ == null) + { + gameAccountId_ = new Bgs.Protocol.EntityId(); + } + input.ReadMessage(gameAccountId_); + break; + } + case 26: + { + challenges_.AddEntriesFrom(input, _repeated_challenges_codec); + break; + } + case 37: + { + Context = input.ReadFixed32(); + break; + } + case 40: + { + Timeout = input.ReadUInt64(); + break; + } + case 50: + { + attributes_.AddEntriesFrom(input, _repeated_attributes_codec); + break; + } + case 58: + { + if (host_ == null) + { + host_ = new Bgs.Protocol.ProcessId(); + } + input.ReadMessage(host_); + break; + } + case 66: + { + if (accountId_ == null) + { + accountId_ = new Bgs.Protocol.EntityId(); + } + input.ReadMessage(accountId_); + break; + } + } + } + } + + } + + [System.Diagnostics.DebuggerNonUserCodeAttribute()] + public sealed partial class SendChallengeToUserResponse : pb::IMessage + { + private static readonly pb::MessageParser _parser = new pb::MessageParser(() => new SendChallengeToUserResponse()); + public static pb::MessageParser Parser { get { return _parser; } } + + public static pbr::MessageDescriptor Descriptor + { + get { return Bgs.Protocol.Challenge.V1.ChallengeServiceReflection.Descriptor.MessageTypes[7]; } + } + + pbr::MessageDescriptor pb::IMessage.Descriptor + { + get { return Descriptor; } + } + + public SendChallengeToUserResponse() + { + OnConstruction(); + } + + partial void OnConstruction(); + + public SendChallengeToUserResponse(SendChallengeToUserResponse other) : this() + { + id_ = other.id_; + } + + public SendChallengeToUserResponse Clone() + { + return new SendChallengeToUserResponse(this); + } + + /// Field number for the "id" field. + public const int IdFieldNumber = 1; + private uint id_; + public uint Id + { + get { return id_; } + set + { + id_ = value; + } + } + + public override bool Equals(object other) + { + return Equals(other as SendChallengeToUserResponse); + } + + public bool Equals(SendChallengeToUserResponse other) + { + if (ReferenceEquals(other, null)) + { + return false; + } + if (ReferenceEquals(other, this)) + { + return true; + } + if (Id != other.Id) return false; + return true; + } + + public override int GetHashCode() + { + int hash = 1; + if (Id != 0) hash ^= Id.GetHashCode(); + return hash; + } + + public override string ToString() + { + return pb::JsonFormatter.ToDiagnosticString(this); + } + + public void WriteTo(pb::CodedOutputStream output) + { + if (Id != 0) + { + output.WriteRawTag(8); + output.WriteUInt32(Id); + } + } + + public int CalculateSize() + { + int size = 0; + if (Id != 0) + { + size += 1 + pb::CodedOutputStream.ComputeUInt32Size(Id); + } + return size; + } + + public void MergeFrom(SendChallengeToUserResponse other) + { + if (other == null) + { + return; + } + if (other.Id != 0) + { + Id = other.Id; + } + } + + public void MergeFrom(pb::CodedInputStream input) + { + uint tag; + while ((tag = input.ReadTag()) != 0) + { + switch (tag) + { + default: + input.SkipLastField(); + break; + case 8: + { + Id = input.ReadUInt32(); + break; + } + } + } + } + + } + + [System.Diagnostics.DebuggerNonUserCodeAttribute()] + public sealed partial class ChallengeUserRequest : pb::IMessage + { + private static readonly pb::MessageParser _parser = new pb::MessageParser(() => new ChallengeUserRequest()); + public static pb::MessageParser Parser { get { return _parser; } } + + public static pbr::MessageDescriptor Descriptor + { + get { return Bgs.Protocol.Challenge.V1.ChallengeServiceReflection.Descriptor.MessageTypes[8]; } + } + + pbr::MessageDescriptor pb::IMessage.Descriptor + { + get { return Descriptor; } + } + + public ChallengeUserRequest() + { + OnConstruction(); + } + + partial void OnConstruction(); + + public ChallengeUserRequest(ChallengeUserRequest other) : this() + { + challenges_ = other.challenges_.Clone(); + context_ = other.context_; + id_ = other.id_; + deadline_ = other.deadline_; + attributes_ = other.attributes_.Clone(); + GameAccountId = other.gameAccountId_ != null ? other.GameAccountId.Clone() : null; + } + + public ChallengeUserRequest Clone() + { + return new ChallengeUserRequest(this); + } + + /// Field number for the "challenges" field. + public const int ChallengesFieldNumber = 1; + private static readonly pb::FieldCodec _repeated_challenges_codec + = pb::FieldCodec.ForMessage(10, Bgs.Protocol.Challenge.V1.Challenge.Parser); + private readonly pbc::RepeatedField challenges_ = new pbc::RepeatedField(); + public pbc::RepeatedField Challenges + { + get { return challenges_; } + } + + /// Field number for the "context" field. + public const int ContextFieldNumber = 2; + private uint context_; + public uint Context + { + get { return context_; } + set + { + context_ = value; + } + } + + /// Field number for the "id" field. + public const int IdFieldNumber = 3; + private uint id_; + public uint Id + { + get { return id_; } + set + { + id_ = value; + } + } + + /// Field number for the "deadline" field. + public const int DeadlineFieldNumber = 4; + private ulong deadline_; + public ulong Deadline + { + get { return deadline_; } + set + { + deadline_ = value; + } + } + + /// Field number for the "attributes" field. + public const int AttributesFieldNumber = 5; + private static readonly pb::FieldCodec _repeated_attributes_codec + = pb::FieldCodec.ForMessage(42, Bgs.Protocol.Attribute.Parser); + private readonly pbc::RepeatedField attributes_ = new pbc::RepeatedField(); + public pbc::RepeatedField Attributes + { + get { return attributes_; } + } + + /// Field number for the "game_account_id" field. + public const int GameAccountIdFieldNumber = 6; + private Bgs.Protocol.EntityId gameAccountId_; + public Bgs.Protocol.EntityId GameAccountId + { + get { return gameAccountId_; } + set + { + gameAccountId_ = value; + } + } + + public override bool Equals(object other) + { + return Equals(other as ChallengeUserRequest); + } + + public bool Equals(ChallengeUserRequest other) + { + if (ReferenceEquals(other, null)) + { + return false; + } + if (ReferenceEquals(other, this)) + { + return true; + } + if (!challenges_.Equals(other.challenges_)) return false; + if (Context != other.Context) return false; + if (Id != other.Id) return false; + if (Deadline != other.Deadline) return false; + if (!attributes_.Equals(other.attributes_)) return false; + if (!object.Equals(GameAccountId, other.GameAccountId)) return false; + return true; + } + + public override int GetHashCode() + { + int hash = 1; + hash ^= challenges_.GetHashCode(); + if (Context != 0) hash ^= Context.GetHashCode(); + if (Id != 0) hash ^= Id.GetHashCode(); + if (Deadline != 0UL) hash ^= Deadline.GetHashCode(); + hash ^= attributes_.GetHashCode(); + if (gameAccountId_ != null) hash ^= GameAccountId.GetHashCode(); + return hash; + } + + public override string ToString() + { + return pb::JsonFormatter.ToDiagnosticString(this); + } + + public void WriteTo(pb::CodedOutputStream output) + { + challenges_.WriteTo(output, _repeated_challenges_codec); + if (Context != 0) + { + output.WriteRawTag(21); + output.WriteFixed32(Context); + } + if (Id != 0) + { + output.WriteRawTag(24); + output.WriteUInt32(Id); + } + if (Deadline != 0UL) + { + output.WriteRawTag(32); + output.WriteUInt64(Deadline); + } + attributes_.WriteTo(output, _repeated_attributes_codec); + if (gameAccountId_ != null) + { + output.WriteRawTag(50); + output.WriteMessage(GameAccountId); + } + } + + public int CalculateSize() + { + int size = 0; + size += challenges_.CalculateSize(_repeated_challenges_codec); + if (Context != 0) + { + size += 1 + 4; + } + if (Id != 0) + { + size += 1 + pb::CodedOutputStream.ComputeUInt32Size(Id); + } + if (Deadline != 0UL) + { + size += 1 + pb::CodedOutputStream.ComputeUInt64Size(Deadline); + } + size += attributes_.CalculateSize(_repeated_attributes_codec); + if (gameAccountId_ != null) + { + size += 1 + pb::CodedOutputStream.ComputeMessageSize(GameAccountId); + } + return size; + } + + public void MergeFrom(ChallengeUserRequest other) + { + if (other == null) + { + return; + } + challenges_.Add(other.challenges_); + if (other.Context != 0) + { + Context = other.Context; + } + if (other.Id != 0) + { + Id = other.Id; + } + if (other.Deadline != 0UL) + { + Deadline = other.Deadline; + } + attributes_.Add(other.attributes_); + if (other.gameAccountId_ != null) + { + if (gameAccountId_ == null) + { + gameAccountId_ = new Bgs.Protocol.EntityId(); + } + GameAccountId.MergeFrom(other.GameAccountId); + } + } + + public void MergeFrom(pb::CodedInputStream input) + { + uint tag; + while ((tag = input.ReadTag()) != 0) + { + switch (tag) + { + default: + input.SkipLastField(); + break; + case 10: + { + challenges_.AddEntriesFrom(input, _repeated_challenges_codec); + break; + } + case 21: + { + Context = input.ReadFixed32(); + break; + } + case 24: + { + Id = input.ReadUInt32(); + break; + } + case 32: + { + Deadline = input.ReadUInt64(); + break; + } + case 42: + { + attributes_.AddEntriesFrom(input, _repeated_attributes_codec); + break; + } + case 50: + { + if (gameAccountId_ == null) + { + gameAccountId_ = new Bgs.Protocol.EntityId(); + } + input.ReadMessage(gameAccountId_); + break; + } + } + } + } + + } + + [System.Diagnostics.DebuggerNonUserCodeAttribute()] + public sealed partial class ChallengeResultRequest : pb::IMessage + { + private static readonly pb::MessageParser _parser = new pb::MessageParser(() => new ChallengeResultRequest()); + public static pb::MessageParser Parser { get { return _parser; } } + + public static pbr::MessageDescriptor Descriptor + { + get { return Bgs.Protocol.Challenge.V1.ChallengeServiceReflection.Descriptor.MessageTypes[9]; } + } + + pbr::MessageDescriptor pb::IMessage.Descriptor + { + get { return Descriptor; } + } + + public ChallengeResultRequest() + { + OnConstruction(); + } + + partial void OnConstruction(); + + public ChallengeResultRequest(ChallengeResultRequest other) : this() + { + id_ = other.id_; + type_ = other.type_; + errorId_ = other.errorId_; + answer_ = other.answer_; + } + + public ChallengeResultRequest Clone() + { + return new ChallengeResultRequest(this); + } + + /// Field number for the "id" field. + public const int IdFieldNumber = 1; + private uint id_; + public uint Id + { + get { return id_; } + set + { + id_ = value; + } + } + + /// Field number for the "type" field. + public const int TypeFieldNumber = 2; + private uint type_; + public uint Type + { + get { return type_; } + set + { + type_ = value; + } + } + + /// Field number for the "error_id" field. + public const int ErrorIdFieldNumber = 3; + private uint errorId_; + public uint ErrorId + { + get { return errorId_; } + set + { + errorId_ = value; + } + } + + /// Field number for the "answer" field. + public const int AnswerFieldNumber = 4; + private pb::ByteString answer_ = pb::ByteString.Empty; + public pb::ByteString Answer + { + get { return answer_; } + set + { + answer_ = pb::ProtoPreconditions.CheckNotNull(value, "value"); + } + } + + public override bool Equals(object other) + { + return Equals(other as ChallengeResultRequest); + } + + public bool Equals(ChallengeResultRequest other) + { + if (ReferenceEquals(other, null)) + { + return false; + } + if (ReferenceEquals(other, this)) + { + return true; + } + if (Id != other.Id) return false; + if (Type != other.Type) return false; + if (ErrorId != other.ErrorId) return false; + if (Answer != other.Answer) return false; + return true; + } + + public override int GetHashCode() + { + int hash = 1; + if (Id != 0) hash ^= Id.GetHashCode(); + if (Type != 0) hash ^= Type.GetHashCode(); + if (ErrorId != 0) hash ^= ErrorId.GetHashCode(); + if (Answer.Length != 0) hash ^= Answer.GetHashCode(); + return hash; + } + + public override string ToString() + { + return pb::JsonFormatter.ToDiagnosticString(this); + } + + public void WriteTo(pb::CodedOutputStream output) + { + if (Id != 0) + { + output.WriteRawTag(8); + output.WriteUInt32(Id); + } + if (Type != 0) + { + output.WriteRawTag(21); + output.WriteFixed32(Type); + } + if (ErrorId != 0) + { + output.WriteRawTag(24); + output.WriteUInt32(ErrorId); + } + if (Answer.Length != 0) + { + output.WriteRawTag(34); + output.WriteBytes(Answer); + } + } + + public int CalculateSize() + { + int size = 0; + if (Id != 0) + { + size += 1 + pb::CodedOutputStream.ComputeUInt32Size(Id); + } + if (Type != 0) + { + size += 1 + 4; + } + if (ErrorId != 0) + { + size += 1 + pb::CodedOutputStream.ComputeUInt32Size(ErrorId); + } + if (Answer.Length != 0) + { + size += 1 + pb::CodedOutputStream.ComputeBytesSize(Answer); + } + return size; + } + + public void MergeFrom(ChallengeResultRequest other) + { + if (other == null) + { + return; + } + if (other.Id != 0) + { + Id = other.Id; + } + if (other.Type != 0) + { + Type = other.Type; + } + if (other.ErrorId != 0) + { + ErrorId = other.ErrorId; + } + if (other.Answer.Length != 0) + { + Answer = other.Answer; + } + } + + public void MergeFrom(pb::CodedInputStream input) + { + uint tag; + while ((tag = input.ReadTag()) != 0) + { + switch (tag) + { + default: + input.SkipLastField(); + break; + case 8: + { + Id = input.ReadUInt32(); + break; + } + case 21: + { + Type = input.ReadFixed32(); + break; + } + case 24: + { + ErrorId = input.ReadUInt32(); + break; + } + case 34: + { + Answer = input.ReadBytes(); + break; + } + } + } + } + + } + + [System.Diagnostics.DebuggerNonUserCodeAttribute()] + public sealed partial class ChallengeExternalRequest : pb::IMessage + { + private static readonly pb::MessageParser _parser = new pb::MessageParser(() => new ChallengeExternalRequest()); + public static pb::MessageParser Parser { get { return _parser; } } + + public static pbr::MessageDescriptor Descriptor + { + get { return Bgs.Protocol.Challenge.V1.ChallengeServiceReflection.Descriptor.MessageTypes[10]; } + } + + pbr::MessageDescriptor pb::IMessage.Descriptor + { + get { return Descriptor; } + } + + public ChallengeExternalRequest() + { + OnConstruction(); + } + + partial void OnConstruction(); + + public ChallengeExternalRequest(ChallengeExternalRequest other) : this() + { + requestToken_ = other.requestToken_; + payloadType_ = other.payloadType_; + payload_ = other.payload_; + } + + public ChallengeExternalRequest Clone() + { + return new ChallengeExternalRequest(this); + } + + /// Field number for the "request_token" field. + public const int RequestTokenFieldNumber = 1; + private string requestToken_ = ""; + public string RequestToken + { + get { return requestToken_; } + set + { + requestToken_ = pb::ProtoPreconditions.CheckNotNull(value, "value"); + } + } + + /// Field number for the "payload_type" field. + public const int PayloadTypeFieldNumber = 2; + private string payloadType_ = ""; + public string PayloadType + { + get { return payloadType_; } + set + { + payloadType_ = pb::ProtoPreconditions.CheckNotNull(value, "value"); + } + } + + /// Field number for the "payload" field. + public const int PayloadFieldNumber = 3; + private pb::ByteString payload_ = pb::ByteString.Empty; + public pb::ByteString Payload + { + get { return payload_; } + set + { + payload_ = pb::ProtoPreconditions.CheckNotNull(value, "value"); + } + } + + public override bool Equals(object other) + { + return Equals(other as ChallengeExternalRequest); + } + + public bool Equals(ChallengeExternalRequest other) + { + if (ReferenceEquals(other, null)) + { + return false; + } + if (ReferenceEquals(other, this)) + { + return true; + } + if (RequestToken != other.RequestToken) return false; + if (PayloadType != other.PayloadType) return false; + if (Payload != other.Payload) return false; + return true; + } + + public override int GetHashCode() + { + int hash = 1; + if (RequestToken.Length != 0) hash ^= RequestToken.GetHashCode(); + if (PayloadType.Length != 0) hash ^= PayloadType.GetHashCode(); + if (Payload.Length != 0) hash ^= Payload.GetHashCode(); + return hash; + } + + public override string ToString() + { + return pb::JsonFormatter.ToDiagnosticString(this); + } + + public void WriteTo(pb::CodedOutputStream output) + { + if (RequestToken.Length != 0) + { + output.WriteRawTag(10); + output.WriteString(RequestToken); + } + if (PayloadType.Length != 0) + { + output.WriteRawTag(18); + output.WriteString(PayloadType); + } + if (Payload.Length != 0) + { + output.WriteRawTag(26); + output.WriteBytes(Payload); + } + } + + public int CalculateSize() + { + int size = 0; + if (RequestToken.Length != 0) + { + size += 1 + pb::CodedOutputStream.ComputeStringSize(RequestToken); + } + if (PayloadType.Length != 0) + { + size += 1 + pb::CodedOutputStream.ComputeStringSize(PayloadType); + } + if (Payload.Length != 0) + { + size += 1 + pb::CodedOutputStream.ComputeBytesSize(Payload); + } + return size; + } + + public void MergeFrom(ChallengeExternalRequest other) + { + if (other == null) + { + return; + } + if (other.RequestToken.Length != 0) + { + RequestToken = other.RequestToken; + } + if (other.PayloadType.Length != 0) + { + PayloadType = other.PayloadType; + } + if (other.Payload.Length != 0) + { + Payload = other.Payload; + } + } + + public void MergeFrom(pb::CodedInputStream input) + { + uint tag; + while ((tag = input.ReadTag()) != 0) + { + switch (tag) + { + default: + input.SkipLastField(); + break; + case 10: + { + RequestToken = input.ReadString(); + break; + } + case 18: + { + PayloadType = input.ReadString(); + break; + } + case 26: + { + Payload = input.ReadBytes(); + break; + } + } + } + } + + } + + [System.Diagnostics.DebuggerNonUserCodeAttribute()] + public sealed partial class ChallengeExternalResult : pb::IMessage + { + private static readonly pb::MessageParser _parser = new pb::MessageParser(() => new ChallengeExternalResult()); + public static pb::MessageParser Parser { get { return _parser; } } + + public static pbr::MessageDescriptor Descriptor + { + get { return Bgs.Protocol.Challenge.V1.ChallengeServiceReflection.Descriptor.MessageTypes[11]; } + } + + pbr::MessageDescriptor pb::IMessage.Descriptor + { + get { return Descriptor; } + } + + public ChallengeExternalResult() + { + OnConstruction(); + } + + partial void OnConstruction(); + + public ChallengeExternalResult(ChallengeExternalResult other) : this() + { + requestToken_ = other.requestToken_; + passed_ = other.passed_; + } + + public ChallengeExternalResult Clone() + { + return new ChallengeExternalResult(this); + } + + /// Field number for the "request_token" field. + public const int RequestTokenFieldNumber = 1; + private string requestToken_ = ""; + public string RequestToken + { + get { return requestToken_; } + set + { + requestToken_ = pb::ProtoPreconditions.CheckNotNull(value, "value"); + } + } + + /// Field number for the "passed" field. + public const int PassedFieldNumber = 2; + private bool passed_; + public bool Passed + { + get { return passed_; } + set + { + passed_ = value; + } + } + + public override bool Equals(object other) + { + return Equals(other as ChallengeExternalResult); + } + + public bool Equals(ChallengeExternalResult other) + { + if (ReferenceEquals(other, null)) + { + return false; + } + if (ReferenceEquals(other, this)) + { + return true; + } + if (RequestToken != other.RequestToken) return false; + if (Passed != other.Passed) return false; + return true; + } + + public override int GetHashCode() + { + int hash = 1; + if (RequestToken.Length != 0) hash ^= RequestToken.GetHashCode(); + if (Passed != false) hash ^= Passed.GetHashCode(); + return hash; + } + + public override string ToString() + { + return pb::JsonFormatter.ToDiagnosticString(this); + } + + public void WriteTo(pb::CodedOutputStream output) + { + if (RequestToken.Length != 0) + { + output.WriteRawTag(10); + output.WriteString(RequestToken); + } + if (Passed != false) + { + output.WriteRawTag(16); + output.WriteBool(Passed); + } + } + + public int CalculateSize() + { + int size = 0; + if (RequestToken.Length != 0) + { + size += 1 + pb::CodedOutputStream.ComputeStringSize(RequestToken); + } + if (Passed != false) + { + size += 1 + 1; + } + return size; + } + + public void MergeFrom(ChallengeExternalResult other) + { + if (other == null) + { + return; + } + if (other.RequestToken.Length != 0) + { + RequestToken = other.RequestToken; + } + if (other.Passed != false) + { + Passed = other.Passed; + } + } + + public void MergeFrom(pb::CodedInputStream input) + { + uint tag; + while ((tag = input.ReadTag()) != 0) + { + switch (tag) + { + default: + input.SkipLastField(); + break; + case 10: + { + RequestToken = input.ReadString(); + break; + } + case 16: + { + Passed = input.ReadBool(); + break; + } + } + } + } + + } + + #endregion +} + +#endregion Designer generated code diff --git a/Framework/Proto/ChannelService.cs b/Framework/Proto/ChannelService.cs new file mode 100644 index 000000000..57445a76d --- /dev/null +++ b/Framework/Proto/ChannelService.cs @@ -0,0 +1,2992 @@ +// Generated by the protocol buffer compiler. DO NOT EDIT! +// source: bgs/low/pb/client/channel_service.proto +#pragma warning disable 1591, 0612, 3021 +#region Designer generated code + +using pb = Google.Protobuf; +using pbc = Google.Protobuf.Collections; +using pbr = Google.Protobuf.Reflection; +namespace Bgs.Protocol.Channel.V1 +{ + + /// Holder for reflection information generated from bgs/low/pb/client/channel_service.proto + [System.Diagnostics.DebuggerNonUserCodeAttribute()] + public static partial class ChannelServiceReflection { + + #region Descriptor + /// File descriptor for bgs/low/pb/client/channel_service.proto + public static pbr::FileDescriptor Descriptor { + get { return descriptor; } + } + private static pbr::FileDescriptor descriptor; + + static ChannelServiceReflection() { + byte[] descriptorData = System.Convert.FromBase64String( + string.Concat( + "CidiZ3MvbG93L3BiL2NsaWVudC9jaGFubmVsX3NlcnZpY2UucHJvdG8SF2Jn", + "cy5wcm90b2NvbC5jaGFubmVsLnYxGiViZ3MvbG93L3BiL2NsaWVudC9hY2Nv", + "dW50X3R5cGVzLnByb3RvGiRiZ3MvbG93L3BiL2NsaWVudC9lbnRpdHlfdHlw", + "ZXMucHJvdG8aJWJncy9sb3cvcGIvY2xpZW50L2NoYW5uZWxfdHlwZXMucHJv", + "dG8aIWJncy9sb3cvcGIvY2xpZW50L3JwY190eXBlcy5wcm90byLPAQoQQWRk", + "TWVtYmVyUmVxdWVzdBIoCghhZ2VudF9pZBgBIAEoCzIWLmJncy5wcm90b2Nv", + "bC5FbnRpdHlJZBIvCg9tZW1iZXJfaWRlbnRpdHkYAiABKAsyFi5iZ3MucHJv", + "dG9jb2wuSWRlbnRpdHkSOgoMbWVtYmVyX3N0YXRlGAMgASgLMiQuYmdzLnBy", + "b3RvY29sLmNoYW5uZWwudjEuTWVtYmVyU3RhdGUSEQoJb2JqZWN0X2lkGAQg", + "ASgEEhEKCXN1YnNjcmliZRgFIAEoCCJ6ChNSZW1vdmVNZW1iZXJSZXF1ZXN0", + "EigKCGFnZW50X2lkGAEgASgLMhYuYmdzLnByb3RvY29sLkVudGl0eUlkEikK", + "CW1lbWJlcl9pZBgCIAEoCzIWLmJncy5wcm90b2NvbC5FbnRpdHlJZBIOCgZy", + "ZWFzb24YAyABKA0ibwoYVW5zdWJzY3JpYmVNZW1iZXJSZXF1ZXN0EigKCGFn", + "ZW50X2lkGAEgASgLMhYuYmdzLnByb3RvY29sLkVudGl0eUlkEikKCW1lbWJl", + "cl9pZBgCIAEoCzIWLmJncy5wcm90b2NvbC5FbnRpdHlJZCKOAQoSU2VuZE1l", + "c3NhZ2VSZXF1ZXN0EigKCGFnZW50X2lkGAEgASgLMhYuYmdzLnByb3RvY29s", + "LkVudGl0eUlkEjEKB21lc3NhZ2UYAiABKAsyIC5iZ3MucHJvdG9jb2wuY2hh", + "bm5lbC52MS5NZXNzYWdlEhsKE3JlcXVpcmVkX3ByaXZpbGVnZXMYAyABKAQi", + "ggEKGVVwZGF0ZUNoYW5uZWxTdGF0ZVJlcXVlc3QSKAoIYWdlbnRfaWQYASAB", + "KAsyFi5iZ3MucHJvdG9jb2wuRW50aXR5SWQSOwoMc3RhdGVfY2hhbmdlGAIg", + "ASgLMiUuYmdzLnByb3RvY29sLmNoYW5uZWwudjEuQ2hhbm5lbFN0YXRlIpUB", + "ChhVcGRhdGVNZW1iZXJTdGF0ZVJlcXVlc3QSKAoIYWdlbnRfaWQYASABKAsy", + "Fi5iZ3MucHJvdG9jb2wuRW50aXR5SWQSNQoMc3RhdGVfY2hhbmdlGAIgAygL", + "Mh8uYmdzLnByb3RvY29sLmNoYW5uZWwudjEuTWVtYmVyEhgKDHJlbW92ZWRf", + "cm9sZRgDIAMoDUICEAEiSwoPRGlzc29sdmVSZXF1ZXN0EigKCGFnZW50X2lk", + "GAEgASgLMhYuYmdzLnByb3RvY29sLkVudGl0eUlkEg4KBnJlYXNvbhgCIAEo", + "DSJ4Cg9TZXRSb2xlc1JlcXVlc3QSKAoIYWdlbnRfaWQYASABKAsyFi5iZ3Mu", + "cHJvdG9jb2wuRW50aXR5SWQSEAoEcm9sZRgCIAMoDUICEAESKQoJbWVtYmVy", + "X2lkGAMgAygLMhYuYmdzLnByb3RvY29sLkVudGl0eUlkIp8CChBKb2luTm90", + "aWZpY2F0aW9uEi0KBHNlbGYYASABKAsyHy5iZ3MucHJvdG9jb2wuY2hhbm5l", + "bC52MS5NZW1iZXISLwoGbWVtYmVyGAIgAygLMh8uYmdzLnByb3RvY29sLmNo", + "YW5uZWwudjEuTWVtYmVyEjwKDWNoYW5uZWxfc3RhdGUYAyABKAsyJS5iZ3Mu", + "cHJvdG9jb2wuY2hhbm5lbC52MS5DaGFubmVsU3RhdGUSNgoKY2hhbm5lbF9p", + "ZBgEIAEoCzIiLmJncy5wcm90b2NvbC5jaGFubmVsLnYxLkNoYW5uZWxJZBI1", + "CgpzdWJzY3JpYmVyGAUgASgLMiEuYmdzLnByb3RvY29sLmFjY291bnQudjEu", + "SWRlbnRpdHkiuQEKF01lbWJlckFkZGVkTm90aWZpY2F0aW9uEi8KBm1lbWJl", + "chgBIAEoCzIfLmJncy5wcm90b2NvbC5jaGFubmVsLnYxLk1lbWJlchI2Cgpj", + "aGFubmVsX2lkGAIgASgLMiIuYmdzLnByb3RvY29sLmNoYW5uZWwudjEuQ2hh", + "bm5lbElkEjUKCnN1YnNjcmliZXIYAyABKAsyIS5iZ3MucHJvdG9jb2wuYWNj", + "b3VudC52MS5JZGVudGl0eSLnAQoRTGVhdmVOb3RpZmljYXRpb24SKAoIYWdl", + "bnRfaWQYASABKAsyFi5iZ3MucHJvdG9jb2wuRW50aXR5SWQSKQoJbWVtYmVy", + "X2lkGAIgASgLMhYuYmdzLnByb3RvY29sLkVudGl0eUlkEg4KBnJlYXNvbhgD", + "IAEoDRI2CgpjaGFubmVsX2lkGAQgASgLMiIuYmdzLnByb3RvY29sLmNoYW5u", + "ZWwudjEuQ2hhbm5lbElkEjUKCnN1YnNjcmliZXIYBSABKAsyIS5iZ3MucHJv", + "dG9jb2wuYWNjb3VudC52MS5JZGVudGl0eSLvAQoZTWVtYmVyUmVtb3ZlZE5v", + "dGlmaWNhdGlvbhIoCghhZ2VudF9pZBgBIAEoCzIWLmJncy5wcm90b2NvbC5F", + "bnRpdHlJZBIpCgltZW1iZXJfaWQYAiABKAsyFi5iZ3MucHJvdG9jb2wuRW50", + "aXR5SWQSDgoGcmVhc29uGAMgASgNEjYKCmNoYW5uZWxfaWQYBCABKAsyIi5i", + "Z3MucHJvdG9jb2wuY2hhbm5lbC52MS5DaGFubmVsSWQSNQoKc3Vic2NyaWJl", + "chgFIAEoCzIhLmJncy5wcm90b2NvbC5hY2NvdW50LnYxLklkZW50aXR5IpQC", + "ChdTZW5kTWVzc2FnZU5vdGlmaWNhdGlvbhIoCghhZ2VudF9pZBgBIAEoCzIW", + "LmJncy5wcm90b2NvbC5FbnRpdHlJZBIxCgdtZXNzYWdlGAIgASgLMiAuYmdz", + "LnByb3RvY29sLmNoYW5uZWwudjEuTWVzc2FnZRIbChNyZXF1aXJlZF9wcml2", + "aWxlZ2VzGAMgASgEEhAKCGlkZW50aXR5GAQgASgJEjYKCmNoYW5uZWxfaWQY", + "BSABKAsyIi5iZ3MucHJvdG9jb2wuY2hhbm5lbC52MS5DaGFubmVsSWQSNQoK", + "c3Vic2NyaWJlchgGIAEoCzIhLmJncy5wcm90b2NvbC5hY2NvdW50LnYxLklk", + "ZW50aXR5IvYBCh5VcGRhdGVDaGFubmVsU3RhdGVOb3RpZmljYXRpb24SKAoI", + "YWdlbnRfaWQYASABKAsyFi5iZ3MucHJvdG9jb2wuRW50aXR5SWQSOwoMc3Rh", + "dGVfY2hhbmdlGAIgASgLMiUuYmdzLnByb3RvY29sLmNoYW5uZWwudjEuQ2hh", + "bm5lbFN0YXRlEjYKCmNoYW5uZWxfaWQYAyABKAsyIi5iZ3MucHJvdG9jb2wu", + "Y2hhbm5lbC52MS5DaGFubmVsSWQSNQoKc3Vic2NyaWJlchgEIAEoCzIhLmJn", + "cy5wcm90b2NvbC5hY2NvdW50LnYxLklkZW50aXR5IokCCh1VcGRhdGVNZW1i", + "ZXJTdGF0ZU5vdGlmaWNhdGlvbhIoCghhZ2VudF9pZBgBIAEoCzIWLmJncy5w", + "cm90b2NvbC5FbnRpdHlJZBI1CgxzdGF0ZV9jaGFuZ2UYAiADKAsyHy5iZ3Mu", + "cHJvdG9jb2wuY2hhbm5lbC52MS5NZW1iZXISGAoMcmVtb3ZlZF9yb2xlGAMg", + "AygNQgIQARI2CgpjaGFubmVsX2lkGAQgASgLMiIuYmdzLnByb3RvY29sLmNo", + "YW5uZWwudjEuQ2hhbm5lbElkEjUKCnN1YnNjcmliZXIYBSABKAsyIS5iZ3Mu", + "cHJvdG9jb2wuYWNjb3VudC52MS5JZGVudGl0eTK4BQoOQ2hhbm5lbFNlcnZp", + "Y2USTAoJQWRkTWVtYmVyEikuYmdzLnByb3RvY29sLmNoYW5uZWwudjEuQWRk", + "TWVtYmVyUmVxdWVzdBoULmJncy5wcm90b2NvbC5Ob0RhdGESUgoMUmVtb3Zl", + "TWVtYmVyEiwuYmdzLnByb3RvY29sLmNoYW5uZWwudjEuUmVtb3ZlTWVtYmVy", + "UmVxdWVzdBoULmJncy5wcm90b2NvbC5Ob0RhdGESUAoLU2VuZE1lc3NhZ2US", + "Ky5iZ3MucHJvdG9jb2wuY2hhbm5lbC52MS5TZW5kTWVzc2FnZVJlcXVlc3Qa", + "FC5iZ3MucHJvdG9jb2wuTm9EYXRhEl4KElVwZGF0ZUNoYW5uZWxTdGF0ZRIy", + "LmJncy5wcm90b2NvbC5jaGFubmVsLnYxLlVwZGF0ZUNoYW5uZWxTdGF0ZVJl", + "cXVlc3QaFC5iZ3MucHJvdG9jb2wuTm9EYXRhElwKEVVwZGF0ZU1lbWJlclN0", + "YXRlEjEuYmdzLnByb3RvY29sLmNoYW5uZWwudjEuVXBkYXRlTWVtYmVyU3Rh", + "dGVSZXF1ZXN0GhQuYmdzLnByb3RvY29sLk5vRGF0YRJKCghEaXNzb2x2ZRIo", + "LmJncy5wcm90b2NvbC5jaGFubmVsLnYxLkRpc3NvbHZlUmVxdWVzdBoULmJn", + "cy5wcm90b2NvbC5Ob0RhdGESSgoIU2V0Um9sZXMSKC5iZ3MucHJvdG9jb2wu", + "Y2hhbm5lbC52MS5TZXRSb2xlc1JlcXVlc3QaFC5iZ3MucHJvdG9jb2wuTm9E", + "YXRhElwKEVVuc3Vic2NyaWJlTWVtYmVyEjEuYmdzLnByb3RvY29sLmNoYW5u", + "ZWwudjEuVW5zdWJzY3JpYmVNZW1iZXJSZXF1ZXN0GhQuYmdzLnByb3RvY29s", + "Lk5vRGF0YTKnBQoPQ2hhbm5lbExpc3RlbmVyEk4KBk9uSm9pbhIpLmJncy5w", + "cm90b2NvbC5jaGFubmVsLnYxLkpvaW5Ob3RpZmljYXRpb24aGS5iZ3MucHJv", + "dG9jb2wuTk9fUkVTUE9OU0USXAoNT25NZW1iZXJBZGRlZBIwLmJncy5wcm90", + "b2NvbC5jaGFubmVsLnYxLk1lbWJlckFkZGVkTm90aWZpY2F0aW9uGhkuYmdz", + "LnByb3RvY29sLk5PX1JFU1BPTlNFElAKB09uTGVhdmUSKi5iZ3MucHJvdG9j", + "b2wuY2hhbm5lbC52MS5MZWF2ZU5vdGlmaWNhdGlvbhoZLmJncy5wcm90b2Nv", + "bC5OT19SRVNQT05TRRJgCg9Pbk1lbWJlclJlbW92ZWQSMi5iZ3MucHJvdG9j", + "b2wuY2hhbm5lbC52MS5NZW1iZXJSZW1vdmVkTm90aWZpY2F0aW9uGhkuYmdz", + "LnByb3RvY29sLk5PX1JFU1BPTlNFElwKDU9uU2VuZE1lc3NhZ2USMC5iZ3Mu", + "cHJvdG9jb2wuY2hhbm5lbC52MS5TZW5kTWVzc2FnZU5vdGlmaWNhdGlvbhoZ", + "LmJncy5wcm90b2NvbC5OT19SRVNQT05TRRJqChRPblVwZGF0ZUNoYW5uZWxT", + "dGF0ZRI3LmJncy5wcm90b2NvbC5jaGFubmVsLnYxLlVwZGF0ZUNoYW5uZWxT", + "dGF0ZU5vdGlmaWNhdGlvbhoZLmJncy5wcm90b2NvbC5OT19SRVNQT05TRRJo", + "ChNPblVwZGF0ZU1lbWJlclN0YXRlEjYuYmdzLnByb3RvY29sLmNoYW5uZWwu", + "djEuVXBkYXRlTWVtYmVyU3RhdGVOb3RpZmljYXRpb24aGS5iZ3MucHJvdG9j", + "b2wuTk9fUkVTUE9OU0VCBUgCgAEAYgZwcm90bzM=")); + descriptor = pbr::FileDescriptor.FromGeneratedCode(descriptorData, + new pbr::FileDescriptor[] { Bgs.Protocol.Account.V1.AccountTypesReflection.Descriptor, Bgs.Protocol.EntityTypesReflection.Descriptor, Bgs.Protocol.Channel.V1.ChannelTypesReflection.Descriptor, Bgs.Protocol.RpcTypesReflection.Descriptor, }, + new pbr::GeneratedClrTypeInfo(null, new pbr::GeneratedClrTypeInfo[] { + new pbr::GeneratedClrTypeInfo(typeof(Bgs.Protocol.Channel.V1.AddMemberRequest), Bgs.Protocol.Channel.V1.AddMemberRequest.Parser, new[]{ "AgentId", "MemberIdentity", "MemberState", "ObjectId", "Subscribe" }, null, null, null), + new pbr::GeneratedClrTypeInfo(typeof(Bgs.Protocol.Channel.V1.RemoveMemberRequest), Bgs.Protocol.Channel.V1.RemoveMemberRequest.Parser, new[]{ "AgentId", "MemberId", "Reason" }, null, null, null), + new pbr::GeneratedClrTypeInfo(typeof(Bgs.Protocol.Channel.V1.UnsubscribeMemberRequest), Bgs.Protocol.Channel.V1.UnsubscribeMemberRequest.Parser, new[]{ "AgentId", "MemberId" }, null, null, null), + new pbr::GeneratedClrTypeInfo(typeof(Bgs.Protocol.Channel.V1.SendMessageRequest), Bgs.Protocol.Channel.V1.SendMessageRequest.Parser, new[]{ "AgentId", "Message", "RequiredPrivileges" }, null, null, null), + new pbr::GeneratedClrTypeInfo(typeof(Bgs.Protocol.Channel.V1.UpdateChannelStateRequest), Bgs.Protocol.Channel.V1.UpdateChannelStateRequest.Parser, new[]{ "AgentId", "StateChange" }, null, null, null), + new pbr::GeneratedClrTypeInfo(typeof(Bgs.Protocol.Channel.V1.UpdateMemberStateRequest), Bgs.Protocol.Channel.V1.UpdateMemberStateRequest.Parser, new[]{ "AgentId", "StateChange", "RemovedRole" }, null, null, null), + new pbr::GeneratedClrTypeInfo(typeof(Bgs.Protocol.Channel.V1.DissolveRequest), Bgs.Protocol.Channel.V1.DissolveRequest.Parser, new[]{ "AgentId", "Reason" }, null, null, null), + new pbr::GeneratedClrTypeInfo(typeof(Bgs.Protocol.Channel.V1.SetRolesRequest), Bgs.Protocol.Channel.V1.SetRolesRequest.Parser, new[]{ "AgentId", "Role", "MemberId" }, null, null, null), + new pbr::GeneratedClrTypeInfo(typeof(Bgs.Protocol.Channel.V1.JoinNotification), Bgs.Protocol.Channel.V1.JoinNotification.Parser, new[]{ "Self", "Member", "ChannelState", "ChannelId", "Subscriber" }, null, null, null), + new pbr::GeneratedClrTypeInfo(typeof(Bgs.Protocol.Channel.V1.MemberAddedNotification), Bgs.Protocol.Channel.V1.MemberAddedNotification.Parser, new[]{ "Member", "ChannelId", "Subscriber" }, null, null, null), + new pbr::GeneratedClrTypeInfo(typeof(Bgs.Protocol.Channel.V1.LeaveNotification), Bgs.Protocol.Channel.V1.LeaveNotification.Parser, new[]{ "AgentId", "MemberId", "Reason", "ChannelId", "Subscriber" }, null, null, null), + new pbr::GeneratedClrTypeInfo(typeof(Bgs.Protocol.Channel.V1.MemberRemovedNotification), Bgs.Protocol.Channel.V1.MemberRemovedNotification.Parser, new[]{ "AgentId", "MemberId", "Reason", "ChannelId", "Subscriber" }, null, null, null), + new pbr::GeneratedClrTypeInfo(typeof(Bgs.Protocol.Channel.V1.SendMessageNotification), Bgs.Protocol.Channel.V1.SendMessageNotification.Parser, new[]{ "AgentId", "Message", "RequiredPrivileges", "Identity", "ChannelId", "Subscriber" }, null, null, null), + new pbr::GeneratedClrTypeInfo(typeof(Bgs.Protocol.Channel.V1.UpdateChannelStateNotification), Bgs.Protocol.Channel.V1.UpdateChannelStateNotification.Parser, new[]{ "AgentId", "StateChange", "ChannelId", "Subscriber" }, null, null, null), + new pbr::GeneratedClrTypeInfo(typeof(Bgs.Protocol.Channel.V1.UpdateMemberStateNotification), Bgs.Protocol.Channel.V1.UpdateMemberStateNotification.Parser, new[]{ "AgentId", "StateChange", "RemovedRole", "ChannelId", "Subscriber" }, null, null, null) + })); + } + #endregion + + } + #region Messages + [System.Diagnostics.DebuggerNonUserCodeAttribute()] + public sealed partial class AddMemberRequest : pb::IMessage { + private static readonly pb::MessageParser _parser = new pb::MessageParser(() => new AddMemberRequest()); + public static pb::MessageParser Parser { get { return _parser; } } + + public static pbr::MessageDescriptor Descriptor { + get { return Bgs.Protocol.Channel.V1.ChannelServiceReflection.Descriptor.MessageTypes[0]; } + } + + pbr::MessageDescriptor pb::IMessage.Descriptor { + get { return Descriptor; } + } + + public AddMemberRequest() { + OnConstruction(); + } + + partial void OnConstruction(); + + public AddMemberRequest(AddMemberRequest other) : this() { + AgentId = other.agentId_ != null ? other.AgentId.Clone() : null; + MemberIdentity = other.memberIdentity_ != null ? other.MemberIdentity.Clone() : null; + MemberState = other.memberState_ != null ? other.MemberState.Clone() : null; + objectId_ = other.objectId_; + subscribe_ = other.subscribe_; + } + + public AddMemberRequest Clone() { + return new AddMemberRequest(this); + } + + /// Field number for the "agent_id" field. + public const int AgentIdFieldNumber = 1; + private Bgs.Protocol.EntityId agentId_; + public Bgs.Protocol.EntityId AgentId { + get { return agentId_; } + set { + agentId_ = value; + } + } + + /// Field number for the "member_identity" field. + public const int MemberIdentityFieldNumber = 2; + private Bgs.Protocol.Identity memberIdentity_; + public Bgs.Protocol.Identity MemberIdentity { + get { return memberIdentity_; } + set { + memberIdentity_ = value; + } + } + + /// Field number for the "member_state" field. + public const int MemberStateFieldNumber = 3; + private Bgs.Protocol.Channel.V1.MemberState memberState_; + public Bgs.Protocol.Channel.V1.MemberState MemberState { + get { return memberState_; } + set { + memberState_ = value; + } + } + + /// Field number for the "object_id" field. + public const int ObjectIdFieldNumber = 4; + private ulong objectId_; + public ulong ObjectId { + get { return objectId_; } + set { + objectId_ = value; + } + } + + /// Field number for the "subscribe" field. + public const int SubscribeFieldNumber = 5; + private bool subscribe_; + public bool Subscribe { + get { return subscribe_; } + set { + subscribe_ = value; + } + } + + public override bool Equals(object other) { + return Equals(other as AddMemberRequest); + } + + public bool Equals(AddMemberRequest other) { + if (ReferenceEquals(other, null)) { + return false; + } + if (ReferenceEquals(other, this)) { + return true; + } + if (!object.Equals(AgentId, other.AgentId)) return false; + if (!object.Equals(MemberIdentity, other.MemberIdentity)) return false; + if (!object.Equals(MemberState, other.MemberState)) return false; + if (ObjectId != other.ObjectId) return false; + if (Subscribe != other.Subscribe) return false; + return true; + } + + public override int GetHashCode() { + int hash = 1; + if (agentId_ != null) hash ^= AgentId.GetHashCode(); + if (memberIdentity_ != null) hash ^= MemberIdentity.GetHashCode(); + if (memberState_ != null) hash ^= MemberState.GetHashCode(); + if (ObjectId != 0UL) hash ^= ObjectId.GetHashCode(); + if (Subscribe != false) hash ^= Subscribe.GetHashCode(); + return hash; + } + + public override string ToString() { + return pb::JsonFormatter.ToDiagnosticString(this); + } + + public void WriteTo(pb::CodedOutputStream output) { + if (agentId_ != null) { + output.WriteRawTag(10); + output.WriteMessage(AgentId); + } + if (memberIdentity_ != null) { + output.WriteRawTag(18); + output.WriteMessage(MemberIdentity); + } + if (memberState_ != null) { + output.WriteRawTag(26); + output.WriteMessage(MemberState); + } + if (ObjectId != 0UL) { + output.WriteRawTag(32); + output.WriteUInt64(ObjectId); + } + if (Subscribe != false) { + output.WriteRawTag(40); + output.WriteBool(Subscribe); + } + } + + public int CalculateSize() { + int size = 0; + if (agentId_ != null) { + size += 1 + pb::CodedOutputStream.ComputeMessageSize(AgentId); + } + if (memberIdentity_ != null) { + size += 1 + pb::CodedOutputStream.ComputeMessageSize(MemberIdentity); + } + if (memberState_ != null) { + size += 1 + pb::CodedOutputStream.ComputeMessageSize(MemberState); + } + if (ObjectId != 0UL) { + size += 1 + pb::CodedOutputStream.ComputeUInt64Size(ObjectId); + } + if (Subscribe != false) { + size += 1 + 1; + } + return size; + } + + public void MergeFrom(AddMemberRequest other) { + if (other == null) { + return; + } + if (other.agentId_ != null) { + if (agentId_ == null) { + agentId_ = new Bgs.Protocol.EntityId(); + } + AgentId.MergeFrom(other.AgentId); + } + if (other.memberIdentity_ != null) { + if (memberIdentity_ == null) { + memberIdentity_ = new Bgs.Protocol.Identity(); + } + MemberIdentity.MergeFrom(other.MemberIdentity); + } + if (other.memberState_ != null) { + if (memberState_ == null) { + memberState_ = new Bgs.Protocol.Channel.V1.MemberState(); + } + MemberState.MergeFrom(other.MemberState); + } + if (other.ObjectId != 0UL) { + ObjectId = other.ObjectId; + } + if (other.Subscribe != false) { + Subscribe = other.Subscribe; + } + } + + public void MergeFrom(pb::CodedInputStream input) { + uint tag; + while ((tag = input.ReadTag()) != 0) { + switch(tag) { + default: + input.SkipLastField(); + break; + case 10: { + if (agentId_ == null) { + agentId_ = new Bgs.Protocol.EntityId(); + } + input.ReadMessage(agentId_); + break; + } + case 18: { + if (memberIdentity_ == null) { + memberIdentity_ = new Bgs.Protocol.Identity(); + } + input.ReadMessage(memberIdentity_); + break; + } + case 26: { + if (memberState_ == null) { + memberState_ = new Bgs.Protocol.Channel.V1.MemberState(); + } + input.ReadMessage(memberState_); + break; + } + case 32: { + ObjectId = input.ReadUInt64(); + break; + } + case 40: { + Subscribe = input.ReadBool(); + break; + } + } + } + } + + } + + [System.Diagnostics.DebuggerNonUserCodeAttribute()] + public sealed partial class RemoveMemberRequest : pb::IMessage { + private static readonly pb::MessageParser _parser = new pb::MessageParser(() => new RemoveMemberRequest()); + public static pb::MessageParser Parser { get { return _parser; } } + + public static pbr::MessageDescriptor Descriptor { + get { return Bgs.Protocol.Channel.V1.ChannelServiceReflection.Descriptor.MessageTypes[1]; } + } + + pbr::MessageDescriptor pb::IMessage.Descriptor { + get { return Descriptor; } + } + + public RemoveMemberRequest() { + OnConstruction(); + } + + partial void OnConstruction(); + + public RemoveMemberRequest(RemoveMemberRequest other) : this() { + AgentId = other.agentId_ != null ? other.AgentId.Clone() : null; + MemberId = other.memberId_ != null ? other.MemberId.Clone() : null; + reason_ = other.reason_; + } + + public RemoveMemberRequest Clone() { + return new RemoveMemberRequest(this); + } + + /// Field number for the "agent_id" field. + public const int AgentIdFieldNumber = 1; + private Bgs.Protocol.EntityId agentId_; + public Bgs.Protocol.EntityId AgentId { + get { return agentId_; } + set { + agentId_ = value; + } + } + + /// Field number for the "member_id" field. + public const int MemberIdFieldNumber = 2; + private Bgs.Protocol.EntityId memberId_; + public Bgs.Protocol.EntityId MemberId { + get { return memberId_; } + set { + memberId_ = value; + } + } + + /// Field number for the "reason" field. + public const int ReasonFieldNumber = 3; + private uint reason_; + public uint Reason { + get { return reason_; } + set { + reason_ = value; + } + } + + public override bool Equals(object other) { + return Equals(other as RemoveMemberRequest); + } + + public bool Equals(RemoveMemberRequest other) { + if (ReferenceEquals(other, null)) { + return false; + } + if (ReferenceEquals(other, this)) { + return true; + } + if (!object.Equals(AgentId, other.AgentId)) return false; + if (!object.Equals(MemberId, other.MemberId)) return false; + if (Reason != other.Reason) return false; + return true; + } + + public override int GetHashCode() { + int hash = 1; + if (agentId_ != null) hash ^= AgentId.GetHashCode(); + if (memberId_ != null) hash ^= MemberId.GetHashCode(); + if (Reason != 0) hash ^= Reason.GetHashCode(); + return hash; + } + + public override string ToString() { + return pb::JsonFormatter.ToDiagnosticString(this); + } + + public void WriteTo(pb::CodedOutputStream output) { + if (agentId_ != null) { + output.WriteRawTag(10); + output.WriteMessage(AgentId); + } + if (memberId_ != null) { + output.WriteRawTag(18); + output.WriteMessage(MemberId); + } + if (Reason != 0) { + output.WriteRawTag(24); + output.WriteUInt32(Reason); + } + } + + public int CalculateSize() { + int size = 0; + if (agentId_ != null) { + size += 1 + pb::CodedOutputStream.ComputeMessageSize(AgentId); + } + if (memberId_ != null) { + size += 1 + pb::CodedOutputStream.ComputeMessageSize(MemberId); + } + if (Reason != 0) { + size += 1 + pb::CodedOutputStream.ComputeUInt32Size(Reason); + } + return size; + } + + public void MergeFrom(RemoveMemberRequest other) { + if (other == null) { + return; + } + if (other.agentId_ != null) { + if (agentId_ == null) { + agentId_ = new Bgs.Protocol.EntityId(); + } + AgentId.MergeFrom(other.AgentId); + } + if (other.memberId_ != null) { + if (memberId_ == null) { + memberId_ = new Bgs.Protocol.EntityId(); + } + MemberId.MergeFrom(other.MemberId); + } + if (other.Reason != 0) { + Reason = other.Reason; + } + } + + public void MergeFrom(pb::CodedInputStream input) { + uint tag; + while ((tag = input.ReadTag()) != 0) { + switch(tag) { + default: + input.SkipLastField(); + break; + case 10: { + if (agentId_ == null) { + agentId_ = new Bgs.Protocol.EntityId(); + } + input.ReadMessage(agentId_); + break; + } + case 18: { + if (memberId_ == null) { + memberId_ = new Bgs.Protocol.EntityId(); + } + input.ReadMessage(memberId_); + break; + } + case 24: { + Reason = input.ReadUInt32(); + break; + } + } + } + } + + } + + [System.Diagnostics.DebuggerNonUserCodeAttribute()] + public sealed partial class UnsubscribeMemberRequest : pb::IMessage { + private static readonly pb::MessageParser _parser = new pb::MessageParser(() => new UnsubscribeMemberRequest()); + public static pb::MessageParser Parser { get { return _parser; } } + + public static pbr::MessageDescriptor Descriptor { + get { return Bgs.Protocol.Channel.V1.ChannelServiceReflection.Descriptor.MessageTypes[2]; } + } + + pbr::MessageDescriptor pb::IMessage.Descriptor { + get { return Descriptor; } + } + + public UnsubscribeMemberRequest() { + OnConstruction(); + } + + partial void OnConstruction(); + + public UnsubscribeMemberRequest(UnsubscribeMemberRequest other) : this() { + AgentId = other.agentId_ != null ? other.AgentId.Clone() : null; + MemberId = other.memberId_ != null ? other.MemberId.Clone() : null; + } + + public UnsubscribeMemberRequest Clone() { + return new UnsubscribeMemberRequest(this); + } + + /// Field number for the "agent_id" field. + public const int AgentIdFieldNumber = 1; + private Bgs.Protocol.EntityId agentId_; + public Bgs.Protocol.EntityId AgentId { + get { return agentId_; } + set { + agentId_ = value; + } + } + + /// Field number for the "member_id" field. + public const int MemberIdFieldNumber = 2; + private Bgs.Protocol.EntityId memberId_; + public Bgs.Protocol.EntityId MemberId { + get { return memberId_; } + set { + memberId_ = value; + } + } + + public override bool Equals(object other) { + return Equals(other as UnsubscribeMemberRequest); + } + + public bool Equals(UnsubscribeMemberRequest other) { + if (ReferenceEquals(other, null)) { + return false; + } + if (ReferenceEquals(other, this)) { + return true; + } + if (!object.Equals(AgentId, other.AgentId)) return false; + if (!object.Equals(MemberId, other.MemberId)) return false; + return true; + } + + public override int GetHashCode() { + int hash = 1; + if (agentId_ != null) hash ^= AgentId.GetHashCode(); + if (memberId_ != null) hash ^= MemberId.GetHashCode(); + return hash; + } + + public override string ToString() { + return pb::JsonFormatter.ToDiagnosticString(this); + } + + public void WriteTo(pb::CodedOutputStream output) { + if (agentId_ != null) { + output.WriteRawTag(10); + output.WriteMessage(AgentId); + } + if (memberId_ != null) { + output.WriteRawTag(18); + output.WriteMessage(MemberId); + } + } + + public int CalculateSize() { + int size = 0; + if (agentId_ != null) { + size += 1 + pb::CodedOutputStream.ComputeMessageSize(AgentId); + } + if (memberId_ != null) { + size += 1 + pb::CodedOutputStream.ComputeMessageSize(MemberId); + } + return size; + } + + public void MergeFrom(UnsubscribeMemberRequest other) { + if (other == null) { + return; + } + if (other.agentId_ != null) { + if (agentId_ == null) { + agentId_ = new Bgs.Protocol.EntityId(); + } + AgentId.MergeFrom(other.AgentId); + } + if (other.memberId_ != null) { + if (memberId_ == null) { + memberId_ = new Bgs.Protocol.EntityId(); + } + MemberId.MergeFrom(other.MemberId); + } + } + + public void MergeFrom(pb::CodedInputStream input) { + uint tag; + while ((tag = input.ReadTag()) != 0) { + switch(tag) { + default: + input.SkipLastField(); + break; + case 10: { + if (agentId_ == null) { + agentId_ = new Bgs.Protocol.EntityId(); + } + input.ReadMessage(agentId_); + break; + } + case 18: { + if (memberId_ == null) { + memberId_ = new Bgs.Protocol.EntityId(); + } + input.ReadMessage(memberId_); + break; + } + } + } + } + + } + + [System.Diagnostics.DebuggerNonUserCodeAttribute()] + public sealed partial class SendMessageRequest : pb::IMessage { + private static readonly pb::MessageParser _parser = new pb::MessageParser(() => new SendMessageRequest()); + public static pb::MessageParser Parser { get { return _parser; } } + + public static pbr::MessageDescriptor Descriptor { + get { return Bgs.Protocol.Channel.V1.ChannelServiceReflection.Descriptor.MessageTypes[3]; } + } + + pbr::MessageDescriptor pb::IMessage.Descriptor { + get { return Descriptor; } + } + + public SendMessageRequest() { + OnConstruction(); + } + + partial void OnConstruction(); + + public SendMessageRequest(SendMessageRequest other) : this() { + AgentId = other.agentId_ != null ? other.AgentId.Clone() : null; + Message = other.message_ != null ? other.Message.Clone() : null; + requiredPrivileges_ = other.requiredPrivileges_; + } + + public SendMessageRequest Clone() { + return new SendMessageRequest(this); + } + + /// Field number for the "agent_id" field. + public const int AgentIdFieldNumber = 1; + private Bgs.Protocol.EntityId agentId_; + public Bgs.Protocol.EntityId AgentId { + get { return agentId_; } + set { + agentId_ = value; + } + } + + /// Field number for the "message" field. + public const int MessageFieldNumber = 2; + private Bgs.Protocol.Channel.V1.Message message_; + public Bgs.Protocol.Channel.V1.Message Message { + get { return message_; } + set { + message_ = value; + } + } + + /// Field number for the "required_privileges" field. + public const int RequiredPrivilegesFieldNumber = 3; + private ulong requiredPrivileges_; + public ulong RequiredPrivileges { + get { return requiredPrivileges_; } + set { + requiredPrivileges_ = value; + } + } + + public override bool Equals(object other) { + return Equals(other as SendMessageRequest); + } + + public bool Equals(SendMessageRequest other) { + if (ReferenceEquals(other, null)) { + return false; + } + if (ReferenceEquals(other, this)) { + return true; + } + if (!object.Equals(AgentId, other.AgentId)) return false; + if (!object.Equals(Message, other.Message)) return false; + if (RequiredPrivileges != other.RequiredPrivileges) return false; + return true; + } + + public override int GetHashCode() { + int hash = 1; + if (agentId_ != null) hash ^= AgentId.GetHashCode(); + if (message_ != null) hash ^= Message.GetHashCode(); + if (RequiredPrivileges != 0UL) hash ^= RequiredPrivileges.GetHashCode(); + return hash; + } + + public override string ToString() { + return pb::JsonFormatter.ToDiagnosticString(this); + } + + public void WriteTo(pb::CodedOutputStream output) { + if (agentId_ != null) { + output.WriteRawTag(10); + output.WriteMessage(AgentId); + } + if (message_ != null) { + output.WriteRawTag(18); + output.WriteMessage(Message); + } + if (RequiredPrivileges != 0UL) { + output.WriteRawTag(24); + output.WriteUInt64(RequiredPrivileges); + } + } + + public int CalculateSize() { + int size = 0; + if (agentId_ != null) { + size += 1 + pb::CodedOutputStream.ComputeMessageSize(AgentId); + } + if (message_ != null) { + size += 1 + pb::CodedOutputStream.ComputeMessageSize(Message); + } + if (RequiredPrivileges != 0UL) { + size += 1 + pb::CodedOutputStream.ComputeUInt64Size(RequiredPrivileges); + } + return size; + } + + public void MergeFrom(SendMessageRequest other) { + if (other == null) { + return; + } + if (other.agentId_ != null) { + if (agentId_ == null) { + agentId_ = new Bgs.Protocol.EntityId(); + } + AgentId.MergeFrom(other.AgentId); + } + if (other.message_ != null) { + if (message_ == null) { + message_ = new Bgs.Protocol.Channel.V1.Message(); + } + Message.MergeFrom(other.Message); + } + if (other.RequiredPrivileges != 0UL) { + RequiredPrivileges = other.RequiredPrivileges; + } + } + + public void MergeFrom(pb::CodedInputStream input) { + uint tag; + while ((tag = input.ReadTag()) != 0) { + switch(tag) { + default: + input.SkipLastField(); + break; + case 10: { + if (agentId_ == null) { + agentId_ = new Bgs.Protocol.EntityId(); + } + input.ReadMessage(agentId_); + break; + } + case 18: { + if (message_ == null) { + message_ = new Bgs.Protocol.Channel.V1.Message(); + } + input.ReadMessage(message_); + break; + } + case 24: { + RequiredPrivileges = input.ReadUInt64(); + break; + } + } + } + } + + } + + [System.Diagnostics.DebuggerNonUserCodeAttribute()] + public sealed partial class UpdateChannelStateRequest : pb::IMessage { + private static readonly pb::MessageParser _parser = new pb::MessageParser(() => new UpdateChannelStateRequest()); + public static pb::MessageParser Parser { get { return _parser; } } + + public static pbr::MessageDescriptor Descriptor { + get { return Bgs.Protocol.Channel.V1.ChannelServiceReflection.Descriptor.MessageTypes[4]; } + } + + pbr::MessageDescriptor pb::IMessage.Descriptor { + get { return Descriptor; } + } + + public UpdateChannelStateRequest() { + OnConstruction(); + } + + partial void OnConstruction(); + + public UpdateChannelStateRequest(UpdateChannelStateRequest other) : this() { + AgentId = other.agentId_ != null ? other.AgentId.Clone() : null; + StateChange = other.stateChange_ != null ? other.StateChange.Clone() : null; + } + + public UpdateChannelStateRequest Clone() { + return new UpdateChannelStateRequest(this); + } + + /// Field number for the "agent_id" field. + public const int AgentIdFieldNumber = 1; + private Bgs.Protocol.EntityId agentId_; + public Bgs.Protocol.EntityId AgentId { + get { return agentId_; } + set { + agentId_ = value; + } + } + + /// Field number for the "state_change" field. + public const int StateChangeFieldNumber = 2; + private Bgs.Protocol.Channel.V1.ChannelState stateChange_; + public Bgs.Protocol.Channel.V1.ChannelState StateChange { + get { return stateChange_; } + set { + stateChange_ = value; + } + } + + public override bool Equals(object other) { + return Equals(other as UpdateChannelStateRequest); + } + + public bool Equals(UpdateChannelStateRequest other) { + if (ReferenceEquals(other, null)) { + return false; + } + if (ReferenceEquals(other, this)) { + return true; + } + if (!object.Equals(AgentId, other.AgentId)) return false; + if (!object.Equals(StateChange, other.StateChange)) return false; + return true; + } + + public override int GetHashCode() { + int hash = 1; + if (agentId_ != null) hash ^= AgentId.GetHashCode(); + if (stateChange_ != null) hash ^= StateChange.GetHashCode(); + return hash; + } + + public override string ToString() { + return pb::JsonFormatter.ToDiagnosticString(this); + } + + public void WriteTo(pb::CodedOutputStream output) { + if (agentId_ != null) { + output.WriteRawTag(10); + output.WriteMessage(AgentId); + } + if (stateChange_ != null) { + output.WriteRawTag(18); + output.WriteMessage(StateChange); + } + } + + public int CalculateSize() { + int size = 0; + if (agentId_ != null) { + size += 1 + pb::CodedOutputStream.ComputeMessageSize(AgentId); + } + if (stateChange_ != null) { + size += 1 + pb::CodedOutputStream.ComputeMessageSize(StateChange); + } + return size; + } + + public void MergeFrom(UpdateChannelStateRequest other) { + if (other == null) { + return; + } + if (other.agentId_ != null) { + if (agentId_ == null) { + agentId_ = new Bgs.Protocol.EntityId(); + } + AgentId.MergeFrom(other.AgentId); + } + if (other.stateChange_ != null) { + if (stateChange_ == null) { + stateChange_ = new Bgs.Protocol.Channel.V1.ChannelState(); + } + StateChange.MergeFrom(other.StateChange); + } + } + + public void MergeFrom(pb::CodedInputStream input) { + uint tag; + while ((tag = input.ReadTag()) != 0) { + switch(tag) { + default: + input.SkipLastField(); + break; + case 10: { + if (agentId_ == null) { + agentId_ = new Bgs.Protocol.EntityId(); + } + input.ReadMessage(agentId_); + break; + } + case 18: { + if (stateChange_ == null) { + stateChange_ = new Bgs.Protocol.Channel.V1.ChannelState(); + } + input.ReadMessage(stateChange_); + break; + } + } + } + } + + } + + [System.Diagnostics.DebuggerNonUserCodeAttribute()] + public sealed partial class UpdateMemberStateRequest : pb::IMessage { + private static readonly pb::MessageParser _parser = new pb::MessageParser(() => new UpdateMemberStateRequest()); + public static pb::MessageParser Parser { get { return _parser; } } + + public static pbr::MessageDescriptor Descriptor { + get { return Bgs.Protocol.Channel.V1.ChannelServiceReflection.Descriptor.MessageTypes[5]; } + } + + pbr::MessageDescriptor pb::IMessage.Descriptor { + get { return Descriptor; } + } + + public UpdateMemberStateRequest() { + OnConstruction(); + } + + partial void OnConstruction(); + + public UpdateMemberStateRequest(UpdateMemberStateRequest other) : this() { + AgentId = other.agentId_ != null ? other.AgentId.Clone() : null; + stateChange_ = other.stateChange_.Clone(); + removedRole_ = other.removedRole_.Clone(); + } + + public UpdateMemberStateRequest Clone() { + return new UpdateMemberStateRequest(this); + } + + /// Field number for the "agent_id" field. + public const int AgentIdFieldNumber = 1; + private Bgs.Protocol.EntityId agentId_; + public Bgs.Protocol.EntityId AgentId { + get { return agentId_; } + set { + agentId_ = value; + } + } + + /// Field number for the "state_change" field. + public const int StateChangeFieldNumber = 2; + private static readonly pb::FieldCodec _repeated_stateChange_codec + = pb::FieldCodec.ForMessage(18, Bgs.Protocol.Channel.V1.Member.Parser); + private readonly pbc::RepeatedField stateChange_ = new pbc::RepeatedField(); + public pbc::RepeatedField StateChange { + get { return stateChange_; } + } + + /// Field number for the "removed_role" field. + public const int RemovedRoleFieldNumber = 3; + private static readonly pb::FieldCodec _repeated_removedRole_codec + = pb::FieldCodec.ForUInt32(26); + private readonly pbc::RepeatedField removedRole_ = new pbc::RepeatedField(); + public pbc::RepeatedField RemovedRole { + get { return removedRole_; } + } + + public override bool Equals(object other) { + return Equals(other as UpdateMemberStateRequest); + } + + public bool Equals(UpdateMemberStateRequest other) { + if (ReferenceEquals(other, null)) { + return false; + } + if (ReferenceEquals(other, this)) { + return true; + } + if (!object.Equals(AgentId, other.AgentId)) return false; + if(!stateChange_.Equals(other.stateChange_)) return false; + if(!removedRole_.Equals(other.removedRole_)) return false; + return true; + } + + public override int GetHashCode() { + int hash = 1; + if (agentId_ != null) hash ^= AgentId.GetHashCode(); + hash ^= stateChange_.GetHashCode(); + hash ^= removedRole_.GetHashCode(); + return hash; + } + + public override string ToString() { + return pb::JsonFormatter.ToDiagnosticString(this); + } + + public void WriteTo(pb::CodedOutputStream output) { + if (agentId_ != null) { + output.WriteRawTag(10); + output.WriteMessage(AgentId); + } + stateChange_.WriteTo(output, _repeated_stateChange_codec); + removedRole_.WriteTo(output, _repeated_removedRole_codec); + } + + public int CalculateSize() { + int size = 0; + if (agentId_ != null) { + size += 1 + pb::CodedOutputStream.ComputeMessageSize(AgentId); + } + size += stateChange_.CalculateSize(_repeated_stateChange_codec); + size += removedRole_.CalculateSize(_repeated_removedRole_codec); + return size; + } + + public void MergeFrom(UpdateMemberStateRequest other) { + if (other == null) { + return; + } + if (other.agentId_ != null) { + if (agentId_ == null) { + agentId_ = new Bgs.Protocol.EntityId(); + } + AgentId.MergeFrom(other.AgentId); + } + stateChange_.Add(other.stateChange_); + removedRole_.Add(other.removedRole_); + } + + public void MergeFrom(pb::CodedInputStream input) { + uint tag; + while ((tag = input.ReadTag()) != 0) { + switch(tag) { + default: + input.SkipLastField(); + break; + case 10: { + if (agentId_ == null) { + agentId_ = new Bgs.Protocol.EntityId(); + } + input.ReadMessage(agentId_); + break; + } + case 18: { + stateChange_.AddEntriesFrom(input, _repeated_stateChange_codec); + break; + } + case 26: + case 24: { + removedRole_.AddEntriesFrom(input, _repeated_removedRole_codec); + break; + } + } + } + } + + } + + [System.Diagnostics.DebuggerNonUserCodeAttribute()] + public sealed partial class DissolveRequest : pb::IMessage { + private static readonly pb::MessageParser _parser = new pb::MessageParser(() => new DissolveRequest()); + public static pb::MessageParser Parser { get { return _parser; } } + + public static pbr::MessageDescriptor Descriptor { + get { return Bgs.Protocol.Channel.V1.ChannelServiceReflection.Descriptor.MessageTypes[6]; } + } + + pbr::MessageDescriptor pb::IMessage.Descriptor { + get { return Descriptor; } + } + + public DissolveRequest() { + OnConstruction(); + } + + partial void OnConstruction(); + + public DissolveRequest(DissolveRequest other) : this() { + AgentId = other.agentId_ != null ? other.AgentId.Clone() : null; + reason_ = other.reason_; + } + + public DissolveRequest Clone() { + return new DissolveRequest(this); + } + + /// Field number for the "agent_id" field. + public const int AgentIdFieldNumber = 1; + private Bgs.Protocol.EntityId agentId_; + public Bgs.Protocol.EntityId AgentId { + get { return agentId_; } + set { + agentId_ = value; + } + } + + /// Field number for the "reason" field. + public const int ReasonFieldNumber = 2; + private uint reason_; + public uint Reason { + get { return reason_; } + set { + reason_ = value; + } + } + + public override bool Equals(object other) { + return Equals(other as DissolveRequest); + } + + public bool Equals(DissolveRequest other) { + if (ReferenceEquals(other, null)) { + return false; + } + if (ReferenceEquals(other, this)) { + return true; + } + if (!object.Equals(AgentId, other.AgentId)) return false; + if (Reason != other.Reason) return false; + return true; + } + + public override int GetHashCode() { + int hash = 1; + if (agentId_ != null) hash ^= AgentId.GetHashCode(); + if (Reason != 0) hash ^= Reason.GetHashCode(); + return hash; + } + + public override string ToString() { + return pb::JsonFormatter.ToDiagnosticString(this); + } + + public void WriteTo(pb::CodedOutputStream output) { + if (agentId_ != null) { + output.WriteRawTag(10); + output.WriteMessage(AgentId); + } + if (Reason != 0) { + output.WriteRawTag(16); + output.WriteUInt32(Reason); + } + } + + public int CalculateSize() { + int size = 0; + if (agentId_ != null) { + size += 1 + pb::CodedOutputStream.ComputeMessageSize(AgentId); + } + if (Reason != 0) { + size += 1 + pb::CodedOutputStream.ComputeUInt32Size(Reason); + } + return size; + } + + public void MergeFrom(DissolveRequest other) { + if (other == null) { + return; + } + if (other.agentId_ != null) { + if (agentId_ == null) { + agentId_ = new Bgs.Protocol.EntityId(); + } + AgentId.MergeFrom(other.AgentId); + } + if (other.Reason != 0) { + Reason = other.Reason; + } + } + + public void MergeFrom(pb::CodedInputStream input) { + uint tag; + while ((tag = input.ReadTag()) != 0) { + switch(tag) { + default: + input.SkipLastField(); + break; + case 10: { + if (agentId_ == null) { + agentId_ = new Bgs.Protocol.EntityId(); + } + input.ReadMessage(agentId_); + break; + } + case 16: { + Reason = input.ReadUInt32(); + break; + } + } + } + } + + } + + [System.Diagnostics.DebuggerNonUserCodeAttribute()] + public sealed partial class SetRolesRequest : pb::IMessage { + private static readonly pb::MessageParser _parser = new pb::MessageParser(() => new SetRolesRequest()); + public static pb::MessageParser Parser { get { return _parser; } } + + public static pbr::MessageDescriptor Descriptor { + get { return Bgs.Protocol.Channel.V1.ChannelServiceReflection.Descriptor.MessageTypes[7]; } + } + + pbr::MessageDescriptor pb::IMessage.Descriptor { + get { return Descriptor; } + } + + public SetRolesRequest() { + OnConstruction(); + } + + partial void OnConstruction(); + + public SetRolesRequest(SetRolesRequest other) : this() { + AgentId = other.agentId_ != null ? other.AgentId.Clone() : null; + role_ = other.role_.Clone(); + memberId_ = other.memberId_.Clone(); + } + + public SetRolesRequest Clone() { + return new SetRolesRequest(this); + } + + /// Field number for the "agent_id" field. + public const int AgentIdFieldNumber = 1; + private Bgs.Protocol.EntityId agentId_; + public Bgs.Protocol.EntityId AgentId { + get { return agentId_; } + set { + agentId_ = value; + } + } + + /// Field number for the "role" field. + public const int RoleFieldNumber = 2; + private static readonly pb::FieldCodec _repeated_role_codec + = pb::FieldCodec.ForUInt32(18); + private readonly pbc::RepeatedField role_ = new pbc::RepeatedField(); + public pbc::RepeatedField Role { + get { return role_; } + } + + /// Field number for the "member_id" field. + public const int MemberIdFieldNumber = 3; + private static readonly pb::FieldCodec _repeated_memberId_codec + = pb::FieldCodec.ForMessage(26, Bgs.Protocol.EntityId.Parser); + private readonly pbc::RepeatedField memberId_ = new pbc::RepeatedField(); + public pbc::RepeatedField MemberId { + get { return memberId_; } + } + + public override bool Equals(object other) { + return Equals(other as SetRolesRequest); + } + + public bool Equals(SetRolesRequest other) { + if (ReferenceEquals(other, null)) { + return false; + } + if (ReferenceEquals(other, this)) { + return true; + } + if (!object.Equals(AgentId, other.AgentId)) return false; + if(!role_.Equals(other.role_)) return false; + if(!memberId_.Equals(other.memberId_)) return false; + return true; + } + + public override int GetHashCode() { + int hash = 1; + if (agentId_ != null) hash ^= AgentId.GetHashCode(); + hash ^= role_.GetHashCode(); + hash ^= memberId_.GetHashCode(); + return hash; + } + + public override string ToString() { + return pb::JsonFormatter.ToDiagnosticString(this); + } + + public void WriteTo(pb::CodedOutputStream output) { + if (agentId_ != null) { + output.WriteRawTag(10); + output.WriteMessage(AgentId); + } + role_.WriteTo(output, _repeated_role_codec); + memberId_.WriteTo(output, _repeated_memberId_codec); + } + + public int CalculateSize() { + int size = 0; + if (agentId_ != null) { + size += 1 + pb::CodedOutputStream.ComputeMessageSize(AgentId); + } + size += role_.CalculateSize(_repeated_role_codec); + size += memberId_.CalculateSize(_repeated_memberId_codec); + return size; + } + + public void MergeFrom(SetRolesRequest other) { + if (other == null) { + return; + } + if (other.agentId_ != null) { + if (agentId_ == null) { + agentId_ = new Bgs.Protocol.EntityId(); + } + AgentId.MergeFrom(other.AgentId); + } + role_.Add(other.role_); + memberId_.Add(other.memberId_); + } + + public void MergeFrom(pb::CodedInputStream input) { + uint tag; + while ((tag = input.ReadTag()) != 0) { + switch(tag) { + default: + input.SkipLastField(); + break; + case 10: { + if (agentId_ == null) { + agentId_ = new Bgs.Protocol.EntityId(); + } + input.ReadMessage(agentId_); + break; + } + case 18: + case 16: { + role_.AddEntriesFrom(input, _repeated_role_codec); + break; + } + case 26: { + memberId_.AddEntriesFrom(input, _repeated_memberId_codec); + break; + } + } + } + } + + } + + [System.Diagnostics.DebuggerNonUserCodeAttribute()] + public sealed partial class JoinNotification : pb::IMessage { + private static readonly pb::MessageParser _parser = new pb::MessageParser(() => new JoinNotification()); + public static pb::MessageParser Parser { get { return _parser; } } + + public static pbr::MessageDescriptor Descriptor { + get { return Bgs.Protocol.Channel.V1.ChannelServiceReflection.Descriptor.MessageTypes[8]; } + } + + pbr::MessageDescriptor pb::IMessage.Descriptor { + get { return Descriptor; } + } + + public JoinNotification() { + OnConstruction(); + } + + partial void OnConstruction(); + + public JoinNotification(JoinNotification other) : this() { + Self = other.self_ != null ? other.Self.Clone() : null; + member_ = other.member_.Clone(); + ChannelState = other.channelState_ != null ? other.ChannelState.Clone() : null; + ChannelId = other.channelId_ != null ? other.ChannelId.Clone() : null; + Subscriber = other.subscriber_ != null ? other.Subscriber.Clone() : null; + } + + public JoinNotification Clone() { + return new JoinNotification(this); + } + + /// Field number for the "self" field. + public const int SelfFieldNumber = 1; + private Bgs.Protocol.Channel.V1.Member self_; + public Bgs.Protocol.Channel.V1.Member Self { + get { return self_; } + set { + self_ = value; + } + } + + /// Field number for the "member" field. + public const int MemberFieldNumber = 2; + private static readonly pb::FieldCodec _repeated_member_codec + = pb::FieldCodec.ForMessage(18, Bgs.Protocol.Channel.V1.Member.Parser); + private readonly pbc::RepeatedField member_ = new pbc::RepeatedField(); + public pbc::RepeatedField Member { + get { return member_; } + } + + /// Field number for the "channel_state" field. + public const int ChannelStateFieldNumber = 3; + private Bgs.Protocol.Channel.V1.ChannelState channelState_; + public Bgs.Protocol.Channel.V1.ChannelState ChannelState { + get { return channelState_; } + set { + channelState_ = value; + } + } + + /// Field number for the "channel_id" field. + public const int ChannelIdFieldNumber = 4; + private Bgs.Protocol.Channel.V1.ChannelId channelId_; + public Bgs.Protocol.Channel.V1.ChannelId ChannelId { + get { return channelId_; } + set { + channelId_ = value; + } + } + + /// Field number for the "subscriber" field. + public const int SubscriberFieldNumber = 5; + private Bgs.Protocol.Account.V1.Identity subscriber_; + public Bgs.Protocol.Account.V1.Identity Subscriber { + get { return subscriber_; } + set { + subscriber_ = value; + } + } + + public override bool Equals(object other) { + return Equals(other as JoinNotification); + } + + public bool Equals(JoinNotification other) { + if (ReferenceEquals(other, null)) { + return false; + } + if (ReferenceEquals(other, this)) { + return true; + } + if (!object.Equals(Self, other.Self)) return false; + if(!member_.Equals(other.member_)) return false; + if (!object.Equals(ChannelState, other.ChannelState)) return false; + if (!object.Equals(ChannelId, other.ChannelId)) return false; + if (!object.Equals(Subscriber, other.Subscriber)) return false; + return true; + } + + public override int GetHashCode() { + int hash = 1; + if (self_ != null) hash ^= Self.GetHashCode(); + hash ^= member_.GetHashCode(); + if (channelState_ != null) hash ^= ChannelState.GetHashCode(); + if (channelId_ != null) hash ^= ChannelId.GetHashCode(); + if (subscriber_ != null) hash ^= Subscriber.GetHashCode(); + return hash; + } + + public override string ToString() { + return pb::JsonFormatter.ToDiagnosticString(this); + } + + public void WriteTo(pb::CodedOutputStream output) { + if (self_ != null) { + output.WriteRawTag(10); + output.WriteMessage(Self); + } + member_.WriteTo(output, _repeated_member_codec); + if (channelState_ != null) { + output.WriteRawTag(26); + output.WriteMessage(ChannelState); + } + if (channelId_ != null) { + output.WriteRawTag(34); + output.WriteMessage(ChannelId); + } + if (subscriber_ != null) { + output.WriteRawTag(42); + output.WriteMessage(Subscriber); + } + } + + public int CalculateSize() { + int size = 0; + if (self_ != null) { + size += 1 + pb::CodedOutputStream.ComputeMessageSize(Self); + } + size += member_.CalculateSize(_repeated_member_codec); + if (channelState_ != null) { + size += 1 + pb::CodedOutputStream.ComputeMessageSize(ChannelState); + } + if (channelId_ != null) { + size += 1 + pb::CodedOutputStream.ComputeMessageSize(ChannelId); + } + if (subscriber_ != null) { + size += 1 + pb::CodedOutputStream.ComputeMessageSize(Subscriber); + } + return size; + } + + public void MergeFrom(JoinNotification other) { + if (other == null) { + return; + } + if (other.self_ != null) { + if (self_ == null) { + self_ = new Bgs.Protocol.Channel.V1.Member(); + } + Self.MergeFrom(other.Self); + } + member_.Add(other.member_); + if (other.channelState_ != null) { + if (channelState_ == null) { + channelState_ = new Bgs.Protocol.Channel.V1.ChannelState(); + } + ChannelState.MergeFrom(other.ChannelState); + } + if (other.channelId_ != null) { + if (channelId_ == null) { + channelId_ = new Bgs.Protocol.Channel.V1.ChannelId(); + } + ChannelId.MergeFrom(other.ChannelId); + } + if (other.subscriber_ != null) { + if (subscriber_ == null) { + subscriber_ = new Bgs.Protocol.Account.V1.Identity(); + } + Subscriber.MergeFrom(other.Subscriber); + } + } + + public void MergeFrom(pb::CodedInputStream input) { + uint tag; + while ((tag = input.ReadTag()) != 0) { + switch(tag) { + default: + input.SkipLastField(); + break; + case 10: { + if (self_ == null) { + self_ = new Bgs.Protocol.Channel.V1.Member(); + } + input.ReadMessage(self_); + break; + } + case 18: { + member_.AddEntriesFrom(input, _repeated_member_codec); + break; + } + case 26: { + if (channelState_ == null) { + channelState_ = new Bgs.Protocol.Channel.V1.ChannelState(); + } + input.ReadMessage(channelState_); + break; + } + case 34: { + if (channelId_ == null) { + channelId_ = new Bgs.Protocol.Channel.V1.ChannelId(); + } + input.ReadMessage(channelId_); + break; + } + case 42: { + if (subscriber_ == null) { + subscriber_ = new Bgs.Protocol.Account.V1.Identity(); + } + input.ReadMessage(subscriber_); + break; + } + } + } + } + + } + + [System.Diagnostics.DebuggerNonUserCodeAttribute()] + public sealed partial class MemberAddedNotification : pb::IMessage { + private static readonly pb::MessageParser _parser = new pb::MessageParser(() => new MemberAddedNotification()); + public static pb::MessageParser Parser { get { return _parser; } } + + public static pbr::MessageDescriptor Descriptor { + get { return Bgs.Protocol.Channel.V1.ChannelServiceReflection.Descriptor.MessageTypes[9]; } + } + + pbr::MessageDescriptor pb::IMessage.Descriptor { + get { return Descriptor; } + } + + public MemberAddedNotification() { + OnConstruction(); + } + + partial void OnConstruction(); + + public MemberAddedNotification(MemberAddedNotification other) : this() { + Member = other.member_ != null ? other.Member.Clone() : null; + ChannelId = other.channelId_ != null ? other.ChannelId.Clone() : null; + Subscriber = other.subscriber_ != null ? other.Subscriber.Clone() : null; + } + + public MemberAddedNotification Clone() { + return new MemberAddedNotification(this); + } + + /// Field number for the "member" field. + public const int MemberFieldNumber = 1; + private Bgs.Protocol.Channel.V1.Member member_; + public Bgs.Protocol.Channel.V1.Member Member { + get { return member_; } + set { + member_ = value; + } + } + + /// Field number for the "channel_id" field. + public const int ChannelIdFieldNumber = 2; + private Bgs.Protocol.Channel.V1.ChannelId channelId_; + public Bgs.Protocol.Channel.V1.ChannelId ChannelId { + get { return channelId_; } + set { + channelId_ = value; + } + } + + /// Field number for the "subscriber" field. + public const int SubscriberFieldNumber = 3; + private Bgs.Protocol.Account.V1.Identity subscriber_; + public Bgs.Protocol.Account.V1.Identity Subscriber { + get { return subscriber_; } + set { + subscriber_ = value; + } + } + + public override bool Equals(object other) { + return Equals(other as MemberAddedNotification); + } + + public bool Equals(MemberAddedNotification other) { + if (ReferenceEquals(other, null)) { + return false; + } + if (ReferenceEquals(other, this)) { + return true; + } + if (!object.Equals(Member, other.Member)) return false; + if (!object.Equals(ChannelId, other.ChannelId)) return false; + if (!object.Equals(Subscriber, other.Subscriber)) return false; + return true; + } + + public override int GetHashCode() { + int hash = 1; + if (member_ != null) hash ^= Member.GetHashCode(); + if (channelId_ != null) hash ^= ChannelId.GetHashCode(); + if (subscriber_ != null) hash ^= Subscriber.GetHashCode(); + return hash; + } + + public override string ToString() { + return pb::JsonFormatter.ToDiagnosticString(this); + } + + public void WriteTo(pb::CodedOutputStream output) { + if (member_ != null) { + output.WriteRawTag(10); + output.WriteMessage(Member); + } + if (channelId_ != null) { + output.WriteRawTag(18); + output.WriteMessage(ChannelId); + } + if (subscriber_ != null) { + output.WriteRawTag(26); + output.WriteMessage(Subscriber); + } + } + + public int CalculateSize() { + int size = 0; + if (member_ != null) { + size += 1 + pb::CodedOutputStream.ComputeMessageSize(Member); + } + if (channelId_ != null) { + size += 1 + pb::CodedOutputStream.ComputeMessageSize(ChannelId); + } + if (subscriber_ != null) { + size += 1 + pb::CodedOutputStream.ComputeMessageSize(Subscriber); + } + return size; + } + + public void MergeFrom(MemberAddedNotification other) { + if (other == null) { + return; + } + if (other.member_ != null) { + if (member_ == null) { + member_ = new Bgs.Protocol.Channel.V1.Member(); + } + Member.MergeFrom(other.Member); + } + if (other.channelId_ != null) { + if (channelId_ == null) { + channelId_ = new Bgs.Protocol.Channel.V1.ChannelId(); + } + ChannelId.MergeFrom(other.ChannelId); + } + if (other.subscriber_ != null) { + if (subscriber_ == null) { + subscriber_ = new Bgs.Protocol.Account.V1.Identity(); + } + Subscriber.MergeFrom(other.Subscriber); + } + } + + public void MergeFrom(pb::CodedInputStream input) { + uint tag; + while ((tag = input.ReadTag()) != 0) { + switch(tag) { + default: + input.SkipLastField(); + break; + case 10: { + if (member_ == null) { + member_ = new Bgs.Protocol.Channel.V1.Member(); + } + input.ReadMessage(member_); + break; + } + case 18: { + if (channelId_ == null) { + channelId_ = new Bgs.Protocol.Channel.V1.ChannelId(); + } + input.ReadMessage(channelId_); + break; + } + case 26: { + if (subscriber_ == null) { + subscriber_ = new Bgs.Protocol.Account.V1.Identity(); + } + input.ReadMessage(subscriber_); + break; + } + } + } + } + + } + + [System.Diagnostics.DebuggerNonUserCodeAttribute()] + public sealed partial class LeaveNotification : pb::IMessage { + private static readonly pb::MessageParser _parser = new pb::MessageParser(() => new LeaveNotification()); + public static pb::MessageParser Parser { get { return _parser; } } + + public static pbr::MessageDescriptor Descriptor { + get { return Bgs.Protocol.Channel.V1.ChannelServiceReflection.Descriptor.MessageTypes[10]; } + } + + pbr::MessageDescriptor pb::IMessage.Descriptor { + get { return Descriptor; } + } + + public LeaveNotification() { + OnConstruction(); + } + + partial void OnConstruction(); + + public LeaveNotification(LeaveNotification other) : this() { + AgentId = other.agentId_ != null ? other.AgentId.Clone() : null; + MemberId = other.memberId_ != null ? other.MemberId.Clone() : null; + reason_ = other.reason_; + ChannelId = other.channelId_ != null ? other.ChannelId.Clone() : null; + Subscriber = other.subscriber_ != null ? other.Subscriber.Clone() : null; + } + + public LeaveNotification Clone() { + return new LeaveNotification(this); + } + + /// Field number for the "agent_id" field. + public const int AgentIdFieldNumber = 1; + private Bgs.Protocol.EntityId agentId_; + public Bgs.Protocol.EntityId AgentId { + get { return agentId_; } + set { + agentId_ = value; + } + } + + /// Field number for the "member_id" field. + public const int MemberIdFieldNumber = 2; + private Bgs.Protocol.EntityId memberId_; + public Bgs.Protocol.EntityId MemberId { + get { return memberId_; } + set { + memberId_ = value; + } + } + + /// Field number for the "reason" field. + public const int ReasonFieldNumber = 3; + private uint reason_; + public uint Reason { + get { return reason_; } + set { + reason_ = value; + } + } + + /// Field number for the "channel_id" field. + public const int ChannelIdFieldNumber = 4; + private Bgs.Protocol.Channel.V1.ChannelId channelId_; + public Bgs.Protocol.Channel.V1.ChannelId ChannelId { + get { return channelId_; } + set { + channelId_ = value; + } + } + + /// Field number for the "subscriber" field. + public const int SubscriberFieldNumber = 5; + private Bgs.Protocol.Account.V1.Identity subscriber_; + public Bgs.Protocol.Account.V1.Identity Subscriber { + get { return subscriber_; } + set { + subscriber_ = value; + } + } + + public override bool Equals(object other) { + return Equals(other as LeaveNotification); + } + + public bool Equals(LeaveNotification other) { + if (ReferenceEquals(other, null)) { + return false; + } + if (ReferenceEquals(other, this)) { + return true; + } + if (!object.Equals(AgentId, other.AgentId)) return false; + if (!object.Equals(MemberId, other.MemberId)) return false; + if (Reason != other.Reason) return false; + if (!object.Equals(ChannelId, other.ChannelId)) return false; + if (!object.Equals(Subscriber, other.Subscriber)) return false; + return true; + } + + public override int GetHashCode() { + int hash = 1; + if (agentId_ != null) hash ^= AgentId.GetHashCode(); + if (memberId_ != null) hash ^= MemberId.GetHashCode(); + if (Reason != 0) hash ^= Reason.GetHashCode(); + if (channelId_ != null) hash ^= ChannelId.GetHashCode(); + if (subscriber_ != null) hash ^= Subscriber.GetHashCode(); + return hash; + } + + public override string ToString() { + return pb::JsonFormatter.ToDiagnosticString(this); + } + + public void WriteTo(pb::CodedOutputStream output) { + if (agentId_ != null) { + output.WriteRawTag(10); + output.WriteMessage(AgentId); + } + if (memberId_ != null) { + output.WriteRawTag(18); + output.WriteMessage(MemberId); + } + if (Reason != 0) { + output.WriteRawTag(24); + output.WriteUInt32(Reason); + } + if (channelId_ != null) { + output.WriteRawTag(34); + output.WriteMessage(ChannelId); + } + if (subscriber_ != null) { + output.WriteRawTag(42); + output.WriteMessage(Subscriber); + } + } + + public int CalculateSize() { + int size = 0; + if (agentId_ != null) { + size += 1 + pb::CodedOutputStream.ComputeMessageSize(AgentId); + } + if (memberId_ != null) { + size += 1 + pb::CodedOutputStream.ComputeMessageSize(MemberId); + } + if (Reason != 0) { + size += 1 + pb::CodedOutputStream.ComputeUInt32Size(Reason); + } + if (channelId_ != null) { + size += 1 + pb::CodedOutputStream.ComputeMessageSize(ChannelId); + } + if (subscriber_ != null) { + size += 1 + pb::CodedOutputStream.ComputeMessageSize(Subscriber); + } + return size; + } + + public void MergeFrom(LeaveNotification other) { + if (other == null) { + return; + } + if (other.agentId_ != null) { + if (agentId_ == null) { + agentId_ = new Bgs.Protocol.EntityId(); + } + AgentId.MergeFrom(other.AgentId); + } + if (other.memberId_ != null) { + if (memberId_ == null) { + memberId_ = new Bgs.Protocol.EntityId(); + } + MemberId.MergeFrom(other.MemberId); + } + if (other.Reason != 0) { + Reason = other.Reason; + } + if (other.channelId_ != null) { + if (channelId_ == null) { + channelId_ = new Bgs.Protocol.Channel.V1.ChannelId(); + } + ChannelId.MergeFrom(other.ChannelId); + } + if (other.subscriber_ != null) { + if (subscriber_ == null) { + subscriber_ = new Bgs.Protocol.Account.V1.Identity(); + } + Subscriber.MergeFrom(other.Subscriber); + } + } + + public void MergeFrom(pb::CodedInputStream input) { + uint tag; + while ((tag = input.ReadTag()) != 0) { + switch(tag) { + default: + input.SkipLastField(); + break; + case 10: { + if (agentId_ == null) { + agentId_ = new Bgs.Protocol.EntityId(); + } + input.ReadMessage(agentId_); + break; + } + case 18: { + if (memberId_ == null) { + memberId_ = new Bgs.Protocol.EntityId(); + } + input.ReadMessage(memberId_); + break; + } + case 24: { + Reason = input.ReadUInt32(); + break; + } + case 34: { + if (channelId_ == null) { + channelId_ = new Bgs.Protocol.Channel.V1.ChannelId(); + } + input.ReadMessage(channelId_); + break; + } + case 42: { + if (subscriber_ == null) { + subscriber_ = new Bgs.Protocol.Account.V1.Identity(); + } + input.ReadMessage(subscriber_); + break; + } + } + } + } + + } + + [System.Diagnostics.DebuggerNonUserCodeAttribute()] + public sealed partial class MemberRemovedNotification : pb::IMessage { + private static readonly pb::MessageParser _parser = new pb::MessageParser(() => new MemberRemovedNotification()); + public static pb::MessageParser Parser { get { return _parser; } } + + public static pbr::MessageDescriptor Descriptor { + get { return Bgs.Protocol.Channel.V1.ChannelServiceReflection.Descriptor.MessageTypes[11]; } + } + + pbr::MessageDescriptor pb::IMessage.Descriptor { + get { return Descriptor; } + } + + public MemberRemovedNotification() { + OnConstruction(); + } + + partial void OnConstruction(); + + public MemberRemovedNotification(MemberRemovedNotification other) : this() { + AgentId = other.agentId_ != null ? other.AgentId.Clone() : null; + MemberId = other.memberId_ != null ? other.MemberId.Clone() : null; + reason_ = other.reason_; + ChannelId = other.channelId_ != null ? other.ChannelId.Clone() : null; + Subscriber = other.subscriber_ != null ? other.Subscriber.Clone() : null; + } + + public MemberRemovedNotification Clone() { + return new MemberRemovedNotification(this); + } + + /// Field number for the "agent_id" field. + public const int AgentIdFieldNumber = 1; + private Bgs.Protocol.EntityId agentId_; + public Bgs.Protocol.EntityId AgentId { + get { return agentId_; } + set { + agentId_ = value; + } + } + + /// Field number for the "member_id" field. + public const int MemberIdFieldNumber = 2; + private Bgs.Protocol.EntityId memberId_; + public Bgs.Protocol.EntityId MemberId { + get { return memberId_; } + set { + memberId_ = value; + } + } + + /// Field number for the "reason" field. + public const int ReasonFieldNumber = 3; + private uint reason_; + public uint Reason { + get { return reason_; } + set { + reason_ = value; + } + } + + /// Field number for the "channel_id" field. + public const int ChannelIdFieldNumber = 4; + private Bgs.Protocol.Channel.V1.ChannelId channelId_; + public Bgs.Protocol.Channel.V1.ChannelId ChannelId { + get { return channelId_; } + set { + channelId_ = value; + } + } + + /// Field number for the "subscriber" field. + public const int SubscriberFieldNumber = 5; + private Bgs.Protocol.Account.V1.Identity subscriber_; + public Bgs.Protocol.Account.V1.Identity Subscriber { + get { return subscriber_; } + set { + subscriber_ = value; + } + } + + public override bool Equals(object other) { + return Equals(other as MemberRemovedNotification); + } + + public bool Equals(MemberRemovedNotification other) { + if (ReferenceEquals(other, null)) { + return false; + } + if (ReferenceEquals(other, this)) { + return true; + } + if (!object.Equals(AgentId, other.AgentId)) return false; + if (!object.Equals(MemberId, other.MemberId)) return false; + if (Reason != other.Reason) return false; + if (!object.Equals(ChannelId, other.ChannelId)) return false; + if (!object.Equals(Subscriber, other.Subscriber)) return false; + return true; + } + + public override int GetHashCode() { + int hash = 1; + if (agentId_ != null) hash ^= AgentId.GetHashCode(); + if (memberId_ != null) hash ^= MemberId.GetHashCode(); + if (Reason != 0) hash ^= Reason.GetHashCode(); + if (channelId_ != null) hash ^= ChannelId.GetHashCode(); + if (subscriber_ != null) hash ^= Subscriber.GetHashCode(); + return hash; + } + + public override string ToString() { + return pb::JsonFormatter.ToDiagnosticString(this); + } + + public void WriteTo(pb::CodedOutputStream output) { + if (agentId_ != null) { + output.WriteRawTag(10); + output.WriteMessage(AgentId); + } + if (memberId_ != null) { + output.WriteRawTag(18); + output.WriteMessage(MemberId); + } + if (Reason != 0) { + output.WriteRawTag(24); + output.WriteUInt32(Reason); + } + if (channelId_ != null) { + output.WriteRawTag(34); + output.WriteMessage(ChannelId); + } + if (subscriber_ != null) { + output.WriteRawTag(42); + output.WriteMessage(Subscriber); + } + } + + public int CalculateSize() { + int size = 0; + if (agentId_ != null) { + size += 1 + pb::CodedOutputStream.ComputeMessageSize(AgentId); + } + if (memberId_ != null) { + size += 1 + pb::CodedOutputStream.ComputeMessageSize(MemberId); + } + if (Reason != 0) { + size += 1 + pb::CodedOutputStream.ComputeUInt32Size(Reason); + } + if (channelId_ != null) { + size += 1 + pb::CodedOutputStream.ComputeMessageSize(ChannelId); + } + if (subscriber_ != null) { + size += 1 + pb::CodedOutputStream.ComputeMessageSize(Subscriber); + } + return size; + } + + public void MergeFrom(MemberRemovedNotification other) { + if (other == null) { + return; + } + if (other.agentId_ != null) { + if (agentId_ == null) { + agentId_ = new Bgs.Protocol.EntityId(); + } + AgentId.MergeFrom(other.AgentId); + } + if (other.memberId_ != null) { + if (memberId_ == null) { + memberId_ = new Bgs.Protocol.EntityId(); + } + MemberId.MergeFrom(other.MemberId); + } + if (other.Reason != 0) { + Reason = other.Reason; + } + if (other.channelId_ != null) { + if (channelId_ == null) { + channelId_ = new Bgs.Protocol.Channel.V1.ChannelId(); + } + ChannelId.MergeFrom(other.ChannelId); + } + if (other.subscriber_ != null) { + if (subscriber_ == null) { + subscriber_ = new Bgs.Protocol.Account.V1.Identity(); + } + Subscriber.MergeFrom(other.Subscriber); + } + } + + public void MergeFrom(pb::CodedInputStream input) { + uint tag; + while ((tag = input.ReadTag()) != 0) { + switch(tag) { + default: + input.SkipLastField(); + break; + case 10: { + if (agentId_ == null) { + agentId_ = new Bgs.Protocol.EntityId(); + } + input.ReadMessage(agentId_); + break; + } + case 18: { + if (memberId_ == null) { + memberId_ = new Bgs.Protocol.EntityId(); + } + input.ReadMessage(memberId_); + break; + } + case 24: { + Reason = input.ReadUInt32(); + break; + } + case 34: { + if (channelId_ == null) { + channelId_ = new Bgs.Protocol.Channel.V1.ChannelId(); + } + input.ReadMessage(channelId_); + break; + } + case 42: { + if (subscriber_ == null) { + subscriber_ = new Bgs.Protocol.Account.V1.Identity(); + } + input.ReadMessage(subscriber_); + break; + } + } + } + } + + } + + [System.Diagnostics.DebuggerNonUserCodeAttribute()] + public sealed partial class SendMessageNotification : pb::IMessage { + private static readonly pb::MessageParser _parser = new pb::MessageParser(() => new SendMessageNotification()); + public static pb::MessageParser Parser { get { return _parser; } } + + public static pbr::MessageDescriptor Descriptor { + get { return Bgs.Protocol.Channel.V1.ChannelServiceReflection.Descriptor.MessageTypes[12]; } + } + + pbr::MessageDescriptor pb::IMessage.Descriptor { + get { return Descriptor; } + } + + public SendMessageNotification() { + OnConstruction(); + } + + partial void OnConstruction(); + + public SendMessageNotification(SendMessageNotification other) : this() { + AgentId = other.agentId_ != null ? other.AgentId.Clone() : null; + Message = other.message_ != null ? other.Message.Clone() : null; + requiredPrivileges_ = other.requiredPrivileges_; + identity_ = other.identity_; + ChannelId = other.channelId_ != null ? other.ChannelId.Clone() : null; + Subscriber = other.subscriber_ != null ? other.Subscriber.Clone() : null; + } + + public SendMessageNotification Clone() { + return new SendMessageNotification(this); + } + + /// Field number for the "agent_id" field. + public const int AgentIdFieldNumber = 1; + private Bgs.Protocol.EntityId agentId_; + public Bgs.Protocol.EntityId AgentId { + get { return agentId_; } + set { + agentId_ = value; + } + } + + /// Field number for the "message" field. + public const int MessageFieldNumber = 2; + private Bgs.Protocol.Channel.V1.Message message_; + public Bgs.Protocol.Channel.V1.Message Message { + get { return message_; } + set { + message_ = value; + } + } + + /// Field number for the "required_privileges" field. + public const int RequiredPrivilegesFieldNumber = 3; + private ulong requiredPrivileges_; + public ulong RequiredPrivileges { + get { return requiredPrivileges_; } + set { + requiredPrivileges_ = value; + } + } + + /// Field number for the "identity" field. + public const int IdentityFieldNumber = 4; + private string identity_ = ""; + public string Identity { + get { return identity_; } + set { + identity_ = pb::ProtoPreconditions.CheckNotNull(value, "value"); + } + } + + /// Field number for the "channel_id" field. + public const int ChannelIdFieldNumber = 5; + private Bgs.Protocol.Channel.V1.ChannelId channelId_; + public Bgs.Protocol.Channel.V1.ChannelId ChannelId { + get { return channelId_; } + set { + channelId_ = value; + } + } + + /// Field number for the "subscriber" field. + public const int SubscriberFieldNumber = 6; + private Bgs.Protocol.Account.V1.Identity subscriber_; + public Bgs.Protocol.Account.V1.Identity Subscriber { + get { return subscriber_; } + set { + subscriber_ = value; + } + } + + public override bool Equals(object other) { + return Equals(other as SendMessageNotification); + } + + public bool Equals(SendMessageNotification other) { + if (ReferenceEquals(other, null)) { + return false; + } + if (ReferenceEquals(other, this)) { + return true; + } + if (!object.Equals(AgentId, other.AgentId)) return false; + if (!object.Equals(Message, other.Message)) return false; + if (RequiredPrivileges != other.RequiredPrivileges) return false; + if (Identity != other.Identity) return false; + if (!object.Equals(ChannelId, other.ChannelId)) return false; + if (!object.Equals(Subscriber, other.Subscriber)) return false; + return true; + } + + public override int GetHashCode() { + int hash = 1; + if (agentId_ != null) hash ^= AgentId.GetHashCode(); + if (message_ != null) hash ^= Message.GetHashCode(); + if (RequiredPrivileges != 0UL) hash ^= RequiredPrivileges.GetHashCode(); + if (Identity.Length != 0) hash ^= Identity.GetHashCode(); + if (channelId_ != null) hash ^= ChannelId.GetHashCode(); + if (subscriber_ != null) hash ^= Subscriber.GetHashCode(); + return hash; + } + + public override string ToString() { + return pb::JsonFormatter.ToDiagnosticString(this); + } + + public void WriteTo(pb::CodedOutputStream output) { + if (agentId_ != null) { + output.WriteRawTag(10); + output.WriteMessage(AgentId); + } + if (message_ != null) { + output.WriteRawTag(18); + output.WriteMessage(Message); + } + if (RequiredPrivileges != 0UL) { + output.WriteRawTag(24); + output.WriteUInt64(RequiredPrivileges); + } + if (Identity.Length != 0) { + output.WriteRawTag(34); + output.WriteString(Identity); + } + if (channelId_ != null) { + output.WriteRawTag(42); + output.WriteMessage(ChannelId); + } + if (subscriber_ != null) { + output.WriteRawTag(50); + output.WriteMessage(Subscriber); + } + } + + public int CalculateSize() { + int size = 0; + if (agentId_ != null) { + size += 1 + pb::CodedOutputStream.ComputeMessageSize(AgentId); + } + if (message_ != null) { + size += 1 + pb::CodedOutputStream.ComputeMessageSize(Message); + } + if (RequiredPrivileges != 0UL) { + size += 1 + pb::CodedOutputStream.ComputeUInt64Size(RequiredPrivileges); + } + if (Identity.Length != 0) { + size += 1 + pb::CodedOutputStream.ComputeStringSize(Identity); + } + if (channelId_ != null) { + size += 1 + pb::CodedOutputStream.ComputeMessageSize(ChannelId); + } + if (subscriber_ != null) { + size += 1 + pb::CodedOutputStream.ComputeMessageSize(Subscriber); + } + return size; + } + + public void MergeFrom(SendMessageNotification other) { + if (other == null) { + return; + } + if (other.agentId_ != null) { + if (agentId_ == null) { + agentId_ = new Bgs.Protocol.EntityId(); + } + AgentId.MergeFrom(other.AgentId); + } + if (other.message_ != null) { + if (message_ == null) { + message_ = new Bgs.Protocol.Channel.V1.Message(); + } + Message.MergeFrom(other.Message); + } + if (other.RequiredPrivileges != 0UL) { + RequiredPrivileges = other.RequiredPrivileges; + } + if (other.Identity.Length != 0) { + Identity = other.Identity; + } + if (other.channelId_ != null) { + if (channelId_ == null) { + channelId_ = new Bgs.Protocol.Channel.V1.ChannelId(); + } + ChannelId.MergeFrom(other.ChannelId); + } + if (other.subscriber_ != null) { + if (subscriber_ == null) { + subscriber_ = new Bgs.Protocol.Account.V1.Identity(); + } + Subscriber.MergeFrom(other.Subscriber); + } + } + + public void MergeFrom(pb::CodedInputStream input) { + uint tag; + while ((tag = input.ReadTag()) != 0) { + switch(tag) { + default: + input.SkipLastField(); + break; + case 10: { + if (agentId_ == null) { + agentId_ = new Bgs.Protocol.EntityId(); + } + input.ReadMessage(agentId_); + break; + } + case 18: { + if (message_ == null) { + message_ = new Bgs.Protocol.Channel.V1.Message(); + } + input.ReadMessage(message_); + break; + } + case 24: { + RequiredPrivileges = input.ReadUInt64(); + break; + } + case 34: { + Identity = input.ReadString(); + break; + } + case 42: { + if (channelId_ == null) { + channelId_ = new Bgs.Protocol.Channel.V1.ChannelId(); + } + input.ReadMessage(channelId_); + break; + } + case 50: { + if (subscriber_ == null) { + subscriber_ = new Bgs.Protocol.Account.V1.Identity(); + } + input.ReadMessage(subscriber_); + break; + } + } + } + } + + } + + [System.Diagnostics.DebuggerNonUserCodeAttribute()] + public sealed partial class UpdateChannelStateNotification : pb::IMessage { + private static readonly pb::MessageParser _parser = new pb::MessageParser(() => new UpdateChannelStateNotification()); + public static pb::MessageParser Parser { get { return _parser; } } + + public static pbr::MessageDescriptor Descriptor { + get { return Bgs.Protocol.Channel.V1.ChannelServiceReflection.Descriptor.MessageTypes[13]; } + } + + pbr::MessageDescriptor pb::IMessage.Descriptor { + get { return Descriptor; } + } + + public UpdateChannelStateNotification() { + OnConstruction(); + } + + partial void OnConstruction(); + + public UpdateChannelStateNotification(UpdateChannelStateNotification other) : this() { + AgentId = other.agentId_ != null ? other.AgentId.Clone() : null; + StateChange = other.stateChange_ != null ? other.StateChange.Clone() : null; + ChannelId = other.channelId_ != null ? other.ChannelId.Clone() : null; + Subscriber = other.subscriber_ != null ? other.Subscriber.Clone() : null; + } + + public UpdateChannelStateNotification Clone() { + return new UpdateChannelStateNotification(this); + } + + /// Field number for the "agent_id" field. + public const int AgentIdFieldNumber = 1; + private Bgs.Protocol.EntityId agentId_; + public Bgs.Protocol.EntityId AgentId { + get { return agentId_; } + set { + agentId_ = value; + } + } + + /// Field number for the "state_change" field. + public const int StateChangeFieldNumber = 2; + private Bgs.Protocol.Channel.V1.ChannelState stateChange_; + public Bgs.Protocol.Channel.V1.ChannelState StateChange { + get { return stateChange_; } + set { + stateChange_ = value; + } + } + + /// Field number for the "channel_id" field. + public const int ChannelIdFieldNumber = 3; + private Bgs.Protocol.Channel.V1.ChannelId channelId_; + public Bgs.Protocol.Channel.V1.ChannelId ChannelId { + get { return channelId_; } + set { + channelId_ = value; + } + } + + /// Field number for the "subscriber" field. + public const int SubscriberFieldNumber = 4; + private Bgs.Protocol.Account.V1.Identity subscriber_; + public Bgs.Protocol.Account.V1.Identity Subscriber { + get { return subscriber_; } + set { + subscriber_ = value; + } + } + + public override bool Equals(object other) { + return Equals(other as UpdateChannelStateNotification); + } + + public bool Equals(UpdateChannelStateNotification other) { + if (ReferenceEquals(other, null)) { + return false; + } + if (ReferenceEquals(other, this)) { + return true; + } + if (!object.Equals(AgentId, other.AgentId)) return false; + if (!object.Equals(StateChange, other.StateChange)) return false; + if (!object.Equals(ChannelId, other.ChannelId)) return false; + if (!object.Equals(Subscriber, other.Subscriber)) return false; + return true; + } + + public override int GetHashCode() { + int hash = 1; + if (agentId_ != null) hash ^= AgentId.GetHashCode(); + if (stateChange_ != null) hash ^= StateChange.GetHashCode(); + if (channelId_ != null) hash ^= ChannelId.GetHashCode(); + if (subscriber_ != null) hash ^= Subscriber.GetHashCode(); + return hash; + } + + public override string ToString() { + return pb::JsonFormatter.ToDiagnosticString(this); + } + + public void WriteTo(pb::CodedOutputStream output) { + if (agentId_ != null) { + output.WriteRawTag(10); + output.WriteMessage(AgentId); + } + if (stateChange_ != null) { + output.WriteRawTag(18); + output.WriteMessage(StateChange); + } + if (channelId_ != null) { + output.WriteRawTag(26); + output.WriteMessage(ChannelId); + } + if (subscriber_ != null) { + output.WriteRawTag(34); + output.WriteMessage(Subscriber); + } + } + + public int CalculateSize() { + int size = 0; + if (agentId_ != null) { + size += 1 + pb::CodedOutputStream.ComputeMessageSize(AgentId); + } + if (stateChange_ != null) { + size += 1 + pb::CodedOutputStream.ComputeMessageSize(StateChange); + } + if (channelId_ != null) { + size += 1 + pb::CodedOutputStream.ComputeMessageSize(ChannelId); + } + if (subscriber_ != null) { + size += 1 + pb::CodedOutputStream.ComputeMessageSize(Subscriber); + } + return size; + } + + public void MergeFrom(UpdateChannelStateNotification other) { + if (other == null) { + return; + } + if (other.agentId_ != null) { + if (agentId_ == null) { + agentId_ = new Bgs.Protocol.EntityId(); + } + AgentId.MergeFrom(other.AgentId); + } + if (other.stateChange_ != null) { + if (stateChange_ == null) { + stateChange_ = new Bgs.Protocol.Channel.V1.ChannelState(); + } + StateChange.MergeFrom(other.StateChange); + } + if (other.channelId_ != null) { + if (channelId_ == null) { + channelId_ = new Bgs.Protocol.Channel.V1.ChannelId(); + } + ChannelId.MergeFrom(other.ChannelId); + } + if (other.subscriber_ != null) { + if (subscriber_ == null) { + subscriber_ = new Bgs.Protocol.Account.V1.Identity(); + } + Subscriber.MergeFrom(other.Subscriber); + } + } + + public void MergeFrom(pb::CodedInputStream input) { + uint tag; + while ((tag = input.ReadTag()) != 0) { + switch(tag) { + default: + input.SkipLastField(); + break; + case 10: { + if (agentId_ == null) { + agentId_ = new Bgs.Protocol.EntityId(); + } + input.ReadMessage(agentId_); + break; + } + case 18: { + if (stateChange_ == null) { + stateChange_ = new Bgs.Protocol.Channel.V1.ChannelState(); + } + input.ReadMessage(stateChange_); + break; + } + case 26: { + if (channelId_ == null) { + channelId_ = new Bgs.Protocol.Channel.V1.ChannelId(); + } + input.ReadMessage(channelId_); + break; + } + case 34: { + if (subscriber_ == null) { + subscriber_ = new Bgs.Protocol.Account.V1.Identity(); + } + input.ReadMessage(subscriber_); + break; + } + } + } + } + + } + + [System.Diagnostics.DebuggerNonUserCodeAttribute()] + public sealed partial class UpdateMemberStateNotification : pb::IMessage { + private static readonly pb::MessageParser _parser = new pb::MessageParser(() => new UpdateMemberStateNotification()); + public static pb::MessageParser Parser { get { return _parser; } } + + public static pbr::MessageDescriptor Descriptor { + get { return Bgs.Protocol.Channel.V1.ChannelServiceReflection.Descriptor.MessageTypes[14]; } + } + + pbr::MessageDescriptor pb::IMessage.Descriptor { + get { return Descriptor; } + } + + public UpdateMemberStateNotification() { + OnConstruction(); + } + + partial void OnConstruction(); + + public UpdateMemberStateNotification(UpdateMemberStateNotification other) : this() { + AgentId = other.agentId_ != null ? other.AgentId.Clone() : null; + stateChange_ = other.stateChange_.Clone(); + removedRole_ = other.removedRole_.Clone(); + ChannelId = other.channelId_ != null ? other.ChannelId.Clone() : null; + Subscriber = other.subscriber_ != null ? other.Subscriber.Clone() : null; + } + + public UpdateMemberStateNotification Clone() { + return new UpdateMemberStateNotification(this); + } + + /// Field number for the "agent_id" field. + public const int AgentIdFieldNumber = 1; + private Bgs.Protocol.EntityId agentId_; + public Bgs.Protocol.EntityId AgentId { + get { return agentId_; } + set { + agentId_ = value; + } + } + + /// Field number for the "state_change" field. + public const int StateChangeFieldNumber = 2; + private static readonly pb::FieldCodec _repeated_stateChange_codec + = pb::FieldCodec.ForMessage(18, Bgs.Protocol.Channel.V1.Member.Parser); + private readonly pbc::RepeatedField stateChange_ = new pbc::RepeatedField(); + public pbc::RepeatedField StateChange { + get { return stateChange_; } + } + + /// Field number for the "removed_role" field. + public const int RemovedRoleFieldNumber = 3; + private static readonly pb::FieldCodec _repeated_removedRole_codec + = pb::FieldCodec.ForUInt32(26); + private readonly pbc::RepeatedField removedRole_ = new pbc::RepeatedField(); + public pbc::RepeatedField RemovedRole { + get { return removedRole_; } + } + + /// Field number for the "channel_id" field. + public const int ChannelIdFieldNumber = 4; + private Bgs.Protocol.Channel.V1.ChannelId channelId_; + public Bgs.Protocol.Channel.V1.ChannelId ChannelId { + get { return channelId_; } + set { + channelId_ = value; + } + } + + /// Field number for the "subscriber" field. + public const int SubscriberFieldNumber = 5; + private Bgs.Protocol.Account.V1.Identity subscriber_; + public Bgs.Protocol.Account.V1.Identity Subscriber { + get { return subscriber_; } + set { + subscriber_ = value; + } + } + + public override bool Equals(object other) { + return Equals(other as UpdateMemberStateNotification); + } + + public bool Equals(UpdateMemberStateNotification other) { + if (ReferenceEquals(other, null)) { + return false; + } + if (ReferenceEquals(other, this)) { + return true; + } + if (!object.Equals(AgentId, other.AgentId)) return false; + if(!stateChange_.Equals(other.stateChange_)) return false; + if(!removedRole_.Equals(other.removedRole_)) return false; + if (!object.Equals(ChannelId, other.ChannelId)) return false; + if (!object.Equals(Subscriber, other.Subscriber)) return false; + return true; + } + + public override int GetHashCode() { + int hash = 1; + if (agentId_ != null) hash ^= AgentId.GetHashCode(); + hash ^= stateChange_.GetHashCode(); + hash ^= removedRole_.GetHashCode(); + if (channelId_ != null) hash ^= ChannelId.GetHashCode(); + if (subscriber_ != null) hash ^= Subscriber.GetHashCode(); + return hash; + } + + public override string ToString() { + return pb::JsonFormatter.ToDiagnosticString(this); + } + + public void WriteTo(pb::CodedOutputStream output) { + if (agentId_ != null) { + output.WriteRawTag(10); + output.WriteMessage(AgentId); + } + stateChange_.WriteTo(output, _repeated_stateChange_codec); + removedRole_.WriteTo(output, _repeated_removedRole_codec); + if (channelId_ != null) { + output.WriteRawTag(34); + output.WriteMessage(ChannelId); + } + if (subscriber_ != null) { + output.WriteRawTag(42); + output.WriteMessage(Subscriber); + } + } + + public int CalculateSize() { + int size = 0; + if (agentId_ != null) { + size += 1 + pb::CodedOutputStream.ComputeMessageSize(AgentId); + } + size += stateChange_.CalculateSize(_repeated_stateChange_codec); + size += removedRole_.CalculateSize(_repeated_removedRole_codec); + if (channelId_ != null) { + size += 1 + pb::CodedOutputStream.ComputeMessageSize(ChannelId); + } + if (subscriber_ != null) { + size += 1 + pb::CodedOutputStream.ComputeMessageSize(Subscriber); + } + return size; + } + + public void MergeFrom(UpdateMemberStateNotification other) { + if (other == null) { + return; + } + if (other.agentId_ != null) { + if (agentId_ == null) { + agentId_ = new Bgs.Protocol.EntityId(); + } + AgentId.MergeFrom(other.AgentId); + } + stateChange_.Add(other.stateChange_); + removedRole_.Add(other.removedRole_); + if (other.channelId_ != null) { + if (channelId_ == null) { + channelId_ = new Bgs.Protocol.Channel.V1.ChannelId(); + } + ChannelId.MergeFrom(other.ChannelId); + } + if (other.subscriber_ != null) { + if (subscriber_ == null) { + subscriber_ = new Bgs.Protocol.Account.V1.Identity(); + } + Subscriber.MergeFrom(other.Subscriber); + } + } + + public void MergeFrom(pb::CodedInputStream input) { + uint tag; + while ((tag = input.ReadTag()) != 0) { + switch(tag) { + default: + input.SkipLastField(); + break; + case 10: { + if (agentId_ == null) { + agentId_ = new Bgs.Protocol.EntityId(); + } + input.ReadMessage(agentId_); + break; + } + case 18: { + stateChange_.AddEntriesFrom(input, _repeated_stateChange_codec); + break; + } + case 26: + case 24: { + removedRole_.AddEntriesFrom(input, _repeated_removedRole_codec); + break; + } + case 34: { + if (channelId_ == null) { + channelId_ = new Bgs.Protocol.Channel.V1.ChannelId(); + } + input.ReadMessage(channelId_); + break; + } + case 42: { + if (subscriber_ == null) { + subscriber_ = new Bgs.Protocol.Account.V1.Identity(); + } + input.ReadMessage(subscriber_); + break; + } + } + } + } + + } + + #endregion + +} + +#endregion Designer generated code diff --git a/Framework/Proto/ChannelTypes.cs b/Framework/Proto/ChannelTypes.cs new file mode 100644 index 000000000..31e1ba855 --- /dev/null +++ b/Framework/Proto/ChannelTypes.cs @@ -0,0 +1,1764 @@ +// Generated by the protocol buffer compiler. DO NOT EDIT! +// source: bgs/low/pb/client/channel_types.proto +#pragma warning disable 1591, 0612, 3021 +#region Designer generated code + +using pb = Google.Protobuf; +using pbc = Google.Protobuf.Collections; +using pbr = Google.Protobuf.Reflection; +namespace Bgs.Protocol.Channel.V1 +{ + + /// Holder for reflection information generated from bgs/low/pb/client/channel_types.proto + [System.Diagnostics.DebuggerNonUserCodeAttribute()] + public static partial class ChannelTypesReflection { + + #region Descriptor + /// File descriptor for bgs/low/pb/client/channel_types.proto + public static pbr::FileDescriptor Descriptor { + get { return descriptor; } + } + private static pbr::FileDescriptor descriptor; + + static ChannelTypesReflection() { + byte[] descriptorData = System.Convert.FromBase64String( + string.Concat( + "CiViZ3MvbG93L3BiL2NsaWVudC9jaGFubmVsX3R5cGVzLnByb3RvEhdiZ3Mu", + "cHJvdG9jb2wuY2hhbm5lbC52MRonYmdzL2xvdy9wYi9jbGllbnQvYXR0cmli", + "dXRlX3R5cGVzLnByb3RvGiRiZ3MvbG93L3BiL2NsaWVudC9lbnRpdHlfdHlw", + "ZXMucHJvdG8aKGJncy9sb3cvcGIvY2xpZW50L2ludml0YXRpb25fdHlwZXMu", + "cHJvdG8aIWJncy9sb3cvcGIvY2xpZW50L3JwY190eXBlcy5wcm90byJMCglD", + "aGFubmVsSWQSDAoEdHlwZRgBIAEoDRIlCgRob3N0GAIgASgLMhcuYmdzLnBy", + "b3RvY29sLlByb2Nlc3NJZBIKCgJpZBgDIAEoByJDCgdNZXNzYWdlEioKCWF0", + "dHJpYnV0ZRgBIAMoCzIXLmJncy5wcm90b2NvbC5BdHRyaWJ1dGUSDAoEcm9s", + "ZRgCIAEoDSLUAQoTTGlzdENoYW5uZWxzT3B0aW9ucxITCgtzdGFydF9pbmRl", + "eBgBIAEoDRITCgttYXhfcmVzdWx0cxgCIAEoDRIMCgRuYW1lGAMgASgJEg8K", + "B3Byb2dyYW0YBCABKAcSDgoGbG9jYWxlGAUgASgHEhUKDWNhcGFjaXR5X2Z1", + "bGwYBiABKA0SNwoQYXR0cmlidXRlX2ZpbHRlchgHIAEoCzIdLmJncy5wcm90", + "b2NvbC5BdHRyaWJ1dGVGaWx0ZXISFAoMY2hhbm5lbF90eXBlGAggASgJIo8B", + "ChJDaGFubmVsRGVzY3JpcHRpb24SKgoKY2hhbm5lbF9pZBgBIAEoCzIWLmJn", + "cy5wcm90b2NvbC5FbnRpdHlJZBIXCg9jdXJyZW50X21lbWJlcnMYAiABKA0S", + "NAoFc3RhdGUYAyABKAsyJS5iZ3MucHJvdG9jb2wuY2hhbm5lbC52MS5DaGFu", + "bmVsU3RhdGUigAEKC0NoYW5uZWxJbmZvEkAKC2Rlc2NyaXB0aW9uGAEgASgL", + "MisuYmdzLnByb3RvY29sLmNoYW5uZWwudjEuQ2hhbm5lbERlc2NyaXB0aW9u", + "Ei8KBm1lbWJlchgCIAMoCzIfLmJncy5wcm90b2NvbC5jaGFubmVsLnYxLk1l", + "bWJlciLeBAoMQ2hhbm5lbFN0YXRlEhMKC21heF9tZW1iZXJzGAEgASgNEhMK", + "C21pbl9tZW1iZXJzGAIgASgNEioKCWF0dHJpYnV0ZRgDIAMoCzIXLmJncy5w", + "cm90b2NvbC5BdHRyaWJ1dGUSLAoKaW52aXRhdGlvbhgEIAMoCzIYLmJncy5w", + "cm90b2NvbC5JbnZpdGF0aW9uEhcKD21heF9pbnZpdGF0aW9ucxgFIAEoDRIO", + "CgZyZWFzb24YBiABKA0SSQoNcHJpdmFjeV9sZXZlbBgHIAEoDjIyLmJncy5w", + "cm90b2NvbC5jaGFubmVsLnYxLkNoYW5uZWxTdGF0ZS5Qcml2YWN5TGV2ZWwS", + "DAoEbmFtZRgIIAEoCRIVCg1kZWxlZ2F0ZV9uYW1lGAkgASgJEhQKDGNoYW5u", + "ZWxfdHlwZRgKIAEoCRIPCgdwcm9ncmFtGAsgASgHEh0KFWFsbG93X29mZmxp", + "bmVfbWVtYmVycxgMIAEoCBIdChVzdWJzY3JpYmVfdG9fcHJlc2VuY2UYDSAB", + "KAgSIAoYZGVzdHJveV9vbl9mb3VuZGVyX2xlYXZlGA4gASgIIqkBCgxQcml2", + "YWN5TGV2ZWwSFgoSUFJJVkFDWV9MRVZFTF9OT05FEAASFgoSUFJJVkFDWV9M", + "RVZFTF9PUEVOEAESLAooUFJJVkFDWV9MRVZFTF9PUEVOX0lOVklUQVRJT05f", + "QU5EX0ZSSUVORBACEiEKHVBSSVZBQ1lfTEVWRUxfT1BFTl9JTlZJVEFUSU9O", + "EAMSGAoUUFJJVkFDWV9MRVZFTF9DTE9TRUQQBCKnAQoLTWVtYmVyU3RhdGUS", + "KgoJYXR0cmlidXRlGAEgAygLMhcuYmdzLnByb3RvY29sLkF0dHJpYnV0ZRIQ", + "CgRyb2xlGAIgAygNQgIQARISCgpwcml2aWxlZ2VzGAMgASgEEicKBGluZm8Y", + "BCABKAsyGS5iZ3MucHJvdG9jb2wuQWNjb3VudEluZm8SHQoRREVQUkVDQVRF", + "RF9oaWRkZW4YBSABKAhCAhgBImcKBk1lbWJlchIoCghpZGVudGl0eRgBIAEo", + "CzIWLmJncy5wcm90b2NvbC5JZGVudGl0eRIzCgVzdGF0ZRgCIAEoCzIkLmJn", + "cy5wcm90b2NvbC5jaGFubmVsLnYxLk1lbWJlclN0YXRlQgJIAmIGcHJvdG8z")); + descriptor = pbr::FileDescriptor.FromGeneratedCode(descriptorData, + new pbr::FileDescriptor[] { Bgs.Protocol.AttributeTypesReflection.Descriptor, Bgs.Protocol.EntityTypesReflection.Descriptor, Bgs.Protocol.InvitationTypesReflection.Descriptor, Bgs.Protocol.RpcTypesReflection.Descriptor, }, + new pbr::GeneratedClrTypeInfo(null, new pbr::GeneratedClrTypeInfo[] { + new pbr::GeneratedClrTypeInfo(typeof(Bgs.Protocol.Channel.V1.ChannelId), Bgs.Protocol.Channel.V1.ChannelId.Parser, new[]{ "Type", "Host", "Id" }, null, null, null), + new pbr::GeneratedClrTypeInfo(typeof(Bgs.Protocol.Channel.V1.Message), Bgs.Protocol.Channel.V1.Message.Parser, new[]{ "Attribute", "Role" }, null, null, null), + new pbr::GeneratedClrTypeInfo(typeof(Bgs.Protocol.Channel.V1.ListChannelsOptions), Bgs.Protocol.Channel.V1.ListChannelsOptions.Parser, new[]{ "StartIndex", "MaxResults", "Name", "Program", "Locale", "CapacityFull", "AttributeFilter", "ChannelType" }, null, null, null), + new pbr::GeneratedClrTypeInfo(typeof(Bgs.Protocol.Channel.V1.ChannelDescription), Bgs.Protocol.Channel.V1.ChannelDescription.Parser, new[]{ "ChannelId", "CurrentMembers", "State" }, null, null, null), + new pbr::GeneratedClrTypeInfo(typeof(Bgs.Protocol.Channel.V1.ChannelInfo), Bgs.Protocol.Channel.V1.ChannelInfo.Parser, new[]{ "Description", "Member" }, null, null, null), + new pbr::GeneratedClrTypeInfo(typeof(Bgs.Protocol.Channel.V1.ChannelState), Bgs.Protocol.Channel.V1.ChannelState.Parser, new[]{ "MaxMembers", "MinMembers", "Attribute", "Invitation", "MaxInvitations", "Reason", "PrivacyLevel", "Name", "DelegateName", "ChannelType", "Program", "AllowOfflineMembers", "SubscribeToPresence", "DestroyOnFounderLeave" }, null, new[]{ typeof(Bgs.Protocol.Channel.V1.ChannelState.Types.PrivacyLevel) }, null), + new pbr::GeneratedClrTypeInfo(typeof(Bgs.Protocol.Channel.V1.MemberState), Bgs.Protocol.Channel.V1.MemberState.Parser, new[]{ "Attribute", "Role", "Privileges", "Info", "DEPRECATEDHidden" }, null, null, null), + new pbr::GeneratedClrTypeInfo(typeof(Bgs.Protocol.Channel.V1.Member), Bgs.Protocol.Channel.V1.Member.Parser, new[]{ "Identity", "State" }, null, null, null) + })); + } + #endregion + + } + #region Messages + [System.Diagnostics.DebuggerNonUserCodeAttribute()] + public sealed partial class ChannelId : pb::IMessage { + private static readonly pb::MessageParser _parser = new pb::MessageParser(() => new ChannelId()); + public static pb::MessageParser Parser { get { return _parser; } } + + public static pbr::MessageDescriptor Descriptor { + get { return Bgs.Protocol.Channel.V1.ChannelTypesReflection.Descriptor.MessageTypes[0]; } + } + + pbr::MessageDescriptor pb::IMessage.Descriptor { + get { return Descriptor; } + } + + public ChannelId() { + OnConstruction(); + } + + partial void OnConstruction(); + + public ChannelId(ChannelId other) : this() { + type_ = other.type_; + Host = other.host_ != null ? other.Host.Clone() : null; + id_ = other.id_; + } + + public ChannelId Clone() { + return new ChannelId(this); + } + + /// Field number for the "type" field. + public const int TypeFieldNumber = 1; + private uint type_; + public uint Type { + get { return type_; } + set { + type_ = value; + } + } + + /// Field number for the "host" field. + public const int HostFieldNumber = 2; + private Bgs.Protocol.ProcessId host_; + public Bgs.Protocol.ProcessId Host { + get { return host_; } + set { + host_ = value; + } + } + + /// Field number for the "id" field. + public const int IdFieldNumber = 3; + private uint id_; + public uint Id { + get { return id_; } + set { + id_ = value; + } + } + + public override bool Equals(object other) { + return Equals(other as ChannelId); + } + + public bool Equals(ChannelId other) { + if (ReferenceEquals(other, null)) { + return false; + } + if (ReferenceEquals(other, this)) { + return true; + } + if (Type != other.Type) return false; + if (!object.Equals(Host, other.Host)) return false; + if (Id != other.Id) return false; + return true; + } + + public override int GetHashCode() { + int hash = 1; + if (Type != 0) hash ^= Type.GetHashCode(); + if (host_ != null) hash ^= Host.GetHashCode(); + if (Id != 0) hash ^= Id.GetHashCode(); + return hash; + } + + public override string ToString() { + return pb::JsonFormatter.ToDiagnosticString(this); + } + + public void WriteTo(pb::CodedOutputStream output) { + if (Type != 0) { + output.WriteRawTag(8); + output.WriteUInt32(Type); + } + if (host_ != null) { + output.WriteRawTag(18); + output.WriteMessage(Host); + } + if (Id != 0) { + output.WriteRawTag(29); + output.WriteFixed32(Id); + } + } + + public int CalculateSize() { + int size = 0; + if (Type != 0) { + size += 1 + pb::CodedOutputStream.ComputeUInt32Size(Type); + } + if (host_ != null) { + size += 1 + pb::CodedOutputStream.ComputeMessageSize(Host); + } + if (Id != 0) { + size += 1 + 4; + } + return size; + } + + public void MergeFrom(ChannelId other) { + if (other == null) { + return; + } + if (other.Type != 0) { + Type = other.Type; + } + if (other.host_ != null) { + if (host_ == null) { + host_ = new Bgs.Protocol.ProcessId(); + } + Host.MergeFrom(other.Host); + } + if (other.Id != 0) { + Id = other.Id; + } + } + + public void MergeFrom(pb::CodedInputStream input) { + uint tag; + while ((tag = input.ReadTag()) != 0) { + switch(tag) { + default: + input.SkipLastField(); + break; + case 8: { + Type = input.ReadUInt32(); + break; + } + case 18: { + if (host_ == null) { + host_ = new Bgs.Protocol.ProcessId(); + } + input.ReadMessage(host_); + break; + } + case 29: { + Id = input.ReadFixed32(); + break; + } + } + } + } + + } + + [System.Diagnostics.DebuggerNonUserCodeAttribute()] + public sealed partial class Message : pb::IMessage { + private static readonly pb::MessageParser _parser = new pb::MessageParser(() => new Message()); + public static pb::MessageParser Parser { get { return _parser; } } + + public static pbr::MessageDescriptor Descriptor { + get { return Bgs.Protocol.Channel.V1.ChannelTypesReflection.Descriptor.MessageTypes[1]; } + } + + pbr::MessageDescriptor pb::IMessage.Descriptor { + get { return Descriptor; } + } + + public Message() { + OnConstruction(); + } + + partial void OnConstruction(); + + public Message(Message other) : this() { + attribute_ = other.attribute_.Clone(); + role_ = other.role_; + } + + public Message Clone() { + return new Message(this); + } + + /// Field number for the "attribute" field. + public const int AttributeFieldNumber = 1; + private static readonly pb::FieldCodec _repeated_attribute_codec + = pb::FieldCodec.ForMessage(10, Bgs.Protocol.Attribute.Parser); + private readonly pbc::RepeatedField attribute_ = new pbc::RepeatedField(); + public pbc::RepeatedField Attribute { + get { return attribute_; } + } + + /// Field number for the "role" field. + public const int RoleFieldNumber = 2; + private uint role_; + public uint Role { + get { return role_; } + set { + role_ = value; + } + } + + public override bool Equals(object other) { + return Equals(other as Message); + } + + public bool Equals(Message other) { + if (ReferenceEquals(other, null)) { + return false; + } + if (ReferenceEquals(other, this)) { + return true; + } + if(!attribute_.Equals(other.attribute_)) return false; + if (Role != other.Role) return false; + return true; + } + + public override int GetHashCode() { + int hash = 1; + hash ^= attribute_.GetHashCode(); + if (Role != 0) hash ^= Role.GetHashCode(); + return hash; + } + + public override string ToString() { + return pb::JsonFormatter.ToDiagnosticString(this); + } + + public void WriteTo(pb::CodedOutputStream output) { + attribute_.WriteTo(output, _repeated_attribute_codec); + if (Role != 0) { + output.WriteRawTag(16); + output.WriteUInt32(Role); + } + } + + public int CalculateSize() { + int size = 0; + size += attribute_.CalculateSize(_repeated_attribute_codec); + if (Role != 0) { + size += 1 + pb::CodedOutputStream.ComputeUInt32Size(Role); + } + return size; + } + + public void MergeFrom(Message other) { + if (other == null) { + return; + } + attribute_.Add(other.attribute_); + if (other.Role != 0) { + Role = other.Role; + } + } + + public void MergeFrom(pb::CodedInputStream input) { + uint tag; + while ((tag = input.ReadTag()) != 0) { + switch(tag) { + default: + input.SkipLastField(); + break; + case 10: { + attribute_.AddEntriesFrom(input, _repeated_attribute_codec); + break; + } + case 16: { + Role = input.ReadUInt32(); + break; + } + } + } + } + + } + + [System.Diagnostics.DebuggerNonUserCodeAttribute()] + public sealed partial class ListChannelsOptions : pb::IMessage { + private static readonly pb::MessageParser _parser = new pb::MessageParser(() => new ListChannelsOptions()); + public static pb::MessageParser Parser { get { return _parser; } } + + public static pbr::MessageDescriptor Descriptor { + get { return Bgs.Protocol.Channel.V1.ChannelTypesReflection.Descriptor.MessageTypes[2]; } + } + + pbr::MessageDescriptor pb::IMessage.Descriptor { + get { return Descriptor; } + } + + public ListChannelsOptions() { + OnConstruction(); + } + + partial void OnConstruction(); + + public ListChannelsOptions(ListChannelsOptions other) : this() { + startIndex_ = other.startIndex_; + maxResults_ = other.maxResults_; + name_ = other.name_; + program_ = other.program_; + locale_ = other.locale_; + capacityFull_ = other.capacityFull_; + AttributeFilter = other.attributeFilter_ != null ? other.AttributeFilter.Clone() : null; + channelType_ = other.channelType_; + } + + public ListChannelsOptions Clone() { + return new ListChannelsOptions(this); + } + + /// Field number for the "start_index" field. + public const int StartIndexFieldNumber = 1; + private uint startIndex_; + public uint StartIndex { + get { return startIndex_; } + set { + startIndex_ = value; + } + } + + /// Field number for the "max_results" field. + public const int MaxResultsFieldNumber = 2; + private uint maxResults_; + public uint MaxResults { + get { return maxResults_; } + set { + maxResults_ = value; + } + } + + /// Field number for the "name" field. + public const int NameFieldNumber = 3; + private string name_ = ""; + public string Name { + get { return name_; } + set { + name_ = pb::ProtoPreconditions.CheckNotNull(value, "value"); + } + } + + /// Field number for the "program" field. + public const int ProgramFieldNumber = 4; + private uint program_; + public uint Program { + get { return program_; } + set { + program_ = value; + } + } + + /// Field number for the "locale" field. + public const int LocaleFieldNumber = 5; + private uint locale_; + public uint Locale { + get { return locale_; } + set { + locale_ = value; + } + } + + /// Field number for the "capacity_full" field. + public const int CapacityFullFieldNumber = 6; + private uint capacityFull_; + public uint CapacityFull { + get { return capacityFull_; } + set { + capacityFull_ = value; + } + } + + /// Field number for the "attribute_filter" field. + public const int AttributeFilterFieldNumber = 7; + private Bgs.Protocol.AttributeFilter attributeFilter_; + public Bgs.Protocol.AttributeFilter AttributeFilter { + get { return attributeFilter_; } + set { + attributeFilter_ = value; + } + } + + /// Field number for the "channel_type" field. + public const int ChannelTypeFieldNumber = 8; + private string channelType_ = ""; + public string ChannelType { + get { return channelType_; } + set { + channelType_ = pb::ProtoPreconditions.CheckNotNull(value, "value"); + } + } + + public override bool Equals(object other) { + return Equals(other as ListChannelsOptions); + } + + public bool Equals(ListChannelsOptions other) { + if (ReferenceEquals(other, null)) { + return false; + } + if (ReferenceEquals(other, this)) { + return true; + } + if (StartIndex != other.StartIndex) return false; + if (MaxResults != other.MaxResults) return false; + if (Name != other.Name) return false; + if (Program != other.Program) return false; + if (Locale != other.Locale) return false; + if (CapacityFull != other.CapacityFull) return false; + if (!object.Equals(AttributeFilter, other.AttributeFilter)) return false; + if (ChannelType != other.ChannelType) return false; + return true; + } + + public override int GetHashCode() { + int hash = 1; + if (StartIndex != 0) hash ^= StartIndex.GetHashCode(); + if (MaxResults != 0) hash ^= MaxResults.GetHashCode(); + if (Name.Length != 0) hash ^= Name.GetHashCode(); + if (Program != 0) hash ^= Program.GetHashCode(); + if (Locale != 0) hash ^= Locale.GetHashCode(); + if (CapacityFull != 0) hash ^= CapacityFull.GetHashCode(); + if (attributeFilter_ != null) hash ^= AttributeFilter.GetHashCode(); + if (ChannelType.Length != 0) hash ^= ChannelType.GetHashCode(); + return hash; + } + + public override string ToString() { + return pb::JsonFormatter.ToDiagnosticString(this); + } + + public void WriteTo(pb::CodedOutputStream output) { + if (StartIndex != 0) { + output.WriteRawTag(8); + output.WriteUInt32(StartIndex); + } + if (MaxResults != 0) { + output.WriteRawTag(16); + output.WriteUInt32(MaxResults); + } + if (Name.Length != 0) { + output.WriteRawTag(26); + output.WriteString(Name); + } + if (Program != 0) { + output.WriteRawTag(37); + output.WriteFixed32(Program); + } + if (Locale != 0) { + output.WriteRawTag(45); + output.WriteFixed32(Locale); + } + if (CapacityFull != 0) { + output.WriteRawTag(48); + output.WriteUInt32(CapacityFull); + } + if (attributeFilter_ != null) { + output.WriteRawTag(58); + output.WriteMessage(AttributeFilter); + } + if (ChannelType.Length != 0) { + output.WriteRawTag(66); + output.WriteString(ChannelType); + } + } + + public int CalculateSize() { + int size = 0; + if (StartIndex != 0) { + size += 1 + pb::CodedOutputStream.ComputeUInt32Size(StartIndex); + } + if (MaxResults != 0) { + size += 1 + pb::CodedOutputStream.ComputeUInt32Size(MaxResults); + } + if (Name.Length != 0) { + size += 1 + pb::CodedOutputStream.ComputeStringSize(Name); + } + if (Program != 0) { + size += 1 + 4; + } + if (Locale != 0) { + size += 1 + 4; + } + if (CapacityFull != 0) { + size += 1 + pb::CodedOutputStream.ComputeUInt32Size(CapacityFull); + } + if (attributeFilter_ != null) { + size += 1 + pb::CodedOutputStream.ComputeMessageSize(AttributeFilter); + } + if (ChannelType.Length != 0) { + size += 1 + pb::CodedOutputStream.ComputeStringSize(ChannelType); + } + return size; + } + + public void MergeFrom(ListChannelsOptions other) { + if (other == null) { + return; + } + if (other.StartIndex != 0) { + StartIndex = other.StartIndex; + } + if (other.MaxResults != 0) { + MaxResults = other.MaxResults; + } + if (other.Name.Length != 0) { + Name = other.Name; + } + if (other.Program != 0) { + Program = other.Program; + } + if (other.Locale != 0) { + Locale = other.Locale; + } + if (other.CapacityFull != 0) { + CapacityFull = other.CapacityFull; + } + if (other.attributeFilter_ != null) { + if (attributeFilter_ == null) { + attributeFilter_ = new Bgs.Protocol.AttributeFilter(); + } + AttributeFilter.MergeFrom(other.AttributeFilter); + } + if (other.ChannelType.Length != 0) { + ChannelType = other.ChannelType; + } + } + + public void MergeFrom(pb::CodedInputStream input) { + uint tag; + while ((tag = input.ReadTag()) != 0) { + switch(tag) { + default: + input.SkipLastField(); + break; + case 8: { + StartIndex = input.ReadUInt32(); + break; + } + case 16: { + MaxResults = input.ReadUInt32(); + break; + } + case 26: { + Name = input.ReadString(); + break; + } + case 37: { + Program = input.ReadFixed32(); + break; + } + case 45: { + Locale = input.ReadFixed32(); + break; + } + case 48: { + CapacityFull = input.ReadUInt32(); + break; + } + case 58: { + if (attributeFilter_ == null) { + attributeFilter_ = new Bgs.Protocol.AttributeFilter(); + } + input.ReadMessage(attributeFilter_); + break; + } + case 66: { + ChannelType = input.ReadString(); + break; + } + } + } + } + + } + + [System.Diagnostics.DebuggerNonUserCodeAttribute()] + public sealed partial class ChannelDescription : pb::IMessage { + private static readonly pb::MessageParser _parser = new pb::MessageParser(() => new ChannelDescription()); + public static pb::MessageParser Parser { get { return _parser; } } + + public static pbr::MessageDescriptor Descriptor { + get { return Bgs.Protocol.Channel.V1.ChannelTypesReflection.Descriptor.MessageTypes[3]; } + } + + pbr::MessageDescriptor pb::IMessage.Descriptor { + get { return Descriptor; } + } + + public ChannelDescription() { + OnConstruction(); + } + + partial void OnConstruction(); + + public ChannelDescription(ChannelDescription other) : this() { + ChannelId = other.channelId_ != null ? other.ChannelId.Clone() : null; + currentMembers_ = other.currentMembers_; + State = other.state_ != null ? other.State.Clone() : null; + } + + public ChannelDescription Clone() { + return new ChannelDescription(this); + } + + /// Field number for the "channel_id" field. + public const int ChannelIdFieldNumber = 1; + private Bgs.Protocol.EntityId channelId_; + public Bgs.Protocol.EntityId ChannelId { + get { return channelId_; } + set { + channelId_ = value; + } + } + + /// Field number for the "current_members" field. + public const int CurrentMembersFieldNumber = 2; + private uint currentMembers_; + public uint CurrentMembers { + get { return currentMembers_; } + set { + currentMembers_ = value; + } + } + + /// Field number for the "state" field. + public const int StateFieldNumber = 3; + private Bgs.Protocol.Channel.V1.ChannelState state_; + public Bgs.Protocol.Channel.V1.ChannelState State { + get { return state_; } + set { + state_ = value; + } + } + + public override bool Equals(object other) { + return Equals(other as ChannelDescription); + } + + public bool Equals(ChannelDescription other) { + if (ReferenceEquals(other, null)) { + return false; + } + if (ReferenceEquals(other, this)) { + return true; + } + if (!object.Equals(ChannelId, other.ChannelId)) return false; + if (CurrentMembers != other.CurrentMembers) return false; + if (!object.Equals(State, other.State)) return false; + return true; + } + + public override int GetHashCode() { + int hash = 1; + if (channelId_ != null) hash ^= ChannelId.GetHashCode(); + if (CurrentMembers != 0) hash ^= CurrentMembers.GetHashCode(); + if (state_ != null) hash ^= State.GetHashCode(); + return hash; + } + + public override string ToString() { + return pb::JsonFormatter.ToDiagnosticString(this); + } + + public void WriteTo(pb::CodedOutputStream output) { + if (channelId_ != null) { + output.WriteRawTag(10); + output.WriteMessage(ChannelId); + } + if (CurrentMembers != 0) { + output.WriteRawTag(16); + output.WriteUInt32(CurrentMembers); + } + if (state_ != null) { + output.WriteRawTag(26); + output.WriteMessage(State); + } + } + + public int CalculateSize() { + int size = 0; + if (channelId_ != null) { + size += 1 + pb::CodedOutputStream.ComputeMessageSize(ChannelId); + } + if (CurrentMembers != 0) { + size += 1 + pb::CodedOutputStream.ComputeUInt32Size(CurrentMembers); + } + if (state_ != null) { + size += 1 + pb::CodedOutputStream.ComputeMessageSize(State); + } + return size; + } + + public void MergeFrom(ChannelDescription other) { + if (other == null) { + return; + } + if (other.channelId_ != null) { + if (channelId_ == null) { + channelId_ = new Bgs.Protocol.EntityId(); + } + ChannelId.MergeFrom(other.ChannelId); + } + if (other.CurrentMembers != 0) { + CurrentMembers = other.CurrentMembers; + } + if (other.state_ != null) { + if (state_ == null) { + state_ = new Bgs.Protocol.Channel.V1.ChannelState(); + } + State.MergeFrom(other.State); + } + } + + public void MergeFrom(pb::CodedInputStream input) { + uint tag; + while ((tag = input.ReadTag()) != 0) { + switch(tag) { + default: + input.SkipLastField(); + break; + case 10: { + if (channelId_ == null) { + channelId_ = new Bgs.Protocol.EntityId(); + } + input.ReadMessage(channelId_); + break; + } + case 16: { + CurrentMembers = input.ReadUInt32(); + break; + } + case 26: { + if (state_ == null) { + state_ = new Bgs.Protocol.Channel.V1.ChannelState(); + } + input.ReadMessage(state_); + break; + } + } + } + } + + } + + [System.Diagnostics.DebuggerNonUserCodeAttribute()] + public sealed partial class ChannelInfo : pb::IMessage { + private static readonly pb::MessageParser _parser = new pb::MessageParser(() => new ChannelInfo()); + public static pb::MessageParser Parser { get { return _parser; } } + + public static pbr::MessageDescriptor Descriptor { + get { return Bgs.Protocol.Channel.V1.ChannelTypesReflection.Descriptor.MessageTypes[4]; } + } + + pbr::MessageDescriptor pb::IMessage.Descriptor { + get { return Descriptor; } + } + + public ChannelInfo() { + OnConstruction(); + } + + partial void OnConstruction(); + + public ChannelInfo(ChannelInfo other) : this() { + Description = other.description_ != null ? other.Description.Clone() : null; + member_ = other.member_.Clone(); + } + + public ChannelInfo Clone() { + return new ChannelInfo(this); + } + + /// Field number for the "description" field. + public const int DescriptionFieldNumber = 1; + private Bgs.Protocol.Channel.V1.ChannelDescription description_; + public Bgs.Protocol.Channel.V1.ChannelDescription Description { + get { return description_; } + set { + description_ = value; + } + } + + /// Field number for the "member" field. + public const int MemberFieldNumber = 2; + private static readonly pb::FieldCodec _repeated_member_codec + = pb::FieldCodec.ForMessage(18, Bgs.Protocol.Channel.V1.Member.Parser); + private readonly pbc::RepeatedField member_ = new pbc::RepeatedField(); + public pbc::RepeatedField Member { + get { return member_; } + } + + public override bool Equals(object other) { + return Equals(other as ChannelInfo); + } + + public bool Equals(ChannelInfo other) { + if (ReferenceEquals(other, null)) { + return false; + } + if (ReferenceEquals(other, this)) { + return true; + } + if (!object.Equals(Description, other.Description)) return false; + if(!member_.Equals(other.member_)) return false; + return true; + } + + public override int GetHashCode() { + int hash = 1; + if (description_ != null) hash ^= Description.GetHashCode(); + hash ^= member_.GetHashCode(); + return hash; + } + + public override string ToString() { + return pb::JsonFormatter.ToDiagnosticString(this); + } + + public void WriteTo(pb::CodedOutputStream output) { + if (description_ != null) { + output.WriteRawTag(10); + output.WriteMessage(Description); + } + member_.WriteTo(output, _repeated_member_codec); + } + + public int CalculateSize() { + int size = 0; + if (description_ != null) { + size += 1 + pb::CodedOutputStream.ComputeMessageSize(Description); + } + size += member_.CalculateSize(_repeated_member_codec); + return size; + } + + public void MergeFrom(ChannelInfo other) { + if (other == null) { + return; + } + if (other.description_ != null) { + if (description_ == null) { + description_ = new Bgs.Protocol.Channel.V1.ChannelDescription(); + } + Description.MergeFrom(other.Description); + } + member_.Add(other.member_); + } + + public void MergeFrom(pb::CodedInputStream input) { + uint tag; + while ((tag = input.ReadTag()) != 0) { + switch(tag) { + default: + input.SkipLastField(); + break; + case 10: { + if (description_ == null) { + description_ = new Bgs.Protocol.Channel.V1.ChannelDescription(); + } + input.ReadMessage(description_); + break; + } + case 18: { + member_.AddEntriesFrom(input, _repeated_member_codec); + break; + } + } + } + } + + } + + [System.Diagnostics.DebuggerNonUserCodeAttribute()] + public sealed partial class ChannelState : pb::IMessage { + private static readonly pb::MessageParser _parser = new pb::MessageParser(() => new ChannelState()); + public static pb::MessageParser Parser { get { return _parser; } } + + public static pbr::MessageDescriptor Descriptor { + get { return Bgs.Protocol.Channel.V1.ChannelTypesReflection.Descriptor.MessageTypes[5]; } + } + + pbr::MessageDescriptor pb::IMessage.Descriptor { + get { return Descriptor; } + } + + public ChannelState() { + OnConstruction(); + } + + partial void OnConstruction(); + + public ChannelState(ChannelState other) : this() { + maxMembers_ = other.maxMembers_; + minMembers_ = other.minMembers_; + attribute_ = other.attribute_.Clone(); + invitation_ = other.invitation_.Clone(); + maxInvitations_ = other.maxInvitations_; + reason_ = other.reason_; + privacyLevel_ = other.privacyLevel_; + name_ = other.name_; + delegateName_ = other.delegateName_; + channelType_ = other.channelType_; + program_ = other.program_; + allowOfflineMembers_ = other.allowOfflineMembers_; + subscribeToPresence_ = other.subscribeToPresence_; + destroyOnFounderLeave_ = other.destroyOnFounderLeave_; + } + + public ChannelState Clone() { + return new ChannelState(this); + } + + /// Field number for the "max_members" field. + public const int MaxMembersFieldNumber = 1; + private uint maxMembers_; + public uint MaxMembers { + get { return maxMembers_; } + set { + maxMembers_ = value; + } + } + + /// Field number for the "min_members" field. + public const int MinMembersFieldNumber = 2; + private uint minMembers_; + public uint MinMembers { + get { return minMembers_; } + set { + minMembers_ = value; + } + } + + /// Field number for the "attribute" field. + public const int AttributeFieldNumber = 3; + private static readonly pb::FieldCodec _repeated_attribute_codec + = pb::FieldCodec.ForMessage(26, Bgs.Protocol.Attribute.Parser); + private readonly pbc::RepeatedField attribute_ = new pbc::RepeatedField(); + public pbc::RepeatedField Attribute { + get { return attribute_; } + } + + /// Field number for the "invitation" field. + public const int InvitationFieldNumber = 4; + private static readonly pb::FieldCodec _repeated_invitation_codec + = pb::FieldCodec.ForMessage(34, Bgs.Protocol.Invitation.Parser); + private readonly pbc::RepeatedField invitation_ = new pbc::RepeatedField(); + public pbc::RepeatedField Invitation { + get { return invitation_; } + } + + /// Field number for the "max_invitations" field. + public const int MaxInvitationsFieldNumber = 5; + private uint maxInvitations_; + public uint MaxInvitations { + get { return maxInvitations_; } + set { + maxInvitations_ = value; + } + } + + /// Field number for the "reason" field. + public const int ReasonFieldNumber = 6; + private uint reason_; + public uint Reason { + get { return reason_; } + set { + reason_ = value; + } + } + + /// Field number for the "privacy_level" field. + public const int PrivacyLevelFieldNumber = 7; + private Bgs.Protocol.Channel.V1.ChannelState.Types.PrivacyLevel privacyLevel_ = Bgs.Protocol.Channel.V1.ChannelState.Types.PrivacyLevel.PRIVACY_LEVEL_NONE; + public Bgs.Protocol.Channel.V1.ChannelState.Types.PrivacyLevel PrivacyLevel { + get { return privacyLevel_; } + set { + privacyLevel_ = value; + } + } + + /// Field number for the "name" field. + public const int NameFieldNumber = 8; + private string name_ = ""; + public string Name { + get { return name_; } + set { + name_ = pb::ProtoPreconditions.CheckNotNull(value, "value"); + } + } + + /// Field number for the "delegate_name" field. + public const int DelegateNameFieldNumber = 9; + private string delegateName_ = ""; + public string DelegateName { + get { return delegateName_; } + set { + delegateName_ = pb::ProtoPreconditions.CheckNotNull(value, "value"); + } + } + + /// Field number for the "channel_type" field. + public const int ChannelTypeFieldNumber = 10; + private string channelType_ = ""; + public string ChannelType { + get { return channelType_; } + set { + channelType_ = pb::ProtoPreconditions.CheckNotNull(value, "value"); + } + } + + /// Field number for the "program" field. + public const int ProgramFieldNumber = 11; + private uint program_; + public uint Program { + get { return program_; } + set { + program_ = value; + } + } + + /// Field number for the "allow_offline_members" field. + public const int AllowOfflineMembersFieldNumber = 12; + private bool allowOfflineMembers_; + public bool AllowOfflineMembers { + get { return allowOfflineMembers_; } + set { + allowOfflineMembers_ = value; + } + } + + /// Field number for the "subscribe_to_presence" field. + public const int SubscribeToPresenceFieldNumber = 13; + private bool subscribeToPresence_; + public bool SubscribeToPresence { + get { return subscribeToPresence_; } + set { + subscribeToPresence_ = value; + } + } + + /// Field number for the "destroy_on_founder_leave" field. + public const int DestroyOnFounderLeaveFieldNumber = 14; + private bool destroyOnFounderLeave_; + public bool DestroyOnFounderLeave { + get { return destroyOnFounderLeave_; } + set { + destroyOnFounderLeave_ = value; + } + } + + public override bool Equals(object other) { + return Equals(other as ChannelState); + } + + public bool Equals(ChannelState other) { + if (ReferenceEquals(other, null)) { + return false; + } + if (ReferenceEquals(other, this)) { + return true; + } + if (MaxMembers != other.MaxMembers) return false; + if (MinMembers != other.MinMembers) return false; + if(!attribute_.Equals(other.attribute_)) return false; + if(!invitation_.Equals(other.invitation_)) return false; + if (MaxInvitations != other.MaxInvitations) return false; + if (Reason != other.Reason) return false; + if (PrivacyLevel != other.PrivacyLevel) return false; + if (Name != other.Name) return false; + if (DelegateName != other.DelegateName) return false; + if (ChannelType != other.ChannelType) return false; + if (Program != other.Program) return false; + if (AllowOfflineMembers != other.AllowOfflineMembers) return false; + if (SubscribeToPresence != other.SubscribeToPresence) return false; + if (DestroyOnFounderLeave != other.DestroyOnFounderLeave) return false; + return true; + } + + public override int GetHashCode() { + int hash = 1; + if (MaxMembers != 0) hash ^= MaxMembers.GetHashCode(); + if (MinMembers != 0) hash ^= MinMembers.GetHashCode(); + hash ^= attribute_.GetHashCode(); + hash ^= invitation_.GetHashCode(); + if (MaxInvitations != 0) hash ^= MaxInvitations.GetHashCode(); + if (Reason != 0) hash ^= Reason.GetHashCode(); + if (PrivacyLevel != Bgs.Protocol.Channel.V1.ChannelState.Types.PrivacyLevel.PRIVACY_LEVEL_NONE) hash ^= PrivacyLevel.GetHashCode(); + if (Name.Length != 0) hash ^= Name.GetHashCode(); + if (DelegateName.Length != 0) hash ^= DelegateName.GetHashCode(); + if (ChannelType.Length != 0) hash ^= ChannelType.GetHashCode(); + if (Program != 0) hash ^= Program.GetHashCode(); + if (AllowOfflineMembers != false) hash ^= AllowOfflineMembers.GetHashCode(); + if (SubscribeToPresence != false) hash ^= SubscribeToPresence.GetHashCode(); + if (DestroyOnFounderLeave != false) hash ^= DestroyOnFounderLeave.GetHashCode(); + return hash; + } + + public override string ToString() { + return pb::JsonFormatter.ToDiagnosticString(this); + } + + public void WriteTo(pb::CodedOutputStream output) { + if (MaxMembers != 0) { + output.WriteRawTag(8); + output.WriteUInt32(MaxMembers); + } + if (MinMembers != 0) { + output.WriteRawTag(16); + output.WriteUInt32(MinMembers); + } + attribute_.WriteTo(output, _repeated_attribute_codec); + invitation_.WriteTo(output, _repeated_invitation_codec); + if (MaxInvitations != 0) { + output.WriteRawTag(40); + output.WriteUInt32(MaxInvitations); + } + if (Reason != 0) { + output.WriteRawTag(48); + output.WriteUInt32(Reason); + } + if (PrivacyLevel != Bgs.Protocol.Channel.V1.ChannelState.Types.PrivacyLevel.PRIVACY_LEVEL_NONE) { + output.WriteRawTag(56); + output.WriteEnum((int) PrivacyLevel); + } + if (Name.Length != 0) { + output.WriteRawTag(66); + output.WriteString(Name); + } + if (DelegateName.Length != 0) { + output.WriteRawTag(74); + output.WriteString(DelegateName); + } + if (ChannelType.Length != 0) { + output.WriteRawTag(82); + output.WriteString(ChannelType); + } + if (Program != 0) { + output.WriteRawTag(93); + output.WriteFixed32(Program); + } + if (AllowOfflineMembers != false) { + output.WriteRawTag(96); + output.WriteBool(AllowOfflineMembers); + } + if (SubscribeToPresence != false) { + output.WriteRawTag(104); + output.WriteBool(SubscribeToPresence); + } + if (DestroyOnFounderLeave != false) { + output.WriteRawTag(112); + output.WriteBool(DestroyOnFounderLeave); + } + } + + public int CalculateSize() { + int size = 0; + if (MaxMembers != 0) { + size += 1 + pb::CodedOutputStream.ComputeUInt32Size(MaxMembers); + } + if (MinMembers != 0) { + size += 1 + pb::CodedOutputStream.ComputeUInt32Size(MinMembers); + } + size += attribute_.CalculateSize(_repeated_attribute_codec); + size += invitation_.CalculateSize(_repeated_invitation_codec); + if (MaxInvitations != 0) { + size += 1 + pb::CodedOutputStream.ComputeUInt32Size(MaxInvitations); + } + if (Reason != 0) { + size += 1 + pb::CodedOutputStream.ComputeUInt32Size(Reason); + } + if (PrivacyLevel != Bgs.Protocol.Channel.V1.ChannelState.Types.PrivacyLevel.PRIVACY_LEVEL_NONE) { + size += 1 + pb::CodedOutputStream.ComputeEnumSize((int) PrivacyLevel); + } + if (Name.Length != 0) { + size += 1 + pb::CodedOutputStream.ComputeStringSize(Name); + } + if (DelegateName.Length != 0) { + size += 1 + pb::CodedOutputStream.ComputeStringSize(DelegateName); + } + if (ChannelType.Length != 0) { + size += 1 + pb::CodedOutputStream.ComputeStringSize(ChannelType); + } + if (Program != 0) { + size += 1 + 4; + } + if (AllowOfflineMembers != false) { + size += 1 + 1; + } + if (SubscribeToPresence != false) { + size += 1 + 1; + } + if (DestroyOnFounderLeave != false) { + size += 1 + 1; + } + return size; + } + + public void MergeFrom(ChannelState other) { + if (other == null) { + return; + } + if (other.MaxMembers != 0) { + MaxMembers = other.MaxMembers; + } + if (other.MinMembers != 0) { + MinMembers = other.MinMembers; + } + attribute_.Add(other.attribute_); + invitation_.Add(other.invitation_); + if (other.MaxInvitations != 0) { + MaxInvitations = other.MaxInvitations; + } + if (other.Reason != 0) { + Reason = other.Reason; + } + if (other.PrivacyLevel != Bgs.Protocol.Channel.V1.ChannelState.Types.PrivacyLevel.PRIVACY_LEVEL_NONE) { + PrivacyLevel = other.PrivacyLevel; + } + if (other.Name.Length != 0) { + Name = other.Name; + } + if (other.DelegateName.Length != 0) { + DelegateName = other.DelegateName; + } + if (other.ChannelType.Length != 0) { + ChannelType = other.ChannelType; + } + if (other.Program != 0) { + Program = other.Program; + } + if (other.AllowOfflineMembers != false) { + AllowOfflineMembers = other.AllowOfflineMembers; + } + if (other.SubscribeToPresence != false) { + SubscribeToPresence = other.SubscribeToPresence; + } + if (other.DestroyOnFounderLeave != false) { + DestroyOnFounderLeave = other.DestroyOnFounderLeave; + } + } + + public void MergeFrom(pb::CodedInputStream input) { + uint tag; + while ((tag = input.ReadTag()) != 0) { + switch(tag) { + default: + input.SkipLastField(); + break; + case 8: { + MaxMembers = input.ReadUInt32(); + break; + } + case 16: { + MinMembers = input.ReadUInt32(); + break; + } + case 26: { + attribute_.AddEntriesFrom(input, _repeated_attribute_codec); + break; + } + case 34: { + invitation_.AddEntriesFrom(input, _repeated_invitation_codec); + break; + } + case 40: { + MaxInvitations = input.ReadUInt32(); + break; + } + case 48: { + Reason = input.ReadUInt32(); + break; + } + case 56: { + privacyLevel_ = (Bgs.Protocol.Channel.V1.ChannelState.Types.PrivacyLevel) input.ReadEnum(); + break; + } + case 66: { + Name = input.ReadString(); + break; + } + case 74: { + DelegateName = input.ReadString(); + break; + } + case 82: { + ChannelType = input.ReadString(); + break; + } + case 93: { + Program = input.ReadFixed32(); + break; + } + case 96: { + AllowOfflineMembers = input.ReadBool(); + break; + } + case 104: { + SubscribeToPresence = input.ReadBool(); + break; + } + case 112: { + DestroyOnFounderLeave = input.ReadBool(); + break; + } + } + } + } + + #region Nested types + /// Container for nested types declared in the ChannelState message type. + [System.Diagnostics.DebuggerNonUserCodeAttribute()] + public static partial class Types { + public enum PrivacyLevel { + PRIVACY_LEVEL_NONE = 0, + PRIVACY_LEVEL_OPEN = 1, + PRIVACY_LEVEL_OPEN_INVITATION_AND_FRIEND = 2, + PRIVACY_LEVEL_OPEN_INVITATION = 3, + PRIVACY_LEVEL_CLOSED = 4, + } + + } + #endregion + + } + + [System.Diagnostics.DebuggerNonUserCodeAttribute()] + public sealed partial class MemberState : pb::IMessage { + private static readonly pb::MessageParser _parser = new pb::MessageParser(() => new MemberState()); + public static pb::MessageParser Parser { get { return _parser; } } + + public static pbr::MessageDescriptor Descriptor { + get { return Bgs.Protocol.Channel.V1.ChannelTypesReflection.Descriptor.MessageTypes[6]; } + } + + pbr::MessageDescriptor pb::IMessage.Descriptor { + get { return Descriptor; } + } + + public MemberState() { + OnConstruction(); + } + + partial void OnConstruction(); + + public MemberState(MemberState other) : this() { + attribute_ = other.attribute_.Clone(); + role_ = other.role_.Clone(); + privileges_ = other.privileges_; + Info = other.info_ != null ? other.Info.Clone() : null; + dEPRECATEDHidden_ = other.dEPRECATEDHidden_; + } + + public MemberState Clone() { + return new MemberState(this); + } + + /// Field number for the "attribute" field. + public const int AttributeFieldNumber = 1; + private static readonly pb::FieldCodec _repeated_attribute_codec + = pb::FieldCodec.ForMessage(10, Bgs.Protocol.Attribute.Parser); + private readonly pbc::RepeatedField attribute_ = new pbc::RepeatedField(); + public pbc::RepeatedField Attribute { + get { return attribute_; } + } + + /// Field number for the "role" field. + public const int RoleFieldNumber = 2; + private static readonly pb::FieldCodec _repeated_role_codec + = pb::FieldCodec.ForUInt32(18); + private readonly pbc::RepeatedField role_ = new pbc::RepeatedField(); + public pbc::RepeatedField Role { + get { return role_; } + } + + /// Field number for the "privileges" field. + public const int PrivilegesFieldNumber = 3; + private ulong privileges_; + public ulong Privileges { + get { return privileges_; } + set { + privileges_ = value; + } + } + + /// Field number for the "info" field. + public const int InfoFieldNumber = 4; + private Bgs.Protocol.AccountInfo info_; + public Bgs.Protocol.AccountInfo Info { + get { return info_; } + set { + info_ = value; + } + } + + /// Field number for the "DEPRECATED_hidden" field. + public const int DEPRECATEDHiddenFieldNumber = 5; + private bool dEPRECATEDHidden_; + [System.ObsoleteAttribute()] + public bool DEPRECATEDHidden { + get { return dEPRECATEDHidden_; } + set { + dEPRECATEDHidden_ = value; + } + } + + public override bool Equals(object other) { + return Equals(other as MemberState); + } + + public bool Equals(MemberState other) { + if (ReferenceEquals(other, null)) { + return false; + } + if (ReferenceEquals(other, this)) { + return true; + } + if(!attribute_.Equals(other.attribute_)) return false; + if(!role_.Equals(other.role_)) return false; + if (Privileges != other.Privileges) return false; + if (!object.Equals(Info, other.Info)) return false; + if (DEPRECATEDHidden != other.DEPRECATEDHidden) return false; + return true; + } + + public override int GetHashCode() { + int hash = 1; + hash ^= attribute_.GetHashCode(); + hash ^= role_.GetHashCode(); + if (Privileges != 0UL) hash ^= Privileges.GetHashCode(); + if (info_ != null) hash ^= Info.GetHashCode(); + if (DEPRECATEDHidden != false) hash ^= DEPRECATEDHidden.GetHashCode(); + return hash; + } + + public override string ToString() { + return pb::JsonFormatter.ToDiagnosticString(this); + } + + public void WriteTo(pb::CodedOutputStream output) { + attribute_.WriteTo(output, _repeated_attribute_codec); + role_.WriteTo(output, _repeated_role_codec); + if (Privileges != 0UL) { + output.WriteRawTag(24); + output.WriteUInt64(Privileges); + } + if (info_ != null) { + output.WriteRawTag(34); + output.WriteMessage(Info); + } + if (DEPRECATEDHidden != false) { + output.WriteRawTag(40); + output.WriteBool(DEPRECATEDHidden); + } + } + + public int CalculateSize() { + int size = 0; + size += attribute_.CalculateSize(_repeated_attribute_codec); + size += role_.CalculateSize(_repeated_role_codec); + if (Privileges != 0UL) { + size += 1 + pb::CodedOutputStream.ComputeUInt64Size(Privileges); + } + if (info_ != null) { + size += 1 + pb::CodedOutputStream.ComputeMessageSize(Info); + } + if (DEPRECATEDHidden != false) { + size += 1 + 1; + } + return size; + } + + public void MergeFrom(MemberState other) { + if (other == null) { + return; + } + attribute_.Add(other.attribute_); + role_.Add(other.role_); + if (other.Privileges != 0UL) { + Privileges = other.Privileges; + } + if (other.info_ != null) { + if (info_ == null) { + info_ = new Bgs.Protocol.AccountInfo(); + } + Info.MergeFrom(other.Info); + } + if (other.DEPRECATEDHidden != false) { + DEPRECATEDHidden = other.DEPRECATEDHidden; + } + } + + public void MergeFrom(pb::CodedInputStream input) { + uint tag; + while ((tag = input.ReadTag()) != 0) { + switch(tag) { + default: + input.SkipLastField(); + break; + case 10: { + attribute_.AddEntriesFrom(input, _repeated_attribute_codec); + break; + } + case 18: + case 16: { + role_.AddEntriesFrom(input, _repeated_role_codec); + break; + } + case 24: { + Privileges = input.ReadUInt64(); + break; + } + case 34: { + if (info_ == null) { + info_ = new Bgs.Protocol.AccountInfo(); + } + input.ReadMessage(info_); + break; + } + case 40: { + DEPRECATEDHidden = input.ReadBool(); + break; + } + } + } + } + + } + + [System.Diagnostics.DebuggerNonUserCodeAttribute()] + public sealed partial class Member : pb::IMessage { + private static readonly pb::MessageParser _parser = new pb::MessageParser(() => new Member()); + public static pb::MessageParser Parser { get { return _parser; } } + + public static pbr::MessageDescriptor Descriptor { + get { return Bgs.Protocol.Channel.V1.ChannelTypesReflection.Descriptor.MessageTypes[7]; } + } + + pbr::MessageDescriptor pb::IMessage.Descriptor { + get { return Descriptor; } + } + + public Member() { + OnConstruction(); + } + + partial void OnConstruction(); + + public Member(Member other) : this() { + Identity = other.identity_ != null ? other.Identity.Clone() : null; + State = other.state_ != null ? other.State.Clone() : null; + } + + public Member Clone() { + return new Member(this); + } + + /// Field number for the "identity" field. + public const int IdentityFieldNumber = 1; + private Bgs.Protocol.Identity identity_; + public Bgs.Protocol.Identity Identity { + get { return identity_; } + set { + identity_ = value; + } + } + + /// Field number for the "state" field. + public const int StateFieldNumber = 2; + private Bgs.Protocol.Channel.V1.MemberState state_; + public Bgs.Protocol.Channel.V1.MemberState State { + get { return state_; } + set { + state_ = value; + } + } + + public override bool Equals(object other) { + return Equals(other as Member); + } + + public bool Equals(Member other) { + if (ReferenceEquals(other, null)) { + return false; + } + if (ReferenceEquals(other, this)) { + return true; + } + if (!object.Equals(Identity, other.Identity)) return false; + if (!object.Equals(State, other.State)) return false; + return true; + } + + public override int GetHashCode() { + int hash = 1; + if (identity_ != null) hash ^= Identity.GetHashCode(); + if (state_ != null) hash ^= State.GetHashCode(); + return hash; + } + + public override string ToString() { + return pb::JsonFormatter.ToDiagnosticString(this); + } + + public void WriteTo(pb::CodedOutputStream output) { + if (identity_ != null) { + output.WriteRawTag(10); + output.WriteMessage(Identity); + } + if (state_ != null) { + output.WriteRawTag(18); + output.WriteMessage(State); + } + } + + public int CalculateSize() { + int size = 0; + if (identity_ != null) { + size += 1 + pb::CodedOutputStream.ComputeMessageSize(Identity); + } + if (state_ != null) { + size += 1 + pb::CodedOutputStream.ComputeMessageSize(State); + } + return size; + } + + public void MergeFrom(Member other) { + if (other == null) { + return; + } + if (other.identity_ != null) { + if (identity_ == null) { + identity_ = new Bgs.Protocol.Identity(); + } + Identity.MergeFrom(other.Identity); + } + if (other.state_ != null) { + if (state_ == null) { + state_ = new Bgs.Protocol.Channel.V1.MemberState(); + } + State.MergeFrom(other.State); + } + } + + public void MergeFrom(pb::CodedInputStream input) { + uint tag; + while ((tag = input.ReadTag()) != 0) { + switch(tag) { + default: + input.SkipLastField(); + break; + case 10: { + if (identity_ == null) { + identity_ = new Bgs.Protocol.Identity(); + } + input.ReadMessage(identity_); + break; + } + case 18: { + if (state_ == null) { + state_ = new Bgs.Protocol.Channel.V1.MemberState(); + } + input.ReadMessage(state_); + break; + } + } + } + } + + } + + #endregion + +} + +#endregion Designer generated code diff --git a/Framework/Proto/ConnectionService.cs b/Framework/Proto/ConnectionService.cs new file mode 100644 index 000000000..d2c07f4b2 --- /dev/null +++ b/Framework/Proto/ConnectionService.cs @@ -0,0 +1,2025 @@ +// Generated by the protocol buffer compiler. DO NOT EDIT! +// source: bgs/low/pb/client/connection_service.proto +#pragma warning disable 1591, 0612, 3021 +#region Designer generated code + +using pb = Google.Protobuf; +using pbc = Google.Protobuf.Collections; +using pbr = Google.Protobuf.Reflection; +namespace Bgs.Protocol.Connection.V1 +{ + + /// Holder for reflection information generated from bgs/low/pb/client/connection_service.proto + [System.Diagnostics.DebuggerNonUserCodeAttribute()] + public static partial class ConnectionServiceReflection + { + + #region Descriptor + /// File descriptor for bgs/low/pb/client/connection_service.proto + public static pbr::FileDescriptor Descriptor + { + get { return descriptor; } + } + private static pbr::FileDescriptor descriptor; + + static ConnectionServiceReflection() + { + byte[] descriptorData = System.Convert.FromBase64String( + string.Concat( + "CipiZ3MvbG93L3BiL2NsaWVudC9jb25uZWN0aW9uX3NlcnZpY2UucHJvdG8S", + "GmJncy5wcm90b2NvbC5jb25uZWN0aW9uLnYxGixiZ3MvbG93L3BiL2NsaWVu", + "dC9jb250ZW50X2hhbmRsZV90eXBlcy5wcm90bxohYmdzL2xvdy9wYi9jbGll", + "bnQvcnBjX3R5cGVzLnByb3RvIpUBCg5Db25uZWN0UmVxdWVzdBIqCgljbGll", + "bnRfaWQYASABKAsyFy5iZ3MucHJvdG9jb2wuUHJvY2Vzc0lkEj0KDGJpbmRf", + "cmVxdWVzdBgCIAEoCzInLmJncy5wcm90b2NvbC5jb25uZWN0aW9uLnYxLkJp", + "bmRSZXF1ZXN0EhgKEHVzZV9iaW5kbGVzc19ycGMYAyABKAgiVwogQ29ubmVj", + "dGlvbk1ldGVyaW5nQ29udGVudEhhbmRsZXMSMwoOY29udGVudF9oYW5kbGUY", + "ASADKAsyGy5iZ3MucHJvdG9jb2wuQ29udGVudEhhbmRsZSKtAwoPQ29ubmVj", + "dFJlc3BvbnNlEioKCXNlcnZlcl9pZBgBIAEoCzIXLmJncy5wcm90b2NvbC5Q", + "cm9jZXNzSWQSKgoJY2xpZW50X2lkGAIgASgLMhcuYmdzLnByb3RvY29sLlBy", + "b2Nlc3NJZBITCgtiaW5kX3Jlc3VsdBgDIAEoDRI/Cg1iaW5kX3Jlc3BvbnNl", + "GAQgASgLMiguYmdzLnByb3RvY29sLmNvbm5lY3Rpb24udjEuQmluZFJlc3Bv", + "bnNlEloKFGNvbnRlbnRfaGFuZGxlX2FycmF5GAUgASgLMjwuYmdzLnByb3Rv", + "Y29sLmNvbm5lY3Rpb24udjEuQ29ubmVjdGlvbk1ldGVyaW5nQ29udGVudEhh", + "bmRsZXMSEwoLc2VydmVyX3RpbWUYBiABKAQSGAoQdXNlX2JpbmRsZXNzX3Jw", + "YxgHIAEoCBJhChtiaW5hcnlfY29udGVudF9oYW5kbGVfYXJyYXkYCCABKAsy", + "PC5iZ3MucHJvdG9jb2wuY29ubmVjdGlvbi52MS5Db25uZWN0aW9uTWV0ZXJp", + "bmdDb250ZW50SGFuZGxlcyIoCgxCb3VuZFNlcnZpY2USDAoEaGFzaBgBIAEo", + "BxIKCgJpZBgCIAEoDSKYAgoLQmluZFJlcXVlc3QSLgogZGVwcmVjYXRlZF9p", + "bXBvcnRlZF9zZXJ2aWNlX2hhc2gYASADKAdCBBABGAESUQobZGVwcmVjYXRl", + "ZF9leHBvcnRlZF9zZXJ2aWNlGAIgAygLMiguYmdzLnByb3RvY29sLmNvbm5l", + "Y3Rpb24udjEuQm91bmRTZXJ2aWNlQgIYARJCChBleHBvcnRlZF9zZXJ2aWNl", + "GAMgAygLMiguYmdzLnByb3RvY29sLmNvbm5lY3Rpb24udjEuQm91bmRTZXJ2", + "aWNlEkIKEGltcG9ydGVkX3NlcnZpY2UYBCADKAsyKC5iZ3MucHJvdG9jb2wu", + "Y29ubmVjdGlvbi52MS5Cb3VuZFNlcnZpY2UiMQoMQmluZFJlc3BvbnNlEiEK", + "E2ltcG9ydGVkX3NlcnZpY2VfaWQYASADKA1CBBABGAEiQgoLRWNob1JlcXVl", + "c3QSDAoEdGltZRgBIAEoBhIUCgxuZXR3b3JrX29ubHkYAiABKAgSDwoHcGF5", + "bG9hZBgDIAEoDCItCgxFY2hvUmVzcG9uc2USDAoEdGltZRgBIAEoBhIPCgdw", + "YXlsb2FkGAIgASgMIicKEURpc2Nvbm5lY3RSZXF1ZXN0EhIKCmVycm9yX2Nv", + "ZGUYASABKA0iPAoWRGlzY29ubmVjdE5vdGlmaWNhdGlvbhISCgplcnJvcl9j", + "b2RlGAEgASgNEg4KBnJlYXNvbhgCIAEoCSIQCg5FbmNyeXB0UmVxdWVzdDKD", + "BQoRQ29ubmVjdGlvblNlcnZpY2USYgoHQ29ubmVjdBIqLmJncy5wcm90b2Nv", + "bC5jb25uZWN0aW9uLnYxLkNvbm5lY3RSZXF1ZXN0GisuYmdzLnByb3RvY29s", + "LmNvbm5lY3Rpb24udjEuQ29ubmVjdFJlc3BvbnNlEl4KBEJpbmQSJy5iZ3Mu", + "cHJvdG9jb2wuY29ubmVjdGlvbi52MS5CaW5kUmVxdWVzdBooLmJncy5wcm90", + "b2NvbC5jb25uZWN0aW9uLnYxLkJpbmRSZXNwb25zZSIDiAIBElkKBEVjaG8S", + "Jy5iZ3MucHJvdG9jb2wuY29ubmVjdGlvbi52MS5FY2hvUmVxdWVzdBooLmJn", + "cy5wcm90b2NvbC5jb25uZWN0aW9uLnYxLkVjaG9SZXNwb25zZRJgCg9Gb3Jj", + "ZURpc2Nvbm5lY3QSMi5iZ3MucHJvdG9jb2wuY29ubmVjdGlvbi52MS5EaXNj", + "b25uZWN0Tm90aWZpY2F0aW9uGhkuYmdzLnByb3RvY29sLk5PX1JFU1BPTlNF", + "EjwKCUtlZXBBbGl2ZRIULmJncy5wcm90b2NvbC5Ob0RhdGEaGS5iZ3MucHJv", + "dG9jb2wuTk9fUkVTUE9OU0USUAoHRW5jcnlwdBIqLmJncy5wcm90b2NvbC5j", + "b25uZWN0aW9uLnYxLkVuY3J5cHRSZXF1ZXN0GhQuYmdzLnByb3RvY29sLk5v", + "RGF0YSIDiAIBEl0KEVJlcXVlc3REaXNjb25uZWN0Ei0uYmdzLnByb3RvY29s", + "LmNvbm5lY3Rpb24udjEuRGlzY29ubmVjdFJlcXVlc3QaGS5iZ3MucHJvdG9j", + "b2wuTk9fUkVTUE9OU0VCPQobYm5ldC5wcm90b2NvbC5jb25uZWN0aW9uLnYx", + "QhZDb25uZWN0aW9uU2VydmljZVByb3RvSAKAAQCIAQFiBnByb3RvMw==")); + descriptor = pbr::FileDescriptor.FromGeneratedCode(descriptorData, + new pbr::FileDescriptor[] { Bgs.Protocol.ContentHandleTypesReflection.Descriptor, Bgs.Protocol.RpcTypesReflection.Descriptor, }, + new pbr::GeneratedClrTypeInfo(null, new pbr::GeneratedClrTypeInfo[] { + new pbr::GeneratedClrTypeInfo(typeof(Bgs.Protocol.Connection.V1.ConnectRequest), Bgs.Protocol.Connection.V1.ConnectRequest.Parser, new[]{ "ClientId", "BindRequest", "UseBindlessRpc" }, null, null, null), + new pbr::GeneratedClrTypeInfo(typeof(Bgs.Protocol.Connection.V1.ConnectionMeteringContentHandles), Bgs.Protocol.Connection.V1.ConnectionMeteringContentHandles.Parser, new[]{ "ContentHandle" }, null, null, null), + new pbr::GeneratedClrTypeInfo(typeof(Bgs.Protocol.Connection.V1.ConnectResponse), Bgs.Protocol.Connection.V1.ConnectResponse.Parser, new[]{ "ServerId", "ClientId", "BindResult", "BindResponse", "ContentHandleArray", "ServerTime", "UseBindlessRpc", "BinaryContentHandleArray" }, null, null, null), + new pbr::GeneratedClrTypeInfo(typeof(Bgs.Protocol.Connection.V1.BoundService), Bgs.Protocol.Connection.V1.BoundService.Parser, new[]{ "Hash", "Id" }, null, null, null), + new pbr::GeneratedClrTypeInfo(typeof(Bgs.Protocol.Connection.V1.BindRequest), Bgs.Protocol.Connection.V1.BindRequest.Parser, new[]{ "DeprecatedImportedServiceHash", "DeprecatedExportedService", "ExportedService", "ImportedService" }, null, null, null), + new pbr::GeneratedClrTypeInfo(typeof(Bgs.Protocol.Connection.V1.BindResponse), Bgs.Protocol.Connection.V1.BindResponse.Parser, new[]{ "ImportedServiceId" }, null, null, null), + new pbr::GeneratedClrTypeInfo(typeof(Bgs.Protocol.Connection.V1.EchoRequest), Bgs.Protocol.Connection.V1.EchoRequest.Parser, new[]{ "Time", "NetworkOnly", "Payload" }, null, null, null), + new pbr::GeneratedClrTypeInfo(typeof(Bgs.Protocol.Connection.V1.EchoResponse), Bgs.Protocol.Connection.V1.EchoResponse.Parser, new[]{ "Time", "Payload" }, null, null, null), + new pbr::GeneratedClrTypeInfo(typeof(Bgs.Protocol.Connection.V1.DisconnectRequest), Bgs.Protocol.Connection.V1.DisconnectRequest.Parser, new[]{ "ErrorCode" }, null, null, null), + new pbr::GeneratedClrTypeInfo(typeof(Bgs.Protocol.Connection.V1.DisconnectNotification), Bgs.Protocol.Connection.V1.DisconnectNotification.Parser, new[]{ "ErrorCode", "Reason" }, null, null, null), + new pbr::GeneratedClrTypeInfo(typeof(Bgs.Protocol.Connection.V1.EncryptRequest), Bgs.Protocol.Connection.V1.EncryptRequest.Parser, null, null, null, null) + })); + } + #endregion + + } + #region Messages + [System.Diagnostics.DebuggerNonUserCodeAttribute()] + public sealed partial class ConnectRequest : pb::IMessage + { + private static readonly pb::MessageParser _parser = new pb::MessageParser(() => new ConnectRequest()); + public static pb::MessageParser Parser { get { return _parser; } } + + public static pbr::MessageDescriptor Descriptor + { + get { return Bgs.Protocol.Connection.V1.ConnectionServiceReflection.Descriptor.MessageTypes[0]; } + } + + pbr::MessageDescriptor pb::IMessage.Descriptor + { + get { return Descriptor; } + } + + public ConnectRequest() + { + OnConstruction(); + } + + partial void OnConstruction(); + + public ConnectRequest(ConnectRequest other) : this() + { + ClientId = other.clientId_ != null ? other.ClientId.Clone() : null; + BindRequest = other.bindRequest_ != null ? other.BindRequest.Clone() : null; + useBindlessRpc_ = other.useBindlessRpc_; + } + + public ConnectRequest Clone() + { + return new ConnectRequest(this); + } + + /// Field number for the "client_id" field. + public const int ClientIdFieldNumber = 1; + private Bgs.Protocol.ProcessId clientId_; + public Bgs.Protocol.ProcessId ClientId + { + get { return clientId_; } + set + { + clientId_ = value; + } + } + + /// Field number for the "bind_request" field. + public const int BindRequestFieldNumber = 2; + private Bgs.Protocol.Connection.V1.BindRequest bindRequest_; + public Bgs.Protocol.Connection.V1.BindRequest BindRequest + { + get { return bindRequest_; } + set + { + bindRequest_ = value; + } + } + + /// Field number for the "use_bindless_rpc" field. + public const int UseBindlessRpcFieldNumber = 3; + private bool useBindlessRpc_; + public bool UseBindlessRpc + { + get { return useBindlessRpc_; } + set + { + useBindlessRpc_ = value; + } + } + + public override bool Equals(object other) + { + return Equals(other as ConnectRequest); + } + + public bool Equals(ConnectRequest other) + { + if (ReferenceEquals(other, null)) + { + return false; + } + if (ReferenceEquals(other, this)) + { + return true; + } + if (!object.Equals(ClientId, other.ClientId)) return false; + if (!object.Equals(BindRequest, other.BindRequest)) return false; + if (UseBindlessRpc != other.UseBindlessRpc) return false; + return true; + } + + public override int GetHashCode() + { + int hash = 1; + if (clientId_ != null) hash ^= ClientId.GetHashCode(); + if (bindRequest_ != null) hash ^= BindRequest.GetHashCode(); + if (UseBindlessRpc != false) hash ^= UseBindlessRpc.GetHashCode(); + return hash; + } + + public override string ToString() + { + return pb::JsonFormatter.ToDiagnosticString(this); + } + + public void WriteTo(pb::CodedOutputStream output) + { + if (clientId_ != null) + { + output.WriteRawTag(10); + output.WriteMessage(ClientId); + } + if (bindRequest_ != null) + { + output.WriteRawTag(18); + output.WriteMessage(BindRequest); + } + if (UseBindlessRpc != false) + { + output.WriteRawTag(24); + output.WriteBool(UseBindlessRpc); + } + } + + public int CalculateSize() + { + int size = 0; + if (clientId_ != null) + { + size += 1 + pb::CodedOutputStream.ComputeMessageSize(ClientId); + } + if (bindRequest_ != null) + { + size += 1 + pb::CodedOutputStream.ComputeMessageSize(BindRequest); + } + if (UseBindlessRpc != false) + { + size += 1 + 1; + } + return size; + } + + public void MergeFrom(ConnectRequest other) + { + if (other == null) + { + return; + } + if (other.clientId_ != null) + { + if (clientId_ == null) + { + clientId_ = new Bgs.Protocol.ProcessId(); + } + ClientId.MergeFrom(other.ClientId); + } + if (other.bindRequest_ != null) + { + if (bindRequest_ == null) + { + bindRequest_ = new Bgs.Protocol.Connection.V1.BindRequest(); + } + BindRequest.MergeFrom(other.BindRequest); + } + if (other.UseBindlessRpc != false) + { + UseBindlessRpc = other.UseBindlessRpc; + } + } + + public void MergeFrom(pb::CodedInputStream input) + { + uint tag; + while ((tag = input.ReadTag()) != 0) + { + switch (tag) + { + default: + input.SkipLastField(); + break; + case 10: + { + if (clientId_ == null) + { + clientId_ = new Bgs.Protocol.ProcessId(); + } + input.ReadMessage(clientId_); + break; + } + case 18: + { + if (bindRequest_ == null) + { + bindRequest_ = new Bgs.Protocol.Connection.V1.BindRequest(); + } + input.ReadMessage(bindRequest_); + break; + } + case 24: + { + UseBindlessRpc = input.ReadBool(); + break; + } + } + } + } + + } + + [System.Diagnostics.DebuggerNonUserCodeAttribute()] + public sealed partial class ConnectionMeteringContentHandles : pb::IMessage + { + private static readonly pb::MessageParser _parser = new pb::MessageParser(() => new ConnectionMeteringContentHandles()); + public static pb::MessageParser Parser { get { return _parser; } } + + public static pbr::MessageDescriptor Descriptor + { + get { return Bgs.Protocol.Connection.V1.ConnectionServiceReflection.Descriptor.MessageTypes[1]; } + } + + pbr::MessageDescriptor pb::IMessage.Descriptor + { + get { return Descriptor; } + } + + public ConnectionMeteringContentHandles() + { + OnConstruction(); + } + + partial void OnConstruction(); + + public ConnectionMeteringContentHandles(ConnectionMeteringContentHandles other) : this() + { + contentHandle_ = other.contentHandle_.Clone(); + } + + public ConnectionMeteringContentHandles Clone() + { + return new ConnectionMeteringContentHandles(this); + } + + /// Field number for the "content_handle" field. + public const int ContentHandleFieldNumber = 1; + private static readonly pb::FieldCodec _repeated_contentHandle_codec + = pb::FieldCodec.ForMessage(10, Bgs.Protocol.ContentHandle.Parser); + private readonly pbc::RepeatedField contentHandle_ = new pbc::RepeatedField(); + public pbc::RepeatedField ContentHandle + { + get { return contentHandle_; } + } + + public override bool Equals(object other) + { + return Equals(other as ConnectionMeteringContentHandles); + } + + public bool Equals(ConnectionMeteringContentHandles other) + { + if (ReferenceEquals(other, null)) + { + return false; + } + if (ReferenceEquals(other, this)) + { + return true; + } + if (!contentHandle_.Equals(other.contentHandle_)) return false; + return true; + } + + public override int GetHashCode() + { + int hash = 1; + hash ^= contentHandle_.GetHashCode(); + return hash; + } + + public override string ToString() + { + return pb::JsonFormatter.ToDiagnosticString(this); + } + + public void WriteTo(pb::CodedOutputStream output) + { + contentHandle_.WriteTo(output, _repeated_contentHandle_codec); + } + + public int CalculateSize() + { + int size = 0; + size += contentHandle_.CalculateSize(_repeated_contentHandle_codec); + return size; + } + + public void MergeFrom(ConnectionMeteringContentHandles other) + { + if (other == null) + { + return; + } + contentHandle_.Add(other.contentHandle_); + } + + public void MergeFrom(pb::CodedInputStream input) + { + uint tag; + while ((tag = input.ReadTag()) != 0) + { + switch (tag) + { + default: + input.SkipLastField(); + break; + case 10: + { + contentHandle_.AddEntriesFrom(input, _repeated_contentHandle_codec); + break; + } + } + } + } + + } + + [System.Diagnostics.DebuggerNonUserCodeAttribute()] + public sealed partial class ConnectResponse : pb::IMessage + { + private static readonly pb::MessageParser _parser = new pb::MessageParser(() => new ConnectResponse()); + public static pb::MessageParser Parser { get { return _parser; } } + + public static pbr::MessageDescriptor Descriptor + { + get { return Bgs.Protocol.Connection.V1.ConnectionServiceReflection.Descriptor.MessageTypes[2]; } + } + + pbr::MessageDescriptor pb::IMessage.Descriptor + { + get { return Descriptor; } + } + + public ConnectResponse() + { + OnConstruction(); + } + + partial void OnConstruction(); + + public ConnectResponse(ConnectResponse other) : this() + { + ServerId = other.serverId_ != null ? other.ServerId.Clone() : null; + ClientId = other.clientId_ != null ? other.ClientId.Clone() : null; + bindResult_ = other.bindResult_; + BindResponse = other.bindResponse_ != null ? other.BindResponse.Clone() : null; + ContentHandleArray = other.contentHandleArray_ != null ? other.ContentHandleArray.Clone() : null; + serverTime_ = other.serverTime_; + useBindlessRpc_ = other.useBindlessRpc_; + BinaryContentHandleArray = other.binaryContentHandleArray_ != null ? other.BinaryContentHandleArray.Clone() : null; + } + + public ConnectResponse Clone() + { + return new ConnectResponse(this); + } + + /// Field number for the "server_id" field. + public const int ServerIdFieldNumber = 1; + private Bgs.Protocol.ProcessId serverId_; + public Bgs.Protocol.ProcessId ServerId + { + get { return serverId_; } + set + { + serverId_ = value; + } + } + + /// Field number for the "client_id" field. + public const int ClientIdFieldNumber = 2; + private Bgs.Protocol.ProcessId clientId_; + public Bgs.Protocol.ProcessId ClientId + { + get { return clientId_; } + set + { + clientId_ = value; + } + } + + /// Field number for the "bind_result" field. + public const int BindResultFieldNumber = 3; + private uint bindResult_; + public uint BindResult + { + get { return bindResult_; } + set + { + bindResult_ = value; + } + } + + /// Field number for the "bind_response" field. + public const int BindResponseFieldNumber = 4; + private Bgs.Protocol.Connection.V1.BindResponse bindResponse_; + public Bgs.Protocol.Connection.V1.BindResponse BindResponse + { + get { return bindResponse_; } + set + { + bindResponse_ = value; + } + } + + /// Field number for the "content_handle_array" field. + public const int ContentHandleArrayFieldNumber = 5; + private Bgs.Protocol.Connection.V1.ConnectionMeteringContentHandles contentHandleArray_; + public Bgs.Protocol.Connection.V1.ConnectionMeteringContentHandles ContentHandleArray + { + get { return contentHandleArray_; } + set + { + contentHandleArray_ = value; + } + } + + /// Field number for the "server_time" field. + public const int ServerTimeFieldNumber = 6; + private ulong serverTime_; + public ulong ServerTime + { + get { return serverTime_; } + set + { + serverTime_ = value; + } + } + + /// Field number for the "use_bindless_rpc" field. + public const int UseBindlessRpcFieldNumber = 7; + private bool useBindlessRpc_; + public bool UseBindlessRpc + { + get { return useBindlessRpc_; } + set + { + useBindlessRpc_ = value; + } + } + + /// Field number for the "binary_content_handle_array" field. + public const int BinaryContentHandleArrayFieldNumber = 8; + private Bgs.Protocol.Connection.V1.ConnectionMeteringContentHandles binaryContentHandleArray_; + public Bgs.Protocol.Connection.V1.ConnectionMeteringContentHandles BinaryContentHandleArray + { + get { return binaryContentHandleArray_; } + set + { + binaryContentHandleArray_ = value; + } + } + + public override bool Equals(object other) + { + return Equals(other as ConnectResponse); + } + + public bool Equals(ConnectResponse other) + { + if (ReferenceEquals(other, null)) + { + return false; + } + if (ReferenceEquals(other, this)) + { + return true; + } + if (!object.Equals(ServerId, other.ServerId)) return false; + if (!object.Equals(ClientId, other.ClientId)) return false; + if (BindResult != other.BindResult) return false; + if (!object.Equals(BindResponse, other.BindResponse)) return false; + if (!object.Equals(ContentHandleArray, other.ContentHandleArray)) return false; + if (ServerTime != other.ServerTime) return false; + if (UseBindlessRpc != other.UseBindlessRpc) return false; + if (!object.Equals(BinaryContentHandleArray, other.BinaryContentHandleArray)) return false; + return true; + } + + public override int GetHashCode() + { + int hash = 1; + if (serverId_ != null) hash ^= ServerId.GetHashCode(); + if (clientId_ != null) hash ^= ClientId.GetHashCode(); + if (BindResult != 0) hash ^= BindResult.GetHashCode(); + if (bindResponse_ != null) hash ^= BindResponse.GetHashCode(); + if (contentHandleArray_ != null) hash ^= ContentHandleArray.GetHashCode(); + if (ServerTime != 0UL) hash ^= ServerTime.GetHashCode(); + if (UseBindlessRpc != false) hash ^= UseBindlessRpc.GetHashCode(); + if (binaryContentHandleArray_ != null) hash ^= BinaryContentHandleArray.GetHashCode(); + return hash; + } + + public override string ToString() + { + return pb::JsonFormatter.ToDiagnosticString(this); + } + + public void WriteTo(pb::CodedOutputStream output) + { + if (serverId_ != null) + { + output.WriteRawTag(10); + output.WriteMessage(ServerId); + } + if (clientId_ != null) + { + output.WriteRawTag(18); + output.WriteMessage(ClientId); + } + if (BindResult != 0) + { + output.WriteRawTag(24); + output.WriteUInt32(BindResult); + } + if (bindResponse_ != null) + { + output.WriteRawTag(34); + output.WriteMessage(BindResponse); + } + if (contentHandleArray_ != null) + { + output.WriteRawTag(42); + output.WriteMessage(ContentHandleArray); + } + if (ServerTime != 0UL) + { + output.WriteRawTag(48); + output.WriteUInt64(ServerTime); + } + if (UseBindlessRpc != false) + { + output.WriteRawTag(56); + output.WriteBool(UseBindlessRpc); + } + if (binaryContentHandleArray_ != null) + { + output.WriteRawTag(66); + output.WriteMessage(BinaryContentHandleArray); + } + } + + public int CalculateSize() + { + int size = 0; + if (serverId_ != null) + { + size += 1 + pb::CodedOutputStream.ComputeMessageSize(ServerId); + } + if (clientId_ != null) + { + size += 1 + pb::CodedOutputStream.ComputeMessageSize(ClientId); + } + if (BindResult != 0) + { + size += 1 + pb::CodedOutputStream.ComputeUInt32Size(BindResult); + } + if (bindResponse_ != null) + { + size += 1 + pb::CodedOutputStream.ComputeMessageSize(BindResponse); + } + if (contentHandleArray_ != null) + { + size += 1 + pb::CodedOutputStream.ComputeMessageSize(ContentHandleArray); + } + if (ServerTime != 0UL) + { + size += 1 + pb::CodedOutputStream.ComputeUInt64Size(ServerTime); + } + if (UseBindlessRpc != false) + { + size += 1 + 1; + } + if (binaryContentHandleArray_ != null) + { + size += 1 + pb::CodedOutputStream.ComputeMessageSize(BinaryContentHandleArray); + } + return size; + } + + public void MergeFrom(ConnectResponse other) + { + if (other == null) + { + return; + } + if (other.serverId_ != null) + { + if (serverId_ == null) + { + serverId_ = new Bgs.Protocol.ProcessId(); + } + ServerId.MergeFrom(other.ServerId); + } + if (other.clientId_ != null) + { + if (clientId_ == null) + { + clientId_ = new Bgs.Protocol.ProcessId(); + } + ClientId.MergeFrom(other.ClientId); + } + if (other.BindResult != 0) + { + BindResult = other.BindResult; + } + if (other.bindResponse_ != null) + { + if (bindResponse_ == null) + { + bindResponse_ = new Bgs.Protocol.Connection.V1.BindResponse(); + } + BindResponse.MergeFrom(other.BindResponse); + } + if (other.contentHandleArray_ != null) + { + if (contentHandleArray_ == null) + { + contentHandleArray_ = new Bgs.Protocol.Connection.V1.ConnectionMeteringContentHandles(); + } + ContentHandleArray.MergeFrom(other.ContentHandleArray); + } + if (other.ServerTime != 0UL) + { + ServerTime = other.ServerTime; + } + if (other.UseBindlessRpc != false) + { + UseBindlessRpc = other.UseBindlessRpc; + } + if (other.binaryContentHandleArray_ != null) + { + if (binaryContentHandleArray_ == null) + { + binaryContentHandleArray_ = new Bgs.Protocol.Connection.V1.ConnectionMeteringContentHandles(); + } + BinaryContentHandleArray.MergeFrom(other.BinaryContentHandleArray); + } + } + + public void MergeFrom(pb::CodedInputStream input) + { + uint tag; + while ((tag = input.ReadTag()) != 0) + { + switch (tag) + { + default: + input.SkipLastField(); + break; + case 10: + { + if (serverId_ == null) + { + serverId_ = new Bgs.Protocol.ProcessId(); + } + input.ReadMessage(serverId_); + break; + } + case 18: + { + if (clientId_ == null) + { + clientId_ = new Bgs.Protocol.ProcessId(); + } + input.ReadMessage(clientId_); + break; + } + case 24: + { + BindResult = input.ReadUInt32(); + break; + } + case 34: + { + if (bindResponse_ == null) + { + bindResponse_ = new Bgs.Protocol.Connection.V1.BindResponse(); + } + input.ReadMessage(bindResponse_); + break; + } + case 42: + { + if (contentHandleArray_ == null) + { + contentHandleArray_ = new Bgs.Protocol.Connection.V1.ConnectionMeteringContentHandles(); + } + input.ReadMessage(contentHandleArray_); + break; + } + case 48: + { + ServerTime = input.ReadUInt64(); + break; + } + case 56: + { + UseBindlessRpc = input.ReadBool(); + break; + } + case 66: + { + if (binaryContentHandleArray_ == null) + { + binaryContentHandleArray_ = new Bgs.Protocol.Connection.V1.ConnectionMeteringContentHandles(); + } + input.ReadMessage(binaryContentHandleArray_); + break; + } + } + } + } + + } + + [System.Diagnostics.DebuggerNonUserCodeAttribute()] + public sealed partial class BoundService : pb::IMessage + { + private static readonly pb::MessageParser _parser = new pb::MessageParser(() => new BoundService()); + public static pb::MessageParser Parser { get { return _parser; } } + + public static pbr::MessageDescriptor Descriptor + { + get { return Bgs.Protocol.Connection.V1.ConnectionServiceReflection.Descriptor.MessageTypes[3]; } + } + + pbr::MessageDescriptor pb::IMessage.Descriptor + { + get { return Descriptor; } + } + + public BoundService() + { + OnConstruction(); + } + + partial void OnConstruction(); + + public BoundService(BoundService other) : this() + { + hash_ = other.hash_; + id_ = other.id_; + } + + public BoundService Clone() + { + return new BoundService(this); + } + + /// Field number for the "hash" field. + public const int HashFieldNumber = 1; + private uint hash_; + public uint Hash + { + get { return hash_; } + set + { + hash_ = value; + } + } + + /// Field number for the "id" field. + public const int IdFieldNumber = 2; + private uint id_; + public uint Id + { + get { return id_; } + set + { + id_ = value; + } + } + + public override bool Equals(object other) + { + return Equals(other as BoundService); + } + + public bool Equals(BoundService other) + { + if (ReferenceEquals(other, null)) + { + return false; + } + if (ReferenceEquals(other, this)) + { + return true; + } + if (Hash != other.Hash) return false; + if (Id != other.Id) return false; + return true; + } + + public override int GetHashCode() + { + int hash = 1; + if (Hash != 0) hash ^= Hash.GetHashCode(); + if (Id != 0) hash ^= Id.GetHashCode(); + return hash; + } + + public override string ToString() + { + return pb::JsonFormatter.ToDiagnosticString(this); + } + + public void WriteTo(pb::CodedOutputStream output) + { + if (Hash != 0) + { + output.WriteRawTag(13); + output.WriteFixed32(Hash); + } + if (Id != 0) + { + output.WriteRawTag(16); + output.WriteUInt32(Id); + } + } + + public int CalculateSize() + { + int size = 0; + if (Hash != 0) + { + size += 1 + 4; + } + if (Id != 0) + { + size += 1 + pb::CodedOutputStream.ComputeUInt32Size(Id); + } + return size; + } + + public void MergeFrom(BoundService other) + { + if (other == null) + { + return; + } + if (other.Hash != 0) + { + Hash = other.Hash; + } + if (other.Id != 0) + { + Id = other.Id; + } + } + + public void MergeFrom(pb::CodedInputStream input) + { + uint tag; + while ((tag = input.ReadTag()) != 0) + { + switch (tag) + { + default: + input.SkipLastField(); + break; + case 13: + { + Hash = input.ReadFixed32(); + break; + } + case 16: + { + Id = input.ReadUInt32(); + break; + } + } + } + } + + } + + [System.Diagnostics.DebuggerNonUserCodeAttribute()] + public sealed partial class BindRequest : pb::IMessage + { + private static readonly pb::MessageParser _parser = new pb::MessageParser(() => new BindRequest()); + public static pb::MessageParser Parser { get { return _parser; } } + + public static pbr::MessageDescriptor Descriptor + { + get { return Bgs.Protocol.Connection.V1.ConnectionServiceReflection.Descriptor.MessageTypes[4]; } + } + + pbr::MessageDescriptor pb::IMessage.Descriptor + { + get { return Descriptor; } + } + + public BindRequest() + { + OnConstruction(); + } + + partial void OnConstruction(); + + public BindRequest(BindRequest other) : this() + { + deprecatedImportedServiceHash_ = other.deprecatedImportedServiceHash_.Clone(); + deprecatedExportedService_ = other.deprecatedExportedService_.Clone(); + exportedService_ = other.exportedService_.Clone(); + importedService_ = other.importedService_.Clone(); + } + + public BindRequest Clone() + { + return new BindRequest(this); + } + + /// Field number for the "deprecated_imported_service_hash" field. + public const int DeprecatedImportedServiceHashFieldNumber = 1; + private static readonly pb::FieldCodec _repeated_deprecatedImportedServiceHash_codec + = pb::FieldCodec.ForFixed32(10); + private readonly pbc::RepeatedField deprecatedImportedServiceHash_ = new pbc::RepeatedField(); + [System.ObsoleteAttribute()] + public pbc::RepeatedField DeprecatedImportedServiceHash + { + get { return deprecatedImportedServiceHash_; } + } + + /// Field number for the "deprecated_exported_service" field. + public const int DeprecatedExportedServiceFieldNumber = 2; + private static readonly pb::FieldCodec _repeated_deprecatedExportedService_codec + = pb::FieldCodec.ForMessage(18, Bgs.Protocol.Connection.V1.BoundService.Parser); + private readonly pbc::RepeatedField deprecatedExportedService_ = new pbc::RepeatedField(); + [System.ObsoleteAttribute()] + public pbc::RepeatedField DeprecatedExportedService + { + get { return deprecatedExportedService_; } + } + + /// Field number for the "exported_service" field. + public const int ExportedServiceFieldNumber = 3; + private static readonly pb::FieldCodec _repeated_exportedService_codec + = pb::FieldCodec.ForMessage(26, Bgs.Protocol.Connection.V1.BoundService.Parser); + private readonly pbc::RepeatedField exportedService_ = new pbc::RepeatedField(); + public pbc::RepeatedField ExportedService + { + get { return exportedService_; } + } + + /// Field number for the "imported_service" field. + public const int ImportedServiceFieldNumber = 4; + private static readonly pb::FieldCodec _repeated_importedService_codec + = pb::FieldCodec.ForMessage(34, Bgs.Protocol.Connection.V1.BoundService.Parser); + private readonly pbc::RepeatedField importedService_ = new pbc::RepeatedField(); + public pbc::RepeatedField ImportedService + { + get { return importedService_; } + } + + public override bool Equals(object other) + { + return Equals(other as BindRequest); + } + + public bool Equals(BindRequest other) + { + if (ReferenceEquals(other, null)) + { + return false; + } + if (ReferenceEquals(other, this)) + { + return true; + } + if (!deprecatedImportedServiceHash_.Equals(other.deprecatedImportedServiceHash_)) return false; + if (!deprecatedExportedService_.Equals(other.deprecatedExportedService_)) return false; + if (!exportedService_.Equals(other.exportedService_)) return false; + if (!importedService_.Equals(other.importedService_)) return false; + return true; + } + + public override int GetHashCode() + { + int hash = 1; + hash ^= deprecatedImportedServiceHash_.GetHashCode(); + hash ^= deprecatedExportedService_.GetHashCode(); + hash ^= exportedService_.GetHashCode(); + hash ^= importedService_.GetHashCode(); + return hash; + } + + public override string ToString() + { + return pb::JsonFormatter.ToDiagnosticString(this); + } + + public void WriteTo(pb::CodedOutputStream output) + { + deprecatedImportedServiceHash_.WriteTo(output, _repeated_deprecatedImportedServiceHash_codec); + deprecatedExportedService_.WriteTo(output, _repeated_deprecatedExportedService_codec); + exportedService_.WriteTo(output, _repeated_exportedService_codec); + importedService_.WriteTo(output, _repeated_importedService_codec); + } + + public int CalculateSize() + { + int size = 0; + size += deprecatedImportedServiceHash_.CalculateSize(_repeated_deprecatedImportedServiceHash_codec); + size += deprecatedExportedService_.CalculateSize(_repeated_deprecatedExportedService_codec); + size += exportedService_.CalculateSize(_repeated_exportedService_codec); + size += importedService_.CalculateSize(_repeated_importedService_codec); + return size; + } + + public void MergeFrom(BindRequest other) + { + if (other == null) + { + return; + } + deprecatedImportedServiceHash_.Add(other.deprecatedImportedServiceHash_); + deprecatedExportedService_.Add(other.deprecatedExportedService_); + exportedService_.Add(other.exportedService_); + importedService_.Add(other.importedService_); + } + + public void MergeFrom(pb::CodedInputStream input) + { + uint tag; + while ((tag = input.ReadTag()) != 0) + { + switch (tag) + { + default: + input.SkipLastField(); + break; + case 10: + case 13: + { + deprecatedImportedServiceHash_.AddEntriesFrom(input, _repeated_deprecatedImportedServiceHash_codec); + break; + } + case 18: + { + deprecatedExportedService_.AddEntriesFrom(input, _repeated_deprecatedExportedService_codec); + break; + } + case 26: + { + exportedService_.AddEntriesFrom(input, _repeated_exportedService_codec); + break; + } + case 34: + { + importedService_.AddEntriesFrom(input, _repeated_importedService_codec); + break; + } + } + } + } + + } + + [System.Diagnostics.DebuggerNonUserCodeAttribute()] + public sealed partial class BindResponse : pb::IMessage + { + private static readonly pb::MessageParser _parser = new pb::MessageParser(() => new BindResponse()); + public static pb::MessageParser Parser { get { return _parser; } } + + public static pbr::MessageDescriptor Descriptor + { + get { return Bgs.Protocol.Connection.V1.ConnectionServiceReflection.Descriptor.MessageTypes[5]; } + } + + pbr::MessageDescriptor pb::IMessage.Descriptor + { + get { return Descriptor; } + } + + public BindResponse() + { + OnConstruction(); + } + + partial void OnConstruction(); + + public BindResponse(BindResponse other) : this() + { + importedServiceId_ = other.importedServiceId_.Clone(); + } + + public BindResponse Clone() + { + return new BindResponse(this); + } + + /// Field number for the "imported_service_id" field. + public const int ImportedServiceIdFieldNumber = 1; + private static readonly pb::FieldCodec _repeated_importedServiceId_codec + = pb::FieldCodec.ForUInt32(10); + private readonly pbc::RepeatedField importedServiceId_ = new pbc::RepeatedField(); + [System.ObsoleteAttribute()] + public pbc::RepeatedField ImportedServiceId + { + get { return importedServiceId_; } + } + + public override bool Equals(object other) + { + return Equals(other as BindResponse); + } + + public bool Equals(BindResponse other) + { + if (ReferenceEquals(other, null)) + { + return false; + } + if (ReferenceEquals(other, this)) + { + return true; + } + if (!importedServiceId_.Equals(other.importedServiceId_)) return false; + return true; + } + + public override int GetHashCode() + { + int hash = 1; + hash ^= importedServiceId_.GetHashCode(); + return hash; + } + + public override string ToString() + { + return pb::JsonFormatter.ToDiagnosticString(this); + } + + public void WriteTo(pb::CodedOutputStream output) + { + importedServiceId_.WriteTo(output, _repeated_importedServiceId_codec); + } + + public int CalculateSize() + { + int size = 0; + size += importedServiceId_.CalculateSize(_repeated_importedServiceId_codec); + return size; + } + + public void MergeFrom(BindResponse other) + { + if (other == null) + { + return; + } + importedServiceId_.Add(other.importedServiceId_); + } + + public void MergeFrom(pb::CodedInputStream input) + { + uint tag; + while ((tag = input.ReadTag()) != 0) + { + switch (tag) + { + default: + input.SkipLastField(); + break; + case 10: + case 8: + { + importedServiceId_.AddEntriesFrom(input, _repeated_importedServiceId_codec); + break; + } + } + } + } + + } + + [System.Diagnostics.DebuggerNonUserCodeAttribute()] + public sealed partial class EchoRequest : pb::IMessage + { + private static readonly pb::MessageParser _parser = new pb::MessageParser(() => new EchoRequest()); + public static pb::MessageParser Parser { get { return _parser; } } + + public static pbr::MessageDescriptor Descriptor + { + get { return Bgs.Protocol.Connection.V1.ConnectionServiceReflection.Descriptor.MessageTypes[6]; } + } + + pbr::MessageDescriptor pb::IMessage.Descriptor + { + get { return Descriptor; } + } + + public EchoRequest() + { + OnConstruction(); + } + + partial void OnConstruction(); + + public EchoRequest(EchoRequest other) : this() + { + time_ = other.time_; + networkOnly_ = other.networkOnly_; + payload_ = other.payload_; + } + + public EchoRequest Clone() + { + return new EchoRequest(this); + } + + /// Field number for the "time" field. + public const int TimeFieldNumber = 1; + private ulong time_; + public ulong Time + { + get { return time_; } + set + { + time_ = value; + } + } + + /// Field number for the "network_only" field. + public const int NetworkOnlyFieldNumber = 2; + private bool networkOnly_; + public bool NetworkOnly + { + get { return networkOnly_; } + set + { + networkOnly_ = value; + } + } + + /// Field number for the "payload" field. + public const int PayloadFieldNumber = 3; + private pb::ByteString payload_ = pb::ByteString.Empty; + public pb::ByteString Payload + { + get { return payload_; } + set + { + payload_ = pb::ProtoPreconditions.CheckNotNull(value, "value"); + } + } + + public override bool Equals(object other) + { + return Equals(other as EchoRequest); + } + + public bool Equals(EchoRequest other) + { + if (ReferenceEquals(other, null)) + { + return false; + } + if (ReferenceEquals(other, this)) + { + return true; + } + if (Time != other.Time) return false; + if (NetworkOnly != other.NetworkOnly) return false; + if (Payload != other.Payload) return false; + return true; + } + + public override int GetHashCode() + { + int hash = 1; + if (Time != 0UL) hash ^= Time.GetHashCode(); + if (NetworkOnly != false) hash ^= NetworkOnly.GetHashCode(); + if (Payload.Length != 0) hash ^= Payload.GetHashCode(); + return hash; + } + + public override string ToString() + { + return pb::JsonFormatter.ToDiagnosticString(this); + } + + public void WriteTo(pb::CodedOutputStream output) + { + if (Time != 0UL) + { + output.WriteRawTag(9); + output.WriteFixed64(Time); + } + if (NetworkOnly != false) + { + output.WriteRawTag(16); + output.WriteBool(NetworkOnly); + } + if (Payload.Length != 0) + { + output.WriteRawTag(26); + output.WriteBytes(Payload); + } + } + + public int CalculateSize() + { + int size = 0; + if (Time != 0UL) + { + size += 1 + 8; + } + if (NetworkOnly != false) + { + size += 1 + 1; + } + if (Payload.Length != 0) + { + size += 1 + pb::CodedOutputStream.ComputeBytesSize(Payload); + } + return size; + } + + public void MergeFrom(EchoRequest other) + { + if (other == null) + { + return; + } + if (other.Time != 0UL) + { + Time = other.Time; + } + if (other.NetworkOnly != false) + { + NetworkOnly = other.NetworkOnly; + } + if (other.Payload.Length != 0) + { + Payload = other.Payload; + } + } + + public void MergeFrom(pb::CodedInputStream input) + { + uint tag; + while ((tag = input.ReadTag()) != 0) + { + switch (tag) + { + default: + input.SkipLastField(); + break; + case 9: + { + Time = input.ReadFixed64(); + break; + } + case 16: + { + NetworkOnly = input.ReadBool(); + break; + } + case 26: + { + Payload = input.ReadBytes(); + break; + } + } + } + } + + } + + [System.Diagnostics.DebuggerNonUserCodeAttribute()] + public sealed partial class EchoResponse : pb::IMessage + { + private static readonly pb::MessageParser _parser = new pb::MessageParser(() => new EchoResponse()); + public static pb::MessageParser Parser { get { return _parser; } } + + public static pbr::MessageDescriptor Descriptor + { + get { return Bgs.Protocol.Connection.V1.ConnectionServiceReflection.Descriptor.MessageTypes[7]; } + } + + pbr::MessageDescriptor pb::IMessage.Descriptor + { + get { return Descriptor; } + } + + public EchoResponse() + { + OnConstruction(); + } + + partial void OnConstruction(); + + public EchoResponse(EchoResponse other) : this() + { + time_ = other.time_; + payload_ = other.payload_; + } + + public EchoResponse Clone() + { + return new EchoResponse(this); + } + + /// Field number for the "time" field. + public const int TimeFieldNumber = 1; + private ulong time_; + public ulong Time + { + get { return time_; } + set + { + time_ = value; + } + } + + /// Field number for the "payload" field. + public const int PayloadFieldNumber = 2; + private pb::ByteString payload_ = pb::ByteString.Empty; + public pb::ByteString Payload + { + get { return payload_; } + set + { + payload_ = pb::ProtoPreconditions.CheckNotNull(value, "value"); + } + } + + public override bool Equals(object other) + { + return Equals(other as EchoResponse); + } + + public bool Equals(EchoResponse other) + { + if (ReferenceEquals(other, null)) + { + return false; + } + if (ReferenceEquals(other, this)) + { + return true; + } + if (Time != other.Time) return false; + if (Payload != other.Payload) return false; + return true; + } + + public override int GetHashCode() + { + int hash = 1; + if (Time != 0UL) hash ^= Time.GetHashCode(); + if (Payload.Length != 0) hash ^= Payload.GetHashCode(); + return hash; + } + + public override string ToString() + { + return pb::JsonFormatter.ToDiagnosticString(this); + } + + public void WriteTo(pb::CodedOutputStream output) + { + if (Time != 0UL) + { + output.WriteRawTag(9); + output.WriteFixed64(Time); + } + if (Payload.Length != 0) + { + output.WriteRawTag(18); + output.WriteBytes(Payload); + } + } + + public int CalculateSize() + { + int size = 0; + if (Time != 0UL) + { + size += 1 + 8; + } + if (Payload.Length != 0) + { + size += 1 + pb::CodedOutputStream.ComputeBytesSize(Payload); + } + return size; + } + + public void MergeFrom(EchoResponse other) + { + if (other == null) + { + return; + } + if (other.Time != 0UL) + { + Time = other.Time; + } + if (other.Payload.Length != 0) + { + Payload = other.Payload; + } + } + + public void MergeFrom(pb::CodedInputStream input) + { + uint tag; + while ((tag = input.ReadTag()) != 0) + { + switch (tag) + { + default: + input.SkipLastField(); + break; + case 9: + { + Time = input.ReadFixed64(); + break; + } + case 18: + { + Payload = input.ReadBytes(); + break; + } + } + } + } + + } + + [System.Diagnostics.DebuggerNonUserCodeAttribute()] + public sealed partial class DisconnectRequest : pb::IMessage + { + private static readonly pb::MessageParser _parser = new pb::MessageParser(() => new DisconnectRequest()); + public static pb::MessageParser Parser { get { return _parser; } } + + public static pbr::MessageDescriptor Descriptor + { + get { return Bgs.Protocol.Connection.V1.ConnectionServiceReflection.Descriptor.MessageTypes[8]; } + } + + pbr::MessageDescriptor pb::IMessage.Descriptor + { + get { return Descriptor; } + } + + public DisconnectRequest() + { + OnConstruction(); + } + + partial void OnConstruction(); + + public DisconnectRequest(DisconnectRequest other) : this() + { + errorCode_ = other.errorCode_; + } + + public DisconnectRequest Clone() + { + return new DisconnectRequest(this); + } + + /// Field number for the "error_code" field. + public const int ErrorCodeFieldNumber = 1; + private uint errorCode_; + public uint ErrorCode + { + get { return errorCode_; } + set + { + errorCode_ = value; + } + } + + public override bool Equals(object other) + { + return Equals(other as DisconnectRequest); + } + + public bool Equals(DisconnectRequest other) + { + if (ReferenceEquals(other, null)) + { + return false; + } + if (ReferenceEquals(other, this)) + { + return true; + } + if (ErrorCode != other.ErrorCode) return false; + return true; + } + + public override int GetHashCode() + { + int hash = 1; + if (ErrorCode != 0) hash ^= ErrorCode.GetHashCode(); + return hash; + } + + public override string ToString() + { + return pb::JsonFormatter.ToDiagnosticString(this); + } + + public void WriteTo(pb::CodedOutputStream output) + { + if (ErrorCode != 0) + { + output.WriteRawTag(8); + output.WriteUInt32(ErrorCode); + } + } + + public int CalculateSize() + { + int size = 0; + if (ErrorCode != 0) + { + size += 1 + pb::CodedOutputStream.ComputeUInt32Size(ErrorCode); + } + return size; + } + + public void MergeFrom(DisconnectRequest other) + { + if (other == null) + { + return; + } + if (other.ErrorCode != 0) + { + ErrorCode = other.ErrorCode; + } + } + + public void MergeFrom(pb::CodedInputStream input) + { + uint tag; + while ((tag = input.ReadTag()) != 0) + { + switch (tag) + { + default: + input.SkipLastField(); + break; + case 8: + { + ErrorCode = input.ReadUInt32(); + break; + } + } + } + } + + } + + [System.Diagnostics.DebuggerNonUserCodeAttribute()] + public sealed partial class DisconnectNotification : pb::IMessage + { + private static readonly pb::MessageParser _parser = new pb::MessageParser(() => new DisconnectNotification()); + public static pb::MessageParser Parser { get { return _parser; } } + + public static pbr::MessageDescriptor Descriptor + { + get { return Bgs.Protocol.Connection.V1.ConnectionServiceReflection.Descriptor.MessageTypes[9]; } + } + + pbr::MessageDescriptor pb::IMessage.Descriptor + { + get { return Descriptor; } + } + + public DisconnectNotification() + { + OnConstruction(); + } + + partial void OnConstruction(); + + public DisconnectNotification(DisconnectNotification other) : this() + { + errorCode_ = other.errorCode_; + reason_ = other.reason_; + } + + public DisconnectNotification Clone() + { + return new DisconnectNotification(this); + } + + /// Field number for the "error_code" field. + public const int ErrorCodeFieldNumber = 1; + private uint errorCode_; + public uint ErrorCode + { + get { return errorCode_; } + set + { + errorCode_ = value; + } + } + + /// Field number for the "reason" field. + public const int ReasonFieldNumber = 2; + private string reason_ = ""; + public string Reason + { + get { return reason_; } + set + { + reason_ = pb::ProtoPreconditions.CheckNotNull(value, "value"); + } + } + + public override bool Equals(object other) + { + return Equals(other as DisconnectNotification); + } + + public bool Equals(DisconnectNotification other) + { + if (ReferenceEquals(other, null)) + { + return false; + } + if (ReferenceEquals(other, this)) + { + return true; + } + if (ErrorCode != other.ErrorCode) return false; + if (Reason != other.Reason) return false; + return true; + } + + public override int GetHashCode() + { + int hash = 1; + if (ErrorCode != 0) hash ^= ErrorCode.GetHashCode(); + if (Reason.Length != 0) hash ^= Reason.GetHashCode(); + return hash; + } + + public override string ToString() + { + return pb::JsonFormatter.ToDiagnosticString(this); + } + + public void WriteTo(pb::CodedOutputStream output) + { + if (ErrorCode != 0) + { + output.WriteRawTag(8); + output.WriteUInt32(ErrorCode); + } + if (Reason.Length != 0) + { + output.WriteRawTag(18); + output.WriteString(Reason); + } + } + + public int CalculateSize() + { + int size = 0; + if (ErrorCode != 0) + { + size += 1 + pb::CodedOutputStream.ComputeUInt32Size(ErrorCode); + } + if (Reason.Length != 0) + { + size += 1 + pb::CodedOutputStream.ComputeStringSize(Reason); + } + return size; + } + + public void MergeFrom(DisconnectNotification other) + { + if (other == null) + { + return; + } + if (other.ErrorCode != 0) + { + ErrorCode = other.ErrorCode; + } + if (other.Reason.Length != 0) + { + Reason = other.Reason; + } + } + + public void MergeFrom(pb::CodedInputStream input) + { + uint tag; + while ((tag = input.ReadTag()) != 0) + { + switch (tag) + { + default: + input.SkipLastField(); + break; + case 8: + { + ErrorCode = input.ReadUInt32(); + break; + } + case 18: + { + Reason = input.ReadString(); + break; + } + } + } + } + + } + + [System.Diagnostics.DebuggerNonUserCodeAttribute()] + public sealed partial class EncryptRequest : pb::IMessage + { + private static readonly pb::MessageParser _parser = new pb::MessageParser(() => new EncryptRequest()); + public static pb::MessageParser Parser { get { return _parser; } } + + public static pbr::MessageDescriptor Descriptor + { + get { return Bgs.Protocol.Connection.V1.ConnectionServiceReflection.Descriptor.MessageTypes[10]; } + } + + pbr::MessageDescriptor pb::IMessage.Descriptor + { + get { return Descriptor; } + } + + public EncryptRequest() + { + OnConstruction(); + } + + partial void OnConstruction(); + + public EncryptRequest(EncryptRequest other) : this() + { + } + + public EncryptRequest Clone() + { + return new EncryptRequest(this); + } + + public override bool Equals(object other) + { + return Equals(other as EncryptRequest); + } + + public bool Equals(EncryptRequest other) + { + if (ReferenceEquals(other, null)) + { + return false; + } + if (ReferenceEquals(other, this)) + { + return true; + } + return true; + } + + public override int GetHashCode() + { + int hash = 1; + return hash; + } + + public override string ToString() + { + return pb::JsonFormatter.ToDiagnosticString(this); + } + + public void WriteTo(pb::CodedOutputStream output) + { + } + + public int CalculateSize() + { + int size = 0; + return size; + } + + public void MergeFrom(EncryptRequest other) + { + if (other == null) + { + return; + } + } + + public void MergeFrom(pb::CodedInputStream input) + { + uint tag; + while ((tag = input.ReadTag()) != 0) + { + switch (tag) + { + default: + input.SkipLastField(); + break; + } + } + } + + } + + #endregion +} + +#endregion Designer generated code diff --git a/Framework/Proto/ContentHandleTypes.cs b/Framework/Proto/ContentHandleTypes.cs new file mode 100644 index 000000000..edae52e1e --- /dev/null +++ b/Framework/Proto/ContentHandleTypes.cs @@ -0,0 +1,228 @@ +// Generated by the protocol buffer compiler. DO NOT EDIT! +// source: bgs/low/pb/client/content_handle_types.proto +#pragma warning disable 1591, 0612, 3021 +#region Designer generated code + +using pb = Google.Protobuf; +using pbr = Google.Protobuf.Reflection; +namespace Bgs.Protocol +{ + + /// Holder for reflection information generated from bgs/low/pb/client/content_handle_types.proto + [System.Diagnostics.DebuggerNonUserCodeAttribute()] + public static partial class ContentHandleTypesReflection { + + #region Descriptor + /// File descriptor for bgs/low/pb/client/content_handle_types.proto + public static pbr::FileDescriptor Descriptor { + get { return descriptor; } + } + private static pbr::FileDescriptor descriptor; + + static ContentHandleTypesReflection() { + byte[] descriptorData = System.Convert.FromBase64String( + string.Concat( + "CixiZ3MvbG93L3BiL2NsaWVudC9jb250ZW50X2hhbmRsZV90eXBlcy5wcm90", + "bxIMYmdzLnByb3RvY29sIk8KDUNvbnRlbnRIYW5kbGUSDgoGcmVnaW9uGAEg", + "ASgHEg0KBXVzYWdlGAIgASgHEgwKBGhhc2gYAyABKAwSEQoJcHJvdG9fdXJs", + "GAQgASgJQiUKDWJuZXQucHJvdG9jb2xCEkNvbnRlbnRIYW5kbGVQcm90b0gC", + "YgZwcm90bzM=")); + descriptor = pbr::FileDescriptor.FromGeneratedCode(descriptorData, + new pbr::FileDescriptor[] { }, + new pbr::GeneratedClrTypeInfo(null, new pbr::GeneratedClrTypeInfo[] { + new pbr::GeneratedClrTypeInfo(typeof(Bgs.Protocol.ContentHandle), Bgs.Protocol.ContentHandle.Parser, new[]{ "Region", "Usage", "Hash", "ProtoUrl" }, null, null, null) + })); + } + #endregion + + } + #region Messages + [System.Diagnostics.DebuggerNonUserCodeAttribute()] + public sealed partial class ContentHandle : pb::IMessage { + private static readonly pb::MessageParser _parser = new pb::MessageParser(() => new ContentHandle()); + public static pb::MessageParser Parser { get { return _parser; } } + + public static pbr::MessageDescriptor Descriptor { + get { return Bgs.Protocol.ContentHandleTypesReflection.Descriptor.MessageTypes[0]; } + } + + pbr::MessageDescriptor pb::IMessage.Descriptor { + get { return Descriptor; } + } + + public ContentHandle() { + OnConstruction(); + } + + partial void OnConstruction(); + + public ContentHandle(ContentHandle other) : this() { + region_ = other.region_; + usage_ = other.usage_; + hash_ = other.hash_; + protoUrl_ = other.protoUrl_; + } + + public ContentHandle Clone() { + return new ContentHandle(this); + } + + /// Field number for the "region" field. + public const int RegionFieldNumber = 1; + private uint region_; + public uint Region { + get { return region_; } + set { + region_ = value; + } + } + + /// Field number for the "usage" field. + public const int UsageFieldNumber = 2; + private uint usage_; + public uint Usage { + get { return usage_; } + set { + usage_ = value; + } + } + + /// Field number for the "hash" field. + public const int HashFieldNumber = 3; + private pb::ByteString hash_ = pb::ByteString.Empty; + public pb::ByteString Hash { + get { return hash_; } + set { + hash_ = pb::ProtoPreconditions.CheckNotNull(value, "value"); + } + } + + /// Field number for the "proto_url" field. + public const int ProtoUrlFieldNumber = 4; + private string protoUrl_ = ""; + public string ProtoUrl { + get { return protoUrl_; } + set { + protoUrl_ = pb::ProtoPreconditions.CheckNotNull(value, "value"); + } + } + + public override bool Equals(object other) { + return Equals(other as ContentHandle); + } + + public bool Equals(ContentHandle other) { + if (ReferenceEquals(other, null)) { + return false; + } + if (ReferenceEquals(other, this)) { + return true; + } + if (Region != other.Region) return false; + if (Usage != other.Usage) return false; + if (Hash != other.Hash) return false; + if (ProtoUrl != other.ProtoUrl) return false; + return true; + } + + public override int GetHashCode() { + int hash = 1; + if (Region != 0) hash ^= Region.GetHashCode(); + if (Usage != 0) hash ^= Usage.GetHashCode(); + if (Hash.Length != 0) hash ^= Hash.GetHashCode(); + if (ProtoUrl.Length != 0) hash ^= ProtoUrl.GetHashCode(); + return hash; + } + + public override string ToString() { + return pb::JsonFormatter.ToDiagnosticString(this); + } + + public void WriteTo(pb::CodedOutputStream output) { + if (Region != 0) { + output.WriteRawTag(13); + output.WriteFixed32(Region); + } + if (Usage != 0) { + output.WriteRawTag(21); + output.WriteFixed32(Usage); + } + if (Hash.Length != 0) { + output.WriteRawTag(26); + output.WriteBytes(Hash); + } + if (ProtoUrl.Length != 0) { + output.WriteRawTag(34); + output.WriteString(ProtoUrl); + } + } + + public int CalculateSize() { + int size = 0; + if (Region != 0) { + size += 1 + 4; + } + if (Usage != 0) { + size += 1 + 4; + } + if (Hash.Length != 0) { + size += 1 + pb::CodedOutputStream.ComputeBytesSize(Hash); + } + if (ProtoUrl.Length != 0) { + size += 1 + pb::CodedOutputStream.ComputeStringSize(ProtoUrl); + } + return size; + } + + public void MergeFrom(ContentHandle other) { + if (other == null) { + return; + } + if (other.Region != 0) { + Region = other.Region; + } + if (other.Usage != 0) { + Usage = other.Usage; + } + if (other.Hash.Length != 0) { + Hash = other.Hash; + } + if (other.ProtoUrl.Length != 0) { + ProtoUrl = other.ProtoUrl; + } + } + + public void MergeFrom(pb::CodedInputStream input) { + uint tag; + while ((tag = input.ReadTag()) != 0) { + switch(tag) { + default: + input.SkipLastField(); + break; + case 13: { + Region = input.ReadFixed32(); + break; + } + case 21: { + Usage = input.ReadFixed32(); + break; + } + case 26: { + Hash = input.ReadBytes(); + break; + } + case 34: { + ProtoUrl = input.ReadString(); + break; + } + } + } + } + + } + + #endregion + +} + +#endregion Designer generated code diff --git a/Framework/Proto/EntityTypes.cs b/Framework/Proto/EntityTypes.cs new file mode 100644 index 000000000..b543fb8bf --- /dev/null +++ b/Framework/Proto/EntityTypes.cs @@ -0,0 +1,568 @@ +// Generated by the protocol buffer compiler. DO NOT EDIT! +// source: bgs/low/pb/client/entity_types.proto +#pragma warning disable 1591, 0612, 3021 +#region Designer generated code + +using pb = Google.Protobuf; +using pbr = Google.Protobuf.Reflection; +namespace Bgs.Protocol +{ + + /// Holder for reflection information generated from bgs/low/pb/client/entity_types.proto + [System.Diagnostics.DebuggerNonUserCodeAttribute()] + public static partial class EntityTypesReflection { + + #region Descriptor + /// File descriptor for bgs/low/pb/client/entity_types.proto + public static pbr::FileDescriptor Descriptor { + get { return descriptor; } + } + private static pbr::FileDescriptor descriptor; + + static EntityTypesReflection() { + byte[] descriptorData = System.Convert.FromBase64String( + string.Concat( + "CiRiZ3MvbG93L3BiL2NsaWVudC9lbnRpdHlfdHlwZXMucHJvdG8SDGJncy5w", + "cm90b2NvbBo3YmdzL2xvdy9wYi9jbGllbnQvZ2xvYmFsX2V4dGVuc2lvbnMv", + "ZmllbGRfb3B0aW9ucy5wcm90byIlCghFbnRpdHlJZBIMCgRoaWdoGAEgASgG", + "EgsKA2xvdxgCIAEoBiJnCghJZGVudGl0eRIqCgphY2NvdW50X2lkGAEgASgL", + "MhYuYmdzLnByb3RvY29sLkVudGl0eUlkEi8KD2dhbWVfYWNjb3VudF9pZBgC", + "IAEoCzIWLmJncy5wcm90b2NvbC5FbnRpdHlJZCKjAQoLQWNjb3VudEluZm8S", + "FAoMYWNjb3VudF9wYWlkGAEgASgIEhIKCmNvdW50cnlfaWQYAiABKAcSEgoK", + "YmF0dGxlX3RhZxgDIAEoCRIVCg1tYW51YWxfcmV2aWV3GAQgASgIEigKCGlk", + "ZW50aXR5GAUgASgLMhYuYmdzLnByb3RvY29sLklkZW50aXR5EhUKDWFjY291", + "bnRfbXV0ZWQYBiABKAhCHgoNYm5ldC5wcm90b2NvbEILRW50aXR5UHJvdG9I", + "AmIGcHJvdG8z")); + descriptor = pbr::FileDescriptor.FromGeneratedCode(descriptorData, + new pbr::FileDescriptor[] { Bgs.Protocol.FieldOptionsReflection.Descriptor, }, + new pbr::GeneratedClrTypeInfo(null, new pbr::GeneratedClrTypeInfo[] { + new pbr::GeneratedClrTypeInfo(typeof(Bgs.Protocol.EntityId), Bgs.Protocol.EntityId.Parser, new[]{ "High", "Low" }, null, null, null), + new pbr::GeneratedClrTypeInfo(typeof(Bgs.Protocol.Identity), Bgs.Protocol.Identity.Parser, new[]{ "AccountId", "GameAccountId" }, null, null, null), + new pbr::GeneratedClrTypeInfo(typeof(Bgs.Protocol.AccountInfo), Bgs.Protocol.AccountInfo.Parser, new[]{ "AccountPaid", "CountryId", "BattleTag", "ManualReview", "Identity", "AccountMuted" }, null, null, null) + })); + } + #endregion + + } + #region Messages + [System.Diagnostics.DebuggerNonUserCodeAttribute()] + public sealed partial class EntityId : pb::IMessage { + private static readonly pb::MessageParser _parser = new pb::MessageParser(() => new EntityId()); + public static pb::MessageParser Parser { get { return _parser; } } + + public static pbr::MessageDescriptor Descriptor { + get { return Bgs.Protocol.EntityTypesReflection.Descriptor.MessageTypes[0]; } + } + + pbr::MessageDescriptor pb::IMessage.Descriptor { + get { return Descriptor; } + } + + public EntityId() { + OnConstruction(); + } + + partial void OnConstruction(); + + public EntityId(EntityId other) : this() { + high_ = other.high_; + low_ = other.low_; + } + + public EntityId Clone() { + return new EntityId(this); + } + + /// Field number for the "high" field. + public const int HighFieldNumber = 1; + private ulong high_; + public ulong High { + get { return high_; } + set { + high_ = value; + } + } + + /// Field number for the "low" field. + public const int LowFieldNumber = 2; + private ulong low_; + public ulong Low { + get { return low_; } + set { + low_ = value; + } + } + + public override bool Equals(object other) { + return Equals(other as EntityId); + } + + public bool Equals(EntityId other) { + if (ReferenceEquals(other, null)) { + return false; + } + if (ReferenceEquals(other, this)) { + return true; + } + if (High != other.High) return false; + if (Low != other.Low) return false; + return true; + } + + public override int GetHashCode() { + int hash = 1; + if (High != 0UL) hash ^= High.GetHashCode(); + if (Low != 0UL) hash ^= Low.GetHashCode(); + return hash; + } + + public override string ToString() { + return pb::JsonFormatter.ToDiagnosticString(this); + } + + public void WriteTo(pb::CodedOutputStream output) { + if (High != 0UL) { + output.WriteRawTag(9); + output.WriteFixed64(High); + } + if (Low != 0UL) { + output.WriteRawTag(17); + output.WriteFixed64(Low); + } + } + + public int CalculateSize() { + int size = 0; + if (High != 0UL) { + size += 1 + 8; + } + if (Low != 0UL) { + size += 1 + 8; + } + return size; + } + + public void MergeFrom(EntityId other) { + if (other == null) { + return; + } + if (other.High != 0UL) { + High = other.High; + } + if (other.Low != 0UL) { + Low = other.Low; + } + } + + public void MergeFrom(pb::CodedInputStream input) { + uint tag; + while ((tag = input.ReadTag()) != 0) { + switch(tag) { + default: + input.SkipLastField(); + break; + case 9: { + High = input.ReadFixed64(); + break; + } + case 17: { + Low = input.ReadFixed64(); + break; + } + } + } + } + + } + + [System.Diagnostics.DebuggerNonUserCodeAttribute()] + public sealed partial class Identity : pb::IMessage { + private static readonly pb::MessageParser _parser = new pb::MessageParser(() => new Identity()); + public static pb::MessageParser Parser { get { return _parser; } } + + public static pbr::MessageDescriptor Descriptor { + get { return Bgs.Protocol.EntityTypesReflection.Descriptor.MessageTypes[1]; } + } + + pbr::MessageDescriptor pb::IMessage.Descriptor { + get { return Descriptor; } + } + + public Identity() { + OnConstruction(); + } + + partial void OnConstruction(); + + public Identity(Identity other) : this() { + AccountId = other.accountId_ != null ? other.AccountId.Clone() : null; + GameAccountId = other.gameAccountId_ != null ? other.GameAccountId.Clone() : null; + } + + public Identity Clone() { + return new Identity(this); + } + + /// Field number for the "account_id" field. + public const int AccountIdFieldNumber = 1; + private Bgs.Protocol.EntityId accountId_; + public Bgs.Protocol.EntityId AccountId { + get { return accountId_; } + set { + accountId_ = value; + } + } + + /// Field number for the "game_account_id" field. + public const int GameAccountIdFieldNumber = 2; + private Bgs.Protocol.EntityId gameAccountId_; + public Bgs.Protocol.EntityId GameAccountId { + get { return gameAccountId_; } + set { + gameAccountId_ = value; + } + } + + public override bool Equals(object other) { + return Equals(other as Identity); + } + + public bool Equals(Identity other) { + if (ReferenceEquals(other, null)) { + return false; + } + if (ReferenceEquals(other, this)) { + return true; + } + if (!object.Equals(AccountId, other.AccountId)) return false; + if (!object.Equals(GameAccountId, other.GameAccountId)) return false; + return true; + } + + public override int GetHashCode() { + int hash = 1; + if (accountId_ != null) hash ^= AccountId.GetHashCode(); + if (gameAccountId_ != null) hash ^= GameAccountId.GetHashCode(); + return hash; + } + + public override string ToString() { + return pb::JsonFormatter.ToDiagnosticString(this); + } + + public void WriteTo(pb::CodedOutputStream output) { + if (accountId_ != null) { + output.WriteRawTag(10); + output.WriteMessage(AccountId); + } + if (gameAccountId_ != null) { + output.WriteRawTag(18); + output.WriteMessage(GameAccountId); + } + } + + public int CalculateSize() { + int size = 0; + if (accountId_ != null) { + size += 1 + pb::CodedOutputStream.ComputeMessageSize(AccountId); + } + if (gameAccountId_ != null) { + size += 1 + pb::CodedOutputStream.ComputeMessageSize(GameAccountId); + } + return size; + } + + public void MergeFrom(Identity other) { + if (other == null) { + return; + } + if (other.accountId_ != null) { + if (accountId_ == null) { + accountId_ = new Bgs.Protocol.EntityId(); + } + AccountId.MergeFrom(other.AccountId); + } + if (other.gameAccountId_ != null) { + if (gameAccountId_ == null) { + gameAccountId_ = new Bgs.Protocol.EntityId(); + } + GameAccountId.MergeFrom(other.GameAccountId); + } + } + + public void MergeFrom(pb::CodedInputStream input) { + uint tag; + while ((tag = input.ReadTag()) != 0) { + switch(tag) { + default: + input.SkipLastField(); + break; + case 10: { + if (accountId_ == null) { + accountId_ = new Bgs.Protocol.EntityId(); + } + input.ReadMessage(accountId_); + break; + } + case 18: { + if (gameAccountId_ == null) { + gameAccountId_ = new Bgs.Protocol.EntityId(); + } + input.ReadMessage(gameAccountId_); + break; + } + } + } + } + + } + + [System.Diagnostics.DebuggerNonUserCodeAttribute()] + public sealed partial class AccountInfo : pb::IMessage { + private static readonly pb::MessageParser _parser = new pb::MessageParser(() => new AccountInfo()); + public static pb::MessageParser Parser { get { return _parser; } } + + public static pbr::MessageDescriptor Descriptor { + get { return Bgs.Protocol.EntityTypesReflection.Descriptor.MessageTypes[2]; } + } + + pbr::MessageDescriptor pb::IMessage.Descriptor { + get { return Descriptor; } + } + + public AccountInfo() { + OnConstruction(); + } + + partial void OnConstruction(); + + public AccountInfo(AccountInfo other) : this() { + accountPaid_ = other.accountPaid_; + countryId_ = other.countryId_; + battleTag_ = other.battleTag_; + manualReview_ = other.manualReview_; + Identity = other.identity_ != null ? other.Identity.Clone() : null; + accountMuted_ = other.accountMuted_; + } + + public AccountInfo Clone() { + return new AccountInfo(this); + } + + /// Field number for the "account_paid" field. + public const int AccountPaidFieldNumber = 1; + private bool accountPaid_; + public bool AccountPaid { + get { return accountPaid_; } + set { + accountPaid_ = value; + } + } + + /// Field number for the "country_id" field. + public const int CountryIdFieldNumber = 2; + private uint countryId_; + public uint CountryId { + get { return countryId_; } + set { + countryId_ = value; + } + } + + /// Field number for the "battle_tag" field. + public const int BattleTagFieldNumber = 3; + private string battleTag_ = ""; + public string BattleTag { + get { return battleTag_; } + set { + battleTag_ = pb::ProtoPreconditions.CheckNotNull(value, "value"); + } + } + + /// Field number for the "manual_review" field. + public const int ManualReviewFieldNumber = 4; + private bool manualReview_; + public bool ManualReview { + get { return manualReview_; } + set { + manualReview_ = value; + } + } + + /// Field number for the "identity" field. + public const int IdentityFieldNumber = 5; + private Bgs.Protocol.Identity identity_; + public Bgs.Protocol.Identity Identity { + get { return identity_; } + set { + identity_ = value; + } + } + + /// Field number for the "account_muted" field. + public const int AccountMutedFieldNumber = 6; + private bool accountMuted_; + public bool AccountMuted { + get { return accountMuted_; } + set { + accountMuted_ = value; + } + } + + public override bool Equals(object other) { + return Equals(other as AccountInfo); + } + + public bool Equals(AccountInfo other) { + if (ReferenceEquals(other, null)) { + return false; + } + if (ReferenceEquals(other, this)) { + return true; + } + if (AccountPaid != other.AccountPaid) return false; + if (CountryId != other.CountryId) return false; + if (BattleTag != other.BattleTag) return false; + if (ManualReview != other.ManualReview) return false; + if (!object.Equals(Identity, other.Identity)) return false; + if (AccountMuted != other.AccountMuted) return false; + return true; + } + + public override int GetHashCode() { + int hash = 1; + if (AccountPaid != false) hash ^= AccountPaid.GetHashCode(); + if (CountryId != 0) hash ^= CountryId.GetHashCode(); + if (BattleTag.Length != 0) hash ^= BattleTag.GetHashCode(); + if (ManualReview != false) hash ^= ManualReview.GetHashCode(); + if (identity_ != null) hash ^= Identity.GetHashCode(); + if (AccountMuted != false) hash ^= AccountMuted.GetHashCode(); + return hash; + } + + public override string ToString() { + return pb::JsonFormatter.ToDiagnosticString(this); + } + + public void WriteTo(pb::CodedOutputStream output) { + if (AccountPaid != false) { + output.WriteRawTag(8); + output.WriteBool(AccountPaid); + } + if (CountryId != 0) { + output.WriteRawTag(21); + output.WriteFixed32(CountryId); + } + if (BattleTag.Length != 0) { + output.WriteRawTag(26); + output.WriteString(BattleTag); + } + if (ManualReview != false) { + output.WriteRawTag(32); + output.WriteBool(ManualReview); + } + if (identity_ != null) { + output.WriteRawTag(42); + output.WriteMessage(Identity); + } + if (AccountMuted != false) { + output.WriteRawTag(48); + output.WriteBool(AccountMuted); + } + } + + public int CalculateSize() { + int size = 0; + if (AccountPaid != false) { + size += 1 + 1; + } + if (CountryId != 0) { + size += 1 + 4; + } + if (BattleTag.Length != 0) { + size += 1 + pb::CodedOutputStream.ComputeStringSize(BattleTag); + } + if (ManualReview != false) { + size += 1 + 1; + } + if (identity_ != null) { + size += 1 + pb::CodedOutputStream.ComputeMessageSize(Identity); + } + if (AccountMuted != false) { + size += 1 + 1; + } + return size; + } + + public void MergeFrom(AccountInfo other) { + if (other == null) { + return; + } + if (other.AccountPaid != false) { + AccountPaid = other.AccountPaid; + } + if (other.CountryId != 0) { + CountryId = other.CountryId; + } + if (other.BattleTag.Length != 0) { + BattleTag = other.BattleTag; + } + if (other.ManualReview != false) { + ManualReview = other.ManualReview; + } + if (other.identity_ != null) { + if (identity_ == null) { + identity_ = new Bgs.Protocol.Identity(); + } + Identity.MergeFrom(other.Identity); + } + if (other.AccountMuted != false) { + AccountMuted = other.AccountMuted; + } + } + + public void MergeFrom(pb::CodedInputStream input) { + uint tag; + while ((tag = input.ReadTag()) != 0) { + switch(tag) { + default: + input.SkipLastField(); + break; + case 8: { + AccountPaid = input.ReadBool(); + break; + } + case 21: { + CountryId = input.ReadFixed32(); + break; + } + case 26: { + BattleTag = input.ReadString(); + break; + } + case 32: { + ManualReview = input.ReadBool(); + break; + } + case 42: { + if (identity_ == null) { + identity_ = new Bgs.Protocol.Identity(); + } + input.ReadMessage(identity_); + break; + } + case 48: { + AccountMuted = input.ReadBool(); + break; + } + } + } + } + + } + + #endregion + +} + +#endregion Designer generated code diff --git a/Framework/Proto/FriendsService.cs b/Framework/Proto/FriendsService.cs new file mode 100644 index 000000000..38ba1c8cb --- /dev/null +++ b/Framework/Proto/FriendsService.cs @@ -0,0 +1,2154 @@ +// Generated by the protocol buffer compiler. DO NOT EDIT! +// source: bgs/low/pb/client/friends_service.proto +#pragma warning disable 1591, 0612, 3021 +#region Designer generated code + +using pb = Google.Protobuf; +using pbc = Google.Protobuf.Collections; +using pbr = Google.Protobuf.Reflection; +namespace Bgs.Protocol.Friends.V1 +{ + + /// Holder for reflection information generated from bgs/low/pb/client/friends_service.proto + [System.Diagnostics.DebuggerNonUserCodeAttribute()] + public static partial class FriendsServiceReflection { + + #region Descriptor + /// File descriptor for bgs/low/pb/client/friends_service.proto + public static pbr::FileDescriptor Descriptor { + get { return descriptor; } + } + private static pbr::FileDescriptor descriptor; + + static FriendsServiceReflection() { + byte[] descriptorData = System.Convert.FromBase64String( + string.Concat( + "CidiZ3MvbG93L3BiL2NsaWVudC9mcmllbmRzX3NlcnZpY2UucHJvdG8SF2Jn", + "cy5wcm90b2NvbC5mcmllbmRzLnYxGidiZ3MvbG93L3BiL2NsaWVudC9hdHRy", + "aWJ1dGVfdHlwZXMucHJvdG8aJGJncy9sb3cvcGIvY2xpZW50L2VudGl0eV90", + "eXBlcy5wcm90bxolYmdzL2xvdy9wYi9jbGllbnQvZnJpZW5kc190eXBlcy5w", + "cm90bxooYmdzL2xvdy9wYi9jbGllbnQvaW52aXRhdGlvbl90eXBlcy5wcm90", + "bxoiYmdzL2xvdy9wYi9jbGllbnQvcm9sZV90eXBlcy5wcm90bxohYmdzL2xv", + "dy9wYi9jbGllbnQvcnBjX3R5cGVzLnByb3RvIk8KEFN1YnNjcmliZVJlcXVl", + "c3QSKAoIYWdlbnRfaWQYASABKAsyFi5iZ3MucHJvdG9jb2wuRW50aXR5SWQS", + "EQoJb2JqZWN0X2lkGAIgASgEIqgCChFTdWJzY3JpYmVSZXNwb25zZRITCgtt", + "YXhfZnJpZW5kcxgBIAEoDRIgChhtYXhfcmVjZWl2ZWRfaW52aXRhdGlvbnMY", + "AiABKA0SHAoUbWF4X3NlbnRfaW52aXRhdGlvbnMYAyABKA0SIAoEcm9sZRgE", + "IAMoCzISLmJncy5wcm90b2NvbC5Sb2xlEjAKB2ZyaWVuZHMYBSADKAsyHy5i", + "Z3MucHJvdG9jb2wuZnJpZW5kcy52MS5GcmllbmQSMgoQc2VudF9pbnZpdGF0", + "aW9ucxgGIAMoCzIYLmJncy5wcm90b2NvbC5JbnZpdGF0aW9uEjYKFHJlY2Vp", + "dmVkX2ludml0YXRpb25zGAcgAygLMhguYmdzLnByb3RvY29sLkludml0YXRp", + "b24iUQoSVW5zdWJzY3JpYmVSZXF1ZXN0EigKCGFnZW50X2lkGAEgASgLMhYu", + "YmdzLnByb3RvY29sLkVudGl0eUlkEhEKCW9iamVjdF9pZBgCIAEoBCJrChRH", + "ZW5lcmljRnJpZW5kUmVxdWVzdBIoCghhZ2VudF9pZBgBIAEoCzIWLmJncy5w", + "cm90b2NvbC5FbnRpdHlJZBIpCgl0YXJnZXRfaWQYAiABKAsyFi5iZ3MucHJv", + "dG9jb2wuRW50aXR5SWQiTwoVR2VuZXJpY0ZyaWVuZFJlc3BvbnNlEjYKDXRh", + "cmdldF9mcmllbmQYASABKAsyHy5iZ3MucHJvdG9jb2wuZnJpZW5kcy52MS5G", + "cmllbmQidgoRQXNzaWduUm9sZVJlcXVlc3QSKAoIYWdlbnRfaWQYASABKAsy", + "Fi5iZ3MucHJvdG9jb2wuRW50aXR5SWQSKQoJdGFyZ2V0X2lkGAIgASgLMhYu", + "YmdzLnByb3RvY29sLkVudGl0eUlkEgwKBHJvbGUYAyADKAUiewoSVmlld0Zy", + "aWVuZHNSZXF1ZXN0EigKCGFnZW50X2lkGAEgASgLMhYuYmdzLnByb3RvY29s", + "LkVudGl0eUlkEikKCXRhcmdldF9pZBgCIAEoCzIWLmJncy5wcm90b2NvbC5F", + "bnRpdHlJZBIQCgRyb2xlGAMgAygNQgIQASJHChNWaWV3RnJpZW5kc1Jlc3Bv", + "bnNlEjAKB2ZyaWVuZHMYASADKAsyHy5iZ3MucHJvdG9jb2wuZnJpZW5kcy52", + "MS5GcmllbmQitQEKGFVwZGF0ZUZyaWVuZFN0YXRlUmVxdWVzdBIoCghhZ2Vu", + "dF9pZBgBIAEoCzIWLmJncy5wcm90b2NvbC5FbnRpdHlJZBIpCgl0YXJnZXRf", + "aWQYAiABKAsyFi5iZ3MucHJvdG9jb2wuRW50aXR5SWQSKgoJYXR0cmlidXRl", + "GAMgAygLMhcuYmdzLnByb3RvY29sLkF0dHJpYnV0ZRIYChBhdHRyaWJ1dGVz", + "X2Vwb2NoGAQgASgEIskBChJGcmllbmROb3RpZmljYXRpb24SLwoGdGFyZ2V0", + "GAEgASgLMh8uYmdzLnByb3RvY29sLmZyaWVuZHMudjEuRnJpZW5kEi8KD2dh", + "bWVfYWNjb3VudF9pZBgCIAEoCzIWLmJncy5wcm90b2NvbC5FbnRpdHlJZBIl", + "CgRwZWVyGAQgASgLMhcuYmdzLnByb3RvY29sLlByb2Nlc3NJZBIqCgphY2Nv", + "dW50X2lkGAUgASgLMhYuYmdzLnByb3RvY29sLkVudGl0eUlkItwBCh1VcGRh", + "dGVGcmllbmRTdGF0ZU5vdGlmaWNhdGlvbhI3Cg5jaGFuZ2VkX2ZyaWVuZBgB", + "IAEoCzIfLmJncy5wcm90b2NvbC5mcmllbmRzLnYxLkZyaWVuZBIvCg9nYW1l", + "X2FjY291bnRfaWQYAiABKAsyFi5iZ3MucHJvdG9jb2wuRW50aXR5SWQSJQoE", + "cGVlchgEIAEoCzIXLmJncy5wcm90b2NvbC5Qcm9jZXNzSWQSKgoKYWNjb3Vu", + "dF9pZBgFIAEoCzIWLmJncy5wcm90b2NvbC5FbnRpdHlJZCLaAQoWSW52aXRh", + "dGlvbk5vdGlmaWNhdGlvbhIsCgppbnZpdGF0aW9uGAEgASgLMhguYmdzLnBy", + "b3RvY29sLkludml0YXRpb24SLwoPZ2FtZV9hY2NvdW50X2lkGAIgASgLMhYu", + "YmdzLnByb3RvY29sLkVudGl0eUlkEg4KBnJlYXNvbhgDIAEoDRIlCgRwZWVy", + "GAQgASgLMhcuYmdzLnByb3RvY29sLlByb2Nlc3NJZBIqCgphY2NvdW50X2lk", + "GAUgASgLMhYuYmdzLnByb3RvY29sLkVudGl0eUlkMsUICg5GcmllbmRzU2Vy", + "dmljZRJiCglTdWJzY3JpYmUSKS5iZ3MucHJvdG9jb2wuZnJpZW5kcy52MS5T", + "dWJzY3JpYmVSZXF1ZXN0GiouYmdzLnByb3RvY29sLmZyaWVuZHMudjEuU3Vi", + "c2NyaWJlUmVzcG9uc2USSwoOU2VuZEludml0YXRpb24SIy5iZ3MucHJvdG9j", + "b2wuU2VuZEludml0YXRpb25SZXF1ZXN0GhQuYmdzLnByb3RvY29sLk5vRGF0", + "YRJQChBBY2NlcHRJbnZpdGF0aW9uEiYuYmdzLnByb3RvY29sLkdlbmVyaWNJ", + "bnZpdGF0aW9uUmVxdWVzdBoULmJncy5wcm90b2NvbC5Ob0RhdGESVQoQUmV2", + "b2tlSW52aXRhdGlvbhImLmJncy5wcm90b2NvbC5HZW5lcmljSW52aXRhdGlv", + "blJlcXVlc3QaFC5iZ3MucHJvdG9jb2wuTm9EYXRhIgOIAgESUQoRRGVjbGlu", + "ZUludml0YXRpb24SJi5iZ3MucHJvdG9jb2wuR2VuZXJpY0ludml0YXRpb25S", + "ZXF1ZXN0GhQuYmdzLnByb3RvY29sLk5vRGF0YRJQChBJZ25vcmVJbnZpdGF0", + "aW9uEiYuYmdzLnByb3RvY29sLkdlbmVyaWNJbnZpdGF0aW9uUmVxdWVzdBoU", + "LmJncy5wcm90b2NvbC5Ob0RhdGESTgoKQXNzaWduUm9sZRIqLmJncy5wcm90", + "b2NvbC5mcmllbmRzLnYxLkFzc2lnblJvbGVSZXF1ZXN0GhQuYmdzLnByb3Rv", + "Y29sLk5vRGF0YRJtCgxSZW1vdmVGcmllbmQSLS5iZ3MucHJvdG9jb2wuZnJp", + "ZW5kcy52MS5HZW5lcmljRnJpZW5kUmVxdWVzdBouLmJncy5wcm90b2NvbC5m", + "cmllbmRzLnYxLkdlbmVyaWNGcmllbmRSZXNwb25zZRJoCgtWaWV3RnJpZW5k", + "cxIrLmJncy5wcm90b2NvbC5mcmllbmRzLnYxLlZpZXdGcmllbmRzUmVxdWVz", + "dBosLmJncy5wcm90b2NvbC5mcmllbmRzLnYxLlZpZXdGcmllbmRzUmVzcG9u", + "c2USXAoRVXBkYXRlRnJpZW5kU3RhdGUSMS5iZ3MucHJvdG9jb2wuZnJpZW5k", + "cy52MS5VcGRhdGVGcmllbmRTdGF0ZVJlcXVlc3QaFC5iZ3MucHJvdG9jb2wu", + "Tm9EYXRhElAKC1Vuc3Vic2NyaWJlEisuYmdzLnByb3RvY29sLmZyaWVuZHMu", + "djEuVW5zdWJzY3JpYmVSZXF1ZXN0GhQuYmdzLnByb3RvY29sLk5vRGF0YRJb", + "ChRSZXZva2VBbGxJbnZpdGF0aW9ucxItLmJncy5wcm90b2NvbC5mcmllbmRz", + "LnYxLkdlbmVyaWNGcmllbmRSZXF1ZXN0GhQuYmdzLnByb3RvY29sLk5vRGF0", + "YTLZBQoPRnJpZW5kc0xpc3RlbmVyElcKDU9uRnJpZW5kQWRkZWQSKy5iZ3Mu", + "cHJvdG9jb2wuZnJpZW5kcy52MS5GcmllbmROb3RpZmljYXRpb24aGS5iZ3Mu", + "cHJvdG9jb2wuTk9fUkVTUE9OU0USWQoPT25GcmllbmRSZW1vdmVkEisuYmdz", + "LnByb3RvY29sLmZyaWVuZHMudjEuRnJpZW5kTm90aWZpY2F0aW9uGhkuYmdz", + "LnByb3RvY29sLk5PX1JFU1BPTlNFEmcKGU9uUmVjZWl2ZWRJbnZpdGF0aW9u", + "QWRkZWQSLy5iZ3MucHJvdG9jb2wuZnJpZW5kcy52MS5JbnZpdGF0aW9uTm90", + "aWZpY2F0aW9uGhkuYmdzLnByb3RvY29sLk5PX1JFU1BPTlNFEmkKG09uUmVj", + "ZWl2ZWRJbnZpdGF0aW9uUmVtb3ZlZBIvLmJncy5wcm90b2NvbC5mcmllbmRz", + "LnYxLkludml0YXRpb25Ob3RpZmljYXRpb24aGS5iZ3MucHJvdG9jb2wuTk9f", + "UkVTUE9OU0USaAoVT25TZW50SW52aXRhdGlvbkFkZGVkEi8uYmdzLnByb3Rv", + "Y29sLmZyaWVuZHMudjEuSW52aXRhdGlvbk5vdGlmaWNhdGlvbhoZLmJncy5w", + "cm90b2NvbC5OT19SRVNQT05TRSIDiAIBEmoKF09uU2VudEludml0YXRpb25S", + "ZW1vdmVkEi8uYmdzLnByb3RvY29sLmZyaWVuZHMudjEuSW52aXRhdGlvbk5v", + "dGlmaWNhdGlvbhoZLmJncy5wcm90b2NvbC5OT19SRVNQT05TRSIDiAIBEmgK", + "E09uVXBkYXRlRnJpZW5kU3RhdGUSNi5iZ3MucHJvdG9jb2wuZnJpZW5kcy52", + "MS5VcGRhdGVGcmllbmRTdGF0ZU5vdGlmaWNhdGlvbhoZLmJncy5wcm90b2Nv", + "bC5OT19SRVNQT05TRUI3ChhibmV0LnByb3RvY29sLmZyaWVuZHMudjFCE0Zy", + "aWVuZHNTZXJ2aWNlUHJvdG9IAoABAIgBAWIGcHJvdG8z")); + descriptor = pbr::FileDescriptor.FromGeneratedCode(descriptorData, + new pbr::FileDescriptor[] { Bgs.Protocol.AttributeTypesReflection.Descriptor, Bgs.Protocol.EntityTypesReflection.Descriptor, Bgs.Protocol.Friends.V1.FriendsTypesReflection.Descriptor, Bgs.Protocol.InvitationTypesReflection.Descriptor, Bgs.Protocol.RoleTypesReflection.Descriptor, Bgs.Protocol.RpcTypesReflection.Descriptor, }, + new pbr::GeneratedClrTypeInfo(null, new pbr::GeneratedClrTypeInfo[] { + new pbr::GeneratedClrTypeInfo(typeof(Bgs.Protocol.Friends.V1.SubscribeRequest), Bgs.Protocol.Friends.V1.SubscribeRequest.Parser, new[]{ "AgentId", "ObjectId" }, null, null, null), + new pbr::GeneratedClrTypeInfo(typeof(Bgs.Protocol.Friends.V1.SubscribeResponse), Bgs.Protocol.Friends.V1.SubscribeResponse.Parser, new[]{ "MaxFriends", "MaxReceivedInvitations", "MaxSentInvitations", "Role", "Friends", "SentInvitations", "ReceivedInvitations" }, null, null, null), + new pbr::GeneratedClrTypeInfo(typeof(Bgs.Protocol.Friends.V1.UnsubscribeRequest), Bgs.Protocol.Friends.V1.UnsubscribeRequest.Parser, new[]{ "AgentId", "ObjectId" }, null, null, null), + new pbr::GeneratedClrTypeInfo(typeof(Bgs.Protocol.Friends.V1.GenericFriendRequest), Bgs.Protocol.Friends.V1.GenericFriendRequest.Parser, new[]{ "AgentId", "TargetId" }, null, null, null), + new pbr::GeneratedClrTypeInfo(typeof(Bgs.Protocol.Friends.V1.GenericFriendResponse), Bgs.Protocol.Friends.V1.GenericFriendResponse.Parser, new[]{ "TargetFriend" }, null, null, null), + new pbr::GeneratedClrTypeInfo(typeof(Bgs.Protocol.Friends.V1.AssignRoleRequest), Bgs.Protocol.Friends.V1.AssignRoleRequest.Parser, new[]{ "AgentId", "TargetId", "Role" }, null, null, null), + new pbr::GeneratedClrTypeInfo(typeof(Bgs.Protocol.Friends.V1.ViewFriendsRequest), Bgs.Protocol.Friends.V1.ViewFriendsRequest.Parser, new[]{ "AgentId", "TargetId", "Role" }, null, null, null), + new pbr::GeneratedClrTypeInfo(typeof(Bgs.Protocol.Friends.V1.ViewFriendsResponse), Bgs.Protocol.Friends.V1.ViewFriendsResponse.Parser, new[]{ "Friends" }, null, null, null), + new pbr::GeneratedClrTypeInfo(typeof(Bgs.Protocol.Friends.V1.UpdateFriendStateRequest), Bgs.Protocol.Friends.V1.UpdateFriendStateRequest.Parser, new[]{ "AgentId", "TargetId", "Attribute", "AttributesEpoch" }, null, null, null), + new pbr::GeneratedClrTypeInfo(typeof(Bgs.Protocol.Friends.V1.FriendNotification), Bgs.Protocol.Friends.V1.FriendNotification.Parser, new[]{ "Target", "GameAccountId", "Peer", "AccountId" }, null, null, null), + new pbr::GeneratedClrTypeInfo(typeof(Bgs.Protocol.Friends.V1.UpdateFriendStateNotification), Bgs.Protocol.Friends.V1.UpdateFriendStateNotification.Parser, new[]{ "ChangedFriend", "GameAccountId", "Peer", "AccountId" }, null, null, null), + new pbr::GeneratedClrTypeInfo(typeof(Bgs.Protocol.Friends.V1.InvitationNotification), Bgs.Protocol.Friends.V1.InvitationNotification.Parser, new[]{ "Invitation", "GameAccountId", "Reason", "Peer", "AccountId" }, null, null, null) + })); + } + #endregion + + } + #region Messages + [System.Diagnostics.DebuggerNonUserCodeAttribute()] + public sealed partial class SubscribeRequest : pb::IMessage { + private static readonly pb::MessageParser _parser = new pb::MessageParser(() => new SubscribeRequest()); + public static pb::MessageParser Parser { get { return _parser; } } + + public static pbr::MessageDescriptor Descriptor { + get { return Bgs.Protocol.Friends.V1.FriendsServiceReflection.Descriptor.MessageTypes[0]; } + } + + pbr::MessageDescriptor pb::IMessage.Descriptor { + get { return Descriptor; } + } + + public SubscribeRequest() { + OnConstruction(); + } + + partial void OnConstruction(); + + public SubscribeRequest(SubscribeRequest other) : this() { + AgentId = other.agentId_ != null ? other.AgentId.Clone() : null; + objectId_ = other.objectId_; + } + + public SubscribeRequest Clone() { + return new SubscribeRequest(this); + } + + /// Field number for the "agent_id" field. + public const int AgentIdFieldNumber = 1; + private Bgs.Protocol.EntityId agentId_; + public Bgs.Protocol.EntityId AgentId { + get { return agentId_; } + set { + agentId_ = value; + } + } + + /// Field number for the "object_id" field. + public const int ObjectIdFieldNumber = 2; + private ulong objectId_; + public ulong ObjectId { + get { return objectId_; } + set { + objectId_ = value; + } + } + + public override bool Equals(object other) { + return Equals(other as SubscribeRequest); + } + + public bool Equals(SubscribeRequest other) { + if (ReferenceEquals(other, null)) { + return false; + } + if (ReferenceEquals(other, this)) { + return true; + } + if (!object.Equals(AgentId, other.AgentId)) return false; + if (ObjectId != other.ObjectId) return false; + return true; + } + + public override int GetHashCode() { + int hash = 1; + if (agentId_ != null) hash ^= AgentId.GetHashCode(); + if (ObjectId != 0UL) hash ^= ObjectId.GetHashCode(); + return hash; + } + + public override string ToString() { + return pb::JsonFormatter.ToDiagnosticString(this); + } + + public void WriteTo(pb::CodedOutputStream output) { + if (agentId_ != null) { + output.WriteRawTag(10); + output.WriteMessage(AgentId); + } + if (ObjectId != 0UL) { + output.WriteRawTag(16); + output.WriteUInt64(ObjectId); + } + } + + public int CalculateSize() { + int size = 0; + if (agentId_ != null) { + size += 1 + pb::CodedOutputStream.ComputeMessageSize(AgentId); + } + if (ObjectId != 0UL) { + size += 1 + pb::CodedOutputStream.ComputeUInt64Size(ObjectId); + } + return size; + } + + public void MergeFrom(SubscribeRequest other) { + if (other == null) { + return; + } + if (other.agentId_ != null) { + if (agentId_ == null) { + agentId_ = new Bgs.Protocol.EntityId(); + } + AgentId.MergeFrom(other.AgentId); + } + if (other.ObjectId != 0UL) { + ObjectId = other.ObjectId; + } + } + + public void MergeFrom(pb::CodedInputStream input) { + uint tag; + while ((tag = input.ReadTag()) != 0) { + switch(tag) { + default: + input.SkipLastField(); + break; + case 10: { + if (agentId_ == null) { + agentId_ = new Bgs.Protocol.EntityId(); + } + input.ReadMessage(agentId_); + break; + } + case 16: { + ObjectId = input.ReadUInt64(); + break; + } + } + } + } + + } + + [System.Diagnostics.DebuggerNonUserCodeAttribute()] + public sealed partial class SubscribeResponse : pb::IMessage { + private static readonly pb::MessageParser _parser = new pb::MessageParser(() => new SubscribeResponse()); + public static pb::MessageParser Parser { get { return _parser; } } + + public static pbr::MessageDescriptor Descriptor { + get { return Bgs.Protocol.Friends.V1.FriendsServiceReflection.Descriptor.MessageTypes[1]; } + } + + pbr::MessageDescriptor pb::IMessage.Descriptor { + get { return Descriptor; } + } + + public SubscribeResponse() { + OnConstruction(); + } + + partial void OnConstruction(); + + public SubscribeResponse(SubscribeResponse other) : this() { + maxFriends_ = other.maxFriends_; + maxReceivedInvitations_ = other.maxReceivedInvitations_; + maxSentInvitations_ = other.maxSentInvitations_; + role_ = other.role_.Clone(); + friends_ = other.friends_.Clone(); + sentInvitations_ = other.sentInvitations_.Clone(); + receivedInvitations_ = other.receivedInvitations_.Clone(); + } + + public SubscribeResponse Clone() { + return new SubscribeResponse(this); + } + + /// Field number for the "max_friends" field. + public const int MaxFriendsFieldNumber = 1; + private uint maxFriends_; + public uint MaxFriends { + get { return maxFriends_; } + set { + maxFriends_ = value; + } + } + + /// Field number for the "max_received_invitations" field. + public const int MaxReceivedInvitationsFieldNumber = 2; + private uint maxReceivedInvitations_; + public uint MaxReceivedInvitations { + get { return maxReceivedInvitations_; } + set { + maxReceivedInvitations_ = value; + } + } + + /// Field number for the "max_sent_invitations" field. + public const int MaxSentInvitationsFieldNumber = 3; + private uint maxSentInvitations_; + public uint MaxSentInvitations { + get { return maxSentInvitations_; } + set { + maxSentInvitations_ = value; + } + } + + /// Field number for the "role" field. + public const int RoleFieldNumber = 4; + private static readonly pb::FieldCodec _repeated_role_codec + = pb::FieldCodec.ForMessage(34, Bgs.Protocol.Role.Parser); + private readonly pbc::RepeatedField role_ = new pbc::RepeatedField(); + public pbc::RepeatedField Role { + get { return role_; } + } + + /// Field number for the "friends" field. + public const int FriendsFieldNumber = 5; + private static readonly pb::FieldCodec _repeated_friends_codec + = pb::FieldCodec.ForMessage(42, Bgs.Protocol.Friends.V1.Friend.Parser); + private readonly pbc::RepeatedField friends_ = new pbc::RepeatedField(); + public pbc::RepeatedField Friends { + get { return friends_; } + } + + /// Field number for the "sent_invitations" field. + public const int SentInvitationsFieldNumber = 6; + private static readonly pb::FieldCodec _repeated_sentInvitations_codec + = pb::FieldCodec.ForMessage(50, Bgs.Protocol.Invitation.Parser); + private readonly pbc::RepeatedField sentInvitations_ = new pbc::RepeatedField(); + public pbc::RepeatedField SentInvitations { + get { return sentInvitations_; } + } + + /// Field number for the "received_invitations" field. + public const int ReceivedInvitationsFieldNumber = 7; + private static readonly pb::FieldCodec _repeated_receivedInvitations_codec + = pb::FieldCodec.ForMessage(58, Bgs.Protocol.Invitation.Parser); + private readonly pbc::RepeatedField receivedInvitations_ = new pbc::RepeatedField(); + public pbc::RepeatedField ReceivedInvitations { + get { return receivedInvitations_; } + } + + public override bool Equals(object other) { + return Equals(other as SubscribeResponse); + } + + public bool Equals(SubscribeResponse other) { + if (ReferenceEquals(other, null)) { + return false; + } + if (ReferenceEquals(other, this)) { + return true; + } + if (MaxFriends != other.MaxFriends) return false; + if (MaxReceivedInvitations != other.MaxReceivedInvitations) return false; + if (MaxSentInvitations != other.MaxSentInvitations) return false; + if(!role_.Equals(other.role_)) return false; + if(!friends_.Equals(other.friends_)) return false; + if(!sentInvitations_.Equals(other.sentInvitations_)) return false; + if(!receivedInvitations_.Equals(other.receivedInvitations_)) return false; + return true; + } + + public override int GetHashCode() { + int hash = 1; + if (MaxFriends != 0) hash ^= MaxFriends.GetHashCode(); + if (MaxReceivedInvitations != 0) hash ^= MaxReceivedInvitations.GetHashCode(); + if (MaxSentInvitations != 0) hash ^= MaxSentInvitations.GetHashCode(); + hash ^= role_.GetHashCode(); + hash ^= friends_.GetHashCode(); + hash ^= sentInvitations_.GetHashCode(); + hash ^= receivedInvitations_.GetHashCode(); + return hash; + } + + public override string ToString() { + return pb::JsonFormatter.ToDiagnosticString(this); + } + + public void WriteTo(pb::CodedOutputStream output) { + if (MaxFriends != 0) { + output.WriteRawTag(8); + output.WriteUInt32(MaxFriends); + } + if (MaxReceivedInvitations != 0) { + output.WriteRawTag(16); + output.WriteUInt32(MaxReceivedInvitations); + } + if (MaxSentInvitations != 0) { + output.WriteRawTag(24); + output.WriteUInt32(MaxSentInvitations); + } + role_.WriteTo(output, _repeated_role_codec); + friends_.WriteTo(output, _repeated_friends_codec); + sentInvitations_.WriteTo(output, _repeated_sentInvitations_codec); + receivedInvitations_.WriteTo(output, _repeated_receivedInvitations_codec); + } + + public int CalculateSize() { + int size = 0; + if (MaxFriends != 0) { + size += 1 + pb::CodedOutputStream.ComputeUInt32Size(MaxFriends); + } + if (MaxReceivedInvitations != 0) { + size += 1 + pb::CodedOutputStream.ComputeUInt32Size(MaxReceivedInvitations); + } + if (MaxSentInvitations != 0) { + size += 1 + pb::CodedOutputStream.ComputeUInt32Size(MaxSentInvitations); + } + size += role_.CalculateSize(_repeated_role_codec); + size += friends_.CalculateSize(_repeated_friends_codec); + size += sentInvitations_.CalculateSize(_repeated_sentInvitations_codec); + size += receivedInvitations_.CalculateSize(_repeated_receivedInvitations_codec); + return size; + } + + public void MergeFrom(SubscribeResponse other) { + if (other == null) { + return; + } + if (other.MaxFriends != 0) { + MaxFriends = other.MaxFriends; + } + if (other.MaxReceivedInvitations != 0) { + MaxReceivedInvitations = other.MaxReceivedInvitations; + } + if (other.MaxSentInvitations != 0) { + MaxSentInvitations = other.MaxSentInvitations; + } + role_.Add(other.role_); + friends_.Add(other.friends_); + sentInvitations_.Add(other.sentInvitations_); + receivedInvitations_.Add(other.receivedInvitations_); + } + + public void MergeFrom(pb::CodedInputStream input) { + uint tag; + while ((tag = input.ReadTag()) != 0) { + switch(tag) { + default: + input.SkipLastField(); + break; + case 8: { + MaxFriends = input.ReadUInt32(); + break; + } + case 16: { + MaxReceivedInvitations = input.ReadUInt32(); + break; + } + case 24: { + MaxSentInvitations = input.ReadUInt32(); + break; + } + case 34: { + role_.AddEntriesFrom(input, _repeated_role_codec); + break; + } + case 42: { + friends_.AddEntriesFrom(input, _repeated_friends_codec); + break; + } + case 50: { + sentInvitations_.AddEntriesFrom(input, _repeated_sentInvitations_codec); + break; + } + case 58: { + receivedInvitations_.AddEntriesFrom(input, _repeated_receivedInvitations_codec); + break; + } + } + } + } + + } + + [System.Diagnostics.DebuggerNonUserCodeAttribute()] + public sealed partial class UnsubscribeRequest : pb::IMessage { + private static readonly pb::MessageParser _parser = new pb::MessageParser(() => new UnsubscribeRequest()); + public static pb::MessageParser Parser { get { return _parser; } } + + public static pbr::MessageDescriptor Descriptor { + get { return Bgs.Protocol.Friends.V1.FriendsServiceReflection.Descriptor.MessageTypes[2]; } + } + + pbr::MessageDescriptor pb::IMessage.Descriptor { + get { return Descriptor; } + } + + public UnsubscribeRequest() { + OnConstruction(); + } + + partial void OnConstruction(); + + public UnsubscribeRequest(UnsubscribeRequest other) : this() { + AgentId = other.agentId_ != null ? other.AgentId.Clone() : null; + objectId_ = other.objectId_; + } + + public UnsubscribeRequest Clone() { + return new UnsubscribeRequest(this); + } + + /// Field number for the "agent_id" field. + public const int AgentIdFieldNumber = 1; + private Bgs.Protocol.EntityId agentId_; + public Bgs.Protocol.EntityId AgentId { + get { return agentId_; } + set { + agentId_ = value; + } + } + + /// Field number for the "object_id" field. + public const int ObjectIdFieldNumber = 2; + private ulong objectId_; + public ulong ObjectId { + get { return objectId_; } + set { + objectId_ = value; + } + } + + public override bool Equals(object other) { + return Equals(other as UnsubscribeRequest); + } + + public bool Equals(UnsubscribeRequest other) { + if (ReferenceEquals(other, null)) { + return false; + } + if (ReferenceEquals(other, this)) { + return true; + } + if (!object.Equals(AgentId, other.AgentId)) return false; + if (ObjectId != other.ObjectId) return false; + return true; + } + + public override int GetHashCode() { + int hash = 1; + if (agentId_ != null) hash ^= AgentId.GetHashCode(); + if (ObjectId != 0UL) hash ^= ObjectId.GetHashCode(); + return hash; + } + + public override string ToString() { + return pb::JsonFormatter.ToDiagnosticString(this); + } + + public void WriteTo(pb::CodedOutputStream output) { + if (agentId_ != null) { + output.WriteRawTag(10); + output.WriteMessage(AgentId); + } + if (ObjectId != 0UL) { + output.WriteRawTag(16); + output.WriteUInt64(ObjectId); + } + } + + public int CalculateSize() { + int size = 0; + if (agentId_ != null) { + size += 1 + pb::CodedOutputStream.ComputeMessageSize(AgentId); + } + if (ObjectId != 0UL) { + size += 1 + pb::CodedOutputStream.ComputeUInt64Size(ObjectId); + } + return size; + } + + public void MergeFrom(UnsubscribeRequest other) { + if (other == null) { + return; + } + if (other.agentId_ != null) { + if (agentId_ == null) { + agentId_ = new Bgs.Protocol.EntityId(); + } + AgentId.MergeFrom(other.AgentId); + } + if (other.ObjectId != 0UL) { + ObjectId = other.ObjectId; + } + } + + public void MergeFrom(pb::CodedInputStream input) { + uint tag; + while ((tag = input.ReadTag()) != 0) { + switch(tag) { + default: + input.SkipLastField(); + break; + case 10: { + if (agentId_ == null) { + agentId_ = new Bgs.Protocol.EntityId(); + } + input.ReadMessage(agentId_); + break; + } + case 16: { + ObjectId = input.ReadUInt64(); + break; + } + } + } + } + + } + + [System.Diagnostics.DebuggerNonUserCodeAttribute()] + public sealed partial class GenericFriendRequest : pb::IMessage { + private static readonly pb::MessageParser _parser = new pb::MessageParser(() => new GenericFriendRequest()); + public static pb::MessageParser Parser { get { return _parser; } } + + public static pbr::MessageDescriptor Descriptor { + get { return Bgs.Protocol.Friends.V1.FriendsServiceReflection.Descriptor.MessageTypes[3]; } + } + + pbr::MessageDescriptor pb::IMessage.Descriptor { + get { return Descriptor; } + } + + public GenericFriendRequest() { + OnConstruction(); + } + + partial void OnConstruction(); + + public GenericFriendRequest(GenericFriendRequest other) : this() { + AgentId = other.agentId_ != null ? other.AgentId.Clone() : null; + TargetId = other.targetId_ != null ? other.TargetId.Clone() : null; + } + + public GenericFriendRequest Clone() { + return new GenericFriendRequest(this); + } + + /// Field number for the "agent_id" field. + public const int AgentIdFieldNumber = 1; + private Bgs.Protocol.EntityId agentId_; + public Bgs.Protocol.EntityId AgentId { + get { return agentId_; } + set { + agentId_ = value; + } + } + + /// Field number for the "target_id" field. + public const int TargetIdFieldNumber = 2; + private Bgs.Protocol.EntityId targetId_; + public Bgs.Protocol.EntityId TargetId { + get { return targetId_; } + set { + targetId_ = value; + } + } + + public override bool Equals(object other) { + return Equals(other as GenericFriendRequest); + } + + public bool Equals(GenericFriendRequest other) { + if (ReferenceEquals(other, null)) { + return false; + } + if (ReferenceEquals(other, this)) { + return true; + } + if (!object.Equals(AgentId, other.AgentId)) return false; + if (!object.Equals(TargetId, other.TargetId)) return false; + return true; + } + + public override int GetHashCode() { + int hash = 1; + if (agentId_ != null) hash ^= AgentId.GetHashCode(); + if (targetId_ != null) hash ^= TargetId.GetHashCode(); + return hash; + } + + public override string ToString() { + return pb::JsonFormatter.ToDiagnosticString(this); + } + + public void WriteTo(pb::CodedOutputStream output) { + if (agentId_ != null) { + output.WriteRawTag(10); + output.WriteMessage(AgentId); + } + if (targetId_ != null) { + output.WriteRawTag(18); + output.WriteMessage(TargetId); + } + } + + public int CalculateSize() { + int size = 0; + if (agentId_ != null) { + size += 1 + pb::CodedOutputStream.ComputeMessageSize(AgentId); + } + if (targetId_ != null) { + size += 1 + pb::CodedOutputStream.ComputeMessageSize(TargetId); + } + return size; + } + + public void MergeFrom(GenericFriendRequest other) { + if (other == null) { + return; + } + if (other.agentId_ != null) { + if (agentId_ == null) { + agentId_ = new Bgs.Protocol.EntityId(); + } + AgentId.MergeFrom(other.AgentId); + } + if (other.targetId_ != null) { + if (targetId_ == null) { + targetId_ = new Bgs.Protocol.EntityId(); + } + TargetId.MergeFrom(other.TargetId); + } + } + + public void MergeFrom(pb::CodedInputStream input) { + uint tag; + while ((tag = input.ReadTag()) != 0) { + switch(tag) { + default: + input.SkipLastField(); + break; + case 10: { + if (agentId_ == null) { + agentId_ = new Bgs.Protocol.EntityId(); + } + input.ReadMessage(agentId_); + break; + } + case 18: { + if (targetId_ == null) { + targetId_ = new Bgs.Protocol.EntityId(); + } + input.ReadMessage(targetId_); + break; + } + } + } + } + + } + + [System.Diagnostics.DebuggerNonUserCodeAttribute()] + public sealed partial class GenericFriendResponse : pb::IMessage { + private static readonly pb::MessageParser _parser = new pb::MessageParser(() => new GenericFriendResponse()); + public static pb::MessageParser Parser { get { return _parser; } } + + public static pbr::MessageDescriptor Descriptor { + get { return Bgs.Protocol.Friends.V1.FriendsServiceReflection.Descriptor.MessageTypes[4]; } + } + + pbr::MessageDescriptor pb::IMessage.Descriptor { + get { return Descriptor; } + } + + public GenericFriendResponse() { + OnConstruction(); + } + + partial void OnConstruction(); + + public GenericFriendResponse(GenericFriendResponse other) : this() { + TargetFriend = other.targetFriend_ != null ? other.TargetFriend.Clone() : null; + } + + public GenericFriendResponse Clone() { + return new GenericFriendResponse(this); + } + + /// Field number for the "target_friend" field. + public const int TargetFriendFieldNumber = 1; + private Bgs.Protocol.Friends.V1.Friend targetFriend_; + public Bgs.Protocol.Friends.V1.Friend TargetFriend { + get { return targetFriend_; } + set { + targetFriend_ = value; + } + } + + public override bool Equals(object other) { + return Equals(other as GenericFriendResponse); + } + + public bool Equals(GenericFriendResponse other) { + if (ReferenceEquals(other, null)) { + return false; + } + if (ReferenceEquals(other, this)) { + return true; + } + if (!object.Equals(TargetFriend, other.TargetFriend)) return false; + return true; + } + + public override int GetHashCode() { + int hash = 1; + if (targetFriend_ != null) hash ^= TargetFriend.GetHashCode(); + return hash; + } + + public override string ToString() { + return pb::JsonFormatter.ToDiagnosticString(this); + } + + public void WriteTo(pb::CodedOutputStream output) { + if (targetFriend_ != null) { + output.WriteRawTag(10); + output.WriteMessage(TargetFriend); + } + } + + public int CalculateSize() { + int size = 0; + if (targetFriend_ != null) { + size += 1 + pb::CodedOutputStream.ComputeMessageSize(TargetFriend); + } + return size; + } + + public void MergeFrom(GenericFriendResponse other) { + if (other == null) { + return; + } + if (other.targetFriend_ != null) { + if (targetFriend_ == null) { + targetFriend_ = new Bgs.Protocol.Friends.V1.Friend(); + } + TargetFriend.MergeFrom(other.TargetFriend); + } + } + + public void MergeFrom(pb::CodedInputStream input) { + uint tag; + while ((tag = input.ReadTag()) != 0) { + switch(tag) { + default: + input.SkipLastField(); + break; + case 10: { + if (targetFriend_ == null) { + targetFriend_ = new Bgs.Protocol.Friends.V1.Friend(); + } + input.ReadMessage(targetFriend_); + break; + } + } + } + } + + } + + [System.Diagnostics.DebuggerNonUserCodeAttribute()] + public sealed partial class AssignRoleRequest : pb::IMessage { + private static readonly pb::MessageParser _parser = new pb::MessageParser(() => new AssignRoleRequest()); + public static pb::MessageParser Parser { get { return _parser; } } + + public static pbr::MessageDescriptor Descriptor { + get { return Bgs.Protocol.Friends.V1.FriendsServiceReflection.Descriptor.MessageTypes[5]; } + } + + pbr::MessageDescriptor pb::IMessage.Descriptor { + get { return Descriptor; } + } + + public AssignRoleRequest() { + OnConstruction(); + } + + partial void OnConstruction(); + + public AssignRoleRequest(AssignRoleRequest other) : this() { + AgentId = other.agentId_ != null ? other.AgentId.Clone() : null; + TargetId = other.targetId_ != null ? other.TargetId.Clone() : null; + role_ = other.role_.Clone(); + } + + public AssignRoleRequest Clone() { + return new AssignRoleRequest(this); + } + + /// Field number for the "agent_id" field. + public const int AgentIdFieldNumber = 1; + private Bgs.Protocol.EntityId agentId_; + public Bgs.Protocol.EntityId AgentId { + get { return agentId_; } + set { + agentId_ = value; + } + } + + /// Field number for the "target_id" field. + public const int TargetIdFieldNumber = 2; + private Bgs.Protocol.EntityId targetId_; + public Bgs.Protocol.EntityId TargetId { + get { return targetId_; } + set { + targetId_ = value; + } + } + + /// Field number for the "role" field. + public const int RoleFieldNumber = 3; + private static readonly pb::FieldCodec _repeated_role_codec + = pb::FieldCodec.ForInt32(26); + private readonly pbc::RepeatedField role_ = new pbc::RepeatedField(); + public pbc::RepeatedField Role { + get { return role_; } + } + + public override bool Equals(object other) { + return Equals(other as AssignRoleRequest); + } + + public bool Equals(AssignRoleRequest other) { + if (ReferenceEquals(other, null)) { + return false; + } + if (ReferenceEquals(other, this)) { + return true; + } + if (!object.Equals(AgentId, other.AgentId)) return false; + if (!object.Equals(TargetId, other.TargetId)) return false; + if(!role_.Equals(other.role_)) return false; + return true; + } + + public override int GetHashCode() { + int hash = 1; + if (agentId_ != null) hash ^= AgentId.GetHashCode(); + if (targetId_ != null) hash ^= TargetId.GetHashCode(); + hash ^= role_.GetHashCode(); + return hash; + } + + public override string ToString() { + return pb::JsonFormatter.ToDiagnosticString(this); + } + + public void WriteTo(pb::CodedOutputStream output) { + if (agentId_ != null) { + output.WriteRawTag(10); + output.WriteMessage(AgentId); + } + if (targetId_ != null) { + output.WriteRawTag(18); + output.WriteMessage(TargetId); + } + role_.WriteTo(output, _repeated_role_codec); + } + + public int CalculateSize() { + int size = 0; + if (agentId_ != null) { + size += 1 + pb::CodedOutputStream.ComputeMessageSize(AgentId); + } + if (targetId_ != null) { + size += 1 + pb::CodedOutputStream.ComputeMessageSize(TargetId); + } + size += role_.CalculateSize(_repeated_role_codec); + return size; + } + + public void MergeFrom(AssignRoleRequest other) { + if (other == null) { + return; + } + if (other.agentId_ != null) { + if (agentId_ == null) { + agentId_ = new Bgs.Protocol.EntityId(); + } + AgentId.MergeFrom(other.AgentId); + } + if (other.targetId_ != null) { + if (targetId_ == null) { + targetId_ = new Bgs.Protocol.EntityId(); + } + TargetId.MergeFrom(other.TargetId); + } + role_.Add(other.role_); + } + + public void MergeFrom(pb::CodedInputStream input) { + uint tag; + while ((tag = input.ReadTag()) != 0) { + switch(tag) { + default: + input.SkipLastField(); + break; + case 10: { + if (agentId_ == null) { + agentId_ = new Bgs.Protocol.EntityId(); + } + input.ReadMessage(agentId_); + break; + } + case 18: { + if (targetId_ == null) { + targetId_ = new Bgs.Protocol.EntityId(); + } + input.ReadMessage(targetId_); + break; + } + case 26: + case 24: { + role_.AddEntriesFrom(input, _repeated_role_codec); + break; + } + } + } + } + + } + + [System.Diagnostics.DebuggerNonUserCodeAttribute()] + public sealed partial class ViewFriendsRequest : pb::IMessage { + private static readonly pb::MessageParser _parser = new pb::MessageParser(() => new ViewFriendsRequest()); + public static pb::MessageParser Parser { get { return _parser; } } + + public static pbr::MessageDescriptor Descriptor { + get { return Bgs.Protocol.Friends.V1.FriendsServiceReflection.Descriptor.MessageTypes[6]; } + } + + pbr::MessageDescriptor pb::IMessage.Descriptor { + get { return Descriptor; } + } + + public ViewFriendsRequest() { + OnConstruction(); + } + + partial void OnConstruction(); + + public ViewFriendsRequest(ViewFriendsRequest other) : this() { + AgentId = other.agentId_ != null ? other.AgentId.Clone() : null; + TargetId = other.targetId_ != null ? other.TargetId.Clone() : null; + role_ = other.role_.Clone(); + } + + public ViewFriendsRequest Clone() { + return new ViewFriendsRequest(this); + } + + /// Field number for the "agent_id" field. + public const int AgentIdFieldNumber = 1; + private Bgs.Protocol.EntityId agentId_; + public Bgs.Protocol.EntityId AgentId { + get { return agentId_; } + set { + agentId_ = value; + } + } + + /// Field number for the "target_id" field. + public const int TargetIdFieldNumber = 2; + private Bgs.Protocol.EntityId targetId_; + public Bgs.Protocol.EntityId TargetId { + get { return targetId_; } + set { + targetId_ = value; + } + } + + /// Field number for the "role" field. + public const int RoleFieldNumber = 3; + private static readonly pb::FieldCodec _repeated_role_codec + = pb::FieldCodec.ForUInt32(26); + private readonly pbc::RepeatedField role_ = new pbc::RepeatedField(); + public pbc::RepeatedField Role { + get { return role_; } + } + + public override bool Equals(object other) { + return Equals(other as ViewFriendsRequest); + } + + public bool Equals(ViewFriendsRequest other) { + if (ReferenceEquals(other, null)) { + return false; + } + if (ReferenceEquals(other, this)) { + return true; + } + if (!object.Equals(AgentId, other.AgentId)) return false; + if (!object.Equals(TargetId, other.TargetId)) return false; + if(!role_.Equals(other.role_)) return false; + return true; + } + + public override int GetHashCode() { + int hash = 1; + if (agentId_ != null) hash ^= AgentId.GetHashCode(); + if (targetId_ != null) hash ^= TargetId.GetHashCode(); + hash ^= role_.GetHashCode(); + return hash; + } + + public override string ToString() { + return pb::JsonFormatter.ToDiagnosticString(this); + } + + public void WriteTo(pb::CodedOutputStream output) { + if (agentId_ != null) { + output.WriteRawTag(10); + output.WriteMessage(AgentId); + } + if (targetId_ != null) { + output.WriteRawTag(18); + output.WriteMessage(TargetId); + } + role_.WriteTo(output, _repeated_role_codec); + } + + public int CalculateSize() { + int size = 0; + if (agentId_ != null) { + size += 1 + pb::CodedOutputStream.ComputeMessageSize(AgentId); + } + if (targetId_ != null) { + size += 1 + pb::CodedOutputStream.ComputeMessageSize(TargetId); + } + size += role_.CalculateSize(_repeated_role_codec); + return size; + } + + public void MergeFrom(ViewFriendsRequest other) { + if (other == null) { + return; + } + if (other.agentId_ != null) { + if (agentId_ == null) { + agentId_ = new Bgs.Protocol.EntityId(); + } + AgentId.MergeFrom(other.AgentId); + } + if (other.targetId_ != null) { + if (targetId_ == null) { + targetId_ = new Bgs.Protocol.EntityId(); + } + TargetId.MergeFrom(other.TargetId); + } + role_.Add(other.role_); + } + + public void MergeFrom(pb::CodedInputStream input) { + uint tag; + while ((tag = input.ReadTag()) != 0) { + switch(tag) { + default: + input.SkipLastField(); + break; + case 10: { + if (agentId_ == null) { + agentId_ = new Bgs.Protocol.EntityId(); + } + input.ReadMessage(agentId_); + break; + } + case 18: { + if (targetId_ == null) { + targetId_ = new Bgs.Protocol.EntityId(); + } + input.ReadMessage(targetId_); + break; + } + case 26: + case 24: { + role_.AddEntriesFrom(input, _repeated_role_codec); + break; + } + } + } + } + + } + + [System.Diagnostics.DebuggerNonUserCodeAttribute()] + public sealed partial class ViewFriendsResponse : pb::IMessage { + private static readonly pb::MessageParser _parser = new pb::MessageParser(() => new ViewFriendsResponse()); + public static pb::MessageParser Parser { get { return _parser; } } + + public static pbr::MessageDescriptor Descriptor { + get { return Bgs.Protocol.Friends.V1.FriendsServiceReflection.Descriptor.MessageTypes[7]; } + } + + pbr::MessageDescriptor pb::IMessage.Descriptor { + get { return Descriptor; } + } + + public ViewFriendsResponse() { + OnConstruction(); + } + + partial void OnConstruction(); + + public ViewFriendsResponse(ViewFriendsResponse other) : this() { + friends_ = other.friends_.Clone(); + } + + public ViewFriendsResponse Clone() { + return new ViewFriendsResponse(this); + } + + /// Field number for the "friends" field. + public const int FriendsFieldNumber = 1; + private static readonly pb::FieldCodec _repeated_friends_codec + = pb::FieldCodec.ForMessage(10, Bgs.Protocol.Friends.V1.Friend.Parser); + private readonly pbc::RepeatedField friends_ = new pbc::RepeatedField(); + public pbc::RepeatedField Friends { + get { return friends_; } + } + + public override bool Equals(object other) { + return Equals(other as ViewFriendsResponse); + } + + public bool Equals(ViewFriendsResponse other) { + if (ReferenceEquals(other, null)) { + return false; + } + if (ReferenceEquals(other, this)) { + return true; + } + if(!friends_.Equals(other.friends_)) return false; + return true; + } + + public override int GetHashCode() { + int hash = 1; + hash ^= friends_.GetHashCode(); + return hash; + } + + public override string ToString() { + return pb::JsonFormatter.ToDiagnosticString(this); + } + + public void WriteTo(pb::CodedOutputStream output) { + friends_.WriteTo(output, _repeated_friends_codec); + } + + public int CalculateSize() { + int size = 0; + size += friends_.CalculateSize(_repeated_friends_codec); + return size; + } + + public void MergeFrom(ViewFriendsResponse other) { + if (other == null) { + return; + } + friends_.Add(other.friends_); + } + + public void MergeFrom(pb::CodedInputStream input) { + uint tag; + while ((tag = input.ReadTag()) != 0) { + switch(tag) { + default: + input.SkipLastField(); + break; + case 10: { + friends_.AddEntriesFrom(input, _repeated_friends_codec); + break; + } + } + } + } + + } + + [System.Diagnostics.DebuggerNonUserCodeAttribute()] + public sealed partial class UpdateFriendStateRequest : pb::IMessage { + private static readonly pb::MessageParser _parser = new pb::MessageParser(() => new UpdateFriendStateRequest()); + public static pb::MessageParser Parser { get { return _parser; } } + + public static pbr::MessageDescriptor Descriptor { + get { return Bgs.Protocol.Friends.V1.FriendsServiceReflection.Descriptor.MessageTypes[8]; } + } + + pbr::MessageDescriptor pb::IMessage.Descriptor { + get { return Descriptor; } + } + + public UpdateFriendStateRequest() { + OnConstruction(); + } + + partial void OnConstruction(); + + public UpdateFriendStateRequest(UpdateFriendStateRequest other) : this() { + AgentId = other.agentId_ != null ? other.AgentId.Clone() : null; + TargetId = other.targetId_ != null ? other.TargetId.Clone() : null; + attribute_ = other.attribute_.Clone(); + attributesEpoch_ = other.attributesEpoch_; + } + + public UpdateFriendStateRequest Clone() { + return new UpdateFriendStateRequest(this); + } + + /// Field number for the "agent_id" field. + public const int AgentIdFieldNumber = 1; + private Bgs.Protocol.EntityId agentId_; + public Bgs.Protocol.EntityId AgentId { + get { return agentId_; } + set { + agentId_ = value; + } + } + + /// Field number for the "target_id" field. + public const int TargetIdFieldNumber = 2; + private Bgs.Protocol.EntityId targetId_; + public Bgs.Protocol.EntityId TargetId { + get { return targetId_; } + set { + targetId_ = value; + } + } + + /// Field number for the "attribute" field. + public const int AttributeFieldNumber = 3; + private static readonly pb::FieldCodec _repeated_attribute_codec + = pb::FieldCodec.ForMessage(26, Bgs.Protocol.Attribute.Parser); + private readonly pbc::RepeatedField attribute_ = new pbc::RepeatedField(); + public pbc::RepeatedField Attribute { + get { return attribute_; } + } + + /// Field number for the "attributes_epoch" field. + public const int AttributesEpochFieldNumber = 4; + private ulong attributesEpoch_; + public ulong AttributesEpoch { + get { return attributesEpoch_; } + set { + attributesEpoch_ = value; + } + } + + public override bool Equals(object other) { + return Equals(other as UpdateFriendStateRequest); + } + + public bool Equals(UpdateFriendStateRequest other) { + if (ReferenceEquals(other, null)) { + return false; + } + if (ReferenceEquals(other, this)) { + return true; + } + if (!object.Equals(AgentId, other.AgentId)) return false; + if (!object.Equals(TargetId, other.TargetId)) return false; + if(!attribute_.Equals(other.attribute_)) return false; + if (AttributesEpoch != other.AttributesEpoch) return false; + return true; + } + + public override int GetHashCode() { + int hash = 1; + if (agentId_ != null) hash ^= AgentId.GetHashCode(); + if (targetId_ != null) hash ^= TargetId.GetHashCode(); + hash ^= attribute_.GetHashCode(); + if (AttributesEpoch != 0UL) hash ^= AttributesEpoch.GetHashCode(); + return hash; + } + + public override string ToString() { + return pb::JsonFormatter.ToDiagnosticString(this); + } + + public void WriteTo(pb::CodedOutputStream output) { + if (agentId_ != null) { + output.WriteRawTag(10); + output.WriteMessage(AgentId); + } + if (targetId_ != null) { + output.WriteRawTag(18); + output.WriteMessage(TargetId); + } + attribute_.WriteTo(output, _repeated_attribute_codec); + if (AttributesEpoch != 0UL) { + output.WriteRawTag(32); + output.WriteUInt64(AttributesEpoch); + } + } + + public int CalculateSize() { + int size = 0; + if (agentId_ != null) { + size += 1 + pb::CodedOutputStream.ComputeMessageSize(AgentId); + } + if (targetId_ != null) { + size += 1 + pb::CodedOutputStream.ComputeMessageSize(TargetId); + } + size += attribute_.CalculateSize(_repeated_attribute_codec); + if (AttributesEpoch != 0UL) { + size += 1 + pb::CodedOutputStream.ComputeUInt64Size(AttributesEpoch); + } + return size; + } + + public void MergeFrom(UpdateFriendStateRequest other) { + if (other == null) { + return; + } + if (other.agentId_ != null) { + if (agentId_ == null) { + agentId_ = new Bgs.Protocol.EntityId(); + } + AgentId.MergeFrom(other.AgentId); + } + if (other.targetId_ != null) { + if (targetId_ == null) { + targetId_ = new Bgs.Protocol.EntityId(); + } + TargetId.MergeFrom(other.TargetId); + } + attribute_.Add(other.attribute_); + if (other.AttributesEpoch != 0UL) { + AttributesEpoch = other.AttributesEpoch; + } + } + + public void MergeFrom(pb::CodedInputStream input) { + uint tag; + while ((tag = input.ReadTag()) != 0) { + switch(tag) { + default: + input.SkipLastField(); + break; + case 10: { + if (agentId_ == null) { + agentId_ = new Bgs.Protocol.EntityId(); + } + input.ReadMessage(agentId_); + break; + } + case 18: { + if (targetId_ == null) { + targetId_ = new Bgs.Protocol.EntityId(); + } + input.ReadMessage(targetId_); + break; + } + case 26: { + attribute_.AddEntriesFrom(input, _repeated_attribute_codec); + break; + } + case 32: { + AttributesEpoch = input.ReadUInt64(); + break; + } + } + } + } + + } + + [System.Diagnostics.DebuggerNonUserCodeAttribute()] + public sealed partial class FriendNotification : pb::IMessage { + private static readonly pb::MessageParser _parser = new pb::MessageParser(() => new FriendNotification()); + public static pb::MessageParser Parser { get { return _parser; } } + + public static pbr::MessageDescriptor Descriptor { + get { return Bgs.Protocol.Friends.V1.FriendsServiceReflection.Descriptor.MessageTypes[9]; } + } + + pbr::MessageDescriptor pb::IMessage.Descriptor { + get { return Descriptor; } + } + + public FriendNotification() { + OnConstruction(); + } + + partial void OnConstruction(); + + public FriendNotification(FriendNotification other) : this() { + Target = other.target_ != null ? other.Target.Clone() : null; + GameAccountId = other.gameAccountId_ != null ? other.GameAccountId.Clone() : null; + Peer = other.peer_ != null ? other.Peer.Clone() : null; + AccountId = other.accountId_ != null ? other.AccountId.Clone() : null; + } + + public FriendNotification Clone() { + return new FriendNotification(this); + } + + /// Field number for the "target" field. + public const int TargetFieldNumber = 1; + private Bgs.Protocol.Friends.V1.Friend target_; + public Bgs.Protocol.Friends.V1.Friend Target { + get { return target_; } + set { + target_ = value; + } + } + + /// Field number for the "game_account_id" field. + public const int GameAccountIdFieldNumber = 2; + private Bgs.Protocol.EntityId gameAccountId_; + public Bgs.Protocol.EntityId GameAccountId { + get { return gameAccountId_; } + set { + gameAccountId_ = value; + } + } + + /// Field number for the "peer" field. + public const int PeerFieldNumber = 4; + private Bgs.Protocol.ProcessId peer_; + public Bgs.Protocol.ProcessId Peer { + get { return peer_; } + set { + peer_ = value; + } + } + + /// Field number for the "account_id" field. + public const int AccountIdFieldNumber = 5; + private Bgs.Protocol.EntityId accountId_; + public Bgs.Protocol.EntityId AccountId { + get { return accountId_; } + set { + accountId_ = value; + } + } + + public override bool Equals(object other) { + return Equals(other as FriendNotification); + } + + public bool Equals(FriendNotification other) { + if (ReferenceEquals(other, null)) { + return false; + } + if (ReferenceEquals(other, this)) { + return true; + } + if (!object.Equals(Target, other.Target)) return false; + if (!object.Equals(GameAccountId, other.GameAccountId)) return false; + if (!object.Equals(Peer, other.Peer)) return false; + if (!object.Equals(AccountId, other.AccountId)) return false; + return true; + } + + public override int GetHashCode() { + int hash = 1; + if (target_ != null) hash ^= Target.GetHashCode(); + if (gameAccountId_ != null) hash ^= GameAccountId.GetHashCode(); + if (peer_ != null) hash ^= Peer.GetHashCode(); + if (accountId_ != null) hash ^= AccountId.GetHashCode(); + return hash; + } + + public override string ToString() { + return pb::JsonFormatter.ToDiagnosticString(this); + } + + public void WriteTo(pb::CodedOutputStream output) { + if (target_ != null) { + output.WriteRawTag(10); + output.WriteMessage(Target); + } + if (gameAccountId_ != null) { + output.WriteRawTag(18); + output.WriteMessage(GameAccountId); + } + if (peer_ != null) { + output.WriteRawTag(34); + output.WriteMessage(Peer); + } + if (accountId_ != null) { + output.WriteRawTag(42); + output.WriteMessage(AccountId); + } + } + + public int CalculateSize() { + int size = 0; + if (target_ != null) { + size += 1 + pb::CodedOutputStream.ComputeMessageSize(Target); + } + if (gameAccountId_ != null) { + size += 1 + pb::CodedOutputStream.ComputeMessageSize(GameAccountId); + } + if (peer_ != null) { + size += 1 + pb::CodedOutputStream.ComputeMessageSize(Peer); + } + if (accountId_ != null) { + size += 1 + pb::CodedOutputStream.ComputeMessageSize(AccountId); + } + return size; + } + + public void MergeFrom(FriendNotification other) { + if (other == null) { + return; + } + if (other.target_ != null) { + if (target_ == null) { + target_ = new Bgs.Protocol.Friends.V1.Friend(); + } + Target.MergeFrom(other.Target); + } + if (other.gameAccountId_ != null) { + if (gameAccountId_ == null) { + gameAccountId_ = new Bgs.Protocol.EntityId(); + } + GameAccountId.MergeFrom(other.GameAccountId); + } + if (other.peer_ != null) { + if (peer_ == null) { + peer_ = new Bgs.Protocol.ProcessId(); + } + Peer.MergeFrom(other.Peer); + } + if (other.accountId_ != null) { + if (accountId_ == null) { + accountId_ = new Bgs.Protocol.EntityId(); + } + AccountId.MergeFrom(other.AccountId); + } + } + + public void MergeFrom(pb::CodedInputStream input) { + uint tag; + while ((tag = input.ReadTag()) != 0) { + switch(tag) { + default: + input.SkipLastField(); + break; + case 10: { + if (target_ == null) { + target_ = new Bgs.Protocol.Friends.V1.Friend(); + } + input.ReadMessage(target_); + break; + } + case 18: { + if (gameAccountId_ == null) { + gameAccountId_ = new Bgs.Protocol.EntityId(); + } + input.ReadMessage(gameAccountId_); + break; + } + case 34: { + if (peer_ == null) { + peer_ = new Bgs.Protocol.ProcessId(); + } + input.ReadMessage(peer_); + break; + } + case 42: { + if (accountId_ == null) { + accountId_ = new Bgs.Protocol.EntityId(); + } + input.ReadMessage(accountId_); + break; + } + } + } + } + + } + + [System.Diagnostics.DebuggerNonUserCodeAttribute()] + public sealed partial class UpdateFriendStateNotification : pb::IMessage { + private static readonly pb::MessageParser _parser = new pb::MessageParser(() => new UpdateFriendStateNotification()); + public static pb::MessageParser Parser { get { return _parser; } } + + public static pbr::MessageDescriptor Descriptor { + get { return Bgs.Protocol.Friends.V1.FriendsServiceReflection.Descriptor.MessageTypes[10]; } + } + + pbr::MessageDescriptor pb::IMessage.Descriptor { + get { return Descriptor; } + } + + public UpdateFriendStateNotification() { + OnConstruction(); + } + + partial void OnConstruction(); + + public UpdateFriendStateNotification(UpdateFriendStateNotification other) : this() { + ChangedFriend = other.changedFriend_ != null ? other.ChangedFriend.Clone() : null; + GameAccountId = other.gameAccountId_ != null ? other.GameAccountId.Clone() : null; + Peer = other.peer_ != null ? other.Peer.Clone() : null; + AccountId = other.accountId_ != null ? other.AccountId.Clone() : null; + } + + public UpdateFriendStateNotification Clone() { + return new UpdateFriendStateNotification(this); + } + + /// Field number for the "changed_friend" field. + public const int ChangedFriendFieldNumber = 1; + private Bgs.Protocol.Friends.V1.Friend changedFriend_; + public Bgs.Protocol.Friends.V1.Friend ChangedFriend { + get { return changedFriend_; } + set { + changedFriend_ = value; + } + } + + /// Field number for the "game_account_id" field. + public const int GameAccountIdFieldNumber = 2; + private Bgs.Protocol.EntityId gameAccountId_; + public Bgs.Protocol.EntityId GameAccountId { + get { return gameAccountId_; } + set { + gameAccountId_ = value; + } + } + + /// Field number for the "peer" field. + public const int PeerFieldNumber = 4; + private Bgs.Protocol.ProcessId peer_; + public Bgs.Protocol.ProcessId Peer { + get { return peer_; } + set { + peer_ = value; + } + } + + /// Field number for the "account_id" field. + public const int AccountIdFieldNumber = 5; + private Bgs.Protocol.EntityId accountId_; + public Bgs.Protocol.EntityId AccountId { + get { return accountId_; } + set { + accountId_ = value; + } + } + + public override bool Equals(object other) { + return Equals(other as UpdateFriendStateNotification); + } + + public bool Equals(UpdateFriendStateNotification other) { + if (ReferenceEquals(other, null)) { + return false; + } + if (ReferenceEquals(other, this)) { + return true; + } + if (!object.Equals(ChangedFriend, other.ChangedFriend)) return false; + if (!object.Equals(GameAccountId, other.GameAccountId)) return false; + if (!object.Equals(Peer, other.Peer)) return false; + if (!object.Equals(AccountId, other.AccountId)) return false; + return true; + } + + public override int GetHashCode() { + int hash = 1; + if (changedFriend_ != null) hash ^= ChangedFriend.GetHashCode(); + if (gameAccountId_ != null) hash ^= GameAccountId.GetHashCode(); + if (peer_ != null) hash ^= Peer.GetHashCode(); + if (accountId_ != null) hash ^= AccountId.GetHashCode(); + return hash; + } + + public override string ToString() { + return pb::JsonFormatter.ToDiagnosticString(this); + } + + public void WriteTo(pb::CodedOutputStream output) { + if (changedFriend_ != null) { + output.WriteRawTag(10); + output.WriteMessage(ChangedFriend); + } + if (gameAccountId_ != null) { + output.WriteRawTag(18); + output.WriteMessage(GameAccountId); + } + if (peer_ != null) { + output.WriteRawTag(34); + output.WriteMessage(Peer); + } + if (accountId_ != null) { + output.WriteRawTag(42); + output.WriteMessage(AccountId); + } + } + + public int CalculateSize() { + int size = 0; + if (changedFriend_ != null) { + size += 1 + pb::CodedOutputStream.ComputeMessageSize(ChangedFriend); + } + if (gameAccountId_ != null) { + size += 1 + pb::CodedOutputStream.ComputeMessageSize(GameAccountId); + } + if (peer_ != null) { + size += 1 + pb::CodedOutputStream.ComputeMessageSize(Peer); + } + if (accountId_ != null) { + size += 1 + pb::CodedOutputStream.ComputeMessageSize(AccountId); + } + return size; + } + + public void MergeFrom(UpdateFriendStateNotification other) { + if (other == null) { + return; + } + if (other.changedFriend_ != null) { + if (changedFriend_ == null) { + changedFriend_ = new Bgs.Protocol.Friends.V1.Friend(); + } + ChangedFriend.MergeFrom(other.ChangedFriend); + } + if (other.gameAccountId_ != null) { + if (gameAccountId_ == null) { + gameAccountId_ = new Bgs.Protocol.EntityId(); + } + GameAccountId.MergeFrom(other.GameAccountId); + } + if (other.peer_ != null) { + if (peer_ == null) { + peer_ = new Bgs.Protocol.ProcessId(); + } + Peer.MergeFrom(other.Peer); + } + if (other.accountId_ != null) { + if (accountId_ == null) { + accountId_ = new Bgs.Protocol.EntityId(); + } + AccountId.MergeFrom(other.AccountId); + } + } + + public void MergeFrom(pb::CodedInputStream input) { + uint tag; + while ((tag = input.ReadTag()) != 0) { + switch(tag) { + default: + input.SkipLastField(); + break; + case 10: { + if (changedFriend_ == null) { + changedFriend_ = new Bgs.Protocol.Friends.V1.Friend(); + } + input.ReadMessage(changedFriend_); + break; + } + case 18: { + if (gameAccountId_ == null) { + gameAccountId_ = new Bgs.Protocol.EntityId(); + } + input.ReadMessage(gameAccountId_); + break; + } + case 34: { + if (peer_ == null) { + peer_ = new Bgs.Protocol.ProcessId(); + } + input.ReadMessage(peer_); + break; + } + case 42: { + if (accountId_ == null) { + accountId_ = new Bgs.Protocol.EntityId(); + } + input.ReadMessage(accountId_); + break; + } + } + } + } + + } + + [System.Diagnostics.DebuggerNonUserCodeAttribute()] + public sealed partial class InvitationNotification : pb::IMessage { + private static readonly pb::MessageParser _parser = new pb::MessageParser(() => new InvitationNotification()); + public static pb::MessageParser Parser { get { return _parser; } } + + public static pbr::MessageDescriptor Descriptor { + get { return Bgs.Protocol.Friends.V1.FriendsServiceReflection.Descriptor.MessageTypes[11]; } + } + + pbr::MessageDescriptor pb::IMessage.Descriptor { + get { return Descriptor; } + } + + public InvitationNotification() { + OnConstruction(); + } + + partial void OnConstruction(); + + public InvitationNotification(InvitationNotification other) : this() { + Invitation = other.invitation_ != null ? other.Invitation.Clone() : null; + GameAccountId = other.gameAccountId_ != null ? other.GameAccountId.Clone() : null; + reason_ = other.reason_; + Peer = other.peer_ != null ? other.Peer.Clone() : null; + AccountId = other.accountId_ != null ? other.AccountId.Clone() : null; + } + + public InvitationNotification Clone() { + return new InvitationNotification(this); + } + + /// Field number for the "invitation" field. + public const int InvitationFieldNumber = 1; + private Bgs.Protocol.Invitation invitation_; + public Bgs.Protocol.Invitation Invitation { + get { return invitation_; } + set { + invitation_ = value; + } + } + + /// Field number for the "game_account_id" field. + public const int GameAccountIdFieldNumber = 2; + private Bgs.Protocol.EntityId gameAccountId_; + public Bgs.Protocol.EntityId GameAccountId { + get { return gameAccountId_; } + set { + gameAccountId_ = value; + } + } + + /// Field number for the "reason" field. + public const int ReasonFieldNumber = 3; + private uint reason_; + public uint Reason { + get { return reason_; } + set { + reason_ = value; + } + } + + /// Field number for the "peer" field. + public const int PeerFieldNumber = 4; + private Bgs.Protocol.ProcessId peer_; + public Bgs.Protocol.ProcessId Peer { + get { return peer_; } + set { + peer_ = value; + } + } + + /// Field number for the "account_id" field. + public const int AccountIdFieldNumber = 5; + private Bgs.Protocol.EntityId accountId_; + public Bgs.Protocol.EntityId AccountId { + get { return accountId_; } + set { + accountId_ = value; + } + } + + public override bool Equals(object other) { + return Equals(other as InvitationNotification); + } + + public bool Equals(InvitationNotification other) { + if (ReferenceEquals(other, null)) { + return false; + } + if (ReferenceEquals(other, this)) { + return true; + } + if (!object.Equals(Invitation, other.Invitation)) return false; + if (!object.Equals(GameAccountId, other.GameAccountId)) return false; + if (Reason != other.Reason) return false; + if (!object.Equals(Peer, other.Peer)) return false; + if (!object.Equals(AccountId, other.AccountId)) return false; + return true; + } + + public override int GetHashCode() { + int hash = 1; + if (invitation_ != null) hash ^= Invitation.GetHashCode(); + if (gameAccountId_ != null) hash ^= GameAccountId.GetHashCode(); + if (Reason != 0) hash ^= Reason.GetHashCode(); + if (peer_ != null) hash ^= Peer.GetHashCode(); + if (accountId_ != null) hash ^= AccountId.GetHashCode(); + return hash; + } + + public override string ToString() { + return pb::JsonFormatter.ToDiagnosticString(this); + } + + public void WriteTo(pb::CodedOutputStream output) { + if (invitation_ != null) { + output.WriteRawTag(10); + output.WriteMessage(Invitation); + } + if (gameAccountId_ != null) { + output.WriteRawTag(18); + output.WriteMessage(GameAccountId); + } + if (Reason != 0) { + output.WriteRawTag(24); + output.WriteUInt32(Reason); + } + if (peer_ != null) { + output.WriteRawTag(34); + output.WriteMessage(Peer); + } + if (accountId_ != null) { + output.WriteRawTag(42); + output.WriteMessage(AccountId); + } + } + + public int CalculateSize() { + int size = 0; + if (invitation_ != null) { + size += 1 + pb::CodedOutputStream.ComputeMessageSize(Invitation); + } + if (gameAccountId_ != null) { + size += 1 + pb::CodedOutputStream.ComputeMessageSize(GameAccountId); + } + if (Reason != 0) { + size += 1 + pb::CodedOutputStream.ComputeUInt32Size(Reason); + } + if (peer_ != null) { + size += 1 + pb::CodedOutputStream.ComputeMessageSize(Peer); + } + if (accountId_ != null) { + size += 1 + pb::CodedOutputStream.ComputeMessageSize(AccountId); + } + return size; + } + + public void MergeFrom(InvitationNotification other) { + if (other == null) { + return; + } + if (other.invitation_ != null) { + if (invitation_ == null) { + invitation_ = new Bgs.Protocol.Invitation(); + } + Invitation.MergeFrom(other.Invitation); + } + if (other.gameAccountId_ != null) { + if (gameAccountId_ == null) { + gameAccountId_ = new Bgs.Protocol.EntityId(); + } + GameAccountId.MergeFrom(other.GameAccountId); + } + if (other.Reason != 0) { + Reason = other.Reason; + } + if (other.peer_ != null) { + if (peer_ == null) { + peer_ = new Bgs.Protocol.ProcessId(); + } + Peer.MergeFrom(other.Peer); + } + if (other.accountId_ != null) { + if (accountId_ == null) { + accountId_ = new Bgs.Protocol.EntityId(); + } + AccountId.MergeFrom(other.AccountId); + } + } + + public void MergeFrom(pb::CodedInputStream input) { + uint tag; + while ((tag = input.ReadTag()) != 0) { + switch(tag) { + default: + input.SkipLastField(); + break; + case 10: { + if (invitation_ == null) { + invitation_ = new Bgs.Protocol.Invitation(); + } + input.ReadMessage(invitation_); + break; + } + case 18: { + if (gameAccountId_ == null) { + gameAccountId_ = new Bgs.Protocol.EntityId(); + } + input.ReadMessage(gameAccountId_); + break; + } + case 24: { + Reason = input.ReadUInt32(); + break; + } + case 34: { + if (peer_ == null) { + peer_ = new Bgs.Protocol.ProcessId(); + } + input.ReadMessage(peer_); + break; + } + case 42: { + if (accountId_ == null) { + accountId_ = new Bgs.Protocol.EntityId(); + } + input.ReadMessage(accountId_); + break; + } + } + } + } + + } + + #endregion + +} + +#endregion Designer generated code diff --git a/Framework/Proto/FriendsTypes.cs b/Framework/Proto/FriendsTypes.cs new file mode 100644 index 000000000..7abc6a42b --- /dev/null +++ b/Framework/Proto/FriendsTypes.cs @@ -0,0 +1,759 @@ +// Generated by the protocol buffer compiler. DO NOT EDIT! +// source: bgs/low/pb/client/friends_types.proto +#pragma warning disable 1591, 0612, 3021 +#region Designer generated code + +using pb = Google.Protobuf; +using pbc = Google.Protobuf.Collections; +using pbr = Google.Protobuf.Reflection; +namespace Bgs.Protocol.Friends.V1 +{ + + /// Holder for reflection information generated from bgs/low/pb/client/friends_types.proto + [System.Diagnostics.DebuggerNonUserCodeAttribute()] + public static partial class FriendsTypesReflection { + + #region Descriptor + /// File descriptor for bgs/low/pb/client/friends_types.proto + public static pbr::FileDescriptor Descriptor { + get { return descriptor; } + } + private static pbr::FileDescriptor descriptor; + + static FriendsTypesReflection() { + byte[] descriptorData = System.Convert.FromBase64String( + string.Concat( + "CiViZ3MvbG93L3BiL2NsaWVudC9mcmllbmRzX3R5cGVzLnByb3RvEhdiZ3Mu", + "cHJvdG9jb2wuZnJpZW5kcy52MRonYmdzL2xvdy9wYi9jbGllbnQvYXR0cmli", + "dXRlX3R5cGVzLnByb3RvGiRiZ3MvbG93L3BiL2NsaWVudC9lbnRpdHlfdHlw", + "ZXMucHJvdG8aKGJncy9sb3cvcGIvY2xpZW50L2ludml0YXRpb25fdHlwZXMu", + "cHJvdG8ixwEKBkZyaWVuZBIqCgphY2NvdW50X2lkGAEgASgLMhYuYmdzLnBy", + "b3RvY29sLkVudGl0eUlkEioKCWF0dHJpYnV0ZRgCIAMoCzIXLmJncy5wcm90", + "b2NvbC5BdHRyaWJ1dGUSEAoEcm9sZRgDIAMoDUICEAESEgoKcHJpdmlsZWdl", + "cxgEIAEoBBIYChBhdHRyaWJ1dGVzX2Vwb2NoGAUgASgEEhEKCWZ1bGxfbmFt", + "ZRgGIAEoCRISCgpiYXR0bGVfdGFnGAcgASgJIoIBChBGcmllbmRJbnZpdGF0", + "aW9uEhYKDmZpcnN0X3JlY2VpdmVkGAEgASgIEhAKBHJvbGUYAiADKA1CAhAB", + "EkQKEWZyaWVuZF9pbnZpdGF0aW9uGGcgASgLMikuYmdzLnByb3RvY29sLmZy", + "aWVuZHMudjEuRnJpZW5kSW52aXRhdGlvbiKgAgoWRnJpZW5kSW52aXRhdGlv", + "blBhcmFtcxIUCgx0YXJnZXRfZW1haWwYASABKAkSGQoRdGFyZ2V0X2JhdHRs", + "ZV90YWcYAiABKAkSGgoSaW52aXRlcl9iYXR0bGVfdGFnGAMgASgJEhkKEWlu", + "dml0ZXJfZnVsbF9uYW1lGAQgASgJEhwKFGludml0ZWVfZGlzcGxheV9uYW1l", + "GAUgASgJEhAKBHJvbGUYBiADKA1CAhABEiYKGHByZXZpb3VzX3JvbGVfZGVw", + "cmVjYXRlZBgHIAMoDUIEEAEYARJGCg1mcmllbmRfcGFyYW1zGGcgASgLMi8u", + "YmdzLnByb3RvY29sLmZyaWVuZHMudjEuRnJpZW5kSW52aXRhdGlvblBhcmFt", + "c0IvChhibmV0LnByb3RvY29sLmZyaWVuZHMudjFCEUZyaWVuZHNUeXBlc1By", + "b3RvSAJiBnByb3RvMw==")); + descriptor = pbr::FileDescriptor.FromGeneratedCode(descriptorData, + new pbr::FileDescriptor[] { Bgs.Protocol.AttributeTypesReflection.Descriptor, Bgs.Protocol.EntityTypesReflection.Descriptor, Bgs.Protocol.InvitationTypesReflection.Descriptor, }, + new pbr::GeneratedClrTypeInfo(null, new pbr::GeneratedClrTypeInfo[] { + new pbr::GeneratedClrTypeInfo(typeof(Bgs.Protocol.Friends.V1.Friend), Bgs.Protocol.Friends.V1.Friend.Parser, new[]{ "AccountId", "Attribute", "Role", "Privileges", "AttributesEpoch", "FullName", "BattleTag" }, null, null, null), + new pbr::GeneratedClrTypeInfo(typeof(Bgs.Protocol.Friends.V1.FriendInvitation), Bgs.Protocol.Friends.V1.FriendInvitation.Parser, new[]{ "FirstReceived", "Role", "FriendInvitation_" }, null, null, null), + new pbr::GeneratedClrTypeInfo(typeof(Bgs.Protocol.Friends.V1.FriendInvitationParams), Bgs.Protocol.Friends.V1.FriendInvitationParams.Parser, new[]{ "TargetEmail", "TargetBattleTag", "InviterBattleTag", "InviterFullName", "InviteeDisplayName", "Role", "PreviousRoleDeprecated", "FriendParams" }, null, null, null) + })); + } + #endregion + + } + #region Messages + [System.Diagnostics.DebuggerNonUserCodeAttribute()] + public sealed partial class Friend : pb::IMessage { + private static readonly pb::MessageParser _parser = new pb::MessageParser(() => new Friend()); + public static pb::MessageParser Parser { get { return _parser; } } + + public static pbr::MessageDescriptor Descriptor { + get { return Bgs.Protocol.Friends.V1.FriendsTypesReflection.Descriptor.MessageTypes[0]; } + } + + pbr::MessageDescriptor pb::IMessage.Descriptor { + get { return Descriptor; } + } + + public Friend() { + OnConstruction(); + } + + partial void OnConstruction(); + + public Friend(Friend other) : this() { + AccountId = other.accountId_ != null ? other.AccountId.Clone() : null; + attribute_ = other.attribute_.Clone(); + role_ = other.role_.Clone(); + privileges_ = other.privileges_; + attributesEpoch_ = other.attributesEpoch_; + fullName_ = other.fullName_; + battleTag_ = other.battleTag_; + } + + public Friend Clone() { + return new Friend(this); + } + + /// Field number for the "account_id" field. + public const int AccountIdFieldNumber = 1; + private Bgs.Protocol.EntityId accountId_; + public Bgs.Protocol.EntityId AccountId { + get { return accountId_; } + set { + accountId_ = value; + } + } + + /// Field number for the "attribute" field. + public const int AttributeFieldNumber = 2; + private static readonly pb::FieldCodec _repeated_attribute_codec + = pb::FieldCodec.ForMessage(18, Bgs.Protocol.Attribute.Parser); + private readonly pbc::RepeatedField attribute_ = new pbc::RepeatedField(); + public pbc::RepeatedField Attribute { + get { return attribute_; } + } + + /// Field number for the "role" field. + public const int RoleFieldNumber = 3; + private static readonly pb::FieldCodec _repeated_role_codec + = pb::FieldCodec.ForUInt32(26); + private readonly pbc::RepeatedField role_ = new pbc::RepeatedField(); + public pbc::RepeatedField Role { + get { return role_; } + } + + /// Field number for the "privileges" field. + public const int PrivilegesFieldNumber = 4; + private ulong privileges_; + public ulong Privileges { + get { return privileges_; } + set { + privileges_ = value; + } + } + + /// Field number for the "attributes_epoch" field. + public const int AttributesEpochFieldNumber = 5; + private ulong attributesEpoch_; + public ulong AttributesEpoch { + get { return attributesEpoch_; } + set { + attributesEpoch_ = value; + } + } + + /// Field number for the "full_name" field. + public const int FullNameFieldNumber = 6; + private string fullName_ = ""; + public string FullName { + get { return fullName_; } + set { + fullName_ = pb::ProtoPreconditions.CheckNotNull(value, "value"); + } + } + + /// Field number for the "battle_tag" field. + public const int BattleTagFieldNumber = 7; + private string battleTag_ = ""; + public string BattleTag { + get { return battleTag_; } + set { + battleTag_ = pb::ProtoPreconditions.CheckNotNull(value, "value"); + } + } + + public override bool Equals(object other) { + return Equals(other as Friend); + } + + public bool Equals(Friend other) { + if (ReferenceEquals(other, null)) { + return false; + } + if (ReferenceEquals(other, this)) { + return true; + } + if (!object.Equals(AccountId, other.AccountId)) return false; + if(!attribute_.Equals(other.attribute_)) return false; + if(!role_.Equals(other.role_)) return false; + if (Privileges != other.Privileges) return false; + if (AttributesEpoch != other.AttributesEpoch) return false; + if (FullName != other.FullName) return false; + if (BattleTag != other.BattleTag) return false; + return true; + } + + public override int GetHashCode() { + int hash = 1; + if (accountId_ != null) hash ^= AccountId.GetHashCode(); + hash ^= attribute_.GetHashCode(); + hash ^= role_.GetHashCode(); + if (Privileges != 0UL) hash ^= Privileges.GetHashCode(); + if (AttributesEpoch != 0UL) hash ^= AttributesEpoch.GetHashCode(); + if (FullName.Length != 0) hash ^= FullName.GetHashCode(); + if (BattleTag.Length != 0) hash ^= BattleTag.GetHashCode(); + return hash; + } + + public override string ToString() { + return pb::JsonFormatter.ToDiagnosticString(this); + } + + public void WriteTo(pb::CodedOutputStream output) { + if (accountId_ != null) { + output.WriteRawTag(10); + output.WriteMessage(AccountId); + } + attribute_.WriteTo(output, _repeated_attribute_codec); + role_.WriteTo(output, _repeated_role_codec); + if (Privileges != 0UL) { + output.WriteRawTag(32); + output.WriteUInt64(Privileges); + } + if (AttributesEpoch != 0UL) { + output.WriteRawTag(40); + output.WriteUInt64(AttributesEpoch); + } + if (FullName.Length != 0) { + output.WriteRawTag(50); + output.WriteString(FullName); + } + if (BattleTag.Length != 0) { + output.WriteRawTag(58); + output.WriteString(BattleTag); + } + } + + public int CalculateSize() { + int size = 0; + if (accountId_ != null) { + size += 1 + pb::CodedOutputStream.ComputeMessageSize(AccountId); + } + size += attribute_.CalculateSize(_repeated_attribute_codec); + size += role_.CalculateSize(_repeated_role_codec); + if (Privileges != 0UL) { + size += 1 + pb::CodedOutputStream.ComputeUInt64Size(Privileges); + } + if (AttributesEpoch != 0UL) { + size += 1 + pb::CodedOutputStream.ComputeUInt64Size(AttributesEpoch); + } + if (FullName.Length != 0) { + size += 1 + pb::CodedOutputStream.ComputeStringSize(FullName); + } + if (BattleTag.Length != 0) { + size += 1 + pb::CodedOutputStream.ComputeStringSize(BattleTag); + } + return size; + } + + public void MergeFrom(Friend other) { + if (other == null) { + return; + } + if (other.accountId_ != null) { + if (accountId_ == null) { + accountId_ = new Bgs.Protocol.EntityId(); + } + AccountId.MergeFrom(other.AccountId); + } + attribute_.Add(other.attribute_); + role_.Add(other.role_); + if (other.Privileges != 0UL) { + Privileges = other.Privileges; + } + if (other.AttributesEpoch != 0UL) { + AttributesEpoch = other.AttributesEpoch; + } + if (other.FullName.Length != 0) { + FullName = other.FullName; + } + if (other.BattleTag.Length != 0) { + BattleTag = other.BattleTag; + } + } + + public void MergeFrom(pb::CodedInputStream input) { + uint tag; + while ((tag = input.ReadTag()) != 0) { + switch(tag) { + default: + input.SkipLastField(); + break; + case 10: { + if (accountId_ == null) { + accountId_ = new Bgs.Protocol.EntityId(); + } + input.ReadMessage(accountId_); + break; + } + case 18: { + attribute_.AddEntriesFrom(input, _repeated_attribute_codec); + break; + } + case 26: + case 24: { + role_.AddEntriesFrom(input, _repeated_role_codec); + break; + } + case 32: { + Privileges = input.ReadUInt64(); + break; + } + case 40: { + AttributesEpoch = input.ReadUInt64(); + break; + } + case 50: { + FullName = input.ReadString(); + break; + } + case 58: { + BattleTag = input.ReadString(); + break; + } + } + } + } + + } + + [System.Diagnostics.DebuggerNonUserCodeAttribute()] + public sealed partial class FriendInvitation : pb::IMessage { + private static readonly pb::MessageParser _parser = new pb::MessageParser(() => new FriendInvitation()); + public static pb::MessageParser Parser { get { return _parser; } } + + public static pbr::MessageDescriptor Descriptor { + get { return Bgs.Protocol.Friends.V1.FriendsTypesReflection.Descriptor.MessageTypes[1]; } + } + + pbr::MessageDescriptor pb::IMessage.Descriptor { + get { return Descriptor; } + } + + public FriendInvitation() { + OnConstruction(); + } + + partial void OnConstruction(); + + public FriendInvitation(FriendInvitation other) : this() { + firstReceived_ = other.firstReceived_; + role_ = other.role_.Clone(); + FriendInvitation_ = other.friendInvitation_ != null ? other.FriendInvitation_.Clone() : null; + } + + public FriendInvitation Clone() { + return new FriendInvitation(this); + } + + /// Field number for the "first_received" field. + public const int FirstReceivedFieldNumber = 1; + private bool firstReceived_; + public bool FirstReceived { + get { return firstReceived_; } + set { + firstReceived_ = value; + } + } + + /// Field number for the "role" field. + public const int RoleFieldNumber = 2; + private static readonly pb::FieldCodec _repeated_role_codec + = pb::FieldCodec.ForUInt32(18); + private readonly pbc::RepeatedField role_ = new pbc::RepeatedField(); + public pbc::RepeatedField Role { + get { return role_; } + } + + /// Field number for the "friend_invitation" field. + public const int FriendInvitation_FieldNumber = 103; + private Bgs.Protocol.Friends.V1.FriendInvitation friendInvitation_; + public Bgs.Protocol.Friends.V1.FriendInvitation FriendInvitation_ { + get { return friendInvitation_; } + set { + friendInvitation_ = value; + } + } + + public override bool Equals(object other) { + return Equals(other as FriendInvitation); + } + + public bool Equals(FriendInvitation other) { + if (ReferenceEquals(other, null)) { + return false; + } + if (ReferenceEquals(other, this)) { + return true; + } + if (FirstReceived != other.FirstReceived) return false; + if(!role_.Equals(other.role_)) return false; + if (!object.Equals(FriendInvitation_, other.FriendInvitation_)) return false; + return true; + } + + public override int GetHashCode() { + int hash = 1; + if (FirstReceived != false) hash ^= FirstReceived.GetHashCode(); + hash ^= role_.GetHashCode(); + if (friendInvitation_ != null) hash ^= FriendInvitation_.GetHashCode(); + return hash; + } + + public override string ToString() { + return pb::JsonFormatter.ToDiagnosticString(this); + } + + public void WriteTo(pb::CodedOutputStream output) { + if (FirstReceived != false) { + output.WriteRawTag(8); + output.WriteBool(FirstReceived); + } + role_.WriteTo(output, _repeated_role_codec); + if (friendInvitation_ != null) { + output.WriteRawTag(186, 6); + output.WriteMessage(FriendInvitation_); + } + } + + public int CalculateSize() { + int size = 0; + if (FirstReceived != false) { + size += 1 + 1; + } + size += role_.CalculateSize(_repeated_role_codec); + if (friendInvitation_ != null) { + size += 2 + pb::CodedOutputStream.ComputeMessageSize(FriendInvitation_); + } + return size; + } + + public void MergeFrom(FriendInvitation other) { + if (other == null) { + return; + } + if (other.FirstReceived != false) { + FirstReceived = other.FirstReceived; + } + role_.Add(other.role_); + if (other.friendInvitation_ != null) { + if (friendInvitation_ == null) { + friendInvitation_ = new Bgs.Protocol.Friends.V1.FriendInvitation(); + } + FriendInvitation_.MergeFrom(other.FriendInvitation_); + } + } + + public void MergeFrom(pb::CodedInputStream input) { + uint tag; + while ((tag = input.ReadTag()) != 0) { + switch(tag) { + default: + input.SkipLastField(); + break; + case 8: { + FirstReceived = input.ReadBool(); + break; + } + case 18: + case 16: { + role_.AddEntriesFrom(input, _repeated_role_codec); + break; + } + case 826: { + if (friendInvitation_ == null) { + friendInvitation_ = new Bgs.Protocol.Friends.V1.FriendInvitation(); + } + input.ReadMessage(friendInvitation_); + break; + } + } + } + } + + } + + [System.Diagnostics.DebuggerNonUserCodeAttribute()] + public sealed partial class FriendInvitationParams : pb::IMessage { + private static readonly pb::MessageParser _parser = new pb::MessageParser(() => new FriendInvitationParams()); + public static pb::MessageParser Parser { get { return _parser; } } + + public static pbr::MessageDescriptor Descriptor { + get { return Bgs.Protocol.Friends.V1.FriendsTypesReflection.Descriptor.MessageTypes[2]; } + } + + pbr::MessageDescriptor pb::IMessage.Descriptor { + get { return Descriptor; } + } + + public FriendInvitationParams() { + OnConstruction(); + } + + partial void OnConstruction(); + + public FriendInvitationParams(FriendInvitationParams other) : this() { + targetEmail_ = other.targetEmail_; + targetBattleTag_ = other.targetBattleTag_; + inviterBattleTag_ = other.inviterBattleTag_; + inviterFullName_ = other.inviterFullName_; + inviteeDisplayName_ = other.inviteeDisplayName_; + role_ = other.role_.Clone(); + previousRoleDeprecated_ = other.previousRoleDeprecated_.Clone(); + FriendParams = other.friendParams_ != null ? other.FriendParams.Clone() : null; + } + + public FriendInvitationParams Clone() { + return new FriendInvitationParams(this); + } + + /// Field number for the "target_email" field. + public const int TargetEmailFieldNumber = 1; + private string targetEmail_ = ""; + public string TargetEmail { + get { return targetEmail_; } + set { + targetEmail_ = pb::ProtoPreconditions.CheckNotNull(value, "value"); + } + } + + /// Field number for the "target_battle_tag" field. + public const int TargetBattleTagFieldNumber = 2; + private string targetBattleTag_ = ""; + public string TargetBattleTag { + get { return targetBattleTag_; } + set { + targetBattleTag_ = pb::ProtoPreconditions.CheckNotNull(value, "value"); + } + } + + /// Field number for the "inviter_battle_tag" field. + public const int InviterBattleTagFieldNumber = 3; + private string inviterBattleTag_ = ""; + public string InviterBattleTag { + get { return inviterBattleTag_; } + set { + inviterBattleTag_ = pb::ProtoPreconditions.CheckNotNull(value, "value"); + } + } + + /// Field number for the "inviter_full_name" field. + public const int InviterFullNameFieldNumber = 4; + private string inviterFullName_ = ""; + public string InviterFullName { + get { return inviterFullName_; } + set { + inviterFullName_ = pb::ProtoPreconditions.CheckNotNull(value, "value"); + } + } + + /// Field number for the "invitee_display_name" field. + public const int InviteeDisplayNameFieldNumber = 5; + private string inviteeDisplayName_ = ""; + public string InviteeDisplayName { + get { return inviteeDisplayName_; } + set { + inviteeDisplayName_ = pb::ProtoPreconditions.CheckNotNull(value, "value"); + } + } + + /// Field number for the "role" field. + public const int RoleFieldNumber = 6; + private static readonly pb::FieldCodec _repeated_role_codec + = pb::FieldCodec.ForUInt32(50); + private readonly pbc::RepeatedField role_ = new pbc::RepeatedField(); + public pbc::RepeatedField Role { + get { return role_; } + } + + /// Field number for the "previous_role_deprecated" field. + public const int PreviousRoleDeprecatedFieldNumber = 7; + private static readonly pb::FieldCodec _repeated_previousRoleDeprecated_codec + = pb::FieldCodec.ForUInt32(58); + private readonly pbc::RepeatedField previousRoleDeprecated_ = new pbc::RepeatedField(); + [System.ObsoleteAttribute()] + public pbc::RepeatedField PreviousRoleDeprecated { + get { return previousRoleDeprecated_; } + } + + /// Field number for the "friend_params" field. + public const int FriendParamsFieldNumber = 103; + private Bgs.Protocol.Friends.V1.FriendInvitationParams friendParams_; + public Bgs.Protocol.Friends.V1.FriendInvitationParams FriendParams { + get { return friendParams_; } + set { + friendParams_ = value; + } + } + + public override bool Equals(object other) { + return Equals(other as FriendInvitationParams); + } + + public bool Equals(FriendInvitationParams other) { + if (ReferenceEquals(other, null)) { + return false; + } + if (ReferenceEquals(other, this)) { + return true; + } + if (TargetEmail != other.TargetEmail) return false; + if (TargetBattleTag != other.TargetBattleTag) return false; + if (InviterBattleTag != other.InviterBattleTag) return false; + if (InviterFullName != other.InviterFullName) return false; + if (InviteeDisplayName != other.InviteeDisplayName) return false; + if(!role_.Equals(other.role_)) return false; + if(!previousRoleDeprecated_.Equals(other.previousRoleDeprecated_)) return false; + if (!object.Equals(FriendParams, other.FriendParams)) return false; + return true; + } + + public override int GetHashCode() { + int hash = 1; + if (TargetEmail.Length != 0) hash ^= TargetEmail.GetHashCode(); + if (TargetBattleTag.Length != 0) hash ^= TargetBattleTag.GetHashCode(); + if (InviterBattleTag.Length != 0) hash ^= InviterBattleTag.GetHashCode(); + if (InviterFullName.Length != 0) hash ^= InviterFullName.GetHashCode(); + if (InviteeDisplayName.Length != 0) hash ^= InviteeDisplayName.GetHashCode(); + hash ^= role_.GetHashCode(); + hash ^= previousRoleDeprecated_.GetHashCode(); + if (friendParams_ != null) hash ^= FriendParams.GetHashCode(); + return hash; + } + + public override string ToString() { + return pb::JsonFormatter.ToDiagnosticString(this); + } + + public void WriteTo(pb::CodedOutputStream output) { + if (TargetEmail.Length != 0) { + output.WriteRawTag(10); + output.WriteString(TargetEmail); + } + if (TargetBattleTag.Length != 0) { + output.WriteRawTag(18); + output.WriteString(TargetBattleTag); + } + if (InviterBattleTag.Length != 0) { + output.WriteRawTag(26); + output.WriteString(InviterBattleTag); + } + if (InviterFullName.Length != 0) { + output.WriteRawTag(34); + output.WriteString(InviterFullName); + } + if (InviteeDisplayName.Length != 0) { + output.WriteRawTag(42); + output.WriteString(InviteeDisplayName); + } + role_.WriteTo(output, _repeated_role_codec); + previousRoleDeprecated_.WriteTo(output, _repeated_previousRoleDeprecated_codec); + if (friendParams_ != null) { + output.WriteRawTag(186, 6); + output.WriteMessage(FriendParams); + } + } + + public int CalculateSize() { + int size = 0; + if (TargetEmail.Length != 0) { + size += 1 + pb::CodedOutputStream.ComputeStringSize(TargetEmail); + } + if (TargetBattleTag.Length != 0) { + size += 1 + pb::CodedOutputStream.ComputeStringSize(TargetBattleTag); + } + if (InviterBattleTag.Length != 0) { + size += 1 + pb::CodedOutputStream.ComputeStringSize(InviterBattleTag); + } + if (InviterFullName.Length != 0) { + size += 1 + pb::CodedOutputStream.ComputeStringSize(InviterFullName); + } + if (InviteeDisplayName.Length != 0) { + size += 1 + pb::CodedOutputStream.ComputeStringSize(InviteeDisplayName); + } + size += role_.CalculateSize(_repeated_role_codec); + size += previousRoleDeprecated_.CalculateSize(_repeated_previousRoleDeprecated_codec); + if (friendParams_ != null) { + size += 2 + pb::CodedOutputStream.ComputeMessageSize(FriendParams); + } + return size; + } + + public void MergeFrom(FriendInvitationParams other) { + if (other == null) { + return; + } + if (other.TargetEmail.Length != 0) { + TargetEmail = other.TargetEmail; + } + if (other.TargetBattleTag.Length != 0) { + TargetBattleTag = other.TargetBattleTag; + } + if (other.InviterBattleTag.Length != 0) { + InviterBattleTag = other.InviterBattleTag; + } + if (other.InviterFullName.Length != 0) { + InviterFullName = other.InviterFullName; + } + if (other.InviteeDisplayName.Length != 0) { + InviteeDisplayName = other.InviteeDisplayName; + } + role_.Add(other.role_); + previousRoleDeprecated_.Add(other.previousRoleDeprecated_); + if (other.friendParams_ != null) { + if (friendParams_ == null) { + friendParams_ = new Bgs.Protocol.Friends.V1.FriendInvitationParams(); + } + FriendParams.MergeFrom(other.FriendParams); + } + } + + public void MergeFrom(pb::CodedInputStream input) { + uint tag; + while ((tag = input.ReadTag()) != 0) { + switch(tag) { + default: + input.SkipLastField(); + break; + case 10: { + TargetEmail = input.ReadString(); + break; + } + case 18: { + TargetBattleTag = input.ReadString(); + break; + } + case 26: { + InviterBattleTag = input.ReadString(); + break; + } + case 34: { + InviterFullName = input.ReadString(); + break; + } + case 42: { + InviteeDisplayName = input.ReadString(); + break; + } + case 50: + case 48: { + role_.AddEntriesFrom(input, _repeated_role_codec); + break; + } + case 58: + case 56: { + previousRoleDeprecated_.AddEntriesFrom(input, _repeated_previousRoleDeprecated_codec); + break; + } + case 826: { + if (friendParams_ == null) { + friendParams_ = new Bgs.Protocol.Friends.V1.FriendInvitationParams(); + } + input.ReadMessage(friendParams_); + break; + } + } + } + } + + } + + #endregion + +} + +#endregion Designer generated code diff --git a/Framework/Proto/GameUtilitiesService.cs b/Framework/Proto/GameUtilitiesService.cs new file mode 100644 index 000000000..a7778bfb3 --- /dev/null +++ b/Framework/Proto/GameUtilitiesService.cs @@ -0,0 +1,2398 @@ +// Generated by the protocol buffer compiler. DO NOT EDIT! +// source: bgs/low/pb/client/game_utilities_service.proto +#pragma warning disable 1591, 0612, 3021 +#region Designer generated code + +using pb = Google.Protobuf; +using pbc = Google.Protobuf.Collections; +using pbr = Google.Protobuf.Reflection; +namespace Bgs.Protocol.GameUtilities.V1 +{ + /// Holder for reflection information generated from bgs/low/pb/client/game_utilities_service.proto + [System.Diagnostics.DebuggerNonUserCodeAttribute()] + public static partial class GameUtilitiesServiceReflection + { + + #region Descriptor + /// File descriptor for bgs/low/pb/client/game_utilities_service.proto + public static pbr::FileDescriptor Descriptor + { + get { return descriptor; } + } + private static pbr::FileDescriptor descriptor; + + static GameUtilitiesServiceReflection() + { + byte[] descriptorData = System.Convert.FromBase64String( + string.Concat( + "Ci5iZ3MvbG93L3BiL2NsaWVudC9nYW1lX3V0aWxpdGllc19zZXJ2aWNlLnBy", + "b3RvEh5iZ3MucHJvdG9jb2wuZ2FtZV91dGlsaXRpZXMudjEaJ2Jncy9sb3cv", + "cGIvY2xpZW50L2F0dHJpYnV0ZV90eXBlcy5wcm90bxosYmdzL2xvdy9wYi9j", + "bGllbnQvY29udGVudF9oYW5kbGVfdHlwZXMucHJvdG8aJGJncy9sb3cvcGIv", + "Y2xpZW50L2VudGl0eV90eXBlcy5wcm90bxosYmdzL2xvdy9wYi9jbGllbnQv", + "Z2FtZV91dGlsaXRpZXNfdHlwZXMucHJvdG8aIWJncy9sb3cvcGIvY2xpZW50", + "L3JwY190eXBlcy5wcm90byKRAgoNQ2xpZW50UmVxdWVzdBIqCglhdHRyaWJ1", + "dGUYASADKAsyFy5iZ3MucHJvdG9jb2wuQXR0cmlidXRlEiUKBGhvc3QYAiAB", + "KAsyFy5iZ3MucHJvdG9jb2wuUHJvY2Vzc0lkEioKCmFjY291bnRfaWQYAyAB", + "KAsyFi5iZ3MucHJvdG9jb2wuRW50aXR5SWQSLwoPZ2FtZV9hY2NvdW50X2lk", + "GAQgASgLMhYuYmdzLnByb3RvY29sLkVudGl0eUlkEg8KB3Byb2dyYW0YBSAB", + "KAcSPwoLY2xpZW50X2luZm8YBiABKAsyKi5iZ3MucHJvdG9jb2wuZ2FtZV91", + "dGlsaXRpZXMudjEuQ2xpZW50SW5mbyI8Cg5DbGllbnRSZXNwb25zZRIqCglh", + "dHRyaWJ1dGUYASADKAsyFy5iZ3MucHJvdG9jb2wuQXR0cmlidXRlInMKDVNl", + "cnZlclJlcXVlc3QSKgoJYXR0cmlidXRlGAEgAygLMhcuYmdzLnByb3RvY29s", + "LkF0dHJpYnV0ZRIPCgdwcm9ncmFtGAIgASgHEiUKBGhvc3QYAyABKAsyFy5i", + "Z3MucHJvdG9jb2wuUHJvY2Vzc0lkIjwKDlNlcnZlclJlc3BvbnNlEioKCWF0", + "dHJpYnV0ZRgBIAMoCzIXLmJncy5wcm90b2NvbC5BdHRyaWJ1dGUixwEKHVBy", + "ZXNlbmNlQ2hhbm5lbENyZWF0ZWRSZXF1ZXN0EiIKAmlkGAEgASgLMhYuYmdz", + "LnByb3RvY29sLkVudGl0eUlkEi8KD2dhbWVfYWNjb3VudF9pZBgDIAEoCzIW", + "LmJncy5wcm90b2NvbC5FbnRpdHlJZBIqCgphY2NvdW50X2lkGAQgASgLMhYu", + "YmdzLnByb3RvY29sLkVudGl0eUlkEiUKBGhvc3QYBSABKAsyFy5iZ3MucHJv", + "dG9jb2wuUHJvY2Vzc0lkIo0BChlHZXRQbGF5ZXJWYXJpYWJsZXNSZXF1ZXN0", + "EkkKEHBsYXllcl92YXJpYWJsZXMYASADKAsyLy5iZ3MucHJvdG9jb2wuZ2Ft", + "ZV91dGlsaXRpZXMudjEuUGxheWVyVmFyaWFibGVzEiUKBGhvc3QYAiABKAsy", + "Fy5iZ3MucHJvdG9jb2wuUHJvY2Vzc0lkImcKGkdldFBsYXllclZhcmlhYmxl", + "c1Jlc3BvbnNlEkkKEHBsYXllcl92YXJpYWJsZXMYASADKAsyLy5iZ3MucHJv", + "dG9jb2wuZ2FtZV91dGlsaXRpZXMudjEuUGxheWVyVmFyaWFibGVzIosBCh1H", + "YW1lQWNjb3VudE9ubGluZU5vdGlmaWNhdGlvbhIvCg9nYW1lX2FjY291bnRf", + "aWQYASABKAsyFi5iZ3MucHJvdG9jb2wuRW50aXR5SWQSJQoEaG9zdBgCIAEo", + "CzIXLmJncy5wcm90b2NvbC5Qcm9jZXNzSWQSEgoKc2Vzc2lvbl9pZBgDIAEo", + "CSKMAQoeR2FtZUFjY291bnRPZmZsaW5lTm90aWZpY2F0aW9uEi8KD2dhbWVf", + "YWNjb3VudF9pZBgBIAEoCzIWLmJncy5wcm90b2NvbC5FbnRpdHlJZBIlCgRo", + "b3N0GAIgASgLMhcuYmdzLnByb3RvY29sLlByb2Nlc3NJZBISCgpzZXNzaW9u", + "X2lkGAMgASgJIkMKGkdldEFjaGlldmVtZW50c0ZpbGVSZXF1ZXN0EiUKBGhv", + "c3QYASABKAsyFy5iZ3MucHJvdG9jb2wuUHJvY2Vzc0lkIlIKG0dldEFjaGll", + "dmVtZW50c0ZpbGVSZXNwb25zZRIzCg5jb250ZW50X2hhbmRsZRgBIAEoCzIb", + "LmJncy5wcm90b2NvbC5Db250ZW50SGFuZGxlInMKH0dldEFsbFZhbHVlc0Zv", + "ckF0dHJpYnV0ZVJlcXVlc3QSFQoNYXR0cmlidXRlX2tleRgBIAEoCRIoCghh", + "Z2VudF9pZBgCIAEoCzIWLmJncy5wcm90b2NvbC5FbnRpdHlJZBIPCgdwcm9n", + "cmFtGAUgASgHIlIKIEdldEFsbFZhbHVlc0ZvckF0dHJpYnV0ZVJlc3BvbnNl", + "Ei4KD2F0dHJpYnV0ZV92YWx1ZRgBIAMoCzIVLmJncy5wcm90b2NvbC5WYXJp", + "YW50MpYIChRHYW1lVXRpbGl0aWVzU2VydmljZRJ1ChRQcm9jZXNzQ2xpZW50", + "UmVxdWVzdBItLmJncy5wcm90b2NvbC5nYW1lX3V0aWxpdGllcy52MS5DbGll", + "bnRSZXF1ZXN0Gi4uYmdzLnByb3RvY29sLmdhbWVfdXRpbGl0aWVzLnYxLkNs", + "aWVudFJlc3BvbnNlEm0KFlByZXNlbmNlQ2hhbm5lbENyZWF0ZWQSPS5iZ3Mu", + "cHJvdG9jb2wuZ2FtZV91dGlsaXRpZXMudjEuUHJlc2VuY2VDaGFubmVsQ3Jl", + "YXRlZFJlcXVlc3QaFC5iZ3MucHJvdG9jb2wuTm9EYXRhEosBChJHZXRQbGF5", + "ZXJWYXJpYWJsZXMSOS5iZ3MucHJvdG9jb2wuZ2FtZV91dGlsaXRpZXMudjEu", + "R2V0UGxheWVyVmFyaWFibGVzUmVxdWVzdBo6LmJncy5wcm90b2NvbC5nYW1l", + "X3V0aWxpdGllcy52MS5HZXRQbGF5ZXJWYXJpYWJsZXNSZXNwb25zZRJ1ChRQ", + "cm9jZXNzU2VydmVyUmVxdWVzdBItLmJncy5wcm90b2NvbC5nYW1lX3V0aWxp", + "dGllcy52MS5TZXJ2ZXJSZXF1ZXN0Gi4uYmdzLnByb3RvY29sLmdhbWVfdXRp", + "bGl0aWVzLnYxLlNlcnZlclJlc3BvbnNlEm8KE09uR2FtZUFjY291bnRPbmxp", + "bmUSPS5iZ3MucHJvdG9jb2wuZ2FtZV91dGlsaXRpZXMudjEuR2FtZUFjY291", + "bnRPbmxpbmVOb3RpZmljYXRpb24aGS5iZ3MucHJvdG9jb2wuTk9fUkVTUE9O", + "U0UScQoUT25HYW1lQWNjb3VudE9mZmxpbmUSPi5iZ3MucHJvdG9jb2wuZ2Ft", + "ZV91dGlsaXRpZXMudjEuR2FtZUFjY291bnRPZmZsaW5lTm90aWZpY2F0aW9u", + "GhkuYmdzLnByb3RvY29sLk5PX1JFU1BPTlNFEo4BChNHZXRBY2hpZXZlbWVu", + "dHNGaWxlEjouYmdzLnByb3RvY29sLmdhbWVfdXRpbGl0aWVzLnYxLkdldEFj", + "aGlldmVtZW50c0ZpbGVSZXF1ZXN0GjsuYmdzLnByb3RvY29sLmdhbWVfdXRp", + "bGl0aWVzLnYxLkdldEFjaGlldmVtZW50c0ZpbGVSZXNwb25zZRKdAQoYR2V0", + "QWxsVmFsdWVzRm9yQXR0cmlidXRlEj8uYmdzLnByb3RvY29sLmdhbWVfdXRp", + "bGl0aWVzLnYxLkdldEFsbFZhbHVlc0ZvckF0dHJpYnV0ZVJlcXVlc3QaQC5i", + "Z3MucHJvdG9jb2wuZ2FtZV91dGlsaXRpZXMudjEuR2V0QWxsVmFsdWVzRm9y", + "QXR0cmlidXRlUmVzcG9uc2VCRAofYm5ldC5wcm90b2NvbC5nYW1lX3V0aWxp", + "dGllcy52MUIZR2FtZVV0aWxpdGllc1NlcnZpY2VQcm90b0gCgAEAiAEBYgZw", + "cm90bzM=")); + descriptor = pbr::FileDescriptor.FromGeneratedCode(descriptorData, + new pbr::FileDescriptor[] { Bgs.Protocol.AttributeTypesReflection.Descriptor, Bgs.Protocol.ContentHandleTypesReflection.Descriptor, Bgs.Protocol.EntityTypesReflection.Descriptor, Bgs.Protocol.GameUtilities.V1.GameUtilitiesTypesReflection.Descriptor, Bgs.Protocol.RpcTypesReflection.Descriptor, }, + new pbr::GeneratedClrTypeInfo(null, new pbr::GeneratedClrTypeInfo[] { + new pbr::GeneratedClrTypeInfo(typeof(Bgs.Protocol.GameUtilities.V1.ClientRequest), Bgs.Protocol.GameUtilities.V1.ClientRequest.Parser, new[]{ "Attribute", "Host", "AccountId", "GameAccountId", "Program", "ClientInfo" }, null, null, null), + new pbr::GeneratedClrTypeInfo(typeof(Bgs.Protocol.GameUtilities.V1.ClientResponse), Bgs.Protocol.GameUtilities.V1.ClientResponse.Parser, new[]{ "Attribute" }, null, null, null), + new pbr::GeneratedClrTypeInfo(typeof(Bgs.Protocol.GameUtilities.V1.ServerRequest), Bgs.Protocol.GameUtilities.V1.ServerRequest.Parser, new[]{ "Attribute", "Program", "Host" }, null, null, null), + new pbr::GeneratedClrTypeInfo(typeof(Bgs.Protocol.GameUtilities.V1.ServerResponse), Bgs.Protocol.GameUtilities.V1.ServerResponse.Parser, new[]{ "Attribute" }, null, null, null), + new pbr::GeneratedClrTypeInfo(typeof(Bgs.Protocol.GameUtilities.V1.PresenceChannelCreatedRequest), Bgs.Protocol.GameUtilities.V1.PresenceChannelCreatedRequest.Parser, new[]{ "Id", "GameAccountId", "AccountId", "Host" }, null, null, null), + new pbr::GeneratedClrTypeInfo(typeof(Bgs.Protocol.GameUtilities.V1.GetPlayerVariablesRequest), Bgs.Protocol.GameUtilities.V1.GetPlayerVariablesRequest.Parser, new[]{ "PlayerVariables", "Host" }, null, null, null), + new pbr::GeneratedClrTypeInfo(typeof(Bgs.Protocol.GameUtilities.V1.GetPlayerVariablesResponse), Bgs.Protocol.GameUtilities.V1.GetPlayerVariablesResponse.Parser, new[]{ "PlayerVariables" }, null, null, null), + new pbr::GeneratedClrTypeInfo(typeof(Bgs.Protocol.GameUtilities.V1.GameAccountOnlineNotification), Bgs.Protocol.GameUtilities.V1.GameAccountOnlineNotification.Parser, new[]{ "GameAccountId", "Host", "SessionId" }, null, null, null), + new pbr::GeneratedClrTypeInfo(typeof(Bgs.Protocol.GameUtilities.V1.GameAccountOfflineNotification), Bgs.Protocol.GameUtilities.V1.GameAccountOfflineNotification.Parser, new[]{ "GameAccountId", "Host", "SessionId" }, null, null, null), + new pbr::GeneratedClrTypeInfo(typeof(Bgs.Protocol.GameUtilities.V1.GetAchievementsFileRequest), Bgs.Protocol.GameUtilities.V1.GetAchievementsFileRequest.Parser, new[]{ "Host" }, null, null, null), + new pbr::GeneratedClrTypeInfo(typeof(Bgs.Protocol.GameUtilities.V1.GetAchievementsFileResponse), Bgs.Protocol.GameUtilities.V1.GetAchievementsFileResponse.Parser, new[]{ "ContentHandle" }, null, null, null), + new pbr::GeneratedClrTypeInfo(typeof(Bgs.Protocol.GameUtilities.V1.GetAllValuesForAttributeRequest), Bgs.Protocol.GameUtilities.V1.GetAllValuesForAttributeRequest.Parser, new[]{ "AttributeKey", "AgentId", "Program" }, null, null, null), + new pbr::GeneratedClrTypeInfo(typeof(Bgs.Protocol.GameUtilities.V1.GetAllValuesForAttributeResponse), Bgs.Protocol.GameUtilities.V1.GetAllValuesForAttributeResponse.Parser, new[]{ "AttributeValue" }, null, null, null) + })); + } + #endregion + + } + #region Messages + [System.Diagnostics.DebuggerNonUserCodeAttribute()] + public sealed partial class ClientRequest : pb::IMessage + { + private static readonly pb::MessageParser _parser = new pb::MessageParser(() => new ClientRequest()); + public static pb::MessageParser Parser { get { return _parser; } } + + public static pbr::MessageDescriptor Descriptor + { + get { return Bgs.Protocol.GameUtilities.V1.GameUtilitiesServiceReflection.Descriptor.MessageTypes[0]; } + } + + pbr::MessageDescriptor pb::IMessage.Descriptor + { + get { return Descriptor; } + } + + public ClientRequest() + { + OnConstruction(); + } + + partial void OnConstruction(); + + public ClientRequest(ClientRequest other) : this() + { + attribute_ = other.attribute_.Clone(); + Host = other.host_ != null ? other.Host.Clone() : null; + AccountId = other.accountId_ != null ? other.AccountId.Clone() : null; + GameAccountId = other.gameAccountId_ != null ? other.GameAccountId.Clone() : null; + program_ = other.program_; + ClientInfo = other.clientInfo_ != null ? other.ClientInfo.Clone() : null; + } + + public ClientRequest Clone() + { + return new ClientRequest(this); + } + + /// Field number for the "attribute" field. + public const int AttributeFieldNumber = 1; + private static readonly pb::FieldCodec _repeated_attribute_codec + = pb::FieldCodec.ForMessage(10, Bgs.Protocol.Attribute.Parser); + private readonly pbc::RepeatedField attribute_ = new pbc::RepeatedField(); + public pbc::RepeatedField Attribute + { + get { return attribute_; } + } + + /// Field number for the "host" field. + public const int HostFieldNumber = 2; + private Bgs.Protocol.ProcessId host_; + public Bgs.Protocol.ProcessId Host + { + get { return host_; } + set + { + host_ = value; + } + } + + /// Field number for the "account_id" field. + public const int AccountIdFieldNumber = 3; + private Bgs.Protocol.EntityId accountId_; + public Bgs.Protocol.EntityId AccountId + { + get { return accountId_; } + set + { + accountId_ = value; + } + } + + /// Field number for the "game_account_id" field. + public const int GameAccountIdFieldNumber = 4; + private Bgs.Protocol.EntityId gameAccountId_; + public Bgs.Protocol.EntityId GameAccountId + { + get { return gameAccountId_; } + set + { + gameAccountId_ = value; + } + } + + /// Field number for the "program" field. + public const int ProgramFieldNumber = 5; + private uint program_; + public uint Program + { + get { return program_; } + set + { + program_ = value; + } + } + + /// Field number for the "client_info" field. + public const int ClientInfoFieldNumber = 6; + private Bgs.Protocol.GameUtilities.V1.ClientInfo clientInfo_; + public Bgs.Protocol.GameUtilities.V1.ClientInfo ClientInfo + { + get { return clientInfo_; } + set + { + clientInfo_ = value; + } + } + + public override bool Equals(object other) + { + return Equals(other as ClientRequest); + } + + public bool Equals(ClientRequest other) + { + if (ReferenceEquals(other, null)) + { + return false; + } + if (ReferenceEquals(other, this)) + { + return true; + } + if (!attribute_.Equals(other.attribute_)) return false; + if (!object.Equals(Host, other.Host)) return false; + if (!object.Equals(AccountId, other.AccountId)) return false; + if (!object.Equals(GameAccountId, other.GameAccountId)) return false; + if (Program != other.Program) return false; + if (!object.Equals(ClientInfo, other.ClientInfo)) return false; + return true; + } + + public override int GetHashCode() + { + int hash = 1; + hash ^= attribute_.GetHashCode(); + if (host_ != null) hash ^= Host.GetHashCode(); + if (accountId_ != null) hash ^= AccountId.GetHashCode(); + if (gameAccountId_ != null) hash ^= GameAccountId.GetHashCode(); + if (Program != 0) hash ^= Program.GetHashCode(); + if (clientInfo_ != null) hash ^= ClientInfo.GetHashCode(); + return hash; + } + + public override string ToString() + { + return pb::JsonFormatter.ToDiagnosticString(this); + } + + public void WriteTo(pb::CodedOutputStream output) + { + attribute_.WriteTo(output, _repeated_attribute_codec); + if (host_ != null) + { + output.WriteRawTag(18); + output.WriteMessage(Host); + } + if (accountId_ != null) + { + output.WriteRawTag(26); + output.WriteMessage(AccountId); + } + if (gameAccountId_ != null) + { + output.WriteRawTag(34); + output.WriteMessage(GameAccountId); + } + if (Program != 0) + { + output.WriteRawTag(45); + output.WriteFixed32(Program); + } + if (clientInfo_ != null) + { + output.WriteRawTag(50); + output.WriteMessage(ClientInfo); + } + } + + public int CalculateSize() + { + int size = 0; + size += attribute_.CalculateSize(_repeated_attribute_codec); + if (host_ != null) + { + size += 1 + pb::CodedOutputStream.ComputeMessageSize(Host); + } + if (accountId_ != null) + { + size += 1 + pb::CodedOutputStream.ComputeMessageSize(AccountId); + } + if (gameAccountId_ != null) + { + size += 1 + pb::CodedOutputStream.ComputeMessageSize(GameAccountId); + } + if (Program != 0) + { + size += 1 + 4; + } + if (clientInfo_ != null) + { + size += 1 + pb::CodedOutputStream.ComputeMessageSize(ClientInfo); + } + return size; + } + + public void MergeFrom(ClientRequest other) + { + if (other == null) + { + return; + } + attribute_.Add(other.attribute_); + if (other.host_ != null) + { + if (host_ == null) + { + host_ = new Bgs.Protocol.ProcessId(); + } + Host.MergeFrom(other.Host); + } + if (other.accountId_ != null) + { + if (accountId_ == null) + { + accountId_ = new Bgs.Protocol.EntityId(); + } + AccountId.MergeFrom(other.AccountId); + } + if (other.gameAccountId_ != null) + { + if (gameAccountId_ == null) + { + gameAccountId_ = new Bgs.Protocol.EntityId(); + } + GameAccountId.MergeFrom(other.GameAccountId); + } + if (other.Program != 0) + { + Program = other.Program; + } + if (other.clientInfo_ != null) + { + if (clientInfo_ == null) + { + clientInfo_ = new Bgs.Protocol.GameUtilities.V1.ClientInfo(); + } + ClientInfo.MergeFrom(other.ClientInfo); + } + } + + public void MergeFrom(pb::CodedInputStream input) + { + uint tag; + while ((tag = input.ReadTag()) != 0) + { + switch (tag) + { + default: + input.SkipLastField(); + break; + case 10: + { + attribute_.AddEntriesFrom(input, _repeated_attribute_codec); + break; + } + case 18: + { + if (host_ == null) + { + host_ = new Bgs.Protocol.ProcessId(); + } + input.ReadMessage(host_); + break; + } + case 26: + { + if (accountId_ == null) + { + accountId_ = new Bgs.Protocol.EntityId(); + } + input.ReadMessage(accountId_); + break; + } + case 34: + { + if (gameAccountId_ == null) + { + gameAccountId_ = new Bgs.Protocol.EntityId(); + } + input.ReadMessage(gameAccountId_); + break; + } + case 45: + { + Program = input.ReadFixed32(); + break; + } + case 50: + { + if (clientInfo_ == null) + { + clientInfo_ = new Bgs.Protocol.GameUtilities.V1.ClientInfo(); + } + input.ReadMessage(clientInfo_); + break; + } + } + } + } + + } + + [System.Diagnostics.DebuggerNonUserCodeAttribute()] + public sealed partial class ClientResponse : pb::IMessage + { + private static readonly pb::MessageParser _parser = new pb::MessageParser(() => new ClientResponse()); + public static pb::MessageParser Parser { get { return _parser; } } + + public static pbr::MessageDescriptor Descriptor + { + get { return Bgs.Protocol.GameUtilities.V1.GameUtilitiesServiceReflection.Descriptor.MessageTypes[1]; } + } + + pbr::MessageDescriptor pb::IMessage.Descriptor + { + get { return Descriptor; } + } + + public ClientResponse() + { + OnConstruction(); + } + + partial void OnConstruction(); + + public ClientResponse(ClientResponse other) : this() + { + attribute_ = other.attribute_.Clone(); + } + + public ClientResponse Clone() + { + return new ClientResponse(this); + } + + /// Field number for the "attribute" field. + public const int AttributeFieldNumber = 1; + private static readonly pb::FieldCodec _repeated_attribute_codec + = pb::FieldCodec.ForMessage(10, Bgs.Protocol.Attribute.Parser); + private readonly pbc::RepeatedField attribute_ = new pbc::RepeatedField(); + public pbc::RepeatedField Attribute + { + get { return attribute_; } + } + + public override bool Equals(object other) + { + return Equals(other as ClientResponse); + } + + public bool Equals(ClientResponse other) + { + if (ReferenceEquals(other, null)) + { + return false; + } + if (ReferenceEquals(other, this)) + { + return true; + } + if (!attribute_.Equals(other.attribute_)) return false; + return true; + } + + public override int GetHashCode() + { + int hash = 1; + hash ^= attribute_.GetHashCode(); + return hash; + } + + public override string ToString() + { + return pb::JsonFormatter.ToDiagnosticString(this); + } + + public void WriteTo(pb::CodedOutputStream output) + { + attribute_.WriteTo(output, _repeated_attribute_codec); + } + + public int CalculateSize() + { + int size = 0; + size += attribute_.CalculateSize(_repeated_attribute_codec); + return size; + } + + public void MergeFrom(ClientResponse other) + { + if (other == null) + { + return; + } + attribute_.Add(other.attribute_); + } + + public void MergeFrom(pb::CodedInputStream input) + { + uint tag; + while ((tag = input.ReadTag()) != 0) + { + switch (tag) + { + default: + input.SkipLastField(); + break; + case 10: + { + attribute_.AddEntriesFrom(input, _repeated_attribute_codec); + break; + } + } + } + } + + } + + [System.Diagnostics.DebuggerNonUserCodeAttribute()] + public sealed partial class ServerRequest : pb::IMessage + { + private static readonly pb::MessageParser _parser = new pb::MessageParser(() => new ServerRequest()); + public static pb::MessageParser Parser { get { return _parser; } } + + public static pbr::MessageDescriptor Descriptor + { + get { return Bgs.Protocol.GameUtilities.V1.GameUtilitiesServiceReflection.Descriptor.MessageTypes[2]; } + } + + pbr::MessageDescriptor pb::IMessage.Descriptor + { + get { return Descriptor; } + } + + public ServerRequest() + { + OnConstruction(); + } + + partial void OnConstruction(); + + public ServerRequest(ServerRequest other) : this() + { + attribute_ = other.attribute_.Clone(); + program_ = other.program_; + Host = other.host_ != null ? other.Host.Clone() : null; + } + + public ServerRequest Clone() + { + return new ServerRequest(this); + } + + /// Field number for the "attribute" field. + public const int AttributeFieldNumber = 1; + private static readonly pb::FieldCodec _repeated_attribute_codec + = pb::FieldCodec.ForMessage(10, Bgs.Protocol.Attribute.Parser); + private readonly pbc::RepeatedField attribute_ = new pbc::RepeatedField(); + public pbc::RepeatedField Attribute + { + get { return attribute_; } + } + + /// Field number for the "program" field. + public const int ProgramFieldNumber = 2; + private uint program_; + public uint Program + { + get { return program_; } + set + { + program_ = value; + } + } + + /// Field number for the "host" field. + public const int HostFieldNumber = 3; + private Bgs.Protocol.ProcessId host_; + public Bgs.Protocol.ProcessId Host + { + get { return host_; } + set + { + host_ = value; + } + } + + public override bool Equals(object other) + { + return Equals(other as ServerRequest); + } + + public bool Equals(ServerRequest other) + { + if (ReferenceEquals(other, null)) + { + return false; + } + if (ReferenceEquals(other, this)) + { + return true; + } + if (!attribute_.Equals(other.attribute_)) return false; + if (Program != other.Program) return false; + if (!object.Equals(Host, other.Host)) return false; + return true; + } + + public override int GetHashCode() + { + int hash = 1; + hash ^= attribute_.GetHashCode(); + if (Program != 0) hash ^= Program.GetHashCode(); + if (host_ != null) hash ^= Host.GetHashCode(); + return hash; + } + + public override string ToString() + { + return pb::JsonFormatter.ToDiagnosticString(this); + } + + public void WriteTo(pb::CodedOutputStream output) + { + attribute_.WriteTo(output, _repeated_attribute_codec); + if (Program != 0) + { + output.WriteRawTag(21); + output.WriteFixed32(Program); + } + if (host_ != null) + { + output.WriteRawTag(26); + output.WriteMessage(Host); + } + } + + public int CalculateSize() + { + int size = 0; + size += attribute_.CalculateSize(_repeated_attribute_codec); + if (Program != 0) + { + size += 1 + 4; + } + if (host_ != null) + { + size += 1 + pb::CodedOutputStream.ComputeMessageSize(Host); + } + return size; + } + + public void MergeFrom(ServerRequest other) + { + if (other == null) + { + return; + } + attribute_.Add(other.attribute_); + if (other.Program != 0) + { + Program = other.Program; + } + if (other.host_ != null) + { + if (host_ == null) + { + host_ = new Bgs.Protocol.ProcessId(); + } + Host.MergeFrom(other.Host); + } + } + + public void MergeFrom(pb::CodedInputStream input) + { + uint tag; + while ((tag = input.ReadTag()) != 0) + { + switch (tag) + { + default: + input.SkipLastField(); + break; + case 10: + { + attribute_.AddEntriesFrom(input, _repeated_attribute_codec); + break; + } + case 21: + { + Program = input.ReadFixed32(); + break; + } + case 26: + { + if (host_ == null) + { + host_ = new Bgs.Protocol.ProcessId(); + } + input.ReadMessage(host_); + break; + } + } + } + } + + } + + [System.Diagnostics.DebuggerNonUserCodeAttribute()] + public sealed partial class ServerResponse : pb::IMessage + { + private static readonly pb::MessageParser _parser = new pb::MessageParser(() => new ServerResponse()); + public static pb::MessageParser Parser { get { return _parser; } } + + public static pbr::MessageDescriptor Descriptor + { + get { return Bgs.Protocol.GameUtilities.V1.GameUtilitiesServiceReflection.Descriptor.MessageTypes[3]; } + } + + pbr::MessageDescriptor pb::IMessage.Descriptor + { + get { return Descriptor; } + } + + public ServerResponse() + { + OnConstruction(); + } + + partial void OnConstruction(); + + public ServerResponse(ServerResponse other) : this() + { + attribute_ = other.attribute_.Clone(); + } + + public ServerResponse Clone() + { + return new ServerResponse(this); + } + + /// Field number for the "attribute" field. + public const int AttributeFieldNumber = 1; + private static readonly pb::FieldCodec _repeated_attribute_codec + = pb::FieldCodec.ForMessage(10, Bgs.Protocol.Attribute.Parser); + private readonly pbc::RepeatedField attribute_ = new pbc::RepeatedField(); + public pbc::RepeatedField Attribute + { + get { return attribute_; } + } + + public override bool Equals(object other) + { + return Equals(other as ServerResponse); + } + + public bool Equals(ServerResponse other) + { + if (ReferenceEquals(other, null)) + { + return false; + } + if (ReferenceEquals(other, this)) + { + return true; + } + if (!attribute_.Equals(other.attribute_)) return false; + return true; + } + + public override int GetHashCode() + { + int hash = 1; + hash ^= attribute_.GetHashCode(); + return hash; + } + + public override string ToString() + { + return pb::JsonFormatter.ToDiagnosticString(this); + } + + public void WriteTo(pb::CodedOutputStream output) + { + attribute_.WriteTo(output, _repeated_attribute_codec); + } + + public int CalculateSize() + { + int size = 0; + size += attribute_.CalculateSize(_repeated_attribute_codec); + return size; + } + + public void MergeFrom(ServerResponse other) + { + if (other == null) + { + return; + } + attribute_.Add(other.attribute_); + } + + public void MergeFrom(pb::CodedInputStream input) + { + uint tag; + while ((tag = input.ReadTag()) != 0) + { + switch (tag) + { + default: + input.SkipLastField(); + break; + case 10: + { + attribute_.AddEntriesFrom(input, _repeated_attribute_codec); + break; + } + } + } + } + + } + + [System.Diagnostics.DebuggerNonUserCodeAttribute()] + public sealed partial class PresenceChannelCreatedRequest : pb::IMessage + { + private static readonly pb::MessageParser _parser = new pb::MessageParser(() => new PresenceChannelCreatedRequest()); + public static pb::MessageParser Parser { get { return _parser; } } + + public static pbr::MessageDescriptor Descriptor + { + get { return Bgs.Protocol.GameUtilities.V1.GameUtilitiesServiceReflection.Descriptor.MessageTypes[4]; } + } + + pbr::MessageDescriptor pb::IMessage.Descriptor + { + get { return Descriptor; } + } + + public PresenceChannelCreatedRequest() + { + OnConstruction(); + } + + partial void OnConstruction(); + + public PresenceChannelCreatedRequest(PresenceChannelCreatedRequest other) : this() + { + Id = other.id_ != null ? other.Id.Clone() : null; + GameAccountId = other.gameAccountId_ != null ? other.GameAccountId.Clone() : null; + AccountId = other.accountId_ != null ? other.AccountId.Clone() : null; + Host = other.host_ != null ? other.Host.Clone() : null; + } + + public PresenceChannelCreatedRequest Clone() + { + return new PresenceChannelCreatedRequest(this); + } + + /// Field number for the "id" field. + public const int IdFieldNumber = 1; + private Bgs.Protocol.EntityId id_; + public Bgs.Protocol.EntityId Id + { + get { return id_; } + set + { + id_ = value; + } + } + + /// Field number for the "game_account_id" field. + public const int GameAccountIdFieldNumber = 3; + private Bgs.Protocol.EntityId gameAccountId_; + public Bgs.Protocol.EntityId GameAccountId + { + get { return gameAccountId_; } + set + { + gameAccountId_ = value; + } + } + + /// Field number for the "account_id" field. + public const int AccountIdFieldNumber = 4; + private Bgs.Protocol.EntityId accountId_; + public Bgs.Protocol.EntityId AccountId + { + get { return accountId_; } + set + { + accountId_ = value; + } + } + + /// Field number for the "host" field. + public const int HostFieldNumber = 5; + private Bgs.Protocol.ProcessId host_; + public Bgs.Protocol.ProcessId Host + { + get { return host_; } + set + { + host_ = value; + } + } + + public override bool Equals(object other) + { + return Equals(other as PresenceChannelCreatedRequest); + } + + public bool Equals(PresenceChannelCreatedRequest other) + { + if (ReferenceEquals(other, null)) + { + return false; + } + if (ReferenceEquals(other, this)) + { + return true; + } + if (!object.Equals(Id, other.Id)) return false; + if (!object.Equals(GameAccountId, other.GameAccountId)) return false; + if (!object.Equals(AccountId, other.AccountId)) return false; + if (!object.Equals(Host, other.Host)) return false; + return true; + } + + public override int GetHashCode() + { + int hash = 1; + if (id_ != null) hash ^= Id.GetHashCode(); + if (gameAccountId_ != null) hash ^= GameAccountId.GetHashCode(); + if (accountId_ != null) hash ^= AccountId.GetHashCode(); + if (host_ != null) hash ^= Host.GetHashCode(); + return hash; + } + + public override string ToString() + { + return pb::JsonFormatter.ToDiagnosticString(this); + } + + public void WriteTo(pb::CodedOutputStream output) + { + if (id_ != null) + { + output.WriteRawTag(10); + output.WriteMessage(Id); + } + if (gameAccountId_ != null) + { + output.WriteRawTag(26); + output.WriteMessage(GameAccountId); + } + if (accountId_ != null) + { + output.WriteRawTag(34); + output.WriteMessage(AccountId); + } + if (host_ != null) + { + output.WriteRawTag(42); + output.WriteMessage(Host); + } + } + + public int CalculateSize() + { + int size = 0; + if (id_ != null) + { + size += 1 + pb::CodedOutputStream.ComputeMessageSize(Id); + } + if (gameAccountId_ != null) + { + size += 1 + pb::CodedOutputStream.ComputeMessageSize(GameAccountId); + } + if (accountId_ != null) + { + size += 1 + pb::CodedOutputStream.ComputeMessageSize(AccountId); + } + if (host_ != null) + { + size += 1 + pb::CodedOutputStream.ComputeMessageSize(Host); + } + return size; + } + + public void MergeFrom(PresenceChannelCreatedRequest other) + { + if (other == null) + { + return; + } + if (other.id_ != null) + { + if (id_ == null) + { + id_ = new Bgs.Protocol.EntityId(); + } + Id.MergeFrom(other.Id); + } + if (other.gameAccountId_ != null) + { + if (gameAccountId_ == null) + { + gameAccountId_ = new Bgs.Protocol.EntityId(); + } + GameAccountId.MergeFrom(other.GameAccountId); + } + if (other.accountId_ != null) + { + if (accountId_ == null) + { + accountId_ = new Bgs.Protocol.EntityId(); + } + AccountId.MergeFrom(other.AccountId); + } + if (other.host_ != null) + { + if (host_ == null) + { + host_ = new Bgs.Protocol.ProcessId(); + } + Host.MergeFrom(other.Host); + } + } + + public void MergeFrom(pb::CodedInputStream input) + { + uint tag; + while ((tag = input.ReadTag()) != 0) + { + switch (tag) + { + default: + input.SkipLastField(); + break; + case 10: + { + if (id_ == null) + { + id_ = new Bgs.Protocol.EntityId(); + } + input.ReadMessage(id_); + break; + } + case 26: + { + if (gameAccountId_ == null) + { + gameAccountId_ = new Bgs.Protocol.EntityId(); + } + input.ReadMessage(gameAccountId_); + break; + } + case 34: + { + if (accountId_ == null) + { + accountId_ = new Bgs.Protocol.EntityId(); + } + input.ReadMessage(accountId_); + break; + } + case 42: + { + if (host_ == null) + { + host_ = new Bgs.Protocol.ProcessId(); + } + input.ReadMessage(host_); + break; + } + } + } + } + + } + + [System.Diagnostics.DebuggerNonUserCodeAttribute()] + public sealed partial class GetPlayerVariablesRequest : pb::IMessage + { + private static readonly pb::MessageParser _parser = new pb::MessageParser(() => new GetPlayerVariablesRequest()); + public static pb::MessageParser Parser { get { return _parser; } } + + public static pbr::MessageDescriptor Descriptor + { + get { return Bgs.Protocol.GameUtilities.V1.GameUtilitiesServiceReflection.Descriptor.MessageTypes[5]; } + } + + pbr::MessageDescriptor pb::IMessage.Descriptor + { + get { return Descriptor; } + } + + public GetPlayerVariablesRequest() + { + OnConstruction(); + } + + partial void OnConstruction(); + + public GetPlayerVariablesRequest(GetPlayerVariablesRequest other) : this() + { + playerVariables_ = other.playerVariables_.Clone(); + Host = other.host_ != null ? other.Host.Clone() : null; + } + + public GetPlayerVariablesRequest Clone() + { + return new GetPlayerVariablesRequest(this); + } + + /// Field number for the "player_variables" field. + public const int PlayerVariablesFieldNumber = 1; + private static readonly pb::FieldCodec _repeated_playerVariables_codec + = pb::FieldCodec.ForMessage(10, Bgs.Protocol.GameUtilities.V1.PlayerVariables.Parser); + private readonly pbc::RepeatedField playerVariables_ = new pbc::RepeatedField(); + public pbc::RepeatedField PlayerVariables + { + get { return playerVariables_; } + } + + /// Field number for the "host" field. + public const int HostFieldNumber = 2; + private Bgs.Protocol.ProcessId host_; + public Bgs.Protocol.ProcessId Host + { + get { return host_; } + set + { + host_ = value; + } + } + + public override bool Equals(object other) + { + return Equals(other as GetPlayerVariablesRequest); + } + + public bool Equals(GetPlayerVariablesRequest other) + { + if (ReferenceEquals(other, null)) + { + return false; + } + if (ReferenceEquals(other, this)) + { + return true; + } + if (!playerVariables_.Equals(other.playerVariables_)) return false; + if (!object.Equals(Host, other.Host)) return false; + return true; + } + + public override int GetHashCode() + { + int hash = 1; + hash ^= playerVariables_.GetHashCode(); + if (host_ != null) hash ^= Host.GetHashCode(); + return hash; + } + + public override string ToString() + { + return pb::JsonFormatter.ToDiagnosticString(this); + } + + public void WriteTo(pb::CodedOutputStream output) + { + playerVariables_.WriteTo(output, _repeated_playerVariables_codec); + if (host_ != null) + { + output.WriteRawTag(18); + output.WriteMessage(Host); + } + } + + public int CalculateSize() + { + int size = 0; + size += playerVariables_.CalculateSize(_repeated_playerVariables_codec); + if (host_ != null) + { + size += 1 + pb::CodedOutputStream.ComputeMessageSize(Host); + } + return size; + } + + public void MergeFrom(GetPlayerVariablesRequest other) + { + if (other == null) + { + return; + } + playerVariables_.Add(other.playerVariables_); + if (other.host_ != null) + { + if (host_ == null) + { + host_ = new Bgs.Protocol.ProcessId(); + } + Host.MergeFrom(other.Host); + } + } + + public void MergeFrom(pb::CodedInputStream input) + { + uint tag; + while ((tag = input.ReadTag()) != 0) + { + switch (tag) + { + default: + input.SkipLastField(); + break; + case 10: + { + playerVariables_.AddEntriesFrom(input, _repeated_playerVariables_codec); + break; + } + case 18: + { + if (host_ == null) + { + host_ = new Bgs.Protocol.ProcessId(); + } + input.ReadMessage(host_); + break; + } + } + } + } + + } + + [System.Diagnostics.DebuggerNonUserCodeAttribute()] + public sealed partial class GetPlayerVariablesResponse : pb::IMessage + { + private static readonly pb::MessageParser _parser = new pb::MessageParser(() => new GetPlayerVariablesResponse()); + public static pb::MessageParser Parser { get { return _parser; } } + + public static pbr::MessageDescriptor Descriptor + { + get { return Bgs.Protocol.GameUtilities.V1.GameUtilitiesServiceReflection.Descriptor.MessageTypes[6]; } + } + + pbr::MessageDescriptor pb::IMessage.Descriptor + { + get { return Descriptor; } + } + + public GetPlayerVariablesResponse() + { + OnConstruction(); + } + + partial void OnConstruction(); + + public GetPlayerVariablesResponse(GetPlayerVariablesResponse other) : this() + { + playerVariables_ = other.playerVariables_.Clone(); + } + + public GetPlayerVariablesResponse Clone() + { + return new GetPlayerVariablesResponse(this); + } + + /// Field number for the "player_variables" field. + public const int PlayerVariablesFieldNumber = 1; + private static readonly pb::FieldCodec _repeated_playerVariables_codec + = pb::FieldCodec.ForMessage(10, Bgs.Protocol.GameUtilities.V1.PlayerVariables.Parser); + private readonly pbc::RepeatedField playerVariables_ = new pbc::RepeatedField(); + public pbc::RepeatedField PlayerVariables + { + get { return playerVariables_; } + } + + public override bool Equals(object other) + { + return Equals(other as GetPlayerVariablesResponse); + } + + public bool Equals(GetPlayerVariablesResponse other) + { + if (ReferenceEquals(other, null)) + { + return false; + } + if (ReferenceEquals(other, this)) + { + return true; + } + if (!playerVariables_.Equals(other.playerVariables_)) return false; + return true; + } + + public override int GetHashCode() + { + int hash = 1; + hash ^= playerVariables_.GetHashCode(); + return hash; + } + + public override string ToString() + { + return pb::JsonFormatter.ToDiagnosticString(this); + } + + public void WriteTo(pb::CodedOutputStream output) + { + playerVariables_.WriteTo(output, _repeated_playerVariables_codec); + } + + public int CalculateSize() + { + int size = 0; + size += playerVariables_.CalculateSize(_repeated_playerVariables_codec); + return size; + } + + public void MergeFrom(GetPlayerVariablesResponse other) + { + if (other == null) + { + return; + } + playerVariables_.Add(other.playerVariables_); + } + + public void MergeFrom(pb::CodedInputStream input) + { + uint tag; + while ((tag = input.ReadTag()) != 0) + { + switch (tag) + { + default: + input.SkipLastField(); + break; + case 10: + { + playerVariables_.AddEntriesFrom(input, _repeated_playerVariables_codec); + break; + } + } + } + } + + } + + [System.Diagnostics.DebuggerNonUserCodeAttribute()] + public sealed partial class GameAccountOnlineNotification : pb::IMessage + { + private static readonly pb::MessageParser _parser = new pb::MessageParser(() => new GameAccountOnlineNotification()); + public static pb::MessageParser Parser { get { return _parser; } } + + public static pbr::MessageDescriptor Descriptor + { + get { return Bgs.Protocol.GameUtilities.V1.GameUtilitiesServiceReflection.Descriptor.MessageTypes[7]; } + } + + pbr::MessageDescriptor pb::IMessage.Descriptor + { + get { return Descriptor; } + } + + public GameAccountOnlineNotification() + { + OnConstruction(); + } + + partial void OnConstruction(); + + public GameAccountOnlineNotification(GameAccountOnlineNotification other) : this() + { + GameAccountId = other.gameAccountId_ != null ? other.GameAccountId.Clone() : null; + Host = other.host_ != null ? other.Host.Clone() : null; + sessionId_ = other.sessionId_; + } + + public GameAccountOnlineNotification Clone() + { + return new GameAccountOnlineNotification(this); + } + + /// Field number for the "game_account_id" field. + public const int GameAccountIdFieldNumber = 1; + private Bgs.Protocol.EntityId gameAccountId_; + public Bgs.Protocol.EntityId GameAccountId + { + get { return gameAccountId_; } + set + { + gameAccountId_ = value; + } + } + + /// Field number for the "host" field. + public const int HostFieldNumber = 2; + private Bgs.Protocol.ProcessId host_; + public Bgs.Protocol.ProcessId Host + { + get { return host_; } + set + { + host_ = value; + } + } + + /// Field number for the "session_id" field. + public const int SessionIdFieldNumber = 3; + private string sessionId_ = ""; + public string SessionId + { + get { return sessionId_; } + set + { + sessionId_ = pb::ProtoPreconditions.CheckNotNull(value, "value"); + } + } + + public override bool Equals(object other) + { + return Equals(other as GameAccountOnlineNotification); + } + + public bool Equals(GameAccountOnlineNotification other) + { + if (ReferenceEquals(other, null)) + { + return false; + } + if (ReferenceEquals(other, this)) + { + return true; + } + if (!object.Equals(GameAccountId, other.GameAccountId)) return false; + if (!object.Equals(Host, other.Host)) return false; + if (SessionId != other.SessionId) return false; + return true; + } + + public override int GetHashCode() + { + int hash = 1; + if (gameAccountId_ != null) hash ^= GameAccountId.GetHashCode(); + if (host_ != null) hash ^= Host.GetHashCode(); + if (SessionId.Length != 0) hash ^= SessionId.GetHashCode(); + return hash; + } + + public override string ToString() + { + return pb::JsonFormatter.ToDiagnosticString(this); + } + + public void WriteTo(pb::CodedOutputStream output) + { + if (gameAccountId_ != null) + { + output.WriteRawTag(10); + output.WriteMessage(GameAccountId); + } + if (host_ != null) + { + output.WriteRawTag(18); + output.WriteMessage(Host); + } + if (SessionId.Length != 0) + { + output.WriteRawTag(26); + output.WriteString(SessionId); + } + } + + public int CalculateSize() + { + int size = 0; + if (gameAccountId_ != null) + { + size += 1 + pb::CodedOutputStream.ComputeMessageSize(GameAccountId); + } + if (host_ != null) + { + size += 1 + pb::CodedOutputStream.ComputeMessageSize(Host); + } + if (SessionId.Length != 0) + { + size += 1 + pb::CodedOutputStream.ComputeStringSize(SessionId); + } + return size; + } + + public void MergeFrom(GameAccountOnlineNotification other) + { + if (other == null) + { + return; + } + if (other.gameAccountId_ != null) + { + if (gameAccountId_ == null) + { + gameAccountId_ = new Bgs.Protocol.EntityId(); + } + GameAccountId.MergeFrom(other.GameAccountId); + } + if (other.host_ != null) + { + if (host_ == null) + { + host_ = new Bgs.Protocol.ProcessId(); + } + Host.MergeFrom(other.Host); + } + if (other.SessionId.Length != 0) + { + SessionId = other.SessionId; + } + } + + public void MergeFrom(pb::CodedInputStream input) + { + uint tag; + while ((tag = input.ReadTag()) != 0) + { + switch (tag) + { + default: + input.SkipLastField(); + break; + case 10: + { + if (gameAccountId_ == null) + { + gameAccountId_ = new Bgs.Protocol.EntityId(); + } + input.ReadMessage(gameAccountId_); + break; + } + case 18: + { + if (host_ == null) + { + host_ = new Bgs.Protocol.ProcessId(); + } + input.ReadMessage(host_); + break; + } + case 26: + { + SessionId = input.ReadString(); + break; + } + } + } + } + + } + + [System.Diagnostics.DebuggerNonUserCodeAttribute()] + public sealed partial class GameAccountOfflineNotification : pb::IMessage + { + private static readonly pb::MessageParser _parser = new pb::MessageParser(() => new GameAccountOfflineNotification()); + public static pb::MessageParser Parser { get { return _parser; } } + + public static pbr::MessageDescriptor Descriptor + { + get { return Bgs.Protocol.GameUtilities.V1.GameUtilitiesServiceReflection.Descriptor.MessageTypes[8]; } + } + + pbr::MessageDescriptor pb::IMessage.Descriptor + { + get { return Descriptor; } + } + + public GameAccountOfflineNotification() + { + OnConstruction(); + } + + partial void OnConstruction(); + + public GameAccountOfflineNotification(GameAccountOfflineNotification other) : this() + { + GameAccountId = other.gameAccountId_ != null ? other.GameAccountId.Clone() : null; + Host = other.host_ != null ? other.Host.Clone() : null; + sessionId_ = other.sessionId_; + } + + public GameAccountOfflineNotification Clone() + { + return new GameAccountOfflineNotification(this); + } + + /// Field number for the "game_account_id" field. + public const int GameAccountIdFieldNumber = 1; + private Bgs.Protocol.EntityId gameAccountId_; + public Bgs.Protocol.EntityId GameAccountId + { + get { return gameAccountId_; } + set + { + gameAccountId_ = value; + } + } + + /// Field number for the "host" field. + public const int HostFieldNumber = 2; + private Bgs.Protocol.ProcessId host_; + public Bgs.Protocol.ProcessId Host + { + get { return host_; } + set + { + host_ = value; + } + } + + /// Field number for the "session_id" field. + public const int SessionIdFieldNumber = 3; + private string sessionId_ = ""; + public string SessionId + { + get { return sessionId_; } + set + { + sessionId_ = pb::ProtoPreconditions.CheckNotNull(value, "value"); + } + } + + public override bool Equals(object other) + { + return Equals(other as GameAccountOfflineNotification); + } + + public bool Equals(GameAccountOfflineNotification other) + { + if (ReferenceEquals(other, null)) + { + return false; + } + if (ReferenceEquals(other, this)) + { + return true; + } + if (!object.Equals(GameAccountId, other.GameAccountId)) return false; + if (!object.Equals(Host, other.Host)) return false; + if (SessionId != other.SessionId) return false; + return true; + } + + public override int GetHashCode() + { + int hash = 1; + if (gameAccountId_ != null) hash ^= GameAccountId.GetHashCode(); + if (host_ != null) hash ^= Host.GetHashCode(); + if (SessionId.Length != 0) hash ^= SessionId.GetHashCode(); + return hash; + } + + public override string ToString() + { + return pb::JsonFormatter.ToDiagnosticString(this); + } + + public void WriteTo(pb::CodedOutputStream output) + { + if (gameAccountId_ != null) + { + output.WriteRawTag(10); + output.WriteMessage(GameAccountId); + } + if (host_ != null) + { + output.WriteRawTag(18); + output.WriteMessage(Host); + } + if (SessionId.Length != 0) + { + output.WriteRawTag(26); + output.WriteString(SessionId); + } + } + + public int CalculateSize() + { + int size = 0; + if (gameAccountId_ != null) + { + size += 1 + pb::CodedOutputStream.ComputeMessageSize(GameAccountId); + } + if (host_ != null) + { + size += 1 + pb::CodedOutputStream.ComputeMessageSize(Host); + } + if (SessionId.Length != 0) + { + size += 1 + pb::CodedOutputStream.ComputeStringSize(SessionId); + } + return size; + } + + public void MergeFrom(GameAccountOfflineNotification other) + { + if (other == null) + { + return; + } + if (other.gameAccountId_ != null) + { + if (gameAccountId_ == null) + { + gameAccountId_ = new Bgs.Protocol.EntityId(); + } + GameAccountId.MergeFrom(other.GameAccountId); + } + if (other.host_ != null) + { + if (host_ == null) + { + host_ = new Bgs.Protocol.ProcessId(); + } + Host.MergeFrom(other.Host); + } + if (other.SessionId.Length != 0) + { + SessionId = other.SessionId; + } + } + + public void MergeFrom(pb::CodedInputStream input) + { + uint tag; + while ((tag = input.ReadTag()) != 0) + { + switch (tag) + { + default: + input.SkipLastField(); + break; + case 10: + { + if (gameAccountId_ == null) + { + gameAccountId_ = new Bgs.Protocol.EntityId(); + } + input.ReadMessage(gameAccountId_); + break; + } + case 18: + { + if (host_ == null) + { + host_ = new Bgs.Protocol.ProcessId(); + } + input.ReadMessage(host_); + break; + } + case 26: + { + SessionId = input.ReadString(); + break; + } + } + } + } + + } + + [System.Diagnostics.DebuggerNonUserCodeAttribute()] + public sealed partial class GetAchievementsFileRequest : pb::IMessage + { + private static readonly pb::MessageParser _parser = new pb::MessageParser(() => new GetAchievementsFileRequest()); + public static pb::MessageParser Parser { get { return _parser; } } + + public static pbr::MessageDescriptor Descriptor + { + get { return Bgs.Protocol.GameUtilities.V1.GameUtilitiesServiceReflection.Descriptor.MessageTypes[9]; } + } + + pbr::MessageDescriptor pb::IMessage.Descriptor + { + get { return Descriptor; } + } + + public GetAchievementsFileRequest() + { + OnConstruction(); + } + + partial void OnConstruction(); + + public GetAchievementsFileRequest(GetAchievementsFileRequest other) : this() + { + Host = other.host_ != null ? other.Host.Clone() : null; + } + + public GetAchievementsFileRequest Clone() + { + return new GetAchievementsFileRequest(this); + } + + /// Field number for the "host" field. + public const int HostFieldNumber = 1; + private Bgs.Protocol.ProcessId host_; + public Bgs.Protocol.ProcessId Host + { + get { return host_; } + set + { + host_ = value; + } + } + + public override bool Equals(object other) + { + return Equals(other as GetAchievementsFileRequest); + } + + public bool Equals(GetAchievementsFileRequest other) + { + if (ReferenceEquals(other, null)) + { + return false; + } + if (ReferenceEquals(other, this)) + { + return true; + } + if (!object.Equals(Host, other.Host)) return false; + return true; + } + + public override int GetHashCode() + { + int hash = 1; + if (host_ != null) hash ^= Host.GetHashCode(); + return hash; + } + + public override string ToString() + { + return pb::JsonFormatter.ToDiagnosticString(this); + } + + public void WriteTo(pb::CodedOutputStream output) + { + if (host_ != null) + { + output.WriteRawTag(10); + output.WriteMessage(Host); + } + } + + public int CalculateSize() + { + int size = 0; + if (host_ != null) + { + size += 1 + pb::CodedOutputStream.ComputeMessageSize(Host); + } + return size; + } + + public void MergeFrom(GetAchievementsFileRequest other) + { + if (other == null) + { + return; + } + if (other.host_ != null) + { + if (host_ == null) + { + host_ = new Bgs.Protocol.ProcessId(); + } + Host.MergeFrom(other.Host); + } + } + + public void MergeFrom(pb::CodedInputStream input) + { + uint tag; + while ((tag = input.ReadTag()) != 0) + { + switch (tag) + { + default: + input.SkipLastField(); + break; + case 10: + { + if (host_ == null) + { + host_ = new Bgs.Protocol.ProcessId(); + } + input.ReadMessage(host_); + break; + } + } + } + } + + } + + [System.Diagnostics.DebuggerNonUserCodeAttribute()] + public sealed partial class GetAchievementsFileResponse : pb::IMessage + { + private static readonly pb::MessageParser _parser = new pb::MessageParser(() => new GetAchievementsFileResponse()); + public static pb::MessageParser Parser { get { return _parser; } } + + public static pbr::MessageDescriptor Descriptor + { + get { return Bgs.Protocol.GameUtilities.V1.GameUtilitiesServiceReflection.Descriptor.MessageTypes[10]; } + } + + pbr::MessageDescriptor pb::IMessage.Descriptor + { + get { return Descriptor; } + } + + public GetAchievementsFileResponse() + { + OnConstruction(); + } + + partial void OnConstruction(); + + public GetAchievementsFileResponse(GetAchievementsFileResponse other) : this() + { + ContentHandle = other.contentHandle_ != null ? other.ContentHandle.Clone() : null; + } + + public GetAchievementsFileResponse Clone() + { + return new GetAchievementsFileResponse(this); + } + + /// Field number for the "content_handle" field. + public const int ContentHandleFieldNumber = 1; + private Bgs.Protocol.ContentHandle contentHandle_; + public Bgs.Protocol.ContentHandle ContentHandle + { + get { return contentHandle_; } + set + { + contentHandle_ = value; + } + } + + public override bool Equals(object other) + { + return Equals(other as GetAchievementsFileResponse); + } + + public bool Equals(GetAchievementsFileResponse other) + { + if (ReferenceEquals(other, null)) + { + return false; + } + if (ReferenceEquals(other, this)) + { + return true; + } + if (!object.Equals(ContentHandle, other.ContentHandle)) return false; + return true; + } + + public override int GetHashCode() + { + int hash = 1; + if (contentHandle_ != null) hash ^= ContentHandle.GetHashCode(); + return hash; + } + + public override string ToString() + { + return pb::JsonFormatter.ToDiagnosticString(this); + } + + public void WriteTo(pb::CodedOutputStream output) + { + if (contentHandle_ != null) + { + output.WriteRawTag(10); + output.WriteMessage(ContentHandle); + } + } + + public int CalculateSize() + { + int size = 0; + if (contentHandle_ != null) + { + size += 1 + pb::CodedOutputStream.ComputeMessageSize(ContentHandle); + } + return size; + } + + public void MergeFrom(GetAchievementsFileResponse other) + { + if (other == null) + { + return; + } + if (other.contentHandle_ != null) + { + if (contentHandle_ == null) + { + contentHandle_ = new Bgs.Protocol.ContentHandle(); + } + ContentHandle.MergeFrom(other.ContentHandle); + } + } + + public void MergeFrom(pb::CodedInputStream input) + { + uint tag; + while ((tag = input.ReadTag()) != 0) + { + switch (tag) + { + default: + input.SkipLastField(); + break; + case 10: + { + if (contentHandle_ == null) + { + contentHandle_ = new Bgs.Protocol.ContentHandle(); + } + input.ReadMessage(contentHandle_); + break; + } + } + } + } + + } + + [System.Diagnostics.DebuggerNonUserCodeAttribute()] + public sealed partial class GetAllValuesForAttributeRequest : pb::IMessage + { + private static readonly pb::MessageParser _parser = new pb::MessageParser(() => new GetAllValuesForAttributeRequest()); + public static pb::MessageParser Parser { get { return _parser; } } + + public static pbr::MessageDescriptor Descriptor + { + get { return Bgs.Protocol.GameUtilities.V1.GameUtilitiesServiceReflection.Descriptor.MessageTypes[11]; } + } + + pbr::MessageDescriptor pb::IMessage.Descriptor + { + get { return Descriptor; } + } + + public GetAllValuesForAttributeRequest() + { + OnConstruction(); + } + + partial void OnConstruction(); + + public GetAllValuesForAttributeRequest(GetAllValuesForAttributeRequest other) : this() + { + attributeKey_ = other.attributeKey_; + AgentId = other.agentId_ != null ? other.AgentId.Clone() : null; + program_ = other.program_; + } + + public GetAllValuesForAttributeRequest Clone() + { + return new GetAllValuesForAttributeRequest(this); + } + + /// Field number for the "attribute_key" field. + public const int AttributeKeyFieldNumber = 1; + private string attributeKey_ = ""; + public string AttributeKey + { + get { return attributeKey_; } + set + { + attributeKey_ = pb::ProtoPreconditions.CheckNotNull(value, "value"); + } + } + + /// Field number for the "agent_id" field. + public const int AgentIdFieldNumber = 2; + private Bgs.Protocol.EntityId agentId_; + public Bgs.Protocol.EntityId AgentId + { + get { return agentId_; } + set + { + agentId_ = value; + } + } + + /// Field number for the "program" field. + public const int ProgramFieldNumber = 5; + private uint program_; + public uint Program + { + get { return program_; } + set + { + program_ = value; + } + } + + public override bool Equals(object other) + { + return Equals(other as GetAllValuesForAttributeRequest); + } + + public bool Equals(GetAllValuesForAttributeRequest other) + { + if (ReferenceEquals(other, null)) + { + return false; + } + if (ReferenceEquals(other, this)) + { + return true; + } + if (AttributeKey != other.AttributeKey) return false; + if (!object.Equals(AgentId, other.AgentId)) return false; + if (Program != other.Program) return false; + return true; + } + + public override int GetHashCode() + { + int hash = 1; + if (AttributeKey.Length != 0) hash ^= AttributeKey.GetHashCode(); + if (agentId_ != null) hash ^= AgentId.GetHashCode(); + if (Program != 0) hash ^= Program.GetHashCode(); + return hash; + } + + public override string ToString() + { + return pb::JsonFormatter.ToDiagnosticString(this); + } + + public void WriteTo(pb::CodedOutputStream output) + { + if (AttributeKey.Length != 0) + { + output.WriteRawTag(10); + output.WriteString(AttributeKey); + } + if (agentId_ != null) + { + output.WriteRawTag(18); + output.WriteMessage(AgentId); + } + if (Program != 0) + { + output.WriteRawTag(45); + output.WriteFixed32(Program); + } + } + + public int CalculateSize() + { + int size = 0; + if (AttributeKey.Length != 0) + { + size += 1 + pb::CodedOutputStream.ComputeStringSize(AttributeKey); + } + if (agentId_ != null) + { + size += 1 + pb::CodedOutputStream.ComputeMessageSize(AgentId); + } + if (Program != 0) + { + size += 1 + 4; + } + return size; + } + + public void MergeFrom(GetAllValuesForAttributeRequest other) + { + if (other == null) + { + return; + } + if (other.AttributeKey.Length != 0) + { + AttributeKey = other.AttributeKey; + } + if (other.agentId_ != null) + { + if (agentId_ == null) + { + agentId_ = new Bgs.Protocol.EntityId(); + } + AgentId.MergeFrom(other.AgentId); + } + if (other.Program != 0) + { + Program = other.Program; + } + } + + public void MergeFrom(pb::CodedInputStream input) + { + uint tag; + while ((tag = input.ReadTag()) != 0) + { + switch (tag) + { + default: + input.SkipLastField(); + break; + case 10: + { + AttributeKey = input.ReadString(); + break; + } + case 18: + { + if (agentId_ == null) + { + agentId_ = new Bgs.Protocol.EntityId(); + } + input.ReadMessage(agentId_); + break; + } + case 45: + { + Program = input.ReadFixed32(); + break; + } + } + } + } + + } + + [System.Diagnostics.DebuggerNonUserCodeAttribute()] + public sealed partial class GetAllValuesForAttributeResponse : pb::IMessage + { + private static readonly pb::MessageParser _parser = new pb::MessageParser(() => new GetAllValuesForAttributeResponse()); + public static pb::MessageParser Parser { get { return _parser; } } + + public static pbr::MessageDescriptor Descriptor + { + get { return Bgs.Protocol.GameUtilities.V1.GameUtilitiesServiceReflection.Descriptor.MessageTypes[12]; } + } + + pbr::MessageDescriptor pb::IMessage.Descriptor + { + get { return Descriptor; } + } + + public GetAllValuesForAttributeResponse() + { + OnConstruction(); + } + + partial void OnConstruction(); + + public GetAllValuesForAttributeResponse(GetAllValuesForAttributeResponse other) : this() + { + attributeValue_ = other.attributeValue_.Clone(); + } + + public GetAllValuesForAttributeResponse Clone() + { + return new GetAllValuesForAttributeResponse(this); + } + + /// Field number for the "attribute_value" field. + public const int AttributeValueFieldNumber = 1; + private static readonly pb::FieldCodec _repeated_attributeValue_codec + = pb::FieldCodec.ForMessage(10, Bgs.Protocol.Variant.Parser); + private readonly pbc::RepeatedField attributeValue_ = new pbc::RepeatedField(); + public pbc::RepeatedField AttributeValue + { + get { return attributeValue_; } + } + + public override bool Equals(object other) + { + return Equals(other as GetAllValuesForAttributeResponse); + } + + public bool Equals(GetAllValuesForAttributeResponse other) + { + if (ReferenceEquals(other, null)) + { + return false; + } + if (ReferenceEquals(other, this)) + { + return true; + } + if (!attributeValue_.Equals(other.attributeValue_)) return false; + return true; + } + + public override int GetHashCode() + { + int hash = 1; + hash ^= attributeValue_.GetHashCode(); + return hash; + } + + public override string ToString() + { + return pb::JsonFormatter.ToDiagnosticString(this); + } + + public void WriteTo(pb::CodedOutputStream output) + { + attributeValue_.WriteTo(output, _repeated_attributeValue_codec); + } + + public int CalculateSize() + { + int size = 0; + size += attributeValue_.CalculateSize(_repeated_attributeValue_codec); + return size; + } + + public void MergeFrom(GetAllValuesForAttributeResponse other) + { + if (other == null) + { + return; + } + attributeValue_.Add(other.attributeValue_); + } + + public void MergeFrom(pb::CodedInputStream input) + { + uint tag; + while ((tag = input.ReadTag()) != 0) + { + switch (tag) + { + default: + input.SkipLastField(); + break; + case 10: + { + attributeValue_.AddEntriesFrom(input, _repeated_attributeValue_codec); + break; + } + } + } + } + + } + + #endregion +} + +#endregion Designer generated code diff --git a/Framework/Proto/GameUtilitiesTypes.cs b/Framework/Proto/GameUtilitiesTypes.cs new file mode 100644 index 000000000..0b56ca7ea --- /dev/null +++ b/Framework/Proto/GameUtilitiesTypes.cs @@ -0,0 +1,336 @@ +// Generated by the protocol buffer compiler. DO NOT EDIT! +// source: bgs/low/pb/client/game_utilities_types.proto +#pragma warning disable 1591, 0612, 3021 +#region Designer generated code + +using pb = Google.Protobuf; +using pbc = Google.Protobuf.Collections; +using pbr = Google.Protobuf.Reflection; +namespace Bgs.Protocol.GameUtilities.V1 +{ + + /// Holder for reflection information generated from bgs/low/pb/client/game_utilities_types.proto + [System.Diagnostics.DebuggerNonUserCodeAttribute()] + public static partial class GameUtilitiesTypesReflection { + + #region Descriptor + /// File descriptor for bgs/low/pb/client/game_utilities_types.proto + public static pbr::FileDescriptor Descriptor { + get { return descriptor; } + } + private static pbr::FileDescriptor descriptor; + + static GameUtilitiesTypesReflection() { + byte[] descriptorData = System.Convert.FromBase64String( + string.Concat( + "CixiZ3MvbG93L3BiL2NsaWVudC9nYW1lX3V0aWxpdGllc190eXBlcy5wcm90", + "bxIeYmdzLnByb3RvY29sLmdhbWVfdXRpbGl0aWVzLnYxGidiZ3MvbG93L3Bi", + "L2NsaWVudC9hdHRyaWJ1dGVfdHlwZXMucHJvdG8aJGJncy9sb3cvcGIvY2xp", + "ZW50L2VudGl0eV90eXBlcy5wcm90byJ3Cg9QbGF5ZXJWYXJpYWJsZXMSKAoI", + "aWRlbnRpdHkYASABKAsyFi5iZ3MucHJvdG9jb2wuSWRlbnRpdHkSDgoGcmF0", + "aW5nGAIgASgBEioKCWF0dHJpYnV0ZRgDIAMoCzIXLmJncy5wcm90b2NvbC5B", + "dHRyaWJ1dGUiQAoKQ2xpZW50SW5mbxIWCg5jbGllbnRfYWRkcmVzcxgBIAEo", + "CRIaChJwcml2aWxlZ2VkX25ldHdvcmsYAiABKAhCPAofYm5ldC5wcm90b2Nv", + "bC5nYW1lX3V0aWxpdGllcy52MUIXR2FtZVV0aWxpdGllc1R5cGVzUHJvdG9I", + "AmIGcHJvdG8z")); + descriptor = pbr::FileDescriptor.FromGeneratedCode(descriptorData, + new pbr::FileDescriptor[] { Bgs.Protocol.AttributeTypesReflection.Descriptor, Bgs.Protocol.EntityTypesReflection.Descriptor, }, + new pbr::GeneratedClrTypeInfo(null, new pbr::GeneratedClrTypeInfo[] { + new pbr::GeneratedClrTypeInfo(typeof(Bgs.Protocol.GameUtilities.V1.PlayerVariables), Bgs.Protocol.GameUtilities.V1.PlayerVariables.Parser, new[]{ "Identity", "Rating", "Attribute" }, null, null, null), + new pbr::GeneratedClrTypeInfo(typeof(Bgs.Protocol.GameUtilities.V1.ClientInfo), Bgs.Protocol.GameUtilities.V1.ClientInfo.Parser, new[]{ "ClientAddress", "PrivilegedNetwork" }, null, null, null) + })); + } + #endregion + + } + #region Messages + [System.Diagnostics.DebuggerNonUserCodeAttribute()] + public sealed partial class PlayerVariables : pb::IMessage { + private static readonly pb::MessageParser _parser = new pb::MessageParser(() => new PlayerVariables()); + public static pb::MessageParser Parser { get { return _parser; } } + + public static pbr::MessageDescriptor Descriptor { + get { return Bgs.Protocol.GameUtilities.V1.GameUtilitiesTypesReflection.Descriptor.MessageTypes[0]; } + } + + pbr::MessageDescriptor pb::IMessage.Descriptor { + get { return Descriptor; } + } + + public PlayerVariables() { + OnConstruction(); + } + + partial void OnConstruction(); + + public PlayerVariables(PlayerVariables other) : this() { + Identity = other.identity_ != null ? other.Identity.Clone() : null; + rating_ = other.rating_; + attribute_ = other.attribute_.Clone(); + } + + public PlayerVariables Clone() { + return new PlayerVariables(this); + } + + /// Field number for the "identity" field. + public const int IdentityFieldNumber = 1; + private Bgs.Protocol.Identity identity_; + public Bgs.Protocol.Identity Identity { + get { return identity_; } + set { + identity_ = value; + } + } + + /// Field number for the "rating" field. + public const int RatingFieldNumber = 2; + private double rating_; + public double Rating { + get { return rating_; } + set { + rating_ = value; + } + } + + /// Field number for the "attribute" field. + public const int AttributeFieldNumber = 3; + private static readonly pb::FieldCodec _repeated_attribute_codec + = pb::FieldCodec.ForMessage(26, Bgs.Protocol.Attribute.Parser); + private readonly pbc::RepeatedField attribute_ = new pbc::RepeatedField(); + public pbc::RepeatedField Attribute { + get { return attribute_; } + } + + public override bool Equals(object other) { + return Equals(other as PlayerVariables); + } + + public bool Equals(PlayerVariables other) { + if (ReferenceEquals(other, null)) { + return false; + } + if (ReferenceEquals(other, this)) { + return true; + } + if (!object.Equals(Identity, other.Identity)) return false; + if (Rating != other.Rating) return false; + if(!attribute_.Equals(other.attribute_)) return false; + return true; + } + + public override int GetHashCode() { + int hash = 1; + if (identity_ != null) hash ^= Identity.GetHashCode(); + if (Rating != 0D) hash ^= Rating.GetHashCode(); + hash ^= attribute_.GetHashCode(); + return hash; + } + + public override string ToString() { + return pb::JsonFormatter.ToDiagnosticString(this); + } + + public void WriteTo(pb::CodedOutputStream output) { + if (identity_ != null) { + output.WriteRawTag(10); + output.WriteMessage(Identity); + } + if (Rating != 0D) { + output.WriteRawTag(17); + output.WriteDouble(Rating); + } + attribute_.WriteTo(output, _repeated_attribute_codec); + } + + public int CalculateSize() { + int size = 0; + if (identity_ != null) { + size += 1 + pb::CodedOutputStream.ComputeMessageSize(Identity); + } + if (Rating != 0D) { + size += 1 + 8; + } + size += attribute_.CalculateSize(_repeated_attribute_codec); + return size; + } + + public void MergeFrom(PlayerVariables other) { + if (other == null) { + return; + } + if (other.identity_ != null) { + if (identity_ == null) { + identity_ = new Bgs.Protocol.Identity(); + } + Identity.MergeFrom(other.Identity); + } + if (other.Rating != 0D) { + Rating = other.Rating; + } + attribute_.Add(other.attribute_); + } + + public void MergeFrom(pb::CodedInputStream input) { + uint tag; + while ((tag = input.ReadTag()) != 0) { + switch(tag) { + default: + input.SkipLastField(); + break; + case 10: { + if (identity_ == null) { + identity_ = new Bgs.Protocol.Identity(); + } + input.ReadMessage(identity_); + break; + } + case 17: { + Rating = input.ReadDouble(); + break; + } + case 26: { + attribute_.AddEntriesFrom(input, _repeated_attribute_codec); + break; + } + } + } + } + + } + + [System.Diagnostics.DebuggerNonUserCodeAttribute()] + public sealed partial class ClientInfo : pb::IMessage { + private static readonly pb::MessageParser _parser = new pb::MessageParser(() => new ClientInfo()); + public static pb::MessageParser Parser { get { return _parser; } } + + public static pbr::MessageDescriptor Descriptor { + get { return Bgs.Protocol.GameUtilities.V1.GameUtilitiesTypesReflection.Descriptor.MessageTypes[1]; } + } + + pbr::MessageDescriptor pb::IMessage.Descriptor { + get { return Descriptor; } + } + + public ClientInfo() { + OnConstruction(); + } + + partial void OnConstruction(); + + public ClientInfo(ClientInfo other) : this() { + clientAddress_ = other.clientAddress_; + privilegedNetwork_ = other.privilegedNetwork_; + } + + public ClientInfo Clone() { + return new ClientInfo(this); + } + + /// Field number for the "client_address" field. + public const int ClientAddressFieldNumber = 1; + private string clientAddress_ = ""; + public string ClientAddress { + get { return clientAddress_; } + set { + clientAddress_ = pb::ProtoPreconditions.CheckNotNull(value, "value"); + } + } + + /// Field number for the "privileged_network" field. + public const int PrivilegedNetworkFieldNumber = 2; + private bool privilegedNetwork_; + public bool PrivilegedNetwork { + get { return privilegedNetwork_; } + set { + privilegedNetwork_ = value; + } + } + + public override bool Equals(object other) { + return Equals(other as ClientInfo); + } + + public bool Equals(ClientInfo other) { + if (ReferenceEquals(other, null)) { + return false; + } + if (ReferenceEquals(other, this)) { + return true; + } + if (ClientAddress != other.ClientAddress) return false; + if (PrivilegedNetwork != other.PrivilegedNetwork) return false; + return true; + } + + public override int GetHashCode() { + int hash = 1; + if (ClientAddress.Length != 0) hash ^= ClientAddress.GetHashCode(); + if (PrivilegedNetwork != false) hash ^= PrivilegedNetwork.GetHashCode(); + return hash; + } + + public override string ToString() { + return pb::JsonFormatter.ToDiagnosticString(this); + } + + public void WriteTo(pb::CodedOutputStream output) { + if (ClientAddress.Length != 0) { + output.WriteRawTag(10); + output.WriteString(ClientAddress); + } + if (PrivilegedNetwork != false) { + output.WriteRawTag(16); + output.WriteBool(PrivilegedNetwork); + } + } + + public int CalculateSize() { + int size = 0; + if (ClientAddress.Length != 0) { + size += 1 + pb::CodedOutputStream.ComputeStringSize(ClientAddress); + } + if (PrivilegedNetwork != false) { + size += 1 + 1; + } + return size; + } + + public void MergeFrom(ClientInfo other) { + if (other == null) { + return; + } + if (other.ClientAddress.Length != 0) { + ClientAddress = other.ClientAddress; + } + if (other.PrivilegedNetwork != false) { + PrivilegedNetwork = other.PrivilegedNetwork; + } + } + + public void MergeFrom(pb::CodedInputStream input) { + uint tag; + while ((tag = input.ReadTag()) != 0) { + switch(tag) { + default: + input.SkipLastField(); + break; + case 10: { + ClientAddress = input.ReadString(); + break; + } + case 16: { + PrivilegedNetwork = input.ReadBool(); + break; + } + } + } + } + + } + + #endregion + +} + +#endregion Designer generated code diff --git a/Framework/Proto/GlobalExtensions/FieldOptions.cs b/Framework/Proto/GlobalExtensions/FieldOptions.cs new file mode 100644 index 000000000..eaaf9c99e --- /dev/null +++ b/Framework/Proto/GlobalExtensions/FieldOptions.cs @@ -0,0 +1,48 @@ +// Generated by the protocol buffer compiler. DO NOT EDIT! +// source: bgs/low/pb/client/global_extensions/field_options.proto +#pragma warning disable 1591, 0612, 3021 +#region Designer generated code + +using pbr = Google.Protobuf.Reflection; +namespace Bgs.Protocol +{ + + /// Holder for reflection information generated from bgs/low/pb/client/global_extensions/field_options.proto + [System.Diagnostics.DebuggerNonUserCodeAttribute()] + public static partial class FieldOptionsReflection { + + #region Descriptor + /// File descriptor for bgs/low/pb/client/global_extensions/field_options.proto + public static pbr::FileDescriptor Descriptor { + get { return descriptor; } + } + private static pbr::FileDescriptor descriptor; + + static FieldOptionsReflection() { + byte[] descriptorData = System.Convert.FromBase64String( + string.Concat( + "CjdiZ3MvbG93L3BiL2NsaWVudC9nbG9iYWxfZXh0ZW5zaW9ucy9maWVsZF9v", + "cHRpb25zLnByb3RvEgxiZ3MucHJvdG9jb2waIGdvb2dsZS9wcm90b2J1Zi9k", + "ZXNjcmlwdG9yLnByb3RvKioKCUxvZ09wdGlvbhIICgROT05FEAASCgoGSElE", + "REVOEAESBwoDSEVYEAI6RQoDbG9nEh0uZ29vZ2xlLnByb3RvYnVmLkZpZWxk", + "T3B0aW9ucxjQhgMgASgOMhcuYmdzLnByb3RvY29sLkxvZ09wdGlvbkIkCg1i", + "bmV0LnByb3RvY29sQhFGaWVsZE9wdGlvbnNQcm90b0gCYgZwcm90bzM=")); + descriptor = pbr::FileDescriptor.FromGeneratedCode(descriptorData, + new pbr::FileDescriptor[] { pbr::FileDescriptor.DescriptorProtoFileDescriptor, }, + new pbr::GeneratedClrTypeInfo(new[] {typeof(Bgs.Protocol.LogOption), }, null)); + } + #endregion + + } + #region Enums + public enum LogOption { + NONE = 0, + HIDDEN = 1, + HEX = 2, + } + + #endregion + +} + +#endregion Designer generated code diff --git a/Framework/Proto/GlobalExtensions/MethodOptions.cs b/Framework/Proto/GlobalExtensions/MethodOptions.cs new file mode 100644 index 000000000..800557cca --- /dev/null +++ b/Framework/Proto/GlobalExtensions/MethodOptions.cs @@ -0,0 +1,38 @@ +// Generated by the protocol buffer compiler. DO NOT EDIT! +// source: bgs/low/pb/client/global_extensions/method_options.proto +#pragma warning disable 1591, 0612, 3021 +#region Designer generated code + +using pbr = Google.Protobuf.Reflection; +namespace Bgs.Protocol +{ + + /// Holder for reflection information generated from bgs/low/pb/client/global_extensions/method_options.proto + [System.Diagnostics.DebuggerNonUserCodeAttribute()] + public static partial class MethodOptionsReflection { + + #region Descriptor + /// File descriptor for bgs/low/pb/client/global_extensions/method_options.proto + public static pbr::FileDescriptor Descriptor { + get { return descriptor; } + } + private static pbr::FileDescriptor descriptor; + + static MethodOptionsReflection() { + byte[] descriptorData = System.Convert.FromBase64String( + string.Concat( + "CjhiZ3MvbG93L3BiL2NsaWVudC9nbG9iYWxfZXh0ZW5zaW9ucy9tZXRob2Rf", + "b3B0aW9ucy5wcm90bxIMYmdzLnByb3RvY29sGiBnb29nbGUvcHJvdG9idWYv", + "ZGVzY3JpcHRvci5wcm90bzozCgltZXRob2RfaWQSHi5nb29nbGUucHJvdG9i", + "dWYuTWV0aG9kT3B0aW9ucxjQhgMgASgNQiUKDWJuZXQucHJvdG9jb2xCEk1l", + "dGhvZE9wdGlvbnNQcm90b0gCYgZwcm90bzM=")); + descriptor = pbr::FileDescriptor.FromGeneratedCode(descriptorData, + new pbr::FileDescriptor[] { pbr::FileDescriptor.DescriptorProtoFileDescriptor, }, + new pbr::GeneratedClrTypeInfo(null, null)); + } + #endregion + + } +} + +#endregion Designer generated code diff --git a/Framework/Proto/GlobalExtensions/ServiceOptions.cs b/Framework/Proto/GlobalExtensions/ServiceOptions.cs new file mode 100644 index 000000000..c0403b888 --- /dev/null +++ b/Framework/Proto/GlobalExtensions/ServiceOptions.cs @@ -0,0 +1,40 @@ +// Generated by the protocol buffer compiler. DO NOT EDIT! +// source: bgs/low/pb/client/global_extensions/service_options.proto +#pragma warning disable 1591, 0612, 3021 +#region Designer generated code + +using pbr = Google.Protobuf.Reflection; +namespace Bgs.Protocol +{ + + /// Holder for reflection information generated from bgs/low/pb/client/global_extensions/service_options.proto + [System.Diagnostics.DebuggerNonUserCodeAttribute()] + public static partial class ServiceOptionsReflection { + + #region Descriptor + /// File descriptor for bgs/low/pb/client/global_extensions/service_options.proto + public static pbr::FileDescriptor Descriptor { + get { return descriptor; } + } + private static pbr::FileDescriptor descriptor; + + static ServiceOptionsReflection() { + byte[] descriptorData = System.Convert.FromBase64String( + string.Concat( + "CjliZ3MvbG93L3BiL2NsaWVudC9nbG9iYWxfZXh0ZW5zaW9ucy9zZXJ2aWNl", + "X29wdGlvbnMucHJvdG8SDGJncy5wcm90b2NvbBogZ29vZ2xlL3Byb3RvYnVm", + "L2Rlc2NyaXB0b3IucHJvdG86Ugoob3JpZ2luYWxfZnVsbHlfcXVhbGlmaWVk", + "X2Rlc2NyaXB0b3JfbmFtZRIfLmdvb2dsZS5wcm90b2J1Zi5TZXJ2aWNlT3B0", + "aW9ucxjpByABKAk6NQoKc2VydmljZV9pZBIfLmdvb2dsZS5wcm90b2J1Zi5T", + "ZXJ2aWNlT3B0aW9ucxjQhgMgASgNQiYKDWJuZXQucHJvdG9jb2xCE1NlcnZp", + "Y2VPcHRpb25zUHJvdG9IAmIGcHJvdG8z")); + descriptor = pbr::FileDescriptor.FromGeneratedCode(descriptorData, + new pbr::FileDescriptor[] { pbr::FileDescriptor.DescriptorProtoFileDescriptor, }, + new pbr::GeneratedClrTypeInfo(null, null)); + } + #endregion + + } +} + +#endregion Designer generated code diff --git a/Framework/Proto/InvitationTypes.cs b/Framework/Proto/InvitationTypes.cs new file mode 100644 index 000000000..5949ecd13 --- /dev/null +++ b/Framework/Proto/InvitationTypes.cs @@ -0,0 +1,1751 @@ +// Generated by the protocol buffer compiler. DO NOT EDIT! +// source: bgs/low/pb/client/invitation_types.proto +#pragma warning disable 1591, 0612, 3021 +#region Designer generated code + +using pb = Google.Protobuf; +using pbc = Google.Protobuf.Collections; +using pbr = Google.Protobuf.Reflection; +namespace Bgs.Protocol +{ + + /// Holder for reflection information generated from bgs/low/pb/client/invitation_types.proto + [System.Diagnostics.DebuggerNonUserCodeAttribute()] + public static partial class InvitationTypesReflection { + + #region Descriptor + /// File descriptor for bgs/low/pb/client/invitation_types.proto + public static pbr::FileDescriptor Descriptor { + get { return descriptor; } + } + private static pbr::FileDescriptor descriptor; + + static InvitationTypesReflection() { + byte[] descriptorData = System.Convert.FromBase64String( + string.Concat( + "CihiZ3MvbG93L3BiL2NsaWVudC9pbnZpdGF0aW9uX3R5cGVzLnByb3RvEgxi", + "Z3MucHJvdG9jb2waJGJncy9sb3cvcGIvY2xpZW50L2VudGl0eV90eXBlcy5w", + "cm90byL0AQoKSW52aXRhdGlvbhIKCgJpZBgBIAEoBhIwChBpbnZpdGVyX2lk", + "ZW50aXR5GAIgASgLMhYuYmdzLnByb3RvY29sLklkZW50aXR5EjAKEGludml0", + "ZWVfaWRlbnRpdHkYAyABKAsyFi5iZ3MucHJvdG9jb2wuSWRlbnRpdHkSFAoM", + "aW52aXRlcl9uYW1lGAQgASgJEhQKDGludml0ZWVfbmFtZRgFIAEoCRIaChJp", + "bnZpdGF0aW9uX21lc3NhZ2UYBiABKAkSFQoNY3JlYXRpb25fdGltZRgHIAEo", + "BBIXCg9leHBpcmF0aW9uX3RpbWUYCCABKAQihAIKFEludml0YXRpb25TdWdn", + "ZXN0aW9uEioKCmNoYW5uZWxfaWQYASABKAsyFi5iZ3MucHJvdG9jb2wuRW50", + "aXR5SWQSLAoMc3VnZ2VzdGVyX2lkGAIgASgLMhYuYmdzLnByb3RvY29sLkVu", + "dGl0eUlkEiwKDHN1Z2dlc3RlZV9pZBgDIAEoCzIWLmJncy5wcm90b2NvbC5F", + "bnRpdHlJZBIWCg5zdWdnZXN0ZXJfbmFtZRgEIAEoCRIWCg5zdWdnZXN0ZWVf", + "bmFtZRgFIAEoCRI0ChRzdWdnZXN0ZXJfYWNjb3VudF9pZBgGIAEoCzIWLmJn", + "cy5wcm90b2NvbC5FbnRpdHlJZCJfChBJbnZpdGF0aW9uVGFyZ2V0EigKCGlk", + "ZW50aXR5GAEgASgLMhYuYmdzLnByb3RvY29sLklkZW50aXR5Eg0KBWVtYWls", + "GAIgASgJEhIKCmJhdHRsZV90YWcYAyABKAkiRwoQSW52aXRhdGlvblBhcmFt", + "cxIaChJpbnZpdGF0aW9uX21lc3NhZ2UYASABKAkSFwoPZXhwaXJhdGlvbl90", + "aW1lGAIgASgEIoUCChVTZW5kSW52aXRhdGlvblJlcXVlc3QSLgoOYWdlbnRf", + "aWRlbnRpdHkYASABKAsyFi5iZ3MucHJvdG9jb2wuSWRlbnRpdHkSLQoJdGFy", + "Z2V0X2lkGAIgASgLMhYuYmdzLnByb3RvY29sLkVudGl0eUlkQgIYARIuCgZw", + "YXJhbXMYAyABKAsyHi5iZ3MucHJvdG9jb2wuSW52aXRhdGlvblBhcmFtcxIt", + "CgphZ2VudF9pbmZvGAQgASgLMhkuYmdzLnByb3RvY29sLkFjY291bnRJbmZv", + "Ei4KBnRhcmdldBgFIAEoCzIeLmJncy5wcm90b2NvbC5JbnZpdGF0aW9uVGFy", + "Z2V0IkYKFlNlbmRJbnZpdGF0aW9uUmVzcG9uc2USLAoKaW52aXRhdGlvbhgC", + "IAEoCzIYLmJncy5wcm90b2NvbC5JbnZpdGF0aW9uIpABChdVcGRhdGVJbnZp", + "dGF0aW9uUmVxdWVzdBIuCg5hZ2VudF9pZGVudGl0eRgBIAEoCzIWLmJncy5w", + "cm90b2NvbC5JZGVudGl0eRIVCg1pbnZpdGF0aW9uX2lkGAIgASgGEi4KBnBh", + "cmFtcxgDIAEoCzIeLmJncy5wcm90b2NvbC5JbnZpdGF0aW9uUGFyYW1zIvcB", + "ChhHZW5lcmljSW52aXRhdGlvblJlcXVlc3QSKAoIYWdlbnRfaWQYASABKAsy", + "Fi5iZ3MucHJvdG9jb2wuRW50aXR5SWQSKQoJdGFyZ2V0X2lkGAIgASgLMhYu", + "YmdzLnByb3RvY29sLkVudGl0eUlkEhUKDWludml0YXRpb25faWQYAyABKAYS", + "FAoMaW52aXRlZV9uYW1lGAQgASgJEhQKDGludml0ZXJfbmFtZRgFIAEoCRIZ", + "Cg1wcmV2aW91c19yb2xlGAYgAygNQgIQARIYCgxkZXNpcmVkX3JvbGUYByAD", + "KA1CAhABEg4KBnJlYXNvbhgIIAEoDUInCg1ibmV0LnByb3RvY29sQhRJbnZp", + "dGF0aW9uVHlwZXNQcm90b0gCYgZwcm90bzM=")); + descriptor = pbr::FileDescriptor.FromGeneratedCode(descriptorData, + new pbr::FileDescriptor[] { Bgs.Protocol.EntityTypesReflection.Descriptor, }, + new pbr::GeneratedClrTypeInfo(null, new pbr::GeneratedClrTypeInfo[] { + new pbr::GeneratedClrTypeInfo(typeof(Bgs.Protocol.Invitation), Bgs.Protocol.Invitation.Parser, new[]{ "Id", "InviterIdentity", "InviteeIdentity", "InviterName", "InviteeName", "InvitationMessage", "CreationTime", "ExpirationTime" }, null, null, null), + new pbr::GeneratedClrTypeInfo(typeof(Bgs.Protocol.InvitationSuggestion), Bgs.Protocol.InvitationSuggestion.Parser, new[]{ "ChannelId", "SuggesterId", "SuggesteeId", "SuggesterName", "SuggesteeName", "SuggesterAccountId" }, null, null, null), + new pbr::GeneratedClrTypeInfo(typeof(Bgs.Protocol.InvitationTarget), Bgs.Protocol.InvitationTarget.Parser, new[]{ "Identity", "Email", "BattleTag" }, null, null, null), + new pbr::GeneratedClrTypeInfo(typeof(Bgs.Protocol.InvitationParams), Bgs.Protocol.InvitationParams.Parser, new[]{ "InvitationMessage", "ExpirationTime" }, null, null, null), + new pbr::GeneratedClrTypeInfo(typeof(Bgs.Protocol.SendInvitationRequest), Bgs.Protocol.SendInvitationRequest.Parser, new[]{ "AgentIdentity", "TargetId", "Params", "AgentInfo", "Target" }, null, null, null), + new pbr::GeneratedClrTypeInfo(typeof(Bgs.Protocol.SendInvitationResponse), Bgs.Protocol.SendInvitationResponse.Parser, new[]{ "Invitation" }, null, null, null), + new pbr::GeneratedClrTypeInfo(typeof(Bgs.Protocol.UpdateInvitationRequest), Bgs.Protocol.UpdateInvitationRequest.Parser, new[]{ "AgentIdentity", "InvitationId", "Params" }, null, null, null), + new pbr::GeneratedClrTypeInfo(typeof(Bgs.Protocol.GenericInvitationRequest), Bgs.Protocol.GenericInvitationRequest.Parser, new[]{ "AgentId", "TargetId", "InvitationId", "InviteeName", "InviterName", "PreviousRole", "DesiredRole", "Reason" }, null, null, null) + })); + } + #endregion + + } + #region Messages + [System.Diagnostics.DebuggerNonUserCodeAttribute()] + public sealed partial class Invitation : pb::IMessage { + private static readonly pb::MessageParser _parser = new pb::MessageParser(() => new Invitation()); + public static pb::MessageParser Parser { get { return _parser; } } + + public static pbr::MessageDescriptor Descriptor { + get { return Bgs.Protocol.InvitationTypesReflection.Descriptor.MessageTypes[0]; } + } + + pbr::MessageDescriptor pb::IMessage.Descriptor { + get { return Descriptor; } + } + + public Invitation() { + OnConstruction(); + } + + partial void OnConstruction(); + + public Invitation(Invitation other) : this() { + id_ = other.id_; + InviterIdentity = other.inviterIdentity_ != null ? other.InviterIdentity.Clone() : null; + InviteeIdentity = other.inviteeIdentity_ != null ? other.InviteeIdentity.Clone() : null; + inviterName_ = other.inviterName_; + inviteeName_ = other.inviteeName_; + invitationMessage_ = other.invitationMessage_; + creationTime_ = other.creationTime_; + expirationTime_ = other.expirationTime_; + } + + public Invitation Clone() { + return new Invitation(this); + } + + /// Field number for the "id" field. + public const int IdFieldNumber = 1; + private ulong id_; + public ulong Id { + get { return id_; } + set { + id_ = value; + } + } + + /// Field number for the "inviter_identity" field. + public const int InviterIdentityFieldNumber = 2; + private Bgs.Protocol.Identity inviterIdentity_; + public Bgs.Protocol.Identity InviterIdentity { + get { return inviterIdentity_; } + set { + inviterIdentity_ = value; + } + } + + /// Field number for the "invitee_identity" field. + public const int InviteeIdentityFieldNumber = 3; + private Bgs.Protocol.Identity inviteeIdentity_; + public Bgs.Protocol.Identity InviteeIdentity { + get { return inviteeIdentity_; } + set { + inviteeIdentity_ = value; + } + } + + /// Field number for the "inviter_name" field. + public const int InviterNameFieldNumber = 4; + private string inviterName_ = ""; + public string InviterName { + get { return inviterName_; } + set { + inviterName_ = pb::ProtoPreconditions.CheckNotNull(value, "value"); + } + } + + /// Field number for the "invitee_name" field. + public const int InviteeNameFieldNumber = 5; + private string inviteeName_ = ""; + public string InviteeName { + get { return inviteeName_; } + set { + inviteeName_ = pb::ProtoPreconditions.CheckNotNull(value, "value"); + } + } + + /// Field number for the "invitation_message" field. + public const int InvitationMessageFieldNumber = 6; + private string invitationMessage_ = ""; + public string InvitationMessage { + get { return invitationMessage_; } + set { + invitationMessage_ = pb::ProtoPreconditions.CheckNotNull(value, "value"); + } + } + + /// Field number for the "creation_time" field. + public const int CreationTimeFieldNumber = 7; + private ulong creationTime_; + public ulong CreationTime { + get { return creationTime_; } + set { + creationTime_ = value; + } + } + + /// Field number for the "expiration_time" field. + public const int ExpirationTimeFieldNumber = 8; + private ulong expirationTime_; + public ulong ExpirationTime { + get { return expirationTime_; } + set { + expirationTime_ = value; + } + } + + public override bool Equals(object other) { + return Equals(other as Invitation); + } + + public bool Equals(Invitation other) { + if (ReferenceEquals(other, null)) { + return false; + } + if (ReferenceEquals(other, this)) { + return true; + } + if (Id != other.Id) return false; + if (!object.Equals(InviterIdentity, other.InviterIdentity)) return false; + if (!object.Equals(InviteeIdentity, other.InviteeIdentity)) return false; + if (InviterName != other.InviterName) return false; + if (InviteeName != other.InviteeName) return false; + if (InvitationMessage != other.InvitationMessage) return false; + if (CreationTime != other.CreationTime) return false; + if (ExpirationTime != other.ExpirationTime) return false; + return true; + } + + public override int GetHashCode() { + int hash = 1; + if (Id != 0UL) hash ^= Id.GetHashCode(); + if (inviterIdentity_ != null) hash ^= InviterIdentity.GetHashCode(); + if (inviteeIdentity_ != null) hash ^= InviteeIdentity.GetHashCode(); + if (InviterName.Length != 0) hash ^= InviterName.GetHashCode(); + if (InviteeName.Length != 0) hash ^= InviteeName.GetHashCode(); + if (InvitationMessage.Length != 0) hash ^= InvitationMessage.GetHashCode(); + if (CreationTime != 0UL) hash ^= CreationTime.GetHashCode(); + if (ExpirationTime != 0UL) hash ^= ExpirationTime.GetHashCode(); + return hash; + } + + public override string ToString() { + return pb::JsonFormatter.ToDiagnosticString(this); + } + + public void WriteTo(pb::CodedOutputStream output) { + if (Id != 0UL) { + output.WriteRawTag(9); + output.WriteFixed64(Id); + } + if (inviterIdentity_ != null) { + output.WriteRawTag(18); + output.WriteMessage(InviterIdentity); + } + if (inviteeIdentity_ != null) { + output.WriteRawTag(26); + output.WriteMessage(InviteeIdentity); + } + if (InviterName.Length != 0) { + output.WriteRawTag(34); + output.WriteString(InviterName); + } + if (InviteeName.Length != 0) { + output.WriteRawTag(42); + output.WriteString(InviteeName); + } + if (InvitationMessage.Length != 0) { + output.WriteRawTag(50); + output.WriteString(InvitationMessage); + } + if (CreationTime != 0UL) { + output.WriteRawTag(56); + output.WriteUInt64(CreationTime); + } + if (ExpirationTime != 0UL) { + output.WriteRawTag(64); + output.WriteUInt64(ExpirationTime); + } + } + + public int CalculateSize() { + int size = 0; + if (Id != 0UL) { + size += 1 + 8; + } + if (inviterIdentity_ != null) { + size += 1 + pb::CodedOutputStream.ComputeMessageSize(InviterIdentity); + } + if (inviteeIdentity_ != null) { + size += 1 + pb::CodedOutputStream.ComputeMessageSize(InviteeIdentity); + } + if (InviterName.Length != 0) { + size += 1 + pb::CodedOutputStream.ComputeStringSize(InviterName); + } + if (InviteeName.Length != 0) { + size += 1 + pb::CodedOutputStream.ComputeStringSize(InviteeName); + } + if (InvitationMessage.Length != 0) { + size += 1 + pb::CodedOutputStream.ComputeStringSize(InvitationMessage); + } + if (CreationTime != 0UL) { + size += 1 + pb::CodedOutputStream.ComputeUInt64Size(CreationTime); + } + if (ExpirationTime != 0UL) { + size += 1 + pb::CodedOutputStream.ComputeUInt64Size(ExpirationTime); + } + return size; + } + + public void MergeFrom(Invitation other) { + if (other == null) { + return; + } + if (other.Id != 0UL) { + Id = other.Id; + } + if (other.inviterIdentity_ != null) { + if (inviterIdentity_ == null) { + inviterIdentity_ = new Bgs.Protocol.Identity(); + } + InviterIdentity.MergeFrom(other.InviterIdentity); + } + if (other.inviteeIdentity_ != null) { + if (inviteeIdentity_ == null) { + inviteeIdentity_ = new Bgs.Protocol.Identity(); + } + InviteeIdentity.MergeFrom(other.InviteeIdentity); + } + if (other.InviterName.Length != 0) { + InviterName = other.InviterName; + } + if (other.InviteeName.Length != 0) { + InviteeName = other.InviteeName; + } + if (other.InvitationMessage.Length != 0) { + InvitationMessage = other.InvitationMessage; + } + if (other.CreationTime != 0UL) { + CreationTime = other.CreationTime; + } + if (other.ExpirationTime != 0UL) { + ExpirationTime = other.ExpirationTime; + } + } + + public void MergeFrom(pb::CodedInputStream input) { + uint tag; + while ((tag = input.ReadTag()) != 0) { + switch(tag) { + default: + input.SkipLastField(); + break; + case 9: { + Id = input.ReadFixed64(); + break; + } + case 18: { + if (inviterIdentity_ == null) { + inviterIdentity_ = new Bgs.Protocol.Identity(); + } + input.ReadMessage(inviterIdentity_); + break; + } + case 26: { + if (inviteeIdentity_ == null) { + inviteeIdentity_ = new Bgs.Protocol.Identity(); + } + input.ReadMessage(inviteeIdentity_); + break; + } + case 34: { + InviterName = input.ReadString(); + break; + } + case 42: { + InviteeName = input.ReadString(); + break; + } + case 50: { + InvitationMessage = input.ReadString(); + break; + } + case 56: { + CreationTime = input.ReadUInt64(); + break; + } + case 64: { + ExpirationTime = input.ReadUInt64(); + break; + } + } + } + } + + } + + [System.Diagnostics.DebuggerNonUserCodeAttribute()] + public sealed partial class InvitationSuggestion : pb::IMessage { + private static readonly pb::MessageParser _parser = new pb::MessageParser(() => new InvitationSuggestion()); + public static pb::MessageParser Parser { get { return _parser; } } + + public static pbr::MessageDescriptor Descriptor { + get { return Bgs.Protocol.InvitationTypesReflection.Descriptor.MessageTypes[1]; } + } + + pbr::MessageDescriptor pb::IMessage.Descriptor { + get { return Descriptor; } + } + + public InvitationSuggestion() { + OnConstruction(); + } + + partial void OnConstruction(); + + public InvitationSuggestion(InvitationSuggestion other) : this() { + ChannelId = other.channelId_ != null ? other.ChannelId.Clone() : null; + SuggesterId = other.suggesterId_ != null ? other.SuggesterId.Clone() : null; + SuggesteeId = other.suggesteeId_ != null ? other.SuggesteeId.Clone() : null; + suggesterName_ = other.suggesterName_; + suggesteeName_ = other.suggesteeName_; + SuggesterAccountId = other.suggesterAccountId_ != null ? other.SuggesterAccountId.Clone() : null; + } + + public InvitationSuggestion Clone() { + return new InvitationSuggestion(this); + } + + /// Field number for the "channel_id" field. + public const int ChannelIdFieldNumber = 1; + private Bgs.Protocol.EntityId channelId_; + public Bgs.Protocol.EntityId ChannelId { + get { return channelId_; } + set { + channelId_ = value; + } + } + + /// Field number for the "suggester_id" field. + public const int SuggesterIdFieldNumber = 2; + private Bgs.Protocol.EntityId suggesterId_; + public Bgs.Protocol.EntityId SuggesterId { + get { return suggesterId_; } + set { + suggesterId_ = value; + } + } + + /// Field number for the "suggestee_id" field. + public const int SuggesteeIdFieldNumber = 3; + private Bgs.Protocol.EntityId suggesteeId_; + public Bgs.Protocol.EntityId SuggesteeId { + get { return suggesteeId_; } + set { + suggesteeId_ = value; + } + } + + /// Field number for the "suggester_name" field. + public const int SuggesterNameFieldNumber = 4; + private string suggesterName_ = ""; + public string SuggesterName { + get { return suggesterName_; } + set { + suggesterName_ = pb::ProtoPreconditions.CheckNotNull(value, "value"); + } + } + + /// Field number for the "suggestee_name" field. + public const int SuggesteeNameFieldNumber = 5; + private string suggesteeName_ = ""; + public string SuggesteeName { + get { return suggesteeName_; } + set { + suggesteeName_ = pb::ProtoPreconditions.CheckNotNull(value, "value"); + } + } + + /// Field number for the "suggester_account_id" field. + public const int SuggesterAccountIdFieldNumber = 6; + private Bgs.Protocol.EntityId suggesterAccountId_; + public Bgs.Protocol.EntityId SuggesterAccountId { + get { return suggesterAccountId_; } + set { + suggesterAccountId_ = value; + } + } + + public override bool Equals(object other) { + return Equals(other as InvitationSuggestion); + } + + public bool Equals(InvitationSuggestion other) { + if (ReferenceEquals(other, null)) { + return false; + } + if (ReferenceEquals(other, this)) { + return true; + } + if (!object.Equals(ChannelId, other.ChannelId)) return false; + if (!object.Equals(SuggesterId, other.SuggesterId)) return false; + if (!object.Equals(SuggesteeId, other.SuggesteeId)) return false; + if (SuggesterName != other.SuggesterName) return false; + if (SuggesteeName != other.SuggesteeName) return false; + if (!object.Equals(SuggesterAccountId, other.SuggesterAccountId)) return false; + return true; + } + + public override int GetHashCode() { + int hash = 1; + if (channelId_ != null) hash ^= ChannelId.GetHashCode(); + if (suggesterId_ != null) hash ^= SuggesterId.GetHashCode(); + if (suggesteeId_ != null) hash ^= SuggesteeId.GetHashCode(); + if (SuggesterName.Length != 0) hash ^= SuggesterName.GetHashCode(); + if (SuggesteeName.Length != 0) hash ^= SuggesteeName.GetHashCode(); + if (suggesterAccountId_ != null) hash ^= SuggesterAccountId.GetHashCode(); + return hash; + } + + public override string ToString() { + return pb::JsonFormatter.ToDiagnosticString(this); + } + + public void WriteTo(pb::CodedOutputStream output) { + if (channelId_ != null) { + output.WriteRawTag(10); + output.WriteMessage(ChannelId); + } + if (suggesterId_ != null) { + output.WriteRawTag(18); + output.WriteMessage(SuggesterId); + } + if (suggesteeId_ != null) { + output.WriteRawTag(26); + output.WriteMessage(SuggesteeId); + } + if (SuggesterName.Length != 0) { + output.WriteRawTag(34); + output.WriteString(SuggesterName); + } + if (SuggesteeName.Length != 0) { + output.WriteRawTag(42); + output.WriteString(SuggesteeName); + } + if (suggesterAccountId_ != null) { + output.WriteRawTag(50); + output.WriteMessage(SuggesterAccountId); + } + } + + public int CalculateSize() { + int size = 0; + if (channelId_ != null) { + size += 1 + pb::CodedOutputStream.ComputeMessageSize(ChannelId); + } + if (suggesterId_ != null) { + size += 1 + pb::CodedOutputStream.ComputeMessageSize(SuggesterId); + } + if (suggesteeId_ != null) { + size += 1 + pb::CodedOutputStream.ComputeMessageSize(SuggesteeId); + } + if (SuggesterName.Length != 0) { + size += 1 + pb::CodedOutputStream.ComputeStringSize(SuggesterName); + } + if (SuggesteeName.Length != 0) { + size += 1 + pb::CodedOutputStream.ComputeStringSize(SuggesteeName); + } + if (suggesterAccountId_ != null) { + size += 1 + pb::CodedOutputStream.ComputeMessageSize(SuggesterAccountId); + } + return size; + } + + public void MergeFrom(InvitationSuggestion other) { + if (other == null) { + return; + } + if (other.channelId_ != null) { + if (channelId_ == null) { + channelId_ = new Bgs.Protocol.EntityId(); + } + ChannelId.MergeFrom(other.ChannelId); + } + if (other.suggesterId_ != null) { + if (suggesterId_ == null) { + suggesterId_ = new Bgs.Protocol.EntityId(); + } + SuggesterId.MergeFrom(other.SuggesterId); + } + if (other.suggesteeId_ != null) { + if (suggesteeId_ == null) { + suggesteeId_ = new Bgs.Protocol.EntityId(); + } + SuggesteeId.MergeFrom(other.SuggesteeId); + } + if (other.SuggesterName.Length != 0) { + SuggesterName = other.SuggesterName; + } + if (other.SuggesteeName.Length != 0) { + SuggesteeName = other.SuggesteeName; + } + if (other.suggesterAccountId_ != null) { + if (suggesterAccountId_ == null) { + suggesterAccountId_ = new Bgs.Protocol.EntityId(); + } + SuggesterAccountId.MergeFrom(other.SuggesterAccountId); + } + } + + public void MergeFrom(pb::CodedInputStream input) { + uint tag; + while ((tag = input.ReadTag()) != 0) { + switch(tag) { + default: + input.SkipLastField(); + break; + case 10: { + if (channelId_ == null) { + channelId_ = new Bgs.Protocol.EntityId(); + } + input.ReadMessage(channelId_); + break; + } + case 18: { + if (suggesterId_ == null) { + suggesterId_ = new Bgs.Protocol.EntityId(); + } + input.ReadMessage(suggesterId_); + break; + } + case 26: { + if (suggesteeId_ == null) { + suggesteeId_ = new Bgs.Protocol.EntityId(); + } + input.ReadMessage(suggesteeId_); + break; + } + case 34: { + SuggesterName = input.ReadString(); + break; + } + case 42: { + SuggesteeName = input.ReadString(); + break; + } + case 50: { + if (suggesterAccountId_ == null) { + suggesterAccountId_ = new Bgs.Protocol.EntityId(); + } + input.ReadMessage(suggesterAccountId_); + break; + } + } + } + } + + } + + [System.Diagnostics.DebuggerNonUserCodeAttribute()] + public sealed partial class InvitationTarget : pb::IMessage { + private static readonly pb::MessageParser _parser = new pb::MessageParser(() => new InvitationTarget()); + public static pb::MessageParser Parser { get { return _parser; } } + + public static pbr::MessageDescriptor Descriptor { + get { return Bgs.Protocol.InvitationTypesReflection.Descriptor.MessageTypes[2]; } + } + + pbr::MessageDescriptor pb::IMessage.Descriptor { + get { return Descriptor; } + } + + public InvitationTarget() { + OnConstruction(); + } + + partial void OnConstruction(); + + public InvitationTarget(InvitationTarget other) : this() { + Identity = other.identity_ != null ? other.Identity.Clone() : null; + email_ = other.email_; + battleTag_ = other.battleTag_; + } + + public InvitationTarget Clone() { + return new InvitationTarget(this); + } + + /// Field number for the "identity" field. + public const int IdentityFieldNumber = 1; + private Bgs.Protocol.Identity identity_; + public Bgs.Protocol.Identity Identity { + get { return identity_; } + set { + identity_ = value; + } + } + + /// Field number for the "email" field. + public const int EmailFieldNumber = 2; + private string email_ = ""; + public string Email { + get { return email_; } + set { + email_ = pb::ProtoPreconditions.CheckNotNull(value, "value"); + } + } + + /// Field number for the "battle_tag" field. + public const int BattleTagFieldNumber = 3; + private string battleTag_ = ""; + public string BattleTag { + get { return battleTag_; } + set { + battleTag_ = pb::ProtoPreconditions.CheckNotNull(value, "value"); + } + } + + public override bool Equals(object other) { + return Equals(other as InvitationTarget); + } + + public bool Equals(InvitationTarget other) { + if (ReferenceEquals(other, null)) { + return false; + } + if (ReferenceEquals(other, this)) { + return true; + } + if (!object.Equals(Identity, other.Identity)) return false; + if (Email != other.Email) return false; + if (BattleTag != other.BattleTag) return false; + return true; + } + + public override int GetHashCode() { + int hash = 1; + if (identity_ != null) hash ^= Identity.GetHashCode(); + if (Email.Length != 0) hash ^= Email.GetHashCode(); + if (BattleTag.Length != 0) hash ^= BattleTag.GetHashCode(); + return hash; + } + + public override string ToString() { + return pb::JsonFormatter.ToDiagnosticString(this); + } + + public void WriteTo(pb::CodedOutputStream output) { + if (identity_ != null) { + output.WriteRawTag(10); + output.WriteMessage(Identity); + } + if (Email.Length != 0) { + output.WriteRawTag(18); + output.WriteString(Email); + } + if (BattleTag.Length != 0) { + output.WriteRawTag(26); + output.WriteString(BattleTag); + } + } + + public int CalculateSize() { + int size = 0; + if (identity_ != null) { + size += 1 + pb::CodedOutputStream.ComputeMessageSize(Identity); + } + if (Email.Length != 0) { + size += 1 + pb::CodedOutputStream.ComputeStringSize(Email); + } + if (BattleTag.Length != 0) { + size += 1 + pb::CodedOutputStream.ComputeStringSize(BattleTag); + } + return size; + } + + public void MergeFrom(InvitationTarget other) { + if (other == null) { + return; + } + if (other.identity_ != null) { + if (identity_ == null) { + identity_ = new Bgs.Protocol.Identity(); + } + Identity.MergeFrom(other.Identity); + } + if (other.Email.Length != 0) { + Email = other.Email; + } + if (other.BattleTag.Length != 0) { + BattleTag = other.BattleTag; + } + } + + public void MergeFrom(pb::CodedInputStream input) { + uint tag; + while ((tag = input.ReadTag()) != 0) { + switch(tag) { + default: + input.SkipLastField(); + break; + case 10: { + if (identity_ == null) { + identity_ = new Bgs.Protocol.Identity(); + } + input.ReadMessage(identity_); + break; + } + case 18: { + Email = input.ReadString(); + break; + } + case 26: { + BattleTag = input.ReadString(); + break; + } + } + } + } + + } + + [System.Diagnostics.DebuggerNonUserCodeAttribute()] + public sealed partial class InvitationParams : pb::IMessage { + private static readonly pb::MessageParser _parser = new pb::MessageParser(() => new InvitationParams()); + public static pb::MessageParser Parser { get { return _parser; } } + + public static pbr::MessageDescriptor Descriptor { + get { return Bgs.Protocol.InvitationTypesReflection.Descriptor.MessageTypes[3]; } + } + + pbr::MessageDescriptor pb::IMessage.Descriptor { + get { return Descriptor; } + } + + public InvitationParams() { + OnConstruction(); + } + + partial void OnConstruction(); + + public InvitationParams(InvitationParams other) : this() { + invitationMessage_ = other.invitationMessage_; + expirationTime_ = other.expirationTime_; + } + + public InvitationParams Clone() { + return new InvitationParams(this); + } + + /// Field number for the "invitation_message" field. + public const int InvitationMessageFieldNumber = 1; + private string invitationMessage_ = ""; + public string InvitationMessage { + get { return invitationMessage_; } + set { + invitationMessage_ = pb::ProtoPreconditions.CheckNotNull(value, "value"); + } + } + + /// Field number for the "expiration_time" field. + public const int ExpirationTimeFieldNumber = 2; + private ulong expirationTime_; + public ulong ExpirationTime { + get { return expirationTime_; } + set { + expirationTime_ = value; + } + } + + public override bool Equals(object other) { + return Equals(other as InvitationParams); + } + + public bool Equals(InvitationParams other) { + if (ReferenceEquals(other, null)) { + return false; + } + if (ReferenceEquals(other, this)) { + return true; + } + if (InvitationMessage != other.InvitationMessage) return false; + if (ExpirationTime != other.ExpirationTime) return false; + return true; + } + + public override int GetHashCode() { + int hash = 1; + if (InvitationMessage.Length != 0) hash ^= InvitationMessage.GetHashCode(); + if (ExpirationTime != 0UL) hash ^= ExpirationTime.GetHashCode(); + return hash; + } + + public override string ToString() { + return pb::JsonFormatter.ToDiagnosticString(this); + } + + public void WriteTo(pb::CodedOutputStream output) { + if (InvitationMessage.Length != 0) { + output.WriteRawTag(10); + output.WriteString(InvitationMessage); + } + if (ExpirationTime != 0UL) { + output.WriteRawTag(16); + output.WriteUInt64(ExpirationTime); + } + } + + public int CalculateSize() { + int size = 0; + if (InvitationMessage.Length != 0) { + size += 1 + pb::CodedOutputStream.ComputeStringSize(InvitationMessage); + } + if (ExpirationTime != 0UL) { + size += 1 + pb::CodedOutputStream.ComputeUInt64Size(ExpirationTime); + } + return size; + } + + public void MergeFrom(InvitationParams other) { + if (other == null) { + return; + } + if (other.InvitationMessage.Length != 0) { + InvitationMessage = other.InvitationMessage; + } + if (other.ExpirationTime != 0UL) { + ExpirationTime = other.ExpirationTime; + } + } + + public void MergeFrom(pb::CodedInputStream input) { + uint tag; + while ((tag = input.ReadTag()) != 0) { + switch(tag) { + default: + input.SkipLastField(); + break; + case 10: { + InvitationMessage = input.ReadString(); + break; + } + case 16: { + ExpirationTime = input.ReadUInt64(); + break; + } + } + } + } + + } + + [System.Diagnostics.DebuggerNonUserCodeAttribute()] + public sealed partial class SendInvitationRequest : pb::IMessage { + private static readonly pb::MessageParser _parser = new pb::MessageParser(() => new SendInvitationRequest()); + public static pb::MessageParser Parser { get { return _parser; } } + + public static pbr::MessageDescriptor Descriptor { + get { return Bgs.Protocol.InvitationTypesReflection.Descriptor.MessageTypes[4]; } + } + + pbr::MessageDescriptor pb::IMessage.Descriptor { + get { return Descriptor; } + } + + public SendInvitationRequest() { + OnConstruction(); + } + + partial void OnConstruction(); + + public SendInvitationRequest(SendInvitationRequest other) : this() { + AgentIdentity = other.agentIdentity_ != null ? other.AgentIdentity.Clone() : null; + TargetId = other.targetId_ != null ? other.TargetId.Clone() : null; + Params = other.params_ != null ? other.Params.Clone() : null; + AgentInfo = other.agentInfo_ != null ? other.AgentInfo.Clone() : null; + Target = other.target_ != null ? other.Target.Clone() : null; + } + + public SendInvitationRequest Clone() { + return new SendInvitationRequest(this); + } + + /// Field number for the "agent_identity" field. + public const int AgentIdentityFieldNumber = 1; + private Bgs.Protocol.Identity agentIdentity_; + public Bgs.Protocol.Identity AgentIdentity { + get { return agentIdentity_; } + set { + agentIdentity_ = value; + } + } + + /// Field number for the "target_id" field. + public const int TargetIdFieldNumber = 2; + private Bgs.Protocol.EntityId targetId_; + [System.ObsoleteAttribute()] + public Bgs.Protocol.EntityId TargetId { + get { return targetId_; } + set { + targetId_ = value; + } + } + + /// Field number for the "params" field. + public const int ParamsFieldNumber = 3; + private Bgs.Protocol.InvitationParams params_; + public Bgs.Protocol.InvitationParams Params { + get { return params_; } + set { + params_ = value; + } + } + + /// Field number for the "agent_info" field. + public const int AgentInfoFieldNumber = 4; + private Bgs.Protocol.AccountInfo agentInfo_; + public Bgs.Protocol.AccountInfo AgentInfo { + get { return agentInfo_; } + set { + agentInfo_ = value; + } + } + + /// Field number for the "target" field. + public const int TargetFieldNumber = 5; + private Bgs.Protocol.InvitationTarget target_; + public Bgs.Protocol.InvitationTarget Target { + get { return target_; } + set { + target_ = value; + } + } + + public override bool Equals(object other) { + return Equals(other as SendInvitationRequest); + } + + public bool Equals(SendInvitationRequest other) { + if (ReferenceEquals(other, null)) { + return false; + } + if (ReferenceEquals(other, this)) { + return true; + } + if (!object.Equals(AgentIdentity, other.AgentIdentity)) return false; + if (!object.Equals(TargetId, other.TargetId)) return false; + if (!object.Equals(Params, other.Params)) return false; + if (!object.Equals(AgentInfo, other.AgentInfo)) return false; + if (!object.Equals(Target, other.Target)) return false; + return true; + } + + public override int GetHashCode() { + int hash = 1; + if (agentIdentity_ != null) hash ^= AgentIdentity.GetHashCode(); + if (targetId_ != null) hash ^= TargetId.GetHashCode(); + if (params_ != null) hash ^= Params.GetHashCode(); + if (agentInfo_ != null) hash ^= AgentInfo.GetHashCode(); + if (target_ != null) hash ^= Target.GetHashCode(); + return hash; + } + + public override string ToString() { + return pb::JsonFormatter.ToDiagnosticString(this); + } + + public void WriteTo(pb::CodedOutputStream output) { + if (agentIdentity_ != null) { + output.WriteRawTag(10); + output.WriteMessage(AgentIdentity); + } + if (targetId_ != null) { + output.WriteRawTag(18); + output.WriteMessage(TargetId); + } + if (params_ != null) { + output.WriteRawTag(26); + output.WriteMessage(Params); + } + if (agentInfo_ != null) { + output.WriteRawTag(34); + output.WriteMessage(AgentInfo); + } + if (target_ != null) { + output.WriteRawTag(42); + output.WriteMessage(Target); + } + } + + public int CalculateSize() { + int size = 0; + if (agentIdentity_ != null) { + size += 1 + pb::CodedOutputStream.ComputeMessageSize(AgentIdentity); + } + if (targetId_ != null) { + size += 1 + pb::CodedOutputStream.ComputeMessageSize(TargetId); + } + if (params_ != null) { + size += 1 + pb::CodedOutputStream.ComputeMessageSize(Params); + } + if (agentInfo_ != null) { + size += 1 + pb::CodedOutputStream.ComputeMessageSize(AgentInfo); + } + if (target_ != null) { + size += 1 + pb::CodedOutputStream.ComputeMessageSize(Target); + } + return size; + } + + public void MergeFrom(SendInvitationRequest other) { + if (other == null) { + return; + } + if (other.agentIdentity_ != null) { + if (agentIdentity_ == null) { + agentIdentity_ = new Bgs.Protocol.Identity(); + } + AgentIdentity.MergeFrom(other.AgentIdentity); + } + if (other.targetId_ != null) { + if (targetId_ == null) { + targetId_ = new Bgs.Protocol.EntityId(); + } + TargetId.MergeFrom(other.TargetId); + } + if (other.params_ != null) { + if (params_ == null) { + params_ = new Bgs.Protocol.InvitationParams(); + } + Params.MergeFrom(other.Params); + } + if (other.agentInfo_ != null) { + if (agentInfo_ == null) { + agentInfo_ = new Bgs.Protocol.AccountInfo(); + } + AgentInfo.MergeFrom(other.AgentInfo); + } + if (other.target_ != null) { + if (target_ == null) { + target_ = new Bgs.Protocol.InvitationTarget(); + } + Target.MergeFrom(other.Target); + } + } + + public void MergeFrom(pb::CodedInputStream input) { + uint tag; + while ((tag = input.ReadTag()) != 0) { + switch(tag) { + default: + input.SkipLastField(); + break; + case 10: { + if (agentIdentity_ == null) { + agentIdentity_ = new Bgs.Protocol.Identity(); + } + input.ReadMessage(agentIdentity_); + break; + } + case 18: { + if (targetId_ == null) { + targetId_ = new Bgs.Protocol.EntityId(); + } + input.ReadMessage(targetId_); + break; + } + case 26: { + if (params_ == null) { + params_ = new Bgs.Protocol.InvitationParams(); + } + input.ReadMessage(params_); + break; + } + case 34: { + if (agentInfo_ == null) { + agentInfo_ = new Bgs.Protocol.AccountInfo(); + } + input.ReadMessage(agentInfo_); + break; + } + case 42: { + if (target_ == null) { + target_ = new Bgs.Protocol.InvitationTarget(); + } + input.ReadMessage(target_); + break; + } + } + } + } + + } + + [System.Diagnostics.DebuggerNonUserCodeAttribute()] + public sealed partial class SendInvitationResponse : pb::IMessage { + private static readonly pb::MessageParser _parser = new pb::MessageParser(() => new SendInvitationResponse()); + public static pb::MessageParser Parser { get { return _parser; } } + + public static pbr::MessageDescriptor Descriptor { + get { return Bgs.Protocol.InvitationTypesReflection.Descriptor.MessageTypes[5]; } + } + + pbr::MessageDescriptor pb::IMessage.Descriptor { + get { return Descriptor; } + } + + public SendInvitationResponse() { + OnConstruction(); + } + + partial void OnConstruction(); + + public SendInvitationResponse(SendInvitationResponse other) : this() { + Invitation = other.invitation_ != null ? other.Invitation.Clone() : null; + } + + public SendInvitationResponse Clone() { + return new SendInvitationResponse(this); + } + + /// Field number for the "invitation" field. + public const int InvitationFieldNumber = 2; + private Bgs.Protocol.Invitation invitation_; + public Bgs.Protocol.Invitation Invitation { + get { return invitation_; } + set { + invitation_ = value; + } + } + + public override bool Equals(object other) { + return Equals(other as SendInvitationResponse); + } + + public bool Equals(SendInvitationResponse other) { + if (ReferenceEquals(other, null)) { + return false; + } + if (ReferenceEquals(other, this)) { + return true; + } + if (!object.Equals(Invitation, other.Invitation)) return false; + return true; + } + + public override int GetHashCode() { + int hash = 1; + if (invitation_ != null) hash ^= Invitation.GetHashCode(); + return hash; + } + + public override string ToString() { + return pb::JsonFormatter.ToDiagnosticString(this); + } + + public void WriteTo(pb::CodedOutputStream output) { + if (invitation_ != null) { + output.WriteRawTag(18); + output.WriteMessage(Invitation); + } + } + + public int CalculateSize() { + int size = 0; + if (invitation_ != null) { + size += 1 + pb::CodedOutputStream.ComputeMessageSize(Invitation); + } + return size; + } + + public void MergeFrom(SendInvitationResponse other) { + if (other == null) { + return; + } + if (other.invitation_ != null) { + if (invitation_ == null) { + invitation_ = new Bgs.Protocol.Invitation(); + } + Invitation.MergeFrom(other.Invitation); + } + } + + public void MergeFrom(pb::CodedInputStream input) { + uint tag; + while ((tag = input.ReadTag()) != 0) { + switch(tag) { + default: + input.SkipLastField(); + break; + case 18: { + if (invitation_ == null) { + invitation_ = new Bgs.Protocol.Invitation(); + } + input.ReadMessage(invitation_); + break; + } + } + } + } + + } + + [System.Diagnostics.DebuggerNonUserCodeAttribute()] + public sealed partial class UpdateInvitationRequest : pb::IMessage { + private static readonly pb::MessageParser _parser = new pb::MessageParser(() => new UpdateInvitationRequest()); + public static pb::MessageParser Parser { get { return _parser; } } + + public static pbr::MessageDescriptor Descriptor { + get { return Bgs.Protocol.InvitationTypesReflection.Descriptor.MessageTypes[6]; } + } + + pbr::MessageDescriptor pb::IMessage.Descriptor { + get { return Descriptor; } + } + + public UpdateInvitationRequest() { + OnConstruction(); + } + + partial void OnConstruction(); + + public UpdateInvitationRequest(UpdateInvitationRequest other) : this() { + AgentIdentity = other.agentIdentity_ != null ? other.AgentIdentity.Clone() : null; + invitationId_ = other.invitationId_; + Params = other.params_ != null ? other.Params.Clone() : null; + } + + public UpdateInvitationRequest Clone() { + return new UpdateInvitationRequest(this); + } + + /// Field number for the "agent_identity" field. + public const int AgentIdentityFieldNumber = 1; + private Bgs.Protocol.Identity agentIdentity_; + public Bgs.Protocol.Identity AgentIdentity { + get { return agentIdentity_; } + set { + agentIdentity_ = value; + } + } + + /// Field number for the "invitation_id" field. + public const int InvitationIdFieldNumber = 2; + private ulong invitationId_; + public ulong InvitationId { + get { return invitationId_; } + set { + invitationId_ = value; + } + } + + /// Field number for the "params" field. + public const int ParamsFieldNumber = 3; + private Bgs.Protocol.InvitationParams params_; + public Bgs.Protocol.InvitationParams Params { + get { return params_; } + set { + params_ = value; + } + } + + public override bool Equals(object other) { + return Equals(other as UpdateInvitationRequest); + } + + public bool Equals(UpdateInvitationRequest other) { + if (ReferenceEquals(other, null)) { + return false; + } + if (ReferenceEquals(other, this)) { + return true; + } + if (!object.Equals(AgentIdentity, other.AgentIdentity)) return false; + if (InvitationId != other.InvitationId) return false; + if (!object.Equals(Params, other.Params)) return false; + return true; + } + + public override int GetHashCode() { + int hash = 1; + if (agentIdentity_ != null) hash ^= AgentIdentity.GetHashCode(); + if (InvitationId != 0UL) hash ^= InvitationId.GetHashCode(); + if (params_ != null) hash ^= Params.GetHashCode(); + return hash; + } + + public override string ToString() { + return pb::JsonFormatter.ToDiagnosticString(this); + } + + public void WriteTo(pb::CodedOutputStream output) { + if (agentIdentity_ != null) { + output.WriteRawTag(10); + output.WriteMessage(AgentIdentity); + } + if (InvitationId != 0UL) { + output.WriteRawTag(17); + output.WriteFixed64(InvitationId); + } + if (params_ != null) { + output.WriteRawTag(26); + output.WriteMessage(Params); + } + } + + public int CalculateSize() { + int size = 0; + if (agentIdentity_ != null) { + size += 1 + pb::CodedOutputStream.ComputeMessageSize(AgentIdentity); + } + if (InvitationId != 0UL) { + size += 1 + 8; + } + if (params_ != null) { + size += 1 + pb::CodedOutputStream.ComputeMessageSize(Params); + } + return size; + } + + public void MergeFrom(UpdateInvitationRequest other) { + if (other == null) { + return; + } + if (other.agentIdentity_ != null) { + if (agentIdentity_ == null) { + agentIdentity_ = new Bgs.Protocol.Identity(); + } + AgentIdentity.MergeFrom(other.AgentIdentity); + } + if (other.InvitationId != 0UL) { + InvitationId = other.InvitationId; + } + if (other.params_ != null) { + if (params_ == null) { + params_ = new Bgs.Protocol.InvitationParams(); + } + Params.MergeFrom(other.Params); + } + } + + public void MergeFrom(pb::CodedInputStream input) { + uint tag; + while ((tag = input.ReadTag()) != 0) { + switch(tag) { + default: + input.SkipLastField(); + break; + case 10: { + if (agentIdentity_ == null) { + agentIdentity_ = new Bgs.Protocol.Identity(); + } + input.ReadMessage(agentIdentity_); + break; + } + case 17: { + InvitationId = input.ReadFixed64(); + break; + } + case 26: { + if (params_ == null) { + params_ = new Bgs.Protocol.InvitationParams(); + } + input.ReadMessage(params_); + break; + } + } + } + } + + } + + [System.Diagnostics.DebuggerNonUserCodeAttribute()] + public sealed partial class GenericInvitationRequest : pb::IMessage { + private static readonly pb::MessageParser _parser = new pb::MessageParser(() => new GenericInvitationRequest()); + public static pb::MessageParser Parser { get { return _parser; } } + + public static pbr::MessageDescriptor Descriptor { + get { return Bgs.Protocol.InvitationTypesReflection.Descriptor.MessageTypes[7]; } + } + + pbr::MessageDescriptor pb::IMessage.Descriptor { + get { return Descriptor; } + } + + public GenericInvitationRequest() { + OnConstruction(); + } + + partial void OnConstruction(); + + public GenericInvitationRequest(GenericInvitationRequest other) : this() { + AgentId = other.agentId_ != null ? other.AgentId.Clone() : null; + TargetId = other.targetId_ != null ? other.TargetId.Clone() : null; + invitationId_ = other.invitationId_; + inviteeName_ = other.inviteeName_; + inviterName_ = other.inviterName_; + previousRole_ = other.previousRole_.Clone(); + desiredRole_ = other.desiredRole_.Clone(); + reason_ = other.reason_; + } + + public GenericInvitationRequest Clone() { + return new GenericInvitationRequest(this); + } + + /// Field number for the "agent_id" field. + public const int AgentIdFieldNumber = 1; + private Bgs.Protocol.EntityId agentId_; + public Bgs.Protocol.EntityId AgentId { + get { return agentId_; } + set { + agentId_ = value; + } + } + + /// Field number for the "target_id" field. + public const int TargetIdFieldNumber = 2; + private Bgs.Protocol.EntityId targetId_; + public Bgs.Protocol.EntityId TargetId { + get { return targetId_; } + set { + targetId_ = value; + } + } + + /// Field number for the "invitation_id" field. + public const int InvitationIdFieldNumber = 3; + private ulong invitationId_; + public ulong InvitationId { + get { return invitationId_; } + set { + invitationId_ = value; + } + } + + /// Field number for the "invitee_name" field. + public const int InviteeNameFieldNumber = 4; + private string inviteeName_ = ""; + public string InviteeName { + get { return inviteeName_; } + set { + inviteeName_ = pb::ProtoPreconditions.CheckNotNull(value, "value"); + } + } + + /// Field number for the "inviter_name" field. + public const int InviterNameFieldNumber = 5; + private string inviterName_ = ""; + public string InviterName { + get { return inviterName_; } + set { + inviterName_ = pb::ProtoPreconditions.CheckNotNull(value, "value"); + } + } + + /// Field number for the "previous_role" field. + public const int PreviousRoleFieldNumber = 6; + private static readonly pb::FieldCodec _repeated_previousRole_codec + = pb::FieldCodec.ForUInt32(50); + private readonly pbc::RepeatedField previousRole_ = new pbc::RepeatedField(); + public pbc::RepeatedField PreviousRole { + get { return previousRole_; } + } + + /// Field number for the "desired_role" field. + public const int DesiredRoleFieldNumber = 7; + private static readonly pb::FieldCodec _repeated_desiredRole_codec + = pb::FieldCodec.ForUInt32(58); + private readonly pbc::RepeatedField desiredRole_ = new pbc::RepeatedField(); + public pbc::RepeatedField DesiredRole { + get { return desiredRole_; } + } + + /// Field number for the "reason" field. + public const int ReasonFieldNumber = 8; + private uint reason_; + public uint Reason { + get { return reason_; } + set { + reason_ = value; + } + } + + public override bool Equals(object other) { + return Equals(other as GenericInvitationRequest); + } + + public bool Equals(GenericInvitationRequest other) { + if (ReferenceEquals(other, null)) { + return false; + } + if (ReferenceEquals(other, this)) { + return true; + } + if (!object.Equals(AgentId, other.AgentId)) return false; + if (!object.Equals(TargetId, other.TargetId)) return false; + if (InvitationId != other.InvitationId) return false; + if (InviteeName != other.InviteeName) return false; + if (InviterName != other.InviterName) return false; + if(!previousRole_.Equals(other.previousRole_)) return false; + if(!desiredRole_.Equals(other.desiredRole_)) return false; + if (Reason != other.Reason) return false; + return true; + } + + public override int GetHashCode() { + int hash = 1; + if (agentId_ != null) hash ^= AgentId.GetHashCode(); + if (targetId_ != null) hash ^= TargetId.GetHashCode(); + if (InvitationId != 0UL) hash ^= InvitationId.GetHashCode(); + if (InviteeName.Length != 0) hash ^= InviteeName.GetHashCode(); + if (InviterName.Length != 0) hash ^= InviterName.GetHashCode(); + hash ^= previousRole_.GetHashCode(); + hash ^= desiredRole_.GetHashCode(); + if (Reason != 0) hash ^= Reason.GetHashCode(); + return hash; + } + + public override string ToString() { + return pb::JsonFormatter.ToDiagnosticString(this); + } + + public void WriteTo(pb::CodedOutputStream output) { + if (agentId_ != null) { + output.WriteRawTag(10); + output.WriteMessage(AgentId); + } + if (targetId_ != null) { + output.WriteRawTag(18); + output.WriteMessage(TargetId); + } + if (InvitationId != 0UL) { + output.WriteRawTag(25); + output.WriteFixed64(InvitationId); + } + if (InviteeName.Length != 0) { + output.WriteRawTag(34); + output.WriteString(InviteeName); + } + if (InviterName.Length != 0) { + output.WriteRawTag(42); + output.WriteString(InviterName); + } + previousRole_.WriteTo(output, _repeated_previousRole_codec); + desiredRole_.WriteTo(output, _repeated_desiredRole_codec); + if (Reason != 0) { + output.WriteRawTag(64); + output.WriteUInt32(Reason); + } + } + + public int CalculateSize() { + int size = 0; + if (agentId_ != null) { + size += 1 + pb::CodedOutputStream.ComputeMessageSize(AgentId); + } + if (targetId_ != null) { + size += 1 + pb::CodedOutputStream.ComputeMessageSize(TargetId); + } + if (InvitationId != 0UL) { + size += 1 + 8; + } + if (InviteeName.Length != 0) { + size += 1 + pb::CodedOutputStream.ComputeStringSize(InviteeName); + } + if (InviterName.Length != 0) { + size += 1 + pb::CodedOutputStream.ComputeStringSize(InviterName); + } + size += previousRole_.CalculateSize(_repeated_previousRole_codec); + size += desiredRole_.CalculateSize(_repeated_desiredRole_codec); + if (Reason != 0) { + size += 1 + pb::CodedOutputStream.ComputeUInt32Size(Reason); + } + return size; + } + + public void MergeFrom(GenericInvitationRequest other) { + if (other == null) { + return; + } + if (other.agentId_ != null) { + if (agentId_ == null) { + agentId_ = new Bgs.Protocol.EntityId(); + } + AgentId.MergeFrom(other.AgentId); + } + if (other.targetId_ != null) { + if (targetId_ == null) { + targetId_ = new Bgs.Protocol.EntityId(); + } + TargetId.MergeFrom(other.TargetId); + } + if (other.InvitationId != 0UL) { + InvitationId = other.InvitationId; + } + if (other.InviteeName.Length != 0) { + InviteeName = other.InviteeName; + } + if (other.InviterName.Length != 0) { + InviterName = other.InviterName; + } + previousRole_.Add(other.previousRole_); + desiredRole_.Add(other.desiredRole_); + if (other.Reason != 0) { + Reason = other.Reason; + } + } + + public void MergeFrom(pb::CodedInputStream input) { + uint tag; + while ((tag = input.ReadTag()) != 0) { + switch(tag) { + default: + input.SkipLastField(); + break; + case 10: { + if (agentId_ == null) { + agentId_ = new Bgs.Protocol.EntityId(); + } + input.ReadMessage(agentId_); + break; + } + case 18: { + if (targetId_ == null) { + targetId_ = new Bgs.Protocol.EntityId(); + } + input.ReadMessage(targetId_); + break; + } + case 25: { + InvitationId = input.ReadFixed64(); + break; + } + case 34: { + InviteeName = input.ReadString(); + break; + } + case 42: { + InviterName = input.ReadString(); + break; + } + case 50: + case 48: { + previousRole_.AddEntriesFrom(input, _repeated_previousRole_codec); + break; + } + case 58: + case 56: { + desiredRole_.AddEntriesFrom(input, _repeated_desiredRole_codec); + break; + } + case 64: { + Reason = input.ReadUInt32(); + break; + } + } + } + } + + } + + #endregion + +} + +#endregion Designer generated code diff --git a/Framework/Proto/NotificationTypes.cs b/Framework/Proto/NotificationTypes.cs new file mode 100644 index 000000000..382beb67a --- /dev/null +++ b/Framework/Proto/NotificationTypes.cs @@ -0,0 +1,727 @@ +// Generated by the protocol buffer compiler. DO NOT EDIT! +// source: bgs/low/pb/client/notification_types.proto +#pragma warning disable 1591, 0612, 3021 +#region Designer generated code + +using pb = Google.Protobuf; +using pbc = Google.Protobuf.Collections; +using pbr = Google.Protobuf.Reflection; +namespace Bgs.Protocol.Notification.V1 +{ + + /// Holder for reflection information generated from bgs/low/pb/client/notification_types.proto + [System.Diagnostics.DebuggerNonUserCodeAttribute()] + public static partial class NotificationTypesReflection { + + #region Descriptor + /// File descriptor for bgs/low/pb/client/notification_types.proto + public static pbr::FileDescriptor Descriptor { + get { return descriptor; } + } + private static pbr::FileDescriptor descriptor; + + static NotificationTypesReflection() { + byte[] descriptorData = System.Convert.FromBase64String( + string.Concat( + "CipiZ3MvbG93L3BiL2NsaWVudC9ub3RpZmljYXRpb25fdHlwZXMucHJvdG8S", + "HGJncy5wcm90b2NvbC5ub3RpZmljYXRpb24udjEaJWJncy9sb3cvcGIvY2xp", + "ZW50L2FjY291bnRfdHlwZXMucHJvdG8aJ2Jncy9sb3cvcGIvY2xpZW50L2F0", + "dHJpYnV0ZV90eXBlcy5wcm90bxokYmdzL2xvdy9wYi9jbGllbnQvZW50aXR5", + "X3R5cGVzLnByb3RvGiFiZ3MvbG93L3BiL2NsaWVudC9ycGNfdHlwZXMucHJv", + "dG8iSwoGVGFyZ2V0EjMKCGlkZW50aXR5GAEgASgLMiEuYmdzLnByb3RvY29s", + "LmFjY291bnQudjEuSWRlbnRpdHkSDAoEdHlwZRgCIAEoCSKWAQoMU3Vic2Ny", + "aXB0aW9uEjQKBnRhcmdldBgBIAMoCzIkLmJncy5wcm90b2NvbC5ub3RpZmlj", + "YXRpb24udjEuVGFyZ2V0EjUKCnN1YnNjcmliZXIYAiABKAsyIS5iZ3MucHJv", + "dG9jb2wuYWNjb3VudC52MS5JZGVudGl0eRIZChFkZWxpdmVyeV9yZXF1aXJl", + "ZBgDIAEoCCKhAwoMTm90aWZpY2F0aW9uEikKCXNlbmRlcl9pZBgBIAEoCzIW", + "LmJncy5wcm90b2NvbC5FbnRpdHlJZBIpCgl0YXJnZXRfaWQYAiABKAsyFi5i", + "Z3MucHJvdG9jb2wuRW50aXR5SWQSDAoEdHlwZRgDIAEoCRIqCglhdHRyaWJ1", + "dGUYBCADKAsyFy5iZ3MucHJvdG9jb2wuQXR0cmlidXRlEjEKEXNlbmRlcl9h", + "Y2NvdW50X2lkGAUgASgLMhYuYmdzLnByb3RvY29sLkVudGl0eUlkEjEKEXRh", + "cmdldF9hY2NvdW50X2lkGAYgASgLMhYuYmdzLnByb3RvY29sLkVudGl0eUlk", + "EhkKEXNlbmRlcl9iYXR0bGVfdGFnGAcgASgJEhkKEXRhcmdldF9iYXR0bGVf", + "dGFnGAggASgJEiUKBHBlZXIYCSABKAsyFy5iZ3MucHJvdG9jb2wuUHJvY2Vz", + "c0lkEj4KE2ZvcndhcmRpbmdfaWRlbnRpdHkYCiABKAsyIS5iZ3MucHJvdG9j", + "b2wuYWNjb3VudC52MS5JZGVudGl0eUICSAJiBnByb3RvMw==")); + descriptor = pbr::FileDescriptor.FromGeneratedCode(descriptorData, + new pbr::FileDescriptor[] { Bgs.Protocol.Account.V1.AccountTypesReflection.Descriptor, Bgs.Protocol.AttributeTypesReflection.Descriptor, Bgs.Protocol.EntityTypesReflection.Descriptor, Bgs.Protocol.RpcTypesReflection.Descriptor, }, + new pbr::GeneratedClrTypeInfo(null, new pbr::GeneratedClrTypeInfo[] { + new pbr::GeneratedClrTypeInfo(typeof(Bgs.Protocol.Notification.V1.Target), Bgs.Protocol.Notification.V1.Target.Parser, new[]{ "Identity", "Type" }, null, null, null), + new pbr::GeneratedClrTypeInfo(typeof(Bgs.Protocol.Notification.V1.Subscription), Bgs.Protocol.Notification.V1.Subscription.Parser, new[]{ "Target", "Subscriber", "DeliveryRequired" }, null, null, null), + new pbr::GeneratedClrTypeInfo(typeof(Bgs.Protocol.Notification.V1.Notification), Bgs.Protocol.Notification.V1.Notification.Parser, new[]{ "SenderId", "TargetId", "Type", "Attribute", "SenderAccountId", "TargetAccountId", "SenderBattleTag", "TargetBattleTag", "Peer", "ForwardingIdentity" }, null, null, null) + })); + } + #endregion + + } + #region Messages + [System.Diagnostics.DebuggerNonUserCodeAttribute()] + public sealed partial class Target : pb::IMessage { + private static readonly pb::MessageParser _parser = new pb::MessageParser(() => new Target()); + public static pb::MessageParser Parser { get { return _parser; } } + + public static pbr::MessageDescriptor Descriptor { + get { return Bgs.Protocol.Notification.V1.NotificationTypesReflection.Descriptor.MessageTypes[0]; } + } + + pbr::MessageDescriptor pb::IMessage.Descriptor { + get { return Descriptor; } + } + + public Target() { + OnConstruction(); + } + + partial void OnConstruction(); + + public Target(Target other) : this() { + Identity = other.identity_ != null ? other.Identity.Clone() : null; + type_ = other.type_; + } + + public Target Clone() { + return new Target(this); + } + + /// Field number for the "identity" field. + public const int IdentityFieldNumber = 1; + private Bgs.Protocol.Account.V1.Identity identity_; + public Bgs.Protocol.Account.V1.Identity Identity { + get { return identity_; } + set { + identity_ = value; + } + } + + /// Field number for the "type" field. + public const int TypeFieldNumber = 2; + private string type_ = ""; + public string Type { + get { return type_; } + set { + type_ = pb::ProtoPreconditions.CheckNotNull(value, "value"); + } + } + + public override bool Equals(object other) { + return Equals(other as Target); + } + + public bool Equals(Target other) { + if (ReferenceEquals(other, null)) { + return false; + } + if (ReferenceEquals(other, this)) { + return true; + } + if (!object.Equals(Identity, other.Identity)) return false; + if (Type != other.Type) return false; + return true; + } + + public override int GetHashCode() { + int hash = 1; + if (identity_ != null) hash ^= Identity.GetHashCode(); + if (Type.Length != 0) hash ^= Type.GetHashCode(); + return hash; + } + + public override string ToString() { + return pb::JsonFormatter.ToDiagnosticString(this); + } + + public void WriteTo(pb::CodedOutputStream output) { + if (identity_ != null) { + output.WriteRawTag(10); + output.WriteMessage(Identity); + } + if (Type.Length != 0) { + output.WriteRawTag(18); + output.WriteString(Type); + } + } + + public int CalculateSize() { + int size = 0; + if (identity_ != null) { + size += 1 + pb::CodedOutputStream.ComputeMessageSize(Identity); + } + if (Type.Length != 0) { + size += 1 + pb::CodedOutputStream.ComputeStringSize(Type); + } + return size; + } + + public void MergeFrom(Target other) { + if (other == null) { + return; + } + if (other.identity_ != null) { + if (identity_ == null) { + identity_ = new Bgs.Protocol.Account.V1.Identity(); + } + Identity.MergeFrom(other.Identity); + } + if (other.Type.Length != 0) { + Type = other.Type; + } + } + + public void MergeFrom(pb::CodedInputStream input) { + uint tag; + while ((tag = input.ReadTag()) != 0) { + switch(tag) { + default: + input.SkipLastField(); + break; + case 10: { + if (identity_ == null) { + identity_ = new Bgs.Protocol.Account.V1.Identity(); + } + input.ReadMessage(identity_); + break; + } + case 18: { + Type = input.ReadString(); + break; + } + } + } + } + + } + + [System.Diagnostics.DebuggerNonUserCodeAttribute()] + public sealed partial class Subscription : pb::IMessage { + private static readonly pb::MessageParser _parser = new pb::MessageParser(() => new Subscription()); + public static pb::MessageParser Parser { get { return _parser; } } + + public static pbr::MessageDescriptor Descriptor { + get { return Bgs.Protocol.Notification.V1.NotificationTypesReflection.Descriptor.MessageTypes[1]; } + } + + pbr::MessageDescriptor pb::IMessage.Descriptor { + get { return Descriptor; } + } + + public Subscription() { + OnConstruction(); + } + + partial void OnConstruction(); + + public Subscription(Subscription other) : this() { + target_ = other.target_.Clone(); + Subscriber = other.subscriber_ != null ? other.Subscriber.Clone() : null; + deliveryRequired_ = other.deliveryRequired_; + } + + public Subscription Clone() { + return new Subscription(this); + } + + /// Field number for the "target" field. + public const int TargetFieldNumber = 1; + private static readonly pb::FieldCodec _repeated_target_codec + = pb::FieldCodec.ForMessage(10, Bgs.Protocol.Notification.V1.Target.Parser); + private readonly pbc::RepeatedField target_ = new pbc::RepeatedField(); + public pbc::RepeatedField Target { + get { return target_; } + } + + /// Field number for the "subscriber" field. + public const int SubscriberFieldNumber = 2; + private Bgs.Protocol.Account.V1.Identity subscriber_; + public Bgs.Protocol.Account.V1.Identity Subscriber { + get { return subscriber_; } + set { + subscriber_ = value; + } + } + + /// Field number for the "delivery_required" field. + public const int DeliveryRequiredFieldNumber = 3; + private bool deliveryRequired_; + public bool DeliveryRequired { + get { return deliveryRequired_; } + set { + deliveryRequired_ = value; + } + } + + public override bool Equals(object other) { + return Equals(other as Subscription); + } + + public bool Equals(Subscription other) { + if (ReferenceEquals(other, null)) { + return false; + } + if (ReferenceEquals(other, this)) { + return true; + } + if(!target_.Equals(other.target_)) return false; + if (!object.Equals(Subscriber, other.Subscriber)) return false; + if (DeliveryRequired != other.DeliveryRequired) return false; + return true; + } + + public override int GetHashCode() { + int hash = 1; + hash ^= target_.GetHashCode(); + if (subscriber_ != null) hash ^= Subscriber.GetHashCode(); + if (DeliveryRequired != false) hash ^= DeliveryRequired.GetHashCode(); + return hash; + } + + public override string ToString() { + return pb::JsonFormatter.ToDiagnosticString(this); + } + + public void WriteTo(pb::CodedOutputStream output) { + target_.WriteTo(output, _repeated_target_codec); + if (subscriber_ != null) { + output.WriteRawTag(18); + output.WriteMessage(Subscriber); + } + if (DeliveryRequired != false) { + output.WriteRawTag(24); + output.WriteBool(DeliveryRequired); + } + } + + public int CalculateSize() { + int size = 0; + size += target_.CalculateSize(_repeated_target_codec); + if (subscriber_ != null) { + size += 1 + pb::CodedOutputStream.ComputeMessageSize(Subscriber); + } + if (DeliveryRequired != false) { + size += 1 + 1; + } + return size; + } + + public void MergeFrom(Subscription other) { + if (other == null) { + return; + } + target_.Add(other.target_); + if (other.subscriber_ != null) { + if (subscriber_ == null) { + subscriber_ = new Bgs.Protocol.Account.V1.Identity(); + } + Subscriber.MergeFrom(other.Subscriber); + } + if (other.DeliveryRequired != false) { + DeliveryRequired = other.DeliveryRequired; + } + } + + public void MergeFrom(pb::CodedInputStream input) { + uint tag; + while ((tag = input.ReadTag()) != 0) { + switch(tag) { + default: + input.SkipLastField(); + break; + case 10: { + target_.AddEntriesFrom(input, _repeated_target_codec); + break; + } + case 18: { + if (subscriber_ == null) { + subscriber_ = new Bgs.Protocol.Account.V1.Identity(); + } + input.ReadMessage(subscriber_); + break; + } + case 24: { + DeliveryRequired = input.ReadBool(); + break; + } + } + } + } + + } + + [System.Diagnostics.DebuggerNonUserCodeAttribute()] + public sealed partial class Notification : pb::IMessage { + private static readonly pb::MessageParser _parser = new pb::MessageParser(() => new Notification()); + public static pb::MessageParser Parser { get { return _parser; } } + + public static pbr::MessageDescriptor Descriptor { + get { return Bgs.Protocol.Notification.V1.NotificationTypesReflection.Descriptor.MessageTypes[2]; } + } + + pbr::MessageDescriptor pb::IMessage.Descriptor { + get { return Descriptor; } + } + + public Notification() { + OnConstruction(); + } + + partial void OnConstruction(); + + public Notification(Notification other) : this() { + SenderId = other.senderId_ != null ? other.SenderId.Clone() : null; + TargetId = other.targetId_ != null ? other.TargetId.Clone() : null; + type_ = other.type_; + attribute_ = other.attribute_.Clone(); + SenderAccountId = other.senderAccountId_ != null ? other.SenderAccountId.Clone() : null; + TargetAccountId = other.targetAccountId_ != null ? other.TargetAccountId.Clone() : null; + senderBattleTag_ = other.senderBattleTag_; + targetBattleTag_ = other.targetBattleTag_; + Peer = other.peer_ != null ? other.Peer.Clone() : null; + ForwardingIdentity = other.forwardingIdentity_ != null ? other.ForwardingIdentity.Clone() : null; + } + + public Notification Clone() { + return new Notification(this); + } + + /// Field number for the "sender_id" field. + public const int SenderIdFieldNumber = 1; + private Bgs.Protocol.EntityId senderId_; + public Bgs.Protocol.EntityId SenderId { + get { return senderId_; } + set { + senderId_ = value; + } + } + + /// Field number for the "target_id" field. + public const int TargetIdFieldNumber = 2; + private Bgs.Protocol.EntityId targetId_; + public Bgs.Protocol.EntityId TargetId { + get { return targetId_; } + set { + targetId_ = value; + } + } + + /// Field number for the "type" field. + public const int TypeFieldNumber = 3; + private string type_ = ""; + public string Type { + get { return type_; } + set { + type_ = pb::ProtoPreconditions.CheckNotNull(value, "value"); + } + } + + /// Field number for the "attribute" field. + public const int AttributeFieldNumber = 4; + private static readonly pb::FieldCodec _repeated_attribute_codec + = pb::FieldCodec.ForMessage(34, Bgs.Protocol.Attribute.Parser); + private readonly pbc::RepeatedField attribute_ = new pbc::RepeatedField(); + public pbc::RepeatedField Attribute { + get { return attribute_; } + } + + /// Field number for the "sender_account_id" field. + public const int SenderAccountIdFieldNumber = 5; + private Bgs.Protocol.EntityId senderAccountId_; + public Bgs.Protocol.EntityId SenderAccountId { + get { return senderAccountId_; } + set { + senderAccountId_ = value; + } + } + + /// Field number for the "target_account_id" field. + public const int TargetAccountIdFieldNumber = 6; + private Bgs.Protocol.EntityId targetAccountId_; + public Bgs.Protocol.EntityId TargetAccountId { + get { return targetAccountId_; } + set { + targetAccountId_ = value; + } + } + + /// Field number for the "sender_battle_tag" field. + public const int SenderBattleTagFieldNumber = 7; + private string senderBattleTag_ = ""; + public string SenderBattleTag { + get { return senderBattleTag_; } + set { + senderBattleTag_ = pb::ProtoPreconditions.CheckNotNull(value, "value"); + } + } + + /// Field number for the "target_battle_tag" field. + public const int TargetBattleTagFieldNumber = 8; + private string targetBattleTag_ = ""; + public string TargetBattleTag { + get { return targetBattleTag_; } + set { + targetBattleTag_ = pb::ProtoPreconditions.CheckNotNull(value, "value"); + } + } + + /// Field number for the "peer" field. + public const int PeerFieldNumber = 9; + private Bgs.Protocol.ProcessId peer_; + public Bgs.Protocol.ProcessId Peer { + get { return peer_; } + set { + peer_ = value; + } + } + + /// Field number for the "forwarding_identity" field. + public const int ForwardingIdentityFieldNumber = 10; + private Bgs.Protocol.Account.V1.Identity forwardingIdentity_; + public Bgs.Protocol.Account.V1.Identity ForwardingIdentity { + get { return forwardingIdentity_; } + set { + forwardingIdentity_ = value; + } + } + + public override bool Equals(object other) { + return Equals(other as Notification); + } + + public bool Equals(Notification other) { + if (ReferenceEquals(other, null)) { + return false; + } + if (ReferenceEquals(other, this)) { + return true; + } + if (!object.Equals(SenderId, other.SenderId)) return false; + if (!object.Equals(TargetId, other.TargetId)) return false; + if (Type != other.Type) return false; + if(!attribute_.Equals(other.attribute_)) return false; + if (!object.Equals(SenderAccountId, other.SenderAccountId)) return false; + if (!object.Equals(TargetAccountId, other.TargetAccountId)) return false; + if (SenderBattleTag != other.SenderBattleTag) return false; + if (TargetBattleTag != other.TargetBattleTag) return false; + if (!object.Equals(Peer, other.Peer)) return false; + if (!object.Equals(ForwardingIdentity, other.ForwardingIdentity)) return false; + return true; + } + + public override int GetHashCode() { + int hash = 1; + if (senderId_ != null) hash ^= SenderId.GetHashCode(); + if (targetId_ != null) hash ^= TargetId.GetHashCode(); + if (Type.Length != 0) hash ^= Type.GetHashCode(); + hash ^= attribute_.GetHashCode(); + if (senderAccountId_ != null) hash ^= SenderAccountId.GetHashCode(); + if (targetAccountId_ != null) hash ^= TargetAccountId.GetHashCode(); + if (SenderBattleTag.Length != 0) hash ^= SenderBattleTag.GetHashCode(); + if (TargetBattleTag.Length != 0) hash ^= TargetBattleTag.GetHashCode(); + if (peer_ != null) hash ^= Peer.GetHashCode(); + if (forwardingIdentity_ != null) hash ^= ForwardingIdentity.GetHashCode(); + return hash; + } + + public override string ToString() { + return pb::JsonFormatter.ToDiagnosticString(this); + } + + public void WriteTo(pb::CodedOutputStream output) { + if (senderId_ != null) { + output.WriteRawTag(10); + output.WriteMessage(SenderId); + } + if (targetId_ != null) { + output.WriteRawTag(18); + output.WriteMessage(TargetId); + } + if (Type.Length != 0) { + output.WriteRawTag(26); + output.WriteString(Type); + } + attribute_.WriteTo(output, _repeated_attribute_codec); + if (senderAccountId_ != null) { + output.WriteRawTag(42); + output.WriteMessage(SenderAccountId); + } + if (targetAccountId_ != null) { + output.WriteRawTag(50); + output.WriteMessage(TargetAccountId); + } + if (SenderBattleTag.Length != 0) { + output.WriteRawTag(58); + output.WriteString(SenderBattleTag); + } + if (TargetBattleTag.Length != 0) { + output.WriteRawTag(66); + output.WriteString(TargetBattleTag); + } + if (peer_ != null) { + output.WriteRawTag(74); + output.WriteMessage(Peer); + } + if (forwardingIdentity_ != null) { + output.WriteRawTag(82); + output.WriteMessage(ForwardingIdentity); + } + } + + public int CalculateSize() { + int size = 0; + if (senderId_ != null) { + size += 1 + pb::CodedOutputStream.ComputeMessageSize(SenderId); + } + if (targetId_ != null) { + size += 1 + pb::CodedOutputStream.ComputeMessageSize(TargetId); + } + if (Type.Length != 0) { + size += 1 + pb::CodedOutputStream.ComputeStringSize(Type); + } + size += attribute_.CalculateSize(_repeated_attribute_codec); + if (senderAccountId_ != null) { + size += 1 + pb::CodedOutputStream.ComputeMessageSize(SenderAccountId); + } + if (targetAccountId_ != null) { + size += 1 + pb::CodedOutputStream.ComputeMessageSize(TargetAccountId); + } + if (SenderBattleTag.Length != 0) { + size += 1 + pb::CodedOutputStream.ComputeStringSize(SenderBattleTag); + } + if (TargetBattleTag.Length != 0) { + size += 1 + pb::CodedOutputStream.ComputeStringSize(TargetBattleTag); + } + if (peer_ != null) { + size += 1 + pb::CodedOutputStream.ComputeMessageSize(Peer); + } + if (forwardingIdentity_ != null) { + size += 1 + pb::CodedOutputStream.ComputeMessageSize(ForwardingIdentity); + } + return size; + } + + public void MergeFrom(Notification other) { + if (other == null) { + return; + } + if (other.senderId_ != null) { + if (senderId_ == null) { + senderId_ = new Bgs.Protocol.EntityId(); + } + SenderId.MergeFrom(other.SenderId); + } + if (other.targetId_ != null) { + if (targetId_ == null) { + targetId_ = new Bgs.Protocol.EntityId(); + } + TargetId.MergeFrom(other.TargetId); + } + if (other.Type.Length != 0) { + Type = other.Type; + } + attribute_.Add(other.attribute_); + if (other.senderAccountId_ != null) { + if (senderAccountId_ == null) { + senderAccountId_ = new Bgs.Protocol.EntityId(); + } + SenderAccountId.MergeFrom(other.SenderAccountId); + } + if (other.targetAccountId_ != null) { + if (targetAccountId_ == null) { + targetAccountId_ = new Bgs.Protocol.EntityId(); + } + TargetAccountId.MergeFrom(other.TargetAccountId); + } + if (other.SenderBattleTag.Length != 0) { + SenderBattleTag = other.SenderBattleTag; + } + if (other.TargetBattleTag.Length != 0) { + TargetBattleTag = other.TargetBattleTag; + } + if (other.peer_ != null) { + if (peer_ == null) { + peer_ = new Bgs.Protocol.ProcessId(); + } + Peer.MergeFrom(other.Peer); + } + if (other.forwardingIdentity_ != null) { + if (forwardingIdentity_ == null) { + forwardingIdentity_ = new Bgs.Protocol.Account.V1.Identity(); + } + ForwardingIdentity.MergeFrom(other.ForwardingIdentity); + } + } + + public void MergeFrom(pb::CodedInputStream input) { + uint tag; + while ((tag = input.ReadTag()) != 0) { + switch(tag) { + default: + input.SkipLastField(); + break; + case 10: { + if (senderId_ == null) { + senderId_ = new Bgs.Protocol.EntityId(); + } + input.ReadMessage(senderId_); + break; + } + case 18: { + if (targetId_ == null) { + targetId_ = new Bgs.Protocol.EntityId(); + } + input.ReadMessage(targetId_); + break; + } + case 26: { + Type = input.ReadString(); + break; + } + case 34: { + attribute_.AddEntriesFrom(input, _repeated_attribute_codec); + break; + } + case 42: { + if (senderAccountId_ == null) { + senderAccountId_ = new Bgs.Protocol.EntityId(); + } + input.ReadMessage(senderAccountId_); + break; + } + case 50: { + if (targetAccountId_ == null) { + targetAccountId_ = new Bgs.Protocol.EntityId(); + } + input.ReadMessage(targetAccountId_); + break; + } + case 58: { + SenderBattleTag = input.ReadString(); + break; + } + case 66: { + TargetBattleTag = input.ReadString(); + break; + } + case 74: { + if (peer_ == null) { + peer_ = new Bgs.Protocol.ProcessId(); + } + input.ReadMessage(peer_); + break; + } + case 82: { + if (forwardingIdentity_ == null) { + forwardingIdentity_ = new Bgs.Protocol.Account.V1.Identity(); + } + input.ReadMessage(forwardingIdentity_); + break; + } + } + } + } + + } + + #endregion + +} + +#endregion Designer generated code diff --git a/Framework/Proto/PresenceService.cs b/Framework/Proto/PresenceService.cs new file mode 100644 index 000000000..165760cc1 --- /dev/null +++ b/Framework/Proto/PresenceService.cs @@ -0,0 +1,1157 @@ +// Generated by the protocol buffer compiler. DO NOT EDIT! +// source: bgs/low/pb/client/presence_service.proto +#pragma warning disable 1591, 0612, 3021 +#region Designer generated code + +using pb = Google.Protobuf; +using pbc = Google.Protobuf.Collections; +using pbr = Google.Protobuf.Reflection; +namespace Bgs.Protocol.Presence.V1 +{ + + /// Holder for reflection information generated from bgs/low/pb/client/presence_service.proto + [System.Diagnostics.DebuggerNonUserCodeAttribute()] + public static partial class PresenceServiceReflection { + + #region Descriptor + /// File descriptor for bgs/low/pb/client/presence_service.proto + public static pbr::FileDescriptor Descriptor { + get { return descriptor; } + } + private static pbr::FileDescriptor descriptor; + + static PresenceServiceReflection() { + byte[] descriptorData = System.Convert.FromBase64String( + string.Concat( + "CihiZ3MvbG93L3BiL2NsaWVudC9wcmVzZW5jZV9zZXJ2aWNlLnByb3RvEhhi", + "Z3MucHJvdG9jb2wucHJlc2VuY2UudjEaJGJncy9sb3cvcGIvY2xpZW50L2Vu", + "dGl0eV90eXBlcy5wcm90bxomYmdzL2xvdy9wYi9jbGllbnQvcHJlc2VuY2Vf", + "dHlwZXMucHJvdG8aIWJncy9sb3cvcGIvY2xpZW50L3JwY190eXBlcy5wcm90", + "byKkAQoQU3Vic2NyaWJlUmVxdWVzdBIoCghhZ2VudF9pZBgBIAEoCzIWLmJn", + "cy5wcm90b2NvbC5FbnRpdHlJZBIpCgllbnRpdHlfaWQYAiABKAsyFi5iZ3Mu", + "cHJvdG9jb2wuRW50aXR5SWQSEQoJb2JqZWN0X2lkGAMgASgEEg8KB3Byb2dy", + "YW0YBCADKAcSFwoLZmxhZ19wdWJsaWMYBSABKAhCAhgBIkkKHFN1YnNjcmli", + "ZU5vdGlmaWNhdGlvblJlcXVlc3QSKQoJZW50aXR5X2lkGAEgASgLMhYuYmdz", + "LnByb3RvY29sLkVudGl0eUlkInwKElVuc3Vic2NyaWJlUmVxdWVzdBIoCghh", + "Z2VudF9pZBgBIAEoCzIWLmJncy5wcm90b2NvbC5FbnRpdHlJZBIpCgllbnRp", + "dHlfaWQYAiABKAsyFi5iZ3MucHJvdG9jb2wuRW50aXR5SWQSEQoJb2JqZWN0", + "X2lkGAMgASgEIroBCg1VcGRhdGVSZXF1ZXN0EikKCWVudGl0eV9pZBgBIAEo", + "CzIWLmJncy5wcm90b2NvbC5FbnRpdHlJZBJBCg9maWVsZF9vcGVyYXRpb24Y", + "AiADKAsyKC5iZ3MucHJvdG9jb2wucHJlc2VuY2UudjEuRmllbGRPcGVyYXRp", + "b24SEQoJbm9fY3JlYXRlGAMgASgIEigKCGFnZW50X2lkGAQgASgLMhYuYmdz", + "LnByb3RvY29sLkVudGl0eUlkIpQBCgxRdWVyeVJlcXVlc3QSKQoJZW50aXR5", + "X2lkGAEgASgLMhYuYmdzLnByb3RvY29sLkVudGl0eUlkEi8KA2tleRgCIAMo", + "CzIiLmJncy5wcm90b2NvbC5wcmVzZW5jZS52MS5GaWVsZEtleRIoCghhZ2Vu", + "dF9pZBgDIAEoCzIWLmJncy5wcm90b2NvbC5FbnRpdHlJZCI/Cg1RdWVyeVJl", + "c3BvbnNlEi4KBWZpZWxkGAIgAygLMh8uYmdzLnByb3RvY29sLnByZXNlbmNl", + "LnYxLkZpZWxkIlgKEE93bmVyc2hpcFJlcXVlc3QSKQoJZW50aXR5X2lkGAEg", + "ASgLMhYuYmdzLnByb3RvY29sLkVudGl0eUlkEhkKEXJlbGVhc2Vfb3duZXJz", + "aGlwGAIgASgIMowECg9QcmVzZW5jZVNlcnZpY2USTQoJU3Vic2NyaWJlEiou", + "YmdzLnByb3RvY29sLnByZXNlbmNlLnYxLlN1YnNjcmliZVJlcXVlc3QaFC5i", + "Z3MucHJvdG9jb2wuTm9EYXRhElEKC1Vuc3Vic2NyaWJlEiwuYmdzLnByb3Rv", + "Y29sLnByZXNlbmNlLnYxLlVuc3Vic2NyaWJlUmVxdWVzdBoULmJncy5wcm90", + "b2NvbC5Ob0RhdGESRwoGVXBkYXRlEicuYmdzLnByb3RvY29sLnByZXNlbmNl", + "LnYxLlVwZGF0ZVJlcXVlc3QaFC5iZ3MucHJvdG9jb2wuTm9EYXRhElgKBVF1", + "ZXJ5EiYuYmdzLnByb3RvY29sLnByZXNlbmNlLnYxLlF1ZXJ5UmVxdWVzdBon", + "LmJncy5wcm90b2NvbC5wcmVzZW5jZS52MS5RdWVyeVJlc3BvbnNlEk0KCU93", + "bmVyc2hpcBIqLmJncy5wcm90b2NvbC5wcmVzZW5jZS52MS5Pd25lcnNoaXBS", + "ZXF1ZXN0GhQuYmdzLnByb3RvY29sLk5vRGF0YRJlChVTdWJzY3JpYmVOb3Rp", + "ZmljYXRpb24SNi5iZ3MucHJvdG9jb2wucHJlc2VuY2UudjEuU3Vic2NyaWJl", + "Tm90aWZpY2F0aW9uUmVxdWVzdBoULmJncy5wcm90b2NvbC5Ob0RhdGFCBUgC", + "gAEAYgZwcm90bzM=")); + descriptor = pbr::FileDescriptor.FromGeneratedCode(descriptorData, + new pbr::FileDescriptor[] { Bgs.Protocol.EntityTypesReflection.Descriptor, Bgs.Protocol.Presence.V1.PresenceTypesReflection.Descriptor, Bgs.Protocol.RpcTypesReflection.Descriptor, }, + new pbr::GeneratedClrTypeInfo(null, new pbr::GeneratedClrTypeInfo[] { + new pbr::GeneratedClrTypeInfo(typeof(Bgs.Protocol.Presence.V1.SubscribeRequest), Bgs.Protocol.Presence.V1.SubscribeRequest.Parser, new[]{ "AgentId", "EntityId", "ObjectId", "Program", "FlagPublic" }, null, null, null), + new pbr::GeneratedClrTypeInfo(typeof(Bgs.Protocol.Presence.V1.SubscribeNotificationRequest), Bgs.Protocol.Presence.V1.SubscribeNotificationRequest.Parser, new[]{ "EntityId" }, null, null, null), + new pbr::GeneratedClrTypeInfo(typeof(Bgs.Protocol.Presence.V1.UnsubscribeRequest), Bgs.Protocol.Presence.V1.UnsubscribeRequest.Parser, new[]{ "AgentId", "EntityId", "ObjectId" }, null, null, null), + new pbr::GeneratedClrTypeInfo(typeof(Bgs.Protocol.Presence.V1.UpdateRequest), Bgs.Protocol.Presence.V1.UpdateRequest.Parser, new[]{ "EntityId", "FieldOperation", "NoCreate", "AgentId" }, null, null, null), + new pbr::GeneratedClrTypeInfo(typeof(Bgs.Protocol.Presence.V1.QueryRequest), Bgs.Protocol.Presence.V1.QueryRequest.Parser, new[]{ "EntityId", "Key", "AgentId" }, null, null, null), + new pbr::GeneratedClrTypeInfo(typeof(Bgs.Protocol.Presence.V1.QueryResponse), Bgs.Protocol.Presence.V1.QueryResponse.Parser, new[]{ "Field" }, null, null, null), + new pbr::GeneratedClrTypeInfo(typeof(Bgs.Protocol.Presence.V1.OwnershipRequest), Bgs.Protocol.Presence.V1.OwnershipRequest.Parser, new[]{ "EntityId", "ReleaseOwnership" }, null, null, null) + })); + } + #endregion + + } + #region Messages + [System.Diagnostics.DebuggerNonUserCodeAttribute()] + public sealed partial class SubscribeRequest : pb::IMessage { + private static readonly pb::MessageParser _parser = new pb::MessageParser(() => new SubscribeRequest()); + public static pb::MessageParser Parser { get { return _parser; } } + + public static pbr::MessageDescriptor Descriptor { + get { return Bgs.Protocol.Presence.V1.PresenceServiceReflection.Descriptor.MessageTypes[0]; } + } + + pbr::MessageDescriptor pb::IMessage.Descriptor { + get { return Descriptor; } + } + + public SubscribeRequest() { + OnConstruction(); + } + + partial void OnConstruction(); + + public SubscribeRequest(SubscribeRequest other) : this() { + AgentId = other.agentId_ != null ? other.AgentId.Clone() : null; + EntityId = other.entityId_ != null ? other.EntityId.Clone() : null; + objectId_ = other.objectId_; + program_ = other.program_.Clone(); + flagPublic_ = other.flagPublic_; + } + + public SubscribeRequest Clone() { + return new SubscribeRequest(this); + } + + /// Field number for the "agent_id" field. + public const int AgentIdFieldNumber = 1; + private Bgs.Protocol.EntityId agentId_; + public Bgs.Protocol.EntityId AgentId { + get { return agentId_; } + set { + agentId_ = value; + } + } + + /// Field number for the "entity_id" field. + public const int EntityIdFieldNumber = 2; + private Bgs.Protocol.EntityId entityId_; + public Bgs.Protocol.EntityId EntityId { + get { return entityId_; } + set { + entityId_ = value; + } + } + + /// Field number for the "object_id" field. + public const int ObjectIdFieldNumber = 3; + private ulong objectId_; + public ulong ObjectId { + get { return objectId_; } + set { + objectId_ = value; + } + } + + /// Field number for the "program" field. + public const int ProgramFieldNumber = 4; + private static readonly pb::FieldCodec _repeated_program_codec + = pb::FieldCodec.ForFixed32(34); + private readonly pbc::RepeatedField program_ = new pbc::RepeatedField(); + public pbc::RepeatedField Program { + get { return program_; } + } + + /// Field number for the "flag_public" field. + public const int FlagPublicFieldNumber = 5; + private bool flagPublic_; + [System.ObsoleteAttribute()] + public bool FlagPublic { + get { return flagPublic_; } + set { + flagPublic_ = value; + } + } + + public override bool Equals(object other) { + return Equals(other as SubscribeRequest); + } + + public bool Equals(SubscribeRequest other) { + if (ReferenceEquals(other, null)) { + return false; + } + if (ReferenceEquals(other, this)) { + return true; + } + if (!object.Equals(AgentId, other.AgentId)) return false; + if (!object.Equals(EntityId, other.EntityId)) return false; + if (ObjectId != other.ObjectId) return false; + if(!program_.Equals(other.program_)) return false; + if (FlagPublic != other.FlagPublic) return false; + return true; + } + + public override int GetHashCode() { + int hash = 1; + if (agentId_ != null) hash ^= AgentId.GetHashCode(); + if (entityId_ != null) hash ^= EntityId.GetHashCode(); + if (ObjectId != 0UL) hash ^= ObjectId.GetHashCode(); + hash ^= program_.GetHashCode(); + if (FlagPublic != false) hash ^= FlagPublic.GetHashCode(); + return hash; + } + + public override string ToString() { + return pb::JsonFormatter.ToDiagnosticString(this); + } + + public void WriteTo(pb::CodedOutputStream output) { + if (agentId_ != null) { + output.WriteRawTag(10); + output.WriteMessage(AgentId); + } + if (entityId_ != null) { + output.WriteRawTag(18); + output.WriteMessage(EntityId); + } + if (ObjectId != 0UL) { + output.WriteRawTag(24); + output.WriteUInt64(ObjectId); + } + program_.WriteTo(output, _repeated_program_codec); + if (FlagPublic != false) { + output.WriteRawTag(40); + output.WriteBool(FlagPublic); + } + } + + public int CalculateSize() { + int size = 0; + if (agentId_ != null) { + size += 1 + pb::CodedOutputStream.ComputeMessageSize(AgentId); + } + if (entityId_ != null) { + size += 1 + pb::CodedOutputStream.ComputeMessageSize(EntityId); + } + if (ObjectId != 0UL) { + size += 1 + pb::CodedOutputStream.ComputeUInt64Size(ObjectId); + } + size += program_.CalculateSize(_repeated_program_codec); + if (FlagPublic != false) { + size += 1 + 1; + } + return size; + } + + public void MergeFrom(SubscribeRequest other) { + if (other == null) { + return; + } + if (other.agentId_ != null) { + if (agentId_ == null) { + agentId_ = new Bgs.Protocol.EntityId(); + } + AgentId.MergeFrom(other.AgentId); + } + if (other.entityId_ != null) { + if (entityId_ == null) { + entityId_ = new Bgs.Protocol.EntityId(); + } + EntityId.MergeFrom(other.EntityId); + } + if (other.ObjectId != 0UL) { + ObjectId = other.ObjectId; + } + program_.Add(other.program_); + if (other.FlagPublic != false) { + FlagPublic = other.FlagPublic; + } + } + + public void MergeFrom(pb::CodedInputStream input) { + uint tag; + while ((tag = input.ReadTag()) != 0) { + switch(tag) { + default: + input.SkipLastField(); + break; + case 10: { + if (agentId_ == null) { + agentId_ = new Bgs.Protocol.EntityId(); + } + input.ReadMessage(agentId_); + break; + } + case 18: { + if (entityId_ == null) { + entityId_ = new Bgs.Protocol.EntityId(); + } + input.ReadMessage(entityId_); + break; + } + case 24: { + ObjectId = input.ReadUInt64(); + break; + } + case 34: + case 37: { + program_.AddEntriesFrom(input, _repeated_program_codec); + break; + } + case 40: { + FlagPublic = input.ReadBool(); + break; + } + } + } + } + + } + + [System.Diagnostics.DebuggerNonUserCodeAttribute()] + public sealed partial class SubscribeNotificationRequest : pb::IMessage { + private static readonly pb::MessageParser _parser = new pb::MessageParser(() => new SubscribeNotificationRequest()); + public static pb::MessageParser Parser { get { return _parser; } } + + public static pbr::MessageDescriptor Descriptor { + get { return Bgs.Protocol.Presence.V1.PresenceServiceReflection.Descriptor.MessageTypes[1]; } + } + + pbr::MessageDescriptor pb::IMessage.Descriptor { + get { return Descriptor; } + } + + public SubscribeNotificationRequest() { + OnConstruction(); + } + + partial void OnConstruction(); + + public SubscribeNotificationRequest(SubscribeNotificationRequest other) : this() { + EntityId = other.entityId_ != null ? other.EntityId.Clone() : null; + } + + public SubscribeNotificationRequest Clone() { + return new SubscribeNotificationRequest(this); + } + + /// Field number for the "entity_id" field. + public const int EntityIdFieldNumber = 1; + private Bgs.Protocol.EntityId entityId_; + public Bgs.Protocol.EntityId EntityId { + get { return entityId_; } + set { + entityId_ = value; + } + } + + public override bool Equals(object other) { + return Equals(other as SubscribeNotificationRequest); + } + + public bool Equals(SubscribeNotificationRequest other) { + if (ReferenceEquals(other, null)) { + return false; + } + if (ReferenceEquals(other, this)) { + return true; + } + if (!object.Equals(EntityId, other.EntityId)) return false; + return true; + } + + public override int GetHashCode() { + int hash = 1; + if (entityId_ != null) hash ^= EntityId.GetHashCode(); + return hash; + } + + public override string ToString() { + return pb::JsonFormatter.ToDiagnosticString(this); + } + + public void WriteTo(pb::CodedOutputStream output) { + if (entityId_ != null) { + output.WriteRawTag(10); + output.WriteMessage(EntityId); + } + } + + public int CalculateSize() { + int size = 0; + if (entityId_ != null) { + size += 1 + pb::CodedOutputStream.ComputeMessageSize(EntityId); + } + return size; + } + + public void MergeFrom(SubscribeNotificationRequest other) { + if (other == null) { + return; + } + if (other.entityId_ != null) { + if (entityId_ == null) { + entityId_ = new Bgs.Protocol.EntityId(); + } + EntityId.MergeFrom(other.EntityId); + } + } + + public void MergeFrom(pb::CodedInputStream input) { + uint tag; + while ((tag = input.ReadTag()) != 0) { + switch(tag) { + default: + input.SkipLastField(); + break; + case 10: { + if (entityId_ == null) { + entityId_ = new Bgs.Protocol.EntityId(); + } + input.ReadMessage(entityId_); + break; + } + } + } + } + + } + + [System.Diagnostics.DebuggerNonUserCodeAttribute()] + public sealed partial class UnsubscribeRequest : pb::IMessage { + private static readonly pb::MessageParser _parser = new pb::MessageParser(() => new UnsubscribeRequest()); + public static pb::MessageParser Parser { get { return _parser; } } + + public static pbr::MessageDescriptor Descriptor { + get { return Bgs.Protocol.Presence.V1.PresenceServiceReflection.Descriptor.MessageTypes[2]; } + } + + pbr::MessageDescriptor pb::IMessage.Descriptor { + get { return Descriptor; } + } + + public UnsubscribeRequest() { + OnConstruction(); + } + + partial void OnConstruction(); + + public UnsubscribeRequest(UnsubscribeRequest other) : this() { + AgentId = other.agentId_ != null ? other.AgentId.Clone() : null; + EntityId = other.entityId_ != null ? other.EntityId.Clone() : null; + objectId_ = other.objectId_; + } + + public UnsubscribeRequest Clone() { + return new UnsubscribeRequest(this); + } + + /// Field number for the "agent_id" field. + public const int AgentIdFieldNumber = 1; + private Bgs.Protocol.EntityId agentId_; + public Bgs.Protocol.EntityId AgentId { + get { return agentId_; } + set { + agentId_ = value; + } + } + + /// Field number for the "entity_id" field. + public const int EntityIdFieldNumber = 2; + private Bgs.Protocol.EntityId entityId_; + public Bgs.Protocol.EntityId EntityId { + get { return entityId_; } + set { + entityId_ = value; + } + } + + /// Field number for the "object_id" field. + public const int ObjectIdFieldNumber = 3; + private ulong objectId_; + public ulong ObjectId { + get { return objectId_; } + set { + objectId_ = value; + } + } + + public override bool Equals(object other) { + return Equals(other as UnsubscribeRequest); + } + + public bool Equals(UnsubscribeRequest other) { + if (ReferenceEquals(other, null)) { + return false; + } + if (ReferenceEquals(other, this)) { + return true; + } + if (!object.Equals(AgentId, other.AgentId)) return false; + if (!object.Equals(EntityId, other.EntityId)) return false; + if (ObjectId != other.ObjectId) return false; + return true; + } + + public override int GetHashCode() { + int hash = 1; + if (agentId_ != null) hash ^= AgentId.GetHashCode(); + if (entityId_ != null) hash ^= EntityId.GetHashCode(); + if (ObjectId != 0UL) hash ^= ObjectId.GetHashCode(); + return hash; + } + + public override string ToString() { + return pb::JsonFormatter.ToDiagnosticString(this); + } + + public void WriteTo(pb::CodedOutputStream output) { + if (agentId_ != null) { + output.WriteRawTag(10); + output.WriteMessage(AgentId); + } + if (entityId_ != null) { + output.WriteRawTag(18); + output.WriteMessage(EntityId); + } + if (ObjectId != 0UL) { + output.WriteRawTag(24); + output.WriteUInt64(ObjectId); + } + } + + public int CalculateSize() { + int size = 0; + if (agentId_ != null) { + size += 1 + pb::CodedOutputStream.ComputeMessageSize(AgentId); + } + if (entityId_ != null) { + size += 1 + pb::CodedOutputStream.ComputeMessageSize(EntityId); + } + if (ObjectId != 0UL) { + size += 1 + pb::CodedOutputStream.ComputeUInt64Size(ObjectId); + } + return size; + } + + public void MergeFrom(UnsubscribeRequest other) { + if (other == null) { + return; + } + if (other.agentId_ != null) { + if (agentId_ == null) { + agentId_ = new Bgs.Protocol.EntityId(); + } + AgentId.MergeFrom(other.AgentId); + } + if (other.entityId_ != null) { + if (entityId_ == null) { + entityId_ = new Bgs.Protocol.EntityId(); + } + EntityId.MergeFrom(other.EntityId); + } + if (other.ObjectId != 0UL) { + ObjectId = other.ObjectId; + } + } + + public void MergeFrom(pb::CodedInputStream input) { + uint tag; + while ((tag = input.ReadTag()) != 0) { + switch(tag) { + default: + input.SkipLastField(); + break; + case 10: { + if (agentId_ == null) { + agentId_ = new Bgs.Protocol.EntityId(); + } + input.ReadMessage(agentId_); + break; + } + case 18: { + if (entityId_ == null) { + entityId_ = new Bgs.Protocol.EntityId(); + } + input.ReadMessage(entityId_); + break; + } + case 24: { + ObjectId = input.ReadUInt64(); + break; + } + } + } + } + + } + + [System.Diagnostics.DebuggerNonUserCodeAttribute()] + public sealed partial class UpdateRequest : pb::IMessage { + private static readonly pb::MessageParser _parser = new pb::MessageParser(() => new UpdateRequest()); + public static pb::MessageParser Parser { get { return _parser; } } + + public static pbr::MessageDescriptor Descriptor { + get { return Bgs.Protocol.Presence.V1.PresenceServiceReflection.Descriptor.MessageTypes[3]; } + } + + pbr::MessageDescriptor pb::IMessage.Descriptor { + get { return Descriptor; } + } + + public UpdateRequest() { + OnConstruction(); + } + + partial void OnConstruction(); + + public UpdateRequest(UpdateRequest other) : this() { + EntityId = other.entityId_ != null ? other.EntityId.Clone() : null; + fieldOperation_ = other.fieldOperation_.Clone(); + noCreate_ = other.noCreate_; + AgentId = other.agentId_ != null ? other.AgentId.Clone() : null; + } + + public UpdateRequest Clone() { + return new UpdateRequest(this); + } + + /// Field number for the "entity_id" field. + public const int EntityIdFieldNumber = 1; + private Bgs.Protocol.EntityId entityId_; + public Bgs.Protocol.EntityId EntityId { + get { return entityId_; } + set { + entityId_ = value; + } + } + + /// Field number for the "field_operation" field. + public const int FieldOperationFieldNumber = 2; + private static readonly pb::FieldCodec _repeated_fieldOperation_codec + = pb::FieldCodec.ForMessage(18, Bgs.Protocol.Presence.V1.FieldOperation.Parser); + private readonly pbc::RepeatedField fieldOperation_ = new pbc::RepeatedField(); + public pbc::RepeatedField FieldOperation { + get { return fieldOperation_; } + } + + /// Field number for the "no_create" field. + public const int NoCreateFieldNumber = 3; + private bool noCreate_; + public bool NoCreate { + get { return noCreate_; } + set { + noCreate_ = value; + } + } + + /// Field number for the "agent_id" field. + public const int AgentIdFieldNumber = 4; + private Bgs.Protocol.EntityId agentId_; + public Bgs.Protocol.EntityId AgentId { + get { return agentId_; } + set { + agentId_ = value; + } + } + + public override bool Equals(object other) { + return Equals(other as UpdateRequest); + } + + public bool Equals(UpdateRequest other) { + if (ReferenceEquals(other, null)) { + return false; + } + if (ReferenceEquals(other, this)) { + return true; + } + if (!object.Equals(EntityId, other.EntityId)) return false; + if(!fieldOperation_.Equals(other.fieldOperation_)) return false; + if (NoCreate != other.NoCreate) return false; + if (!object.Equals(AgentId, other.AgentId)) return false; + return true; + } + + public override int GetHashCode() { + int hash = 1; + if (entityId_ != null) hash ^= EntityId.GetHashCode(); + hash ^= fieldOperation_.GetHashCode(); + if (NoCreate != false) hash ^= NoCreate.GetHashCode(); + if (agentId_ != null) hash ^= AgentId.GetHashCode(); + return hash; + } + + public override string ToString() { + return pb::JsonFormatter.ToDiagnosticString(this); + } + + public void WriteTo(pb::CodedOutputStream output) { + if (entityId_ != null) { + output.WriteRawTag(10); + output.WriteMessage(EntityId); + } + fieldOperation_.WriteTo(output, _repeated_fieldOperation_codec); + if (NoCreate != false) { + output.WriteRawTag(24); + output.WriteBool(NoCreate); + } + if (agentId_ != null) { + output.WriteRawTag(34); + output.WriteMessage(AgentId); + } + } + + public int CalculateSize() { + int size = 0; + if (entityId_ != null) { + size += 1 + pb::CodedOutputStream.ComputeMessageSize(EntityId); + } + size += fieldOperation_.CalculateSize(_repeated_fieldOperation_codec); + if (NoCreate != false) { + size += 1 + 1; + } + if (agentId_ != null) { + size += 1 + pb::CodedOutputStream.ComputeMessageSize(AgentId); + } + return size; + } + + public void MergeFrom(UpdateRequest other) { + if (other == null) { + return; + } + if (other.entityId_ != null) { + if (entityId_ == null) { + entityId_ = new Bgs.Protocol.EntityId(); + } + EntityId.MergeFrom(other.EntityId); + } + fieldOperation_.Add(other.fieldOperation_); + if (other.NoCreate != false) { + NoCreate = other.NoCreate; + } + if (other.agentId_ != null) { + if (agentId_ == null) { + agentId_ = new Bgs.Protocol.EntityId(); + } + AgentId.MergeFrom(other.AgentId); + } + } + + public void MergeFrom(pb::CodedInputStream input) { + uint tag; + while ((tag = input.ReadTag()) != 0) { + switch(tag) { + default: + input.SkipLastField(); + break; + case 10: { + if (entityId_ == null) { + entityId_ = new Bgs.Protocol.EntityId(); + } + input.ReadMessage(entityId_); + break; + } + case 18: { + fieldOperation_.AddEntriesFrom(input, _repeated_fieldOperation_codec); + break; + } + case 24: { + NoCreate = input.ReadBool(); + break; + } + case 34: { + if (agentId_ == null) { + agentId_ = new Bgs.Protocol.EntityId(); + } + input.ReadMessage(agentId_); + break; + } + } + } + } + + } + + [System.Diagnostics.DebuggerNonUserCodeAttribute()] + public sealed partial class QueryRequest : pb::IMessage { + private static readonly pb::MessageParser _parser = new pb::MessageParser(() => new QueryRequest()); + public static pb::MessageParser Parser { get { return _parser; } } + + public static pbr::MessageDescriptor Descriptor { + get { return Bgs.Protocol.Presence.V1.PresenceServiceReflection.Descriptor.MessageTypes[4]; } + } + + pbr::MessageDescriptor pb::IMessage.Descriptor { + get { return Descriptor; } + } + + public QueryRequest() { + OnConstruction(); + } + + partial void OnConstruction(); + + public QueryRequest(QueryRequest other) : this() { + EntityId = other.entityId_ != null ? other.EntityId.Clone() : null; + key_ = other.key_.Clone(); + AgentId = other.agentId_ != null ? other.AgentId.Clone() : null; + } + + public QueryRequest Clone() { + return new QueryRequest(this); + } + + /// Field number for the "entity_id" field. + public const int EntityIdFieldNumber = 1; + private Bgs.Protocol.EntityId entityId_; + public Bgs.Protocol.EntityId EntityId { + get { return entityId_; } + set { + entityId_ = value; + } + } + + /// Field number for the "key" field. + public const int KeyFieldNumber = 2; + private static readonly pb::FieldCodec _repeated_key_codec + = pb::FieldCodec.ForMessage(18, Bgs.Protocol.Presence.V1.FieldKey.Parser); + private readonly pbc::RepeatedField key_ = new pbc::RepeatedField(); + public pbc::RepeatedField Key { + get { return key_; } + } + + /// Field number for the "agent_id" field. + public const int AgentIdFieldNumber = 3; + private Bgs.Protocol.EntityId agentId_; + public Bgs.Protocol.EntityId AgentId { + get { return agentId_; } + set { + agentId_ = value; + } + } + + public override bool Equals(object other) { + return Equals(other as QueryRequest); + } + + public bool Equals(QueryRequest other) { + if (ReferenceEquals(other, null)) { + return false; + } + if (ReferenceEquals(other, this)) { + return true; + } + if (!object.Equals(EntityId, other.EntityId)) return false; + if(!key_.Equals(other.key_)) return false; + if (!object.Equals(AgentId, other.AgentId)) return false; + return true; + } + + public override int GetHashCode() { + int hash = 1; + if (entityId_ != null) hash ^= EntityId.GetHashCode(); + hash ^= key_.GetHashCode(); + if (agentId_ != null) hash ^= AgentId.GetHashCode(); + return hash; + } + + public override string ToString() { + return pb::JsonFormatter.ToDiagnosticString(this); + } + + public void WriteTo(pb::CodedOutputStream output) { + if (entityId_ != null) { + output.WriteRawTag(10); + output.WriteMessage(EntityId); + } + key_.WriteTo(output, _repeated_key_codec); + if (agentId_ != null) { + output.WriteRawTag(26); + output.WriteMessage(AgentId); + } + } + + public int CalculateSize() { + int size = 0; + if (entityId_ != null) { + size += 1 + pb::CodedOutputStream.ComputeMessageSize(EntityId); + } + size += key_.CalculateSize(_repeated_key_codec); + if (agentId_ != null) { + size += 1 + pb::CodedOutputStream.ComputeMessageSize(AgentId); + } + return size; + } + + public void MergeFrom(QueryRequest other) { + if (other == null) { + return; + } + if (other.entityId_ != null) { + if (entityId_ == null) { + entityId_ = new Bgs.Protocol.EntityId(); + } + EntityId.MergeFrom(other.EntityId); + } + key_.Add(other.key_); + if (other.agentId_ != null) { + if (agentId_ == null) { + agentId_ = new Bgs.Protocol.EntityId(); + } + AgentId.MergeFrom(other.AgentId); + } + } + + public void MergeFrom(pb::CodedInputStream input) { + uint tag; + while ((tag = input.ReadTag()) != 0) { + switch(tag) { + default: + input.SkipLastField(); + break; + case 10: { + if (entityId_ == null) { + entityId_ = new Bgs.Protocol.EntityId(); + } + input.ReadMessage(entityId_); + break; + } + case 18: { + key_.AddEntriesFrom(input, _repeated_key_codec); + break; + } + case 26: { + if (agentId_ == null) { + agentId_ = new Bgs.Protocol.EntityId(); + } + input.ReadMessage(agentId_); + break; + } + } + } + } + + } + + [System.Diagnostics.DebuggerNonUserCodeAttribute()] + public sealed partial class QueryResponse : pb::IMessage { + private static readonly pb::MessageParser _parser = new pb::MessageParser(() => new QueryResponse()); + public static pb::MessageParser Parser { get { return _parser; } } + + public static pbr::MessageDescriptor Descriptor { + get { return Bgs.Protocol.Presence.V1.PresenceServiceReflection.Descriptor.MessageTypes[5]; } + } + + pbr::MessageDescriptor pb::IMessage.Descriptor { + get { return Descriptor; } + } + + public QueryResponse() { + OnConstruction(); + } + + partial void OnConstruction(); + + public QueryResponse(QueryResponse other) : this() { + field_ = other.field_.Clone(); + } + + public QueryResponse Clone() { + return new QueryResponse(this); + } + + /// Field number for the "field" field. + public const int FieldFieldNumber = 2; + private static readonly pb::FieldCodec _repeated_field_codec + = pb::FieldCodec.ForMessage(18, Bgs.Protocol.Presence.V1.Field.Parser); + private readonly pbc::RepeatedField field_ = new pbc::RepeatedField(); + public pbc::RepeatedField Field { + get { return field_; } + } + + public override bool Equals(object other) { + return Equals(other as QueryResponse); + } + + public bool Equals(QueryResponse other) { + if (ReferenceEquals(other, null)) { + return false; + } + if (ReferenceEquals(other, this)) { + return true; + } + if(!field_.Equals(other.field_)) return false; + return true; + } + + public override int GetHashCode() { + int hash = 1; + hash ^= field_.GetHashCode(); + return hash; + } + + public override string ToString() { + return pb::JsonFormatter.ToDiagnosticString(this); + } + + public void WriteTo(pb::CodedOutputStream output) { + field_.WriteTo(output, _repeated_field_codec); + } + + public int CalculateSize() { + int size = 0; + size += field_.CalculateSize(_repeated_field_codec); + return size; + } + + public void MergeFrom(QueryResponse other) { + if (other == null) { + return; + } + field_.Add(other.field_); + } + + public void MergeFrom(pb::CodedInputStream input) { + uint tag; + while ((tag = input.ReadTag()) != 0) { + switch(tag) { + default: + input.SkipLastField(); + break; + case 18: { + field_.AddEntriesFrom(input, _repeated_field_codec); + break; + } + } + } + } + + } + + [System.Diagnostics.DebuggerNonUserCodeAttribute()] + public sealed partial class OwnershipRequest : pb::IMessage { + private static readonly pb::MessageParser _parser = new pb::MessageParser(() => new OwnershipRequest()); + public static pb::MessageParser Parser { get { return _parser; } } + + public static pbr::MessageDescriptor Descriptor { + get { return Bgs.Protocol.Presence.V1.PresenceServiceReflection.Descriptor.MessageTypes[6]; } + } + + pbr::MessageDescriptor pb::IMessage.Descriptor { + get { return Descriptor; } + } + + public OwnershipRequest() { + OnConstruction(); + } + + partial void OnConstruction(); + + public OwnershipRequest(OwnershipRequest other) : this() { + EntityId = other.entityId_ != null ? other.EntityId.Clone() : null; + releaseOwnership_ = other.releaseOwnership_; + } + + public OwnershipRequest Clone() { + return new OwnershipRequest(this); + } + + /// Field number for the "entity_id" field. + public const int EntityIdFieldNumber = 1; + private Bgs.Protocol.EntityId entityId_; + public Bgs.Protocol.EntityId EntityId { + get { return entityId_; } + set { + entityId_ = value; + } + } + + /// Field number for the "release_ownership" field. + public const int ReleaseOwnershipFieldNumber = 2; + private bool releaseOwnership_; + public bool ReleaseOwnership { + get { return releaseOwnership_; } + set { + releaseOwnership_ = value; + } + } + + public override bool Equals(object other) { + return Equals(other as OwnershipRequest); + } + + public bool Equals(OwnershipRequest other) { + if (ReferenceEquals(other, null)) { + return false; + } + if (ReferenceEquals(other, this)) { + return true; + } + if (!object.Equals(EntityId, other.EntityId)) return false; + if (ReleaseOwnership != other.ReleaseOwnership) return false; + return true; + } + + public override int GetHashCode() { + int hash = 1; + if (entityId_ != null) hash ^= EntityId.GetHashCode(); + if (ReleaseOwnership != false) hash ^= ReleaseOwnership.GetHashCode(); + return hash; + } + + public override string ToString() { + return pb::JsonFormatter.ToDiagnosticString(this); + } + + public void WriteTo(pb::CodedOutputStream output) { + if (entityId_ != null) { + output.WriteRawTag(10); + output.WriteMessage(EntityId); + } + if (ReleaseOwnership != false) { + output.WriteRawTag(16); + output.WriteBool(ReleaseOwnership); + } + } + + public int CalculateSize() { + int size = 0; + if (entityId_ != null) { + size += 1 + pb::CodedOutputStream.ComputeMessageSize(EntityId); + } + if (ReleaseOwnership != false) { + size += 1 + 1; + } + return size; + } + + public void MergeFrom(OwnershipRequest other) { + if (other == null) { + return; + } + if (other.entityId_ != null) { + if (entityId_ == null) { + entityId_ = new Bgs.Protocol.EntityId(); + } + EntityId.MergeFrom(other.EntityId); + } + if (other.ReleaseOwnership != false) { + ReleaseOwnership = other.ReleaseOwnership; + } + } + + public void MergeFrom(pb::CodedInputStream input) { + uint tag; + while ((tag = input.ReadTag()) != 0) { + switch(tag) { + default: + input.SkipLastField(); + break; + case 10: { + if (entityId_ == null) { + entityId_ = new Bgs.Protocol.EntityId(); + } + input.ReadMessage(entityId_); + break; + } + case 16: { + ReleaseOwnership = input.ReadBool(); + break; + } + } + } + } + + } + + #endregion + +} + +#endregion Designer generated code diff --git a/Framework/Proto/PresenceTypes.cs b/Framework/Proto/PresenceTypes.cs new file mode 100644 index 000000000..bce269cbb --- /dev/null +++ b/Framework/Proto/PresenceTypes.cs @@ -0,0 +1,882 @@ +// Generated by the protocol buffer compiler. DO NOT EDIT! +// source: bgs/low/pb/client/presence_types.proto +#pragma warning disable 1591, 0612, 3021 +#region Designer generated code + +using pb = Google.Protobuf; +using pbc = Google.Protobuf.Collections; +using pbr = Google.Protobuf.Reflection; +namespace Bgs.Protocol.Presence.V1 +{ + + /// Holder for reflection information generated from bgs/low/pb/client/presence_types.proto + [System.Diagnostics.DebuggerNonUserCodeAttribute()] + public static partial class PresenceTypesReflection { + + #region Descriptor + /// File descriptor for bgs/low/pb/client/presence_types.proto + public static pbr::FileDescriptor Descriptor { + get { return descriptor; } + } + private static pbr::FileDescriptor descriptor; + + static PresenceTypesReflection() { + byte[] descriptorData = System.Convert.FromBase64String( + string.Concat( + "CiZiZ3MvbG93L3BiL2NsaWVudC9wcmVzZW5jZV90eXBlcy5wcm90bxIYYmdz", + "LnByb3RvY29sLnByZXNlbmNlLnYxGidiZ3MvbG93L3BiL2NsaWVudC9hdHRy", + "aWJ1dGVfdHlwZXMucHJvdG8aJGJncy9sb3cvcGIvY2xpZW50L2VudGl0eV90", + "eXBlcy5wcm90bxolYmdzL2xvdy9wYi9jbGllbnQvY2hhbm5lbF90eXBlcy5w", + "cm90byJXChtSaWNoUHJlc2VuY2VMb2NhbGl6YXRpb25LZXkSDwoHcHJvZ3Jh", + "bRgBIAEoBxIOCgZzdHJlYW0YAiABKAcSFwoPbG9jYWxpemF0aW9uX2lkGAMg", + "ASgNIkwKCEZpZWxkS2V5Eg8KB3Byb2dyYW0YASABKA0SDQoFZ3JvdXAYAiAB", + "KA0SDQoFZmllbGQYAyABKA0SEQoJdW5pcXVlX2lkGAQgASgEIl4KBUZpZWxk", + "Ei8KA2tleRgBIAEoCzIiLmJncy5wcm90b2NvbC5wcmVzZW5jZS52MS5GaWVs", + "ZEtleRIkCgV2YWx1ZRgCIAEoCzIVLmJncy5wcm90b2NvbC5WYXJpYW50IrAB", + "Cg5GaWVsZE9wZXJhdGlvbhIuCgVmaWVsZBgBIAEoCzIfLmJncy5wcm90b2Nv", + "bC5wcmVzZW5jZS52MS5GaWVsZBJJCglvcGVyYXRpb24YAiABKA4yNi5iZ3Mu", + "cHJvdG9jb2wucHJlc2VuY2UudjEuRmllbGRPcGVyYXRpb24uT3BlcmF0aW9u", + "VHlwZSIjCg1PcGVyYXRpb25UeXBlEgcKA1NFVBAAEgkKBUNMRUFSEAEixwEK", + "DENoYW5uZWxTdGF0ZRIpCgllbnRpdHlfaWQYASABKAsyFi5iZ3MucHJvdG9j", + "b2wuRW50aXR5SWQSQQoPZmllbGRfb3BlcmF0aW9uGAIgAygLMiguYmdzLnBy", + "b3RvY29sLnByZXNlbmNlLnYxLkZpZWxkT3BlcmF0aW9uEg8KB2hlYWxpbmcY", + "AyABKAgSOAoIcHJlc2VuY2UYZSABKAsyJi5iZ3MucHJvdG9jb2wucHJlc2Vu", + "Y2UudjEuQ2hhbm5lbFN0YXRlQgJIAmIGcHJvdG8z")); + descriptor = pbr::FileDescriptor.FromGeneratedCode(descriptorData, + new pbr::FileDescriptor[] { Bgs.Protocol.AttributeTypesReflection.Descriptor, Bgs.Protocol.EntityTypesReflection.Descriptor, Bgs.Protocol.Channel.V1.ChannelTypesReflection.Descriptor, }, + new pbr::GeneratedClrTypeInfo(null, new pbr::GeneratedClrTypeInfo[] { + new pbr::GeneratedClrTypeInfo(typeof(Bgs.Protocol.Presence.V1.RichPresenceLocalizationKey), Bgs.Protocol.Presence.V1.RichPresenceLocalizationKey.Parser, new[]{ "Program", "Stream", "LocalizationId" }, null, null, null), + new pbr::GeneratedClrTypeInfo(typeof(Bgs.Protocol.Presence.V1.FieldKey), Bgs.Protocol.Presence.V1.FieldKey.Parser, new[]{ "Program", "Group", "Field", "UniqueId" }, null, null, null), + new pbr::GeneratedClrTypeInfo(typeof(Bgs.Protocol.Presence.V1.Field), Bgs.Protocol.Presence.V1.Field.Parser, new[]{ "Key", "Value" }, null, null, null), + new pbr::GeneratedClrTypeInfo(typeof(Bgs.Protocol.Presence.V1.FieldOperation), Bgs.Protocol.Presence.V1.FieldOperation.Parser, new[]{ "Field", "Operation" }, null, new[]{ typeof(Bgs.Protocol.Presence.V1.FieldOperation.Types.OperationType) }, null), + new pbr::GeneratedClrTypeInfo(typeof(Bgs.Protocol.Presence.V1.ChannelState), Bgs.Protocol.Presence.V1.ChannelState.Parser, new[]{ "EntityId", "FieldOperation", "Healing", "Presence" }, null, null, null) + })); + } + #endregion + + } + #region Messages + [System.Diagnostics.DebuggerNonUserCodeAttribute()] + public sealed partial class RichPresenceLocalizationKey : pb::IMessage { + private static readonly pb::MessageParser _parser = new pb::MessageParser(() => new RichPresenceLocalizationKey()); + public static pb::MessageParser Parser { get { return _parser; } } + + public static pbr::MessageDescriptor Descriptor { + get { return Bgs.Protocol.Presence.V1.PresenceTypesReflection.Descriptor.MessageTypes[0]; } + } + + pbr::MessageDescriptor pb::IMessage.Descriptor { + get { return Descriptor; } + } + + public RichPresenceLocalizationKey() { + OnConstruction(); + } + + partial void OnConstruction(); + + public RichPresenceLocalizationKey(RichPresenceLocalizationKey other) : this() { + program_ = other.program_; + stream_ = other.stream_; + localizationId_ = other.localizationId_; + } + + public RichPresenceLocalizationKey Clone() { + return new RichPresenceLocalizationKey(this); + } + + /// Field number for the "program" field. + public const int ProgramFieldNumber = 1; + private uint program_; + public uint Program { + get { return program_; } + set { + program_ = value; + } + } + + /// Field number for the "stream" field. + public const int StreamFieldNumber = 2; + private uint stream_; + public uint Stream { + get { return stream_; } + set { + stream_ = value; + } + } + + /// Field number for the "localization_id" field. + public const int LocalizationIdFieldNumber = 3; + private uint localizationId_; + public uint LocalizationId { + get { return localizationId_; } + set { + localizationId_ = value; + } + } + + public override bool Equals(object other) { + return Equals(other as RichPresenceLocalizationKey); + } + + public bool Equals(RichPresenceLocalizationKey other) { + if (ReferenceEquals(other, null)) { + return false; + } + if (ReferenceEquals(other, this)) { + return true; + } + if (Program != other.Program) return false; + if (Stream != other.Stream) return false; + if (LocalizationId != other.LocalizationId) return false; + return true; + } + + public override int GetHashCode() { + int hash = 1; + if (Program != 0) hash ^= Program.GetHashCode(); + if (Stream != 0) hash ^= Stream.GetHashCode(); + if (LocalizationId != 0) hash ^= LocalizationId.GetHashCode(); + return hash; + } + + public override string ToString() { + return pb::JsonFormatter.ToDiagnosticString(this); + } + + public void WriteTo(pb::CodedOutputStream output) { + if (Program != 0) { + output.WriteRawTag(13); + output.WriteFixed32(Program); + } + if (Stream != 0) { + output.WriteRawTag(21); + output.WriteFixed32(Stream); + } + if (LocalizationId != 0) { + output.WriteRawTag(24); + output.WriteUInt32(LocalizationId); + } + } + + public int CalculateSize() { + int size = 0; + if (Program != 0) { + size += 1 + 4; + } + if (Stream != 0) { + size += 1 + 4; + } + if (LocalizationId != 0) { + size += 1 + pb::CodedOutputStream.ComputeUInt32Size(LocalizationId); + } + return size; + } + + public void MergeFrom(RichPresenceLocalizationKey other) { + if (other == null) { + return; + } + if (other.Program != 0) { + Program = other.Program; + } + if (other.Stream != 0) { + Stream = other.Stream; + } + if (other.LocalizationId != 0) { + LocalizationId = other.LocalizationId; + } + } + + public void MergeFrom(pb::CodedInputStream input) { + uint tag; + while ((tag = input.ReadTag()) != 0) { + switch(tag) { + default: + input.SkipLastField(); + break; + case 13: { + Program = input.ReadFixed32(); + break; + } + case 21: { + Stream = input.ReadFixed32(); + break; + } + case 24: { + LocalizationId = input.ReadUInt32(); + break; + } + } + } + } + + } + + [System.Diagnostics.DebuggerNonUserCodeAttribute()] + public sealed partial class FieldKey : pb::IMessage { + private static readonly pb::MessageParser _parser = new pb::MessageParser(() => new FieldKey()); + public static pb::MessageParser Parser { get { return _parser; } } + + public static pbr::MessageDescriptor Descriptor { + get { return Bgs.Protocol.Presence.V1.PresenceTypesReflection.Descriptor.MessageTypes[1]; } + } + + pbr::MessageDescriptor pb::IMessage.Descriptor { + get { return Descriptor; } + } + + public FieldKey() { + OnConstruction(); + } + + partial void OnConstruction(); + + public FieldKey(FieldKey other) : this() { + program_ = other.program_; + group_ = other.group_; + field_ = other.field_; + uniqueId_ = other.uniqueId_; + } + + public FieldKey Clone() { + return new FieldKey(this); + } + + /// Field number for the "program" field. + public const int ProgramFieldNumber = 1; + private uint program_; + public uint Program { + get { return program_; } + set { + program_ = value; + } + } + + /// Field number for the "group" field. + public const int GroupFieldNumber = 2; + private uint group_; + public uint Group { + get { return group_; } + set { + group_ = value; + } + } + + /// Field number for the "field" field. + public const int FieldFieldNumber = 3; + private uint field_; + public uint Field { + get { return field_; } + set { + field_ = value; + } + } + + /// Field number for the "unique_id" field. + public const int UniqueIdFieldNumber = 4; + private ulong uniqueId_; + public ulong UniqueId { + get { return uniqueId_; } + set { + uniqueId_ = value; + } + } + + public override bool Equals(object other) { + return Equals(other as FieldKey); + } + + public bool Equals(FieldKey other) { + if (ReferenceEquals(other, null)) { + return false; + } + if (ReferenceEquals(other, this)) { + return true; + } + if (Program != other.Program) return false; + if (Group != other.Group) return false; + if (Field != other.Field) return false; + if (UniqueId != other.UniqueId) return false; + return true; + } + + public override int GetHashCode() { + int hash = 1; + if (Program != 0) hash ^= Program.GetHashCode(); + if (Group != 0) hash ^= Group.GetHashCode(); + if (Field != 0) hash ^= Field.GetHashCode(); + if (UniqueId != 0UL) hash ^= UniqueId.GetHashCode(); + return hash; + } + + public override string ToString() { + return pb::JsonFormatter.ToDiagnosticString(this); + } + + public void WriteTo(pb::CodedOutputStream output) { + if (Program != 0) { + output.WriteRawTag(8); + output.WriteUInt32(Program); + } + if (Group != 0) { + output.WriteRawTag(16); + output.WriteUInt32(Group); + } + if (Field != 0) { + output.WriteRawTag(24); + output.WriteUInt32(Field); + } + if (UniqueId != 0UL) { + output.WriteRawTag(32); + output.WriteUInt64(UniqueId); + } + } + + public int CalculateSize() { + int size = 0; + if (Program != 0) { + size += 1 + pb::CodedOutputStream.ComputeUInt32Size(Program); + } + if (Group != 0) { + size += 1 + pb::CodedOutputStream.ComputeUInt32Size(Group); + } + if (Field != 0) { + size += 1 + pb::CodedOutputStream.ComputeUInt32Size(Field); + } + if (UniqueId != 0UL) { + size += 1 + pb::CodedOutputStream.ComputeUInt64Size(UniqueId); + } + return size; + } + + public void MergeFrom(FieldKey other) { + if (other == null) { + return; + } + if (other.Program != 0) { + Program = other.Program; + } + if (other.Group != 0) { + Group = other.Group; + } + if (other.Field != 0) { + Field = other.Field; + } + if (other.UniqueId != 0UL) { + UniqueId = other.UniqueId; + } + } + + public void MergeFrom(pb::CodedInputStream input) { + uint tag; + while ((tag = input.ReadTag()) != 0) { + switch(tag) { + default: + input.SkipLastField(); + break; + case 8: { + Program = input.ReadUInt32(); + break; + } + case 16: { + Group = input.ReadUInt32(); + break; + } + case 24: { + Field = input.ReadUInt32(); + break; + } + case 32: { + UniqueId = input.ReadUInt64(); + break; + } + } + } + } + + } + + [System.Diagnostics.DebuggerNonUserCodeAttribute()] + public sealed partial class Field : pb::IMessage { + private static readonly pb::MessageParser _parser = new pb::MessageParser(() => new Field()); + public static pb::MessageParser Parser { get { return _parser; } } + + public static pbr::MessageDescriptor Descriptor { + get { return Bgs.Protocol.Presence.V1.PresenceTypesReflection.Descriptor.MessageTypes[2]; } + } + + pbr::MessageDescriptor pb::IMessage.Descriptor { + get { return Descriptor; } + } + + public Field() { + OnConstruction(); + } + + partial void OnConstruction(); + + public Field(Field other) : this() { + Key = other.key_ != null ? other.Key.Clone() : null; + Value = other.value_ != null ? other.Value.Clone() : null; + } + + public Field Clone() { + return new Field(this); + } + + /// Field number for the "key" field. + public const int KeyFieldNumber = 1; + private Bgs.Protocol.Presence.V1.FieldKey key_; + public Bgs.Protocol.Presence.V1.FieldKey Key { + get { return key_; } + set { + key_ = value; + } + } + + /// Field number for the "value" field. + public const int ValueFieldNumber = 2; + private Bgs.Protocol.Variant value_; + public Bgs.Protocol.Variant Value { + get { return value_; } + set { + value_ = value; + } + } + + public override bool Equals(object other) { + return Equals(other as Field); + } + + public bool Equals(Field other) { + if (ReferenceEquals(other, null)) { + return false; + } + if (ReferenceEquals(other, this)) { + return true; + } + if (!object.Equals(Key, other.Key)) return false; + if (!object.Equals(Value, other.Value)) return false; + return true; + } + + public override int GetHashCode() { + int hash = 1; + if (key_ != null) hash ^= Key.GetHashCode(); + if (value_ != null) hash ^= Value.GetHashCode(); + return hash; + } + + public override string ToString() { + return pb::JsonFormatter.ToDiagnosticString(this); + } + + public void WriteTo(pb::CodedOutputStream output) { + if (key_ != null) { + output.WriteRawTag(10); + output.WriteMessage(Key); + } + if (value_ != null) { + output.WriteRawTag(18); + output.WriteMessage(Value); + } + } + + public int CalculateSize() { + int size = 0; + if (key_ != null) { + size += 1 + pb::CodedOutputStream.ComputeMessageSize(Key); + } + if (value_ != null) { + size += 1 + pb::CodedOutputStream.ComputeMessageSize(Value); + } + return size; + } + + public void MergeFrom(Field other) { + if (other == null) { + return; + } + if (other.key_ != null) { + if (key_ == null) { + key_ = new Bgs.Protocol.Presence.V1.FieldKey(); + } + Key.MergeFrom(other.Key); + } + if (other.value_ != null) { + if (value_ == null) { + value_ = new Bgs.Protocol.Variant(); + } + Value.MergeFrom(other.Value); + } + } + + public void MergeFrom(pb::CodedInputStream input) { + uint tag; + while ((tag = input.ReadTag()) != 0) { + switch(tag) { + default: + input.SkipLastField(); + break; + case 10: { + if (key_ == null) { + key_ = new Bgs.Protocol.Presence.V1.FieldKey(); + } + input.ReadMessage(key_); + break; + } + case 18: { + if (value_ == null) { + value_ = new Bgs.Protocol.Variant(); + } + input.ReadMessage(value_); + break; + } + } + } + } + + } + + [System.Diagnostics.DebuggerNonUserCodeAttribute()] + public sealed partial class FieldOperation : pb::IMessage { + private static readonly pb::MessageParser _parser = new pb::MessageParser(() => new FieldOperation()); + public static pb::MessageParser Parser { get { return _parser; } } + + public static pbr::MessageDescriptor Descriptor { + get { return Bgs.Protocol.Presence.V1.PresenceTypesReflection.Descriptor.MessageTypes[3]; } + } + + pbr::MessageDescriptor pb::IMessage.Descriptor { + get { return Descriptor; } + } + + public FieldOperation() { + OnConstruction(); + } + + partial void OnConstruction(); + + public FieldOperation(FieldOperation other) : this() { + Field = other.field_ != null ? other.Field.Clone() : null; + operation_ = other.operation_; + } + + public FieldOperation Clone() { + return new FieldOperation(this); + } + + /// Field number for the "field" field. + public const int FieldFieldNumber = 1; + private Bgs.Protocol.Presence.V1.Field field_; + public Bgs.Protocol.Presence.V1.Field Field { + get { return field_; } + set { + field_ = value; + } + } + + /// Field number for the "operation" field. + public const int OperationFieldNumber = 2; + private Bgs.Protocol.Presence.V1.FieldOperation.Types.OperationType operation_ = Bgs.Protocol.Presence.V1.FieldOperation.Types.OperationType.SET; + public Bgs.Protocol.Presence.V1.FieldOperation.Types.OperationType Operation { + get { return operation_; } + set { + operation_ = value; + } + } + + public override bool Equals(object other) { + return Equals(other as FieldOperation); + } + + public bool Equals(FieldOperation other) { + if (ReferenceEquals(other, null)) { + return false; + } + if (ReferenceEquals(other, this)) { + return true; + } + if (!object.Equals(Field, other.Field)) return false; + if (Operation != other.Operation) return false; + return true; + } + + public override int GetHashCode() { + int hash = 1; + if (field_ != null) hash ^= Field.GetHashCode(); + if (Operation != Bgs.Protocol.Presence.V1.FieldOperation.Types.OperationType.SET) hash ^= Operation.GetHashCode(); + return hash; + } + + public override string ToString() { + return pb::JsonFormatter.ToDiagnosticString(this); + } + + public void WriteTo(pb::CodedOutputStream output) { + if (field_ != null) { + output.WriteRawTag(10); + output.WriteMessage(Field); + } + if (Operation != Bgs.Protocol.Presence.V1.FieldOperation.Types.OperationType.SET) { + output.WriteRawTag(16); + output.WriteEnum((int) Operation); + } + } + + public int CalculateSize() { + int size = 0; + if (field_ != null) { + size += 1 + pb::CodedOutputStream.ComputeMessageSize(Field); + } + if (Operation != Bgs.Protocol.Presence.V1.FieldOperation.Types.OperationType.SET) { + size += 1 + pb::CodedOutputStream.ComputeEnumSize((int) Operation); + } + return size; + } + + public void MergeFrom(FieldOperation other) { + if (other == null) { + return; + } + if (other.field_ != null) { + if (field_ == null) { + field_ = new Bgs.Protocol.Presence.V1.Field(); + } + Field.MergeFrom(other.Field); + } + if (other.Operation != Bgs.Protocol.Presence.V1.FieldOperation.Types.OperationType.SET) { + Operation = other.Operation; + } + } + + public void MergeFrom(pb::CodedInputStream input) { + uint tag; + while ((tag = input.ReadTag()) != 0) { + switch(tag) { + default: + input.SkipLastField(); + break; + case 10: { + if (field_ == null) { + field_ = new Bgs.Protocol.Presence.V1.Field(); + } + input.ReadMessage(field_); + break; + } + case 16: { + operation_ = (Bgs.Protocol.Presence.V1.FieldOperation.Types.OperationType) input.ReadEnum(); + break; + } + } + } + } + + #region Nested types + /// Container for nested types declared in the FieldOperation message type. + [System.Diagnostics.DebuggerNonUserCodeAttribute()] + public static partial class Types { + public enum OperationType { + SET = 0, + CLEAR = 1, + } + + } + #endregion + + } + + [System.Diagnostics.DebuggerNonUserCodeAttribute()] + public sealed partial class ChannelState : pb::IMessage { + private static readonly pb::MessageParser _parser = new pb::MessageParser(() => new ChannelState()); + public static pb::MessageParser Parser { get { return _parser; } } + + public static pbr::MessageDescriptor Descriptor { + get { return Bgs.Protocol.Presence.V1.PresenceTypesReflection.Descriptor.MessageTypes[4]; } + } + + pbr::MessageDescriptor pb::IMessage.Descriptor { + get { return Descriptor; } + } + + public ChannelState() { + OnConstruction(); + } + + partial void OnConstruction(); + + public ChannelState(ChannelState other) : this() { + EntityId = other.entityId_ != null ? other.EntityId.Clone() : null; + fieldOperation_ = other.fieldOperation_.Clone(); + healing_ = other.healing_; + Presence = other.presence_ != null ? other.Presence.Clone() : null; + } + + public ChannelState Clone() { + return new ChannelState(this); + } + + /// Field number for the "entity_id" field. + public const int EntityIdFieldNumber = 1; + private Bgs.Protocol.EntityId entityId_; + public Bgs.Protocol.EntityId EntityId { + get { return entityId_; } + set { + entityId_ = value; + } + } + + /// Field number for the "field_operation" field. + public const int FieldOperationFieldNumber = 2; + private static readonly pb::FieldCodec _repeated_fieldOperation_codec + = pb::FieldCodec.ForMessage(18, Bgs.Protocol.Presence.V1.FieldOperation.Parser); + private readonly pbc::RepeatedField fieldOperation_ = new pbc::RepeatedField(); + public pbc::RepeatedField FieldOperation { + get { return fieldOperation_; } + } + + /// Field number for the "healing" field. + public const int HealingFieldNumber = 3; + private bool healing_; + public bool Healing { + get { return healing_; } + set { + healing_ = value; + } + } + + /// Field number for the "presence" field. + public const int PresenceFieldNumber = 101; + private Bgs.Protocol.Presence.V1.ChannelState presence_; + public Bgs.Protocol.Presence.V1.ChannelState Presence { + get { return presence_; } + set { + presence_ = value; + } + } + + public override bool Equals(object other) { + return Equals(other as ChannelState); + } + + public bool Equals(ChannelState other) { + if (ReferenceEquals(other, null)) { + return false; + } + if (ReferenceEquals(other, this)) { + return true; + } + if (!object.Equals(EntityId, other.EntityId)) return false; + if(!fieldOperation_.Equals(other.fieldOperation_)) return false; + if (Healing != other.Healing) return false; + if (!object.Equals(Presence, other.Presence)) return false; + return true; + } + + public override int GetHashCode() { + int hash = 1; + if (entityId_ != null) hash ^= EntityId.GetHashCode(); + hash ^= fieldOperation_.GetHashCode(); + if (Healing != false) hash ^= Healing.GetHashCode(); + if (presence_ != null) hash ^= Presence.GetHashCode(); + return hash; + } + + public override string ToString() { + return pb::JsonFormatter.ToDiagnosticString(this); + } + + public void WriteTo(pb::CodedOutputStream output) { + if (entityId_ != null) { + output.WriteRawTag(10); + output.WriteMessage(EntityId); + } + fieldOperation_.WriteTo(output, _repeated_fieldOperation_codec); + if (Healing != false) { + output.WriteRawTag(24); + output.WriteBool(Healing); + } + if (presence_ != null) { + output.WriteRawTag(170, 6); + output.WriteMessage(Presence); + } + } + + public int CalculateSize() { + int size = 0; + if (entityId_ != null) { + size += 1 + pb::CodedOutputStream.ComputeMessageSize(EntityId); + } + size += fieldOperation_.CalculateSize(_repeated_fieldOperation_codec); + if (Healing != false) { + size += 1 + 1; + } + if (presence_ != null) { + size += 2 + pb::CodedOutputStream.ComputeMessageSize(Presence); + } + return size; + } + + public void MergeFrom(ChannelState other) { + if (other == null) { + return; + } + if (other.entityId_ != null) { + if (entityId_ == null) { + entityId_ = new Bgs.Protocol.EntityId(); + } + EntityId.MergeFrom(other.EntityId); + } + fieldOperation_.Add(other.fieldOperation_); + if (other.Healing != false) { + Healing = other.Healing; + } + if (other.presence_ != null) { + if (presence_ == null) { + presence_ = new Bgs.Protocol.Presence.V1.ChannelState(); + } + Presence.MergeFrom(other.Presence); + } + } + + public void MergeFrom(pb::CodedInputStream input) { + uint tag; + while ((tag = input.ReadTag()) != 0) { + switch(tag) { + default: + input.SkipLastField(); + break; + case 10: { + if (entityId_ == null) { + entityId_ = new Bgs.Protocol.EntityId(); + } + input.ReadMessage(entityId_); + break; + } + case 18: { + fieldOperation_.AddEntriesFrom(input, _repeated_fieldOperation_codec); + break; + } + case 24: { + Healing = input.ReadBool(); + break; + } + case 810: { + if (presence_ == null) { + presence_ = new Bgs.Protocol.Presence.V1.ChannelState(); + } + input.ReadMessage(presence_); + break; + } + } + } + } + + } + + #endregion + +} + +#endregion Designer generated code diff --git a/Framework/Proto/ProfanityFilterConfig.cs b/Framework/Proto/ProfanityFilterConfig.cs new file mode 100644 index 000000000..150bd45f6 --- /dev/null +++ b/Framework/Proto/ProfanityFilterConfig.cs @@ -0,0 +1,271 @@ +// Generated by the protocol buffer compiler. DO NOT EDIT! +// source: bgs/low/pb/client/profanity_filter_config.proto +#pragma warning disable 1591, 0612, 3021 +#region Designer generated code + +using pb = Google.Protobuf; +using pbc = Google.Protobuf.Collections; +using pbr = Google.Protobuf.Reflection; +namespace Bgs.Protocol.Profanity.V1 +{ + + /// Holder for reflection information generated from bgs/low/pb/client/profanity_filter_config.proto + [System.Diagnostics.DebuggerNonUserCodeAttribute()] + public static partial class ProfanityFilterConfigReflection { + + #region Descriptor + /// File descriptor for bgs/low/pb/client/profanity_filter_config.proto + public static pbr::FileDescriptor Descriptor { + get { return descriptor; } + } + private static pbr::FileDescriptor descriptor; + + static ProfanityFilterConfigReflection() { + byte[] descriptorData = System.Convert.FromBase64String( + string.Concat( + "Ci9iZ3MvbG93L3BiL2NsaWVudC9wcm9mYW5pdHlfZmlsdGVyX2NvbmZpZy5w", + "cm90bxIZYmdzLnByb3RvY29sLnByb2Zhbml0eS52MSIpCgpXb3JkRmlsdGVy", + "EgwKBHR5cGUYASABKAkSDQoFcmVnZXgYAiABKAkiRQoLV29yZEZpbHRlcnMS", + "NgoHZmlsdGVycxgBIAMoCzIlLmJncy5wcm90b2NvbC5wcm9mYW5pdHkudjEu", + "V29yZEZpbHRlckICSAJiBnByb3RvMw==")); + descriptor = pbr::FileDescriptor.FromGeneratedCode(descriptorData, + new pbr::FileDescriptor[] { }, + new pbr::GeneratedClrTypeInfo(null, new pbr::GeneratedClrTypeInfo[] { + new pbr::GeneratedClrTypeInfo(typeof(Bgs.Protocol.Profanity.V1.WordFilter), Bgs.Protocol.Profanity.V1.WordFilter.Parser, new[]{ "Type", "Regex" }, null, null, null), + new pbr::GeneratedClrTypeInfo(typeof(Bgs.Protocol.Profanity.V1.WordFilters), Bgs.Protocol.Profanity.V1.WordFilters.Parser, new[]{ "Filters" }, null, null, null) + })); + } + #endregion + + } + #region Messages + [System.Diagnostics.DebuggerNonUserCodeAttribute()] + public sealed partial class WordFilter : pb::IMessage { + private static readonly pb::MessageParser _parser = new pb::MessageParser(() => new WordFilter()); + public static pb::MessageParser Parser { get { return _parser; } } + + public static pbr::MessageDescriptor Descriptor { + get { return Bgs.Protocol.Profanity.V1.ProfanityFilterConfigReflection.Descriptor.MessageTypes[0]; } + } + + pbr::MessageDescriptor pb::IMessage.Descriptor { + get { return Descriptor; } + } + + public WordFilter() { + OnConstruction(); + } + + partial void OnConstruction(); + + public WordFilter(WordFilter other) : this() { + type_ = other.type_; + regex_ = other.regex_; + } + + public WordFilter Clone() { + return new WordFilter(this); + } + + /// Field number for the "type" field. + public const int TypeFieldNumber = 1; + private string type_ = ""; + public string Type { + get { return type_; } + set { + type_ = pb::ProtoPreconditions.CheckNotNull(value, "value"); + } + } + + /// Field number for the "regex" field. + public const int RegexFieldNumber = 2; + private string regex_ = ""; + public string Regex { + get { return regex_; } + set { + regex_ = pb::ProtoPreconditions.CheckNotNull(value, "value"); + } + } + + public override bool Equals(object other) { + return Equals(other as WordFilter); + } + + public bool Equals(WordFilter other) { + if (ReferenceEquals(other, null)) { + return false; + } + if (ReferenceEquals(other, this)) { + return true; + } + if (Type != other.Type) return false; + if (Regex != other.Regex) return false; + return true; + } + + public override int GetHashCode() { + int hash = 1; + if (Type.Length != 0) hash ^= Type.GetHashCode(); + if (Regex.Length != 0) hash ^= Regex.GetHashCode(); + return hash; + } + + public override string ToString() { + return pb::JsonFormatter.ToDiagnosticString(this); + } + + public void WriteTo(pb::CodedOutputStream output) { + if (Type.Length != 0) { + output.WriteRawTag(10); + output.WriteString(Type); + } + if (Regex.Length != 0) { + output.WriteRawTag(18); + output.WriteString(Regex); + } + } + + public int CalculateSize() { + int size = 0; + if (Type.Length != 0) { + size += 1 + pb::CodedOutputStream.ComputeStringSize(Type); + } + if (Regex.Length != 0) { + size += 1 + pb::CodedOutputStream.ComputeStringSize(Regex); + } + return size; + } + + public void MergeFrom(WordFilter other) { + if (other == null) { + return; + } + if (other.Type.Length != 0) { + Type = other.Type; + } + if (other.Regex.Length != 0) { + Regex = other.Regex; + } + } + + public void MergeFrom(pb::CodedInputStream input) { + uint tag; + while ((tag = input.ReadTag()) != 0) { + switch(tag) { + default: + input.SkipLastField(); + break; + case 10: { + Type = input.ReadString(); + break; + } + case 18: { + Regex = input.ReadString(); + break; + } + } + } + } + + } + + [System.Diagnostics.DebuggerNonUserCodeAttribute()] + public sealed partial class WordFilters : pb::IMessage { + private static readonly pb::MessageParser _parser = new pb::MessageParser(() => new WordFilters()); + public static pb::MessageParser Parser { get { return _parser; } } + + public static pbr::MessageDescriptor Descriptor { + get { return Bgs.Protocol.Profanity.V1.ProfanityFilterConfigReflection.Descriptor.MessageTypes[1]; } + } + + pbr::MessageDescriptor pb::IMessage.Descriptor { + get { return Descriptor; } + } + + public WordFilters() { + OnConstruction(); + } + + partial void OnConstruction(); + + public WordFilters(WordFilters other) : this() { + filters_ = other.filters_.Clone(); + } + + public WordFilters Clone() { + return new WordFilters(this); + } + + /// Field number for the "filters" field. + public const int FiltersFieldNumber = 1; + private static readonly pb::FieldCodec _repeated_filters_codec + = pb::FieldCodec.ForMessage(10, Bgs.Protocol.Profanity.V1.WordFilter.Parser); + private readonly pbc::RepeatedField filters_ = new pbc::RepeatedField(); + public pbc::RepeatedField Filters { + get { return filters_; } + } + + public override bool Equals(object other) { + return Equals(other as WordFilters); + } + + public bool Equals(WordFilters other) { + if (ReferenceEquals(other, null)) { + return false; + } + if (ReferenceEquals(other, this)) { + return true; + } + if(!filters_.Equals(other.filters_)) return false; + return true; + } + + public override int GetHashCode() { + int hash = 1; + hash ^= filters_.GetHashCode(); + return hash; + } + + public override string ToString() { + return pb::JsonFormatter.ToDiagnosticString(this); + } + + public void WriteTo(pb::CodedOutputStream output) { + filters_.WriteTo(output, _repeated_filters_codec); + } + + public int CalculateSize() { + int size = 0; + size += filters_.CalculateSize(_repeated_filters_codec); + return size; + } + + public void MergeFrom(WordFilters other) { + if (other == null) { + return; + } + filters_.Add(other.filters_); + } + + public void MergeFrom(pb::CodedInputStream input) { + uint tag; + while ((tag = input.ReadTag()) != 0) { + switch(tag) { + default: + input.SkipLastField(); + break; + case 10: { + filters_.AddEntriesFrom(input, _repeated_filters_codec); + break; + } + } + } + } + + } + + #endregion + +} + +#endregion Designer generated code diff --git a/Framework/Proto/ReportService.cs b/Framework/Proto/ReportService.cs new file mode 100644 index 000000000..72a4d07b7 --- /dev/null +++ b/Framework/Proto/ReportService.cs @@ -0,0 +1,406 @@ +// Generated by the protocol buffer compiler. DO NOT EDIT! +// source: bgs/low/pb/client/report_service.proto +#pragma warning disable 1591, 0612, 3021 +#region Designer generated code + +using pb = Google.Protobuf; +using pbc = Google.Protobuf.Collections; +using pbr = Google.Protobuf.Reflection; +namespace Bgs.Protocol.Report.V1 +{ + + /// Holder for reflection information generated from bgs/low/pb/client/report_service.proto + [System.Diagnostics.DebuggerNonUserCodeAttribute()] + public static partial class ReportServiceReflection { + + #region Descriptor + /// File descriptor for bgs/low/pb/client/report_service.proto + public static pbr::FileDescriptor Descriptor { + get { return descriptor; } + } + private static pbr::FileDescriptor descriptor; + + static ReportServiceReflection() { + byte[] descriptorData = System.Convert.FromBase64String( + string.Concat( + "CiZiZ3MvbG93L3BiL2NsaWVudC9yZXBvcnRfc2VydmljZS5wcm90bxIWYmdz", + "LnByb3RvY29sLnJlcG9ydC52MRonYmdzL2xvdy9wYi9jbGllbnQvYXR0cmli", + "dXRlX3R5cGVzLnByb3RvGiRiZ3MvbG93L3BiL2NsaWVudC9lbnRpdHlfdHlw", + "ZXMucHJvdG8aIWJncy9sb3cvcGIvY2xpZW50L3JwY190eXBlcy5wcm90byLi", + "AQoGUmVwb3J0EhMKC3JlcG9ydF90eXBlGAEgASgJEioKCWF0dHJpYnV0ZRgC", + "IAMoCzIXLmJncy5wcm90b2NvbC5BdHRyaWJ1dGUSEgoKcmVwb3J0X3FvcxgD", + "IAEoBRIxChFyZXBvcnRpbmdfYWNjb3VudBgEIAEoCzIWLmJncy5wcm90b2Nv", + "bC5FbnRpdHlJZBI2ChZyZXBvcnRpbmdfZ2FtZV9hY2NvdW50GAUgASgLMhYu", + "YmdzLnByb3RvY29sLkVudGl0eUlkEhgKEHJlcG9ydF90aW1lc3RhbXAYBiAB", + "KAYiQwoRU2VuZFJlcG9ydFJlcXVlc3QSLgoGcmVwb3J0GAEgASgLMh4uYmdz", + "LnByb3RvY29sLnJlcG9ydC52MS5SZXBvcnQyXgoNUmVwb3J0U2VydmljZRJN", + "CgpTZW5kUmVwb3J0EikuYmdzLnByb3RvY29sLnJlcG9ydC52MS5TZW5kUmVw", + "b3J0UmVxdWVzdBoULmJncy5wcm90b2NvbC5Ob0RhdGFCBUgCgAEAYgZwcm90", + "bzM=")); + descriptor = pbr::FileDescriptor.FromGeneratedCode(descriptorData, + new pbr::FileDescriptor[] { Bgs.Protocol.AttributeTypesReflection.Descriptor, Bgs.Protocol.EntityTypesReflection.Descriptor, Bgs.Protocol.RpcTypesReflection.Descriptor, }, + new pbr::GeneratedClrTypeInfo(null, new pbr::GeneratedClrTypeInfo[] { + new pbr::GeneratedClrTypeInfo(typeof(Bgs.Protocol.Report.V1.Report), Bgs.Protocol.Report.V1.Report.Parser, new[]{ "ReportType", "Attribute", "ReportQos", "ReportingAccount", "ReportingGameAccount", "ReportTimestamp" }, null, null, null), + new pbr::GeneratedClrTypeInfo(typeof(Bgs.Protocol.Report.V1.SendReportRequest), Bgs.Protocol.Report.V1.SendReportRequest.Parser, new[]{ "Report" }, null, null, null) + })); + } + #endregion + + } + #region Messages + [System.Diagnostics.DebuggerNonUserCodeAttribute()] + public sealed partial class Report : pb::IMessage { + private static readonly pb::MessageParser _parser = new pb::MessageParser(() => new Report()); + public static pb::MessageParser Parser { get { return _parser; } } + + public static pbr::MessageDescriptor Descriptor { + get { return Bgs.Protocol.Report.V1.ReportServiceReflection.Descriptor.MessageTypes[0]; } + } + + pbr::MessageDescriptor pb::IMessage.Descriptor { + get { return Descriptor; } + } + + public Report() { + OnConstruction(); + } + + partial void OnConstruction(); + + public Report(Report other) : this() { + reportType_ = other.reportType_; + attribute_ = other.attribute_.Clone(); + reportQos_ = other.reportQos_; + ReportingAccount = other.reportingAccount_ != null ? other.ReportingAccount.Clone() : null; + ReportingGameAccount = other.reportingGameAccount_ != null ? other.ReportingGameAccount.Clone() : null; + reportTimestamp_ = other.reportTimestamp_; + } + + public Report Clone() { + return new Report(this); + } + + /// Field number for the "report_type" field. + public const int ReportTypeFieldNumber = 1; + private string reportType_ = ""; + public string ReportType { + get { return reportType_; } + set { + reportType_ = pb::ProtoPreconditions.CheckNotNull(value, "value"); + } + } + + /// Field number for the "attribute" field. + public const int AttributeFieldNumber = 2; + private static readonly pb::FieldCodec _repeated_attribute_codec + = pb::FieldCodec.ForMessage(18, Bgs.Protocol.Attribute.Parser); + private readonly pbc::RepeatedField attribute_ = new pbc::RepeatedField(); + public pbc::RepeatedField Attribute { + get { return attribute_; } + } + + /// Field number for the "report_qos" field. + public const int ReportQosFieldNumber = 3; + private int reportQos_; + public int ReportQos { + get { return reportQos_; } + set { + reportQos_ = value; + } + } + + /// Field number for the "reporting_account" field. + public const int ReportingAccountFieldNumber = 4; + private Bgs.Protocol.EntityId reportingAccount_; + public Bgs.Protocol.EntityId ReportingAccount { + get { return reportingAccount_; } + set { + reportingAccount_ = value; + } + } + + /// Field number for the "reporting_game_account" field. + public const int ReportingGameAccountFieldNumber = 5; + private Bgs.Protocol.EntityId reportingGameAccount_; + public Bgs.Protocol.EntityId ReportingGameAccount { + get { return reportingGameAccount_; } + set { + reportingGameAccount_ = value; + } + } + + /// Field number for the "report_timestamp" field. + public const int ReportTimestampFieldNumber = 6; + private ulong reportTimestamp_; + public ulong ReportTimestamp { + get { return reportTimestamp_; } + set { + reportTimestamp_ = value; + } + } + + public override bool Equals(object other) { + return Equals(other as Report); + } + + public bool Equals(Report other) { + if (ReferenceEquals(other, null)) { + return false; + } + if (ReferenceEquals(other, this)) { + return true; + } + if (ReportType != other.ReportType) return false; + if(!attribute_.Equals(other.attribute_)) return false; + if (ReportQos != other.ReportQos) return false; + if (!object.Equals(ReportingAccount, other.ReportingAccount)) return false; + if (!object.Equals(ReportingGameAccount, other.ReportingGameAccount)) return false; + if (ReportTimestamp != other.ReportTimestamp) return false; + return true; + } + + public override int GetHashCode() { + int hash = 1; + if (ReportType.Length != 0) hash ^= ReportType.GetHashCode(); + hash ^= attribute_.GetHashCode(); + if (ReportQos != 0) hash ^= ReportQos.GetHashCode(); + if (reportingAccount_ != null) hash ^= ReportingAccount.GetHashCode(); + if (reportingGameAccount_ != null) hash ^= ReportingGameAccount.GetHashCode(); + if (ReportTimestamp != 0UL) hash ^= ReportTimestamp.GetHashCode(); + return hash; + } + + public override string ToString() { + return pb::JsonFormatter.ToDiagnosticString(this); + } + + public void WriteTo(pb::CodedOutputStream output) { + if (ReportType.Length != 0) { + output.WriteRawTag(10); + output.WriteString(ReportType); + } + attribute_.WriteTo(output, _repeated_attribute_codec); + if (ReportQos != 0) { + output.WriteRawTag(24); + output.WriteInt32(ReportQos); + } + if (reportingAccount_ != null) { + output.WriteRawTag(34); + output.WriteMessage(ReportingAccount); + } + if (reportingGameAccount_ != null) { + output.WriteRawTag(42); + output.WriteMessage(ReportingGameAccount); + } + if (ReportTimestamp != 0UL) { + output.WriteRawTag(49); + output.WriteFixed64(ReportTimestamp); + } + } + + public int CalculateSize() { + int size = 0; + if (ReportType.Length != 0) { + size += 1 + pb::CodedOutputStream.ComputeStringSize(ReportType); + } + size += attribute_.CalculateSize(_repeated_attribute_codec); + if (ReportQos != 0) { + size += 1 + pb::CodedOutputStream.ComputeInt32Size(ReportQos); + } + if (reportingAccount_ != null) { + size += 1 + pb::CodedOutputStream.ComputeMessageSize(ReportingAccount); + } + if (reportingGameAccount_ != null) { + size += 1 + pb::CodedOutputStream.ComputeMessageSize(ReportingGameAccount); + } + if (ReportTimestamp != 0UL) { + size += 1 + 8; + } + return size; + } + + public void MergeFrom(Report other) { + if (other == null) { + return; + } + if (other.ReportType.Length != 0) { + ReportType = other.ReportType; + } + attribute_.Add(other.attribute_); + if (other.ReportQos != 0) { + ReportQos = other.ReportQos; + } + if (other.reportingAccount_ != null) { + if (reportingAccount_ == null) { + reportingAccount_ = new Bgs.Protocol.EntityId(); + } + ReportingAccount.MergeFrom(other.ReportingAccount); + } + if (other.reportingGameAccount_ != null) { + if (reportingGameAccount_ == null) { + reportingGameAccount_ = new Bgs.Protocol.EntityId(); + } + ReportingGameAccount.MergeFrom(other.ReportingGameAccount); + } + if (other.ReportTimestamp != 0UL) { + ReportTimestamp = other.ReportTimestamp; + } + } + + public void MergeFrom(pb::CodedInputStream input) { + uint tag; + while ((tag = input.ReadTag()) != 0) { + switch(tag) { + default: + input.SkipLastField(); + break; + case 10: { + ReportType = input.ReadString(); + break; + } + case 18: { + attribute_.AddEntriesFrom(input, _repeated_attribute_codec); + break; + } + case 24: { + ReportQos = input.ReadInt32(); + break; + } + case 34: { + if (reportingAccount_ == null) { + reportingAccount_ = new Bgs.Protocol.EntityId(); + } + input.ReadMessage(reportingAccount_); + break; + } + case 42: { + if (reportingGameAccount_ == null) { + reportingGameAccount_ = new Bgs.Protocol.EntityId(); + } + input.ReadMessage(reportingGameAccount_); + break; + } + case 49: { + ReportTimestamp = input.ReadFixed64(); + break; + } + } + } + } + + } + + [System.Diagnostics.DebuggerNonUserCodeAttribute()] + public sealed partial class SendReportRequest : pb::IMessage { + private static readonly pb::MessageParser _parser = new pb::MessageParser(() => new SendReportRequest()); + public static pb::MessageParser Parser { get { return _parser; } } + + public static pbr::MessageDescriptor Descriptor { + get { return Bgs.Protocol.Report.V1.ReportServiceReflection.Descriptor.MessageTypes[1]; } + } + + pbr::MessageDescriptor pb::IMessage.Descriptor { + get { return Descriptor; } + } + + public SendReportRequest() { + OnConstruction(); + } + + partial void OnConstruction(); + + public SendReportRequest(SendReportRequest other) : this() { + Report = other.report_ != null ? other.Report.Clone() : null; + } + + public SendReportRequest Clone() { + return new SendReportRequest(this); + } + + /// Field number for the "report" field. + public const int ReportFieldNumber = 1; + private Bgs.Protocol.Report.V1.Report report_; + public Bgs.Protocol.Report.V1.Report Report { + get { return report_; } + set { + report_ = value; + } + } + + public override bool Equals(object other) { + return Equals(other as SendReportRequest); + } + + public bool Equals(SendReportRequest other) { + if (ReferenceEquals(other, null)) { + return false; + } + if (ReferenceEquals(other, this)) { + return true; + } + if (!object.Equals(Report, other.Report)) return false; + return true; + } + + public override int GetHashCode() { + int hash = 1; + if (report_ != null) hash ^= Report.GetHashCode(); + return hash; + } + + public override string ToString() { + return pb::JsonFormatter.ToDiagnosticString(this); + } + + public void WriteTo(pb::CodedOutputStream output) { + if (report_ != null) { + output.WriteRawTag(10); + output.WriteMessage(Report); + } + } + + public int CalculateSize() { + int size = 0; + if (report_ != null) { + size += 1 + pb::CodedOutputStream.ComputeMessageSize(Report); + } + return size; + } + + public void MergeFrom(SendReportRequest other) { + if (other == null) { + return; + } + if (other.report_ != null) { + if (report_ == null) { + report_ = new Bgs.Protocol.Report.V1.Report(); + } + Report.MergeFrom(other.Report); + } + } + + public void MergeFrom(pb::CodedInputStream input) { + uint tag; + while ((tag = input.ReadTag()) != 0) { + switch(tag) { + default: + input.SkipLastField(); + break; + case 10: { + if (report_ == null) { + report_ = new Bgs.Protocol.Report.V1.Report(); + } + input.ReadMessage(report_); + break; + } + } + } + } + + } + + #endregion + +} + +#endregion Designer generated code diff --git a/Framework/Proto/ResourceService.cs b/Framework/Proto/ResourceService.cs new file mode 100644 index 000000000..2f2fdc827 --- /dev/null +++ b/Framework/Proto/ResourceService.cs @@ -0,0 +1,244 @@ +// Generated by the protocol buffer compiler. DO NOT EDIT! +// source: bgs/low/pb/client/resource_service.proto +#pragma warning disable 1591, 0612, 3021 +#region Designer generated code + +using pb = Google.Protobuf; +using pbr = Google.Protobuf.Reflection; +namespace Bgs.Protocol.Resources.V1 +{ + + /// Holder for reflection information generated from bgs/low/pb/client/resource_service.proto + [System.Diagnostics.DebuggerNonUserCodeAttribute()] + public static partial class ResourceServiceReflection + { + + #region Descriptor + /// File descriptor for bgs/low/pb/client/resource_service.proto + public static pbr::FileDescriptor Descriptor + { + get { return descriptor; } + } + private static pbr::FileDescriptor descriptor; + + static ResourceServiceReflection() + { + byte[] descriptorData = System.Convert.FromBase64String( + string.Concat( + "CihiZ3MvbG93L3BiL2NsaWVudC9yZXNvdXJjZV9zZXJ2aWNlLnByb3RvEhli", + "Z3MucHJvdG9jb2wucmVzb3VyY2VzLnYxGixiZ3MvbG93L3BiL2NsaWVudC9j", + "b250ZW50X2hhbmRsZV90eXBlcy5wcm90bxohYmdzL2xvdy9wYi9jbGllbnQv", + "cnBjX3R5cGVzLnByb3RvIkgKFENvbnRlbnRIYW5kbGVSZXF1ZXN0Eg8KB3By", + "b2dyYW0YASABKAcSDgoGc3RyZWFtGAIgASgHEg8KB3ZlcnNpb24YAyABKAcy", + "dAoQUmVzb3VyY2VzU2VydmljZRJgChBHZXRDb250ZW50SGFuZGxlEi8uYmdz", + "LnByb3RvY29sLnJlc291cmNlcy52MS5Db250ZW50SGFuZGxlUmVxdWVzdBob", + "LmJncy5wcm90b2NvbC5Db250ZW50SGFuZGxlQgVIAoABAGIGcHJvdG8z")); + descriptor = pbr::FileDescriptor.FromGeneratedCode(descriptorData, + new pbr::FileDescriptor[] { Bgs.Protocol.ContentHandleTypesReflection.Descriptor, Bgs.Protocol.RpcTypesReflection.Descriptor, }, + new pbr::GeneratedClrTypeInfo(null, new pbr::GeneratedClrTypeInfo[] { + new pbr::GeneratedClrTypeInfo(typeof(Bgs.Protocol.Resources.V1.ContentHandleRequest), Bgs.Protocol.Resources.V1.ContentHandleRequest.Parser, new[]{ "Program", "Stream", "Version" }, null, null, null) + })); + } + #endregion + + } + #region Messages + [System.Diagnostics.DebuggerNonUserCodeAttribute()] + public sealed partial class ContentHandleRequest : pb::IMessage + { + private static readonly pb::MessageParser _parser = new pb::MessageParser(() => new ContentHandleRequest()); + public static pb::MessageParser Parser { get { return _parser; } } + + public static pbr::MessageDescriptor Descriptor + { + get { return Bgs.Protocol.Resources.V1.ResourceServiceReflection.Descriptor.MessageTypes[0]; } + } + + pbr::MessageDescriptor pb::IMessage.Descriptor + { + get { return Descriptor; } + } + + public ContentHandleRequest() + { + OnConstruction(); + } + + partial void OnConstruction(); + + public ContentHandleRequest(ContentHandleRequest other) : this() + { + program_ = other.program_; + stream_ = other.stream_; + version_ = other.version_; + } + + public ContentHandleRequest Clone() + { + return new ContentHandleRequest(this); + } + + /// Field number for the "program" field. + public const int ProgramFieldNumber = 1; + private uint program_; + public uint Program + { + get { return program_; } + set + { + program_ = value; + } + } + + /// Field number for the "stream" field. + public const int StreamFieldNumber = 2; + private uint stream_; + public uint Stream + { + get { return stream_; } + set + { + stream_ = value; + } + } + + /// Field number for the "version" field. + public const int VersionFieldNumber = 3; + private uint version_; + public uint Version + { + get { return version_; } + set + { + version_ = value; + } + } + + public override bool Equals(object other) + { + return Equals(other as ContentHandleRequest); + } + + public bool Equals(ContentHandleRequest other) + { + if (ReferenceEquals(other, null)) + { + return false; + } + if (ReferenceEquals(other, this)) + { + return true; + } + if (Program != other.Program) return false; + if (Stream != other.Stream) return false; + if (Version != other.Version) return false; + return true; + } + + public override int GetHashCode() + { + int hash = 1; + if (Program != 0) hash ^= Program.GetHashCode(); + if (Stream != 0) hash ^= Stream.GetHashCode(); + if (Version != 0) hash ^= Version.GetHashCode(); + return hash; + } + + public override string ToString() + { + return pb::JsonFormatter.ToDiagnosticString(this); + } + + public void WriteTo(pb::CodedOutputStream output) + { + if (Program != 0) + { + output.WriteRawTag(13); + output.WriteFixed32(Program); + } + if (Stream != 0) + { + output.WriteRawTag(21); + output.WriteFixed32(Stream); + } + if (Version != 0) + { + output.WriteRawTag(29); + output.WriteFixed32(Version); + } + } + + public int CalculateSize() + { + int size = 0; + if (Program != 0) + { + size += 1 + 4; + } + if (Stream != 0) + { + size += 1 + 4; + } + if (Version != 0) + { + size += 1 + 4; + } + return size; + } + + public void MergeFrom(ContentHandleRequest other) + { + if (other == null) + { + return; + } + if (other.Program != 0) + { + Program = other.Program; + } + if (other.Stream != 0) + { + Stream = other.Stream; + } + if (other.Version != 0) + { + Version = other.Version; + } + } + + public void MergeFrom(pb::CodedInputStream input) + { + uint tag; + while ((tag = input.ReadTag()) != 0) + { + switch (tag) + { + default: + input.SkipLastField(); + break; + case 13: + { + Program = input.ReadFixed32(); + break; + } + case 21: + { + Stream = input.ReadFixed32(); + break; + } + case 29: + { + Version = input.ReadFixed32(); + break; + } + } + } + } + + } + + #endregion + +} + +#endregion Designer generated code diff --git a/Framework/Proto/RoleTypes.cs b/Framework/Proto/RoleTypes.cs new file mode 100644 index 000000000..2a4ff91e2 --- /dev/null +++ b/Framework/Proto/RoleTypes.cs @@ -0,0 +1,357 @@ +// Generated by the protocol buffer compiler. DO NOT EDIT! +// source: bgs/low/pb/client/role_types.proto +#pragma warning disable 1591, 0612, 3021 +#region Designer generated code + +using pb = Google.Protobuf; +using pbc = Google.Protobuf.Collections; +using pbr = Google.Protobuf.Reflection; +namespace Bgs.Protocol +{ + + /// Holder for reflection information generated from bgs/low/pb/client/role_types.proto + [System.Diagnostics.DebuggerNonUserCodeAttribute()] + public static partial class RoleTypesReflection { + + #region Descriptor + /// File descriptor for bgs/low/pb/client/role_types.proto + public static pbr::FileDescriptor Descriptor { + get { return descriptor; } + } + private static pbr::FileDescriptor descriptor; + + static RoleTypesReflection() { + byte[] descriptorData = System.Convert.FromBase64String( + string.Concat( + "CiJiZ3MvbG93L3BiL2NsaWVudC9yb2xlX3R5cGVzLnByb3RvEgxiZ3MucHJv", + "dG9jb2waJ2Jncy9sb3cvcGIvY2xpZW50L2F0dHJpYnV0ZV90eXBlcy5wcm90", + "byLuAQoEUm9sZRIKCgJpZBgBIAEoDRIMCgRuYW1lGAIgASgJEhEKCXByaXZp", + "bGVnZRgDIAMoCRIbCg9hc3NpZ25hYmxlX3JvbGUYBCADKA1CAhABEhAKCHJl", + "cXVpcmVkGAUgASgIEg4KBnVuaXF1ZRgGIAEoCBIXCg9yZWxlZ2F0aW9uX3Jv", + "bGUYByABKA0SKgoJYXR0cmlidXRlGAggAygLMhcuYmdzLnByb3RvY29sLkF0", + "dHJpYnV0ZRIZCg1raWNrYWJsZV9yb2xlGAkgAygNQgIQARIaCg5yZW1vdmFi", + "bGVfcm9sZRgKIAMoDUICEAFCAkgCYgZwcm90bzM=")); + descriptor = pbr::FileDescriptor.FromGeneratedCode(descriptorData, + new pbr::FileDescriptor[] { Bgs.Protocol.AttributeTypesReflection.Descriptor, }, + new pbr::GeneratedClrTypeInfo(null, new pbr::GeneratedClrTypeInfo[] { + new pbr::GeneratedClrTypeInfo(typeof(Bgs.Protocol.Role), Bgs.Protocol.Role.Parser, new[]{ "Id", "Name", "Privilege", "AssignableRole", "Required", "Unique", "RelegationRole", "Attribute", "KickableRole", "RemovableRole" }, null, null, null) + })); + } + #endregion + + } + #region Messages + [System.Diagnostics.DebuggerNonUserCodeAttribute()] + public sealed partial class Role : pb::IMessage { + private static readonly pb::MessageParser _parser = new pb::MessageParser(() => new Role()); + public static pb::MessageParser Parser { get { return _parser; } } + + public static pbr::MessageDescriptor Descriptor { + get { return Bgs.Protocol.RoleTypesReflection.Descriptor.MessageTypes[0]; } + } + + pbr::MessageDescriptor pb::IMessage.Descriptor { + get { return Descriptor; } + } + + public Role() { + OnConstruction(); + } + + partial void OnConstruction(); + + public Role(Role other) : this() { + id_ = other.id_; + name_ = other.name_; + privilege_ = other.privilege_.Clone(); + assignableRole_ = other.assignableRole_.Clone(); + required_ = other.required_; + unique_ = other.unique_; + relegationRole_ = other.relegationRole_; + attribute_ = other.attribute_.Clone(); + kickableRole_ = other.kickableRole_.Clone(); + removableRole_ = other.removableRole_.Clone(); + } + + public Role Clone() { + return new Role(this); + } + + /// Field number for the "id" field. + public const int IdFieldNumber = 1; + private uint id_; + public uint Id { + get { return id_; } + set { + id_ = value; + } + } + + /// Field number for the "name" field. + public const int NameFieldNumber = 2; + private string name_ = ""; + public string Name { + get { return name_; } + set { + name_ = pb::ProtoPreconditions.CheckNotNull(value, "value"); + } + } + + /// Field number for the "privilege" field. + public const int PrivilegeFieldNumber = 3; + private static readonly pb::FieldCodec _repeated_privilege_codec + = pb::FieldCodec.ForString(26); + private readonly pbc::RepeatedField privilege_ = new pbc::RepeatedField(); + public pbc::RepeatedField Privilege { + get { return privilege_; } + } + + /// Field number for the "assignable_role" field. + public const int AssignableRoleFieldNumber = 4; + private static readonly pb::FieldCodec _repeated_assignableRole_codec + = pb::FieldCodec.ForUInt32(34); + private readonly pbc::RepeatedField assignableRole_ = new pbc::RepeatedField(); + public pbc::RepeatedField AssignableRole { + get { return assignableRole_; } + } + + /// Field number for the "required" field. + public const int RequiredFieldNumber = 5; + private bool required_; + public bool Required { + get { return required_; } + set { + required_ = value; + } + } + + /// Field number for the "unique" field. + public const int UniqueFieldNumber = 6; + private bool unique_; + public bool Unique { + get { return unique_; } + set { + unique_ = value; + } + } + + /// Field number for the "relegation_role" field. + public const int RelegationRoleFieldNumber = 7; + private uint relegationRole_; + public uint RelegationRole { + get { return relegationRole_; } + set { + relegationRole_ = value; + } + } + + /// Field number for the "attribute" field. + public const int AttributeFieldNumber = 8; + private static readonly pb::FieldCodec _repeated_attribute_codec + = pb::FieldCodec.ForMessage(66, Bgs.Protocol.Attribute.Parser); + private readonly pbc::RepeatedField attribute_ = new pbc::RepeatedField(); + public pbc::RepeatedField Attribute { + get { return attribute_; } + } + + /// Field number for the "kickable_role" field. + public const int KickableRoleFieldNumber = 9; + private static readonly pb::FieldCodec _repeated_kickableRole_codec + = pb::FieldCodec.ForUInt32(74); + private readonly pbc::RepeatedField kickableRole_ = new pbc::RepeatedField(); + public pbc::RepeatedField KickableRole { + get { return kickableRole_; } + } + + /// Field number for the "removable_role" field. + public const int RemovableRoleFieldNumber = 10; + private static readonly pb::FieldCodec _repeated_removableRole_codec + = pb::FieldCodec.ForUInt32(82); + private readonly pbc::RepeatedField removableRole_ = new pbc::RepeatedField(); + public pbc::RepeatedField RemovableRole { + get { return removableRole_; } + } + + public override bool Equals(object other) { + return Equals(other as Role); + } + + public bool Equals(Role other) { + if (ReferenceEquals(other, null)) { + return false; + } + if (ReferenceEquals(other, this)) { + return true; + } + if (Id != other.Id) return false; + if (Name != other.Name) return false; + if(!privilege_.Equals(other.privilege_)) return false; + if(!assignableRole_.Equals(other.assignableRole_)) return false; + if (Required != other.Required) return false; + if (Unique != other.Unique) return false; + if (RelegationRole != other.RelegationRole) return false; + if(!attribute_.Equals(other.attribute_)) return false; + if(!kickableRole_.Equals(other.kickableRole_)) return false; + if(!removableRole_.Equals(other.removableRole_)) return false; + return true; + } + + public override int GetHashCode() { + int hash = 1; + if (Id != 0) hash ^= Id.GetHashCode(); + if (Name.Length != 0) hash ^= Name.GetHashCode(); + hash ^= privilege_.GetHashCode(); + hash ^= assignableRole_.GetHashCode(); + if (Required != false) hash ^= Required.GetHashCode(); + if (Unique != false) hash ^= Unique.GetHashCode(); + if (RelegationRole != 0) hash ^= RelegationRole.GetHashCode(); + hash ^= attribute_.GetHashCode(); + hash ^= kickableRole_.GetHashCode(); + hash ^= removableRole_.GetHashCode(); + return hash; + } + + public override string ToString() { + return pb::JsonFormatter.ToDiagnosticString(this); + } + + public void WriteTo(pb::CodedOutputStream output) { + if (Id != 0) { + output.WriteRawTag(8); + output.WriteUInt32(Id); + } + if (Name.Length != 0) { + output.WriteRawTag(18); + output.WriteString(Name); + } + privilege_.WriteTo(output, _repeated_privilege_codec); + assignableRole_.WriteTo(output, _repeated_assignableRole_codec); + if (Required != false) { + output.WriteRawTag(40); + output.WriteBool(Required); + } + if (Unique != false) { + output.WriteRawTag(48); + output.WriteBool(Unique); + } + if (RelegationRole != 0) { + output.WriteRawTag(56); + output.WriteUInt32(RelegationRole); + } + attribute_.WriteTo(output, _repeated_attribute_codec); + kickableRole_.WriteTo(output, _repeated_kickableRole_codec); + removableRole_.WriteTo(output, _repeated_removableRole_codec); + } + + public int CalculateSize() { + int size = 0; + if (Id != 0) { + size += 1 + pb::CodedOutputStream.ComputeUInt32Size(Id); + } + if (Name.Length != 0) { + size += 1 + pb::CodedOutputStream.ComputeStringSize(Name); + } + size += privilege_.CalculateSize(_repeated_privilege_codec); + size += assignableRole_.CalculateSize(_repeated_assignableRole_codec); + if (Required != false) { + size += 1 + 1; + } + if (Unique != false) { + size += 1 + 1; + } + if (RelegationRole != 0) { + size += 1 + pb::CodedOutputStream.ComputeUInt32Size(RelegationRole); + } + size += attribute_.CalculateSize(_repeated_attribute_codec); + size += kickableRole_.CalculateSize(_repeated_kickableRole_codec); + size += removableRole_.CalculateSize(_repeated_removableRole_codec); + return size; + } + + public void MergeFrom(Role other) { + if (other == null) { + return; + } + if (other.Id != 0) { + Id = other.Id; + } + if (other.Name.Length != 0) { + Name = other.Name; + } + privilege_.Add(other.privilege_); + assignableRole_.Add(other.assignableRole_); + if (other.Required != false) { + Required = other.Required; + } + if (other.Unique != false) { + Unique = other.Unique; + } + if (other.RelegationRole != 0) { + RelegationRole = other.RelegationRole; + } + attribute_.Add(other.attribute_); + kickableRole_.Add(other.kickableRole_); + removableRole_.Add(other.removableRole_); + } + + public void MergeFrom(pb::CodedInputStream input) { + uint tag; + while ((tag = input.ReadTag()) != 0) { + switch(tag) { + default: + input.SkipLastField(); + break; + case 8: { + Id = input.ReadUInt32(); + break; + } + case 18: { + Name = input.ReadString(); + break; + } + case 26: { + privilege_.AddEntriesFrom(input, _repeated_privilege_codec); + break; + } + case 34: + case 32: { + assignableRole_.AddEntriesFrom(input, _repeated_assignableRole_codec); + break; + } + case 40: { + Required = input.ReadBool(); + break; + } + case 48: { + Unique = input.ReadBool(); + break; + } + case 56: { + RelegationRole = input.ReadUInt32(); + break; + } + case 66: { + attribute_.AddEntriesFrom(input, _repeated_attribute_codec); + break; + } + case 74: + case 72: { + kickableRole_.AddEntriesFrom(input, _repeated_kickableRole_codec); + break; + } + case 82: + case 80: { + removableRole_.AddEntriesFrom(input, _repeated_removableRole_codec); + break; + } + } + } + } + + } + + #endregion + +} + +#endregion Designer generated code diff --git a/Framework/Proto/RpcConfig.cs b/Framework/Proto/RpcConfig.cs new file mode 100644 index 000000000..484c784a4 --- /dev/null +++ b/Framework/Proto/RpcConfig.cs @@ -0,0 +1,972 @@ +// Generated by the protocol buffer compiler. DO NOT EDIT! +// source: bgs/low/pb/client/rpc_config.proto +#pragma warning disable 1591, 0612, 3021 +#region Designer generated code + +using pb = Google.Protobuf; +using pbc = Google.Protobuf.Collections; +using pbr = Google.Protobuf.Reflection; +namespace Bgs.Protocol.Config +{ + + /// Holder for reflection information generated from bgs/low/pb/client/rpc_config.proto + [System.Diagnostics.DebuggerNonUserCodeAttribute()] + public static partial class RpcConfigReflection { + + #region Descriptor + /// File descriptor for bgs/low/pb/client/rpc_config.proto + public static pbr::FileDescriptor Descriptor { + get { return descriptor; } + } + private static pbr::FileDescriptor descriptor; + + static RpcConfigReflection() { + byte[] descriptorData = System.Convert.FromBase64String( + string.Concat( + "CiJiZ3MvbG93L3BiL2NsaWVudC9ycGNfY29uZmlnLnByb3RvEhNiZ3MucHJv", + "dG9jb2wuY29uZmlnIvwCCg9SUENNZXRob2RDb25maWcSGAoMc2VydmljZV9u", + "YW1lGAEgASgJQgIYARIXCgttZXRob2RfbmFtZRgCIAEoCUICGAESFwoPZml4", + "ZWRfY2FsbF9jb3N0GAMgASgNEhkKEWZpeGVkX3BhY2tldF9zaXplGAQgASgN", + "EhsKE3ZhcmlhYmxlX211bHRpcGxpZXIYBSABKAISEgoKbXVsdGlwbGllchgG", + "IAEoAhIYChByYXRlX2xpbWl0X2NvdW50GAcgASgNEhoKEnJhdGVfbGltaXRf", + "c2Vjb25kcxgIIAEoDRIXCg9tYXhfcGFja2V0X3NpemUYCSABKA0SGAoQbWF4", + "X2VuY29kZWRfc2l6ZRgKIAEoDRIPCgd0aW1lb3V0GAsgASgCEhMKC2NhcF9i", + "YWxhbmNlGAwgASgNEhkKEWluY29tZV9wZXJfc2Vjb25kGA0gASgCEhQKDHNl", + "cnZpY2VfaGFzaBgOIAEoDRIRCgltZXRob2RfaWQYDyABKA0ipwEKDlJQQ01l", + "dGVyQ29uZmlnEjQKBm1ldGhvZBgBIAMoCzIkLmJncy5wcm90b2NvbC5jb25m", + "aWcuUlBDTWV0aG9kQ29uZmlnEhkKEWluY29tZV9wZXJfc2Vjb25kGAIgASgN", + "EhcKD2luaXRpYWxfYmFsYW5jZRgDIAEoDRITCgtjYXBfYmFsYW5jZRgEIAEo", + "DRIWCg5zdGFydHVwX3BlcmlvZBgFIAEoAiJJCg1Qcm90b2NvbEFsaWFzEhsK", + "E3NlcnZlcl9zZXJ2aWNlX25hbWUYASABKAkSGwoTY2xpZW50X3NlcnZpY2Vf", + "bmFtZRgCIAEoCSJMCg5TZXJ2aWNlQWxpYXNlcxI6Cg5wcm90b2NvbF9hbGlh", + "cxgBIAMoCzIiLmJncy5wcm90b2NvbC5jb25maWcuUHJvdG9jb2xBbGlhc0IC", + "SAJiBnByb3RvMw==")); + descriptor = pbr::FileDescriptor.FromGeneratedCode(descriptorData, + new pbr::FileDescriptor[] { }, + new pbr::GeneratedClrTypeInfo(null, new pbr::GeneratedClrTypeInfo[] { + new pbr::GeneratedClrTypeInfo(typeof(Bgs.Protocol.Config.RPCMethodConfig), Bgs.Protocol.Config.RPCMethodConfig.Parser, new[]{ "ServiceName", "MethodName", "FixedCallCost", "FixedPacketSize", "VariableMultiplier", "Multiplier", "RateLimitCount", "RateLimitSeconds", "MaxPacketSize", "MaxEncodedSize", "Timeout", "CapBalance", "IncomePerSecond", "ServiceHash", "MethodId" }, null, null, null), + new pbr::GeneratedClrTypeInfo(typeof(Bgs.Protocol.Config.RPCMeterConfig), Bgs.Protocol.Config.RPCMeterConfig.Parser, new[]{ "Method", "IncomePerSecond", "InitialBalance", "CapBalance", "StartupPeriod" }, null, null, null), + new pbr::GeneratedClrTypeInfo(typeof(Bgs.Protocol.Config.ProtocolAlias), Bgs.Protocol.Config.ProtocolAlias.Parser, new[]{ "ServerServiceName", "ClientServiceName" }, null, null, null), + new pbr::GeneratedClrTypeInfo(typeof(Bgs.Protocol.Config.ServiceAliases), Bgs.Protocol.Config.ServiceAliases.Parser, new[]{ "ProtocolAlias" }, null, null, null) + })); + } + #endregion + + } + #region Messages + [System.Diagnostics.DebuggerNonUserCodeAttribute()] + public sealed partial class RPCMethodConfig : pb::IMessage { + private static readonly pb::MessageParser _parser = new pb::MessageParser(() => new RPCMethodConfig()); + public static pb::MessageParser Parser { get { return _parser; } } + + public static pbr::MessageDescriptor Descriptor { + get { return Bgs.Protocol.Config.RpcConfigReflection.Descriptor.MessageTypes[0]; } + } + + pbr::MessageDescriptor pb::IMessage.Descriptor { + get { return Descriptor; } + } + + public RPCMethodConfig() { + OnConstruction(); + } + + partial void OnConstruction(); + + public RPCMethodConfig(RPCMethodConfig other) : this() { + serviceName_ = other.serviceName_; + methodName_ = other.methodName_; + fixedCallCost_ = other.fixedCallCost_; + fixedPacketSize_ = other.fixedPacketSize_; + variableMultiplier_ = other.variableMultiplier_; + multiplier_ = other.multiplier_; + rateLimitCount_ = other.rateLimitCount_; + rateLimitSeconds_ = other.rateLimitSeconds_; + maxPacketSize_ = other.maxPacketSize_; + maxEncodedSize_ = other.maxEncodedSize_; + timeout_ = other.timeout_; + capBalance_ = other.capBalance_; + incomePerSecond_ = other.incomePerSecond_; + serviceHash_ = other.serviceHash_; + methodId_ = other.methodId_; + } + + public RPCMethodConfig Clone() { + return new RPCMethodConfig(this); + } + + /// Field number for the "service_name" field. + public const int ServiceNameFieldNumber = 1; + private string serviceName_ = ""; + [System.ObsoleteAttribute()] + public string ServiceName { + get { return serviceName_; } + set { + serviceName_ = pb::ProtoPreconditions.CheckNotNull(value, "value"); + } + } + + /// Field number for the "method_name" field. + public const int MethodNameFieldNumber = 2; + private string methodName_ = ""; + [System.ObsoleteAttribute()] + public string MethodName { + get { return methodName_; } + set { + methodName_ = pb::ProtoPreconditions.CheckNotNull(value, "value"); + } + } + + /// Field number for the "fixed_call_cost" field. + public const int FixedCallCostFieldNumber = 3; + private uint fixedCallCost_; + public uint FixedCallCost { + get { return fixedCallCost_; } + set { + fixedCallCost_ = value; + } + } + + /// Field number for the "fixed_packet_size" field. + public const int FixedPacketSizeFieldNumber = 4; + private uint fixedPacketSize_; + public uint FixedPacketSize { + get { return fixedPacketSize_; } + set { + fixedPacketSize_ = value; + } + } + + /// Field number for the "variable_multiplier" field. + public const int VariableMultiplierFieldNumber = 5; + private float variableMultiplier_; + public float VariableMultiplier { + get { return variableMultiplier_; } + set { + variableMultiplier_ = value; + } + } + + /// Field number for the "multiplier" field. + public const int MultiplierFieldNumber = 6; + private float multiplier_; + public float Multiplier { + get { return multiplier_; } + set { + multiplier_ = value; + } + } + + /// Field number for the "rate_limit_count" field. + public const int RateLimitCountFieldNumber = 7; + private uint rateLimitCount_; + public uint RateLimitCount { + get { return rateLimitCount_; } + set { + rateLimitCount_ = value; + } + } + + /// Field number for the "rate_limit_seconds" field. + public const int RateLimitSecondsFieldNumber = 8; + private uint rateLimitSeconds_; + public uint RateLimitSeconds { + get { return rateLimitSeconds_; } + set { + rateLimitSeconds_ = value; + } + } + + /// Field number for the "max_packet_size" field. + public const int MaxPacketSizeFieldNumber = 9; + private uint maxPacketSize_; + public uint MaxPacketSize { + get { return maxPacketSize_; } + set { + maxPacketSize_ = value; + } + } + + /// Field number for the "max_encoded_size" field. + public const int MaxEncodedSizeFieldNumber = 10; + private uint maxEncodedSize_; + public uint MaxEncodedSize { + get { return maxEncodedSize_; } + set { + maxEncodedSize_ = value; + } + } + + /// Field number for the "timeout" field. + public const int TimeoutFieldNumber = 11; + private float timeout_; + public float Timeout { + get { return timeout_; } + set { + timeout_ = value; + } + } + + /// Field number for the "cap_balance" field. + public const int CapBalanceFieldNumber = 12; + private uint capBalance_; + public uint CapBalance { + get { return capBalance_; } + set { + capBalance_ = value; + } + } + + /// Field number for the "income_per_second" field. + public const int IncomePerSecondFieldNumber = 13; + private float incomePerSecond_; + public float IncomePerSecond { + get { return incomePerSecond_; } + set { + incomePerSecond_ = value; + } + } + + /// Field number for the "service_hash" field. + public const int ServiceHashFieldNumber = 14; + private uint serviceHash_; + public uint ServiceHash { + get { return serviceHash_; } + set { + serviceHash_ = value; + } + } + + /// Field number for the "method_id" field. + public const int MethodIdFieldNumber = 15; + private uint methodId_; + public uint MethodId { + get { return methodId_; } + set { + methodId_ = value; + } + } + + public override bool Equals(object other) { + return Equals(other as RPCMethodConfig); + } + + public bool Equals(RPCMethodConfig other) { + if (ReferenceEquals(other, null)) { + return false; + } + if (ReferenceEquals(other, this)) { + return true; + } + if (ServiceName != other.ServiceName) return false; + if (MethodName != other.MethodName) return false; + if (FixedCallCost != other.FixedCallCost) return false; + if (FixedPacketSize != other.FixedPacketSize) return false; + if (VariableMultiplier != other.VariableMultiplier) return false; + if (Multiplier != other.Multiplier) return false; + if (RateLimitCount != other.RateLimitCount) return false; + if (RateLimitSeconds != other.RateLimitSeconds) return false; + if (MaxPacketSize != other.MaxPacketSize) return false; + if (MaxEncodedSize != other.MaxEncodedSize) return false; + if (Timeout != other.Timeout) return false; + if (CapBalance != other.CapBalance) return false; + if (IncomePerSecond != other.IncomePerSecond) return false; + if (ServiceHash != other.ServiceHash) return false; + if (MethodId != other.MethodId) return false; + return true; + } + + public override int GetHashCode() { + int hash = 1; + if (ServiceName.Length != 0) hash ^= ServiceName.GetHashCode(); + if (MethodName.Length != 0) hash ^= MethodName.GetHashCode(); + if (FixedCallCost != 0) hash ^= FixedCallCost.GetHashCode(); + if (FixedPacketSize != 0) hash ^= FixedPacketSize.GetHashCode(); + if (VariableMultiplier != 0F) hash ^= VariableMultiplier.GetHashCode(); + if (Multiplier != 0F) hash ^= Multiplier.GetHashCode(); + if (RateLimitCount != 0) hash ^= RateLimitCount.GetHashCode(); + if (RateLimitSeconds != 0) hash ^= RateLimitSeconds.GetHashCode(); + if (MaxPacketSize != 0) hash ^= MaxPacketSize.GetHashCode(); + if (MaxEncodedSize != 0) hash ^= MaxEncodedSize.GetHashCode(); + if (Timeout != 0F) hash ^= Timeout.GetHashCode(); + if (CapBalance != 0) hash ^= CapBalance.GetHashCode(); + if (IncomePerSecond != 0F) hash ^= IncomePerSecond.GetHashCode(); + if (ServiceHash != 0) hash ^= ServiceHash.GetHashCode(); + if (MethodId != 0) hash ^= MethodId.GetHashCode(); + return hash; + } + + public override string ToString() { + return pb::JsonFormatter.ToDiagnosticString(this); + } + + public void WriteTo(pb::CodedOutputStream output) { + if (ServiceName.Length != 0) { + output.WriteRawTag(10); + output.WriteString(ServiceName); + } + if (MethodName.Length != 0) { + output.WriteRawTag(18); + output.WriteString(MethodName); + } + if (FixedCallCost != 0) { + output.WriteRawTag(24); + output.WriteUInt32(FixedCallCost); + } + if (FixedPacketSize != 0) { + output.WriteRawTag(32); + output.WriteUInt32(FixedPacketSize); + } + if (VariableMultiplier != 0F) { + output.WriteRawTag(45); + output.WriteFloat(VariableMultiplier); + } + if (Multiplier != 0F) { + output.WriteRawTag(53); + output.WriteFloat(Multiplier); + } + if (RateLimitCount != 0) { + output.WriteRawTag(56); + output.WriteUInt32(RateLimitCount); + } + if (RateLimitSeconds != 0) { + output.WriteRawTag(64); + output.WriteUInt32(RateLimitSeconds); + } + if (MaxPacketSize != 0) { + output.WriteRawTag(72); + output.WriteUInt32(MaxPacketSize); + } + if (MaxEncodedSize != 0) { + output.WriteRawTag(80); + output.WriteUInt32(MaxEncodedSize); + } + if (Timeout != 0F) { + output.WriteRawTag(93); + output.WriteFloat(Timeout); + } + if (CapBalance != 0) { + output.WriteRawTag(96); + output.WriteUInt32(CapBalance); + } + if (IncomePerSecond != 0F) { + output.WriteRawTag(109); + output.WriteFloat(IncomePerSecond); + } + if (ServiceHash != 0) { + output.WriteRawTag(112); + output.WriteUInt32(ServiceHash); + } + if (MethodId != 0) { + output.WriteRawTag(120); + output.WriteUInt32(MethodId); + } + } + + public int CalculateSize() { + int size = 0; + if (ServiceName.Length != 0) { + size += 1 + pb::CodedOutputStream.ComputeStringSize(ServiceName); + } + if (MethodName.Length != 0) { + size += 1 + pb::CodedOutputStream.ComputeStringSize(MethodName); + } + if (FixedCallCost != 0) { + size += 1 + pb::CodedOutputStream.ComputeUInt32Size(FixedCallCost); + } + if (FixedPacketSize != 0) { + size += 1 + pb::CodedOutputStream.ComputeUInt32Size(FixedPacketSize); + } + if (VariableMultiplier != 0F) { + size += 1 + 4; + } + if (Multiplier != 0F) { + size += 1 + 4; + } + if (RateLimitCount != 0) { + size += 1 + pb::CodedOutputStream.ComputeUInt32Size(RateLimitCount); + } + if (RateLimitSeconds != 0) { + size += 1 + pb::CodedOutputStream.ComputeUInt32Size(RateLimitSeconds); + } + if (MaxPacketSize != 0) { + size += 1 + pb::CodedOutputStream.ComputeUInt32Size(MaxPacketSize); + } + if (MaxEncodedSize != 0) { + size += 1 + pb::CodedOutputStream.ComputeUInt32Size(MaxEncodedSize); + } + if (Timeout != 0F) { + size += 1 + 4; + } + if (CapBalance != 0) { + size += 1 + pb::CodedOutputStream.ComputeUInt32Size(CapBalance); + } + if (IncomePerSecond != 0F) { + size += 1 + 4; + } + if (ServiceHash != 0) { + size += 1 + pb::CodedOutputStream.ComputeUInt32Size(ServiceHash); + } + if (MethodId != 0) { + size += 1 + pb::CodedOutputStream.ComputeUInt32Size(MethodId); + } + return size; + } + + public void MergeFrom(RPCMethodConfig other) { + if (other == null) { + return; + } + if (other.ServiceName.Length != 0) { + ServiceName = other.ServiceName; + } + if (other.MethodName.Length != 0) { + MethodName = other.MethodName; + } + if (other.FixedCallCost != 0) { + FixedCallCost = other.FixedCallCost; + } + if (other.FixedPacketSize != 0) { + FixedPacketSize = other.FixedPacketSize; + } + if (other.VariableMultiplier != 0F) { + VariableMultiplier = other.VariableMultiplier; + } + if (other.Multiplier != 0F) { + Multiplier = other.Multiplier; + } + if (other.RateLimitCount != 0) { + RateLimitCount = other.RateLimitCount; + } + if (other.RateLimitSeconds != 0) { + RateLimitSeconds = other.RateLimitSeconds; + } + if (other.MaxPacketSize != 0) { + MaxPacketSize = other.MaxPacketSize; + } + if (other.MaxEncodedSize != 0) { + MaxEncodedSize = other.MaxEncodedSize; + } + if (other.Timeout != 0F) { + Timeout = other.Timeout; + } + if (other.CapBalance != 0) { + CapBalance = other.CapBalance; + } + if (other.IncomePerSecond != 0F) { + IncomePerSecond = other.IncomePerSecond; + } + if (other.ServiceHash != 0) { + ServiceHash = other.ServiceHash; + } + if (other.MethodId != 0) { + MethodId = other.MethodId; + } + } + + public void MergeFrom(pb::CodedInputStream input) { + uint tag; + while ((tag = input.ReadTag()) != 0) { + switch(tag) { + default: + input.SkipLastField(); + break; + case 10: { + ServiceName = input.ReadString(); + break; + } + case 18: { + MethodName = input.ReadString(); + break; + } + case 24: { + FixedCallCost = input.ReadUInt32(); + break; + } + case 32: { + FixedPacketSize = input.ReadUInt32(); + break; + } + case 45: { + VariableMultiplier = input.ReadFloat(); + break; + } + case 53: { + Multiplier = input.ReadFloat(); + break; + } + case 56: { + RateLimitCount = input.ReadUInt32(); + break; + } + case 64: { + RateLimitSeconds = input.ReadUInt32(); + break; + } + case 72: { + MaxPacketSize = input.ReadUInt32(); + break; + } + case 80: { + MaxEncodedSize = input.ReadUInt32(); + break; + } + case 93: { + Timeout = input.ReadFloat(); + break; + } + case 96: { + CapBalance = input.ReadUInt32(); + break; + } + case 109: { + IncomePerSecond = input.ReadFloat(); + break; + } + case 112: { + ServiceHash = input.ReadUInt32(); + break; + } + case 120: { + MethodId = input.ReadUInt32(); + break; + } + } + } + } + + } + + [System.Diagnostics.DebuggerNonUserCodeAttribute()] + public sealed partial class RPCMeterConfig : pb::IMessage { + private static readonly pb::MessageParser _parser = new pb::MessageParser(() => new RPCMeterConfig()); + public static pb::MessageParser Parser { get { return _parser; } } + + public static pbr::MessageDescriptor Descriptor { + get { return Bgs.Protocol.Config.RpcConfigReflection.Descriptor.MessageTypes[1]; } + } + + pbr::MessageDescriptor pb::IMessage.Descriptor { + get { return Descriptor; } + } + + public RPCMeterConfig() { + OnConstruction(); + } + + partial void OnConstruction(); + + public RPCMeterConfig(RPCMeterConfig other) : this() { + method_ = other.method_.Clone(); + incomePerSecond_ = other.incomePerSecond_; + initialBalance_ = other.initialBalance_; + capBalance_ = other.capBalance_; + startupPeriod_ = other.startupPeriod_; + } + + public RPCMeterConfig Clone() { + return new RPCMeterConfig(this); + } + + /// Field number for the "method" field. + public const int MethodFieldNumber = 1; + private static readonly pb::FieldCodec _repeated_method_codec + = pb::FieldCodec.ForMessage(10, Bgs.Protocol.Config.RPCMethodConfig.Parser); + private readonly pbc::RepeatedField method_ = new pbc::RepeatedField(); + public pbc::RepeatedField Method { + get { return method_; } + } + + /// Field number for the "income_per_second" field. + public const int IncomePerSecondFieldNumber = 2; + private uint incomePerSecond_; + public uint IncomePerSecond { + get { return incomePerSecond_; } + set { + incomePerSecond_ = value; + } + } + + /// Field number for the "initial_balance" field. + public const int InitialBalanceFieldNumber = 3; + private uint initialBalance_; + public uint InitialBalance { + get { return initialBalance_; } + set { + initialBalance_ = value; + } + } + + /// Field number for the "cap_balance" field. + public const int CapBalanceFieldNumber = 4; + private uint capBalance_; + public uint CapBalance { + get { return capBalance_; } + set { + capBalance_ = value; + } + } + + /// Field number for the "startup_period" field. + public const int StartupPeriodFieldNumber = 5; + private float startupPeriod_; + public float StartupPeriod { + get { return startupPeriod_; } + set { + startupPeriod_ = value; + } + } + + public override bool Equals(object other) { + return Equals(other as RPCMeterConfig); + } + + public bool Equals(RPCMeterConfig other) { + if (ReferenceEquals(other, null)) { + return false; + } + if (ReferenceEquals(other, this)) { + return true; + } + if(!method_.Equals(other.method_)) return false; + if (IncomePerSecond != other.IncomePerSecond) return false; + if (InitialBalance != other.InitialBalance) return false; + if (CapBalance != other.CapBalance) return false; + if (StartupPeriod != other.StartupPeriod) return false; + return true; + } + + public override int GetHashCode() { + int hash = 1; + hash ^= method_.GetHashCode(); + if (IncomePerSecond != 0) hash ^= IncomePerSecond.GetHashCode(); + if (InitialBalance != 0) hash ^= InitialBalance.GetHashCode(); + if (CapBalance != 0) hash ^= CapBalance.GetHashCode(); + if (StartupPeriod != 0F) hash ^= StartupPeriod.GetHashCode(); + return hash; + } + + public override string ToString() { + return pb::JsonFormatter.ToDiagnosticString(this); + } + + public void WriteTo(pb::CodedOutputStream output) { + method_.WriteTo(output, _repeated_method_codec); + if (IncomePerSecond != 0) { + output.WriteRawTag(16); + output.WriteUInt32(IncomePerSecond); + } + if (InitialBalance != 0) { + output.WriteRawTag(24); + output.WriteUInt32(InitialBalance); + } + if (CapBalance != 0) { + output.WriteRawTag(32); + output.WriteUInt32(CapBalance); + } + if (StartupPeriod != 0F) { + output.WriteRawTag(45); + output.WriteFloat(StartupPeriod); + } + } + + public int CalculateSize() { + int size = 0; + size += method_.CalculateSize(_repeated_method_codec); + if (IncomePerSecond != 0) { + size += 1 + pb::CodedOutputStream.ComputeUInt32Size(IncomePerSecond); + } + if (InitialBalance != 0) { + size += 1 + pb::CodedOutputStream.ComputeUInt32Size(InitialBalance); + } + if (CapBalance != 0) { + size += 1 + pb::CodedOutputStream.ComputeUInt32Size(CapBalance); + } + if (StartupPeriod != 0F) { + size += 1 + 4; + } + return size; + } + + public void MergeFrom(RPCMeterConfig other) { + if (other == null) { + return; + } + method_.Add(other.method_); + if (other.IncomePerSecond != 0) { + IncomePerSecond = other.IncomePerSecond; + } + if (other.InitialBalance != 0) { + InitialBalance = other.InitialBalance; + } + if (other.CapBalance != 0) { + CapBalance = other.CapBalance; + } + if (other.StartupPeriod != 0F) { + StartupPeriod = other.StartupPeriod; + } + } + + public void MergeFrom(pb::CodedInputStream input) { + uint tag; + while ((tag = input.ReadTag()) != 0) { + switch(tag) { + default: + input.SkipLastField(); + break; + case 10: { + method_.AddEntriesFrom(input, _repeated_method_codec); + break; + } + case 16: { + IncomePerSecond = input.ReadUInt32(); + break; + } + case 24: { + InitialBalance = input.ReadUInt32(); + break; + } + case 32: { + CapBalance = input.ReadUInt32(); + break; + } + case 45: { + StartupPeriod = input.ReadFloat(); + break; + } + } + } + } + + } + + [System.Diagnostics.DebuggerNonUserCodeAttribute()] + public sealed partial class ProtocolAlias : pb::IMessage { + private static readonly pb::MessageParser _parser = new pb::MessageParser(() => new ProtocolAlias()); + public static pb::MessageParser Parser { get { return _parser; } } + + public static pbr::MessageDescriptor Descriptor { + get { return Bgs.Protocol.Config.RpcConfigReflection.Descriptor.MessageTypes[2]; } + } + + pbr::MessageDescriptor pb::IMessage.Descriptor { + get { return Descriptor; } + } + + public ProtocolAlias() { + OnConstruction(); + } + + partial void OnConstruction(); + + public ProtocolAlias(ProtocolAlias other) : this() { + serverServiceName_ = other.serverServiceName_; + clientServiceName_ = other.clientServiceName_; + } + + public ProtocolAlias Clone() { + return new ProtocolAlias(this); + } + + /// Field number for the "server_service_name" field. + public const int ServerServiceNameFieldNumber = 1; + private string serverServiceName_ = ""; + public string ServerServiceName { + get { return serverServiceName_; } + set { + serverServiceName_ = pb::ProtoPreconditions.CheckNotNull(value, "value"); + } + } + + /// Field number for the "client_service_name" field. + public const int ClientServiceNameFieldNumber = 2; + private string clientServiceName_ = ""; + public string ClientServiceName { + get { return clientServiceName_; } + set { + clientServiceName_ = pb::ProtoPreconditions.CheckNotNull(value, "value"); + } + } + + public override bool Equals(object other) { + return Equals(other as ProtocolAlias); + } + + public bool Equals(ProtocolAlias other) { + if (ReferenceEquals(other, null)) { + return false; + } + if (ReferenceEquals(other, this)) { + return true; + } + if (ServerServiceName != other.ServerServiceName) return false; + if (ClientServiceName != other.ClientServiceName) return false; + return true; + } + + public override int GetHashCode() { + int hash = 1; + if (ServerServiceName.Length != 0) hash ^= ServerServiceName.GetHashCode(); + if (ClientServiceName.Length != 0) hash ^= ClientServiceName.GetHashCode(); + return hash; + } + + public override string ToString() { + return pb::JsonFormatter.ToDiagnosticString(this); + } + + public void WriteTo(pb::CodedOutputStream output) { + if (ServerServiceName.Length != 0) { + output.WriteRawTag(10); + output.WriteString(ServerServiceName); + } + if (ClientServiceName.Length != 0) { + output.WriteRawTag(18); + output.WriteString(ClientServiceName); + } + } + + public int CalculateSize() { + int size = 0; + if (ServerServiceName.Length != 0) { + size += 1 + pb::CodedOutputStream.ComputeStringSize(ServerServiceName); + } + if (ClientServiceName.Length != 0) { + size += 1 + pb::CodedOutputStream.ComputeStringSize(ClientServiceName); + } + return size; + } + + public void MergeFrom(ProtocolAlias other) { + if (other == null) { + return; + } + if (other.ServerServiceName.Length != 0) { + ServerServiceName = other.ServerServiceName; + } + if (other.ClientServiceName.Length != 0) { + ClientServiceName = other.ClientServiceName; + } + } + + public void MergeFrom(pb::CodedInputStream input) { + uint tag; + while ((tag = input.ReadTag()) != 0) { + switch(tag) { + default: + input.SkipLastField(); + break; + case 10: { + ServerServiceName = input.ReadString(); + break; + } + case 18: { + ClientServiceName = input.ReadString(); + break; + } + } + } + } + + } + + [System.Diagnostics.DebuggerNonUserCodeAttribute()] + public sealed partial class ServiceAliases : pb::IMessage { + private static readonly pb::MessageParser _parser = new pb::MessageParser(() => new ServiceAliases()); + public static pb::MessageParser Parser { get { return _parser; } } + + public static pbr::MessageDescriptor Descriptor { + get { return Bgs.Protocol.Config.RpcConfigReflection.Descriptor.MessageTypes[3]; } + } + + pbr::MessageDescriptor pb::IMessage.Descriptor { + get { return Descriptor; } + } + + public ServiceAliases() { + OnConstruction(); + } + + partial void OnConstruction(); + + public ServiceAliases(ServiceAliases other) : this() { + protocolAlias_ = other.protocolAlias_.Clone(); + } + + public ServiceAliases Clone() { + return new ServiceAliases(this); + } + + /// Field number for the "protocol_alias" field. + public const int ProtocolAliasFieldNumber = 1; + private static readonly pb::FieldCodec _repeated_protocolAlias_codec + = pb::FieldCodec.ForMessage(10, Bgs.Protocol.Config.ProtocolAlias.Parser); + private readonly pbc::RepeatedField protocolAlias_ = new pbc::RepeatedField(); + public pbc::RepeatedField ProtocolAlias { + get { return protocolAlias_; } + } + + public override bool Equals(object other) { + return Equals(other as ServiceAliases); + } + + public bool Equals(ServiceAliases other) { + if (ReferenceEquals(other, null)) { + return false; + } + if (ReferenceEquals(other, this)) { + return true; + } + if(!protocolAlias_.Equals(other.protocolAlias_)) return false; + return true; + } + + public override int GetHashCode() { + int hash = 1; + hash ^= protocolAlias_.GetHashCode(); + return hash; + } + + public override string ToString() { + return pb::JsonFormatter.ToDiagnosticString(this); + } + + public void WriteTo(pb::CodedOutputStream output) { + protocolAlias_.WriteTo(output, _repeated_protocolAlias_codec); + } + + public int CalculateSize() { + int size = 0; + size += protocolAlias_.CalculateSize(_repeated_protocolAlias_codec); + return size; + } + + public void MergeFrom(ServiceAliases other) { + if (other == null) { + return; + } + protocolAlias_.Add(other.protocolAlias_); + } + + public void MergeFrom(pb::CodedInputStream input) { + uint tag; + while ((tag = input.ReadTag()) != 0) { + switch(tag) { + default: + input.SkipLastField(); + break; + case 10: { + protocolAlias_.AddEntriesFrom(input, _repeated_protocolAlias_codec); + break; + } + } + } + } + + } + + #endregion + +} + +#endregion Designer generated code diff --git a/Framework/Proto/RpcTypes.cs b/Framework/Proto/RpcTypes.cs new file mode 100644 index 000000000..7d3331b84 --- /dev/null +++ b/Framework/Proto/RpcTypes.cs @@ -0,0 +1,1428 @@ +// Generated by the protocol buffer compiler. DO NOT EDIT! +// source: bgs/low/pb/client/rpc_types.proto +#pragma warning disable 1591, 0612, 3021 +#region Designer generated code + +using pb = Google.Protobuf; +using pbc = Google.Protobuf.Collections; +using pbr = Google.Protobuf.Reflection; +namespace Bgs.Protocol +{ + + /// Holder for reflection information generated from bgs/low/pb/client/rpc_types.proto + [System.Diagnostics.DebuggerNonUserCodeAttribute()] + public static partial class RpcTypesReflection + { + + #region Descriptor + /// File descriptor for bgs/low/pb/client/rpc_types.proto + public static pbr::FileDescriptor Descriptor + { + get { return descriptor; } + } + private static pbr::FileDescriptor descriptor; + + static RpcTypesReflection() + { + byte[] descriptorData = System.Convert.FromBase64String( + string.Concat( + "CiFiZ3MvbG93L3BiL2NsaWVudC9ycGNfdHlwZXMucHJvdG8SDGJncy5wcm90", + "b2NvbBo4YmdzL2xvdy9wYi9jbGllbnQvZ2xvYmFsX2V4dGVuc2lvbnMvbWV0", + "aG9kX29wdGlvbnMucHJvdG8aOWJncy9sb3cvcGIvY2xpZW50L2dsb2JhbF9l", + "eHRlbnNpb25zL3NlcnZpY2Vfb3B0aW9ucy5wcm90bxo3YmdzL2xvdy9wYi9j", + "bGllbnQvZ2xvYmFsX2V4dGVuc2lvbnMvZmllbGRfb3B0aW9ucy5wcm90byIN", + "CgtOT19SRVNQT05TRSIoCgdBZGRyZXNzEg8KB2FkZHJlc3MYASABKAkSDAoE", + "cG9ydBgCIAEoDSIpCglQcm9jZXNzSWQSDQoFbGFiZWwYASABKA0SDQoFZXBv", + "Y2gYAiABKA0iSQoNT2JqZWN0QWRkcmVzcxIlCgRob3N0GAEgASgLMhcuYmdz", + "LnByb3RvY29sLlByb2Nlc3NJZBIRCglvYmplY3RfaWQYAiABKAQiCAoGTm9E", + "YXRhInkKCUVycm9ySW5mbxIzCg5vYmplY3RfYWRkcmVzcxgBIAEoCzIbLmJn", + "cy5wcm90b2NvbC5PYmplY3RBZGRyZXNzEg4KBnN0YXR1cxgCIAEoDRIUCgxz", + "ZXJ2aWNlX2hhc2gYAyABKA0SEQoJbWV0aG9kX2lkGAQgASgNIoUCCgZIZWFk", + "ZXISEgoKc2VydmljZV9pZBgBIAEoDRIRCgltZXRob2RfaWQYAiABKA0SDQoF", + "dG9rZW4YAyABKA0SEQoJb2JqZWN0X2lkGAQgASgEEgwKBHNpemUYBSABKA0S", + "DgoGc3RhdHVzGAYgASgNEiYKBWVycm9yGAcgAygLMhcuYmdzLnByb3RvY29s", + "LkVycm9ySW5mbxIPCgd0aW1lb3V0GAggASgEEhMKC2lzX3Jlc3BvbnNlGAkg", + "ASgIEjAKD2ZvcndhcmRfdGFyZ2V0cxgKIAMoCzIXLmJncy5wcm90b2NvbC5Q", + "cm9jZXNzSWQSFAoMc2VydmljZV9oYXNoGAsgASgHQhsKDWJuZXQucHJvdG9j", + "b2xCCFJwY1Byb3RvSAJQAFABUAJiBnByb3RvMw==")); + descriptor = pbr::FileDescriptor.FromGeneratedCode(descriptorData, + new pbr::FileDescriptor[] { Bgs.Protocol.MethodOptionsReflection.Descriptor, Bgs.Protocol.ServiceOptionsReflection.Descriptor, Bgs.Protocol.FieldOptionsReflection.Descriptor, }, + new pbr::GeneratedClrTypeInfo(null, new pbr::GeneratedClrTypeInfo[] { + new pbr::GeneratedClrTypeInfo(typeof(Bgs.Protocol.NO_RESPONSE), Bgs.Protocol.NO_RESPONSE.Parser, null, null, null, null), + new pbr::GeneratedClrTypeInfo(typeof(Bgs.Protocol.Address), Bgs.Protocol.Address.Parser, new[]{ "Address_", "Port" }, null, null, null), + new pbr::GeneratedClrTypeInfo(typeof(Bgs.Protocol.ProcessId), Bgs.Protocol.ProcessId.Parser, new[]{ "Label", "Epoch" }, null, null, null), + new pbr::GeneratedClrTypeInfo(typeof(Bgs.Protocol.ObjectAddress), Bgs.Protocol.ObjectAddress.Parser, new[]{ "Host", "ObjectId" }, null, null, null), + new pbr::GeneratedClrTypeInfo(typeof(Bgs.Protocol.NoData), Bgs.Protocol.NoData.Parser, null, null, null, null), + new pbr::GeneratedClrTypeInfo(typeof(Bgs.Protocol.ErrorInfo), Bgs.Protocol.ErrorInfo.Parser, new[]{ "ObjectAddress", "Status", "ServiceHash", "MethodId" }, null, null, null), + new pbr::GeneratedClrTypeInfo(typeof(Bgs.Protocol.Header), Bgs.Protocol.Header.Parser, new[]{ "ServiceId", "MethodId", "Token", "ObjectId", "Size", "Status", "Error", "Timeout", "IsResponse", "ForwardTargets", "ServiceHash" }, null, null, null) + })); + } + #endregion + + } + #region Messages + [System.Diagnostics.DebuggerNonUserCodeAttribute()] + public sealed partial class NO_RESPONSE : pb::IMessage + { + private static readonly pb::MessageParser _parser = new pb::MessageParser(() => new NO_RESPONSE()); + public static pb::MessageParser Parser { get { return _parser; } } + + public static pbr::MessageDescriptor Descriptor + { + get { return Bgs.Protocol.RpcTypesReflection.Descriptor.MessageTypes[0]; } + } + + pbr::MessageDescriptor pb::IMessage.Descriptor + { + get { return Descriptor; } + } + + public NO_RESPONSE() + { + OnConstruction(); + } + + partial void OnConstruction(); + + public NO_RESPONSE(NO_RESPONSE other) : this() + { + } + + public NO_RESPONSE Clone() + { + return new NO_RESPONSE(this); + } + + public override bool Equals(object other) + { + return Equals(other as NO_RESPONSE); + } + + public bool Equals(NO_RESPONSE other) + { + if (ReferenceEquals(other, null)) + { + return false; + } + if (ReferenceEquals(other, this)) + { + return true; + } + return true; + } + + public override int GetHashCode() + { + int hash = 1; + return hash; + } + + public override string ToString() + { + return pb::JsonFormatter.ToDiagnosticString(this); + } + + public void WriteTo(pb::CodedOutputStream output) + { + } + + public int CalculateSize() + { + int size = 0; + return size; + } + + public void MergeFrom(NO_RESPONSE other) + { + if (other == null) + { + return; + } + } + + public void MergeFrom(pb::CodedInputStream input) + { + uint tag; + while ((tag = input.ReadTag()) != 0) + { + switch (tag) + { + default: + input.SkipLastField(); + break; + } + } + } + + } + + [System.Diagnostics.DebuggerNonUserCodeAttribute()] + public sealed partial class Address : pb::IMessage
+ { + private static readonly pb::MessageParser
_parser = new pb::MessageParser
(() => new Address()); + public static pb::MessageParser
Parser { get { return _parser; } } + + public static pbr::MessageDescriptor Descriptor + { + get { return Bgs.Protocol.RpcTypesReflection.Descriptor.MessageTypes[1]; } + } + + pbr::MessageDescriptor pb::IMessage.Descriptor + { + get { return Descriptor; } + } + + public Address() + { + OnConstruction(); + } + + partial void OnConstruction(); + + public Address(Address other) : this() + { + address_ = other.address_; + port_ = other.port_; + } + + public Address Clone() + { + return new Address(this); + } + + /// Field number for the "address" field. + public const int Address_FieldNumber = 1; + private string address_ = ""; + public string Address_ + { + get { return address_; } + set + { + address_ = pb::ProtoPreconditions.CheckNotNull(value, "value"); + } + } + + /// Field number for the "port" field. + public const int PortFieldNumber = 2; + private uint port_; + public uint Port + { + get { return port_; } + set + { + port_ = value; + } + } + + public override bool Equals(object other) + { + return Equals(other as Address); + } + + public bool Equals(Address other) + { + if (ReferenceEquals(other, null)) + { + return false; + } + if (ReferenceEquals(other, this)) + { + return true; + } + if (Address_ != other.Address_) return false; + if (Port != other.Port) return false; + return true; + } + + public override int GetHashCode() + { + int hash = 1; + if (Address_.Length != 0) hash ^= Address_.GetHashCode(); + if (Port != 0) hash ^= Port.GetHashCode(); + return hash; + } + + public override string ToString() + { + return pb::JsonFormatter.ToDiagnosticString(this); + } + + public void WriteTo(pb::CodedOutputStream output) + { + if (Address_.Length != 0) + { + output.WriteRawTag(10); + output.WriteString(Address_); + } + if (Port != 0) + { + output.WriteRawTag(16); + output.WriteUInt32(Port); + } + } + + public int CalculateSize() + { + int size = 0; + if (Address_.Length != 0) + { + size += 1 + pb::CodedOutputStream.ComputeStringSize(Address_); + } + if (Port != 0) + { + size += 1 + pb::CodedOutputStream.ComputeUInt32Size(Port); + } + return size; + } + + public void MergeFrom(Address other) + { + if (other == null) + { + return; + } + if (other.Address_.Length != 0) + { + Address_ = other.Address_; + } + if (other.Port != 0) + { + Port = other.Port; + } + } + + public void MergeFrom(pb::CodedInputStream input) + { + uint tag; + while ((tag = input.ReadTag()) != 0) + { + switch (tag) + { + default: + input.SkipLastField(); + break; + case 10: + { + Address_ = input.ReadString(); + break; + } + case 16: + { + Port = input.ReadUInt32(); + break; + } + } + } + } + + } + + [System.Diagnostics.DebuggerNonUserCodeAttribute()] + public sealed partial class ProcessId : pb::IMessage + { + private static readonly pb::MessageParser _parser = new pb::MessageParser(() => new ProcessId()); + public static pb::MessageParser Parser { get { return _parser; } } + + public static pbr::MessageDescriptor Descriptor + { + get { return Bgs.Protocol.RpcTypesReflection.Descriptor.MessageTypes[2]; } + } + + pbr::MessageDescriptor pb::IMessage.Descriptor + { + get { return Descriptor; } + } + + public ProcessId() + { + OnConstruction(); + } + + partial void OnConstruction(); + + public ProcessId(ProcessId other) : this() + { + label_ = other.label_; + epoch_ = other.epoch_; + } + + public ProcessId Clone() + { + return new ProcessId(this); + } + + /// Field number for the "label" field. + public const int LabelFieldNumber = 1; + private uint label_; + public uint Label + { + get { return label_; } + set + { + label_ = value; + } + } + + /// Field number for the "epoch" field. + public const int EpochFieldNumber = 2; + private uint epoch_; + public uint Epoch + { + get { return epoch_; } + set + { + epoch_ = value; + } + } + + public override bool Equals(object other) + { + return Equals(other as ProcessId); + } + + public bool Equals(ProcessId other) + { + if (ReferenceEquals(other, null)) + { + return false; + } + if (ReferenceEquals(other, this)) + { + return true; + } + if (Label != other.Label) return false; + if (Epoch != other.Epoch) return false; + return true; + } + + public override int GetHashCode() + { + int hash = 1; + if (Label != 0) hash ^= Label.GetHashCode(); + if (Epoch != 0) hash ^= Epoch.GetHashCode(); + return hash; + } + + public override string ToString() + { + return pb::JsonFormatter.ToDiagnosticString(this); + } + + public void WriteTo(pb::CodedOutputStream output) + { + if (Label != 0) + { + output.WriteRawTag(8); + output.WriteUInt32(Label); + } + if (Epoch != 0) + { + output.WriteRawTag(16); + output.WriteUInt32(Epoch); + } + } + + public int CalculateSize() + { + int size = 0; + if (Label != 0) + { + size += 1 + pb::CodedOutputStream.ComputeUInt32Size(Label); + } + if (Epoch != 0) + { + size += 1 + pb::CodedOutputStream.ComputeUInt32Size(Epoch); + } + return size; + } + + public void MergeFrom(ProcessId other) + { + if (other == null) + { + return; + } + if (other.Label != 0) + { + Label = other.Label; + } + if (other.Epoch != 0) + { + Epoch = other.Epoch; + } + } + + public void MergeFrom(pb::CodedInputStream input) + { + uint tag; + while ((tag = input.ReadTag()) != 0) + { + switch (tag) + { + default: + input.SkipLastField(); + break; + case 8: + { + Label = input.ReadUInt32(); + break; + } + case 16: + { + Epoch = input.ReadUInt32(); + break; + } + } + } + } + + } + + [System.Diagnostics.DebuggerNonUserCodeAttribute()] + public sealed partial class ObjectAddress : pb::IMessage + { + private static readonly pb::MessageParser _parser = new pb::MessageParser(() => new ObjectAddress()); + public static pb::MessageParser Parser { get { return _parser; } } + + public static pbr::MessageDescriptor Descriptor + { + get { return Bgs.Protocol.RpcTypesReflection.Descriptor.MessageTypes[3]; } + } + + pbr::MessageDescriptor pb::IMessage.Descriptor + { + get { return Descriptor; } + } + + public ObjectAddress() + { + OnConstruction(); + } + + partial void OnConstruction(); + + public ObjectAddress(ObjectAddress other) : this() + { + Host = other.host_ != null ? other.Host.Clone() : null; + objectId_ = other.objectId_; + } + + public ObjectAddress Clone() + { + return new ObjectAddress(this); + } + + /// Field number for the "host" field. + public const int HostFieldNumber = 1; + private Bgs.Protocol.ProcessId host_; + public Bgs.Protocol.ProcessId Host + { + get { return host_; } + set + { + host_ = value; + } + } + + /// Field number for the "object_id" field. + public const int ObjectIdFieldNumber = 2; + private ulong objectId_; + public ulong ObjectId + { + get { return objectId_; } + set + { + objectId_ = value; + } + } + + public override bool Equals(object other) + { + return Equals(other as ObjectAddress); + } + + public bool Equals(ObjectAddress other) + { + if (ReferenceEquals(other, null)) + { + return false; + } + if (ReferenceEquals(other, this)) + { + return true; + } + if (!object.Equals(Host, other.Host)) return false; + if (ObjectId != other.ObjectId) return false; + return true; + } + + public override int GetHashCode() + { + int hash = 1; + if (host_ != null) hash ^= Host.GetHashCode(); + if (ObjectId != 0UL) hash ^= ObjectId.GetHashCode(); + return hash; + } + + public override string ToString() + { + return pb::JsonFormatter.ToDiagnosticString(this); + } + + public void WriteTo(pb::CodedOutputStream output) + { + if (host_ != null) + { + output.WriteRawTag(10); + output.WriteMessage(Host); + } + if (ObjectId != 0UL) + { + output.WriteRawTag(16); + output.WriteUInt64(ObjectId); + } + } + + public int CalculateSize() + { + int size = 0; + if (host_ != null) + { + size += 1 + pb::CodedOutputStream.ComputeMessageSize(Host); + } + if (ObjectId != 0UL) + { + size += 1 + pb::CodedOutputStream.ComputeUInt64Size(ObjectId); + } + return size; + } + + public void MergeFrom(ObjectAddress other) + { + if (other == null) + { + return; + } + if (other.host_ != null) + { + if (host_ == null) + { + host_ = new Bgs.Protocol.ProcessId(); + } + Host.MergeFrom(other.Host); + } + if (other.ObjectId != 0UL) + { + ObjectId = other.ObjectId; + } + } + + public void MergeFrom(pb::CodedInputStream input) + { + uint tag; + while ((tag = input.ReadTag()) != 0) + { + switch (tag) + { + default: + input.SkipLastField(); + break; + case 10: + { + if (host_ == null) + { + host_ = new Bgs.Protocol.ProcessId(); + } + input.ReadMessage(host_); + break; + } + case 16: + { + ObjectId = input.ReadUInt64(); + break; + } + } + } + } + + } + + [System.Diagnostics.DebuggerNonUserCodeAttribute()] + public sealed partial class NoData : pb::IMessage + { + private static readonly pb::MessageParser _parser = new pb::MessageParser(() => new NoData()); + public static pb::MessageParser Parser { get { return _parser; } } + + public static pbr::MessageDescriptor Descriptor + { + get { return Bgs.Protocol.RpcTypesReflection.Descriptor.MessageTypes[4]; } + } + + pbr::MessageDescriptor pb::IMessage.Descriptor + { + get { return Descriptor; } + } + + public NoData() + { + OnConstruction(); + } + + partial void OnConstruction(); + + public NoData(NoData other) : this() + { + } + + public NoData Clone() + { + return new NoData(this); + } + + public override bool Equals(object other) + { + return Equals(other as NoData); + } + + public bool Equals(NoData other) + { + if (ReferenceEquals(other, null)) + { + return false; + } + if (ReferenceEquals(other, this)) + { + return true; + } + return true; + } + + public override int GetHashCode() + { + int hash = 1; + return hash; + } + + public override string ToString() + { + return pb::JsonFormatter.ToDiagnosticString(this); + } + + public void WriteTo(pb::CodedOutputStream output) + { + } + + public int CalculateSize() + { + int size = 0; + return size; + } + + public void MergeFrom(NoData other) + { + if (other == null) + { + return; + } + } + + public void MergeFrom(pb::CodedInputStream input) + { + uint tag; + while ((tag = input.ReadTag()) != 0) + { + switch (tag) + { + default: + input.SkipLastField(); + break; + } + } + } + + } + + [System.Diagnostics.DebuggerNonUserCodeAttribute()] + public sealed partial class ErrorInfo : pb::IMessage + { + private static readonly pb::MessageParser _parser = new pb::MessageParser(() => new ErrorInfo()); + public static pb::MessageParser Parser { get { return _parser; } } + + public static pbr::MessageDescriptor Descriptor + { + get { return Bgs.Protocol.RpcTypesReflection.Descriptor.MessageTypes[5]; } + } + + pbr::MessageDescriptor pb::IMessage.Descriptor + { + get { return Descriptor; } + } + + public ErrorInfo() + { + OnConstruction(); + } + + partial void OnConstruction(); + + public ErrorInfo(ErrorInfo other) : this() + { + ObjectAddress = other.objectAddress_ != null ? other.ObjectAddress.Clone() : null; + status_ = other.status_; + serviceHash_ = other.serviceHash_; + methodId_ = other.methodId_; + } + + public ErrorInfo Clone() + { + return new ErrorInfo(this); + } + + /// Field number for the "object_address" field. + public const int ObjectAddressFieldNumber = 1; + private Bgs.Protocol.ObjectAddress objectAddress_; + public Bgs.Protocol.ObjectAddress ObjectAddress + { + get { return objectAddress_; } + set + { + objectAddress_ = value; + } + } + + /// Field number for the "status" field. + public const int StatusFieldNumber = 2; + private uint status_; + public uint Status + { + get { return status_; } + set + { + status_ = value; + } + } + + /// Field number for the "service_hash" field. + public const int ServiceHashFieldNumber = 3; + private uint serviceHash_; + public uint ServiceHash + { + get { return serviceHash_; } + set + { + serviceHash_ = value; + } + } + + /// Field number for the "method_id" field. + public const int MethodIdFieldNumber = 4; + private uint methodId_; + public uint MethodId + { + get { return methodId_; } + set + { + methodId_ = value; + } + } + + public override bool Equals(object other) + { + return Equals(other as ErrorInfo); + } + + public bool Equals(ErrorInfo other) + { + if (ReferenceEquals(other, null)) + { + return false; + } + if (ReferenceEquals(other, this)) + { + return true; + } + if (!object.Equals(ObjectAddress, other.ObjectAddress)) return false; + if (Status != other.Status) return false; + if (ServiceHash != other.ServiceHash) return false; + if (MethodId != other.MethodId) return false; + return true; + } + + public override int GetHashCode() + { + int hash = 1; + if (objectAddress_ != null) hash ^= ObjectAddress.GetHashCode(); + if (Status != 0) hash ^= Status.GetHashCode(); + if (ServiceHash != 0) hash ^= ServiceHash.GetHashCode(); + if (MethodId != 0) hash ^= MethodId.GetHashCode(); + return hash; + } + + public override string ToString() + { + return pb::JsonFormatter.ToDiagnosticString(this); + } + + public void WriteTo(pb::CodedOutputStream output) + { + if (objectAddress_ != null) + { + output.WriteRawTag(10); + output.WriteMessage(ObjectAddress); + } + if (Status != 0) + { + output.WriteRawTag(16); + output.WriteUInt32(Status); + } + if (ServiceHash != 0) + { + output.WriteRawTag(24); + output.WriteUInt32(ServiceHash); + } + if (MethodId != 0) + { + output.WriteRawTag(32); + output.WriteUInt32(MethodId); + } + } + + public int CalculateSize() + { + int size = 0; + if (objectAddress_ != null) + { + size += 1 + pb::CodedOutputStream.ComputeMessageSize(ObjectAddress); + } + if (Status != 0) + { + size += 1 + pb::CodedOutputStream.ComputeUInt32Size(Status); + } + if (ServiceHash != 0) + { + size += 1 + pb::CodedOutputStream.ComputeUInt32Size(ServiceHash); + } + if (MethodId != 0) + { + size += 1 + pb::CodedOutputStream.ComputeUInt32Size(MethodId); + } + return size; + } + + public void MergeFrom(ErrorInfo other) + { + if (other == null) + { + return; + } + if (other.objectAddress_ != null) + { + if (objectAddress_ == null) + { + objectAddress_ = new Bgs.Protocol.ObjectAddress(); + } + ObjectAddress.MergeFrom(other.ObjectAddress); + } + if (other.Status != 0) + { + Status = other.Status; + } + if (other.ServiceHash != 0) + { + ServiceHash = other.ServiceHash; + } + if (other.MethodId != 0) + { + MethodId = other.MethodId; + } + } + + public void MergeFrom(pb::CodedInputStream input) + { + uint tag; + while ((tag = input.ReadTag()) != 0) + { + switch (tag) + { + default: + input.SkipLastField(); + break; + case 10: + { + if (objectAddress_ == null) + { + objectAddress_ = new Bgs.Protocol.ObjectAddress(); + } + input.ReadMessage(objectAddress_); + break; + } + case 16: + { + Status = input.ReadUInt32(); + break; + } + case 24: + { + ServiceHash = input.ReadUInt32(); + break; + } + case 32: + { + MethodId = input.ReadUInt32(); + break; + } + } + } + } + + } + + [System.Diagnostics.DebuggerNonUserCodeAttribute()] + public sealed partial class Header : pb::IMessage
+ { + private static readonly pb::MessageParser
_parser = new pb::MessageParser
(() => new Header()); + public static pb::MessageParser
Parser { get { return _parser; } } + + public static pbr::MessageDescriptor Descriptor + { + get { return Bgs.Protocol.RpcTypesReflection.Descriptor.MessageTypes[6]; } + } + + pbr::MessageDescriptor pb::IMessage.Descriptor + { + get { return Descriptor; } + } + + public Header() + { + OnConstruction(); + } + + partial void OnConstruction(); + + public Header(Header other) : this() + { + serviceId_ = other.serviceId_; + methodId_ = other.methodId_; + token_ = other.token_; + objectId_ = other.objectId_; + size_ = other.size_; + status_ = other.status_; + error_ = other.error_.Clone(); + timeout_ = other.timeout_; + isResponse_ = other.isResponse_; + forwardTargets_ = other.forwardTargets_.Clone(); + serviceHash_ = other.serviceHash_; + } + + public Header Clone() + { + return new Header(this); + } + + /// Field number for the "service_id" field. + public const int ServiceIdFieldNumber = 0; + private uint serviceId_; + public uint ServiceId + { + get { return serviceId_; } + set + { + bitArray.Set(ServiceIdFieldNumber, true); + serviceId_ = value; + } + } + + /// Field number for the "method_id" field. + public const int MethodIdFieldNumber = 1; + private uint methodId_; + public uint MethodId + { + get { return methodId_; } + set + { + bitArray.Set(MethodIdFieldNumber, true); + methodId_ = value; + } + } + + /// Field number for the "token" field. + public const int TokenFieldNumber = 2; + private uint token_; + public uint Token + { + get { return token_; } + set + { + bitArray.Set(TokenFieldNumber, true); + token_ = value; + } + } + + /// Field number for the "object_id" field. + public const int ObjectIdFieldNumber = 3; + private ulong objectId_; + public ulong ObjectId + { + get { return objectId_; } + set + { + bitArray.Set(ObjectIdFieldNumber, true); + objectId_ = value; + } + } + + /// Field number for the "size" field. + public const int SizeFieldNumber = 4; + private uint size_; + public uint Size + { + get { return size_; } + set + { + bitArray.Set(SizeFieldNumber, true); + size_ = value; + } + } + + /// Field number for the "status" field. + public const int StatusFieldNumber = 5; + private uint status_; + public uint Status + { + get { return status_; } + set + { + bitArray.Set(StatusFieldNumber, true); + status_ = value; + } + } + + /// Field number for the "error" field. + public const int ErrorFieldNumber = 6; + private static readonly pb::FieldCodec _repeated_error_codec + = pb::FieldCodec.ForMessage(58, Bgs.Protocol.ErrorInfo.Parser); + private readonly pbc::RepeatedField error_ = new pbc::RepeatedField(); + public pbc::RepeatedField Error + { + get { return error_; } + } + + /// Field number for the "timeout" field. + public const int TimeoutFieldNumber = 7; + private ulong timeout_; + public ulong Timeout + { + get { return timeout_; } + set + { + bitArray.Set(TimeoutFieldNumber, true); + timeout_ = value; + } + } + + /// Field number for the "is_response" field. + public const int IsResponseFieldNumber = 8; + private bool isResponse_; + public bool IsResponse + { + get { return isResponse_; } + set + { + bitArray.Set(IsResponseFieldNumber, true); + isResponse_ = value; + } + } + + /// Field number for the "forward_targets" field. + public const int ForwardTargetsFieldNumber = 9; + private static readonly pb::FieldCodec _repeated_forwardTargets_codec + = pb::FieldCodec.ForMessage(82, Bgs.Protocol.ProcessId.Parser); + private readonly pbc::RepeatedField forwardTargets_ = new pbc::RepeatedField(); + public pbc::RepeatedField ForwardTargets + { + get { return forwardTargets_; } + } + + /// Field number for the "service_hash" field. + public const int ServiceHashFieldNumber = 10; + private uint serviceHash_; + public uint ServiceHash + { + get { return serviceHash_; } + set + { + bitArray.Set(ServiceHashFieldNumber, true); + serviceHash_ = value; + } + } + + public override bool Equals(object other) + { + return Equals(other as Header); + } + + public bool Equals(Header other) + { + if (ReferenceEquals(other, null)) + { + return false; + } + if (ReferenceEquals(other, this)) + { + return true; + } + if (ServiceId != other.ServiceId) return false; + if (MethodId != other.MethodId) return false; + if (Token != other.Token) return false; + if (ObjectId != other.ObjectId) return false; + if (Size != other.Size) return false; + if (Status != other.Status) return false; + if (!error_.Equals(other.error_)) return false; + if (Timeout != other.Timeout) return false; + if (IsResponse != other.IsResponse) return false; + if (!forwardTargets_.Equals(other.forwardTargets_)) return false; + if (ServiceHash != other.ServiceHash) return false; + return true; + } + + public override int GetHashCode() + { + int hash = 1; + if (ServiceId != 0) hash ^= ServiceId.GetHashCode(); + if (MethodId != 0) hash ^= MethodId.GetHashCode(); + if (Token != 0) hash ^= Token.GetHashCode(); + if (ObjectId != 0UL) hash ^= ObjectId.GetHashCode(); + if (Size != 0) hash ^= Size.GetHashCode(); + if (Status != 0) hash ^= Status.GetHashCode(); + hash ^= error_.GetHashCode(); + if (Timeout != 0UL) hash ^= Timeout.GetHashCode(); + if (IsResponse != false) hash ^= IsResponse.GetHashCode(); + hash ^= forwardTargets_.GetHashCode(); + if (ServiceHash != 0) hash ^= ServiceHash.GetHashCode(); + return hash; + } + + public override string ToString() + { + return pb::JsonFormatter.ToDiagnosticString(this); + } + + public void WriteTo(pb::CodedOutputStream output) + { + if (bitArray.Get(ServiceIdFieldNumber)) + { + output.WriteRawTag(8); + output.WriteUInt32(ServiceId); + } + if (bitArray.Get(MethodIdFieldNumber)) + { + output.WriteRawTag(16); + output.WriteUInt32(MethodId); + } + if (bitArray.Get(TokenFieldNumber)) + { + output.WriteRawTag(24); + output.WriteUInt32(Token); + } + if (bitArray.Get(ObjectIdFieldNumber)) + { + output.WriteRawTag(32); + output.WriteUInt64(ObjectId); + } + if (bitArray.Get(SizeFieldNumber)) + { + output.WriteRawTag(40); + output.WriteUInt32(Size); + } + if (bitArray.Get(StatusFieldNumber)) + { + output.WriteRawTag(48); + output.WriteUInt32(Status); + } + error_.WriteTo(output, _repeated_error_codec); + if (bitArray.Get(TimeoutFieldNumber)) + { + output.WriteRawTag(64); + output.WriteUInt64(Timeout); + } + if (bitArray.Get(IsResponseFieldNumber)) + { + output.WriteRawTag(72); + output.WriteBool(IsResponse); + } + forwardTargets_.WriteTo(output, _repeated_forwardTargets_codec); + if (bitArray.Get(ServiceHashFieldNumber)) + { + output.WriteRawTag(93); + output.WriteFixed32(ServiceHash); + } + } + + public int CalculateSize() + { + int size = 0; + if (bitArray.Get(ServiceIdFieldNumber)) + { + size += 1 + pb::CodedOutputStream.ComputeUInt32Size(ServiceId); + } + if (bitArray.Get(MethodIdFieldNumber)) + { + size += 1 + pb::CodedOutputStream.ComputeUInt32Size(MethodId); + } + if (bitArray.Get(TokenFieldNumber)) + { + size += 1 + pb::CodedOutputStream.ComputeUInt32Size(Token); + } + if (bitArray.Get(ObjectIdFieldNumber)) + { + size += 1 + pb::CodedOutputStream.ComputeUInt64Size(ObjectId); + } + if (bitArray.Get(SizeFieldNumber)) + { + size += 1 + pb::CodedOutputStream.ComputeUInt32Size(Size); + } + if (bitArray.Get(StatusFieldNumber)) + { + size += 1 + pb::CodedOutputStream.ComputeUInt32Size(Status); + } + size += error_.CalculateSize(_repeated_error_codec); + if (bitArray.Get(TimeoutFieldNumber)) + { + size += 1 + pb::CodedOutputStream.ComputeUInt64Size(Timeout); + } + if (bitArray.Get(IsResponseFieldNumber)) + { + size += 1 + 1; + } + size += forwardTargets_.CalculateSize(_repeated_forwardTargets_codec); + if (bitArray.Get(ServiceHashFieldNumber)) + { + size += 1 + 4; + } + return size; + } + + public void MergeFrom(Header other) + { + if (other == null) + { + return; + } + if (other.ServiceId != 0) + { + ServiceId = other.ServiceId; + } + if (other.MethodId != 0) + { + MethodId = other.MethodId; + } + if (other.Token != 0) + { + Token = other.Token; + } + if (other.ObjectId != 0UL) + { + ObjectId = other.ObjectId; + } + if (other.Size != 0) + { + Size = other.Size; + } + if (other.Status != 0) + { + Status = other.Status; + } + error_.Add(other.error_); + if (other.Timeout != 0UL) + { + Timeout = other.Timeout; + } + if (other.IsResponse != false) + { + IsResponse = other.IsResponse; + } + forwardTargets_.Add(other.forwardTargets_); + if (other.ServiceHash != 0) + { + ServiceHash = other.ServiceHash; + } + } + + public void MergeFrom(pb::CodedInputStream input) + { + uint tag; + while ((tag = input.ReadTag()) != 0) + { + switch (tag) + { + default: + input.SkipLastField(); + break; + case 8: + { + ServiceId = input.ReadUInt32(); + break; + } + case 16: + { + MethodId = input.ReadUInt32(); + break; + } + case 24: + { + Token = input.ReadUInt32(); + break; + } + case 32: + { + ObjectId = input.ReadUInt64(); + break; + } + case 40: + { + Size = input.ReadUInt32(); + break; + } + case 48: + { + Status = input.ReadUInt32(); + break; + } + case 58: + { + error_.AddEntriesFrom(input, _repeated_error_codec); + break; + } + case 64: + { + Timeout = input.ReadUInt64(); + break; + } + case 72: + { + IsResponse = input.ReadBool(); + break; + } + case 82: + { + forwardTargets_.AddEntriesFrom(input, _repeated_forwardTargets_codec); + break; + } + case 93: + { + ServiceHash = input.ReadFixed32(); + break; + } + } + } + } + + System.Collections.BitArray bitArray = new System.Collections.BitArray(11); + } + + #endregion + +} + +#endregion Designer generated code diff --git a/Framework/Proto/UserManagerService.cs b/Framework/Proto/UserManagerService.cs new file mode 100644 index 000000000..ee0740497 --- /dev/null +++ b/Framework/Proto/UserManagerService.cs @@ -0,0 +1,1880 @@ +// Generated by the protocol buffer compiler. DO NOT EDIT! +// source: bgs/low/pb/client/user_manager_service.proto +#pragma warning disable 1591, 0612, 3021 +#region Designer generated code + +using pb = Google.Protobuf; +using pbc = Google.Protobuf.Collections; +using pbr = Google.Protobuf.Reflection; +namespace Bgs.Protocol.UserManager.V1 +{ + + /// Holder for reflection information generated from bgs/low/pb/client/user_manager_service.proto + [System.Diagnostics.DebuggerNonUserCodeAttribute()] + public static partial class UserManagerServiceReflection { + + #region Descriptor + /// File descriptor for bgs/low/pb/client/user_manager_service.proto + public static pbr::FileDescriptor Descriptor { + get { return descriptor; } + } + private static pbr::FileDescriptor descriptor; + + static UserManagerServiceReflection() { + byte[] descriptorData = System.Convert.FromBase64String( + string.Concat( + "CixiZ3MvbG93L3BiL2NsaWVudC91c2VyX21hbmFnZXJfc2VydmljZS5wcm90", + "bxIcYmdzLnByb3RvY29sLnVzZXJfbWFuYWdlci52MRoqYmdzL2xvdy9wYi9j", + "bGllbnQvdXNlcl9tYW5hZ2VyX3R5cGVzLnByb3RvGiRiZ3MvbG93L3BiL2Ns", + "aWVudC9lbnRpdHlfdHlwZXMucHJvdG8aImJncy9sb3cvcGIvY2xpZW50L3Jv", + "bGVfdHlwZXMucHJvdG8aIWJncy9sb3cvcGIvY2xpZW50L3JwY190eXBlcy5w", + "cm90byJPChBTdWJzY3JpYmVSZXF1ZXN0EigKCGFnZW50X2lkGAEgASgLMhYu", + "YmdzLnByb3RvY29sLkVudGl0eUlkEhEKCW9iamVjdF9pZBgCIAEoBCK/AQoR", + "U3Vic2NyaWJlUmVzcG9uc2USRAoPYmxvY2tlZF9wbGF5ZXJzGAEgAygLMisu", + "YmdzLnByb3RvY29sLnVzZXJfbWFuYWdlci52MS5CbG9ja2VkUGxheWVyEkIK", + "DnJlY2VudF9wbGF5ZXJzGAIgAygLMiouYmdzLnByb3RvY29sLnVzZXJfbWFu", + "YWdlci52MS5SZWNlbnRQbGF5ZXISIAoEcm9sZRgDIAMoCzISLmJncy5wcm90", + "b2NvbC5Sb2xlIlEKElVuc3Vic2NyaWJlUmVxdWVzdBIoCghhZ2VudF9pZBgB", + "IAEoCzIWLmJncy5wcm90b2NvbC5FbnRpdHlJZBIRCglvYmplY3RfaWQYAiAB", + "KAQikQEKF0FkZFJlY2VudFBsYXllcnNSZXF1ZXN0EjsKB3BsYXllcnMYASAD", + "KAsyKi5iZ3MucHJvdG9jb2wudXNlcl9tYW5hZ2VyLnYxLlJlY2VudFBsYXll", + "chIoCghhZ2VudF9pZBgCIAEoCzIWLmJncy5wcm90b2NvbC5FbnRpdHlJZBIP", + "Cgdwcm9ncmFtGAMgASgNInYKGEFkZFJlY2VudFBsYXllcnNSZXNwb25zZRJB", + "Cg1wbGF5ZXJzX2FkZGVkGAEgAygLMiouYmdzLnByb3RvY29sLnVzZXJfbWFu", + "YWdlci52MS5SZWNlbnRQbGF5ZXISFwoPcGxheWVyc19yZW1vdmVkGAMgAygH", + "IlYKGUNsZWFyUmVjZW50UGxheWVyc1JlcXVlc3QSKAoIYWdlbnRfaWQYASAB", + "KAsyFi5iZ3MucHJvdG9jb2wuRW50aXR5SWQSDwoHcHJvZ3JhbRgCIAEoDSI1", + "ChpDbGVhclJlY2VudFBsYXllcnNSZXNwb25zZRIXCg9wbGF5ZXJzX3JlbW92", + "ZWQYASADKAcidwoSQmxvY2tQbGF5ZXJSZXF1ZXN0EigKCGFnZW50X2lkGAEg", + "ASgLMhYuYmdzLnByb3RvY29sLkVudGl0eUlkEikKCXRhcmdldF9pZBgCIAEo", + "CzIWLmJncy5wcm90b2NvbC5FbnRpdHlJZBIMCgRyb2xlGAMgASgNImsKFFVu", + "YmxvY2tQbGF5ZXJSZXF1ZXN0EigKCGFnZW50X2lkGAEgASgLMhYuYmdzLnBy", + "b3RvY29sLkVudGl0eUlkEikKCXRhcmdldF9pZBgCIAEoCzIWLmJncy5wcm90", + "b2NvbC5FbnRpdHlJZCK6AQoeQmxvY2tlZFBsYXllckFkZGVkTm90aWZpY2F0", + "aW9uEjsKBnBsYXllchgBIAEoCzIrLmJncy5wcm90b2NvbC51c2VyX21hbmFn", + "ZXIudjEuQmxvY2tlZFBsYXllchIvCg9nYW1lX2FjY291bnRfaWQYAiABKAsy", + "Fi5iZ3MucHJvdG9jb2wuRW50aXR5SWQSKgoKYWNjb3VudF9pZBgDIAEoCzIW", + "LmJncy5wcm90b2NvbC5FbnRpdHlJZCK8AQogQmxvY2tlZFBsYXllclJlbW92", + "ZWROb3RpZmljYXRpb24SOwoGcGxheWVyGAEgASgLMisuYmdzLnByb3RvY29s", + "LnVzZXJfbWFuYWdlci52MS5CbG9ja2VkUGxheWVyEi8KD2dhbWVfYWNjb3Vu", + "dF9pZBgCIAEoCzIWLmJncy5wcm90b2NvbC5FbnRpdHlJZBIqCgphY2NvdW50", + "X2lkGAMgASgLMhYuYmdzLnByb3RvY29sLkVudGl0eUlkIlwKHlJlY2VudFBs", + "YXllcnNBZGRlZE5vdGlmaWNhdGlvbhI6CgZwbGF5ZXIYASADKAsyKi5iZ3Mu", + "cHJvdG9jb2wudXNlcl9tYW5hZ2VyLnYxLlJlY2VudFBsYXllciJeCiBSZWNl", + "bnRQbGF5ZXJzUmVtb3ZlZE5vdGlmaWNhdGlvbhI6CgZwbGF5ZXIYASADKAsy", + "Ki5iZ3MucHJvdG9jb2wudXNlcl9tYW5hZ2VyLnYxLlJlY2VudFBsYXllcjK5", + "BgoSVXNlck1hbmFnZXJTZXJ2aWNlEmwKCVN1YnNjcmliZRIuLmJncy5wcm90", + "b2NvbC51c2VyX21hbmFnZXIudjEuU3Vic2NyaWJlUmVxdWVzdBovLmJncy5w", + "cm90b2NvbC51c2VyX21hbmFnZXIudjEuU3Vic2NyaWJlUmVzcG9uc2USgQEK", + "EEFkZFJlY2VudFBsYXllcnMSNS5iZ3MucHJvdG9jb2wudXNlcl9tYW5hZ2Vy", + "LnYxLkFkZFJlY2VudFBsYXllcnNSZXF1ZXN0GjYuYmdzLnByb3RvY29sLnVz", + "ZXJfbWFuYWdlci52MS5BZGRSZWNlbnRQbGF5ZXJzUmVzcG9uc2UShwEKEkNs", + "ZWFyUmVjZW50UGxheWVycxI3LmJncy5wcm90b2NvbC51c2VyX21hbmFnZXIu", + "djEuQ2xlYXJSZWNlbnRQbGF5ZXJzUmVxdWVzdBo4LmJncy5wcm90b2NvbC51", + "c2VyX21hbmFnZXIudjEuQ2xlYXJSZWNlbnRQbGF5ZXJzUmVzcG9uc2USVQoL", + "QmxvY2tQbGF5ZXISMC5iZ3MucHJvdG9jb2wudXNlcl9tYW5hZ2VyLnYxLkJs", + "b2NrUGxheWVyUmVxdWVzdBoULmJncy5wcm90b2NvbC5Ob0RhdGESWQoNVW5i", + "bG9ja1BsYXllchIyLmJncy5wcm90b2NvbC51c2VyX21hbmFnZXIudjEuVW5i", + "bG9ja1BsYXllclJlcXVlc3QaFC5iZ3MucHJvdG9jb2wuTm9EYXRhEl8KFUJs", + "b2NrUGxheWVyRm9yU2Vzc2lvbhIwLmJncy5wcm90b2NvbC51c2VyX21hbmFn", + "ZXIudjEuQmxvY2tQbGF5ZXJSZXF1ZXN0GhQuYmdzLnByb3RvY29sLk5vRGF0", + "YRI9Cg1Mb2FkQmxvY2tMaXN0EhYuYmdzLnByb3RvY29sLkVudGl0eUlkGhQu", + "YmdzLnByb3RvY29sLk5vRGF0YRJVCgtVbnN1YnNjcmliZRIwLmJncy5wcm90", + "b2NvbC51c2VyX21hbmFnZXIudjEuVW5zdWJzY3JpYmVSZXF1ZXN0GhQuYmdz", + "LnByb3RvY29sLk5vRGF0YTLhAwoTVXNlck1hbmFnZXJMaXN0ZW5lchJvChRP", + "bkJsb2NrZWRQbGF5ZXJBZGRlZBI8LmJncy5wcm90b2NvbC51c2VyX21hbmFn", + "ZXIudjEuQmxvY2tlZFBsYXllckFkZGVkTm90aWZpY2F0aW9uGhkuYmdzLnBy", + "b3RvY29sLk5PX1JFU1BPTlNFEnMKFk9uQmxvY2tlZFBsYXllclJlbW92ZWQS", + "Pi5iZ3MucHJvdG9jb2wudXNlcl9tYW5hZ2VyLnYxLkJsb2NrZWRQbGF5ZXJS", + "ZW1vdmVkTm90aWZpY2F0aW9uGhkuYmdzLnByb3RvY29sLk5PX1JFU1BPTlNF", + "Em8KFE9uUmVjZW50UGxheWVyc0FkZGVkEjwuYmdzLnByb3RvY29sLnVzZXJf", + "bWFuYWdlci52MS5SZWNlbnRQbGF5ZXJzQWRkZWROb3RpZmljYXRpb24aGS5i", + "Z3MucHJvdG9jb2wuTk9fUkVTUE9OU0UScwoWT25SZWNlbnRQbGF5ZXJzUmVt", + "b3ZlZBI+LmJncy5wcm90b2NvbC51c2VyX21hbmFnZXIudjEuUmVjZW50UGxh", + "eWVyc1JlbW92ZWROb3RpZmljYXRpb24aGS5iZ3MucHJvdG9jb2wuTk9fUkVT", + "UE9OU0VCBUgCgAEAYgZwcm90bzM=")); + descriptor = pbr::FileDescriptor.FromGeneratedCode(descriptorData, + new pbr::FileDescriptor[] { Bgs.Protocol.UserManager.V1.UserManagerTypesReflection.Descriptor, Bgs.Protocol.EntityTypesReflection.Descriptor, Bgs.Protocol.RoleTypesReflection.Descriptor, Bgs.Protocol.RpcTypesReflection.Descriptor, }, + new pbr::GeneratedClrTypeInfo(null, new pbr::GeneratedClrTypeInfo[] { + new pbr::GeneratedClrTypeInfo(typeof(Bgs.Protocol.UserManager.V1.SubscribeRequest), Bgs.Protocol.UserManager.V1.SubscribeRequest.Parser, new[]{ "AgentId", "ObjectId" }, null, null, null), + new pbr::GeneratedClrTypeInfo(typeof(Bgs.Protocol.UserManager.V1.SubscribeResponse), Bgs.Protocol.UserManager.V1.SubscribeResponse.Parser, new[]{ "BlockedPlayers", "RecentPlayers", "Role" }, null, null, null), + new pbr::GeneratedClrTypeInfo(typeof(Bgs.Protocol.UserManager.V1.UnsubscribeRequest), Bgs.Protocol.UserManager.V1.UnsubscribeRequest.Parser, new[]{ "AgentId", "ObjectId" }, null, null, null), + new pbr::GeneratedClrTypeInfo(typeof(Bgs.Protocol.UserManager.V1.AddRecentPlayersRequest), Bgs.Protocol.UserManager.V1.AddRecentPlayersRequest.Parser, new[]{ "Players", "AgentId", "Program" }, null, null, null), + new pbr::GeneratedClrTypeInfo(typeof(Bgs.Protocol.UserManager.V1.AddRecentPlayersResponse), Bgs.Protocol.UserManager.V1.AddRecentPlayersResponse.Parser, new[]{ "PlayersAdded", "PlayersRemoved" }, null, null, null), + new pbr::GeneratedClrTypeInfo(typeof(Bgs.Protocol.UserManager.V1.ClearRecentPlayersRequest), Bgs.Protocol.UserManager.V1.ClearRecentPlayersRequest.Parser, new[]{ "AgentId", "Program" }, null, null, null), + new pbr::GeneratedClrTypeInfo(typeof(Bgs.Protocol.UserManager.V1.ClearRecentPlayersResponse), Bgs.Protocol.UserManager.V1.ClearRecentPlayersResponse.Parser, new[]{ "PlayersRemoved" }, null, null, null), + new pbr::GeneratedClrTypeInfo(typeof(Bgs.Protocol.UserManager.V1.BlockPlayerRequest), Bgs.Protocol.UserManager.V1.BlockPlayerRequest.Parser, new[]{ "AgentId", "TargetId", "Role" }, null, null, null), + new pbr::GeneratedClrTypeInfo(typeof(Bgs.Protocol.UserManager.V1.UnblockPlayerRequest), Bgs.Protocol.UserManager.V1.UnblockPlayerRequest.Parser, new[]{ "AgentId", "TargetId" }, null, null, null), + new pbr::GeneratedClrTypeInfo(typeof(Bgs.Protocol.UserManager.V1.BlockedPlayerAddedNotification), Bgs.Protocol.UserManager.V1.BlockedPlayerAddedNotification.Parser, new[]{ "Player", "GameAccountId", "AccountId" }, null, null, null), + new pbr::GeneratedClrTypeInfo(typeof(Bgs.Protocol.UserManager.V1.BlockedPlayerRemovedNotification), Bgs.Protocol.UserManager.V1.BlockedPlayerRemovedNotification.Parser, new[]{ "Player", "GameAccountId", "AccountId" }, null, null, null), + new pbr::GeneratedClrTypeInfo(typeof(Bgs.Protocol.UserManager.V1.RecentPlayersAddedNotification), Bgs.Protocol.UserManager.V1.RecentPlayersAddedNotification.Parser, new[]{ "Player" }, null, null, null), + new pbr::GeneratedClrTypeInfo(typeof(Bgs.Protocol.UserManager.V1.RecentPlayersRemovedNotification), Bgs.Protocol.UserManager.V1.RecentPlayersRemovedNotification.Parser, new[]{ "Player" }, null, null, null) + })); + } + #endregion + + } + #region Messages + [System.Diagnostics.DebuggerNonUserCodeAttribute()] + public sealed partial class SubscribeRequest : pb::IMessage { + private static readonly pb::MessageParser _parser = new pb::MessageParser(() => new SubscribeRequest()); + public static pb::MessageParser Parser { get { return _parser; } } + + public static pbr::MessageDescriptor Descriptor { + get { return Bgs.Protocol.UserManager.V1.UserManagerServiceReflection.Descriptor.MessageTypes[0]; } + } + + pbr::MessageDescriptor pb::IMessage.Descriptor { + get { return Descriptor; } + } + + public SubscribeRequest() { + OnConstruction(); + } + + partial void OnConstruction(); + + public SubscribeRequest(SubscribeRequest other) : this() { + AgentId = other.agentId_ != null ? other.AgentId.Clone() : null; + objectId_ = other.objectId_; + } + + public SubscribeRequest Clone() { + return new SubscribeRequest(this); + } + + /// Field number for the "agent_id" field. + public const int AgentIdFieldNumber = 1; + private Bgs.Protocol.EntityId agentId_; + public Bgs.Protocol.EntityId AgentId { + get { return agentId_; } + set { + agentId_ = value; + } + } + + /// Field number for the "object_id" field. + public const int ObjectIdFieldNumber = 2; + private ulong objectId_; + public ulong ObjectId { + get { return objectId_; } + set { + objectId_ = value; + } + } + + public override bool Equals(object other) { + return Equals(other as SubscribeRequest); + } + + public bool Equals(SubscribeRequest other) { + if (ReferenceEquals(other, null)) { + return false; + } + if (ReferenceEquals(other, this)) { + return true; + } + if (!object.Equals(AgentId, other.AgentId)) return false; + if (ObjectId != other.ObjectId) return false; + return true; + } + + public override int GetHashCode() { + int hash = 1; + if (agentId_ != null) hash ^= AgentId.GetHashCode(); + if (ObjectId != 0UL) hash ^= ObjectId.GetHashCode(); + return hash; + } + + public override string ToString() { + return pb::JsonFormatter.ToDiagnosticString(this); + } + + public void WriteTo(pb::CodedOutputStream output) { + if (agentId_ != null) { + output.WriteRawTag(10); + output.WriteMessage(AgentId); + } + if (ObjectId != 0UL) { + output.WriteRawTag(16); + output.WriteUInt64(ObjectId); + } + } + + public int CalculateSize() { + int size = 0; + if (agentId_ != null) { + size += 1 + pb::CodedOutputStream.ComputeMessageSize(AgentId); + } + if (ObjectId != 0UL) { + size += 1 + pb::CodedOutputStream.ComputeUInt64Size(ObjectId); + } + return size; + } + + public void MergeFrom(SubscribeRequest other) { + if (other == null) { + return; + } + if (other.agentId_ != null) { + if (agentId_ == null) { + agentId_ = new Bgs.Protocol.EntityId(); + } + AgentId.MergeFrom(other.AgentId); + } + if (other.ObjectId != 0UL) { + ObjectId = other.ObjectId; + } + } + + public void MergeFrom(pb::CodedInputStream input) { + uint tag; + while ((tag = input.ReadTag()) != 0) { + switch(tag) { + default: + input.SkipLastField(); + break; + case 10: { + if (agentId_ == null) { + agentId_ = new Bgs.Protocol.EntityId(); + } + input.ReadMessage(agentId_); + break; + } + case 16: { + ObjectId = input.ReadUInt64(); + break; + } + } + } + } + + } + + [System.Diagnostics.DebuggerNonUserCodeAttribute()] + public sealed partial class SubscribeResponse : pb::IMessage { + private static readonly pb::MessageParser _parser = new pb::MessageParser(() => new SubscribeResponse()); + public static pb::MessageParser Parser { get { return _parser; } } + + public static pbr::MessageDescriptor Descriptor { + get { return Bgs.Protocol.UserManager.V1.UserManagerServiceReflection.Descriptor.MessageTypes[1]; } + } + + pbr::MessageDescriptor pb::IMessage.Descriptor { + get { return Descriptor; } + } + + public SubscribeResponse() { + OnConstruction(); + } + + partial void OnConstruction(); + + public SubscribeResponse(SubscribeResponse other) : this() { + blockedPlayers_ = other.blockedPlayers_.Clone(); + recentPlayers_ = other.recentPlayers_.Clone(); + role_ = other.role_.Clone(); + } + + public SubscribeResponse Clone() { + return new SubscribeResponse(this); + } + + /// Field number for the "blocked_players" field. + public const int BlockedPlayersFieldNumber = 1; + private static readonly pb::FieldCodec _repeated_blockedPlayers_codec + = pb::FieldCodec.ForMessage(10, Bgs.Protocol.UserManager.V1.BlockedPlayer.Parser); + private readonly pbc::RepeatedField blockedPlayers_ = new pbc::RepeatedField(); + public pbc::RepeatedField BlockedPlayers { + get { return blockedPlayers_; } + } + + /// Field number for the "recent_players" field. + public const int RecentPlayersFieldNumber = 2; + private static readonly pb::FieldCodec _repeated_recentPlayers_codec + = pb::FieldCodec.ForMessage(18, Bgs.Protocol.UserManager.V1.RecentPlayer.Parser); + private readonly pbc::RepeatedField recentPlayers_ = new pbc::RepeatedField(); + public pbc::RepeatedField RecentPlayers { + get { return recentPlayers_; } + } + + /// Field number for the "role" field. + public const int RoleFieldNumber = 3; + private static readonly pb::FieldCodec _repeated_role_codec + = pb::FieldCodec.ForMessage(26, Bgs.Protocol.Role.Parser); + private readonly pbc::RepeatedField role_ = new pbc::RepeatedField(); + public pbc::RepeatedField Role { + get { return role_; } + } + + public override bool Equals(object other) { + return Equals(other as SubscribeResponse); + } + + public bool Equals(SubscribeResponse other) { + if (ReferenceEquals(other, null)) { + return false; + } + if (ReferenceEquals(other, this)) { + return true; + } + if(!blockedPlayers_.Equals(other.blockedPlayers_)) return false; + if(!recentPlayers_.Equals(other.recentPlayers_)) return false; + if(!role_.Equals(other.role_)) return false; + return true; + } + + public override int GetHashCode() { + int hash = 1; + hash ^= blockedPlayers_.GetHashCode(); + hash ^= recentPlayers_.GetHashCode(); + hash ^= role_.GetHashCode(); + return hash; + } + + public override string ToString() { + return pb::JsonFormatter.ToDiagnosticString(this); + } + + public void WriteTo(pb::CodedOutputStream output) { + blockedPlayers_.WriteTo(output, _repeated_blockedPlayers_codec); + recentPlayers_.WriteTo(output, _repeated_recentPlayers_codec); + role_.WriteTo(output, _repeated_role_codec); + } + + public int CalculateSize() { + int size = 0; + size += blockedPlayers_.CalculateSize(_repeated_blockedPlayers_codec); + size += recentPlayers_.CalculateSize(_repeated_recentPlayers_codec); + size += role_.CalculateSize(_repeated_role_codec); + return size; + } + + public void MergeFrom(SubscribeResponse other) { + if (other == null) { + return; + } + blockedPlayers_.Add(other.blockedPlayers_); + recentPlayers_.Add(other.recentPlayers_); + role_.Add(other.role_); + } + + public void MergeFrom(pb::CodedInputStream input) { + uint tag; + while ((tag = input.ReadTag()) != 0) { + switch(tag) { + default: + input.SkipLastField(); + break; + case 10: { + blockedPlayers_.AddEntriesFrom(input, _repeated_blockedPlayers_codec); + break; + } + case 18: { + recentPlayers_.AddEntriesFrom(input, _repeated_recentPlayers_codec); + break; + } + case 26: { + role_.AddEntriesFrom(input, _repeated_role_codec); + break; + } + } + } + } + + } + + [System.Diagnostics.DebuggerNonUserCodeAttribute()] + public sealed partial class UnsubscribeRequest : pb::IMessage { + private static readonly pb::MessageParser _parser = new pb::MessageParser(() => new UnsubscribeRequest()); + public static pb::MessageParser Parser { get { return _parser; } } + + public static pbr::MessageDescriptor Descriptor { + get { return Bgs.Protocol.UserManager.V1.UserManagerServiceReflection.Descriptor.MessageTypes[2]; } + } + + pbr::MessageDescriptor pb::IMessage.Descriptor { + get { return Descriptor; } + } + + public UnsubscribeRequest() { + OnConstruction(); + } + + partial void OnConstruction(); + + public UnsubscribeRequest(UnsubscribeRequest other) : this() { + AgentId = other.agentId_ != null ? other.AgentId.Clone() : null; + objectId_ = other.objectId_; + } + + public UnsubscribeRequest Clone() { + return new UnsubscribeRequest(this); + } + + /// Field number for the "agent_id" field. + public const int AgentIdFieldNumber = 1; + private Bgs.Protocol.EntityId agentId_; + public Bgs.Protocol.EntityId AgentId { + get { return agentId_; } + set { + agentId_ = value; + } + } + + /// Field number for the "object_id" field. + public const int ObjectIdFieldNumber = 2; + private ulong objectId_; + public ulong ObjectId { + get { return objectId_; } + set { + objectId_ = value; + } + } + + public override bool Equals(object other) { + return Equals(other as UnsubscribeRequest); + } + + public bool Equals(UnsubscribeRequest other) { + if (ReferenceEquals(other, null)) { + return false; + } + if (ReferenceEquals(other, this)) { + return true; + } + if (!object.Equals(AgentId, other.AgentId)) return false; + if (ObjectId != other.ObjectId) return false; + return true; + } + + public override int GetHashCode() { + int hash = 1; + if (agentId_ != null) hash ^= AgentId.GetHashCode(); + if (ObjectId != 0UL) hash ^= ObjectId.GetHashCode(); + return hash; + } + + public override string ToString() { + return pb::JsonFormatter.ToDiagnosticString(this); + } + + public void WriteTo(pb::CodedOutputStream output) { + if (agentId_ != null) { + output.WriteRawTag(10); + output.WriteMessage(AgentId); + } + if (ObjectId != 0UL) { + output.WriteRawTag(16); + output.WriteUInt64(ObjectId); + } + } + + public int CalculateSize() { + int size = 0; + if (agentId_ != null) { + size += 1 + pb::CodedOutputStream.ComputeMessageSize(AgentId); + } + if (ObjectId != 0UL) { + size += 1 + pb::CodedOutputStream.ComputeUInt64Size(ObjectId); + } + return size; + } + + public void MergeFrom(UnsubscribeRequest other) { + if (other == null) { + return; + } + if (other.agentId_ != null) { + if (agentId_ == null) { + agentId_ = new Bgs.Protocol.EntityId(); + } + AgentId.MergeFrom(other.AgentId); + } + if (other.ObjectId != 0UL) { + ObjectId = other.ObjectId; + } + } + + public void MergeFrom(pb::CodedInputStream input) { + uint tag; + while ((tag = input.ReadTag()) != 0) { + switch(tag) { + default: + input.SkipLastField(); + break; + case 10: { + if (agentId_ == null) { + agentId_ = new Bgs.Protocol.EntityId(); + } + input.ReadMessage(agentId_); + break; + } + case 16: { + ObjectId = input.ReadUInt64(); + break; + } + } + } + } + + } + + [System.Diagnostics.DebuggerNonUserCodeAttribute()] + public sealed partial class AddRecentPlayersRequest : pb::IMessage { + private static readonly pb::MessageParser _parser = new pb::MessageParser(() => new AddRecentPlayersRequest()); + public static pb::MessageParser Parser { get { return _parser; } } + + public static pbr::MessageDescriptor Descriptor { + get { return Bgs.Protocol.UserManager.V1.UserManagerServiceReflection.Descriptor.MessageTypes[3]; } + } + + pbr::MessageDescriptor pb::IMessage.Descriptor { + get { return Descriptor; } + } + + public AddRecentPlayersRequest() { + OnConstruction(); + } + + partial void OnConstruction(); + + public AddRecentPlayersRequest(AddRecentPlayersRequest other) : this() { + players_ = other.players_.Clone(); + AgentId = other.agentId_ != null ? other.AgentId.Clone() : null; + program_ = other.program_; + } + + public AddRecentPlayersRequest Clone() { + return new AddRecentPlayersRequest(this); + } + + /// Field number for the "players" field. + public const int PlayersFieldNumber = 1; + private static readonly pb::FieldCodec _repeated_players_codec + = pb::FieldCodec.ForMessage(10, Bgs.Protocol.UserManager.V1.RecentPlayer.Parser); + private readonly pbc::RepeatedField players_ = new pbc::RepeatedField(); + public pbc::RepeatedField Players { + get { return players_; } + } + + /// Field number for the "agent_id" field. + public const int AgentIdFieldNumber = 2; + private Bgs.Protocol.EntityId agentId_; + public Bgs.Protocol.EntityId AgentId { + get { return agentId_; } + set { + agentId_ = value; + } + } + + /// Field number for the "program" field. + public const int ProgramFieldNumber = 3; + private uint program_; + public uint Program { + get { return program_; } + set { + program_ = value; + } + } + + public override bool Equals(object other) { + return Equals(other as AddRecentPlayersRequest); + } + + public bool Equals(AddRecentPlayersRequest other) { + if (ReferenceEquals(other, null)) { + return false; + } + if (ReferenceEquals(other, this)) { + return true; + } + if(!players_.Equals(other.players_)) return false; + if (!object.Equals(AgentId, other.AgentId)) return false; + if (Program != other.Program) return false; + return true; + } + + public override int GetHashCode() { + int hash = 1; + hash ^= players_.GetHashCode(); + if (agentId_ != null) hash ^= AgentId.GetHashCode(); + if (Program != 0) hash ^= Program.GetHashCode(); + return hash; + } + + public override string ToString() { + return pb::JsonFormatter.ToDiagnosticString(this); + } + + public void WriteTo(pb::CodedOutputStream output) { + players_.WriteTo(output, _repeated_players_codec); + if (agentId_ != null) { + output.WriteRawTag(18); + output.WriteMessage(AgentId); + } + if (Program != 0) { + output.WriteRawTag(24); + output.WriteUInt32(Program); + } + } + + public int CalculateSize() { + int size = 0; + size += players_.CalculateSize(_repeated_players_codec); + if (agentId_ != null) { + size += 1 + pb::CodedOutputStream.ComputeMessageSize(AgentId); + } + if (Program != 0) { + size += 1 + pb::CodedOutputStream.ComputeUInt32Size(Program); + } + return size; + } + + public void MergeFrom(AddRecentPlayersRequest other) { + if (other == null) { + return; + } + players_.Add(other.players_); + if (other.agentId_ != null) { + if (agentId_ == null) { + agentId_ = new Bgs.Protocol.EntityId(); + } + AgentId.MergeFrom(other.AgentId); + } + if (other.Program != 0) { + Program = other.Program; + } + } + + public void MergeFrom(pb::CodedInputStream input) { + uint tag; + while ((tag = input.ReadTag()) != 0) { + switch(tag) { + default: + input.SkipLastField(); + break; + case 10: { + players_.AddEntriesFrom(input, _repeated_players_codec); + break; + } + case 18: { + if (agentId_ == null) { + agentId_ = new Bgs.Protocol.EntityId(); + } + input.ReadMessage(agentId_); + break; + } + case 24: { + Program = input.ReadUInt32(); + break; + } + } + } + } + + } + + [System.Diagnostics.DebuggerNonUserCodeAttribute()] + public sealed partial class AddRecentPlayersResponse : pb::IMessage { + private static readonly pb::MessageParser _parser = new pb::MessageParser(() => new AddRecentPlayersResponse()); + public static pb::MessageParser Parser { get { return _parser; } } + + public static pbr::MessageDescriptor Descriptor { + get { return Bgs.Protocol.UserManager.V1.UserManagerServiceReflection.Descriptor.MessageTypes[4]; } + } + + pbr::MessageDescriptor pb::IMessage.Descriptor { + get { return Descriptor; } + } + + public AddRecentPlayersResponse() { + OnConstruction(); + } + + partial void OnConstruction(); + + public AddRecentPlayersResponse(AddRecentPlayersResponse other) : this() { + playersAdded_ = other.playersAdded_.Clone(); + playersRemoved_ = other.playersRemoved_.Clone(); + } + + public AddRecentPlayersResponse Clone() { + return new AddRecentPlayersResponse(this); + } + + /// Field number for the "players_added" field. + public const int PlayersAddedFieldNumber = 1; + private static readonly pb::FieldCodec _repeated_playersAdded_codec + = pb::FieldCodec.ForMessage(10, Bgs.Protocol.UserManager.V1.RecentPlayer.Parser); + private readonly pbc::RepeatedField playersAdded_ = new pbc::RepeatedField(); + public pbc::RepeatedField PlayersAdded { + get { return playersAdded_; } + } + + /// Field number for the "players_removed" field. + public const int PlayersRemovedFieldNumber = 3; + private static readonly pb::FieldCodec _repeated_playersRemoved_codec + = pb::FieldCodec.ForFixed32(26); + private readonly pbc::RepeatedField playersRemoved_ = new pbc::RepeatedField(); + public pbc::RepeatedField PlayersRemoved { + get { return playersRemoved_; } + } + + public override bool Equals(object other) { + return Equals(other as AddRecentPlayersResponse); + } + + public bool Equals(AddRecentPlayersResponse other) { + if (ReferenceEquals(other, null)) { + return false; + } + if (ReferenceEquals(other, this)) { + return true; + } + if(!playersAdded_.Equals(other.playersAdded_)) return false; + if(!playersRemoved_.Equals(other.playersRemoved_)) return false; + return true; + } + + public override int GetHashCode() { + int hash = 1; + hash ^= playersAdded_.GetHashCode(); + hash ^= playersRemoved_.GetHashCode(); + return hash; + } + + public override string ToString() { + return pb::JsonFormatter.ToDiagnosticString(this); + } + + public void WriteTo(pb::CodedOutputStream output) { + playersAdded_.WriteTo(output, _repeated_playersAdded_codec); + playersRemoved_.WriteTo(output, _repeated_playersRemoved_codec); + } + + public int CalculateSize() { + int size = 0; + size += playersAdded_.CalculateSize(_repeated_playersAdded_codec); + size += playersRemoved_.CalculateSize(_repeated_playersRemoved_codec); + return size; + } + + public void MergeFrom(AddRecentPlayersResponse other) { + if (other == null) { + return; + } + playersAdded_.Add(other.playersAdded_); + playersRemoved_.Add(other.playersRemoved_); + } + + public void MergeFrom(pb::CodedInputStream input) { + uint tag; + while ((tag = input.ReadTag()) != 0) { + switch(tag) { + default: + input.SkipLastField(); + break; + case 10: { + playersAdded_.AddEntriesFrom(input, _repeated_playersAdded_codec); + break; + } + case 26: + case 29: { + playersRemoved_.AddEntriesFrom(input, _repeated_playersRemoved_codec); + break; + } + } + } + } + + } + + [System.Diagnostics.DebuggerNonUserCodeAttribute()] + public sealed partial class ClearRecentPlayersRequest : pb::IMessage { + private static readonly pb::MessageParser _parser = new pb::MessageParser(() => new ClearRecentPlayersRequest()); + public static pb::MessageParser Parser { get { return _parser; } } + + public static pbr::MessageDescriptor Descriptor { + get { return Bgs.Protocol.UserManager.V1.UserManagerServiceReflection.Descriptor.MessageTypes[5]; } + } + + pbr::MessageDescriptor pb::IMessage.Descriptor { + get { return Descriptor; } + } + + public ClearRecentPlayersRequest() { + OnConstruction(); + } + + partial void OnConstruction(); + + public ClearRecentPlayersRequest(ClearRecentPlayersRequest other) : this() { + AgentId = other.agentId_ != null ? other.AgentId.Clone() : null; + program_ = other.program_; + } + + public ClearRecentPlayersRequest Clone() { + return new ClearRecentPlayersRequest(this); + } + + /// Field number for the "agent_id" field. + public const int AgentIdFieldNumber = 1; + private Bgs.Protocol.EntityId agentId_; + public Bgs.Protocol.EntityId AgentId { + get { return agentId_; } + set { + agentId_ = value; + } + } + + /// Field number for the "program" field. + public const int ProgramFieldNumber = 2; + private uint program_; + public uint Program { + get { return program_; } + set { + program_ = value; + } + } + + public override bool Equals(object other) { + return Equals(other as ClearRecentPlayersRequest); + } + + public bool Equals(ClearRecentPlayersRequest other) { + if (ReferenceEquals(other, null)) { + return false; + } + if (ReferenceEquals(other, this)) { + return true; + } + if (!object.Equals(AgentId, other.AgentId)) return false; + if (Program != other.Program) return false; + return true; + } + + public override int GetHashCode() { + int hash = 1; + if (agentId_ != null) hash ^= AgentId.GetHashCode(); + if (Program != 0) hash ^= Program.GetHashCode(); + return hash; + } + + public override string ToString() { + return pb::JsonFormatter.ToDiagnosticString(this); + } + + public void WriteTo(pb::CodedOutputStream output) { + if (agentId_ != null) { + output.WriteRawTag(10); + output.WriteMessage(AgentId); + } + if (Program != 0) { + output.WriteRawTag(16); + output.WriteUInt32(Program); + } + } + + public int CalculateSize() { + int size = 0; + if (agentId_ != null) { + size += 1 + pb::CodedOutputStream.ComputeMessageSize(AgentId); + } + if (Program != 0) { + size += 1 + pb::CodedOutputStream.ComputeUInt32Size(Program); + } + return size; + } + + public void MergeFrom(ClearRecentPlayersRequest other) { + if (other == null) { + return; + } + if (other.agentId_ != null) { + if (agentId_ == null) { + agentId_ = new Bgs.Protocol.EntityId(); + } + AgentId.MergeFrom(other.AgentId); + } + if (other.Program != 0) { + Program = other.Program; + } + } + + public void MergeFrom(pb::CodedInputStream input) { + uint tag; + while ((tag = input.ReadTag()) != 0) { + switch(tag) { + default: + input.SkipLastField(); + break; + case 10: { + if (agentId_ == null) { + agentId_ = new Bgs.Protocol.EntityId(); + } + input.ReadMessage(agentId_); + break; + } + case 16: { + Program = input.ReadUInt32(); + break; + } + } + } + } + + } + + [System.Diagnostics.DebuggerNonUserCodeAttribute()] + public sealed partial class ClearRecentPlayersResponse : pb::IMessage { + private static readonly pb::MessageParser _parser = new pb::MessageParser(() => new ClearRecentPlayersResponse()); + public static pb::MessageParser Parser { get { return _parser; } } + + public static pbr::MessageDescriptor Descriptor { + get { return Bgs.Protocol.UserManager.V1.UserManagerServiceReflection.Descriptor.MessageTypes[6]; } + } + + pbr::MessageDescriptor pb::IMessage.Descriptor { + get { return Descriptor; } + } + + public ClearRecentPlayersResponse() { + OnConstruction(); + } + + partial void OnConstruction(); + + public ClearRecentPlayersResponse(ClearRecentPlayersResponse other) : this() { + playersRemoved_ = other.playersRemoved_.Clone(); + } + + public ClearRecentPlayersResponse Clone() { + return new ClearRecentPlayersResponse(this); + } + + /// Field number for the "players_removed" field. + public const int PlayersRemovedFieldNumber = 1; + private static readonly pb::FieldCodec _repeated_playersRemoved_codec + = pb::FieldCodec.ForFixed32(10); + private readonly pbc::RepeatedField playersRemoved_ = new pbc::RepeatedField(); + public pbc::RepeatedField PlayersRemoved { + get { return playersRemoved_; } + } + + public override bool Equals(object other) { + return Equals(other as ClearRecentPlayersResponse); + } + + public bool Equals(ClearRecentPlayersResponse other) { + if (ReferenceEquals(other, null)) { + return false; + } + if (ReferenceEquals(other, this)) { + return true; + } + if(!playersRemoved_.Equals(other.playersRemoved_)) return false; + return true; + } + + public override int GetHashCode() { + int hash = 1; + hash ^= playersRemoved_.GetHashCode(); + return hash; + } + + public override string ToString() { + return pb::JsonFormatter.ToDiagnosticString(this); + } + + public void WriteTo(pb::CodedOutputStream output) { + playersRemoved_.WriteTo(output, _repeated_playersRemoved_codec); + } + + public int CalculateSize() { + int size = 0; + size += playersRemoved_.CalculateSize(_repeated_playersRemoved_codec); + return size; + } + + public void MergeFrom(ClearRecentPlayersResponse other) { + if (other == null) { + return; + } + playersRemoved_.Add(other.playersRemoved_); + } + + public void MergeFrom(pb::CodedInputStream input) { + uint tag; + while ((tag = input.ReadTag()) != 0) { + switch(tag) { + default: + input.SkipLastField(); + break; + case 10: + case 13: { + playersRemoved_.AddEntriesFrom(input, _repeated_playersRemoved_codec); + break; + } + } + } + } + + } + + [System.Diagnostics.DebuggerNonUserCodeAttribute()] + public sealed partial class BlockPlayerRequest : pb::IMessage { + private static readonly pb::MessageParser _parser = new pb::MessageParser(() => new BlockPlayerRequest()); + public static pb::MessageParser Parser { get { return _parser; } } + + public static pbr::MessageDescriptor Descriptor { + get { return Bgs.Protocol.UserManager.V1.UserManagerServiceReflection.Descriptor.MessageTypes[7]; } + } + + pbr::MessageDescriptor pb::IMessage.Descriptor { + get { return Descriptor; } + } + + public BlockPlayerRequest() { + OnConstruction(); + } + + partial void OnConstruction(); + + public BlockPlayerRequest(BlockPlayerRequest other) : this() { + AgentId = other.agentId_ != null ? other.AgentId.Clone() : null; + TargetId = other.targetId_ != null ? other.TargetId.Clone() : null; + role_ = other.role_; + } + + public BlockPlayerRequest Clone() { + return new BlockPlayerRequest(this); + } + + /// Field number for the "agent_id" field. + public const int AgentIdFieldNumber = 1; + private Bgs.Protocol.EntityId agentId_; + public Bgs.Protocol.EntityId AgentId { + get { return agentId_; } + set { + agentId_ = value; + } + } + + /// Field number for the "target_id" field. + public const int TargetIdFieldNumber = 2; + private Bgs.Protocol.EntityId targetId_; + public Bgs.Protocol.EntityId TargetId { + get { return targetId_; } + set { + targetId_ = value; + } + } + + /// Field number for the "role" field. + public const int RoleFieldNumber = 3; + private uint role_; + public uint Role { + get { return role_; } + set { + role_ = value; + } + } + + public override bool Equals(object other) { + return Equals(other as BlockPlayerRequest); + } + + public bool Equals(BlockPlayerRequest other) { + if (ReferenceEquals(other, null)) { + return false; + } + if (ReferenceEquals(other, this)) { + return true; + } + if (!object.Equals(AgentId, other.AgentId)) return false; + if (!object.Equals(TargetId, other.TargetId)) return false; + if (Role != other.Role) return false; + return true; + } + + public override int GetHashCode() { + int hash = 1; + if (agentId_ != null) hash ^= AgentId.GetHashCode(); + if (targetId_ != null) hash ^= TargetId.GetHashCode(); + if (Role != 0) hash ^= Role.GetHashCode(); + return hash; + } + + public override string ToString() { + return pb::JsonFormatter.ToDiagnosticString(this); + } + + public void WriteTo(pb::CodedOutputStream output) { + if (agentId_ != null) { + output.WriteRawTag(10); + output.WriteMessage(AgentId); + } + if (targetId_ != null) { + output.WriteRawTag(18); + output.WriteMessage(TargetId); + } + if (Role != 0) { + output.WriteRawTag(24); + output.WriteUInt32(Role); + } + } + + public int CalculateSize() { + int size = 0; + if (agentId_ != null) { + size += 1 + pb::CodedOutputStream.ComputeMessageSize(AgentId); + } + if (targetId_ != null) { + size += 1 + pb::CodedOutputStream.ComputeMessageSize(TargetId); + } + if (Role != 0) { + size += 1 + pb::CodedOutputStream.ComputeUInt32Size(Role); + } + return size; + } + + public void MergeFrom(BlockPlayerRequest other) { + if (other == null) { + return; + } + if (other.agentId_ != null) { + if (agentId_ == null) { + agentId_ = new Bgs.Protocol.EntityId(); + } + AgentId.MergeFrom(other.AgentId); + } + if (other.targetId_ != null) { + if (targetId_ == null) { + targetId_ = new Bgs.Protocol.EntityId(); + } + TargetId.MergeFrom(other.TargetId); + } + if (other.Role != 0) { + Role = other.Role; + } + } + + public void MergeFrom(pb::CodedInputStream input) { + uint tag; + while ((tag = input.ReadTag()) != 0) { + switch(tag) { + default: + input.SkipLastField(); + break; + case 10: { + if (agentId_ == null) { + agentId_ = new Bgs.Protocol.EntityId(); + } + input.ReadMessage(agentId_); + break; + } + case 18: { + if (targetId_ == null) { + targetId_ = new Bgs.Protocol.EntityId(); + } + input.ReadMessage(targetId_); + break; + } + case 24: { + Role = input.ReadUInt32(); + break; + } + } + } + } + + } + + [System.Diagnostics.DebuggerNonUserCodeAttribute()] + public sealed partial class UnblockPlayerRequest : pb::IMessage { + private static readonly pb::MessageParser _parser = new pb::MessageParser(() => new UnblockPlayerRequest()); + public static pb::MessageParser Parser { get { return _parser; } } + + public static pbr::MessageDescriptor Descriptor { + get { return Bgs.Protocol.UserManager.V1.UserManagerServiceReflection.Descriptor.MessageTypes[8]; } + } + + pbr::MessageDescriptor pb::IMessage.Descriptor { + get { return Descriptor; } + } + + public UnblockPlayerRequest() { + OnConstruction(); + } + + partial void OnConstruction(); + + public UnblockPlayerRequest(UnblockPlayerRequest other) : this() { + AgentId = other.agentId_ != null ? other.AgentId.Clone() : null; + TargetId = other.targetId_ != null ? other.TargetId.Clone() : null; + } + + public UnblockPlayerRequest Clone() { + return new UnblockPlayerRequest(this); + } + + /// Field number for the "agent_id" field. + public const int AgentIdFieldNumber = 1; + private Bgs.Protocol.EntityId agentId_; + public Bgs.Protocol.EntityId AgentId { + get { return agentId_; } + set { + agentId_ = value; + } + } + + /// Field number for the "target_id" field. + public const int TargetIdFieldNumber = 2; + private Bgs.Protocol.EntityId targetId_; + public Bgs.Protocol.EntityId TargetId { + get { return targetId_; } + set { + targetId_ = value; + } + } + + public override bool Equals(object other) { + return Equals(other as UnblockPlayerRequest); + } + + public bool Equals(UnblockPlayerRequest other) { + if (ReferenceEquals(other, null)) { + return false; + } + if (ReferenceEquals(other, this)) { + return true; + } + if (!object.Equals(AgentId, other.AgentId)) return false; + if (!object.Equals(TargetId, other.TargetId)) return false; + return true; + } + + public override int GetHashCode() { + int hash = 1; + if (agentId_ != null) hash ^= AgentId.GetHashCode(); + if (targetId_ != null) hash ^= TargetId.GetHashCode(); + return hash; + } + + public override string ToString() { + return pb::JsonFormatter.ToDiagnosticString(this); + } + + public void WriteTo(pb::CodedOutputStream output) { + if (agentId_ != null) { + output.WriteRawTag(10); + output.WriteMessage(AgentId); + } + if (targetId_ != null) { + output.WriteRawTag(18); + output.WriteMessage(TargetId); + } + } + + public int CalculateSize() { + int size = 0; + if (agentId_ != null) { + size += 1 + pb::CodedOutputStream.ComputeMessageSize(AgentId); + } + if (targetId_ != null) { + size += 1 + pb::CodedOutputStream.ComputeMessageSize(TargetId); + } + return size; + } + + public void MergeFrom(UnblockPlayerRequest other) { + if (other == null) { + return; + } + if (other.agentId_ != null) { + if (agentId_ == null) { + agentId_ = new Bgs.Protocol.EntityId(); + } + AgentId.MergeFrom(other.AgentId); + } + if (other.targetId_ != null) { + if (targetId_ == null) { + targetId_ = new Bgs.Protocol.EntityId(); + } + TargetId.MergeFrom(other.TargetId); + } + } + + public void MergeFrom(pb::CodedInputStream input) { + uint tag; + while ((tag = input.ReadTag()) != 0) { + switch(tag) { + default: + input.SkipLastField(); + break; + case 10: { + if (agentId_ == null) { + agentId_ = new Bgs.Protocol.EntityId(); + } + input.ReadMessage(agentId_); + break; + } + case 18: { + if (targetId_ == null) { + targetId_ = new Bgs.Protocol.EntityId(); + } + input.ReadMessage(targetId_); + break; + } + } + } + } + + } + + [System.Diagnostics.DebuggerNonUserCodeAttribute()] + public sealed partial class BlockedPlayerAddedNotification : pb::IMessage { + private static readonly pb::MessageParser _parser = new pb::MessageParser(() => new BlockedPlayerAddedNotification()); + public static pb::MessageParser Parser { get { return _parser; } } + + public static pbr::MessageDescriptor Descriptor { + get { return Bgs.Protocol.UserManager.V1.UserManagerServiceReflection.Descriptor.MessageTypes[9]; } + } + + pbr::MessageDescriptor pb::IMessage.Descriptor { + get { return Descriptor; } + } + + public BlockedPlayerAddedNotification() { + OnConstruction(); + } + + partial void OnConstruction(); + + public BlockedPlayerAddedNotification(BlockedPlayerAddedNotification other) : this() { + Player = other.player_ != null ? other.Player.Clone() : null; + GameAccountId = other.gameAccountId_ != null ? other.GameAccountId.Clone() : null; + AccountId = other.accountId_ != null ? other.AccountId.Clone() : null; + } + + public BlockedPlayerAddedNotification Clone() { + return new BlockedPlayerAddedNotification(this); + } + + /// Field number for the "player" field. + public const int PlayerFieldNumber = 1; + private Bgs.Protocol.UserManager.V1.BlockedPlayer player_; + public Bgs.Protocol.UserManager.V1.BlockedPlayer Player { + get { return player_; } + set { + player_ = value; + } + } + + /// Field number for the "game_account_id" field. + public const int GameAccountIdFieldNumber = 2; + private Bgs.Protocol.EntityId gameAccountId_; + public Bgs.Protocol.EntityId GameAccountId { + get { return gameAccountId_; } + set { + gameAccountId_ = value; + } + } + + /// Field number for the "account_id" field. + public const int AccountIdFieldNumber = 3; + private Bgs.Protocol.EntityId accountId_; + public Bgs.Protocol.EntityId AccountId { + get { return accountId_; } + set { + accountId_ = value; + } + } + + public override bool Equals(object other) { + return Equals(other as BlockedPlayerAddedNotification); + } + + public bool Equals(BlockedPlayerAddedNotification other) { + if (ReferenceEquals(other, null)) { + return false; + } + if (ReferenceEquals(other, this)) { + return true; + } + if (!object.Equals(Player, other.Player)) return false; + if (!object.Equals(GameAccountId, other.GameAccountId)) return false; + if (!object.Equals(AccountId, other.AccountId)) return false; + return true; + } + + public override int GetHashCode() { + int hash = 1; + if (player_ != null) hash ^= Player.GetHashCode(); + if (gameAccountId_ != null) hash ^= GameAccountId.GetHashCode(); + if (accountId_ != null) hash ^= AccountId.GetHashCode(); + return hash; + } + + public override string ToString() { + return pb::JsonFormatter.ToDiagnosticString(this); + } + + public void WriteTo(pb::CodedOutputStream output) { + if (player_ != null) { + output.WriteRawTag(10); + output.WriteMessage(Player); + } + if (gameAccountId_ != null) { + output.WriteRawTag(18); + output.WriteMessage(GameAccountId); + } + if (accountId_ != null) { + output.WriteRawTag(26); + output.WriteMessage(AccountId); + } + } + + public int CalculateSize() { + int size = 0; + if (player_ != null) { + size += 1 + pb::CodedOutputStream.ComputeMessageSize(Player); + } + if (gameAccountId_ != null) { + size += 1 + pb::CodedOutputStream.ComputeMessageSize(GameAccountId); + } + if (accountId_ != null) { + size += 1 + pb::CodedOutputStream.ComputeMessageSize(AccountId); + } + return size; + } + + public void MergeFrom(BlockedPlayerAddedNotification other) { + if (other == null) { + return; + } + if (other.player_ != null) { + if (player_ == null) { + player_ = new Bgs.Protocol.UserManager.V1.BlockedPlayer(); + } + Player.MergeFrom(other.Player); + } + if (other.gameAccountId_ != null) { + if (gameAccountId_ == null) { + gameAccountId_ = new Bgs.Protocol.EntityId(); + } + GameAccountId.MergeFrom(other.GameAccountId); + } + if (other.accountId_ != null) { + if (accountId_ == null) { + accountId_ = new Bgs.Protocol.EntityId(); + } + AccountId.MergeFrom(other.AccountId); + } + } + + public void MergeFrom(pb::CodedInputStream input) { + uint tag; + while ((tag = input.ReadTag()) != 0) { + switch(tag) { + default: + input.SkipLastField(); + break; + case 10: { + if (player_ == null) { + player_ = new Bgs.Protocol.UserManager.V1.BlockedPlayer(); + } + input.ReadMessage(player_); + break; + } + case 18: { + if (gameAccountId_ == null) { + gameAccountId_ = new Bgs.Protocol.EntityId(); + } + input.ReadMessage(gameAccountId_); + break; + } + case 26: { + if (accountId_ == null) { + accountId_ = new Bgs.Protocol.EntityId(); + } + input.ReadMessage(accountId_); + break; + } + } + } + } + + } + + [System.Diagnostics.DebuggerNonUserCodeAttribute()] + public sealed partial class BlockedPlayerRemovedNotification : pb::IMessage { + private static readonly pb::MessageParser _parser = new pb::MessageParser(() => new BlockedPlayerRemovedNotification()); + public static pb::MessageParser Parser { get { return _parser; } } + + public static pbr::MessageDescriptor Descriptor { + get { return Bgs.Protocol.UserManager.V1.UserManagerServiceReflection.Descriptor.MessageTypes[10]; } + } + + pbr::MessageDescriptor pb::IMessage.Descriptor { + get { return Descriptor; } + } + + public BlockedPlayerRemovedNotification() { + OnConstruction(); + } + + partial void OnConstruction(); + + public BlockedPlayerRemovedNotification(BlockedPlayerRemovedNotification other) : this() { + Player = other.player_ != null ? other.Player.Clone() : null; + GameAccountId = other.gameAccountId_ != null ? other.GameAccountId.Clone() : null; + AccountId = other.accountId_ != null ? other.AccountId.Clone() : null; + } + + public BlockedPlayerRemovedNotification Clone() { + return new BlockedPlayerRemovedNotification(this); + } + + /// Field number for the "player" field. + public const int PlayerFieldNumber = 1; + private Bgs.Protocol.UserManager.V1.BlockedPlayer player_; + public Bgs.Protocol.UserManager.V1.BlockedPlayer Player { + get { return player_; } + set { + player_ = value; + } + } + + /// Field number for the "game_account_id" field. + public const int GameAccountIdFieldNumber = 2; + private Bgs.Protocol.EntityId gameAccountId_; + public Bgs.Protocol.EntityId GameAccountId { + get { return gameAccountId_; } + set { + gameAccountId_ = value; + } + } + + /// Field number for the "account_id" field. + public const int AccountIdFieldNumber = 3; + private Bgs.Protocol.EntityId accountId_; + public Bgs.Protocol.EntityId AccountId { + get { return accountId_; } + set { + accountId_ = value; + } + } + + public override bool Equals(object other) { + return Equals(other as BlockedPlayerRemovedNotification); + } + + public bool Equals(BlockedPlayerRemovedNotification other) { + if (ReferenceEquals(other, null)) { + return false; + } + if (ReferenceEquals(other, this)) { + return true; + } + if (!object.Equals(Player, other.Player)) return false; + if (!object.Equals(GameAccountId, other.GameAccountId)) return false; + if (!object.Equals(AccountId, other.AccountId)) return false; + return true; + } + + public override int GetHashCode() { + int hash = 1; + if (player_ != null) hash ^= Player.GetHashCode(); + if (gameAccountId_ != null) hash ^= GameAccountId.GetHashCode(); + if (accountId_ != null) hash ^= AccountId.GetHashCode(); + return hash; + } + + public override string ToString() { + return pb::JsonFormatter.ToDiagnosticString(this); + } + + public void WriteTo(pb::CodedOutputStream output) { + if (player_ != null) { + output.WriteRawTag(10); + output.WriteMessage(Player); + } + if (gameAccountId_ != null) { + output.WriteRawTag(18); + output.WriteMessage(GameAccountId); + } + if (accountId_ != null) { + output.WriteRawTag(26); + output.WriteMessage(AccountId); + } + } + + public int CalculateSize() { + int size = 0; + if (player_ != null) { + size += 1 + pb::CodedOutputStream.ComputeMessageSize(Player); + } + if (gameAccountId_ != null) { + size += 1 + pb::CodedOutputStream.ComputeMessageSize(GameAccountId); + } + if (accountId_ != null) { + size += 1 + pb::CodedOutputStream.ComputeMessageSize(AccountId); + } + return size; + } + + public void MergeFrom(BlockedPlayerRemovedNotification other) { + if (other == null) { + return; + } + if (other.player_ != null) { + if (player_ == null) { + player_ = new Bgs.Protocol.UserManager.V1.BlockedPlayer(); + } + Player.MergeFrom(other.Player); + } + if (other.gameAccountId_ != null) { + if (gameAccountId_ == null) { + gameAccountId_ = new Bgs.Protocol.EntityId(); + } + GameAccountId.MergeFrom(other.GameAccountId); + } + if (other.accountId_ != null) { + if (accountId_ == null) { + accountId_ = new Bgs.Protocol.EntityId(); + } + AccountId.MergeFrom(other.AccountId); + } + } + + public void MergeFrom(pb::CodedInputStream input) { + uint tag; + while ((tag = input.ReadTag()) != 0) { + switch(tag) { + default: + input.SkipLastField(); + break; + case 10: { + if (player_ == null) { + player_ = new Bgs.Protocol.UserManager.V1.BlockedPlayer(); + } + input.ReadMessage(player_); + break; + } + case 18: { + if (gameAccountId_ == null) { + gameAccountId_ = new Bgs.Protocol.EntityId(); + } + input.ReadMessage(gameAccountId_); + break; + } + case 26: { + if (accountId_ == null) { + accountId_ = new Bgs.Protocol.EntityId(); + } + input.ReadMessage(accountId_); + break; + } + } + } + } + + } + + [System.Diagnostics.DebuggerNonUserCodeAttribute()] + public sealed partial class RecentPlayersAddedNotification : pb::IMessage { + private static readonly pb::MessageParser _parser = new pb::MessageParser(() => new RecentPlayersAddedNotification()); + public static pb::MessageParser Parser { get { return _parser; } } + + public static pbr::MessageDescriptor Descriptor { + get { return Bgs.Protocol.UserManager.V1.UserManagerServiceReflection.Descriptor.MessageTypes[11]; } + } + + pbr::MessageDescriptor pb::IMessage.Descriptor { + get { return Descriptor; } + } + + public RecentPlayersAddedNotification() { + OnConstruction(); + } + + partial void OnConstruction(); + + public RecentPlayersAddedNotification(RecentPlayersAddedNotification other) : this() { + player_ = other.player_.Clone(); + } + + public RecentPlayersAddedNotification Clone() { + return new RecentPlayersAddedNotification(this); + } + + /// Field number for the "player" field. + public const int PlayerFieldNumber = 1; + private static readonly pb::FieldCodec _repeated_player_codec + = pb::FieldCodec.ForMessage(10, Bgs.Protocol.UserManager.V1.RecentPlayer.Parser); + private readonly pbc::RepeatedField player_ = new pbc::RepeatedField(); + public pbc::RepeatedField Player { + get { return player_; } + } + + public override bool Equals(object other) { + return Equals(other as RecentPlayersAddedNotification); + } + + public bool Equals(RecentPlayersAddedNotification other) { + if (ReferenceEquals(other, null)) { + return false; + } + if (ReferenceEquals(other, this)) { + return true; + } + if(!player_.Equals(other.player_)) return false; + return true; + } + + public override int GetHashCode() { + int hash = 1; + hash ^= player_.GetHashCode(); + return hash; + } + + public override string ToString() { + return pb::JsonFormatter.ToDiagnosticString(this); + } + + public void WriteTo(pb::CodedOutputStream output) { + player_.WriteTo(output, _repeated_player_codec); + } + + public int CalculateSize() { + int size = 0; + size += player_.CalculateSize(_repeated_player_codec); + return size; + } + + public void MergeFrom(RecentPlayersAddedNotification other) { + if (other == null) { + return; + } + player_.Add(other.player_); + } + + public void MergeFrom(pb::CodedInputStream input) { + uint tag; + while ((tag = input.ReadTag()) != 0) { + switch(tag) { + default: + input.SkipLastField(); + break; + case 10: { + player_.AddEntriesFrom(input, _repeated_player_codec); + break; + } + } + } + } + + } + + [System.Diagnostics.DebuggerNonUserCodeAttribute()] + public sealed partial class RecentPlayersRemovedNotification : pb::IMessage { + private static readonly pb::MessageParser _parser = new pb::MessageParser(() => new RecentPlayersRemovedNotification()); + public static pb::MessageParser Parser { get { return _parser; } } + + public static pbr::MessageDescriptor Descriptor { + get { return Bgs.Protocol.UserManager.V1.UserManagerServiceReflection.Descriptor.MessageTypes[12]; } + } + + pbr::MessageDescriptor pb::IMessage.Descriptor { + get { return Descriptor; } + } + + public RecentPlayersRemovedNotification() { + OnConstruction(); + } + + partial void OnConstruction(); + + public RecentPlayersRemovedNotification(RecentPlayersRemovedNotification other) : this() { + player_ = other.player_.Clone(); + } + + public RecentPlayersRemovedNotification Clone() { + return new RecentPlayersRemovedNotification(this); + } + + /// Field number for the "player" field. + public const int PlayerFieldNumber = 1; + private static readonly pb::FieldCodec _repeated_player_codec + = pb::FieldCodec.ForMessage(10, Bgs.Protocol.UserManager.V1.RecentPlayer.Parser); + private readonly pbc::RepeatedField player_ = new pbc::RepeatedField(); + public pbc::RepeatedField Player { + get { return player_; } + } + + public override bool Equals(object other) { + return Equals(other as RecentPlayersRemovedNotification); + } + + public bool Equals(RecentPlayersRemovedNotification other) { + if (ReferenceEquals(other, null)) { + return false; + } + if (ReferenceEquals(other, this)) { + return true; + } + if(!player_.Equals(other.player_)) return false; + return true; + } + + public override int GetHashCode() { + int hash = 1; + hash ^= player_.GetHashCode(); + return hash; + } + + public override string ToString() { + return pb::JsonFormatter.ToDiagnosticString(this); + } + + public void WriteTo(pb::CodedOutputStream output) { + player_.WriteTo(output, _repeated_player_codec); + } + + public int CalculateSize() { + int size = 0; + size += player_.CalculateSize(_repeated_player_codec); + return size; + } + + public void MergeFrom(RecentPlayersRemovedNotification other) { + if (other == null) { + return; + } + player_.Add(other.player_); + } + + public void MergeFrom(pb::CodedInputStream input) { + uint tag; + while ((tag = input.ReadTag()) != 0) { + switch(tag) { + default: + input.SkipLastField(); + break; + case 10: { + player_.AddEntriesFrom(input, _repeated_player_codec); + break; + } + } + } + } + + } + + #endregion + +} + +#endregion Designer generated code diff --git a/Framework/Proto/UserManagerTypes.cs b/Framework/Proto/UserManagerTypes.cs new file mode 100644 index 000000000..74f9b9a42 --- /dev/null +++ b/Framework/Proto/UserManagerTypes.cs @@ -0,0 +1,471 @@ +// Generated by the protocol buffer compiler. DO NOT EDIT! +// source: bgs/low/pb/client/user_manager_types.proto +#pragma warning disable 1591, 0612, 3021 +#region Designer generated code + +using pb = Google.Protobuf; +using pbc = Google.Protobuf.Collections; +using pbr = Google.Protobuf.Reflection; +namespace Bgs.Protocol.UserManager.V1 +{ + + /// Holder for reflection information generated from bgs/low/pb/client/user_manager_types.proto + [System.Diagnostics.DebuggerNonUserCodeAttribute()] + public static partial class UserManagerTypesReflection { + + #region Descriptor + /// File descriptor for bgs/low/pb/client/user_manager_types.proto + public static pbr::FileDescriptor Descriptor { + get { return descriptor; } + } + private static pbr::FileDescriptor descriptor; + + static UserManagerTypesReflection() { + byte[] descriptorData = System.Convert.FromBase64String( + string.Concat( + "CipiZ3MvbG93L3BiL2NsaWVudC91c2VyX21hbmFnZXJfdHlwZXMucHJvdG8S", + "HGJncy5wcm90b2NvbC51c2VyX21hbmFnZXIudjEaJGJncy9sb3cvcGIvY2xp", + "ZW50L2VudGl0eV90eXBlcy5wcm90bxonYmdzL2xvdy9wYi9jbGllbnQvYXR0", + "cmlidXRlX3R5cGVzLnByb3RvIq4BCgxSZWNlbnRQbGF5ZXISKQoJZW50aXR5", + "X2lkGAEgASgLMhYuYmdzLnByb3RvY29sLkVudGl0eUlkEg8KB3Byb2dyYW0Y", + "AiABKAkSGAoQdGltZXN0YW1wX3BsYXllZBgDIAEoBhIrCgphdHRyaWJ1dGVz", + "GAQgAygLMhcuYmdzLnByb3RvY29sLkF0dHJpYnV0ZRIKCgJpZBgFIAEoBxIP", + "Cgdjb3VudGVyGAYgASgHIm8KDUJsb2NrZWRQbGF5ZXISKgoKYWNjb3VudF9p", + "ZBgBIAEoCzIWLmJncy5wcm90b2NvbC5FbnRpdHlJZBIMCgRuYW1lGAIgASgJ", + "EhAKBHJvbGUYAyADKA1CAhABEhIKCnByaXZpbGVnZXMYBCABKARCAkgCYgZw", + "cm90bzM=")); + descriptor = pbr::FileDescriptor.FromGeneratedCode(descriptorData, + new pbr::FileDescriptor[] { Bgs.Protocol.EntityTypesReflection.Descriptor, Bgs.Protocol.AttributeTypesReflection.Descriptor, }, + new pbr::GeneratedClrTypeInfo(null, new pbr::GeneratedClrTypeInfo[] { + new pbr::GeneratedClrTypeInfo(typeof(Bgs.Protocol.UserManager.V1.RecentPlayer), Bgs.Protocol.UserManager.V1.RecentPlayer.Parser, new[]{ "EntityId", "Program", "TimestampPlayed", "Attributes", "Id", "Counter" }, null, null, null), + new pbr::GeneratedClrTypeInfo(typeof(Bgs.Protocol.UserManager.V1.BlockedPlayer), Bgs.Protocol.UserManager.V1.BlockedPlayer.Parser, new[]{ "AccountId", "Name", "Role", "Privileges" }, null, null, null) + })); + } + #endregion + + } + #region Messages + [System.Diagnostics.DebuggerNonUserCodeAttribute()] + public sealed partial class RecentPlayer : pb::IMessage { + private static readonly pb::MessageParser _parser = new pb::MessageParser(() => new RecentPlayer()); + public static pb::MessageParser Parser { get { return _parser; } } + + public static pbr::MessageDescriptor Descriptor { + get { return Bgs.Protocol.UserManager.V1.UserManagerTypesReflection.Descriptor.MessageTypes[0]; } + } + + pbr::MessageDescriptor pb::IMessage.Descriptor { + get { return Descriptor; } + } + + public RecentPlayer() { + OnConstruction(); + } + + partial void OnConstruction(); + + public RecentPlayer(RecentPlayer other) : this() { + EntityId = other.entityId_ != null ? other.EntityId.Clone() : null; + program_ = other.program_; + timestampPlayed_ = other.timestampPlayed_; + attributes_ = other.attributes_.Clone(); + id_ = other.id_; + counter_ = other.counter_; + } + + public RecentPlayer Clone() { + return new RecentPlayer(this); + } + + /// Field number for the "entity_id" field. + public const int EntityIdFieldNumber = 1; + private Bgs.Protocol.EntityId entityId_; + public Bgs.Protocol.EntityId EntityId { + get { return entityId_; } + set { + entityId_ = value; + } + } + + /// Field number for the "program" field. + public const int ProgramFieldNumber = 2; + private string program_ = ""; + public string Program { + get { return program_; } + set { + program_ = pb::ProtoPreconditions.CheckNotNull(value, "value"); + } + } + + /// Field number for the "timestamp_played" field. + public const int TimestampPlayedFieldNumber = 3; + private ulong timestampPlayed_; + public ulong TimestampPlayed { + get { return timestampPlayed_; } + set { + timestampPlayed_ = value; + } + } + + /// Field number for the "attributes" field. + public const int AttributesFieldNumber = 4; + private static readonly pb::FieldCodec _repeated_attributes_codec + = pb::FieldCodec.ForMessage(34, Bgs.Protocol.Attribute.Parser); + private readonly pbc::RepeatedField attributes_ = new pbc::RepeatedField(); + public pbc::RepeatedField Attributes { + get { return attributes_; } + } + + /// Field number for the "id" field. + public const int IdFieldNumber = 5; + private uint id_; + public uint Id { + get { return id_; } + set { + id_ = value; + } + } + + /// Field number for the "counter" field. + public const int CounterFieldNumber = 6; + private uint counter_; + public uint Counter { + get { return counter_; } + set { + counter_ = value; + } + } + + public override bool Equals(object other) { + return Equals(other as RecentPlayer); + } + + public bool Equals(RecentPlayer other) { + if (ReferenceEquals(other, null)) { + return false; + } + if (ReferenceEquals(other, this)) { + return true; + } + if (!object.Equals(EntityId, other.EntityId)) return false; + if (Program != other.Program) return false; + if (TimestampPlayed != other.TimestampPlayed) return false; + if(!attributes_.Equals(other.attributes_)) return false; + if (Id != other.Id) return false; + if (Counter != other.Counter) return false; + return true; + } + + public override int GetHashCode() { + int hash = 1; + if (entityId_ != null) hash ^= EntityId.GetHashCode(); + if (Program.Length != 0) hash ^= Program.GetHashCode(); + if (TimestampPlayed != 0UL) hash ^= TimestampPlayed.GetHashCode(); + hash ^= attributes_.GetHashCode(); + if (Id != 0) hash ^= Id.GetHashCode(); + if (Counter != 0) hash ^= Counter.GetHashCode(); + return hash; + } + + public override string ToString() { + return pb::JsonFormatter.ToDiagnosticString(this); + } + + public void WriteTo(pb::CodedOutputStream output) { + if (entityId_ != null) { + output.WriteRawTag(10); + output.WriteMessage(EntityId); + } + if (Program.Length != 0) { + output.WriteRawTag(18); + output.WriteString(Program); + } + if (TimestampPlayed != 0UL) { + output.WriteRawTag(25); + output.WriteFixed64(TimestampPlayed); + } + attributes_.WriteTo(output, _repeated_attributes_codec); + if (Id != 0) { + output.WriteRawTag(45); + output.WriteFixed32(Id); + } + if (Counter != 0) { + output.WriteRawTag(53); + output.WriteFixed32(Counter); + } + } + + public int CalculateSize() { + int size = 0; + if (entityId_ != null) { + size += 1 + pb::CodedOutputStream.ComputeMessageSize(EntityId); + } + if (Program.Length != 0) { + size += 1 + pb::CodedOutputStream.ComputeStringSize(Program); + } + if (TimestampPlayed != 0UL) { + size += 1 + 8; + } + size += attributes_.CalculateSize(_repeated_attributes_codec); + if (Id != 0) { + size += 1 + 4; + } + if (Counter != 0) { + size += 1 + 4; + } + return size; + } + + public void MergeFrom(RecentPlayer other) { + if (other == null) { + return; + } + if (other.entityId_ != null) { + if (entityId_ == null) { + entityId_ = new Bgs.Protocol.EntityId(); + } + EntityId.MergeFrom(other.EntityId); + } + if (other.Program.Length != 0) { + Program = other.Program; + } + if (other.TimestampPlayed != 0UL) { + TimestampPlayed = other.TimestampPlayed; + } + attributes_.Add(other.attributes_); + if (other.Id != 0) { + Id = other.Id; + } + if (other.Counter != 0) { + Counter = other.Counter; + } + } + + public void MergeFrom(pb::CodedInputStream input) { + uint tag; + while ((tag = input.ReadTag()) != 0) { + switch(tag) { + default: + input.SkipLastField(); + break; + case 10: { + if (entityId_ == null) { + entityId_ = new Bgs.Protocol.EntityId(); + } + input.ReadMessage(entityId_); + break; + } + case 18: { + Program = input.ReadString(); + break; + } + case 25: { + TimestampPlayed = input.ReadFixed64(); + break; + } + case 34: { + attributes_.AddEntriesFrom(input, _repeated_attributes_codec); + break; + } + case 45: { + Id = input.ReadFixed32(); + break; + } + case 53: { + Counter = input.ReadFixed32(); + break; + } + } + } + } + + } + + [System.Diagnostics.DebuggerNonUserCodeAttribute()] + public sealed partial class BlockedPlayer : pb::IMessage { + private static readonly pb::MessageParser _parser = new pb::MessageParser(() => new BlockedPlayer()); + public static pb::MessageParser Parser { get { return _parser; } } + + public static pbr::MessageDescriptor Descriptor { + get { return Bgs.Protocol.UserManager.V1.UserManagerTypesReflection.Descriptor.MessageTypes[1]; } + } + + pbr::MessageDescriptor pb::IMessage.Descriptor { + get { return Descriptor; } + } + + public BlockedPlayer() { + OnConstruction(); + } + + partial void OnConstruction(); + + public BlockedPlayer(BlockedPlayer other) : this() { + AccountId = other.accountId_ != null ? other.AccountId.Clone() : null; + name_ = other.name_; + role_ = other.role_.Clone(); + privileges_ = other.privileges_; + } + + public BlockedPlayer Clone() { + return new BlockedPlayer(this); + } + + /// Field number for the "account_id" field. + public const int AccountIdFieldNumber = 1; + private Bgs.Protocol.EntityId accountId_; + public Bgs.Protocol.EntityId AccountId { + get { return accountId_; } + set { + accountId_ = value; + } + } + + /// Field number for the "name" field. + public const int NameFieldNumber = 2; + private string name_ = ""; + public string Name { + get { return name_; } + set { + name_ = pb::ProtoPreconditions.CheckNotNull(value, "value"); + } + } + + /// Field number for the "role" field. + public const int RoleFieldNumber = 3; + private static readonly pb::FieldCodec _repeated_role_codec + = pb::FieldCodec.ForUInt32(26); + private readonly pbc::RepeatedField role_ = new pbc::RepeatedField(); + public pbc::RepeatedField Role { + get { return role_; } + } + + /// Field number for the "privileges" field. + public const int PrivilegesFieldNumber = 4; + private ulong privileges_; + public ulong Privileges { + get { return privileges_; } + set { + privileges_ = value; + } + } + + public override bool Equals(object other) { + return Equals(other as BlockedPlayer); + } + + public bool Equals(BlockedPlayer other) { + if (ReferenceEquals(other, null)) { + return false; + } + if (ReferenceEquals(other, this)) { + return true; + } + if (!object.Equals(AccountId, other.AccountId)) return false; + if (Name != other.Name) return false; + if(!role_.Equals(other.role_)) return false; + if (Privileges != other.Privileges) return false; + return true; + } + + public override int GetHashCode() { + int hash = 1; + if (accountId_ != null) hash ^= AccountId.GetHashCode(); + if (Name.Length != 0) hash ^= Name.GetHashCode(); + hash ^= role_.GetHashCode(); + if (Privileges != 0UL) hash ^= Privileges.GetHashCode(); + return hash; + } + + public override string ToString() { + return pb::JsonFormatter.ToDiagnosticString(this); + } + + public void WriteTo(pb::CodedOutputStream output) { + if (accountId_ != null) { + output.WriteRawTag(10); + output.WriteMessage(AccountId); + } + if (Name.Length != 0) { + output.WriteRawTag(18); + output.WriteString(Name); + } + role_.WriteTo(output, _repeated_role_codec); + if (Privileges != 0UL) { + output.WriteRawTag(32); + output.WriteUInt64(Privileges); + } + } + + public int CalculateSize() { + int size = 0; + if (accountId_ != null) { + size += 1 + pb::CodedOutputStream.ComputeMessageSize(AccountId); + } + if (Name.Length != 0) { + size += 1 + pb::CodedOutputStream.ComputeStringSize(Name); + } + size += role_.CalculateSize(_repeated_role_codec); + if (Privileges != 0UL) { + size += 1 + pb::CodedOutputStream.ComputeUInt64Size(Privileges); + } + return size; + } + + public void MergeFrom(BlockedPlayer other) { + if (other == null) { + return; + } + if (other.accountId_ != null) { + if (accountId_ == null) { + accountId_ = new Bgs.Protocol.EntityId(); + } + AccountId.MergeFrom(other.AccountId); + } + if (other.Name.Length != 0) { + Name = other.Name; + } + role_.Add(other.role_); + if (other.Privileges != 0UL) { + Privileges = other.Privileges; + } + } + + public void MergeFrom(pb::CodedInputStream input) { + uint tag; + while ((tag = input.ReadTag()) != 0) { + switch(tag) { + default: + input.SkipLastField(); + break; + case 10: { + if (accountId_ == null) { + accountId_ = new Bgs.Protocol.EntityId(); + } + input.ReadMessage(accountId_); + break; + } + case 18: { + Name = input.ReadString(); + break; + } + case 26: + case 24: { + role_.AddEntriesFrom(input, _repeated_role_codec); + break; + } + case 32: { + Privileges = input.ReadUInt64(); + break; + } + } + } + } + + } + + #endregion + +} + +#endregion Designer generated code diff --git a/Framework/Realm/Realm.cs b/Framework/Realm/Realm.cs new file mode 100644 index 000000000..c09327f16 --- /dev/null +++ b/Framework/Realm/Realm.cs @@ -0,0 +1,160 @@ +/* + * Copyright (C) 2012-2017 CypherCore + * + * 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, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +using Framework.Constants; +using System; +using System.Net; +using System.Net.Sockets; + +public class Realm : IEquatable +{ + public void SetName(string name) + { + Name = name; + NormalizedName = name; + NormalizedName.Replace(" ", ""); + } + + public IPEndPoint GetAddressForClient(IPAddress clientAddr) + { + IPAddress realmIp; + + // Attempt to send best address for client + if (IPAddress.IsLoopback(clientAddr)) + { + // Try guessing if realm is also connected locally + if (IPAddress.IsLoopback(LocalAddress) || IPAddress.IsLoopback(ExternalAddress)) + realmIp = clientAddr; + else + { + // Assume that user connecting from the machine that authserver is located on + // has all realms available in his local network + realmIp = LocalAddress; + } + } + else + { + if (clientAddr.AddressFamily == AddressFamily.InterNetwork && clientAddr.GetNetworkAddress(LocalSubnetMask).Equals(LocalAddress.GetNetworkAddress(LocalSubnetMask))) + realmIp = LocalAddress; + else + realmIp = ExternalAddress; + } + + IPEndPoint endpoint = new IPEndPoint(realmIp, Port); + + // Return external IP + return endpoint; + } + + public uint GetConfigId() + { + return ConfigIdByType[Type]; + } + + uint[] ConfigIdByType = + { + 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14 + }; + + public override bool Equals(object obj) + { + return obj != null && obj is Realm && Equals((Realm)obj); + } + + public bool Equals(Realm other) + { + return other.ExternalAddress.Equals(ExternalAddress) + && other.LocalAddress.Equals(LocalAddress) + && other.LocalSubnetMask.Equals(LocalSubnetMask) + && other.Port == Port + && other.Name == Name + && other.Type == Type + && other.Flags == Flags + && other.Timezone == Timezone + && other.AllowedSecurityLevel == AllowedSecurityLevel + && other.PopulationLevel == PopulationLevel; + } + + public override int GetHashCode() + { + return new { ExternalAddress, LocalAddress, LocalSubnetMask, Port, Name, Type, Flags, Timezone, AllowedSecurityLevel, PopulationLevel }.GetHashCode(); + } + + public RealmHandle Id; + public uint Build; + public IPAddress ExternalAddress; + public IPAddress LocalAddress; + public IPAddress LocalSubnetMask; + public ushort Port; + public string Name; + public string NormalizedName; + public byte Type; + public RealmFlags Flags; + public byte Timezone; + public AccountTypes AllowedSecurityLevel; + public float PopulationLevel; +} + +public struct RealmHandle : IEquatable +{ + public RealmHandle(byte region, byte battlegroup, uint index) + { + Region = region; + Site = battlegroup; + Realm = index; + } + public RealmHandle(uint realmAddress) + { + Region = (byte)((realmAddress >> 24) & 0xFF); + Site = (byte)((realmAddress >> 16) & 0xFF); + Realm = realmAddress & 0xFFFF; + } + + public uint GetAddress() + { + return (uint)((Region << 24) | (Site << 16) | (ushort)Realm); + } + public string GetAddressString() + { + return string.Format("{0}-{1}-{2}", Region, Site, Realm); + } + + public string GetSubRegionAddress() + { + return string.Format("{0}-{1}-0", Region, Site); + } + + public override bool Equals(object obj) + { + return obj != null && obj is RealmHandle && Equals((RealmHandle)obj); + } + + public bool Equals(RealmHandle other) + { + return other.Realm == Realm; + } + + public override int GetHashCode() + { + return new { Site, Region, Realm }.GetHashCode(); + } + + public byte Region; + public byte Site; + public uint Realm; // primary key in `realmlist` table +} + diff --git a/Framework/Realm/RealmManager.cs b/Framework/Realm/RealmManager.cs new file mode 100644 index 000000000..9e017ecb9 --- /dev/null +++ b/Framework/Realm/RealmManager.cs @@ -0,0 +1,347 @@ +/* + * Copyright (C) 2012-2017 CypherCore + * + * 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, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +using Framework.Constants; +using Framework.Cryptography; +using Framework.Database; +using Framework.Rest; +using Framework.Serialization; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Net; +using System.Timers; + +public class RealmManager : Singleton +{ + RealmManager() { } + + public void Initialize(int updateInterval) + { + _updateTimer = new Timer(TimeSpan.FromSeconds(updateInterval).TotalMilliseconds); + _updateTimer.Elapsed += UpdateRealms; + + UpdateRealms(null, null); + + _updateTimer.Start(); + } + + public void Close() + { + _updateTimer.Close(); + } + + void UpdateRealm(Realm realm) + { + var oldRealm = _realms.LookupByKey(realm.Id); + if (oldRealm == null) + { + _realms[realm.Id] = realm; + return; + } + + if (oldRealm == realm) + return; + + _realms[realm.Id] = realm; + } + + void UpdateRealms(object source, ElapsedEventArgs e) + { + Log.outInfo(LogFilter.Realmlist, "Updating Realm List..."); + + PreparedStatement stmt = DB.Login.GetPreparedStatement(LoginStatements.SEL_REALMLIST); + SQLResult result = DB.Login.Query(stmt); + + Dictionary existingRealms = new Dictionary(); + foreach (var p in _realms.ToList()) + existingRealms[p.Key] = p.Value.Name; + + _realms.Clear(); + + // Circle through results and add them to the realm map + if (!result.IsEmpty()) + { + do + { + var realm = new Realm(); + uint realmId = result.Read(0); + realm.Name = result.Read(1); + realm.ExternalAddress = IPAddress.Parse(result.Read(2)); + realm.LocalAddress = IPAddress.Parse(result.Read(3)); + realm.LocalSubnetMask = IPAddress.Parse(result.Read(4)); + realm.Port = result.Read(5); + RealmType realmType = (RealmType)result.Read(6); + if (realmType == RealmType.FFAPVP) + realmType = RealmType.PVP; + if (realmType >= RealmType.MaxType) + realmType = RealmType.Normal; + + realm.Type = (byte)realmType; + realm.Flags = (RealmFlags)result.Read(7); + realm.Timezone = result.Read(8); + AccountTypes allowedSecurityLevel = (AccountTypes)result.Read(9); + realm.AllowedSecurityLevel = (allowedSecurityLevel <= AccountTypes.Administrator ? allowedSecurityLevel : AccountTypes.Administrator); + realm.PopulationLevel = result.Read(10); + realm.Build = result.Read(11); + byte region = result.Read(12); + byte battlegroup = result.Read(13); + + realm.Id = new RealmHandle(region, battlegroup, realmId); + + UpdateRealm(realm); + + var subRegion = new RealmHandle(region, battlegroup, 0).GetAddressString(); + if (!_subRegions.Contains(subRegion)) + _subRegions.Add(subRegion); + + if (!existingRealms.ContainsKey(realm.Id)) + Log.outInfo(LogFilter.Realmlist, "Added realm \"{0}\" at {1}:{2}.", realm.Name, realm.ExternalAddress.ToString(), realm.Port); + else + Log.outDebug(LogFilter.Realmlist, "Updating realm \"{0}\" at {1}:{2}.", realm.Name, realm.ExternalAddress.ToString(), realm.Port); + + existingRealms.Remove(realm.Id); + } + while (result.NextRow()); + } + + foreach (var pair in existingRealms) + Log.outInfo(LogFilter.Realmlist, "Removed realm \"{0}\".", pair.Value); + } + + public Realm GetRealm(RealmHandle id) + { + return _realms.LookupByKey(id); + } + + RealmBuildInfo GetBuildInfo(uint build) + { + // List of client builds for verbose version info in realmlist packet + RealmBuildInfo[] ClientBuilds = + { + new RealmBuildInfo(21355, 6, 2, 4, ' '), + new RealmBuildInfo( 20726, 6, 2, 3, ' '), + new RealmBuildInfo(20574, 6, 2, 2, 'a'), + new RealmBuildInfo( 20490, 6, 2, 2, 'a'), + new RealmBuildInfo( 15595, 4, 3, 4, ' '), + new RealmBuildInfo( 14545, 4, 2, 2, ' '), + new RealmBuildInfo( 13623, 4, 0, 6, 'a'), + new RealmBuildInfo( 13930, 3, 3, 5, 'a'), // 3.3.5a China Mainland build + new RealmBuildInfo( 12340, 3, 3, 5, 'a'), + new RealmBuildInfo( 11723, 3, 3, 3, 'a'), + new RealmBuildInfo( 11403, 3, 3, 2, ' '), + new RealmBuildInfo( 11159, 3, 3, 0, 'a'), + new RealmBuildInfo( 10505, 3, 2, 2, 'a'), + new RealmBuildInfo( 9947, 3, 1, 3, ' '), + new RealmBuildInfo( 8606, 2, 4, 3, ' '), + new RealmBuildInfo( 6141, 1, 12, 3, ' '), + new RealmBuildInfo( 6005, 1, 12, 2, ' '), + new RealmBuildInfo( 5875, 1, 12, 1, ' '), + }; + + foreach (var clientBuild in ClientBuilds) + if (clientBuild.Build == build) + return clientBuild; + + return null; + } + + public void WriteSubRegions(Bgs.Protocol.GameUtilities.V1.GetAllValuesForAttributeResponse response) + { + foreach (string subRegion in GetSubRegions()) + { + var variant = new Bgs.Protocol.Variant(); + variant.StringValue = subRegion; + response.AttributeValue.Add(variant); + } + } + + public byte[] GetRealmEntryJSON(RealmHandle id, uint build) + { + byte[] compressed = new byte[0]; + Realm realm = GetRealm(id); + if (realm != null) + { + if (!realm.Flags.HasAnyFlag(RealmFlags.Offline) && realm.Build == build) + { + var realmEntry = new RealmEntry(); + realmEntry.WowRealmAddress = (int)realm.Id.GetAddress(); + realmEntry.CfgTimezonesID = 1; + realmEntry.PopulationState = Math.Max((int)realm.PopulationLevel, 1); + realmEntry.CfgCategoriesID = realm.Timezone; + + ClientVersion version = new ClientVersion(); + RealmBuildInfo buildInfo = GetBuildInfo(realm.Build); + if (buildInfo != null) + { + version.Major = (int)buildInfo.MajorVersion; + version.Minor = (int)buildInfo.MinorVersion; + version.Revision = (int)buildInfo.BugfixVersion; + version.Build = (int)buildInfo.Build; + } + else + { + version.Major = 6; + version.Minor = 2; + version.Revision = 4; + version.Build = (int)realm.Build; + } + realmEntry.Version = version; + + realmEntry.CfgRealmsID = (int)realm.Id.Realm; + realmEntry.Flags = (int)realm.Flags; + realmEntry.Name = realm.Name; + realmEntry.CfgConfigsID = (int)realm.GetConfigId(); + realmEntry.CfgLanguagesID = 1; + + compressed = Json.Deflate("JamJSONRealmEntry", realmEntry); + } + } + + return compressed; + } + + public byte[] GetRealmList(uint build, string subRegion) + { + var realmList = new RealmListUpdates(); + foreach (var realm in _realms.ToList()) + { + if (realm.Value.Id.GetSubRegionAddress() != subRegion) + continue; + + RealmFlags flag = realm.Value.Flags; + if (realm.Value.Build != build) + flag |= RealmFlags.VersionMismatch; + + RealmListUpdate realmListUpdate = new RealmListUpdate(); + realmListUpdate.Update.WowRealmAddress = (int)realm.Value.Id.GetAddress(); + realmListUpdate.Update.CfgTimezonesID = 1; + realmListUpdate.Update.PopulationState = (realm.Value.Flags.HasAnyFlag(RealmFlags.Offline) ? 0 : Math.Max((int)realm.Value.PopulationLevel, 1)); + realmListUpdate.Update.CfgCategoriesID = realm.Value.Timezone; + + RealmBuildInfo buildInfo = GetBuildInfo(realm.Value.Build); + if (buildInfo != null) + { + realmListUpdate.Update.Version.Major = (int)buildInfo.MajorVersion; + realmListUpdate.Update.Version.Minor = (int)buildInfo.MinorVersion; + realmListUpdate.Update.Version.Revision = (int)buildInfo.BugfixVersion; + realmListUpdate.Update.Version.Build = (int)buildInfo.Build; + } + else + { + realmListUpdate.Update.Version.Major = 7; + realmListUpdate.Update.Version.Minor = 1; + realmListUpdate.Update.Version.Revision = 0; + realmListUpdate.Update.Version.Build = (int)realm.Value.Build; + } + + realmListUpdate.Update.CfgRealmsID = (int)realm.Value.Id.Realm; + realmListUpdate.Update.Flags = (int)flag; + realmListUpdate.Update.Name = realm.Value.Name; + realmListUpdate.Update.CfgConfigsID = (int)realm.Value.GetConfigId(); + realmListUpdate.Update.CfgLanguagesID = 1; + + realmListUpdate.Deleting = false; + + realmList.Updates.Add(realmListUpdate); + } + + return Json.Deflate("JSONRealmListUpdates", realmList); + } + + public BattlenetRpcErrorCode JoinRealm(uint realmAddress, uint build, IPAddress clientAddress, Array clientSecret, LocaleConstant locale, string os, string accountName, Bgs.Protocol.GameUtilities.V1.ClientResponse response) + { + Realm realm = GetRealm(new RealmHandle(realmAddress)); + if (realm != null) + { + if (realm.Flags.HasAnyFlag(RealmFlags.Offline) || realm.Build != build) + return BattlenetRpcErrorCode.UserServerNotPermittedOnRealm; + + RealmListServerIPAddresses serverAddresses = new RealmListServerIPAddresses(); + AddressFamily addressFamily = new AddressFamily(); + addressFamily.Id = 1; + + var address = new Address(); + address.Ip = realm.GetAddressForClient(clientAddress).Address.ToString(); + address.Port = realm.Port; + addressFamily.Addresses.Add(address); + serverAddresses.Families.Add(addressFamily); + + byte[] compressed = Json.Deflate("JSONRealmListServerIPAddresses", serverAddresses); + + byte[] serverSecret = new byte[0].GenerateRandomKey(32); + + Sha256 sha256 = new Sha256(); + sha256.Process(clientSecret.ToArray(), clientSecret.Count); + sha256.Finish(serverSecret, 32); + + PreparedStatement stmt = DB.Login.GetPreparedStatement(LoginStatements.UPD_BNET_GAME_ACCOUNT_LOGIN_INFO); + stmt.AddValue(0, sha256.Digest.ToHexString()); + stmt.AddValue(1, clientAddress.ToString()); + stmt.AddValue(2, locale); + stmt.AddValue(3, os); + stmt.AddValue(4, accountName); + DB.Login.Execute(stmt); + + Bgs.Protocol.Attribute attribute = new Bgs.Protocol.Attribute(); + attribute.Name = "Param_RealmJoinTicket"; + attribute.Value = new Bgs.Protocol.Variant(); + attribute.Value.BlobValue = Google.Protobuf.ByteString.CopyFrom(accountName, System.Text.Encoding.UTF8); + response.Attribute.Add(attribute); + + attribute = new Bgs.Protocol.Attribute(); + attribute.Name = "Param_ServerAddresses"; + attribute.Value = new Bgs.Protocol.Variant(); + attribute.Value.BlobValue = Google.Protobuf.ByteString.CopyFrom(compressed); + response.Attribute.Add(attribute); + + attribute = new Bgs.Protocol.Attribute(); + attribute.Name = "Param_JoinSecret"; + attribute.Value = new Bgs.Protocol.Variant(); + attribute.Value.BlobValue = Google.Protobuf.ByteString.CopyFrom(serverSecret); + response.Attribute.Add(attribute); + return BattlenetRpcErrorCode.Ok; + } + + return BattlenetRpcErrorCode.UtilServerUnknownRealm; + } + + public List GetRealms() { return _realms.Values.ToList(); } + List GetSubRegions() { return _subRegions; } + + Dictionary _realms = new Dictionary(); + List _subRegions = new List(); + Timer _updateTimer; +} + +class RealmBuildInfo +{ + public RealmBuildInfo(uint build, uint majorVersion, uint minorVersion, uint bugfixVersion, char hotfixVersion = ' ') + { + Build = build; + MajorVersion = majorVersion; + MinorVersion = minorVersion; + BugfixVersion = bugfixVersion; + HotfixVersion = hotfixVersion; + } + + public uint Build; + public uint MajorVersion; + public uint MinorVersion; + public uint BugfixVersion; + public char HotfixVersion; +} diff --git a/Framework/RecastDetour/Detour/DetourCommon.cs b/Framework/RecastDetour/Detour/DetourCommon.cs new file mode 100644 index 000000000..adddad05d --- /dev/null +++ b/Framework/RecastDetour/Detour/DetourCommon.cs @@ -0,0 +1,989 @@ +using System; + +public static partial class Detour +{ + + /** + @defgroup detour Detour + + Members in this module are used to create, manipulate, and query navigation + meshes. + + @note This is a summary list of members. Use the index or search + feature to find minor members. + */ + + /// Derives the closest point on a triangle from the specified reference point. + /// @param[out] closest The closest point on the triangle. + /// @param[in] p The reference point from which to test. [(x, y, z)] + /// @param[in] a Vertex A of triangle ABC. [(x, y, z)] + /// @param[in] b Vertex B of triangle ABC. [(x, y, z)] + /// @param[in] c Vertex C of triangle ABC. [(x, y, z)] + public static void dtClosestPtPointTriangle(float[] closest, float[] p, float[] a, float[] b, float[] c) + { + // Check if P in vertex region outside A + float[] ab = new float[3];//, ac[3], ap[3]; + float[] ac = new float[3]; + float[] ap = new float[3]; + dtVsub(ab, b, a); + dtVsub(ac, c, a); + dtVsub(ap, p, a); + float d1 = dtVdot(ab, ap); + float d2 = dtVdot(ac, ap); + if (d1 <= 0.0f && d2 <= 0.0f) + { + // barycentric coordinates (1,0,0) + dtVcopy(closest, a); + return; + } + + // Check if P in vertex region outside B + float[] bp = new float[3]; + dtVsub(bp, p, b); + float d3 = dtVdot(ab, bp); + float d4 = dtVdot(ac, bp); + if (d3 >= 0.0f && d4 <= d3) + { + // barycentric coordinates (0,1,0) + dtVcopy(closest, b); + return; + } + + // Check if P in edge region of AB, if so return projection of P onto AB + float vc = d1 * d4 - d3 * d2; + if (vc <= 0.0f && d1 >= 0.0f && d3 <= 0.0f) + { + // barycentric coordinates (1-v,v,0) + float _v = d1 / (d1 - d3); + closest[0] = a[0] + _v * ab[0]; + closest[1] = a[1] + _v * ab[1]; + closest[2] = a[2] + _v * ab[2]; + return; + } + + // Check if P in vertex region outside C + float[] cp = new float[3]; + dtVsub(cp, p, c); + float d5 = dtVdot(ab, cp); + float d6 = dtVdot(ac, cp); + if (d6 >= 0.0f && d5 <= d6) + { + // barycentric coordinates (0,0,1) + dtVcopy(closest, c); + return; + } + + // Check if P in edge region of AC, if so return projection of P onto AC + float vb = d5 * d2 - d1 * d6; + if (vb <= 0.0f && d2 >= 0.0f && d6 <= 0.0f) + { + // barycentric coordinates (1-w,0,w) + float _w = d2 / (d2 - d6); + closest[0] = a[0] + _w * ac[0]; + closest[1] = a[1] + _w * ac[1]; + closest[2] = a[2] + _w * ac[2]; + return; + } + + // Check if P in edge region of BC, if so return projection of P onto BC + float va = d3 * d6 - d5 * d4; + if (va <= 0.0f && (d4 - d3) >= 0.0f && (d5 - d6) >= 0.0f) + { + // barycentric coordinates (0,1-w,w) + float _w = (d4 - d3) / ((d4 - d3) + (d5 - d6)); + closest[0] = b[0] + _w * (c[0] - b[0]); + closest[1] = b[1] + _w * (c[1] - b[1]); + closest[2] = b[2] + _w * (c[2] - b[2]); + return; + } + + // P inside face region. Compute Q through its barycentric coordinates (u,v,w) + float denom = 1.0f / (va + vb + vc); + float v = vb * denom; + float w = vc * denom; + closest[0] = a[0] + ab[0] * v + ac[0] * w; + closest[1] = a[1] + ab[1] * v + ac[1] * w; + closest[2] = a[2] + ab[2] * v + ac[2] * w; + } + + public static bool dtIntersectSegmentPoly2D(float[] p0, float[] p1, float[] verts, int nverts, ref float tmin, ref float tmax, ref int segMin, ref int segMax) + { + const float EPS = 0.00000001f; + + tmin = 0; + tmax = 1; + segMin = -1; + segMax = -1; + + float[] dir = new float[3]; + dtVsub(dir, p1, p0); + + for (int i = 0, j = nverts - 1; i < nverts; j = i++) + { + float[] edge = new float[3]; + float[] diff = new float[3]; + dtVsub(edge, 0, verts, i * 3, verts, j * 3); + dtVsub(diff, 0, p0, 0, verts, j * 3); + float n = dtVperp2D(edge, diff); + float d = dtVperp2D(dir, edge); + if (Math.Abs(d) < EPS) + { + // S is nearly parallel to this edge + if (n < 0) + return false; + else + continue; + } + float t = n / d; + if (d < 0) + { + // segment S is entering across this edge + if (t > tmin) + { + tmin = t; + segMin = j; + // S enters after leaving polygon + if (tmin > tmax) + return false; + } + } + else + { + // segment S is leaving across this edge + if (t < tmax) + { + tmax = t; + segMax = j; + // S leaves before entering polygon + if (tmax < tmin) + return false; + } + } + } + + return true; + } + + public static float dtDistancePtSegSqr2D(float[] pt, int ptStart, float[] p, int pStart, float[] q, int qStart, ref float t) + { + float pqx = q[qStart + 0] - p[pStart + 0]; + float pqz = q[qStart + 2] - p[pStart + 2]; + float dx = pt[ptStart + 0] - p[pStart + 0]; + float dz = pt[ptStart + 2] - p[pStart + 2]; + float d = pqx * pqx + pqz * pqz; + t = pqx * dx + pqz * dz; + if (d > 0) t /= d; + if (t < 0) t = 0; + else if (t > 1) t = 1; + dx = p[pStart + 0] + t * pqx - pt[ptStart + 0]; + dz = p[pStart + 2] + t * pqz - pt[ptStart + 2]; + return dx * dx + dz * dz; + } + + /// Derives the centroid of a convex polygon. + /// @param[out] tc The centroid of the polgyon. [(x, y, z)] + /// @param[in] idx The polygon indices. [(vertIndex) * @p nidx] + /// @param[in] nidx The number of indices in the polygon. [Limit: >= 3] + /// @param[in] verts The polygon vertices. [(x, y, z) * vertCount] + public static void dtCalcPolyCenter(float[] tc, ushort[] idx, int nidx, float[] verts) + { + tc[0] = 0.0f; + tc[1] = 0.0f; + tc[2] = 0.0f; + for (int j = 0; j < nidx; ++j) + { + int vIndex = idx[j] * 3; + tc[0] += verts[vIndex + 0]; + tc[1] += verts[vIndex + 1]; + tc[2] += verts[vIndex + 2]; + } + float s = 1.0f / nidx; + tc[0] *= s; + tc[1] *= s; + tc[2] *= s; + } + + /// Derives the y-axis height of the closest point on the triangle from the specified reference point. + /// @param[in] p The reference point from which to test. [(x, y, z)] + /// @param[in] a Vertex A of triangle ABC. [(x, y, z)] + /// @param[in] b Vertex B of triangle ABC. [(x, y, z)] + /// @param[in] c Vertex C of triangle ABC. [(x, y, z)] + /// @param[out] h The resulting height. + public static bool dtClosestHeightPointTriangle(float[] p, int pStart, float[] a, int aStart, float[] b, int bStart, float[] c, int cStart, ref float h) + { + float[] v0 = new float[3]; + float[] v1 = new float[3]; + float[] v2 = new float[3]; + dtVsub(v0, 0, c, cStart, a, aStart); + dtVsub(v1, 0, b, bStart, a, aStart); + dtVsub(v2, 0, p, pStart, a, aStart); + + float dot00 = dtVdot2D(v0, v0); + float dot01 = dtVdot2D(v0, v1); + float dot02 = dtVdot2D(v0, v2); + float dot11 = dtVdot2D(v1, v1); + float dot12 = dtVdot2D(v1, v2); + + // Compute barycentric coordinates + float invDenom = 1.0f / (dot00 * dot11 - dot01 * dot01); + float u = (dot11 * dot02 - dot01 * dot12) * invDenom; + float v = (dot00 * dot12 - dot01 * dot02) * invDenom; + + // The (sloppy) epsilon is needed to allow to get height of points which + // are interpolated along the edges of the triangles. + const float EPS = 1e-4f; + + // If point lies inside the triangle, return interpolated ycoord. + if (u >= -EPS && v >= -EPS && (u + v) <= 1 + EPS) + { + h = a[aStart + 1] + v0[1] * u + v1[1] * v; + return true; + } + + return false; + } + + /// Determines if the specified point is inside the convex polygon on the xz-plane. + /// @param[in] pt The point to check. [(x, y, z)] + /// @param[in] verts The polygon vertices. [(x, y, z) * @p nverts] + /// @param[in] nverts The number of vertices. [Limit: >= 3] + /// @return True if the point is inside the polygon. + /// @par + /// + /// All points are projected onto the xz-plane, so the y-values are ignored. + public static bool dtPointInPolygon(float[] pt, float[] verts, int nverts) + { + // TODO: Replace pnpoly with triArea2D tests? + int i, j; + bool c = false; + for (i = 0, j = nverts - 1; i < nverts; j = i++) + { + int viIndex = i * 3; + int vjIndex = j * 3; + if (((verts[viIndex + 2] > pt[2]) != (verts[vjIndex + 2] > pt[2])) && + (pt[0] < (verts[vjIndex + 0] - verts[viIndex + 0]) * (pt[2] - verts[viIndex + 2]) / (verts[vjIndex + 2] - verts[viIndex + 2]) + verts[viIndex + 0])) + c = !c; + } + return c; + } + + public static bool dtDistancePtPolyEdgesSqr(float[] pt, int ptStart, float[] v, int nverts, float[] ed, float[] et) + { + // TODO: Replace pnpoly with triArea2D tests? + int i, j; + bool c = false; + for (i = 0, j = nverts - 1; i < nverts; j = i++) + { + int vi = i * 3; + int vj = j * 3; + if (((v[vi + 2] > pt[ptStart + 2]) != (v[vj + 2] > pt[ptStart + 2])) && + (pt[ptStart + 0] < (v[vj + 0] - v[vi + 0]) * (pt[ptStart + 2] - v[vi + 2]) / (v[vj + 2] - v[vi + 2]) + v[vi + 0])) + c = !c; + ed[j] = dtDistancePtSegSqr2D(pt, ptStart, v, vj, v, vi, ref et[j]); + } + return c; + } + + public static void projectPoly(float[] axis, float[] poly, int npoly, ref float rmin, ref float rmax) + { + rmin = rmax = dtVdot2D(axis, poly); + for (int i = 1; i < npoly; ++i) + { + float d = dtVdot2D(axis, 0, poly, i * 3); + rmin = Math.Min(rmin, d); + rmax = Math.Max(rmax, d); + } + } + + public static bool overlapRange(float amin, float amax, float bmin, float bmax, float eps) + { + return ((amin + eps) > bmax || (amax - eps) < bmin) ? false : true; + } + + /// Determines if the two convex polygons overlap on the xz-plane. + /// @param[in] polya Polygon A vertices. [(x, y, z) * @p npolya] + /// @param[in] npolya The number of vertices in polygon A. + /// @param[in] polyb Polygon B vertices. [(x, y, z) * @p npolyb] + /// @param[in] npolyb The number of vertices in polygon B. + /// @return True if the two polygons overlap. + /// @par + /// + /// All vertices are projected onto the xz-plane, so the y-values are ignored. + public static bool dtOverlapPolyPoly2D(float[] polya, int npolya, float[] polyb, int npolyb) + { + const float eps = 1e-4f; + + for (int i = 0, j = npolya - 1; i < npolya; j = i++) + { + int vaStart = j * 3; + int vbStart = i * 3; + float[] n = new float[] { polya[vbStart + 2] - polya[vaStart + 2], 0, -(polya[vbStart + 0] - polya[vaStart + 0]) }; + float amin = 0.0f, amax = 0.0f, bmin = 0.0f, bmax = 0.0f; + projectPoly(n, polya, npolya, ref amin, ref amax); + projectPoly(n, polyb, npolyb, ref bmin, ref bmax); + if (!overlapRange(amin, amax, bmin, bmax, eps)) + { + // Found separating axis + return false; + } + } + for (int i = 0, j = npolyb - 1; i < npolyb; j = i++) + { + int vaStart = j * 3; + int vbStart = i * 3; + float[] n = new float[] { polyb[vbStart + 2] - polyb[vaStart + 2], 0, -(polyb[vbStart + 0] - polyb[vaStart + 0]) }; + float amin = 0.0f, amax = 0.0f, bmin = 0.0f, bmax = 0.0f; + projectPoly(n, polya, npolya, ref amin, ref amax); + projectPoly(n, polyb, npolyb, ref bmin, ref bmax); + if (!overlapRange(amin, amax, bmin, bmax, eps)) + { + // Found separating axis + return false; + } + } + return true; + } + + // Returns a random point in a convex polygon. + // Adapted from Graphics Gems article. + public static void dtRandomPointInConvexPoly(float[] pts, int npts, float[] areas, float s, float t, float[] _out) + { + // Calc triangle araes + float areasum = 0.0f; + for (int i = 2; i < npts; i++) + { + areas[i] = dtTriArea2D(pts, 0, pts, (i - 1) * 3, pts, i * 3); + areasum += Math.Max(0.001f, areas[i]); + } + // Find sub triangle weighted by area. + float thr = s * areasum; + float acc = 0.0f; + float u = 0.0f; + int tri = 0; + for (int i = 2; i < npts; i++) + { + float dacc = areas[i]; + if (thr >= acc && thr < (acc + dacc)) + { + u = (thr - acc) / dacc; + tri = i; + break; + } + acc += dacc; + } + + float v = (float)Math.Sqrt(t); + + float a = 1 - v; + float b = (1 - u) * v; + float c = u * v; + int paStart = 0; + int pbStart = (tri - 1) * 3; + int pcStart = tri * 3; + + _out[0] = a * pts[paStart + 0] + b * pts[pbStart + 0] + c * pts[pcStart + 0]; + _out[1] = a * pts[paStart + 1] + b * pts[pbStart + 1] + c * pts[pcStart + 1]; + _out[2] = a * pts[paStart + 2] + b * pts[pbStart + 2] + c * pts[pcStart + 2]; + } + + public static float vperpXZ(float[] a, float[] b) + { + return a[0] * b[2] - a[2] * b[0]; + } + + public static bool dtIntersectSegSeg2D(float[] ap, float[] aq, float[] bp, float[] bq, ref float s, ref float t) + { + float[] u = new float[3]; + float[] v = new float[3]; + float[] w = new float[3]; + dtVsub(u, aq, ap); + dtVsub(v, bq, bp); + dtVsub(w, ap, bp); + float d = vperpXZ(u, v); + if (Math.Abs(d) < 1e-6f) return false; + s = vperpXZ(v, w) / d; + t = vperpXZ(u, w) / d; + return true; + } + + public static bool dtIntersectSegSeg2D(float[] ap, int apStart, float[] aq, int aqStart, float[] bp, int bpStart, float[] bq, int bqStart, ref float s, ref float t) + { + float[] u = new float[3]; + float[] v = new float[3]; + float[] w = new float[3]; + dtVsub(u, 0, aq, aqStart, ap, apStart); + dtVsub(v, 0, bq, bqStart, bp, bpStart); + dtVsub(w, 0, ap, apStart, bp, bpStart); + float d = vperpXZ(u, v); + if (Math.Abs(d) < 1e-6f) return false; + s = vperpXZ(v, w) / d; + t = vperpXZ(u, w) / d; + return true; + } + + /// Swaps the values of the two parameters. + /// @param[in,out] a Value A + /// @param[in,out] b Value B + static void dtSwap(ref T lhs, ref T rhs) + { + T temp = lhs; + lhs = rhs; + rhs = temp; + } + + /// Returns the square of the value. + /// @param[in] a The value. + /// @return The square of the value. + public static float dtSqr(float a) + { + return a * a; + } + public static int dtSqr(int a) + { + return a * a; + } + public static uint dtSqr(uint a) + { + return a * a; + } + public static byte dtSqr(byte a) + { + return (byte)(a * a); + } + + /// Clamps the value to the specified range. + /// @param[in] v The value to clamp. + /// @param[in] mn The minimum permitted return value. + /// @param[in] mx The maximum permitted return value. + /// @return The value, clamped to the specified range. + // C#: Originally a template function but operators and template types in c# are a no + public static int dtClamp(int v, int mn, int mx) + { + return v < mn ? mn : (v > mx ? mx : v); + } + public static uint dtClamp(uint v, uint mn, uint mx) + { + return v < mn ? mn : (v > mx ? mx : v); + } + public static byte dtClamp(byte v, byte mn, byte mx) + { + return v < mn ? mn : (v > mx ? mx : v); + } + public static ushort dtClamp(ushort v, ushort mn, ushort mx) + { + return v < mn ? mn : (v > mx ? mx : v); + } + public static float dtClamp(float v, float mn, float mx) + { + return v < mn ? mn : (v > mx ? mx : v); + } + + /// @} + /// @name Vector helper functions. + /// @{ + + /// Derives the cross product of two vectors. (@p v1 x @p v2) + /// @param[out] dest The cross product. [(x, y, z)] + /// @param[in] v1 A Vector [(x, y, z)] + /// @param[in] v2 A vector [(x, y, z)] + public static void dtVcross(float[] dest, float[] v1, float[] v2) + { + dest[0] = v1[1] * v2[2] - v1[2] * v2[1]; + dest[1] = v1[2] * v2[0] - v1[0] * v2[2]; + dest[2] = v1[0] * v2[1] - v1[1] * v2[0]; + } + /// Derives the dot product of two vectors. (@p v1 . @p v2) + /// @param[in] v1 A Vector [(x, y, z)] + /// @param[in] v2 A vector [(x, y, z)] + /// @return The dot product. + public static float dtVdot(float[] v1, float[] v2) + { + return v1[0] * v2[0] + v1[1] * v2[1] + v1[2] * v2[2]; + } + public static float dtVdot(float[] v1, int v1Start, float[] v2, int v2Start) + { + return v1[v1Start + 0] * v2[v2Start + 0] + v1[v1Start + 1] * v2[v2Start + 1] + v1[v1Start + 2] * v2[v2Start + 2]; + } + + /// Performs a scaled vector addition. (@p v1 + (@p v2 * @p s)) + /// @param[out] dest The result vector. [(x, y, z)] + /// @param[in] v1 The base vector. [(x, y, z)] + /// @param[in] v2 The vector to scale and add to @p v1. [(x, y, z)] + /// @param[in] s The amount to scale @p v2 by before adding to @p v1. + public static void dtVmad(float[] dest, float[] v1, float[] v2, float s) + { + dest[0] = v1[0] + v2[0] * s; + dest[1] = v1[1] + v2[1] * s; + dest[2] = v1[2] + v2[2] * s; + } + + /// Performs a linear interpolation between two vectors. (@p v1 toward @p v2) + /// @param[out] dest The result vector. [(x, y, x)] + /// @param[in] v1 The starting vector. + /// @param[in] v2 The destination vector. + /// @param[in] t The interpolation factor. [Limits: 0 <= value <= 1.0] + public static void dtVlerp(float[] dest, float[] v1, float[] v2, float t) + { + dest[0] = v1[0] + (v2[0] - v1[0]) * t; + dest[1] = v1[1] + (v2[1] - v1[1]) * t; + dest[2] = v1[2] + (v2[2] - v1[2]) * t; + } + public static void dtVlerp(float[] dest, int destStart, float[] v1, int v1Start, float[] v2, int v2Start, float t) + { + dest[destStart + 0] = v1[v1Start + 0] + (v2[v2Start + 0] - v1[v1Start + 0]) * t; + dest[destStart + 1] = v1[v1Start + 1] + (v2[v2Start + 1] - v1[v1Start + 1]) * t; + dest[destStart + 2] = v1[v1Start + 2] + (v2[v2Start + 2] - v1[v1Start + 2]) * t; + } + /// Performs a vector addition. (@p v1 + @p v2) + /// @param[out] dest The result vector. [(x, y, z)] + /// @param[in] v1 The base vector. [(x, y, z)] + /// @param[in] v2 The vector to add to @p v1. [(x, y, z)] + public static void dtVadd(float[] dest, float[] v1, float[] v2) + { + dest[0] = v1[0] + v2[0]; + dest[1] = v1[1] + v2[1]; + dest[2] = v1[2] + v2[2]; + } + public static void dtVadd(float[] dest, int destStart, float[] v1, int v1Start, float[] v2, int v2Start) + { + dest[destStart + 0] = v1[v1Start + 0] + v2[v2Start + 0]; + dest[destStart + 1] = v1[v1Start + 1] + v2[v2Start + 1]; + dest[destStart + 2] = v1[v1Start + 2] + v2[v2Start + 2]; + } + + /// Performs a vector subtraction. (@p v1 - @p v2) + /// @param[out] dest The result vector. [(x, y, z)] + /// @param[in] v1 The base vector. [(x, y, z)] + /// @param[in] v2 The vector to subtract from @p v1. [(x, y, z)] + public static void dtVsub(float[] dest, float[] v1, float[] v2) + { + dest[0] = v1[0] - v2[0]; + dest[1] = v1[1] - v2[1]; + dest[2] = v1[2] - v2[2]; + } + public static void dtVsub(float[] dest, int destStart, float[] v1, int v1Start, float[] v2, int v2Start) + { + dest[destStart + 0] = v1[v1Start + 0] - v2[v2Start + 0]; + dest[destStart + 1] = v1[v1Start + 1] - v2[v2Start + 1]; + dest[destStart + 2] = v1[v1Start + 2] - v2[v2Start + 2]; + } + + /// Scales the vector by the specified value. (@p v * @p t) + /// @param[out] dest The result vector. [(x, y, z)] + /// @param[in] v The vector to scale. [(x, y, z)] + /// @param[in] t The scaling factor. + public static void dtVscale(float[] dest, float[] v, float t) + { + dest[0] = v[0] * t; + dest[1] = v[1] * t; + dest[2] = v[2] * t; + } + public static void dtVscale(float[] dest, int destStart, float[] v, int vStart, float t) + { + dest[destStart + 0] = v[vStart + 0] * t; + dest[destStart + 1] = v[vStart + 1] * t; + dest[destStart + 2] = v[vStart + 2] * t; + } + /// Selects the minimum value of each element from the specified vectors. + /// @param[in,out] mn A vector. (Will be updated with the result.) [(x, y, z)] + /// @param[in] v A vector. [(x, y, z)] + public static void dtVmin(float[] mn, float[] v) + { + mn[0] = Math.Min(mn[0], v[0]); + mn[1] = Math.Min(mn[1], v[1]); + mn[2] = Math.Min(mn[2], v[2]); + } + public static void dtVmin(float[] mn, int mnStart, float[] v, int vStart) + { + mn[mnStart + 0] = Math.Min(mn[mnStart + 0], v[vStart + 0]); + mn[mnStart + 1] = Math.Min(mn[mnStart + 1], v[vStart + 1]); + mn[mnStart + 2] = Math.Min(mn[mnStart + 2], v[vStart + 2]); + } + + /// Selects the maximum value of each element from the specified vectors. + /// @param[in,out] mx A vector. (Will be updated with the result.) [(x, y, z)] + /// @param[in] v A vector. [(x, y, z)] + public static void dtVmax(float[] mx, float[] v) + { + mx[0] = Math.Max(mx[0], v[0]); + mx[1] = Math.Max(mx[1], v[1]); + mx[2] = Math.Max(mx[2], v[2]); + } + public static void dtVmax(float[] mx, int mxStart, float[] v, int vStart) + { + mx[mxStart + 0] = Math.Max(mx[mxStart + 0], v[vStart + 0]); + mx[mxStart + 1] = Math.Max(mx[mxStart + 1], v[vStart + 1]); + mx[mxStart + 2] = Math.Max(mx[mxStart + 2], v[vStart + 2]); + } + + /// Sets the vector elements to the specified values. + /// @param[out] dest The result vector. [(x, y, z)] + /// @param[in] x The x-value of the vector. + /// @param[in] y The y-value of the vector. + /// @param[in] z The z-value of the vector. + public static void dtVset(float[] dest, float x, float y, float z) + { + dest[0] = x; dest[1] = y; dest[2] = z; + } + public static void dtVset(float[] dest, int destStart, float x, float y, float z) + { + dest[destStart + 0] = x; dest[destStart + 1] = y; dest[destStart + 2] = z; + } + + /// Performs a vector copy. + /// @param[out] dest The result. [(x, y, z)] + /// @param[in] a The vector to copy. [(x, y, z)] + public static void dtVcopy(float[] dest, float[] a) + { + dest[0] = a[0]; + dest[1] = a[1]; + dest[2] = a[2]; + } + public static void dtVcopy(float[] dest, int destStart, float[] a, int aStart) + { + dest[destStart + 0] = a[aStart + 0]; + dest[destStart + 1] = a[aStart + 1]; + dest[destStart + 2] = a[aStart + 2]; + } + /// Derives the scalar length of the vector. + /// @param[in] v The vector. [(x, y, z)] + /// @return The scalar length of the vector. + public static float dtVlen(float[] v) + { + return (float)Math.Sqrt(v[0] * v[0] + v[1] * v[1] + v[2] * v[2]); + } + public static float dtVlen(float[] v, int vStart) + { + return (float)Math.Sqrt(v[0 + vStart] * v[0 + vStart] + v[1 + vStart] * v[1 + vStart] + v[2 + vStart] * v[2 + vStart]); + } + + /// Derives the square of the scalar length of the vector. (len * len) + /// @param[in] v The vector. [(x, y, z)] + /// @return The square of the scalar length of the vector. + public static float dtVlenSqr(float[] v) + { + return v[0] * v[0] + v[1] * v[1] + v[2] * v[2]; + } + public static float dtVlenSqr(float[] v, int vStart) + { + return v[0 + vStart] * v[0 + vStart] + v[1 + vStart] * v[1 + vStart] + v[2 + vStart] * v[2 + vStart]; + } + + /// Returns the distance between two points. + /// @param[in] v1 A point. [(x, y, z)] + /// @param[in] v2 A point. [(x, y, z)] + /// @return The distance between the two points. + public static float dtVdist(float[] v1, float[] v2) + { + float dx = v2[0] - v1[0]; + float dy = v2[1] - v1[1]; + float dz = v2[2] - v1[2]; + return (float)Math.Sqrt(dx * dx + dy * dy + dz * dz); + } + public static float dtVdist(float[] v1, int v1Start, float[] v2, int v2Start) + { + float dx = v2[v2Start + 0] - v1[v1Start + 0]; + float dy = v2[v2Start + 1] - v1[v1Start + 1]; + float dz = v2[v2Start + 2] - v1[v1Start + 2]; + return (float)Math.Sqrt(dx * dx + dy * dy + dz * dz); + } + + /// Returns the square of the distance between two points. + /// @param[in] v1 A point. [(x, y, z)] + /// @param[in] v2 A point. [(x, y, z)] + /// @return The square of the distance between the two points. + public static float dtVdistSqr(float[] v1, float[] v2) + { + float dx = v2[0] - v1[0]; + float dy = v2[1] - v1[1]; + float dz = v2[2] - v1[2]; + return dx * dx + dy * dy + dz * dz; + } + public static float dtVdistSqr(float[] v1, int v1Start, float[] v2, int v2Start) + { + float dx = v2[v2Start + 0] - v1[v1Start + 0]; + float dy = v2[v2Start + 1] - v1[v1Start + 1]; + float dz = v2[v2Start + 2] - v1[v1Start + 2]; + return dx * dx + dy * dy + dz * dz; + } + + /// Derives the distance between the specified points on the xz-plane. + /// @param[in] v1 A point. [(x, y, z)] + /// @param[in] v2 A point. [(x, y, z)] + /// @return The distance between the point on the xz-plane. + /// + /// The vectors are projected onto the xz-plane, so the y-values are ignored. + public static float dtVdist2D(float[] v1, float[] v2) + { + float dx = v2[0] - v1[0]; + float dz = v2[2] - v1[2]; + return (float)Math.Sqrt(dx * dx + dz * dz); + } + public static float dtVdist2D(float[] v1, int v1Start, float[] v2, int v2Start) + { + float dx = v2[v2Start + 0] - v1[v1Start + 0]; + float dz = v2[v2Start + 2] - v1[v1Start + 2]; + return (float)Math.Sqrt(dx * dx + dz * dz); + } + /// Derives the square of the distance between the specified points on the xz-plane. + /// @param[in] v1 A point. [(x, y, z)] + /// @param[in] v2 A point. [(x, y, z)] + /// @return The square of the distance between the point on the xz-plane. + public static float dtVdist2DSqr(float[] v1, float[] v2) + { + float dx = v2[0] - v1[0]; + float dz = v2[2] - v1[2]; + return dx * dx + dz * dz; + } + public static float dtVdist2DSqr(float[] v1, int v1Start, float[] v2, int v2Start) + { + float dx = v2[v2Start + 0] - v1[v1Start + 0]; + float dz = v2[v2Start + 2] - v1[v1Start + 2]; + return dx * dx + dz * dz; + } + /// Normalizes the vector. + /// @param[in,out] v The vector to normalize. [(x, y, z)] + public static void dtVnormalize(float[] v) + { + float d = 1.0f / (float)Math.Sqrt(v[0] * v[0] + v[1] * v[1] + v[2] * v[2]); + v[0] *= d; + v[1] *= d; + v[2] *= d; + } + public static void dtVnormalize(float[] v, int vStart) + { + float d = 1.0f / (float)Math.Sqrt(v[vStart + 0] * v[vStart + 0] + v[vStart + 1] * v[vStart + 1] + v[vStart + 2] * v[vStart + 2]); + v[vStart + 0] *= d; + v[vStart + 1] *= d; + v[vStart + 2] *= d; + } + + /// Performs a 'sloppy' colocation check of the specified points. + /// @param[in] p0 A point. [(x, y, z)] + /// @param[in] p1 A point. [(x, y, z)] + /// @return True if the points are considered to be at the same location. + /// + /// Basically, this function will return true if the specified points are + /// close enough to eachother to be considered colocated. + public static bool dtVequal(float[] p0, float[] p1) + { + const float thrSqrt = (1.0f / 16384.0f); + const float thr = thrSqrt * thrSqrt; + float d = dtVdistSqr(p0, p1); + return d < thr; + } + public static bool dtVequal(float[] p0, int p0Start, float[] p1, int p1Start) + { + const float thrSqrt = (1.0f / 16384.0f); + const float thr = thrSqrt * thrSqrt; + float d = dtVdistSqr(p0, p0Start, p1, p1Start); + return d < thr; + } + /// Derives the dot product of two vectors on the xz-plane. (@p u . @p v) + /// @param[in] u A vector [(x, y, z)] + /// @param[in] v A vector [(x, y, z)] + /// @return The dot product on the xz-plane. + /// + /// The vectors are projected onto the xz-plane, so the y-values are ignored. + public static float dtVdot2D(float[] u, float[] v) + { + return u[0] * v[0] + u[2] * v[2]; + } + public static float dtVdot2D(float[] u, int uStart, float[] v, int vStart) + { + return u[uStart + 0] * v[vStart + 0] + u[uStart + 2] * v[vStart + 2]; + } + + /// Derives the xz-plane 2D perp product of the two vectors. (uz*vx - ux*vz) + /// @param[in] u The LHV vector [(x, y, z)] + /// @param[in] v The RHV vector [(x, y, z)] + /// @return The dot product on the xz-plane. + /// + /// The vectors are projected onto the xz-plane, so the y-values are ignored. + public static float dtVperp2D(float[] u, float[] v) + { + return u[2] * v[0] - u[0] * v[2]; + } + public static float dtVperp2D(float[] u, int uStart, float[] v, int vStart) + { + return u[uStart + 2] * v[vStart + 0] - u[uStart + 0] * v[vStart + 2]; + } + /// @} + /// @name Computational geometry helper functions. + /// @{ + + /** + + @fn float dtTriArea2D(const float* a, const float* b, const float* c) + @par + + The vertices are projected onto the xz-plane, so the y-values are ignored. + + This is a low cost function than can be used for various purposes. Its main purpose + is for point/line relationship testing. + + In all cases: A value of zero indicates that all vertices are collinear or represent the same point. + (On the xz-plane.) + + When used for point/line relationship tests, AB usually represents a line against which + the C point is to be tested. In this case: + + A positive value indicates that point C is to the left of line AB, looking from A toward B.
+ A negative value indicates that point C is to the right of lineAB, looking from A toward B. + + When used for evaluating a triangle: + + The absolute value of the return value is two times the area of the triangle when it is + projected onto the xz-plane. + + A positive return value indicates: + +
    +
  • The vertices are wrapped in the normal Detour wrap direction.
  • +
  • The triangle's 3D face normal is in the general up direction.
  • +
+ + A negative return value indicates: + +
    +
  • The vertices are reverse wrapped. (Wrapped opposite the normal Detour wrap direction.)
  • +
  • The triangle's 3D face normal is in the general down direction.
  • +
+ + */ + + /// Derives the signed xz-plane area of the triangle ABC, or the relationship of line AB to point C. + /// @param[in] a Vertex A. [(x, y, z)] + /// @param[in] b Vertex B. [(x, y, z)] + /// @param[in] c Vertex C. [(x, y, z)] + /// @return The signed xz-plane area of the triangle. + public static float dtTriArea2D(float[] a, float[] b, float[] c) + { + float abx = b[0] - a[0]; + float abz = b[2] - a[2]; + float acx = c[0] - a[0]; + float acz = c[2] - a[2]; + return acx * abz - abx * acz; + } + public static float dtTriArea2D(float[] a, int aStart, float[] b, int bStart, float[] c, int cStart) + { + float abx = b[bStart + 0] - a[aStart + 0]; + float abz = b[bStart + 2] - a[aStart + 2]; + float acx = c[cStart + 0] - a[aStart + 0]; + float acz = c[cStart + 2] - a[aStart + 2]; + return acx * abz - abx * acz; + } + /// Determines if two axis-aligned bounding boxes overlap. + /// @param[in] amin Minimum bounds of box A. [(x, y, z)] + /// @param[in] amax Maximum bounds of box A. [(x, y, z)] + /// @param[in] bmin Minimum bounds of box B. [(x, y, z)] + /// @param[in] bmax Maximum bounds of box B. [(x, y, z)] + /// @return True if the two AABB's overlap. + /// @see dtOverlapBounds + public static bool dtOverlapQuantBounds(ushort[] amin, ushort[] amax, ushort[] bmin, ushort[] bmax) + { + bool overlap = true; + overlap = (amin[0] > bmax[0] || amax[0] < bmin[0]) ? false : overlap; + overlap = (amin[1] > bmax[1] || amax[1] < bmin[1]) ? false : overlap; + overlap = (amin[2] > bmax[2] || amax[2] < bmin[2]) ? false : overlap; + return overlap; + } + + /// Determines if two axis-aligned bounding boxes overlap. + /// @param[in] amin Minimum bounds of box A. [(x, y, z)] + /// @param[in] amax Maximum bounds of box A. [(x, y, z)] + /// @param[in] bmin Minimum bounds of box B. [(x, y, z)] + /// @param[in] bmax Maximum bounds of box B. [(x, y, z)] + /// @return True if the two AABB's overlap. + /// @see dtOverlapQuantBounds + public static bool dtOverlapBounds(float[] amin, float[] amax, float[] bmin, float[] bmax) + { + bool overlap = true; + overlap = (amin[0] > bmax[0] || amax[0] < bmin[0]) ? false : overlap; + overlap = (amin[1] > bmax[1] || amax[1] < bmin[1]) ? false : overlap; + overlap = (amin[2] > bmax[2] || amax[2] < bmin[2]) ? false : overlap; + return overlap; + } + + /// @} + /// @name Miscellanious functions. + /// @{ + + public static uint dtNextPow2(uint v) + { + v--; + v |= v >> 1; + v |= v >> 2; + v |= v >> 4; + v |= v >> 8; + v |= v >> 16; + v++; + return v; + } + + public static int dtIlog2(uint v) + { + //C# not happy with shifting uints + int r; + int shift; + r = ((v > 0xffff) ? 1 : 0) << 4; v >>= r; + shift = ((v > 0xff) ? 1 : 0) << 3; v >>= shift; r |= shift; + shift = ((v > 0xf) ? 1 : 0) << 2; v >>= shift; r |= shift; + shift = ((v > 0x3) ? 1 : 0) << 1; v >>= shift; r |= shift; + r |= ((int)v >> 1); + return r; + } + + public static int dtAlign4(int x) + { + return (x + 3) & ~3; + } + + public static int dtOppositeTile(int side) + { + return (side + 4) & 0x7; + } + + public static void dtSwapEndian(ref ushort v) + { + byte[] bytes = BitConverter.GetBytes(v); + System.Array.Reverse(bytes); + v = BitConverter.ToUInt16(bytes, 0); + } + + public static void dtSwapEndian(ref short v) + { + byte[] bytes = BitConverter.GetBytes(v); + System.Array.Reverse(bytes); + v = BitConverter.ToInt16(bytes, 0); + } + + public static void dtSwapEndian(ref uint v) + { + byte[] bytes = BitConverter.GetBytes(v); + System.Array.Reverse(bytes); + v = BitConverter.ToUInt32(bytes, 0); + } + + public static void dtSwapEndian(ref int v) + { + byte[] bytes = BitConverter.GetBytes(v); + System.Array.Reverse(bytes); + v = BitConverter.ToInt32(bytes, 0); + } + + public static void dtSwapEndian(ref float v) + { + byte[] bytes = BitConverter.GetBytes(v); + System.Array.Reverse(bytes); + v = BitConverter.ToSingle(bytes, 0); + } + + public static void dtcsArrayItemsCreate(T[] array) where T : class, new() + { + for (int i = 0; i < array.Length; ++i) + { + array[i] = new T(); + } + } +} \ No newline at end of file diff --git a/Framework/RecastDetour/Detour/DetourNavMesh.cs b/Framework/RecastDetour/Detour/DetourNavMesh.cs new file mode 100644 index 000000000..423ee1da3 --- /dev/null +++ b/Framework/RecastDetour/Detour/DetourNavMesh.cs @@ -0,0 +1,1784 @@ +using System; +using System.Diagnostics; +/** + @typedef dtPolyRef + @par + + Polygon references are subject to the same invalidate/preserve/restore + rules that apply to #dtTileRef's. If the #dtTileRef for the polygon's + tile changes, the polygon reference becomes invalid. + + Changing a polygon's flags, area id, etc. does not impact its polygon + reference. + + @typedef dtTileRef + @par + + The following changes will invalidate a tile reference: + + - The referenced tile has been removed from the navigation mesh. + - The navigation mesh has been initialized using a different set + of #dtNavMeshParams. + + A tile reference is preserved/restored if the tile is added to a navigation + mesh initialized with the original #dtNavMeshParams and is added at the + original reference location. (E.g. The lastRef parameter is used with + dtNavMesh::addTile.) + + Basically, if the storage structure of a tile changes, its associated + tile reference changes. + */ + +using dtPolyRef = System.UInt64; +using dtStatus = System.UInt32; +using dtTileRef = System.UInt64; + +public static partial class Detour +{ + public const uint DT_SALT_BITS = 16; + public const uint DT_TILE_BITS = 28; + public const uint DT_POLY_BITS = 20; +} + + +public static partial class Detour +{ + public static bool overlapSlabs(float[] amin, float[] amax, float[] bmin, float[] bmax, float px, float py) + { + // Check for horizontal overlap. + // The segment is shrunken a little so that slabs which touch + // at end points are not connected. + float minx = (float)Math.Max(amin[0] + px, bmin[0] + px); + float maxx = (float)Math.Min(amax[0] - px, bmax[0] - px); + if (minx > maxx) + return false; + + // Check vertical overlap. + float ad = (amax[1] - amin[1]) / (amax[0] - amin[0]); + float ak = amin[1] - ad * amin[0]; + float bd = (bmax[1] - bmin[1]) / (bmax[0] - bmin[0]); + float bk = bmin[1] - bd * bmin[0]; + float aminy = ad * minx + ak; + float amaxy = ad * maxx + ak; + float bminy = bd * minx + bk; + float bmaxy = bd * maxx + bk; + float dmin = bminy - aminy; + float dmax = bmaxy - amaxy; + + // Crossing segments always overlap. + if (dmin * dmax < 0) + return true; + + // Check for overlap at endpoints. + float thr = dtSqr(py * 2); + if (dmin * dmin <= thr || dmax * dmax <= thr) + return true; + + return false; + } + + public static float getSlabCoord(float[] va, int side) + { + if (side == 0 || side == 4) + return va[0]; + else if (side == 2 || side == 6) + return va[2]; + return 0; + } + public static float getSlabCoord(float[] va, int vaStart, int side) + { + if (side == 0 || side == 4) + return va[vaStart + 0]; + else if (side == 2 || side == 6) + return va[vaStart + 2]; + return 0; + } + + + public static void calcSlabEndPoints(float[] va, int vaStart, float[] vb, int vbStart, float[] bmin, float[] bmax, int side) + { + if (side == 0 || side == 4) + { + if (va[vaStart + 2] < vb[vbStart + 2]) + { + bmin[0] = va[vaStart + 2]; + bmin[1] = va[vaStart + 1]; + bmax[0] = vb[vbStart + 2]; + bmax[1] = vb[vbStart + 1]; + } + else + { + bmin[0] = vb[vbStart + 2]; + bmin[1] = vb[vbStart + 1]; + bmax[0] = va[vaStart + 2]; + bmax[1] = va[vaStart + 1]; + } + } + else if (side == 2 || side == 6) + { + if (va[vaStart + 0] < vb[0]) + { + bmin[0] = va[vaStart + 0]; + bmin[1] = va[vaStart + 1]; + bmax[0] = vb[vbStart + 0]; + bmax[1] = vb[vbStart + 1]; + } + else + { + bmin[0] = vb[vbStart + 0]; + bmin[1] = vb[vbStart + 1]; + bmax[0] = va[vaStart + 0]; + bmax[1] = va[vaStart + 1]; + } + } + } + + public static int computeTileHash(int x, int y, int mask) + { + const uint h1 = 0x8da6b343; // Large multiplicative constants; + const uint h2 = 0xd8163841; // here arbitrarily chosen primes + uint n = (uint)(h1 * x + h2 * y); + return (int)(n & mask); + } + + public static uint allocLink(dtMeshTile tile) + { + if (tile.linksFreeList == Detour.DT_NULL_LINK) + return DT_NULL_LINK; + uint link = tile.linksFreeList; + tile.linksFreeList = tile.links[link].next; + return link; + } + + public static void freeLink(dtMeshTile tile, uint link) + { + tile.links[link].next = tile.linksFreeList; + tile.linksFreeList = link; + } + + /* + dtNavMesh* dtAllocNavMesh() + { + void* mem = dtAlloc(sizeof(dtNavMesh), DT_ALLOC_PERM); + if (!mem) return 0; + return new(mem) dtNavMesh; + } + */ + /// @par + /// + /// This function will only free the memory for tiles with the #DT_TILE_FREE_DATA + /// flag set. + /* + void dtFreeNavMesh(dtNavMesh* navmesh) + { + if (!navmesh) return; + navmesh.~dtNavMesh(); + dtFree(navmesh); + } + */ + ////////////////////////////////////////////////////////////////////////////////////////// + + /** + @class dtNavMesh + + The navigation mesh consists of one or more tiles defining three primary types of structural data: + + A polygon mesh which defines most of the navigation graph. (See rcPolyMesh for its structure.) + A detail mesh used for determining surface height on the polygon mesh. (See rcPolyMeshDetail for its structure.) + Off-mesh connections, which define custom point-to-point edges within the navigation graph. + + The general build process is as follows: + + -# Create rcPolyMesh and rcPolyMeshDetail data using the Recast build pipeline. + -# Optionally, create off-mesh connection data. + -# Combine the source data into a dtNavMeshCreateParams structure. + -# Create a tile data array using dtCreateNavMeshData(). + -# Allocate at dtNavMesh object and initialize it. (For single tile navigation meshes, + the tile data is loaded during this step.) + -# For multi-tile navigation meshes, load the tile data using dtNavMesh::addTile(). + + Notes: + + - This class is usually used in conjunction with the dtNavMeshQuery class for pathfinding. + - Technically, all navigation meshes are tiled. A 'solo' mesh is simply a navigation mesh initialized + to have only a single tile. + - This class does not implement any asynchronous methods. So the ::dtStatus result of all methods will + always contain either a success or failure flag. + + @see dtNavMeshQuery, dtCreateNavMeshData, dtNavMeshCreateParams, #dtAllocNavMesh, #dtFreeNavMesh + */ + /// A navigation mesh based on tiles of convex polygons. + /// @ingroup detour + public class dtNavMesh + { + private dtNavMeshParams m_params; //< Current initialization params. TODO: do not store this info twice. + private float[] m_orig = new float[3]; //< Origin of the tile (0,0) + private float m_tileWidth, m_tileHeight; //< Dimensions of each tile. + private int m_maxTiles; //< Max number of tiles. + private int m_tileLutSize; //< Tile hash lookup size (must be pot). + private int m_tileLutMask; //< Tile hash lookup mask. + + //dtMeshTile** + private dtMeshTile[] m_posLookup; //< Tile hash lookup. + private dtMeshTile m_nextFree; //< Freelist of tiles. + private dtMeshTile[] m_tiles; //< List of tiles. + + public dtNavMesh() + { +#if DT_POLYREF64 + m_saltBits = 0; + m_tileBits = 0; + m_polyBits = 0; +#endif + m_params = new dtNavMeshParams(); + m_orig[0] = 0; + m_orig[1] = 0; + m_orig[2] = 0; + } + + ~dtNavMesh() + { + //C#: all this auto + /* + for (int i = 0; i < m_maxTiles; ++i) + { + if (m_tiles[i].flags & Detour.DT_TILE_FREE_DATA) + { + //dtFree(m_tiles[i].data); + m_tiles[i].data = null; + m_tiles[i].dataSize = 0; + } + } + //dtFree(m_posLookup); + //dtFree(m_tiles); + m_posLookup = null; + m_tiles = null; + * */ + } + /// Derives a standard polygon reference. + /// @note This function is generally meant for internal use only. + /// @param[in] salt The tile's salt value. + /// @param[in] it The index of the tile. + /// @param[in] ip The index of the polygon within the tile. + public dtPolyRef encodePolyId(uint salt, uint it, uint ip) + { + return ((dtPolyRef)salt << (int)(DT_POLY_BITS + DT_TILE_BITS)) | ((dtPolyRef)it << (int)DT_POLY_BITS) | (dtPolyRef)ip; + } + + /// Decodes a standard polygon reference. + /// @note This function is generally meant for internal use only. + /// @param[in] ref The polygon reference to decode. + /// @param[out] salt The tile's salt value. + /// @param[out] it The index of the tile. + /// @param[out] ip The index of the polygon within the tile. + /// @see #encodePolyId + public void decodePolyId(dtPolyRef polyRef, ref uint salt, ref uint it, ref uint ip) + { + dtPolyRef saltMask = (dtPolyRef)(1 << (int)DT_SALT_BITS) - 1; + dtPolyRef tileMask = (dtPolyRef)((1 << (int)DT_TILE_BITS) - 1); + dtPolyRef polyMask = (dtPolyRef)((1 << (int)DT_POLY_BITS) - 1); + salt = (uint)((polyRef >> (int)(DT_POLY_BITS + DT_TILE_BITS)) & saltMask); + it = (uint)((polyRef >> (int)DT_POLY_BITS) & tileMask); + ip = (uint)(polyRef & polyMask); + } + + /// Extracts a tile's salt value from the specified polygon reference. + /// @note This function is generally meant for internal use only. + /// @param[in] ref The polygon reference. + /// @see #encodePolyId + public uint decodePolyIdSalt(dtPolyRef polyRef) + { + dtPolyRef saltMask = (dtPolyRef)((1 << (int)DT_SALT_BITS) - 1); + return (uint)((polyRef >> (int)(DT_POLY_BITS + DT_TILE_BITS)) & saltMask); + } + + /// Extracts the tile's index from the specified polygon reference. + /// @note This function is generally meant for internal use only. + /// @param[in] ref The polygon reference. + /// @see #encodePolyId + public uint decodePolyIdTile(dtPolyRef polyRef) + { + dtPolyRef tileMask = (dtPolyRef)((1 << (int)DT_TILE_BITS) - 1); + return (uint)((polyRef >> (int)DT_POLY_BITS) & tileMask); + } + + /// Extracts the polygon's index (within its tile) from the specified polygon reference. + /// @note This function is generally meant for internal use only. + /// @param[in] ref The polygon reference. + /// @see #encodePolyId + public uint decodePolyIdPoly(dtPolyRef polyRef) + { + dtPolyRef polyMask = (dtPolyRef)((1 << (int)DT_POLY_BITS) - 1); + return (uint)(polyRef & polyMask); + } + + /// @{ + /// @name Initialization and Tile Management + + /// Initializes the navigation mesh for tiled use. + /// @param[in] params Initialization parameters. + /// @return The status flags for the operation. + public dtStatus init(dtNavMeshParams navMeshParams) + { + //memcpy(&m_params, params, sizeof(dtNavMeshParams)); + m_params = navMeshParams.Clone(); + dtVcopy(m_orig, navMeshParams.orig); + m_tileWidth = navMeshParams.tileWidth; + m_tileHeight = navMeshParams.tileHeight; + + // Init tiles + m_maxTiles = navMeshParams.maxTiles; + m_tileLutSize = (int)dtNextPow2((uint)(navMeshParams.maxTiles / 4)); + if (m_tileLutSize == 0) + m_tileLutSize = 1; + m_tileLutMask = m_tileLutSize - 1; + + //m_tiles = (dtMeshTile*)dtAlloc(sizeof(dtMeshTile)*m_maxTiles, DT_ALLOC_PERM); + m_tiles = new dtMeshTile[m_maxTiles]; + dtcsArrayItemsCreate(m_tiles); + + if (m_tiles == null) + return (DT_FAILURE | DT_OUT_OF_MEMORY); + //m_posLookup = (dtMeshTile**)dtAlloc(sizeof(dtMeshTile*)*m_tileLutSize, DT_ALLOC_PERM); + m_posLookup = new dtMeshTile[m_tileLutSize]; + dtcsArrayItemsCreate(m_posLookup); + if (m_posLookup == null) + return DT_FAILURE | DT_OUT_OF_MEMORY; + //memset(m_tiles, 0, sizeof(dtMeshTile)*m_maxTiles); + //memset(m_posLookup, 0, sizeof(dtMeshTile*)*m_tileLutSize); + m_nextFree = null; + for (int i = m_maxTiles - 1; i >= 0; --i) + { + m_tiles[i].salt = 1; + m_tiles[i].next = m_nextFree; + m_nextFree = m_tiles[i]; + } + + // Init ID generator values. + + return DT_SUCCESS; + } + + /// Initializes the navigation mesh for single tile use. + /// @param[in] data Data of the new tile. (See: #dtCreateNavMeshData) + /// @param[in] dataSize The data size of the new tile. + /// @param[in] flags The tile flags. (See: #dtTileFlags) + /// @return The status flags for the operation. + /// @see dtCreateNavMeshData + public dtStatus init(dtRawTileData rawTile, int flags) + { + //C#: Using an intermediate class dtRawTileData because Cpp uses a binary buffer. + + // Make sure the data is in right format. + //dtMeshHeader header = (dtMeshHeader*)data; + dtMeshHeader header = rawTile.header; + if (header.magic != DT_NAVMESH_MAGIC) + return DT_FAILURE | DT_WRONG_MAGIC; + if (header.version != DT_NAVMESH_VERSION) + return DT_FAILURE | DT_WRONG_VERSION; + + dtNavMeshParams navMeshParams = new dtNavMeshParams(); + dtVcopy(navMeshParams.orig, header.bmin); + navMeshParams.tileWidth = header.bmax[0] - header.bmin[0]; + navMeshParams.tileHeight = header.bmax[2] - header.bmin[2]; + navMeshParams.maxTiles = 1; + navMeshParams.maxPolys = header.polyCount; + + dtStatus status = init(navMeshParams); + if (dtStatusFailed(status)) + return status; + + //return addTile(data, dataSize, flags, 0, 0); + dtTileRef dummyResult = 0; + return addTile(rawTile, flags, 0, ref dummyResult); + } + + /// The navigation mesh initialization params. + /// @par + /// + /// @note The parameters are created automatically when the single tile + /// initialization is performed. + public dtNavMeshParams getParams() + { + return m_params; + } + + ////////////////////////////////////////////////////////////////////////////////////////// + /// Returns all polygons in neighbour tile based on portal defined by the segment. + public int findConnectingPolys(float[] va, int vaStart, float[] vb, int vbStart, dtMeshTile tile, int side, dtPolyRef[] con, float[] conarea, int maxcon) + { + if (tile == null) + return 0; + + float[] amin = new float[2]; + float[] amax = new float[2]; + calcSlabEndPoints(va, vaStart, vb, vbStart, amin, amax, side); + float apos = getSlabCoord(va, vaStart, side); + + // Remove links pointing to 'side' and compact the links array. + float[] bmin = new float[2]; + float[] bmax = new float[2]; + ushort m = (ushort)(DT_EXT_LINK | (ushort)side); + int n = 0; + + dtPolyRef polyRefBase = getPolyRefBase(tile); + + for (uint i = 0; i < tile.header.polyCount; ++i) + { + dtPoly poly = tile.polys[i]; + int nv = (int)poly.vertCount; + for (int j = 0; j < nv; ++j) + { + // Skip edges which do not point to the right side. + if (poly.neis[j] != m) + continue; + + //const float* vc = &tile.verts[poly.verts[j]*3]; + //const float* vd = &tile.verts[poly.verts[(j+1) % nv]*3]; + int vcStart = poly.verts[j] * 3; + int vdStart = poly.verts[(j + 1) % nv] * 3; + float bpos = getSlabCoord(tile.verts, vcStart, side); + + // Segments are not close enough. + if (Math.Abs(apos - bpos) > 0.01f) + continue; + + // Check if the segments touch. + calcSlabEndPoints(tile.verts, vcStart, tile.verts, vdStart, bmin, bmax, side); + + if (!overlapSlabs(amin, amax, bmin, bmax, 0.01f, tile.header.walkableClimb)) + continue; + + // Add return value. + if (n < maxcon) + { + conarea[n * 2 + 0] = Math.Max(amin[0], bmin[0]); + conarea[n * 2 + 1] = Math.Min(amax[0], bmax[0]); + con[n] = polyRefBase | (dtPolyRef)i; + n++; + } + break; + } + } + return n; + } + + /// Removes external links at specified side. + public void unconnectExtLinks(dtMeshTile tile, dtMeshTile target) + { + if (tile == null || target == null) + return; + + uint targetNum = decodePolyIdTile(getTileRef(target)); + + for (int i = 0; i < tile.header.polyCount; ++i) + { + dtPoly poly = tile.polys[i]; + uint j = poly.firstLink; + uint pj = DT_NULL_LINK; + while (j != DT_NULL_LINK) + { + if (tile.links[j].side != 0xff && + decodePolyIdTile(tile.links[j].polyRef) == targetNum) + { + // Revove link. + uint nj = tile.links[j].next; + if (pj == DT_NULL_LINK) + poly.firstLink = nj; + else + tile.links[pj].next = nj; + freeLink(tile, j); + j = nj; + } + else + { + // Advance + pj = j; + j = tile.links[j].next; + } + } + } + } + + /// Builds external polygon links for a tile. + public void connectExtLinks(dtMeshTile tile, dtMeshTile target, int side) + { + if (tile == null) + return; + + // Connect border links. + for (int i = 0; i < tile.header.polyCount; ++i) + { + dtPoly poly = tile.polys[i]; + + // Create new links. + // ushort m = DT_EXT_LINK | (ushort)side; + + int nv = (int)poly.vertCount; + for (int j = 0; j < nv; ++j) + { + // Skip non-portal edges. + if ((poly.neis[j] & DT_EXT_LINK) == 0) + continue; + + int dir = (int)(poly.neis[j] & 0xff); + if (side != -1 && dir != side) + continue; + + // Create new links + //const float* va = &tile.verts[poly.verts[j]*3]; + //const float* vb = &tile.verts[poly.verts[(j+1) % nv]*3]; + int vaStart = poly.verts[j] * 3; + int vbStart = poly.verts[(j + 1) % nv] * 3; + + dtPolyRef[] nei = new dtPolyRef[4]; + float[] neia = new float[4 * 2]; + int nnei = findConnectingPolys(tile.verts, vaStart, tile.verts, vbStart, target, dtOppositeTile(dir), nei, neia, 4); + for (int k = 0; k < nnei; ++k) + { + uint idx = allocLink(tile); + if (idx != DT_NULL_LINK) + { + dtLink link = tile.links[idx]; + link.polyRef = nei[k]; + link.edge = (byte)j; + link.side = (byte)dir; + + link.next = poly.firstLink; + poly.firstLink = idx; + + // Compress portal limits to a byte value. + if (dir == 0 || dir == 4) + { + float tmin = (neia[k * 2 + 0] - tile.verts[vaStart + 2]) / (tile.verts[vbStart + 2] - tile.verts[vaStart + 2]); + float tmax = (neia[k * 2 + 1] - tile.verts[vaStart + 2]) / (tile.verts[vbStart + 2] - tile.verts[vaStart + 2]); + if (tmin > tmax) + dtSwap(ref tmin, ref tmax); + link.bmin = (byte)(dtClamp(tmin, 0.0f, 1.0f) * 255.0f); + link.bmax = (byte)(dtClamp(tmax, 0.0f, 1.0f) * 255.0f); + } + else if (dir == 2 || dir == 6) + { + float tmin = (neia[k * 2 + 0] - tile.verts[vaStart + 0]) / (tile.verts[vbStart + 0] - tile.verts[vaStart + 0]); + float tmax = (neia[k * 2 + 1] - tile.verts[vaStart + 0]) / (tile.verts[vbStart + 0] - tile.verts[vaStart + 0]); + if (tmin > tmax) + dtSwap(ref tmin, ref tmax); + link.bmin = (byte)(dtClamp(tmin, 0.0f, 1.0f) * 255.0f); + link.bmax = (byte)(dtClamp(tmax, 0.0f, 1.0f) * 255.0f); + } + } + } + } + } + } + + /// Builds external polygon links for a tile. + public void connectExtOffMeshLinks(dtMeshTile tile, dtMeshTile target, int side) + { + if (tile == null) + return; + + // Connect off-mesh links. + // We are interested on links which land from target tile to this tile. + byte oppositeSide = (side == -1) ? (byte)0xff : (byte)dtOppositeTile(side); + + for (int i = 0; i < target.header.offMeshConCount; ++i) + { + dtOffMeshConnection targetCon = target.offMeshCons[i]; + if (targetCon.side != oppositeSide) + continue; + + dtPoly targetPoly = target.polys[targetCon.poly]; + // Skip off-mesh connections which start location could not be connected at all. + if (targetPoly.firstLink == DT_NULL_LINK) + continue; + + float[] ext = new float[] { targetCon.rad, target.header.walkableClimb, targetCon.rad }; + + // Find polygon to connect to. + //const float* p = &targetCon.pos[3]; + int pIndex = 3; + float[] nearestPt = new float[3]; + dtPolyRef polyRef = findNearestPolyInTile(tile, targetCon.pos, pIndex, ext, nearestPt); + if (polyRef == 0) + continue; + // findNearestPoly may return too optimistic results, further check to make sure. + if (dtSqr(nearestPt[0] - targetCon.pos[pIndex]) + dtSqr(nearestPt[2] - targetCon.pos[pIndex + 2]) > dtSqr(targetCon.rad)) + continue; + // Make sure the location is on current mesh. + //float* v = &target.verts[targetPoly.verts[1]*3]; + int vIndex = targetPoly.verts[1] * 3; + dtVcopy(target.verts, vIndex, nearestPt, 0); + + // Link off-mesh connection to target poly. + uint idx = allocLink(target); + if (idx != DT_NULL_LINK) + { + dtLink link = target.links[idx]; + link.polyRef = polyRef; + link.edge = (byte)1; + link.side = oppositeSide; + link.bmin = link.bmax = 0; + // Add to linked list. + link.next = targetPoly.firstLink; + targetPoly.firstLink = idx; + } + + // Link target poly to off-mesh connection. + if ((targetCon.flags & DT_OFFMESH_CON_BIDIR) != 0) + { + uint tidx = allocLink(tile); + if (tidx != DT_NULL_LINK) + { + ushort landPolyIdx = (ushort)decodePolyIdPoly(polyRef); + dtPoly landPoly = tile.polys[landPolyIdx]; + dtLink link = tile.links[tidx]; + link.polyRef = getPolyRefBase(target) | (dtPolyRef)(targetCon.poly); + link.edge = 0xff; + link.side = (byte)(side == -1 ? 0xff : side); + link.bmin = link.bmax = 0; + // Add to linked list. + link.next = landPoly.firstLink; + landPoly.firstLink = tidx; + } + } + } + + } + /// Builds internal polygons links for a tile. + public void connectIntLinks(dtMeshTile tile) + { + if (tile == null) + return; + + dtPolyRef polyRefBase = getPolyRefBase(tile); + + for (int i = 0; i < tile.header.polyCount; ++i) + { + dtPoly poly = tile.polys[i]; + poly.firstLink = DT_NULL_LINK; + + if (poly.getType() == (byte)dtPolyTypes.DT_POLYTYPE_OFFMESH_CONNECTION) + continue; + + // Build edge links backwards so that the links will be + // in the linked list from lowest index to highest. + for (int j = poly.vertCount - 1; j >= 0; --j) + { + // Skip hard and non-internal edges. + if (poly.neis[j] == 0 || (poly.neis[j] & DT_EXT_LINK) != 0) + continue; + + uint idx = allocLink(tile); + if (idx != DT_NULL_LINK) + { + dtLink link = tile.links[idx]; + link.polyRef = polyRefBase | (dtPolyRef)(poly.neis[j] - 1u); + link.edge = (byte)j; + link.side = 0xff; + link.bmin = link.bmax = 0; + // Add to linked list. + link.next = poly.firstLink; + poly.firstLink = idx; + } + } + } + } + + /// Builds internal polygons links for a tile. + public void baseOffMeshLinks(dtMeshTile tile) + { + if (tile == null) + return; + + dtPolyRef polyRefBase = getPolyRefBase(tile); + + // Base off-mesh connection start points. + for (int i = 0; i < tile.header.offMeshConCount; ++i) + { + dtOffMeshConnection con = tile.offMeshCons[i]; + dtPoly poly = tile.polys[con.poly]; + + float[] ext = new float[] { con.rad, tile.header.walkableClimb, con.rad }; + + // Find polygon to connect to. + //const float* p = &con.pos[0]; // First vertex + + float[] nearestPt = new float[3]; + dtPolyRef polyRef = findNearestPolyInTile(tile, con.pos, 0, ext, nearestPt); + if (polyRef == 0) + continue; + // findNearestPoly may return too optimistic results, further check to make sure. + if (dtSqr(nearestPt[0] - con.pos[0]) + dtSqr(nearestPt[2] - con.pos[2]) > dtSqr(con.rad)) + continue; + // Make sure the location is on current mesh. + //float* v = &tile.verts[poly.verts[0]*3]; + int vIndex = poly.verts[0] * 3; + dtVcopy(tile.verts, vIndex, nearestPt, 0); + + // Link off-mesh connection to target poly. + uint idx = allocLink(tile); + if (idx != DT_NULL_LINK) + { + dtLink link = tile.links[idx]; + link.polyRef = polyRef; + link.edge = (byte)0; + link.side = 0xff; + link.bmin = link.bmax = 0; + // Add to linked list. + link.next = poly.firstLink; + poly.firstLink = idx; + } + + // Start end-point is always connect back to off-mesh connection. + uint tidx = allocLink(tile); + if (tidx != DT_NULL_LINK) + { + ushort landPolyIdx = (ushort)decodePolyIdPoly(polyRef); + dtPoly landPoly = tile.polys[landPolyIdx]; + dtLink link = tile.links[tidx]; + link.polyRef = polyRefBase | (dtPolyRef)(con.poly); + link.edge = 0xff; + link.side = 0xff; + link.bmin = link.bmax = 0; + // Add to linked list. + link.next = landPoly.firstLink; + landPoly.firstLink = tidx; + } + } + } + + public void closestPointOnPoly(dtPolyRef polyRef, float[] pos, int posStart, float[] closest, ref bool posOverPoly) + { + dtMeshTile tile = null; + dtPoly poly = null; + uint ip = 0; + getTileAndPolyByRefUnsafe(polyRef, ref tile, ref poly, ref ip); + + // Off-mesh connections don't have detail polygons. + if (poly.getType() == (byte)dtPolyTypes.DT_POLYTYPE_OFFMESH_CONNECTION) + { + //const float* v0 = &tile.verts[poly.verts[0]*3]; + //const float* v1 = &tile.verts[poly.verts[1]*3]; + int v0Start = poly.verts[0] * 3; + int v1Start = poly.verts[1] * 3; + float d0 = dtVdist(pos, posStart, tile.verts, v0Start); + float d1 = dtVdist(pos, posStart, tile.verts, v1Start); + float u = d0 / (d0 + d1); + dtVlerp(closest, 0, tile.verts, v0Start, tile.verts, v1Start, u); + + posOverPoly = false; + return; + } + + //uint ip = (uint)(poly - tile.polys); + dtPolyDetail pd = tile.detailMeshes[ip]; + + // Clamp point to be inside the polygon. + float[] verts = new float[DT_VERTS_PER_POLYGON * 3]; + float[] edged = new float[DT_VERTS_PER_POLYGON]; + float[] edget = new float[DT_VERTS_PER_POLYGON]; + int nv = poly.vertCount; + for (int i = 0; i < nv; ++i) + { + dtVcopy(verts, i * 3, tile.verts, poly.verts[i] * 3); + } + + dtVcopy(closest, 0, pos, posStart); + if (!dtDistancePtPolyEdgesSqr(pos, posStart, verts, nv, edged, edget)) + { + // Point is outside the polygon, dtClamp to nearest edge. + float dmin = float.MaxValue; + int imin = -1; + for (int i = 0; i < nv; ++i) + { + if (edged[i] < dmin) + { + dmin = edged[i]; + imin = i; + } + } + //float[] va = &verts[imin*3]; + //const float* vb = &verts[((imin+1)%nv)*3]; + int vaStart = imin * 3; + int vbStart = ((imin + 1) % nv) * 3; + dtVlerp(closest, 0, verts, vaStart, verts, vbStart, edget[imin]); + + posOverPoly = false; + } + else + { + posOverPoly = true; + } + + // Find height at the location. + for (int j = 0; j < pd.triCount; ++j) + { + //const byte* t = &tile.detailTris[(pd.triBase+j)*4]; + int tIndex = (int)(pd.triBase + j) * 4; + //float* v[3]; + int[] vIndices = new int[3]; + float[][] vArrays = new float[3][]; + for (int k = 0; k < 3; ++k) + { + if (tile.detailTris[tIndex + k] < poly.vertCount) + { + //v[k] = &tile.verts[poly.verts[t[k]]*3]; + vIndices[k] = poly.verts[tile.detailTris[tIndex + k]] * 3; + vArrays[k] = tile.verts; + } + else + { + //v[k] = &tile.detailVerts[(pd.vertBase+(tile.detailTris[tIndex + k]-poly.vertCount))*3]; + vIndices[k] = (int)(pd.vertBase + (tile.detailTris[tIndex + k] - poly.vertCount)) * 3; + vArrays[k] = tile.detailVerts; + } + } + float h = .0f; ; + if (dtClosestHeightPointTriangle(pos, posStart, vArrays[0], vIndices[0], vArrays[1], vIndices[1], vArrays[2], vIndices[2], ref h)) + { + closest[1] = h; + break; + } + } + } + + public dtPolyRef findNearestPolyInTile(dtMeshTile tile, float[] center, int centerStart, float[] extents, float[] nearestPt) + { + float[] bmin = new float[3];//, bmax[3]; + float[] bmax = new float[3]; + dtVsub(bmin, 0, center, centerStart, extents, 0); + dtVadd(bmax, 0, center, centerStart, extents, 0); + + // Get nearby polygons from proximity grid. + dtPolyRef[] polys = new dtPolyRef[128]; + int polyCount = queryPolygonsInTile(tile, bmin, bmax, polys, 128); + + // Find nearest polygon amongst the nearby polygons. + dtPolyRef nearest = 0; + float nearestDistanceSqr = float.MaxValue; + for (int i = 0; i < polyCount; ++i) + { + dtPolyRef polyRef = polys[i]; + float[] closestPtPoly = new float[3]; + float[] diff = new float[3]; + bool posOverPoly = false; + float d = 0; + closestPointOnPoly(polyRef, center, centerStart, closestPtPoly, ref posOverPoly); + + // If a point is directly over a polygon and closer than + // climb height, favor that instead of straight line nearest point. + dtVsub(diff, 0, center, centerStart, closestPtPoly, 0); + if (posOverPoly) + { + d = Math.Abs(diff[1]) - tile.header.walkableClimb; + d = d > 0 ? d * d : 0; + } + else + { + d = dtVlenSqr(diff); + } + + if (d < nearestDistanceSqr) + { + dtVcopy(nearestPt, closestPtPoly); + nearestDistanceSqr = d; + nearest = polyRef; + } + } + + return nearest; + } + + public int queryPolygonsInTile(dtMeshTile tile, float[] qmin, float[] qmax, dtPolyRef[] polys, int maxPolys) + { + if (tile.bvTree != null) + { + // tile.bvTree[0]; + //dtBVNode end = tile.bvTree[tile.header.bvNodeCount]; + int endNodeIndex = tile.header.bvNodeCount; + float[] tbmin = tile.header.bmin; + float[] tbmax = tile.header.bmax; + float qfac = tile.header.bvQuantFactor; + + // Calculate quantized box + ushort[] bmin = new ushort[3];//, bmax[3]; + ushort[] bmax = new ushort[3]; + // dtClamp query box to world box. + float minx = dtClamp(qmin[0], tbmin[0], tbmax[0]) - tbmin[0]; + float miny = dtClamp(qmin[1], tbmin[1], tbmax[1]) - tbmin[1]; + float minz = dtClamp(qmin[2], tbmin[2], tbmax[2]) - tbmin[2]; + float maxx = dtClamp(qmax[0], tbmin[0], tbmax[0]) - tbmin[0]; + float maxy = dtClamp(qmax[1], tbmin[1], tbmax[1]) - tbmin[1]; + float maxz = dtClamp(qmax[2], tbmin[2], tbmax[2]) - tbmin[2]; + // Quantize + bmin[0] = (ushort)((ushort)(qfac * minx) & 0xfffe); + bmin[1] = (ushort)((ushort)(qfac * miny) & 0xfffe); + bmin[2] = (ushort)((ushort)(qfac * minz) & 0xfffe); + bmax[0] = (ushort)((ushort)(qfac * maxx + 1) | 1); + bmax[1] = (ushort)((ushort)(qfac * maxy + 1) | 1); + bmax[2] = (ushort)((ushort)(qfac * maxz + 1) | 1); + + // Traverse tree + dtPolyRef polyRefBase = getPolyRefBase(tile); + int n = 0; + int curNode = 0; + dtBVNode node = null; + while (curNode < endNodeIndex) + { + node = tile.bvTree[curNode]; + + bool overlap = dtOverlapQuantBounds(bmin, bmax, node.bmin, node.bmax); + bool isLeafNode = node.i >= 0; + + if (isLeafNode && overlap) + { + if (n < maxPolys) + { + polys[n++] = polyRefBase | (uint)node.i; + } + } + + if (overlap || isLeafNode) + { + //node++; + ++curNode; + } + else + { + int escapeIndex = -node.i; + curNode += escapeIndex; + } + } + + return n; + } + else + { + float[] bmin = new float[3];//, bmax[3]; + float[] bmax = new float[3]; + int n = 0; + dtPolyRef polyRefBase = getPolyRefBase(tile); + for (int i = 0; i < tile.header.polyCount; ++i) + { + dtPoly p = tile.polys[i]; + + // Do not return off-mesh connection polygons. + if (p.getType() == (byte)dtPolyTypes.DT_POLYTYPE_OFFMESH_CONNECTION) + continue; + // Calc polygon bounds. + //float[] v = tile.verts[p.verts[0]*3]; + int vIndex = p.verts[0] * 3; + dtVcopy(bmin, 0, tile.verts, vIndex); + dtVcopy(bmax, 0, tile.verts, vIndex); + for (int j = 1; j < p.vertCount; ++j) + { + //v = &tile.verts[p.verts[j]*3]; + vIndex = p.verts[j] * 3; + dtVmin(bmin, 0, tile.verts, vIndex); + dtVmax(bmax, 0, tile.verts, vIndex); + } + if (dtOverlapBounds(qmin, qmax, bmin, bmax)) + { + if (n < maxPolys) + polys[n++] = polyRefBase | (uint)i; + } + } + return n; + } + } + + /// @par + /// + /// The add operation will fail if the data is in the wrong format, the allocated tile + /// space is full, or there is a tile already at the specified reference. + /// + /// The lastRef parameter is used to restore a tile with the same tile + /// reference it had previously used. In this case the #dtPolyRef's for the + /// tile will be restored to the same values they were before the tile was + /// removed. + /// + /// @see dtCreateNavMeshData, #removeTile + /// Adds a tile to the navigation mesh. + /// @param[in] data Data for the new tile mesh. (See: #dtCreateNavMeshData) + /// @param[in] dataSize Data size of the new tile mesh. + /// @param[in] flags Tile flags. (See: #dtTileFlags) + /// @param[in] lastRef The desired reference for the tile. (When reloading a tile.) [opt] [Default: 0] + /// @param[out] result The tile reference. (If the tile was succesfully added.) [opt] + /// @return The status flags for the operation. + public dtStatus addTile(dtRawTileData rawTileData, int flags, dtTileRef lastRef, ref dtTileRef result) + { + //C#: Using an intermediate class dtRawTileData because Cpp uses a binary buffer. + + // Make sure the data is in right format. + dtMeshHeader header = rawTileData.header; + if (header.magic != DT_NAVMESH_MAGIC) + return DT_FAILURE | DT_WRONG_MAGIC; + if (header.version != DT_NAVMESH_VERSION) + return DT_FAILURE | DT_WRONG_VERSION; + + // Make sure the location is free. + if (getTileAt(header.x, header.y, header.layer) != null) + return DT_FAILURE; + + // Allocate a tile. + dtMeshTile tile = null; + if (lastRef == 0) + { + if (m_nextFree != null) + { + tile = m_nextFree; + m_nextFree = tile.next; + tile.next = null; + } + } + else + { + // Try to relocate the tile to specific index with same salt. + int tileIndex = (int)decodePolyIdTile((dtPolyRef)lastRef); + if (tileIndex >= m_maxTiles) + return DT_FAILURE | DT_OUT_OF_MEMORY; + // Try to find the specific tile id from the free list. + dtMeshTile target = m_tiles[tileIndex]; + dtMeshTile prev = null; + tile = m_nextFree; + while (tile != null && tile != target) + { + prev = tile; + tile = tile.next; + } + // Could not find the correct location. + if (tile != target) + return DT_FAILURE | DT_OUT_OF_MEMORY; + // Remove from freelist + if (prev == null) + m_nextFree = tile.next; + else + prev.next = tile.next; + + // Restore salt. + tile.salt = decodePolyIdSalt((dtPolyRef)lastRef); + } + + // Make sure we could allocate a tile. + if (tile == null) + return DT_FAILURE | DT_OUT_OF_MEMORY; + + // Insert tile into the position lut. + int h = computeTileHash(header.x, header.y, m_tileLutMask); + tile.next = m_posLookup[h]; + m_posLookup[h] = tile; + + // Patch header pointers. + + //int vertsCount = 3*header.vertCount; + //int polysSize = header.polyCount; + //int linksSize = header.maxLinkCount; + //int detailMeshesSize = header.detailMeshCount; + //int detailVertsSize = 3*header.detailVertCount; + //int detailTrisSize = 4*header.detailTriCount; + //int bvtreeSize = header.bvNodeCount; + //int offMeshLinksSize = header.offMeshConCount; + + //byte* d = data + headerSize; + tile.verts = rawTileData.verts; + tile.polys = rawTileData.polys; + tile.links = rawTileData.links; + tile.detailMeshes = rawTileData.detailMeshes; + tile.detailVerts = rawTileData.detailVerts; + tile.detailTris = rawTileData.detailTris; + tile.bvTree = rawTileData.bvTree; + tile.offMeshCons = rawTileData.offMeshCons; + + // If there are no items in the bvtree, reset the tree pointer. + //c#: unnecessary, Cpp is afraid to point to whatever data ends up here + //if (bvtreeSize == 0) + // tile.bvTree = null; + + // Build links freelist + tile.linksFreeList = 0; + tile.links[header.maxLinkCount - 1].next = DT_NULL_LINK; + for (int i = 0; i < header.maxLinkCount - 1; ++i) + { + tile.links[i].next = (uint)i + 1; + } + + // Init tile. + tile.header = header; + tile.data = rawTileData; + //tile.dataSize = dataSize; + tile.flags = flags; + + connectIntLinks(tile); + baseOffMeshLinks(tile); + + // Create connections with neighbour tiles. + const int MAX_NEIS = 32; + dtMeshTile[] neis = new dtMeshTile[MAX_NEIS]; + int nneis; + + // Connect with layers in current tile. + nneis = getTilesAt(header.x, header.y, neis, MAX_NEIS); + for (int j = 0; j < nneis; ++j) + { + if (neis[j] != tile) + { + connectExtLinks(tile, neis[j], -1); + connectExtLinks(neis[j], tile, -1); + } + connectExtOffMeshLinks(tile, neis[j], -1); + connectExtOffMeshLinks(neis[j], tile, -1); + } + + // Connect with neighbour tiles. + for (int i = 0; i < 8; ++i) + { + nneis = getNeighbourTilesAt(header.x, header.y, i, neis, MAX_NEIS); + for (int j = 0; j < nneis; ++j) + { + connectExtLinks(tile, neis[j], i); + connectExtLinks(neis[j], tile, dtOppositeTile(i)); + connectExtOffMeshLinks(tile, neis[j], i); + connectExtOffMeshLinks(neis[j], tile, dtOppositeTile(i)); + } + } + + + result = getTileRef(tile); + + return DT_SUCCESS; + } + + /// Gets the tile at the specified grid location. + /// @param[in] x The tile's x-location. (x, y, layer) + /// @param[in] y The tile's y-location. (x, y, layer) + /// @param[in] layer The tile's layer. (x, y, layer) + /// @return The tile, or null if the tile does not exist. + public dtMeshTile getTileAt(int x, int y, int layer) + { + // Find tile based on hash. + int h = computeTileHash(x, y, m_tileLutMask); + dtMeshTile tile = m_posLookup[h]; + while (tile != null) + { + if (tile.header != null && + tile.header.x == x && + tile.header.y == y && + tile.header.layer == layer) + { + return tile; + } + tile = tile.next; + } + return null; + } + + public int getNeighbourTilesAt(int x, int y, int side, dtMeshTile[] tiles, int maxTiles) + { + int nx = x, ny = y; + switch (side) + { + case 0: nx++; break; + case 1: nx++; ny++; break; + case 2: ny++; break; + case 3: nx--; ny++; break; + case 4: nx--; break; + case 5: nx--; ny--; break; + case 6: ny--; break; + case 7: nx++; ny--; break; + }; + + return getTilesAt(nx, ny, tiles, maxTiles); + } + + + /// @par + /// + /// This function will not fail if the tiles array is too small to hold the + /// entire result set. It will simply fill the array to capacity. + /// Gets all tiles at the specified grid location. (All layers.) + /// @param[in] x The tile's x-location. (x, y) + /// @param[in] y The tile's y-location. (x, y) + /// @param[out] tiles A pointer to an array of tiles that will hold the result. + /// @param[in] maxTiles The maximum tiles the tiles parameter can hold. + /// @return The number of tiles returned in the tiles array. + public int getTilesAt(int x, int y, dtMeshTile[] tiles, int maxTiles) + { + int n = 0; + + // Find tile based on hash. + int h = computeTileHash(x, y, m_tileLutMask); + dtMeshTile tile = m_posLookup[h]; + while (tile != null) + { + if (tile.header != null && + tile.header.x == x && + tile.header.y == y) + { + if (n < maxTiles) + tiles[n++] = tile; + } + tile = tile.next; + } + + return n; + } + + /// Gets the tile reference for the tile at specified grid location. + /// @param[in] x The tile's x-location. (x, y, layer) + /// @param[in] y The tile's y-location. (x, y, layer) + /// @param[in] layer The tile's layer. (x, y, layer) + /// @return The tile reference of the tile, or 0 if there is none. + public dtTileRef getTileRefAt(int x, int y, int layer) + { + // Find tile based on hash. + int h = computeTileHash(x, y, m_tileLutMask); + dtMeshTile tile = m_posLookup[h]; + while (tile != null) + { + if (tile.header != null && + tile.header.x == x && + tile.header.y == y && + tile.header.layer == layer) + { + return getTileRef(tile); + } + tile = tile.next; + } + return 0; + } + /// Gets the tile for the specified tile reference. + /// @param[in] ref The tile reference of the tile to retrieve. + /// @return The tile for the specified reference, or null if the + /// reference is invalid. + public dtMeshTile getTileByRef(dtTileRef tileRef) + { + if (tileRef != 0) + return null; + uint tileIndex = decodePolyIdTile((dtPolyRef)tileRef); + uint tileSalt = decodePolyIdSalt((dtPolyRef)tileRef); + if ((int)tileIndex >= m_maxTiles) + return null; + dtMeshTile tile = m_tiles[tileIndex]; + if (tile.salt != tileSalt) + return null; + return tile; + } + + /// The maximum number of tiles supported by the navigation mesh. + /// @return The maximum number of tiles supported by the navigation mesh. + public int getMaxTiles() + { + return m_maxTiles; + } + + /// Gets the tile at the specified index. + /// @param[in] i The tile index. [Limit: 0 >= index < #getMaxTiles()] + /// @return The tile at the specified index. + public dtMeshTile getTile(int i) + { + return m_tiles[i]; + } + + /// Calculates the tile grid location for the specified world position. + /// @param[in] pos The world position for the query. [(x, y, z)] + /// @param[out] tx The tile's x-location. (x, y) + /// @param[out] ty The tile's y-location. (x, y) + public void calcTileLoc(float[] pos, ref int tx, ref int ty) + { + tx = (int)Math.Floor((pos[0] - m_orig[0]) / m_tileWidth); + ty = (int)Math.Floor((pos[2] - m_orig[2]) / m_tileHeight); + } + + /// Gets the tile and polygon for the specified polygon reference. + /// @param[in] ref The reference for the a polygon. + /// @param[out] tile The tile containing the polygon. + /// @param[out] poly The polygon. + /// @return The status flags for the operation. + public dtStatus getTileAndPolyByRef(dtPolyRef polyRef, ref dtMeshTile tile, ref dtPoly poly) + { + if (polyRef == 0) + return DT_FAILURE; + uint salt = 0, it = 0, ip = 0; + decodePolyId(polyRef, ref salt, ref it, ref ip); + if (it >= (uint)m_maxTiles) + return DT_FAILURE | DT_INVALID_PARAM; + if (m_tiles[it].salt != salt || m_tiles[it].header == null) + return DT_FAILURE | DT_INVALID_PARAM; + if (ip >= (uint)m_tiles[it].header.polyCount) + return DT_FAILURE | DT_INVALID_PARAM; + tile = m_tiles[it]; + poly = m_tiles[it].polys[ip]; + return DT_SUCCESS; + } + + //C# port : also return ip because the code used to do pointer arithmetics on the + // array's addresses, which is a no in C# because managed array may not be contiguous in memory + public dtStatus getTileAndPolyByRef(dtPolyRef polyRef, ref dtMeshTile tile, ref dtPoly poly, ref uint ip) + { + if (polyRef == 0) + return DT_FAILURE; + uint salt = 0, it = 0; + decodePolyId(polyRef, ref salt, ref it, ref ip); + if (it >= (uint)m_maxTiles) + return DT_FAILURE | DT_INVALID_PARAM; + if (m_tiles[it].salt != salt || m_tiles[it].header == null) + return DT_FAILURE | DT_INVALID_PARAM; + if (ip >= (uint)m_tiles[it].header.polyCount) + return DT_FAILURE | DT_INVALID_PARAM; + tile = m_tiles[it]; + poly = m_tiles[it].polys[ip]; + return DT_SUCCESS; + } + + /// @par + /// + /// @warning Only use this function if it is known that the provided polygon + /// reference is valid. This function is faster than #getTileAndPolyByRef, but + /// it does not validate the reference. + /// Returns the tile and polygon for the specified polygon reference. + /// @param[in] ref A known valid reference for a polygon. + /// @param[out] tile The tile containing the polygon. + /// @param[out] poly The polygon. + public void getTileAndPolyByRefUnsafe(dtPolyRef polyRef, ref dtMeshTile tile, ref dtPoly poly) + { + uint salt = 0, it = 0, ip = 0; + decodePolyId(polyRef, ref salt, ref it, ref ip); + tile = m_tiles[it]; + poly = m_tiles[it].polys[ip]; + } + + public void getTileAndPolyByRefUnsafe(dtPolyRef polyRef, ref dtMeshTile tile, ref dtPoly poly, ref uint ip) + { + uint salt = 0, it = 0; + decodePolyId(polyRef, ref salt, ref it, ref ip); + tile = m_tiles[it]; + poly = m_tiles[it].polys[ip]; + } + + /// Checks the validity of a polygon reference. + /// @param[in] ref The polygon reference to check. + /// @return True if polygon reference is valid for the navigation mesh. + public bool isValidPolyRef(dtPolyRef polyRef) + { + if (polyRef == 0) + return false; + uint salt = 0, it = 0, ip = 0; + decodePolyId(polyRef, ref salt, ref it, ref ip); + if (it >= (uint)m_maxTiles) + return false; + if (m_tiles[it].salt != salt || m_tiles[it].header == null) + return false; + if (ip >= (uint)m_tiles[it].header.polyCount) + return false; + return true; + } + + /// @par + /// + /// This function returns the data for the tile so that, if desired, + /// it can be added back to the navigation mesh at a later point. + /// + /// @see #addTile + /// Removes the specified tile from the navigation mesh. + /// @param[in] ref The reference of the tile to remove. + /// @param[out] data Data associated with deleted tile. + /// @param[out] dataSize Size of the data associated with deleted tile. + /// @return The status flags for the operation. + public dtStatus removeTile(dtTileRef tileRef, out dtRawTileData rawTileData) + { + rawTileData = null; + + if (tileRef == 0) + return DT_FAILURE | DT_INVALID_PARAM; + uint tileIndex = decodePolyIdTile((dtPolyRef)tileRef); + uint tileSalt = decodePolyIdSalt((dtPolyRef)tileRef); + if ((int)tileIndex >= m_maxTiles) + return DT_FAILURE | DT_INVALID_PARAM; + dtMeshTile tile = m_tiles[tileIndex]; + if (tile.salt != tileSalt) + return DT_FAILURE | DT_INVALID_PARAM; + + // Remove tile from hash lookup. + int h = computeTileHash(tile.header.x, tile.header.y, m_tileLutMask); + dtMeshTile prev = null; + dtMeshTile cur = m_posLookup[h]; + while (cur != null) + { + if (cur == tile) + { + if (prev != null) + prev.next = cur.next; + else + m_posLookup[h] = cur.next; + break; + } + prev = cur; + cur = cur.next; + } + + // Remove connections to neighbour tiles. + // Create connections with neighbour tiles. + const int MAX_NEIS = 32; + dtMeshTile[] neis = new dtMeshTile[MAX_NEIS]; + int nneis; + + // Connect with layers in current tile. + nneis = getTilesAt(tile.header.x, tile.header.y, neis, MAX_NEIS); + for (int j = 0; j < nneis; ++j) + { + if (neis[j] == tile) continue; + unconnectExtLinks(neis[j], tile); + } + + // Connect with neighbour tiles. + for (int i = 0; i < 8; ++i) + { + nneis = getNeighbourTilesAt(tile.header.x, tile.header.y, i, neis, MAX_NEIS); + for (int j = 0; j < nneis; ++j) + unconnectExtLinks(neis[j], tile); + } + + // Reset tile. + if ((tile.flags & (int)dtTileFlags.DT_TILE_FREE_DATA) != 0) + { + // Owns data + //dtFree(tile.data); + tile.data = null; + //tile.dataSize = 0; + //if (data) *data = 0; + //if (dataSize) *dataSize = 0; + rawTileData = null; + + } + else + { + //if (data) *data = tile.data; + //if (dataSize) *dataSize = tile.dataSize; + rawTileData = tile.data; + } + + tile.header = null; + tile.flags = 0; + tile.linksFreeList = 0; + tile.polys = null; + tile.verts = null; + tile.links = null; + tile.detailMeshes = null; + tile.detailVerts = null; + tile.detailTris = null; + tile.bvTree = null; + tile.offMeshCons = null; + + // Update salt, salt should never be zero. +#if DT_POLYREF64 + tile.salt = (tile.salt+1) & ((1<= #getTileStateSize] + /// @return The status flags for the operation. + dtStatus dtNavMesh::storeTileState(const dtMeshTile* tile, byte* data, const int maxDataSize) const + { + // Make sure there is enough space to store the state. + const int sizeReq = getTileStateSize(tile); + if (maxDataSize < sizeReq) + return DT_FAILURE | DT_BUFFER_TOO_SMALL; + + dtTileState* tileState = (dtTileState*)data; data += dtAlign4(sizeof(dtTileState)); + dtPolyState* polyStates = (dtPolyState*)data; data += dtAlign4(sizeof(dtPolyState) * tile.header.polyCount); + + // Store tile state. + tileState.magic = DT_NAVMESH_STATE_MAGIC; + tileState.version = DT_NAVMESH_STATE_VERSION; + tileState.ref = getTileRef(tile); + + // Store per poly state. + for (int i = 0; i < tile.header.polyCount; ++i) + { + const dtPoly* p = &tile.polys[i]; + dtPolyState* s = &polyStates[i]; + s.flags = p.flags; + s.area = p.getArea(); + } + + return DT_SUCCESS; + } + + /// @par + /// + /// Tile state includes non-structural data such as polygon flags, area ids, etc. + /// @note This function does not impact the tile's #dtTileRef and #dtPolyRef's. + /// @see #storeTileState + /// Restores the state of the tile. + /// @param[in] tile The tile. + /// @param[in] data The new state. (Obtained from #storeTileState.) + /// @param[in] maxDataSize The size of the state within the data buffer. + /// @return The status flags for the operation. + dtStatus dtNavMesh::restoreTileState(dtMeshTile* tile, const byte* data, const int maxDataSize) + { + // Make sure there is enough space to store the state. + const int sizeReq = getTileStateSize(tile); + if (maxDataSize < sizeReq) + return DT_FAILURE | DT_INVALID_PARAM; + + const dtTileState* tileState = (const dtTileState*)data; data += dtAlign4(sizeof(dtTileState)); + const dtPolyState* polyStates = (const dtPolyState*)data; data += dtAlign4(sizeof(dtPolyState) * tile.header.polyCount); + + // Check that the restore is possible. + if (tileState.magic != DT_NAVMESH_STATE_MAGIC) + return DT_FAILURE | DT_WRONG_MAGIC; + if (tileState.version != DT_NAVMESH_STATE_VERSION) + return DT_FAILURE | DT_WRONG_VERSION; + if (tileState.ref != getTileRef(tile)) + return DT_FAILURE | DT_INVALID_PARAM; + + // Restore per poly state. + for (int i = 0; i < tile.header.polyCount; ++i) + { + dtPoly* p = &tile.polys[i]; + const dtPolyState* s = &polyStates[i]; + p.flags = s.flags; + p.setArea(s.area); + } + + return DT_SUCCESS; + } + */ + /// @par + /// + /// Off-mesh connections are stored in the navigation mesh as special 2-vertex + /// polygons with a single edge. At least one of the vertices is expected to be + /// inside a normal polygon. So an off-mesh connection is "entered" from a + /// normal polygon at one of its endpoints. This is the polygon identified by + /// the prevRef parameter. + /// Gets the endpoints for an off-mesh connection, ordered by "direction of travel". + /// @param[in] prevRef The reference of the polygon before the connection. + /// @param[in] polyRef The reference of the off-mesh connection polygon. + /// @param[out] startPos The start position of the off-mesh connection. [(x, y, z)] + /// @param[out] endPos The end position of the off-mesh connection. [(x, y, z)] + /// @return The status flags for the operation. + public dtStatus getOffMeshConnectionPolyEndPoints(dtPolyRef prevRef, dtPolyRef polyRef, float[] startPos, float[] endPos) + { + uint salt = 0, it = 0, ip = 0; + + if (polyRef == 0) + return DT_FAILURE; + + // Get current polygon + decodePolyId(polyRef, ref salt, ref it, ref ip); + if (it >= (uint)m_maxTiles) return DT_FAILURE | DT_INVALID_PARAM; + if (m_tiles[it].salt != salt || m_tiles[it].header == null) return DT_FAILURE | DT_INVALID_PARAM; + dtMeshTile tile = m_tiles[it]; + if (ip >= (uint)tile.header.polyCount) return DT_FAILURE | DT_INVALID_PARAM; + dtPoly poly = tile.polys[ip]; + + // Make sure that the current poly is indeed off-mesh link. + if (poly.getType() != (byte)dtPolyTypes.DT_POLYTYPE_OFFMESH_CONNECTION) + return DT_FAILURE; + + // Figure out which way to hand out the vertices. + int idx0 = 0, idx1 = 1; + + // Find link that points to first vertex. + for (uint i = poly.firstLink; i != DT_NULL_LINK; i = tile.links[i].next) + { + if (tile.links[i].edge == 0) + { + if (tile.links[i].polyRef != prevRef) + { + idx0 = 1; + idx1 = 0; + } + break; + } + } + + dtVcopy(startPos, 0, tile.verts, poly.verts[idx0] * 3); + dtVcopy(endPos, 0, tile.verts, poly.verts[idx1] * 3); + + return DT_SUCCESS; + } + + /// Gets the specified off-mesh connection. + /// @param[in] ref The polygon reference of the off-mesh connection. + /// @return The specified off-mesh connection, or null if the polygon reference is not valid. + public dtOffMeshConnection getOffMeshConnectionByRef(dtPolyRef polyRef) + { + uint salt = 0, it = 0, ip = 0; + + if (polyRef == 0) + return null; + + // Get current polygon + decodePolyId(polyRef, ref salt, ref it, ref ip); + if (it >= (uint)m_maxTiles) return null; + if (m_tiles[it].salt != salt || m_tiles[it].header == null) return null; + dtMeshTile tile = m_tiles[it]; + if (ip >= (uint)tile.header.polyCount) return null; + dtPoly poly = tile.polys[ip]; + + // Make sure that the current poly is indeed off-mesh link. + if (poly.getType() != (byte)dtPolyTypes.DT_POLYTYPE_OFFMESH_CONNECTION) + return null; + + uint idx = (uint)(ip - tile.header.offMeshBase); + Debug.Assert(idx < (uint)tile.header.offMeshConCount); + return tile.offMeshCons[idx]; + } + + /// @{ + /// @name State Management + /// These functions do not effect #dtTileRef or #dtPolyRef's. + + /// Sets the user defined flags for the specified polygon. + /// @param[in] ref The polygon reference. + /// @param[in] flags The new flags for the polygon. + /// @return The status flags for the operation. + public dtStatus setPolyFlags(dtPolyRef polyRef, ushort flags) + { + if (polyRef == 0) return DT_FAILURE; + uint salt = 0, it = 0, ip = 0; + decodePolyId(polyRef, ref salt, ref it, ref ip); + if (it >= (uint)m_maxTiles) return DT_FAILURE | DT_INVALID_PARAM; + if (m_tiles[it].salt != salt || m_tiles[it].header == null) return DT_FAILURE | DT_INVALID_PARAM; + dtMeshTile tile = m_tiles[it]; + if (ip >= (uint)tile.header.polyCount) return DT_FAILURE | DT_INVALID_PARAM; + dtPoly poly = tile.polys[ip]; + + // Change flags. + poly.flags = flags; + + return DT_SUCCESS; + } + + //dtStatus setPolyFlags(dtPolyRef ref, ushort flags); + + /// Gets the user defined flags for the specified polygon. + /// @param[in] ref The polygon reference. + /// @param[out] resultFlags The polygon flags. + /// @return The status flags for the operation. + public dtStatus getPolyFlags(dtPolyRef polyRef, ref ushort resultFlags) + { + if (polyRef == 0) return DT_FAILURE; + uint salt = 0, it = 0, ip = 0; + decodePolyId(polyRef, ref salt, ref it, ref ip); + if (it >= (uint)m_maxTiles) return DT_FAILURE | DT_INVALID_PARAM; + if (m_tiles[it].salt != salt || m_tiles[it].header == null) return DT_FAILURE | DT_INVALID_PARAM; + dtMeshTile tile = m_tiles[it]; + if (ip >= (uint)tile.header.polyCount) return DT_FAILURE | DT_INVALID_PARAM; + dtPoly poly = tile.polys[ip]; + + resultFlags = poly.flags; + + return DT_SUCCESS; + } + + /// Sets the user defined area for the specified polygon. + /// @param[in] ref The polygon reference. + /// @param[in] area The new area id for the polygon. [Limit: < #DT_MAX_AREAS] + /// @return The status flags for the operation. + public dtStatus setPolyArea(dtPolyRef polyRef, byte area) + { + if (polyRef == 0) return DT_FAILURE; + uint salt = 0, it = 0, ip = 0; + decodePolyId(polyRef, ref salt, ref it, ref ip); + if (it >= (uint)m_maxTiles) return DT_FAILURE | DT_INVALID_PARAM; + if (m_tiles[it].salt != salt || m_tiles[it].header == null) return DT_FAILURE | DT_INVALID_PARAM; + dtMeshTile tile = m_tiles[it]; + if (ip >= (uint)tile.header.polyCount) return DT_FAILURE | DT_INVALID_PARAM; + dtPoly poly = tile.polys[ip]; + + poly.setArea(area); + + return DT_SUCCESS; + } + + /// Gets the user defined area for the specified polygon. + /// @param[in] ref The polygon reference. + /// @param[out] resultArea The area id for the polygon. + /// @return The status flags for the operation. + public dtStatus getPolyArea(dtPolyRef polyRef, ref byte resultArea) + { + if (polyRef == 0) return DT_FAILURE; + uint salt = 0, it = 0, ip = 0; + decodePolyId(polyRef, ref salt, ref it, ref ip); + if (it >= (uint)m_maxTiles) return DT_FAILURE | DT_INVALID_PARAM; + if (m_tiles[it].salt != salt || m_tiles[it].header == null) return DT_FAILURE | DT_INVALID_PARAM; + dtMeshTile tile = m_tiles[it]; + if (ip >= (uint)tile.header.polyCount) return DT_FAILURE | DT_INVALID_PARAM; + dtPoly poly = tile.polys[ip]; + + resultArea = poly.getArea(); + + return DT_SUCCESS; + } + } +} + diff --git a/Framework/RecastDetour/Detour/DetourNavMeshBuilder.cs b/Framework/RecastDetour/Detour/DetourNavMeshBuilder.cs new file mode 100644 index 000000000..31dc64005 --- /dev/null +++ b/Framework/RecastDetour/Detour/DetourNavMeshBuilder.cs @@ -0,0 +1,1632 @@ +using System; +using System.Collections.Generic; + +using dtPolyRef = System.UInt64; + +public static partial class Detour +{ + /// The maximum number of vertices per navigation polygon. + /// @ingroup detour + public const int DT_VERTS_PER_POLYGON = 6; + + /// @{ + /// @name Tile Serialization Constants + /// These constants are used to detect whether a navigation tile's data + /// and state format is compatible with the current build. + /// + + /// A magic number used to detect compatibility of navigation tile data. + public const int DT_NAVMESH_MAGIC = 'D' << 24 | 'N' << 16 | 'A' << 8 | 'V'; + + /// A version number used to detect compatibility of navigation tile data. + public const int DT_NAVMESH_VERSION = 7; + + /// A magic number used to detect the compatibility of navigation tile states. + public const int DT_NAVMESH_STATE_MAGIC = 'D' << 24 | 'N' << 16 | 'M' << 8 | 'S'; + + /// A version number used to detect compatibility of navigation tile states. + public const int DT_NAVMESH_STATE_VERSION = 1; + + /// @} + + /// A flag that indicates that an entity links to an external entity. + /// (E.g. A polygon edge is a portal that links to another polygon.) + public const ushort DT_EXT_LINK = 0x8000; + + /// A value that indicates the entity does not link to anything. + public const uint DT_NULL_LINK = 0xffffffff; + + /// A flag that indicates that an off-mesh connection can be traversed in both directions. (Is bidirectional.) + public const uint DT_OFFMESH_CON_BIDIR = 1; + + /// The maximum number of user defined area ids. + /// @ingroup detour + public const int DT_MAX_AREAS = 64; + + const ushort MESH_NULL_IDX = 0xffff; + + /// Tile flags used for various functions and fields. + /// For an example, see dtNavMesh::addTile(). + public enum dtTileFlags + { + /// The navigation mesh owns the tile memory and is responsible for freeing it. + DT_TILE_FREE_DATA = 0x01, + }; + + /// Vertex flags returned by dtNavMeshQuery::findStraightPath. + public enum dtStraightPathFlags + { + DT_STRAIGHTPATH_START = 0x01, //< The vertex is the start position in the path. + DT_STRAIGHTPATH_END = 0x02, //< The vertex is the end position in the path. + DT_STRAIGHTPATH_OFFMESH_CONNECTION = 0x04, //< The vertex is the start of an off-mesh connection. + }; + + /// Options for dtNavMeshQuery::findStraightPath. + public enum dtStraightPathOptions + { + DT_STRAIGHTPATH_AREA_CROSSINGS = 0x01, //< Add a vertex at every polygon edge crossing where area changes. + DT_STRAIGHTPATH_ALL_CROSSINGS = 0x02, //< Add a vertex at every polygon edge crossing. + }; + + /// Flags representing the type of a navigation mesh polygon. + public enum dtPolyTypes + { + /// The polygon is a standard convex polygon that is part of the surface of the mesh. + DT_POLYTYPE_GROUND = 0, + /// The polygon is an off-mesh connection consisting of two vertices. + DT_POLYTYPE_OFFMESH_CONNECTION = 1, + }; + + + /// Defines a polyogn within a dtMeshTile object. + /// @ingroup detour + public class dtPoly + { + /// Index to first link in linked list. (Or #DT_NULL_LINK if there is no link.) + public uint firstLink; + + /// The indices of the polygon's vertices. + /// The actual vertices are located in dtMeshTile::verts. + public ushort[] verts = new ushort[DT_VERTS_PER_POLYGON]; + + /// Packed data representing neighbor polygons references and flags for each edge. + /* + @var ushort dtPoly::neis[DT_VERTS_PER_POLYGON] + @par + + Each entry represents data for the edge starting at the vertex of the same index. + E.g. The entry at index n represents the edge data for vertex[n] to vertex[n+1]. + + A value of zero indicates the edge has no polygon connection. (It makes up the + border of the navigation mesh.) + + The information can be extracted as follows: + @code + neighborRef = neis[n] & 0xff; // Get the neighbor polygon reference. + + if (neis[n] & #DT_EX_LINK) + { + // The edge is an external (portal) edge. + } + @endcode + * */ + public ushort[] neis = new ushort[DT_VERTS_PER_POLYGON]; + + /// The user defined polygon flags. + public ushort flags; + + /// The number of vertices in the polygon. + public byte vertCount; + + /// The bit packed area id and polygon type. + /// @note Use the structure's set and get methods to acess this value. + public byte areaAndtype; + + public int FromBytes(byte[] array, int start) + { + firstLink = BitConverter.ToUInt32(array, start); start += sizeof(uint); + for (int i = 0; i < DT_VERTS_PER_POLYGON; ++i) + { + verts[i] = BitConverter.ToUInt16(array, start); start += sizeof(ushort); + } + for (int i = 0; i < DT_VERTS_PER_POLYGON; ++i) + { + neis[i] = BitConverter.ToUInt16(array, start); start += sizeof(ushort); + } + flags = BitConverter.ToUInt16(array, start); start += sizeof(ushort); + vertCount = array[start]; start += sizeof(byte); + areaAndtype = array[start]; start += sizeof(byte); + return start; + } + + public byte[] ToBytes() + { + List bytes = new List(); + + bytes.AddRange(BitConverter.GetBytes(firstLink)); + for (int i = 0; i < DT_VERTS_PER_POLYGON; ++i) + { + bytes.AddRange(BitConverter.GetBytes(verts[i])); + } + for (int i = 0; i < DT_VERTS_PER_POLYGON; ++i) + { + bytes.AddRange(BitConverter.GetBytes(neis[i])); + } + bytes.AddRange(BitConverter.GetBytes(flags)); + bytes.Add(vertCount); + bytes.Add(areaAndtype); + + return bytes.ToArray(); + } + /// Sets the user defined area id. [Limit: < #DT_MAX_AREAS] + public void setArea(byte a) + { + //Bitwise operators are done on ints in C# + areaAndtype = (byte)(((int)areaAndtype & 0xc0) | ((int)a & 0x3f)); + } + + /// Sets the polygon type. (See: #dtPolyTypes.) + public void setType(byte t) + { + areaAndtype = (byte)(((int)areaAndtype & 0x3f) | (t << 6)); + } + + /// Gets the user defined area id. + public byte getArea() + { + return (byte)((int)areaAndtype & 0x3f); + } + + /// Gets the polygon type. (See: #dtPolyTypes) + public byte getType() + { + return (byte)((int)areaAndtype >> 6); + } + }; + + /// Defines the location of detail sub-mesh data within a dtMeshTile. + public class dtPolyDetail + { + public uint vertBase; //< The offset of the vertices in the dtMeshTile::detailVerts array. + public uint triBase; //< The offset of the triangles in the dtMeshTile::detailTris array. + public byte vertCount; //< The number of vertices in the sub-mesh. + public byte triCount; //< The number of triangles in the sub-mesh. + + public int FromBytes(byte[] array, int start) + { + vertBase = BitConverter.ToUInt32(array, start); start += sizeof(uint); + triBase = BitConverter.ToUInt32(array, start); start += sizeof(uint); + vertCount = array[start]; start += sizeof(byte); + triCount = array[start]; start += sizeof(byte); + start += sizeof(ushort); //two byte padding + return start; + } + + public byte[] ToBytes() + { + List bytes = new List(); + + bytes.AddRange(BitConverter.GetBytes(vertBase)); + bytes.AddRange(BitConverter.GetBytes(triBase)); + bytes.AddRange(BitConverter.GetBytes(vertCount)); + bytes.AddRange(BitConverter.GetBytes(triCount)); + + return bytes.ToArray(); + } + }; + + /// Defines a link between polygons. + /// @note This structure is rarely if ever used by the end user. + /// @see dtMeshTile + public class dtLink + { + public dtPolyRef polyRef; //< Neighbour reference. (The neighbor that is linked to.) + public uint next; //< Index of the next link. + public byte edge; //< Index of the polygon edge that owns this link. + public byte side; //< If a boundary link, defines on which side the link is. + public byte bmin; //< If a boundary link, defines the minimum sub-edge area. + public byte bmax; //< If a boundary link, defines the maximum sub-edge area. + + public int FromBytes(byte[] array, int start) + { + polyRef = BitConverter.ToUInt64(array, start); start += sizeof(ulong); + next = BitConverter.ToUInt32(array, start); start += sizeof(uint); + edge = array[start]; start += sizeof(byte); + side = array[start]; start += sizeof(byte); + bmin = array[start]; start += sizeof(byte); + bmax = array[start]; start += sizeof(byte); + return start; + } + + public byte[] ToBytes() + { + List bytes = new List(); + + bytes.AddRange(BitConverter.GetBytes(polyRef)); + bytes.AddRange(BitConverter.GetBytes(next)); + bytes.Add(edge); + bytes.Add(side); + bytes.Add(bmin); + bytes.Add(bmax); + + return bytes.ToArray(); + } + }; + + /// Bounding volume node. + /// @note This structure is rarely if ever used by the end user. + /// @see dtMeshTile + public class dtBVNode + { + public ushort[] bmin = new ushort[3]; //< Minimum bounds of the node's AABB. [(x, y, z)] + public ushort[] bmax = new ushort[3]; //< Maximum bounds of the node's AABB. [(x, y, z)] + public int i; //< The node's index. (Negative for escape sequence.) + + public int FromBytes(byte[] array, int start) + { + for (int j = 0; j < bmin.Length; ++j) + { + bmin[j] = BitConverter.ToUInt16(array, start); start += sizeof(ushort); + } + for (int j = 0; j < bmax.Length; ++j) + { + bmax[j] = BitConverter.ToUInt16(array, start); start += sizeof(ushort); + } + i = BitConverter.ToInt32(array, start); start += sizeof(int); + return start; + } + + public byte[] ToBytes() + { + List bytes = new List(); + for (int j = 0; j < bmin.Length; ++j) + { + bytes.AddRange(BitConverter.GetBytes(bmin[j])); + } + for (int j = 0; j < bmax.Length; ++j) + { + bytes.AddRange(BitConverter.GetBytes(bmax[j])); + } + bytes.AddRange(BitConverter.GetBytes(i)); + + return bytes.ToArray(); + } + }; + + /// Defines an navigation mesh off-mesh connection within a dtMeshTile object. + /// An off-mesh connection is a user defined traversable connection made up to two vertices. + public class dtOffMeshConnection + { + /// The endpoints of the connection. [(ax, ay, az, bx, by, bz)] + public float[] pos = new float[6]; + + /// The radius of the endpoints. [Limit: >= 0] + public float rad; + + /// The polygon reference of the connection within the tile. + public ushort poly; + + /// Link flags. + /// @note These are not the connection's user defined flags. Those are assigned via the + /// connection's dtPoly definition. These are link flags used for internal purposes. + public byte flags; + + /// End point side. + public byte side; + + /// The id of the offmesh connection. (User assigned when the navigation mesh is built.) + public uint userId; + + + public int FromBytes(byte[] array, int start) + { + for (int i = 0; i < 6; ++i) + { + pos[i] = BitConverter.ToSingle(array, start); start += sizeof(float); + } + rad = BitConverter.ToSingle(array, start); start += sizeof(float); + poly = BitConverter.ToUInt16(array, start); start += sizeof(ushort); + flags = array[start]; ++start; + side = array[start]; ++start; + userId = BitConverter.ToUInt32(array, start); start += sizeof(uint); + return start; + } + + public byte[] ToBytes() + { + List bytes = new List(); + for (int i = 0; i < 6; ++i) + { + bytes.AddRange(BitConverter.GetBytes(pos[i])); + } + bytes.AddRange(BitConverter.GetBytes(rad)); + bytes.AddRange(BitConverter.GetBytes(poly)); + bytes.Add(flags); + bytes.Add(side); + bytes.AddRange(BitConverter.GetBytes(userId)); + + return bytes.ToArray(); + } + }; + + + + /// Provides high level information related to a dtMeshTile object. + /// @ingroup detour + public class dtMeshHeader + { + public int magic; //< Tile magic number. (Used to identify the data format.) + public int version; //< Tile data format version number. + public int x; //< The x-position of the tile within the dtNavMesh tile grid. (x, y, layer) + public int y; //< The y-position of the tile within the dtNavMesh tile grid. (x, y, layer) + public int layer; //< The layer of the tile within the dtNavMesh tile grid. (x, y, layer) + public uint userId; //< The user defined id of the tile. + public int polyCount; //< The number of polygons in the tile. + public int vertCount; //< The number of vertices in the tile. + public int maxLinkCount; //< The number of allocated links. + public int detailMeshCount; //< The number of sub-meshes in the detail mesh. + + /// The number of unique vertices in the detail mesh. (In addition to the polygon vertices.) + public int detailVertCount; + + public int detailTriCount; //< The number of triangles in the detail mesh. + public int bvNodeCount; //< The number of bounding volume nodes. (Zero if bounding volumes are disabled.) + public int offMeshConCount; //< The number of off-mesh connections. + public int offMeshBase; //< The index of the first polygon which is an off-mesh connection. + public float walkableHeight; //< The height of the agents using the tile. + public float walkableRadius; //< The radius of the agents using the tile. + public float walkableClimb; //< The maximum climb height of the agents using the tile. + public float[] bmin = new float[3]; //< The minimum bounds of the tile's AABB. [(x, y, z)] + public float[] bmax = new float[3]; //< The maximum bounds of the tile's AABB. [(x, y, z)] + + /// The bounding volume quantization factor. + /* + @var float dtMeshHeader::bvQuantFactor + @par + + This value is used for converting between world and bounding volume coordinates. + For example: + @code + const float cs = 1.0f / tile.header.bvQuantFactor; + const dtBVNode* n = &tile.bvTree[i]; + if (n.i >= 0) + { + // This is a leaf node. + float worldMinX = tile.header.bmin[0] + n.bmin[0]*cs; + float worldMinY = tile.header.bmin[0] + n.bmin[1]*cs; + // Etc... + } + @endcode */ + public float bvQuantFactor; + + public int FromBytes(byte[] array, int start) + { + magic = BitConverter.ToInt32(array, start); start += sizeof(int); + version = BitConverter.ToInt32(array, start); start += sizeof(int); + x = BitConverter.ToInt32(array, start); start += sizeof(int); + y = BitConverter.ToInt32(array, start); start += sizeof(int); + layer = BitConverter.ToInt32(array, start); start += sizeof(int); + userId = BitConverter.ToUInt32(array, start); start += sizeof(uint); + polyCount = BitConverter.ToInt32(array, start); start += sizeof(int); + vertCount = BitConverter.ToInt32(array, start); start += sizeof(int); + maxLinkCount = BitConverter.ToInt32(array, start); start += sizeof(int); + detailMeshCount = BitConverter.ToInt32(array, start); start += sizeof(int); + + detailVertCount = BitConverter.ToInt32(array, start); start += sizeof(int); + + detailTriCount = BitConverter.ToInt32(array, start); start += sizeof(int); + bvNodeCount = BitConverter.ToInt32(array, start); start += sizeof(int); + offMeshConCount = BitConverter.ToInt32(array, start); start += sizeof(int); + offMeshBase = BitConverter.ToInt32(array, start); start += sizeof(int); + walkableHeight = BitConverter.ToSingle(array, start); start += sizeof(float); + walkableRadius = BitConverter.ToSingle(array, start); start += sizeof(float); + walkableClimb = BitConverter.ToSingle(array, start); start += sizeof(float); + + for (int i = 0; i < 3; ++i) + { + bmin[i] = BitConverter.ToSingle(array, start); start += sizeof(float); + } + for (int i = 0; i < 3; ++i) + { + bmax[i] = BitConverter.ToSingle(array, start); start += sizeof(float); + } + + bvQuantFactor = BitConverter.ToSingle(array, start); start += sizeof(float); + return start; + } + + public byte[] ToBytes() + { + List bytes = new List(); + + bytes.AddRange(BitConverter.GetBytes(magic)); + bytes.AddRange(BitConverter.GetBytes(version)); + bytes.AddRange(BitConverter.GetBytes(x)); + bytes.AddRange(BitConverter.GetBytes(y)); + bytes.AddRange(BitConverter.GetBytes(layer)); + bytes.AddRange(BitConverter.GetBytes(userId)); + bytes.AddRange(BitConverter.GetBytes(polyCount)); + bytes.AddRange(BitConverter.GetBytes(vertCount)); + bytes.AddRange(BitConverter.GetBytes(maxLinkCount)); + bytes.AddRange(BitConverter.GetBytes(detailMeshCount)); + + bytes.AddRange(BitConverter.GetBytes(detailVertCount)); + + bytes.AddRange(BitConverter.GetBytes(detailTriCount)); + bytes.AddRange(BitConverter.GetBytes(bvNodeCount)); + bytes.AddRange(BitConverter.GetBytes(offMeshConCount)); + bytes.AddRange(BitConverter.GetBytes(offMeshBase)); + bytes.AddRange(BitConverter.GetBytes(walkableHeight)); + bytes.AddRange(BitConverter.GetBytes(walkableRadius)); + bytes.AddRange(BitConverter.GetBytes(walkableClimb)); + for (int i = 0; i < 3; ++i) + { + bytes.AddRange(BitConverter.GetBytes(bmin[i])); + } + for (int i = 0; i < 3; ++i) + { + bytes.AddRange(BitConverter.GetBytes(bmax[i])); + } + bytes.AddRange(BitConverter.GetBytes(bvQuantFactor)); + return bytes.ToArray(); + } + }; + + + public class dtRawTileData + { + public dtMeshHeader header; //< The tile header. + public dtPoly[] polys; //< The tile polygons. [Size: dtMeshHeader::polyCount] + public float[] verts; //< The tile vertices. [Size: dtMeshHeader::vertCount] + public dtLink[] links; //< The tile links. [Size: dtMeshHeader::maxLinkCount] + public dtPolyDetail[] detailMeshes; //< The tile's detail sub-meshes. [Size: dtMeshHeader::detailMeshCount] + + /// The detail mesh's unique vertices. [(x, y, z) * dtMeshHeader::detailVertCount] + public float[] detailVerts; + + /// The detail mesh's triangles. [(vertA, vertB, vertC) * dtMeshHeader::detailTriCount] + public byte[] detailTris; + + /// The tile bounding volume nodes. [Size: dtMeshHeader::bvNodeCount] + /// (Will be null if bounding volumes are disabled.) + public dtBVNode[] bvTree; + + public dtOffMeshConnection[] offMeshCons; //< The tile off-mesh connections. [Size: dtMeshHeader::offMeshConCount] + + public int flags; //< Tile flags. (See: #dtTileFlags) + + public int FromBytes(byte[] array, int start) + { + header = new dtMeshHeader(); + start = header.FromBytes(array, start); + + int count = header.vertCount * 3; + verts = new float[count]; + int c = 0; + for (; c < count; ++c) + { + verts[c] = BitConverter.ToSingle(array, start); + start += sizeof(float); + } + count = header.polyCount; + polys = new dtPoly[count]; + for (int i = 0; i < count; ++i) + { + polys[i] = new dtPoly(); + start = polys[i].FromBytes(array, start); + } + count = header.maxLinkCount; + links = new dtLink[count]; + for (int i = 0; i < count; ++i) + { + links[i] = new dtLink(); + start = links[i].FromBytes(array, start); + } + count = header.detailMeshCount; + detailMeshes = new dtPolyDetail[count]; + for (int i = 0; i < count; ++i) + { + detailMeshes[i] = new dtPolyDetail(); + start = detailMeshes[i].FromBytes(array, start); + } + count = header.detailVertCount * 3; + detailVerts = new float[count]; + for (int i = 0; i < count; ++i) + { + detailVerts[i] = BitConverter.ToSingle(array, start); + start += sizeof(float); + } + count = header.detailTriCount * 4; + detailTris = new byte[count]; + for (int i = 0; i < count; ++i) + { + detailTris[i] = array[start + i]; + } + start += count; + count = header.bvNodeCount; + bvTree = new dtBVNode[count]; + for (int i = 0; i < count; ++i) + { + bvTree[i] = new dtBVNode(); + start = bvTree[i].FromBytes(array, start); + } + count = header.offMeshConCount; + offMeshCons = new dtOffMeshConnection[count]; + for (int i = 0; i < count; ++i) + { + offMeshCons[i] = new dtOffMeshConnection(); + start = offMeshCons[i].FromBytes(array, start); + } + //flags = BitConverter.ToInt32(array, start); + //start += sizeof(int); + return start; + } + + public byte[] ToBytes() + { + List bytes = new List(); + + bytes.AddRange(header.ToBytes()); + for (int i = 0; i < polys.Length; ++i) + { + bytes.AddRange(polys[i].ToBytes()); + } + for (int i = 0; i < verts.Length; ++i) + { + bytes.AddRange(BitConverter.GetBytes(verts[i])); + } + for (int i = 0; i < links.Length; ++i) + { + bytes.AddRange(links[i].ToBytes()); + } + for (int i = 0; i < detailMeshes.Length; ++i) + { + bytes.AddRange(detailMeshes[i].ToBytes()); + } + for (int i = 0; i < detailVerts.Length; ++i) + { + bytes.AddRange(BitConverter.GetBytes(detailVerts[i])); + } + for (int i = 0; i < detailTris.Length; ++i) + { + bytes.Add(detailTris[i]); + } + for (int i = 0; i < bvTree.Length; ++i) + { + bytes.AddRange(bvTree[i].ToBytes()); + } + for (int i = 0; i < offMeshCons.Length; ++i) + { + bytes.AddRange(offMeshCons[i].ToBytes()); + } + bytes.AddRange(BitConverter.GetBytes(flags)); + + return bytes.ToArray(); + } + } + /// Defines a navigation mesh tile. + /// @ingroup detour + /* + @struct dtMeshTile + @par + + Tiles generally only exist within the context of a dtNavMesh object. + + Some tile content is optional. For example, a tile may not contain any + off-mesh connections. In this case the associated pointer will be null. + + If a detail mesh exists it will share vertices with the base polygon mesh. + Only the vertices unique to the detail mesh will be stored in #detailVerts. + + @warning Tiles returned by a dtNavMesh object are not guarenteed to be populated. + For example: The tile at a location might not have been loaded yet, or may have been removed. + In this case, pointers will be null. So if in doubt, check the polygon count in the + tile's header to determine if a tile has polygons defined. + + @var float dtOffMeshConnection::pos[6] + @par + + For a properly built navigation mesh, vertex A will always be within the bounds of the mesh. + Vertex B is not required to be within the bounds of the mesh. + + */ + public class dtMeshTile + { + public uint salt; //< Counter describing modifications to the tile. + + public uint linksFreeList; //< Index to the next free link. + public dtMeshHeader header; //< The tile header. + public dtPoly[] polys; //< The tile polygons. [Size: dtMeshHeader::polyCount] + public float[] verts; //< The tile vertices. [Size: dtMeshHeader::vertCount] + public dtLink[] links; //< The tile links. [Size: dtMeshHeader::maxLinkCount] + public dtPolyDetail[] detailMeshes; //< The tile's detail sub-meshes. [Size: dtMeshHeader::detailMeshCount] + + /// The detail mesh's unique vertices. [(x, y, z) * dtMeshHeader::detailVertCount] + public float[] detailVerts; + + /// The detail mesh's triangles. [(vertA, vertB, vertC) * dtMeshHeader::detailTriCount] + public byte[] detailTris; + + /// The tile bounding volume nodes. [Size: dtMeshHeader::bvNodeCount] + /// (Will be null if bounding volumes are disabled.) + public dtBVNode[] bvTree; + + public dtOffMeshConnection[] offMeshCons; //< The tile off-mesh connections. [Size: dtMeshHeader::offMeshConCount] + + public dtRawTileData data; //< The tile data. (Not directly accessed under normal situations.) + public int dataSize; //< Size of the tile data. + public int flags; //< Tile flags. (See: #dtTileFlags) + public dtMeshTile next; //< The next free tile, or the next tile in the spatial grid. + }; + + /// Configuration parameters used to define multi-tile navigation meshes. + /// The values are used to allocate space during the initialization of a navigation mesh. + /// @see dtNavMesh::init() + /// @ingroup detour + public class dtNavMeshParams + { + public float[] orig = new float[3]; //< The world space origin of the navigation mesh's tile space. [(x, y, z)] + public float tileWidth; //< The width of each tile. (Along the x-axis.) + public float tileHeight; //< The height of each tile. (Along the z-axis.) + public int maxTiles; //< The maximum number of tiles the navigation mesh can contain. + public int maxPolys; //< The maximum number of polygons each tile can contain. + /// + public dtNavMeshParams Clone() + { + dtNavMeshParams copy = new dtNavMeshParams(); + for (int i = 0; i < orig.Length; ++i) + { + copy.orig[i] = orig[i]; + } + copy.tileWidth = tileWidth; + copy.tileHeight = tileHeight; + copy.maxTiles = maxTiles; + copy.maxPolys = maxPolys; + return copy; + } + }; + + /// Represents the source data used to build an navigation mesh tile. + /// @ingroup detour + /** + @struct dtNavMeshCreateParams + @par + + This structure is used to marshal data between the Recast mesh generation pipeline and Detour navigation components. + + See the rcPolyMesh and rcPolyMeshDetail documentation for detailed information related to mesh structure. + + Units are usually in voxels (vx) or world units (wu). The units for voxels, grid size, and cell size + are all based on the values of #cs and #ch. + + The standard navigation mesh build process is to create tile data using dtCreateNavMeshData, then add the tile + to a navigation mesh using either the dtNavMesh single tile init() function or the dtNavMesh::addTile() + function. + + @see dtCreateNavMeshData + + */ + public class dtNavMeshCreateParams + { + + /// @name Polygon Mesh Attributes + /// Used to create the base navigation graph. + /// See #rcPolyMesh for details related to these attributes. + /// @{ + + public ushort[] verts; //< The polygon mesh vertices. [(x, y, z) * #vertCount] [Unit: vx] + public int vertCount; //< The number vertices in the polygon mesh. [Limit: >= 3] + public ushort[] polys; //< The polygon data. [Size: #polyCount * 2 * #nvp] + public ushort[] polyFlags; //< The user defined flags assigned to each polygon. [Size: #polyCount] + public byte[] polyAreas; //< The user defined area ids assigned to each polygon. [Size: #polyCount] + public int polyCount; //< Number of polygons in the mesh. [Limit: >= 1] + public int nvp; //< Number maximum number of vertices per polygon. [Limit: >= 3] + + /// @} + /// @name Height Detail Attributes (Optional) + /// See #rcPolyMeshDetail for details related to these attributes. + /// @{ + + public uint[] detailMeshes; //< The height detail sub-mesh data. [Size: 4 * #polyCount] + public float[] detailVerts; //< The detail mesh vertices. [Size: 3 * #detailVertsCount] [Unit: wu] + public int detailVertsCount; //< The number of vertices in the detail mesh. + public byte[] detailTris; //< The detail mesh triangles. [Size: 4 * #detailTriCount] + public int detailTriCount; //< The number of triangles in the detail mesh. + + /// @} + /// @name Off-Mesh Connections Attributes (Optional) + /// Used to define a custom point-to-point edge within the navigation graph, an + /// off-mesh connection is a user defined traversable connection made up to two vertices, + /// at least one of which resides within a navigation mesh polygon. + /// @{ + + /// Off-mesh connection vertices. [(ax, ay, az, bx, by, bz) * #offMeshConCount] [Unit: wu] + public float[] offMeshConVerts; + /// Off-mesh connection radii. [Size: #offMeshConCount] [Unit: wu] + public float[] offMeshConRad; + /// User defined flags assigned to the off-mesh connections. [Size: #offMeshConCount] + public ushort[] offMeshConFlags; + /// User defined area ids assigned to the off-mesh connections. [Size: #offMeshConCount] + public byte[] offMeshConAreas; + /// The permitted travel direction of the off-mesh connections. [Size: #offMeshConCount] + /// + /// 0 = Travel only from endpoint A to endpoint B.
+ /// #DT_OFFMESH_CON_BIDIR = Bidirectional travel. + public byte[] offMeshConDir; + /// The user defined ids of the off-mesh connection. [Size: #offMeshConCount] + public uint[] offMeshConUserID; + /// The number of off-mesh connections. [Limit: >= 0] + public int offMeshConCount; + + /// @} + /// @name Tile Attributes + /// @note The tile grid/layer data can be left at zero if the destination is a single tile mesh. + /// @{ + + public uint userId; //< The user defined id of the tile. + public int tileX; //< The tile's x-grid location within the multi-tile destination mesh. (Along the x-axis.) + public int tileY; //< The tile's y-grid location within the multi-tile desitation mesh. (Along the z-axis.) + public int tileLayer; //< The tile's layer within the layered destination mesh. [Limit: >= 0] (Along the y-axis.) + public float[] bmin = new float[3]; //< The minimum bounds of the tile. [(x, y, z)] [Unit: wu] + public float[] bmax = new float[3]; //< The maximum bounds of the tile. [(x, y, z)] [Unit: wu] + + /// @} + /// @name General Configuration Attributes + /// @{ + + public float walkableHeight; //< The agent height. [Unit: wu] + public float walkableRadius; //< The agent radius. [Unit: wu] + public float walkableClimb; //< The agent maximum traversable ledge. (Up/Down) [Unit: wu] + public float cs; //< The xz-plane cell size of the polygon mesh. [Limit: > 0] [Unit: wu] + public float ch; //< The y-axis cell height of the polygon mesh. [Limit: > 0] [Unit: wu] + + /// True if a bounding volume tree should be built for the tile. + /// @note The BVTree is not normally needed for layered navigation meshes. + public bool buildBvTree; + + /// @} + } + + public class BVItem + { + public ushort[] bmin = new ushort[3]; + public ushort[] bmax = new ushort[3]; + public int i; + }; + + //public static int compareItemX(const void* va, const void* vb) + public class BVItemCompareX : IComparer + { + // Compares by Height, Length, and Width. + public int Compare(BVItem a, BVItem b) + { + if (a.bmin[0] < b.bmin[0]) + return -1; + if (a.bmin[0] > b.bmin[0]) + return 1; + return 0; + } + } + + //static int compareItemY(const void* va, const void* vb) + public class BVItemCompareY : IComparer + { + public int Compare(BVItem a, BVItem b) + { + if (a.bmin[1] < b.bmin[1]) + return -1; + if (a.bmin[1] > b.bmin[1]) + return 1; + return 0; + } + } + public class BVItemCompareZ : IComparer + { + public int Compare(BVItem a, BVItem b) + { + if (a.bmin[2] < b.bmin[2]) + return -1; + if (a.bmin[2] > b.bmin[2]) + return 1; + return 0; + } + } + + static void calcExtends(BVItem[] items, int nitems, int imin, int imax, ushort[] bmin, ushort[] bmax) + { + bmin[0] = items[imin].bmin[0]; + bmin[1] = items[imin].bmin[1]; + bmin[2] = items[imin].bmin[2]; + + bmax[0] = items[imin].bmax[0]; + bmax[1] = items[imin].bmax[1]; + bmax[2] = items[imin].bmax[2]; + + for (int i = imin + 1; i < imax; ++i) + { + //const BVItem& it = items[i]; + BVItem it = items[i]; + if (it.bmin[0] < bmin[0]) bmin[0] = it.bmin[0]; + if (it.bmin[1] < bmin[1]) bmin[1] = it.bmin[1]; + if (it.bmin[2] < bmin[2]) bmin[2] = it.bmin[2]; + + if (it.bmax[0] > bmax[0]) bmax[0] = it.bmax[0]; + if (it.bmax[1] > bmax[1]) bmax[1] = it.bmax[1]; + if (it.bmax[2] > bmax[2]) bmax[2] = it.bmax[2]; + } + } + + public static int longestAxis(ushort x, ushort y, ushort z) + { + int axis = 0; + ushort maxVal = x; + if (y > maxVal) + { + axis = 1; + maxVal = y; + } + if (z > maxVal) + { + axis = 2; + maxVal = z; + } + return axis; + } + + public static void subdivide(BVItem[] items, int nitems, int imin, int imax, ref int curNode, dtBVNode[] nodes) + { + int inum = imax - imin; + int icur = curNode; + + dtBVNode node = nodes[curNode++]; + + if (inum == 1) + { + // Leaf + node.bmin[0] = items[imin].bmin[0]; + node.bmin[1] = items[imin].bmin[1]; + node.bmin[2] = items[imin].bmin[2]; + + node.bmax[0] = items[imin].bmax[0]; + node.bmax[1] = items[imin].bmax[1]; + node.bmax[2] = items[imin].bmax[2]; + + node.i = items[imin].i; + } + else + { + // Split + calcExtends(items, nitems, imin, imax, node.bmin, node.bmax); + + int axis = longestAxis((ushort)(node.bmax[0] - node.bmin[0]), + (ushort)(node.bmax[1] - node.bmin[1]), + (ushort)(node.bmax[2] - node.bmin[2])); + + if (axis == 0) + { + // Sort along x-axis + //qsort(items+imin, inum, sizeof(BVItem), compareItemX); + Array.Sort(items, imin, inum, new BVItemCompareX()); + } + else if (axis == 1) + { + // Sort along y-axis + //qsort(items+imin, inum, sizeof(BVItem), compareItemY); + Array.Sort(items, imin, inum, new BVItemCompareY()); + } + else + { + // Sort along z-axis + //qsort(items+imin, inum, sizeof(BVItem), compareItemZ); + Array.Sort(items, imin, inum, new BVItemCompareZ()); + } + + int isplit = imin + inum / 2; + + // Left + subdivide(items, nitems, imin, isplit, ref curNode, nodes); + // Right + subdivide(items, nitems, isplit, imax, ref curNode, nodes); + + int iescape = curNode - icur; + // Negative index means escape. + node.i = -iescape; + } + } + + public static int createBVTree(ushort[] verts, int nverts, ushort[] polys, int npolys, int nvp, float cs, float ch, int nnodes, dtBVNode[] nodes) + { + // Build tree + BVItem[] items = new BVItem[npolys];//(BVItem*)dtAlloc(sizeof(BVItem)*npolys, DT_ALLOC_TEMP); + dtcsArrayItemsCreate(items); + for (int i = 0; i < npolys; i++) + { + BVItem it = items[i]; + it.i = i; + // Calc polygon bounds. + //const ushort* p = &polys[i*nvp*2]; + int pIndex = i * nvp * 2; + it.bmin[0] = it.bmax[0] = verts[polys[pIndex + 0] * 3 + 0]; + it.bmin[1] = it.bmax[1] = verts[polys[pIndex + 0] * 3 + 1]; + it.bmin[2] = it.bmax[2] = verts[polys[pIndex + 0] * 3 + 2]; + + for (int j = 1; j < nvp; ++j) + { + if (polys[pIndex + j] == MESH_NULL_IDX) break; + ushort x = verts[polys[pIndex + j] * 3 + 0]; + ushort y = verts[polys[pIndex + j] * 3 + 1]; + ushort z = verts[polys[pIndex + j] * 3 + 2]; + + if (x < it.bmin[0]) it.bmin[0] = x; + if (y < it.bmin[1]) it.bmin[1] = y; + if (z < it.bmin[2]) it.bmin[2] = z; + + if (x > it.bmax[0]) it.bmax[0] = x; + if (y > it.bmax[1]) it.bmax[1] = y; + if (z > it.bmax[2]) it.bmax[2] = z; + } + // Remap y + it.bmin[1] = (ushort)Math.Floor((float)it.bmin[1] * ch / cs); + it.bmax[1] = (ushort)Math.Ceiling((float)it.bmax[1] * ch / cs); + } + + int curNode = 0; + subdivide(items, npolys, 0, npolys, ref curNode, nodes); + + //dtFree(items); + + return curNode; + } + + public static byte classifyOffMeshPoint(float[] pt, int ptStart, float[] bmin, float[] bmax) + { + const byte XP = 1 << 0; + const byte ZP = 1 << 1; + const byte XM = 1 << 2; + const byte ZM = 1 << 3; + + byte outcode = 0; + outcode |= (pt[ptStart + 0] >= bmax[0]) ? XP : (byte)0; + outcode |= (pt[ptStart + 2] >= bmax[2]) ? ZP : (byte)0; + outcode |= (pt[ptStart + 0] < bmin[0]) ? XM : (byte)0; + outcode |= (pt[ptStart + 2] < bmin[2]) ? ZM : (byte)0; + + switch (outcode) + { + case XP: return 0; + case XP | ZP: return 1; + case ZP: return 2; + case XM | ZP: return 3; + case XM: return 4; + case XM | ZM: return 5; + case ZM: return 6; + case XP | ZM: return 7; + }; + + return 0xff; + } + + // TODO: Better error handling. + + /// @par + /// + /// The output data array is allocated using the detour allocator (dtAlloc()). The method + /// used to free the memory will be determined by how the tile is added to the navigation + /// mesh. + /// + /// @see dtNavMesh, dtNavMesh::addTile() + /// Builds navigation mesh tile data from the provided tile creation data. + /// @ingroup detour + /// @param[in] params Tile creation data. + /// @param[out] outData The resulting tile data. + /// @param[out] outDataSize The size of the tile data array. + /// @return True if the tile data was successfully created. + public static bool dtCreateNavMeshData(dtNavMeshCreateParams createParams, out dtRawTileData outTile)//ref byte[] outData, ref int outDataSize) + { + outTile = null; + + if (createParams.nvp > DT_VERTS_PER_POLYGON) + return false; + if (createParams.vertCount >= 0xffff) + return false; + if (createParams.vertCount == 0 || createParams.verts == null) + return false; + if (createParams.polyCount == 0 || createParams.polys == null) + return false; + + int nvp = createParams.nvp; + + // Classify off-mesh connection points. We store only the connections + // whose start point is inside the tile. + byte[] offMeshConClass = null; + int storedOffMeshConCount = 0; + int offMeshConLinkCount = 0; + + if (createParams.offMeshConCount > 0) + { + //offMeshConClass = (byte*)dtAlloc(sizeof(byte)*createParams.offMeshConCount*2, DT_ALLOC_TEMP); + offMeshConClass = new byte[createParams.offMeshConCount * 2]; + if (offMeshConClass == null) + return false; + + // Find tight heigh bounds, used for culling out off-mesh start locations. + float hmin = float.MaxValue; + float hmax = -float.MaxValue; + + if (createParams.detailVerts != null && createParams.detailVertsCount != 0) + { + for (int i = 0; i < createParams.detailVertsCount; ++i) + { + float h = createParams.detailVerts[i * 3 + 1]; + hmin = Math.Min(hmin, h); + hmax = Math.Max(hmax, h); + } + } + else + { + for (int i = 0; i < createParams.vertCount; ++i) + { + //ushort* iv = &createParams.verts[i*3]; + float h = createParams.bmin[1] + createParams.verts[i * 3 + 1] * createParams.ch; + hmin = Math.Min(hmin, h); + hmax = Math.Max(hmax, h); + } + } + hmin -= createParams.walkableClimb; + hmax += createParams.walkableClimb; + float[] bmin = new float[3];//, bmax[3]; + float[] bmax = new float[3]; + dtVcopy(bmin, createParams.bmin); + dtVcopy(bmax, createParams.bmax); + bmin[1] = hmin; + bmax[1] = hmax; + + for (int i = 0; i < createParams.offMeshConCount; ++i) + { + //const float* p0 = &createParams.offMeshConVerts[(i*2+0)*3]; + //const float* p1 = &createParams.offMeshConVerts[(i*2+1)*3]; + int p0Start = (i * 2 + 0) * 3; + int p1Start = (i * 2 + 1) * 3; + offMeshConClass[i * 2 + 0] = classifyOffMeshPoint(createParams.offMeshConVerts, p0Start, bmin, bmax); + offMeshConClass[i * 2 + 1] = classifyOffMeshPoint(createParams.offMeshConVerts, p1Start, bmin, bmax); + + // Zero out off-mesh start positions which are not even potentially touching the mesh. + if (offMeshConClass[i * 2 + 0] == 0xff) + { + if (createParams.offMeshConVerts[p0Start + 1] < bmin[1] || createParams.offMeshConVerts[p0Start + 1] > bmax[1]) + { + offMeshConClass[i * 2 + 0] = 0; + } + } + + // Cound how many links should be allocated for off-mesh connections. + if (offMeshConClass[i * 2 + 0] == 0xff) + offMeshConLinkCount++; + if (offMeshConClass[i * 2 + 1] == 0xff) + offMeshConLinkCount++; + + if (offMeshConClass[i * 2 + 0] == 0xff) + storedOffMeshConCount++; + } + } + + // Off-mesh connectionss are stored as polygons, adjust values. + int totPolyCount = createParams.polyCount + storedOffMeshConCount; + int totVertCount = createParams.vertCount + storedOffMeshConCount * 2; + + // Find portal edges which are at tile borders. + int edgeCount = 0; + int portalCount = 0; + for (int i = 0; i < createParams.polyCount; ++i) + { + //const ushort* p = &createParams.polys[i*2*nvp]; + int pStart = i * 2 * nvp; + for (int j = 0; j < nvp; ++j) + { + if (createParams.polys[pStart + j] == MESH_NULL_IDX) break; + edgeCount++; + + if ((createParams.polys[pStart + nvp + j] & 0x8000) != 0) + { + ushort dir = (ushort)(createParams.polys[pStart + nvp + j] & 0xf); + if (dir != 0xf) + portalCount++; + } + } + } + + int maxLinkCount = edgeCount + portalCount * 2 + offMeshConLinkCount * 2; + + // Find unique detail vertices. + int uniqueDetailVertCount = 0; + int detailTriCount = 0; + if (createParams.detailMeshes != null) + { + // Has detail mesh, count unique detail vertex count and use input detail tri count. + detailTriCount = createParams.detailTriCount; + for (int i = 0; i < createParams.polyCount; ++i) + { + //const ushort* p = &createParams.polys[i*nvp*2]; + int pStart = i * nvp * 2; + int ndv = (int)createParams.detailMeshes[i * 4 + 1]; + int nv = 0; + for (int j = 0; j < nvp; ++j) + { + if (createParams.polys[pStart + j] == MESH_NULL_IDX) + break; + nv++; + } + ndv -= nv; + uniqueDetailVertCount += ndv; + } + } + else + { + // No input detail mesh, build detail mesh from nav polys. + uniqueDetailVertCount = 0; // No extra detail verts. + detailTriCount = 0; + for (int i = 0; i < createParams.polyCount; ++i) + { + //const ushort* p = &createParams.polys[i*nvp*2]; + int pStart = i * nvp * 2; + int nv = 0; + for (int j = 0; j < nvp; ++j) + { + if (createParams.polys[pStart + j] == MESH_NULL_IDX) break; + nv++; + } + detailTriCount += nv - 2; + } + } + + outTile = new dtRawTileData(); + /* + // Calculate data size + const int headerSize = dtAlign4(sizeof(dtMeshHeader)); + const int vertsSize = dtAlign4(sizeof(float)*3*totVertCount); + const int polysSize = dtAlign4(sizeof(dtPoly)*totPolyCount); + const int linksSize = dtAlign4(sizeof(dtLink)*maxLinkCount); + const int detailMeshesSize = dtAlign4(sizeof(dtPolyDetail)*createParams.polyCount); + const int detailVertsSize = dtAlign4(sizeof(float)*3*uniqueDetailVertCount); + const int detailTrisSize = dtAlign4(sizeof(byte)*4*detailTriCount); + const int bvTreeSize = createParams.buildBvTree ? dtAlign4(sizeof(dtBVNode)*createParams.polyCount*2) : 0; + const int offMeshConsSize = dtAlign4(sizeof(dtOffMeshConnection)*storedOffMeshConCount); + + const int dataSize = headerSize + vertsSize + polysSize + linksSize + + detailMeshesSize + detailVertsSize + detailTrisSize + + bvTreeSize + offMeshConsSize; + + byte* data = (byte*)dtAlloc(sizeof(byte)*dataSize, DT_ALLOC_PERM); + if (!data) + { + dtFree(offMeshConClass); + return false; + } + memset(data, 0, dataSize); + + + + byte* d = data; + dtMeshHeader* header = (dtMeshHeader*)d; d += headerSize; + float* navVerts = (float*)d; d += vertsSize; + dtPoly* navPolys = (dtPoly*)d; d += polysSize; + d += linksSize; + dtPolyDetail* navDMeshes = (dtPolyDetail*)d; d += detailMeshesSize; + float* navDVerts = (float*)d; d += detailVertsSize; + byte* navDTris = (byte*)d; d += detailTrisSize; + dtBVNode* navBvtree = (dtBVNode*)d; d += bvTreeSize; + dtOffMeshConnection* offMeshCons = (dtOffMeshConnection*)d; d += offMeshConsSize; + */ + + outTile.header = new dtMeshHeader(); + dtMeshHeader header = outTile.header; + // Store header + header.magic = DT_NAVMESH_MAGIC; + header.version = DT_NAVMESH_VERSION; + header.x = createParams.tileX; + header.y = createParams.tileY; + header.layer = createParams.tileLayer; + header.userId = createParams.userId; + header.polyCount = totPolyCount; + header.vertCount = totVertCount; + header.maxLinkCount = maxLinkCount; + dtVcopy(header.bmin, createParams.bmin); + dtVcopy(header.bmax, createParams.bmax); + header.detailMeshCount = createParams.polyCount; + header.detailVertCount = uniqueDetailVertCount; + header.detailTriCount = detailTriCount; + header.bvQuantFactor = 1.0f / createParams.cs; + header.offMeshBase = createParams.polyCount; + header.walkableHeight = createParams.walkableHeight; + header.walkableRadius = createParams.walkableRadius; + header.walkableClimb = createParams.walkableClimb; + header.offMeshConCount = storedOffMeshConCount; + header.bvNodeCount = createParams.buildBvTree ? createParams.polyCount * 2 : 0; + + int offMeshVertsBase = createParams.vertCount; + int offMeshPolyBase = createParams.polyCount; + + outTile.links = new dtLink[header.maxLinkCount]; + dtcsArrayItemsCreate(outTile.links); + + // Store vertices + // Mesh vertices + //const int vertsSize = dtAlign4(sizeof(float)*3*totVertCount); + //float* navVerts = (float*)d; d += vertsSize; + outTile.verts = new float[totVertCount * 3]; + float[] navVerts = outTile.verts; + for (int i = 0; i < createParams.vertCount; ++i) + { + //const ushort* iv = &createParams.verts[i*3]; + //float* v = &navVerts[i*3]; + int ivIndex = i * 3; + int vIndex = i * 3; + navVerts[vIndex + 0] = createParams.bmin[0] + createParams.verts[ivIndex + 0] * createParams.cs; + navVerts[vIndex + 1] = createParams.bmin[1] + createParams.verts[ivIndex + 1] * createParams.ch; + navVerts[vIndex + 2] = createParams.bmin[2] + createParams.verts[ivIndex + 2] * createParams.cs; + } + // Off-mesh link vertices. + int n = 0; + for (int i = 0; i < createParams.offMeshConCount; ++i) + { + // Only store connections which start from this tile. + if (offMeshConClass[i * 2 + 0] == 0xff) + { + //const float* linkv = &createParams.offMeshConVerts[i*2*3]; + //float* v = &navVerts[(offMeshVertsBase + n*2)*3]; + int linkVStart = i * 2 * 3; + int vStart = (offMeshVertsBase + n * 2) * 3; + dtVcopy(navVerts, vStart + 0, createParams.offMeshConVerts, linkVStart + 0); + dtVcopy(navVerts, vStart + 3, createParams.offMeshConVerts, linkVStart + 3); + n++; + } + } + + // Store polygons + // Mesh polys + //const ushort* src = createParams.polys; + //ushort[] src = createParams.polys; + int srcIndex = 0; + //const int polysSize = dtAlign4(sizeof(dtPoly)*totPolyCount); + //dtPoly* navPolys = (dtPoly*)d; d += polysSize; + outTile.polys = new dtPoly[totPolyCount]; + dtcsArrayItemsCreate(outTile.polys); + //outTile.offMeshCons ?? + dtPoly[] navPolys = outTile.polys; + //outTile. + for (int i = 0; i < createParams.polyCount; ++i) + { + //dtPoly* p = &navPolys[i]; + dtPoly p = navPolys[i]; + p.vertCount = 0; + p.flags = createParams.polyFlags[i]; + p.setArea(createParams.polyAreas[i]); + p.setType((byte)dtPolyTypes.DT_POLYTYPE_GROUND); + for (int j = 0; j < nvp; ++j) + { + if (createParams.polys[srcIndex + j] == MESH_NULL_IDX) + break; + p.verts[j] = createParams.polys[srcIndex + j]; + if ((createParams.polys[srcIndex + nvp + j] & 0x8000) != 0) + { + // Border or portal edge. + ushort dir = (ushort)(createParams.polys[srcIndex + nvp + j] & 0xf); + if (dir == 0xf) // Border + p.neis[j] = 0; + else if (dir == 0) // Portal x- + p.neis[j] = DT_EXT_LINK | 4; + else if (dir == 1) // Portal z+ + p.neis[j] = DT_EXT_LINK | 2; + else if (dir == 2) // Portal x+ + p.neis[j] = DT_EXT_LINK | 0; + else if (dir == 3) // Portal z- + p.neis[j] = DT_EXT_LINK | 6; + } + else + { + // Normal connection + p.neis[j] = (ushort)(createParams.polys[srcIndex + nvp + j] + 1); + } + + p.vertCount++; + } + srcIndex += nvp * 2; + } + // Off-mesh connection vertices. + n = 0; + for (int i = 0; i < createParams.offMeshConCount; ++i) + { + // Only store connections which start from this tile. + if (offMeshConClass[i * 2 + 0] == 0xff) + { + dtPoly p = navPolys[offMeshPolyBase + n]; + p.vertCount = 2; + p.verts[0] = (ushort)(offMeshVertsBase + n * 2 + 0); + p.verts[1] = (ushort)(offMeshVertsBase + n * 2 + 1); + p.flags = createParams.offMeshConFlags[i]; + p.setArea(createParams.offMeshConAreas[i]); + p.setType((byte)dtPolyTypes.DT_POLYTYPE_OFFMESH_CONNECTION); + n++; + } + } + + // Store detail meshes and vertices. + // The nav polygon vertices are stored as the first vertices on each mesh. + // We compress the mesh data by skipping them and using the navmesh coordinates. + //const int detailMeshesSize = dtAlign4(sizeof(dtPolyDetail)*createParams.polyCount); + //dtPolyDetail* navDMeshes = (dtPolyDetail*)d; d += detailMeshesSize; + outTile.detailMeshes = new dtPolyDetail[createParams.polyCount]; + dtPolyDetail[] navDMeshes = outTile.detailMeshes; + dtcsArrayItemsCreate(navDMeshes); + + outTile.detailVerts = new float[3 * uniqueDetailVertCount]; + float[] navDVerts = outTile.detailVerts; + + outTile.detailTris = new byte[4 * detailTriCount]; + byte[] navDTris = outTile.detailTris; + + if (createParams.detailMeshes != null) + { + ushort vbase = 0; + for (int i = 0; i < createParams.polyCount; ++i) + { + dtPolyDetail dtl = navDMeshes[i]; + int vb = (int)createParams.detailMeshes[i * 4 + 0]; + int ndv = (int)createParams.detailMeshes[i * 4 + 1]; + int nv = navPolys[i].vertCount; + dtl.vertBase = (uint)vbase; + dtl.vertCount = (byte)(ndv - nv); + dtl.triBase = (uint)createParams.detailMeshes[i * 4 + 2]; + dtl.triCount = (byte)createParams.detailMeshes[i * 4 + 3]; + // Copy vertices except the first 'nv' verts which are equal to nav poly verts. + if (ndv - nv != 0) + { + //memcpy(&navDVerts[vbase*3], &createParams.detailVerts[(vb+nv)*3], sizeof(float)*3*(ndv-nv)); + for (int j = 0; j < 3 * (ndv - nv); ++j) + { + navDVerts[j + vbase * 3] = createParams.detailVerts[j + (vb + nv) * 3]; + } + vbase += (ushort)(ndv - nv); + } + } + // Store triangles. + //memcpy(navDTris, createParams.detailTris, sizeof(byte)*4*createParams.detailTriCount); + for (int j = 0; j < 4 * createParams.detailTriCount; ++j) + { + navDTris[j] = createParams.detailTris[j]; + } + } + else + { + // Create dummy detail mesh by triangulating polys. + int tbase = 0; + for (int i = 0; i < createParams.polyCount; ++i) + { + dtPolyDetail dtl = navDMeshes[i]; + int nv = navPolys[i].vertCount; + dtl.vertBase = 0; + dtl.vertCount = 0; + dtl.triBase = (uint)tbase; + dtl.triCount = (byte)(nv - 2); + // Triangulate polygon (local indices). + for (int j = 2; j < nv; ++j) + { + //byte* t = &navDTris[tbase*4]; + int tIndex = tbase * 4; + navDTris[tIndex + 0] = 0; + navDTris[tIndex + 1] = (byte)(j - 1); + navDTris[tIndex + 2] = (byte)j; + // Bit for each edge that belongs to poly boundary. + navDTris[tIndex + 3] = (1 << 2); + if (j == 2) + navDTris[tIndex + 3] |= (1 << 0); + if (j == nv - 1) + navDTris[tIndex + 3] |= (1 << 4); + tbase++; + } + } + } + + //createParams.buildBvTree ? dtAlign4(sizeof(dtBVNode)*createParams.polyCount*2) : 0; + int bvTreeNodeCout = createParams.buildBvTree ? createParams.polyCount * 2 : 0; + outTile.bvTree = new dtBVNode[bvTreeNodeCout]; + dtcsArrayItemsCreate(outTile.bvTree); + dtBVNode[] navBvtree = outTile.bvTree; + + //const int offMeshConsSize = dtAlign4(sizeof(dtOffMeshConnection)*storedOffMeshConCount); + outTile.offMeshCons = new dtOffMeshConnection[storedOffMeshConCount]; + dtOffMeshConnection[] offMeshCons = outTile.offMeshCons; + // Store and create BVtree. + // TODO: take detail mesh into account! use byte per bbox extent? + if (createParams.buildBvTree) + { + createBVTree(createParams.verts, createParams.vertCount, createParams.polys, createParams.polyCount, + nvp, createParams.cs, createParams.ch, createParams.polyCount * 2, navBvtree); + } + + // Store Off-Mesh connections. + n = 0; + for (int i = 0; i < createParams.offMeshConCount; ++i) + { + // Only store connections which start from this tile. + if (offMeshConClass[i * 2 + 0] == 0xff) + { + dtOffMeshConnection con = offMeshCons[n]; + con.poly = (ushort)(offMeshPolyBase + n); + // Copy connection end-points. + //float[] endPts = createParams.offMeshConVerts[i*2*3]; + int endPtsStart = i * 2 * 3; + dtVcopy(con.pos, 0, createParams.offMeshConVerts, endPtsStart + 0); + dtVcopy(con.pos, 3, createParams.offMeshConVerts, endPtsStart + 3); + con.rad = createParams.offMeshConRad[i]; + con.flags = createParams.offMeshConDir[i] != 0 ? (byte)DT_OFFMESH_CON_BIDIR : (byte)0; + con.side = offMeshConClass[i * 2 + 1]; + if (createParams.offMeshConUserID != null) + { + con.userId = createParams.offMeshConUserID[i]; + } + n++; + } + } + + //dtFree(offMeshConClass); + + //*outData = data; + //*outDataSize = dataSize; + + return true; + } + /* + /// Swaps the endianess of the tile data's header (#dtMeshHeader). + /// @param[in,out] data The tile data array. + /// @param[in] dataSize The size of the data array. + bool dtNavMeshHeaderSwapEndian(byte* data, const int dataSize) + { + dtMeshHeader* header = (dtMeshHeader*)data; + + int swappedMagic = DT_NAVMESH_MAGIC; + int swappedVersion = DT_NAVMESH_VERSION; + dtSwapEndian(&swappedMagic); + dtSwapEndian(&swappedVersion); + + if ((header.magic != DT_NAVMESH_MAGIC || header.version != DT_NAVMESH_VERSION) && + (header.magic != swappedMagic || header.version != swappedVersion)) + { + return false; + } + + dtSwapEndian(&header.magic); + dtSwapEndian(&header.version); + dtSwapEndian(&header.x); + dtSwapEndian(&header.y); + dtSwapEndian(&header.layer); + dtSwapEndian(&header.userId); + dtSwapEndian(&header.polyCount); + dtSwapEndian(&header.vertCount); + dtSwapEndian(&header.maxLinkCount); + dtSwapEndian(&header.detailMeshCount); + dtSwapEndian(&header.detailVertCount); + dtSwapEndian(&header.detailTriCount); + dtSwapEndian(&header.bvNodeCount); + dtSwapEndian(&header.offMeshConCount); + dtSwapEndian(&header.offMeshBase); + dtSwapEndian(&header.walkableHeight); + dtSwapEndian(&header.walkableRadius); + dtSwapEndian(&header.walkableClimb); + dtSwapEndian(&header.bmin[0]); + dtSwapEndian(&header.bmin[1]); + dtSwapEndian(&header.bmin[2]); + dtSwapEndian(&header.bmax[0]); + dtSwapEndian(&header.bmax[1]); + dtSwapEndian(&header.bmax[2]); + dtSwapEndian(&header.bvQuantFactor); + + // Freelist index and pointers are updated when tile is added, no need to swap. + + return true; + } + */ + /// @par + /// + /// @warning This function assumes that the header is in the correct endianess already. + /// Call #dtNavMeshHeaderSwapEndian() first on the data if the data is expected to be in wrong endianess + /// to start with. Call #dtNavMeshHeaderSwapEndian() after the data has been swapped if converting from + /// native to foreign endianess. + /// Swaps endianess of the tile data. + /// @param[in,out] data The tile data array. + /// @param[in] dataSize The size of the data array. + /* + bool dtNavMeshDataSwapEndian(byte* data, const int dataSize) + { + // Make sure the data is in right format. + dtMeshHeader* header = (dtMeshHeader*)data; + if (header.magic != DT_NAVMESH_MAGIC) + return false; + if (header.version != DT_NAVMESH_VERSION) + return false; + + // Patch header pointers. + const int headerSize = dtAlign4(sizeof(dtMeshHeader)); + const int vertsSize = dtAlign4(sizeof(float)*3*header.vertCount); + const int polysSize = dtAlign4(sizeof(dtPoly)*header.polyCount); + const int linksSize = dtAlign4(sizeof(dtLink)*(header.maxLinkCount)); + const int detailMeshesSize = dtAlign4(sizeof(dtPolyDetail)*header.detailMeshCount); + const int detailVertsSize = dtAlign4(sizeof(float)*3*header.detailVertCount); + const int detailTrisSize = dtAlign4(sizeof(byte)*4*header.detailTriCount); + const int bvtreeSize = dtAlign4(sizeof(dtBVNode)*header.bvNodeCount); + const int offMeshLinksSize = dtAlign4(sizeof(dtOffMeshConnection)*header.offMeshConCount); + + byte* d = data + headerSize; + float* verts = (float*)d; d += vertsSize; + dtPoly* polys = (dtPoly*)d; d += polysSize; + //dtLink* links = (dtLink*)d; + d += linksSize; + + dtPolyDetail* detailMeshes = (dtPolyDetail*)d; d += detailMeshesSize; + float* detailVerts = (float*)d; d += detailVertsSize; + //byte* detailTris = (byte*)d; + d += detailTrisSize; + dtBVNode* bvTree = (dtBVNode*)d; d += bvtreeSize; + dtOffMeshConnection* offMeshCons = (dtOffMeshConnection*)d; d += offMeshLinksSize; + + // Vertices + for (int i = 0; i < header.vertCount*3; ++i) + { + dtSwapEndian(&verts[i]); + } + + // Polys + for (int i = 0; i < header.polyCount; ++i) + { + dtPoly* p = &polys[i]; + // poly.firstLink is update when tile is added, no need to swap. + for (int j = 0; j < DT_VERTS_PER_POLYGON; ++j) + { + dtSwapEndian(&p.verts[j]); + dtSwapEndian(&p.neis[j]); + } + dtSwapEndian(&p.flags); + } + + // Links are rebuild when tile is added, no need to swap. + + // Detail meshes + for (int i = 0; i < header.detailMeshCount; ++i) + { + dtPolyDetail* pd = &detailMeshes[i]; + dtSwapEndian(&pd.vertBase); + dtSwapEndian(&pd.triBase); + } + + // Detail verts + for (int i = 0; i < header.detailVertCount*3; ++i) + { + dtSwapEndian(&detailVerts[i]); + } + + // BV-tree + for (int i = 0; i < header.bvNodeCount; ++i) + { + dtBVNode* node = &bvTree[i]; + for (int j = 0; j < 3; ++j) + { + dtSwapEndian(&node.bmin[j]); + dtSwapEndian(&node.bmax[j]); + } + dtSwapEndian(&node.i); + } + + // Off-mesh Connections. + for (int i = 0; i < header.offMeshConCount; ++i) + { + dtOffMeshConnection* con = &offMeshCons[i]; + for (int j = 0; j < 6; ++j) + dtSwapEndian(&con.pos[j]); + dtSwapEndian(&con.rad); + dtSwapEndian(&con.poly); + } + + return true; + } + * */ +} diff --git a/Framework/RecastDetour/Detour/DetourNavMeshQuery.cs b/Framework/RecastDetour/Detour/DetourNavMeshQuery.cs new file mode 100644 index 000000000..63ba2bf65 --- /dev/null +++ b/Framework/RecastDetour/Detour/DetourNavMeshQuery.cs @@ -0,0 +1,3708 @@ +/// @class dtQueryFilter +/// +/// The Default Implementation +/// +/// At construction: All area costs default to 1.0. All flags are included +/// and none are excluded. +/// +/// If a polygon has both an include and an exclude flag, it will be excluded. +/// +/// The way filtering works, a navigation mesh polygon must have at least one flag +/// set to ever be considered by a query. So a polygon with no flags will never +/// be considered. +/// +/// Setting the include flags to 0 will result in all polygons being excluded. +/// +/// Custom Implementations +/// +/// DT_VIRTUAL_QUERYFILTER must be defined in order to extend this class. +/// +/// Implement a custom query filter by overriding the virtual passFilter() +/// and getCost() functions. If this is done, both functions should be as +/// fast as possible. Use cached local copies of data rather than accessing +/// your own objects where possible. +/// +/// Custom implementations do not need to adhere to the flags or cost logic +/// used by the default implementation. +/// +/// In order for A* searches to work properly, the cost should be proportional to +/// the travel distance. Implementing a cost modifier less than 1.0 is likely +/// to lead to problems during pathfinding. +/// +/// @see dtNavMeshQuery +/// + +using System; +using System.Diagnostics; +using dtPolyRef = System.UInt64; +using dtStatus = System.UInt32; + +// Define DT_VIRTUAL_QUERYFILTER if you wish to derive a custom filter from dtQueryFilter. +// On certain platforms indirect or virtual function call is expensive. The default +// setting is to use non-virtual functions, the actual implementations of the functions +// are declared as inline for maximum speed. + +//#define DT_VIRTUAL_QUERYFILTER 1 + +public static partial class Detour +{ + const float H_SCALE = 0.999f; // Search heuristic scale. + + /// Defines polygon filtering and traversal costs for navigation mesh query operations. + /// @ingroup detour + public class dtQueryFilter + { + public float[] m_areaCost = new float[DT_MAX_AREAS]; //< Cost per area type. (Used by default implementation.) + public ushort m_includeFlags; //< Flags for polygons that can be visited. (Used by default implementation.) + public ushort m_excludeFlags; //< Flags for polygons that should not be visted. (Used by default implementation.) + + public dtQueryFilter() + { + m_includeFlags = 0xffff; + m_excludeFlags = 0; + for (int i = 0; i < DT_MAX_AREAS; ++i) + m_areaCost[i] = 1.0f; + } + + /// Returns true if the polygon can be visited. (I.e. Is traversable.) + /// @param[in] ref The reference id of the polygon test. + /// @param[in] tile The tile containing the polygon. + /// @param[in] poly The polygon to test. + public bool passFilter(dtPolyRef polyRef, dtMeshTile tile, dtPoly poly) + { + return (poly.flags & m_includeFlags) != 0 && (poly.flags & m_excludeFlags) == 0; + } + + /// Returns cost to move from the beginning to the end of a line segment + /// that is fully contained within a polygon. + /// @param[in] pa The start position on the edge of the previous and current polygon. [(x, y, z)] + /// @param[in] pb The end position on the edge of the current and next polygon. [(x, y, z)] + /// @param[in] prevRef The reference id of the previous polygon. [opt] + /// @param[in] prevTile The tile containing the previous polygon. [opt] + /// @param[in] prevPoly The previous polygon. [opt] + /// @param[in] curRef The reference id of the current polygon. + /// @param[in] curTile The tile containing the current polygon. + /// @param[in] curPoly The current polygon. + /// @param[in] nextRef The refernece id of the next polygon. [opt] + /// @param[in] nextTile The tile containing the next polygon. [opt] + /// @param[in] nextPoly The next polygon. [opt] + public float getCost(float[] pa, float[] pb, dtPolyRef prevRef, dtMeshTile prevTile, dtPoly prevPoly, dtPolyRef curRef, dtMeshTile curTile, dtPoly curPoly, dtPolyRef nextRef, dtMeshTile nextTile, dtPoly nextPoly) + { + return dtVdist(pa, pb) * m_areaCost[curPoly.getArea()]; + } + + /// @name Getters and setters for the default implementation data. + ///@{ + + /// Returns the traversal cost of the area. + /// @param[in] i The id of the area. + /// @returns The traversal cost of the area. + public float getAreaCost(int i) + { + return m_areaCost[i]; + } + + /// Sets the traversal cost of the area. + /// @param[in] i The id of the area. + /// @param[in] cost The new cost of traversing the area. + public void setAreaCost(int i, float cost) + { + m_areaCost[i] = cost; + } + + /// Returns the include flags for the filter. + /// Any polygons that include one or more of these flags will be + /// included in the operation. + public ushort getIncludeFlags() + { + return m_includeFlags; + } + + /// Sets the include flags for the filter. + /// @param[in] flags The new flags. + public void setIncludeFlags(ushort flags) + { + m_includeFlags = flags; + } + + /// Returns the exclude flags for the filter. + /// Any polygons that include one ore more of these flags will be + /// excluded from the operation. + public ushort getExcludeFlags() + { + return m_excludeFlags; + } + + /// Sets the exclude flags for the filter. + /// @param[in] flags The new flags. + public void setExcludeFlags(ushort flags) + { + m_excludeFlags = flags; + } + } + + ////////////////////////////////////////////////////////////////////////////////////////// + + /// @class dtNavMeshQuery + /// Provides the ability to perform pathfinding related queries against + /// a navigation mesh. + /// + /// For methods that support undersized buffers, if the buffer is too small + /// to hold the entire result set the return status of the method will include + /// the #DT_BUFFER_TOO_SMALL flag. + /// + /// Constant member functions can be used by multiple clients without side + /// effects. (E.g. No change to the closed list. No impact on an in-progress + /// sliced path query. Etc.) + /// + /// Walls and portals: A @e wall is a polygon segment that is + /// considered impassable. A @e portal is a passable segment between polygons. + /// A portal may be treated as a wall based on the dtQueryFilter used for a query. + /// + /// @see dtNavMesh, dtQueryFilter, #dtAllocNavMeshQuery(), #dtAllocNavMeshQuery() + /// @ingroup detour + public class dtNavMeshQuery + { + private dtNavMesh m_nav; //< Pointer to navmesh data. + + private class dtQueryData + { + public dtStatus status; + public dtNode lastBestNode = null; + public float lastBestNodeCost; + public dtPolyRef startRef; + public dtPolyRef endRef; + public float[] startPos = new float[3]; + public float[] endPos = new float[3]; + public dtQueryFilter filter = null; + + public void dtcsClear() + { + status = 0; + lastBestNode = null; + lastBestNodeCost = .0f; + startRef = 0; + endRef = 0; + for (int i = 0; i < 3; ++i) + { + startPos[i] = .0f; + endPos[i] = .0f; + } + filter = null; + } + } + private dtQueryData m_query = new dtQueryData(); //< Sliced query state. + + private dtNodePool m_tinyNodePool; //< Pointer to small node pool. + private dtNodePool m_nodePool; //< Pointer to node pool. + private dtNodeQueue m_openList; //< Pointer to open list queue. + + public dtNavMeshQuery() + { + } + + /// @par + /// + /// Must be the first function called after construction, before other + /// functions are used. + /// + /// This function can be used multiple times. + /// Initializes the query object. + /// @param[in] nav Pointer to the dtNavMesh object to use for all queries. + /// @param[in] maxNodes Maximum number of search nodes. [Limits: 0 < value <= 65536] + /// @returns The status flags for the query. + public dtStatus init(dtNavMesh nav, int maxNodes) + { + m_nav = nav; + + if (m_nodePool == null || m_nodePool.getMaxNodes() < maxNodes) + { + if (m_nodePool != null) + { + //m_nodePool.~dtNodePool(); + //dtFree(m_nodePool); + m_nodePool = null; + } + m_nodePool = new dtNodePool(maxNodes, (int)dtNextPow2((uint)(maxNodes / 4)));//(dtAlloc(sizeof(dtNodePool), DT_ALLOC_PERM)) dtNodePool(maxNodes, dtNextPow2(maxNodes/4)); + if (m_nodePool == null) + return DT_FAILURE | DT_OUT_OF_MEMORY; + } + else + { + m_nodePool.clear(); + } + + if (m_tinyNodePool == null) + { + m_tinyNodePool = new dtNodePool(64, 32);//(dtAlloc(sizeof(dtNodePool), DT_ALLOC_PERM)) dtNodePool(64, 32); + if (m_tinyNodePool == null) + return DT_FAILURE | DT_OUT_OF_MEMORY; + } + else + { + m_tinyNodePool.clear(); + } + + // TODO: check the open list size too. + if (m_openList == null || m_openList.getCapacity() < maxNodes) + { + if (m_openList != null) + { + //m_openList.~dtNodeQueue(); + //dtFree(m_openList); + m_openList = null; + } + m_openList = new dtNodeQueue(maxNodes);//(dtAlloc(sizeof(dtNodeQueue), DT_ALLOC_PERM)) dtNodeQueue(maxNodes); + if (m_openList == null) + return DT_FAILURE | DT_OUT_OF_MEMORY; + } + else + { + m_openList.clear(); + } + + return DT_SUCCESS; + } + + /// @name Standard Pathfinding Functions + + public delegate float randomFloatGenerator(); + + /// Returns random location on navmesh. + /// Polygons are chosen weighted by area. The search runs in linear related to number of polygon. + /// @param[in] filter The polygon filter to apply to the query. + /// @param[in] frand Function returning a random number [0..1). + /// @param[out] randomRef The reference id of the random location. + /// @param[out] randomPt The random location. + /// @returns The status flags for the query. + public dtStatus findRandomPoint(dtQueryFilter filter, randomFloatGenerator frand, ref dtPolyRef randomRef, ref float[] randomPt) + { + Debug.Assert(m_nav != null); + + // Randomly pick one tile. Assume that all tiles cover roughly the same area. + dtMeshTile tile = null; + float tsum = 0.0f; + for (int i = 0; i < m_nav.getMaxTiles(); i++) + { + dtMeshTile curTile = m_nav.getTile(i); + if (curTile == null || curTile.header == null) continue; + + // Choose random tile using reservoi sampling. + const float area = 1.0f; // Could be tile area too. + tsum += area; + float u = frand(); + if (u * tsum <= area) + tile = curTile; + } + if (tile == null) + return DT_FAILURE; + + // Randomly pick one polygon weighted by polygon area. + dtPoly poly = null; + dtPolyRef polyRef = 0; + dtPolyRef polyRefBase = m_nav.getPolyRefBase(tile); + + float areaSum = 0.0f; + for (int i = 0; i < tile.header.polyCount; ++i) + { + dtPoly p = tile.polys[i]; + // Do not return off-mesh connection polygons. + if (p.getType() != (byte)dtPolyTypes.DT_POLYTYPE_GROUND) + continue; + // Must pass filter + dtPolyRef pRef = polyRefBase | (uint)i; + if (!filter.passFilter(pRef, tile, p)) + continue; + + // Calc area of the polygon. + float polyArea = 0.0f; + for (int j = 2; j < p.vertCount; ++j) + { + //float* va = &tile.verts[p.verts[0]*3]; + //float* vb = &tile.verts[p.verts[j-1]*3]; + //float* vc = &tile.verts[p.verts[j]*3]; + + polyArea += Detour.dtTriArea2D(tile.verts, p.verts[0] * 3, tile.verts, p.verts[j - 1] * 3, tile.verts, p.verts[j] * 3); + } + + // Choose random polygon weighted by area, using reservoi sampling. + areaSum += polyArea; + float u = frand(); + if (u * areaSum <= polyArea) + { + poly = p; + polyRef = pRef; + } + } + + if (poly == null) + return DT_FAILURE; + + // Randomly pick point on polygon. + //const float* v = &tile.verts[poly.verts[0]*3]; + int vStart = poly.verts[0] * 3; + float[] verts = new float[3 * DT_VERTS_PER_POLYGON]; + float[] areas = new float[DT_VERTS_PER_POLYGON]; + Detour.dtVcopy(verts, 0 * 3, tile.verts, vStart); + for (int j = 1; j < poly.vertCount; ++j) + { + //v = &tile.verts[poly.verts[j]*3]; + Detour.dtVcopy(verts, j * 3, tile.verts, poly.verts[j] * 3); + } + + float s = frand(); + float t = frand(); + + float[] pt = new float[3]; + dtRandomPointInConvexPoly(verts, poly.vertCount, areas, s, t, pt); + + float h = 0.0f; + dtStatus status = getPolyHeight(polyRef, pt, ref h); + if (dtStatusFailed(status)) + return status; + pt[1] = h; + + Detour.dtVcopy(randomPt, 0, pt, 0); + randomRef = polyRef; + + return DT_SUCCESS; + } + + /// Returns random location on navmesh within the reach of specified location. + /// Polygons are chosen weighted by area. The search runs in linear related to number of polygon. + /// The location is not exactly constrained by the circle, but it limits the visited polygons. + /// @param[in] startRef The reference id of the polygon where the search starts. + /// @param[in] centerPos The center of the search circle. [(x, y, z)] + /// @param[in] filter The polygon filter to apply to the query. + /// @param[in] frand Function returning a random number [0..1). + /// @param[out] randomRef The reference id of the random location. + /// @param[out] randomPt The random location. [(x, y, z)] + /// @returns The status flags for the query. + public dtStatus findRandomPointAroundCircle(dtPolyRef startRef, float[] centerPos, float radius, dtQueryFilter filter, randomFloatGenerator frand, ref dtPolyRef randomRef, ref float[] randomPt) + { + Debug.Assert(m_nav != null); + Debug.Assert(m_nodePool != null); + Debug.Assert(m_openList != null); + + // Validate input + if (startRef == 0 || !m_nav.isValidPolyRef(startRef)) + return DT_FAILURE | DT_INVALID_PARAM; + + dtMeshTile startTile = null; + dtPoly startPoly = null; + m_nav.getTileAndPolyByRefUnsafe(startRef, ref startTile, ref startPoly); + if (!filter.passFilter(startRef, startTile, startPoly)) + return DT_FAILURE | DT_INVALID_PARAM; + + m_nodePool.clear(); + m_openList.clear(); + + dtNode startNode = m_nodePool.getNode(startRef); + dtVcopy(startNode.pos, centerPos); + startNode.pidx = 0; + startNode.cost = 0; + startNode.total = 0; + startNode.id = startRef; + startNode.flags = (byte)dtNodeFlags.DT_NODE_OPEN; + m_openList.push(startNode); + + dtStatus status = DT_SUCCESS; + + float radiusSqr = dtSqr(radius); + float areaSum = 0.0f; + + dtMeshTile randomTile = null; + dtPoly randomPoly = null; + dtPolyRef randomPolyRef = 0; + + while (!m_openList.empty()) + { + dtNode bestNode = m_openList.pop(); + unchecked + { + bestNode.flags &= (byte)(~dtNodeFlags.DT_NODE_OPEN); + } + bestNode.flags |= (byte)dtNodeFlags.DT_NODE_CLOSED; + + // Get poly and tile. + // The API input has been cheked already, skip checking internal data. + dtPolyRef bestRef = bestNode.id; + dtMeshTile bestTile = null; + dtPoly bestPoly = null; + m_nav.getTileAndPolyByRefUnsafe(bestRef, ref bestTile, ref bestPoly); + + // Place random locations on on ground. + if (bestPoly.getType() == (byte)dtPolyTypes.DT_POLYTYPE_GROUND) + { + // Calc area of the polygon. + float polyArea = 0.0f; + for (int j = 2; j < bestPoly.vertCount; ++j) + { + //const float* va = &bestTile.verts[bestPoly.verts[0]*3]; + //const float* vb = &bestTile.verts[bestPoly.verts[j-1]*3]; + //const float* vc = &bestTile.verts[bestPoly.verts[j]*3]; + polyArea += dtTriArea2D(bestTile.verts, bestPoly.verts[0] * 3, bestTile.verts, bestPoly.verts[j - 1] * 3, bestTile.verts, bestPoly.verts[j] * 3); + } + // Choose random polygon weighted by area, using reservoi sampling. + areaSum += polyArea; + float u = frand(); + if (u * areaSum <= polyArea) + { + randomTile = bestTile; + randomPoly = bestPoly; + randomPolyRef = bestRef; + } + } + + + // Get parent poly and tile. + dtPolyRef parentRef = 0; + dtMeshTile parentTile = null; + dtPoly parentPoly = null; + if (bestNode.pidx != 0) + parentRef = m_nodePool.getNodeAtIdx(bestNode.pidx).id; + if (parentRef != 0) + m_nav.getTileAndPolyByRefUnsafe(parentRef, ref parentTile, ref parentPoly); + + for (uint i = bestPoly.firstLink; i != DT_NULL_LINK; i = bestTile.links[i].next) + { + dtLink link = bestTile.links[i]; + dtPolyRef neighbourRef = link.polyRef; + // Skip invalid neighbours and do not follow back to parent. + if (neighbourRef == 0 || neighbourRef == parentRef) + continue; + + // Expand to neighbour + dtMeshTile neighbourTile = null; + dtPoly neighbourPoly = null; + m_nav.getTileAndPolyByRefUnsafe(neighbourRef, ref neighbourTile, ref neighbourPoly); + + // Do not advance if the polygon is excluded by the filter. + if (!filter.passFilter(neighbourRef, neighbourTile, neighbourPoly)) + continue; + + // Find edge and calc distance to the edge. + float[] va = new float[3];//, vb[3]; + float[] vb = new float[3]; + if (getPortalPoints(bestRef, bestPoly, bestTile, neighbourRef, neighbourPoly, neighbourTile, va, vb) == 0) + continue; + + // If the circle is not touching the next polygon, skip it. + float tseg = .0f; + float distSqr = dtDistancePtSegSqr2D(centerPos, 0, va, 0, vb, 0, ref tseg); + if (distSqr > radiusSqr) + continue; + + dtNode neighbourNode = m_nodePool.getNode(neighbourRef); + if (neighbourNode == null) + { + status |= DT_OUT_OF_NODES; + continue; + } + + if ((neighbourNode.flags & (byte)dtNodeFlags.DT_NODE_CLOSED) != 0) + continue; + + // Cost + if (neighbourNode.flags == 0) + { + dtVlerp(neighbourNode.pos, va, vb, 0.5f); + } + + float total = bestNode.total + dtVdist(bestNode.pos, neighbourNode.pos); + + // The node is already in open list and the new result is worse, skip. + if (((neighbourNode.flags & (byte)dtNodeFlags.DT_NODE_OPEN) != 0) && total >= neighbourNode.total) + continue; + + neighbourNode.id = neighbourRef; + unchecked + { + neighbourNode.flags = (byte)(neighbourNode.flags & (byte)(~dtNodeFlags.DT_NODE_CLOSED)); + } + neighbourNode.pidx = m_nodePool.getNodeIdx(bestNode); + neighbourNode.total = total; + + if ((neighbourNode.flags & (byte)dtNodeFlags.DT_NODE_OPEN) != 0) + { + m_openList.modify(neighbourNode); + } + else + { + neighbourNode.flags = (byte)dtNodeFlags.DT_NODE_OPEN; + m_openList.push(neighbourNode); + } + } + } + + if (randomPoly == null) + return DT_FAILURE; + + // Randomly pick point on polygon. + //float* v = &randomTile.verts[randomPoly.verts[0]*3]; + float[] verts = new float[3 * DT_VERTS_PER_POLYGON]; + float[] areas = new float[DT_VERTS_PER_POLYGON]; + dtVcopy(verts, 0 * 3, randomTile.verts, 0); + for (int j = 1; j < randomPoly.vertCount; ++j) + { + //v = &randomTile.verts[randomPoly.verts[j]*3]; + dtVcopy(verts, j * 3, randomTile.verts, randomPoly.verts[j] * 3); + } + + float s = frand(); + float t = frand(); + + float[] pt = new float[3]; + dtRandomPointInConvexPoly(verts, randomPoly.vertCount, areas, s, t, pt); + + float h = 0.0f; + dtStatus stat = getPolyHeight(randomPolyRef, pt, ref h); + if (dtStatusFailed(status)) + return stat; + pt[1] = h; + + dtVcopy(randomPt, pt); + randomRef = randomPolyRef; + + return DT_SUCCESS; + } + + ////////////////////////////////////////////////////////////////////////////////////////// + + /// Finds the closest point on the specified polygon. + /// @param[in] ref The reference id of the polygon. + /// @param[in] pos The position to check. [(x, y, z)] + /// @param[out] closest The closest point on the polygon. [(x, y, z)] + /// @param[out] posOverPoly True of the position is over the polygon. + /// @returns The status flags for the query. + /// @par + /// + /// Uses the detail polygons to find the surface height. (Most accurate.) + /// + /// @p pos does not have to be within the bounds of the polygon or navigation mesh. + /// + /// See closestPointOnPolyBoundary() for a limited but faster option. + /// + public dtStatus closestPointOnPoly(dtPolyRef polyRef, float[] pos, float[] closest, ref bool posOverPoly) + { + Debug.Assert(m_nav != null); + dtMeshTile tile = null; + dtPoly poly = null; + uint ip = 0; + if (dtStatusFailed(m_nav.getTileAndPolyByRef(polyRef, ref tile, ref poly, ref ip))) + return DT_FAILURE | DT_INVALID_PARAM; + if (tile == null) + return DT_FAILURE | DT_INVALID_PARAM; + + // Off-mesh connections don't have detail polygons. + if (poly.getType() == (byte)dtPolyTypes.DT_POLYTYPE_OFFMESH_CONNECTION) + { + //const float* v0 = &tile.verts[poly.verts[0]*3]; + //const float* v1 = &tile.verts[poly.verts[1]*3]; + int v0Start = poly.verts[0] * 3; + int v1Start = poly.verts[1] * 3; + float d0 = dtVdist(pos, 0, tile.verts, v0Start); + float d1 = dtVdist(pos, 0, tile.verts, v1Start); + float u = d0 / (d0 + d1); + dtVlerp(closest, 0, tile.verts, v0Start, tile.verts, v1Start, u); + //if (posOverPoly) + posOverPoly = false; + return DT_SUCCESS; + } + + //uint ip = (uint)(poly - tile.polys); + dtPolyDetail pd = tile.detailMeshes[ip]; + + // Clamp point to be inside the polygon. + float[] verts = new float[DT_VERTS_PER_POLYGON * 3]; + float[] edged = new float[DT_VERTS_PER_POLYGON]; + float[] edget = new float[DT_VERTS_PER_POLYGON]; + int nv = poly.vertCount; + for (int i = 0; i < nv; ++i) + { + dtVcopy(verts, i * 3, tile.verts, poly.verts[i] * 3); + } + + dtVcopy(closest, pos); + if (!dtDistancePtPolyEdgesSqr(pos, 0, verts, nv, edged, edget)) + { + // Point is outside the polygon, dtClamp to nearest edge. + float dmin = float.MaxValue; + int imin = -1; + for (int i = 0; i < nv; ++i) + { + if (edged[i] < dmin) + { + dmin = edged[i]; + imin = i; + } + } + //const float* va = &verts[imin*3]; + //const float* vb = &verts[((imin+1)%nv)*3]; + int vaStart = imin * 3; + int vbStart = ((imin + 1) % nv) * 3; + dtVlerp(closest, 0, verts, vaStart, verts, vbStart, edget[imin]); + + //if (posOverPoly) + posOverPoly = false; + } + else + { + //if (posOverPoly) + posOverPoly = true; + } + + // Find height at the location. + for (int j = 0; j < pd.triCount; ++j) + { + //byte[] t = &tile.detailTris[(pd.triBase+j)*4]; + //const float* v[3]; + int tStart = (int)(pd.triBase + j) * 4; + int[] vStarts = new int[3]; + float[][] vSrc = new float[3][]; + for (int k = 0; k < 3; ++k) + { + byte tk = tile.detailTris[tStart + k]; + byte vCount = poly.vertCount; + if (tk < vCount) + { + //v[k] = &tile.verts[poly.verts[tile.detailTris[tStart + k]]*3]; + vStarts[k] = poly.verts[tk] * 3; + vSrc[k] = tile.verts; + } + else + { + //v[k] = &tile.detailVerts[(pd.vertBase+(t[k]-poly.vertCount))*3]; + vStarts[k] = (int)(pd.vertBase + (tk - vCount)) * 3; + vSrc[k] = tile.detailVerts; + } + } + float h = .0f; + if (dtClosestHeightPointTriangle(pos, 0, vSrc[0], vStarts[0], vSrc[1], vStarts[1], vSrc[2], vStarts[2], ref h)) + { + closest[1] = h; + break; + } + } + + return DT_SUCCESS; + } + + /// Returns a point on the boundary closest to the source point if the source point is outside the + /// polygon's xz-bounds. + /// @param[in] ref The reference id to the polygon. + /// @param[in] pos The position to check. [(x, y, z)] + /// @param[out] closest The closest point. [(x, y, z)] + /// @returns The status flags for the query. + /// @par + /// + /// Much faster than closestPointOnPoly(). + /// + /// If the provided position lies within the polygon's xz-bounds (above or below), + /// then @p pos and @p closest will be equal. + /// + /// The height of @p closest will be the polygon boundary. The height detail is not used. + /// + /// @p pos does not have to be within the bounds of the polybon or the navigation mesh. + /// + public dtStatus closestPointOnPolyBoundary(dtPolyRef polyRef, float[] pos, float[] closest) + { + Debug.Assert(m_nav != null); + + dtMeshTile tile = null; + dtPoly poly = null; + if (dtStatusFailed(m_nav.getTileAndPolyByRef(polyRef, ref tile, ref poly))) + return DT_FAILURE | DT_INVALID_PARAM; + + // Collect vertices. + float[] verts = new float[DT_VERTS_PER_POLYGON * 3]; + float[] edged = new float[DT_VERTS_PER_POLYGON]; + float[] edget = new float[DT_VERTS_PER_POLYGON]; + int nv = 0; + for (int i = 0; i < (int)poly.vertCount; ++i) + { + dtVcopy(verts, nv * 3, tile.verts, poly.verts[i] * 3); + nv++; + } + + bool inside = dtDistancePtPolyEdgesSqr(pos, 0, verts, nv, edged, edget); + if (inside) + { + // Point is inside the polygon, return the point. + dtVcopy(closest, pos); + } + else + { + // Point is outside the polygon, dtClamp to nearest edge. + float dmin = float.MaxValue; + int imin = -1; + for (int i = 0; i < nv; ++i) + { + if (edged[i] < dmin) + { + dmin = edged[i]; + imin = i; + } + } + //const float* va = &verts[imin*3]; + //const float* vb = &verts[((imin+1)%nv)*3]; + int vaStart = imin * 3; + int vbStart = ((imin + 1) % nv) * 3; + dtVlerp(closest, 0, verts, vaStart, verts, vbStart, edget[imin]); + } + + return DT_SUCCESS; + } + + /// Gets the height of the polygon at the provided position using the height detail. (Most accurate.) + /// @param[in] ref The reference id of the polygon. + /// @param[in] pos A position within the xz-bounds of the polygon. [(x, y, z)] + /// @param[out] height The height at the surface of the polygon. + /// @returns The status flags for the query. + /// @par + /// + /// Will return #DT_FAILURE if the provided position is outside the xz-bounds + /// of the polygon. + /// + public dtStatus getPolyHeight(dtPolyRef polyRef, float[] pos, ref float height) + { + Debug.Assert(m_nav != null); + + dtMeshTile tile = null; + dtPoly poly = null; + uint ip = 0; + if (dtStatusFailed(m_nav.getTileAndPolyByRef(polyRef, ref tile, ref poly, ref ip))) + return DT_FAILURE | DT_INVALID_PARAM; + + if (poly.getType() == (byte)dtPolyTypes.DT_POLYTYPE_OFFMESH_CONNECTION) + { + //const float* v0 = &tile.verts[poly.verts[0]*3]; + //const float* v1 = &tile.verts[poly.verts[1]*3]; + int v0Start = poly.verts[0] * 3; + int v1Start = poly.verts[1] * 3; + float d0 = dtVdist2D(pos, 0, tile.verts, v0Start); + float d1 = dtVdist2D(pos, 0, tile.verts, v1Start); + float u = d0 / (d0 + d1); + //if (height) + height = tile.verts[v0Start + 1] + (tile.verts[v1Start + 1] - tile.verts[v0Start + 1]) * u; + return DT_SUCCESS; + } + else + { + //const uint ip = (uint)(poly - tile.polys); + dtPolyDetail pd = tile.detailMeshes[ip]; + for (int j = 0; j < pd.triCount; ++j) + { + //byte[] t = tile.detailTris[(pd.triBase+j)*4] ; + //float* v[3]; + int tStart = (int)(pd.triBase + j) * 4; + int[] vStarts = new int[3]; + float[][] vSrc = new float[3][]; + + for (int k = 0; k < 3; ++k) + { + if (tile.detailTris[tStart + k] < poly.vertCount) + { + //v[k] = &tile.verts[poly.verts[tile.detailTris[tStart + k]]*3]; + vStarts[k] = poly.verts[tile.detailTris[tStart + k]] * 3; + vSrc[k] = tile.verts; + } + else + { + //v[k] = &tile.detailVerts[(pd.vertBase+(tile.detailTris[tStart + k]-poly.vertCount))*3]; + vStarts[k] = (int)(pd.vertBase + (tile.detailTris[tStart + k] - poly.vertCount)) * 3; + vSrc[k] = tile.detailVerts; + } + } + float h = .0f; + if (dtClosestHeightPointTriangle(pos, 0, vSrc[0], vStarts[0], vSrc[1], vStarts[1], vSrc[2], vStarts[2], ref h)) + { + //if (height) + height = h; + return DT_SUCCESS; + } + } + } + + return DT_FAILURE | DT_INVALID_PARAM; + } + + /// @} + /// @name Local Query Functions + ///@{ + + /// Finds the polygon nearest to the specified center point. + /// @param[in] center The center of the search box. [(x, y, z)] + /// @param[in] extents The search distance along each axis. [(x, y, z)] + /// @param[in] filter The polygon filter to apply to the query. + /// @param[out] nearestRef The reference id of the nearest polygon. + /// @param[out] nearestPt The nearest point on the polygon. [opt] [(x, y, z)] + /// @returns The status flags for the query. + /// @par + /// + /// @note If the search box does not intersect any polygons the search will + /// return #DT_SUCCESS, but @p nearestRef will be zero. So if in doubt, check + /// @p nearestRef before using @p nearestPt. + /// + /// @warning This function is not suitable for large area searches. If the search + /// extents overlaps more than 128 polygons it may return an invalid result. + /// + public dtStatus findNearestPoly(float[] center, float[] extents, dtQueryFilter filter, ref dtPolyRef nearestRef, ref float[] nearestPt) + { + Debug.Assert(m_nav != null); + + nearestRef = 0; + + // Get nearby polygons from proximity grid. + dtPolyRef[] polys = new dtPolyRef[128]; + int polyCount = 0; + if (dtStatusFailed(queryPolygons(center, extents, filter, polys, ref polyCount, 128))) + return DT_FAILURE | DT_INVALID_PARAM; + + // Find nearest polygon amongst the nearby polygons. + dtPolyRef nearest = 0; + float nearestDistanceSqr = float.MaxValue; + for (int i = 0; i < polyCount; ++i) + { + dtPolyRef polyRef = polys[i]; + float[] closestPtPoly = new float[3]; + float[] diff = new float[3]; + bool posOverPoly = false; + float d = 0; + closestPointOnPoly(polyRef, center, closestPtPoly, ref posOverPoly); + + // If a point is directly over a polygon and closer than + // climb height, favor that instead of straight line nearest point. + dtVsub(diff, center, closestPtPoly); + if (posOverPoly) + { + dtMeshTile tile = null; + dtPoly poly = null; + m_nav.getTileAndPolyByRefUnsafe(polys[i], ref tile, ref poly); + d = (float)(Math.Abs(diff[1]) - tile.header.walkableClimb); + d = d > 0 ? d * d : 0; + } + else + { + d = dtVlenSqr(diff); + } + + if (d < nearestDistanceSqr) + { + //if (nearestPt != null) + dtVcopy(nearestPt, closestPtPoly); + + nearestDistanceSqr = d; + nearest = polyRef; + } + } + + //if (nearestRef) + nearestRef = nearest; + + return DT_SUCCESS; + } + + /// Queries polygons within a tile. + public int queryPolygonsInTile(dtMeshTile tile, float[] qmin, float[] qmax, dtQueryFilter filter, dtPolyRef[] polys, int polyStart, int maxPolys) + { + Debug.Assert(m_nav != null); + + if (tile.bvTree != null) + { + dtBVNode node = tile.bvTree[0]; + //dtBVNode* end = &tile.bvTree[tile.header.bvNodeCount]; + int endIndex = tile.header.bvNodeCount; + float[] tbmin = tile.header.bmin; + float[] tbmax = tile.header.bmax; + float qfac = tile.header.bvQuantFactor; + + // Calculate quantized box + ushort[] bmin = new ushort[3];//, bmax[3]; + ushort[] bmax = new ushort[3]; + // dtClamp query box to world box. + float minx = dtClamp(qmin[0], tbmin[0], tbmax[0]) - tbmin[0]; + float miny = dtClamp(qmin[1], tbmin[1], tbmax[1]) - tbmin[1]; + float minz = dtClamp(qmin[2], tbmin[2], tbmax[2]) - tbmin[2]; + float maxx = dtClamp(qmax[0], tbmin[0], tbmax[0]) - tbmin[0]; + float maxy = dtClamp(qmax[1], tbmin[1], tbmax[1]) - tbmin[1]; + float maxz = dtClamp(qmax[2], tbmin[2], tbmax[2]) - tbmin[2]; + // Quantize + bmin[0] = (ushort)((int)(qfac * minx) & 0xfffe); + bmin[1] = (ushort)((int)(qfac * miny) & 0xfffe); + bmin[2] = (ushort)((int)(qfac * minz) & 0xfffe); + bmax[0] = (ushort)((int)(qfac * maxx + 1) | 1); + bmax[1] = (ushort)((int)(qfac * maxy + 1) | 1); + bmax[2] = (ushort)((int)(qfac * maxz + 1) | 1); + + // Traverse tree + dtPolyRef polyRefBase = m_nav.getPolyRefBase(tile); + int n = 0; + int nodeIndex = 0; + while (nodeIndex < endIndex) + { + node = tile.bvTree[nodeIndex]; + + bool overlap = dtOverlapQuantBounds(bmin, bmax, node.bmin, node.bmax); + bool isLeafNode = node.i >= 0; + + if (isLeafNode && overlap) + { + dtPolyRef polyRef = polyRefBase | (uint)node.i; + if (filter.passFilter(polyRef, tile, tile.polys[node.i])) + { + if (n < maxPolys) + polys[polyStart + n++] = polyRef; + } + } + + if (overlap || isLeafNode) + { + nodeIndex++; + } + else + { + int escapeIndex = -node.i; + nodeIndex += escapeIndex; + } + } + + return n; + } + else + { + float[] bmin = new float[3];//, bmax[3]; + float[] bmax = new float[3]; + int n = 0; + dtPolyRef polyRefBase = m_nav.getPolyRefBase(tile); + for (int i = 0; i < tile.header.polyCount; ++i) + { + dtPoly p = tile.polys[i]; + // Do not return off-mesh connection polygons. + if (p.getType() == (byte)dtPolyTypes.DT_POLYTYPE_OFFMESH_CONNECTION) + continue; + // Must pass filter + dtPolyRef polyRef = polyRefBase | (uint)i; + if (!filter.passFilter(polyRef, tile, p)) + continue; + // Calc polygon bounds. + //const float* v = &tile.verts[p.verts[0]*3]; + int vStart = p.verts[0] * 3; + dtVcopy(bmin, 0, tile.verts, vStart); + dtVcopy(bmax, 0, tile.verts, vStart); + for (int j = 1; j < p.vertCount; ++j) + { + //v = &tile.verts[p.verts[j]*3]; + vStart = p.verts[j] * 3; + dtVmin(bmin, 0, tile.verts, vStart); + dtVmax(bmax, 0, tile.verts, vStart); + } + if (dtOverlapBounds(qmin, qmax, bmin, bmax)) + { + if (n < maxPolys) + polys[polyStart + n++] = polyRef; + } + } + return n; + } + } + + /// Finds polygons that overlap the search box. + /// @param[in] center The center of the search box. [(x, y, z)] + /// @param[in] extents The search distance along each axis. [(x, y, z)] + /// @param[in] filter The polygon filter to apply to the query. + /// @param[out] polys The reference ids of the polygons that overlap the query box. + /// @param[out] polyCount The number of polygons in the search result. + /// @param[in] maxPolys The maximum number of polygons the search result can hold. + /// @returns The status flags for the query. + /// @par + /// + /// If no polygons are found, the function will return #DT_SUCCESS with a + /// @p polyCount of zero. + /// + /// If @p polys is too small to hold the entire result set, then the array will + /// be filled to capacity. The method of choosing which polygons from the + /// full set are included in the partial result set is undefined. + /// + public dtStatus queryPolygons(float[] center, float[] extents, dtQueryFilter filter, dtPolyRef[] polys, ref int polyCount, int maxPolys) + { + Debug.Assert(m_nav != null); + + float[] bmin = new float[3];//, bmax[3]; + float[] bmax = new float[3]; + dtVsub(bmin, center, extents); + dtVadd(bmax, center, extents); + + // Find tiles the query touches. + int minx = 0, miny = 0, maxx = 0, maxy = 0; + m_nav.calcTileLoc(bmin, ref minx, ref miny); + m_nav.calcTileLoc(bmax, ref maxx, ref maxy); + + int MAX_NEIS = 32; + dtMeshTile[] neis = new dtMeshTile[MAX_NEIS]; + + int n = 0; + for (int y = miny; y <= maxy; ++y) + { + for (int x = minx; x <= maxx; ++x) + { + int nneis = m_nav.getTilesAt(x, y, neis, MAX_NEIS); + for (int j = 0; j < nneis; ++j) + { + n += queryPolygonsInTile(neis[j], bmin, bmax, filter, polys, n, maxPolys - n); + if (n >= maxPolys) + { + polyCount = n; + return DT_SUCCESS | DT_BUFFER_TOO_SMALL; + } + } + } + } + polyCount = n; + + return DT_SUCCESS; + } + + /// Finds a path from the start polygon to the end polygon. + /// @param[in] startRef The refrence id of the start polygon. + /// @param[in] endRef The reference id of the end polygon. + /// @param[in] startPos A position within the start polygon. [(x, y, z)] + /// @param[in] endPos A position within the end polygon. [(x, y, z)] + /// @param[in] filter The polygon filter to apply to the query. + /// @param[out] path An ordered list of polygon references representing the path. (Start to end.) + /// [(polyRef) * @p pathCount] + /// @param[out] pathCount The number of polygons returned in the @p path array. + /// @param[in] maxPath The maximum number of polygons the @p path array can hold. [Limit: >= 1] + /// @par + /// + /// If the end polygon cannot be reached through the navigation graph, + /// the last polygon in the path will be the nearest the end polygon. + /// + /// If the path array is to small to hold the full result, it will be filled as + /// far as possible from the start polygon toward the end polygon. + /// + /// The start and end positions are used to calculate traversal costs. + /// (The y-values impact the result.) + /// + public dtStatus findPath(dtPolyRef startRef, dtPolyRef endRef, float[] startPos, float[] endPos, dtQueryFilter filter, dtPolyRef[] path, ref uint pathCount, int maxPath) + { + Debug.Assert(m_nav != null); + Debug.Assert(m_nodePool != null); + Debug.Assert(m_openList != null); + + pathCount = 0; + + if (startRef == 0 || endRef == 0) + return DT_FAILURE | DT_INVALID_PARAM; + + if (maxPath == 0) + return DT_FAILURE | DT_INVALID_PARAM; + + // Validate input + if (!m_nav.isValidPolyRef(startRef) || !m_nav.isValidPolyRef(endRef)) + return DT_FAILURE | DT_INVALID_PARAM; + + if (startRef == endRef) + { + path[0] = startRef; + pathCount = 1; + return DT_SUCCESS; + } + + m_nodePool.clear(); + m_openList.clear(); + + dtNode startNode = m_nodePool.getNode(startRef); + dtVcopy(startNode.pos, startPos); + startNode.pidx = 0; + startNode.cost = 0; + startNode.total = dtVdist(startPos, endPos) * H_SCALE; + startNode.id = startRef; + startNode.flags = (byte)dtNodeFlags.DT_NODE_OPEN; + m_openList.push(startNode); + + dtNode lastBestNode = startNode; + float lastBestNodeCost = startNode.total; + + dtStatus status = DT_SUCCESS; + + while (!m_openList.empty()) + { + // Remove node from open list and put it in closed list. + dtNode bestNode = m_openList.pop(); + unchecked + { + bestNode.flags &= (byte)(~dtNodeFlags.DT_NODE_OPEN); + } + bestNode.flags |= (byte)dtNodeFlags.DT_NODE_CLOSED; + + // Reached the goal, stop searching. + if (bestNode.id == endRef) + { + lastBestNode = bestNode; + break; + } + + // Get current poly and tile. + // The API input has been cheked already, skip checking internal data. + dtPolyRef bestRef = bestNode.id; + dtMeshTile bestTile = null; + dtPoly bestPoly = null; + m_nav.getTileAndPolyByRefUnsafe(bestRef, ref bestTile, ref bestPoly); + + // Get parent poly and tile. + dtPolyRef parentRef = 0; + dtMeshTile parentTile = null; + dtPoly parentPoly = null; + if (bestNode.pidx != 0) + parentRef = m_nodePool.getNodeAtIdx(bestNode.pidx).id; + if (parentRef != 0) + m_nav.getTileAndPolyByRefUnsafe(parentRef, ref parentTile, ref parentPoly); + + for (uint i = bestPoly.firstLink; i != DT_NULL_LINK; i = bestTile.links[i].next) + { + dtPolyRef neighbourRef = bestTile.links[i].polyRef; + + // Skip invalid ids and do not expand back to where we came from. + if (neighbourRef == 0 || neighbourRef == parentRef) + continue; + + // Get neighbour poly and tile. + // The API input has been cheked already, skip checking internal data. + dtMeshTile neighbourTile = null; + dtPoly neighbourPoly = null; + m_nav.getTileAndPolyByRefUnsafe(neighbourRef, ref neighbourTile, ref neighbourPoly); + + if (!filter.passFilter(neighbourRef, neighbourTile, neighbourPoly)) + continue; + + dtNode neighbourNode = m_nodePool.getNode(neighbourRef); + if (neighbourNode == null) + { + status |= DT_OUT_OF_NODES; + continue; + } + + // If the node is visited the first time, calculate node position. + if (neighbourNode.flags == 0) + { + getEdgeMidPoint(bestRef, bestPoly, bestTile, + neighbourRef, neighbourPoly, neighbourTile, + neighbourNode.pos); + } + + // Calculate cost and heuristic. + float cost = 0; + float heuristic = 0; + + // Special case for last node. + if (neighbourRef == endRef) + { + // Cost + float curCost = filter.getCost(bestNode.pos, neighbourNode.pos, + parentRef, parentTile, parentPoly, + bestRef, bestTile, bestPoly, + neighbourRef, neighbourTile, neighbourPoly); + float endCost = filter.getCost(neighbourNode.pos, endPos, + bestRef, bestTile, bestPoly, + neighbourRef, neighbourTile, neighbourPoly, + 0, null, null); + + cost = bestNode.cost + curCost + endCost; + heuristic = 0; + } + else + { + // Cost + float curCost = filter.getCost(bestNode.pos, neighbourNode.pos, + parentRef, parentTile, parentPoly, + bestRef, bestTile, bestPoly, + neighbourRef, neighbourTile, neighbourPoly); + cost = bestNode.cost + curCost; + heuristic = dtVdist(neighbourNode.pos, endPos) * H_SCALE; + } + + float total = cost + heuristic; + + // The node is already in open list and the new result is worse, skip. + if ((neighbourNode.flags & (byte)dtNodeFlags.DT_NODE_OPEN) != 0 && total >= neighbourNode.total) + continue; + // The node is already visited and process, and the new result is worse, skip. + if ((neighbourNode.flags & (byte)dtNodeFlags.DT_NODE_CLOSED) != 0 && total >= neighbourNode.total) + continue; + + // Add or update the node. + neighbourNode.pidx = m_nodePool.getNodeIdx(bestNode); + neighbourNode.id = neighbourRef; + unchecked + { + neighbourNode.flags = (byte)(neighbourNode.flags & (byte)~dtNodeFlags.DT_NODE_CLOSED); + } + neighbourNode.cost = cost; + neighbourNode.total = total; + + if ((neighbourNode.flags & (byte)dtNodeFlags.DT_NODE_OPEN) != 0) + { + // Already in open, update node location. + m_openList.modify(neighbourNode); + } + else + { + // Put the node in open list. + neighbourNode.flags |= (byte)dtNodeFlags.DT_NODE_OPEN; + m_openList.push(neighbourNode); + } + + // Update nearest node to target so far. + if (heuristic < lastBestNodeCost) + { + lastBestNodeCost = heuristic; + lastBestNode = neighbourNode; + } + } + } + + if (lastBestNode.id != endRef) + status |= DT_PARTIAL_RESULT; + + // Reverse the path. + dtNode prev = null; + dtNode node = lastBestNode; + do + { + dtNode next = m_nodePool.getNodeAtIdx(node.pidx); + node.pidx = m_nodePool.getNodeIdx(prev); + prev = node; + node = next; + } + while (node != null); + + // Store path + node = prev; + int n = 0; + do + { + path[n++] = node.id; + if (n >= maxPath) + { + status |= DT_BUFFER_TOO_SMALL; + break; + } + node = m_nodePool.getNodeAtIdx(node.pidx); + } + while (node != null); + + pathCount = (uint)n; + + return status; + } + + ///@} + /// @name Sliced Pathfinding Functions + /// Common use case: + /// -# Call initSlicedFindPath() to initialize the sliced path query. + /// -# Call updateSlicedFindPath() until it returns complete. + /// -# Call finalizeSlicedFindPath() to get the path. + ///@{ + + /// Intializes a sliced path query. + /// @param[in] startRef The refrence id of the start polygon. + /// @param[in] endRef The reference id of the end polygon. + /// @param[in] startPos A position within the start polygon. [(x, y, z)] + /// @param[in] endPos A position within the end polygon. [(x, y, z)] + /// @param[in] filter The polygon filter to apply to the query. + /// @returns The status flags for the query. + /// @par + /// + /// @warning Calling any non-slice methods before calling finalizeSlicedFindPath() + /// or finalizeSlicedFindPathPartial() may result in corrupted data! + /// + /// The @p filter pointer is stored and used for the duration of the sliced + /// path query. + /// + public dtStatus initSlicedFindPath(dtPolyRef startRef, dtPolyRef endRef, float[] startPos, float[] endPos, dtQueryFilter filter) + { + Debug.Assert(m_nav != null); + Debug.Assert(m_nodePool != null); + Debug.Assert(m_openList != null); + + // Init path state. + //memset(&m_query, 0, sizeof(dtQueryData)); + + m_query.status = DT_FAILURE; + m_query.startRef = startRef; + m_query.endRef = endRef; + dtVcopy(m_query.startPos, startPos); + dtVcopy(m_query.endPos, endPos); + m_query.filter = filter; + + if (startRef == 0 || endRef == 0) + return DT_FAILURE | DT_INVALID_PARAM; + + // Validate input + if (!m_nav.isValidPolyRef(startRef) || !m_nav.isValidPolyRef(endRef)) + return DT_FAILURE | DT_INVALID_PARAM; + + if (startRef == endRef) + { + m_query.status = DT_SUCCESS; + return DT_SUCCESS; + } + + m_nodePool.clear(); + m_openList.clear(); + + dtNode startNode = m_nodePool.getNode(startRef); + dtVcopy(startNode.pos, startPos); + startNode.pidx = 0; + startNode.cost = 0; + startNode.total = dtVdist(startPos, endPos) * H_SCALE; + startNode.id = startRef; + startNode.flags = (byte)dtNodeFlags.DT_NODE_OPEN; + m_openList.push(startNode); + + m_query.status = DT_IN_PROGRESS; + m_query.lastBestNode = startNode; + m_query.lastBestNodeCost = startNode.total; + + return m_query.status; + } + + /// Updates an in-progress sliced path query. + /// @param[in] maxIter The maximum number of iterations to perform. + /// @param[out] doneIters The actual number of iterations completed. [opt] + /// @returns The status flags for the query. + public dtStatus updateSlicedFindPath(int maxIter, ref int doneIters) + { + if (!dtStatusInProgress(m_query.status)) + return m_query.status; + + // Make sure the request is still valid. + if (!m_nav.isValidPolyRef(m_query.startRef) || !m_nav.isValidPolyRef(m_query.endRef)) + { + m_query.status = DT_FAILURE; + return DT_FAILURE; + } + + int iter = 0; + while (iter < maxIter && !m_openList.empty()) + { + iter++; + + // Remove node from open list and put it in closed list. + dtNode bestNode = m_openList.pop(); + bestNode.dtcsClearFlag(dtNodeFlags.DT_NODE_OPEN); + bestNode.dtcsSetFlag(dtNodeFlags.DT_NODE_CLOSED); + + // Reached the goal, stop searching. + if (bestNode.id == m_query.endRef) + { + m_query.lastBestNode = bestNode; + dtStatus details = m_query.status & DT_STATUS_DETAIL_MASK; + m_query.status = DT_SUCCESS | details; + //if (doneIters) + doneIters = iter; + return m_query.status; + } + + // Get current poly and tile. + // The API input has been cheked already, skip checking internal data. + dtPolyRef bestRef = bestNode.id; + dtMeshTile bestTile = null; + dtPoly bestPoly = null; + if (dtStatusFailed(m_nav.getTileAndPolyByRef(bestRef, ref bestTile, ref bestPoly))) + { + // The polygon has disappeared during the sliced query, fail. + m_query.status = DT_FAILURE; + //if (doneIters) + doneIters = iter; + return m_query.status; + } + + // Get parent poly and tile. + dtPolyRef parentRef = 0; + dtMeshTile parentTile = null; + dtPoly parentPoly = null; + if (bestNode.pidx != 0) + parentRef = m_nodePool.getNodeAtIdx(bestNode.pidx).id; + if (parentRef != 0) + { + if (dtStatusFailed(m_nav.getTileAndPolyByRef(parentRef, ref parentTile, ref parentPoly))) + { + // The polygon has disappeared during the sliced query, fail. + m_query.status = DT_FAILURE; + //if (doneIters) + doneIters = iter; + return m_query.status; + } + } + + for (uint i = bestPoly.firstLink; i != DT_NULL_LINK; i = bestTile.links[i].next) + { + dtPolyRef neighbourRef = bestTile.links[i].polyRef; + + // Skip invalid ids and do not expand back to where we came from. + if (neighbourRef == 0 || neighbourRef == parentRef) + continue; + + // Get neighbour poly and tile. + // The API input has been cheked already, skip checking internal data. + dtMeshTile neighbourTile = null; + dtPoly neighbourPoly = null; + m_nav.getTileAndPolyByRefUnsafe(neighbourRef, ref neighbourTile, ref neighbourPoly); + + if (!m_query.filter.passFilter(neighbourRef, neighbourTile, neighbourPoly)) + continue; + + dtNode neighbourNode = m_nodePool.getNode(neighbourRef); + if (neighbourNode == null) + { + m_query.status |= DT_OUT_OF_NODES; + continue; + } + + // If the node is visited the first time, calculate node position. + if (neighbourNode.flags == 0) + { + getEdgeMidPoint(bestRef, bestPoly, bestTile, + neighbourRef, neighbourPoly, neighbourTile, + neighbourNode.pos); + } + + // Calculate cost and heuristic. + float cost = 0; + float heuristic = 0; + + // Special case for last node. + if (neighbourRef == m_query.endRef) + { + // Cost + float curCost = m_query.filter.getCost(bestNode.pos, neighbourNode.pos, + parentRef, parentTile, parentPoly, + bestRef, bestTile, bestPoly, + neighbourRef, neighbourTile, neighbourPoly); + float endCost = m_query.filter.getCost(neighbourNode.pos, m_query.endPos, + bestRef, bestTile, bestPoly, + neighbourRef, neighbourTile, neighbourPoly, + 0, null, null); + + cost = bestNode.cost + curCost + endCost; + heuristic = 0; + } + else + { + // Cost + float curCost = m_query.filter.getCost(bestNode.pos, neighbourNode.pos, + parentRef, parentTile, parentPoly, + bestRef, bestTile, bestPoly, + neighbourRef, neighbourTile, neighbourPoly); + cost = bestNode.cost + curCost; + heuristic = dtVdist(neighbourNode.pos, m_query.endPos) * H_SCALE; + } + + float total = cost + heuristic; + + // The node is already in open list and the new result is worse, skip. + if ((neighbourNode.flags & (byte)dtNodeFlags.DT_NODE_OPEN) != 0 && total >= neighbourNode.total) + continue; + // The node is already visited and process, and the new result is worse, skip. + if ((neighbourNode.flags & (byte)dtNodeFlags.DT_NODE_CLOSED) != 0 && total >= neighbourNode.total) + continue; + + // Add or update the node. + neighbourNode.pidx = m_nodePool.getNodeIdx(bestNode); + neighbourNode.id = neighbourRef; + //neighbourNode.flags = (neighbourNode.flags & ~DT_NODE_CLOSED); + neighbourNode.dtcsClearFlag(dtNodeFlags.DT_NODE_CLOSED); + neighbourNode.cost = cost; + neighbourNode.total = total; + + if ((neighbourNode.flags & (byte)dtNodeFlags.DT_NODE_OPEN) != 0) + { + // Already in open, update node location. + m_openList.modify(neighbourNode); + } + else + { + // Put the node in open list. + //neighbourNode.flags |= DT_NODE_OPEN; + neighbourNode.dtcsSetFlag(dtNodeFlags.DT_NODE_OPEN); + m_openList.push(neighbourNode); + } + + // Update nearest node to target so far. + if (heuristic < m_query.lastBestNodeCost) + { + m_query.lastBestNodeCost = heuristic; + m_query.lastBestNode = neighbourNode; + } + } + } + + // Exhausted all nodes, but could not find path. + if (m_openList.empty()) + { + dtStatus details = m_query.status & DT_STATUS_DETAIL_MASK; + m_query.status = DT_SUCCESS | details; + } + + //if (doneIters) + doneIters = iter; + + return m_query.status; + } + + /// Finalizes and returns the results of a sliced path query. + /// @param[out] path An ordered list of polygon references representing the path. (Start to end.) + /// [(polyRef) * @p pathCount] + /// @param[out] pathCount The number of polygons returned in the @p path array. + /// @param[in] maxPath The max number of polygons the path array can hold. [Limit: >= 1] + /// @returns The status flags for the query. + public dtStatus finalizeSlicedFindPath(dtPolyRef[] path, ref int pathCount, int maxPath) + { + pathCount = 0; + + if (dtStatusFailed(m_query.status)) + { + // Reset query. + //memset(&m_query, 0, sizeof(dtQueryData)); + m_query.dtcsClear(); + return DT_FAILURE; + } + + int n = 0; + + if (m_query.startRef == m_query.endRef) + { + // Special case: the search starts and ends at same poly. + path[n++] = m_query.startRef; + } + else + { + // Reverse the path. + Debug.Assert(m_query.lastBestNode != null); + + if (m_query.lastBestNode.id != m_query.endRef) + m_query.status |= DT_PARTIAL_RESULT; + + dtNode prev = null; + dtNode node = m_query.lastBestNode; + do + { + dtNode next = m_nodePool.getNodeAtIdx(node.pidx); + node.pidx = m_nodePool.getNodeIdx(prev); + prev = node; + node = next; + } + while (node != null); + + // Store path + node = prev; + do + { + path[n++] = node.id; + if (n >= maxPath) + { + m_query.status |= DT_BUFFER_TOO_SMALL; + break; + } + node = m_nodePool.getNodeAtIdx(node.pidx); + } + while (node != null); + } + + dtStatus details = m_query.status & DT_STATUS_DETAIL_MASK; + + // Reset query. + //memset(&m_query, 0, sizeof(dtQueryData)); + m_query.dtcsClear(); + + pathCount = n; + + return DT_SUCCESS | details; + } + + /// Finalizes and returns the results of an incomplete sliced path query, returning the path to the furthest + /// polygon on the existing path that was visited during the search. + /// @param[in] existing An array of polygon references for the existing path. + /// @param[in] existingSize The number of polygon in the @p existing array. + /// @param[out] path An ordered list of polygon references representing the path. (Start to end.) + /// [(polyRef) * @p pathCount] + /// @param[out] pathCount The number of polygons returned in the @p path array. + /// @param[in] maxPath The max number of polygons the @p path array can hold. [Limit: >= 1] + /// @returns The status flags for the query. + public dtStatus finalizeSlicedFindPathPartial(dtPolyRef[] existing, int existingSize, dtPolyRef[] path, ref int pathCount, int maxPath) + { + pathCount = 0; + + if (existingSize == 0) + { + return DT_FAILURE; + } + + if (dtStatusFailed(m_query.status)) + { + // Reset query. + //memset(&m_query, 0, sizeof(dtQueryData)); + m_query.dtcsClear(); + return DT_FAILURE; + } + + int n = 0; + + if (m_query.startRef == m_query.endRef) + { + // Special case: the search starts and ends at same poly. + path[n++] = m_query.startRef; + } + else + { + // Find furthest existing node that was visited. + dtNode prev = null; + dtNode node = null; + for (int i = existingSize - 1; i >= 0; --i) + { + node = m_nodePool.findNode(existing[i]); + if (node != null) + break; + } + + if (node == null) + { + m_query.status |= DT_PARTIAL_RESULT; + Debug.Assert(m_query.lastBestNode != null); + node = m_query.lastBestNode; + } + + // Reverse the path. + do + { + dtNode next = m_nodePool.getNodeAtIdx(node.pidx); + node.pidx = m_nodePool.getNodeIdx(prev); + prev = node; + node = next; + } + while (node != null); + + // Store path + node = prev; + do + { + path[n++] = node.id; + if (n >= maxPath) + { + m_query.status |= DT_BUFFER_TOO_SMALL; + break; + } + node = m_nodePool.getNodeAtIdx(node.pidx); + } + while (node != null); + } + + dtStatus details = m_query.status & DT_STATUS_DETAIL_MASK; + + // Reset query. + //memset(&m_query, 0, sizeof(dtQueryData)); + m_query.dtcsClear(); + + pathCount = n; + + return DT_SUCCESS | details; + } + + // Appends vertex to a straight path + dtStatus appendVertex(float[] pos, byte flags, dtPolyRef polyRef, float[] straightPath, byte[] straightPathFlags, dtPolyRef[] straightPathRefs, ref int straightPathCount, int maxStraightPath) + { + if (straightPathCount > 0 && dtVequal(straightPath, (straightPathCount - 1) * 3, pos, 0)) + { + // The vertices are equal, update flags and poly. + if (straightPathFlags != null) + straightPathFlags[straightPathCount - 1] = flags; + if (straightPathRefs != null) + straightPathRefs[straightPathCount - 1] = polyRef; + } + else + { + // Append new vertex. + dtVcopy(straightPath, straightPathCount * 3, pos, 0); + if (straightPathFlags != null) + straightPathFlags[straightPathCount] = flags; + if (straightPathRefs != null) + straightPathRefs[straightPathCount] = polyRef; + straightPathCount++; + // If reached end of path or there is no space to append more vertices, return. + if (flags == (byte)dtStraightPathFlags.DT_STRAIGHTPATH_END || straightPathCount >= maxStraightPath) + { + return DT_SUCCESS | ((straightPathCount >= maxStraightPath) ? DT_BUFFER_TOO_SMALL : 0); + } + } + return DT_IN_PROGRESS; + } + + // Appends intermediate portal points to a straight path. + dtStatus appendPortals(int startIdx, int endIdx, float[] endPos, dtPolyRef[] path, float[] straightPath, byte[] straightPathFlags, dtPolyRef[] straightPathRefs, ref int straightPathCount, int maxStraightPath, int options) + { + //float* startPos = &straightPath[(*straightPathCount-1)*3]; + int startPosStart = (straightPathCount - 1) * 3; + // Append or update last vertex + dtStatus stat = 0; + for (int i = startIdx; i < endIdx; i++) + { + // Calculate portal + dtPolyRef from = path[i]; + dtMeshTile fromTile = null; + dtPoly fromPoly = null; + if (dtStatusFailed(m_nav.getTileAndPolyByRef(from, ref fromTile, ref fromPoly))) + return DT_FAILURE | DT_INVALID_PARAM; + + dtPolyRef to = path[i + 1]; + dtMeshTile toTile = null; + dtPoly toPoly = null; + if (dtStatusFailed(m_nav.getTileAndPolyByRef(to, ref toTile, ref toPoly))) + return DT_FAILURE | DT_INVALID_PARAM; + + float[] left = new float[3];//, right[3]; + float[] right = new float[3]; + if (dtStatusFailed(getPortalPoints(from, fromPoly, fromTile, to, toPoly, toTile, left, right))) + break; + + if ((options & (int)dtStraightPathOptions.DT_STRAIGHTPATH_AREA_CROSSINGS) != 0) + { + // Skip intersection if only area crossings are requested. + if (fromPoly.getArea() == toPoly.getArea()) + continue; + } + + // Append intersection + float s = .0f, t = .0f; + if (dtIntersectSegSeg2D(straightPath, startPosStart, endPos, 0, left, 0, right, 0, ref s, ref t)) + { + float[] pt = new float[3]; + dtVlerp(pt, left, right, t); + + stat = appendVertex(pt, 0, path[i + 1], + straightPath, straightPathFlags, straightPathRefs, + ref straightPathCount, maxStraightPath); + if (stat != DT_IN_PROGRESS) + return stat; + } + } + return DT_IN_PROGRESS; + } + + /// Finds the straight path from the start to the end position within the polygon corridor. + /// @param[in] startPos Path start position. [(x, y, z)] + /// @param[in] endPos Path end position. [(x, y, z)] + /// @param[in] path An array of polygon references that represent the path corridor. + /// @param[in] pathSize The number of polygons in the @p path array. + /// @param[out] straightPath Points describing the straight path. [(x, y, z) * @p straightPathCount]. + /// @param[out] straightPathFlags Flags describing each point. (See: #dtStraightPathFlags) [opt] + /// @param[out] straightPathRefs The reference id of the polygon that is being entered at each point. [opt] + /// @param[out] straightPathCount The number of points in the straight path. + /// @param[in] maxStraightPath The maximum number of points the straight path arrays can hold. [Limit: > 0] + /// @param[in] options Query options. (see: #dtStraightPathOptions) + /// @returns The status flags for the query. + /// @par + /// + /// This method peforms what is often called 'string pulling'. + /// + /// The start position is clamped to the first polygon in the path, and the + /// end position is clamped to the last. So the start and end positions should + /// normally be within or very near the first and last polygons respectively. + /// + /// The returned polygon references represent the reference id of the polygon + /// that is entered at the associated path position. The reference id associated + /// with the end point will always be zero. This allows, for example, matching + /// off-mesh link points to their representative polygons. + /// + /// If the provided result buffers are too small for the entire result set, + /// they will be filled as far as possible from the start toward the end + /// position. + /// + public dtStatus findStraightPath(float[] startPos, float[] endPos, dtPolyRef[] path, int pathSize, float[] straightPath, byte[] straightPathFlags, dtPolyRef[] straightPathRefs, ref int straightPathCount, int maxStraightPath, int options) + { + Debug.Assert(m_nav != null); + + straightPathCount = 0; + + if (maxStraightPath == 0) + return DT_FAILURE | DT_INVALID_PARAM; + + if (path[0] == 0) + return DT_FAILURE | DT_INVALID_PARAM; + + dtStatus stat = 0; + + // TODO: Should this be callers responsibility? + float[] closestStartPos = new float[3]; + if (dtStatusFailed(closestPointOnPolyBoundary(path[0], startPos, closestStartPos))) + return DT_FAILURE | DT_INVALID_PARAM; + + float[] closestEndPos = new float[3]; + if (dtStatusFailed(closestPointOnPolyBoundary(path[pathSize - 1], endPos, closestEndPos))) + return DT_FAILURE | DT_INVALID_PARAM; + + // Add start point. + stat = appendVertex(closestStartPos, (byte)dtStraightPathFlags.DT_STRAIGHTPATH_START, path[0], + straightPath, straightPathFlags, straightPathRefs, + ref straightPathCount, maxStraightPath); + if (stat != DT_IN_PROGRESS) + return stat; + + if (pathSize > 1) + { + float[] portalApex = new float[3];//, portalLeft[3], portalRight[3]; + float[] portalLeft = new float[3]; + float[] portalRight = new float[3]; + dtVcopy(portalApex, closestStartPos); + dtVcopy(portalLeft, portalApex); + dtVcopy(portalRight, portalApex); + int apexIndex = 0; + int leftIndex = 0; + int rightIndex = 0; + + byte leftPolyType = 0; + byte rightPolyType = 0; + + dtPolyRef leftPolyRef = path[0]; + dtPolyRef rightPolyRef = path[0]; + + for (int i = 0; i < pathSize; ++i) + { + float[] left = new float[3];//, right[3]; + float[] right = new float[3]; + byte fromType = 0, toType = 0; + + if (i + 1 < pathSize) + { + // Next portal. + if (dtStatusFailed(getPortalPoints(path[i], path[i + 1], left, right, ref fromType, ref toType))) + { + // Failed to get portal points, in practice this means that path[i+1] is invalid polygon. + // Clamp the end point to path[i], and return the path so far. + + if (dtStatusFailed(closestPointOnPolyBoundary(path[i], endPos, closestEndPos))) + { + // This should only happen when the first polygon is invalid. + return DT_FAILURE | DT_INVALID_PARAM; + } + + // Apeend portals along the current straight path segment. + if ((options & (int)(dtStraightPathOptions.DT_STRAIGHTPATH_AREA_CROSSINGS | dtStraightPathOptions.DT_STRAIGHTPATH_ALL_CROSSINGS)) != 0) + { + stat = appendPortals(apexIndex, i, closestEndPos, path, + straightPath, straightPathFlags, straightPathRefs, + ref straightPathCount, maxStraightPath, options); + } + + stat = appendVertex(closestEndPos, 0, path[i], + straightPath, straightPathFlags, straightPathRefs, + ref straightPathCount, maxStraightPath); + + return DT_SUCCESS | DT_PARTIAL_RESULT | ((straightPathCount >= maxStraightPath) ? DT_BUFFER_TOO_SMALL : (uint)0); + } + + // If starting really close the portal, advance. + if (i == 0) + { + float t = 0.0f; + if (dtDistancePtSegSqr2D(portalApex, 0, left, 0, right, 0, ref t) < dtSqr(0.001f)) + continue; + } + } + else + { + // End of the path. + dtVcopy(left, closestEndPos); + dtVcopy(right, closestEndPos); + + toType = (byte)dtPolyTypes.DT_POLYTYPE_GROUND; + fromType = (byte)dtPolyTypes.DT_POLYTYPE_GROUND; + } + + // Right vertex. + if (dtTriArea2D(portalApex, portalRight, right) <= 0.0f) + { + if (dtVequal(portalApex, portalRight) || dtTriArea2D(portalApex, portalLeft, right) > 0.0f) + { + dtVcopy(portalRight, right); + rightPolyRef = (i + 1 < pathSize) ? path[i + 1] : 0; + rightPolyType = toType; + rightIndex = i; + } + else + { + // Append portals along the current straight path segment. + if ((options & (int)(dtStraightPathOptions.DT_STRAIGHTPATH_AREA_CROSSINGS | dtStraightPathOptions.DT_STRAIGHTPATH_ALL_CROSSINGS)) != 0) + { + stat = appendPortals(apexIndex, leftIndex, portalLeft, path, + straightPath, straightPathFlags, straightPathRefs, + ref straightPathCount, maxStraightPath, options); + if (stat != DT_IN_PROGRESS) + return stat; + } + + dtVcopy(portalApex, portalLeft); + apexIndex = leftIndex; + + byte flags = 0; + if (leftPolyRef == 0) + flags = (byte)dtStraightPathFlags.DT_STRAIGHTPATH_END; + else if (leftPolyType == (byte)dtPolyTypes.DT_POLYTYPE_OFFMESH_CONNECTION) + flags = (byte)Detour.dtStraightPathFlags.DT_STRAIGHTPATH_OFFMESH_CONNECTION; + dtPolyRef polyRef = leftPolyRef; + + // Append or update vertex + stat = appendVertex(portalApex, flags, polyRef, + straightPath, straightPathFlags, straightPathRefs, + ref straightPathCount, maxStraightPath); + if (stat != DT_IN_PROGRESS) + return stat; + + dtVcopy(portalLeft, portalApex); + dtVcopy(portalRight, portalApex); + leftIndex = apexIndex; + rightIndex = apexIndex; + + // Restart + i = apexIndex; + + continue; + } + } + + // Left vertex. + if (dtTriArea2D(portalApex, portalLeft, left) >= 0.0f) + { + if (dtVequal(portalApex, portalLeft) || dtTriArea2D(portalApex, portalRight, left) < 0.0f) + { + dtVcopy(portalLeft, left); + leftPolyRef = (i + 1 < pathSize) ? path[i + 1] : 0; + leftPolyType = toType; + leftIndex = i; + } + else + { + // Append portals along the current straight path segment. + if ((options & (int)(dtStraightPathOptions.DT_STRAIGHTPATH_AREA_CROSSINGS | dtStraightPathOptions.DT_STRAIGHTPATH_ALL_CROSSINGS)) != 0) + { + stat = appendPortals(apexIndex, rightIndex, portalRight, path, + straightPath, straightPathFlags, straightPathRefs, + ref straightPathCount, maxStraightPath, options); + if (stat != DT_IN_PROGRESS) + return stat; + } + + dtVcopy(portalApex, portalRight); + apexIndex = rightIndex; + + byte flags = 0; + if (rightPolyRef == 0) + flags = (byte)dtStraightPathFlags.DT_STRAIGHTPATH_END; + else if (rightPolyType == (byte)dtPolyTypes.DT_POLYTYPE_OFFMESH_CONNECTION) + flags = (byte)dtStraightPathFlags.DT_STRAIGHTPATH_OFFMESH_CONNECTION; + dtPolyRef polyRef = rightPolyRef; + + // Append or update vertex + stat = appendVertex(portalApex, flags, polyRef, + straightPath, straightPathFlags, straightPathRefs, + ref straightPathCount, maxStraightPath); + if (stat != DT_IN_PROGRESS) + return stat; + + dtVcopy(portalLeft, portalApex); + dtVcopy(portalRight, portalApex); + leftIndex = apexIndex; + rightIndex = apexIndex; + + // Restart + i = apexIndex; + + continue; + } + } + } + + // Append portals along the current straight path segment. + if ((options & (int)(dtStraightPathOptions.DT_STRAIGHTPATH_AREA_CROSSINGS | dtStraightPathOptions.DT_STRAIGHTPATH_ALL_CROSSINGS)) != 0) + { + stat = appendPortals(apexIndex, pathSize - 1, closestEndPos, path, + straightPath, straightPathFlags, straightPathRefs, + ref straightPathCount, maxStraightPath, options); + if (stat != DT_IN_PROGRESS) + return stat; + } + } + + stat = appendVertex(closestEndPos, (byte)dtStraightPathFlags.DT_STRAIGHTPATH_END, 0, + straightPath, straightPathFlags, straightPathRefs, + ref straightPathCount, maxStraightPath); + + return DT_SUCCESS | ((straightPathCount >= maxStraightPath) ? DT_BUFFER_TOO_SMALL : 0); + } + + /// Moves from the start to the end position constrained to the navigation mesh. + /// @param[in] startRef The reference id of the start polygon. + /// @param[in] startPos A position of the mover within the start polygon. [(x, y, x)] + /// @param[in] endPos The desired end position of the mover. [(x, y, z)] + /// @param[in] filter The polygon filter to apply to the query. + /// @param[out] resultPos The result position of the mover. [(x, y, z)] + /// @param[out] visited The reference ids of the polygons visited during the move. + /// @param[out] visitedCount The number of polygons visited during the move. + /// @param[in] maxVisitedSize The maximum number of polygons the @p visited array can hold. + /// @returns The status flags for the query. + /// @par + /// + /// This method is optimized for small delta movement and a small number of + /// polygons. If used for too great a distance, the result set will form an + /// incomplete path. + /// + /// @p resultPos will equal the @p endPos if the end is reached. + /// Otherwise the closest reachable position will be returned. + /// + /// @p resultPos is not projected onto the surface of the navigation + /// mesh. Use #getPolyHeight if this is needed. + /// + /// This method treats the end position in the same manner as + /// the #raycast method. (As a 2D point.) See that method's documentation + /// for details. + /// + /// If the @p visited array is too small to hold the entire result set, it will + /// be filled as far as possible from the start position toward the end + /// position. + /// + public dtStatus moveAlongSurface(dtPolyRef startRef, float[] startPos, float[] endPos, dtQueryFilter filter, float[] resultPos, dtPolyRef[] visited, ref int visitedCount, int maxVisitedSize) + { + Debug.Assert(m_nav != null); + Debug.Assert(m_tinyNodePool != null); + + visitedCount = 0; + + // Validate input + if (startRef == 0) + return DT_FAILURE | DT_INVALID_PARAM; + if (!m_nav.isValidPolyRef(startRef)) + return DT_FAILURE | DT_INVALID_PARAM; + + dtStatus status = DT_SUCCESS; + + const int MAX_STACK = 48; + dtNode[] stack = new dtNode[MAX_STACK]; + int nstack = 0; + + m_tinyNodePool.clear(); + + dtNode startNode = m_tinyNodePool.getNode(startRef); + startNode.pidx = 0; + startNode.cost = 0; + startNode.total = 0; + startNode.id = startRef; + startNode.flags = (byte)dtNodeFlags.DT_NODE_CLOSED; + stack[nstack++] = startNode; + + float[] bestPos = new float[3]; + float bestDist = float.MaxValue; + dtNode bestNode = null; + dtVcopy(bestPos, startPos); + + // Search constraints + float[] searchPos = new float[3];//, searchRadSqr; + float searchRadSqr = .0f; + dtVlerp(searchPos, startPos, endPos, 0.5f); + searchRadSqr = dtSqr(dtVdist(startPos, endPos) / 2.0f + 0.001f); + + float[] verts = new float[DT_VERTS_PER_POLYGON * 3]; + + while (nstack != 0) + { + // Pop front. + dtNode curNode = stack[0]; + for (int i = 0; i < nstack - 1; ++i) + stack[i] = stack[i + 1]; + nstack--; + + // Get poly and tile. + // The API input has been cheked already, skip checking internal data. + dtPolyRef curRef = curNode.id; + dtMeshTile curTile = null; + dtPoly curPoly = null; + m_nav.getTileAndPolyByRefUnsafe(curRef, ref curTile, ref curPoly); + + // Collect vertices. + int nverts = curPoly.vertCount; + for (int i = 0; i < nverts; ++i) + { + dtVcopy(verts, i * 3, curTile.verts, curPoly.verts[i] * 3); + } + + // If target is inside the poly, stop search. + if (dtPointInPolygon(endPos, verts, nverts)) + { + bestNode = curNode; + dtVcopy(bestPos, endPos); + break; + } + + // Find wall edges and find nearest point inside the walls. + for (int i = 0, j = (int)curPoly.vertCount - 1; i < (int)curPoly.vertCount; j = i++) + { + // Find links to neighbours. + const int MAX_NEIS = 8; + int nneis = 0; + dtPolyRef[] neis = new dtPolyRef[MAX_NEIS]; + + if ((curPoly.neis[j] & DT_EXT_LINK) != 0) + { + // Tile border. + for (uint k = curPoly.firstLink; k != DT_NULL_LINK; k = curTile.links[k].next) + { + dtLink link = curTile.links[k]; + if (link.edge == j) + { + if (link.polyRef != 0) + { + dtMeshTile neiTile = null; + dtPoly neiPoly = null; + m_nav.getTileAndPolyByRefUnsafe(link.polyRef, ref neiTile, ref neiPoly); + if (filter.passFilter(link.polyRef, neiTile, neiPoly)) + { + if (nneis < MAX_NEIS) + neis[nneis++] = link.polyRef; + } + } + } + } + } + else if (curPoly.neis[j] != 0) + { + uint idx = (uint)(curPoly.neis[j] - 1); + dtPolyRef polyRef = m_nav.getPolyRefBase(curTile) | idx; + if (filter.passFilter(polyRef, curTile, curTile.polys[idx])) + { + // Internal edge, encode id. + neis[nneis++] = polyRef; + } + } + + if (nneis == 0) + { + // Wall edge, calc distance. + //const float* vj = &verts[j*3]; + //const float* vi = &verts[i*3]; + int vjStart = j * 3; + int viStart = i * 3; + float tseg = .0f; + float distSqr = dtDistancePtSegSqr2D(endPos, 0, verts, vjStart, verts, viStart, ref tseg); + if (distSqr < bestDist) + { + // Update nearest distance. + dtVlerp(bestPos, 0, verts, vjStart, verts, viStart, tseg); + bestDist = distSqr; + bestNode = curNode; + } + } + else + { + for (int k = 0; k < nneis; ++k) + { + // Skip if no node can be allocated. + dtNode neighbourNode = m_tinyNodePool.getNode(neis[k]); + if (neighbourNode == null) + continue; + // Skip if already visited. + if ((neighbourNode.flags & (byte)dtNodeFlags.DT_NODE_CLOSED) != 0) + continue; + + // Skip the link if it is too far from search constraint. + // TODO: Maybe should use getPortalPoints(), but this one is way faster. + int vjStart = j * 3; + int viStart = i * 3; + float tseg = .0f; + float distSqr = dtDistancePtSegSqr2D(searchPos, 0, verts, vjStart, verts, viStart, ref tseg); + if (distSqr > searchRadSqr) + { + continue; + } + + // Mark as the node as visited and push to queue. + if (nstack < MAX_STACK) + { + neighbourNode.pidx = m_tinyNodePool.getNodeIdx(curNode); + neighbourNode.dtcsSetFlag(dtNodeFlags.DT_NODE_CLOSED); + stack[nstack++] = neighbourNode; + } + } + } + } + } + + int n = 0; + if (bestNode != null) + { + // Reverse the path. + dtNode prev = null; + dtNode node = bestNode; + do + { + dtNode next = m_tinyNodePool.getNodeAtIdx(node.pidx); + node.pidx = m_tinyNodePool.getNodeIdx(prev); + prev = node; + node = next; + } + while (node != null); + + // Store result + node = prev; + do + { + + visited[n++] = node.id; + if (n >= maxVisitedSize) + { + status |= DT_BUFFER_TOO_SMALL; + break; + } + node = m_tinyNodePool.getNodeAtIdx(node.pidx); + } + while (node != null); + } + + dtVcopy(resultPos, bestPos); + + visitedCount = n; + + return status; + } + + /// Returns portal points between two polygons. + dtStatus getPortalPoints(dtPolyRef from, dtPolyRef to, float[] left, float[] right, ref byte fromType, ref byte toType) + { + Debug.Assert(m_nav != null); + + dtMeshTile fromTile = null; + dtPoly fromPoly = null; + if (dtStatusFailed(m_nav.getTileAndPolyByRef(from, ref fromTile, ref fromPoly))) + return DT_FAILURE | DT_INVALID_PARAM; + fromType = fromPoly.getType(); + + dtMeshTile toTile = null; + dtPoly toPoly = null; + if (dtStatusFailed(m_nav.getTileAndPolyByRef(to, ref toTile, ref toPoly))) + return DT_FAILURE | DT_INVALID_PARAM; + toType = toPoly.getType(); + + return getPortalPoints(from, fromPoly, fromTile, to, toPoly, toTile, left, right); + } + + // Returns portal points between two polygons. + dtStatus getPortalPoints(dtPolyRef from, dtPoly fromPoly, dtMeshTile fromTile, dtPolyRef to, dtPoly toPoly, dtMeshTile toTile, float[] left, float[] right) + { + // Find the link that points to the 'to' polygon. + dtLink link = null; + for (uint i = fromPoly.firstLink; i != DT_NULL_LINK; i = fromTile.links[i].next) + { + if (fromTile.links[i].polyRef == to) + { + link = fromTile.links[i]; + break; + } + } + if (link == null) + return DT_FAILURE | DT_INVALID_PARAM; + + // Handle off-mesh connections. + if (fromPoly.getType() == (byte)dtPolyTypes.DT_POLYTYPE_OFFMESH_CONNECTION) + { + // Find link that points to first vertex. + for (uint i = fromPoly.firstLink; i != DT_NULL_LINK; i = fromTile.links[i].next) + { + if (fromTile.links[i].polyRef == to) + { + int v = fromTile.links[i].edge; + dtVcopy(left, 0, fromTile.verts, fromPoly.verts[v] * 3); + dtVcopy(right, 0, fromTile.verts, fromPoly.verts[v] * 3); + return DT_SUCCESS; + } + } + return DT_FAILURE | DT_INVALID_PARAM; + } + + if (toPoly.getType() == (byte)dtPolyTypes.DT_POLYTYPE_OFFMESH_CONNECTION) + { + for (uint i = toPoly.firstLink; i != DT_NULL_LINK; i = toTile.links[i].next) + { + if (toTile.links[i].polyRef == from) + { + int v = toTile.links[i].edge; + dtVcopy(left, 0, toTile.verts, toPoly.verts[v] * 3); + dtVcopy(right, 0, toTile.verts, toPoly.verts[v] * 3); + return DT_SUCCESS; + } + } + return DT_FAILURE | DT_INVALID_PARAM; + } + + // Find portal vertices. + int v0 = fromPoly.verts[link.edge]; + int v1 = fromPoly.verts[(link.edge + 1) % (int)fromPoly.vertCount]; + dtVcopy(left, 0, fromTile.verts, v0 * 3); + dtVcopy(right, 0, fromTile.verts, v1 * 3); + + // If the link is at tile boundary, dtClamp the vertices to + // the link width. + if (link.side != 0xff) + { + // Unpack portal limits. + if (link.bmin != 0 || link.bmax != 255) + { + float s = 1.0f / 255.0f; + float tmin = link.bmin * s; + float tmax = link.bmax * s; + dtVlerp(left, 0, fromTile.verts, v0 * 3, fromTile.verts, v1 * 3, tmin); + dtVlerp(right, 0, fromTile.verts, v0 * 3, fromTile.verts, v1 * 3, tmax); + } + } + + return DT_SUCCESS; + } + + // Returns edge mid point between two polygons. + dtStatus getEdgeMidPoint(dtPolyRef from, dtPolyRef to, float[] mid) + { + float[] left = new float[3];//, right[3]; + float[] right = new float[3]; + byte fromType = 0, toType = 0; + if (dtStatusFailed(getPortalPoints(from, to, left, right, ref fromType, ref toType))) + return DT_FAILURE | DT_INVALID_PARAM; + mid[0] = (left[0] + right[0]) * 0.5f; + mid[1] = (left[1] + right[1]) * 0.5f; + mid[2] = (left[2] + right[2]) * 0.5f; + return DT_SUCCESS; + } + + dtStatus getEdgeMidPoint(dtPolyRef from, dtPoly fromPoly, dtMeshTile fromTile, dtPolyRef to, dtPoly toPoly, dtMeshTile toTile, float[] mid) + { + float[] left = new float[3];//, right[3]; + float[] right = new float[3]; + if (dtStatusFailed(getPortalPoints(from, fromPoly, fromTile, to, toPoly, toTile, left, right))) + return DT_FAILURE | DT_INVALID_PARAM; + mid[0] = (left[0] + right[0]) * 0.5f; + mid[1] = (left[1] + right[1]) * 0.5f; + mid[2] = (left[2] + right[2]) * 0.5f; + return DT_SUCCESS; + } + + /// Casts a 'walkability' ray along the surface of the navigation mesh from + /// the start position toward the end position. + /// @param[in] startRef The reference id of the start polygon. + /// @param[in] startPos A position within the start polygon representing + /// the start of the ray. [(x, y, z)] + /// @param[in] endPos The position to cast the ray toward. [(x, y, z)] + /// @param[out] t The hit parameter. (FLT_MAX if no wall hit.) + /// @param[out] hitNormal The normal of the nearest wall hit. [(x, y, z)] + /// @param[in] filter The polygon filter to apply to the query. + /// @param[out] path The reference ids of the visited polygons. [opt] + /// @param[out] pathCount The number of visited polygons. [opt] + /// @param[in] maxPath The maximum number of polygons the @p path array can hold. + /// @returns The status flags for the query. + /// @par + /// + /// This method is meant to be used for quick, short distance checks. + /// + /// If the path array is too small to hold the result, it will be filled as + /// far as possible from the start postion toward the end position. + /// + /// Using the Hit Parameter (t) + /// + /// If the hit parameter is a very high value (FLT_MAX), then the ray has hit + /// the end position. In this case the path represents a valid corridor to the + /// end position and the value of @p hitNormal is undefined. + /// + /// If the hit parameter is zero, then the start position is on the wall that + /// was hit and the value of @p hitNormal is undefined. + /// + /// If 0 < t < 1.0 then the following applies: + /// + /// @code + /// distanceToHitBorder = distanceToEndPosition * t + /// hitPoint = startPos + (endPos - startPos) * t + /// @endcode + /// + /// Use Case Restriction + /// + /// The raycast ignores the y-value of the end position. (2D check.) This + /// places significant limits on how it can be used. For example: + /// + /// Consider a scene where there is a main floor with a second floor balcony + /// that hangs over the main floor. So the first floor mesh extends below the + /// balcony mesh. The start position is somewhere on the first floor. The end + /// position is on the balcony. + /// + /// The raycast will search toward the end position along the first floor mesh. + /// If it reaches the end position's xz-coordinates it will indicate FLT_MAX + /// (no wall hit), meaning it reached the end position. This is one example of why + /// this method is meant for short distance checks. + /// + public dtStatus raycast(dtPolyRef startRef, float[] startPos, float[] endPos, dtQueryFilter filter, ref float t, float[] hitNormal, dtPolyRef[] path, ref uint pathCount, int maxPath) + { + Debug.Assert(m_nav != null); + + t = 0; + //if (pathCount) + pathCount = 0; + + // Validate input + if (startRef == 0 || !m_nav.isValidPolyRef(startRef)) + return DT_FAILURE | DT_INVALID_PARAM; + + dtPolyRef curRef = startRef; + float[] verts = new float[DT_VERTS_PER_POLYGON * 3]; + int n = 0; + + hitNormal[0] = 0; + hitNormal[1] = 0; + hitNormal[2] = 0; + + dtStatus status = DT_SUCCESS; + + while (curRef != 0) + { + // Cast ray against current polygon. + + // The API input has been cheked already, skip checking internal data. + dtMeshTile tile = null; + dtPoly poly = null; + m_nav.getTileAndPolyByRefUnsafe(curRef, ref tile, ref poly); + + // Collect vertices. + int nv = 0; + for (int i = 0; i < (int)poly.vertCount; ++i) + { + dtVcopy(verts, nv * 3, tile.verts, poly.verts[i] * 3); + nv++; + } + + float tmin = 0, tmax = 0; + int segMin = 0, segMax = 0; + if (!dtIntersectSegmentPoly2D(startPos, endPos, verts, nv, ref tmin, ref tmax, ref segMin, ref segMax)) + { + // Could not hit the polygon, keep the old t and report hit. + pathCount = (uint)n; + return status; + } + // Keep track of furthest t so far. + if (tmax > t) + t = tmax; + + // Store visited polygons. + if (n < maxPath) + path[n++] = curRef; + else + status |= DT_BUFFER_TOO_SMALL; + + // Ray end is completely inside the polygon. + if (segMax == -1) + { + t = float.MaxValue; + //if (pathCount) + pathCount = (uint)n; + return status; + } + + // Follow neighbours. + dtPolyRef nextRef = 0; + + for (uint i = poly.firstLink; i != DT_NULL_LINK; i = tile.links[i].next) + { + dtLink link = tile.links[i]; + + // Find link which contains this edge. + if ((int)link.edge != segMax) + continue; + + // Get pointer to the next polygon. + dtMeshTile nextTile = null; + dtPoly nextPoly = null; + m_nav.getTileAndPolyByRefUnsafe(link.polyRef, ref nextTile, ref nextPoly); + + // Skip off-mesh connections. + if (nextPoly.getType() == (byte)dtPolyTypes.DT_POLYTYPE_OFFMESH_CONNECTION) + continue; + + // Skip links based on filter. + if (!filter.passFilter(link.polyRef, nextTile, nextPoly)) + continue; + + // If the link is internal, just return the ref. + if (link.side == 0xff) + { + nextRef = link.polyRef; + break; + } + + // If the link is at tile boundary, + + // Check if the link spans the whole edge, and accept. + if (link.bmin == 0 && link.bmax == 255) + { + nextRef = link.polyRef; + break; + } + + // Check for partial edge links. + int v0 = poly.verts[link.edge]; + int v1 = poly.verts[(link.edge + 1) % poly.vertCount]; + //const float* left = &tile.verts[v0*3]; + //const float* right = &tile.verts[v1*3]; + int leftStart = v0 * 3; + int rightStart = v1 * 3; + + // Check that the intersection lies inside the link portal. + if (link.side == 0 || link.side == 4) + { + // Calculate link size. + const float s = 1.0f / 255.0f; + float lmin = tile.verts[leftStart + 2] + (tile.verts[rightStart + 2] - tile.verts[leftStart + 2]) * (link.bmin * s); + float lmax = tile.verts[leftStart + 2] + (tile.verts[rightStart + 2] - tile.verts[leftStart + 2]) * (link.bmax * s); + if (lmin > lmax) + dtSwap(ref lmin, ref lmax); + + // Find Z intersection. + float z = startPos[2] + (endPos[2] - startPos[2]) * tmax; + if (z >= lmin && z <= lmax) + { + nextRef = link.polyRef; + break; + } + } + else if (link.side == 2 || link.side == 6) + { + // Calculate link size. + const float s = 1.0f / 255.0f; + float lmin = tile.verts[leftStart + 0] + (tile.verts[rightStart + 0] - tile.verts[leftStart + 0]) * (link.bmin * s); + float lmax = tile.verts[leftStart + 0] + (tile.verts[rightStart + 0] - tile.verts[leftStart + 0]) * (link.bmax * s); + if (lmin > lmax) + dtSwap(ref lmin, ref lmax); + + // Find X intersection. + float x = startPos[0] + (endPos[0] - startPos[0]) * tmax; + if (x >= lmin && x <= lmax) + { + nextRef = link.polyRef; + break; + } + } + } + + if (nextRef == 0) + { + // No neighbour, we hit a wall. + + // Calculate hit normal. + int a = segMax; + int b = segMax + 1 < nv ? segMax + 1 : 0; + //const float* va = &verts[a*3]; + //const float* vb = &verts[b*3]; + int vaStart = a * 3; + int vbStart = b * 3; + float dx = verts[vbStart + 0] - verts[vaStart + 0]; + float dz = verts[vbStart + 2] - verts[vaStart + 2]; + hitNormal[0] = dz; + hitNormal[1] = 0; + hitNormal[2] = -dx; + dtVnormalize(hitNormal); + + //if (pathCount) + pathCount = (uint)n; + return status; + } + + // No hit, advance to neighbour polygon. + curRef = nextRef; + } + + //if (pathCount) + pathCount = (uint)n; + + return status; + } + + ///@} + /// @name Dijkstra Search Functions + /// @{ + + /// Finds the polygons along the navigation graph that touch the specified circle. + /// @param[in] startRef The reference id of the polygon where the search starts. + /// @param[in] centerPos The center of the search circle. [(x, y, z)] + /// @param[in] radius The radius of the search circle. + /// @param[in] filter The polygon filter to apply to the query. + /// @param[out] resultRef The reference ids of the polygons touched by the circle. [opt] + /// @param[out] resultParent The reference ids of the parent polygons for each result. + /// Zero if a result polygon has no parent. [opt] + /// @param[out] resultCost The search cost from @p centerPos to the polygon. [opt] + /// @param[out] resultCount The number of polygons found. [opt] + /// @param[in] maxResult The maximum number of polygons the result arrays can hold. + /// @returns The status flags for the query. + /// @par + /// + /// At least one result array must be provided. + /// + /// The order of the result set is from least to highest cost to reach the polygon. + /// + /// A common use case for this method is to perform Dijkstra searches. + /// Candidate polygons are found by searching the graph beginning at the start polygon. + /// + /// If a polygon is not found via the graph search, even if it intersects the + /// search circle, it will not be included in the result set. For example: + /// + /// polyA is the start polygon. + /// polyB shares an edge with polyA. (Is adjacent.) + /// polyC shares an edge with polyB, but not with polyA + /// Even if the search circle overlaps polyC, it will not be included in the + /// result set unless polyB is also in the set. + /// + /// The value of the center point is used as the start position for cost + /// calculations. It is not projected onto the surface of the mesh, so its + /// y-value will effect the costs. + /// + /// Intersection tests occur in 2D. All polygons and the search circle are + /// projected onto the xz-plane. So the y-value of the center point does not + /// effect intersection tests. + /// + /// If the result arrays are to small to hold the entire result set, they will be + /// filled to capacity. + /// + dtStatus findPolysAroundCircle(dtPolyRef startRef, float[] centerPos, float radius, dtQueryFilter filter, dtPolyRef[] resultRef, dtPolyRef[] resultParent, float[] resultCost, ref int resultCount, int maxResult) + { + Debug.Assert(m_nav != null); + Debug.Assert(m_nodePool != null); + Debug.Assert(m_openList != null); + + resultCount = 0; + + // Validate input + if (startRef == 0 || !m_nav.isValidPolyRef(startRef)) + return DT_FAILURE | DT_INVALID_PARAM; + + m_nodePool.clear(); + m_openList.clear(); + + dtNode startNode = m_nodePool.getNode(startRef); + dtVcopy(startNode.pos, centerPos); + startNode.pidx = 0; + startNode.cost = 0; + startNode.total = 0; + startNode.id = startRef; + startNode.flags = (byte)dtNodeFlags.DT_NODE_OPEN; + m_openList.push(startNode); + + dtStatus status = DT_SUCCESS; + + int n = 0; + if (n < maxResult) + { + if (resultRef != null) + resultRef[n] = startNode.id; + if (resultParent != null) + resultParent[n] = 0; + if (resultCost != null) + resultCost[n] = 0; + ++n; + } + else + { + status |= DT_BUFFER_TOO_SMALL; + } + + float radiusSqr = dtSqr(radius); + + while (!m_openList.empty()) + { + dtNode bestNode = m_openList.pop(); + //bestNode.flags &= ~DT_NODE_OPEN; + //bestNode.flags |= DT_NODE_CLOSED; + bestNode.dtcsClearFlag(dtNodeFlags.DT_NODE_OPEN); + bestNode.dtcsSetFlag(dtNodeFlags.DT_NODE_CLOSED); + + // Get poly and tile. + // The API input has been cheked already, skip checking internal data. + dtPolyRef bestRef = bestNode.id; + dtMeshTile bestTile = null; + dtPoly bestPoly = null; + m_nav.getTileAndPolyByRefUnsafe(bestRef, ref bestTile, ref bestPoly); + + // Get parent poly and tile. + dtPolyRef parentRef = 0; + dtMeshTile parentTile = null; + dtPoly parentPoly = null; + if (bestNode.pidx != 0) + parentRef = m_nodePool.getNodeAtIdx(bestNode.pidx).id; + if (parentRef != 0) + m_nav.getTileAndPolyByRefUnsafe(parentRef, ref parentTile, ref parentPoly); + + for (uint i = bestPoly.firstLink; i != DT_NULL_LINK; i = bestTile.links[i].next) + { + dtLink link = bestTile.links[i]; + dtPolyRef neighbourRef = link.polyRef; + // Skip invalid neighbours and do not follow back to parent. + if (neighbourRef == 0 || neighbourRef == parentRef) + continue; + + // Expand to neighbour + dtMeshTile neighbourTile = null; + dtPoly neighbourPoly = null; + m_nav.getTileAndPolyByRefUnsafe(neighbourRef, ref neighbourTile, ref neighbourPoly); + + // Do not advance if the polygon is excluded by the filter. + if (!filter.passFilter(neighbourRef, neighbourTile, neighbourPoly)) + continue; + + // Find edge and calc distance to the edge. + float[] va = new float[3];//, vb[3]; + float[] vb = new float[3]; + if (getPortalPoints(bestRef, bestPoly, bestTile, neighbourRef, neighbourPoly, neighbourTile, va, vb) == 0) + continue; + + // If the circle is not touching the next polygon, skip it. + float tseg = 0.0f; + float distSqr = dtDistancePtSegSqr2D(centerPos, 0, va, 0, vb, 0, ref tseg); + if (distSqr > radiusSqr) + continue; + + dtNode neighbourNode = m_nodePool.getNode(neighbourRef); + if (neighbourNode == null) + { + status |= DT_OUT_OF_NODES; + continue; + } + + if (neighbourNode.dtcsTestFlag(dtNodeFlags.DT_NODE_CLOSED)) + continue; + + // Cost + if (neighbourNode.flags == 0) + dtVlerp(neighbourNode.pos, va, vb, 0.5f); + + float total = bestNode.total + dtVdist(bestNode.pos, neighbourNode.pos); + + // The node is already in open list and the new result is worse, skip. + if ((neighbourNode.dtcsTestFlag(dtNodeFlags.DT_NODE_OPEN)) && total >= neighbourNode.total) + continue; + + neighbourNode.id = neighbourRef; + //neighbourNode.flags = (neighbourNode.flags & ~DT_NODE_CLOSED); + neighbourNode.dtcsClearFlag(dtNodeFlags.DT_NODE_CLOSED); + neighbourNode.pidx = m_nodePool.getNodeIdx(bestNode); + neighbourNode.total = total; + + if (neighbourNode.dtcsTestFlag(dtNodeFlags.DT_NODE_OPEN)) + { + m_openList.modify(neighbourNode); + } + else + { + if (n < maxResult) + { + if (resultRef != null) + resultRef[n] = neighbourNode.id; + if (resultParent != null) + resultParent[n] = m_nodePool.getNodeAtIdx(neighbourNode.pidx).id; + if (resultCost != null) + resultCost[n] = neighbourNode.total; + ++n; + } + else + { + status |= DT_BUFFER_TOO_SMALL; + } + neighbourNode.flags = (byte)dtNodeFlags.DT_NODE_OPEN; + m_openList.push(neighbourNode); + } + } + } + + resultCount = n; + + return status; + } + + /// Finds the polygons along the naviation graph that touch the specified convex polygon. + /// @param[in] startRef The reference id of the polygon where the search starts. + /// @param[in] verts The vertices describing the convex polygon. (CCW) + /// [(x, y, z) * @p nverts] + /// @param[in] nverts The number of vertices in the polygon. + /// @param[in] filter The polygon filter to apply to the query. + /// @param[out] resultRef The reference ids of the polygons touched by the search polygon. [opt] + /// @param[out] resultParent The reference ids of the parent polygons for each result. Zero if a + /// result polygon has no parent. [opt] + /// @param[out] resultCost The search cost from the centroid point to the polygon. [opt] + /// @param[out] resultCount The number of polygons found. + /// @param[in] maxResult The maximum number of polygons the result arrays can hold. + /// @returns The status flags for the query. + /// @par + /// + /// The order of the result set is from least to highest cost. + /// + /// At least one result array must be provided. + /// + /// A common use case for this method is to perform Dijkstra searches. + /// Candidate polygons are found by searching the graph beginning at the start + /// polygon. + /// + /// The same intersection test restrictions that apply to findPolysAroundCircle() + /// method apply to this method. + /// + /// The 3D centroid of the search polygon is used as the start position for cost + /// calculations. + /// + /// Intersection tests occur in 2D. All polygons are projected onto the + /// xz-plane. So the y-values of the vertices do not effect intersection tests. + /// + /// If the result arrays are is too small to hold the entire result set, they will + /// be filled to capacity. + /// + dtStatus findPolysAroundShape(dtPolyRef startRef, float[] verts, int nverts, dtQueryFilter filter, dtPolyRef[] resultRef, dtPolyRef[] resultParent, float[] resultCost, ref int resultCount, int maxResult) + { + Debug.Assert(m_nav != null); + Debug.Assert(m_nodePool != null); + Debug.Assert(m_openList != null); + + resultCount = 0; + + // Validate input + if (startRef == 0 || !m_nav.isValidPolyRef(startRef)) + return DT_FAILURE | DT_INVALID_PARAM; + + m_nodePool.clear(); + m_openList.clear(); + + float[] centerPos = new float[] { 0, 0, 0 }; + for (int i = 0; i < nverts; ++i) + { + dtVadd(centerPos, 0, centerPos, 0, verts, i * 3); + } + dtVscale(centerPos, centerPos, 1.0f / nverts); + + dtNode startNode = m_nodePool.getNode(startRef); + dtVcopy(startNode.pos, centerPos); + startNode.pidx = 0; + startNode.cost = 0; + startNode.total = 0; + startNode.id = startRef; + startNode.flags = (byte)dtNodeFlags.DT_NODE_OPEN; + m_openList.push(startNode); + + dtStatus status = DT_SUCCESS; + + int n = 0; + if (n < maxResult) + { + if (resultRef != null) + resultRef[n] = startNode.id; + if (resultParent != null) + resultParent[n] = 0; + if (resultCost != null) + resultCost[n] = 0; + ++n; + } + else + { + status |= DT_BUFFER_TOO_SMALL; + } + + while (!m_openList.empty()) + { + dtNode bestNode = m_openList.pop(); + //bestNode.flags &= ~DT_NODE_OPEN; + //bestNode.flags |= DT_NODE_CLOSED; + bestNode.dtcsClearFlag(dtNodeFlags.DT_NODE_OPEN); + bestNode.dtcsSetFlag(dtNodeFlags.DT_NODE_CLOSED); + + // Get poly and tile. + // The API input has been cheked already, skip checking internal data. + dtPolyRef bestRef = bestNode.id; + dtMeshTile bestTile = null; + dtPoly bestPoly = null; + m_nav.getTileAndPolyByRefUnsafe(bestRef, ref bestTile, ref bestPoly); + + // Get parent poly and tile. + dtPolyRef parentRef = 0; + dtMeshTile parentTile = null; + dtPoly parentPoly = null; + if (bestNode.pidx != 0) + parentRef = m_nodePool.getNodeAtIdx(bestNode.pidx).id; + if (parentRef != 0) + m_nav.getTileAndPolyByRefUnsafe(parentRef, ref parentTile, ref parentPoly); + + for (uint i = bestPoly.firstLink; i != DT_NULL_LINK; i = bestTile.links[i].next) + { + dtLink link = bestTile.links[i]; + dtPolyRef neighbourRef = link.polyRef; + // Skip invalid neighbours and do not follow back to parent. + if (neighbourRef == 0 || neighbourRef == parentRef) + continue; + + // Expand to neighbour + dtMeshTile neighbourTile = null; + dtPoly neighbourPoly = null; + m_nav.getTileAndPolyByRefUnsafe(neighbourRef, ref neighbourTile, ref neighbourPoly); + + // Do not advance if the polygon is excluded by the filter. + if (!filter.passFilter(neighbourRef, neighbourTile, neighbourPoly)) + continue; + + // Find edge and calc distance to the edge. + float[] va = new float[3];//, vb[3]; + float[] vb = new float[3]; + if (getPortalPoints(bestRef, bestPoly, bestTile, neighbourRef, neighbourPoly, neighbourTile, va, vb) == 0) + continue; + + // If the poly is not touching the edge to the next polygon, skip the connection it. + float tmin = 0, tmax = 0; + int segMin = 0, segMax = 0; + if (dtIntersectSegmentPoly2D(va, vb, verts, nverts, ref tmin, ref tmax, ref segMin, ref segMax)) + continue; + if (tmin > 1.0f || tmax < 0.0f) + continue; + + dtNode neighbourNode = m_nodePool.getNode(neighbourRef); + if (neighbourNode == null) + { + status |= DT_OUT_OF_NODES; + continue; + } + + if (neighbourNode.dtcsTestFlag(dtNodeFlags.DT_NODE_CLOSED)) + continue; + + // Cost + if (neighbourNode.flags == 0) + dtVlerp(neighbourNode.pos, va, vb, 0.5f); + + float total = bestNode.total + dtVdist(bestNode.pos, neighbourNode.pos); + + // The node is already in open list and the new result is worse, skip. + if ((neighbourNode.dtcsTestFlag(dtNodeFlags.DT_NODE_OPEN)) && total >= neighbourNode.total) + continue; + + neighbourNode.id = neighbourRef; + neighbourNode.dtcsClearFlag(dtNodeFlags.DT_NODE_CLOSED);// = (neighbourNode.flags & ~DT_NODE_CLOSED); + neighbourNode.pidx = m_nodePool.getNodeIdx(bestNode); + neighbourNode.total = total; + + if (neighbourNode.dtcsTestFlag(dtNodeFlags.DT_NODE_OPEN))// .flags & DT_NODE_OPEN) + { + m_openList.modify(neighbourNode); + } + else + { + if (n < maxResult) + { + if (resultRef != null) + resultRef[n] = neighbourNode.id; + if (resultParent != null) + resultParent[n] = m_nodePool.getNodeAtIdx(neighbourNode.pidx).id; + if (resultCost != null) + resultCost[n] = neighbourNode.total; + ++n; + } + else + { + status |= DT_BUFFER_TOO_SMALL; + } + neighbourNode.flags = (byte)dtNodeFlags.DT_NODE_OPEN; + m_openList.push(neighbourNode); + } + } + } + + resultCount = n; + + return status; + } + + /// Finds the non-overlapping navigation polygons in the local neighbourhood around the center position. + /// @param[in] startRef The reference id of the polygon where the search starts. + /// @param[in] centerPos The center of the query circle. [(x, y, z)] + /// @param[in] radius The radius of the query circle. + /// @param[in] filter The polygon filter to apply to the query. + /// @param[out] resultRef The reference ids of the polygons touched by the circle. + /// @param[out] resultParent The reference ids of the parent polygons for each result. + /// Zero if a result polygon has no parent. [opt] + /// @param[out] resultCount The number of polygons found. + /// @param[in] maxResult The maximum number of polygons the result arrays can hold. + /// @returns The status flags for the query. + /// @par + /// + /// This method is optimized for a small search radius and small number of result + /// polygons. + /// + /// Candidate polygons are found by searching the navigation graph beginning at + /// the start polygon. + /// + /// The same intersection test restrictions that apply to the findPolysAroundCircle + /// mehtod applies to this method. + /// + /// The value of the center point is used as the start point for cost calculations. + /// It is not projected onto the surface of the mesh, so its y-value will effect + /// the costs. + /// + /// Intersection tests occur in 2D. All polygons and the search circle are + /// projected onto the xz-plane. So the y-value of the center point does not + /// effect intersection tests. + /// + /// If the result arrays are is too small to hold the entire result set, they will + /// be filled to capacity. + /// + dtStatus findLocalNeighbourhood(dtPolyRef startRef, float[] centerPos, float radius, dtQueryFilter filter, dtPolyRef[] resultRef, dtPolyRef[] resultParent, ref int resultCount, int maxResult) + { + Debug.Assert(m_nav != null); + Debug.Assert(m_tinyNodePool != null); + + resultCount = 0; + + // Validate input + if (startRef == 0 || !m_nav.isValidPolyRef(startRef)) + return DT_FAILURE | DT_INVALID_PARAM; + + const int MAX_STACK = 48; + dtNode[] stack = new dtNode[MAX_STACK]; + dtcsArrayItemsCreate(stack); + int nstack = 0; + + m_tinyNodePool.clear(); + + dtNode startNode = m_tinyNodePool.getNode(startRef); + startNode.pidx = 0; + startNode.id = startRef; + startNode.flags = (byte)dtNodeFlags.DT_NODE_CLOSED; + stack[nstack++] = startNode; + + float radiusSqr = dtSqr(radius); + + float[] pa = new float[DT_VERTS_PER_POLYGON * 3]; + float[] pb = new float[DT_VERTS_PER_POLYGON * 3]; + + dtStatus status = DT_SUCCESS; + + int n = 0; + if (n < maxResult) + { + resultRef[n] = startNode.id; + if (resultParent != null) + resultParent[n] = 0; + ++n; + } + else + { + status |= DT_BUFFER_TOO_SMALL; + } + + while (nstack != 0) + { + // Pop front. + dtNode curNode = stack[0]; + for (int i = 0; i < nstack - 1; ++i) + stack[i] = stack[i + 1]; + nstack--; + + // Get poly and tile. + // The API input has been cheked already, skip checking internal data. + dtPolyRef curRef = curNode.id; + dtMeshTile curTile = null; + dtPoly curPoly = null; + m_nav.getTileAndPolyByRefUnsafe(curRef, ref curTile, ref curPoly); + + for (uint i = curPoly.firstLink; i != DT_NULL_LINK; i = curTile.links[i].next) + { + dtLink link = curTile.links[i]; + dtPolyRef neighbourRef = link.polyRef; + // Skip invalid neighbours. + if (neighbourRef == 0) + continue; + + // Skip if cannot alloca more nodes. + dtNode neighbourNode = m_tinyNodePool.getNode(neighbourRef); + if (neighbourNode == null) + continue; + // Skip visited. + if (neighbourNode.dtcsTestFlag(dtNodeFlags.DT_NODE_CLOSED))// .flags & DT_NODE_CLOSED) + continue; + + // Expand to neighbour + dtMeshTile neighbourTile = null; + dtPoly neighbourPoly = null; + m_nav.getTileAndPolyByRefUnsafe(neighbourRef, ref neighbourTile, ref neighbourPoly); + + // Skip off-mesh connections. + if (neighbourPoly.getType() == (byte)dtPolyTypes.DT_POLYTYPE_OFFMESH_CONNECTION) + continue; + + // Do not advance if the polygon is excluded by the filter. + if (!filter.passFilter(neighbourRef, neighbourTile, neighbourPoly)) + continue; + + // Find edge and calc distance to the edge. + float[] va = new float[3];//, vb[3]; + float[] vb = new float[3]; + if (getPortalPoints(curRef, curPoly, curTile, neighbourRef, neighbourPoly, neighbourTile, va, vb) == 0) + continue; + + // If the circle is not touching the next polygon, skip it. + float tseg = .0f; + float distSqr = dtDistancePtSegSqr2D(centerPos, 0, va, 0, vb, 0, ref tseg); + if (distSqr > radiusSqr) + continue; + + // Mark node visited, this is done before the overlap test so that + // we will not visit the poly again if the test fails. + //neighbourNode.flags |= DT_NODE_CLOSED; + neighbourNode.dtcsSetFlag(dtNodeFlags.DT_NODE_CLOSED); + neighbourNode.pidx = m_tinyNodePool.getNodeIdx(curNode); + + // Check that the polygon does not collide with existing polygons. + + // Collect vertices of the neighbour poly. + int npa = neighbourPoly.vertCount; + for (int k = 0; k < npa; ++k) + { + dtVcopy(pa, k * 3, neighbourTile.verts, neighbourPoly.verts[k] * 3); + } + + bool overlap = false; + for (int j = 0; j < n; ++j) + { + dtPolyRef pastRef = resultRef[j]; + + // Connected polys do not overlap. + bool connected = false; + for (uint k = curPoly.firstLink; k != DT_NULL_LINK; k = curTile.links[k].next) + { + if (curTile.links[k].polyRef == pastRef) + { + connected = true; + break; + } + } + if (connected) + continue; + + // Potentially overlapping. + dtMeshTile pastTile = null; + dtPoly pastPoly = null; + m_nav.getTileAndPolyByRefUnsafe(pastRef, ref pastTile, ref pastPoly); + + // Get vertices and test overlap + int npb = pastPoly.vertCount; + for (int k = 0; k < npb; ++k) + { + dtVcopy(pb, k * 3, pastTile.verts, pastPoly.verts[k] * 3); + } + + if (dtOverlapPolyPoly2D(pa, npa, pb, npb)) + { + overlap = true; + break; + } + } + if (overlap) + continue; + + // This poly is fine, store and advance to the poly. + if (n < maxResult) + { + resultRef[n] = neighbourRef; + if (resultParent != null) + resultParent[n] = curRef; + ++n; + } + else + { + status |= DT_BUFFER_TOO_SMALL; + } + + if (nstack < MAX_STACK) + { + stack[nstack++] = neighbourNode; + } + } + } + + resultCount = n; + + return status; + } + + class dtSegInterval + { + public dtPolyRef polyRef; + public short tmin; + public short tmax; + }; + + static void insertInterval(dtSegInterval[] ints, ref int nints, int maxInts, short tmin, short tmax, dtPolyRef polyRef) + { + if (nints + 1 > maxInts) + return; + // Find insertion point. + int idx = 0; + while (idx < nints) + { + if (tmax <= ints[idx].tmin) + break; + idx++; + } + // Move current results. + if (nints - idx != 0) + { + //memmove(ints+idx+1, ints+idx, sizeof(dtSegInterval)*(nints-idx)); + for (int i = 0; i < (nints - idx); ++i) + { + ints[idx + 1 + i] = ints[idx + i]; + } + } + // Store + ints[idx].polyRef = polyRef; + ints[idx].tmin = tmin; + ints[idx].tmax = tmax; + nints++; + } + + /// Returns the segments for the specified polygon, optionally including portals. + /// @param[in] ref The reference id of the polygon. + /// @param[in] filter The polygon filter to apply to the query. + /// @param[out] segmentVerts The segments. [(ax, ay, az, bx, by, bz) * segmentCount] + /// @param[out] segmentRefs The reference ids of each segment's neighbor polygon. + /// Or zero if the segment is a wall. [opt] [(parentRef) * @p segmentCount] + /// @param[out] segmentCount The number of segments returned. + /// @param[in] maxSegments The maximum number of segments the result arrays can hold. + /// @returns The status flags for the query. + /// @par + /// + /// If the @p segmentRefs parameter is provided, then all polygon segments will be returned. + /// Otherwise only the wall segments are returned. + /// + /// A segment that is normally a portal will be included in the result set as a + /// wall if the @p filter results in the neighbor polygon becoomming impassable. + /// + /// The @p segmentVerts and @p segmentRefs buffers should normally be sized for the + /// maximum segments per polygon of the source navigation mesh. + /// + dtStatus getPolyWallSegments(dtPolyRef polyRef, dtQueryFilter filter, float[] segmentVerts, dtPolyRef[] segmentRefs, ref int segmentCount, int maxSegments) + { + Debug.Assert(m_nav != null); + + segmentCount = 0; + + dtMeshTile tile = null; + dtPoly poly = null; + if (dtStatusFailed(m_nav.getTileAndPolyByRef(polyRef, ref tile, ref poly))) + return DT_FAILURE | DT_INVALID_PARAM; + + int n = 0; + const int MAX_INTERVAL = 16; + dtSegInterval[] ints = new dtSegInterval[MAX_INTERVAL]; + dtcsArrayItemsCreate(ints); + int nints; + + bool storePortals = segmentRefs != null; + + dtStatus status = DT_SUCCESS; + + for (int i = 0, j = (int)poly.vertCount - 1; i < (int)poly.vertCount; j = i++) + { + // Skip non-solid edges. + nints = 0; + if ((poly.neis[j] & DT_EXT_LINK) != 0) + { + // Tile border. + for (uint k = poly.firstLink; k != DT_NULL_LINK; k = tile.links[k].next) + { + dtLink link = tile.links[k]; + if (link.edge == j) + { + if (link.polyRef != 0) + { + dtMeshTile neiTile = null; + dtPoly neiPoly = null; + m_nav.getTileAndPolyByRefUnsafe(link.polyRef, ref neiTile, ref neiPoly); + if (filter.passFilter(link.polyRef, neiTile, neiPoly)) + { + insertInterval(ints, ref nints, MAX_INTERVAL, link.bmin, link.bmax, link.polyRef); + } + } + } + } + } + else + { + // Internal edge + dtPolyRef neiRef = 0; + if (poly.neis[j] != 0) + { + uint idx = (uint)(poly.neis[j] - 1); + neiRef = m_nav.getPolyRefBase(tile) | idx; + if (!filter.passFilter(neiRef, tile, tile.polys[idx])) + neiRef = 0; + } + + // If the edge leads to another polygon and portals are not stored, skip. + if (neiRef != 0 && !storePortals) + continue; + + if (n < maxSegments) + { + //const float* vj = &tile.verts[poly.verts[j]*3]; + //const float* vi = &tile.verts[poly.verts[i]*3]; + //float* seg = &segmentVerts[n*6]; + int vjStart = poly.verts[j] * 3; + int viStart = poly.verts[i] * 3; + int segStart = n * 6; + dtVcopy(segmentVerts, segStart, tile.verts, vjStart); + dtVcopy(segmentVerts, segStart + 3, tile.verts, viStart); + if (segmentRefs != null) + segmentRefs[n] = neiRef; + n++; + } + else + { + status |= DT_BUFFER_TOO_SMALL; + } + + continue; + } + + // Add sentinels + insertInterval(ints, ref nints, MAX_INTERVAL, -1, 0, 0); + insertInterval(ints, ref nints, MAX_INTERVAL, 255, 256, 0); + + // Store segments. + //const float* vj = &tile.verts[poly.verts[j]*3]; + //const float* vi = &tile.verts[poly.verts[i]*3]; + int vjStart2 = poly.verts[j] * 3; + int viStart2 = poly.verts[i] * 3; + for (int k = 1; k < nints; ++k) + { + // Portal segment. + if (storePortals && ints[k].polyRef != 0) + { + float tmin = ints[k].tmin / 255.0f; + float tmax = ints[k].tmax / 255.0f; + if (n < maxSegments) + { + //float* seg = &segmentVerts[n*6]; + int segStart = n * 6; + dtVlerp(segmentVerts, segStart, tile.verts, vjStart2, tile.verts, viStart2, tmin); + dtVlerp(segmentVerts, segStart + 3, tile.verts, vjStart2, tile.verts, viStart2, tmax); + if (segmentRefs != null) + segmentRefs[n] = ints[k].polyRef; + n++; + } + else + { + status |= DT_BUFFER_TOO_SMALL; + } + } + + // Wall segment. + int imin = ints[k - 1].tmax; + int imax = ints[k].tmin; + if (imin != imax) + { + float tmin = imin / 255.0f; + float tmax = imax / 255.0f; + if (n < maxSegments) + { + //float* seg = &segmentVerts[n*6]; + int segStart = n * 6; + dtVlerp(segmentVerts, segStart, tile.verts, vjStart2, tile.verts, viStart2, tmin); + dtVlerp(segmentVerts, segStart + 3, tile.verts, vjStart2, tile.verts, viStart2, tmax); + if (segmentRefs != null) + segmentRefs[n] = 0; + n++; + } + else + { + status |= DT_BUFFER_TOO_SMALL; + } + } + } + } + + segmentCount = n; + + return status; + } + + /// Finds the distance from the specified position to the nearest polygon wall. + /// @param[in] startRef The reference id of the polygon containing @p centerPos. + /// @param[in] centerPos The center of the search circle. [(x, y, z)] + /// @param[in] maxRadius The radius of the search circle. + /// @param[in] filter The polygon filter to apply to the query. + /// @param[out] hitDist The distance to the nearest wall from @p centerPos. + /// @param[out] hitPos The nearest position on the wall that was hit. [(x, y, z)] + /// @param[out] hitNormal The normalized ray formed from the wall point to the + /// source point. [(x, y, z)] + /// @returns The status flags for the query. + /// @par + /// + /// @p hitPos is not adjusted using the height detail data. + /// + /// @p hitDist will equal the search radius if there is no wall within the + /// radius. In this case the values of @p hitPos and @p hitNormal are + /// undefined. + /// + /// The normal will become unpredicable if @p hitDist is a very small number. + /// + dtStatus findDistanceToWall(dtPolyRef startRef, float[] centerPos, float maxRadius, dtQueryFilter filter, ref float hitDist, float[] hitPos, float[] hitNormal) + { + Debug.Assert(m_nav != null); + Debug.Assert(m_nodePool != null); + Debug.Assert(m_openList != null); + + // Validate input + if (startRef == 0 || !m_nav.isValidPolyRef(startRef)) + return DT_FAILURE | DT_INVALID_PARAM; + + m_nodePool.clear(); + m_openList.clear(); + + dtNode startNode = m_nodePool.getNode(startRef); + dtVcopy(startNode.pos, centerPos); + startNode.pidx = 0; + startNode.cost = 0; + startNode.total = 0; + startNode.id = startRef; + startNode.flags = (byte)dtNodeFlags.DT_NODE_OPEN; + m_openList.push(startNode); + + float radiusSqr = dtSqr(maxRadius); + + dtStatus status = DT_SUCCESS; + + while (!m_openList.empty()) + { + dtNode bestNode = m_openList.pop(); + //bestNode.flags &= ~DT_NODE_OPEN; + //bestNode.flags |= DT_NODE_CLOSED; + bestNode.dtcsClearFlag(dtNodeFlags.DT_NODE_OPEN); + bestNode.dtcsSetFlag(dtNodeFlags.DT_NODE_CLOSED); + + // Get poly and tile. + // The API input has been cheked already, skip checking internal data. + dtPolyRef bestRef = bestNode.id; + dtMeshTile bestTile = null; + dtPoly bestPoly = null; + m_nav.getTileAndPolyByRefUnsafe(bestRef, ref bestTile, ref bestPoly); + + // Get parent poly and tile. + dtPolyRef parentRef = 0; + dtMeshTile parentTile = null; + dtPoly parentPoly = null; + if (bestNode.pidx != 0) + parentRef = m_nodePool.getNodeAtIdx(bestNode.pidx).id; + if (parentRef != 0) + m_nav.getTileAndPolyByRefUnsafe(parentRef, ref parentTile, ref parentPoly); + + // Hit test walls. + for (int i = 0, j = (int)bestPoly.vertCount - 1; i < (int)bestPoly.vertCount; j = i++) + { + // Skip non-solid edges. + if ((bestPoly.neis[j] & DT_EXT_LINK) != 0) + { + // Tile border. + bool solid = true; + for (uint k = bestPoly.firstLink; k != DT_NULL_LINK; k = bestTile.links[k].next) + { + dtLink link = bestTile.links[k]; + if (link.edge == j) + { + if (link.polyRef != 0) + { + dtMeshTile neiTile = null; + dtPoly neiPoly = null; + m_nav.getTileAndPolyByRefUnsafe(link.polyRef, ref neiTile, ref neiPoly); + if (filter.passFilter(link.polyRef, neiTile, neiPoly)) + solid = false; + } + break; + } + } + if (!solid) continue; + } + else if (bestPoly.neis[j] != 0) + { + // Internal edge + uint idx = (uint)(bestPoly.neis[j] - 1); + dtPolyRef polyRef = m_nav.getPolyRefBase(bestTile) | idx; + if (filter.passFilter(polyRef, bestTile, bestTile.polys[idx])) + continue; + } + + // Calc distance to the edge. + //const float* vj = &bestTile.verts[bestPoly.verts[j]*3]; + //const float* vi = &bestTile.verts[bestPoly.verts[i]*3]; + int vjStart = bestPoly.verts[j] * 3; + int viStart = bestPoly.verts[i] * 3; + float tseg = .0f; + float distSqr = dtDistancePtSegSqr2D(centerPos, 0, bestTile.verts, vjStart, bestTile.verts, viStart, ref tseg); + + // Edge is too far, skip. + if (distSqr > radiusSqr) + continue; + + // Hit wall, update radius. + radiusSqr = distSqr; + // Calculate hit pos. + hitPos[0] = bestTile.verts[vjStart + 0] + (bestTile.verts[viStart + 0] - bestTile.verts[vjStart + 0]) * tseg; + hitPos[1] = bestTile.verts[vjStart + 1] + (bestTile.verts[viStart + 1] - bestTile.verts[vjStart + 1]) * tseg; + hitPos[2] = bestTile.verts[vjStart + 2] + (bestTile.verts[viStart + 2] - bestTile.verts[vjStart + 2]) * tseg; + } + + for (uint i = bestPoly.firstLink; i != DT_NULL_LINK; i = bestTile.links[i].next) + { + dtLink link = bestTile.links[i]; + dtPolyRef neighbourRef = link.polyRef; + // Skip invalid neighbours and do not follow back to parent. + if (neighbourRef != 0 || neighbourRef == parentRef) + continue; + + // Expand to neighbour. + dtMeshTile neighbourTile = null; + dtPoly neighbourPoly = null; + m_nav.getTileAndPolyByRefUnsafe(neighbourRef, ref neighbourTile, ref neighbourPoly); + + // Skip off-mesh connections. + if (neighbourPoly.getType() == (byte)dtPolyTypes.DT_POLYTYPE_OFFMESH_CONNECTION) + continue; + + // Calc distance to the edge. + //const float* va = &bestTile.verts[bestPoly.verts[link.edge]*3]; + //const float* vb = &bestTile.verts[bestPoly.verts[(link.edge+1) % bestPoly.vertCount]*3]; + int vaStart = bestPoly.verts[link.edge] * 3; + int vbStart = bestPoly.verts[(link.edge + 1) % bestPoly.vertCount] * 3; + + float tseg = .0f; + float distSqr = dtDistancePtSegSqr2D(centerPos, 0, bestTile.verts, vaStart, bestTile.verts, vbStart, ref tseg); + + // If the circle is not touching the next polygon, skip it. + if (distSqr > radiusSqr) + continue; + + if (!filter.passFilter(neighbourRef, neighbourTile, neighbourPoly)) + continue; + + dtNode neighbourNode = m_nodePool.getNode(neighbourRef); + if (neighbourNode == null) + { + status |= DT_OUT_OF_NODES; + continue; + } + + if (neighbourNode.dtcsTestFlag(dtNodeFlags.DT_NODE_CLOSED))// .flags & DT_NODE_CLOSED) + continue; + + // Cost + if (neighbourNode.flags == 0) + { + getEdgeMidPoint(bestRef, bestPoly, bestTile, + neighbourRef, neighbourPoly, neighbourTile, neighbourNode.pos); + } + + float total = bestNode.total + dtVdist(bestNode.pos, neighbourNode.pos); + + // The node is already in open list and the new result is worse, skip. + if (neighbourNode.dtcsTestFlag(dtNodeFlags.DT_NODE_OPEN) && total >= neighbourNode.total) + continue; + + neighbourNode.id = neighbourRef; + //neighbourNode.flags = (neighbourNode.flags & ~DT_NODE_CLOSED); + neighbourNode.dtcsClearFlag(dtNodeFlags.DT_NODE_CLOSED); + neighbourNode.pidx = m_nodePool.getNodeIdx(bestNode); + neighbourNode.total = total; + + if (neighbourNode.dtcsTestFlag(dtNodeFlags.DT_NODE_OPEN))// .flags & DT_NODE_OPEN) + { + m_openList.modify(neighbourNode); + } + else + { + //neighbourNode.flags |= DT_NODE_OPEN; + neighbourNode.dtcsSetFlag(dtNodeFlags.DT_NODE_OPEN); + m_openList.push(neighbourNode); + } + } + } + + // Calc hit normal. + dtVsub(hitNormal, centerPos, hitPos); + dtVnormalize(hitNormal); + + hitDist = (float)Math.Sqrt(radiusSqr); + + return status; + } + + /// @} + /// @name Miscellaneous Functions + /// @{ + + /// Returns true if the polygon reference is valid and passes the filter restrictions. + /// @param[in] ref The polygon reference to check. + /// @param[in] filter The filter to apply. + bool isValidPolyRef(dtPolyRef polyRef, dtQueryFilter filter) + { + dtMeshTile tile = null; + dtPoly poly = null; + dtStatus status = m_nav.getTileAndPolyByRef(polyRef, ref tile, ref poly); + // If cannot get polygon, assume it does not exists and boundary is invalid. + if (dtStatusFailed(status)) + return false; + // If cannot pass filter, assume flags has changed and boundary is invalid. + if (!filter.passFilter(polyRef, tile, poly)) + return false; + return true; + } + + /// Returns true if the polygon reference is in the closed list. + /// @param[in] ref The reference id of the polygon to check. + /// @returns True if the polygon is in closed list. + /// @par + /// + /// The closed list is the list of polygons that were fully evaluated during + /// the last navigation graph search. (A* or Dijkstra) + /// + bool isInClosedList(dtPolyRef polyRef) + { + if (m_nodePool == null) return false; + dtNode node = m_nodePool.findNode(polyRef); + return node != null && node.dtcsTestFlag(dtNodeFlags.DT_NODE_CLOSED);// .flags & DT_NODE_CLOSED; + } + + /// Gets the navigation mesh the query object is using. + /// @return The navigation mesh the query object is using. + public dtNavMesh getAttachedNavMesh() + { + return m_nav; + } + } +} + + + + + + + + + + + + + + + + + + + diff --git a/Framework/RecastDetour/Detour/DetourNode.cs b/Framework/RecastDetour/Detour/DetourNode.cs new file mode 100644 index 000000000..bbc602bda --- /dev/null +++ b/Framework/RecastDetour/Detour/DetourNode.cs @@ -0,0 +1,301 @@ +using System.Diagnostics; +using dtNodeIndex = System.UInt16; +using dtPolyRef = System.UInt64; + +public partial class Detour +{ + // From Thomas Wang, https://gist.github.com/badboy/6267743 + public static uint dtHashRef(dtPolyRef a) + { + a = (~a) + (a << 18); // a = (a << 18) - a - 1; + a = a ^ (a >> 31); + a = a * 21; // a = (a + (a << 2)) + (a << 4); + a = a ^ (a >> 11); + a = a + (a << 6); + a = a ^ (a >> 22); + return (uint)a; + } +} + +public partial class Detour +{ + + public enum dtNodeFlags + { + DT_NODE_OPEN = 0x01, + DT_NODE_CLOSED = 0x02, + }; + + public const dtNodeIndex DT_NULL_IDX = dtNodeIndex.MaxValue; //(dtNodeIndex)~0; + + public class dtNode + { + public float[] pos = new float[3]; //< Position of the node. + public float cost; //< Cost from previous node to current node. + public float total; //< Cost up to the node. + public uint pidx;// : 30; //< Index to parent node. + public byte flags;// : 2; //< Node flags 0/open/closed. + public dtPolyRef id; //< Polygon ref the node corresponds to. + /// + public static int getSizeOf() + { + //C# can't guess the sizeof of the float array, let's pretend + return sizeof(float) * (3 + 1 + 1) + + sizeof(uint) + + sizeof(byte) + + sizeof(dtPolyRef); + } + public void dtcsClearFlag(dtNodeFlags flag) + { + unchecked + { + flags &= (byte)(~flag); + } + } + public void dtcsSetFlag(dtNodeFlags flag) + { + flags |= (byte)flag; + } + public bool dtcsTestFlag(dtNodeFlags flag) + { + return (flags & (byte)flag) != 0; + } + }; + + + public class dtNodePool + { + private dtNode[] m_nodes; + private dtNodeIndex[] m_first; + private dtNodeIndex[] m_next; + private int m_maxNodes; + private int m_hashSize; + private int m_nodeCount; + + ////////////////////////////////////////////////////////////////////////////////////////// + public dtNodePool(int maxNodes, int hashSize) + + { + m_maxNodes = maxNodes; + m_hashSize = hashSize; + + Debug.Assert(dtNextPow2((uint)m_hashSize) == (uint)m_hashSize); + Debug.Assert(m_maxNodes > 0); + + m_nodes = new dtNode[m_maxNodes]; + dtcsArrayItemsCreate(m_nodes); + m_next = new dtNodeIndex[m_maxNodes]; + m_first = new dtNodeIndex[hashSize]; + + Debug.Assert(m_nodes != null); + Debug.Assert(m_next != null); + Debug.Assert(m_first != null); + + for (int i = 0; i < hashSize; ++i) + { + m_first[i] = DT_NULL_IDX; + } + for (int i = 0; i < m_maxNodes; ++i) + { + m_next[i] = DT_NULL_IDX; + } + } + + public void clear() + { + for (int i = 0; i < m_hashSize; ++i) + { + m_first[i] = DT_NULL_IDX; + } + m_nodeCount = 0; + } + + public uint getNodeIdx(dtNode node) + { + if (node == null) + return 0; + + return (uint)(System.Array.IndexOf(m_nodes, node)) + 1; + } + + public dtNode getNodeAtIdx(uint idx) + { + if (idx == 0) + return null; + return m_nodes[idx - 1]; + } + + public int getMemUsed() + { + return + sizeof(int) * 3 + + dtNode.getSizeOf() * m_maxNodes + + sizeof(dtNodeIndex) * m_maxNodes + + sizeof(dtNodeIndex) * m_hashSize; + } + + public int getMaxNodes() + { + return m_maxNodes; + } + + public int getHashSize() + { + return m_hashSize; + } + public dtNodeIndex getFirst(int bucket) + { + return m_first[bucket]; + } + public dtNodeIndex getNext(int i) + { + return m_next[i]; + } + + public dtNode findNode(dtPolyRef id) + { + uint bucket = (uint)(dtHashRef(id) & (m_hashSize - 1)); + dtNodeIndex i = m_first[bucket]; + while (i != DT_NULL_IDX) + { + if (m_nodes[i].id == id) + return m_nodes[i]; + i = m_next[i]; + } + return null; + } + + public dtNode getNode(dtPolyRef id) + { + uint bucket = (uint)(dtHashRef(id) & (m_hashSize - 1)); + dtNodeIndex i = m_first[bucket]; + dtNode node = null; + while (i != DT_NULL_IDX) + { + if (m_nodes[i].id == id) + return m_nodes[i]; + i = m_next[i]; + } + + if (m_nodeCount >= m_maxNodes) + return null; + + i = (dtNodeIndex)m_nodeCount; + m_nodeCount++; + + // Init node + node = m_nodes[i]; + node.pidx = 0; + node.cost = 0; + node.total = 0; + node.id = id; + node.flags = 0; + + m_next[i] = m_first[bucket]; + m_first[bucket] = i; + + return node; + } + } + + + ////////////////////////////////////////////////////////////////////////////////////////// + public class dtNodeQueue + { + private dtNode[] m_heap; + private int m_capacity; + private int m_size; + + public dtNodeQueue(int n) + { + m_capacity = n; + Debug.Assert(m_capacity > 0); + + m_heap = new dtNode[m_capacity + 1];//(dtNode**)dtAlloc(sizeof(dtNode*)*(m_capacity+1), DT_ALLOC_PERM); + Debug.Assert(m_heap != null); + } + + public void clear() + { + m_size = 0; + } + + public dtNode top() + { + return m_heap[0]; + } + + public dtNode pop() + { + dtNode result = m_heap[0]; + m_size--; + trickleDown(0, m_heap[m_size]); + return result; + } + + public void push(dtNode node) + { + m_size++; + bubbleUp(m_size - 1, node); + } + + public void modify(dtNode node) + { + for (int i = 0; i < m_size; ++i) + { + if (m_heap[i] == node) + { + bubbleUp(i, node); + return; + } + } + } + + public bool empty() + { + return m_size == 0; + } + + public int getMemUsed() + { + return sizeof(int) * 2 + + dtNode.getSizeOf() * (m_capacity + 1); + } + + public int getCapacity() + { + return m_capacity; + } + + + public void bubbleUp(int i, dtNode node) + { + int parent = (i - 1) / 2; + // note: (index > 0) means there is a parent + while ((i > 0) && (m_heap[parent].total > node.total)) + { + m_heap[i] = m_heap[parent]; + i = parent; + parent = (i - 1) / 2; + } + m_heap[i] = node; + } + + public void trickleDown(int i, dtNode node) + { + int child = (i * 2) + 1; + while (child < m_size) + { + if (((child + 1) < m_size) && + (m_heap[child].total > m_heap[child + 1].total)) + { + child++; + } + m_heap[i] = m_heap[child]; + i = child; + child = (i * 2) + 1; + } + bubbleUp(i, node); + } + } +} \ No newline at end of file diff --git a/Framework/RecastDetour/Detour/DetourStatus.cs b/Framework/RecastDetour/Detour/DetourStatus.cs new file mode 100644 index 000000000..4999082be --- /dev/null +++ b/Framework/RecastDetour/Detour/DetourStatus.cs @@ -0,0 +1,44 @@ +using dtStatus = System.UInt32; + +public static partial class Detour +{ + // High level status. + public const uint DT_FAILURE = 1u << 31; // Operation failed. + public const uint DT_SUCCESS = 1u << 30; // Operation succeed. + public const uint DT_IN_PROGRESS = 1u << 29; // Operation still in progress. + + // Detail information for status. + public const uint DT_STATUS_DETAIL_MASK = 0x0ffffff; + public const uint DT_WRONG_MAGIC = 1 << 0; // Input data is not recognized. + public const uint DT_WRONG_VERSION = 1 << 1; // Input data is in wrong version. + public const uint DT_OUT_OF_MEMORY = 1 << 2; // Operation ran out of memory. + public const uint DT_INVALID_PARAM = 1 << 3; // An input parameter was invalid. + public const uint DT_BUFFER_TOO_SMALL = 1 << 4; // Result buffer for the query was too small to store all results. + public const uint DT_OUT_OF_NODES = 1 << 5; // Query ran out of nodes during search. + public const uint DT_PARTIAL_RESULT = 1 << 6; // Query did not reach the end location, returning best guess. + + + // Returns true of status is success. + public static bool dtStatusSucceed(dtStatus status) + { + return (status & DT_SUCCESS) != 0; + } + + // Returns true of status is failure. + public static bool dtStatusFailed(dtStatus status) + { + return (status & DT_FAILURE) != 0; + } + + // Returns true of status is in progress. + public static bool dtStatusInProgress(dtStatus status) + { + return (status & DT_IN_PROGRESS) != 0; + } + + // Returns true if specific detail is set. + public static bool dtStatusDetail(dtStatus status, uint detail) + { + return (status & detail) != 0; + } +} diff --git a/Framework/RecastDetour/Recast/Recast.cs b/Framework/RecastDetour/Recast/Recast.cs new file mode 100644 index 000000000..b4fcdcc49 --- /dev/null +++ b/Framework/RecastDetour/Recast/Recast.cs @@ -0,0 +1,1692 @@ +using System; +using System.Diagnostics; +using System.IO; +using System.Text; + +public static partial class Recast { + + /// The value of PI used by Recast. + public const float RC_PI = 3.14159265f; + + /// Defines the number of bits allocated to rcSpan::smin and rcSpan::smax. + public const int RC_SPAN_HEIGHT_BITS = 13; + /// Defines the maximum value for rcSpan::smin and rcSpan::smax. + public const int RC_SPAN_MAX_HEIGHT = (1 << RC_SPAN_HEIGHT_BITS) - 1; + + /// Recast log categories. + /// @see rcContext + public enum rcLogCategory { + RC_LOG_PROGRESS = 1, //< A progress log entry. + RC_LOG_WARNING, //< A warning log entry. + RC_LOG_ERROR, //< An error log entry. + }; + + /// Recast performance timer categories. + /// @see rcContext + public enum rcTimerLabel { + /// The user defined total time of the build. + RC_TIMER_TOTAL, + /// A user defined build time. + RC_TIMER_TEMP, + /// The time to rasterize the triangles. (See: #rcRasterizeTriangle) + RC_TIMER_RASTERIZE_TRIANGLES, + /// The time to build the compact heightfield. (See: #rcBuildCompactHeightfield) + RC_TIMER_BUILD_COMPACTHEIGHTFIELD, + /// The total time to build the contours. (See: #rcBuildContours) + RC_TIMER_BUILD_CONTOURS, + /// The time to trace the boundaries of the contours. (See: #rcBuildContours) + RC_TIMER_BUILD_CONTOURS_TRACE, + /// The time to simplify the contours. (See: #rcBuildContours) + RC_TIMER_BUILD_CONTOURS_SIMPLIFY, + /// The time to filter ledge spans. (See: #rcFilterLedgeSpans) + RC_TIMER_FILTER_BORDER, + /// The time to filter low height spans. (See: #rcFilterWalkableLowHeightSpans) + RC_TIMER_FILTER_WALKABLE, + /// The time to apply the median filter. (See: #rcMedianFilterWalkableArea) + RC_TIMER_MEDIAN_AREA, + /// The time to filter low obstacles. (See: #rcFilterLowHangingWalkableObstacles) + RC_TIMER_FILTER_LOW_OBSTACLES, + /// The time to build the polygon mesh. (See: #rcBuildPolyMesh) + RC_TIMER_BUILD_POLYMESH, + /// The time to merge polygon meshes. (See: #rcMergePolyMeshes) + RC_TIMER_MERGE_POLYMESH, + /// The time to erode the walkable area. (See: #rcErodeWalkableArea) + RC_TIMER_ERODE_AREA, + /// The time to mark a box area. (See: #rcMarkBoxArea) + RC_TIMER_MARK_BOX_AREA, + /// The time to mark a cylinder area. (See: #rcMarkCylinderArea) + RC_TIMER_MARK_CYLINDER_AREA, + /// The time to mark a convex polygon area. (See: #rcMarkConvexPolyArea) + RC_TIMER_MARK_CONVEXPOLY_AREA, + /// The total time to build the distance field. (See: #rcBuildDistanceField) + RC_TIMER_BUILD_DISTANCEFIELD, + /// The time to build the distances of the distance field. (See: #rcBuildDistanceField) + RC_TIMER_BUILD_DISTANCEFIELD_DIST, + /// The time to blur the distance field. (See: #rcBuildDistanceField) + RC_TIMER_BUILD_DISTANCEFIELD_BLUR, + /// The total time to build the regions. (See: #rcBuildRegions, #rcBuildRegionsMonotone) + RC_TIMER_BUILD_REGIONS, + /// The total time to apply the watershed algorithm. (See: #rcBuildRegions) + RC_TIMER_BUILD_REGIONS_WATERSHED, + /// The time to expand regions while applying the watershed algorithm. (See: #rcBuildRegions) + RC_TIMER_BUILD_REGIONS_EXPAND, + /// The time to flood regions while applying the watershed algorithm. (See: #rcBuildRegions) + RC_TIMER_BUILD_REGIONS_FLOOD, + /// The time to filter out small regions. (See: #rcBuildRegions, #rcBuildRegionsMonotone) + RC_TIMER_BUILD_REGIONS_FILTER, + /// The time to build heightfield layers. (See: #rcBuildHeightfieldLayers) + RC_TIMER_BUILD_LAYERS, + /// The time to build the polygon mesh detail. (See: #rcBuildPolyMeshDetail) + RC_TIMER_BUILD_POLYMESHDETAIL, + /// The time to merge polygon mesh details. (See: #rcMergePolyMeshDetails) + RC_TIMER_MERGE_POLYMESHDETAIL, + /// The maximum number of timers. (Used for iterating timers.) + RC_MAX_TIMERS + }; + + + public static void rcCalcBounds(float[] verts, int nv, float[] bmin, float[] bmax) { + // Calculate bounding box. + rcVcopy(bmin, verts); + rcVcopy(bmax, verts); + for (int i = 1; i < nv; ++i) { + int vStart = i * 3; + rcVmin(bmin, 0, verts, vStart); + rcVmax(bmax, 0, verts, vStart); + } + } + + public static void rcCalcGridSize(float[] bmin, float[] bmax, float cs, out int w, out int h) { + w = (int)((bmax[0] - bmin[0]) / cs + 0.5f); + h = (int)((bmax[2] - bmin[2]) / cs + 0.5f); + } + + + /// @par + /// + /// See the #rcConfig documentation for more information on the configuration parameters. + /// + /// @see rcAllocHeightfield, rcHeightfield + public static bool rcCreateHeightfield(rcContext ctx, rcHeightfield hf, int width, int height, + float[] bmin, float[] bmax, + float cs, float ch) { + rcIgnoreUnused(ctx); + + hf.width = width; + hf.height = height; + rcVcopy(hf.bmin, bmin); + rcVcopy(hf.bmax, bmax); + hf.cs = cs; + hf.ch = ch; + hf.spans = new rcSpan[hf.width * hf.height];//(rcSpan**)rcAlloc(sizeof(rcSpan*)*hf.width*hf.height, RC_ALLOC_PERM); + if (hf.spans == null) + return false; + //memset(hf.spans, 0, sizeof(rcSpan*)*hf.width*hf.height); + return true; + } + + public static void calcTriNormal(float[] v0, float[] v1, float[] v2, float[] norm) { + float[] e0 = new float[3]; + float[] e1 = new float[3]; + rcVsub(e0, v1, v0); + rcVsub(e1, v2, v0); + rcVcross(norm, e0, e1); + rcVnormalize(norm); + } + + public static void calcTriNormal(float[] v0, int v0Start, float[] v1, int v1Start, float[] v2, int v2Start, float[] norm) { + float[] e0 = new float[3]; + float[] e1 = new float[3]; + rcVsub(e0, 0, v1, v1Start, v0, v0Start); + rcVsub(e1, 0, v2, v2Start, v0, v0Start); + rcVcross(norm, 0, e0, 0, e1, 0); + rcVnormalize(norm); + } + + /// @par + /// + /// Only sets the aread id's for the walkable triangles. Does not alter the + /// area id's for unwalkable triangles. + /// + /// See the #rcConfig documentation for more information on the configuration parameters. + /// + /// @see rcHeightfield, rcClearUnwalkableTriangles, rcRasterizeTriangles + public static void rcMarkWalkableTriangles(rcContext ctx, float walkableSlopeAngle, + float[] verts, int nv, + int[] tris, int nt, + byte[] areas) { + rcIgnoreUnused(ctx); + + float walkableThr = (float)Math.Cos(walkableSlopeAngle / 180.0f * RC_PI); + + float[] norm = new float[3]; + + for (int i = 0; i < nt; ++i) { + int triStart = i * 3; + calcTriNormal(verts, tris[triStart + 0] * 3, verts, tris[triStart + 1] * 3, verts, tris[triStart + 2] * 3, norm); + // Check if the face is walkable. + if (norm[1] > walkableThr) + areas[i] = RC_WALKABLE_AREA; + } + } + + /// @par + /// + /// Only sets the aread id's for the unwalkable triangles. Does not alter the + /// area id's for walkable triangles. + /// + /// See the #rcConfig documentation for more information on the configuration parameters. + /// + /// @see rcHeightfield, rcClearUnwalkableTriangles, rcRasterizeTriangles + public static void rcClearUnwalkableTriangles(rcContext ctx, float walkableSlopeAngle, + float[] verts, int nv, + int[] tris, int nt, + byte[] areas) { + rcIgnoreUnused(ctx); + + float walkableThr = (float)Math.Cos(walkableSlopeAngle / 180.0f * RC_PI); + + float[] norm = new float[3]; + + for (int i = 0; i < nt; ++i) { + int triStart = i * 3; + calcTriNormal(verts, tris[triStart + 0] * 3, verts, tris[triStart + 1] * 3, verts, tris[triStart + 2] * 3, norm); + // Check if the face is walkable. + if (norm[1] <= walkableThr) + areas[i] = RC_NULL_AREA; + } + } + + public static int rcGetHeightFieldSpanCount(rcContext ctx, rcHeightfield hf) { + rcIgnoreUnused(ctx); + + int w = hf.width; + int h = hf.height; + int spanCount = 0; + for (int y = 0; y < h; ++y) { + for (int x = 0; x < w; ++x) { + for (rcSpan s = hf.spans[x + y * w]; s != null; s = s.next) { + if (s.area != RC_NULL_AREA) + spanCount++; + } + } + } + return spanCount; + } + + public static void rccsArrayItemsCreate(T[] array) where T : class, new() { + for (int i = 0; i < array.Length; ++i) { + array[i] = new T(); + } + } + + /// @par + /// + /// This is just the beginning of the process of fully building a compact heightfield. + /// Various filters may be applied applied, then the distance field and regions built. + /// E.g: #rcBuildDistanceField and #rcBuildRegions + /// + /// See the #rcConfig documentation for more information on the configuration parameters. + /// + /// @see rcAllocCompactHeightfield, rcHeightfield, rcCompactHeightfield, rcConfig + public static bool rcBuildCompactHeightfield(rcContext ctx, int walkableHeight, int walkableClimb, + rcHeightfield hf, rcCompactHeightfield chf) { + Debug.Assert(ctx != null, "rcBuildCompactHeightfield Assert(ctx != null)"); + + ctx.startTimer(rcTimerLabel.RC_TIMER_BUILD_COMPACTHEIGHTFIELD); + + int w = hf.width; + int h = hf.height; + int spanCount = rcGetHeightFieldSpanCount(ctx, hf); + + // Fill in header. + chf.width = w; + chf.height = h; + chf.spanCount = spanCount; + chf.walkableHeight = walkableHeight; + chf.walkableClimb = walkableClimb; + chf.maxRegions = 0; + rcVcopy(chf.bmin, hf.bmin); + rcVcopy(chf.bmax, hf.bmax); + chf.bmax[1] += walkableHeight * hf.ch; + chf.cs = hf.cs; + chf.ch = hf.ch; + chf.cells = new rcCompactCell[w * h]; + //chf.cells = (rcCompactCell*)rcAlloc(sizeof(rcCompactCell)*w*h, RC_ALLOC_PERM); + + if (chf.cells == null) { + ctx.log(rcLogCategory.RC_LOG_ERROR, "rcBuildCompactHeightfield: Out of memory 'chf.cells' (" + (w * h) + ")"); + return false; + } + chf.spans = new rcCompactSpan[spanCount];// (rcCompactSpan*)rcAlloc(sizeof(rcCompactSpan)*spanCount, RC_ALLOC_PERM); + if (chf.spans == null) { + ctx.log(rcLogCategory.RC_LOG_ERROR, "rcBuildCompactHeightfield: Out of memory 'chf.spans' (" + spanCount + ")"); + return false; + } + chf.areas = new byte[spanCount]; //(byte*)rcAlloc(sizeof(byte)*spanCount, RC_ALLOC_PERM); + if (chf.areas == null) { + ctx.log(rcLogCategory.RC_LOG_ERROR, "rcBuildCompactHeightfield: Out of memory 'chf.areas' (" + spanCount + ")"); ; + return false; + } + + int MAX_HEIGHT = 0xffff; + + // Fill in cells and spans. + int idx = 0; + for (int y = 0; y < h; ++y) { + for (int x = 0; x < w; ++x) { + rcSpan s = hf.spans[x + y * w]; + // If there are no spans at this cell, just leave the data to index=0, count=0. + if (s == null) { + continue; + } + rcCompactCell c;// = chf.cells[x + y * w]; + c.index = (uint)idx; + c.count = 0; + + while (s != null) { + if (s.area != RC_NULL_AREA) { + int bot = (int)s.smax; + int top = s.next != null ? (int)s.next.smin : MAX_HEIGHT; + chf.spans[idx].y = (ushort)rcClamp(bot, 0, 0xffff); + chf.spans[idx].h = (byte)rcClamp(top - bot, 0, 0xff); + chf.areas[idx] = s.area; + idx++; + c.count++; + } + s = s.next; + } + chf.cells[x + y * w] = c; + } + } + + // Find neighbour connections. + int MAX_LAYERS = RC_NOT_CONNECTED - 1; + int tooHighNeighbour = 0; + for (int y = 0; y < h; ++y) { + for (int x = 0; x < w; ++x) { + rcCompactCell c = chf.cells[x + y * w]; + for (int i = (int)c.index, ni = (int)(c.index + c.count); i < ni; ++i) { + rcCompactSpan s = chf.spans[i]; + + for (int dir = 0; dir < 4; ++dir) { + rcSetCon(ref s, dir, RC_NOT_CONNECTED); + int nx = x + rcGetDirOffsetX(dir); + int ny = y + rcGetDirOffsetY(dir); + // First check that the neighbour cell is in bounds. + if (nx < 0 || ny < 0 || nx >= w || ny >= h) + continue; + + // Iterate over all neighbour spans and check if any of the is + // accessible from current cell. + rcCompactCell nc = chf.cells[nx + ny * w]; + for (int k = (int)nc.index, nk = (int)(nc.index + nc.count); k < nk; ++k) { + rcCompactSpan ns = chf.spans[k]; + int bot = Math.Max(s.y, ns.y); + int top = Math.Min(s.y + s.h, ns.y + ns.h); + + // Check that the gap between the spans is walkable, + // and that the climb height between the gaps is not too high. + if ((top - bot) >= walkableHeight && Math.Abs((int)ns.y - (int)s.y) <= walkableClimb) { + // Mark direction as walkable. + int lidx = k - (int)nc.index; + if (lidx < 0 || lidx > MAX_LAYERS) { + tooHighNeighbour = Math.Max(tooHighNeighbour, lidx); + continue; + } + rcSetCon(ref s, dir, lidx); + break; + } + } + + } + + chf.spans[i] = s; + } + } + } + + if (tooHighNeighbour > MAX_LAYERS) { + ctx.log(rcLogCategory.RC_LOG_ERROR, "rcBuildCompactHeightfield: Heightfield has too many layers " + tooHighNeighbour + " (max: " + MAX_LAYERS + ")"); + } + + ctx.stopTimer(rcTimerLabel.RC_TIMER_BUILD_COMPACTHEIGHTFIELD); + + return true; + } + + + /* + static int getHeightfieldMemoryUsage(const rcHeightfield& hf) + { + int size = 0; + size += sizeof(hf); + size += hf.width * hf.height * sizeof(rcSpan*); + + rcSpanPool* pool = hf.pools; + while (pool) + { + size += (sizeof(rcSpanPool) - sizeof(rcSpan)) + sizeof(rcSpan)*RC_SPANS_PER_POOL; + pool = pool.next; + } + return size; + } + + static int getCompactHeightFieldMemoryusage(const rcCompactHeightfield& chf) + { + int size = 0; + size += sizeof(rcCompactHeightfield); + size += sizeof(rcCompactSpan) * chf.spanCount; + size += sizeof(rcCompactCell) * chf.width * chf.height; + return size; + } + */ + + /// @class rcContext + /// @par + /// + /// This class does not provide logging or timer functionality on its + /// own. Both must be provided by a concrete implementation + /// by overriding the protected member functions. Also, this class does not + /// provide an interface for extracting log messages. (Only adding them.) + /// So concrete implementations must provide one. + /// + /// If no logging or timers are required, just pass an instance of this + /// class through the Recast build process. + /// + + /// @par + /// + /// Example: + /// @code + /// // Where ctx is an instance of rcContext and filepath is a char array. + /// ctx.log(rcLogCategory.RC_LOG_ERROR, "buildTiledNavigation: Could not load '%s'", filepath); + /// @endcode + /// Provides an interface for optional logging and performance tracking of the Recast + /// build process. + /// @ingroup recast + public class rcContext { + + /// True if logging is enabled. + bool m_logEnabled = true; + + /// True if the performance timers are enabled. + bool m_timerEnabled = true; + + /// Contructor. + /// @param[in] state TRUE if the logging and performance timers should be enabled. [Default: true] + public rcContext(bool state = true) { + m_logEnabled = state; + m_timerEnabled = state; + } + + /// Enables or disables logging. + /// @param[in] state TRUE if logging should be enabled. + public void enableLog(bool state) { + m_logEnabled = state; + } + + /// Clears all log entries. + public void resetLog() { + if (m_logEnabled) { + doResetLog(); + } + } + + /// Logs a message. + /// @param[in] category The category of the message. + /// @param[in] message The message. + //public void log(rcLogCategory category, string message); + public void log(rcLogCategory category, string message) { + if (!m_logEnabled) { + return; + } + + doLog(category, message); + } + + /// Enables or disables the performance timers. + /// @param[in] state TRUE if timers should be enabled. + public void enableTimer(bool state) { + m_timerEnabled = state; + } + + /// Clears all peformance timers. (Resets all to unused.) + public void resetTimers() { + if (m_timerEnabled) { + doResetTimers(); + } + } + + /// Starts the specified performance timer. + /// @param label The category of timer. + public void startTimer(rcTimerLabel label) { + if (m_timerEnabled) { + doStartTimer(label); + } + } + + /// Stops the specified performance timer. + /// @param label The category of the timer. + public void stopTimer(rcTimerLabel label) { + if (m_timerEnabled) { + doStopTimer(label); + } + } + + /// Returns the total accumulated time of the specified performance timer. + /// @param label The category of the timer. + /// @return The accumulated time of the timer, or -1 if timers are disabled or the timer has never been started. + public long getAccumulatedTime(rcTimerLabel label) { + return m_timerEnabled ? doGetAccumulatedTime(label) : -1; + } + public double getAccumulatedTimeHiResolution(rcTimerLabel label) { + return m_timerEnabled ? doGetAccumulatedTimeHiResolution(label) : -1.0; + } + + + /// Clears all log entries. + protected virtual void doResetLog() { } + + /// Logs a message. + /// @param[in] category The category of the message. + /// @param[in] msg The formatted message. + protected virtual void doLog(rcLogCategory category, string msg) { //int len unnecessary because c# string + } + + /// Clears all timers. (Resets all to unused.) + protected virtual void doResetTimers() { + } + + /// Starts the specified performance timer. + /// @param[in] label The category of timer. + protected virtual void doStartTimer(rcTimerLabel label) { + } + + /// Stops the specified performance timer. + /// @param[in] label The category of the timer. + protected virtual void doStopTimer(rcTimerLabel label) { + } + + /// Returns the total accumulated time of the specified performance timer. + /// @param[in] label The category of the timer. + /// @return The accumulated time of the timer, or -1 if timers are disabled or the timer has never been started. + protected virtual long doGetAccumulatedTime(rcTimerLabel label) { + return -1; + } + + //C# port: alternate return type to use high precision timer on platforms where it's available + /// Returns the total accumulated time of the specified performance timer. + /// @param[in] label The category of the timer. + /// @return The accumulated time of the timer, or -1 if timers are disabled or the timer has never been started. + protected virtual double doGetAccumulatedTimeHiResolution(rcTimerLabel label) { + return -1.0; + } + + }; + + /// Specifies a configuration to use when performing Recast builds. + /// @ingroup recast + public class rcConfig { + /// The width of the field along the x-axis. [Limit: >= 0] [Units: vx] + public int width; + + /// The height of the field along the z-axis. [Limit: >= 0] [Units: vx] + public int height; + + /// The width/height size of tile's on the xz-plane. [Limit: >= 0] [Units: vx] + public int tileSize; + + /// The size of the non-navigable border around the heightfield. [Limit: >=0] [Units: vx] + public int borderSize; + + /// The xz-plane cell size to use for fields. [Limit: > 0] [Units: wu] + public float cs; + + /// The y-axis cell size to use for fields. [Limit: > 0] [Units: wu] + public float ch; + + /// The minimum bounds of the field's AABB. [(x, y, z)] [Units: wu] + public float[] bmin = new float[3]; + + /// The maximum bounds of the field's AABB. [(x, y, z)] [Units: wu] + public float[] bmax = new float[3]; + + /// The maximum slope that is considered walkable. [Limits: 0 <= value < 90] [Units: Degrees] + public float walkableSlopeAngle; + + /// Minimum floor to 'ceiling' height that will still allow the floor area to + /// be considered walkable. [Limit: >= 3] [Units: vx] + public int walkableHeight; + + /// Maximum ledge height that is considered to still be traversable. [Limit: >=0] [Units: vx] + public int walkableClimb; + + /// The distance to erode/shrink the walkable area of the heightfield away from + /// obstructions. [Limit: >=0] [Units: vx] + public int walkableRadius; + + /// The maximum allowed length for contour edges along the border of the mesh. [Limit: >=0] [Units: vx] + public int maxEdgeLen; + + /// The maximum distance a simplfied contour's border edges should deviate + /// the original raw contour. [Limit: >=0] [Units: vx] + public float maxSimplificationError; + + /// The minimum number of cells allowed to form isolated island areas. [Limit: >=0] [Units: vx] + public int minRegionArea; + + /// Any regions with a span count smaller than this value will, if possible, + /// be merged with larger regions. [Limit: >=0] [Units: vx] + public int mergeRegionArea; + + /// The maximum number of vertices allowed for polygons generated during the + /// contour to polygon conversion process. [Limit: >= 3] + public int maxVertsPerPoly; + + /// Sets the sampling distance to use when generating the detail mesh. + /// (For height detail only.) [Limits: 0 or >= 0.9] [Units: wu] + public float detailSampleDist; + + /// The maximum distance the detail mesh surface should deviate from heightfield + /// data. (For height detail only.) [Limit: >=0] [Units: wu] + public float detailSampleMaxError; + }; + + + + /// Represents a span in a heightfield. + /// @see rcHeightfield + public class rcSpan { + public ushort smin;// : 13; //< The lower limit of the span. [Limit: < #smax] + public ushort smax;// : 13; //< The upper limit of the span. [Limit: <= #RC_SPAN_MAX_HEIGHT] + public byte area;// : 6; //< The area id assigned to the span. + public rcSpan next = null; //< The next span higher up in column. + } + // A memory pool used for quick allocation of spans within a heightfield. + // @see rcHeightfield + + public class rcSpanPool { + public rcSpanPool next = null; //< The next span pool. + public rcSpan[] items = new rcSpan[RC_SPANS_PER_POOL]; //< Array of spans in the pool. + /// + public rcSpanPool() { + for (int i = 0; i < items.Length; ++i) { + items[i] = new rcSpan(); + } + } + }; + + /// A dynamic heightfield representing obstructed space. + /// @ingroup recast + public class rcHeightfield { + public int width; //< The width of the heightfield. (Along the x-axis in cell units.) + public int height; //< The height of the heightfield. (Along the z-axis in cell units.) + public float[] bmin = new float[3]; //< The minimum bounds in world space. [(x, y, z)] + public float[] bmax = new float[3]; //< The maximum bounds in world space. [(x, y, z)] + public float cs; //< The size of each cell. (On the xz-plane.) + public float ch; //< The height of each cell. (The minimum increment along the y-axis.) + public rcSpan[] spans = null;//** //< Heightfield of spans (width*height). + public rcSpanPool pools = null; //< Linked list of span pools. + public rcSpan freelist = null; //< The next free span. + }; + + /// Provides information on the content of a cell column in a compact heightfield. + public struct rcCompactCell { + public uint index;// : 24; //< Index to the first span in the column. + public ushort count;// : 8; //< Number of spans in the column. + }; + + /// Represents a span of unobstructed space within a compact heightfield. + public struct rcCompactSpan { + public ushort y; //< The lower extent of the span. (Measured from the heightfield's base.) + public ushort reg; //< The id of the region the span belongs to. (Or zero if not in a region.) + public uint con;// : 24; //< Packed neighbor connection data. + public ushort h;// : 8; //< The height of the span. (Measured from #y.) + }; + + /// A compact, static heightfield representing unobstructed space. + /// @ingroup recast + public class rcCompactHeightfield { + public int width; //< The width of the heightfield. (Along the x-axis in cell units.) + public int height; //< The height of the heightfield. (Along the z-axis in cell units.) + public int spanCount; //< The number of spans in the heightfield. + public int walkableHeight; //< The walkable height used during the build of the field. (See: rcConfig::walkableHeight) + public int walkableClimb; //< The walkable climb used during the build of the field. (See: rcConfig::walkableClimb) + public int borderSize; //< The AABB border size used during the build of the field. (See: rcConfig::borderSize) + public ushort maxDistance; //< The maximum distance value of any span within the field. + public ushort maxRegions; //< The maximum region id of any span within the field. + public float[] bmin = new float[3]; //< The minimum bounds in world space. [(x, y, z)] + public float[] bmax = new float[3]; //< The maximum bounds in world space. [(x, y, z)] + public float cs; //< The size of each cell. (On the xz-plane.) + public float ch; //< The height of each cell. (The minimum increment along the y-axis.) + public rcCompactCell[] cells = null; //< Array of cells. [Size: #width*#height] + public rcCompactSpan[] spans = null; //< Array of spans. [Size: #spanCount] + public ushort[] dist = null; //< Array containing border distance data. [Size: #spanCount] + public byte[] areas = null; //< Array containing area id data. [Size: #spanCount] + }; + + /// Represents a heightfield layer within a layer set. + /// @see rcHeightfieldLayerSet + public class rcHeightfieldLayer { + public float[] bmin = new float[3]; //< The minimum bounds in world space. [(x, y, z)] + public float[] bmax = new float[3]; //< The maximum bounds in world space. [(x, y, z)] + public float cs; //< The size of each cell. (On the xz-plane.) + public float ch; //< The height of each cell. (The minimum increment along the y-axis.) + public int width; //< The width of the heightfield. (Along the x-axis in cell units.) + public int height; //< The height of the heightfield. (Along the z-axis in cell units.) + public int minx; //< The minimum x-bounds of usable data. + public int maxx; //< The maximum x-bounds of usable data. + public int miny; //< The minimum y-bounds of usable data. (Along the z-axis.) + public int maxy; //< The maximum y-bounds of usable data. (Along the z-axis.) + public int hmin; //< The minimum height bounds of usable data. (Along the y-axis.) + public int hmax; //< The maximum height bounds of usable data. (Along the y-axis.) + public byte[] heights; //< The heightfield. [Size: (width - borderSize*2) * (h - borderSize*2)] + public byte[] areas; //< Area ids. [Size: Same as #heights] + public byte[] cons; //< Packed neighbor connection information. [Size: Same as #heights] + }; + + /// Represents a set of heightfield layers. + /// @ingroup recast + /// @see rcAllocHeightfieldLayerSet, rcFreeHeightfieldLayerSet + public class rcHeightfieldLayerSet { + public rcHeightfieldLayer[] layers = null; //< The layers in the set. [Size: #nlayers] + public int nlayers; //< The number of layers in the set. + }; + + /// Represents a simple, non-overlapping contour in field space. + public struct rcContour { + public int[] verts; //< Simplified contour vertex and connection data. [Size: 4 * #nverts] + public int nverts; //< The number of vertices in the simplified contour. + public int[] rverts; //< Raw contour vertex and connection data. [Size: 4 * #nrverts] + public int nrverts; //< The number of vertices in the raw contour. + public ushort reg; //< The region id of the contour. + public byte area; //< The area id of the contour. + /// + public void dumpToTxt(StreamWriter stream) { + stream.WriteLine("\treg: " + reg); + stream.WriteLine("\tarea: " + area); + stream.WriteLine("\tnverts: " + nverts); + for (int i = 0; i < nverts; ++i) { + int vIndex = i * 4; + stream.WriteLine("\t\tverts[" + i + "]: x:" + verts[vIndex] + " y:" + verts[vIndex + 1] + " z:" + verts[vIndex + 2] + " ?:" + verts[vIndex + 3]); + } + stream.WriteLine("\tnrverts: " + nrverts); + for (int i = 0; i < nrverts; ++i) { + int vIndex = i * 4; + stream.WriteLine("\t\trverts[" + i + "]: x:" + rverts[vIndex] + " y:" + rverts[vIndex + 1] + " z:" + rverts[vIndex + 2] + " ?:" + rverts[vIndex + 3]); + } + } + public string dumpToString() { + StringBuilder sb = new StringBuilder(); + sb.AppendLine("\treg: " + reg); + sb.AppendLine("\tarea: " + area); + sb.AppendLine("\tnverts: " + nverts); + for (int i = 0; i < nverts; ++i) { + int vIndex = i * 4; + sb.AppendLine("\t\tverts[" + i + "]: x:" + verts[vIndex] + " y:" + verts[vIndex + 1] + " z:" + verts[vIndex + 2] + " ?:" + verts[vIndex + 3]); + } + sb.AppendLine("\tnrverts: " + nrverts); + for (int i = 0; i < nrverts; ++i) { + int vIndex = i * 4; + sb.AppendLine("\t\trverts[" + i + "]: x:" + rverts[vIndex] + " y:" + rverts[vIndex + 1] + " z:" + rverts[vIndex + 2] + " ?:" + rverts[vIndex + 3]); + } + return sb.ToString(); + } + }; + + /// Represents a group of related contours. + /// @ingroup recast + public class rcContourSet { + public rcContour[] conts = null; //< An array of the contours in the set. [Size: #nconts] + public int nconts; //< The number of contours in the set. + public float[] bmin = new float[3]; //< The minimum bounds in world space. [(x, y, z)] + public float[] bmax = new float[3]; //< The maximum bounds in world space. [(x, y, z)] + public float cs; //< The size of each cell. (On the xz-plane.) + public float ch; //< The height of each cell. (The minimum increment along the y-axis.) + public int width; //< The width of the set. (Along the x-axis in cell units.) + public int height; //< The height of the set. (Along the z-axis in cell units.) + public int borderSize; //< The AABB border size used to generate the source data from which the contours were derived. + /// + public override string ToString() { + StringBuilder sb = new StringBuilder(); + + sb.AppendLine("nconts: " + nconts); + sb.AppendLine("bmin: " + bmin[0] + " " + bmin[1] + " " + bmin[2]); + sb.AppendLine("bmax: " + bmax[0] + " " + bmax[1] + " " + bmax[2]); + sb.AppendLine("cs: " + cs); + sb.AppendLine("ch: " + ch); + sb.AppendLine("width: " + width); + sb.AppendLine("height: " + height); + sb.AppendLine("bordersize: " + borderSize); + + for (int i = 0; i < nconts; ++i) { + sb.Append("contour[" + i + "]: "); + sb.AppendLine(conts[i].ToString()); + } + + return sb.ToString(); + } + }; + + /// Represents a polygon mesh suitable for use in building a navigation mesh. + /// @ingroup recast + public class rcPolyMesh { + public ushort[] verts = null; //< The mesh vertices. [Form: (x, y, z) * #nverts] + public ushort[] polys = null; //< Polygon and neighbor data. [Length: #maxpolys * 2 * #nvp] + public ushort[] regs = null; //< The region id assigned to each polygon. [Length: #maxpolys] + public ushort[] flags = null; //< The user defined flags for each polygon. [Length: #maxpolys] + public byte[] areas = null; //< The area id assigned to each polygon. [Length: #maxpolys] + public int nverts; //< The number of vertices. + public int npolys; //< The number of polygons. + public int maxpolys; //< The number of allocated polygons. + public int nvp; //< The maximum number of vertices per polygon. + public float[] bmin = new float[3]; //< The minimum bounds in world space. [(x, y, z)] + public float[] bmax = new float[3]; //< The maximum bounds in world space. [(x, y, z)] + public float cs; //< The size of each cell. (On the xz-plane.) + public float ch; //< The height of each cell. (The minimum increment along the y-axis.) + public int borderSize; //< The AABB border size used to generate the source data from which the mesh was derived. + /// + public override string ToString() { + StringBuilder sb = new StringBuilder(); + + sb.AppendLine("bmin: " + bmin[0] + " " + bmin[1] + " " + bmin[2]); + sb.AppendLine("bmax: " + bmax[0] + " " + bmax[1] + " " + bmax[2]); + sb.AppendLine("cs: " + cs); + sb.AppendLine("ch: " + ch); + sb.AppendLine("bordersize: " + borderSize); + + sb.AppendLine("nverts: " + nverts); + for (int i = 0; i < nverts; ++i) { + int vIndex = i * 3; + sb.AppendLine("\tverts[" + i + "]: x:" + verts[vIndex] + " y:" + verts[vIndex + 1] + " z:" + verts[vIndex + 2]); + } + sb.AppendLine("\tmaxpolys: " + maxpolys); + sb.AppendLine("\tnvp: " + nvp); + sb.AppendLine("\tnpolys: " + npolys); + for (int i = 0; i < maxpolys; ++i) { + int vIndex = i * nvp; + sb.Append("\t\tpolys[" + i + "]: "); + for (int j = 0; j < nvp; ++j) { + sb.Append(" " + j + ":" + polys[vIndex + j]); + } + + vIndex += nvp; + sb.AppendLine(); + sb.Append("\t\tneighbor[" + i + "]: "); + for (int j = 0; j < nvp; ++j) { + sb.Append(" " + j + ":" + polys[vIndex + j]); + } + sb.AppendLine(); + } + + for (int i = 0; i < maxpolys; ++i) { + sb.AppendLine("regs[" + i + "]: " + regs[i]); + } + sb.AppendLine(); + for (int i = 0; i < flags.Length; ++i) { + sb.AppendLine("flags[" + i + "]: " + flags[i]); + } + + return sb.ToString(); + } + + public string ToObj(){ + StringBuilder sb = new StringBuilder(); + + sb.AppendLine("# Recast Navmesh"); + sb.AppendLine("o NavMesh"); + + sb.AppendLine(); + + for (int i = 0; i < nverts; ++i) { + //ushort* v = &pmesh.verts[i*3]; + int vIndex = i * 3; + float x = bmin[0] + verts[vIndex + 0] * cs; + float y = bmin[1] + (verts[vIndex + 1] + 1) * ch + 0.1f; + float z = bmin[2] + verts[vIndex + 2] * cs; + //ioprintf(io, "v %f %f %f\n", x,y,z); + sb.AppendLine("v " + x + " " + y + " " + z); + } + + sb.AppendLine(); + + for (int i = 0; i < npolys; ++i) { + //const unsigned short* p = &pmesh.polys[i*nvp*2]; + int pIndex = i * nvp * 2; + for (int j = 2; j < nvp; ++j) { + if (polys[pIndex + j] == RC_MESH_NULL_IDX) + break; + //ioprintf(io, "f %d %d %d\n", p[0]+1, p[j-1]+1, p[j]+1); + int a = polys[pIndex] + 1; + int b = polys[pIndex + j - 1] + 1; + int c = polys[pIndex + j] + 1; + sb.AppendLine("f " + a + " " + b + " " + c); + } + } + return sb.ToString (); + } + }; + + /// Contains triangle meshes that represent detailed height data associated + /// with the polygons in its associated polygon mesh object. + /// @ingroup recast + public class rcPolyMeshDetail { + public uint[] meshes = null; //< The sub-mesh data. [Size: 4*#nmeshes] + public float[] verts = null; //< The mesh vertices. [Size: 3*#nverts] + public byte[] tris = null; //< The mesh triangles. [Size: 4*#ntris] + public int nmeshes; //< The number of sub-meshes defined by #meshes. + public int nverts; //< The number of vertices in #verts. + public int ntris; //< The number of triangles in #tris. + + public override string ToString() + { + StringBuilder sb = new StringBuilder(); + + sb.AppendLine("nmeshes: " + nmeshes); + for (int i = 0; i < nmeshes; ++i) { + int vIndex = i * 4; + sb.AppendLine("\tmeshes[" + i + "]: a:" + meshes[vIndex] + " b:" + meshes[vIndex + 1] + " c:" + meshes[vIndex + 2] + " d:" + meshes[vIndex + 3]); + } + sb.AppendLine("nverts: " + nverts); + for (int i = 0; i < nverts; ++i) { + int vIndex = i * 3; + sb.AppendLine("\tverts[" + i + "]: x:" + verts[vIndex] + " y:" + verts[vIndex + 1] + " z:" + verts[vIndex + 2]); + } + sb.AppendLine("ntris: " + ntris); + for (int i = 0; i < ntris; ++i) { + int vIndex = i * 4; + sb.AppendLine("\ttris[" + i + "]: a:" + tris[vIndex] + " b:" + tris[vIndex + 1] + " c:" + tris[vIndex + 2] + " d:" + tris[vIndex + 3]); + } + + return sb.ToString(); + } + + public string ToObj() + { + StringBuilder sb = new StringBuilder(); + + sb.AppendLine("# Recast C# Navmesh\n"); + sb.AppendLine("o NavMesh\n"); + + sb.AppendLine("\n"); + + for (int i = 0; i < nverts; ++i) { + int vIndex = i * 3; + sb.AppendLine("v " + verts[vIndex + 0] + " " + verts[vIndex + 1] + " " + verts[vIndex + 2]); + } + + sb.AppendLine(); + + for (int i = 0; i < nmeshes; ++i) { + //uint* m = &dmesh.meshes[i*4]; + int mIndex = i * 4; + uint bverts = meshes[mIndex + 0]; + uint btris = meshes[mIndex + 2]; + uint _ntris = meshes[mIndex + 3]; + uint trisIndex = btris * 4; + for (uint j = 0; j < _ntris; ++j) { + sb.AppendLine("f " + + ((int)(bverts + tris[trisIndex + j * 4 + 0]) + 1) + " " + + ((int)(bverts + tris[trisIndex + j * 4 + 1]) + 1) + " " + + ((int)(bverts + tris[trisIndex + j * 4 + 2]) + 1) + " "); + } + } + + return sb.ToString(); + } + }; + + /// @name Allocation Functions + /// Functions used to allocate and de-allocate Recast objects. + /// @see rcAllocSetCustom + /// @{ + + /// Allocates a heightfield object using the Recast allocator. + /// @return A heightfield that is ready for initialization, or null on failure. + /// @ingroup recast + /// @see rcCreateHeightfield, rcFreeHeightField + //rcHeightfield* rcAllocHeightfield(); + + /// Frees the specified heightfield object using the Recast allocator. + /// @param[in] hf A heightfield allocated using #rcAllocHeightfield + /// @ingroup recast + /// @see rcAllocHeightfield + //void rcFreeHeightField(rcHeightfield* hf); + + /// Allocates a compact heightfield object using the Recast allocator. + /// @return A compact heightfield that is ready for initialization, or null on failure. + /// @ingroup recast + /// @see rcBuildCompactHeightfield, rcFreeCompactHeightfield + //rcCompactHeightfield* rcAllocCompactHeightfield(); + + /// Frees the specified compact heightfield object using the Recast allocator. + /// @param[in] chf A compact heightfield allocated using #rcAllocCompactHeightfield + /// @ingroup recast + /// @see rcAllocCompactHeightfield + //void rcFreeCompactHeightfield(rcCompactHeightfield* chf); + + /// Allocates a heightfield layer set using the Recast allocator. + /// @return A heightfield layer set that is ready for initialization, or null on failure. + /// @ingroup recast + /// @see rcBuildHeightfieldLayers, rcFreeHeightfieldLayerSet + //rcHeightfieldLayerSet* rcAllocHeightfieldLayerSet(); + + /// Frees the specified heightfield layer set using the Recast allocator. + /// @param[in] lset A heightfield layer set allocated using #rcAllocHeightfieldLayerSet + /// @ingroup recast + /// @see rcAllocHeightfieldLayerSet + //void rcFreeHeightfieldLayerSet(rcHeightfieldLayerSet* lset); + + /// Allocates a contour set object using the Recast allocator. + /// @return A contour set that is ready for initialization, or null on failure. + /// @ingroup recast + /// @see rcBuildContours, rcFreeContourSet + //rcContourSet* rcAllocContourSet(); + + /// Frees the specified contour set using the Recast allocator. + /// @param[in] cset A contour set allocated using #rcAllocContourSet + /// @ingroup recast + /// @see rcAllocContourSet + //void rcFreeContourSet(rcContourSet* cset); + + /// Allocates a polygon mesh object using the Recast allocator. + /// @return A polygon mesh that is ready for initialization, or null on failure. + /// @ingroup recast + /// @see rcBuildPolyMesh, rcFreePolyMesh + //rcPolyMesh* rcAllocPolyMesh(); + + /// Frees the specified polygon mesh using the Recast allocator. + /// @param[in] pmesh A polygon mesh allocated using #rcAllocPolyMesh + /// @ingroup recast + /// @see rcAllocPolyMesh + //void rcFreePolyMesh(rcPolyMesh* pmesh); + + /// Allocates a detail mesh object using the Recast allocator. + /// @return A detail mesh that is ready for initialization, or null on failure. + /// @ingroup recast + /// @see rcBuildPolyMeshDetail, rcFreePolyMeshDetail + //rcPolyMeshDetail* rcAllocPolyMeshDetail(); + + /// Frees the specified detail mesh using the Recast allocator. + /// @param[in] dmesh A detail mesh allocated using #rcAllocPolyMeshDetail + /// @ingroup recast + /// @see rcAllocPolyMeshDetail + //void rcFreePolyMeshDetail(rcPolyMeshDetail* dmesh); + + /// The number of spans allocated per span spool. + /// @see rcSpanPool + public const int RC_SPANS_PER_POOL = 2048; + + /// Heighfield border flag. + /// If a heightfield region ID has this bit set, then the region is a border + /// region and its spans are considered unwalkable. + /// (Used during the region and contour build process.) + /// @see rcCompactSpan::reg + + public const ushort RC_BORDER_REG = 0x8000; + + /// Border vertex flag. + /// If a region ID has this bit set, then the associated element lies on + /// a tile border. If a contour vertex's region ID has this bit set, the + /// vertex will later be removed in order to match the segments and vertices + /// at tile boundaries. + /// (Used during the build process.) + /// @see rcCompactSpan::reg, #rcContour::verts, #rcContour::rverts + public const int RC_BORDER_VERTEX = 0x10000; + + /// Area border flag. + /// If a region ID has this bit set, then the associated element lies on + /// the border of an area. + /// (Used during the region and contour build process.) + /// @see rcCompactSpan::reg, #rcContour::verts, #rcContour::rverts + public const int RC_AREA_BORDER = 0x20000; + + /// Contour build flags. + /// @see rcBuildContours + public enum rcBuildContoursFlags { + RC_CONTOUR_TESS_WALL_EDGES = 0x01, //< Tessellate solid (impassable) edges during contour simplification. + RC_CONTOUR_TESS_AREA_EDGES = 0x02, //< Tessellate edges between areas during contour simplification. + }; + + /// Applied to the region id field of contour vertices in order to extract the region id. + /// The region id field of a vertex may have several flags applied to it. So the + /// fields value can't be used directly. + /// @see rcContour::verts, rcContour::rverts + public const int RC_CONTOUR_REG_MASK = 0xffff; + + /// An value which indicates an invalid index within a mesh. + /// @note This does not necessarily indicate an error. + /// @see rcPolyMesh::polys + public const ushort RC_MESH_NULL_IDX = 0xffff; + + /// Represents the null area. + /// When a data element is given this value it is considered to no longer be + /// assigned to a usable area. (E.g. It is unwalkable.) + public const byte RC_NULL_AREA = 0; + + /// The default area id used to indicate a walkable polygon. + /// This is also the maximum allowed area id, and the only non-null area id + /// recognized by some steps in the build process. + public const byte RC_WALKABLE_AREA = 63; + + /// The value returned by #rcGetCon if the specified direction is not connected + /// to another span. (Has no neighbor.) + public const int RC_NOT_CONNECTED = 0x3f; + + /// @name General helper functions + /// @{ + + /// Used to ignore a function parameter. VS complains about unused parameters + /// and this silences the warning. + /// @param [in] _ Unused parameter + public static void rcIgnoreUnused(T t) { } + + //Use C# for this kind of things + /// Swaps the values of the two parameters. + /// @param[in,out] a Value A + /// @param[in,out] b Value B + //public void rcSwap(T a, T b) { T t = a; a = b; b = t; } + static void rcSwap(ref T lhs, ref T rhs) { + T temp = lhs; + lhs = rhs; + rhs = temp; + } + + /// Returns the minimum of two values. + /// @param[in] a Value A + /// @param[in] b Value B + /// @return The minimum of the two values. + //public T Math.Min(T a, T b) { + // return a < b ? a : b; + //} + + /// Returns the maximum of two values. + /// @param[in] a Value A + /// @param[in] b Value B + /// @return The maximum of the two values. + //template inline T Math.Max(T a, T b) { return a > b ? a : b; } + + /// Returns the absolute value. + /// @param[in] a The value. + /// @return The absolute value of the specified value. + //template inline T rcAbs(T a) { return a < 0 ? -a : a; } + + /// Returns the square of the value. + /// @param[in] a The value. + /// @return The square of the value. + //template inline T rcSqr(T a) { return a*a; } + + /// Clamps the value to the specified range. + /// @param[in] v The value to clamp. + /// @param[in] mn The minimum permitted return value. + /// @param[in] mx The maximum permitted return value. + /// @return The value, clamped to the specified range. + //template inline T rcClamp(T v, T mn, T mx) { return v < mn ? mn : (v > mx ? mx : v); } + public static int rcClamp(int v, int mn, int mx) { + return v < mn ? mn : (v > mx ? mx : v); + } + + /// Returns the square root of the value. + /// @param[in] x The value. + /// @return The square root of the vlaue. + //float rcSqrt(float x); + + /// @} + /// @name Vector helper functions. + /// @{ + + /// Derives the cross product of two vectors. (@p v1 x @p v2) + /// @param[out] dest The cross product. [(x, y, z)] + /// @param[in] v1 A Vector [(x, y, z)] + /// @param[in] v2 A vector [(x, y, z)] + + public static void rcVcross(float[] dest, float[] v1, float[] v2) { + dest[0] = v1[1] * v2[2] - v1[2] * v2[1]; + dest[1] = v1[2] * v2[0] - v1[0] * v2[2]; + dest[2] = v1[0] * v2[1] - v1[1] * v2[0]; + } + public static void rcVcross(float[] dest, int destStart, float[] v1, int v1Start, float[] v2, int v2Start) { + dest[destStart + 0] = v1[v1Start + 1] * v2[v2Start + 2] - v1[v1Start + 2] * v2[v2Start + 1]; + dest[destStart + 1] = v1[v1Start + 2] * v2[v2Start + 0] - v1[v1Start + 0] * v2[v2Start + 2]; + dest[destStart + 2] = v1[v1Start + 0] * v2[v2Start + 1] - v1[v1Start + 1] * v2[v2Start + 0]; + } + + /// Derives the dot product of two vectors. (@p v1 . @p v2) + /// @param[in] v1 A Vector [(x, y, z)] + /// @param[in] v2 A vector [(x, y, z)] + /// @return The dot product. + public static float rcVdot(float[] v1, float[] v2) { + return v1[0] * v2[0] + v1[1] * v2[1] + v1[2] * v2[2]; + } + + /// Performs a scaled vector addition. (@p v1 + (@p v2 * @p s)) + /// @param[out] dest The result vector. [(x, y, z)] + /// @param[in] v1 The base vector. [(x, y, z)] + /// @param[in] v2 The vector to scale and add to @p v1. [(x, y, z)] + /// @param[in] s The amount to scale @p v2 by before adding to @p v1. + public static void rcVmad(float[] dest, float[] v1, float[] v2, float s) { + dest[0] = v1[0] + v2[0] * s; + dest[1] = v1[1] + v2[1] * s; + dest[2] = v1[2] + v2[2] * s; + } + + /// Performs a vector addition. (@p v1 + @p v2) + /// @param[out] dest The result vector. [(x, y, z)] + /// @param[in] v1 The base vector. [(x, y, z)] + /// @param[in] v2 The vector to add to @p v1. [(x, y, z)] + public static void rcVadd(float[] dest, float[] v1, float[] v2) { + dest[0] = v1[0] + v2[0]; + dest[1] = v1[1] + v2[1]; + dest[2] = v1[2] + v2[2]; + } + + /// Performs a vector subtraction. (@p v1 - @p v2) + /// @param[out] dest The result vector. [(x, y, z)] + /// @param[in] v1 The base vector. [(x, y, z)] + /// @param[in] v2 The vector to subtract from @p v1. [(x, y, z)] + public static void rcVsub(float[] dest, float[] v1, float[] v2) { + dest[0] = v1[0] - v2[0]; + dest[1] = v1[1] - v2[1]; + dest[2] = v1[2] - v2[2]; + } + + public static void rcVsub(float[] dest, int destStart, float[] v1, int v1Start, float[] v2, int v2Start) { + dest[destStart + 0] = v1[v1Start + 0] - v2[v2Start + 0]; + dest[destStart + 1] = v1[v1Start + 1] - v2[v2Start + 1]; + dest[destStart + 2] = v1[v1Start + 2] - v2[v2Start + 2]; + } + + /// Selects the minimum value of each element from the specified vectors. + /// @param[in,out] mn A vector. (Will be updated with the result.) [(x, y, z)] + /// @param[in] v A vector. [(x, y, z)] + public static void rcVmin(float[] mn, float[] v) { + mn[0] = Math.Min(mn[0], v[0]); + mn[1] = Math.Min(mn[1], v[1]); + mn[2] = Math.Min(mn[2], v[2]); + } + + public static void rcVmin(float[] mn, int mnStart, float[] v, int vStart) { + mn[0 + mnStart] = Math.Min(mn[0 + mnStart], v[0 + vStart]); + mn[1 + mnStart] = Math.Min(mn[1 + mnStart], v[1 + vStart]); + mn[2 + mnStart] = Math.Min(mn[2 + mnStart], v[2 + vStart]); + } + + + /// Selects the maximum value of each element from the specified vectors. + /// @param[in,out] mx A vector. (Will be updated with the result.) [(x, y, z)] + /// @param[in] v A vector. [(x, y, z)] + public static void rcVmax(float[] mx, float[] v) { + mx[0] = Math.Max(mx[0], v[0]); + mx[1] = Math.Max(mx[1], v[1]); + mx[2] = Math.Max(mx[2], v[2]); + } + + public static void rcVmax(float[] mx, int mxStart, float[] v, int vStart) { + mx[0 + mxStart] = Math.Max(mx[0 + mxStart], v[0 + vStart]); + mx[1 + mxStart] = Math.Max(mx[1 + mxStart], v[1 + vStart]); + mx[2 + mxStart] = Math.Max(mx[2 + mxStart], v[2 + vStart]); + } + + /// Performs a vector copy. + /// @param[out] dest The result. [(x, y, z)] + /// @param[in] v The vector to copy. [(x, y, z)] + public static void rcVcopy(float[] dest, float[] v) { + dest[0] = v[0]; + dest[1] = v[1]; + dest[2] = v[2]; + } + public static void rcVcopy(float[] dest, int destStart, float[] v, int vStart) { + dest[destStart + 0] = v[vStart + 0]; + dest[destStart + 1] = v[vStart + 1]; + dest[destStart + 2] = v[vStart + 2]; + } + + /// Returns the distance between two points. + /// @param[in] v1 A point. [(x, y, z)] + /// @param[in] v2 A point. [(x, y, z)] + /// @return The distance between the two points. + public static float rcVdist(float[] v1, float[] v2) { + float dx = v2[0] - v1[0]; + float dy = v2[1] - v1[1]; + float dz = v2[2] - v1[2]; + return (float)Math.Sqrt(dx * dx + dy * dy + dz * dz); + } + + /// Returns the square of the distance between two points. + /// @param[in] v1 A point. [(x, y, z)] + /// @param[in] v2 A point. [(x, y, z)] + /// @return The square of the distance between the two points. + public static float rcVdistSqr(float[] v1, float[] v2) { + float dx = v2[0] - v1[0]; + float dy = v2[1] - v1[1]; + float dz = v2[2] - v1[2]; + return dx * dx + dy * dy + dz * dz; + } + + /// Normalizes the vector. + /// @param[in,out] v The vector to normalize. [(x, y, z)] + public static void rcVnormalize(float[] v) { + float d = 1.0f / (float)Math.Sqrt((v[0] * v[0]) + (v[1] * v[1]) + (v[2] * v[2])); + v[0] *= d; + v[1] *= d; + v[2] *= d; + } + + /// @} + /// @name Heightfield Functions + /// @see rcHeightfield + /// @{ + + /// Calculates the bounding box of an array of vertices. + /// @ingroup recast + /// @param[in] verts An array of vertices. [(x, y, z) * @p nv] + /// @param[in] nv The number of vertices in the @p verts array. + /// @param[out] bmin The minimum bounds of the AABB. [(x, y, z)] [Units: wu] + /// @param[out] bmax The maximum bounds of the AABB. [(x, y, z)] [Units: wu] + //void rcCalcBounds(const float* verts, int nv, float* bmin, float* bmax); + + /// Calculates the grid size based on the bounding box and grid cell size. + /// @ingroup recast + /// @param[in] bmin The minimum bounds of the AABB. [(x, y, z)] [Units: wu] + /// @param[in] bmax The maximum bounds of the AABB. [(x, y, z)] [Units: wu] + /// @param[in] cs The xz-plane cell size. [Limit: > 0] [Units: wu] + /// @param[out] w The width along the x-axis. [Limit: >= 0] [Units: vx] + /// @param[out] h The height along the z-axis. [Limit: >= 0] [Units: vx] + //void rcCalcGridSize(const float* bmin, const float* bmax, float cs, int* w, int* h); + + /// Initializes a new heightfield. + /// @ingroup recast + /// @param[in,out] ctx The build context to use during the operation. + /// @param[in,out] hf The allocated heightfield to initialize. + /// @param[in] width The width of the field along the x-axis. [Limit: >= 0] [Units: vx] + /// @param[in] height The height of the field along the z-axis. [Limit: >= 0] [Units: vx] + /// @param[in] bmin The minimum bounds of the field's AABB. [(x, y, z)] [Units: wu] + /// @param[in] bmax The maximum bounds of the field's AABB. [(x, y, z)] [Units: wu] + /// @param[in] cs The xz-plane cell size to use for the field. [Limit: > 0] [Units: wu] + /// @param[in] ch The y-axis cell size to use for field. [Limit: > 0] [Units: wu] + //bool rcCreateHeightfield(rcContext* ctx, rcHeightfield& hf, int width, int height, + // const float* bmin, const float* bmax, + // float cs, float ch); + + /// Sets the area id of all triangles with a slope below the specified value + /// to #RC_WALKABLE_AREA. + /// @ingroup recast + /// @param[in,out] ctx The build context to use during the operation. + /// @param[in] walkableSlopeAngle The maximum slope that is considered walkable. + /// [Limits: 0 <= value < 90] [Units: Degrees] + /// @param[in] verts The vertices. [(x, y, z) * @p nv] + /// @param[in] nv The number of vertices. + /// @param[in] tris The triangle vertex indices. [(vertA, vertB, vertC) * @p nt] + /// @param[in] nt The number of triangles. + /// @param[out] areas The triangle area ids. [Length: >= @p nt] + //void rcMarkWalkableTriangles(rcContext* ctx, const float walkableSlopeAngle, const float* verts, int nv, + // const int* tris, int nt, byte* areas); + + /// Sets the area id of all triangles with a slope greater than or equal to the specified value to #RC_NULL_AREA. + /// @ingroup recast + /// @param[in,out] ctx The build context to use during the operation. + /// @param[in] walkableSlopeAngle The maximum slope that is considered walkable. + /// [Limits: 0 <= value < 90] [Units: Degrees] + /// @param[in] verts The vertices. [(x, y, z) * @p nv] + /// @param[in] nv The number of vertices. + /// @param[in] tris The triangle vertex indices. [(vertA, vertB, vertC) * @p nt] + /// @param[in] nt The number of triangles. + /// @param[out] areas The triangle area ids. [Length: >= @p nt] + //void rcClearUnwalkableTriangles(rcContext* ctx, const float walkableSlopeAngle, const float* verts, int nv, + //const int* tris, int nt, byte* areas); + + /// Adds a span to the specified heightfield. + /// @ingroup recast + /// @param[in,out] ctx The build context to use during the operation. + /// @param[in,out] hf An initialized heightfield. + /// @param[in] x The width index where the span is to be added. + /// [Limits: 0 <= value < rcHeightfield::width] + /// @param[in] y The height index where the span is to be added. + /// [Limits: 0 <= value < rcHeightfield::height] + /// @param[in] smin The minimum height of the span. [Limit: < @p smax] [Units: vx] + /// @param[in] smax The maximum height of the span. [Limit: <= #RC_SPAN_MAX_HEIGHT] [Units: vx] + /// @param[in] area The area id of the span. [Limit: <= #RC_WALKABLE_AREA) + /// @param[in] flagMergeThr The merge theshold. [Limit: >= 0] [Units: vx] + //void rcAddSpan(rcContext* ctx, rcHeightfield& hf, const int x, const int y, + // const ushort smin, const ushort smax, + // const byte area, const int flagMergeThr); + + /// Rasterizes a triangle into the specified heightfield. + /// @ingroup recast + /// @param[in,out] ctx The build context to use during the operation. + /// @param[in] v0 Triangle vertex 0 [(x, y, z)] + /// @param[in] v1 Triangle vertex 1 [(x, y, z)] + /// @param[in] v2 Triangle vertex 2 [(x, y, z)] + /// @param[in] area The area id of the triangle. [Limit: <= #RC_WALKABLE_AREA] + /// @param[in,out] solid An initialized heightfield. + /// @param[in] flagMergeThr The distance where the walkable flag is favored over the non-walkable flag. + /// [Limit: >= 0] [Units: vx] + //void rcRasterizeTriangle(rcContext* ctx, const float* v0, const float* v1, const float* v2, + // const byte area, rcHeightfield& solid, + // const int flagMergeThr = 1); + + /// Rasterizes an indexed triangle mesh into the specified heightfield. + /// @ingroup recast + /// @param[in,out] ctx The build context to use during the operation. + /// @param[in] verts The vertices. [(x, y, z) * @p nv] + /// @param[in] nv The number of vertices. + /// @param[in] tris The triangle indices. [(vertA, vertB, vertC) * @p nt] + /// @param[in] areas The area id's of the triangles. [Limit: <= #RC_WALKABLE_AREA] [Size: @p nt] + /// @param[in] nt The number of triangles. + /// @param[in,out] solid An initialized heightfield. + /// @param[in] flagMergeThr The distance where the walkable flag is favored over the non-walkable flag. + /// [Limit: >= 0] [Units: vx] + //void rcRasterizeTriangles(rcContext* ctx, const float* verts, const int nv, + // const int* tris, const byte* areas, const int nt, + // rcHeightfield& solid, const int flagMergeThr = 1); + + /// Rasterizes an indexed triangle mesh into the specified heightfield. + /// @ingroup recast + /// @param[in,out] ctx The build context to use during the operation. + /// @param[in] verts The vertices. [(x, y, z) * @p nv] + /// @param[in] nv The number of vertices. + /// @param[in] tris The triangle indices. [(vertA, vertB, vertC) * @p nt] + /// @param[in] areas The area id's of the triangles. [Limit: <= #RC_WALKABLE_AREA] [Size: @p nt] + /// @param[in] nt The number of triangles. + /// @param[in,out] solid An initialized heightfield. + /// @param[in] flagMergeThr The distance where the walkable flag is favored over the non-walkable flag. + /// [Limit: >= 0] [Units: vx] + //void rcRasterizeTriangles(rcContext* ctx, const float* verts, const int nv, + // const ushort* tris, const byte* areas, const int nt, + // rcHeightfield& solid, const int flagMergeThr = 1); + + /// Rasterizes triangles into the specified heightfield. + /// @ingroup recast + /// @param[in,out] ctx The build context to use during the operation. + /// @param[in] verts The triangle vertices. [(ax, ay, az, bx, by, bz, cx, by, cx) * @p nt] + /// @param[in] areas The area id's of the triangles. [Limit: <= #RC_WALKABLE_AREA] [Size: @p nt] + /// @param[in] nt The number of triangles. + /// @param[in,out] solid An initialized heightfield. + /// @param[in] flagMergeThr The distance where the walkable flag is favored over the non-walkable flag. + /// [Limit: >= 0] [Units: vx] + //void rcRasterizeTriangles(rcContext* ctx, const float* verts, const byte* areas, const int nt, + // rcHeightfield& solid, const int flagMergeThr = 1); + + /// Marks non-walkable spans as walkable if their maximum is within @p walkableClimp of a walkable neihbor. + /// @ingroup recast + /// @param[in,out] ctx The build context to use during the operation. + /// @param[in] walkableClimb Maximum ledge height that is considered to still be traversable. + /// [Limit: >=0] [Units: vx] + /// @param[in,out] solid A fully built heightfield. (All spans have been added.) + //void rcFilterLowHangingWalkableObstacles(rcContext* ctx, const int walkableClimb, rcHeightfield& solid); + + /// Marks spans that are ledges as not-walkable. + /// @ingroup recast + /// @param[in,out] ctx The build context to use during the operation. + /// @param[in] walkableHeight Minimum floor to 'ceiling' height that will still allow the floor area to + /// be considered walkable. [Limit: >= 3] [Units: vx] + /// @param[in] walkableClimb Maximum ledge height that is considered to still be traversable. + /// [Limit: >=0] [Units: vx] + /// @param[in,out] solid A fully built heightfield. (All spans have been added.) + //void rcFilterLedgeSpans(rcContext* ctx, const int walkableHeight, + // const int walkableClimb, rcHeightfield& solid); + + /// Marks walkable spans as not walkable if the clearence above the span is less than the specified height. + /// @ingroup recast + /// @param[in,out] ctx The build context to use during the operation. + /// @param[in] walkableHeight Minimum floor to 'ceiling' height that will still allow the floor area to + /// be considered walkable. [Limit: >= 3] [Units: vx] + /// @param[in,out] solid A fully built heightfield. (All spans have been added.) + //void rcFilterWalkableLowHeightSpans(rcContext* ctx, int walkableHeight, rcHeightfield& solid); + + /// Returns the number of spans contained in the specified heightfield. + /// @ingroup recast + /// @param[in,out] ctx The build context to use during the operation. + /// @param[in] hf An initialized heightfield. + /// @returns The number of spans in the heightfield. + //int rcGetHeightFieldSpanCount(rcContext* ctx, rcHeightfield& hf); + + /// @} + /// @name Compact Heightfield Functions + /// @see rcCompactHeightfield + /// @{ + + /// Builds a compact heightfield representing open space, from a heightfield representing solid space. + /// @ingroup recast + /// @param[in,out] ctx The build context to use during the operation. + /// @param[in] walkableHeight Minimum floor to 'ceiling' height that will still allow the floor area + /// to be considered walkable. [Limit: >= 3] [Units: vx] + /// @param[in] walkableClimb Maximum ledge height that is considered to still be traversable. + /// [Limit: >=0] [Units: vx] + /// @param[in] hf The heightfield to be compacted. + /// @param[out] chf The resulting compact heightfield. (Must be pre-allocated.) + /// @returns True if the operation completed successfully. + //bool rcBuildCompactHeightfield(rcContext* ctx, const int walkableHeight, const int walkableClimb, + // rcHeightfield& hf, rcCompactHeightfield& chf); + + + /// Erodes the walkable area within the heightfield by the specified radius. + /// @ingroup recast + /// @param[in,out] ctx The build context to use during the operation. + /// @param[in] radius The radius of erosion. [Limits: 0 < value < 255] [Units: vx] + /// @param[in,out] chf The populated compact heightfield to erode. + /// @returns True if the operation completed successfully. + //bool rcErodeWalkableArea(rcContext* ctx, int radius, rcCompactHeightfield& chf); + + /// Applies a median filter to walkable area types (based on area id), removing noise. + /// @ingroup recast + /// @param[in,out] ctx The build context to use during the operation. + /// @param[in,out] chf A populated compact heightfield. + /// @returns True if the operation completed successfully. + //bool rcMedianFilterWalkableArea(rcContext* ctx, rcCompactHeightfield& chf); + + /// Applies an area id to all spans within the specified bounding box. (AABB) + /// @ingroup recast + /// @param[in,out] ctx The build context to use during the operation. + /// @param[in] bmin The minimum of the bounding box. [(x, y, z)] + /// @param[in] bmax The maximum of the bounding box. [(x, y, z)] + /// @param[in] areaId The area id to apply. [Limit: <= #RC_WALKABLE_AREA] + /// @param[in,out] chf A populated compact heightfield. + //void rcMarkBoxArea(rcContext* ctx, const float* bmin, const float* bmax, byte areaId, + // rcCompactHeightfield& chf); + + /// Applies the area id to the all spans within the specified convex polygon. + /// @ingroup recast + /// @param[in,out] ctx The build context to use during the operation. + /// @param[in] verts The vertices of the polygon [Fomr: (x, y, z) * @p nverts] + /// @param[in] nverts The number of vertices in the polygon. + /// @param[in] hmin The height of the base of the polygon. + /// @param[in] hmax The height of the top of the polygon. + /// @param[in] areaId The area id to apply. [Limit: <= #RC_WALKABLE_AREA] + /// @param[in,out] chf A populated compact heightfield. + //void rcMarkConvexPolyArea(rcContext* ctx, const float* verts, const int nverts, + // const float hmin, const float hmax, byte areaId, + // rcCompactHeightfield& chf); + + /// Helper function to offset voncex polygons for rcMarkConvexPolyArea. + /// @ingroup recast + /// @param[in] verts The vertices of the polygon [Form: (x, y, z) * @p nverts] + /// @param[in] nverts The number of vertices in the polygon. + /// @param[out] outVerts The offset vertices (should hold up to 2 * @p nverts) [Form: (x, y, z) * return value] + /// @param[in] maxOutVerts The max number of vertices that can be stored to @p outVerts. + /// @returns Number of vertices in the offset polygon or 0 if too few vertices in @p outVerts. + //int rcOffsetPoly(const float* verts, const int nverts, const float offset, + // float* outVerts, const int maxOutVerts); + + /// Applies the area id to all spans within the specified cylinder. + /// @ingroup recast + /// @param[in,out] ctx The build context to use during the operation. + /// @param[in] pos The center of the base of the cylinder. [Form: (x, y, z)] + /// @param[in] r The radius of the cylinder. + /// @param[in] h The height of the cylinder. + /// @param[in] areaId The area id to apply. [Limit: <= #RC_WALKABLE_AREA] + /// @param[in,out] chf A populated compact heightfield. + //void rcMarkCylinderArea(rcContext* ctx, const float* pos, + // const float r, const float h, byte areaId, + // rcCompactHeightfield& chf); + + /// Builds the distance field for the specified compact heightfield. + /// @ingroup recast + /// @param[in,out] ctx The build context to use during the operation. + /// @param[in,out] chf A populated compact heightfield. + /// @returns True if the operation completed successfully. + //bool rcBuildDistanceField(rcContext* ctx, rcCompactHeightfield& chf); + + /// Builds region data for the heightfield using watershed partitioning. + /// @ingroup recast + /// @param[in,out] ctx The build context to use during the operation. + /// @param[in,out] chf A populated compact heightfield. + /// @param[in] borderSize The size of the non-navigable border around the heightfield. + /// [Limit: >=0] [Units: vx] + /// @param[in] minRegionArea The minimum number of cells allowed to form isolated island areas. + /// [Limit: >=0] [Units: vx]. + /// @param[in] mergeRegionArea Any regions with a span count smaller than this value will, if possible, + /// be merged with larger regions. [Limit: >=0] [Units: vx] + /// @returns True if the operation completed successfully. + //bool rcBuildRegions(rcContext* ctx, rcCompactHeightfield& chf, + // const int borderSize, const int minRegionArea, const int mergeRegionArea); + + /// Builds region data for the heightfield using simple monotone partitioning. + /// @ingroup recast + /// @param[in,out] ctx The build context to use during the operation. + /// @param[in,out] chf A populated compact heightfield. + /// @param[in] borderSize The size of the non-navigable border around the heightfield. + /// [Limit: >=0] [Units: vx] + /// @param[in] minRegionArea The minimum number of cells allowed to form isolated island areas. + /// [Limit: >=0] [Units: vx]. + /// @param[in] mergeRegionArea Any regions with a span count smaller than this value will, if possible, + /// be merged with larger regions. [Limit: >=0] [Units: vx] + /// @returns True if the operation completed successfully. + //bool rcBuildRegionsMonotone(rcContext* ctx, rcCompactHeightfield& chf, + // const int borderSize, const int minRegionArea, const int mergeRegionArea); + + + /// Sets the neighbor connection data for the specified direction. + /// @param[in] s The span to update. + /// @param[in] dir The direction to set. [Limits: 0 <= value < 4] + /// @param[in] i The index of the neighbor span. + public static void rcSetCon(ref rcCompactSpan s, int dir, int i) { + uint udir = (uint)dir; + int shift = (int)(udir * 6); + uint con = s.con; + s.con = (uint)(con & ~(0x3f << shift)) | (((uint)i & 0x3f) << shift); + } + + /// Gets neighbor connection data for the specified direction. + /// @param[in] s The span to check. + /// @param[in] dir The direction to check. [Limits: 0 <= value < 4] + /// @return The neighbor connection data for the specified direction, + /// or #RC_NOT_CONNECTED if there is no connection. + public static int rcGetCon(rcCompactSpan s, int dir) { + uint udir = (uint)dir; + int shift = (int)(udir * 6); + return (int)((s.con >> shift) & 0x3f); + } + + /// Gets the standard width (x-axis) offset for the specified direction. + /// @param[in] dir The direction. [Limits: 0 <= value < 4] + /// @return The width offset to apply to the current cell position to move + /// in the direction. + public static int rcGetDirOffsetX(int dir) { + int[] offset = new int[] { -1, 0, 1, 0, }; + return offset[dir & 0x03]; + } + + /// Gets the standard height (z-axis) offset for the specified direction. + /// @param[in] dir The direction. [Limits: 0 <= value < 4] + /// @return The height offset to apply to the current cell position to move + /// in the direction. + public static int rcGetDirOffsetY(int dir) { + int[] offset = new int[] { 0, 1, 0, -1 }; + return offset[dir & 0x03]; + } + + /// @} + /// @name Layer, Contour, Polymesh, and Detail Mesh Functions + /// @see rcHeightfieldLayer, rcContourSet, rcPolyMesh, rcPolyMeshDetail + /// @{ + + /// Builds a layer set from the specified compact heightfield. + /// @ingroup recast + /// @param[in,out] ctx The build context to use during the operation. + /// @param[in] chf A fully built compact heightfield. + /// @param[in] borderSize The size of the non-navigable border around the heightfield. [Limit: >=0] + /// [Units: vx] + /// @param[in] walkableHeight Minimum floor to 'ceiling' height that will still allow the floor area + /// to be considered walkable. [Limit: >= 3] [Units: vx] + /// @param[out] lset The resulting layer set. (Must be pre-allocated.) + /// @returns True if the operation completed successfully. + //bool rcBuildHeightfieldLayers(rcContext* ctx, rcCompactHeightfield& chf, + // const int borderSize, const int walkableHeight, + // rcHeightfieldLayerSet& lset); + + /// Builds a contour set from the region outlines in the provided compact heightfield. + /// @ingroup recast + /// @param[in,out] ctx The build context to use during the operation. + /// @param[in] chf A fully built compact heightfield. + /// @param[in] maxError The maximum distance a simplfied contour's border edges should deviate + /// the original raw contour. [Limit: >=0] [Units: wu] + /// @param[in] maxEdgeLen The maximum allowed length for contour edges along the border of the mesh. + /// [Limit: >=0] [Units: vx] + /// @param[out] cset The resulting contour set. (Must be pre-allocated.) + /// @param[in] buildFlags The build flags. (See: #rcBuildContoursFlags) + /// @returns True if the operation completed successfully. + //bool rcBuildContours(rcContext* ctx, rcCompactHeightfield& chf, + // const float maxError, const int maxEdgeLen, + // rcContourSet& cset, const int flags = RC_CONTOUR_TESS_WALL_EDGES); + + /// Builds a polygon mesh from the provided contours. + /// @ingroup recast + /// @param[in,out] ctx The build context to use during the operation. + /// @param[in] cset A fully built contour set. + /// @param[in] nvp The maximum number of vertices allowed for polygons generated during the + /// contour to polygon conversion process. [Limit: >= 3] + /// @param[out] mesh The resulting polygon mesh. (Must be re-allocated.) + /// @returns True if the operation completed successfully. + //bool rcBuildPolyMesh(rcContext* ctx, rcContourSet& cset, const int nvp, rcPolyMesh& mesh); + + /// Merges multiple polygon meshes into a single mesh. + /// @ingroup recast + /// @param[in,out] ctx The build context to use during the operation. + /// @param[in] meshes An array of polygon meshes to merge. [Size: @p nmeshes] + /// @param[in] nmeshes The number of polygon meshes in the meshes array. + /// @param[in] mesh The resulting polygon mesh. (Must be pre-allocated.) + /// @returns True if the operation completed successfully. + //bool rcMergePolyMeshes(rcContext* ctx, rcPolyMesh** meshes, const int nmeshes, rcPolyMesh& mesh); + + /// Builds a detail mesh from the provided polygon mesh. + /// @ingroup recast + /// @param[in,out] ctx The build context to use during the operation. + /// @param[in] mesh A fully built polygon mesh. + /// @param[in] chf The compact heightfield used to build the polygon mesh. + /// @param[in] sampleDist Sets the distance to use when samping the heightfield. [Limit: >=0] [Units: wu] + /// @param[in] sampleMaxError The maximum distance the detail mesh surface should deviate from + /// heightfield data. [Limit: >=0] [Units: wu] + /// @param[out] dmesh The resulting detail mesh. (Must be pre-allocated.) + /// @returns True if the operation completed successfully. + //bool rcBuildPolyMeshDetail(rcContext* ctx, const rcPolyMesh& mesh, const rcCompactHeightfield& chf, + // const float sampleDist, const float sampleMaxError, + // rcPolyMeshDetail& dmesh); + + /// Copies the poly mesh data from src to dst. + /// @ingroup recast + /// @param[in,out] ctx The build context to use during the operation. + /// @param[in] src The source mesh to copy from. + /// @param[out] dst The resulting detail mesh. (Must be pre-allocated, must be empty mesh.) + /// @returns True if the operation completed successfully. + //bool rcCopyPolyMesh(rcContext* ctx, const rcPolyMesh& src, rcPolyMesh& dst); + + /// Merges multiple detail meshes into a single detail mesh. + /// @ingroup recast + /// @param[in,out] ctx The build context to use during the operation. + /// @param[in] meshes An array of detail meshes to merge. [Size: @p nmeshes] + /// @param[in] nmeshes The number of detail meshes in the meshes array. + /// @param[out] mesh The resulting detail mesh. (Must be pre-allocated.) + /// @returns True if the operation completed successfully. + //bool rcMergePolyMeshDetails(rcContext* ctx, rcPolyMeshDetail** meshes, const int nmeshes, rcPolyMeshDetail& mesh); + + /// @} + +} + +/////////////////////////////////////////////////////////////////////////// + +// Due to the large amount of detail documentation for this file, +// the content normally located at the end of the header file has been separated +// out to a file in /Docs/Extern. diff --git a/Framework/RecastDetour/Recast/RecastArea.cs b/Framework/RecastDetour/Recast/RecastArea.cs new file mode 100644 index 000000000..84776e018 --- /dev/null +++ b/Framework/RecastDetour/Recast/RecastArea.cs @@ -0,0 +1,526 @@ +using System; +using System.Diagnostics; + +public static partial class Recast { + /// @par + /// + /// Basically, any spans that are closer to a boundary or obstruction than the specified radius + /// are marked as unwalkable. + /// + /// This method is usually called immediately after the heightfield has been built. + /// + /// @see rcCompactHeightfield, rcBuildCompactHeightfield, rcConfig::walkableRadius + public static bool rcErodeWalkableArea(rcContext ctx, int radius, rcCompactHeightfield chf) { + Debug.Assert(ctx != null, "rcContext is null"); + + int w = chf.width; + int h = chf.height; + + ctx.startTimer(rcTimerLabel.RC_TIMER_ERODE_AREA); + + byte[] dist = new byte[chf.spanCount];//(byte*)rcAlloc(sizeof(byte)*chf.spanCount, RC_ALLOC_TEMP); + if (dist == null) { + ctx.log(rcLogCategory.RC_LOG_ERROR, "erodeWalkableArea: Out of memory 'dist' " + chf.spanCount); + return false; + } + + // Init distance. + for (int i=0; i < chf.spanCount; ++i) { + dist[i] = 0xff; + } + // memset(dist, 0xff, sizeof(byte)*chf.spanCount); + + // Mark boundary cells. + for (int y = 0; y < h; ++y) { + for (int x = 0; x < w; ++x) { + rcCompactCell c = chf.cells[x + y * w]; + for (int i = (int)c.index, ni = (int)(c.index + c.count); i < ni; ++i) { + if (chf.areas[i] == RC_NULL_AREA) { + dist[i] = 0; + } else { + rcCompactSpan s = chf.spans[i]; + int nc = 0; + for (int dir = 0; dir < 4; ++dir) { + if (rcGetCon(s, dir) != RC_NOT_CONNECTED) { + int nx = x + rcGetDirOffsetX(dir); + int ny = y + rcGetDirOffsetY(dir); + int nidx = (int)chf.cells[nx + ny * w].index + rcGetCon(s, dir); + if (chf.areas[nidx] != RC_NULL_AREA) { + nc++; + } + } + } + // At least one missing neighbour. + if (nc != 4) + dist[i] = 0; + } + } + } + } + + byte nd = 0; + + // Pass 1 + for (int y = 0; y < h; ++y) { + for (int x = 0; x < w; ++x) { + rcCompactCell c = chf.cells[x + y * w]; + for (int i = (int)c.index, ni = (int)(c.index + c.count); i < ni; ++i) { + rcCompactSpan s = chf.spans[i]; + + if (rcGetCon(s, 0) != RC_NOT_CONNECTED) { + // (-1,0) + int ax = x + rcGetDirOffsetX(0); + int ay = y + rcGetDirOffsetY(0); + int ai = (int)chf.cells[ax + ay * w].index + rcGetCon(s, 0); + rcCompactSpan aSpan = chf.spans[ai]; + nd = (byte)Math.Min((int)dist[ai] + 2, 255); + if (nd < dist[i]) + dist[i] = nd; + + // (-1,-1) + if (rcGetCon(aSpan, 3) != RC_NOT_CONNECTED) { + int aax = ax + rcGetDirOffsetX(3); + int aay = ay + rcGetDirOffsetY(3); + int aai = (int)chf.cells[aax + aay * w].index + rcGetCon(aSpan, 3); + nd = (byte)Math.Min((int)dist[aai] + 3, 255); + if (nd < dist[i]) + dist[i] = nd; + } + } + if (rcGetCon(s, 3) != RC_NOT_CONNECTED) { + // (0,-1) + int ax = x + rcGetDirOffsetX(3); + int ay = y + rcGetDirOffsetY(3); + int ai = (int)chf.cells[ax + ay * w].index + rcGetCon(s, 3); + rcCompactSpan aSpan = chf.spans[ai]; + nd = (byte)Math.Min((int)dist[ai] + 2, 255); + if (nd < dist[i]) + dist[i] = nd; + + // (1,-1) + if (rcGetCon(aSpan, 2) != RC_NOT_CONNECTED) { + int aax = ax + rcGetDirOffsetX(2); + int aay = ay + rcGetDirOffsetY(2); + int aai = (int)chf.cells[aax + aay * w].index + rcGetCon(aSpan, 2); + nd = (byte)Math.Min((int)dist[aai] + 3, 255); + if (nd < dist[i]) + dist[i] = nd; + } + } + } + } + } + + // Pass 2 + for (int y = h - 1; y >= 0; --y) { + for (int x = w - 1; x >= 0; --x) { + rcCompactCell c = chf.cells[x + y * w]; + for (int i = (int)c.index, ni = (int)(c.index + c.count); i < ni; ++i) { + rcCompactSpan s = chf.spans[i]; + + if (rcGetCon(s, 2) != RC_NOT_CONNECTED) { + // (1,0) + int ax = x + rcGetDirOffsetX(2); + int ay = y + rcGetDirOffsetY(2); + int ai = (int)chf.cells[ax + ay * w].index + rcGetCon(s, 2); + rcCompactSpan aSpan = chf.spans[ai]; + nd = (byte)Math.Min((int)dist[ai] + 2, 255); + if (nd < dist[i]) + dist[i] = nd; + + // (1,1) + if (rcGetCon(aSpan, 1) != RC_NOT_CONNECTED) { + int aax = ax + rcGetDirOffsetX(1); + int aay = ay + rcGetDirOffsetY(1); + int aai = (int)chf.cells[aax + aay * w].index + rcGetCon(aSpan, 1); + nd = (byte)Math.Min((int)dist[aai] + 3, 255); + if (nd < dist[i]) + dist[i] = nd; + } + } + if (rcGetCon(s, 1) != RC_NOT_CONNECTED) { + // (0,1) + int ax = x + rcGetDirOffsetX(1); + int ay = y + rcGetDirOffsetY(1); + int ai = (int)chf.cells[ax + ay * w].index + rcGetCon(s, 1); + rcCompactSpan aSpan = chf.spans[ai]; + nd = (byte)Math.Min((int)dist[ai] + 2, 255); + if (nd < dist[i]) + dist[i] = nd; + + // (-1,1) + if (rcGetCon(aSpan, 0) != RC_NOT_CONNECTED) { + int aax = ax + rcGetDirOffsetX(0); + int aay = ay + rcGetDirOffsetY(0); + int aai = (int)chf.cells[aax + aay * w].index + rcGetCon(aSpan, 0); + nd = (byte)Math.Min((int)dist[aai] + 3, 255); + if (nd < dist[i]) + dist[i] = nd; + } + } + } + } + } + + byte thr = (byte)(radius * 2); + for (int i = 0; i < chf.spanCount; ++i) + if (dist[i] < thr) + chf.areas[i] = RC_NULL_AREA; + + ctx.stopTimer(rcTimerLabel.RC_TIMER_ERODE_AREA); + + return true; + } + + static void insertSort(byte[] a, int n) { + int i, j; + for (i = 1; i < n; i++) { + byte value = a[i]; + for (j = i - 1; j >= 0 && a[j] > value; j--) + a[j + 1] = a[j]; + a[j + 1] = value; + } + } + + /// @par + /// + /// This filter is usually applied after applying area id's using functions + /// such as #rcMarkBoxArea, #rcMarkConvexPolyArea, and #rcMarkCylinderArea. + /// + /// @see rcCompactHeightfield + public static bool rcMedianFilterWalkableArea(rcContext ctx, rcCompactHeightfield chf) { + Debug.Assert(ctx != null, "rcContext is null"); + + int w = chf.width; + int h = chf.height; + + ctx.startTimer(rcTimerLabel.RC_TIMER_MEDIAN_AREA); + + byte[] areas = new byte[chf.spanCount];//(byte*)rcAlloc(sizeof(byte)*chf.spanCount, RC_ALLOC_TEMP); + if (areas == null) { + ctx.log(rcLogCategory.RC_LOG_ERROR, "medianFilterWalkableArea: Out of memory 'areas' " + chf.spanCount); + return false; + } + + // Init distance. + for (int i = 0; i < chf.spanCount; ++i) { + areas[i] = 0xff; + } + //memset(areas, 0xff, sizeof(byte)*chf.spanCount); + + for (int y = 0; y < h; ++y) { + for (int x = 0; x < w; ++x) { + rcCompactCell c = chf.cells[x + y * w]; + for (int i = (int)c.index, ni = (int)(c.index + c.count); i < ni; ++i) { + rcCompactSpan s = chf.spans[i]; + if (chf.areas[i] == RC_NULL_AREA) { + areas[i] = chf.areas[i]; + continue; + } + + byte[] nei = new byte[9]; + for (int j = 0; j < 9; ++j) + nei[j] = chf.areas[i]; + + for (int dir = 0; dir < 4; ++dir) { + if (rcGetCon(s, dir) != RC_NOT_CONNECTED) { + int ax = x + rcGetDirOffsetX(dir); + int ay = y + rcGetDirOffsetY(dir); + int ai = (int)chf.cells[ax + ay * w].index + rcGetCon(s, dir); + if (chf.areas[ai] != RC_NULL_AREA) + nei[dir * 2 + 0] = chf.areas[ai]; + + rcCompactSpan aSpan = chf.spans[ai]; + int dir2 = (dir + 1) & 0x3; + if (rcGetCon(aSpan, dir2) != RC_NOT_CONNECTED) { + int ax2 = ax + rcGetDirOffsetX(dir2); + int ay2 = ay + rcGetDirOffsetY(dir2); + int ai2 = (int)chf.cells[ax2 + ay2 * w].index + rcGetCon(aSpan, dir2); + if (chf.areas[ai2] != RC_NULL_AREA) + nei[dir * 2 + 1] = chf.areas[ai2]; + } + } + } + insertSort(nei, 9); + areas[i] = nei[4]; + } + } + } + + chf.areas = areas; + //memcpy(chf.areas, areas, sizeof(byte)*chf.spanCount); + + //rcFree(areas); + + ctx.stopTimer(rcTimerLabel.RC_TIMER_MEDIAN_AREA); + + return true; + } + + /// @par + /// + /// The value of spacial parameters are in world units. + /// + /// @see rcCompactHeightfield, rcMedianFilterWalkableArea + public static void rcMarkBoxArea(rcContext ctx, float[] bmin, float[] bmax, byte areaId, + rcCompactHeightfield chf) { + Debug.Assert(ctx != null, "rcContext is null"); + + ctx.startTimer(rcTimerLabel.RC_TIMER_MARK_BOX_AREA); + + int minx = (int)((bmin[0] - chf.bmin[0]) / chf.cs); + int miny = (int)((bmin[1] - chf.bmin[1]) / chf.ch); + int minz = (int)((bmin[2] - chf.bmin[2]) / chf.cs); + int maxx = (int)((bmax[0] - chf.bmin[0]) / chf.cs); + int maxy = (int)((bmax[1] - chf.bmin[1]) / chf.ch); + int maxz = (int)((bmax[2] - chf.bmin[2]) / chf.cs); + + if (maxx < 0) return; + if (minx >= chf.width) return; + if (maxz < 0) return; + if (minz >= chf.height) return; + + if (minx < 0) minx = 0; + if (maxx >= chf.width) maxx = chf.width - 1; + if (minz < 0) minz = 0; + if (maxz >= chf.height) maxz = chf.height - 1; + + for (int z = minz; z <= maxz; ++z) { + for (int x = minx; x <= maxx; ++x) { + rcCompactCell c = chf.cells[x + z * chf.width]; + for (int i = (int)c.index, ni = (int)(c.index + c.count); i < ni; ++i) { + rcCompactSpan s = chf.spans[i]; + if ((int)s.y >= miny && (int)s.y <= maxy) { + if (chf.areas[i] != RC_NULL_AREA) + chf.areas[i] = areaId; + } + } + } + } + + ctx.stopTimer(rcTimerLabel.RC_TIMER_MARK_BOX_AREA); + + } + + + public static bool pointInPoly(int nvert, float[] verts, float[] p) { + bool c = false; + int i = 0; + int j = 0; + for (i = 0, j = nvert - 1; i < nvert; j = i++) { + int viStart = i * 3; + int vjStart = j * 3; + if (((verts[viStart + 2] > p[2]) != (verts[vjStart + 2] > p[2])) && + (p[0] < (verts[vjStart + 0] - verts[viStart + 0]) * (p[2] - verts[viStart + 2]) / (verts[vjStart + 2] - verts[viStart + 2]) + verts[viStart + 0])) { + c = !c; + } + } + return c; + } + + /// @par + /// + /// The value of spacial parameters are in world units. + /// + /// The y-values of the polygon vertices are ignored. So the polygon is effectively + /// projected onto the xz-plane at @p hmin, then extruded to @p hmax. + /// + /// @see rcCompactHeightfield, rcMedianFilterWalkableArea + public static void rcMarkConvexPolyArea(rcContext ctx, float[] verts, int nverts, + float hmin, float hmax, byte areaId, + rcCompactHeightfield chf) { + Debug.Assert(ctx != null, "rcContext is null"); + + ctx.startTimer(rcTimerLabel.RC_TIMER_MARK_CONVEXPOLY_AREA); + + float[] bmin = new float[3]; + float[] bmax = new float[3]; + rcVcopy(bmin, verts); + rcVcopy(bmax, verts); + for (int i = 1; i < nverts; ++i) { + int vStart = i * 3; + rcVmin(bmin, 0, verts, vStart); + rcVmax(bmax, 0, verts, vStart); + } + bmin[1] = hmin; + bmax[1] = hmax; + + int minx = (int)((bmin[0] - chf.bmin[0]) / chf.cs); + int miny = (int)((bmin[1] - chf.bmin[1]) / chf.ch); + int minz = (int)((bmin[2] - chf.bmin[2]) / chf.cs); + int maxx = (int)((bmax[0] - chf.bmin[0]) / chf.cs); + int maxy = (int)((bmax[1] - chf.bmin[1]) / chf.ch); + int maxz = (int)((bmax[2] - chf.bmin[2]) / chf.cs); + + if (maxx < 0) return; + if (minx >= chf.width) return; + if (maxz < 0) return; + if (minz >= chf.height) return; + + if (minx < 0) minx = 0; + if (maxx >= chf.width) maxx = chf.width - 1; + if (minz < 0) minz = 0; + if (maxz >= chf.height) maxz = chf.height - 1; + + + // TODO: Optimize. + for (int z = minz; z <= maxz; ++z) { + for (int x = minx; x <= maxx; ++x) { + rcCompactCell c = chf.cells[x + z * chf.width]; + for (int i = (int)c.index, ni = (int)(c.index + c.count); i < ni; ++i) { + rcCompactSpan s = chf.spans[i]; + if (chf.areas[i] == RC_NULL_AREA) + continue; + if ((int)s.y >= miny && (int)s.y <= maxy) { + float[] p = new float[3]; + p[0] = chf.bmin[0] + (x + 0.5f) * chf.cs; + p[1] = 0; + p[2] = chf.bmin[2] + (z + 0.5f) * chf.cs; + + if (pointInPoly(nverts, verts, p)) { + chf.areas[i] = areaId; + } + } + } + } + } + + ctx.stopTimer(rcTimerLabel.RC_TIMER_MARK_CONVEXPOLY_AREA); + } + + static int rcOffsetPoly(float[] verts, int nverts, float offset, + float[] outVerts, int maxOutVerts) { + const float MITER_LIMIT = 1.20f; + + int n = 0; + + for (int i = 0; i < nverts; i++) { + int a = (i + nverts - 1) % nverts; + int b = i; + int c = (i + 1) % nverts; + int vaStart = a * 3; + int vbStart = b * 3; + int vcStart = c * 3; + float dx0 = verts[vbStart + 0] - verts[vaStart + 0]; + float dy0 = verts[vbStart + 2] - verts[vaStart + 2]; + float d0 = dx0 * dx0 + dy0 * dy0; + if (d0 > 1e-6f) { + d0 = 1.0f / (float)Math.Sqrt(d0); + dx0 *= d0; + dy0 *= d0; + } + float dx1 = verts[vcStart + 0] - verts[vbStart + 0]; + float dy1 = verts[vcStart + 2] - verts[vbStart + 2]; + float d1 = dx1 * dx1 + dy1 * dy1; + if (d1 > 1e-6f) { + d1 = 1.0f / (float)Math.Sqrt(d1); + dx1 *= d1; + dy1 *= d1; + } + float dlx0 = -dy0; + float dly0 = dx0; + float dlx1 = -dy1; + float dly1 = dx1; + float cross = dx1 * dy0 - dx0 * dy1; + float dmx = (dlx0 + dlx1) * 0.5f; + float dmy = (dly0 + dly1) * 0.5f; + float dmr2 = dmx * dmx + dmy * dmy; + bool bevel = dmr2 * MITER_LIMIT * MITER_LIMIT < 1.0f; + if (dmr2 > 1e-6f) { + float scale = 1.0f / dmr2; + dmx *= scale; + dmy *= scale; + } + + if (bevel && cross < 0.0f) { + if (n + 2 >= maxOutVerts) + return 0; + float d = (1.0f - (dx0 * dx1 + dy0 * dy1)) * 0.5f; + outVerts[n * 3 + 0] = verts[vbStart + 0] + (-dlx0 + dx0 * d) * offset; + outVerts[n * 3 + 1] = verts[vbStart + 1]; + outVerts[n * 3 + 2] = verts[vbStart + 2] + (-dly0 + dy0 * d) * offset; + n++; + outVerts[n * 3 + 0] = verts[vbStart + 0] + (-dlx1 - dx1 * d) * offset; + outVerts[n * 3 + 1] = verts[vbStart + 1]; + outVerts[n * 3 + 2] = verts[vbStart + 2] + (-dly1 - dy1 * d) * offset; + n++; + } else { + if (n + 1 >= maxOutVerts) + return 0; + outVerts[n * 3 + 0] = verts[vbStart + 0] - dmx * offset; + outVerts[n * 3 + 1] = verts[vbStart + 1]; + outVerts[n * 3 + 2] = verts[vbStart + 2] - dmy * offset; + n++; + } + } + + return n; + } + + + /// @par + /// + /// The value of spacial parameters are in world units. + /// + /// @see rcCompactHeightfield, rcMedianFilterWalkableArea + static public void rcMarkCylinderArea(rcContext ctx, float[] pos, + float r, float h, byte areaId, + rcCompactHeightfield chf) { + Debug.Assert(ctx != null, "rcContext is null"); + + ctx.startTimer(rcTimerLabel.RC_TIMER_MARK_CYLINDER_AREA); + + float[] bmin = new float[3]; + float[] bmax = new float[3]; + bmin[0] = pos[0] - r; + bmin[1] = pos[1]; + bmin[2] = pos[2] - r; + bmax[0] = pos[0] + r; + bmax[1] = pos[1] + h; + bmax[2] = pos[2] + r; + float r2 = r * r; + + int minx = (int)((bmin[0] - chf.bmin[0]) / chf.cs); + int miny = (int)((bmin[1] - chf.bmin[1]) / chf.ch); + int minz = (int)((bmin[2] - chf.bmin[2]) / chf.cs); + int maxx = (int)((bmax[0] - chf.bmin[0]) / chf.cs); + int maxy = (int)((bmax[1] - chf.bmin[1]) / chf.ch); + int maxz = (int)((bmax[2] - chf.bmin[2]) / chf.cs); + + if (maxx < 0) return; + if (minx >= chf.width) return; + if (maxz < 0) return; + if (minz >= chf.height) return; + + if (minx < 0) minx = 0; + if (maxx >= chf.width) maxx = chf.width - 1; + if (minz < 0) minz = 0; + if (maxz >= chf.height) maxz = chf.height - 1; + + + for (int z = minz; z <= maxz; ++z) { + for (int x = minx; x <= maxx; ++x) { + rcCompactCell c = chf.cells[x + z * chf.width]; + for (int i = (int)c.index, ni = (int)(c.index + c.count); i < ni; ++i) { + rcCompactSpan s = chf.spans[i]; + + if (chf.areas[i] == RC_NULL_AREA) + continue; + + if ((int)s.y >= miny && (int)s.y <= maxy) { + float sx = chf.bmin[0] + (x + 0.5f) * chf.cs; + float sz = chf.bmin[2] + (z + 0.5f) * chf.cs; + float dx = sx - pos[0]; + float dz = sz - pos[2]; + + if (dx * dx + dz * dz < r2) { + chf.areas[i] = areaId; + } + } + } + } + } + + ctx.stopTimer(rcTimerLabel.RC_TIMER_MARK_CYLINDER_AREA); + } +} diff --git a/Framework/RecastDetour/Recast/RecastContour.cs b/Framework/RecastDetour/Recast/RecastContour.cs new file mode 100644 index 000000000..290c87106 --- /dev/null +++ b/Framework/RecastDetour/Recast/RecastContour.cs @@ -0,0 +1,860 @@ +using System; +using System.Collections.Generic; +using System.Diagnostics; + +public static partial class Recast{ +static int getCornerHeight(int x, int y, int i, int dir, + rcCompactHeightfield chf, + ref bool isBorderVertex) +{ + rcCompactSpan s = chf.spans[i]; + int ch = (int)s.y; + int dirp = (dir+1) & 0x3; + + uint[] regs = new uint[] {0,0,0,0}; + + // Combine region and area codes in order to prevent + // border vertices which are in between two areas to be removed. + regs[0] = (uint)( chf.spans[i].reg | (chf.areas[i] << 16) ); + + if (rcGetCon(s, dir) != RC_NOT_CONNECTED) + { + int ax = x + rcGetDirOffsetX(dir); + int ay = y + rcGetDirOffsetY(dir); + int ai = (int)chf.cells[ax+ay*chf.width].index + rcGetCon(s, dir); + rcCompactSpan aSpan = chf.spans[ai]; + ch = Math.Max(ch, (int)aSpan.y); + regs[1] = (uint)( chf.spans[ai].reg | (chf.areas[ai] << 16) ); + if (rcGetCon(aSpan, dirp) != RC_NOT_CONNECTED) + { + int ax2 = ax + rcGetDirOffsetX(dirp); + int ay2 = ay + rcGetDirOffsetY(dirp); + int ai2 = (int)chf.cells[ax2+ay2*chf.width].index + rcGetCon(aSpan, dirp); + rcCompactSpan as2 = chf.spans[ai2]; + ch = Math.Max(ch, (int)as2.y); + regs[2] = (uint)(chf.spans[ai2].reg | (chf.areas[ai2] << 16)); + } + } + if (rcGetCon(s, dirp) != RC_NOT_CONNECTED) + { + int ax = x + rcGetDirOffsetX(dirp); + int ay = y + rcGetDirOffsetY(dirp); + int ai = (int)chf.cells[ax+ay*chf.width].index + rcGetCon(s, dirp); + rcCompactSpan aSpan = chf.spans[ai]; + ch = Math.Max(ch, (int)aSpan.y); + regs[3] = (uint)(chf.spans[ai].reg | (chf.areas[ai] << 16)); + if (rcGetCon(aSpan, dir) != RC_NOT_CONNECTED) + { + int ax2 = ax + rcGetDirOffsetX(dir); + int ay2 = ay + rcGetDirOffsetY(dir); + int ai2 = (int)chf.cells[ax2+ay2*chf.width].index + rcGetCon(aSpan, dir); + rcCompactSpan as2 = chf.spans[ai2]; + ch = Math.Max(ch, (int)as2.y); + regs[2] = (uint)(chf.spans[ai2].reg | (chf.areas[ai2] << 16)); + } + } + + // Check if the vertex is special edge vertex, these vertices will be removed later. + for (int j = 0; j < 4; ++j) + { + int a = j; + int b = (j+1) & 0x3; + int c = (j+2) & 0x3; + int d = (j+3) & 0x3; + + // The vertex is a border vertex there are two same exterior cells in a row, + // followed by two interior cells and none of the regions are out of bounds. + bool twoSameExts = (regs[a] & regs[b] & RC_BORDER_REG) != 0 && regs[a] == regs[b]; + bool twoInts = ((regs[c] | regs[d]) & RC_BORDER_REG) == 0; + bool intsSameArea = (regs[c]>>16) == (regs[d]>>16); + bool noZeros = regs[a] != 0 && regs[b] != 0 && regs[c] != 0 && regs[d] != 0; + if (twoSameExts && twoInts && intsSameArea && noZeros) + { + isBorderVertex = true; + break; + } + } + + return ch; +} + +public static void walkContour(int x, int y, int i, + rcCompactHeightfield chf, + byte[] flags, List points) +{ + // Choose the first non-connected edge + byte dir = 0; + while ((flags[i] & (1 << dir)) == 0) + dir++; + + byte startDir = dir; + int starti = i; + + byte area = chf.areas[i]; + + int iter = 0; + while (++iter < 40000) + { + if ((flags[i] & (1 << dir)) != 0) + { + // Choose the edge corner + bool isBorderVertex = false; + bool isAreaBorder = false; + int px = x; + int py = getCornerHeight(x, y, i, dir, chf,ref isBorderVertex); + int pz = y; + switch(dir) + { + case 0: pz++; break; + case 1: px++; pz++; break; + case 2: px++; break; + } + int r = 0; + rcCompactSpan s = chf.spans[i]; + if (rcGetCon(s, dir) != RC_NOT_CONNECTED) + { + int ax = x + rcGetDirOffsetX(dir); + int ay = y + rcGetDirOffsetY(dir); + int ai = (int)chf.cells[ax+ay*chf.width].index + rcGetCon(s, dir); + r = (int)chf.spans[ai].reg; + if (area != chf.areas[ai]) + isAreaBorder = true; + } + if (isBorderVertex) + r |= RC_BORDER_VERTEX; + if (isAreaBorder) + r |= RC_AREA_BORDER; + points.Add(px); + points.Add(py); + points.Add(pz); + points.Add(r); + + flags[i] &= (byte)( ~(1 << dir) ); // Remove visited edges + dir = (byte)( (dir+1) & 0x3); // Rotate CW + } + else + { + int ni = -1; + int nx = x + rcGetDirOffsetX(dir); + int ny = y + rcGetDirOffsetY(dir); + rcCompactSpan s = chf.spans[i]; + if (rcGetCon(s, dir) != RC_NOT_CONNECTED) + { + rcCompactCell nc = chf.cells[nx+ny*chf.width]; + ni = (int)nc.index + rcGetCon(s, dir); + } + if (ni == -1) + { + // Should not happen. + return; + } + x = nx; + y = ny; + i = ni; + dir = (byte)((dir+3) & 0x3); // Rotate CCW + } + + if (starti == i && startDir == dir) + { + break; + } + } +} + +public static float distancePtSeg(int x, int z, + int px, int pz, + int qx, int qz) +{ +/* float pqx = (float)(qx - px); + float pqy = (float)(qy - py); + float pqz = (float)(qz - pz); + float dx = (float)(x - px); + float dy = (float)(y - py); + float dz = (float)(z - pz); + float d = pqx*pqx + pqy*pqy + pqz*pqz; + float t = pqx*dx + pqy*dy + pqz*dz; + if (d > 0) + t /= d; + if (t < 0) + t = 0; + else if (t > 1) + t = 1; + + dx = px + t*pqx - x; + dy = py + t*pqy - y; + dz = pz + t*pqz - z; + + return dx*dx + dy*dy + dz*dz;*/ + + float pqx = (float)(qx - px); + float pqz = (float)(qz - pz); + float dx = (float)(x - px); + float dz = (float)(z - pz); + float d = pqx*pqx + pqz*pqz; + float t = pqx*dx + pqz*dz; + if (d > 0) + t /= d; + if (t < 0) + t = 0; + else if (t > 1) + t = 1; + + dx = px + t*pqx - x; + dz = pz + t*pqz - z; + + return dx*dx + dz*dz; +} + +public static void simplifyContour(List points, List simplified, + float maxError, int maxEdgeLen, int buildFlags) +{ + // Add initial points. + bool hasConnections = false; + for (int i = 0; i < points.Count; i += 4) + { + if ((points[i+3] & RC_CONTOUR_REG_MASK) != 0) + { + hasConnections = true; + break; + } + } + + if (hasConnections) + { + // The contour has some portals to other regions. + // Add a new point to every location where the region changes. + for (int i = 0, ni = points.Count /4; i < ni; ++i) + { + int ii = (i+1) % ni; + bool differentRegs = (points[i*4+3] & RC_CONTOUR_REG_MASK) != (points[ii*4+3] & RC_CONTOUR_REG_MASK); + bool areaBorders = (points[i*4+3] & RC_AREA_BORDER) != (points[ii*4+3] & RC_AREA_BORDER); + if (differentRegs || areaBorders) + { + simplified.Add(points[i*4+0]); + simplified.Add(points[i*4+1]); + simplified.Add(points[i*4+2]); + simplified.Add(i); + } + } + } + + if (simplified.Count == 0) + { + // If there is no connections at all, + // create some initial points for the simplification process. + // Find lower-left and upper-right vertices of the contour. + int llx = points[0]; + int lly = points[1]; + int llz = points[2]; + int lli = 0; + int urx = points[0]; + int ury = points[1]; + int urz = points[2]; + int uri = 0; + for (int i = 0; i < points.Count; i += 4) + { + int x = points[i+0]; + int y = points[i+1]; + int z = points[i+2]; + if (x < llx || (x == llx && z < llz)) + { + llx = x; + lly = y; + llz = z; + lli = i/4; + } + if (x > urx || (x == urx && z > urz)) + { + urx = x; + ury = y; + urz = z; + uri = i/4; + } + } + simplified.Add(llx); + simplified.Add(lly); + simplified.Add(llz); + simplified.Add(lli); + + simplified.Add(urx); + simplified.Add(ury); + simplified.Add(urz); + simplified.Add(uri); + } + + // Add points until all raw points are within + // error tolerance to the simplified shape. + int pn = points.Count/4; + for (int i = 0; i < simplified.Count/4; ) + { + int ii = (i+1) % (simplified.Count/4); + + int ax = simplified[i*4+0]; + int az = simplified[i*4+2]; + int ai = simplified[i*4+3]; + + int bx = simplified[ii*4+0]; + int bz = simplified[ii*4+2]; + int bi = simplified[ii*4+3]; + + // Find maximum deviation from the segment. + float maxd = 0; + int maxi = -1; + int ci, cinc, endi; + + // Traverse the segment in lexilogical order so that the + // max deviation is calculated similarly when traversing + // opposite segments. + if (bx > ax || (bx == ax && bz > az)) + { + cinc = 1; + ci = (ai+cinc) % pn; + endi = bi; + } + else + { + cinc = pn-1; + ci = (bi+cinc) % pn; + endi = ai; + } + + // Tessellate only outer edges or edges between areas. + if ((points[ci*4+3] & RC_CONTOUR_REG_MASK) == 0 || + (points[ci*4+3] & RC_AREA_BORDER) != 0) + { + while (ci != endi) + { + float d = distancePtSeg(points[ci*4+0], points[ci*4+2], ax, az, bx, bz); + if (d > maxd) + { + maxd = d; + maxi = ci; + } + ci = (ci+cinc) % pn; + } + } + + + // If the max deviation is larger than accepted error, + // add new point, else continue to next segment. + if (maxi != -1 && maxd > (maxError*maxError)) + { + // Add space for the new point. + //simplified.resize(simplified.Count+4); + rccsResizeList(simplified, simplified.Count + 4); + int n = simplified.Count/4; + for (int j = n-1; j > i; --j) + { + simplified[j*4+0] = simplified[(j-1)*4+0]; + simplified[j*4+1] = simplified[(j-1)*4+1]; + simplified[j*4+2] = simplified[(j-1)*4+2]; + simplified[j*4+3] = simplified[(j-1)*4+3]; + } + // Add the point. + simplified[(i+1)*4+0] = points[maxi*4+0]; + simplified[(i+1)*4+1] = points[maxi*4+1]; + simplified[(i+1)*4+2] = points[maxi*4+2]; + simplified[(i+1)*4+3] = maxi; + } + else + { + ++i; + } + } + + // Split too long edges. + if (maxEdgeLen > 0 && (buildFlags & (int)(rcBuildContoursFlags.RC_CONTOUR_TESS_WALL_EDGES|rcBuildContoursFlags.RC_CONTOUR_TESS_AREA_EDGES)) != 0) + { + for (int i = 0; i < simplified.Count/4; ) + { + int ii = (i+1) % (simplified.Count/4); + + int ax = simplified[i*4+0]; + int az = simplified[i*4+2]; + int ai = simplified[i*4+3]; + + int bx = simplified[ii*4+0]; + int bz = simplified[ii*4+2]; + int bi = simplified[ii*4+3]; + + // Find maximum deviation from the segment. + int maxi = -1; + int ci = (ai+1) % pn; + + // Tessellate only outer edges or edges between areas. + bool tess = false; + // Wall edges. + if ((buildFlags & (int)rcBuildContoursFlags.RC_CONTOUR_TESS_WALL_EDGES) != 0 && (points[ci*4+3] & RC_CONTOUR_REG_MASK) == 0) + tess = true; + // Edges between areas. + if ((buildFlags & (int)rcBuildContoursFlags.RC_CONTOUR_TESS_AREA_EDGES) != 0 && (points[ci*4+3] & RC_AREA_BORDER) != 0) + tess = true; + + if (tess) + { + int dx = bx - ax; + int dz = bz - az; + if (dx*dx + dz*dz > maxEdgeLen*maxEdgeLen) + { + // Round based on the segments in lexilogical order so that the + // max tesselation is consistent regardles in which direction + // segments are traversed. + int n = bi < ai ? (bi+pn - ai) : (bi - ai); + if (n > 1) + { + if (bx > ax || (bx == ax && bz > az)) + maxi = (ai + n/2) % pn; + else + maxi = (ai + (n+1)/2) % pn; + } + } + } + + // If the max deviation is larger than accepted error, + // add new point, else continue to next segment. + if (maxi != -1) + { + // Add space for the new point. + rccsResizeList(simplified, simplified.Count + 4); + int n = simplified.Count/4; + for (int j = n-1; j > i; --j) + { + simplified[j*4+0] = simplified[(j-1)*4+0]; + simplified[j*4+1] = simplified[(j-1)*4+1]; + simplified[j*4+2] = simplified[(j-1)*4+2]; + simplified[j*4+3] = simplified[(j-1)*4+3]; + } + // Add the point. + simplified[(i+1)*4+0] = points[maxi*4+0]; + simplified[(i+1)*4+1] = points[maxi*4+1]; + simplified[(i+1)*4+2] = points[maxi*4+2]; + simplified[(i+1)*4+3] = maxi; + } + else + { + ++i; + } + } + } + + for (int i = 0; i < simplified.Count/4; ++i) + { + // The edge vertex flag is take from the current raw point, + // and the neighbour region is take from the next raw point. + int ai = (simplified[i*4+3]+1) % pn; + int bi = simplified[i*4+3]; + simplified[i*4+3] = (points[ai*4+3] & (RC_CONTOUR_REG_MASK|RC_AREA_BORDER)) | (points[bi*4+3] & RC_BORDER_VERTEX); + } + +} + +public static void removeDegenerateSegments(List simplified) +{ + // Remove adjacent vertices which are equal on xz-plane, + // or else the triangulator will get confused. + for (int i = 0; i < simplified.Count/4; ++i) + { + int ni = i+1; + if (ni >= (simplified.Count/4)) + ni = 0; + + if (simplified[i*4+0] == simplified[ni*4+0] && + simplified[i*4+2] == simplified[ni*4+2]) + { + // Degenerate segment, remove. + for (int j = i; j < simplified.Count/4-1; ++j) + { + simplified[j*4+0] = simplified[(j+1)*4+0]; + simplified[j*4+1] = simplified[(j+1)*4+1]; + simplified[j*4+2] = simplified[(j+1)*4+2]; + simplified[j*4+3] = simplified[(j+1)*4+3]; + } + //simplified.Capacity = (simplified.Count-4); + rccsResizeList(simplified, simplified.Count - 4); + } + } +} + +public static int calcAreaOfPolygon2D(int[] verts, int nverts) +{ + int area = 0; + for (int i = 0, j = nverts-1; i < nverts; j=i++) + { + int viStart = i * 4; + int vjStart = j * 4; + area += verts[viStart + 0] * verts[vjStart + 2] - verts[vjStart + 0] * verts[viStart + 2]; + } + return (area+1) / 2; +} + +public static bool ileft(int[] a, int[] b, int[] c) +{ + return (b[0] - a[0]) * (c[2] - a[2]) - (c[0] - a[0]) * (b[2] - a[2]) <= 0; +} + + +public static bool ileft(int[] a,int aStart, int[] b, int bStart, int[] c, int cStart) { + return (b[bStart + 0] - a[aStart + 0]) * (c[cStart + 2] - a[aStart + 2]) - (c[cStart + 0] - a[aStart + 0]) * (b[bStart + 2] - a[aStart + 2]) <= 0; +} + +public static void getClosestIndices(int[] vertsa, int nvertsa, + int[] vertsb, int nvertsb, + ref int ia, ref int ib) +{ + int closestDist = 0xfffffff; + ia = -1; + ib = -1; + for (int i = 0; i < nvertsa; ++i) + { + int i_n = (i+1) % nvertsa; + int ip = (i+nvertsa-1) % nvertsa; + int vaStart = i * 4; + int vanStart = i_n * 4; + int vapStart = ip * 4; + + for (int j = 0; j < nvertsb; ++j) + { + int vbStart = j * 4; + // vb must be "infront" of va. + if (ileft(vertsa,vapStart,vertsa,vaStart,vertsb,vbStart) && ileft(vertsa,vaStart,vertsa,vanStart,vertsb,vbStart)) + { + int dx = vertsb[vbStart+0] - vertsa[vaStart + 0]; + int dz = vertsb[vbStart+2] - vertsa[vaStart+2]; + int d = dx*dx + dz*dz; + if (d < closestDist) + { + ia = i; + ib = j; + closestDist = d; + } + } + } + } +} + +public static bool mergeContours(ref rcContour ca, ref rcContour cb, int ia, int ib) +{ + int maxVerts = ca.nverts + cb.nverts + 2; + int[] verts = new int[maxVerts * 4];//(int*)rcAlloc(sizeof(int)*maxVerts*4, RC_ALLOC_PERM); + if (verts == null) + return false; + + int nv = 0; + + // Copy contour A. + for (int i = 0; i <= ca.nverts; ++i) + { + //int* dst = &verts[nv*4]; + int dstIndex = nv*4; + int srcIndex = ((ia+i)%ca.nverts)*4; + for (int j=0;i<4;++i){ + verts[dstIndex + j] = ca.verts[srcIndex + j]; + } + nv++; + } + + // Copy contour B + for (int i = 0; i <= cb.nverts; ++i) + { + int dstIndex = nv*4; + int srcIndex = ((ib+i)%cb.nverts)*4; + //int* dst = &verts[nv*4]; + //const int* src = &cb.verts[((ib+i)%cb.nverts)*4]; + for (int j=0;j<4;++j){ + verts[dstIndex + j] = cb.verts[srcIndex + j]; + } + nv++; + } + + ca.verts = verts; + ca.nverts = nv; + + cb.verts = null; + cb.nverts = 0; + + return true; +} + +/// @par +/// +/// The raw contours will match the region outlines exactly. The @p maxError and @p maxEdgeLen +/// parameters control how closely the simplified contours will match the raw contours. +/// +/// Simplified contours are generated such that the vertices for portals between areas match up. +/// (They are considered mandatory vertices.) +/// +/// Setting @p maxEdgeLength to zero will disabled the edge length feature. +/// +/// See the #rcConfig documentation for more information on the configuration parameters. +/// +/// @see rcAllocContourSet, rcCompactHeightfield, rcContourSet, rcConfig +public static bool rcBuildContours(rcContext ctx, rcCompactHeightfield chf, + float maxError, int maxEdgeLen, + rcContourSet cset, int buildFlags) +{ + Debug.Assert(ctx != null, "rcContext is null"); + + int w = chf.width; + int h = chf.height; + int borderSize = chf.borderSize; + + ctx.startTimer(rcTimerLabel.RC_TIMER_BUILD_CONTOURS); + + rcVcopy(cset.bmin, chf.bmin); + rcVcopy(cset.bmax, chf.bmax); + if (borderSize > 0) + { + // If the heightfield was build with bordersize, remove the offset. + float pad = borderSize*chf.cs; + cset.bmin[0] += pad; + cset.bmin[2] += pad; + cset.bmax[0] -= pad; + cset.bmax[2] -= pad; + } + cset.cs = chf.cs; + cset.ch = chf.ch; + cset.width = chf.width - chf.borderSize*2; + cset.height = chf.height - chf.borderSize*2; + cset.borderSize = chf.borderSize; + + int maxContours = Math.Max((int)chf.maxRegions, 8); + //cset.conts = (rcContour*)rcAlloc(sizeof(rcContour)*maxContours, RC_ALLOC_PERM); + cset.conts = new rcContour[maxContours]; + //if (cset.conts == null) +// return false; + cset.nconts = 0; + + //rcScopedDelete flags = (byte*)rcAlloc(sizeof(byte)*chf.spanCount, RC_ALLOC_TEMP); + byte[] flags = new byte[chf.spanCount]; + if (flags == null) + { + ctx.log(rcLogCategory.RC_LOG_ERROR, "rcBuildContours: Out of memory 'flags' " + chf.spanCount); + return false; + } + + ctx.startTimer(rcTimerLabel.RC_TIMER_BUILD_CONTOURS_TRACE); + + // Mark boundaries. + for (int y = 0; y < h; ++y) + { + for (int x = 0; x < w; ++x) + { + rcCompactCell c = chf.cells[x+y*w]; + for (int i = (int)c.index, ni = (int)(c.index+c.count); i < ni; ++i) + { + byte res = 0; + rcCompactSpan s = chf.spans[i]; + if (chf.spans[i].reg == 0 || (chf.spans[i].reg & RC_BORDER_REG) != 0) + { + flags[i] = 0; + continue; + } + for (int dir = 0; dir < 4; ++dir) + { + ushort r = 0; + if (rcGetCon(s, dir) != RC_NOT_CONNECTED) + { + int ax = x + rcGetDirOffsetX(dir); + int ay = y + rcGetDirOffsetY(dir); + int ai = (int)chf.cells[ax+ay*w].index + rcGetCon(s, dir); + r = chf.spans[ai].reg; + } + if (r == chf.spans[i].reg) + res |= (byte)(1 << dir); + } + flags[i] = (byte)(res ^ 0xf); // Inverse, mark non connected edges. + } + } + } + + ctx.stopTimer(rcTimerLabel.RC_TIMER_BUILD_CONTOURS_TRACE); + + //List verts(256); + List verts = new List(); + verts.Capacity = 256; + //List simplified(64); + List simplified = new List(); + simplified.Capacity = 64; + + for (int y = 0; y < h; ++y) + { + for (int x = 0; x < w; ++x) + { + rcCompactCell c = chf.cells[x+y*w]; + for (int i = (int)c.index, ni = (int)(c.index+c.count); i < ni; ++i) + { + if (flags[i] == 0 || flags[i] == 0xf) + { + flags[i] = 0; + continue; + } + ushort reg = chf.spans[i].reg; + if (reg == 0 || (reg & RC_BORDER_REG) != 0) { + continue; + } + byte area = chf.areas[i]; + + //verts.resize(0); + //simplified.resize(0); + verts.Clear(); + simplified.Clear(); + + ctx.startTimer(rcTimerLabel.RC_TIMER_BUILD_CONTOURS_TRACE); + walkContour(x, y, i, chf, flags, verts); + ctx.stopTimer(rcTimerLabel.RC_TIMER_BUILD_CONTOURS_TRACE); + + ctx.startTimer(rcTimerLabel.RC_TIMER_BUILD_CONTOURS_SIMPLIFY); + simplifyContour(verts, simplified, maxError, maxEdgeLen, buildFlags); + removeDegenerateSegments(simplified); + ctx.stopTimer(rcTimerLabel.RC_TIMER_BUILD_CONTOURS_SIMPLIFY); + + + // Store region.contour remap info. + // Create contour. + if (simplified.Count/4 >= 3) + { + if (cset.nconts >= maxContours) + { + // Allocate more contours. + // This can happen when there are tiny holes in the heightfield. + int oldMax = maxContours; + maxContours *= 2; + rcContour[] newConts = new rcContour[maxContours];// (rcContour*)rcAlloc(sizeof(rcContour) * maxContours, RC_ALLOC_PERM); + for (int j = 0; j < cset.nconts; ++j) + { + newConts[j] = cset.conts[j]; + // Reset source pointers to prevent data deletion. + cset.conts[j].verts = null; + cset.conts[j].rverts = null; + } + //rcFree(cset.conts); + cset.conts = newConts; + + ctx.log(rcLogCategory.RC_LOG_WARNING, "rcBuildContours: Expanding max contours from " + oldMax + " to "+ maxContours); + } + + int contId = cset.nconts; + cset.nconts++; + rcContour cont = cset.conts[contId]; + + cont.nverts = simplified.Count/4; + cont.verts = new int[cont.nverts * 4]; //(int*)rcAlloc(sizeof(int)*cont.nverts*4, RC_ALLOC_PERM); + if (cont.verts == null) + { + ctx.log(rcLogCategory.RC_LOG_ERROR, "rcBuildContours: Out of memory 'verts' " + cont.nverts); + return false; + } + //memcpy(cont.verts, &simplified[0], sizeof(int)*cont.nverts*4); + for (int j = 0; j < cont.nverts * 4; ++j) { + cont.verts[j] = simplified[j]; + } + if (borderSize > 0) + { + // If the heightfield was build with bordersize, remove the offset. + for (int j = 0; j < cont.nverts; ++j) + { + //int* v = &cont.verts[j*4]; + cont.verts[j * 4] -= borderSize; + cont.verts[j*4 + 2] -= borderSize; + //v[0] -= borderSize; + //v[2] -= borderSize; + } + } + + cont.nrverts = verts.Count/4; + cont.rverts = new int[cont.nrverts * 4];//(int*)rcAlloc(sizeof(int)*cont.nrverts*4, RC_ALLOC_PERM); + if (cont.rverts == null) + { + ctx.log(rcLogCategory.RC_LOG_ERROR, "rcBuildContours: Out of memory 'rverts' " + cont.nrverts); + return false; + } + //memcpy(cont.rverts, &verts[0], sizeof(int)*cont.nrverts*4); + for (int j = 0; j < cont.nrverts * 4; ++j) { + cont.rverts[j] = verts[j]; + } + if (borderSize > 0) + { + // If the heightfield was build with bordersize, remove the offset. + for (int j = 0; j < cont.nrverts; ++j) + { + //int* v = &cont.rverts[j*4]; + cont.rverts[j * 4] -= borderSize; + cont.rverts[j * 4 + 2] -= borderSize; + } + } + +/* cont.cx = cont.cy = cont.cz = 0; + for (int i = 0; i < cont.nverts; ++i) + { + cont.cx += cont.verts[i*4+0]; + cont.cy += cont.verts[i*4+1]; + cont.cz += cont.verts[i*4+2]; + } + cont.cx /= cont.nverts; + cont.cy /= cont.nverts; + cont.cz /= cont.nverts;*/ + + cont.reg = reg; + cont.area = area; + + cset.conts[contId] = cont; + } + } + } + } + + // Check and merge droppings. + // Sometimes the previous algorithms can fail and create several contours + // per area. This pass will try to merge the holes into the main region. + for (int i = 0; i < cset.nconts; ++i) + { + rcContour cont = cset.conts[i]; + // Check if the contour is would backwards. + if (calcAreaOfPolygon2D(cont.verts, cont.nverts) < 0) + { + // Find another contour which has the same region ID. + int mergeIdx = -1; + for (int j = 0; j < cset.nconts; ++j) + { + if (i == j) continue; + if (cset.conts[j].nverts != 0 && cset.conts[j].reg == cont.reg) + { + // Make sure the polygon is correctly oriented. + if (calcAreaOfPolygon2D(cset.conts[j].verts, cset.conts[j].nverts) != 0) + { + mergeIdx = j; + break; + } + } + } + if (mergeIdx == -1) + { + ctx.log(rcLogCategory.RC_LOG_WARNING, "rcBuildContours: Could not find merge target for bad contour " + i); + } + else + { + rcContour mcont = cset.conts[mergeIdx]; + // Merge by closest points. + int ia = 0, ib = 0; + getClosestIndices(mcont.verts, mcont.nverts, cont.verts, cont.nverts, ref ia, ref ib); + if (ia == -1 || ib == -1) + { + ctx.log(rcLogCategory.RC_LOG_WARNING, "rcBuildContours: Failed to find merge points for " + i + " and " + mergeIdx); + continue; + } + if (!mergeContours(ref mcont,ref cont, ia, ib)) + { + ctx.log(rcLogCategory.RC_LOG_WARNING, "rcBuildContours: Failed to merge contours " + i + " and " + mergeIdx); + continue; + } + cset.conts[mergeIdx] = mcont; + cset.conts[i] = cont; + } + } + } + + ctx.stopTimer(rcTimerLabel.RC_TIMER_BUILD_CONTOURS); + + return true; +} +} \ No newline at end of file diff --git a/Framework/RecastDetour/Recast/RecastFilter.cs b/Framework/RecastDetour/Recast/RecastFilter.cs new file mode 100644 index 000000000..30c4bb339 --- /dev/null +++ b/Framework/RecastDetour/Recast/RecastFilter.cs @@ -0,0 +1,192 @@ +using System; +using System.Diagnostics; + +public static partial class Recast{ + /// @par + /// + /// Allows the formation of walkable regions that will flow over low lying + /// objects such as curbs, and up structures such as stairways. + /// + /// Two neighboring spans are walkable if: rcAbs(currentSpan.smax - neighborSpan.smax) < waklableClimb + /// + /// @warning Will override the effect of #rcFilterLedgeSpans. So if both filters are used, call + /// #rcFilterLedgeSpans after calling this filter. + /// + /// @see rcHeightfield, rcConfig + public static void rcFilterLowHangingWalkableObstacles(rcContext ctx, int walkableClimb, rcHeightfield solid) + { + Debug.Assert(ctx != null, "rcContext is null"); + + ctx.startTimer(rcTimerLabel.RC_TIMER_FILTER_LOW_OBSTACLES); + + int w = solid.width; + int h = solid.height; + + for (int y = 0; y < h; ++y) + { + for (int x = 0; x < w; ++x) + { + rcSpan ps = null; + bool previousWalkable = false; + byte previousArea = RC_NULL_AREA; + + for (rcSpan s = solid.spans[x + y*w]; s != null; ps = s, s = s.next) + { + bool walkable = s.area != RC_NULL_AREA; + // If current span is not walkable, but there is walkable + // span just below it, mark the span above it walkable too. + if (!walkable && previousWalkable) + { + if (Math.Abs((int)s.smax - (int)ps.smax) <= walkableClimb){ + s.area = previousArea; + } + } + // Copy walkable flag so that it cannot propagate + // past multiple non-walkable objects. + previousWalkable = walkable; + previousArea = s.area; + } + } + } + + ctx.stopTimer(rcTimerLabel.RC_TIMER_FILTER_LOW_OBSTACLES); + } + + /// @par + /// + /// A ledge is a span with one or more neighbors whose maximum is further away than @p walkableClimb + /// from the current span's maximum. + /// This method removes the impact of the overestimation of conservative voxelization + /// so the resulting mesh will not have regions hanging in the air over ledges. + /// + /// A span is a ledge if: rcAbs(currentSpan.smax - neighborSpan.smax) > walkableClimb + /// + /// @see rcHeightfield, rcConfig + public static void rcFilterLedgeSpans(rcContext ctx, int walkableHeight, int walkableClimb, + rcHeightfield solid) + { + Debug.Assert(ctx != null, "rcContext is null"); + + ctx.startTimer(rcTimerLabel.RC_TIMER_FILTER_BORDER); + + int w = solid.width; + int h = solid.height; + int MAX_HEIGHT = 0xffff; + + // Mark border spans. + for (int y = 0; y < h; ++y) + { + for (int x = 0; x < w; ++x) + { + for (rcSpan s = solid.spans[x + y*w]; s != null; s = s.next) + { + // Skip non walkable spans. + if (s.area == RC_NULL_AREA){ + continue; + } + + int bot = (int)(s.smax); + int top = s.next != null ? (int)(s.next.smin) : MAX_HEIGHT; + + // Find neighbours minimum height. + int minh = MAX_HEIGHT; + + // Min and max height of accessible neighbours. + int asmin = s.smax; + int asmax = s.smax; + + for (int dir = 0; dir < 4; ++dir) + { + int dx = x + rcGetDirOffsetX(dir); + int dy = y + rcGetDirOffsetY(dir); + // Skip neighbours which are out of bounds. + if (dx < 0 || dy < 0 || dx >= w || dy >= h) + { + minh = Math.Min(minh, -walkableClimb - bot); + continue; + } + + // From minus infinity to the first span. + rcSpan ns = solid.spans[dx + dy*w]; + int nbot = -walkableClimb; + int ntop = ns != null ? (int)ns.smin : MAX_HEIGHT; + // Skip neightbour if the gap between the spans is too small. + if (Math.Min(top,ntop) - Math.Max(bot,nbot) > walkableHeight) + minh = Math.Min(minh, nbot - bot); + + // Rest of the spans. + for (ns = solid.spans[dx + dy*w]; ns != null; ns = ns.next) + { + nbot = (int)ns.smax; + ntop = ns.next != null ? (int)ns.next.smin : MAX_HEIGHT; + // Skip neightbour if the gap between the spans is too small. + if (Math.Min(top,ntop) - Math.Max(bot,nbot) > walkableHeight) + { + minh = Math.Min(minh, nbot - bot); + + // Find min/max accessible neighbour height. + if (Math.Abs(nbot - bot) <= walkableClimb) + { + if (nbot < asmin) asmin = nbot; + if (nbot > asmax) asmax = nbot; + } + + } + } + } + + // The current span is close to a ledge if the drop to any + // neighbour span is less than the walkableClimb. + if (minh < -walkableClimb){ + s.area = RC_NULL_AREA; + } + + // If the difference between all neighbours is too large, + // we are at steep slope, mark the span as ledge. + if ((asmax - asmin) > walkableClimb) + { + s.area = RC_NULL_AREA; + } + } + } + } + + ctx.stopTimer(rcTimerLabel.RC_TIMER_FILTER_BORDER); + } + + /// @par + /// + /// For this filter, the clearance above the span is the distance from the span's + /// maximum to the next higher span's minimum. (Same grid column.) + /// + /// @see rcHeightfield, rcConfig + public static void rcFilterWalkableLowHeightSpans(rcContext ctx, int walkableHeight, rcHeightfield solid) + { + Debug.Assert(ctx != null, "rcContext is null"); + + ctx.startTimer(rcTimerLabel.RC_TIMER_FILTER_WALKABLE); + + int w = solid.width; + int h = solid.height; + int MAX_HEIGHT = 0xffff; + + // Remove walkable flag from spans which do not have enough + // space above them for the agent to stand there. + for (int y = 0; y < h; ++y) + { + for (int x = 0; x < w; ++x) + { + for (rcSpan s = solid.spans[x + y*w]; s != null; s = s.next) + { + int bot = (int)(s.smax); + int top = s.next != null ? (int)(s.next.smin) : MAX_HEIGHT; + if ((top - bot) <= walkableHeight) { + s.area = RC_NULL_AREA; + } + } + } + } + + ctx.stopTimer(rcTimerLabel.RC_TIMER_FILTER_WALKABLE); + } +} \ No newline at end of file diff --git a/Framework/RecastDetour/Recast/RecastLayers.cs b/Framework/RecastDetour/Recast/RecastLayers.cs new file mode 100644 index 000000000..f92067c74 --- /dev/null +++ b/Framework/RecastDetour/Recast/RecastLayers.cs @@ -0,0 +1,641 @@ +using System; +using System.Diagnostics; + +public static partial class Recast{ + + const int RC_MAX_LAYERS = RC_NOT_CONNECTED; + const int RC_MAX_NEIS = 16; + + public class rcLayerRegion + { + public byte[] layers = new byte[RC_MAX_LAYERS]; + public byte[] neis = new byte[RC_MAX_NEIS]; + public ushort ymin; + public ushort ymax; + public byte layerId; // Layer ID + public byte nlayers; // Layer count + public byte nneis; // Neighbour count + public byte baseFlag; // Flag indicating if the region is hte base of merged regions. + }; + + + public static void addUnique(byte[] a,ref byte an, byte v) + { + int n = (int)an; + for (int i = 0; i < n; ++i){ + if (a[i] == v){ + return; + } + } + a[an] = v; + an++; + } + + public static bool contains(byte[] a, byte an, byte v) + { + int n = (int)an; + for (int i = 0; i < n; ++i){ + if (a[i] == v){ + return true; + } + } + return false; + } + + public static bool overlapRange( ushort amin, ushort amax, + ushort bmin, ushort bmax) + { + return (amin > bmax || amax < bmin) ? false : true; + } + + + + public class rcLayerSweepSpan + { + public ushort ns; // number samples + public byte id; // region id + public byte nei; // neighbour id + }; + + /// @par + /// + /// See the #rcConfig documentation for more information on the configuration parameters. + /// + /// @see rcAllocHeightfieldLayerSet, rcCompactHeightfield, rcHeightfieldLayerSet, rcConfig + public static bool rcBuildHeightfieldLayers(rcContext ctx, rcCompactHeightfield chf, + int borderSize, int walkableHeight, + rcHeightfieldLayerSet lset) + { + Debug.Assert(ctx != null, "rcContext is null"); + + ctx.startTimer(rcTimerLabel.RC_TIMER_BUILD_LAYERS); + + int w = chf.width; + int h = chf.height; + + //rcScopedDelete srcReg = (byte*)rcAlloc(sizeof(byte)*chf.spanCount, RC_ALLOC_TEMP); + byte[] srcReg = new byte[chf.spanCount]; + if (srcReg == null) + { + ctx.log(rcLogCategory.RC_LOG_ERROR, "rcBuildHeightfieldLayers: Out of memory 'srcReg' " + chf.spanCount); + return false; + } + //memset(srcReg,0xff,sizeof(byte)*chf.spanCount); + for (int i=0;i sweeps = (rcLayerSweepSpan*)rcAlloc(sizeof(rcLayerSweepSpan)*nsweeps, RC_ALLOC_TEMP); + rcLayerSweepSpan[] sweeps = new rcLayerSweepSpan[nsweeps]; + if (sweeps == null) + { + ctx.log(rcLogCategory.RC_LOG_ERROR, "rcBuildHeightfieldLayers: Out of memory 'sweeps' " + nsweeps); + return false; + } + + + // Partition walkable area into monotone regions. + int[] prevCount = new int[256]; + byte regId = 0; + + for (int y = borderSize; y < h-borderSize; ++y) + { + //memset to 0 is done by C# alloc + //memset(prevCount,0,sizeof(int)*regId); + + byte sweepId = 0; + + for (int x = borderSize; x < w-borderSize; ++x) + { + rcCompactCell c = chf.cells[x+y*w]; + + for (int i = (int)c.index, ni = (int)(c.index+c.count); i < ni; ++i) + { + rcCompactSpan s = chf.spans[i]; + if (chf.areas[i] == RC_NULL_AREA) continue; + + byte sid = 0xff; + + // -x + if (rcGetCon(s, 0) != RC_NOT_CONNECTED) + { + int ax = x + rcGetDirOffsetX(0); + int ay = y + rcGetDirOffsetY(0); + int ai = (int)chf.cells[ax+ay*w].index + rcGetCon(s, 0); + if (chf.areas[ai] != RC_NULL_AREA && srcReg[ai] != 0xff) + sid = srcReg[ai]; + } + + if (sid == 0xff) + { + sid = sweepId++; + sweeps[sid].nei = (byte)0xff; + sweeps[sid].ns = 0; + } + + // -y + if (rcGetCon(s,3) != RC_NOT_CONNECTED) + { + int ax = x + rcGetDirOffsetX(3); + int ay = y + rcGetDirOffsetY(3); + int ai = (int)chf.cells[ax+ay*w].index + rcGetCon(s, 3); + byte nr = srcReg[ai]; + if (nr != 0xff) + { + // Set neighbour when first valid neighbour is encoutered. + if (sweeps[sid].ns == 0) + sweeps[sid].nei = nr; + + if (sweeps[sid].nei == nr) + { + // Update existing neighbour + sweeps[sid].ns++; + prevCount[nr]++; + } + else + { + // This is hit if there is nore than one neighbour. + // Invalidate the neighbour. + sweeps[sid].nei = 0xff; + } + } + } + + srcReg[i] = sid; + } + } + + // Create unique ID. + for (int i = 0; i < sweepId; ++i) + { + // If the neighbour is set and there is only one continuous connection to it, + // the sweep will be merged with the previous one, else new region is created. + if (sweeps[i].nei != 0xff && prevCount[sweeps[i].nei] == (int)sweeps[i].ns) + { + sweeps[i].id = sweeps[i].nei; + } + else + { + if (regId == 255) + { + ctx.log(rcLogCategory.RC_LOG_ERROR, "rcBuildHeightfieldLayers: Region ID overflow."); + return false; + } + sweeps[i].id = regId++; + } + } + + // Remap local sweep ids to region ids. + for (int x = borderSize; x < w-borderSize; ++x) + { + rcCompactCell c = chf.cells[x+y*w]; + for (int i = (int)c.index, ni = (int)(c.index+c.count); i < ni; ++i) + { + if (srcReg[i] != 0xff) + srcReg[i] = sweeps[srcReg[i]].id; + } + } + } + + // Allocate and init layer regions. + int nregs = (int)regId; + //rcScopedDelete regs = (rcLayerRegion*)rcAlloc(sizeof(rcLayerRegion)*nregs, RC_ALLOC_TEMP); + rcLayerRegion[] regs = new rcLayerRegion[nregs]; + if (regs == null) + { + ctx.log(rcLogCategory.RC_LOG_ERROR, "rcBuildHeightfieldLayers: Out of memory 'regs' " + nregs); + return false; + } + //memset(regs, 0, sizeof(rcLayerRegion)*nregs); + for (int i = 0; i < nregs; ++i) + { + regs[i].layerId = 0xff; + regs[i].ymin = 0xffff; + regs[i].ymax = 0; + } + + // Find region neighbours and overlapping regions. + for (int y = 0; y < h; ++y) + { + for (int x = 0; x < w; ++x) + { + rcCompactCell c = chf.cells[x+y*w]; + + byte[] lregs = new byte[RC_MAX_LAYERS]; + int nlregs = 0; + + for (int i = (int)c.index, ni = (int)(c.index+c.count); i < ni; ++i) + { + rcCompactSpan s = chf.spans[i]; + byte ri = srcReg[i]; + if (ri == 0xff){ + continue; + } + + regs[ri].ymin = Math.Min(regs[ri].ymin, s.y); + regs[ri].ymax = Math.Max(regs[ri].ymax, s.y); + + // Collect all region layers. + if (nlregs < RC_MAX_LAYERS) + lregs[nlregs++] = ri; + + // Update neighbours + for (int dir = 0; dir < 4; ++dir) + { + if (rcGetCon(s, dir) != RC_NOT_CONNECTED) + { + int ax = x + rcGetDirOffsetX(dir); + int ay = y + rcGetDirOffsetY(dir); + int ai = (int)chf.cells[ax+ay*w].index + rcGetCon(s, dir); + byte rai = srcReg[ai]; + if (rai != 0xff && rai != ri){ + addUnique(regs[ri].neis,ref regs[ri].nneis, rai); + } + } + } + + } + + // Update overlapping regions. + for (int i = 0; i < nlregs-1; ++i) + { + for (int j = i+1; j < nlregs; ++j) + { + if (lregs[i] != lregs[j]) + { + rcLayerRegion ri = regs[lregs[i]]; + rcLayerRegion rj = regs[lregs[j]]; + addUnique(ri.layers,ref ri.nlayers, lregs[j]); + addUnique(rj.layers,ref rj.nlayers, lregs[i]); + } + } + } + + } + } + + // Create 2D layers from regions. + byte layerId = 0; + + const int MAX_STACK = 64; + byte[] stack = new byte[MAX_STACK]; + int nstack = 0; + + for (int i = 0; i < nregs; ++i) + { + rcLayerRegion root = regs[i]; + // Skip alreadu visited. + if (root.layerId != 0xff){ + continue; + } + + // Start search. + root.layerId = layerId; + root.baseFlag = 1; + + nstack = 0; + stack[nstack++] = (byte)i; + + while (nstack != 0) + { + // Pop front + rcLayerRegion reg = regs[stack[0]]; + nstack--; + for (int j = 0; j < nstack; ++j){ + stack[j] = stack[j+1]; + } + + int nneis = (int)reg.nneis; + for (int j = 0; j < nneis; ++j) + { + byte nei = reg.neis[j]; + rcLayerRegion regn = regs[nei]; + // Skip already visited. + if (regn.layerId != 0xff){ + continue; + } + // Skip if the neighbour is overlapping root region. + if (contains(root.layers, root.nlayers, nei)){ + continue; + } + // Skip if the height range would become too large. + int ymin = Math.Min(root.ymin, regn.ymin); + int ymax = Math.Max(root.ymax, regn.ymax); + if ((ymax - ymin) >= 255){ + continue; + } + + if (nstack < MAX_STACK) + { + // Deepen + stack[nstack++] = (byte)nei; + + // Mark layer id + regn.layerId = layerId; + // Merge current layers to root. + for (int k = 0; k < regn.nlayers; ++k){ + addUnique(root.layers,ref root.nlayers, regn.layers[k]); + } + root.ymin = Math.Min(root.ymin, regn.ymin); + root.ymax = Math.Max(root.ymax, regn.ymax); + } + } + } + + layerId++; + } + + // Merge non-overlapping regions that are close in height. + ushort mergeHeight = (ushort)(walkableHeight * 4); + + for (int i = 0; i < nregs; ++i) + { + rcLayerRegion ri = regs[i]; + if (ri.baseFlag == 0){ + continue; + } + + byte newId = ri.layerId; + + for (;;) + { + byte oldId = 0xff; + + for (int j = 0; j < nregs; ++j) + { + if (i == j){ + continue; + } + rcLayerRegion rj = regs[j]; + if (rj.baseFlag == 0){ + continue; + } + + // Skip if teh regions are not close to each other. + if (!overlapRange(ri.ymin, + (ushort)(ri.ymax + mergeHeight), + rj.ymin, + (ushort)(rj.ymax + mergeHeight))){ + continue; + } + // Skip if the height range would become too large. + int ymin = Math.Min(ri.ymin, rj.ymin); + int ymax = Math.Max(ri.ymax, rj.ymax); + if ((ymax - ymin) >= 255){ + continue; + } + + // Make sure that there is no overlap when mergin 'ri' and 'rj'. + bool overlap = false; + // Iterate over all regions which have the same layerId as 'rj' + for (int k = 0; k < nregs; ++k) + { + if (regs[k].layerId != rj.layerId) + continue; + // Check if region 'k' is overlapping region 'ri' + // Index to 'regs' is the same as region id. + if (contains(ri.layers,ri.nlayers, (byte)k)) + { + overlap = true; + break; + } + } + // Cannot merge of regions overlap. + if (overlap) + continue; + + // Can merge i and j. + oldId = rj.layerId; + break; + } + + // Could not find anything to merge with, stop. + if (oldId == 0xff) + break; + + // Merge + for (int j = 0; j < nregs; ++j) + { + rcLayerRegion rj = regs[j]; + if (rj.layerId == oldId) + { + rj.baseFlag = 0; + // Remap layerIds. + rj.layerId = newId; + // Add overlaid layers from 'rj' to 'ri'. + for (int k = 0; k < rj.nlayers; ++k){ + addUnique(ri.layers,ref ri.nlayers, rj.layers[k]); + } + // Update heigh bounds. + ri.ymin = Math.Min(ri.ymin, rj.ymin); + ri.ymax = Math.Max(ri.ymax, rj.ymax); + } + } + } + } + + // Compact layerIds + byte[] remap = new byte[256]; + //memset(remap, 0, 256); + + // Find number of unique layers. + layerId = 0; + for (int i = 0; i < nregs; ++i){ + remap[regs[i].layerId] = 1; + } + for (int i = 0; i < 256; ++i) + { + if (remap[i] != 0){ + remap[i] = layerId++; + } + else{ + remap[i] = 0xff; + } + } + // Remap ids. + for (int i = 0; i < nregs; ++i){ + regs[i].layerId = remap[regs[i].layerId]; + } + + // No layers, return empty. + if (layerId == 0) + { + ctx.stopTimer(rcTimerLabel.RC_TIMER_BUILD_LAYERS); + return true; + } + + // Create layers. + Debug.Assert(lset.layers == null,"Assert lset.layers == 0"); + + int lw = w - borderSize*2; + int lh = h - borderSize*2; + + // Build contracted bbox for layers. + float[] bmin = new float[3]; + float[] bmax = new float[3]; + rcVcopy(bmin, chf.bmin); + rcVcopy(bmax, chf.bmax); + bmin[0] += borderSize*chf.cs; + bmin[2] += borderSize*chf.cs; + bmax[0] -= borderSize*chf.cs; + bmax[2] -= borderSize*chf.cs; + + lset.nlayers = (int)layerId; + + //lset.layers = (rcHeightfieldLayer*)rcAlloc(sizeof(rcHeightfieldLayer)*lset.nlayers, RC_ALLOC_PERM); + lset.layers = new rcHeightfieldLayer[lset.nlayers]; + if (lset.layers == null) + { + ctx.log(rcLogCategory.RC_LOG_ERROR, "rcBuildHeightfieldLayers: Out of memory 'layers' " + lset.nlayers); + return false; + } + //memset(lset.layers, 0, sizeof(rcHeightfieldLayer)*lset.nlayers); + + + // Store layers. + for (int i = 0; i < lset.nlayers; ++i) + { + byte curId = (byte)i; + + // Allocate memory for the current layer. + rcHeightfieldLayer layer = lset.layers[i]; + //memset(layer, 0, sizeof(rcHeightfieldLayer)); + + int gridSize = sizeof(byte)*lw*lh; + + layer.heights = new byte[gridSize];//(byte*)rcAlloc(gridSize, RC_ALLOC_PERM); + if (layer.heights == null) + { + ctx.log(rcLogCategory.RC_LOG_ERROR, "rcBuildHeightfieldLayers: Out of memory 'heights' " + gridSize); + return false; + } + //memset(layer.heights, 0xff, gridSize); + for (int j=0;j hmin) + layer.heights[idx] = Math.Max(layer.heights[idx], (byte)(aSpan.y - hmin)); + } + // Valid connection mask + if (chf.areas[ai] != RC_NULL_AREA && lid == alid) + { + int nx = ax - borderSize; + int ny = ay - borderSize; + if (nx >= 0 && ny >= 0 && nx < lw && ny < lh) + con |= (byte)(1< layer.maxx) + layer.minx = layer.maxx = 0; + if (layer.miny > layer.maxy) + layer.miny = layer.maxy = 0; + } + + ctx.stopTimer(rcTimerLabel.RC_TIMER_BUILD_LAYERS); + + return true; + } +} \ No newline at end of file diff --git a/Framework/RecastDetour/Recast/RecastMesh.cs b/Framework/RecastDetour/Recast/RecastMesh.cs new file mode 100644 index 000000000..054f60b88 --- /dev/null +++ b/Framework/RecastDetour/Recast/RecastMesh.cs @@ -0,0 +1,1513 @@ +using System; +using System.Diagnostics; + +public static partial class Recast { + + public class rcEdge { + public ushort[] vert = new ushort[2]; + public ushort[] polyEdge = new ushort[2]; + public ushort[] poly = new ushort[2]; + }; + + public static bool buildMeshAdjacency(ushort[] polys, int npolys, + int nverts, int vertsPerPoly) { + // Based on code by Eric Lengyel from: + // http://www.terathon.com/code/edges.php + + int maxEdgeCount = npolys * vertsPerPoly; + ushort[] firstEdge = new ushort[nverts + maxEdgeCount];//(ushort*)rcAlloc(sizeof(ushort)*(nverts + maxEdgeCount), RC_ALLOC_TEMP); + if (firstEdge == null) + return false; + //ushort* nextEdge = firstEdge + nverts; + int nextEdgeIndex = nverts; + int edgeCount = 0; + + //rcEdge* edges = (rcEdge*)rcAlloc(sizeof(rcEdge)*maxEdgeCount, RC_ALLOC_TEMP); + rcEdge[] edges = new rcEdge[maxEdgeCount]; + rccsArrayItemsCreate(edges); + if (edges == null) { + //rcFree(firstEdge); + firstEdge = null; + return false; + } + + for (int i = 0; i < nverts; i++) { + firstEdge[i] = RC_MESH_NULL_IDX; + } + + for (int i = 0; i < npolys; ++i) { + int tIndex = i * vertsPerPoly * 2; + //ushort* t = &polys[i*vertsPerPoly*2]; + for (int j = 0; j < vertsPerPoly; ++j) { + if (polys[tIndex + j] == RC_MESH_NULL_IDX) break; + ushort v0 = polys[tIndex + j]; + ushort v1 = (j + 1 >= vertsPerPoly || polys[tIndex + j + 1] == RC_MESH_NULL_IDX) ? polys[tIndex + 0] : polys[tIndex + j + 1]; + if (v0 < v1) { + rcEdge edge = edges[edgeCount]; + edge.vert[0] = v0; + edge.vert[1] = v1; + edge.poly[0] = (ushort)i; + edge.polyEdge[0] = (ushort)j; + edge.poly[1] = (ushort)i; + edge.polyEdge[1] = 0; + // Insert edge + firstEdge[nextEdgeIndex + edgeCount] = firstEdge[v0]; + firstEdge[v0] = (ushort)edgeCount; + edgeCount++; + } + } + } + + for (int i = 0; i < npolys; ++i) { + //ushort* t = &polys[i*vertsPerPoly*2]; + int tIndex = i * vertsPerPoly * 2; + for (int j = 0; j < vertsPerPoly; ++j) { + if (polys[tIndex + j] == RC_MESH_NULL_IDX) break; + ushort v0 = polys[tIndex + j]; + ushort v1 = (j + 1 >= vertsPerPoly || polys[tIndex + j + 1] == RC_MESH_NULL_IDX) ? polys[tIndex + 0] : polys[tIndex + j + 1]; + if (v0 > v1) { + for (ushort e = firstEdge[v1]; e != RC_MESH_NULL_IDX; e = firstEdge[nextEdgeIndex + e]) { + rcEdge edge = edges[e]; + if (edge.vert[1] == v0 && edge.poly[0] == edge.poly[1]) { + edge.poly[1] = (ushort)i; + edge.polyEdge[1] = (ushort)j; + break; + } + } + } + } + } + + // Store adjacency + for (int i = 0; i < edgeCount; ++i) { + rcEdge e = edges[i]; + if (e.poly[0] != e.poly[1]) { + //ushort* p0 = &polys[e.poly[0]*vertsPerPoly*2]; + //ushort* p1 = &polys[e.poly[1]*vertsPerPoly*2]; + //p0[vertsPerPoly + e.polyEdge[0]] = e.poly[1]; + //p1[vertsPerPoly + e.polyEdge[1]] = e.poly[0]; + polys[e.poly[0] * vertsPerPoly * 2 + vertsPerPoly + e.polyEdge[0]] = e.poly[1]; + polys[e.poly[1] * vertsPerPoly * 2 + vertsPerPoly + e.polyEdge[1]] = e.poly[0]; + } + } + + //rcFree(firstEdge); + //rcFree(edges); + + return true; + } + + + const int VERTEX_BUCKET_COUNT = (1 << 12); + + public static int computeVertexHash(int x, int y, int z) { + uint h1 = 0x8da6b343; // Large multiplicative constants; + uint h2 = 0xd8163841; // here arbitrarily chosen primes + uint h3 = 0xcb1ab31f; + uint n = (uint)(h1 * x + h2 * y + h3 * z); + return (int)(n & (VERTEX_BUCKET_COUNT - 1)); + } + + public static ushort addVertex(ushort x, ushort y, ushort z, + ushort[] verts, int[] firstVert, int[] nextVert, ref int nv) { + int bucket = computeVertexHash(x, 0, z); + int i = firstVert[bucket]; + + while (i != -1) { + //const ushort* v = &verts[i*3]; + int vIndex = i * 3; + if (verts[vIndex] == x && (Math.Abs(verts[vIndex + 1] - y) <= 2) && verts[vIndex + 2] == z) { + return (ushort)i; + } + i = nextVert[i]; // next + } + + // Could not find, create new. + i = nv; nv++; + //ushort[] v = &verts[i*3]; + int vInd = i * 3; + verts[vInd] = x; + verts[vInd + 1] = y; + verts[vInd + 2] = z; + nextVert[i] = firstVert[bucket]; + firstVert[bucket] = i; + + return (ushort)i; + } + + public static int prev(int i, int n) { + return i - 1 >= 0 ? i - 1 : n - 1; + } + public static int next(int i, int n) { + return i + 1 < n ? i + 1 : 0; + } + + public static int area2(int[] a, int[] b, int[] c) { + return (b[0] - a[0]) * (c[2] - a[2]) - (c[0] - a[0]) * (b[2] - a[2]); + } + public static int area2(int[] a, int aStart, int[] b, int bStart, int[] c, int cStart) { + return (b[bStart + 0] - a[aStart + 0]) * (c[cStart + 2] - a[aStart + 2]) - (c[cStart + 0] - a[aStart + 0]) * (b[bStart + 2] - a[aStart + 2]); + } + + // Exclusive or: true iff exactly one argument is true. + // The arguments are negated to ensure that they are 0/1 + // values. Then the bitwise Xor operator may apply. + // (This idea is due to Michael Baldwin.) + public static bool xorb(bool x, bool y) { + return !x ^ !y; + } + + // Returns true iff c is strictly to the left of the directed + // line through a to b. + public static bool left(int[] a, int[] b, int[] c) { + return area2(a, b, c) < 0; + } + public static bool left(int[] a, int aStart, int[] b, int bStart, int[] c, int cStart) { + return area2(a, aStart, b, bStart, c, cStart) < 0; + } + + public static bool leftOn(int[] a, int[] b, int[] c) { + return area2(a, b, c) <= 0; + } + public static bool leftOn(int[] a, int aStart, int[] b, int bStart, int[] c, int cStart) { + return area2(a, aStart, b, bStart, c, cStart) <= 0; + } + + public static bool collinear(int[] a, int[] b, int[] c) { + return area2(a, b, c) == 0; + } + public static bool collinear(int[] a, int aStart, int[] b, int bStart, int[] c, int cStart) { + return area2(a, aStart, b, bStart, c, cStart) == 0; + } + + // Returns true iff ab properly intersects cd: they share + // a point interior to both segments. The properness of the + // intersection is ensured by using strict leftness. + public static bool intersectProp(int[] a, int[] b, int[] c, int[] d) { + // Eliminate improper cases. + if (collinear(a, b, c) || collinear(a, b, d) || + collinear(c, d, a) || collinear(c, d, b)) + return false; + + return xorb(left(a, b, c), left(a, b, d)) && xorb(left(c, d, a), left(c, d, b)); + } + public static bool intersectProp(int[] a, int aStart, int[] b, int bStart, int[] c, int cStart, int[] d, int dStart) { + // Eliminate improper cases. + if (collinear(a, aStart, b, bStart, c, cStart) || collinear(a, aStart, b, bStart, d, dStart) || + collinear(c, cStart, d, dStart, a, aStart) || collinear(c, cStart, d, dStart, b, bStart)) + return false; + + return xorb(left(a, aStart, b, bStart, c, cStart), left(a, aStart, b, bStart, d, dStart)) && xorb(left(c, cStart, d, dStart, a, aStart), left(c, cStart, d, dStart, b, bStart)); + } + + // Returns T iff (a,b,c) are collinear and point c lies + // on the closed segement ab. + public static bool between(int[] a, int[] b, int[] c) { + if (!collinear(a, b, c)) + return false; + // If ab not vertical, check betweenness on x; else on y. + if (a[0] != b[0]) + return ((a[0] <= c[0]) && (c[0] <= b[0])) || ((a[0] >= c[0]) && (c[0] >= b[0])); + else + return ((a[2] <= c[2]) && (c[2] <= b[2])) || ((a[2] >= c[2]) && (c[2] >= b[2])); + } + public static bool between(int[] a, int aStart, int[] b, int bStart, int[] c, int cStart) { + if (!collinear(a, aStart, b, bStart, c, cStart)) + return false; + // If ab not vertical, check betweenness on x; else on y. + if (a[aStart+0] != b[bStart+0]) + return ((a[aStart+0] <= c[cStart+0]) && (c[cStart+0] <= b[bStart+0])) || ((a[aStart+0] >= c[cStart+0]) && (c[cStart+0] >= b[bStart+0])); + else + return ((a[aStart+2] <= c[cStart+2]) && (c[cStart+2] <= b[bStart+2])) || ((a[aStart+2] >= c[cStart+2]) && (c[cStart+2] >= b[bStart+2])); + } + + + // Returns true iff segments ab and cd intersect, properly or improperly. + public static bool intersect(int[] a, int[] b, int[] c, int[] d) { + if (intersectProp(a, b, c, d)) + return true; + else if (between(a, b, c) || between(a, b, d) || + between(c, d, a) || between(c, d, b)) + return true; + else + return false; + } + public static bool intersect(int[] a, int aStart, int[] b, int bStart, int[] c, int cStart, int[] d, int dStart) { + if (intersectProp(a, aStart, b, bStart, c, cStart, d, dStart)) + return true; + else if (between(a, aStart, b, bStart, c, cStart) || between(a, aStart, b, bStart, d, dStart) || + between(c, cStart, d, dStart, a, aStart) || between(c, cStart, d, dStart, b, bStart)) + return true; + else + return false; + } + + public static bool vequal(int[] a, int[] b) { + return a[0] == b[0] && a[2] == b[2]; + } + public static bool vequal(int[] a, int aStart, int[] b, int bStart) { + return a[aStart + 0] == b[bStart + 0] && a[aStart + 2] == b[bStart + 2]; + } + + + // Returns T iff (v_i, v_j) is a proper internal *or* external + // diagonal of P, *ignoring edges incident to v_i and v_j*. + public static bool diagonalie(int i, int j, int n, int[] verts, int[] indices) { + //int* d0 = &verts[(indices[i] & 0x0fffffff) * 4]; + //int* d1 = &verts[(indices[j] & 0x0fffffff) * 4]; + int d0Start = (indices[i] & 0x0fffffff) * 4; + int d1Start = (indices[j] & 0x0fffffff) * 4; + + // For each edge (k,k+1) of P + for (int k = 0; k < n; k++) { + int k1 = next(k, n); + // Skip edges incident to i or j + if (!((k == i) || (k1 == i) || (k == j) || (k1 == j))) { + int p0Start = (indices[k] & 0x0fffffff) * 4; + int p1Start = (indices[k1] & 0x0fffffff) * 4; + + if (vequal(verts, d0Start, verts, p0Start) || vequal(verts,d1Start, verts, p0Start) || vequal(verts, d0Start, verts, p1Start) || vequal(verts, d1Start, verts, p1Start)) + continue; + + if (intersect(verts, d0Start,verts, d1Start,verts, p0Start, verts, p1Start)) + return false; + } + } + return true; + } + + // Returns true iff the diagonal (i,j) is strictly internal to the + // polygon P in the neighborhood of the i endpoint. + public static bool inCone(int i, int j, int n, int[] verts, int[] indices) { + int piStart = (indices[i] & 0x0fffffff) * 4; + int pjStart = (indices[j] & 0x0fffffff) * 4; + int pi1Start = (indices[next(i, n)] & 0x0fffffff) * 4; + int pin1Start = (indices[prev(i, n)] & 0x0fffffff) * 4; + + // If P[i] is a convex vertex [ i+1 left or on (i-1,i) ]. + if (leftOn(verts, pin1Start,verts, piStart,verts, pi1Start)) + return left(verts,piStart,verts, pjStart,verts, pin1Start) && left(verts,pjStart,verts, piStart,verts, pi1Start); + // Assume (i-1,i,i+1) not collinear. + // else P[i] is reflex. + return !(leftOn(verts,piStart,verts, pjStart,verts, pi1Start) && leftOn(verts, pjStart,verts, piStart, verts, pin1Start)); + } + + // Returns T iff (v_i, v_j) is a proper internal + // diagonal of P. + public static bool diagonal(int i, int j, int n, int[] verts, int[] indices) { + return inCone(i, j, n, verts, indices) && diagonalie(i, j, n, verts, indices); + } + + public static int triangulate(int n, int[] verts, int[] indices, int[] tris) { + int ntris = 0; + //int* dst = tris; + //int[] dst = tris; + int dstIndex = 0; + + int removeVertexFlag = 0; + unchecked { + removeVertexFlag = (int)0x80000000; + } + + // The last bit of the index is used to indicate if the vertex can be removed. + for (int i = 0; i < n; i++) { + int _i1 = next(i, n); + int _i2 = next(_i1, n); + if (diagonal(i, _i2, n, verts, indices)) { + unchecked { + indices[_i1] |= removeVertexFlag; + } + } + } + + while (n > 3) { + int minLen = -1; + int mini = -1; + for (int i = 0; i < n; i++) { + int _i1 = next(i, n); + if ((indices[_i1] & removeVertexFlag) != 0) { + int p0Start = (indices[i] & 0x0fffffff) * 4; + int p2Start = (indices[next(_i1, n)] & 0x0fffffff) * 4; + int dx = verts[p2Start+0] - verts[p0Start+0]; + int dy = verts[p2Start+2] - verts[p0Start+2]; + int len = dx * dx + dy * dy; + + if (minLen < 0 || len < minLen) { + minLen = len; + mini = i; + } + } + } + + if (mini == -1) { + // Should not happen. + /* printf("mini == -1 ntris=%d n=%d\n", ntris, n); + for (int i = 0; i < n; i++) + { + printf("%d ", indices[i] & 0x0fffffff); + } + printf("\n");*/ + return -ntris; + } + + int i0 = mini; + int i1 = next(i0, n); + int i2 = next(i1, n); + + tris[dstIndex] = indices[i0] & 0x0fffffff; + ++dstIndex; + tris[dstIndex] = indices[i1] & 0x0fffffff; + ++dstIndex; + tris[dstIndex] = indices[i2] & 0x0fffffff; + ++dstIndex; + + ntris++; + + // Removes P[i1] by copying P[i+1]...P[n-1] left one index. + n--; + for (int k = i1; k < n; k++) + indices[k] = indices[k + 1]; + + if (i1 >= n) i1 = 0; + i0 = prev(i1, n); + // Update diagonal flags. + if (diagonal(prev(i0, n), i1, n, verts, indices)) + indices[i0] |= removeVertexFlag; + else + indices[i0] &= 0x0fffffff; + + if (diagonal(i0, next(i1, n), n, verts, indices)) + indices[i1] |= removeVertexFlag; + else + indices[i1] &= 0x0fffffff; + } + + // Append the remaining triangle. + tris[dstIndex] = indices[0] & 0x0fffffff; + ++dstIndex; + tris[dstIndex] = indices[1] & 0x0fffffff; + ++dstIndex; + tris[dstIndex] = indices[2] & 0x0fffffff; + ++dstIndex; + ntris++; + + return ntris; + } + + public static int countPolyVerts(ushort[] p, int pStart, int nvp) { + for (int i = 0; i < nvp; ++i) { + if (p[pStart + i] == RC_MESH_NULL_IDX) { + return i; + } + } + return nvp; + } + + public static bool uleft(ushort[] a, ushort[] b, ushort[] c) { + return ((int)b[0] - (int)a[0]) * ((int)c[2] - (int)a[2]) - + ((int)c[0] - (int)a[0]) * ((int)b[2] - (int)a[2]) < 0; + } + + public static bool uleft(ushort[] a, int aStart, ushort[] b, int bStart, ushort[] c, int cStart) { + return ((int)b[bStart + 0] - (int)a[aStart + 0]) * ((int)c[cStart + 2] - (int)a[aStart + 2]) - + ((int)c[cStart + 0] - (int)a[aStart + 0]) * ((int)b[bStart + 2] - (int)a[aStart + 2]) < 0; + } + + public static int getPolyMergeValue(ushort[] pa, int paStart, ushort[] pb, int pbStart, + ushort[] verts, ref int ea, ref int eb, + int nvp) { + int na = countPolyVerts(pa, paStart, nvp); + int nb = countPolyVerts(pb, pbStart, nvp); + + // If the merged polygon would be too big, do not merge. + if (na + nb - 2 > nvp) + return -1; + + // Check if the polygons share an edge. + ea = -1; + eb = -1; + + for (int i = 0; i < na; ++i) { + ushort va0 = pa[paStart + i]; + ushort va1 = pa[paStart + ((i + 1) % na)]; + if (va0 > va1) { + rcSwap(ref va0, ref va1); + } + for (int j = 0; j < nb; ++j) { + ushort vb0 = pb[pbStart + j]; + ushort vb1 = pb[pbStart + ((j + 1) % nb)]; + if (vb0 > vb1) + rcSwap(ref vb0, ref vb1); + if (va0 == vb0 && va1 == vb1) { + ea = i; + eb = j; + break; + } + } + } + + // No common edge, cannot merge. + if (ea == -1 || eb == -1) + return -1; + + // Check to see if the merged polygon would be convex. + ushort va, vb, vc; + + va = pa[paStart + ((ea + na - 1) % na)]; + vb = pa[paStart + ea]; + vc = pb[pbStart + ((eb + 2) % nb)]; + if (!uleft(verts, va * 3, verts, vb * 3, verts, vc * 3)) + return -1; + + va = pb[pbStart + ((eb + nb - 1) % nb)]; + vb = pb[pbStart + eb]; + vc = pa[paStart + ((ea + 2) % na)]; + if (!uleft(verts, va * 3, verts, vb * 3, verts, vc * 3)) + return -1; + + va = pa[paStart + ea]; + vb = pa[paStart + ((ea + 1) % na)]; + + int dx = (int)verts[va * 3 + 0] - (int)verts[vb * 3 + 0]; + int dy = (int)verts[va * 3 + 2] - (int)verts[vb * 3 + 2]; + + return dx * dx + dy * dy; + } + + public static void mergePolys(ushort[] pa, int paStart, ushort[] pb, int pbStart, int ea, int eb, + ushort[] tmp, int tmpStart, int nvp) { + int na = countPolyVerts(pa, paStart, nvp); + int nb = countPolyVerts(pb, pbStart, nvp); + + // Merge polygons. + //memset(tmp, 0xff, sizeof(ushort)*nvp); + for (int i = 0; i < nvp; ++i) { + tmp[tmpStart + i] = 0xffff; + } + int n = 0; + // Add pa + for (int i = 0; i < na - 1; ++i) + tmp[tmpStart + n++] = pa[paStart + ((ea + 1 + i) % na)]; + // Add pb + for (int i = 0; i < nb - 1; ++i) + tmp[tmpStart + n++] = pb[pbStart + ((eb + 1 + i) % nb)]; + + //memcpy(pa, tmp, sizeof(ushort)*nvp); + for (int i = 0; i < nvp; ++i) { + pa[paStart + i] = tmp[tmpStart + i]; + } + } + + + public static void pushFront(int v, int[] arr, ref int an) { + an++; + for (int i = an - 1; i > 0; --i) { + arr[i] = arr[i - 1]; + } + arr[0] = v; + } + + public static void pushBack(int v, int[] arr, ref int an) { + arr[an] = v; + an++; + } + + public static bool canRemoveVertex(rcContext ctx, rcPolyMesh mesh, ushort rem) { + int nvp = mesh.nvp; + + // Count number of polygons to remove. + int numRemovedVerts = 0; + int numTouchedVerts = 0; + int numRemainingEdges = 0; + for (int i = 0; i < mesh.npolys; ++i) { + //ushort* p = &mesh.polys[i*nvp*2]; + int pIndex = i * nvp * 2; + int nv = countPolyVerts(mesh.polys, i * nvp * 2, nvp); + int numRemoved = 0; + int numVerts = 0; + for (int j = 0; j < nv; ++j) { + if (mesh.polys[pIndex + j] == rem) { + numTouchedVerts++; + numRemoved++; + } + numVerts++; + } + if (numRemoved != 0) { + numRemovedVerts += numRemoved; + numRemainingEdges += numVerts - (numRemoved + 1); + } + } + + // There would be too few edges remaining to create a polygon. + // This can happen for example when a tip of a triangle is marked + // as deletion, but there are no other polys that share the vertex. + // In this case, the vertex should not be removed. + if (numRemainingEdges <= 2) + return false; + + // Find edges which share the removed vertex. + int maxEdges = numTouchedVerts * 2; + int nedges = 0; + //rcScopedDelete edges = (int*)rcAlloc(sizeof(int)*maxEdges*3, RC_ALLOC_TEMP); + int[] edges = new int[maxEdges * 3]; + if (edges == null) { + ctx.log(rcLogCategory.RC_LOG_WARNING, "canRemoveVertex: Out of memory 'edges' " + maxEdges * 3); + return false; + } + + for (int i = 0; i < mesh.npolys; ++i) { + //ushort* p = &mesh.polys[i*nvp*2]; + int pIndex = i * nvp * 2; + int nv = countPolyVerts(mesh.polys, pIndex, nvp); + + // Collect edges which touches the removed vertex. + for (int j = 0, k = nv - 1; j < nv; k = j++) { + if (mesh.polys[pIndex + j] == rem || mesh.polys[pIndex + k] == rem) { + // Arrange edge so that a=rem. + int a = mesh.polys[pIndex + j], b = mesh.polys[pIndex + k]; + if (b == rem) { + rcSwap(ref a, ref b); + } + + // Check if the edge exists + bool exists = false; + for (int m = 0; m < nedges; ++m) { + //int* e = &edges[m*3]; + int eIndex = m * 3; + if (edges[eIndex + 1] == b) { + // Exists, increment vertex share count. + edges[eIndex + 2]++; + exists = true; + } + } + // Add new edge. + if (!exists) { + //int* e = &edges[nedges*3]; + int eIndex = nedges * 3; + edges[eIndex + 0] = a; + edges[eIndex + 1] = b; + edges[eIndex + 2] = 1; + nedges++; + } + } + } + } + + // There should be no more than 2 open edges. + // This catches the case that two non-adjacent polygons + // share the removed vertex. In that case, do not remove the vertex. + int numOpenEdges = 0; + for (int i = 0; i < nedges; ++i) { + if (edges[i * 3 + 2] < 2) + numOpenEdges++; + } + if (numOpenEdges > 2) + return false; + + return true; + } + + public static bool removeVertex(rcContext ctx, rcPolyMesh mesh, ushort rem, int maxTris) { + int nvp = mesh.nvp; + + // Count number of polygons to remove. + int numRemovedVerts = 0; + for (int i = 0; i < mesh.npolys; ++i) { + //ushort* p = &mesh.polys[i*nvp*2]; + int pIndex = i * nvp * 2; + int nv = countPolyVerts(mesh.polys, pIndex, nvp); + for (int j = 0; j < nv; ++j) { + if (mesh.polys[pIndex + j] == rem) + numRemovedVerts++; + } + } + + int nedges = 0; + //rcScopedDelete edges = (int*)rcAlloc(sizeof(int)*numRemovedVerts*nvp*4, RC_ALLOC_TEMP); + int[] edges = new int[numRemovedVerts * nvp * 4]; + if (edges == null) { + ctx.log(rcLogCategory.RC_LOG_WARNING, "removeVertex: Out of memory 'edges' " + numRemovedVerts * nvp * 4); + return false; + } + + int nhole = 0; + //rcScopedDelete hole = (int*)rcAlloc(sizeof(int)*numRemovedVerts*nvp, RC_ALLOC_TEMP); + int[] hole = new int[numRemovedVerts * nvp]; + if (hole == null) { + ctx.log(rcLogCategory.RC_LOG_WARNING, "removeVertex: Out of memory 'hole' " + numRemovedVerts * nvp); + return false; + } + + int nhreg = 0; + //rcScopedDelete hreg = (int*)rcAlloc(sizeof(int)*numRemovedVerts*nvp, RC_ALLOC_TEMP); + int[] hreg = new int[numRemovedVerts * nvp]; + if (hreg == null) { + ctx.log(rcLogCategory.RC_LOG_WARNING, "removeVertex: Out of memory 'hreg' " + numRemovedVerts * nvp); + return false; + } + + int nharea = 0; + //rcScopedDelete harea = (int*)rcAlloc(sizeof(int)*numRemovedVerts*nvp, RC_ALLOC_TEMP); + int[] harea = new int[numRemovedVerts * nvp]; + if (harea == null) { + ctx.log(rcLogCategory.RC_LOG_WARNING, "removeVertex: Out of memory 'harea' " + numRemovedVerts * nvp); + return false; + } + + for (int i = 0; i < mesh.npolys; ++i) { + //ushort* p = &mesh.polys[i*nvp*2]; + int pIndex = i * nvp * 2; + int nv = countPolyVerts(mesh.polys, pIndex, nvp); + bool hasRem = false; + for (int j = 0; j < nv; ++j) + if (mesh.polys[pIndex + j] == rem) hasRem = true; + if (hasRem) { + // Collect edges which does not touch the removed vertex. + for (int j = 0, k = nv - 1; j < nv; k = j++) { + if (mesh.polys[pIndex + j] != rem && mesh.polys[pIndex + k] != rem) { + //int[] e = &edges[nedges*4]; + int eIndex = nedges * 4; + edges[eIndex + 0] = mesh.polys[pIndex + k]; + edges[eIndex + 1] = mesh.polys[pIndex + j]; + edges[eIndex + 2] = mesh.regs[i]; + edges[eIndex + 3] = mesh.areas[i]; + nedges++; + } + } + // Remove the polygon. + //ushort* p2 = &mesh.polys[(mesh.npolys-1)*nvp*2]; + int p2Index = (mesh.npolys - 1) * nvp * 2; + if (mesh.polys[pIndex] != mesh.polys[p2Index]) { + //memcpy(p,p2,sizeof(ushort)*nvp); + for (int j = 0; j < nvp; ++j) { + mesh.polys[pIndex + j] = mesh.polys[p2Index + j]; + } + } + //memset(p+nvp,0xff,sizeof(ushort)*nvp); + for (int j = 0; j < nvp; ++j) { + mesh.polys[pIndex + nvp + j] = 0xffff; + } + mesh.regs[i] = mesh.regs[mesh.npolys - 1]; + mesh.areas[i] = mesh.areas[mesh.npolys - 1]; + mesh.npolys--; + --i; + } + } + + // Remove vertex. + for (int i = (int)rem; i < mesh.nverts; ++i) { + mesh.verts[i * 3 + 0] = mesh.verts[(i + 1) * 3 + 0]; + mesh.verts[i * 3 + 1] = mesh.verts[(i + 1) * 3 + 1]; + mesh.verts[i * 3 + 2] = mesh.verts[(i + 1) * 3 + 2]; + } + mesh.nverts--; + + // Adjust indices to match the removed vertex layout. + for (int i = 0; i < mesh.npolys; ++i) { + //ushort* p = &mesh.polys[i*nvp*2]; + int pIndex = i * nvp * 2; + int nv = countPolyVerts(mesh.polys, i * nvp * 2, nvp); + for (int j = 0; j < nv; ++j) { + if (mesh.polys[pIndex + j] > rem) { + mesh.polys[pIndex + j]--; + } + } + } + for (int i = 0; i < nedges; ++i) { + if (edges[i * 4 + 0] > rem) { + edges[i * 4 + 0]--; + } + if (edges[i * 4 + 1] > rem) { + edges[i * 4 + 1]--; + } + } + + if (nedges == 0) { + return true; + } + + // Start with one vertex, keep appending connected + // segments to the start and end of the hole. + pushBack(edges[0], hole, ref nhole); + pushBack(edges[2], hreg, ref nhreg); + pushBack(edges[3], harea, ref nharea); + + while (nedges != 0) { + bool match = false; + + for (int i = 0; i < nedges; ++i) { + int ea = edges[i * 4 + 0]; + int eb = edges[i * 4 + 1]; + int r = edges[i * 4 + 2]; + int a = edges[i * 4 + 3]; + bool add = false; + if (hole[0] == eb) { + // The segment matches the beginning of the hole boundary. + pushFront(ea, hole, ref nhole); + pushFront(r, hreg, ref nhreg); + pushFront(a, harea, ref nharea); + add = true; + } else if (hole[nhole - 1] == ea) { + // The segment matches the end of the hole boundary. + pushBack(eb, hole, ref nhole); + pushBack(r, hreg, ref nhreg); + pushBack(a, harea, ref nharea); + add = true; + } + if (add) { + // The edge segment was added, remove it. + edges[i * 4 + 0] = edges[(nedges - 1) * 4 + 0]; + edges[i * 4 + 1] = edges[(nedges - 1) * 4 + 1]; + edges[i * 4 + 2] = edges[(nedges - 1) * 4 + 2]; + edges[i * 4 + 3] = edges[(nedges - 1) * 4 + 3]; + --nedges; + match = true; + --i; + } + } + + if (!match) + break; + } + + //rcScopedDelete tris = (int*)rcAlloc(sizeof(int)*nhole*3, RC_ALLOC_TEMP); + int[] tris = new int[nhole * 3]; + if (tris == null) { + ctx.log(rcLogCategory.RC_LOG_WARNING, "removeVertex: Out of memory 'tris' " + nhole * 3); + return false; + } + + //rcScopedDelete tverts = (int*)rcAlloc(sizeof(int)*nhole*4, RC_ALLOC_TEMP); + int[] tverts = new int[nhole * 4]; + if (tverts == null) { + ctx.log(rcLogCategory.RC_LOG_WARNING, "removeVertex: Out of memory 'tverts' " + nhole * 4); + return false; + } + + //rcScopedDelete thole = (int*)rcAlloc(sizeof(int)*nhole, RC_ALLOC_TEMP); + int[] thole = new int[nhole]; + if (tverts == null) { + ctx.log(rcLogCategory.RC_LOG_WARNING, "removeVertex: Out of memory 'thole' " + nhole); + return false; + } + + // Generate temp vertex array for triangulation. + for (int i = 0; i < nhole; ++i) { + int pi = hole[i]; + tverts[i * 4 + 0] = mesh.verts[pi * 3 + 0]; + tverts[i * 4 + 1] = mesh.verts[pi * 3 + 1]; + tverts[i * 4 + 2] = mesh.verts[pi * 3 + 2]; + tverts[i * 4 + 3] = 0; + thole[i] = i; + } + + // Triangulate the hole. + int ntris = triangulate(nhole, tverts, thole, tris); + if (ntris < 0) { + ntris = -ntris; + ctx.log(rcLogCategory.RC_LOG_WARNING, "removeVertex: triangulate() returned bad results."); + } + + // Merge the hole triangles back to polygons. + //rcScopedDelete polys = (ushort*)rcAlloc(sizeof(ushort)*(ntris+1)*nvp, RC_ALLOC_TEMP); + ushort[] polys = new ushort[(ntris + 1) * nvp]; + if (polys == null) { + ctx.log(rcLogCategory.RC_LOG_ERROR, "removeVertex: Out of memory 'polys' " + (ntris + 1) * nvp); + return false; + } + //rcScopedDelete pregs = (ushort*)rcAlloc(sizeof(ushort)*ntris, RC_ALLOC_TEMP); + ushort[] pregs = new ushort[ntris]; + if (pregs == null) { + ctx.log(rcLogCategory.RC_LOG_ERROR, "removeVertex: Out of memory 'pregs' " + ntris); + return false; + } + //rcScopedDelete pareas = (byte*)rcAlloc(sizeof(byte)*ntris, RC_ALLOC_TEMP); + byte[] pareas = new byte[ntris]; + if (pregs == null) { + ctx.log(rcLogCategory.RC_LOG_ERROR, "removeVertex: Out of memory 'pareas' " + ntris); + return false; + } + + int tmpPolyIndex = ntris * nvp; + //ushort* tmpPoly = &polys[ntris*nvp]; + + // Build initial polygons. + int npolys = 0; + //memset(polys, 0xff, ntris*nvp*sizeof(ushort)); + for (int i = 0; i < ntris * nvp; ++i) { + polys[i] = 0xffff; + } + for (int j = 0; j < ntris; ++j) { + //int* t = &tris[j*3]; + int tIndex = j * 3; + if (tris[tIndex + 0] != tris[tIndex + 1] && tris[tIndex + 0] != tris[tIndex + 2] && tris[tIndex + 1] != tris[tIndex + 2]) { + polys[npolys * nvp + 0] = (ushort)hole[tris[tIndex + 0]]; + polys[npolys * nvp + 1] = (ushort)hole[tris[tIndex + 1]]; + polys[npolys * nvp + 2] = (ushort)hole[tris[tIndex + 2]]; + pregs[npolys] = (ushort)hreg[tris[tIndex + 0]]; + pareas[npolys] = (byte)harea[tris[tIndex + 0]]; + npolys++; + } + } + if (npolys == 0) { + return true; + } + + // Merge polygons. + if (nvp > 3) { + for (; ; ) { + // Find best polygons to merge. + int bestMergeVal = 0; + int bestPa = 0, bestPb = 0, bestEa = 0, bestEb = 0; + + for (int j = 0; j < npolys - 1; ++j) { + int pjIndex = j * nvp; + //ushort* pj = &polys[j*nvp]; + for (int k = j + 1; k < npolys; ++k) { + int pkIndex = k * nvp; + //ushort* pk = &polys[k*nvp]; + int ea = 0; + int eb = 0; + int v = getPolyMergeValue(polys, pjIndex, polys, pkIndex, mesh.verts, ref ea, ref eb, nvp); + if (v > bestMergeVal) { + bestMergeVal = v; + bestPa = j; + bestPb = k; + bestEa = ea; + bestEb = eb; + } + } + } + + if (bestMergeVal > 0) { + // Found best, merge. + + //ushort* pa = &polys[bestPa*nvp]; + //ushort* pb = &polys[bestPb*nvp]; + int paIndex = bestPa * nvp; + int pbIndex = bestPb * nvp; + mergePolys(polys, paIndex, polys, pbIndex, bestEa, bestEb, polys, tmpPolyIndex, nvp); + //ushort* last = &polys[(npolys-1)*nvp]; + int lastIndex = (npolys - 1) * nvp; + if (polys[pbIndex] != polys[lastIndex]) { + //memcpy(pb, last, sizeof(ushort)*nvp); + for (int j = 0; j < nvp; ++j) { + polys[pbIndex + j] = polys[lastIndex + j]; + } + } + pregs[bestPb] = pregs[npolys - 1]; + pareas[bestPb] = pareas[npolys - 1]; + npolys--; + } else { + // Could not merge any polygons, stop. + break; + } + } + } + + // Store polygons. + for (int i = 0; i < npolys; ++i) { + if (mesh.npolys >= maxTris) break; + //ushort* p = &mesh.polys[mesh.npolys*nvp*2]; + int pIndex = mesh.npolys * nvp * 2; + for (int j = 0; j < nvp * 2; ++j) { + polys[pIndex + j] = 0xffff; + } + //memset(p,0xff,sizeof(ushort)*nvp*2); + for (int j = 0; j < nvp; ++j) { + polys[pIndex + j] = polys[i * nvp + j]; + } + mesh.regs[mesh.npolys] = pregs[i]; + mesh.areas[mesh.npolys] = pareas[i]; + mesh.npolys++; + if (mesh.npolys > maxTris) { + ctx.log(rcLogCategory.RC_LOG_ERROR, "removeVertex: Too many polygons " + mesh.npolys + " (max:" + maxTris + ")"); + return false; + } + } + + return true; + } + + /// @par + /// + /// @note If the mesh data is to be used to construct a Detour navigation mesh, then the upper + /// limit must be retricted to <= #DT_VERTS_PER_POLYGON. + /// + /// @see rcAllocPolyMesh, rcContourSet, rcPolyMesh, rcConfig + public static bool rcBuildPolyMesh(rcContext ctx, rcContourSet cset, int nvp, rcPolyMesh mesh) { + Debug.Assert(ctx != null, "rcContext is null"); + + ctx.startTimer(rcTimerLabel.RC_TIMER_BUILD_POLYMESH); + + rcVcopy(mesh.bmin, cset.bmin); + rcVcopy(mesh.bmax, cset.bmax); + mesh.cs = cset.cs; + mesh.ch = cset.ch; + mesh.borderSize = cset.borderSize; + + int maxVertices = 0; + int maxTris = 0; + int maxVertsPerCont = 0; + for (int i = 0; i < cset.nconts; ++i) { + // Skip null contours. + if (cset.conts[i].nverts < 3) continue; + maxVertices += cset.conts[i].nverts; + maxTris += cset.conts[i].nverts - 2; + maxVertsPerCont = Math.Max(maxVertsPerCont, cset.conts[i].nverts); + } + + if (maxVertices >= 0xfffe) { + ctx.log(rcLogCategory.RC_LOG_ERROR, "rcBuildPolyMesh: Too many vertices " + maxVertices); + return false; + } + + //rcScopedDelete vflags = (byte*)rcAlloc(sizeof(byte)*maxVertices, RC_ALLOC_TEMP); + byte[] vflags = new byte[maxVertices]; + if (vflags == null) { + ctx.log(rcLogCategory.RC_LOG_ERROR, "rcBuildPolyMesh: Out of memory 'vflags' " + maxVertices); + return false; + } + //memset(vflags, 0, maxVertices); + + //mesh.verts = (ushort*)rcAlloc(sizeof(ushort)*maxVertices*3, RC_ALLOC_PERM); + mesh.verts = new ushort[maxVertices * 3]; + if (mesh.verts == null) { + ctx.log(rcLogCategory.RC_LOG_ERROR, "rcBuildPolyMesh: Out of memory 'mesh.verts' " + maxVertices); + return false; + } + //mesh.polys = (ushort*)rcAlloc(sizeof(ushort)*maxTris*nvp*2, RC_ALLOC_PERM); + mesh.polys = new ushort[maxTris * nvp * 2]; + if (mesh.polys == null) { + ctx.log(rcLogCategory.RC_LOG_ERROR, "rcBuildPolyMesh: Out of memory 'mesh.polys' " + maxTris * nvp * 2); + return false; + } + //mesh.regs = (ushort*)rcAlloc(sizeof(ushort)*maxTris, RC_ALLOC_PERM); + mesh.regs = new ushort[maxTris]; + if (mesh.regs == null) { + ctx.log(rcLogCategory.RC_LOG_ERROR, "rcBuildPolyMesh: Out of memory 'mesh.regs' " + maxTris); + return false; + } + //mesh.areas = (byte*)rcAlloc(sizeof(byte)*maxTris, RC_ALLOC_PERM); + mesh.areas = new byte[maxTris]; + if (mesh.areas == null) { + ctx.log(rcLogCategory.RC_LOG_ERROR, "rcBuildPolyMesh: Out of memory 'mesh.areas' " + maxTris); + return false; + } + + mesh.nverts = 0; + mesh.npolys = 0; + mesh.nvp = nvp; + mesh.maxpolys = maxTris; + + //memset(mesh.verts, 0, sizeof(ushort)*maxVertices*3); + //memset(mesh.polys, 0xff, sizeof(ushort)*maxTris*nvp*2); + for (int i = 0; i < maxTris * nvp * 2; ++i) { + mesh.polys[i] = 0xffff; + } + //memset(mesh.regs, 0, sizeof(ushort)*maxTris); + //memset(mesh.areas, 0, sizeof(byte)*maxTris); + + //rcScopedDelete nextVert = (int*)rcAlloc(sizeof(int)*maxVertices, RC_ALLOC_TEMP); + int[] nextVert = new int[maxVertices]; + if (nextVert == null) { + ctx.log(rcLogCategory.RC_LOG_ERROR, "rcBuildPolyMesh: Out of memory 'nextVert' " + maxVertices); + return false; + } + //memset(nextVert, 0, sizeof(int)*maxVertices); + + //rcScopedDelete firstVert = (int*)rcAlloc(sizeof(int)*VERTEX_BUCKET_COUNT, RC_ALLOC_TEMP); + int[] firstVert = new int[VERTEX_BUCKET_COUNT]; + if (firstVert == null) { + ctx.log(rcLogCategory.RC_LOG_ERROR, "rcBuildPolyMesh: Out of memory 'firstVert' " + VERTEX_BUCKET_COUNT); + return false; + } + for (int i = 0; i < VERTEX_BUCKET_COUNT; ++i) + firstVert[i] = -1; + + //rcScopedDelete indices = (int*)rcAlloc(sizeof(int)*maxVertsPerCont, RC_ALLOC_TEMP); + int[] indices = new int[maxVertsPerCont]; + if (indices == null) { + ctx.log(rcLogCategory.RC_LOG_ERROR, "rcBuildPolyMesh: Out of memory 'indices' " + maxVertsPerCont); + return false; + } + //rcScopedDelete tris = (int*)rcAlloc(sizeof(int)*maxVertsPerCont*3, RC_ALLOC_TEMP); + int[] tris = new int[maxVertsPerCont * 3]; + if (tris == null) { + ctx.log(rcLogCategory.RC_LOG_ERROR, "rcBuildPolyMesh: Out of memory 'tris' " + maxVertsPerCont * 3); + return false; + } + //rcScopedDelete polys = (ushort*)rcAlloc(sizeof(ushort)*(maxVertsPerCont+1)*nvp, RC_ALLOC_TEMP); + ushort[] polys = new ushort[(maxVertsPerCont + 1) * nvp]; + if (polys == null) { + ctx.log(rcLogCategory.RC_LOG_ERROR, "rcBuildPolyMesh: Out of memory 'polys' " + (maxVertsPerCont + 1) * nvp); + return false; + } + int tmpPolyIndex = maxVertsPerCont * nvp; + //ushort[] tmpPoly = &polys[maxVertsPerCont*nvp]; + + for (int i = 0; i < cset.nconts; ++i) { + rcContour cont = cset.conts[i]; + + // Skip null contours. + if (cont.nverts < 3) + continue; + + // Triangulate contour + for (int j = 0; j < cont.nverts; ++j) + indices[j] = j; + + int ntris = triangulate(cont.nverts, cont.verts, indices, tris); + if (ntris <= 0) { + // Bad triangulation, should not happen. + /* printf("\tconst float bmin[3] = {%ff,%ff,%ff};\n", cset.bmin[0], cset.bmin[1], cset.bmin[2]); + printf("\tconst float cs = %ff;\n", cset.cs); + printf("\tconst float ch = %ff;\n", cset.ch); + printf("\tconst int verts[] = {\n"); + for (int k = 0; k < cont.nverts; ++k) + { + const int* v = &cont.verts[k*4]; + printf("\t\t%d,%d,%d,%d,\n", v[0], v[1], v[2], v[3]); + } + printf("\t};\n\tconst int nverts = sizeof(verts)/(sizeof(int)*4);\n");*/ + ctx.log(rcLogCategory.RC_LOG_WARNING, "rcBuildPolyMesh: Bad triangulation Contour " + i); + ntris = -ntris; + } + + // Add and merge vertices. + for (int j = 0; j < cont.nverts; ++j) { + int vIndex = j * 4; + //const int* v = &cont.verts[j*4]; + indices[j] = addVertex((ushort)cont.verts[vIndex + 0], (ushort)cont.verts[vIndex + 1], (ushort)cont.verts[vIndex + 2], + mesh.verts, firstVert, nextVert, ref mesh.nverts); + if ((cont.verts[vIndex + 3] & RC_BORDER_VERTEX) != 0) { + // This vertex should be removed. + vflags[indices[j]] = 1; + } + } + + // Build initial polygons. + int npolys = 0; + //memset(polys, 0xff, maxVertsPerCont*nvp*sizeof(ushort)); + for (int j = 0; j < nvp * maxVertsPerCont; ++j) { + polys[j] = 0xffff; + } + for (int j = 0; j < ntris; ++j) { + int tIndex = j * 3; + //int* t = &tris[j*3]; + if (tris[tIndex + 0] != tris[tIndex + 1] && tris[tIndex + 0] != tris[tIndex + 2] && tris[tIndex + 1] != tris[tIndex + 2]) { + polys[npolys * nvp + 0] = (ushort)indices[tris[tIndex + 0]]; + polys[npolys * nvp + 1] = (ushort)indices[tris[tIndex + 1]]; + polys[npolys * nvp + 2] = (ushort)indices[tris[tIndex + 2]]; + npolys++; + } + } + if (npolys == 0) { + continue; + } + + // Merge polygons. + if (nvp > 3) { + for (; ; ) { + // Find best polygons to merge. + int bestMergeVal = 0; + int bestPa = 0, bestPb = 0, bestEa = 0, bestEb = 0; + + for (int j = 0; j < npolys - 1; ++j) { + int pjIndex = j * nvp; + //ushort* pj = &polys[j*nvp]; + for (int k = j + 1; k < npolys; ++k) { + //ushort* pk = &polys[k*nvp]; + int pkIndex = k * nvp; + int ea = 0, eb = 0; + int v = getPolyMergeValue(polys, pjIndex, polys, pkIndex, mesh.verts, ref ea, ref eb, nvp); + if (v > bestMergeVal) { + bestMergeVal = v; + bestPa = j; + bestPb = k; + bestEa = ea; + bestEb = eb; + } + } + } + + if (bestMergeVal > 0) { + // Found best, merge. + //ushort* pa = &polys[bestPa*nvp]; + //ushort* pb = &polys[bestPb*nvp]; + int paIndex = bestPa * nvp; + int pbIndex = bestPb * nvp; + mergePolys(polys, paIndex, polys, pbIndex, bestEa, bestEb, polys, tmpPolyIndex, nvp); + //ushort* lastPoly = &polys[(npolys-1)*nvp]; + int lastPolyIndex = (npolys - 1) * nvp; + if (pbIndex != lastPolyIndex) { + //memcpy(pb, lastPoly, sizeof(ushort)*nvp); + for (int j = 0; j < nvp; ++j) { + polys[pbIndex + j] = polys[lastPolyIndex + j]; + } + } + npolys--; + } else { + // Could not merge any polygons, stop. + break; + } + } + } + + // Store polygons. + for (int j = 0; j < npolys; ++j) { + //ushort* p = &mesh.polys[mesh.npolys*nvp*2]; + //ushort* q = &polys[j*nvp]; + int pIndex = mesh.npolys * nvp * 2; + int qIndex = j * nvp; + for (int k = 0; k < nvp; ++k) { + mesh.polys[pIndex + k] = polys[qIndex + k]; + } + mesh.regs[mesh.npolys] = cont.reg; + mesh.areas[mesh.npolys] = cont.area; + mesh.npolys++; + if (mesh.npolys > maxTris) { + ctx.log(rcLogCategory.RC_LOG_ERROR, "rcBuildPolyMesh: Too many polygons " + mesh.npolys + " max " + maxTris); + return false; + } + } + } + + + // Remove edge vertices. + for (int i = 0; i < mesh.nverts; ++i) { + if (vflags[i] != 0) { + if (!canRemoveVertex(ctx, mesh, (ushort)i)) { + continue; + } + if (!removeVertex(ctx, mesh, (ushort)i, maxTris)) { + // Failed to remove vertex + ctx.log(rcLogCategory.RC_LOG_ERROR, "rcBuildPolyMesh: Failed to remove edge vertex " + i); + return false; + } + // Remove vertex + // Note: mesh.nverts is already decremented inside removeVertex()! + // Fixup vertex flags + for (int j = i; j < mesh.nverts; ++j) + vflags[j] = vflags[j + 1]; + --i; + } + } + + // Calculate adjacency. + if (!buildMeshAdjacency(mesh.polys, mesh.npolys, mesh.nverts, nvp)) { + ctx.log(rcLogCategory.RC_LOG_ERROR, "rcBuildPolyMesh: Adjacency failed."); + return false; + } + + // Find portal edges + if (mesh.borderSize > 0) { + int w = cset.width; + int h = cset.height; + for (int i = 0; i < mesh.npolys; ++i) { + int pIndex = i * 2 * nvp; + //ushort* p = &mesh.polys[i*2*nvp]; + for (int j = 0; j < nvp; ++j) { + if (mesh.polys[pIndex + j] == RC_MESH_NULL_IDX) { + break; + } + // Skip connected edges. + if (mesh.polys[pIndex + nvp + j] != RC_MESH_NULL_IDX) { + continue; + } + int nj = j + 1; + if (nj >= nvp || mesh.polys[pIndex + nj] == RC_MESH_NULL_IDX) nj = 0; + //ushort* va = &mesh.verts[mesh.polys[pIndex + j]*3]; + //ushort* vb = &mesh.verts[mesh.polys[pIndex + nj]*3]; + int vaIndex = mesh.polys[pIndex + j] * 3; + int vbIndex = mesh.polys[pIndex + nj] * 3; + + if ((int)mesh.verts[vaIndex + 0] == 0 && (int)mesh.verts[vbIndex + 0] == 0) + mesh.polys[pIndex + nvp + j] = 0x8000 | 0; + else if ((int)mesh.verts[vaIndex + 2] == h && (int)mesh.verts[vbIndex + 2] == h) + mesh.polys[pIndex + nvp + j] = 0x8000 | 1; + else if ((int)mesh.verts[vaIndex + 0] == w && (int)mesh.verts[vbIndex + 0] == w) + mesh.polys[pIndex + nvp + j] = 0x8000 | 2; + else if ((int)mesh.verts[vaIndex + 2] == 0 && (int)mesh.verts[vbIndex + 2] == 0) + mesh.polys[pIndex + nvp + j] = 0x8000 | 3; + } + } + } + + // Just allocate the mesh flags array. The user is resposible to fill it. + //mesh.flags = (ushort*)rcAlloc(sizeof(ushort)*mesh.npolys, RC_ALLOC_PERM); + mesh.flags = new ushort[mesh.npolys]; + if (mesh.flags == null) { + ctx.log(rcLogCategory.RC_LOG_ERROR, "rcBuildPolyMesh: Out of memory 'mesh.flags' " + mesh.npolys); + return false; + } + //memset(mesh.flags, 0, sizeof(ushort) * mesh.npolys); + + if (mesh.nverts > 0xffff) { + ctx.log(rcLogCategory.RC_LOG_ERROR, "rcBuildPolyMesh: The resulting mesh has too many vertices " + mesh.nverts + "(max " + 0xffff + ") Data can be corrupted."); + } + if (mesh.npolys > 0xffff) { + ctx.log(rcLogCategory.RC_LOG_ERROR, "rcBuildPolyMesh: The resulting mesh has too many polygons " + mesh.npolys + " (max " + 0xffff + "). Data can be corrupted."); + } + + ctx.stopTimer(rcTimerLabel.RC_TIMER_BUILD_POLYMESH); + + return true; + } + + /// @see rcAllocPolyMesh, rcPolyMesh + public static bool rcMergePolyMeshes(rcContext ctx, ref rcPolyMesh[] meshes, int nmeshes, rcPolyMesh mesh) { + Debug.Assert(ctx != null, "rcContext is null"); + + if (nmeshes == 0 || meshes == null) + return true; + + ctx.startTimer(rcTimerLabel.RC_TIMER_MERGE_POLYMESH); + + mesh.nvp = meshes[0].nvp; + mesh.cs = meshes[0].cs; + mesh.ch = meshes[0].ch; + rcVcopy(mesh.bmin, meshes[0].bmin); + rcVcopy(mesh.bmax, meshes[0].bmax); + + int maxVerts = 0; + int maxPolys = 0; + int maxVertsPerMesh = 0; + for (int i = 0; i < nmeshes; ++i) { + rcVmin(mesh.bmin, meshes[i].bmin); + rcVmax(mesh.bmax, meshes[i].bmax); + maxVertsPerMesh = Math.Max(maxVertsPerMesh, meshes[i].nverts); + maxVerts += meshes[i].nverts; + maxPolys += meshes[i].npolys; + } + + mesh.nverts = 0; + //mesh.verts = (ushort*)rcAlloc(sizeof(ushort)*maxVerts*3, RC_ALLOC_PERM); + mesh.verts = new ushort[maxVerts * 3]; + if (mesh.verts == null) { + ctx.log(rcLogCategory.RC_LOG_ERROR, "rcMergePolyMeshes: Out of memory 'mesh.verts' " + maxVerts * 3); + return false; + } + + mesh.npolys = 0; + //mesh.polys = (ushort*)rcAlloc(sizeof(ushort)*maxPolys*2*mesh.nvp, RC_ALLOC_PERM); + mesh.polys = new ushort[maxPolys * 2 * mesh.nvp]; + if (mesh.polys == null) { + ctx.log(rcLogCategory.RC_LOG_ERROR, "rcMergePolyMeshes: Out of memory 'mesh.polys' " + maxPolys * 2 * mesh.nvp); + return false; + } + //memset(mesh.polys, 0xff, sizeof(ushort)*maxPolys*2*mesh.nvp); + for (int i = 0; i < maxPolys * 2 * mesh.nvp; ++i) { + mesh.polys[i] = 0xffff; + } + + //mesh.regs = (ushort*)rcAlloc(sizeof(ushort)*maxPolys, RC_ALLOC_PERM); + mesh.regs = new ushort[maxPolys]; + if (mesh.regs == null) { + ctx.log(rcLogCategory.RC_LOG_ERROR, "rcMergePolyMeshes: Out of memory 'mesh.regs' " + maxPolys); + return false; + } + //memset(mesh.regs, 0, sizeof(ushort)*maxPolys); + + //mesh.areas = (byte*)rcAlloc(sizeof(byte)*maxPolys, RC_ALLOC_PERM); + mesh.areas = new byte[maxPolys]; + if (mesh.areas == null) { + ctx.log(rcLogCategory.RC_LOG_ERROR, "rcMergePolyMeshes: Out of memory 'mesh.areas' " + maxPolys); + return false; + } + //memset(mesh.areas, 0, sizeof(byte)*maxPolys); + + //mesh.flags = (ushort*)rcAlloc(sizeof(ushort)*maxPolys, RC_ALLOC_PERM); + mesh.flags = new ushort[maxPolys]; + if (mesh.flags == null) { + ctx.log(rcLogCategory.RC_LOG_ERROR, "rcMergePolyMeshes: Out of memory 'mesh.flags' " + maxPolys); + return false; + } + //memset(mesh.flags, 0, sizeof(ushort)*maxPolys); + + //rcScopedDelete nextVert = (int*)rcAlloc(sizeof(int)*maxVerts, RC_ALLOC_TEMP); + int[] nextVert = new int[maxVerts]; + if (nextVert == null) { + ctx.log(rcLogCategory.RC_LOG_ERROR, "rcMergePolyMeshes: Out of memory 'nextVert' " + maxVerts); + return false; + } + //memset(nextVert, 0, sizeof(int)*maxVerts); + + //rcScopedDelete firstVert = (int*)rcAlloc(sizeof(int)*VERTEX_BUCKET_COUNT, RC_ALLOC_TEMP); + int[] firstVert = new int[VERTEX_BUCKET_COUNT]; + if (firstVert == null) { + ctx.log(rcLogCategory.RC_LOG_ERROR, "rcMergePolyMeshes: Out of memory 'firstVert' " + VERTEX_BUCKET_COUNT); + return false; + } + for (int i = 0; i < VERTEX_BUCKET_COUNT; ++i) { + firstVert[i] = -1; + } + + //rcScopedDelete vremap = (ushort*)rcAlloc(sizeof(ushort)*maxVertsPerMesh, RC_ALLOC_PERM); + ushort[] vremap = new ushort[maxVertsPerMesh]; + if (vremap == null) { + ctx.log(rcLogCategory.RC_LOG_ERROR, "rcMergePolyMeshes: Out of memory 'vremap' " + maxVertsPerMesh); + return false; + } + //memset(vremap, 0, sizeof(ushort)*maxVertsPerMesh); + + for (int i = 0; i < nmeshes; ++i) { + rcPolyMesh pmesh = meshes[i]; + + ushort ox = (ushort)Math.Floor((pmesh.bmin[0] - mesh.bmin[0]) / mesh.cs + 0.5f); + ushort oz = (ushort)Math.Floor((pmesh.bmin[2] - mesh.bmin[2]) / mesh.cs + 0.5f); + + bool isMinX = (ox == 0); + bool isMinZ = (oz == 0); + bool isMaxX = ((ushort)Math.Floor((mesh.bmax[0] - pmesh.bmax[0]) / mesh.cs + 0.5f)) == 0; + bool isMaxZ = ((ushort)Math.Floor((mesh.bmax[2] - pmesh.bmax[2]) / mesh.cs + 0.5f)) == 0; + bool isOnBorder = (isMinX || isMinZ || isMaxX || isMaxZ); + + for (int j = 0; j < pmesh.nverts; ++j) { + //ushort* v = &pmesh.verts[j*3]; + int vIndex = j * 3; + vremap[j] = addVertex((ushort)(pmesh.verts[vIndex + 0] + ox), pmesh.verts[vIndex + 1], (ushort)(pmesh.verts[vIndex + 2] + oz), + mesh.verts, firstVert, nextVert, ref mesh.nverts); + } + + for (int j = 0; j < pmesh.npolys; ++j) { + //ushort* tgt = &mesh.polys[mesh.npolys*2*mesh.nvp]; + //ushort* src = &pmesh.polys[j*2*mesh.nvp]; + int tgtIndex = mesh.npolys * 2 * mesh.nvp; + int srcIndex = j * 2 * mesh.nvp; + + mesh.regs[mesh.npolys] = pmesh.regs[j]; + mesh.areas[mesh.npolys] = pmesh.areas[j]; + mesh.flags[mesh.npolys] = pmesh.flags[j]; + mesh.npolys++; + for (int k = 0; k < mesh.nvp; ++k) { + if (pmesh.polys[srcIndex + k] == RC_MESH_NULL_IDX) { + break; + } + mesh.polys[tgtIndex + k] = vremap[pmesh.polys[srcIndex + k]]; + } + + if (isOnBorder) { + for (int k = mesh.nvp; k < mesh.nvp * 2; ++k) { + if ((pmesh.polys[srcIndex + k] & 0x8000) != 0 && (pmesh.polys[srcIndex + k] != 0xffff)) { + ushort dir = (ushort)(pmesh.polys[srcIndex + k] & 0xf); + switch (dir) { + case 0: // Portal x- + if (isMinX) + mesh.polys[tgtIndex + k] = pmesh.polys[srcIndex + k]; + break; + case 1: // Portal z+ + if (isMaxZ) + mesh.polys[tgtIndex + k] = pmesh.polys[srcIndex + k]; + break; + case 2: // Portal x+ + if (isMaxX) + mesh.polys[tgtIndex + k] = pmesh.polys[srcIndex + k]; + break; + case 3: // Portal z- + if (isMinZ) + mesh.polys[tgtIndex + k] = pmesh.polys[srcIndex + k]; + break; + } + } + } + } + } + } + + // Calculate adjacency. + if (!buildMeshAdjacency(mesh.polys, mesh.npolys, mesh.nverts, mesh.nvp)) { + ctx.log(rcLogCategory.RC_LOG_ERROR, "rcMergePolyMeshes: Adjacency failed."); + return false; + } + + if (mesh.nverts > 0xffff) { + ctx.log(rcLogCategory.RC_LOG_ERROR, "rcMergePolyMeshes: The resulting mesh has too many vertices " + mesh.nverts + " (max " + 0xffff + "). Data can be corrupted."); + } + if (mesh.npolys > 0xffff) { + ctx.log(rcLogCategory.RC_LOG_ERROR, "rcMergePolyMeshes: The resulting mesh has too many polygons " + mesh.npolys + " (max " + 0xffff + "). Data can be corrupted."); + } + + ctx.stopTimer(rcTimerLabel.RC_TIMER_MERGE_POLYMESH); + + return true; + } + + public static bool rcCopyPolyMesh(rcContext ctx, rcPolyMesh src, rcPolyMesh dst) { + Debug.Assert(ctx != null, "rcContext is null"); + + // Destination must be empty. + Debug.Assert(dst.verts == null); + Debug.Assert(dst.polys == null); + Debug.Assert(dst.regs == null); + Debug.Assert(dst.areas == null); + Debug.Assert(dst.flags == null); + + dst.nverts = src.nverts; + dst.npolys = src.npolys; + dst.maxpolys = src.npolys; + dst.nvp = src.nvp; + rcVcopy(dst.bmin, src.bmin); + rcVcopy(dst.bmax, src.bmax); + dst.cs = src.cs; + dst.ch = src.ch; + dst.borderSize = src.borderSize; + + //dst.verts = (ushort*)rcAlloc(sizeof(ushort)*src.nverts*3, RC_ALLOC_PERM); + dst.verts = new ushort[src.nverts * 3]; + if (dst.verts == null) { + ctx.log(rcLogCategory.RC_LOG_ERROR, "rcCopyPolyMesh: Out of memory 'dst.verts' (" + src.nverts * 3 + ")."); + return false; + } + //memcpy(dst.verts, src.verts, sizeof(ushort)*src.nverts*3); + for (int i = 0; i < src.nverts * 3; ++i) { + dst.verts[i] = src.verts[i]; + } + + //dst.polys = (ushort*)rcAlloc(sizeof(ushort)*src.npolys*2*src.nvp, RC_ALLOC_PERM); + dst.polys = new ushort[src.npolys * 2 * src.nvp]; + if (dst.polys == null) { + ctx.log(rcLogCategory.RC_LOG_ERROR, "rcCopyPolyMesh: Out of memory 'dst.polys' (" + src.npolys * 2 * src.nvp + ")."); + return false; + } + //memcpy(dst.polys, src.polys, sizeof(ushort)*src.npolys*2*src.nvp); + for (int i = 0; i < src.npolys * 2 * src.nvp; ++i) { + dst.polys[i] = src.polys[i]; + } + + //dst.regs = (ushort*)rcAlloc(sizeof(ushort)*src.npolys, RC_ALLOC_PERM); + dst.regs = new ushort[src.npolys]; + if (dst.regs == null) { + ctx.log(rcLogCategory.RC_LOG_ERROR, "rcCopyPolyMesh: Out of memory 'dst.regs' (" + src.npolys + ")."); + return false; + } + //memcpy(dst.regs, src.regs, sizeof(ushort)*src.npolys); + for (int i = 0; i < src.npolys; ++i) { + dst.regs[i] = src.regs[i]; + } + + //dst.areas = (byte*)rcAlloc(sizeof(byte)*src.npolys, RC_ALLOC_PERM); + dst.areas = new byte[src.npolys]; + if (dst.areas == null) { + ctx.log(rcLogCategory.RC_LOG_ERROR, "rcCopyPolyMesh: Out of memory 'dst.areas' (" + src.npolys + ")."); + return false; + } + //memcpy(dst.areas, src.areas, sizeof(byte)*src.npolys); + for (int i = 0; i < src.npolys; ++i) { + dst.areas[i] = src.areas[i]; + } + + //dst.flags = (ushort*)rcAlloc(sizeof(ushort)*src.npolys, RC_ALLOC_PERM); + dst.flags = new ushort[src.npolys]; + if (dst.flags != null) { + ctx.log(rcLogCategory.RC_LOG_ERROR, "rcCopyPolyMesh: Out of memory 'dst.flags' (" + src.npolys + ")."); + return false; + } + //memcpy(dst.flags, src.flags, sizeof(byte)*src.npolys); + for (int i = 0; i < src.npolys; ++i) { + dst.flags[i] = src.flags[i]; + } + + return true; + } +} \ No newline at end of file diff --git a/Framework/RecastDetour/Recast/RecastMeshDetail.cs b/Framework/RecastDetour/Recast/RecastMeshDetail.cs new file mode 100644 index 000000000..9c375dce9 --- /dev/null +++ b/Framework/RecastDetour/Recast/RecastMeshDetail.cs @@ -0,0 +1,1650 @@ +using System; +using System.Collections.Generic; +using System.Diagnostics; + +public static partial class Recast{ + const ushort RC_UNSET_HEIGHT = 0xffff; + + public class rcHeightPatch + { + public rcHeightPatch(){ + } + + public ushort[] data = null; + public int xmin = 0; + public int ymin = 0; + public int width = 0; + public int height = 0; + }; + + + public static float vdot2(float[] a, float[] b) + { + return a[0]*b[0] + a[2]*b[2]; + } + + public static float vdot2(float[] a, int aStart, float[] b, int bStart) + { + return a[aStart]*b[bStart] + a[aStart +2]*b[bStart + 2]; + } + + public static float vdistSq2(float[] p, float[] q) + { + float dx = q[0] - p[0]; + float dy = q[2] - p[2]; + return dx*dx + dy*dy; + } + + public static float vdistSq2(float[] p, int pStart, float[] q, int qStart) + { + float dx = q[qStart + 0] - p[pStart + 0]; + float dy = q[qStart + 2] - p[pStart + 2]; + return dx*dx + dy*dy; + } + + + public static float vdist2(float[] p, float[] q) + { + return (float)Math.Sqrt(vdistSq2(p,q)); + } + + public static float vdist2(float[] p, int pStart, float[] q, int qStart) + { + return (float)Math.Sqrt(vdistSq2(p,pStart,q,qStart)); + } + + public static float vcross2(float[] p1, float[] p2, float[]p3) + { + float u1 = p2[0] - p1[0]; + float v1 = p2[2] - p1[2]; + float u2 = p3[0] - p1[0]; + float v2 = p3[2] - p1[2]; + return u1 * v2 - v1 * u2; + } + + + public static float vcross2(float[] p1, int p1Start, float[] p2, int p2Start, float[]p3, int p3Start) + { + float u1 = p2[0 + p2Start] - p1[0 + p1Start]; + float v1 = p2[2 + p2Start] - p1[2 + p1Start]; + float u2 = p3[0 + p3Start] - p1[0 + p1Start]; + float v2 = p3[2 + p3Start] - p1[2 + p1Start]; + return u1 * v2 - v1 * u2; + } + + public static bool circumCircle(float[] p1, float[] p2, float[]p3, + float[] c,ref float r) + { + const float EPS = 1e-6f; + + float cp = vcross2(p1, p2, p3); + if (Math.Abs(cp) > EPS) + { + float p1Sq = vdot2(p1,p1); + float p2Sq = vdot2(p2,p2); + float p3Sq = vdot2(p3,p3); + c[0] = (p1Sq*(p2[2]-p3[2]) + p2Sq*(p3[2]-p1[2]) + p3Sq*(p1[2]-p2[2])) / (2*cp); + c[2] = (p1Sq*(p3[0]-p2[0]) + p2Sq*(p1[0]-p3[0]) + p3Sq*(p2[0]-p1[0])) / (2*cp); + r = vdist2(c, p1); + return true; + } + + c[0] = p1[0]; + c[2] = p1[2]; + r = 0; + return false; + } + + public static bool circumCircle(float[] p1, int p1Start, float[] p2, int p2Start, float[]p3, int p3Start, + float[] c, int cStart ,ref float r) + { + const float EPS = 1e-6f; + + float cp = vcross2(p1, p1Start, p2, p2Start, p3, p3Start); + if (Math.Abs(cp) > EPS) + { + float p1Sq = vdot2(p1,p1Start,p1, p1Start); + float p2Sq = vdot2(p2, p2Start,p2, p2Start); + float p3Sq = vdot2(p3,p3Start,p3,p3Start); + c[cStart+ 0] = (p1Sq * (p2[p2Start + 2] - p3[p3Start + 2]) + p2Sq * (p3[p3Start + 2] - p1[p1Start + 2]) + p3Sq * (p1[p1Start + 2] - p2[p2Start + 2])) / (2 * cp); + c[cStart+ 2] = (p1Sq * (p3[p3Start + 0] - p2[p2Start + 0]) + p2Sq * (p1[p1Start + 0] - p3[p3Start + 0]) + p3Sq * (p2[p2Start + 0] - p1[p1Start + 0])) / (2 * cp); + r = vdist2(c, cStart, p1, p1Start); + return true; + } + + c[cStart + 0] = p1[p1Start + 0]; + c[cStart + 2] = p1[p1Start + 2]; + r = 0; + return false; + } + + static float distPtTri(float[] p, float[]a, float[] b, float[]c) + { + float[] v0 = new float[3]; + float[] v1 = new float[3]; + float[] v2 = new float[3]; + rcVsub(v0, c,a); + rcVsub(v1, b,a); + rcVsub(v2, p,a); + + float dot00 = vdot2(v0, v0); + float dot01 = vdot2(v0, v1); + float dot02 = vdot2(v0, v2); + float dot11 = vdot2(v1, v1); + float dot12 = vdot2(v1, v2); + + // Compute barycentric coordinates + float invDenom = 1.0f / (dot00 * dot11 - dot01 * dot01); + float u = (dot11 * dot02 - dot01 * dot12) * invDenom; + float v = (dot00 * dot12 - dot01 * dot02) * invDenom; + + // If point lies inside the triangle, return interpolated y-coord. + const float EPS = 1e-4f; + if (u >= -EPS && v >= -EPS && (u+v) <= 1+EPS) + { + float y = a[1] + v0[1]*u + v1[1]*v; + return Math.Abs(y-p[1]); + } + return float.MaxValue; + } + static float distPtTri(float[] p, int pStart, float[] a, int aStart, float[] b, int bStart, float[] c, int cStart) { + float[] v0 = new float[3]; + float[] v1 = new float[3]; + float[] v2 = new float[3]; + rcVsub(v0,0, c,cStart, a,aStart); + rcVsub(v1,0, b,bStart, a,aStart); + rcVsub(v2,0, p,pStart, a,aStart); + + float dot00 = vdot2(v0, v0); + float dot01 = vdot2(v0, v1); + float dot02 = vdot2(v0, v2); + float dot11 = vdot2(v1, v1); + float dot12 = vdot2(v1, v2); + + // Compute barycentric coordinates + float invDenom = 1.0f / (dot00 * dot11 - dot01 * dot01); + float u = (dot11 * dot02 - dot01 * dot12) * invDenom; + float v = (dot00 * dot12 - dot01 * dot02) * invDenom; + + // If point lies inside the triangle, return interpolated y-coord. + const float EPS = 1e-4f; + if (u >= -EPS && v >= -EPS && (u + v) <= 1 + EPS) { + float y = a[aStart+1] + v0[1] * u + v1[1] * v; + return Math.Abs(y - p[pStart+1]); + } + return float.MaxValue; + } + + + static float distancePtSeg(float[] pt, float[] p, float[] q) + { + float pqx = q[0] - p[0]; + float pqy = q[1] - p[1]; + float pqz = q[2] - p[2]; + float dx = pt[0] - p[0]; + float dy = pt[1] - p[1]; + float dz = pt[2] - p[2]; + float d = pqx*pqx + pqy*pqy + pqz*pqz; + float t = pqx*dx + pqy*dy + pqz*dz; + if (d > 0) + t /= d; + if (t < 0) + t = 0; + else if (t > 1) + t = 1; + + dx = p[0] + t*pqx - pt[0]; + dy = p[1] + t*pqy - pt[1]; + dz = p[2] + t*pqz - pt[2]; + + return dx*dx + dy*dy + dz*dz; + } + static float distancePtSeg(float[] pt, int ptStart, float[] p, int pStart, float[] q, int qStart) { + float pqx = q[qStart + 0] - p[pStart + 0]; + float pqy = q[qStart + 1] - p[pStart + 1]; + float pqz = q[qStart + 2] - p[pStart + 2]; + float dx = pt[ptStart + 0] - p[pStart + 0]; + float dy = pt[ptStart + 1] - p[pStart + 1]; + float dz = pt[ptStart + 2] - p[pStart + 2]; + float d = pqx * pqx + pqy * pqy + pqz * pqz; + float t = pqx * dx + pqy * dy + pqz * dz; + if (d > 0) + t /= d; + if (t < 0) + t = 0; + else if (t > 1) + t = 1; + + dx = p[pStart + 0] + t * pqx - pt[ptStart + 0]; + dy = p[pStart + 1] + t * pqy - pt[ptStart + 1]; + dz = p[pStart + 2] + t * pqz - pt[ptStart + 2]; + + return dx * dx + dy * dy + dz * dz; + } + + static float distancePtSeg2d(float[] pt, float[] p, float[] q) + { + float pqx = q[0] - p[0]; + float pqz = q[2] - p[2]; + float dx = pt[0] - p[0]; + float dz = pt[2] - p[2]; + float d = pqx*pqx + pqz*pqz; + float t = pqx*dx + pqz*dz; + if (d > 0) + t /= d; + if (t < 0) + t = 0; + else if (t > 1) + t = 1; + + dx = p[0] + t*pqx - pt[0]; + dz = p[2] + t*pqz - pt[2]; + + return dx*dx + dz*dz; + } + + static float distancePtSeg2d(float[] pt, int ptStart, float[] p, int pStart, float[] q, int qStart) + { + float pqx = q[qStart + 0] - p[0 + pStart]; + float pqz = q[qStart + 2] - p[2 + pStart]; + float dx = pt[ptStart + 0] - p[0 + pStart]; + float dz = pt[ptStart + 2] - p[2 + pStart]; + float d = pqx*pqx + pqz*pqz; + float t = pqx*dx + pqz*dz; + if (d > 0) + t /= d; + if (t < 0) + t = 0; + else if (t > 1) + t = 1; + + dx = p[0 + pStart] + t*pqx - pt[ptStart + 0]; + dz = p[2 + pStart] + t*pqz - pt[ptStart + 2]; + + return dx*dx + dz*dz; + } + + static float distToTriMesh(float[] p, float[] verts, int nverts, List tris, int ntris) + { + float dmin = float.MaxValue; + for (int i = 0; i < ntris; ++i) + { + int vaStart = tris[i * 4 + 0] * 3; + int vbStart = tris[i * 4 + 1] * 3; + int vcStart = tris[i * 4 + 2] * 3; + float d = distPtTri(p,0, verts,vaStart,verts,vbStart,verts,vcStart); + if (d < dmin) + dmin = d; + } + if (dmin == float.MaxValue) return -1; + return dmin; + } + + static float distToPoly(int nvert, float[] verts, float[] p) + { + + float dmin = float.MaxValue; + int i, j; + bool c = false; + for (i = 0, j = nvert-1; i < nvert; j = i++) + { + int viStart = i * 3; + int vjStart = j * 3; + if (((verts[viStart+2] > p[2]) != (verts[vjStart+2] > p[2])) && + (p[0] < (verts[vjStart + 0] - verts[viStart + 0]) * (p[2] - verts[viStart + 2]) / (verts[vjStart + 2] - verts[viStart + 2]) + verts[viStart + 0])) + c = !c; + dmin = Math.Min(dmin, distancePtSeg2d(p, 0, verts, vjStart, verts, viStart)); + } + return c ? -dmin : dmin; + } + + + static ushort getHeight(float fx, float fy, float fz, + float cs, float ics, float ch, + rcHeightPatch hp) + { + int ix = (int)Math.Floor(fx*ics + 0.01f); + int iz = (int)Math.Floor(fz*ics + 0.01f); + ix = rcClamp(ix-hp.xmin, 0, hp.width - 1); + iz = rcClamp(iz-hp.ymin, 0, hp.height - 1); + ushort h = hp.data[ix+iz*hp.width]; + if (h == RC_UNSET_HEIGHT) + { + // Special case when data might be bad. + // Find nearest neighbour pixel which has valid height. + int[] off/*[8*2]*/ = new int[] { -1,0, -1,-1, 0,-1, 1,-1, 1,0, 1,1, 0,1, -1,1}; + float dmin = float.MaxValue; + for (int i = 0; i < 8; ++i) + { + int nx = ix+off[i*2+0]; + int nz = iz+off[i*2+1]; + if (nx < 0 || nz < 0 || nx >= hp.width || nz >= hp.height) { + continue; + } + ushort nh = hp.data[nx+nz*hp.width]; + if (nh == RC_UNSET_HEIGHT){ + continue; + } + + float d = Math.Abs(nh*ch - fy); + if (d < dmin) + { + h = nh; + dmin = d; + } + + /* const float dx = (nx+0.5f)*cs - fx; + const float dz = (nz+0.5f)*cs - fz; + const float d = dx*dx+dz*dz; + if (d < dmin) + { + h = nh; + dmin = d; + } */ + } + } + return h; + } + + + public static class EdgeValues + { + public const int UNDEF = -1; + public const int HULL = -2; + }; + + static int findEdge(List edges, int nedges, int s, int t) + { + for (int i = 0; i < nedges; i++) + { + //int[] e = &edges[i*4]; + int eIndex = i*4; + if ((edges[eIndex + 0] == s && edges[eIndex + 1] == t) || (edges[eIndex + 0] == t && edges[eIndex + 1] == s)){ + return i; + } + } + return EdgeValues.UNDEF; + } + + static int findEdge(int[] edges, int nedges, int s, int t) + { + for (int i = 0; i < nedges; i++) + { + //int[] e = &edges[i*4]; + int eIndex = i*4; + if ((edges[eIndex + 0] == s && edges[eIndex + 1] == t) || (edges[eIndex + 0] == t && edges[eIndex + 1] == s)){ + return i; + } + } + return EdgeValues.UNDEF; + } + + static int addEdge(rcContext ctx, List edges, ref int nedges, int maxEdges, int s, int t, int l, int r) + { + if (nedges >= maxEdges) + { + ctx.log(rcLogCategory.RC_LOG_ERROR, "addEdge (list version): Too many edges ("+nedges+"/"+maxEdges+")."); + return EdgeValues.UNDEF; + } + + // Add edge if not already in the triangulation. + int e = findEdge(edges, nedges, s, t); + if (e == EdgeValues.UNDEF) + { + //int* edge = &edges[nedges*4]; + //int edgeIndex = nedges*4; + /*edges[edgeIndex + 0] = s; + edges[edgeIndex + 1] = t; + edges[edgeIndex + 2] = l; + edges[edgeIndex + 3] = r;*/ + edges.Add(s); + edges.Add(t); + edges.Add(l); + edges.Add(r); + return nedges++; + } + else + { + return EdgeValues.UNDEF; + } + } + + static int addEdge(rcContext ctx, int[] edges, ref int nedges, int maxEdges, int s, int t, int l, int r) + { + if (nedges >= maxEdges) + { + ctx.log(rcLogCategory.RC_LOG_ERROR, "addEdge: Too many edges ("+nedges+"/"+maxEdges+")."); + return EdgeValues.UNDEF; + } + + // Add edge if not already in the triangulation. + int e = findEdge(edges, nedges, s, t); + if (e == EdgeValues.UNDEF) + { + //int* edge = &edges[nedges*4]; + int edgeIndex = nedges*4; + edges[edgeIndex + 0] = s; + edges[edgeIndex + 1] = t; + edges[edgeIndex + 2] = l; + edges[edgeIndex + 3] = r; + return nedges++; + } + else + { + return EdgeValues.UNDEF; + } + } + static void updateLeftFace(List e, int eStart, int s, int t, int f) + { + if (e[eStart + 0] == s && e[eStart + 1] == t && e[eStart + 2] == EdgeValues.UNDEF){ + e[eStart + 2] = f; + }else if (e[eStart + 1] == s && e[eStart + 0] == t && e[eStart + 3] == EdgeValues.UNDEF){ + e[eStart + 3] = f; + } + } + + static void updateLeftFace(List e, int s, int t, int f) + { + if (e[0] == s && e[1] == t && e[2] == EdgeValues.UNDEF){ + e[2] = f; + }else if (e[1] == s && e[0] == t && e[3] == EdgeValues.UNDEF){ + e[3] = f; + } + } + + static int overlapSegSeg2d(float[] a, float[] b, float[] c, float[] d) + { + float a1 = vcross2(a, b, d); + float a2 = vcross2(a, b, c); + if (a1*a2 < 0.0f) + { + float a3 = vcross2(c, d, a); + float a4 = a3 + a2 - a1; + if (a3 * a4 < 0.0f){ + return 1; + } + } + return 0; + } + + static int overlapSegSeg2d(float[] a, int aStart, float[] b, int bStart, float[] c, int cStart, float[] d, int dStart) + { + float a1 = vcross2(a, aStart, b, bStart, d, dStart); + float a2 = vcross2(a, aStart, b, bStart, c, cStart); + if (a1*a2 < 0.0f) + { + float a3 = vcross2(c, cStart, d, dStart, a, aStart); + float a4 = a3 + a2 - a1; + if (a3 * a4 < 0.0f){ + return 1; + } + } + return 0; + } + + + static bool overlapEdges(float[] pts, List edges, int nedges, int s1, int t1) + { + for (int i = 0; i < nedges; ++i) + { + int s0 = edges[i*4+0]; + int t0 = edges[i*4+1]; + // Same or connected edges do not overlap. + if (s0 == s1 || s0 == t1 || t0 == s1 || t0 == t1){ + continue; + } + if (overlapSegSeg2d(pts,s0*3,pts,t0*3, pts,s1*3,pts,t1*3) != 0){ + return true; + } + } + return false; + } + + static void completeFacet(rcContext ctx, float[] pts, int npts, List edges, ref int nedges, int maxEdges, ref int nfaces, int e) + { + const float EPS = 1e-5f; + + //int[] edge = &edges[e*4]; + int edgeIndex = e*4; + + // Cache s and t. + int s,t; + if (edges[edgeIndex + 2] == EdgeValues.UNDEF) + { + s = edges[edgeIndex + 0]; + t = edges[edgeIndex + 1]; + } + else if (edges[edgeIndex + 3] == EdgeValues.UNDEF) + { + s = edges[edgeIndex + 1]; + t = edges[edgeIndex + 0]; + } + else + { + // Edge already completed. + return; + } + + // Find best point on left of edge. + int pt = npts; + float[] c = new float[] {0,0,0}; + float r = -1.0f; + for (int u = 0; u < npts; ++u) + { + if (u == s || u == t) { + continue; + } + if (vcross2(pts,s*3, pts, t*3, pts, u*3) > EPS) + { + if (r < 0) + { + // The circle is not updated yet, do it now. + pt = u; + circumCircle(pts,s*3, pts,t*3, pts,u*3, c, 0,ref r); + continue; + } + float d = vdist2(c, 0 , pts, u*3); + float tol = 0.001f; + if (d > r*(1+tol)) + { + // Outside current circumcircle, skip. + continue; + } + else if (d < r*(1-tol)) + { + // Inside safe circumcircle, update circle. + pt = u; + circumCircle(pts,s*3, pts,t*3, pts,u*3, c, 0,ref r); + } + else + { + // Inside epsilon circum circle, do extra tests to make sure the edge is valid. + // s-u and t-u cannot overlap with s-pt nor t-pt if they exists. + if (overlapEdges(pts, edges, nedges, s,u)) + continue; + if (overlapEdges(pts, edges, nedges, t,u)) + continue; + // Edge is valid. + pt = u; + circumCircle(pts,s*3, pts,t*3, pts,u*3, c, 0, ref r); + } + } + } + + // Add new triangle or update edge info if s-t is on hull. + if (pt < npts) + { + // Update face information of edge being completed. + updateLeftFace(edges,e*4, s, t, nfaces); + + // Add new edge or update face info of old edge. + e = findEdge(edges, nedges, pt, s); + if (e == EdgeValues.UNDEF) + addEdge(ctx, edges, ref nedges, maxEdges, pt, s, nfaces, EdgeValues.UNDEF); + else + updateLeftFace(edges,e*4, pt, s, nfaces); + + // Add new edge or update face info of old edge. + e = findEdge(edges, nedges, t, pt); + if (e == EdgeValues.UNDEF) + addEdge(ctx, edges, ref nedges, maxEdges, t, pt, nfaces, EdgeValues.UNDEF); + else + updateLeftFace(edges,e*4, t, pt, nfaces); + + nfaces++; + } + else + { + updateLeftFace(edges,e*4, s, t, EdgeValues.HULL); + } + } + + static void delaunayHull(rcContext ctx, int npts, float[] pts, + int nhull, int[] hull, + List tris, List edges) + { + int nfaces = 0; + int nedges = 0; + int maxEdges = npts*10; + //edges.resize(maxEdges*4); + edges.Capacity = maxEdges * 4; + + for (int i = 0, j = nhull-1; i < nhull; j=i++){ + addEdge(ctx, edges, ref nedges, maxEdges, hull[j],hull[i], EdgeValues.HULL, EdgeValues.UNDEF); + } + + int currentEdge = 0; + while (currentEdge < nedges) + { + if (edges[currentEdge*4+2] == EdgeValues.UNDEF){ + completeFacet(ctx, pts, npts, edges,ref nedges, maxEdges, ref nfaces, currentEdge); + } + if (edges[currentEdge*4+3] == EdgeValues.UNDEF){ + completeFacet(ctx, pts, npts, edges, ref nedges, maxEdges, ref nfaces, currentEdge); + } + currentEdge++; + } + + // Create tris + //tris.resize(nfaces*4); + tris.Capacity = nfaces*4; + tris.Clear(); + for (int i = 0; i < nfaces*4; ++i){ + //tris[i] = -1; + tris.Add(-1); + } + + for (int i = 0; i < nedges; ++i) + { + //const int* e = &edges[i*4]; + int edgeIndex = i*4; + if (edges[edgeIndex + 3] >= 0) + { + // Left face + //int* t = &tris[e[3]*4]; + int tIndex = edges[edgeIndex +3]*4; + if (tris[tIndex + 0] == -1) + { + tris[tIndex + 0] = edges[edgeIndex +0]; + tris[tIndex + 1] = edges[edgeIndex +1]; + } + else if (tris[tIndex + 0] == edges[edgeIndex +1]) + tris[tIndex + 2] = edges[edgeIndex +0]; + else if (tris[tIndex + 1] == edges[edgeIndex +0]) + tris[tIndex + 2] = edges[edgeIndex +1]; + } + if (edges[edgeIndex +2] >= 0) + { + // Right + //int* t = &tris[e[2]*4]; + int tIndex = edges[edgeIndex + 2]*4; + if (tris[tIndex + 0] == -1) + { + tris[tIndex + 0] = edges[edgeIndex + 1]; + tris[tIndex + 1] = edges[edgeIndex + 0]; + } + else if (tris[tIndex + 0] == edges[edgeIndex + 0]) + tris[tIndex + 2] = edges[edgeIndex + 1]; + else if (tris[tIndex + 1] == edges[edgeIndex + 1]) + tris[tIndex + 2] = edges[edgeIndex + 0]; + } + } + + for (int i = 0; i < tris.Count/4; ++i) + { + //int* t = &tris[i*4]; + int tIndex = i*4; + if (tris[tIndex + 0] == -1 || tris[tIndex + 1] == -1 || tris[tIndex + 2] == -1) + { + ctx.log(rcLogCategory.RC_LOG_WARNING, "delaunayHull: Removing dangling face "+i+" [" + tris[tIndex + 0] + "," + tris[tIndex + 1] + "," + tris[tIndex + 2] + "]."); + tris[tIndex + 0] = tris[tris.Count-4]; + tris[tIndex + 1] = tris[tris.Count-3]; + tris[tIndex + 2] = tris[tris.Count-2]; + tris[tIndex + 3] = tris[tris.Count-1]; + //tris.resize(tris.Count-4); + //tris.Capacity = tris.Count - 4; + tris.RemoveRange(tris.Count - 4, 4); + --i; + } + } + } + + + public static float getJitterX(int i) + { + return (((i * 0x8da6b343) & 0xffff) / 65535.0f * 2.0f) - 1.0f; + } + + public static float getJitterY(int i) + { + return (((i * 0xd8163841) & 0xffff) / 65535.0f * 2.0f) - 1.0f; + } + + static bool buildPolyDetail(rcContext ctx, float[] _in, int nin, + float sampleDist, float sampleMaxError, + rcCompactHeightfield chf,rcHeightPatch hp, + float[] verts, ref int nverts, List tris, + List edges, List samples) + { + const int MAX_VERTS = 127; + const int MAX_TRIS = 255; // Max tris for delaunay is 2n-2-k (n=num verts, k=num hull verts). + const int MAX_VERTS_PER_EDGE = 32; + float[] edge = new float[(MAX_VERTS_PER_EDGE+1)*3]; + int[] hull = new int[MAX_VERTS]; + int nhull = 0; + + nverts = 0; + + for (int i = 0; i < nin; ++i){ + rcVcopy(verts,i*3, _in,i*3); + } + nverts = nin; + + float cs = chf.cs; + float ics = 1.0f/cs; + + // Tessellate outlines. + // This is done in separate pass in order to ensure + // seamless height values across the ply boundaries. + if (sampleDist > 0) + { + for (int i = 0, j = nin-1; i < nin; j=i++) + { + //const float* vj = &in[j*3]; + //const float* vi = &in[i*3]; + int vjStart = j*3; + int viStart = i*3; + bool swapped = false; + // Make sure the segments are always handled in same order + // using lexological sort or else there will be seams. + if (Math.Abs(_in[vjStart]-_in[viStart]) < 1e-6f) + { + if (_in[vjStart + 2] > _in[viStart + 2]) + { + rcSwap(ref vjStart,ref viStart); + swapped = true; + } + } + else + { + if (_in[vjStart] > _in[viStart]) + { + rcSwap(ref vjStart,ref viStart); + swapped = true; + } + } + // Create samples along the edge. + float dx = _in[viStart] - _in[vjStart];//vi[0] - vj[0]; + float dy = _in[viStart+1] - _in[vjStart+1];//vi[1] - vj[1]; + float dz = _in[viStart+2] - _in[vjStart+2];//vi[2] - vj[2]; + float d = (float)Math.Sqrt(dx*dx + dz*dz); + int nn = 1 + (int)Math.Floor(d/sampleDist); + if (nn >= MAX_VERTS_PER_EDGE) { + nn = MAX_VERTS_PER_EDGE-1; + } + if (nverts+nn >= MAX_VERTS){ + nn = MAX_VERTS-1-nverts; + } + + for (int k = 0; k <= nn; ++k) + { + float u = (float)k/(float)nn; + //float* pos = &edge[k*3]; + int posStart = k*3; + edge[posStart + 0] = _in[vjStart + 0] + dx*u; + edge[posStart + 1] = _in[vjStart + 1] + dy*u; + edge[posStart + 2] = _in[vjStart + 2] + dz*u; + edge[posStart + 1] = getHeight(edge[posStart + 0],edge[posStart + 1],edge[posStart + 2], cs, ics, chf.ch, hp)*chf.ch; + } + // Simplify samples. + int[] idx = new int[MAX_VERTS_PER_EDGE];// {0,nn}; + idx[1] = nn; + int nidx = 2; + for (int k = 0; k < nidx-1; ) + { + int a = idx[k]; + int b = idx[k+1]; + //float* va = &edge[a*3]; + //float* vb = &edge[b*3]; + int vaStart = a*3; + int vbStart = b*3; + // Find maximum deviation along the segment. + float maxd = 0; + int maxi = -1; + for (int m = a+1; m < b; ++m) + { + int ptStart = m * 3; + float dev = distancePtSeg(edge, ptStart, edge, vaStart,edge, vbStart); + if (dev > maxd) + { + maxd = dev; + maxi = m; + } + } + // If the max deviation is larger than accepted error, + // add new point, else continue to next segment. + if (maxi != -1 && maxd > sampleMaxError * sampleMaxError) + { + for (int m = nidx; m > k; --m) + idx[m] = idx[m-1]; + idx[k+1] = maxi; + nidx++; + } + else + { + ++k; + } + } + + hull[nhull++] = j; + // Add new vertices. + if (swapped) + { + for (int k = nidx-2; k > 0; --k) + { + //rcVcopy(&verts[nverts*3], &edge[idx[k]*3]); + rcVcopy(verts,nverts*3,edge,idx[k]*3); + hull[nhull++] = nverts; + nverts++; + } + } + else + { + for (int k = 1; k < nidx-1; ++k) + { + //rcVcopy(&verts[nverts*3], &edge[idx[k]*3]); + rcVcopy(verts,nverts*3,edge,idx[k]*3); + hull[nhull++] = nverts; + nverts++; + } + } + } + } + + + // Tessellate the base mesh. + //edges.resize(0); + //tris.resize(0); + edges.Clear(); + tris.Clear(); + + delaunayHull(ctx, nverts, verts, nhull, hull, tris, edges); + + if (tris.Count == 0) + { + // Could not triangulate the poly, make sure there is some valid data there. + ctx.log(rcLogCategory.RC_LOG_WARNING, "buildPolyDetail: Could not triangulate polygon, adding default data."); + for (int i = 2; i < nverts; ++i) + { + tris.Add(0); + tris.Add(i-1); + tris.Add(i); + tris.Add(0); + } + return true; + } + + if (sampleDist > 0) + { + // Create sample locations in a grid. + float[] bmin = new float[3]; + float[] bmax = new float[3]; + rcVcopy(bmin, _in); + rcVcopy(bmax, _in); + for (int i = 1; i < nin; ++i) + { + rcVmin(bmin, 0, _in,i*3); + rcVmax(bmax, 0, _in,i*3); + } + int x0 = (int)Math.Floor(bmin[0]/sampleDist); + int x1 = (int)Math.Ceiling(bmax[0]/sampleDist); + int z0 = (int)Math.Floor(bmin[2]/sampleDist); + int z1 = (int)Math.Ceiling(bmax[2]/sampleDist); + //samples.resize(0); + samples.Clear(); + for (int z = z0; z < z1; ++z) + { + for (int x = x0; x < x1; ++x) + { + float[] pt = new float[3]; + pt[0] = x*sampleDist; + pt[1] = (bmax[1]+bmin[1])*0.5f; + pt[2] = z*sampleDist; + // Make sure the samples are not too close to the edges. + if (distToPoly(nin,_in,pt) > -sampleDist/2) { + continue; + } + samples.Add(x); + samples.Add(getHeight(pt[0], pt[1], pt[2], cs, ics, chf.ch, hp)); + samples.Add(z); + samples.Add(0); // Not added + } + } + + // Add the samples starting from the one that has the most + // error. The procedure stops when all samples are added + // or when the max error is within treshold. + int nsamples = samples.Count/4; + for (int iter = 0; iter < nsamples; ++iter) + { + if (nverts >= MAX_VERTS){ + break; + } + + // Find sample with most error. + float[] bestpt = new float[] {0.0f,0.0f,0.0f}; + float bestd = 0; + int besti = -1; + for (int i = 0; i < nsamples; ++i) + { + // int* s = &samples[i*4]; + int sStart = i*4; + if (samples[sStart + 3] != 0) + continue; // skip added. + float[] pt = new float[3]; + // The sample location is jittered to get rid of some bad triangulations + // which are cause by symmetrical data from the grid structure. + pt[0] = samples[sStart + 0]*sampleDist + getJitterX(i)*cs*0.1f; + pt[1] = samples[sStart + 1]*chf.ch; + pt[2] = samples[sStart + 2]*sampleDist + getJitterY(i)*cs*0.1f; + float d = distToTriMesh(pt, verts, nverts, tris, tris.Count/4); + if (d < 0) + continue; // did not hit the mesh. + if (d > bestd) + { + bestd = d; + besti = i; + rcVcopy(bestpt,pt); + } + } + // If the max error is within accepted threshold, stop tesselating. + if (bestd <= sampleMaxError || besti == -1) + break; + // Mark sample as added. + samples[besti*4+3] = 1; + // Add the new sample point. + rcVcopy(verts,nverts*3,bestpt,0); + nverts++; + + // Create new triangulation. + // TODO: Incremental add instead of full rebuild. + //edges.resize(0); + //tris.resize(0); + edges.Clear(); + tris.Clear(); + delaunayHull(ctx, nverts, verts, nhull, hull, tris, edges); + } + } + + int ntris = tris.Count/4; + if (ntris > MAX_TRIS) + { + //tris.resize(MAX_TRIS*4); + tris.RemoveRange(MAX_TRIS*4, tris.Count - MAX_TRIS*4); + ctx.log(rcLogCategory.RC_LOG_ERROR, "rcBuildPolyMeshDetail: Shrinking triangle count from "+ntris+" to max "+MAX_TRIS+"."); + } + + return true; + } + + + static void getHeightDataSeedsFromVertices(rcCompactHeightfield chf, + ushort[] poly, int polyStart, int npoly, + ushort[] verts, int bs, + rcHeightPatch hp, List stack) + { + // Floodfill the heightfield to get 2D height data, + // starting at vertex locations as seeds. + + // Note: Reads to the compact heightfield are offset by border size (bs) + // since border size offset is already removed from the polymesh vertices. + + //memset(hp.data, 0, sizeof(ushort)*hp.width*hp.height); + for (int i=0;i= hp.xmin+hp.width || + az < hp.ymin || az >= hp.ymin+hp.height) + continue; + + rcCompactCell c = chf.cells[(ax+bs)+(az+bs)*chf.width]; + for (int i = (int)c.index, ni = (int)(c.index+c.count); i < ni; ++i) + { + rcCompactSpan s = chf.spans[i]; + int d = Math.Abs(ay - (int)s.y); + if (d < dmin) + { + cx = ax; + cz = az; + ci = i; + dmin = d; + } + } + } + if (ci != -1) + { + stack.Add(cx); + stack.Add(cz); + stack.Add(ci); + } + } + + // Find center of the polygon using flood fill. + int pcx = 0, pcz = 0; + for (int j = 0; j < npoly; ++j) + { + pcx += (int)verts[poly[polyStart + j]*3+0]; + pcz += (int)verts[poly[polyStart + j]*3+2]; + } + pcx /= npoly; + pcz /= npoly; + + for (int i = 0; i < stack.Count; i += 3) + { + int cx = stack[i+0]; + int cy = stack[i+1]; + int idx = cx-hp.xmin+(cy-hp.ymin)*hp.width; + hp.data[idx] = 1; + } + + while (stack.Count > 0) + { + int ci = stack[stack.Count - 1]; + stack.RemoveAt(stack.Count - 1); + int cy = stack[stack.Count - 1]; + stack.RemoveAt(stack.Count - 1); + int cx = stack[stack.Count - 1]; + stack.RemoveAt(stack.Count - 1); + + // Check if close to center of the polygon. + if (Math.Abs(cx-pcx) <= 1 && Math.Abs(cy-pcz) <= 1) + { + //stack.resize(0); + stack.Clear(); + stack.Add(cx); + stack.Add(cy); + stack.Add(ci); + break; + } + + rcCompactSpan cs = chf.spans[ci]; + + for (int dir = 0; dir < 4; ++dir) + { + if (rcGetCon(cs, dir) == RC_NOT_CONNECTED) + continue; + + int ax = cx + rcGetDirOffsetX(dir); + int ay = cy + rcGetDirOffsetY(dir); + + if (ax < hp.xmin || ax >= (hp.xmin+hp.width) || + ay < hp.ymin || ay >= (hp.ymin+hp.height)) + continue; + + if (hp.data[ax-hp.xmin+(ay-hp.ymin)*hp.width] != 0) + continue; + + int ai = (int)chf.cells[(ax+bs)+(ay+bs)*chf.width].index + rcGetCon(cs, dir); + + int idx = ax-hp.xmin+(ay-hp.ymin)*hp.width; + hp.data[idx] = 1; + + stack.Add(ax); + stack.Add(ay); + stack.Add(ai); + } + } + + //memset(hp.data, 0xff, sizeof(ushort)*hp.width*hp.height); + for (int i=0;i stack, + int region) + { + // Note: Reads to the compact heightfield are offset by border size (bs) + // since border size offset is already removed from the polymesh vertices. + + //stack.resize(0); + //memset(hp.data, 0xff, sizeof(ushort)*hp.width*hp.height); + stack.Clear(); + for (int i=0;i= RETRACT_SIZE) + { + head = 0; + if (stack.Count > RETRACT_SIZE*3){ + //memmove(&stack[0], &stack[RETRACT_SIZE*3], sizeof(int)*(stack.Count-RETRACT_SIZE*3)); + for (int i=0;i 0,"Resizing under zero"); + stack.RemoveRange(newSize, stack.Count - newSize); + } + + rcCompactSpan cs = chf.spans[ci]; + for (int dir = 0; dir < 4; ++dir) + { + if (rcGetCon(cs, dir) == RC_NOT_CONNECTED) + continue; + + int ax = cx + rcGetDirOffsetX(dir); + int ay = cy + rcGetDirOffsetY(dir); + int hx = ax - hp.xmin - bs; + int hy = ay - hp.ymin - bs; + + if (hx < 0 || hx >= hp.width || hy < 0 || hy >= hp.height) + continue; + + if (hp.data[hx + hy*hp.width] != RC_UNSET_HEIGHT) + continue; + + int ai = (int)chf.cells[ax + ay*chf.width].index + rcGetCon(cs, dir); + rcCompactSpan aSpan = chf.spans[ai]; + + hp.data[hx + hy*hp.width] = aSpan.y; + + stack.Add(ax); + stack.Add(ay); + stack.Add(ai); + } + } + } + + static byte getEdgeFlags(float[] va,float[] vb, + float[] vpoly, int npoly) + { + // Return true if edge (va,vb) is part of the polygon. + float thrSqr = 0.001f * 0.001f; + for (int i = 0, j = npoly-1; i < npoly; j=i++) + { + if (distancePtSeg2d(va, 0, vpoly, j*3, vpoly, i*3) < thrSqr && + distancePtSeg2d(vb, 0, vpoly, j*3, vpoly, i*3) < thrSqr) + return 1; + } + return 0; + } + static byte getEdgeFlags(float[] va, int vaStart, float[] vb, int vbStart, + float[] vpoly, int vpolyStart, int npoly) { + // Return true if edge (va,vb) is part of the polygon. + float thrSqr = 0.001f * 0.001f; + for (int i = 0, j = npoly - 1; i < npoly; j = i++) { + if (distancePtSeg2d(va, vaStart, vpoly, vpolyStart + j * 3, vpoly, vpolyStart + i * 3) < thrSqr && + distancePtSeg2d(vb, vbStart, vpoly, vpolyStart + j * 3, vpoly, vpolyStart + i * 3) < thrSqr) + return 1; + } + return 0; + } + + static byte getTriFlags(float[] va, float[] vb, float[] vc, + float[] vpoly, int npoly) + { + byte flags = 0; + flags |= (byte)( getEdgeFlags(va,vb,vpoly,npoly) << 0); + flags |= (byte)( getEdgeFlags(vb,vc,vpoly,npoly) << 2); + flags |= (byte)( getEdgeFlags(vc,va,vpoly,npoly) << 4); + return flags; + } + static byte getTriFlags(float[] va, int vaStart, float[] vb, int vbStart, float[] vc, int vcStart, + float[] vpoly, int vpolyStart, int npoly) { + byte flags = 0; + flags |= (byte)(getEdgeFlags(va, vaStart, vb, vbStart, vpoly, vpolyStart, npoly) << 0); + flags |= (byte)(getEdgeFlags(vb, vbStart, vc, vcStart, vpoly, vpolyStart, npoly) << 2); + flags |= (byte)(getEdgeFlags(vc, vcStart, va, vaStart, vpoly, vpolyStart, npoly) << 4); + return flags; + } + + public static int rccsPop(List list) { + //Let it crash if empty, so that we know there s a pb + int ret = list[list.Count - 1]; + list.RemoveAt(list.Count - 1); + return ret; + } + + public static void rccsResizeList(List list, int length) { + if (length > list.Count){ + for (int i = list.Count; i < length; ++i) { + list.Add(0); + } + } + else if (length < list.Count){ + list.RemoveRange(length, list.Count - length); + } + } + /// @par + /// + /// See the #rcConfig documentation for more information on the configuration parameters. + /// + /// @see rcAllocPolyMeshDetail, rcPolyMesh, rcCompactHeightfield, rcPolyMeshDetail, rcConfig + public static bool rcBuildPolyMeshDetail(rcContext ctx, rcPolyMesh mesh, rcCompactHeightfield chf, + float sampleDist, float sampleMaxError, + rcPolyMeshDetail dmesh) + { + Debug.Assert(ctx != null, "rcContext is null"); + + ctx.startTimer(rcTimerLabel.RC_TIMER_BUILD_POLYMESHDETAIL); + + if (mesh.nverts == 0 || mesh.npolys == 0) + return true; + + int nvp = mesh.nvp; + float cs = mesh.cs; + float ch = mesh.ch; + float[] orig = mesh.bmin; + int borderSize = mesh.borderSize; + + List edges = new List(); + List tris = new List(); + List stack = new List(); + List samples = new List(); + edges.Capacity = 64; + tris.Capacity = 512; + stack.Capacity = 512; + samples.Capacity = 512; + float[] verts = new float[256*3]; + rcHeightPatch hp = new rcHeightPatch(); + int nPolyVerts = 0; + int maxhw = 0, maxhh = 0; + + //rcScopedDelete bounds = (int*)rcAlloc(sizeof(int)*mesh.npolys*4, RC_ALLOC_TEMP); + int[] bounds = new int[mesh.npolys*4]; + if (bounds == null) + { + ctx.log(rcLogCategory.RC_LOG_ERROR, "rcBuildPolyMeshDetail: Out of memory 'bounds' ("+ mesh.npolys*4+")."); + return false; + } + //rcScopedDelete poly = (float*)rcAlloc(sizeof(float)*nvp*3, RC_ALLOC_TEMP); + float[] poly = new float[nvp*3]; + if (poly == null) + { + ctx.log(rcLogCategory.RC_LOG_ERROR, "rcBuildPolyMeshDetail: Out of memory 'poly' ("+nvp*3+")."); + return false; + } + + // Find max size for a polygon area. + for (int i = 0; i < mesh.npolys; ++i) + { + //ushort* p = &mesh.polys[i*nvp*2]; + int pStart = i*nvp*2; + //int& xmin = bounds[i*4+0]; + //int& xmax = bounds[i*4+1]; + //int& ymin = bounds[i*4+2]; + //int& ymax = bounds[i*4+3]; + int xmin = i*4+0; + int xmax = i*4+1; + int ymin = i*4+2; + int ymax = i*4+3; + bounds[xmin] = chf.width; + bounds[xmax] = 0; + bounds[ymin] = chf.height; + bounds[ymax] = 0; + for (int j = 0; j < nvp; ++j) + { + if(mesh.polys[pStart + j] == RC_MESH_NULL_IDX) + break; + //t ushort* v = &mesh.verts[p[j]*3]; + int vIndex = mesh.polys[pStart + j] * 3; + bounds[xmin] = Math.Min(bounds[xmin], (int)mesh.verts[vIndex + 0]); + bounds[xmax] = Math.Max(bounds[xmax], (int)mesh.verts[vIndex + 0]); + bounds[ymin] = Math.Min(bounds[ymin], (int)mesh.verts[vIndex + 2]); + bounds[ymax] = Math.Max(bounds[ymax], (int)mesh.verts[vIndex + 2]); + nPolyVerts++; + } + bounds[xmin] = Math.Max(0,bounds[xmin]-1); + bounds[xmax] = Math.Min(chf.width,bounds[xmax]+1); + bounds[ymin] = Math.Max(0,bounds[ymin]-1); + bounds[ymax] = Math.Min(chf.height,bounds[ymax]+1); + if (bounds[xmin] >= bounds[xmax] || bounds[ymin] >= bounds[ymax]) continue; + maxhw = Math.Max(maxhw, bounds[xmax]-bounds[xmin]); + maxhh = Math.Max(maxhh, bounds[ymax]-bounds[ymin]); + } + + //hp.data = (ushort*)rcAlloc(sizeof(ushort)*maxhw*maxhh, RC_ALLOC_TEMP); + hp.data = new ushort[maxhh*maxhw]; + if (hp.data == null) + { + ctx.log(rcLogCategory.RC_LOG_ERROR, "rcBuildPolyMeshDetail: Out of memory 'hp.data' ("+maxhw*maxhh+")."); + return false; + } + + dmesh.nmeshes = mesh.npolys; + dmesh.nverts = 0; + dmesh.ntris = 0; + //dmesh.meshes = (uint*)rcAlloc(sizeof(uint)*dmesh.nmeshes*4, RC_ALLOC_PERM); + dmesh.meshes = new uint[dmesh.nmeshes*4]; + if (dmesh.meshes == null) + { + ctx.log(rcLogCategory.RC_LOG_ERROR, "rcBuildPolyMeshDetail: Out of memory 'dmesh.meshes' ("+dmesh.nmeshes*4+")."); + return false; + } + + int vcap = nPolyVerts+nPolyVerts/2; + int tcap = vcap*2; + + dmesh.nverts = 0; + //dmesh.verts = (float*)rcAlloc(sizeof(float)*vcap*3, RC_ALLOC_PERM); + dmesh.verts = new float[vcap*3]; + if (dmesh.verts == null) + { + ctx.log(rcLogCategory.RC_LOG_ERROR, "rcBuildPolyMeshDetail: Out of memory 'dmesh.verts' ("+vcap*3+")."); + return false; + } + dmesh.ntris = 0; + //dmesh.tris = (byte*)rcAlloc(sizeof(byte*)*tcap*4, RC_ALLOC_PERM); + dmesh.tris = new byte[tcap*4]; + if (dmesh.tris == null) + { + ctx.log(rcLogCategory.RC_LOG_ERROR, "rcBuildPolyMeshDetail: Out of memory 'dmesh.tris' ("+tcap*4+")."); + return false; + } + + for (int i = 0; i < mesh.npolys; ++i) + { + //const ushort* p = &mesh.polys[i*nvp*2]; + int pIndex = i*nvp*2; + + // Store polygon vertices for processing. + int npoly = 0; + for (int j = 0; j < nvp; ++j) + { + if(mesh.polys[pIndex + j] == RC_MESH_NULL_IDX) + break; + //const ushort* v = &mesh.verts[p[j]*3]; + int vIndex = mesh.polys[pIndex + j] * 3; + poly[j*3+0] = mesh.verts[vIndex + 0]*cs; + poly[j*3+1] = mesh.verts[vIndex + 1]*ch; + poly[j*3+2] = mesh.verts[vIndex + 2]*cs; + npoly++; + } + + // Get the height data from the area of the polygon. + hp.xmin = bounds[i*4+0]; + hp.ymin = bounds[i*4+2]; + hp.width = bounds[i*4+1]-bounds[i*4+0]; + hp.height = bounds[i*4+3]-bounds[i*4+2]; + getHeightData(chf, mesh.polys, pIndex, npoly, mesh.verts, borderSize, hp, stack, mesh.regs[i]); + + // Build detail mesh. + int nverts = 0; + if (!buildPolyDetail(ctx, poly, npoly, + sampleDist, sampleMaxError, + chf, hp, verts, ref nverts, tris, + edges, samples)) + { + return false; + } + + // Move detail verts to world space. + for (int j = 0; j < nverts; ++j) + { + verts[j*3+0] += orig[0]; + verts[j*3+1] += orig[1] + chf.ch; // Is this offset necessary? + verts[j*3+2] += orig[2]; + } + // Offset poly too, will be used to flag checking. + for (int j = 0; j < npoly; ++j) + { + poly[j*3+0] += orig[0]; + poly[j*3+1] += orig[1]; + poly[j*3+2] += orig[2]; + } + + // Store detail submesh. + int ntris = tris.Count/4; + + dmesh.meshes[i*4+0] = (uint)dmesh.nverts; + dmesh.meshes[i*4+1] = (uint)nverts; + dmesh.meshes[i*4+2] = (uint)dmesh.ntris; + dmesh.meshes[i*4+3] = (uint)ntris; + + // Store vertices, allocate more memory if necessary. + if (dmesh.nverts+nverts > vcap) + { + while (dmesh.nverts+nverts > vcap){ + vcap += 256; + } + + //float* newv = (float*)rcAlloc(sizeof(float)*vcap*3, RC_ALLOC_PERM); + float[] newv = new float[vcap*3]; + if (newv == null) + { + ctx.log(rcLogCategory.RC_LOG_ERROR, "rcBuildPolyMeshDetail: Out of memory 'newv' ("+vcap*3+")."); + return false; + } + if (dmesh.nverts != 0){ + //memcpy(newv, dmesh.verts, sizeof(float)*3*dmesh.nverts); + for (int j=0;j<3*dmesh.nverts;++j){ + newv[j] = dmesh.verts[j]; + } + } + //rcFree(dmesh.verts); + //dmesh.verts = null; + dmesh.verts = newv; + } + for (int j = 0; j < nverts; ++j) + { + dmesh.verts[dmesh.nverts*3+0] = verts[j*3+0]; + dmesh.verts[dmesh.nverts*3+1] = verts[j*3+1]; + dmesh.verts[dmesh.nverts*3+2] = verts[j*3+2]; + dmesh.nverts++; + } + + // Store triangles, allocate more memory if necessary. + if (dmesh.ntris+ntris > tcap) + { + while (dmesh.ntris+ntris > tcap){ + tcap += 256; + } + //byte* newt = (byte*)rcAlloc(sizeof(byte)*tcap*4, RC_ALLOC_PERM); + byte[] newt = new byte[tcap*4]; + if (newt == null) + { + ctx.log(rcLogCategory.RC_LOG_ERROR, "rcBuildPolyMeshDetail: Out of memory 'newt' ("+tcap*4+")."); + return false; + } + if (dmesh.ntris != 0){ + //memcpy(newt, dmesh.tris, sizeof(byte)*4*dmesh.ntris); + for (int j = 0;j<4*dmesh.ntris;++j){ + newt[j] = dmesh.tris[j]; + } + } + //rcFree(dmesh.tris); + dmesh.tris = newt; + } + for (int j = 0; j < ntris; ++j) + { + //const int* t = &tris[j*4]; + int tIndex = j*4; + dmesh.tris[dmesh.ntris*4+0] = (byte)tris[tIndex + 0]; + dmesh.tris[dmesh.ntris*4+1] = (byte)tris[tIndex + 1]; + dmesh.tris[dmesh.ntris*4+2] = (byte)tris[tIndex + 2]; + dmesh.tris[dmesh.ntris*4+3] = getTriFlags(verts, tris[tIndex + 0]*3, verts, tris[tIndex + 1]*3, verts, tris[tIndex + 2]*3, poly, 0, npoly); + dmesh.ntris++; + } + } + + ctx.stopTimer(rcTimerLabel.RC_TIMER_BUILD_POLYMESHDETAIL); + + return true; + } + + /// @see rcAllocPolyMeshDetail, rcPolyMeshDetail + static bool rcMergePolyMeshDetails(rcContext ctx, rcPolyMeshDetail[] meshes, int nmeshes, ref rcPolyMeshDetail mesh) + { + Debug.Assert(ctx != null, "rcContext is null"); + + ctx.startTimer(rcTimerLabel.RC_TIMER_MERGE_POLYMESHDETAIL); + + int maxVerts = 0; + int maxTris = 0; + int maxMeshes = 0; + + for (int i = 0; i < nmeshes; ++i) + { + if (meshes[i] == null) { + continue; + } + maxVerts += meshes[i].nverts; + maxTris += meshes[i].ntris; + maxMeshes += meshes[i].nmeshes; + } + + mesh.nmeshes = 0; + //mesh.meshes = (uint*)rcAlloc(sizeof(uint)*maxMeshes*4, RC_ALLOC_PERM); + mesh.meshes = new uint[maxMeshes*4]; + if (mesh.meshes == null) + { + ctx.log(rcLogCategory.RC_LOG_ERROR, "rcBuildPolyMeshDetail: Out of memory 'pmdtl.meshes' ("+maxMeshes*4+")."); + return false; + } + + mesh.ntris = 0; + //mesh.tris = (byte*)rcAlloc(sizeof(byte)*maxTris*4, RC_ALLOC_PERM); + mesh.tris = new byte[maxTris*4]; + if (mesh.tris == null) + { + ctx.log(rcLogCategory.RC_LOG_ERROR, "rcBuildPolyMeshDetail: Out of memory 'dmesh.tris' (" + maxTris*4 + ")."); + return false; + } + + mesh.nverts = 0; + //mesh.verts = (float*)rcAlloc(sizeof(float)*maxVerts*3, RC_ALLOC_PERM); + mesh.verts = new float[maxVerts*3]; + if (mesh.verts == null) + { + ctx.log(rcLogCategory.RC_LOG_ERROR, "rcBuildPolyMeshDetail: Out of memory 'dmesh.verts' ("+maxVerts*3+")."); + return false; + } + + // Merge datas. + for (int i = 0; i < nmeshes; ++i) + { + rcPolyMeshDetail dm = meshes[i]; + if (dm == null) { + continue; + } + for (int j = 0; j < dm.nmeshes; ++j) + { + //uint* dst = &mesh.meshes[mesh.nmeshes*4]; + //uint* src = &dm.meshes[j*4]; + int dstIndex = mesh.nmeshes*4; + int srcIndex = j*4; + mesh.meshes[dstIndex + 0] = (uint)mesh.nverts + dm.meshes[srcIndex + 0]; + mesh.meshes[dstIndex + 1] = dm.meshes[srcIndex + 1]; + mesh.meshes[dstIndex + 2] = (uint)mesh.ntris + dm.meshes[srcIndex + 2]; + mesh.meshes[dstIndex + 3] = dm.meshes[srcIndex + 3]; + mesh.nmeshes++; + } + + for (int k = 0; k < dm.nverts; ++k) + { + rcVcopy(mesh.verts,mesh.nverts*3, dm.verts, k*3); + mesh.nverts++; + } + for (int k = 0; k < dm.ntris; ++k) + { + mesh.tris[mesh.ntris*4+0] = dm.tris[k*4+0]; + mesh.tris[mesh.ntris*4+1] = dm.tris[k*4+1]; + mesh.tris[mesh.ntris*4+2] = dm.tris[k*4+2]; + mesh.tris[mesh.ntris*4+3] = dm.tris[k*4+3]; + mesh.ntris++; + } + } + + ctx.stopTimer(rcTimerLabel.RC_TIMER_MERGE_POLYMESHDETAIL); + + return true; + } +} + diff --git a/Framework/RecastDetour/Recast/RecastRasterization.cs b/Framework/RecastDetour/Recast/RecastRasterization.cs new file mode 100644 index 000000000..115dacc55 --- /dev/null +++ b/Framework/RecastDetour/Recast/RecastRasterization.cs @@ -0,0 +1,430 @@ +using System; +using System.Diagnostics; + +public static partial class Recast{ + static bool overlapBounds(float[] amin, float[] amax, float[] bmin, float[] bmax) + { + bool overlap = true; + overlap = (amin[0] > bmax[0] || amax[0] < bmin[0]) ? false : overlap; + overlap = (amin[1] > bmax[1] || amax[1] < bmin[1]) ? false : overlap; + overlap = (amin[2] > bmax[2] || amax[2] < bmin[2]) ? false : overlap; + return overlap; + } + + static bool overlapInterval(ushort amin, ushort amax, + ushort bmin, ushort bmax) + { + if (amax < bmin) return false; + if (amin > bmax) return false; + return true; + } + + + static rcSpan allocSpan(rcHeightfield hf) + { + // If running out of memory, allocate new page and update the freelist. + if (hf.freelist == null || hf.freelist.next == null) + { + // Create new page. + // Allocate memory for the new pool. + //rcSpanPool* pool = (rcSpanPool*)rcAlloc(sizeof(rcSpanPool), RC_ALLOC_PERM); + rcSpanPool pool = new rcSpanPool(); + if (pool == null) + return null; + pool.next = null; + // Add the pool into the list of pools. + pool.next = hf.pools; + hf.pools = pool; + // Add new items to the free list. + rcSpan freelist = hf.freelist; + //rcSpan head = pool.items[0]; + //rcSpan it = pool.items[RC_SPANS_PER_POOL]; + int itIndex = RC_SPANS_PER_POOL; + do + { + --itIndex; + pool.items[itIndex].next = freelist; + freelist = pool.items[itIndex]; + } + while (itIndex != 0); + hf.freelist = pool.items[itIndex]; + } + + // Pop item from in front of the free list. + rcSpan it = hf.freelist; + hf.freelist = hf.freelist.next; + return it; + } + + static void freeSpan(rcHeightfield hf, rcSpan ptr) + { + if (ptr == null) { + return; + } + // Add the node in front of the free list. + ptr.next = hf.freelist; + hf.freelist = ptr; + } + + static void addSpan(rcHeightfield hf, int x, int y, + ushort smin, ushort smax, + byte area, int flagMergeThr) + { + + int idx = x + y*hf.width; + + rcSpan s = allocSpan(hf); + s.smin = smin; + s.smax = smax; + s.area = area; + s.next = null; + + // Empty cell, add the first span. + if (hf.spans[idx] == null) + { + hf.spans[idx] = s; + return; + } + rcSpan prev = null; + rcSpan cur = hf.spans[idx]; + + // Insert and merge spans. + while (cur != null) + { + if (cur.smin > s.smax) + { + // Current span is further than the new span, break. + break; + } + else if (cur.smax < s.smin) + { + // Current span is before the new span advance. + prev = cur; + cur = cur.next; + } + else + { + // Merge spans. + if (cur.smin < s.smin) + s.smin = cur.smin; + if (cur.smax > s.smax) + s.smax = cur.smax; + + // Merge flags. + if (Math.Abs((int)s.smax - (int)cur.smax) <= flagMergeThr){ + s.area = Math.Max(s.area, cur.area); + } + + // Remove current span. + rcSpan next = cur.next; + freeSpan(hf, cur); + if (prev != null) + prev.next = next; + else + hf.spans[idx] = next; + cur = next; + } + } + + // Insert new span. + if (prev != null) + { + s.next = prev.next; + prev.next = s; + } + else + { + s.next = hf.spans[idx]; + hf.spans[idx] = s; + } + } + + /// @par + /// + /// The span addition can be set to favor flags. If the span is merged to + /// another span and the new @p smax is within @p flagMergeThr units + /// from the existing span, the span flags are merged. + /// + /// @see rcHeightfield, rcSpan. + static void rcAddSpan(rcContext ctx, rcHeightfield hf, int x, int y, + ushort smin, ushort smax, + byte area, int flagMergeThr) + { + // Debug.Assert(ctx != null, "rcContext is null"); + addSpan(hf, x,y, smin, smax, area, flagMergeThr); + } + + // divides a convex polygons into two convex polygons on both sides of a line + static void dividePoly(float[] _in, int nin, + float[] out1, ref int nout1, + float[] out2, ref int nout2, + float x, int axis) + { + float[] d = new float[12]; + for (int i = 0; i < nin; ++i){ + d[i] = x - _in[i*3+axis]; + } + + int m = 0, n = 0; + for (int i = 0, j = nin-1; i < nin; j=i, ++i) + { + bool ina = d[j] >= 0; + bool inb = d[i] >= 0; + if (ina != inb) + { + float s = d[j] / (d[j] - d[i]); + out1[m*3+0] = _in[j*3+0] + (_in[i*3+0] - _in[j*3+0])*s; + out1[m*3+1] = _in[j*3+1] + (_in[i*3+1] - _in[j*3+1])*s; + out1[m*3+2] = _in[j*3+2] + (_in[i*3+2] - _in[j*3+2])*s; + rcVcopy(out2, n*3, out1, m*3); + m++; + n++; + // add the i'th point to the right polygon. Do NOT add points that are on the dividing line + // since these were already added above + if (d[i] > 0) + { + rcVcopy(out1,m*3, _in, i*3); + m++; + } + else if (d[i] < 0) + { + rcVcopy(out2,n*3, _in, i*3); + n++; + } + } + else // same side + { + // add the i'th point to the right polygon. Addition is done even for points on the dividing line + if (d[i] >= 0) + { + rcVcopy(out1, m*3, _in, i*3); + m++; + if (d[i] != 0) + continue; + } + rcVcopy(out2, n*3, _in, i*3); + n++; + } + } + + nout1 = m; + nout2 = n; + } + + + + static void rasterizeTri(float[] v0, int v0Start, float[] v1, int v1Start, float[] v2, int v2Start, + byte area, rcHeightfield hf, + float[] bmin, float[] bmax, + float cs, float ics, float ich, + int flagMergeThr) + { + int w = hf.width; + int h = hf.height; + float[] tmin = new float[3]; + float[] tmax = new float[3]; + float by = bmax[1] - bmin[1]; + + // Calculate the bounding box of the triangle. + rcVcopy(tmin, 0, v0, v0Start); + rcVcopy(tmax, 0, v0, v0Start); + rcVmin(tmin, 0, v1, v1Start); + rcVmin(tmin, 0, v2, v2Start); + rcVmax(tmax, 0, v1, v1Start); + rcVmax(tmax, 0, v2, v2Start); + + // If the triangle does not touch the bbox of the heightfield, skip the triagle. + if (!overlapBounds(bmin, bmax, tmin, tmax)) + return; + + // Calculate the footprint of the triangle on the grid's y-axis + int y0 = (int)((tmin[2] - bmin[2])*ics); + int y1 = (int)((tmax[2] - bmin[2])*ics); + y0 = rcClamp(y0, 0, h-1); + y1 = rcClamp(y1, 0, h-1); + + // Clip the triangle into all grid cells it touches. + //float[] buf = new float[7*3*4]; + + float[] _in = new float[7*3]; + float[] inrow = new float[7*3]; + float[] p1 = new float[7*3]; + float[] p2 = new float[7*3]; + + rcVcopy(_in,0 , v0, v0Start); + rcVcopy(_in,1*3, v1, v1Start); + rcVcopy(_in,2*3, v2, v2Start); + + int nvrow = 0; + int nvIn = 3; + + for (int y = y0; y <= y1; ++y) + { + // Clip polygon to row. Store the remaining polygon as well + float cz = bmin[2] + y*cs; + dividePoly(_in, nvIn, inrow, ref nvrow, p1, ref nvIn, cz+cs, 2); + //rcSwap(_in, p1); + float[] tmp = _in; + _in = p1; + p1 = tmp; + + if (nvrow < 3) + continue; + + // find the horizontal bounds in the row + float minX = inrow[0], maxX = inrow[0]; + for (int i=1; i inrow[i*3]) minX = inrow[i*3]; + if (maxX < inrow[i*3]) maxX = inrow[i*3]; + } + int x0 = (int)((minX - bmin[0])*ics); + int x1 = (int)((maxX - bmin[0])*ics); + x0 = rcClamp(x0, 0, w-1); + x1 = rcClamp(x1, 0, w-1); + + int nv = 0; + int nv2 = nvrow; + + for (int x = x0; x <= x1; ++x) + { + // Clip polygon to column. store the remaining polygon as well + float cx = bmin[0] + x*cs; + dividePoly(inrow, nv2, p1, ref nv, p2, ref nv2, cx+cs, 0); + //rcSwap(inrow, p2); + tmp = inrow; + inrow = p2; + p2 = tmp; + if (nv < 3) { + continue; + } + + // Calculate min and max of the span. + float smin = p1[1], smax = p1[1]; + for (int i = 1; i < nv; ++i) + { + smin = Math.Min(smin, p1[i*3+1]); + smax = Math.Max(smax, p1[i*3+1]); + } + smin -= bmin[1]; + smax -= bmin[1]; + // Skip the span if it is outside the heightfield bbox + if (smax < 0.0f) continue; + if (smin > by) continue; + // Clamp the span to the heightfield bbox. + if (smin < 0.0f) smin = 0; + if (smax > by) smax = by; + + // Snap the span to the heightfield height grid. + ushort ismin = (ushort)rcClamp((int)Math.Floor(smin * ich), 0, RC_SPAN_MAX_HEIGHT); + ushort ismax = (ushort)rcClamp((int)Math.Ceiling(smax * ich), (int)ismin+1, RC_SPAN_MAX_HEIGHT); + + addSpan(hf, x, y, ismin, ismax, area, flagMergeThr); + } + } + } + + /// @par + /// + /// No spans will be added if the triangle does not overlap the heightfield grid. + /// + /// @see rcHeightfield + public static void rcRasterizeTriangle(rcContext ctx, float[] v0, int v0Start, float[] v1, int v1Start, float[] v2, int v2Start, + byte area, rcHeightfield solid, + int flagMergeThr) + { + Debug.Assert(ctx != null, "rcContext is null"); + + ctx.startTimer(rcTimerLabel.RC_TIMER_RASTERIZE_TRIANGLES); + + float ics = 1.0f/solid.cs; + float ich = 1.0f/solid.ch; + rasterizeTri(v0, v0Start, v1, v1Start, v2, v2Start, area, solid, solid.bmin, solid.bmax, solid.cs, ics, ich, flagMergeThr); + + ctx.stopTimer(rcTimerLabel.RC_TIMER_RASTERIZE_TRIANGLES); + } + + /// @par + /// + /// Spans will only be added for triangles that overlap the heightfield grid. + /// + /// @see rcHeightfield + public static void rcRasterizeTriangles(rcContext ctx, float[] verts, int nv, + int[] tris, byte[] areas, int nt, + rcHeightfield solid, int flagMergeThr) + { + Debug.Assert(ctx != null, "rcContext is null"); + + ctx.startTimer(rcTimerLabel.RC_TIMER_RASTERIZE_TRIANGLES); + + float ics = 1.0f/solid.cs; + float ich = 1.0f/solid.ch; + // Rasterize triangles. + for (int i = 0; i < nt; ++i) + { + int v0Start = tris[i*3+0]*3; + int v1Start = tris[i*3+1]*3; + int v2Start = tris[i*3+2]*3; + // Rasterize. + rasterizeTri(verts, v0Start, verts, v1Start, verts, v2Start, areas[i], solid, solid.bmin, solid.bmax, solid.cs, ics, ich, flagMergeThr); + } + + ctx.stopTimer(rcTimerLabel.RC_TIMER_RASTERIZE_TRIANGLES); + } + + /// @par + /// + /// Spans will only be added for triangles that overlap the heightfield grid. + /// + /// @see rcHeightfield + public static void rcRasterizeTriangles(rcContext ctx, float[] verts, int nv, + ushort[] tris, byte[] areas, int nt, + rcHeightfield solid, int flagMergeThr) + { + Debug.Assert(ctx != null, "rcContext is null"); + + ctx.startTimer(rcTimerLabel.RC_TIMER_RASTERIZE_TRIANGLES); + + float ics = 1.0f/solid.cs; + float ich = 1.0f/solid.ch; + // Rasterize triangles. + for (int i = 0; i < nt; ++i) + { + int v0Start = tris[i*3+0]*3; + int v1Start = tris[i*3+1]*3; + int v2Start = tris[i*3+2]*3; + + // Rasterize. + rasterizeTri(verts, v0Start, verts, v1Start, verts, v2Start, areas[i], solid, solid.bmin, solid.bmax, solid.cs, ics, ich, flagMergeThr); + } + + ctx.stopTimer(rcTimerLabel.RC_TIMER_RASTERIZE_TRIANGLES); + } + + /// @par + /// + /// Spans will only be added for triangles that overlap the heightfield grid. + /// + /// @see rcHeightfield + public static void rcRasterizeTriangles(rcContext ctx, float[] verts, byte[] areas, int nt, + rcHeightfield solid, int flagMergeThr) + { + Debug.Assert(ctx != null, "rcContext is null"); + + ctx.startTimer(rcTimerLabel.RC_TIMER_RASTERIZE_TRIANGLES); + + float ics = 1.0f/solid.cs; + float ich = 1.0f/solid.ch; + // Rasterize triangles. + for (int i = 0; i < nt; ++i) + { + int v0Start = (i*3+0)*3; + int v1Start = (i*3+1)*3; + int v2Start = (i*3+2)*3; + // Rasterize. + rasterizeTri(verts, v0Start, verts, v1Start, verts, v2Start, areas[i], solid, solid.bmin, solid.bmax, solid.cs, ics, ich, flagMergeThr); + } + + ctx.stopTimer(rcTimerLabel.RC_TIMER_RASTERIZE_TRIANGLES); + } +} \ No newline at end of file diff --git a/Framework/RecastDetour/Recast/RecastRegion.cs b/Framework/RecastDetour/Recast/RecastRegion.cs new file mode 100644 index 000000000..469488290 --- /dev/null +++ b/Framework/RecastDetour/Recast/RecastRegion.cs @@ -0,0 +1,1426 @@ +using System; +using System.Collections.Generic; +using System.Diagnostics; + +public static partial class Recast{ + static void calculateDistanceField( rcContext ctx, rcCompactHeightfield chf, ushort[] src, ref ushort maxDist) + { + int w = chf.width; + int h = chf.height; + + // Init distance and points. + for (int i = 0; i < chf.spanCount; ++i) + src[i] = 0xffff; + + // Mark boundary cells. + for (int y = 0; y < h; ++y) + { + for (int x = 0; x < w; ++x) + { + rcCompactCell c = chf.cells[x+y*w]; + for (int i = (int)c.index, ni = (int)(c.index+c.count); i < ni; ++i) + { + rcCompactSpan s = chf.spans[i]; + byte area = chf.areas[i]; + + int nc = 0; + for (int dir = 0; dir < 4; ++dir) + { + if (rcGetCon(s, dir) != RC_NOT_CONNECTED) + { + int ax = x + rcGetDirOffsetX(dir); + int ay = y + rcGetDirOffsetY(dir); + int ai = (int)chf.cells[ax+ay*w].index + rcGetCon(s, dir); + if (area == chf.areas[ai]) + nc++; + } + } + if (nc != 4) + src[i] = 0; + } + } + } + // Pass 1 + for (int y = 0; y < h; ++y) + { + for (int x = 0; x < w; ++x) + { + rcCompactCell c = chf.cells[x+y*w]; + for (int i = (int)c.index, ni = (int)(c.index+c.count); i < ni; ++i) + { + rcCompactSpan s = chf.spans[i]; + + if (rcGetCon(s, 0) != RC_NOT_CONNECTED) + { + // (-1,0) + int ax = x + rcGetDirOffsetX(0); + int ay = y + rcGetDirOffsetY(0); + int ai = (int)chf.cells[ax+ay*w].index + rcGetCon(s, 0); + rcCompactSpan aSpan = chf.spans[ai]; + if (src[ai]+2 < src[i]){ + src[i] = (ushort)(src[ai]+2); + } + + // (-1,-1) + if (rcGetCon(aSpan, 3) != RC_NOT_CONNECTED) + { + int aax = ax + rcGetDirOffsetX(3); + int aay = ay + rcGetDirOffsetY(3); + int aai = (int)chf.cells[aax+aay*w].index + rcGetCon(aSpan, 3); + if (src[aai]+3 < src[i]){ + src[i] = (ushort)(src[aai]+3); + } + } + } + if (rcGetCon(s, 3) != RC_NOT_CONNECTED) + { + // (0,-1) + int ax = x + rcGetDirOffsetX(3); + int ay = y + rcGetDirOffsetY(3); + int ai = (int)chf.cells[ax+ay*w].index + rcGetCon(s, 3); + rcCompactSpan aSpan = chf.spans[ai]; + if (src[ai]+2 < src[i]){ + src[i] = (ushort)(src[ai]+2); + } + + // (1,-1) + if (rcGetCon(aSpan, 2) != RC_NOT_CONNECTED) + { + int aax = ax + rcGetDirOffsetX(2); + int aay = ay + rcGetDirOffsetY(2); + int aai = (int)chf.cells[aax+aay*w].index + rcGetCon(aSpan, 2); + if (src[aai]+3 < src[i]){ + src[i] = (ushort)(src[aai]+3); + } + } + } + } + } + } + // Pass 2 + for (int y = h-1; y >= 0; --y) + { + for (int x = w-1; x >= 0; --x) + { + rcCompactCell c = chf.cells[x+y*w]; + for (int i = (int)c.index, ni = (int)(c.index+c.count); i < ni; ++i) + { + rcCompactSpan s = chf.spans[i]; + + if (rcGetCon(s, 2) != RC_NOT_CONNECTED) + { + // (1,0) + int ax = x + rcGetDirOffsetX(2); + int ay = y + rcGetDirOffsetY(2); + int ai = (int)chf.cells[ax+ay*w].index + rcGetCon(s, 2); + rcCompactSpan aSpan = chf.spans[ai]; + if (src[ai]+2 < src[i]){ + src[i] = (ushort)(src[ai]+2); + } + + // (1,1) + if (rcGetCon(aSpan, 1) != RC_NOT_CONNECTED) + { + int aax = ax + rcGetDirOffsetX(1); + int aay = ay + rcGetDirOffsetY(1); + int aai = (int)chf.cells[aax+aay*w].index + rcGetCon(aSpan, 1); + if (src[aai]+3 < src[i]){ + src[i] = (ushort)(src[aai]+3); + } + } + } + if (rcGetCon(s, 1) != RC_NOT_CONNECTED) + { + // (0,1) + int ax = x + rcGetDirOffsetX(1); + int ay = y + rcGetDirOffsetY(1); + int ai = (int)chf.cells[ax+ay*w].index + rcGetCon(s, 1); + rcCompactSpan aSpan = chf.spans[ai]; + if (src[ai]+2 < src[i]){ + src[i] = (ushort)(src[ai]+2); + } + + // (-1,1) + if (rcGetCon(aSpan, 0) != RC_NOT_CONNECTED) + { + int aax = ax + rcGetDirOffsetX(0); + int aay = ay + rcGetDirOffsetY(0); + int aai = (int)chf.cells[aax+aay*w].index + rcGetCon(aSpan, 0); + if (src[aai]+3 < src[i]){ + src[i] = (ushort)(src[aai]+3); + } + } + } + } + } + } + maxDist = 0; + for (int i = 0; i < chf.spanCount; ++i){ + maxDist = Math.Max(src[i], maxDist); + } + + } + + static ushort[] boxBlur(rcCompactHeightfield chf, int thr, + ushort[] src, ushort[] dst) + { + int w = chf.width; + int h = chf.height; + + thr *= 2; + + for (int y = 0; y < h; ++y) + { + for (int x = 0; x < w; ++x) + { + rcCompactCell c = chf.cells[x+y*w]; + for (int i = (int)c.index, ni = (int)(c.index+c.count); i < ni; ++i) + { + rcCompactSpan s = chf.spans[i]; + ushort cd = src[i]; + if (cd <= thr) + { + dst[i] = cd; + continue; + } + + int d = (int)cd; + for (int dir = 0; dir < 4; ++dir) + { + if (rcGetCon(s, dir) != RC_NOT_CONNECTED) + { + int ax = x + rcGetDirOffsetX(dir); + int ay = y + rcGetDirOffsetY(dir); + int ai = (int)chf.cells[ax+ay*w].index + rcGetCon(s, dir); + d += (int)src[ai]; + + rcCompactSpan aSpan = chf.spans[ai]; + int dir2 = (dir+1) & 0x3; + if (rcGetCon(aSpan, dir2) != RC_NOT_CONNECTED) + { + int ax2 = ax + rcGetDirOffsetX(dir2); + int ay2 = ay + rcGetDirOffsetY(dir2); + int ai2 = (int)chf.cells[ax2+ay2*w].index + rcGetCon(aSpan, dir2); + d += (int)src[ai2]; + } + else + { + d += cd; + } + } + else + { + d += cd*2; + } + } + dst[i] = (ushort)((d+5)/9); + } + } + } + return dst; + } + + + static bool floodRegion(int x, int y, int i, + ushort level, ushort r, + rcCompactHeightfield chf, + ushort[] srcReg, ushort[] srcDist, + List stack) + { + int w = chf.width; + + byte area = chf.areas[i]; + + // Flood fill mark region. + //stack.resize(0); + stack.Clear(); + stack.Add((int)x); + stack.Add((int)y); + stack.Add((int)i); + srcReg[i] = r; + srcDist[i] = 0; + + ushort lev = (ushort)(level >= 2 ? level-2 : 0); + int count = 0; + + while (stack.Count > 0) + { + int ci = rccsPop(stack); + int cy = rccsPop(stack); + int cx = rccsPop(stack); + + rcCompactSpan cs = chf.spans[ci]; + + // Check if any of the neighbours already have a valid region set. + ushort ar = 0; + for (int dir = 0; dir < 4; ++dir) + { + // 8 connected + if (rcGetCon(cs, dir) != RC_NOT_CONNECTED) + { + int ax = cx + rcGetDirOffsetX(dir); + int ay = cy + rcGetDirOffsetY(dir); + int ai = (int)chf.cells[ax+ay*w].index + rcGetCon(cs, dir); + if (chf.areas[ai] != area) + continue; + ushort nr = srcReg[ai]; + if ((nr & RC_BORDER_REG) != 0) // Do not take borders into account. + continue; + if (nr != 0 && nr != r) + { + ar = nr; + break; + } + + rcCompactSpan aSpan = chf.spans[ai]; + + int dir2 = (dir+1) & 0x3; + if (rcGetCon(aSpan, dir2) != RC_NOT_CONNECTED) + { + int ax2 = ax + rcGetDirOffsetX(dir2); + int ay2 = ay + rcGetDirOffsetY(dir2); + int ai2 = (int)chf.cells[ax2+ay2*w].index + rcGetCon(aSpan, dir2); + if (chf.areas[ai2] != area) + continue; + ushort nr2 = srcReg[ai2]; + if (nr2 != 0 && nr2 != r) + { + ar = nr2; + break; + } + } + } + } + if (ar != 0) + { + srcReg[ci] = 0; + continue; + } + count++; + + // Expand neighbours. + for (int dir = 0; dir < 4; ++dir) + { + if (rcGetCon(cs, dir) != RC_NOT_CONNECTED) + { + int ax = cx + rcGetDirOffsetX(dir); + int ay = cy + rcGetDirOffsetY(dir); + int ai = (int)chf.cells[ax+ay*w].index + rcGetCon(cs, dir); + if (chf.areas[ai] != area) + continue; + if (chf.dist[ai] >= lev && srcReg[ai] == 0) + { + srcReg[ai] = r; + srcDist[ai] = 0; + stack.Add(ax); + stack.Add(ay); + stack.Add(ai); + } + } + } + } + + return count > 0; + } + + static ushort[] expandRegions(int maxIter, ushort level, + rcCompactHeightfield chf, + ushort[] srcReg, ushort[] srcDist, + ushort[] dstReg, ushort[] dstDist, + List stack, + bool fillStack) + { + int w = chf.width; + int h = chf.height; + + if (fillStack) + { + // Find cells revealed by the raised level. + stack.Clear(); + for (int y = 0; y < h; ++y) + { + for (int x = 0; x < w; ++x) + { + rcCompactCell c = chf.cells[x+y*w]; + for (int i = (int)c.index, ni = (int)(c.index+c.count); i < ni; ++i) + { + if (chf.dist[i] >= level && srcReg[i] == 0 && chf.areas[i] != RC_NULL_AREA) + { + stack.Add(x); + stack.Add(y); + stack.Add(i); + } + } + } + } + } + else // use cells in the input stack + { + // mark all cells which already have a region + for (int j=0; j 0) + { + int failed = 0; + + //memcpy(dstReg, srcReg, sizeof(ushort)*chf.spanCount); + for (int i=0;i 0 && (srcReg[ai] & RC_BORDER_REG) == 0) + { + if ((int)srcDist[ai]+2 < (int)d2) + { + r = srcReg[ai]; + d2 = (ushort)(srcDist[ai]+2); + } + } + } + if (r != 0) + { + stack[j+2] = -1; // mark as used + dstReg[i] = r; + dstDist[i] = d2; + } + else + { + failed++; + } + } + + // rcSwap source and dest. + rcSwap(ref srcReg, ref dstReg); + rcSwap(ref srcDist, ref dstDist); + + if (failed*3 == stack.Count) + break; + + if (level > 0) + { + ++iter; + if (iter >= maxIter) + break; + } + } + + return srcReg; + } + + + + static void sortCellsByLevel(ushort startLevel, + rcCompactHeightfield chf, + ushort[] srcReg, + uint nbStacks, List[] stacks, + ushort loglevelsPerStack) // the levels per stack (2 in our case) as a bit shift + { + int w = chf.width; + int h = chf.height; + startLevel = (ushort)(startLevel >> loglevelsPerStack); + + for (uint j=0; j> loglevelsPerStack; + int sId = startLevel - level; + if (sId >= (int)nbStacks) + continue; + if (sId < 0) + sId = 0; + + stacks[sId].Add(x); + stacks[sId].Add(y); + stacks[sId].Add(i); + } + } + } + } + + + static void appendStacks(List srcStack, List dstStack, + ushort[] srcReg) + { + for (int j=0; j connections = new List(); + public List floors = new List(); + }; + + static void removeAdjacentNeighbours(rcRegion reg) + { + // Remove adjacent duplicates. + for (int i = 0; i < reg.connections.Count && reg.connections.Count > 1; ) + { + int ni = (i+1) % reg.connections.Count; + if (reg.connections[i] == reg.connections[ni]) + { + // Remove duplicate + for (int j = i; j < reg.connections.Count-1; ++j){ + reg.connections[j] = reg.connections[j+1]; + } + rccsPop(reg.connections); + } + else + ++i; + } + } + + static void replaceNeighbour(rcRegion reg, ushort oldId, ushort newId) + { + bool neiChanged = false; + for (int i = 0; i < reg.connections.Count; ++i) + { + if (reg.connections[i] == oldId) + { + reg.connections[i] = newId; + neiChanged = true; + } + } + for (int i = 0; i < reg.floors.Count; ++i) + { + if (reg.floors[i] == oldId) + reg.floors[i] = newId; + } + if (neiChanged) + removeAdjacentNeighbours(reg); + } + + static bool canMergeWithRegion(rcRegion rega, rcRegion regb) + { + if (rega.areaType != regb.areaType) + return false; + int n = 0; + for (int i = 0; i < rega.connections.Count; ++i) + { + if (rega.connections[i] == regb.id) + n++; + } + if (n > 1) + return false; + for (int i = 0; i < rega.floors.Count; ++i) + { + if (rega.floors[i] == regb.id) + return false; + } + return true; + } + + static void addUniqueFloorRegion(rcRegion reg, int n) + { + for (int i = 0; i < reg.floors.Count; ++i) + if (reg.floors[i] == n) + return; + reg.floors.Add(n); + } + + static bool mergeRegions(rcRegion rega, rcRegion regb) + { + ushort aid = rega.id; + ushort bid = regb.id; + + // Duplicate current neighbourhood. + List acon = new List(); + + for (int i = 0; i < rega.connections.Count; ++i) + acon.Add( rega.connections[i] ); + List bcon = regb.connections; + + // Find insertion point on A. + int insa = -1; + for (int i = 0; i < acon.Count; ++i) + { + if (acon[i] == bid) + { + insa = i; + break; + } + } + if (insa == -1) + return false; + + // Find insertion point on B. + int insb = -1; + for (int i = 0; i < bcon.Count; ++i) + { + if (bcon[i] == aid) + { + insb = i; + break; + } + } + if (insb == -1) + return false; + + // Merge neighbours. + rega.connections.Clear(); + for (int i = 0, ni = acon.Count; i < ni-1; ++i) + rega.connections.Add(acon[(insa+1+i) % ni]); + + for (int i = 0, ni = bcon.Count; i < ni-1; ++i) + rega.connections.Add(bcon[(insb+1+i) % ni]); + + removeAdjacentNeighbours(rega); + + for (int j = 0; j < regb.floors.Count; ++j) + addUniqueFloorRegion(rega, regb.floors[j]); + rega.spanCount += regb.spanCount; + regb.spanCount = 0; + regb.connections.Clear(); + + return true; + } + + static bool isRegionConnectedToBorder(rcRegion reg) + { + // Region is connected to border if + // one of the neighbours is null id. + for (int i = 0; i < reg.connections.Count; ++i) + { + if (reg.connections[i] == 0) + return true; + } + return false; + } + + static bool isSolidEdge(rcCompactHeightfield chf, ushort[] srcReg, + int x, int y, int i, int dir) + { + rcCompactSpan s = chf.spans[i]; + ushort r = 0; + if (rcGetCon(s, dir) != RC_NOT_CONNECTED) + { + int ax = x + rcGetDirOffsetX(dir); + int ay = y + rcGetDirOffsetY(dir); + int ai = (int)chf.cells[ax+ay*chf.width].index + rcGetCon(s, dir); + r = srcReg[ai]; + } + if (r == srcReg[i]) + return false; + return true; + } + + static void walkContour(int x, int y, int i, int dir, + rcCompactHeightfield chf, + ushort[] srcReg, + List cont) + { + int startDir = dir; + int starti = i; + + rcCompactSpan ss = chf.spans[i]; + ushort curReg = 0; + if (rcGetCon(ss, dir) != RC_NOT_CONNECTED) + { + int ax = x + rcGetDirOffsetX(dir); + int ay = y + rcGetDirOffsetY(dir); + int ai = (int)chf.cells[ax+ay*chf.width].index + rcGetCon(ss, dir); + curReg = srcReg[ai]; + } + cont.Add(curReg); + + int iter = 0; + while (++iter < 40000) + { + rcCompactSpan s = chf.spans[i]; + + if (isSolidEdge(chf, srcReg, x, y, i, dir)) + { + // Choose the edge corner + ushort r = 0; + if (rcGetCon(s, dir) != RC_NOT_CONNECTED) + { + int ax = x + rcGetDirOffsetX(dir); + int ay = y + rcGetDirOffsetY(dir); + int ai = (int)chf.cells[ax+ay*chf.width].index + rcGetCon(s, dir); + r = srcReg[ai]; + } + if (r != curReg) + { + curReg = r; + cont.Add(curReg); + } + + dir = (dir+1) & 0x3; // Rotate CW + } + else + { + int ni = -1; + int nx = x + rcGetDirOffsetX(dir); + int ny = y + rcGetDirOffsetY(dir); + if (rcGetCon(s, dir) != RC_NOT_CONNECTED) + { + rcCompactCell nc = chf.cells[nx+ny*chf.width]; + ni = (int)nc.index + rcGetCon(s, dir); + } + if (ni == -1) + { + // Should not happen. + return; + } + x = nx; + y = ny; + i = ni; + dir = (dir+3) & 0x3; // Rotate CCW + } + + if (starti == i && startDir == dir) + { + break; + } + } + + // Remove adjacent duplicates. + if (cont.Count > 1) + { + for (int j = 0; j < cont.Count; ) + { + int nj = (j+1) % cont.Count; + if (cont[j] == cont[nj]) + { + for (int k = j; k < cont.Count-1; ++k) + cont[k] = cont[k+1]; + rccsPop(cont); + } + else + ++j; + } + } + } + + static bool filterSmallRegions(rcContext ctx, int minRegionArea, int mergeRegionSize, + ref ushort maxRegionId, + rcCompactHeightfield chf, + ushort[] srcReg) + { + int w = chf.width; + int h = chf.height; + + int nreg = maxRegionId+1; + + rcRegion[] regions = new rcRegion[nreg]; + if (regions == null) + { + ctx.log(rcLogCategory.RC_LOG_ERROR, "filterSmallRegions: Out of memory 'regions' (" +nreg+ ")."); + return false; + } + + // Construct regions + for (int i = 0; i < nreg; ++i){ + regions[i] = new rcRegion((ushort) i); + } + + // Find edge of a region and find connections around the contour. + for (int y = 0; y < h; ++y) + { + for (int x = 0; x < w; ++x) + { + rcCompactCell c = chf.cells[x+y*w]; + for (int i = (int)c.index, ni = (int)(c.index+c.count); i < ni; ++i) + { + ushort r = srcReg[i]; + if (r == 0 || r >= nreg) + continue; + + rcRegion reg = regions[r]; + reg.spanCount++; + + // Update floors. + for (int j = (int)c.index; j < ni; ++j) + { + if (i == j) continue; + ushort floorId = srcReg[j]; + if (floorId == 0 || floorId >= nreg) + continue; + addUniqueFloorRegion(reg, floorId); + } + + // Have found contour + if (reg.connections.Count > 0) + continue; + + reg.areaType = chf.areas[i]; + + // Check if this cell is next to a border. + int ndir = -1; + for (int dir = 0; dir < 4; ++dir) + { + if (isSolidEdge(chf, srcReg, x, y, i, dir)) + { + ndir = dir; + break; + } + } + + if (ndir != -1) + { + // The cell is at border. + // Walk around the contour to find all the neighbours. + walkContour(x, y, i, ndir, chf, srcReg, reg.connections); + } + } + } + } + + // Remove too small regions. + List stack = new List();//(32); + List trace= new List();//(32); + stack.Capacity = 32; + trace.Capacity = 32; + for (int i = 0; i < nreg; ++i) + { + rcRegion reg = regions[i]; + if (reg.id == 0 || (reg.id & RC_BORDER_REG) != 0) + continue; + if (reg.spanCount == 0) + continue; + if (reg.visited) + continue; + + // Count the total size of all the connected regions. + // Also keep track of the regions connects to a tile border. + bool connectsToBorder = false; + int spanCount = 0; + stack.Clear(); + trace.Clear(); + + reg.visited = true; + stack.Add(i); + + while (stack.Count != 0) + { + // Pop + int ri = rccsPop(stack); + + rcRegion creg = regions[ri]; + + spanCount += creg.spanCount; + trace.Add(ri); + + for (int j = 0; j < creg.connections.Count; ++j) + { + if ((creg.connections[j] & RC_BORDER_REG) != 0) + { + connectsToBorder = true; + continue; + } + rcRegion neireg = regions[creg.connections[j]]; + if (neireg.visited) + continue; + if (neireg.id == 0 || (neireg.id & RC_BORDER_REG) != 0) + continue; + // Visit + stack.Add(neireg.id); + neireg.visited = true; + } + } + + // If the accumulated regions size is too small, remove it. + // Do not remove areas which connect to tile borders + // as their size cannot be estimated correctly and removing them + // can potentially remove necessary areas. + if (spanCount < minRegionArea && !connectsToBorder) + { + // Kill all visited regions. + for (int j = 0; j < trace.Count; ++j) + { + regions[trace[j]].spanCount = 0; + regions[trace[j]].id = 0; + } + } + } + + // Merge too small regions to neighbour regions. + int mergeCount = 0 ; + do + { + mergeCount = 0; + for (int i = 0; i < nreg; ++i) + { + rcRegion reg = regions[i]; + if (reg.id == 0 || (reg.id & RC_BORDER_REG) != 0) + continue; + if (reg.spanCount == 0) + continue; + + // Check to see if the region should be merged. + if (reg.spanCount > mergeRegionSize && isRegionConnectedToBorder(reg)) + continue; + + // Small region with more than 1 connection. + // Or region which is not connected to a border at all. + // Find smallest neighbour region that connects to this one. + int smallest = 0xfffffff; + ushort mergeId = reg.id; + for (int j = 0; j < reg.connections.Count; ++j) + { + if ((reg.connections[j] & RC_BORDER_REG) != 0) + continue; + rcRegion mreg = regions[reg.connections[j]]; + if (mreg.id == 0 || (mreg.id & RC_BORDER_REG) != 0) + continue; + if (mreg.spanCount < smallest && + canMergeWithRegion(reg, mreg) && + canMergeWithRegion(mreg, reg)) + { + smallest = mreg.spanCount; + mergeId = mreg.id; + } + } + // Found new id. + if (mergeId != reg.id) + { + ushort oldId = reg.id; + rcRegion target = regions[mergeId]; + + // Merge neighbours. + if ( mergeRegions(target, reg)) + { + // Fixup regions pointing to current region. + for (int j = 0; j < nreg; ++j) + { + + + if (regions[j].id == 0 || (regions[j].id & RC_BORDER_REG) != 0) + continue; + // If another region was already merged into current region + // change the nid of the previous region too. + if (regions[j].id == oldId) + regions[j].id = mergeId; + // Replace the current region with the new one if the + // current regions is neighbour. + replaceNeighbour(regions[j], oldId, mergeId); + } + mergeCount++; + } + + } + } + } + while (mergeCount > 0); + + // Compress region Ids. + for (int i = 0; i < nreg; ++i) + { + regions[i].remap = false; + if (regions[i].id == 0) + continue; // Skip nil regions. + if ((regions[i].id & RC_BORDER_REG) != 0) + continue; // Skip external regions. + regions[i].remap = true; + } + + ushort regIdGen = 0; + for (int i = 0; i < nreg; ++i) + { + if (!regions[i].remap) + continue; + ushort oldId = regions[i].id; + ushort newId = ++regIdGen; + for (int j = i; j < nreg; ++j) + { + if (regions[j].id == oldId) + { + regions[j].id = newId; + regions[j].remap = false; + } + } + } + maxRegionId = regIdGen; + + // Remap regions. + for (int i = 0; i < chf.spanCount; ++i) + { + if ((srcReg[i] & RC_BORDER_REG) == 0) + srcReg[i] = regions[srcReg[i]].id; + } + + return true; + } + + /// @par + /// + /// This is usually the second to the last step in creating a fully built + /// compact heightfield. This step is required before regions are built + /// using #rcBuildRegions or #rcBuildRegionsMonotone. + /// + /// After this step, the distance data is available via the rcCompactHeightfield::maxDistance + /// and rcCompactHeightfield::dist fields. + /// + /// @see rcCompactHeightfield, rcBuildRegions, rcBuildRegionsMonotone + public static bool rcBuildDistanceField(rcContext ctx, rcCompactHeightfield chf) + { + Debug.Assert(ctx != null, "rcContext is null"); + + ctx.startTimer(rcTimerLabel.RC_TIMER_BUILD_DISTANCEFIELD); + + chf.dist = null; + + //ushort* src = (ushort*)rcAlloc(sizeof(ushort)*chf.spanCount, RC_ALLOC_TEMP); + ushort[] src = new ushort[chf.spanCount]; + if (src == null) + { + ctx.log(rcLogCategory.RC_LOG_ERROR, "rcBuildDistanceField: Out of memory 'src' ("+chf.spanCount+")."); + return false; + } + //ushort* dst = (ushort*)rcAlloc(sizeof(ushort)*chf.spanCount, RC_ALLOC_TEMP); + ushort[] dst = new ushort[chf.spanCount]; + if (dst == null) + { + ctx.log(rcLogCategory.RC_LOG_ERROR, "rcBuildDistanceField: Out of memory 'dst' ("+chf.spanCount+")."); + //rcFree(src); + return false; + } + + ushort maxDist = 0; + + ctx.startTimer(rcTimerLabel.RC_TIMER_BUILD_DISTANCEFIELD_DIST); + + calculateDistanceField(ctx, chf, src, ref maxDist); + chf.maxDistance = maxDist; + + ctx.stopTimer(rcTimerLabel.RC_TIMER_BUILD_DISTANCEFIELD_DIST); + + ctx.startTimer(rcTimerLabel.RC_TIMER_BUILD_DISTANCEFIELD_BLUR); + + // Blur + if (boxBlur(chf, 1, src, dst) != src){ + rcSwap(ref src,ref dst); + } + + // Store distance. + chf.dist = src; + + ctx.stopTimer(rcTimerLabel.RC_TIMER_BUILD_DISTANCEFIELD_BLUR); + + ctx.stopTimer(rcTimerLabel.RC_TIMER_BUILD_DISTANCEFIELD); + + //rcFree(dst); + dst = null; + + return true; + } + + public static void paintRectRegion(int minx, int maxx, int miny, int maxy, ushort regId, + rcCompactHeightfield chf, ushort[] srcReg) + { + int w = chf.width; + for (int y = miny; y < maxy; ++y) + { + for (int x = minx; x < maxx; ++x) + { + rcCompactCell c = chf.cells[x+y*w]; + for (int i = (int)c.index, ni = (int)(c.index+c.count); i < ni; ++i) + { + if (chf.areas[i] != RC_NULL_AREA) + srcReg[i] = regId; + } + } + } + } + + + const ushort RC_NULL_NEI = 0xffff; + + public class rcSweepSpan + { + public ushort rid = 0; // row id + public ushort id = 0; // region id + public ushort ns = 0; // number samples + public ushort nei = 0; // neighbour id + }; + + /// @par + /// + /// Non-null regions will consist of connected, non-overlapping walkable spans that form a single contour. + /// Contours will form simple polygons. + /// + /// If multiple regions form an area that is smaller than @p minRegionArea, then all spans will be + /// re-assigned to the zero (null) region. + /// + /// Partitioning can result in smaller than necessary regions. @p mergeRegionArea helps + /// reduce unecessarily small regions. + /// + /// See the #rcConfig documentation for more information on the configuration parameters. + /// + /// The region data will be available via the rcCompactHeightfield::maxRegions + /// and rcCompactSpan::reg fields. + /// + /// @warning The distance field must be created using #rcBuildDistanceField before attempting to build regions. + /// + /// @see rcCompactHeightfield, rcCompactSpan, rcBuildDistanceField, rcBuildRegionsMonotone, rcConfig + public static bool rcBuildRegionsMonotone(rcContext ctx, rcCompactHeightfield chf, + int borderSize, int minRegionArea, int mergeRegionArea) + { + Debug.Assert(ctx != null, "rcContext is null"); + + ctx.startTimer(rcTimerLabel.RC_TIMER_BUILD_REGIONS); + + int w = chf.width; + int h = chf.height; + ushort id = 1; + + ushort[] srcReg = new ushort[chf.spanCount]; + if (srcReg == null) + { + ctx.log(rcLogCategory.RC_LOG_ERROR, "rcBuildRegionsMonotone: Out of memory 'src' ("+chf.spanCount+")."); + return false; + } + + int nsweeps = Math.Max(chf.width,chf.height); + rcSweepSpan[] sweeps = new rcSweepSpan[nsweeps]; + rccsArrayItemsCreate(sweeps); + if (sweeps == null) + { + ctx.log(rcLogCategory.RC_LOG_ERROR, "rcBuildRegionsMonotone: Out of memory 'sweeps' ("+nsweeps+")."); + return false; + } + + // Mark border regions. + if (borderSize > 0) + { + // Make sure border will not overflow. + int bw = Math.Min(w, borderSize); + int bh = Math.Min(h, borderSize); + // Paint regions + paintRectRegion(0, bw, 0, h, (ushort)(id|RC_BORDER_REG), chf, srcReg); id++; + paintRectRegion(w-bw, w, 0, h, (ushort)(id|RC_BORDER_REG), chf, srcReg); id++; + paintRectRegion(0, w, 0, bh, (ushort)(id|RC_BORDER_REG), chf, srcReg); id++; + paintRectRegion(0, w, h-bh, h, (ushort)(id|RC_BORDER_REG), chf, srcReg); id++; + + chf.borderSize = borderSize; + } + + List prev = new List();//256 + prev.Capacity = 256; + // Sweep one line at a time. + for (int y = borderSize; y < h-borderSize; ++y) + { + // Collect spans from this row. + rccsResizeList(prev, id+1); + for (int i=0;i 0 && srcReg[i] < rid) + srcReg[i] = sweeps[srcReg[i]].id; + } + } + } + + ctx.startTimer(rcTimerLabel.RC_TIMER_BUILD_REGIONS_FILTER); + + // Filter out small regions. + chf.maxRegions = id; + if (!filterSmallRegions(ctx, minRegionArea, mergeRegionArea, ref chf.maxRegions, chf, srcReg)) + return false; + + ctx.stopTimer(rcTimerLabel.RC_TIMER_BUILD_REGIONS_FILTER); + + // Store the result out. + for (int i = 0; i < chf.spanCount; ++i) + chf.spans[i].reg = srcReg[i]; + + ctx.stopTimer(rcTimerLabel.RC_TIMER_BUILD_REGIONS); + + return true; + } + + /// @par + /// + /// Non-null regions will consist of connected, non-overlapping walkable spans that form a single contour. + /// Contours will form simple polygons. + /// + /// If multiple regions form an area that is smaller than @p minRegionArea, then all spans will be + /// re-assigned to the zero (null) region. + /// + /// Watershed partitioning can result in smaller than necessary regions, especially in diagonal corridors. + /// @p mergeRegionArea helps reduce unecessarily small regions. + /// + /// See the #rcConfig documentation for more information on the configuration parameters. + /// + /// The region data will be available via the rcCompactHeightfield::maxRegions + /// and rcCompactSpan::reg fields. + /// + /// @warning The distance field must be created using #rcBuildDistanceField before attempting to build regions. + /// + /// @see rcCompactHeightfield, rcCompactSpan, rcBuildDistanceField, rcBuildRegionsMonotone, rcConfig + public static bool rcBuildRegions(rcContext ctx, rcCompactHeightfield chf, + int borderSize, int minRegionArea, int mergeRegionArea) + { + Debug.Assert(ctx != null, "rcContext is null"); + + ctx.startTimer(rcTimerLabel.RC_TIMER_BUILD_REGIONS); + + int w = chf.width; + int h = chf.height; + + //rcScopedDelete buf = (ushort*)rcAlloc(sizeof(ushort)*chf.spanCount*4, RC_ALLOC_TEMP); + /* + ushort[] buf = new ushort[chf.spanCount*4]; + if (buf == null) + { + ctx.log(rcLogCategory.RC_LOG_ERROR, "rcBuildRegions: Out of memory 'tmp' ("+chf.spanCount*4+")."); + return false; + } + */ + ctx.startTimer(rcTimerLabel.RC_TIMER_BUILD_REGIONS_WATERSHED); + + const int LOG_NB_STACKS = 3; + const int NB_STACKS = 1 << LOG_NB_STACKS; + List[] lvlStacks = new List[NB_STACKS]; + for (int i = 0; i < NB_STACKS; ++i) { + lvlStacks[i] = new List(); + //rccsResizeList(lvlStacks[i], 1024); + lvlStacks[i].Capacity = 1024; + } + + List stack = new List();//(1024); + List visited = new List();//(1024); + stack.Capacity = 1024; + visited.Capacity = 1024; + //rccResizeList(stack, 1024); + //rccResizeList(visited, 1024); + + ushort[] srcReg = new ushort[chf.spanCount]; + ushort[] srcDist = new ushort[chf.spanCount];//buf+chf.spanCount; + ushort[] dstReg = new ushort[chf.spanCount];// buf+chf.spanCount*2; + ushort[] dstDist = new ushort[chf.spanCount];//buf+chf.spanCount*3; + + //memset(srcReg, 0, sizeof(ushort)*chf.spanCount); + //memset(srcDist, 0, sizeof(ushort)*chf.spanCount); + + ushort regionId = 1; + ushort level = (ushort)((chf.maxDistance+1) & ~1); + + // TODO: Figure better formula, expandIters defines how much the + // watershed "overflows" and simplifies the regions. Tying it to + // agent radius was usually good indication how greedy it could be. + // const int expandIters = 4 + walkableRadius * 2; + const int expandIters = 8; + + if (borderSize > 0) + { + // Make sure border will not overflow. + int bw = Math.Min(w, borderSize); + int bh = Math.Min(h, borderSize); + // Paint regions + paintRectRegion(0, bw, 0, h,(ushort)( regionId|RC_BORDER_REG ), chf, srcReg); regionId++; + paintRectRegion(w - bw, w, 0, h, (ushort)(regionId | RC_BORDER_REG), chf, srcReg); regionId++; + paintRectRegion(0, w, 0, bh, (ushort)(regionId | RC_BORDER_REG), chf, srcReg); regionId++; + paintRectRegion(0, w, h - bh, h, (ushort)(regionId | RC_BORDER_REG), chf, srcReg); regionId++; + + chf.borderSize = borderSize; + } + + int sId = -1; + while (level > 0) + { + level = (ushort)(level >= 2 ? level-2 : 0); + sId = (sId+1) & (NB_STACKS-1); + + // ctx.startTimer(rcTimerLabel.RC_TIMER_DIVIDE_TO_LEVELS); + + if (sId == 0) + sortCellsByLevel(level, chf, srcReg, NB_STACKS, lvlStacks, 1); + else + appendStacks(lvlStacks[sId-1], lvlStacks[sId], srcReg); // copy left overs from last level + + // ctx.stopTimer(rcTimerLabel.RC_TIMER_DIVIDE_TO_LEVELS); + + ctx.startTimer(rcTimerLabel.RC_TIMER_BUILD_REGIONS_EXPAND); + + // Expand current regions until no empty connected cells found. + if (expandRegions(expandIters, level, chf, srcReg, srcDist, dstReg, dstDist, lvlStacks[sId], false) != srcReg) + { + rcSwap(ref srcReg,ref dstReg); + rcSwap(ref srcDist,ref dstDist); + } + + ctx.stopTimer(rcTimerLabel.RC_TIMER_BUILD_REGIONS_EXPAND); + + ctx.startTimer(rcTimerLabel.RC_TIMER_BUILD_REGIONS_FLOOD); + + // Mark new regions with IDs. + for (int j=0; j= 0 && srcReg[i] == 0) + { + if (floodRegion(x, y, i, level, regionId, chf, srcReg, srcDist, stack)) + regionId++; + } + } + + ctx.stopTimer(rcTimerLabel.RC_TIMER_BUILD_REGIONS_FLOOD); + } + + // Expand current regions until no empty connected cells found. + if (expandRegions(expandIters*8, 0, chf, srcReg, srcDist, dstReg, dstDist, stack, true) != srcReg) + { + rcSwap(ref srcReg,ref dstReg); + rcSwap(ref srcDist,ref dstDist); + } + + ctx.stopTimer(rcTimerLabel.RC_TIMER_BUILD_REGIONS_WATERSHED); + + ctx.startTimer(rcTimerLabel.RC_TIMER_BUILD_REGIONS_FILTER); + + // Filter out small regions. + chf.maxRegions = regionId; + if (!filterSmallRegions(ctx, minRegionArea, mergeRegionArea, ref chf.maxRegions, chf, srcReg)) + return false; + + ctx.stopTimer(rcTimerLabel.RC_TIMER_BUILD_REGIONS_FILTER); + + // Write the result out. + for (int i = 0; i < chf.spanCount; ++i) + chf.spans[i].reg = srcReg[i]; + + ctx.stopTimer(rcTimerLabel.RC_TIMER_BUILD_REGIONS); + + return true; + } +} \ No newline at end of file diff --git a/Framework/Rest/Authentication/LogonData.cs b/Framework/Rest/Authentication/LogonData.cs new file mode 100644 index 000000000..fd09cf0fd --- /dev/null +++ b/Framework/Rest/Authentication/LogonData.cs @@ -0,0 +1,41 @@ +/* + * Copyright (C) 2012-2017 CypherCore + * + * 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, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +using System.Collections.Generic; +using System.Linq; +using System.Runtime.Serialization; + +namespace Framework.Rest +{ + [DataContract] + public class LogonData + { + public string this[string inputId] => Inputs.SingleOrDefault(i => i.Id == inputId)?.Value; + + [DataMember(Name = "version")] + public string Version { get; set; } + + [DataMember(Name = "program_id")] + public string Program { get; set; } + + [DataMember(Name = "platform_id")] + public string Platform { get; set; } + + [DataMember(Name = "inputs")] + public List Inputs { get; set; } = new List(); + } +} diff --git a/Framework/Rest/Authentication/LogonResult.cs b/Framework/Rest/Authentication/LogonResult.cs new file mode 100644 index 000000000..7aa38add3 --- /dev/null +++ b/Framework/Rest/Authentication/LogonResult.cs @@ -0,0 +1,52 @@ +/* + * Copyright (C) 2012-2017 CypherCore + * + * 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, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +using System.Runtime.Serialization; + +namespace Framework.Rest +{ + [DataContract] + public class LogonResult + { + [DataMember(Name = "authentication_state")] + public string AuthenticationState { get; set; } + + [DataMember(Name = "login_ticket")] + public string LoginTicket { get; set; } + + [DataMember(Name = "error_code")] + public string ErrorCode { get; set; } + + [DataMember(Name = "error_message")] + public string ErrorMessage { get; set; } + + [DataMember(Name = "support_error_code")] + public string SupportErrorCode { get; set; } + + [DataMember(Name = "authenticator_form")] + public FormInputs AuthenticatorForm { get; set; } = new FormInputs(); + } + + public enum AuthenticationState + { + NONE = 0, + LOGIN = 1, + LEGAL = 2, + AUTHENTICATOR = 3, + DONE = 4, + } +} diff --git a/Framework/Rest/Forms/FormInput.cs b/Framework/Rest/Forms/FormInput.cs new file mode 100644 index 000000000..26e368ff4 --- /dev/null +++ b/Framework/Rest/Forms/FormInput.cs @@ -0,0 +1,37 @@ +/* + * Copyright (C) 2012-2017 CypherCore + * + * 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, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +using System.Runtime.Serialization; + +namespace Framework.Rest +{ + [DataContract] + public class FormInput + { + [DataMember(Name = "input_id")] + public string Id { get; set; } + + [DataMember(Name = "type")] + public string Type { get; set; } + + [DataMember(Name = "label")] + public string Label { get; set; } + + [DataMember(Name = "max_length")] + public int MaxLength { get; set; } + } +} diff --git a/Framework/Rest/Forms/FormInputValue.cs b/Framework/Rest/Forms/FormInputValue.cs new file mode 100644 index 000000000..d82e9b542 --- /dev/null +++ b/Framework/Rest/Forms/FormInputValue.cs @@ -0,0 +1,31 @@ +/* + * Copyright (C) 2012-2017 CypherCore + * + * 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, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +using System.Runtime.Serialization; + +namespace Framework.Rest +{ + [DataContract] + public class FormInputValue + { + [DataMember(Name = "input_id")] + public string Id { get; set; } + + [DataMember(Name = "value")] + public string Value { get; set; } + } +} diff --git a/Framework/Rest/Forms/FormInputs.cs b/Framework/Rest/Forms/FormInputs.cs new file mode 100644 index 000000000..42d14d511 --- /dev/null +++ b/Framework/Rest/Forms/FormInputs.cs @@ -0,0 +1,35 @@ +/* + * Copyright (C) 2012-2017 CypherCore + * + * 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, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +using System.Collections.Generic; +using System.Runtime.Serialization; + +namespace Framework.Rest +{ + [DataContract] + public class FormInputs + { + [DataMember(Name = "type")] + public string Type { get; set; } + + [DataMember(Name = "prompt")] + public string Prompt { get; set; } + + [DataMember(Name = "inputs")] + public List Inputs { get; set; } = new List(); + } +} diff --git a/Framework/Rest/Misc/Address.cs b/Framework/Rest/Misc/Address.cs new file mode 100644 index 000000000..9ee3b49a0 --- /dev/null +++ b/Framework/Rest/Misc/Address.cs @@ -0,0 +1,31 @@ +/* + * Copyright (C) 2012-2017 CypherCore + * + * 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, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +using System.Runtime.Serialization; + +namespace Framework.Rest +{ + [DataContract] + public class Address + { + [DataMember(Name = "ip")] + public string Ip { get; set; } + + [DataMember(Name = "port")] + public int Port { get; set; } + } +} diff --git a/Framework/Rest/Misc/AddressFamily.cs b/Framework/Rest/Misc/AddressFamily.cs new file mode 100644 index 000000000..1bf80f21d --- /dev/null +++ b/Framework/Rest/Misc/AddressFamily.cs @@ -0,0 +1,32 @@ +/* + * Copyright (C) 2012-2017 CypherCore + * + * 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, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +using System.Collections.Generic; +using System.Runtime.Serialization; + +namespace Framework.Rest +{ + [DataContract] + public class AddressFamily + { + [DataMember(Name = "family")] + public int Id { get; set; } + + [DataMember(Name = "addresses")] + public IList
Addresses { get; set; } = new List
(); + } +} diff --git a/Framework/Rest/Misc/ClientVersion.cs b/Framework/Rest/Misc/ClientVersion.cs new file mode 100644 index 000000000..414488492 --- /dev/null +++ b/Framework/Rest/Misc/ClientVersion.cs @@ -0,0 +1,37 @@ +/* + * Copyright (C) 2012-2017 CypherCore + * + * 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, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +using System.Runtime.Serialization; + +namespace Framework.Rest +{ + [DataContract] + public class ClientVersion + { + [DataMember(Name = "versionMajor")] + public int Major { get; set; } + + [DataMember(Name = "versionBuild")] + public int Build { get; set; } + + [DataMember(Name = "versionMinor")] + public int Minor { get; set; } + + [DataMember(Name = "versionRevision")] + public int Revision { get; set; } + } +} diff --git a/Framework/Rest/Misc/RealmCharacterCountEntry.cs b/Framework/Rest/Misc/RealmCharacterCountEntry.cs new file mode 100644 index 000000000..ff070e736 --- /dev/null +++ b/Framework/Rest/Misc/RealmCharacterCountEntry.cs @@ -0,0 +1,31 @@ +/* + * Copyright (C) 2012-2017 CypherCore + * + * 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, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +using System.Runtime.Serialization; + +namespace Framework.Rest +{ + [DataContract] + public class RealmCharacterCountEntry + { + [DataMember(Name = "wowRealmAddress")] + public int WowRealmAddress { get; set; } + + [DataMember(Name = "count")] + public int Count { get; set; } + } +} diff --git a/Framework/Rest/Realmlist/RealmCharacterCountList.cs b/Framework/Rest/Realmlist/RealmCharacterCountList.cs new file mode 100644 index 000000000..bc70767ba --- /dev/null +++ b/Framework/Rest/Realmlist/RealmCharacterCountList.cs @@ -0,0 +1,29 @@ +/* + * Copyright (C) 2012-2017 CypherCore + * + * 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, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +using System.Collections.Generic; +using System.Runtime.Serialization; + +namespace Framework.Rest +{ + [DataContract] + public class RealmCharacterCountList + { + [DataMember(Name = "counts")] + public IList Counts { get; set; } = new List(); + } +} diff --git a/Framework/Rest/Realmlist/RealmEntry.cs b/Framework/Rest/Realmlist/RealmEntry.cs new file mode 100644 index 000000000..2394e5dd2 --- /dev/null +++ b/Framework/Rest/Realmlist/RealmEntry.cs @@ -0,0 +1,56 @@ +/* + * Copyright (C) 2012-2017 CypherCore + * + * 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, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +using System.Runtime.Serialization; + +namespace Framework.Rest +{ + [DataContract] + public class RealmEntry + { + + [DataMember(Name = "wowRealmAddress")] + public int WowRealmAddress { get; set; } + + [DataMember(Name = "cfgTimezonesID")] + public int CfgTimezonesID { get; set; } + + [DataMember(Name = "populationState")] + public int PopulationState { get; set; } + + [DataMember(Name = "cfgCategoriesID")] + public int CfgCategoriesID { get; set; } + + [DataMember(Name = "version")] + public ClientVersion Version { get; set; } = new ClientVersion(); + + [DataMember(Name = "cfgRealmsID")] + public int CfgRealmsID { get; set; } + + [DataMember(Name = "flags")] + public int Flags { get; set; } + + [DataMember(Name = "name")] + public string Name { get; set; } + + [DataMember(Name = "cfgConfigsID")] + public int CfgConfigsID { get; set; } + + [DataMember(Name = "cfgLanguagesID")] + public int CfgLanguagesID { get; set; } + } +} diff --git a/Framework/Rest/Realmlist/RealmListServerIPAddresses.cs b/Framework/Rest/Realmlist/RealmListServerIPAddresses.cs new file mode 100644 index 000000000..ace2dba2e --- /dev/null +++ b/Framework/Rest/Realmlist/RealmListServerIPAddresses.cs @@ -0,0 +1,29 @@ +/* + * Copyright (C) 2012-2017 CypherCore + * + * 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, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +using System.Collections.Generic; +using System.Runtime.Serialization; + +namespace Framework.Rest +{ + [DataContract] + public class RealmListServerIPAddresses + { + [DataMember(Name = "families")] + public IList Families { get; set; } = new List(); + } +} diff --git a/Framework/Rest/Realmlist/RealmListTicketClientInformation.cs b/Framework/Rest/Realmlist/RealmListTicketClientInformation.cs new file mode 100644 index 000000000..f732b4d40 --- /dev/null +++ b/Framework/Rest/Realmlist/RealmListTicketClientInformation.cs @@ -0,0 +1,28 @@ +/* + * Copyright (C) 2012-2017 CypherCore + * + * 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, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +using System.Runtime.Serialization; + +namespace Framework.Rest +{ + [DataContract] + public class RealmListTicketClientInformation + { + [DataMember(Name = "info")] + public RealmListTicketInformation Info { get; set; } = new RealmListTicketInformation(); + } +} diff --git a/Framework/Rest/Realmlist/RealmListTicketIdentity.cs b/Framework/Rest/Realmlist/RealmListTicketIdentity.cs new file mode 100644 index 000000000..bf6ed5282 --- /dev/null +++ b/Framework/Rest/Realmlist/RealmListTicketIdentity.cs @@ -0,0 +1,31 @@ +/* + * Copyright (C) 2012-2017 CypherCore + * + * 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, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +using System.Runtime.Serialization; + +namespace Framework.Rest +{ + [DataContract] + public class RealmListTicketIdentity + { + [DataMember(Name = "gameAccountID")] + public int GameAccountId { get; set; } + + [DataMember(Name = "gameAccountRegion")] + public int GameAccountRegion { get; set; } + } +} diff --git a/Framework/Rest/Realmlist/RealmListTicketInformation.cs b/Framework/Rest/Realmlist/RealmListTicketInformation.cs new file mode 100644 index 000000000..b89a25181 --- /dev/null +++ b/Framework/Rest/Realmlist/RealmListTicketInformation.cs @@ -0,0 +1,68 @@ +/* + * Copyright (C) 2012-2017 CypherCore + * + * 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, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +using System.Collections.Generic; +using System.Runtime.Serialization; + +namespace Framework.Rest +{ + [DataContract] + public class RealmListTicketInformation + { + [DataMember(Name = "platform")] + public int Platform { get; set; } + + [DataMember(Name = "buildVariant")] + public string BuildVariant { get; set; } + + [DataMember(Name = "type")] + public int Type { get; set; } + + [DataMember(Name = "timeZone")] + public string Timezone { get; set; } + + [DataMember(Name = "currentTime")] + public int CurrentTime { get; set; } + + [DataMember(Name = "textLocale")] + public int TextLocale { get; set; } + + [DataMember(Name = "audioLocale")] + public int AudioLocale { get; set; } + + [DataMember(Name = "versionDataBuild")] + public int VersionDataBuild { get; set; } + + [DataMember(Name = "version")] + public ClientVersion ClientVersion { get; set; } = new ClientVersion(); + + [DataMember(Name = "secret")] + public List Secret { get; set; } + + [DataMember(Name = "clientArch")] + public int ClientArch { get; set; } + + [DataMember(Name = "systemVersion")] + public string SystemVersion { get; set; } + + [DataMember(Name = "platformType")] + public int PlatformType { get; set; } + + [DataMember(Name = "systemArch")] + public int SystemArch { get; set; } + } +} diff --git a/Framework/Rest/Realmlist/RealmListUpdate.cs b/Framework/Rest/Realmlist/RealmListUpdate.cs new file mode 100644 index 000000000..2b276c822 --- /dev/null +++ b/Framework/Rest/Realmlist/RealmListUpdate.cs @@ -0,0 +1,31 @@ +/* + * Copyright (C) 2012-2017 CypherCore + * + * 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, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +using System.Runtime.Serialization; + +namespace Framework.Rest +{ + [DataContract] + public class RealmListUpdate + { + [DataMember(Name = "update")] + public RealmEntry Update { get; set; } = new RealmEntry(); + + [DataMember(Name = "deleting")] + public bool Deleting { get; set; } + } +} diff --git a/Framework/Rest/Realmlist/RealmListUpdates.cs b/Framework/Rest/Realmlist/RealmListUpdates.cs new file mode 100644 index 000000000..90007a995 --- /dev/null +++ b/Framework/Rest/Realmlist/RealmListUpdates.cs @@ -0,0 +1,29 @@ +/* + * Copyright (C) 2012-2017 CypherCore + * + * 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, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +using System.Collections.Generic; +using System.Runtime.Serialization; + +namespace Framework.Rest +{ + [DataContract] + public class RealmListUpdates + { + [DataMember(Name = "updates")] + public IList Updates { get; set; } = new List(); + } +} diff --git a/Framework/Runtime/ApplicationSignal.cs b/Framework/Runtime/ApplicationSignal.cs new file mode 100644 index 000000000..9740e1dbc --- /dev/null +++ b/Framework/Runtime/ApplicationSignal.cs @@ -0,0 +1,68 @@ +/* + * Copyright (C) 2012-2017 CypherCore + * + * 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, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +using System.Runtime.InteropServices; +using System; +using System.Diagnostics; + +namespace Framework.Runtime +{ + public delegate bool EventHandler(CtrlType sig); + + public class ApplicationSignal + { + [DllImport("Kernel32")] + public static extern bool SetConsoleCtrlHandler(EventHandler handler, bool add); + + [DllImport("kernel32.dll", SetLastError = true)] + static extern IntPtr GetStdHandle(int nStdHandle); + + [DllImport("kernel32.dll")] + static extern bool GetConsoleMode(IntPtr hConsoleHandle, out uint lpMode); + + [DllImport("kernel32.dll")] + static extern bool SetConsoleMode(IntPtr hConsoleHandle, uint dwMode); + + public static void RemoveConsoleQuickEditMode() + { + IntPtr handle = GetStdHandle(-10); + + // get current console mode + uint consoleMode; + if (!GetConsoleMode(handle, out consoleMode)) + { + // ERROR: Unable to get console mode. + return; + } + + // Clear the quick edit bit in the mode flags + consoleMode &= ~0x0040u; + + // set the new mode + SetConsoleMode(handle, consoleMode); + } + } + + public enum CtrlType + { + CTRL_C_EVENT = 0, + CTRL_BREAK_EVENT = 1, + CTRL_CLOSE_EVENT = 2, + CTRL_LOGOFF_EVENT = 5, + CTRL_SHUTDOWN_EVENT = 6 + } +} diff --git a/Framework/Serialization/Json.cs b/Framework/Serialization/Json.cs new file mode 100644 index 000000000..9faa445ad --- /dev/null +++ b/Framework/Serialization/Json.cs @@ -0,0 +1,79 @@ +/* + * Copyright (C) 2012-2017 CypherCore + * + * 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, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +using System; +using System.IO; +using System.Runtime.Serialization.Json; +using System.Text; + +namespace Framework.Serialization +{ + public class Json + { + public static string CreateString(T dataObject) + { + return Encoding.UTF8.GetString(CreateArray(dataObject)); + } + + public static byte[] CreateArray(T dataObject) + { + var serializer = new DataContractJsonSerializer(typeof(T)); + var stream = new MemoryStream(); + + serializer.WriteObject(stream, dataObject); + + return stream.ToArray(); + } + + public static T CreateObject(Stream jsonData) + { + var serializer = new DataContractJsonSerializer(typeof(T)); + + return (T)serializer.ReadObject(jsonData); + } + + public static T CreateObject(string jsonData, bool split = false) + { + return CreateObject(Encoding.UTF8.GetBytes(split ? jsonData.Split(new[] { ':' }, 2)[1] : jsonData)); + } + + public static T CreateObject(byte[] jsonData) => (T)CreateObject(new MemoryStream(jsonData)); + + public static object CreateObject(Stream jsonData, Type type) + { + var serializer = new DataContractJsonSerializer(type); + + return serializer.ReadObject(jsonData); + } + + public static object CreateObject(string jsonData, Type type, bool split = false) + { + return CreateObject(Encoding.UTF8.GetBytes(split ? jsonData.Split(new[] { ':' }, 2)[1] : jsonData), type); + } + + public static object CreateObject(byte[] jsonData, Type type) => CreateObject(new MemoryStream(jsonData), type); + + // Used for protobuf json strings. + public static byte[] Deflate(string name, T data) + { + var jsonData = Encoding.UTF8.GetBytes(name + ":" + CreateString(data) + "\0"); + var compressedData = IO.ZLib.Compress(jsonData); + + return BitConverter.GetBytes(jsonData.Length).Combine(compressedData); + } + } +} diff --git a/Framework/Singleton/Singleton.cs b/Framework/Singleton/Singleton.cs new file mode 100644 index 000000000..f44960018 --- /dev/null +++ b/Framework/Singleton/Singleton.cs @@ -0,0 +1,47 @@ +/* + * Copyright (C) 2012-2017 CypherCore + * + * 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, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +using System; +using System.Reflection; + +public class Singleton where T : class +{ + private static volatile T instance; + private static object syncRoot = new Object(); + + public Singleton() { } + + public static T Instance + { + get + { + if (instance == null) + { + lock (syncRoot) + { + if (instance == null) + { + ConstructorInfo constructorInfo = typeof(T).GetConstructor(BindingFlags.NonPublic | BindingFlags.Instance, null, Type.EmptyTypes, null); + instance = (T)constructorInfo.Invoke(new object[0]); + } + } + } + + return instance; + } + } +} diff --git a/Framework/Util/CollectionExtensions.cs b/Framework/Util/CollectionExtensions.cs new file mode 100644 index 000000000..808332aab --- /dev/null +++ b/Framework/Util/CollectionExtensions.cs @@ -0,0 +1,176 @@ +/* + * Copyright (C) 2012-2017 CypherCore + * + * 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, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +using System.Linq; + +namespace System.Collections.Generic +{ + public static class CollectionExtensions + { + public static bool Empty(this ICollection collection) + { + return collection.Count == 0; + } + + public static bool Empty(this IDictionary dictionary) + { + return dictionary.Count == 0; + } + + /// + /// Returns the entry in this list at the given index, or the default value of the element + /// type if the index was out of bounds. + /// + /// The type of the elements in the list. + /// The list to retrieve from. + /// The index to try to retrieve at. + /// The value, or the default value of the element type. + public static T LookupByIndex(this IList list, int index) + { + return index >= list.Count ? default(T) : list[index]; + } + + /// + /// Returns the entry in this dictionary at the given key, or the default value of the key + /// if none. + /// + /// The key type. + /// The value type. + /// The dictionary to operate on. + /// The key of the element to retrieve. + /// The value (if any). + public static TValue LookupByKey(this IDictionary dict, object key) + { + TValue val; + TKey newkey = (TKey)Convert.ChangeType(key, typeof(TKey)); + return dict.TryGetValue(newkey, out val) ? val : default(TValue); + } + public static TValue LookupByKey(this IDictionary dict, TKey key) + { + TValue val; + return dict.TryGetValue(key, out val) ? val : default(TValue); + } + public static KeyValuePair Find(this IDictionary dict, TKey key) + { + return new KeyValuePair(key, dict[key]); + } + + public static bool ContainsKey(this IDictionary dict, object key) + { + TKey newkey = (TKey)Convert.ChangeType(key, typeof(TKey)); + return dict.ContainsKey(newkey); + } + + public static void RemoveAll(this List collection, ICheck check) + { + collection.RemoveAll(check.Invoke); + } + + public static T PickRandom(this IEnumerable source) + { + return source.PickRandom(1).Single(); + } + + public static IEnumerable PickRandom(this IEnumerable source, uint count) + { + return source.Shuffle().Take((int)count); + } + + public static IEnumerable Shuffle(this IEnumerable source) + { + return source.OrderBy(x => Guid.NewGuid()); + } + + public static void Swap(this T[] array, int position1, int position2) + { + // + // Swaps elements in an array. Doesn't need to return a reference. + // + T temp = array[position1]; // Copy the first position's element + array[position1] = array[position2]; // Assign to the second element + array[position2] = temp; // Assign to the first element + } + + public static void Resize(this List list, uint size) + { + int cur = list.Count; + if (size < cur) + list.RemoveRange((int)size, cur - (int)size); + } + + public static void RandomResize(this IList list, uint size) + { + int listSize = list.Count; + + while (listSize > size) + { + list.RemoveAt(RandomHelper.IRand(0, listSize)); + --listSize; + } + } + + public static void RandomResize(this ICollection list, Predicate predicate, uint size) + { + List listCopy = new List(); + foreach (var obj in list) + if (predicate(obj)) + listCopy.Add(obj); + + if (size != 0) + listCopy.Resize(size); + + list = listCopy; + } + + public static T SelectRandomElementByWeight(this IEnumerable sequence, Func weightSelector) + { + float totalWeight = sequence.Sum(weightSelector); + // The weight we are after... + float itemWeightIndex = (float)RandomHelper.NextDouble() * totalWeight; + float currentWeightIndex = 0; + + foreach (var item in from weightedItem in sequence select new { Value = weightedItem, Weight = weightSelector(weightedItem) }) + { + currentWeightIndex += item.Weight; + + // If we've hit or passed the weight we are after for this item then it's the one we want.... + if (currentWeightIndex >= itemWeightIndex) + return item.Value; + + } + + return default(T); + } + + public static uint[] ToBlockRange(this BitSet array) + { + uint[] blockValues = new uint[array.Length / 32 + 1]; + array.CopyTo(blockValues, 0); + return blockValues; + } + } + + public interface ICheck + { + bool Invoke(T obj); + } + + public interface IDoWork + { + void Invoke(T obj); + } +} diff --git a/Framework/Util/Extensions.cs b/Framework/Util/Extensions.cs new file mode 100644 index 000000000..f60e61dc2 --- /dev/null +++ b/Framework/Util/Extensions.cs @@ -0,0 +1,372 @@ +/* + * Copyright (C) 2012-2017 CypherCore + * + * 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, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Linq.Expressions; +using System.Numerics; +using System.Reflection; +using System.Reflection.Emit; +using System.Runtime.InteropServices; +using System.Text; +using System.Text.RegularExpressions; + +namespace System +{ + public static class Extensions + { + public static bool HasAnyFlag(this T value, T flag) where T : struct + { + long lValue = Convert.ToInt64(value); + long lFlag = Convert.ToInt64(flag); + return (lValue & lFlag) != 0; + } + + public static string ToHexString(this byte[] byteArray) + { + return byteArray.Aggregate("", (current, b) => current + b.ToString("X2")); + } + + public static byte[] ToByteArray(this string str) + { + str = str.Replace(" ", String.Empty); + + var res = new byte[str.Length / 2]; + for (int i = 0; i < res.Length; ++i) + { + string temp = String.Concat(str[i * 2], str[i * 2 + 1]); + res[i] = Convert.ToByte(temp, 16); + } + return res; + } + + public static byte[] ToByteArray(this string value, char separator) + { + return Array.ConvertAll(value.Split(separator), s => byte.Parse(s)); + } + + static uint LeftRotate(this uint value, int shiftCount) + { + return (value << shiftCount) | (value >> (0x20 - shiftCount)); + } + + public static byte[] GenerateRandomKey(this byte[] s, int length) + { + var random = new Random((int)((uint)(Guid.NewGuid().GetHashCode() ^ 1 >> 89 << 2 ^ 42)).LeftRotate(13)); + var key = new byte[length]; + + for (int i = 0; i < length; i++) + { + int randValue = -1; + + do + { + randValue = (int)((uint)random.Next(0xFF)).LeftRotate(1) ^ i; + } while (randValue > 0xFF && randValue <= 0); + + key[i] = (byte)randValue; + } + + return key; + } + + public static bool Compare(this byte[] b, byte[] b2) + { + for (int i = 0; i < b2.Length; i++) + if (b[i] != b2[i]) + return false; + + return true; + } + + public static byte[] Combine(this byte[] data, params byte[][] pData) + { + var combined = data; + + foreach (var arr in pData) + { + var currentSize = combined.Length; + + Array.Resize(ref combined, currentSize + arr.Length); + + Buffer.BlockCopy(arr, 0, combined, currentSize, arr.Length); + } + + return combined; + } + + public static object[] Combine(this object[] data, params object[][] pData) + { + var combined = data; + + foreach (var arr in pData) + { + var currentSize = combined.Length; + + Array.Resize(ref combined, currentSize + arr.Length); + + Array.Copy(arr, 0, combined, currentSize, arr.Length); + } + + return combined; + } + + public static BigInteger ToBigInteger(this T value, bool isBigEndian = false) + { + var ret = BigInteger.Zero; + + switch (typeof(T).Name) + { + case "Byte[]": + var data = value as byte[]; + + if (isBigEndian) + Array.Reverse(data); + + ret = new BigInteger(data.Combine(new byte[] { 0 })); + break; + case "BigInteger": + ret = (BigInteger)Convert.ChangeType(value, typeof(BigInteger)); + break; + default: + throw new NotSupportedException(string.Format("'{0}' conversion to 'BigInteger' not supported.", typeof(T).Name)); + } + + return ret; + } + + public static void Swap(ref T left, ref T right) + { + T temp = left; + left = right; + right = temp; + } + + public static Func CompileGetter(this FieldInfo field) + { + string methodName = field.ReflectedType.FullName + ".get_" + field.Name; + DynamicMethod setterMethod = new DynamicMethod(methodName, typeof(object), new[] { typeof(object) }, true); + ILGenerator gen = setterMethod.GetILGenerator(); + if (field.IsStatic) + { + gen.Emit(OpCodes.Ldsfld, field); + gen.Emit(field.FieldType.IsClass ? OpCodes.Castclass : OpCodes.Box, field.FieldType); + } + else + { + gen.Emit(OpCodes.Ldarg_0); + gen.Emit(OpCodes.Castclass, field.DeclaringType); + gen.Emit(OpCodes.Ldfld, field); + gen.Emit(field.FieldType.IsClass ? OpCodes.Castclass : OpCodes.Box, field.FieldType); + } + gen.Emit(OpCodes.Ret); + return (Func)setterMethod.CreateDelegate(typeof(Func)); + } + + public static Action CompileSetter(this FieldInfo field) + { + string methodName = field.ReflectedType.FullName + ".set_" + field.Name; + DynamicMethod setterMethod = new DynamicMethod(methodName, null, new[] { typeof(object), typeof(object) }, true); + ILGenerator gen = setterMethod.GetILGenerator(); + if (field.IsStatic) + { + gen.Emit(OpCodes.Ldarg_1); + gen.Emit(field.FieldType.IsClass ? OpCodes.Castclass : OpCodes.Unbox_Any, field.FieldType); + gen.Emit(OpCodes.Stsfld, field); + } + else + { + gen.Emit(OpCodes.Ldarg_0); + gen.Emit(OpCodes.Castclass, field.DeclaringType); + gen.Emit(OpCodes.Ldarg_1); + gen.Emit(field.FieldType.IsClass ? OpCodes.Castclass : OpCodes.Unbox_Any, field.FieldType); + gen.Emit(OpCodes.Stfld, field); + } + gen.Emit(OpCodes.Ret); + return (Action)setterMethod.CreateDelegate(typeof(Action)); + } + + public static Func GetGetter(this FieldInfo fieldInfo) + { + var paramExpression = Expression.Parameter(typeof(T)); + var propertyExpression = Expression.Field(paramExpression, fieldInfo); + var convertExpression = Expression.TypeAs(propertyExpression, typeof(object)); + + return Expression.Lambda>(convertExpression, paramExpression).Compile(); + } + + public static Action GetSetter(this FieldInfo fieldInfo) + { + var paramExpression = Expression.Parameter(typeof(T)); + var propertyExpression = Expression.Field(paramExpression, fieldInfo); + var valueExpression = Expression.Parameter(typeof(object)); + var convertExpression = Expression.Convert(valueExpression, fieldInfo.FieldType); + var assignExpression = Expression.Assign(propertyExpression, convertExpression); + + return Expression.Lambda>(assignExpression, paramExpression, valueExpression).Compile(); + } + + public static uint[] SerializeObject(this T obj) + { + //if (obj.GetType()() == null) + //return null; + + var size = Marshal.SizeOf(typeof(T)); + var ptr = Marshal.AllocHGlobal(size); + byte[] array = new byte[size]; + + Marshal.StructureToPtr(obj, ptr, true); + Marshal.Copy(ptr, array, 0, size); + + Marshal.FreeHGlobal(ptr); + + uint[] result = new uint[size / 4]; + Buffer.BlockCopy(array, 0, result, 0, array.Length); + + return result; + } + + public static List DeserializeObjects(this ICollection data) + { + List list = new List(); + + if (data.Count == 0) + return list; + + if (typeof(T).GetCustomAttribute() == null) + return list; + + byte[] result = new byte[data.Count * sizeof(uint)]; + Buffer.BlockCopy(data.ToArray(), 0, result, 0, result.Length); + + var typeSize = Marshal.SizeOf(typeof(T)); + var objCount = data.Count / (typeSize / sizeof(uint)); + + for (var i = 0; i < objCount; ++i) + { + var ptr = Marshal.AllocHGlobal(typeSize); + Marshal.Copy(result, typeSize * i, ptr, typeSize); + list.Add((T)Marshal.PtrToStructure(ptr, typeof(T))); + Marshal.FreeHGlobal(ptr); + } + + return list; + } + + #region Strings + public static bool IsEmpty(this string str) + { + return string.IsNullOrEmpty(str); + } + + public static T ToEnum(this string str) where T : struct + { + T value; + if (!Enum.TryParse(str, out value)) + return default(T); + + return value; + } + + public static string ConvertFormatSyntax(this string str) + { + // @"%(\d+(\.\d+)?)?(d|f|s|u|i|llX|X|ll)"; old working + //@"%(\d+(\.\d+)?)?(-\d+[a-z]|[[a-z])"; working somewhat + string pattern = @"(%\W*\d*[a-zA-Z]*)"; //Working fully???? + + int count = 0; + string result = Regex.Replace(str, pattern, m => + { + return string.Concat("{", count++, "}"); + }); + + return result; + } + + public static bool Like(this string toSearch, string toFind) + { + return toSearch.ToLower().Contains(toFind.ToLower()); + } + + public static bool IsNumber(this string str) + { + double value; + return double.TryParse(str, out value); + } + #endregion + + #region BinaryReader + public static T ReadStruct(this BinaryReader reader) where T : struct + { + byte[] data = reader.ReadBytes(Marshal.SizeOf(typeof(T))); + GCHandle handle = GCHandle.Alloc(data, GCHandleType.Pinned); + T returnObject = (T)Marshal.PtrToStructure(handle.AddrOfPinnedObject(), typeof(T)); + + handle.Free(); + return returnObject; + } + + public static T ReadStruct(this BinaryReader reader, uint offset) where T : struct + { + reader.BaseStream.Position = offset; + byte[] data = reader.ReadBytes(Marshal.SizeOf(typeof(T))); + GCHandle handle = GCHandle.Alloc(data, GCHandleType.Pinned); + T returnObject = (T)Marshal.PtrToStructure(handle.AddrOfPinnedObject(), typeof(T)); + + handle.Free(); + return returnObject; + } + + public static string ReadCString(this BinaryReader reader) + { + byte num; + List temp = new List(); + + while ((num = reader.ReadByte()) != 0) + temp.Add(num); + + return Encoding.UTF8.GetString(temp.ToArray()); + } + + public static string ReadString(this BinaryReader reader, int count) + { + var array = reader.ReadBytes(count); + return Encoding.ASCII.GetString(array); + } + + public static string ReadStringFromChars(this BinaryReader reader, int count) + { + return new string(reader.ReadChars(count)); + } + + public static byte[] ToByteArray(this BinaryReader reader) + { + var data = new byte[reader.BaseStream.Length]; + + long pos = reader.BaseStream.Position; + reader.BaseStream.Seek(0, SeekOrigin.Begin); + for (int i = 0; i < data.Length; i++) + data[i] = (byte)reader.BaseStream.ReadByte(); + + reader.BaseStream.Seek(pos, SeekOrigin.Begin); + return data; + + } + #endregion + } +} \ No newline at end of file diff --git a/Framework/Util/MathFunctions.cs b/Framework/Util/MathFunctions.cs new file mode 100644 index 000000000..c4765886d --- /dev/null +++ b/Framework/Util/MathFunctions.cs @@ -0,0 +1,286 @@ +/* + * Copyright (C) 2012-2017 CypherCore + * + * 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, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +using Framework.Constants; +using System; + +public static class MathFunctions +{ + public const float E = 2.71828f; + public const float Log10E = 0.434294f; + public const float Log2E = 1.4427f; + public const float PI = 3.14159f; + public const float PiOver2 = 1.5708f; + public const float PiOver4 = 0.785398f; + public const float TwoPi = 6.28319f; + public const float Epsilon = 4.76837158203125E-7f; + + public static float wrap(float t, float lo, float hi) + { + if ((t >= lo) && (t < hi)) + { + return t; + } + + float interval = hi - lo; + return (float)(t - interval * Math.Floor((t - lo) / interval)); + } + + public static void Swap(ref T lhs, ref T rhs) + { + T temp = lhs; + lhs = rhs; + rhs = temp; + } + + #region Clamp + /// + /// Clamp a to if it is withon the range. + /// + /// The value to clamp. + /// The clamped value. + /// The tolerance value. + /// + /// Returns the clamped value. + /// result = (tolerance > Abs(value-calmpedValue)) ? calmpedValue : value; + /// + public static double Clamp(double value, double calmpedValue, double tolerance) + { + return (tolerance > Math.Abs(value - calmpedValue)) ? calmpedValue : value; + } + /// + /// Clamp a to if it is withon the range. + /// + /// The value to clamp. + /// The clamped value. + /// The tolerance value. + /// + /// Returns the clamped value. + /// result = (tolerance > Abs(value-calmpedValue)) ? calmpedValue : value; + /// + public static float Clamp(float value, float calmpedValue, float tolerance) + { + return (tolerance > Math.Abs(value - calmpedValue)) ? calmpedValue : value; + } + /// + /// Clamp a to using the default tolerance value. + /// + /// The value to clamp. + /// The clamped value. + /// + /// Returns the clamped value. + /// result = (Epsilon > Abs(value-calmpedValue)) ? calmpedValue : value; + /// + /// is used for tolerance. + public static double Clamp(double value, double calmpedValue) + { + return (Epsilon > Math.Abs(value - calmpedValue)) ? calmpedValue : value; + } + /// + /// Clamp a to using the default tolerance value. + /// + /// The value to clamp. + /// The clamped value. + /// + /// Returns the clamped value. + /// result = (EpsilonF > Abs(value-calmpedValue)) ? calmpedValue : value; + /// + /// is used for tolerance. + public static float Clamp(float value, float calmpedValue) + { + return (Epsilon > Math.Abs(value - calmpedValue)) ? calmpedValue : value; + } + #endregion + + static double eps(double a, double b) + { + double aa = Math.Abs(a) + 1.0; + if (aa == double.PositiveInfinity) + return double.Epsilon; + else + return double.Epsilon * aa; + } + + public static float lerp(float a, float b, float f) + { + return a + (b - a) * f; + } + + #region Fuzzy + public static bool fuzzyEq(double a, double b) + { + return (a == b) || (Math.Abs(a - b) <= eps(a, b)); + } + public static bool fuzzyGt(double a, double b) + { + return a > b + eps(a, b); + } + public static bool fuzzyLt(double a, double b) + { + return a < b - eps(a, b); + } + public static bool fuzzyNe(double a, double b) + { + return !fuzzyEq(a, b); + } + + public static bool fuzzyLe(double a, double b) + { + return a < b + eps(a, b); + } + + + #endregion + + public static int ApplyPct(ref int Base, float pct) + { + return Base = CalculatePct(Base, pct); + } + public static uint ApplyPct(ref uint Base, float pct) + { + return Base = CalculatePct(Base, pct); + } + public static float ApplyPct(ref float Base, float pct) + { + return Base = CalculatePct(Base, pct); + } + + public static long AddPct(ref long value, float pct) + { + return value += (long)CalculatePct(value, pct); + } + public static int AddPct(ref int value, float pct) + { + return value += CalculatePct(value, pct); + } + public static uint AddPct(ref uint value, float pct) + { + return value += CalculatePct(value, pct); + } + public static float AddPct(ref float value, float pct) + { + return value += CalculatePct(value, pct); + } + + public static int CalculatePct(int value, float pct) + { + return (int)(value * Convert.ToSingle(pct) / 100.0f); + } + public static uint CalculatePct(uint value, float pct) + { + return (uint)(value * Convert.ToSingle(pct) / 100.0f); + } + public static float CalculatePct(float value, float pct) + { + return value * pct / 100.0f; + } + public static ulong CalculatePct(ulong value, float pct) + { + return (ulong)(value * pct / 100.0f); + } + + public static int RoundToInterval(ref int num, dynamic floor, dynamic ceil) + { + return num = Math.Min(Math.Max(num, floor), ceil); + } + public static uint RoundToInterval(ref uint num, dynamic floor, dynamic ceil) + { + return num = Math.Min(Math.Max(num, floor), ceil); + } + public static float RoundToInterval(ref float num, dynamic floor, dynamic ceil) + { + return num = Math.Min(Math.Max(num, floor), ceil); + } + + public static void ApplyPercentModFloatVar(ref float value, float val, bool apply) + { + if (val == -100.0f) // prevent set var to zero + val = -99.99f; + value *= (apply ? (100.0f + val) / 100.0f : 100.0f / (100.0f + val)); + } + + public static bool CompareValues(ComparisionType type, uint val1, uint val2) + { + switch (type) + { + case ComparisionType.EQ: + return val1 == val2; + case ComparisionType.High: + return val1 > val2; + case ComparisionType.Low: + return val1 < val2; + case ComparisionType.HighEQ: + return val1 >= val2; + case ComparisionType.LowEQ: + return val1 <= val2; + default: + // incorrect parameter + //Contract.Assert(false); + return false; + } + + } + public static bool CompareValues(ComparisionType type, float val1, float val2) + { + switch (type) + { + case ComparisionType.EQ: + return val1 == val2; + case ComparisionType.High: + return val1 > val2; + case ComparisionType.Low: + return val1 < val2; + case ComparisionType.HighEQ: + return val1 >= val2; + case ComparisionType.LowEQ: + return val1 <= val2; + default: + // incorrect parameter + //Contract.Assert(false); + return false; + } + } + + public static ulong MakePair64(uint l, uint h) + { + return (ulong)l | ((ulong)h << 32); + } + public static uint Pair64_HiPart(ulong x) + { + return (uint)((x >> 32) & 0x00000000FFFFFFFF); + } + public static uint Pair64_LoPart(ulong x) + { + return (uint)(x & 0x00000000FFFFFFFF); + } + public static ushort Pair32_HiPart(uint x) + { + return (ushort)((x >> 16) & 0x0000FFFF); + } + public static ushort Pair32_LoPart(uint x) + { + return (ushort)(x & 0x0000FFFF); + } + public static uint MakePair32(uint l, uint h) + { + return (ushort)l | (h << 16); + } + public static ushort MakePair16(uint l, uint h) + { + return (ushort)((byte)l | (ushort)h << 8); + } +} diff --git a/Framework/Util/NetworkExtensions.cs b/Framework/Util/NetworkExtensions.cs new file mode 100644 index 000000000..20fefea55 --- /dev/null +++ b/Framework/Util/NetworkExtensions.cs @@ -0,0 +1,38 @@ +/* + * Copyright (C) 2012-2017 CypherCore + * + * 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, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +using System; +using System.Net; + +public static class NetworkExtensions +{ + public static IPAddress GetNetworkAddress(this IPAddress address, IPAddress subnetMask) + { + byte[] ipAdressBytes = address.GetAddressBytes(); + byte[] subnetMaskBytes = subnetMask.GetAddressBytes(); + + if (ipAdressBytes.Length != subnetMaskBytes.Length) + throw new ArgumentException("Lengths of IP address and subnet mask do not match."); + + byte[] broadcastAddress = new byte[ipAdressBytes.Length]; + for (int i = 0; i < broadcastAddress.Length; i++) + { + broadcastAddress[i] = (byte)(ipAdressBytes[i] & (subnetMaskBytes[i])); + } + return new IPAddress(broadcastAddress); + } +} \ No newline at end of file diff --git a/Framework/Util/RandomHelper.cs b/Framework/Util/RandomHelper.cs new file mode 100644 index 000000000..acf0c4943 --- /dev/null +++ b/Framework/Util/RandomHelper.cs @@ -0,0 +1,108 @@ +/* + * Copyright (C) 2012-2017 CypherCore + * + * 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, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +using System; +using System.Diagnostics.Contracts; + +public class RandomHelper +{ + private readonly static Random rand; + + static RandomHelper() + { + rand = new Random(); + } + + /// + /// Returns a random number between 0.0 and 1.0. + /// + /// + public static double NextDouble() + { + return rand.NextDouble(); + } + + /// + /// Returns a nonnegative random number. + /// + /// + public static uint Rand32() + { + return (uint)rand.Next(); + } + + /// + /// Returns a nonnegative random number less than the specified maximum. + /// + /// + /// + public static uint Rand32(dynamic maxValue) + { + return (uint)rand.Next(maxValue); + } + + /// + /// Returns a random number within a specified range. + /// + /// + /// + /// + public static int IRand(int minValue, int maxValue) + { + return rand.Next(minValue, maxValue); + } + public static uint URand(dynamic minValue, dynamic maxValue) + { + return (uint)rand.Next(Convert.ToInt32(minValue), Convert.ToInt32(maxValue)); + } + public static float FRand(float min, float max) + { + Contract.Assert(max >= min); + return (float)(rand.NextDouble() * (max - min) + min); + } + + /// + /// Returns true if rand.Next less then i + /// + /// + /// + public static bool randChance(float i) + { + return i > rand.Next(0, 100); + } + + public static double randChance() + { + return rand.NextDouble() * 100.0; + } + + /// + /// Fills the elements of a specified array of bytes with random numbers. + /// + /// + public static void NextBytes(byte[] buffer) + { + rand.NextBytes(buffer); + } + + public static T RAND(params T[] args) + { + int rand = IRand(0, args.Length - 1); + + return args[rand]; + } +} + diff --git a/Framework/Util/Time.cs b/Framework/Util/Time.cs new file mode 100644 index 000000000..76aacfa79 --- /dev/null +++ b/Framework/Util/Time.cs @@ -0,0 +1,351 @@ +/* + * Copyright (C) 2012-2017 CypherCore + * + * 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, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +using System; + +public static class Time +{ + public const int Minute = 60; + public const int Hour = Minute * 60; + public const int Day = Hour * 24; + public const int Week = Day * 7; + public const int Month = Day * 30; + public const int Year = Month * 12; + public const int InMilliseconds = 1000; + + public static readonly DateTime Epoch = new DateTime(1970, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc); + public static readonly DateTime ApplicationStartTime = DateTime.Now; + + /// + /// Gets the current Unix time. + /// + public static long UnixTime + { + get + { + return (long)(DateTime.Now - Epoch).TotalSeconds; + } + } + + /// + /// Gets the current Unix time, in milliseconds. + /// + public static long UnixTimeMilliseconds + { + get + { + var ts = (DateTime.Now - Epoch); + return ts.ToMilliseconds(); + } + } + + /// + /// Converts a TimeSpan to its equivalent representation in milliseconds (Int64). + /// + /// The time span value to convert. + public static long ToMilliseconds(this TimeSpan span) + { + return (long)span.TotalMilliseconds; + } + + /// + /// Gets the system uptime. + /// + /// the system uptime in milliseconds + public static uint GetSystemTime() + { + return (uint)Environment.TickCount; + } + + public static uint GetMSTime() + { + return (uint)(DateTime.Now - ApplicationStartTime).ToMilliseconds(); + } + + public static uint GetMSTimeDiff(uint oldMSTime, uint newMSTime) + { + if (oldMSTime > newMSTime) + return (0xFFFFFFFF - oldMSTime) + newMSTime; + else + return newMSTime - oldMSTime; + } + + public static uint GetMSTimeDiffToNow(uint oldMSTime) + { + var newMSTime = GetMSTime(); + if (oldMSTime > newMSTime) + return (0xFFFFFFFF - oldMSTime) + newMSTime; + else + return newMSTime - oldMSTime; + } + + public static DateTime UnixTimeToDateTime(long unixTime) + { + return Epoch.AddSeconds(unixTime); + } + public static long DateTimeToUnixTime(DateTime dateTime) + { + return (long)(dateTime - Epoch).TotalSeconds; + } + + public static long GetNextResetUnixTime(int hours) + { + return DateTimeToUnixTime((DateTime.Now.Date + new TimeSpan(hours, 0, 0))); + } + public static long GetNextResetUnixTime(int days, int hours) + { + return DateTimeToUnixTime((DateTime.Now.Date + new TimeSpan(days, hours, 0, 0))); + } + public static long GetNextResetUnixTime(int months, int days, int hours) + { + return DateTimeToUnixTime((DateTime.Now.Date + new TimeSpan(months + days, hours, 0))); + } + + public static string secsToTimeString(ulong timeInSecs, bool shortText = false, bool hoursOnly = false) + { + ulong secs = timeInSecs % Minute; + ulong minutes = timeInSecs % Hour / Minute; + ulong hours = timeInSecs % Day / Hour; + ulong days = timeInSecs / Day; + + string ss = ""; + if (days != 0) + ss += days + (shortText ? "d" : " Day(s) "); + if (hours != 0 || hoursOnly) + ss += hours + (shortText ? "h" : " Hour(s) "); + if (!hoursOnly) + { + if (minutes != 0) + ss += minutes + (shortText ? "m" : " Minute(s) "); + if (secs != 0 || (days == 0 && hours == 0 && minutes == 0)) + ss += secs + (shortText ? "s" : " Second(s)."); + } + + return ss; + } + + public static uint TimeStringToSecs(string timestring) + { + int secs = 0; + int buffer = 0; + int multiplier; + + foreach (var c in timestring) + { + if (char.IsDigit(c)) + { + buffer *= 10; + buffer += c - '0'; + } + else + { + switch (c) + { + case 'd': + multiplier = Day; + break; + case 'h': + multiplier = Hour; + break; + case 'm': + multiplier = Minute; + break; + case 's': + multiplier = 1; + break; + default: + return 0; //bad format + } + buffer *= multiplier; + secs += buffer; + buffer = 0; + } + } + + return (uint)secs; + } + + public static string GetTimeString(long time) + { + long days = time / Day; + long hours = (time % Day) / Hour; + long minute = (time % Hour) / Minute; + + return string.Format("Days: {0} Hours: {1} Minutes: {2}", days, hours, minute); + } + + public static void Profile(string description, int iterations, Action func) + { + //Run at highest priority to minimize fluctuations caused by other processes/threads + System.Diagnostics.Process.GetCurrentProcess().PriorityClass = System.Diagnostics.ProcessPriorityClass.High; + System.Threading.Thread.CurrentThread.Priority = System.Threading.ThreadPriority.Highest; + + // warm up + func(); + + var watch = new System.Diagnostics.Stopwatch(); + + // clean up + GC.Collect(); + GC.WaitForPendingFinalizers(); + GC.Collect(); + + watch.Start(); + for (int i = 0; i < iterations; i++) + { + func(); + } + watch.Stop(); + Console.Write(description); + Console.WriteLine(" Time Elapsed {0} ms", watch.Elapsed.TotalMilliseconds); + } +} + +public class TimeTrackerSmall +{ + public TimeTrackerSmall(int expiry = 0) + { + i_expiryTime = expiry; + } + + public void Update(int diff) + { + i_expiryTime -= diff; + } + + public bool Passed() + { + return i_expiryTime <= 0; + } + + public void Reset(int interval) + { + i_expiryTime = interval; + } + + public int GetExpiry() + { + return i_expiryTime; + } + int i_expiryTime; +} + +public class TimeTracker +{ + public TimeTracker(long expiry = 0) + { + i_expiryTime = expiry; + } + + public void Update(long diff) + { + i_expiryTime -= diff; + } + + public bool Passed() + { + return i_expiryTime <= 0; + } + + public void Reset(long interval) + { + i_expiryTime = interval; + } + + public long GetExpiry() + { + return i_expiryTime; + } + + long i_expiryTime; +} + +public class IntervalTimer +{ + public void Update(long diff) + { + _current += diff; + if (_current < 0) + _current = 0; + } + + public bool Passed() + { + return _current >= _interval; + } + + public void Reset() + { + if (_current >= _interval) + _current %= _interval; + } + + public void SetCurrent(long current) + { + _current = current; + } + + public void SetInterval(long interval) + { + _interval = interval; + } + + public long GetInterval() + { + return _interval; + } + + public long GetCurrent() + { + return _current; + } + + long _interval; + long _current; +} + +public class PeriodicTimer +{ + public PeriodicTimer(int period, int start_time) + { + i_period = period; + i_expireTime = start_time; + } + + public bool Update(int diff) + { + if ((i_expireTime -= diff) > 0) + return false; + + i_expireTime += i_period > diff ? i_period : diff; + return true; + } + + public void SetPeriodic(int period, int start_time) + { + i_expireTime = start_time; + i_period = period; + } + + // Tracker interface + public void TUpdate(int diff) { i_expireTime -= diff; } + public bool TPassed() { return i_expireTime <= 0; } + public void TReset(int diff, int period) { i_expireTime += period > diff ? period : diff; } + + int i_period; + int i_expireTime; +} diff --git a/Framework/Web/Http.cs b/Framework/Web/Http.cs new file mode 100644 index 000000000..9806e3461 --- /dev/null +++ b/Framework/Web/Http.cs @@ -0,0 +1,139 @@ +/* + * Copyright (C) 2012-2017 CypherCore + * + * 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, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Reflection; +using System.Text; + +namespace Framework.Web +{ + public class HttpHeader + { + public string Method { get; set; } + public string Path { get; set; } + public string Type { get; set; } + public string Host { get; set; } + public string DeviceId { get; set; } + public string ContentType { get; set; } + public int ContentLength { get; set; } + public string AcceptLanguage { get; set; } + public string Accept { get; set; } + public string UserAgent { get; set; } + public string Content { get; set; } + } + + public enum HttpCode + { + Ok = 200, + BadRequest = 400, + NotFound = 404, + InternalServerError = 500 + } + + public class HttpHelper + { + public static byte[] CreateResponse(HttpCode httpCode, string content, bool closeConnection = false) + { + var sb = new StringBuilder(); + + using (var sw = new StringWriter(sb)) + { + sw.WriteLine($"HTTP/1.1 {(int)httpCode} {httpCode}"); + + //sw.WriteLine($"Date: {DateTime.Now.ToUniversalTime():r}"); + //sw.WriteLine("Server: Arctium-Emulation"); + //sw.WriteLine("Retry-After: 600"); + sw.WriteLine($"Content-Length: {content.Length}"); + //sw.WriteLine("Vary: Accept-Encoding"); + + if (closeConnection) + sw.WriteLine("Connection: close"); + + sw.WriteLine("Content-Type: application/json;charset=UTF-8"); + sw.WriteLine(); + + sw.WriteLine(content); + } + + return Encoding.UTF8.GetBytes(sb.ToString()); + } + + public static HttpHeader ParseRequest(byte[] data, int length) + { + var headerValues = new Dictionary(); + var header = new HttpHeader(); + + using (var sr = new StreamReader(new MemoryStream(data, 0, length))) + { + var info = sr.ReadLine().Split(new string[] { " " }, StringSplitOptions.RemoveEmptyEntries); + + if (info.Length != 3) + return null; + + headerValues.Add("method", info[0]); + headerValues.Add("path", info[1]); + headerValues.Add("type", info[2]); + + while (!sr.EndOfStream) + { + info = sr.ReadLine().Split(new string[] { ": " }, StringSplitOptions.RemoveEmptyEntries); + + if (info.Length == 2) + headerValues.Add(info[0].Replace("-", "").ToLower(), info[1]); + else if (info.Length > 2) + { + var val = ""; + + info.Skip(1); + + headerValues.Add(info[0].Replace("-", "").ToLower(), val); + } + else + { + // We are at content here. + var content = sr.ReadLine(); + + headerValues.Add("content", content); + + // There shouldn't be anything after the content! + break; + } + } + } + + var httpFields = typeof(HttpHeader).GetTypeInfo().GetProperties(); + + foreach (var f in httpFields) + { + object val; + + if (headerValues.TryGetValue(f.Name.ToLower(), out val)) + { + if (f.PropertyType == typeof(int)) + f.SetValue(header, Convert.ChangeType(Convert.ToInt32(val), f.PropertyType)); + else + f.SetValue(header, Convert.ChangeType(val, f.PropertyType)); + } + } + + return header; + } + } +} diff --git a/Game/AI/AISelector.cs b/Game/AI/AISelector.cs new file mode 100644 index 000000000..2789ace76 --- /dev/null +++ b/Game/AI/AISelector.cs @@ -0,0 +1,129 @@ +/* + * Copyright (C) 2012-2017 CypherCore + * + * 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, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +using Framework.Constants; +using Game.Entities; +using Game.Movement; + +namespace Game.AI +{ + public class AISelector + { + public static CreatureAI SelectAI(Creature creature) + { + if (creature.IsPet()) + return new PetAI(creature); + + //scriptname in db + CreatureAI scriptedAI = Global.ScriptMgr.GetCreatureAI(creature); + if (scriptedAI != null) + return scriptedAI; + + switch (creature.GetCreatureTemplate().AIName) + { + case "AggressorAI": + return new AggressorAI(creature); + case "ArcherAI": + return new ArcherAI(creature); + case "CombatAI": + return new CombatAI(creature); + case "CritterAI": + return new CritterAI(creature); + case "GuardAI": + return new GuardAI(creature); + case "NullCreatureAI": + return new NullCreatureAI(creature); + case "PassiveAI": + return new PassiveAI(creature); + case "PetAI": + return new PetAI(creature); + case "ReactorAI": + return new ReactorAI(creature); + case "SmartAI": + return new SmartAI(creature); + case "TotemAI": + return new TotemAI(creature); + case "TriggerAI": + return new TriggerAI(creature); + case "TurretAI": + return new TurretAI(creature); + case "VehicleAI": + return new VehicleAI(creature); + } + + // select by NPC flags + if (creature.IsVehicle()) + return new VehicleAI(creature); + else if (creature.HasUnitTypeMask(UnitTypeMask.ControlableGuardian) && ((Guardian)creature).GetOwner().IsTypeId(TypeId.Player)) + return new PetAI(creature); + else if (creature.HasFlag64(UnitFields.NpcFlags, NPCFlags.SpellClick)) + return new NullCreatureAI(creature); + else if (creature.IsGuard()) + return new GuardAI(creature); + else if (creature.HasUnitTypeMask(UnitTypeMask.ControlableGuardian)) + return new PetAI(creature); + else if (creature.IsTotem()) + return new TotemAI(creature); + else if (creature.IsTrigger()) + { + if (creature.m_spells[0] != 0) + return new TriggerAI(creature); + else + return new NullCreatureAI(creature); + } + else if (creature.IsCritter() && !creature.HasUnitTypeMask(UnitTypeMask.Guardian)) + return new CritterAI(creature); + + /*if (!creature.IsCivilian() && !creature.IsNeutralToAll()) + return new AggressorAI(creature); + + if (creature.IsCivilian() || creature.IsNeutralToAll()) + return new ReactorAI(creature);*/ + + return new NullCreatureAI(creature); + } + + public static IMovementGenerator SelectMovementAI(Creature creature) + { + switch (creature.m_defaultMovementType) + { + case MovementGeneratorType.Random: + return new RandomMovementGenerator(); + case MovementGeneratorType.Waypoint: + return new WaypointMovementGenerator(); + } + return null; + } + + public static GameObjectAI SelectGameObjectAI(GameObject go) + { + // scriptname in db + GameObjectAI scriptedAI = Global.ScriptMgr.GetGameObjectAI(go); + if (scriptedAI != null) + return scriptedAI; + + switch (go.GetAIName()) + { + case "GameObjectAI": + return new GameObjectAI(go); + case "SmartGameObjectAI": + return new SmartGameObjectAI(go); + } + return new NullGameObjectAI(go); + } + } +} diff --git a/Game/AI/CoreAI/AreaTriggerAI.cs b/Game/AI/CoreAI/AreaTriggerAI.cs new file mode 100644 index 000000000..f2521758e --- /dev/null +++ b/Game/AI/CoreAI/AreaTriggerAI.cs @@ -0,0 +1,60 @@ +/* + * Copyright (C) 2012-2017 CypherCore + * + * 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, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +using Game.Entities; + +namespace Game.AI +{ + public class AreaTriggerAI + { + public AreaTriggerAI(AreaTrigger a) + { + at = a; + } + + // Called when the AreaTrigger has just been initialized, just before added to map + public virtual void OnInitialize() { } + + // Called when the AreaTrigger has just been created + public virtual void OnCreate() { } + + // Called on each AreaTrigger update + public virtual void OnUpdate(uint diff) { } + + // Called when the AreaTrigger reach splineIndex + public virtual void OnSplineIndexReached(int splineIndex) { } + + // Called when the AreaTrigger reach its destination + public virtual void OnDestinationReached() { } + + // Called when an unit enter the AreaTrigger + public virtual void OnUnitEnter(Unit unit) { } + + // Called when an unit exit the AreaTrigger, or when the AreaTrigger is removed + public virtual void OnUnitExit(Unit unit) { } + + // Called when the AreaTrigger is removed + public virtual void OnRemove() { } + + protected AreaTrigger at; + } + + class NullAreaTriggerAI : AreaTriggerAI + { + public NullAreaTriggerAI(AreaTrigger areaTrigger) : base(areaTrigger) { } + } +} diff --git a/Game/AI/CoreAI/CombatAI.cs b/Game/AI/CoreAI/CombatAI.cs new file mode 100644 index 000000000..e6e2185ab --- /dev/null +++ b/Game/AI/CoreAI/CombatAI.cs @@ -0,0 +1,361 @@ +/* + * Copyright (C) 2012-2017 CypherCore + * + * 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, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +using Framework.Constants; +using Game.Entities; +using System.Collections.Generic; + +namespace Game.AI +{ + public class CombatAI : CreatureAI + { + public CombatAI(Creature c) : base(c) { } + + public override void InitializeAI() + { + for (var i = 0; i < SharedConst.MaxCreatureSpells; ++i) + if (me.m_spells[i] != 0 && Global.SpellMgr.GetSpellInfo(me.m_spells[i]) != null) + spells.Add(me.m_spells[i]); + + base.InitializeAI(); + } + + public override void Reset() + { + _events.Reset(); + } + + public override void JustDied(Unit killer) + { + foreach (var id in spells) + if (AISpellInfo[id].condition == AICondition.Die) + me.CastSpell(killer, id, true); + } + + public override void EnterCombat(Unit victim) + { + foreach (var id in spells) + { + if (AISpellInfo[id].condition == AICondition.Aggro) + me.CastSpell(victim, id, false); + else if (AISpellInfo[id].condition == AICondition.Combat) + _events.ScheduleEvent(id, AISpellInfo[id].cooldown + RandomHelper.Rand32() % AISpellInfo[id].cooldown); + } + } + + public override void UpdateAI(uint diff) + { + if (!UpdateVictim()) + return; + + _events.Update(diff); + + if (me.HasUnitState(UnitState.Casting)) + return; + + uint spellId = _events.ExecuteEvent(); + if (spellId != 0) + { + DoCast(spellId); + _events.ScheduleEvent(spellId, AISpellInfo[spellId].cooldown + RandomHelper.Rand32() % AISpellInfo[spellId].cooldown); + } + + DoMeleeAttackIfReady(); + } + + public override void SpellInterrupted(uint spellId, uint unTimeMs) + { + _events.RescheduleEvent(spellId, unTimeMs); + } + + public List spells = new List(); + } + + public class AggressorAI : CreatureAI + { + public AggressorAI(Creature c) : base(c) { } + + public override void UpdateAI(uint diff) + { + if (!UpdateVictim()) + return; + + DoMeleeAttackIfReady(); + } + } + + public class CasterAI : CombatAI + { + public CasterAI(Creature c) + : base(c) + { + m_attackDist = SharedConst.MeleeRange; + } + + public override void InitializeAI() + { + base.InitializeAI(); + + m_attackDist = 30.0f; + foreach (var id in spells) + if (AISpellInfo[id].condition == AICondition.Combat && m_attackDist > AISpellInfo[id].maxRange) + m_attackDist = AISpellInfo[id].maxRange; + if (m_attackDist == 30.0f) + m_attackDist = SharedConst.MeleeRange; + } + + public override void AttackStart(Unit victim) + { + AttackStartCaster(victim, m_attackDist); + } + + public override void EnterCombat(Unit victim) + { + if (spells.Empty()) + return; + + int spell = (int)(RandomHelper.Rand32() % spells.Count); + uint count = 0; + foreach (var id in spells) + { + + if (AISpellInfo[id].condition == AICondition.Aggro) + me.CastSpell(victim, id, false); + else if (AISpellInfo[id].condition == AICondition.Combat) + { + uint cooldown = AISpellInfo[id].realCooldown; + if (count == spell) + { + DoCast(spells[spell]); + cooldown += (uint)me.GetCurrentSpellCastTime(id); + } + _events.ScheduleEvent(id, cooldown); + } + } + } + + public override void UpdateAI(uint diff) + { + if (!UpdateVictim()) + return; + + _events.Update(diff); + + if (me.GetVictim().HasBreakableByDamageCrowdControlAura(me)) + { + me.InterruptNonMeleeSpells(false); + return; + } + + if (me.HasUnitState(UnitState.Casting)) + return; + + uint spellId = _events.ExecuteEvent(); + if (spellId != 0) + { + DoCast(spellId); + uint casttime = (uint)me.GetCurrentSpellCastTime(spellId); + _events.ScheduleEvent(spellId, (casttime != 0 ? casttime : 500) + AISpellInfo[spellId].realCooldown); + } + } + + float m_attackDist; + } + + public class ArcherAI : CreatureAI + { + public ArcherAI(Creature c) + : base(c) + { + if (me.m_spells[0] == 0) + Log.outError(LogFilter.ScriptsAi, "ArcherAI set for creature (entry = {0}) with spell1=0. AI will do nothing", me.GetEntry()); + + var spellInfo = Global.SpellMgr.GetSpellInfo(me.m_spells[0]); + m_minRange = spellInfo != null ? spellInfo.GetMinRange(false) : 0; + + if (m_minRange == 0) + m_minRange = SharedConst.MeleeRange; + me.m_CombatDistance = spellInfo != null ? spellInfo.GetMaxRange(false) : 0; + me.m_SightDistance = me.m_CombatDistance; + } + + public override void AttackStart(Unit who) + { + if (who == null) + return; + + if (me.IsWithinCombatRange(who, m_minRange)) + { + if (me.Attack(who, true) && !who.IsFlying()) + me.GetMotionMaster().MoveChase(who); + } + else + { + if (me.Attack(who, false) && !who.IsFlying()) + me.GetMotionMaster().MoveChase(who, me.m_CombatDistance); + } + + if (who.IsFlying()) + me.GetMotionMaster().MoveIdle(); + } + public override void UpdateAI(uint diff) + { + if (!UpdateVictim()) + return; + + if (!me.IsWithinCombatRange(me.GetVictim(), m_minRange)) + DoSpellAttackIfReady(me.m_spells[0]); + else + DoMeleeAttackIfReady(); + } + + float m_minRange; + } + + public class TurretAI : CreatureAI + { + public TurretAI(Creature c) + : base(c) + { + if (me.m_spells[0] == 0) + Log.outError(LogFilter.Server, "TurretAI set for creature (entry = {0}) with spell1=0. AI will do nothing", me.GetEntry()); + + var spellInfo = Global.SpellMgr.GetSpellInfo(me.m_spells[0]); + m_minRange = spellInfo != null ? spellInfo.GetMinRange(false) : 0; + me.m_CombatDistance = spellInfo != null ? spellInfo.GetMaxRange(false) : 0; + me.m_SightDistance = me.m_CombatDistance; + } + public override bool CanAIAttack(Unit victim) + { + /// todo use one function to replace it + if (!me.IsWithinCombatRange(me.GetVictim(), me.m_CombatDistance) + || (m_minRange != 0 && me.IsWithinCombatRange(me.GetVictim(), m_minRange))) + return false; + return true; + } + public override void AttackStart(Unit victim) + { + if (victim != null) + me.Attack(victim, false); + } + public override void UpdateAI(uint diff) + { + if (!UpdateVictim()) + return; + + DoSpellAttackIfReady(me.m_spells[0]); + } + + float m_minRange; + } + + public class VehicleAI : CreatureAI + { + const int VEHICLE_CONDITION_CHECK_TIME = 1000; + const int VEHICLE_DISMISS_TIME = 5000; + + public VehicleAI(Creature creature) : base(creature) + { + m_ConditionsTimer = VEHICLE_CONDITION_CHECK_TIME; + LoadConditions(); + m_DoDismiss = false; + m_DismissTimer = VEHICLE_DISMISS_TIME; + } + + public override void UpdateAI(uint diff) + { + CheckConditions(diff); + + if (m_DoDismiss) + { + if (m_DismissTimer < diff) + { + m_DoDismiss = false; + me.DespawnOrUnsummon(); + } + else + m_DismissTimer -= diff; + } + } + + public override void OnCharmed(bool apply) + { + if (!me.GetVehicleKit().IsVehicleInUse() && !apply && m_HasConditions)//was used and has conditions + m_DoDismiss = true;//needs reset + else if (apply) + m_DoDismiss = false;//in use again + + m_DismissTimer = VEHICLE_DISMISS_TIME;//reset timer + } + + void LoadConditions() + { + m_HasConditions = Global.ConditionMgr.HasConditionsForNotGroupedEntry(ConditionSourceType.CreatureTemplateVehicle, me.GetEntry()); + } + + void CheckConditions(uint diff) + { + if (m_ConditionsTimer < diff) + { + if (m_HasConditions) + { + Vehicle vehicleKit = me.GetVehicleKit(); + if (vehicleKit) + { + foreach (var pair in vehicleKit.Seats) + { + Unit passenger = Global.ObjAccessor.GetUnit(me, pair.Value.Passenger.Guid); + if (passenger) + { + Player player = passenger.ToPlayer(); + if (player) + { + if (!Global.ConditionMgr.IsObjectMeetingNotGroupedConditions(ConditionSourceType.CreatureTemplateVehicle, me.GetEntry(), player, me)) + { + player.ExitVehicle(); + return;//check other pessanger in next tick + } + } + } + } + } + } + m_ConditionsTimer = VEHICLE_CONDITION_CHECK_TIME; + } + else + m_ConditionsTimer -= diff; + } + + bool m_HasConditions; + uint m_ConditionsTimer; + bool m_DoDismiss; + uint m_DismissTimer; + } + + public class ReactorAI : CreatureAI + { + public ReactorAI(Creature c) : base(c) { } + + public override void UpdateAI(uint diff) + { + if (!UpdateVictim()) + return; + + DoMeleeAttackIfReady(); + } + } +} diff --git a/Game/AI/CoreAI/CreatureAI.cs b/Game/AI/CoreAI/CreatureAI.cs new file mode 100644 index 000000000..1e3876033 --- /dev/null +++ b/Game/AI/CoreAI/CreatureAI.cs @@ -0,0 +1,496 @@ +/* + * Copyright (C) 2012-2017 CypherCore + * + * 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, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +using Framework.Constants; +using Framework.Dynamic; +using Game.Entities; +using Game.Maps; +using Game.Spells; +using System.Collections.Generic; +using System.Linq; + +namespace Game.AI +{ + public class CreatureAI : UnitAI + { + public CreatureAI(Creature _creature) : base(_creature) + { + me = _creature; + MoveInLineOfSight_locked = false; + } + + public override void OnCharmed(bool apply) + { + if (apply) + { + me.NeedChangeAI = true; + me.IsAIEnabled = false; + } + } + + public void Talk(uint id, WorldObject whisperTarget = null) + { + Global.CreatureTextMgr.SendChat(me, (byte)id, whisperTarget); + } + + public void DoZoneInCombat(Creature creature = null, float maxRangeToNearestTarget = 250.0f) + { + if (!creature) + creature = me; + + if (!creature.CanHaveThreatList()) + return; + + Map map = creature.GetMap(); + if (!map.IsDungeon()) //use IsDungeon instead of Instanceable, in case Battlegrounds will be instantiated + { + Log.outError(LogFilter.Server, "DoZoneInCombat call for map that isn't an instance (creature entry = {0})", creature.IsTypeId(TypeId.Unit) ? creature.ToCreature().GetEntry() : 0); + return; + } + + if (!creature.HasReactState(ReactStates.Passive) && creature.GetVictim() == null) + { + Unit nearTarget = creature.SelectNearestTarget(maxRangeToNearestTarget); + if (nearTarget != null) + creature.GetAI().AttackStart(nearTarget); + else if (creature.IsSummon()) + { + Unit summoner = creature.ToTempSummon().GetSummoner(); + if (summoner != null) + { + Unit target = summoner.getAttackerForHelper(); + if (target == null && summoner.CanHaveThreatList() && !summoner.GetThreatManager().isThreatListEmpty()) + target = summoner.GetThreatManager().getHostilTarget(); + if (target != null && (creature.IsFriendlyTo(summoner) || creature.IsHostileTo(target))) + creature.GetAI().AttackStart(target); + } + } + } + + // Intended duplicated check, the code above this should select a victim + // If it can't find a suitable attack target then we should error out. + if (!creature.HasReactState(ReactStates.Passive) && creature.GetVictim() == null) + { + Log.outError(LogFilter.Server, "DoZoneInCombat called for creature that has empty threat list (creature entry = {0})", creature.GetEntry()); + return; + } + + var playerList = map.GetPlayers(); + if (playerList.Empty()) + return; + + foreach (var player in playerList) + { + if (player.IsGameMaster()) + continue; + + if (player.IsAlive()) + { + creature.SetInCombatWith(player); + player.SetInCombatWith(creature); + creature.AddThreat(player, 0.0f); + } + + /* Causes certain things to never leave the threat list (Priest Lightwell, etc): + foreach (var unit in player.m_Controlled) + { + me.SetInCombatWith(unit); + unit.SetInCombatWith(me); + me.AddThreat(unit, 0.0f); + }*/ + } + } + + public virtual void MoveInLineOfSight_Safe(Unit who) + { + if (MoveInLineOfSight_locked) + return; + MoveInLineOfSight_locked = true; + MoveInLineOfSight(who); + MoveInLineOfSight_locked = false; + } + + public virtual void MoveInLineOfSight(Unit who) + { + if (me.GetVictim() != null) + return; + + if (me.GetCreatureType() == CreatureType.NonCombatPet) + return; + + if (me.HasReactState(ReactStates.Aggressive) && me.CanStartAttack(who, false)) + AttackStart(who); + } + + // Distract creature, if player gets too close while stealthed/prowling + public void TriggerAlert(Unit who) + { + // If there's no target, or target isn't a player do nothing + if (!who || !who.IsTypeId(TypeId.Player)) + return; + + // If this unit isn't an NPC, is already distracted, is in combat, is confused, stunned or fleeing, do nothing + if (!me.IsTypeId(TypeId.Unit) || me.IsInCombat() || me.HasUnitState(UnitState.Confused | UnitState.Stunned | UnitState.Fleeing | UnitState.Distracted)) + return; + + // Only alert for hostiles! + if (me.IsCivilian() || me.HasReactState(ReactStates.Passive) || !me.IsHostileTo(who) || !me._IsTargetAcceptable(who)) + return; + + // Send alert sound (if any) for this creature + me.SendAIReaction(AiReaction.Alert); + + // Face the unit (stealthed player) and set distracted state for 5 seconds + me.SetFacingTo(me.GetAngle(who.GetPositionX(), who.GetPositionY()), true); + me.StopMoving(); + me.GetMotionMaster().MoveDistract(5 * Time.InMilliseconds); + } + + // Called for reaction at stopping attack at no attackers or targets + public virtual void EnterEvadeMode(EvadeReason why = EvadeReason.Other) + { + if (!_EnterEvadeMode(why)) + return; + + Log.outDebug( LogFilter.Unit, "Creature {0} enters evade mode.", me.GetEntry()); + + if (me.GetVehicle() == null) // otherwise me will be in evade mode forever + { + Unit owner = me.GetCharmerOrOwner(); + if (owner != null) + { + me.GetMotionMaster().Clear(false); + me.GetMotionMaster().MoveFollow(owner, SharedConst.PetFollowDist, me.GetFollowAngle(), MovementSlot.Active); + } + else + { + // Required to prevent attacking creatures that are evading and cause them to reenter combat + // Does not apply to MoveFollow + me.AddUnitState(UnitState.Evade); + me.GetMotionMaster().MoveTargetedHome(); + } + } + + Reset(); + + if (me.IsVehicle()) // use the same sequence of addtoworld, aireset may remove all summons! + me.GetVehicleKit().Reset(true); + } + + void SetGazeOn(Unit target) + { + if (me.IsValidAttackTarget(target)) + { + if (!me.IsFocusing(null, true)) + AttackStart(target); + me.SetReactState(ReactStates.Passive); + } + } + + public bool UpdateVictimWithGaze() + { + if (!me.IsInCombat()) + return false; + + if (me.HasReactState(ReactStates.Passive)) + { + if (me.GetVictim() != null) + return true; + else + me.SetReactState(ReactStates.Aggressive); + } + + Unit victim = me.SelectVictim(); + if (victim != null) + { + if (!me.IsFocusing(null, true)) + AttackStart(victim); + } + + return me.GetVictim() != null; + } + + public bool UpdateVictim() + { + if (!me.IsInCombat()) + return false; + + if (!me.HasReactState(ReactStates.Passive)) + { + Unit victim = me.SelectVictim(); + if (victim != null) + if (!me.IsFocusing(null, true)) + AttackStart(victim); + + return me.GetVictim() != null; + } + else if (me.GetThreatManager().isThreatListEmpty()) + { + EnterEvadeMode(EvadeReason.NoHostiles); + return false; + } + + return true; + } + + public bool _EnterEvadeMode(EvadeReason why = EvadeReason.Other) + { + if (!me.IsAlive()) + return false; + + me.RemoveAurasOnEvade(); + + // sometimes bosses stuck in combat? + me.DeleteThreatList(); + me.CombatStop(true); + me.SetLootRecipient(null); + me.ResetPlayerDamageReq(); + me.SetLastDamagedTime(0); + me.SetCannotReachTarget(false); + + if (me.IsInEvadeMode()) + return false; + + return true; + } + + public CypherStrings VisualizeBoundary(int duration, Unit owner = null, bool fill = false) + { + if (!owner) + return 0; + + if (_boundary.Empty()) + return CypherStrings.CreatureMovementNotBounded; + + List> Q = new List>(); + List> alreadyChecked = new List>(); + List> outOfBounds = new List>(); + + Position startPosition = owner.GetPosition(); + if (!CheckBoundary(startPosition)) // fall back to creature position + { + startPosition = me.GetPosition(); + if (!CheckBoundary(startPosition)) + { + startPosition = me.GetHomePosition(); + if (!CheckBoundary(startPosition)) // fall back to creature home position + return CypherStrings.CreatureNoInteriorPointFound; + } + } + float spawnZ = startPosition.GetPositionZ() + SharedConst.BoundaryVisualizeSpawnHeight; + + bool boundsWarning = false; + Q.Add(new KeyValuePair(0, 0)); + while (!Q.Empty()) + { + var front = Q.First(); + bool hasOutOfBoundsNeighbor = false; + foreach (var off in new List>() { new KeyValuePair(1, 0), new KeyValuePair(0, 1), new KeyValuePair(-1, 0), new KeyValuePair(0, -1) }) + { + var next = new KeyValuePair(front.Key + off.Key, front.Value + off.Value); + if (next.Key > SharedConst.BoundaryVisualizeFailsafeLimit || next.Key < -SharedConst.BoundaryVisualizeFailsafeLimit || next.Value > SharedConst.BoundaryVisualizeFailsafeLimit || next.Value < -SharedConst.BoundaryVisualizeFailsafeLimit) + { + boundsWarning = true; + continue; + } + if (!alreadyChecked.Contains(next)) // never check a coordinate twice + { + Position nextPos = new Position(startPosition.GetPositionX() + next.Key * SharedConst.BoundaryVisualizeStepSize, startPosition.GetPositionY() + next.Value * SharedConst.BoundaryVisualizeStepSize, startPosition.GetPositionZ()); + if (CheckBoundary(nextPos)) + Q.Add(next); + else + { + outOfBounds.Add(next); + hasOutOfBoundsNeighbor = true; + } + alreadyChecked.Add(next); + } + else + { + if (outOfBounds.Contains(next)) + hasOutOfBoundsNeighbor = true; + } + } + if (fill || hasOutOfBoundsNeighbor) + { + var pos = new Position(startPosition.GetPositionX() + front.Key * SharedConst.BoundaryVisualizeStepSize, startPosition.GetPositionY() + front.Value * SharedConst.BoundaryVisualizeStepSize, spawnZ); + TempSummon point = owner.SummonCreature(SharedConst.BoundaryVisualizeCreature, pos, TempSummonType.TimedDespawn, (uint)(duration * Time.InMilliseconds)); + if (point) + { + point.SetObjectScale(SharedConst.BoundaryVisualizeCreatureScale); + point.SetFlag(UnitFields.Flags, UnitFlags.ImmuneToPc | UnitFlags.Stunned | UnitFlags.ImmuneToNpc); + if (!hasOutOfBoundsNeighbor) + point.SetFlag(UnitFields.Flags, UnitFlags.NotSelectable); + } + Q.Remove(front); + } + } + return boundsWarning ? CypherStrings.CreatureMovementMaybeUnbounded : 0; + } + + public bool CheckBoundary(Position who = null) + { + if (who == null) + who = me; + + foreach (var boundary in _boundary) + if (!boundary.IsWithinBoundary(who)) + return false; + + return true; + } + + public bool CheckInRoom() + { + if (CheckBoundary()) + return true; + else + { + EnterEvadeMode(EvadeReason.Boundary); + return false; + } + } + + public Creature DoSummon(uint entry, Position pos, uint despawnTime = 30000, TempSummonType summonType = TempSummonType.CorpseTimedDespawn) + { + return me.SummonCreature(entry, pos, summonType, despawnTime); + } + + public Creature DoSummon(uint entry, WorldObject obj, float radius = 5.0f, uint despawnTime = 30000, TempSummonType summonType = TempSummonType.CorpseTimedDespawn) + { + Position pos = obj.GetRandomNearPosition(radius); + return me.SummonCreature(entry, pos, summonType, despawnTime); + } + + public Creature DoSummonFlyer(uint entry, WorldObject obj, float flightZ, float radius = 5.0f, uint despawnTime = 30000, TempSummonType summonType = TempSummonType.CorpseTimedDespawn) + { + Position pos = obj.GetRandomNearPosition(radius); + pos.posZ += flightZ; + return me.SummonCreature(entry, pos, summonType, despawnTime); + } + + public void SetBoundary(List boundary) + { + _boundary = boundary; + me.DoImmediateBoundaryCheck(); + } + + // Called for reaction at enter to combat if not in combat yet (enemy can be NULL) + public virtual void EnterCombat(Unit victim) { } + + // Called when the creature is killed + public virtual void JustDied(Unit killer) { } + + // Called when the creature kills a unit + public virtual void KilledUnit(Unit victim) {} + + // Called when the creature summon successfully other creature + public virtual void JustSummoned(Creature summon) { } + public virtual void IsSummonedBy(Unit summoner) { } + + public virtual void SummonedCreatureDespawn(Creature summon) { } + public virtual void SummonedCreatureDies(Creature summon, Unit killer) { } + + // Called when the creature successfully summons a gameobject + public virtual void JustSummonedGameobject(GameObject gameobject) { } + public virtual void SummonedGameobjectDespawn(GameObject gameobject) { } + + // Called when the creature successfully registers a dynamicobject + public virtual void JustRegisteredDynObject(DynamicObject dynObject) { } + public virtual void JustUnregisteredDynObject(DynamicObject dynObject) { } + + // Called when the creature successfully registers an areatrigger + public virtual void JustRegisteredAreaTrigger(AreaTrigger areaTrigger) { } + public virtual void JustUnregisteredAreaTrigger(AreaTrigger areaTrigger) { } + + // Called when hit by a spell + public virtual void SpellHit(Unit caster, SpellInfo spell) {} + + // Called when spell hits a target + public virtual void SpellHitTarget(Unit target, SpellInfo spell) {} + + // Called when the creature is target of hostile action: swing, hostile spell landed, fear/etc) + public virtual void AttackedBy(Unit attacker) { } + public virtual bool IsEscorted() { return false; } + + // Called when creature is spawned or respawned + public virtual void JustRespawned() { } + + public virtual void MovementInform(MovementGeneratorType type, uint id) { } + + // Called at reaching home after evade + public virtual void JustReachedHome() { } + + // Called at text emote receive from player + public virtual void ReceiveEmote(Player player, TextEmotes emoteId) { } + + // Called when owner takes damage + public virtual void OwnerAttackedBy(Unit attacker) {} + + // Called when owner attacks something + public virtual void OwnerAttacked(Unit target) {} + + // called when the corpse of this creature gets removed + public virtual void CorpseRemoved(long respawnDelay) {} + + public virtual void PassengerBoarded(Unit passenger, sbyte seatId, bool apply) { } + + public virtual void OnSpellClick(Unit clicker, ref bool result) { } + + public virtual bool CanSeeAlways(WorldObject obj) { return false; } + + // Called when a player is charmed by the creature + // If a PlayerAI* is returned, that AI is placed on the player instead of the default charm AI + // Object destruction is handled by Unit::RemoveCharmedBy + public virtual PlayerAI GetAIForCharmedPlayer(Player who) { return null; } + + List GetBoundary() { return _boundary; } + + bool MoveInLineOfSight_locked; + protected new Creature me; + List _boundary = new List(); + + protected EventMap _events = new EventMap(); + protected TaskScheduler _scheduler = new TaskScheduler(); + } + + public struct AISpellInfoType + { + public AITarget target; + public AICondition condition; + public uint cooldown; + public uint realCooldown; + public float maxRange; + } + + public enum AITarget + { + Self, + Victim, + Enemy, + Ally, + Buff, + Debuff + } + + public enum AICondition + { + Aggro, + Combat, + Die + } +} diff --git a/Game/AI/CoreAI/GameObjectAI.cs b/Game/AI/CoreAI/GameObjectAI.cs new file mode 100644 index 000000000..409ac4ba4 --- /dev/null +++ b/Game/AI/CoreAI/GameObjectAI.cs @@ -0,0 +1,67 @@ +/* + * Copyright (C) 2012-2017 CypherCore + * + * 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, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +using Framework.Dynamic; +using Game.Entities; + +namespace Game.AI +{ + public class GameObjectAI + { + public GameObjectAI(GameObject g) + { + go = g; + _scheduler = new TaskScheduler(); + } + + public virtual void UpdateAI(uint diff) { } + + public virtual void InitializeAI() { Reset(); } + + public virtual void Reset() { } + + // Pass parameters between AI + public virtual void DoAction(int param = 0) { } + public virtual void SetGUID(ulong guid, int id = 0) { } + public virtual ulong GetGUID(int id = 0) { return 0; } + + public virtual bool GossipHello(Player player, bool isUse) { return false; } + public virtual bool GossipSelect(Player player, uint sender, uint action) { return false; } + public virtual bool GossipSelectCode(Player player, uint sender, uint action, string code) { return false; } + public virtual bool QuestAccept(Player player, Quest quest) { return false; } + public virtual bool QuestReward(Player player, Quest quest, uint opt) { return false; } + public virtual uint GetDialogStatus(Player player) { return 100; } + public virtual void Destroyed(Player player, uint eventId) { } + public virtual void SetData64(uint id, ulong value) { } + public virtual ulong GetData64(uint id) { return 0; } + public virtual uint GetData(uint id) { return 0; } + public virtual void SetData(uint id, uint value) { } + + public virtual void OnGameEvent(bool start, ushort eventId) { } + public virtual void OnStateChanged(uint state, Unit unit) { } + public virtual void EventInform(uint eventId) { } + + protected TaskScheduler _scheduler; + + public GameObject go; + } + + public class NullGameObjectAI : GameObjectAI + { + public NullGameObjectAI(GameObject g) : base(g) { } + } +} diff --git a/Game/AI/CoreAI/GuardAI.cs b/Game/AI/CoreAI/GuardAI.cs new file mode 100644 index 000000000..4a44ab5b1 --- /dev/null +++ b/Game/AI/CoreAI/GuardAI.cs @@ -0,0 +1,76 @@ +/* + * Copyright (C) 2012-2017 CypherCore + * + * 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, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +using Framework.Constants; +using Game.Entities; + +namespace Game.AI +{ + public class GuardAI : ScriptedAI + { + public GuardAI(Creature creature) : base(creature) { } + + public override void UpdateAI(uint diff) + { + if (!UpdateVictim()) + return; + + DoMeleeAttackIfReady(); + } + + public override bool CanSeeAlways(WorldObject obj) + { + if (!obj.isTypeMask(TypeMask.Unit)) + return false; + + var threatList = me.GetThreatManager().getThreatList(); + foreach (var refe in threatList) + if (refe.getUnitGuid() == obj.GetGUID()) + return true; + + return false; + } + + public override void EnterEvadeMode(EvadeReason why) + { + if (!me.IsAlive()) + { + me.GetMotionMaster().MoveIdle(); + me.CombatStop(true); + me.DeleteThreatList(); + return; + } + + Log.outDebug(LogFilter.Unit, "Guard entry: {0} enters evade mode.", me.GetEntry()); + + me.RemoveAllAuras(); + me.DeleteThreatList(); + me.CombatStop(true); + + // Remove ChaseMovementGenerator from MotionMaster stack list, and add HomeMovementGenerator instead + if (me.GetMotionMaster().GetCurrentMovementGeneratorType() == MovementGeneratorType.Chase) + me.GetMotionMaster().MoveTargetedHome(); + } + + public override void JustDied(Unit killer) + { + Player player = killer.GetCharmerOrOwnerPlayerOrPlayerItself(); + if (player != null) + me.SendZoneUnderAttackMessage(player); + } + } +} diff --git a/Game/AI/CoreAI/PassiveAI.cs b/Game/AI/CoreAI/PassiveAI.cs new file mode 100644 index 000000000..f723a2612 --- /dev/null +++ b/Game/AI/CoreAI/PassiveAI.cs @@ -0,0 +1,134 @@ +/* + * Copyright (C) 2012-2017 CypherCore + * + * 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, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +using Framework.Constants; +using Game.Entities; +using System.Collections.Generic; + +namespace Game.AI +{ + public class PassiveAI : CreatureAI + { + public PassiveAI(Creature c) : base(c) + { + me.SetReactState(ReactStates.Passive); + } + + public override void UpdateAI(uint diff) + { + if (me.IsInCombat() && me.getAttackers().Empty()) + EnterEvadeMode(EvadeReason.NoHostiles); + } + + public override void AttackStart(Unit victim) { } + + public override void MoveInLineOfSight(Unit who) { } + } + + public class PossessedAI : CreatureAI + { + public PossessedAI(Creature c) : base(c) + { + me.SetReactState(ReactStates.Passive); + } + + public override void AttackStart(Unit target) + { + me.Attack(target, true); + } + + public override void UpdateAI(uint diff) + { + if (me.GetVictim() != null) + { + if (!me.IsValidAttackTarget(me.GetVictim())) + me.AttackStop(); + else + DoMeleeAttackIfReady(); + } + } + + public override void JustDied(Unit unit) + { + // We died while possessed, disable our loot + me.RemoveFlag(ObjectFields.DynamicFlags, UnitDynFlags.Lootable); + } + + public override void KilledUnit(Unit victim) + { + // We killed a creature, disable victim's loot + if (victim.IsTypeId(TypeId.Unit)) + victim.RemoveFlag(ObjectFields.DynamicFlags, UnitDynFlags.Lootable); + } + + public override void OnCharmed(bool apply) + { + me.NeedChangeAI = true; + me.IsAIEnabled = false; + } + + public override void MoveInLineOfSight(Unit who) { } + + public override void EnterEvadeMode(EvadeReason why) { } + } + + public class NullCreatureAI : CreatureAI + { + public NullCreatureAI(Creature c) : base(c) + { + me.SetReactState(ReactStates.Passive); + } + + public override void MoveInLineOfSight(Unit unit) { } + public override void AttackStart(Unit unit) { } + public override void UpdateAI(uint diff) { } + public override void EnterEvadeMode(EvadeReason why) { } + public override void OnCharmed(bool apply) { } + } + + public class CritterAI : PassiveAI + { + public CritterAI(Creature c) : base(c) + { + me.SetReactState(ReactStates.Passive); + } + + public override void DamageTaken(Unit done_by, ref uint damage) + { + if (!me.HasUnitState(UnitState.Fleeing)) + me.SetControlled(true, UnitState.Fleeing); + } + + public override void EnterEvadeMode(EvadeReason why) + { + if (me.HasUnitState(UnitState.Fleeing)) + me.SetControlled(false, UnitState.Fleeing); + base.EnterEvadeMode(why); + } + } + + public class TriggerAI : NullCreatureAI + { + public TriggerAI(Creature c) : base(c) { } + + public override void IsSummonedBy(Unit summoner) + { + if (me.m_spells[0] != 0) + me.CastSpell(me, me.m_spells[0], false, null, null, summoner.GetGUID()); + } + } +} diff --git a/Game/AI/CoreAI/PetAI.cs b/Game/AI/CoreAI/PetAI.cs new file mode 100644 index 000000000..c68ee938b --- /dev/null +++ b/Game/AI/CoreAI/PetAI.cs @@ -0,0 +1,645 @@ +/* + * Copyright (C) 2012-2017 CypherCore + * + * 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, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +using Framework.Constants; +using Game.Entities; +using Game.Groups; +using Game.Spells; +using System; +using System.Collections.Generic; + +namespace Game.AI +{ + public class PetAI : CreatureAI + { + public PetAI(Creature c) : base(c) + { + i_tracker = new TimeTracker(5000); + + UpdateAllies(); + } + + bool _needToStop() + { + // This is needed for charmed creatures, as once their target was reset other effects can trigger threat + if (me.IsCharmed() && me.GetVictim() == me.GetCharmer()) + return true; + + return !me.IsValidAttackTarget(me.GetVictim()); + } + + void _stopAttack() + { + if (!me.IsAlive()) + { + Log.outDebug(LogFilter.Server, "Creature stoped attacking cuz his dead [{0}]", me.GetGUID().ToString()); + me.GetMotionMaster().Clear(); + me.GetMotionMaster().MoveIdle(); + me.CombatStop(); + me.getHostileRefManager().deleteReferences(); + + return; + } + + me.AttackStop(); + me.InterruptNonMeleeSpells(false); + me.SendMeleeAttackStop(); // Should stop pet's attack button from flashing + me.GetCharmInfo().SetIsCommandAttack(false); + ClearCharmInfoFlags(); + HandleReturnMovement(); + } + + public override void UpdateAI(uint diff) + { + if (!me.IsAlive() || me.GetCharmInfo() == null) + return; + + Unit owner = me.GetCharmerOrOwner(); + + if (m_updateAlliesTimer <= diff) + // UpdateAllies self set update timer + UpdateAllies(); + else + m_updateAlliesTimer -= diff; + + if (me.GetVictim() && me.GetVictim().IsAlive()) + { + // is only necessary to stop casting, the pet must not exit combat + if (!me.GetCurrentSpell(CurrentSpellTypes.Channeled) && // ignore channeled spells (Pin, Seduction) + (me.GetVictim() && me.GetVictim().HasBreakableByDamageCrowdControlAura(me))) + { + me.InterruptNonMeleeSpells(false); + return; + } + + if (_needToStop()) + { + Log.outDebug(LogFilter.Server, "Pet AI stopped attacking [{0}]", me.GetGUID().ToString()); + _stopAttack(); + return; + } + + // Check before attacking to prevent pets from leaving stay position + if (me.GetCharmInfo().HasCommandState(CommandStates.Stay)) + { + if (me.GetCharmInfo().IsCommandAttack() || (me.GetCharmInfo().IsAtStay() && me.IsWithinMeleeRange(me.GetVictim()))) + DoMeleeAttackIfReady(); + } + else + DoMeleeAttackIfReady(); + } + else + { + if (me.HasReactState(ReactStates.Aggressive) || me.GetCharmInfo().IsAtStay()) + { + // Every update we need to check targets only in certain cases + // Aggressive - Allow auto select if owner or pet don't have a target + // Stay - Only pick from pet or owner targets / attackers so targets won't run by + // while chasing our owner. Don't do auto select. + // All other cases (ie: defensive) - Targets are assigned by AttackedBy(), OwnerAttackedBy(), OwnerAttacked(), etc. + Unit nextTarget = SelectNextTarget(me.HasReactState(ReactStates.Aggressive)); + + if (nextTarget) + AttackStart(nextTarget); + else + HandleReturnMovement(); + } + else + HandleReturnMovement(); + } + + // Autocast (casted only in combat or persistent spells in any state) + if (!me.HasUnitState(UnitState.Casting)) + { + List> targetSpellStore = new List>(); + + for (byte i = 0; i < me.GetPetAutoSpellSize(); ++i) + { + uint spellID = me.GetPetAutoSpellOnPos(i); + if (spellID == 0) + continue; + + SpellInfo spellInfo = Global.SpellMgr.GetSpellInfo(spellID); + if (spellInfo == null) + continue; + + if (me.GetCharmInfo() != null && me.GetSpellHistory().HasGlobalCooldown(spellInfo)) + continue; + + // check spell cooldown + if (!me.GetSpellHistory().IsReady(spellInfo)) + continue; + + if (spellInfo.IsPositive()) + { + if (spellInfo.CanBeUsedInCombat()) + { + // Check if we're in combat or commanded to attack + if (!me.IsInCombat() && !me.GetCharmInfo().IsCommandAttack()) + continue; + } + + Spell spell = new Spell(me, spellInfo, TriggerCastFlags.None); + bool spellUsed = false; + + // Some spells can target enemy or friendly (DK Ghoul's Leap) + // Check for enemy first (pet then owner) + Unit target = me.getAttackerForHelper(); + if (!target && owner) + target = owner.getAttackerForHelper(); + + if (target) + { + if (CanAIAttack(target) && spell.CanAutoCast(target)) + { + targetSpellStore.Add(Tuple.Create(target, spell)); + spellUsed = true; + } + } + + if (spellInfo.HasEffect(SpellEffectName.JumpDest)) + { + if (!spellUsed) + spell.Dispose(); + continue; // Pets must only jump to target + } + + // No enemy, check friendly + if (!spellUsed) + { + foreach (var tar in m_AllySet) + { + Unit ally = Global.ObjAccessor.GetUnit(me, tar); + + //only buff targets that are in combat, unless the spell can only be cast while out of combat + if (!ally) + continue; + + if (spell.CanAutoCast(ally)) + { + targetSpellStore.Add(Tuple.Create(ally, spell)); + spellUsed = true; + break; + } + } + } + + // No valid targets at all + if (!spellUsed) + spell.Dispose(); + } + else if (me.GetVictim() && CanAIAttack(me.GetVictim()) && spellInfo.CanBeUsedInCombat()) + { + Spell spell = new Spell(me, spellInfo, TriggerCastFlags.None); + if (spell.CanAutoCast(me.GetVictim())) + targetSpellStore.Add(Tuple.Create(me.GetVictim(), spell)); + else + spell.Dispose(); + } + } + + //found units to cast on to + if (!targetSpellStore.Empty()) + { + int index = RandomHelper.IRand(0, targetSpellStore.Count - 1); + var tss = targetSpellStore[index]; + + Spell spell = tss.Item2; + Unit target = tss.Item1; + + targetSpellStore.RemoveAt(index); + + SpellCastTargets targets = new SpellCastTargets(); + targets.SetUnitTarget(target); + + spell.prepare(targets); + } + + // deleted cached Spell objects + foreach (var pair in targetSpellStore) + pair.Item2.Dispose(); + } + + // Update speed as needed to prevent dropping too far behind and despawning + me.UpdateSpeed(UnitMoveType.Run); + me.UpdateSpeed(UnitMoveType.Walk); + me.UpdateSpeed(UnitMoveType.Flight); + } + + void UpdateAllies() + { + m_updateAlliesTimer = 10 * Time.InMilliseconds; // update friendly targets every 10 seconds, lesser checks increase performance + + Unit owner = me.GetCharmerOrOwner(); + if (!owner) + return; + + Group group = null; + Player player = owner.ToPlayer(); + if (player) + group = player.GetGroup(); + + //only pet and owner/not in group.ok + if (m_AllySet.Count == 2 && !group) + return; + + //owner is in group; group members filled in already (no raid . subgroupcount = whole count) + if (group && !group.isRaidGroup() && m_AllySet.Count == (group.GetMembersCount() + 2)) + return; + + m_AllySet.Clear(); + m_AllySet.Add(me.GetGUID()); + if (group) //add group + { + for (GroupReference refe = group.GetFirstMember(); refe != null; refe = refe.next()) + { + Player Target = refe.GetSource(); + if (!Target || !group.SameSubGroup(owner.ToPlayer(), Target)) + continue; + + if (Target.GetGUID() == owner.GetGUID()) + continue; + + m_AllySet.Add(Target.GetGUID()); + } + } + else //remove group + m_AllySet.Add(owner.GetGUID()); + } + + public override void KilledUnit(Unit victim) + { + // Called from Unit.Kill() in case where pet or owner kills something + // if owner killed this victim, pet may still be attacking something else + if (me.GetVictim() && me.GetVictim() != victim) + return; + + // Clear target just in case. May help problem where health / focus / mana + // regen gets stuck. Also resets attack command. + // Can't use _stopAttack() because that activates movement handlers and ignores + // next target selection + me.AttackStop(); + me.InterruptNonMeleeSpells(false); + me.SendMeleeAttackStop(); // Stops the pet's 'Attack' button from flashing + + // Before returning to owner, see if there are more things to attack + Unit nextTarget = SelectNextTarget(false); + if (nextTarget) + AttackStart(nextTarget); + else + HandleReturnMovement(); // Return + } + + public override void AttackStart(Unit victim) + { + // Overrides Unit.AttackStart to correctly evaluate Pet states + + // Check all pet states to decide if we can attack this target + if (!CanAIAttack(victim)) + return; + + // Only chase if not commanded to stay or if stay but commanded to attack + DoAttack(victim, (!me.GetCharmInfo().HasCommandState(CommandStates.Stay) || me.GetCharmInfo().IsCommandAttack())); + } + + public override void OwnerAttackedBy(Unit attacker) + { + // Called when owner takes damage. This function helps keep pets from running off + // simply due to owner gaining aggro. + + if (!attacker) + return; + + // Passive pets don't do anything + if (me.HasReactState(ReactStates.Passive)) + return; + + // Prevent pet from disengaging from current target + if (me.GetVictim() && me.GetVictim().IsAlive()) + return; + + // Continue to evaluate and attack if necessary + AttackStart(attacker); + } + + public override void OwnerAttacked(Unit target) + { + // Called when owner attacks something. Allows defensive pets to know + // that they need to assist + + // Target might be null if called from spell with invalid cast targets + if (!target) + return; + + // Passive pets don't do anything + if (me.HasReactState(ReactStates.Passive)) + return; + + // Prevent pet from disengaging from current target + if (me.GetVictim() && me.GetVictim().IsAlive()) + return; + + // Continue to evaluate and attack if necessary + AttackStart(target); + } + + Unit SelectNextTarget(bool allowAutoSelect) + { + // Provides next target selection after current target death. + // This function should only be called internally by the AI + // Targets are not evaluated here for being valid targets, that is done in _CanAttack() + // The parameter: allowAutoSelect lets us disable aggressive pet auto targeting for certain situations + + // Passive pets don't do next target selection + if (me.HasReactState(ReactStates.Passive)) + return null; + + // Check pet attackers first so we don't drag a bunch of targets to the owner + Unit myAttacker = me.getAttackerForHelper(); + if (myAttacker) + if (!myAttacker.HasBreakableByDamageCrowdControlAura()) + return myAttacker; + + // Not sure why we wouldn't have an owner but just in case... + if (!me.GetCharmerOrOwner()) + return null; + + // Check owner attackers + Unit ownerAttacker = me.GetCharmerOrOwner().getAttackerForHelper(); + if (ownerAttacker) + if (!ownerAttacker.HasBreakableByDamageCrowdControlAura()) + return ownerAttacker; + + // Check owner victim + // 3.0.2 - Pets now start attacking their owners victim in defensive mode as soon as the hunter does + Unit ownerVictim = me.GetCharmerOrOwner().GetVictim(); + if (ownerVictim) + return ownerVictim; + + // Neither pet or owner had a target and aggressive pets can pick any target + // To prevent aggressive pets from chain selecting targets and running off, we + // only select a random target if certain conditions are met. + if (me.HasReactState(ReactStates.Aggressive) && allowAutoSelect) + { + if (!me.GetCharmInfo().IsReturning() || me.GetCharmInfo().IsFollowing() || me.GetCharmInfo().IsAtStay()) + { + Unit nearTarget = me.SelectNearestHostileUnitInAggroRange(true); + if (nearTarget) + return nearTarget; + } + } + + // Default - no valid targets + return null; + } + + void HandleReturnMovement() + { + // Handles moving the pet back to stay or owner + + // Prevent activating movement when under control of spells + // such as "Eyes of the Beast" + if (me.IsCharmed()) + return; + + if (me.GetCharmInfo().HasCommandState(CommandStates.Stay)) + { + if (!me.GetCharmInfo().IsAtStay() && !me.GetCharmInfo().IsReturning()) + { + // Return to previous position where stay was clicked + float x, y, z; + + me.GetCharmInfo().GetStayPosition(out x, out y, out z); + ClearCharmInfoFlags(); + me.GetCharmInfo().SetIsReturning(true); + me.GetMotionMaster().Clear(); + me.GetMotionMaster().MovePoint(me.GetGUID().GetCounter(), x, y, z); + } + } + else // COMMAND_FOLLOW + { + if (!me.GetCharmInfo().IsFollowing() && !me.GetCharmInfo().IsReturning()) + { + ClearCharmInfoFlags(); + me.GetCharmInfo().SetIsReturning(true); + me.GetMotionMaster().Clear(); + me.GetMotionMaster().MoveFollow(me.GetCharmerOrOwner(), SharedConst.PetFollowDist, me.GetFollowAngle()); + } + } + } + + void DoAttack(Unit target, bool chase) + { + // Handles attack with or without chase and also resets flags + // for next update / creature kill + + if (me.Attack(target, true)) + { + Unit owner = me.GetOwner(); + if (owner) + owner.SetInCombatWith(target); + + // Play sound to let the player know the pet is attacking something it picked on its own + if (me.HasReactState(ReactStates.Aggressive) && !me.GetCharmInfo().IsCommandAttack()) + me.SendPetAIReaction(me.GetGUID()); + + if (chase) + { + bool oldCmdAttack = me.GetCharmInfo().IsCommandAttack(); // This needs to be reset after other flags are cleared + ClearCharmInfoFlags(); + me.GetCharmInfo().SetIsCommandAttack(oldCmdAttack); // For passive pets commanded to attack so they will use spells + me.GetMotionMaster().Clear(); + me.GetMotionMaster().MoveChase(target); + } + else + { + ClearCharmInfoFlags(); + me.GetCharmInfo().SetIsAtStay(true); + me.GetMotionMaster().Clear(); + me.GetMotionMaster().MoveIdle(); + } + } + } + + public override void MovementInform(MovementGeneratorType type, uint id) + { + // Receives notification when pet reaches stay or follow owner + switch (type) + { + case MovementGeneratorType.Point: + { + // Pet is returning to where stay was clicked. data should be + // pet's GUIDLow since we set that as the waypoint ID + if (id == me.GetGUID().GetCounter() && me.GetCharmInfo().IsReturning()) + { + ClearCharmInfoFlags(); + me.GetCharmInfo().SetIsAtStay(true); + me.GetMotionMaster().Clear(); + me.GetMotionMaster().MoveIdle(); + } + break; + } + case MovementGeneratorType.Follow: + { + // If data is owner's GUIDLow then we've reached follow point, + // otherwise we're probably chasing a creature + if (me.GetCharmerOrOwner() && me.GetCharmInfo() != null && id == me.GetCharmerOrOwner().GetGUID().GetCounter() && me.GetCharmInfo().IsReturning()) + { + ClearCharmInfoFlags(); + me.GetCharmInfo().SetIsFollowing(true); + } + break; + } + default: + break; + } + } + + public override bool CanAIAttack(Unit victim) + { + // Evaluates wether a pet can attack a specific target based on CommandState, ReactState and other flags + // IMPORTANT: The order in which things are checked is important, be careful if you add or remove checks + + // Hmmm... + if (!victim) + return false; + + if (!victim.IsAlive()) + { + // Clear target to prevent getting stuck on dead targets + me.AttackStop(); + me.InterruptNonMeleeSpells(false); + me.SendMeleeAttackStop(); + return false; + } + + // Passive - passive pets can attack if told to + if (me.HasReactState(ReactStates.Passive)) + return me.GetCharmInfo().IsCommandAttack(); + + // CC - mobs under crowd control can be attacked if owner commanded + if (victim.HasBreakableByDamageCrowdControlAura()) + return me.GetCharmInfo().IsCommandAttack(); + + // Returning - pets ignore attacks only if owner clicked follow + if (me.GetCharmInfo().IsReturning()) + return !me.GetCharmInfo().IsCommandFollow(); + + // Stay - can attack if target is within range or commanded to + if (me.GetCharmInfo().HasCommandState(CommandStates.Stay)) + return (me.IsWithinMeleeRange(victim) || me.GetCharmInfo().IsCommandAttack()); + + // Pets attacking something (or chasing) should only switch targets if owner tells them to + if (me.GetVictim() && me.GetVictim() != victim) + { + // Check if our owner selected this target and clicked "attack" + Unit ownerTarget = null; + Player owner = me.GetCharmerOrOwner().ToPlayer(); + if (owner) + ownerTarget = owner.GetSelectedUnit(); + else + ownerTarget = me.GetCharmerOrOwner().GetVictim(); + + if (ownerTarget && me.GetCharmInfo().IsCommandAttack()) + return (victim.GetGUID() == ownerTarget.GetGUID()); + } + + // Follow + if (me.GetCharmInfo().HasCommandState(CommandStates.Follow)) + return !me.GetCharmInfo().IsReturning(); + + // default, though we shouldn't ever get here + return false; + } + + public override void ReceiveEmote(Player player, TextEmotes emoteId) + { + if (!me.GetOwnerGUID().IsEmpty() && me.GetOwnerGUID() == player.GetGUID()) + switch (emoteId) + { + case TextEmotes.Cower: + if (me.IsPet() && me.ToPet().IsPetGhoul()) + me.HandleEmoteCommand(Emote.OneshotOmnicastGhoul); + break; + case TextEmotes.Angry: + if (me.IsPet() && me.ToPet().IsPetGhoul()) + me.HandleEmoteCommand(Emote.StateStun); + break; + case TextEmotes.Glare: + if (me.IsPet() && me.ToPet().IsPetGhoul()) + me.HandleEmoteCommand(Emote.StateStun); + break; + case TextEmotes.Soothe: + if (me.IsPet() && me.ToPet().IsPetGhoul()) + me.HandleEmoteCommand( Emote.OneshotOmnicastGhoul); + break; + } + } + + public override void OnCharmed(bool apply) + { + me.NeedChangeAI = true; + me.IsAIEnabled = false; + } + + void ClearCharmInfoFlags() + { + // Quick access to set all flags to FALSE + + CharmInfo ci = me.GetCharmInfo(); + + if (ci != null) + { + ci.SetIsAtStay(false); + ci.SetIsCommandAttack(false); + ci.SetIsCommandFollow(false); + ci.SetIsFollowing(false); + ci.SetIsReturning(false); + } + } + + public override void AttackedBy(Unit attacker) + { + // Called when pet takes damage. This function helps keep pets from running off + // simply due to gaining aggro. + + if (!attacker) + return; + + // Passive pets don't do anything + if (me.HasReactState( ReactStates.Passive)) + return; + + // Prevent pet from disengaging from current target + if (me.GetVictim() && me.GetVictim().IsAlive()) + return; + + // Continue to evaluate and attack if necessary + AttackStart(attacker); + } + + // The following aren't used by the PetAI but need to be defined to override + // default CreatureAI functions which interfere with the PetAI + public override void MoveInLineOfSight(Unit who) { } + public override void MoveInLineOfSight_Safe(Unit who) { } + public override void EnterEvadeMode(EvadeReason why) { } + + TimeTracker i_tracker; + List m_AllySet = new List(); + uint m_updateAlliesTimer; + } +} diff --git a/Game/AI/CoreAI/TotemAI.cs b/Game/AI/CoreAI/TotemAI.cs new file mode 100644 index 000000000..e7124e5ac --- /dev/null +++ b/Game/AI/CoreAI/TotemAI.cs @@ -0,0 +1,83 @@ +/* + * Copyright (C) 2012-2017 CypherCore + * + * 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, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +using Framework.Constants; +using Game.Entities; +using Game.Maps; + +namespace Game.AI +{ + public class TotemAI : CreatureAI + { + public TotemAI(Creature c) : base(c) + { + i_victimGuid = ObjectGuid.Empty; + } + + public override void EnterEvadeMode(EvadeReason why) + { + me.CombatStop(true); + } + + public override void UpdateAI(uint diff) + { + if (me.ToTotem().GetTotemType() != TotemType.Active) + return; + + if (!me.IsAlive() || me.IsNonMeleeSpellCast(false)) + return; + + // Search spell + var spellInfo = Global.SpellMgr.GetSpellInfo(me.ToTotem().GetSpell()); + if (spellInfo == null) + return; + + // Get spell range + float max_range = spellInfo.GetMaxRange(false); + + // SPELLMOD_RANGE not applied in this place just because not existence range mods for attacking totems + + Unit victim = !i_victimGuid.IsEmpty() ? Global.ObjAccessor.GetUnit(me, i_victimGuid) : null; + + // Search victim if no, not attackable, or out of range, or friendly (possible in case duel end) + if (victim == null || !victim.isTargetableForAttack() || !me.IsWithinDistInMap(victim, max_range) || + me.IsFriendlyTo(victim) || !me.CanSeeOrDetect(victim)) + { + victim = null; + var u_check = new NearestAttackableUnitInObjectRangeCheck(me, me, max_range); + var checker = new UnitLastSearcher(me, u_check); + Cell.VisitAllObjects(me, checker, max_range); + victim = checker.GetTarget(); + } + + // If have target + if (victim != null) + { + // remember + i_victimGuid = victim.GetGUID(); + + // attack + me.SetInFront(victim); // client change orientation by self + me.CastSpell(victim, me.ToTotem().GetSpell(), false); + } + else + i_victimGuid.Clear(); + } + + ObjectGuid i_victimGuid; + } +} diff --git a/Game/AI/CoreAI/UnitAI.cs b/Game/AI/CoreAI/UnitAI.cs new file mode 100644 index 000000000..ee48fd34b --- /dev/null +++ b/Game/AI/CoreAI/UnitAI.cs @@ -0,0 +1,566 @@ +/* + * Copyright (C) 2012-2017 CypherCore + * + * 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, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +using Framework.Constants; +using Game.Combat; +using Game.Entities; +using Game.Spells; +using System; +using System.Collections.Generic; +using System.Diagnostics.Contracts; +using System.Linq; + +namespace Game.AI +{ + public class UnitAI + { + public UnitAI(Unit _unit) + { + me = _unit; + } + + public virtual void AttackStart(Unit victim) + { + if (victim != null && me.Attack(victim, true)) + { + // Clear distracted state on attacking + if (me.HasUnitState(UnitState.Distracted)) + { + me.ClearUnitState(UnitState.Distracted); + me.GetMotionMaster().Clear(); + } + me.GetMotionMaster().MoveChase(victim); + } + } + + public void AttackStartCaster(Unit victim, float dist) + { + if (victim != null && me.Attack(victim, false)) + me.GetMotionMaster().MoveChase(victim, dist); + } + + ThreatManager GetThreatManager() + { + return me.GetThreatManager(); + } + + void SortByDistanceTo(Unit reference, List targets) + { + targets.Sort(new ObjectDistanceOrderPred(reference)); + } + + public void DoMeleeAttackIfReady() + { + if (me.HasUnitState(UnitState.Casting)) + return; + + Unit victim = me.GetVictim(); + + if (!me.IsWithinMeleeRange(victim)) + return; + + //Make sure our attack is ready and we aren't currently casting before checking distance + if (me.isAttackReady()) + { + me.AttackerStateUpdate(victim); + me.resetAttackTimer(); + } + + if (me.haveOffhandWeapon() && me.isAttackReady(WeaponAttackType.OffAttack)) + { + me.AttackerStateUpdate(victim, WeaponAttackType.OffAttack); + me.resetAttackTimer(WeaponAttackType.OffAttack); + } + } + + public bool DoSpellAttackIfReady(uint spell) + { + if (me.HasUnitState(UnitState.Casting) || !me.isAttackReady()) + return true; + + var spellInfo = Global.SpellMgr.GetSpellInfo(spell); + if (spellInfo != null) + { + if (me.IsWithinCombatRange(me.GetVictim(), spellInfo.GetMaxRange(false))) + { + me.CastSpell(me.GetVictim(), spellInfo, TriggerCastFlags.None); + me.resetAttackTimer(); + return true; + } + } + + return false; + } + + public Unit SelectTarget(SelectAggroTarget targetType, uint position = 0, float dist = 0.0f, bool playerOnly = false, int aura = 0) + { + return SelectTarget(targetType, position, new DefaultTargetSelector(me, dist, playerOnly, aura)); + } + + // Select the targets satifying the predicate. + public Unit SelectTarget(SelectAggroTarget targetType, uint position, ISelector selector) + { + var threatlist = GetThreatManager().getThreatList(); + if (position >= threatlist.Count) + return null; + + List targetList = new List(); + Unit currentVictim = null; + + var currentVictimReference = GetThreatManager().getCurrentVictim(); + if (currentVictimReference != null) + { + currentVictim = currentVictimReference.getTarget(); + + // Current victim always goes first + if (currentVictim && selector.Check(currentVictim)) + targetList.Add(currentVictim); + } + + foreach (var hostileRef in threatlist) + { + if (currentVictim != null && hostileRef.getTarget() != currentVictim && selector.Check(hostileRef.getTarget())) + targetList.Add(hostileRef.getTarget()); + else if (currentVictim == null && selector.Check(hostileRef.getTarget())) + targetList.Add(hostileRef.getTarget()); + } + + if (position >= targetList.Count) + return null; + + if (targetType == SelectAggroTarget.Nearest || targetType == SelectAggroTarget.Farthest) + SortByDistanceTo(me, targetList); + + switch (targetType) + { + case SelectAggroTarget.Nearest: + case SelectAggroTarget.TopAggro: + { + return targetList.First(); + } + case SelectAggroTarget.Farthest: + case SelectAggroTarget.BottomAggro: + { + return targetList.Last(); + } + case SelectAggroTarget.Random: + { + return targetList.PickRandom(); + } + default: + break; + } + + return null; + } + + public List SelectTargetList(uint num, SelectAggroTarget targetType, float dist, bool playerOnly, int aura = 0) + { + return SelectTargetList(new DefaultTargetSelector(me, dist, playerOnly, aura), num, targetType); + } + + // Select the targets satifying the predicate. + // predicate shall extend std.unary_function + public List SelectTargetList(ISelector selector, uint maxTargets, SelectAggroTarget targetType) + { + var targetList = new List(); + + var threatlist = GetThreatManager().getThreatList(); + if (threatlist.Empty()) + return targetList; + + foreach (var hostileRef in threatlist) + if (selector.Check(hostileRef.getTarget())) + targetList.Add(hostileRef.getTarget()); + + if (targetList.Count < maxTargets) + return targetList; + + if (targetType == SelectAggroTarget.Nearest || targetType == SelectAggroTarget.Farthest) + SortByDistanceTo(me, targetList); + + if (targetType == SelectAggroTarget.Farthest || targetType == SelectAggroTarget.BottomAggro) + targetList.Reverse(); + + if (targetType == SelectAggroTarget.Random) + targetList = targetList.PickRandom(maxTargets).ToList(); + else + targetList.Resize(maxTargets); + + return targetList; + } + + public void DoCast(uint spellId) + { + Unit target = null; + + switch (AISpellInfo[spellId].target) + { + default: + case AITarget.Self: + target = me; + break; + case AITarget.Victim: + target = me.GetVictim(); + break; + case AITarget.Enemy: + { + var spellInfo = Global.SpellMgr.GetSpellInfo(spellId); + if (spellInfo != null) + { + bool playerOnly = spellInfo.HasAttribute(SpellAttr3.OnlyTargetPlayers); + target = SelectTarget(SelectAggroTarget.Random, 0, spellInfo.GetMaxRange(false), playerOnly); + } + break; + } + case AITarget.Ally: + case AITarget.Buff: + target = me; + break; + case AITarget.Debuff: + { + var spellInfo = Global.SpellMgr.GetSpellInfo(spellId); + if (spellInfo != null) + { + bool playerOnly = spellInfo.HasAttribute(SpellAttr3.OnlyTargetPlayers); + float range = spellInfo.GetMaxRange(false); + + DefaultTargetSelector targetSelector = new DefaultTargetSelector(me, range, playerOnly, -(int)spellId); + if (!spellInfo.AuraInterruptFlags.HasAnyFlag(SpellAuraInterruptFlags.NotVictim) + && targetSelector.Check(me.GetVictim())) + target = me.GetVictim(); + else + target = SelectTarget(SelectAggroTarget.Random, 0, targetSelector); + } + break; + } + } + + if (target != null) + me.CastSpell(target, spellId, false); + } + + public void DoCast(Unit victim, uint spellId, bool triggered = false) + { + if (victim == null || (me.HasUnitState(UnitState.Casting) && !triggered)) + return; + + me.CastSpell(victim, spellId, triggered); + } + + public void DoCastSelf(uint spellId, bool triggered = false) { DoCast(me, spellId, triggered); } + + public void DoCastVictim(uint spellId, bool triggered = false) + { + if (me.GetVictim() == null || (me.HasUnitState(UnitState.Casting) && !triggered)) + return; + + me.CastSpell(me.GetVictim(), spellId, triggered); + } + + public void DoCastAOE(uint spellId, bool triggered = false) + { + if (!triggered && me.HasUnitState(UnitState.Casting)) + return; + + me.CastSpell((Unit)null, spellId, triggered); + } + + public static void FillAISpellInfo() + { + var spellStorage = Global.SpellMgr.GetSpellInfoStorage(); + AISpellInfo = new AISpellInfoType[spellStorage.Keys.Max() + 1]; + + foreach (var spellInfo in spellStorage.Values) + { + AISpellInfoType AIInfo = AISpellInfo[spellInfo.Id]; + if (spellInfo.HasAttribute(SpellAttr0.CastableWhileDead)) + AIInfo.condition = AICondition.Die; + else if (spellInfo.IsPassive() || spellInfo.GetDuration() == -1) + AIInfo.condition = AICondition.Aggro; + else + AIInfo.condition = AICondition.Combat; + + if (AIInfo.cooldown < spellInfo.RecoveryTime) + AIInfo.cooldown = spellInfo.RecoveryTime; + + if (spellInfo.GetMaxRange(false) == 0) + { + if (AIInfo.target < AITarget.Self) + AIInfo.target = AITarget.Self; + } + else + { + foreach (SpellEffectInfo effect in spellInfo.GetEffectsForDifficulty(Difficulty.None)) + { + if (effect == null) + continue; + + var targetType = effect.TargetA.GetTarget(); + + if (targetType == Targets.UnitEnemy || targetType == Targets.DestEnemy) + { + if (AIInfo.target < AITarget.Victim) + AIInfo.target = AITarget.Victim; + } + else if (targetType == Targets.UnitDestAreaEnemy) + { + if (AIInfo.target < AITarget.Enemy) + AIInfo.target = AITarget.Enemy; + } + + if (effect.Effect == SpellEffectName.ApplyAura) + { + if (targetType == Targets.UnitEnemy) + { + if (AIInfo.target < AITarget.Debuff) + AIInfo.target = AITarget.Debuff; + } + else if (spellInfo.IsPositive()) + { + if (AIInfo.target < AITarget.Buff) + AIInfo.target = AITarget.Buff; + } + } + } + } + AIInfo.realCooldown = spellInfo.RecoveryTime + spellInfo.StartRecoveryTime; + AIInfo.maxRange = spellInfo.GetMaxRange(false) * 3 / 4; + } + } + + public virtual bool CanAIAttack(Unit victim) { return true; } + + public virtual void UpdateAI(uint diff) { } + + public virtual void InitializeAI() + { + if (!me.IsDead()) + Reset(); + } + + public virtual void Reset() { } + + public virtual void OnCharmed(bool apply) { } + + public virtual void DoAction(int param) { } + public virtual uint GetData(uint id = 0) { return 0; } + public virtual void SetData(uint id, uint value) { } + public virtual void SetGUID(ObjectGuid guid, int id = 0) { } + public virtual ObjectGuid GetGUID(int id = 0) { return ObjectGuid.Empty; } + + public virtual void DamageDealt(Unit victim, ref uint damage, DamageEffectType damageType) { } + public virtual void DamageTaken(Unit attacker, ref uint damage) { } + public virtual void HealReceived(Unit by, uint addhealth) { } + public virtual void HealDone(Unit to, uint addhealth) { } + public virtual void SpellInterrupted(uint spellId, uint unTimeMs) {} + + public virtual void sGossipHello(Player player) { } + public virtual void sGossipSelect(Player player, uint menuId, uint gossipListId) { } + public virtual void sGossipSelectCode(Player player, uint menuId, uint gossipListId, string code) { } + public virtual void sQuestAccept(Player player, Quest quest) { } + public virtual void sQuestSelect(Player player, Quest quest) { } + public virtual void sQuestComplete(Player player, Quest quest) { } + public virtual void sQuestReward(Player player, Quest quest, uint opt) { } + public virtual bool sOnDummyEffect(Unit caster, uint spellId, int effIndex) { return false; } + public virtual void sOnGameEvent(bool start, ushort eventId) { } + + public static AISpellInfoType[] AISpellInfo; + + protected Unit me { get; private set; } + } + + public enum SelectAggroTarget + { + Random = 0, //Just selects a random target + TopAggro, //Selects targes from top aggro to bottom + BottomAggro, //Selects targets from bottom aggro to top + Nearest, + Farthest + } + + public interface ISelector + { + bool Check(Unit target); + } + + // default predicate function to select target based on distance, player and/or aura criteria + public class DefaultTargetSelector : ISelector + { + Unit me; + float m_dist; + bool m_playerOnly; + int m_aura; + + // unit: the reference unit + // dist: if 0: ignored, if > 0: maximum distance to the reference unit, if < 0: minimum distance to the reference unit + // playerOnly: self explaining + // aura: if 0: ignored, if > 0: the target shall have the aura, if < 0, the target shall NOT have the aura + public DefaultTargetSelector(Unit unit, float dist, bool playerOnly, int aura) + { + me = unit; + m_dist = dist; + m_playerOnly = playerOnly; + m_aura = aura; + } + + public bool Check(Unit target) + { + + if (me == null) + return false; + + if (target == null) + return false; + + if (m_playerOnly && !target.IsTypeId(TypeId.Player)) + return false; + + if (m_dist > 0.0f && !me.IsWithinCombatRange(target, m_dist)) + return false; + + if (m_dist < 0.0f && me.IsWithinCombatRange(target, -m_dist)) + return false; + + if (m_aura != 0) + { + if (m_aura > 0) + { + if (!target.HasAura((uint)m_aura)) + return false; + } + else + { + if (target.HasAura((uint)-m_aura)) + return false; + } + } + + return true; + } + } + + // Target selector for spell casts checking range, auras and attributes + // todo Add more checks from Spell.CheckCast + public class SpellTargetSelector : ISelector + { + public SpellTargetSelector(Unit caster, uint spellId) + { + _caster = caster; + _spellInfo = Global.SpellMgr.GetSpellInfo(spellId); + + Contract.Assert(_spellInfo != null); + } + + public bool Check(Unit target) + { + if (target == null) + return false; + + if (_spellInfo.CheckTarget(_caster, target) != SpellCastResult.SpellCastOk) + return false; + + // copypasta from Spell.CheckRange + float minRange = 0.0f; + float maxRange = 0.0f; + float rangeMod = 0.0f; + if (_spellInfo.RangeEntry != null) + { + if (_spellInfo.RangeEntry.Flags.HasAnyFlag(SpellRangeFlag.Melee)) + { + rangeMod = _caster.GetCombatReach() + 4.0f / 3.0f; + rangeMod += target.GetCombatReach(); + + rangeMod = Math.Max(rangeMod, SharedConst.NominalMeleeRange); + } + else + { + float meleeRange = 0.0f; + if (_spellInfo.RangeEntry.Flags.HasAnyFlag(SpellRangeFlag.Ranged)) + { + meleeRange = _caster.GetCombatReach() + 4.0f / 3.0f; + meleeRange += target.GetCombatReach(); + + meleeRange = Math.Max(meleeRange, SharedConst.NominalMeleeRange); + } + + minRange = _caster.GetSpellMinRangeForTarget(target, _spellInfo) + meleeRange; + maxRange = _caster.GetSpellMaxRangeForTarget(target, _spellInfo); + + rangeMod = _caster.GetCombatReach(); + rangeMod += target.GetCombatReach(); + + if (minRange > 0.0f && !_spellInfo.RangeEntry.Flags.HasAnyFlag(SpellRangeFlag.Ranged)) + minRange += rangeMod; + } + + if (_caster.isMoving() && target.isMoving() && !_caster.IsWalking() && !target.IsWalking() && + (_spellInfo.RangeEntry.Flags.HasAnyFlag(SpellRangeFlag.Melee) || target.IsTypeId(TypeId.Player))) + rangeMod += 8.0f / 3.0f; + } + + maxRange += rangeMod; + + minRange *= minRange; + maxRange *= maxRange; + + if (target != _caster) + { + if (_caster.GetExactDistSq(target) > maxRange) + return false; + + if (minRange > 0.0f && _caster.GetExactDistSq(target) < minRange) + return false; + } + + return true; + } + + Unit _caster; + SpellInfo _spellInfo; + } + + // Very simple target selector, will just skip main target + // NOTE: When passing to UnitAI.SelectTarget remember to use 0 as position for random selection + // because tank will not be in the temporary list + public class NonTankTargetSelector : ISelector + { + public NonTankTargetSelector(Unit source, bool playerOnly = true) + { + _source = source; + _playerOnly = playerOnly; + } + + public bool Check(Unit target) + { + if (target == null) + return false; + + if (_playerOnly && !target.IsTypeId(TypeId.Player)) + return false; + + HostileReference currentVictim = _source.GetThreatManager().getCurrentVictim(); + if (currentVictim != null) + return target.GetGUID() != currentVictim.getUnitGuid(); + + return target != _source.GetVictim(); + } + + Unit _source; + bool _playerOnly; + } +} diff --git a/Game/AI/PlayerAI/PlayerAI.cs b/Game/AI/PlayerAI/PlayerAI.cs new file mode 100644 index 000000000..ee5137539 --- /dev/null +++ b/Game/AI/PlayerAI/PlayerAI.cs @@ -0,0 +1,1349 @@ +/* + * Copyright (C) 2012-2017 CypherCore + * + * 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, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +using Framework.Constants; +using Game.Entities; +using Game.Spells; +using System; +using System.Collections.Generic; + +namespace Game.AI +{ + public struct Spells + { + /* Generic */ + public const uint AutoShot = 75; + public const uint Shoot = 3018; + public const uint Throw = 2764; + public const uint Wand = 5019; + + /* Warrior - Generic */ + public const uint BattleStance = 2457; + public const uint BerserkerStance = 2458; + public const uint DefensiveStance = 71; + public const uint Charge = 11578; + public const uint Intercept = 20252; + public const uint EnragedRegen = 55694; + public const uint IntimidatingShout = 5246; + public const uint Pummel = 6552; + public const uint ShieldBash = 72; + public const uint Bloodrage = 2687; + + /* Warrior - Arms */ + public const uint SweepingStrikes = 12328; + public const uint MortalStrike = 12294; + public const uint Bladestorm = 46924; + public const uint Rend = 47465; + public const uint Retaliation = 20230; + public const uint ShatteringThrow = 64382; + public const uint ThunderClap = 47502; + + /* Warrior - Fury */ + public const uint DeathWish = 12292; + public const uint Bloodthirst = 23881; + public const uint PassiveTitansGrip = 46917; + public const uint DemoShout = 47437; + public const uint Execute = 47471; + public const uint HeroicFury = 60970; + public const uint Recklessness = 1719; + public const uint PiercingHowl = 12323; + + /* Warrior - Protection */ + public const uint Vigilance = 50720; + public const uint Devastate = 20243; + public const uint Shockwave = 46968; + public const uint ConcussionBlow = 12809; + public const uint Disarm = 676; + public const uint LastStand = 12975; + public const uint ShieldBlock = 2565; + public const uint ShieldSlam = 47488; + public const uint ShieldWall = 871; + public const uint Reflection = 23920; + + /* Paladin - Generic */ + public const uint PalAuraMastery = 31821; + public const uint LayOnHands = 48788; + public const uint BlessingOfMight = 48932; + public const uint AvengingWrath = 31884; + public const uint DivineProtection = 498; + public const uint DivineShield = 642; + public const uint HammerOfJustice = 10308; + public const uint HandOfFreedom = 1044; + public const uint HandOfProtection = 10278; + public const uint HandOfSacrifice = 6940; + + /* Paladin - Holy*/ + public const uint PassiveIllumination = 20215; + public const uint HolyShock = 20473; + public const uint BeaconOfLight = 53563; + public const uint Consecration = 48819; + public const uint FlashOfLight = 48785; + public const uint HolyLight = 48782; + public const uint DivineFavor = 20216; + public const uint DivineIllumination = 31842; + + /* Paladin - Protection */ + public const uint BlessOfSanc = 20911; + public const uint HolyShield = 20925; + public const uint AvengersShield = 48827; + public const uint DivineSacrifice = 64205; + public const uint HammerOfRighteous = 53595; + public const uint RighteousFury = 25780; + public const uint ShieldOfRighteous = 61411; + + /* Paladin - Retribution */ + public const uint SealOfCommand = 20375; + public const uint CrusaderStrike = 35395; + public const uint DivineStorm = 53385; + public const uint Judgement = 20271; + public const uint HammerOfWrath = 48806; + + /* Hunter - Generic */ + public const uint Deterrence = 19263; + public const uint ExplosiveTrap = 49067; + public const uint FreezingArrow = 60192; + public const uint RapidFire = 3045; + public const uint KillShot = 61006; + public const uint MultiShot = 49048; + public const uint ViperSting = 3034; + + /* Hunter - Beast Mastery */ + public const uint BestialWrath = 19574; + public const uint PassiveBeastWithin = 34692; + public const uint PassiveBeastMastery = 53270; + + /* Hunter - Marksmanship */ + public const uint AimedShot = 19434; + public const uint PassiveTrueshotAura = 19506; + public const uint ChimeraShot = 53209; + public const uint ArcaneShot = 49045; + public const uint SteadyShot = 49052; + public const uint Readiness = 23989; + public const uint SilencingShot = 34490; + + /* Hunter - Survival */ + public const uint PassiveLockAndLoad = 56344; + public const uint WyvernSting = 19386; + public const uint ExplosiveShot = 53301; + public const uint BlackArrow = 3674; + + /* Rogue - Generic */ + public const uint Dismantle = 51722; + public const uint Evasion = 26669; + public const uint Kick = 1766; + public const uint Vanish = 26889; + public const uint Blind = 2094; + public const uint CloakOfShadows = 31224; + + /* Rogue - Assassination */ + public const uint ColdBlood = 14177; + public const uint Mutilate = 1329; + public const uint HungerForBlood = 51662; + public const uint Envenom = 57993; + + /* Rogue - Combat */ + public const uint SinisterStrike = 48637; + public const uint BladeFlurry = 13877; + public const uint AdrenalineRush = 13750; + public const uint KillingSpree = 51690; + public const uint Eviscerate = 48668; + + /* Rogue - Sublety */ + public const uint Hemorrhage = 16511; + public const uint Premeditation = 14183; + public const uint ShadowDance = 51713; + public const uint Preparation = 14185; + public const uint Shadowstep = 36554; + + /* Priest - Generic */ + public const uint FearWard = 6346; + public const uint PowerWordFort = 48161; + public const uint DivineSpirit = 48073; + public const uint ShadowProtection = 48169; + public const uint DivineHymn = 64843; + public const uint HymnOfHope = 64901; + public const uint ShadowWordDeath = 48158; + public const uint PsychicScream = 10890; + + /* Priest - Discipline */ + public const uint PassiveSoulWarding = 63574; + public const uint PowerInfusion = 10060; + public const uint Penance = 47540; + public const uint PainSuppression = 33206; + public const uint InnerFocus = 14751; + public const uint PowerWordShield = 48066; + + /* Priest - Holy */ + public const uint PassiveSpiritRedemption = 20711; + public const uint DesperatePrayer = 19236; + public const uint GuardianSpirit = 47788; + public const uint FlashHeal = 48071; + public const uint Renew = 48068; + + /* Priest - Shadow */ + public const uint VampiricEmbrace = 15286; + public const uint Shadowform = 15473; + public const uint VampiricTouch = 34914; + public const uint MindFlay = 15407; + public const uint MindBlast = 48127; + public const uint ShadowWordPain = 48125; + public const uint DevouringPlague = 48300; + public const uint Dispersion = 47585; + + /* Death Knight - Generic */ + public const uint DeathGrip = 49576; + public const uint Strangulate = 47476; + public const uint EmpowerRuneWeap = 47568; + public const uint IcebornFortitude = 48792; + public const uint AntiMagicShell = 48707; + public const uint DeathCoilDk = 49895; + public const uint MindFreeze = 47528; + public const uint IcyTouch = 49909; + public const uint AuraFrostFever = 55095; + public const uint PlagueStrike = 49921; + public const uint AuraBloodPlague = 55078; + public const uint Pestilence = 50842; + + /* Death Knight - Blood */ + public const uint RuneTap = 48982; + public const uint Hysteria = 49016; + public const uint HeartStrike = 55050; + public const uint DeathStrike = 49924; + public const uint BloodStrike = 49930; + public const uint MarkOfBlood = 49005; + public const uint VampiricBlood = 55233; + + /* Death Knight - Frost */ + public const uint PassiveIcyTalons = 50887; + public const uint FrostStrike = 49143; + public const uint HowlingBlast = 49184; + public const uint UnbreakableArmor = 51271; + public const uint Obliterate = 51425; + public const uint Deathchill = 49796; + + /* Death Knight - Unholy */ + public const uint PassiveUnholyBlight = 49194; + public const uint PassiveMasterOfGhoul = 52143; + public const uint ScourgeStrike = 55090; + public const uint DeathAndDecay = 49938; + public const uint AntiMagicZone = 51052; + public const uint SummonGargoyle = 49206; + + /* Shaman - Generic */ + public const uint Heroism = 32182; + public const uint Bloodlust = 2825; + public const uint GroundingTotem = 8177; + + /* Shaman - Elemental*/ + public const uint PassiveElementalFocus = 16164; + public const uint TotemOfWrath = 30706; + public const uint Thunderstorm = 51490; + public const uint LightningBolt = 49238; + public const uint EarthShock = 49231; + public const uint FlameShock = 49233; + public const uint LavaBurst = 60043; + public const uint ChainLightning = 49271; + public const uint ElementalMastery = 16166; + + /* Shaman - Enhancement */ + public const uint PassiveSpiritWeapons = 16268; + public const uint LavaLash = 60103; + public const uint FeralSpirit = 51533; + public const uint AuraMaelstromWeapon = 53817; + public const uint Stormstrike = 17364; + public const uint ShamanisticRage = 30823; + + /* Shaman - Restoration*/ + public const uint ShaNatureSwift = 591; + public const uint ManaTideTotem = 590; + public const uint EarthShield = 49284; + public const uint Riptide = 61295; + public const uint HealingWave = 49273; + public const uint LesserHealWave = 49276; + public const uint TidalForce = 55198; + + /* Mage - Generic */ + public const uint DampenMagic = 43015; + public const uint Evocation = 12051; + public const uint ManaShield = 43020; + public const uint MirrorImage = 55342; + public const uint Spellsteal = 30449; + public const uint Counterspell = 2139; + public const uint IceBlock = 45438; + + /* Mage - Arcane */ + public const uint FocusMagic = 54646; + public const uint ArcanePower = 12042; + public const uint ArcaneBarrage = 44425; + public const uint ArcaneBlast = 42897; + public const uint AuraArcaneBlast = 36032; + public const uint ArcaneMissiles = 42846; + public const uint PresenceOfMind = 12043; + + /* Mage - Fire */ + public const uint Pyroblast = 11366; + public const uint Combustion = 11129; + public const uint LivingBomb = 44457; + public const uint Fireball = 42833; + public const uint FireBlast = 42873; + public const uint DragonsBreath = 31661; + public const uint BlastWave = 11113; + + /* Mage - Frost */ + public const uint IcyVeins = 12472; + public const uint IceBarrier = 11426; + public const uint DeepFreeze = 44572; + public const uint FrostNova = 42917; + public const uint Frostbolt = 42842; + public const uint ColdSnap = 11958; + public const uint IceLance = 42914; + + /* Warlock - Generic */ + public const uint Fear = 6215; + public const uint HowlOfTerror = 17928; + public const uint Corruption = 47813; + public const uint DeathCoilW = 47860; + public const uint ShadowBolt = 47809; + public const uint Incinerate = 47838; + public const uint Immolate = 47811; + public const uint SeedOfCorruption = 47836; + + /* Warlock - Affliction */ + public const uint PassiveSiphonLife = 63108; + public const uint UnstableAffliction = 30108; + public const uint Haunt = 48181; + public const uint CurseOfAgony = 47864; + public const uint DrainSoul = 47855; + + /* Warlock - Demonology */ + public const uint SoulLink = 19028; + public const uint DemonicEmpowerment = 47193; + public const uint Metamorphosis = 59672; + public const uint ImmolationAura = 50589; + public const uint DemonCharge = 54785; + public const uint AuraDecimation = 63167; + public const uint AuraMoltenCore = 71165; + public const uint SoulFire = 47825; + + /* Warlock - Destruction */ + public const uint Shadowburn = 17877; + public const uint Conflagrate = 17962; + public const uint ChaosBolt = 50796; + public const uint Shadowfury = 47847; + + /* Druid - Generic */ + public const uint Barkskin = 22812; + public const uint Innervate = 29166; + + /* Druid - Balance */ + public const uint InsectSwarm = 5570; + public const uint MoonkinForm = 24858; + public const uint Starfall = 48505; + public const uint Typhoon = 61384; + public const uint AuraEclipseLunar = 48518; + public const uint Moonfire = 48463; + public const uint Starfire = 48465; + public const uint Wrath = 48461; + + /* Druid - Feral */ + public const uint CatForm = 768; + public const uint SurvivalInstincts = 61336; + public const uint Mangle = 33917; + public const uint Berserk = 50334; + public const uint MangleCat = 48566; + public const uint FeralChargeCat = 49376; + public const uint Rake = 48574; + public const uint Rip = 49800; + public const uint SavageRoar = 52610; + public const uint TigerFury = 50213; + public const uint Claw = 48570; + public const uint Dash = 33357; + public const uint Maim = 49802; + + /* Druid - Restoration */ + public const uint Swiftmend = 18562; + public const uint TreeOfLife = 33891; + public const uint WildGrowth = 48438; + public const uint NatureSwiftness = 17116; + public const uint Tranquility = 48447; + public const uint Nourish = 50464; + public const uint HealingTouch = 48378; + public const uint Rejuvenation = 48441; + public const uint Regrowth = 48443; + public const uint Lifebloom = 48451; + } + + public class PlayerAI : UnitAI + { + public PlayerAI(Player player) : base(player) + { + me = player; + _selfSpec = player.GetUInt32Value(PlayerFields.CurrentSpecId); + _isSelfHealer = IsPlayerHealer(player); + _isSelfRangedAttacker = IsPlayerRangedAttacker(player); + } + + bool IsPlayerHealer(Player who) + { + if (!who) + return false; + + switch (who.GetClass()) + { + case Class.Warrior: + case Class.Hunter: + case Class.Rogue: + case Class.Deathknight: + case Class.Mage: + case Class.Warlock: + case Class.DemonHunter: + default: + return false; + case Class.Paladin: + return who.GetUInt32Value(PlayerFields.CurrentSpecId) == (uint)TalentSpecialization.PaladinHoly; + case Class.Priest: + return who.GetUInt32Value(PlayerFields.CurrentSpecId) == (uint)TalentSpecialization.PriestDiscipline || who.GetUInt32Value(PlayerFields.CurrentSpecId) == (uint)TalentSpecialization.PriestHoly; + case Class.Shaman: + return who.GetUInt32Value(PlayerFields.CurrentSpecId) == (uint)TalentSpecialization.ShamanRestoration; + case Class.Monk: + return who.GetUInt32Value(PlayerFields.CurrentSpecId) == (uint)TalentSpecialization.MonkMistweaver; + case Class.Druid: + return who.GetUInt32Value(PlayerFields.CurrentSpecId) == (uint)TalentSpecialization.DruidRestoration; + } + } + bool IsPlayerRangedAttacker(Player who) + { + if (!who) + return false; + + switch (who.GetClass()) + { + case Class.Warrior: + case Class.Paladin: + case Class.Rogue: + case Class.Deathknight: + default: + return false; + case Class.Mage: + case Class.Warlock: + return true; + case Class.Hunter: + { + // check if we have a ranged weapon equipped + Item rangedSlot = who.GetItemByPos(InventorySlots.Bag0, EquipmentSlot.Ranged); + + ItemTemplate rangedTemplate = rangedSlot ? rangedSlot.GetTemplate() : null; + if (rangedTemplate != null) + if (Convert.ToBoolean((1 << (int)rangedTemplate.GetSubClass()) & (int)ItemSubClassWeapon.MaskRanged)) + return true; + + return false; + } + case Class.Priest: + return who.GetUInt32Value(PlayerFields.CurrentSpecId) == (uint)TalentSpecialization.PriestShadow; + case Class.Shaman: + return who.GetUInt32Value(PlayerFields.CurrentSpecId) == (uint)TalentSpecialization.ShamanElemental; + case Class.Druid: + return who.GetUInt32Value(PlayerFields.CurrentSpecId) == (uint)TalentSpecialization.DruidBalance; + } + } + + Tuple VerifySpellCast(uint spellId, Unit target) + { + // Find highest spell rank that we know + uint knownRank, nextRank; + if (me.HasSpell(spellId)) + { + // this will save us some lookups if the player has the highest rank (expected case) + knownRank = spellId; + nextRank = Global.SpellMgr.GetNextSpellInChain(spellId); + } + else + { + knownRank = 0; + nextRank = Global.SpellMgr.GetFirstSpellInChain(spellId); + } + + while (nextRank != 0 && me.HasSpell(nextRank)) + { + knownRank = nextRank; + nextRank = Global.SpellMgr.GetNextSpellInChain(knownRank); + } + + if (knownRank == 0) + return null; + + SpellInfo spellInfo = Global.SpellMgr.GetSpellInfo(knownRank); + if (spellInfo == null) + return null; + + if (me.GetSpellHistory().HasGlobalCooldown(spellInfo)) + return null; + + Spell spell = new Spell(me, spellInfo, TriggerCastFlags.None); + if (spell.CanAutoCast(target)) + return Tuple.Create(spell, target); + + return null; + } + + Tuple VerifySpellCast(uint spellId, SpellTarget target) + { + Unit pTarget = null; + switch (target) + { + case SpellTarget.None: + break; + case SpellTarget.Victim: + pTarget = me.GetVictim(); + if (!pTarget) + return null; + break; + case SpellTarget.Charmer: + pTarget = me.GetCharmer(); + if (!pTarget) + return null; + break; + case SpellTarget.Self: + pTarget = me; + break; + } + + return VerifySpellCast(spellId, pTarget); + } + + public Tuple SelectSpellCast(List, uint>> spells) + { + if (spells.Empty()) + return null; + + uint totalWeights = 0; + foreach (var wSpell in spells) + totalWeights += wSpell.Item2; + + Tuple selected = null; + uint randNum = RandomHelper.URand(0, totalWeights - 1); + foreach (var wSpell in spells) + { + if (selected != null) + { + //delete wSpell.first.first; + continue; + } + + if (randNum < wSpell.Item2) + selected = wSpell.Item1; + else + { + randNum -= wSpell.Item2; + //delete wSpell.first.first; + } + } + + spells.Clear(); + return selected; + } + + void VerifyAndPushSpellCast(List, uint>> spells, uint spellId, T target, uint weight) where T : Unit + { + Tuple spell = VerifySpellCast(spellId, target); + if (spell != null) + spells.Add(Tuple.Create(spell, weight)); + } + + public void DoCastAtTarget(Tuple spell) + { + SpellCastTargets targets = new SpellCastTargets(); + targets.SetUnitTarget(spell.Item2); + spell.Item1.prepare(targets); + } + + void DoRangedAttackIfReady() + { + if (me.HasUnitState(UnitState.Casting)) + return; + + if (!me.isAttackReady(WeaponAttackType.RangedAttack)) + return; + + Unit victim = me.GetVictim(); + if (!victim) + return; + + uint rangedAttackSpell = 0; + + Item rangedItem = me.GetItemByPos(InventorySlots.Bag0, EquipmentSlot.Ranged); + ItemTemplate rangedTemplate = rangedItem ? rangedItem.GetTemplate() : null; + if (rangedTemplate != null) + { + switch ((ItemSubClassWeapon)rangedTemplate.GetSubClass()) + { + case ItemSubClassWeapon.Bow: + case ItemSubClassWeapon.Gun: + case ItemSubClassWeapon.Crossbow: + rangedAttackSpell = Spells.Shoot; + break; + case ItemSubClassWeapon.Thrown: + rangedAttackSpell = Spells.Throw; + break; + case ItemSubClassWeapon.Wand: + rangedAttackSpell = Spells.Wand; + break; + } + } + + if (rangedAttackSpell == 0) + return; + + me.CastSpell(victim, rangedAttackSpell, TriggerCastFlags.CastDirectly); + me.resetAttackTimer(WeaponAttackType.RangedAttack); + } + + public void DoAutoAttackIfReady() + { + if (IsRangedAttacker()) + DoRangedAttackIfReady(); + else + DoMeleeAttackIfReady(); + } + + void CancelAllShapeshifts() + { + List shapeshiftAuras = me.GetAuraEffectsByType(AuraType.ModShapeshift); + List removableShapeshifts = new List(); + foreach (AuraEffect auraEff in shapeshiftAuras) + { + Aura aura = auraEff.GetBase(); + if (aura == null) + continue; + SpellInfo auraInfo = aura.GetSpellInfo(); + if (auraInfo == null) + continue; + if (auraInfo.HasAttribute(SpellAttr0.CantCancel)) + continue; + if (!auraInfo.IsPositive() || auraInfo.IsPassive()) + continue; + removableShapeshifts.Add(aura); + } + + foreach (Aura aura in removableShapeshifts) + me.RemoveOwnedAura(aura, AuraRemoveMode.Cancel); + } + + public Creature GetCharmer() + { + if (me.GetCharmerGUID().IsCreature()) + return ObjectAccessor.GetCreature(me, me.GetCharmerGUID()); + return null; + } + + public override void OnCharmed(bool apply) { } + + // helper functions to determine player info + bool IsHealer(Player who = null) + { + return (!who || who == me) ? _isSelfHealer : IsPlayerHealer(who); + } + public bool IsRangedAttacker(Player who = null) { return (!who || who == me) ? _isSelfRangedAttacker : IsPlayerRangedAttacker(who); } + uint GetSpec(Player who = null) { return (!who || who == me) ? _selfSpec : who.GetUInt32Value(PlayerFields.CurrentSpecId); } + void SetIsRangedAttacker(bool state) { _isSelfRangedAttacker = state; } // this allows overriding of the default ranged attacker detection + + public virtual Unit SelectAttackTarget() { return me.GetCharmer() ? me.GetCharmer().GetVictim() : null; } + + protected new Player me; + uint _selfSpec; + bool _isSelfHealer; + bool _isSelfRangedAttacker; + + enum SpellTarget + { + None, + Victim, + Charmer, + Self + } + } + + class SimpleCharmedPlayerAI : PlayerAI + { + public SimpleCharmedPlayerAI(Player player) : base(player) + { + _castCheckTimer = 500; + _chaseCloser = false; + _forceFacing = true; + } + + public override Unit SelectAttackTarget() + { + Unit charmer = me.GetCharmer(); + if (charmer) + return charmer.IsAIEnabled ? charmer.GetAI().SelectTarget(SelectAggroTarget.Random, 0, new UncontrolledTargetSelectPredicate()) : charmer.GetVictim(); + return null; + } + + Tuple SelectAppropriateCastForSpec() + { + List, uint>> spells = new List, uint>>(); + /* + switch (me.getClass()) + { + case CLASS_WARRIOR: + if (!me.IsWithinMeleeRange(me.GetVictim())) + { + VerifyAndPushSpellCast(spells, SPELL_CHARGE, TARGET_VICTIM, 15); + VerifyAndPushSpellCast(spells, SPELL_INTERCEPT, TARGET_VICTIM, 10); + } + VerifyAndPushSpellCast(spells, SPELL_ENRAGED_REGEN, TARGET_NONE, 3); + VerifyAndPushSpellCast(spells, SPELL_INTIMIDATING_SHOUT, TARGET_VICTIM, 4); + if (me.GetVictim() && me.GetVictim().HasUnitState(UNIT_STATE_CASTING)) + { + VerifyAndPushSpellCast(spells, SPELL_PUMMEL, TARGET_VICTIM, 15); + VerifyAndPushSpellCast(spells, SPELL_SHIELD_BASH, TARGET_VICTIM, 15); + } + VerifyAndPushSpellCast(spells, SPELL_BLOODRAGE, TARGET_NONE, 5); + switch (GetSpec()) + { + case TALENT_SPEC_WARRIOR_PROTECTION: + VerifyAndPushSpellCast(spells, SPELL_SHOCKWAVE, TARGET_VICTIM, 3); + VerifyAndPushSpellCast(spells, SPELL_CONCUSSION_BLOW, TARGET_VICTIM, 5); + VerifyAndPushSpellCast(spells, SPELL_DISARM, TARGET_VICTIM, 2); + VerifyAndPushSpellCast(spells, SPELL_LAST_STAND, TARGET_NONE, 5); + VerifyAndPushSpellCast(spells, SPELL_SHIELD_BLOCK, TARGET_NONE, 1); + VerifyAndPushSpellCast(spells, SPELL_SHIELD_SLAM, TARGET_VICTIM, 4); + VerifyAndPushSpellCast(spells, SPELL_SHIELD_WALL, TARGET_NONE, 5); + VerifyAndPushSpellCast(spells, SPELL_SPELL_REFLECTION, TARGET_NONE, 3); + VerifyAndPushSpellCast(spells, SPELL_DEVASTATE, TARGET_VICTIM, 2); + VerifyAndPushSpellCast(spells, SPELL_REND, TARGET_VICTIM, 1); + VerifyAndPushSpellCast(spells, SPELL_THUNDER_CLAP, TARGET_VICTIM, 2); + VerifyAndPushSpellCast(spells, SPELL_DEMO_SHOUT, TARGET_VICTIM, 1); + break; + case TALENT_SPEC_WARRIOR_ARMS: + VerifyAndPushSpellCast(spells, SPELL_SWEEPING_STRIKES, TARGET_NONE, 2); + VerifyAndPushSpellCast(spells, SPELL_MORTAL_STRIKE, TARGET_VICTIM, 5); + VerifyAndPushSpellCast(spells, SPELL_BLADESTORM, TARGET_NONE, 10); + VerifyAndPushSpellCast(spells, SPELL_REND, TARGET_VICTIM, 1); + VerifyAndPushSpellCast(spells, SPELL_RETALIATION, TARGET_NONE, 3); + VerifyAndPushSpellCast(spells, SPELL_SHATTERING_THROW, TARGET_VICTIM, 3); + VerifyAndPushSpellCast(spells, SPELL_SWEEPING_STRIKES, TARGET_NONE, 5); + VerifyAndPushSpellCast(spells, SPELL_THUNDER_CLAP, TARGET_VICTIM, 1); + VerifyAndPushSpellCast(spells, SPELL_EXECUTE, TARGET_VICTIM, 15); + break; + case TALENT_SPEC_WARRIOR_FURY: + VerifyAndPushSpellCast(spells, SPELL_DEATH_WISH, TARGET_NONE, 10); + VerifyAndPushSpellCast(spells, SPELL_BLOODTHIRST, TARGET_VICTIM, 4); + VerifyAndPushSpellCast(spells, SPELL_DEMO_SHOUT, TARGET_VICTIM, 2); + VerifyAndPushSpellCast(spells, SPELL_EXECUTE, TARGET_VICTIM, 15); + VerifyAndPushSpellCast(spells, SPELL_HEROIC_FURY, TARGET_NONE, 5); + VerifyAndPushSpellCast(spells, SPELL_RECKLESSNESS, TARGET_NONE, 8); + VerifyAndPushSpellCast(spells, SPELL_PIERCING_HOWL, TARGET_VICTIM, 2); + break; + } + break; + case CLASS_PALADIN: + VerifyAndPushSpellCast(spells, SPELL_AURA_MASTERY, TARGET_NONE, 3); + VerifyAndPushSpellCast(spells, SPELL_LAY_ON_HANDS, TARGET_CHARMER, 8); + VerifyAndPushSpellCast(spells, SPELL_BLESSING_OF_MIGHT, TARGET_CHARMER, 8); + VerifyAndPushSpellCast(spells, SPELL_AVENGING_WRATH, TARGET_NONE, 5); + VerifyAndPushSpellCast(spells, SPELL_DIVINE_PROTECTION, TARGET_NONE, 4); + VerifyAndPushSpellCast(spells, SPELL_DIVINE_SHIELD, TARGET_NONE, 2); + VerifyAndPushSpellCast(spells, SPELL_HAMMER_OF_JUSTICE, TARGET_VICTIM, 6); + VerifyAndPushSpellCast(spells, SPELL_HAND_OF_FREEDOM, TARGET_SELF, 3); + VerifyAndPushSpellCast(spells, SPELL_HAND_OF_PROTECTION, TARGET_SELF, 1); + if (Creature* creatureCharmer = GetCharmer()) + { + if (creatureCharmer.IsDungeonBoss() || creatureCharmer.isWorldBoss()) + VerifyAndPushSpellCast(spells, SPELL_HAND_OF_SACRIFICE, creatureCharmer, 10); + else + VerifyAndPushSpellCast(spells, SPELL_HAND_OF_PROTECTION, creatureCharmer, 3); + } + + switch (GetSpec()) + { + case TALENT_SPEC_PALADIN_PROTECTION: + VerifyAndPushSpellCast(spells, SPELL_HAMMER_OF_RIGHTEOUS, TARGET_VICTIM, 3); + VerifyAndPushSpellCast(spells, SPELL_DIVINE_SACRIFICE, TARGET_NONE, 2); + VerifyAndPushSpellCast(spells, SPELL_SHIELD_OF_RIGHTEOUS, TARGET_VICTIM, 4); + VerifyAndPushSpellCast(spells, SPELL_JUDGEMENT, TARGET_VICTIM, 2); + VerifyAndPushSpellCast(spells, SPELL_CONSECRATION, TARGET_VICTIM, 2); + VerifyAndPushSpellCast(spells, SPELL_HOLY_SHIELD, TARGET_NONE, 1); + break; + case TALENT_SPEC_PALADIN_HOLY: + VerifyAndPushSpellCast(spells, SPELL_HOLY_SHOCK, TARGET_CHARMER, 3); + VerifyAndPushSpellCast(spells, SPELL_HOLY_SHOCK, TARGET_VICTIM, 1); + VerifyAndPushSpellCast(spells, SPELL_FLASH_OF_LIGHT, TARGET_CHARMER, 4); + VerifyAndPushSpellCast(spells, SPELL_HOLY_LIGHT, TARGET_CHARMER, 3); + VerifyAndPushSpellCast(spells, SPELL_DIVINE_FAVOR, TARGET_NONE, 5); + VerifyAndPushSpellCast(spells, SPELL_DIVINE_ILLUMINATION, TARGET_NONE, 3); + break; + case TALENT_SPEC_PALADIN_RETRIBUTION: + VerifyAndPushSpellCast(spells, SPELL_CRUSADER_STRIKE, TARGET_VICTIM, 4); + VerifyAndPushSpellCast(spells, SPELL_DIVINE_STORM, TARGET_VICTIM, 5); + VerifyAndPushSpellCast(spells, SPELL_JUDGEMENT, TARGET_VICTIM, 3); + VerifyAndPushSpellCast(spells, SPELL_HAMMER_OF_WRATH, TARGET_VICTIM, 5); + VerifyAndPushSpellCast(spells, SPELL_RIGHTEOUS_FURY, TARGET_NONE, 2); + break; + } + break; + case CLASS_HUNTER: + VerifyAndPushSpellCast(spells, SPELL_DETERRENCE, TARGET_NONE, 3); + VerifyAndPushSpellCast(spells, SPELL_EXPLOSIVE_TRAP, TARGET_NONE, 1); + VerifyAndPushSpellCast(spells, SPELL_FREEZING_ARROW, TARGET_VICTIM, 2); + VerifyAndPushSpellCast(spells, SPELL_RAPID_FIRE, TARGET_NONE, 10); + VerifyAndPushSpellCast(spells, SPELL_KILL_SHOT, TARGET_VICTIM, 10); + if (me.GetVictim() && me.GetVictim().getPowerType() == POWER_MANA && !me.GetVictim().GetAuraApplicationOfRankedSpell(SPELL_VIPER_STING, me.GetGUID())) + VerifyAndPushSpellCast(spells, SPELL_VIPER_STING, TARGET_VICTIM, 5); + + switch (GetSpec()) + { + case TALENT_SPEC_HUNTER_BEASTMASTER: + VerifyAndPushSpellCast(spells, SPELL_AIMED_SHOT, TARGET_VICTIM, 2); + VerifyAndPushSpellCast(spells, SPELL_ARCANE_SHOT, TARGET_VICTIM, 3); + VerifyAndPushSpellCast(spells, SPELL_STEADY_SHOT, TARGET_VICTIM, 2); + VerifyAndPushSpellCast(spells, SPELL_MULTI_SHOT, TARGET_VICTIM, 2); + break; + case TALENT_SPEC_HUNTER_MARKSMAN: + VerifyAndPushSpellCast(spells, SPELL_AIMED_SHOT, TARGET_VICTIM, 2); + VerifyAndPushSpellCast(spells, SPELL_CHIMERA_SHOT, TARGET_VICTIM, 5); + VerifyAndPushSpellCast(spells, SPELL_ARCANE_SHOT, TARGET_VICTIM, 3); + VerifyAndPushSpellCast(spells, SPELL_STEADY_SHOT, TARGET_VICTIM, 2); + VerifyAndPushSpellCast(spells, SPELL_READINESS, TARGET_NONE, 10); + VerifyAndPushSpellCast(spells, SPELL_SILENCING_SHOT, TARGET_VICTIM, 5); + break; + case TALENT_SPEC_HUNTER_SURVIVAL: + VerifyAndPushSpellCast(spells, SPELL_EXPLOSIVE_SHOT, TARGET_VICTIM, 8); + VerifyAndPushSpellCast(spells, SPELL_BLACK_ARROW, TARGET_VICTIM, 5); + VerifyAndPushSpellCast(spells, SPELL_MULTI_SHOT, TARGET_VICTIM, 3); + VerifyAndPushSpellCast(spells, SPELL_STEADY_SHOT, TARGET_VICTIM, 1); + break; + } + break; + case CLASS_ROGUE: + { + VerifyAndPushSpellCast(spells, SPELL_DISMANTLE, TARGET_VICTIM, 8); + VerifyAndPushSpellCast(spells, SPELL_EVASION, TARGET_NONE, 8); + VerifyAndPushSpellCast(spells, SPELL_VANISH, TARGET_NONE, 4); + VerifyAndPushSpellCast(spells, SPELL_BLIND, TARGET_VICTIM, 2); + VerifyAndPushSpellCast(spells, SPELL_CLOAK_OF_SHADOWS, TARGET_NONE, 2); + + uint32 builder, finisher; + switch (GetSpec()) + { + case TALENT_SPEC_ROGUE_ASSASSINATION: + builder = SPELL_MUTILATE, finisher = SPELL_ENVENOM; + VerifyAndPushSpellCast(spells, SPELL_COLD_BLOOD, TARGET_NONE, 20); + break; + case TALENT_SPEC_ROGUE_COMBAT: + builder = SPELL_SINISTER_STRIKE, finisher = SPELL_EVISCERATE; + VerifyAndPushSpellCast(spells, SPELL_ADRENALINE_RUSH, TARGET_NONE, 6); + VerifyAndPushSpellCast(spells, SPELL_BLADE_FLURRY, TARGET_NONE, 5); + VerifyAndPushSpellCast(spells, SPELL_KILLING_SPREE, TARGET_NONE, 25); + break; + case TALENT_SPEC_ROGUE_SUBTLETY: + builder = SPELL_HEMORRHAGE, finisher = SPELL_EVISCERATE; + VerifyAndPushSpellCast(spells, SPELL_PREPARATION, TARGET_NONE, 10); + if (!me.IsWithinMeleeRange(me.GetVictim())) + VerifyAndPushSpellCast(spells, SPELL_SHADOWSTEP, TARGET_VICTIM, 25); + VerifyAndPushSpellCast(spells, SPELL_SHADOW_DANCE, TARGET_NONE, 10); + break; + } + + if (Unit* victim = me.GetVictim()) + { + if (victim.HasUnitState(UNIT_STATE_CASTING)) + VerifyAndPushSpellCast(spells, SPELL_KICK, TARGET_VICTIM, 25); + + uint8 const cp = me.GetPower(POWER_COMBO_POINTS); + if (cp >= 4) + VerifyAndPushSpellCast(spells, finisher, TARGET_VICTIM, 10); + if (cp <= 4) + VerifyAndPushSpellCast(spells, builder, TARGET_VICTIM, 5); + } + break; + } + case CLASS_PRIEST: + VerifyAndPushSpellCast(spells, SPELL_FEAR_WARD, TARGET_SELF, 2); + VerifyAndPushSpellCast(spells, SPELL_POWER_WORD_FORT, TARGET_CHARMER, 1); + VerifyAndPushSpellCast(spells, SPELL_DIVINE_SPIRIT, TARGET_CHARMER, 1); + VerifyAndPushSpellCast(spells, SPELL_SHADOW_PROTECTION, TARGET_CHARMER, 2); + VerifyAndPushSpellCast(spells, SPELL_DIVINE_HYMN, TARGET_NONE, 5); + VerifyAndPushSpellCast(spells, SPELL_HYMN_OF_HOPE, TARGET_NONE, 5); + VerifyAndPushSpellCast(spells, SPELL_SHADOW_WORD_DEATH, TARGET_VICTIM, 1); + VerifyAndPushSpellCast(spells, SPELL_PSYCHIC_SCREAM, TARGET_VICTIM, 3); + switch (GetSpec()) + { + case TALENT_SPEC_PRIEST_DISCIPLINE: + VerifyAndPushSpellCast(spells, SPELL_POWER_WORD_SHIELD, TARGET_CHARMER, 3); + VerifyAndPushSpellCast(spells, SPELL_INNER_FOCUS, TARGET_NONE, 3); + VerifyAndPushSpellCast(spells, SPELL_PAIN_SUPPRESSION, TARGET_CHARMER, 15); + VerifyAndPushSpellCast(spells, SPELL_POWER_INFUSION, TARGET_CHARMER, 10); + VerifyAndPushSpellCast(spells, SPELL_PENANCE, TARGET_CHARMER, 3); + VerifyAndPushSpellCast(spells, SPELL_FLASH_HEAL, TARGET_CHARMER, 1); + break; + case TALENT_SPEC_PRIEST_HOLY: + VerifyAndPushSpellCast(spells, SPELL_DESPERATE_PRAYER, TARGET_NONE, 3); + VerifyAndPushSpellCast(spells, SPELL_GUARDIAN_SPIRIT, TARGET_CHARMER, 5); + VerifyAndPushSpellCast(spells, SPELL_FLASH_HEAL, TARGET_CHARMER, 1); + VerifyAndPushSpellCast(spells, SPELL_RENEW, TARGET_CHARMER, 3); + break; + case TALENT_SPEC_PRIEST_SHADOW: + if (!me.HasAura(SPELL_SHADOWFORM)) + { + VerifyAndPushSpellCast(spells, SPELL_SHADOWFORM, TARGET_NONE, 100); + break; + } + if (Unit* victim = me.GetVictim()) + { + if (!victim.GetAuraApplicationOfRankedSpell(SPELL_VAMPIRIC_TOUCH, me.GetGUID())) + VerifyAndPushSpellCast(spells, SPELL_VAMPIRIC_TOUCH, TARGET_VICTIM, 4); + if (!victim.GetAuraApplicationOfRankedSpell(SPELL_SHADOW_WORD_PAIN, me.GetGUID())) + VerifyAndPushSpellCast(spells, SPELL_SHADOW_WORD_PAIN, TARGET_VICTIM, 3); + if (!victim.GetAuraApplicationOfRankedSpell(SPELL_DEVOURING_PLAGUE, me.GetGUID())) + VerifyAndPushSpellCast(spells, SPELL_DEVOURING_PLAGUE, TARGET_VICTIM, 4); + } + VerifyAndPushSpellCast(spells, SPELL_MIND_BLAST, TARGET_VICTIM, 3); + VerifyAndPushSpellCast(spells, SPELL_MIND_FLAY, TARGET_VICTIM, 2); + VerifyAndPushSpellCast(spells, SPELL_DISPERSION, TARGET_NONE, 10); + break; + } + break; + case CLASS_DEATH_KNIGHT: + { + if (!me.IsWithinMeleeRange(me.GetVictim())) + VerifyAndPushSpellCast(spells, SPELL_DEATH_GRIP, TARGET_VICTIM, 25); + VerifyAndPushSpellCast(spells, SPELL_STRANGULATE, TARGET_VICTIM, 15); + VerifyAndPushSpellCast(spells, SPELL_EMPOWER_RUNE_WEAP, TARGET_NONE, 5); + VerifyAndPushSpellCast(spells, SPELL_ICEBORN_FORTITUDE, TARGET_NONE, 15); + VerifyAndPushSpellCast(spells, SPELL_ANTI_MAGIC_SHELL, TARGET_NONE, 10); + + bool hasFF = false, hasBP = false; + if (Unit* victim = me.GetVictim()) + { + if (victim.HasUnitState(UNIT_STATE_CASTING)) + VerifyAndPushSpellCast(spells, SPELL_MIND_FREEZE, TARGET_VICTIM, 25); + + hasFF = !!victim.GetAuraApplicationOfRankedSpell(AURA_FROST_FEVER, me.GetGUID()), hasBP = !!victim.GetAuraApplicationOfRankedSpell(AURA_BLOOD_PLAGUE, me.GetGUID()); + if (hasFF && hasBP) + VerifyAndPushSpellCast(spells, SPELL_PESTILENCE, TARGET_VICTIM, 3); + if (!hasFF) + VerifyAndPushSpellCast(spells, SPELL_ICY_TOUCH, TARGET_VICTIM, 4); + if (!hasBP) + VerifyAndPushSpellCast(spells, SPELL_PLAGUE_STRIKE, TARGET_VICTIM, 4); + } + switch (GetSpec()) + { + case TALENT_SPEC_DEATHKNIGHT_BLOOD: + VerifyAndPushSpellCast(spells, SPELL_RUNE_TAP, TARGET_NONE, 2); + VerifyAndPushSpellCast(spells, SPELL_HYSTERIA, TARGET_SELF, 5); + if (Creature* creatureCharmer = GetCharmer()) + if (!creatureCharmer.IsDungeonBoss() && !creatureCharmer.isWorldBoss()) + VerifyAndPushSpellCast(spells, SPELL_HYSTERIA, creatureCharmer, 15); + VerifyAndPushSpellCast(spells, SPELL_HEART_STRIKE, TARGET_VICTIM, 2); + if (hasFF && hasBP) + VerifyAndPushSpellCast(spells, SPELL_DEATH_STRIKE, TARGET_VICTIM, 8); + VerifyAndPushSpellCast(spells, SPELL_DEATH_COIL_DK, TARGET_VICTIM, 1); + VerifyAndPushSpellCast(spells, SPELL_MARK_OF_BLOOD, TARGET_VICTIM, 20); + VerifyAndPushSpellCast(spells, SPELL_VAMPIRIC_BLOOD, TARGET_NONE, 10); + break; + case TALENT_SPEC_DEATHKNIGHT_FROST: + if (hasFF && hasBP) + VerifyAndPushSpellCast(spells, SPELL_OBLITERATE, TARGET_VICTIM, 5); + VerifyAndPushSpellCast(spells, SPELL_HOWLING_BLAST, TARGET_VICTIM, 2); + VerifyAndPushSpellCast(spells, SPELL_UNBREAKABLE_ARMOR, TARGET_NONE, 10); + VerifyAndPushSpellCast(spells, SPELL_DEATHCHILL, TARGET_NONE, 10); + VerifyAndPushSpellCast(spells, SPELL_FROST_STRIKE, TARGET_VICTIM, 3); + VerifyAndPushSpellCast(spells, SPELL_BLOOD_STRIKE, TARGET_VICTIM, 1); + break; + case TALENT_SPEC_DEATHKNIGHT_UNHOLY: + if (hasFF && hasBP) + VerifyAndPushSpellCast(spells, SPELL_SCOURGE_STRIKE, TARGET_VICTIM, 5); + VerifyAndPushSpellCast(spells, SPELL_DEATH_AND_DECAY, TARGET_VICTIM, 2); + VerifyAndPushSpellCast(spells, SPELL_ANTI_MAGIC_ZONE, TARGET_NONE, 8); + VerifyAndPushSpellCast(spells, SPELL_SUMMON_GARGOYLE, TARGET_VICTIM, 7); + VerifyAndPushSpellCast(spells, SPELL_BLOOD_STRIKE, TARGET_VICTIM, 1); + VerifyAndPushSpellCast(spells, SPELL_DEATH_COIL_DK, TARGET_VICTIM, 3); + break; + } + break; + } + case CLASS_SHAMAN: + VerifyAndPushSpellCast(spells, SPELL_HEROISM, TARGET_NONE, 25); + VerifyAndPushSpellCast(spells, SPELL_BLOODLUST, TARGET_NONE, 25); + VerifyAndPushSpellCast(spells, SPELL_GROUNDING_TOTEM, TARGET_NONE, 2); + switch (GetSpec()) + { + case TALENT_SPEC_SHAMAN_RESTORATION: + if (Unit* charmer = me.GetCharmer()) + if (!charmer.GetAuraApplicationOfRankedSpell(SPELL_EARTH_SHIELD, me.GetGUID())) + VerifyAndPushSpellCast(spells, SPELL_EARTH_SHIELD, charmer, 2); + if (me.HasAura(SPELL_SHA_NATURE_SWIFT)) + VerifyAndPushSpellCast(spells, SPELL_HEALING_WAVE, TARGET_CHARMER, 20); + else + VerifyAndPushSpellCast(spells, SPELL_LESSER_HEAL_WAVE, TARGET_CHARMER, 1); + VerifyAndPushSpellCast(spells, SPELL_TIDAL_FORCE, TARGET_NONE, 4); + VerifyAndPushSpellCast(spells, SPELL_SHA_NATURE_SWIFT, TARGET_NONE, 4); + VerifyAndPushSpellCast(spells, SPELL_MANA_TIDE_TOTEM, TARGET_NONE, 3); + break; + case TALENT_SPEC_SHAMAN_ELEMENTAL: + if (Unit* victim = me.GetVictim()) + { + if (victim.GetAuraOfRankedSpell(SPELL_FLAME_SHOCK, GetGUID())) + VerifyAndPushSpellCast(spells, SPELL_LAVA_BURST, TARGET_VICTIM, 5); + else + VerifyAndPushSpellCast(spells, SPELL_FLAME_SHOCK, TARGET_VICTIM, 3); + } + VerifyAndPushSpellCast(spells, SPELL_CHAIN_LIGHTNING, TARGET_VICTIM, 2); + VerifyAndPushSpellCast(spells, SPELL_LIGHTNING_BOLT, TARGET_VICTIM, 1); + VerifyAndPushSpellCast(spells, SPELL_ELEMENTAL_MASTERY, TARGET_VICTIM, 5); + VerifyAndPushSpellCast(spells, SPELL_THUNDERSTORM, TARGET_NONE, 3); + break; + case TALENT_SPEC_SHAMAN_ENHANCEMENT: + if (Aura const* maelstrom = me.GetAura(AURA_MAELSTROM_WEAPON)) + if (maelstrom.GetStackAmount() == 5) + VerifyAndPushSpellCast(spells, SPELL_LIGHTNING_BOLT, TARGET_VICTIM, 5); + VerifyAndPushSpellCast(spells, SPELL_STORMSTRIKE, TARGET_VICTIM, 3); + VerifyAndPushSpellCast(spells, SPELL_EARTH_SHOCK, TARGET_VICTIM, 2); + VerifyAndPushSpellCast(spells, SPELL_LAVA_LASH, TARGET_VICTIM, 1); + VerifyAndPushSpellCast(spells, SPELL_SHAMANISTIC_RAGE, TARGET_NONE, 10); + break; + } + break; + case CLASS_MAGE: + if (me.GetVictim() && me.GetVictim().HasUnitState(UNIT_STATE_CASTING)) + VerifyAndPushSpellCast(spells, SPELL_COUNTERSPELL, TARGET_VICTIM, 25); + VerifyAndPushSpellCast(spells, SPELL_DAMPEN_MAGIC, TARGET_CHARMER, 2); + VerifyAndPushSpellCast(spells, SPELL_EVOCATION, TARGET_NONE, 3); + VerifyAndPushSpellCast(spells, SPELL_MANA_SHIELD, TARGET_NONE, 1); + VerifyAndPushSpellCast(spells, SPELL_MIRROR_IMAGE, TARGET_NONE, 3); + VerifyAndPushSpellCast(spells, SPELL_SPELLSTEAL, TARGET_VICTIM, 2); + VerifyAndPushSpellCast(spells, SPELL_ICE_BLOCK, TARGET_NONE, 1); + VerifyAndPushSpellCast(spells, SPELL_ICY_VEINS, TARGET_NONE, 3); + switch (GetSpec()) + { + case TALENT_SPEC_MAGE_ARCANE: + if (Aura* abAura = me.GetAura(AURA_ARCANE_BLAST)) + if (abAura.GetStackAmount() >= 3) + VerifyAndPushSpellCast(spells, SPELL_ARCANE_MISSILES, TARGET_VICTIM, 7); + VerifyAndPushSpellCast(spells, SPELL_ARCANE_BLAST, TARGET_VICTIM, 5); + VerifyAndPushSpellCast(spells, SPELL_ARCANE_BARRAGE, TARGET_VICTIM, 1); + VerifyAndPushSpellCast(spells, SPELL_ARCANE_POWER, TARGET_NONE, 8); + VerifyAndPushSpellCast(spells, SPELL_PRESENCE_OF_MIND, TARGET_NONE, 7); + break; + case TALENT_SPEC_MAGE_FIRE: + if (me.GetVictim() && !me.GetVictim().GetAuraApplicationOfRankedSpell(SPELL_LIVING_BOMB)) + VerifyAndPushSpellCast(spells, SPELL_LIVING_BOMB, TARGET_VICTIM, 3); + VerifyAndPushSpellCast(spells, SPELL_COMBUSTION, TARGET_VICTIM, 3); + VerifyAndPushSpellCast(spells, SPELL_FIREBALL, TARGET_VICTIM, 2); + VerifyAndPushSpellCast(spells, SPELL_FIRE_BLAST, TARGET_VICTIM, 1); + VerifyAndPushSpellCast(spells, SPELL_DRAGONS_BREATH, TARGET_VICTIM, 2); + VerifyAndPushSpellCast(spells, SPELL_BLAST_WAVE, TARGET_VICTIM, 1); + break; + case TALENT_SPEC_MAGE_FROST: + VerifyAndPushSpellCast(spells, SPELL_DEEP_FREEZE, TARGET_VICTIM, 10); + VerifyAndPushSpellCast(spells, SPELL_FROST_NOVA, TARGET_VICTIM, 3); + VerifyAndPushSpellCast(spells, SPELL_FROSTBOLT, TARGET_VICTIM, 3); + VerifyAndPushSpellCast(spells, SPELL_COLD_SNAP, TARGET_VICTIM, 5); + if (me.GetVictim() && me.GetVictim().HasAuraState(AURA_STATE_FROZEN, nullptr, me)) + VerifyAndPushSpellCast(spells, SPELL_ICE_LANCE, TARGET_VICTIM, 5); + break; + } + break; + case CLASS_WARLOCK: + VerifyAndPushSpellCast(spells, SPELL_DEATH_COIL_W, TARGET_VICTIM, 2); + VerifyAndPushSpellCast(spells, SPELL_FEAR, TARGET_VICTIM, 2); + VerifyAndPushSpellCast(spells, SPELL_SEED_OF_CORRUPTION, TARGET_VICTIM, 4); + VerifyAndPushSpellCast(spells, SPELL_HOWL_OF_TERROR, TARGET_NONE, 2); + if (me.GetVictim() && !me.GetVictim().GetAuraApplicationOfRankedSpell(SPELL_CORRUPTION, me.GetGUID())) + VerifyAndPushSpellCast(spells, SPELL_CORRUPTION, TARGET_VICTIM, 10); + switch (GetSpec()) + { + case TALENT_SPEC_WARLOCK_AFFLICTION: + if (Unit* victim = me.GetVictim()) + { + VerifyAndPushSpellCast(spells, SPELL_SHADOW_BOLT, TARGET_VICTIM, 7); + if (!victim.GetAuraApplicationOfRankedSpell(SPELL_UNSTABLE_AFFLICTION, me.GetGUID())) + VerifyAndPushSpellCast(spells, SPELL_UNSTABLE_AFFLICTION, TARGET_VICTIM, 8); + if (!victim.GetAuraApplicationOfRankedSpell(SPELL_HAUNT, me.GetGUID())) + VerifyAndPushSpellCast(spells, SPELL_HAUNT, TARGET_VICTIM, 8); + if (!victim.GetAuraApplicationOfRankedSpell(SPELL_CURSE_OF_AGONY, me.GetGUID())) + VerifyAndPushSpellCast(spells, SPELL_CURSE_OF_AGONY, TARGET_VICTIM, 4); + if (victim.HealthBelowPct(25)) + VerifyAndPushSpellCast(spells, SPELL_DRAIN_SOUL, TARGET_VICTIM, 100); + } + break; + case TALENT_SPEC_WARLOCK_DEMONOLOGY: + VerifyAndPushSpellCast(spells, SPELL_METAMORPHOSIS, TARGET_NONE, 15); + VerifyAndPushSpellCast(spells, SPELL_SHADOW_BOLT, TARGET_VICTIM, 7); + if (me.HasAura(AURA_DECIMATION)) + VerifyAndPushSpellCast(spells, SPELL_SOUL_FIRE, TARGET_VICTIM, 100); + if (me.HasAura(SPELL_METAMORPHOSIS)) + { + VerifyAndPushSpellCast(spells, SPELL_IMMOLATION_AURA, TARGET_NONE, 30); + if (!me.IsWithinMeleeRange(me.GetVictim())) + VerifyAndPushSpellCast(spells, SPELL_DEMON_CHARGE, TARGET_VICTIM, 20); + } + if (me.GetVictim() && !me.GetVictim().GetAuraApplicationOfRankedSpell(SPELL_IMMOLATE, me.GetGUID())) + VerifyAndPushSpellCast(spells, SPELL_IMMOLATE, TARGET_VICTIM, 5); + if (me.HasAura(AURA_MOLTEN_CORE)) + VerifyAndPushSpellCast(spells, SPELL_INCINERATE, TARGET_VICTIM, 10); + break; + case TALENT_SPEC_WARLOCK_DESTRUCTION: + if (me.GetVictim() && !me.GetVictim().GetAuraApplicationOfRankedSpell(SPELL_IMMOLATE, me.GetGUID())) + VerifyAndPushSpellCast(spells, SPELL_IMMOLATE, TARGET_VICTIM, 8); + if (me.GetVictim() && me.GetVictim().GetAuraApplicationOfRankedSpell(SPELL_IMMOLATE, me.GetGUID())) + VerifyAndPushSpellCast(spells, SPELL_CONFLAGRATE, TARGET_VICTIM, 8); + VerifyAndPushSpellCast(spells, SPELL_SHADOWFURY, TARGET_VICTIM, 5); + VerifyAndPushSpellCast(spells, SPELL_CHAOS_BOLT, TARGET_VICTIM, 10); + VerifyAndPushSpellCast(spells, SPELL_SHADOWBURN, TARGET_VICTIM, 3); + VerifyAndPushSpellCast(spells, SPELL_INCINERATE, TARGET_VICTIM, 7); + break; + } + break; + case CLASS_MONK: + switch (GetSpec()) + { + case TALENT_SPEC_MONK_BREWMASTER: + case TALENT_SPEC_MONK_BATTLEDANCER: + case TALENT_SPEC_MONK_MISTWEAVER: + break; + } + break; + case CLASS_DRUID: + VerifyAndPushSpellCast(spells, SPELL_INNERVATE, TARGET_CHARMER, 5); + VerifyAndPushSpellCast(spells, SPELL_BARKSKIN, TARGET_NONE, 5); + switch (GetSpec()) + { + case TALENT_SPEC_DRUID_RESTORATION: + if (!me.HasAura(SPELL_TREE_OF_LIFE)) + { + CancelAllShapeshifts(); + VerifyAndPushSpellCast(spells, SPELL_TREE_OF_LIFE, TARGET_NONE, 100); + break; + } + VerifyAndPushSpellCast(spells, SPELL_TRANQUILITY, TARGET_NONE, 10); + VerifyAndPushSpellCast(spells, SPELL_NATURE_SWIFTNESS, TARGET_NONE, 7); + if (Creature* creatureCharmer = GetCharmer()) + { + VerifyAndPushSpellCast(spells, SPELL_NOURISH, creatureCharmer, 5); + VerifyAndPushSpellCast(spells, SPELL_WILD_GROWTH, creatureCharmer, 5); + if (!creatureCharmer.GetAuraApplicationOfRankedSpell(SPELL_REJUVENATION, me.GetGUID())) + VerifyAndPushSpellCast(spells, SPELL_REJUVENATION, creatureCharmer, 8); + if (!creatureCharmer.GetAuraApplicationOfRankedSpell(SPELL_REGROWTH, me.GetGUID())) + VerifyAndPushSpellCast(spells, SPELL_REGROWTH, creatureCharmer, 8); + uint8 lifebloomStacks = 0; + if (Aura const* lifebloom = creatureCharmer.GetAura(SPELL_LIFEBLOOM, me.GetGUID())) + lifebloomStacks = lifebloom.GetStackAmount(); + if (lifebloomStacks < 3) + VerifyAndPushSpellCast(spells, SPELL_LIFEBLOOM, creatureCharmer, 5); + if (creatureCharmer.GetAuraApplicationOfRankedSpell(SPELL_REJUVENATION) || + creatureCharmer.GetAuraApplicationOfRankedSpell(SPELL_REGROWTH)) + VerifyAndPushSpellCast(spells, SPELL_SWIFTMEND, creatureCharmer, 10); + if (me.HasAura(SPELL_NATURE_SWIFTNESS)) + VerifyAndPushSpellCast(spells, SPELL_HEALING_TOUCH, creatureCharmer, 100); + } + break; + case TALENT_SPEC_DRUID_BALANCE: + { + if (!me.HasAura(SPELL_MOONKIN_FORM)) + { + CancelAllShapeshifts(); + VerifyAndPushSpellCast(spells, SPELL_MOONKIN_FORM, TARGET_NONE, 100); + break; + } + uint32 const mainAttackSpell = me.HasAura(AURA_ECLIPSE_LUNAR) ? SPELL_STARFIRE : SPELL_WRATH; + VerifyAndPushSpellCast(spells, SPELL_STARFALL, TARGET_NONE, 20); + VerifyAndPushSpellCast(spells, mainAttackSpell, TARGET_VICTIM, 10); + if (me.GetVictim() && !me.GetVictim().GetAuraApplicationOfRankedSpell(SPELL_INSECT_SWARM, me.GetGUID())) + VerifyAndPushSpellCast(spells, SPELL_INSECT_SWARM, TARGET_VICTIM, 7); + if (me.GetVictim() && !me.GetVictim().GetAuraApplicationOfRankedSpell(SPELL_MOONFIRE, me.GetGUID())) + VerifyAndPushSpellCast(spells, SPELL_MOONFIRE, TARGET_VICTIM, 5); + if (me.GetVictim() && me.GetVictim().HasUnitState(UNIT_STATE_CASTING)) + VerifyAndPushSpellCast(spells, SPELL_TYPHOON, TARGET_NONE, 15); + break; + } + case TALENT_SPEC_DRUID_CAT: + case TALENT_SPEC_DRUID_BEAR: + if (!me.HasAura(SPELL_CAT_FORM)) + { + CancelAllShapeshifts(); + VerifyAndPushSpellCast(spells, SPELL_CAT_FORM, TARGET_NONE, 100); + break; + } + VerifyAndPushSpellCast(spells, SPELL_BERSERK, TARGET_NONE, 20); + VerifyAndPushSpellCast(spells, SPELL_SURVIVAL_INSTINCTS, TARGET_NONE, 15); + VerifyAndPushSpellCast(spells, SPELL_TIGER_FURY, TARGET_NONE, 15); + VerifyAndPushSpellCast(spells, SPELL_DASH, TARGET_NONE, 5); + if (Unit* victim = me.GetVictim()) + { + uint8 const cp = me.GetPower(POWER_COMBO_POINTS); + if (victim.HasUnitState(UNIT_STATE_CASTING) && cp >= 1) + VerifyAndPushSpellCast(spells, SPELL_MAIM, TARGET_VICTIM, 25); + if (!me.IsWithinMeleeRange(victim)) + VerifyAndPushSpellCast(spells, SPELL_FERAL_CHARGE_CAT, TARGET_VICTIM, 25); + if (cp >= 4) + VerifyAndPushSpellCast(spells, SPELL_RIP, TARGET_VICTIM, 50); + if (cp <= 4) + { + VerifyAndPushSpellCast(spells, SPELL_MANGLE_CAT, TARGET_VICTIM, 10); + VerifyAndPushSpellCast(spells, SPELL_CLAW, TARGET_VICTIM, 5); + if (!victim.GetAuraApplicationOfRankedSpell(SPELL_RAKE, me.GetGUID())) + VerifyAndPushSpellCast(spells, SPELL_RAKE, TARGET_VICTIM, 8); + if (!me.HasAura(SPELL_SAVAGE_ROAR)) + VerifyAndPushSpellCast(spells, SPELL_SAVAGE_ROAR, TARGET_NONE, 15); + } + } + break; + } + break; + case CLASS_DEMON_HUNTER: + switch (GetSpec()) + { + case TALENT_SPEC_DEMON_HUNTER_HAVOC: + case TALENT_SPEC_DEMON_HUNTER_VENGEANCE: + break; + } + break; + } + */ + return SelectSpellCast(spells); + } + + const float CASTER_CHASE_DISTANCE = 28.0f; + + public override void UpdateAI(uint diff) + { + Creature charmer = GetCharmer(); + if (!charmer) + return; + + //kill self if charm aura has infinite duration + if (charmer.IsInEvadeMode()) + { + var auras = me.GetAuraEffectsByType(AuraType.ModCharm); + foreach (var effect in auras) + { + if (effect.GetCasterGUID() == charmer.GetGUID() && effect.GetBase().IsPermanent()) + { + me.KillSelf(); + return; + } + } + } + + if (charmer.IsInCombat()) + { + Unit target = me.GetVictim(); + if (!target || !charmer.IsValidAttackTarget(target) || target.HasBreakableByDamageCrowdControlAura()) + { + target = SelectAttackTarget(); + if (!target) + return; + + if (IsRangedAttacker()) + { + _chaseCloser = !me.IsWithinLOSInMap(target); + if (_chaseCloser) + AttackStart(target); + else + AttackStartCaster(target, CASTER_CHASE_DISTANCE); + } + else + AttackStart(target); + _forceFacing = true; + } + + if (me.IsStopped() && !me.HasUnitState(UnitState.CannotTurn)) + { + float targetAngle = me.GetAngle(target); + if (_forceFacing || Math.Abs(me.GetOrientation() - targetAngle) > 0.4f) + { + me.SetFacingTo(targetAngle); + _forceFacing = false; + } + } + + if (_castCheckTimer <= diff) + { + if (me.HasUnitState(UnitState.Casting)) + _castCheckTimer = 0; + else + { + if (IsRangedAttacker()) + { // chase to zero if the target isn't in line of sight + bool inLOS = me.IsWithinLOSInMap(target); + if (_chaseCloser != !inLOS) + { + _chaseCloser = !inLOS; + if (_chaseCloser) + AttackStart(target); + else + AttackStartCaster(target, CASTER_CHASE_DISTANCE); + } + } + Tuple shouldCast = SelectAppropriateCastForSpec(); + if (shouldCast != null) + DoCastAtTarget(shouldCast); + _castCheckTimer = 500; + } + } + else + _castCheckTimer -= diff; + + DoAutoAttackIfReady(); + } + else + { + me.AttackStop(); + me.CastStop(); + me.StopMoving(); + me.GetMotionMaster().Clear(); + me.GetMotionMaster().MoveFollow(charmer, SharedConst.PetFollowDist, SharedConst.PetFollowAngle); + } + } + + public override void OnCharmed(bool apply) + { + if (apply) + { + me.CastStop(); + me.AttackStop(); + me.StopMoving(); + me.GetMotionMaster().Clear(); + me.GetMotionMaster().MovePoint(0, me.GetPosition(), false); // force re-sync of current position for all clients + } + else + { + me.CastStop(); + me.AttackStop(); + // @todo only voluntary movement (don't cancel stuff like death grip or charge mid-animation) + me.GetMotionMaster().Clear(); + me.StopMoving(); + } + } + + uint _castCheckTimer; + bool _chaseCloser; + bool _forceFacing; + } + + struct UncontrolledTargetSelectPredicate : ISelector + { + public bool Check(Unit target) + { + return !target.HasBreakableByDamageCrowdControlAura(); + } + } +} diff --git a/Game/AI/ScriptedAI/ScriptedAI.cs b/Game/AI/ScriptedAI/ScriptedAI.cs new file mode 100644 index 000000000..b92043793 --- /dev/null +++ b/Game/AI/ScriptedAI/ScriptedAI.cs @@ -0,0 +1,802 @@ +/* + * Copyright (C) 2012-2017 CypherCore + * + * 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, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +using Framework.Constants; +using Game.DataStorage; +using Game.Entities; +using Game.Maps; +using Game.Spells; +using System; +using System.Collections.Generic; +using System.Linq; + +namespace Game.AI +{ + public class ScriptedAI : CreatureAI + { + public ScriptedAI(Creature creature) : base(creature) + { + _isCombatMovementAllowed = true; + _isHeroic = me.GetMap().IsHeroic(); + _difficulty = me.GetMap().GetSpawnMode(); + } + + void AttackStartNoMove(Unit target) + { + if (target == null) + return; + + if (me.Attack(target, true)) + DoStartNoMovement(target); + } + + // Called before EnterCombat even before the creature is in combat. + public override void AttackStart(Unit target) + { + if (IsCombatMovementAllowed()) + base.AttackStart(target); + else + AttackStartNoMove(target); + } + + //Called at World update tick + public override void UpdateAI(uint diff) + { + //Check if we have a current target + if (!UpdateVictim()) + return; + + DoMeleeAttackIfReady(); + } + + //Start movement toward victim + public void DoStartMovement(Unit target, float distance = 0.0f, float angle = 0.0f) + { + if (target != null) + me.GetMotionMaster().MoveChase(target, distance, angle); + } + + //Start no movement on victim + public void DoStartNoMovement(Unit target) + { + if (target == null) + return; + + me.GetMotionMaster().MoveIdle(); + } + + //Stop attack of current victim + public void DoStopAttack() + { + if (me.GetVictim() != null) + me.AttackStop(); + } + + //Cast spell by spell info + void DoCastSpell(Unit target, SpellInfo spellInfo, bool triggered = false) + { + if (target == null || me.IsNonMeleeSpellCast(false)) + return; + + me.StopMoving(); + me.CastSpell(target, spellInfo, triggered ? TriggerCastFlags.FullMask : TriggerCastFlags.None); + } + + //Plays a sound to all nearby players + public void DoPlaySoundToSet(WorldObject source, uint soundId) + { + if (source == null) + return; + + if (!CliDB.SoundKitStorage.ContainsKey(soundId)) + { + Log.outError(LogFilter.Scripts, "Invalid soundId {0} used in DoPlaySoundToSet (Source: TypeId {1}, GUID {2})", soundId, source.GetTypeId(), source.GetGUID().ToString()); + return; + } + + source.PlayDirectSound(soundId); + } + + //Spawns a creature relative to me + public Creature DoSpawnCreature(uint entry, float offsetX, float offsetY, float offsetZ, float angle, TempSummonType type, uint despawntime) + { + return me.SummonCreature(entry, me.GetPositionX() + offsetX, me.GetPositionY() + offsetY, me.GetPositionZ() + offsetZ, angle, type, despawntime); + } + + //Returns spells that meet the specified criteria from the creatures spell list + public SpellInfo SelectSpell(Unit target, SpellSchoolMask school, Mechanics mechanic, SelectTargetType targets, float rangeMin, float rangeMax, SelectEffect effect) + { + //No target so we can't cast + if (target == null) + return null; + + //Silenced so we can't cast + if (me.HasFlag(UnitFields.Flags, UnitFlags.Silenced)) + return null; + + //Using the extended script system we first create a list of viable spells + SpellInfo[] apSpell = new SpellInfo[SharedConst.MaxCreatureSpells]; + + uint spellCount = 0; + + SpellInfo tempSpell = null; + + //Check if each spell is viable(set it to null if not) + for (uint i = 0; i < SharedConst.MaxCreatureSpells; i++) + { + tempSpell = Global.SpellMgr.GetSpellInfo(me.m_spells[i]); + + //This spell doesn't exist + if (tempSpell == null) + continue; + + // Targets and Effects checked first as most used restrictions + //Check the spell targets if specified + if (targets != 0 && !Convert.ToBoolean(Global.ScriptMgr.spellSummaryStorage[me.m_spells[i]].Targets & (1 << ((int)targets - 1)))) + continue; + + //Check the type of spell if we are looking for a specific spell type + if (effect != 0 && !Convert.ToBoolean(Global.ScriptMgr.spellSummaryStorage[me.m_spells[i]].Effects & (1 << ((int)effect - 1)))) + continue; + + //Check for school if specified + if (school != 0 && (tempSpell.SchoolMask & school) == 0) + continue; + + //Check for spell mechanic if specified + if (mechanic != 0 && tempSpell.Mechanic != mechanic) + continue; + + //Check if the spell meets our range requirements + if (rangeMin != 0 && me.GetSpellMinRangeForTarget(target, tempSpell) < rangeMin) + continue; + if (rangeMax != 0 && me.GetSpellMaxRangeForTarget(target, tempSpell) > rangeMax) + continue; + + //Check if our target is in range + if (me.IsWithinDistInMap(target, me.GetSpellMinRangeForTarget(target, tempSpell)) || !me.IsWithinDistInMap(target, me.GetSpellMaxRangeForTarget(target, tempSpell))) + continue; + + //All good so lets add it to the spell list + apSpell[spellCount] = tempSpell; + ++spellCount; + } + + //We got our usable spells so now lets randomly pick one + if (spellCount == 0) + return null; + + return apSpell[RandomHelper.IRand(0, (int)(spellCount - 1))]; + } + + //Drops all threat to 0%. Does not remove players from the threat list + public void DoResetThreat() + { + if (!me.CanHaveThreatList() || me.GetThreatManager().isThreatListEmpty()) + { + Log.outError(LogFilter.Scripts, "DoResetThreat called for creature that either cannot have threat list or has empty threat list (me entry = {0})", me.GetEntry()); + return; + } + + var threatlist = me.GetThreatManager().getThreatList(); + + foreach (var refe in threatlist) + { + Unit unit = Global.ObjAccessor.GetUnit(me, refe.getUnitGuid()); + if (unit != null && DoGetThreat(unit) != 0) + DoModifyThreatPercent(unit, -100); + } + } + + public float DoGetThreat(Unit unit) + { + if (unit == null) + return 0.0f; + return me.GetThreatManager().getThreat(unit); + } + + public void DoModifyThreatPercent(Unit unit, int pct) + { + if (unit == null) + return; + me.GetThreatManager().modifyThreatPercent(unit, pct); + } + + void DoTeleportTo(float x, float y, float z, uint time = 0) + { + me.Relocate(x, y, z); + float speed = me.GetDistance(x, y, z) / (time * 0.001f); + me.MonsterMoveWithSpeed(x, y, z, speed); + } + + void DoTeleportTo(float[] position) + { + me.NearTeleportTo(position[0], position[1], position[2], position[3]); + } + + //Teleports a player without dropping threat (only teleports to same map) + void DoTeleportPlayer(Unit unit, float x, float y, float z, float o) + { + if (unit == null) + return; + Player player = unit.ToPlayer(); + if (player != null) + player.TeleportTo(unit.GetMapId(), x, y, z, o, TeleportToOptions.NotLeaveCombat); + else + Log.outError(LogFilter.Scripts, "Creature {0} (Entry: {1}) Tried to teleport non-player unit (Type: {2} GUID: {3}) to X: {4} Y: {5} Z: {6} O: {7}. Aborted.", + me.GetGUID(), me.GetEntry(), unit.GetTypeId(), unit.GetGUID(), x, y, z, o); + } + + void DoTeleportAll(float x, float y, float z, float o) + { + Map map = me.GetMap(); + if (!map.IsDungeon()) + return; + + var PlayerList = map.GetPlayers(); + foreach (var player in PlayerList) + if (player.IsAlive()) + player.TeleportTo(me.GetMapId(), x, y, z, o, TeleportToOptions.NotLeaveCombat); + + } + + //Returns friendly unit with the most amount of hp missing from max hp + public Unit DoSelectLowestHpFriendly(float range, uint minHPDiff = 1) + { + var u_check = new MostHPMissingInRange(me, range, minHPDiff); + var searcher = new UnitLastSearcher(me, u_check); + Cell.VisitAllObjects(me, searcher, range); + + return searcher.GetTarget(); + } + + //Returns a list of friendly CC'd units within range + List DoFindFriendlyCC(float range) + { + List list = new List(); + var u_check = new FriendlyCCedInRange(me, range); + var searcher = new CreatureListSearcher(me, list, u_check); + Cell.VisitAllObjects(me, searcher, range); + + return list; + } + + //Returns a list of all friendly units missing a specific buff within range + public List DoFindFriendlyMissingBuff(float range, uint spellId) + { + List list = new List(); + var u_check = new FriendlyMissingBuffInRange(me, range, spellId); + var searcher = new CreatureListSearcher(me, list, u_check); + Cell.VisitAllObjects(me, searcher, range); + + return list; + } + + //Return a player with at least minimumRange from me + Player GetPlayerAtMinimumRange(float minimumRange) + { + var check = new PlayerAtMinimumRangeAway(me, minimumRange); + var searcher = new PlayerSearcher(me, check); + Cell.VisitWorldObjects(me, searcher, minimumRange); + + return searcher.GetTarget(); + } + + public void SetEquipmentSlots(bool loadDefault, int mainHand = -1, int offHand = -1, int ranged = -1) + { + if (loadDefault) + { + me.LoadEquipment(me.GetOriginalEquipmentId(), true); + return; + } + + if (mainHand >= 0) + me.SetVirtualItem(0, (uint)mainHand); + + if (offHand >= 0) + me.SetVirtualItem(1, (uint)offHand); + + if (ranged >= 0) + me.SetVirtualItem(2, (uint)ranged); + } + + // Used to control if MoveChase() is to be used or not in AttackStart(). Some creatures does not chase victims + // NOTE: If you use SetCombatMovement while the creature is in combat, it will do NOTHING - This only affects AttackStart + // You should make the necessary to make it happen so. + // Remember that if you modified _isCombatMovementAllowed (e.g: using SetCombatMovement) it will not be reset at Reset(). + // It will keep the last value you set. + public void SetCombatMovement(bool allowMovement) + { + _isCombatMovementAllowed = allowMovement; + } + + /*bool EnterEvadeIfOutOfCombatArea(uint diff) + { + if (_evadeCheckCooldown <= diff) + _evadeCheckCooldown = 2500; + else + { + _evadeCheckCooldown -= diff; + return false; + } + + if (me.IsInEvadeMode() || me.GetVictim() == null) + return false; + + float x = me.GetPositionX(); + float y = me.GetPositionY(); + float z = me.GetPositionZ(); + + switch (me.GetEntry()) + { + case 12017: // broodlord (not move down stairs) + if (z > 448.60f) + return false; + break; + case 19516: // void reaver (calculate from center of room) + if (me.GetDistance2d(432.59f, 371.93f) < 105.0f) + return false; + break; + case 23578: // jan'alai (calculate by Z) + if (z > 12.0f) + return false; + break; + case 28860: // sartharion (calculate box) + if (x > 3218.86f && x < 3275.69f && y < 572.40f && y > 484.68f) + return false; + break; + default: // For most of creatures that certain area is their home area. + Log.outInfo(LogFilter.Server, "Scripts: EnterEvadeIfOutOfCombatArea used for creature entry {0}, but does not have any definition. Using the default one.", me.GetEntry()); + uint homeAreaId = me.GetMap().GetAreaId(me.GetHomePosition().posX, me.GetHomePosition().posY, me.GetHomePosition().posZ); + if (me.GetAreaId() == homeAreaId) + return false; + break; + } + + EnterEvadeMode(); + return true; + }*/ + + // Called at any Damage from any attacker (before damage apply) + public override void DamageTaken(Unit attacker, ref uint damage) { } + + //Called at creature death + public override void JustDied(Unit killer) { } + + //Called at creature killing another unit + public override void KilledUnit(Unit victim) { } + + // Called when the creature summon successfully other creature + public override void JustSummoned(Creature summon) { } + + // Called when a summoned creature is despawned + public override void SummonedCreatureDespawn(Creature summon) { } + + // Called when hit by a spell + public override void SpellHit(Unit caster, SpellInfo spell) { } + + // Called when spell hits a target + public override void SpellHitTarget(Unit target, SpellInfo spell) { } + + //Called at waypoint reached or PointMovement end + public override void MovementInform(MovementGeneratorType type, uint id) { } + + // Called when AI is temporarily replaced or put back when possess is applied or removed + public virtual void OnPossess(bool apply) { } + + public static Creature GetClosestCreatureWithEntry(WorldObject source, uint entry, float maxSearchRange, bool alive = true) + { + return source.FindNearestCreature(entry, maxSearchRange, alive); + } + + public GameObject GetClosestGameObjectWithEntry(WorldObject source, uint entry, float maxSearchRange) + { + return source.FindNearestGameObject(entry, maxSearchRange); + } + + //Called at creature reset either by death or evade + public override void Reset() { } + + //Called at creature aggro either by MoveInLOS or Attack Start + public override void EnterCombat(Unit victim) { } + + public bool HealthBelowPct(int pct) { return me.HealthBelowPct(pct); } + public bool HealthAbovePct(int pct) { return me.HealthAbovePct(pct); } + + public bool IsCombatMovementAllowed() { return _isCombatMovementAllowed; } + + // return true for heroic mode. i.e. + // - for dungeon in mode 10-heroic, + // - for raid in mode 10-Heroic + // - for raid in mode 25-heroic + // DO NOT USE to check raid in mode 25-normal. + public bool IsHeroic() { return _isHeroic; } + + // return the dungeon or raid difficulty + public Difficulty GetDifficulty() { return _difficulty; } + + // return true for 25 man or 25 man heroic mode + public bool Is25ManRaid() { return _difficulty == Difficulty.Raid25N || _difficulty == Difficulty.Raid25HC; } + + public T DungeonMode(T normal5, T heroic10) + { + switch (_difficulty) + { + case Difficulty.Normal: + return normal5; + case Difficulty.Heroic: + default: + return heroic10; + } + } + + public T RaidMode(T normal10, T normal25) + { + switch (_difficulty) + { + case Difficulty.Raid10N: + return normal10; + case Difficulty.Raid25N: + default: + return normal25; + } + } + public T RaidMode(T normal10, T normal25, T heroic10, T heroic25) + { + switch (_difficulty) + { + case Difficulty.Raid10N: + return normal10; + case Difficulty.Raid25N: + return normal25; + case Difficulty.Raid10HC: + return heroic10; + case Difficulty.Raid25HC: + default: + return heroic25; + } + } + + Difficulty _difficulty; + bool _isCombatMovementAllowed; + bool _isHeroic; + } + + public class BossAI : ScriptedAI + { + public BossAI(Creature creature, uint bossId) : base(creature) + { + instance = creature.GetInstanceScript(); + summons = new SummonList(creature); + _bossId = bossId; + + if (instance != null) + SetBoundary(instance.GetBossBoundary(bossId)); + + _scheduler.SetValidator(() => + { + return !me.HasUnitState(UnitState.Casting); + }); + } + + public void _Reset() + { + if (!me.IsAlive()) + return; + + me.SetCombatPulseDelay(0); + me.ResetLootMode(); + _events.Reset(); + summons.DespawnAll(); + _scheduler.CancelAll(); + if (instance != null) + instance.SetBossState(_bossId, EncounterState.NotStarted); + } + + public void _JustDied() + { + _events.Reset(); + summons.DespawnAll(); + _scheduler.CancelAll(); + if (instance != null) + instance.SetBossState(_bossId, EncounterState.Done); + } + + public void _EnterCombat() + { + if (instance != null) + { + // bosses do not respawn, check only on enter combat + if (!instance.CheckRequiredBosses(_bossId)) + { + EnterEvadeMode(EvadeReason.SequenceBreak); + return; + } + instance.SetBossState(_bossId, EncounterState.InProgress); + } + + me.SetCombatPulseDelay(5); + me.setActive(true); + DoZoneInCombat(); + ScheduleTasks(); + } + + void TeleportCheaters() + { + float x, y, z; + me.GetPosition(out x, out y, out z); + + var threatList = me.GetThreatManager().getThreatList(); + foreach (var refe in threatList) + { + Unit target = refe.getTarget(); + if (target) + if (target.IsTypeId(TypeId.Player) && !CheckBoundary(target)) + target.NearTeleportTo(x, y, z, 0); + } + } + + public override void JustSummoned(Creature summon) + { + summons.Summon(summon); + if (me.IsInCombat()) + DoZoneInCombat(summon); + } + + public override void SummonedCreatureDespawn(Creature summon) + { + summons.Despawn(summon); + } + + public override void UpdateAI(uint diff) + { + if (!UpdateVictim()) + return; + + _events.Update(diff); + + if (me.HasUnitState(UnitState.Casting)) + return; + + + _events.ExecuteEvents(eventId => + { + ExecuteEvent(eventId); + + if (me.HasUnitState(UnitState.Casting)) + return; + }); + + DoMeleeAttackIfReady(); + } + + public void _DespawnAtEvade(uint delayToRespawn = 30, Creature who = null) + { + if (delayToRespawn < 2) + { + Log.outError(LogFilter.Scripts, "_DespawnAtEvade called with delay of {0} seconds, defaulting to 2.", delayToRespawn); + delayToRespawn = 2; + } + + if (!who) + who = me; + TempSummon whoSummon = who.ToTempSummon(); + if (whoSummon) + { + Log.outWarn(LogFilter.ScriptsAi, "_DespawnAtEvade called on a temporary summon."); + whoSummon.UnSummon(); + return; + } + + me.DespawnOrUnsummon(0, TimeSpan.FromSeconds(delayToRespawn)); + + if (instance != null && who == me) + instance.SetBossState(_bossId, EncounterState.Fail); + } + + public virtual void ExecuteEvent(uint eventId) { } + + public virtual void ScheduleTasks() { } + + public override void Reset() { _Reset(); } + public override void EnterCombat(Unit who) { _EnterCombat(); } + public override void JustDied(Unit killer) { _JustDied(); } + public override void JustReachedHome() { _JustReachedHome(); } + + public override bool CanAIAttack(Unit victim) { return CheckBoundary(victim); } + + public void _JustReachedHome() { me.setActive(false); } + + public InstanceScript instance; + public SummonList summons; + uint _bossId; + } + + public class WorldBossAI : ScriptedAI + { + public WorldBossAI(Creature creature) : base(creature) + { + summons = new SummonList(creature); + } + + void _Reset() + { + if (!me.IsAlive()) + return; + + _events.Reset(); + summons.DespawnAll(); + } + + void _JustDied() + { + _events.Reset(); + summons.DespawnAll(); + } + + void _EnterCombat() + { + Unit target = SelectTarget(SelectAggroTarget.Random, 0, 0.0f, true); + if (target) + AttackStart(target); + } + + public override void JustSummoned(Creature summon) + { + summons.Summon(summon); + Unit target = SelectTarget(SelectAggroTarget.Random, 0, 0.0f, true); + if (target) + summon.GetAI().AttackStart(target); + } + + public override void SummonedCreatureDespawn(Creature summon) + { + summons.Despawn(summon); + } + + public override void UpdateAI(uint diff) + { + if (!UpdateVictim()) + return; + + _events.Update(diff); + + if (me.HasUnitState(UnitState.Casting)) + return; + + _events.ExecuteEvents(eventId => + { + ExecuteEvent(eventId); + + if (me.HasUnitState(UnitState.Casting)) + return; + }); + + DoMeleeAttackIfReady(); + } + + // Hook used to execute events scheduled into EventMap without the need + // to override UpdateAI + // note: You must re-schedule the event within this method if the event + // is supposed to run more than once + public virtual void ExecuteEvent(uint eventId) { } + + public override void Reset() { _Reset(); } + + public override void EnterCombat(Unit who) { _EnterCombat(); } + + public override void JustDied(Unit killer) { _JustDied(); } + + SummonList summons; + } + + public class SummonList : List + { + public SummonList(Creature creature) + { + me = creature; + } + + public void Summon(Creature summon) { Add(summon.GetGUID()); } + + public void DoZoneInCombat(uint entry = 0, float maxRangeToNearestTarget = 250.0f) + { + foreach (var id in this) + { + Creature summon = ObjectAccessor.GetCreature(me, id); + if (summon && summon.IsAIEnabled && (entry == 0 || summon.GetEntry() == entry)) + { + summon.GetAI().DoZoneInCombat(null, maxRangeToNearestTarget); + } + } + } + + public void DespawnEntry(uint entry) + { + foreach (var id in this) + { + Creature summon = ObjectAccessor.GetCreature(me, id); + if (!summon) + Remove(id); + else if (summon.GetEntry() == entry) + { + Remove(id); + summon.DespawnOrUnsummon(); + } + } + } + + public void DespawnAll() + { + while (!this.Empty()) + { + Creature summon = ObjectAccessor.GetCreature(me, this.FirstOrDefault()); + this.RemoveAt(0); + if (summon) + summon.DespawnOrUnsummon(); + } + } + + public void Despawn(Creature summon) { Remove(summon.GetGUID()); } + + public void DespawnIf(Predicate predicate) + { + RemoveAll(predicate); + } + + public void RemoveNotExisting() + { + foreach (var id in this.ToList()) + { + if (!ObjectAccessor.GetCreature(me, id)) + Remove(id); + } + } + + public void DoAction(int info, Predicate predicate, ushort max = 0) + { + // We need to use a copy of SummonList here, otherwise original SummonList would be modified + List listCopy = new List(this); + listCopy.RandomResize(predicate, max); + DoActionImpl(info, listCopy); + } + + public bool HasEntry(uint entry) + { + foreach (var id in this) + { + Creature summon = ObjectAccessor.GetCreature(me, id); + if (summon && summon.GetEntry() == entry) + return true; + } + + return false; + } + + void DoActionImpl(int action, List summons) + { + foreach (var guid in summons) + { + Creature summon = ObjectAccessor.GetCreature(me, guid); + if (summon && summon.IsAIEnabled) + summon.GetAI().DoAction(action); + } + } + + Creature me; + } +} diff --git a/Game/AI/ScriptedAI/ScriptedEscortAI.cs b/Game/AI/ScriptedAI/ScriptedEscortAI.cs new file mode 100644 index 000000000..9e659ae31 --- /dev/null +++ b/Game/AI/ScriptedAI/ScriptedEscortAI.cs @@ -0,0 +1,627 @@ +/* + * Copyright (C) 2012-2017 CypherCore + * + * 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, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +using Framework.Constants; +using Game.Entities; +using Game.Groups; +using System; +using System.Collections.Generic; +using System.Linq; + +namespace Game.AI +{ + public class npc_escortAI : ScriptedAI + { + public npc_escortAI(Creature creature) : base(creature) + { + m_uiPlayerGUID = ObjectGuid.Empty; + m_uiWPWaitTimer = 2500; + m_uiPlayerCheckTimer = 1000; + m_uiEscortState = eEscortState.None; + MaxPlayerDistance = 50; + m_pQuestForEscort = null; + m_bIsActiveAttacker = true; + m_bIsRunning = false; + m_bCanInstantRespawn = false; + m_bCanReturnToStart = false; + DespawnAtEnd = true; + DespawnAtFar = true; + ScriptWP = false; + HasImmuneToNPCFlags = false; + } + + public override void AttackStart(Unit who) + { + if (!who) + return; + + if (me.Attack(who, true)) + { + if (me.GetMotionMaster().GetCurrentMovementGeneratorType() == MovementGeneratorType.Point) + me.GetMotionMaster().MovementExpired(); + + if (IsCombatMovementAllowed()) + me.GetMotionMaster().MoveChase(who); + } + } + + //see followerAI + bool AssistPlayerInCombatAgainst(Unit who) + { + if (!who || !who.GetVictim()) + return false; + + //experimental (unknown) flag not present + if (!me.GetCreatureTemplate().TypeFlags.HasAnyFlag(CreatureTypeFlags.CanAssist)) + return false; + + //not a player + if (!who.GetVictim().GetCharmerOrOwnerPlayerOrPlayerItself()) + return false; + + //never attack friendly + if (me.IsFriendlyTo(who)) + return false; + + //too far away and no free sight? + if (me.IsWithinDistInMap(who, GetMaxPlayerDistance()) && me.IsWithinLOSInMap(who)) + { + //already fighting someone? + if (!me.GetVictim()) + { + AttackStart(who); + return true; + } + else + { + who.SetInCombatWith(me); + me.AddThreat(who, 0.0f); + return true; + } + } + + return false; + } + + public override void MoveInLineOfSight(Unit who) + { + if (me.HasReactState(ReactStates.Aggressive) && !me.HasUnitState(UnitState.Stunned) && who.isTargetableForAttack() && who.isInAccessiblePlaceFor(me)) + { + if (HasEscortState(eEscortState.Escorting) && AssistPlayerInCombatAgainst(who)) + return; + + if (!me.CanFly() && me.GetDistanceZ(who) > SharedConst.CreatureAttackRangeZ) + return; + + if (me.IsHostileTo(who)) + { + float fAttackRadius = me.GetAttackDistance(who); + if (me.IsWithinDistInMap(who, fAttackRadius) && me.IsWithinLOSInMap(who)) + { + if (!me.GetVictim()) + { + // Clear distracted state on combat + if (me.HasUnitState(UnitState.Distracted)) + { + me.ClearUnitState(UnitState.Distracted); + me.GetMotionMaster().Clear(); + } + + AttackStart(who); + } + else if (me.GetMap().IsDungeon()) + { + who.SetInCombatWith(me); + me.AddThreat(who, 0.0f); + } + } + } + } + } + + public override void JustDied(Unit killer) + { + if (!HasEscortState(eEscortState.Escorting) || m_uiPlayerGUID.IsEmpty() || m_pQuestForEscort == null) + return; + + Player player = GetPlayerForEscort(); + if (player) + { + Group group = player.GetGroup(); + if (group) + { + for (GroupReference groupRef = group.GetFirstMember(); groupRef != null; groupRef = groupRef.next()) + { + Player member = groupRef.GetSource(); + if (member) + if (member.GetQuestStatus(m_pQuestForEscort.Id) == QuestStatus.Incomplete) + member.FailQuest(m_pQuestForEscort.Id); + } + } + else + { + if (player.GetQuestStatus(m_pQuestForEscort.Id) == QuestStatus.Incomplete) + player.FailQuest(m_pQuestForEscort.Id); + } + } + } + + public override void JustRespawned() + { + m_uiEscortState = eEscortState.None; + + if (!IsCombatMovementAllowed()) + SetCombatMovement(true); + + //add a small delay before going to first waypoint, normal in near all cases + m_uiWPWaitTimer = 2500; + + if (me.getFaction() != me.GetCreatureTemplate().Faction) + me.RestoreFaction(); + + Reset(); + } + + void ReturnToLastPoint() + { + float x, y, z, o; + me.GetHomePosition(out x, out y, out z, out o); + me.GetMotionMaster().MovePoint(0xFFFFFF, x, y, z); + } + + public override void EnterEvadeMode(EvadeReason why = EvadeReason.Other) + { + me.RemoveAllAuras(); + me.DeleteThreatList(); + me.CombatStop(true); + me.SetLootRecipient(null); + + if (HasEscortState(eEscortState.Escorting)) + { + AddEscortState(eEscortState.Returning); + ReturnToLastPoint(); + Log.outDebug(LogFilter.Scripts, "EscortAI has left combat and is now returning to last point"); + } + else + { + me.GetMotionMaster().MoveTargetedHome(); + if (HasImmuneToNPCFlags) + me.SetFlag(UnitFields.Flags, UnitFlags.ImmuneToNpc); + Reset(); + } + } + + bool IsPlayerOrGroupInRange() + { + Player player = GetPlayerForEscort(); + if (player) + { + Group group = player.GetGroup(); + if (group) + { + for (GroupReference groupRef = group.GetFirstMember(); groupRef != null; groupRef = groupRef.next()) + { + Player member = groupRef.GetSource(); + if (member) + if (me.IsWithinDistInMap(member, GetMaxPlayerDistance())) + return true; + } + } + else if (me.IsWithinDistInMap(player, GetMaxPlayerDistance())) + return true; + } + + return false; + } + + public override void UpdateAI(uint diff) + { + //Waypoint Updating + if (HasEscortState(eEscortState.Escorting) && !me.GetVictim() && m_uiWPWaitTimer != 0 && !HasEscortState(eEscortState.Returning)) + { + if (m_uiWPWaitTimer <= diff) + { + //End of the line + if (WPIndex == WaypointList.Count) + { + if (DespawnAtEnd) + { + Log.outDebug(LogFilter.Scripts, "EscortAI reached end of waypoints"); + + if (m_bCanReturnToStart) + { + float fRetX, fRetY, fRetZ; + me.GetRespawnPosition(out fRetX, out fRetY, out fRetZ); + + me.GetMotionMaster().MovePoint(0xFFFFFE, fRetX, fRetY, fRetZ); + + m_uiWPWaitTimer = 0; + + Log.outDebug(LogFilter.Scripts, "EscortAI are returning home to spawn location: {0}, {1}, {2}, {3}", 0xFFFFFE, fRetX, fRetY, fRetZ); + return; + } + + if (m_bCanInstantRespawn) + { + me.setDeathState(DeathState.JustDied); + me.Respawn(); + } + else + me.DespawnOrUnsummon(); + + return; + } + else + { + Log.outDebug(LogFilter.Scripts, "EscortAI reached end of waypoints with Despawn off"); + return; + } + } + + if (!HasEscortState(eEscortState.Paused)) + { + me.GetMotionMaster().MovePoint(GetCurrentWaypoint().id, GetCurrentWaypoint().x, GetCurrentWaypoint().y, GetCurrentWaypoint().z); + Log.outDebug(LogFilter.Scripts, "EscortAI start waypoint {0} ({1}, {2}, {3}).", GetCurrentWaypoint().id, GetCurrentWaypoint().x, GetCurrentWaypoint().y, GetCurrentWaypoint().z); + + WaypointStart(GetCurrentWaypoint().id); + + m_uiWPWaitTimer = 0; + } + } + else + m_uiWPWaitTimer -= diff; + } + + //Check if player or any member of his group is within range + if (HasEscortState(eEscortState.Escorting) && !m_uiPlayerGUID.IsEmpty() && !me.GetVictim() && !HasEscortState(eEscortState.Returning)) + { + if (m_uiPlayerCheckTimer <= diff) + { + if (DespawnAtFar && !IsPlayerOrGroupInRange()) + { + Log.outDebug(LogFilter.Scripts, "EscortAI failed because player/group was to far away or not found"); + + if (m_bCanInstantRespawn) + { + me.setDeathState(DeathState.JustDied); + me.Respawn(); + } + else + me.DespawnOrUnsummon(); + + return; + } + + m_uiPlayerCheckTimer = 1000; + } + else + m_uiPlayerCheckTimer -= diff; + } + + UpdateEscortAI(diff); + } + + void UpdateEscortAI(uint diff) + { + if (!UpdateVictim()) + return; + + DoMeleeAttackIfReady(); + } + + public override void MovementInform(MovementGeneratorType moveType, uint pointId) + { + if (moveType != MovementGeneratorType.Point || !HasEscortState(eEscortState.Escorting)) + return; + + //Combat start position reached, continue waypoint movement + if (pointId == 0xFFFFFF) + { + Log.outDebug(LogFilter.Scripts, "EscortAI has returned to original position before combat"); + + me.SetWalk(!m_bIsRunning); + RemoveEscortState(eEscortState.Returning); + + if (m_uiWPWaitTimer == 0) + m_uiWPWaitTimer = 1; + } + else if (pointId == 0xFFFFFE) + { + Log.outDebug(LogFilter.Scripts, "EscortAI has returned to original home location and will continue from beginning of waypoint list."); + + WPIndex = 0; + m_uiWPWaitTimer = 1; + } + else + { + //Make sure that we are still on the right waypoint + if (GetCurrentWaypoint().id != pointId) + { + Log.outDebug(LogFilter.Server, "TSCR ERROR: EscortAI reached waypoint out of order {0}, expected {1}, creature entry {2}", pointId, GetCurrentWaypoint().id, me.GetEntry()); + return; + } + + Log.outDebug(LogFilter.Scripts, "EscortAI Waypoint {0} reached", GetCurrentWaypoint().id); + + //Call WP function + WaypointReached(GetCurrentWaypoint().id); + + m_uiWPWaitTimer = GetCurrentWaypoint().WaitTimeMs + 1; + + ++WPIndex; + } + } + + public void AddWaypoint(uint id, float x, float y, float z, uint waitTime = 0) + { + Escort_Waypoint t = new Escort_Waypoint(id, x, y, z, waitTime); + + WaypointList.Add(t); + ScriptWP = true; + } + + void FillPointMovementListForCreature() + { + var movePoints = Global.ScriptMgr.GetPointMoveList(me.GetEntry()); + if (movePoints.Empty()) + return; + + foreach (var pointMove in movePoints) + { + Escort_Waypoint point = new Escort_Waypoint(pointMove.uiPointId, pointMove.fX, pointMove.fY, pointMove.fZ, pointMove.uiWaitTime); + WaypointList.Add(point); + } + } + + public void SetRun(bool on = true) + { + if (on) + { + if (!m_bIsRunning) + me.SetWalk(false); + else + Log.outDebug(LogFilter.Scripts, "EscortAI attempt to set run mode, but is already running."); + } + else + { + if (m_bIsRunning) + me.SetWalk(true); + else + Log.outDebug(LogFilter.Scripts, "EscortAI attempt to set walk mode, but is already walking."); + } + + m_bIsRunning = on; + } + + /// todo get rid of this many variables passed in function. + public void Start(bool isActiveAttacker = true, bool run = false, ObjectGuid playerGUID = default(ObjectGuid), Quest quest = null, bool instantRespawn = false, bool canLoopPath = false, bool resetWaypoints = true) + { + if (me.GetVictim()) + { + Log.outError(LogFilter.Server, "TSCR ERROR: EscortAI (script: {0}, creature entry: {1}) attempts to Start while in combat", me.GetScriptName(), me.GetEntry()); + return; + } + + if (HasEscortState(eEscortState.Escorting)) + { + Log.outError(LogFilter.Scripts, "EscortAI (script: {0}, creature entry: {1}) attempts to Start while already escorting", me.GetScriptName(), me.GetEntry()); + return; + } + + if (!ScriptWP && resetWaypoints) // sd2 never adds wp in script, but tc does + { + if (!WaypointList.Empty()) + WaypointList.Clear(); + FillPointMovementListForCreature(); + } + + if (WaypointList.Empty()) + { + Log.outError(LogFilter.Scripts, "EscortAI (script: {0}, creature entry: {1}) starts with 0 waypoints (possible missing entry in script_waypoint. Quest: {2}).", + me.GetScriptName(), me.GetEntry(), quest != null ? quest.Id : 0); + return; + } + + //set variables + m_bIsActiveAttacker = isActiveAttacker; + m_bIsRunning = run; + + m_uiPlayerGUID = playerGUID; + m_pQuestForEscort = quest; + + m_bCanInstantRespawn = instantRespawn; + m_bCanReturnToStart = canLoopPath; + + if (m_bCanReturnToStart && m_bCanInstantRespawn) + Log.outDebug(LogFilter.Scripts, "EscortAI is set to return home after waypoint end and instant respawn at waypoint end. Creature will never despawn."); + + if (me.GetMotionMaster().GetCurrentMovementGeneratorType() == MovementGeneratorType.Waypoint) + { + me.GetMotionMaster().MovementExpired(); + me.GetMotionMaster().MoveIdle(); + Log.outDebug(LogFilter.Scripts, "EscortAI start with WAYPOINT_MOTION_TYPE, changed to MoveIdle."); + } + + //disable npcflags + me.SetUInt64Value(UnitFields.NpcFlags, (ulong)NPCFlags.None); + if (me.HasFlag(UnitFields.Flags, UnitFlags.ImmuneToNpc)) + { + HasImmuneToNPCFlags = true; + me.RemoveFlag(UnitFields.Flags, UnitFlags.ImmuneToNpc); + } + + Log.outDebug(LogFilter.Scripts, "EscortAI started with {0} waypoints. ActiveAttacker = {1}, Run = {2}, PlayerGUID = {3}", WaypointList.Count, m_bIsActiveAttacker, m_bIsRunning, m_uiPlayerGUID); + + WPIndex = 0; + + //Set initial speed + if (m_bIsRunning) + me.SetWalk(false); + else + me.SetWalk(true); + + AddEscortState(eEscortState.Escorting); + } + + public void SetEscortPaused(bool on) + { + if (!HasEscortState(eEscortState.Escorting)) + return; + + if (on) + AddEscortState(eEscortState.Paused); + else + RemoveEscortState(eEscortState.Paused); + } + + bool SetNextWaypoint(uint pointId, float x, float y, float z, float orientation) + { + me.UpdatePosition(x, y, z, orientation); + return SetNextWaypoint(pointId, false, true); + } + + bool SetNextWaypoint(uint pointId, bool setPosition, bool resetWaypointsOnFail) + { + if (!WaypointList.Empty()) + WaypointList.Clear(); + + FillPointMovementListForCreature(); + + if (WaypointList.Empty()) + return false; + + int size = WaypointList.Count; + Escort_Waypoint waypoint = new Escort_Waypoint(0, 0, 0, 0, 0); + do + { + waypoint = WaypointList.FirstOrDefault(); + WaypointList.RemoveAt(0); + if (waypoint.id == pointId) + { + if (setPosition) + me.UpdatePosition(waypoint.x, waypoint.y, waypoint.z, me.GetOrientation()); + + WPIndex = 0; + return true; + } + } + while (!WaypointList.Empty()); + + // we failed. + // we reset the waypoints in the start; if we pulled any, reset it again + if (resetWaypointsOnFail && size != WaypointList.Count) + { + if (!WaypointList.Empty()) + WaypointList.Clear(); + + FillPointMovementListForCreature(); + } + + return false; + } + + public bool GetWaypointPosition(uint pointId, ref float x, ref float y, ref float z) + { + var waypoints = Global.ScriptMgr.GetPointMoveList(me.GetEntry()); + if (waypoints.Empty()) + return false; + + foreach (var point in waypoints) + { + if (point.uiPointId == pointId) + { + x = point.fX; + y = point.fY; + z = point.fZ; + return true; + } + } + + return false; + } + + public virtual void WaypointReached(uint pointId) { } + public virtual void WaypointStart(uint pointId) { } + + public bool HasEscortState(eEscortState escortState) { return m_uiEscortState.HasAnyFlag(escortState); } + public override bool IsEscorted() { return m_uiEscortState.HasAnyFlag(eEscortState.Escorting); } + + public void SetMaxPlayerDistance(float newMax) { MaxPlayerDistance = newMax; } + public float GetMaxPlayerDistance() { return MaxPlayerDistance; } + + public void SetDespawnAtEnd(bool despawn) { DespawnAtEnd = despawn; } + public void SetDespawnAtFar(bool despawn) { DespawnAtFar = despawn; } + public bool GetAttack() { return m_bIsActiveAttacker; }//used in EnterEvadeMode override + public void SetCanAttack(bool attack) { m_bIsActiveAttacker = attack; } + public ObjectGuid GetEventStarterGUID() { return m_uiPlayerGUID; } + + public Player GetPlayerForEscort() { return Global.ObjAccessor.GetPlayer(me, m_uiPlayerGUID); } + + void AddEscortState(eEscortState escortState) { m_uiEscortState |= escortState; } + void RemoveEscortState(eEscortState escortState) { m_uiEscortState &= ~escortState; } + + ObjectGuid m_uiPlayerGUID; + uint m_uiWPWaitTimer; + uint m_uiPlayerCheckTimer; + eEscortState m_uiEscortState; + float MaxPlayerDistance; + + Quest m_pQuestForEscort; //generally passed in Start() when regular escort script. + + List WaypointList = new List(); + Escort_Waypoint GetCurrentWaypoint() + { + return WaypointList[WPIndex]; + } + int WPIndex; + + bool m_bIsActiveAttacker; //obsolete, determined by faction. + bool m_bIsRunning; //all creatures are walking by default (has flag MOVEMENTFLAG_WALK) + bool m_bCanInstantRespawn; //if creature should respawn instantly after escort over (if not, database respawntime are used) + bool m_bCanReturnToStart; //if creature can walk same path (loop) without despawn. Not for regular escort quests. + bool DespawnAtEnd; + bool DespawnAtFar; + bool ScriptWP; + bool HasImmuneToNPCFlags; + } + + public class Escort_Waypoint + { + public Escort_Waypoint(uint _id, float _x, float _y, float _z, uint _w) + { + id = _id; + x = _x; + y = _y; + z = _z; + WaitTimeMs = _w; + } + + public uint id; + public float x; + public float y; + public float z; + public uint WaitTimeMs; + } + + public enum eEscortState + { + None = 0x000, //nothing in progress + Escorting = 0x001, //escort are in progress + Returning = 0x002, //escort is returning after being in combat + Paused = 0x004 //will not proceed with waypoints before state is removed + } +} diff --git a/Game/AI/ScriptedAI/ScriptedFollowerAI.cs b/Game/AI/ScriptedAI/ScriptedFollowerAI.cs new file mode 100644 index 000000000..033eb76bf --- /dev/null +++ b/Game/AI/ScriptedAI/ScriptedFollowerAI.cs @@ -0,0 +1,400 @@ +/* + * Copyright (C) 2012-2017 CypherCore + * + * 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, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +using Framework.Constants; +using Game.Entities; +using Game.Groups; +using System; + +namespace Game.AI +{ + enum eFollowState + { + None = 0x000, + Inprogress = 0x001, //must always have this state for any follow + Returning = 0x002, //when returning to combat start after being in combat + Paused = 0x004, //disables following + Complete = 0x008, //follow is completed and may end + PreEvent = 0x010, //not implemented (allow pre event to run, before follow is initiated) + PostEvent = 0x020 //can be set at complete and allow post event to run + } + + class FollowerAI : ScriptedAI + { + public FollowerAI(Creature creature) : base(creature) + { + m_uiUpdateFollowTimer = 2500; + m_uiFollowState = eFollowState.None; + m_pQuestForFollow = null; + } + + public override void AttackStart(Unit who) + { + if (!who) + return; + + if (me.Attack(who, true)) + { + me.AddThreat(who, 0.0f); + me.SetInCombatWith(who); + who.SetInCombatWith(me); + + if (me.HasUnitState(UnitState.Follow)) + me.ClearUnitState(UnitState.Follow); + + if (IsCombatMovementAllowed()) + me.GetMotionMaster().MoveChase(who); + } + } + + //This part provides assistance to a player that are attacked by who, even if out of normal aggro range + //It will cause me to attack who that are attacking _any_ player (which has been confirmed may happen also on offi) + //The flag (type_flag) is unconfirmed, but used here for further research and is a good candidate. + bool AssistPlayerInCombatAgainst(Unit who) + { + if (!who || !who.GetVictim()) + return false; + + //experimental (unknown) flag not present + if (!me.GetCreatureTemplate().TypeFlags.HasAnyFlag(CreatureTypeFlags.CanAssist)) + return false; + + //not a player + if (!who.GetVictim().GetCharmerOrOwnerPlayerOrPlayerItself()) + return false; + + //never attack friendly + if (me.IsFriendlyTo(who)) + return false; + + //too far away and no free sight? + if (me.IsWithinDistInMap(who, 100.0f) && me.IsWithinLOSInMap(who)) + { + //already fighting someone? + if (!me.GetVictim()) + { + AttackStart(who); + return true; + } + else + { + who.SetInCombatWith(me); + me.AddThreat(who, 0.0f); + return true; + } + } + + return false; + } + + public override void MoveInLineOfSight(Unit who) + { + if (me.HasReactState(ReactStates.Aggressive) && !me.HasUnitState(UnitState.Stunned) && who.isTargetableForAttack() && who.isInAccessiblePlaceFor(me)) + { + if (HasFollowState(eFollowState.Inprogress) && AssistPlayerInCombatAgainst(who)) + return; + + if (!me.CanFly() && me.GetDistanceZ(who) > SharedConst.CreatureAttackRangeZ) + return; + + if (me.IsHostileTo(who)) + { + float fAttackRadius = me.GetAttackDistance(who); + if (me.IsWithinDistInMap(who, fAttackRadius) && me.IsWithinLOSInMap(who)) + { + if (!me.GetVictim()) + { + // Clear distracted state on combat + if (me.HasUnitState(UnitState.Distracted)) + { + me.ClearUnitState(UnitState.Distracted); + me.GetMotionMaster().Clear(); + } + + AttackStart(who); + } + else if (me.GetMap().IsDungeon()) + { + who.SetInCombatWith(me); + me.AddThreat(who, 0.0f); + } + } + } + } + } + + public override void JustDied(Unit killer) + { + if (!HasFollowState(eFollowState.Inprogress) || m_uiLeaderGUID.IsEmpty() || m_pQuestForFollow == null) + return; + + /// @todo need a better check for quests with time limit. + Player player = GetLeaderForFollower(); + if (player) + { + Group group = player.GetGroup(); + if (group) + { + for (GroupReference groupRef = group.GetFirstMember(); groupRef != null; groupRef = groupRef.next()) + { + Player member = groupRef.GetSource(); + if (member) + { + if (member.GetQuestStatus(m_pQuestForFollow.Id) == QuestStatus.Incomplete) + member.FailQuest(m_pQuestForFollow.Id); + } + } + } + else + { + if (player.GetQuestStatus(m_pQuestForFollow.Id) == QuestStatus.Incomplete) + player.FailQuest(m_pQuestForFollow.Id); + } + } + } + + public override void JustRespawned() + { + m_uiFollowState = eFollowState.None; + + if (!IsCombatMovementAllowed()) + SetCombatMovement(true); + + if (me.getFaction() != me.GetCreatureTemplate().Faction) + me.SetFaction(me.GetCreatureTemplate().Faction); + + Reset(); + } + + public override void EnterEvadeMode(EvadeReason why) + { + me.RemoveAllAuras(); + me.DeleteThreatList(); + me.CombatStop(true); + me.SetLootRecipient(null); + + if (HasFollowState(eFollowState.Inprogress)) + { + Log.outDebug(LogFilter.Scripts, "FollowerAI left combat, returning to CombatStartPosition."); + + if (me.GetMotionMaster().GetCurrentMovementGeneratorType() == MovementGeneratorType.Chase) + { + float fPosX, fPosY, fPosZ; + me.GetPosition(out fPosX, out fPosY, out fPosZ); + me.GetMotionMaster().MovePoint(0xFFFFFF, fPosX, fPosY, fPosZ); + } + } + else + { + if (me.GetMotionMaster().GetCurrentMovementGeneratorType() == MovementGeneratorType.Chase) + me.GetMotionMaster().MoveTargetedHome(); + } + + Reset(); + } + + public override void UpdateAI(uint uiDiff) + { + if (HasFollowState(eFollowState.Inprogress) && !me.GetVictim()) + { + if (m_uiUpdateFollowTimer <= uiDiff) + { + if (HasFollowState(eFollowState.Complete) && !HasFollowState(eFollowState.PostEvent)) + { + Log.outDebug(LogFilter.Scripts, "FollowerAI is set completed, despawns."); + me.DespawnOrUnsummon(); + return; + } + + bool bIsMaxRangeExceeded = true; + + Player player = GetLeaderForFollower(); + if (player) + { + if (HasFollowState(eFollowState.Returning)) + { + Log.outDebug(LogFilter.Scripts, "FollowerAI is returning to leader."); + + RemoveFollowState(eFollowState.Returning); + me.GetMotionMaster().MoveFollow(player, SharedConst.PetFollowDist, SharedConst.PetFollowAngle); + return; + } + + Group group = player.GetGroup(); + if (group) + { + for (GroupReference groupRef = group.GetFirstMember(); groupRef != null; groupRef = groupRef.next()) + { + Player member = groupRef.GetSource(); + if (member && me.IsWithinDistInMap(member, 100.0f)) + { + bIsMaxRangeExceeded = false; + break; + } + } + } + else + { + if (me.IsWithinDistInMap(player, 100.0f)) + bIsMaxRangeExceeded = false; + } + } + + if (bIsMaxRangeExceeded) + { + Log.outDebug(LogFilter.Scripts, "FollowerAI failed because player/group was to far away or not found"); + me.DespawnOrUnsummon(); + return; + } + + m_uiUpdateFollowTimer = 1000; + } + else + m_uiUpdateFollowTimer -= uiDiff; + } + + UpdateFollowerAI(uiDiff); + } + + void UpdateFollowerAI(uint diff) + { + if (!UpdateVictim()) + return; + + DoMeleeAttackIfReady(); + } + + public override void MovementInform(MovementGeneratorType motionType, uint pointId) + { + if (motionType != MovementGeneratorType.Point || !HasFollowState(eFollowState.Inprogress)) + return; + + if (pointId == 0xFFFFFF) + { + if (GetLeaderForFollower()) + { + if (!HasFollowState(eFollowState.Paused)) + AddFollowState(eFollowState.Returning); + } + else + me.DespawnOrUnsummon(); + } + } + + void StartFollow(Player player, uint factionForFollower = 0, Quest quest = null) + { + if (me.GetVictim()) + { + Log.outDebug(LogFilter.Scripts, "FollowerAI attempt to StartFollow while in combat."); + return; + } + + if (HasFollowState(eFollowState.Inprogress)) + { + Log.outError(LogFilter.Scenario, "FollowerAI attempt to StartFollow while already following."); + return; + } + + //set variables + m_uiLeaderGUID = player.GetGUID(); + + if (factionForFollower != 0) + me.SetFaction(factionForFollower); + + m_pQuestForFollow = quest; + + if (me.GetMotionMaster().GetCurrentMovementGeneratorType() == MovementGeneratorType.Waypoint) + { + me.GetMotionMaster().Clear(); + me.GetMotionMaster().MoveIdle(); + Log.outDebug(LogFilter.Scripts, "FollowerAI start with WAYPOINT_MOTION_TYPE, set to MoveIdle."); + } + + me.SetUInt64Value(UnitFields.NpcFlags, (uint)NPCFlags.None); + + AddFollowState(eFollowState.Inprogress); + + me.GetMotionMaster().MoveFollow(player, SharedConst.PetFollowDist, SharedConst.PetFollowAngle); + + Log.outDebug(LogFilter.Scripts, "FollowerAI start follow {0} ({1})", player.GetName(), m_uiLeaderGUID.ToString()); + } + + Player GetLeaderForFollower() + { + Player player = Global.ObjAccessor.GetPlayer(me, m_uiLeaderGUID); + if (player) + { + if (player.IsAlive()) + return player; + else + { + Group group = player.GetGroup(); + if (group) + { + for (GroupReference groupRef = group.GetFirstMember(); groupRef != null; groupRef = groupRef.next()) + { + Player member = groupRef.GetSource(); + + if (member && member.IsAlive() && me.IsWithinDistInMap(member, 100.0f)) + { + Log.outDebug(LogFilter.Scripts, "FollowerAI GetLeader changed and returned new leader."); + m_uiLeaderGUID = member.GetGUID(); + return member; + } + } + } + } + } + + Log.outDebug(LogFilter.Scripts, "FollowerAI GetLeader can not find suitable leader."); + return null; + } + + void SetFollowComplete(bool bWithEndEvent = false) + { + if (me.HasUnitState(UnitState.Follow)) + { + me.ClearUnitState(UnitState.Follow); + + me.StopMoving(); + me.GetMotionMaster().Clear(); + me.GetMotionMaster().MoveIdle(); + } + + if (bWithEndEvent) + AddFollowState(eFollowState.PostEvent); + else + { + if (HasFollowState(eFollowState.PostEvent)) + RemoveFollowState(eFollowState.PostEvent); + } + + AddFollowState(eFollowState.Complete); + } + + bool HasFollowState(eFollowState uiFollowState) { return (m_uiFollowState & uiFollowState) != 0; } + + void AddFollowState(eFollowState uiFollowState) { m_uiFollowState |= uiFollowState; } + void RemoveFollowState(eFollowState uiFollowState) { m_uiFollowState &= ~uiFollowState; } + + ObjectGuid m_uiLeaderGUID; + uint m_uiUpdateFollowTimer; + eFollowState m_uiFollowState; + + Quest m_pQuestForFollow; //normally we have a quest + } +} diff --git a/Game/AI/SmartScripts/SmartAI.cs b/Game/AI/SmartScripts/SmartAI.cs new file mode 100644 index 000000000..48b5ee263 --- /dev/null +++ b/Game/AI/SmartScripts/SmartAI.cs @@ -0,0 +1,1114 @@ +/* + * Copyright (C) 2012-2017 CypherCore + * + * 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, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +using Framework.Constants; +using Game.DataStorage; +using Game.Entities; +using Game.Groups; +using Game.Scripting; +using Game.Spells; +using System; +using System.Collections.Generic; +using System.Linq; + +namespace Game.AI +{ + public class SmartAI : CreatureAI + { + const int SMART_ESCORT_MAX_PLAYER_DIST = 50; + const int SMART_MAX_AID_DIST = SMART_ESCORT_MAX_PLAYER_DIST / 2; + + public SmartAI(Creature creature) : base(creature) + { + // copy script to local (protection for table reload) + + mWayPoints = null; + mEscortState = SmartEscortState.None; + mCurrentWPID = 0;//first wp id is 1 !! + mWPReached = false; + mWPPauseTimer = 0; + mLastWP = null; + + mCanRepeatPath = false; + + // spawn in run mode + creature.SetWalk(false); + mRun = false; + + mLastOOCPos = creature.GetPosition(); + + mCanAutoAttack = true; + mCanCombatMove = true; + + mForcedPaused = false; + mLastWPIDReached = 0; + + mEscortQuestID = 0; + + mDespawnTime = 0; + mDespawnState = 0; + + mEscortInvokerCheckTimer = 1000; + mFollowGuid = ObjectGuid.Empty; + mFollowDist = 0; + mFollowAngle = 0; + mFollowCredit = 0; + mFollowArrivedEntry = 0; + mFollowCreditType = 0; + mInvincibilityHpLevel = 0; + } + + bool IsAIControlled() + { + if (me.IsControlledByPlayer()) + return false; + if (mIsCharmed) + return false; + return true; + } + + void UpdateDespawn(uint diff) + { + if (mDespawnState <= 1 || mDespawnState > 3) + return; + + if (mDespawnTime < diff) + { + if (mDespawnState == 2) + { + me.SetVisible(false); + mDespawnTime = 5000; + mDespawnState++; + } + else + me.DespawnOrUnsummon(); + } + else + mDespawnTime -= diff; + } + + public override void Reset() + { + if (!HasEscortState(SmartEscortState.Escorting))//dont mess up escort movement after combat + SetRun(mRun); + GetScript().OnReset(); + } + + SmartPath GetNextWayPoint() + { + if (mWayPoints == null || mWayPoints.Empty()) + return null; + + mCurrentWPID++; + var path = mWayPoints.LookupByKey(mCurrentWPID); + if (path != null) + { + mLastWP = path; + if (mLastWP.id != mCurrentWPID) + { + Log.outError(LogFilter.Server, "SmartAI.GetNextWayPoint: Got not expected waypoint id {0}, expected {1}", mLastWP.id, mCurrentWPID); + } + return path; + } + return null; + } + + public void StartPath(bool run = false, uint path = 0, bool repeat = false, Unit invoker = null) + { + if (me.IsInCombat())// no wp movement in combat + { + Log.outError(LogFilter.Server, "SmartAI.StartPath: Creature entry {0} wanted to start waypoint movement while in combat, ignoring.", me.GetEntry()); + return; + } + if (HasEscortState(SmartEscortState.Escorting)) + StopPath(); + + if (path != 0) + if (!LoadPath(path)) + return; + + if (mWayPoints == null || mWayPoints.Empty()) + return; + + AddEscortState(SmartEscortState.Escorting); + mCanRepeatPath = repeat; + + SetRun(run); + + SmartPath wp = GetNextWayPoint(); + if (wp != null) + { + mLastOOCPos = me.GetPosition(); + me.GetMotionMaster().MovePoint(wp.id, wp.x, wp.y, wp.z); + GetScript().ProcessEventsFor(SmartEvents.WaypointStart, null, wp.id, GetScript().GetPathId()); + } + } + + bool LoadPath(uint entry) + { + if (HasEscortState(SmartEscortState.Escorting)) + return false; + + mWayPoints = Global.SmartAIMgr.GetPath(entry); + if (mWayPoints == null) + { + GetScript().SetPathId(0); + return false; + } + GetScript().SetPathId(entry); + return true; + } + + public void PausePath(uint delay, bool forced) + { + if (!HasEscortState(SmartEscortState.Escorting)) + return; + if (HasEscortState(SmartEscortState.Paused)) + { + Log.outError(LogFilter.Server, "SmartAI.PausePath: Creature entry {0} wanted to pause waypoint movement while already paused, ignoring.", me.GetEntry()); + return; + } + mForcedPaused = forced; + mLastOOCPos = me.GetPosition(); + AddEscortState(SmartEscortState.Paused); + mWPPauseTimer = delay; + if (forced) + { + SetRun(mRun); + me.StopMoving();//force stop + me.GetMotionMaster().MoveIdle();//force stop + } + GetScript().ProcessEventsFor(SmartEvents.WaypointPaused, null, mLastWP.id, GetScript().GetPathId()); + } + + public void StopPath(uint DespawnTime = 0, uint quest = 0, bool fail = false) + { + if (!HasEscortState(SmartEscortState.Escorting)) + return; + + if (quest != 0) + mEscortQuestID = quest; + SetDespawnTime(DespawnTime); + mDespawnTime = DespawnTime; + + mLastOOCPos = me.GetPosition(); + me.StopMoving();//force stop + me.GetMotionMaster().MoveIdle(); + GetScript().ProcessEventsFor(SmartEvents.WaypointStopped, null, mLastWP.id, GetScript().GetPathId()); + EndPath(fail); + } + + public void EndPath(bool fail = false) + { + GetScript().ProcessEventsFor(SmartEvents.WaypointEnded, null, mLastWP.id, GetScript().GetPathId()); + + RemoveEscortState(SmartEscortState.Escorting | SmartEscortState.Paused | SmartEscortState.Returning); + mWayPoints = null; + mCurrentWPID = 0; + mWPPauseTimer = 0; + mLastWP = null; + + if (mCanRepeatPath) + { + if (IsAIControlled()) + StartPath(mRun, GetScript().GetPathId(), true); + } + else + GetScript().SetPathId(0); + + List targets = GetScript().GetTargetList(SharedConst.SmartEscortTargets); + if (targets != null && mEscortQuestID != 0) + { + if (targets.Count == 1 && GetScript().IsPlayer(targets.First())) + { + Player player = targets.First().ToPlayer(); + if (!fail && player.IsAtGroupRewardDistance(me) && player.GetCorpse() == null) + player.GroupEventHappens(mEscortQuestID, me); + + if (fail && player.GetQuestStatus(mEscortQuestID) == QuestStatus.Incomplete) + player.FailQuest(mEscortQuestID); + + Group group = player.GetGroup(); + if (group) + { + for (GroupReference groupRef = group.GetFirstMember(); groupRef != null; groupRef = groupRef.next()) + { + Player groupGuy = groupRef.GetSource(); + + if (!fail && groupGuy.IsAtGroupRewardDistance(me) && !groupGuy.GetCorpse()) + groupGuy.AreaExploredOrEventHappens(mEscortQuestID); + if (fail && groupGuy.GetQuestStatus(mEscortQuestID) == QuestStatus.Incomplete) + groupGuy.FailQuest(mEscortQuestID); + } + } + } + else + { + foreach (var obj in targets) + { + if (GetScript().IsPlayer(obj)) + { + Player player = obj.ToPlayer(); + if (!fail && player.IsAtGroupRewardDistance(me) && player.GetCorpse() == null) + player.AreaExploredOrEventHappens(mEscortQuestID); + if (fail && player.GetQuestStatus(mEscortQuestID) == QuestStatus.Incomplete) + player.FailQuest(mEscortQuestID); + } + } + } + } + if (mDespawnState == 1) + StartDespawn(); + } + + public void ResumePath() + { + SetRun(mRun); + if (mLastWP != null) + me.GetMotionMaster().MovePoint(mLastWP.id, mLastWP.x, mLastWP.y, mLastWP.z); + } + + void ReturnToLastOOCPos() + { + if (!IsAIControlled()) + return; + + SetRun(mRun); + me.GetMotionMaster().MovePoint(EventId.SmartEscortLastOCCPoint, mLastOOCPos); + } + + void UpdatePath(uint diff) + { + if (!HasEscortState(SmartEscortState.Escorting)) + return; + if (mEscortInvokerCheckTimer < diff) + { + if (!IsEscortInvokerInRange()) + { + StopPath(mDespawnTime, mEscortQuestID, true); + } + mEscortInvokerCheckTimer = 1000; + } + else mEscortInvokerCheckTimer -= diff; + // handle pause + if (HasEscortState(SmartEscortState.Paused)) + { + if (mWPPauseTimer < diff) + { + if (!me.IsInCombat() && !HasEscortState(SmartEscortState.Returning) && (mWPReached || mLastWPIDReached == EventId.SmartEscortLastOCCPoint || mForcedPaused)) + { + GetScript().ProcessEventsFor(SmartEvents.WaypointResumed, null, mLastWP.id, GetScript().GetPathId()); + RemoveEscortState(SmartEscortState.Paused); + if (mForcedPaused)// if paused between 2 wps resend movement + { + ResumePath(); + mWPReached = false; + mForcedPaused = false; + } + if (mLastWPIDReached == EventId.SmartEscortLastOCCPoint) + mWPReached = true; + } + mWPPauseTimer = 0; + } + else + { + mWPPauseTimer -= diff; + } + } + if (HasEscortState(SmartEscortState.Returning)) + { + if (mWPReached)//reached OOC WP + { + RemoveEscortState(SmartEscortState.Returning); + if (!HasEscortState(SmartEscortState.Paused)) + ResumePath(); + mWPReached = false; + } + } + if (me.IsInCombat() || HasEscortState(SmartEscortState.Paused | SmartEscortState.Returning)) + return; + // handle next wp + if (mWPReached)//reached WP + { + mWPReached = false; + SmartPath wp = GetNextWayPoint(); + if (mCurrentWPID == GetWPCount()) + { + EndPath(); + } + else if (wp != null) + { + SetRun(mRun); + me.GetMotionMaster().MovePoint(wp.id, wp.x, wp.y, wp.z); + } + } + } + + public override void UpdateAI(uint diff) + { + GetScript().OnUpdate(diff); + UpdatePath(diff); + UpdateDespawn(diff); + + UpdateFollow(diff); + + if (!IsAIControlled()) + return; + + if (!UpdateVictim()) + return; + + if (mCanAutoAttack) + DoMeleeAttackIfReady(); + } + + bool IsEscortInvokerInRange() + { + var targets = GetScript().GetTargetList(SharedConst.SmartEscortTargets); + if (targets != null) + { + if (targets.Count == 1 && GetScript().IsPlayer(targets.First())) + { + Player player = targets.First().ToPlayer(); + if (me.GetDistance(player) <= SMART_ESCORT_MAX_PLAYER_DIST) + return true; + + Group group = player.GetGroup(); + if (group) + { + for (GroupReference groupRef = group.GetFirstMember(); groupRef != null; groupRef = groupRef.next()) + { + Player groupGuy = groupRef.GetSource(); + + if (me.GetDistance(groupGuy) <= SMART_ESCORT_MAX_PLAYER_DIST) + return true; + } + } + } + else + { + foreach (var obj in targets) + { + if (GetScript().IsPlayer(obj)) + { + if (me.GetDistance(obj.ToPlayer()) <= SMART_ESCORT_MAX_PLAYER_DIST) + return true; + } + } + } + } + return true;//escort targets were not set, ignore range check + } + + void MovepointReached(uint id) + { + if (id != EventId.SmartEscortLastOCCPoint && mLastWPIDReached != id) + GetScript().ProcessEventsFor(SmartEvents.WaypointReached, null, id); + + mLastWPIDReached = id; + mWPReached = true; + } + + public override void MovementInform(MovementGeneratorType MovementType, uint Data) + { + if ((MovementType == MovementGeneratorType.Point && Data == EventId.SmartEscortLastOCCPoint) || MovementType == MovementGeneratorType.Follow) + me.ClearUnitState(UnitState.Evade); + + GetScript().ProcessEventsFor(SmartEvents.Movementinform, null, (uint)MovementType, Data); + if (MovementType != MovementGeneratorType.Point || !HasEscortState(SmartEscortState.Escorting)) + return; + MovepointReached(Data); + } + + void RemoveAuras() + { + //fixme: duplicated logic in CreatureAI._EnterEvadeMode (could use RemoveAllAurasExceptType) + foreach (var pair in me.GetAppliedAuras()) + { + Aura aura = pair.Value.GetBase(); + if (!aura.IsPassive() && !aura.HasEffectType(AuraType.ControlVehicle) && !aura.HasEffectType(AuraType.CloneCaster) && aura.GetCasterGUID() != me.GetGUID()) + me.RemoveAura(pair); + } + } + + public override void EnterEvadeMode(EvadeReason why = EvadeReason.Other) + { + if (mEvadeDisabled) + { + GetScript().ProcessEventsFor(SmartEvents.Evade); + return; + } + + if (!_EnterEvadeMode()) + return; + + me.AddUnitState(UnitState.Evade); + + GetScript().ProcessEventsFor(SmartEvents.Evade);//must be after aura clear so we can cast spells from db + + SetRun(mRun); + Unit target = !mFollowGuid.IsEmpty() ? Global.ObjAccessor.GetUnit(me, mFollowGuid) : null; + Unit owner = me.GetCharmerOrOwner(); + if (HasEscortState(SmartEscortState.Escorting)) + { + AddEscortState(SmartEscortState.Returning); + ReturnToLastOOCPos(); + } + else if (target) + { + me.GetMotionMaster().MoveFollow(target, mFollowDist, mFollowAngle); + // evade is not cleared in MoveFollow, so we can't keep it + me.ClearUnitState(UnitState.Evade); + } + else if (owner) + { + me.GetMotionMaster().MoveFollow(owner, SharedConst.PetFollowDist, SharedConst.PetFollowAngle); + me.ClearUnitState(UnitState.Evade); + } + else + me.GetMotionMaster().MoveTargetedHome(); + + Reset(); + } + + public override void MoveInLineOfSight(Unit who) + { + if (who == null) + return; + + GetScript().OnMoveInLineOfSight(who); + + if (!IsAIControlled()) + return; + + if (AssistPlayerInCombatAgainst(who)) + return; + + base.MoveInLineOfSight(who); + } + + public override bool CanAIAttack(Unit victim) + { + return !me.HasReactState(ReactStates.Passive); + } + + bool AssistPlayerInCombatAgainst(Unit who) + { + if (me.HasReactState(ReactStates.Passive) || !IsAIControlled()) + return false; + + if (who == null || who.GetVictim() == null) + return false; + + //experimental (unknown) flag not present + if (!me.GetCreatureTemplate().TypeFlags.HasAnyFlag(CreatureTypeFlags.CanAssist)) + return false; + + //not a player + if (who.GetVictim().GetCharmerOrOwnerPlayerOrPlayerItself() == null) + return false; + + //never attack friendly + if (me.IsFriendlyTo(who)) + return false; + + //too far away and no free sight? + if (me.IsWithinDistInMap(who, SMART_MAX_AID_DIST) && me.IsWithinLOSInMap(who)) + { + //already fighting someone? + if (me.GetVictim() == null) + { + AttackStart(who); + return true; + } + else + { + who.SetInCombatWith(me); + me.AddThreat(who, 0.0f); + return true; + } + } + + return false; + } + + public override void JustRespawned() + { + mDespawnTime = 0; + mDespawnState = 0; + mEscortState = SmartEscortState.None; + me.SetVisible(true); + if (me.getFaction() != me.GetCreatureTemplate().Faction) + me.RestoreFaction(); + mJustReset = true; + JustReachedHome(); + GetScript().ProcessEventsFor(SmartEvents.Respawn); + mFollowGuid.Clear();//do not reset follower on Reset(), we need it after combat evade + mFollowDist = 0; + mFollowAngle = 0; + mFollowCredit = 0; + mFollowArrivedTimer = 1000; + mFollowArrivedEntry = 0; + mFollowCreditType = 0; + } + + public override void JustReachedHome() + { + GetScript().OnReset(); + if (!mJustReset) + { + GetScript().ProcessEventsFor(SmartEvents.ReachedHome); + + if (!UpdateVictim() && me.GetMotionMaster().GetCurrentMovementGeneratorType() == MovementGeneratorType.Idle && me.GetWaypointPath() != 0) + me.GetMotionMaster().MovePath(me.GetWaypointPath(), true); + } + mJustReset = false; + } + + public override void EnterCombat(Unit victim) + { + if (IsAIControlled()) + me.InterruptNonMeleeSpells(false); // must be before ProcessEvents + + GetScript().ProcessEventsFor(SmartEvents.Aggro, victim); + + if (!IsAIControlled()) + return; + + mLastOOCPos = me.GetPosition(); + SetRun(mRun); + if (me.GetMotionMaster().GetMotionSlotType(MovementSlot.Active) == MovementGeneratorType.Point) + me.GetMotionMaster().MovementExpired(); + } + + public override void JustDied(Unit killer) + { + GetScript().ProcessEventsFor(SmartEvents.Death, killer); + if (HasEscortState(SmartEscortState.Escorting)) + { + EndPath(true); + me.StopMoving();//force stop + me.GetMotionMaster().MoveIdle(); + } + } + + public override void KilledUnit(Unit victim) + { + GetScript().ProcessEventsFor(SmartEvents.Kill, victim); + } + + public override void JustSummoned(Creature summon) + { + GetScript().ProcessEventsFor(SmartEvents.SummonedUnit, summon); + } + + public override void AttackStart(Unit who) + { + if (who != null && me.Attack(who, true)) + { + SetRun(mRun); + if (me.GetMotionMaster().GetCurrentMovementGeneratorType() == MovementGeneratorType.Point) + me.GetMotionMaster().MovementExpired(); + + if (mCanCombatMove) + me.GetMotionMaster().MoveChase(who); + + mLastOOCPos = me.GetPosition(); + } + } + + public override void SpellHit(Unit caster, SpellInfo spell) + { + GetScript().ProcessEventsFor(SmartEvents.SpellHit, caster, 0, 0, false, spell); + } + + public override void SpellHitTarget(Unit target, SpellInfo spell) + { + GetScript().ProcessEventsFor(SmartEvents.SpellhitTarget, target, 0, 0, false, spell); + } + + public override void DamageTaken(Unit attacker, ref uint damage) + { + GetScript().ProcessEventsFor(SmartEvents.Damaged, attacker, damage); + + if (!IsAIControlled()) // don't allow players to use unkillable units + return; + + if (mInvincibilityHpLevel != 0 && (damage >= me.GetHealth() - mInvincibilityHpLevel)) + damage = (uint)(me.GetHealth() - mInvincibilityHpLevel); // damage should not be nullified, because of player damage req. + } + + public override void HealReceived(Unit by, uint addhealth) + { + GetScript().ProcessEventsFor(SmartEvents.ReceiveHeal, by, addhealth); + } + + public override void ReceiveEmote(Player player, TextEmotes emoteId) + { + GetScript().ProcessEventsFor(SmartEvents.ReceiveEmote, player, (uint)emoteId); + } + + public override void IsSummonedBy(Unit summoner) + { + GetScript().ProcessEventsFor(SmartEvents.JustSummoned, summoner); + } + + public override void DamageDealt(Unit victim, ref uint damage, DamageEffectType damageType) + { + GetScript().ProcessEventsFor(SmartEvents.DamagedTarget, victim, damage); + } + + public override void SummonedCreatureDespawn(Creature summon) + { + GetScript().ProcessEventsFor(SmartEvents.SummonDespawned, summon); + } + + public override void CorpseRemoved(long respawnDelay) + { + GetScript().ProcessEventsFor(SmartEvents.CorpseRemoved, null, (uint)respawnDelay); + } + + public override void PassengerBoarded(Unit passenger, sbyte seatId, bool apply) + { + GetScript().ProcessEventsFor(apply ? SmartEvents.PassengerBoarded : SmartEvents.PassengerRemoved, passenger, (uint)seatId, 0, apply); + } + + public override void InitializeAI() + { + mScript.OnInitialize(me); + if (!me.IsDead()) + mJustReset = true; + JustReachedHome(); + GetScript().ProcessEventsFor(SmartEvents.Respawn); + } + + public override void OnCharmed(bool apply) + { + if (apply) // do this before we change charmed state, as charmed state might prevent these things from processing + { + if (HasEscortState(SmartEscortState.Escorting | SmartEscortState.Paused | SmartEscortState.Returning)) + EndPath(true); + me.StopMoving(); + } + mIsCharmed = apply; + + if (!apply && !me.IsInEvadeMode()) + { + if (mCanRepeatPath) + StartPath(mRun, GetScript().GetPathId(), true); + else + me.SetWalk(!mRun); + + Unit charmer = me.GetCharmer(); + if (charmer) + AttackStart(charmer); + } + + GetScript().ProcessEventsFor(SmartEvents.Charmed, null, 0, 0, apply); + } + + public override void DoAction(int param) + { + GetScript().ProcessEventsFor(SmartEvents.ActionDone, null, (uint)param); + } + + public override uint GetData(uint id) + { + return 0; + } + + public override void SetData(uint id, uint value) + { + GetScript().ProcessEventsFor(SmartEvents.DataSet, null, id, value); + } + + public override void SetGUID(ObjectGuid guid, int id) { } + + public override ObjectGuid GetGUID(int id) + { + return ObjectGuid.Empty; + } + + public void SetRun(bool run) + { + me.SetWalk(!run); + mRun = run; + } + + public void SetFly(bool fly) + { + me.SetDisableGravity(fly); + } + + public void SetSwim(bool swim) + { + me.SetSwim(swim); + } + + public void SetEvadeDisabled(bool disable) + { + mEvadeDisabled = disable; + } + + public override void sGossipHello(Player player) + { + GetScript().ProcessEventsFor(SmartEvents.GossipHello, player); + } + + public override void sGossipSelect(Player player, uint menuId, uint gossipListId) + { + GetScript().ProcessEventsFor(SmartEvents.GossipSelect, player, menuId, gossipListId); + } + + public override void sGossipSelectCode(Player player, uint menuId, uint gossipListId, string code) { } + + public override void sQuestAccept(Player player, Quest quest) + { + GetScript().ProcessEventsFor(SmartEvents.AcceptedQuest, player, quest.Id); + } + + public override void sQuestReward(Player player, Quest quest, uint opt) + { + GetScript().ProcessEventsFor(SmartEvents.RewardQuest, player, quest.Id, opt); + } + + public override bool sOnDummyEffect(Unit caster, uint spellId, int effIndex) + { + GetScript().ProcessEventsFor(SmartEvents.DummyEffect, caster, spellId, (uint)effIndex); + return true; + } + + public void SetCombatMove(bool on) + { + if (mCanCombatMove == on) + return; + + mCanCombatMove = on; + if (!IsAIControlled()) + return; + + if (!HasEscortState(SmartEscortState.Escorting)) + { + if (on && me.GetVictim() != null) + { + if (me.GetMotionMaster().GetCurrentMovementGeneratorType() == MovementGeneratorType.Idle) + { + SetRun(mRun); + me.GetMotionMaster().MoveChase(me.GetVictim()); + me.CastStop(); + } + } + else + { + if (me.HasUnitState(UnitState.ConfusedMove | UnitState.FleeingMove)) + return; + + me.GetMotionMaster().MovementExpired(); + me.GetMotionMaster().Clear(true); + me.StopMoving(); + me.GetMotionMaster().MoveIdle(); + } + } + } + + public void SetFollow(Unit target, float dist, float angle, uint credit, uint end, uint creditType) + { + if (target == null) + return; + + mFollowGuid = target.GetGUID(); + mFollowDist = dist >= 0.0f ? dist : SharedConst.PetFollowDist; + mFollowAngle = angle >= 0.0f ? angle : me.GetFollowAngle(); + mFollowArrivedTimer = 1000; + mFollowCredit = credit; + mFollowArrivedEntry = end; + mFollowCreditType = creditType; + SetRun(mRun); + me.GetMotionMaster().MoveFollow(target, mFollowDist, mFollowAngle); + } + + public void SetScript9(SmartScriptHolder e, uint entry, Unit invoker) + { + if (invoker != null) + GetScript().mLastInvoker = invoker.GetGUID(); + GetScript().SetScript9(e, entry); + } + + public override void sOnGameEvent(bool start, ushort eventId) + { + GetScript().ProcessEventsFor(start ? SmartEvents.GameEventStart : SmartEvents.GameEventEnd, null, eventId); + } + + public override void OnSpellClick(Unit clicker, ref bool result) + { + if (!result) + return; + + GetScript().ProcessEventsFor(SmartEvents.OnSpellclick, clicker); + } + + public void UpdateFollow(uint diff) + { + if (!mFollowGuid.IsEmpty()) + { + if (mFollowArrivedTimer < diff) + { + if (me.FindNearestCreature(mFollowArrivedEntry, SharedConst.InteractionDistance, true) != null) + { + Player player = Global.ObjAccessor.GetPlayer(me, mFollowGuid); + if (player != null) + { + if (mFollowCreditType == 0) + player.RewardPlayerAndGroupAtEvent(mFollowCredit, me); + else + player.GroupEventHappens(mFollowCredit, me); + } + mFollowGuid.Clear(); + mFollowDist = 0; + mFollowAngle = 0; + mFollowCredit = 0; + mFollowArrivedTimer = 1000; + mFollowArrivedEntry = 0; + mFollowCreditType = 0; + SetDespawnTime(5000); + me.StopMoving(); + me.GetMotionMaster().MoveIdle(); + StartDespawn(); + GetScript().ProcessEventsFor(SmartEvents.FollowCompleted); + return; + } + mFollowArrivedTimer = 1000; + } + else mFollowArrivedTimer -= diff; + } + } + + bool HasEscortState(SmartEscortState uiEscortState) { return mEscortState.HasAnyFlag(uiEscortState); } + void AddEscortState(SmartEscortState uiEscortState) { mEscortState |= uiEscortState; } + void RemoveEscortState(SmartEscortState uiEscortState) { mEscortState &= ~uiEscortState; } + public void SetAutoAttack(bool on) { mCanAutoAttack = on; } + public bool CanCombatMove() { return mCanCombatMove; } + + public SmartScript GetScript() { return mScript; } + + public void SetInvincibilityHpLevel(uint level) { mInvincibilityHpLevel = level; } + + public void SetDespawnTime(uint t) + { + mDespawnTime = t; + mDespawnState = (uint)(t != 0 ? 1 : 0); + } + + public void StartDespawn() { mDespawnState = 2; } + + int GetWPCount() { return mWayPoints != null ? mWayPoints.Count : 0; } + + bool mIsCharmed; + uint mFollowCreditType; + uint mFollowArrivedTimer; + uint mFollowCredit; + uint mFollowArrivedEntry; + ObjectGuid mFollowGuid; + float mFollowDist; + float mFollowAngle; + + SmartScript mScript = new SmartScript(); + Dictionary mWayPoints; + SmartEscortState mEscortState; + uint mCurrentWPID; + uint mLastWPIDReached; + bool mWPReached; + uint mWPPauseTimer; + SmartPath mLastWP; + Position mLastOOCPos;//set on enter combat + + bool mCanRepeatPath; + bool mRun; + bool mEvadeDisabled; + bool mCanAutoAttack; + bool mCanCombatMove; + bool mForcedPaused; + uint mInvincibilityHpLevel; + + uint mDespawnTime; + uint mDespawnState; + + public uint mEscortQuestID; + + uint mEscortInvokerCheckTimer; + bool mJustReset; + } + + public class SmartGameObjectAI : GameObjectAI + { + public SmartGameObjectAI(GameObject g) + : base(g) + { + go = g; + mScript = new SmartScript(); + } + + public override void UpdateAI(uint diff) + { + GetScript().OnUpdate(diff); + } + + public override void InitializeAI() + { + GetScript().OnInitialize(go); + GetScript().ProcessEventsFor(SmartEvents.Respawn); + } + + public override void Reset() + { + GetScript().OnReset(); + } + + public override bool GossipHello(Player player, bool isUse) + { + Log.outDebug(LogFilter.ScriptsAi, "SmartGameObjectAI.GossipHello"); + GetScript().ProcessEventsFor(SmartEvents.GossipHello, player, 0, 0, false, null, go); + return false; + } + + public override bool GossipSelect(Player player, uint sender, uint action) + { + GetScript().ProcessEventsFor(SmartEvents.GossipSelect, player, sender, action, false, null, go); + return false; + } + + public override bool GossipSelectCode(Player player, uint sender, uint action, string code) + { + return false; + } + + public override bool QuestAccept(Player player, Quest quest) + { + GetScript().ProcessEventsFor(SmartEvents.AcceptedQuest, player, quest.Id, 0, false, null, go); + return false; + } + + public override bool QuestReward(Player player, Quest quest, uint opt) + { + GetScript().ProcessEventsFor(SmartEvents.RewardQuest, player, quest.Id, opt, false, null, go); + return false; + } + + public override uint GetDialogStatus(Player player) + { + return 100; + } + + public override void Destroyed(Player player, uint eventId) + { + GetScript().ProcessEventsFor(SmartEvents.Death, player, eventId, 0, false, null, go); + } + + public override void SetData(uint id, uint value) + { + GetScript().ProcessEventsFor(SmartEvents.DataSet, null, id, value); + } + + public void SetScript9(SmartScriptHolder e, uint entry, Unit invoker) + { + if (invoker != null) + GetScript().mLastInvoker = invoker.GetGUID(); + GetScript().SetScript9(e, entry); + } + + public override void OnGameEvent(bool start, ushort eventId) + { + GetScript().ProcessEventsFor(start ? SmartEvents.GameEventStart : SmartEvents.GameEventEnd, null, eventId); + } + + public override void OnStateChanged(uint state, Unit unit) + { + GetScript().ProcessEventsFor(SmartEvents.GoStateChanged, unit, state); + } + + public override void EventInform(uint eventId) + { + GetScript().ProcessEventsFor(SmartEvents.GoEventInform, null, eventId); + } + + public SmartScript GetScript() { return mScript; } + + + new GameObject go; + SmartScript mScript; + } + + [Script] + class SmartTrigger : AreaTriggerScript + { + public SmartTrigger() : base("SmartTrigger") { } + + public override bool OnTrigger(Player player, AreaTriggerRecord trigger, bool entered) + { + if (!player.IsAlive()) + return false; + + Log.outDebug(LogFilter.ScriptsAi, "AreaTrigger {0} is using SmartTrigger script", trigger.Id); + SmartScript script = new SmartScript(); + script.OnInitialize(null, trigger); + script.ProcessEventsFor(SmartEvents.AreatriggerOntrigger, player, trigger.Id); + return true; + } + } + + [Script] + class SmartScene : SceneScript + { + public SmartScene() : base("SmartScene") { } + + public override void OnSceneStart(Player player, uint sceneInstanceID, SceneTemplate sceneTemplate) + { + SmartScript smartScript = new SmartScript(); + smartScript.OnInitialize(null, null, sceneTemplate); + smartScript.ProcessEventsFor(SmartEvents.SceneStart, player); + } + + public override void OnSceneTriggerEvent(Player player, uint sceneInstanceID, SceneTemplate sceneTemplate, string triggerName) + { + SmartScript smartScript = new SmartScript(); + smartScript.OnInitialize(null, null, sceneTemplate); + smartScript.ProcessEventsFor(SmartEvents.SceneTrigger, player, 0, 0, false, null, null, triggerName); + } + + public override void OnSceneCancel(Player player, uint sceneInstanceID, SceneTemplate sceneTemplate) + { + SmartScript smartScript = new SmartScript(); + smartScript.OnInitialize(null, null, sceneTemplate); + smartScript.ProcessEventsFor(SmartEvents.SceneCancel, player); + } + + public override void OnSceneComplete(Player player, uint sceneInstanceID, SceneTemplate sceneTemplate) + { + SmartScript smartScript = new SmartScript(); + smartScript.OnInitialize(null, null, sceneTemplate); + smartScript.ProcessEventsFor(SmartEvents.SceneComplete, player); + } + } + + public enum SmartEscortState + { + None = 0x00, //nothing in progress + Escorting = 0x01, //escort is in progress + Returning = 0x02, //escort is returning after being in combat + Paused = 0x04 //will not proceed with waypoints before state is removed + } +} diff --git a/Game/AI/SmartScripts/SmartAIManager.cs b/Game/AI/SmartScripts/SmartAIManager.cs new file mode 100644 index 000000000..885efc1d2 --- /dev/null +++ b/Game/AI/SmartScripts/SmartAIManager.cs @@ -0,0 +1,2776 @@ +/* + * Copyright (C) 2012-2017 CypherCore + * + * 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, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +using Framework.Constants; +using Framework.Database; +using Game.DataStorage; +using Game.Entities; +using Game.Spells; +using System; +using System.Collections.Generic; +using System.Runtime.InteropServices; + +namespace Game.AI +{ + public class SmartAIManager : Singleton + { + SmartAIManager() { } + + public void LoadFromDB() + { + uint oldMSTime = Time.GetMSTime(); + + PreparedStatement stmt = DB.World.GetPreparedStatement(WorldStatements.SEL_SMART_SCRIPTS); + SQLResult result = DB.World.Query(stmt); + if (result.IsEmpty()) + { + Log.outInfo(LogFilter.ServerLoading, "Loaded 0 SmartAI scripts. DB table `smartai_scripts` is empty."); + return; + } + + int count = 0; + do + { + SmartScriptHolder temp = new SmartScriptHolder(); + + temp.entryOrGuid = result.Read(0); + SmartScriptType source_type = (SmartScriptType)result.Read(1); + if (source_type >= SmartScriptType.Max) + { + Log.outError(LogFilter.Sql, "SmartAIMgr.LoadSmartAI: invalid source_type ({0}), skipped loading.", source_type); + continue; + } + if (temp.entryOrGuid >= 0) + { + switch (source_type) + { + case SmartScriptType.Creature: + if (Global.ObjectMgr.GetCreatureTemplate((uint)temp.entryOrGuid) == null) + { + Log.outError(LogFilter.Sql, "SmartAIMgr.LoadSmartAI: Creature entry ({0}) does not exist, skipped loading.", temp.entryOrGuid); + continue; + } + break; + + case SmartScriptType.GameObject: + { + if (Global.ObjectMgr.GetGameObjectTemplate((uint)temp.entryOrGuid) == null) + { + Log.outError(LogFilter.Sql, "SmartAIMgr.LoadSmartAI: GameObject entry ({0}) does not exist, skipped loading.", temp.entryOrGuid); + continue; + } + break; + } + case SmartScriptType.AreaTrigger: + { + if (CliDB.AreaTableStorage.LookupByKey((uint)temp.entryOrGuid) == null) + { + Log.outError(LogFilter.Sql, "SmartAIMgr.LoadSmartAI: AreaTrigger entry ({0}) does not exist, skipped loading.", temp.entryOrGuid); + continue; + } + break; + } + case SmartScriptType.Scene: + { + if (Global.ObjectMgr.GetSceneTemplate((uint)temp.entryOrGuid) == null) + { + Log.outError(LogFilter.Sql, "SmartAIMgr.LoadSmartAIFromDB: Scene id ({0}) does not exist, skipped loading.", temp.entryOrGuid); + continue; + } + break; + } + case SmartScriptType.TimedActionlist: + break;//nothing to check, really + default: + Log.outError(LogFilter.Sql, "SmartAIMgr.LoadSmartAIFromDB: not yet implemented source_type {0}", source_type); + continue; + } + } + else + { + if (Global.ObjectMgr.GetCreatureData((uint)Math.Abs(temp.entryOrGuid)) == null) + { + Log.outError(LogFilter.Sql, "SmartAIMgr.LoadSmartAI: Creature guid ({0}) does not exist, skipped loading.", Math.Abs(temp.entryOrGuid)); + continue; + } + } + + temp.source_type = source_type; + temp.event_id = result.Read(2); + temp.link = result.Read(3); + temp.Event.type = (SmartEvents)result.Read(4); + temp.Event.event_phase_mask = result.Read(5); + temp.Event.event_chance = result.Read(6); + temp.Event.event_flags = (SmartEventFlags)result.Read(7); + + temp.Event.raw.param1 = result.Read(8); + temp.Event.raw.param2 = result.Read(9); + temp.Event.raw.param3 = result.Read(10); + temp.Event.raw.param4 = result.Read(11); + temp.Event.param_string = result.Read(12); + + temp.Action.type = (SmartActions)result.Read(13); + temp.Action.raw.param1 = result.Read(14); + temp.Action.raw.param2 = result.Read(15); + temp.Action.raw.param3 = result.Read(16); + temp.Action.raw.param4 = result.Read(17); + temp.Action.raw.param5 = result.Read(18); + temp.Action.raw.param6 = result.Read(19); + + temp.Target.type = (SmartTargets)result.Read(20); + temp.Target.raw.param1 = result.Read(21); + temp.Target.raw.param2 = result.Read(22); + temp.Target.raw.param3 = result.Read(23); + temp.Target.x = result.Read(24); + temp.Target.y = result.Read(25); + temp.Target.z = result.Read(26); + temp.Target.o = result.Read(27); + + //check target + if (!IsTargetValid(temp)) + continue; + + // check all event and action params + if (!IsEventValid(temp)) + continue; + + // creature entry / guid not found in storage, create empty event list for it and increase counters + if (mEventMap[(int)source_type] == null) + mEventMap[(int)source_type] = new MultiMap(); + + if (!mEventMap[(int)source_type].ContainsKey(temp.entryOrGuid)) + ++count; + + // store the new event + mEventMap[(uint)source_type].Add(temp.entryOrGuid, temp); + } while (result.NextRow()); + + // Post Loading Validation + for (byte i = 0; i < (int)SmartScriptType.Max; ++i) + { + if (mEventMap[i] == null) + continue; + + foreach (var key in mEventMap[i].Keys) + { + var list = mEventMap[i].LookupByKey(key); + foreach (var e in list) + { + if (e.link != 0) + { + if (FindLinkedEvent(list, e.link) == null) + { + Log.outError(LogFilter.Sql, "SmartAIMgr.LoadFromDB: Entry {0} SourceType {1}, Event {2}, Link Event {3} not found or invalid.", + e.entryOrGuid, e.GetScriptType(), e.event_id, e.link); + } + } + + if (e.GetEventType() == SmartEvents.Link) + { + if (FindLinkedSourceEvent(list, e.event_id) == null) + { + Log.outError(LogFilter.Sql, "SmartAIMgr.LoadFromDB: Entry {0} SourceType {1}, Event {2}, Link Source Event not found or invalid. Event will never trigger.", + e.entryOrGuid, e.GetScriptType(), e.event_id); + } + } + } + } + } + + Log.outInfo(LogFilter.ServerLoading, "Loaded {0} SmartAI scripts in {1} ms", count, Time.GetMSTimeDiffToNow(oldMSTime)); + } + public void LoadWaypointFromDB() + { + uint oldMSTime = Time.GetMSTime(); + + waypoint_map.Clear(); + + PreparedStatement stmt = DB.World.GetPreparedStatement(WorldStatements.SEL_SMARTAI_WP); + SQLResult result = DB.World.Query(stmt); + + if (result.IsEmpty()) + { + Log.outInfo(LogFilter.ServerLoading, "Loaded 0 SmartAI Waypoint Paths. DB table `waypoints` is empty."); + + return; + } + + uint count = 0; + uint total = 0; + uint last_entry = 0; + uint last_id = 1; + + do + { + uint entry = result.Read(0); + uint id = result.Read(1); + float x, y, z; + x = result.Read(2); + y = result.Read(3); + z = result.Read(4); + + if (last_entry != entry) + { + waypoint_map[entry] = new Dictionary(); + last_id = 1; + count++; + } + + if (last_id != id) + Log.outError(LogFilter.Sql, "SmartWaypointMgr.LoadFromDB: Path entry {0}, unexpected point id {1}, expected {2}.", entry, id, last_id); + + last_id++; + waypoint_map[entry][id] = new SmartPath(id, x, y, z); + + last_entry = entry; + total++; + } + while (result.NextRow()); + + Log.outInfo(LogFilter.ServerLoading, "Loaded {0} SmartAI waypoint paths (total {1} waypoints) in {2} ms", count, total, Time.GetMSTimeDiffToNow(oldMSTime)); + } + + bool IsTargetValid(SmartScriptHolder e) + { + if (Math.Abs(e.Target.o) > 2 * MathFunctions.PI) + Log.outError(LogFilter.Sql, "SmartAIMgr: Entry {0} SourceType {1} Event {2} Action {3} has abs(`target.o` = {4}) > 2*PI (orientation is expressed in radians)", + e.entryOrGuid, e.GetScriptType(), e.event_id, e.GetActionType(), e.Target.o); + + if (e.GetActionType() == SmartActions.InstallAiTemplate) + return true; // AI template has special handling + + switch (e.GetTargetType()) + { + case SmartTargets.CreatureDistance: + case SmartTargets.CreatureRange: + { + if (e.Target.unitDistance.creature != 0 && Global.ObjectMgr.GetCreatureTemplate(e.Target.unitDistance.creature) == null) + { + Log.outError(LogFilter.ScriptsAi, "SmartAIMgr: Entry {0} SourceType {1} Event {2} Action {3} uses non-existent Creature entry {4} as target_param1, skipped.", e.entryOrGuid, e.GetScriptType(), e.event_id, e.GetActionType(), e.Target.unitDistance.creature); + return false; + } + break; + } + case SmartTargets.GameobjectDistance: + case SmartTargets.GameobjectRange: + { + if (e.Target.goDistance.entry != 0 && Global.ObjectMgr.GetGameObjectTemplate(e.Target.goDistance.entry) == null) + { + Log.outError(LogFilter.ScriptsAi, "SmartAIMgr: Entry {0} SourceType {1} Event {2} Action {3} uses non-existent GameObject entry {4} as target_param1, skipped.", e.entryOrGuid, e.GetScriptType(), e.event_id, e.GetActionType(), e.Target.goDistance.entry); + return false; + } + break; + } + case SmartTargets.CreatureGuid: + { + if (e.Target.unitGUID.entry != 0 && !IsCreatureValid(e, e.Target.unitGUID.entry)) + return false; + break; + } + case SmartTargets.GameobjectGuid: + { + if (e.Target.goGUID.entry != 0 && !IsGameObjectValid(e, e.Target.goGUID.entry)) + return false; + break; + } + case SmartTargets.PlayerDistance: + case SmartTargets.ClosestPlayer: + { + if (e.Target.playerDistance.dist == 0) + { + Log.outError(LogFilter.ScriptsAi, "SmartAIMgr: Entry {0} SourceType {1} Event {2} Action {3} has maxDist 0 as target_param1, skipped.", e.entryOrGuid, e.GetScriptType(), e.event_id, e.GetActionType()); + return false; + } + break; + } + case SmartTargets.PlayerRange: + case SmartTargets.Self: + case SmartTargets.Victim: + case SmartTargets.HostileSecondAggro: + case SmartTargets.HostileLastAggro: + case SmartTargets.HostileRandom: + case SmartTargets.HostileRandomNotTop: + case SmartTargets.ActionInvoker: + case SmartTargets.InvokerParty: + case SmartTargets.Position: + case SmartTargets.None: + case SmartTargets.ActionInvokerVehicle: + case SmartTargets.OwnerOrSummoner: + case SmartTargets.ThreatList: + case SmartTargets.ClosestGameobject: + case SmartTargets.ClosestCreature: + case SmartTargets.ClosestEnemy: + case SmartTargets.ClosestFriendly: + case SmartTargets.Stored: + case SmartTargets.LootRecipients: + case SmartTargets.VehicleAccessory: + break; + default: + Log.outError(LogFilter.ScriptsAi, "SmartAIMgr: Not handled target_type({0}), Entry {1} SourceType {2} Event {3} Action {4}, skipped.", e.GetTargetType(), e.entryOrGuid, e.GetScriptType(), e.event_id, e.GetActionType()); + return false; + } + return true; + } + + bool IsEventValid(SmartScriptHolder e) + { + if (e.Event.type >= SmartEvents.End) + { + Log.outError(LogFilter.ScriptsAi, "SmartAIMgr: EntryOrGuid {0} using event({1}) has invalid event type ({2}), skipped.", e.entryOrGuid, e.event_id, e.GetEventType()); + return false; + } + + // in SMART_SCRIPT_TYPE_TIMED_ACTIONLIST all event types are overriden by core + if (e.GetScriptType() != SmartScriptType.TimedActionlist && !Convert.ToBoolean(SmartAIEventMask[e.Event.type] & SmartAITypeMask[e.GetScriptType()])) + { + Log.outError(LogFilter.Scripts, "SmartAIMgr: EntryOrGuid {0}, event type {1} can not be used for Script type {2}", e.entryOrGuid, e.GetEventType(), e.GetScriptType()); + return false; + } + if (e.Action.type <= 0 || e.Action.type >= SmartActions.End) + { + Log.outError(LogFilter.ScriptsAi, "SmartAIMgr: EntryOrGuid {0} using event({1}) has invalid action type ({2}), skipped.", e.entryOrGuid, e.event_id, e.GetActionType()); + return false; + } + if (e.Event.event_phase_mask > (uint)SmartPhase.All) + { + Log.outError(LogFilter.ScriptsAi, "SmartAIMgr: EntryOrGuid {0} using event({1}) has invalid phase mask ({2}), skipped.", e.entryOrGuid, e.event_id, e.Event.event_phase_mask); + return false; + } + if (e.Event.event_flags > SmartEventFlags.All) + { + Log.outError(LogFilter.ScriptsAi, "SmartAIMgr: EntryOrGuid {0} using event({1}) has invalid event flags ({2}), skipped.", e.entryOrGuid, e.event_id, e.Event.event_flags); + return false; + } + if (e.link != 0 && e.link == e.event_id) + { + Log.outError(LogFilter.Sql, "SmartAIMgr: EntryOrGuid {0} SourceType {1}, Event {2}, Event is linking self (infinite loop), skipped.", e.entryOrGuid, e.GetScriptType(), e.event_id); + return false; + } + if (e.GetScriptType() == SmartScriptType.TimedActionlist) + { + e.Event.type = SmartEvents.UpdateOoc;//force default OOC, can change when calling the script! + if (!IsMinMaxValid(e, e.Event.minMaxRepeat.min, e.Event.minMaxRepeat.max)) + return false; + + if (!IsMinMaxValid(e, e.Event.minMaxRepeat.repeatMin, e.Event.minMaxRepeat.repeatMax)) + return false; + } + else + { + switch (e.Event.type) + { + case SmartEvents.Update: + case SmartEvents.UpdateIc: + case SmartEvents.UpdateOoc: + case SmartEvents.HealtPct: + case SmartEvents.ManaPct: + case SmartEvents.TargetHealthPct: + case SmartEvents.TargetManaPct: + case SmartEvents.Range: + case SmartEvents.Damaged: + case SmartEvents.DamagedTarget: + case SmartEvents.ReceiveHeal: + if (!IsMinMaxValid(e, e.Event.minMaxRepeat.min, e.Event.minMaxRepeat.max)) + return false; + + if (!IsMinMaxValid(e, e.Event.minMaxRepeat.repeatMin, e.Event.minMaxRepeat.repeatMax)) + return false; + break; + case SmartEvents.SpellHit: + case SmartEvents.SpellhitTarget: + if (e.Event.spellHit.spell != 0) + { + SpellInfo spellInfo = Global.SpellMgr.GetSpellInfo(e.Event.spellHit.spell); + if (spellInfo == null) + { + Log.outError(LogFilter.ScriptsAi, "SmartAIMgr: Entry {0} SourceType {1} Event {2} Action {3} uses non-existent Spell entry {4}, skipped.", e.entryOrGuid, e.GetScriptType(), e.event_id, e.GetActionType(), e.Event.spellHit.spell); + return false; + } + if (e.Event.spellHit.school != 0 && ((SpellSchoolMask)e.Event.spellHit.school & spellInfo.SchoolMask) != spellInfo.SchoolMask) + { + Log.outError(LogFilter.ScriptsAi, "SmartAIMgr: Entry {0} SourceType {1} Event {2} Action {3} uses Spell entry {4} with invalid school mask, skipped.", e.entryOrGuid, e.GetScriptType(), e.event_id, e.GetActionType(), e.Event.spellHit.spell); + return false; + } + } + if (!IsMinMaxValid(e, e.Event.spellHit.cooldownMin, e.Event.spellHit.cooldownMax)) + return false; + break; + case SmartEvents.OocLos: + case SmartEvents.IcLos: + if (!IsMinMaxValid(e, e.Event.los.cooldownMin, e.Event.los.cooldownMax)) + return false; + break; + case SmartEvents.Respawn: + if (e.Event.respawn.type == (uint)SmartRespawnCondition.Map && CliDB.MapStorage.LookupByKey(e.Event.respawn.map) == null) + { + Log.outError(LogFilter.ScriptsAi, "SmartAIMgr: Entry {0} SourceType {1} Event {2} Action {3} uses non-existent Map entry {4}, skipped.", e.entryOrGuid, e.GetScriptType(), e.event_id, e.GetActionType(), e.Event.respawn.map); + return false; + } + if (e.Event.respawn.type == (uint)SmartRespawnCondition.Area && !CliDB.AreaTableStorage.ContainsKey(e.Event.respawn.area)) + { + Log.outError(LogFilter.ScriptsAi, "SmartAIMgr: Entry {0} SourceType {1} Event {2} Action {3} uses non-existent Area entry {4}, skipped.", e.entryOrGuid, e.GetScriptType(), e.event_id, e.GetActionType(), e.Event.respawn.area); + return false; + } + break; + case SmartEvents.FriendlyHealth: + if (!NotNULL(e, e.Event.friendlyHealth.radius)) + return false; + + if (!IsMinMaxValid(e, e.Event.friendlyHealth.repeatMin, e.Event.friendlyHealth.repeatMax)) + return false; + break; + case SmartEvents.FriendlyIsCc: + if (!IsMinMaxValid(e, e.Event.friendlyCC.repeatMin, e.Event.friendlyCC.repeatMax)) + return false; + break; + case SmartEvents.FriendlyMissingBuff: + { + if (!IsSpellValid(e, e.Event.missingBuff.spell)) + return false; + + if (!NotNULL(e, e.Event.missingBuff.radius)) + return false; + + if (!IsMinMaxValid(e, e.Event.missingBuff.repeatMin, e.Event.missingBuff.repeatMax)) + return false; + break; + } + case SmartEvents.Kill: + if (!IsMinMaxValid(e, e.Event.kill.cooldownMin, e.Event.kill.cooldownMax)) + return false; + + if (e.Event.kill.creature != 0 && !IsCreatureValid(e, e.Event.kill.creature)) + return false; + break; + case SmartEvents.VictimCasting: + case SmartEvents.PassengerBoarded: + case SmartEvents.PassengerRemoved: + if (!IsMinMaxValid(e, e.Event.minMax.repeatMin, e.Event.minMax.repeatMax)) + return false; + break; + case SmartEvents.SummonDespawned: + case SmartEvents.SummonedUnit: + if (e.Event.summoned.creature != 0 && !IsCreatureValid(e, e.Event.summoned.creature)) + return false; + + if (!IsMinMaxValid(e, e.Event.summoned.cooldownMin, e.Event.summoned.cooldownMax)) + return false; + break; + case SmartEvents.AcceptedQuest: + case SmartEvents.RewardQuest: + if (e.Event.quest.questId != 0 && !IsQuestValid(e, e.Event.quest.questId)) + return false; + break; + case SmartEvents.ReceiveEmote: + { + if (e.Event.emote.emoteId != 0 && !IsTextEmoteValid(e, e.Event.emote.emoteId)) + return false; + + if (!IsMinMaxValid(e, e.Event.emote.cooldownMin, e.Event.emote.cooldownMax)) + return false; + break; + } + case SmartEvents.HasAura: + case SmartEvents.TargetBuffed: + { + if (!IsSpellValid(e, e.Event.aura.spell)) + return false; + + if (!IsMinMaxValid(e, e.Event.aura.repeatMin, e.Event.aura.repeatMax)) + return false; + break; + } + case SmartEvents.TransportAddcreature: + { + if (e.Event.transportAddCreature.creature != 0 && !IsCreatureValid(e, e.Event.transportAddCreature.creature)) + return false; + break; + } + case SmartEvents.Movementinform: + { + if (e.Event.movementInform.type >= (uint)MovementGeneratorType.Max) + { + Log.outError(LogFilter.ScriptsAi, "SmartAIMgr: Entry {0} SourceType {1} Event {2} Action {3} uses invalid Motion type {4}, skipped.", e.entryOrGuid, e.GetScriptType(), e.event_id, e.GetActionType(), e.Event.movementInform.type); + return false; + } + break; + } + case SmartEvents.DataSet: + { + if (!IsMinMaxValid(e, e.Event.dataSet.cooldownMin, e.Event.dataSet.cooldownMax)) + return false; + break; + } + case SmartEvents.AreatriggerOntrigger: + { + if (e.Event.areatrigger.id != 0 && !IsAreaTriggerValid(e, e.Event.areatrigger.id)) + return false; + break; + } + case SmartEvents.TextOver: + { + if (!IsTextValid(e, e.Event.textOver.textGroupID)) + return false; + break; + } + case SmartEvents.DummyEffect: + { + if (!IsSpellValid(e, e.Event.dummy.spell)) + return false; + + if (e.Event.dummy.effIndex > 2) + return false; + break; + } + case SmartEvents.IsBehindTarget: + { + if (!IsMinMaxValid(e, e.Event.behindTarget.cooldownMin, e.Event.behindTarget.cooldownMax)) + return false; + break; + } + case SmartEvents.GameEventStart: + case SmartEvents.GameEventEnd: + { + var events = Global.GameEventMgr.GetEventMap(); + if (e.Event.gameEvent.gameEventId >= events.Length || !events[e.Event.gameEvent.gameEventId].isValid()) + return false; + + break; + } + case SmartEvents.ActionDone: + { + if (e.Event.doAction.eventId > EventId.Charge) + { + Log.outError(LogFilter.ScriptsAi, "SmartAIMgr: Entry {0} SourceType {1} Event {2} Action {3} uses invalid event id {4}, skipped.", e.entryOrGuid, e.GetScriptType(), e.event_id, e.GetActionType(), e.Event.doAction.eventId); + return false; + } + break; + } + case SmartEvents.FriendlyHealthPCT: + if (!IsMinMaxValid(e, e.Event.friendlyHealthPct.repeatMin, e.Event.friendlyHealthPct.repeatMax)) + return false; + + if (e.Event.friendlyHealthPct.maxHpPct > 100 || e.Event.friendlyHealthPct.minHpPct > 100) + { + Log.outError(LogFilter.Sql, "SmartAIMgr: Entry {0} SourceType {1} Event {2} Action {3} has pct value above 100, skipped.", e.entryOrGuid, e.GetScriptType(), e.event_id, e.GetActionType()); + return false; + } + + switch (e.GetTargetType()) + { + case SmartTargets.CreatureRange: + case SmartTargets.CreatureGuid: + case SmartTargets.CreatureDistance: + case SmartTargets.ClosestCreature: + case SmartTargets.ClosestPlayer: + case SmartTargets.PlayerRange: + case SmartTargets.PlayerDistance: + break; + default: + Log.outError(LogFilter.Sql, "SmartAIMgr: Entry {0} SourceType {1} Event {2} Action {3} uses invalid target_type {4}, skipped.", e.entryOrGuid, e.GetScriptType(), e.event_id, e.GetActionType(), e.GetTargetType()); + return false; + } + break; + case SmartEvents.DistanceCreature: + if (e.Event.distance.guid == 0 && e.Event.distance.entry == 0) + { + Log.outError(LogFilter.Sql, "SmartAIMgr: Event SMART_EVENT_DISTANCE_CREATURE did not provide creature guid or entry, skipped."); + return false; + } + + if (e.Event.distance.guid != 0 && e.Event.distance.entry != 0) + { + Log.outError(LogFilter.Sql, "SmartAIMgr: Event SMART_EVENT_DISTANCE_CREATURE provided both an entry and guid, skipped."); + return false; + } + + if (e.Event.distance.guid != 0 && Global.ObjectMgr.GetCreatureData(e.Event.distance.guid) == null) + { + Log.outError(LogFilter.Sql, "SmartAIMgr: Event SMART_EVENT_DISTANCE_CREATURE using invalid creature guid {0}, skipped.", e.Event.distance.guid); + return false; + } + + if (e.Event.distance.entry != 0 && Global.ObjectMgr.GetCreatureTemplate(e.Event.distance.entry) == null) + { + Log.outError(LogFilter.Sql, "SmartAIMgr: Event SMART_EVENT_DISTANCE_CREATURE using invalid creature entry {0}, skipped.", e.Event.distance.entry); + return false; + } + break; + case SmartEvents.DistanceGameobject: + if (e.Event.distance.guid == 0 && e.Event.distance.entry == 0) + { + Log.outError(LogFilter.Sql, "SmartAIMgr: Event SMART_EVENT_DISTANCE_GAMEOBJECT did not provide gameobject guid or entry, skipped."); + return false; + } + + if (e.Event.distance.guid != 0 && e.Event.distance.entry != 0) + { + Log.outError(LogFilter.Sql, "SmartAIMgr: Event SMART_EVENT_DISTANCE_GAMEOBJECT provided both an entry and guid, skipped."); + return false; + } + + if (e.Event.distance.guid != 0 && Global.ObjectMgr.GetGOData(e.Event.distance.guid) == null) + { + Log.outError(LogFilter.Sql, "SmartAIMgr: Event SMART_EVENT_DISTANCE_GAMEOBJECT using invalid gameobject guid {0}, skipped.", e.Event.distance.guid); + return false; + } + + if (e.Event.distance.entry != 0 && Global.ObjectMgr.GetGameObjectTemplate(e.Event.distance.entry) == null) + { + Log.outError(LogFilter.Sql, "SmartAIMgr: Event SMART_EVENT_DISTANCE_GAMEOBJECT using invalid gameobject entry {0}, skipped.", e.Event.distance.entry); + return false; + } + break; + case SmartEvents.CounterSet: + if (!IsMinMaxValid(e, e.Event.counter.cooldownMin, e.Event.counter.cooldownMax)) + return false; + + if (e.Event.counter.id == 0) + { + Log.outError(LogFilter.Sql, "SmartAIMgr: Event SMART_EVENT_COUNTER_SET using invalid counter id {0}, skipped.", e.Event.counter.id); + return false; + } + + if (e.Event.counter.value == 0) + { + Log.outError(LogFilter.Sql, "SmartAIMgr: Event SMART_EVENT_COUNTER_SET using invalid value {0}, skipped.", e.Event.counter.value); + return false; + } + break; + case SmartEvents.Link: + case SmartEvents.GoStateChanged: + case SmartEvents.GoEventInform: + case SmartEvents.TimedEventTriggered: + case SmartEvents.InstancePlayerEnter: + case SmartEvents.TransportRelocate: + case SmartEvents.Charmed: + case SmartEvents.CharmedTarget: + case SmartEvents.CorpseRemoved: + case SmartEvents.AiInit: + case SmartEvents.TransportAddplayer: + case SmartEvents.TransportRemovePlayer: + case SmartEvents.Aggro: + case SmartEvents.Death: + case SmartEvents.Evade: + case SmartEvents.ReachedHome: + case SmartEvents.Reset: + case SmartEvents.QuestAccepted: + case SmartEvents.QuestObjCompletion: + case SmartEvents.QuestCompletion: + case SmartEvents.QuestRewarded: + case SmartEvents.QuestFail: + case SmartEvents.JustSummoned: + case SmartEvents.WaypointStart: + case SmartEvents.WaypointReached: + case SmartEvents.WaypointPaused: + case SmartEvents.WaypointResumed: + case SmartEvents.WaypointStopped: + case SmartEvents.WaypointEnded: + case SmartEvents.GossipSelect: + case SmartEvents.GossipHello: + case SmartEvents.JustCreated: + case SmartEvents.FollowCompleted: + case SmartEvents.OnSpellclick: + case SmartEvents.SceneStart: + case SmartEvents.SceneCancel: + case SmartEvents.SceneComplete: + case SmartEvents.SceneTrigger: + break; + default: + Log.outError(LogFilter.ScriptsAi, "SmartAIMgr: Not handled event_type({0}), Entry {1} SourceType {2} Event {3} Action {4}, skipped.", e.GetEventType(), e.entryOrGuid, e.GetScriptType(), e.event_id, e.GetActionType()); + return false; + } + } + + switch (e.GetActionType()) + { + case SmartActions.Talk: + case SmartActions.SimpleTalk: + { + if (!IsTextValid(e, e.Action.talk.textGroupId)) + return false; + break; + } + case SmartActions.SetFaction: + if (e.Action.faction.factionID != 0 && CliDB.FactionTemplateStorage.LookupByKey(e.Action.faction.factionID) == null) + { + Log.outError(LogFilter.ScriptsAi, "SmartAIMgr: Entry {0} SourceType {1} Event {2} Action {3} uses non-existent Faction {4}, skipped.", e.entryOrGuid, e.GetScriptType(), e.event_id, e.GetActionType(), e.Action.faction.factionID); + return false; + } + break; + case SmartActions.MorphToEntryOrModel: + case SmartActions.MountToEntryOrModel: + if (e.Action.morphOrMount.creature != 0 || e.Action.morphOrMount.model != 0) + { + if (e.Action.morphOrMount.creature > 0 && Global.ObjectMgr.GetCreatureTemplate(e.Action.morphOrMount.creature) == null) + { + Log.outError(LogFilter.ScriptsAi, "SmartAIMgr: Entry {0} SourceType {1} Event {2} Action {3} uses non-existent Creature entry {4}, skipped.", e.entryOrGuid, e.GetScriptType(), e.event_id, e.GetActionType(), e.Action.morphOrMount.creature); + return false; + } + + if (e.Action.morphOrMount.model != 0) + { + if (e.Action.morphOrMount.creature != 0) + { + Log.outError(LogFilter.ScriptsAi, "SmartAIMgr: Entry {0} SourceType {1} Event {2} Action {3} has ModelID set with also set CreatureId, skipped.", e.entryOrGuid, e.GetScriptType(), e.event_id, e.GetActionType()); + return false; + } + else if (!CliDB.CreatureDisplayInfoStorage.ContainsKey(e.Action.morphOrMount.model)) + { + Log.outError(LogFilter.ScriptsAi, "SmartAIMgr: Entry {0} SourceType {1} Event {2} Action {3} uses non-existent Model id {4}, skipped.", e.entryOrGuid, e.GetScriptType(), e.event_id, e.GetActionType(), e.Action.morphOrMount.model); + return false; + } + } + } + break; + case SmartActions.Sound: + if (!IsSoundValid(e, e.Action.sound.soundId)) + return false; + break; + case SmartActions.SetEmoteState: + case SmartActions.PlayEmote: + if (!IsEmoteValid(e, e.Action.emote.emoteId)) + return false; + break; + case SmartActions.PlayAnimkit: + if (e.Action.animKit.animKit != 0 && !IsAnimKitValid(e, e.Action.animKit.animKit)) + return false; + + if (e.Action.animKit.type > 3) + { + Log.outError(LogFilter.Sql, "SmartAIMgr: Entry {0} SourceType {1} Event {2} Action {3} uses invalid AnimKit type {4}, skipped.", + e.entryOrGuid, e.GetScriptType(), e.event_id, e.GetActionType(), e.Action.animKit.type); + return false; + } + break; + case SmartActions.FailQuest: + case SmartActions.OfferQuest: + if (e.Action.quest.questId == 0 || !IsQuestValid(e, e.Action.quest.questId)) + return false; + break; + case SmartActions.ActivateTaxi: + { + if (!CliDB.TaxiPathStorage.ContainsKey(e.Action.taxi.id)) + { + Log.outError(LogFilter.ScriptsAi, "SmartAIMgr: Entry {0} SourceType {1} Event {2} Action {3} uses invalid Taxi path ID {4}, skipped.", e.entryOrGuid, e.GetScriptType(), e.event_id, e.GetActionType(), e.Action.taxi.id); + return false; + } + break; + } + case SmartActions.RandomEmote: + if (e.Action.randomEmote.emote1 != 0 && !IsEmoteValid(e, e.Action.randomEmote.emote1)) + return false; + if (e.Action.randomEmote.emote2 != 0 && !IsEmoteValid(e, e.Action.randomEmote.emote2)) + return false; + if (e.Action.randomEmote.emote3 != 0 && !IsEmoteValid(e, e.Action.randomEmote.emote3)) + return false; + if (e.Action.randomEmote.emote4 != 0 && !IsEmoteValid(e, e.Action.randomEmote.emote4)) + return false; + if (e.Action.randomEmote.emote5 != 0 && !IsEmoteValid(e, e.Action.randomEmote.emote5)) + return false; + if (e.Action.randomEmote.emote6 != 0 && !IsEmoteValid(e, e.Action.randomEmote.emote6)) + return false; + break; + case SmartActions.RandomSound: + if (e.Action.randomSound.sound1 != 0 && !IsSoundValid(e, e.Action.randomSound.sound1)) + return false; + if (e.Action.randomSound.sound2 != 0 && !IsSoundValid(e, e.Action.randomSound.sound2)) + return false; + if (e.Action.randomSound.sound3 != 0 && !IsSoundValid(e, e.Action.randomSound.sound3)) + return false; + if (e.Action.randomSound.sound4 != 0 && !IsSoundValid(e, e.Action.randomSound.sound4)) + return false; + if (e.Action.randomSound.sound5 != 0 && !IsSoundValid(e, e.Action.randomSound.sound5)) + return false; + break; + case SmartActions.Cast: + { + if (!IsSpellValid(e, e.Action.cast.spell)) + return false; + + SpellInfo spellInfo = Global.SpellMgr.GetSpellInfo(e.Action.cast.spell); + foreach (SpellEffectInfo effect in spellInfo.GetEffectsForDifficulty(Difficulty.None)) + { + if (effect != null && (effect.IsEffect(SpellEffectName.KillCredit) || effect.IsEffect(SpellEffectName.KillCredit2))) + { + if (effect.TargetA.GetTarget() == Targets.UnitCaster) + Log.outError(LogFilter.Sql, "SmartAIMgr: Entry {0} SourceType {1} Event {2} Action {3} Effect: SPELL_EFFECT_KILL_CREDIT: (SpellId: {4} targetA: {5} - targetB: {6}) has invalid target for this Action", + e.entryOrGuid, e.GetScriptType(), e.event_id, e.GetActionType(), e.Action.cast.spell, effect.TargetA.GetTarget(), effect.TargetB.GetTarget()); + } + } + break; + } + case SmartActions.AddAura: + case SmartActions.InvokerCast: + if (!IsSpellValid(e, e.Action.cast.spell)) + return false; + break; + case SmartActions.CallAreaexploredoreventhappens: + case SmartActions.CallGroupeventhappens: + Quest qid = Global.ObjectMgr.GetQuestTemplate(e.Action.quest.questId); + if (qid != null) + { + if (!qid.HasSpecialFlag(QuestSpecialFlags.ExplorationOrEvent)) + { + Log.outError(LogFilter.ScriptsAi, "SmartAIMgr: Entry {0} SourceType {1} Event {2} Action {3} SpecialFlags for Quest entry {4} does not include FLAGS_EXPLORATION_OR_EVENT(2), skipped.", e.entryOrGuid, e.GetScriptType(), e.event_id, e.GetActionType(), e.Action.quest.questId); + return false; + } + } + else + { + Log.outError(LogFilter.ScriptsAi, "SmartAIMgr: Entry {0} SourceType {1} Event {2} Action {3} uses non-existent Quest entry {4}, skipped.", e.entryOrGuid, e.GetScriptType(), e.event_id, e.GetActionType(), e.Action.quest.questId); + return false; + } + break; + case SmartActions.SetEventPhase: + if (e.Action.setEventPhase.phase >= (uint)SmartPhase.Max) + { + Log.outError(LogFilter.ScriptsAi, "SmartAIMgr: Entry {0} SourceType {1} Event {2} Action {3} attempts to set phase {4}. Phase mask cannot be used past phase {5}, skipped.", e.entryOrGuid, e.GetScriptType(), e.event_id, e.GetActionType(), e.Action.setEventPhase.phase, SmartPhase.Max - 1); + return false; + } + break; + case SmartActions.IncEventPhase: + if (e.Action.incEventPhase.inc == 0 && e.Action.incEventPhase.dec == 0) + { + Log.outError(LogFilter.ScriptsAi, "SmartAIMgr: Entry {0} SourceType {1} Event {2} Action {3} is incrementing phase by 0, skipped.", e.entryOrGuid, e.GetScriptType(), e.event_id, e.GetActionType()); + return false; + } + else if (e.Action.incEventPhase.inc > (uint)SmartPhase.Max || e.Action.incEventPhase.dec > (uint)SmartPhase.Max) + { + Log.outError(LogFilter.ScriptsAi, "SmartAIMgr: Entry {0} SourceType {1} Event {2} Action {3} attempts to increment phase by too large value, skipped.", e.entryOrGuid, e.GetScriptType(), e.event_id, e.GetActionType()); + return false; + } + break; + case SmartActions.Removeaurasfromspell: + if (e.Action.removeAura.spell != 0 && !IsSpellValid(e, e.Action.removeAura.spell)) + return false; + break; + case SmartActions.RandomPhase: + { + if (e.Action.randomPhase.phase1 >= (uint)SmartPhase.Max || + e.Action.randomPhase.phase2 >= (uint)SmartPhase.Max || + e.Action.randomPhase.phase3 >= (uint)SmartPhase.Max || + e.Action.randomPhase.phase4 >= (uint)SmartPhase.Max || + e.Action.randomPhase.phase5 >= (uint)SmartPhase.Max || + e.Action.randomPhase.phase6 >= (uint)SmartPhase.Max) + { + Log.outError(LogFilter.ScriptsAi, "SmartAIMgr: Entry {0} SourceType {1} Event {2} Action {3} attempts to set invalid phase, skipped.", e.entryOrGuid, e.GetScriptType(), e.event_id, e.GetActionType()); + return false; + } + } + break; + case SmartActions.RandomPhaseRange: //PhaseMin, PhaseMax + { + if (e.Action.randomPhaseRange.phaseMin >= (uint)SmartPhase.Max || + e.Action.randomPhaseRange.phaseMax >= (uint)SmartPhase.Max) + { + Log.outError(LogFilter.ScriptsAi, "SmartAIMgr: Entry {0} SourceType {1} Event {2} Action {3} attempts to set invalid phase, skipped.", e.entryOrGuid, e.GetScriptType(), e.event_id, e.GetActionType()); + return false; + } + if (!IsMinMaxValid(e, e.Action.randomPhaseRange.phaseMin, e.Action.randomPhaseRange.phaseMax)) + return false; + break; + } + case SmartActions.SummonCreature: + if (!IsCreatureValid(e, e.Action.summonCreature.creature)) + return false; + + if (e.Action.summonCreature.type < (uint)TempSummonType.TimedOrDeadDespawn || e.Action.summonCreature.type > (uint)TempSummonType.ManualDespawn) + { + Log.outError(LogFilter.ScriptsAi, "SmartAIMgr: Entry {0} SourceType {1} Event {2} Action {3} uses incorrect TempSummonType {4}, skipped.", e.entryOrGuid, e.GetScriptType(), e.event_id, e.GetActionType(), e.Action.summonCreature.type); + return false; + } + break; + case SmartActions.CallKilledmonster: + if (!IsCreatureValid(e, e.Action.killedMonster.creature)) + return false; + + if (e.GetTargetType() == SmartTargets.Position) + { + Log.outError(LogFilter.Sql, "SmartAIMgr: Entry {0} SourceType {1} Event {2} Action {3} uses incorrect TargetType {4}, skipped.", e.entryOrGuid, e.GetScriptType(), e.event_id, e.GetActionType(), e.GetTargetType()); + return false; + } + break; + case SmartActions.UpdateTemplate: + if (e.Action.updateTemplate.creature != 0 && !IsCreatureValid(e, e.Action.updateTemplate.creature)) + return false; + break; + case SmartActions.SetSheath: + if (e.Action.setSheath.sheath != 0 && e.Action.setSheath.sheath >= (uint)SheathState.Max) + { + Log.outError(LogFilter.ScriptsAi, "SmartAIMgr: Entry {0} SourceType {1} Event {2} Action {3} uses incorrect Sheath state {4}, skipped.", e.entryOrGuid, e.GetScriptType(), e.event_id, e.GetActionType(), e.Action.setSheath.sheath); + return false; + } + break; + case SmartActions.SetReactState: + { + if (e.Action.react.state > (uint)ReactStates.Aggressive) + { + Log.outError(LogFilter.ScriptsAi, "SmartAIMgr: Creature {0} Event {1} Action {2} uses invalid React State {3}, skipped.", e.entryOrGuid, e.event_id, e.GetActionType(), e.Action.react.state); + return false; + } + break; + } + case SmartActions.SummonGo: + if (!IsGameObjectValid(e, e.Action.summonGO.entry)) + return false; + break; + case SmartActions.RemoveItem: + if (!IsItemValid(e, e.Action.item.entry)) + return false; + + if (!NotNULL(e, e.Action.item.count)) + return false; + break; + case SmartActions.AddItem: + if (!IsItemValid(e, e.Action.item.entry)) + return false; + + if (!NotNULL(e, e.Action.item.count)) + return false; + break; + case SmartActions.Teleport: + if (!CliDB.MapStorage.ContainsKey(e.Action.teleport.mapID)) + { + Log.outError(LogFilter.ScriptsAi, "SmartAIMgr: Entry {0} SourceType {1} Event {2} Action {3} uses non-existent Map entry {4}, skipped.", e.entryOrGuid, e.GetScriptType(), e.event_id, e.GetActionType(), e.Action.teleport.mapID); + return false; + } + break; + case SmartActions.SetCounter: + if (e.Action.setCounter.counterId == 0) + { + Log.outError(LogFilter.Sql, "SmartAIMgr: Entry {0} SourceType {1} Event {2} Action {3} uses wrong counterId {4}, skipped.", e.entryOrGuid, e.GetScriptType(), e.event_id, e.GetActionType(), e.Action.setCounter.counterId); + return false; + } + + if (e.Action.setCounter.value == 0) + { + Log.outError(LogFilter.Sql, "SmartAIMgr: Entry {0} SourceType {1} Event {2} Action {3} uses wrong value {4}, skipped.", e.entryOrGuid, e.GetScriptType(), e.event_id, e.GetActionType(), e.Action.setCounter.value); + return false; + } + + break; + case SmartActions.InstallAiTemplate: + if (e.Action.installTtemplate.id >= (uint)SmartAITemplate.End) + { + Log.outError(LogFilter.ScriptsAi, "SmartAIMgr: Creature {0} Event {1} Action {2} uses non-existent AI template id {3}, skipped.", e.entryOrGuid, e.event_id, e.GetActionType(), e.Action.installTtemplate.id); + return false; + } + break; + case SmartActions.WpStop: + if (e.Action.wpStop.quest != 0 && !IsQuestValid(e, e.Action.wpStop.quest)) + return false; + break; + case SmartActions.WpStart: + { + if (Global.SmartAIMgr.GetPath(e.Action.wpStart.pathID) == null) + { + Log.outError(LogFilter.ScriptsAi, "SmartAIMgr: Creature {0} Event {1} Action {2} uses non-existent WaypointPath id {3}, skipped.", e.entryOrGuid, e.event_id, e.GetActionType(), e.Action.wpStart.pathID); + return false; + } + if (e.Action.wpStart.quest != 0 && !IsQuestValid(e, e.Action.wpStart.quest)) + return false; + if (e.Action.wpStart.reactState > (uint)ReactStates.Aggressive) + { + Log.outError(LogFilter.ScriptsAi, "SmartAIMgr: Creature {0} Event {1} Action {2} uses invalid React State {3}, skipped.", e.entryOrGuid, e.event_id, e.GetActionType(), e.Action.wpStart.reactState); + return false; + } + break; + } + case SmartActions.CreateTimedEvent: + { + if (!IsMinMaxValid(e, e.Action.timeEvent.min, e.Action.timeEvent.max)) + return false; + + if (!IsMinMaxValid(e, e.Action.timeEvent.repeatMin, e.Action.timeEvent.repeatMax)) + return false; + break; + } + case SmartActions.CallRandomRangeTimedActionlist: + { + if (!IsMinMaxValid(e, e.Action.randTimedActionList.entry1, e.Action.randTimedActionList.entry2)) + return false; + break; + } + case SmartActions.SetPower: + case SmartActions.AddPower: + case SmartActions.RemovePower: + if (e.Action.power.powerType > (int)PowerType.Max) + { + Log.outError(LogFilter.Sql, "SmartAIMgr: Entry {0} SourceType {1} Event {2} Action {3} uses non-existent Power {4}, skipped.", e.entryOrGuid, e.GetScriptType(), e.event_id, e.GetActionType(), e.Action.power.powerType); + return false; + } + break; + case SmartActions.GameEventStop: + { + uint eventId = e.Action.gameEventStop.id; + + var events = Global.GameEventMgr.GetEventMap(); + if (eventId < 1 || eventId >= events.Length) + { + Log.outError(LogFilter.Sql, "SmartAIMgr: Entry {0} SourceType {1} Event {2} Action {3} uses non-existent event, eventId {4}, skipped.", e.entryOrGuid, e.GetScriptType(), e.event_id, e.GetActionType(), e.Action.gameEventStop.id); + return false; + } + + GameEventData eventData = events[eventId]; + if (!eventData.isValid()) + { + Log.outError(LogFilter.Sql, "SmartAIMgr: Entry {0} SourceType {1} Event {2} Action {3} uses non-existent event, eventId {4}, skipped.", e.entryOrGuid, e.GetScriptType(), e.event_id, e.GetActionType(), e.Action.gameEventStop.id); + return false; + } + break; + } + case SmartActions.GameEventStart: + { + uint eventId = e.Action.gameEventStart.id; + + var events = Global.GameEventMgr.GetEventMap(); + if (eventId < 1 || eventId >= events.Length) + { + Log.outError(LogFilter.Sql, "SmartAIMgr: Entry {0} SourceType {1} Event {2} Action {3} uses non-existent event, eventId {4}, skipped.", e.entryOrGuid, e.GetScriptType(), e.event_id, e.GetActionType(), e.Action.gameEventStart.id); + return false; + } + + GameEventData eventData = events[eventId]; + if (!eventData.isValid()) + { + Log.outError(LogFilter.Sql, "SmartAIMgr: Entry {0} SourceType {1} Event {2} Action {3} uses non-existent event, eventId {4}, skipped.", e.entryOrGuid, e.GetScriptType(), e.event_id, e.GetActionType(), e.Action.gameEventStart.id); + return false; + } + break; + } + case SmartActions.Equip: + { + if (e.GetScriptType() == SmartScriptType.Creature) + { + sbyte equipId = (sbyte)e.Action.equip.entry; + + if (equipId != 0 && Global.ObjectMgr.GetEquipmentInfo((uint)e.entryOrGuid, equipId) == null) + { + Log.outError(LogFilter.Sql, "SmartScript: SMART_ACTION_EQUIP uses non-existent equipment info id {0} for creature {1}, skipped.", equipId, e.entryOrGuid); + return false; + } + } + break; + } + case SmartActions.SetInstData: + { + if (e.Action.setInstanceData.type > 1) + { + Log.outError(LogFilter.Sql, "SmartAIMgr: Entry {0} SourceType {1} Event {2} Action {3} uses invalid data type {4} (value range 0-1), skipped.", e.entryOrGuid, e.GetScriptType(), e.event_id, e.GetActionType(), e.Action.setInstanceData.type); + return false; + } + else if (e.Action.setInstanceData.type == 1) + { + if (e.Action.setInstanceData.data > (int)EncounterState.ToBeDecided) + { + Log.outError(LogFilter.Sql, "SmartAIMgr: Entry {0} SourceType {1} Event {2} Action {3} uses invalid boss state {4} (value range 0-5), skipped.", e.entryOrGuid, e.GetScriptType(), e.event_id, e.GetActionType(), e.Action.setInstanceData.data); + return false; + } + } + break; + } + case SmartActions.SetIngamePhaseId: + { + uint phaseId = e.Action.ingamePhaseId.id; + uint apply = e.Action.ingamePhaseId.apply; + + if (apply != 0 && apply != 1) + { + Log.outError(LogFilter.Sql, "SmartScript: SMART_ACTION_SET_INGAME_PHASE_ID uses invalid apply value {0} (Should be 0 or 1) for creature {1}, skipped", apply, e.entryOrGuid); + return false; + } + + if (!CliDB.PhaseStorage.ContainsKey(phaseId)) + { + Log.outError(LogFilter.Sql, "SmartScript: SMART_ACTION_SET_INGAME_PHASE_ID uses invalid phaseid {0} for creature {1}, skipped", phaseId, e.entryOrGuid); + return false; + } + break; + } + case SmartActions.SetIngamePhaseGroup: + { + uint phaseGroup = e.Action.ingamePhaseGroup.groupId; + uint apply = e.Action.ingamePhaseGroup.apply; + + if (apply != 0 && apply != 1) + { + Log.outError(LogFilter.Sql, "SmartScript: SMART_ACTION_SET_INGAME_PHASE_GROUP uses invalid apply value {0} (Should be 0 or 1) for creature {1}, skipped", apply, e.entryOrGuid); + return false; + } + + if (Global.DB2Mgr.GetPhasesForGroup(phaseGroup).Empty()) + { + Log.outError(LogFilter.Sql, "SmartScript: SMART_ACTION_SET_INGAME_PHASE_GROUP uses invalid phase group id {0} for creature {1}, skipped", phaseGroup, e.entryOrGuid); + return false; + } + break; + } + case SmartActions.ScenePlay: + { + if (Global.ObjectMgr.GetSceneTemplate(e.Action.scene.sceneId) == null) + { + Log.outError(LogFilter.Sql, "SmartScript: SMART_ACTION_SCENE_PLAY uses sceneId {0} but scene don't exist, skipped", e.Action.scene.sceneId); + return false; + } + + break; + } + case SmartActions.SceneCancel: + { + if (Global.ObjectMgr.GetSceneTemplate(e.Action.scene.sceneId) == null) + { + Log.outError(LogFilter.Sql, "SmartScript: SMART_ACTION_SCENE_CANCEL uses sceneId {0} but scene don't exist, skipped", e.Action.scene.sceneId); + return false; + } + + break; + } + case SmartActions.Follow: + case SmartActions.SetOrientation: + case SmartActions.StoreTargetList: + case SmartActions.Evade: + case SmartActions.FleeForAssist: + case SmartActions.CombatStop: + case SmartActions.Die: + case SmartActions.SetInCombatWithZone: + case SmartActions.SetActive: + case SmartActions.WpResume: + case SmartActions.KillUnit: + case SmartActions.SetInvincibilityHpLevel: + case SmartActions.ResetGobject: + case SmartActions.AttackStart: + case SmartActions.ThreatAllPct: + case SmartActions.ThreatSinglePct: + case SmartActions.SetInstData64: + case SmartActions.AutoAttack: + case SmartActions.AllowCombatMovement: + case SmartActions.CallForHelp: + case SmartActions.SetData: + case SmartActions.SetVisibility: + case SmartActions.WpPause: + case SmartActions.SetFly: + case SmartActions.SetRun: + case SmartActions.SetSwim: + case SmartActions.ForceDespawn: + case SmartActions.SetUnitFlag: + case SmartActions.RemoveUnitFlag: + case SmartActions.Playmovie: + case SmartActions.MoveToPos: + case SmartActions.RespawnTarget: + case SmartActions.CloseGossip: + case SmartActions.TriggerTimedEvent: + case SmartActions.RemoveTimedEvent: + case SmartActions.OverrideScriptBaseObject: + case SmartActions.ResetScriptBaseObject: + case SmartActions.ActivateGobject: + case SmartActions.CallScriptReset: + case SmartActions.SetRangedMovement: + case SmartActions.CallTimedActionlist: + case SmartActions.SetNpcFlag: + case SmartActions.AddNpcFlag: + case SmartActions.RemoveNpcFlag: + case SmartActions.CrossCast: + case SmartActions.CallRandomTimedActionlist: + case SmartActions.RandomMove: + case SmartActions.SetUnitFieldBytes1: + case SmartActions.RemoveUnitFieldBytes1: + case SmartActions.InterruptSpell: + case SmartActions.SendGoCustomAnim: + case SmartActions.SetDynamicFlag: + case SmartActions.AddDynamicFlag: + case SmartActions.RemoveDynamicFlag: + case SmartActions.JumpToPos: + case SmartActions.SendGossipMenu: + case SmartActions.GoSetLootState: + case SmartActions.SendTargetToTarget: + case SmartActions.SetHomePos: + case SmartActions.SetHealthRegen: + case SmartActions.SetRoot: + case SmartActions.SetGoFlag: + case SmartActions.AddGoFlag: + case SmartActions.RemoveGoFlag: + case SmartActions.SummonCreatureGroup: + case SmartActions.MoveOffset: + case SmartActions.SetCorpseDelay: + case SmartActions.DisableEvade: + break; + default: + Log.outError(LogFilter.ScriptsAi, "SmartAIMgr: Not handled action_type({0}), event_type({1}), Entry {2} SourceType {3} Event {4}, skipped.", e.GetActionType(), e.GetEventType(), e.entryOrGuid, e.GetScriptType(), e.event_id); + return false; + } + + return true; + } + bool IsAnimKitValid(SmartScriptHolder e, uint entry) + { + if (!CliDB.AnimKitStorage.ContainsKey(entry)) + { + Log.outError(LogFilter.Sql, "SmartAIMgr: Entry {0} SourceType {1} Event {2} Action {3} uses non-existent AnimKit entry {4}, skipped.", + e.entryOrGuid, e.GetScriptType(), e.event_id, e.GetActionType(), entry); + return false; + } + return true; + } + bool IsTextValid(SmartScriptHolder e, uint id) + { + if (e.GetScriptType() != SmartScriptType.Creature) + return true; + + uint entry = 0; + if (e.GetEventType() == SmartEvents.TextOver) + { + entry = e.Event.textOver.creatureEntry; + } + else + { + switch (e.GetTargetType()) + { + case SmartTargets.CreatureDistance: + case SmartTargets.CreatureRange: + case SmartTargets.ClosestCreature: + return true; // ignore + default: + if (e.entryOrGuid < 0) + { + ulong guid = (ulong)-e.entryOrGuid; + CreatureData data = Global.ObjectMgr.GetCreatureData(guid); + if (data == null) + { + Log.outError(LogFilter.Sql, "SmartAIMgr: Entry {0} SourceType {1} Event {2} Action {3} using non-existent Creature guid {4}, skipped.", e.entryOrGuid, e.GetScriptType(), e.event_id, e.GetActionType(), guid); + return false; + } + else + entry = data.id; + } + else + entry = (uint)e.entryOrGuid; + break; + } + } + + if (entry == 0 || !Global.CreatureTextMgr.TextExist(entry, (byte)id)) + { + Log.outError(LogFilter.Sql, "SmartAIMgr: Entry {0} SourceType {1} Event {2} Action {3} using non-existent Text id {4}, skipped.", e.entryOrGuid, e.GetScriptType(), e.event_id, e.GetActionType(), id); + return false; + } + + return true; + } + bool IsCreatureValid(SmartScriptHolder e, uint entry) + { + if (Global.ObjectMgr.GetCreatureTemplate(entry) == null) + { + Log.outError(LogFilter.ScriptsAi, "SmartAIMgr: Entry {0} SourceType {1} Event {2} Action {3} uses non-existent Creature entry {4}, skipped.", e.entryOrGuid, e.GetScriptType(), e.event_id, e.GetActionType(), entry); + return false; + } + return true; + } + bool IsGameObjectValid(SmartScriptHolder e, uint entry) + { + if (Global.ObjectMgr.GetGameObjectTemplate(entry) == null) + { + Log.outError(LogFilter.ScriptsAi, "SmartAIMgr: Entry {0} SourceType {1} Event {2} Action {3} uses non-existent GameObject entry {4}, skipped.", e.entryOrGuid, e.GetScriptType(), e.event_id, e.GetActionType(), entry); + return false; + } + return true; + } + bool IsQuestValid(SmartScriptHolder e, uint entry) + { + if (Global.ObjectMgr.GetQuestTemplate(entry) == null) + { + Log.outError(LogFilter.ScriptsAi, "SmartAIMgr: Entry {0} SourceType {1} Event {2} Action {3} uses non-existent Quest entry {4}, skipped.", e.entryOrGuid, e.GetScriptType(), e.event_id, e.GetActionType(), entry); + return false; + } + return true; + } + bool IsSpellValid(SmartScriptHolder e, uint entry) + { + if (!Global.SpellMgr.HasSpellInfo(entry)) + { + Log.outError(LogFilter.ScriptsAi, "SmartAIMgr: Entry {0} SourceType {1} Event {2} Action {3} uses non-existent Spell entry {4}, skipped.", e.entryOrGuid, e.GetScriptType(), e.event_id, e.GetActionType(), entry); + return false; + } + return true; + } + bool IsMinMaxValid(SmartScriptHolder e, uint min, uint max) + { + if (max < min) + { + Log.outError(LogFilter.ScriptsAi, "SmartAIMgr: Entry {0} SourceType {1} Event {2} Action {3} uses min/max params wrong ({4}/{5}), skipped.", e.entryOrGuid, e.GetScriptType(), e.event_id, e.GetActionType(), min, max); + return false; + } + return true; + } + bool NotNULL(SmartScriptHolder e, uint data) + { + if (data == 0) + { + Log.outError(LogFilter.ScriptsAi, "SmartAIMgr: Entry {0} SourceType {1} Event {2} Action {3} Parameter can not be NULL, skipped.", e.entryOrGuid, e.GetScriptType(), e.event_id, e.GetActionType()); + return false; + } + return true; + } + bool IsEmoteValid(SmartScriptHolder e, uint entry) + { + if (!CliDB.EmotesStorage.ContainsKey(entry)) + { + Log.outError(LogFilter.ScriptsAi, "SmartAIMgr: Entry {0} SourceType {1} Event {2} Action {3} uses non-existent Emote entry {4}, skipped.", e.entryOrGuid, e.GetScriptType(), e.event_id, e.GetActionType(), entry); + return false; + } + return true; + } + bool IsItemValid(SmartScriptHolder e, uint entry) + { + if (!CliDB.ItemSparseStorage.ContainsKey(entry)) + { + Log.outError(LogFilter.ScriptsAi, "SmartAIMgr: Entry {0} SourceType {1} Event {2} Action {3} uses non-existent Item entry {4}, skipped.", e.entryOrGuid, e.GetScriptType(), e.event_id, e.GetActionType(), entry); + return false; + } + return true; + } + bool IsTextEmoteValid(SmartScriptHolder e, uint entry) + { + if (!CliDB.EmotesTextStorage.ContainsKey(entry)) + { + Log.outError(LogFilter.ScriptsAi, "SmartAIMgr: Entry {0} SourceType {1} Event {2} Action {3} uses non-existent Text Emote entry {4}, skipped.", e.entryOrGuid, e.GetScriptType(), e.event_id, e.GetActionType(), entry); + return false; + } + return true; + } + bool IsAreaTriggerValid(SmartScriptHolder e, uint entry) + { + if (!CliDB.AreaTriggerStorage.ContainsKey(entry)) + { + Log.outError(LogFilter.ScriptsAi, "SmartAIMgr: Entry {0} SourceType {1} Event {2} Action {3} uses non-existent AreaTrigger entry {4}, skipped.", e.entryOrGuid, e.GetScriptType(), e.event_id, e.GetActionType(), entry); + return false; + } + return true; + } + bool IsSoundValid(SmartScriptHolder e, uint entry) + { + if (!CliDB.SoundKitStorage.ContainsKey(entry)) + { + Log.outError(LogFilter.ScriptsAi, "SmartAIMgr: Entry {0} SourceType {1} Event {2} Action {3} uses non-existent Sound entry {4}, skipped.", e.entryOrGuid, e.GetScriptType(), e.event_id, e.GetActionType(), entry); + return false; + } + return true; + } + + public List GetScript(int entry, SmartScriptType type) + { + List temp = new List(); + if (mEventMap[(uint)type].ContainsKey(entry)) + { + foreach (var holder in mEventMap[(uint)type][entry]) + temp.Add(new SmartScriptHolder(holder)); + } + else + { + if (entry > 0)//first search is for guid (negative), do not drop error if not found + Log.outDebug(LogFilter.ScriptsAi, "SmartAIMgr.GetScript: Could not load Script for Entry {0} ScriptType {1}.", entry, type); + } + + return temp; + } + + public Dictionary GetPath(uint id) + { + return waypoint_map.LookupByKey(id); + } + + public SmartScriptHolder FindLinkedSourceEvent(List list, uint eventId) + { + var sch = list.Find(p => p.link == eventId); + if (sch != null) + return sch; + + return null; + } + + public SmartScriptHolder FindLinkedEvent(List list, uint link) + { + var sch = list.Find(p => p.event_id == link && p.GetEventType() == SmartEvents.Link); + if (sch != null) + return sch; + + return null; + } + + MultiMap[] mEventMap = new MultiMap[(int)SmartScriptType.Max]; + Dictionary> waypoint_map = new Dictionary>(); + + Dictionary SmartAITypeMask = new Dictionary + { + { SmartScriptType.Creature, SmartScriptTypeMaskId.Creature }, + { SmartScriptType.GameObject, SmartScriptTypeMaskId.Gameobject }, + { SmartScriptType.AreaTrigger, SmartScriptTypeMaskId.Areatrigger }, + { SmartScriptType.Event, SmartScriptTypeMaskId.Event }, + { SmartScriptType.Gossip, SmartScriptTypeMaskId.Gossip }, + { SmartScriptType.Quest, SmartScriptTypeMaskId.Quest }, + { SmartScriptType.Spell, SmartScriptTypeMaskId.Spell }, + { SmartScriptType.Transport, SmartScriptTypeMaskId.Transport }, + { SmartScriptType.Instance, SmartScriptTypeMaskId.Instance }, + { SmartScriptType.TimedActionlist, SmartScriptTypeMaskId.TimedActionlist }, + { SmartScriptType.Scene, SmartScriptTypeMaskId.Scene } + }; + + Dictionary SmartAIEventMask = new Dictionary + { + { SmartEvents.UpdateIc, SmartScriptTypeMaskId.Creature + SmartScriptTypeMaskId.TimedActionlist }, + { SmartEvents.UpdateOoc, SmartScriptTypeMaskId.Creature + SmartScriptTypeMaskId.Gameobject + SmartScriptTypeMaskId.Instance }, + { SmartEvents.HealtPct, SmartScriptTypeMaskId.Creature }, + { SmartEvents.ManaPct, SmartScriptTypeMaskId.Creature }, + { SmartEvents.Aggro, SmartScriptTypeMaskId.Creature }, + { SmartEvents.Kill, SmartScriptTypeMaskId.Creature }, + { SmartEvents.Death, SmartScriptTypeMaskId.Creature }, + { SmartEvents.Evade, SmartScriptTypeMaskId.Creature }, + { SmartEvents.SpellHit, SmartScriptTypeMaskId.Creature + SmartScriptTypeMaskId.Gameobject }, + { SmartEvents.Range, SmartScriptTypeMaskId.Creature }, + { SmartEvents.OocLos, SmartScriptTypeMaskId.Creature }, + { SmartEvents.Respawn, SmartScriptTypeMaskId.Creature + SmartScriptTypeMaskId.Gameobject }, + { SmartEvents.TargetHealthPct, SmartScriptTypeMaskId.Creature }, + { SmartEvents.VictimCasting, SmartScriptTypeMaskId.Creature }, + { SmartEvents.FriendlyHealth, SmartScriptTypeMaskId.Creature }, + { SmartEvents.FriendlyIsCc, SmartScriptTypeMaskId.Creature }, + { SmartEvents.FriendlyMissingBuff, SmartScriptTypeMaskId.Creature }, + { SmartEvents.SummonedUnit, SmartScriptTypeMaskId.Creature + SmartScriptTypeMaskId.Gameobject }, + { SmartEvents.TargetManaPct, SmartScriptTypeMaskId.Creature }, + { SmartEvents.AcceptedQuest, SmartScriptTypeMaskId.Creature + SmartScriptTypeMaskId.Gameobject }, + { SmartEvents.RewardQuest, SmartScriptTypeMaskId.Creature + SmartScriptTypeMaskId.Gameobject }, + { SmartEvents.ReachedHome, SmartScriptTypeMaskId.Creature }, + { SmartEvents.ReceiveEmote, SmartScriptTypeMaskId.Creature }, + { SmartEvents.HasAura, SmartScriptTypeMaskId.Creature }, + { SmartEvents.TargetBuffed, SmartScriptTypeMaskId.Creature }, + { SmartEvents.Reset, SmartScriptTypeMaskId.Creature }, + { SmartEvents.IcLos, SmartScriptTypeMaskId.Creature }, + { SmartEvents.PassengerBoarded, SmartScriptTypeMaskId.Creature }, + { SmartEvents.PassengerRemoved, SmartScriptTypeMaskId.Creature }, + { SmartEvents.Charmed, SmartScriptTypeMaskId.Creature }, + { SmartEvents.CharmedTarget, SmartScriptTypeMaskId.Creature }, + { SmartEvents.SpellhitTarget, SmartScriptTypeMaskId.Creature }, + { SmartEvents.Damaged, SmartScriptTypeMaskId.Creature }, + { SmartEvents.DamagedTarget, SmartScriptTypeMaskId.Creature }, + { SmartEvents.Movementinform, SmartScriptTypeMaskId.Creature }, + { SmartEvents.SummonDespawned, SmartScriptTypeMaskId.Creature + SmartScriptTypeMaskId.Gameobject }, + { SmartEvents.CorpseRemoved, SmartScriptTypeMaskId.Creature }, + { SmartEvents.AiInit, SmartScriptTypeMaskId.Creature + SmartScriptTypeMaskId.Gameobject }, + { SmartEvents.DataSet, SmartScriptTypeMaskId.Creature + SmartScriptTypeMaskId.Gameobject }, + { SmartEvents.WaypointStart, SmartScriptTypeMaskId.Creature }, + { SmartEvents.WaypointReached, SmartScriptTypeMaskId.Creature }, + { SmartEvents.TransportAddplayer, SmartScriptTypeMaskId.Transport }, + { SmartEvents.TransportAddcreature, SmartScriptTypeMaskId.Transport }, + { SmartEvents.TransportRemovePlayer, SmartScriptTypeMaskId.Transport }, + { SmartEvents.TransportRelocate, SmartScriptTypeMaskId.Transport }, + { SmartEvents.InstancePlayerEnter, SmartScriptTypeMaskId.Instance }, + { SmartEvents.AreatriggerOntrigger, SmartScriptTypeMaskId.Areatrigger }, + { SmartEvents.QuestAccepted, SmartScriptTypeMaskId.Quest }, + { SmartEvents.QuestObjCompletion, SmartScriptTypeMaskId.Quest }, + { SmartEvents.QuestRewarded, SmartScriptTypeMaskId.Quest }, + { SmartEvents.QuestCompletion, SmartScriptTypeMaskId.Quest }, + { SmartEvents.QuestFail, SmartScriptTypeMaskId.Quest }, + { SmartEvents.TextOver, SmartScriptTypeMaskId.Creature + SmartScriptTypeMaskId.Gameobject }, + { SmartEvents.ReceiveHeal, SmartScriptTypeMaskId.Creature }, + { SmartEvents.JustSummoned, SmartScriptTypeMaskId.Creature }, + { SmartEvents.WaypointPaused, SmartScriptTypeMaskId.Creature }, + { SmartEvents.WaypointResumed, SmartScriptTypeMaskId.Creature }, + { SmartEvents.WaypointStopped, SmartScriptTypeMaskId.Creature }, + { SmartEvents.WaypointEnded, SmartScriptTypeMaskId.Creature }, + { SmartEvents.TimedEventTriggered, SmartScriptTypeMaskId.Creature + SmartScriptTypeMaskId.Gameobject }, + { SmartEvents.Update, SmartScriptTypeMaskId.Creature + SmartScriptTypeMaskId.Gameobject }, + { SmartEvents.Link, SmartScriptTypeMaskId.Creature + SmartScriptTypeMaskId.Gameobject + SmartScriptTypeMaskId.Areatrigger + SmartScriptTypeMaskId.Event + SmartScriptTypeMaskId.Gossip + SmartScriptTypeMaskId.Quest + SmartScriptTypeMaskId.Spell + SmartScriptTypeMaskId.Transport + SmartScriptTypeMaskId.Instance }, + { SmartEvents.GossipSelect, SmartScriptTypeMaskId.Creature + SmartScriptTypeMaskId.Gameobject }, + { SmartEvents.JustCreated, SmartScriptTypeMaskId.Creature + SmartScriptTypeMaskId.Gameobject }, + { SmartEvents.GossipHello, SmartScriptTypeMaskId.Creature + SmartScriptTypeMaskId.Gameobject }, + { SmartEvents.FollowCompleted, SmartScriptTypeMaskId.Creature }, + { SmartEvents.DummyEffect, SmartScriptTypeMaskId.Spell }, + { SmartEvents.IsBehindTarget, SmartScriptTypeMaskId.Creature }, + { SmartEvents.GameEventStart, SmartScriptTypeMaskId.Creature + SmartScriptTypeMaskId.Gameobject }, + { SmartEvents.GameEventEnd, SmartScriptTypeMaskId.Creature + SmartScriptTypeMaskId.Gameobject }, + { SmartEvents.GoStateChanged, SmartScriptTypeMaskId.Gameobject }, + { SmartEvents.GoEventInform, SmartScriptTypeMaskId.Gameobject }, + { SmartEvents.ActionDone, SmartScriptTypeMaskId.Creature }, + { SmartEvents.OnSpellclick, SmartScriptTypeMaskId.Creature }, + { SmartEvents.FriendlyHealthPCT, SmartScriptTypeMaskId.Creature }, + { SmartEvents.DistanceCreature, SmartScriptTypeMaskId.Creature }, + { SmartEvents.DistanceGameobject, SmartScriptTypeMaskId.Creature }, + { SmartEvents.CounterSet, SmartScriptTypeMaskId.Creature + SmartScriptTypeMaskId.Gameobject }, + { SmartEvents.SceneStart, SmartScriptTypeMaskId.Scene }, + { SmartEvents.SceneTrigger, SmartScriptTypeMaskId.Scene }, + { SmartEvents.SceneCancel, SmartScriptTypeMaskId.Scene }, + { SmartEvents.SceneComplete, SmartScriptTypeMaskId.Scene } + }; + } + + public class SmartPath + { + public SmartPath(uint _id, float _x, float _y, float _z) + { + id = _id; + x = _x; + y = _y; + z = _z; + } + + public uint id; + public float x; + public float y; + public float z; + } + + public class SmartScriptHolder + { + public SmartScriptHolder() { } + public SmartScriptHolder(SmartScriptHolder other) + { + entryOrGuid = other.entryOrGuid; + source_type = other.source_type; + event_id = other.event_id; + link = other.link; + Event = other.Event; + Action = other.Action; + Target = other.Target; + timer = other.timer; + active = other.active; + runOnce = other.runOnce; + enableTimed = other.enableTimed; + } + + public SmartScriptType GetScriptType() { return source_type; } + public SmartEvents GetEventType() { return Event.type; } + public SmartActions GetActionType() { return Action.type; } + public SmartTargets GetTargetType() { return Target.type; } + + public int entryOrGuid; + public SmartScriptType source_type; + public uint event_id; + public uint link; + public SmartEvent Event; + public SmartAction Action; + public SmartTarget Target; + public uint timer; + public bool active; + public bool runOnce; + public bool enableTimed; + } + + [StructLayout(LayoutKind.Explicit)] + public struct SmartEvent + { + [FieldOffset(0)] + public SmartEvents type; + + [FieldOffset(4)] + public uint event_phase_mask; + + [FieldOffset(8)] + public uint event_chance; + + [FieldOffset(12)] + public SmartEventFlags event_flags; + + [FieldOffset(16)] + public MinMaxRepeat minMaxRepeat; + + [FieldOffset(16)] + public Kill kill; + + [FieldOffset(16)] + public SpellHit spellHit; + + [FieldOffset(16)] + public Los los; + + [FieldOffset(16)] + public Respawn respawn; + + [FieldOffset(16)] + public MinMax minMax; + + [FieldOffset(16)] + public TargetCasting targetCasting; + + [FieldOffset(16)] + public FriendlyHealt friendlyHealth; + + [FieldOffset(16)] + public FriendlyCC friendlyCC; + + [FieldOffset(16)] + public MissingBuff missingBuff; + + [FieldOffset(16)] + public Summoned summoned; + + [FieldOffset(16)] + public Quest quest; + + [FieldOffset(16)] + public Emote emote; + + [FieldOffset(16)] + public Aura aura; + + [FieldOffset(16)] + public Charm charm; + + [FieldOffset(16)] + public TargetAura targetAura; + + [FieldOffset(16)] + public MovementInform movementInform; + + [FieldOffset(16)] + public DataSet dataSet; + + [FieldOffset(16)] + public Waypoint waypoint; + + [FieldOffset(16)] + public TransportAddCreature transportAddCreature; + + [FieldOffset(16)] + public TransportRelocate transportRelocate; + + [FieldOffset(16)] + public InstancePlayerEnter instancePlayerEnter; + + [FieldOffset(16)] + public Areatrigger areatrigger; + + [FieldOffset(16)] + public TextOver textOver; + + [FieldOffset(16)] + public TimedEvent timedEvent; + + [FieldOffset(16)] + public Gossip gossip; + + [FieldOffset(16)] + public Dummy dummy; + + [FieldOffset(16)] + public BehindTarget behindTarget; + + [FieldOffset(16)] + public GameEvent gameEvent; + + [FieldOffset(16)] + public GoStateChanged goStateChanged; + + [FieldOffset(16)] + public EventInform eventInform; + + [FieldOffset(16)] + public DoAction doAction; + + [FieldOffset(16)] + public FriendlyHealthPct friendlyHealthPct; + + [FieldOffset(16)] + public Distance distance; + + [FieldOffset(16)] + public Counter counter; + + [FieldOffset(16)] + public Scene scene; + + [FieldOffset(16)] + public Raw raw; + + [FieldOffset(32)] + public string param_string; + + #region Structs + public struct MinMaxRepeat + { + public uint min; + public uint max; + public uint repeatMin; + public uint repeatMax; + } + public struct Kill + { + public uint cooldownMin; + public uint cooldownMax; + public uint playerOnly; + public uint creature; + } + public struct SpellHit + { + public uint spell; + public uint school; + public uint cooldownMin; + public uint cooldownMax; + } + public struct Los + { + public uint noHostile; + public uint maxDist; + public uint cooldownMin; + public uint cooldownMax; + } + public struct Respawn + { + public uint type; + public uint map; + public uint area; + } + public struct MinMax + { + public uint repeatMin; + public uint repeatMax; + } + public struct TargetCasting + { + public uint repeatMin; + public uint repeatMax; + public uint spellId; + } + public struct FriendlyHealt + { + public uint hpDeficit; + public uint radius; + public uint repeatMin; + public uint repeatMax; + } + public struct FriendlyCC + { + public uint radius; + public uint repeatMin; + public uint repeatMax; + } + public struct MissingBuff + { + public uint spell; + public uint radius; + public uint repeatMin; + public uint repeatMax; + } + public struct Summoned + { + public uint creature; + public uint cooldownMin; + public uint cooldownMax; + } + public struct Quest + { + public uint questId; + } + public struct Emote + { + public uint emoteId; + public uint cooldownMin; + public uint cooldownMax; + } + public struct Aura + { + public uint spell; + public uint count; + public uint repeatMin; + public uint repeatMax; + } + public struct Charm + { + public uint onRemove; + } + public struct TargetAura + { + public uint spell; + public uint count; + public uint repeatMin; + public uint repeatMax; + } + public struct MovementInform + { + public uint type; + public uint id; + } + public struct DataSet + { + public uint id; + public uint value; + public uint cooldownMin; + public uint cooldownMax; + } + public struct Waypoint + { + public uint pointID; + public uint pathID; + } + public struct TransportAddCreature + { + public uint creature; + } + public struct TransportRelocate + { + public uint pointID; + } + public struct InstancePlayerEnter + { + public uint team; + public uint cooldownMin; + public uint cooldownMax; + } + public struct Areatrigger + { + public uint id; + } + public struct TextOver + { + public uint textGroupID; + public uint creatureEntry; + } + public struct TimedEvent + { + public uint id; + } + public struct Gossip + { + public uint sender; + public uint action; + } + public struct Dummy + { + public uint spell; + public uint effIndex; + } + public struct BehindTarget + { + public uint cooldownMin; + public uint cooldownMax; + } + public struct GameEvent + { + public uint gameEventId; + } + public struct GoStateChanged + { + public uint state; + } + public struct EventInform + { + public uint eventId; + } + public struct DoAction + { + public uint eventId; + } + public struct FriendlyHealthPct + { + public uint minHpPct; + public uint maxHpPct; + public uint repeatMin; + public uint repeatMax; + } + public struct Distance + { + public uint guid; + public uint entry; + public uint dist; + public uint repeat; + } + public struct Counter + { + public uint id; + public uint value; + public uint cooldownMin; + public uint cooldownMax; + } + public struct Scene + { + public uint sceneId; + } + public struct Raw + { + public uint param1; + public uint param2; + public uint param3; + public uint param4; + } + #endregion + } + + [StructLayout(LayoutKind.Explicit)] + public struct SmartAction + { + [FieldOffset(0)] + public SmartActions type; + + [FieldOffset(4)] + public Talk talk; + + [FieldOffset(4)] + public Faction faction; + + [FieldOffset(4)] + public MorphOrMount morphOrMount; + + [FieldOffset(4)] + public Sound sound; + + [FieldOffset(4)] + public Emote emote; + + [FieldOffset(4)] + public Quest quest; + + [FieldOffset(4)] + public QuestOffer questOffer; + + [FieldOffset(4)] + public React react; + + [FieldOffset(4)] + public RandomEmote randomEmote; + + [FieldOffset(4)] + public Cast cast; + + [FieldOffset(4)] + public CrossCast crossCast; + + [FieldOffset(4)] + public SummonCreature summonCreature; + + [FieldOffset(4)] + public ThreatPCT threatPCT; + + [FieldOffset(4)] + public CastCreatureOrGO castCreatureOrGO; + + [FieldOffset(4)] + public AddUnitFlag addUnitFlag; + + [FieldOffset(4)] + public RemoveUnitFlag removeUnitFlag; + + [FieldOffset(4)] + public AutoAttack autoAttack; + + [FieldOffset(4)] + public CombatMove combatMove; + + [FieldOffset(4)] + public SetEventPhase setEventPhase; + + [FieldOffset(4)] + public IncEventPhase incEventPhase; + + [FieldOffset(4)] + public CastedCreatureOrGO castedCreatureOrGO; + + [FieldOffset(4)] + public RemoveAura removeAura; + + [FieldOffset(4)] + public Follow follow; + + [FieldOffset(4)] + public RandomPhase randomPhase; + + [FieldOffset(4)] + public RandomPhaseRange randomPhaseRange; + + [FieldOffset(4)] + public KilledMonster killedMonster; + + [FieldOffset(4)] + public SetInstanceData setInstanceData; + + [FieldOffset(4)] + public SetInstanceData64 setInstanceData64; + + [FieldOffset(4)] + public UpdateTemplate updateTemplate; + + [FieldOffset(4)] + public CallHelp callHelp; + + [FieldOffset(4)] + public SetSheath setSheath; + + [FieldOffset(4)] + public ForceDespawn forceDespawn; + + [FieldOffset(4)] + public InvincHP invincHP; + + [FieldOffset(4)] + public IngamePhaseId ingamePhaseId; + + [FieldOffset(4)] + public IngamePhaseGroup ingamePhaseGroup; + + [FieldOffset(4)] + public SetData setData; + + [FieldOffset(4)] + public MoveRandom moveRandom; + + [FieldOffset(4)] + public Visibility visibility; + + [FieldOffset(4)] + public SummonGO summonGO; + + [FieldOffset(4)] + public Active active; + + [FieldOffset(4)] + public Taxi taxi; + + [FieldOffset(4)] + public WpStart wpStart; + + [FieldOffset(4)] + public WpPause wpPause; + + [FieldOffset(4)] + public WpStop wpStop; + + [FieldOffset(4)] + public Item item; + + [FieldOffset(4)] + public InstallTtemplate installTtemplate; + + [FieldOffset(4)] + public SetRun setRun; + + [FieldOffset(4)] + public SetFly setFly; + + [FieldOffset(4)] + public SetSwim setSwim; + + [FieldOffset(4)] + public Teleport teleport; + + [FieldOffset(4)] + public SetCounter setCounter; + + [FieldOffset(4)] + public StoreVar storeVar; + + [FieldOffset(4)] + public StoreTargets storeTargets; + + [FieldOffset(4)] + public TimeEvent timeEvent; + + [FieldOffset(4)] + public Movie movie; + + [FieldOffset(4)] + public Equip equip; + + [FieldOffset(4)] + public UnitFlag unitFlag; + + [FieldOffset(4)] + public SetunitByte setunitByte; + + [FieldOffset(4)] + public DelunitByte delunitByte; + + [FieldOffset(4)] + public EnterVehicle enterVehicle; + + [FieldOffset(4)] + public TimedActionList timedActionList; + + [FieldOffset(4)] + public RandTimedActionList randTimedActionList; + + [FieldOffset(4)] + public InterruptSpellCasting interruptSpellCasting; + + [FieldOffset(4)] + public SendGoCustomAnim sendGoCustomAnim; + + [FieldOffset(4)] + public Jump jump; + + [FieldOffset(4)] + public Flee flee; + + [FieldOffset(4)] + public RespawnTarget respawnTarget; + + [FieldOffset(4)] + public MoveToPos moveToPos; + + [FieldOffset(4)] + public SendGossipMenu sendGossipMenu; + + [FieldOffset(4)] + public SetGoLootState setGoLootState; + + [FieldOffset(4)] + public SendTargetToTarget sendTargetToTarget; + + [FieldOffset(4)] + public SetRangedMovement setRangedMovement; + + [FieldOffset(4)] + public SetHealthRegen setHealthRegen; + + [FieldOffset(4)] + public SetRoot setRoot; + + [FieldOffset(4)] + public GoFlag goFlag; + + [FieldOffset(4)] + public CreatureGroup creatureGroup; + + [FieldOffset(4)] + public Power power; + + [FieldOffset(4)] + public GameEventStop gameEventStop; + + [FieldOffset(4)] + public GameEventStart gameEventStart; + + [FieldOffset(4)] + public ClosestWaypointFromList closestWaypointFromList; + + [FieldOffset(4)] + public RandomSound randomSound; + + [FieldOffset(4)] + public CorpseDelay corpseDelay; + + [FieldOffset(4)] + public DisableEvade disableEvade; + + [FieldOffset(4)] + public AnimKit animKit; + + [FieldOffset(4)] + public Scene scene; + + [FieldOffset(4)] + public Raw raw; + + #region Stucts + public struct Talk + { + public uint textGroupId; + public uint duration; + public uint useTalkTarget; + } + public struct Faction + { + public uint factionID; + } + public struct MorphOrMount + { + public uint creature; + public uint model; + } + public struct Sound + { + public uint soundId; + public uint onlySelf; + } + public struct Emote + { + public uint emoteId; + } + public struct Quest + { + public uint questId; + } + public struct QuestOffer + { + public uint questId; + public uint directAdd; + } + public struct React + { + public uint state; + } + public struct RandomEmote + { + public uint emote1; + public uint emote2; + public uint emote3; + public uint emote4; + public uint emote5; + public uint emote6; + } + public struct Cast + { + public uint spell; + public uint castFlags; + public uint triggerFlags; + } + public struct CrossCast + { + public uint spell; + public uint castFlags; + public uint targetType; + public uint targetParam1; + public uint targetParam2; + public uint targetParam3; + } + public struct SummonCreature + { + public uint creature; + public uint type; + public uint duration; + public uint storageID; + public uint attackInvoker; + } + public struct ThreatPCT + { + public uint threatINC; + public uint threatDEC; + } + public struct CastCreatureOrGO + { + public uint quest; + public uint spell; + } + public struct AddUnitFlag + { + public uint flag1; + public uint flag2; + public uint flag3; + public uint flag4; + public uint flag5; + public uint flag6; + } + public struct RemoveUnitFlag + { + public uint flag1; + public uint flag2; + public uint flag3; + public uint flag4; + public uint flag5; + public uint flag6; + } + public struct AutoAttack + { + public uint attack; + } + public struct CombatMove + { + public uint move; + } + public struct SetEventPhase + { + public uint phase; + } + public struct IncEventPhase + { + public uint inc; + public uint dec; + } + public struct CastedCreatureOrGO + { + public uint creature; + public uint spell; + } + public struct RemoveAura + { + public uint spell; + } + public struct Follow + { + public uint dist; + public uint angle; + public uint entry; + public uint credit; + public uint creditType; + } + public struct RandomPhase + { + public uint phase1; + public uint phase2; + public uint phase3; + public uint phase4; + public uint phase5; + public uint phase6; + } + public struct RandomPhaseRange + { + public uint phaseMin; + public uint phaseMax; + } + public struct KilledMonster + { + public uint creature; + } + public struct SetInstanceData + { + public uint field; + public uint data; + public uint type; + } + public struct SetInstanceData64 + { + public uint field; + } + public struct UpdateTemplate + { + public uint creature; + public uint updateLevel; + } + public struct CallHelp + { + public uint range; + public uint withEmote; + } + public struct SetSheath + { + public uint sheath; + } + public struct ForceDespawn + { + public uint delay; + } + public struct InvincHP + { + public uint minHP; + public uint percent; + } + public struct IngamePhaseId + { + public uint id; + public uint apply; + } + public struct IngamePhaseGroup + { + public uint groupId; + public uint apply; + } + public struct SetData + { + public uint field; + public uint data; + } + public struct MoveRandom + { + public uint distance; + } + public struct Visibility + { + public uint state; + } + public struct SummonGO + { + public uint entry; + public uint despawnTime; + } + public struct Active + { + public uint state; + } + public struct Taxi + { + public uint id; + } + public struct WpStart + { + public uint run; + public uint pathID; + public uint repeat; + public uint quest; + public uint despawnTime; + public uint reactState; + } + public struct WpPause + { + public uint delay; + } + public struct WpStop + { + public uint despawnTime; + public uint quest; + public uint fail; + } + public struct Item + { + public uint entry; + public uint count; + } + public struct InstallTtemplate + { + public uint id; + public uint param1; + public uint param2; + public uint param3; + public uint param4; + public uint param5; + } + public struct SetRun + { + public uint run; + } + public struct SetFly + { + public uint fly; + } + public struct SetSwim + { + public uint swim; + } + public struct Teleport + { + public uint mapID; + } + public struct SetCounter + { + public uint counterId; + public uint value; + public uint reset; + } + public struct StoreVar + { + public uint id; + public uint number; + } + public struct StoreTargets + { + public uint id; + } + public struct TimeEvent + { + public uint id; + public uint min; + public uint max; + public uint repeatMin; + public uint repeatMax; + public uint chance; + } + public struct Movie + { + public uint entry; + } + + public struct Equip + { + public uint entry; + public uint mask; + public uint slot1; + public uint slot2; + public uint slot3; + } + public struct UnitFlag + { + public uint flag; + public uint type; + } + public struct SetunitByte + { + public uint byte1; + public uint type; + } + public struct DelunitByte + { + public uint byte1; + public uint type; + } + public struct EnterVehicle + { + public uint seat; + } + public struct TimedActionList + { + public uint id; + public uint timerType; + } + public struct RandTimedActionList + { + public uint entry1; + public uint entry2; + public uint entry3; + public uint entry4; + public uint entry5; + public uint entry6; + } + public struct InterruptSpellCasting + { + public uint withDelayed; + public uint spell_id; + public uint withInstant; + } + public struct SendGoCustomAnim + { + public uint anim; + } + public struct Jump + { + public uint speedxy; + public uint speedz; + } + public struct Flee + { + public uint withEmote; + } + public struct RespawnTarget + { + public uint goRespawnTime; + } + public struct MoveToPos + { + public uint pointId; + public uint transport; + public uint disablePathfinding; + } + public struct SendGossipMenu + { + public uint gossipMenuId; + public uint gossipNpcTextId; + } + public struct SetGoLootState + { + public uint state; + } + public struct SendTargetToTarget + { + public uint id; + } + public struct SetRangedMovement + { + public uint distance; + public uint angle; + } + public struct SetHealthRegen + { + public uint regenHealth; + } + public struct SetRoot + { + public uint root; + } + public struct GoFlag + { + public uint flag; + } + public struct CreatureGroup + { + public uint group; + public uint attackInvoker; + } + public struct Power + { + public uint powerType; + public uint newPower; + } + public struct GameEventStop + { + public uint id; + } + public struct GameEventStart + { + public uint id; + } + public struct ClosestWaypointFromList + { + public uint wp1; + public uint wp2; + public uint wp3; + public uint wp4; + public uint wp5; + public uint wp6; + } + public struct RandomSound + { + public uint sound1; + public uint sound2; + public uint sound3; + public uint sound4; + public uint sound5; + public uint onlySelf; + } + public struct CorpseDelay + { + public uint timer; + } + public struct DisableEvade + { + public uint disable; + } + public struct AnimKit + { + public uint animKit; + public uint type; + } + public struct Scene + { + public uint sceneId; + } + public struct Raw + { + public uint param1; + public uint param2; + public uint param3; + public uint param4; + public uint param5; + public uint param6; + } + #endregion + } + + [StructLayout(LayoutKind.Explicit)] + public struct SmartTarget + { + [FieldOffset(0)] + public SmartTargets type; + + [FieldOffset(4)] + public float x; + + [FieldOffset(8)] + public float y; + + [FieldOffset(12)] + public float z; + + [FieldOffset(16)] + public float o; + + [FieldOffset(20)] + public UnitRange unitRange; + + [FieldOffset(20)] + public UnitGUID unitGUID; + + [FieldOffset(20)] + public UnitDistance unitDistance; + + [FieldOffset(20)] + public PlayerDistance playerDistance; + + [FieldOffset(20)] + public PlayerRange playerRange; + + [FieldOffset(20)] + public Stored stored; + + [FieldOffset(20)] + public GoRange goRange; + + [FieldOffset(20)] + public GoGUID goGUID; + + [FieldOffset(20)] + public GoDistance goDistance; + + [FieldOffset(20)] + public Position postion; + + [FieldOffset(20)] + public Closest closest; + + [FieldOffset(20)] + public ClosestAttackable closestAttackable; + + [FieldOffset(20)] + public ClosestFriendly closestFriendly; + + [FieldOffset(20)] + public Vehicle vehicle; + + [FieldOffset(20)] + public Raw raw; + + #region Structs + + public struct UnitRange + { + public uint creature; + public uint minDist; + public uint maxDist; + } + public struct UnitGUID + { + public uint dbGuid; + public uint entry; + } + public struct UnitDistance + { + public uint creature; + public uint dist; + } + public struct PlayerDistance + { + public uint dist; + } + public struct PlayerRange + { + public uint minDist; + public uint maxDist; + } + public struct Stored + { + public uint id; + } + public struct GoRange + { + public uint entry; + public uint minDist; + public uint maxDist; + } + public struct GoGUID + { + public uint dbGuid; + public uint entry; + } + public struct GoDistance + { + public uint entry; + public uint dist; + } + public struct Position + { + public uint map; + } + public struct Closest + { + public uint entry; + public uint dist; + public uint dead; + } + public struct ClosestAttackable + { + public uint maxDist; + } + public struct ClosestFriendly + { + public uint maxDist; + } + public struct Vehicle + { + public uint seat; + } + public struct Raw + { + public uint param1; + public uint param2; + public uint param3; + } + #endregion + } +} diff --git a/Game/AI/SmartScripts/SmartScript.cs b/Game/AI/SmartScripts/SmartScript.cs new file mode 100644 index 000000000..3df3ba3a0 --- /dev/null +++ b/Game/AI/SmartScripts/SmartScript.cs @@ -0,0 +1,4057 @@ +/* + * Copyright (C) 2012-2017 CypherCore + * + * 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, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +using Framework.Constants; +using Framework.GameMath; +using Game.Chat; +using Game.DataStorage; +using Game.Entities; +using Game.Groups; +using Game.Maps; +using Game.Misc; +using Game.Spells; +using System; +using System.Collections.Generic; +using System.Linq; + +namespace Game.AI +{ + public class SmartScript + { + public SmartScript() + { + go = null; + me = null; + trigger = null; + mEventPhase = 0; + mPathId = 0; + mTargetStorage = new MultiMap(); + mTextTimer = 0; + mLastTextID = 0; + mTextGUID = ObjectGuid.Empty; + mUseTextTimer = false; + mTalkerEntry = 0; + mTemplate = SmartAITemplate.Basic; + meOrigGUID = ObjectGuid.Empty; + goOrigGUID = ObjectGuid.Empty; + mLastInvoker = ObjectGuid.Empty; + mScriptType = SmartScriptType.Creature; + } + + public void OnReset() + { + SetPhase(0); + ResetBaseObject(); + foreach (var holder in mEvents) + { + if (!holder.Event.event_flags.HasAnyFlag(SmartEventFlags.DontReset)) + { + InitTimer(holder); + holder.runOnce = false; + } + } + ProcessEventsFor(SmartEvents.Reset); + mLastInvoker = ObjectGuid.Empty; + } + + public void ProcessEventsFor(SmartEvents e, Unit unit = null, uint var0 = 0, uint var1 = 0, bool bvar = false, SpellInfo spell = null, GameObject gob = null, string varString = "") + { + foreach (var Event in mEvents) + { + SmartEvents eventType = Event.GetEventType(); + if (eventType == SmartEvents.Link)//special handling + continue; + + if (eventType == e) + if (Global.ConditionMgr.IsObjectMeetingSmartEventConditions(Event.entryOrGuid, Event.event_id, Event.source_type, unit, GetBaseObject())) + ProcessEvent(Event, unit, var0, var1, bvar, spell, gob, varString); + } + } + + void ProcessAction(SmartScriptHolder e, Unit unit = null, uint var0 = 0, uint var1 = 0, bool bvar = false, SpellInfo spell = null, GameObject gob = null, string varString = "") + { + //calc random + if (e.GetEventType() != SmartEvents.Link && e.Event.event_chance < 100 && e.Event.event_chance != 0) + { + uint rnd = RandomHelper.URand(1, 100); + if (e.Event.event_chance <= rnd) + return; + } + e.runOnce = true;//used for repeat check + + if (unit != null) + mLastInvoker = unit.GetGUID(); + + Unit tempInvoker = GetLastInvoker(); + if (tempInvoker != null) + Log.outDebug(LogFilter.ScriptsAi, "SmartScript.ProcessAction: Invoker: {0} (guidlow: {1})", tempInvoker.GetName(), tempInvoker.GetGUID().ToString()); + + switch (e.GetActionType()) + { + case SmartActions.Talk: + { + List targets = GetTargets(e, unit); + Creature talker = me; + Player targetPlayer = null; + Unit talkTarget = null; + + if (!targets.Empty()) + { + foreach (var obj in targets) + { + if (IsCreature(obj) && !obj.ToCreature().IsPet()) // Prevented sending text to pets. + { + if (e.Action.talk.useTalkTarget != 0) + talkTarget = obj.ToCreature(); + else + talker = obj.ToCreature(); + break; + } + else if (IsPlayer(obj)) + { + targetPlayer = obj.ToPlayer(); + break; + } + } + } + + if (!talker) + break; + + mTalkerEntry = talker.GetEntry(); + mLastTextID = e.Action.talk.textGroupId; + mTextTimer = e.Action.talk.duration; + + if (IsPlayer(GetLastInvoker())) // used for $vars in texts and whisper target + talkTarget = GetLastInvoker(); + else if (targetPlayer != null) + talkTarget = targetPlayer; + + mUseTextTimer = true; + Global.CreatureTextMgr.SendChat(talker, (byte)e.Action.talk.textGroupId, talkTarget); + Log.outDebug(LogFilter.ScriptsAi, "SmartScript.ProcessAction: SMART_ACTION_TALK: talker: {0} (Guid: {1}), textGuid: {2}", + talker.GetName(), talker.GetGUID().ToString(), mTextGUID.ToString()); + break; + } + case SmartActions.SimpleTalk: + { + List targets = GetTargets(e, unit); + if (!targets.Empty()) + { + foreach (var obj in targets) + { + if (IsCreature(obj)) + Global.CreatureTextMgr.SendChat(obj.ToCreature(), (byte)e.Action.talk.textGroupId, IsPlayer(GetLastInvoker()) ? GetLastInvoker() : null); + else if (IsPlayer(obj) && me != null) + { + Unit templastInvoker = GetLastInvoker(); + Global.CreatureTextMgr.SendChat(me, (byte)e.Action.talk.textGroupId, IsPlayer(templastInvoker) ? templastInvoker : null, ChatMsg.Addon, Language.Addon, CreatureTextRange.Normal, 0, Team.Other, false, obj.ToPlayer()); + } + Log.outDebug(LogFilter.ScriptsAi, "SmartScript.ProcessAction. SMART_ACTION_SIMPLE_TALK: talker: {0} (GuidLow: {1}), textGroupId: {2}", + obj.GetName(), obj.GetGUID().ToString(), e.Action.talk.textGroupId); + } + + + } + break; + } + case SmartActions.PlayEmote: + { + List targets = GetTargets(e, unit); + if (!targets.Empty()) + { + foreach (var obj in targets) + { + if (IsUnit(obj)) + { + obj.ToUnit().HandleEmoteCommand((Emote)e.Action.emote.emoteId); + Log.outDebug(LogFilter.ScriptsAi, "SmartScript.ProcessAction. SMART_ACTION_PLAY_EMOTE: target: {0} (GuidLow: {1}), emote: {2}", + obj.GetName(), obj.GetGUID().ToString(), e.Action.emote.emoteId); + } + } + } + break; + } + case SmartActions.Sound: + { + List targets = GetTargets(e, unit); + if (!targets.Empty()) + { + foreach (var obj in targets) + { + if (IsUnit(obj)) + { + obj.PlayDirectSound(e.Action.sound.soundId, e.Action.sound.onlySelf != 0 ? obj.ToPlayer() : null); + Log.outDebug(LogFilter.ScriptsAi, "SmartScript.ProcessAction. SMART_ACTION_SOUND: target: {0} (GuidLow: {1}), sound: {2}, onlyself: {3}", + obj.GetName(), obj.GetGUID().ToString(), e.Action.sound.soundId, e.Action.sound.onlySelf); + } + } + } + break; + } + case SmartActions.SetFaction: + { + List targets = GetTargets(e, unit); + if (!targets.Empty()) + { + foreach (var obj in targets) + { + if (IsCreature(obj)) + { + if (e.Action.faction.factionID != 0) + { + obj.ToCreature().SetFaction(e.Action.faction.factionID); + Log.outDebug(LogFilter.ScriptsAi, "SmartScript.ProcessAction. SMART_ACTION_SET_FACTION: Creature entry {0}, GuidLow {1} set faction to {2}", + obj.GetEntry(), obj.GetGUID().ToString(), e.Action.faction.factionID); + } + else + { + CreatureTemplate ci = Global.ObjectMgr.GetCreatureTemplate(obj.ToCreature().GetEntry()); + if (ci != null) + { + if (obj.ToCreature().getFaction() != ci.Faction) + { + obj.ToCreature().SetFaction(ci.Faction); + Log.outDebug(LogFilter.ScriptsAi, "SmartScript.ProcessAction. SMART_ACTION_SET_FACTION: Creature entry {0}, GuidLow {1} set faction to {2}", + obj.GetEntry(), obj.GetGUID().ToString(), ci.Faction); + } + } + } + } + } + + + } + break; + } + case SmartActions.MorphToEntryOrModel: + { + List targets = GetTargets(e, unit); + if (targets.Empty()) + break; + + foreach (var obj in targets) + { + if (!IsCreature(obj)) + continue; + + if (e.Action.morphOrMount.creature != 0 || e.Action.morphOrMount.model != 0) + { + //set model based on entry from creature_template + if (e.Action.morphOrMount.creature != 0) + { + CreatureTemplate ci = Global.ObjectMgr.GetCreatureTemplate(e.Action.morphOrMount.creature); + if (ci != null) + { + uint displayId = ObjectManager.ChooseDisplayId(ci); + obj.ToCreature().SetDisplayId(displayId); + Log.outDebug(LogFilter.ScriptsAi, "SmartScript.ProcessAction. SMART_ACTION_MORPH_TO_ENTRY_OR_MODEL: Creature entry {0}, GuidLow {1} set displayid to {2}", + obj.GetEntry(), obj.GetGUID().ToString(), displayId); + } + } + //if no param1, then use value from param2 (modelId) + else + { + obj.ToCreature().SetDisplayId(e.Action.morphOrMount.model); + Log.outDebug(LogFilter.ScriptsAi, "SmartScript.ProcessAction. SMART_ACTION_MORPH_TO_ENTRY_OR_MODEL: Creature entry {0}, GuidLow {1} set displayid to {2}", + obj.GetEntry(), obj.GetGUID().ToString(), e.Action.morphOrMount.model); + } + } + else + { + obj.ToCreature().DeMorph(); + Log.outDebug(LogFilter.ScriptsAi, "SmartScript.ProcessAction. SMART_ACTION_MORPH_TO_ENTRY_OR_MODEL: Creature entry {0}, GuidLow {1} demorphs.", + obj.GetEntry(), obj.GetGUID().ToString()); + } + } + break; + } + case SmartActions.FailQuest: + { + List targets = GetTargets(e, unit); + if (targets.Empty()) + break; + + foreach (var obj in targets) + { + if (IsPlayer(obj)) + { + obj.ToPlayer().FailQuest(e.Action.quest.questId); + Log.outDebug(LogFilter.ScriptsAi, "SmartScript.ProcessAction. SMART_ACTION_FAIL_QUEST: Player guidLow {0} fails quest {1}", + obj.GetGUID().ToString(), e.Action.quest.questId); + } + } + + + break; + } + case SmartActions.OfferQuest: + { + List targets = GetTargets(e, unit); + if (targets.Empty()) + break; + + foreach (var obj in targets) + { + Player pTarget = obj.ToPlayer(); + if (pTarget) + { + Quest q = Global.ObjectMgr.GetQuestTemplate(e.Action.questOffer.questId); + if (q != null) + { + if (me && e.Action.questOffer.directAdd == 0) + { + if (pTarget.CanTakeQuest(q, true)) + { + WorldSession session = pTarget.GetSession(); + if (session) + { + PlayerMenu menu = new PlayerMenu(session); + menu.SendQuestGiverQuestDetails(q, me.GetGUID(), true); + Log.outDebug(LogFilter.ScriptsAi, "SmartScript.ProcessAction:: SMART_ACTION_OFFER_QUEST: Player {0} - offering quest {1}", pTarget.GetGUID().ToString(), e.Action.questOffer.questId); + } + } + } + else + { + pTarget.AddQuestAndCheckCompletion(q, null); + Log.outDebug(LogFilter.ScriptsAi, "SmartScript.ProcessAction: SMART_ACTION_ADD_QUEST: Player {0} add quest {1}", pTarget.GetGUID().ToString(), e.Action.questOffer.questId); + } + } + } + } + + + break; + } + case SmartActions.SetReactState: + { + if (me == null) + break; + + me.SetReactState((ReactStates)e.Action.react.state); + Log.outDebug(LogFilter.ScriptsAi, "SmartScript.ProcessAction. SMART_ACTION_SET_REACT_STATE: Creature guidLow {0} set reactstate {1}", + me.GetGUID().ToString(), e.Action.react.state); + break; + } + case SmartActions.RandomEmote: + { + List targets = GetTargets(e, unit); + if (targets.Empty()) + break; + + uint[] emotes = + { + e.Action.randomEmote.emote1, + e.Action.randomEmote.emote2, + e.Action.randomEmote.emote3, + e.Action.randomEmote.emote4, + e.Action.randomEmote.emote5, + e.Action.randomEmote.emote6, + }; + uint[] temp = new uint[SharedConst.SmartActionParamCount]; + int count = 0; + for (byte i = 0; i < SharedConst.SmartActionParamCount; i++) + { + if (emotes[i] != 0) + { + temp[count] = emotes[i]; + ++count; + } + } + + foreach (var obj in targets) + { + if (IsUnit(obj)) + { + uint emote = temp[RandomHelper.IRand(0, count)]; + obj.ToUnit().HandleEmoteCommand((Emote)emote); + Log.outDebug(LogFilter.ScriptsAi, "SmartScript.ProcessAction. SMART_ACTION_RANDOM_EMOTE: Creature guidLow {0} handle random emote {1}", + obj.GetGUID().ToString(), emote); + } + } + + + break; + } + case SmartActions.ThreatAllPct: + { + if (me == null) + break; + + var threatList = me.GetThreatManager().getThreatList(); + foreach (var refe in threatList) + { + Unit target = Global.ObjAccessor.GetUnit(me, refe.getUnitGuid()); + if (target != null) + { + me.GetThreatManager().modifyThreatPercent(target, e.Action.threatPCT.threatINC != 0 ? (int)e.Action.threatPCT.threatINC : -(int)e.Action.threatPCT.threatDEC); + Log.outDebug(LogFilter.ScriptsAi, "SmartScript.ProcessAction. SMART_ACTION_THREAT_ALL_PCT: Creature guidLow {0} modify threat for unit {1}, value {2}", + me.GetGUID().ToString(), target.GetGUID().ToString(), e.Action.threatPCT.threatINC != 0 ? (int)e.Action.threatPCT.threatINC : -(int)e.Action.threatPCT.threatDEC); + } + } + break; + } + case SmartActions.ThreatSinglePct: + { + if (me == null) + break; + + List targets = GetTargets(e, unit); + if (targets.Empty()) + break; + + foreach (var obj in targets) + { + if (IsUnit(obj)) + { + me.GetThreatManager().modifyThreatPercent(obj.ToUnit(), e.Action.threatPCT.threatINC != 0 ? (int)e.Action.threatPCT.threatINC : -(int)e.Action.threatPCT.threatDEC); + Log.outDebug(LogFilter.ScriptsAi, "SmartScript.ProcessAction. SMART_ACTION_THREAT_SINGLE_PCT: Creature guidLow {0} modify threat for unit {1}, value {2}", + me.GetGUID().ToString(), obj.GetGUID().ToString(), e.Action.threatPCT.threatINC != 0 ? (int)e.Action.threatPCT.threatINC : -(int)e.Action.threatPCT.threatDEC); + } + } + + + break; + } + case SmartActions.CallAreaexploredoreventhappens: + { + List targets = GetTargets(e, unit); + if (targets.Empty()) + break; + + foreach (var obj in targets) + { + // Special handling for vehicles + if (IsUnit(obj)) + { + Vehicle vehicle = obj.ToUnit().GetVehicleKit(); + if (vehicle != null) + foreach (var seat in vehicle.Seats) + { + Player player = Global.ObjAccessor.FindPlayer(seat.Value.Passenger.Guid); + if (player != null) + player.AreaExploredOrEventHappens(e.Action.quest.questId); + } + } + + if (IsPlayer(obj)) + { + obj.ToPlayer().AreaExploredOrEventHappens(e.Action.quest.questId); + Log.outDebug(LogFilter.ScriptsAi, "SmartScript.ProcessAction. SMART_ACTION_CALL_AREAEXPLOREDOREVENTHAPPENS: {0} credited quest {1}", + obj.GetGUID().ToString(), e.Action.quest.questId); + } + } + + + break; + } + case SmartActions.Cast: + { + List targets = GetTargets(e, unit); + if (targets.Empty()) + break; + + foreach (var obj in targets) + { + if (!IsUnit(obj)) + continue; + + if (!e.Action.cast.castFlags.HasAnyFlag((uint)SmartCastFlags.AuraNotPresent) || !obj.ToUnit().HasAura(e.Action.cast.spell)) + { + TriggerCastFlags triggerFlag = TriggerCastFlags.None; + if (e.Action.cast.castFlags.HasAnyFlag((uint)SmartCastFlags.Triggered)) + { + if (e.Action.cast.triggerFlags != 0) + triggerFlag = (TriggerCastFlags)e.Action.cast.triggerFlags; + else + triggerFlag = TriggerCastFlags.FullMask; + } + + if (me) + { + if (e.Action.cast.castFlags.HasAnyFlag((uint)SmartCastFlags.InterruptPrevious)) + me.InterruptNonMeleeSpells(false); + + if (e.Action.cast.castFlags.HasAnyFlag((uint)SmartCastFlags.CombatMove)) + { + // If cast flag SMARTCAST_COMBAT_MOVE is set combat movement will not be allowed + // unless target is outside spell range, out of mana, or LOS. + + bool _allowMove = false; + SpellInfo spellInfo = Global.SpellMgr.GetSpellInfo(e.Action.cast.spell); + var costs = spellInfo.CalcPowerCost(me, spellInfo.GetSchoolMask()); + bool hasPower = true; + foreach (var cost in costs) + { + if (cost.Power == PowerType.Health) + { + if (me.GetHealth() <= (uint)cost.Amount) + { + hasPower = false; + break; + } + } + else + { + if (me.GetPower(cost.Power) < cost.Amount) + { + hasPower = false; + break; + } + } + } + + if (me.GetDistance(obj) > spellInfo.GetMaxRange(true) || + me.GetDistance(obj) < spellInfo.GetMinRange(true) || + !me.IsWithinLOSInMap(obj) || !hasPower) + _allowMove = true; + + ((SmartAI)me.GetAI()).SetCombatMove(_allowMove); + } + + me.CastSpell(obj.ToUnit(), e.Action.cast.spell, triggerFlag); + } + else if (go) + go.CastSpell(obj.ToUnit(), e.Action.cast.spell, triggerFlag); + + Log.outDebug(LogFilter.ScriptsAi, "SmartScript.ProcessAction. SMART_ACTION_CAST. Creature {0} casts spell {1} on target {2} with castflags {3}", + me.GetGUID().ToString(), e.Action.cast.spell, obj.GetGUID().ToString(), e.Action.cast.castFlags); + } + else + Log.outDebug(LogFilter.ScriptsAi, "Spell {0} not casted because it has flag SMARTCAST_AURA_NOT_PRESENT and the target (Guid: {1} Entry: {2} Type: {3}) already has the aura", + e.Action.cast.spell, obj.GetGUID(), obj.GetEntry(), obj.GetTypeId()); + } + break; + } + case SmartActions.InvokerCast: + { + Unit tempLastInvoker = GetLastInvoker(); + if (tempLastInvoker == null) + break; + + List targets = GetTargets(e, unit); + if (targets.Empty()) + break; + + foreach (var obj in targets) + { + if (!IsUnit(obj)) + continue; + + if (!e.Action.cast.castFlags.HasAnyFlag((uint)SmartCastFlags.AuraNotPresent) || !obj.ToUnit().HasAura(e.Action.cast.spell)) + { + + if (e.Action.cast.castFlags.HasAnyFlag((uint)SmartCastFlags.InterruptPrevious)) + tempLastInvoker.InterruptNonMeleeSpells(false); + + TriggerCastFlags triggerFlag = TriggerCastFlags.None; + if (e.Action.cast.castFlags.HasAnyFlag((uint)SmartCastFlags.Triggered)) + { + if (e.Action.cast.triggerFlags != 0) + triggerFlag = (TriggerCastFlags)e.Action.cast.triggerFlags; + else + triggerFlag = TriggerCastFlags.FullMask; + } + + tempLastInvoker.CastSpell(obj.ToUnit(), e.Action.cast.spell, triggerFlag); + + Log.outDebug(LogFilter.ScriptsAi, "SmartScript.ProcessAction. SMART_ACTION_INVOKER_CAST: Invoker {0} casts spell {1} on target {2} with castflags {3}", + tempLastInvoker.GetGUID().ToString(), e.Action.cast.spell, obj.GetGUID().ToString(), e.Action.cast.castFlags); + } + else + Log.outDebug(LogFilter.ScriptsAi, "Spell {0} not cast because it has flag SMARTCAST_AURA_NOT_PRESENT and the target ({1}) already has the aura", e.Action.cast.spell, obj.GetGUID().ToString()); + } + break; + } + case SmartActions.AddAura: + { + List targets = GetTargets(e, unit); + if (targets.Empty()) + break; + + foreach (var obj in targets) + { + if (IsUnit(obj)) + { + obj.ToUnit().AddAura(e.Action.cast.spell, obj.ToUnit()); + Log.outDebug(LogFilter.ScriptsAi, "SmartScript.ProcessAction. SMART_ACTION_ADD_AURA: Adding aura {0} to unit {1}", + e.Action.cast.spell, obj.GetGUID().ToString()); + } + } + + + break; + } + case SmartActions.ActivateGobject: + { + List targets = GetTargets(e, unit); + if (targets.Empty()) + break; + + foreach (var obj in targets) + { + if (IsGameObject(obj)) + { + // Activate + obj.ToGameObject().SetLootState(LootState.Ready); + obj.ToGameObject().UseDoorOrButton(0, false, unit); + Log.outDebug(LogFilter.ScriptsAi, "SmartScript.ProcessAction. SMART_ACTION_ACTIVATE_GOBJECT. Gameobject {0} (entry: {1}) activated", + obj.GetGUID().ToString(), obj.GetEntry()); + } + } + + + break; + } + case SmartActions.ResetGobject: + { + List targets = GetTargets(e, unit); + if (targets.Empty()) + break; + + foreach (var obj in targets) + { + if (IsGameObject(obj)) + { + obj.ToGameObject().ResetDoorOrButton(); + Log.outDebug(LogFilter.ScriptsAi, "SmartScript.ProcessAction. SMART_ACTION_RESET_GOBJECT. Gameobject {0} (entry: {1}) reset", + obj.GetGUID().ToString(), obj.GetEntry()); + } + } + + + break; + } + case SmartActions.SetEmoteState: + { + List targets = GetTargets(e, unit); + if (targets.Empty()) + break; + + foreach (var obj in targets) + { + if (IsUnit(obj)) + { + obj.ToUnit().SetUInt32Value(UnitFields.NpcEmotestate, e.Action.emote.emoteId); + Log.outDebug(LogFilter.ScriptsAi, "SmartScript.ProcessAction. SMART_ACTION_SET_EMOTE_STATE. Unit {0} set emotestate to {1}", + obj.GetGUID().ToString(), e.Action.emote.emoteId); + } + } + + + break; + } + case SmartActions.SetUnitFlag: + { + List targets = GetTargets(e, unit); + if (targets.Empty()) + break; + + foreach (var obj in targets) + { + if (IsUnit(obj)) + { + if (e.Action.unitFlag.type == 0) + { + obj.ToUnit().SetFlag(UnitFields.Flags, e.Action.unitFlag.flag); + Log.outDebug(LogFilter.ScriptsAi, "SmartScript.ProcessAction. SMART_ACTION_SET_UNIT_FLAG. Unit {0} added flag {1} to UNIT_FIELD_FLAGS", + obj.GetGUID().ToString(), e.Action.unitFlag.flag); + } + else + { + obj.ToUnit().SetFlag(UnitFields.Flags2, e.Action.unitFlag.flag); + Log.outDebug(LogFilter.ScriptsAi, "SmartScript.ProcessAction. SMART_ACTION_SET_UNIT_FLAG. Unit {0} added flag {1} to UNIT_FIELD_FLAGS_2", + obj.GetGUID().ToString(), e.Action.unitFlag.flag); + } + } + } + + + break; + } + case SmartActions.RemoveUnitFlag: + { + List targets = GetTargets(e, unit); + if (targets.Empty()) + break; + + foreach (var obj in targets) + { + if (IsUnit(obj)) + { + if (e.Action.unitFlag.type == 0) + { + obj.ToUnit().RemoveFlag(UnitFields.Flags2, e.Action.unitFlag.flag); + Log.outDebug(LogFilter.ScriptsAi, "SmartScript.ProcessAction. SMART_ACTION_REMOVE_UNIT_FLAG. Unit {0} removed flag {1} to UNIT_FIELD_FLAGS", + obj.GetGUID().ToString(), e.Action.unitFlag.flag); + } + else + { + obj.ToUnit().RemoveFlag(UnitFields.Flags2, e.Action.unitFlag.flag); + Log.outDebug(LogFilter.ScriptsAi, "SmartScript.ProcessAction. SMART_ACTION_REMOVE_UNIT_FLAG. Unit {0} removed flag {1} to UNIT_FIELD_FLAGS_2", + obj.GetGUID().ToString(), e.Action.unitFlag.flag); + } + } + } + + + break; + } + case SmartActions.AutoAttack: + { + if (!IsSmart()) + break; + + ((SmartAI)me.GetAI()).SetAutoAttack(e.Action.autoAttack.attack != 0 ? true : false); + Log.outDebug(LogFilter.ScriptsAi, "SmartScript.ProcessAction. SMART_ACTION_AUTO_ATTACK: Creature: {0} bool on = {1}", + me.GetGUID().ToString(), e.Action.autoAttack.attack); + break; + } + case SmartActions.AllowCombatMovement: + { + if (!IsSmart()) + break; + + bool move = e.Action.combatMove.move != 0 ? true : false; + ((SmartAI)me.GetAI()).SetCombatMove(move); + Log.outDebug(LogFilter.ScriptsAi, "SmartScript.ProcessAction. SMART_ACTION_ALLOW_COMBAT_MOVEMENT: Creature {0} bool on = {1}", + me.GetGUID().ToString(), e.Action.combatMove.move); + break; + } + case SmartActions.SetEventPhase: + { + if (GetBaseObject() == null) + break; + + SetPhase(e.Action.setEventPhase.phase); + Log.outDebug(LogFilter.ScriptsAi, "SmartScript.ProcessAction. SMART_ACTION_SET_EVENT_PHASE: Creature {0} set event phase {1}", + GetBaseObject().GetGUID().ToString(), e.Action.setEventPhase.phase); + break; + } + case SmartActions.IncEventPhase: + { + if (GetBaseObject() == null) + break; + + IncPhase((int)e.Action.incEventPhase.inc); + DecPhase((int)e.Action.incEventPhase.dec); + Log.outDebug(LogFilter.ScriptsAi, "SmartScript.ProcessAction. SMART_ACTION_INC_EVENT_PHASE: Creature {0} inc event phase by {1}, " + + "decrease by {2}", GetBaseObject().GetGUID().ToString(), e.Action.incEventPhase.inc, e.Action.incEventPhase.dec); + break; + } + case SmartActions.Evade: + { + if (me == null) + break; + + me.GetAI().EnterEvadeMode(); + Log.outDebug(LogFilter.ScriptsAi, "SmartScript.ProcessAction. SMART_ACTION_EVADE: Creature {0} EnterEvadeMode", me.GetGUID().ToString()); + break; + } + case SmartActions.FleeForAssist: + { + if (!me) + break; + + me.DoFleeToGetAssistance(); + if (e.Action.flee.withEmote != 0) + { + var builder = new BroadcastTextBuilder(me, ChatMsg.MonsterEmote, (uint)BroadcastTextIds.FleeForAssist); + Global.CreatureTextMgr.SendChatPacket(me, builder, ChatMsg.Emote); + } + Log.outDebug(LogFilter.ScriptsAi, "SmartScript.ProcessAction. SMART_ACTION_FLEE_FOR_ASSIST: Creature {0} DoFleeToGetAssistance", me.GetGUID().ToString()); + break; + } + case SmartActions.CallGroupeventhappens: + { + if (unit == null) + break; + + if (IsPlayer(unit) && GetBaseObject() != null) + { + unit.ToPlayer().GroupEventHappens(e.Action.quest.questId, GetBaseObject()); + Log.outDebug(LogFilter.ScriptsAi, "SmartScript.ProcessAction: SMART_ACTION_CALL_GROUPEVENTHAPPENS: Player {0}, group credit for quest {1}", + unit.GetGUID().ToString(), e.Action.quest.questId); + } + + // Special handling for vehicles + Vehicle vehicle = unit.GetVehicleKit(); + if (vehicle != null) + { + foreach (var seat in vehicle.Seats) + { + Player player = Global.ObjAccessor.FindPlayer(seat.Value.Passenger.Guid); + if (player != null) + player.GroupEventHappens(e.Action.quest.questId, GetBaseObject()); + } + } + break; + } + case SmartActions.CombatStop: + { + if (!me) + break; + + me.CombatStop(true); + Log.outDebug(LogFilter.ScriptsAi, "SmartScript.ProcessAction: SMART_ACTION_COMBAT_STOP: {0} CombatStop", me.GetGUID().ToString()); + break; + } + case SmartActions.Removeaurasfromspell: + { + List targets = GetTargets(e, unit); + if (targets.Empty()) + break; + + foreach (var obj in targets) + { + if (!IsUnit(obj)) + continue; + + if (e.Action.removeAura.spell == 0) + obj.ToUnit().RemoveAllAuras(); + else + obj.ToUnit().RemoveAurasDueToSpell(e.Action.removeAura.spell); + + Log.outDebug(LogFilter.ScriptsAi, "SmartScript.ProcessAction: SMART_ACTION_REMOVEAURASFROMSPELL: Unit {0}, spell {1}", + obj.GetGUID().ToString(), e.Action.removeAura.spell); + } + + + break; + } + case SmartActions.Follow: + { + if (!IsSmart()) + break; + + List targets = GetTargets(e, unit); + if (targets.Empty()) + break; + + foreach (var obj in targets) + { + if (IsUnit(obj)) + { + ((SmartAI)me.GetAI()).SetFollow(obj.ToUnit(), e.Action.follow.dist, e.Action.follow.angle, e.Action.follow.credit, e.Action.follow.entry, e.Action.follow.creditType); + Log.outDebug(LogFilter.ScriptsAi, "SmartScript.ProcessAction: SMART_ACTION_FOLLOW: Creature {0} following target {1}", + me.GetGUID().ToString(), obj.GetGUID().ToString()); + break; + } + } + + + break; + } + case SmartActions.RandomPhase: + { + if (GetBaseObject() == null) + break; + + uint[] phases = + { + e.Action.randomPhase.phase1, + e.Action.randomPhase.phase2, + e.Action.randomPhase.phase3, + e.Action.randomPhase.phase4, + e.Action.randomPhase.phase5, + e.Action.randomPhase.phase6, + }; + uint[] temp = new uint[SharedConst.SmartActionParamCount]; + uint count = 0; + for (byte i = 0; i < SharedConst.SmartActionParamCount; i++) + { + if (phases[i] > 0) + { + temp[count] = phases[i]; + ++count; + } + } + + uint phase = temp[RandomHelper.IRand(0, (int)count)]; + SetPhase(phase); + Log.outDebug(LogFilter.ScriptsAi, "SmartScript.ProcessAction: SMART_ACTION_RANDOM_PHASE: Creature {0} sets event phase to {1}", + GetBaseObject().GetGUID().ToString(), phase); + break; + } + case SmartActions.RandomPhaseRange: + { + if (GetBaseObject() == null) + break; + + uint phase = RandomHelper.URand(e.Action.randomPhaseRange.phaseMin, e.Action.randomPhaseRange.phaseMax); + SetPhase(phase); + Log.outDebug(LogFilter.ScriptsAi, "SmartScript.ProcessAction: SMART_ACTION_RANDOM_PHASE_RANGE: Creature {0} sets event phase to {1}", + GetBaseObject().GetGUID().ToString(), phase); + break; + } + case SmartActions.CallKilledmonster: + { + if (e.Target.type == SmartTargets.None || e.Target.type == SmartTargets.Self) // Loot recipient and his group members + { + if (me == null) + break; + + Player player = me.GetLootRecipient(); + if (player != null) + { + player.RewardPlayerAndGroupAtEvent(e.Action.killedMonster.creature, player); + Log.outDebug(LogFilter.ScriptsAi, "SmartScript.ProcessAction: SMART_ACTION_CALL_KILLEDMONSTER: Player {0}, Killcredit: {1}", + player.GetGUID().ToString(), e.Action.killedMonster.creature); + } + } + else // Specific target type + { + List targets = GetTargets(e, unit); + if (targets.Empty()) + break; + + foreach (var obj in targets) + { + if (IsPlayer(obj)) + { + obj.ToPlayer().KilledMonsterCredit(e.Action.killedMonster.creature); + Log.outDebug(LogFilter.ScriptsAi, "SmartScript.ProcessAction: SMART_ACTION_CALL_KILLEDMONSTER: Player {0}, Killcredit: {1}", + obj.GetGUID().ToString(), e.Action.killedMonster.creature); + } + else if (IsUnit(obj)) // Special handling for vehicles + { + Vehicle vehicle = obj.ToUnit().GetVehicleKit(); + if (vehicle != null) + { + foreach (var seat in vehicle.Seats) + { + Player player = Global.ObjAccessor.FindPlayer(seat.Value.Passenger.Guid); + if (player != null) + player.KilledMonsterCredit(e.Action.killedMonster.creature); + } + } + } + } + } + break; + } + case SmartActions.SetInstData: + { + WorldObject obj = GetBaseObject(); + if (obj == null) + obj = unit; + + if (obj == null) + break; + + InstanceScript instance = obj.GetInstanceScript(); + if (instance == null) + { + Log.outError(LogFilter.Sql, "SmartScript: Event {0} attempt to set instance data without instance script. EntryOrGuid {1}", e.GetEventType(), e.entryOrGuid); + break; + } + + switch (e.Action.setInstanceData.type) + { + case 0: + instance.SetData(e.Action.setInstanceData.field, e.Action.setInstanceData.data); + Log.outDebug(LogFilter.ScriptsAi, "SmartScript.ProcessAction: SMART_ACTION_SET_INST_DATA: SetData Field: {0}, data: {1}", + e.Action.setInstanceData.field, e.Action.setInstanceData.data); + break; + case 1: + instance.SetBossState(e.Action.setInstanceData.field, (EncounterState)e.Action.setInstanceData.data); + Log.outDebug(LogFilter.ScriptsAi, "SmartScript.ProcessAction: SMART_ACTION_SET_INST_DATA: SetBossState BossId: {0}, State: {1} ({2})", + e.Action.setInstanceData.field, e.Action.setInstanceData.data, (EncounterState)e.Action.setInstanceData.data); + break; + default: // Static analysis + break; + } + break; + } + case SmartActions.SetInstData64: + { + WorldObject obj = GetBaseObject(); + if (obj == null) + obj = unit; + + if (obj == null) + break; + + InstanceScript instance = obj.GetInstanceScript(); + if (instance == null) + { + Log.outError(LogFilter.Sql, "SmartScript: Event {0} attempt to set instance data without instance script. EntryOrGuid {1}", e.GetEventType(), e.entryOrGuid); + break; + } + + List targets = GetTargets(e, unit); + if (targets.Empty()) + break; + + instance.SetGuidData(e.Action.setInstanceData64.field, targets.First().GetGUID()); + Log.outDebug(LogFilter.ScriptsAi, "SmartScript.ProcessAction: SMART_ACTION_SET_INST_DATA64: Field: {0}, data: {1}", + e.Action.setInstanceData64.field, targets.First().GetGUID()); + break; + } + case SmartActions.UpdateTemplate: + { + var targets = GetTargets(e, unit); + + if (targets.Empty()) + break; + + foreach (var obj in targets) + if (IsCreature(obj)) + obj.ToCreature().UpdateEntry(e.Action.updateTemplate.creature, null, e.Action.updateTemplate.updateLevel != 0); + break; + } + case SmartActions.Die: + { + if (me != null && !me.IsDead()) + { + me.KillSelf(); + Log.outDebug(LogFilter.ScriptsAi, "SmartScript.ProcessAction: SMART_ACTION_DIE: Creature {0}", me.GetGUID().ToString()); + } + break; + } + case SmartActions.SetInCombatWithZone: + { + if (me != null) + { + me.SetInCombatWithZone(); + Log.outDebug(LogFilter.ScriptsAi, "SmartScript.ProcessAction: SMART_ACTION_SET_IN_COMBAT_WITH_ZONE: Creature {0}", me.GetGUID().ToString()); + } + break; + } + case SmartActions.CallForHelp: + { + if (me != null) + { + me.CallForHelp(e.Action.callHelp.range); + if (e.Action.callHelp.withEmote != 0) + { + var builder = new BroadcastTextBuilder(me, ChatMsg.Emote, (uint)BroadcastTextIds.CallForHelp); + Global.CreatureTextMgr.SendChatPacket(me, builder, ChatMsg.MonsterEmote); + } + Log.outDebug(LogFilter.ScriptsAi, "SmartScript.ProcessAction: SMART_ACTION_CALL_FOR_HELP: Creature {0}", me.GetGUID().ToString()); + } + break; + } + case SmartActions.SetSheath: + { + if (me != null) + { + me.SetSheath((SheathState)e.Action.setSheath.sheath); + Log.outDebug(LogFilter.ScriptsAi, "SmartScript.ProcessAction: SMART_ACTION_SET_SHEATH: Creature {0}, State: {1}", + me.GetGUID().ToString(), e.Action.setSheath.sheath); + } + break; + } + case SmartActions.ForceDespawn: + { + var targets = GetTargets(e, unit); + + if (targets == null) + break; + + foreach (var obj in targets) + { + if (obj.IsTypeId(TypeId.Unit)) + { + Creature target = obj.ToCreature(); + if (target.IsAlive() && IsSmart(target)) + { + ((SmartAI)target.GetAI()).SetDespawnTime(e.Action.forceDespawn.delay + 1); // Next tick + ((SmartAI)target.GetAI()).StartDespawn(); + } + else + target.DespawnOrUnsummon(e.Action.forceDespawn.delay); + } + else if (obj.IsTypeId(TypeId.GameObject)) + { + GameObject goTarget = obj.ToGameObject(); + if (goTarget) + goTarget.SetRespawnTime((int)e.Action.forceDespawn.delay + 1); + } + } + + break; + } + case SmartActions.SetIngamePhaseId: + { + var targets = GetTargets(e, unit); + + if (targets.Empty()) + break; + + foreach (var obj in targets) + obj.SetInPhase(e.Action.ingamePhaseId.id, true, e.Action.ingamePhaseId.apply == 1); + + break; + } + case SmartActions.SetIngamePhaseGroup: + { + var targets = GetTargets(e, unit); + + if (targets.Empty()) + break; + + var phases = Global.DB2Mgr.GetPhasesForGroup(e.Action.ingamePhaseGroup.groupId); + + foreach (var obj in targets) + { + foreach (var phase in phases) + obj.SetInPhase(phase, true, e.Action.ingamePhaseGroup.apply == 1); + } + + break; + } + case SmartActions.MountToEntryOrModel: + { + List targets = GetTargets(e, unit); + if (targets.Empty()) + break; + + foreach (var obj in targets) + { + if (!IsUnit(obj)) + continue; + + if (e.Action.morphOrMount.creature != 0 || e.Action.morphOrMount.model != 0) + { + if (e.Action.morphOrMount.creature > 0) + { + CreatureTemplate cInfo = Global.ObjectMgr.GetCreatureTemplate(e.Action.morphOrMount.creature); + if (cInfo != null) + obj.ToUnit().Mount(ObjectManager.ChooseDisplayId(cInfo)); + } + else + obj.ToUnit().Mount(e.Action.morphOrMount.model); + } + else + obj.ToUnit().Dismount(); + } + + + break; + } + case SmartActions.SetInvincibilityHpLevel: + { + if (me == null) + break; + + SmartAI ai = ((SmartAI)me.GetAI()); + + if (ai == null) + break; + + if (e.Action.invincHP.percent != 0) + ai.SetInvincibilityHpLevel((uint)me.CountPctFromMaxHealth((int)e.Action.invincHP.percent)); + else + ai.SetInvincibilityHpLevel(e.Action.invincHP.minHP); + break; + } + case SmartActions.SetData: + { + List targets = GetTargets(e, unit); + if (targets.Empty()) + break; + + foreach (var obj in targets) + { + if (IsCreature(obj)) + obj.ToCreature().GetAI().SetData(e.Action.setData.field, e.Action.setData.data); + else if (IsGameObject(obj)) + obj.ToGameObject().GetAI().SetData(e.Action.setData.field, e.Action.setData.data); + } + + + break; + } + case SmartActions.MoveOffset: + { + var targets = GetTargets(e, unit); + if (!targets.Empty()) + { + foreach (var obj in targets) + { + if (!IsCreature(obj)) + continue; + + if (!e.Event.event_flags.HasAnyFlag(SmartEventFlags.WhileCharmed) && !IsCreatureInControlOfSelf(obj)) + continue; + + Position pos = obj.GetPosition(); + + // Use forward/backward/left/right cartesian plane movement + float x, y, z, o; + o = pos.GetOrientation(); + x = (float)(pos.GetPositionX() + (Math.Cos(o - (Math.PI / 2)) * e.Target.x) + (Math.Cos(o) * e.Target.y)); + y = (float)(pos.GetPositionY() + (Math.Sin(o - (Math.PI / 2)) * e.Target.x) + (Math.Sin(o) * e.Target.y)); + z = pos.GetPositionZ() + e.Target.z; + obj.ToCreature().GetMotionMaster().MovePoint(EventId.SmartRandomPoint, x, y, z); + } + } + break; + } + case SmartActions.SetVisibility: + { + if (me != null) + me.SetVisible(Convert.ToBoolean(e.Action.visibility.state)); + break; + } + case SmartActions.SetActive: + { + WorldObject baseObj = GetBaseObject(); + if (baseObj != null) + baseObj.setActive(e.Action.active.state != 0); + break; + } + case SmartActions.AttackStart: + { + if (me == null) + break; + + List targets = GetTargets(e, unit); + if (targets.Empty()) + break; + + foreach (var obj in targets) + { + if (IsUnit(obj)) + { + me.GetAI().AttackStart(obj.ToUnit()); + break; + } + } + + + break; + } + case SmartActions.SummonCreature: + { + WorldObject summoner = GetBaseObjectOrUnit(unit); + if (!summoner) + break; + + List targets = GetTargets(e, unit); + if (!targets.Empty()) + { + float x, y, z, o; + foreach (var obj in targets) + { + obj.GetPosition(out x, out y, out z, out o); + x += e.Target.x; + y += e.Target.y; + z += e.Target.z; + o += e.Target.o; + Creature summon = summoner.SummonCreature(e.Action.summonCreature.creature, x, y, z, o, (TempSummonType)e.Action.summonCreature.type, e.Action.summonCreature.duration); + if (summon != null) + if (e.Action.summonCreature.attackInvoker != 0) + summon.GetAI().AttackStart(obj.ToUnit()); + } + } + + if (e.GetTargetType() != SmartTargets.Position) + break; + + Creature summon1 = summoner.SummonCreature(e.Action.summonCreature.creature, e.Target.x, e.Target.y, e.Target.z, e.Target.o, (TempSummonType)e.Action.summonCreature.type, e.Action.summonCreature.duration); + if (summon1 != null) + if (unit != null && e.Action.summonCreature.attackInvoker != 0) + summon1.GetAI().AttackStart(unit); + break; + } + case SmartActions.SummonGo: + { + WorldObject summoner = GetBaseObjectOrUnit(unit); + if (!summoner) + break; + + List targets = GetTargets(e, unit); + if (!targets.Empty()) + { + foreach (var obj in targets) + { + if (!IsUnit(obj)) + continue; + + Position pos = obj.GetPositionWithOffset(new Position(e.Target.x, e.Target.y, e.Target.z, e.Target.o)); + Quaternion rot = new Quaternion(Matrix3.fromEulerAnglesZYX(pos.GetOrientation(), 0.0f, 0.0f)); + summoner.SummonGameObject(e.Action.summonGO.entry, pos, rot, e.Action.summonGO.despawnTime); + } + } + + if (e.GetTargetType() != SmartTargets.Position) + break; + + Quaternion rot1 = new Quaternion(Matrix3.fromEulerAnglesZYX(e.Target.o, 0.0f, 0.0f)); + summoner.SummonGameObject(e.Action.summonGO.entry, new Position(e.Target.x, e.Target.y, e.Target.z, e.Target.o), rot1, e.Action.summonGO.despawnTime); + break; + } + case SmartActions.KillUnit: + { + List targets = GetTargets(e, unit); + if (targets.Empty()) + break; + + foreach (var obj in targets) + { + if (!IsUnit(obj)) + continue; + + obj.ToUnit().KillSelf(); + } + break; + } + case SmartActions.InstallAiTemplate: + { + InstallTemplate(e); + break; + } + case SmartActions.AddItem: + { + List targets = GetTargets(e, unit); + if (targets.Empty()) + break; + + foreach (var obj in targets) + { + if (!IsPlayer(obj)) + continue; + + obj.ToPlayer().AddItem(e.Action.item.entry, e.Action.item.count); + } + break; + } + case SmartActions.RemoveItem: + { + List targets = GetTargets(e, unit); + if (targets.Empty()) + break; + + foreach (var obj in targets) + { + if (!IsPlayer(obj)) + continue; + + obj.ToPlayer().DestroyItemCount(e.Action.item.entry, e.Action.item.count, true); + } + break; + } + case SmartActions.StoreTargetList: + { + List targets = GetTargets(e, unit); + StoreTargetList(targets, e.Action.storeTargets.id); + break; + } + case SmartActions.Teleport: + { + List targets = GetTargets(e, unit); + if (targets.Empty()) + break; + + foreach (var obj in targets) + { + if (IsPlayer(obj)) + obj.ToPlayer().TeleportTo(e.Action.teleport.mapID, e.Target.x, e.Target.y, e.Target.z, e.Target.o); + else if (IsCreature(obj)) + obj.ToCreature().NearTeleportTo(e.Target.x, e.Target.y, e.Target.z, e.Target.o); + } + + + break; + } + case SmartActions.SetFly: + { + if (!IsSmart()) + break; + + ((SmartAI)me.GetAI()).SetFly(e.Action.setFly.fly != 0 ? true : false); + break; + } + case SmartActions.SetRun: + { + if (!IsSmart()) + break; + + ((SmartAI)me.GetAI()).SetRun(e.Action.setRun.run != 0 ? true : false); + break; + } + case SmartActions.SetSwim: + { + if (!IsSmart()) + break; + + ((SmartAI)me.GetAI()).SetSwim(e.Action.setSwim.swim != 0 ? true : false); + break; + } + case SmartActions.SetCounter: + { + var targets = GetTargets(e, unit); + if (!targets.Empty()) + { + foreach (var obj in targets) + { + if (IsCreature(obj)) + { + SmartAI ai = (SmartAI)obj.ToCreature().GetAI(); + if (ai != null) + ai.GetScript().StoreCounter(e.Action.setCounter.counterId, e.Action.setCounter.value, e.Action.setCounter.reset); + else + Log.outError(LogFilter.Sql, "SmartScript: Action target for SMART_ACTION_SET_COUNTER is not using SmartAI, skipping"); + } + else if (IsGameObject(obj)) + { + SmartGameObjectAI ai = (SmartGameObjectAI)obj.ToGameObject().GetAI(); + if (ai != null) + ai.GetScript().StoreCounter(e.Action.setCounter.counterId, e.Action.setCounter.value, e.Action.setCounter.reset); + else + Log.outError(LogFilter.Sql, "SmartScript: Action target for SMART_ACTION_SET_COUNTER is not using SmartGameObjectAI, skipping"); + } + } + } + else + StoreCounter(e.Action.setCounter.counterId, e.Action.setCounter.value, e.Action.setCounter.reset); + + break; + } + case SmartActions.WpStart: + { + if (!IsSmart()) + break; + + bool run = e.Action.wpStart.run != 0 ? true : false; + uint entry = e.Action.wpStart.pathID; + bool repeat = e.Action.wpStart.repeat != 0 ? true : false; + List targets = GetTargets(e, unit); + StoreTargetList(targets, SharedConst.SmartEscortTargets); + me.SetReactState((ReactStates)e.Action.wpStart.reactState); + ((SmartAI)me.GetAI()).StartPath(run, entry, repeat, unit); + + uint quest = e.Action.wpStart.quest; + uint DespawnTime = e.Action.wpStart.despawnTime; + ((SmartAI)me.GetAI()).mEscortQuestID = quest; + ((SmartAI)me.GetAI()).SetDespawnTime(DespawnTime); + break; + } + case SmartActions.WpPause: + { + if (!IsSmart()) + break; + + uint delay = e.Action.wpPause.delay; + ((SmartAI)me.GetAI()).PausePath(delay, e.GetEventType() == SmartEvents.WaypointReached ? false : true); + break; + } + case SmartActions.WpStop: + { + if (!IsSmart()) + break; + + uint DespawnTime = e.Action.wpStop.despawnTime; + uint quest = e.Action.wpStop.quest; + bool fail = e.Action.wpStop.fail != 0 ? true : false; + ((SmartAI)me.GetAI()).StopPath(DespawnTime, quest, fail); + break; + } + case SmartActions.WpResume: + { + if (!IsSmart()) + break; + + ((SmartAI)me.GetAI()).ResumePath(); + break; + } + case SmartActions.SetOrientation: + { + if (me == null) + break; + List targets = GetTargets(e, unit); + if (e.GetTargetType() == SmartTargets.Self) + me.SetFacingTo(me.GetHomePosition().GetOrientation()); + else if (e.GetTargetType() == SmartTargets.Position) + me.SetFacingTo(e.Target.o); + else if (!targets.Empty()) + { + me.SetFacingToObject(targets.First()); + } + + break; + } + case SmartActions.Playmovie: + { + List targets = GetTargets(e, unit); + if (targets.Empty()) + break; + + foreach (var obj in targets) + { + if (!IsPlayer(obj)) + continue; + + obj.ToPlayer().SendMovieStart(e.Action.movie.entry); + } + + + break; + } + case SmartActions.MoveToPos: + { + if (!IsSmart()) + break; + + WorldObject target = null; + + if (e.GetTargetType() == SmartTargets.CreatureRange || e.GetTargetType() == SmartTargets.CreatureGuid || + e.GetTargetType() == SmartTargets.CreatureDistance || e.GetTargetType() == SmartTargets.GameobjectRange || + e.GetTargetType() == SmartTargets.GameobjectGuid || e.GetTargetType() == SmartTargets.GameobjectDistance || + e.GetTargetType() == SmartTargets.ClosestCreature || e.GetTargetType() == SmartTargets.ClosestGameobject || + e.GetTargetType() == SmartTargets.OwnerOrSummoner || e.GetTargetType() == SmartTargets.ActionInvoker || + e.GetTargetType() == SmartTargets.ClosestEnemy || e.GetTargetType() == SmartTargets.ClosestFriendly) + { + List targets = GetTargets(e, unit); + if (targets.Empty()) + break; + + target = targets.First(); + } + + if (!target) + { + float x = e.Target.x; + float y = e.Target.y; + float z = e.Target.z; + float o = 0; + if (e.Action.moveToPos.transport != 0) + { + ITransport trans = me.GetDirectTransport(); + if (trans != null) + trans.CalculatePassengerPosition(ref x, ref y, ref z, ref o); + } + + me.GetMotionMaster().MovePoint(e.Action.moveToPos.pointId, x, y, z, e.Action.moveToPos.disablePathfinding == 0); + } + else + me.GetMotionMaster().MovePoint(e.Action.moveToPos.pointId, target.GetPositionX(), target.GetPositionY(), target.GetPositionZ(), e.Action.moveToPos.disablePathfinding == 0); + break; + } + case SmartActions.RespawnTarget: + { + List targets = GetTargets(e, unit); + if (targets.Empty()) + break; + + foreach (var obj in targets) + { + if (IsCreature(obj)) + obj.ToCreature().Respawn(); + else if (IsGameObject(obj)) + obj.ToGameObject().SetRespawnTime((int)e.Action.respawnTarget.goRespawnTime); + } + + + break; + } + case SmartActions.CloseGossip: + { + List targets = GetTargets(e, unit); + if (targets.Empty()) + break; + + foreach (var obj in targets) + if (IsPlayer(obj)) + obj.ToPlayer().PlayerTalkClass.SendCloseGossip(); + + + break; + } + case SmartActions.Equip: + { + List targets = GetTargets(e, unit); + if (targets.Empty()) + break; + + foreach (var obj in targets) + { + Creature npc = obj.ToCreature(); + if (npc != null) + { + EquipmentItem[] slot = new EquipmentItem[3]; + sbyte equipId = (sbyte)e.Action.equip.entry; + if (equipId != 0) + { + EquipmentInfo einfo = Global.ObjectMgr.GetEquipmentInfo(npc.GetEntry(), equipId); + if (einfo == null) + { + Log.outError(LogFilter.Sql, "SmartScript: SMART_ACTION_EQUIP uses non-existent equipment info id {0} for creature {1}", equipId, npc.GetEntry()); + break; + } + + npc.SetCurrentEquipmentId((byte)equipId); + slot[0] = einfo.Items[0]; + slot[1] = einfo.Items[1]; + slot[2] = einfo.Items[2]; + } + else + { + slot[0].ItemId = e.Action.equip.slot1; + slot[1].ItemId = e.Action.equip.slot2; + slot[2].ItemId = e.Action.equip.slot3; + } + if (e.Action.equip.mask == 0 || Convert.ToBoolean(e.Action.equip.mask & 1)) + npc.SetVirtualItem(0, slot[0].ItemId, slot[0].AppearanceModId, slot[0].ItemVisual); + if (e.Action.equip.mask == 0 || Convert.ToBoolean(e.Action.equip.mask & 2)) + npc.SetVirtualItem(1, slot[1].ItemId, slot[1].AppearanceModId, slot[1].ItemVisual); + if (e.Action.equip.mask == 0 || Convert.ToBoolean(e.Action.equip.mask & 4)) + npc.SetVirtualItem(2, slot[2].ItemId, slot[2].AppearanceModId, slot[2].ItemVisual); + } + } + break; + } + case SmartActions.CreateTimedEvent: + { + SmartEvent ne = new SmartEvent(); + ne.type = SmartEvents.Update; + ne.event_chance = e.Action.timeEvent.chance; + if (ne.event_chance == 0) + ne.event_chance = 100; + + ne.minMaxRepeat.min = e.Action.timeEvent.min; + ne.minMaxRepeat.max = e.Action.timeEvent.max; + ne.minMaxRepeat.repeatMin = e.Action.timeEvent.repeatMin; + ne.minMaxRepeat.repeatMax = e.Action.timeEvent.repeatMax; + + ne.event_flags = 0; + if (ne.minMaxRepeat.repeatMin == 0 && ne.minMaxRepeat.repeatMax == 0) + ne.event_flags |= SmartEventFlags.NotRepeatable; + + SmartAction ac = new SmartAction(); + ac.type = SmartActions.TriggerTimedEvent; + ac.timeEvent.id = e.Action.timeEvent.id; + + SmartScriptHolder ev = new SmartScriptHolder(); + ev.Event = ne; + ev.event_id = e.Action.timeEvent.id; + ev.Target = e.Target; + ev.Action = ac; + InitTimer(ev); + mStoredEvents.Add(ev); + break; + } + case SmartActions.TriggerTimedEvent: + ProcessEventsFor(SmartEvents.TimedEventTriggered, null, e.Action.timeEvent.id); + break; + case SmartActions.RemoveTimedEvent: + mRemIDs.Add(e.Action.timeEvent.id); + break; + case SmartActions.OverrideScriptBaseObject: + { + List targets = GetTargets(e, unit); + if (targets.Empty()) + break; + + foreach (var obj in targets) + { + if (IsCreature(obj)) + { + if (meOrigGUID.IsEmpty() && me) + meOrigGUID = me.GetGUID(); + if (goOrigGUID.IsEmpty() && go) + goOrigGUID = go.GetGUID(); + go = null; + me = obj.ToCreature(); + break; + } + else if (IsGameObject(obj)) + { + if (meOrigGUID.IsEmpty() && me) + meOrigGUID = me.GetGUID(); + if (goOrigGUID.IsEmpty() && go) + goOrigGUID = go.GetGUID(); + go = obj.ToGameObject(); + me = null; + break; + } + } + break; + } + case SmartActions.ResetScriptBaseObject: + ResetBaseObject(); + break; + case SmartActions.CallScriptReset: + OnReset(); + break; + case SmartActions.SetRangedMovement: + { + if (!IsSmart()) + break; + + float attackDistance = e.Action.setRangedMovement.distance; + float attackAngle = e.Action.setRangedMovement.angle / 180.0f * MathFunctions.PI; + + List targets = GetTargets(e, unit); + if (!targets.Empty()) + { + foreach (var obj in targets) + { + Creature target = obj.ToCreature(); + if (target != null) + if (IsSmart(target) && target.GetVictim() != null) + if (((SmartAI)target.GetAI()).CanCombatMove()) + target.GetMotionMaster().MoveChase(target.GetVictim(), attackDistance, attackAngle); + } + } + break; + } + case SmartActions.CallTimedActionlist: + { + if (e.GetTargetType() == SmartTargets.None) + { + Log.outError(LogFilter.Sql, "SmartScript: Entry {0} SourceType {1} Event {2} Action {3} is using TARGET_NONE(0) for Script9 target. Please correct target_type in database.", e.entryOrGuid, e.GetScriptType(), e.GetEventType(), e.GetActionType()); + break; + } + + List targets = GetTargets(e, unit); + if (!targets.Empty()) + { + foreach (var obj in targets) + { + Creature target = obj.ToCreature(); + GameObject goTarget = obj.ToGameObject(); + if (target != null) + { + if (IsSmart(target)) + ((SmartAI)target.GetAI()).SetScript9(e, e.Action.timedActionList.id, GetLastInvoker()); + } + else if (goTarget != null) + { + if (IsSmartGO(goTarget)) + ((SmartGameObjectAI)goTarget.GetAI()).SetScript9(e, e.Action.timedActionList.id, GetLastInvoker()); + } + } + } + break; + } + case SmartActions.SetNpcFlag: + { + List targets = GetTargets(e, unit); + if (targets.Empty()) + break; + + foreach (var obj in targets) + if (IsUnit(obj)) + obj.ToUnit().SetUInt64Value(UnitFields.NpcFlags, e.Action.unitFlag.flag); + break; + } + case SmartActions.AddNpcFlag: + { + List targets = GetTargets(e, unit); + if (targets.Empty()) + break; + + foreach (var obj in targets) + if (IsUnit(obj)) + obj.ToUnit().SetFlag64(UnitFields.NpcFlags, e.Action.unitFlag.flag); + break; + } + case SmartActions.RemoveNpcFlag: + { + List targets = GetTargets(e, unit); + if (targets.Empty()) + break; + + foreach (var obj in targets) + if (IsUnit(obj)) + obj.ToUnit().RemoveFlag64(UnitFields.NpcFlags, e.Action.unitFlag.flag); + break; + } + case SmartActions.CrossCast: + { + List casters = GetTargets(CreateSmartEvent(SmartEvents.UpdateIc, 0, 0, 0, 0, 0, SmartActions.None, 0, 0, 0, 0, 0, 0, (SmartTargets)e.Action.crossCast.targetType, e.Action.crossCast.targetParam1, e.Action.crossCast.targetParam2, e.Action.crossCast.targetParam3, 0), unit); + if (casters.Empty()) + break; + + List targets = GetTargets(e, unit); + if (targets.Empty()) + break; + + foreach (var obj in casters) + { + if (!IsUnit(obj)) + continue; + + Unit targetUnit = obj.ToUnit(); + + bool interruptedSpell = false; + + foreach (var targetObj in targets) + { + if (!IsUnit(targetObj)) + continue; + + if (!(e.Action.crossCast.castFlags.HasAnyFlag((uint)SmartCastFlags.AuraNotPresent)) || !targetObj.ToUnit().HasAura(e.Action.crossCast.spell)) + { + if (!interruptedSpell && e.Action.crossCast.castFlags.HasAnyFlag((uint)SmartCastFlags.InterruptPrevious)) + { + targetUnit.InterruptNonMeleeSpells(false); + interruptedSpell = true; + } + + targetUnit.CastSpell(targetObj.ToUnit(), e.Action.crossCast.spell, e.Action.crossCast.castFlags.HasAnyFlag((uint)SmartCastFlags.Triggered)); + } + else + Log.outDebug(LogFilter.ScriptsAi, "Spell {0} not cast because it has flag SMARTCAST_AURA_NOT_PRESENT and the target ({1}) already has the aura", e.Action.crossCast.spell, targetObj.GetGUID().ToString()); + } + } + break; + } + case SmartActions.CallRandomTimedActionlist: + { + uint[] actions = + { + e.Action.randTimedActionList.entry1, + e.Action.randTimedActionList.entry2, + e.Action.randTimedActionList.entry3, + e.Action.randTimedActionList.entry4, + e.Action.randTimedActionList.entry5, + e.Action.randTimedActionList.entry6, + }; + uint[] temp = new uint[SharedConst.SmartActionParamCount]; + uint count = 0; + for (byte i = 0; i < SharedConst.SmartActionParamCount; i++) + { + if (actions[i] > 0) + { + temp[count] = actions[i]; + ++count; + } + } + + uint id = temp[RandomHelper.IRand(0, (int)count)]; + if (e.GetTargetType() == SmartTargets.None) + { + Log.outError(LogFilter.Sql, "SmartScript: Entry {0} SourceType {1} Event {2} Action {3} is using TARGET_NONE(0) for Script9 target. Please correct target_type in database.", e.entryOrGuid, e.GetScriptType(), e.GetEventType(), e.GetActionType()); + break; + } + + List targets = GetTargets(e, unit); + if (!targets.Empty()) + { + foreach (var obj in targets) + { + Creature target = obj.ToCreature(); + GameObject goTarget = obj.ToGameObject(); + if (target != null) + { + if (IsSmart(target)) + ((SmartAI)target.GetAI()).SetScript9(e, id, GetLastInvoker()); + } + else if (goTarget != null) + { + if (IsSmartGO(goTarget)) + ((SmartGameObjectAI)goTarget.GetAI()).SetScript9(e, id, GetLastInvoker()); + } + } + } + break; + } + case SmartActions.CallRandomRangeTimedActionlist: + { + uint id = RandomHelper.URand(e.Action.randTimedActionList.entry1, e.Action.randTimedActionList.entry2); + if (e.GetTargetType() == SmartTargets.None) + { + Log.outError(LogFilter.Sql, "SmartScript: Entry {0} SourceType {1} Event {2} Action {3} is using TARGET_NONE(0) for Script9 target. Please correct target_type in database.", e.entryOrGuid, e.GetScriptType(), e.GetEventType(), e.GetActionType()); + break; + } + + List targets = GetTargets(e, unit); + if (!targets.Empty()) + { + foreach (var obj in targets) + { + Creature target = obj.ToCreature(); + GameObject goTarget = obj.ToGameObject(); + if (target != null) + { + if (IsSmart(target)) + ((SmartAI)target.GetAI()).SetScript9(e, id, GetLastInvoker()); + } + else if (goTarget != null) + { + if (IsSmartGO(goTarget)) + ((SmartGameObjectAI)goTarget.GetAI()).SetScript9(e, id, GetLastInvoker()); + } + } + } + break; + } + case SmartActions.ActivateTaxi: + { + List targets = GetTargets(e, unit); + if (targets.Empty()) + break; + + foreach (var obj in targets) + if (IsPlayer(obj)) + obj.ToPlayer().ActivateTaxiPathTo(e.Action.taxi.id); + break; + } + case SmartActions.RandomMove: + { + List targets = GetTargets(e, unit); + if (targets.Empty()) + break; + + foreach (var obj in targets) + { + if (IsCreature(obj)) + { + if (e.Action.moveRandom.distance != 0) + obj.ToCreature().GetMotionMaster().MoveRandom(e.Action.moveRandom.distance); + else + obj.ToCreature().GetMotionMaster().MoveIdle(); + } + } + break; + } + case SmartActions.SetUnitFieldBytes1: + { + List targets = GetTargets(e, unit); + if (targets.Empty()) + break; + foreach (var obj in targets) + if (IsUnit(obj)) + obj.ToUnit().SetByteFlag(UnitFields.Bytes1, (byte)e.Action.setunitByte.type, e.Action.setunitByte.byte1); + break; + } + case SmartActions.RemoveUnitFieldBytes1: + { + List targets = GetTargets(e, unit); + if (targets.Empty()) + break; + + foreach (var obj in targets) + if (IsUnit(obj)) + obj.ToUnit().RemoveByteFlag(UnitFields.Bytes1, (byte)e.Action.delunitByte.type, e.Action.delunitByte.byte1); + break; + } + case SmartActions.InterruptSpell: + { + List targets = GetTargets(e, unit); + if (targets.Empty()) + break; + + foreach (var obj in targets) + if (IsUnit(obj)) + obj.ToUnit().InterruptNonMeleeSpells(e.Action.interruptSpellCasting.withDelayed != 0, e.Action.interruptSpellCasting.spell_id, e.Action.interruptSpellCasting.withInstant != 0); + break; + } + case SmartActions.SendGoCustomAnim: + { + List targets = GetTargets(e, unit); + if (targets.Empty()) + break; + + foreach (var obj in targets) + if (IsGameObject(obj)) + obj.ToGameObject().SendCustomAnim(e.Action.sendGoCustomAnim.anim); + break; + } + case SmartActions.SetDynamicFlag: + { + List targets = GetTargets(e, unit); + if (targets.Empty()) + break; + + foreach (var obj in targets) + if (IsUnit(obj)) + obj.ToUnit().SetUInt32Value(ObjectFields.DynamicFlags, e.Action.unitFlag.flag); + break; + } + case SmartActions.AddDynamicFlag: + { + List targets = GetTargets(e, unit); + if (targets.Empty()) + break; + + foreach (var obj in targets) + if (IsUnit(obj)) + obj.ToUnit().SetFlag(ObjectFields.DynamicFlags, e.Action.unitFlag.flag); + break; + } + case SmartActions.RemoveDynamicFlag: + { + List targets = GetTargets(e, unit); + if (targets.Empty()) + break; + + foreach (var obj in targets) + if (IsUnit(obj)) + obj.ToUnit().RemoveFlag(ObjectFields.DynamicFlags, e.Action.unitFlag.flag); + break; + } + case SmartActions.JumpToPos: + { + List targets = GetTargets(e, unit); + if (targets.Empty()) + break; + + foreach (var obj in targets) + { + Creature creature = obj.ToCreature(); + if (creature != null) + { + creature.GetMotionMaster().Clear(); + // @todo add optional jump orientation support? + creature.GetMotionMaster().MoveJump(e.Target.x, e.Target.y, e.Target.z, 0.0f, e.Action.jump.speedxy, e.Action.jump.speedz); + } + } + //todo Resume path when reached jump location + break; + } + case SmartActions.GoSetLootState: + { + List targets = GetTargets(e, unit); + + if (targets.Empty()) + break; + + foreach (var obj in targets) + if (IsGameObject(obj)) + obj.ToGameObject().SetLootState((LootState)e.Action.setGoLootState.state); + break; + } + case SmartActions.SendTargetToTarget: + { + List targets = GetTargets(e, unit); + if (targets.Empty()) + break; + + List storedTargets = GetTargetList(e.Action.sendTargetToTarget.id); + foreach (var obj in targets) + { + if (IsCreature(obj)) + { + SmartAI ai = (SmartAI)obj.ToCreature().GetAI(); + if (ai != null) + ai.GetScript().StoreTargetList(new List(storedTargets), e.Action.sendTargetToTarget.id); // store a copy of target list + else + Log.outError(LogFilter.Sql, "SmartScript: Action target for SMART_ACTION_SEND_TARGET_TO_TARGET is not using SmartAI, skipping"); + } + else if (IsGameObject(obj)) + { + SmartGameObjectAI ai = (SmartGameObjectAI)obj.ToGameObject().GetAI(); + if (ai != null) + ai.GetScript().StoreTargetList(new List(storedTargets), e.Action.sendTargetToTarget.id); // store a copy of target list + else + Log.outError(LogFilter.Sql, "SmartScript: Action target for SMART_ACTION_SEND_TARGET_TO_TARGET is not using SmartGameObjectAI, skipping"); + } + } + break; + } + case SmartActions.SendGossipMenu: + { + if (GetBaseObject() == null) + break; + + Log.outDebug(LogFilter.ScriptsAi, "SmartScript.ProcessAction. SMART_ACTION_SEND_GOSSIP_MENU: gossipMenuId {0}, gossipNpcTextId {1}", + e.Action.sendGossipMenu.gossipMenuId, e.Action.sendGossipMenu.gossipNpcTextId); + + List targets = GetTargets(e, unit); + if (targets.Empty()) + break; + + foreach (var obj in targets) + { + Player player = obj.ToPlayer(); + if (player != null) + { + if (e.Action.sendGossipMenu.gossipMenuId != 0) + player.PrepareGossipMenu(GetBaseObject(), e.Action.sendGossipMenu.gossipMenuId, true); + else + player.PlayerTalkClass.ClearMenus(); + + player.PlayerTalkClass.SendGossipMenu(e.Action.sendGossipMenu.gossipNpcTextId, GetBaseObject().GetGUID()); + } + } + break; + } + case SmartActions.SetHomePos: + { + List targets = GetTargets(e, unit); + if (targets.Empty()) + break; + + foreach (var obj in targets) + { + if (IsCreature(obj)) + { + if (e.GetTargetType() == SmartTargets.Self) + obj.ToCreature().SetHomePosition(me.GetPositionX(), me.GetPositionY(), me.GetPositionZ(), me.GetOrientation()); + else if (e.GetTargetType() == SmartTargets.Position) + obj.ToCreature().SetHomePosition(e.Target.x, e.Target.y, e.Target.z, e.Target.o); + else if (e.GetTargetType() == SmartTargets.CreatureRange || e.GetTargetType() == SmartTargets.CreatureGuid || + e.GetTargetType() == SmartTargets.CreatureDistance || e.GetTargetType() == SmartTargets.GameobjectRange || + e.GetTargetType() == SmartTargets.GameobjectGuid || e.GetTargetType() == SmartTargets.GameobjectDistance || + e.GetTargetType() == SmartTargets.ClosestCreature || e.GetTargetType() == SmartTargets.ClosestGameobject || + e.GetTargetType() == SmartTargets.OwnerOrSummoner || e.GetTargetType() == SmartTargets.ActionInvoker || + e.GetTargetType() == SmartTargets.ClosestEnemy || e.GetTargetType() == SmartTargets.ClosestFriendly) + { + obj.ToCreature().SetHomePosition(obj.GetPositionX(), obj.GetPositionY(), obj.GetPositionZ(), obj.GetOrientation()); + } + else + Log.outError(LogFilter.Sql, "SmartScript: Action target for SMART_ACTION_SET_HOME_POS is invalid, skipping"); + } + } + break; + } + case SmartActions.SetHealthRegen: + { + List targets = GetTargets(e, unit); + if (targets.Empty()) + break; + + foreach (var obj in targets) + if (IsCreature(obj)) + obj.ToCreature().setRegeneratingHealth(e.Action.setHealthRegen.regenHealth != 0 ? true : false); + break; + } + case SmartActions.SetRoot: + { + List targets = GetTargets(e, unit); + if (targets.Empty()) + break; + + foreach (var obj in targets) + if (IsCreature(obj)) + obj.ToCreature().SetControlled(e.Action.setRoot.root != 0 ? true : false, UnitState.Root); + break; + } + case SmartActions.SetGoFlag: + { + List targets = GetTargets(e, unit); + if (targets.Empty()) + break; + + foreach (var obj in targets) + if (IsGameObject(obj)) + obj.ToGameObject().SetUInt32Value(GameObjectFields.Flags, e.Action.goFlag.flag); + break; + } + case SmartActions.AddGoFlag: + { + List targets = GetTargets(e, unit); + if (targets.Empty()) + break; + + foreach (var obj in targets) + if (IsGameObject(obj)) + obj.ToGameObject().SetFlag(GameObjectFields.Flags, e.Action.goFlag.flag); + break; + } + case SmartActions.RemoveGoFlag: + { + List targets = GetTargets(e, unit); + if (targets.Empty()) + break; + + foreach (var obj in targets) + if (IsGameObject(obj)) + obj.ToGameObject().RemoveFlag(GameObjectFields.Flags, e.Action.goFlag.flag); + break; + } + case SmartActions.SummonCreatureGroup: + { + List summonList; + GetBaseObject().SummonCreatureGroup((byte)e.Action.creatureGroup.group, out summonList); + + foreach (var obj in summonList) + if (unit == null && e.Action.creatureGroup.attackInvoker != 0) + obj.GetAI().AttackStart(unit); + break; + } + case SmartActions.SetPower: + { + List targets = GetTargets(e, unit); + + if (!targets.Empty()) + foreach (var obj in targets) + if (IsUnit(obj)) + obj.ToUnit().SetPower((PowerType)e.Action.power.powerType, (int)e.Action.power.newPower); + break; + } + case SmartActions.AddPower: + { + List targets = GetTargets(e, unit); + + if (!targets.Empty()) + foreach (var obj in targets) + if (IsUnit(obj)) + obj.ToUnit().SetPower((PowerType)e.Action.power.powerType, obj.ToUnit().GetPower((PowerType)e.Action.power.powerType) + (int)e.Action.power.newPower); + break; + } + case SmartActions.RemovePower: + { + List targets = GetTargets(e, unit); + + if (!targets.Empty()) + foreach (var obj in targets) + if (IsUnit(obj)) + obj.ToUnit().SetPower((PowerType)e.Action.power.powerType, obj.ToUnit().GetPower((PowerType)e.Action.power.powerType) - (int)e.Action.power.newPower); + break; + } + case SmartActions.GameEventStop: + { + ushort eventId = (ushort)e.Action.gameEventStop.id; + if (!Global.GameEventMgr.IsActiveEvent(eventId)) + { + Log.outError(LogFilter.Sql, "SmartScript.ProcessAction: At case SMART_ACTION_GAME_EVENT_STOP, inactive event (id: {0})", eventId); + break; + } + Global.GameEventMgr.StopEvent(eventId, true); + break; + } + case SmartActions.GameEventStart: + { + ushort eventId = (ushort)e.Action.gameEventStart.id; + if (Global.GameEventMgr.IsActiveEvent(eventId)) + { + Log.outError(LogFilter.Sql, "SmartScript.ProcessAction: At case SMART_ACTION_GAME_EVENT_START, already activated event (id: {0})", eventId); + break; + } + Global.GameEventMgr.StartEvent(eventId, true); + break; + } + case SmartActions.StartClosestWaypoint: + { + uint[] waypoints = new uint[SharedConst.SmartActionParamCount]; + waypoints[0] = e.Action.closestWaypointFromList.wp1; + waypoints[1] = e.Action.closestWaypointFromList.wp2; + waypoints[2] = e.Action.closestWaypointFromList.wp3; + waypoints[3] = e.Action.closestWaypointFromList.wp4; + waypoints[4] = e.Action.closestWaypointFromList.wp5; + waypoints[5] = e.Action.closestWaypointFromList.wp6; + float distanceToClosest = float.MaxValue; + SmartPath closestWp = null; + + var targets = GetTargets(e, unit); + + foreach (var obj in targets) + { + Creature target = obj.ToCreature(); + if (target) + { + if (IsSmart(target)) + { + for (byte i = 0; i < SharedConst.SmartActionParamCount; i++) + { + if (waypoints[i] == 0) + continue; + + var path = Global.SmartAIMgr.GetPath(waypoints[i]); + + if (path.Empty()) + continue; + + SmartPath wp = path[0]; + if (wp != null) + { + float distToThisPath = target.GetDistance(wp.x, wp.y, wp.z); + + if (distToThisPath < distanceToClosest) + { + distanceToClosest = distToThisPath; + closestWp = wp; + } + + } + } + + if (closestWp != null) + ((SmartAI)target.GetAI()).StartPath(false, closestWp.id, true); + } + } + } + break; + } + case SmartActions.RandomSound: + { + uint[] sounds = new uint[SharedConst.SmartActionParamCount - 1]; + sounds[0] = e.Action.randomSound.sound1; + sounds[1] = e.Action.randomSound.sound2; + sounds[2] = e.Action.randomSound.sound3; + sounds[3] = e.Action.randomSound.sound4; + sounds[4] = e.Action.randomSound.sound5; + + bool onlySelf = e.Action.randomSound.onlySelf != 0; + + var targets = GetTargets(e, unit); + if (targets != null) + { + foreach (var obj in targets) + { + if (IsUnit(obj)) + { + uint sound = sounds.PickRandom(); + obj.PlayDirectSound(sound, onlySelf ? obj.ToPlayer() : null); + Log.outDebug(LogFilter.ScriptsAi, "SmartScript.ProcessAction:: SMART_ACTION_RANDOM_SOUND: target: {0} ({1}), sound: {2}, onlyself: {3}", + obj.GetName(), obj.GetGUID().ToString(), sound, onlySelf); + } + } + } + break; + } + case SmartActions.SetCorpseDelay: + { + var targets = GetTargets(e, unit); + if (targets == null) + break; + + foreach (var obj in targets) + { + if (IsCreature(obj)) + obj.ToCreature().SetCorpseDelay(e.Action.corpseDelay.timer); + } + + break; + } + case SmartActions.DisableEvade: + { + if (!IsSmart()) + break; + + ((SmartAI)me.GetAI()).SetEvadeDisabled(e.Action.disableEvade.disable != 0); + break; + } + case SmartActions.PlayAnimkit: + { + var targets = GetTargets(e, unit); + if (targets.Empty()) + break; + + foreach (var obj in targets) + { + if (IsCreature(obj)) + { + if (e.Action.animKit.type == 0) + obj.ToCreature().PlayOneShotAnimKitId((ushort)e.Action.animKit.animKit); + else if (e.Action.animKit.type == 1) + obj.ToCreature().SetAIAnimKitId((ushort)e.Action.animKit.animKit); + else if (e.Action.animKit.type == 2) + obj.ToCreature().SetMeleeAnimKitId((ushort)e.Action.animKit.animKit); + else if (e.Action.animKit.type == 3) + obj.ToCreature().SetMovementAnimKitId((ushort)e.Action.animKit.animKit); + else + { + Log.outError(LogFilter.Sql, "SmartScript: Invalid type for SMART_ACTION_PLAY_ANIMKIT, skipping"); + break; + } + + Log.outDebug(LogFilter.ScriptsAi, "SmartScript.ProcessAction:: SMART_ACTION_PLAY_ANIMKIT: target: {0} ({1}), AnimKit: {2}, Type: {3}", + obj.GetName(), obj.GetGUID().ToString(), e.Action.animKit.animKit, e.Action.animKit.type); + } + } + break; + } + case SmartActions.ScenePlay: + { + var targets = GetTargets(e, unit); + if (targets.Empty()) + break; + + foreach (WorldObject target in targets) + { + Player playerTarget = target.ToPlayer(); + if (playerTarget) + playerTarget.GetSceneMgr().PlayScene(e.Action.scene.sceneId); + } + + break; + } + case SmartActions.SceneCancel: + { + var targets = GetTargets(e, unit); + if (targets.Empty()) + break; + + foreach (WorldObject target in targets) + { + Player playerTarget = target.ToPlayer(); + if (playerTarget) + playerTarget.GetSceneMgr().CancelSceneBySceneId(e.Action.scene.sceneId); + } + + break; + } + default: + Log.outError(LogFilter.Sql, "SmartScript.ProcessAction: Entry {0} SourceType {1}, Event {2}, Unhandled Action type {3}", e.entryOrGuid, e.GetScriptType(), e.event_id, e.GetActionType()); + break; + } + + if (e.link != 0 && e.link != e.event_id) + { + SmartScriptHolder linked = Global.SmartAIMgr.FindLinkedEvent(mEvents, e.link); + if (linked != null) + ProcessEvent(linked, unit, var0, var1, bvar, spell, gob, varString); + else + Log.outError(LogFilter.Sql, "SmartScript.ProcessAction: Entry {0} SourceType {1}, Event {2}, Link Event {3} not found or invalid, skipped.", e.entryOrGuid, e.GetScriptType(), e.event_id, e.link); + } + } + + void ProcessTimedAction(SmartScriptHolder e, uint min, uint max, Unit unit = null, uint var0 = 0, uint var1 = 0, bool bvar = false, SpellInfo spell = null, GameObject gob = null, string varString = "") + { + if (Global.ConditionMgr.IsObjectMeetingSmartEventConditions(e.entryOrGuid, e.event_id, e.source_type, unit, GetBaseObject())) + ProcessAction(e, unit, var0, var1, bvar, spell, gob, varString); + + RecalcTimer(e, min, max); + } + + void InstallTemplate(SmartScriptHolder e) + { + if (GetBaseObject() == null) + return; + if (mTemplate != 0) + { + Log.outError(LogFilter.Sql, "SmartScript.InstallTemplate: Entry {0} SourceType {1} AI Template can not be set more then once, skipped.", e.entryOrGuid, e.GetScriptType()); + return; + } + mTemplate = (SmartAITemplate)e.Action.installTtemplate.id; + switch ((SmartAITemplate)e.Action.installTtemplate.id) + { + case SmartAITemplate.Caster: + { + AddEvent(SmartEvents.UpdateIc, 0, 0, 0, e.Action.installTtemplate.param2, e.Action.installTtemplate.param3, SmartActions.Cast, e.Action.installTtemplate.param1, e.Target.raw.param1, 0, 0, 0, 0, SmartTargets.Victim, 0, 0, 0, 1); + AddEvent(SmartEvents.Range, 0, e.Action.installTtemplate.param4, 300, 0, 0, SmartActions.AllowCombatMovement, 1, 0, 0, 0, 0, 0, SmartTargets.None, 0, 0, 0, 1); + AddEvent(SmartEvents.Range, 0, 0, e.Action.installTtemplate.param4 > 10 ? e.Action.installTtemplate.param4 - 10 : 0, 0, 0, SmartActions.AllowCombatMovement, 0, 0, 0, 0, 0, 0, SmartTargets.None, 0, 0, 0, 1); + AddEvent(SmartEvents.ManaPct, 0, e.Action.installTtemplate.param5 - 15 > 100 ? 100 : e.Action.installTtemplate.param5 + 15, 100, 1000, 1000, SmartActions.SetEventPhase, 1, 0, 0, 0, 0, 0, SmartTargets.None, 0, 0, 0, 0); + AddEvent(SmartEvents.ManaPct, 0, 0, e.Action.installTtemplate.param5, 1000, 1000, SmartActions.SetEventPhase, 0, 0, 0, 0, 0, 0, SmartTargets.None, 0, 0, 0, 0); + AddEvent(SmartEvents.ManaPct, 0, 0, e.Action.installTtemplate.param5, 1000, 1000, SmartActions.AllowCombatMovement, 1, 0, 0, 0, 0, 0, SmartTargets.None, 0, 0, 0, 0); + break; + } + case SmartAITemplate.Turret: + { + AddEvent(SmartEvents.UpdateIc, 0, 0, 0, e.Action.installTtemplate.param2, e.Action.installTtemplate.param3, SmartActions.Cast, e.Action.installTtemplate.param1, e.Target.raw.param1, 0, 0, 0, 0, SmartTargets.Victim, 0, 0, 0, 0); + AddEvent(SmartEvents.JustCreated, 0, 0, 0, 0, 0, SmartActions.AllowCombatMovement, 0, 0, 0, 0, 0, 0, SmartTargets.None, 0, 0, 0, 0); + break; + } + case SmartAITemplate.CagedNPCPart: + { + if (me == null) + return; + //store cage as id1 + AddEvent(SmartEvents.DataSet, 0, 0, 0, 0, 0, SmartActions.StoreTargetList, 1, 0, 0, 0, 0, 0, SmartTargets.ClosestGameobject, e.Action.installTtemplate.param1, 10, 0, 0); + + //reset(close) cage on hostage(me) respawn + AddEvent(SmartEvents.Update, SmartEventFlags.NotRepeatable, 0, 0, 0, 0, SmartActions.ResetGobject, 0, 0, 0, 0, 0, 0, SmartTargets.GameobjectDistance, e.Action.installTtemplate.param1, 5, 0, 0); + + AddEvent(SmartEvents.DataSet, 0, 0, 0, 0, 0, SmartActions.SetRun, e.Action.installTtemplate.param3, 0, 0, 0, 0, 0, SmartTargets.None, 0, 0, 0, 0); + AddEvent(SmartEvents.DataSet, 0, 0, 0, 0, 0, SmartActions.SetEventPhase, 1, 0, 0, 0, 0, 0, SmartTargets.None, 0, 0, 0, 0); + + AddEvent(SmartEvents.Update, SmartEventFlags.NotRepeatable, 1000, 1000, 0, 0, SmartActions.MoveOffset, 0, 0, 0, 0, 0, 0, SmartTargets.Self, 0, e.Action.installTtemplate.param4, 0, 1); + //phase 1: give quest credit on movepoint reached + AddEvent(SmartEvents.Movementinform, 0, (uint)MovementGeneratorType.Point, EventId.SmartRandomPoint, 0, 0, SmartActions.SetData, 0, 0, 0, 0, 0, 0, SmartTargets.Stored, 1, 0, 0, 1); + //phase 1: despawn after time on movepoint reached + AddEvent(SmartEvents.Movementinform, 0, (uint)MovementGeneratorType.Point, EventId.SmartRandomPoint, 0, 0, SmartActions.ForceDespawn, e.Action.installTtemplate.param2, 0, 0, 0, 0, 0, SmartTargets.None, 0, 0, 0, 1); + + if (Global.CreatureTextMgr.TextExist(me.GetEntry(), (byte)e.Action.installTtemplate.param5)) + AddEvent(SmartEvents.Movementinform, 0, (uint)MovementGeneratorType.Point, EventId.SmartRandomPoint, 0, 0, SmartActions.Talk, e.Action.installTtemplate.param5, 0, 0, 0, 0, 0, SmartTargets.None, 0, 0, 0, 1); + break; + } + case SmartAITemplate.CagedGOPart: + { + if (go == null) + return; + //store hostage as id1 + AddEvent(SmartEvents.GoStateChanged, 0, 2, 0, 0, 0, SmartActions.StoreTargetList, 1, 0, 0, 0, 0, 0, SmartTargets.ClosestCreature, e.Action.installTtemplate.param1, 10, 0, 0); + //store invoker as id2 + AddEvent(SmartEvents.GoStateChanged, 0, 2, 0, 0, 0, SmartActions.StoreTargetList, 2, 0, 0, 0, 0, 0, SmartTargets.None, 0, 0, 0, 0); + //signal hostage + AddEvent(SmartEvents.GoStateChanged, 0, 2, 0, 0, 0, SmartActions.SetData, 0, 0, 0, 0, 0, 0, SmartTargets.Stored, 1, 0, 0, 0); + //when hostage raeched end point, give credit to invoker + if (e.Action.installTtemplate.param2 != 0) + AddEvent(SmartEvents.DataSet, 0, 0, 0, 0, 0, SmartActions.CallKilledmonster, e.Action.installTtemplate.param1, 0, 0, 0, 0, 0, SmartTargets.Stored, 2, 0, 0, 0); + else + AddEvent(SmartEvents.GoStateChanged, 0, 2, 0, 0, 0, SmartActions.CallKilledmonster, e.Action.installTtemplate.param1, 0, 0, 0, 0, 0, SmartTargets.Stored, 2, 0, 0, 0); + break; + } + default: + return; + } + } + + void AddEvent(SmartEvents e, SmartEventFlags event_flags, uint event_param1, uint event_param2, uint event_param3, uint event_param4, + SmartActions action, uint action_param1, uint action_param2, uint action_param3, uint action_param4, uint action_param5, uint action_param6, + SmartTargets t, uint target_param1, uint target_param2, uint target_param3, uint phaseMask) + { + mInstallEvents.Add(CreateSmartEvent(e, event_flags, event_param1, event_param2, event_param3, event_param4, action, action_param1, action_param2, action_param3, action_param4, action_param5, action_param6, t, target_param1, target_param2, target_param3, phaseMask)); + } + + SmartScriptHolder CreateSmartEvent(SmartEvents e, SmartEventFlags event_flags, uint event_param1, uint event_param2, uint event_param3, uint event_param4, + SmartActions action, uint action_param1, uint action_param2, uint action_param3, uint action_param4, uint action_param5, uint action_param6, + SmartTargets t, uint target_param1, uint target_param2, uint target_param3, uint phaseMask) + { + SmartScriptHolder script = new SmartScriptHolder(); + script.Event.type = e; + script.Event.raw.param1 = event_param1; + script.Event.raw.param2 = event_param2; + script.Event.raw.param3 = event_param3; + script.Event.raw.param4 = event_param4; + script.Event.event_phase_mask = phaseMask; + script.Event.event_flags = event_flags; + script.Event.event_chance = 100; + + script.Action.type = action; + script.Action.raw.param1 = action_param1; + script.Action.raw.param2 = action_param2; + script.Action.raw.param3 = action_param3; + script.Action.raw.param4 = action_param4; + script.Action.raw.param5 = action_param5; + script.Action.raw.param6 = action_param6; + + script.Target.type = t; + script.Target.raw.param1 = target_param1; + script.Target.raw.param2 = target_param2; + script.Target.raw.param3 = target_param3; + + script.source_type = SmartScriptType.Creature; + InitTimer(script); + return script; + } + + List GetTargets(SmartScriptHolder e, Unit invoker = null) + { + Unit scriptTrigger = null; + Unit tempLastInvoker = GetLastInvoker(); + if (invoker != null) + scriptTrigger = invoker; + else if (tempLastInvoker != null) + scriptTrigger = tempLastInvoker; + + WorldObject baseObject = GetBaseObject(); + + List l = new List(); + switch (e.GetTargetType()) + { + case SmartTargets.Self: + if (baseObject != null) + l.Add(baseObject); + break; + case SmartTargets.Victim: + if (me != null && me.GetVictim() != null) + l.Add(me.GetVictim()); + break; + case SmartTargets.HostileSecondAggro: + if (me != null) + { + Unit u = me.GetAI().SelectTarget(SelectAggroTarget.TopAggro, 1); + if (u != null) + l.Add(u); + } + break; + case SmartTargets.HostileLastAggro: + if (me != null) + { + Unit u = me.GetAI().SelectTarget(SelectAggroTarget.BottomAggro, 0); + if (u != null) + l.Add(u); + } + break; + case SmartTargets.HostileRandom: + if (me != null) + { + Unit u = me.GetAI().SelectTarget(SelectAggroTarget.Random, 0); + if (u != null) + l.Add(u); + } + break; + case SmartTargets.HostileRandomNotTop: + if (me != null) + { + Unit u = me.GetAI().SelectTarget(SelectAggroTarget.Random, 1); + if (u != null) + l.Add(u); + } + break; + case SmartTargets.None: + case SmartTargets.ActionInvoker: + if (scriptTrigger != null) + l.Add(scriptTrigger); + break; + case SmartTargets.ActionInvokerVehicle: + if (scriptTrigger != null && scriptTrigger.GetVehicle() != null && scriptTrigger.GetVehicle().GetBase() != null) + l.Add(scriptTrigger.GetVehicle().GetBase()); + break; + case SmartTargets.InvokerParty: + if (scriptTrigger != null) + { + Player player = scriptTrigger.ToPlayer(); + if (player != null) + { + Group group = player.GetGroup(); + if (group) + { + for (GroupReference groupRef = group.GetFirstMember(); groupRef != null; groupRef = groupRef.next()) + { + Player member = groupRef.GetSource(); + if (member) + l.Add(member); + } + } + // We still add the player to the list if there is no group. If we do + // this even if there is a group (thus the else-check), it will add the + // same player to the list twice. We don't want that to happen. + else + l.Add(scriptTrigger); + } + } + break; + case SmartTargets.CreatureRange: + { + List units = GetWorldObjectsInDist(e.Target.unitRange.maxDist); + foreach (var obj in units) + { + if (!IsCreature(obj)) + continue; + + if (me != null && me == obj) + continue; + + if ((e.Target.unitRange.creature == 0 || obj.ToCreature().GetEntry() == e.Target.unitRange.creature) && baseObject.IsInRange(obj, e.Target.unitRange.minDist, e.Target.unitRange.maxDist)) + l.Add(obj); + } + break; + } + case SmartTargets.CreatureDistance: + { + List units = GetWorldObjectsInDist(e.Target.unitDistance.dist); + foreach (var obj in units) + { + if (!IsCreature(obj)) + continue; + + if (me != null && me == obj) + continue; + + if (e.Target.unitDistance.creature == 0 || obj.ToCreature().GetEntry() == e.Target.unitDistance.creature) + l.Add(obj); + } + break; + } + case SmartTargets.GameobjectDistance: + { + List units = GetWorldObjectsInDist(e.Target.goDistance.dist); + foreach (var obj in units) + { + if (!IsGameObject(obj)) + continue; + + if (go != null && go == obj) + continue; + + if (e.Target.goDistance.entry == 0 || obj.ToGameObject().GetEntry() == e.Target.goDistance.entry) + l.Add(obj); + } + break; + } + case SmartTargets.GameobjectRange: + { + List units = GetWorldObjectsInDist(e.Target.goRange.maxDist); + foreach (var obj in units) + { + if (!IsGameObject(obj)) + continue; + + if (go != null && go == obj) + continue; + + if ((e.Target.goRange.entry == 0 && obj.ToGameObject().GetEntry() == e.Target.goRange.entry) && baseObject.IsInRange(obj, e.Target.goRange.minDist, e.Target.goRange.maxDist)) + l.Add(obj); + } + break; + } + case SmartTargets.CreatureGuid: + { + if (scriptTrigger == null && baseObject == null) + { + Log.outError(LogFilter.Sql, "SMART_TARGET_CREATURE_GUID can not be used without invoker"); + break; + } + + Creature target = FindCreatureNear(scriptTrigger != null ? scriptTrigger : baseObject, e.Target.unitGUID.dbGuid); + if (target) + if (target != null && (e.Target.unitGUID.entry == 0 || target.GetEntry() == e.Target.unitGUID.entry)) + l.Add(target); + break; + } + case SmartTargets.GameobjectGuid: + { + if (scriptTrigger == null && baseObject == null) + { + Log.outError(LogFilter.Sql, "SMART_TARGET_GAMEOBJECT_GUID can not be used without invoker"); + break; + } + + GameObject target = FindGameObjectNear(scriptTrigger != null ? scriptTrigger : baseObject, e.Target.goGUID.dbGuid); + if (target) + if (target != null && (e.Target.goGUID.entry == 0 || target.GetEntry() == e.Target.goGUID.entry)) + l.Add(target); + break; + } + case SmartTargets.PlayerRange: + { + List units = GetWorldObjectsInDist(e.Target.playerRange.maxDist); + if (!units.Empty() && baseObject != null) + foreach (var obj in units) + if (IsPlayer(obj) && baseObject.IsInRange(obj, e.Target.playerRange.minDist, e.Target.playerRange.maxDist)) + l.Add(obj); + + break; + } + case SmartTargets.PlayerDistance: + { + List units = GetWorldObjectsInDist(e.Target.playerDistance.dist); + foreach (var obj in units) + if (IsPlayer(obj)) + l.Add(obj); + break; + } + case SmartTargets.Stored: + { + var list = mTargetStorage.LookupByKey(e.Target.stored.id); + if (!list.Empty()) + l.AddRange(list); + + return l; + } + case SmartTargets.ClosestCreature: + { + Creature target = baseObject.FindNearestCreature(e.Target.closest.entry, e.Target.closest.dist != 0 ? e.Target.closest.dist : 100, e.Target.closest.dead != 0 ? false : true); + if (target) + l.Add(target); + break; + } + case SmartTargets.ClosestGameobject: + { + GameObject target = baseObject.FindNearestGameObject(e.Target.closest.entry, e.Target.closest.dist != 0 ? e.Target.closest.dist : 100); + if (target) + l.Add(target); + break; + } + case SmartTargets.ClosestPlayer: + { + if (me) + { + Player target = me.SelectNearestPlayer(e.Target.playerDistance.dist); + if (target) + l.Add(target); + } + break; + } + case SmartTargets.OwnerOrSummoner: + { + if (me != null) + { + ObjectGuid charmerOrOwnerGuid = me.GetCharmerOrOwnerGUID(); + if (charmerOrOwnerGuid.IsEmpty()) + { + TempSummon tempSummon = me.ToTempSummon(); + if (tempSummon) + { + Unit summoner = tempSummon.GetSummoner(); + if (summoner) + charmerOrOwnerGuid = summoner.GetGUID(); + } + } + + if (charmerOrOwnerGuid.IsEmpty()) + charmerOrOwnerGuid = me.GetCreatorGUID(); + + Unit owner = Global.ObjAccessor.GetUnit(me, charmerOrOwnerGuid); + if (owner != null) + l.Add(owner); + } + break; + } + case SmartTargets.ThreatList: + { + if (me != null) + { + var threatList = me.GetThreatManager().getThreatList(); + foreach (var i in threatList) + { + Unit temp = Global.ObjAccessor.GetUnit(me, i.getUnitGuid()); + if (temp != null) + l.Add(temp); + } + } + break; + } + case SmartTargets.ClosestEnemy: + { + if (me != null) + { + Unit target = me.SelectNearestTarget(e.Target.closestAttackable.maxDist); + if (target != null) + l.Add(target); + } + + break; + } + case SmartTargets.ClosestFriendly: + { + if (me != null) + { + Unit target = DoFindClosestFriendlyInRange(e.Target.closestFriendly.maxDist); + if (target != null) + l.Add(target); + } + break; + } + case SmartTargets.LootRecipients: + { + if (me) + { + Group lootGroup = me.GetLootRecipientGroup(); + if (lootGroup) + { + for (GroupReference refe = lootGroup.GetFirstMember(); refe != null; refe = refe.next()) + { + Player recipient = refe.GetSource(); + if (recipient) + l.Add(recipient); + } + } + else + { + Player recipient = me.GetLootRecipient(); + if (recipient) + l.Add(recipient); + } + } + break; + } + case SmartTargets.VehicleAccessory: + { + if (me && me.IsVehicle()) + { + Unit target = me.GetVehicleKit().GetPassenger((sbyte)e.Target.vehicle.seat); + if (target) + l.Add(target); + } + break; + } + case SmartTargets.Position: + default: + break; + } + + return l; + } + + List GetWorldObjectsInDist(float dist) + { + List targets = new List(); + WorldObject obj = GetBaseObject(); + if (obj) + { + var u_check = new AllWorldObjectsInRange(obj, dist); + var searcher = new WorldObjectListSearcher(obj, targets, u_check); + Cell.VisitAllObjects(obj, searcher, dist); + } + return targets; + } + + void ProcessEvent(SmartScriptHolder e, Unit unit = null, uint var0 = 0, uint var1 = 0, bool bvar = false, SpellInfo spell = null, GameObject gob = null, string varString = "") + { + if (!e.active && e.GetEventType() != SmartEvents.Link) + return; + + if ((e.Event.event_phase_mask != 0 && !IsInPhase(e.Event.event_phase_mask)) || (e.Event.event_flags.HasAnyFlag(SmartEventFlags.NotRepeatable) && e.runOnce)) + return; + + if (!e.Event.event_flags.HasAnyFlag(SmartEventFlags.WhileCharmed) && IsCreature(me) && !IsCreatureInControlOfSelf(me)) + return; + + switch (e.GetEventType()) + { + case SmartEvents.Link://special handling + ProcessAction(e, unit, var0, var1, bvar, spell, gob); + break; + //called from Update tick + case SmartEvents.Update: + ProcessTimedAction(e, e.Event.minMaxRepeat.repeatMin, e.Event.minMaxRepeat.repeatMax); + break; + case SmartEvents.UpdateOoc: + if (me != null && me.IsInCombat()) + return; + ProcessTimedAction(e, e.Event.minMaxRepeat.repeatMin, e.Event.minMaxRepeat.repeatMax); + break; + case SmartEvents.UpdateIc: + if (me == null || !me.IsInCombat()) + return; + ProcessTimedAction(e, e.Event.minMaxRepeat.repeatMin, e.Event.minMaxRepeat.repeatMax); + break; + case SmartEvents.HealtPct: + { + if (me == null || !me.IsInCombat() || me.GetMaxHealth() == 0) + return; + uint perc = (uint)me.GetHealthPct(); + if (perc > e.Event.minMaxRepeat.max || perc < e.Event.minMaxRepeat.min) + return; + ProcessTimedAction(e, e.Event.minMaxRepeat.repeatMin, e.Event.minMaxRepeat.repeatMax); + break; + } + case SmartEvents.TargetHealthPct: + { + if (me == null || !me.IsInCombat() || me.GetVictim() == null || me.GetVictim().GetMaxHealth() == 0) + return; + uint perc = (uint)me.GetVictim().GetHealthPct(); + if (perc > e.Event.minMaxRepeat.max || perc < e.Event.minMaxRepeat.min) + return; + ProcessTimedAction(e, e.Event.minMaxRepeat.repeatMin, e.Event.minMaxRepeat.repeatMax, me.GetVictim()); + break; + } + case SmartEvents.ManaPct: + { + if (me == null || !me.IsInCombat() || me.GetMaxPower(PowerType.Mana) == 0) + return; + uint perc = (uint)(100.0f * me.GetPower(PowerType.Mana) / me.GetMaxPower(PowerType.Mana)); + if (perc > e.Event.minMaxRepeat.max || perc < e.Event.minMaxRepeat.min) + return; + ProcessTimedAction(e, e.Event.minMaxRepeat.repeatMin, e.Event.minMaxRepeat.repeatMax); + break; + } + case SmartEvents.TargetManaPct: + { + if (me == null || !me.IsInCombat() || me.GetVictim() == null || me.GetVictim().GetMaxPower(PowerType.Mana) == 0) + return; + uint perc = (uint)(100.0f * me.GetVictim().GetPower(PowerType.Mana) / me.GetVictim().GetMaxPower(PowerType.Mana)); + if (perc > e.Event.minMaxRepeat.max || perc < e.Event.minMaxRepeat.min) + return; + ProcessTimedAction(e, e.Event.minMaxRepeat.repeatMin, e.Event.minMaxRepeat.repeatMax, me.GetVictim()); + break; + } + case SmartEvents.Range: + { + if (me == null || !me.IsInCombat() || me.GetVictim() == null) + return; + + if (me.IsInRange(me.GetVictim(), e.Event.minMaxRepeat.min, e.Event.minMaxRepeat.max)) + ProcessTimedAction(e, e.Event.minMaxRepeat.repeatMin, e.Event.minMaxRepeat.repeatMax, me.GetVictim()); + break; + } + case SmartEvents.VictimCasting: + { + if (me == null || !me.IsInCombat()) + return; + + Unit victim = me.GetVictim(); + + if (victim == null || !victim.IsNonMeleeSpellCast(false, false, true)) + return; + + if (e.Event.targetCasting.spellId > 0) + { + Spell currSpell = victim.GetCurrentSpell(CurrentSpellTypes.Generic); + if (currSpell != null) + if (currSpell.m_spellInfo.Id != e.Event.targetCasting.spellId) + return; + } + + ProcessTimedAction(e, e.Event.targetCasting.repeatMin, e.Event.targetCasting.repeatMax, me.GetVictim()); + break; + } + case SmartEvents.FriendlyHealth: + { + if (me == null || !me.IsInCombat()) + return; + + Unit target = DoSelectLowestHpFriendly(e.Event.friendlyHealth.radius, e.Event.friendlyHealth.hpDeficit); + if (target == null || !target.IsInCombat()) + return; + ProcessTimedAction(e, e.Event.friendlyHealth.repeatMin, e.Event.friendlyHealth.repeatMax, target); + break; + } + case SmartEvents.FriendlyIsCc: + { + if (me == null || !me.IsInCombat()) + return; + + List pList = new List(); + DoFindFriendlyCC(pList, e.Event.friendlyCC.radius); + if (pList.Empty()) + return; + ProcessTimedAction(e, e.Event.friendlyCC.repeatMin, e.Event.friendlyCC.repeatMax, pList.First()); + break; + } + case SmartEvents.FriendlyMissingBuff: + { + List pList = new List(); + DoFindFriendlyMissingBuff(pList, e.Event.missingBuff.radius, e.Event.missingBuff.spell); + + if (pList.Empty()) + return; + + ProcessTimedAction(e, e.Event.missingBuff.repeatMin, e.Event.missingBuff.repeatMax, pList.First()); + break; + } + case SmartEvents.HasAura: + { + if (me == null) + return; + uint count = me.GetAuraCount(e.Event.aura.spell); + if ((e.Event.aura.count == 0 && count == 0) || (e.Event.aura.count != 0 && count >= e.Event.aura.count)) + ProcessTimedAction(e, e.Event.aura.repeatMin, e.Event.aura.repeatMax); + break; + } + case SmartEvents.TargetBuffed: + { + if (me == null || me.GetVictim() == null) + return; + uint count = me.GetVictim().GetAuraCount(e.Event.aura.spell); + if (count < e.Event.aura.count) + return; + ProcessTimedAction(e, e.Event.aura.repeatMin, e.Event.aura.repeatMax); + break; + } + case SmartEvents.Charmed: + { + if (bvar == (e.Event.charm.onRemove != 1)) + ProcessAction(e, unit, var0, var1, bvar, spell, gob); + break; + } + //no params + case SmartEvents.Aggro: + case SmartEvents.Death: + case SmartEvents.Evade: + case SmartEvents.ReachedHome: + case SmartEvents.CharmedTarget: + case SmartEvents.CorpseRemoved: + case SmartEvents.AiInit: + case SmartEvents.TransportAddplayer: + case SmartEvents.TransportRemovePlayer: + case SmartEvents.QuestAccepted: + case SmartEvents.QuestObjCompletion: + case SmartEvents.QuestCompletion: + case SmartEvents.QuestRewarded: + case SmartEvents.QuestFail: + case SmartEvents.JustSummoned: + case SmartEvents.Reset: + case SmartEvents.JustCreated: + case SmartEvents.GossipHello: + case SmartEvents.FollowCompleted: + case SmartEvents.OnSpellclick: + ProcessAction(e, unit, var0, var1, bvar, spell, gob); + break; + case SmartEvents.IsBehindTarget: + { + if (me == null) + return; + + Unit victim = me.GetVictim(); + if (victim != null) + { + if (!victim.HasInArc(MathFunctions.PI, me)) + ProcessTimedAction(e, e.Event.behindTarget.cooldownMin, e.Event.behindTarget.cooldownMax, victim); + } + break; + } + case SmartEvents.ReceiveEmote: + if (e.Event.emote.emoteId == var0) + { + ProcessAction(e, unit); + RecalcTimer(e, e.Event.emote.cooldownMin, e.Event.emote.cooldownMax); + } + break; + case SmartEvents.Kill: + { + if (me == null || unit == null) + return; + if (e.Event.kill.playerOnly != 0 && !unit.IsTypeId(TypeId.Player)) + return; + if (e.Event.kill.creature != 0 && unit.GetEntry() != e.Event.kill.creature) + return; + ProcessAction(e, unit); + RecalcTimer(e, e.Event.kill.cooldownMin, e.Event.kill.cooldownMax); + break; + } + case SmartEvents.SpellhitTarget: + case SmartEvents.SpellHit: + { + if (spell == null) + return; + if ((e.Event.spellHit.spell == 0 || spell.Id == e.Event.spellHit.spell) && + (e.Event.spellHit.school == 0 || Convert.ToBoolean((uint)spell.SchoolMask & e.Event.spellHit.school))) + { + ProcessAction(e, unit, 0, 0, bvar, spell); + RecalcTimer(e, e.Event.spellHit.cooldownMin, e.Event.spellHit.cooldownMax); + } + break; + } + case SmartEvents.OocLos: + { + if (me == null || me.IsInCombat()) + return; + //can trigger if closer than fMaxAllowedRange + float range = e.Event.los.maxDist; + + //if range is ok and we are actually in LOS + if (me.IsWithinDistInMap(unit, range) && me.IsWithinLOSInMap(unit)) + { + //if friendly event&&who is not hostile OR hostile event&&who is hostile + if ((e.Event.los.noHostile != 0 && !me.IsHostileTo(unit)) || + (e.Event.los.noHostile == 0 && me.IsHostileTo(unit))) + { + ProcessAction(e, unit); + RecalcTimer(e, e.Event.los.cooldownMin, e.Event.los.cooldownMax); + } + } + break; + } + case SmartEvents.IcLos: + { + if (me == null || !me.IsInCombat()) + return; + //can trigger if closer than fMaxAllowedRange + float range = e.Event.los.maxDist; + + //if range is ok and we are actually in LOS + if (me.IsWithinDistInMap(unit, range) && me.IsWithinLOSInMap(unit)) + { + //if friendly event&&who is not hostile OR hostile event&&who is hostile + if ((e.Event.los.noHostile != 0 && !me.IsHostileTo(unit)) || + (e.Event.los.noHostile == 0 && me.IsHostileTo(unit))) + { + ProcessAction(e, unit); + RecalcTimer(e, e.Event.los.cooldownMin, e.Event.los.cooldownMax); + } + } + break; + } + case SmartEvents.Respawn: + { + if (GetBaseObject() == null) + return; + if (e.Event.respawn.type == (uint)SmartRespawnCondition.Map && GetBaseObject().GetMapId() != e.Event.respawn.map) + return; + if (e.Event.respawn.type == (uint)SmartRespawnCondition.Area && GetBaseObject().GetZoneId() != e.Event.respawn.area) + return; + ProcessAction(e); + break; + } + case SmartEvents.SummonedUnit: + { + if (!IsCreature(unit)) + return; + if (e.Event.summoned.creature != 0 && unit.GetEntry() != e.Event.summoned.creature) + return; + ProcessAction(e, unit); + RecalcTimer(e, e.Event.summoned.cooldownMin, e.Event.summoned.cooldownMax); + break; + } + case SmartEvents.ReceiveHeal: + case SmartEvents.Damaged: + case SmartEvents.DamagedTarget: + { + if (var0 > e.Event.minMaxRepeat.max || var0 < e.Event.minMaxRepeat.min) + return; + ProcessAction(e, unit); + RecalcTimer(e, e.Event.minMaxRepeat.repeatMin, e.Event.minMaxRepeat.repeatMax); + break; + } + case SmartEvents.Movementinform: + { + if ((e.Event.movementInform.type != 0 && var0 != e.Event.movementInform.type) || (e.Event.movementInform.id != 0 && var1 != e.Event.movementInform.id)) + return; + ProcessAction(e, unit, var0, var1); + break; + } + case SmartEvents.TransportRelocate: + case SmartEvents.WaypointStart: + { + if (e.Event.waypoint.pathID != 0 && var0 != e.Event.waypoint.pathID) + return; + ProcessAction(e, unit, var0); + break; + } + case SmartEvents.WaypointReached: + case SmartEvents.WaypointResumed: + case SmartEvents.WaypointPaused: + case SmartEvents.WaypointStopped: + case SmartEvents.WaypointEnded: + { + if (me == null || (e.Event.waypoint.pointID != 0 && var0 != e.Event.waypoint.pointID) || (e.Event.waypoint.pathID != 0 && GetPathId() != e.Event.waypoint.pathID)) + return; + ProcessAction(e, unit); + break; + } + case SmartEvents.SummonDespawned: + case SmartEvents.InstancePlayerEnter: + { + if (e.Event.instancePlayerEnter.team != 0 && var0 != e.Event.instancePlayerEnter.team) + return; + ProcessAction(e, unit, var0); + RecalcTimer(e, e.Event.instancePlayerEnter.cooldownMin, e.Event.instancePlayerEnter.cooldownMax); + break; + } + case SmartEvents.AcceptedQuest: + case SmartEvents.RewardQuest: + { + if (e.Event.quest.questId != 0 && var0 != e.Event.quest.questId) + return; + ProcessAction(e, unit, var0); + break; + } + case SmartEvents.TransportAddcreature: + { + if (e.Event.transportAddCreature.creature != 0 && var0 != e.Event.transportAddCreature.creature) + return; + ProcessAction(e, unit, var0); + break; + } + case SmartEvents.AreatriggerOntrigger: + { + if (e.Event.areatrigger.id != 0 && var0 != e.Event.areatrigger.id) + return; + ProcessAction(e, unit, var0); + break; + } + case SmartEvents.TextOver: + { + if (var0 != e.Event.textOver.textGroupID || (e.Event.textOver.creatureEntry != 0 && e.Event.textOver.creatureEntry != var1)) + return; + ProcessAction(e, unit, var0); + break; + } + case SmartEvents.DataSet: + { + if (e.Event.dataSet.id != var0 || e.Event.dataSet.value != var1) + return; + ProcessAction(e, unit, var0, var1); + RecalcTimer(e, e.Event.dataSet.cooldownMin, e.Event.dataSet.cooldownMax); + break; + } + case SmartEvents.PassengerRemoved: + case SmartEvents.PassengerBoarded: + { + if (unit == null) + return; + ProcessAction(e, unit); + RecalcTimer(e, e.Event.minMax.repeatMin, e.Event.minMax.repeatMax); + break; + } + case SmartEvents.TimedEventTriggered: + { + if (e.Event.timedEvent.id == var0) + ProcessAction(e, unit); + break; + } + case SmartEvents.GossipSelect: + { + Log.outDebug(LogFilter.ScriptsAi, "SmartScript: Gossip Select: menu {0} action {1}", var0, var1);//little help for scripters + if (e.Event.gossip.sender != var0 || e.Event.gossip.action != var1) + return; + ProcessAction(e, unit, var0, var1); + break; + } + case SmartEvents.DummyEffect: + { + if (e.Event.dummy.spell != var0 || e.Event.dummy.effIndex != var1) + return; + ProcessAction(e, unit, var0, var1); + break; + } + case SmartEvents.GameEventStart: + case SmartEvents.GameEventEnd: + { + if (e.Event.gameEvent.gameEventId != var0) + return; + ProcessAction(e, null, var0); + break; + } + case SmartEvents.GoStateChanged: + { + if (e.Event.goStateChanged.state != var0) + return; + ProcessAction(e, unit, var0, var1); + break; + } + case SmartEvents.GoEventInform: + { + if (e.Event.eventInform.eventId != var0) + return; + ProcessAction(e, null, var0); + break; + } + case SmartEvents.ActionDone: + { + if (e.Event.doAction.eventId != var0) + return; + ProcessAction(e, unit, var0); + break; + } + case SmartEvents.FriendlyHealthPCT: + { + if (me == null || !me.IsInCombat()) + return; + + List _targets = null; + + switch (e.GetTargetType()) + { + case SmartTargets.CreatureRange: + case SmartTargets.CreatureGuid: + case SmartTargets.CreatureDistance: + case SmartTargets.ClosestCreature: + case SmartTargets.ClosestPlayer: + case SmartTargets.PlayerRange: + case SmartTargets.PlayerDistance: + _targets = GetTargets(e); + break; + default: + return; + } + + if (_targets == null) + return; + + Unit target = null; + + foreach (var obj in _targets) + { + if (IsUnit(obj) && me.IsFriendlyTo(obj.ToUnit()) && obj.ToUnit().IsAlive() && obj.ToUnit().IsInCombat()) + { + uint healthPct = (uint)obj.ToUnit().GetHealthPct(); + + if (healthPct > e.Event.friendlyHealthPct.maxHpPct || healthPct < e.Event.friendlyHealthPct.minHpPct) + continue; + + target = obj.ToUnit(); + break; + } + } + + if (target == null) + return; + + ProcessTimedAction(e, e.Event.friendlyHealthPct.repeatMin, e.Event.friendlyHealthPct.repeatMax, target); + break; + } + case SmartEvents.DistanceCreature: + { + if (!me) + return; + + Creature creature = null; + + if (e.Event.distance.guid != 0) + { + creature = FindCreatureNear(me, e.Event.distance.guid); + + if (!creature) + return; + + if (!me.IsInRange(creature, 0, e.Event.distance.dist)) + return; + } + else if (e.Event.distance.entry != 0) + { + List list = new List(); + me.GetCreatureListWithEntryInGrid(list, e.Event.distance.entry, e.Event.distance.dist); + + if (!list.Empty()) + creature = list.FirstOrDefault(); + } + + if (creature) + ProcessTimedAction(e, e.Event.distance.repeat, e.Event.distance.repeat, creature); + + break; + } + case SmartEvents.DistanceGameobject: + { + if (!me) + return; + + GameObject gameobject = null; + + if (e.Event.distance.guid != 0) + { + gameobject = FindGameObjectNear(me, e.Event.distance.guid); + + if (!gameobject) + return; + + if (!me.IsInRange(gameobject, 0, e.Event.distance.dist)) + return; + } + else if (e.Event.distance.entry != 0) + { + List list = new List(); + me.GetGameObjectListWithEntryInGrid(list, e.Event.distance.entry, e.Event.distance.dist); + + if (!list.Empty()) + gameobject = list.FirstOrDefault(); + } + + if (gameobject) + ProcessTimedAction(e, e.Event.distance.repeat, e.Event.distance.repeat, null, 0, 0, false, null, gameobject); + + break; + } + case SmartEvents.CounterSet: + if (GetCounterId(e.Event.counter.id) != 0 && GetCounterValue(e.Event.counter.id) == e.Event.counter.value) + ProcessTimedAction(e, e.Event.counter.cooldownMin, e.Event.counter.cooldownMax); + break; + case SmartEvents.SceneStart: + case SmartEvents.SceneCancel: + case SmartEvents.SceneComplete: + { + ProcessAction(e, unit); + break; + } + case SmartEvents.SceneTrigger: + { + if (e.Event.param_string != varString) + return; + + ProcessAction(e, unit, var0, 0, false, null, null, varString); + break; + } + default: + Log.outError(LogFilter.Sql, "SmartScript.ProcessEvent: Unhandled Event type {0}", e.GetEventType()); + break; + } + } + + void InitTimer(SmartScriptHolder e) + { + switch (e.GetEventType()) + { + //set only events which have initial timers + case SmartEvents.Update: + case SmartEvents.UpdateIc: + case SmartEvents.UpdateOoc: + RecalcTimer(e, e.Event.minMaxRepeat.min, e.Event.minMaxRepeat.max); + break; + case SmartEvents.DistanceCreature: + case SmartEvents.DistanceGameobject: + RecalcTimer(e, e.Event.distance.repeat, e.Event.distance.repeat); + break; + default: + e.active = true; + break; + } + } + + void RecalcTimer(SmartScriptHolder e, uint min, uint max) + { + if (e.entryOrGuid == 15294 && e.timer != 0) + { + Log.outError(LogFilter.Server, "Called RecalcTimer"); + } + // min/max was checked at loading! + e.timer = RandomHelper.URand(min, max); + e.active = e.timer != 0 ? false : true; + } + + void UpdateTimer(SmartScriptHolder e, uint diff) + { + if (e.GetEventType() == SmartEvents.Link) + return; + + if (e.Event.event_phase_mask != 0 && !IsInPhase(e.Event.event_phase_mask)) + return; + + if (e.GetEventType() == SmartEvents.UpdateIc && (me == null || !me.IsInCombat())) + return; + + if (e.GetEventType() == SmartEvents.UpdateOoc && (me != null && me.IsInCombat()))//can be used with me=NULL (go script) + return; + + if (e.timer < diff) + { + // delay spell cast event if another spell is being casted + if (e.GetActionType() == SmartActions.Cast) + { + if (!Convert.ToBoolean(e.Action.cast.castFlags & (uint)SmartCastFlags.InterruptPrevious)) + { + if (me != null && me.HasUnitState(UnitState.Casting)) + { + e.timer = 1; + return; + } + } + } + + // Delay flee for assist event if stunned or rooted + if (e.GetActionType() == SmartActions.FleeForAssist) + { + if (me && me.HasUnitState(UnitState.Root | UnitState.Stunned)) + { + e.timer = 1; + return; + } + } + + e.active = true;//activate events with cooldown + switch (e.GetEventType())//process ONLY timed events + { + case SmartEvents.Update: + case SmartEvents.UpdateIc: + case SmartEvents.UpdateOoc: + case SmartEvents.HealtPct: + case SmartEvents.TargetHealthPct: + case SmartEvents.ManaPct: + case SmartEvents.TargetManaPct: + case SmartEvents.Range: + case SmartEvents.VictimCasting: + case SmartEvents.FriendlyHealth: + case SmartEvents.FriendlyIsCc: + case SmartEvents.FriendlyMissingBuff: + case SmartEvents.HasAura: + case SmartEvents.TargetBuffed: + case SmartEvents.IsBehindTarget: + case SmartEvents.FriendlyHealthPCT: + case SmartEvents.DistanceCreature: + case SmartEvents.DistanceGameobject: + { + ProcessEvent(e); + if (e.GetScriptType() == SmartScriptType.TimedActionlist) + { + e.enableTimed = false;//disable event if it is in an ActionList and was processed once + foreach (var holder in mTimedActionList) + { + //find the first event which is not the current one and enable it + if (holder.event_id > e.event_id) + { + holder.enableTimed = true; + break; + } + } + } + break; + } + } + } + else + { + e.timer -= diff; + if (e.entryOrGuid == 15294 && me.GetGUID().GetCounter() == 55039 && e.timer != 0) + { + Log.outError(LogFilter.Server, "Called UpdateTimer: reduce timer: e.timer: {0}, diff: {1} current time: {2}", e.timer, diff, Time.GetMSTime()); + } + } + + } + + bool CheckTimer(SmartScriptHolder e) + { + return e.active; + } + + void InstallEvents() + { + if (!mInstallEvents.Empty()) + { + foreach (var holder in mInstallEvents) + mEvents.Add(holder);//must be before UpdateTimers + + mInstallEvents.Clear(); + } + } + + public void OnUpdate(uint diff) + { + if ((mScriptType == SmartScriptType.Creature || mScriptType == SmartScriptType.GameObject) && GetBaseObject() == null) + return; + + InstallEvents();//before UpdateTimers + + foreach (var holder in mEvents) + UpdateTimer(holder, diff); + + if (!mStoredEvents.Empty()) + foreach (var holder in mStoredEvents) + UpdateTimer(holder, diff); + + bool needCleanup = true; + if (!mTimedActionList.Empty()) + { + foreach (var holder in mTimedActionList) + { + if (holder.enableTimed) + { + UpdateTimer(holder, diff); + needCleanup = false; + } + } + } + if (needCleanup) + mTimedActionList.Clear(); + + if (!mRemIDs.Empty()) + { + foreach (var id in mRemIDs) + { + RemoveStoredEvent(id); + } + } + if (mUseTextTimer && me != null) + { + if (mTextTimer < diff) + { + uint textID = mLastTextID; + mLastTextID = 0; + uint entry = mTalkerEntry; + mTalkerEntry = 0; + mTextTimer = 0; + mUseTextTimer = false; + ProcessEventsFor(SmartEvents.TextOver, null, textID, entry); + } + else mTextTimer -= diff; + } + } + + void FillScript(List e, WorldObject obj, AreaTriggerRecord at, SceneTemplate scene) + { + if (e.Empty()) + { + if (obj != null) + Log.outDebug(LogFilter.ScriptsAi, "SmartScript: EventMap for Entry {0} is empty but is using SmartScript.", obj.GetEntry()); + if (at != null) + Log.outDebug(LogFilter.ScriptsAi, "SmartScript: EventMap for AreaTrigger {0} is empty but is using SmartScript.", at.Id); + if (scene != null) + Log.outDebug(LogFilter.ScriptsAi, "SmartScript: EventMap for SceneId {0} is empty but is using SmartScript.", scene.SceneId); + return; + } + foreach (var holder in e) + { + if (holder.Event.event_flags.HasAnyFlag(SmartEventFlags.DifficultyAll))//if has instance flag add only if in it + { + if (obj != null && obj.GetMap().IsDungeon()) + { + if (Convert.ToBoolean(1 << ((int)obj.GetMap().GetSpawnMode() + 1) & (int)holder.Event.event_flags)) + { + mEvents.Add(holder); + } + } + continue; + } + mEvents.Add(holder);//NOTE: 'world(0)' events still get processed in ANY instance mode + } + if (mEvents.Empty() && obj != null) + Log.outError(LogFilter.Sql, "SmartScript: Entry {0} has events but no events added to list because of instance flags.", obj.GetEntry()); + if (mEvents.Empty() && at != null) + Log.outError(LogFilter.Sql, "SmartScript: AreaTrigger {0} has events but no events added to list because of instance flags. NOTE: triggers can not handle any instance flags.", at.Id); + if (mEvents.Empty() && scene != null) + Log.outError(LogFilter.Sql, "SmartScript: SceneId {0} has events but no events added to list because of instance flags. NOTE: scenes can not handle any instance flags.", scene.SceneId); + } + + void GetScript() + { + List e; + if (me != null) + { + e = Global.SmartAIMgr.GetScript(-((int)me.GetSpawnId()), mScriptType); + if (e.Empty()) + e = Global.SmartAIMgr.GetScript((int)me.GetEntry(), mScriptType); + FillScript(e, me, null, null); + } + else if (go != null) + { + e = Global.SmartAIMgr.GetScript(-((int)go.GetSpawnId()), mScriptType); + if (e.Empty()) + e = Global.SmartAIMgr.GetScript((int)go.GetEntry(), mScriptType); + FillScript(e, go, null, null); + } + else if (trigger != null) + { + e = Global.SmartAIMgr.GetScript((int)trigger.Id, mScriptType); + FillScript(e, null, trigger, null); + } + else if (sceneTemplate != null) + { + e = Global.SmartAIMgr.GetScript((int)sceneTemplate.SceneId, mScriptType); + FillScript(e, null, null, sceneTemplate); + } + } + + public void OnInitialize(WorldObject obj, AreaTriggerRecord at = null, SceneTemplate scene = null) + { + if (obj != null) + { + switch (obj.GetTypeId()) + { + case TypeId.Unit: + mScriptType = SmartScriptType.Creature; + me = obj.ToCreature(); + Log.outDebug(LogFilter.Scripts, "SmartScript.OnInitialize: source is Creature {0}", me.GetEntry()); + break; + case TypeId.GameObject: + mScriptType = SmartScriptType.GameObject; + go = obj.ToGameObject(); + Log.outDebug(LogFilter.Scripts, "SmartScript.OnInitialize: source is GameObject {0}", go.GetEntry()); + break; + default: + Log.outError(LogFilter.Scripts, "SmartScript.OnInitialize: Unhandled TypeID !WARNING!"); + return; + } + } + else if (at != null) + { + mScriptType = SmartScriptType.AreaTrigger; + trigger = at; + Log.outDebug(LogFilter.ScriptsAi, "SmartScript.OnInitialize: source is AreaTrigger {0}", trigger.Id); + } + else if (scene != null) + { + mScriptType = SmartScriptType.Scene; + sceneTemplate = scene; + Log.outDebug(LogFilter.ScriptsAi, "SmartScript.OnInitialize: source is Scene with id {0}", scene.SceneId); + } + else + { + Log.outError(LogFilter.ScriptsAi, "SmartScript.OnInitialize: !WARNING! Initialized objects are Null."); + return; + } + + GetScript();//load copy of script + + foreach (var holder in mEvents) + InitTimer(holder);//calculate timers for first time use + + ProcessEventsFor(SmartEvents.AiInit); + InstallEvents(); + ProcessEventsFor(SmartEvents.JustCreated); + } + + public void OnMoveInLineOfSight(Unit who) + { + ProcessEventsFor(SmartEvents.OocLos, who); + + if (me == null) + return; + + if (me.GetVictim() != null) + return; + + ProcessEventsFor(SmartEvents.IcLos, who); + } + + Unit DoSelectLowestHpFriendly(float range, uint MinHPDiff) + { + if (!me) + return null; + + var u_check = new MostHPMissingInRange(me, range, MinHPDiff); + var searcher = new UnitLastSearcher(me, u_check); + Cell.VisitGridObjects(me, searcher, range); + return searcher.GetTarget(); + } + + void DoFindFriendlyCC(List _list, float range) + { + if (me == null) + return; + + var u_check = new FriendlyCCedInRange(me, range); + var searcher = new CreatureListSearcher(me, _list, u_check); + Cell.VisitGridObjects(me, searcher, range); + } + + void DoFindFriendlyMissingBuff(List list, float range, uint spellid) + { + if (me == null) + return; + + var u_check = new FriendlyMissingBuffInRange(me, range, spellid); + var searcher = new CreatureListSearcher(me, list, u_check); + Cell.VisitGridObjects(me, searcher, range); + } + + Unit DoFindClosestFriendlyInRange(float range) + { + if (!me) + return null; + + var u_check = new AnyFriendlyUnitInObjectRangeCheck(me, me, range); + var searcher = new UnitLastSearcher(me, u_check); + Cell.VisitAllObjects(me, searcher, range); + return searcher.GetTarget(); + } + + public void SetScript9(SmartScriptHolder e, uint entry) + { + mTimedActionList.Clear(); + mTimedActionList = Global.SmartAIMgr.GetScript((int)entry, SmartScriptType.TimedActionlist); + if (mTimedActionList.Empty()) + return; + int i = 0; + foreach (var holder in mTimedActionList) + { + if (i++ == 0) + { + holder.enableTimed = true;//enable processing only for the first action + } + else holder.enableTimed = false; + + if (e.Action.timedActionList.timerType == 1) + holder.Event.type = SmartEvents.UpdateIc; + else if (e.Action.timedActionList.timerType > 1) + holder.Event.type = SmartEvents.Update; + InitTimer(holder); + } + } + + Unit GetLastInvoker() + { + WorldObject lookupRoot = me; + if (!lookupRoot) + lookupRoot = go; + + if (lookupRoot) + return Global.ObjAccessor.GetUnit(lookupRoot, mLastInvoker); + + return Global.ObjAccessor.FindPlayer(mLastInvoker); + } + + public void SetPathId(uint id) { mPathId = id; } + public uint GetPathId() { return mPathId; } + WorldObject GetBaseObject() + { + WorldObject obj = null; + if (me != null) + obj = me; + else if (go != null) + obj = go; + return obj; + } + WorldObject GetBaseObjectOrUnit(Unit unit) + { + WorldObject summoner = GetBaseObject(); + + if (!summoner && unit) + return unit; + + return summoner; + } + + bool IsUnit(WorldObject obj) { return obj != null && obj.IsTypeId(TypeId.Unit) || obj.IsTypeId(TypeId.Player); } + public bool IsPlayer(WorldObject obj) { return obj != null && obj.IsTypeId(TypeId.Player); } + bool IsCreature(WorldObject obj) { return obj != null && obj.IsTypeId(TypeId.Unit); } + static bool IsCreatureInControlOfSelf(WorldObject obj) + { + Creature creatureObj = obj?.ToCreature(); + if (creatureObj) + return !creatureObj.IsCharmed() && !creatureObj.IsControlledByPlayer(); + else + return false; + } + bool IsGameObject(WorldObject obj) { return obj != null && obj.IsTypeId(TypeId.GameObject); } + + void StoreTargetList(List targets, uint id) + { + if (targets == null) + return; + + if (mTargetStorage.ContainsKey(id)) + { + // check if already stored + if (mTargetStorage[id] == targets) + return; + } + + mTargetStorage[id] = targets; + } + + bool IsSmart(Creature c = null) + { + bool smart = true; + if (c != null && c.GetAIName() != "SmartAI") + smart = false; + + if (me == null || me.GetAIName() != "SmartAI") + smart = false; + + if (!smart) + Log.outError(LogFilter.Sql, "SmartScript: Action target Creature (GUID: {0} Entry: {1}) is not using SmartAI, action skipped to prevent crash.", c != null ? c.GetSpawnId() : (me != null ? me.GetSpawnId() : 0), c != null ? c.GetEntry() : (me != null ? me.GetEntry() : 0)); + + return smart; + } + + bool IsSmartGO(GameObject g = null) + { + bool smart = true; + if (g != null && g.GetAIName() != "SmartGameObjectAI") + smart = false; + + if (go == null || go.GetAIName() != "SmartGameObjectAI") + smart = false; + if (!smart) + Log.outError(LogFilter.Sql, "SmartScript: Action target GameObject (GUID: {0} Entry: {1}) is not using SmartGameObjectAI, action skipped to prevent crash.", g != null ? g.GetSpawnId() : (go != null ? go.GetSpawnId() : 0), g != null ? g.GetEntry() : (go != null ? go.GetEntry() : 0)); + + return smart; + } + + public List GetTargetList(uint id) + { + var list = mTargetStorage.LookupByKey(id); + if (!list.Empty()) + return list; + return null; + } + + void StoreCounter(uint id, uint value, uint reset) + { + if (mCounterList.ContainsKey(id)) + { + if (reset == 0) + value += GetCounterValue(id); + mCounterList.Remove(id); + } + + mCounterList.Add(id, value); + ProcessEventsFor(SmartEvents.CounterSet); + } + + uint GetCounterId(uint id) + { + if (mCounterList.ContainsKey(id)) + return id; + return 0; + } + + uint GetCounterValue(uint id) + { + if (mCounterList.ContainsKey(id)) + return mCounterList[id]; + return 0; + } + + GameObject FindGameObjectNear(WorldObject searchObject, ulong guid) + { + var bounds = searchObject.GetMap().GetGameObjectBySpawnIdStore().LookupByKey(guid); + if (bounds.Empty()) + return null; + + return bounds[0]; + } + + Creature FindCreatureNear(WorldObject searchObject, ulong guid) + { + var bounds = searchObject.GetMap().GetCreatureBySpawnIdStore().LookupByKey(guid); + if (bounds.Empty()) + return null; + + var foundCreature = bounds.Find(creature => { return creature.IsAlive(); }); + + return foundCreature ?? bounds[0]; + } + + void ResetBaseObject() + { + WorldObject lookupRoot = me; + if (!lookupRoot) + lookupRoot = go; + + if (lookupRoot) + { + if (!meOrigGUID.IsEmpty()) + { + Creature m = ObjectAccessor.GetCreature(lookupRoot, meOrigGUID); + if (m != null) + { + me = m; + go = null; + } + } + + if (!goOrigGUID.IsEmpty()) + { + GameObject o = ObjectAccessor.GetGameObject(lookupRoot, goOrigGUID); + if (o != null) + { + me = null; + go = o; + } + } + } + goOrigGUID.Clear(); + meOrigGUID.Clear(); + } + + void IncPhase(int p = 1) + { + if (p >= 0) + mEventPhase += (uint)p; + else + DecPhase(-p); + } + + void DecPhase(int p = 1) + { + if (mEventPhase > p) + mEventPhase -= (uint)p; + else + mEventPhase = 0; + } + bool IsInPhase(uint p) { return Convert.ToBoolean((1 << (int)(mEventPhase - 1)) & p); } + void SetPhase(uint p = 0) { mEventPhase = p; } + + void RemoveStoredEvent(uint id) + { + if (!mStoredEvents.Empty()) + { + foreach (var holder in mStoredEvents) + { + if (holder.event_id == id) + { + mStoredEvents.Remove(holder); + return; + } + } + } + } + + MultiMap mTargetStorage = new MultiMap(); + + public ObjectGuid mLastInvoker; + + Dictionary mCounterList = new Dictionary(); + + List mEvents = new List(); + List mInstallEvents = new List(); + List mTimedActionList = new List(); + Creature me; + ObjectGuid meOrigGUID; + GameObject go; + ObjectGuid goOrigGUID; + AreaTriggerRecord trigger; + SceneTemplate sceneTemplate; + SmartScriptType mScriptType; + uint mEventPhase; + + uint mPathId; + List mStoredEvents = new List(); + List mRemIDs = new List(); + + uint mTextTimer; + uint mLastTextID; + ObjectGuid mTextGUID; + uint mTalkerEntry; + bool mUseTextTimer; + + SmartAITemplate mTemplate; + } +} diff --git a/Game/Accounts/AccountManager.cs b/Game/Accounts/AccountManager.cs new file mode 100644 index 000000000..45b4a60c0 --- /dev/null +++ b/Game/Accounts/AccountManager.cs @@ -0,0 +1,515 @@ +/* + * Copyright (C) 2012-2017 CypherCore + * + * 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, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +using Framework.Constants; +using Framework.Database; +using Game.Accounts; +using Game.Entities; +using System; +using System.Collections.Generic; +using System.Security.Cryptography; +using System.Text; + +namespace Game +{ + public sealed class AccountManager : Singleton + { + const int MaxAccountLength = 16; + const int MaxEmailLength = 64; + + AccountManager() { } + + public AccountOpResult CreateAccount(string username, string password, string email = "", uint bnetAccountId = 0, byte bnetIndex = 0) + { + if (username.Length > MaxAccountLength) + return AccountOpResult.NameTooLong; // username's too long + + if (password.Length > MaxAccountLength) + return AccountOpResult.PassTooLong; // password's too long + + if (GetId(username) != 0) + return AccountOpResult.NameAlreadyExist; // username does already exist + + PreparedStatement stmt = DB.Login.GetPreparedStatement(LoginStatements.INS_ACCOUNT); + stmt.AddValue(0, username); + stmt.AddValue(1, CalculateShaPassHash(username, password)); + stmt.AddValue(2, email); + stmt.AddValue(3, email); + if (bnetAccountId != 0 && bnetIndex != 0) + { + stmt.AddValue(4, bnetAccountId); + stmt.AddValue(5, bnetIndex); + } + else + { + stmt.AddValue(4, null); + stmt.AddValue(5, null); + } + DB.Login.Execute(stmt); // Enforce saving, otherwise AddGroup can fail + + stmt = DB.Login.GetPreparedStatement(LoginStatements.INS_REALM_CHARACTERS_INIT); + DB.Login.Execute(stmt); + + return AccountOpResult.Ok; // everything's fine + } + + public AccountOpResult DeleteAccount(uint accountId) + { + // Check if accounts exists + PreparedStatement stmt = DB.Login.GetPreparedStatement(LoginStatements.SEL_ACCOUNT_BY_ID); + stmt.AddValue(0, accountId); + SQLResult result = DB.Login.Query(stmt); + + if (result.IsEmpty()) + return AccountOpResult.NameNotExist; + + // Obtain accounts characters + stmt = DB.Characters.GetPreparedStatement(CharStatements.SEL_CHARS_BY_ACCOUNT_ID); + stmt.AddValue(0, accountId); + result = DB.Characters.Query(stmt); + + if (!result.IsEmpty()) + { + do + { + ObjectGuid guid = ObjectGuid.Create(HighGuid.Player, result.Read(0)); + + // Kick if player is online + Player p = Global.ObjAccessor.FindPlayer(guid); + if (p) + { + WorldSession s = p.GetSession(); + s.KickPlayer(); // mark session to remove at next session list update + s.LogoutPlayer(false); // logout player without waiting next session list update + } + + Player.DeleteFromDB(guid, accountId, false); // no need to update realm characters + } while (result.NextRow()); + } + + // table realm specific but common for all characters of account for realm + stmt = DB.Characters.GetPreparedStatement(CharStatements.DEL_TUTORIALS); + stmt.AddValue(0, accountId); + DB.Characters.Execute(stmt); + + stmt = DB.Characters.GetPreparedStatement(CharStatements.DEL_ACCOUNT_DATA); + stmt.AddValue(0, accountId); + DB.Characters.Execute(stmt); + + stmt = DB.Characters.GetPreparedStatement(CharStatements.DEL_CHARACTER_BAN); + stmt.AddValue(0, accountId); + DB.Characters.Execute(stmt); + + SQLTransaction trans = new SQLTransaction(); + + stmt = DB.Login.GetPreparedStatement(LoginStatements.DEL_ACCOUNT); + stmt.AddValue(0, accountId); + trans.Append(stmt); + + stmt = DB.Login.GetPreparedStatement(LoginStatements.DEL_ACCOUNT_ACCESS); + stmt.AddValue(0, accountId); + trans.Append(stmt); + + stmt = DB.Login.GetPreparedStatement(LoginStatements.DEL_REALM_CHARACTERS); + stmt.AddValue(0, accountId); + trans.Append(stmt); + + stmt = DB.Login.GetPreparedStatement(LoginStatements.DEL_ACCOUNT_BANNED); + stmt.AddValue(0, accountId); + trans.Append(stmt); + + stmt = DB.Login.GetPreparedStatement(LoginStatements.DEL_ACCOUNT_MUTED); + stmt.AddValue(0, accountId); + trans.Append(stmt); + + DB.Login.CommitTransaction(trans); + + return AccountOpResult.Ok; + } + + public AccountOpResult ChangeUsername(uint accountId, string newUsername, string newPassword) + { + // Check if accounts exists + PreparedStatement stmt = DB.Login.GetPreparedStatement(LoginStatements.SEL_ACCOUNT_BY_ID); + stmt.AddValue(0, accountId); + SQLResult result = DB.Login.Query(stmt); + + if (result.IsEmpty()) + return AccountOpResult.NameNotExist; + + if (newUsername.Length > MaxAccountLength) + return AccountOpResult.NameTooLong; + + if (newPassword.Length > MaxAccountLength) + return AccountOpResult.PassTooLong; + + stmt = DB.Login.GetPreparedStatement(LoginStatements.UPD_USERNAME); + stmt.AddValue(0, newUsername); + stmt.AddValue(1, CalculateShaPassHash(newUsername, newPassword)); + stmt.AddValue(2, accountId); + DB.Login.Execute(stmt); + + return AccountOpResult.Ok; + } + + public AccountOpResult ChangePassword(uint accountId, string newPassword) + { + string username; + + if (!GetName(accountId, out username)) + return AccountOpResult.NameNotExist; // account doesn't exist + + if (newPassword.Length > MaxAccountLength) + return AccountOpResult.PassTooLong; + + PreparedStatement stmt = DB.Login.GetPreparedStatement(LoginStatements.UPD_PASSWORD); + stmt.AddValue(0, CalculateShaPassHash(username, newPassword)); + stmt.AddValue(1, accountId); + DB.Login.Execute(stmt); + + stmt = DB.Login.GetPreparedStatement(LoginStatements.UPD_VS); + stmt.AddValue(0, ""); + stmt.AddValue(1, ""); + stmt.AddValue(2, username); + DB.Login.Execute(stmt); + + return AccountOpResult.Ok; + } + + public AccountOpResult ChangeEmail(uint accountId, string newEmail) + { + string username; + + if (!GetName(accountId, out username)) + return AccountOpResult.NameNotExist; // account doesn't exist + + if (newEmail.Length > MaxEmailLength) + return AccountOpResult.EmailTooLong; + + PreparedStatement stmt = DB.Login.GetPreparedStatement(LoginStatements.UPD_EMAIL); + stmt.AddValue(0, newEmail); + stmt.AddValue(1, accountId); + DB.Login.Execute(stmt); + + return AccountOpResult.Ok; + } + + public AccountOpResult ChangeRegEmail(uint accountId, string newEmail) + { + string username; + + if (!GetName(accountId, out username)) + return AccountOpResult.NameNotExist; // account doesn't exist + + if (newEmail.Length > MaxEmailLength) + return AccountOpResult.EmailTooLong; + + PreparedStatement stmt = DB.Login.GetPreparedStatement(LoginStatements.UPD_REG_EMAIL); + stmt.AddValue(0, newEmail); + stmt.AddValue(1, accountId); + DB.Login.Execute(stmt); + + return AccountOpResult.Ok; + } + + public uint GetId(string username) + { + PreparedStatement stmt = DB.Login.GetPreparedStatement(LoginStatements.GET_ACCOUNT_ID_BY_USERNAME); + stmt.AddValue(0, username); + SQLResult result = DB.Login.Query(stmt); + + return !result.IsEmpty() ? result.Read(0) : 0; + } + + public AccountTypes GetSecurity(uint accountId) + { + PreparedStatement stmt = DB.Login.GetPreparedStatement(LoginStatements.GET_ACCOUNT_ACCESS_GMLEVEL); + stmt.AddValue(0, accountId); + SQLResult result = DB.Login.Query(stmt); + + return !result.IsEmpty() ? (AccountTypes)result.Read(0) : AccountTypes.Player; + } + + public AccountTypes GetSecurity(uint accountId, int realmId) + { + PreparedStatement stmt = DB.Login.GetPreparedStatement(LoginStatements.GET_GMLEVEL_BY_REALMID); + stmt.AddValue(0, accountId); + stmt.AddValue(1, realmId); + SQLResult result = DB.Login.Query(stmt); + + return !result.IsEmpty() ? (AccountTypes)result.Read(0) : AccountTypes.Player; + } + + public bool GetName(uint accountId, out string name) + { + name = ""; + PreparedStatement stmt = DB.Login.GetPreparedStatement(LoginStatements.GET_USERNAME_BY_ID); + stmt.AddValue(0, accountId); + SQLResult result = DB.Login.Query(stmt); + + if (!result.IsEmpty()) + { + name = result.Read(0); + return true; + } + + return false; + } + + bool GetEmail(uint accountId, out string email) + { + email = ""; + PreparedStatement stmt = DB.Login.GetPreparedStatement(LoginStatements.GET_EMAIL_BY_ID); + stmt.AddValue(0, accountId); + SQLResult result = DB.Login.Query(stmt); + + if (!result.IsEmpty()) + { + email = result.Read(0); + return true; + } + + return false; + } + + public bool CheckPassword(uint accountId, string password) + { + string username; + + if (!GetName(accountId, out username)) + return false; + + PreparedStatement stmt = DB.Login.GetPreparedStatement(LoginStatements.SEL_CHECK_PASSWORD); + stmt.AddValue(0, accountId); + stmt.AddValue(1, CalculateShaPassHash(username, password)); + SQLResult result = DB.Login.Query(stmt); + + return result.IsEmpty() ? false : true; + } + + public bool CheckEmail(uint accountId, string newEmail) + { + string oldEmail; + + // We simply return false for a non-existing email + if (!GetEmail(accountId, out oldEmail)) + return false; + + if (oldEmail == newEmail) + return true; + + return false; + } + + public uint GetCharactersCount(uint accountId) + { + // check character count + PreparedStatement stmt = DB.Characters.GetPreparedStatement(CharStatements.SEL_SUM_CHARS); + stmt.AddValue(0, accountId); + SQLResult result = DB.Characters.Query(stmt); + + return result.IsEmpty() ? 0 : (uint)result.Read(0); + } + + string CalculateShaPassHash(string name, string password) + { + SHA1 sha = SHA1.Create(); + return sha.ComputeHash(Encoding.UTF8.GetBytes(name + ":" + password)).ToHexString(); + } + + public bool IsPlayerAccount(AccountTypes gmlevel) + { + return gmlevel == AccountTypes.Player; + } + + public bool IsAdminAccount(AccountTypes gmlevel) + { + return gmlevel >= AccountTypes.Administrator && gmlevel <= AccountTypes.Console; + } + + public bool IsConsoleAccount(AccountTypes gmlevel) + { + return gmlevel == AccountTypes.Console; + } + + public void LoadRBAC() + { + ClearRBAC(); + + Log.outDebug(LogFilter.Rbac, "AccountMgr:LoadRBAC"); + uint oldMSTime = Time.GetMSTime(); + uint count1 = 0; + uint count2 = 0; + uint count3 = 0; + + Log.outDebug(LogFilter.Rbac, "AccountMgr:LoadRBAC: Loading permissions"); + SQLResult result = DB.Login.Query("SELECT id, name FROM rbac_permissions"); + if (result.IsEmpty()) + { + Log.outInfo(LogFilter.ServerLoading, "Loaded 0 account permission definitions. DB table `rbac_permissions` is empty."); + return; + } + + do + { + uint id = result.Read(0); + _permissions[id] = new RBACPermission(id, result.Read(1)); + ++count1; + } + while (result.NextRow()); + + Log.outDebug(LogFilter.Rbac, "AccountMgr:LoadRBAC: Loading linked permissions"); + result = DB.Login.Query("SELECT id, linkedId FROM rbac_linked_permissions ORDER BY id ASC"); + if (result.IsEmpty()) + { + Log.outInfo(LogFilter.ServerLoading, "Loaded 0 linked permissions. DB table `rbac_linked_permissions` is empty."); + return; + } + + uint permissionId = 0; + RBACPermission permission = null; + + do + { + uint newId = result.Read(0); + if (permissionId != newId) + { + permissionId = newId; + permission = _permissions[newId]; + } + + uint linkedPermissionId = result.Read(1); + if (linkedPermissionId == permissionId) + { + Log.outError(LogFilter.Sql, "RBAC Permission {0} has itself as linked permission. Ignored", permissionId); + continue; + } + permission.AddLinkedPermission(linkedPermissionId); + ++count2; + } + while (result.NextRow()); + + Log.outDebug(LogFilter.Rbac, "AccountMgr:LoadRBAC: Loading default permissions"); + result = DB.Login.Query("SELECT secId, permissionId FROM rbac_default_permissions ORDER BY secId ASC"); + if (result.IsEmpty()) + { + Log.outInfo(LogFilter.ServerLoading, "Loaded 0 default permission definitions. DB table `rbac_default_permissions` is empty."); + return; + } + + uint secId = 255; + do + { + uint newId = result.Read(0); + if (secId != newId) + secId = newId; + + _defaultPermissions.Add((byte)secId, result.Read(1)); + ++count3; + } + while (result.NextRow()); + + Log.outInfo(LogFilter.ServerLoading, "Loaded {0} permission definitions, {1} linked permissions and {2} default permissions in {3} ms", count1, count2, count3, Time.GetMSTimeDiffToNow(oldMSTime)); + } + + public void UpdateAccountAccess(RBACData rbac, uint accountId, byte securityLevel, int realmId) + { + if (rbac != null && securityLevel == rbac.GetSecurityLevel()) + rbac.SetSecurityLevel(securityLevel); + + PreparedStatement stmt; + SQLTransaction trans = new SQLTransaction(); + // Delete old security level from DB + if (realmId == -1) + { + stmt = DB.Login.GetPreparedStatement(LoginStatements.DEL_ACCOUNT_ACCESS); + stmt.AddValue(0, accountId); + trans.Append(stmt); + } + else + { + stmt = DB.Login.GetPreparedStatement(LoginStatements.DEL_ACCOUNT_ACCESS_BY_REALM); + stmt.AddValue(0, accountId); + stmt.AddValue(1, realmId); + trans.Append(stmt); + } + + // Add new security level + if (securityLevel != 0) + { + stmt = DB.Login.GetPreparedStatement(LoginStatements.INS_ACCOUNT_ACCESS); + stmt.AddValue(0, accountId); + stmt.AddValue(1, securityLevel); + stmt.AddValue(2, realmId); + trans.Append(stmt); + } + + DB.Login.CommitTransaction(trans); + } + + public RBACPermission GetRBACPermission(uint permissionId) + { + Log.outDebug(LogFilter.Rbac, "AccountMgr:GetRBACPermission: {0}", permissionId); + return _permissions.LookupByKey(permissionId); + } + + public bool HasPermission(uint accountId, RBACPermissions permissionId, uint realmId) + { + if (accountId == 0) + { + Log.outError(LogFilter.Rbac, "AccountMgr:HasPermission: Wrong accountId 0"); + return false; + } + + RBACData rbac = new RBACData(accountId, "", (int)realmId); + rbac.LoadFromDB(); + bool hasPermission = rbac.HasPermission(permissionId); + + Log.outDebug(LogFilter.Rbac, "AccountMgr:HasPermission [AccountId: {0}, PermissionId: {1}, realmId: {2}]: {3}", + accountId, permissionId, realmId, hasPermission); + return hasPermission; + } + + void ClearRBAC() + { + _permissions.Clear(); + _defaultPermissions.Clear(); + } + + public List GetRBACDefaultPermissions(byte secLevel) + { + return _defaultPermissions[secLevel]; + } + + public Dictionary GetRBACPermissionList() { return _permissions; } + + Dictionary _permissions = new Dictionary(); + MultiMap _defaultPermissions = new MultiMap(); + } + + public enum AccountOpResult + { + Ok, + NameTooLong, + PassTooLong, + EmailTooLong, + NameAlreadyExist, + NameNotExist, + DBInternalError, + BadLink + } +} diff --git a/Game/Accounts/BNetAccountManager.cs b/Game/Accounts/BNetAccountManager.cs new file mode 100644 index 000000000..422fefa56 --- /dev/null +++ b/Game/Accounts/BNetAccountManager.cs @@ -0,0 +1,183 @@ +/* + * Copyright (C) 2012-2017 CypherCore + * + * 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, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +using Framework.Database; +using System; +using System.Diagnostics.Contracts; +using System.Security.Cryptography; +using System.Text; + +namespace Game +{ + public sealed class BNetAccountManager : Singleton + { + BNetAccountManager() { } + + public AccountOpResult CreateBattlenetAccount(string email, string password, bool withGameAccount, out string gameAccountName) + { + gameAccountName = ""; + + if (email.Length > 320) + return AccountOpResult.NameTooLong; + + if (password.Length > 16) + return AccountOpResult.PassTooLong; + + if (GetId(email) != 0) + return AccountOpResult.NameAlreadyExist; + + PreparedStatement stmt = DB.Login.GetPreparedStatement(LoginStatements.INS_BNET_ACCOUNT); + stmt.AddValue(0, email); + stmt.AddValue(1, CalculateShaPassHash(email, password)); + DB.Login.Execute(stmt); + + uint newAccountId = GetId(email); + Contract.Assert(newAccountId != 0); + + if (withGameAccount) + { + gameAccountName = newAccountId + "#1"; + Global.AccountMgr.CreateAccount(gameAccountName, password, email, newAccountId, 1); + } + + return AccountOpResult.Ok; + } + + public AccountOpResult ChangePassword(uint accountId, string newPassword) + { + string username; + if (!GetName(accountId, out username)) + return AccountOpResult.NameNotExist; + + if (newPassword.Length > 16) + return AccountOpResult.PassTooLong; + + PreparedStatement stmt = DB.Login.GetPreparedStatement(LoginStatements.UPD_BNET_PASSWORD); + stmt.AddValue(0, CalculateShaPassHash(username, newPassword)); + stmt.AddValue(1, accountId); + DB.Login.Execute(stmt); + + return AccountOpResult.Ok; + } + + public bool CheckPassword(uint accountId, string password) + { + string username; + if (!GetName(accountId, out username)) + return false; + + PreparedStatement stmt = DB.Login.GetPreparedStatement(LoginStatements.SEL_BNET_CHECK_PASSWORD); + stmt.AddValue(0, accountId); + stmt.AddValue(1, CalculateShaPassHash(username, password)); + + return !DB.Login.Query(stmt).IsEmpty(); + } + + public AccountOpResult LinkWithGameAccount(string email, string gameAccountName) + { + uint bnetAccountId = GetId(email); + if (bnetAccountId == 0) + return AccountOpResult.NameNotExist; + + uint gameAccountId = Global.AccountMgr.GetId(gameAccountName); + if (gameAccountId == 0) + return AccountOpResult.NameNotExist; + + if (GetIdByGameAccount(gameAccountId) != 0) + return AccountOpResult.BadLink; + + PreparedStatement stmt = DB.Login.GetPreparedStatement(LoginStatements.UPD_BNET_GAME_ACCOUNT_LINK); + stmt.AddValue(0, bnetAccountId); + stmt.AddValue(1, GetMaxIndex(bnetAccountId) + 1); + stmt.AddValue(2, gameAccountId); + DB.Login.Execute(stmt); + return AccountOpResult.Ok; + } + + public AccountOpResult UnlinkGameAccount(string gameAccountName) + { + uint gameAccountId = Global.AccountMgr.GetId(gameAccountName); + if (gameAccountId == 0) + return AccountOpResult.NameNotExist; + + if (GetIdByGameAccount(gameAccountId) == 0) + return AccountOpResult.BadLink; + + PreparedStatement stmt = DB.Login.GetPreparedStatement(LoginStatements.UPD_BNET_GAME_ACCOUNT_LINK); + stmt.AddValue(0, null); + stmt.AddValue(1, null); + stmt.AddValue(2, gameAccountId); + DB.Login.Execute(stmt); + return AccountOpResult.Ok; + } + + public uint GetId(string username) + { + PreparedStatement stmt = DB.Login.GetPreparedStatement(LoginStatements.SEL_BNET_ACCOUNT_ID_BY_EMAIL); + stmt.AddValue(0, username); + SQLResult result = DB.Login.Query(stmt); + if (!result.IsEmpty()) + return result.Read(0); + + return 0; + } + + public bool GetName(uint accountId, out string name) + { + name = ""; + PreparedStatement stmt = DB.Login.GetPreparedStatement(LoginStatements.SEL_BNET_ACCOUNT_EMAIL_BY_ID); + stmt.AddValue(0, accountId); + SQLResult result = DB.Login.Query(stmt); + if (!result.IsEmpty()) + { + name = result.Read(0); + return true; + } + + return false; + } + + public uint GetIdByGameAccount(uint gameAccountId) + { + PreparedStatement stmt = DB.Login.GetPreparedStatement(LoginStatements.SEL_BNET_ACCOUNT_ID_BY_GAME_ACCOUNT); + stmt.AddValue(0, gameAccountId); + SQLResult result = DB.Login.Query(stmt); + if (!result.IsEmpty()) + return result.Read(0); + + return 0; + } + + public byte GetMaxIndex(uint accountId) + { + PreparedStatement stmt = DB.Login.GetPreparedStatement(LoginStatements.SEL_BNET_MAX_ACCOUNT_INDEX); + stmt.AddValue(0, accountId); + SQLResult result = DB.Login.Query(stmt); + if (!result.IsEmpty()) + return result.Read(0); + + return 0; + } + + string CalculateShaPassHash(string name, string password) + { + SHA256 sha256 = SHA256.Create(); + var i = sha256.ComputeHash(Encoding.UTF8.GetBytes(name)); + return sha256.ComputeHash(Encoding.UTF8.GetBytes(i.ToHexString() + ":" + password)).ToHexString(); + } + } +} diff --git a/Game/Accounts/RoleAccessControl.cs b/Game/Accounts/RoleAccessControl.cs new file mode 100644 index 000000000..1ff42140f --- /dev/null +++ b/Game/Accounts/RoleAccessControl.cs @@ -0,0 +1,385 @@ +/* + * Copyright (C) 2012-2017 CypherCore + * + * 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, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +using Framework.Constants; +using Framework.Database; +using System.Collections.Generic; +using System.Linq; + +namespace Game.Accounts +{ + public class RBACData + { + public RBACData(uint id, string name, int realmId, byte secLevel = 255) + { + _id = id; + _name = name; + _realmId = realmId; + _secLevel = secLevel; + } + + public RBACCommandResult GrantPermission(uint permissionId, int realmId = 0) + { + // Check if permission Id exists + RBACPermission perm = Global.AccountMgr.GetRBACPermission(permissionId); + if (perm == null) + { + Log.outDebug(LogFilter.Rbac, "RBACData.GrantPermission [Id: {0} Name: {1}] (Permission {2}, RealmId {3}). Permission does not exists", + GetId(), GetName(), permissionId, realmId); + return RBACCommandResult.IdDoesNotExists; + } + + // Check if already added in denied list + if (HasDeniedPermission(permissionId)) + { + Log.outDebug(LogFilter.Rbac, "RBACData.GrantPermission [Id: {0} Name: {1}] (Permission {2}, RealmId {3}). Permission in deny list", + GetId(), GetName(), permissionId, realmId); + return RBACCommandResult.InDeniedList; + } + + // Already added? + if (HasGrantedPermission(permissionId)) + { + Log.outDebug(LogFilter.Rbac, "RBACData.GrantPermission [Id: {0} Name: {1}] (Permission {2}, RealmId {3}). Permission already granted", + GetId(), GetName(), permissionId, realmId); + return RBACCommandResult.CantAddAlreadyAdded; + } + + AddGrantedPermission(permissionId); + + // Do not save to db when loading data from DB (realmId = 0) + if (realmId != 0) + { + Log.outDebug(LogFilter.Rbac, "RBACData.GrantPermission [Id: {0} Name: {1}] (Permission {2}, RealmId {3}). Ok and DB updated", + GetId(), GetName(), permissionId, realmId); + SavePermission(permissionId, true, realmId); + CalculateNewPermissions(); + } + else + Log.outDebug(LogFilter.Rbac, "RBACData.GrantPermission [Id: {0} Name: {1}] (Permission {2}, RealmId {3}). Ok", + GetId(), GetName(), permissionId, realmId); + + return RBACCommandResult.OK; + } + + public RBACCommandResult DenyPermission(uint permissionId, int realmId = 0) + { + // Check if permission Id exists + RBACPermission perm = Global.AccountMgr.GetRBACPermission(permissionId); + if (perm == null) + { + Log.outDebug(LogFilter.Rbac, "RBACData.DenyPermission [Id: {0} Name: {1}] (Permission {2}, RealmId {3}). Permission does not exists", + GetId(), GetName(), permissionId, realmId); + return RBACCommandResult.IdDoesNotExists; + } + + // Check if already added in granted list + if (HasGrantedPermission(permissionId)) + { + Log.outDebug(LogFilter.Rbac, "RBACData.DenyPermission [Id: {0} Name: {1}] (Permission {2}, RealmId {3}). Permission in grant list", + GetId(), GetName(), permissionId, realmId); + return RBACCommandResult.InGrantedList; + } + + // Already added? + if (HasDeniedPermission(permissionId)) + { + Log.outDebug(LogFilter.Rbac, "RBACData.DenyPermission [Id: {0} Name: {1}] (Permission {2}, RealmId {3}). Permission already denied", + GetId(), GetName(), permissionId, realmId); + return RBACCommandResult.CantAddAlreadyAdded; + } + + AddDeniedPermission(permissionId); + + // Do not save to db when loading data from DB (realmId = 0) + if (realmId != 0) + { + Log.outDebug(LogFilter.Rbac, "RBACData.DenyPermission [Id: {0} Name: {1}] (Permission {2}, RealmId {3}). Ok and DB updated", + GetId(), GetName(), permissionId, realmId); + SavePermission(permissionId, false, realmId); + CalculateNewPermissions(); + } + else + Log.outDebug(LogFilter.Rbac, "RBACData.DenyPermission [Id: {0} Name: {1}] (Permission {2}, RealmId {3}). Ok", + GetId(), GetName(), permissionId, realmId); + + return RBACCommandResult.OK; + } + + void SavePermission(uint permission, bool granted, int realmId) + { + PreparedStatement stmt = DB.Login.GetPreparedStatement(LoginStatements.INS_RBAC_ACCOUNT_PERMISSION); + stmt.AddValue(0, GetId()); + stmt.AddValue(1, permission); + stmt.AddValue(2, granted); + stmt.AddValue(3, realmId); + DB.Login.Execute(stmt); + } + + public RBACCommandResult RevokePermission(uint permissionId, int realmId = 0) + { + // Check if it's present in any list + if (!HasGrantedPermission(permissionId) && !HasDeniedPermission(permissionId)) + { + Log.outDebug(LogFilter.Rbac, "RBACData.RevokePermission [Id: {0} Name: {1}] (Permission {2}, RealmId {3}). Not granted or revoked", + GetId(), GetName(), permissionId, realmId); + return RBACCommandResult.CantRevokeNotInList; + } + + RemoveGrantedPermission(permissionId); + RemoveDeniedPermission(permissionId); + + // Do not save to db when loading data from DB (realmId = 0) + if (realmId != 0) + { + Log.outDebug(LogFilter.Rbac, "RBACData.RevokePermission [Id: {0} Name: {1}] (Permission {2}, RealmId {3}). Ok and DB updated", + GetId(), GetName(), permissionId, realmId); + PreparedStatement stmt = DB.Login.GetPreparedStatement(LoginStatements.DEL_RBAC_ACCOUNT_PERMISSION); + stmt.AddValue(0, GetId()); + stmt.AddValue(1, permissionId); + stmt.AddValue(2, realmId); + DB.Login.Execute(stmt); + + CalculateNewPermissions(); + } + else + Log.outDebug(LogFilter.Rbac, "RBACData.RevokePermission [Id: {0} Name: {1}] (Permission {2}, RealmId {3}). Ok", + GetId(), GetName(), permissionId, realmId); + + return RBACCommandResult.OK; + } + + public void LoadFromDB() + { + ClearData(); + + Log.outDebug(LogFilter.Rbac, "RBACData.LoadFromDB [Id: {0} Name: {1}]: Loading permissions", GetId(), GetName()); + // Load account permissions (granted and denied) that affect current realm + PreparedStatement stmt = DB.Login.GetPreparedStatement(LoginStatements.SEL_RBAC_ACCOUNT_PERMISSIONS); + stmt.AddValue(0, GetId()); + stmt.AddValue(1, GetRealmId()); + + LoadFromDBCallback(DB.Login.Query(stmt)); + } + + public QueryCallback LoadFromDBAsync() + { + ClearData(); + + Log.outDebug(LogFilter.Rbac, "RBACData.LoadFromDB [Id: {0} Name: {1}]: Loading permissions", GetId(), GetName()); + // Load account permissions (granted and denied) that affect current realm + PreparedStatement stmt = DB.Login.GetPreparedStatement(LoginStatements.SEL_RBAC_ACCOUNT_PERMISSIONS); + stmt.AddValue(0, GetId()); + stmt.AddValue(1, GetRealmId()); + + return DB.Login.AsyncQuery(stmt); + } + + public void LoadFromDBCallback(SQLResult result) + { + if (!result.IsEmpty()) + { + do + { + if (result.Read(1)) + GrantPermission(result.Read(0)); + else + DenyPermission(result.Read(0)); + + } while (result.NextRow()); + } + + // Add default permissions + List permissions = Global.AccountMgr.GetRBACDefaultPermissions(_secLevel); + foreach (var id in permissions) + GrantPermission(id); + + // Force calculation of permissions + CalculateNewPermissions(); + } + + void CalculateNewPermissions() + { + Log.outDebug(LogFilter.Rbac, "RBACData.CalculateNewPermissions [Id: {0} Name: {1}]", GetId(), GetName()); + + // Get the list of granted permissions + _globalPerms = GetGrantedPermissions(); + ExpandPermissions(_globalPerms); + List revoked = GetDeniedPermissions(); + ExpandPermissions(revoked); + RemovePermissions(_globalPerms, revoked); + } + + void AddPermissions(List permsFrom, List permsTo) + { + foreach (var id in permsFrom) + permsTo.Add(id); + } + + /// + /// Removes a list of permissions from another list + /// + /// + /// + void RemovePermissions(List permsFrom, List permsToRemove) + { + foreach (var id in permsToRemove) + permsFrom.Remove(id); + } + + void ExpandPermissions(List permissions) + { + List toCheck = new List(permissions); + permissions.Clear(); + + while (!toCheck.Empty()) + { + // remove the permission from original list + uint permissionId = toCheck.FirstOrDefault(); + toCheck.RemoveAt(0); + + RBACPermission permission = Global.AccountMgr.GetRBACPermission(permissionId); + if (permission == null) + continue; + + // insert into the final list (expanded list) + permissions.Add(permissionId); + + // add all linked permissions (that are not already expanded) to the list of permissions to be checked + List linkedPerms = permission.GetLinkedPermissions(); + foreach (var id in linkedPerms) + if (!permissions.Contains(id)) + toCheck.Add(id); + } + + //Log.outDebug(LogFilter.General, "RBACData:ExpandPermissions: Expanded: {0}", GetDebugPermissionString(permissions)); + } + + void ClearData() + { + _grantedPerms.Clear(); + _deniedPerms.Clear(); + _globalPerms.Clear(); + } + + // Gets the Name of the Object + public string GetName() { return _name; } + // Gets the Id of the Object + public uint GetId() { return _id; } + + public bool HasPermission(RBACPermissions permission) + { + return _globalPerms.Contains((uint)permission); + } + + // Returns all the granted permissions (after computation) + public List GetPermissions() { return _globalPerms; } + // Returns all the granted permissions + public List GetGrantedPermissions() { return _grantedPerms; } + // Returns all the denied permissions + public List GetDeniedPermissions() { return _deniedPerms; } + + public void SetSecurityLevel(byte id) + { + _secLevel = id; + LoadFromDB(); + } + + public byte GetSecurityLevel() { return _secLevel; } + int GetRealmId() { return _realmId; } + + // Checks if a permission is granted + bool HasGrantedPermission(uint permissionId) + { + return _grantedPerms.Contains(permissionId); + } + + // Checks if a permission is denied + bool HasDeniedPermission(uint permissionId) + { + return _deniedPerms.Contains(permissionId); + } + + // Adds a new granted permission + void AddGrantedPermission(uint permissionId) + { + _grantedPerms.Add(permissionId); + } + + // Removes a granted permission + void RemoveGrantedPermission(uint permissionId) + { + _grantedPerms.Remove(permissionId); + } + + // Adds a new denied permission + void AddDeniedPermission(uint permissionId) + { + _deniedPerms.Add(permissionId); + } + + // Removes a denied permission + void RemoveDeniedPermission(uint permissionId) + { + _deniedPerms.Remove(permissionId); + } + + uint _id; // Account id + string _name; // Account name + int _realmId; // RealmId Affected + byte _secLevel; // Account SecurityLevel + List _grantedPerms = new List(); // Granted permissions + List _deniedPerms = new List(); // Denied permissions + List _globalPerms = new List(); // Calculated permissions + } + + public class RBACPermission + { + public RBACPermission(uint id = 0, string name = "") + { + _id = id; + _name = name; + } + + // Gets the Name of the Object + public string GetName() { return _name; } + // Gets the Id of the Object + public uint GetId() { return _id; } + + // Gets the Permissions linked to this permission + public List GetLinkedPermissions() { return _perms; } + // Adds a new linked Permission + public void AddLinkedPermission(uint id) { _perms.Add(id); } + // Removes a linked Permission + void RemoveLinkedPermission(uint id) { _perms.Remove(id); } + + + uint _id; // id of the object + string _name; // name of the object + List _perms = new List(); // Set of permissions + } + + public enum RBACCommandResult + { + OK, + CantAddAlreadyAdded, + CantRevokeNotInList, + InGrantedList, + InDeniedList, + IdDoesNotExists + } +} diff --git a/Game/Achievements/AchievementManager.cs b/Game/Achievements/AchievementManager.cs new file mode 100644 index 000000000..a12e4eae9 --- /dev/null +++ b/Game/Achievements/AchievementManager.cs @@ -0,0 +1,1325 @@ +/* + * Copyright (C) 2012-2017 CypherCore + * + * 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, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +using Framework.Collections; +using Framework.Constants; +using Framework.Database; +using Game.Chat; +using Game.DataStorage; +using Game.Entities; +using Game.Groups; +using Game.Guilds; +using Game.Mails; +using Game.Maps; +using Game.Network; +using Game.Network.Packets; +using System; +using System.Collections.Generic; +using System.Text; + +namespace Game.Achievements +{ + public class AchievementManager : CriteriaHandler + { + /// + /// called at player login. The player might have fulfilled some achievements when the achievement system wasn't working yet + /// + /// + public void CheckAllAchievementCriteria(Player referencePlayer) + { + // suppress sending packets + for (CriteriaTypes i = 0; i < CriteriaTypes.TotalTypes; ++i) + UpdateCriteria(i, 0, 0, 0, null, referencePlayer); + } + + public bool HasAchieved(uint achievementId) + { + return _completedAchievements.ContainsKey(achievementId); + } + + public uint GetAchievementPoints() + { + return _achievementPoints; + } + + public override bool CanUpdateCriteriaTree(Criteria criteria, CriteriaTree tree, Player referencePlayer) + { + AchievementRecord achievement = tree.Achievement; + if (achievement == null) + return false; + + if (HasAchieved(achievement.Id)) + { + Log.outTrace(LogFilter.Achievement, "CanUpdateCriteriaTree: (Id: {0} Type {1} Achievement {2}) Achievement already earned", + criteria.ID, criteria.Entry.Type, achievement.Id); + return false; + } + + if (achievement.MapID != -1 && referencePlayer.GetMapId() != achievement.MapID) + { + Log.outTrace(LogFilter.Achievement, "CanUpdateCriteriaTree: (Id: {0} Type {1} Achievement {2}) Wrong map", + criteria.ID, criteria.Entry.Type, achievement.Id); + return false; + } + + if ((achievement.Faction == AchievementFaction.Horde && referencePlayer.GetTeam() != Team.Horde) || + (achievement.Faction == AchievementFaction.Alliance && referencePlayer.GetTeam() != Team.Alliance)) + { + Log.outTrace(LogFilter.Achievement, "CanUpdateCriteriaTree: (Id: {0} Type {1} Achievement {2}) Wrong faction", + criteria.ID, criteria.Entry.Type, achievement.Id); + return false; + } + + return base.CanUpdateCriteriaTree(criteria, tree, referencePlayer); + } + + public override bool CanCompleteCriteriaTree(CriteriaTree tree) + { + AchievementRecord achievement = tree.Achievement; + if (achievement == null) + return false; + + // counter can never complete + if (achievement.Flags.HasAnyFlag(AchievementFlags.Counter)) + return false; + + if (achievement.Flags.HasAnyFlag(AchievementFlags.RealmFirstReach | AchievementFlags.RealmFirstKill)) + { + // someone on this realm has already completed that achievement + if (Global.AchievementMgr.IsRealmCompleted(achievement)) + return false; + } + + return true; + } + + public override void CompletedCriteriaTree(CriteriaTree tree, Player referencePlayer) + { + AchievementRecord achievement = tree.Achievement; + if (achievement == null) + return; + + // counter can never complete + if (achievement.Flags.HasAnyFlag(AchievementFlags.Counter)) + return; + + // already completed and stored + if (HasAchieved(achievement.Id)) + return; + + if (IsCompletedAchievement(achievement)) + CompletedAchievement(achievement, referencePlayer); + } + + public override void AfterCriteriaTreeUpdate(CriteriaTree tree, Player referencePlayer) + { + AchievementRecord achievement = tree.Achievement; + if (achievement == null) + return; + + // check again the completeness for SUMM and REQ COUNT achievements, + // as they don't depend on the completed criteria but on the sum of the progress of each individual criteria + if (achievement.Flags.HasAnyFlag(AchievementFlags.Summ)) + if (IsCompletedAchievement(achievement)) + CompletedAchievement(achievement, referencePlayer); + + var achRefList = Global.AchievementMgr.GetAchievementByReferencedId(achievement.Id); + foreach (AchievementRecord refAchievement in achRefList) + if (IsCompletedAchievement(refAchievement)) + CompletedAchievement(refAchievement, referencePlayer); + } + + bool IsCompletedAchievement(AchievementRecord entry) + { + // counter can never complete + if (entry.Flags.HasAnyFlag(AchievementFlags.Counter)) + return false; + + CriteriaTree tree = Global.CriteriaMgr.GetCriteriaTree(entry.CriteriaTree); + if (tree == null) + return false; + + // For SUMM achievements, we have to count the progress of each criteria of the achievement. + // Oddly, the target count is NOT contained in the achievement, but in each individual criteria + if (entry.Flags.HasAnyFlag(AchievementFlags.Summ)) + { + ulong progress = 0; + CriteriaManager.WalkCriteriaTree(tree, criteriaTree => + { + if (criteriaTree.Criteria != null) + { + CriteriaProgress criteriaProgress = this.GetCriteriaProgress(criteriaTree.Criteria); + if (criteriaProgress != null) + progress += criteriaProgress.Counter; + } + }); + return progress >= tree.Entry.Amount; + } + + return IsCompletedCriteriaTree(tree); + } + + public override bool RequiredAchievementSatisfied(uint achievementId) + { + return HasAchieved(achievementId); + } + + public virtual void CompletedAchievement(AchievementRecord entry, Player referencePlayer) { } + + public Func, AchievementRecord> VisibleAchievementCheck = new Func, AchievementRecord>(value => + { + AchievementRecord achievement = CliDB.AchievementStorage.LookupByKey(value.Key); + if (achievement != null && !achievement.Flags.HasAnyFlag(AchievementFlags.Hidden)) + return achievement; + return null; + }); + + protected Dictionary _completedAchievements = new Dictionary(); + protected uint _achievementPoints; + } + + public class PlayerAchievementMgr : AchievementManager + { + public PlayerAchievementMgr(Player owner) + { + _owner = owner; + } + + public override void Reset() + { + base.Reset(); + + foreach (var iter in _completedAchievements) + { + AchievementDeleted achievementDeleted = new AchievementDeleted(); + achievementDeleted.AchievementID = iter.Key; + SendPacket(achievementDeleted); + } + + _completedAchievements.Clear(); + _achievementPoints = 0; + DeleteFromDB(_owner.GetGUID()); + + // re-fill data + CheckAllAchievementCriteria(_owner); + } + + public static void DeleteFromDB(ObjectGuid guid) + { + SQLTransaction trans = new SQLTransaction(); + + PreparedStatement stmt = DB.Characters.GetPreparedStatement(CharStatements.DEL_CHAR_ACHIEVEMENT); + stmt.AddValue(0, guid.GetCounter()); + DB.Characters.Execute(stmt); + + stmt = DB.Characters.GetPreparedStatement(CharStatements.DEL_CHAR_ACHIEVEMENT_PROGRESS); + stmt.AddValue(0, guid.GetCounter()); + DB.Characters.Execute(stmt); + + DB.Characters.CommitTransaction(trans); + } + + public void LoadFromDB(SQLResult achievementResult, SQLResult criteriaResult) + { + if (!achievementResult.IsEmpty()) + { + do + { + uint achievementid = achievementResult.Read(0); + + // must not happen: cleanup at server startup in sAchievementMgr.LoadCompletedAchievements() + AchievementRecord achievement = CliDB.AchievementStorage.LookupByKey(achievementid); + if (achievement == null) + continue; + + CompletedAchievementData ca = new CompletedAchievementData(); + ca.Date = achievementResult.Read(1); + ca.Changed = false; + + _achievementPoints += achievement.Points; + + // title achievement rewards are retroactive + var reward = Global.AchievementMgr.GetAchievementReward(achievement); + if (reward != null) + { + uint titleId = reward.TitleId[Player.TeamForRace(_owner.GetRace()) == Team.Alliance ? 0 : 1]; + if (titleId != 0) + { + CharTitlesRecord titleEntry = CliDB.CharTitlesStorage.LookupByKey(titleId); + if (titleEntry != null) + _owner.SetTitle(titleEntry); + } + } + _completedAchievements[achievementid] = ca; + + } while (achievementResult.NextRow()); + } + + if (!criteriaResult.IsEmpty()) + { + var now = Time.UnixTime; + do + { + uint id = criteriaResult.Read(0); + ulong counter = criteriaResult.Read(1); + long date = criteriaResult.Read(2); + + Criteria criteria = Global.CriteriaMgr.GetCriteria(id); + if (criteria == null) + { + // Removing non-existing criteria data for all characters + Log.outError(LogFilter.Achievement, "Non-existing achievement criteria {0} data removed from table `character_achievement_progress`.", id); + + PreparedStatement stmt = DB.Characters.GetPreparedStatement(CharStatements.DEL_INVALID_ACHIEV_PROGRESS_CRITERIA); + stmt.AddValue(0, id); + DB.Characters.Execute(stmt); + continue; + } + + if (criteria.Entry.StartTimer != 0 && (date + criteria.Entry.StartTimer) < now) + continue; + + CriteriaProgress progress = new CriteriaProgress(); + progress.Counter = counter; + progress.Date = date; + progress.Changed = false; + + _criteriaProgress[id] = progress; + + } while (criteriaResult.NextRow()); + } + } + + public void SaveToDB(SQLTransaction trans) + { + if (!_completedAchievements.Empty()) + { + foreach (var pair in _completedAchievements) + { + if (!pair.Value.Changed) + continue; + + PreparedStatement stmt = DB.Characters.GetPreparedStatement(CharStatements.DEL_CHAR_ACHIEVEMENT_BY_ACHIEVEMENT); + stmt.AddValue(0, pair.Key); + stmt.AddValue(1, _owner.GetGUID().GetCounter()); + trans.Append(stmt); + + stmt = DB.Characters.GetPreparedStatement(CharStatements.INS_CHAR_ACHIEVEMENT); + stmt.AddValue(0, _owner.GetGUID().GetCounter()); + stmt.AddValue(1, pair.Key); + stmt.AddValue(2, pair.Value.Date); + trans.Append(stmt); + + pair.Value.Changed = false; + } + } + + if (!_criteriaProgress.Empty()) + { + foreach (var pair in _criteriaProgress) + { + if (!pair.Value.Changed) + continue; + + PreparedStatement stmt = DB.Characters.GetPreparedStatement(CharStatements.DEL_CHAR_ACHIEVEMENT_PROGRESS_BY_CRITERIA); + stmt.AddValue(0, _owner.GetGUID().GetCounter()); + stmt.AddValue(1, pair.Key); + trans.Append(stmt); + + if (pair.Value.Counter != 0) + { + stmt = DB.Characters.GetPreparedStatement(CharStatements.INS_CHAR_ACHIEVEMENT_PROGRESS); + stmt.AddValue(0, _owner.GetGUID().GetCounter()); + stmt.AddValue(1, pair.Key); + stmt.AddValue(2, pair.Value.Counter); + stmt.AddValue(3, pair.Value.Date); + trans.Append(stmt); + } + + pair.Value.Changed = false; + } + } + } + + public void ResetCriteria(CriteriaTypes type, ulong miscValue1, ulong miscValue2, bool evenIfCriteriaComplete) + { + Log.outDebug(LogFilter.Achievement, "ResetAchievementCriteria({0}, {1}, {2})", type, miscValue1, miscValue2); + + // disable for gamemasters with GM-mode enabled + if (_owner.IsGameMaster()) + return; + + var achievementCriteriaList = GetCriteriaByType(type); + foreach (Criteria achievementCriteria in achievementCriteriaList) + { + if (achievementCriteria.Entry.FailEvent != miscValue1 || (achievementCriteria.Entry.FailAsset != 0 && achievementCriteria.Entry.FailAsset != miscValue2)) + continue; + + var trees = Global.CriteriaMgr.GetCriteriaTreesByCriteria(achievementCriteria.ID); + bool allComplete = true; + foreach (CriteriaTree tree in trees) + { + // don't update already completed criteria if not forced or achievement already complete + if (!(IsCompletedCriteriaTree(tree) && !evenIfCriteriaComplete) || !HasAchieved(tree.Achievement.Id)) + { + allComplete = false; + break; + } + } + + if (allComplete) + continue; + + RemoveCriteriaProgress(achievementCriteria); + } + } + + public override void SendAllData(Player receiver) + { + AllAchievementData achievementData = new AllAchievementData(); + + foreach (var pair in _completedAchievements) + { + AchievementRecord achievement = VisibleAchievementCheck(pair); + if (achievement == null) + continue; + + EarnedAchievement earned = new EarnedAchievement(); + earned.Id = pair.Key; + earned.Date = pair.Value.Date; + if (!achievement.Flags.HasAnyFlag(AchievementFlags.Account)) + { + earned.Owner = _owner.GetGUID(); + earned.VirtualRealmAddress = earned.NativeRealmAddress = Global.WorldMgr.GetVirtualRealmAddress(); + } + achievementData.Data.Earned.Add(earned); + } + + foreach (var pair in _criteriaProgress) + { + CriteriaProgressPkt progress = new CriteriaProgressPkt(); + progress.Id = pair.Key; + progress.Quantity = pair.Value.Counter; + progress.Player = pair.Value.PlayerGUID; + progress.Flags = 0; + progress.Date = pair.Value.Date; + progress.TimeFromStart = 0; + progress.TimeFromCreate = 0; + achievementData.Data.Progress.Add(progress); + } + + SendPacket(achievementData); + } + + public void SendAchievementInfo(Player receiver, uint achievementId = 0) + { + RespondInspectAchievements inspectedAchievements = new RespondInspectAchievements(); + inspectedAchievements.Player = _owner.GetGUID(); + + foreach (var pair in _completedAchievements) + { + AchievementRecord achievement = VisibleAchievementCheck(pair); + if (achievement == null) + continue; + + EarnedAchievement earned = new EarnedAchievement(); + earned.Id = pair.Key; + earned.Date = pair.Value.Date; + if (!achievement.Flags.HasAnyFlag(AchievementFlags.Account)) + { + earned.Owner = _owner.GetGUID(); + earned.VirtualRealmAddress = earned.NativeRealmAddress = Global.WorldMgr.GetVirtualRealmAddress(); + } + inspectedAchievements.Data.Earned.Add(earned); + } + + foreach (var pair in _criteriaProgress) + { + CriteriaProgressPkt progress = new CriteriaProgressPkt(); + progress.Id = pair.Key; + progress.Quantity = pair.Value.Counter; + progress.Player = pair.Value.PlayerGUID; + progress.Flags = 0; + progress.Date = pair.Value.Date; + progress.TimeFromStart = 0; + progress.TimeFromCreate = 0; + inspectedAchievements.Data.Progress.Add(progress); + + } + receiver.SendPacket(inspectedAchievements); + } + + public override void CompletedAchievement(AchievementRecord achievement, Player referencePlayer) + { + // disable for gamemasters with GM-mode enabled + if (_owner.IsGameMaster()) + return; + + if ((achievement.Faction == AchievementFaction.Horde && referencePlayer.GetTeam() != Team.Horde) || + (achievement.Faction == AchievementFaction.Alliance && referencePlayer.GetTeam() != Team.Alliance)) + return; + + if (achievement.Flags.HasAnyFlag(AchievementFlags.Counter) || HasAchieved(achievement.Id)) + return; + + if (achievement.Flags.HasAnyFlag(AchievementFlags.ShowInGuildNews)) + { + Guild guild = referencePlayer.GetGuild(); + if (guild) + guild.AddGuildNews(GuildNews.PlayerAchievement, referencePlayer.GetGUID(), (uint)(achievement.Flags & AchievementFlags.ShowInGuildHeader), achievement.Id); + } + + if (!_owner.GetSession().PlayerLoading()) + SendAchievementEarned(achievement); + + Log.outDebug(LogFilter.Achievement, "PlayerAchievementMgr.CompletedAchievement({0}). {1}", achievement.Id, GetOwnerInfo()); + + CompletedAchievementData ca = new CompletedAchievementData(); + ca.Date = Time.UnixTime; + ca.Changed = true; + _completedAchievements[achievement.Id] = ca; + + if (achievement.Flags.HasAnyFlag(AchievementFlags.RealmFirstReach | AchievementFlags.RealmFirstKill)) + Global.AchievementMgr.SetRealmCompleted(achievement); + + if (!achievement.Flags.HasAnyFlag(AchievementFlags.TrackingFlag)) + _achievementPoints += achievement.Points; + + UpdateCriteria(CriteriaTypes.CompleteAchievement, 0, 0, 0, null, referencePlayer); + UpdateCriteria(CriteriaTypes.EarnAchievementPoints, achievement.Points, 0, 0, null, referencePlayer); + + // reward items and titles if any + AchievementReward reward = Global.AchievementMgr.GetAchievementReward(achievement); + + // no rewards + if (reward == null) + return; + + // titles + //! Currently there's only one achievement that deals with gender-specific titles. + //! Since no common attributes were found, (not even in titleRewardFlags field) + //! we explicitly check by ID. Maybe in the future we could move the achievement_reward + //! condition fields to the condition system. + uint titleId = reward.TitleId[achievement.Id == 1793 ? _owner.GetByteValue(PlayerFields.Bytes3, PlayerFieldOffsets.Bytes3OffsetGender) : (_owner.GetTeam() == Team.Alliance ? 0 : 1)]; + if (titleId != 0) + { + CharTitlesRecord titleEntry = CliDB.CharTitlesStorage.LookupByKey(titleId); + if (titleEntry != null) + _owner.SetTitle(titleEntry); + } + + // mail + if (reward.SenderCreatureId != 0) + { + MailDraft draft = new MailDraft(reward.MailTemplateId); + + if (reward.MailTemplateId == 0) + { + // subject and text + string subject = reward.Subject; + string text = reward.Body; + + LocaleConstant localeConstant = _owner.GetSession().GetSessionDbLocaleIndex(); + if (localeConstant != LocaleConstant.enUS) + { + AchievementRewardLocale loc = Global.AchievementMgr.GetAchievementRewardLocale(achievement); + if (loc != null) + { + ObjectManager.GetLocaleString(loc.Subject, localeConstant, ref subject); + ObjectManager.GetLocaleString(loc.Body, localeConstant, ref text); + } + } + + draft = new MailDraft(subject, text); + } + + SQLTransaction trans = new SQLTransaction(); + + Item item = reward.ItemId != 0 ? Item.CreateItem(reward.ItemId, 1, _owner) : null; + if (item) + { + // save new item before send + item.SaveToDB(trans); // save for prevent lost at next mail load, if send fail then item will deleted + + // item + draft.AddItem(item); + } + + draft.SendMailTo(trans, _owner, new MailSender(MailMessageType.Creature, reward.SenderCreatureId)); + DB.Characters.CommitTransaction(trans); + } + } + + public bool ModifierTreeSatisfied(uint modifierTreeId) + { + return AdditionalRequirementsSatisfied(Global.CriteriaMgr.GetModifierTree(modifierTreeId), 0, 0, null, _owner); + } + + public override void SendCriteriaUpdate(Criteria criteria, CriteriaProgress progress, uint timeElapsed, bool timedCompleted) + { + CriteriaUpdate criteriaUpdate = new CriteriaUpdate(); + + criteriaUpdate.CriteriaID = criteria.ID; + criteriaUpdate.Quantity = progress.Counter; + criteriaUpdate.PlayerGUID = _owner.GetGUID(); + criteriaUpdate.Flags = 0; + if (criteria.Entry.StartTimer != 0) + criteriaUpdate.Flags = timedCompleted ? 1 : 0u; // 1 is for keeping the counter at 0 in client + + criteriaUpdate.CurrentTime = progress.Date; + criteriaUpdate.ElapsedTime = timeElapsed; + criteriaUpdate.CreationTime = 0; + + SendPacket(criteriaUpdate); + } + + public override void SendCriteriaProgressRemoved(uint criteriaId) + { + CriteriaDeleted criteriaDeleted = new CriteriaDeleted(); + criteriaDeleted.CriteriaID = criteriaId; + SendPacket(criteriaDeleted); + } + + void SendAchievementEarned(AchievementRecord achievement) + { + // Don't send for achievements with ACHIEVEMENT_FLAG_HIDDEN + if (achievement.Flags.HasAnyFlag(AchievementFlags.Hidden)) + return; + + Log.outDebug(LogFilter.Achievement, "PlayerAchievementMgr.SendAchievementEarned({0})", achievement.Id); + + if (!achievement.Flags.HasAnyFlag(AchievementFlags.TrackingFlag)) + { + Guild guild = Global.GuildMgr.GetGuildById(_owner.GetGuildId()); + if (guild) + { + BroadcastTextBuilder say_builder = new BroadcastTextBuilder(_owner, ChatMsg.GuildAchievement, (uint)BroadcastTextIds.AchivementEarned, _owner, achievement.Id); + var say_do = new LocalizedPacketDo(say_builder); + guild.BroadcastWorker(say_do, _owner); + } + + if (achievement.Flags.HasAnyFlag(AchievementFlags.RealmFirstReach | AchievementFlags.RealmFirstKill)) + { + // broadcast realm first reached + ServerFirstAchievement serverFirstAchievement = new ServerFirstAchievement(); + serverFirstAchievement.Name = _owner.GetName(); + serverFirstAchievement.PlayerGUID = _owner.GetGUID(); + serverFirstAchievement.AchievementID = achievement.Id; + Global.WorldMgr.SendGlobalMessage(serverFirstAchievement); + } + // if player is in world he can tell his friends about new achievement + else if (_owner.IsInWorld) + { + BroadcastTextBuilder _builder = new BroadcastTextBuilder(_owner, ChatMsg.Achievement, (uint)BroadcastTextIds.AchivementEarned, _owner, achievement.Id); + var _localizer = new LocalizedPacketDo(_builder); + var _worker = new PlayerDistWorker(_owner, WorldConfig.GetFloatValue(WorldCfg.ListenRangeSay), _localizer); + Cell.VisitWorldObjects(_owner, _worker, WorldConfig.GetFloatValue(WorldCfg.ListenRangeSay)); + } + } + + AchievementEarned achievementEarned = new AchievementEarned(); + achievementEarned.Sender = _owner.GetGUID(); + achievementEarned.Earner = _owner.GetGUID(); + achievementEarned.EarnerNativeRealm = achievementEarned.EarnerVirtualRealm = Global.WorldMgr.GetVirtualRealmAddress(); + achievementEarned.AchievementID = achievement.Id; + achievementEarned.Time = Time.UnixTime; + if (!achievement.Flags.HasAnyFlag(AchievementFlags.TrackingFlag)) + _owner.SendMessageToSetInRange(achievementEarned, WorldConfig.GetFloatValue(WorldCfg.ListenRangeSay), true); + else + _owner.SendPacket(achievementEarned); + } + + public override void SendPacket(ServerPacket data) + { + _owner.SendPacket(data); + } + + public override List GetCriteriaByType(CriteriaTypes type) + { + return Global.CriteriaMgr.GetPlayerCriteriaByType(type); + } + + public override string GetOwnerInfo() + { + return string.Format("{0} {1}", _owner.GetGUID().ToString(), _owner.GetName()); + } + + Player _owner; + } + + public class GuildAchievementMgr : AchievementManager + { + public GuildAchievementMgr(Guild owner) + { + _owner = owner; + } + + public override void Reset() + { + base.Reset(); + + ObjectGuid guid = _owner.GetGUID(); + foreach (var iter in _completedAchievements) + { + GuildAchievementDeleted guildAchievementDeleted = new GuildAchievementDeleted(); + guildAchievementDeleted.AchievementID = iter.Key; + guildAchievementDeleted.GuildGUID = guid; + guildAchievementDeleted.TimeDeleted = Time.UnixTime; + SendPacket(guildAchievementDeleted); + } + + _achievementPoints = 0; + _completedAchievements.Clear(); + DeleteFromDB(guid); + } + + void DeleteFromDB(ObjectGuid guid) + { + SQLTransaction trans = new SQLTransaction(); + + PreparedStatement stmt = DB.Characters.GetPreparedStatement(CharStatements.DEL_ALL_GUILD_ACHIEVEMENTS); + stmt.AddValue(0, guid.GetCounter()); + trans.Append(stmt); + + stmt = DB.Characters.GetPreparedStatement(CharStatements.DEL_ALL_GUILD_ACHIEVEMENT_CRITERIA); + stmt.AddValue(0, guid.GetCounter()); + trans.Append(stmt); + + DB.Characters.CommitTransaction(trans); + } + + public void LoadFromDB(SQLResult achievementResult, SQLResult criteriaResult) + { + if (!achievementResult.IsEmpty()) + { + do + { + uint achievementid = achievementResult.Read(0); + + // must not happen: cleanup at server startup in sAchievementMgr.LoadCompletedAchievements() + AchievementRecord achievement = CliDB.AchievementStorage.LookupByKey(achievementid); + if (achievement == null) + continue; + + CompletedAchievementData ca = _completedAchievements[achievementid]; + ca.Date = achievementResult.Read(1); + var guids = new StringArray(achievementResult.Read(2), ' '); + for (int i = 0; i < guids.Length; ++i) + ca.CompletingPlayers.Add(ObjectGuid.Create(HighGuid.Player, ulong.Parse(guids[i]))); + + ca.Changed = false; + + _achievementPoints += achievement.Points; + } while (achievementResult.NextRow()); + } + + if (!criteriaResult.IsEmpty()) + { + long now = Time.UnixTime; + do + { + uint id = criteriaResult.Read(0); + ulong counter = criteriaResult.Read(1); + long date = criteriaResult.Read(2); + ulong guidLow = criteriaResult.Read(3); + + Criteria criteria = Global.CriteriaMgr.GetCriteria(id); + if (criteria == null) + { + // we will remove not existed criteria for all guilds + Log.outError(LogFilter.Achievement, "Non-existing achievement criteria {0} data removed from table `guild_achievement_progress`.", id); + + PreparedStatement stmt = DB.Characters.GetPreparedStatement(CharStatements.DEL_INVALID_ACHIEV_PROGRESS_CRITERIA_GUILD); + stmt.AddValue(0, id); + DB.Characters.Execute(stmt); + continue; + } + + if (criteria.Entry.StartTimer != 0 && date + criteria.Entry.StartTimer < now) + continue; + + CriteriaProgress progress = new CriteriaProgress(); + progress.Counter = counter; + progress.Date = date; + progress.PlayerGUID = ObjectGuid.Create(HighGuid.Player, guidLow); + progress.Changed = false; + + _criteriaProgress[id] = progress; + + } while (criteriaResult.NextRow()); + } + } + + public void SaveToDB(SQLTransaction trans) + { + PreparedStatement stmt; + StringBuilder guidstr = new StringBuilder(); + foreach (var pair in _completedAchievements) + { + if (!pair.Value.Changed) + continue; + + stmt = DB.Characters.GetPreparedStatement(CharStatements.DEL_GUILD_ACHIEVEMENT); + stmt.AddValue(0, _owner.GetId()); + stmt.AddValue(1, pair.Key); + trans.Append(stmt); + + stmt = DB.Characters.GetPreparedStatement(CharStatements.INS_GUILD_ACHIEVEMENT); + stmt.AddValue(0, _owner.GetId()); + stmt.AddValue(1, pair.Key); + stmt.AddValue(2, (uint)pair.Value.Date); + foreach (var guid in pair.Value.CompletingPlayers) + guidstr.AppendFormat("{0},", guid.GetCounter()); + + stmt.AddValue(3, guidstr.ToString()); + trans.Append(stmt); + + guidstr.Clear(); + } + + foreach (var pair in _criteriaProgress) + { + if (!pair.Value.Changed) + continue; + + stmt = DB.Characters.GetPreparedStatement(CharStatements.DEL_GUILD_ACHIEVEMENT_CRITERIA); + stmt.AddValue(0, _owner.GetId()); + stmt.AddValue(1, pair.Key); + trans.Append(stmt); + + stmt = DB.Characters.GetPreparedStatement(CharStatements.INS_GUILD_ACHIEVEMENT_CRITERIA); + stmt.AddValue(0, _owner.GetId()); + stmt.AddValue(1, pair.Key); + stmt.AddValue(2, pair.Value.Counter); + stmt.AddValue(3, (uint)pair.Value.Date); + stmt.AddValue(4, pair.Value.PlayerGUID.GetCounter()); + trans.Append(stmt); + } + } + + public override void SendAllData(Player receiver) + { + AllGuildAchievements allGuildAchievements = new AllGuildAchievements(); + + foreach (var pair in _completedAchievements) + { + AchievementRecord achievement = VisibleAchievementCheck(pair); + if (achievement == null) + continue; + + EarnedAchievement earned = new EarnedAchievement(); + earned.Id = pair.Key; + earned.Date = pair.Value.Date; + allGuildAchievements.Earned.Add(earned); + } + + receiver.SendPacket(allGuildAchievements); + } + + public void SendAchievementInfo(Player receiver, uint achievementId = 0) + { + GuildCriteriaUpdate guildCriteriaUpdate = new GuildCriteriaUpdate(); + AchievementRecord achievement = CliDB.AchievementStorage.LookupByKey(achievementId); + if (achievement != null) + { + CriteriaTree tree = Global.CriteriaMgr.GetCriteriaTree(achievement.CriteriaTree); + if (tree != null) + { + CriteriaManager.WalkCriteriaTree(tree, node => + { + if (node.Criteria != null) + { + var progress = _criteriaProgress.LookupByKey(node.Criteria.ID); + if (progress != null) + { + GuildCriteriaProgress guildCriteriaProgress = new GuildCriteriaProgress(); + guildCriteriaProgress.CriteriaID = node.Criteria.ID; + guildCriteriaProgress.DateCreated = 0; + guildCriteriaProgress.DateStarted = 0; + guildCriteriaProgress.DateUpdated = progress.Date; + guildCriteriaProgress.Quantity = progress.Counter; + guildCriteriaProgress.PlayerGUID = progress.PlayerGUID; + guildCriteriaProgress.Flags = 0; + + guildCriteriaUpdate.Progress.Add(guildCriteriaProgress); + } + } + }); + } + } + + receiver.SendPacket(guildCriteriaUpdate); + } + + public void SendAllTrackedCriterias(Player receiver, List trackedCriterias) + { + GuildCriteriaUpdate guildCriteriaUpdate = new GuildCriteriaUpdate(); + + foreach (uint criteriaId in trackedCriterias) + { + var progress = _criteriaProgress.LookupByKey(criteriaId); + if (progress == null) + continue; + + GuildCriteriaProgress guildCriteriaProgress = new GuildCriteriaProgress(); + guildCriteriaProgress.CriteriaID = criteriaId; + guildCriteriaProgress.DateCreated = 0; + guildCriteriaProgress.DateStarted = 0; + guildCriteriaProgress.DateUpdated = progress.Date; + guildCriteriaProgress.Quantity = progress.Counter; + guildCriteriaProgress.PlayerGUID = progress.PlayerGUID; + guildCriteriaProgress.Flags = 0; + + guildCriteriaUpdate.Progress.Add(guildCriteriaProgress); + } + + receiver.SendPacket(guildCriteriaUpdate); + } + + public void SendAchievementMembers(Player receiver, uint achievementId) + { + var achievementData = _completedAchievements.LookupByKey(achievementId); + if (achievementData != null) + { + GuildAchievementMembers guildAchievementMembers = new GuildAchievementMembers(); + guildAchievementMembers.GuildGUID = _owner.GetGUID(); + guildAchievementMembers.AchievementID = achievementId; + + foreach (ObjectGuid guid in achievementData.CompletingPlayers) + guildAchievementMembers.Member.Add(guid); + + receiver.SendPacket(guildAchievementMembers); + } + } + + public override void CompletedAchievement(AchievementRecord achievement, Player referencePlayer) + { + Log.outDebug(LogFilter.Achievement, "CompletedAchievement({0})", achievement.Id); + + if (achievement.Flags.HasAnyFlag(AchievementFlags.Counter) || HasAchieved(achievement.Id)) + return; + + if (achievement.Flags.HasAnyFlag(AchievementFlags.ShowInGuildNews)) + { + Guild guild = referencePlayer.GetGuild(); + if (guild) + guild.AddGuildNews(GuildNews.Achievement, ObjectGuid.Empty, (uint)(achievement.Flags & AchievementFlags.ShowInGuildHeader), achievement.Id); + } + + SendAchievementEarned(achievement); + CompletedAchievementData ca = new CompletedAchievementData(); + ca.Date = Time.UnixTime; + ca.Changed = true; + + if (achievement.Flags.HasAnyFlag(AchievementFlags.ShowGuildMembers)) + { + if (referencePlayer.GetGuildId() == _owner.GetId()) + ca.CompletingPlayers.Add(referencePlayer.GetGUID()); + + Group group = referencePlayer.GetGroup(); + if (group) + { + for (GroupReference refe = group.GetFirstMember(); refe != null; refe = refe.next()) + { + Player groupMember = refe.GetSource(); + if (groupMember) + if (groupMember.GetGuildId() == _owner.GetId()) + ca.CompletingPlayers.Add(groupMember.GetGUID()); + } + } + } + + _completedAchievements[achievement.Id] = ca; + + if (achievement.Flags.HasAnyFlag(AchievementFlags.RealmFirstReach | AchievementFlags.RealmFirstKill)) + Global.AchievementMgr.SetRealmCompleted(achievement); + + if (!achievement.Flags.HasAnyFlag(AchievementFlags.TrackingFlag)) + _achievementPoints += achievement.Points; + + UpdateCriteria(CriteriaTypes.CompleteAchievement, 0, 0, 0, null, referencePlayer); + UpdateCriteria(CriteriaTypes.EarnAchievementPoints, achievement.Points, 0, 0, null, referencePlayer); + } + + public override void SendCriteriaUpdate(Criteria entry, CriteriaProgress progress, uint timeElapsed, bool timedCompleted) + { + GuildCriteriaUpdate guildCriteriaUpdate = new GuildCriteriaUpdate(); + + GuildCriteriaProgress guildCriteriaProgress = new GuildCriteriaProgress(); + guildCriteriaProgress.CriteriaID = entry.ID; + guildCriteriaProgress.DateCreated = 0; + guildCriteriaProgress.DateStarted = 0; + guildCriteriaProgress.DateUpdated = progress.Date; + guildCriteriaProgress.Quantity = progress.Counter; + guildCriteriaProgress.PlayerGUID = progress.PlayerGUID; + guildCriteriaProgress.Flags = 0; + + guildCriteriaUpdate.Progress[0] = guildCriteriaProgress; + + _owner.BroadcastPacketIfTrackingAchievement(guildCriteriaUpdate, entry.ID); + } + + public override void SendCriteriaProgressRemoved(uint criteriaId) + { + GuildCriteriaDeleted guildCriteriaDeleted = new GuildCriteriaDeleted(); + guildCriteriaDeleted.GuildGUID = _owner.GetGUID(); + guildCriteriaDeleted.CriteriaID = criteriaId; + SendPacket(guildCriteriaDeleted); + } + + void SendAchievementEarned(AchievementRecord achievement) + { + if (achievement.Flags.HasAnyFlag(AchievementFlags.RealmFirstReach | AchievementFlags.RealmFirstKill)) + { + // broadcast realm first reached + ServerFirstAchievement serverFirstAchievement = new ServerFirstAchievement(); + serverFirstAchievement.Name = _owner.GetName(); + serverFirstAchievement.PlayerGUID = _owner.GetGUID(); + serverFirstAchievement.AchievementID = achievement.Id; + serverFirstAchievement.GuildAchievement = true; + Global.WorldMgr.SendGlobalMessage(serverFirstAchievement); + } + + GuildAchievementEarned guildAchievementEarned = new GuildAchievementEarned(); + guildAchievementEarned.AchievementID = achievement.Id; + guildAchievementEarned.GuildGUID = _owner.GetGUID(); + guildAchievementEarned.TimeEarned = Time.UnixTime; + SendPacket(guildAchievementEarned); + } + + public override void SendPacket(ServerPacket data) + { + _owner.BroadcastPacket(data); + } + + public override List GetCriteriaByType(CriteriaTypes type) + { + return Global.CriteriaMgr.GetGuildCriteriaByType(type); + } + + public override string GetOwnerInfo() + { + return string.Format("Guild ID {0} {1}", _owner.GetId(), _owner.GetName()); + } + + Guild _owner; + } + + public class AchievementGlobalMgr : Singleton + { + AchievementGlobalMgr() { } + + public List GetAchievementByReferencedId(uint id) + { + return _achievementListByReferencedId.LookupByKey(id); + } + + public AchievementReward GetAchievementReward(AchievementRecord achievement) + { + return _achievementRewards.LookupByKey(achievement.Id); + } + + public AchievementRewardLocale GetAchievementRewardLocale(AchievementRecord achievement) + { + return _achievementRewardLocales.LookupByKey(achievement.Id); + } + + public bool IsRealmCompleted(AchievementRecord achievement) + { + var time = _allCompletedAchievements.LookupByKey(achievement.Id); + if (time == null) + return false; + + if (time == DateTime.MinValue) + return false; + + // Allow completing the realm first kill for entire minute after first person did it + // it may allow more than one group to achieve it (highly unlikely) + // but apparently this is how blizz handles it as well + if (achievement.Flags.HasAnyFlag(AchievementFlags.RealmFirstKill)) + return (DateTime.Now - time) > TimeSpan.FromMinutes(1); + + return true; + } + + public void SetRealmCompleted(AchievementRecord achievement) + { + if (IsRealmCompleted(achievement)) + return; + + _allCompletedAchievements[achievement.Id] = DateTime.Now; + } + + //========================================================== + public void LoadAchievementReferenceList() + { + uint oldMSTime = Time.GetMSTime(); + + if (CliDB.AchievementStorage.Empty()) + { + Log.outInfo(LogFilter.ServerLoading, "Loaded 0 achievement references."); + return; + } + + uint count = 0; + foreach (var achievement in CliDB.AchievementStorage.Values) + { + if (achievement.SharesCriteria == 0) + continue; + + _achievementListByReferencedId.Add(achievement.SharesCriteria, achievement); + ++count; + } + + // Once Bitten, Twice Shy (10 player) - Icecrown Citadel + AchievementRecord achievement1 = CliDB.AchievementStorage.LookupByKey(4539); + if (achievement1 != null) + achievement1.MapID = 631; // Correct map requirement (currently has Ulduar); 6.0.3 note - it STILL has ulduar requirement + + Log.outInfo(LogFilter.ServerLoading, "Loaded {0} achievement references in {1} ms.", count, Time.GetMSTimeDiffToNow(oldMSTime)); + } + + public void LoadCompletedAchievements() + { + uint oldMSTime = Time.GetMSTime(); + + // Populate _allCompletedAchievements with all realm first achievement ids to make multithreaded access safer + // while it will not prevent races, it will prevent crashes that happen because std::unordered_map key was added + // instead the only potential race will happen on value associated with the key + foreach (AchievementRecord achievement in CliDB.AchievementStorage.Values) + if (achievement.Flags.HasAnyFlag(AchievementFlags.RealmFirstReach | AchievementFlags.RealmFirstKill)) + _allCompletedAchievements[achievement.Id] = DateTime.MinValue; + + SQLResult result = DB.Characters.Query("SELECT achievement FROM character_achievement GROUP BY achievement"); + if (result.IsEmpty()) + { + Log.outInfo(LogFilter.ServerLoading, "Loaded 0 realm first completed achievements. DB table `character_achievement` is empty."); + return; + } + + do + { + uint achievementId = result.Read(0); + AchievementRecord achievement = CliDB.AchievementStorage.LookupByKey(achievementId); + if (achievement == null) + { + // Remove non-existing achievements from all characters + Log.outError(LogFilter.Achievement, "Non-existing achievement {0} data has been removed from the table `character_achievement`.", achievementId); + + PreparedStatement stmt = DB.Characters.GetPreparedStatement(CharStatements.DEL_INVALID_ACHIEVMENT); + stmt.AddValue(0, achievementId); + DB.Characters.Execute(stmt); + + continue; + } + else if (achievement.Flags.HasAnyFlag(AchievementFlags.RealmFirstReach | AchievementFlags.RealmFirstKill)) + _allCompletedAchievements[achievementId] = DateTime.MaxValue; + } + while (result.NextRow()); + + Log.outInfo(LogFilter.ServerLoading, "Loaded {0} realm first completed achievements in {1} ms.", _allCompletedAchievements.Count, Time.GetMSTimeDiffToNow(oldMSTime)); + } + + public void LoadRewards() + { + uint oldMSTime = Time.GetMSTime(); + + _achievementRewards.Clear(); // need for reload case + + // 0 1 2 3 4 5 6 7 + SQLResult result = DB.World.Query("SELECT entry, title_A, title_H, item, sender, subject, text, mailTemplate FROM achievement_reward"); + if (result.IsEmpty()) + { + Log.outError(LogFilter.ServerLoading, ">> Loaded 0 achievement rewards. DB table `achievement_reward` is empty."); + return; + } + + uint count = 0; + do + { + uint entry = result.Read(0); + AchievementRecord achievement = CliDB.AchievementStorage.LookupByKey(entry); + if (achievement == null) + { + Log.outError(LogFilter.Sql, "Table `achievement_reward` contains a wrong achievement entry (Entry: {0}), ignored.", entry); + continue; + } + + AchievementReward reward = new AchievementReward(); + reward.TitleId[0] = result.Read(1); + reward.TitleId[1] = result.Read(2); + reward.ItemId = result.Read(3); + reward.SenderCreatureId = result.Read(4); + reward.Subject = result.Read(5); + reward.Body = result.Read(6); + reward.MailTemplateId = result.Read(7); + + // must be title or mail at least + if (reward.TitleId[0] == 0 && reward.TitleId[1] == 0 && reward.SenderCreatureId == 0) + { + Log.outError(LogFilter.Sql, "Table `achievement_reward` (Entry: {0}) does not contain title or item reward data. Ignored.", entry); + continue; + } + + if (achievement.Faction == AchievementFaction.Any && (reward.TitleId[0] == 0 ^ reward.TitleId[1] == 0)) + Log.outError(LogFilter.Sql, "Table `achievement_reward` (Entry: {0}) contains the title (A: {1} H: {2}) for only one team.", entry, reward.TitleId[0], reward.TitleId[1]); + + if (reward.TitleId[0] != 0) + { + CharTitlesRecord titleEntry = CliDB.CharTitlesStorage.LookupByKey(reward.TitleId[0]); + if (titleEntry == null) + { + Log.outError(LogFilter.Sql, "Table `achievement_reward` (Entry: {0}) contains an invalid title id ({1}) in `title_A`, set to 0", entry, reward.TitleId[0]); + reward.TitleId[0] = 0; + } + } + + if (reward.TitleId[1] != 0) + { + CharTitlesRecord titleEntry = CliDB.CharTitlesStorage.LookupByKey(reward.TitleId[1]); + if (titleEntry == null) + { + Log.outError(LogFilter.Sql, "Table `achievement_reward` (Entry: {0}) contains an invalid title id ({1}) in `title_H`, set to 0", entry, reward.TitleId[1]); + reward.TitleId[1] = 0; + } + } + + //check mail data before item for report including wrong item case + if (reward.SenderCreatureId != 0) + { + if (Global.ObjectMgr.GetCreatureTemplate(reward.SenderCreatureId) == null) + { + Log.outError(LogFilter.Sql, "Table `achievement_reward` (Entry: {0}) contains an invalid creature entry {1} as sender, mail reward skipped.", entry, reward.SenderCreatureId); + reward.SenderCreatureId = 0; + } + } + else + { + if (reward.ItemId != 0) + Log.outError(LogFilter.Sql, "Table `achievement_reward` (Entry: {0}) does not have sender data, but contains an item reward. Item will not be rewarded.", entry); + + if (!reward.Subject.IsEmpty()) + Log.outError(LogFilter.Sql, "Table `achievement_reward` (Entry: {0}) does not have sender data, but contains a mail subject.", entry); + + if (!reward.Body.IsEmpty()) + Log.outError(LogFilter.Sql, "Table `achievement_reward` (Entry: {0}) does not have sender data, but contains mail text.", entry); + + if (reward.MailTemplateId != 0) + Log.outError(LogFilter.Sql, "Table `achievement_reward` (Entry: {0}) does not have sender data, but has a MailTemplateId.", entry); + } + + if (reward.MailTemplateId != 0) + { + if (!CliDB.MailTemplateStorage.ContainsKey(reward.MailTemplateId)) + { + Log.outError(LogFilter.Sql, "Table `achievement_reward` (Entry: {0}) is using an invalid MailTemplateId ({1}).", entry, reward.MailTemplateId); + reward.MailTemplateId = 0; + } + else if (!reward.Subject.IsEmpty() || !reward.Body.IsEmpty()) + Log.outError(LogFilter.Sql, "Table `achievement_reward` (Entry: {0}) is using MailTemplateId ({1}) and mail subject/text.", entry, reward.MailTemplateId); + } + + if (reward.ItemId != 0) + { + if (Global.ObjectMgr.GetItemTemplate(reward.ItemId) == null) + { + Log.outError(LogFilter.Sql, "Table `achievement_reward` (Entry: {0}) contains an invalid item id {1}, reward mail will not contain the rewarded item.", entry, reward.ItemId); + reward.ItemId = 0; + } + } + + _achievementRewards[entry] = reward; + ++count; + } + while (result.NextRow()); + + Log.outInfo(LogFilter.ServerLoading, "Loaded {0} achievement rewards in {1} ms.", count, Time.GetMSTimeDiffToNow(oldMSTime)); + } + + public void LoadRewardLocales() + { + uint oldMSTime = Time.GetMSTime(); + + _achievementRewardLocales.Clear(); // need for reload case + + SQLResult result = DB.World.Query("SELECT entry, subject_loc1, text_loc1, subject_loc2, text_loc2, subject_loc3, text_loc3, subject_loc4, text_loc4, " + + "subject_loc5, text_loc5, subject_loc6, text_loc6, subject_loc7, text_loc7, subject_loc8, text_loc8 FROM locales_achievement_reward"); + if (result.IsEmpty()) + { + Log.outInfo(LogFilter.ServerLoading, "Loaded 0 achievement reward locale strings. DB table `locales_achievement_reward` is empty."); + return; + } + + do + { + uint entry = result.Read(0); + + if (!_achievementRewards.ContainsKey(entry)) + { + Log.outError(LogFilter.Sql, "Table `locales_achievement_reward` (Entry: {0}) contains locale strings for a non-existing achievement reward.", entry); + continue; + } + + AchievementRewardLocale data = new AchievementRewardLocale(); + + for (int i = (int)LocaleConstant.OldTotal - 1; i > 0; --i) + { + LocaleConstant locale = (LocaleConstant)i; + ObjectManager.AddLocaleString(result.Read(1 + 2 * (i - 1)), locale, data.Subject); + ObjectManager.AddLocaleString(result.Read(1 + 2 * (i - 1) + 1), locale, data.Body); + } + + _achievementRewardLocales[entry] = data; + } + while (result.NextRow()); + + Log.outInfo(LogFilter.ServerLoading, "Loaded {0} achievement reward locale strings in {1} ms.", _achievementRewardLocales.Count, Time.GetMSTimeDiffToNow(oldMSTime)); + } + + // store achievements by referenced achievement id to speed up lookup + MultiMap _achievementListByReferencedId = new MultiMap(); + + // store realm first achievements + Dictionary _allCompletedAchievements = new Dictionary(); + + Dictionary _achievementRewards = new Dictionary(); + Dictionary _achievementRewardLocales = new Dictionary(); + } + + public class AchievementReward + { + public uint[] TitleId = new uint[2]; + public uint ItemId; + public uint SenderCreatureId; + public string Subject; + public string Body; + public uint MailTemplateId; + } + + public class AchievementRewardLocale + { + public StringArray Subject = new StringArray((int)LocaleConstant.Total); + public StringArray Body = new StringArray((int)LocaleConstant.Total); + } + + public class CompletedAchievementData + { + public long Date; + public List CompletingPlayers = new List(); + public bool Changed; + } +} \ No newline at end of file diff --git a/Game/Achievements/CriteriaHandler.cs b/Game/Achievements/CriteriaHandler.cs new file mode 100644 index 000000000..de5dbbfec --- /dev/null +++ b/Game/Achievements/CriteriaHandler.cs @@ -0,0 +1,2299 @@ +/* + * Copyright (C) 2012-2017 CypherCore + * + * 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, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +using Framework.Constants; +using Framework.Database; +using Game.Arenas; +using Game.BattleGrounds; +using Game.DataStorage; +using Game.Entities; +using Game.Garrisons; +using Game.Maps; +using Game.Network; +using Game.Spells; +using System; +using System.Collections.Generic; +using System.Diagnostics.Contracts; +using System.Linq; +using System.Runtime.InteropServices; + +namespace Game.Achievements +{ + public class CriteriaHandler + { + public virtual void Reset() + { + foreach (var iter in _criteriaProgress) + SendCriteriaProgressRemoved(iter.Key); + + _criteriaProgress.Clear(); + } + + /// + /// this function will be called whenever the user might have done a criteria relevant action + /// + /// + /// + /// + /// + /// + /// + public void UpdateCriteria(CriteriaTypes type, ulong miscValue1 = 0, ulong miscValue2 = 0, ulong miscValue3 = 0, Unit unit = null, Player referencePlayer = null) + { + if (type >= CriteriaTypes.TotalTypes) + { + Log.outDebug(LogFilter.Achievement, "UpdateCriteria: Wrong criteria type {0}", type); + return; + } + + if (!referencePlayer) + { + Log.outDebug(LogFilter.Achievement, "UpdateCriteria: Player is NULL! Cant update criteria"); + return; + } + + // disable for gamemasters with GM-mode enabled + if (referencePlayer.IsGameMaster()) + { + Log.outDebug(LogFilter.Achievement, "UpdateCriteria: [Player {0} GM mode on] {1}, {2} ({3}), {4}, {5}, {6}", referencePlayer.GetName(), GetOwnerInfo(), type, type, miscValue1, miscValue2, miscValue3); + return; + } + + Log.outDebug(LogFilter.Achievement, "UpdateCriteria({0}, {1}, {2}, {3}) {4}", type, type, miscValue1, miscValue2, miscValue3, GetOwnerInfo()); + + List criteriaList = GetCriteriaByType(type); + foreach (Criteria criteria in criteriaList) + { + List trees = Global.CriteriaMgr.GetCriteriaTreesByCriteria(criteria.ID); + if (!CanUpdateCriteria(criteria, trees, miscValue1, miscValue2, miscValue3, unit, referencePlayer)) + continue; + + // requirements not found in the dbc + CriteriaDataSet data = Global.CriteriaMgr.GetCriteriaDataSet(criteria); + if (data != null) + if (!data.Meets(referencePlayer, unit, (uint)miscValue1)) + continue; + + switch (type) + { + // std. case: increment at 1 + case CriteriaTypes.NumberOfTalentResets: + case CriteriaTypes.LoseDuel: + case CriteriaTypes.CreateAuction: + case CriteriaTypes.WonAuctions: //FIXME: for online player only currently + case CriteriaTypes.RollNeed: + case CriteriaTypes.RollGreed: + case CriteriaTypes.QuestAbandoned: + case CriteriaTypes.FlightPathsTaken: + case CriteriaTypes.AcceptedSummonings: + case CriteriaTypes.LootEpicItem: + case CriteriaTypes.ReceiveEpicItem: + case CriteriaTypes.Death: + case CriteriaTypes.CompleteDailyQuest: + case CriteriaTypes.DeathAtMap: + case CriteriaTypes.DeathInDungeon: + case CriteriaTypes.KilledByCreature: + case CriteriaTypes.KilledByPlayer: + case CriteriaTypes.DeathsFrom: + case CriteriaTypes.BeSpellTarget: + case CriteriaTypes.BeSpellTarget2: + case CriteriaTypes.CastSpell: + case CriteriaTypes.CastSpell2: + case CriteriaTypes.WinRatedArena: + case CriteriaTypes.UseItem: + case CriteriaTypes.RollNeedOnLoot: + case CriteriaTypes.RollGreedOnLoot: + case CriteriaTypes.DoEmote: + case CriteriaTypes.UseGameobject: + case CriteriaTypes.FishInGameobject: + case CriteriaTypes.WinDuel: + case CriteriaTypes.HkClass: + case CriteriaTypes.HkRace: + case CriteriaTypes.BgObjectiveCapture: + case CriteriaTypes.HonorableKill: + case CriteriaTypes.SpecialPvpKill: + case CriteriaTypes.GetKillingBlows: + case CriteriaTypes.HonorableKillAtArea: + case CriteriaTypes.WinArena: // This also behaves like ACHIEVEMENT_CRITERIA_TYPE_WIN_RATED_ARENA + case CriteriaTypes.OnLogin: + case CriteriaTypes.PlaceGarrisonBuilding: + case CriteriaTypes.OwnBattlePetCount: + case CriteriaTypes.HonorLevelReached: + case CriteriaTypes.PrestigeReached: + SetCriteriaProgress(criteria, 1, referencePlayer, ProgressType.Accumulate); + break; + // std case: increment at miscValue1 + case CriteriaTypes.MoneyFromVendors: + case CriteriaTypes.GoldSpentForTalents: + case CriteriaTypes.MoneyFromQuestReward: + case CriteriaTypes.GoldSpentForTravelling: + case CriteriaTypes.GoldSpentAtBarber: + case CriteriaTypes.GoldSpentForMail: + case CriteriaTypes.LootMoney: + case CriteriaTypes.GoldEarnedByAuctions: //FIXME: for online player only currently + case CriteriaTypes.TotalDamageReceived: + case CriteriaTypes.TotalHealingReceived: + case CriteriaTypes.UseLfdToGroupWithPlayers: + case CriteriaTypes.WinBg: + case CriteriaTypes.CompleteBattleground: + case CriteriaTypes.DamageDone: + case CriteriaTypes.HealingDone: + SetCriteriaProgress(criteria, miscValue1, referencePlayer, ProgressType.Accumulate); + break; + case CriteriaTypes.KillCreature: + case CriteriaTypes.KillCreatureType: + case CriteriaTypes.LootType: + case CriteriaTypes.OwnItem: + case CriteriaTypes.LootItem: + case CriteriaTypes.Currency: + SetCriteriaProgress(criteria, miscValue2, referencePlayer, ProgressType.Accumulate); + break; + // std case: high value at miscValue1 + case CriteriaTypes.HighestAuctionBid: + case CriteriaTypes.HighestAuctionSold: //FIXME: for online player only currently + case CriteriaTypes.HighestHitDealt: + case CriteriaTypes.HighestHitReceived: + case CriteriaTypes.HighestHealCasted: + case CriteriaTypes.HighestHealingReceived: + SetCriteriaProgress(criteria, miscValue1, referencePlayer, ProgressType.Highest); + break; + case CriteriaTypes.ReachLevel: + SetCriteriaProgress(criteria, referencePlayer.getLevel(), referencePlayer); + break; + case CriteriaTypes.ReachSkillLevel: + uint skillvalue = referencePlayer.GetBaseSkillValue((SkillType)criteria.Entry.Asset); + if (skillvalue != 0) + SetCriteriaProgress(criteria, skillvalue, referencePlayer); + break; + case CriteriaTypes.LearnSkillLevel: + uint maxSkillvalue = referencePlayer.GetPureMaxSkillValue((SkillType)criteria.Entry.Asset); + if (maxSkillvalue != 0) + SetCriteriaProgress(criteria, maxSkillvalue, referencePlayer); + break; + case CriteriaTypes.CompleteQuestCount: + SetCriteriaProgress(criteria, (uint)referencePlayer.GetRewardedQuestCount(), referencePlayer); + break; + case CriteriaTypes.CompleteDailyQuestDaily: + { + long nextDailyResetTime = Global.WorldMgr.GetNextDailyQuestsResetTime(); + CriteriaProgress progress = GetCriteriaProgress(criteria); + + if (miscValue1 == 0) // Login case. + { + // reset if player missed one day. + if (progress != null && progress.Date < (nextDailyResetTime - 2 * Time.Day)) + SetCriteriaProgress(criteria, 0, referencePlayer); + continue; + } + + ProgressType progressType; + if (progress == null) + // 1st time. Start count. + progressType = ProgressType.Set; + else if (progress.Date < (nextDailyResetTime - 2 * Time.Day)) + // last progress is older than 2 days. Player missed 1 day => Restart count. + progressType = ProgressType.Set; + else if (progress.Date < (nextDailyResetTime - Time.Day)) + // last progress is between 1 and 2 days. => 1st time of the day. + progressType = ProgressType.Accumulate; + else + // last progress is within the day before the reset => Already counted today. + continue; + + SetCriteriaProgress(criteria, 1, referencePlayer, progressType); + break; + } + case CriteriaTypes.CompleteQuestsInZone: + { + uint counter = 0; + + var rewQuests = referencePlayer.getRewardedQuests(); + foreach (var id in rewQuests) + { + Quest quest = Global.ObjectMgr.GetQuestTemplate(id); + if (quest != null && quest.QuestSortID >= 0 && quest.QuestSortID == criteria.Entry.Asset) + ++counter; + } + SetCriteriaProgress(criteria, counter, referencePlayer); + break; + } + case CriteriaTypes.FallWithoutDying: + // miscValue1 is the ingame fallheight*100 as stored in dbc + SetCriteriaProgress(criteria, miscValue1, referencePlayer); + break; + case CriteriaTypes.CompleteQuest: + case CriteriaTypes.LearnSpell: + case CriteriaTypes.ExploreArea: + case CriteriaTypes.VisitBarberShop: + case CriteriaTypes.EquipEpicItem: + case CriteriaTypes.EquipItem: + case CriteriaTypes.CompleteAchievement: + case CriteriaTypes.RecruitGarrisonFollower: + case CriteriaTypes.OwnBattlePet: + SetCriteriaProgress(criteria, 1, referencePlayer); + break; + case CriteriaTypes.BuyBankSlot: + SetCriteriaProgress(criteria, referencePlayer.GetBankBagSlotCount(), referencePlayer); + break; + case CriteriaTypes.GainReputation: + { + int reputation = referencePlayer.GetReputationMgr().GetReputation(criteria.Entry.Asset); + if (reputation > 0) + SetCriteriaProgress(criteria, (uint)reputation, referencePlayer); + break; + } + case CriteriaTypes.GainExaltedReputation: + SetCriteriaProgress(criteria, referencePlayer.GetReputationMgr().GetExaltedFactionCount(), referencePlayer); + break; + case CriteriaTypes.LearnSkilllineSpells: + case CriteriaTypes.LearnSkillLine: + { + uint spellCount = 0; + foreach (var spell in referencePlayer.GetSpellMap()) + { + var bounds = Global.SpellMgr.GetSkillLineAbilityMapBounds(spell.Key); + foreach (var skill in bounds) + { + if (skill.SkillLine == criteria.Entry.Asset) + spellCount++; + } + } + SetCriteriaProgress(criteria, spellCount, referencePlayer); + break; + } + case CriteriaTypes.GainReveredReputation: + SetCriteriaProgress(criteria, referencePlayer.GetReputationMgr().GetReveredFactionCount(), referencePlayer); + break; + case CriteriaTypes.GainHonoredReputation: + SetCriteriaProgress(criteria, referencePlayer.GetReputationMgr().GetHonoredFactionCount(), referencePlayer); + break; + case CriteriaTypes.KnownFactions: + SetCriteriaProgress(criteria, referencePlayer.GetReputationMgr().GetVisibleFactionCount(), referencePlayer); + break; + case CriteriaTypes.EarnHonorableKill: + SetCriteriaProgress(criteria, referencePlayer.GetUInt32Value(PlayerFields.LifetimeHonorableKills), referencePlayer); + break; + case CriteriaTypes.HighestGoldValueOwned: + SetCriteriaProgress(criteria, referencePlayer.GetMoney(), referencePlayer, ProgressType.Highest); + break; + case CriteriaTypes.EarnAchievementPoints: + if (miscValue1 == 0) + continue; + SetCriteriaProgress(criteria, miscValue1, referencePlayer, ProgressType.Accumulate); + break; + case CriteriaTypes.HighestPersonalRating: + { + uint reqTeamType = criteria.Entry.Asset; + + if (miscValue1 != 0) + { + if (miscValue2 != reqTeamType) + continue; + + SetCriteriaProgress(criteria, miscValue1, referencePlayer, ProgressType.Highest); + } + else // login case + { + + for (byte arena_slot = 0; arena_slot < SharedConst.MaxArenaSlot; ++arena_slot) + { + uint teamId = referencePlayer.GetArenaTeamId(arena_slot); + if (teamId == 0) + continue; + + ArenaTeam team = Global.ArenaTeamMgr.GetArenaTeamById(teamId); + if (team == null || team.GetArenaType() != reqTeamType) + continue; + + ArenaTeamMember member = team.GetMember(referencePlayer.GetGUID()); + if (member != null) + { + SetCriteriaProgress(criteria, member.PersonalRating, referencePlayer, ProgressType.Highest); + break; + } + } + } + break; + } + case CriteriaTypes.ReachGuildLevel: + SetCriteriaProgress(criteria, miscValue1, referencePlayer); + break; + // FIXME: not triggered in code as result, need to implement + case CriteriaTypes.CompleteRaid: + case CriteriaTypes.PlayArena: + case CriteriaTypes.HighestTeamRating: + case CriteriaTypes.OwnRank: + case CriteriaTypes.SpentGoldGuildRepairs: + case CriteriaTypes.CraftItemsGuild: + case CriteriaTypes.CatchFromPool: + case CriteriaTypes.BuyGuildBankSlots: + case CriteriaTypes.EarnGuildAchievementPoints: + case CriteriaTypes.WinRatedBattleground: + case CriteriaTypes.ReachBgRating: + case CriteriaTypes.BuyGuildTabard: + case CriteriaTypes.CompleteQuestsGuild: + case CriteriaTypes.HonorableKillsGuild: + case CriteriaTypes.KillCreatureTypeGuild: + case CriteriaTypes.CompleteArchaeologyProjects: + case CriteriaTypes.CompleteGuildChallengeType: + case CriteriaTypes.CompleteGuildChallenge: + case CriteriaTypes.LfrDungeonsCompleted: + case CriteriaTypes.LfrLeaves: + case CriteriaTypes.LfrVoteKicksInitiatedByPlayer: + case CriteriaTypes.LfrVoteKicksNotInitByPlayer: + case CriteriaTypes.BeKickedFromLfr: + case CriteriaTypes.CountOfLfrQueueBoostsByTank: + case CriteriaTypes.CompleteScenarioCount: + case CriteriaTypes.CompleteScenario: + case CriteriaTypes.CaptureBattlePet: + case CriteriaTypes.WinPetBattle: + case CriteriaTypes.LevelBattlePet: + case CriteriaTypes.CaptureBattlePetCredit: + case CriteriaTypes.LevelBattlePetCredit: + case CriteriaTypes.EnterArea: + case CriteriaTypes.LeaveArea: + case CriteriaTypes.CompleteDungeonEncounter: + case CriteriaTypes.UpgradeGarrisonBuilding: + case CriteriaTypes.ConstructGarrisonBuilding: + case CriteriaTypes.UpgradeGarrison: + case CriteriaTypes.StartGarrisonMission: + case CriteriaTypes.CompleteGarrisonMissionCount: + case CriteriaTypes.CompleteGarrisonMission: + case CriteriaTypes.RecruitGarrisonFollowerCount: + case CriteriaTypes.LearnGarrisonBlueprintCount: + case CriteriaTypes.CompleteGarrisonShipment: + case CriteriaTypes.RaiseGarrisonFollowerItemLevel: + case CriteriaTypes.RaiseGarrisonFollowerLevel: + case CriteriaTypes.OwnToy: + case CriteriaTypes.OwnToyCount: + case CriteriaTypes.OwnHeirlooms: + break; // Not implemented yet :( + } + + foreach (CriteriaTree tree in trees) + { + if (IsCompletedCriteriaTree(tree)) + CompletedCriteriaTree(tree, referencePlayer); + + AfterCriteriaTreeUpdate(tree, referencePlayer); + } + } + } + + public void UpdateTimedCriteria(uint timeDiff) + { + if (!_timeCriteriaTrees.Empty()) + { + foreach (var key in _timeCriteriaTrees.Keys.ToList()) + { + var value = _timeCriteriaTrees[key]; + // Time is up, remove timer and reset progress + if (value <= timeDiff) + { + CriteriaTree criteriaTree = Global.CriteriaMgr.GetCriteriaTree(key); + if (criteriaTree.Criteria != null) + RemoveCriteriaProgress(criteriaTree.Criteria); + + _timeCriteriaTrees.Remove(key); + } + else + { + value -= timeDiff; + } + } + } + } + + public void StartCriteriaTimer(CriteriaTimedTypes type, uint entry, uint timeLost = 0) + { + List criteriaList = Global.CriteriaMgr.GetTimedCriteriaByType(type); + foreach (Criteria criteria in criteriaList) + { + if (criteria.Entry.StartAsset != entry) + continue; + + List trees = Global.CriteriaMgr.GetCriteriaTreesByCriteria(criteria.ID); + bool canStart = false; + foreach (CriteriaTree tree in trees) + { + if (!_timeCriteriaTrees.ContainsKey(tree.ID) && !IsCompletedCriteriaTree(tree)) + { + // Start the timer + if (criteria.Entry.StartTimer * Time.InMilliseconds > timeLost) + { + _timeCriteriaTrees[tree.ID] = (uint)(criteria.Entry.StartTimer * Time.InMilliseconds - timeLost); + canStart = true; + } + } + } + + if (!canStart) + continue; + + // and at client too + SetCriteriaProgress(criteria, 0, null, ProgressType.Set); + } + } + + public void RemoveCriteriaTimer(CriteriaTimedTypes type, uint entry) + { + List criteriaList = Global.CriteriaMgr.GetTimedCriteriaByType(type); + foreach (Criteria criteria in criteriaList) + { + if (criteria.Entry.StartAsset != entry) + continue; + + List trees = Global.CriteriaMgr.GetCriteriaTreesByCriteria(criteria.ID); + // Remove the timer from all trees + foreach (CriteriaTree tree in trees) + _timeCriteriaTrees.Remove(tree.ID); + + // remove progress + RemoveCriteriaProgress(criteria); + } + } + + public CriteriaProgress GetCriteriaProgress(Criteria entry) + { + return _criteriaProgress.LookupByKey(entry.ID); + } + + public void SetCriteriaProgress(Criteria criteria, ulong changeValue, Player referencePlayer, ProgressType progressType = ProgressType.Set) + { + // Don't allow to cheat - doing timed criteria without timer active + List trees = null; + if (criteria.Entry.StartTimer != 0) + { + trees = Global.CriteriaMgr.GetCriteriaTreesByCriteria(criteria.ID); + if (trees.Empty()) + return; + + bool hasTreeForTimed = false; + foreach (CriteriaTree tree in trees) + { + var timedIter = _timeCriteriaTrees.LookupByKey(tree.ID); + if (timedIter != 0) + { + hasTreeForTimed = true; + break; + } + } + + if (!hasTreeForTimed) + return; + } + + Log.outDebug(LogFilter.Achievement, "SetCriteriaProgress({0}, {1}) for {2}", criteria.ID, changeValue, GetOwnerInfo()); + + CriteriaProgress progress = GetCriteriaProgress(criteria); + if (progress == null) + { + // not create record for 0 counter but allow it for timed criteria + // we will need to send 0 progress to client to start the timer + if (changeValue == 0 && criteria.Entry.StartTimer == 0) + return; + + progress = new CriteriaProgress(); + progress.Counter = changeValue; + + } + else + { + ulong newValue = 0; + switch (progressType) + { + case ProgressType.Set: + newValue = changeValue; + break; + case ProgressType.Accumulate: + { + // avoid overflow + ulong max_value = ulong.MaxValue; + newValue = max_value - progress.Counter > changeValue ? progress.Counter + changeValue : max_value; + break; + } + case ProgressType.Highest: + newValue = progress.Counter < changeValue ? changeValue : progress.Counter; + break; + } + + // not update (not mark as changed) if counter will have same value + if (progress.Counter == newValue && criteria.Entry.StartTimer == 0) + return; + + progress.Counter = newValue; + } + + progress.Changed = true; + progress.Date = Time.UnixTime; // set the date to the latest update. + progress.PlayerGUID = referencePlayer ? referencePlayer.GetGUID() : ObjectGuid.Empty; + _criteriaProgress[criteria.ID] = progress; + + uint timeElapsed = 0; + if (criteria.Entry.StartTimer != 0) + { + Contract.Assert(trees != null); + + foreach (CriteriaTree tree in trees) + { + var timedIter = _timeCriteriaTrees.LookupByKey(tree.ID); + if (timedIter != 0) + { + // Client expects this in packet + timeElapsed = criteria.Entry.StartTimer - (timedIter / Time.InMilliseconds); + + // Remove the timer, we wont need it anymore + if (IsCompletedCriteriaTree(tree)) + _timeCriteriaTrees.Remove(tree.ID); + } + } + } + + SendCriteriaUpdate(criteria, progress, timeElapsed, true); + } + + public void RemoveCriteriaProgress(Criteria criteria) + { + if (criteria == null) + return; + + if (!_criteriaProgress.ContainsKey(criteria.ID)) + return; + + SendCriteriaProgressRemoved(criteria.ID); + + _criteriaProgress.Remove(criteria.ID); + } + + public bool IsCompletedCriteriaTree(CriteriaTree tree) + { + if (!CanCompleteCriteriaTree(tree)) + return false; + + ulong requiredCount = tree.Entry.Amount; + switch ((CriteriaTreeOperator)tree.Entry.Operator) + { + case CriteriaTreeOperator.Single: + return tree.Criteria != null && IsCompletedCriteria(tree.Criteria, requiredCount); + case CriteriaTreeOperator.SinglerNotCompleted: + return tree.Criteria == null || !IsCompletedCriteria(tree.Criteria, requiredCount); + case CriteriaTreeOperator.All: + foreach (CriteriaTree node in tree.Children) + if (!IsCompletedCriteriaTree(node)) + return false; + return true; + case CriteriaTreeOperator.SumChildren: + { + ulong progress = 0; + CriteriaManager.WalkCriteriaTree(tree, criteriaTree => + { + if (criteriaTree.Criteria != null) + { + CriteriaProgress criteriaProgress = GetCriteriaProgress(criteriaTree.Criteria); + if (criteriaProgress != null) + progress += criteriaProgress.Counter; + } + }); + return progress >= requiredCount; + } + case CriteriaTreeOperator.MaxChild: + { + ulong progress = 0; + CriteriaManager.WalkCriteriaTree(tree, criteriaTree => + { + if (criteriaTree.Criteria != null) + { + CriteriaProgress criteriaProgress = GetCriteriaProgress(criteriaTree.Criteria); + if (criteriaProgress != null) + if (criteriaProgress.Counter > progress) + progress = criteriaProgress.Counter; + } + }); + return progress >= requiredCount; + } + case CriteriaTreeOperator.CountDirectChildren: + { + ulong progress = 0; + foreach (CriteriaTree node in tree.Children) + { + if (node.Criteria != null) + { + CriteriaProgress criteriaProgress = GetCriteriaProgress(node.Criteria); + if (criteriaProgress != null) + if (criteriaProgress.Counter >= 1) + if (++progress >= requiredCount) + return true; + } + } + + return false; + } + case CriteriaTreeOperator.Any: + { + ulong progress = 0; + foreach (CriteriaTree node in tree.Children) + if (IsCompletedCriteriaTree(node)) + if (++progress >= requiredCount) + return true; + + return false; + } + case CriteriaTreeOperator.SumChildrenWeight: + { + ulong progress = 0; + CriteriaManager.WalkCriteriaTree(tree, criteriaTree => + { + if (criteriaTree.Criteria != null) + { + CriteriaProgress criteriaProgress = GetCriteriaProgress(criteriaTree.Criteria); + if (criteriaProgress != null) + progress += criteriaProgress.Counter * criteriaTree.Entry.Amount; + } + }); + return progress >= requiredCount; + } + default: + break; + } + + return false; + } + + public virtual bool CanUpdateCriteriaTree(Criteria criteria, CriteriaTree tree, Player referencePlayer) + { + if ((tree.Entry.Flags.HasAnyFlag(CriteriaTreeFlags.HordeOnly) && referencePlayer.GetTeam() != Team.Horde) || + (tree.Entry.Flags.HasAnyFlag(CriteriaTreeFlags.AllianceOnly) && referencePlayer.GetTeam() != Team.Alliance)) + { + Log.outTrace(LogFilter.Achievement, "CriteriaHandler.CanUpdateCriteriaTree: (Id: {0} Type {1} CriteriaTree {2}) Wrong faction", + criteria.ID, criteria.Entry.Type, tree.Entry.Id); + return false; + } + + return true; + } + + public virtual bool CanCompleteCriteriaTree(CriteriaTree tree) + { + return true; + } + + bool IsCompletedCriteria(Criteria criteria, ulong requiredAmount) + { + CriteriaProgress progress = GetCriteriaProgress(criteria); + if (progress == null) + return false; + + switch (criteria.Entry.Type) + { + case CriteriaTypes.WinBg: + case CriteriaTypes.KillCreature: + case CriteriaTypes.ReachLevel: + case CriteriaTypes.ReachGuildLevel: + case CriteriaTypes.ReachSkillLevel: + case CriteriaTypes.CompleteQuestCount: + case CriteriaTypes.CompleteDailyQuestDaily: + case CriteriaTypes.CompleteQuestsInZone: + case CriteriaTypes.DamageDone: + case CriteriaTypes.HealingDone: + case CriteriaTypes.CompleteDailyQuest: + case CriteriaTypes.FallWithoutDying: + case CriteriaTypes.BeSpellTarget: + case CriteriaTypes.BeSpellTarget2: + case CriteriaTypes.CastSpell: + case CriteriaTypes.CastSpell2: + case CriteriaTypes.BgObjectiveCapture: + case CriteriaTypes.HonorableKillAtArea: + case CriteriaTypes.HonorableKill: + case CriteriaTypes.EarnHonorableKill: + case CriteriaTypes.OwnItem: + case CriteriaTypes.WinRatedArena: + case CriteriaTypes.HighestPersonalRating: + case CriteriaTypes.UseItem: + case CriteriaTypes.LootItem: + case CriteriaTypes.BuyBankSlot: + case CriteriaTypes.GainReputation: + case CriteriaTypes.GainExaltedReputation: + case CriteriaTypes.VisitBarberShop: + case CriteriaTypes.EquipEpicItem: + case CriteriaTypes.RollNeedOnLoot: + case CriteriaTypes.RollGreedOnLoot: + case CriteriaTypes.HkClass: + case CriteriaTypes.HkRace: + case CriteriaTypes.DoEmote: + case CriteriaTypes.EquipItem: + case CriteriaTypes.MoneyFromQuestReward: + case CriteriaTypes.LootMoney: + case CriteriaTypes.UseGameobject: + case CriteriaTypes.SpecialPvpKill: + case CriteriaTypes.FishInGameobject: + case CriteriaTypes.LearnSkilllineSpells: + case CriteriaTypes.LearnSkillLine: + case CriteriaTypes.WinDuel: + case CriteriaTypes.LootType: + case CriteriaTypes.UseLfdToGroupWithPlayers: + case CriteriaTypes.GetKillingBlows: + case CriteriaTypes.Currency: + case CriteriaTypes.PlaceGarrisonBuilding: + case CriteriaTypes.OwnBattlePetCount: + return progress.Counter >= requiredAmount; + case CriteriaTypes.CompleteAchievement: + case CriteriaTypes.CompleteQuest: + case CriteriaTypes.LearnSpell: + case CriteriaTypes.ExploreArea: + case CriteriaTypes.RecruitGarrisonFollower: + case CriteriaTypes.OwnBattlePet: + case CriteriaTypes.HonorLevelReached: + case CriteriaTypes.PrestigeReached: + return progress.Counter >= 1; + case CriteriaTypes.LearnSkillLevel: + return progress.Counter >= (requiredAmount * 75); + case CriteriaTypes.EarnAchievementPoints: + return progress.Counter >= 9000; + case CriteriaTypes.WinArena: + return requiredAmount != 0 && progress.Counter >= requiredAmount; + case CriteriaTypes.OnLogin: + return true; + // handle all statistic-only criteria here + default: + break; + } + + return false; + } + + bool CanUpdateCriteria(Criteria criteria, List trees, ulong miscValue1, ulong miscValue2, ulong miscValue3, Unit unit, Player referencePlayer) + { + if (Global.DisableMgr.IsDisabledFor(DisableType.Criteria, criteria.ID, null)) + { + Log.outError(LogFilter.Achievement, "CanUpdateCriteria: (Id: {0} Type {1}) Disabled", criteria.ID, criteria.Entry.Type); + return false; + } + + bool treeRequirementPassed = false; + foreach (CriteriaTree tree in trees) + { + if (!CanUpdateCriteriaTree(criteria, tree, referencePlayer)) + continue; + + treeRequirementPassed = true; + break; + } + + if (!treeRequirementPassed) + return false; + + if (!RequirementsSatisfied(criteria, miscValue1, miscValue2, miscValue3, unit, referencePlayer)) + { + Log.outTrace(LogFilter.Achievement, "CanUpdateCriteria: (Id: {0} Type {1}) Requirements not satisfied", criteria.ID, criteria.Entry.Type); + return false; + } + + if (criteria.Modifier != null && !AdditionalRequirementsSatisfied(criteria.Modifier, miscValue1, miscValue2, unit, referencePlayer)) + { + Log.outTrace(LogFilter.Achievement, "CanUpdateCriteria: (Id: {0} Type {1}) Requirements have not been satisfied", criteria.ID, criteria.Entry.Type); + return false; + } + + if (!ConditionsSatisfied(criteria, referencePlayer)) + { + Log.outTrace(LogFilter.Achievement, "CanUpdateCriteria: (Id: {0} Type {1}) Conditions have not been satisfied", criteria.ID, criteria.Entry.Type); + return false; + } + + return true; + } + + bool ConditionsSatisfied(Criteria criteria, Player referencePlayer) + { + if (criteria.Entry.FailEvent == 0) + return true; + + switch ((CriteriaCondition)criteria.Entry.FailEvent) + { + case CriteriaCondition.BgMap: + if (!referencePlayer.InBattleground()) + return false; + break; + case CriteriaCondition.NotInGroup: + if (referencePlayer.GetGroup()) + return false; + break; + default: + break; + } + + return true; + } + + bool RequirementsSatisfied(Criteria criteria, ulong miscValue1, ulong miscValue2, ulong miscValue3, Unit unit, Player referencePlayer) + { + switch (criteria.Entry.Type) + { + case CriteriaTypes.AcceptedSummonings: + case CriteriaTypes.CompleteDailyQuest: + case CriteriaTypes.CreateAuction: + case CriteriaTypes.FallWithoutDying: + case CriteriaTypes.FlightPathsTaken: + case CriteriaTypes.GetKillingBlows: + case CriteriaTypes.GoldEarnedByAuctions: + case CriteriaTypes.GoldSpentAtBarber: + case CriteriaTypes.GoldSpentForMail: + case CriteriaTypes.GoldSpentForTalents: + case CriteriaTypes.GoldSpentForTravelling: + case CriteriaTypes.HighestAuctionBid: + case CriteriaTypes.HighestAuctionSold: + case CriteriaTypes.HighestHealingReceived: + case CriteriaTypes.HighestHealCasted: + case CriteriaTypes.HighestHitDealt: + case CriteriaTypes.HighestHitReceived: + case CriteriaTypes.HonorableKill: + case CriteriaTypes.LootMoney: + case CriteriaTypes.LoseDuel: + case CriteriaTypes.MoneyFromQuestReward: + case CriteriaTypes.MoneyFromVendors: + case CriteriaTypes.NumberOfTalentResets: + case CriteriaTypes.QuestAbandoned: + case CriteriaTypes.ReachGuildLevel: + case CriteriaTypes.RollGreed: + case CriteriaTypes.RollNeed: + case CriteriaTypes.SpecialPvpKill: + case CriteriaTypes.TotalDamageReceived: + case CriteriaTypes.TotalHealingReceived: + case CriteriaTypes.UseLfdToGroupWithPlayers: + case CriteriaTypes.VisitBarberShop: + case CriteriaTypes.WinDuel: + case CriteriaTypes.WinRatedArena: + case CriteriaTypes.WonAuctions: + if (miscValue1 == 0) + return false; + break; + case CriteriaTypes.BuyBankSlot: + case CriteriaTypes.CompleteDailyQuestDaily: + case CriteriaTypes.CompleteQuestCount: + case CriteriaTypes.EarnAchievementPoints: + case CriteriaTypes.GainExaltedReputation: + case CriteriaTypes.GainHonoredReputation: + case CriteriaTypes.GainReveredReputation: + case CriteriaTypes.HighestGoldValueOwned: + case CriteriaTypes.HighestPersonalRating: + case CriteriaTypes.KnownFactions: + case CriteriaTypes.ReachLevel: + case CriteriaTypes.OnLogin: + break; + case CriteriaTypes.CompleteAchievement: + if (!RequiredAchievementSatisfied(criteria.Entry.Asset)) + return false; + break; + case CriteriaTypes.WinBg: + case CriteriaTypes.CompleteBattleground: + case CriteriaTypes.DeathAtMap: + if (miscValue1 == 0 || criteria.Entry.Asset != referencePlayer.GetMapId()) + return false; + break; + case CriteriaTypes.KillCreature: + case CriteriaTypes.KilledByCreature: + if (miscValue1 == 0 || criteria.Entry.Asset != miscValue1) + return false; + break; + case CriteriaTypes.ReachSkillLevel: + case CriteriaTypes.LearnSkillLevel: + // update at loading or specific skill update + if (miscValue1 != 0 && miscValue1 != criteria.Entry.Asset) + return false; + break; + case CriteriaTypes.CompleteQuestsInZone: + if (miscValue1 != 0 && miscValue1 != criteria.Entry.Asset) + return false; + break; + case CriteriaTypes.Death: + { + if (miscValue1 == 0) + return false; + break; + } + case CriteriaTypes.DeathInDungeon: + { + if (miscValue1 == 0) + return false; + + Map map = referencePlayer.IsInWorld ? referencePlayer.GetMap() : Global.MapMgr.FindMap(referencePlayer.GetMapId(), referencePlayer.GetInstanceId()); + if (!map || !map.IsDungeon()) + return false; + + //FIXME: work only for instances where max == min for players + if (map.ToInstanceMap().GetMaxPlayers() != criteria.Entry.Asset) + return false; + break; + } + case CriteriaTypes.KilledByPlayer: + if (miscValue1 == 0 || !unit || !unit.IsTypeId(TypeId.Player)) + return false; + break; + case CriteriaTypes.DeathsFrom: + if (miscValue1 == 0 || miscValue2 != criteria.Entry.Asset) + return false; + break; + case CriteriaTypes.CompleteQuest: + { + // if miscValues != 0, it contains the questID. + if (miscValue1 != 0) + { + if (miscValue1 != criteria.Entry.Asset) + return false; + } + else + { + // login case. + if (!referencePlayer.GetQuestRewardStatus(criteria.Entry.Asset)) + return false; + } + CriteriaDataSet data = Global.CriteriaMgr.GetCriteriaDataSet(criteria); + if (data != null) + if (!data.Meets(referencePlayer, unit)) + return false; + break; + } + case CriteriaTypes.BeSpellTarget: + case CriteriaTypes.BeSpellTarget2: + case CriteriaTypes.CastSpell: + case CriteriaTypes.CastSpell2: + if (miscValue1 == 0 || miscValue1 != criteria.Entry.Asset) + return false; + break; + case CriteriaTypes.LearnSpell: + if (miscValue1 != 0 && miscValue1 != criteria.Entry.Asset) + return false; + + if (!referencePlayer.HasSpell(criteria.Entry.Asset)) + return false; + break; + case CriteriaTypes.LootType: + // miscValue1 = itemId - miscValue2 = count of item loot + // miscValue3 = loot_type (note: 0 = LOOT_CORPSE and then it ignored) + if (miscValue1 == 0 || miscValue2 == 0 || miscValue3 == 0 || miscValue3 != criteria.Entry.Asset) + return false; + break; + case CriteriaTypes.OwnItem: + if (miscValue1 != 0 && criteria.Entry.Asset != miscValue1) + return false; + break; + case CriteriaTypes.UseItem: + case CriteriaTypes.LootItem: + case CriteriaTypes.EquipItem: + if (miscValue1 == 0 || criteria.Entry.Asset != miscValue1) + return false; + break; + case CriteriaTypes.ExploreArea: + { + WorldMapOverlayRecord worldOverlayEntry = CliDB.WorldMapOverlayStorage.LookupByKey(criteria.Entry.Asset); + if (worldOverlayEntry == null) + break; + + bool matchFound = false; + for (int j = 0; j < SharedConst.MaxWorldMapOverlayArea; ++j) + { + AreaTableRecord area = CliDB.AreaTableStorage.LookupByKey(worldOverlayEntry.AreaID[j]); + if (area == null) + break; + + if (area.AreaBit < 0) + continue; + + int playerIndexOffset = (int)((uint)area.AreaBit / 32); + if (playerIndexOffset >= PlayerConst.ExploredZonesSize) + continue; + + uint mask = 1u << (int)((uint)area.AreaBit % 32); + if (Convert.ToBoolean(referencePlayer.GetUInt32Value(PlayerFields.ExploredZones1 + playerIndexOffset) & mask)) + { + matchFound = true; + break; + } + } + + if (!matchFound) + return false; + break; + } + case CriteriaTypes.GainReputation: + if (miscValue1 != 0 && miscValue1 != criteria.Entry.Asset) + return false; + break; + case CriteriaTypes.EquipEpicItem: + // miscValue1 = itemid miscValue2 = itemSlot + if (miscValue1 == 0 || miscValue2 != criteria.Entry.Asset) + return false; + break; + case CriteriaTypes.RollNeedOnLoot: + case CriteriaTypes.RollGreedOnLoot: + { + // miscValue1 = itemid miscValue2 = diced value + if (miscValue1 == 0 || miscValue2 != criteria.Entry.Asset) + return false; + + ItemTemplate proto = Global.ObjectMgr.GetItemTemplate((uint)miscValue1); + if (proto == null) + return false; + break; + } + case CriteriaTypes.DoEmote: + if (miscValue1 == 0 || miscValue1 != criteria.Entry.Asset) + return false; + break; + case CriteriaTypes.DamageDone: + case CriteriaTypes.HealingDone: + if (miscValue1 == 0) + return false; + + if (criteria.Entry.FailEvent == (uint)CriteriaCondition.BgMap) + { + if (!referencePlayer.InBattleground()) + return false; + + // map specific case (BG in fact) expected player targeted damage/heal + if (!unit || !unit.IsTypeId(TypeId.Player)) + return false; + } + break; + case CriteriaTypes.UseGameobject: + case CriteriaTypes.FishInGameobject: + if (miscValue1 == 0 || miscValue1 != criteria.Entry.Asset) + return false; + break; + case CriteriaTypes.LearnSkilllineSpells: + case CriteriaTypes.LearnSkillLine: + if (miscValue1 != 0 && miscValue1 != criteria.Entry.Asset) + return false; + break; + case CriteriaTypes.LootEpicItem: + case CriteriaTypes.ReceiveEpicItem: + { + if (miscValue1 == 0) + return false; + ItemTemplate proto = Global.ObjectMgr.GetItemTemplate((uint)miscValue1); + if (proto == null || proto.GetQuality() < ItemQuality.Epic) + return false; + break; + } + case CriteriaTypes.HkClass: + if (miscValue1 == 0 || miscValue1 != criteria.Entry.Asset) + return false; + break; + case CriteriaTypes.HkRace: + if (miscValue1 == 0 || miscValue1 != criteria.Entry.Asset) + return false; + break; + case CriteriaTypes.BgObjectiveCapture: + if (miscValue1 == 0 || miscValue1 != criteria.Entry.Asset) + return false; + break; + case CriteriaTypes.HonorableKillAtArea: + if (miscValue1 == 0 || miscValue1 != criteria.Entry.Asset) + return false; + break; + case CriteriaTypes.Currency: + if (miscValue1 == 0 || miscValue2 == 0 || (long)miscValue2 < 0 + || miscValue1 != criteria.Entry.Asset) + return false; + break; + case CriteriaTypes.WinArena: + if (miscValue1 != criteria.Entry.Asset) + return false; + break; + case CriteriaTypes.HighestTeamRating: + return false; + case CriteriaTypes.PlaceGarrisonBuilding: + if (miscValue1 != criteria.Entry.Asset) + return false; + break; + default: + break; + } + return true; + } + + public bool AdditionalRequirementsSatisfied(ModifierTreeNode tree, ulong miscValue1, ulong miscValue2, Unit unit, Player referencePlayer) + { + foreach (ModifierTreeNode node in tree.Children) + if (!AdditionalRequirementsSatisfied(node, miscValue1, miscValue2, unit, referencePlayer)) + return false; + + uint reqType = tree.Entry.Type; + if (reqType == 0) + return true; + + uint reqValue = tree.Entry.Asset[0]; + switch ((CriteriaAdditionalCondition)reqType) + { + case CriteriaAdditionalCondition.ItemLevel: // 3 + { + // miscValue1 is itemid + ItemTemplate item = Global.ObjectMgr.GetItemTemplate((uint)miscValue1); + if (item == null || item.GetBaseItemLevel() < reqValue) + return false; + break; + } + case CriteriaAdditionalCondition.TargetCreatureEntry: // 4 + if (unit == null || unit.GetEntry() != reqValue) + return false; + break; + case CriteriaAdditionalCondition.TargetMustBePlayer: // 5 + if (unit == null || !unit.IsTypeId(TypeId.Player)) + return false; + break; + case CriteriaAdditionalCondition.TargetMustBeDead: // 6 + if (unit == null || unit.IsAlive()) + return false; + break; + case CriteriaAdditionalCondition.TargetMustBeEnemy: // 7 + if (unit == null || !referencePlayer.IsHostileTo(unit)) + return false; + break; + case CriteriaAdditionalCondition.SourceHasAura: // 8 + if (!referencePlayer.HasAura(reqValue)) + return false; + break; + case CriteriaAdditionalCondition.TargetHasAura: // 10 + if (unit == null || !unit.HasAura(reqValue)) + return false; + break; + case CriteriaAdditionalCondition.TargetHasAuraType: // 11 + if (unit == null || !unit.HasAuraType((AuraType)reqValue)) + return false; + break; + case CriteriaAdditionalCondition.ItemQualityMin: // 14 + { + // miscValue1 is itemid + ItemTemplate item = Global.ObjectMgr.GetItemTemplate((uint)miscValue1); + if (item == null || (uint)item.GetQuality() < reqValue) + return false; + break; + } + case CriteriaAdditionalCondition.ItemQualityEquals: // 15 + { + // miscValue1 is itemid + ItemTemplate item = Global.ObjectMgr.GetItemTemplate((uint)miscValue1); + if (item == null || (uint)item.GetQuality() != reqValue) + return false; + break; + } + case CriteriaAdditionalCondition.SourceAreaOrZone: // 17 + { + uint zoneId, areaId; + referencePlayer.GetZoneAndAreaId(out zoneId, out areaId); + if (zoneId != reqValue && areaId != reqValue) + return false; + break; + } + case CriteriaAdditionalCondition.TargetAreaOrZone: // 18 + { + if (unit == null) + return false; + uint zoneId, areaId; + unit.GetZoneAndAreaId(out zoneId, out areaId); + if (zoneId != reqValue && areaId != reqValue) + return false; + break; + } + case CriteriaAdditionalCondition.MapDifficultyOld: // 20 + DifficultyRecord difficulty = CliDB.DifficultyStorage.LookupByKey(referencePlayer.GetMap().GetDifficultyID()); + if (difficulty == null || difficulty.OldEnumValue == -1 || difficulty.OldEnumValue != reqValue) + return false; + break; + case CriteriaAdditionalCondition.ArenaType: // 24 + { + Battleground bg = referencePlayer.GetBattleground(); + if (!bg || !bg.isArena() || bg.GetArenaType() != (ArenaTypes)reqValue) + return false; + break; + } + case CriteriaAdditionalCondition.SourceRace: // 25 + if ((uint)referencePlayer.GetRace() != reqValue) + return false; + break; + case CriteriaAdditionalCondition.SourceClass: // 26 + if ((uint)referencePlayer.GetClass() != reqValue) + return false; + break; + case CriteriaAdditionalCondition.TargetRace: // 27 + if (unit == null || !unit.IsTypeId(TypeId.Player) || (uint)unit.GetRace() != reqValue) + return false; + break; + case CriteriaAdditionalCondition.TargetClass: // 28 + if (unit == null || !unit.IsTypeId(TypeId.Player) || (uint)unit.GetClass() != reqValue) + return false; + break; + case CriteriaAdditionalCondition.MaxGroupMembers: // 29 + if (referencePlayer.GetGroup() && referencePlayer.GetGroup().GetMembersCount() >= reqValue) + return false; + break; + case CriteriaAdditionalCondition.TargetCreatureType: // 30 + { + if (unit == null) + return false; + + if (!unit.IsTypeId(TypeId.Unit) || (uint)unit.GetCreatureType() != reqValue) + return false; + break; + } + case CriteriaAdditionalCondition.SourceMap: // 32 + if (referencePlayer.GetMapId() != reqValue) + return false; + break; + case CriteriaAdditionalCondition.TitleBitIndex: // 38 // miscValue1 is title's bit index + if (miscValue1 != reqValue) + return false; + break; + case CriteriaAdditionalCondition.SourceLevel: // 39 + if (referencePlayer.getLevel() != reqValue) + return false; + break; + case CriteriaAdditionalCondition.TargetLevel: // 40 + if (unit == null || unit.getLevel() != reqValue) + return false; + break; + case CriteriaAdditionalCondition.TargetZone: // 41 + if (unit == null || unit.GetZoneId() != reqValue) + return false; + break; + case CriteriaAdditionalCondition.TargetHealthPercentBelow: // 46 + if (unit == null || unit.GetHealthPct() >= reqValue) + return false; + break; + case CriteriaAdditionalCondition.RatedBattlegroundRating: // 64 + if (referencePlayer.GetRBGPersonalRating() < reqValue) + return false; + break; + case CriteriaAdditionalCondition.BattlePetSpecies: // 91 + if (miscValue1 != reqValue) + return false; + break; + case CriteriaAdditionalCondition.GarrisonFollowerEntry: // 144 + { + if (!referencePlayer) + return false; + Garrison garrison = referencePlayer.GetGarrison(); + if (garrison == null) + return false; + Garrison.Follower follower = garrison.GetFollower(miscValue1); + if (follower == null || follower.PacketInfo.GarrFollowerID != reqValue) + return false; + break; + } + case CriteriaAdditionalCondition.GarrisonFollowerQuality: // 145 + { + if (!referencePlayer) + return false; + Garrison garrison = referencePlayer.GetGarrison(); + if (garrison == null) + return false; + Garrison.Follower follower = garrison.GetFollower(miscValue1); + if (follower == null || follower.PacketInfo.Quality != reqValue) + return false; + + break; + } + case CriteriaAdditionalCondition.GarrisonFollowerLevel: // 146 + { + if (!referencePlayer) + return false; + Garrison garrison = referencePlayer.GetGarrison(); + if (garrison == null) + return false; + Garrison.Follower follower = garrison.GetFollower(miscValue1); + if (follower == null || follower.PacketInfo.FollowerLevel < reqValue) + return false; + + break; + } + case CriteriaAdditionalCondition.GarrisonFollowILvl: // 184 + { + if (!referencePlayer) + return false; + Garrison garrison = referencePlayer.GetGarrison(); + if (garrison == null) + return false; + Garrison.Follower follower = garrison.GetFollower(miscValue1); + if (follower == null || follower.GetItemLevel() < reqValue) + return false; + break; + } + case CriteriaAdditionalCondition.HonorLevel: // 193 + if (!referencePlayer || referencePlayer.GetHonorLevel() != reqValue) + return false; + break; + case CriteriaAdditionalCondition.PrestigeLevel: // 194 + if (!referencePlayer || referencePlayer.GetPrestigeLevel() != reqValue) + return false; + break; + default: + break; + } + return true; + } + + public virtual void SendAllData(Player receiver) { } + public virtual void SendCriteriaUpdate(Criteria criteria, CriteriaProgress progress, uint timeElapsed, bool timedCompleted) { } + public virtual void SendCriteriaProgressRemoved(uint criteriaId) { } + + public virtual void CompletedCriteriaTree(CriteriaTree tree, Player referencePlayer) { } + public virtual void AfterCriteriaTreeUpdate(CriteriaTree tree, Player referencePlayer) { } + + public virtual void SendPacket(ServerPacket data) { } + + public virtual bool RequiredAchievementSatisfied(uint achievementId) { return false; } + + public virtual string GetOwnerInfo() { return ""; } + public virtual List GetCriteriaByType(CriteriaTypes type) { return null; } + + protected Dictionary _criteriaProgress = new Dictionary(); + Dictionary _timeCriteriaTrees = new Dictionary(); + } + + public class CriteriaManager : Singleton + { + CriteriaManager() { } + + public void LoadCriteriaModifiersTree() + { + uint oldMSTime = Time.GetMSTime(); + + if (CliDB.ModifierTreeStorage.Empty()) + { + Log.outError(LogFilter.ServerLoading, "Loaded 0 criteria modifiers."); + return; + } + + // Load modifier tree nodes + foreach (var tree in CliDB.ModifierTreeStorage.Values) + { + ModifierTreeNode node = new ModifierTreeNode(); + node.Entry = tree; + _criteriaModifiers[node.Entry.Id] = node; + } + + // Build tree + foreach (var treeNode in _criteriaModifiers.Values) + { + if (treeNode.Entry.Parent == 0) + continue; + + var parent = _criteriaModifiers.LookupByKey(treeNode.Entry.Parent); + if (parent != null) + parent.Children.Add(treeNode); + } + + Log.outInfo(LogFilter.ServerLoading, "Loaded {0} criteria modifiers in {1} ms", _criteriaModifiers.Count, Time.GetMSTimeDiffToNow(oldMSTime)); + } + + T GetEntry(Dictionary map, CriteriaTreeRecord tree) where T : new() + { + CriteriaTreeRecord cur = tree; + var obj = map.LookupByKey(tree.Id); + while (obj == null) + { + if (cur.Parent == 0) + break; + + cur = CliDB.CriteriaTreeStorage.LookupByKey(cur.Parent); + if (cur == null) + break; + + obj = map.LookupByKey(cur.Id); + } + + if (obj == null) + return default(T); + + return obj; + } + + public void LoadCriteriaList() + { + uint oldMSTime = Time.GetMSTime(); + + if (CliDB.CriteriaTreeStorage.Empty()) + { + Log.outError(LogFilter.ServerLoading, "Loaded 0 criteria."); + return; + } + + Dictionary achievementCriteriaTreeIds = new Dictionary(); + foreach (AchievementRecord achievement in CliDB.AchievementStorage.Values) + if (achievement.CriteriaTree != 0) + achievementCriteriaTreeIds[achievement.CriteriaTree] = achievement; + + Dictionary scenarioCriteriaTreeIds = new Dictionary(); + foreach (ScenarioStepRecord scenarioStep in CliDB.ScenarioStepStorage.Values) + { + if (scenarioStep.CriteriaTreeID != 0) + scenarioCriteriaTreeIds[scenarioStep.CriteriaTreeID] = scenarioStep; + } + + // Load criteria tree nodes + foreach (CriteriaTreeRecord tree in CliDB.CriteriaTreeStorage.Values) + { + // Find linked achievement + AchievementRecord achievement = GetEntry(achievementCriteriaTreeIds, tree); + ScenarioStepRecord scenarioStep = GetEntry(scenarioCriteriaTreeIds, tree); + if (achievement == null && scenarioStep == null) + continue; + + CriteriaTree criteriaTree = new CriteriaTree(); + criteriaTree.ID = tree.Id; + criteriaTree.Achievement = achievement; + criteriaTree.ScenarioStep = scenarioStep; + criteriaTree.Entry = tree; + + _criteriaTrees[criteriaTree.Entry.Id] = criteriaTree; + } + + // Build tree + foreach (var pair in _criteriaTrees) + { + if (pair.Value.Entry.Parent == 0) + continue; + + var parent = _criteriaTrees.LookupByKey(pair.Value.Entry.Parent); + if (parent != null) + { + parent.Children.Add(pair.Value); + while (parent != null) + { + var cur = parent; + parent = _criteriaTrees.LookupByKey(parent.Entry.Parent); + if (parent == null) + { + if (CliDB.CriteriaStorage.ContainsKey(pair.Value.Entry.CriteriaID)) + _criteriaTreeByCriteria.Add(pair.Value.Entry.CriteriaID, cur); + } + } + } + else if (CliDB.CriteriaStorage.ContainsKey(pair.Value.Entry.CriteriaID)) + _criteriaTreeByCriteria.Add(pair.Value.Entry.CriteriaID, pair.Value); + } + + // Load criteria + uint criterias = 0; + uint guildCriterias = 0; + uint scenarioCriterias = 0; + foreach (CriteriaRecord criteriaEntry in CliDB.CriteriaStorage.Values) + { + Contract.Assert(criteriaEntry.Type < CriteriaTypes.TotalTypes, string.Format("CRITERIA_TYPE_TOTAL must be greater than or equal to {0} but is currently equal to {1}", criteriaEntry.Type + 1, CriteriaTypes.TotalTypes)); + + var treeList = _criteriaTreeByCriteria.LookupByKey(criteriaEntry.Id); + if (treeList.Empty()) + continue; + + Criteria criteria = new Criteria(); + criteria.ID = criteriaEntry.Id; + criteria.Entry = criteriaEntry; + var mod = _criteriaModifiers.LookupByKey(criteriaEntry.ModifierTreeId); + if (mod != null) + criteria.Modifier = mod; + + _criteria[criteria.ID] = criteria; + + foreach (CriteriaTree tree in treeList) + { + AchievementRecord achievement = tree.Achievement; + if (achievement != null) + { + if (achievement.Flags.HasAnyFlag(AchievementFlags.Guild)) + criteria.FlagsCu |= CriteriaFlagsCu.Guild; + else if (achievement.Flags.HasAnyFlag(AchievementFlags.Account)) + criteria.FlagsCu |= CriteriaFlagsCu.Account; + else + criteria.FlagsCu |= CriteriaFlagsCu.Player; + } + else if (tree.ScenarioStep != null) + criteria.FlagsCu |= CriteriaFlagsCu.Scenario; + } + + if (criteria.FlagsCu.HasAnyFlag(CriteriaFlagsCu.Player | CriteriaFlagsCu.Account)) + { + ++criterias; + _criteriasByType.Add(criteriaEntry.Type, criteria); + } + + if (criteria.FlagsCu.HasAnyFlag(CriteriaFlagsCu.Guild)) + { + ++guildCriterias; + _guildCriteriasByType.Add(criteriaEntry.Type, criteria); + } + + if (criteria.FlagsCu.HasAnyFlag(CriteriaFlagsCu.Scenario)) + { + ++scenarioCriterias; + _scenarioCriteriasByType.Add(criteriaEntry.Type, criteria); + } + + if (criteriaEntry.StartTimer != 0) + _criteriasByTimedType.Add(criteriaEntry.StartEvent, criteria); + } + + foreach (var p in _criteriaTrees) + p.Value.Criteria = GetCriteria(p.Value.Entry.CriteriaID); + + Log.outInfo(LogFilter.ServerLoading, "Loaded {0} criteria, {1} guild criteria and {2} scenario criteria in {3} ms.", criterias, guildCriterias, scenarioCriterias, Time.GetMSTimeDiffToNow(oldMSTime)); + } + + public void LoadCriteriaData() + { + uint oldMSTime = Time.GetMSTime(); + + _criteriaDataMap.Clear(); // need for reload case + + SQLResult result = DB.World.Query("SELECT criteria_id, type, value1, value2, ScriptName FROM criteria_data"); + if (result.IsEmpty()) + { + Log.outInfo(LogFilter.ServerLoading, "Loaded 0 additional criteria data. DB table `criteria_data` is empty."); + return; + } + + uint count = 0; + do + { + uint criteria_id = result.Read(0); + + Criteria criteria = GetCriteria(criteria_id); + if (criteria == null) + { + Log.outError(LogFilter.Sql, "Table `criteria_data` contains data for non-existing criteria (Entry: {0}). Ignored.", criteria_id); + continue; + } + + CriteriaDataType dataType = (CriteriaDataType)result.Read(1); + string scriptName = result.Read(4); + uint scriptId = 0; + if (!scriptName.IsEmpty()) + { + if (dataType != CriteriaDataType.Script) + Log.outError(LogFilter.Sql, "Table `criteria_data` contains a ScriptName for non-scripted data type (Entry: {0}, type {1}), useless data.", criteria_id, dataType); + else + scriptId = Global.ObjectMgr.GetScriptId(scriptName); + } + + CriteriaData data = new CriteriaData(dataType, result.Read(2), result.Read(3), scriptId); + + if (!data.IsValid(criteria)) + continue; + + // this will allocate empty data set storage + CriteriaDataSet dataSet = new CriteriaDataSet(); + dataSet.SetCriteriaId(criteria_id); + + // add real data only for not NONE data types + if (data.DataType != CriteriaDataType.None) + dataSet.Add(data); + + _criteriaDataMap[criteria_id] = dataSet; + // counting data by and data types + ++count; + } + while (result.NextRow()); + + Log.outInfo(LogFilter.ServerLoading, "Loaded {0} additional criteria data in {1} ms", count, Time.GetMSTimeDiffToNow(oldMSTime)); + } + + public CriteriaTree GetCriteriaTree(uint criteriaTreeId) + { + return _criteriaTrees.LookupByKey(criteriaTreeId); + } + + public Criteria GetCriteria(uint criteriaId) + { + return _criteria.LookupByKey(criteriaId); + } + + public ModifierTreeNode GetModifierTree(uint modifierTreeId) + { + return _criteriaModifiers.LookupByKey(modifierTreeId); + } + + public List GetPlayerCriteriaByType(CriteriaTypes type) + { + return _criteriasByType.LookupByKey(type); + } + + public List GetGuildCriteriaByType(CriteriaTypes type) + { + return _guildCriteriasByType.LookupByKey(type); + } + + public List GetScenarioCriteriaByType(CriteriaTypes type) + { + return _scenarioCriteriasByType.LookupByKey(type); + } + + public List GetCriteriaTreesByCriteria(uint criteriaId) + { + return _criteriaTreeByCriteria.LookupByKey(criteriaId); + } + + public List GetTimedCriteriaByType(CriteriaTimedTypes type) + { + return _criteriasByTimedType.LookupByKey(type); + } + + public CriteriaDataSet GetCriteriaDataSet(Criteria criteria) + { + return _criteriaDataMap.LookupByKey(criteria.ID); + } + + public static bool IsGroupCriteriaType(CriteriaTypes type) + { + switch (type) + { + case CriteriaTypes.KillCreature: + case CriteriaTypes.WinBg: + case CriteriaTypes.BeSpellTarget: // NYI + case CriteriaTypes.WinRatedArena: + case CriteriaTypes.BeSpellTarget2: // NYI + case CriteriaTypes.WinRatedBattleground: // NYI + return true; + default: + break; + } + + return false; + } + + public static void WalkCriteriaTree(CriteriaTree tree, Action func) + { + foreach (CriteriaTree node in tree.Children) + WalkCriteriaTree(node, func); + + func(tree); + } + + Dictionary _criteriaDataMap = new Dictionary(); + + Dictionary _criteriaTrees = new Dictionary(); + Dictionary _criteria = new Dictionary(); + Dictionary _criteriaModifiers = new Dictionary(); + + MultiMap _criteriaTreeByCriteria = new MultiMap(); + + // store criterias by type to speed up lookup + MultiMap _criteriasByType = new MultiMap(); + MultiMap _guildCriteriasByType = new MultiMap(); + MultiMap _scenarioCriteriasByType = new MultiMap(); + + MultiMap _criteriasByTimedType = new MultiMap(); + } + + public class ModifierTreeNode + { + public ModifierTreeRecord Entry; + public List Children = new List(); + } + + public class Criteria + { + public uint ID; + public CriteriaRecord Entry; + public ModifierTreeNode Modifier; + public CriteriaFlagsCu FlagsCu; + } + + public class CriteriaTree + { + public uint ID; + public CriteriaTreeRecord Entry; + public AchievementRecord Achievement; + public ScenarioStepRecord ScenarioStep; + public Criteria Criteria; + public List Children = new List(); + } + + public class CriteriaProgress + { + public ulong Counter; + public long Date; // latest update time. + public ObjectGuid PlayerGUID; // GUID of the player that completed this criteria (guild achievements) + public bool Changed; + } + + [StructLayout(LayoutKind.Explicit)] + public class CriteriaData + { + public CriteriaData() + { + DataType = CriteriaDataType.None; + + Raw.Value1 = 0; + Raw.Value2 = 0; + ScriptId = 0; + } + + public CriteriaData(CriteriaDataType _dataType, uint _value1, uint _value2, uint _scriptId) + { + this.DataType = _dataType; + + Raw.Value1 = _value1; + Raw.Value2 = _value2; + ScriptId = _scriptId; + } + + public bool IsValid(Criteria criteria) + { + if (DataType >= CriteriaDataType.Max) + { + Log.outError(LogFilter.Sql, "Table `criteria_data` for criteria (Entry: {0}) has wrong data type ({1}), ignored.", criteria.ID, DataType); + return false; + } + + switch (criteria.Entry.Type) + { + case CriteriaTypes.KillCreature: + case CriteriaTypes.KillCreatureType: + case CriteriaTypes.WinBg: + case CriteriaTypes.FallWithoutDying: + case CriteriaTypes.CompleteQuest: // only hardcoded list + case CriteriaTypes.CastSpell: + case CriteriaTypes.WinRatedArena: + case CriteriaTypes.DoEmote: + case CriteriaTypes.SpecialPvpKill: + case CriteriaTypes.WinDuel: + case CriteriaTypes.LootType: + case CriteriaTypes.CastSpell2: + case CriteriaTypes.BeSpellTarget: + case CriteriaTypes.BeSpellTarget2: + case CriteriaTypes.EquipEpicItem: + case CriteriaTypes.RollNeedOnLoot: + case CriteriaTypes.RollGreedOnLoot: + case CriteriaTypes.BgObjectiveCapture: + case CriteriaTypes.HonorableKill: + case CriteriaTypes.CompleteDailyQuest: // only Children's Week achievements + case CriteriaTypes.UseItem: // only Children's Week achievements + case CriteriaTypes.GetKillingBlows: + case CriteriaTypes.ReachLevel: + case CriteriaTypes.OnLogin: + case CriteriaTypes.LootEpicItem: + case CriteriaTypes.ReceiveEpicItem: + break; + default: + if (DataType != CriteriaDataType.Script) + { + Log.outError(LogFilter.Sql, "Table `criteria_data` has data for non-supported criteria type (Entry: {0} Type: {1}), ignored.", criteria.ID, (CriteriaTypes)criteria.Entry.Type); + return false; + } + break; + } + + switch (DataType) + { + case CriteriaDataType.None: + case CriteriaDataType.InstanceScript: + return true; + case CriteriaDataType.TCreature: + if (Creature.Id == 0 || Global.ObjectMgr.GetCreatureTemplate(Creature.Id) == null) + { + Log.outError(LogFilter.Sql, "Table `criteria_data` (Entry: {0} Type: {1}) for data type CRITERIA_DATA_TYPE_CREATURE ({2}) has non-existing creature id in value1 ({3}), ignored.", + criteria.ID, criteria.Entry.Type, DataType, Creature.Id); + return false; + } + return true; + case CriteriaDataType.TPlayerClassRace: + if (ClassRace.ClassId == 0 && ClassRace.RaceId == 0) + { + Log.outError(LogFilter.Sql, "Table `criteria_data` (Entry: {0} Type: {1}) for data type CRITERIA_DATA_TYPE_T_PLAYER_CLASS_RACE ({2}) must not have 0 in either value field, ignored.", + criteria.ID, criteria.Entry.Type, DataType); + return false; + } + if (ClassRace.ClassId != 0 && ((1 << (int)(ClassRace.ClassId - 1)) & (int)Class.ClassMaskAllPlayable) == 0) + { + Log.outError(LogFilter.Sql, "Table `criteria_data` (Entry: {0} Type: {1}) for data type CRITERIA_DATA_TYPE_T_PLAYER_CLASS_RACE ({2}) has non-existing class in value1 ({3}), ignored.", + criteria.ID, criteria.Entry.Type, DataType, ClassRace.ClassId); + return false; + } + if (ClassRace.RaceId != 0 && ((1 << (int)(ClassRace.RaceId - 1)) & (int)Race.RaceMaskAllPlayable) == 0) + { + Log.outError(LogFilter.Sql, "Table `criteria_data` (Entry: {0} Type: {1}) for data type CRITERIA_DATA_TYPE_T_PLAYER_CLASS_RACE ({2}) has non-existing race in value2 ({3}), ignored.", + criteria.ID, criteria.Entry.Type, DataType, ClassRace.RaceId); + return false; + } + return true; + case CriteriaDataType.TPlayerLessHealth: + if (Health.Percent < 1 || Health.Percent > 100) + { + Log.outError(LogFilter.Sql, "Table `criteria_data` (Entry: {0} Type: {1}) for data type CRITERIA_DATA_TYPE_PLAYER_LESS_HEALTH ({2}) has wrong percent value in value1 ({3}), ignored.", + criteria.ID, criteria.Entry.Type, DataType, Health.Percent); + return false; + } + return true; + case CriteriaDataType.SAura: + case CriteriaDataType.TAura: + { + SpellInfo spellEntry = Global.SpellMgr.GetSpellInfo(Aura.SpellId); + if (spellEntry == null) + { + Log.outError(LogFilter.Sql, "Table `criteria_data` (Entry: {0} Type: {1}) for data type {2} has wrong spell id in value1 ({3}), ignored.", + criteria.ID, criteria.Entry.Type, DataType, Aura.SpellId); + return false; + } + SpellEffectInfo effect = spellEntry.GetEffect(Difficulty.None, Aura.EffectIndex); + if (effect == null) + { + Log.outError(LogFilter.Sql, "Table `criteria_data` (Entry: {0} Type: {1}) for data type {2} has wrong spell effect index in value2 ({3}), ignored.", + criteria.ID, criteria.Entry.Type, DataType, Aura.EffectIndex); + return false; + } + if (effect.ApplyAuraName == 0) + { + Log.outError(LogFilter.Sql, "Table `criteria_data` (Entry: {0} Type: {1}) for data type {2} has non-aura spell effect (ID: {3} Effect: {4}), ignores.", + criteria.ID, criteria.Entry.Type, DataType, Aura.SpellId, Aura.EffectIndex); + return false; + } + return true; + } + case CriteriaDataType.Value: + if (Value.ComparisonType >= (int)ComparisionType.Max) + { + Log.outError(LogFilter.Sql, "Table `criteria_data` (Entry: {0} Type: {1}) for data type CRITERIA_DATA_TYPE_VALUE ({2}) has wrong ComparisionType in value2 ({3}), ignored.", + criteria.ID, criteria.Entry.Type, DataType, Value.ComparisonType); + return false; + } + return true; + case CriteriaDataType.TLevel: + if (Level.Min > SharedConst.GTMaxLevel) + { + Log.outError(LogFilter.Sql, "Table `criteria_data` (Entry: {0} Type: {1}) for data type CRITERIA_DATA_TYPE_T_LEVEL ({2}) has wrong minlevel in value1 ({3}), ignored.", + criteria.ID, criteria.Entry.Type, DataType, Level.Min); + return false; + } + return true; + case CriteriaDataType.TGender: + if (Gender.Gender > (int)Framework.Constants.Gender.None) + { + Log.outError(LogFilter.Sql, "Table `criteria_data` (Entry: {0} Type: {1}) for data type CRITERIA_DATA_TYPE_T_GENDER ({2}) has wrong gender in value1 ({3}), ignored.", + criteria.ID, criteria.Entry.Type, DataType, Gender.Gender); + return false; + } + return true; + case CriteriaDataType.Script: + if (ScriptId == 0) + { + Log.outError(LogFilter.Sql, "Table `criteria_data` (Entry: {0} Type: {1}) for data type CRITERIA_DATA_TYPE_SCRIPT ({2}) does not have ScriptName set, ignored.", + criteria.ID, criteria.Entry.Type, DataType); + return false; + } + return true; + case CriteriaDataType.MapPlayerCount: + if (MapPlayers.MaxCount <= 0) + { + Log.outError(LogFilter.Sql, "Table `criteria_data` (Entry: {0} Type: {1}) for data type CRITERIA_DATA_TYPE_MAP_PLAYER_COUNT ({2}) has wrong max players count in value1 ({3}), ignored.", + criteria.ID, criteria.Entry.Type, DataType, MapPlayers.MaxCount); + return false; + } + return true; + case CriteriaDataType.TTeam: + if (TeamId.Team != (int)Team.Alliance && TeamId.Team != (int)Team.Horde) + { + Log.outError(LogFilter.Sql, "Table `criteria_data` (Entry: {0} Type: {1}) for data type CRITERIA_DATA_TYPE_T_TEAM ({2}) has unknown team in value1 ({3}), ignored.", + criteria.ID, criteria.Entry.Type, DataType, TeamId.Team); + return false; + } + return true; + case CriteriaDataType.SDrunk: + if (Drunk.State >= 4) + { + Log.outError(LogFilter.Sql, "Table `criteria_data` (Entry: {0} Type: {1}) for data type CRITERIA_DATA_TYPE_S_DRUNK ({2}) has unknown drunken state in value1 ({3}), ignored.", + criteria.ID, criteria.Entry.Type, DataType, Drunk.State); + return false; + } + return true; + case CriteriaDataType.Holiday: + if (!CliDB.HolidaysStorage.ContainsKey(Holiday.Id)) + { + Log.outError(LogFilter.Sql, "Table `criteria_data`(Entry: {0} Type: {1}) for data type CRITERIA_DATA_TYPE_HOLIDAY ({2}) has unknown holiday in value1 ({3}), ignored.", + criteria.ID, criteria.Entry.Type, DataType, Holiday.Id); + return false; + } + return true; + case CriteriaDataType.GameEvent: + { + var events = Global.GameEventMgr.GetEventMap(); + if (GameEvent.Id < 1 || GameEvent.Id >= events.Length) + { + Log.outError(LogFilter.Sql, "Table `criteria_data` (Entry: {0} Type: {1}) for data type CRITERIA_DATA_TYPE_GAME_EVENT ({2}) has unknown game_event in value1 ({3}), ignored.", + criteria.ID, criteria.Entry.Type, DataType, GameEvent.Id); + return false; + } + return true; + } + case CriteriaDataType.BgLossTeamScore: + return true; // not check correctness node indexes + case CriteriaDataType.SEquippedItem: + if (EquippedItem.ItemQuality >= (uint)ItemQuality.Max) + { + Log.outError(LogFilter.Sql, "Table `achievement_criteria_requirement` (Entry: {0} Type: {1}) for requirement ACHIEVEMENT_CRITERIA_REQUIRE_S_EQUIPED_ITEM ({2}) has unknown quality state in value1 ({3}), ignored.", + criteria.ID, criteria.Entry.Type, DataType, EquippedItem.ItemQuality); + return false; + } + return true; + case CriteriaDataType.MapId: + if (!CliDB.MapStorage.ContainsKey(MapId.Id)) + { + Log.outError(LogFilter.Sql, "Table `criteria_data` (Entry: {0} Type: {1}) for data type CRITERIA_DATA_TYPE_MAP_ID ({2}) contains an unknown map entry in value1 ({3}), ignored.", + criteria.ID, criteria.Entry.Type, DataType, MapId.Id); + } + return true; + case CriteriaDataType.SPlayerClassRace: + if (ClassRace.ClassId == 0 && ClassRace.RaceId == 0) + { + Log.outError(LogFilter.Sql, "Table `criteria_data` (Entry: {0} Type: {1}) for data type CRITERIA_DATA_TYPE_S_PLAYER_CLASS_RACE ({2}) must not have 0 in either value field, ignored.", + criteria.ID, criteria.Entry.Type, DataType); + return false; + } + if (ClassRace.ClassId != 0 && ((1 << (int)(ClassRace.ClassId - 1)) & (int)Class.ClassMaskAllPlayable) == 0) + { + Log.outError(LogFilter.Sql, "Table `criteria_data` (Entry: {0} Type: {1}) for data type CRITERIA_DATA_TYPE_S_PLAYER_CLASS_RACE ({2}) has non-existing class in value1 ({3}), ignored.", + criteria.ID, criteria.Entry.Type, DataType, ClassRace.ClassId); + return false; + } + if (ClassRace.RaceId != 0 && ((1 << (int)(ClassRace.RaceId - 1)) & (int)Race.RaceMaskAllPlayable) == 0) + { + Log.outError(LogFilter.Sql, "Table `criteria_data` (Entry: {0} Type: {1}) for data type CRITERIA_DATA_TYPE_S_PLAYER_CLASS_RACE ({2}) has non-existing race in value2 ({3}), ignored.", + criteria.ID, criteria.Entry.Type, DataType, ClassRace.RaceId); + return false; + } + return true; + case CriteriaDataType.SKnownTitle: + if (!CliDB.CharTitlesStorage.ContainsKey(KnownTitle.Id)) + { + Log.outError(LogFilter.Sql, "Table `criteria_data` (Entry: {0} Type: {1}) for data type CRITERIA_DATA_TYPE_S_KNOWN_TITLE ({2}) contains an unknown title_id in value1 ({3}), ignore.", + criteria.ID, criteria.Entry.Type, DataType, KnownTitle.Id); + return false; + } + return true; + case CriteriaDataType.SItemQuality: + if (itemQuality.Quality >= (uint)ItemQuality.Max) + { + Log.outError(LogFilter.Sql, "Table `criteria_data` (Entry: {0} Type: {1}) for data type CRITERIA_DATA_TYPE_S_ITEM_QUALITY ({2}) contains an unknown quality state value in value1 ({3}), ignored.", + criteria.ID, criteria.Entry.Type, DataType, itemQuality.Quality); + return false; + } + return true; + default: + Log.outError(LogFilter.Sql, "Table `criteria_data` (Entry: {0} Type: {1}) contains data of a non-supported data type ({2}), ignored.", criteria.ID, criteria.Entry.Type, DataType); + return false; + } + } + + public bool Meets(uint criteria_id, Player source, Unit target, uint miscValue1 = 0) + { + switch (DataType) + { + case CriteriaDataType.None: + return true; + case CriteriaDataType.TCreature: + if (target == null || !target.IsTypeId(TypeId.Unit)) + return false; + return target.GetEntry() == Creature.Id; + case CriteriaDataType.TPlayerClassRace: + if (target == null || !target.IsTypeId(TypeId.Player)) + return false; + if (ClassRace.ClassId != 0 && ClassRace.ClassId != (uint)target.ToPlayer().GetClass()) + return false; + if (ClassRace.RaceId != 0 && ClassRace.RaceId != (uint)target.ToPlayer().GetRace()) + return false; + return true; + case CriteriaDataType.SPlayerClassRace: + if (source == null || !source.IsTypeId(TypeId.Player)) + return false; + if (ClassRace.ClassId != 0 && ClassRace.ClassId != (uint)source.ToPlayer().GetClass()) + return false; + if (ClassRace.RaceId != 0 && ClassRace.RaceId != (uint)source.ToPlayer().GetRace()) + return false; + return true; + case CriteriaDataType.TPlayerLessHealth: + if (target == null || !target.IsTypeId(TypeId.Player)) + return false; + return !target.HealthAbovePct((int)Health.Percent); + case CriteriaDataType.SAura: + return source.HasAuraEffect(Aura.SpellId, (byte)Aura.EffectIndex); + case CriteriaDataType.TAura: + return target != null && target.HasAuraEffect(Aura.SpellId, (byte)Aura.EffectIndex); + case CriteriaDataType.Value: + return MathFunctions.CompareValues((ComparisionType)Value.ComparisonType, miscValue1, Value.Value); + case CriteriaDataType.TLevel: + if (target == null) + return false; + return target.getLevel() >= Level.Min; + case CriteriaDataType.TGender: + if (target == null) + return false; + return (uint)target.GetGender() == Gender.Gender; + case CriteriaDataType.Script: + return Global.ScriptMgr.OnCriteriaCheck(ScriptId, source, target); + case CriteriaDataType.MapPlayerCount: + return source.GetMap().GetPlayersCountExceptGMs() <= MapPlayers.MaxCount; + case CriteriaDataType.TTeam: + if (target == null || !target.IsTypeId(TypeId.Player)) + return false; + return (uint)target.ToPlayer().GetTeam() == TeamId.Team; + case CriteriaDataType.SDrunk: + return Player.GetDrunkenstateByValue(source.GetDrunkValue()) >= (DrunkenState)Drunk.State; + case CriteriaDataType.Holiday: + return Global.GameEventMgr.IsHolidayActive((HolidayIds)Holiday.Id); + case CriteriaDataType.GameEvent: + return Global.GameEventMgr.IsEventActive((ushort)GameEvent.Id); + case CriteriaDataType.BgLossTeamScore: + { + Battleground bg = source.GetBattleground(); + if (!bg) + return false; + + int score = (int)bg.GetTeamScore(source.GetTeam() == Team.Alliance ? Framework.Constants.TeamId.Horde : Framework.Constants.TeamId.Alliance); + return score >= BattlegroundScore.Min && score <= BattlegroundScore.Max; + } + case CriteriaDataType.InstanceScript: + { + if (!source.IsInWorld) + return false; + Map map = source.GetMap(); + if (!map.IsDungeon()) + { + Log.outError(LogFilter.Achievement, "Achievement system call AchievementCriteriaDataType.InstanceScript ({0}) for achievement criteria {1} for non-dungeon/non-raid map {2}", + CriteriaDataType.InstanceScript, criteria_id, map.GetId()); + return false; + } + InstanceScript instance = ((InstanceMap)map).GetInstanceScript(); + if (instance == null) + { + Log.outError(LogFilter.Achievement, "Achievement system call criteria_data_INSTANCE_SCRIPT ({0}) for achievement criteria {1} for map {2} but map does not have a instance script", + CriteriaDataType.InstanceScript, criteria_id, map.GetId()); + return false; + } + return instance.CheckAchievementCriteriaMeet(criteria_id, source, target, miscValue1); + } + case CriteriaDataType.SEquippedItem: + { + ItemTemplate pProto = Global.ObjectMgr.GetItemTemplate(miscValue1); + if (pProto == null) + return false; + return pProto.GetBaseItemLevel() >= EquippedItem.ItemLevel && (int)pProto.GetQuality() >= EquippedItem.ItemQuality; + } + case CriteriaDataType.MapId: + return source.GetMapId() == MapId.Id; + case CriteriaDataType.SKnownTitle: + { + CharTitlesRecord titleInfo = CliDB.CharTitlesStorage.LookupByKey(KnownTitle.Id); + if (titleInfo != null) + return source && source.HasTitle(titleInfo.MaskID); + + return false; + } + case CriteriaDataType.SItemQuality: + { + ItemTemplate pProto = Global.ObjectMgr.GetItemTemplate(miscValue1); + if (pProto == null) + return false; + return (uint)pProto.GetQuality() == itemQuality.Quality; + } + default: + break; + } + return false; + } + + [FieldOffset(0)] + public CriteriaDataType DataType; + + [FieldOffset(4)] + public CreatureStruct Creature; + + [FieldOffset(4)] + public ClassRaceStruct ClassRace; + + [FieldOffset(4)] + public HealthStruct Health; + + [FieldOffset(4)] + public AuraStruct Aura; + + [FieldOffset(4)] + public ValueStruct Value; + + [FieldOffset(4)] + public LevelStruct Level; + + [FieldOffset(4)] + public GenderStruct Gender; + + [FieldOffset(4)] + public MapPlayersStruct MapPlayers; + + [FieldOffset(4)] + public TeamStruct TeamId; + + [FieldOffset(4)] + public DrunkStruct Drunk; + + [FieldOffset(4)] + public HolidayStruct Holiday; + + [FieldOffset(4)] + public BgLossTeamScoreStruct BattlegroundScore; + + [FieldOffset(4)] + public EquippedItemStruct EquippedItem; + + [FieldOffset(4)] + public MapIdStruct MapId; + + [FieldOffset(4)] + public KnownTitleStruct KnownTitle; + + [FieldOffset(4)] + public GameEventStruct GameEvent; + + [FieldOffset(4)] + public ItemQualityStruct itemQuality; + + [FieldOffset(4)] + public RawStruct Raw; + + [FieldOffset(12)] + public uint ScriptId; + + #region Structs + // criteria_data_TYPE_NONE = 0 (no data) + // criteria_data_TYPE_T_CREATURE = 1 + public struct CreatureStruct + { + public uint Id; + } + // criteria_data_TYPE_T_PLAYER_CLASS_RACE = 2 + // criteria_data_TYPE_S_PLAYER_CLASS_RACE = 21 + public struct ClassRaceStruct + { + public uint ClassId; + public uint RaceId; + } + // criteria_data_TYPE_T_PLAYER_LESS_HEALTH = 3 + public struct HealthStruct + { + public uint Percent; + } + // criteria_data_TYPE_S_AURA = 5 + // criteria_data_TYPE_T_AURA = 7 + public struct AuraStruct + { + public uint SpellId; + public uint EffectIndex; + } + // criteria_data_TYPE_VALUE = 8 + public struct ValueStruct + { + public uint Value; + public uint ComparisonType; + } + // criteria_data_TYPE_T_LEVEL = 9 + public struct LevelStruct + { + public uint Min; + } + // criteria_data_TYPE_T_GENDER = 10 + public struct GenderStruct + { + public uint Gender; + } + // criteria_data_TYPE_SCRIPT = 11 (no data) + // criteria_data_TYPE_MAP_PLAYER_COUNT = 13 + public struct MapPlayersStruct + { + public uint MaxCount; + } + // criteria_data_TYPE_T_TEAM = 14 + public struct TeamStruct + { + public uint Team; + } + // criteria_data_TYPE_S_DRUNK = 15 + public struct DrunkStruct + { + public uint State; + } + // criteria_data_TYPE_HOLIDAY = 16 + public struct HolidayStruct + { + public uint Id; + } + // criteria_data_TYPE_BG_LOSS_TEAM_SCORE= 17 + public struct BgLossTeamScoreStruct + { + public uint Min; + public uint Max; + } + // criteria_data_INSTANCE_SCRIPT = 18 (no data) + // criteria_data_TYPE_S_EQUIPED_ITEM = 19 + public struct EquippedItemStruct + { + public uint ItemLevel; + public uint ItemQuality; + } + // criteria_data_TYPE_MAP_ID = 20 + public struct MapIdStruct + { + public uint Id; + } + // criteria_data_TYPE_KNOWN_TITLE = 23 + public struct KnownTitleStruct + { + public uint Id; + } + // CRITERIA_DATA_TYPE_S_ITEM_QUALITY = 24 + public struct ItemQualityStruct + { + public uint Quality; + } + // criteria_data_TYPE_GAME_EVENT = 25 + public struct GameEventStruct + { + public uint Id; + } + // raw + public struct RawStruct + { + public uint Value1; + public uint Value2; + } + #endregion + } + + public class CriteriaDataSet + { + public void Add(CriteriaData data) { storage.Add(data); } + + public bool Meets(Player source, Unit target, uint miscValue = 0) + { + foreach (var data in storage) + if (!data.Meets(criteria_id, source, target, miscValue)) + return false; + + return true; + } + + public void SetCriteriaId(uint id) { criteria_id = id; } + + uint criteria_id; + List storage = new List(); + } +} diff --git a/Game/Arenas/Arena.cs b/Game/Arenas/Arena.cs new file mode 100644 index 000000000..d8744b24b --- /dev/null +++ b/Game/Arenas/Arena.cs @@ -0,0 +1,293 @@ +/* + * Copyright (C) 2012-2017 CypherCore + * + * 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, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +using Framework.Constants; +using Game.BattleGrounds; +using Game.Entities; +using Game.Guilds; +using Game.Network.Packets; +using System; +using System.Collections.Generic; + +namespace Game.Arenas +{ + public class Arena : Battleground + { + public Arena() + { + StartDelayTimes[BattlegroundConst.EventIdFirst] = BattlegroundStartTimeIntervals.Delay1m; + StartDelayTimes[BattlegroundConst.EventIdSecond] = BattlegroundStartTimeIntervals.Delay30s; + StartDelayTimes[BattlegroundConst.EventIdThird] = BattlegroundStartTimeIntervals.Delay15s; + StartDelayTimes[BattlegroundConst.EventIdFourth] = BattlegroundStartTimeIntervals.None; + + StartMessageIds[BattlegroundConst.EventIdFirst] = CypherStrings.ArenaOneMinute; + StartMessageIds[BattlegroundConst.EventIdSecond] = CypherStrings.ArenaThirtySeconds; + StartMessageIds[BattlegroundConst.EventIdThird] = CypherStrings.ArenaFifteenSeconds; + StartMessageIds[BattlegroundConst.EventIdFourth] = CypherStrings.ArenaHasBegun; + } + + public override void AddPlayer(Player player) + { + base.AddPlayer(player); + PlayerScores[player.GetGUID()] = new ArenaScore(player.GetGUID(), player.GetBGTeam()); + + if (player.GetBGTeam() == Team.Alliance) // gold + { + if (player.GetTeam() == Team.Horde) + player.CastSpell(player, ArenaSpellIds.HordeGoldFlag, true); + else + player.CastSpell(player, ArenaSpellIds.AllianceGoldFlag, true); + } + else // green + { + if (player.GetTeam() == Team.Horde) + player.CastSpell(player, ArenaSpellIds.HordeGreenFlag, true); + else + player.CastSpell(player, ArenaSpellIds.AllianceGreenFlag, true); + } + + UpdateArenaWorldState(); + } + + public override void RemovePlayer(Player player, ObjectGuid guid, Team team) + { + if (GetStatus() == BattlegroundStatus.WaitLeave) + return; + + UpdateArenaWorldState(); + CheckWinConditions(); + } + + public override void FillInitialWorldStates(InitWorldStates packet) + { + packet.AddState(ArenaWorldStates.AlivePlayersGreen, (int)GetAlivePlayersCountByTeam(Team.Horde)); + packet.AddState(ArenaWorldStates.AlivePlayersGold, (int)GetAlivePlayersCountByTeam(Team.Alliance)); + } + + void UpdateArenaWorldState() + { + UpdateWorldState(ArenaWorldStates.AlivePlayersGreen, GetAlivePlayersCountByTeam(Team.Horde)); + UpdateWorldState(ArenaWorldStates.AlivePlayersGold, GetAlivePlayersCountByTeam(Team.Alliance)); + } + + public override void HandleKillPlayer(Player victim, Player killer) + { + if (GetStatus() != BattlegroundStatus.InProgress) + return; + + base.HandleKillPlayer(victim, killer); + + UpdateArenaWorldState(); + CheckWinConditions(); + } + + public override void RemovePlayerAtLeave(ObjectGuid guid, bool Transport, bool SendPacket) + { + if (isRated() && GetStatus() == BattlegroundStatus.InProgress) + { + var bgPlayer = GetPlayers().LookupByKey(guid); + if (bgPlayer != null) // check if the player was a participant of the match, or only entered through gm command (appear) + { + // if the player was a match participant, calculate rating + + ArenaTeam winnerArenaTeam = Global.ArenaTeamMgr.GetArenaTeamById(GetArenaTeamIdForTeam(GetOtherTeam(bgPlayer.Team))); + ArenaTeam loserArenaTeam = Global.ArenaTeamMgr.GetArenaTeamById(GetArenaTeamIdForTeam(bgPlayer.Team)); + + // left a rated match while the encounter was in progress, consider as loser + if (winnerArenaTeam != null && loserArenaTeam != null && winnerArenaTeam != loserArenaTeam) + { + Player player = _GetPlayer(guid, bgPlayer.OfflineRemoveTime != 0, "Arena.RemovePlayerAtLeave"); + if (player) + loserArenaTeam.MemberLost(player, GetArenaMatchmakerRating(GetOtherTeam(bgPlayer.Team))); + else + loserArenaTeam.OfflineMemberLost(guid, GetArenaMatchmakerRating(GetOtherTeam(bgPlayer.Team))); + } + } + } + + // remove player + base.RemovePlayerAtLeave(guid, Transport, SendPacket); + } + + public override void CheckWinConditions() + { + if (GetAlivePlayersCountByTeam(Team.Alliance) == 0 && GetPlayersCountByTeam(Team.Horde) != 0) + EndBattleground(Team.Horde); + else if (GetPlayersCountByTeam(Team.Alliance) != 0 && GetAlivePlayersCountByTeam(Team.Horde) == 0) + EndBattleground(Team.Alliance); + } + + public override void EndBattleground(Team winner) + { + // arena rating calculation + if (isRated()) + { + uint loserTeamRating = 0; + uint loserMatchmakerRating = 0; + int loserChange = 0; + int loserMatchmakerChange = 0; + uint winnerTeamRating = 0; + uint winnerMatchmakerRating = 0; + int winnerChange = 0; + int winnerMatchmakerChange = 0; + bool guildAwarded = false; + + // In case of arena draw, follow this logic: + // winnerArenaTeam => ALLIANCE, loserArenaTeam => HORDE + ArenaTeam winnerArenaTeam = Global.ArenaTeamMgr.GetArenaTeamById(GetArenaTeamIdForTeam(winner == 0 ? Team.Alliance : winner)); + ArenaTeam loserArenaTeam = Global.ArenaTeamMgr.GetArenaTeamById(GetArenaTeamIdForTeam(winner == 0 ? Team.Horde : GetOtherTeam(winner))); + + if (winnerArenaTeam != null && loserArenaTeam != null && winnerArenaTeam != loserArenaTeam) + { + // In case of arena draw, follow this logic: + // winnerMatchmakerRating => ALLIANCE, loserMatchmakerRating => HORDE + loserTeamRating = loserArenaTeam.GetRating(); + loserMatchmakerRating = GetArenaMatchmakerRating(winner == 0 ? Team.Horde : GetOtherTeam(winner)); + winnerTeamRating = winnerArenaTeam.GetRating(); + winnerMatchmakerRating = GetArenaMatchmakerRating(winner == 0 ? Team.Alliance : winner); + + if (winner != 0) + { + winnerMatchmakerChange = winnerArenaTeam.WonAgainst(winnerMatchmakerRating, loserMatchmakerRating, winnerChange); + loserMatchmakerChange = loserArenaTeam.LostAgainst(loserMatchmakerRating, winnerMatchmakerRating, loserChange); + + Log.outDebug(LogFilter.Arena, "match Type: {0} --- Winner: old rating: {1}, rating gain: {2}, old MMR: {3}, MMR gain: {4} --- Loser: old rating: {5}, " + + "rating loss: {6}, old MMR: {7}, MMR loss: {8} ---", GetArenaType(), winnerTeamRating, winnerChange, winnerMatchmakerRating, winnerMatchmakerChange, + loserTeamRating, loserChange, loserMatchmakerRating, loserMatchmakerChange); + + SetArenaMatchmakerRating(winner, (uint)(winnerMatchmakerRating + winnerMatchmakerChange)); + SetArenaMatchmakerRating(GetOtherTeam(winner), (uint)(loserMatchmakerRating + loserMatchmakerChange)); + + // bg team that the client expects is different to TeamId + // alliance 1, horde 0 + byte winnerTeam = (byte)(winner == Team.Alliance ? BattlegroundTeamId.Alliance : BattlegroundTeamId.Horde); + byte loserTeam = (byte)(winner == Team.Alliance ? BattlegroundTeamId.Horde : BattlegroundTeamId.Alliance); + + _arenaTeamScores[winnerTeam].Assign((int)winnerTeamRating, (int)(winnerTeamRating + winnerChange), winnerMatchmakerRating); + _arenaTeamScores[loserTeam].Assign((int)loserTeamRating, (int)(loserTeamRating + loserChange), loserMatchmakerRating); + + Log.outDebug(LogFilter.Arena, "Arena match Type: {0} for Team1Id: {1} - Team2Id: {2} ended. WinnerTeamId: {3}. Winner rating: +{4}, Loser rating: {5}", + GetArenaType(), GetArenaTeamIdByIndex(TeamId.Alliance), GetArenaTeamIdByIndex(TeamId.Horde), winnerArenaTeam.GetId(), winnerChange, loserChange); + + if (WorldConfig.GetBoolValue(WorldCfg.ArenaLogExtendedInfo)) + { + foreach (var score in PlayerScores) + { + Player player = Global.ObjAccessor.FindPlayer(score.Key); + if (player) + { + Log.outDebug(LogFilter.Arena, "Statistics match Type: {0} for {1} (GUID: {2}, Team: {3}, IP: {4}): {5}", + GetArenaType(), player.GetName(), score.Key, player.GetArenaTeamId((byte)(GetArenaType() == ArenaTypes.Team5v5 ? 2 : (GetArenaType() == ArenaTypes.Team3v3 ? 1 : 0))), + player.GetSession().GetRemoteAddress(), score.Value.ToString()); + } + } + } + } + // Deduct 16 points from each teams arena-rating if there are no winners after 45+2 minutes + else + { + _arenaTeamScores[(int)BattlegroundTeamId.Alliance].Assign((int)winnerTeamRating, (int)(winnerTeamRating + SharedConst.ArenaTimeLimitPointsLoss), winnerMatchmakerRating); + _arenaTeamScores[(int)BattlegroundTeamId.Horde].Assign((int)loserTeamRating, (int)(loserTeamRating + SharedConst.ArenaTimeLimitPointsLoss), loserMatchmakerRating); + + + winnerArenaTeam.FinishGame(SharedConst.ArenaTimeLimitPointsLoss); + loserArenaTeam.FinishGame(SharedConst.ArenaTimeLimitPointsLoss); + } + + uint aliveWinners = GetAlivePlayersCountByTeam(winner); + foreach (var pair in GetPlayers()) + { + Team team = pair.Value.Team; + + if (pair.Value.OfflineRemoveTime != 0) + { + // if rated arena match - make member lost! + if (team == winner) + winnerArenaTeam.OfflineMemberLost(pair.Key, loserMatchmakerRating, winnerMatchmakerChange); + else + { + if (winner == 0) + winnerArenaTeam.OfflineMemberLost(pair.Key, loserMatchmakerRating, winnerMatchmakerChange); + + loserArenaTeam.OfflineMemberLost(pair.Key, winnerMatchmakerRating, loserMatchmakerChange); + } + continue; + } + + Player player = _GetPlayer(pair.Key, pair.Value.OfflineRemoveTime != 0, "Arena.EndBattleground"); + if (!player) + continue; + + // per player calculation + if (team == winner) + { + // update achievement BEFORE personal rating update + uint rating = player.GetArenaPersonalRating(winnerArenaTeam.GetSlot()); + player.UpdateCriteria(CriteriaTypes.WinRatedArena, rating != 0 ? rating : 1); + player.UpdateCriteria(CriteriaTypes.WinRatedArena, GetMapId()); + + // Last standing - Rated 5v5 arena & be solely alive player + if (GetArenaType() == ArenaTypes.Team5v5 && aliveWinners == 1 && player.IsAlive()) + player.CastSpell(player, ArenaSpellIds.LastManStanding, true); + + if (!guildAwarded) + { + guildAwarded = true; + ulong guildId = GetBgMap().GetOwnerGuildId(player.GetBGTeam()); + if (guildId != 0) + { + Guild guild = Global.GuildMgr.GetGuildById(guildId); + if (guild) + guild.UpdateCriteria(CriteriaTypes.WinRatedArena, Math.Max(winnerArenaTeam.GetRating(), 1), 0, 0, null, player); + } + } + + winnerArenaTeam.MemberWon(player, loserMatchmakerRating, winnerMatchmakerChange); + } + else + { + if (winner == 0) + winnerArenaTeam.MemberLost(player, loserMatchmakerRating, winnerMatchmakerChange); + + loserArenaTeam.MemberLost(player, winnerMatchmakerRating, loserMatchmakerChange); + + // Arena lost => reset the win_rated_arena having the "no_lose" condition + player.ResetCriteria(CriteriaTypes.WinRatedArena, (uint)CriteriaCondition.NoLose); + } + } + + // save the stat changes + winnerArenaTeam.SaveToDB(); + loserArenaTeam.SaveToDB(); + // send updated arena team stats to players + // this way all arena team members will get notified, not only the ones who participated in this match + winnerArenaTeam.NotifyStatsChanged(); + loserArenaTeam.NotifyStatsChanged(); + } + } + + // end Battleground + base.EndBattleground(winner); + } + } + + struct ArenaWorldStates + { + public const uint AlivePlayersGreen = 3600; + public const uint AlivePlayersGold = 3601; + } +} diff --git a/Game/Arenas/ArenaScore.cs b/Game/Arenas/ArenaScore.cs new file mode 100644 index 000000000..77f64b6ff --- /dev/null +++ b/Game/Arenas/ArenaScore.cs @@ -0,0 +1,61 @@ +/* + * Copyright (C) 2012-2017 CypherCore + * + * 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, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +using Framework.Constants; +using Game.BattleGrounds; +using Game.Entities; +using System.Collections.Generic; + +namespace Game.Arenas +{ + class ArenaScore : BattlegroundScore + { + public ArenaScore(ObjectGuid playerGuid, Team team) : base(playerGuid, team) + { + TeamId = (int)(team == Team.Alliance ? BattlegroundTeamId.Alliance : BattlegroundTeamId.Horde); + } + + public override void BuildObjectivesBlock(List stats) { } + + // For Logging purpose + public override string ToString() + { + return string.Format("Damage done: {0}, Healing done: {1}, Killing blows: {2}", DamageDone, HealingDone, KillingBlows); + } + } + + public class ArenaTeamScore + { + void Reset() + { + OldRating = 0; + NewRating = 0; + MatchmakerRating = 0; + } + + public void Assign(int oldRating, int newRating, uint matchMakerRating) + { + OldRating = oldRating; + NewRating = newRating; + MatchmakerRating = matchMakerRating; + } + + public int OldRating; + public int NewRating; + public uint MatchmakerRating; + } +} diff --git a/Game/Arenas/ArenaTeam.cs b/Game/Arenas/ArenaTeam.cs new file mode 100644 index 000000000..7f30fc5e0 --- /dev/null +++ b/Game/Arenas/ArenaTeam.cs @@ -0,0 +1,861 @@ +/* + * Copyright (C) 2012-2017 CypherCore + * + * 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, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +using Framework.Constants; +using Framework.Database; +using Game.Entities; +using Game.Groups; +using Game.Network; +using System; +using System.Collections.Generic; +using System.Linq; + +namespace Game.Arenas +{ + public class ArenaTeam + { + public ArenaTeam() + { + stats.Rating = (ushort)WorldConfig.GetIntValue(WorldCfg.ArenaStartRating); + } + + public bool Create(ObjectGuid captainGuid, byte _type, string arenaTeamName, uint backgroundColor, byte emblemStyle, uint emblemColor, byte borderStyle, uint borderColor) + { + // Check if captain is present + if (!Global.ObjAccessor.FindPlayer(captainGuid)) + return false; + + // Check if arena team name is already taken + if (Global.ArenaTeamMgr.GetArenaTeamByName(arenaTeamName) != null) + return false; + + // Generate new arena team id + teamId = Global.ArenaTeamMgr.GenerateArenaTeamId(); + + // Assign member variables + CaptainGuid = captainGuid; + type = _type; + TeamName = arenaTeamName; + BackgroundColor = backgroundColor; + EmblemStyle = emblemStyle; + EmblemColor = emblemColor; + BorderStyle = borderStyle; + BorderColor = borderColor; + ulong captainLowGuid = captainGuid.GetCounter(); + + // Save arena team to db + PreparedStatement stmt = DB.Characters.GetPreparedStatement(CharStatements.INS_ARENA_TEAM); + stmt.AddValue(0, teamId); + stmt.AddValue(1, TeamName); + stmt.AddValue(2, captainLowGuid); + stmt.AddValue(3, type); + stmt.AddValue(4, stats.Rating); + stmt.AddValue(5, BackgroundColor); + stmt.AddValue(6, EmblemStyle); + stmt.AddValue(7, EmblemColor); + stmt.AddValue(8, BorderStyle); + stmt.AddValue(9, BorderColor); + DB.Characters.Execute(stmt); + + // Add captain as member + AddMember(CaptainGuid); + + Log.outDebug(LogFilter.Arena, "New ArenaTeam created Id: {0}, Name: {1} Type: {2} Captain low GUID: {3}", GetId(), GetName(), GetArenaType(), captainLowGuid); + return true; + } + + public bool AddMember(ObjectGuid playerGuid) + { + string playerName; + Class playerClass; + + // Check if arena team is full (Can't have more than type * 2 players) + if (GetMembersSize() >= GetArenaType() * 2) + return false; + + // Get player name and class either from db or ObjectMgr + CharacterInfo characterInfo; + Player player = Global.ObjAccessor.FindPlayer(playerGuid); + if (player) + { + playerClass = player.GetClass(); + playerName = player.GetName(); + } + else if ((characterInfo = Global.WorldMgr.GetCharacterInfo(playerGuid)) != null) + { + playerName = characterInfo.Name; + playerClass = characterInfo.ClassID; + } + else + return false; + + // Check if player is already in a similar arena team + if ((player && player.GetArenaTeamId(GetSlot()) != 0) || Player.GetArenaTeamIdFromDB(playerGuid, GetArenaType()) != 0) + { + Log.outDebug(LogFilter.Arena, "Arena: {0} {1} already has an arena team of type {2}", playerGuid.ToString(), playerName, GetArenaType()); + return false; + } + + // Set player's personal rating + uint personalRating = 0; + + if (WorldConfig.GetIntValue(WorldCfg.ArenaStartPersonalRating) > 0) + personalRating = WorldConfig.GetUIntValue(WorldCfg.ArenaStartPersonalRating); + else if (GetRating() >= 1000) + personalRating = 1000; + + // Try to get player's match maker rating from db and fall back to config setting if not found + PreparedStatement stmt = DB.Characters.GetPreparedStatement(CharStatements.SEL_MATCH_MAKER_RATING); + stmt.AddValue(0, playerGuid.GetCounter()); + stmt.AddValue(1, GetSlot()); + SQLResult result = DB.Characters.Query(stmt); + + uint matchMakerRating; + if (!result.IsEmpty()) + matchMakerRating = result.Read(0); + else + matchMakerRating = WorldConfig.GetUIntValue(WorldCfg.ArenaStartMatchmakerRating); + + // Remove all player signatures from other petitions + // This will prevent player from joining too many arena teams and corrupt arena team data integrity + //Player.RemovePetitionsAndSigns(playerGuid, GetArenaType()); + + // Feed data to the struct + ArenaTeamMember newMember = new ArenaTeamMember(); + newMember.Name = playerName; + newMember.Guid = playerGuid; + newMember.Class = (byte)playerClass; + newMember.SeasonGames = 0; + newMember.WeekGames = 0; + newMember.SeasonWins = 0; + newMember.WeekWins = 0; + newMember.PersonalRating = (ushort)(uint)0; + newMember.MatchMakerRating = (ushort)matchMakerRating; + + Members.Add(newMember); + + // Save player's arena team membership to db + stmt = DB.Characters.GetPreparedStatement(CharStatements.INS_ARENA_TEAM_MEMBER); + stmt.AddValue(0, teamId); + stmt.AddValue(1, playerGuid.GetCounter()); + DB.Characters.Execute(stmt); + + // Inform player if online + if (player) + { + player.SetInArenaTeam(teamId, GetSlot(), GetArenaType()); + player.SetArenaTeamIdInvited(0); + + // Hide promote/remove buttons + if (CaptainGuid != playerGuid) + player.SetArenaTeamInfoField(GetSlot(), ArenaTeamInfoType.Member, 1); + } + + Log.outDebug(LogFilter.Arena, "Player: {0} [{1}] joined arena team type: {2} [Id: {3}, Name: {4}].", playerName, playerGuid.ToString(), GetArenaType(), GetId(), GetName()); + + return true; + } + + public bool LoadArenaTeamFromDB(SQLResult result) + { + if (result.IsEmpty()) + return false; + + teamId = result.Read(0); + TeamName = result.Read(1); + CaptainGuid = ObjectGuid.Create(HighGuid.Player, result.Read(2)); + type = result.Read(3); + BackgroundColor = result.Read(4); + EmblemStyle = result.Read(5); + EmblemColor = result.Read(6); + BorderStyle = result.Read(7); + BorderColor = result.Read(8); + stats.Rating = result.Read(9); + stats.WeekGames = result.Read(10); + stats.WeekWins = result.Read(11); + stats.SeasonGames = result.Read(12); + stats.SeasonWins = result.Read(13); + stats.Rank = result.Read(14); + + return true; + } + + public bool LoadMembersFromDB(SQLResult result) + { + if (result.IsEmpty()) + return false; + + bool captainPresentInTeam = false; + + do + { + uint arenaTeamId = result.Read(0); + + // We loaded all members for this arena_team already, break cycle + if (arenaTeamId > teamId) + break; + + ArenaTeamMember newMember = new ArenaTeamMember(); + newMember.Guid = ObjectGuid.Create(HighGuid.Player, result.Read(1)); + newMember.WeekGames = result.Read(2); + newMember.WeekWins = result.Read(3); + newMember.SeasonGames = result.Read(4); + newMember.SeasonWins = result.Read(5); + newMember.Name = result.Read(6); + newMember.Class = result.Read(7); + newMember.PersonalRating = result.Read(8); + newMember.MatchMakerRating = (ushort)(result.Read(9) > 0 ? result.Read(9) : 1500); + + // Delete member if character information is missing + if (string.IsNullOrEmpty(newMember.Name)) + { + Log.outError(LogFilter.Sql, "ArenaTeam {0} has member with empty name - probably {1} doesn't exist, deleting him from memberlist!", arenaTeamId, newMember.Guid.ToString()); + DelMember(newMember.Guid, true); + continue; + } + + // Check if team team has a valid captain + if (newMember.Guid == GetCaptain()) + captainPresentInTeam = true; + + // Put the player in the team + Members.Add(newMember); + } + while (result.NextRow()); + + if (Empty() || !captainPresentInTeam) + { + // Arena team is empty or captain is not in team, delete from db + Log.outDebug(LogFilter.Arena, "ArenaTeam {0} does not have any members or its captain is not in team, disbanding it...", teamId); + return false; + } + + return true; + } + + public bool SetName(string name) + { + if (TeamName == name || string.IsNullOrEmpty(name) || name.Length > 24 || Global.ObjectMgr.IsReservedName(name) || !ObjectManager.IsValidCharterName(name)) + return false; + + TeamName = name; + PreparedStatement stmt = DB.Characters.GetPreparedStatement(CharStatements.UPD_ARENA_TEAM_NAME); + stmt.AddValue(0, TeamName); + stmt.AddValue(1, GetId()); + DB.Characters.Execute(stmt); + return true; + } + + public void SetCaptain(ObjectGuid guid) + { + // Disable remove/promote buttons + Player oldCaptain = Global.ObjAccessor.FindPlayer(GetCaptain()); + if (oldCaptain) + oldCaptain.SetArenaTeamInfoField(GetSlot(), ArenaTeamInfoType.Member, 1); + + // Set new captain + CaptainGuid = guid; + + // Update database + PreparedStatement stmt = DB.Characters.GetPreparedStatement(CharStatements.UPD_ARENA_TEAM_CAPTAIN); + stmt.AddValue(0, guid.GetCounter()); + stmt.AddValue(1, GetId()); + DB.Characters.Execute(stmt); + + // Enable remove/promote buttons + Player newCaptain = Global.ObjAccessor.FindPlayer(guid); + if (newCaptain) + { + newCaptain.SetArenaTeamInfoField(GetSlot(), ArenaTeamInfoType.Member, 0); + if (oldCaptain) + { + Log.outDebug(LogFilter.Arena, "Player: {0} [GUID: {1}] promoted player: {2} [GUID: {3}] to leader of arena team [Id: {4}, Name: {5}] [Type: {6}].", + oldCaptain.GetName(), oldCaptain.GetGUID().ToString(), newCaptain.GetName(), + newCaptain.GetGUID().ToString(), GetId(), GetName(), GetArenaType()); + } + } + } + + public void DelMember(ObjectGuid guid, bool cleanDb) + { + // Remove member from team + foreach (var member in Members) + if (member.Guid == guid) + { + Members.Remove(member); + break; + } + + // Remove arena team info from player data + Player player = Global.ObjAccessor.FindPlayer(guid); + if (player) + { + // delete all info regarding this team + for (uint i = 0; i < (int)ArenaTeamInfoType.End; ++i) + player.SetArenaTeamInfoField(GetSlot(), (ArenaTeamInfoType)i, 0); + Log.outDebug(LogFilter.Arena, "Player: {0} [GUID: {1}] left arena team type: {2} [Id: {3}, Name: {4}].", player.GetName(), player.GetGUID().ToString(), GetArenaType(), GetId(), GetName()); + } + + // Only used for single member deletion, for arena team disband we use a single query for more efficiency + if (cleanDb) + { + PreparedStatement stmt = DB.Characters.GetPreparedStatement(CharStatements.DEL_ARENA_TEAM_MEMBER); + stmt.AddValue(0, GetId()); + stmt.AddValue(1, guid.GetCounter()); + DB.Characters.Execute(stmt); + } + } + + public void Disband(WorldSession session) + { + // Broadcast update + if (session != null) + { + Player player = session.GetPlayer(); + if (player) + Log.outDebug(LogFilter.Arena, "Player: {0} [GUID: {1}] disbanded arena team type: {2} [Id: {3}, Name: {4}].", player.GetName(), player.GetGUID().ToString(), GetArenaType(), GetId(), GetName()); + } + + // Remove all members from arena team + while (!Members.Empty()) + DelMember(Members.FirstOrDefault().Guid, false); + + // Update database + SQLTransaction trans = new SQLTransaction(); + + PreparedStatement stmt = DB.Characters.GetPreparedStatement(CharStatements.DEL_ARENA_TEAM); + stmt.AddValue(0, teamId); + trans.Append(stmt); + + stmt = DB.Characters.GetPreparedStatement(CharStatements.DEL_ARENA_TEAM_MEMBERS); + stmt.AddValue(0, teamId); + trans.Append(stmt); + + DB.Characters.CommitTransaction(trans); + + // Remove arena team from ObjectMgr + Global.ArenaTeamMgr.RemoveArenaTeam(teamId); + } + + public void Disband() + { + // Remove all members from arena team + while (!Members.Empty()) + DelMember(Members.First().Guid, false); + + // Update database + SQLTransaction trans = new SQLTransaction(); + + PreparedStatement stmt = DB.Characters.GetPreparedStatement(CharStatements.DEL_ARENA_TEAM); + stmt.AddValue(0, teamId); + trans.Append(stmt); + + stmt = DB.Characters.GetPreparedStatement(CharStatements.DEL_ARENA_TEAM_MEMBERS); + stmt.AddValue(0, teamId); + trans.Append(stmt); + + DB.Characters.CommitTransaction(trans); + + // Remove arena team from ObjectMgr + Global.ArenaTeamMgr.RemoveArenaTeam(teamId); + } + + public void SendStats(WorldSession session) + { + /*WorldPacket data = new WorldPacket(ServerOpcodes.ArenaTeamStats); + data.WriteUInt32(GetId()); // team id + data.WriteUInt32(stats.Rating); // rating + data.WriteUInt32(stats.WeekGames); // games this week + data.WriteUInt32(stats.WeekWins); // wins this week + data.WriteUInt32(stats.SeasonGames); // played this season + data.WriteUInt32(stats.SeasonWins); // wins this season + data.WriteUInt32(stats.Rank); // rank + session.SendPacket(data);*/ + } + + public void NotifyStatsChanged() + { + // This is called after a rated match ended + // Updates arena team stats for every member of the team (not only the ones who participated!) + foreach (var member in Members) + { + Player player = Global.ObjAccessor.FindPlayer(member.Guid); + if (player) + SendStats(player.GetSession()); + } + } + + public void Inspect(WorldSession session, ObjectGuid guid) + { + ArenaTeamMember member = GetMember(guid); + if (member == null) + return; + + WorldPacket data = new WorldPacket(ServerOpcodes.InspectPvp); + data.WritePackedGuid(guid); // player guid + data.WriteUInt8(GetSlot()); // slot (0...2) + data.WriteUInt32(GetId()); // arena team id + data.WriteUInt32(stats.Rating); // rating + data.WriteUInt32(stats.SeasonGames); // season played + data.WriteUInt32(stats.SeasonWins); // season wins + data.WriteUInt32(member.SeasonGames); // played (count of all games, that the inspected member participated...) + data.WriteUInt32(member.PersonalRating); // personal rating + //session.SendPacket(data); + } + + void BroadcastPacket(ServerPacket packet) + { + foreach (var member in Members) + { + Player player = Global.ObjAccessor.FindPlayer(member.Guid); + if (player) + player.SendPacket(packet); + } + } + + public static byte GetSlotByType(uint type) + { + switch ((ArenaTypes)type) + { + case ArenaTypes.Team2v2: return 0; + case ArenaTypes.Team3v3: return 1; + case ArenaTypes.Team5v5: return 2; + default: + break; + } + Log.outError(LogFilter.Arena, "FATAL: Unknown arena team type {0} for some arena team", type); + return 0xFF; + } + + public static byte GetTypeBySlot(byte slot) + { + switch (slot) + { + case 0: return (byte)ArenaTypes.Team2v2; + case 1: return (byte)ArenaTypes.Team3v3; + case 2: return (byte)ArenaTypes.Team5v5; + default: + break; + } + Log.outError(LogFilter.Arena, "FATAL: Unknown arena team slot {0} for some arena team", slot); + return 0xFF; + } + + public bool IsMember(ObjectGuid guid) + { + foreach (var member in Members) + if (member.Guid == guid) + return true; + + return false; + } + + public uint GetAverageMMR(Group group) + { + if (!group) + return 0; + + uint matchMakerRating = 0; + uint playerDivider = 0; + foreach (var member in Members) + { + // Skip if player is not online + if (!Global.ObjAccessor.FindPlayer(member.Guid)) + continue; + + // Skip if player is not a member of group + if (!group.IsMember(member.Guid)) + continue; + + matchMakerRating += member.MatchMakerRating; + ++playerDivider; + } + + // x/0 = crash + if (playerDivider == 0) + playerDivider = 1; + + matchMakerRating /= playerDivider; + + return matchMakerRating; + } + + float GetChanceAgainst(uint ownRating, uint opponentRating) + { + // Returns the chance to win against a team with the given rating, used in the rating adjustment calculation + // ELO system + return (float)(1.0f / (1.0f + Math.Exp(Math.Log(10.0f) * ((float)opponentRating - (float)ownRating) / 650.0f))); + } + + int GetMatchmakerRatingMod(uint ownRating, uint opponentRating, bool won) + { + // 'Chance' calculation - to beat the opponent + // This is a simulation. Not much info on how it really works + float chance = GetChanceAgainst(ownRating, opponentRating); + float won_mod = (won) ? 1.0f : 0.0f; + float mod = won_mod - chance; + + // Work in progress: + /* + // This is a simulation, as there is not much info on how it really works + float confidence_mod = min(1.0f - fabs(mod), 0.5f); + + // Apply confidence factor to the mod: + mod *= confidence_factor + + // And only after that update the new confidence factor + confidence_factor -= ((confidence_factor - 1.0f) * confidence_mod) / confidence_factor; + */ + + // Real rating modification + mod *= WorldConfig.GetFloatValue(WorldCfg.ArenaMatchmakerRatingModifier); + + return (int)Math.Ceiling(mod); + } + + int GetRatingMod(uint ownRating, uint opponentRating, bool won) + { + // 'Chance' calculation - to beat the opponent + // This is a simulation. Not much info on how it really works + float chance = GetChanceAgainst(ownRating, opponentRating); + + // Calculate the rating modification + float mod; + + // todo Replace this hack with using the confidence factor (limiting the factor to 2.0f) + if (won) + { + if (ownRating < 1300) + { + float win_rating_modifier1 = WorldConfig.GetFloatValue(WorldCfg.ArenaWinRatingModifier1); + + if (ownRating < 1000) + mod = win_rating_modifier1 * (1.0f - chance); + else + mod = ((win_rating_modifier1 / 2.0f) + ((win_rating_modifier1 / 2.0f) * (1300.0f - ownRating) / 300.0f)) * (1.0f - chance); + } + else + mod = WorldConfig.GetFloatValue(WorldCfg.ArenaWinRatingModifier2) * (1.0f - chance); + } + else + mod = WorldConfig.GetFloatValue(WorldCfg.ArenaLoseRatingModifier) * (-chance); + + return (int)Math.Ceiling(mod); + } + + public void FinishGame(int mod) + { + // Rating can only drop to 0 + if (stats.Rating + mod < 0) + stats.Rating = 0; + else + { + stats.Rating += (ushort)mod; + + // Check if rating related achivements are met + foreach (var member in Members) + { + Player player = Global.ObjAccessor.FindPlayer(member.Guid); + if (player) + player.UpdateCriteria(CriteriaTypes.HighestTeamRating, stats.Rating, type); + } + } + + // Update number of games played per season or week + stats.WeekGames += 1; + stats.SeasonGames += 1; + + // Update team's rank, start with rank 1 and increase until no team with more rating was found + stats.Rank = 1; + foreach (var i in Global.ArenaTeamMgr.GetArenaTeamMap()) + { + if (i.Value.GetArenaType() == type && i.Value.GetStats().Rating > stats.Rating) + ++stats.Rank; + } + } + + public int WonAgainst(uint ownMMRating, uint opponentMMRating, int ratingChange) + { + // Called when the team has won + // Change in Matchmaker rating + int mod = GetMatchmakerRatingMod(ownMMRating, opponentMMRating, true); + + // Change in Team Rating + ratingChange = GetRatingMod(stats.Rating, opponentMMRating, true); + + // Modify the team stats accordingly + FinishGame(ratingChange); + + // Update number of wins per season and week + stats.WeekWins += 1; + stats.SeasonWins += 1; + + // Return the rating change, used to display it on the results screen + return mod; + } + + public int LostAgainst(uint ownMMRating, uint opponentMMRating, int ratingChange) + { + // Called when the team has lost + // Change in Matchmaker Rating + int mod = GetMatchmakerRatingMod(ownMMRating, opponentMMRating, false); + + // Change in Team Rating + ratingChange = GetRatingMod(stats.Rating, opponentMMRating, false); + + // Modify the team stats accordingly + FinishGame(ratingChange); + + // return the rating change, used to display it on the results screen + return mod; + } + + public void MemberLost(Player player, uint againstMatchmakerRating, int matchmakerRatingChange = -12) + { + // Called for each participant of a match after losing + foreach (var member in Members) + { + if (member.Guid == player.GetGUID()) + { + // Update personal rating + int mod = GetRatingMod(member.PersonalRating, againstMatchmakerRating, false); + member.ModifyPersonalRating(player, mod, GetArenaType()); + + // Update matchmaker rating + member.ModifyMatchmakerRating(matchmakerRatingChange, GetSlot()); + + // Update personal played stats + member.WeekGames += 1; + member.SeasonGames += 1; + + // update the unit fields + player.SetArenaTeamInfoField(GetSlot(), ArenaTeamInfoType.GamesWeek, member.WeekGames); + player.SetArenaTeamInfoField(GetSlot(), ArenaTeamInfoType.GamesSeason, member.SeasonGames); + return; + } + } + } + + public void OfflineMemberLost(ObjectGuid guid, uint againstMatchmakerRating, int matchmakerRatingChange = -12) + { + // Called for offline player after ending rated arena match! + foreach (var member in Members) + { + if (member.Guid == guid) + { + // update personal rating + int mod = GetRatingMod(member.PersonalRating, againstMatchmakerRating, false); + member.ModifyPersonalRating(null, mod, GetArenaType()); + + // update matchmaker rating + member.ModifyMatchmakerRating(matchmakerRatingChange, GetSlot()); + + // update personal played stats + member.WeekGames += 1; + member.SeasonGames += 1; + return; + } + } + } + + public void MemberWon(Player player, uint againstMatchmakerRating, int matchmakerRatingChange) + { + // called for each participant after winning a match + foreach (var member in Members) + { + if (member.Guid == player.GetGUID()) + { + // update personal rating + int mod = GetRatingMod(member.PersonalRating, againstMatchmakerRating, true); + member.ModifyPersonalRating(player, mod, GetArenaType()); + + // update matchmaker rating + member.ModifyMatchmakerRating(matchmakerRatingChange, GetSlot()); + + // update personal stats + member.WeekGames += 1; + member.SeasonGames += 1; + member.SeasonWins += 1; + member.WeekWins += 1; + // update unit fields + player.SetArenaTeamInfoField(GetSlot(), ArenaTeamInfoType.GamesWeek, member.WeekGames); + player.SetArenaTeamInfoField(GetSlot(), ArenaTeamInfoType.GamesSeason, member.SeasonGames); + return; + } + } + } + + public void SaveToDB() + { + // Save team and member stats to db + // Called after a match has ended or when calculating arena_points + + SQLTransaction trans = new SQLTransaction(); + + PreparedStatement stmt = DB.Characters.GetPreparedStatement(CharStatements.UPD_ARENA_TEAM_STATS); + stmt.AddValue(0, stats.Rating); + stmt.AddValue(1, stats.WeekGames); + stmt.AddValue(2, stats.WeekWins); + stmt.AddValue(3, stats.SeasonGames); + stmt.AddValue(4, stats.SeasonWins); + stmt.AddValue(5, stats.Rank); + stmt.AddValue(6, GetId()); + trans.Append(stmt); + + foreach (var member in Members) + { + stmt = DB.Characters.GetPreparedStatement(CharStatements.UPD_ARENA_TEAM_MEMBER); + stmt.AddValue(0, member.PersonalRating); + stmt.AddValue(1, member.WeekGames); + stmt.AddValue(2, member.WeekWins); + stmt.AddValue(3, member.SeasonGames); + stmt.AddValue(4, member.SeasonWins); + stmt.AddValue(5, GetId()); + stmt.AddValue(6, member.Guid.GetCounter()); + trans.Append(stmt); + + stmt = DB.Characters.GetPreparedStatement(CharStatements.REP_CHARACTER_ARENA_STATS); + stmt.AddValue(0, member.Guid.GetCounter()); + stmt.AddValue(1, GetSlot()); + stmt.AddValue(2, member.MatchMakerRating); + trans.Append(stmt); + } + + DB.Characters.CommitTransaction(trans); + } + + public void FinishWeek() + { + // Reset team stats + stats.WeekGames = 0; + stats.WeekWins = 0; + + // Reset member stats + foreach (var member in Members) + { + member.WeekGames = 0; + member.WeekWins = 0; + } + } + + public bool IsFighting() + { + foreach (var member in Members) + { + Player player = Global.ObjAccessor.FindPlayer(member.Guid); + if (player) + if (player.GetMap().IsBattleArena()) + return true; + } + + return false; + } + + public ArenaTeamMember GetMember(string name) + { + foreach (var member in Members) + if (member.Name == name) + return member; + + return null; + } + + public ArenaTeamMember GetMember(ObjectGuid guid) + { + foreach (var member in Members) + if (member.Guid == guid) + return member; + + return null; + } + + public uint GetId() { return teamId; } + public byte GetArenaType() { return type; } + public byte GetSlot() { return GetSlotByType(GetArenaType()); } + + public ObjectGuid GetCaptain() { return CaptainGuid; } + public string GetName() { return TeamName; } + public ArenaTeamStats GetStats() { return stats; } + public uint GetRating() { return stats.Rating; } + + public int GetMembersSize() { return Members.Count; } + bool Empty() { return Members.Empty(); } + public List GetMembers() + { + return Members; + } + + uint teamId; + byte type; + string TeamName; + ObjectGuid CaptainGuid; + + uint BackgroundColor; // ARGB format + byte EmblemStyle; // icon id + uint EmblemColor; // ARGB format + byte BorderStyle; // border image id + uint BorderColor; // ARGB format + + List Members = new List(); + ArenaTeamStats stats; + } + + public class ArenaTeamMember + { + public ObjectGuid Guid; + public string Name; + public byte Class; + public ushort WeekGames; + public ushort WeekWins; + public ushort SeasonGames; + public ushort SeasonWins; + public ushort PersonalRating; + public ushort MatchMakerRating; + + public void ModifyPersonalRating(Player player, int mod, uint type) + { + if (PersonalRating + mod < 0) + PersonalRating = 0; + else + PersonalRating += (ushort)mod; + + if (player) + { + player.SetArenaTeamInfoField(ArenaTeam.GetSlotByType(type), ArenaTeamInfoType.PersonalRating, PersonalRating); + player.UpdateCriteria(CriteriaTypes.HighestPersonalRating, PersonalRating, type); + } + } + + public void ModifyMatchmakerRating(int mod, uint slot) + { + if (MatchMakerRating + mod < 0) + MatchMakerRating = 0; + else + MatchMakerRating += (ushort)mod; + } + } + + public struct ArenaTeamStats + { + public ushort Rating; + public ushort WeekGames; + public ushort WeekWins; + public ushort SeasonGames; + public ushort SeasonWins; + public uint Rank; + } +} diff --git a/Game/Arenas/ArenaTeamManager.cs b/Game/Arenas/ArenaTeamManager.cs new file mode 100644 index 000000000..b1a73db04 --- /dev/null +++ b/Game/Arenas/ArenaTeamManager.cs @@ -0,0 +1,129 @@ +/* + * Copyright (C) 2012-2017 CypherCore + * + * 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, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +using Framework.Database; +using Game.Entities; +using System.Collections.Generic; + +namespace Game.Arenas +{ + public class ArenaTeamManager : Singleton + { + ArenaTeamManager() + { + NextArenaTeamId = 1; + } + + public ArenaTeam GetArenaTeamById(uint arenaTeamId) + { + return ArenaTeamStorage.LookupByKey(arenaTeamId); + } + + public ArenaTeam GetArenaTeamByName(string arenaTeamName) + { + string search = arenaTeamName.ToLower(); + foreach (var team in ArenaTeamStorage.Values) + { + string teamName = team.GetName().ToLower(); + if (search == teamName) + return team; + } + return null; + } + + public ArenaTeam GetArenaTeamByCaptain(ObjectGuid guid) + { + foreach (var pair in ArenaTeamStorage) + if (pair.Value.GetCaptain() == guid) + return pair.Value; + + return null; + } + + public void AddArenaTeam(ArenaTeam arenaTeam) + { + ArenaTeamStorage[arenaTeam.GetId()] = arenaTeam; + } + + public void RemoveArenaTeam(uint arenaTeamId) + { + ArenaTeamStorage.Remove(arenaTeamId); + } + + public uint GenerateArenaTeamId() + { + if (NextArenaTeamId >= 0xFFFFFFFE) + { + Log.outError(LogFilter.Battleground, "Arena team ids overflow!! Can't continue, shutting down server. "); + Global.WorldMgr.StopNow(); + } + return NextArenaTeamId++; + } + + public void LoadArenaTeams() + { + uint oldMSTime = Time.GetMSTime(); + + // Clean out the trash before loading anything + DB.Characters.Execute("DELETE FROM arena_team_member WHERE arenaTeamId NOT IN (SELECT arenaTeamId FROM arena_team)"); // One-time query + + // 0 1 2 3 4 5 6 7 8 + SQLResult result = DB.Characters.Query("SELECT arenaTeamId, name, captainGuid, type, backgroundColor, emblemStyle, emblemColor, borderStyle, borderColor, " + + // 9 10 11 12 13 14 + "rating, weekGames, weekWins, seasonGames, seasonWins, rank FROM arena_team ORDER BY arenaTeamId ASC"); + + if (result.IsEmpty()) + { + Log.outInfo(LogFilter.ServerLoading, "Loaded 0 arena teams. DB table `arena_team` is empty!"); + return; + } + + SQLResult result2 = DB.Characters.Query( + // 0 1 2 3 4 5 6 7 8 9 + "SELECT arenaTeamId, atm.guid, atm.weekGames, atm.weekWins, atm.seasonGames, atm.seasonWins, c.name, class, personalRating, matchMakerRating FROM arena_team_member atm" + + " INNER JOIN arena_team ate USING (arenaTeamId) LEFT JOIN characters AS c ON atm.guid = c.guid" + + " LEFT JOIN character_arena_stats AS cas ON c.guid = cas.guid AND (cas.slot = 0 AND ate.type = 2 OR cas.slot = 1 AND ate.type = 3 OR cas.slot = 2 AND ate.type = 5)" + + " ORDER BY atm.arenateamid ASC"); + + uint count = 0; + do + { + ArenaTeam newArenaTeam = new ArenaTeam(); + + if (!newArenaTeam.LoadArenaTeamFromDB(result) || !newArenaTeam.LoadMembersFromDB(result2)) + { + newArenaTeam.Disband(null); + continue; + } + + AddArenaTeam(newArenaTeam); + + ++count; + } + while (result.NextRow()); + + Log.outInfo(LogFilter.ServerLoading, "Loaded {0} arena teams in {1} ms", count, Time.GetMSTimeDiffToNow(oldMSTime)); + } + + public void SetNextArenaTeamId(uint Id) { NextArenaTeamId = Id; } + + public Dictionary GetArenaTeamMap() { return ArenaTeamStorage; } + + uint NextArenaTeamId; + Dictionary ArenaTeamStorage = new Dictionary(); + } +} diff --git a/Game/Arenas/Zones/BladesEdgeArena.cs b/Game/Arenas/Zones/BladesEdgeArena.cs new file mode 100644 index 000000000..abcfbad7d --- /dev/null +++ b/Game/Arenas/Zones/BladesEdgeArena.cs @@ -0,0 +1,111 @@ +/* + * Copyright (C) 2012-2017 CypherCore + * + * 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, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +using Framework.Constants; +using Game.Entities; +using Game.Network.Packets; + +namespace Game.Arenas +{ + public class BladesEdgeArena : Arena + { + public override void StartingEventCloseDoors() + { + for (int i = BladeEdgeObjectTypes.Door1; i <= BladeEdgeObjectTypes.Door4; ++i) + SpawnBGObject(i, BattlegroundConst.RespawnImmediately); + + for (int i = BladeEdgeObjectTypes.Buff1; i <= BladeEdgeObjectTypes.Buff2; ++i) + SpawnBGObject(i, BattlegroundConst.RespawnOneDay); + } + + public override void StartingEventOpenDoors() + { + for (int i = BladeEdgeObjectTypes.Door1; i <= BladeEdgeObjectTypes.Door4; ++i) + DoorOpen(i); + + for (int i = BladeEdgeObjectTypes.Buff1; i <= BladeEdgeObjectTypes.Buff2; ++i) + SpawnBGObject(i, 60); + } + + public override void HandleAreaTrigger(Player player, uint trigger, bool entered) + { + if (GetStatus() != BattlegroundStatus.InProgress) + return; + + switch (trigger) + { + case 4538: // buff trigger? + case 4539: // buff trigger? + break; + default: + base.HandleAreaTrigger(player, trigger, entered); + break; + } + } + + public override void FillInitialWorldStates(InitWorldStates packet) + { + packet.AddState(0x9f3, 1); + base.FillInitialWorldStates(packet); + } + + public override bool SetupBattleground() + { + bool result = true; + result &= AddObject(BladeEdgeObjectTypes.Door1, BladeEfgeGameObjects.Door1, 6287.277f, 282.1877f, 3.810925f, -2.260201f, 0, 0, 0.9044551f, -0.4265689f, BattlegroundConst.RespawnImmediately); + result &= AddObject(BladeEdgeObjectTypes.Door2, BladeEfgeGameObjects.Door2, 6189.546f, 241.7099f, 3.101481f, 0.8813917f, 0, 0, 0.4265689f, 0.9044551f, BattlegroundConst.RespawnImmediately); + result &= AddObject(BladeEdgeObjectTypes.Door3, BladeEfgeGameObjects.Door3, 6299.116f, 296.5494f, 3.308032f, 0.8813917f, 0, 0, 0.4265689f, 0.9044551f, BattlegroundConst.RespawnImmediately); + result &= AddObject(BladeEdgeObjectTypes.Door4, BladeEfgeGameObjects.Door4, 6177.708f, 227.3481f, 3.604374f, -2.260201f, 0, 0, 0.9044551f, -0.4265689f, BattlegroundConst.RespawnImmediately); + if (!result) + { + Log.outError(LogFilter.Sql, "BatteGroundBE: Failed to spawn door object!"); + return false; + } + + result &= AddObject(BladeEdgeObjectTypes.Buff1, BladeEfgeGameObjects.Buff1, 6249.042f, 275.3239f, 11.22033f, -1.448624f, 0, 0, 0.6626201f, -0.7489557f, 120); + result &= AddObject(BladeEdgeObjectTypes.Buff2, BladeEfgeGameObjects.Buff2, 6228.26f, 249.566f, 11.21812f, -0.06981307f, 0, 0, 0.03489945f, -0.9993908f, 120); + if (!result) + { + Log.outError(LogFilter.Sql, "BladesEdgeArena: Failed to spawn buff object!"); + return false; + } + + return true; + } + } + + struct BladeEdgeObjectTypes + { + public const int Door1 = 0; + public const int Door2 = 1; + public const int Door3 = 2; + public const int Door4 = 3; + public const int Buff1 = 4; + public const int Buff2 = 5; + public const int Max = 6; + } + + struct BladeEfgeGameObjects + { + public const uint Door1 = 183971; + public const uint Door2 = 183973; + public const uint Door3 = 183970; + public const uint Door4 = 183972; + public const uint Buff1 = 184663; + public const uint Buff2 = 184664; + } +} diff --git a/Game/Arenas/Zones/DalaranSewers.cs b/Game/Arenas/Zones/DalaranSewers.cs new file mode 100644 index 000000000..6b2c4d6f5 --- /dev/null +++ b/Game/Arenas/Zones/DalaranSewers.cs @@ -0,0 +1,241 @@ +/* + * Copyright (C) 2012-2017 CypherCore + * + * 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, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +using Framework.Constants; +using Framework.Dynamic; +using Game.Entities; +using Game.Network.Packets; + +namespace Game.Arenas +{ + class DalaranSewersArena : Arena + { + public DalaranSewersArena() + { + _events = new EventMap(); + } + + public override void StartingEventCloseDoors() + { + for (int i = DalaranSewersObjectTypes.Door1; i <= DalaranSewersObjectTypes.Door2; ++i) + SpawnBGObject(i, BattlegroundConst.RespawnImmediately); + } + + public override void StartingEventOpenDoors() + { + for (int i = DalaranSewersObjectTypes.Door1; i <= DalaranSewersObjectTypes.Door2; ++i) + DoorOpen(i); + + for (int i = DalaranSewersObjectTypes.Buff1; i <= DalaranSewersObjectTypes.Buff2; ++i) + SpawnBGObject(i, 60); + + _events.ScheduleEvent(DalaranSewersEvents.WaterfallWarning, RandomHelper.URand(DalaranSewersData.WaterfallTimerMin, DalaranSewersData.WaterfallTimerMax)); + _events.ScheduleEvent(DalaranSewersEvents.PipeKnockback, DalaranSewersData.PipeKnockbackFirstDelay); + + SpawnBGObject(DalaranSewersObjectTypes.Water2, BattlegroundConst.RespawnImmediately); + + DoorOpen(DalaranSewersObjectTypes.Water1); // Turn off collision + DoorOpen(DalaranSewersObjectTypes.Water2); + + // Remove effects of Demonic Circle Summon + foreach (var pair in GetPlayers()) + { + Player player = _GetPlayer(pair, "BattlegroundDS::StartingEventOpenDoors"); + if (player) + player.RemoveAurasDueToSpell(DalaranSewersSpells.DemonicCircle); + } + } + + public override void HandleAreaTrigger(Player player, uint trigger, bool entered) + { + if (GetStatus() != BattlegroundStatus.InProgress) + return; + + switch (trigger) + { + case 5347: + case 5348: + // Remove effects of Demonic Circle Summon + player.RemoveAurasDueToSpell(DalaranSewersSpells.DemonicCircle); + + // Someone has get back into the pipes and the knockback has already been performed, + // so we reset the knockback count for kicking the player again into the arena. + _events.ScheduleEvent(DalaranSewersEvents.PipeKnockback, DalaranSewersData.PipeKnockbackDelay); + break; + default: + base.HandleAreaTrigger(player, trigger, entered); + break; + } + } + + public override void FillInitialWorldStates(InitWorldStates packet) + { + packet.AddState(3610, 1); + base.FillInitialWorldStates(packet); + } + + public override bool SetupBattleground() + { + bool result = true; + result &= AddObject(DalaranSewersObjectTypes.Door1, DalaranSewersGameObjects.Door1, 1350.95f, 817.2f, 20.8096f, 3.15f, 0, 0, 0.99627f, 0.0862864f, BattlegroundConst.RespawnImmediately); + result &= AddObject(DalaranSewersObjectTypes.Door2, DalaranSewersGameObjects.Door2, 1232.65f, 764.913f, 20.0729f, 6.3f, 0, 0, 0.0310211f, -0.999519f, BattlegroundConst.RespawnImmediately); + if (!result) + { + Log.outError(LogFilter.Sql, "DalaranSewersArena: Failed to spawn door object!"); + return false; + } + + // buffs + result &= AddObject(DalaranSewersObjectTypes.Buff1, DalaranSewersGameObjects.Buff1, 1291.7f, 813.424f, 7.11472f, 4.64562f, 0, 0, 0.730314f, -0.683111f, 120); + result &= AddObject(DalaranSewersObjectTypes.Buff2, DalaranSewersGameObjects.Buff2, 1291.7f, 768.911f, 7.11472f, 1.55194f, 0, 0, 0.700409f, 0.713742f, 120); + if (!result) + { + Log.outError(LogFilter.Sql, "DalaranSewersArena: Failed to spawn buff object!"); + return false; + } + + result &= AddObject(DalaranSewersObjectTypes.Water1, DalaranSewersGameObjects.Water1, 1291.56f, 790.837f, 7.1f, 3.14238f, 0, 0, 0.694215f, -0.719768f, 120); + result &= AddObject(DalaranSewersObjectTypes.Water2, DalaranSewersGameObjects.Water2, 1291.56f, 790.837f, 7.1f, 3.14238f, 0, 0, 0.694215f, -0.719768f, 120); + result &= AddCreature(DalaranSewersData.NpcWaterSpout, DalaranSewersCreatureTypes.WaterfallKnockback, 1292.587f, 790.2205f, 7.19796f, 3.054326f, TeamId.Neutral, BattlegroundConst.RespawnImmediately); + result &= AddCreature(DalaranSewersData.NpcWaterSpout, DalaranSewersCreatureTypes.PipeKnockback1, 1369.977f, 817.2882f, 16.08718f, 3.106686f, TeamId.Neutral, BattlegroundConst.RespawnImmediately); + result &= AddCreature(DalaranSewersData.NpcWaterSpout, DalaranSewersCreatureTypes.PipeKnockback2, 1212.833f, 765.3871f, 16.09484f, 0.0f, TeamId.Neutral, BattlegroundConst.RespawnImmediately); + if (!result) + { + Log.outError(LogFilter.Sql, "DalaranSewersArena: Failed to spawn collision object!"); + return false; + } + + return true; + } + + public override void PostUpdateImpl(uint diff) + { + if (GetStatus() != BattlegroundStatus.InProgress) + return; + + _events.ExecuteEvents(eventId => + { + switch (eventId) + { + case DalaranSewersEvents.WaterfallWarning: + // Add the water + DoorClose(DalaranSewersObjectTypes.Water2); + _events.ScheduleEvent(DalaranSewersEvents.WaterfallOn, DalaranSewersData.WaterWarningDuration); + break; + case DalaranSewersEvents.WaterfallOn: + // Active collision and start knockback timer + DoorClose(DalaranSewersObjectTypes.Water1); + _events.ScheduleEvent(DalaranSewersEvents.WaterfallOff, DalaranSewersData.WaterfallDuration); + _events.ScheduleEvent(DalaranSewersEvents.WaterfallKnockback, DalaranSewersData.WaterfallKnockbackTimer); + break; + case DalaranSewersEvents.WaterfallOff: + // Remove collision and water + DoorOpen(DalaranSewersObjectTypes.Water1); + DoorOpen(DalaranSewersObjectTypes.Water2); + _events.CancelEvent(DalaranSewersEvents.WaterfallKnockback); + _events.ScheduleEvent(DalaranSewersEvents.WaterfallWarning, RandomHelper.URand(DalaranSewersData.WaterfallTimerMin, DalaranSewersData.WaterfallTimerMax)); + break; + case DalaranSewersEvents.WaterfallKnockback: + { + // Repeat knockback while the waterfall still active + Creature waterSpout = GetBGCreature(DalaranSewersCreatureTypes.WaterfallKnockback); + if (waterSpout) + waterSpout.CastSpell(waterSpout, DalaranSewersSpells.WaterSpout, true); + _events.ScheduleEvent(eventId, DalaranSewersData.WaterfallKnockbackTimer); + } + break; + case DalaranSewersEvents.PipeKnockback: + { + for (int i = DalaranSewersCreatureTypes.PipeKnockback1; i <= DalaranSewersCreatureTypes.PipeKnockback2; ++i) + { + Creature waterSpout = GetBGCreature(i); + if (waterSpout) + waterSpout.CastSpell(waterSpout, DalaranSewersSpells.Flush, true); + } + } + break; + } + }); + } + + EventMap _events; + } + + struct DalaranSewersEvents + { + public const int WaterfallWarning = 1; // Water starting to fall, but no LoS Blocking nor movement blocking + public const uint WaterfallOn = 2; // LoS and Movement blocking active + public const uint WaterfallOff = 3; + public const uint WaterfallKnockback = 4; + + public const uint PipeKnockback = 5; + } + + struct DalaranSewersObjectTypes + { + public const int Door1 = 0; + public const int Door2 = 1; + public const int Water1 = 2; // Collision + public const int Water2 = 3; + public const int Buff1 = 4; + public const int Buff2 = 5; + public const int Max = 6; + } + + struct DalaranSewersGameObjects + { + public const uint Door1 = 192642; + public const uint Door2 = 192643; + public const uint Water1 = 194395; // Collision + public const uint Water2 = 191877; + public const uint Buff1 = 184663; + public const uint Buff2 = 184664; + } + + struct DalaranSewersCreatureTypes + { + public const int WaterfallKnockback = 0; + public const int PipeKnockback1 = 1; + public const int PipeKnockback2 = 2; + public const int Max = 3; + } + + struct DalaranSewersData + { + // These values are NOT blizzlike... need the correct data! + public const uint WaterfallTimerMin = 30000; + public const uint WaterfallTimerMax = 60000; + public const uint WaterWarningDuration = 5000; + public const uint WaterfallDuration = 30000; + public const uint WaterfallKnockbackTimer = 1500; + + public const uint PipeKnockbackFirstDelay = 5000; + public const uint PipeKnockbackDelay = 3000; + public const uint PipeKnockbackTotalCount = 2; + + public const uint NpcWaterSpout = 28567; + } + + struct DalaranSewersSpells + { + public const uint Flush = 57405; // Visual And Target Selector For The Starting Knockback From The Pipe + public const uint FlushKnockback = 61698; // Knockback Effect For Previous Spell (Triggered, Not Needed To Be Cast) + public const uint WaterSpout = 58873; // Knockback Effect Of The Central Waterfall + + public const uint DemonicCircle = 48018; // Demonic Circle Summon + } +} diff --git a/Game/Arenas/Zones/NagrandArena.cs b/Game/Arenas/Zones/NagrandArena.cs new file mode 100644 index 000000000..b2896d8e7 --- /dev/null +++ b/Game/Arenas/Zones/NagrandArena.cs @@ -0,0 +1,108 @@ +/* + * Copyright (C) 2012-2017 CypherCore + * + * 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, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +using Framework.Constants; +using Game.Entities; +using Game.Network.Packets; + +namespace Game.Arenas +{ + public class NagrandArena : Arena + { + public override void StartingEventCloseDoors() + { + for (int i = NagrandArenaObjectTypes.Door1; i <= NagrandArenaObjectTypes.Door4; ++i) + SpawnBGObject(i, BattlegroundConst.RespawnImmediately); + } + + public override void StartingEventOpenDoors() + { + for (int i = NagrandArenaObjectTypes.Door1; i <= NagrandArenaObjectTypes.Door4; ++i) + DoorOpen(i); + + for (int i = NagrandArenaObjectTypes.Buff1; i <= NagrandArenaObjectTypes.Buff2; ++i) + SpawnBGObject(i, 60); + } + + public override void HandleAreaTrigger(Player player, uint trigger, bool entered) + { + if (GetStatus() != BattlegroundStatus.InProgress) + return; + + switch (trigger) + { + case 4536: // buff trigger? + case 4537: // buff trigger? + break; + default: + base.HandleAreaTrigger(player, trigger, entered); + break; + } + } + + public override void FillInitialWorldStates(InitWorldStates packet) + { + packet.AddState(0xa11, 1); + base.FillInitialWorldStates(packet); + } + + public override bool SetupBattleground() + { + bool result = true; + result &= AddObject(NagrandArenaObjectTypes.Door1, NagrandArenaObjects.Door1, 4031.854f, 2966.833f, 12.6462f, -2.648788f, 0, 0, 0.9697962f, -0.2439165f, BattlegroundConst.RespawnImmediately); + result &= AddObject(NagrandArenaObjectTypes.Door2, NagrandArenaObjects.Door2, 4081.179f, 2874.97f, 12.39171f, 0.4928045f, 0, 0, 0.2439165f, 0.9697962f, BattlegroundConst.RespawnImmediately); + result &= AddObject(NagrandArenaObjectTypes.Door3, NagrandArenaObjects.Door3, 4023.709f, 2981.777f, 10.70117f, -2.648788f, 0, 0, 0.9697962f, -0.2439165f, BattlegroundConst.RespawnImmediately); + result &= AddObject(NagrandArenaObjectTypes.Door4, NagrandArenaObjects.Door4, 4090.064f, 2858.438f, 10.23631f, 0.4928045f, 0, 0, 0.2439165f, 0.9697962f, BattlegroundConst.RespawnImmediately); + if (!result) + { + Log.outError(LogFilter.Sql, "NagrandArena: Failed to spawn door object!"); + return false; + } + + result &= AddObject(NagrandArenaObjectTypes.Buff1, NagrandArenaObjects.Buff1, 4009.189941f, 2895.250000f, 13.052700f, -1.448624f, 0, 0, 0.6626201f, -0.7489557f, 120); + result &= AddObject(NagrandArenaObjectTypes.Buff2, NagrandArenaObjects.Buff2, 4103.330078f, 2946.350098f, 13.051300f, -0.06981307f, 0, 0, 0.03489945f, -0.9993908f, 120); + if (!result) + { + Log.outError(LogFilter.Sql, "NagrandArena: Failed to spawn buff object!"); + return false; + } + + return true; + } + } + + struct NagrandArenaObjectTypes + { + public const int Door1 = 0; + public const int Door2 = 1; + public const int Door3 = 2; + public const int Door4 = 3; + public const int Buff1 = 4; + public const int Buff2 = 5; + public const int Max = 6; + } + + struct NagrandArenaObjects + { + public const uint Door1 = 183978; + public const uint Door2 = 183980; + public const uint Door3 = 183977; + public const uint Door4 = 183979; + public const uint Buff1 = 184663; + public const uint Buff2 = 184664; + } +} diff --git a/Game/Arenas/Zones/RingofValor.cs b/Game/Arenas/Zones/RingofValor.cs new file mode 100644 index 000000000..272866b7a --- /dev/null +++ b/Game/Arenas/Zones/RingofValor.cs @@ -0,0 +1,264 @@ +/* + * Copyright (C) 2012-2017 CypherCore + * + * 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, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +using Framework.Constants; +using Framework.Dynamic; +using Game.Entities; +using Game.Network.Packets; + +namespace Game.Arenas +{ + class RingofValorArena : Arena + { + public RingofValorArena() + { + _events = new EventMap(); + } + + public override bool SetupBattleground() + { + bool result = true; + result &= AddObject(RingofValorObjectTypes.Elevator1, RingofValorGameObjects.Elevator1, 763.536377f, -294.535767f, 0.505383f, 3.141593f, 0, 0, 0, 0); + result &= AddObject(RingofValorObjectTypes.Elevator2, RingofValorGameObjects.Elevator2, 763.506348f, -273.873352f, 0.505383f, 0.000000f, 0, 0, 0, 0); + if (!result) + { + Log.outError(LogFilter.Sql, "RingofValorArena: Failed to spawn elevator object!"); + return false; + } + + result &= AddObject(RingofValorObjectTypes.Buff1, RingofValorGameObjects.Buff1, 735.551819f, -284.794678f, 28.276682f, 0.034906f, 0, 0, 0, 0); + result &= AddObject(RingofValorObjectTypes.Buff2, RingofValorGameObjects.Buff2, 791.224487f, -284.794464f, 28.276682f, 2.600535f, 0, 0, 0, 0); + if (!result) + { + Log.outError(LogFilter.Sql, "RingofValorArena: Failed to spawn buff object!"); + return false; + } + + result &= AddObject(RingofValorObjectTypes.Fire1, RingofValorGameObjects.Fire1, 743.543457f, -283.799469f, 28.286655f, 3.141593f, 0, 0, 0, 0); + result &= AddObject(RingofValorObjectTypes.Fire2, RingofValorGameObjects.Fire2, 782.971802f, -283.799469f, 28.286655f, 3.141593f, 0, 0, 0, 0); + result &= AddObject(RingofValorObjectTypes.Firedoor1, RingofValorGameObjects.Firedoor1, 743.711060f, -284.099609f, 27.542587f, 3.141593f, 0, 0, 0, 0); + result &= AddObject(RingofValorObjectTypes.Firedoor2, RingofValorGameObjects.Firedoor2, 783.221252f, -284.133362f, 27.535686f, 0.000000f, 0, 0, 0, 0); + if (!result) + { + Log.outError(LogFilter.Sql, "RingofValorArena: Failed to spawn fire/firedoor object!"); + return false; + } + + result &= AddObject(RingofValorObjectTypes.Gear1, RingofValorGameObjects.Gear1, 763.664551f, -261.872986f, 26.686588f, 0.000000f, 0, 0, 0, 0); + result &= AddObject(RingofValorObjectTypes.Gear2, RingofValorGameObjects.Gear2, 763.578979f, -306.146149f, 26.665222f, 3.141593f, 0, 0, 0, 0); + result &= AddObject(RingofValorObjectTypes.Pulley1, RingofValorGameObjects.Pulley1, 700.722290f, -283.990662f, 39.517582f, 3.141593f, 0, 0, 0, 0); + result &= AddObject(RingofValorObjectTypes.Pulley2, RingofValorGameObjects.Pulley2, 826.303833f, -283.996429f, 39.517582f, 0.000000f, 0, 0, 0, 0); + if (!result) + { + Log.outError(LogFilter.Sql, "RingofValorArena: Failed to spawn gear/pully object!"); + return false; + } + + result &= AddObject(RingofValorObjectTypes.Pilar1, RingofValorGameObjects.Pilar1, 763.632385f, -306.162384f, 25.909504f, 3.141593f, 0, 0, 0, 0); + result &= AddObject(RingofValorObjectTypes.Pilar2, RingofValorGameObjects.Pilar2, 723.644287f, -284.493256f, 24.648525f, 3.141593f, 0, 0, 0, 0); + result &= AddObject(RingofValorObjectTypes.Pilar3, RingofValorGameObjects.Pilar3, 763.611145f, -261.856750f, 25.909504f, 0.000000f, 0, 0, 0, 0); + result &= AddObject(RingofValorObjectTypes.Pilar4, RingofValorGameObjects.Pilar4, 802.211609f, -284.493256f, 24.648525f, 0.000000f, 0, 0, 0, 0); + result &= AddObject(RingofValorObjectTypes.PilarCollision1, RingofValorGameObjects.PilarCollision1, 763.632385f, -306.162384f, 30.639660f, 3.141593f, 0, 0, 0, 0); + result &= AddObject(RingofValorObjectTypes.PilarCollision2, RingofValorGameObjects.PilarCollision2, 723.644287f, -284.493256f, 32.382710f, 0.000000f, 0, 0, 0, 0); + result &= AddObject(RingofValorObjectTypes.PilarCollision3, RingofValorGameObjects.PilarCollision3, 763.611145f, -261.856750f, 30.639660f, 0.000000f, 0, 0, 0, 0); + result &= AddObject(RingofValorObjectTypes.PilarCollision4, RingofValorGameObjects.PilarCollision4, 802.211609f, -284.493256f, 32.382710f, 3.141593f, 0, 0, 0, 0); + if (!result) + { + Log.outError(LogFilter.Sql, "RingofValorArena: Failed to spawn pilar object!"); + return false; + } + + return true; + } + + public override void StartingEventOpenDoors() + { + // Buff respawn + SpawnBGObject(RingofValorObjectTypes.Buff1, 90); + SpawnBGObject(RingofValorObjectTypes.Buff2, 90); + // Elevators + DoorOpen(RingofValorObjectTypes.Elevator1); + DoorOpen(RingofValorObjectTypes.Elevator2); + + _events.ScheduleEvent(RingofValorEvents.OpenFences, 20133); + + // Should be false at first, TogglePillarCollision will do it. + TogglePillarCollision(true); + } + + public override void HandleAreaTrigger(Player player, uint trigger, bool entered) + { + if (GetStatus() != BattlegroundStatus.InProgress) + return; + + switch (trigger) + { + case 5224: + case 5226: + // fire was removed in 3.2.0 + case 5473: + case 5474: + break; + default: + base.HandleAreaTrigger(player, trigger, entered); + break; + } + } + + public override void FillInitialWorldStates(InitWorldStates packet) + { + packet.AddState(0xe1a, 1); + base.FillInitialWorldStates(packet); + } + + public override void PostUpdateImpl(uint diff) + { + if (GetStatus() != BattlegroundStatus.InProgress) + return; + + _events.Update(diff); + + _events.ExecuteEvents(eventId => + { + switch (eventId) + { + case RingofValorEvents.OpenFences: + // Open fire (only at game start) + for (byte i = RingofValorObjectTypes.Fire1; i <= RingofValorObjectTypes.Firedoor2; ++i) + DoorOpen(i); + _events.ScheduleEvent(RingofValorEvents.CloseFire, 5000); + break; + case RingofValorEvents.CloseFire: + for (byte i = RingofValorObjectTypes.Fire1; i <= RingofValorObjectTypes.Firedoor2; ++i) + DoorClose(i); + // Fire got closed after five seconds, leaves twenty seconds before toggling pillars + _events.ScheduleEvent(RingofValorEvents.SwitchPillars, 20000); + break; + case RingofValorEvents.SwitchPillars: + TogglePillarCollision(true); + _events.Repeat(25000); + break; + } + }); + } + + void TogglePillarCollision(bool enable) + { + // Toggle visual pillars, pulley, gear, and collision based on previous state + for (int i = RingofValorObjectTypes.Pilar1; i <= RingofValorObjectTypes.Gear2; ++i) + { + if (enable) + DoorOpen(i); + else + DoorClose(i); + } + + for (byte i = RingofValorObjectTypes.Pilar2; i <= RingofValorObjectTypes.Pulley2; ++i) + { + if (enable) + DoorClose(i); + else + DoorOpen(i); + } + + for (byte i = RingofValorObjectTypes.Pilar1; i <= RingofValorObjectTypes.PilarCollision4; ++i) + { + GameObject go = GetBGObject(i); + if (go) + { + if (i >= RingofValorObjectTypes.PilarCollision1) + { + GameObjectState state = ((go.GetGoInfo().Door.startOpen != 0) == enable) ? GameObjectState.Active : GameObjectState.Ready; + go.SetGoState(state); + } + + foreach (var guid in GetPlayers().Keys) + { + Player player = Global.ObjAccessor.FindPlayer(guid); + if (player) + go.SendUpdateToPlayer(player); + } + } + } + } + + EventMap _events; + } + + struct RingofValorEvents + { + public const int OpenFences = 0; + public const int SwitchPillars = 1; + public const int CloseFire = 2; + } + + struct RingofValorObjectTypes + { + public const int Buff1 = 1; + public const int Buff2 = 2; + public const int Fire1 = 3; + public const int Fire2 = 4; + public const int Firedoor1 = 5; + public const int Firedoor2 = 6; + + public const int Pilar1 = 7; + public const int Pilar3 = 8; + public const int Gear1 = 9; + public const int Gear2 = 10; + + public const int Pilar2 = 11; + public const int Pilar4 = 12; + public const int Pulley1 = 13; + public const int Pulley2 = 14; + + public const int PilarCollision1 = 15; + public const int PilarCollision2 = 16; + public const int PilarCollision3 = 17; + public const int PilarCollision4 = 18; + + public const int Elevator1 = 19; + public const int Elevator2= 20; + public const int Max = 21; + } + + struct RingofValorGameObjects + { + public const uint Buff1 = 184663; + public const uint Buff2 = 184664; + public const uint Fire1 = 192704; + public const uint Fire2 = 192705; + + public const uint Firedoor2 = 192387; + public const uint Firedoor1 = 192388; + public const uint Pulley1 = 192389; + public const uint Pulley2 = 192390; + public const uint Gear1 = 192393; + public const uint Gear2 = 192394; + public const uint Elevator1 = 194582; + public const uint Elevator2 = 194586; + + public const uint PilarCollision1 = 194580; // Axe + public const uint PilarCollision2 = 194579; // Arena + public const uint PilarCollision3 = 194581; // Lightning + public const uint PilarCollision4 = 194578; // Ivory + + public const uint Pilar1 = 194583; // Axe + public const uint Pilar2 = 194584; // Arena + public const uint Pilar3 = 194585; // Lightning + public const uint Pilar4 = 194587; // Ivory + } +} diff --git a/Game/Arenas/Zones/RuinsofLordaeron.cs b/Game/Arenas/Zones/RuinsofLordaeron.cs new file mode 100644 index 000000000..50d7c9ec0 --- /dev/null +++ b/Game/Arenas/Zones/RuinsofLordaeron.cs @@ -0,0 +1,102 @@ +/* + * Copyright (C) 2012-2017 CypherCore + * + * 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, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +using Framework.Constants; +using Game.Entities; +using Game.Network.Packets; + +namespace Game.Arenas +{ + class RuinsofLordaeronArena : Arena + { + public override bool SetupBattleground() + { + bool result = true; + result &= AddObject(RuinsofLordaeronObjectTypes.Door1, RuinsofLordaeronObjectTypes.Door1, 1293.561f, 1601.938f, 31.60557f, -1.457349f, 0, 0, -0.6658813f, 0.7460576f); + result &= AddObject(RuinsofLordaeronObjectTypes.Door2, RuinsofLordaeronObjectTypes.Door2, 1278.648f, 1730.557f, 31.60557f, 1.684245f, 0, 0, 0.7460582f, 0.6658807f); + if (!result) + { + Log.outError(LogFilter.Sql, "RuinsofLordaeronArena: Failed to spawn door object!"); + return false; + } + + result &= AddObject(RuinsofLordaeronObjectTypes.Buff1, RuinsofLordaeronObjectTypes.Buff1, 1328.719971f, 1632.719971f, 36.730400f, -1.448624f, 0, 0, 0.6626201f, -0.7489557f, 120); + result &= AddObject(RuinsofLordaeronObjectTypes.Buff2, RuinsofLordaeronObjectTypes.Buff2, 1243.300049f, 1699.170044f, 34.872601f, -0.06981307f, 0, 0, 0.03489945f, -0.9993908f, 120); + if (!result) + { + Log.outError(LogFilter.Sql, "RuinsofLordaeronArena: Failed to spawn buff object!"); + return false; + } + + return true; + } + + public override void StartingEventCloseDoors() + { + for (int i = RuinsofLordaeronObjectTypes.Door1; i <= RuinsofLordaeronObjectTypes.Door2; ++i) + SpawnBGObject(i, BattlegroundConst.RespawnImmediately); + } + + public override void StartingEventOpenDoors() + { + for (int i = RuinsofLordaeronObjectTypes.Door1; i <= RuinsofLordaeronObjectTypes.Door2; ++i) + DoorOpen(i); + + for (int i = RuinsofLordaeronObjectTypes.Buff1; i <= RuinsofLordaeronObjectTypes.Buff2; ++i) + SpawnBGObject(i, 60); + } + + public override void HandleAreaTrigger(Player player, uint trigger, bool entered) + { + if (GetStatus() != BattlegroundStatus.InProgress) + return; + + switch (trigger) + { + case 4696: // buff trigger? + case 4697: // buff trigger? + break; + default: + base.HandleAreaTrigger(player, trigger, entered); + break; + } + } + + public override void FillInitialWorldStates(InitWorldStates packet) + { + packet.AddState(0xbba, 1); + base.FillInitialWorldStates(packet); + } + } + + struct RuinsofLordaeronObjectTypes + { + public const int Door1 = 0; + public const int Door2 = 1; + public const int Buff1 = 2; + public const int Buff2 = 3; + public const int Max = 4; + } + + struct RuinsofLordaeronGameObjects + { + public const uint Door1 = 185918; + public const uint Door2 = 185917; + public const uint Buff1 = 184663; + public const uint Buff2 = 184664; + } +} diff --git a/Game/Arenas/Zones/TigersPeak.cs b/Game/Arenas/Zones/TigersPeak.cs new file mode 100644 index 000000000..59b8270db --- /dev/null +++ b/Game/Arenas/Zones/TigersPeak.cs @@ -0,0 +1,23 @@ +/* + * Copyright (C) 2012-2017 CypherCore + * + * 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, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +namespace Game.Arenas +{ + class TigersPeak + { + } +} diff --git a/Game/Arenas/Zones/TolvironArena.cs b/Game/Arenas/Zones/TolvironArena.cs new file mode 100644 index 000000000..fae6b7e45 --- /dev/null +++ b/Game/Arenas/Zones/TolvironArena.cs @@ -0,0 +1,23 @@ +/* + * Copyright (C) 2012-2017 CypherCore + * + * 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, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +namespace Game.Arenas +{ + class TolvironArena + { + } +} diff --git a/Game/AuctionHouse/AuctionManager.cs b/Game/AuctionHouse/AuctionManager.cs new file mode 100644 index 000000000..46b614771 --- /dev/null +++ b/Game/AuctionHouse/AuctionManager.cs @@ -0,0 +1,845 @@ +/* + * Copyright (C) 2012-2017 CypherCore + * + * 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, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +using Framework.Constants; +using Framework.Database; +using Framework.Dynamic; +using Game.DataStorage; +using Game.Entities; +using Game.Mails; +using Game.Network.Packets; +using System; +using System.Collections.Generic; +using System.Diagnostics.Contracts; + +namespace Game +{ + public class AuctionManager : Singleton + { + const int AH_MINIMUM_DEPOSIT = 100; + + AuctionManager() { } + + public AuctionHouseObject GetAuctionsMap(uint factionTemplateId) + { + if (WorldConfig.GetBoolValue(WorldCfg.AllowTwoSideInteractionAuction)) + return mNeutralAuctions; + + // teams have linked auction houses + FactionTemplateRecord uEntry = CliDB.FactionTemplateStorage.LookupByKey(factionTemplateId); + if (uEntry == null) + return mNeutralAuctions; + else if ((uEntry.Mask & (int)FactionMasks.Alliance) != 0) + return mAllianceAuctions; + else if ((uEntry.Mask & (int)FactionMasks.Horde) != 0) + return mHordeAuctions; + else + return mNeutralAuctions; + } + + public uint GetAuctionDeposit(AuctionHouseRecord entry, uint time, Item pItem, uint count) + { + uint MSV = pItem.GetTemplate().GetSellPrice(); + + if (MSV <= 0) + return AH_MINIMUM_DEPOSIT * (uint)WorldConfig.GetFloatValue(WorldCfg.RateAuctionDeposit); + + float multiplier = MathFunctions.CalculatePct((float)entry.DepositRate, 3); + uint timeHr = (((time / 60) / 60) / 12); + uint deposit = (uint)(((multiplier * MSV * count / 3) * timeHr * 3) * WorldConfig.GetFloatValue(WorldCfg.RateAuctionDeposit)); + + Log.outDebug(LogFilter.Auctionhouse, "MSV: {0}", MSV); + Log.outDebug(LogFilter.Auctionhouse, "Items: {0}", count); + Log.outDebug(LogFilter.Auctionhouse, "Multiplier: {0}", multiplier); + Log.outDebug(LogFilter.Auctionhouse, "Deposit: {0}", deposit); + + if (deposit < AH_MINIMUM_DEPOSIT * WorldConfig.GetFloatValue(WorldCfg.RateAuctionDeposit)) + return AH_MINIMUM_DEPOSIT * (uint)WorldConfig.GetFloatValue(WorldCfg.RateAuctionDeposit); + else + return deposit; + } + + public void SendAuctionWonMail(AuctionEntry auction, SQLTransaction trans) + { + Item item = GetAItem(auction.itemGUIDLow); + if (!item) + return; + + uint bidderAccId = 0; + ObjectGuid bidderGuid = ObjectGuid.Create(HighGuid.Player, auction.bidder); + Player bidder = Global.ObjAccessor.FindPlayer(bidderGuid); + // data for gm.log + string bidderName = ""; + bool logGmTrade = false; + + if (bidder) + { + bidderAccId = bidder.GetSession().GetAccountId(); + bidderName = bidder.GetName(); + logGmTrade = bidder.GetSession().HasPermission(RBACPermissions.LogGmTrade); + } + else + { + bidderAccId = ObjectManager.GetPlayerAccountIdByGUID(bidderGuid); + logGmTrade = Global.AccountMgr.HasPermission(bidderAccId, RBACPermissions.LogGmTrade, Global.WorldMgr.GetRealm().Id.Realm); + + if (logGmTrade && !ObjectManager.GetPlayerNameByGUID(bidderGuid, out bidderName)) + bidderName = Global.ObjectMgr.GetCypherString(CypherStrings.Unknown); + } + + if (logGmTrade) + { + ObjectGuid ownerGuid = ObjectGuid.Create(HighGuid.Player, auction.owner); + string ownerName; + if (!ObjectManager.GetPlayerNameByGUID(ownerGuid, out ownerName)) + ownerName = Global.ObjectMgr.GetCypherString(CypherStrings.Unknown); + + uint ownerAccId = ObjectManager.GetPlayerAccountIdByGUID(ownerGuid); + + Log.outCommand(bidderAccId, "GM {0} (Account: {1}) won item in auction: {2} (Entry: {3} Count: {4}) and pay money: {5}. Original owner {6} (Account: {7})", + bidderName, bidderAccId, item.GetTemplate().GetName(), item.GetEntry(), item.GetCount(), auction.bid, ownerName, ownerAccId); + } + + // receiver exist + if (bidder || bidderAccId != 0) + { + // set owner to bidder (to prevent delete item with sender char deleting) + // owner in `data` will set at mail receive and item extracting + PreparedStatement stmt = DB.Characters.GetPreparedStatement(CharStatements.UPD_ITEM_OWNER); + stmt.AddValue(0, auction.bidder); + stmt.AddValue(1, item.GetGUID().GetCounter()); + trans.Append(stmt); + + if (bidder) + { + bidder.GetSession().SendAuctionWonNotification(auction, item); + // FIXME: for offline player need also + bidder.UpdateCriteria(CriteriaTypes.WonAuctions, 1); + } + + new MailDraft(auction.BuildAuctionMailSubject(MailAuctionAnswers.Won), AuctionEntry.BuildAuctionMailBody(auction.owner, auction.bid, auction.buyout, 0, 0)) + .AddItem(item) + .SendMailTo(trans, new MailReceiver(bidder, auction.bidder), new MailSender(auction), MailCheckMask.Copied); + } + else + { + // bidder doesn't exist, delete the item + Global.AuctionMgr.RemoveAItem(auction.itemGUIDLow, true); + } + } + + public void SendAuctionSalePendingMail(AuctionEntry auction, SQLTransaction trans) + { + ObjectGuid owner_guid = ObjectGuid.Create(HighGuid.Player, auction.owner); + Player owner = Global.ObjAccessor.FindPlayer(owner_guid); + uint owner_accId = ObjectManager.GetPlayerAccountIdByGUID(owner_guid); + // owner exist (online or offline) + if (owner || owner_accId != 0) + new MailDraft(auction.BuildAuctionMailSubject(MailAuctionAnswers.SalePending), AuctionEntry.BuildAuctionMailBody(auction.bidder, auction.bid, auction.buyout, auction.deposit, auction.GetAuctionCut())) + .SendMailTo(trans, new MailReceiver(owner, auction.owner), new MailSender(auction), MailCheckMask.Copied); + } + + //call this method to send mail to auction owner, when auction is successful, it does not clear ram + public void SendAuctionSuccessfulMail(AuctionEntry auction, SQLTransaction trans) + { + ObjectGuid owner_guid = ObjectGuid.Create(HighGuid.Player, auction.owner); + Player owner = Global.ObjAccessor.FindPlayer(owner_guid); + uint owner_accId = ObjectManager.GetPlayerAccountIdByGUID(owner_guid); + Item item = GetAItem(auction.itemGUIDLow); + + // owner exist + if (owner || owner_accId != 0) + { + uint profit = auction.bid + auction.deposit - auction.GetAuctionCut(); + + //FIXME: what do if owner offline + if (owner && item) + { + owner.UpdateCriteria(CriteriaTypes.GoldEarnedByAuctions, profit); + owner.UpdateCriteria(CriteriaTypes.HighestAuctionSold, auction.bid); + // send auction owner notification, bidder must be current! + owner.GetSession().SendAuctionClosedNotification(auction, WorldConfig.GetIntValue(WorldCfg.MailDeliveryDelay), true, item); + } + + new MailDraft(auction.BuildAuctionMailSubject(MailAuctionAnswers.Successful), AuctionEntry.BuildAuctionMailBody(auction.bidder, auction.bid, auction.buyout, auction.deposit, auction.GetAuctionCut())) + .AddMoney(profit) + .SendMailTo(trans, new MailReceiver(owner, auction.owner), new MailSender(auction), MailCheckMask.Copied, WorldConfig.GetUIntValue(WorldCfg.MailDeliveryDelay)); + } + } + + //does not clear ram + public void SendAuctionExpiredMail(AuctionEntry auction, SQLTransaction trans) + { + //return an item in auction to its owner by mail + Item item = GetAItem(auction.itemGUIDLow); + if (!item) + return; + + ObjectGuid owner_guid = ObjectGuid.Create(HighGuid.Player, auction.owner); + Player owner = Global.ObjAccessor.FindPlayer(owner_guid); + uint owner_accId = ObjectManager.GetPlayerAccountIdByGUID(owner_guid); + // owner exist + if (owner || owner_accId != 0) + { + if (owner) + owner.GetSession().SendAuctionClosedNotification(auction, 0f, false, item); + + new MailDraft(auction.BuildAuctionMailSubject(MailAuctionAnswers.Expired), AuctionEntry.BuildAuctionMailBody(0, 0, auction.buyout, auction.deposit, 0)) + .AddItem(item) + .SendMailTo(trans, new MailReceiver(owner, auction.owner), new MailSender(auction), MailCheckMask.Copied, 0); + } + else + { + // owner doesn't exist, delete the item + Global.AuctionMgr.RemoveAItem(auction.itemGUIDLow, true); + } + } + + //this function sends mail to old bidder + public void SendAuctionOutbiddedMail(AuctionEntry auction, ulong newPrice, Player newBidder, SQLTransaction trans) + { + ObjectGuid oldBidder_guid = ObjectGuid.Create(HighGuid.Player, auction.bidder); + Player oldBidder = Global.ObjAccessor.FindPlayer(oldBidder_guid); + + uint oldBidder_accId = 0; + if (oldBidder == null) + oldBidder_accId = ObjectManager.GetPlayerAccountIdByGUID(oldBidder_guid); + + Item item = GetAItem(auction.itemGUIDLow); + + // old bidder exist + if (oldBidder || oldBidder_accId != 0) + { + if (oldBidder && item) + oldBidder.GetSession().SendAuctionOutBidNotification(auction, item); + + new MailDraft(auction.BuildAuctionMailSubject(MailAuctionAnswers.Outbidded), AuctionEntry.BuildAuctionMailBody(auction.owner, auction.bid, auction.buyout, auction.deposit, auction.GetAuctionCut())) + .AddMoney(auction.bid) + .SendMailTo(trans, new MailReceiver(oldBidder, auction.bidder), new MailSender(auction), MailCheckMask.Copied); + } + } + + //this function sends mail, when auction is cancelled to old bidder + public void SendAuctionCancelledToBidderMail(AuctionEntry auction, SQLTransaction trans) + { + ObjectGuid bidder_guid = ObjectGuid.Create(HighGuid.Player, auction.bidder); + Player bidder = Global.ObjAccessor.FindPlayer(bidder_guid); + + uint bidder_accId = 0; + if (!bidder) + bidder_accId = ObjectManager.GetPlayerAccountIdByGUID(bidder_guid); + + // bidder exist + if (bidder || bidder_accId != 0) + new MailDraft(auction.BuildAuctionMailSubject(MailAuctionAnswers.CancelledToBidder), AuctionEntry.BuildAuctionMailBody(auction.owner, auction.bid, auction.buyout, auction.deposit, 0)) + .AddMoney(auction.bid) + .SendMailTo(trans, new MailReceiver(bidder, auction.bidder), new MailSender(auction), MailCheckMask.Copied); + } + + public void LoadAuctionItems() + { + uint oldMSTime = Time.GetMSTime(); + + // need to clear in case we are reloading + mAitems.Clear(); + + // data needs to be at first place for Item.LoadFromDB + PreparedStatement stmt = DB.Characters.GetPreparedStatement(CharStatements.SEL_AUCTION_ITEMS); + SQLResult result = DB.Characters.Query(stmt); + + if (result.IsEmpty()) + { + Log.outInfo(LogFilter.ServerLoading, "Loaded 0 auction items. DB table `auctionhouse` or `item_instance` is empty!"); + return; + } + + uint count = 0; + do + { + ulong itemGuid = result.Read(0); + uint itemEntry = result.Read(1); + + ItemTemplate proto = Global.ObjectMgr.GetItemTemplate(itemEntry); + if (proto == null) + { + Log.outError(LogFilter.Server, "AuctionHouseMgr:LoadAuctionItems: Unknown item (GUID: {0} item entry: {1}) in auction, skipped.", itemGuid, itemEntry); + continue; + } + + Item item = Bag.NewItemOrBag(proto); + if (!item.LoadFromDB(itemGuid, ObjectGuid.Empty, result.GetFields(), itemEntry)) + continue; + + AddAItem(item); + ++count; + } + while (result.NextRow()); + + Log.outInfo(LogFilter.ServerLoading, "Loaded {0} auction items in {1} ms", count, Time.GetMSTimeDiffToNow(oldMSTime)); + } + + public void LoadAuctions() + { + uint oldMSTime = Time.GetMSTime(); + + PreparedStatement stmt = DB.Characters.GetPreparedStatement(CharStatements.SEL_AUCTIONS); + SQLResult result = DB.Characters.Query(stmt); + + if (result.IsEmpty()) + { + Log.outInfo(LogFilter.ServerLoading, "Loaded 0 auctions. DB table `auctionhouse` is empty."); + return; + } + + uint count = 0; + SQLTransaction trans = new SQLTransaction(); + do + { + AuctionEntry aItem = new AuctionEntry(); + if (!aItem.LoadFromDB(result.GetFields())) + { + aItem.DeleteFromDB(trans); + continue; + } + + GetAuctionsMap(aItem.factionTemplateId).AddAuction(aItem); + ++count; + } while (result.NextRow()); + + DB.Characters.CommitTransaction(trans); + + Log.outInfo(LogFilter.ServerLoading, "Loaded {0} auctions in {1} ms", count, Time.GetMSTimeDiffToNow(oldMSTime)); + + } + + public void AddAItem(Item it) + { + Contract.Assert(it); + Contract.Assert(!mAitems.ContainsKey(it.GetGUID().GetCounter())); + mAitems[it.GetGUID().GetCounter()] = it; + } + + public bool RemoveAItem(ulong id, bool deleteItem = false) + { + var item = mAitems.LookupByKey(id); + if (item == null) + return false; + + if (deleteItem) + { + item.FSetState(ItemUpdateState.Removed); + item.SaveToDB(null); + } + + mAitems.Remove(id); + return true; + } + + public void Update() + { + mHordeAuctions.Update(); + mAllianceAuctions.Update(); + mNeutralAuctions.Update(); + } + + public AuctionHouseRecord GetAuctionHouseEntry(uint factionTemplateId) + { + uint houseId = 0; + return GetAuctionHouseEntry(factionTemplateId, ref houseId); + } + + public AuctionHouseRecord GetAuctionHouseEntry(uint factionTemplateId, ref uint houseId) + { + uint houseid = 7; // goblin auction house + + if (!WorldConfig.GetBoolValue(WorldCfg.AllowTwoSideInteractionAuction)) + { + // FIXME: found way for proper auctionhouse selection by another way + // AuctionHouse.dbc have faction field with _player_ factions associated with auction house races. + // but no easy way convert creature faction to player race faction for specific city + switch (factionTemplateId) + { + case 12: houseid = 1; break; // human + case 29: houseid = 6; break; // orc, and generic for horde + case 55: houseid = 2; break; // dwarf, and generic for alliance + case 68: houseid = 4; break; // undead + case 80: houseid = 3; break; // n-elf + case 104: houseid = 5; break; // trolls + case 120: houseid = 7; break; // booty bay, neutral + case 474: houseid = 7; break; // gadgetzan, neutral + case 855: houseid = 7; break; // everlook, neutral + case 1604: houseid = 6; break; // b-elfs, + default: // for unknown case + { + FactionTemplateRecord u_entry = CliDB.FactionTemplateStorage.LookupByKey(factionTemplateId); + if (u_entry == null) + houseid = 7; // goblin auction house + else if ((u_entry.Mask & (int)FactionMasks.Alliance) != 0) + houseid = 1; // human auction house + else if ((u_entry.Mask & (int)FactionMasks.Horde) != 0) + houseid = 6; // orc auction house + else + houseid = 7; // goblin auction house + break; + } + } + } + + houseId = houseid; + + return CliDB.AuctionHouseStorage.LookupByKey(houseid); + } + + public Item GetAItem(ulong id) + { + return mAitems.LookupByKey(id); + } + + AuctionHouseObject mHordeAuctions = new AuctionHouseObject(); + AuctionHouseObject mAllianceAuctions = new AuctionHouseObject(); + AuctionHouseObject mNeutralAuctions = new AuctionHouseObject(); + + Dictionary mAitems = new Dictionary(); + } + + public class AuctionHouseObject + { + public void AddAuction(AuctionEntry auction) + { + Contract.Assert(auction != null); + + AuctionsMap[auction.Id] = auction; + Global.ScriptMgr.OnAuctionAdd(this, auction); + } + + public bool RemoveAuction(AuctionEntry auction) + { + bool wasInMap = AuctionsMap.Remove(auction.Id) ? true : false; + + Global.ScriptMgr.OnAuctionRemove(this, auction); + + // we need to delete the entry, it is not referenced any more + auction = null; + + return wasInMap; + } + + public void Update() + { + long curTime = Global.WorldMgr.GetGameTime(); + // Handle expired auctions + + // If storage is empty, no need to update. next == NULL in this case. + if (AuctionsMap.Empty()) + return; + + SQLTransaction trans = new SQLTransaction(); + + foreach (var auction in AuctionsMap.Values) + { + // filter auctions expired on next update + if (auction.expire_time > curTime + 60) + continue; + + // Either cancel the auction if there was no bidder + if (auction.bidder == 0 && auction.bid == 0) + { + Global.AuctionMgr.SendAuctionExpiredMail(auction, trans); + Global.ScriptMgr.OnAuctionExpire(this, auction); + } + // Or perform the transaction + else + { + //we should send an "item sold" message if the seller is online + //we send the item to the winner + //we send the money to the seller + Global.AuctionMgr.SendAuctionSuccessfulMail(auction, trans); + Global.AuctionMgr.SendAuctionWonMail(auction, trans); + Global.ScriptMgr.OnAuctionSuccessful(this, auction); + } + + // In any case clear the auction + auction.DeleteFromDB(trans); + + Global.AuctionMgr.RemoveAItem(auction.itemGUIDLow); + RemoveAuction(auction); + } + + // Run DB changes + DB.Characters.CommitTransaction(trans); + } + + public void BuildListBidderItems(AuctionListBidderItemsResult packet, Player player, ref uint totalcount) + { + foreach (var Aentry in AuctionsMap.Values) + { + if (Aentry != null && Aentry.bidder == player.GetGUID().GetCounter()) + { + Aentry.BuildAuctionInfo(packet.Items, false); + ++totalcount; + } + } + } + + public void BuildListOwnerItems(AuctionListOwnerItemsResult packet, Player player, ref uint totalcount) + { + foreach (var Aentry in AuctionsMap.Values) + { + if (Aentry != null && Aentry.owner == player.GetGUID().GetCounter()) + { + Aentry.BuildAuctionInfo(packet.Items, false); + ++totalcount; + } + } + } + + public void BuildListAuctionItems(AuctionListItemsResult packet, Player player, string searchedname, uint listfrom, byte levelmin, byte levelmax, bool usable, Optional filters, uint quality) + { + long curTime = Global.WorldMgr.GetGameTime(); + + foreach (var Aentry in AuctionsMap.Values) + { + // Skip expired auctions + if (Aentry.expire_time < curTime) + continue; + + Item item = Global.AuctionMgr.GetAItem(Aentry.itemGUIDLow); + if (!item) + continue; + + ItemTemplate proto = item.GetTemplate(); + if (filters.HasValue) + { + // if we dont want any class filters, Optional is not initialized + // if we dont want this class included, SubclassMask is set to FILTER_SKIP_CLASS + // if we want this class and did not specify and subclasses, its set to FILTER_SKIP_SUBCLASS + // otherwise full restrictions apply + if (filters.Value.Classes[(int)proto.GetClass()].SubclassMask == AuctionSearchFilters.FilterType.SkipClass) + continue; + + if (filters.Value.Classes[(int)proto.GetClass()].SubclassMask != AuctionSearchFilters.FilterType.SkipSubclass) + { + if (!Convert.ToBoolean((int)filters.Value.Classes[(int)proto.GetClass()].SubclassMask & (1u << (int)proto.GetSubClass()))) + continue; + + if (!Convert.ToBoolean(filters.Value.Classes[(int)proto.GetClass()].InvTypes[(int)proto.GetSubClass()] & (1u << (int)proto.GetInventoryType()))) + continue; + } + } + + if (quality != 0xffffffff && (uint)proto.GetQuality() != quality) + continue; + + if (levelmin != 0 && (proto.GetBaseRequiredLevel() < levelmin || (levelmax != 0x00 && proto.GetBaseRequiredLevel() > levelmax))) + continue; + + if (usable && player.CanUseItem(item) != InventoryResult.Ok) + continue; + + // Allow search by suffix (ie: of the Monkey) or partial name (ie: Monkey) + // No need to do any of this if no search term was entered + if (!string.IsNullOrEmpty(searchedname)) + { + string name = proto.GetName(player.GetSession().GetSessionDbcLocale()); + if (string.IsNullOrEmpty(name)) + continue; + + // DO NOT use GetItemEnchantMod(proto.RandomProperty) as it may return a result + // that matches the search but it may not equal item.GetItemRandomPropertyId() + // used in BuildAuctionInfo() which then causes wrong items to be listed + int propRefID = item.GetItemRandomPropertyId(); + if (propRefID != 0) + { + string suffix = null; + // Append the suffix to the name (ie: of the Monkey) if one exists + // These are found in ItemRandomProperties.dbc, not ItemRandomSuffix.dbc + // even though the DBC names seem misleading + if (propRefID < 0) + { + ItemRandomSuffixRecord itemRandSuffix = CliDB.ItemRandomSuffixStorage.LookupByKey(-propRefID); + if (itemRandSuffix != null) + suffix = itemRandSuffix.Name[player.GetSession().GetSessionDbcLocale()]; + } + else + { + ItemRandomPropertiesRecord itemRandProp = CliDB.ItemRandomPropertiesStorage.LookupByKey(propRefID); + if (itemRandProp != null) + suffix = itemRandProp.Name[player.GetSession().GetSessionDbcLocale()]; + } + + // dbc local name + if (!string.IsNullOrEmpty(suffix)) + { + // Append the suffix (ie: of the Monkey) to the name using localization + // or default enUS if localization is invalid + name += ' ' + suffix; + } + } + } + + // Add the item if no search term or if entered search term was found + if (packet.Items.Count < 50 && packet.TotalCount >= listfrom) + Aentry.BuildAuctionInfo(packet.Items, true); + + ++packet.TotalCount; + } + } + + public AuctionEntry GetAuction(uint id) + { + return AuctionsMap.LookupByKey(id); + } + + Dictionary AuctionsMap = new Dictionary(); + } + + public class AuctionEntry + { + public void BuildAuctionInfo(List items, bool listAuctionItems) + { + Item item = Global.AuctionMgr.GetAItem(itemGUIDLow); + if (!item) + { + Log.outError(LogFilter.Server, "AuctionEntry:BuildAuctionInfo: Auction {0} has a non-existent item: {1}", Id, itemGUIDLow); + return; + } + AuctionItem auctionItem = new AuctionItem(); + + auctionItem.AuctionItemID = (int)Id; + auctionItem.Item = new ItemInstance(item); + auctionItem.BuyoutPrice = buyout; + auctionItem.CensorBidInfo = false; + auctionItem.CensorServerSideInfo = listAuctionItems; + auctionItem.Charges = item.GetSpellCharges(); + auctionItem.Count = (int)item.GetCount(); + auctionItem.DeleteReason = 0; // Always 0 ? + auctionItem.DurationLeft = (int)((expire_time - Time.UnixTime) * Time.InMilliseconds); + auctionItem.EndTime = (uint)expire_time; + auctionItem.Flags = 0; // todo + auctionItem.ItemGuid = item.GetGUID(); + auctionItem.MinBid = startbid; + auctionItem.Owner = ObjectGuid.Create(HighGuid.Player, owner); + auctionItem.OwnerAccountID = ObjectGuid.Create(HighGuid.WowAccount, ObjectManager.GetPlayerAccountIdByGUID(auctionItem.Owner)); + auctionItem.MinIncrement = bidder != 0 ? GetAuctionOutBid() : 0; + auctionItem.Bidder = bidder != 0 ? ObjectGuid.Create(HighGuid.Player, bidder) : ObjectGuid.Empty; + auctionItem.BidAmount = bidder != 0 ? bid : 0; + + for (EnchantmentSlot c = 0; c < EnchantmentSlot.MaxInspected; c++) + { + if (item.GetEnchantmentId(c) == 0) + continue; + + auctionItem.Enchantments.Add(new ItemEnchantData((int)item.GetEnchantmentId(c), item.GetEnchantmentDuration(c), (int)item.GetEnchantmentCharges(c), (byte)c)); + } + + byte i = 0; + foreach (ItemDynamicFieldGems gemData in item.GetGems()) + { + if (gemData.ItemId != 0) + { + ItemGemData gem = new ItemGemData(); + gem.Slot = i; + gem.Item = new ItemInstance(gemData); + auctionItem.Gems.Add(gem); + } + ++i; + } + + items.Add(auctionItem); + } + + public uint GetAuctionCut() + { + int cut = (int)(MathFunctions.CalculatePct(bid, auctionHouseEntry.ConsignmentRate) * WorldConfig.GetFloatValue(WorldCfg.RateAuctionCut)); + return (uint)Math.Max(cut, 0); + } + + /// + /// the sum of outbid is (1% from current bid)*5, if bid is very small, it is 1c + /// + /// + public uint GetAuctionOutBid() + { + uint outbid = MathFunctions.CalculatePct(bid, 5); + return outbid != 0 ? outbid : 1; + } + + public void DeleteFromDB(SQLTransaction trans) + { + PreparedStatement stmt = DB.Characters.GetPreparedStatement(CharStatements.DEL_AUCTION); + stmt.AddValue(0, Id); + trans.Append(stmt); + } + + public void SaveToDB(SQLTransaction trans) + { + PreparedStatement stmt = DB.Characters.GetPreparedStatement(CharStatements.INS_AUCTION); + stmt.AddValue(0, Id); + stmt.AddValue(1, auctioneer); + stmt.AddValue(2, itemGUIDLow); + stmt.AddValue(3, owner); + stmt.AddValue(4, buyout); + stmt.AddValue(5, expire_time); + stmt.AddValue(6, bidder); + stmt.AddValue(7, bid); + stmt.AddValue(8, startbid); + stmt.AddValue(9, deposit); + trans.Append(stmt); + } + + public bool LoadFromDB(SQLFields fields) + { + Id = fields.Read(0); + auctioneer = fields.Read(1); + itemGUIDLow = fields.Read(2); + itemEntry = fields.Read(3); + itemCount = fields.Read(4); + owner = fields.Read(5); + buyout = fields.Read(6); + expire_time = fields.Read(7); + bidder = fields.Read(8); + bid = fields.Read(9); + startbid = fields.Read(10); + deposit = fields.Read(11); + + CreatureData auctioneerData = Global.ObjectMgr.GetCreatureData(auctioneer); + if (auctioneerData == null) + { + Log.outError(LogFilter.Server, "Auction {0} has not a existing auctioneer (GUID : {1})", Id, auctioneer); + return false; + } + + CreatureTemplate auctioneerInfo = Global.ObjectMgr.GetCreatureTemplate(auctioneerData.id); + if (auctioneerInfo == null) + { + Log.outError(LogFilter.Server, "Auction {0} has not a existing auctioneer (GUID : {1} Entry: {2})", Id, auctioneer, auctioneerData.id); + return false; + } + + factionTemplateId = auctioneerInfo.Faction; + auctionHouseEntry = Global.AuctionMgr.GetAuctionHouseEntry(factionTemplateId, ref houseId); + if (auctionHouseEntry == null) + { + Log.outError(LogFilter.Server, "Auction {0} has auctioneer (GUID : {1} Entry: {2}) with wrong faction {3}", Id, auctioneer, auctioneerData.id, factionTemplateId); + return false; + } + + // check if sold item exists for guid + // and itemEntry in fact (GetAItem will fail if problematic in result check in AuctionHouseMgr.LoadAuctionItems) + if (!Global.AuctionMgr.GetAItem(itemGUIDLow)) + { + Log.outError(LogFilter.Server, "Auction {0} has not a existing item : {1}", Id, itemGUIDLow); + return false; + } + return true; + } + + public bool LoadFromFieldList(SQLFields fields) + { + // Loads an AuctionEntry item from a field list. Unlike "LoadFromDB()", this one + // does not require the AuctionEntryMap to have been loaded with items. It simply + // acts as a wrapper to fill out an AuctionEntry struct from a field list + + Id = fields.Read(0); + auctioneer = fields.Read(1); + itemGUIDLow = fields.Read(2); + itemEntry = fields.Read(3); + itemCount = fields.Read(4); + owner = fields.Read(5); + buyout = fields.Read(6); + expire_time = fields.Read(7); + bidder = fields.Read(8); + bid = fields.Read(9); + startbid = fields.Read(10); + deposit = fields.Read(11); + + CreatureData auctioneerData = Global.ObjectMgr.GetCreatureData(auctioneer); + if (auctioneerData == null) + { + Log.outError(LogFilter.Server, "AuctionEntry:LoadFromFieldList() - Auction {0} has not a existing auctioneer (GUID : {1})", Id, auctioneer); + return false; + } + + CreatureTemplate auctioneerInfo = Global.ObjectMgr.GetCreatureTemplate(auctioneerData.id); + if (auctioneerInfo == null) + { + Log.outError(LogFilter.Server, "AuctionEntry:LoadFromFieldList() - Auction {0} has not a existing auctioneer (GUID : {1} Entry: {2})", Id, auctioneer, auctioneerData.id); + return false; + } + + factionTemplateId = auctioneerInfo.Faction; + auctionHouseEntry = Global.AuctionMgr.GetAuctionHouseEntry(factionTemplateId); + + if (auctionHouseEntry == null) + { + Log.outError(LogFilter.Server, "AuctionEntry:LoadFromFieldList() - Auction {0} has auctioneer (GUID : {1} Entry: {2}) with wrong faction {3}", Id, auctioneer, auctioneerData.id, factionTemplateId); + return false; + } + + return true; + } + + public string BuildAuctionMailSubject(MailAuctionAnswers response) + { + return string.Format("{0}:0:{1}:{2}:{3}", itemEntry, response, Id, itemCount); + } + + public static string BuildAuctionMailBody(ulong lowGuid, uint bid, uint buyout, uint deposit, uint cut) + { + return string.Format("{0}:{1}:{2}:{3}:{4}", lowGuid, bid, buyout, deposit, cut); + } + + // helpers + public uint GetHouseId() { return houseId; } + public uint GetHouseFaction() { return auctionHouseEntry.FactionID; } + + public uint Id; + public ulong auctioneer; // creature low guid + public ulong itemGUIDLow; + public uint itemEntry; + public uint itemCount; + public ulong owner; + public uint startbid; //maybe useless + public uint bid; + public uint buyout; + public long expire_time; + public ulong bidder; + public uint deposit; //deposit can be calculated only when creating auction + public uint etime; + uint houseId; + public AuctionHouseRecord auctionHouseEntry; // in AuctionHouse.dbc + public uint factionTemplateId; + } + + public class AuctionSearchFilters + { + public enum FilterType : uint + { + SkipClass = 0, + SkipSubclass = 0xFFFFFFFF, + SkipInvtype = 0xFFFFFFFF + } + + public Array Classes = new Array((int)ItemClass.Max); + + public class SubclassFilter + { + public FilterType SubclassMask = FilterType.SkipClass; + public Array InvTypes = new Array(ItemConst.MaxItemSubclassTotal); + } + } +} diff --git a/Game/BattleFields/BattleField.cs b/Game/BattleFields/BattleField.cs new file mode 100644 index 000000000..af21ac86d --- /dev/null +++ b/Game/BattleFields/BattleField.cs @@ -0,0 +1,1354 @@ +/* + * Copyright (C) 2012-2017 CypherCore + * + * 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, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +using Framework.Constants; +using Framework.GameMath; +using Game.DataStorage; +using Game.Entities; +using Game.Groups; +using Game.Maps; +using Game.Network; +using Game.Network.Packets; +using System; +using System.Collections.Generic; +using System.Diagnostics.Contracts; + +namespace Game.BattleFields +{ + public enum BattleFieldTypes + { + WinterGrasp, + TolBarad + } + + public class BattleField : ZoneScript + { + public BattleField() + { + m_IsEnabled = true; + m_DefenderTeam = TeamId.Neutral; + + m_TimeForAcceptInvite = 20; + m_uiKickDontAcceptTimer = 1000; + m_uiKickAfkPlayersTimer = 1000; + + m_LastResurectTimer = 30 * Time.InMilliseconds; + + for (byte i = 0; i < 2; ++i) + { + m_players[i] = new List(); + m_PlayersInQueue[i] = new List(); + m_PlayersInWar[i] = new List(); + m_InvitedPlayers[i] = new Dictionary(); + m_PlayersWillBeKick[i] = new Dictionary(); + m_Groups[i] = new List(); + } + } + + public void HandlePlayerEnterZone(Player player, uint zone) + { + // If battle is started, + // If not full of players > invite player to join the war + // If full of players > announce to player that BF is full and kick him after a few second if he desn't leave + if (IsWarTime()) + { + if (m_PlayersInWar[player.GetTeamId()].Count + m_InvitedPlayers[player.GetTeamId()].Count < m_MaxPlayer) // Vacant spaces + InvitePlayerToWar(player); + else // No more vacant places + { + // todo Send a packet to announce it to player + m_PlayersWillBeKick[player.GetTeamId()][player.GetGUID()] = Time.UnixTime + 10; + InvitePlayerToQueue(player); + } + } + else + { + // If time left is < 15 minutes invite player to join queue + if (m_Timer <= m_StartGroupingTimer) + InvitePlayerToQueue(player); + } + + // Add player in the list of player in zone + m_players[player.GetTeamId()].Add(player.GetGUID()); + OnPlayerEnterZone(player); + } + + // Called when a player leave the zone + public void HandlePlayerLeaveZone(Player player, uint zone) + { + if (IsWarTime()) + { + // If the player is participating to the battle + if (m_PlayersInWar[player.GetTeamId()].Contains(player.GetGUID())) + { + m_PlayersInWar[player.GetTeamId()].Remove(player.GetGUID()); + player.GetSession().SendBfLeaveMessage(GetQueueId(), GetState(), player.GetZoneId() == GetZoneId()); + Group group = player.GetGroup(); + if (group) // Remove the player from the raid group + group.RemoveMember(player.GetGUID()); + + OnPlayerLeaveWar(player); + } + } + + foreach (var capturePoint in m_capturePoints.Values) + capturePoint.HandlePlayerLeave(player); + + m_InvitedPlayers[player.GetTeamId()].Remove(player.GetGUID()); + m_PlayersWillBeKick[player.GetTeamId()].Remove(player.GetGUID()); + m_players[player.GetTeamId()].Remove(player.GetGUID()); + SendRemoveWorldStates(player); + RemovePlayerFromResurrectQueue(player.GetGUID()); + OnPlayerLeaveZone(player); + } + + public virtual bool Update(uint diff) + { + if (m_Timer <= diff) + { + // Battlefield ends on time + if (IsWarTime()) + EndBattle(true); + else // Time to start a new battle! + StartBattle(); + } + else + m_Timer -= diff; + + // Invite players a few minutes before the battle's beginning + if (!IsWarTime() && !m_StartGrouping && m_Timer <= m_StartGroupingTimer) + { + m_StartGrouping = true; + InvitePlayersInZoneToQueue(); + OnStartGrouping(); + } + + bool objective_changed = false; + if (IsWarTime()) + { + if (m_uiKickAfkPlayersTimer <= diff) + { + m_uiKickAfkPlayersTimer = 1000; + KickAfkPlayers(); + } + else + m_uiKickAfkPlayersTimer -= diff; + + // Kick players who chose not to accept invitation to the battle + if (m_uiKickDontAcceptTimer <= diff) + { + long now = Time.UnixTime; + for (int team = 0; team < SharedConst.BGTeamsCount; team++) + { + foreach (var pair in m_InvitedPlayers[team]) + if (pair.Value <= now) + KickPlayerFromBattlefield(pair.Key); + } + + InvitePlayersInZoneToWar(); + for (int team = 0; team < SharedConst.BGTeamsCount; team++) + { + foreach (var pair in m_PlayersWillBeKick[team]) + if (pair.Value <= now) + KickPlayerFromBattlefield(pair.Key); + } + + m_uiKickDontAcceptTimer = 1000; + } + else + m_uiKickDontAcceptTimer -= diff; + + foreach (var pair in m_capturePoints) + if (pair.Value.Update(diff)) + objective_changed = true; + } + + + if (m_LastResurectTimer <= diff) + { + for (byte i = 0; i < m_GraveyardList.Count; i++) + if (GetGraveyardById(i) != null) + m_GraveyardList[i].Resurrect(); + m_LastResurectTimer = BattlegroundConst.ResurrectionInterval; + } + else + m_LastResurectTimer -= diff; + + return objective_changed; + } + + void InvitePlayersInZoneToQueue() + { + for (byte team = 0; team < SharedConst.BGTeamsCount; ++team) + { + foreach (var guid in m_players[team]) + { + Player player = Global.ObjAccessor.FindPlayer(guid); + if (player) + InvitePlayerToQueue(player); + } + } + } + + void InvitePlayerToQueue(Player player) + { + if (m_PlayersInQueue[player.GetTeamId()].Contains(player.GetGUID())) + return; + + if (m_PlayersInQueue[player.GetTeamId()].Count <= m_MinPlayer || m_PlayersInQueue[GetOtherTeam(player.GetTeamId())].Count >= m_MinPlayer) + player.GetSession().SendBfInvitePlayerToQueue(GetQueueId(), GetState()); + } + + void InvitePlayersInQueueToWar() + { + for (byte team = 0; team < 2; ++team) + { + foreach (var guid in m_PlayersInQueue[team]) + { + Player player = Global.ObjAccessor.FindPlayer(guid); + if (player) + { + if (m_PlayersInWar[player.GetTeamId()].Count + m_InvitedPlayers[player.GetTeamId()].Count < m_MaxPlayer) + InvitePlayerToWar(player); + else + { + //Full + } + } + } + m_PlayersInQueue[team].Clear(); + } + } + + void InvitePlayersInZoneToWar() + { + for (byte team = 0; team < 2; ++team) + { + foreach (var guid in m_players[team]) + { + Player player = Global.ObjAccessor.FindPlayer(guid); + if (player) + { + if (m_PlayersInWar[player.GetTeamId()].Contains(player.GetGUID()) || m_InvitedPlayers[player.GetTeamId()].ContainsKey(player.GetGUID())) + continue; + if (m_PlayersInWar[player.GetTeamId()].Count + m_InvitedPlayers[player.GetTeamId()].Count < m_MaxPlayer) + InvitePlayerToWar(player); + else // Battlefield is full of players + m_PlayersWillBeKick[player.GetTeamId()][player.GetGUID()] = Time.UnixTime + 10; + } + } + } + } + + void InvitePlayerToWar(Player player) + { + if (!player) + return; + + // todo needed ? + if (player.IsInFlight()) + return; + + if (player.InArena() || player.GetBattleground()) + { + m_PlayersInQueue[player.GetTeamId()].Remove(player.GetGUID()); + return; + } + + // If the player does not match minimal level requirements for the battlefield, kick him + if (player.getLevel() < m_MinLevel) + { + if (!m_PlayersWillBeKick[player.GetTeamId()].ContainsKey(player.GetGUID())) + m_PlayersWillBeKick[player.GetTeamId()][player.GetGUID()] = Time.UnixTime + 10; + return; + } + + // Check if player is not already in war + if (m_PlayersInWar[player.GetTeamId()].Contains(player.GetGUID()) || m_InvitedPlayers[player.GetTeamId()].ContainsKey(player.GetGUID())) + return; + + m_PlayersWillBeKick[player.GetTeamId()].Remove(player.GetGUID()); + m_InvitedPlayers[player.GetTeamId()][player.GetGUID()] = Time.UnixTime + m_TimeForAcceptInvite; + player.GetSession().SendBfInvitePlayerToWar(GetQueueId(), m_ZoneId, m_TimeForAcceptInvite); + } + + public void InitStalker(uint entry, Position pos) + { + Creature creature = SpawnCreature(entry, pos); + if (creature) + StalkerGuid = creature.GetGUID(); + else + Log.outError(LogFilter.Battlefield, "Battlefield.InitStalker: could not spawn Stalker (Creature entry {0}), zone messeges will be un-available", entry); + } + + void KickAfkPlayers() + { + for (byte team = 0; team < 2; ++team) + { + foreach (var guid in m_PlayersInWar[team]) + { + Player player = Global.ObjAccessor.FindPlayer(guid); + if (player) + if (player.isAFK()) + KickPlayerFromBattlefield(guid); + } + } + } + + public void KickPlayerFromBattlefield(ObjectGuid guid) + { + Player player = Global.ObjAccessor.FindPlayer(guid); + if (player) + if (player.GetZoneId() == GetZoneId()) + player.TeleportTo(KickPosition); + } + + public void StartBattle() + { + if (m_isActive) + return; + + for (int team = 0; team < 2; team++) + { + m_PlayersInWar[team].Clear(); + m_Groups[team].Clear(); + } + + m_Timer = m_BattleTime; + m_isActive = true; + + InvitePlayersInZoneToWar(); + InvitePlayersInQueueToWar(); + + OnBattleStart(); + } + + public void EndBattle(bool endByTimer) + { + if (!m_isActive) + return; + + m_isActive = false; + + m_StartGrouping = false; + + if (!endByTimer) + SetDefenderTeam(GetAttackerTeam()); + + OnBattleEnd(endByTimer); + + // Reset battlefield timer + m_Timer = m_NoWarBattleTime; + SendInitWorldStatesToAll(); + } + + void DoPlaySoundToAll(uint soundID) + { + BroadcastPacketToWar(new PlaySound(ObjectGuid.Empty, soundID)); + } + + public bool HasPlayer(Player player) + { + return m_players[player.GetTeamId()].Contains(player.GetGUID()); + } + + // Called in WorldSession:HandleBfQueueInviteResponse + public void PlayerAcceptInviteToQueue(Player player) + { + // Add player in queue + m_PlayersInQueue[player.GetTeamId()].Add(player.GetGUID()); + // Send notification + player.GetSession().SendBfQueueInviteResponse(GetQueueId(), m_ZoneId, GetState()); + } + + // Called in WorldSession:HandleBfExitRequest + public void AskToLeaveQueue(Player player) + { + // Remove player from queue + m_PlayersInQueue[player.GetTeamId()].Remove(player.GetGUID()); + } + + // Called in WorldSession::HandleHearthAndResurrect + public void PlayerAskToLeave(Player player) + { + // Player leaving Wintergrasp, teleport to Dalaran. + // ToDo: confirm teleport destination. + player.TeleportTo(571, 5804.1499f, 624.7710f, 647.7670f, 1.6400f); + } + + // Called in WorldSession:HandleBfEntryInviteResponse + public void PlayerAcceptInviteToWar(Player player) + { + if (!IsWarTime()) + return; + + if (AddOrSetPlayerToCorrectBfGroup(player)) + { + player.GetSession().SendBfEntered(GetQueueId(), player.GetZoneId() != GetZoneId(), player.GetTeamId() == GetAttackerTeam()); + m_PlayersInWar[player.GetTeamId()].Add(player.GetGUID()); + m_InvitedPlayers[player.GetTeamId()].Remove(player.GetGUID()); + + if (player.isAFK()) + player.ToggleAFK(); + + OnPlayerJoinWar(player); //for scripting + } + } + + public void TeamCastSpell(uint teamIndex, int spellId) + { + foreach (var guid in m_PlayersInWar[teamIndex]) + { + Player player = Global.ObjAccessor.FindPlayer(guid); + if (player) + { + if (spellId > 0) + player.CastSpell(player, (uint)spellId, true); + else + player.RemoveAuraFromStack((uint)-spellId); + } + } + } + + public void BroadcastPacketToZone(ServerPacket data) + { + for (byte team = 0; team < 2; ++team) + { + foreach (var guid in m_players[team]) + { + Player player = Global.ObjAccessor.FindPlayer(guid); + if (player) + player.SendPacket(data); + } + } + } + + public void BroadcastPacketToQueue(ServerPacket data) + { + for (byte team = 0; team < 2; ++team) + { + foreach (var guid in m_PlayersInQueue[team]) + { + Player player = Global.ObjAccessor.FindPlayer(guid); + if (player) + player.SendPacket(data); + } + } + } + + public void BroadcastPacketToWar(ServerPacket data) + { + for (byte team = 0; team < 2; ++team) + { + foreach (var guid in m_PlayersInWar[team]) + { + Player player = Global.ObjAccessor.FindPlayer(guid); + if (player) + player.SendPacket(data); + } + } + } + + public void SendWarning(uint id, WorldObject target = null) + { + Creature stalker = GetCreature(StalkerGuid); + if (stalker) + Global.CreatureTextMgr.SendChat(stalker, (byte)id, target); + } + + public void SendUpdateWorldState(uint variable, uint value, bool hidden = false) + { + UpdateWorldState worldstate = new UpdateWorldState(); + worldstate.VariableID = variable; + worldstate.Value = (int)value; + worldstate.Hidden = hidden; + BroadcastPacketToZone(worldstate); + } + + public void AddCapturePoint(BfCapturePoint cp) + { + if (m_capturePoints.ContainsKey(cp.GetCapturePointEntry())) + Log.outError(LogFilter.Battlefield, "Battlefield.AddCapturePoint: CapturePoint {0} already exists!", cp.GetCapturePointEntry()); + + m_capturePoints[cp.GetCapturePointEntry()] = cp; + } + + BfCapturePoint GetCapturePoint(uint entry) + { + return m_capturePoints.LookupByKey(entry); + } + + public void RegisterZone(uint zoneId) + { + Global.BattleFieldMgr.AddZone(zoneId, this); + } + + public void HideNpc(Creature creature) + { + creature.CombatStop(); + creature.SetReactState(ReactStates.Passive); + creature.SetFlag(UnitFields.Flags, UnitFlags.NonAttackable | UnitFlags.NotSelectable); + creature.DisappearAndDie(); + creature.SetVisible(false); + } + + public void ShowNpc(Creature creature, bool aggressive) + { + creature.SetVisible(true); + creature.RemoveFlag(UnitFields.Flags, UnitFlags.NonAttackable | UnitFlags.NotSelectable); + if (!creature.IsAlive()) + creature.Respawn(true); + if (aggressive) + creature.SetReactState(ReactStates.Aggressive); + else + { + creature.SetFlag(UnitFields.Flags, UnitFlags.NonAttackable); + creature.SetReactState(ReactStates.Passive); + } + } + + // **************************************************** + // ******************* Group System ******************* + // **************************************************** + Group GetFreeBfRaid(int teamIndex) + { + foreach (var guid in m_Groups[teamIndex]) + { + Group group = Global.GroupMgr.GetGroupByGUID(guid); + if (group) + if (!group.IsFull()) + return group; + } + + return null; + } + + Group GetGroupPlayer(ObjectGuid plguid, int teamIndex) + { + foreach (var guid in m_Groups[teamIndex]) + { + Group group = Global.GroupMgr.GetGroupByGUID(guid); + if (group) + if (group.IsMember(plguid)) + return group; + } + + return null; + } + + bool AddOrSetPlayerToCorrectBfGroup(Player player) + { + if (!player.IsInWorld) + return false; + + Group oldgroup = player.GetGroup(); + if (oldgroup) + oldgroup.RemoveMember(player.GetGUID()); + + Group group = GetFreeBfRaid(player.GetTeamId()); + if (!group) + { + group = new Group(); + group.SetBattlefieldGroup(this); + group.Create(player); + Global.GroupMgr.AddGroup(group); + m_Groups[player.GetTeamId()].Add(group.GetGUID()); + } + else if (group.IsMember(player.GetGUID())) + { + byte subgroup = group.GetMemberGroup(player.GetGUID()); + player.SetBattlegroundOrBattlefieldRaid(group, subgroup); + } + else + group.AddMember(player); + + return true; + } + + //***************End of Group System******************* + + public BfGraveyard GetGraveyardById(int id) + { + if (id < m_GraveyardList.Count) + { + BfGraveyard graveyard = m_GraveyardList[id]; + if (graveyard != null) + return graveyard; + else + Log.outError(LogFilter.Battlefield, "Battlefield:GetGraveyardById Id: {0} not existed", id); + } + else + Log.outError(LogFilter.Battlefield, "Battlefield:GetGraveyardById Id: {0} cant be found", id); + + return null; + } + + public WorldSafeLocsRecord GetClosestGraveYard(Player player) + { + BfGraveyard closestGY = null; + float maxdist = -1; + for (byte i = 0; i < m_GraveyardList.Count; i++) + { + if (m_GraveyardList[i] != null) + { + if (m_GraveyardList[i].GetControlTeamId() != player.GetTeamId()) + continue; + + float dist = m_GraveyardList[i].GetDistance(player); + if (dist < maxdist || maxdist < 0) + { + closestGY = m_GraveyardList[i]; + maxdist = dist; + } + } + } + + if (closestGY != null) + return CliDB.WorldSafeLocsStorage.LookupByKey(closestGY.GetGraveyardId()); + + return null; + } + + public virtual void AddPlayerToResurrectQueue(ObjectGuid npcGuid, ObjectGuid playerGuid) + { + for (byte i = 0; i < m_GraveyardList.Count; i++) + { + if (m_GraveyardList[i] == null) + continue; + + if (m_GraveyardList[i].HasNpc(npcGuid)) + { + m_GraveyardList[i].AddPlayer(playerGuid); + break; + } + } + } + + public void RemovePlayerFromResurrectQueue(ObjectGuid playerGuid) + { + for (byte i = 0; i < m_GraveyardList.Count; i++) + { + if (m_GraveyardList[i] == null) + continue; + + if (m_GraveyardList[i].HasPlayer(playerGuid)) + { + m_GraveyardList[i].RemovePlayer(playerGuid); + break; + } + } + } + + public void SendAreaSpiritHealerQuery(Player player, ObjectGuid guid) + { + AreaSpiritHealerTime areaSpiritHealerTime = new AreaSpiritHealerTime(); + areaSpiritHealerTime.HealerGuid = guid; + areaSpiritHealerTime.TimeLeft = m_LastResurectTimer;// resurrect every 30 seconds + + player.SendPacket(areaSpiritHealerTime); + } + + public Creature SpawnCreature(uint entry, Position pos) + { + //Get map object + Map map = Global.MapMgr.CreateBaseMap(m_MapId); + if (!map) + { + Log.outError(LogFilter.Battlefield, "Battlefield:SpawnCreature: Can't create creature entry: {0} map not found", entry); + return null; + } + + CreatureTemplate cinfo = Global.ObjectMgr.GetCreatureTemplate(entry); + if (cinfo == null) + { + Log.outError(LogFilter.Battlefield, "Battlefield:SpawnCreature: entry {0} does not exist.", entry); + return null; + } + + float x, y, z, o; + pos.GetPosition(out x, out y, out z, out o); + + Creature creature = new Creature(); + if (!creature.Create(map.GenerateLowGuid(HighGuid.Creature), map, PhaseMasks.Normal, entry, x, y, z, o)) + { + Log.outError(LogFilter.Battlefield, "Battlefield:SpawnCreature: Can't create creature entry: {0}", entry); + return null; + } + + creature.SetHomePosition(x, y, z, o); + + // Set creature in world + map.AddToMap(creature); + creature.setActive(true); + + return creature; + } + + // Method for spawning gameobject on map + public GameObject SpawnGameObject(uint entry, Position pos, Quaternion rotation) + { + // Get map object + Map map = Global.MapMgr.CreateBaseMap(m_MapId); + if (!map) + return null; + + GameObjectTemplate goInfo = Global.ObjectMgr.GetGameObjectTemplate(entry); + if (goInfo == null) + { + Log.outError(LogFilter.Battlefield, "Battlefield.SpawnGameObject: GameObject template {0} not found in database! Battlefield not created!", entry); + return null; + } + + // Create gameobject + GameObject go = new GameObject(); + if (!go.Create(entry, map, PhaseMasks.Normal, pos, rotation, 255, GameObjectState.Ready)) + { + Log.outError(LogFilter.Battlefield, "Battlefield:SpawnGameObject: Cannot create gameobject template {1}! Battlefield not created!", entry); + return null; + } + + // Add to world + map.AddToMap(go); + go.setActive(true); + + return go; + } + + public Creature GetCreature(ObjectGuid guid) + { + if (!m_Map) + return null; + return m_Map.GetCreature(guid); + } + + public GameObject GetGameObject(ObjectGuid guid) + { + if (!m_Map) + return null; + return m_Map.GetGameObject(guid); + } + + // Call this to init the Battlefield + public virtual bool SetupBattlefield() { return true; } + + // Called when a Unit is kill in battlefield zone + public virtual void HandleKill(Player killer, Unit killed) { } + + public uint GetTypeId() { return m_TypeId; } + public uint GetZoneId() { return m_ZoneId; } + public ulong GetQueueId() { return MathFunctions.MakePair64(m_BattleId | 0x20000, 0x1F100000); } + + // Return true if battle is start, false if battle is not started + public bool IsWarTime() { return m_isActive; } + + BattlefieldState GetState() { return m_isActive ? BattlefieldState.InProgress : (m_Timer <= m_StartGroupingTimer ? BattlefieldState.Warnup : BattlefieldState.Inactive); } + + // Enable or Disable battlefield + public void ToggleBattlefield(bool enable) { m_IsEnabled = enable; } + + // Return if battlefield is enable + public bool IsEnabled() { return m_IsEnabled; } + + // All-purpose data storage 64 bit + public virtual ulong GetData64(int dataId) { return m_Data64[dataId]; } + public virtual void SetData64(int dataId, ulong value) { m_Data64[dataId] = value; } + + // All-purpose data storage 32 bit + public virtual uint GetData(int dataId) { return m_Data32[dataId]; } + public virtual void SetData(int dataId, uint value) { m_Data32[dataId] = value; } + public virtual void UpdateData(int index, int pad) + { + if (pad < 0) + m_Data32[index] -= (uint)-pad; + else + m_Data32[index] += (uint)pad; + } + + // Battlefield - generic methods + public uint GetDefenderTeam() { return m_DefenderTeam; } + public uint GetAttackerTeam() { return 1 - m_DefenderTeam; } + public int GetOtherTeam(int teamIndex) { return (teamIndex == TeamId.Horde ? TeamId.Alliance : TeamId.Horde); } + void SetDefenderTeam(uint team) { m_DefenderTeam = team; } + + // Called on start + public virtual void OnBattleStart() { } + // Called at the end of battle + public virtual void OnBattleEnd(bool endByTimer) { } + // Called x minutes before battle start when player in zone are invite to join queue + public virtual void OnStartGrouping() { } + // Called when a player accept to join the battle + public virtual void OnPlayerJoinWar(Player player) { } + // Called when a player leave the battle + public virtual void OnPlayerLeaveWar(Player player) { } + // Called when a player leave battlefield zone + public virtual void OnPlayerLeaveZone(Player player) { } + // Called when a player enter in battlefield zone + public virtual void OnPlayerEnterZone(Player player) { } + + public uint GetBattleId() { return m_BattleId; } + + public virtual void DoCompleteOrIncrementAchievement(uint achievement, Player player, byte incrementNumber = 1) { } + + // Send all worldstate data to all player in zone. + public virtual void SendInitWorldStatesToAll() { } + + public virtual void FillInitialWorldStates(InitWorldStates data) { } + + // Return if we can use mount in battlefield + public bool CanFlyIn() { return !m_isActive; } + + List GetGraveyardVector() { return m_GraveyardList; } + + public uint GetTimer() { return m_Timer; } + public void SetTimer(uint timer) { m_Timer = timer; } + + // use for switch off all worldstate for client + public virtual void SendRemoveWorldStates(Player player) { } + + public ObjectGuid StalkerGuid; + protected uint m_Timer; // Global timer for event + protected bool m_IsEnabled; + protected bool m_isActive; + protected uint m_DefenderTeam; + + // Map of the objectives belonging to this OutdoorPvP + Dictionary m_capturePoints = new Dictionary(); + + // Players info maps + protected List[] m_players = new List[2]; // Players in zone + protected List[] m_PlayersInQueue = new List[2]; // Players in the queue + protected List[] m_PlayersInWar = new List[2]; // Players in WG combat + protected Dictionary[] m_InvitedPlayers = new Dictionary[2]; + protected Dictionary[] m_PlayersWillBeKick = new Dictionary[2]; + + // Variables that must exist for each battlefield + protected uint m_TypeId; // See enum BattlefieldTypes + protected uint m_BattleId; // BattleID (for packet) + protected uint m_ZoneId; // ZoneID of Wintergrasp = 4197 + protected uint m_MapId; // MapId where is Battlefield + protected Map m_Map; + protected uint m_MaxPlayer; // Maximum number of player that participated to Battlefield + protected uint m_MinPlayer; // Minimum number of player for Battlefield start + protected uint m_MinLevel; // Required level to participate at Battlefield + protected uint m_BattleTime; // Length of a battle + protected uint m_NoWarBattleTime; // Time between two battles + protected uint m_RestartAfterCrash; // Delay to restart Wintergrasp if the server crashed during a running battle. + protected uint m_TimeForAcceptInvite; + protected uint m_uiKickDontAcceptTimer; + protected WorldLocation KickPosition; // Position where players are teleported if they switch to afk during the battle or if they don't accept invitation + + uint m_uiKickAfkPlayersTimer; // Timer for check Afk in war + + // Graveyard variables + protected List m_GraveyardList = new List(); // Vector witch contain the different GY of the battle + uint m_LastResurectTimer; // Timer for resurect player every 30 sec + + protected uint m_StartGroupingTimer; // Timer for invite players in area 15 minute before start battle + protected bool m_StartGrouping; // bool for know if all players in area has been invited + + List[] m_Groups = new List[2]; // Contain different raid group + + Dictionary m_Data64 = new Dictionary(); + protected Dictionary m_Data32 = new Dictionary(); + } + + public class BfGraveyard + { + public BfGraveyard(BattleField battlefield) + { + m_Bf = battlefield; + m_GraveyardId = 0; + m_ControlTeam = TeamId.Neutral; + m_SpiritGuide[0] = ObjectGuid.Empty; + m_SpiritGuide[1] = ObjectGuid.Empty; + } + + public void Initialize(uint startControl, uint graveyardId) + { + m_ControlTeam = startControl; + m_GraveyardId = graveyardId; + } + + public void SetSpirit(Creature spirit, int teamIndex) + { + if (!spirit) + { + Log.outError(LogFilter.Battlefield, "BfGraveyard:SetSpirit: Invalid Spirit."); + return; + } + + m_SpiritGuide[teamIndex] = spirit.GetGUID(); + spirit.SetReactState(ReactStates.Passive); + } + + public float GetDistance(Player player) + { + WorldSafeLocsRecord safeLoc = CliDB.WorldSafeLocsStorage.LookupByKey(m_GraveyardId); + return player.GetDistance2d(safeLoc.Loc.X, safeLoc.Loc.Y); + } + + public void AddPlayer(ObjectGuid playerGuid) + { + if (!m_ResurrectQueue.Contains(playerGuid)) + { + m_ResurrectQueue.Add(playerGuid); + Player player = Global.ObjAccessor.FindPlayer(playerGuid); + if (player) + player.CastSpell(player, BattlegroundConst.SpellWaitingForResurrect, true); + } + } + + public void RemovePlayer(ObjectGuid playerGuid) + { + m_ResurrectQueue.Remove(playerGuid); + + Player player = Global.ObjAccessor.FindPlayer(playerGuid); + if (player) + player.RemoveAurasDueToSpell(BattlegroundConst.SpellWaitingForResurrect); + } + + public void Resurrect() + { + if (m_ResurrectQueue.Empty()) + return; + + foreach (var guid in m_ResurrectQueue) + { + // Get player object from his guid + Player player = Global.ObjAccessor.FindPlayer(guid); + if (!player) + continue; + + // Check if the player is in world and on the good graveyard + if (player.IsInWorld) + { + Creature spirit = m_Bf.GetCreature(m_SpiritGuide[m_ControlTeam]); + if (spirit) + spirit.CastSpell(spirit, BattlegroundConst.SpellSpiritHeal, true); + } + + // Resurect player + player.CastSpell(player, BattlegroundConst.SpellResurrectionVisual, true); + player.ResurrectPlayer(1.0f); + player.CastSpell(player, 6962, true); + player.CastSpell(player, BattlegroundConst.SpellSpiritHealMana, true); + + player.SpawnCorpseBones(false); + } + + m_ResurrectQueue.Clear(); + } + + // For changing graveyard control + public void GiveControlTo(uint team) + { + // Guide switching + // Note: Visiblity changes are made by phasing + /*if (m_SpiritGuide[1 - team]) + m_SpiritGuide[1 - team].SetVisible(false); + if (m_SpiritGuide[team]) + m_SpiritGuide[team].SetVisible(true);*/ + + m_ControlTeam = team; + // Teleport to other graveyard, player witch were on this graveyard + RelocateDeadPlayers(); + } + + void RelocateDeadPlayers() + { + WorldSafeLocsRecord closestGrave = null; + foreach (var guid in m_ResurrectQueue) + { + Player player = Global.ObjAccessor.FindPlayer(guid); + if (!player) + continue; + + if (closestGrave != null) + player.TeleportTo(player.GetMapId(), closestGrave.Loc.X, closestGrave.Loc.Y, closestGrave.Loc.Z, player.GetOrientation()); + else + { + closestGrave = m_Bf.GetClosestGraveYard(player); + if (closestGrave != null) + player.TeleportTo(player.GetMapId(), closestGrave.Loc.X, closestGrave.Loc.Y, closestGrave.Loc.Z, player.GetOrientation()); + } + } + } + + public bool HasNpc(ObjectGuid guid) + { + if (m_SpiritGuide[TeamId.Alliance].IsEmpty() || m_SpiritGuide[TeamId.Horde].IsEmpty()) + return false; + + if (!m_Bf.GetCreature(m_SpiritGuide[TeamId.Alliance]) || + !m_Bf.GetCreature(m_SpiritGuide[TeamId.Horde])) + return false; + + return (m_SpiritGuide[TeamId.Alliance] == guid || m_SpiritGuide[TeamId.Horde] == guid); + } + + // Check if a player is in this graveyard's ressurect queue + public bool HasPlayer(ObjectGuid guid) { return m_ResurrectQueue.Contains(guid); } + + // Get the graveyard's ID. + public uint GetGraveyardId() { return m_GraveyardId; } + + public uint GetControlTeamId() { return m_ControlTeam; } + + uint m_ControlTeam; + uint m_GraveyardId; + ObjectGuid[] m_SpiritGuide = new ObjectGuid[SharedConst.BGTeamsCount]; + List m_ResurrectQueue = new List(); + protected BattleField m_Bf; + } + + public class BfCapturePoint + { + public BfCapturePoint(BattleField battlefield) + { + m_Bf = battlefield; + m_capturePointGUID = ObjectGuid.Empty; + m_team = TeamId.Neutral; + m_value = 0; + m_minValue = 0.0f; + m_maxValue = 0.0f; + m_State = BattleFieldObjectiveStates.Neutral; + m_OldState = BattleFieldObjectiveStates.Neutral; + m_capturePointEntry = 0; + m_neutralValuePct = 0; + m_maxSpeed = 0; + + m_activePlayers[0] = new List(); + m_activePlayers[1] = new List(); + } + + public virtual bool HandlePlayerEnter(Player player) + { + if (!m_capturePointGUID.IsEmpty()) + { + GameObject capturePoint = m_Bf.GetGameObject(m_capturePointGUID); + if (capturePoint) + { + player.SendUpdateWorldState(capturePoint.GetGoInfo().ControlZone.worldState1, 1); + player.SendUpdateWorldState(capturePoint.GetGoInfo().ControlZone.worldstate2, (uint)(Math.Ceiling((m_value + m_maxValue) / (2 * m_maxValue) * 100.0f))); + player.SendUpdateWorldState(capturePoint.GetGoInfo().ControlZone.worldstate3, m_neutralValuePct); + } + } + + m_activePlayers[player.GetTeamId()].Add(player.GetGUID()); + return true; + } + + //Index of place in for loop + public virtual int HandlePlayerLeave(Player player) + { + if (!m_capturePointGUID.IsEmpty()) + { + GameObject capturePoint = m_Bf.GetGameObject(m_capturePointGUID); + if (capturePoint) + player.SendUpdateWorldState(capturePoint.GetGoInfo().ControlZone.worldState1, 0); + } + + var index = m_activePlayers[player.GetTeamId()].IndexOf(player.GetGUID()); + + if (index == m_activePlayers[player.GetTeamId()].Count) + return m_activePlayers[player.GetTeamId()].Count; // return end() + + m_activePlayers[player.GetTeamId()].Remove(player.GetGUID()); + return ++index; + } + + public virtual void SendChangePhase() + { + if (m_capturePointGUID.IsEmpty()) + return; + + GameObject capturePoint = m_Bf.GetGameObject(m_capturePointGUID); + if (capturePoint) + { + // send this too, sometimes the slider disappears, dunno why :( + SendUpdateWorldState(capturePoint.GetGoInfo().ControlZone.worldState1, 1); + // send these updates to only the ones in this objective + SendUpdateWorldState(capturePoint.GetGoInfo().ControlZone.worldstate2, (uint)Math.Ceiling((m_value + m_maxValue) / (2 * m_maxValue) * 100.0f)); + // send this too, sometimes it resets :S + SendUpdateWorldState(capturePoint.GetGoInfo().ControlZone.worldstate3, m_neutralValuePct); + } + } + + public bool SetCapturePointData(GameObject capturePoint) + { + Contract.Assert(capturePoint); + + Log.outError(LogFilter.Battlefield, "Creating capture point {0}", capturePoint.GetEntry()); + + m_capturePointGUID = capturePoint.GetGUID(); + m_capturePointEntry = capturePoint.GetEntry(); + + // check info existence + GameObjectTemplate goinfo = capturePoint.GetGoInfo(); + if (goinfo.type != GameObjectTypes.ControlZone) + { + Log.outError(LogFilter.Server, "OutdoorPvP: GO {0} is not capture point!", capturePoint.GetEntry()); + return false; + } + + // get the needed values from goinfo + m_maxValue = goinfo.ControlZone.maxTime; + m_maxSpeed = m_maxValue / (goinfo.ControlZone.minTime != 0 ? goinfo.ControlZone.minTime : 60); + m_neutralValuePct = goinfo.ControlZone.neutralPercent; + m_minValue = m_maxValue * goinfo.ControlZone.neutralPercent / 100; + + if (m_team == TeamId.Alliance) + { + m_value = m_maxValue; + m_State = BattleFieldObjectiveStates.Alliance; + } + else + { + m_value = -m_maxValue; + m_State = BattleFieldObjectiveStates.Horde; + } + + return true; + } + + GameObject GetCapturePointGo() + { + return m_Bf.GetGameObject(m_capturePointGUID); + } + + bool DelCapturePoint() + { + if (!m_capturePointGUID.IsEmpty()) + { + GameObject capturePoint = m_Bf.GetGameObject(m_capturePointGUID); + if (capturePoint) + { + capturePoint.SetRespawnTime(0); // not save respawn time + capturePoint.Delete(); + capturePoint = null; + } + m_capturePointGUID.Clear(); + } + + return true; + } + + public virtual bool Update(uint diff) + { + if (m_capturePointGUID.IsEmpty()) + return false; + + GameObject capturePoint = m_Bf.GetGameObject(m_capturePointGUID); + if (capturePoint) + { + float radius = capturePoint.GetGoInfo().ControlZone.radius; + + for (byte team = 0; team < SharedConst.BGTeamsCount; ++team) + { + for (int i = 0; i < m_activePlayers[team].Count; ) + { + Player player = Global.ObjAccessor.FindPlayer(m_activePlayers[team][i]); + if (player) + { + if (!capturePoint.IsWithinDistInMap(player, radius) || !player.IsOutdoorPvPActive()) + i = HandlePlayerLeave(player); + else + ++i; + } + else + ++i; + } + } + + List players = new List(); + var checker = new AnyPlayerInObjectRangeCheck(capturePoint, radius); + var searcher = new PlayerListSearcher(capturePoint, players, checker); + Cell.VisitWorldObjects(capturePoint, searcher, radius); + + foreach (var player in players) + { + if (player.IsOutdoorPvPActive()) + { + m_activePlayers[player.GetTeamId()].Add(player.GetGUID()); + HandlePlayerEnter(player); + } + } + } + + // get the difference of numbers + float fact_diff = (m_activePlayers[TeamId.Alliance].Count - m_activePlayers[TeamId.Horde].Count) * diff / 1000; + if (MathFunctions.fuzzyEq(fact_diff, 0.0f)) + return false; + + Team Challenger = 0; + float maxDiff = m_maxSpeed * diff; + + if (fact_diff < 0) + { + // horde is in majority, but it's already horde-controlled . no change + if (m_State == BattleFieldObjectiveStates.Horde && m_value <= -m_maxValue) + return false; + + if (fact_diff < -maxDiff) + fact_diff = -maxDiff; + + Challenger = Team.Horde; + } + else + { + // ally is in majority, but it's already ally-controlled . no change + if (m_State == BattleFieldObjectiveStates.Alliance && m_value >= m_maxValue) + return false; + + if (fact_diff > maxDiff) + fact_diff = maxDiff; + + Challenger = Team.Alliance; + } + + float oldValue = m_value; + uint oldTeam = m_team; + + m_OldState = m_State; + + m_value += fact_diff; + + if (m_value < -m_minValue) // red + { + if (m_value < -m_maxValue) + m_value = -m_maxValue; + m_State = BattleFieldObjectiveStates.Horde; + m_team = TeamId.Horde; + } + else if (m_value > m_minValue) // blue + { + if (m_value > m_maxValue) + m_value = m_maxValue; + m_State = BattleFieldObjectiveStates.Alliance; + m_team = TeamId.Alliance; + } + else if (oldValue * m_value <= 0) // grey, go through mid point + { + // if challenger is ally, then n.a challenge + if (Challenger == Team.Alliance) + m_State = BattleFieldObjectiveStates.NeutralAllianceChallenge; + // if challenger is horde, then n.h challenge + else if (Challenger == Team.Horde) + m_State = BattleFieldObjectiveStates.NeutralHordeChallenge; + m_team = TeamId.Neutral; + } + else // grey, did not go through mid point + { + // old phase and current are on the same side, so one team challenges the other + if (Challenger == Team.Alliance && (m_OldState == BattleFieldObjectiveStates.Horde || m_OldState == BattleFieldObjectiveStates.NeutralHordeChallenge)) + m_State = BattleFieldObjectiveStates.HordeAllianceChallenge; + else if (Challenger == Team.Horde && (m_OldState == BattleFieldObjectiveStates.Alliance || m_OldState == BattleFieldObjectiveStates.NeutralAllianceChallenge)) + m_State = BattleFieldObjectiveStates.AllianceHordeChallenge; + m_team = TeamId.Neutral; + } + + if (MathFunctions.fuzzyNe(m_value, oldValue)) + SendChangePhase(); + + if (m_OldState != m_State) + { + if (oldTeam != m_team) + ChangeTeam(oldTeam); + return true; + } + + return false; + } + + void SendUpdateWorldState(uint field, uint value) + { + for (byte team = 0; team < SharedConst.BGTeamsCount; ++team) + { + foreach (var guid in m_activePlayers[team]) // send to all players present in the area + { + Player player = Global.ObjAccessor.FindPlayer(guid); + if (player) + player.SendUpdateWorldState(field, value); + } + } + } + + void SendObjectiveComplete(uint id, ObjectGuid guid) + { + uint team; + switch (m_State) + { + case BattleFieldObjectiveStates.Alliance: + team = TeamId.Alliance; + break; + case BattleFieldObjectiveStates.Horde: + team = TeamId.Horde; + break; + default: + return; + } + + // send to all players present in the area + foreach (var _guid in m_activePlayers[team]) + { + Player player = Global.ObjAccessor.FindPlayer(_guid); + if (player) + player.KilledMonsterCredit(id, guid); + } + } + + bool IsInsideObjective(Player player) + { + return m_activePlayers[player.GetTeamId()].Contains(player.GetGUID()); + } + + public virtual void ChangeTeam(uint oldTeam) { } + + public uint GetCapturePointEntry() { return m_capturePointEntry; } + uint GetTeamId() { return m_team; } + + // active Players in the area of the objective, 0 - alliance, 1 - horde + List[] m_activePlayers = new List[SharedConst.BGTeamsCount]; + + // Total shift needed to capture the objective + float m_maxValue; + float m_minValue; + + // Maximum speed of capture + float m_maxSpeed; + + // The status of the objective + float m_value; + protected uint m_team; + + // Objective states + BattleFieldObjectiveStates m_OldState; + BattleFieldObjectiveStates m_State; + + // Neutral value on capture bar + uint m_neutralValuePct; + + // Battlefield this objective belongs to + protected BattleField m_Bf; + + // Capture point entry + uint m_capturePointEntry; + + // Gameobject related to that capture point + ObjectGuid m_capturePointGUID; + } +} diff --git a/Game/BattleFields/BattleFieldManager.cs b/Game/BattleFields/BattleFieldManager.cs new file mode 100644 index 000000000..0af369f68 --- /dev/null +++ b/Game/BattleFields/BattleFieldManager.cs @@ -0,0 +1,155 @@ +/* + * Copyright (C) 2012-2017 CypherCore + * + * 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, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +using Game.Entities; +using Game.Maps; +using System.Collections.Generic; + +namespace Game.BattleFields +{ + public class BattleFieldManager : Singleton + { + BattleFieldManager() { } + + public void InitBattlefield() + { + BattleField wg = new BattlefieldWG(); + // respawn, init variables + if (!wg.SetupBattlefield()) + { + Log.outError(LogFilter.Battlefield, "Battlefield: Wintergrasp init failed."); + } + else + { + _battlefieldSet.Add(wg); + Log.outInfo(LogFilter.Battlefield, "Battlefield: Wintergrasp successfully initiated."); + } + + /* + For Cataclysm: Tol Barad + Battlefield* tb = new BattlefieldTB; + // respawn, init variables + if (!tb.SetupBattlefield()) + { + TC_LOG_DEBUG("bg.battlefield", "Battlefield: Tol Barad init failed."); + delete tb; + } + else + { + _battlefieldSet.push_back(tb); + TC_LOG_DEBUG("bg.battlefield", "Battlefield: Tol Barad successfully initiated."); + } + */ + } + + public void AddZone(uint zoneId, BattleField bf) + { + _battlefieldMap[zoneId] = bf; + } + + public void HandlePlayerEnterZone(Player player, uint zoneId) + { + var bf = _battlefieldMap.LookupByKey(zoneId); + if (bf == null) + return; + + if (!bf.IsEnabled() || bf.HasPlayer(player)) + return; + + bf.HandlePlayerEnterZone(player, zoneId); + Log.outDebug(LogFilter.Battlefield, "Player {0} entered battlefield id {1}", player.GetGUID().ToString(), bf.GetTypeId()); + } + + public void HandlePlayerLeaveZone(Player player, uint zoneId) + { + var bf = _battlefieldMap.LookupByKey(zoneId); + if (bf == null) + return; + + // teleport: remove once in removefromworld, once in updatezone + if (!bf.HasPlayer(player)) + return; + + bf.HandlePlayerLeaveZone(player, zoneId); + Log.outDebug(LogFilter.Battlefield, "Player {0} left battlefield id {1}", player.GetGUID().ToString(), bf.GetTypeId()); + } + + public BattleField GetBattlefieldToZoneId(uint zoneId) + { + var bf = _battlefieldMap.LookupByKey(zoneId); + if (bf == null) + { + // no handle for this zone, return + return null; + } + + if (!bf.IsEnabled()) + return null; + + return bf; + } + + public BattleField GetBattlefieldByBattleId(uint battleId) + { + foreach (var bf in _battlefieldSet) + { + if (bf.GetBattleId() == battleId) + return bf; + } + return null; + } + + public BattleField GetBattlefieldByQueueId(ulong queueId) + { + foreach (var bf in _battlefieldSet) + if (bf.GetQueueId() == queueId) + return bf; + + return null; + } + + ZoneScript GetZoneScript(uint zoneId) + { + var bf = _battlefieldMap.LookupByKey(zoneId); + if (bf != null) + return bf; + + return null; + } + + public void Update(uint diff) + { + _updateTimer += diff; + if (_updateTimer > 1000) + { + foreach (var bf in _battlefieldSet) + if (bf.IsEnabled()) + bf.Update(_updateTimer); + _updateTimer = 0; + } + } + + // contains all initiated battlefield events + // used when initing / cleaning up + List _battlefieldSet = new List(); + // maps the zone ids to an battlefield event + // used in player event handling + Dictionary _battlefieldMap = new Dictionary(); + // update interval + uint _updateTimer; + } +} diff --git a/Game/BattleFields/Zones/WinterGrasp.cs b/Game/BattleFields/Zones/WinterGrasp.cs new file mode 100644 index 000000000..ccfc8c5cf --- /dev/null +++ b/Game/BattleFields/Zones/WinterGrasp.cs @@ -0,0 +1,1705 @@ +/* + * Copyright (C) 2012-2017 CypherCore + * + * 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, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +using Framework.Constants; +using Game.DataStorage; +using Game.Entities; +using Game.Network.Packets; +using Game.Spells; +using System.Collections.Generic; +using System.Diagnostics.Contracts; + +namespace Game.BattleFields +{ + class BattlefieldWG : BattleField + { + public override bool SetupBattlefield() + { + InitStalker(WGNpcs.Stalker, WGConst.WintergraspStalkerPos); + + m_TypeId = (uint)BattleFieldTypes.WinterGrasp; // See enum BattlefieldTypes + m_BattleId = BattlefieldIds.WG; + m_ZoneId = WGConst.ZoneId; + m_MapId = WGConst.MapId; + m_Map = Global.MapMgr.FindMap(m_MapId, 0); + + m_MaxPlayer = WorldConfig.GetUIntValue(WorldCfg.WintergraspPlrMax); + m_IsEnabled = WorldConfig.GetBoolValue(WorldCfg.WintergraspEnable); + m_MinPlayer = WorldConfig.GetUIntValue(WorldCfg.WintergraspPlrMin); + m_MinLevel = WorldConfig.GetUIntValue(WorldCfg.WintergraspPlrMinLvl); + m_BattleTime = WorldConfig.GetUIntValue(WorldCfg.WintergraspBattletime) * Time.Minute * Time.InMilliseconds; + m_NoWarBattleTime = WorldConfig.GetUIntValue(WorldCfg.WintergraspNobattletime) * Time.Minute * Time.InMilliseconds; + m_RestartAfterCrash = WorldConfig.GetUIntValue(WorldCfg.WintergraspRestartAfterCrash) * Time.Minute * Time.InMilliseconds; + + m_TimeForAcceptInvite = 20; + m_StartGroupingTimer = 15 * Time.Minute * Time.InMilliseconds; + + KickPosition = new WorldLocation(m_MapId, 5728.117f, 2714.346f, 697.733f, 0); + + RegisterZone(m_ZoneId); + + for (var team = 0; team < SharedConst.BGTeamsCount; ++team) + { + KeepCreature[team] = new List(); + OutsideCreature[team] = new List(); + DefenderPortalList[team] = new List(); + m_vehicles[team] = new List(); + } + + m_saveTimer = 60000; + + // Load from db + if ((Global.WorldMgr.getWorldState(WGWorldStates.Active) == 0) && (Global.WorldMgr.getWorldState(WGWorldStates.Defender) == 0) && (Global.WorldMgr.getWorldState(WGConst.ClockWorldState[0]) == 0)) + { + Global.WorldMgr.setWorldState(WGWorldStates.Active, 0); + Global.WorldMgr.setWorldState(WGWorldStates.Defender, RandomHelper.URand(0, 1)); + Global.WorldMgr.setWorldState(WGConst.ClockWorldState[0], m_NoWarBattleTime); + } + + m_isActive = Global.WorldMgr.getWorldState(WGWorldStates.Active) != 0; + m_DefenderTeam = Global.WorldMgr.getWorldState(WGWorldStates.Defender); + + m_Timer = Global.WorldMgr.getWorldState(WGConst.ClockWorldState[0]); + if (m_isActive) + { + m_isActive = false; + m_Timer = m_RestartAfterCrash; + } + + SetData(WGData.WonA, Global.WorldMgr.getWorldState(WGWorldStates.AttackedA)); + SetData(WGData.DefA, Global.WorldMgr.getWorldState(WGWorldStates.DefendedA)); + SetData(WGData.WonH, Global.WorldMgr.getWorldState(WGWorldStates.AttackedH)); + SetData(WGData.DefH, Global.WorldMgr.getWorldState(WGWorldStates.DefendedH)); + + foreach (var gy in WGConst.WGGraveYard) + { + BfGraveyardWG graveyard = new BfGraveyardWG(this); + + // When between games, the graveyard is controlled by the defending team + if (gy.StartControl == TeamId.Neutral) + graveyard.Initialize(m_DefenderTeam, gy.GraveyardID); + else + graveyard.Initialize(gy.StartControl, gy.GraveyardID); + + graveyard.SetTextId(gy.TextId); + m_GraveyardList.Add(graveyard); + } + + + // Spawn workshop creatures and gameobjects + for (byte i = 0; i < WGConst.MaxWorkshops; i++) + { + WGWorkshop workshop = new WGWorkshop(this, i); + if (i < WGWorkshopIds.KeepWest) + workshop.GiveControlTo(GetAttackerTeam(), true); + else + workshop.GiveControlTo(GetDefenderTeam(), true); + + // Note: Capture point is added once the gameobject is created. + Workshops.Add(workshop); + } + + // Spawn NPCs in the defender's keep, both Horde and Alliance + foreach (var npc in WGConst.WGKeepNPC) + { + // Horde npc + Creature creature = SpawnCreature(npc.HordeEntry, npc.Pos); + if (creature) + KeepCreature[TeamId.Horde].Add(creature.GetGUID()); + // Alliance npc + creature = SpawnCreature(npc.AllianceEntry, npc.Pos); + if (creature) + KeepCreature[TeamId.Alliance].Add(creature.GetGUID()); + } + + // Hide NPCs from the Attacker's team in the keep + foreach (var guid in KeepCreature[GetAttackerTeam()]) + { + Creature creature = GetCreature(guid); + if (creature) + HideNpc(creature); + } + + // Spawn Horde NPCs outside the keep + for (var i = 0; i < WGConst.OutsideAllianceNpc; i++) + { + var npc = WGConst.WGOutsideNPC[i]; + Creature creature = SpawnCreature(npc.HordeEntry, npc.Pos); + if (creature) + OutsideCreature[TeamId.Horde].Add(creature.GetGUID()); + } + + // Spawn Alliance NPCs outside the keep + for (var i = WGConst.OutsideAllianceNpc; i < WGConst.MaxOutsideNpcs; i++) + { + var npc = WGConst.WGOutsideNPC[i]; + Creature creature = SpawnCreature(npc.AllianceEntry, npc.Pos); + if (creature) + OutsideCreature[TeamId.Alliance].Add(creature.GetGUID()); + } + + // Hide units outside the keep that are defenders + foreach (var guid in OutsideCreature[GetDefenderTeam()]) + { + Creature creature = GetCreature(guid); + if (creature) + HideNpc(creature); + } + + // Spawn turrets and hide them per default + foreach (var turret in WGConst.WGTurret) + { + Position towerCannonPos = turret.GetPosition(); + Creature creature = SpawnCreature(WGNpcs.TowerCannon, towerCannonPos); + if (creature) + { + CanonList.Add(creature.GetGUID()); + HideNpc(creature); + } + } + + // Spawn all gameobjects + foreach (var build in WGConst.WGGameObjectBuilding) + { + GameObject go = SpawnGameObject(build.Entry, build.Pos, build.Rot); + if (go) + { + BfWGGameObjectBuilding b = new BfWGGameObjectBuilding(this, build.BuildingType, build.WorldState); + b.Init(go); + if (!IsEnabled() && go.GetEntry() == WGGameObjects.VaultGate) + go.SetDestructibleState(GameObjectDestructibleState.Destroyed); + BuildingsInZone.Add(b); + } + } + + // Spawning portal defender + foreach (var teleporter in WGConst.WGPortalDefenderData) + { + GameObject go = SpawnGameObject(teleporter.AllianceEntry, teleporter.Pos, teleporter.Rot); + if (go) + { + DefenderPortalList[TeamId.Alliance].Add(go.GetGUID()); + go.SetRespawnTime((int)(GetDefenderTeam() == TeamId.Alliance ? BattlegroundConst.RespawnImmediately : BattlegroundConst.RespawnOneDay)); + } + go = SpawnGameObject(teleporter.HordeEntry, teleporter.Pos, teleporter.Rot); + if (go) + { + DefenderPortalList[TeamId.Horde].Add(go.GetGUID()); + go.SetRespawnTime((int)(GetDefenderTeam() == TeamId.Horde ? BattlegroundConst.RespawnImmediately : BattlegroundConst.RespawnOneDay)); + } + } + + UpdateCounterVehicle(true); + return true; + } + + public override bool Update(uint diff) + { + bool m_return = base.Update(diff); + if (m_saveTimer <= diff) + { + Global.WorldMgr.setWorldState(WGWorldStates.Active, m_isActive); + Global.WorldMgr.setWorldState(WGWorldStates.Defender, m_DefenderTeam); + Global.WorldMgr.setWorldState(WGConst.ClockWorldState[0], m_Timer); + Global.WorldMgr.setWorldState(WGWorldStates.AttackedA, GetData(WGData.WonA)); + Global.WorldMgr.setWorldState(WGWorldStates.DefendedA, GetData(WGData.DefA)); + Global.WorldMgr.setWorldState(WGWorldStates.AttackedH, GetData(WGData.WonH)); + Global.WorldMgr.setWorldState(WGWorldStates.DefendedH, GetData(WGData.DefH)); + m_saveTimer = 60 * Time.InMilliseconds; + } + else + m_saveTimer -= diff; + + return m_return; + } + + public override void OnBattleStart() + { + // Spawn titan relic + GameObject relic = SpawnGameObject(WGGameObjects.TitanSRelic, WGConst.RelicPos, WGConst.RelicRot); + if (relic) + { + // Update faction of relic, only attacker can click on + relic.SetFaction(WGConst.WintergraspFaction[GetAttackerTeam()]); + // Set in use (not allow to click on before last door is broken) + relic.SetFlag(GameObjectFields.Flags, GameObjectFlags.InUse | GameObjectFlags.NotSelectable); + m_titansRelicGUID = relic.GetGUID(); + } + else + Log.outError(LogFilter.Battlefield, "WG: Failed to spawn titan relic."); + + + // Update tower visibility and update faction + foreach (var guid in CanonList) + { + Creature creature = GetCreature(guid); + if (creature) + { + ShowNpc(creature, true); + creature.SetFaction(WGConst.WintergraspFaction[GetDefenderTeam()]); + } + } + + // Rebuild all wall + foreach (var wall in BuildingsInZone) + { + if (wall != null) + { + wall.Rebuild(); + wall.UpdateTurretAttack(false); + } + } + + SetData(WGData.BrokenTowerAtt, 0); + SetData(WGData.BrokenTowerDef, 0); + SetData(WGData.DamagedTowerAtt, 0); + SetData(WGData.DamagedTowerDef, 0); + + // Update graveyard (in no war time all graveyard is to deffender, in war time, depend of base) + foreach (var workShop in Workshops) + { + if (workShop != null) + workShop.UpdateGraveyardAndWorkshop(); + } + + for (byte team = 0; team < SharedConst.BGTeamsCount; ++team) + { + foreach (var guid in m_players[team]) + { + // Kick player in orb room, TODO: offline player ? + Player player = Global.ObjAccessor.FindPlayer(guid); + if (player) + { + float x, y, z; + player.GetPosition(out x, out y, out z); + if (5500 > x && x > 5392 && y < 2880 && y > 2800 && z < 480) + player.TeleportTo(571, 5349.8686f, 2838.481f, 409.240f, 0.046328f); + SendInitWorldStatesTo(player); + } + } + } + // Initialize vehicle counter + UpdateCounterVehicle(true); + // Send start warning to all players + SendWarning(WintergraspText.StartBattle); + } + + public void UpdateCounterVehicle(bool init) + { + if (init) + { + SetData(WGData.VehicleH, 0); + SetData(WGData.VehicleA, 0); + } + SetData(WGData.MaxVehicleH, 0); + SetData(WGData.MaxVehicleA, 0); + + foreach (var workshop in Workshops) + { + if (workshop.GetTeamControl() == TeamId.Alliance) + UpdateData(WGData.MaxVehicleA, 4); + else if (workshop.GetTeamControl() == TeamId.Horde) + UpdateData(WGData.MaxVehicleH, 4); + } + + UpdateVehicleCountWG(); + } + + public override void OnBattleEnd(bool endByTimer) + { + // Remove relic + if (!m_titansRelicGUID.IsEmpty()) + { + GameObject relic = GetGameObject(m_titansRelicGUID); + if (relic) + relic.RemoveFromWorld(); + } + m_titansRelicGUID.Clear(); + + // successful defense + if (endByTimer) + UpdateData(GetDefenderTeam() == TeamId.Horde ? WGData.DefH : WGData.DefA, 1); + // successful attack (note that teams have already been swapped, so defender team is the one who won) + else + UpdateData(GetDefenderTeam() == TeamId.Horde ? WGData.WonH : WGData.WonA, 1); + + // Remove turret + foreach (var guid in CanonList) + { + Creature creature = GetCreature(guid); + if (creature) + { + if (!endByTimer) + creature.SetFaction(WGConst.WintergraspFaction[GetDefenderTeam()]); + HideNpc(creature); + } + } + + if (!endByTimer) // One player triggered the relic + { + // Change all npc in keep + foreach (var guid in KeepCreature[GetAttackerTeam()]) + { + Creature creature = GetCreature(guid); + if (creature) + HideNpc(creature); + } + + foreach (var guid in KeepCreature[GetDefenderTeam()]) + { + Creature creature = GetCreature(guid); + if (creature) + ShowNpc(creature, true); + } + + // Change all npc out of keep + foreach (var guid in OutsideCreature[GetDefenderTeam()]) + { + Creature creature = GetCreature(guid); + if (creature) + HideNpc(creature); + } + + foreach (var guid in OutsideCreature[GetAttackerTeam()]) + { + Creature creature = GetCreature(guid); + if (creature) + ShowNpc(creature, true); + }; + } + + // Update all graveyard, control is to defender when no wartime + for (byte i = 0; i < WGGraveyardId.Horde; i++) + { + BfGraveyard graveyard = GetGraveyardById(i); + if (graveyard != null) + graveyard.GiveControlTo(GetDefenderTeam()); + } + + // Update portals + foreach (var guid in DefenderPortalList[GetDefenderTeam()]) + { + GameObject portal = GetGameObject(guid); + if (portal) + portal.SetRespawnTime((int)BattlegroundConst.RespawnImmediately); + } + + foreach (var guid in DefenderPortalList[GetAttackerTeam()]) + { + GameObject portal = GetGameObject(guid); + if (portal) + portal.SetRespawnTime((int)BattlegroundConst.RespawnOneDay); + } + + // Saving data + foreach (var obj in BuildingsInZone) + obj.Save(); + foreach (var workShop in Workshops) + workShop.Save(); + + foreach (var guid in m_PlayersInWar[GetDefenderTeam()]) + { + Player player = Global.ObjAccessor.FindPlayer(guid); + if (player) + { + player.CastSpell(player, WGSpells.EssenceOfWintergrasp, true); + player.CastSpell(player, WGSpells.VictoryReward, true); + // Complete victory quests + player.AreaExploredOrEventHappens(WintergraspQuests.VictoryAlliance); + player.AreaExploredOrEventHappens(WintergraspQuests.VictoryHorde); + // Send Wintergrasp victory achievement + DoCompleteOrIncrementAchievement(WGAchievements.WinWg, player); + // Award achievement for succeeding in Wintergrasp in 10 minutes or less + if (!endByTimer && GetTimer() <= 10000) + DoCompleteOrIncrementAchievement(WGAchievements.WinWgTimer10, player); + } + } + + foreach (var guid in m_PlayersInWar[GetAttackerTeam()]) + { + Player player = Global.ObjAccessor.FindPlayer(guid); + if (player) + player.CastSpell(player, WGSpells.DefeatReward, true); + } + + for (byte team = 0; team < SharedConst.BGTeamsCount; ++team) + { + foreach (var guid in m_PlayersInWar[team]) + { + Player player = Global.ObjAccessor.FindPlayer(guid); + if (player) + RemoveAurasFromPlayer(player); + } + + m_PlayersInWar[team].Clear(); + + foreach (var guid in m_vehicles[team]) + { + Creature creature = GetCreature(guid); + if (creature) + if (creature.IsVehicle()) + creature.DespawnOrUnsummon(); + } + + m_vehicles[team].Clear(); + } + + if (!endByTimer) + { + for (byte team = 0; team < SharedConst.BGTeamsCount; ++team) + { + foreach (var guid in m_players[team]) + { + Player player = Global.ObjAccessor.FindPlayer(guid); + if (player) + { + player.RemoveAurasDueToSpell(m_DefenderTeam == TeamId.Alliance ? WGSpells.HordeControlPhaseShift : WGSpells.AllianceControlPhaseShift, player.GetGUID()); + player.AddAura(m_DefenderTeam == TeamId.Horde ? WGSpells.HordeControlPhaseShift : WGSpells.AllianceControlPhaseShift, player); + } + } + } + } + + if (!endByTimer) // win alli/horde + SendWarning((GetDefenderTeam() == TeamId.Alliance) ? WintergraspText.FortressCaptureAlliance : WintergraspText.FortressCaptureHorde); + else // defend alli/horde + SendWarning((GetDefenderTeam() == TeamId.Alliance) ? WintergraspText.FortressDefendAlliance : WintergraspText.FortressDefendHorde); + } + + public override void DoCompleteOrIncrementAchievement(uint achievement, Player player, byte incrementNumber = 1) + { + AchievementRecord achievementEntry = CliDB.AchievementStorage.LookupByKey(achievement); + if (achievementEntry == null) + return; + + switch (achievement) + { + //removed by TC + //case ACHIEVEMENTS_WIN_WG_100: + //{ + // player.UpdateAchievementCriteria(); + //} + default: + { + if (player) + player.CompletedAchievement(achievementEntry); + break; + } + } + } + + public override void OnStartGrouping() + { + SendWarning(WintergraspText.StartGrouping); + } + + uint GetSpiritGraveyardId(uint areaId) + { + switch (areaId) + { + case WintergraspAreaIds.WintergraspFortress: + return WGGraveyardId.Keep; + case WintergraspAreaIds.TheSunkenRing: + return WGGraveyardId.WorkshopNE; + case WintergraspAreaIds.TheBrokenTemplate: + return WGGraveyardId.WorkshopNW; + case WintergraspAreaIds.WestparkWorkshop: + return WGGraveyardId.WorkshopSW; + case WintergraspAreaIds.EastparkWorkshop: + return WGGraveyardId.WorkshopSE; + case WintergraspAreaIds.Wintergrasp: + return WGGraveyardId.Alliance; + case WintergraspAreaIds.TheChilledQuagmire: + return WGGraveyardId.Horde; + default: + Log.outError(LogFilter.Battlefield, "BattlefieldWG.GetSpiritGraveyardId: Unexpected Area Id {0}", areaId); + break; + } + + return 0; + } + + public override void OnCreatureCreate(Creature creature) + { + // Accessing to db spawned creatures + switch (creature.GetEntry()) + { + case WGNpcs.DwarvenSpiritGuide: + case WGNpcs.TaunkaSpiritGuide: + { + int teamIndex = (creature.GetEntry() == WGNpcs.DwarvenSpiritGuide ? TeamId.Alliance : TeamId.Horde); + byte graveyardId = (byte)GetSpiritGraveyardId(creature.GetAreaId()); + if (m_GraveyardList[graveyardId] != null) + m_GraveyardList[graveyardId].SetSpirit(creature, teamIndex); + break; + } + } + + // untested code - not sure if it is valid. + if (IsWarTime()) + { + switch (creature.GetEntry()) + { + case WGNpcs.SiegeEngineAlliance: + case WGNpcs.SiegeEngineHorde: + case WGNpcs.Catapult: + case WGNpcs.Demolisher: + { + if (!creature.ToTempSummon() || creature.ToTempSummon().GetSummonerGUID().IsEmpty() || !Global.ObjAccessor.FindPlayer(creature.ToTempSummon().GetSummonerGUID())) + { + creature.DespawnOrUnsummon(); + return; + } + + Player creator = Global.ObjAccessor.FindPlayer(creature.ToTempSummon().GetSummonerGUID()); + int teamIndex = creator.GetTeamId(); + if (teamIndex == TeamId.Horde) + { + if (GetData(WGData.VehicleH) < GetData(WGData.MaxVehicleH)) + { + UpdateData(WGData.VehicleH, 1); + creature.AddAura(WGSpells.HordeFlag, creature); + m_vehicles[teamIndex].Add(creature.GetGUID()); + UpdateVehicleCountWG(); + } + else + { + creature.DespawnOrUnsummon(); + return; + } + } + else + { + if (GetData(WGData.VehicleA) < GetData(WGData.MaxVehicleA)) + { + UpdateData(WGData.VehicleA, 1); + creature.AddAura(WGSpells.AllianceFlag, creature); + m_vehicles[teamIndex].Add(creature.GetGUID()); + UpdateVehicleCountWG(); + } + else + { + creature.DespawnOrUnsummon(); + return; + } + } + + creature.CastSpell(creator, WGSpells.GrabPassenger, true); + break; + } + } + } + } + + public override void OnCreatureRemove(Creature c) { } + + public override void OnGameObjectCreate(GameObject go) + { + uint workshopId = 0; + + switch (go.GetEntry()) + { + case WGGameObjects.FactoryBannerNe: + workshopId = WGWorkshopIds.Ne; + break; + case WGGameObjects.FactoryBannerNw: + workshopId = WGWorkshopIds.Nw; + break; + case WGGameObjects.FactoryBannerSe: + workshopId = WGWorkshopIds.Se; + break; + case WGGameObjects.FactoryBannerSw: + workshopId = WGWorkshopIds.Sw; + break; + default: + return; + } + + foreach (var workshop in Workshops) + { + if (workshop.GetId() == workshopId) + { + WintergraspCapturePoint capturePoint = new WintergraspCapturePoint(this, GetAttackerTeam()); + + capturePoint.SetCapturePointData(go); + capturePoint.LinkToWorkshop(workshop); + AddCapturePoint(capturePoint); + break; + } + } + } + + public override void HandleKill(Player killer, Unit victim) + { + if (killer == victim) + return; + + bool again = false; + int killerTeam = killer.GetTeamId(); + if (victim.IsTypeId(TypeId.Player)) + { + foreach (var guid in m_PlayersInWar[killerTeam]) + { + Player player = Global.ObjAccessor.FindPlayer(guid); + if (player) + if (player.GetDistance2d(killer) < 40) + PromotePlayer(player); + } + return; + } + + foreach (var guid in KeepCreature[GetOtherTeam(killerTeam)]) + { + Creature creature = GetCreature(guid); + if (creature) + { + if (victim.GetEntry() == creature.GetEntry() && !again) + { + again = true; + foreach (var guid2 in m_PlayersInWar[killerTeam]) + { + Player player = Global.ObjAccessor.FindPlayer(guid2); + if (player) + if (player.GetDistance2d(killer) < 40.0f) + PromotePlayer(player); + } + } + } + } + // @todo Recent PvP activity worldstate + } + + bool FindAndRemoveVehicleFromList(Unit vehicle) + { + for (byte i = 0; i < SharedConst.BGTeamsCount; ++i) + { + if (m_vehicles[i].Contains(vehicle.GetGUID())) + { + m_vehicles[i].Remove(vehicle.GetGUID()); + if (i == TeamId.Horde) + UpdateData(WGData.VehicleH, -1); + else + UpdateData(WGData.VehicleA, -1); + return true; + } + } + return false; + } + + public override void OnUnitDeath(Unit unit) + { + if (IsWarTime()) + if (unit.IsVehicle()) + if (FindAndRemoveVehicleFromList(unit)) + UpdateVehicleCountWG(); + } + + // Update rank for player + void PromotePlayer(Player killer) + { + if (!m_isActive) + return; + // Updating rank of player + Aura aur = killer.GetAura(WGSpells.Recruit); + if (aur != null) + { + if (aur.GetStackAmount() >= 5) + { + killer.RemoveAura(WGSpells.Recruit); + killer.CastSpell(killer, WGSpells.Corporal, true); + Creature stalker = GetCreature(StalkerGuid); + if (stalker) + Global.CreatureTextMgr.SendChat(stalker, WintergraspText.RankCorporal, killer, ChatMsg.Addon, Language.Addon, CreatureTextRange.Normal, 0, Team.Other, false, killer); + } + else + killer.CastSpell(killer, WGSpells.Recruit, true); + } + else if ((aur = killer.GetAura(WGSpells.Corporal)) != null) + { + if (aur.GetStackAmount() >= 5) + { + killer.RemoveAura(WGSpells.Corporal); + killer.CastSpell(killer, WGSpells.Lieutenant, true); + Creature stalker = GetCreature(StalkerGuid); + if (stalker) + Global.CreatureTextMgr.SendChat(stalker, WintergraspText.RankFirstLieutenant, killer, ChatMsg.Addon, Language.Addon, CreatureTextRange.Normal, 0, Team.Other, false, killer); + } + else + killer.CastSpell(killer, WGSpells.Corporal, true); + } + } + + void RemoveAurasFromPlayer(Player player) + { + player.RemoveAurasDueToSpell(WGSpells.Recruit); + player.RemoveAurasDueToSpell(WGSpells.Corporal); + player.RemoveAurasDueToSpell(WGSpells.Lieutenant); + player.RemoveAurasDueToSpell(WGSpells.TowerControl); + player.RemoveAurasDueToSpell(WGSpells.SpiritualImmunity); + player.RemoveAurasDueToSpell(WGSpells.Tenacity); + player.RemoveAurasDueToSpell(WGSpells.EssenceOfWintergrasp); + player.RemoveAurasDueToSpell(WGSpells.WintergraspRestrictedFlightArea); + } + + public override void OnPlayerJoinWar(Player player) + { + RemoveAurasFromPlayer(player); + + player.CastSpell(player, WGSpells.Recruit, true); + + if (player.GetZoneId() != m_ZoneId) + { + if (player.GetTeamId() == GetDefenderTeam()) + player.TeleportTo(571, 5345, 2842, 410, 3.14f); + else + { + if (player.GetTeamId() == TeamId.Horde) + player.TeleportTo(571, 5025.857422f, 3674.628906f, 362.737122f, 4.135169f); + else + player.TeleportTo(571, 5101.284f, 2186.564f, 373.549f, 3.812f); + } + } + + UpdateTenacity(); + + if (player.GetTeamId() == GetAttackerTeam()) + { + if (GetData(WGData.BrokenTowerAtt) < 3) + player.SetAuraStack(WGSpells.TowerControl, player, 3 - GetData(WGData.BrokenTowerAtt)); + } + else + { + if (GetData(WGData.BrokenTowerAtt) > 0) + player.SetAuraStack(WGSpells.TowerControl, player, GetData(WGData.BrokenTowerAtt)); + } + SendInitWorldStatesTo(player); + } + + public override void OnPlayerLeaveWar(Player player) + { + // Remove all aura from WG /// @todo false we can go out of this zone on retail and keep Rank buff, remove on end of WG + if (!player.GetSession().PlayerLogout()) + { + Creature vehicle = player.GetVehicleCreatureBase(); + if (vehicle) // Remove vehicle of player if he go out. + vehicle.DespawnOrUnsummon(); + + RemoveAurasFromPlayer(player); + } + + player.RemoveAurasDueToSpell(WGSpells.HordeControlsFactoryPhaseShift); + player.RemoveAurasDueToSpell(WGSpells.AllianceControlsFactoryPhaseShift); + player.RemoveAurasDueToSpell(WGSpells.HordeControlPhaseShift); + player.RemoveAurasDueToSpell(WGSpells.AllianceControlPhaseShift); + } + + public override void OnPlayerLeaveZone(Player player) + { + if (!m_isActive) + RemoveAurasFromPlayer(player); + + player.RemoveAurasDueToSpell(WGSpells.HordeControlsFactoryPhaseShift); + player.RemoveAurasDueToSpell(WGSpells.AllianceControlsFactoryPhaseShift); + player.RemoveAurasDueToSpell(WGSpells.HordeControlPhaseShift); + player.RemoveAurasDueToSpell(WGSpells.AllianceControlPhaseShift); + } + + public override void OnPlayerEnterZone(Player player) + { + if (!m_isActive) + RemoveAurasFromPlayer(player); + + player.AddAura(m_DefenderTeam == TeamId.Horde ? WGSpells.HordeControlPhaseShift : WGSpells.AllianceControlPhaseShift, player); + // Send worldstate to player + SendInitWorldStatesTo(player); + } + + public override uint GetData(uint data) + { + switch (data) + { + // Used to determine when the phasing spells must be cast + // See: SpellArea.IsFitToRequirements + case WintergraspAreaIds.TheSunkenRing: + case WintergraspAreaIds.TheBrokenTemplate: + case WintergraspAreaIds.WestparkWorkshop: + case WintergraspAreaIds.EastparkWorkshop: + // Graveyards and Workshops are controlled by the same team. + BfGraveyard graveyard = GetGraveyardById((int)GetSpiritGraveyardId(data)); + if (graveyard != null) + return graveyard.GetControlTeamId(); + break; + default: + break; + } + + return base.GetData(data); + } + + public override void FillInitialWorldStates(InitWorldStates packet) + { + packet.AddState(WGWorldStates.Attacker, (int)GetAttackerTeam()); + packet.AddState(WGWorldStates.Defender, (int)GetDefenderTeam()); + // Note: cleanup these two, their names look awkward + packet.AddState(WGWorldStates.Active, IsWarTime()); + packet.AddState(WGWorldStates.ShowWorldstate, IsWarTime()); + + for (uint i = 0; i < 2; ++i) + packet.AddState(WGConst.ClockWorldState[i], (int)(Time.UnixTime + (m_Timer / 1000))); + + packet.AddState(WGWorldStates.VehicleH, (int)GetData(WGData.VehicleH)); + packet.AddState(WGWorldStates.MaxVehicleH, (int)GetData(WGData.MaxVehicleH)); + packet.AddState(WGWorldStates.VehicleA, (int)GetData(WGData.VehicleA)); + packet.AddState(WGWorldStates.MaxVehicleA, (int)GetData(WGData.MaxVehicleA)); + + foreach (BfWGGameObjectBuilding building in BuildingsInZone) + building.FillInitialWorldStates(packet); + + foreach (WGWorkshop workshop in Workshops) + workshop.FillInitialWorldStates(packet); + } + + void SendInitWorldStatesTo(Player player) + { + InitWorldStates packet = new InitWorldStates(); + packet.AreaID = m_ZoneId; + packet.MapID = m_MapId; + packet.SubareaID = 0; + + FillInitialWorldStates(packet); + + player.SendPacket(packet); + } + + public override void SendInitWorldStatesToAll() + { + for (byte team = 0; team < SharedConst.BGTeamsCount; team++) + { + foreach (var guid in m_players[team]) + { + Player player = Global.ObjAccessor.FindPlayer(guid); + if (player) + SendInitWorldStatesTo(player); + } + } + } + + public void BrokenWallOrTower(uint team) { } + + // Called when a tower is broke + public void UpdatedDestroyedTowerCount(uint team) + { + // Southern tower + if (team == GetAttackerTeam()) + { + // Update counter + UpdateData(WGData.DamagedTowerAtt, -1); + UpdateData(WGData.BrokenTowerAtt, 1); + + // Remove buff stack on attackers + foreach (var guid in m_PlayersInWar[GetAttackerTeam()]) + { + Player player = Global.ObjAccessor.FindPlayer(guid); + if (player) + player.RemoveAuraFromStack(WGSpells.TowerControl); + } + + // Add buff stack to defenders and give achievement/quest credit + foreach (var guid in m_PlayersInWar[GetDefenderTeam()]) + { + Player player = Global.ObjAccessor.FindPlayer(guid); + if (player) + { + player.CastSpell(player, WGSpells.TowerControl, true); + player.KilledMonsterCredit(WintergraspQuests.CreditTowersDestroyed); + DoCompleteOrIncrementAchievement(WGAchievements.WgTowerDestroy, player); + } + } + + // If all three south towers are destroyed (ie. all attack towers), remove ten minutes from battle time + if (GetData(WGData.BrokenTowerAtt) == 3) + { + if ((int)(m_Timer - 600000) < 0) + m_Timer = 0; + else + m_Timer -= 600000; + SendInitWorldStatesToAll(); + } + } + else // Keep tower + { + UpdateData(WGData.DamagedTowerDef, -1); + UpdateData(WGData.BrokenTowerDef, 1); + } + } + + public override void ProcessEvent(WorldObject obj, uint eventId) + { + if (!obj || !IsWarTime()) + return; + + // We handle only gameobjects here + GameObject go = obj.ToGameObject(); + if (!go) + return; + + // On click on titan relic + if (go.GetEntry() == WGGameObjects.TitanSRelic) + { + GameObject relic = GetRelic(); + if (CanInteractWithRelic()) + EndBattle(false); + else if (relic) + relic.SetRespawnTime(0); + } + + // if destroy or damage event, search the wall/tower and update worldstate/send warning message + foreach (var building in BuildingsInZone) + { + if (go.GetGUID() == building.GetGUID()) + { + GameObject buildingGo = GetGameObject(building.GetGUID()); + if (buildingGo) + { + if (buildingGo.GetGoInfo().DestructibleBuilding.DamagedEvent == eventId) + building.Damaged(); + + if (buildingGo.GetGoInfo().DestructibleBuilding.DestroyedEvent == eventId) + building.Destroyed(); + + break; + } + } + } + } + + // Called when a tower is damaged, used for honor reward calcul + public void UpdateDamagedTowerCount(uint team) + { + if (team == GetAttackerTeam()) + UpdateData(WGData.DamagedTowerAtt, 1); + else + UpdateData(WGData.DamagedTowerDef, 1); + } + + // Update vehicle count WorldState to player + void UpdateVehicleCountWG() + { + SendUpdateWorldState(WGWorldStates.VehicleH, GetData(WGData.VehicleH)); + SendUpdateWorldState(WGWorldStates.MaxVehicleH, GetData(WGData.MaxVehicleH)); + SendUpdateWorldState(WGWorldStates.VehicleA, GetData(WGData.VehicleA)); + SendUpdateWorldState(WGWorldStates.MaxVehicleA, GetData(WGData.MaxVehicleA)); + } + + void UpdateTenacity() + { + int teamIndex = TeamId.Neutral; + int alliancePlayers = m_PlayersInWar[TeamId.Alliance].Count; + int hordePlayers = m_PlayersInWar[TeamId.Horde].Count; + int newStack = 0; + + if (alliancePlayers != 0 && hordePlayers != 0) + { + if (alliancePlayers < hordePlayers) + newStack = (int)(((float)(hordePlayers / alliancePlayers) - 1) * 4); // positive, should cast on alliance + else if (alliancePlayers > hordePlayers) + newStack = (int)((1 - (float)(alliancePlayers / hordePlayers)) * 4); // negative, should cast on horde + } + + if (newStack == m_tenacityStack) + return; + + if (m_tenacityStack > 0 && newStack <= 0) // old buff was on alliance + teamIndex = TeamId.Alliance; + else if (newStack >= 0) // old buff was on horde + teamIndex = TeamId.Horde; + + m_tenacityStack = (uint)newStack; + // Remove old buff + if (teamIndex != TeamId.Neutral) + { + foreach (var guid in m_players[teamIndex]) + { + Player player = Global.ObjAccessor.FindPlayer(guid); + if (player) + if (player.getLevel() >= m_MinLevel) + player.RemoveAurasDueToSpell(WGSpells.Tenacity); + } + + foreach (var guid in m_vehicles[teamIndex]) + { + Creature creature = GetCreature(guid); + if (creature) + creature.RemoveAurasDueToSpell(WGSpells.TenacityVehicle); + } + } + + // Apply new buff + if (newStack != 0) + { + teamIndex = newStack > 0 ? TeamId.Alliance : TeamId.Horde; + + if (newStack < 0) + newStack = -newStack; + if (newStack > 20) + newStack = 20; + + uint buff_honor = WGSpells.GreatestHonor; + if (newStack < 15) + buff_honor = WGSpells.GreaterHonor; + if (newStack < 10) + buff_honor = WGSpells.GreatHonor; + if (newStack < 5) + buff_honor = 0; + + foreach (var guid in m_PlayersInWar[teamIndex]) + { + Player player = Global.ObjAccessor.FindPlayer(guid); + if (player) + player.SetAuraStack(WGSpells.Tenacity, player, (uint)newStack); + } + + foreach (var guid in m_vehicles[teamIndex]) + { + Creature creature = GetCreature(guid); + if (creature) + creature.SetAuraStack(WGSpells.TenacityVehicle, creature, (uint)newStack); + } + + if (buff_honor != 0) + { + foreach (var guid in m_PlayersInWar[teamIndex]) + { + Player player = Global.ObjAccessor.FindPlayer(guid); + if (player) + player.CastSpell(player, buff_honor, true); + } + + foreach (var guid in m_vehicles[teamIndex]) + { + Creature creature = GetCreature(guid); + if (creature) + creature.CastSpell(creature, buff_honor, true); + } + } + } + } + + public GameObject GetRelic() { return GetGameObject(m_titansRelicGUID); } + + // Define relic object + void SetRelic(ObjectGuid relicGUID) { m_titansRelicGUID = relicGUID; } + + // Check if players can interact with the relic (Only if the last door has been broken) + bool CanInteractWithRelic() { return m_isRelicInteractible; } + + // Define if player can interact with the relic + public void SetRelicInteractible(bool allow) { m_isRelicInteractible = allow; } + + + bool m_isRelicInteractible; + + List Workshops = new List(); + + List[] DefenderPortalList = new List[SharedConst.BGTeamsCount]; + List BuildingsInZone = new List(); + + List[] m_vehicles = new List[SharedConst.BGTeamsCount]; + List CanonList = new List(); + List[] KeepCreature = new List[SharedConst.BGTeamsCount]; + List[] OutsideCreature = new List[SharedConst.BGTeamsCount]; + + uint m_tenacityStack; + uint m_saveTimer; + + ObjectGuid m_titansRelicGUID; + } + + class BfWGGameObjectBuilding + { + public BfWGGameObjectBuilding(BattlefieldWG WG, WGGameObjectBuildingType type, uint worldState) + { + _wg = WG; + _teamControl = TeamId.Neutral; + _type = type; + _worldState = worldState; + _state = WGGameObjectState.None; + + for (var i = 0; i < 2; ++i) + { + m_GameObjectList[i] = new List(); + m_CreatureBottomList[i] = new List(); + m_CreatureTopList[i] = new List(); + } + } + + public void Rebuild() + { + switch (_type) + { + case WGGameObjectBuildingType.KeepTower: + case WGGameObjectBuildingType.DoorLast: + case WGGameObjectBuildingType.Door: + case WGGameObjectBuildingType.Wall: + _teamControl = _wg.GetDefenderTeam(); // Objects that are part of the keep should be the defender's + break; + case WGGameObjectBuildingType.Tower: + _teamControl = _wg.GetAttackerTeam(); // The towers in the south should be the attacker's + break; + default: + _teamControl = TeamId.Neutral; + break; + } + + GameObject build = _wg.GetGameObject(_buildGUID); + if (build) + { + // Rebuild gameobject + if (build.IsDestructibleBuilding()) + { + build.SetDestructibleState(GameObjectDestructibleState.Rebuilding, null, true); + if (build.GetEntry() == WGGameObjects.VaultGate) + { + GameObject go = build.FindNearestGameObject(WGGameObjects.KeepCollisionWall, 50.0f); + if (go) + go.SetGoState(GameObjectState.Ready); + } + + // Update worldstate + _state = WGGameObjectState.AllianceIntact - ((int)_teamControl * 3); + _wg.SendUpdateWorldState(_worldState, (uint)_state); + } + UpdateCreatureAndGo(); + build.SetFaction(WGConst.WintergraspFaction[_teamControl]); + } + } + + // Called when associated gameobject is damaged + public void Damaged() + { + // Update worldstate + _state = WGGameObjectState.AllianceDamage - ((int)_teamControl * 3); + _wg.SendUpdateWorldState(_worldState, (uint)_state); + + // Send warning message + if (_staticTowerInfo != null) // tower damage + name + _wg.SendWarning(_staticTowerInfo.DamagedTextId); + + foreach (var guid in m_CreatureTopList[_wg.GetAttackerTeam()]) + { + Creature creature = _wg.GetCreature(guid); + if (creature) + _wg.HideNpc(creature); + } + + foreach (var guid in m_TurretTopList) + { + Creature creature = _wg.GetCreature(guid); + if (creature) + _wg.HideNpc(creature); + } + + if (_type == WGGameObjectBuildingType.KeepTower) + _wg.UpdateDamagedTowerCount(_wg.GetDefenderTeam()); + else if (_type == WGGameObjectBuildingType.Tower) + _wg.UpdateDamagedTowerCount(_wg.GetAttackerTeam()); + } + + // Called when associated gameobject is destroyed + public void Destroyed() + { + // Update worldstate + _state = WGGameObjectState.AllianceDestroy - ((int)_teamControl * 3); + _wg.SendUpdateWorldState(_worldState, (uint)_state); + + // Warn players + if (_staticTowerInfo != null) + _wg.SendWarning(_staticTowerInfo.DestroyedTextId); + + switch (_type) + { + // Inform the global wintergrasp script of the destruction of this object + case WGGameObjectBuildingType.Tower: + case WGGameObjectBuildingType.KeepTower: + _wg.UpdatedDestroyedTowerCount(_teamControl); + break; + case WGGameObjectBuildingType.DoorLast: + GameObject build = _wg.GetGameObject(_buildGUID); + if (build) + { + GameObject go = build.FindNearestGameObject(WGGameObjects.KeepCollisionWall, 50.0f); + if (go) + go.SetGoState(GameObjectState.Active); + } + _wg.SetRelicInteractible(true); + if (_wg.GetRelic()) + _wg.GetRelic().RemoveFlag(GameObjectFields.Flags, GameObjectFlags.InUse | GameObjectFlags.NotSelectable); + else + Log.outError(LogFilter.Server, "BattlefieldWG: Titan Relic not found."); + break; + } + + _wg.BrokenWallOrTower(_teamControl); + } + + public void Init(GameObject go) + { + if (!go) + return; + + // GameObject associated to object + _buildGUID = go.GetGUID(); + + switch (_type) + { + case WGGameObjectBuildingType.KeepTower: + case WGGameObjectBuildingType.DoorLast: + case WGGameObjectBuildingType.Door: + case WGGameObjectBuildingType.Wall: + _teamControl = _wg.GetDefenderTeam(); // Objects that are part of the keep should be the defender's + break; + case WGGameObjectBuildingType.Tower: + _teamControl = _wg.GetAttackerTeam(); // The towers in the south should be the attacker's + break; + default: + _teamControl = TeamId.Neutral; + break; + } + + _state = (WGGameObjectState)Global.WorldMgr.getWorldState(_worldState); + switch (_state) + { + case WGGameObjectState.NeutralIntact: + case WGGameObjectState.AllianceIntact: + case WGGameObjectState.HordeIntact: + go.SetDestructibleState(GameObjectDestructibleState.Rebuilding, null, true); + break; + case WGGameObjectState.NeutralDestroy: + case WGGameObjectState.AllianceDestroy: + case WGGameObjectState.HordeDestroy: + go.SetDestructibleState(GameObjectDestructibleState.Destroyed); + break; + case WGGameObjectState.NeutralDamage: + case WGGameObjectState.AllianceDamage: + case WGGameObjectState.HordeDamage: + go.SetDestructibleState(GameObjectDestructibleState.Damaged); + break; + } + + int towerId = -1; + switch (go.GetEntry()) + { + case WGGameObjects.FortressTower1: + towerId = 0; + break; + case WGGameObjects.FortressTower2: + towerId = 1; + break; + case WGGameObjects.FortressTower3: + towerId = 2; + break; + case WGGameObjects.FortressTower4: + towerId = 3; + break; + case WGGameObjects.ShadowsightTower: + towerId = 4; + break; + case WGGameObjects.WinterSEdgeTower: + towerId = 5; + break; + case WGGameObjects.FlamewatchTower: + towerId = 6; + break; + } + + if (towerId > 3) // Attacker towers + { + // Spawn associate gameobjects + foreach (var gobData in WGConst.AttackTowers[towerId - 4].GameObject) + { + GameObject goHorde = _wg.SpawnGameObject(gobData.HordeEntry, gobData.Pos, gobData.Rot); + if (goHorde) + m_GameObjectList[TeamId.Horde].Add(goHorde.GetGUID()); + + GameObject goAlliance = _wg.SpawnGameObject(gobData.AllianceEntry, gobData.Pos, gobData.Rot); + if (goAlliance) + m_GameObjectList[TeamId.Alliance].Add(goAlliance.GetGUID()); + } + + // Spawn associate npc bottom + foreach (var creatureData in WGConst.AttackTowers[towerId - 4].CreatureBottom) + { + Creature creature = _wg.SpawnCreature(creatureData.HordeEntry, creatureData.Pos); + if (creature) + m_CreatureBottomList[TeamId.Horde].Add(creature.GetGUID()); + + creature = _wg.SpawnCreature(creatureData.AllianceEntry, creatureData.Pos); + if (creature) + m_CreatureBottomList[TeamId.Alliance].Add(creature.GetGUID()); + } + } + + if (towerId >= 0) + { + _staticTowerInfo = WGConst.TowerData[towerId]; + + // Spawn Turret bottom + foreach (var turretPos in WGConst.TowerCannon[towerId].TowerCannonBottom) + { + Creature turret = _wg.SpawnCreature(WGNpcs.TowerCannon, turretPos); + if (turret) + { + m_TowerCannonBottomList.Add(turret.GetGUID()); + switch (go.GetEntry()) + { + case WGGameObjects.FortressTower1: + case WGGameObjects.FortressTower2: + case WGGameObjects.FortressTower3: + case WGGameObjects.FortressTower4: + turret.SetFaction(WGConst.WintergraspFaction[_wg.GetDefenderTeam()]); + break; + case WGGameObjects.ShadowsightTower: + case WGGameObjects.WinterSEdgeTower: + case WGGameObjects.FlamewatchTower: + turret.SetFaction(WGConst.WintergraspFaction[_wg.GetAttackerTeam()]); + break; + } + _wg.HideNpc(turret); + } + } + + // Spawn Turret top + foreach (var towerCannonPos in WGConst.TowerCannon[towerId].TurretTop) + { + Creature turret = _wg.SpawnCreature(WGNpcs.TowerCannon, towerCannonPos); + if (turret) + { + m_TurretTopList.Add(turret.GetGUID()); + switch (go.GetEntry()) + { + case WGGameObjects.FortressTower1: + case WGGameObjects.FortressTower2: + case WGGameObjects.FortressTower3: + case WGGameObjects.FortressTower4: + turret.SetFaction(WGConst.WintergraspFaction[_wg.GetDefenderTeam()]); + break; + case WGGameObjects.ShadowsightTower: + case WGGameObjects.WinterSEdgeTower: + case WGGameObjects.FlamewatchTower: + turret.SetFaction(WGConst.WintergraspFaction[_wg.GetAttackerTeam()]); + break; + } + _wg.HideNpc(turret); + } + } + UpdateCreatureAndGo(); + } + } + + void UpdateCreatureAndGo() + { + foreach (var guid in m_CreatureTopList[_wg.GetDefenderTeam()]) + { + Creature creature = _wg.GetCreature(guid); + if (creature) + _wg.HideNpc(creature); + } + + foreach (var guid in m_CreatureTopList[_wg.GetAttackerTeam()]) + { + Creature creature = _wg.GetCreature(guid); + if (creature) + _wg.ShowNpc(creature, true); + } + + foreach (var guid in m_CreatureBottomList[_wg.GetDefenderTeam()]) + { + Creature creature = _wg.GetCreature(guid); + if (creature) + _wg.HideNpc(creature); + } + + foreach (var guid in m_CreatureBottomList[_wg.GetAttackerTeam()]) + { + Creature creature = _wg.GetCreature(guid); + if (creature) + _wg.ShowNpc(creature, true); + } + + foreach (var guid in m_GameObjectList[_wg.GetDefenderTeam()]) + { + GameObject obj = _wg.GetGameObject(guid); + if (obj) + obj.SetRespawnTime(Time.Day); + } + + foreach (var guid in m_GameObjectList[_wg.GetAttackerTeam()]) + { + GameObject obj = _wg.GetGameObject(guid); + if (obj) + obj.SetRespawnTime(0); + } + } + + public void UpdateTurretAttack(bool disable) + { + foreach (var guid in m_TowerCannonBottomList) + { + Creature creature = _wg.GetCreature(guid); + if (creature) + { + GameObject build = _wg.GetGameObject(_buildGUID); + if (build) + { + if (disable) + _wg.HideNpc(creature); + else + _wg.ShowNpc(creature, true); + + switch (build.GetEntry()) + { + case WGGameObjects.FortressTower1: + case WGGameObjects.FortressTower2: + case WGGameObjects.FortressTower3: + case WGGameObjects.FortressTower4: + { + creature.SetFaction(WGConst.WintergraspFaction[_wg.GetDefenderTeam()]); + break; + } + case WGGameObjects.ShadowsightTower: + case WGGameObjects.WinterSEdgeTower: + case WGGameObjects.FlamewatchTower: + { + creature.SetFaction(WGConst.WintergraspFaction[_wg.GetAttackerTeam()]); + break; + } + } + } + } + } + + foreach (var guid in m_TurretTopList) + { + Creature creature = _wg.GetCreature(guid); + if (creature) + { + GameObject build = _wg.GetGameObject(_buildGUID); + if (build) + { + if (disable) + _wg.HideNpc(creature); + else + _wg.ShowNpc(creature, true); + + switch (build.GetEntry()) + { + case WGGameObjects.FortressTower1: + case WGGameObjects.FortressTower2: + case WGGameObjects.FortressTower3: + case WGGameObjects.FortressTower4: + { + creature.SetFaction(WGConst.WintergraspFaction[_wg.GetDefenderTeam()]); + break; + } + case WGGameObjects.ShadowsightTower: + case WGGameObjects.WinterSEdgeTower: + case WGGameObjects.FlamewatchTower: + { + creature.SetFaction(WGConst.WintergraspFaction[_wg.GetAttackerTeam()]); + break; + } + } + } + } + } + } + + public void FillInitialWorldStates(InitWorldStates packet) + { + packet.AddState(_worldState, (int)_state); + } + + public void Save() + { + Global.WorldMgr.setWorldState(_worldState, (ulong)_state); + } + + public ObjectGuid GetGUID() { return _buildGUID; } + + // WG object + BattlefieldWG _wg; + + // Linked gameobject + ObjectGuid _buildGUID; + + // the team that controls this point + uint _teamControl; + + WGGameObjectBuildingType _type; + uint _worldState; + + WGGameObjectState _state; + + StaticWintergraspTowerInfo _staticTowerInfo; + + // GameObject associations + List[] m_GameObjectList = new List[SharedConst.BGTeamsCount]; + + // Creature associations + List[] m_CreatureBottomList = new List[SharedConst.BGTeamsCount]; + List[] m_CreatureTopList = new List[SharedConst.BGTeamsCount]; + List m_TowerCannonBottomList = new List(); + List m_TurretTopList = new List(); + } + + class WGWorkshop + { + public WGWorkshop(BattlefieldWG wg, byte type) + { + _wg = wg; + _state = WGGameObjectState.None; + _teamControl = TeamId.Neutral; + _staticInfo = WGConst.WorkshopData[type]; + } + + public byte GetId() + { + return _staticInfo.WorkshopId; + } + + public void GiveControlTo(uint teamId, bool init) + { + switch (teamId) + { + case TeamId.Neutral: + { + // Send warning message to all player to inform a faction attack to a workshop + // alliance / horde attacking a workshop + _wg.SendWarning(_teamControl != 0 ? _staticInfo.HordeAttackTextId : _staticInfo.AllianceAttackTextId); + break; + } + case TeamId.Alliance: + { + // Updating worldstate + _state = WGGameObjectState.AllianceIntact; + _wg.SendUpdateWorldState(_staticInfo.WorldStateId, (uint)_state); + + // Warning message + if (!init) + _wg.SendWarning(_staticInfo.AllianceCaptureTextId); // workshop taken - alliance + + // Found associate graveyard and update it + if (_staticInfo.WorkshopId < WGWorkshopIds.KeepWest) + { + BfGraveyard gy = _wg.GetGraveyardById(_staticInfo.WorkshopId); + if (gy != null) + gy.GiveControlTo(TeamId.Alliance); + } + _teamControl = teamId; + break; + } + case TeamId.Horde: + { + // Update worldstate + _state = WGGameObjectState.HordeIntact; + _wg.SendUpdateWorldState(_staticInfo.WorldStateId, (uint)_state); + + // Warning message + if (!init) + _wg.SendWarning(_staticInfo.HordeCaptureTextId); // workshop taken - horde + + // Update graveyard control + if (_staticInfo.WorkshopId < WGWorkshopIds.KeepWest) + { + BfGraveyard gy = _wg.GetGraveyardById(_staticInfo.WorkshopId); + if (gy != null) + gy.GiveControlTo(TeamId.Horde); + } + + _teamControl = teamId; + break; + } + } + + if (!init) + _wg.UpdateCounterVehicle(false); + } + + public void UpdateGraveyardAndWorkshop() + { + if (_staticInfo.WorkshopId < WGWorkshopIds.KeepWest) + _wg.GetGraveyardById(_staticInfo.WorkshopId).GiveControlTo(_teamControl); + else + GiveControlTo(_wg.GetDefenderTeam(), true); + } + + public void FillInitialWorldStates(InitWorldStates packet) + { + packet.AddState(_staticInfo.WorldStateId, (int)_state); + } + + public void Save() + { + Global.WorldMgr.setWorldState(_staticInfo.WorldStateId, (uint)_state); + } + + public uint GetTeamControl() { return _teamControl; } + + BattlefieldWG _wg; // Pointer to wintergrasp + //ObjectGuid _buildGUID; + WGGameObjectState _state; // For worldstate + uint _teamControl; // Team witch control the workshop + + StaticWintergraspWorkshopInfo _staticInfo; + } + + class WintergraspCapturePoint : BfCapturePoint + { + public WintergraspCapturePoint(BattlefieldWG battlefield, uint teamInControl) + : base(battlefield) + { + m_Bf = battlefield; + m_team = teamInControl; + } + + public void LinkToWorkshop(WGWorkshop workshop) { m_Workshop = workshop; } + + public override void ChangeTeam(uint oldteam) + { + Contract.Assert(m_Workshop != null); + m_Workshop.GiveControlTo(m_team, false); + } + uint GetTeam() { return m_team; } + + protected WGWorkshop m_Workshop; + } + + class BfGraveyardWG : BfGraveyard + { + public BfGraveyardWG(BattlefieldWG battlefield) + : base(battlefield) + { + m_Bf = battlefield; + m_GossipTextId = 0; + } + + public void SetTextId(int textid) { m_GossipTextId = textid; } + int GetTextId() { return m_GossipTextId; } + + protected int m_GossipTextId; + } +} diff --git a/Game/BattleFields/Zones/WinterGraspConst.cs b/Game/BattleFields/Zones/WinterGraspConst.cs new file mode 100644 index 000000000..f680af961 --- /dev/null +++ b/Game/BattleFields/Zones/WinterGraspConst.cs @@ -0,0 +1,836 @@ +/* + * Copyright (C) 2012-2017 CypherCore + * + * 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, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +using Framework.Constants; +using Framework.GameMath; +using Game.Entities; + +namespace Game.BattleFields +{ + static class WGConst + { + public const uint ZoneId = 4197; // Wintergrasp + public const uint MapId = 571; // Northrend + + public const byte MaxOutsideNpcs = 14; + public const byte OutsideAllianceNpc = 7; + public const byte MaxWorkshops = 6; + + #region Data + public static BfWGCoordGY[] WGGraveYard = + { + new BfWGCoordGY(5104.750f, 2300.940f, 368.579f, 0.733038f, 1329, WGGossipText.GYNE, TeamId.Neutral), + new BfWGCoordGY(5099.120f, 3466.036f, 368.484f, 5.317802f, 1330, WGGossipText.GYNW, TeamId.Neutral), + new BfWGCoordGY(4314.648f, 2408.522f, 392.642f, 6.268125f, 1333, WGGossipText.GYSE, TeamId.Neutral), + new BfWGCoordGY(4331.716f, 3235.695f, 390.251f, 0.008500f, 1334, WGGossipText.GYSW, TeamId.Neutral), + new BfWGCoordGY(5537.986f, 2897.493f, 517.057f, 4.819249f, 1285, WGGossipText.GYKeep, TeamId.Neutral), + new BfWGCoordGY(5032.454f, 3711.382f, 372.468f, 3.971623f, 1331, WGGossipText.GYHorde, TeamId.Horde), + new BfWGCoordGY(5140.790f, 2179.120f, 390.950f, 1.972220f, 1332, WGGossipText.GYAlliance, TeamId.Alliance), + }; + + public static uint[] ClockWorldState = { 3781, 4354 }; + public static uint[] WintergraspFaction = { 1732, 1735, 35 }; + + public static Position WintergraspStalkerPos = new Position(4948.985f, 2937.789f, 550.5172f, 1.815142f); + + public static Position RelicPos = new Position(5440.379f, 2840.493f, 430.2816f, -1.832595f); + public static Quaternion RelicRot = new Quaternion(0.0f, 0.0f, -0.7933531f, 0.6087617f); + + //Destructible (Wall, Tower..) + public static WintergraspBuildingSpawnData[] WGGameObjectBuilding = + { + // Wall (Not spawned in db) + // Entry WS X Y Z O rX rY rZ rW Type + new WintergraspBuildingSpawnData(190219, 3749, 5371.457f, 3047.472f, 407.5710f, 3.14159300f, 0.0f, 0.0f, -1.000000000f, 0.00000000, WGGameObjectBuildingType.Wall), + new WintergraspBuildingSpawnData(190220, 3750, 5331.264f, 3047.105f, 407.9228f, 0.05235888f, 0.0f, 0.0f, 0.026176450f, 0.99965730, WGGameObjectBuildingType.Wall), + new WintergraspBuildingSpawnData(191795, 3764, 5385.841f, 2909.490f, 409.7127f, 0.00872424f, 0.0f, 0.0f, 0.004362106f, 0.99999050, WGGameObjectBuildingType.Wall), + new WintergraspBuildingSpawnData(191796, 3772, 5384.452f, 2771.835f, 410.2704f, 3.14159300f, 0.0f, 0.0f, -1.000000000f, 0.00000000, WGGameObjectBuildingType.Wall), + new WintergraspBuildingSpawnData(191799, 3762, 5371.436f, 2630.610f, 408.8163f, 3.13285800f, 0.0f, 0.0f, 0.999990500f, 0.00436732, WGGameObjectBuildingType.Wall), + new WintergraspBuildingSpawnData(191800, 3766, 5301.838f, 2909.089f, 409.8661f, 0.00872424f, 0.0f, 0.0f, 0.004362106f, 0.99999050, WGGameObjectBuildingType.Wall), + new WintergraspBuildingSpawnData(191801, 3770, 5301.063f, 2771.411f, 409.9014f, 3.14159300f, 0.0f, 0.0f, -1.000000000f, 0.00000000, WGGameObjectBuildingType.Wall), + new WintergraspBuildingSpawnData(191802, 3751, 5280.197f, 2995.583f, 408.8249f, 1.61442800f, 0.0f, 0.0f, 0.722363500f, 0.69151360, WGGameObjectBuildingType.Wall), + new WintergraspBuildingSpawnData(191803, 3752, 5279.136f, 2956.023f, 408.6041f, 1.57079600f, 0.0f, 0.0f, 0.707106600f, 0.70710690, WGGameObjectBuildingType.Wall), + new WintergraspBuildingSpawnData(191804, 3767, 5278.685f, 2882.513f, 409.5388f, 1.57079600f, 0.0f, 0.0f, 0.707106600f, 0.70710690, WGGameObjectBuildingType.Wall), + new WintergraspBuildingSpawnData(191806, 3769, 5279.502f, 2798.945f, 409.9983f, 1.57079600f, 0.0f, 0.0f, 0.707106600f, 0.70710690, WGGameObjectBuildingType.Wall), + new WintergraspBuildingSpawnData(191807, 3759, 5279.937f, 2724.766f, 409.9452f, 1.56207000f, 0.0f, 0.0f, 0.704014800f, 0.71018530, WGGameObjectBuildingType.Wall), + new WintergraspBuildingSpawnData(191808, 3760, 5279.601f, 2683.786f, 409.8488f, 1.55334100f, 0.0f, 0.0f, 0.700908700f, 0.71325110, WGGameObjectBuildingType.Wall), + new WintergraspBuildingSpawnData(191809, 3761, 5330.955f, 2630.777f, 409.2826f, 3.13285800f, 0.0f, 0.0f, 0.999990500f, 0.00436732, WGGameObjectBuildingType.Wall), + new WintergraspBuildingSpawnData(190369, 3753, 5256.085f, 2933.963f, 409.3571f, 3.13285800f, 0.0f, 0.0f, 0.999990500f, 0.00436732, WGGameObjectBuildingType.Wall), + new WintergraspBuildingSpawnData(190370, 3758, 5257.463f, 2747.327f, 409.7427f, -3.13285800f, 0.0f, 0.0f, -0.999990500f, 0.00436732, WGGameObjectBuildingType.Wall), + new WintergraspBuildingSpawnData(190371, 3754, 5214.960f, 2934.089f, 409.1905f, -0.00872424f, 0.0f, 0.0f, -0.004362106f, 0.99999050, WGGameObjectBuildingType.Wall), + new WintergraspBuildingSpawnData(190372, 3757, 5215.821f, 2747.566f, 409.1884f, -3.13285800f, 0.0f, 0.0f, -0.999990500f, 0.00436732, WGGameObjectBuildingType.Wall), + new WintergraspBuildingSpawnData(190374, 3755, 5162.273f, 2883.043f, 410.2556f, 1.57952200f, 0.0f, 0.0f, 0.710185100f, 0.70401500, WGGameObjectBuildingType.Wall), + new WintergraspBuildingSpawnData(190376, 3756, 5163.724f, 2799.838f, 409.2270f, 1.57952200f, 0.0f, 0.0f, 0.710185100f, 0.70401500, WGGameObjectBuildingType.Wall), + + // Tower of keep (Not spawned in db) + new WintergraspBuildingSpawnData(190221, 3711, 5281.154f, 3044.588f, 407.8434f, 3.115388f, 0.0f, 0.0f, 0.9999142f, 0.013101960, WGGameObjectBuildingType.KeepTower), // NW + new WintergraspBuildingSpawnData(190373, 3713, 5163.757f, 2932.228f, 409.1904f, 3.124123f, 0.0f, 0.0f, 0.9999619f, 0.008734641, WGGameObjectBuildingType.KeepTower), // SW + new WintergraspBuildingSpawnData(190377, 3714, 5166.397f, 2748.368f, 409.1884f, -1.570796f, 0.0f, 0.0f, -0.7071066f, 0.707106900, WGGameObjectBuildingType.KeepTower), // SE + new WintergraspBuildingSpawnData(190378, 3712, 5281.192f, 2632.479f, 409.0985f, -1.588246f, 0.0f, 0.0f, -0.7132492f, 0.700910500, WGGameObjectBuildingType.KeepTower), // NE + + // Wall (with passage) (Not spawned in db) + new WintergraspBuildingSpawnData(191797, 3765, 5343.290f, 2908.860f, 409.5757f, 0.00872424f, 0.0f, 0.0f, 0.004362106f, 0.9999905, WGGameObjectBuildingType.Wall), + new WintergraspBuildingSpawnData(191798, 3771, 5342.719f, 2771.386f, 409.6249f, 3.14159300f, 0.0f, 0.0f, -1.000000000f, 0.0000000, WGGameObjectBuildingType.Wall), + new WintergraspBuildingSpawnData(191805, 3768, 5279.126f, 2840.797f, 409.7826f, 1.57952200f, 0.0f, 0.0f, 0.710185100f, 0.7040150, WGGameObjectBuildingType.Wall), + + // South tower (Not spawned in db) + new WintergraspBuildingSpawnData(190356, 3704, 4557.173f, 3623.943f, 395.8828f, 1.675516f, 0.0f, 0.0f, 0.7431450f, 0.669130400, WGGameObjectBuildingType.Tower), // W + new WintergraspBuildingSpawnData(190357, 3705, 4398.172f, 2822.497f, 405.6270f, -3.124123f, 0.0f, 0.0f, -0.9999619f, 0.008734641, WGGameObjectBuildingType.Tower), // S + new WintergraspBuildingSpawnData(190358, 3706, 4459.105f, 1944.326f, 434.9912f, -2.002762f, 0.0f, 0.0f, -0.8422165f, 0.539139500, WGGameObjectBuildingType.Tower), // E + + // Door of forteress (Not spawned in db) + new WintergraspBuildingSpawnData(WGGameObjects.FortressGate, 3763, 5162.991f, 2841.232f, 410.1892f, -3.132858f, 0.0f, 0.0f, -0.9999905f, 0.00436732, WGGameObjectBuildingType.Door), + + // Last door (Not spawned in db) + new WintergraspBuildingSpawnData(WGGameObjects.VaultGate, 3773, 5397.108f, 2841.54f, 425.9014f, 3.141593f, 0.0f, 0.0f, -1.0f, 0.0f, WGGameObjectBuildingType.DoorLast), + }; + + public static StaticWintergraspTowerInfo[] TowerData = + { + new StaticWintergraspTowerInfo(WintergraspTowerIds.FortressNW, WintergraspText.NwKeeptowerDamage, WintergraspText.NwKeeptowerDestroy), + new StaticWintergraspTowerInfo(WintergraspTowerIds.FortressSW,WintergraspText.SwKeeptowerDamage,WintergraspText.SwKeeptowerDestroy), + new StaticWintergraspTowerInfo(WintergraspTowerIds.FortressSE,WintergraspText.SeKeeptowerDamage,WintergraspText.SeKeeptowerDestroy), + new StaticWintergraspTowerInfo(WintergraspTowerIds.FortressNE,WintergraspText.NeKeeptowerDamage,WintergraspText.NeKeeptowerDestroy), + new StaticWintergraspTowerInfo(WintergraspTowerIds.Shadowsight,WintergraspText.WesternTowerDamage,WintergraspText.WesternTowerDestroy), + new StaticWintergraspTowerInfo(WintergraspTowerIds.WintersEdge,WintergraspText.SouthernTowerDamage,WintergraspText.SouthernTowerDestroy), + new StaticWintergraspTowerInfo(WintergraspTowerIds.Flamewatch,WintergraspText.EasternTowerDamage,WintergraspText.EasternTowerDestroy) + }; + + public static Position[] WGTurret = + { + new Position(5391.19f, 3060.8f, 419.616f, 1.69557f), + new Position(5266.75f, 2976.5f, 421.067f, 3.20354f), + new Position(5234.86f, 2948.8f, 420.88f, 1.61311f), + new Position(5323.05f, 2923.7f, 421.645f, 1.5817f), + new Position(5363.82f, 2923.87f, 421.709f, 1.60527f), + new Position(5264.04f, 2861.34f, 421.587f, 3.21142f), + new Position(5264.68f, 2819.78f, 421.656f, 3.15645f), + new Position(5322.16f, 2756.69f, 421.646f, 4.69978f), + new Position(5363.78f, 2756.77f, 421.629f, 4.78226f), + new Position(5236.2f, 2732.68f, 421.649f, 4.72336f), + new Position(5265.02f, 2704.63f, 421.7f, 3.12507f), + new Position(5350.87f, 2616.03f, 421.243f, 4.72729f), + new Position(5390.95f, 2615.5f, 421.126f, 4.6409f), + new Position(5148.8f, 2820.24f, 421.621f, 3.16043f), + new Position(5147.98f, 2861.93f, 421.63f, 3.18792f), + }; + + // Here there is all npc keeper spawn point + public static WintergraspObjectPositionData[] WGKeepNPC = + { + // North East + new WintergraspObjectPositionData(5326.203125f, 2660.026367f, 409.100891f, 2.543383f, WGNpcs.GuardH, WGNpcs.GuardA), // Roaming Guard + new WintergraspObjectPositionData(5298.430176f, 2738.760010f, 409.316010f, 3.971740f, WGNpcs.VieronBlazefeather, WGNpcs.BowyerRandolph), // Vieron Blazefeather + new WintergraspObjectPositionData(5335.310059f, 2764.110107f, 409.274994f, 4.834560f, WGNpcs.GuardH, WGNpcs.GuardA), // Standing Guard + new WintergraspObjectPositionData(5349.810059f, 2763.629883f, 409.333008f, 4.660030f, WGNpcs.GuardH, WGNpcs.GuardA), // Standing Guard + // North + new WintergraspObjectPositionData(5373.470215f, 2789.060059f, 409.322998f, 2.600540f, WGNpcs.StoneGuardMukar, WGNpcs.KnightDameron), // Stone Guard Mukar + new WintergraspObjectPositionData(5296.560059f, 2789.870117f, 409.274994f, 0.733038f, WGNpcs.HoodooMasterFuJin, WGNpcs.SorceressKaylana), // Voodoo Master Fu'jin + new WintergraspObjectPositionData(5372.670000f, 2786.740000f, 409.442000f, 2.809980f, WGNpcs.ChampionRosSlai, WGNpcs.MarshalMagruder), // Wintergrasp Quartermaster + new WintergraspObjectPositionData(5368.709961f, 2856.360107f, 409.322998f, 2.949610f, WGNpcs.GuardH, WGNpcs.GuardA), // Standing Guard + new WintergraspObjectPositionData(5367.910156f, 2826.520020f, 409.322998f, 3.333580f, WGNpcs.GuardH, WGNpcs.GuardA), // Standing Guard + new WintergraspObjectPositionData(5389.270020f, 2847.370117f, 418.759003f, 3.106690f, WGNpcs.GuardH, WGNpcs.GuardA), // Standing Guard + new WintergraspObjectPositionData(5388.560059f, 2834.770020f, 418.759003f, 3.071780f, WGNpcs.GuardH, WGNpcs.GuardA), // Standing Guard + new WintergraspObjectPositionData(5359.129883f, 2837.989990f, 409.364014f, 4.698930f, WGNpcs.CommanderDardosh, WGNpcs.CommanderZanneth), // Commander Dardosh + new WintergraspObjectPositionData(5366.129883f, 2833.399902f, 409.322998f, 3.141590f, WGNpcs.TacticalOfficerKilrath, WGNpcs.TacticalOfficerAhbramis), // Tactical Officer Kilrath + // North West + new WintergraspObjectPositionData(5350.680176f, 2917.010010f, 409.274994f, 1.466080f, WGNpcs.GuardH, WGNpcs.GuardA), // Standing Guard + new WintergraspObjectPositionData(5335.120117f, 2916.800049f, 409.444000f, 1.500980f, WGNpcs.GuardH, WGNpcs.GuardA), // Standing Guard + new WintergraspObjectPositionData(5295.560059f, 2926.669922f, 409.274994f, 0.872665f, WGNpcs.SiegesmithStronghoof, WGNpcs.SiegeMasterStouthandle), // Stronghoof + new WintergraspObjectPositionData(5371.399902f, 3026.510010f, 409.205994f, 3.250030f, WGNpcs.PrimalistMulfort, WGNpcs.AnchoriteTessa), // Primalist Mulfort + new WintergraspObjectPositionData(5392.123535f, 3031.110352f, 409.187683f, 3.677212f, WGNpcs.GuardH, WGNpcs.GuardA), // Roaming Guard + // South + new WintergraspObjectPositionData(5270.060059f, 2847.550049f, 409.274994f, 3.071780f, WGNpcs.GuardH, WGNpcs.GuardA), // Standing Guard + new WintergraspObjectPositionData(5270.160156f, 2833.479980f, 409.274994f, 3.124140f, WGNpcs.GuardH, WGNpcs.GuardA), // Standing Guard + new WintergraspObjectPositionData(5179.109863f, 2837.129883f, 409.274994f, 3.211410f, WGNpcs.GuardH, WGNpcs.GuardA), // Standing Guard + new WintergraspObjectPositionData(5179.669922f, 2846.600098f, 409.274994f, 3.089230f, WGNpcs.GuardH, WGNpcs.GuardA), // Standing Guard + new WintergraspObjectPositionData(5234.970215f, 2883.399902f, 409.274994f, 4.293510f, WGNpcs.LieutenantMurp, WGNpcs.SeniorDemolitionistLegoso), // Lieutenant Murp + // Portal guards (from around the fortress) + new WintergraspObjectPositionData(5319.209473f, 3055.947754f, 409.176636f, 1.020201f, WGNpcs.GuardH, WGNpcs.GuardA), // Standing Guard + new WintergraspObjectPositionData(5311.612305f, 3061.207275f, 408.734161f, 0.965223f, WGNpcs.GuardH, WGNpcs.GuardA), // Standing Guard + new WintergraspObjectPositionData(5264.713379f, 3017.283447f, 408.479706f, 3.482424f, WGNpcs.GuardH, WGNpcs.GuardA), // Standing Guard + new WintergraspObjectPositionData(5269.096191f, 3008.315918f, 408.826294f, 3.843706f, WGNpcs.GuardH, WGNpcs.GuardA), // Standing Guard + new WintergraspObjectPositionData(5201.414551f, 2945.096924f, 409.190735f, 0.945592f, WGNpcs.GuardH, WGNpcs.GuardA), // Standing Guard + new WintergraspObjectPositionData(5193.386230f, 2949.617188f, 409.190735f, 1.145859f, WGNpcs.GuardH, WGNpcs.GuardA), // Standing Guard + new WintergraspObjectPositionData(5148.116211f, 2904.761963f, 409.193756f, 3.368532f, WGNpcs.GuardH, WGNpcs.GuardA), // Standing Guard + new WintergraspObjectPositionData(5153.355957f, 2895.501465f, 409.199310f, 3.549174f, WGNpcs.GuardH, WGNpcs.GuardA), // Standing Guard + new WintergraspObjectPositionData(5154.353027f, 2787.349365f, 409.250183f, 2.555644f, WGNpcs.GuardH, WGNpcs.GuardA), // Standing Guard + new WintergraspObjectPositionData(5150.066406f, 2777.876953f, 409.343903f, 2.708797f, WGNpcs.GuardH, WGNpcs.GuardA), // Standing Guard + new WintergraspObjectPositionData(5193.706543f, 2732.882812f, 409.189514f, 4.845073f, WGNpcs.GuardH, WGNpcs.GuardA), // Standing Guard + new WintergraspObjectPositionData(5202.126953f, 2737.570557f, 409.189514f, 5.375215f, WGNpcs.GuardH, WGNpcs.GuardA), // Standing Guard + new WintergraspObjectPositionData(5269.181152f, 2671.174072f, 409.098999f, 2.457459f, WGNpcs.GuardH, WGNpcs.GuardA), // Standing Guard + new WintergraspObjectPositionData(5264.960938f, 2662.332520f, 409.098999f, 2.598828f, WGNpcs.GuardH, WGNpcs.GuardA), // Standing Guard + new WintergraspObjectPositionData(5307.111816f, 2616.006836f, 409.095734f, 5.355575f, WGNpcs.GuardH, WGNpcs.GuardA), // Standing Guard + new WintergraspObjectPositionData(5316.770996f, 2619.430176f, 409.027740f, 5.363431f, WGNpcs.GuardH, WGNpcs.GuardA) // Standing Guard + }; + + public static WintergraspObjectPositionData[] WGOutsideNPC = + { + new WintergraspObjectPositionData(5032.04f, 3681.79f, 362.980f, 4.210f, WGNpcs.VieronBlazefeather, 0), + new WintergraspObjectPositionData(5020.71f, 3626.19f, 360.150f, 4.640f, WGNpcs.HoodooMasterFuJin, 0), + new WintergraspObjectPositionData(4994.85f, 3660.51f, 359.150f, 2.260f, WGNpcs.CommanderDardosh, 0), + new WintergraspObjectPositionData(5015.46f, 3677.11f, 362.970f, 6.009f, WGNpcs.TacticalOfficerKilrath, 0), + new WintergraspObjectPositionData(5031.12f, 3663.77f, 363.500f, 3.110f, WGNpcs.SiegesmithStronghoof, 0), + new WintergraspObjectPositionData(5042.74f, 3675.82f, 363.060f, 3.358f, WGNpcs.PrimalistMulfort, 0), + new WintergraspObjectPositionData(5014.45f, 3640.87f, 361.390f, 3.280f, WGNpcs.LieutenantMurp, 0), + new WintergraspObjectPositionData(5100.07f, 2168.89f, 365.779f, 1.972f, 0, WGNpcs.BowyerRandolph), + new WintergraspObjectPositionData(5081.70f, 2173.73f, 365.878f, 0.855f, 0, WGNpcs.SorceressKaylana), + new WintergraspObjectPositionData(5078.28f, 2183.70f, 365.029f, 1.466f, 0, WGNpcs.CommanderZanneth), + new WintergraspObjectPositionData(5088.49f, 2188.18f, 365.647f, 5.253f, 0, WGNpcs.TacticalOfficerAhbramis), + new WintergraspObjectPositionData(5095.67f, 2193.28f, 365.924f, 4.939f, 0, WGNpcs.SiegeMasterStouthandle), + new WintergraspObjectPositionData(5088.61f, 2167.66f, 365.689f, 0.680f, 0, WGNpcs.AnchoriteTessa), + new WintergraspObjectPositionData(5080.40f, 2199.00f, 359.489f, 2.967f, 0, WGNpcs.SeniorDemolitionistLegoso), + }; + + public static WintergraspGameObjectData[] WGPortalDefenderData = + { + // Player teleporter + new WintergraspGameObjectData(5153.408f, 2901.349f, 409.1913f, -0.06981169f, 0.0f, 0.0f, -0.03489876f, 0.9993908f, 190763, 191575), + new WintergraspGameObjectData(5268.698f, 2666.421f, 409.0985f, -0.71558490f, 0.0f, 0.0f, -0.35020730f, 0.9366722f, 190763, 191575), + new WintergraspGameObjectData(5197.050f, 2944.814f, 409.1913f, 2.33874000f, 0.0f, 0.0f, 0.92050460f, 0.3907318f, 190763, 191575), + new WintergraspGameObjectData(5196.671f, 2737.345f, 409.1892f, -2.93213900f, 0.0f, 0.0f, -0.99452110f, 0.1045355f, 190763, 191575), + new WintergraspGameObjectData(5314.580f, 3055.852f, 408.8620f, 0.54105060f, 0.0f, 0.0f, 0.26723770f, 0.9636307f, 190763, 191575), + new WintergraspGameObjectData(5391.277f, 2828.094f, 418.6752f, -2.16420600f, 0.0f, 0.0f, -0.88294700f, 0.4694727f, 190763, 191575), + new WintergraspGameObjectData(5153.931f, 2781.671f, 409.2455f, 1.65806200f, 0.0f, 0.0f, 0.73727700f, 0.6755905f, 190763, 191575), + new WintergraspGameObjectData(5311.445f, 2618.931f, 409.0916f, -2.37364400f, 0.0f, 0.0f, -0.92718320f, 0.3746083f, 190763, 191575), + new WintergraspGameObjectData(5269.208f, 3013.838f, 408.8276f, -1.76278200f, 0.0f, 0.0f, -0.77162460f, 0.6360782f, 190763, 191575), + + new WintergraspGameObjectData(5401.634f, 2853.667f, 418.6748f, 2.63544400f, 0.0f, 0.0f, 0.96814730f, 0.2503814f, 192819, 192819), // return portal inside fortress, neutral + + // Vehicle teleporter + new WintergraspGameObjectData(5314.515f, 2703.687f, 408.5502f, -0.89011660f, 0.0f, 0.0f, -0.43051050f, 0.9025856f, 192951, 192951), + new WintergraspGameObjectData(5316.252f, 2977.042f, 408.5385f, -0.82030330f, 0.0f, 0.0f, -0.39874840f, 0.9170604f, 192951, 192951) + }; + + public static WintergraspTowerData[] AttackTowers = + { + //West Tower + new WintergraspTowerData() + { + towerEntry = 190356, + GameObject = new WintergraspGameObjectData[] + { + new WintergraspGameObjectData(4559.113f, 3606.216f, 419.9992f, 4.799657f, 0.0f, 0.0f, -0.67558960f, 0.73727790f, 192488, 192501), // Flag on tower + new WintergraspGameObjectData(4539.420f, 3622.490f, 420.0342f, 3.211419f, 0.0f, 0.0f, -0.99939060f, 0.03490613f, 192488, 192501), // Flag on tower + new WintergraspGameObjectData(4555.258f, 3641.648f, 419.9740f, 1.675514f, 0.0f, 0.0f, 0.74314400f, 0.66913150f, 192488, 192501), // Flag on tower + new WintergraspGameObjectData(4574.872f, 3625.911f, 420.0792f, 0.087266f, 0.0f, 0.0f, 0.04361916f, 0.99904820f, 192488, 192501), // Flag on tower + new WintergraspGameObjectData(4433.899f, 3534.142f, 360.2750f, 4.433136f, 0.0f, 0.0f, -0.79863550f, 0.60181500f, 192269, 192278), // Flag near workshop + new WintergraspGameObjectData(4572.933f, 3475.519f, 363.0090f, 1.422443f, 0.0f, 0.0f, 0.65275960f, 0.75756520f, 192269, 192277) // Flag near bridge + }, + CreatureBottom = new WintergraspObjectPositionData[] + { + new WintergraspObjectPositionData(4418.688477f, 3506.251709f, 358.975494f, 4.293305f, WGNpcs.GuardH, WGNpcs.GuardA), // Roaming Guard + } + }, + + //South Tower + new WintergraspTowerData() + { + towerEntry = 190357, + GameObject = new WintergraspGameObjectData[] + { + new WintergraspGameObjectData(4416.004f, 2822.666f, 429.8512f, 6.2657330f, 0.0f, 0.0f, -0.00872612f, 0.99996190f, 192488, 192501), // Flag on tower + new WintergraspGameObjectData(4398.819f, 2804.698f, 429.7920f, 4.6949370f, 0.0f, 0.0f, -0.71325020f, 0.70090960f, 192488, 192501), // Flag on tower + new WintergraspGameObjectData(4387.622f, 2719.566f, 389.9351f, 4.7385700f, 0.0f, 0.0f, -0.69779010f, 0.71630230f, 192366, 192414), // Flag near tower + new WintergraspGameObjectData(4464.124f, 2855.453f, 406.1106f, 0.8290324f, 0.0f, 0.0f, 0.40274720f, 0.91531130f, 192366, 192429), // Flag near tower + new WintergraspGameObjectData(4526.457f, 2810.181f, 391.1997f, 3.2899610f, 0.0f, 0.0f, -0.99724960f, 0.07411628f, 192269, 192278) // Flag near bridge + }, + CreatureBottom = new WintergraspObjectPositionData[] + { + new WintergraspObjectPositionData(4452.859863f, 2808.870117f, 402.604004f, 6.056290f, WGNpcs.GuardH, WGNpcs.GuardA), // Standing Guard + new WintergraspObjectPositionData(4455.899902f, 2835.958008f, 401.122559f, 0.034907f, WGNpcs.GuardH, WGNpcs.GuardA), // Standing Guard + new WintergraspObjectPositionData(4412.649414f, 2953.792236f, 374.799957f, 0.980838f, WGNpcs.GuardH, WGNpcs.GuardA), // Roaming Guard + new WintergraspObjectPositionData(4362.089844f, 2811.510010f, 407.337006f, 3.193950f, WGNpcs.GuardH, WGNpcs.GuardA), // Standing Guard + new WintergraspObjectPositionData(4412.290039f, 2753.790039f, 401.015015f, 5.829400f, WGNpcs.GuardH, WGNpcs.GuardA), // Standing Guard + new WintergraspObjectPositionData(4421.939941f, 2773.189941f, 400.894989f, 5.707230f, WGNpcs.GuardH, WGNpcs.GuardA), // Standing Guard + } + }, + + //East Tower + new WintergraspTowerData() + { + towerEntry = 190358, + GameObject = new WintergraspGameObjectData[] + { + new WintergraspGameObjectData(4466.793f, 1960.418f, 459.1437f, 1.151916f, 0.0f, 0.0f, 0.5446386f, 0.8386708f, 192488, 192501), // Flag on tower + new WintergraspGameObjectData(4475.351f, 1937.031f, 459.0702f, 5.846854f, 0.0f, 0.0f, -0.2164392f, 0.9762961f, 192488, 192501), // Flag on tower + new WintergraspGameObjectData(4451.758f, 1928.104f, 459.0759f, 4.276057f, 0.0f, 0.0f, -0.8433914f, 0.5372996f, 192488, 192501), // Flag on tower + new WintergraspGameObjectData(4442.987f, 1951.898f, 459.0930f, 2.740162f, 0.0f, 0.0f, 0.9799242f, 0.1993704f, 192488, 192501) // Flag on tower + }, + CreatureBottom = new WintergraspObjectPositionData[] + { + new WintergraspObjectPositionData(4501.060059f, 1990.280029f, 431.157013f, 1.029740f, WGNpcs.GuardH, WGNpcs.GuardA), // Standing Guard + new WintergraspObjectPositionData(4463.830078f, 2015.180054f, 430.299988f, 1.431170f, WGNpcs.GuardH, WGNpcs.GuardA), // Standing Guard + new WintergraspObjectPositionData(4494.580078f, 1943.760010f, 435.627014f, 6.195920f, WGNpcs.GuardH, WGNpcs.GuardA), // Standing Guard + new WintergraspObjectPositionData(4450.149902f, 1897.579956f, 435.045013f, 4.398230f, WGNpcs.GuardH, WGNpcs.GuardA), // Standing Guard + new WintergraspObjectPositionData(4428.870117f, 1906.869995f, 432.648010f, 3.996800f, WGNpcs.GuardH, WGNpcs.GuardA), // Standing Guard + } + } + }; + + public static WintergraspTowerCannonData[] TowerCannon = + { + new WintergraspTowerCannonData() + { + towerEntry = 190221, + TurretTop = new Position[] + { + new Position(5255.88f, 3047.63f, 438.499f, 3.13677f), + new Position(5280.9f, 3071.32f, 438.499f, 1.62879f), + }, + }, + + new WintergraspTowerCannonData() + { + towerEntry = 190373, + TurretTop = new Position[] + { + new Position(5138.59f, 2935.16f, 439.845f, 3.11723f), + new Position(5163.06f, 2959.52f, 439.846f, 1.47258f), + }, + }, + + new WintergraspTowerCannonData() + { + towerEntry = 190377, + TurretTop = new Position[] + { + new Position(5163.84f, 2723.74f, 439.844f, 1.3994f), + new Position(5139.69f, 2747.4f, 439.844f, 3.17221f), + }, + }, + + new WintergraspTowerCannonData() + { + towerEntry = 190378, + TurretTop = new Position[] + { + new Position(5278.21f, 2607.23f, 439.755f, 4.71944f), + new Position(5255.01f, 2631.98f, 439.755f, 3.15257f), + }, + }, + + new WintergraspTowerCannonData() + { + towerEntry = 190356, + TowerCannonBottom = new Position[] + { + new Position(4537.380371f, 3599.531738f, 402.886993f, 3.998462f), + new Position(4581.497559f, 3604.087158f, 402.886963f, 5.651723f), + }, + TurretTop = new Position[] + { + new Position(4469.448242f, 1966.623779f, 465.647217f, 1.153573f), + new Position(4581.895996f, 3626.438477f, 426.539062f, 0.117806f), + }, + }, + + new WintergraspTowerCannonData() + { + towerEntry = 190357, + TowerCannonBottom = new Position[] + { + new Position(4421.640137f, 2799.935791f, 412.630920f, 5.459298f), + new Position(4420.263184f, 2845.340332f, 412.630951f, 0.742197f), + }, + TurretTop = new Position[] + { + new Position(4423.430664f, 2822.762939f, 436.283142f, 6.223487f), + new Position(4397.825684f, 2847.629639f, 436.283325f, 1.579430f), + new Position(4398.814941f, 2797.266357f, 436.283051f, 4.703747f), + }, + }, + + new WintergraspTowerCannonData() + { + towerEntry = 190358, + TowerCannonBottom = new Position[] + { + new Position(4448.138184f, 1974.998779f, 441.995911f, 1.967238f), + new Position(4448.713379f, 1955.148682f, 441.995178f, 0.380733f), + }, + TurretTop = new Position[] + { + new Position(4469.448242f, 1966.623779f, 465.647217f, 1.153573f), + new Position(4481.996582f, 1933.658325f, 465.647186f, 5.873029f), + }, + } + }; + + public static StaticWintergraspWorkshopInfo[] WorkshopData = + { + new StaticWintergraspWorkshopInfo() + { + WorkshopId = WGWorkshopIds.Ne, + WorldStateId = WGWorldStates.NE, + AllianceCaptureTextId = WintergraspText.SunkenRingCaptureAlliance, + AllianceAttackTextId = WintergraspText.SunkenRingAttackAlliance, + HordeCaptureTextId = WintergraspText.SunkenRingCaptureHorde, + HordeAttackTextId = WintergraspText.SunkenRingAttackHorde + }, + new StaticWintergraspWorkshopInfo() + { + WorkshopId = WGWorkshopIds.Nw, + WorldStateId = WGWorldStates.NW, + AllianceCaptureTextId = WintergraspText.BrokenTempleCaptureAlliance, + AllianceAttackTextId = WintergraspText.BrokenTempleAttackAlliance, + HordeCaptureTextId = WintergraspText.BrokenTempleCaptureHorde, + HordeAttackTextId = WintergraspText.BrokenTempleAttackHorde + }, + new StaticWintergraspWorkshopInfo() + { + WorkshopId = WGWorkshopIds.Se, + WorldStateId = WGWorldStates.SE, + AllianceCaptureTextId = WintergraspText.EastsparkCaptureAlliance, + AllianceAttackTextId = WintergraspText.EastsparkAttackAlliance, + HordeCaptureTextId = WintergraspText.EastsparkCaptureHorde, + HordeAttackTextId = WintergraspText.EastsparkAttackHorde + }, + + new StaticWintergraspWorkshopInfo() + { + WorkshopId = WGWorkshopIds.Sw, + WorldStateId = WGWorldStates.SW, + AllianceCaptureTextId = WintergraspText.WestsparkCaptureAlliance, + AllianceAttackTextId = WintergraspText.WestsparkAttackAlliance, + HordeCaptureTextId = WintergraspText.WestsparkCaptureHorde, + HordeAttackTextId = WintergraspText.WestsparkAttackHorde + }, + + // KEEP WORKSHOPS - It can't be taken, so it doesn't have a textids + new StaticWintergraspWorkshopInfo() + { + WorkshopId = WGWorkshopIds.KeepWest, + WorldStateId = WGWorldStates.KeepW + }, + + new StaticWintergraspWorkshopInfo() + { + WorkshopId = WGWorkshopIds.KeepEast, + WorldStateId = WGWorldStates.KeepE + } + }; + #endregion + } + + struct WGData + { + public const int DamagedTowerDef = 0; + public const int BrokenTowerDef = 1; + public const int DamagedTowerAtt = 2; + public const int BrokenTowerAtt = 3; + public const int MaxVehicleA = 4; + public const int MaxVehicleH = 5; + public const int VehicleA = 6; + public const int VehicleH = 7; + public const int WonA = 8; + public const int DefA = 9; + public const int WonH = 10; + public const int DefH = 11; + public const int Max = 12; + } + + struct WGAchievements + { + public const uint WinWg = 1717; + public const uint WinWg100 = 1718; /// @Todo: Has To Be Implemented + public const uint WgGnomeslaughter = 1723; /// @Todo: Has To Be Implemented + public const uint WgTowerDestroy = 1727; + public const uint DestructionDerbyA = 1737; /// @Todo: Has To Be Implemented + public const uint WgTowerCannonKill = 1751; /// @Todo: Has To Be Implemented + public const uint WgMasterA = 1752; /// @Todo: Has To Be Implemented + public const uint WinWgTimer10 = 1755; + public const uint StoneKeeper50 = 2085; /// @Todo: Has To Be Implemented + public const uint StoneKeeper100 = 2086; /// @Todo: Has To Be Implemented + public const uint StoneKeeper250 = 2087; /// @Todo: Has To Be Implemented + public const uint StoneKeeper500 = 2088; /// @Todo: Has To Be Implemented + public const uint StoneKeeper1000 = 2089; /// @Todo: Has To Be Implemented + public const uint WgRanger = 2199; /// @Todo: Has To Be Implemented + public const uint DestructionDerbyH = 2476; /// @Todo: Has To Be Implemented + public const uint WgMasterH = 2776; /// @Todo: Has To Be Implemented + } + + struct WGSpells + { + // Wartime Auras + public const uint Recruit = 37795; + public const uint Corporal = 33280; + public const uint Lieutenant = 55629; + public const uint Tenacity = 58549; + public const uint TenacityVehicle = 59911; + public const uint TowerControl = 62064; + public const uint SpiritualImmunity = 58729; + public const uint GreatHonor = 58555; + public const uint GreaterHonor = 58556; + public const uint GreatestHonor = 58557; + public const uint AllianceFlag = 14268; + public const uint HordeFlag = 14267; + public const uint GrabPassenger = 61178; + + // Reward Spells + public const uint VictoryReward = 56902; + public const uint DefeatReward = 58494; + public const uint DamagedTower = 59135; + public const uint DestroyedTower = 59136; + public const uint DamagedBuilding = 59201; + public const uint IntactBuilding = 59203; + + public const uint TeleportBridge = 59096; + public const uint TeleportFortress = 60035; + + public const uint TeleportDalaran = 53360; + public const uint VictoryAura = 60044; + + // Other Spells + public const uint WintergraspWater = 36444; + public const uint EssenceOfWintergrasp = 58045; + public const uint WintergraspRestrictedFlightArea = 91604; + + // Phasing Spells + public const uint HordeControlsFactoryPhaseShift = 56618; // Adds Phase 16 + public const uint AllianceControlsFactoryPhaseShift = 56617; // Adds Phase 32 + + public const uint HordeControlPhaseShift = 55773; // Adds Phase 64 + public const uint AllianceControlPhaseShift = 55774; // Adds Phase 128 + } + + struct WGNpcs + { + public const uint GuardH = 30739; + public const uint GuardA = 30740; + public const uint Stalker = 15214; + + public const uint VieronBlazefeather = 31102; + public const uint StoneGuardMukar = 32296; // + public const uint HoodooMasterFuJin = 31101; // + public const uint ChampionRosSlai = 39173; // + public const uint CommanderDardosh = 31091; + public const uint TacticalOfficerKilrath = 31151; + public const uint SiegesmithStronghoof = 31106; + public const uint PrimalistMulfort = 31053; + public const uint LieutenantMurp = 31107; + + public const uint BowyerRandolph = 31052; + public const uint KnightDameron = 32294; // + public const uint SorceressKaylana = 31051; // + public const uint MarshalMagruder = 39172; // + public const uint CommanderZanneth = 31036; + public const uint TacticalOfficerAhbramis = 31153; + public const uint SiegeMasterStouthandle = 31108; + public const uint AnchoriteTessa = 31054; + public const uint SeniorDemolitionistLegoso = 31109; + + public const uint TaunkaSpiritGuide = 31841; // Horde Spirit Guide For Wintergrasp + public const uint DwarvenSpiritGuide = 31842; // Alliance Spirit Guide For Wintergrasp + + public const uint SiegeEngineAlliance = 28312; + public const uint SiegeEngineHorde = 32627; + public const uint Catapult = 27881; + public const uint Demolisher = 28094; + public const uint TowerCannon = 28366; + } + + struct WGGameObjects + { + public const uint FactoryBannerNe = 190475; + public const uint FactoryBannerNw = 190487; + public const uint FactoryBannerSe = 194959; + public const uint FactoryBannerSw = 194962; + + public const uint TitanSRelic = 192829; + + public const uint FortressTower1 = 190221; + public const uint FortressTower2 = 190373; + public const uint FortressTower3 = 190377; + public const uint FortressTower4 = 190378; + + public const uint ShadowsightTower = 190356; + public const uint WinterSEdgeTower = 190357; + public const uint FlamewatchTower = 190358; + + public const uint FortressGate = 190375; + public const uint VaultGate = 191810; + + public const uint KeepCollisionWall = 194323; + } + + struct WintergraspTowerIds + { + public const byte FortressNW = 0; + public const byte FortressSW = 1; + public const byte FortressSE = 2; + public const byte FortressNE = 3; + public const byte Shadowsight = 4; + public const byte WintersEdge = 5; + public const byte Flamewatch = 6; + } + + struct WGWorkshopIds + { + public const byte Ne = 0; + public const byte Nw = 1; + public const byte Se = 2; + public const byte Sw = 3; + public const byte KeepWest = 4; + public const byte KeepEast = 5; + } + + struct WGWorldStates + { + public const uint NE = 3701; + public const uint NW = 3700; + public const uint SE = 3703; + public const uint SW = 3702; + public const uint KeepW = 3698; + public const uint KeepE = 3699; + + public const uint VehicleH = 3490; + public const uint MaxVehicleH = 3491; + public const uint VehicleA = 3680; + public const uint MaxVehicleA = 3681; + public const uint Active = 3801; + public const uint Defender = 3802; + public const uint Attacker = 3803; + public const uint ShowWorldstate = 3710; + public const uint AttackedH = 4022; + public const uint AttackedA = 4023; + public const uint DefendedH = 4024; + public const uint DefendedA = 4025; + } + + struct WGGossipText + { + public const int GYNE = 20071; + public const int GYNW = 20072; + public const int GYSE = 20074; + public const int GYSW = 20073; + public const int GYKeep = 20070; + public const int GYHorde = 20075; + public const int GYAlliance = 20076; + } + + struct WGGraveyardId + { + public const uint WorkshopNE = 0; + public const uint WorkshopNW = 1; + public const uint WorkshopSE = 2; + public const uint WorkshopSW = 3; + public const uint Keep = 4; + public const uint Horde = 5; + public const uint Alliance = 6; + public const uint Max = 7; + } + + struct WintergraspAreaIds + { + public const uint WintergraspFortress = 4575; + public const uint TheSunkenRing = 4538; + public const uint TheBrokenTemplate = 4539; + public const uint WestparkWorkshop = 4611; + public const uint EastparkWorkshop = 4612; + public const uint Wintergrasp = 4197; + public const uint TheChilledQuagmire = 4589; + } + + struct WintergraspQuests + { + public const uint VictoryAlliance = 13181; + public const uint VictoryHorde = 13183; + public const uint CreditTowersDestroyed = 35074; + } + + struct WintergraspText + { + // Invisible Stalker + public const byte SouthernTowerDamage = 1; + public const byte SouthernTowerDestroy = 2; + public const byte EasternTowerDamage = 3; + public const byte EasternTowerDestroy = 4; + public const byte WesternTowerDamage = 5; + public const byte WesternTowerDestroy = 6; + public const byte NwKeeptowerDamage = 7; + public const byte NwKeeptowerDestroy = 8; + public const byte SeKeeptowerDamage = 9; + public const byte SeKeeptowerDestroy = 10; + public const byte BrokenTempleAttackAlliance = 11; + public const byte BrokenTempleCaptureAlliance = 12; + public const byte BrokenTempleAttackHorde = 13; + public const byte BrokenTempleCaptureHorde = 14; + public const byte EastsparkAttackAlliance = 15; + public const byte EastsparkCaptureAlliance = 16; + public const byte EastsparkAttackHorde = 17; + public const byte EastsparkCaptureHorde = 18; + public const byte SunkenRingAttackAlliance = 19; + public const byte SunkenRingCaptureAlliance = 20; + public const byte SunkenRingAttackHorde = 21; + public const byte SunkenRingCaptureHorde = 22; + public const byte WestsparkAttackAlliance = 23; + public const byte WestsparkCaptureAlliance = 24; + public const byte WestsparkAttackHorde = 25; + public const byte WestsparkCaptureHorde = 26; + + public const byte StartGrouping = 27; + public const byte StartBattle = 28; + public const byte FortressDefendAlliance = 29; + public const byte FortressCaptureAlliance = 30; + public const byte FortressDefendHorde = 31; + public const byte FortressCaptureHorde = 32; + + public const byte NeKeeptowerDamage = 33; + public const byte NeKeeptowerDestroy = 34; + public const byte SwKeeptowerDamage = 35; + public const byte SwKeeptowerDestroy = 36; + + public const byte RankCorporal = 37; + public const byte RankFirstLieutenant = 38; + } + + enum WGGameObjectState + { + None, + NeutralIntact, + NeutralDamage, + NeutralDestroy, + HordeIntact, + HordeDamage, + HordeDestroy, + AllianceIntact, + AllianceDamage, + AllianceDestroy + } + + enum WGGameObjectBuildingType + { + Door, + Titanrelic, + Wall, + DoorLast, + KeepTower, + Tower + } + + //Data Structs + struct BfWGCoordGY + { + public BfWGCoordGY(float x, float y, float z, float o, uint graveyardId, int textId, uint startControl) + { + pos = new Position(x, y, z, o); + GraveyardID = graveyardId; + TextId = textId; + StartControl = startControl; + } + + public Position pos; + public uint GraveyardID; + public int TextId;// for gossip menu + public uint StartControl; + } + + struct WintergraspBuildingSpawnData + { + public WintergraspBuildingSpawnData(uint entry, uint worldstate, float x, float y, float z, float o, double rX, double rY, double rZ, double rW, WGGameObjectBuildingType type) + { + Entry = entry; + WorldState = worldstate; + Pos = new Position(x, y, z, o); + Rot = new Quaternion(rX, rY, rZ, rW); + BuildingType = type; + } + + public uint Entry; + public uint WorldState; + public Position Pos; + public Quaternion Rot; + public WGGameObjectBuildingType BuildingType; + } + + struct WintergraspGameObjectData + { + public WintergraspGameObjectData(float x, float y, float z, float o, float rX, float rY, float rZ, float rW, uint hordeEntry, uint allianceEntry) + { + Pos = new Position(x, y, z, o); + Rot = new Quaternion(rX, rY, rZ, rW); + HordeEntry = hordeEntry; + AllianceEntry = allianceEntry; + } + + public Position Pos; + public Quaternion Rot; + public uint HordeEntry; + public uint AllianceEntry; + } + + class WintergraspTowerData + { + public uint towerEntry; // Gameobject id of tower + public WintergraspGameObjectData[] GameObject = new WintergraspGameObjectData[6]; // Gameobject position and entry (Horde/Alliance) + + // Creature: Turrets and Guard /// @todo: Killed on Tower destruction ? Tower damage ? Requires confirming + public WintergraspObjectPositionData[] CreatureBottom = new WintergraspObjectPositionData[9]; + } + + struct WintergraspObjectPositionData + { + public WintergraspObjectPositionData(float x, float y, float z, float o, uint hordeEntry, uint allianceEntry) + { + Pos = new Position(x, y, z, o); + HordeEntry = hordeEntry; + AllianceEntry = allianceEntry; + } + + public Position Pos; + public uint HordeEntry; + public uint AllianceEntry; + } + + class WintergraspTowerCannonData + { + public WintergraspTowerCannonData() + { + TowerCannonBottom = new Position[0]; + TurretTop = new Position[0]; + } + + public uint towerEntry; + public Position[] TowerCannonBottom; + public Position[] TurretTop; + } + + class StaticWintergraspWorkshopInfo + { + public byte WorkshopId; + public uint WorldStateId; + public byte AllianceCaptureTextId; + public byte AllianceAttackTextId; + public byte HordeCaptureTextId; + public byte HordeAttackTextId; + } + + class StaticWintergraspTowerInfo + { + public StaticWintergraspTowerInfo(byte towerId, byte damagedTextId, byte destroyedTextId) + { + TowerId = towerId; + DamagedTextId = damagedTextId; + DestroyedTextId = destroyedTextId; + } + + public byte TowerId; + public byte DamagedTextId; + public byte DestroyedTextId; + } +} diff --git a/Game/BattleGrounds/BattleGround.cs b/Game/BattleGrounds/BattleGround.cs new file mode 100644 index 000000000..21fdc5ce9 --- /dev/null +++ b/Game/BattleGrounds/BattleGround.cs @@ -0,0 +1,2144 @@ +/* + * Copyright (C) 2012-2017 CypherCore + * + * 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, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +using Framework.Constants; +using Framework.Database; +using Framework.GameMath; +using Game.Arenas; +using Game.Chat; +using Game.DataStorage; +using Game.Entities; +using Game.Groups; +using Game.Guilds; +using Game.Maps; +using Game.Network; +using Game.Network.Packets; +using Game.Spells; +using System; +using System.Collections.Generic; +using System.Diagnostics.Contracts; +using System.Linq; + +namespace Game.BattleGrounds +{ + public class Battleground : IDisposable + { + public Battleground() + { + m_TypeID = BattlegroundTypeId.None; + m_RandomTypeID = BattlegroundTypeId.None; + m_Status = BattlegroundStatus.None; + m_BracketId = BattlegroundBracketId.First; + _winnerTeamId = BattlegroundTeamId.Neutral; + m_Name = ""; + + m_HonorMode = BGHonorMode.Normal; + + StartDelayTimes[BattlegroundConst.EventIdFirst] = BattlegroundStartTimeIntervals.Delay2m; + StartDelayTimes[BattlegroundConst.EventIdSecond] = BattlegroundStartTimeIntervals.Delay1m; + StartDelayTimes[BattlegroundConst.EventIdThird] = BattlegroundStartTimeIntervals.Delay30s; + StartDelayTimes[BattlegroundConst.EventIdFourth] = BattlegroundStartTimeIntervals.None; + //we must set to some default existing values + StartMessageIds[BattlegroundConst.EventIdFirst] = CypherStrings.BgWsStartTwoMinutes; + StartMessageIds[BattlegroundConst.EventIdSecond] = CypherStrings.BgWsStartOneMinute; + StartMessageIds[BattlegroundConst.EventIdThird] = CypherStrings.BgWsStartHalfMinute; + StartMessageIds[BattlegroundConst.EventIdFourth] = CypherStrings.BgWsHasBegun; + } + + public virtual void Dispose() + { + // remove objects and creatures + // (this is done automatically in mapmanager update, when the instance is reset after the reset time) + foreach (var type in BgCreatures.Keys) + DelCreature(type); + + foreach (var type in BgObjects.Keys) + DelObject(type); + + Global.BattlegroundMgr.RemoveBattleground(GetTypeID(), GetInstanceID()); + // unload map + if (m_Map) + m_Map.SetUnload(); + + // remove from bg free slot queue + RemoveFromBGFreeSlotQueue(); + } + + public Battleground GetCopy() + { + return (Battleground)MemberwiseClone(); + } + + public void Update(uint diff) + { + if (!PreUpdateImpl(diff)) + return; + + if (GetPlayersSize() == 0) + { + //BG is empty + // if there are no players invited, delete BG + // this will delete arena or bg object, where any player entered + // [[ but if you use Battleground object again (more battles possible to be played on 1 instance) + // then this condition should be removed and code: + // if (!GetInvitedCount(Team.Horde) && !GetInvitedCount(Team.Alliance)) + // this.AddToFreeBGObjectsQueue(); // not yet implemented + // should be used instead of current + // ]] + // Battleground Template instance cannot be updated, because it would be deleted + if (GetInvitedCount(Team.Horde) == 0 && GetInvitedCount(Team.Alliance) == 0) + m_SetDeleteThis = true; + return; + } + + switch (GetStatus()) + { + case BattlegroundStatus.WaitJoin: + if (GetPlayersSize() != 0) + { + _ProcessJoin(diff); + _CheckSafePositions(diff); + } + break; + case BattlegroundStatus.InProgress: + _ProcessOfflineQueue(); + _ProcessPlayerPositionBroadcast(diff); + // after 47 Time.Minutes without one team losing, the arena closes with no winner and no rating change + if (isArena()) + { + if (GetElapsedTime() >= 47 * Time.Minute * Time.InMilliseconds) + { + EndBattleground(0); + return; + } + } + else + { + _ProcessRessurect(diff); + if (Global.BattlegroundMgr.GetPrematureFinishTime() != 0 && (GetPlayersCountByTeam(Team.Alliance) < GetMinPlayersPerTeam() || GetPlayersCountByTeam(Team.Horde) < GetMinPlayersPerTeam())) + _ProcessProgress(diff); + else if (m_PrematureCountDown) + m_PrematureCountDown = false; + } + break; + case BattlegroundStatus.WaitLeave: + _ProcessLeave(diff); + break; + default: + break; + } + + // Update start time and reset stats timer + SetElapsedTime(GetElapsedTime() + diff); + if (GetStatus() == BattlegroundStatus.WaitJoin) + { + m_ResetStatTimer += diff; + m_CountdownTimer += diff; + } + + PostUpdateImpl(diff); + } + + void _CheckSafePositions(uint diff) + { + float maxDist = GetStartMaxDist(); + if (maxDist == 0.0f) + return; + + m_ValidStartPositionTimer += diff; + if (m_ValidStartPositionTimer >= BattlegroundConst.CheckPlayerPositionInverval) + { + m_ValidStartPositionTimer = 0; + + foreach (var guid in GetPlayers().Keys) + { + Player player = Global.ObjAccessor.FindPlayer(guid); + if (player) + { + if (player.IsGameMaster()) + continue; + + Position pos = player.GetPosition(); + Position startPos = GetTeamStartPosition(GetTeamIndexByTeamId(player.GetBGTeam())); + if (pos.GetExactDistSq(startPos) > maxDist) + { + Log.outDebug(LogFilter.Battleground, "Battleground: Sending {0} back to start location (map: {1}) (possible exploit)", player.GetName(), GetMapId()); + player.TeleportTo(GetMapId(), startPos.GetPositionX(), startPos.GetPositionY(), startPos.GetPositionZ(), startPos.GetOrientation()); + } + } + } + } + } + + void _ProcessPlayerPositionBroadcast(uint diff) + { + m_LastPlayerPositionBroadcast += diff; + if (m_LastPlayerPositionBroadcast >= BattlegroundConst.PlayerPositionUpdateInterval) + { + m_LastPlayerPositionBroadcast = 0; + + BattlegroundPlayerPositions playerPositions = new BattlegroundPlayerPositions(); + GetPlayerPositionData(playerPositions.FlagCarriers); + SendPacketToAll(playerPositions); + } + } + + public virtual void GetPlayerPositionData(List positions) { } + + void _ProcessOfflineQueue() + { + // remove offline players from bg after 5 Time.Minutes + if (!m_OfflineQueue.Empty()) + { + var guid = m_OfflineQueue.FirstOrDefault(); + var bgPlayer = m_Players.LookupByKey(guid); + if (bgPlayer != null) + { + if (bgPlayer.OfflineRemoveTime <= Global.WorldMgr.GetGameTime()) + { + RemovePlayerAtLeave(guid, true, true);// remove player from BG + m_OfflineQueue.RemoveAt(0); // remove from offline queue + } + } + } + } + + void _ProcessRessurect(uint diff) + { + // ********************************************************* + // *** Battleground RESSURECTION SYSTEM *** + // ********************************************************* + // this should be handled by spell system + m_LastResurrectTime += diff; + if (m_LastResurrectTime >= BattlegroundConst.ResurrectionInterval) + { + if (GetReviveQueueSize() != 0) + { + foreach (var pair in m_ReviveQueue) + { + Creature sh = null; + Player player = Global.ObjAccessor.FindPlayer(pair.Value); + if (!player) + continue; + + if (!sh && player.IsInWorld) + { + sh = player.GetMap().GetCreature(pair.Key); + // only for visual effect + if (sh) + // Spirit Heal, effect 117 + sh.CastSpell(sh, BattlegroundConst.SpellSpiritHeal, true); + } + + // Resurrection visual + player.CastSpell(player, BattlegroundConst.SpellResurrectionVisual, true); + m_ResurrectQueue.Add(pair.Value); + } + + m_ReviveQueue.Clear(); + m_LastResurrectTime = 0; + } + else + // queue is clear and time passed, just update last resurrection time + m_LastResurrectTime = 0; + } + else if (m_LastResurrectTime > 500) // Resurrect players only half a second later, to see spirit heal effect on NPC + { + foreach (var guid in m_ResurrectQueue) + { + Player player = Global.ObjAccessor.FindPlayer(guid); + if (!player) + continue; + player.ResurrectPlayer(1.0f); + player.CastSpell(player, 6962, true); + player.CastSpell(player, BattlegroundConst.SpellSpiritHealMana, true); + player.SpawnCorpseBones(false); + } + m_ResurrectQueue.Clear(); + } + } + + public virtual Team GetPrematureWinner() + { + Team winner = 0; + if (GetPlayersCountByTeam(Team.Alliance) >= GetMinPlayersPerTeam()) + winner = Team.Alliance; + else if (GetPlayersCountByTeam(Team.Horde) >= GetMinPlayersPerTeam()) + winner = Team.Horde; + + return winner; + } + + void _ProcessProgress(uint diff) + { + // ********************************************************* + // *** Battleground BALLANCE SYSTEM *** + // ********************************************************* + // if less then minimum players are in on one side, then start premature finish timer + if (!m_PrematureCountDown) + { + m_PrematureCountDown = true; + m_PrematureCountDownTimer = Global.BattlegroundMgr.GetPrematureFinishTime(); + } + else if (m_PrematureCountDownTimer < diff) + { + // time's up! + EndBattleground(GetPrematureWinner()); + m_PrematureCountDown = false; + } + else if (!Global.BattlegroundMgr.isTesting()) + { + uint newtime = m_PrematureCountDownTimer - diff; + // announce every Time.Minute + if (newtime > (Time.Minute * Time.InMilliseconds)) + { + if (newtime / (Time.Minute * Time.InMilliseconds) != m_PrematureCountDownTimer / (Time.Minute * Time.InMilliseconds)) + SendMessageToAll(CypherStrings.BattlegroundPrematureFinishWarning, ChatMsg.System, null, m_PrematureCountDownTimer / (Time.Minute * Time.InMilliseconds)); + } + else + { + //announce every 15 seconds + if (newtime / (15 * Time.InMilliseconds) != m_PrematureCountDownTimer / (15 * Time.InMilliseconds)) + SendMessageToAll(CypherStrings.BattlegroundPrematureFinishWarningSecs, ChatMsg.System, null, m_PrematureCountDownTimer / Time.InMilliseconds); + } + m_PrematureCountDownTimer = newtime; + } + } + + void _ProcessJoin(uint diff) + { + // ********************************************************* + // *** Battleground STARTING SYSTEM *** + // ********************************************************* + ModifyStartDelayTime((int)diff); + + if (!isArena()) + SetRemainingTime(300000); + + if (m_ResetStatTimer > 5000) + { + m_ResetStatTimer = 0; + foreach (var guid in GetPlayers().Keys) + { + Player player = Global.ObjAccessor.FindPlayer(guid); + if (player) + player.ResetAllPowers(); + } + } + + // Send packet every 10 seconds until the 2nd field reach 0 + if (m_CountdownTimer >= 10000) + { + uint countdownMaxForBGType = isArena() ? BattlegroundConst.ArenaCountdownMax : BattlegroundConst.BattlegroundCountdownMax; + + StartTimer timer = new StartTimer(); + timer.Type = TimerType.Pvp; + timer.TimeRemaining = countdownMaxForBGType - (GetElapsedTime() / 1000); + timer.TotalTime = countdownMaxForBGType; + timer.Write(); + + foreach (var guid in GetPlayers().Keys) + { + Player player = Global.ObjAccessor.FindPlayer(guid); + if (player) + player.SendPacket(timer, false); + } + + m_CountdownTimer = 0; + } + + if (!m_Events.HasAnyFlag(BattlegroundEventFlags.Event1)) + { + m_Events |= BattlegroundEventFlags.Event1; + + if (!FindBgMap()) + { + Log.outError(LogFilter.Battleground, "Battleground._ProcessJoin: map (map id: {0}, instance id: {1}) is not created!", m_MapId, m_InstanceID); + EndNow(); + return; + } + + // Setup here, only when at least one player has ported to the map + if (!SetupBattleground()) + { + EndNow(); + return; + } + + StartingEventCloseDoors(); + SetStartDelayTime(StartDelayTimes[BattlegroundConst.EventIdFirst]); + // First start warning - 2 or 1 Time.Minute + SendMessageToAll(StartMessageIds[BattlegroundConst.EventIdFirst], ChatMsg.BgSystemNeutral); + } + // After 1 Time.Minute or 30 seconds, warning is signaled + else if (GetStartDelayTime() <= (int)StartDelayTimes[BattlegroundConst.EventIdSecond] && !m_Events.HasAnyFlag(BattlegroundEventFlags.Event2)) + { + m_Events |= BattlegroundEventFlags.Event2; + SendMessageToAll(StartMessageIds[BattlegroundConst.EventIdSecond], ChatMsg.BgSystemNeutral); + } + // After 30 or 15 seconds, warning is signaled + else if (GetStartDelayTime() <= (int)StartDelayTimes[BattlegroundConst.EventIdThird] && !m_Events.HasAnyFlag(BattlegroundEventFlags.Event3)) + { + m_Events |= BattlegroundEventFlags.Event3; + SendMessageToAll(StartMessageIds[BattlegroundConst.EventIdThird], ChatMsg.BgSystemNeutral); + } + // Delay expired (after 2 or 1 Time.Minute) + else if (GetStartDelayTime() <= 0 && !m_Events.HasAnyFlag(BattlegroundEventFlags.Event4)) + { + m_Events |= BattlegroundEventFlags.Event4; + + StartingEventOpenDoors(); + + SendMessageToAll(StartMessageIds[BattlegroundConst.EventIdFourth], ChatMsg.RaidBossEmote); + SetStatus(BattlegroundStatus.InProgress); + SetStartDelayTime(StartDelayTimes[BattlegroundConst.EventIdFourth]); + + // Remove preparation + if (isArena()) + { + //todo add arena sound PlaySoundToAll(SOUND_ARENA_START); + foreach (var guid in GetPlayers().Keys) + { + Player player = Global.ObjAccessor.FindPlayer(guid); + if (player) + { + // BG Status packet + BattlegroundQueueTypeId bgQueueTypeId = Global.BattlegroundMgr.BGQueueTypeId(m_TypeID, GetArenaType()); + uint queueSlot = player.GetBattlegroundQueueIndex(bgQueueTypeId); + + BattlefieldStatusActive battlefieldStatus; + Global.BattlegroundMgr.BuildBattlegroundStatusActive(out battlefieldStatus, this, player, queueSlot, player.GetBattlegroundQueueJoinTime(bgQueueTypeId), GetArenaType()); + player.SendPacket(battlefieldStatus); + + // Correctly display EnemyUnitFrame + player.SetByteValue(PlayerFields.Bytes4, 3, (byte)player.GetBGTeam()); + + player.RemoveAurasDueToSpell(BattlegroundConst.SpellArenaPreparation); + player.ResetAllPowers(); + if (!player.IsGameMaster()) + { + // remove auras with duration lower than 30s + player.RemoveAppliedAuras(aurApp => + { + Aura aura = aurApp.GetBase(); + return !aura.IsPermanent() && aura.GetDuration() <= 30 * Time.InMilliseconds && aurApp.IsPositive() + && !aura.GetSpellInfo().HasAttribute(SpellAttr0.UnaffectedByInvulnerability) && !aura.HasEffectType(AuraType.ModInvisibility); + }); + } + } + } + + CheckWinConditions(); + } + else + { + PlaySoundToAll((uint)BattlegroundSounds.BgStart); + + foreach (var guid in GetPlayers().Keys) + { + Player player = Global.ObjAccessor.FindPlayer(guid); + if (player) + { + player.RemoveAurasDueToSpell(BattlegroundConst.SpellPreparation); + player.ResetAllPowers(); + } + } + // Announce BG starting + if (WorldConfig.GetBoolValue(WorldCfg.BattlegroundQueueAnnouncerEnable)) + Global.WorldMgr.SendWorldText(CypherStrings.BgStartedAnnounceWorld, GetName(), GetMinLevel(), GetMaxLevel()); + } + } + + if (GetRemainingTime() > 0 && (m_EndTime -= (int)diff) > 0) + SetRemainingTime(GetRemainingTime() - diff); + } + + void _ProcessLeave(uint diff) + { + // ********************************************************* + // *** Battleground ENDING SYSTEM *** + // ********************************************************* + // remove all players from Battleground after 2 Time.Minutes + SetRemainingTime(GetRemainingTime() - diff); + if (GetRemainingTime() <= 0) + { + SetRemainingTime(0); + foreach (var guid in m_Players.Keys) + { + RemovePlayerAtLeave(guid, true, true);// remove player from BG + // do not change any Battleground's private variables + } + } + } + + public Player _GetPlayer(ObjectGuid guid, bool offlineRemove, string context) + { + Player player = null; + if (!offlineRemove) + { + player = Global.ObjAccessor.FindPlayer(guid); + if (!player) + Log.outError(LogFilter.Battleground, "Battleground.{0}: player ({1}) not found for BG (map: {1}, instance id: {2})!", context, guid.ToString(), m_MapId, m_InstanceID); + } + return player; + } + + public Player _GetPlayer(KeyValuePair pair, string context) + { + return _GetPlayer(pair.Key, pair.Value.OfflineRemoveTime != 0, context); + } + + Player _GetPlayerForTeam(Team teamId, KeyValuePair pair, string context) + { + Player player = _GetPlayer(pair, context); + if (player) + { + Team team = pair.Value.Team; + if (team == 0) + team = player.GetTeam(); + if (team != teamId) + player = null; + } + return player; + } + + public void SetTeamStartLoc(int teamIndex, Position pos) + { + Contract.Assert(teamIndex < TeamId.Neutral); + StartPosition[teamIndex] = pos; + } + + void SendPacketToAll(ServerPacket packet) + { + foreach (var pair in m_Players) + { + Player player = _GetPlayer(pair, "SendPacketToAll"); + if (player) + player.SendPacket(packet); + } + } + + void SendPacketToTeam(Team team, ServerPacket packet, Player sender, bool self) + { + foreach (var pair in m_Players) + { + Player player = _GetPlayerForTeam(team, pair, "SendPacketToTeam"); + if (player) + { + if (self || sender != player) + player.SendPacket(packet); + } + } + } + + void SendChatMessage(Creature source, byte textId, WorldObject target = null) + { + Global.CreatureTextMgr.SendChat(source, textId, target); + } + + public void PlaySoundToAll(uint soundID) + { + SendPacketToAll(new PlaySound(ObjectGuid.Empty, soundID)); + } + + void PlaySoundToTeam(uint soundID, Team team) + { + foreach (var pair in m_Players) + { + Player player = _GetPlayerForTeam(team, pair, "PlaySoundToTeam"); + if (player) + player.SendPacket(new PlaySound(ObjectGuid.Empty, soundID)); + } + } + + public void CastSpellOnTeam(uint SpellID, Team team) + { + foreach (var pair in m_Players) + { + Player player = _GetPlayerForTeam(team, pair, "CastSpellOnTeam"); + if (player) + player.CastSpell(player, SpellID, true); + } + } + + void RemoveAuraOnTeam(uint SpellID, Team team) + { + foreach (var pair in m_Players) + { + Player player = _GetPlayerForTeam(team, pair, "RemoveAuraOnTeam"); + if (player) + player.RemoveAura(SpellID); + } + } + + public void RewardHonorToTeam(uint Honor, Team team) + { + foreach (var pair in m_Players) + { + Player player = _GetPlayerForTeam(team, pair, "RewardHonorToTeam"); + if (player) + UpdatePlayerScore(player, ScoreType.BonusHonor, Honor); + } + } + + public void RewardReputationToTeam(uint faction_id, uint Reputation, Team team) + { + FactionRecord factionEntry = CliDB.FactionStorage.LookupByKey(faction_id); + if (factionEntry == null) + return; + + foreach (var pair in m_Players) + { + Player player = _GetPlayerForTeam(team, pair, "RewardReputationToTeam"); + if (!player) + continue; + + uint repGain = Reputation; + MathFunctions.AddPct(ref repGain, player.GetTotalAuraModifier(AuraType.ModReputationGain)); + MathFunctions.AddPct(ref repGain, player.GetTotalAuraModifierByMiscValue(AuraType.ModFactionReputationGain, (int)faction_id)); + player.GetReputationMgr().ModifyReputation(factionEntry, (int)repGain); + } + } + + public void UpdateWorldState(uint variable, uint value, bool hidden = false) + { + UpdateWorldState worldstate = new UpdateWorldState(); + worldstate.VariableID = variable; + worldstate.Value = (int)value; + worldstate.Hidden = hidden; + SendPacketToAll(worldstate); + } + + public virtual void EndBattleground(Team winner) + { + RemoveFromBGFreeSlotQueue(); + + CypherStrings winmsg_id = 0; + bool guildAwarded = false; + + if (winner == Team.Alliance) + { + winmsg_id = isBattleground() ? CypherStrings.BgAWins : CypherStrings.ArenaGoldWins; + + PlaySoundToAll((uint)BattlegroundSounds.AllianceWins); + + SetWinner(BattlegroundTeamId.Alliance); + } + else if (winner == Team.Horde) + { + winmsg_id = isBattleground() ? CypherStrings.BgHWins : CypherStrings.ArenaGreenWins; + + PlaySoundToAll((uint)BattlegroundSounds.HordeWins); + + SetWinner(BattlegroundTeamId.Horde); + } + else + { + SetWinner(BattlegroundTeamId.Neutral); + } + + PreparedStatement stmt = null; + ulong battlegroundId = 1; + if (isBattleground() && WorldConfig.GetBoolValue(WorldCfg.BattlegroundStoreStatisticsEnable)) + { + stmt = DB.Characters.GetPreparedStatement(CharStatements.SEL_PVPSTATS_MAXID); + SQLResult result = DB.Characters.Query(stmt); + + if (!result.IsEmpty()) + battlegroundId = result.Read(0) + 1; + + stmt = DB.Characters.GetPreparedStatement(CharStatements.INS_PVPSTATS_BATTLEGROUND); + stmt.AddValue(0, battlegroundId); + stmt.AddValue(1, GetWinner()); + stmt.AddValue(2, GetUniqueBracketId()); + stmt.AddValue(3, GetTypeID(true)); + DB.Characters.Execute(stmt); + } + + SetStatus(BattlegroundStatus.WaitLeave); + //we must set it this way, because end time is sent in packet! + SetRemainingTime(BattlegroundConst.AutocloseBattleground); + + PVPLogData pvpLogData; + BuildPvPLogDataPacket(out pvpLogData); + + BattlegroundQueueTypeId bgQueueTypeId = Global.BattlegroundMgr.BGQueueTypeId(GetTypeID(), GetArenaType()); + + foreach (var pair in m_Players) + { + Team team = pair.Value.Team; + + Player player = _GetPlayer(pair, "EndBattleground"); + if (!player) + continue; + + // should remove spirit of redemption + if (player.HasAuraType(AuraType.SpiritOfRedemption)) + player.RemoveAurasByType(AuraType.ModShapeshift); + + if (!player.IsAlive()) + { + player.ResurrectPlayer(1.0f); + player.SpawnCorpseBones(); + } + else + { + //needed cause else in av some creatures will kill the players at the end + player.CombatStop(); + player.getHostileRefManager().deleteReferences(); + } + + // remove temporary currency bonus auras before rewarding player + player.RemoveAura(BattlegroundConst.SpellHonorableDefender25y); + player.RemoveAura(BattlegroundConst.SpellHonorableDefender60y); + + uint winnerKills = player.GetRandomWinner() ? WorldConfig.GetUIntValue(WorldCfg.BgRewardWinnerHonorLast) : WorldConfig.GetUIntValue(WorldCfg.BgRewardWinnerHonorFirst); + uint loserKills = player.GetRandomWinner() ? WorldConfig.GetUIntValue(WorldCfg.BgRewardLoserHonorLast) : WorldConfig.GetUIntValue(WorldCfg.BgRewardLoserHonorFirst); + + if (isBattleground() && WorldConfig.GetBoolValue(WorldCfg.BattlegroundStoreStatisticsEnable)) + { + stmt = DB.Characters.GetPreparedStatement(CharStatements.INS_PVPSTATS_PLAYER); + var score = PlayerScores.LookupByKey(player.GetGUID()); + + stmt.AddValue(0, battlegroundId); + stmt.AddValue(1, player.GetGUID().GetCounter()); + stmt.AddValue(2, team == winner); + stmt.AddValue(3, score.KillingBlows); + stmt.AddValue(4, score.Deaths); + stmt.AddValue(5, score.HonorableKills); + stmt.AddValue(6, score.BonusHonor); + stmt.AddValue(7, score.DamageDone); + stmt.AddValue(8, score.HealingDone); + stmt.AddValue(9, score.GetAttr1()); + stmt.AddValue(10, score.GetAttr2()); + stmt.AddValue(11, score.GetAttr3()); + stmt.AddValue(12, score.GetAttr4()); + stmt.AddValue(13, score.GetAttr5()); + + DB.Characters.Execute(stmt); + } + + // Reward winner team + if (team == winner) + { + if (IsRandom() || Global.BattlegroundMgr.IsBGWeekend(GetTypeID())) + { + UpdatePlayerScore(player, ScoreType.BonusHonor, GetBonusHonorFromKill(winnerKills)); + if (!player.GetRandomWinner()) + { + player.SetRandomWinner(true); + // TODO: win honor xp + } + } + else + { + // TODO: lose honor xp + } + + player.UpdateCriteria(CriteriaTypes.WinBg, 1); + if (!guildAwarded) + { + guildAwarded = true; + uint guildId = GetBgMap().GetOwnerGuildId(player.GetBGTeam()); + if (guildId != 0) + { + Guild guild = Global.GuildMgr.GetGuildById(guildId); + if (guild) + guild.UpdateCriteria(CriteriaTypes.WinBg, 1, 0, 0, null, player); + } + } + } + else + { + if (IsRandom() || Global.BattlegroundMgr.IsBGWeekend(GetTypeID())) + UpdatePlayerScore(player, ScoreType.BonusHonor, GetBonusHonorFromKill(loserKills)); + } + + player.ResetAllPowers(); + player.CombatStopWithPets(true); + + BlockMovement(player); + + player.SendPacket(pvpLogData); + + BattlefieldStatusActive battlefieldStatus; + Global.BattlegroundMgr.BuildBattlegroundStatusActive(out battlefieldStatus, this, player, player.GetBattlegroundQueueIndex(bgQueueTypeId), player.GetBattlegroundQueueJoinTime(bgQueueTypeId), GetArenaType()); + player.SendPacket(battlefieldStatus); + + player.UpdateCriteria(CriteriaTypes.CompleteBattleground, 1); + } + + if (winmsg_id != 0) + SendMessageToAll(winmsg_id, ChatMsg.BgSystemNeutral); + } + + public uint GetBonusHonorFromKill(uint kills) + { + //variable kills means how many honorable kills you scored (so we need kills * honor_for_one_kill) + uint maxLevel = Math.Min(GetMaxLevel(), 80U); + return Formulas.hk_honor_at_level(maxLevel, kills); + } + + void BlockMovement(Player player) + { + player.SetClientControl(player, false); // movement disabled NOTE: the effect will be automatically removed by client when the player is teleported from the Battleground, so no need to send with byte(1) in RemovePlayerAtLeave() + } + + public virtual void RemovePlayerAtLeave(ObjectGuid guid, bool Transport, bool SendPacket) + { + Team team = GetPlayerTeam(guid); + bool participant = false; + // Remove from lists/maps + var bgPlayer = m_Players.LookupByKey(guid); + if (bgPlayer != null) + { + UpdatePlayersCountByTeam(team, true); // -1 player + m_Players.Remove(guid); + // check if the player was a participant of the match, or only entered through gm command (goname) + participant = true; + } + + if (PlayerScores.ContainsKey(guid)) + PlayerScores.Remove(guid); + + RemovePlayerFromResurrectQueue(guid); + + Player player = Global.ObjAccessor.FindPlayer(guid); + if (player) + { + // should remove spirit of redemption + if (player.HasAuraType(AuraType.SpiritOfRedemption)) + player.RemoveAurasByType(AuraType.ModShapeshift); + + player.RemoveAurasByType(AuraType.Mounted); + + if (!player.IsAlive()) // resurrect on exit + { + player.ResurrectPlayer(1.0f); + player.SpawnCorpseBones(); + } + } + else + Player.OfflineResurrect(guid, null); + + RemovePlayer(player, guid, team); // BG subclass specific code + + BattlegroundTypeId bgTypeId = GetTypeID(); + BattlegroundQueueTypeId bgQueueTypeId = Global.BattlegroundMgr.BGQueueTypeId(GetTypeID(), GetArenaType()); + + if (participant) // if the player was a match participant, remove auras, calc rating, update queue + { + if (player) + { + player.ClearAfkReports(); + + // if arena, remove the specific arena auras + if (isArena()) + { + bgTypeId = BattlegroundTypeId.AA; // set the bg type to all arenas (it will be used for queue refreshing) + + // unsummon current and summon old pet if there was one and there isn't a current pet + player.RemovePet(null, PetSaveMode.NotInSlot); + player.ResummonPetTemporaryUnSummonedIfAny(); + } + if (SendPacket) + { + BattlefieldStatusNone battlefieldStatus; + Global.BattlegroundMgr.BuildBattlegroundStatusNone(out battlefieldStatus, player, player.GetBattlegroundQueueIndex(bgQueueTypeId), player.GetBattlegroundQueueJoinTime(bgQueueTypeId), m_ArenaType); + player.SendPacket(battlefieldStatus); + } + + // this call is important, because player, when joins to Battleground, this method is not called, so it must be called when leaving bg + player.RemoveBattlegroundQueueId(bgQueueTypeId); + } + + // remove from raid group if player is member + Group group = GetBgRaid(team); + if (group) + { + if (!group.RemoveMember(guid)) // group was disbanded + SetBgRaid(team, null); + } + DecreaseInvitedCount(team); + //we should update Battleground queue, but only if bg isn't ending + if (isBattleground() && GetStatus() < BattlegroundStatus.WaitLeave) + { + // a player has left the Battleground, so there are free slots . add to queue + AddToBGFreeSlotQueue(); + Global.BattlegroundMgr.ScheduleQueueUpdate(0, 0, bgQueueTypeId, bgTypeId, GetBracketId()); + } + // Let others know + BattlegroundPlayerLeft playerLeft = new BattlegroundPlayerLeft(); + playerLeft.Guid = guid; + SendPacketToTeam(team, playerLeft, player, false); + } + + if (player) + { + // Do next only if found in Battleground + player.SetBattlegroundId(0, BattlegroundTypeId.None); // We're not in BG. + // reset destination bg team + player.SetBGTeam(0); + + if (Transport) + player.TeleportToBGEntryPoint(); + + Log.outDebug(LogFilter.Battleground, "Removed player {0} from Battleground.", player.GetName()); + } + + //Battleground object will be deleted next Battleground.Update() call + } + + // this method is called when no players remains in Battleground + public virtual void Reset() + { + SetWinner(BattlegroundTeamId.Neutral); + SetStatus(BattlegroundStatus.WaitQueue); + SetElapsedTime(0); + SetRemainingTime(0); + SetLastResurrectTime(0); + m_Events = 0; + + if (m_InvitedAlliance > 0 || m_InvitedHorde > 0) + Log.outError(LogFilter.Battleground, "Battleground.Reset: one of the counters is not 0 (Team.Alliance: {0}, Team.Horde: {1}) for BG (map: {2}, instance id: {3})!", + m_InvitedAlliance, m_InvitedHorde, m_MapId, m_InstanceID); + + m_InvitedAlliance = 0; + m_InvitedHorde = 0; + m_InBGFreeSlotQueue = false; + + m_Players.Clear(); + + PlayerScores.Clear(); + + ResetBGSubclass(); + } + + public void StartBattleground() + { + SetElapsedTime(0); + SetLastResurrectTime(0); + // add BG to free slot queue + AddToBGFreeSlotQueue(); + + // add bg to update list + // This must be done here, because we need to have already invited some players when first BG.Update() method is executed + // and it doesn't matter if we call StartBattleground() more times, because m_Battlegrounds is a map and instance id never changes + Global.BattlegroundMgr.AddBattleground(this); + + if (m_IsRated) + Log.outDebug(LogFilter.Arena, "Arena match type: {0} for Team1Id: {1} - Team2Id: {2} started.", m_ArenaType, m_ArenaTeamIds[TeamId.Alliance], m_ArenaTeamIds[TeamId.Horde]); + } + + public void TeleportPlayerToExploitLocation(Player player) + { + WorldSafeLocsRecord loc = GetExploitTeleportLocation(player.GetBGTeam()); + if (loc != null) + player.TeleportTo(loc.MapID, loc.Loc.X, loc.Loc.Y, loc.Loc.Z, loc.Facing); + } + + public virtual void AddPlayer(Player player) + { + // remove afk from player + if (player.HasFlag(PlayerFields.Flags, PlayerFlags.AFK)) + player.ToggleAFK(); + + // score struct must be created in inherited class + + ObjectGuid guid = player.GetGUID(); + Team team = player.GetBGTeam(); + + BattlegroundPlayer bp = new BattlegroundPlayer(); + bp.OfflineRemoveTime = 0; + bp.Team = team; + bp.ActiveSpec = player.GetInt32Value(PlayerFields.CurrentSpecId); + + // Add to list/maps + m_Players[guid] = bp; + + UpdatePlayersCountByTeam(team, false); // +1 player + + BattlegroundPlayerJoined playerJoined = new BattlegroundPlayerJoined(); + playerJoined.Guid = player.GetGUID(); + SendPacketToTeam(team, playerJoined, player, false); + + // BG Status packet + BattlegroundQueueTypeId bgQueueTypeId = Global.BattlegroundMgr.BGQueueTypeId(m_TypeID, GetArenaType()); + uint queueSlot = player.GetBattlegroundQueueIndex(bgQueueTypeId); + + BattlefieldStatusActive battlefieldStatus; + Global.BattlegroundMgr.BuildBattlegroundStatusActive(out battlefieldStatus, this, player, queueSlot, player.GetBattlegroundQueueJoinTime(bgQueueTypeId), GetArenaType()); + player.SendPacket(battlefieldStatus); + + player.RemoveAurasByType(AuraType.Mounted); + + // add arena specific auras + if (isArena()) + { + player.RemoveArenaEnchantments(EnchantmentSlot.Temp); + + player.DestroyConjuredItems(true); + player.UnsummonPetTemporaryIfAny(); + + if (GetStatus() == BattlegroundStatus.WaitJoin) // not started yet + { + player.CastSpell(player, BattlegroundConst.SpellArenaPreparation, true); + player.ResetAllPowers(); + } + } + else + { + if (GetStatus() == BattlegroundStatus.WaitJoin) // not started yet + { + player.CastSpell(player, BattlegroundConst.SpellPreparation, true); // reduces all mana cost of spells. + + uint countdownMaxForBGType = isArena() ? BattlegroundConst.ArenaCountdownMax : BattlegroundConst.BattlegroundCountdownMax; + StartTimer timer = new StartTimer(); + timer.Type = TimerType.Pvp; + timer.TimeRemaining = countdownMaxForBGType - (GetElapsedTime() / 1000); + timer.TotalTime = countdownMaxForBGType; + + player.SendPacket(timer); + } + } + + player.ResetCriteria(CriteriaTypes.KillCreature, (int)CriteriaCondition.BgMap, GetMapId(), true); + player.ResetCriteria(CriteriaTypes.WinBg, (int)CriteriaCondition.BgMap, GetMapId(), true); + player.ResetCriteria(CriteriaTypes.DamageDone, (int)CriteriaCondition.BgMap, GetMapId(), true); + player.ResetCriteria(CriteriaTypes.BeSpellTarget, (int)CriteriaCondition.BgMap, GetMapId(), true); + player.ResetCriteria(CriteriaTypes.CastSpell, (int)CriteriaCondition.BgMap, GetMapId(), true); + player.ResetCriteria(CriteriaTypes.BgObjectiveCapture, (int)CriteriaCondition.BgMap, GetMapId(), true); + player.ResetCriteria(CriteriaTypes.HonorableKillAtArea, (int)CriteriaCondition.BgMap, GetMapId(), true); + player.ResetCriteria(CriteriaTypes.HonorableKill, (int)CriteriaCondition.BgMap, GetMapId(), true); + player.ResetCriteria(CriteriaTypes.HealingDone, (int)CriteriaCondition.BgMap, GetMapId(), true); + player.ResetCriteria(CriteriaTypes.GetKillingBlows, (int)CriteriaCondition.BgMap, GetMapId(), true); + player.ResetCriteria(CriteriaTypes.SpecialPvpKill, (int)CriteriaCondition.BgMap, GetMapId(), true); + + // setup BG group membership + PlayerAddedToBGCheckIfBGIsRunning(player); + AddOrSetPlayerToCorrectBgGroup(player, team); + } + + // this method adds player to his team's bg group, or sets his correct group if player is already in bg group + public void AddOrSetPlayerToCorrectBgGroup(Player player, Team team) + { + ObjectGuid playerGuid = player.GetGUID(); + Group group = GetBgRaid(team); + if (!group) // first player joined + { + group = new Group(); + SetBgRaid(team, group); + group.Create(player); + } + else // raid already exist + { + if (group.IsMember(playerGuid)) + { + byte subgroup = group.GetMemberGroup(playerGuid); + player.SetBattlegroundOrBattlefieldRaid(group, subgroup); + } + else + { + group.AddMember(player); + Group originalGroup = player.GetOriginalGroup(); + if (originalGroup) + if (originalGroup.IsLeader(playerGuid)) + { + group.ChangeLeader(playerGuid); + group.SendUpdate(); + } + } + } + } + + // This method should be called when player logs into running Battleground + public void EventPlayerLoggedIn(Player player) + { + ObjectGuid guid = player.GetGUID(); + // player is correct pointer + foreach (var id in m_OfflineQueue) + { + if (id == guid) + { + m_OfflineQueue.Remove(id); + break; + } + } + m_Players[guid].OfflineRemoveTime = 0; + PlayerAddedToBGCheckIfBGIsRunning(player); + // if Battleground is starting, then add preparation aura + // we don't have to do that, because preparation aura isn't removed when player logs out + } + + // This method should be called when player logs out from running Battleground + public void EventPlayerLoggedOut(Player player) + { + ObjectGuid guid = player.GetGUID(); + if (!IsPlayerInBattleground(guid)) // Check if this player really is in Battleground (might be a GM who teleported inside) + return; + + // player is correct pointer, it is checked in WorldSession.LogoutPlayer() + m_OfflineQueue.Add(player.GetGUID()); + m_Players[guid].OfflineRemoveTime = Global.WorldMgr.GetGameTime() + BattlegroundConst.MaxOfflineTime; + if (GetStatus() == BattlegroundStatus.InProgress) + { + // drop flag and handle other cleanups + RemovePlayer(player, guid, GetPlayerTeam(guid)); + + // 1 player is logging out, if it is the last, then end arena! + if (isArena()) + if (GetAlivePlayersCountByTeam(player.GetBGTeam()) <= 1 && GetPlayersCountByTeam(GetOtherTeam(player.GetBGTeam())) != 0) + EndBattleground(GetOtherTeam(player.GetBGTeam())); + } + } + + // This method should be called only once ... it adds pointer to queue + void AddToBGFreeSlotQueue() + { + if (!m_InBGFreeSlotQueue && isBattleground()) + { + Global.BattlegroundMgr.AddToBGFreeSlotQueue(m_TypeID, this); + m_InBGFreeSlotQueue = true; + } + } + + // This method removes this Battleground from free queue - it must be called when deleting Battleground + public void RemoveFromBGFreeSlotQueue() + { + if (m_InBGFreeSlotQueue) + { + Global.BattlegroundMgr.RemoveFromBGFreeSlotQueue(m_TypeID, m_InstanceID); + m_InBGFreeSlotQueue = false; + } + } + + // get the number of free slots for team + // returns the number how many players can join Battleground to MaxPlayersPerTeam + public uint GetFreeSlotsForTeam(Team Team) + { + // if BG is starting and WorldCfg.BattlegroundInvitationType == BattlegroundQueueInvitationTypeB.NoBalance, invite anyone + if (GetStatus() == BattlegroundStatus.WaitJoin && WorldConfig.GetIntValue(WorldCfg.BattlegroundInvitationType) == (int)BattlegroundQueueInvitationType.NoBalance) + return (GetInvitedCount(Team) < GetMaxPlayersPerTeam()) ? GetMaxPlayersPerTeam() - GetInvitedCount(Team) : 0; + + // if BG is already started or WorldCfg.BattlegroundInvitationType != BattlegroundQueueInvitationType.NoBalance, do not allow to join too much players of one faction + uint otherTeamInvitedCount; + uint thisTeamInvitedCount; + uint otherTeamPlayersCount; + uint thisTeamPlayersCount; + + if (Team == Team.Alliance) + { + thisTeamInvitedCount = GetInvitedCount(Team.Alliance); + otherTeamInvitedCount = GetInvitedCount(Team.Horde); + thisTeamPlayersCount = GetPlayersCountByTeam(Team.Alliance); + otherTeamPlayersCount = GetPlayersCountByTeam(Team.Horde); + } + else + { + thisTeamInvitedCount = GetInvitedCount(Team.Horde); + otherTeamInvitedCount = GetInvitedCount(Team.Alliance); + thisTeamPlayersCount = GetPlayersCountByTeam(Team.Horde); + otherTeamPlayersCount = GetPlayersCountByTeam(Team.Alliance); + } + if (GetStatus() == BattlegroundStatus.InProgress || GetStatus() == BattlegroundStatus.WaitJoin) + { + // difference based on ppl invited (not necessarily entered battle) + // default: allow 0 + uint diff = 0; + + // allow join one person if the sides are equal (to fill up bg to minPlayerPerTeam) + if (otherTeamInvitedCount == thisTeamInvitedCount) + diff = 1; + // allow join more ppl if the other side has more players + else if (otherTeamInvitedCount > thisTeamInvitedCount) + diff = otherTeamInvitedCount - thisTeamInvitedCount; + + // difference based on max players per team (don't allow inviting more) + uint diff2 = (thisTeamInvitedCount < GetMaxPlayersPerTeam()) ? GetMaxPlayersPerTeam() - thisTeamInvitedCount : 0; + // difference based on players who already entered + // default: allow 0 + uint diff3 = 0; + // allow join one person if the sides are equal (to fill up bg minPlayerPerTeam) + if (otherTeamPlayersCount == thisTeamPlayersCount) + diff3 = 1; + // allow join more ppl if the other side has more players + else if (otherTeamPlayersCount > thisTeamPlayersCount) + diff3 = otherTeamPlayersCount - thisTeamPlayersCount; + // or other side has less than minPlayersPerTeam + else if (thisTeamInvitedCount <= GetMinPlayersPerTeam()) + diff3 = GetMinPlayersPerTeam() - thisTeamInvitedCount + 1; + + // return the minimum of the 3 differences + + // min of diff and diff 2 + diff = Math.Min(diff, diff2); + // min of diff, diff2 and diff3 + return Math.Min(diff, diff3); + } + return 0; + } + + public bool HasFreeSlots() + { + return GetPlayersSize() < GetMaxPlayers(); + } + + public void BuildPvPLogDataPacket(out PVPLogData pvpLogData) + { + pvpLogData = new PVPLogData(); + + if (GetStatus() == BattlegroundStatus.WaitLeave) + pvpLogData.Winner.Set((byte)GetWinner()); + + foreach (var score in PlayerScores) + { + PVPLogData.PlayerData playerData = new PVPLogData.PlayerData(); + + playerData.PlayerGUID = score.Value.PlayerGuid; + playerData.Kills = score.Value.KillingBlows; + playerData.Faction = (byte)score.Value.TeamId; + if (score.Value.HonorableKills != 0 || score.Value.Deaths != 0 || score.Value.BonusHonor != 0) + { + playerData.Honor.HasValue = true; + PVPLogData.HonorData honorData = playerData.Honor.Value; + honorData.HonorKills = score.Value.HonorableKills; + honorData.Deaths = score.Value.Deaths; + honorData.ContributionPoints = score.Value.BonusHonor; + } + + playerData.DamageDone = score.Value.DamageDone; + playerData.HealingDone = score.Value.HealingDone; + score.Value.BuildObjectivesBlock(playerData.Stats); + + Player player = Global.ObjAccessor.GetPlayer(GetBgMap(), playerData.PlayerGUID); + if (player) + { + playerData.IsInWorld = true; + playerData.PrimaryTalentTree = (int)player.GetUInt32Value(PlayerFields.CurrentSpecId); + playerData.PlayerRace = player.GetRace(); + } + + //if (isRated()) + //{ + // playerData.PreMatchRating; + // playerData.RatingChange; + // playerData.PreMatchMMR; + // playerData.MmrChange; + //} + + pvpLogData.Players.Add(playerData); + } + + if (isRated()) + { + pvpLogData.Ratings.HasValue = true; + PVPLogData.RatingData ratingData = pvpLogData.Ratings.Value; + for (byte i = 0; i < SharedConst.BGTeamsCount; ++i) + { + ratingData.Postmatch[i] = _arenaTeamScores[i].NewRating; + ratingData.Prematch[i] = _arenaTeamScores[i].OldRating; + ratingData.PrematchMMR[i] = (int)_arenaTeamScores[i].MatchmakerRating; + } + } + + pvpLogData.PlayerCount[0] = (sbyte)GetPlayersCountByTeam(Team.Horde); + pvpLogData.PlayerCount[1] = (sbyte)GetPlayersCountByTeam(Team.Alliance); + } + + public virtual bool UpdatePlayerScore(Player player, ScoreType type, uint value, bool doAddHonor = true) + { + var bgScore = PlayerScores.LookupByKey(player.GetGUID()); + if (bgScore == null) // player not found... + return false; + + if (type == ScoreType.BonusHonor && doAddHonor && isBattleground()) + player.RewardHonor(null, 1, (int)value); + else + bgScore.UpdateScore(type, value); + + return true; + } + + public void AddPlayerToResurrectQueue(ObjectGuid npc_guid, ObjectGuid player_guid) + { + m_ReviveQueue.Add(npc_guid, player_guid); + + Player player = Global.ObjAccessor.FindPlayer(player_guid); + if (!player) + return; + + player.CastSpell(player, BattlegroundConst.SpellWaitingForResurrect, true); + } + + public void RemovePlayerFromResurrectQueue(ObjectGuid player_guid) + { + foreach (var pair in m_ReviveQueue.KeyValueList) + { + if (pair.Value == player_guid) + { + m_ReviveQueue.Remove(pair); + Player player = Global.ObjAccessor.FindPlayer(player_guid); + if (player) + player.RemoveAurasDueToSpell(BattlegroundConst.SpellWaitingForResurrect); + return; + } + + } + } + + public void RelocateDeadPlayers(ObjectGuid guideGuid) + { + // Those who are waiting to resurrect at this node are taken to the closest own node's graveyard + List ghostList = m_ReviveQueue[guideGuid]; + if (!ghostList.Empty()) + { + WorldSafeLocsRecord closestGrave = null; + foreach (var guid in ghostList) + { + Player player = Global.ObjAccessor.FindPlayer(guid); + if (!player) + continue; + + if (closestGrave == null) + closestGrave = GetClosestGraveYard(player); + + if (closestGrave != null) + player.TeleportTo(GetMapId(), closestGrave.Loc.X, closestGrave.Loc.Y, closestGrave.Loc.Z, player.GetOrientation()); + } + ghostList.Clear(); + } + } + + public bool AddObject(int type, uint entry, float x, float y, float z, float o, float rotation0, float rotation1, float rotation2, float rotation3, uint respawnTime = 0, GameObjectState goState = GameObjectState.Ready) + { + Map map = FindBgMap(); + if (!map) + return false; + + Quaternion rotation = new Quaternion(rotation0, rotation1, rotation2, rotation3); + // Temporally add safety check for bad spawns and send log (object rotations need to be rechecked in sniff) + if (rotation0 == 0 && rotation1 == 0 && rotation2 == 0 && rotation3 == 0) + { + Log.outDebug(LogFilter.Battleground, "Battleground.AddObject: gameoobject [entry: {0}, object type: {1}] for BG (map: {2}) has zeroed rotation fields, " + + "orientation used temporally, but please fix the spawn", entry, type, m_MapId); + + rotation = new Quaternion(Matrix3.fromEulerAnglesZYX(o, 0.0f, 0.0f)); + } + + // Must be created this way, adding to godatamap would add it to the base map of the instance + // and when loading it (in go.LoadFromDB()), a new guid would be assigned to the object, and a new object would be created + // So we must create it specific for this instance + GameObject go = new GameObject(); + if (!go.Create(entry, GetBgMap(), PhaseMasks.Normal, new Position(x, y, z, o), rotation, 100, goState)) + { + Log.outError(LogFilter.Battleground, "Battleground.AddObject: cannot create gameobject (entry: {0}) for BG (map: {1}, instance id: {2})!", entry, m_MapId, m_InstanceID); + return false; + } + + // Add to world, so it can be later looked up from HashMapHolder + if (!map.AddToMap(go)) + return false; + + BgObjects[type] = go.GetGUID(); + return true; + } + + public bool AddObject(int type, uint entry, Position pos, float rotation0, float rotation1, float rotation2, float rotation3, uint respawnTime = 0, GameObjectState goState = GameObjectState.Ready) + { + return AddObject(type, entry, pos.GetPositionX(), pos.GetPositionY(), pos.GetPositionZ(), pos.GetOrientation(), rotation0, rotation1, rotation2, rotation3, respawnTime, goState); + } + + // Some doors aren't despawned so we cannot handle their closing in gameobject.update() + // It would be nice to correctly implement GO_ACTIVATED state and open/close doors in gameobject code + public void DoorClose(int type) + { + GameObject obj = GetBgMap().GetGameObject(BgObjects[type]); + if (obj) + { + // If doors are open, close it + if (obj.getLootState() == LootState.Activated && obj.GetGoState() != GameObjectState.Ready) + { + obj.SetLootState(LootState.Ready); + obj.SetGoState(GameObjectState.Ready); + } + } + else + Log.outError(LogFilter.Battleground, "Battleground.DoorClose: door gameobject (type: {0}, {1}) not found for BG (map: {2}, instance id: {3})!", + type, BgObjects[type].ToString(), m_MapId, m_InstanceID); + } + + public void DoorOpen(int type) + { + GameObject obj = GetBgMap().GetGameObject(BgObjects[type]); + if (obj) + { + obj.SetLootState(LootState.Activated); + obj.SetGoState(GameObjectState.Active); + } + else + Log.outError(LogFilter.Battleground, "Battleground.DoorOpen: door gameobject (type: {0}, {1}) not found for BG (map: {2}, instance id: {3})!", + type, BgObjects[type].ToString(), m_MapId, m_InstanceID); + } + + public GameObject GetBGObject(int type) + { + if (!BgObjects.ContainsKey(type)) + return null; + + GameObject obj = GetBgMap().GetGameObject(BgObjects[type]); + if (!obj) + Log.outError(LogFilter.Battleground, "Battleground.GetBGObject: gameobject (type: {0}, {1}) not found for BG (map: {2}, instance id: {3})!", type, BgObjects[type].ToString(), m_MapId, m_InstanceID); + + return obj; + } + + public Creature GetBGCreature(int type) + { + if (!BgCreatures.ContainsKey(type)) + return null; + + Creature creature = GetBgMap().GetCreature(BgCreatures[type]); + if (!creature) + Log.outError(LogFilter.Battleground, "Battleground.GetBGCreature: creature (type: {0}, {1}) not found for BG (map: {2}, instance id: {3})!", type, BgCreatures[type].ToString(), m_MapId, m_InstanceID); + + return creature; + } + + public void SpawnBGObject(int type, uint respawntime) + { + Map map = FindBgMap(); + if (map != null) + { + GameObject obj = map.GetGameObject(BgObjects[type]); + if (obj) + { + if (respawntime != 0) + obj.SetLootState(LootState.JustDeactivated); + else + { + if (obj.getLootState() == LootState.JustDeactivated) + // Change state from GO_JUST_DEACTIVATED to GO_READY in case Battleground is starting again + obj.SetLootState(LootState.Ready); + } + obj.SetRespawnTime((int)respawntime); + map.AddToMap(obj); + } + } + } + + public virtual Creature AddCreature(uint entry, int type, float x, float y, float z, float o, int teamIndex = TeamId.Neutral, uint respawntime = 0, Transport transport = null) + { + Map map = FindBgMap(); + if (!map) + return null; + + if (transport) + { + Creature transCreature = transport.SummonPassenger(entry, new Position(x, y, z, o), TempSummonType.ManualDespawn); + if (transCreature) + { + BgCreatures[type] = transCreature.GetGUID(); + return transCreature; + } + + return null; + } + + Creature creature = new Creature(); + if (!creature.Create(map.GenerateLowGuid(HighGuid.Creature), map, PhaseMasks.Normal, entry, x, y, z, o)) + { + Log.outError(LogFilter.Battleground, "Battleground.AddCreature: cannot create creature (entry: {0}) for BG (map: {1}, instance id: {2})!", + entry, m_MapId, m_InstanceID); + return null; + } + + creature.SetHomePosition(x, y, z, o); + + CreatureTemplate cinfo = Global.ObjectMgr.GetCreatureTemplate(entry); + if (cinfo == null) + { + Log.outError(LogFilter.Battleground, "Battleground.AddCreature: creature template (entry: {0}) does not exist for BG (map: {1}, instance id: {2})!", + entry, m_MapId, m_InstanceID); + return null; + } + // Force using DB speeds + creature.SetSpeed(UnitMoveType.Walk, cinfo.SpeedWalk); + creature.SetSpeed(UnitMoveType.Run, cinfo.SpeedRun); + + if (!map.AddToMap(creature)) + return null; + + BgCreatures[type] = creature.GetGUID(); + + if (respawntime != 0) + creature.SetRespawnDelay(respawntime); + + return creature; + } + + public Creature AddCreature(uint entry, int type, Position pos, int teamIndex = TeamId.Neutral, uint respawntime = 0, Transport transport = null) + { + return AddCreature(entry, type, pos.GetPositionX(), pos.GetPositionY(), pos.GetPositionZ(), pos.GetOrientation(), teamIndex, respawntime, transport); + } + + public bool DelCreature(int type) + { + if (!BgCreatures.ContainsKey(type) || BgCreatures[type].IsEmpty()) + return true; + + Creature creature = GetBgMap().GetCreature(BgCreatures[type]); + if (creature) + { + creature.AddObjectToRemoveList(); + BgCreatures[type].Clear(); + return true; + } + + Log.outError(LogFilter.Battleground, "Battleground.DelCreature: creature (type: {0}, {1}) not found for BG (map: {2}, instance id: {3})!", + type, BgCreatures[type].ToString(), m_MapId, m_InstanceID); + BgCreatures[type].Clear(); + return false; + } + + bool DelObject(int type) + { + if (BgObjects[type].IsEmpty()) + return true; + + GameObject obj = GetBgMap().GetGameObject(BgObjects[type]); + if (obj) + { + obj.SetRespawnTime(0); // not save respawn time + obj.Delete(); + BgObjects[type].Clear(); + return true; + } + Log.outError(LogFilter.Battleground, "Battleground.DelObject: gameobject (type: {0}, {1}) not found for BG (map: {2}, instance id: {3})!", + type, BgObjects[type].ToString(), m_MapId, m_InstanceID); + BgObjects[type].Clear(); + return false; + } + + public bool AddSpiritGuide(int type, float x, float y, float z, float o, int teamIndex) + { + uint entry = (uint)(teamIndex == TeamId.Alliance ? BattlegroundCreatures.A_SpiritGuide : BattlegroundCreatures.H_SpiritGuide); + + Creature creature = AddCreature(entry, type, x, y, z, o); + if (creature) + { + creature.setDeathState(DeathState.Dead); + creature.AddChannelObject(creature.GetGUID()); + // aura + //todo Fix display here + // creature.SetVisibleAura(0, SPELL_SPIRIT_HEAL_CHANNEL); + // casting visual effect + creature.SetUInt32Value(UnitFields.ChannelSpell, BattlegroundConst.SpellSpiritHealChannel); + // correct cast speed + creature.SetFloatValue(UnitFields.ModCastSpeed, 1.0f); + creature.SetFloatValue(UnitFields.ModCastHaste, 1.0f); + //creature.CastSpell(creature, SPELL_SPIRIT_HEAL_CHANNEL, true); + return true; + } + Log.outError(LogFilter.Battleground, "Battleground.AddSpiritGuide: cannot create spirit guide (type: {0}, entry: {1}) for BG (map: {2}, instance id: {3})!", + type, entry, m_MapId, m_InstanceID); + EndNow(); + return false; + } + + public bool AddSpiritGuide(int type, Position pos, int teamIndex = TeamId.Neutral) + { + return AddSpiritGuide(type, pos.GetPositionX(), pos.GetPositionY(), pos.GetPositionZ(), pos.GetOrientation(), teamIndex); + } + + public void SendMessageToAll(CypherStrings entry, ChatMsg type, Player source = null) + { + if (entry == 0) + return; + + var bg_builder = new BattlegroundChatBuilder(type, entry, source); + var bg_do = new LocalizedPacketDo(bg_builder); + BroadcastWorker(bg_do); + } + + public void SendMessageToAll(CypherStrings entry, ChatMsg type, Player source, params object[] args) + { + if (entry == 0) + return; + + var bg_builder = new BattlegroundChatBuilder(type, entry, source, args); + var bg_do = new LocalizedPacketDo(bg_builder); + BroadcastWorker(bg_do); + } + + public void SendMessage2ToAll(CypherStrings entry, ChatMsg type, Player source, CypherStrings arg1 = 0, CypherStrings arg2 = 0) + { + var bg_builder = new BattlegroundChatBuilder(type, entry, source, arg1, arg2); + var bg_do = new LocalizedPacketDo(bg_builder); + BroadcastWorker(bg_do); + } + + void EndNow() + { + RemoveFromBGFreeSlotQueue(); + SetStatus(BattlegroundStatus.WaitLeave); + SetRemainingTime(0); + } + + // IMPORTANT NOTICE: + // buffs aren't spawned/despawned when players captures anything + // buffs are in their positions when Battleground starts + public void HandleTriggerBuff(ObjectGuid goGuid) + { + GameObject obj = GetBgMap().GetGameObject(goGuid); + if (!obj || obj.GetGoType() != GameObjectTypes.Trap || !obj.isSpawned()) + return; + + // Change buff type, when buff is used: + int index = BgObjects.Count - 1; + while (index >= 0 && BgObjects[index] != goGuid) + index--; + if (index < 0) + { + Log.outError(LogFilter.Battleground, "Battleground.HandleTriggerBuff: cannot find buff gameobject ({0}, entry: {1}, type: {2}) in internal data for BG (map: {3}, instance id: {4})!", + goGuid.ToString(), obj.GetEntry(), obj.GetGoType(), m_MapId, m_InstanceID); + return; + } + + // Randomly select new buff + int buff = RandomHelper.IRand(0, 2); + uint entry = obj.GetEntry(); + if (m_BuffChange && entry != Buff_Entries[buff]) + { + // Despawn current buff + SpawnBGObject(index, BattlegroundConst.RespawnOneDay); + // Set index for new one + for (byte currBuffTypeIndex = 0; currBuffTypeIndex < 3; ++currBuffTypeIndex) + { + if (entry == Buff_Entries[currBuffTypeIndex]) + { + index -= currBuffTypeIndex; + index += buff; + } + } + } + + SpawnBGObject(index, BattlegroundConst.BuffRespawnTime); + } + + public virtual void HandleKillPlayer(Player victim, Player killer) + { + // Keep in mind that for arena this will have to be changed a bit + + // Add +1 deaths + UpdatePlayerScore(victim, ScoreType.Deaths, 1); + // Add +1 kills to group and +1 killing_blows to killer + if (killer) + { + // Don't reward credit for killing ourselves, like fall damage of hellfire (warlock) + if (killer == victim) + return; + + UpdatePlayerScore(killer, ScoreType.HonorableKills, 1); + UpdatePlayerScore(killer, ScoreType.KillingBlows, 1); + + foreach (var guid in m_Players.Keys) + { + Player creditedPlayer = Global.ObjAccessor.FindPlayer(guid); + if (!creditedPlayer || creditedPlayer == killer) + continue; + + if (creditedPlayer.GetTeam() == killer.GetTeam() && creditedPlayer.IsAtGroupRewardDistance(victim)) + UpdatePlayerScore(creditedPlayer, ScoreType.HonorableKills, 1); + } + } + + if (!isArena()) + { + // To be able to remove insignia -- ONLY IN Battlegrounds + victim.SetFlag(UnitFields.Flags, UnitFlags.Skinnable); + RewardXPAtKill(killer, victim); + } + } + public virtual void HandleKillUnit(Creature creature, Player killer) { } + + // Return the player's team based on Battlegroundplayer info + // Used in same faction arena matches mainly + Team GetPlayerTeam(ObjectGuid guid) + { + var player = m_Players.LookupByKey(guid); + if (player != null) + return player.Team; + return 0; + } + + public Team GetOtherTeam(Team teamId) + { + switch (teamId) + { + case Team.Alliance: + return Team.Horde; + case Team.Horde: + return Team.Alliance; + default: + return Team.Other; + } + } + + public bool IsPlayerInBattleground(ObjectGuid guid) + { + return m_Players.ContainsKey(guid); + } + + void PlayerAddedToBGCheckIfBGIsRunning(Player player) + { + if (GetStatus() != BattlegroundStatus.WaitLeave) + return; + + PVPLogData pvpLogData; + BattlegroundQueueTypeId bgQueueTypeId = Global.BattlegroundMgr.BGQueueTypeId(GetTypeID(), GetArenaType()); + + BlockMovement(player); + + BuildPvPLogDataPacket(out pvpLogData); + player.SendPacket(pvpLogData); + + BattlefieldStatusActive battlefieldStatus; + Global.BattlegroundMgr.BuildBattlegroundStatusActive(out battlefieldStatus, this, player, player.GetBattlegroundQueueIndex(bgQueueTypeId), player.GetBattlegroundQueueJoinTime(bgQueueTypeId), GetArenaType()); + player.SendPacket(battlefieldStatus); + } + + public uint GetAlivePlayersCountByTeam(Team Team) + { + uint count = 0; + foreach (var pair in m_Players) + { + if (pair.Value.Team == Team) + { + Player player = Global.ObjAccessor.FindPlayer(pair.Key); + if (player && player.IsAlive() && player.GetShapeshiftForm() != ShapeShiftForm.SpiritOfRedemption) + ++count; + } + } + return count; + } + + public void SetHoliday(bool is_holiday) + { + m_HonorMode = is_holiday ? BGHonorMode.Holiday : BGHonorMode.Normal; + } + + int GetObjectType(ObjectGuid guid) + { + for (int i = 0; i < BgObjects.Count; ++i) + if (BgObjects[i] == guid) + return i; + Log.outError(LogFilter.Battleground, "Battleground.GetObjectType: player used gameobject ({0}) which is not in internal data for BG (map: {1}, instance id: {2}), cheating?", + guid.ToString(), m_MapId, m_InstanceID); + return -1; + } + + void SetBgRaid(Team team, Group bg_raid) + { + Group old_raid = m_BgRaids[GetTeamIndexByTeamId(team)]; + if (old_raid) + old_raid.SetBattlegroundGroup(null); + if (bg_raid) + bg_raid.SetBattlegroundGroup(this); + m_BgRaids[GetTeamIndexByTeamId(team)] = bg_raid; + } + + public virtual WorldSafeLocsRecord GetClosestGraveYard(Player player) + { + return Global.ObjectMgr.GetClosestGraveYard(player.GetPositionX(), player.GetPositionY(), player.GetPositionZ(), player.GetMapId(), player.GetTeam()); + } + + public void StartCriteriaTimer(CriteriaTimedTypes type, uint entry) + { + foreach (var guid in GetPlayers().Keys) + { + Player player = Global.ObjAccessor.FindPlayer(guid); + if (player) + player.StartCriteriaTimer(type, entry); + } + } + + public void SetBracket(PvpDifficultyRecord bracketEntry) + { + m_BracketId = bracketEntry.GetBracketId(); + SetLevelRange(bracketEntry.MinLevel, bracketEntry.MaxLevel); + } + + void RewardXPAtKill(Player killer, Player victim) + { + if (WorldConfig.GetBoolValue(WorldCfg.BgXpForKill) && killer && victim) + killer.RewardPlayerAndGroupAtKill(victim, true); + } + + public uint GetTeamScore(int teamIndex) + { + if (teamIndex == TeamId.Alliance || teamIndex == TeamId.Horde) + return m_TeamScores[teamIndex]; + + Log.outError(LogFilter.Battleground, "GetTeamScore with wrong Team {0} for BG {1}", teamIndex, GetTypeID()); + return 0; + } + + public virtual void HandleAreaTrigger(Player player, uint trigger, bool entered) + { + Log.outDebug(LogFilter.Battleground, "Unhandled AreaTrigger {0} in Battleground {1}. Player coords (x: {2}, y: {3}, z: {4})", + trigger, player.GetMapId(), player.GetPositionX(), player.GetPositionY(), player.GetPositionZ()); + } + + public virtual bool CheckAchievementCriteriaMeet(uint criteriaId, Player source, Unit target, uint miscvalue1 = 0) + { + Log.outError(LogFilter.Battleground, "Battleground.CheckAchievementCriteriaMeet: No implementation for criteria {0}", criteriaId); + return false; + } + + public virtual bool SetupBattleground() + { + return true; + } + + byte GetUniqueBracketId() + { + return (byte)((GetMinLevel() / 5) - 1); // 10 - 1, 15 - 2, 20 - 3, etc. + } + + public virtual void StartingEventCloseDoors() { } + public virtual void StartingEventOpenDoors() { } + public virtual void ResetBGSubclass() { } // must be implemented in BG subclass + + public virtual void DestroyGate(Player player, GameObject go) { } + public virtual bool IsAllNodesControlledByTeam(Team team) { return false; } + + public string GetName() { return m_Name; } + public ulong GetQueueId() { return m_queueId; } + public BattlegroundTypeId GetTypeID(bool GetRandom = false) { return GetRandom ? m_RandomTypeID : m_TypeID; } + public BattlegroundBracketId GetBracketId() { return m_BracketId; } + public uint GetInstanceID() { return m_InstanceID; } + public BattlegroundStatus GetStatus() { return m_Status; } + public uint GetClientInstanceID() { return m_ClientInstanceID; } + public uint GetElapsedTime() { return m_StartTime; } + public uint GetRemainingTime() { return (uint)m_EndTime; } + public uint GetLastResurrectTime() { return m_LastResurrectTime; } + uint GetMaxPlayers() { return m_MaxPlayers; } + uint GetMinPlayers() { return m_MinPlayers; } + + public uint GetMinLevel() { return m_LevelMin; } + public uint GetMaxLevel() { return m_LevelMax; } + + public uint GetMaxPlayersPerTeam() { return m_MaxPlayersPerTeam; } + public uint GetMinPlayersPerTeam() { return m_MinPlayersPerTeam; } + + int GetStartDelayTime() { return m_StartDelayTime; } + public ArenaTypes GetArenaType() { return m_ArenaType; } + BattlegroundTeamId GetWinner() { return _winnerTeamId; } + uint GetScriptId() { return ScriptId; } + public bool IsRandom() { return m_IsRandom; } + + public void SetQueueId(ulong queueId) { m_queueId = queueId; } + public void SetName(string Name) { m_Name = Name; } + public void SetTypeID(BattlegroundTypeId TypeID) { m_TypeID = TypeID; } + public void SetRandomTypeID(BattlegroundTypeId TypeID) { m_RandomTypeID = TypeID; } + //here we can count minlevel and maxlevel for players + public void SetInstanceID(uint InstanceID) { m_InstanceID = InstanceID; } + public void SetStatus(BattlegroundStatus Status) { m_Status = Status; } + public void SetClientInstanceID(uint InstanceID) { m_ClientInstanceID = InstanceID; } + public void SetElapsedTime(uint Time) { m_StartTime = Time; } + public void SetRemainingTime(uint Time) { m_EndTime = (int)Time; } + public void SetLastResurrectTime(uint Time) { m_LastResurrectTime = Time; } + public void SetMaxPlayers(uint MaxPlayers) { m_MaxPlayers = MaxPlayers; } + public void SetMinPlayers(uint MinPlayers) { m_MinPlayers = MinPlayers; } + public void SetLevelRange(uint min, uint max) { m_LevelMin = min; m_LevelMax = max; } + public void SetRated(bool state) { m_IsRated = state; } + public void SetArenaType(ArenaTypes type) { m_ArenaType = type; } + public void SetArenaorBGType(bool _isArena) { m_IsArena = _isArena; } + public void SetWinner(BattlegroundTeamId winnerTeamId) { _winnerTeamId = winnerTeamId; } + public void SetScriptId(uint scriptId) { ScriptId = scriptId; } + + void ModifyStartDelayTime(int diff) { m_StartDelayTime -= diff; } + void SetStartDelayTime(BattlegroundStartTimeIntervals Time) { m_StartDelayTime = (int)Time; } + + public void SetMaxPlayersPerTeam(uint MaxPlayers) { m_MaxPlayersPerTeam = MaxPlayers; } + public void SetMinPlayersPerTeam(uint MinPlayers) { m_MinPlayersPerTeam = MinPlayers; } + + public void DecreaseInvitedCount(Team team) + { + if (team == Team.Alliance) + --m_InvitedAlliance; + else + --m_InvitedHorde; + } + public void IncreaseInvitedCount(Team team) + { + if (team == Team.Alliance) + ++m_InvitedAlliance; + else + ++m_InvitedHorde; + } + + public void SetRandom(bool isRandom) { m_IsRandom = isRandom; } + uint GetInvitedCount(Team team) { return (team == Team.Alliance) ? m_InvitedAlliance : m_InvitedHorde; } + + public bool isArena() { return m_IsArena; } + public bool isBattleground() { return !m_IsArena; } + public bool isRated() { return m_IsRated; } + + public Dictionary GetPlayers() { return m_Players; } + uint GetPlayersSize() { return (uint)m_Players.Count; } + uint GetPlayerScoresSize() { return (uint)PlayerScores.Count; } + uint GetReviveQueueSize() { return (uint)m_ReviveQueue.Count; } + + public void SetMapId(uint MapID) { m_MapId = MapID; } + public uint GetMapId() { return m_MapId; } + + public void SetBgMap(BattlegroundMap map) { m_Map = map; } + public BattlegroundMap GetBgMap() { return m_Map; } + BattlegroundMap FindBgMap() { return m_Map; } + + public Position GetTeamStartPosition(int teamIndex) + { + Contract.Assert(teamIndex < TeamId.Neutral); + return StartPosition[teamIndex]; + } + + public void SetStartMaxDist(float startMaxDist) { m_StartMaxDist = startMaxDist; } + float GetStartMaxDist() { return m_StartMaxDist; } + + public virtual void FillInitialWorldStates(InitWorldStates data) { } + + Group GetBgRaid(Team team) { return m_BgRaids[GetTeamIndexByTeamId(team)]; } + + public static int GetTeamIndexByTeamId(Team team) { return team == Team.Alliance ? TeamId.Alliance : TeamId.Horde; } + public uint GetPlayersCountByTeam(Team team) { return m_PlayersCount[GetTeamIndexByTeamId(team)]; } + void UpdatePlayersCountByTeam(Team team, bool remove) + { + if (remove) + --m_PlayersCount[GetTeamIndexByTeamId(team)]; + else + ++m_PlayersCount[GetTeamIndexByTeamId(team)]; + } + + public virtual void CheckWinConditions() { } + + public void SetArenaTeamIdForTeam(Team team, uint ArenaTeamId) { m_ArenaTeamIds[GetTeamIndexByTeamId(team)] = ArenaTeamId; } + public uint GetArenaTeamIdForTeam(Team team) { return m_ArenaTeamIds[GetTeamIndexByTeamId(team)]; } + public uint GetArenaTeamIdByIndex(uint index) { return m_ArenaTeamIds[index]; } + public void SetArenaMatchmakerRating(Team team, uint MMR) { m_ArenaTeamMMR[GetTeamIndexByTeamId(team)] = MMR; } + public uint GetArenaMatchmakerRating(Team team) { return m_ArenaTeamMMR[GetTeamIndexByTeamId(team)]; } + + // Battleground events + public virtual void EventPlayerDroppedFlag(Player player) { } + public virtual void EventPlayerClickedOnFlag(Player player, GameObject target_obj) { } + + public virtual void ProcessEvent(WorldObject obj, uint eventId, WorldObject invoker = null) { } + + // this function can be used by spell to interact with the BG map + public virtual void DoAction(uint action, ulong arg) { } + + public virtual void HandlePlayerResurrect(Player player) { } + + public virtual WorldSafeLocsRecord GetExploitTeleportLocation(Team team) { return null; } + + public virtual bool HandlePlayerUnderMap(Player player) { return false; } + + public bool ToBeDeleted() { return m_SetDeleteThis; } + void SetDeleteThis() { m_SetDeleteThis = true; } + + bool CanAwardArenaPoints() { return m_LevelMin >= 71; } + + public virtual ObjectGuid GetFlagPickerGUID(int teamIndex = -1) { return ObjectGuid.Empty; } + public virtual void SetDroppedFlagGUID(ObjectGuid guid, int teamIndex = -1) { } + public virtual void HandleQuestComplete(uint questid, Player player) { } + public virtual bool CanActivateGO(int entry, uint team) { return true; } + public virtual bool IsSpellAllowed(uint spellId, Player player) { return true; } + + public virtual void RemovePlayer(Player player, ObjectGuid guid, Team team) { } + + public virtual bool PreUpdateImpl(uint diff) { return true; } + + public virtual void PostUpdateImpl(uint diff) { } + + void BroadcastWorker(IDoWork _do) + { + foreach (var pair in m_Players) + { + Player player = _GetPlayer(pair, "BroadcastWorker"); + if (player) + _do.Invoke(player); + } + } + + public static implicit operator bool (Battleground bg) + { + return bg != null; + } + + #region Fields + protected Dictionary PlayerScores = new Dictionary(); // Player scores + // Player lists, those need to be accessible by inherited classes + Dictionary m_Players = new Dictionary(); + // Spirit Guide guid + Player list GUIDS + MultiMap m_ReviveQueue = new MultiMap(); + + // these are important variables used for starting messages + BattlegroundEventFlags m_Events; + public BattlegroundStartTimeIntervals[] StartDelayTimes = new BattlegroundStartTimeIntervals[4]; + // this must be filled inructors! + public CypherStrings[] StartMessageIds = new CypherStrings[4]; + + public bool m_BuffChange; + bool m_IsRandom; + + public BGHonorMode m_HonorMode; + public uint[] m_TeamScores = new uint[SharedConst.BGTeamsCount]; + + public ArenaTeamScore[] _arenaTeamScores = new ArenaTeamScore[SharedConst.BGTeamsCount]; + + protected Dictionary BgObjects = new Dictionary(); + protected Dictionary BgCreatures = new Dictionary(); + + public uint[] Buff_Entries = { BattlegroundConst.SpeedBuff, BattlegroundConst.RegenBuff, BattlegroundConst.BerserkerBuff }; + + // Battleground + BattlegroundTypeId m_TypeID; + BattlegroundTypeId m_RandomTypeID; + uint m_InstanceID; // Battleground Instance's GUID! + BattlegroundStatus m_Status; + uint m_ClientInstanceID; // the instance-id which is sent to the client and without any other internal use + uint m_StartTime; + uint m_CountdownTimer; + uint m_ResetStatTimer; + uint m_ValidStartPositionTimer; + int m_EndTime; // it is set to 120000 when bg is ending and it decreases itself + uint m_LastResurrectTime; + BattlegroundBracketId m_BracketId; + ArenaTypes m_ArenaType; // 2=2v2, 3=3v3, 5=5v5 + bool m_InBGFreeSlotQueue; // used to make sure that BG is only once inserted into the BattlegroundMgr.BGFreeSlotQueue[bgTypeId] deque + bool m_SetDeleteThis; // used for safe deletion of the bg after end / all players leave + bool m_IsArena; + BattlegroundTeamId _winnerTeamId; + int m_StartDelayTime; + bool m_IsRated; // is this battle rated? + bool m_PrematureCountDown; + uint m_PrematureCountDownTimer; + string m_Name; + ulong m_queueId; + uint m_LastPlayerPositionBroadcast; + + // Player lists + List m_ResurrectQueue = new List(); // Player GUID + List m_OfflineQueue = new List(); // Player GUID + + // Invited counters are useful for player invitation to BG - do not allow, if BG is started to one faction to have 2 more players than another faction + // Invited counters will be changed only when removing already invited player from queue, removing player from Battleground and inviting player to BG + // Invited players counters + uint m_InvitedAlliance; + uint m_InvitedHorde; + + // Raid Group + Group[] m_BgRaids = new Group[SharedConst.BGTeamsCount]; // 0 - Team.Alliance, 1 - Team.Horde + + // Players count by team + uint[] m_PlayersCount = new uint[SharedConst.BGTeamsCount]; + + // Arena team ids by team + uint[] m_ArenaTeamIds = new uint[SharedConst.BGTeamsCount]; + + int[] m_ArenaTeamRatingChanges = new int[SharedConst.BGTeamsCount]; + uint[] m_ArenaTeamMMR = new uint[SharedConst.BGTeamsCount]; + + // Limits + uint m_LevelMin; + uint m_LevelMax; + uint m_MaxPlayersPerTeam; + uint m_MaxPlayers; + uint m_MinPlayersPerTeam; + uint m_MinPlayers; + + // Start location + uint m_MapId; + BattlegroundMap m_Map; + Position[] StartPosition = new Position[SharedConst.BGTeamsCount]; + float m_StartMaxDist; + uint ScriptId; + #endregion + } + + class BattlegroundChatBuilder : MessageBuilder + { + public BattlegroundChatBuilder(ChatMsg msgtype, CypherStrings textId, Player source, params object[] args) + { + _msgtype = msgtype; + _textId = textId; + _source = source; + _args = args; + } + + public override ServerPacket Invoke(LocaleConstant loc_idx = LocaleConstant.enUS) + { + string text = Global.ObjectMgr.GetCypherString(_textId, loc_idx); + string str = string.Format(text, _args); + var packet = new ChatPkt(); + packet.Initialize(_msgtype, Language.Universal, _source, _source, str); + return packet; + } + + ChatMsg _msgtype; + CypherStrings _textId; + Player _source; + object[] _args; + } + + class Battleground2ChatBuilder : MessageBuilder + { + public Battleground2ChatBuilder(ChatMsg msgtype, CypherStrings textId, Player source, CypherStrings arg1, CypherStrings arg2) + { + _msgtype = msgtype; + _textId = textId; + _source = source; + _arg1 = arg1; + _arg2 = arg2; + } + + public override ServerPacket Invoke(LocaleConstant loc_idx = LocaleConstant.enUS) + { + string text = Global.ObjectMgr.GetCypherString(_textId, loc_idx); + string arg1str = _arg1 != 0 ? Global.ObjectMgr.GetCypherString(_arg1, loc_idx) : ""; + string arg2str = _arg2 != 0 ? Global.ObjectMgr.GetCypherString(_arg2, loc_idx) : ""; + + string str = string.Format(text, arg1str, arg2str); + var packet = new ChatPkt(); + packet.Initialize(_msgtype, Language.Universal, _source, _source, str); + return packet; + } + + ChatMsg _msgtype; + CypherStrings _textId; + Player _source; + CypherStrings _arg1; + CypherStrings _arg2; + } + + public class BattlegroundPlayer + { + public long OfflineRemoveTime; // for tracking and removing offline players from queue after 5 Time.Minutes + public Team Team; // Player's team + public int ActiveSpec; + } +} diff --git a/Game/BattleGrounds/BattleGroundManager.cs b/Game/BattleGrounds/BattleGroundManager.cs new file mode 100644 index 000000000..f94484aef --- /dev/null +++ b/Game/BattleGrounds/BattleGroundManager.cs @@ -0,0 +1,917 @@ +/* + * Copyright (C) 2012-2017 CypherCore + * + * 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, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +using Framework.Constants; +using Framework.Database; +using Game.Arenas; +using Game.DataStorage; +using Game.Entities; +using Game.Network.Packets; +using System; +using System.Collections.Generic; +using System.Linq; + +namespace Game.BattleGrounds +{ + public class BattlegroundManager : Singleton + { + BattlegroundManager() + { + m_NextRatedArenaUpdate = WorldConfig.GetUIntValue(WorldCfg.ArenaRatedUpdateTimer); + + for (var i = 0; i < (int)BattlegroundQueueTypeId.Max; ++i) + m_BattlegroundQueues[i] = new BattlegroundQueue(); + } + + public void DeleteAllBattlegrounds() + { + foreach (var data in bgDataStore.Values) + { + data.m_Battlegrounds.Clear(); + data.BGFreeSlotQueue.Clear(); + } + + bgDataStore.Clear(); + } + + public void Update(uint diff) + { + m_UpdateTimer += diff; + if (m_UpdateTimer > 1000) + { + foreach (var data in bgDataStore.Values) + { + var bgs = data.m_Battlegrounds; + + // first one is template and should not be deleted + foreach (var pair in bgs.ToList()) + { + Battleground bg = pair.Value; + bg.Update(m_UpdateTimer); + + if (bg.ToBeDeleted()) + { + bgs.Remove(pair.Key); + var clients = data.m_ClientBattlegroundIds[(int)bg.GetBracketId()]; + if (!clients.Empty()) + clients.Remove(bg.GetClientInstanceID()); + + bg.Dispose(); + } + } + } + + m_UpdateTimer = 0; + } + // update events timer + for (var qtype = BattlegroundQueueTypeId.None; qtype < BattlegroundQueueTypeId.Max; ++qtype) + m_BattlegroundQueues[(int)qtype].UpdateEvents(diff); + + // update scheduled queues + if (!m_QueueUpdateScheduler.Empty()) + { + List scheduled = new List(); + Extensions.Swap(ref scheduled, ref m_QueueUpdateScheduler); + + for (byte i = 0; i < scheduled.Count; i++) + { + uint arenaMMRating = (uint)(scheduled[i] >> 32); + byte arenaType = (byte)(scheduled[i] >> 24 & 255); + BattlegroundQueueTypeId bgQueueTypeId = (BattlegroundQueueTypeId)(scheduled[i] >> 16 & 255); + BattlegroundTypeId bgTypeId = (BattlegroundTypeId)((scheduled[i] >> 8) & 255); + BattlegroundBracketId bracket_id = (BattlegroundBracketId)(scheduled[i] & 255); + m_BattlegroundQueues[(int)bgQueueTypeId].BattlegroundQueueUpdate(diff, bgTypeId, bracket_id, arenaType, arenaMMRating > 0, arenaMMRating); + } + } + + // if rating difference counts, maybe force-update queues + if (WorldConfig.GetIntValue(WorldCfg.ArenaMaxRatingDifference) != 0 && WorldConfig.GetIntValue(WorldCfg.ArenaRatedUpdateTimer) != 0) + { + // it's time to force update + if (m_NextRatedArenaUpdate < diff) + { + // forced update for rated arenas (scan all, but skipped non rated) + Log.outDebug(LogFilter.Arena, "BattlegroundMgr: UPDATING ARENA QUEUES"); + for (var qtype = BattlegroundQueueTypeId.Arena2v2; qtype <= BattlegroundQueueTypeId.Arena5v5; ++qtype) + { + for (int bracket = (int)BattlegroundBracketId.First; bracket < (int)BattlegroundBracketId.Max; ++bracket) + { + m_BattlegroundQueues[(int)qtype].BattlegroundQueueUpdate(diff, + BattlegroundTypeId.AA, (BattlegroundBracketId)bracket, + (byte)BGArenaType(qtype), true, 0); + } + } + + m_NextRatedArenaUpdate = WorldConfig.GetUIntValue(WorldCfg.ArenaRatedUpdateTimer); + } + else + m_NextRatedArenaUpdate -= diff; + } + } + + void BuildBattlegroundStatusHeader(ref BattlefieldStatusHeader header, Battleground bg, Player player, uint ticketId, uint joinTime, ArenaTypes arenaType) + { + header.Ticket.RequesterGuid = player.GetGUID(); + header.Ticket.Id = ticketId; + header.Ticket.Type = bg.isArena() ? (int)arenaType : 1; + header.Ticket.Time = joinTime; + header.QueueID = bg.GetQueueId(); + header.RangeMin = (byte)bg.GetMinLevel(); + header.RangeMax = (byte)bg.GetMaxLevel(); + header.TeamSize = (byte)(bg.isArena() ? arenaType : 0); + header.InstanceID = bg.GetClientInstanceID(); + header.RegisteredMatch = bg.isRated(); + header.TournamentRules = false; + } + + public void BuildBattlegroundStatusNone(out BattlefieldStatusNone battlefieldStatus, Player player, uint ticketId, uint joinTime, ArenaTypes arenaType) + { + battlefieldStatus = new BattlefieldStatusNone(); + battlefieldStatus.Ticket.RequesterGuid = player.GetGUID(); + battlefieldStatus.Ticket.Id = ticketId; + battlefieldStatus.Ticket.Type = (int)arenaType; + battlefieldStatus.Ticket.Time = joinTime; + } + + public void BuildBattlegroundStatusNeedConfirmation(out BattlefieldStatusNeedConfirmation battlefieldStatus, Battleground bg, Player player, uint ticketId, uint joinTime, uint timeout, ArenaTypes arenaType) + { + battlefieldStatus = new BattlefieldStatusNeedConfirmation(); + BuildBattlegroundStatusHeader(ref battlefieldStatus.Hdr, bg, player, ticketId, joinTime, arenaType); + battlefieldStatus.Mapid = bg.GetMapId(); + battlefieldStatus.Timeout = timeout; + battlefieldStatus.Role = 0; + } + + public void BuildBattlegroundStatusActive(out BattlefieldStatusActive battlefieldStatus, Battleground bg, Player player, uint ticketId, uint joinTime, ArenaTypes arenaType) + { + battlefieldStatus = new BattlefieldStatusActive(); + BuildBattlegroundStatusHeader(ref battlefieldStatus.Hdr, bg, player, ticketId, joinTime, arenaType); + battlefieldStatus.ShutdownTimer = bg.GetRemainingTime(); + battlefieldStatus.ArenaFaction = (byte)(player.GetBGTeam() == Team.Horde ? TeamId.Horde : TeamId.Alliance); + battlefieldStatus.LeftEarly = false; + battlefieldStatus.StartTimer = bg.GetElapsedTime(); + battlefieldStatus.Mapid = bg.GetMapId(); + } + + public void BuildBattlegroundStatusQueued(out BattlefieldStatusQueued battlefieldStatus, Battleground bg, Player player, uint ticketId, uint joinTime, uint avgWaitTime, ArenaTypes arenaType, bool asGroup) + { + battlefieldStatus = new BattlefieldStatusQueued(); + BuildBattlegroundStatusHeader(ref battlefieldStatus.Hdr, bg, player, ticketId, joinTime, arenaType); + battlefieldStatus.AverageWaitTime = avgWaitTime; + battlefieldStatus.AsGroup = asGroup; + battlefieldStatus.SuspendedQueue = false; + battlefieldStatus.EligibleForMatchmaking = true; + battlefieldStatus.WaitTime = Time.GetMSTimeDiffToNow(joinTime); + } + + public void BuildBattlegroundStatusFailed(out BattlefieldStatusFailed battlefieldStatus, Battleground bg, Player pPlayer, uint ticketId, ArenaTypes arenaType, GroupJoinBattlegroundResult result, ObjectGuid errorGuid = default(ObjectGuid)) + { + battlefieldStatus = new BattlefieldStatusFailed(); + battlefieldStatus.Ticket.RequesterGuid = pPlayer.GetGUID(); + battlefieldStatus.Ticket.Id = ticketId; + battlefieldStatus.Ticket.Type = (int)arenaType; + battlefieldStatus.Ticket.Time = pPlayer.GetBattlegroundQueueJoinTime(BGQueueTypeId(bg.GetTypeID(), arenaType)); + battlefieldStatus.QueueID = bg.GetQueueId(); + battlefieldStatus.Reason = (int)result; + if (!errorGuid.IsEmpty() && (result == GroupJoinBattlegroundResult.NotInBattleground || result == GroupJoinBattlegroundResult.JoinTimedOut)) + battlefieldStatus.ClientID = errorGuid; + } + + public Battleground GetBattleground(uint instanceId, BattlegroundTypeId bgTypeId) + { + if (instanceId == 0) + return null; + + if (bgTypeId != BattlegroundTypeId.None) + { + var data = bgDataStore.LookupByKey(bgTypeId); + return data.m_Battlegrounds.LookupByKey(instanceId); + } + + foreach (var it in bgDataStore) + { + var bgs = it.Value.m_Battlegrounds; + var bg = bgs.LookupByKey(instanceId); + if (bg) + return bg; + } + + return null; + } + + public Battleground GetBattlegroundTemplate(BattlegroundTypeId bgTypeId) + { + if (bgDataStore.ContainsKey(bgTypeId)) + return bgDataStore[bgTypeId].Template; + + return null; + } + + uint CreateClientVisibleInstanceId(BattlegroundTypeId bgTypeId, BattlegroundBracketId bracket_id) + { + if (IsArenaType(bgTypeId)) + return 0; //arenas don't have client-instanceids + + // we create here an instanceid, which is just for + // displaying this to the client and without any other use.. + // the client-instanceIds are unique for each Battleground-type + // the instance-id just needs to be as low as possible, beginning with 1 + // the following works, because std.set is default ordered with "<" + // the optimalization would be to use as bitmask std.vector - but that would only make code unreadable + + var clientIds = bgDataStore[bgTypeId].m_ClientBattlegroundIds[(int)bracket_id]; + uint lastId = 0; + foreach (var id in clientIds) + { + if (++lastId != id) //if there is a gap between the ids, we will break.. + break; + lastId = id; + } + + clientIds.Add(++lastId); + return lastId; + } + + // create a new Battleground that will really be used to play + public Battleground CreateNewBattleground(BattlegroundTypeId originalBgTypeId, PvpDifficultyRecord bracketEntry, ArenaTypes arenaType, bool isRated) + { + BattlegroundTypeId bgTypeId = GetRandomBG(originalBgTypeId); + + // get the template BG + Battleground bg_template = GetBattlegroundTemplate(bgTypeId); + if (bg_template == null) + { + Log.outError(LogFilter.Battleground, "Battleground: CreateNewBattleground - bg template not found for {0}", bgTypeId); + return null; + } + + if (bgTypeId == BattlegroundTypeId.RB || bgTypeId == BattlegroundTypeId.AA) + return null; + + // create a copy of the BG template + Battleground bg = bg_template.GetCopy(); + + bool isRandom = bgTypeId != originalBgTypeId && !bg.isArena(); + + bg.SetBracket(bracketEntry); + bg.SetInstanceID(Global.MapMgr.GenerateInstanceId()); + bg.SetClientInstanceID(CreateClientVisibleInstanceId(isRandom ? BattlegroundTypeId.RB : bgTypeId, bracketEntry.GetBracketId())); + bg.Reset(); // reset the new bg (set status to status_wait_queue from status_none) + bg.SetStatus(BattlegroundStatus.WaitJoin); // start the joining of the bg + bg.SetArenaType(arenaType); + bg.SetTypeID(originalBgTypeId); + bg.SetRandomTypeID(bgTypeId); + bg.SetRated(isRated); + bg.SetRandom(isRandom); + bg.SetQueueId((ulong)bgTypeId | 0x1F10000000000000); + + // Set up correct min/max player counts for scoreboards + if (bg.isArena()) + { + uint maxPlayersPerTeam = 0; + switch (arenaType) + { + case ArenaTypes.Team2v2: + maxPlayersPerTeam = 2; + break; + case ArenaTypes.Team3v3: + maxPlayersPerTeam = 3; + break; + case ArenaTypes.Team5v5: + maxPlayersPerTeam = 5; + break; + } + + bg.SetMaxPlayersPerTeam(maxPlayersPerTeam); + bg.SetMaxPlayers(maxPlayersPerTeam * 2); + } + + return bg; + } + + // used to create the BG templates + bool CreateBattleground(BattlegroundTemplate bgTemplate) + { + Battleground bg = GetBattlegroundTemplate(bgTemplate.Id); + if (!bg) + { + // Create the BG + switch (bgTemplate.Id) + { + //case BattlegroundTypeId.AV: + // bg = new BattlegroundAV(); + //break; + case BattlegroundTypeId.WS: + bg = new BgWarsongGluch(); + break; + case BattlegroundTypeId.AB: + bg = new BgArathiBasin(); + break; + case BattlegroundTypeId.NA: + bg = new NagrandArena(); + break; + case BattlegroundTypeId.BE: + bg = new BladesEdgeArena(); + break; + case BattlegroundTypeId.EY: + bg = new BgEyeofStorm(); + break; + case BattlegroundTypeId.RL: + bg = new RuinsofLordaeronArena(); + break; + //case BattlegroundTypeId.SA: + //bg = new BattlegroundSA(); + //break; + case BattlegroundTypeId.DS: + bg = new DalaranSewersArena(); + break; + case BattlegroundTypeId.RV: + bg = new RingofValorArena(); + break; + //case BattlegroundTypeId.IC: + //bg = new BattlegroundIC(); + //break; + case BattlegroundTypeId.AA: + bg = new Battleground(); + break; + case BattlegroundTypeId.RB: + bg = new Battleground(); + bg.SetRandom(true); + break; + /* + case BattlegroundTypeId.TP: + bg = new BattlegroundTP(); + break; + case BattlegroundTypeId.BFG: + bg = new BattlegroundBFG(); + break; + */ + default: + return false; + } + bg.SetTypeID(bgTemplate.Id); + } + + bg.SetMapId((uint)bgTemplate.BattlemasterEntry.MapId[0]); + bg.SetName(bgTemplate.BattlemasterEntry.Name[Global.WorldMgr.GetDefaultDbcLocale()]); + bg.SetInstanceID(0); + bg.SetArenaorBGType(bgTemplate.IsArena()); + bg.SetMinPlayersPerTeam(bgTemplate.MinPlayersPerTeam); + bg.SetMaxPlayersPerTeam(bgTemplate.MaxPlayersPerTeam); + bg.SetMinPlayers(bgTemplate.MinPlayersPerTeam * 2); + bg.SetMaxPlayers(bgTemplate.MaxPlayersPerTeam * 2); + bg.SetTeamStartLoc(TeamId.Alliance, bgTemplate.StartLocation[TeamId.Alliance]); + bg.SetTeamStartLoc(TeamId.Horde, bgTemplate.StartLocation[TeamId.Horde]); + bg.SetStartMaxDist(bgTemplate.StartMaxDist); + bg.SetLevelRange(bgTemplate.MinLevel, bgTemplate.MaxLevel); + bg.SetScriptId(bgTemplate.scriptId); + bg.SetQueueId((ulong)bgTemplate.Id | 0x1F10000000000000); + + if (!bgDataStore.ContainsKey(bg.GetTypeID())) + bgDataStore[bg.GetTypeID()] = new BattlegroundData(); + + bgDataStore[bg.GetTypeID()].Template = bg; + + return true; + } + + public void LoadBattlegroundTemplates() + { + uint oldMSTime = Time.GetMSTime(); + + _BattlegroundMapTemplates.Clear(); + _BattlegroundTemplates.Clear(); + + // 0 1 2 3 4 5 6 7 8 9 + SQLResult result = DB.World.Query("SELECT ID, MinPlayersPerTeam, MaxPlayersPerTeam, MinLvl, MaxLvl, AllianceStartLoc, HordeStartLoc, StartMaxDist, Weight, ScriptName FROM Battleground_template"); + if (result.IsEmpty()) + { + Log.outError(LogFilter.ServerLoading, "Loaded 0 Battlegrounds. DB table `Battleground_template` is empty."); + return; + } + + uint count = 0; + do + { + BattlegroundTypeId bgTypeId = (BattlegroundTypeId)result.Read(0); + if (Global.DisableMgr.IsDisabledFor(DisableType.Battleground, (uint)bgTypeId, null)) + continue; + + // can be overwrite by values from DB + BattlemasterListRecord bl = CliDB.BattlemasterListStorage.LookupByKey(bgTypeId); + if (bl == null) + { + Log.outError(LogFilter.Battleground, "Battleground ID {0} not found in BattlemasterList.dbc. Battleground not created.", bgTypeId); + continue; + } + + BattlegroundTemplate bgTemplate = new BattlegroundTemplate(); + bgTemplate.Id = bgTypeId; + bgTemplate.MinPlayersPerTeam = result.Read(1); + bgTemplate.MaxPlayersPerTeam = result.Read(2); + bgTemplate.MinLevel = result.Read(3); + bgTemplate.MaxLevel = result.Read(4); + float dist = result.Read(7); + bgTemplate.StartMaxDist = dist * dist; + bgTemplate.Weight = result.Read(8); + + bgTemplate.scriptId = Global.ObjectMgr.GetScriptId(result.Read(9)); + bgTemplate.BattlemasterEntry = bl; + + if (bgTemplate.MaxPlayersPerTeam == 0 || bgTemplate.MinPlayersPerTeam > bgTemplate.MaxPlayersPerTeam) + { + Log.outError(LogFilter.Sql, "Table `Battleground_template` for Id {0} has bad values for MinPlayersPerTeam ({1}) and MaxPlayersPerTeam({2})", + bgTemplate.Id, bgTemplate.MinPlayersPerTeam, bgTemplate.MaxPlayersPerTeam); + continue; + } + + if (bgTemplate.MinLevel == 0 || bgTemplate.MaxLevel == 0 || bgTemplate.MinLevel > bgTemplate.MaxLevel) + { + Log.outError(LogFilter.Sql, "Table `Battleground_template` for Id {0} has bad values for LevelMin ({1}) and LevelMax({2})", + bgTemplate.Id, bgTemplate.MinLevel, bgTemplate.MaxLevel); + continue; + } + + if (bgTemplate.Id != BattlegroundTypeId.AA && bgTemplate.Id != BattlegroundTypeId.RB) + { + uint startId = result.Read(5); + if (CliDB.WorldSafeLocsStorage.ContainsKey(startId)) + { + WorldSafeLocsRecord start = CliDB.WorldSafeLocsStorage.LookupByKey(startId); + bgTemplate.StartLocation[TeamId.Alliance] = new Position(start.Loc.X, start.Loc.Y, start.Loc.Z, (start.Facing + MathFunctions.PI) / 180); + } + else + { + Log.outError(LogFilter.Sql, "Table `Battleground_template` for Id {0} has a non-existed WorldSafeLocs.dbc id {1} in field `AllianceStartLoc`. BG not created.", bgTemplate.Id, startId); + continue; + } + + startId = result.Read(6); + if (CliDB.WorldSafeLocsStorage.ContainsKey(startId)) + { + WorldSafeLocsRecord start = CliDB.WorldSafeLocsStorage.LookupByKey(startId); + bgTemplate.StartLocation[TeamId.Horde] = new Position(start.Loc.X, start.Loc.Y, start.Loc.Z, result.Read(8)); + } + else + { + Log.outError(LogFilter.Sql, "Table `Battleground_template` for Id {0} has a non-existed WorldSafeLocs.dbc id {1} in field `HordeStartLoc`. BG not created.", bgTemplate.Id, startId); + continue; + } + } + + if (!CreateBattleground(bgTemplate)) + continue; + + _BattlegroundTemplates[bgTypeId] = bgTemplate; + + if (bgTemplate.BattlemasterEntry.MapId[1] == -1) // in this case we have only one mapId + _BattlegroundMapTemplates[(uint)bgTemplate.BattlemasterEntry.MapId[0]] = _BattlegroundTemplates[bgTypeId]; + + ++count; + } + while (result.NextRow()); + + Log.outInfo(LogFilter.ServerLoading, "Loaded {0} Battlegrounds in {1} ms", count, Time.GetMSTimeDiffToNow(oldMSTime)); + } + + public void SendBattlegroundList(Player player, ObjectGuid guid, BattlegroundTypeId bgTypeId) + { + BattlegroundTemplate bgTemplate = GetBattlegroundTemplateByTypeId(bgTypeId); + if (bgTemplate == null) + return; + + BattlefieldList battlefieldList = new BattlefieldList(); + battlefieldList.BattlemasterGuid = guid; + battlefieldList.BattlemasterListID = (int)bgTypeId; + battlefieldList.MinLevel = (byte)bgTemplate.MinLevel; + battlefieldList.MaxLevel = (byte)bgTemplate.MaxLevel; + battlefieldList.PvpAnywhere = guid.IsEmpty(); + battlefieldList.HasRandomWinToday = player.GetRandomWinner(); + player.SendPacket(battlefieldList); + } + + public void SendToBattleground(Player player, uint instanceId, BattlegroundTypeId bgTypeId) + { + Battleground bg = GetBattleground(instanceId, bgTypeId); + if (bg) + { + uint mapid = bg.GetMapId(); + Team team = player.GetBGTeam(); + + Position pos = bg.GetTeamStartPosition(Battleground.GetTeamIndexByTeamId(team)); + Log.outDebug(LogFilter.Battleground, "BattlegroundMgr.SendToBattleground: Sending {0} to map {1}, {2} (bgType {3})", player.GetName(), mapid, pos.ToString(), bgTypeId); + player.TeleportTo(mapid, pos.GetPositionX(), pos.GetPositionY(), pos.GetPositionZ(), pos.GetOrientation()); + } + else + Log.outError(LogFilter.Battleground, "BattlegroundMgr.SendToBattleground: Instance {0} (bgType {1}) not found while trying to teleport player {2}", instanceId, bgTypeId, player.GetName()); + } + + public void SendAreaSpiritHealerQuery(Player player, Battleground bg, ObjectGuid guid) + { + uint time = 30000 - bg.GetLastResurrectTime(); // resurrect every 30 seconds + if (time == 0xFFFFFFFF) + time = 0; + + AreaSpiritHealerTime areaSpiritHealerTime = new AreaSpiritHealerTime(); + areaSpiritHealerTime.HealerGuid = guid; + areaSpiritHealerTime.TimeLeft = time; + + player.SendPacket(areaSpiritHealerTime); + } + + bool IsArenaType(BattlegroundTypeId bgTypeId) + { + return bgTypeId == BattlegroundTypeId.AA || bgTypeId == BattlegroundTypeId.BE || bgTypeId == BattlegroundTypeId.NA + || bgTypeId == BattlegroundTypeId.DS || bgTypeId == BattlegroundTypeId.RV || bgTypeId == BattlegroundTypeId.RL; + } + + public BattlegroundQueueTypeId BGQueueTypeId(BattlegroundTypeId bgTypeId, ArenaTypes arenaType) + { + switch (bgTypeId) + { + case BattlegroundTypeId.AB: + return BattlegroundQueueTypeId.AB; + case BattlegroundTypeId.AV: + return BattlegroundQueueTypeId.AV; + case BattlegroundTypeId.EY: + return BattlegroundQueueTypeId.EY; + case BattlegroundTypeId.IC: + return BattlegroundQueueTypeId.IC; + case BattlegroundTypeId.TP: + return BattlegroundQueueTypeId.TP; + case BattlegroundTypeId.BFG: + return BattlegroundQueueTypeId.BFG; + case BattlegroundTypeId.RB: + return BattlegroundQueueTypeId.RB; + case BattlegroundTypeId.SA: + return BattlegroundQueueTypeId.SA; + case BattlegroundTypeId.WS: + return BattlegroundQueueTypeId.WS; + case BattlegroundTypeId.AA: + case BattlegroundTypeId.BE: + case BattlegroundTypeId.DS: + case BattlegroundTypeId.NA: + case BattlegroundTypeId.RL: + case BattlegroundTypeId.RV: + switch (arenaType) + { + case ArenaTypes.Team2v2: + return BattlegroundQueueTypeId.Arena2v2; + case ArenaTypes.Team3v3: + return BattlegroundQueueTypeId.Arena3v3; + case ArenaTypes.Team5v5: + return BattlegroundQueueTypeId.Arena5v5; + default: + return BattlegroundQueueTypeId.None; + } + default: + return BattlegroundQueueTypeId.None; + } + } + + public BattlegroundTypeId BGTemplateId(BattlegroundQueueTypeId bgQueueTypeId) + { + switch (bgQueueTypeId) + { + case BattlegroundQueueTypeId.WS: + return BattlegroundTypeId.WS; + case BattlegroundQueueTypeId.AB: + return BattlegroundTypeId.AB; + case BattlegroundQueueTypeId.AV: + return BattlegroundTypeId.AV; + case BattlegroundQueueTypeId.EY: + return BattlegroundTypeId.EY; + case BattlegroundQueueTypeId.SA: + return BattlegroundTypeId.SA; + case BattlegroundQueueTypeId.IC: + return BattlegroundTypeId.IC; + case BattlegroundQueueTypeId.TP: + return BattlegroundTypeId.TP; + case BattlegroundQueueTypeId.BFG: + return BattlegroundTypeId.BFG; + case BattlegroundQueueTypeId.RB: + return BattlegroundTypeId.RB; + case BattlegroundQueueTypeId.Arena2v2: + case BattlegroundQueueTypeId.Arena3v3: + case BattlegroundQueueTypeId.Arena5v5: + return BattlegroundTypeId.AA; + default: + return 0; // used for unknown template (it existed and do nothing) + } + } + + public ArenaTypes BGArenaType(BattlegroundQueueTypeId bgQueueTypeId) + { + switch (bgQueueTypeId) + { + case BattlegroundQueueTypeId.Arena2v2: + return ArenaTypes.Team2v2; + case BattlegroundQueueTypeId.Arena3v3: + return ArenaTypes.Team3v3; + case BattlegroundQueueTypeId.Arena5v5: + return ArenaTypes.Team5v5; + default: + return 0; + } + } + + public void ToggleTesting() + { + m_Testing = !m_Testing; + Global.WorldMgr.SendWorldText(m_Testing ? CypherStrings.DebugBgOn : CypherStrings.DebugBgOff); + } + + public void ToggleArenaTesting() + { + m_ArenaTesting = !m_ArenaTesting; + Global.WorldMgr.SendWorldText(m_ArenaTesting ? CypherStrings.DebugArenaOn : CypherStrings.DebugArenaOff); + } + + public void SetHolidayWeekends(uint mask) + { + // The current code supports battlegrounds up to BattlegroundTypeId(31) + for (var bgtype = 1; bgtype < (int)BattlegroundTypeId.Max && bgtype < 32; ++bgtype) + { + Battleground bg = GetBattlegroundTemplate((BattlegroundTypeId)bgtype); + if (bg) + bg.SetHoliday(Convert.ToBoolean(mask & (1 << bgtype))); + } + } + + public void ScheduleQueueUpdate(uint arenaMatchmakerRating, ArenaTypes arenaType, BattlegroundQueueTypeId bgQueueTypeId, BattlegroundTypeId bgTypeId, BattlegroundBracketId bracket_id) + { + //we will use only 1 number created of bgTypeId and bracket_id + ulong scheduleId = ((ulong)arenaMatchmakerRating << 32) | ((uint)arenaType << 24) | ((uint)bgQueueTypeId << 16) | ((uint)bgTypeId << 8) | (uint)bracket_id; + if (!m_QueueUpdateScheduler.Contains(scheduleId)) + m_QueueUpdateScheduler.Add(scheduleId); + } + + public uint GetMaxRatingDifference() + { + // this is for stupid people who can't use brain and set max rating difference to 0 + uint diff = WorldConfig.GetUIntValue(WorldCfg.ArenaMaxRatingDifference); + if (diff == 0) + diff = 5000; + return diff; + } + + public uint GetRatingDiscardTimer() + { + return WorldConfig.GetUIntValue(WorldCfg.ArenaRatingDiscardTimer); + } + + public uint GetPrematureFinishTime() + { + return WorldConfig.GetUIntValue(WorldCfg.BattlegroundPrematureFinishTimer); + } + + public void LoadBattleMastersEntry() + { + uint oldMSTime = Time.GetMSTime(); + + mBattleMastersMap.Clear(); // need for reload case + + SQLResult result = DB.World.Query("SELECT entry, bg_template FROM battlemaster_entry"); + if (result.IsEmpty()) + { + Log.outInfo(LogFilter.ServerLoading, "Loaded 0 battlemaster entries. DB table `battlemaster_entry` is empty!"); + return; + } + + uint count = 0; + + do + { + uint entry = result.Read(0); + CreatureTemplate cInfo = Global.ObjectMgr.GetCreatureTemplate(entry); + if (cInfo != null) + { + if (!cInfo.Npcflag.HasAnyFlag(NPCFlags.BattleMaster)) + Log.outError(LogFilter.Sql, "Creature (Entry: {0}) listed in `battlemaster_entry` is not a battlemaster.", entry); + } + else + { + Log.outError(LogFilter.Sql, "Creature (Entry: {0}) listed in `battlemaster_entry` does not exist.", entry); + continue; + } + + uint bgTypeId = result.Read(1); + if (!CliDB.BattlemasterListStorage.ContainsKey(bgTypeId)) + { + Log.outError(LogFilter.Sql, "Table `battlemaster_entry` contain entry {0} for not existed Battleground type {1}, ignored.", entry, bgTypeId); + continue; + } + + ++count; + mBattleMastersMap[entry] = (BattlegroundTypeId)bgTypeId; + } + while (result.NextRow()); + + CheckBattleMasters(); + + Log.outInfo(LogFilter.ServerLoading, "Loaded {0} battlemaster entries in {1} ms", count, Time.GetMSTimeDiffToNow(oldMSTime)); + } + + void CheckBattleMasters() + { + var templates = Global.ObjectMgr.GetCreatureTemplates(); + foreach (var creature in templates) + { + if (creature.Value.Npcflag.HasAnyFlag(NPCFlags.BattleMaster) && !mBattleMastersMap.ContainsKey(creature.Value.Entry)) + { + Log.outError(LogFilter.Sql, "CreatureTemplate (Entry: {0}) has UNIT_NPC_FLAG_BATTLEMASTER but no data in `battlemaster_entry` table. Removing flag!", creature.Value.Entry); + templates[creature.Key].Npcflag &= ~NPCFlags.BattleMaster; + } + } + } + + HolidayIds BGTypeToWeekendHolidayId(BattlegroundTypeId bgTypeId) + { + switch (bgTypeId) + { + case BattlegroundTypeId.AV: + return HolidayIds.CallToArmsAv; + case BattlegroundTypeId.EY: + return HolidayIds.CallToArmsEy; + case BattlegroundTypeId.WS: + return HolidayIds.CallToArmsWs; + case BattlegroundTypeId.SA: + return HolidayIds.CallToArmsSa; + case BattlegroundTypeId.AB: + return HolidayIds.CallToArmsAb; + case BattlegroundTypeId.IC: + return HolidayIds.CallToArmsIc; + case BattlegroundTypeId.TP: + return HolidayIds.CallToArmsTp; + case BattlegroundTypeId.BFG: + return HolidayIds.CallToArmsBfg; + default: + return HolidayIds.None; + } + } + + public BattlegroundTypeId WeekendHolidayIdToBGType(HolidayIds holiday) + { + switch (holiday) + { + case HolidayIds.CallToArmsAv: + return BattlegroundTypeId.AV; + case HolidayIds.CallToArmsEy: + return BattlegroundTypeId.EY; + case HolidayIds.CallToArmsWs: + return BattlegroundTypeId.WS; + case HolidayIds.CallToArmsSa: + return BattlegroundTypeId.SA; + case HolidayIds.CallToArmsAb: + return BattlegroundTypeId.AB; + case HolidayIds.CallToArmsIc: + return BattlegroundTypeId.IC; + case HolidayIds.CallToArmsTp: + return BattlegroundTypeId.TP; + case HolidayIds.CallToArmsBfg: + return BattlegroundTypeId.BFG; + default: + return BattlegroundTypeId.None; + } + } + + public bool IsBGWeekend(BattlegroundTypeId bgTypeId) + { + return Global.GameEventMgr.IsHolidayActive(BGTypeToWeekendHolidayId(bgTypeId)); + } + + BattlegroundTypeId GetRandomBG(BattlegroundTypeId bgTypeId) + { + BattlegroundTemplate bgTemplate = GetBattlegroundTemplateByTypeId(bgTypeId); + if (bgTemplate != null) + { + Dictionary selectionWeights = new Dictionary(); + + foreach (var mapId in bgTemplate.BattlemasterEntry.MapId) + { + if (mapId == -1) + break; + + BattlegroundTemplate bg = GetBattlegroundTemplateByMapId((uint)mapId); + if (bg != null) + { + selectionWeights.Add(bg.Id, bg.Weight); + } + } + + return selectionWeights.SelectRandomElementByWeight(i => i.Value).Key; + } + + return BattlegroundTypeId.None; + } + + public List GetBGFreeSlotQueueStore(BattlegroundTypeId bgTypeId) + { + return bgDataStore[bgTypeId].BGFreeSlotQueue; + } + + public void AddToBGFreeSlotQueue(BattlegroundTypeId bgTypeId, Battleground bg) + { + bgDataStore[bgTypeId].BGFreeSlotQueue.Insert(0, bg); + } + + public void RemoveFromBGFreeSlotQueue(BattlegroundTypeId bgTypeId, uint instanceId) + { + var queues = bgDataStore[bgTypeId].BGFreeSlotQueue; + foreach (var bg in queues.ToList()) + { + if (bg.GetInstanceID() == instanceId) + { + queues.Remove(bg); + return; + } + } + } + + public void AddBattleground(Battleground bg) + { + if (bg) + bgDataStore[bg.GetTypeID()].m_Battlegrounds[bg.GetInstanceID()] = bg; + } + + public void RemoveBattleground(BattlegroundTypeId bgTypeId, uint instanceId) + { + bgDataStore[bgTypeId].m_Battlegrounds.Remove(instanceId); + } + + public BattlegroundQueue GetBattlegroundQueue(BattlegroundQueueTypeId bgQueueTypeId) { return m_BattlegroundQueues[(int)bgQueueTypeId]; } + + public bool isArenaTesting() { return m_ArenaTesting; } + public bool isTesting() { return m_Testing; } + + public BattlegroundTypeId GetBattleMasterBG(uint entry) + { + return mBattleMastersMap.LookupByKey(entry); + } + + BattlegroundTemplate GetBattlegroundTemplateByTypeId(BattlegroundTypeId id) + { + return _BattlegroundTemplates.LookupByKey(id); + } + + BattlegroundTemplate GetBattlegroundTemplateByMapId(uint mapId) + { + return _BattlegroundMapTemplates.LookupByKey(mapId); + } + + Dictionary bgDataStore = new Dictionary(); + BattlegroundQueue[] m_BattlegroundQueues = new BattlegroundQueue[(int)BattlegroundQueueTypeId.Max]; + Dictionary mBattleMastersMap = new Dictionary(); + Dictionary _BattlegroundTemplates = new Dictionary(); + Dictionary _BattlegroundMapTemplates = new Dictionary(); + List m_QueueUpdateScheduler = new List(); + uint m_NextRatedArenaUpdate; + uint m_UpdateTimer; + bool m_ArenaTesting; + bool m_Testing; + } + + public class BattlegroundData + { + public BattlegroundData() + { + for (var i = 0; i < (int)BattlegroundBracketId.Max; ++i) + m_ClientBattlegroundIds[i] = new List(); + } + + public Dictionary m_Battlegrounds = new Dictionary(); + public List[] m_ClientBattlegroundIds = new List[(int)BattlegroundBracketId.Max]; + public List BGFreeSlotQueue = new List(); + public Battleground Template; + } + + class BattlegroundTemplate + { + public BattlegroundTypeId Id; + public uint MinPlayersPerTeam; + public uint MaxPlayersPerTeam; + public uint MinLevel; + public uint MaxLevel; + public Position[] StartLocation = new Position[SharedConst.BGTeamsCount]; + public float StartMaxDist; + public byte Weight; + public uint scriptId; + public BattlemasterListRecord BattlemasterEntry; + + public bool IsArena() { return BattlemasterEntry.InstanceType == (uint)MapTypes.Arena; } + } +} diff --git a/Game/BattleGrounds/BattleGroundQueue.cs b/Game/BattleGrounds/BattleGroundQueue.cs new file mode 100644 index 000000000..68e9f1cec --- /dev/null +++ b/Game/BattleGrounds/BattleGroundQueue.cs @@ -0,0 +1,1192 @@ +/* + * Copyright (C) 2012-2017 CypherCore + * + * 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, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +using Framework.Constants; +using Framework.Dynamic; +using Game.Arenas; +using Game.DataStorage; +using Game.Entities; +using Game.Groups; +using Game.Network.Packets; +using System; +using System.Collections.Generic; +using System.Linq; + +namespace Game.BattleGrounds +{ + public class BattlegroundQueue + { + public BattlegroundQueue() + { + for (var i = 0; i < (int)BattlegroundBracketId.Max; ++i) + { + m_QueuedGroups[i] = new List[BattlegroundConst.BgQueueTypesCount]; + + for (var c = 0; c < BattlegroundConst.BgQueueTypesCount; ++c) + m_QueuedGroups[i][c] = new List(); + } + + for (var i = 0; i < SharedConst.BGTeamsCount; ++i) + { + m_WaitTimes[i] = new uint[(int)BattlegroundBracketId.Max][]; + for (var c = 0; c < (int)BattlegroundBracketId.Max; ++c) + m_WaitTimes[i][c] = new uint[SharedConst.CountOfPlayersToAverageWaitTime]; + + m_WaitTimeLastPlayer[i] = new uint[(int)BattlegroundBracketId.Max]; + m_SumOfWaitTimes[i] = new uint[(int)BattlegroundBracketId.Max]; + } + + m_SelectionPools[0] = new SelectionPool(); + m_SelectionPools[1] = new SelectionPool(); + } + + // add group or player (grp == null) to bg queue with the given leader and bg specifications + public GroupQueueInfo AddGroup(Player leader, Group grp, BattlegroundTypeId BgTypeId, PvpDifficultyRecord bracketEntry, ArenaTypes ArenaType, bool isRated, bool isPremade, uint ArenaRating, uint MatchmakerRating, uint arenateamid = 0) + { + BattlegroundBracketId bracketId = bracketEntry.GetBracketId(); + + // create new ginfo + GroupQueueInfo ginfo = new GroupQueueInfo(); + ginfo.BgTypeId = BgTypeId; + ginfo.ArenaType = ArenaType; + ginfo.ArenaTeamId = arenateamid; + ginfo.IsRated = isRated; + ginfo.IsInvitedToBGInstanceGUID = 0; + ginfo.JoinTime = Time.GetMSTime(); + ginfo.RemoveInviteTime = 0; + ginfo.Team = leader.GetTeam(); + ginfo.ArenaTeamRating = ArenaRating; + ginfo.ArenaMatchmakerRating = MatchmakerRating; + ginfo.OpponentsTeamRating = 0; + ginfo.OpponentsMatchmakerRating = 0; + + ginfo.Players.Clear(); + + //compute index (if group is premade or joined a rated match) to queues + uint index = 0; + if (!isRated && !isPremade) + index += SharedConst.BGTeamsCount; + if (ginfo.Team == Team.Horde) + index++; + Log.outDebug(LogFilter.Battleground, "Adding Group to BattlegroundQueue bgTypeId : {0}, bracket_id : {1}, index : {2}", BgTypeId, bracketId, index); + + uint lastOnlineTime = Time.GetMSTime(); + + //announce world (this don't need mutex) + if (isRated && WorldConfig.GetBoolValue(WorldCfg.ArenaQueueAnnouncerEnable)) + { + ArenaTeam team = Global.ArenaTeamMgr.GetArenaTeamById(arenateamid); + if (team != null) + Global.WorldMgr.SendWorldText( CypherStrings.ArenaQueueAnnounceWorldJoin, team.GetName(), ginfo.ArenaType, ginfo.ArenaType, ginfo.ArenaTeamRating); + } + + //add players from group to ginfo + if (grp) + { + for (GroupReference refe = grp.GetFirstMember(); refe != null; refe = refe.next()) + { + Player member = refe.GetSource(); + if (!member) + continue; // this should never happen + + PlayerQueueInfo pl_info = new PlayerQueueInfo(); + pl_info.LastOnlineTime = lastOnlineTime; + pl_info.GroupInfo = ginfo; + + m_QueuedPlayers[member.GetGUID()] = pl_info; + // add the pinfo to ginfo's list + ginfo.Players[member.GetGUID()] = pl_info; + } + } + else + { + PlayerQueueInfo pl_info = new PlayerQueueInfo(); + pl_info.LastOnlineTime = lastOnlineTime; + pl_info.GroupInfo = ginfo; + + m_QueuedPlayers[leader.GetGUID()] = pl_info; + ginfo.Players[leader.GetGUID()] = pl_info; + } + + //add GroupInfo to m_QueuedGroups + { + //ACE_Guard guard(m_Lock); + m_QueuedGroups[(int)bracketId][index].Add(ginfo); + + //announce to world, this code needs mutex + if (!isRated && !isPremade && WorldConfig.GetBoolValue(WorldCfg.BattlegroundQueueAnnouncerEnable)) + { + Battleground bg = Global.BattlegroundMgr.GetBattlegroundTemplate(ginfo.BgTypeId); + if (bg) + { + string bgName = bg.GetName(); + uint MinPlayers = bg.GetMinPlayersPerTeam(); + uint qHorde = 0; + uint qAlliance = 0; + uint q_min_level = bracketEntry.MinLevel; + uint q_max_level = bracketEntry.MaxLevel; + + foreach (var groupQueueInfo in m_QueuedGroups[(int)bracketId][BattlegroundConst.BgQueueNormalAlliance]) + if (groupQueueInfo.IsInvitedToBGInstanceGUID == 0) + qAlliance += (uint)groupQueueInfo.Players.Count; + foreach (var groupQueueInfo in m_QueuedGroups[(int)bracketId][BattlegroundConst.BgQueueNormalHorde]) + if (groupQueueInfo.IsInvitedToBGInstanceGUID == 0) + qHorde += (uint)groupQueueInfo.Players.Count; + + // Show queue status to player only (when joining queue) + if (WorldConfig.GetBoolValue(WorldCfg.BattlegroundQueueAnnouncerPlayeronly)) + { + leader.SendSysMessage(CypherStrings.BgQueueAnnounceSelf, bgName, q_min_level, q_max_level, + qAlliance, (MinPlayers > qAlliance) ? MinPlayers - qAlliance : 0, qHorde, (MinPlayers > qHorde) ? MinPlayers - qHorde : 0); + } + // System message + else + { + Global.WorldMgr.SendWorldText(CypherStrings.BgQueueAnnounceWorld, bgName, q_min_level, q_max_level, + qAlliance, (MinPlayers > qAlliance) ? MinPlayers - qAlliance : 0, qHorde, (MinPlayers > qHorde) ? MinPlayers - qHorde : 0); + } + } + } + //release mutex + } + + return ginfo; + } + + void PlayerInvitedToBGUpdateAverageWaitTime(GroupQueueInfo ginfo, BattlegroundBracketId bracket_id) + { + uint timeInQueue = Time.GetMSTimeDiff(ginfo.JoinTime, Time.GetMSTime()); + uint team_index = TeamId.Alliance; //default set to TeamIndex.Alliance - or non rated arenas! + if (ginfo.ArenaType == 0) + { + if (ginfo.Team == Team.Horde) + team_index = TeamId.Horde; + } + else + { + if (ginfo.IsRated) + team_index = TeamId.Horde; //for rated arenas use TeamIndex.Horde + } + + //store pointer to arrayindex of player that was added first + uint lastPlayerAddedPointer = m_WaitTimeLastPlayer[team_index][(int)bracket_id]; + //remove his time from sum + m_SumOfWaitTimes[team_index][(int)bracket_id] -= m_WaitTimes[team_index][(int)bracket_id][lastPlayerAddedPointer]; + //set average time to new + m_WaitTimes[team_index][(int)bracket_id][lastPlayerAddedPointer] = timeInQueue; + //add new time to sum + m_SumOfWaitTimes[team_index][(int)bracket_id] += timeInQueue; + //set index of last player added to next one + lastPlayerAddedPointer++; + lastPlayerAddedPointer %= SharedConst.CountOfPlayersToAverageWaitTime; + } + + public uint GetAverageQueueWaitTime(GroupQueueInfo ginfo, BattlegroundBracketId bracket_id) + { + uint team_index = TeamId.Alliance; //default set to TeamIndex.Alliance - or non rated arenas! + if (ginfo.ArenaType == 0) + { + if (ginfo.Team == Team.Horde) + team_index = TeamId.Horde; + } + else + { + if (ginfo.IsRated) + team_index = TeamId.Horde; //for rated arenas use TeamIndex.Horde + } + //check if there is enought values(we always add values > 0) + if (m_WaitTimes[team_index][(int)bracket_id][SharedConst.CountOfPlayersToAverageWaitTime - 1] != 0) + return (m_SumOfWaitTimes[team_index][(int)bracket_id] / SharedConst.CountOfPlayersToAverageWaitTime); + else + //if there aren't enough values return 0 - not available + return 0; + } + + //remove player from queue and from group info, if group info is empty then remove it too + public void RemovePlayer(ObjectGuid guid, bool decreaseInvitedCount) + { + int bracket_id = -1; // signed for proper for-loop finish + + //remove player from map, if he's there + var playerQueueInfo = m_QueuedPlayers.LookupByKey(guid); + if (playerQueueInfo == null) + { + string playerName = "Unknown"; + Player player = Global.ObjAccessor.FindPlayer(guid); + if (player) + playerName = player.GetName(); + Log.outDebug(LogFilter.Battleground, "BattlegroundQueue: couldn't find player {0} ({1})", playerName, guid.ToString()); + return; + } + + GroupQueueInfo group = playerQueueInfo.GroupInfo; + GroupQueueInfo groupQueseInfo = null; + // mostly people with the highest levels are in Battlegrounds, thats why + // we count from MAX_Battleground_QUEUES - 1 to 0 + + uint index = (group.Team == Team.Horde) ? BattlegroundConst.BgQueuePremadeHorde : BattlegroundConst.BgQueuePremadeAlliance; + + for (int bracket_id_tmp = (int)BattlegroundBracketId.Max - 1; bracket_id_tmp >= 0 && bracket_id == -1; --bracket_id_tmp) + { + //we must check premade and normal team's queue - because when players from premade are joining bg, + //they leave groupinfo so we can't use its players size to find out index + for (uint j = index; j < BattlegroundConst.BgQueueTypesCount; j += SharedConst.BGTeamsCount) + { + foreach (var k in m_QueuedGroups[bracket_id_tmp][j]) + { + if (k == group) + { + bracket_id = bracket_id_tmp; + groupQueseInfo = k; + //we must store index to be able to erase iterator + index = j; + break; + } + } + } + } + + //player can't be in queue without group, but just in case + if (bracket_id == -1) + { + Log.outError(LogFilter.Battleground, "BattlegroundQueue: ERROR Cannot find groupinfo for {0}", guid.ToString()); + return; + } + Log.outDebug(LogFilter.Battleground, "BattlegroundQueue: Removing {0}, from bracket_id {1}", guid.ToString(), bracket_id); + + // ALL variables are correctly set + // We can ignore leveling up in queue - it should not cause crash + // remove player from group + // if only one player there, remove group + + // remove player queue info from group queue info + if (group.Players.ContainsKey(guid)) + group.Players.Remove(guid); + + // if invited to bg, and should decrease invited count, then do it + if (decreaseInvitedCount && group.IsInvitedToBGInstanceGUID != 0) + { + Battleground bg = Global.BattlegroundMgr.GetBattleground(group.IsInvitedToBGInstanceGUID, group.BgTypeId); + if (bg) + bg.DecreaseInvitedCount(group.Team); + } + + // remove player queue info + m_QueuedPlayers.Remove(guid); + + // announce to world if arena team left queue for rated match, show only once + if (group.ArenaType != 0 && group.IsRated && group.Players.Empty() && WorldConfig.GetBoolValue(WorldCfg.ArenaQueueAnnouncerEnable)) + { + ArenaTeam team = Global.ArenaTeamMgr.GetArenaTeamById(group.ArenaTeamId); + if (team != null) + Global.WorldMgr.SendWorldText( CypherStrings.ArenaQueueAnnounceWorldExit, team.GetName(), group.ArenaType, group.ArenaType, group.ArenaTeamRating); + } + + // if player leaves queue and he is invited to rated arena match, then he have to lose + if (group.IsInvitedToBGInstanceGUID != 0 && group.IsRated && decreaseInvitedCount) + { + ArenaTeam at = Global.ArenaTeamMgr.GetArenaTeamById(group.ArenaTeamId); + if (at != null) + { + Log.outDebug(LogFilter.Battleground, "UPDATING memberLost's personal arena rating for {0} by opponents rating: {1}", guid.ToString(), group.OpponentsTeamRating); + Player player = Global.ObjAccessor.FindPlayer(guid); + if (player) + at.MemberLost(player, group.OpponentsMatchmakerRating); + else + at.OfflineMemberLost(guid, group.OpponentsMatchmakerRating); + at.SaveToDB(); + } + } + + // remove group queue info if needed + if (group.Players.Empty()) + { + m_QueuedGroups[bracket_id][index].Remove(groupQueseInfo); + return; + } + + // if group wasn't empty, so it wasn't deleted, and player have left a rated + // queue . everyone from the group should leave too + // don't remove recursively if already invited to bg! + if (group.IsInvitedToBGInstanceGUID == 0 && group.IsRated) + { + // remove next player, this is recursive + // first send removal information + Player plr2 = Global.ObjAccessor.FindConnectedPlayer(group.Players.FirstOrDefault().Key); + if (plr2) + { + BattlegroundQueueTypeId bgQueueTypeId = Global.BattlegroundMgr.BGQueueTypeId(group.BgTypeId, group.ArenaType); + uint queueSlot = plr2.GetBattlegroundQueueIndex(bgQueueTypeId); + + plr2.RemoveBattlegroundQueueId(bgQueueTypeId); // must be called this way, because if you move this call to + // queue.removeplayer, it causes bugs + + BattlefieldStatusNone battlefieldStatus; + Global.BattlegroundMgr.BuildBattlegroundStatusNone(out battlefieldStatus, plr2, queueSlot, plr2.GetBattlegroundQueueJoinTime(bgQueueTypeId), group.ArenaType); + plr2.SendPacket(battlefieldStatus); + } + // then actually delete, this may delete the group as well! + RemovePlayer(group.Players.First().Key, decreaseInvitedCount); + } + } + + //returns true when player pl_guid is in queue and is invited to bgInstanceGuid + public bool IsPlayerInvited(ObjectGuid pl_guid, uint bgInstanceGuid, uint removeTime) + { + var queueInfo = m_QueuedPlayers.LookupByKey(pl_guid); + return (queueInfo != null + && queueInfo.GroupInfo.IsInvitedToBGInstanceGUID == bgInstanceGuid + && queueInfo.GroupInfo.RemoveInviteTime == removeTime); + } + + public bool GetPlayerGroupInfoData(ObjectGuid guid, out GroupQueueInfo ginfo) + { + ginfo = null; + var playerQueueInfo = m_QueuedPlayers.LookupByKey(guid); + if (playerQueueInfo == null) + return false; + + ginfo = playerQueueInfo.GroupInfo; + return true; + } + + uint GetPlayersInQueue(uint id) + { + return m_SelectionPools[id].GetPlayerCount(); + } + + bool InviteGroupToBG(GroupQueueInfo ginfo, Battleground bg, Team side) + { + // set side if needed + if (side != 0) + ginfo.Team = side; + + if (ginfo.IsInvitedToBGInstanceGUID == 0) + { + // not yet invited + // set invitation + ginfo.IsInvitedToBGInstanceGUID = bg.GetInstanceID(); + BattlegroundTypeId bgTypeId = bg.GetTypeID(); + BattlegroundQueueTypeId bgQueueTypeId = Global.BattlegroundMgr.BGQueueTypeId(bgTypeId, bg.GetArenaType()); + BattlegroundBracketId bracket_id = bg.GetBracketId(); + + // set ArenaTeamId for rated matches + if (bg.isArena() && bg.isRated()) + bg.SetArenaTeamIdForTeam(ginfo.Team, ginfo.ArenaTeamId); + + ginfo.RemoveInviteTime = Time.GetMSTime() + BattlegroundConst.InviteAcceptWaitTime; + + // loop through the players + foreach (var guid in ginfo.Players.Keys) + { + // get the player + Player player = Global.ObjAccessor.FindPlayer(guid); + // if offline, skip him, this should not happen - player is removed from queue when he logs out + if (!player) + continue; + + // invite the player + PlayerInvitedToBGUpdateAverageWaitTime(ginfo, bracket_id); + + // set invited player counters + bg.IncreaseInvitedCount(ginfo.Team); + + player.SetInviteForBattlegroundQueueType(bgQueueTypeId, ginfo.IsInvitedToBGInstanceGUID); + + // create remind invite events + BGQueueInviteEvent inviteEvent = new BGQueueInviteEvent(player.GetGUID(), ginfo.IsInvitedToBGInstanceGUID, bgTypeId, ginfo.ArenaType, ginfo.RemoveInviteTime); + m_events.AddEvent(inviteEvent, m_events.CalculateTime(BattlegroundConst.InvitationRemindTime)); + // create automatic remove events + BGQueueRemoveEvent removeEvent = new BGQueueRemoveEvent(player.GetGUID(), ginfo.IsInvitedToBGInstanceGUID, bgTypeId, ginfo.ArenaType, bgQueueTypeId, ginfo.RemoveInviteTime); + m_events.AddEvent(removeEvent, m_events.CalculateTime(BattlegroundConst.InviteAcceptWaitTime)); + + uint queueSlot = player.GetBattlegroundQueueIndex(bgQueueTypeId); + + Log.outDebug(LogFilter.Battleground, "Battleground: invited player {0} ({1}) to BG instance {2} queueindex {3} bgtype {4}", + player.GetName(), player.GetGUID().ToString(), bg.GetInstanceID(), queueSlot, bg.GetTypeID()); + + BattlefieldStatusNeedConfirmation battlefieldStatus; + Global.BattlegroundMgr.BuildBattlegroundStatusNeedConfirmation(out battlefieldStatus, bg, player, queueSlot, player.GetBattlegroundQueueJoinTime(bgQueueTypeId), BattlegroundConst.InviteAcceptWaitTime, ginfo.ArenaType); + player.SendPacket(battlefieldStatus); + } + return true; + } + + return false; + } + + /* + This function is inviting players to already running Battlegrounds + Invitation type is based on config file + large groups are disadvantageous, because they will be kicked first if invitation type = 1 + */ + void FillPlayersToBG(Battleground bg, BattlegroundBracketId bracket_id) + { + uint hordeFree = bg.GetFreeSlotsForTeam(Team.Horde); + uint aliFree = bg.GetFreeSlotsForTeam(Team.Alliance); + int aliCount = m_QueuedGroups[(int)bracket_id][BattlegroundConst.BgQueueNormalAlliance].Count; + int hordeCount = m_QueuedGroups[(int)bracket_id][BattlegroundConst.BgQueueNormalHorde].Count; + + // try to get even teams + if (WorldConfig.GetIntValue(WorldCfg.BattlegroundInvitationType) == (int)BattlegroundQueueInvitationType.Even) + { + // check if the teams are even + if (hordeFree == 1 && aliFree == 1) + { + // if we are here, the teams have the same amount of players + // then we have to allow to join the same amount of players + int hordeExtra = hordeCount - aliCount; + int aliExtra = aliCount - hordeCount; + + hordeExtra = Math.Max(hordeExtra, 0); + aliExtra = Math.Max(aliExtra, 0); + + if (aliCount != hordeCount) + { + aliFree -= (uint)aliExtra; + hordeFree -= (uint)hordeExtra; + + aliFree = Math.Max(aliFree, 0); + hordeFree = Math.Max(hordeFree, 0); + } + } + } + + //count of groups in queue - used to stop cycles + int alyIndex = 0; + { + int listIndex = 0; + var info = m_QueuedGroups[(int)bracket_id][BattlegroundConst.BgQueueNormalAlliance].FirstOrDefault(); + for (; alyIndex < aliCount && m_SelectionPools[TeamId.Alliance].AddGroup(info, aliFree); alyIndex++) + info = m_QueuedGroups[(int)bracket_id][BattlegroundConst.BgQueueNormalAlliance][listIndex++]; + } + + //the same thing for horde + int hordeIndex = 0; + { + int listIndex = 0; + var info = m_QueuedGroups[(int)bracket_id][BattlegroundConst.BgQueueNormalHorde].FirstOrDefault(); + for (; hordeIndex < hordeCount && m_SelectionPools[TeamId.Horde].AddGroup(info, hordeFree); hordeIndex++) + info = m_QueuedGroups[(int)bracket_id][BattlegroundConst.BgQueueNormalHorde][listIndex++]; + } + + //if ofc like BG queue invitation is set in config, then we are happy + if (WorldConfig.GetIntValue(WorldCfg.BattlegroundInvitationType) == (int)BattlegroundQueueInvitationType.NoBalance) + return; + /* + if we reached this code, then we have to solve NP - complete problem called Subset sum problem + So one solution is to check all possible invitation subgroups, or we can use these conditions: + 1. Last time when BattlegroundQueue.Update was executed we invited all possible players - so there is only small possibility + that we will invite now whole queue, because only 1 change has been made to queues from the last BattlegroundQueue.Update call + 2. Other thing we should consider is group order in queue + */ + + // At first we need to compare free space in bg and our selection pool + int diffAli = (int)(aliFree - m_SelectionPools[TeamId.Alliance].GetPlayerCount()); + int diffHorde = (int)(hordeFree - m_SelectionPools[TeamId.Horde].GetPlayerCount()); + while (Math.Abs(diffAli - diffHorde) > 1 && (m_SelectionPools[TeamId.Horde].GetPlayerCount() > 0 || m_SelectionPools[TeamId.Alliance].GetPlayerCount() > 0)) + { + //each cycle execution we need to kick at least 1 group + if (diffAli < diffHorde) + { + //kick alliance group, add to pool new group if needed + if (m_SelectionPools[TeamId.Alliance].KickGroup((uint)(diffHorde - diffAli))) + { + for (; alyIndex < aliCount && m_SelectionPools[TeamId.Alliance].AddGroup(m_QueuedGroups[(int)bracket_id][BattlegroundConst.BgQueueNormalAlliance][alyIndex], (uint)((aliFree >= diffHorde) ? aliFree - diffHorde : 0)); alyIndex++) + ++alyIndex; + } + //if ali selection is already empty, then kick horde group, but if there are less horde than ali in bg - break; + if (m_SelectionPools[TeamId.Alliance].GetPlayerCount() == 0) + { + if (aliFree <= diffHorde + 1) + break; + m_SelectionPools[TeamId.Horde].KickGroup((uint)(diffHorde - diffAli)); + } + } + else + { + //kick horde group, add to pool new group if needed + if (m_SelectionPools[TeamId.Horde].KickGroup((uint)(diffAli - diffHorde))) + { + for (; hordeIndex < hordeCount && m_SelectionPools[TeamId.Horde].AddGroup(m_QueuedGroups[(int)bracket_id][BattlegroundConst.BgQueueNormalHorde][hordeIndex], (uint)((hordeFree >= diffAli) ? hordeFree - diffAli : 0)); hordeIndex++) + ++hordeIndex; + } + if (m_SelectionPools[TeamId.Horde].GetPlayerCount() == 0) + { + if (hordeFree <= diffAli + 1) + break; + m_SelectionPools[TeamId.Alliance].KickGroup((uint)(diffAli - diffHorde)); + } + } + //count diffs after small update + diffAli = (int)(aliFree - m_SelectionPools[TeamId.Alliance].GetPlayerCount()); + diffHorde = (int)(hordeFree - m_SelectionPools[TeamId.Horde].GetPlayerCount()); + } + } + + // this method checks if premade versus premade Battleground is possible + // then after 30 mins (default) in queue it moves premade group to normal queue + // it tries to invite as much players as it can - to MaxPlayersPerTeam, because premade groups have more than MinPlayersPerTeam players + bool CheckPremadeMatch(BattlegroundBracketId bracket_id, uint MinPlayersPerTeam, uint MaxPlayersPerTeam) + { + //check match + if (!m_QueuedGroups[(int)bracket_id][BattlegroundConst.BgQueuePremadeAlliance].Empty() && !m_QueuedGroups[(int)bracket_id][BattlegroundConst.BgQueuePremadeHorde].Empty()) + { + //start premade match + //if groups aren't invited + GroupQueueInfo ali_group = null; + GroupQueueInfo horde_group = null; + foreach (var groupQueueInfo in m_QueuedGroups[(int)bracket_id][BattlegroundConst.BgQueuePremadeAlliance]) + { + ali_group = groupQueueInfo; + if (ali_group.IsInvitedToBGInstanceGUID == 0) + break; + } + foreach (var groupQueueInfo in m_QueuedGroups[(int)bracket_id][BattlegroundConst.BgQueuePremadeHorde]) + { + horde_group = groupQueueInfo; + if (horde_group.IsInvitedToBGInstanceGUID == 0) + break; + } + + if (ali_group != null && horde_group != null) + { + m_SelectionPools[TeamId.Alliance].AddGroup(ali_group, MaxPlayersPerTeam); + m_SelectionPools[TeamId.Horde].AddGroup(horde_group, MaxPlayersPerTeam); + //add groups/players from normal queue to size of bigger group + uint maxPlayers = Math.Min(m_SelectionPools[TeamId.Alliance].GetPlayerCount(), m_SelectionPools[TeamId.Horde].GetPlayerCount()); + for (uint i = 0; i < SharedConst.BGTeamsCount; i++) + { + foreach (var groupQueueInfo in m_QueuedGroups[(int)bracket_id][BattlegroundConst.BgQueueNormalAlliance + i]) + { + //if groupQueueInfo can join BG and player count is less that maxPlayers, then add group to selectionpool + if (groupQueueInfo.IsInvitedToBGInstanceGUID == 0 && !m_SelectionPools[i].AddGroup(groupQueueInfo, maxPlayers)) + break; + } + } + //premade selection pools are set + return true; + } + } + // now check if we can move group from Premade queue to normal queue (timer has expired) or group size lowered!! + // this could be 2 cycles but i'm checking only first team in queue - it can cause problem - + // if first is invited to BG and seconds timer expired, but we can ignore it, because players have only 80 seconds to click to enter bg + // and when they click or after 80 seconds the queue info is removed from queue + uint time_before = (uint)(Time.GetMSTime() - WorldConfig.GetIntValue(WorldCfg.BattlegroundPremadeGroupWaitForMatch)); + for (uint i = 0; i < SharedConst.BGTeamsCount; i++) + { + if (!m_QueuedGroups[(int)bracket_id][BattlegroundConst.BgQueuePremadeAlliance + i].Empty()) + { + var groupQueueInfo = m_QueuedGroups[(int)bracket_id][BattlegroundConst.BgQueuePremadeAlliance + i].First(); + if (groupQueueInfo.IsInvitedToBGInstanceGUID == 0 && (groupQueueInfo.JoinTime < time_before || groupQueueInfo.Players.Count < MinPlayersPerTeam)) + { + //we must insert group to normal queue and erase pointer from premade queue + m_QueuedGroups[(int)bracket_id][BattlegroundConst.BgQueueNormalAlliance + i].Insert(0, groupQueueInfo); + m_QueuedGroups[(int)bracket_id][BattlegroundConst.BgQueuePremadeAlliance + i].Remove(groupQueueInfo); + } + } + } + //selection pools are not set + return false; + } + + // this method tries to create Battleground or arena with MinPlayersPerTeam against MinPlayersPerTeam + bool CheckNormalMatch(Battleground bg_template, BattlegroundBracketId bracket_id, uint minPlayers, uint maxPlayers) + { + int[] teamIndex = new int[SharedConst.BGTeamsCount]; + for (uint i = 0; i < SharedConst.BGTeamsCount; i++) + { + teamIndex[i] = 0; + for (; teamIndex[i] != m_QueuedGroups[(int)bracket_id][BattlegroundConst.BgQueueNormalAlliance + i].Count; ++teamIndex[i]) + { + var groupQueueInfo = m_QueuedGroups[(int)bracket_id][BattlegroundConst.BgQueueNormalAlliance + i][teamIndex[i]]; + if (groupQueueInfo.IsInvitedToBGInstanceGUID == 0) + { + m_SelectionPools[i].AddGroup(groupQueueInfo, maxPlayers); + if (m_SelectionPools[i].GetPlayerCount() >= minPlayers) + break; + } + } + } + //try to invite same number of players - this cycle may cause longer wait time even if there are enough players in queue, but we want ballanced bg + uint j = TeamId.Alliance; + if (m_SelectionPools[TeamId.Horde].GetPlayerCount() < m_SelectionPools[TeamId.Alliance].GetPlayerCount()) + j = TeamId.Horde; + + if (WorldConfig.GetIntValue(WorldCfg.BattlegroundInvitationType) != (int)BattlegroundQueueInvitationType.NoBalance + && m_SelectionPools[TeamId.Horde].GetPlayerCount() >= minPlayers && m_SelectionPools[TeamId.Alliance].GetPlayerCount() >= minPlayers) + { + //we will try to invite more groups to team with less players indexed by j + ++(teamIndex[j]); //this will not cause a crash, because for cycle above reached break; + for (; teamIndex[j] != m_QueuedGroups[(int)bracket_id][BattlegroundConst.BgQueueNormalAlliance + j].Count; ++teamIndex[j]) + { + var groupQueueInfo = m_QueuedGroups[(int)bracket_id][BattlegroundConst.BgQueueNormalAlliance + j][teamIndex[j]]; + if (groupQueueInfo.IsInvitedToBGInstanceGUID == 0) + if (!m_SelectionPools[j].AddGroup(groupQueueInfo, m_SelectionPools[(j + 1) % SharedConst.BGTeamsCount].GetPlayerCount())) + break; + } + // do not allow to start bg with more than 2 players more on 1 faction + if (Math.Abs((m_SelectionPools[TeamId.Horde].GetPlayerCount() - m_SelectionPools[TeamId.Alliance].GetPlayerCount())) > 2) + return false; + } + //allow 1v0 if debug bg + if (Global.BattlegroundMgr.isTesting() && (m_SelectionPools[TeamId.Alliance].GetPlayerCount() != 0 || m_SelectionPools[TeamId.Horde].GetPlayerCount() != 0)) + return true; + //return true if there are enough players in selection pools - enable to work .debug bg command correctly + return m_SelectionPools[TeamId.Alliance].GetPlayerCount() >= minPlayers && m_SelectionPools[TeamId.Horde].GetPlayerCount() >= minPlayers; + } + + // this method will check if we can invite players to same faction skirmish match + bool CheckSkirmishForSameFaction(BattlegroundBracketId bracket_id, uint minPlayersPerTeam) + { + if (m_SelectionPools[TeamId.Alliance].GetPlayerCount() < minPlayersPerTeam && m_SelectionPools[TeamId.Horde].GetPlayerCount() < minPlayersPerTeam) + return false; + + uint teamIndex = TeamId.Alliance; + uint otherTeam = TeamId.Horde; + Team otherTeamId = Team.Horde; + if (m_SelectionPools[TeamId.Horde].GetPlayerCount() == minPlayersPerTeam) + { + teamIndex = TeamId.Horde; + otherTeam = TeamId.Alliance; + otherTeamId = Team.Alliance; + } + //clear other team's selection + m_SelectionPools[otherTeam].Init(); + //store last ginfo pointer + GroupQueueInfo ginfo = m_SelectionPools[teamIndex].SelectedGroups.Last(); + //set itr_team to group that was added to selection pool latest + int team = 0; + foreach (var groupQueueInfo in m_QueuedGroups[(int)bracket_id][BattlegroundConst.BgQueueNormalAlliance + teamIndex]) + if (ginfo == groupQueueInfo) + break; + + if (team == m_QueuedGroups[(int)bracket_id][BattlegroundConst.BgQueueNormalAlliance + teamIndex].Count - 1) + return false; + + var team2 = team; + ++team2; + //invite players to other selection pool + for (; team2 != m_QueuedGroups[(int)bracket_id][BattlegroundConst.BgQueueNormalAlliance + teamIndex].Count - 1; ++team2) + { + var groupQueueInfo = m_QueuedGroups[(int)bracket_id][BattlegroundConst.BgQueueNormalAlliance + teamIndex][team2]; + //if selection pool is full then break; + if (groupQueueInfo.IsInvitedToBGInstanceGUID == 0 && !m_SelectionPools[otherTeam].AddGroup(groupQueueInfo, minPlayersPerTeam)) + break; + } + if (m_SelectionPools[otherTeam].GetPlayerCount() != minPlayersPerTeam) + return false; + + //here we have correct 2 selections and we need to change one teams team and move selection pool teams to other team's queue + foreach (var groupQueueInfo in m_SelectionPools[otherTeam].SelectedGroups) + { + //set correct team + groupQueueInfo.Team = otherTeamId; + //add team to other queue + m_QueuedGroups[(int)bracket_id][BattlegroundConst.BgQueueNormalAlliance + otherTeam].Insert(0, groupQueueInfo); + //remove team from old queue + var team3 = team; + ++team3; + for (; team3 != m_QueuedGroups[(int)bracket_id][BattlegroundConst.BgQueueNormalAlliance + teamIndex].Count - 1; ++team3) + { + var groupQueueInfo1 = m_QueuedGroups[(int)bracket_id][BattlegroundConst.BgQueueNormalAlliance + teamIndex][team3]; + if (groupQueueInfo1 == groupQueueInfo) + { + m_QueuedGroups[(int)bracket_id][BattlegroundConst.BgQueueNormalAlliance + teamIndex].Remove(groupQueueInfo1); + break; + } + } + } + return true; + } + + public void UpdateEvents(uint diff) + { + m_events.Update(diff); + } + + /// + /// this method is called when group is inserted, or player / group is removed from BG Queue - there is only one player's status changed, so we don't use while (true) cycles to invite whole queue + /// it must be called after fully adding the members of a group to ensure group joining + /// should be called from Battleground.RemovePlayer function in some cases + /// + /// + /// + /// + /// + /// + /// + public void BattlegroundQueueUpdate(uint diff, BattlegroundTypeId bgTypeId, BattlegroundBracketId bracket_id, byte arenaType, bool isRated, uint arenaRating) + { + //if no players in queue - do nothing + if (m_QueuedGroups[(int)bracket_id][BattlegroundConst.BgQueuePremadeAlliance].Empty() && + m_QueuedGroups[(int)bracket_id][BattlegroundConst.BgQueuePremadeHorde].Empty() && + m_QueuedGroups[(int)bracket_id][BattlegroundConst.BgQueueNormalAlliance].Empty() && + m_QueuedGroups[(int)bracket_id][BattlegroundConst.BgQueueNormalHorde].Empty()) + return; + + // Battleground with free slot for player should be always in the beggining of the queue + // maybe it would be better to create bgfreeslotqueue for each bracket_id + var bgQueues = Global.BattlegroundMgr.GetBGFreeSlotQueueStore(bgTypeId); + foreach (var bg in bgQueues) + { + // DO NOT allow queue manager to invite new player to rated games + if (!bg.isRated() && bg.GetTypeID() == bgTypeId && bg.GetBracketId() == bracket_id && + bg.GetStatus() > BattlegroundStatus.WaitQueue && bg.GetStatus() < BattlegroundStatus.WaitLeave) + { + // clear selection pools + m_SelectionPools[TeamId.Alliance].Init(); + m_SelectionPools[TeamId.Horde].Init(); + + // call a function that does the job for us + FillPlayersToBG(bg, bracket_id); + + // now everything is set, invite players + foreach (var queueInfo in m_SelectionPools[TeamId.Alliance].SelectedGroups) + InviteGroupToBG(queueInfo, bg, queueInfo.Team); + + foreach (var queueInfo in m_SelectionPools[TeamId.Horde].SelectedGroups) + InviteGroupToBG(queueInfo, bg, queueInfo.Team); + + if (!bg.HasFreeSlots()) + bg.RemoveFromBGFreeSlotQueue(); + } + } + + // finished iterating through the bgs with free slots, maybe we need to create a new bg + + Battleground bg_template = Global.BattlegroundMgr.GetBattlegroundTemplate(bgTypeId); + if (!bg_template) + { + Log.outError(LogFilter.Battleground, "Battleground: Update: bg template not found for {0}", bgTypeId); + return; + } + + PvpDifficultyRecord bracketEntry = Global.DB2Mgr.GetBattlegroundBracketById(bg_template.GetMapId(), bracket_id); + if (bracketEntry == null) + { + Log.outError(LogFilter.Battleground, "Battleground: Update: bg bracket entry not found for map {0} bracket id {1}", bg_template.GetMapId(), bracket_id); + return; + } + + // get the min. players per team, properly for larger arenas as well. (must have full teams for arena matches!) + uint MinPlayersPerTeam = bg_template.GetMinPlayersPerTeam(); + uint MaxPlayersPerTeam = bg_template.GetMaxPlayersPerTeam(); + + if (bg_template.isArena()) + { + MaxPlayersPerTeam = arenaType; + MinPlayersPerTeam = (uint)(Global.BattlegroundMgr.isArenaTesting() ? 1 : arenaType); + } + else if (Global.BattlegroundMgr.isTesting()) + MinPlayersPerTeam = 1; + + m_SelectionPools[TeamId.Alliance].Init(); + m_SelectionPools[TeamId.Horde].Init(); + + if (bg_template.isBattleground()) + { + if (CheckPremadeMatch(bracket_id, MinPlayersPerTeam, MaxPlayersPerTeam)) + { + // create new Battleground + Battleground bg2 = Global.BattlegroundMgr.CreateNewBattleground(bgTypeId, bracketEntry, 0, false); + if (bg2 == null) + { + Log.outError(LogFilter.Battleground, "BattlegroundQueue.Update - Cannot create Battleground: {0}", bgTypeId); + return; + } + // invite those selection pools + for (uint i = 0; i < SharedConst.BGTeamsCount; i++) + foreach (var queueInfo in m_SelectionPools[TeamId.Alliance + i].SelectedGroups) + InviteGroupToBG(queueInfo, bg2, queueInfo.Team); + + bg2.StartBattleground(); + //clear structures + m_SelectionPools[TeamId.Alliance].Init(); + m_SelectionPools[TeamId.Horde].Init(); + } + } + + // now check if there are in queues enough players to start new game of (normal Battleground, or non-rated arena) + if (!isRated) + { + // if there are enough players in pools, start new Battleground or non rated arena + if (CheckNormalMatch(bg_template, bracket_id, MinPlayersPerTeam, MaxPlayersPerTeam) + || (bg_template.isArena() && CheckSkirmishForSameFaction(bracket_id, MinPlayersPerTeam))) + { + // we successfully created a pool + Battleground bg2 = Global.BattlegroundMgr.CreateNewBattleground(bgTypeId, bracketEntry, (ArenaTypes)arenaType, false); + if (bg2 == null) + { + Log.outError(LogFilter.Battleground, "BattlegroundQueue.Update - Cannot create Battleground: {0}", bgTypeId); + return; + } + + // invite those selection pools + for (uint i = 0; i < SharedConst.BGTeamsCount; i++) + { + foreach (var queueInfo in m_SelectionPools[TeamId.Alliance + i].SelectedGroups) + InviteGroupToBG(queueInfo, bg2, queueInfo.Team); + } + // start bg + bg2.StartBattleground(); + } + } + else if (bg_template.isArena()) + { + // found out the minimum and maximum ratings the newly added team should battle against + // arenaRating is the rating of the latest joined team, or 0 + // 0 is on (automatic update call) and we must set it to team's with longest wait time + if (arenaRating == 0) + { + GroupQueueInfo front1 = null; + GroupQueueInfo front2 = null; + if (!m_QueuedGroups[(int)bracket_id][BattlegroundConst.BgQueuePremadeAlliance].Empty()) + { + front1 = m_QueuedGroups[(int)bracket_id][BattlegroundConst.BgQueuePremadeAlliance].First(); + arenaRating = front1.ArenaMatchmakerRating; + } + if (!m_QueuedGroups[(int)bracket_id][BattlegroundConst.BgQueuePremadeHorde].Empty()) + { + front2 = m_QueuedGroups[(int)bracket_id][BattlegroundConst.BgQueuePremadeHorde].First(); + arenaRating = front2.ArenaMatchmakerRating; + } + if (front1 != null && front2 != null) + { + if (front1.JoinTime < front2.JoinTime) + arenaRating = front1.ArenaMatchmakerRating; + } + else if (front1 == null && front2 == null) + return; //queues are empty + } + + //set rating range + uint arenaMinRating = (arenaRating <= Global.BattlegroundMgr.GetMaxRatingDifference()) ? 0 : arenaRating - Global.BattlegroundMgr.GetMaxRatingDifference(); + uint arenaMaxRating = arenaRating + Global.BattlegroundMgr.GetMaxRatingDifference(); + // if max rating difference is set and the time past since server startup is greater than the rating discard time + // (after what time the ratings aren't taken into account when making teams) then + // the discard time is current_time - time_to_discard, teams that joined after that, will have their ratings taken into account + // else leave the discard time on 0, this way all ratings will be discarded + int discardTime = (int)(Time.GetMSTime() - Global.BattlegroundMgr.GetRatingDiscardTimer()); + + // we need to find 2 teams which will play next game + GroupQueueInfo[] queueArray = new GroupQueueInfo[SharedConst.BGTeamsCount]; + byte found = 0; + byte team = 0; + + for (byte i = (byte)BattlegroundConst.BgQueuePremadeAlliance; i < BattlegroundConst.BgQueueNormalAlliance; i++) + { + // take the group that joined first + foreach (var queueInfo in m_QueuedGroups[(int)bracket_id][i]) + { + // if group match conditions, then add it to pool + if (queueInfo.IsInvitedToBGInstanceGUID == 0 + && ((queueInfo.ArenaMatchmakerRating >= arenaMinRating && queueInfo.ArenaMatchmakerRating <= arenaMaxRating) + || queueInfo.JoinTime < discardTime)) + { + queueArray[found++] = queueInfo; + team = i; + break; + } + } + } + + if (found == 0) + return; + + if (found == 1) + { + foreach (var queueInfo in m_QueuedGroups[(int)bracket_id][team]) + { + if (queueInfo.IsInvitedToBGInstanceGUID == 0 + && ((queueInfo.ArenaMatchmakerRating >= arenaMinRating && queueInfo.ArenaMatchmakerRating <= arenaMaxRating) + || queueInfo.JoinTime < discardTime) + && queueArray[0].ArenaTeamId != queueInfo.ArenaTeamId) + { + queueArray[found++] = queueInfo; + break; + } + } + } + + //if we have 2 teams, then start new arena and invite players! + if (found == 2) + { + GroupQueueInfo aTeam = queueArray[TeamId.Alliance]; + GroupQueueInfo hTeam = queueArray[TeamId.Horde]; + Battleground arena = Global.BattlegroundMgr.CreateNewBattleground(bgTypeId, bracketEntry, (ArenaTypes)arenaType, true); + if (!arena) + { + Log.outError(LogFilter.Battleground, "BattlegroundQueue.Update couldn't create arena instance for rated arena match!"); + return; + } + + aTeam.OpponentsTeamRating = hTeam.ArenaTeamRating; + hTeam.OpponentsTeamRating = aTeam.ArenaTeamRating; + aTeam.OpponentsMatchmakerRating = hTeam.ArenaMatchmakerRating; + hTeam.OpponentsMatchmakerRating = aTeam.ArenaMatchmakerRating; + Log.outDebug(LogFilter.Battleground, "setting oposite teamrating for team {0} to {1}", aTeam.ArenaTeamId, aTeam.OpponentsTeamRating); + Log.outDebug(LogFilter.Battleground, "setting oposite teamrating for team {0} to {1}", hTeam.ArenaTeamId, hTeam.OpponentsTeamRating); + + // now we must move team if we changed its faction to another faction queue, because then we will spam log by errors in Queue.RemovePlayer + if (aTeam.Team != Team.Alliance) + { + m_QueuedGroups[(int)bracket_id][BattlegroundConst.BgQueuePremadeAlliance].Insert(0, aTeam); + m_QueuedGroups[(int)bracket_id][BattlegroundConst.BgQueuePremadeHorde].Remove(queueArray[TeamId.Alliance]); + } + if (hTeam.Team != Team.Horde) + { + m_QueuedGroups[(int)bracket_id][BattlegroundConst.BgQueuePremadeHorde].Insert(0, hTeam); + m_QueuedGroups[(int)bracket_id][BattlegroundConst.BgQueuePremadeAlliance].Remove(queueArray[TeamId.Horde]); + } + + arena.SetArenaMatchmakerRating(Team.Alliance, aTeam.ArenaMatchmakerRating); + arena.SetArenaMatchmakerRating(Team.Horde, hTeam.ArenaMatchmakerRating); + InviteGroupToBG(aTeam, arena, Team.Alliance); + InviteGroupToBG(hTeam, arena, Team.Horde); + + Log.outDebug(LogFilter.Battleground, "Starting rated arena match!"); + arena.StartBattleground(); + } + } + } + + Dictionary m_QueuedPlayers = new Dictionary(); + + /// + /// This two dimensional array is used to store All queued groups + /// First dimension specifies the bgTypeId + /// Second dimension specifies the player's group types - + /// BG_QUEUE_PREMADE_ALLIANCE is used for premade alliance groups and alliance rated arena teams + /// BG_QUEUE_PREMADE_HORDE is used for premade horde groups and horde rated arena teams + /// BattlegroundConst.BgQueueNormalAlliance is used for normal (or small) alliance groups or non-rated arena matches + /// BattlegroundConst.BgQueueNormalHorde is used for normal (or small) horde groups or non-rated arena matches + /// + List[][] m_QueuedGroups = new List[(int)BattlegroundBracketId.Max][]; + + uint[][][] m_WaitTimes = new uint[SharedConst.BGTeamsCount][][]; + uint[][] m_WaitTimeLastPlayer = new uint[SharedConst.BGTeamsCount][]; + uint[][] m_SumOfWaitTimes = new uint[SharedConst.BGTeamsCount][]; + + // Event handler + EventSystem m_events = new EventSystem(); + + SelectionPool[] m_SelectionPools = new SelectionPool[SharedConst.BGTeamsCount]; + // class to select and invite groups to bg + class SelectionPool + { + public void Init() + { + SelectedGroups.Clear(); + PlayerCount = 0; + } + public bool AddGroup(GroupQueueInfo ginfo, uint desiredCount) + { + //if group is larger than desired count - don't allow to add it to pool + if (ginfo.IsInvitedToBGInstanceGUID == 0 && desiredCount >= PlayerCount + ginfo.Players.Count) + { + SelectedGroups.Add(ginfo); + // increase selected players count + PlayerCount += (uint)ginfo.Players.Count; + return true; + } + if (PlayerCount < desiredCount) + return true; + + return false; + } + public bool KickGroup(uint size) + { + //find maxgroup or LAST group with size == size and kick it + bool found = false; + GroupQueueInfo groupToKick = null; + foreach (var groupQueueInfo in SelectedGroups) + { + if (Math.Abs(groupQueueInfo.Players.Count - size) <= 1) + { + groupToKick = groupQueueInfo; + found = true; + } + else if (!found && groupQueueInfo.Players.Count >= groupToKick.Players.Count) + groupToKick = groupQueueInfo; + } + //if pool is empty, do nothing + if (GetPlayerCount() != 0) + { + //update player count + GroupQueueInfo ginfo = groupToKick; + SelectedGroups.Remove(groupToKick); + PlayerCount -= (uint)ginfo.Players.Count; + //return false if we kicked smaller group or there are enough players in selection pool + if (ginfo.Players.Count <= size + 1) + return false; + } + return true; + } + public uint GetPlayerCount() { return PlayerCount; } + + public List SelectedGroups = new List(); + + uint PlayerCount; + } + + } + + /// + /// stores information for players in queue + /// + public class PlayerQueueInfo + { + public uint LastOnlineTime; // for tracking and removing offline players from queue after 5 minutes + public GroupQueueInfo GroupInfo; // pointer to the associated groupqueueinfo + } + + /// + /// stores information about the group in queue (also used when joined as solo!) + /// + public class GroupQueueInfo + { + public Dictionary Players = new Dictionary(); // player queue info map + public Team Team; // Player team (ALLIANCE/HORDE) + public BattlegroundTypeId BgTypeId; // Battleground type id + public bool IsRated; // rated + public ArenaTypes ArenaType; // 2v2, 3v3, 5v5 or 0 when BG + public uint ArenaTeamId; // team id if rated match + public uint JoinTime; // time when group was added + public uint RemoveInviteTime; // time when we will remove invite for players in group + public uint IsInvitedToBGInstanceGUID; // was invited to certain BG + public uint ArenaTeamRating; // if rated match, inited to the rating of the team + public uint ArenaMatchmakerRating; // if rated match, inited to the rating of the team + public uint OpponentsTeamRating; // for rated arena matches + public uint OpponentsMatchmakerRating; // for rated arena matches + } + + /// + /// This class is used to invite player to BG again, when minute lasts from his first invitation + /// it is capable to solve all possibilities + /// + class BGQueueInviteEvent : BasicEvent + { + public BGQueueInviteEvent(ObjectGuid pl_guid, uint BgInstanceGUID, BattlegroundTypeId BgTypeId, ArenaTypes arenaType, uint removeTime) + { + m_PlayerGuid = pl_guid; + m_BgInstanceGUID = BgInstanceGUID; + m_BgTypeId = BgTypeId; + m_ArenaType = arenaType; + m_RemoveTime = removeTime; + } + + public override bool Execute(ulong e_time, uint p_time) + { + Player player = Global.ObjAccessor.FindPlayer(m_PlayerGuid); + // player logged off (we should do nothing, he is correctly removed from queue in another procedure) + if (!player) + return true; + + Battleground bg = Global.BattlegroundMgr.GetBattleground(m_BgInstanceGUID, m_BgTypeId); + //if Battleground ended and its instance deleted - do nothing + if (bg == null) + return true; + + BattlegroundQueueTypeId bgQueueTypeId = Global.BattlegroundMgr.BGQueueTypeId(bg.GetTypeID(), bg.GetArenaType()); + uint queueSlot = player.GetBattlegroundQueueIndex(bgQueueTypeId); + if (queueSlot < SharedConst.BGTeamsCount) // player is in queue or in Battleground + { + // check if player is invited to this bg + BattlegroundQueue bgQueue = Global.BattlegroundMgr.GetBattlegroundQueue(bgQueueTypeId); + if (bgQueue.IsPlayerInvited(m_PlayerGuid, m_BgInstanceGUID, m_RemoveTime)) + { + BattlefieldStatusNeedConfirmation battlefieldStatus; + Global.BattlegroundMgr.BuildBattlegroundStatusNeedConfirmation(out battlefieldStatus, bg, player, queueSlot, player.GetBattlegroundQueueJoinTime(bgQueueTypeId), BattlegroundConst.InviteAcceptWaitTime - BattlegroundConst.InvitationRemindTime, m_ArenaType); + player.SendPacket(battlefieldStatus); + } + } + return true; //event will be deleted + } + + public override void Abort(ulong e_time) { } + + ObjectGuid m_PlayerGuid; + uint m_BgInstanceGUID; + BattlegroundTypeId m_BgTypeId; + ArenaTypes m_ArenaType; + uint m_RemoveTime; + } + + /// + /// This class is used to remove player from BG queue after 1 minute 20 seconds from first invitation + /// We must store removeInvite time in case player left queue and joined and is invited again + /// We must store bgQueueTypeId, because Battleground can be deleted already, when player entered it + /// + class BGQueueRemoveEvent : BasicEvent + { + public BGQueueRemoveEvent(ObjectGuid pl_guid, uint bgInstanceGUID, BattlegroundTypeId BgTypeId, ArenaTypes arenaType, BattlegroundQueueTypeId bgQueueTypeId, uint removeTime) + { + m_PlayerGuid = pl_guid; + m_BgInstanceGUID = bgInstanceGUID; + m_RemoveTime = removeTime; + m_ArenaType = arenaType; + m_BgTypeId = BgTypeId; + m_BgQueueTypeId = bgQueueTypeId; + } + + public override bool Execute(ulong e_time, uint p_time) + { + Player player = Global.ObjAccessor.FindPlayer(m_PlayerGuid); + if (!player) + // player logged off (we should do nothing, he is correctly removed from queue in another procedure) + return true; + + Battleground bg = Global.BattlegroundMgr.GetBattleground(m_BgInstanceGUID, m_BgTypeId); + //Battleground can be deleted already when we are removing queue info + //bg pointer can be NULL! so use it carefully! + + uint queueSlot = player.GetBattlegroundQueueIndex(m_BgQueueTypeId); + if (queueSlot < SharedConst.BGTeamsCount) // player is in queue, or in Battleground + { + // check if player is in queue for this BG and if we are removing his invite event + BattlegroundQueue bgQueue = Global.BattlegroundMgr.GetBattlegroundQueue(m_BgQueueTypeId); + if (bgQueue.IsPlayerInvited(m_PlayerGuid, m_BgInstanceGUID, m_RemoveTime)) + { + Log.outDebug(LogFilter.Battleground, "Battleground: removing player {0} from bg queue for instance {1} because of not pressing enter battle in time.", player.GetGUID().ToString(), m_BgInstanceGUID); + + player.RemoveBattlegroundQueueId(m_BgQueueTypeId); + bgQueue.RemovePlayer(m_PlayerGuid, true); + //update queues if Battleground isn't ended + if (bg && bg.isBattleground() && bg.GetStatus() != BattlegroundStatus.WaitLeave) + Global.BattlegroundMgr.ScheduleQueueUpdate(0, 0, m_BgQueueTypeId, m_BgTypeId, bg.GetBracketId()); + + BattlefieldStatusNone battlefieldStatus; + Global.BattlegroundMgr.BuildBattlegroundStatusNone(out battlefieldStatus, player, queueSlot, player.GetBattlegroundQueueJoinTime(m_BgQueueTypeId), m_ArenaType); + player.SendPacket(battlefieldStatus); + } + } + + //event will be deleted + return true; + } + + public override void Abort(ulong e_time) { } + + ObjectGuid m_PlayerGuid; + uint m_BgInstanceGUID; + uint m_RemoveTime; + BattlegroundTypeId m_BgTypeId; + ArenaTypes m_ArenaType; + BattlegroundQueueTypeId m_BgQueueTypeId; + } +} diff --git a/Game/BattleGrounds/BattleGroundScore.cs b/Game/BattleGrounds/BattleGroundScore.cs new file mode 100644 index 000000000..074200175 --- /dev/null +++ b/Game/BattleGrounds/BattleGroundScore.cs @@ -0,0 +1,80 @@ +/* + * Copyright (C) 2012-2017 CypherCore + * + * 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, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +using Framework.Constants; +using Game.Entities; +using System.Collections.Generic; +using System.Diagnostics.Contracts; + +namespace Game.BattleGrounds +{ + public class BattlegroundScore + { + public BattlegroundScore(ObjectGuid playerGuid, Team team) + { + PlayerGuid = playerGuid; + TeamId = team == Team.Alliance ? 1 : 0; + } + + public virtual void UpdateScore(ScoreType type, uint value) + { + switch (type) + { + case ScoreType.KillingBlows: + KillingBlows += value; + break; + case ScoreType.Deaths: + Deaths += value; + break; + case ScoreType.HonorableKills: + HonorableKills += value; + break; + case ScoreType.BonusHonor: + BonusHonor += value; + break; + case ScoreType.DamageDone: + DamageDone += value; + break; + case ScoreType.HealingDone: + HealingDone += value; + break; + default: + Contract.Assert(false, "Not implemented Battleground score type!"); + break; + } + } + + public virtual void BuildObjectivesBlock(List stats) { } + + public virtual uint GetAttr1() { return 0; } + public virtual uint GetAttr2() { return 0; } + public virtual uint GetAttr3() { return 0; } + public virtual uint GetAttr4() { return 0; } + public virtual uint GetAttr5() { return 0; } + + public ObjectGuid PlayerGuid; + public int TeamId; + + // Default score, present in every type + public uint KillingBlows; + public uint Deaths; + public uint HonorableKills; + public uint BonusHonor; + public uint DamageDone; + public uint HealingDone; + } +} diff --git a/Game/BattleGrounds/Zones/AlteracValley.cs b/Game/BattleGrounds/Zones/AlteracValley.cs new file mode 100644 index 000000000..268f34149 --- /dev/null +++ b/Game/BattleGrounds/Zones/AlteracValley.cs @@ -0,0 +1,23 @@ +/* + * Copyright (C) 2012-2017 CypherCore + * + * 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, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +namespace Game.BattleGrounds.Zones +{ + class AlteracValley + { + } +} diff --git a/Game/BattleGrounds/Zones/ArathiBasin.cs b/Game/BattleGrounds/Zones/ArathiBasin.cs new file mode 100644 index 000000000..a8bc79731 --- /dev/null +++ b/Game/BattleGrounds/Zones/ArathiBasin.cs @@ -0,0 +1,1017 @@ +/* + * Copyright (C) 2012-2017 CypherCore + * + * 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, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +using Framework.Constants; +using Game.DataStorage; +using Game.Entities; +using Game.Network.Packets; +using System; +using System.Collections.Generic; +using System.Diagnostics.Contracts; + +namespace Game.BattleGrounds +{ + class BgArathiBasin : Battleground + { + public BgArathiBasin() + { + m_IsInformedNearVictory = false; + m_BuffChange = true; + + for (byte i = 0; i < BattlegroundNodes.DynamicNodesCount; ++i) + { + m_Nodes[i] = 0; + m_prevNodes[i] = 0; + m_NodeTimers[i] = 0; + m_BannerTimers[i].timer = 0; + m_BannerTimers[i].type = 0; + m_BannerTimers[i].teamIndex = 0; + } + + for (byte i = 0; i < SharedConst.BGTeamsCount; ++i) + { + m_lastTick[i] = 0; + m_HonorScoreTics[i] = 0; + m_ReputationScoreTics[i] = 0; + m_TeamScores500Disadvantage[i] = false; + } + + m_HonorTics = 0; + m_ReputationTics = 0; + + StartMessageIds[BattlegroundConst.EventIdFirst] = CypherStrings.BgAbStartTwoMinutes; + StartMessageIds[BattlegroundConst.EventIdSecond] = CypherStrings.BgAbStartOneMinute; + StartMessageIds[BattlegroundConst.EventIdThird] = CypherStrings.BgAbStartHalfMinute; + StartMessageIds[BattlegroundConst.EventIdFourth] = CypherStrings.BgAbHasBegun; + } + + public override void PostUpdateImpl(uint diff) + { + if (GetStatus() == BattlegroundStatus.InProgress) + { + int[] team_points = { 0, 0 }; + + for (byte node = 0; node < BattlegroundNodes.DynamicNodesCount; ++node) + { + // 3 sec delay to spawn new banner instead previous despawned one + if (m_BannerTimers[node].timer != 0) + { + if (m_BannerTimers[node].timer > diff) + m_BannerTimers[node].timer -= diff; + else + { + m_BannerTimers[node].timer = 0; + _CreateBanner(node, (NodeStatus)m_BannerTimers[node].type, m_BannerTimers[node].teamIndex, false); + } + } + + // 1-minute to occupy a node from contested state + if (m_NodeTimers[node] != 0) + { + if (m_NodeTimers[node] > diff) + m_NodeTimers[node] -= diff; + else + { + m_NodeTimers[node] = 0; + // Change from contested to occupied ! + int teamIndex = (int)m_Nodes[node] - 1; + m_prevNodes[node] = m_Nodes[node]; + m_Nodes[node] += 2; + // burn current contested banner + _DelBanner(node, NodeStatus.Contested, (byte)teamIndex); + // create new occupied banner + _CreateBanner(node, NodeStatus.Occupied, teamIndex, true); + _SendNodeUpdate(node); + _NodeOccupied(node, (teamIndex == 0) ? Team.Alliance : Team.Horde); + // Message to chatlog + + if (teamIndex == 0) + { + // FIXME: team and node names not localized + SendMessage2ToAll(CypherStrings.BgAbNodeTaken, ChatMsg.BgSystemAlliance, null, CypherStrings.BgAbAlly, _GetNodeNameId(node)); + PlaySoundToAll(SoundCapturedAlliance); + } + else + { + // FIXME: team and node names not localized + SendMessage2ToAll(CypherStrings.BgAbNodeTaken, ChatMsg.BgSystemHorde, null, CypherStrings.BgAbHorde, _GetNodeNameId(node)); + PlaySoundToAll(SoundCapturedHorde); + } + } + } + + for (int team = 0; team < SharedConst.BGTeamsCount; ++team) + if (m_Nodes[node] == team + NodeStatus.Occupied) + ++team_points[team]; + } + + // Accumulate points + for (int team = 0; team < SharedConst.BGTeamsCount; ++team) + { + int points = team_points[team]; + if (points == 0) + continue; + + m_lastTick[team] += diff; + + if (m_lastTick[team] > TickIntervals[points]) + { + m_lastTick[team] -= TickIntervals[points]; + m_TeamScores[team] += TickPoints[points]; + m_HonorScoreTics[team] += TickPoints[points]; + m_ReputationScoreTics[team] += TickPoints[points]; + + if (m_ReputationScoreTics[team] >= m_ReputationTics) + { + if (team == TeamId.Alliance) + RewardReputationToTeam(509, 10, Team.Alliance); + else + RewardReputationToTeam(510, 10, Team.Horde); + + m_ReputationScoreTics[team] -= m_ReputationTics; + } + + if (m_HonorScoreTics[team] >= m_HonorTics) + { + RewardHonorToTeam(GetBonusHonorFromKill(1), (team == TeamId.Alliance) ? Team.Alliance : Team.Horde); + m_HonorScoreTics[team] -= m_HonorTics; + } + + if (!m_IsInformedNearVictory && m_TeamScores[team] > WarningNearVictoryScore) + { + if (team == TeamId.Alliance) + SendMessageToAll(CypherStrings.BgAbANearVictory, ChatMsg.BgSystemNeutral); + else + SendMessageToAll(CypherStrings.BgAbHNearVictory, ChatMsg.BgSystemNeutral); + PlaySoundToAll(SoundNearVictory); + m_IsInformedNearVictory = true; + } + + if (m_TeamScores[team] > MaxTeamScore) + m_TeamScores[team] = MaxTeamScore; + + if (team == TeamId.Alliance) + UpdateWorldState(WorldStates.ResourcesAlly, m_TeamScores[team]); + else if (team == TeamId.Horde) + UpdateWorldState(WorldStates.ResourcesHorde, m_TeamScores[team]); + // update achievement flags + // we increased m_TeamScores[team] so we just need to check if it is 500 more than other teams resources + int otherTeam = (team + 1) % SharedConst.BGTeamsCount; + if (m_TeamScores[team] > m_TeamScores[otherTeam] + 500) + m_TeamScores500Disadvantage[otherTeam] = true; + } + } + + // Test win condition + if (m_TeamScores[TeamId.Alliance] >= MaxTeamScore) + EndBattleground(Team.Alliance); + else if (m_TeamScores[TeamId.Horde] >= MaxTeamScore) + EndBattleground(Team.Horde); + } + } + + public override void StartingEventCloseDoors() + { + // despawn banners, auras and buffs + for (int obj = ObjectType.BannerNeutral; obj < BattlegroundNodes.DynamicNodesCount * 8; ++obj) + SpawnBGObject(obj, BattlegroundConst.RespawnOneDay); + for (int i = 0; i < BattlegroundNodes.DynamicNodesCount * 3; ++i) + SpawnBGObject(ObjectType.SpeedbuffStables + i, BattlegroundConst.RespawnOneDay); + + // Starting doors + DoorClose(ObjectType.GateA); + DoorClose(ObjectType.GateH); + SpawnBGObject(ObjectType.GateA, BattlegroundConst.RespawnImmediately); + SpawnBGObject(ObjectType.GateH, BattlegroundConst.RespawnImmediately); + + // Starting base spirit guides + _NodeOccupied(BattlegroundNodes.SpiritAliance, Team.Alliance); + _NodeOccupied(BattlegroundNodes.SpiritHorde, Team.Horde); + } + + public override void StartingEventOpenDoors() + { + // spawn neutral banners + for (int banner = ObjectType.BannerNeutral, i = 0; i < 5; banner += 8, ++i) + SpawnBGObject(banner, BattlegroundConst.RespawnImmediately); + for (int i = 0; i < BattlegroundNodes.DynamicNodesCount; ++i) + { + //randomly select buff to spawn + int buff = RandomHelper.IRand(0, 2); + SpawnBGObject(ObjectType.SpeedbuffStables + buff + i * 3, BattlegroundConst.RespawnImmediately); + } + DoorOpen(ObjectType.GateA); + DoorOpen(ObjectType.GateH); + + // Achievement: Let's Get This Done + StartCriteriaTimer(CriteriaTimedTypes.Event, EventStartBattle); + } + + public override void AddPlayer(Player player) + { + base.AddPlayer(player); + PlayerScores[player.GetGUID()] = new BattlegroundABScore(player.GetGUID(), player.GetBGTeam()); + } + + public override void RemovePlayer(Player Player, ObjectGuid guid, Team team) + { + } + + public override void HandleAreaTrigger(Player player, uint trigger, bool entered) + { + switch (trigger) + { + case 6635: // Horde Start + case 6634: // Alliance Start + if (GetStatus() == BattlegroundStatus.WaitJoin && !entered) + TeleportPlayerToExploitLocation(player); + break; + case 3948: // Arathi Basin Alliance Exit. + if (player.GetTeam() != Team.Alliance) + player.GetSession().SendNotification("Only The Alliance can use that portal"); + else + player.LeaveBattleground(); + break; + case 3949: // Arathi Basin Horde Exit. + if (player.GetTeam() != Team.Horde) + player.GetSession().SendNotification("Only The Horde can use that portal"); + else + player.LeaveBattleground(); + break; + case 3866: // Stables + case 3869: // Gold Mine + case 3867: // Farm + case 3868: // Lumber Mill + case 3870: // Black Smith + case 4020: // Unk1 + case 4021: // Unk2 + case 4674: // Unk3 + //break; + default: + base.HandleAreaTrigger(player, trigger, entered); + break; + } + } + + void _CreateBanner(byte node, NodeStatus type, int teamIndex, bool delay) + { + // Just put it into the queue + if (delay) + { + m_BannerTimers[node].timer = 2000; + m_BannerTimers[node].type = (byte)type; + m_BannerTimers[node].teamIndex = (byte)teamIndex; + return; + } + + int obj = node * 8 + (byte)type + teamIndex; + + SpawnBGObject(obj, BattlegroundConst.RespawnImmediately); + + // handle aura with banner + if (type == 0) + return; + obj = node * 8 + ((type == NodeStatus.Occupied) ? (5 + teamIndex) : 7); + SpawnBGObject(obj, BattlegroundConst.RespawnImmediately); + } + + void _DelBanner(byte node, NodeStatus type, byte teamIndex) + { + int obj = node * 8 + (byte)type + teamIndex; + SpawnBGObject(obj, BattlegroundConst.RespawnOneDay); + + // handle aura with banner + if (type == 0) + return; + obj = node * 8 + ((type == NodeStatus.Occupied) ? (5 + teamIndex) : 7); + SpawnBGObject(obj, BattlegroundConst.RespawnOneDay); + } + + CypherStrings _GetNodeNameId(byte node) + { + switch (node) + { + case BattlegroundNodes.NodeStables: + return CypherStrings.BgAbNodeStables; + case BattlegroundNodes.NodeBlacksmith: + return CypherStrings.BgAbNodeBlacksmith; + case BattlegroundNodes.NodeFarm: + return CypherStrings.BgAbNodeFarm; + case BattlegroundNodes.NodeLumberMill: + return CypherStrings.BgAbNodeLumberMill; + case BattlegroundNodes.NodeGoldMine: + return CypherStrings.BgAbNodeGoldMine; + default: + Contract.Assert(false); + break; + } + return 0; + } + + public override void FillInitialWorldStates(InitWorldStates packet) + { + byte[] plusArray = { 0, 2, 3, 0, 1 }; + + // Node icons + for (byte node = 0; node < BattlegroundNodes.DynamicNodesCount; ++node) + packet.AddState(NodeIcons[node], (m_Nodes[node] == 0)); + + // Node occupied states + for (byte node = 0; node < BattlegroundNodes.DynamicNodesCount; ++node) + for (byte i = 1; i < BattlegroundNodes.DynamicNodesCount; ++i) + packet.AddState(NodeStates[node] + plusArray[i], ((int)m_Nodes[node] == i)); + + // How many bases each team owns + byte ally = 0, horde = 0; + for (byte node = 0; node < BattlegroundNodes.DynamicNodesCount; ++node) + if (m_Nodes[node] == NodeStatus.AllyOccupied) + ++ally; + else if (m_Nodes[node] == NodeStatus.HordeOccupied) + ++horde; + + packet.AddState(WorldStates.OccupiedBasesAlly, ally); + packet.AddState(WorldStates.OccupiedBasesHorde, horde); + + // Team scores + packet.AddState(WorldStates.ResourcesMax, MaxTeamScore); + packet.AddState(WorldStates.ResourcesWarning, WarningNearVictoryScore); + packet.AddState(WorldStates.ResourcesAlly, (int)m_TeamScores[TeamId.Alliance]); + packet.AddState(WorldStates.ResourcesHorde, (int)m_TeamScores[TeamId.Horde]); + + // other unknown + packet.AddState(0x745, 0x2); + } + + void _SendNodeUpdate(byte node) + { + // Send node owner state update to refresh map icons on client + byte[] plusArray = { 0, 2, 3, 0, 1 }; + + if (m_prevNodes[node] != 0) + UpdateWorldState(NodeStates[node] + plusArray[(int)m_prevNodes[node]], 0); + else + UpdateWorldState(NodeIcons[node], 0); + + UpdateWorldState(NodeStates[node] + plusArray[(byte)m_Nodes[node]], 1); + + // How many bases each team owns + byte ally = 0, horde = 0; + for (byte i = 0; i < BattlegroundNodes.DynamicNodesCount; ++i) + if (m_Nodes[i] == NodeStatus.AllyOccupied) + ++ally; + else if (m_Nodes[i] == NodeStatus.HordeOccupied) + ++horde; + + UpdateWorldState(WorldStates.OccupiedBasesAlly, ally); + UpdateWorldState(WorldStates.OccupiedBasesHorde, horde); + } + + void _NodeOccupied(byte node, Team team) + { + if (!AddSpiritGuide(node, SpiritGuidePos[node], GetTeamIndexByTeamId(team))) + Log.outError(LogFilter.Battleground, "Failed to spawn spirit guide! point: {0}, team: {1}, ", node, team); + + if (node >= BattlegroundNodes.DynamicNodesCount)//only dynamic nodes, no start points + return; + + byte capturedNodes = 0; + for (byte i = 0; i < BattlegroundNodes.DynamicNodesCount; ++i) + if (m_Nodes[i] == NodeStatus.Occupied + GetTeamIndexByTeamId(team) && m_NodeTimers[i] == 0) + ++capturedNodes; + + if (capturedNodes >= 5) + CastSpellOnTeam(BattlegroundConst.AbQuestReward5Bases, team); + if (capturedNodes >= 4) + CastSpellOnTeam(BattlegroundConst.AbQuestReward4Bases, team); + + Creature trigger = BgCreatures.ContainsKey(node + 7) ? GetBGCreature(node + 7) : null; // 0-6 spirit guides + if (!trigger) + trigger = AddCreature(SharedConst.WorldTrigger, node + 7, NodePositions[node], GetTeamIndexByTeamId(team)); + + //add bonus honor aura trigger creature when node is accupied + //cast bonus aura (+50% honor in 25yards) + //aura should only apply to players who have accupied the node, set correct faction for trigger + if (trigger) + { + trigger.SetFaction(team == Team.Alliance ? 84u : 83u); + trigger.CastSpell(trigger, BattlegroundConst.SpellHonorableDefender25y, false); + } + } + + void _NodeDeOccupied(byte node) + { + if (node >= BattlegroundNodes.DynamicNodesCount) + return; + + //remove bonus honor aura trigger creature when node is lost + if (node < BattlegroundNodes.DynamicNodesCount)//only dynamic nodes, no start points + DelCreature(node + 7);//null checks are in DelCreature! 0-6 spirit guides + + RelocateDeadPlayers(BgCreatures[node]); + + DelCreature(node); + + // buff object isn't despawned + } + + //Invoked if a player used a banner as a gameobject + public override void EventPlayerClickedOnFlag(Player source, GameObject target_obj) + { + if (GetStatus() != BattlegroundStatus.InProgress) + return; + + byte node = BattlegroundNodes.NodeStables; + GameObject obj = GetBgMap().GetGameObject(BgObjects[node * 8 + 7]); + while ((node < BattlegroundNodes.DynamicNodesCount) && ((!obj) || (!source.IsWithinDistInMap(obj, 10)))) + { + ++node; + obj = GetBgMap().GetGameObject(BgObjects[node * 8 + ObjectType.AuraContested]); + } + + if (node == BattlegroundNodes.DynamicNodesCount) + { + // this means our player isn't close to any of banners - maybe cheater ?? + return; + } + + int teamIndex = GetTeamIndexByTeamId(source.GetTeam()); + + // Check if player really could use this banner, not cheated + if (!(m_Nodes[node] == 0 || teamIndex == (int)m_Nodes[node] % 2)) + return; + + source.RemoveAurasWithInterruptFlags(SpellAuraInterruptFlags.EnterPvpCombat); + uint sound = 0; + // If node is neutral, change to contested + if (m_Nodes[node] == NodeStatus.Neutral) + { + UpdatePlayerScore(source, ScoreType.BasesAssaulted, 1); + m_prevNodes[node] = m_Nodes[node]; + m_Nodes[node] = (NodeStatus)(teamIndex + 1); + // burn current neutral banner + _DelBanner(node, NodeStatus.Neutral, 0); + // create new contested banner + _CreateBanner(node, NodeStatus.Contested, (byte)teamIndex, true); + _SendNodeUpdate(node); + m_NodeTimers[node] = FlagCapturingTime; + + // FIXME: team and node names not localized + if (teamIndex == 0) + SendMessage2ToAll(CypherStrings.BgAbNodeClaimed, ChatMsg.BgSystemAlliance, source, _GetNodeNameId(node), CypherStrings.BgAbAlly); + else + SendMessage2ToAll(CypherStrings.BgAbNodeClaimed, ChatMsg.BgSystemHorde, source, _GetNodeNameId(node), CypherStrings.BgAbHorde); + + sound = SoundClaimed; + } + // If node is contested + else if ((m_Nodes[node] == NodeStatus.AllyContested) || (m_Nodes[node] == NodeStatus.HordeContested)) + { + // If last state is NOT occupied, change node to enemy-contested + if (m_prevNodes[node] < NodeStatus.Occupied) + { + UpdatePlayerScore(source, ScoreType.BasesAssaulted, 1); + m_prevNodes[node] = m_Nodes[node]; + m_Nodes[node] = (NodeStatus.Contested + (int)teamIndex); + // burn current contested banner + _DelBanner(node, NodeStatus.Contested, (byte)teamIndex); + // create new contested banner + _CreateBanner(node, NodeStatus.Contested, (byte)teamIndex, true); + _SendNodeUpdate(node); + m_NodeTimers[node] = FlagCapturingTime; + + // FIXME: node names not localized + if (teamIndex == TeamId.Alliance) + SendMessage2ToAll(CypherStrings.BgAbNodeAssaulted, ChatMsg.BgSystemAlliance, source, _GetNodeNameId(node)); + else + SendMessage2ToAll(CypherStrings.BgAbNodeAssaulted, ChatMsg.BgSystemHorde, source, _GetNodeNameId(node)); + } + // If contested, change back to occupied + else + { + UpdatePlayerScore(source, ScoreType.BasesDefended, 1); + m_prevNodes[node] = m_Nodes[node]; + m_Nodes[node] = (NodeStatus.Occupied + (int)teamIndex); + // burn current contested banner + _DelBanner(node, NodeStatus.Contested, (byte)teamIndex); + // create new occupied banner + _CreateBanner(node, NodeStatus.Occupied, (byte)teamIndex, true); + _SendNodeUpdate(node); + m_NodeTimers[node] = 0; + _NodeOccupied(node, (teamIndex == TeamId.Alliance) ? Team.Alliance : Team.Horde); + + // FIXME: node names not localized + if (teamIndex == TeamId.Alliance) + SendMessage2ToAll(CypherStrings.BgAbNodeDefended, ChatMsg.BgSystemAlliance, source, _GetNodeNameId(node)); + else + SendMessage2ToAll(CypherStrings.BgAbNodeDefended, ChatMsg.BgSystemHorde, source, _GetNodeNameId(node)); + } + sound = (teamIndex == TeamId.Alliance) ? SoundAssaultedAlliance : SoundAssaultedHorde; + } + // If node is occupied, change to enemy-contested + else + { + UpdatePlayerScore(source, ScoreType.BasesAssaulted, 1); + m_prevNodes[node] = m_Nodes[node]; + m_Nodes[node] = (NodeStatus.Contested + (int)teamIndex); + // burn current occupied banner + _DelBanner(node, NodeStatus.Occupied, (byte)teamIndex); + // create new contested banner + _CreateBanner(node, NodeStatus.Contested, (byte)teamIndex, true); + _SendNodeUpdate(node); + _NodeDeOccupied(node); + m_NodeTimers[node] = FlagCapturingTime; + + // FIXME: node names not localized + if (teamIndex == TeamId.Alliance) + SendMessage2ToAll(CypherStrings.BgAbNodeAssaulted, ChatMsg.BgSystemAlliance, source, _GetNodeNameId(node)); + else + SendMessage2ToAll(CypherStrings.BgAbNodeAssaulted, ChatMsg.BgSystemHorde, source, _GetNodeNameId(node)); + + sound = (teamIndex == TeamId.Alliance) ? SoundAssaultedAlliance : SoundAssaultedHorde; + } + + // If node is occupied again, send "X has taken the Y" msg. + if (m_Nodes[node] >= NodeStatus.Occupied) + { + // FIXME: team and node names not localized + if (teamIndex == TeamId.Alliance) + SendMessage2ToAll(CypherStrings.BgAbNodeTaken, ChatMsg.BgSystemAlliance, null, CypherStrings.BgAbAlly, _GetNodeNameId(node)); + else + SendMessage2ToAll(CypherStrings.BgAbNodeTaken, ChatMsg.BgSystemHorde, null, CypherStrings.BgAbHorde, _GetNodeNameId(node)); + } + PlaySoundToAll(sound); + } + + public override Team GetPrematureWinner() + { + // How many bases each team owns + byte ally = 0, horde = 0; + for (byte i = 0; i < BattlegroundNodes.DynamicNodesCount; ++i) + if (m_Nodes[i] == NodeStatus.AllyOccupied) + ++ally; + else if (m_Nodes[i] == NodeStatus.HordeOccupied) + ++horde; + + if (ally > horde) + return Team.Alliance; + else if (horde > ally) + return Team.Horde; + + // If the values are equal, fall back to the original result (based on number of players on each team) + return base.GetPrematureWinner(); + } + + public override bool SetupBattleground() + { + bool result = true; + for (int i = 0; i < BattlegroundNodes.DynamicNodesCount; ++i) + { + result &= AddObject(ObjectType.BannerNeutral + 8 * i, (uint)(NodeObjectId.Banner0 + i), NodePositions[i], 0, 0, (float)Math.Sin(NodePositions[i].GetOrientation() / 2), (float)Math.Cos(NodePositions[i].GetOrientation() / 2), BattlegroundConst.RespawnOneDay); + result &= AddObject(ObjectType.BannerContA + 8 * i, ObjectIds.BannerContA, NodePositions[i], 0, 0, (float)Math.Sin(NodePositions[i].GetOrientation() / 2), (float)Math.Cos(NodePositions[i].GetOrientation() / 2), BattlegroundConst.RespawnOneDay); + result &= AddObject(ObjectType.BannerContH + 8 * i, ObjectIds.BannerContH, NodePositions[i], 0, 0, (float)Math.Sin(NodePositions[i].GetOrientation() / 2), (float)Math.Cos(NodePositions[i].GetOrientation() / 2), BattlegroundConst.RespawnOneDay); + result &= AddObject(ObjectType.BannerAlly + 8 * i, ObjectIds.BannerA, NodePositions[i], 0, 0, (float)Math.Sin(NodePositions[i].GetOrientation() / 2), (float)Math.Cos(NodePositions[i].GetOrientation() / 2), BattlegroundConst.RespawnOneDay); + result &= AddObject(ObjectType.BannerHorde + 8 * i, ObjectIds.BannerH, NodePositions[i], 0, 0, (float)Math.Sin(NodePositions[i].GetOrientation() / 2), (float)Math.Cos(NodePositions[i].GetOrientation() / 2), BattlegroundConst.RespawnOneDay); + result &= AddObject(ObjectType.AuraAlly + 8 * i, ObjectIds.AuraA, NodePositions[i], 0, 0, (float)Math.Sin(NodePositions[i].GetOrientation() / 2), (float)Math.Cos(NodePositions[i].GetOrientation() / 2), BattlegroundConst.RespawnOneDay); + result &= AddObject(ObjectType.AuraHorde + 8 * i, ObjectIds.AuraH, NodePositions[i], 0, 0, (float)Math.Sin(NodePositions[i].GetOrientation() / 2), (float)Math.Cos(NodePositions[i].GetOrientation() / 2), BattlegroundConst.RespawnOneDay); + result &= AddObject(ObjectType.AuraContested + 8 * i, ObjectIds.AuraC, NodePositions[i], 0, 0, (float)Math.Sin(NodePositions[i].GetOrientation() / 2), (float)Math.Cos(NodePositions[i].GetOrientation() / 2), BattlegroundConst.RespawnOneDay); + if (!result) + { + Log.outError(LogFilter.Sql, "BatteGroundAB: Failed to spawn some object Battleground not created!"); + return false; + } + } + + result &= AddObject(ObjectType.GateA, ObjectIds.GateA, DoorPositions[0][0], DoorPositions[0][1], DoorPositions[0][2], DoorPositions[0][3], DoorPositions[0][4], DoorPositions[0][5], DoorPositions[0][6], DoorPositions[0][7], BattlegroundConst.RespawnImmediately); + result &= AddObject(ObjectType.GateH, ObjectIds.GateH, DoorPositions[1][0], DoorPositions[1][1], DoorPositions[1][2], DoorPositions[1][3], DoorPositions[1][4], DoorPositions[1][5], DoorPositions[1][6], DoorPositions[1][7], BattlegroundConst.RespawnImmediately); + if (!result) + { + Log.outError(LogFilter.Sql, "BatteGroundAB: Failed to spawn door object Battleground not created!"); + return false; + } + + //buffs + for (int i = 0; i < BattlegroundNodes.DynamicNodesCount; ++i) + { + result &= AddObject(ObjectType.SpeedbuffStables + 3 * i, Buff_Entries[0], BuffPositions[i][0], BuffPositions[i][1], BuffPositions[i][2], BuffPositions[i][3], 0, 0, (float)Math.Sin(BuffPositions[i][3] / 2), (float)Math.Cos(BuffPositions[i][3] / 2), BattlegroundConst.RespawnOneDay); + result &= AddObject(ObjectType.SpeedbuffStables + 3 * i + 1, Buff_Entries[1], BuffPositions[i][0], BuffPositions[i][1], BuffPositions[i][2], BuffPositions[i][3], 0, 0, (float)Math.Sin(BuffPositions[i][3] / 2), (float)Math.Cos(BuffPositions[i][3] / 2), BattlegroundConst.RespawnOneDay); + result &= AddObject(ObjectType.SpeedbuffStables + 3 * i + 2, Buff_Entries[2], BuffPositions[i][0], BuffPositions[i][1], BuffPositions[i][2], BuffPositions[i][3], 0, 0, (float)Math.Sin(BuffPositions[i][3] / 2), (float)Math.Cos(BuffPositions[i][3] / 2), BattlegroundConst.RespawnOneDay); + if (!result) + { + Log.outError(LogFilter.Sql, "BatteGroundAB: Failed to spawn buff object!"); + return false; + } + } + + return true; + } + + public override void Reset() + { + //call parent's class reset + base.Reset(); + + for (var i = 0; i < SharedConst.BGTeamsCount; ++i) + { + m_TeamScores[i] = 0; + m_lastTick[i] = 0; + m_HonorScoreTics[i] = 0; + m_ReputationScoreTics[i] = 0; + m_TeamScores500Disadvantage[i] = false; + } + + m_IsInformedNearVictory = false; + bool isBGWeekend = Global.BattlegroundMgr.IsBGWeekend(GetTypeID()); + m_HonorTics = (isBGWeekend) ? ABBGWeekendHonorTicks : NotABBGWeekendHonorTicks; + m_ReputationTics = (isBGWeekend) ? ABBGWeekendReputationTicks : NotABBGWeekendReputationTicks; + + for (byte i = 0; i < BattlegroundNodes.DynamicNodesCount; ++i) + { + m_Nodes[i] = 0; + m_prevNodes[i] = 0; + m_NodeTimers[i] = 0; + m_BannerTimers[i].timer = 0; + } + } + + public override void EndBattleground(Team winner) + { + // Win reward + if (winner == Team.Alliance) + RewardHonorToTeam(GetBonusHonorFromKill(1), Team.Alliance); + if (winner == Team.Horde) + RewardHonorToTeam(GetBonusHonorFromKill(1), Team.Horde); + // Complete map_end rewards (even if no team wins) + RewardHonorToTeam(GetBonusHonorFromKill(1), Team.Horde); + RewardHonorToTeam(GetBonusHonorFromKill(1), Team.Alliance); + + base.EndBattleground(winner); + } + + public override WorldSafeLocsRecord GetClosestGraveYard(Player player) + { + int teamIndex = GetTeamIndexByTeamId(player.GetTeam()); + + // Is there any occupied node for this team? + List nodes = new List(); + for (byte i = 0; i < BattlegroundNodes.DynamicNodesCount; ++i) + if (m_Nodes[i] == NodeStatus.Occupied + (int)teamIndex) + nodes.Add(i); + + WorldSafeLocsRecord good_entry = null; + // If so, select the closest node to place ghost on + if (!nodes.Empty()) + { + float plr_x = player.GetPositionX(); + float plr_y = player.GetPositionY(); + + float mindist = 999999.0f; + for (byte i = 0; i < nodes.Count; ++i) + { + WorldSafeLocsRecord entry = CliDB.WorldSafeLocsStorage.LookupByKey(GraveyardIds[nodes[i]]); + if (entry == null) + continue; + float dist = (entry.Loc.X - plr_x) * (entry.Loc.X - plr_x) + (entry.Loc.Y - plr_y) * (entry.Loc.Y - plr_y); + if (mindist > dist) + { + mindist = dist; + good_entry = entry; + } + } + nodes.Clear(); + } + // If not, place ghost on starting location + if (good_entry == null) + good_entry = CliDB.WorldSafeLocsStorage.LookupByKey(GraveyardIds[teamIndex + 5]); + + return good_entry; + } + + public override WorldSafeLocsRecord GetExploitTeleportLocation(Team team) + { + return CliDB.WorldSafeLocsStorage.LookupByKey(team == Team.Alliance ? ExploitTeleportLocationAlliance : ExploitTeleportLocationHorde); + } + + public override bool UpdatePlayerScore(Player player, ScoreType type, uint value, bool doAddHonor = true) + { + if (!base.UpdatePlayerScore(player, type, value, doAddHonor)) + return false; + + switch (type) + { + case ScoreType.BasesAssaulted: + player.UpdateCriteria(CriteriaTypes.BgObjectiveCapture, (uint)Objectives.AssaultBase); + break; + case ScoreType.BasesDefended: + player.UpdateCriteria(CriteriaTypes.BgObjectiveCapture, (uint)Objectives.DefendBase); + break; + default: + break; + } + return true; + } + + public override bool IsAllNodesControlledByTeam(Team team) + { + uint count = 0; + for (int i = 0; i < BattlegroundNodes.DynamicNodesCount; ++i) + if ((team == Team.Alliance && m_Nodes[i] == NodeStatus.AllyOccupied) || + (team == Team.Horde && m_Nodes[i] == NodeStatus.HordeOccupied)) + ++count; + + return count == BattlegroundNodes.DynamicNodesCount; + } + + public override bool CheckAchievementCriteriaMeet(uint criteriaId, Player player, Unit target, uint miscvalue) + { + switch ((BattlegroundCriteriaId)criteriaId) + { + case BattlegroundCriteriaId.ResilientVictory: + return m_TeamScores500Disadvantage[GetTeamIndexByTeamId(player.GetTeam())]; + } + + return base.CheckAchievementCriteriaMeet(criteriaId, player, target, miscvalue); + } + + /// + /// Nodes info: + /// 0: neutral + /// 1: ally contested + /// 2: horde contested + /// 3: ally occupied + /// 4: horde occupied + /// + NodeStatus[] m_Nodes = new NodeStatus[BattlegroundNodes.DynamicNodesCount]; + NodeStatus[] m_prevNodes = new NodeStatus[BattlegroundNodes.DynamicNodesCount]; + BannerTimer[] m_BannerTimers = new BannerTimer[BattlegroundNodes.DynamicNodesCount]; + uint[] m_NodeTimers = new uint[BattlegroundNodes.DynamicNodesCount]; + uint[] m_lastTick = new uint[SharedConst.BGTeamsCount]; + uint[] m_HonorScoreTics = new uint[SharedConst.BGTeamsCount]; + uint[] m_ReputationScoreTics = new uint[SharedConst.BGTeamsCount]; + bool m_IsInformedNearVictory; + uint m_HonorTics; + uint m_ReputationTics; + // need for achievements + bool[] m_TeamScores500Disadvantage = new bool[SharedConst.BGTeamsCount]; + + //Const + public const uint NotABBGWeekendHonorTicks = 260; + public const uint ABBGWeekendHonorTicks = 160; + public const uint NotABBGWeekendReputationTicks = 160; + public const uint ABBGWeekendReputationTicks = 120; + + public const int EventStartBattle = 9158;// Achievement: Let's Get This Done + + public const int SoundClaimed = 8192; + public const int SoundCapturedAlliance = 8173; + public const int SoundCapturedHorde = 8213; + public const uint SoundAssaultedAlliance = 8212; + public const uint SoundAssaultedHorde = 8174; + public const int SoundNearVictory = 8456; + + public const int FlagCapturingTime = 60000; + + public const int WarningNearVictoryScore = 1400; + public const int MaxTeamScore = 1600; + + public const int ExploitTeleportLocationAlliance = 3705; + public const int ExploitTeleportLocationHorde = 3706; + + public static Position[] NodePositions = + { + new Position(1166.785f, 1200.132f, -56.70859f, 0.9075713f), // stables + new Position(977.0156f, 1046.616f, -44.80923f, -2.600541f), // blacksmith + new Position(806.1821f, 874.2723f, -55.99371f, -2.303835f), // farm + new Position(856.1419f, 1148.902f, 11.18469f, -2.303835f), // lumber mill + new Position(1146.923f, 848.1782f, -110.917f, -0.7330382f) // gold mine + }; + + // x, y, z, o, rot0, rot1, rot2, rot3 + public static float[][] DoorPositions = + { + new float[] {1284.597f, 1281.167f, -15.97792f, 0.7068594f, 0.012957f, -0.060288f, 0.344959f, 0.93659f }, + new float[] {708.0903f, 708.4479f, -17.8342f, -2.391099f, 0.050291f, 0.015127f, 0.929217f, -0.365784f} + }; + + // Tick intervals and given points: case 0, 1, 2, 3, 4, 5 captured nodes + public static uint[] TickIntervals = { 0, 12000, 9000, 6000, 3000, 1000 }; + public static uint[] TickPoints = { 0, 10, 10, 10, 10, 30 }; + + // WorldSafeLocs ids for 5 nodes, and for ally, and horde starting location + public static uint[] GraveyardIds = { 895, 894, 893, 897, 896, 898, 899 }; + + // x, y, z, o + public static float[][] BuffPositions = + { + new float[] {1185.71f, 1185.24f, -56.36f, 2.56f }, // stables + new float[] {990.75f, 1008.18f, -42.60f, 2.43f }, // blacksmith + new float[] {817.66f, 843.34f, -56.54f, 3.01f }, // farm + new float[] {807.46f, 1189.16f, 11.92f, 5.44f }, // lumber mill + new float[] {1146.62f, 816.94f, -98.49f, 6.14f } // gold mine + }; + + public static Position[] SpiritGuidePos = + { + new Position(1200.03f, 1171.09f, -56.47f, 5.15f), // stables + new Position(1017.43f, 960.61f, -42.95f, 4.88f), // blacksmith + new Position(833.00f, 793.00f, -57.25f, 5.27f), // farm + new Position(775.17f, 1206.40f, 15.79f, 1.90f), // lumber mill + new Position(1207.48f, 787.00f, -83.36f, 5.51f), // gold mine + new Position(1354.05f, 1275.48f, -11.30f, 4.77f), // alliance starting base + new Position(714.61f, 646.15f, -10.87f, 4.34f) // horde starting base + }; + + public static uint[] NodeStates = { 1767, 1782, 1772, 1792, 1787 }; + + public static uint[] NodeIcons = { 1842, 1846, 1845, 1844, 1843 }; + } + + class BattlegroundABScore : BattlegroundScore + { + public BattlegroundABScore(ObjectGuid playerGuid, Team team) : base(playerGuid, team) + { + BasesAssaulted = 0; + BasesDefended = 0; + } + + public override void UpdateScore(ScoreType type, uint value) + { + switch (type) + { + case ScoreType.BasesAssaulted: + BasesAssaulted += value; + break; + case ScoreType.BasesDefended: + BasesDefended += value; + break; + default: + base.UpdateScore(type, value); + break; + } + } + + public override void BuildObjectivesBlock(List stats) + { + stats.Add((int)BasesAssaulted); + stats.Add((int)BasesDefended); + } + + public override uint GetAttr1() { return BasesAssaulted; } + public override uint GetAttr2() { return BasesDefended; } + + uint BasesAssaulted; + uint BasesDefended; + } + + struct BannerTimer + { + public uint timer; + public byte type; + public byte teamIndex; + } + + #region Consts + struct WorldStates + { + public const uint OccupiedBasesHorde = 1778; + public const uint OccupiedBasesAlly = 1779; + public const uint ResourcesAlly = 1776; + public const uint ResourcesHorde = 1777; + public const uint ResourcesMax = 1780; + public const uint ResourcesWarning = 1955; + /* + public const uint StableIcon = 1842; //Stable Map Icon (None) + public const uint StableStateAlience = 1767; //Stable Map State (Alience) + public const uint StableStateHorde = 1768; //Stable Map State (Horde) + public const uint StableStateConAli = 1769; //Stable Map State (Con Alience) + public const uint StableStateConHor = 1770; //Stable Map State (Con Horde) + public const uint FarmIcon = 1845; //Farm Map Icon (None) + public const uint FarmStateAlience = 1772; //Farm State (Alience) + public const uint FarmStateHorde = 1773; //Farm State (Horde) + public const uint FarmStateConAli = 1774; //Farm State (Con Alience) + public const uint FarmStateConHor = 1775; //Farm State (Con Horde) + + public const uint BlacksmithIcon = 1846; //Blacksmith Map Icon (None) + public const uint BlacksmithStateAlience = 1782; //Blacksmith Map State (Alience) + public const uint BlacksmithStateHorde = 1783; //Blacksmith Map State (Horde) + public const uint BlacksmithStateConAli = 1784; //Blacksmith Map State (Con Alience) + public const uint BlacksmithStateConHor = 1785; //Blacksmith Map State (Con Horde) + public const uint LumbermillIcon = 1844; //Lumber Mill Map Icon (None) + public const uint LumbermillStateAlience = 1792; //Lumber Mill Map State (Alience) + public const uint LumbermillStateHorde = 1793; //Lumber Mill Map State (Horde) + public const uint LumbermillStateConAli = 1794; //Lumber Mill Map State (Con Alience) + public const uint LumbermillStateConHor = 1795; //Lumber Mill Map State (Con Horde) + public const uint GoldmineIcon = 1843; //Gold Mine Map Icon (None) + public const uint GoldmineStateAlience = 1787; //Gold Mine Map State (Alience) + public const uint GoldmineStateHorde = 1788; //Gold Mine Map State (Horde) + public const uint GoldmineStateConAli = 1789; //Gold Mine Map State (Con Alience + public const uint GoldmineStateConHor = 1790; //Gold Mine Map State (Con Horde) + */ + } + + // Note: code uses that these IDs follow each other + struct NodeObjectId + { + public const uint Banner0 = 180087; // Stables Banner + public const uint Banner1 = 180088; // Blacksmith Banner + public const uint Banner2 = 180089; // Farm Banner + public const uint Banner3 = 180090; // Lumber Mill Banner + public const uint Banner4 = 180091; // Gold Mine Banner + } + + struct ObjectType + { + // for all 5 node points 8*5=40 objects + public const int BannerNeutral = 0; + public const int BannerContA = 1; + public const int BannerContH = 2; + public const int BannerAlly = 3; + public const int BannerHorde = 4; + public const int AuraAlly = 5; + public const int AuraHorde = 6; + public const int AuraContested = 7; + //Gates + public const int GateA = 40; + public const int GateH = 41; + //Buffs + public const int SpeedbuffStables = 42; + public const int RegenbuffStables = 43; + public const int BerserkbuffStables = 44; + public const int SpeedbuffBlacksmith = 45; + public const int RegenbuffBlacksmith = 46; + public const int BerserkbuffBlacksmith = 47; + public const int SpeedbuffFarm = 48; + public const int RegenbuffFarm = 49; + public const int BerserkbuffFarm = 50; + public const int SpeedbuffLumberMill = 51; + public const int RegenbuffLumberMill = 52; + public const int BerserkbuffLumberMill = 53; + public const int SpeedbuffGoldMine = 54; + public const int RegenbuffGoldMine = 55; + public const int BerserkbuffGoldMine = 56; + public const int Max = 57; + } + + // Object id templates from DB + struct ObjectIds + { + public const uint BannerA = 180058; + public const uint BannerContA = 180059; + public const uint BannerH = 180060; + public const uint BannerContH = 180061; + + public const uint AuraA = 180100; + public const uint AuraH = 180101; + public const uint AuraC = 180102; + + public const uint GateA = 180255; + public const uint GateH = 180256; + } + + struct BattlegroundNodes + { + public const int NodeStables = 0; + public const int NodeBlacksmith = 1; + public const int NodeFarm = 2; + public const int NodeLumberMill = 3; + public const int NodeGoldMine = 4; + + public const int DynamicNodesCount = 5; // Dynamic Nodes That Can Be Captured + + public const int SpiritAliance = 5; + public const int SpiritHorde = 6; + + public const int AllNodesCount = 7; // All Nodes (Dynamic And Static) + } + + enum NodeStatus + { + Neutral = 0, + Contested = 1, + AllyContested = 1, + HordeContested = 2, + Occupied = 3, + AllyOccupied = 3, + HordeOccupied = 4 + } + + enum Objectives + { + AssaultBase = 122, + DefendBase = 123 + } + #endregion +} diff --git a/Game/BattleGrounds/Zones/BattleforGilneas.cs b/Game/BattleGrounds/Zones/BattleforGilneas.cs new file mode 100644 index 000000000..7089dea27 --- /dev/null +++ b/Game/BattleGrounds/Zones/BattleforGilneas.cs @@ -0,0 +1,23 @@ +/* + * Copyright (C) 2012-2017 CypherCore + * + * 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, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +namespace Game.BattleGrounds.Zones +{ + class BattleforGilneas + { + } +} diff --git a/Game/BattleGrounds/Zones/DeepwindGorge.cs b/Game/BattleGrounds/Zones/DeepwindGorge.cs new file mode 100644 index 000000000..2cc8ed20f --- /dev/null +++ b/Game/BattleGrounds/Zones/DeepwindGorge.cs @@ -0,0 +1,23 @@ +/* + * Copyright (C) 2012-2017 CypherCore + * + * 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, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +namespace Game.BattleGrounds.Zones +{ + class DeepwindGorge + { + } +} diff --git a/Game/BattleGrounds/Zones/EyeofStorm.cs b/Game/BattleGrounds/Zones/EyeofStorm.cs new file mode 100644 index 000000000..bc4ee9188 --- /dev/null +++ b/Game/BattleGrounds/Zones/EyeofStorm.cs @@ -0,0 +1,1341 @@ +/* + * Copyright (C) 2012-2016 CypherCore + * + * 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, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +using Game.Entities; +using Framework.Constants; +using System.Collections.Generic; +using Game.Network.Packets; +using Game.DataStorage; + +namespace Game.BattleGrounds +{ + class BgEyeofStorm : Battleground + { + public BgEyeofStorm() + { + m_BuffChange = true; + m_Points_Trigger[EotSPoints.FelReaver] = EotSPointsTrigger.FelReaverBuff; + m_Points_Trigger[EotSPoints.BloodElf] = EotSPointsTrigger.BloodElfBuff; + m_Points_Trigger[EotSPoints.DraeneiRuins] = EotSPointsTrigger.DraeneiRuinsBuff; + m_Points_Trigger[EotSPoints.MageTower] = EotSPointsTrigger.MageTowerBuff; + m_HonorScoreTics[TeamId.Alliance] = 0; + m_HonorScoreTics[TeamId.Horde] = 0; + m_TeamPointsCount[TeamId.Alliance] = 0; + m_TeamPointsCount[TeamId.Horde] = 0; + m_FlagKeeper.Clear(); + m_DroppedFlagGUID.Clear(); + m_FlagCapturedBgObjectType = 0; + m_FlagState = EotSFlagState.OnBase; + m_FlagsTimer = 0; + m_TowerCapCheckTimer = 0; + m_PointAddingTimer = 0; + m_HonorTics = 0; + + for (byte i = 0; i < EotSPoints.PointsMax; ++i) + { + m_PointOwnedByTeam[i] = Team.Other; + m_PointState[i] = EotSPointState.Uncontrolled; + m_PointBarStatus[i] = EotSProgressBarConsts.ProgressBarStateMiddle; + } + + for (byte i = 0; i < EotSPoints.PointsMax + 1; ++i) + m_PlayersNearPoint[i] = new List(); + + for (byte i = 0; i < 2 * EotSPoints.PointsMax; ++i) + m_CurrentPointPlayersCount[i] = 0; + + StartMessageIds[BattlegroundConst.EventIdFirst] = CypherStrings.BgEyStartTwoMinutes; + StartMessageIds[BattlegroundConst.EventIdSecond] = CypherStrings.BgEyStartOneMinute; + StartMessageIds[BattlegroundConst.EventIdThird] = CypherStrings.BgEyStartHalfMinute; + StartMessageIds[BattlegroundConst.EventIdFourth] = CypherStrings.BgEyHasBegun; + } + + public override void PostUpdateImpl(uint diff) + { + if (GetStatus() == BattlegroundStatus.InProgress) + { + m_PointAddingTimer -= (int)diff; + if (m_PointAddingTimer <= 0) + { + m_PointAddingTimer = EotSMisc.FPointsTickTime; + if (m_TeamPointsCount[TeamId.Alliance] > 0) + AddPoints(Team.Alliance, EotSMisc.TickPoints[m_TeamPointsCount[TeamId.Alliance] - 1]); + if (m_TeamPointsCount[TeamId.Horde] > 0) + AddPoints(Team.Horde, EotSMisc.TickPoints[m_TeamPointsCount[TeamId.Horde] - 1]); + } + + if (m_FlagState == EotSFlagState.WaitRespawn || m_FlagState == EotSFlagState.OnGround) + { + m_FlagsTimer -= (int)diff; + + if (m_FlagsTimer < 0) + { + m_FlagsTimer = 0; + if (m_FlagState == EotSFlagState.WaitRespawn) + RespawnFlag(true); + else + RespawnFlagAfterDrop(); + } + } + + m_TowerCapCheckTimer -= (int)diff; + if (m_TowerCapCheckTimer <= 0) + { + //check if player joined point + /*I used this order of calls, because although we will check if one player is in gameobject's distance 2 times + but we can count of players on current point in CheckSomeoneLeftPoint + */ + CheckSomeoneJoinedPo(); + //check if player left point + CheckSomeoneLeftPo(); + UpdatePointStatuses(); + m_TowerCapCheckTimer = EotSMisc.FPointsTickTime; + } + } + } + + public override void GetPlayerPositionData(List positions) + { + Player player = Global.ObjAccessor.GetPlayer(GetBgMap(), m_FlagKeeper); + if (player) + { + BattlegroundPlayerPosition position = new BattlegroundPlayerPosition(); + position.Guid = player.GetGUID(); + position.Pos.X = player.GetPositionX(); + position.Pos.Y = player.GetPositionY(); + position.IconID = player.GetTeam() == Team.Alliance ? BattlegroundConst.PlayerPositionIconAllianceFlag : BattlegroundConst.PlayerPositionIconHordeFlag; + position.ArenaSlot = BattlegroundConst.PlayerPositionArenaSlotNone; + positions.Add(position); + } + } + + public override void StartingEventCloseDoors() + { + SpawnBGObject(EotSObjectTypes.DoorA, BattlegroundConst.RespawnImmediately); + SpawnBGObject(EotSObjectTypes.DoorH, BattlegroundConst.RespawnImmediately); + + for (int i = (int)EotSObjectTypes.ABannerFelReaverCenter; i < EotSObjectTypes.Max; ++i) + SpawnBGObject(i, BattlegroundConst.RespawnOneDay); + } + + public override void StartingEventOpenDoors() + { + SpawnBGObject(EotSObjectTypes.DoorA, BattlegroundConst.RespawnOneDay); + SpawnBGObject(EotSObjectTypes.DoorH, BattlegroundConst.RespawnOneDay); + + for (int i = EotSObjectTypes.NBannerFelReaverLeft; i <= EotSObjectTypes.FlagNetherstorm; ++i) + SpawnBGObject(i, BattlegroundConst.RespawnImmediately); + + for (int i = 0; i < EotSPoints.PointsMax; ++i) + { + //randomly spawn buff + byte buff = (byte)RandomHelper.URand(0, 2); + SpawnBGObject(EotSObjectTypes.SpeedbuffFelReaver + buff + i * 3, BattlegroundConst.RespawnImmediately); + } + + // Achievement: Flurry + StartCriteriaTimer(CriteriaTimedTypes.Event, EotSMisc.EventStartBattle); + } + + void AddPoints(Team Team, uint Points) + { + int team_index = GetTeamIndexByTeamId(Team); + m_TeamScores[team_index] += Points; + m_HonorScoreTics[team_index] += Points; + if (m_HonorScoreTics[team_index] >= m_HonorTics) + { + RewardHonorToTeam(GetBonusHonorFromKill(1), Team); + m_HonorScoreTics[team_index] -= m_HonorTics; + } + UpdateTeamScore(team_index); + } + + void CheckSomeoneJoinedPo() + { + GameObject obj = null; + for (byte i = 0; i < EotSPoints.PointsMax; ++i) + { + obj = GetBgMap().GetGameObject(BgObjects[EotSObjectTypes.TowerCapFelReaver + i]); + if (obj) + { + byte j = 0; + while (j < m_PlayersNearPoint[EotSPoints.PointsMax].Count) + { + Player player = Global.ObjAccessor.FindPlayer(m_PlayersNearPoint[EotSPoints.PointsMax][j]); + if (!player) + { + Log.outError(LogFilter.Battleground, "BattlegroundEY:CheckSomeoneJoinedPoint: Player ({0}) could not be found!", m_PlayersNearPoint[EotSPoints.PointsMax][j].ToString()); + ++j; + continue; + } + if (player.CanCaptureTowerPoint() && player.IsWithinDistInMap(obj, (float)EotSProgressBarConsts.PointRadius)) + { + //player joined point! + //show progress bar + player.SendUpdateWorldState(EotSWorldStateIds.ProgressBarPercentGrey, (uint)EotSProgressBarConsts.ProgressBarPercentGrey); + player.SendUpdateWorldState(EotSWorldStateIds.ProgressBarStatus, (uint)m_PointBarStatus[i]); + player.SendUpdateWorldState(EotSWorldStateIds.ProgressBarShow, (uint)EotSProgressBarConsts.ProgressBarShow); + //add player to point + m_PlayersNearPoint[i].Add(m_PlayersNearPoint[EotSPoints.PointsMax][j]); + //remove player from "free space" + m_PlayersNearPoint[EotSPoints.PointsMax].RemoveAt(j); + } + else + ++j; + } + } + } + } + + void CheckSomeoneLeftPo() + { + //reset current point counts + for (byte i = 0; i < 2 * EotSPoints.PointsMax; ++i) + m_CurrentPointPlayersCount[i] = 0; + GameObject obj = null; + for (byte i = 0; i < EotSPoints.PointsMax; ++i) + { + obj = GetBgMap().GetGameObject(BgObjects[EotSObjectTypes.TowerCapFelReaver + i]); + if (obj) + { + byte j = 0; + while (j < m_PlayersNearPoint[i].Count) + { + Player player = Global.ObjAccessor.FindPlayer(m_PlayersNearPoint[i][j]); + if (!player) + { + Log.outError(LogFilter.Battleground, "BattlegroundEY:CheckSomeoneLeftPoint Player ({0}) could not be found!", m_PlayersNearPoint[i][j].ToString()); + //move non-existing players to "free space" - this will cause many errors showing in log, but it is a very important bug + m_PlayersNearPoint[EotSPoints.PointsMax].Add(m_PlayersNearPoint[i][j]); + m_PlayersNearPoint[i].RemoveAt(j); + continue; + } + if (!player.CanCaptureTowerPoint() || !player.IsWithinDistInMap(obj, (float)EotSProgressBarConsts.PointRadius)) + //move player out of point (add him to players that are out of points + { + m_PlayersNearPoint[EotSPoints.PointsMax].Add(m_PlayersNearPoint[i][j]); + m_PlayersNearPoint[i].RemoveAt(j); + player.SendUpdateWorldState(EotSWorldStateIds.ProgressBarShow, (uint)EotSProgressBarConsts.ProgressBarDontShow); + } + else + { + //player is neat flag, so update count: + m_CurrentPointPlayersCount[2 * i + GetTeamIndexByTeamId(player.GetTeam())]++; + ++j; + } + } + } + } + } + + void UpdatePointStatuses() + { + for (byte point = 0; point < EotSPoints.PointsMax; ++point) + { + if (m_PlayersNearPoint[point].Empty()) + continue; + //count new point bar status: + m_PointBarStatus[point] += (m_CurrentPointPlayersCount[2 * point] - m_CurrentPointPlayersCount[2 * point + 1] < (int)EotSProgressBarConsts.PointMaxCapturersCount) ? m_CurrentPointPlayersCount[2 * point] - m_CurrentPointPlayersCount[2 * point + 1] : (int)EotSProgressBarConsts.PointMaxCapturersCount; + + if (m_PointBarStatus[point] > EotSProgressBarConsts.ProgressBarAliControlled) + //point is fully alliance's + m_PointBarStatus[point] = EotSProgressBarConsts.ProgressBarAliControlled; + if (m_PointBarStatus[point] < EotSProgressBarConsts.ProgressBarHordeControlled) + //point is fully horde's + m_PointBarStatus[point] = EotSProgressBarConsts.ProgressBarHordeControlled; + + uint pointOwnerTeamId = 0; + //find which team should own this point + if (m_PointBarStatus[point] <= EotSProgressBarConsts.ProgressBarNeutralLow) + pointOwnerTeamId = (uint)Team.Horde; + else if (m_PointBarStatus[point] >= EotSProgressBarConsts.ProgressBarNeutralHigh) + pointOwnerTeamId = (uint)Team.Alliance; + else + pointOwnerTeamId = (uint)Team.Other; + + for (byte i = 0; i < m_PlayersNearPoint[point].Count; ++i) + { + Player player = Global.ObjAccessor.FindPlayer(m_PlayersNearPoint[point][i]); + if (player) + { + player.SendUpdateWorldState(EotSWorldStateIds.ProgressBarStatus, (uint)m_PointBarStatus[point]); + //if point owner changed we must evoke event! + if (pointOwnerTeamId != (uint)m_PointOwnedByTeam[point]) + { + //point was uncontrolled and player is from team which captured point + if (m_PointState[point] == EotSPointState.Uncontrolled && (uint)player.GetTeam() == pointOwnerTeamId) + EventTeamCapturedPo(player, point); + + //point was under control and player isn't from team which controlled it + if (m_PointState[point] == EotSPointState.UnderControl && player.GetTeam() != m_PointOwnedByTeam[point]) + EventTeamLostPo(player, point); + } + + /// @workaround The original AreaTrigger is covered by a bigger one and not triggered on client side. + if (point == EotSPoints.FelReaver && m_PointOwnedByTeam[point] == player.GetTeam()) + if (m_FlagState != 0 && GetFlagPickerGUID() == player.GetGUID()) + if (player.GetDistance(2044.0f, 1729.729f, 1190.03f) < 3.0f) + EventPlayerCapturedFlag(player, EotSObjectTypes.FlagFelReaver); + } + } + } + } + + void UpdateTeamScore(int team) + { + uint score = GetTeamScore(team); + /// @todo there should be some sound played when one team is near victory!! - and define variables + /*if (!m_IsInformedNearVictory && score >= BG_EY_WARNING_NEAR_VICTORY_SCORE) + { + if (Team == ALLIANCE) + SendMessageToAll(LANG_BG_EY_A_NEAR_VICTORY, CHAT_MSG_BG_SYSTEM_NEUTRAL); + else + SendMessageToAll(LANG_BG_EY_H_NEAR_VICTORY, CHAT_MSG_BG_SYSTEM_NEUTRAL); + PlaySoundToAll(BG_EY_SOUND_NEAR_VICTORY); + m_IsInformedNearVictory = true; + }*/ + + if (score >= EotSScoreIds.MaxTeamScore) + { + score = EotSScoreIds.MaxTeamScore; + if (team == TeamId.Alliance) + EndBattleground(Team.Alliance); + else + EndBattleground(Team.Horde); + } + + if (team == TeamId.Alliance) + UpdateWorldState(EotSWorldStateIds.AllianceResources, score); + else + UpdateWorldState(EotSWorldStateIds.HordeResources, score); + } + + public override void EndBattleground(Team winner) + { + // Win reward + if (winner == Team.Alliance) + RewardHonorToTeam(GetBonusHonorFromKill(1), Team.Alliance); + if (winner == Team.Horde) + RewardHonorToTeam(GetBonusHonorFromKill(1), Team.Horde); + // Complete map reward + RewardHonorToTeam(GetBonusHonorFromKill(1), Team.Alliance); + RewardHonorToTeam(GetBonusHonorFromKill(1), Team.Horde); + + base.EndBattleground(winner); + } + + void UpdatePointsCount(Team team) + { + if (team == Team.Alliance) + UpdateWorldState(EotSWorldStateIds.AllianceBase, m_TeamPointsCount[TeamId.Alliance]); + else + UpdateWorldState(EotSWorldStateIds.HordeBase, m_TeamPointsCount[TeamId.Horde]); + } + + void UpdatePointsIcons(Team team, int Point) + { + //we MUST firstly send 0, after that we can send 1!!! + if (m_PointState[Point] == EotSPointState.UnderControl) + { + UpdateWorldState(EotSMisc.m_PointsIconStruct[Point].WorldStateControlIndex, 0); + if (team == Team.Alliance) + UpdateWorldState(EotSMisc.m_PointsIconStruct[Point].WorldStateAllianceControlledIndex, 1); + else + UpdateWorldState(EotSMisc.m_PointsIconStruct[Point].WorldStateHordeControlledIndex, 1); + } + else + { + if (team == Team.Alliance) + UpdateWorldState(EotSMisc.m_PointsIconStruct[Point].WorldStateAllianceControlledIndex, 0); + else + UpdateWorldState(EotSMisc.m_PointsIconStruct[Point].WorldStateHordeControlledIndex, 0); + UpdateWorldState(EotSMisc.m_PointsIconStruct[Point].WorldStateControlIndex, 1); + } + } + + public override void AddPlayer(Player player) + { + base.AddPlayer(player); + PlayerScores[player.GetGUID()] = new BgEyeOfStormScore(player.GetGUID(), player.GetBGTeam()); + + m_PlayersNearPoint[EotSPoints.PointsMax].Add(player.GetGUID()); + } + + public override void RemovePlayer(Player player, ObjectGuid guid, Team team) + { + // sometimes flag aura not removed :( + for (int j = EotSPoints.PointsMax; j >= 0; --j) + { + for (int i = 0; i < m_PlayersNearPoint[j].Count; ++i) + if (m_PlayersNearPoint[j][i] == guid) + m_PlayersNearPoint[j].RemoveAt(i); + } + if (IsFlagPickedup()) + { + if (m_FlagKeeper == guid) + { + if (player) + EventPlayerDroppedFlag(player); + else + { + SetFlagPicker(ObjectGuid.Empty); + RespawnFlag(true); + } + } + } + } + + public override void HandleAreaTrigger(Player player, uint trigger, bool entered) + { + if (!player.IsAlive()) //hack code, must be removed later + return; + + switch (trigger) + { + case 4530: // Horde Start + case 4531: // Alliance Start + if (GetStatus() == BattlegroundStatus.WaitJoin && !entered) + TeleportPlayerToExploitLocation(player); + break; + case EotSPointsTrigger.BloodElfPoint: + if (m_PointState[EotSPoints.BloodElf] == EotSPointState.UnderControl && m_PointOwnedByTeam[EotSPoints.BloodElf] == player.GetTeam()) + if (m_FlagState != 0 && GetFlagPickerGUID() == player.GetGUID()) + EventPlayerCapturedFlag(player, EotSObjectTypes.FlagBloodElf); + break; + case EotSPointsTrigger.FelReaverPoint: + if (m_PointState[EotSPoints.FelReaver] == EotSPointState.UnderControl && m_PointOwnedByTeam[EotSPoints.FelReaver] == player.GetTeam()) + if (m_FlagState != 0 && GetFlagPickerGUID() == player.GetGUID()) + EventPlayerCapturedFlag(player, EotSObjectTypes.FlagFelReaver); + break; + case EotSPointsTrigger.MageTowerPoint: + if (m_PointState[EotSPoints.MageTower] == EotSPointState.UnderControl && m_PointOwnedByTeam[EotSPoints.MageTower] == player.GetTeam()) + if (m_FlagState != 0 && GetFlagPickerGUID() == player.GetGUID()) + EventPlayerCapturedFlag(player, EotSObjectTypes.FlagMageTower); + break; + case EotSPointsTrigger.DraeneiRuinsPoint: + if (m_PointState[EotSPoints.DraeneiRuins] == EotSPointState.UnderControl && m_PointOwnedByTeam[EotSPoints.DraeneiRuins] == player.GetTeam()) + if (m_FlagState != 0 && GetFlagPickerGUID() == player.GetGUID()) + EventPlayerCapturedFlag(player, EotSObjectTypes.FlagDraeneiRuins); + break; + case 4512: + case 4515: + case 4517: + case 4519: + case 4568: + case 4569: + case 4570: + case 4571: + case 5866: + break; + default: + base.HandleAreaTrigger(player, trigger, entered); + break; + } + } + + public override bool SetupBattleground() + { + // doors + if (!AddObject(EotSObjectTypes.DoorA, EotSObjectIds.ADoor, 2527.6f, 1596.91f, 1262.13f, -3.12414f, -0.173642f, -0.001515f, 0.98477f, -0.008594f, BattlegroundConst.RespawnImmediately) + || !AddObject(EotSObjectTypes.DoorH, EotSObjectIds.HDoor, 1803.21f, 1539.49f, 1261.09f, 3.14159f, 0.173648f, 0, 0.984808f, 0, BattlegroundConst.RespawnImmediately) + // banners (alliance) + || !AddObject(EotSObjectTypes.ABannerFelReaverCenter, EotSObjectIds.ABanner, 2057.46f, 1735.07f, 1187.91f, -0.925024f, 0, 0, 0.446198f, -0.894934f, BattlegroundConst.RespawnOneDay) + || !AddObject(EotSObjectTypes.ABannerFelReaverLeft, EotSObjectIds.ABanner, 2032.25f, 1729.53f, 1190.33f, 1.8675f, 0, 0, 0.803857f, 0.594823f, BattlegroundConst.RespawnOneDay) + || !AddObject(EotSObjectTypes.ABannerFelReaverRight, EotSObjectIds.ABanner, 2092.35f, 1775.46f, 1187.08f, -0.401426f, 0, 0, 0.199368f, -0.979925f, BattlegroundConst.RespawnOneDay) + || !AddObject(EotSObjectTypes.ABannerBloodElfCenter, EotSObjectIds.ABanner, 2047.19f, 1349.19f, 1189.0f, -1.62316f, 0, 0, 0.725374f, -0.688354f, BattlegroundConst.RespawnOneDay) + || !AddObject(EotSObjectTypes.ABannerBloodElfLeft, EotSObjectIds.ABanner, 2074.32f, 1385.78f, 1194.72f, 0.488692f, 0, 0, 0.241922f, 0.970296f, BattlegroundConst.RespawnOneDay) + || !AddObject(EotSObjectTypes.ABannerBloodElfRight, EotSObjectIds.ABanner, 2025.13f, 1386.12f, 1192.74f, 2.3911f, 0, 0, 0.930418f, 0.366501f, BattlegroundConst.RespawnOneDay) + || !AddObject(EotSObjectTypes.ABannerDraeneiRuinsCenter, EotSObjectIds.ABanner, 2276.8f, 1400.41f, 1196.33f, 2.44346f, 0, 0, 0.939693f, 0.34202f, BattlegroundConst.RespawnOneDay) + || !AddObject(EotSObjectTypes.ABannerDraeneiRuinsLeft, EotSObjectIds.ABanner, 2305.78f, 1404.56f, 1199.38f, 1.74533f, 0, 0, 0.766044f, 0.642788f, BattlegroundConst.RespawnOneDay) + || !AddObject(EotSObjectTypes.ABannerDraeneiRuinsRight, EotSObjectIds.ABanner, 2245.4f, 1366.41f, 1195.28f, 2.21657f, 0, 0, 0.894934f, 0.446198f, BattlegroundConst.RespawnOneDay) + || !AddObject(EotSObjectTypes.ABannerMageTowerCenter, EotSObjectIds.ABanner, 2270.84f, 1784.08f, 1186.76f, 2.42601f, 0, 0, 0.936672f, 0.350207f, BattlegroundConst.RespawnOneDay) + || !AddObject(EotSObjectTypes.ABannerMageTowerLeft, EotSObjectIds.ABanner, 2269.13f, 1737.7f, 1186.66f, 0.994838f, 0, 0, 0.477159f, 0.878817f, BattlegroundConst.RespawnOneDay) + || !AddObject(EotSObjectTypes.ABannerMageTowerRight, EotSObjectIds.ABanner, 2300.86f, 1741.25f, 1187.7f, -0.785398f, 0, 0, 0.382683f, -0.92388f, BattlegroundConst.RespawnOneDay) + // banners (horde) + || !AddObject(EotSObjectTypes.HBannerFelReaverCenter, EotSObjectIds.HBanner, 2057.46f, 1735.07f, 1187.91f, -0.925024f, 0, 0, 0.446198f, -0.894934f, BattlegroundConst.RespawnOneDay) + || !AddObject(EotSObjectTypes.HBannerFelReaverLeft, EotSObjectIds.HBanner, 2032.25f, 1729.53f, 1190.33f, 1.8675f, 0, 0, 0.803857f, 0.594823f, BattlegroundConst.RespawnOneDay) + || !AddObject(EotSObjectTypes.HBannerFelReaverRight, EotSObjectIds.HBanner, 2092.35f, 1775.46f, 1187.08f, -0.401426f, 0, 0, 0.199368f, -0.979925f, BattlegroundConst.RespawnOneDay) + || !AddObject(EotSObjectTypes.HBannerBloodElfCenter, EotSObjectIds.HBanner, 2047.19f, 1349.19f, 1189.0f, -1.62316f, 0, 0, 0.725374f, -0.688354f, BattlegroundConst.RespawnOneDay) + || !AddObject(EotSObjectTypes.HBannerBloodElfLeft, EotSObjectIds.HBanner, 2074.32f, 1385.78f, 1194.72f, 0.488692f, 0, 0, 0.241922f, 0.970296f, BattlegroundConst.RespawnOneDay) + || !AddObject(EotSObjectTypes.HBannerBloodElfRight, EotSObjectIds.HBanner, 2025.13f, 1386.12f, 1192.74f, 2.3911f, 0, 0, 0.930418f, 0.366501f, BattlegroundConst.RespawnOneDay) + || !AddObject(EotSObjectTypes.HBannerDraeneiRuinsCenter, EotSObjectIds.HBanner, 2276.8f, 1400.41f, 1196.33f, 2.44346f, 0, 0, 0.939693f, 0.34202f, BattlegroundConst.RespawnOneDay) + || !AddObject(EotSObjectTypes.HBannerDraeneiRuinsLeft, EotSObjectIds.HBanner, 2305.78f, 1404.56f, 1199.38f, 1.74533f, 0, 0, 0.766044f, 0.642788f, BattlegroundConst.RespawnOneDay) + || !AddObject(EotSObjectTypes.HBannerDraeneiRuinsRight, EotSObjectIds.HBanner, 2245.4f, 1366.41f, 1195.28f, 2.21657f, 0, 0, 0.894934f, 0.446198f, BattlegroundConst.RespawnOneDay) + || !AddObject(EotSObjectTypes.HBannerMageTowerCenter, EotSObjectIds.HBanner, 2270.84f, 1784.08f, 1186.76f, 2.42601f, 0, 0, 0.936672f, 0.350207f, BattlegroundConst.RespawnOneDay) + || !AddObject(EotSObjectTypes.HBannerMageTowerLeft, EotSObjectIds.HBanner, 2269.13f, 1737.7f, 1186.66f, 0.994838f, 0, 0, 0.477159f, 0.878817f, BattlegroundConst.RespawnOneDay) + || !AddObject(EotSObjectTypes.HBannerMageTowerRight, EotSObjectIds.HBanner, 2300.86f, 1741.25f, 1187.7f, -0.785398f, 0, 0, 0.382683f, -0.92388f, BattlegroundConst.RespawnOneDay) + // banners (natural) + || !AddObject(EotSObjectTypes.NBannerFelReaverCenter, EotSObjectIds.NBanner, 2057.46f, 1735.07f, 1187.91f, -0.925024f, 0, 0, 0.446198f, -0.894934f, BattlegroundConst.RespawnOneDay) + || !AddObject(EotSObjectTypes.NBannerFelReaverLeft, EotSObjectIds.NBanner, 2032.25f, 1729.53f, 1190.33f, 1.8675f, 0, 0, 0.803857f, 0.594823f, BattlegroundConst.RespawnOneDay) + || !AddObject(EotSObjectTypes.NBannerFelReaverRight, EotSObjectIds.NBanner, 2092.35f, 1775.46f, 1187.08f, -0.401426f, 0, 0, 0.199368f, -0.979925f, BattlegroundConst.RespawnOneDay) + || !AddObject(EotSObjectTypes.NBannerBloodElfCenter, EotSObjectIds.NBanner, 2047.19f, 1349.19f, 1189.0f, -1.62316f, 0, 0, 0.725374f, -0.688354f, BattlegroundConst.RespawnOneDay) + || !AddObject(EotSObjectTypes.NBannerBloodElfLeft, EotSObjectIds.NBanner, 2074.32f, 1385.78f, 1194.72f, 0.488692f, 0, 0, 0.241922f, 0.970296f, BattlegroundConst.RespawnOneDay) + || !AddObject(EotSObjectTypes.NBannerBloodElfRight, EotSObjectIds.NBanner, 2025.13f, 1386.12f, 1192.74f, 2.3911f, 0, 0, 0.930418f, 0.366501f, BattlegroundConst.RespawnOneDay) + || !AddObject(EotSObjectTypes.NBannerDraeneiRuinsCenter, EotSObjectIds.NBanner, 2276.8f, 1400.41f, 1196.33f, 2.44346f, 0, 0, 0.939693f, 0.34202f, BattlegroundConst.RespawnOneDay) + || !AddObject(EotSObjectTypes.NBannerDraeneiRuinsLeft, EotSObjectIds.NBanner, 2305.78f, 1404.56f, 1199.38f, 1.74533f, 0, 0, 0.766044f, 0.642788f, BattlegroundConst.RespawnOneDay) + || !AddObject(EotSObjectTypes.NBannerDraeneiRuinsRight, EotSObjectIds.NBanner, 2245.4f, 1366.41f, 1195.28f, 2.21657f, 0, 0, 0.894934f, 0.446198f, BattlegroundConst.RespawnOneDay) + || !AddObject(EotSObjectTypes.NBannerMageTowerCenter, EotSObjectIds.NBanner, 2270.84f, 1784.08f, 1186.76f, 2.42601f, 0, 0, 0.936672f, 0.350207f, BattlegroundConst.RespawnOneDay) + || !AddObject(EotSObjectTypes.NBannerMageTowerLeft, EotSObjectIds.NBanner, 2269.13f, 1737.7f, 1186.66f, 0.994838f, 0, 0, 0.477159f, 0.878817f, BattlegroundConst.RespawnOneDay) + || !AddObject(EotSObjectTypes.NBannerMageTowerRight, EotSObjectIds.NBanner, 2300.86f, 1741.25f, 1187.7f, -0.785398f, 0, 0, 0.382683f, -0.92388f, BattlegroundConst.RespawnOneDay) + // flags + || !AddObject(EotSObjectTypes.FlagNetherstorm, EotSObjectIds.Flag2, 2174.782227f, 1569.054688f, 1160.361938f, -1.448624f, 0, 0, 0.662620f, -0.748956f, BattlegroundConst.RespawnOneDay) + || !AddObject(EotSObjectTypes.FlagFelReaver, EotSObjectIds.Flag1, 2044.28f, 1729.68f, 1189.96f, -0.017453f, 0, 0, 0.008727f, -0.999962f, BattlegroundConst.RespawnOneDay) + || !AddObject(EotSObjectTypes.FlagBloodElf, EotSObjectIds.Flag1, 2048.83f, 1393.65f, 1194.49f, 0.20944f, 0, 0, 0.104528f, 0.994522f, BattlegroundConst.RespawnOneDay) + || !AddObject(EotSObjectTypes.FlagDraeneiRuins, EotSObjectIds.Flag1, 2286.56f, 1402.36f, 1197.11f, 3.72381f, 0, 0, 0.957926f, -0.287016f, BattlegroundConst.RespawnOneDay) + || !AddObject(EotSObjectTypes.FlagMageTower, EotSObjectIds.Flag1, 2284.48f, 1731.23f, 1189.99f, 2.89725f, 0, 0, 0.992546f, 0.121869f, BattlegroundConst.RespawnOneDay) + // tower cap + || !AddObject(EotSObjectTypes.TowerCapFelReaver, EotSObjectIds.FrTowerCap, 2024.600708f, 1742.819580f, 1195.157715f, 2.443461f, 0, 0, 0.939693f, 0.342020f, BattlegroundConst.RespawnOneDay) + || !AddObject(EotSObjectTypes.TowerCapBloodElf, EotSObjectIds.BeTowerCap, 2050.493164f, 1372.235962f, 1194.563477f, 1.710423f, 0, 0, 0.754710f, 0.656059f, BattlegroundConst.RespawnOneDay) + || !AddObject(EotSObjectTypes.TowerCapDraeneiRuins, EotSObjectIds.DrTowerCap, 2301.010498f, 1386.931641f, 1197.183472f, 1.570796f, 0, 0, 0.707107f, 0.707107f, BattlegroundConst.RespawnOneDay) + || !AddObject(EotSObjectTypes.TowerCapMageTower, EotSObjectIds.HuTowerCap, 2282.121582f, 1760.006958f, 1189.707153f, 1.919862f, 0, 0, 0.819152f, 0.573576f, BattlegroundConst.RespawnOneDay) + ) + { + Log.outError(LogFilter.Sql, "BatteGroundEY: Failed to spawn some objects. The battleground was not created."); + return false; + } + + //buffs + for (int i = 0; i < EotSPoints.PointsMax; ++i) + { + AreaTriggerRecord at = CliDB.AreaTriggerStorage.LookupByKey(m_Points_Trigger[i]); + if (at == null) + { + Log.outError(LogFilter.Battleground, "BattlegroundEY: Unknown trigger: {0}", m_Points_Trigger[i]); + continue; + } + if (!AddObject(EotSObjectTypes.SpeedbuffFelReaver + i * 3, Buff_Entries[0], at.Pos.X, at.Pos.Y, at.Pos.Z, 0.907571f, 0, 0, 0.438371f, 0.898794f, BattlegroundConst.RespawnOneDay) + || !AddObject(EotSObjectTypes.SpeedbuffFelReaver + i * 3 + 1, Buff_Entries[1], at.Pos.X, at.Pos.Y, at.Pos.Z, 0.907571f, 0, 0, 0.438371f, 0.898794f, BattlegroundConst.RespawnOneDay) + || !AddObject(EotSObjectTypes.SpeedbuffFelReaver + i * 3 + 2, Buff_Entries[2], at.Pos.X, at.Pos.Y, at.Pos.Z, 0.907571f, 0, 0, 0.438371f, 0.898794f, BattlegroundConst.RespawnOneDay) + ) + Log.outError(LogFilter.Battleground, "BattlegroundEY: Could not spawn Speedbuff Fel Reaver."); + } + + WorldSafeLocsRecord sg = null; + sg = CliDB.WorldSafeLocsStorage.LookupByKey(EotSGaveyardIds.MainAlliance); + if (sg == null || !AddSpiritGuide(EotSGaveyardIds.MainAlliance, sg.Loc.X, sg.Loc.Y, sg.Loc.Z, 3.124139f, TeamId.Alliance)) + { + Log.outError(LogFilter.Sql, "BatteGroundEY: Failed to spawn spirit guide. The battleground was not created."); + return false; + } + + sg = CliDB.WorldSafeLocsStorage.LookupByKey(EotSGaveyardIds.MainHorde); + if (sg == null || !AddSpiritGuide((int)EotSCreaturesTypes.SpiritMainHorde, sg.Loc.X, sg.Loc.Y, sg.Loc.Z, 3.193953f, TeamId.Horde)) + { + Log.outError(LogFilter.Sql, "BatteGroundEY: Failed to spawn spirit guide. The battleground was not created."); + return false; + } + + return true; + } + + public override void Reset() + { + //call parent's class reset + base.Reset(); + + m_TeamScores[TeamId.Alliance] = 0; + m_TeamScores[TeamId.Horde] = 0; + m_TeamPointsCount[TeamId.Alliance] = 0; + m_TeamPointsCount[TeamId.Horde] = 0; + m_HonorScoreTics[TeamId.Alliance] = 0; + m_HonorScoreTics[TeamId.Horde] = 0; + m_FlagState = EotSFlagState.OnBase; + m_FlagCapturedBgObjectType = 0; + m_FlagKeeper.Clear(); + m_DroppedFlagGUID.Clear(); + m_PointAddingTimer = 0; + m_TowerCapCheckTimer = 0; + bool isBGWeekend = Global.BattlegroundMgr.IsBGWeekend(GetTypeID()); + m_HonorTics = (isBGWeekend) ? EotSMisc.EYWeekendHonorTicks : EotSMisc.NotEYWeekendHonorTicks; + + for (byte i = 0; i < EotSPoints.PointsMax; ++i) + { + m_PointOwnedByTeam[i] = Team.Other; + m_PointState[i] = EotSPointState.Uncontrolled; + m_PointBarStatus[i] = EotSProgressBarConsts.ProgressBarStateMiddle; + m_PlayersNearPoint[i].Clear(); + //m_PlayersNearPoint[i].reserve(15); //tip size + } + m_PlayersNearPoint[EotSPoints.PlayersOutOfPoints].Clear(); + //m_PlayersNearPoint[EotSPoints.PlayersOutOfPoints].reserve(30); + } + + void RespawnFlag(bool send_message) + { + if (m_FlagCapturedBgObjectType > 0) + SpawnBGObject((int)m_FlagCapturedBgObjectType, BattlegroundConst.RespawnOneDay); + + m_FlagCapturedBgObjectType = 0; + m_FlagState = EotSFlagState.OnBase; + SpawnBGObject(EotSObjectTypes.FlagNetherstorm, BattlegroundConst.RespawnImmediately); + + if (send_message) + { + SendMessageToAll(CypherStrings.BgEyResetedFlag, ChatMsg.BgSystemNeutral); + PlaySoundToAll(EotSSoundIds.FlagReset); // flags respawned sound... + } + + UpdateWorldState(EotSWorldStateIds.NetherstormFlag, 1); + } + + void RespawnFlagAfterDrop() + { + RespawnFlag(true); + + GameObject obj = GetBgMap().GetGameObject(GetDroppedFlagGUID()); + if (obj) + obj.Delete(); + else + Log.outError(LogFilter.Battleground, "BattlegroundEY: Unknown dropped flag ({0}).", GetDroppedFlagGUID().ToString()); + + SetDroppedFlagGUID(ObjectGuid.Empty); + } + + public override void HandleKillPlayer(Player player, Player killer) + { + if (GetStatus() != BattlegroundStatus.InProgress) + return; + + base.HandleKillPlayer(player, killer); + EventPlayerDroppedFlag(player); + } + + public override void EventPlayerDroppedFlag(Player player) + { + if (GetStatus() != BattlegroundStatus.InProgress) + { + // if not running, do not cast things at the dropper player, neither send unnecessary messages + // just take off the aura + if (IsFlagPickedup() && GetFlagPickerGUID() == player.GetGUID()) + { + SetFlagPicker(ObjectGuid.Empty); + player.RemoveAurasDueToSpell(EotSMisc.SpellNetherstormFlag); + } + return; + } + + if (!IsFlagPickedup()) + return; + + if (GetFlagPickerGUID() != player.GetGUID()) + return; + + SetFlagPicker(ObjectGuid.Empty); + player.RemoveAurasDueToSpell(EotSMisc.SpellNetherstormFlag); + m_FlagState = EotSFlagState.OnGround; + m_FlagsTimer = EotSMisc.FlagRespawnTime; + player.CastSpell(player, BattlegroundConst.SpellRecentlyDroppedFlag, true); + player.CastSpell(player, EotSMisc.SpellPlayerDroppedFlag, true); + //this does not work correctly :((it should remove flag carrier name) + UpdateWorldState(EotSWorldStateIds.NetherstormFlagStateHorde, (uint)EotSFlagState.WaitRespawn); + UpdateWorldState(EotSWorldStateIds.NetherstormFlagStateAlliance, (uint)EotSFlagState.WaitRespawn); + + if (player.GetTeam() == Team.Alliance) + SendMessageToAll(CypherStrings.BgEyDroppedFlag, ChatMsg.BgSystemAlliance, null); + else + SendMessageToAll(CypherStrings.BgEyDroppedFlag, ChatMsg.BgSystemHorde, null); + } + + public override void EventPlayerClickedOnFlag(Player player, GameObject target_obj) + { + if (GetStatus() != BattlegroundStatus.InProgress || IsFlagPickedup() || !player.IsWithinDistInMap(target_obj, 10)) + return; + + if (player.GetTeam() == Team.Alliance) + { + UpdateWorldState(EotSWorldStateIds.NetherstormFlagStateAlliance, (uint)EotSFlagState.OnPlayer); + PlaySoundToAll(EotSSoundIds.FlagPickedUpAlliance); + } + else + { + UpdateWorldState(EotSWorldStateIds.NetherstormFlagStateHorde, (uint)EotSFlagState.OnPlayer); + PlaySoundToAll(EotSSoundIds.FlagPickedUpHorde); + } + + if (m_FlagState == EotSFlagState.OnBase) + UpdateWorldState(EotSWorldStateIds.NetherstormFlag, 0); + m_FlagState = EotSFlagState.OnPlayer; + + SpawnBGObject(EotSObjectTypes.FlagNetherstorm, BattlegroundConst.RespawnOneDay); + SetFlagPicker(player.GetGUID()); + //get flag aura on player + player.CastSpell(player, EotSMisc.SpellNetherstormFlag, true); + player.RemoveAurasWithInterruptFlags(SpellAuraInterruptFlags.EnterPvpCombat); + + if (player.GetTeam() == Team.Alliance) + SendMessageToAll(CypherStrings.BgEyHasTakenFlag, ChatMsg.BgSystemAlliance, null, player.GetName()); + else + SendMessageToAll(CypherStrings.BgEyHasTakenFlag, ChatMsg.BgSystemHorde, null, player.GetName()); + } + + void EventTeamLostPo(Player player, int Point) + { + if (GetStatus() != BattlegroundStatus.InProgress) + return; + + //Natural point + Team Team = (Team)m_PointOwnedByTeam[Point]; + + if (Team == 0) + return; + + if (Team == Team.Alliance) + { + m_TeamPointsCount[TeamId.Alliance]--; + SpawnBGObject(EotSMisc.m_LosingPointTypes[Point].DespawnObjectTypeAlliance, BattlegroundConst.RespawnOneDay); + SpawnBGObject(EotSMisc.m_LosingPointTypes[Point].DespawnObjectTypeAlliance + 1, BattlegroundConst.RespawnOneDay); + SpawnBGObject(EotSMisc.m_LosingPointTypes[Point].DespawnObjectTypeAlliance + 2, BattlegroundConst.RespawnOneDay); + } + else + { + m_TeamPointsCount[TeamId.Horde]--; + SpawnBGObject(EotSMisc.m_LosingPointTypes[Point].DespawnObjectTypeHorde, BattlegroundConst.RespawnOneDay); + SpawnBGObject(EotSMisc.m_LosingPointTypes[Point].DespawnObjectTypeHorde + 1, BattlegroundConst.RespawnOneDay); + SpawnBGObject(EotSMisc.m_LosingPointTypes[Point].DespawnObjectTypeHorde + 2, BattlegroundConst.RespawnOneDay); + } + + SpawnBGObject(EotSMisc.m_LosingPointTypes[Point].SpawnNeutralObjectType, BattlegroundConst.RespawnImmediately); + SpawnBGObject(EotSMisc.m_LosingPointTypes[Point].SpawnNeutralObjectType + 1, BattlegroundConst.RespawnImmediately); + SpawnBGObject(EotSMisc.m_LosingPointTypes[Point].SpawnNeutralObjectType + 2, BattlegroundConst.RespawnImmediately); + + //buff isn't despawned + + m_PointOwnedByTeam[Point] = Team.Other; + m_PointState[Point] = EotSPointState.NoOwner; + + if (Team == Team.Alliance) + SendMessageToAll(EotSMisc.m_LosingPointTypes[Point].MessageIdAlliance, ChatMsg.BgSystemAlliance, player); + else + SendMessageToAll(EotSMisc.m_LosingPointTypes[Point].MessageIdHorde, ChatMsg.BgSystemHorde, player); + + UpdatePointsIcons(Team, Point); + UpdatePointsCount(Team); + + //remove bonus honor aura trigger creature when node is lost + if (Point < EotSPoints.PointsMax) + DelCreature(Point + 6);//null checks are in DelCreature! 0-5 spirit guides + } + + void EventTeamCapturedPo(Player player, int Point) + { + if (GetStatus() != BattlegroundStatus.InProgress) + return; + + Team Team = player.GetTeam(); + + SpawnBGObject(EotSMisc.m_CapturingPointTypes[Point].DespawnNeutralObjectType, BattlegroundConst.RespawnOneDay); + SpawnBGObject(EotSMisc.m_CapturingPointTypes[Point].DespawnNeutralObjectType + 1, BattlegroundConst.RespawnOneDay); + SpawnBGObject(EotSMisc.m_CapturingPointTypes[Point].DespawnNeutralObjectType + 2, BattlegroundConst.RespawnOneDay); + + if (Team == Team.Alliance) + { + m_TeamPointsCount[TeamId.Alliance]++; + SpawnBGObject(EotSMisc.m_CapturingPointTypes[Point].SpawnObjectTypeAlliance, BattlegroundConst.RespawnImmediately); + SpawnBGObject(EotSMisc.m_CapturingPointTypes[Point].SpawnObjectTypeAlliance + 1, BattlegroundConst.RespawnImmediately); + SpawnBGObject(EotSMisc.m_CapturingPointTypes[Point].SpawnObjectTypeAlliance + 2, BattlegroundConst.RespawnImmediately); + } + else + { + m_TeamPointsCount[TeamId.Horde]++; + SpawnBGObject(EotSMisc.m_CapturingPointTypes[Point].SpawnObjectTypeHorde, BattlegroundConst.RespawnImmediately); + SpawnBGObject(EotSMisc.m_CapturingPointTypes[Point].SpawnObjectTypeHorde + 1, BattlegroundConst.RespawnImmediately); + SpawnBGObject(EotSMisc.m_CapturingPointTypes[Point].SpawnObjectTypeHorde + 2, BattlegroundConst.RespawnImmediately); + } + + //buff isn't respawned + + m_PointOwnedByTeam[Point] = Team; + m_PointState[Point] = EotSPointState.UnderControl; + + if (Team == Team.Alliance) + SendMessageToAll(EotSMisc.m_CapturingPointTypes[Point].MessageIdAlliance, ChatMsg.BgSystemAlliance, player); + else + SendMessageToAll(EotSMisc.m_CapturingPointTypes[Point].MessageIdHorde, ChatMsg.BgSystemHorde, player); + + if (BgCreatures.ContainsKey(Point) && !BgCreatures[Point].IsEmpty()) + DelCreature(Point); + + WorldSafeLocsRecord sg = null; + sg = CliDB.WorldSafeLocsStorage.LookupByKey(EotSMisc.m_CapturingPointTypes[Point].GraveYardId); + if (sg == null || !AddSpiritGuide(Point, sg.Loc.X, sg.Loc.Y, sg.Loc.Z, 3.124139f, GetTeamIndexByTeamId(Team))) + Log.outError(LogFilter.Battleground, "BatteGroundEY: Failed to spawn spirit guide. point: {0}, team: {1}, graveyard_id: {2}", + Point, Team, EotSMisc.m_CapturingPointTypes[Point].GraveYardId); + + // SpawnBGCreature(Point, RESPAWN_IMMEDIATELY); + + UpdatePointsIcons(Team, Point); + UpdatePointsCount(Team); + + if (Point >= EotSPoints.PointsMax) + return; + + Creature trigger = GetBGCreature(Point + 6);//0-5 spirit guides + if (!trigger) + trigger = AddCreature(SharedConst.WorldTrigger, Point + 6, EotSMisc.TriggerPositions[Point], GetTeamIndexByTeamId(Team)); + + //add bonus honor aura trigger creature when node is accupied + //cast bonus aura (+50% honor in 25yards) + //aura should only apply to players who have accupied the node, set correct faction for trigger + if (trigger) + { + trigger.SetFaction(Team == Team.Alliance ? 84u : 83); + trigger.CastSpell(trigger, BattlegroundConst.SpellHonorableDefender25y, false); + } + } + + void EventPlayerCapturedFlag(Player player, uint BgObjectType) + { + if (GetStatus() != BattlegroundStatus.InProgress || GetFlagPickerGUID() != player.GetGUID()) + return; + + SetFlagPicker(ObjectGuid.Empty); + m_FlagState = EotSFlagState.WaitRespawn; + player.RemoveAurasDueToSpell(EotSMisc.SpellNetherstormFlag); + + player.RemoveAurasWithInterruptFlags(SpellAuraInterruptFlags.EnterPvpCombat); + + if (player.GetTeam() == Team.Alliance) + PlaySoundToAll(EotSSoundIds.FlagCapturedAlliance); + else + PlaySoundToAll(EotSSoundIds.FlagCapturedHorde); + + SpawnBGObject((int)BgObjectType, BattlegroundConst.RespawnImmediately); + + m_FlagsTimer = EotSMisc.FlagRespawnTime; + m_FlagCapturedBgObjectType = BgObjectType; + + uint team_id = 0; + if (player.GetTeam() == Team.Alliance) + { + team_id = TeamId.Alliance; + SendMessageToAll(CypherStrings.BgEyCapturedFlagA, ChatMsg.BgSystemAlliance, player); + } + else + { + team_id = TeamId.Horde; + SendMessageToAll(CypherStrings.BgEyCapturedFlagH, ChatMsg.BgSystemHorde, player); + } + + if (m_TeamPointsCount[team_id] > 0) + AddPoints(player.GetTeam(), EotSMisc.FlagPoints[m_TeamPointsCount[team_id] - 1]); + + UpdatePlayerScore(player, ScoreType.FlagCaptures, 1); + } + + public override bool UpdatePlayerScore(Player player, ScoreType type, uint value, bool doAddHonor = true) + { + if (!base.UpdatePlayerScore(player, type, value, doAddHonor)) + return false; + + switch (type) + { + case ScoreType.FlagCaptures: + player.UpdateCriteria(CriteriaTypes.BgObjectiveCapture, EotSMisc.ObjectiveCaptureFlag); + break; + default: + break; + } + return true; + } + + public override void FillInitialWorldStates(InitWorldStates packet) + { + packet.AddState(EotSWorldStateIds.HordeBase, (int)m_TeamPointsCount[TeamId.Horde]); + packet.AddState(EotSWorldStateIds.AllianceBase, (int)m_TeamPointsCount[TeamId.Alliance]); + packet.AddState(0xAB6, 0x0); + packet.AddState(0xAB5, 0x0); + packet.AddState(0xAB4, 0x0); + packet.AddState(0xAB3, 0x0); + packet.AddState(0xAB2, 0x0); + packet.AddState(0xAB1, 0x0); + packet.AddState(0xAB0, 0x0); + packet.AddState(0xAAF, 0x0); + + packet.AddState((EotSWorldStateIds.DraeneiRuinsHordeControl), (m_PointOwnedByTeam[EotSPoints.DraeneiRuins] == Team.Horde && m_PointState[EotSPoints.DraeneiRuins] == EotSPointState.UnderControl)); + packet.AddState((EotSWorldStateIds.DraeneiRuinsAllianceControl), (m_PointOwnedByTeam[EotSPoints.DraeneiRuins] == Team.Alliance && m_PointState[EotSPoints.DraeneiRuins] == EotSPointState.UnderControl)); + packet.AddState((EotSWorldStateIds.DraeneiRuinsUncontrol), (m_PointState[EotSPoints.DraeneiRuins] != EotSPointState.UnderControl)); + packet.AddState((EotSWorldStateIds.MageTowerAllianceControl), (m_PointOwnedByTeam[EotSPoints.MageTower] == Team.Alliance && m_PointState[EotSPoints.MageTower] == EotSPointState.UnderControl)); + packet.AddState((EotSWorldStateIds.MageTowerHordeControl), (m_PointOwnedByTeam[EotSPoints.MageTower] == Team.Horde && m_PointState[EotSPoints.MageTower] == EotSPointState.UnderControl)); + packet.AddState((EotSWorldStateIds.MageTowerUncontrol), (m_PointState[EotSPoints.MageTower] != EotSPointState.UnderControl)); + packet.AddState((EotSWorldStateIds.FelReaverHordeControl), (m_PointOwnedByTeam[EotSPoints.FelReaver] == Team.Horde && m_PointState[EotSPoints.FelReaver] == EotSPointState.UnderControl)); + packet.AddState((EotSWorldStateIds.FelReaverAllianceControl), (m_PointOwnedByTeam[EotSPoints.FelReaver] == Team.Alliance && m_PointState[EotSPoints.FelReaver] == EotSPointState.UnderControl)); + packet.AddState((EotSWorldStateIds.FelReaverUncontrol), (m_PointState[EotSPoints.FelReaver] != EotSPointState.UnderControl)); + packet.AddState((EotSWorldStateIds.BloodElfHordeControl), (m_PointOwnedByTeam[EotSPoints.BloodElf] == Team.Horde && m_PointState[EotSPoints.BloodElf] == EotSPointState.UnderControl)); + packet.AddState((EotSWorldStateIds.BloodElfAllianceControl), (m_PointOwnedByTeam[EotSPoints.BloodElf] == Team.Alliance && m_PointState[EotSPoints.BloodElf] == EotSPointState.UnderControl)); + packet.AddState((EotSWorldStateIds.BloodElfUncontrol), (m_PointState[EotSPoints.BloodElf] != EotSPointState.UnderControl)); + packet.AddState((EotSWorldStateIds.NetherstormFlag), (m_FlagState == EotSFlagState.OnBase)); + + packet.AddState(0xAD2, 0x1); + packet.AddState(0xAD1, 0x1); + + packet.AddState(0xABE, (int)GetTeamScore(TeamId.Horde)); + packet.AddState(0xABD, (int)GetTeamScore(TeamId.Alliance)); + + packet.AddState(0xA05, 0x8E); + packet.AddState(0xAA0, 0x0); + packet.AddState(0xA9F, 0x0); + packet.AddState(0xA9E, 0x0); + packet.AddState(0xC0D, 0x17B); + } + + public override WorldSafeLocsRecord GetClosestGraveYard(Player player) + { + uint g_id = 0; + + switch (player.GetTeam()) + { + case Team.Alliance: + g_id = EotSGaveyardIds.MainAlliance; + break; + case Team.Horde: + g_id = EotSGaveyardIds.MainHorde; + break; + default: return null; + } + + float distance, nearestDistance; + + WorldSafeLocsRecord entry = null; + WorldSafeLocsRecord nearestEntry = null; + entry = CliDB.WorldSafeLocsStorage.LookupByKey(g_id); + nearestEntry = entry; + + if (entry == null) + { + Log.outError(LogFilter.Battleground, "BattlegroundEY: The main team graveyard could not be found. The graveyard system will not be operational!"); + return null; + } + + float plr_x = player.GetPositionX(); + float plr_y = player.GetPositionY(); + float plr_z = player.GetPositionZ(); + + distance = (entry.Loc.X - plr_x) * (entry.Loc.X - plr_x) + (entry.Loc.Y - plr_y) * (entry.Loc.Y - plr_y) + (entry.Loc.Z - plr_z) * (entry.Loc.Z - plr_z); + nearestDistance = distance; + + for (byte i = 0; i < EotSPoints.PointsMax; ++i) + { + if (m_PointOwnedByTeam[i] == player.GetTeam() && m_PointState[i] == EotSPointState.UnderControl) + { + entry = CliDB.WorldSafeLocsStorage.LookupByKey(EotSMisc.m_CapturingPointTypes[i].GraveYardId); + if (entry == null) + Log.outError(LogFilter.Battleground, "BattlegroundEY: Graveyard {0} could not be found.", EotSMisc.m_CapturingPointTypes[i].GraveYardId); + else + { + distance = (entry.Loc.X - plr_x) * (entry.Loc.X - plr_x) + (entry.Loc.Y - plr_y) * (entry.Loc.Y - plr_y) + (entry.Loc.Z - plr_z) * (entry.Loc.Z - plr_z); + if (distance < nearestDistance) + { + nearestDistance = distance; + nearestEntry = entry; + } + } + } + } + + return nearestEntry; + } + + public override WorldSafeLocsRecord GetExploitTeleportLocation(Team team) + { + return CliDB.WorldSafeLocsStorage.LookupByKey(team == Team.Alliance ? EotSMisc.ExploitTeleportLocationAlliance : EotSMisc.ExploitTeleportLocationHorde); + } + + public override bool IsAllNodesControlledByTeam(Team team) + { + uint count = 0; + for (int i = 0; i < EotSPoints.PointsMax; ++i) + if (m_PointOwnedByTeam[i] == team && m_PointState[i] == EotSPointState.UnderControl) + ++count; + + return count == EotSPoints.PointsMax; + } + + public override Team GetPrematureWinner() + { + if (GetTeamScore(TeamId.Alliance) > GetTeamScore(TeamId.Horde)) + return Team.Alliance; + else if (GetTeamScore(TeamId.Horde) > GetTeamScore(TeamId.Alliance)) + return Team.Horde; + + return base.GetPrematureWinner(); + } + + public override ObjectGuid GetFlagPickerGUID(int team = -1) { return m_FlagKeeper; } + void SetFlagPicker(ObjectGuid guid) { m_FlagKeeper = guid; } + bool IsFlagPickedup() { return !m_FlagKeeper.IsEmpty(); } + byte GetFlagState() { return (byte)m_FlagState; } + + public override void SetDroppedFlagGUID(ObjectGuid guid, int TeamID = -1) { m_DroppedFlagGUID = guid; } + ObjectGuid GetDroppedFlagGUID() { return m_DroppedFlagGUID; } + + void RemovePo(Team TeamID, uint Points = 1) { m_TeamScores[GetTeamIndexByTeamId(TeamID)] -= Points; } + void SetTeamPo(Team TeamID, uint Points = 0) { m_TeamScores[GetTeamIndexByTeamId(TeamID)] = Points; } + + uint[] m_HonorScoreTics = new uint[2]; + uint[] m_TeamPointsCount = new uint[2]; + + uint[] m_Points_Trigger = new uint[EotSPoints.PointsMax]; + + ObjectGuid m_FlagKeeper; // keepers guid + ObjectGuid m_DroppedFlagGUID; + uint m_FlagCapturedBgObjectType; // type that should be despawned when flag is captured + EotSFlagState m_FlagState; // for checking flag state + int m_FlagsTimer; + int m_TowerCapCheckTimer; + + Team[] m_PointOwnedByTeam = new Team[EotSPoints.PointsMax]; + EotSPointState[] m_PointState = new EotSPointState[EotSPoints.PointsMax]; + EotSProgressBarConsts[] m_PointBarStatus = new EotSProgressBarConsts[EotSPoints.PointsMax]; + List[] m_PlayersNearPoint = new List[EotSPoints.PointsMax + 1]; + byte[] m_CurrentPointPlayersCount = new byte[2 * EotSPoints.PointsMax]; + + int m_PointAddingTimer; + uint m_HonorTics; + } + + class BgEyeOfStormScore : BattlegroundScore + { + public BgEyeOfStormScore(ObjectGuid playerGuid, Team team) : base(playerGuid, team) { } + + public override void UpdateScore(ScoreType type, uint value) + { + switch (type) + { + case ScoreType.FlagCaptures: // Flags captured + FlagCaptures += value; + break; + default: + base.UpdateScore(type, value); + break; + } + } + public override void BuildObjectivesBlock(List stats) + { + stats.Add((int)FlagCaptures); + } + + public override uint GetAttr1() { return FlagCaptures; } + + uint FlagCaptures; + } + + struct BattlegroundEYPointIconsStruct + { + public BattlegroundEYPointIconsStruct(uint _WorldStateControlIndex, uint _WorldStateAllianceControlledIndex, uint _WorldStateHordeControlledIndex) + { + WorldStateControlIndex = _WorldStateControlIndex; + WorldStateAllianceControlledIndex = _WorldStateAllianceControlledIndex; + WorldStateHordeControlledIndex = _WorldStateHordeControlledIndex; + } + + public uint WorldStateControlIndex; + public uint WorldStateAllianceControlledIndex; + public uint WorldStateHordeControlledIndex; + } + + struct BattlegroundEYLosingPointStruct + { + public BattlegroundEYLosingPointStruct(int _SpawnNeutralObjectType, int _DespawnObjectTypeAlliance, CypherStrings _MessageIdAlliance, int _DespawnObjectTypeHorde, CypherStrings _MessageIdHorde) + { + SpawnNeutralObjectType = _SpawnNeutralObjectType; + DespawnObjectTypeAlliance = _DespawnObjectTypeAlliance; + MessageIdAlliance = _MessageIdAlliance; + DespawnObjectTypeHorde = _DespawnObjectTypeHorde; + MessageIdHorde = _MessageIdHorde; + } + + public int SpawnNeutralObjectType; + public int DespawnObjectTypeAlliance; + public CypherStrings MessageIdAlliance; + public int DespawnObjectTypeHorde; + public CypherStrings MessageIdHorde; + } + + struct BattlegroundEYCapturingPointStruct + { + public BattlegroundEYCapturingPointStruct(int _DespawnNeutralObjectType, int _SpawnObjectTypeAlliance, CypherStrings _MessageIdAlliance, int _SpawnObjectTypeHorde, CypherStrings _MessageIdHorde, uint _GraveYardId) + { + DespawnNeutralObjectType = _DespawnNeutralObjectType; + SpawnObjectTypeAlliance = _SpawnObjectTypeAlliance; + MessageIdAlliance = _MessageIdAlliance; + SpawnObjectTypeHorde = _SpawnObjectTypeHorde; + MessageIdHorde = _MessageIdHorde; + GraveYardId = _GraveYardId; + } + + public int DespawnNeutralObjectType; + public int SpawnObjectTypeAlliance; + public CypherStrings MessageIdAlliance; + public int SpawnObjectTypeHorde; + public CypherStrings MessageIdHorde; + public uint GraveYardId; + } + + struct EotSMisc + { + public const uint EventStartBattle = 13180; // Achievement: Flurry + public const int FlagRespawnTime = (8 * Time.InMilliseconds); + public const int FPointsTickTime = (2 * Time.InMilliseconds); + + public const uint NotEYWeekendHonorTicks = 260; + public const uint EYWeekendHonorTicks = 160; + + public const uint ObjectiveCaptureFlag = 183; + + public const uint SpellNetherstormFlag = 34976; + public const uint SpellPlayerDroppedFlag = 34991; + + public const uint ExploitTeleportLocationAlliance = 3773; + public const uint ExploitTeleportLocationHorde = 3772; + + public static Position[] TriggerPositions = + { + new Position(2044.28f, 1729.68f, 1189.96f, 0.017453f), // FEL_REAVER center + new Position(2048.83f, 1393.65f, 1194.49f, 0.20944f), // BLOOD_ELF center + new Position(2286.56f, 1402.36f, 1197.11f, 3.72381f), // DRAENEI_RUINS center + new Position(2284.48f, 1731.23f, 1189.99f, 2.89725f) // MAGE_TOWER center + }; + + public static byte[] TickPoints = { 1, 2, 5, 10 }; + public static uint[] FlagPoints = { 75, 85, 100, 500 }; + + public static BattlegroundEYPointIconsStruct[] m_PointsIconStruct = + { + new BattlegroundEYPointIconsStruct(EotSWorldStateIds.FelReaverUncontrol, EotSWorldStateIds.FelReaverAllianceControl, EotSWorldStateIds.FelReaverHordeControl), + new BattlegroundEYPointIconsStruct(EotSWorldStateIds.BloodElfUncontrol, EotSWorldStateIds.BloodElfAllianceControl, EotSWorldStateIds.BloodElfHordeControl), + new BattlegroundEYPointIconsStruct(EotSWorldStateIds.DraeneiRuinsUncontrol, EotSWorldStateIds.DraeneiRuinsAllianceControl, EotSWorldStateIds.DraeneiRuinsHordeControl), + new BattlegroundEYPointIconsStruct(EotSWorldStateIds.MageTowerUncontrol, EotSWorldStateIds.MageTowerAllianceControl, EotSWorldStateIds.MageTowerHordeControl) + }; + public static BattlegroundEYLosingPointStruct[] m_LosingPointTypes = + { + new BattlegroundEYLosingPointStruct(EotSObjectTypes.NBannerFelReaverCenter, EotSObjectTypes.ABannerFelReaverCenter, CypherStrings.BgEyHasLostAFRuins, EotSObjectTypes.HBannerFelReaverCenter, CypherStrings.BgEyHasLostHFRuins), + new BattlegroundEYLosingPointStruct(EotSObjectTypes.NBannerBloodElfCenter, EotSObjectTypes.ABannerBloodElfCenter, CypherStrings.BgEyHasLostABTower, EotSObjectTypes.HBannerBloodElfCenter, CypherStrings.BgEyHasLostHBTower), + new BattlegroundEYLosingPointStruct(EotSObjectTypes.NBannerDraeneiRuinsCenter, EotSObjectTypes.ABannerDraeneiRuinsCenter, CypherStrings.BgEyHasLostADRuins, EotSObjectTypes.HBannerDraeneiRuinsCenter, CypherStrings.BgEyHasLostHDRuins), + new BattlegroundEYLosingPointStruct(EotSObjectTypes.NBannerMageTowerCenter, EotSObjectTypes.ABannerMageTowerCenter, CypherStrings.BgEyHasLostAMTower, EotSObjectTypes.HBannerMageTowerCenter, CypherStrings.BgEyHasLostHMTower) + }; + public static BattlegroundEYCapturingPointStruct[] m_CapturingPointTypes = + { + new BattlegroundEYCapturingPointStruct(EotSObjectTypes.NBannerFelReaverCenter, EotSObjectTypes.ABannerFelReaverCenter, CypherStrings.BgEyHasTakenAFRuins, EotSObjectTypes.HBannerFelReaverCenter, CypherStrings.BgEyHasTakenHFRuins, EotSGaveyardIds.FelReaver), + new BattlegroundEYCapturingPointStruct(EotSObjectTypes.NBannerBloodElfCenter, EotSObjectTypes.ABannerBloodElfCenter, CypherStrings.BgEyHasTakenABTower, EotSObjectTypes.HBannerBloodElfCenter, CypherStrings.BgEyHasTakenHBTower, EotSGaveyardIds.BloodElf), + new BattlegroundEYCapturingPointStruct(EotSObjectTypes.NBannerDraeneiRuinsCenter, EotSObjectTypes.ABannerDraeneiRuinsCenter, CypherStrings.BgEyHasTakenADRuins, EotSObjectTypes.HBannerDraeneiRuinsCenter, CypherStrings.BgEyHasTakenHDRuins, EotSGaveyardIds.DraeneiRuins), + new BattlegroundEYCapturingPointStruct(EotSObjectTypes.NBannerMageTowerCenter, EotSObjectTypes.ABannerMageTowerCenter, CypherStrings.BgEyHasTakenAMTower, EotSObjectTypes.HBannerMageTowerCenter, CypherStrings.BgEyHasTakenHMTower, EotSGaveyardIds.MageTower) + }; + } + + struct EotSWorldStateIds + { + public const uint AllianceResources = 2749; + public const uint HordeResources = 2750; + public const uint AllianceBase = 2752; + public const uint HordeBase = 2753; + public const uint DraeneiRuinsHordeControl = 2733; + public const uint DraeneiRuinsAllianceControl = 2732; + public const uint DraeneiRuinsUncontrol = 2731; + public const uint MageTowerAllianceControl = 2730; + public const uint MageTowerHordeControl = 2729; + public const uint MageTowerUncontrol = 2728; + public const uint FelReaverHordeControl = 2727; + public const uint FelReaverAllianceControl = 2726; + public const uint FelReaverUncontrol = 2725; + public const uint BloodElfHordeControl = 2724; + public const uint BloodElfAllianceControl = 2723; + public const uint BloodElfUncontrol = 2722; + public const uint ProgressBarPercentGrey = 2720; //100 = Empty (Only Grey); 0 = Blue|Red (No Grey) + public const uint ProgressBarStatus = 2719; //50 Init!; 48 ... Hordak Bere .. 33 .. 0 = Full 100% Hordacky; 100 = Full Alliance + public const uint ProgressBarShow = 2718; //1 Init; 0 Druhy Send - Bez Messagu; 1 = Controlled Aliance + public const uint NetherstormFlag = 2757; + //Set To 2 When Flag Is Picked Up; And To 1 If It Is Dropped + public const uint NetherstormFlagStateAlliance = 2769; + public const uint NetherstormFlagStateHorde = 2770; + } + + enum EotSProgressBarConsts + { + PointMaxCapturersCount = 5, + PointRadius = 70, + ProgressBarDontShow = 0, + ProgressBarShow = 1, + ProgressBarPercentGrey = 40, + ProgressBarStateMiddle = 50, + ProgressBarHordeControlled = 0, + ProgressBarNeutralLow = 30, + ProgressBarNeutralHigh = 70, + ProgressBarAliControlled = 100 + } + + struct EotSSoundIds + { + //strange ids, but sure about them + public const uint FlagPickedUpAlliance = 8212; + public const uint FlagCapturedHorde = 8213; + public const uint FlagPickedUpHorde = 8174; + public const uint FlagCapturedAlliance = 8173; + public const uint FlagReset = 8192; + } + + struct EotSObjectIds + { + public const uint ADoor = 184719; //Alliance Door + public const uint HDoor = 184720; //Horde Door + public const uint Flag1 = 184493; //Netherstorm Flag (Generic) + public const uint Flag2 = 184141; //Netherstorm Flag (Flagstand) + public const uint Flag3 = 184142; //Netherstorm Flag (Flagdrop) + public const uint ABanner = 184381; //Visual Banner (Alliance) + public const uint HBanner = 184380; //Visual Banner (Horde) + public const uint NBanner = 184382; //Visual Banner (Neutral) + public const uint BeTowerCap = 184080; //Be Tower Cap Pt + public const uint FrTowerCap = 184081; //Fel Reaver Cap Pt + public const uint HuTowerCap = 184082; //Human Tower Cap Pt + public const uint DrTowerCap = 184083; //Draenei Tower Cap Pt + } + + struct EotSPointsTrigger + { + public const uint BloodElfPoint = 4476; + public const uint FelReaverPoint = 4514; + public const uint MageTowerPoint = 4516; + public const uint DraeneiRuinsPoint = 4518; + public const uint BloodElfBuff = 4568; + public const uint FelReaverBuff = 4569; + public const uint MageTowerBuff = 4570; + public const uint DraeneiRuinsBuff = 4571; + } + + struct EotSGaveyardIds + { + public const int MainAlliance = 1103; + public const uint MainHorde = 1104; + public const uint FelReaver = 1105; + public const uint BloodElf = 1106; + public const uint DraeneiRuins = 1107; + public const uint MageTower = 1108; + } + + struct EotSPoints + { + public const int FelReaver = 0; + public const int BloodElf = 1; + public const int DraeneiRuins = 2; + public const int MageTower = 3; + + public const int PlayersOutOfPoints = 4; + public const int PointsMax = 4; + } + + enum EotSCreaturesTypes + { + SpiritFelReaver = 0, + SpiritBloodElf = 1, + SpiritDraeneiRuins = 2, + SpiritMageTower = 3, + SpiritMainAlliance = 4, + SpiritMainHorde = 5, + + TriggerFelReaver = 6, + TriggerBloodElf = 7, + TriggerDraeneiRuins = 8, + TriggerMageTower = 9, + + BgCreaturesMax = 10 + } + + struct EotSObjectTypes + { + public const int DoorA = 0; + public const int DoorH = 1; + public const int ABannerFelReaverCenter = 2; + public const int ABannerFelReaverLeft = 3; + public const int ABannerFelReaverRight = 4; + public const int ABannerBloodElfCenter = 5; + public const int ABannerBloodElfLeft = 6; + public const int ABannerBloodElfRight = 7; + public const int ABannerDraeneiRuinsCenter = 8; + public const int ABannerDraeneiRuinsLeft = 9; + public const int ABannerDraeneiRuinsRight = 10; + public const int ABannerMageTowerCenter = 11; + public const int ABannerMageTowerLeft = 12; + public const int ABannerMageTowerRight = 13; + public const int HBannerFelReaverCenter = 14; + public const int HBannerFelReaverLeft = 15; + public const int HBannerFelReaverRight = 16; + public const int HBannerBloodElfCenter = 17; + public const int HBannerBloodElfLeft = 18; + public const int HBannerBloodElfRight = 19; + public const int HBannerDraeneiRuinsCenter = 20; + public const int HBannerDraeneiRuinsLeft = 21; + public const int HBannerDraeneiRuinsRight = 22; + public const int HBannerMageTowerCenter = 23; + public const int HBannerMageTowerLeft = 24; + public const int HBannerMageTowerRight = 25; + public const int NBannerFelReaverCenter = 26; + public const int NBannerFelReaverLeft = 27; + public const int NBannerFelReaverRight = 28; + public const int NBannerBloodElfCenter = 29; + public const int NBannerBloodElfLeft = 30; + public const int NBannerBloodElfRight = 31; + public const int NBannerDraeneiRuinsCenter = 32; + public const int NBannerDraeneiRuinsLeft = 33; + public const int NBannerDraeneiRuinsRight = 34; + public const int NBannerMageTowerCenter = 35; + public const int NBannerMageTowerLeft = 36; + public const int NBannerMageTowerRight = 37; + public const int TowerCapFelReaver = 38; + public const int TowerCapBloodElf = 39; + public const int TowerCapDraeneiRuins = 40; + public const int TowerCapMageTower = 41; + public const int FlagNetherstorm = 42; + public const int FlagFelReaver = 43; + public const int FlagBloodElf = 44; + public const int FlagDraeneiRuins = 45; + public const int FlagMageTower = 46; + //Buffs + public const int SpeedbuffFelReaver = 47; + public const int RegenbuffFelReaver = 48; + public const int BerserkbuffFelReaver = 49; + public const int SpeedbuffBloodElf = 50; + public const int RegenbuffBloodElf = 51; + public const int BerserkbuffBloodElf = 52; + public const int SpeedbuffDraeneiRuins = 53; + public const int RegenbuffDraeneiRuins = 54; + public const int BerserkbuffDraeneiRuins = 55; + public const int SpeedbuffMageTower = 56; + public const int RegenbuffMageTower = 57; + public const int BerserkbuffMageTower = 58; + public const int Max = 59; + } + + struct EotSScoreIds + { + public const uint WarningNearVictoryScore = 1400; + public const uint MaxTeamScore = 1600; + } + + enum EotSFlagState + { + OnBase = 0, + WaitRespawn = 1, + OnPlayer = 2, + OnGround = 3 + } + + enum EotSPointState + { + NoOwner = 0, + Uncontrolled = 0, + UnderControl = 3 + } +} diff --git a/Game/BattleGrounds/Zones/IsleofConquest.cs b/Game/BattleGrounds/Zones/IsleofConquest.cs new file mode 100644 index 000000000..3a056fe88 --- /dev/null +++ b/Game/BattleGrounds/Zones/IsleofConquest.cs @@ -0,0 +1,63 @@ +/* + * Copyright (C) 2012-2017 CypherCore + * + * 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, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +namespace Game.BattleGrounds.Zones +{ + class IsleofConquest + { + } + + public struct ICCreatures + { + public const uint HighCommanderHalfordWyrmbane = 34924; // Alliance Boss + public const uint OverlordAgmar = 34922; // Horde Boss + public const uint KorKronGuard = 34918; // Horde Guard + public const uint SevenThLegionInfantry = 34919; // Alliance Guard + public const uint KeepCannon = 34944; + public const uint Demolisher = 34775; + public const uint SiegeEngineH = 35069; + public const uint SiegeEngineA = 34776; + public const uint GlaiveThrowerA = 34802; + public const uint GlaiveThrowerH = 35273; + public const uint Catapult = 34793; + public const uint HordeGunshipCannon = 34935; + public const uint AllianceGunshipCannon = 34929; + public const uint HordeGunshipCaptain = 35003; + public const uint AllianceGunshipCaptain = 34960; + public const uint WorldTriggerNotFloating = 34984; + public const uint WorldTriggerAllianceFriendly = 20213; + public const uint WorldTriggerHordeFriendly = 20212; + } + + public struct ICSpells + { + public const uint OilRefinery = 68719; + public const uint Quarry = 68720; + public const uint Parachute = 66656; + public const uint SlowFall = 12438; + public const uint DestroyedVehicleAchievement = 68357; + public const uint BackDoorJobAchievement = 68502; + public const uint DrivingCreditDemolisher = 68365; + public const uint DrivingCreditGlaive = 68363; + public const uint DrivingCreditSiege = 68364; + public const uint DrivingCreditCatapult = 68362; + public const uint SimpleTeleport = 12980; + public const uint TeleportVisualOnly = 51347; + public const uint ParachuteIc = 66657; + public const uint LaunchNoFallingDamage = 66251; + } +} diff --git a/Game/BattleGrounds/Zones/SilvershardMines.cs b/Game/BattleGrounds/Zones/SilvershardMines.cs new file mode 100644 index 000000000..ebd96bdc8 --- /dev/null +++ b/Game/BattleGrounds/Zones/SilvershardMines.cs @@ -0,0 +1,23 @@ +/* + * Copyright (C) 2012-2017 CypherCore + * + * 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, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +namespace Game.BattleGrounds.Zones +{ + class SilvershardMines + { + } +} diff --git a/Game/BattleGrounds/Zones/StrandofAncients.cs b/Game/BattleGrounds/Zones/StrandofAncients.cs new file mode 100644 index 000000000..91498f5c5 --- /dev/null +++ b/Game/BattleGrounds/Zones/StrandofAncients.cs @@ -0,0 +1,36 @@ +/* + * Copyright (C) 2012-2017 CypherCore + * + * 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, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +namespace Game.BattleGrounds.Zones +{ + class StrandofAncients + { + } + + public struct SACreatureIds + { + public const uint Kanrethad = 29; + public const uint InvisibleStalker = 15214; + public const uint WorldTrigger = 22515; + public const uint WorldTriggerLargeAoiNotImmunePcNpc = 23472; + + public const uint AntiPersonnalCannon = 27894; + public const uint DemolisherSa = 28781; + public const uint RiggerSparklight = 29260; + public const uint GorgrilRigspark = 29262; + } +} diff --git a/Game/BattleGrounds/Zones/TempleofKotmogu.cs b/Game/BattleGrounds/Zones/TempleofKotmogu.cs new file mode 100644 index 000000000..d8288f43c --- /dev/null +++ b/Game/BattleGrounds/Zones/TempleofKotmogu.cs @@ -0,0 +1,23 @@ +/* + * Copyright (C) 2012-2017 CypherCore + * + * 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, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +namespace Game.BattleGrounds.Zones +{ + class TempleofKotmogu + { + } +} diff --git a/Game/BattleGrounds/Zones/TwinPeaks.cs b/Game/BattleGrounds/Zones/TwinPeaks.cs new file mode 100644 index 000000000..df76a3df1 --- /dev/null +++ b/Game/BattleGrounds/Zones/TwinPeaks.cs @@ -0,0 +1,23 @@ +/* + * Copyright (C) 2012-2017 CypherCore + * + * 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, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +namespace Game.BattleGrounds.Zones +{ + class TwinPeaks + { + } +} diff --git a/Game/BattleGrounds/Zones/WarsongGluch.cs b/Game/BattleGrounds/Zones/WarsongGluch.cs new file mode 100644 index 000000000..e761e03de --- /dev/null +++ b/Game/BattleGrounds/Zones/WarsongGluch.cs @@ -0,0 +1,1108 @@ +/* + * Copyright (C) 2012-2017 CypherCore + * + * 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, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +using Framework.Constants; +using Game.DataStorage; +using Game.Entities; +using Game.Network.Packets; +using System.Collections.Generic; + +namespace Game.BattleGrounds +{ + class BgWarsongGluch : Battleground + { + public BgWarsongGluch() + { + StartMessageIds[BattlegroundConst.EventIdFirst] = CypherStrings.BgWsStartTwoMinutes; + StartMessageIds[BattlegroundConst.EventIdSecond] = CypherStrings.BgWsStartOneMinute; + StartMessageIds[BattlegroundConst.EventIdThird] = CypherStrings.BgWsStartHalfMinute; + StartMessageIds[BattlegroundConst.EventIdFourth] = CypherStrings.BgWsHasBegun; + } + + public override void PostUpdateImpl(uint diff) + { + if (GetStatus() == BattlegroundStatus.InProgress) + { + if (GetElapsedTime() >= 27 * Time.Minute * Time.InMilliseconds) + { + if (GetTeamScore(TeamId.Alliance) == 0) + { + if (GetTeamScore(TeamId.Horde) == 0) // No one scored - result is tie + EndBattleground(Team.Other); + else // Horde has more points and thus wins + EndBattleground(Team.Horde); + } + else if (GetTeamScore(TeamId.Horde) == 0) + EndBattleground(Team.Alliance); // Alliance has > 0, Horde has 0, alliance wins + else if (GetTeamScore(TeamId.Horde) == GetTeamScore(TeamId.Alliance)) // Team score equal, winner is team that scored the last flag + EndBattleground((Team)_lastFlagCaptureTeam); + else if (GetTeamScore(TeamId.Horde) > GetTeamScore(TeamId.Alliance)) // Last but not least, check who has the higher score + EndBattleground(Team.Horde); + else + EndBattleground(Team.Alliance); + } + // first update needed after 1 minute of game already in progress + else if (GetElapsedTime() > (_minutesElapsed * Time.Minute * Time.InMilliseconds) + 3 * Time.Minute * Time.InMilliseconds) + { + ++_minutesElapsed; + UpdateWorldState(WSGWorldStates.StateTimer, (uint)(25 - _minutesElapsed)); + } + + if (_flagState[TeamId.Alliance] == WSGFlagState.WaitRespawn) + { + _flagsTimer[TeamId.Alliance] -= (int)diff; + + if (_flagsTimer[TeamId.Alliance] < 0) + { + _flagsTimer[TeamId.Alliance] = 0; + RespawnFlag(Team.Alliance, true); + } + } + + if (_flagState[TeamId.Alliance] == WSGFlagState.OnGround) + { + _flagsDropTimer[TeamId.Alliance] -= (int)diff; + + if (_flagsDropTimer[TeamId.Alliance] < 0) + { + _flagsDropTimer[TeamId.Alliance] = 0; + RespawnFlagAfterDrop(Team.Alliance); + _bothFlagsKept = false; + } + } + + if (_flagState[TeamId.Horde] == WSGFlagState.WaitRespawn) + { + _flagsTimer[TeamId.Horde] -= (int)diff; + + if (_flagsTimer[TeamId.Horde] < 0) + { + _flagsTimer[TeamId.Horde] = 0; + RespawnFlag(Team.Horde, true); + } + } + + if (_flagState[TeamId.Horde] == WSGFlagState.OnGround) + { + _flagsDropTimer[TeamId.Horde] -= (int)diff; + + if (_flagsDropTimer[TeamId.Horde] < 0) + { + _flagsDropTimer[TeamId.Horde] = 0; + RespawnFlagAfterDrop(Team.Horde); + _bothFlagsKept = false; + } + } + + if (_bothFlagsKept) + { + _flagSpellForceTimer += (int)diff; + if (_flagDebuffState == 0 && _flagSpellForceTimer >= 10 * Time.Minute * Time.InMilliseconds) //10 minutes + { + Player player = Global.ObjAccessor.FindPlayer(m_FlagKeepers[0]); + if (player) + player.CastSpell(player, WSGSpellId.FocusedAssault, true); + + player = Global.ObjAccessor.FindPlayer(m_FlagKeepers[1]); + if (player) + player.CastSpell(player, WSGSpellId.FocusedAssault, true); + + _flagDebuffState = 1; + } + else if (_flagDebuffState == 1 && _flagSpellForceTimer >= 900000) //15 minutes + { + Player player = Global.ObjAccessor.FindPlayer(m_FlagKeepers[0]); + if (player) + { + player.RemoveAurasDueToSpell(WSGSpellId.FocusedAssault); + player.CastSpell(player, WSGSpellId.BrutalAssault, true); + } + + player = Global.ObjAccessor.FindPlayer(m_FlagKeepers[1]); + if (player) + { + player.RemoveAurasDueToSpell(WSGSpellId.FocusedAssault); + player.CastSpell(player, WSGSpellId.BrutalAssault, true); + } + _flagDebuffState = 2; + } + } + else + { + Player player = Global.ObjAccessor.FindPlayer(m_FlagKeepers[0]); + if (player) + { + player.RemoveAurasDueToSpell(WSGSpellId.FocusedAssault); + player.RemoveAurasDueToSpell(WSGSpellId.BrutalAssault); + } + + player = Global.ObjAccessor.FindPlayer(m_FlagKeepers[1]); + if (player) + { + player.RemoveAurasDueToSpell(WSGSpellId.FocusedAssault); + player.RemoveAurasDueToSpell(WSGSpellId.BrutalAssault); + } + + _flagSpellForceTimer = 0; //reset timer. + _flagDebuffState = 0; + } + } + } + + public override void GetPlayerPositionData(List positions) + { + Player player = Global.ObjAccessor.GetPlayer(GetBgMap(), m_FlagKeepers[TeamId.Alliance]); + if (player) + { + BattlegroundPlayerPosition position = new BattlegroundPlayerPosition(); + position.Guid = player.GetGUID(); + position.Pos.X = player.GetPositionX(); + position.Pos.Y = player.GetPositionY(); + position.IconID = BattlegroundConst.PlayerPositionIconAllianceFlag; + position.ArenaSlot = BattlegroundConst.PlayerPositionArenaSlotNone; + positions.Add(position); + } + + player = Global.ObjAccessor.GetPlayer(GetBgMap(), m_FlagKeepers[TeamId.Horde]); + if (player) + { + BattlegroundPlayerPosition position = new BattlegroundPlayerPosition(); + position.Guid = player.GetGUID(); + position.Pos.X = player.GetPositionX(); + position.Pos.Y = player.GetPositionY(); + position.IconID = BattlegroundConst.PlayerPositionIconHordeFlag; + position.ArenaSlot = BattlegroundConst.PlayerPositionArenaSlotNone; + positions.Add(position); + } + } + + public override void StartingEventCloseDoors() + { + for (int i = WSGObjectTypes.DoorA1; i <= WSGObjectTypes.DoorH4; ++i) + { + DoorClose(i); + SpawnBGObject(i, BattlegroundConst.RespawnImmediately); + } + for (int i = WSGObjectTypes.AFlag; i <= WSGObjectTypes.Berserkbuff2; ++i) + SpawnBGObject(i, BattlegroundConst.RespawnOneDay); + + UpdateWorldState(WSGWorldStates.StateTimerActive, 1); + UpdateWorldState(WSGWorldStates.StateTimer, 25); + } + + public override void StartingEventOpenDoors() + { + for (int i = WSGObjectTypes.DoorA1; i <= WSGObjectTypes.DoorA6; ++i) + DoorOpen(i); + for (int i = WSGObjectTypes.DoorH1; i <= WSGObjectTypes.DoorH4; ++i) + DoorOpen(i); + + for (int i = WSGObjectTypes.AFlag; i <= WSGObjectTypes.Berserkbuff2; ++i) + SpawnBGObject(i, BattlegroundConst.RespawnImmediately); + + SpawnBGObject(WSGObjectTypes.DoorA5, BattlegroundConst.RespawnOneDay); + SpawnBGObject(WSGObjectTypes.DoorA6, BattlegroundConst.RespawnOneDay); + SpawnBGObject(WSGObjectTypes.DoorH3, BattlegroundConst.RespawnOneDay); + SpawnBGObject(WSGObjectTypes.DoorH4, BattlegroundConst.RespawnOneDay); + + // players joining later are not eligibles + StartCriteriaTimer(CriteriaTimedTypes.Event, 8563); + } + + public override void AddPlayer(Player player) + { + base.AddPlayer(player); + PlayerScores[player.GetGUID()] = new BattlegroundWGScore(player.GetGUID(), player.GetBGTeam()); + } + + void RespawnFlag(Team Team, bool captured) + { + if (Team == Team.Alliance) + { + Log.outDebug(LogFilter.Battleground, "Respawn Alliance flag"); + _flagState[TeamId.Alliance] = WSGFlagState.OnBase; + } + else + { + Log.outDebug(LogFilter.Battleground, "Respawn Horde flag"); + _flagState[TeamId.Horde] = WSGFlagState.OnBase; + } + + if (captured) + { + //when map_update will be allowed for Battlegrounds this code will be useless + SpawnBGObject(WSGObjectTypes.HFlag, BattlegroundConst.RespawnImmediately); + SpawnBGObject(WSGObjectTypes.AFlag, BattlegroundConst.RespawnImmediately); + SendMessageToAll(CypherStrings.BgWsFPlaced, ChatMsg.BgSystemNeutral); + PlaySoundToAll(WSGSound.FlagsRespawned); // flag respawned sound... + } + _bothFlagsKept = false; + } + + void RespawnFlagAfterDrop(Team team) + { + if (GetStatus() != BattlegroundStatus.InProgress) + return; + + RespawnFlag(team, false); + if (team == Team.Alliance) + { + SpawnBGObject(WSGObjectTypes.AFlag, BattlegroundConst.RespawnImmediately); + SendMessageToAll(CypherStrings.BgWsAllianceFlagRespawned, ChatMsg.BgSystemNeutral); + } + else + { + SpawnBGObject(WSGObjectTypes.HFlag, BattlegroundConst.RespawnImmediately); + SendMessageToAll(CypherStrings.BgWsHordeFlagRespawned, ChatMsg.BgSystemNeutral); + } + + PlaySoundToAll(WSGSound.FlagsRespawned); + + GameObject obj = GetBgMap().GetGameObject(GetDroppedFlagGUID(team)); + if (obj) + obj.Delete(); + else + Log.outError(LogFilter.Battleground, "unknown droped flag ({0})", GetDroppedFlagGUID(team).ToString()); + + SetDroppedFlagGUID(ObjectGuid.Empty, GetTeamIndexByTeamId(team)); + _bothFlagsKept = false; + } + + void EventPlayerCapturedFlag(Player player) + { + if (GetStatus() != BattlegroundStatus.InProgress) + return; + + Team winner = 0; + + player.RemoveAurasWithInterruptFlags(SpellAuraInterruptFlags.EnterPvpCombat); + if (player.GetTeam() == Team.Alliance) + { + if (!IsHordeFlagPickedup()) + return; + SetHordeFlagPicker(ObjectGuid.Empty); // must be before aura remove to prevent 2 events (drop+capture) at the same time + // horde flag in base (but not respawned yet) + _flagState[TeamId.Horde] = WSGFlagState.WaitRespawn; + // Drop Horde Flag from Player + player.RemoveAurasDueToSpell(WSGSpellId.WarsongFlag); + if (_flagDebuffState == 1) + player.RemoveAurasDueToSpell(WSGSpellId.FocusedAssault); + else if (_flagDebuffState == 2) + player.RemoveAurasDueToSpell(WSGSpellId.BrutalAssault); + + if (GetTeamScore(TeamId.Alliance) < WSGTimerOrScore.MaxTeamScore) + AddPoint(Team.Alliance, 1); + PlaySoundToAll(WSGSound.FlagCapturedAlliance); + RewardReputationToTeam(890, m_ReputationCapture, Team.Alliance); + } + else + { + if (!IsAllianceFlagPickedup()) + return; + SetAllianceFlagPicker(ObjectGuid.Empty); // must be before aura remove to prevent 2 events (drop+capture) at the same time + // alliance flag in base (but not respawned yet) + _flagState[TeamId.Alliance] = WSGFlagState.WaitRespawn; + // Drop Alliance Flag from Player + player.RemoveAurasDueToSpell(WSGSpellId.SilverwingFlag); + if (_flagDebuffState == 1) + player.RemoveAurasDueToSpell(WSGSpellId.FocusedAssault); + else if (_flagDebuffState == 2) + player.RemoveAurasDueToSpell(WSGSpellId.BrutalAssault); + + if (GetTeamScore(TeamId.Horde) < WSGTimerOrScore.MaxTeamScore) + AddPoint(Team.Horde, 1); + PlaySoundToAll(WSGSound.FlagCapturedHorde); + RewardReputationToTeam(889, m_ReputationCapture, Team.Horde); + } + //for flag capture is reward 2 honorable kills + RewardHonorToTeam(GetBonusHonorFromKill(2), player.GetTeam()); + + SpawnBGObject(WSGObjectTypes.HFlag, WSGTimerOrScore.FlagRespawnTime); + SpawnBGObject(WSGObjectTypes.AFlag, WSGTimerOrScore.FlagRespawnTime); + + if (player.GetTeam() == Team.Alliance) + SendMessageToAll(CypherStrings.BgWsCapturedHf, ChatMsg.BgSystemAlliance, player); + else + SendMessageToAll(CypherStrings.BgWsCapturedAf, ChatMsg.BgSystemHorde, player); + + UpdateFlagState(player.GetTeam(), WSGFlagState.WaitRespawn); // flag state none + UpdateTeamScore(player.GetTeamId()); + // only flag capture should be updated + UpdatePlayerScore(player, ScoreType.FlagCaptures, 1); // +1 flag captures + + // update last flag capture to be used if teamscore is equal + SetLastFlagCapture(player.GetTeam()); + + if (GetTeamScore(TeamId.Alliance) == WSGTimerOrScore.MaxTeamScore) + winner = Team.Alliance; + + if (GetTeamScore(TeamId.Horde) == WSGTimerOrScore.MaxTeamScore) + winner = Team.Horde; + + if (winner != 0) + { + UpdateWorldState(WSGWorldStates.FlagUnkAlliance, 0); + UpdateWorldState(WSGWorldStates.FlagUnkHorde, 0); + UpdateWorldState(WSGWorldStates.FlagStateAlliance, 1); + UpdateWorldState(WSGWorldStates.FlagStateHorde, 1); + UpdateWorldState(WSGWorldStates.StateTimerActive, 0); + + RewardHonorToTeam(Honor[(int)m_HonorMode][(int)WSGRewards.Win], winner); + EndBattleground(winner); + } + else + { + _flagsTimer[GetTeamIndexByTeamId(player.GetTeam())] = WSGTimerOrScore.FlagRespawnTime; + } + } + + public override void EventPlayerDroppedFlag(Player player) + { + if (GetStatus() != BattlegroundStatus.InProgress) + { + // if not running, do not cast things at the dropper player (prevent spawning the "dropped" flag), neither send unnecessary messages + // just take off the aura + if (player.GetTeam() == Team.Alliance) + { + if (!IsHordeFlagPickedup()) + return; + + if (GetFlagPickerGUID(TeamId.Horde) == player.GetGUID()) + { + SetHordeFlagPicker(ObjectGuid.Empty); + player.RemoveAurasDueToSpell(WSGSpellId.WarsongFlag); + } + } + else + { + if (!IsAllianceFlagPickedup()) + return; + + if (GetFlagPickerGUID(TeamId.Alliance) == player.GetGUID()) + { + SetAllianceFlagPicker(ObjectGuid.Empty); + player.RemoveAurasDueToSpell(WSGSpellId.SilverwingFlag); + } + } + return; + } + + bool set = false; + + if (player.GetTeam() == Team.Alliance) + { + if (!IsHordeFlagPickedup()) + return; + if (GetFlagPickerGUID(TeamId.Horde) == player.GetGUID()) + { + SetHordeFlagPicker(ObjectGuid.Empty); + player.RemoveAurasDueToSpell(WSGSpellId.WarsongFlag); + if (_flagDebuffState == 1) + player.RemoveAurasDueToSpell(WSGSpellId.FocusedAssault); + else if (_flagDebuffState == 2) + player.RemoveAurasDueToSpell(WSGSpellId.BrutalAssault); + _flagState[TeamId.Horde] = WSGFlagState.OnGround; + player.CastSpell(player, WSGSpellId.WarsongFlagDropped, true); + set = true; + } + } + else + { + if (!IsAllianceFlagPickedup()) + return; + if (GetFlagPickerGUID(TeamId.Alliance) == player.GetGUID()) + { + SetAllianceFlagPicker(ObjectGuid.Empty); + player.RemoveAurasDueToSpell(WSGSpellId.SilverwingFlag); + if (_flagDebuffState == 1) + player.RemoveAurasDueToSpell(WSGSpellId.FocusedAssault); + else if (_flagDebuffState == 2) + player.RemoveAurasDueToSpell(WSGSpellId.BrutalAssault); + _flagState[TeamId.Alliance] = WSGFlagState.OnGround; + player.CastSpell(player, WSGSpellId.SilverwingFlagDropped, true); + set = true; + } + } + + if (set) + { + player.CastSpell(player, BattlegroundConst.SpellRecentlyDroppedFlag, true); + UpdateFlagState(player.GetTeam(), WSGFlagState.WaitRespawn); + + if (player.GetTeam() == Team.Alliance) + { + SendMessageToAll(CypherStrings.BgWsDroppedHf, ChatMsg.BgSystemHorde, player); + UpdateWorldState(WSGWorldStates.FlagUnkHorde, 0xFFFFFFFF); + } + else + { + SendMessageToAll(CypherStrings.BgWsDroppedAf, ChatMsg.BgSystemAlliance, player); + UpdateWorldState(WSGWorldStates.FlagUnkAlliance, 0xFFFFFFFF); + } + + _flagsDropTimer[GetTeamIndexByTeamId(GetOtherTeam(player.GetTeam()))] = WSGTimerOrScore.FlagDropTime; + } + } + + public override void EventPlayerClickedOnFlag(Player player, GameObject target_obj) + { + if (GetStatus() != BattlegroundStatus.InProgress) + return; + + CypherStrings message_id = 0; + ChatMsg type = ChatMsg.BgSystemNeutral; + + //alliance flag picked up from base + if (player.GetTeam() == Team.Horde && GetFlagState(Team.Alliance) == WSGFlagState.OnBase + && BgObjects[WSGObjectTypes.AFlag] == target_obj.GetGUID()) + { + message_id = CypherStrings.BgWsPickedupAf; + type = ChatMsg.BgSystemHorde; + PlaySoundToAll(WSGSound.AllianceFlagPickedUp); + SpawnBGObject(WSGObjectTypes.AFlag, BattlegroundConst.RespawnOneDay); + SetAllianceFlagPicker(player.GetGUID()); + _flagState[TeamId.Alliance] = WSGFlagState.OnPlayer; + //update world state to show correct flag carrier + UpdateFlagState(Team.Horde, WSGFlagState.OnPlayer); + UpdateWorldState(WSGWorldStates.FlagUnkAlliance, 1); + player.CastSpell(player, WSGSpellId.SilverwingFlag, true); + player.StartCriteriaTimer(CriteriaTimedTypes.SpellTarget, WSGSpellId.SilverwingFlagPicked); + if (_flagState[1] == WSGFlagState.OnPlayer) + _bothFlagsKept = true; + } + + //horde flag picked up from base + if (player.GetTeam() == Team.Alliance && GetFlagState(Team.Horde) == WSGFlagState.OnBase + && BgObjects[WSGObjectTypes.HFlag] == target_obj.GetGUID()) + { + message_id = CypherStrings.BgWsPickedupHf; + type = ChatMsg.BgSystemAlliance; + PlaySoundToAll(WSGSound.HordeFlagPickedUp); + SpawnBGObject(WSGObjectTypes.HFlag, BattlegroundConst.RespawnOneDay); + SetHordeFlagPicker(player.GetGUID()); + _flagState[TeamId.Horde] = WSGFlagState.OnPlayer; + //update world state to show correct flag carrier + UpdateFlagState(Team.Alliance, WSGFlagState.OnPlayer); + UpdateWorldState(WSGWorldStates.FlagUnkHorde, 1); + player.CastSpell(player, WSGSpellId.WarsongFlag, true); + player.StartCriteriaTimer(CriteriaTimedTypes.SpellTarget, WSGSpellId.WarsongFlagPicked); + if (_flagState[0] == WSGFlagState.OnPlayer) + _bothFlagsKept = true; + } + + //Alliance flag on ground(not in base) (returned or picked up again from ground!) + if (GetFlagState(Team.Alliance) == WSGFlagState.OnGround && player.IsWithinDistInMap(target_obj, 10) + && target_obj.GetGoInfo().entry == WSGObjectEntry.AFlagGround) + { + if (player.GetTeam() == Team.Alliance) + { + message_id = CypherStrings.BgWsReturnedAf; + type = ChatMsg.BgSystemAlliance; + UpdateFlagState(Team.Horde, WSGFlagState.WaitRespawn); + RespawnFlag(Team.Alliance, false); + SpawnBGObject(WSGObjectTypes.AFlag, BattlegroundConst.RespawnImmediately); + PlaySoundToAll(WSGSound.FlagReturned); + UpdatePlayerScore(player, ScoreType.FlagReturns, 1); + _bothFlagsKept = false; + } + else + { + message_id = CypherStrings.BgWsPickedupAf; + type = ChatMsg.BgSystemHorde; + PlaySoundToAll(WSGSound.AllianceFlagPickedUp); + SpawnBGObject(WSGObjectTypes.AFlag, BattlegroundConst.RespawnOneDay); + SetAllianceFlagPicker(player.GetGUID()); + player.CastSpell(player, WSGSpellId.SilverwingFlag, true); + _flagState[TeamId.Alliance] = WSGFlagState.OnPlayer; + UpdateFlagState(Team.Horde, WSGFlagState.OnPlayer); + if (_flagDebuffState == 1) + player.CastSpell(player, WSGSpellId.FocusedAssault, true); + else if (_flagDebuffState == 2) + player.CastSpell(player, WSGSpellId.BrutalAssault, true); + UpdateWorldState(WSGWorldStates.FlagUnkAlliance, 1); + } + //called in HandleGameObjectUseOpcode: + //target_obj.Delete(); + } + + //Horde flag on ground(not in base) (returned or picked up again) + if (GetFlagState(Team.Horde) == WSGFlagState.OnGround && player.IsWithinDistInMap(target_obj, 10) + && target_obj.GetGoInfo().entry == WSGObjectEntry.HFlagGround) + { + if (player.GetTeam() == Team.Horde) + { + message_id = CypherStrings.BgWsReturnedHf; + type = ChatMsg.BgSystemHorde; + UpdateFlagState(Team.Alliance, WSGFlagState.WaitRespawn); + RespawnFlag(Team.Horde, false); + SpawnBGObject(WSGObjectTypes.HFlag, BattlegroundConst.RespawnImmediately); + PlaySoundToAll(WSGSound.FlagReturned); + UpdatePlayerScore(player, ScoreType.FlagReturns, 1); + _bothFlagsKept = false; + } + else + { + message_id = CypherStrings.BgWsPickedupHf; + type = ChatMsg.BgSystemAlliance; + PlaySoundToAll(WSGSound.HordeFlagPickedUp); + SpawnBGObject(WSGObjectTypes.HFlag, BattlegroundConst.RespawnOneDay); + SetHordeFlagPicker(player.GetGUID()); + player.CastSpell(player, WSGSpellId.WarsongFlag, true); + _flagState[TeamId.Horde] = WSGFlagState.OnPlayer; + UpdateFlagState(Team.Alliance, WSGFlagState.OnPlayer); + if (_flagDebuffState == 1) + player.CastSpell(player, WSGSpellId.FocusedAssault, true); + else if (_flagDebuffState == 2) + player.CastSpell(player, WSGSpellId.BrutalAssault, true); + UpdateWorldState(WSGWorldStates.FlagUnkHorde, 1); + } + //called in HandleGameObjectUseOpcode: + //target_obj.Delete(); + } + + if (message_id == 0) + return; + + SendMessageToAll(message_id, type, player); + player.RemoveAurasWithInterruptFlags(SpellAuraInterruptFlags.EnterPvpCombat); + } + + public override void RemovePlayer(Player player, ObjectGuid guid, Team team) + { + // sometimes flag aura not removed :( + if (IsAllianceFlagPickedup() && m_FlagKeepers[TeamId.Alliance] == guid) + { + if (!player) + { + Log.outError(LogFilter.Battleground, "BattlegroundWS: Removing offline player who has the FLAG!!"); + SetAllianceFlagPicker(ObjectGuid.Empty); + RespawnFlag(Team.Alliance, false); + } + else + EventPlayerDroppedFlag(player); + } + if (IsHordeFlagPickedup() && m_FlagKeepers[TeamId.Horde] == guid) + { + if (!player) + { + Log.outError(LogFilter.Battleground, "BattlegroundWS: Removing offline player who has the FLAG!!"); + SetHordeFlagPicker(ObjectGuid.Empty); + RespawnFlag(Team.Horde, false); + } + else + EventPlayerDroppedFlag(player); + } + } + + void UpdateFlagState(Team team, WSGFlagState value) + { + if (team == Team.Alliance) + UpdateWorldState(WSGWorldStates.FlagStateAlliance, (uint)value); + else + UpdateWorldState(WSGWorldStates.FlagStateHorde, (uint)value); + } + + void UpdateTeamScore(int team) + { + if (team == TeamId.Alliance) + UpdateWorldState(WSGWorldStates.FlagCapturesAlliance, (uint)GetTeamScore(team)); + else + UpdateWorldState(WSGWorldStates.FlagCapturesHorde, (uint)GetTeamScore(team)); + } + + public override void HandleAreaTrigger(Player player, uint trigger, bool entered) + { + //uint SpellId = 0; + //uint64 buff_guid = 0; + switch (trigger) + { + case 8965: // Horde Start + case 8966: // Alliance Start + if (GetStatus() == BattlegroundStatus.WaitJoin && !entered) + TeleportPlayerToExploitLocation(player); + break; + case 3686: // Alliance elixir of speed spawn. Trigger not working, because located inside other areatrigger, can be replaced by IsWithinDist(object, dist) in Battleground.Update(). + //buff_guid = BgObjects[BG_WS_OBJECT_SPEEDBUFF_1]; + break; + case 3687: // Horde elixir of speed spawn. Trigger not working, because located inside other areatrigger, can be replaced by IsWithinDist(object, dist) in Battleground.Update(). + //buff_guid = BgObjects[BG_WS_OBJECT_SPEEDBUFF_2]; + break; + case 3706: // Alliance elixir of regeneration spawn + //buff_guid = BgObjects[BG_WS_OBJECT_REGENBUFF_1]; + break; + case 3708: // Horde elixir of regeneration spawn + //buff_guid = BgObjects[BG_WS_OBJECT_REGENBUFF_2]; + break; + case 3707: // Alliance elixir of berserk spawn + //buff_guid = BgObjects[BG_WS_OBJECT_BERSERKBUFF_1]; + break; + case 3709: // Horde elixir of berserk spawn + //buff_guid = BgObjects[BG_WS_OBJECT_BERSERKBUFF_2]; + break; + case 3646: // Alliance Flag spawn + if (_flagState[TeamId.Horde] != 0 && _flagState[TeamId.Alliance] == 0) + if (GetFlagPickerGUID(TeamId.Horde) == player.GetGUID()) + EventPlayerCapturedFlag(player); + break; + case 3647: // Horde Flag spawn + if (_flagState[TeamId.Alliance] != 0 && _flagState[TeamId.Horde] == 0) + if (GetFlagPickerGUID(TeamId.Alliance) == player.GetGUID()) + EventPlayerCapturedFlag(player); + break; + case 3649: // unk1 + case 3688: // unk2 + case 4628: // unk3 + case 4629: // unk4 + break; + default: + base.HandleAreaTrigger(player, trigger, entered); + break; + } + + //if (buff_guid) + // HandleTriggerBuff(buff_guid, player); + } + + public override bool SetupBattleground() + { + bool result = true; + result &= AddObject(WSGObjectTypes.AFlag, WSGObjectEntry.AFlag, 1540.423f, 1481.325f, 351.8284f, 3.089233f, 0, 0, 0.9996573f, 0.02617699f, WSGTimerOrScore.FlagRespawnTime / 1000); + result &= AddObject(WSGObjectTypes.HFlag, WSGObjectEntry.HFlag, 916.0226f, 1434.405f, 345.413f, 0.01745329f, 0, 0, 0.008726535f, 0.9999619f, WSGTimerOrScore.FlagRespawnTime / 1000); + if (!result) + { + Log.outError(LogFilter.Sql, "BgWarsongGluch: Failed to spawn flag object!"); + return false; + } + + // buffs + result &= AddObject(WSGObjectTypes.Speedbuff1, Buff_Entries[0], 1449.93f, 1470.71f, 342.6346f, -1.64061f, 0, 0, 0.7313537f, -0.6819983f, BattlegroundConst.BuffRespawnTime); + result &= AddObject(WSGObjectTypes.Speedbuff2, Buff_Entries[0], 1005.171f, 1447.946f, 335.9032f, 1.64061f, 0, 0, 0.7313537f, 0.6819984f, BattlegroundConst.BuffRespawnTime); + result &= AddObject(WSGObjectTypes.Regenbuff1, Buff_Entries[1], 1317.506f, 1550.851f, 313.2344f, -0.2617996f, 0, 0, 0.1305263f, -0.9914448f, BattlegroundConst.BuffRespawnTime); + result &= AddObject(WSGObjectTypes.Regenbuff2, Buff_Entries[1], 1110.451f, 1353.656f, 316.5181f, -0.6806787f, 0, 0, 0.333807f, -0.9426414f, BattlegroundConst.BuffRespawnTime); + result &= AddObject(WSGObjectTypes.Berserkbuff1, Buff_Entries[2], 1320.09f, 1378.79f, 314.7532f, 1.186824f, 0, 0, 0.5591929f, 0.8290376f, BattlegroundConst.BuffRespawnTime); + result &= AddObject(WSGObjectTypes.Berserkbuff2, Buff_Entries[2], 1139.688f, 1560.288f, 306.8432f, -2.443461f, 0, 0, 0.9396926f, -0.3420201f, BattlegroundConst.BuffRespawnTime); + if (!result) + { + Log.outError(LogFilter.Sql, "BgWarsongGluch: Failed to spawn buff object!"); + return false; + } + + // alliance gates + result &= AddObject(WSGObjectTypes.DoorA1, WSGObjectEntry.DoorA1, 1503.335f, 1493.466f, 352.1888f, 3.115414f, 0, 0, 0.9999143f, 0.01308903f, BattlegroundConst.RespawnImmediately); + result &= AddObject(WSGObjectTypes.DoorA2, WSGObjectEntry.DoorA2, 1492.478f, 1457.912f, 342.9689f, 3.115414f, 0, 0, 0.9999143f, 0.01308903f, BattlegroundConst.RespawnImmediately); + result &= AddObject(WSGObjectTypes.DoorA3, WSGObjectEntry.DoorA3, 1468.503f, 1494.357f, 351.8618f, 3.115414f, 0, 0, 0.9999143f, 0.01308903f, BattlegroundConst.RespawnImmediately); + result &= AddObject(WSGObjectTypes.DoorA4, WSGObjectEntry.DoorA4, 1471.555f, 1458.778f, 362.6332f, 3.115414f, 0, 0, 0.9999143f, 0.01308903f, BattlegroundConst.RespawnImmediately); + result &= AddObject(WSGObjectTypes.DoorA5, WSGObjectEntry.DoorA5, 1492.347f, 1458.34f, 342.3712f, -0.03490669f, 0, 0, 0.01745246f, -0.9998477f, BattlegroundConst.RespawnImmediately); + result &= AddObject(WSGObjectTypes.DoorA6, WSGObjectEntry.DoorA6, 1503.466f, 1493.367f, 351.7352f, -0.03490669f, 0, 0, 0.01745246f, -0.9998477f, BattlegroundConst.RespawnImmediately); + // horde gates + result &= AddObject(WSGObjectTypes.DoorH1, WSGObjectEntry.DoorH1, 949.1663f, 1423.772f, 345.6241f, -0.5756807f, -0.01673368f, -0.004956111f, -0.2839723f, 0.9586737f, BattlegroundConst.RespawnImmediately); + result &= AddObject(WSGObjectTypes.DoorH2, WSGObjectEntry.DoorH2, 953.0507f, 1459.842f, 340.6526f, -1.99662f, -0.1971825f, 0.1575096f, -0.8239487f, 0.5073641f, BattlegroundConst.RespawnImmediately); + result &= AddObject(WSGObjectTypes.DoorH3, WSGObjectEntry.DoorH3, 949.9523f, 1422.751f, 344.9273f, 0.0f, 0, 0, 0, 1, BattlegroundConst.RespawnImmediately); + result &= AddObject(WSGObjectTypes.DoorH4, WSGObjectEntry.DoorH4, 950.7952f, 1459.583f, 342.1523f, 0.05235988f, 0, 0, 0.02617695f, 0.9996573f, BattlegroundConst.RespawnImmediately); + if (!result) + { + Log.outError(LogFilter.Sql, "BgWarsongGluch: Failed to spawn door object Battleground not created!"); + return false; + } + + WorldSafeLocsRecord sg = CliDB.WorldSafeLocsStorage.LookupByKey(WSGGraveyards.MainAlliance); + if (sg == null || !AddSpiritGuide(0, sg.Loc.X, sg.Loc.Y, sg.Loc.Z, 3.124139f, TeamId.Alliance)) + { + Log.outError(LogFilter.Sql, "BgWarsongGluch: Failed to spawn Alliance spirit guide! Battleground not created!"); + return false; + } + + sg = CliDB.WorldSafeLocsStorage.LookupByKey(WSGGraveyards.MainHorde); + if (sg == null || !AddSpiritGuide(1, sg.Loc.X, sg.Loc.Y, sg.Loc.Z, 3.193953f, TeamId.Horde)) + { + Log.outError(LogFilter.Sql, "BgWarsongGluch: Failed to spawn Horde spirit guide! Battleground not created!"); + return false; + } + + return true; + } + + public override void Reset() + { + //call parent's class reset + base.Reset(); + + m_FlagKeepers[TeamId.Alliance].Clear(); + m_FlagKeepers[TeamId.Horde].Clear(); + m_DroppedFlagGUID[TeamId.Alliance] = ObjectGuid.Empty; + m_DroppedFlagGUID[TeamId.Horde] = ObjectGuid.Empty; + _flagState[TeamId.Alliance] = WSGFlagState.OnBase; + _flagState[TeamId.Horde] = WSGFlagState.OnBase; + m_TeamScores[TeamId.Alliance] = 0; + m_TeamScores[TeamId.Horde] = 0; + + if (Global.BattlegroundMgr.IsBGWeekend(GetTypeID())) + { + m_ReputationCapture = 45; + m_HonorWinKills = 3; + m_HonorEndKills = 4; + } + else + { + m_ReputationCapture = 35; + m_HonorWinKills = 1; + m_HonorEndKills = 2; + } + _minutesElapsed = 0; + _lastFlagCaptureTeam = 0; + _bothFlagsKept = false; + _flagDebuffState = 0; + _flagSpellForceTimer = 0; + _flagsDropTimer[TeamId.Alliance] = 0; + _flagsDropTimer[TeamId.Horde] = 0; + _flagsTimer[TeamId.Alliance] = 0; + _flagsTimer[TeamId.Horde] = 0; + } + + public override void EndBattleground(Team winner) + { + // Win reward + if (winner == Team.Alliance) + RewardHonorToTeam(GetBonusHonorFromKill(m_HonorWinKills), Team.Alliance); + if (winner == Team.Horde) + RewardHonorToTeam(GetBonusHonorFromKill(m_HonorWinKills), Team.Horde); + // Complete map_end rewards (even if no team wins) + RewardHonorToTeam(GetBonusHonorFromKill(m_HonorEndKills), Team.Alliance); + RewardHonorToTeam(GetBonusHonorFromKill(m_HonorEndKills), Team.Horde); + + base.EndBattleground(winner); + } + + public override void HandleKillPlayer(Player victim, Player killer) + { + if (GetStatus() != BattlegroundStatus.InProgress) + return; + + EventPlayerDroppedFlag(victim); + + base.HandleKillPlayer(victim, killer); + } + + public override bool UpdatePlayerScore(Player player, ScoreType type, uint value, bool doAddHonor = true) + { + if (!base.UpdatePlayerScore(player, type, value, doAddHonor)) + return false; + + switch (type) + { + case ScoreType.FlagCaptures: // flags captured + player.UpdateCriteria(CriteriaTypes.BgObjectiveCapture, 42); + break; + case ScoreType.FlagReturns: // flags returned + player.UpdateCriteria(CriteriaTypes.BgObjectiveCapture, 44); + break; + default: + break; + } + return true; + } + + public override WorldSafeLocsRecord GetClosestGraveYard(Player player) + { + //if status in progress, it returns main graveyards with spiritguides + //else it will return the graveyard in the flagroom - this is especially good + //if a player dies in preparation phase - then the player can't cheat + //and teleport to the graveyard outside the flagroom + //and start running around, while the doors are still closed + if (player.GetTeam() == Team.Alliance) + { + if (GetStatus() == BattlegroundStatus.InProgress) + return CliDB.WorldSafeLocsStorage.LookupByKey(WSGGraveyards.MainAlliance); + else + return CliDB.WorldSafeLocsStorage.LookupByKey(WSGGraveyards.FlagRoomAlliance); + } + else + { + if (GetStatus() == BattlegroundStatus.InProgress) + return CliDB.WorldSafeLocsStorage.LookupByKey(WSGGraveyards.MainHorde); + else + return CliDB.WorldSafeLocsStorage.LookupByKey(WSGGraveyards.FlagRoomHorde); + } + } + + public override WorldSafeLocsRecord GetExploitTeleportLocation(Team team) + { + return CliDB.WorldSafeLocsStorage.LookupByKey(team == Team.Alliance ? ExploitTeleportLocationAlliance : ExploitTeleportLocationHorde); + } + + public override void FillInitialWorldStates(InitWorldStates packet) + { + packet.AddState(WSGWorldStates.FlagCapturesAlliance, (int)GetTeamScore(TeamId.Alliance)); + packet.AddState(WSGWorldStates.FlagCapturesHorde, (int)GetTeamScore(TeamId.Horde)); + + if (_flagState[TeamId.Alliance] == WSGFlagState.OnGround) + packet.AddState(WSGWorldStates.FlagUnkAlliance, -1); + else if (_flagState[TeamId.Alliance] == WSGFlagState.OnPlayer) + packet.AddState(WSGWorldStates.FlagUnkAlliance, 1); + else + packet.AddState(WSGWorldStates.FlagUnkAlliance, 0); + + if (_flagState[TeamId.Horde] == WSGFlagState.OnGround) + packet.AddState(WSGWorldStates.FlagUnkHorde, -1); + else if (_flagState[TeamId.Horde] == WSGFlagState.OnPlayer) + packet.AddState(WSGWorldStates.FlagUnkHorde, 1); + else + packet.AddState(WSGWorldStates.FlagUnkHorde, 0); + + packet.AddState(WSGWorldStates.FlagCapturesMax, (int)WSGTimerOrScore.MaxTeamScore); + + if (GetStatus() == BattlegroundStatus.InProgress) + { + packet.AddState(WSGWorldStates.StateTimerActive, 1); + packet.AddState(WSGWorldStates.StateTimer, 25 - _minutesElapsed); + } + else + packet.AddState(WSGWorldStates.StateTimerActive, 0); + + if (_flagState[TeamId.Horde] == WSGFlagState.OnPlayer) + packet.AddState(WSGWorldStates.FlagStateHorde, 2); + else + packet.AddState(WSGWorldStates.FlagStateHorde, 1); + + if (_flagState[TeamId.Alliance] == WSGFlagState.OnPlayer) + packet.AddState(WSGWorldStates.FlagStateAlliance, 2); + else + packet.AddState(WSGWorldStates.FlagStateAlliance, 1); + } + + public override Team GetPrematureWinner() + { + if (GetTeamScore(TeamId.Alliance) > GetTeamScore(TeamId.Horde)) + return Team.Alliance; + else if (GetTeamScore(TeamId.Horde) > GetTeamScore(TeamId.Alliance)) + return Team.Horde; + + return base.GetPrematureWinner(); + } + + public override bool CheckAchievementCriteriaMeet(uint criteriaId, Player player, Unit target, uint miscValue) + { + switch (criteriaId) + { + case (uint)BattlegroundCriteriaId.SaveTheDay: + if (target) + { + Player playerTarget = target.ToPlayer(); + if (playerTarget) + return GetFlagState(playerTarget.GetTeam()) == WSGFlagState.OnBase; + } + return false; + } + + return base.CheckAchievementCriteriaMeet(criteriaId, player, target, miscValue); + } + + public override ObjectGuid GetFlagPickerGUID(int team = -1) + { + if (team == TeamId.Alliance || team == TeamId.Horde) + return m_FlagKeepers[team]; + + return ObjectGuid.Empty; + } + + void SetAllianceFlagPicker(ObjectGuid guid) { m_FlagKeepers[TeamId.Alliance] = guid; } + void SetHordeFlagPicker(ObjectGuid guid) { m_FlagKeepers[TeamId.Horde] = guid; } + bool IsAllianceFlagPickedup() { return !m_FlagKeepers[TeamId.Alliance].IsEmpty(); } + bool IsHordeFlagPickedup() { return !m_FlagKeepers[TeamId.Horde].IsEmpty(); } + WSGFlagState GetFlagState(Team team) { return _flagState[GetTeamIndexByTeamId(team)]; } + + void SetLastFlagCapture(Team team) { _lastFlagCaptureTeam = (uint)team; } + public override void SetDroppedFlagGUID(ObjectGuid guid, int team = -1) + { + if (team == (int)TeamId.Alliance || team == (int)TeamId.Horde) + m_DroppedFlagGUID[team] = guid; + + } + ObjectGuid GetDroppedFlagGUID(Team team) { return m_DroppedFlagGUID[GetTeamIndexByTeamId(team)]; } + + void AddPoint(Team team, uint Points = 1) { m_TeamScores[GetTeamIndexByTeamId(team)] += Points; } + void SetTeamPoint(Team team, uint Points = 0) { m_TeamScores[GetTeamIndexByTeamId(team)] = Points; } + void RemovePoint(Team team, uint Points = 1) { m_TeamScores[GetTeamIndexByTeamId(team)] -= Points; } + + ObjectGuid[] m_FlagKeepers = new ObjectGuid[2]; // 0 - alliance, 1 - horde + ObjectGuid[] m_DroppedFlagGUID = new ObjectGuid[2]; + WSGFlagState[] _flagState = new WSGFlagState[2]; // for checking flag state + int[] _flagsTimer = new int[2]; + int[] _flagsDropTimer = new int[2]; + uint _lastFlagCaptureTeam; // Winner is based on this if score is equal + + uint m_ReputationCapture; + uint m_HonorWinKills; + uint m_HonorEndKills; + int _flagSpellForceTimer; + bool _bothFlagsKept; + byte _flagDebuffState; // 0 - no debuffs, 1 - focused assault, 2 - brutal assault + byte _minutesElapsed; + + uint[][] Honor = + { + new uint[] {20, 40, 40 }, // normal honor + new uint[] { 60, 40, 80} // holiday + }; + const uint ExploitTeleportLocationAlliance = 3784; + const uint ExploitTeleportLocationHorde = 3785; + } + + class BattlegroundWGScore : BattlegroundScore + { + public BattlegroundWGScore(ObjectGuid playerGuid, Team team) : base(playerGuid, team) { } + + public override void UpdateScore(ScoreType type, uint value) + { + switch (type) + { + case ScoreType.FlagCaptures: // Flags captured + FlagCaptures += value; + break; + case ScoreType.FlagReturns: // Flags returned + FlagReturns += value; + break; + default: + base.UpdateScore(type, value); + break; + } + } + + public override void BuildObjectivesBlock(List stats) + { + stats.Add((int)FlagCaptures); + stats.Add((int)FlagReturns); + } + + public override uint GetAttr1() { return FlagCaptures; } + public override uint GetAttr2() { return FlagReturns; } + + uint FlagCaptures; + uint FlagReturns; + } + + #region Constants + enum WSGRewards + { + Win = 0, + FlapCap, + MapComplete, + RewardNum + } + enum WSGFlagState + { + OnBase = 0, + WaitRespawn = 1, + OnPlayer = 2, + OnGround = 3 + } + + struct WSGObjectTypes + { + public const int DoorA1 = 0; + public const int DoorA2 = 1; + public const int DoorA3 = 2; + public const int DoorA4 = 3; + public const int DoorA5 = 4; + public const int DoorA6 = 5; + public const int DoorH1 = 6; + public const int DoorH2 = 7; + public const int DoorH3 = 8; + public const int DoorH4 = 9; + public const int AFlag = 10; + public const int HFlag = 11; + public const int Speedbuff1 = 12; + public const int Speedbuff2 = 13; + public const int Regenbuff1 = 14; + public const int Regenbuff2 = 15; + public const int Berserkbuff1 = 16; + public const int Berserkbuff2 = 17; + public const int Max = 18; + } + struct WSGObjectEntry + { + public const uint DoorA1 = 179918; + public const uint DoorA2 = 179919; + public const uint DoorA3 = 179920; + public const uint DoorA4 = 179921; + public const uint DoorA5 = 180322; + public const uint DoorA6 = 180322; + public const uint DoorH1 = 179916; + public const uint DoorH2 = 179917; + public const uint DoorH3 = 180322; + public const uint DoorH4 = 180322; + public const uint AFlag = 179830; + public const uint HFlag = 179831; + public const uint AFlagGround = 179785; + public const uint HFlagGround = 179786; + } + + struct WSGWorldStates + { + public const uint FlagUnkAlliance = 1545; + public const uint FlagUnkHorde = 1546; + // FlagUnk = 1547; + public const uint FlagCapturesAlliance = 1581; + public const uint FlagCapturesHorde = 1582; + public const uint FlagCapturesMax = 1601; + public const uint FlagStateHorde = 2338; + public const uint FlagStateAlliance = 2339; + public const uint StateTimer = 4248; + public const uint StateTimerActive = 4247; + } + + struct WSGSpellId + { + public const uint WarsongFlag = 23333; + public const uint WarsongFlagDropped = 23334; + public const uint WarsongFlagPicked = 61266; // Fake Spell; Does Not Exist But Used As Timer Start Event + public const uint SilverwingFlag = 23335; + public const uint SilverwingFlagDropped = 23336; + public const uint SilverwingFlagPicked = 61265; // Fake Spell; Does Not Exist But Used As Timer Start Event + public const uint FocusedAssault = 46392; + public const uint BrutalAssault = 46393; + } + + struct WSGTimerOrScore + { + public const uint MaxTeamScore = 3; + public const int FlagRespawnTime = 23000; + public const int FlagDropTime = 10000; + public const uint SpellForceTime = 600000; + public const uint SpellBrutalTime = 900000; + } + + struct WSGGraveyards + { + public const uint FlagRoomAlliance = 769; + public const uint FlagRoomHorde = 770; + public const uint MainAlliance = 771; + public const uint MainHorde = 772; + } + + struct WSGSound + { + public const uint FlagCapturedAlliance = 8173; + public const uint FlagCapturedHorde = 8213; + public const uint FlagPlaced = 8232; + public const uint FlagReturned = 8192; + public const uint HordeFlagPickedUp = 8212; + public const uint AllianceFlagPickedUp = 8174; + public const uint FlagsRespawned = 8232; + } + #endregion +} diff --git a/Game/BattlePets/BattlePetManager.cs b/Game/BattlePets/BattlePetManager.cs new file mode 100644 index 000000000..d7f9ff6d9 --- /dev/null +++ b/Game/BattlePets/BattlePetManager.cs @@ -0,0 +1,498 @@ +/* + * Copyright (C) 2012-2017 CypherCore + * + * 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, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +using Framework.Constants; +using Framework.Database; +using Game.DataStorage; +using Game.Entities; +using Game.Network.Packets; +using System; +using System.Collections.Generic; +using System.Linq; + +namespace Game.BattlePets +{ + public class BattlePetMgr + { + public BattlePetMgr(WorldSession owner) + { + _owner = owner; + for (byte i = 0; i < SharedConst.MaxPetBattleSlots; ++i) + { + BattlePetSlot slot = new BattlePetSlot(); + slot.Index = i; + _slots.Add(slot); + } + } + + public static void Initialize() + { + SQLResult result = DB.Login.Query("SELECT MAX(guid) FROM battle_pets"); + if (!result.IsEmpty()) + Global.ObjectMgr.GetGenerator(HighGuid.BattlePet).Set(result.Read(0) + 1); + + foreach (var breedState in CliDB.BattlePetBreedStateStorage.Values) + { + if (!_battlePetBreedStates.ContainsKey(breedState.BreedID)) + _battlePetBreedStates[breedState.BreedID] = new Dictionary(); + + _battlePetBreedStates[breedState.BreedID][(BattlePetState)breedState.State] = breedState.Value; + } + + foreach (var speciesState in CliDB.BattlePetSpeciesStateStorage.Values) + { + if (!_battlePetSpeciesStates.ContainsKey(speciesState.SpeciesID)) + _battlePetSpeciesStates[speciesState.SpeciesID] = new Dictionary(); + + _battlePetSpeciesStates[speciesState.SpeciesID][(BattlePetState)speciesState.State] = speciesState.Value; + } + + LoadAvailablePetBreeds(); + LoadDefaultPetQualities(); + } + + static void LoadAvailablePetBreeds() + { + SQLResult result = DB.World.Query("SELECT speciesId, breedId FROM battle_pet_breeds"); + if (result.IsEmpty()) + { + Log.outInfo(LogFilter.ServerLoading, "Loaded 0 battle pet breeds. DB table `battle_pet_breeds` is empty."); + return; + } + + uint count = 0; + do + { + uint speciesId = result.Read(0); + ushort breedId = result.Read(1); + + if (!CliDB.BattlePetSpeciesStorage.ContainsKey(speciesId)) + { + Log.outError(LogFilter.Sql, "Non-existing BattlePetSpecies.db2 entry {0} was referenced in `battle_pet_breeds` by row ({1}, {2}).", speciesId, speciesId, breedId); + continue; + } + + // TODO: verify breed id (3 - 12 (male) or 3 - 22 (male and female)) if needed + + _availableBreedsPerSpecies.Add(speciesId, (byte)breedId); + ++count; + } while (result.NextRow()); + + Log.outInfo(LogFilter.ServerLoading, "Loaded {0} battle pet breeds.", count); + } + + static void LoadDefaultPetQualities() + { + SQLResult result = DB.World.Query("SELECT speciesId, quality FROM battle_pet_quality"); + if (result.IsEmpty()) + { + Log.outInfo(LogFilter.ServerLoading, "Loaded 0 battle pet qualities. DB table `battle_pet_quality` is empty."); + return; + } + + do + { + uint speciesId = result.Read(0); + byte quality = result.Read(1); + + if (!CliDB.BattlePetSpeciesStorage.ContainsKey(speciesId)) + { + Log.outError(LogFilter.Sql, "Non-existing BattlePetSpecies.db2 entry {0} was referenced in `battle_pet_quality` by row ({1}, {2}).", speciesId, speciesId, quality); + continue; + } + + // TODO: verify quality (0 - 3 for player pets or 0 - 5 for both player and tamer pets) if needed + + _defaultQualityPerSpecies[speciesId] = quality; + } while (result.NextRow()); + + Log.outInfo(LogFilter.ServerLoading, "Loaded {0} battle pet qualities.", _defaultQualityPerSpecies.Count); + } + + public static ushort RollPetBreed(uint species) + { + var list = _availableBreedsPerSpecies.LookupByKey(species); + if (list.Empty()) + return 3; // default B/B + + return list.PickRandom(); + } + + public static byte GetDefaultPetQuality(uint species) + { + if (!_defaultQualityPerSpecies.ContainsKey(species)) + return 0; // default poor + + return _defaultQualityPerSpecies[species]; + } + + public void LoadFromDB(SQLResult petsResult, SQLResult slotsResult) + { + if (!petsResult.IsEmpty()) + { + do + { + uint species = petsResult.Read(1); + + BattlePetSpeciesRecord speciesEntry = CliDB.BattlePetSpeciesStorage.LookupByKey(species); + if (speciesEntry != null) + { + if (GetPetCount(species) >= SharedConst.MaxBattlePetsPerSpecies) + { + Log.outError(LogFilter.Misc, "Battlenet account with id {0} has more than 3 battle pets of species {1}", _owner.GetBattlenetAccountId(), species); + continue; + } + + BattlePet pet = new BattlePet(); + pet.PacketInfo.Guid = ObjectGuid.Create(HighGuid.BattlePet, petsResult.Read(0)); + pet.PacketInfo.Species = species; + pet.PacketInfo.Breed = petsResult.Read(2); + pet.PacketInfo.Level = petsResult.Read(3); + pet.PacketInfo.Exp = petsResult.Read(4); + pet.PacketInfo.Health = petsResult.Read(5); + pet.PacketInfo.Quality = petsResult.Read(6); + pet.PacketInfo.Flags = petsResult.Read(7); + pet.PacketInfo.Name = petsResult.Read(8); + pet.PacketInfo.CreatureID = speciesEntry.CreatureID; + pet.SaveInfo = BattlePetSaveInfo.Unchanged; + pet.CalculateStats(); + _pets[pet.PacketInfo.Guid.GetCounter()] = pet; + } + } while (petsResult.NextRow()); + } + + if (!slotsResult.IsEmpty()) + { + byte i = 0; // slots.GetRowCount() should equal MAX_BATTLE_PET_SLOTS + + do + { + _slots[i].Index = slotsResult.Read(0); + var battlePet = _pets.LookupByKey(slotsResult.Read(1)); + if (battlePet != null) + _slots[i].Pet = battlePet.PacketInfo; + _slots[i].Locked = slotsResult.Read(2); + i++; + } while (slotsResult.NextRow()); + } + } + + public void SaveToDB(SQLTransaction trans) + { + PreparedStatement stmt; + + foreach (var pair in _pets.ToList()) + { + switch (pair.Value.SaveInfo) + { + case BattlePetSaveInfo.New: + stmt = DB.Login.GetPreparedStatement(LoginStatements.INS_BATTLE_PETS); + stmt.AddValue(0, pair.Key); + stmt.AddValue(1, _owner.GetBattlenetAccountId()); + stmt.AddValue(2, pair.Value.PacketInfo.Species); + stmt.AddValue(3, pair.Value.PacketInfo.Breed); + stmt.AddValue(4, pair.Value.PacketInfo.Level); + stmt.AddValue(5, pair.Value.PacketInfo.Exp); + stmt.AddValue(6, pair.Value.PacketInfo.Health); + stmt.AddValue(7, pair.Value.PacketInfo.Quality); + stmt.AddValue(8, pair.Value.PacketInfo.Flags); + stmt.AddValue(9, pair.Value.PacketInfo.Name); + trans.Append(stmt); + pair.Value.SaveInfo = BattlePetSaveInfo.Unchanged; + break; + case BattlePetSaveInfo.Changed: + stmt = DB.Login.GetPreparedStatement(LoginStatements.UPD_BATTLE_PETS); + stmt.AddValue(0, pair.Value.PacketInfo.Level); + stmt.AddValue(1, pair.Value.PacketInfo.Exp); + stmt.AddValue(2, pair.Value.PacketInfo.Health); + stmt.AddValue(3, pair.Value.PacketInfo.Quality); + stmt.AddValue(4, pair.Value.PacketInfo.Flags); + stmt.AddValue(5, pair.Value.PacketInfo.Name); + stmt.AddValue(6, _owner.GetBattlenetAccountId()); + stmt.AddValue(7, pair.Key); + trans.Append(stmt); + pair.Value.SaveInfo = BattlePetSaveInfo.Unchanged; + break; + case BattlePetSaveInfo.Removed: + stmt = DB.Login.GetPreparedStatement(LoginStatements.DEL_BATTLE_PETS); + stmt.AddValue(0, _owner.GetBattlenetAccountId()); + stmt.AddValue(1, pair.Key); + trans.Append(stmt); + _pets.Remove(pair.Key); + break; + } + } + + stmt = DB.Login.GetPreparedStatement(LoginStatements.DEL_BATTLE_PET_SLOTS); + stmt.AddValue(0, _owner.GetBattlenetAccountId()); + trans.Append(stmt); + + foreach (var slot in _slots) + { + stmt = DB.Login.GetPreparedStatement(LoginStatements.INS_BATTLE_PET_SLOTS); + stmt.AddValue(0, slot.Index); + stmt.AddValue(1, _owner.GetBattlenetAccountId()); + stmt.AddValue(2, slot.Pet.Guid.GetCounter()); + stmt.AddValue(3, slot.Locked); + trans.Append(stmt); + } + } + + public BattlePet GetPet(ObjectGuid guid) + { + return _pets.LookupByKey(guid.GetCounter()); + } + + public void AddPet(uint species, uint creatureId, ushort level = 1) + { + ushort breed = 3;// default B/B + byte quality = 0; + + if (_availableBreedsPerSpecies.ContainsKey(species)) + breed = _availableBreedsPerSpecies[species].PickRandom(); + + if (_defaultQualityPerSpecies.ContainsKey(species)) + quality = _defaultQualityPerSpecies[species]; + + AddPet(species, creatureId, breed, quality, level); + } + + public void AddPet(uint species, uint creatureId, ushort breed, byte quality, ushort level = 1) + { + BattlePetSpeciesRecord battlePetSpecies = CliDB.BattlePetSpeciesStorage.LookupByKey(species); + if (battlePetSpecies == null) // should never happen + return; + + BattlePet pet = new BattlePet(); + pet.PacketInfo.Guid = ObjectGuid.Create(HighGuid.BattlePet, Global.ObjectMgr.GetGenerator(HighGuid.BattlePet).Generate()); + pet.PacketInfo.Species = species; + pet.PacketInfo.CreatureID = creatureId; + pet.PacketInfo.Level = level; + pet.PacketInfo.Exp = 0; + pet.PacketInfo.Flags = 0; + pet.PacketInfo.Breed = breed; + pet.PacketInfo.Quality = quality; + pet.PacketInfo.Name = ""; + pet.CalculateStats(); + pet.PacketInfo.Health = pet.PacketInfo.MaxHealth; + pet.SaveInfo = BattlePetSaveInfo.New; + + _pets[pet.PacketInfo.Guid.GetCounter()] = pet; + + List updates = new List(); + updates.Add(pet); + SendUpdates(updates, true); + + _owner.GetPlayer().UpdateCriteria(CriteriaTypes.OwnBattlePet, species); + } + + public void RemovePet(ObjectGuid guid) + { + BattlePet pet = GetPet(guid); + if (pet == null) + return; + + pet.SaveInfo = BattlePetSaveInfo.Removed; + + // spell is not unlearned on retail + /*if (GetPetCount(pet.PacketInfo.Species) == 0) + if (BattlePetSpeciesEntry const* speciesEntry = sBattlePetSpeciesStore.LookupEntry(pet.PacketInfo.Species)) + _owner.GetPlayer().RemoveSpell(speciesEntry.SummonSpellID);*/ + } + + public byte GetPetCount(uint species) + { + return (byte)_pets.Values.Count(battlePet => battlePet.PacketInfo.Species == species && battlePet.SaveInfo != BattlePetSaveInfo.Removed); + } + + public void UnlockSlot(byte slot) + { + if (!_slots[slot].Locked) + return; + + _slots[slot].Locked = false; + + PetBattleSlotUpdates updates = new PetBattleSlotUpdates(); + updates.Slots.Add(_slots[slot]); + updates.AutoSlotted = false; // what's this? + updates.NewSlot = true; // causes the "new slot unlocked" bubble to appear + _owner.SendPacket(updates); + } + + public List GetLearnedPets() + { + return _pets.Values.Where(p => p.SaveInfo != BattlePetSaveInfo.Removed).ToList(); + } + + public void CageBattlePet(ObjectGuid guid) + { + BattlePet pet = GetPet(guid); + if (pet == null) + return; + + List dest = new List(); + + if (_owner.GetPlayer().CanStoreNewItem(ItemConst.NullBag, ItemConst.NullSlot, dest, SharedConst.BattlePetCageItemId, 1) != InventoryResult.Ok) + return; + + Item item = _owner.GetPlayer().StoreNewItem(dest, SharedConst.BattlePetCageItemId, true); + if (!item) + return; + + item.SetModifier(ItemModifier.BattlePetSpeciesId, pet.PacketInfo.Species); + item.SetModifier(ItemModifier.BattlePetBreedData, (uint)(pet.PacketInfo.Breed | (pet.PacketInfo.Quality << 24))); + item.SetModifier(ItemModifier.BattlePetLevel, pet.PacketInfo.Level); + item.SetModifier(ItemModifier.BattlePetDisplayId, pet.PacketInfo.CreatureID); + + // FIXME: "You create: ." - item name missing in chat + _owner.GetPlayer().SendNewItem(item, 1, true, true); + + RemovePet(guid); + + BattlePetDeleted deletePet = new BattlePetDeleted(); + deletePet.PetGuid = guid; + _owner.SendPacket(deletePet); + } + + public void HealBattlePetsPct(byte pct) + { + // TODO: After each Pet Battle, any injured companion will automatically + // regain 50 % of the damage that was taken during combat + List updates = new List(); + + foreach (var pet in _pets.Values) + { + if (pet.PacketInfo.Health != pet.PacketInfo.MaxHealth) + { + pet.PacketInfo.Health += MathFunctions.CalculatePct(pet.PacketInfo.MaxHealth, pct); + // don't allow Health to be greater than MaxHealth + pet.PacketInfo.Health = Math.Min(pet.PacketInfo.Health, pet.PacketInfo.MaxHealth); + if (pet.SaveInfo != BattlePetSaveInfo.New) + pet.SaveInfo = BattlePetSaveInfo.Changed; + updates.Add(pet); + } + } + + SendUpdates(updates, false); + } + + public void SummonPet(ObjectGuid guid) + { + BattlePet pet = GetPet(guid); + if (pet == null) + return; + + BattlePetSpeciesRecord speciesEntry = CliDB.BattlePetSpeciesStorage.LookupByKey(pet.PacketInfo.Species); + if (speciesEntry == null) + return; + + // TODO: set proper CreatureID for spell DEFAULT_SUMMON_BATTLE_PET_SPELL (default EffectMiscValueA is 40721 - Murkimus the Gladiator) + _owner.GetPlayer().CastSpell(_owner.GetPlayer(), speciesEntry.SummonSpellID != 0 ? speciesEntry.SummonSpellID : SharedConst.DefaultSummonBattlePetSpell); + + // TODO: set pet level, quality... update fields + } + + void SendUpdates(List pets, bool petAdded) + { + BattlePetUpdates updates = new BattlePetUpdates(); + foreach (var pet in pets) + updates.Pets.Add(pet.PacketInfo); + + updates.PetAdded = petAdded; + _owner.SendPacket(updates); + } + + public void SendError(BattlePetError error, uint creatureId) + { + BattlePetErrorPacket battlePetError = new BattlePetErrorPacket(); + battlePetError.Result = error; + battlePetError.CreatureID = creatureId; + _owner.SendPacket(battlePetError); + } + + public BattlePetSlot GetSlot(byte slot) { return _slots[slot]; } + WorldSession GetOwner() { return _owner; } + + public ushort GetTrapLevel() { return _trapLevel; } + public List GetSlots() { return _slots; } + + WorldSession _owner; + ushort _trapLevel; + Dictionary _pets = new Dictionary(); + List _slots = new List(); + + static Dictionary> _battlePetBreedStates = new Dictionary>(); + static Dictionary> _battlePetSpeciesStates = new Dictionary>(); + static MultiMap _availableBreedsPerSpecies = new MultiMap(); + static Dictionary _defaultQualityPerSpecies = new Dictionary(); + + public class BattlePet + { + public void CalculateStats() + { + float health = 0.0f; + float power = 0.0f; + float speed = 0.0f; + + // get base breed stats + var breedState = _battlePetBreedStates.LookupByKey(PacketInfo.Breed); + if (breedState == null) // non existing breed id + return; + + health = breedState[BattlePetState.StatStamina]; + power = breedState[BattlePetState.StatPower]; + speed = breedState[BattlePetState.StatSpeed]; + + // modify stats depending on species - not all pets have this + var speciesState = _battlePetSpeciesStates.LookupByKey(PacketInfo.Species); + if (speciesState != null) + { + health += speciesState[BattlePetState.StatStamina]; + power += speciesState[BattlePetState.StatPower]; + speed += speciesState[BattlePetState.StatSpeed]; + } + + // modify stats by quality + foreach (var breedQualityRecord in CliDB.BattlePetBreedQualityStorage.Values) + { + if (breedQualityRecord.Quality == PacketInfo.Quality) + { + health *= breedQualityRecord.Modifier; + power *= breedQualityRecord.Modifier; + speed *= breedQualityRecord.Modifier; + break; + } + // TOOD: add check if pet has existing quality + } + + // scale stats depending on level + health *= PacketInfo.Level; + power *= PacketInfo.Level; + speed *= PacketInfo.Level; + + // set stats + // round, ceil or floor? verify this + PacketInfo.MaxHealth = (uint)((Math.Round(health / 20) + 100)); + PacketInfo.Power = (uint)(Math.Round(power / 100)); + PacketInfo.Speed = (uint)(Math.Round(speed / 100)); + } + + public BattlePetStruct PacketInfo; + public BattlePetSaveInfo SaveInfo; + } + } +} diff --git a/Game/BlackMarket/BlackMarketEntry.cs b/Game/BlackMarket/BlackMarketEntry.cs new file mode 100644 index 000000000..cffe49262 --- /dev/null +++ b/Game/BlackMarket/BlackMarketEntry.cs @@ -0,0 +1,231 @@ +/* + * Copyright (C) 2012-2017 CypherCore + * + * 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, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +using Framework.Collections; +using Framework.Constants; +using Framework.Database; +using Game.Entities; +using Game.Network.Packets; +using System.Collections.Generic; + +namespace Game.BlackMarket +{ + public class BlackMarketTemplate + { + public bool LoadFromDB(SQLFields fields) + { + MarketID = fields.Read(0); + SellerNPC = fields.Read(1); + Item.ItemID = fields.Read(2); + Quantity = fields.Read(3); + MinBid = fields.Read(4); + Duration = fields.Read(5); + Chance = fields.Read(6); + + var bonusListIDsTok = new StringArray(fields.Read(7), ' '); + List bonusListIDs = new List(); + foreach (string token in bonusListIDsTok) + bonusListIDs.Add(uint.Parse(token)); + + if (!bonusListIDs.Empty()) + { + Item.ItemBonus.HasValue = true; + Item.ItemBonus.Value.BonusListIDs = bonusListIDs; + } + + if (Global.ObjectMgr.GetCreatureTemplate(SellerNPC) == null) + { + Log.outError(LogFilter.Misc, "Black market template {0} does not have a valid seller. (Entry: {1})", MarketID, SellerNPC); + return false; + } + + if (Global.ObjectMgr.GetItemTemplate(Item.ItemID) == null) + { + Log.outError(LogFilter.Misc, "Black market template {0} does not have a valid item. (Entry: {1})", MarketID, Item.ItemID); + return false; + } + + return true; + } + + public uint MarketID; + public uint SellerNPC; + public uint Quantity; + public ulong MinBid; + public long Duration; + public float Chance; + public ItemInstance Item; + } + + public class BlackMarketEntry + { + public void Initialize(uint marketId, uint duration) + { + _marketId = marketId; + _secondsRemaining = duration; + } + + public void Update(long newTimeOfUpdate) + { + _secondsRemaining = (uint)(_secondsRemaining - (newTimeOfUpdate - Global.BlackMarketMgr.GetLastUpdate())); + } + + public BlackMarketTemplate GetTemplate() + { + return Global.BlackMarketMgr.GetTemplateByID(_marketId); + } + + public uint GetSecondsRemaining() + { + return (uint)(_secondsRemaining - (Time.UnixTime - Global.BlackMarketMgr.GetLastUpdate())); + } + + long GetExpirationTime() + { + return Time.UnixTime + GetSecondsRemaining(); + } + + public bool IsCompleted() + { + return GetSecondsRemaining() <= 0; + } + + public bool LoadFromDB(SQLFields fields) + { + _marketId = fields.Read(0); + + // Invalid MarketID + BlackMarketTemplate templ = Global.BlackMarketMgr.GetTemplateByID(_marketId); + if (templ == null) + { + Log.outError(LogFilter.Misc, "Black market auction {0} does not have a valid id.", _marketId); + return false; + } + + _currentBid = fields.Read(1); + _secondsRemaining = (uint)(fields.Read(2) - Global.BlackMarketMgr.GetLastUpdate()); + _numBids = fields.Read(3); + _bidder = fields.Read(4); + + // Either no bidder or existing player + if (_bidder != 0 && ObjectManager.GetPlayerAccountIdByGUID(ObjectGuid.Create(HighGuid.Player, _bidder)) == 0) // Probably a better way to check if player exists + { + Log.outError(LogFilter.Misc, "Black market auction {0} does not have a valid bidder (GUID: {1}).", _marketId, _bidder); + return false; + } + + return true; + } + + public void SaveToDB(SQLTransaction trans) + { + PreparedStatement stmt = DB.Characters.GetPreparedStatement(CharStatements.INS_BLACKMARKET_AUCTIONS); + + stmt.AddValue(0, _marketId); + stmt.AddValue(1, _currentBid); + stmt.AddValue(2, GetExpirationTime()); + stmt.AddValue(3, _numBids); + stmt.AddValue(4, _bidder); + + trans.Append(stmt); + } + + public void DeleteFromDB(SQLTransaction trans) + { + PreparedStatement stmt = DB.Characters.GetPreparedStatement(CharStatements.DEL_BLACKMARKET_AUCTIONS); + stmt.AddValue(0, _marketId); + trans.Append(stmt); + } + + public bool ValidateBid(ulong bid) + { + if (bid <= _currentBid) + return false; + + if (bid < _currentBid + GetMinIncrement()) + return false; + + if (bid >= BlackMarketConst.MaxBid) + return false; + + return true; + } + + public void PlaceBid(ulong bid, Player player, SQLTransaction trans) //Updated + { + if (bid < _currentBid) + return; + + _currentBid = bid; + ++_numBids; + + if (GetSecondsRemaining() < 30 * Time.Minute) + _secondsRemaining += 30 * Time.Minute; + + _bidder = player.GetGUID().GetCounter(); + + player.ModifyMoney(-(long)bid); + + + PreparedStatement stmt = DB.Characters.GetPreparedStatement(CharStatements.UPD_BLACKMARKET_AUCTIONS); + + stmt.AddValue(0, _currentBid); + stmt.AddValue(1, GetExpirationTime()); + stmt.AddValue(2, _numBids); + stmt.AddValue(3, _bidder); + stmt.AddValue(4, _marketId); + + trans.Append(stmt); + + Global.BlackMarketMgr.Update(true); + } + + public string BuildAuctionMailSubject(BMAHMailAuctionAnswers response) + { + return GetTemplate().Item.ItemID + ":0:" + response + ':' + GetMarketId() + ':' + GetTemplate().Quantity; + } + + public string BuildAuctionMailBody() + { + return GetTemplate().SellerNPC + ":" + _currentBid; + } + + + public uint GetMarketId() { return _marketId; } + + public ulong GetCurrentBid() { return _currentBid; } + void SetCurrentBid(ulong bid) { _currentBid = bid; } + + public uint GetNumBids() { return _numBids; } + void SetNumBids(uint numBids) { _numBids = numBids; } + + public ulong GetBidder() { return _bidder; } + void SetBidder(ulong bidder) { _bidder = bidder; } + + public ulong GetMinIncrement() { return (_currentBid / 20) - ((_currentBid / 20) % MoneyConstants.Gold); } //5% increase every bid (has to be round gold value) + + public void MailSent() { _mailSent = true; } // Set when mail has been sent + public bool GetMailSent() { return _mailSent; } + + uint _marketId; + ulong _currentBid; + uint _numBids; + ulong _bidder; + uint _secondsRemaining; + bool _mailSent; + } +} diff --git a/Game/BlackMarket/BlackMarketManager.cs b/Game/BlackMarket/BlackMarketManager.cs new file mode 100644 index 000000000..1d7a7db16 --- /dev/null +++ b/Game/BlackMarket/BlackMarketManager.cs @@ -0,0 +1,310 @@ +/* + * Copyright (C) 2012-2017 CypherCore + * + * 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, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +using Framework.Constants; +using Framework.Database; +using Game.Entities; +using Game.Mails; +using Game.Network.Packets; +using System.Collections.Generic; +using System.Linq; + +namespace Game.BlackMarket +{ + public class BlackMarketManager : Singleton + { + BlackMarketManager() { } + + public void LoadTemplates() + { + uint oldMSTime = Time.GetMSTime(); + + // Clear in case we are reloading + _templates.Clear(); + + SQLResult result = DB.World.Query("SELECT marketId, sellerNpc, itemEntry, quantity, minBid, duration, chance, bonusListIDs FROM blackmarket_template"); + if (result.IsEmpty()) + { + Log.outInfo(LogFilter.ServerLoading, "Loaded 0 black market templates. DB table `blackmarket_template` is empty."); + return; + } + + do + { + BlackMarketTemplate templ = new BlackMarketTemplate(); + + if (!templ.LoadFromDB(result.GetFields())) // Add checks + continue; + + AddTemplate(templ); + } while (result.NextRow()); + + Log.outInfo(LogFilter.ServerLoading, "Loaded {0} black market templates in {1} ms.", _templates.Count, Time.GetMSTimeDiffToNow(oldMSTime)); + } + + public void LoadAuctions() + { + uint oldMSTime = Time.GetMSTime(); + + // Clear in case we are reloading + _auctions.Clear(); + + PreparedStatement stmt = DB.Characters.GetPreparedStatement(CharStatements.SEL_BLACKMARKET_AUCTIONS); + SQLResult result = DB.Characters.Query(stmt); + if (result.IsEmpty()) + { + Log.outInfo(LogFilter.ServerLoading, "Loaded 0 black market auctions. DB table `blackmarket_auctions` is empty."); + return; + } + + _lastUpdate = Time.UnixTime; //Set update time before loading + + SQLTransaction trans = new SQLTransaction(); + do + { + BlackMarketEntry auction = new BlackMarketEntry(); + + if (!auction.LoadFromDB(result.GetFields())) + { + auction.DeleteFromDB(trans); + continue; + } + + if (auction.IsCompleted()) + { + auction.DeleteFromDB(trans); + continue; + } + + AddAuction(auction); + } while (result.NextRow()); + + DB.Characters.CommitTransaction(trans); + + Log.outInfo(LogFilter.ServerLoading, "Loaded {0} black market auctions in {1} ms.", _auctions.Count, Time.GetMSTimeDiffToNow(oldMSTime)); + } + + public void Update(bool updateTime = false) + { + SQLTransaction trans = new SQLTransaction(); + long now = Time.UnixTime; + foreach (var entry in _auctions.Values) + { + if (entry.IsCompleted() && entry.GetBidder() != 0) + SendAuctionWonMail(entry, trans); + + if (updateTime) + entry.Update(now); + } + + if (updateTime) + _lastUpdate = now; + + DB.Characters.CommitTransaction(trans); + } + + public void RefreshAuctions() + { + SQLTransaction trans = new SQLTransaction(); + // Delete completed auctions + foreach (var pair in _auctions.ToList()) + { + if (!pair.Value.IsCompleted()) + continue; + + pair.Value.DeleteFromDB(trans); + _auctions.Remove(pair.Key); + } + + DB.Characters.CommitTransaction(trans); + trans = new SQLTransaction(); + + List templates = new List(); + foreach (var pair in _templates) + { + if (GetAuctionByID(pair.Value.MarketID) != null) + continue; + if (!RandomHelper.randChance(pair.Value.Chance)) + continue; + + templates.Add(pair.Value); + } + + templates.RandomResize(WorldConfig.GetUIntValue(WorldCfg.BlackmarketMaxAuctions)); + + foreach (BlackMarketTemplate templat in templates) + { + BlackMarketEntry entry = new BlackMarketEntry(); + entry.Initialize(templat.MarketID, (uint)templat.Duration); + entry.SaveToDB(trans); + AddAuction(entry); + } + + DB.Characters.CommitTransaction(trans); + + Update(true); + } + + public bool IsEnabled() + { + return WorldConfig.GetBoolValue(WorldCfg.BlackmarketEnabled); + } + + public void BuildItemsResponse(BlackMarketRequestItemsResult packet, Player player) + { + packet.LastUpdateID = (int)_lastUpdate; + foreach (var pair in _auctions) + { + BlackMarketTemplate templ = pair.Value.GetTemplate(); + + BlackMarketItem item = new BlackMarketItem(); + item.MarketID = pair.Value.GetMarketId(); + item.SellerNPC = templ.SellerNPC; + item.Item = templ.Item; + item.Quantity = templ.Quantity; + + // No bids yet + if (pair.Value.GetNumBids() == 0) + { + item.MinBid = templ.MinBid; + item.MinIncrement = 1; + } + else + { + item.MinIncrement = pair.Value.GetMinIncrement(); // 5% increment minimum + item.MinBid = pair.Value.GetCurrentBid() + item.MinIncrement; + } + + item.CurrentBid = pair.Value.GetCurrentBid(); + item.SecondsRemaining = pair.Value.GetSecondsRemaining(); + item.HighBid = (pair.Value.GetBidder() == player.GetGUID().GetCounter()); + item.NumBids = pair.Value.GetNumBids(); + + packet.Items.Add(item); + } + } + + public void AddAuction(BlackMarketEntry auction) + { + _auctions[auction.GetMarketId()] = auction; + } + + public void AddTemplate(BlackMarketTemplate templ) + { + _templates[templ.MarketID] = templ; + } + + public void SendAuctionWonMail(BlackMarketEntry entry, SQLTransaction trans) + { + // Mail already sent + if (entry.GetMailSent()) + return; + + uint bidderAccId = 0; + ObjectGuid bidderGuid = ObjectGuid.Create(HighGuid.Player, entry.GetBidder()); + Player bidder = Global.ObjAccessor.FindConnectedPlayer(bidderGuid); + // data for gm.log + string bidderName = ""; + bool logGmTrade = false; + + if (bidder) + { + bidderAccId = bidder.GetSession().GetAccountId(); + bidderName = bidder.GetName(); + logGmTrade = bidder.GetSession().HasPermission(RBACPermissions.LogGmTrade); + } + else + { + bidderAccId = ObjectManager.GetPlayerAccountIdByGUID(bidderGuid); + if (bidderAccId == 0) // Account exists + return; + + logGmTrade = Global.AccountMgr.HasPermission(bidderAccId, RBACPermissions.LogGmTrade, Global.WorldMgr.GetRealmId().Realm); + + if (logGmTrade && !ObjectManager.GetPlayerNameByGUID(bidderGuid, out bidderName)) + bidderName = Global.ObjectMgr.GetCypherString(CypherStrings.Unknown); + } + + // Create item + BlackMarketTemplate templ = entry.GetTemplate(); + Item item = Item.CreateItem(templ.Item.ItemID, templ.Quantity); + if (!item) + return; + + if (templ.Item.ItemBonus.HasValue) + { + foreach (uint bonusList in templ.Item.ItemBonus.Value.BonusListIDs) + item.AddBonuses(bonusList); + } + + item.SetOwnerGUID(bidderGuid); + + item.SaveToDB(trans); + + // Log trade + if (logGmTrade) + Log.outCommand(bidderAccId, "GM {0} (Account: {1}) won item in blackmarket auction: {2} (Entry: {3} Count: {4}) and payed gold : {5}.", + bidderName, bidderAccId, item.GetTemplate().GetName(), item.GetEntry(), item.GetCount(), entry.GetCurrentBid() / MoneyConstants.Gold); + + if (bidder) + bidder.GetSession().SendBlackMarketWonNotification(entry, item); + + new MailDraft(entry.BuildAuctionMailSubject(BMAHMailAuctionAnswers.Won), entry.BuildAuctionMailBody()) + .AddItem(item) + .SendMailTo(trans, new MailReceiver(bidder, entry.GetBidder()),new MailSender(entry), MailCheckMask.Copied); + + entry.MailSent(); + } + + public void SendAuctionOutbidMail(BlackMarketEntry entry, SQLTransaction trans) + { + ObjectGuid oldBidder_guid = ObjectGuid.Create(HighGuid.Player, entry.GetBidder()); + Player oldBidder = Global.ObjAccessor.FindConnectedPlayer(oldBidder_guid); + + uint oldBidder_accId = 0; + if (!oldBidder) + oldBidder_accId = ObjectManager.GetPlayerAccountIdByGUID(oldBidder_guid); + + // old bidder exist + if (!oldBidder && oldBidder_accId == 0) + return; + + if (oldBidder) + oldBidder.GetSession().SendBlackMarketOutbidNotification(entry.GetTemplate()); + + new MailDraft(entry.BuildAuctionMailSubject(BMAHMailAuctionAnswers.Outbid), entry.BuildAuctionMailBody()) + .AddMoney(entry.GetCurrentBid()) + .SendMailTo(trans, new MailReceiver(oldBidder, entry.GetBidder()), new MailSender(entry), MailCheckMask.Copied); + } + + public BlackMarketEntry GetAuctionByID(uint marketId) + { + return _auctions.LookupByKey(marketId); + } + + public BlackMarketTemplate GetTemplateByID(uint marketId) + { + return _templates.LookupByKey(marketId); + } + + public long GetLastUpdate() { return _lastUpdate; } + + Dictionary _auctions = new Dictionary(); + Dictionary _templates = new Dictionary(); + long _lastUpdate; + } +} diff --git a/Game/Calendar/CalendarManager.cs b/Game/Calendar/CalendarManager.cs new file mode 100644 index 000000000..89349d5e2 --- /dev/null +++ b/Game/Calendar/CalendarManager.cs @@ -0,0 +1,751 @@ +/* + * Copyright (C) 2012-2017 CypherCore + * + * 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, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +using Framework.Constants; +using Framework.Database; +using Game.Entities; +using Game.Guilds; +using Game.Mails; +using Game.Network; +using Game.Network.Packets; +using System; +using System.Collections.Generic; +using System.Linq; + +namespace Game +{ + public class CalendarManager : Singleton + { + CalendarManager() + { + _events = new List(); + _invites = new MultiMap(); + } + + public void LoadFromDB() + { + uint count = 0; + _maxEventId = 0; + _maxInviteId = 0; + + // 0 1 2 3 4 5 6 7 8 + SQLResult result = DB.Characters.Query("SELECT EventID, Owner, Title, Description, EventType, TextureID, Date, Flags, LockDate FROM calendar_events"); + if (!result.IsEmpty()) + { + do + { + ulong eventID = result.Read(0); + ObjectGuid ownerGUID = ObjectGuid.Create(HighGuid.Player, result.Read(1)); + string title = result.Read(2); + string description = result.Read(3); + CalendarEventType type = (CalendarEventType)result.Read(4); + int textureID = result.Read(5); + uint date = result.Read(6); + CalendarFlags flags = (CalendarFlags)result.Read(7); + uint lockDate = result.Read(8); + ulong guildID = 0; + + if (flags.HasAnyFlag(CalendarFlags.GuildEvent) || flags.HasAnyFlag(CalendarFlags.WithoutInvites)) + guildID = Player.GetGuildIdFromDB(ownerGUID); + + CalendarEvent calendarEvent = new CalendarEvent(eventID, ownerGUID, guildID, type, textureID, date, flags, title, description, lockDate); + _events.Add(calendarEvent); + + _maxEventId = Math.Max(_maxEventId, eventID); + + ++count; + } + while (result.NextRow()); + } + + Log.outInfo(LogFilter.ServerLoading, "Loaded {0} calendar events", count); + count = 0; + + // 0 1 2 3 4 5 6 7 + result = DB.Characters.Query("SELECT InviteID, EventID, Invitee, Sender, Status, ResponseTime, ModerationRank, Note FROM calendar_invites"); + if (!result.IsEmpty()) + { + do + { + ulong inviteId = result.Read(0); + ulong eventId = result.Read(1); + ObjectGuid invitee = ObjectGuid.Create(HighGuid.Player, result.Read(2)); + ObjectGuid senderGUID = ObjectGuid.Create(HighGuid.Player, result.Read(3)); + CalendarInviteStatus status = (CalendarInviteStatus)result.Read(4); + uint responseTime = result.Read(5); + CalendarModerationRank rank = (CalendarModerationRank)result.Read(6); + string note = result.Read(7); + + CalendarInvite invite = new CalendarInvite(inviteId, eventId, invitee, senderGUID, responseTime, status, rank, note); + _invites.Add(eventId, invite); + + _maxInviteId = Math.Max(_maxInviteId, inviteId); + + ++count; + } + while (result.NextRow()); + } + + Log.outInfo(LogFilter.ServerLoading, "Loaded {0} calendar invites", count); + + for (ulong i = 1; i < _maxEventId; ++i) + if (GetEvent(i) == null) + _freeEventIds.Add(i); + + for (ulong i = 1; i < _maxInviteId; ++i) + if (GetInvite(i) == null) + _freeInviteIds.Add(i); + } + + public void AddEvent(CalendarEvent calendarEvent, CalendarSendEventType sendType) + { + _events.Add(calendarEvent); + UpdateEvent(calendarEvent); + SendCalendarEvent(calendarEvent.OwnerGuid, calendarEvent, sendType); + } + + public void AddInvite(CalendarEvent calendarEvent, CalendarInvite invite, SQLTransaction trans = null) + { + if (!calendarEvent.IsGuildAnnouncement() && calendarEvent.OwnerGuid != invite.InviteeGuid) + SendCalendarEventInvite(invite); + + if (!calendarEvent.IsGuildEvent() || invite.InviteeGuid == calendarEvent.OwnerGuid) + SendCalendarEventInviteAlert(calendarEvent, invite); + + if (!calendarEvent.IsGuildAnnouncement()) + { + _invites.Add(invite.EventId, invite); + UpdateInvite(invite, trans); + } + } + + public void RemoveEvent(ulong eventId, ObjectGuid remover) + { + CalendarEvent calendarEvent = GetEvent(eventId); + + if (calendarEvent == null) + { + SendCalendarCommandResult(remover, CalendarError.EventInvalid); + return; + } + + SendCalendarEventRemovedAlert(calendarEvent); + + SQLTransaction trans = new SQLTransaction(); + PreparedStatement stmt; + MailDraft mail = new MailDraft(calendarEvent.BuildCalendarMailSubject(remover), calendarEvent.BuildCalendarMailBody()); + + var eventInvites = _invites[eventId]; + for (int i = 0; i < eventInvites.Count; ++i) + { + CalendarInvite invite = eventInvites[i]; + stmt = DB.Characters.GetPreparedStatement(CharStatements.DEL_CALENDAR_INVITE); + stmt.AddValue(0, invite.InviteId); + trans.Append(stmt); + + // guild events only? check invite status here? + // When an event is deleted, all invited (accepted/declined? - verify) guildies are notified via in-game mail. (wowwiki) + if (!remover.IsEmpty() && invite.InviteeGuid != remover) + mail.SendMailTo(trans, new MailReceiver(invite.InviteeGuid.GetCounter()), new MailSender(calendarEvent), MailCheckMask.Copied); + } + + _invites.Remove(eventId); + + stmt = DB.Characters.GetPreparedStatement(CharStatements.DEL_CALENDAR_EVENT); + stmt.AddValue(0, eventId); + trans.Append(stmt); + DB.Characters.CommitTransaction(trans); + + _events.Remove(calendarEvent); + } + + public void RemoveInvite(ulong inviteId, ulong eventId, ObjectGuid remover) + { + CalendarEvent calendarEvent = GetEvent(eventId); + + if (calendarEvent == null) + return; + + CalendarInvite calendarInvite = null; + foreach (var invite in _invites[eventId]) + { + if (invite.InviteId == inviteId) + { + calendarInvite = invite; + break; + } + } + + if (calendarInvite == null) + return; + + SQLTransaction trans = new SQLTransaction(); + PreparedStatement stmt = DB.Characters.GetPreparedStatement(CharStatements.DEL_CALENDAR_INVITE); + stmt.AddValue(0, calendarInvite.InviteId); + trans.Append(stmt); + DB.Characters.CommitTransaction(trans); + + if (!calendarEvent.IsGuildEvent()) + SendCalendarEventInviteRemoveAlert(calendarInvite.InviteeGuid, calendarEvent, CalendarInviteStatus.Removed); + + SendCalendarEventInviteRemove(calendarEvent, calendarInvite, (uint)calendarEvent.Flags); + + // we need to find out how to use CALENDAR_INVITE_REMOVED_MAIL_SUBJECT to force client to display different mail + //if (itr._invitee != remover) + // MailDraft(calendarEvent.BuildCalendarMailSubject(remover), calendarEvent.BuildCalendarMailBody()) + // .SendMailTo(trans, MailReceiver(itr.GetInvitee()), calendarEvent, MAIL_CHECK_MASK_COPIED); + + _invites.Remove(eventId, calendarInvite); + } + + public void UpdateEvent(CalendarEvent calendarEvent) + { + SQLTransaction trans = new SQLTransaction(); + PreparedStatement stmt = DB.Characters.GetPreparedStatement(CharStatements.REP_CALENDAR_EVENT); + stmt.AddValue(0, calendarEvent.EventId); + stmt.AddValue(1, calendarEvent.OwnerGuid.GetCounter()); + stmt.AddValue(2, calendarEvent.Title); + stmt.AddValue(3, calendarEvent.Description); + stmt.AddValue(4, calendarEvent.EventType); + stmt.AddValue(5, calendarEvent.TextureId); + stmt.AddValue(6, calendarEvent.Date); + stmt.AddValue(7, calendarEvent.Flags); + stmt.AddValue(8, calendarEvent.LockDate); + trans.Append(stmt); + DB.Characters.CommitTransaction(trans); + } + + public void UpdateInvite(CalendarInvite invite, SQLTransaction trans = null) + { + PreparedStatement stmt = DB.Characters.GetPreparedStatement(CharStatements.REP_CALENDAR_INVITE); + stmt.AddValue(0, invite.InviteId); + stmt.AddValue(1, invite.EventId); + stmt.AddValue(2, invite.InviteeGuid.GetCounter()); + stmt.AddValue(3, invite.SenderGuid.GetCounter()); + stmt.AddValue(4, invite.Status); + stmt.AddValue(5, invite.ResponseTime); + stmt.AddValue(6, invite.Rank); + stmt.AddValue(7, invite.Note); + DB.Characters.ExecuteOrAppend(trans, stmt); + } + + public void RemoveAllPlayerEventsAndInvites(ObjectGuid guid) + { + foreach (var calendarEvent in _events) + if (calendarEvent.OwnerGuid == guid) + RemoveEvent(calendarEvent.EventId, ObjectGuid.Empty); // don't send mail if removing a character + + List playerInvites = GetPlayerInvites(guid); + foreach (var calendarInvite in playerInvites) + RemoveInvite(calendarInvite.InviteId, calendarInvite.EventId, guid); + } + + public void RemovePlayerGuildEventsAndSignups(ObjectGuid guid, ulong guildId) + { + foreach (var calendarEvent in _events) + if (calendarEvent.OwnerGuid == guid && (calendarEvent.IsGuildEvent() || calendarEvent.IsGuildAnnouncement())) + RemoveEvent(calendarEvent.EventId, guid); + + List playerInvites = GetPlayerInvites(guid); + foreach (var playerCalendarEvent in playerInvites) + { + CalendarEvent calendarEvent = GetEvent(playerCalendarEvent.EventId); + if (calendarEvent != null) + if (calendarEvent.IsGuildEvent() && calendarEvent.GuildId == guildId) + RemoveInvite(playerCalendarEvent.InviteId, playerCalendarEvent.EventId, guid); + } + } + + public CalendarEvent GetEvent(ulong eventId) + { + foreach (var calendarEvent in _events) + if (calendarEvent.EventId == eventId) + return calendarEvent; + + Log.outDebug(LogFilter.Calendar, "CalendarMgr:GetEvent: {0} not found!", eventId); + return null; + } + + public CalendarInvite GetInvite(ulong inviteId) + { + foreach (var calendarEvent in _invites.Values) + if (calendarEvent.InviteId == inviteId) + return calendarEvent; + + Log.outDebug(LogFilter.Calendar, "CalendarMgr:GetInvite: {0} not found!", inviteId); + return null; + } + + void FreeEventId(ulong id) + { + if (id == _maxEventId) + --_maxEventId; + else + _freeEventIds.Add(id); + } + + public ulong GetFreeEventId() + { + if (_freeEventIds.Empty()) + return ++_maxEventId; + + ulong eventId = _freeEventIds.FirstOrDefault(); + _freeEventIds.RemoveAt(0); + return eventId; + } + + void FreeInviteId(ulong id) + { + if (id == _maxInviteId) + --_maxInviteId; + else + _freeInviteIds.Add(id); + } + + public ulong GetFreeInviteId() + { + if (_freeInviteIds.Empty()) + return ++_maxInviteId; + + ulong inviteId = _freeInviteIds.FirstOrDefault(); + _freeInviteIds.RemoveAt(0); + return inviteId; + } + + public List GetPlayerEvents(ObjectGuid guid) + { + List events = new List(); + + foreach (var pair in _invites) + { + if (pair.Value.InviteeGuid == guid) + { + CalendarEvent Event = GetEvent(pair.Key); + if (Event != null) // null check added as attempt to fix #11512 + events.Add(Event); + } + } + Player player = Global.ObjAccessor.FindPlayer(guid); + if (player) + { + foreach (var calendarEvent in _events) + if (calendarEvent.GuildId == player.GetGuildId()) + events.Add(calendarEvent); + } + + return events; + } + + public List GetEventInvites(ulong eventId) + { + return _invites[eventId]; + } + + public List GetPlayerInvites(ObjectGuid guid) + { + List invites = new List(); + + foreach (var calendarEvent in _invites.Values) + { + if (calendarEvent.InviteeGuid == guid) + invites.Add(calendarEvent); + } + + return invites; + } + + public uint GetPlayerNumPending(ObjectGuid guid) + { + List invites = GetPlayerInvites(guid); + + uint pendingNum = 0; + foreach (var calendarEvent in invites) + { + switch (calendarEvent.Status) + { + case CalendarInviteStatus.Invited: + case CalendarInviteStatus.Tentative: + case CalendarInviteStatus.NotSignedUp: + ++pendingNum; + break; + default: + break; + } + } + + return pendingNum; + } + + public void SendCalendarEventInvite(CalendarInvite invite) + { + CalendarEvent calendarEvent = GetEvent(invite.EventId); + + ObjectGuid invitee = invite.InviteeGuid; + Player player = Global.ObjAccessor.FindPlayer(invitee); + + uint level = player ? player.getLevel() : Player.GetLevelFromDB(invitee); + + SCalendarEventInvite packet = new SCalendarEventInvite(); + packet.EventID = calendarEvent != null ? calendarEvent.EventId : 0; + packet.InviteGuid = invitee; + packet.InviteID = calendarEvent != null ? invite.InviteId : 0; + packet.Level = (byte)level; + packet.ResponseTime = invite.ResponseTime; + packet.Status = invite.Status; + packet.Type = (byte)(calendarEvent != null ? calendarEvent.IsGuildEvent() ? 1 : 0 : 0); // Correct ? + packet.ClearPending = calendarEvent != null ? !calendarEvent.IsGuildEvent() : true; // Correct ? + + if (calendarEvent == null) // Pre-invite + { + player = Global.ObjAccessor.FindPlayer(invite.SenderGuid); + if (player) + player.SendPacket(packet); + } + else + { + if (calendarEvent.OwnerGuid != invite.InviteeGuid) // correct? + SendPacketToAllEventRelatives(packet, calendarEvent); + } + } + + public void SendCalendarEventUpdateAlert(CalendarEvent calendarEvent, long originalDate) + { + CalendarEventUpdatedAlert packet = new CalendarEventUpdatedAlert(); + packet.ClearPending = true; // FIXME + packet.Date = calendarEvent.Date; + packet.Description = calendarEvent.Description; + packet.EventID = calendarEvent.EventId; + packet.EventName = calendarEvent.Title; + packet.EventType = calendarEvent.EventType; + packet.Flags = calendarEvent.Flags; + packet.LockDate = calendarEvent.LockDate; // Always 0 ? + packet.OriginalDate = originalDate; + packet.TextureID = calendarEvent.TextureId; + + SendPacketToAllEventRelatives(packet, calendarEvent); + } + + public void SendCalendarEventStatus(CalendarEvent calendarEvent, CalendarInvite invite) + { + CalendarEventInviteStatus packet = new CalendarEventInviteStatus(); + packet.ClearPending = true; // FIXME + packet.Date = calendarEvent.Date; + packet.EventID = calendarEvent.EventId; + packet.Flags = calendarEvent.Flags; + packet.InviteGuid = invite.InviteeGuid; + packet.ResponseTime = invite.ResponseTime; + packet.Status = invite.Status; + + SendPacketToAllEventRelatives(packet, calendarEvent); + } + + void SendCalendarEventRemovedAlert(CalendarEvent calendarEvent) + { + CalendarEventRemovedAlert packet = new CalendarEventRemovedAlert(); + packet.ClearPending = true; // FIXME + packet.Date = calendarEvent.Date; + packet.EventID = calendarEvent.EventId; + + SendPacketToAllEventRelatives(packet, calendarEvent); + } + + void SendCalendarEventInviteRemove(CalendarEvent calendarEvent, CalendarInvite invite, uint flags) + { + CalendarEventInviteRemoved packet = new CalendarEventInviteRemoved(); + packet.ClearPending = true; // FIXME + packet.EventID = calendarEvent.EventId; + packet.Flags = flags; + packet.InviteGuid = invite.InviteeGuid; + + SendPacketToAllEventRelatives(packet, calendarEvent); + } + + public void SendCalendarEventModeratorStatusAlert(CalendarEvent calendarEvent, CalendarInvite invite) + { + CalendarEventInviteModeratorStatus packet = new CalendarEventInviteModeratorStatus(); + packet.ClearPending = true; // FIXME + packet.EventID = calendarEvent.EventId; + packet.InviteGuid = invite.InviteeGuid; + packet.Status = invite.Status; + + SendPacketToAllEventRelatives(packet, calendarEvent); + } + + void SendCalendarEventInviteAlert(CalendarEvent calendarEvent, CalendarInvite invite) + { + CalendarEventInviteAlert packet = new CalendarEventInviteAlert(); + packet.Date = calendarEvent.Date; + packet.EventID = calendarEvent.EventId; + packet.EventName = calendarEvent.Title; + packet.EventType = calendarEvent.EventType; + packet.Flags = calendarEvent.Flags; + packet.InviteID = invite.InviteId; + packet.InvitedByGuid = invite.SenderGuid; + packet.ModeratorStatus = invite.Rank; + packet.OwnerGuid = calendarEvent.OwnerGuid; + packet.Status = invite.Status; + packet.TextureID = calendarEvent.TextureId; + + Guild guild = Global.GuildMgr.GetGuildById(calendarEvent.GuildId); + packet.EventGuildID = guild ? guild.GetGUID() : ObjectGuid.Empty; + + if (calendarEvent.IsGuildEvent() || calendarEvent.IsGuildAnnouncement()) + { + guild = Global.GuildMgr.GetGuildById(calendarEvent.GuildId); + if (guild) + guild.BroadcastPacket(packet); + } + else + { + Player player = Global.ObjAccessor.FindPlayer(invite.InviteeGuid); + if (player) + player.SendPacket(packet); + } + } + + public void SendCalendarEvent(ObjectGuid guid, CalendarEvent calendarEvent, CalendarSendEventType sendType) + { + Player player = Global.ObjAccessor.FindPlayer(guid); + if (!player) + return; + + List eventInviteeList = _invites[calendarEvent.EventId]; + + CalendarSendEvent packet = new CalendarSendEvent(); + packet.Date = calendarEvent.Date; + packet.Description = calendarEvent.Description; + packet.EventID = calendarEvent.EventId; + packet.EventName = calendarEvent.Title; + packet.EventType = sendType; + packet.Flags = calendarEvent.Flags; + packet.GetEventType = calendarEvent.EventType; + packet.LockDate = calendarEvent.LockDate; // Always 0 ? + packet.OwnerGuid = calendarEvent.OwnerGuid; + packet.TextureID = calendarEvent.TextureId; + + Guild guild = Global.GuildMgr.GetGuildById(calendarEvent.GuildId); + packet.EventGuildID = (guild ? guild.GetGUID() : ObjectGuid.Empty); + + foreach (var calendarInvite in eventInviteeList) + { + ObjectGuid inviteeGuid = calendarInvite.InviteeGuid; + Player invitee = Global.ObjAccessor.FindPlayer(inviteeGuid); + + uint inviteeLevel = invitee ? invitee.getLevel() : Player.GetLevelFromDB(inviteeGuid); + uint inviteeGuildId = invitee ? invitee.GetGuildId() : Player.GetGuildIdFromDB(inviteeGuid); + + CalendarEventInviteInfo inviteInfo = new CalendarEventInviteInfo(); + inviteInfo.Guid = inviteeGuid; + inviteInfo.Level = (byte)inviteeLevel; + inviteInfo.Status = calendarInvite.Status; + inviteInfo.Moderator = calendarInvite.Rank; + inviteInfo.InviteType = (byte)(calendarEvent.IsGuildEvent() && calendarEvent.GuildId == inviteeGuildId ? 1 : 0); + inviteInfo.InviteID = calendarInvite.InviteId; + inviteInfo.ResponseTime = calendarInvite.ResponseTime; + inviteInfo.Notes = calendarInvite.Note; + + packet.Invites.Add(inviteInfo); + } + + player.SendPacket(packet); + } + + void SendCalendarEventInviteRemoveAlert(ObjectGuid guid, CalendarEvent calendarEvent, CalendarInviteStatus status) + { + Player player = Global.ObjAccessor.FindPlayer(guid); + if (player) + { + CalendarEventInviteRemovedAlert packet = new CalendarEventInviteRemovedAlert(); + packet.Date = calendarEvent.Date; + packet.EventID = calendarEvent.EventId; + packet.Flags = calendarEvent.Flags; + packet.Status = status; + + player.SendPacket(packet); + } + } + + public void SendCalendarClearPendingAction(ObjectGuid guid) + { + Player player = Global.ObjAccessor.FindPlayer(guid); + if (player) + player.SendPacket(new CalendarClearPendingAction()); + } + + public void SendCalendarCommandResult(ObjectGuid guid, CalendarError err, string param = null) + { + Player player = Global.ObjAccessor.FindPlayer(guid); + if (player) + { + CalendarCommandResult packet = new CalendarCommandResult(); + packet.Command = 1; // FIXME + packet.Result = err; + + switch (err) + { + case CalendarError.OtherInvitesExceeded: + case CalendarError.AlreadyInvitedToEventS: + case CalendarError.IgnoringYouS: + packet.Name = param; + break; + } + + player.SendPacket(packet); + } + } + + void SendPacketToAllEventRelatives(ServerPacket packet, CalendarEvent calendarEvent) + { + // Send packet to all guild members + if (calendarEvent.IsGuildEvent() || calendarEvent.IsGuildAnnouncement()) + { + Guild guild = Global.GuildMgr.GetGuildById(calendarEvent.GuildId); + if (guild) + guild.BroadcastPacket(packet); + } + + // Send packet to all invitees if event is non-guild, in other case only to non-guild invitees (packet was broadcasted for them) + List invites = _invites[calendarEvent.EventId]; + foreach (var playerCalendarEvent in invites) + { + Player player = Global.ObjAccessor.FindPlayer(playerCalendarEvent.InviteeGuid); + if (player) + if (!calendarEvent.IsGuildEvent() || (calendarEvent.IsGuildEvent() && player.GetGuildId() != calendarEvent.GuildId)) + player.SendPacket(packet); + } + } + + List _events; + MultiMap _invites; + + List _freeEventIds = new List(); + List _freeInviteIds = new List(); + ulong _maxEventId; + ulong _maxInviteId; + } + + public class CalendarInvite + { + public CalendarInvite() + { + InviteId = 1; + ResponseTime = Time.UnixTime; + Status = CalendarInviteStatus.Invited; + Rank = CalendarModerationRank.Player; + Note = ""; + } + public CalendarInvite(CalendarInvite calendarInvite, ulong inviteId, ulong eventId) + { + InviteId = inviteId; + EventId = eventId; + InviteeGuid = calendarInvite.InviteeGuid; + SenderGuid = calendarInvite.SenderGuid; + ResponseTime = calendarInvite.ResponseTime; + Status = calendarInvite.Status; + Rank = calendarInvite.Rank; + Note = calendarInvite.Note; + } + public CalendarInvite(ulong inviteId, ulong eventId, ObjectGuid invitee, ObjectGuid senderGUID, long responseTime, CalendarInviteStatus status, CalendarModerationRank rank, string note) + { + InviteId = inviteId; + EventId = eventId; + InviteeGuid = invitee; + SenderGuid = senderGUID; + ResponseTime = responseTime; + + Status = status; + Rank = rank; + Note = note; + } + + public ulong InviteId { get; set; } + public ulong EventId { get; set; } + public ObjectGuid InviteeGuid { get; set; } + public ObjectGuid SenderGuid { get; set; } + public long ResponseTime { get; set; } + public CalendarInviteStatus Status { get; set; } + public CalendarModerationRank Rank { get; set; } + public string Note { get; set; } + } + + public class CalendarEvent + { + public CalendarEvent(CalendarEvent calendarEvent, ulong eventId) + { + EventId = eventId; + OwnerGuid = calendarEvent.OwnerGuid; + GuildId = calendarEvent.GuildId; + EventType = calendarEvent.EventType; + TextureId = calendarEvent.TextureId; + Date = calendarEvent.Date; + Flags = calendarEvent.Flags; + LockDate = calendarEvent.LockDate; + Title = calendarEvent.Title; + Description = calendarEvent.Description; + } + + public CalendarEvent(ulong eventId, ObjectGuid ownerGuid, ulong guildId, CalendarEventType type, int textureId, long date, CalendarFlags flags, string title, string description, long lockDate) + { + EventId = eventId; + OwnerGuid = ownerGuid; + GuildId = guildId; + EventType = type; + TextureId = textureId; + Date = date; + Flags = flags; + LockDate = lockDate; + Title = title; + Description = description; + } + + public CalendarEvent() + { + EventId = 1; + EventType = CalendarEventType.Other; + TextureId = -1; + Title = ""; + Description = ""; + } + + public string BuildCalendarMailSubject(ObjectGuid remover) + { + return remover + ":" + Title; + } + + public string BuildCalendarMailBody() + { + var now = Time.UnixTimeToDateTime(Date); + uint time = Convert.ToUInt32(((now.Year - 1900) - 100) << 24 | (now.Month - 1) << 20 | (now.Day - 1) << 14 | (int)now.DayOfWeek << 11 | now.Hour << 6 | now.Minute); + return time.ToString(); + } + + public bool IsGuildEvent() { return Flags.HasAnyFlag(CalendarFlags.GuildEvent); } + public bool IsGuildAnnouncement() { return Flags.HasAnyFlag(CalendarFlags.WithoutInvites); } + public bool IsLocked() { return Flags.HasAnyFlag(CalendarFlags.InvitesLocked); } + + public ulong EventId { get; set; } + public ObjectGuid OwnerGuid { get; set; } + public ulong GuildId { get; set; } + public CalendarEventType EventType { get; set; } + public int TextureId { get; set; } + public long Date { get; set; } + public CalendarFlags Flags { get; set; } + public string Title { get; set; } + public string Description { get; set; } + public long LockDate { get; set; } + } +} diff --git a/Game/Chat/Channels/Channel.cs b/Game/Chat/Channels/Channel.cs new file mode 100644 index 000000000..d24a8a4c4 --- /dev/null +++ b/Game/Chat/Channels/Channel.cs @@ -0,0 +1,980 @@ +/* + * Copyright (C) 2012-2017 CypherCore + * + * 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, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +using Framework.Collections; +using Framework.Constants; +using Framework.Database; +using Game.DataStorage; +using Game.Entities; +using Game.Maps; +using Game.Network.Packets; +using System; +using System.Collections.Generic; +using System.Linq; + +namespace Game.Chat +{ + public class Channel + { + public Channel(uint channelId, Team team = 0, AreaTableRecord zoneEntry = null) + { + _channelFlags = ChannelFlags.General; + _channelId = channelId; + _channelTeam = team; + _zoneEntry = zoneEntry; + + ChatChannelsRecord channelEntry = CliDB.ChatChannelsStorage.LookupByKey(channelId); + if (channelEntry.Flags.HasAnyFlag(ChannelDBCFlags.Trade)) // for trade channel + _channelFlags |= ChannelFlags.Trade; + + if (channelEntry.Flags.HasAnyFlag(ChannelDBCFlags.CityOnly2)) // for city only channels + _channelFlags |= ChannelFlags.City; + + if (channelEntry.Flags.HasAnyFlag(ChannelDBCFlags.Lfg)) // for LFG channel + _channelFlags |= ChannelFlags.Lfg; + else // for all other channels + _channelFlags |= ChannelFlags.NotLfg; + } + + public Channel(string name, Team team = 0) + { + _announceEnabled = true; + _ownershipEnabled = true; + _channelFlags = ChannelFlags.Custom; + _channelTeam = team; + _channelName = name; + + // If storing custom channels in the db is enabled either load or save the channel + if (WorldConfig.GetBoolValue(WorldCfg.PreserveCustomChannels)) + { + PreparedStatement stmt = DB.Characters.GetPreparedStatement(CharStatements.SEL_CHANNEL); + stmt.AddValue(0, _channelName); + stmt.AddValue(1, _channelTeam); + SQLResult result = DB.Characters.Query(stmt); + + if (!result.IsEmpty()) //load + { + _channelName = result.Read(0); // re-get channel name. MySQL table collation is case insensitive + _announceEnabled = result.Read(1); + _ownershipEnabled = result.Read(2); + _channelPassword = result.Read(3); + string bannedList = result.Read(4); + + if (string.IsNullOrEmpty(bannedList)) + { + var tokens = new StringArray(bannedList, ' '); + for (var i = 0; i < tokens.Length; ++i) + { + ObjectGuid bannedGuid = new ObjectGuid(); + bannedGuid.SetRawValue(ulong.Parse(tokens[i].Substring(0, 16)), ulong.Parse(tokens[i].Substring(16))); + if (!bannedGuid.IsEmpty()) + { + Log.outDebug(LogFilter.ChatSystem, "Channel({0}) loaded bannedStore guid:{1}", _channelName, bannedGuid); + _bannedStore.Add(bannedGuid); + } + } + } + } + else // save + { + stmt = DB.Characters.GetPreparedStatement(CharStatements.INS_CHANNEL); + stmt.AddValue(0, _channelName); + stmt.AddValue(1, _channelTeam); + DB.Characters.Execute(stmt); + Log.outDebug(LogFilter.ChatSystem, "Channel({0}) saved in database", _channelName); + } + + _persistentChannel = true; + } + } + + public static void GetChannelName(ref string channelName, uint channelId, LocaleConstant locale, AreaTableRecord zoneEntry) + { + if (channelId != 0) + { + ChatChannelsRecord channelEntry = CliDB.ChatChannelsStorage.LookupByKey(channelId); + if (!channelEntry.Flags.HasAnyFlag(ChannelDBCFlags.Global)) + { + if (channelEntry.Flags.HasAnyFlag(ChannelDBCFlags.CityOnly)) + channelName = string.Format(channelEntry.Name[locale].ConvertFormatSyntax(), Global.ObjectMgr.GetCypherString(CypherStrings.ChannelCity, locale)); + else + channelName = string.Format(channelEntry.Name[locale].ConvertFormatSyntax(), zoneEntry.AreaName[locale]); + } + else + channelName = channelEntry.Name[locale]; + } + } + + public string GetName(LocaleConstant locale = LocaleConstant.enUS) + { + string result = _channelName; + GetChannelName(ref result, _channelId, locale, _zoneEntry); + + return result; + } + + void UpdateChannelInDB() + { + if (_persistentChannel) + { + string banlist = ""; + foreach (var iter in _bannedStore) + banlist += iter.GetRawValue().ToHexString() + ' '; + + PreparedStatement stmt = DB.Characters.GetPreparedStatement(CharStatements.UPD_CHANNEL); + stmt.AddValue(0, _announceEnabled); + stmt.AddValue(1, _ownershipEnabled); + stmt.AddValue(2, _channelPassword); + stmt.AddValue(3, banlist); + stmt.AddValue(4, _channelName); + stmt.AddValue(5, _channelTeam); + DB.Characters.Execute(stmt); + + Log.outDebug(LogFilter.ChatSystem, "Channel({0}) updated in database", _channelName); + } + } + + void UpdateChannelUseageInDB() + { + PreparedStatement stmt = DB.Characters.GetPreparedStatement(CharStatements.UPD_CHANNEL_USAGE); + stmt.AddValue(0, _channelName); + stmt.AddValue(1, _channelTeam); + DB.Characters.Execute(stmt); + } + + public static void CleanOldChannelsInDB() + { + if (WorldConfig.GetIntValue(WorldCfg.PreserveCustomChannelDuration) > 0) + { + PreparedStatement stmt = DB.Characters.GetPreparedStatement(CharStatements.DEL_OLD_CHANNELS); + stmt.AddValue(0, WorldConfig.GetIntValue(WorldCfg.PreserveCustomChannelDuration) * Time.Day); + DB.Characters.Execute(stmt); + + Log.outDebug(LogFilter.ChatSystem, "Cleaned out unused custom chat channels."); + } + } + + public void JoinChannel(Player player, string pass) + { + ObjectGuid guid = player.GetGUID(); + if (IsOn(guid)) + { + // Do not send error message for built-in channels + if (!IsConstant()) + { + var builder = new ChannelNameBuilder(this, new PlayerAlreadyMemberAppend(guid)); + SendToOne(builder, guid); + } + return; + } + + if (IsBanned(guid)) + { + var builder = new ChannelNameBuilder(this, new BannedAppend()); + SendToOne(builder, guid); + return; + } + + if (!string.IsNullOrEmpty(_channelPassword) && pass != _channelPassword) + { + var builder = new ChannelNameBuilder(this, new WrongPasswordAppend()); + SendToOne(builder, guid); + return; + } + + if (HasFlag(ChannelFlags.Lfg) && WorldConfig.GetBoolValue(WorldCfg.RestrictedLfgChannel) && + Global.AccountMgr.IsPlayerAccount(player.GetSession().GetSecurity()) && //FIXME: Move to RBAC + player.GetGroup()) + { + var builder = new ChannelNameBuilder(this, new NotInLFGAppend()); + SendToOne(builder, guid); + return; + } + + player.JoinedChannel(this); + + if (_announceEnabled && !player.GetSession().HasPermission(RBACPermissions.SilentlyJoinChannel)) + { + var builder = new ChannelNameBuilder(this, new JoinedAppend(guid)); + SendToAll(builder); + } + + bool newChannel = _playersStore.Empty(); + + PlayerInfo playerInfo = new PlayerInfo(); + playerInfo.SetInvisible(!player.isGMVisible()); + _playersStore[guid] = playerInfo; + + /* + ChannelNameBuilder builder = new ChannelNameBuilder(this, new YouJoinedAppend()); + SendToOne(builder, guid); + */ + + SendToOne(new ChannelNotifyJoinedBuilder(this), guid); + + JoinNotify(player); + + // Custom channel handling + if (!IsConstant()) + { + // Update last_used timestamp in db + if (!_playersStore.Empty()) + UpdateChannelUseageInDB(); + + // If the channel has no owner yet and ownership is allowed, set the new owner. + // or if the owner was a GM with .gm visible off + // don't do this if the new player is, too, an invis GM, unless the channel was empty + if (_ownershipEnabled && (newChannel || !playerInfo.IsInvisible()) && (_ownerGuid.IsEmpty() || _isOwnerInvisible)) + { + _isOwnerInvisible = playerInfo.IsInvisible(); + + SetOwner(guid, !newChannel && !_isOwnerInvisible); + _playersStore[guid].SetModerator(true); + } + } + } + + public void LeaveChannel(Player player, bool send = true) + { + ObjectGuid guid = player.GetGUID(); + if (!IsOn(guid)) + { + if (send) + { + var builder = new ChannelNameBuilder(this, new NotMemberAppend()); + SendToOne(builder, guid); + } + return; + } + + player.LeftChannel(this); + + if (send) + { + /* + ChannelNameBuilder builder = new ChannelNameBuilder(this, new YouLeftAppend()); + SendToOne(builder, guid); + */ + + SendToOne(new ChannelNotifyLeftBuilder(this), guid); + } + + PlayerInfo info = _playersStore.LookupByKey(guid); + bool changeowner = info.IsOwner(); + _playersStore.Remove(guid); + + if (_announceEnabled && !player.GetSession().HasPermission(RBACPermissions.SilentlyJoinChannel)) + { + var builder = new ChannelNameBuilder(this, new LeftAppend(guid)); + SendToAll(builder); + } + + LeaveNotify(player); + + if (!IsConstant()) + { + // Update last_used timestamp in db + UpdateChannelUseageInDB(); + + // If the channel owner left and there are still playersStore inside, pick a new owner + // do not pick invisible gm owner unless there are only invisible gms in that channel (rare) + if (changeowner && _ownershipEnabled && !_playersStore.Empty()) + { + ObjectGuid newowner = ObjectGuid.Empty; + foreach (var key in _playersStore.Keys) + { + if (!_playersStore[key].IsInvisible()) + { + newowner = key; + break; + } + } + + if (newowner.IsEmpty()) + newowner = _playersStore.First().Key; + + _playersStore[newowner].SetModerator(true); + + SetOwner(newowner); + + // if the new owner is invisible gm, set flag to automatically choose a new owner + if (_playersStore[newowner].IsInvisible()) + _isOwnerInvisible = true; + } + } + } + + void KickOrBan(Player player, string badname, bool ban) + { + ObjectGuid good = player.GetGUID(); + + if (!IsOn(good)) + { + ChannelNameBuilder builder = new ChannelNameBuilder(this, new NotMemberAppend()); + SendToOne(builder, good); + return; + } + + PlayerInfo info = _playersStore.LookupByKey(good); + if (!info.IsModerator() && !player.GetSession().HasPermission(RBACPermissions.ChangeChannelNotModerator)) + { + ChannelNameBuilder builder = new ChannelNameBuilder(this, new NotModeratorAppend()); + SendToOne(builder, good); + return; + } + + Player bad = Global.ObjAccessor.FindPlayerByName(badname); + ObjectGuid victim = bad ? bad.GetGUID() : ObjectGuid.Empty; + if (victim.IsEmpty() || !IsOn(victim)) + { + ChannelNameBuilder builder = new ChannelNameBuilder(this, new PlayerNotFoundAppend(badname)); + SendToOne(builder, good); + return; + } + + bool changeowner = _ownerGuid == victim; + + if (!player.GetSession().HasPermission(RBACPermissions.ChangeChannelNotModerator) && changeowner && good != _ownerGuid) + { + ChannelNameBuilder builder = new ChannelNameBuilder(this, new NotOwnerAppend()); + SendToOne(builder, good); + return; + } + + if (ban && !IsBanned(victim)) + { + _bannedStore.Add(victim); + UpdateChannelInDB(); + + if (!player.GetSession().HasPermission(RBACPermissions.SilentlyJoinChannel)) + { + ChannelNameBuilder builder = new ChannelNameBuilder(this, new PlayerBannedAppend(good, victim)); + SendToAll(builder); + } + + } + else if (!player.GetSession().HasPermission(RBACPermissions.SilentlyJoinChannel)) + { + ChannelNameBuilder builder = new ChannelNameBuilder(this, new PlayerKickedAppend(good, victim)); + SendToAll(builder); + } + + _playersStore.Remove(victim); + bad.LeftChannel(this); + + if (changeowner && _ownershipEnabled && !_playersStore.Empty()) + { + info.SetModerator(true); + SetOwner(good); + } + } + + public void UnBan(Player player, string badname) + { + ObjectGuid good = player.GetGUID(); + + if (!IsOn(good)) + { + ChannelNameBuilder builder = new ChannelNameBuilder(this, new NotMemberAppend()); + SendToOne(builder, good); + return; + } + + PlayerInfo info = _playersStore.LookupByKey(good); + if (!info.IsModerator() && !player.GetSession().HasPermission(RBACPermissions.ChangeChannelNotModerator)) + { + ChannelNameBuilder builder = new ChannelNameBuilder(this, new NotModeratorAppend()); + SendToOne(builder, good); + return; + } + + Player bad = Global.ObjAccessor.FindPlayerByName(badname); + ObjectGuid victim = bad ? bad.GetGUID() : ObjectGuid.Empty; + + if (victim.IsEmpty() || !IsBanned(victim)) + { + ChannelNameBuilder builder = new ChannelNameBuilder(this, new PlayerNotFoundAppend(badname)); + SendToOne(builder, good); + return; + } + + _bannedStore.Remove(victim); + + ChannelNameBuilder builder1 = new ChannelNameBuilder(this, new PlayerUnbannedAppend(good, victim)); + SendToAll(builder1); + + UpdateChannelInDB(); + } + + public void Password(Player player, string pass) + { + ObjectGuid guid = player.GetGUID(); + + if (!IsOn(guid)) + { + ChannelNameBuilder builder = new ChannelNameBuilder(this, new NotMemberAppend()); + SendToOne(builder, guid); + return; + } + + PlayerInfo info = _playersStore.LookupByKey(guid); + if (!info.IsModerator() && !player.GetSession().HasPermission(RBACPermissions.ChangeChannelNotModerator)) + { + ChannelNameBuilder builder = new ChannelNameBuilder(this, new NotModeratorAppend()); + SendToOne(builder, guid); + return; + } + + _channelPassword = pass; + + ChannelNameBuilder builder1 = new ChannelNameBuilder(this, new PasswordChangedAppend(guid)); + SendToAll(builder1); + + UpdateChannelInDB(); + } + + void SetMode(Player player, string p2n, bool mod, bool set) + { + ObjectGuid guid = player.GetGUID(); + + if (!IsOn(guid)) + { + ChannelNameBuilder builder = new ChannelNameBuilder(this, new NotMemberAppend()); + SendToOne(builder, guid); + return; + } + + PlayerInfo info = _playersStore.LookupByKey(guid); + if (!info.IsModerator() && !player.GetSession().HasPermission(RBACPermissions.ChangeChannelNotModerator)) + { + ChannelNameBuilder builder = new ChannelNameBuilder(this, new NotModeratorAppend()); + SendToOne(builder, guid); + return; + } + + if (guid == _ownerGuid && p2n == player.GetName() && mod) + return; + + Player newp = Global.ObjAccessor.FindPlayerByName(p2n); + ObjectGuid victim = newp ? newp.GetGUID() : ObjectGuid.Empty; + + if (victim.IsEmpty() || !IsOn(victim) || + (player.GetTeam() != newp.GetTeam() && + (!player.GetSession().HasPermission(RBACPermissions.TwoSideInteractionChannel) || + !newp.GetSession().HasPermission(RBACPermissions.TwoSideInteractionChannel)))) + { + ChannelNameBuilder builder = new ChannelNameBuilder(this, new PlayerNotFoundAppend(p2n)); + SendToOne(builder, guid); + return; + } + + if (_ownerGuid == victim && _ownerGuid != guid) + { + ChannelNameBuilder builder = new ChannelNameBuilder(this, new NotOwnerAppend()); + SendToOne(builder, guid); + return; + } + + if (mod) + SetModerator(newp.GetGUID(), set); + else + SetMute(newp.GetGUID(), set); + } + + public void SetInvisible(Player player, bool on) + { + var playerInfo = _playersStore.LookupByKey(player.GetGUID()); + if (playerInfo == null) + return; + + playerInfo.SetInvisible(on); + + // we happen to be owner too, update flag + if (_ownerGuid == player.GetGUID()) + _isOwnerInvisible = on; + } + + public void SetOwner(Player player, string newname) + { + ObjectGuid guid = player.GetGUID(); + + if (!IsOn(guid)) + { + ChannelNameBuilder builder = new ChannelNameBuilder(this, new NotMemberAppend()); + SendToOne(builder, guid); + return; + } + if (!player.GetSession().HasPermission(RBACPermissions.ChangeChannelNotModerator) && guid != _ownerGuid) + { + ChannelNameBuilder builder = new ChannelNameBuilder(this, new NotOwnerAppend()); + SendToOne(builder, guid); + return; + } + + Player newp = Global.ObjAccessor.FindPlayerByName(newname); + ObjectGuid victim = newp ? newp.GetGUID() : ObjectGuid.Empty; + + if (victim.IsEmpty() || !IsOn(victim) || + (player.GetTeam() != newp.GetTeam() && + (!player.GetSession().HasPermission(RBACPermissions.TwoSideInteractionChannel) || + !newp.GetSession().HasPermission(RBACPermissions.TwoSideInteractionChannel)))) + { + ChannelNameBuilder builder = new ChannelNameBuilder(this, new PlayerNotFoundAppend(newname)); + SendToOne(builder, guid); + return; + } + + _playersStore[victim].SetModerator(true); + SetOwner(victim); + } + + public void SendWhoOwner(Player player) + { + ObjectGuid guid = player.GetGUID(); + if (IsOn(guid)) + { + ChannelNameBuilder builder = new ChannelNameBuilder(this, new ChannelOwnerAppend(this, _ownerGuid)); + SendToOne(builder, guid); + } + else + { + ChannelNameBuilder builder = new ChannelNameBuilder(this, new NotMemberAppend()); + SendToOne(builder, guid); + } + } + + public void List(Player player) + { + ObjectGuid guid = player.GetGUID(); + + if (!IsOn(guid)) + { + ChannelNameBuilder builder = new ChannelNameBuilder(this, new NotMemberAppend()); + SendToOne(builder, guid); + return; + } + + string channelName = GetName(player.GetSession().GetSessionDbcLocale()); + Log.outDebug(LogFilter.ChatSystem, "SMSG_CHANNEL_LIST {0} Channel: {1}", player.GetSession().GetPlayerInfo(), channelName); + + ChannelListResponse list = new ChannelListResponse(); + list.Display = true; /// always true? + list.Channel = channelName; + list.ChannelFlags = GetFlags(); + + uint gmLevelInWhoList = WorldConfig.GetUIntValue(WorldCfg.GmLevelInWhoList); + + foreach (var pair in _playersStore) + { + Player member = Global.ObjAccessor.FindConnectedPlayer(pair.Key); + + // PLAYER can't see MODERATOR, GAME MASTER, ADMINISTRATOR characters + // MODERATOR, GAME MASTER, ADMINISTRATOR can see all + if (member && (player.GetSession().HasPermission(RBACPermissions.WhoSeeAllSecLevels) || + member.GetSession().GetSecurity() <= (AccountTypes)gmLevelInWhoList) && + member.IsVisibleGloballyFor(player)) + { + list.Members.Add(new ChannelListResponse.ChannelPlayer(pair.Key, Global.WorldMgr.GetVirtualRealmAddress(), pair.Value.GetFlags())); + } + } + + player.SendPacket(list); + } + + public void Announce(Player player) + { + ObjectGuid guid = player.GetGUID(); + + if (!IsOn(guid)) + { + ChannelNameBuilder builder = new ChannelNameBuilder(this, new NotMemberAppend()); + SendToOne(builder, guid); + return; + } + + PlayerInfo playerInfo = _playersStore.LookupByKey(guid); + if (!playerInfo.IsModerator() && !player.GetSession().HasPermission(RBACPermissions.ChangeChannelNotModerator)) + { + ChannelNameBuilder builder = new ChannelNameBuilder(this, new NotModeratorAppend()); + SendToOne(builder, guid); + return; + } + + _announceEnabled = !_announceEnabled; + + if (_announceEnabled) + { + ChannelNameBuilder builder = new ChannelNameBuilder(this, new AnnouncementsOnAppend(guid)); + SendToAll(builder); + } + else + { + ChannelNameBuilder builder = new ChannelNameBuilder(this, new AnnouncementsOffAppend(guid)); + SendToAll(builder); + } + + UpdateChannelInDB(); + } + + public void Say(ObjectGuid guid, string what, Language lang) + { + if (string.IsNullOrEmpty(what)) + return; + + // TODO: Add proper RBAC check + if (WorldConfig.GetBoolValue(WorldCfg.AllowTwoSideInteractionChannel)) + lang = Language.Universal; + + if (!IsOn(guid)) + { + ChannelNameBuilder builder = new ChannelNameBuilder(this, new NotMemberAppend()); + SendToOne(builder, guid); + return; + } + + PlayerInfo playerInfo = _playersStore.LookupByKey(guid); + if (playerInfo.IsMuted()) + { + ChannelNameBuilder builder = new ChannelNameBuilder(this, new MutedAppend()); + SendToOne(builder, guid); + return; + } + + SendToAll(new ChannelSayBuilder(this, lang, what, guid), !playerInfo.IsModerator() ? guid : ObjectGuid.Empty); + } + + public void AddonSay(ObjectGuid guid, string prefix, string what) + { + if (what.IsEmpty()) + return; + + if (!IsOn(guid)) + { + NotMemberAppend appender; + ChannelNameBuilder builder = new ChannelNameBuilder(this, appender); + SendToOne(builder, guid); + return; + } + + PlayerInfo playerInfo = _playersStore.LookupByKey(guid); + if (playerInfo.IsMuted()) + { + MutedAppend appender; + ChannelNameBuilder builder = new ChannelNameBuilder(this, appender); + SendToOne(builder, guid); + return; + } + + SendToAllWithAddon(new ChannelWhisperBuilder(this, Language.Addon, what, prefix, guid), prefix, !playerInfo.IsModerator() ? guid : ObjectGuid.Empty); + } + + public void Invite(Player player, string newname) + { + ObjectGuid guid = player.GetGUID(); + + if (!IsOn(guid)) + { + ChannelNameBuilder builder = new ChannelNameBuilder(this, new NotMemberAppend()); + SendToOne(builder, guid); + return; + } + + Player newp = Global.ObjAccessor.FindPlayerByName(newname); + if (!newp || !newp.isGMVisible()) + { + ChannelNameBuilder builder = new ChannelNameBuilder(this, new PlayerNotFoundAppend(newname)); + SendToOne(builder, guid); + return; + } + + if (IsBanned(newp.GetGUID())) + { + ChannelNameBuilder builder = new ChannelNameBuilder(this, new PlayerInviteBannedAppend(newname)); + SendToOne(builder, guid); + return; + } + + if (newp.GetTeam() != player.GetTeam() && + (!player.GetSession().HasPermission(RBACPermissions.TwoSideInteractionChannel) || + !newp.GetSession().HasPermission(RBACPermissions.TwoSideInteractionChannel))) + { + ChannelNameBuilder builder = new ChannelNameBuilder(this, new InviteWrongFactionAppend()); + SendToOne(builder, guid); + return; + } + + if (IsOn(newp.GetGUID())) + { + ChannelNameBuilder builder = new ChannelNameBuilder(this, new PlayerAlreadyMemberAppend(newp.GetGUID())); + SendToOne(builder, guid); + return; + } + + if (!newp.GetSocial().HasIgnore(guid)) + { + ChannelNameBuilder builder = new ChannelNameBuilder(this, new InviteAppend(guid)); + SendToOne(builder, newp.GetGUID()); + } + + ChannelNameBuilder builder1 = new ChannelNameBuilder(this, new PlayerInvitedAppend(newp.GetName())); + SendToOne(builder1, guid); + } + + public void SetOwner(ObjectGuid guid, bool exclaim = true) + { + if (!_ownerGuid.IsEmpty()) + { + // [] will re-add player after it possible removed + var playerInfo = _playersStore.LookupByKey(_ownerGuid); + if (playerInfo != null) + playerInfo.SetOwner(false); + } + + _ownerGuid = guid; + if (!_ownerGuid.IsEmpty()) + { + ChannelMemberFlags oldFlag = GetPlayerFlags(_ownerGuid); + var playerInfo = _playersStore.LookupByKey(_ownerGuid); + if (playerInfo == null) + return; + + playerInfo.SetModerator(true); + playerInfo.SetOwner(true); + + ChannelNameBuilder builder = new ChannelNameBuilder(this, new ModeChangeAppend(_ownerGuid, oldFlag, GetPlayerFlags(_ownerGuid))); + SendToAll(builder); + + if (exclaim) + { + ChannelNameBuilder ownerChangedBuilder = new ChannelNameBuilder(this, new OwnerChangedAppend(_ownerGuid)); + SendToAll(ownerChangedBuilder); + } + + UpdateChannelInDB(); + } + } + + public void SilenceAll(Player player, string name) { } + + public void UnsilenceAll(Player player, string name) { } + + public void DeclineInvite(Player player) { } + + void JoinNotify(Player player) + { + ObjectGuid guid = player.GetGUID(); + + if (IsConstant()) + SendToAllButOne(new ChannelUserlistAddBuilder(this, guid), guid); + else + SendToAll(new ChannelUserlistUpdateBuilder(this, guid)); + } + + void LeaveNotify(Player player) + { + ObjectGuid guid = player.GetGUID(); + + var builder = new ChannelUserlistRemoveBuilder(this, guid); + + if (IsConstant()) + SendToAllButOne(builder, guid); + else + SendToAll(builder); + } + + void SetModerator(ObjectGuid guid, bool set) + { + if (!IsOn(guid)) + return; + + PlayerInfo playerInfo = _playersStore.LookupByKey(guid); + if (playerInfo.IsModerator() != set) + { + ChannelMemberFlags oldFlag = _playersStore[guid].GetFlags(); + playerInfo.SetModerator(set); + + ChannelNameBuilder builder = new ChannelNameBuilder(this, new ModeChangeAppend(guid, oldFlag, playerInfo.GetFlags())); + SendToAll(builder); + } + } + + void SetMute(ObjectGuid guid, bool set) + { + if (!IsOn(guid)) + return; + + PlayerInfo playerInfo = _playersStore.LookupByKey(guid); + if (playerInfo.IsMuted() != set) + { + ChannelMemberFlags oldFlag = _playersStore[guid].GetFlags(); + playerInfo.SetMuted(set); + + ChannelNameBuilder builder = new ChannelNameBuilder(this, new ModeChangeAppend(guid, oldFlag, playerInfo.GetFlags())); + SendToAll(builder); + } + } + + void SendToAll(MessageBuilder builder, ObjectGuid guid = default(ObjectGuid)) + { + LocalizedPacketDo localizer = new LocalizedPacketDo(builder); + + foreach (var pair in _playersStore) + { + Player player = Global.ObjAccessor.FindConnectedPlayer(pair.Key); + if (player) + if (guid.IsEmpty() || !player.GetSocial().HasIgnore(guid)) + localizer.Invoke(player); + } + } + + void SendToAllButOne(MessageBuilder builder, ObjectGuid who) + { + LocalizedPacketDo localizer = new LocalizedPacketDo(builder); + + foreach (var pair in _playersStore) + { + if (pair.Key != who) + { + Player player = Global.ObjAccessor.FindConnectedPlayer(pair.Key); + if (player) + localizer.Invoke(player); + } + } + } + + void SendToOne(MessageBuilder builder, ObjectGuid who) + { + LocalizedPacketDo localizer = new LocalizedPacketDo(builder); + + Player player = Global.ObjAccessor.FindConnectedPlayer(who); + if (player) + localizer.Invoke(player); + } + + void SendToAllWithAddon(MessageBuilder builder, string addonPrefix, ObjectGuid guid = default(ObjectGuid)) + { + LocalizedPacketDo localizer = new LocalizedPacketDo(builder); + + foreach (var pair in _playersStore) + { + Player player = Global.ObjAccessor.FindConnectedPlayer(pair.Key); + if (player) + if (player.GetSession().IsAddonRegistered(addonPrefix) && (guid.IsEmpty() || !player.GetSocial().HasIgnore(guid))) + localizer.Invoke(player); + } + } + + public uint GetChannelId() { return _channelId; } + public bool IsConstant() { return _channelId != 0; } + + public bool IsLFG() { return GetFlags().HasAnyFlag(ChannelFlags.Lfg); } + bool IsAnnounce() { return _announceEnabled; } + void SetAnnounce(bool nannounce) { _announceEnabled = nannounce; } + + string GetPassword() { return _channelPassword; } + void SetPassword(string npassword) { _channelPassword = npassword; } + + public uint GetNumPlayers() { return (uint)_playersStore.Count; } + + public ChannelFlags GetFlags() { return _channelFlags; } + bool HasFlag(ChannelFlags flag) { return _channelFlags.HasAnyFlag(flag); } + + public AreaTableRecord GetZoneEntry() { return _zoneEntry; } + + public void Kick(Player player, string badname) { KickOrBan(player, badname, false); } + public void Ban(Player player, string badname) { KickOrBan(player, badname, true); } + + public void SetModerator(Player player, string newname) { SetMode(player, newname, true, true); } + public void UnsetModerator(Player player, string newname) { SetMode(player, newname, true, false); } + public void SetMute(Player player, string newname) { SetMode(player, newname, false, true); } + public void UnsetMute(Player player, string newname) { SetMode(player, newname, false, false); } + + public void SetOwnership(bool ownership) { _ownershipEnabled = ownership; } + + bool IsOn(ObjectGuid who) { return _playersStore.ContainsKey(who); } + bool IsBanned(ObjectGuid guid) { return _bannedStore.Contains(guid); } + + public ChannelMemberFlags GetPlayerFlags(ObjectGuid guid) + { + var info = _playersStore.LookupByKey(guid); + return info != null ? info.GetFlags() : 0; + } + + bool _announceEnabled; + bool _ownershipEnabled; + bool _persistentChannel; + bool _isOwnerInvisible; + + ChannelFlags _channelFlags; + uint _channelId; + Team _channelTeam; + ObjectGuid _ownerGuid; + string _channelName; + string _channelPassword; + Dictionary _playersStore = new Dictionary(); + List _bannedStore = new List(); + + AreaTableRecord _zoneEntry; + + public class PlayerInfo + { + public ChannelMemberFlags GetFlags() { return flags; } + + public bool IsInvisible() { return _invisible; } + public void SetInvisible(bool on) { _invisible = on; } + + public bool HasFlag(ChannelMemberFlags flag) { return flags.HasAnyFlag(flag); } + + public void SetFlag(ChannelMemberFlags flag) { flags |= flag; } + + public void RemoveFlag(ChannelMemberFlags flag) { flags &= ~flag; } + + public bool IsOwner() { return HasFlag(ChannelMemberFlags.Owner); } + + public void SetOwner(bool state) + { + if (state) + SetFlag(ChannelMemberFlags.Owner); + else + RemoveFlag(ChannelMemberFlags.Owner); + } + + public bool IsModerator() { return HasFlag(ChannelMemberFlags.Moderator); } + + public void SetModerator(bool state) + { + if (state) + SetFlag(ChannelMemberFlags.Moderator); + else + RemoveFlag(ChannelMemberFlags.Moderator); + } + + public bool IsMuted() { return HasFlag(ChannelMemberFlags.Muted); } + + public void SetMuted(bool state) + { + if (state) + SetFlag(ChannelMemberFlags.Muted); + else + RemoveFlag(ChannelMemberFlags.Muted); + } + + ChannelMemberFlags flags; + bool _invisible; + } + } +} diff --git a/Game/Chat/Channels/ChannelAppenders.cs b/Game/Chat/Channels/ChannelAppenders.cs new file mode 100644 index 000000000..76a66dc3b --- /dev/null +++ b/Game/Chat/Channels/ChannelAppenders.cs @@ -0,0 +1,716 @@ +/* + * Copyright (C) 2012-2017 CypherCore + * + * 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, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +using Framework.Constants; +using Game.Entities; +using Game.Network; +using Game.Network.Packets; + +namespace Game.Chat +{ + interface IChannelAppender + { + void Append(ChannelNotify data); + ChatNotify GetNotificationType(); + } + + // initial packet data (notify type and channel name) + class ChannelNameBuilder : MessageBuilder + { + public ChannelNameBuilder(Channel source, IChannelAppender modifier) + { + _source = source; + _modifier = modifier; + } + + public override ServerPacket Invoke(LocaleConstant locale = LocaleConstant.enUS) + { + // LocalizedPacketDo sends client DBC locale, we need to get available to server locale + LocaleConstant localeIdx = Global.WorldMgr.GetAvailableDbcLocale(locale); + + ChannelNotify data = new ChannelNotify(); + data.Type = _modifier.GetNotificationType(); + data.Channel = _source.GetName(localeIdx); + _modifier.Append(data); + return data; + } + + Channel _source; + IChannelAppender _modifier; + } + + class ChannelNotifyJoinedBuilder : MessageBuilder + { + public ChannelNotifyJoinedBuilder(Channel source) + { + _source = source; + } + + public override ServerPacket Invoke(LocaleConstant locale = LocaleConstant.enUS) + { + LocaleConstant localeIdx = Global.WorldMgr.GetAvailableDbcLocale(locale); + + ChannelNotifyJoined notify = new ChannelNotifyJoined(); + //notify->ChannelWelcomeMsg = ""; + notify.ChatChannelID = (int)_source.GetChannelId(); + //notify->InstanceID = 0; + notify.ChannelFlags = _source.GetFlags(); + notify.Channel = _source.GetName(localeIdx); + return notify; + } + + Channel _source; + } + + class ChannelNotifyLeftBuilder : MessageBuilder + { + public ChannelNotifyLeftBuilder(Channel source) + { + _source = source; + } + + public override ServerPacket Invoke(LocaleConstant locale = LocaleConstant.enUS) + { + LocaleConstant localeIdx = Global.WorldMgr.GetAvailableDbcLocale(locale); + + ChannelNotifyLeft notify = new ChannelNotifyLeft(); + notify.Channel = _source.GetName(localeIdx); + notify.ChatChannelID = 0; + //notify->Suspended = false; + return notify; + } + + Channel _source; + } + + class ChannelSayBuilder : MessageBuilder + { + public ChannelSayBuilder(Channel source, Language lang, string what, ObjectGuid guid) + { + _source = source; + _lang = lang; + _what = what; + _guid = guid; + } + + public override ServerPacket Invoke(LocaleConstant locale = LocaleConstant.enUS) + { + LocaleConstant localeIdx = Global.WorldMgr.GetAvailableDbcLocale(locale); + + ChatPkt packet = new ChatPkt(); + Player player = Global.ObjAccessor.FindConnectedPlayer(_guid); + if (player) + packet.Initialize(ChatMsg.Channel, _lang, player, player, _what, 0, _source.GetName(localeIdx)); + else + { + packet.Initialize(ChatMsg.Channel, _lang, null, null, _what, 0, _source.GetName(localeIdx)); + packet.SenderGUID = _guid; + packet.TargetGUID = _guid; + } + + return packet; + } + + Channel _source; + Language _lang; + string _what; + ObjectGuid _guid; + } + + class ChannelWhisperBuilder : MessageBuilder + { + public ChannelWhisperBuilder(Channel source, Language lang, string what, string prefix, ObjectGuid guid) + { + _source = source; + _lang = lang; + _what = what; + _prefix = prefix; + _guid = guid; + } + + public override ServerPacket Invoke(LocaleConstant locale = LocaleConstant.enUS) + { + LocaleConstant localeIdx = Global.WorldMgr.GetAvailableDbcLocale(locale); + + ChatPkt packet = new ChatPkt(); + Player player = Global.ObjAccessor.FindConnectedPlayer(_guid); + if (player) + packet.Initialize(ChatMsg.Channel, Language.Addon, player, player, _what, 0, _source.GetName(localeIdx), LocaleConstant.enUS, _prefix); + else + { + packet.Initialize(ChatMsg.Channel, Language.Addon, null, null, _what, 0, _source.GetName(localeIdx), LocaleConstant.enUS, _prefix); + packet.SenderGUID = _guid; + packet.TargetGUID = _guid; + } + + return packet; + } + + Channel _source; + Language _lang; + string _what; + string _prefix; + ObjectGuid _guid; + } + + class ChannelUserlistAddBuilder : MessageBuilder + { + public ChannelUserlistAddBuilder(Channel source, ObjectGuid guid) + { + _source = source; + _guid = guid; + } + + public override ServerPacket Invoke(LocaleConstant locale = LocaleConstant.enUS) + { + LocaleConstant localeIdx = Global.WorldMgr.GetAvailableDbcLocale(locale); + + UserlistAdd userlistAdd = new UserlistAdd(); + userlistAdd.AddedUserGUID = _guid; + userlistAdd.ChannelFlags = _source.GetFlags(); + userlistAdd.UserFlags = _source.GetPlayerFlags(_guid); + userlistAdd.ChannelID = _source.GetChannelId(); + userlistAdd.ChannelName = _source.GetName(localeIdx); + return userlistAdd; + } + + Channel _source; + ObjectGuid _guid; + } + + class ChannelUserlistUpdateBuilder : MessageBuilder + { + public ChannelUserlistUpdateBuilder(Channel source, ObjectGuid guid) + { + _source = source; + _guid = guid; + } + + public override ServerPacket Invoke(LocaleConstant locale = LocaleConstant.enUS) + { + LocaleConstant localeIdx = Global.WorldMgr.GetAvailableDbcLocale(locale); + + UserlistUpdate userlistUpdate = new UserlistUpdate(); + userlistUpdate.UpdatedUserGUID = _guid; + userlistUpdate.ChannelFlags = _source.GetFlags(); + userlistUpdate.UserFlags = _source.GetPlayerFlags(_guid); + userlistUpdate.ChannelID = _source.GetChannelId(); + userlistUpdate.ChannelName = _source.GetName(localeIdx); + return userlistUpdate; + } + + Channel _source; + ObjectGuid _guid; + } + + class ChannelUserlistRemoveBuilder : MessageBuilder + { + public ChannelUserlistRemoveBuilder(Channel source, ObjectGuid guid) + { + _source = source; + _guid = guid; + } + + public override ServerPacket Invoke(LocaleConstant locale = LocaleConstant.enUS) + { + LocaleConstant localeIdx = Global.WorldMgr.GetAvailableDbcLocale(locale); + + UserlistRemove userlistRemove = new UserlistRemove(); + userlistRemove.RemovedUserGUID = _guid; + userlistRemove.ChannelFlags = _source.GetFlags(); + userlistRemove.ChannelID = _source.GetChannelId(); + userlistRemove.ChannelName = _source.GetName(localeIdx); + return userlistRemove; + } + + Channel _source; + ObjectGuid _guid; + } + + //Appenders + struct JoinedAppend : IChannelAppender + { + public JoinedAppend(ObjectGuid guid) + { + _guid = guid; + } + + public ChatNotify GetNotificationType() => ChatNotify.JoinedNotice; + + public void Append(ChannelNotify data) + { + data.SenderGuid = _guid; + } + + ObjectGuid _guid; + } + + struct LeftAppend : IChannelAppender + { + public LeftAppend(ObjectGuid guid) + { + _guid = guid; + } + + public ChatNotify GetNotificationType() => ChatNotify.LeftNotice; + + public void Append(ChannelNotify data) + { + data.SenderGuid = _guid; + } + + ObjectGuid _guid; + } + + struct YouJoinedAppend : IChannelAppender + { + public YouJoinedAppend(Channel channel) + { + _channel = channel; + } + + public ChatNotify GetNotificationType() => ChatNotify.YouJoinedNotice; + + public void Append(ChannelNotify data) + { + data.ChatChannelID = (int)_channel.GetChannelId(); + } + + Channel _channel; + } + + struct YouLeftAppend : IChannelAppender + { + public YouLeftAppend(Channel channel) + { + _channel = channel; + } + + public ChatNotify GetNotificationType() => ChatNotify.YouLeftNotice; + + public void Append(ChannelNotify data) + { + data.ChatChannelID = (int)_channel.GetChannelId(); + } + + Channel _channel; + } + + struct WrongPasswordAppend : IChannelAppender + { + public ChatNotify GetNotificationType() => ChatNotify.WrongPasswordNotice; + + public void Append(ChannelNotify data) { } + } + + struct NotMemberAppend : IChannelAppender + { + public ChatNotify GetNotificationType() => ChatNotify.NotMemberNotice; + + public void Append(ChannelNotify data) { } + } + + struct NotModeratorAppend : IChannelAppender + { + public ChatNotify GetNotificationType() => ChatNotify.NotModeratorNotice; + + public void Append(ChannelNotify data) { } + } + + struct PasswordChangedAppend : IChannelAppender + { + public PasswordChangedAppend(ObjectGuid guid) + { + _guid = guid; + } + + public ChatNotify GetNotificationType() => ChatNotify.PasswordChangedNotice; + + public void Append(ChannelNotify data) + { + data.SenderGuid = _guid; + } + + ObjectGuid _guid; + } + + struct OwnerChangedAppend : IChannelAppender + { + public OwnerChangedAppend(ObjectGuid guid) + { + _guid = guid; + } + + public ChatNotify GetNotificationType() => ChatNotify.OwnerChangedNotice; + + public void Append(ChannelNotify data) + { + data.SenderGuid = _guid; + } + + ObjectGuid _guid; + } + + struct PlayerNotFoundAppend : IChannelAppender + { + public PlayerNotFoundAppend(string playerName) + { + _playerName = playerName; + } + + public ChatNotify GetNotificationType() => ChatNotify.PlayerNotFoundNotice; + + public void Append(ChannelNotify data) + { + data.Sender = _playerName; + } + + string _playerName; + } + + struct NotOwnerAppend : IChannelAppender + { + public ChatNotify GetNotificationType() => ChatNotify.NotOwnerNotice; + + public void Append(ChannelNotify data) { } + } + + struct ChannelOwnerAppend : IChannelAppender + { + public ChannelOwnerAppend(Channel channel, ObjectGuid ownerGuid) + { + _channel = channel; + _ownerGuid = ownerGuid; + _ownerName = ""; + + CharacterInfo characterInfo = Global.WorldMgr.GetCharacterInfo(_ownerGuid); + if (characterInfo != null) + _ownerName = characterInfo.Name; + } + + public ChatNotify GetNotificationType() => ChatNotify.ChannelOwnerNotice; + + public void Append(ChannelNotify data) + { + data.Sender = ((_channel.IsConstant() || _ownerGuid.IsEmpty()) ? "Nobody" : _ownerName); + } + + Channel _channel; + ObjectGuid _ownerGuid; + + string _ownerName; + } + + struct ModeChangeAppend : IChannelAppender + { + public ModeChangeAppend(ObjectGuid guid, ChannelMemberFlags oldFlags, ChannelMemberFlags newFlags) + { + _guid = guid; + _oldFlags = oldFlags; + _newFlags = newFlags; + } + + public ChatNotify GetNotificationType() => ChatNotify.ModeChangeNotice; + + public void Append(ChannelNotify data) + { + data.SenderGuid = _guid; + data.OldFlags = _oldFlags; + data.NewFlags = _newFlags; + } + + ObjectGuid _guid; + ChannelMemberFlags _oldFlags; + ChannelMemberFlags _newFlags; + } + + struct AnnouncementsOnAppend : IChannelAppender + { + public AnnouncementsOnAppend(ObjectGuid guid) + { + _guid = guid; + } + + public ChatNotify GetNotificationType() => ChatNotify.AnnouncementsOnNotice; + + public void Append(ChannelNotify data) + { + data.SenderGuid = _guid; + } + + ObjectGuid _guid; + } + + struct AnnouncementsOffAppend : IChannelAppender + { + public AnnouncementsOffAppend(ObjectGuid guid) + { + _guid = guid; + } + + public ChatNotify GetNotificationType() => ChatNotify.AnnouncementsOffNotice; + + public void Append(ChannelNotify data) + { + data.SenderGuid = _guid; + } + + ObjectGuid _guid; + } + + struct MutedAppend : IChannelAppender + { + public ChatNotify GetNotificationType() => ChatNotify.MutedNotice; + + public void Append(ChannelNotify data) { } + } + + struct PlayerKickedAppend : IChannelAppender + { + public PlayerKickedAppend(ObjectGuid kicker, ObjectGuid kickee) + { + _kicker = kicker; + _kickee = kickee; + } + + public ChatNotify GetNotificationType() => ChatNotify.PlayerKickedNotice; + + public void Append(ChannelNotify data) + { + data.SenderGuid = _kicker; + data.TargetGuid = _kickee; + } + + ObjectGuid _kicker; + ObjectGuid _kickee; + } + + struct BannedAppend : IChannelAppender + { + public ChatNotify GetNotificationType() => ChatNotify.BannedNotice; + + public void Append(ChannelNotify data) { } + } + + struct PlayerBannedAppend : IChannelAppender + { + public PlayerBannedAppend(ObjectGuid moderator, ObjectGuid banned) + { + _moderator = moderator; + _banned = banned; + } + + public ChatNotify GetNotificationType() => ChatNotify.PlayerBannedNotice; + + public void Append(ChannelNotify data) + { + data.SenderGuid = _moderator; + data.TargetGuid = _banned; + } + + ObjectGuid _moderator; + ObjectGuid _banned; + } + + struct PlayerUnbannedAppend : IChannelAppender + { + public PlayerUnbannedAppend(ObjectGuid moderator, ObjectGuid unbanned) + { + _moderator = moderator; + _unbanned = unbanned; + } + + public ChatNotify GetNotificationType() => ChatNotify.PlayerUnbannedNotice; + + public void Append(ChannelNotify data) + { + data.SenderGuid = _moderator; + data.TargetGuid = _unbanned; + } + + ObjectGuid _moderator; + ObjectGuid _unbanned; + } + + struct PlayerNotBannedAppend : IChannelAppender + { + public PlayerNotBannedAppend(string playerName) + { + _playerName = playerName; + } + + public ChatNotify GetNotificationType() => ChatNotify.PlayerNotBannedNotice; + + public void Append(ChannelNotify data) + { + data.Sender = _playerName; + } + + string _playerName; + } + + struct PlayerAlreadyMemberAppend : IChannelAppender + { + public PlayerAlreadyMemberAppend(ObjectGuid guid) + { + _guid = guid; + } + + public ChatNotify GetNotificationType() => ChatNotify.PlayerAlreadyMemberNotice; + + public void Append(ChannelNotify data) + { + data.SenderGuid = _guid; + } + + ObjectGuid _guid; + } + + struct InviteAppend : IChannelAppender + { + public InviteAppend(ObjectGuid guid) + { + _guid = guid; + } + + public ChatNotify GetNotificationType() => ChatNotify.InviteNotice; + + public void Append(ChannelNotify data) + { + data.SenderGuid = _guid; + } + + ObjectGuid _guid; + } + + struct InviteWrongFactionAppend : IChannelAppender + { + public ChatNotify GetNotificationType() => ChatNotify.InviteWrongFactionNotice; + + public void Append(ChannelNotify data) { } + } + + struct WrongFactionAppend : IChannelAppender + { + public ChatNotify GetNotificationType() => ChatNotify.WrongFactionNotice; + + public void Append(ChannelNotify data) { } + } + + struct InvalidNameAppend : IChannelAppender + { + public ChatNotify GetNotificationType() => ChatNotify.InvalidNameNotice; + + public void Append(ChannelNotify data) { } + } + + struct NotModeratedAppend : IChannelAppender + { + public ChatNotify GetNotificationType() => ChatNotify.NotModeratedNotice; + + public void Append(ChannelNotify data) { } + } + + struct PlayerInvitedAppend : IChannelAppender + { + public PlayerInvitedAppend(string playerName) + { + _playerName = playerName; + } + + public ChatNotify GetNotificationType() => ChatNotify.PlayerInvitedNotice; + + public void Append(ChannelNotify data) + { + data.Sender = _playerName; + } + + string _playerName; + } + + struct PlayerInviteBannedAppend : IChannelAppender + { + public PlayerInviteBannedAppend(string playerName) + { + _playerName = playerName; + } + + public ChatNotify GetNotificationType() => ChatNotify.PlayerInviteBannedNotice; + + public void Append(ChannelNotify data) + { + data.Sender = _playerName; + } + + string _playerName; + } + + struct ThrottledAppend : IChannelAppender + { + public ChatNotify GetNotificationType() => ChatNotify.ThrottledNotice; + + public void Append(ChannelNotify data) { } + } + + struct NotInAreaAppend : IChannelAppender + { + public ChatNotify GetNotificationType() => ChatNotify.NotInAreaNotice; + + public void Append(ChannelNotify data) { } + } + + struct NotInLFGAppend : IChannelAppender + { + public ChatNotify GetNotificationType() => ChatNotify.NotInLfgNotice; + + public void Append(ChannelNotify data) { } + } + + struct VoiceOnAppend : IChannelAppender + { + public VoiceOnAppend(ObjectGuid guid) + { + _guid = guid; + } + + public ChatNotify GetNotificationType() => ChatNotify.VoiceOnNotice; + + public void Append(ChannelNotify data) + { + data.SenderGuid = _guid; + } + + ObjectGuid _guid; + } + + struct VoiceOffAppend : IChannelAppender + { + public VoiceOffAppend(ObjectGuid guid) + { + _guid = guid; + } + + public ChatNotify GetNotificationType() => ChatNotify.VoiceOffNotice; + + public void Append(ChannelNotify data) + { + data.SenderGuid = _guid; + } + + ObjectGuid _guid; + } +} diff --git a/Game/Chat/Channels/ChannelManager.cs b/Game/Chat/Channels/ChannelManager.cs new file mode 100644 index 000000000..c5876fec5 --- /dev/null +++ b/Game/Chat/Channels/ChannelManager.cs @@ -0,0 +1,166 @@ +/* + * Copyright (C) 2012-2017 CypherCore + * + * 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, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +using Framework.Constants; +using Game.DataStorage; +using Game.Entities; +using Game.Network.Packets; +using System; +using System.Collections.Generic; + +namespace Game.Chat +{ + public class ChannelManager + { + public ChannelManager(Team team) + { + _team = team; + } + + public static ChannelManager ForTeam(Team team) + { + if (WorldConfig.GetBoolValue(WorldCfg.AllowTwoSideInteractionChannel)) + return allianceChannelMgr; // cross-faction + + if (team == Team.Alliance) + return allianceChannelMgr; + + if (team == Team.Horde) + return hordeChannelMgr; + + return null; + } + + public static Channel GetChannelForPlayerByNamePart(string namePart, Player playerSearcher) + { + foreach (Channel channel in playerSearcher.GetJoinedChannels()) + { + string chanName = channel.GetName(playerSearcher.GetSession().GetSessionDbcLocale()); + if (chanName.ToLower().Equals(namePart.ToLower())) + return channel; + } + + return null; + } + + public Channel GetJoinChannel(uint channelId, string name, AreaTableRecord zoneEntry = null) + { + if (channelId != 0) // builtin + { + ChatChannelsRecord channelEntry = CliDB.ChatChannelsStorage.LookupByKey(channelId); + uint zoneId = zoneEntry != null ? zoneEntry.Id : 0; + if (channelEntry.Flags.HasAnyFlag(ChannelDBCFlags.Global | ChannelDBCFlags.CityOnly)) + zoneId = 0; + + Tuple key = Tuple.Create(channelId, zoneId); + var channel = _channels.LookupByKey(key); + if (channel != null) + return channel; + + Channel newChannel = new Channel(channelId, _team, zoneEntry); + _channels[key] = newChannel; + return newChannel; + } + else // custom + { + var channel = _customChannels.LookupByKey(name.ToLower()); + if (channel != null) + return channel; + + Channel newChannel = new Channel(name, _team); + _customChannels[name.ToLower()] = newChannel; + return newChannel; + } + } + + public Channel GetChannel(uint channelId, string name, Player player, bool notify = true, AreaTableRecord zoneEntry = null) + { + Channel result = null; + if (channelId != 0) // builtin + { + ChatChannelsRecord channelEntry = CliDB.ChatChannelsStorage.LookupByKey(channelId); + uint zoneId = zoneEntry != null ? zoneEntry.Id : 0; + if (channelEntry.Flags.HasAnyFlag(ChannelDBCFlags.Global | ChannelDBCFlags.CityOnly)) + zoneId = 0; + + Tuple key = Tuple.Create(channelId, zoneId); + var channel = _channels.LookupByKey(key); + if (channel != null) + result = channel; + } + else // custom + { + var channel = _customChannels.LookupByKey(name.ToLower()); + if (channel != null) + result = channel; + } + + if (result == null && notify) + { + string channelName = name; + Channel.GetChannelName(ref channelName, channelId, player.GetSession().GetSessionDbcLocale(), zoneEntry); + + SendNotOnChannelNotify(player, channelName); + } + + return result; + } + + public void LeftChannel(string name) + { + string channelName = name.ToLower(); + + var channel = _customChannels.LookupByKey(channelName); + if (channel == null) + return; + + if (channel.GetNumPlayers() == 0) + _customChannels.Remove(channelName); + } + + public void LeftChannel(uint channelId, AreaTableRecord zoneEntry) + { + ChatChannelsRecord channelEntry = CliDB.ChatChannelsStorage.LookupByKey(channelId); + uint zoneId = zoneEntry != null ? zoneEntry.Id : 0; + if (channelEntry.Flags.HasAnyFlag(ChannelDBCFlags.Global | ChannelDBCFlags.CityOnly)) + zoneId = 0; + + Tuple key = Tuple.Create(channelId, zoneId); + var channel = _channels.LookupByKey(key); + if (channel == null) + return; + + if (channel.GetNumPlayers() == 0) + _channels.Remove(key); + } + + public static void SendNotOnChannelNotify(Player player, string name) + { + ChannelNotify notify = new ChannelNotify(); + notify.Type = ChatNotify.NotMemberNotice; + notify.Channel = name; + player.SendPacket(notify); + } + + Dictionary _customChannels = new Dictionary(); + Dictionary, Channel> _channels = new Dictionary, Channel>(); + Team _team; + + static ChannelManager allianceChannelMgr = new ChannelManager(Team.Alliance); + static ChannelManager hordeChannelMgr = new ChannelManager(Team.Horde); + } +} diff --git a/Game/Chat/CommandAttribute.cs b/Game/Chat/CommandAttribute.cs new file mode 100644 index 000000000..08b200592 --- /dev/null +++ b/Game/Chat/CommandAttribute.cs @@ -0,0 +1,66 @@ +/* + * Copyright (C) 2012-2017 CypherCore + * + * 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, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +using Framework.Constants; +using System; + +namespace Game.Chat +{ + [AttributeUsage(AttributeTargets.Method)] + public class CommandAttribute : Attribute + { + public CommandAttribute(string command, RBACPermissions rbac, bool allowConsole = false) + { + Name = command.ToLower(); + Help = ""; + RBAC = rbac; + AllowConsole = allowConsole; + } + + /// + /// Command's name. + /// + public string Name { get; private set; } + + /// + /// Help text for command. + /// + public string Help { get; set; } + + /// + /// Allow Console? + /// + public bool AllowConsole { get; private set; } + + /// + /// Minimum user level required to invoke the command. + /// + public RBACPermissions RBAC { get; set; } + } + + [AttributeUsage(AttributeTargets.Class)] + public class CommandGroupAttribute : CommandAttribute + { + public CommandGroupAttribute(string command, RBACPermissions rbac, bool allowConsole = false) : base(command, rbac, allowConsole) { } + } + + [AttributeUsage(AttributeTargets.Method)] + public class CommandNonGroupAttribute : CommandAttribute + { + public CommandNonGroupAttribute(string command, RBACPermissions rbac, bool allowConsole = false) : base(command, rbac, allowConsole) { } + } +} diff --git a/Game/Chat/CommandHandler.cs b/Game/Chat/CommandHandler.cs new file mode 100644 index 000000000..ecac2d7a7 --- /dev/null +++ b/Game/Chat/CommandHandler.cs @@ -0,0 +1,877 @@ +/* + * Copyright (C) 2012-2017 CypherCore + * + * 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, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +using Framework.Collections; +using Framework.Constants; +using Framework.IO; +using Game.DataStorage; +using Game.Entities; +using Game.Groups; +using Game.Maps; +using Game.Network.Packets; +using System; +using System.Collections.Generic; +using System.Linq; + +namespace Game.Chat +{ + public class CommandHandler + { + public CommandHandler(WorldSession session = null) + { + _session = session; + } + + public bool ParseCommand(string fullCmd) + { + if (string.IsNullOrEmpty(fullCmd)) + return false; + + string text = fullCmd; + // chat case (.command or !command format) + if (_session != null) + { + if (text[0] != '!' && text[0] != '.') + return false; + } + + if (text.Length < 2) + return false; + + /// ignore messages staring from many dots. + if ((text[0] == '.' && text[1] == '.') || (text[0] == '!' && text[1] == '!')) + return false; + + /// skip first . or ! (in console allowed use command with . and ! and without its) + if (text[0] == '!' || text[0] == '.') + text = text.Substring(1); + + if (!ExecuteCommandInTable(CommandManager.GetCommands(), text, fullCmd)) + { + if (_session != null && !_session.HasPermission(RBACPermissions.CommandsNotifyCommandNotFoundError)) + return false; + + SendSysMessage(CypherStrings.NoCmd); + } + + return true; + } + + public bool ExecuteCommandInTable(List table, string text, string fullcmd) + { + string oldtext = text; + StringArguments args = new StringArguments(text); + string cmd = args.NextString(); + + foreach (var command in table) + { + if (!hasStringAbbr(command.Name, cmd)) + continue; + + bool match = false; + if (command.Name.Length > cmd.Length) + { + foreach (var command2 in table) + { + if (!hasStringAbbr(command2.Name, cmd)) + continue; + + if (command2.Name.Equals(cmd)) + { + match = true; + break; + } + } + } + if (match) + continue; + + if (!command.ChildCommands.Empty()) + { + if (!ExecuteCommandInTable(command.ChildCommands, args.NextString(""), fullcmd)) + { + string arg = args.NextString(""); + if (!arg.IsEmpty()) + SendSysMessage(CypherStrings.NoSubcmd); + else + SendSysMessage(CypherStrings.CmdSyntax); + + ShowHelpForCommand(command.ChildCommands, arg); + } + + return true; + } + + // must be available and have handler + if (command.Handler == null || !IsAvailable(command)) + continue; + + _sentErrorMessage = false; + if (command.Handler(new StringArguments(!command.Name.IsEmpty() ? args.NextString("") : oldtext), this)) + { + if (GetSession() == null) // ignore console + return true; + + Player player = GetPlayer(); + if (!Global.AccountMgr.IsPlayerAccount(GetSession().GetSecurity())) + { + ObjectGuid guid = player.GetTarget(); + uint areaId = player.GetAreaId(); + string areaName = "Unknown"; + string zoneName = "Unknown"; + + AreaTableRecord area = CliDB.AreaTableStorage.LookupByKey(areaId); + if (area != null) + { + var locale = GetSessionDbcLocale(); + areaName = area.AreaName[locale]; + AreaTableRecord zone = CliDB.AreaTableStorage.LookupByKey(area.ParentAreaID); + if (zone != null) + zoneName = zone.AreaName[locale]; + } + + Log.outCommand(GetSession().GetAccountId(), "Command: {0} [Player: {1} ({2}) (Account: {3}) Postion: {4} Map: {5} ({6}) Area: {7} ({8}) Zone: {9} Selected: {10} ({11})]", + fullcmd, player.GetName(), player.GetGUID().ToString(), GetSession().GetAccountId(), player.GetPosition(), player.GetMapId(), + player.GetMap() ? player.GetMap().GetMapName() : "Unknown", areaId, areaName, zoneName, (player.GetSelectedUnit()) ? player.GetSelectedUnit().GetName() : "", guid.ToString()); + } + } + else if (!HasSentErrorMessage()) + { + if (!command.Help.IsEmpty()) + SendSysMessage(command.Help); + else + SendSysMessage(CypherStrings.CmdSyntax); + } + + return true; + } + + return false; + } + + public bool ShowHelpForCommand(List table, string text) + { + StringArguments args = new StringArguments(text); + if (!args.Empty()) + { + string cmd = args.NextString(); + foreach (var command in table) + { + // must be available (ignore handler existence for show command with possible available subcommands) + if (!IsAvailable(command)) + continue; + + if (!hasStringAbbr(command.Name, cmd)) + continue; + + // have subcommand + string subcmd = !cmd.IsEmpty() ? args.NextString() : ""; + if (!command.ChildCommands.Empty() && !subcmd.IsEmpty()) + { + if (ShowHelpForCommand(command.ChildCommands, subcmd)) + return true; + } + + if (!command.Help.IsEmpty()) + SendSysMessage(command.Help); + + if (!command.ChildCommands.Empty()) + if (ShowHelpForSubCommands(command.ChildCommands, command.Name, subcmd)) + return true; + + return !command.Help.IsEmpty(); + } + } + else + { + foreach (var command in table) + { + // must be available (ignore handler existence for show command with possible available subcommands) + if (!IsAvailable(command)) + continue; + + if (!command.Name.IsEmpty()) + continue; + + if (!command.Help.IsEmpty()) + SendSysMessage(command.Help); + + if (!command.ChildCommands.Empty()) + if (ShowHelpForSubCommands(command.ChildCommands, "", "")) + return true; + + return !command.Help.IsEmpty(); + } + } + + return ShowHelpForSubCommands(table, "", text); + } + + public bool ShowHelpForSubCommands(List table, string cmd, string subcmd) + { + string list = ""; + foreach (var command in table) + { + // must be available (ignore handler existence for show command with possible available subcommands) + if (!IsAvailable(command)) + continue; + + // for empty subcmd show all available + if (!subcmd.IsEmpty() && !hasStringAbbr(command.Name, subcmd)) + continue; + + if (GetSession() != null) + list += "\n "; + else + list += "\n\r "; + + list += command.Name; + + if (!command.ChildCommands.Empty()) + list += " ..."; + } + + if (list.IsEmpty()) + return false; + + if (table == CommandManager.GetCommands()) + { + SendSysMessage(CypherStrings.AvailableCmd); + SendSysMessage(list); + } + else + SendSysMessage(CypherStrings.SubcmdsList, cmd, list); + + return true; + } + + public virtual bool IsAvailable(ChatCommand cmd) + { + return HasPermission(cmd.Permission); + } + + public virtual bool HasPermission(RBACPermissions permission) { return _session.HasPermission(permission); } + + public string extractKeyFromLink(StringArguments args, params string[] linkType) + { + int throwaway; + return extractKeyFromLink(args, linkType, out throwaway); + } + public string extractKeyFromLink(StringArguments args, string[] linkType, out int found_idx) + { + string throwaway; + return extractKeyFromLink(args, linkType, out found_idx, out throwaway); + } + public string extractKeyFromLink(StringArguments args, string[] linkType, out int found_idx, out string something1) + { + found_idx = 0; + something1 = null; + + // skip empty + if (args.Empty()) + return null; + + // return non link case + if (args[0] != '|') + return args.NextString(); + + if (args[1] == 'c') + { + string check = args.NextString("|"); + if (string.IsNullOrEmpty(check)) + return null; + } + else + args.NextChar(); + + string cLinkType = args.NextString(":"); + if (string.IsNullOrEmpty(cLinkType)) + return null; + + for (var i = 0; i < linkType.Length; ++i) + { + if (cLinkType == linkType[i]) + { + string cKey = args.NextString(":|"); // extract key + + something1 = args.NextString(":|"); // extract something + + args.NextString("]"); // restart scan tail and skip name with possible spaces + args.NextString(); // skip link tail (to allow continue strtok(NULL, s) use after return from function + found_idx = i; + return cKey; + } + } + + args.NextString(); + SendSysMessage(CypherStrings.WrongLinkType); + return null; + } + + public void extractOptFirstArg(StringArguments args, out string arg1, out string arg2) + { + string p1 = args.NextString(); + string p2 = args.NextString(); + + if (string.IsNullOrEmpty(p2)) + { + p2 = p1; + p1 = null; + } + + arg1 = p1; + arg2 = p2; + } + + public GameTele extractGameTeleFromLink(StringArguments args) + { + string cId = extractKeyFromLink(args, "Htele"); + if (string.IsNullOrEmpty(cId)) + return null; + + uint id; + if (!uint.TryParse(cId, out id)) + return null; + + return Global.ObjectMgr.GetGameTele(id); + } + + public string extractQuotedArg(string str) + { + if (string.IsNullOrEmpty(str)) + return null; + + if (!str.Contains("\"")) + return null; + + return str.Replace("\"", String.Empty); + } + string extractPlayerNameFromLink(StringArguments args) + { + // |color|Hplayer:name|h[name]|h|r + string name = extractKeyFromLink(args, "Hplayer"); + if (name.IsEmpty()) + return ""; + + if (!ObjectManager.NormalizePlayerName(ref name)) + return ""; + + return name; + } + public bool extractPlayerTarget(StringArguments args, out Player player) + { + ObjectGuid guid; + string name; + return extractPlayerTarget(args, out player, out guid, out name); + } + public bool extractPlayerTarget(StringArguments args, out Player player, out ObjectGuid playerGuid) + { + string name; + return extractPlayerTarget(args, out player, out playerGuid, out name); + } + + public bool extractPlayerTarget(StringArguments args, out Player player, out ObjectGuid playerGuid, out string playerName) + { + player = null; + playerGuid = ObjectGuid.Empty; + playerName = ""; + + if (!args.Empty()) + { + string name = extractPlayerNameFromLink(args); + if (string.IsNullOrEmpty(name)) + { + SendSysMessage(CypherStrings.PlayerNotFound); + _sentErrorMessage = true; + return false; + } + + player = Global.ObjAccessor.FindPlayerByName(name); + ObjectGuid guid = player == null ? ObjectManager.GetPlayerGUIDByName(name) : ObjectGuid.Empty; + + playerGuid = player != null ? player.GetGUID() : guid; + playerName = player != null || !guid.IsEmpty() ? name : ""; + } + else + { + player = getSelectedPlayer(); + playerGuid = player != null ? player.GetGUID() : ObjectGuid.Empty; + playerName = player != null ? player.GetName() : ""; + } + + if (player == null && playerGuid.IsEmpty() && string.IsNullOrEmpty(playerName)) + { + SendSysMessage(CypherStrings.PlayerNotFound); + _sentErrorMessage = true; + return false; + } + + return true; + } + public ulong extractLowGuidFromLink(StringArguments args, ref HighGuid guidHigh) + { + int type; + + string[] guidKeys = + { + "Hplayer", + "Hcreature", + "Hgameobject" + }; + // |color|Hcreature:creature_guid|h[name]|h|r + // |color|Hgameobject:go_guid|h[name]|h|r + // |color|Hplayer:name|h[name]|h|r + string idS = extractKeyFromLink(args, guidKeys, out type); + if (string.IsNullOrEmpty(idS)) + return 0; + + switch (type) + { + case 0: + { + guidHigh = HighGuid.Player; + if (!ObjectManager.NormalizePlayerName(ref idS)) + return 0; + + Player player = Global.ObjAccessor.FindPlayerByName(idS); + if (player) + return player.GetGUID().GetCounter(); + + ObjectGuid guid = ObjectManager.GetPlayerGUIDByName(idS); + if (guid.IsEmpty()) + return 0; + + return guid.GetCounter(); + } + case 1: + { + guidHigh = HighGuid.Creature; + ulong lowguid = ulong.Parse(idS); + return lowguid; + } + case 2: + { + guidHigh = HighGuid.GameObject; + ulong lowguid = ulong.Parse(idS); + return lowguid; + } + } + + // unknown type? + return 0; + } + + static string[] spellKeys = + { + "Hspell", // normal spell + "Htalent", // talent spell + "Henchant", // enchanting recipe spell + "Htrade", // profession/skill spell + "Hglyph", // glyph + }; + public uint extractSpellIdFromLink(StringArguments args) + { + // number or [name] Shift-click form |color|Henchant:recipe_spell_id|h[prof_name: recipe_name]|h|r + // number or [name] Shift-click form |color|Hglyph:glyph_slot_id:glyph_prop_id|h[value]|h|r + // number or [name] Shift-click form |color|Hspell:spell_id|h[name]|h|r + // number or [name] Shift-click form |color|Htalent:talent_id, rank|h[name]|h|r + // number or [name] Shift-click form |color|Htrade:spell_id, skill_id, max_value, cur_value|h[name]|h|r + int type = 0; + string param1_str = null; + string idS = extractKeyFromLink(args, spellKeys, out type, out param1_str); + if (string.IsNullOrEmpty(idS)) + return 0; + + uint id = uint.Parse(idS); + + switch (type) + { + case 0: + return id; + case 1: + { + // talent + TalentRecord talentEntry = CliDB.TalentStorage.LookupByKey(id); + if (talentEntry == null) + return 0; + + return talentEntry.SpellID; + } + case 2: + case 3: + return id; + case 4: + { + uint glyph_prop_id = !string.IsNullOrEmpty(param1_str) ? uint.Parse(param1_str) : 0; + + GlyphPropertiesRecord glyphPropEntry = CliDB.GlyphPropertiesStorage.LookupByKey(glyph_prop_id); + if (glyphPropEntry == null) + return 0; + + return glyphPropEntry.SpellID; + } + } + + // unknown type? + return 0; + } + + public Player getSelectedPlayer() + { + if (_session == null) + return null; + + ObjectGuid selected = _session.GetPlayer().GetTarget(); + + if (selected.IsEmpty()) + return _session.GetPlayer(); + + return Global.ObjAccessor.FindPlayer(selected); + } + public Unit getSelectedUnit() + { + if (_session == null) + return null; + + ObjectGuid selected = _session.GetPlayer().GetTarget(); + + if (selected.IsEmpty()) + return _session.GetPlayer(); + + return Global.ObjAccessor.GetUnit(_session.GetPlayer(), selected); + } + public WorldObject GetSelectedObject() + { + if (_session == null) + return null; + + ObjectGuid selected = _session.GetPlayer().GetTarget(); + + if (selected.IsEmpty()) + return GetNearbyGameObject(); + + return Global.ObjAccessor.GetUnit(_session.GetPlayer(), selected); + } + public Creature getSelectedCreature() + { + if (_session == null) + return null; + + return ObjectAccessor.GetCreatureOrPetOrVehicle(_session.GetPlayer(), _session.GetPlayer().GetTarget()); + } + public Player getSelectedPlayerOrSelf() + { + if (_session == null) + return null; + + ObjectGuid selected = _session.GetPlayer().GetTarget(); + if (selected.IsEmpty()) + return _session.GetPlayer(); + + // first try with selected target + Player targetPlayer = Global.ObjAccessor.FindConnectedPlayer(selected); + // if the target is not a player, then return self + if (!targetPlayer) + targetPlayer = _session.GetPlayer(); + + return targetPlayer; + } + + public GameObject GetObjectFromPlayerMapByDbGuid(ulong lowguid) + { + if (_session == null) + return null; + + var bounds = _session.GetPlayer().GetMap().GetGameObjectBySpawnIdStore().LookupByKey(lowguid); + if (!bounds.Empty()) + return bounds.First(); + + return null; + } + + public Creature GetCreatureFromPlayerMapByDbGuid(ulong lowguid) + { + if (!_session) + return null; + + // Select the first alive creature or a dead one if not found + Creature creature = null; + var bounds = _session.GetPlayer().GetMap().GetCreatureBySpawnIdStore().LookupByKey(lowguid); + foreach (var it in bounds) + { + creature = it; + if (it.IsAlive()) + break; + } + + return creature; + } + GameObject GetNearbyGameObject() + { + if (_session == null) + return null; + + Player pl = _session.GetPlayer(); + NearestGameObjectCheck check = new NearestGameObjectCheck(pl); + GameObjectLastSearcher searcher = new GameObjectLastSearcher(pl, check); + Cell.VisitGridObjects(pl, searcher, MapConst.SizeofGrids); + return searcher.GetTarget(); + } + + public string playerLink(string name, bool console = false) + { + return console ? name : "|cffffffff|Hplayer:" + name + "|h[" + name + "]|h|r"; + } + public virtual string GetNameLink() + { + return GetNameLink(_session.GetPlayer()); + } + public string GetNameLink(Player obj) + { + return playerLink(obj.GetName()); + } + public virtual bool needReportToTarget(Player chr) + { + Player pl = _session.GetPlayer(); + return pl != chr && pl.IsVisibleGloballyFor(chr); + } + public bool HasLowerSecurity(Player target, ObjectGuid guid, bool strong = false) + { + WorldSession target_session = null; + uint target_account = 0; + + if (target != null) + target_session = target.GetSession(); + else if (!guid.IsEmpty()) + target_account = ObjectManager.GetPlayerAccountIdByGUID(guid); + + if (target_session == null && target_account == 0) + { + SendSysMessage(CypherStrings.PlayerNotFound); + _sentErrorMessage = true; + return true; + } + + return HasLowerSecurityAccount(target_session, target_account, strong); + } + public bool HasLowerSecurityAccount(WorldSession target, uint target_account, bool strong = false) + { + AccountTypes target_ac_sec; + + // allow everything from console and RA console + if (_session == null) + return false; + + // ignore only for non-players for non strong checks (when allow apply command at least to same sec level) + if (!Global.AccountMgr.IsPlayerAccount(_session.GetSecurity()) && !strong && !WorldConfig.GetBoolValue(WorldCfg.GmLowerSecurity)) + return false; + + if (target != null) + target_ac_sec = target.GetSecurity(); + else if (target_account != 0) + target_ac_sec = Global.AccountMgr.GetSecurity(target_account); + else + return true; // caller must report error for (target == NULL && target_account == 0) + + if (_session.GetSecurity() < target_ac_sec || (strong && _session.GetSecurity() <= target_ac_sec)) + { + SendSysMessage(CypherStrings.YoursSecurityIsLow); + _sentErrorMessage = true; + return true; + } + return false; + } + + bool hasStringAbbr(string name, string part) + { + // non "" command + if (!name.IsEmpty()) + { + // "" part from non-"" command + if (part.IsEmpty()) + return false; + + int partIndex = 0; + while (true) + { + if (partIndex >= part.Length) + return true; + else if (partIndex >= name.Length) + return false; + else if (char.ToLower(name[partIndex]) != char.ToLower(part[partIndex])) + return false; + ++partIndex; + } + } + // allow with any for "" + + return true; + } + + public WorldSession GetSession() + { + return _session; + } + public Player GetPlayer() + { + return _session.GetPlayer(); + } + public string GetCypherString(CypherStrings str) + { + return Global.ObjectMgr.GetCypherString(str); + } + + public virtual LocaleConstant GetSessionDbcLocale() + { + return _session.GetSessionDbcLocale(); + } + public virtual byte GetSessionDbLocaleIndex() + { + return (byte)_session.GetSessionDbLocaleIndex(); + } + public string GetParsedString(CypherStrings cypherString, params object[] args) + { + return string.Format(Global.ObjectMgr.GetCypherString(cypherString), args); + } + + public void SendSysMessage(CypherStrings cypherString, params object[] args) + { + SendSysMessage(Global.ObjectMgr.GetCypherString(cypherString), args); + } + public virtual void SendSysMessage(string str, params object[] args) + { + _sentErrorMessage = true; + string msg = string.Format(str, args); + + ChatPkt messageChat = new ChatPkt(); + + var lines = new StringArray(msg, "\n", "\r"); + for (var i = 0; i < lines.Length; ++i) + { + messageChat.Initialize(ChatMsg.System, Language.Universal, null, null, lines[i]); + _session.SendPacket(messageChat); + } + } + + public void SendNotification(CypherStrings str, params object[] args) + { + _session.SendNotification(str, args); + } + + public void SendGlobalSysMessage(string str) + { + // Chat output + ChatPkt data = new ChatPkt(); + data.Initialize(ChatMsg.System, Language.Universal, null, null, str); + Global.WorldMgr.SendGlobalMessage(data); + } + + public void SendGlobalGMSysMessage(string str) + { + // Chat output + ChatPkt data = new ChatPkt(); + data.Initialize(ChatMsg.System, Language.Universal, null, null, str); + Global.WorldMgr.SendGlobalGMMessage(data); + } + + public bool GetPlayerGroupAndGUIDByName(string name, out Player player, out Group group, out ObjectGuid guid, bool offline = false) + { + player = null; + guid = ObjectGuid.Empty; + group = null; + + if (!name.IsEmpty()) + { + if (!ObjectManager.NormalizePlayerName(ref name)) + { + SendSysMessage(CypherStrings.PlayerNotFound); + return false; + } + + player = Global.ObjAccessor.FindPlayerByName(name); + if (offline) + guid = ObjectManager.GetPlayerGUIDByName(name); + } + + if (player) + { + group = player.GetGroup(); + if (guid.IsEmpty() || !offline) + guid = player.GetGUID(); + } + else + { + if (getSelectedPlayer()) + player = getSelectedPlayer(); + else + player = _session.GetPlayer(); + + if (guid.IsEmpty() || !offline) + guid = player.GetGUID(); + group = player.GetGroup(); + } + + return true; + } + + public bool HasSentErrorMessage() { return _sentErrorMessage; } + + internal bool _sentErrorMessage; + WorldSession _session; + } + + public class ConsoleHandler : CommandHandler + { + public override bool IsAvailable(ChatCommand cmd) + { + return cmd.AllowConsole; + } + + public override bool HasPermission(RBACPermissions permission) + { + return true; + } + + public override void SendSysMessage(string str, params object[] args) + { + _sentErrorMessage = true; + string msg = string.Format(str, args); + + Log.outInfo(LogFilter.Server, msg); + } + + public override string GetNameLink() + { + return GetCypherString(CypherStrings.ConsoleCommand); + } + + public override bool needReportToTarget(Player chr) + { + return true; + } + + public override LocaleConstant GetSessionDbcLocale() + { + return Global.WorldMgr.GetDefaultDbcLocale(); + } + + public override byte GetSessionDbLocaleIndex() + { + return (byte)Global.WorldMgr.GetDefaultDbcLocale(); + } + } +} \ No newline at end of file diff --git a/Game/Chat/CommandManager.cs b/Game/Chat/CommandManager.cs new file mode 100644 index 000000000..f7bf06b67 --- /dev/null +++ b/Game/Chat/CommandManager.cs @@ -0,0 +1,197 @@ +/* + * Copyright (C) 2012-2017 CypherCore + * + * 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, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +using Framework.Configuration; +using Framework.Constants; +using Framework.Database; +using Framework.IO; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Reflection; + +namespace Game.Chat +{ + public class CommandManager + { + static CommandManager() + { + try + { + foreach (var type in Assembly.GetExecutingAssembly().GetTypes()) + { + if (type.Attributes.HasAnyFlag(TypeAttributes.NestedPrivate)) + continue; + + var groupAttribute = type.GetCustomAttribute(true); + if (groupAttribute != null) + { + _commands.Add(groupAttribute.Name, new ChatCommand(type, groupAttribute)); + } + + foreach (var method in type.GetMethods(BindingFlags.Static | BindingFlags.NonPublic)) + { + var commandAttribute = method.GetCustomAttribute(true); + if (commandAttribute != null) + _commands.Add(commandAttribute.Name, new ChatCommand(commandAttribute, (HandleCommandDelegate)method.CreateDelegate(typeof(HandleCommandDelegate)))); + } + } + + PreparedStatement stmt = DB.World.GetPreparedStatement(WorldStatements.SEL_COMMANDS); + SQLResult result = DB.World.Query(stmt); + if (!result.IsEmpty()) + { + do + { + string name = result.Read(0); + SetDataForCommandInTable(GetCommands(), name, result.Read(1), result.Read(2), name); + } + while (result.NextRow()); + } + } + catch (Exception ex) + { + Log.outException(ex); + } + } + + static bool SetDataForCommandInTable(List table, string text, uint permission, string help, string fullcommand) + { + StringArguments args = new StringArguments(text); + string cmd = args.NextString().ToLower(); + + foreach (var command in table) + { + // for data fill use full explicit command names + if (command.Name != cmd) + continue; + + // select subcommand from child commands list (including "") + if (!command.ChildCommands.Empty()) + { + var arg = args.NextString(""); + if (SetDataForCommandInTable(command.ChildCommands, arg, permission, help, fullcommand)) + return true; + else if (!arg.IsEmpty()) + return false; + + // fail with "" subcommands, then use normal level up command instead + } + // expected subcommand by full name DB content + else if (!args.NextString().IsEmpty()) + { + Log.outError(LogFilter.Sql, "Table `command` have unexpected subcommand '{0}' in command '{1}', skip.", text, fullcommand); + return false; + } + + if (command.Permission != (RBACPermissions)permission) + Log.outInfo(LogFilter.Misc, "Table `command` overwrite for command '{0}' default permission ({1}) by {2}", fullcommand, command.Permission, permission); + + command.Permission = (RBACPermissions)permission; + command.Help = help; + return true; + } + + // in case "" command let process by caller + if (!cmd.IsEmpty()) + { + if (table == GetCommands()) + Log.outError(LogFilter.Sql, "Table `command` have not existed command '{0}', skip.", cmd); + else + Log.outError(LogFilter.Sql, "Table `command` have not existed subcommand '{0}' in command '{1}', skip.", cmd, fullcommand); + } + + return false; + } + + public static void InitConsole() + { + if (ConfigMgr.GetDefaultValue("BeepAtStart", true)) + Console.Beep(); + + Console.ForegroundColor = ConsoleColor.Green; + Console.Write("Cypher>> "); + + var handler = new ConsoleHandler(); + while (!Global.WorldMgr.IsStopped) + { + handler.ParseCommand(Console.ReadLine()); + Console.ForegroundColor = ConsoleColor.Green; + Console.Write("Cypher>> "); + } + } + + public static List GetCommands() + { + return _commands.Values.ToList(); + } + + static SortedDictionary _commands = new SortedDictionary(); + } + + public delegate bool HandleCommandDelegate(StringArguments args, CommandHandler handler); + + public class ChatCommand + { + public ChatCommand(CommandAttribute attribute, HandleCommandDelegate handler) + { + Name = attribute.Name; + Permission = attribute.RBAC; + AllowConsole = attribute.AllowConsole; + Handler = handler; + Help = attribute.Help; + } + + public ChatCommand(Type type, CommandAttribute attribute) + { + Name = attribute.Name; + Permission = attribute.RBAC; + AllowConsole = attribute.AllowConsole; + Help = attribute.Help; + + foreach (var method in type.GetMethods(BindingFlags.Static | BindingFlags.NonPublic)) + { + CommandAttribute commandAttribute = method.GetCustomAttribute(false); + if (commandAttribute == null) + continue; + + if (commandAttribute.GetType() == typeof(CommandNonGroupAttribute)) + continue; + + ChildCommands.Add(new ChatCommand(commandAttribute, (HandleCommandDelegate)method.CreateDelegate(typeof(HandleCommandDelegate)))); + } + + foreach (var nestedType in type.GetNestedTypes(BindingFlags.NonPublic)) + { + var groupAttribute = nestedType.GetCustomAttribute(true); + if (groupAttribute == null) + continue; + + ChildCommands.Add(new ChatCommand(nestedType, groupAttribute)); + } + + ChildCommands = ChildCommands.OrderBy(p => string.IsNullOrEmpty(p.Name)).ThenBy(p => p.Name).ToList(); + } + + public string Name; + public RBACPermissions Permission; + public bool AllowConsole; + public HandleCommandDelegate Handler; + public string Help; + public List ChildCommands = new List(); + } +} diff --git a/Game/Chat/Commands/AccountCommands.cs b/Game/Chat/Commands/AccountCommands.cs new file mode 100644 index 000000000..c2300bf07 --- /dev/null +++ b/Game/Chat/Commands/AccountCommands.cs @@ -0,0 +1,815 @@ +/* + * Copyright (C) 2012-2017 CypherCore + * + * 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, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +using Framework.Constants; +using Framework.Database; +using Framework.IO; +using Game.Accounts; +using Game.Entities; +using System; + +namespace Game.Chat +{ + [CommandGroup("account", RBACPermissions.CommandAccount, true)] + class AccountCommands + { + [Command("", RBACPermissions.CommandAccount)] + static bool HandleAccountCommand(StringArguments args, CommandHandler handler) + { + if (handler.GetSession() == null) + return false; + + // GM Level + AccountTypes gmLevel = handler.GetSession().GetSecurity(); + handler.SendSysMessage(CypherStrings.AccountLevel, gmLevel); + + // Security level required + WorldSession session = handler.GetSession(); + bool hasRBAC = (session.HasPermission(RBACPermissions.EmailConfirmForPassChange) ? true : false); + uint pwConfig = 0; // 0 - PW_NONE, 1 - PW_EMAIL, 2 - PW_RBAC + + handler.SendSysMessage(CypherStrings.AccountSecType, (pwConfig == 0 ? "Lowest level: No Email input required." : + pwConfig == 1 ? "Highest level: Email input required." : pwConfig == 2 ? "Special level: Your account may require email input depending on settings. That is the case if another lien is printed." : + "Unknown security level: Notify technician for details.")); + + // RBAC required display - is not displayed for console + if (pwConfig == 2 && session != null && hasRBAC) + handler.SendSysMessage(CypherStrings.RbacEmailRequired); + + // Email display if sufficient rights + if (session.HasPermission(RBACPermissions.MayCheckOwnEmail)) + { + string emailoutput; + uint accountId = session.GetAccountId(); + + PreparedStatement stmt = DB.Login.GetPreparedStatement(LoginStatements.GET_EMAIL_BY_ID); + stmt.AddValue(0, accountId); + SQLResult result = DB.Login.Query(stmt); + + if (!result.IsEmpty()) + { + emailoutput = result.Read(0); + handler.SendSysMessage(CypherStrings.CommandEmailOutput, emailoutput); + } + } + + return true; + } + + [Command("addon", RBACPermissions.CommandAccountAddon)] + static bool HandleAccountAddonCommand(StringArguments args, CommandHandler handler) + { + if (args.Empty()) + { + handler.SendSysMessage(CypherStrings.CmdSyntax); + return false; + } + + uint accountId = handler.GetSession().GetAccountId(); + + int expansion = args.NextInt32(); //get int anyway (0 if error) + if (expansion < 0 || expansion > WorldConfig.GetIntValue(WorldCfg.Expansion)) + { + handler.SendSysMessage(CypherStrings.ImproperValue); + return false; + } + + PreparedStatement stmt = DB.Login.GetPreparedStatement(LoginStatements.UPD_EXPANSION); + stmt.AddValue(0, expansion); + stmt.AddValue(1, accountId); + DB.Login.Execute(stmt); + + handler.SendSysMessage(CypherStrings.AccountAddon, expansion); + return true; + } + + [Command("create", RBACPermissions.CommandAccountCreate, true)] + static bool HandleAccountCreateCommand(StringArguments args, CommandHandler handler) + { + if (args.Empty()) + return false; + + var accountName = args.NextString().ToUpper(); + var password = args.NextString(); + string email = ""; + + if (string.IsNullOrEmpty(accountName) || string.IsNullOrEmpty(password)) + return false; + + if (accountName.Contains("@")) + { + handler.SendSysMessage(CypherStrings.AccountUseBnetCommands); + return false; + } + + AccountOpResult result = Global.AccountMgr.CreateAccount(accountName, password, email); + switch (result) + { + case AccountOpResult.Ok: + handler.SendSysMessage(CypherStrings.AccountCreated, accountName); + if (handler.GetSession() != null) + { + Log.outInfo(LogFilter.Player, "Account: {0} (IP: {1}) Character:[{2}] (GUID: {3}) created Account {4} (Email: '{5}')", + handler.GetSession().GetAccountId(), handler.GetSession().GetRemoteAddress(), + handler.GetSession().GetPlayer().GetName(), handler.GetSession().GetPlayer().GetGUID().ToString(), + accountName, email); + } + break; + case AccountOpResult.NameTooLong: + handler.SendSysMessage(CypherStrings.AccountNameTooLong); + return false; + case AccountOpResult.PassTooLong: + handler.SendSysMessage(CypherStrings.AccountPassTooLong); + return false; + case AccountOpResult.NameAlreadyExist: + handler.SendSysMessage(CypherStrings.AccountAlreadyExist); + return false; + case AccountOpResult.DBInternalError: + handler.SendSysMessage(CypherStrings.AccountNotCreatedSqlError, accountName); + return false; + default: + handler.SendSysMessage(CypherStrings.AccountNotCreated, accountName); + return false; + } + + return true; + } + + [Command("delete", RBACPermissions.CommandAccountDelete, true)] + static bool HandleAccountDeleteCommand(StringArguments args, CommandHandler handler) + { + if (args.Empty()) + return false; + + string accountName = args.NextString(); + + if (string.IsNullOrEmpty(accountName)) + return false; + + uint accountId = Global.AccountMgr.GetId(accountName); + if (accountId == 0) + { + handler.SendSysMessage(CypherStrings.AccountNotExist, accountName); + return false; + } + + if (handler.HasLowerSecurityAccount(null, accountId, true)) + return false; + + AccountOpResult result = Global.AccountMgr.DeleteAccount(accountId); + switch (result) + { + case AccountOpResult.Ok: + handler.SendSysMessage(CypherStrings.AccountDeleted, accountName); + break; + case AccountOpResult.NameNotExist: + handler.SendSysMessage(CypherStrings.AccountNotExist, accountName); + return false; + case AccountOpResult.DBInternalError: + handler.SendSysMessage(CypherStrings.AccountNotDeletedSqlError, accountName); + return false; + default: + handler.SendSysMessage(CypherStrings.AccountNotDeleted, accountName); + return false; + } + + return true; + } + + [Command("email", RBACPermissions.CommandAccountSetSecEmail)] + static bool HandleAccountEmailCommand(StringArguments args, CommandHandler handler) + { + if (args.Empty()) + { + handler.SendSysMessage(CypherStrings.CmdSyntax); + return false; + } + + string oldEmail = args.NextString(); + string password = args.NextString(); + string email = args.NextString(); + string emailConfirmation = args.NextString(); + + if (string.IsNullOrEmpty(oldEmail) || string.IsNullOrEmpty(password) + || string.IsNullOrEmpty(email) || string.IsNullOrEmpty(emailConfirmation)) + { + handler.SendSysMessage(CypherStrings.CmdSyntax); + return false; + } + + if (!Global.AccountMgr.CheckEmail(handler.GetSession().GetAccountId(), oldEmail)) + { + handler.SendSysMessage(CypherStrings.CommandWrongemail); + Log.outInfo(LogFilter.Player, "Account: {0} (IP: {1}) Character:[{2}] (GUID: {3}) Tried to change email, but the provided email [{4}] is not equal to registration email [{5}].", + handler.GetSession().GetAccountId(), handler.GetSession().GetRemoteAddress(), + handler.GetSession().GetPlayer().GetName(), handler.GetSession().GetPlayer().GetGUID().ToString(), + email, oldEmail); + return false; + } + + if (!Global.AccountMgr.CheckPassword(handler.GetSession().GetAccountId(), password)) + { + handler.SendSysMessage(CypherStrings.CommandWrongoldpassword); + Log.outInfo(LogFilter.Player, "Account: {0} (IP: {1}) Character:[{2}] (GUID: {3}) Tried to change email, but the provided password is wrong.", + handler.GetSession().GetAccountId(), handler.GetSession().GetRemoteAddress(), + handler.GetSession().GetPlayer().GetName(), handler.GetSession().GetPlayer().GetGUID().ToString()); + return false; + } + + if (email == oldEmail) + { + handler.SendSysMessage(CypherStrings.OldEmailIsNewEmail); + return false; + } + + if (email != emailConfirmation) + { + handler.SendSysMessage(CypherStrings.NewEmailsNotMatch); + Log.outInfo(LogFilter.Player, "Account: {0} (IP: {1}) Character:[{2}] (GUID: {3}) Tried to change email, but the provided password is wrong.", + handler.GetSession().GetAccountId(), handler.GetSession().GetRemoteAddress(), + handler.GetSession().GetPlayer().GetName(), handler.GetSession().GetPlayer().GetGUID().ToString()); + return false; + } + + + AccountOpResult result = Global.AccountMgr.ChangeEmail(handler.GetSession().GetAccountId(), email); + switch (result) + { + case AccountOpResult.Ok: + handler.SendSysMessage(CypherStrings.CommandEmail); + Log.outInfo(LogFilter.Player, "Account: {0} (IP: {1}) Character:[{2}] (GUID: {3}) Changed Email from [{4}] to [{5}].", + handler.GetSession().GetAccountId(), handler.GetSession().GetRemoteAddress(), + handler.GetSession().GetPlayer().GetName(), handler.GetSession().GetPlayer().GetGUID().ToString(), + oldEmail, email); + break; + case AccountOpResult.EmailTooLong: + handler.SendSysMessage(CypherStrings.EmailTooLong); + return false; + default: + handler.SendSysMessage(CypherStrings.CommandNotchangeemail); + return false; + } + + return true; + } + + [Command("password", RBACPermissions.CommandAccountPassword)] + static bool HandleAccountPasswordCommand(StringArguments args, CommandHandler handler) + { + // If no args are given at all, we can return false right away. + if (args.Empty()) + { + handler.SendSysMessage(CypherStrings.CmdSyntax); + return false; + } + + // First, we check config. What security type (sec type) is it ? Depending on it, the command branches out + uint pwConfig = WorldConfig.GetUIntValue(WorldCfg.AccPasschangesec); // 0 - PW_NONE, 1 - PW_EMAIL, 2 - PW_RBAC + + // Command is supposed to be: .account password [$oldpassword] [$newpassword] [$newpasswordconfirmation] [$emailconfirmation] + string oldPassword = args.NextString(); // This extracts [$oldpassword] + string newPassword = args.NextString(); // This extracts [$newpassword] + string passwordConfirmation = args.NextString(); // This extracts [$newpasswordconfirmation] + string emailConfirmation = args.NextString(); // This defines the emailConfirmation variable, which is optional depending on sec type. + + //Is any of those variables missing for any reason ? We return false. + if (string.IsNullOrEmpty(oldPassword) || string.IsNullOrEmpty(newPassword) + || string.IsNullOrEmpty(passwordConfirmation)) + { + handler.SendSysMessage(CypherStrings.CmdSyntax); + + return false; + } + + // We compare the old, saved password to the entered old password - no chance for the unauthorized. + if (!Global.AccountMgr.CheckPassword(handler.GetSession().GetAccountId(), oldPassword)) + { + handler.SendSysMessage(CypherStrings.CommandWrongoldpassword); + + Log.outInfo(LogFilter.Player, "Account: {0} (IP: {1}) Character:[{2}] (GUID: {3}) Tried to change password, but the provided old password is wrong.", + handler.GetSession().GetAccountId(), handler.GetSession().GetRemoteAddress(), + handler.GetSession().GetPlayer().GetName(), handler.GetSession().GetPlayer().GetGUID().ToString()); + return false; + } + // This compares the old, current email to the entered email - however, only... + if ((pwConfig == 1 || (pwConfig == 2 && handler.GetSession().HasPermission(RBACPermissions.EmailConfirmForPassChange))) // ...if either PW_EMAIL or PW_RBAC with the Permission is active... + && !Global.AccountMgr.CheckEmail(handler.GetSession().GetAccountId(), emailConfirmation)) // ... and returns false if the comparison fails. + { + handler.SendSysMessage(CypherStrings.CommandWrongemail); + + Log.outInfo(LogFilter.Player, "Account: {0} (IP: {1}) Character:[{2}] (GUID: {3}) Tried to change password, but the entered email [{4}] is wrong.", + handler.GetSession().GetAccountId(), handler.GetSession().GetRemoteAddress(), + handler.GetSession().GetPlayer().GetName(), handler.GetSession().GetPlayer().GetGUID().ToString(), + emailConfirmation); + return false; + } + + // Making sure that newly entered password is correctly entered. + if (newPassword != passwordConfirmation) + { + handler.SendSysMessage(CypherStrings.NewPasswordsNotMatch); + return false; + } + + // Changes password and prints result. + AccountOpResult result = Global.AccountMgr.ChangePassword(handler.GetSession().GetAccountId(), newPassword); + switch (result) + { + case AccountOpResult.Ok: + handler.SendSysMessage(CypherStrings.CommandPassword); + Log.outInfo(LogFilter.Player, "Account: {0} (IP: {1}) Character:[{2}] (GUID: {3}) Changed Password.", + handler.GetSession().GetAccountId(), handler.GetSession().GetRemoteAddress(), + handler.GetSession().GetPlayer().GetName(), handler.GetSession().GetPlayer().GetGUID().ToString()); + break; + case AccountOpResult.PassTooLong: + handler.SendSysMessage(CypherStrings.PasswordTooLong); + return false; + default: + handler.SendSysMessage(CypherStrings.CommandNotchangepassword); + return false; + } + + return true; + } + + [Command("onlinelist", RBACPermissions.CommandAccountOnlineList, true)] + static bool HandleAccountOnlineListCommand(StringArguments args, CommandHandler handler) + { + // Get the list of accounts ID logged to the realm + PreparedStatement stmt = DB.Characters.GetPreparedStatement(CharStatements.SEL_CHARACTER_ONLINE); + + SQLResult result = DB.Characters.Query(stmt); + + if (result.IsEmpty()) + { + handler.SendSysMessage(CypherStrings.AccountListEmpty); + return true; + } + + // Display the list of account/characters online + handler.SendSysMessage(CypherStrings.AccountListBarHeader); + handler.SendSysMessage(CypherStrings.AccountListHeader); + handler.SendSysMessage(CypherStrings.AccountListBar); + + // Cycle through accounts + do + { + string name = result.Read(0); + uint account = result.Read(1); + + // Get the username, last IP and GM level of each account + // No SQL injection. account is uint32. + stmt = DB.Login.GetPreparedStatement(LoginStatements.SEL_ACCOUNT_INFO); + stmt.AddValue(0, account); + SQLResult resultLogin = DB.Login.Query(stmt); + + if (!resultLogin.IsEmpty()) + { + handler.SendSysMessage(CypherStrings.AccountListLine, resultLogin.Read(0), + name, resultLogin.Read(1), result.Read(2), result.Read(3), + resultLogin.Read(3), resultLogin.Read(2)); + } + else + handler.SendSysMessage(CypherStrings.AccountListError, name); + } + while (result.NextRow()); + + handler.SendSysMessage(CypherStrings.AccountListBar); + return true; + } + + [CommandGroup("set", RBACPermissions.CommandAccountSet, true)] + class SetCommands + { + [Command("password", RBACPermissions.CommandAccountSetPassword, true)] + static bool HandleSetPasswordCommand(StringArguments args, CommandHandler handler) + { + if (args.Empty()) + { + handler.SendSysMessage(CypherStrings.CmdSyntax); + return false; + } + + // Get the command line arguments + string accountName = args.NextString(); + string password = args.NextString(); + string passwordConfirmation = args.NextString(); + + if (string.IsNullOrEmpty(accountName) || string.IsNullOrEmpty(password) || string.IsNullOrEmpty(passwordConfirmation)) + return false; + + uint targetAccountId = Global.AccountMgr.GetId(accountName); + if (targetAccountId == 0) + { + handler.SendSysMessage(CypherStrings.AccountNotExist, accountName); + return false; + } + + // can set password only for target with less security + // This also restricts setting handler's own password + if (handler.HasLowerSecurityAccount(null, targetAccountId, true)) + return false; + + if (!password.Equals(passwordConfirmation)) + { + handler.SendSysMessage(CypherStrings.NewPasswordsNotMatch); + return false; + } + + AccountOpResult result = Global.AccountMgr.ChangePassword(targetAccountId, password); + switch (result) + { + case AccountOpResult.Ok: + handler.SendSysMessage(CypherStrings.CommandPassword); + break; + case AccountOpResult.NameNotExist: + handler.SendSysMessage(CypherStrings.AccountNotExist, accountName); + return false; + case AccountOpResult.PassTooLong: + handler.SendSysMessage(CypherStrings.PasswordTooLong); + return false; + default: + handler.SendSysMessage(CypherStrings.CommandNotchangepassword); + return false; + } + return true; + } + + [Command("addon", RBACPermissions.CommandAccountSetAddon, true)] + static bool HandleSetAddonCommand(StringArguments args, CommandHandler handler) + { + if (args.Empty()) + return false; + + // Get the command line arguments + string account = args.NextString(); + string exp = args.NextString(); + + if (string.IsNullOrEmpty(account)) + return false; + + string accountName; + uint accountId; + + if (string.IsNullOrEmpty(exp)) + { + Player player = handler.getSelectedPlayer(); + if (!player) + return false; + + accountId = player.GetSession().GetAccountId(); + Global.AccountMgr.GetName(accountId, out accountName); + exp = account; + } + else + { + // Convert Account name to Upper Format + accountName = account.ToUpper(); + + accountId = Global.AccountMgr.GetId(accountName); + if (accountId == 0) + { + handler.SendSysMessage(CypherStrings.AccountNotExist, accountName); + return false; + } + } + + // Let set addon state only for lesser (strong) security level + // or to self account + if (handler.GetSession() != null && handler.GetSession().GetAccountId() != accountId && + handler.HasLowerSecurityAccount(null, accountId, true)) + return false; + + int expansion = int.Parse(exp); //get int anyway (0 if error) + if (expansion < 0 || expansion > WorldConfig.GetIntValue(WorldCfg.Expansion)) + return false; + + PreparedStatement stmt = DB.Login.GetPreparedStatement(LoginStatements.UPD_EXPANSION); + + stmt.AddValue(0, expansion); + stmt.AddValue(1, accountId); + + DB.Login.Execute(stmt); + + handler.SendSysMessage(CypherStrings.AccountSetaddon, accountName, accountId, expansion); + return true; + } + + [Command("gmlevel", RBACPermissions.CommandAccountSetGmlevel, true)] + static bool HandleSetGmLevelCommand(StringArguments args, CommandHandler handler) + { + if (args.Empty()) + { + handler.SendSysMessage(CypherStrings.CmdSyntax); + return false; + } + + string targetAccountName = ""; + uint targetAccountId = 0; + AccountTypes targetSecurity = 0; + uint gm = 0; + string arg1 = args.NextString(); + string arg2 = args.NextString(); + string arg3 = args.NextString(); + bool isAccountNameGiven = true; + + if (string.IsNullOrEmpty(arg3)) + { + if (!handler.getSelectedPlayer()) + return false; + isAccountNameGiven = false; + } + + if (!isAccountNameGiven && string.IsNullOrEmpty(arg2)) + return false; + + if (isAccountNameGiven) + { + targetAccountName = arg1; + if (Global.AccountMgr.GetId(targetAccountName) == 0) + { + handler.SendSysMessage(CypherStrings.AccountNotExist, targetAccountName); + return false; + } + } + + // Check for invalid specified GM level. + gm = isAccountNameGiven ? uint.Parse(arg2) : uint.Parse(arg1); + if (gm > (uint)AccountTypes.Console) + { + handler.SendSysMessage(CypherStrings.BadValue); + return false; + } + + // command.getSession() == NULL only for console + targetAccountId = (isAccountNameGiven) ? Global.AccountMgr.GetId(targetAccountName) : handler.getSelectedPlayer().GetSession().GetAccountId(); + int gmRealmID = (isAccountNameGiven) ? int.Parse(arg3) : int.Parse(arg2); + AccountTypes playerSecurity; + if (handler.GetSession() != null) + playerSecurity = Global.AccountMgr.GetSecurity(handler.GetSession().GetAccountId(), gmRealmID); + else + playerSecurity = AccountTypes.Console; + + // can set security level only for target with less security and to less security that we have + // This is also reject self apply in fact + targetSecurity = Global.AccountMgr.GetSecurity(targetAccountId, gmRealmID); + if (targetSecurity >= playerSecurity || (AccountTypes)gm >= playerSecurity) + { + handler.SendSysMessage(CypherStrings.YoursSecurityIsLow); + return false; + } + PreparedStatement stmt; + // Check and abort if the target gm has a higher rank on one of the realms and the new realm is -1 + if (gmRealmID == -1 && !Global.AccountMgr.IsConsoleAccount(playerSecurity)) + { + stmt = DB.Login.GetPreparedStatement(LoginStatements.SEL_ACCOUNT_ACCESS_GMLEVEL_TEST); + stmt.AddValue(0, targetAccountId); + stmt.AddValue(1, gm); + + SQLResult result = DB.Login.Query(stmt); + + if (!result.IsEmpty()) + { + handler.SendSysMessage(CypherStrings.YoursSecurityIsLow); + return false; + } + } + + // Check if provided realmID has a negative value other than -1 + if (gmRealmID < -1) + { + handler.SendSysMessage(CypherStrings.InvalidRealmid); + return false; + } + + RBACData rbac = isAccountNameGiven ? null : handler.getSelectedPlayer().GetSession().GetRBACData(); + Global.AccountMgr.UpdateAccountAccess(rbac, targetAccountId, (byte)gm, gmRealmID); + handler.SendSysMessage(CypherStrings.YouChangeSecurity, targetAccountName, gm); + return true; + } + + [CommandGroup("sec", RBACPermissions.CommandAccountSetSec, true)] + class SetSecCommands + { + [Command("email", RBACPermissions.CommandAccountSetSecEmail, true)] + static bool HandleSetEmailCommand(StringArguments args, CommandHandler handler) + { + if (args.Empty()) + return false; + + // Get the command line arguments + string accountName = args.NextString(); + string email = args.NextString(); + string emailConfirmation = args.NextString(); + + if (string.IsNullOrEmpty(accountName) || string.IsNullOrEmpty(email) || string.IsNullOrEmpty(emailConfirmation)) + { + handler.SendSysMessage(CypherStrings.CmdSyntax); + return false; + } + + uint targetAccountId = Global.AccountMgr.GetId(accountName); + if (targetAccountId == 0) + { + handler.SendSysMessage(CypherStrings.AccountNotExist, accountName); + return false; + } + + /// can set email only for target with less security + /// This also restricts setting handler's own email. + if (handler.HasLowerSecurityAccount(null, targetAccountId, true)) + return false; + + if (!email.Equals(emailConfirmation)) + { + handler.SendSysMessage(CypherStrings.NewEmailsNotMatch); + return false; + } + + AccountOpResult result = Global.AccountMgr.ChangeEmail(targetAccountId, email); + switch (result) + { + case AccountOpResult.Ok: + handler.SendSysMessage(CypherStrings.CommandEmail); + Log.outInfo(LogFilter.Player, "ChangeEmail: Account {0} [Id: {1}] had it's email changed to {2}.", accountName, targetAccountId, email); + break; + case AccountOpResult.NameNotExist: + handler.SendSysMessage(CypherStrings.AccountNotExist, accountName); + return false; + case AccountOpResult.EmailTooLong: + handler.SendSysMessage(CypherStrings.EmailTooLong); + return false; + default: + handler.SendSysMessage(CypherStrings.CommandNotchangeemail); + return false; + } + + return true; + } + + [Command("regmail", RBACPermissions.CommandAccountSetSecRegmail, true)] + static bool HandleSetRegEmailCommand(StringArguments args, CommandHandler handler) + { + if (args.Empty()) + return false; + + //- We do not want anything short of console to use this by default. + //- So we force that. + if (handler.GetSession()) + return false; + + // Get the command line arguments + string accountName = args.NextString(); + string email = args.NextString(); + string emailConfirmation = args.NextString(); + + if (string.IsNullOrEmpty(accountName) || string.IsNullOrEmpty(email) || string.IsNullOrEmpty(emailConfirmation)) + { + handler.SendSysMessage(CypherStrings.CmdSyntax); + return false; + } + + uint targetAccountId = Global.AccountMgr.GetId(accountName); + if (targetAccountId == 0) + { + handler.SendSysMessage(CypherStrings.AccountNotExist, accountName); + return false; + } + + /// can set email only for target with less security + /// This also restricts setting handler's own email. + if (handler.HasLowerSecurityAccount(null, targetAccountId, true)) + return false; + + if (!email.Equals(emailConfirmation)) + { + handler.SendSysMessage(CypherStrings.NewEmailsNotMatch); + return false; + } + + AccountOpResult result = Global.AccountMgr.ChangeRegEmail(targetAccountId, email); + switch (result) + { + case AccountOpResult.Ok: + handler.SendSysMessage(CypherStrings.CommandEmail); + Log.outInfo(LogFilter.Player, "ChangeRegEmail: Account {0} [Id: {1}] had it's Registration Email changed to {2}.", accountName, targetAccountId, email); + break; + case AccountOpResult.NameNotExist: + handler.SendSysMessage(CypherStrings.AccountNotExist, accountName); + return false; + case AccountOpResult.EmailTooLong: + handler.SendSysMessage(CypherStrings.EmailTooLong); + return false; + default: + handler.SendSysMessage(CypherStrings.CommandNotchangeemail); + return false; + } + + return true; + } + } + } + + [CommandGroup("lock", RBACPermissions.CommandAccountLock)] + class LockCommands + { + [Command("country", RBACPermissions.CommandAccountLockCountry)] + static bool HandleAccountLockCountryCommand(StringArguments args, CommandHandler handler) + { + if (args.Empty()) + { + handler.SendSysMessage(CypherStrings.UseBol); + return false; + } + + string param = args.NextString(); + if (!param.IsEmpty()) + { + if (param == "on") + { + var ipBytes = System.Net.IPAddress.Parse(handler.GetSession().GetRemoteAddress()).GetAddressBytes(); + Array.Reverse(ipBytes); + + PreparedStatement stmt = DB.Login.GetPreparedStatement(LoginStatements.SEL_LOGON_COUNTRY); + stmt.AddValue(0, BitConverter.ToUInt32(ipBytes, 0)); + + SQLResult result = DB.Login.Query(stmt); + if (!result.IsEmpty()) + { + string country = result.Read(0); + stmt = DB.Login.GetPreparedStatement(LoginStatements.UPD_ACCOUNT_LOCK_CONTRY); + stmt.AddValue(0, country); + stmt.AddValue(1, handler.GetSession().GetAccountId()); + DB.Login.Execute(stmt); + handler.SendSysMessage(CypherStrings.CommandAcclocklocked); + } + else + { + handler.SendSysMessage("[IP2NATION] Table empty"); + Log.outDebug(LogFilter.Server, "[IP2NATION] Table empty"); + } + } + else if (param == "off") + { + PreparedStatement stmt = DB.Login.GetPreparedStatement(LoginStatements.UPD_ACCOUNT_LOCK_CONTRY); + stmt.AddValue(0, "00"); + stmt.AddValue(1, handler.GetSession().GetAccountId()); + DB.Login.Execute(stmt); + handler.SendSysMessage(CypherStrings.CommandAcclockunlocked); + } + return true; + } + handler.SendSysMessage(CypherStrings.UseBol); + return false; + } + + [Command("ip", RBACPermissions.CommandAccountLockIp)] + static bool HandleAccountLockIpCommand(StringArguments args, CommandHandler handler) + { + if (args.Empty()) + { + handler.SendSysMessage(CypherStrings.UseBol); + return false; + } + + string param = args.NextString(); + if (!string.IsNullOrEmpty(param)) + { + PreparedStatement stmt = DB.Login.GetPreparedStatement(LoginStatements.UPD_ACCOUNT_LOCK); + + if (param == "on") + { + stmt.AddValue(0, true); // locked + handler.SendSysMessage(CypherStrings.CommandAcclocklocked); + } + else if (param == "off") + { + stmt.AddValue(0, false); // unlocked + handler.SendSysMessage(CypherStrings.CommandAcclockunlocked); + } + stmt.AddValue(1, handler.GetSession().GetAccountId()); + + DB.Login.Execute(stmt); + return true; + } + + handler.SendSysMessage(CypherStrings.UseBol); + return false; + } + } + } +} diff --git a/Game/Chat/Commands/AhBotCommands.cs b/Game/Chat/Commands/AhBotCommands.cs new file mode 100644 index 000000000..8fb47715b --- /dev/null +++ b/Game/Chat/Commands/AhBotCommands.cs @@ -0,0 +1,224 @@ +/* + * Copyright (C) 2012-2017 CypherCore + * + * 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, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +using Framework.Constants; +using Framework.IO; + +namespace Game.Chat.Commands +{ + //Holder for now. + [CommandGroup("ahbot", RBACPermissions.CommandAhbot)] + class AhBotCommands + { + [Command("rebuild", RBACPermissions.CommandAhbotRebuild, true)] + static bool HandleAHBotRebuildCommand(StringArguments args, CommandHandler handler) + { + /*char* arg = strtok((char*)args, " "); + + bool all = false; + if (arg && strcmp(arg, "all") == 0) + all = true; + + sAuctionBot->Rebuild(all);*/ + return true; + } + + [Command("reload", RBACPermissions.CommandAhbotReload, true)] + static bool HandleAHBotReloadCommand(StringArguments args, CommandHandler handler) + { + //sAuctionBot->ReloadAllConfig(); + //handler->SendSysMessage(LANG_AHBOT_RELOAD_OK); + return true; + } + + [Command("status", RBACPermissions.CommandAhbotStatus, true)] + static bool HandleAHBotStatusCommand(StringArguments args, CommandHandler handler) + { + /* char* arg = strtok((char*)args, " "); + if (!arg) + return false; + + bool all = false; + if (strcmp(arg, "all") == 0) + all = true; + + AuctionHouseBotStatusInfo statusInfo; + sAuctionBot->PrepareStatusInfos(statusInfo); + + WorldSession* session = handler->GetSession(); + + if (!session) + { + handler->SendSysMessage(LANG_AHBOT_STATUS_BAR_CONSOLE); + handler->SendSysMessage(LANG_AHBOT_STATUS_TITLE1_CONSOLE); + handler->SendSysMessage(LANG_AHBOT_STATUS_MIDBAR_CONSOLE); + } + else + handler->SendSysMessage(LANG_AHBOT_STATUS_TITLE1_CHAT); + + public uint fmtId = session ? LANG_AHBOT_STATUS_FORMAT_CHAT : LANG_AHBOT_STATUS_FORMAT_CONSOLE; + + handler->PSendSysMessage(fmtId, handler->GetCypherString(LANG_AHBOT_STATUS_ITEM_COUNT), + statusInfo[AUCTION_HOUSE_ALLIANCE].ItemsCount, + statusInfo[AUCTION_HOUSE_HORDE].ItemsCount, + statusInfo[AUCTION_HOUSE_NEUTRAL].ItemsCount, + statusInfo[AUCTION_HOUSE_ALLIANCE].ItemsCount + + statusInfo[AUCTION_HOUSE_HORDE].ItemsCount + + statusInfo[AUCTION_HOUSE_NEUTRAL].ItemsCount); + + if (all) + { + handler->PSendSysMessage(fmtId, handler->GetCypherString(LANG_AHBOT_STATUS_ITEM_RATIO), + sAuctionBotConfig->GetConfig(CONFIG_AHBOT_ALLIANCE_ITEM_AMOUNT_RATIO), + sAuctionBotConfig->GetConfig(CONFIG_AHBOT_HORDE_ITEM_AMOUNT_RATIO), + sAuctionBotConfig->GetConfig(CONFIG_AHBOT_NEUTRAL_ITEM_AMOUNT_RATIO), + sAuctionBotConfig->GetConfig(CONFIG_AHBOT_ALLIANCE_ITEM_AMOUNT_RATIO) + + sAuctionBotConfig->GetConfig(CONFIG_AHBOT_HORDE_ITEM_AMOUNT_RATIO) + + sAuctionBotConfig->GetConfig(CONFIG_AHBOT_NEUTRAL_ITEM_AMOUNT_RATIO)); + + if (!session) + { + handler->SendSysMessage(LANG_AHBOT_STATUS_BAR_CONSOLE); + handler->SendSysMessage(LANG_AHBOT_STATUS_TITLE2_CONSOLE); + handler->SendSysMessage(LANG_AHBOT_STATUS_MIDBAR_CONSOLE); + } + else + handler->SendSysMessage(LANG_AHBOT_STATUS_TITLE2_CHAT); + + for (int i = 0; i < MAX_AUCTION_QUALITY; ++i) + handler->PSendSysMessage(fmtId, handler->GetCypherString(ahbotQualityIds[i]), + statusInfo[AUCTION_HOUSE_ALLIANCE].QualityInfo[i], + statusInfo[AUCTION_HOUSE_HORDE].QualityInfo[i], + statusInfo[AUCTION_HOUSE_NEUTRAL].QualityInfo[i], + sAuctionBotConfig->GetConfigItemQualityAmount(AuctionQuality(i))); + } + + if (!session) + handler->SendSysMessage(LANG_AHBOT_STATUS_BAR_CONSOLE); + */ + return true; + } + + [CommandGroup("items", RBACPermissions.CommandAhbotItems)] + class ItemsCommands + { + [Command("", RBACPermissions.CommandAhbotItems, true)] + static bool HandleAHBotItemsAmountCommand(StringArguments args, CommandHandler handler) + { + /*public uint qVals[MAX_AUCTION_QUALITY]; + char* arg = strtok((char*)args, " "); + for (int i = 0; i < MAX_AUCTION_QUALITY; ++i) + { + if (!arg) + return false; + qVals[i] = atoi(arg); + arg = strtok(NULL, " "); + } + + sAuctionBot->SetItemsAmount(qVals); + + for (int i = 0; i < MAX_AUCTION_QUALITY; ++i) + handler->PSendSysMessage(LANG_AHBOT_ITEMS_AMOUNT, handler->GetCypherString(ahbotQualityIds[i]), sAuctionBotConfig->GetConfigItemQualityAmount(AuctionQuality(i))); + */ + return true; + } + + [Command("blue", RBACPermissions.CommandAhbotItemsBlue, true)] + static bool HandleAHBotItemsAmountQualityBlue(StringArguments args, CommandHandler handler) { return true; } + + [Command("gray", RBACPermissions.CommandAhbotItemsGray, true)] + static bool HandleAHBotItemsAmountQualityGray(StringArguments args, CommandHandler handler) { return true; } + + [Command("green", RBACPermissions.CommandAhbotItemsGreen, true)] + static bool HandleAHBotItemsAmountQualityGreen(StringArguments args, CommandHandler handler) { return true; } + + [Command("orange", RBACPermissions.CommandAhbotItemsOrange, true)] + static bool HandleAHBotItemsAmountQualityOrange(StringArguments args, CommandHandler handler) { return true; } + + [Command("purple", RBACPermissions.CommandAhbotItemsPurple, true)] + static bool HandleAHBotItemsAmountQualityPurple(StringArguments args, CommandHandler handler) { return true; } + + [Command("white", RBACPermissions.CommandAhbotItemsWhite, true)] + static bool HandleAHBotItemsAmountQualityWhite(StringArguments args, CommandHandler handler) { return true; } + + [Command("yellow", RBACPermissions.CommandAhbotItemsYellow, true)] + static bool HandleAHBotItemsAmountQualityYellow(StringArguments args, CommandHandler handler) { return true; } + + static bool HandleAHBotItemsAmountQualityCommand(StringArguments args, CommandHandler handler) + { + /* + char* arg = strtok((char*)args, " "); + if (!arg) + return false; + public uint qualityVal = atoi(arg); + + sAuctionBot->SetItemsAmountForQuality(Q, qualityVal); + handler->PSendSysMessage(LANG_AHBOT_ITEMS_AMOUNT, handler->GetCypherString(ahbotQualityIds[Q]), + sAuctionBotConfig->GetConfigItemQualityAmount(Q)); + */ + return true; + } + } + + [CommandGroup("ratio", RBACPermissions.CommandAhbotRatio)] + class RatioCommands + { + [Command("", RBACPermissions.CommandAhbotRatio, true)] + static bool HandleAHBotItemsRatioCommand(StringArguments args, CommandHandler handler) + { + /*public uint rVal[MAX_AUCTION_QUALITY]; + char* arg = strtok((char*)args, " "); + for (int i = 0; i < MAX_AUCTION_QUALITY; ++i) + { + if (!arg) + return false; + rVal[i] = atoi(arg); + arg = strtok(NULL, " "); + } + + sAuctionBot->SetItemsRatio(rVal[0], rVal[1], rVal[2]); + + for (int i = 0; i < MAX_AUCTION_HOUSE_TYPE; ++i) + handler->PSendSysMessage(LANG_AHBOT_ITEMS_RATIO, AuctionBotConfig::GetHouseTypeName(AuctionHouseType(i)), sAuctionBotConfig->GetConfigItemAmountRatio(AuctionHouseType(i))); + */ + return true; + } + + [Command("alliance", RBACPermissions.CommandAhbotRatioAlliance, true)] + static bool HandleAHBotItemsRatioHouseAlliance(StringArguments args, CommandHandler handler) { return true; } + + [Command("horde", RBACPermissions.CommandAhbotRatioHorde, true)] + static bool HandleAHBotItemsRatioHouseHorde(StringArguments args, CommandHandler handler) { return true; } + + [Command("neutral", RBACPermissions.CommandAhbotRatioNeutral, true)] + static bool HandleAHBotItemsRatioHouseNeutral(StringArguments args, CommandHandler handler) { return true; } + + static bool HandleAHBotItemsRatioHouseCommand(StringArguments args, CommandHandler handler) + { + /*char* arg = strtok((char*)args, " "); + if (!arg) + return false; + public uint ratioVal = atoi(arg); + + sAuctionBot->SetItemsRatioForHouse(H, ratioVal); + handler->PSendSysMessage(LANG_AHBOT_ITEMS_RATIO, AuctionBotConfig::GetHouseTypeName(H), sAuctionBotConfig->GetConfigItemAmountRatio(H)); + */ + return true; + } + } + } +} diff --git a/Game/Chat/Commands/ArenaCommands.cs b/Game/Chat/Commands/ArenaCommands.cs new file mode 100644 index 000000000..53124b3f8 --- /dev/null +++ b/Game/Chat/Commands/ArenaCommands.cs @@ -0,0 +1,294 @@ +/* + * Copyright (C) 2012-2017 CypherCore + * + * 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, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +using Framework.Constants; +using Framework.IO; +using Game.Arenas; +using Game.Entities; + +namespace Game.Chat +{ + [CommandGroup("arena", RBACPermissions.CommandArena)] + class ArenaCommands + { + [Command("create", RBACPermissions.CommandArenaCreate, true)] + static bool HandleArenaCreateCommand(StringArguments args, CommandHandler handler) + { + if (args.Empty()) + return false; + + Player target; + if (!handler.extractPlayerTarget(args[0] != '"' ? args : null, out target)) + return false; + + string name = handler.extractQuotedArg(args.NextString()); + if (string.IsNullOrEmpty(name)) + return false; + + byte type = args.NextByte(); + if (type == 0) + return false; + + if (Global.ArenaTeamMgr.GetArenaTeamByName(name) != null) + { + handler.SendSysMessage(CypherStrings.ArenaErrorNameExists, name); + return false; + } + + if (type == 2 || type == 3 || type == 5) + { + if (Player.GetArenaTeamIdFromDB(target.GetGUID(), type) != 0) + { + handler.SendSysMessage(CypherStrings.ArenaErrorSize, target.GetName()); + return false; + } + + ArenaTeam arena = new ArenaTeam(); + + if (!arena.Create(target.GetGUID(), type, name, 4293102085, 101, 4293253939, 4, 4284049911)) + { + handler.SendSysMessage(CypherStrings.BadValue); + return false; + } + + Global.ArenaTeamMgr.AddArenaTeam(arena); + handler.SendSysMessage(CypherStrings.ArenaCreate, arena.GetName(), arena.GetId(), arena.GetArenaType(), arena.GetCaptain()); + } + else + { + handler.SendSysMessage(CypherStrings.BadValue); + return false; + } + + return true; + } + + [Command("disband", RBACPermissions.CommandArenaDisband, true)] + static bool HandleArenaDisbandCommand(StringArguments args, CommandHandler handler) + { + if (args.Empty()) + return false; + + uint teamId = args.NextUInt32(); + if (teamId == 0) + return false; + + ArenaTeam arena = Global.ArenaTeamMgr.GetArenaTeamById(teamId); + + if (arena == null) + { + handler.SendSysMessage(CypherStrings.ArenaErrorNotFound, teamId); + return false; + } + + if (arena.IsFighting()) + { + handler.SendSysMessage(CypherStrings.ArenaErrorCombat); + return false; + } + + string name = arena.GetName(); + arena.Disband(); + if (handler.GetSession() != null) + Log.outDebug(LogFilter.Arena, "GameMaster: {0} [GUID: {1}] disbanded arena team type: {2} [Id: {3}].", + handler.GetSession().GetPlayer().GetName(), handler.GetSession().GetPlayer().GetGUID().ToString(), arena.GetArenaType(), teamId); + else + Log.outDebug(LogFilter.Arena, "Console: disbanded arena team type: {0} [Id: {1}].", arena.GetArenaType(), teamId); + + handler.SendSysMessage(CypherStrings.ArenaDisband, name, teamId); + return true; + } + + [Command("rename", RBACPermissions.CommandArenaRename, true)] + static bool HandleArenaRenameCommand(StringArguments args, CommandHandler handler) + { + if (args.Empty()) + return false; + + string oldArenaStr = handler.extractQuotedArg(args.NextString()); + if (string.IsNullOrEmpty(oldArenaStr)) + { + handler.SendSysMessage(CypherStrings.BadValue); + return false; + } + + string newArenaStr = handler.extractQuotedArg(args.NextString()); + if (string.IsNullOrEmpty(newArenaStr)) + { + handler.SendSysMessage(CypherStrings.BadValue); + return false; + } + + ArenaTeam arena = Global.ArenaTeamMgr.GetArenaTeamByName(oldArenaStr); + if (arena == null) + { + handler.SendSysMessage(CypherStrings.AreanErrorNameNotFound, oldArenaStr); + return false; + } + + if (Global.ArenaTeamMgr.GetArenaTeamByName(newArenaStr) != null) + { + handler.SendSysMessage(CypherStrings.ArenaErrorNameExists, oldArenaStr); + return false; + } + + if (arena.IsFighting()) + { + handler.SendSysMessage(CypherStrings.ArenaErrorCombat); + return false; + } + + if (!arena.SetName(newArenaStr)) + { + handler.SendSysMessage(CypherStrings.BadValue); + return false; + } + + handler.SendSysMessage(CypherStrings.ArenaRename, arena.GetId(), oldArenaStr, newArenaStr); + if (handler.GetSession() != null) + Log.outDebug(LogFilter.Arena, "GameMaster: {0} [GUID: {1}] rename arena team \"{2}\"[Id: {3}] to \"{4}\"", + handler.GetSession().GetPlayer().GetName(), handler.GetSession().GetPlayer().GetGUID().ToString(), oldArenaStr, arena.GetId(), newArenaStr); + else + Log.outDebug(LogFilter.Arena, "Console: rename arena team \"{0}\"[Id: {1}] to \"{2}\"", oldArenaStr, arena.GetId(), newArenaStr); + + return true; + } + + [Command("captain", RBACPermissions.CommandArenaCaptain)] + static bool HandleArenaCaptainCommand(StringArguments args, CommandHandler handler) + { + if (args.Empty()) + return false; + + string idStr; + string nameStr; + handler.extractOptFirstArg(args, out idStr, out nameStr); + if (string.IsNullOrEmpty(idStr)) + return false; + + uint teamId = uint.Parse(idStr); + if (teamId == 0) + return false; + + Player target; + ObjectGuid targetGuid; + if (!handler.extractPlayerTarget(new StringArguments(nameStr), out target, out targetGuid)) + return false; + + ArenaTeam arena = Global.ArenaTeamMgr.GetArenaTeamById(teamId); + + if (arena == null) + { + handler.SendSysMessage(CypherStrings.ArenaErrorNotFound, teamId); + return false; + } + + if (!target) + { + handler.SendSysMessage(CypherStrings.PlayerNotExistOrOffline, nameStr); + return false; + } + + if (arena.IsFighting()) + { + handler.SendSysMessage(CypherStrings.ArenaErrorCombat); + return false; + } + + if (!arena.IsMember(targetGuid)) + { + handler.SendSysMessage(CypherStrings.ArenaErrorNotMember, nameStr, arena.GetName()); + return false; + } + + if (arena.GetCaptain() == targetGuid) + { + handler.SendSysMessage(CypherStrings.ArenaErrorCaptain, nameStr, arena.GetName()); + return false; + } + + arena.SetCaptain(targetGuid); + + CharacterInfo oldCaptainNameData = Global.WorldMgr.GetCharacterInfo(arena.GetCaptain()); + if (oldCaptainNameData == null) + return false; + + handler.SendSysMessage(CypherStrings.ArenaCaptain, arena.GetName(), arena.GetId(), oldCaptainNameData.Name, target.GetName()); + if (handler.GetSession() != null) + Log.outDebug(LogFilter.Arena, "GameMaster: {0} [GUID: {1}] promoted player: {2} [GUID: {3}] to leader of arena team \"{4}\"[Id: {5}]", + handler.GetSession().GetPlayer().GetName(), handler.GetSession().GetPlayer().GetGUID().ToString(), target.GetName(), target.GetGUID().ToString(), arena.GetName(), arena.GetId()); + else + Log.outDebug(LogFilter.Arena, "Console: promoted player: {0} [GUID: {1}] to leader of arena team \"{2}\"[Id: {3}]", + target.GetName(), target.GetGUID().ToString(), arena.GetName(), arena.GetId()); + + return true; + } + + [Command("info", RBACPermissions.CommandArenaInfo, true)] + static bool HandleArenaInfoCommand(StringArguments args, CommandHandler handler) + { + if (args.Empty()) + return false; + + uint teamId = args.NextUInt32(); + if (teamId == 0) + return false; + + ArenaTeam arena = Global.ArenaTeamMgr.GetArenaTeamById(teamId); + + if (arena == null) + { + handler.SendSysMessage(CypherStrings.ArenaErrorNotFound, teamId); + return false; + } + + handler.SendSysMessage(CypherStrings.ArenaInfoHeader, arena.GetName(), arena.GetId(), arena.GetRating(), arena.GetArenaType(), arena.GetArenaType()); + foreach (var member in arena.GetMembers()) + handler.SendSysMessage(CypherStrings.ArenaInfoMembers, member.Name, member.Guid, member.PersonalRating, (arena.GetCaptain() == member.Guid ? "- Captain" : "")); + + return true; + } + + [Command("lookup", RBACPermissions.CommandArenaLookup)] + static bool HandleArenaLookupCommand(StringArguments args, CommandHandler handler) + { + if (args.Empty()) + return false; + + string name = args.NextString().ToLower(); + + bool found = false; + foreach (var arena in Global.ArenaTeamMgr.GetArenaTeamMap().Values) + { + if (arena.GetName() == name) + { + if (handler.GetSession() != null) + { + handler.SendSysMessage(CypherStrings.ArenaLookup, arena.GetName(), arena.GetId(), arena.GetArenaType(), arena.GetArenaType()); + found = true; + continue; + } + } + } + + if (!found) + handler.SendSysMessage(CypherStrings.AreanErrorNameNotFound, name); + + return true; + } + } +} diff --git a/Game/Chat/Commands/BNetAccountCommands.cs b/Game/Chat/Commands/BNetAccountCommands.cs new file mode 100644 index 000000000..3a2406899 --- /dev/null +++ b/Game/Chat/Commands/BNetAccountCommands.cs @@ -0,0 +1,429 @@ +/* + * Copyright (C) 2012-2017 CypherCore + * + * 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, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +using Framework.Constants; +using Framework.Database; +using Framework.IO; +using System; +using System.Linq; + +namespace Game.Chat.Commands +{ + [CommandGroup("bnetaccount", RBACPermissions.CommandBnetAccount, true)] + class BNetAccountCommands + { + [Command("create", RBACPermissions.CommandBnetAccountCreate, true)] + static bool HandleAccountCreateCommand(StringArguments args, CommandHandler handler) + { + if (args.Empty()) + return false; + + // Parse the command line arguments + string accountName = args.NextString(); + string password = args.NextString(); + if (string.IsNullOrEmpty(accountName) || string.IsNullOrEmpty(password)) + return false; + + if (!accountName.Contains('@')) + { + handler.SendSysMessage(CypherStrings.AccountInvalidBnetName); + return false; + } + + string createGameAccountParam = args.NextString(); + bool createGameAccount = true; + if (!string.IsNullOrEmpty(createGameAccountParam)) + createGameAccount = bool.Parse(createGameAccountParam); + + string gameAccountName; + switch (Global.BNetAccountMgr.CreateBattlenetAccount(accountName, password, createGameAccount, out gameAccountName)) + { + case AccountOpResult.Ok: + if (createGameAccount) + handler.SendSysMessage(CypherStrings.AccountCreatedBnetWithGame, accountName, gameAccountName); + else + handler.SendSysMessage(CypherStrings.AccountCreated, accountName); + + if (handler.GetSession() != null) + { + Log.outInfo(LogFilter.Player, "Account: {0} (IP: {1}) Character:[{2}] ({3}) created Battle.net account {4}{5}{6}", + handler.GetSession().GetAccountId(), handler.GetSession().GetRemoteAddress(), handler.GetSession().GetPlayer().GetName(), + handler.GetSession().GetPlayer().GetGUID().ToString(), accountName, createGameAccount ? " with game account " : "", createGameAccount ? gameAccountName : ""); + } + break; + case AccountOpResult.NameTooLong: + handler.SendSysMessage(CypherStrings.AccountNameTooLong); + return false; + case AccountOpResult.PassTooLong: + handler.SendSysMessage(CypherStrings.AccountPassTooLong); + return false; + case AccountOpResult.NameAlreadyExist: + handler.SendSysMessage(CypherStrings.AccountAlreadyExist); + return false; + default: + break; + } + + return true; + } + + [Command("gameaccountcreate", RBACPermissions.CommandBnetAccountCreateGame, true)] + static bool HandleGameAccountCreateCommand(StringArguments args, CommandHandler handler) + { + if (args.Empty()) + { + handler.SendSysMessage(CypherStrings.CmdSyntax); + return false; + } + + string bnetAccountName = args.NextString(); + uint accountId = Global.BNetAccountMgr.GetId(bnetAccountName); + if (accountId == 0) + { + handler.SendSysMessage(CypherStrings.AccountNotExist, bnetAccountName); + return false; + } + + byte index = (byte)(Global.BNetAccountMgr.GetMaxIndex(accountId) + 1); + string accountName = accountId.ToString() + '#' + index; + + switch (Global.AccountMgr.CreateAccount(accountName, "DUMMY", bnetAccountName, accountId, index)) + { + case AccountOpResult.Ok: + handler.SendSysMessage(CypherStrings.AccountCreated, accountName); + if (handler.GetSession() != null) + { + Log.outInfo(LogFilter.Player, "Account: {0} (IP: {1}) Character:[{2}] ({3}) created Account {4} (Email: '{5}')", + handler.GetSession().GetAccountId(), handler.GetSession().GetRemoteAddress(), handler.GetSession().GetPlayer().GetName(), handler.GetSession().GetPlayer().GetGUID().ToString(), + accountName, bnetAccountName); + } + break; + case AccountOpResult.NameTooLong: + handler.SendSysMessage(CypherStrings.AccountNameTooLong); + return false; + case AccountOpResult.PassTooLong: + handler.SendSysMessage(CypherStrings.AccountPassTooLong); + return false; + case AccountOpResult.NameAlreadyExist: + handler.SendSysMessage(CypherStrings.AccountAlreadyExist); + return false; + case AccountOpResult.DBInternalError: + handler.SendSysMessage(CypherStrings.AccountNotCreatedSqlError, accountName); + return false; + default: + handler.SendSysMessage(CypherStrings.AccountNotCreated, accountName); + return false; + } + + return true; + } + + [Command("link", RBACPermissions.CommandBnetAccountLink, true)] + static bool HandleAccountLinkCommand(StringArguments args, CommandHandler handler) + { + if (args.Empty()) + { + handler.SendSysMessage(CypherStrings.CmdSyntax); + return false; + } + + string bnetAccountName = args.NextString(); + string gameAccountName = args.NextString(); + + switch (Global.BNetAccountMgr.LinkWithGameAccount(bnetAccountName, gameAccountName)) + { + case AccountOpResult.Ok: + handler.SendSysMessage(CypherStrings.AccountBnetLinked, bnetAccountName, gameAccountName); + break; + case AccountOpResult.NameNotExist: + handler.SendSysMessage(CypherStrings.AccountOrBnetDoesNotExist, bnetAccountName, gameAccountName); + break; + case AccountOpResult.BadLink: + handler.SendSysMessage( CypherStrings.AccountAlreadyLinked, gameAccountName); + break; + default: + break; + } + + return true; + } + + [Command("listgameaccounts", RBACPermissions.CommandBnetAccountListGameAccounts, true)] + static bool HandleListGameAccountsCommand(StringArguments args, CommandHandler handler) + { + if (args.Empty()) + return false; + + string battlenetAccountName = args.NextString(); + PreparedStatement stmt = DB.Login.GetPreparedStatement(LoginStatements.SEL_BNET_GAME_ACCOUNT_LIST); + stmt.AddValue(0, battlenetAccountName); + + SQLResult accountList = DB.Login.Query(stmt); + if (!accountList.IsEmpty()) + { + var formatDisplayName = new Func(name => + { + int index = name.IndexOf('#'); + if (index > 0) + return "WoW" + name.Substring(++index); + else + return name; + }); + + handler.SendSysMessage("----------------------------------------------------"); + handler.SendSysMessage(CypherStrings.AccountBnetListHeader); + handler.SendSysMessage("----------------------------------------------------"); + do + { + handler.SendSysMessage("| {10:0} | {1} | {2} |", accountList.Read(0), accountList.Read(1), formatDisplayName(accountList.Read(1))); + } while (accountList.NextRow()); + handler.SendSysMessage("----------------------------------------------------"); + } + else + handler.SendSysMessage(CypherStrings.AccountBnetListNoAccounts, battlenetAccountName); + + return true; + } + + [Command("password", RBACPermissions.CommandBnetAccountPassword, true)] + static bool HandleAccountPasswordCommand(StringArguments args, CommandHandler handler) + { + // If no args are given at all, we can return false right away. + if (args.Empty()) + { + handler.SendSysMessage(CypherStrings.CmdSyntax); + return false; + } + + // Command is supposed to be: .account password [$oldpassword] [$newpassword] [$newpasswordconfirmation] [$emailconfirmation] + string oldPassword = args.NextString(); // This extracts [$oldpassword] + string newPassword = args.NextString(); // This extracts [$newpassword] + string passwordConfirmation = args.NextString(); // This extracts [$newpasswordconfirmation] + + //Is any of those variables missing for any reason ? We return false. + if (string.IsNullOrEmpty(oldPassword) || string.IsNullOrEmpty(newPassword) || string.IsNullOrEmpty(passwordConfirmation)) + { + handler.SendSysMessage(CypherStrings.CmdSyntax); + return false; + } + + // We compare the old, saved password to the entered old password - no chance for the unauthorized. + if (!Global.BNetAccountMgr.CheckPassword(handler.GetSession().GetAccountId(), oldPassword)) + { + handler.SendSysMessage(CypherStrings.CommandWrongoldpassword); + + Log.outInfo(LogFilter.Player, "Battle.net account: {0} (IP: {1}) Character:[{2}] ({3}) Tried to change password, but the provided old password is wrong.", + handler.GetSession().GetAccountId(), handler.GetSession().GetRemoteAddress(), handler.GetSession().GetPlayer().GetName(), handler.GetSession().GetPlayer().GetGUID().ToString()); + return false; + } + + // Making sure that newly entered password is correctly entered. + if (newPassword != passwordConfirmation) + { + handler.SendSysMessage(CypherStrings.NewPasswordsNotMatch); + return false; + } + + // Changes password and prints result. + AccountOpResult result = Global.BNetAccountMgr.ChangePassword(handler.GetSession().GetBattlenetAccountId(), newPassword); + switch (result) + { + case AccountOpResult.Ok: + handler.SendSysMessage(CypherStrings.CommandPassword); + Log.outInfo(LogFilter.Player, "Battle.net account: {0} (IP: {1}) Character:[{2}] ({3}) Changed Password.", + handler.GetSession().GetBattlenetAccountId(), handler.GetSession().GetRemoteAddress(), handler.GetSession().GetPlayer().GetName(), handler.GetSession().GetPlayer().GetGUID().ToString()); + break; + case AccountOpResult.PassTooLong: + handler.SendSysMessage(CypherStrings.PasswordTooLong); + return false; + default: + handler.SendSysMessage(CypherStrings.CommandNotchangepassword); + return false; + } + + return true; + } + + [Command("unlink", RBACPermissions.CommandBnetAccountUnlink, true)] + static bool HandleAccountUnlinkCommand(StringArguments args, CommandHandler handler) + { + if (args.Empty()) + { + handler.SendSysMessage(CypherStrings.CmdSyntax); + return false; + } + + string gameAccountName = args.NextString(); + switch (Global.BNetAccountMgr.UnlinkGameAccount(gameAccountName)) + { + case AccountOpResult.Ok: + handler.SendSysMessage(CypherStrings.AccountBnetUnlinked, gameAccountName); + break; + case AccountOpResult.NameNotExist: + handler.SendSysMessage(CypherStrings.AccountNotExist, gameAccountName); + break; + case AccountOpResult.BadLink: + handler.SendSysMessage(CypherStrings.AccountBnetNotLinked, gameAccountName); + break; + default: + break; + } + + return true; + } + + [CommandGroup("lock", RBACPermissions.CommandBnetAccount, true)] + class LockCommands { + [Command("country", RBACPermissions.CommandBnetAccountLockCountry, true)] + static bool HandleLockCountryCommand(StringArguments args, CommandHandler handler) + { + if (args.Empty()) + { + handler.SendSysMessage(CypherStrings.UseBol); + return false; + } + + string param = args.NextString(); + if (!string.IsNullOrEmpty(param)) + { + if (param == "on") + { + PreparedStatement stmt = DB.Login.GetPreparedStatement(LoginStatements.SEL_LOGON_COUNTRY); + var ipBytes = System.Net.IPAddress.Parse(handler.GetSession().GetRemoteAddress()).GetAddressBytes(); + Array.Reverse(ipBytes); + stmt.AddValue(0, BitConverter.ToUInt32(ipBytes, 0)); + SQLResult result = DB.Login.Query(stmt); + if (!result.IsEmpty()) + { + string country = result.Read(0); + stmt = DB.Login.GetPreparedStatement(LoginStatements.UPD_BNET_ACCOUNT_LOCK_CONTRY); + stmt.AddValue(0, country); + stmt.AddValue(1, handler.GetSession().GetBattlenetAccountId()); + DB.Login.Execute(stmt); + handler.SendSysMessage(CypherStrings.CommandAcclocklocked); + } + else + { + handler.SendSysMessage("[IP2NATION] Table empty"); + Log.outDebug(LogFilter.Server, "[IP2NATION] Table empty"); + } + } + else if (param == "off") + { + PreparedStatement stmt = DB.Login.GetPreparedStatement(LoginStatements.UPD_BNET_ACCOUNT_LOCK_CONTRY); + stmt.AddValue(0, "00"); + stmt.AddValue(1, handler.GetSession().GetBattlenetAccountId()); + DB.Login.Execute(stmt); + handler.SendSysMessage(CypherStrings.CommandAcclockunlocked); + } + return true; + } + + handler.SendSysMessage(CypherStrings.UseBol); + return false; + } + + [Command("ip", RBACPermissions.CommandBnetAccountLockIp, true)] + static bool HandleLockIpCommand(StringArguments args, CommandHandler handler) + { + if (args.Empty()) + { + handler.SendSysMessage(CypherStrings.UseBol); + return false; + } + + string param = args.NextString(); + + if (!string.IsNullOrEmpty(param)) + { + PreparedStatement stmt = DB.Login.GetPreparedStatement(LoginStatements.UPD_BNET_ACCOUNT_LOCK); + if (param == "on") + { + stmt.AddValue(0, true); // locked + handler.SendSysMessage(CypherStrings.CommandAcclocklocked); + } + else if (param == "off") + { + stmt.AddValue(0, false); // unlocked + handler.SendSysMessage(CypherStrings.CommandAcclockunlocked); + } + + stmt.AddValue(1, handler.GetSession().GetBattlenetAccountId()); + DB.Login.Execute(stmt); + return true; + } + + handler.SendSysMessage(CypherStrings.UseBol); + return false; + } + } + + [CommandGroup("set", RBACPermissions.CommandBnetAccountSet, true)] + class SetCommands + { + [Command("password", RBACPermissions.CommandBnetAccountSetPassword, true)] + static bool HandleSetPasswordCommand(StringArguments args, CommandHandler handler) + { + if (args.Empty()) + { + handler.SendSysMessage(CypherStrings.CmdSyntax); + return false; + } + + // Get the command line arguments + string accountName = args.NextString(); + string password = args.NextString(); + string passwordConfirmation = args.NextString(); + + if (string.IsNullOrEmpty(accountName) || string.IsNullOrEmpty(password) || string.IsNullOrEmpty(passwordConfirmation)) + return false; + + uint targetAccountId = Global.BNetAccountMgr.GetId(accountName); + if (targetAccountId == 0) + { + handler.SendSysMessage(CypherStrings.AccountNotExist, accountName); + return false; + } + + if (password != passwordConfirmation) + { + handler.SendSysMessage(CypherStrings.NewPasswordsNotMatch); + return false; + } + + AccountOpResult result = Global.BNetAccountMgr.ChangePassword(targetAccountId, password); + switch (result) + { + case AccountOpResult.Ok: + handler.SendSysMessage(CypherStrings.CommandPassword); + break; + case AccountOpResult.NameNotExist: + handler.SendSysMessage(CypherStrings.AccountNotExist, accountName); + return false; + case AccountOpResult.PassTooLong: + handler.SendSysMessage(CypherStrings.PasswordTooLong); + return false; + default: + break; + } + return true; + } + } + + + } +} diff --git a/Game/Chat/Commands/BanCommands.cs b/Game/Chat/Commands/BanCommands.cs new file mode 100644 index 000000000..987cdcb1a --- /dev/null +++ b/Game/Chat/Commands/BanCommands.cs @@ -0,0 +1,664 @@ +/* + * Copyright (C) 2012-2017 CypherCore + * + * 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, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +using Framework.Constants; +using Framework.Database; +using Framework.IO; +using Game.Entities; +using System; +using System.Net; + +namespace Game.Chat.Commands +{ + [CommandGroup("ban", RBACPermissions.CommandBan, true)] + class BanCommands + { + [Command("account", RBACPermissions.CommandBanAccount, true)] + static bool HandleBanAccountCommand(StringArguments args, CommandHandler handler) + { + return HandleBanHelper(BanMode.Account, args, handler); + } + + [Command("character", RBACPermissions.CommandBanCharacter, true)] + static bool HandleBanCharacterCommand(StringArguments args, CommandHandler handler) + { + if (args.Empty()) + return false; + + string name = args.NextString(); + if (string.IsNullOrEmpty(name)) + return false; + + string durationStr = args.NextString(); + if (string.IsNullOrEmpty(durationStr)) + return false; + + uint duration = uint.Parse(durationStr); + + string reasonStr = args.NextString(""); + if (string.IsNullOrEmpty(reasonStr)) + return false; + + if (!ObjectManager.NormalizePlayerName(ref name)) + { + handler.SendSysMessage(CypherStrings.PlayerNotFound); + return false; + } + + string author = handler.GetSession() != null ? handler.GetSession().GetPlayerName() : "Server"; + + switch (Global.WorldMgr.BanCharacter(name, durationStr, reasonStr, author)) + { + case BanReturn.Success: + { + if (duration > 0) + { + if (WorldConfig.GetBoolValue(WorldCfg.ShowBanInWorld)) + Global.WorldMgr.SendWorldText(CypherStrings.BanCharacterYoubannedmessageWorld, author, name, Time.secsToTimeString(Time.TimeStringToSecs(durationStr), true), reasonStr); + else + handler.SendSysMessage(CypherStrings.BanYoubanned, name, Time.secsToTimeString(Time.TimeStringToSecs(durationStr), true), reasonStr); + } + else + { + if (WorldConfig.GetBoolValue(WorldCfg.ShowBanInWorld)) + Global.WorldMgr.SendWorldText(CypherStrings.BanCharacterYoupermbannedmessageWorld, author, name, reasonStr); + else + handler.SendSysMessage(CypherStrings.BanYoupermbanned, name, reasonStr); + } + break; + } + case BanReturn.Notfound: + { + handler.SendSysMessage(CypherStrings.BanNotfound, "character", name); + + return false; + } + default: + break; + } + + return true; + } + + [Command("playeraccount", RBACPermissions.CommandBanPlayeraccount, true)] + static bool HandleBanAccountByCharCommand(StringArguments args, CommandHandler handler) + { + return HandleBanHelper(BanMode.Character, args, handler); + } + + [Command("ip", RBACPermissions.CommandBanIp, true)] + static bool HandleBanIPCommand(StringArguments args, CommandHandler handler) + { + return HandleBanHelper(BanMode.IP, args, handler); + } + + static bool HandleBanHelper(BanMode mode, StringArguments args, CommandHandler handler) + { + if (args.Empty()) + return false; + + string nameOrIP = args.NextString(); + if (string.IsNullOrEmpty(nameOrIP)) + return false; + + uint duration; + string durationStr = args.NextString(); + if (string.IsNullOrEmpty(durationStr) || !uint.TryParse(durationStr, out duration)) + return false; + + string reasonStr = args.NextString(""); + if (string.IsNullOrEmpty(reasonStr)) + return false; + + switch (mode) + { + case BanMode.Character: + if (!ObjectManager.NormalizePlayerName(ref nameOrIP)) + { + handler.SendSysMessage(CypherStrings.PlayerNotFound); + return false; + } + break; + case BanMode.IP: + IPAddress address; + if (!IPAddress.TryParse(nameOrIP, out address)) + return false; + break; + } + + string author = handler.GetSession() ? handler.GetSession().GetPlayerName() : "Server"; + switch (Global.WorldMgr.BanAccount(mode, nameOrIP, durationStr, reasonStr, author)) + { + case BanReturn.Success: + if (uint.Parse(durationStr) > 0) + { + if (WorldConfig.GetBoolValue(WorldCfg.ShowBanInWorld)) + Global.WorldMgr.SendWorldText(CypherStrings.BanAccountYoubannedmessageWorld, author, nameOrIP, Time.secsToTimeString(Time.TimeStringToSecs(durationStr)).ToString(), reasonStr); + else + handler.SendSysMessage(CypherStrings.BanYoubanned, nameOrIP, Time.secsToTimeString(Time.TimeStringToSecs(durationStr), true), reasonStr); + } + else + { + if (WorldConfig.GetBoolValue(WorldCfg.ShowBanInWorld)) + Global.WorldMgr.SendWorldText(CypherStrings.BanAccountYoupermbannedmessageWorld, author, nameOrIP, reasonStr); + else + handler.SendSysMessage(CypherStrings.BanYoupermbanned, nameOrIP, reasonStr); + } + break; + case BanReturn.SyntaxError: + return false; + case BanReturn.Notfound: + switch (mode) + { + default: + handler.SendSysMessage(CypherStrings.BanNotfound, "account", nameOrIP); + break; + case BanMode.Character: + handler.SendSysMessage(CypherStrings.BanNotfound, "character", nameOrIP); + break; + case BanMode.IP: + handler.SendSysMessage(CypherStrings.BanNotfound, "ip", nameOrIP); + break; + } + + return false; + } + + return true; + } + } + + [CommandGroup("baninfo", RBACPermissions.CommandBaninfo, true)] + class BanInfoCommands + { + [Command("account", RBACPermissions.CommandBaninfoAccount, true)] + static bool HandleBanInfoAccountCommand(StringArguments args, CommandHandler handler) + { + if (args.Empty()) + return false; + + string accountName = args.NextString(""); + if (string.IsNullOrEmpty(accountName)) + return false; + + uint accountId = Global.AccountMgr.GetId(accountName); + if (accountId == 0) + { + handler.SendSysMessage(CypherStrings.AccountNotExist, accountName); + return true; + } + + return HandleBanInfoHelper(accountId, accountName, handler); + } + + [Command("character", RBACPermissions.CommandBaninfoCharacter, true)] + static bool HandleBanInfoCharacterCommand(StringArguments args, CommandHandler handler) + { + if (args.Empty()) + return false; + + string name = args.NextString(); + Player target = Global.ObjAccessor.FindPlayerByName(name); + ObjectGuid targetGuid; + + if (!target) + { + targetGuid = ObjectManager.GetPlayerGUIDByName(name); + if (targetGuid.IsEmpty()) + { + handler.SendSysMessage(CypherStrings.BaninfoNocharacter); + return false; + } + } + else + targetGuid = target.GetGUID(); + + PreparedStatement stmt = DB.Characters.GetPreparedStatement(CharStatements.SEL_BANINFO); + stmt.AddValue(0, targetGuid.GetCounter()); + SQLResult result = DB.Characters.Query(stmt); + if (result.IsEmpty()) + { + handler.SendSysMessage(CypherStrings.CharNotBanned, name); + return true; + } + + handler.SendSysMessage(CypherStrings.BaninfoBanhistory, name); + do + { + long unbanDate = result.Read(3); + bool active = false; + if (result.Read(2) && (result.Read(1) == 0 || unbanDate >= Time.UnixTime)) + active = true; + bool permanent = (result.Read(1) == 0); + string banTime = permanent ? handler.GetCypherString(CypherStrings.BaninfoInfinite) : Time.secsToTimeString(result.Read(1), true); + handler.SendSysMessage(CypherStrings.BaninfoHistoryentry, Time.UnixTimeToDateTime(result.Read(0)).ToShortTimeString(), banTime, + active ? handler.GetCypherString(CypherStrings.Yes) : handler.GetCypherString(CypherStrings.No), result.Read(4), result.Read(5)); + } + while (result.NextRow()); + + return true; + } + + [Command("ip", RBACPermissions.CommandBaninfoIp, true)] + static bool HandleBanInfoIPCommand(StringArguments args, CommandHandler handler) + { + if (args.Empty()) + return false; + + string ip = args.NextString(""); + if (string.IsNullOrEmpty(ip)) + return false; + + SQLResult result = DB.Login.Query("SELECT ip, FROM_UNIXTIME(bandate), FROM_UNIXTIME(unbandate), unbandate-UNIX_TIMESTAMP(), banreason, bannedby, unbandate-bandate FROM ip_banned WHERE ip = '{0}'", ip); + if (result.IsEmpty()) + { + handler.SendSysMessage(CypherStrings.BaninfoNoip); + return true; + } + + bool permanent = result.Read(6) == 0; + handler.SendSysMessage(CypherStrings.BaninfoIpentry, result.Read(0), result.Read(1), permanent ? handler.GetCypherString( CypherStrings.BaninfoNever) : result.Read(2), + permanent ? handler.GetCypherString( CypherStrings.BaninfoInfinite) : Time.secsToTimeString(result.Read(3), true), result.Read(4), result.Read(5)); + + return true; + } + + static bool HandleBanInfoHelper(uint accountId, string accountName, CommandHandler handler) + { + SQLResult result = DB.Login.Query("SELECT FROM_UNIXTIME(bandate), unbandate-bandate, active, unbandate, banreason, bannedby FROM account_banned WHERE id = '{0}' ORDER BY bandate ASC", accountId); + if (result.IsEmpty()) + { + handler.SendSysMessage(CypherStrings.BaninfoNoaccountban, accountName); + return true; + } + + handler.SendSysMessage(CypherStrings.BaninfoBanhistory, accountName); + do + { + long unbanDate = result.Read(3); + bool active = false; + if (result.Read(2) && (result.Read(1) == 0 || unbanDate >= Time.UnixTime)) + active = true; + bool permanent = (result.Read(1) == 0); + string banTime = permanent ? handler.GetCypherString(CypherStrings.BaninfoInfinite) : Time.secsToTimeString(result.Read(1), true); + handler.SendSysMessage(CypherStrings.BaninfoHistoryentry, + result.Read(0), banTime, active ? handler.GetCypherString(CypherStrings.Yes) : handler.GetCypherString(CypherStrings.No), result.Read(4), result.Read(5)); + } + while (result.NextRow()); + + return true; + } + + } + + [CommandGroup("banlist", RBACPermissions.CommandBanlist, true)] + class BanListCommands + { + [Command("account", RBACPermissions.CommandBanlistAccount, true)] + static bool HandleBanListAccountCommand(StringArguments args, CommandHandler handler) + { + PreparedStatement stmt = DB.Login.GetPreparedStatement(LoginStatements.DEL_EXPIRED_IP_BANS); + DB.Login.Execute(stmt); + + string filterStr = args.NextString(); + string filter = !string.IsNullOrEmpty(filterStr) ? filterStr : ""; + + SQLResult result; + if (string.IsNullOrEmpty(filter)) + { + stmt = DB.Login.GetPreparedStatement(LoginStatements.SEL_ACCOUNT_BANNED_ALL); + result = DB.Login.Query(stmt); + } + else + { + stmt = DB.Login.GetPreparedStatement(LoginStatements.SEL_ACCOUNT_BANNED_BY_USERNAME); + stmt.AddValue(0, filter); + result = DB.Login.Query(stmt); + } + + if (result.IsEmpty()) + { + handler.SendSysMessage(CypherStrings.BanlistNoaccount); + return true; + } + + return HandleBanListHelper(result, handler); + } + + [Command("character", RBACPermissions.CommandBanlistCharacter, true)] + static bool HandleBanListCharacterCommand(StringArguments args, CommandHandler handler) + { + if (args.Empty()) + return false; + + string filter = args.NextString(); + if (string.IsNullOrEmpty(filter)) + return false; + + PreparedStatement stmt = DB.Characters.GetPreparedStatement(CharStatements.SEL_GUID_BY_NAME_FILTER); + stmt.AddValue(0, filter); + SQLResult result = DB.Characters.Query(stmt); + if (result.IsEmpty()) + { + handler.SendSysMessage(CypherStrings.BanlistNocharacter); + return true; + } + + handler.SendSysMessage(CypherStrings.BanlistMatchingcharacter); + + // Chat short output + if (handler.GetSession()) + { + do + { + + PreparedStatement stmt2 = DB.Characters.GetPreparedStatement(CharStatements.SEL_BANNED_NAME); + stmt2.AddValue(0, result.Read(0)); + SQLResult banResult = DB.Characters.Query(stmt2); + if (!banResult.IsEmpty()) + handler.SendSysMessage(banResult.Read(0)); + } + while (result.NextRow()); + } + // Console wide output + else + { + handler.SendSysMessage(CypherStrings.BanlistCharacters); + handler.SendSysMessage(" =============================================================================== "); + handler.SendSysMessage(CypherStrings.BanlistCharactersHeader); + do + { + handler.SendSysMessage("-------------------------------------------------------------------------------"); + + string char_name = result.Read(1); + + PreparedStatement stmt2 = DB.Characters.GetPreparedStatement(CharStatements.SEL_BANINFO_LIST); + stmt2.AddValue(0, result.Read(0)); + SQLResult banInfo = DB.Characters.Query(stmt2); + if (!banInfo.IsEmpty()) + { + do + { + long timeBan = banInfo.Read(0); + DateTime tmBan = Time.UnixTimeToDateTime(timeBan); + string bannedby = banInfo.Read(2).Substring(0, 15); + string banreason = banInfo.Read(3).Substring(0, 15); + + if (banInfo.Read(0) == banInfo.Read(1)) + { + handler.SendSysMessage("|{0}|{1:D2}-{2:D2}-{3:D2} {4:D2}:{5:D2}| permanent |{6}|{7}|", + char_name, tmBan.Year % 100, tmBan.Month + 1, tmBan.Day, tmBan.Hour, tmBan.Minute, + bannedby, banreason); + } + else + { + long timeUnban = banInfo.Read(1); + DateTime tmUnban = Time.UnixTimeToDateTime(timeUnban); + handler.SendSysMessage("|{0}|{1:D2}-{2:D2}-{3:D2} {4:D2}:{5:D2}|{6:D2}-{7:D2}-{8:D2} {9:D2}:{10:D2}|{11}|{12}|", + char_name, tmBan.Year % 100, tmBan.Month + 1, tmBan.Day, tmBan.Hour, tmBan.Minute, + tmUnban.Year % 100, tmUnban.Month + 1, tmUnban.Day, tmUnban.Hour, tmUnban.Minute, + bannedby, banreason); + } + } + while (banInfo.NextRow()); + } + } + while (result.NextRow()); + handler.SendSysMessage(" =============================================================================== "); + } + + return true; + } + + [Command("ip", RBACPermissions.CommandBanlistIp, true)] + static bool HandleBanListIPCommand(StringArguments args, CommandHandler handler) + { + PreparedStatement stmt = DB.Login.GetPreparedStatement(LoginStatements.DEL_EXPIRED_IP_BANS); + DB.Login.Execute(stmt); + + string filterStr = args.NextString(); + string filter = !string.IsNullOrEmpty(filterStr) ? filterStr : ""; + + SQLResult result; + + if (string.IsNullOrEmpty(filter)) + { + stmt = DB.Login.GetPreparedStatement(LoginStatements.SEL_IP_BANNED_ALL); + result = DB.Login.Query(stmt); + } + else + { + stmt = DB.Login.GetPreparedStatement(LoginStatements.SEL_IP_BANNED_BY_IP); + stmt.AddValue(0, filter); + result = DB.Login.Query(stmt); + } + + if (result.IsEmpty()) + { + handler.SendSysMessage(CypherStrings.BanlistNoip); + return true; + } + + handler.SendSysMessage(CypherStrings.BanlistMatchingip); + // Chat short output + if (handler.GetSession()) + { + do + { + handler.SendSysMessage("{0}", result.Read(0)); + } + while (result.NextRow()); + } + // Console wide output + else + { + handler.SendSysMessage(CypherStrings.BanlistIps); + handler.SendSysMessage(" ==============================================================================="); + handler.SendSysMessage(CypherStrings.BanlistIpsHeader); + do + { + handler.SendSysMessage("-------------------------------------------------------------------------------"); + + long timeBan = result.Read(1); + DateTime tmBan = Time.UnixTimeToDateTime(timeBan); + string bannedby = result.Read(3).Substring(0, 15); + string banreason = result.Read(4).Substring(0, 15); + + if (result.Read(1) == result.Read(2)) + { + handler.SendSysMessage("|{0}|{1:D2}-{2:D2}-{3:D2} {4:D2}:{5:D2}| permanent |{6}|{7}|", + result.Read(0), tmBan.Year % 100, tmBan.Month + 1, tmBan.Day, tmBan.Hour, tmBan.Minute, + bannedby, banreason); + } + else + { + long timeUnban = result.Read(2); + DateTime tmUnban; + tmUnban = Time.UnixTimeToDateTime(timeUnban); + handler.SendSysMessage("|{0}|{1:D2}-{2:D2}-{3:D2} {4:D2}:{5:D2}|{6:D2}-{7:D2}-{8:D2} {9:D2}:{10:D2}|{11}|{12}|", + result.Read(0), tmBan.Year % 100, tmBan.Month + 1, tmBan.Day, tmBan.Hour, tmBan.Minute, + tmUnban.Year % 100, tmUnban.Month + 1, tmUnban.Day, tmUnban.Hour, tmUnban.Minute, + bannedby, banreason); + } + } + while (result.NextRow()); + + handler.SendSysMessage(" ==============================================================================="); + } + + return true; + } + + static bool HandleBanListHelper(SQLResult result, CommandHandler handler) + { + handler.SendSysMessage(CypherStrings.BanlistMatchingaccount); + + // Chat short output + if (handler.GetSession()) + { + do + { + + uint accountid = result.Read(0); + + SQLResult banResult = DB.Login.Query("SELECT account.username FROM account, account_banned WHERE account_banned.id='{0}' AND account_banned.id=account.id", accountid); + if (!banResult.IsEmpty()) + { + handler.SendSysMessage(banResult.Read(0)); + } + } + while (result.NextRow()); + } + // Console wide output + else + { + handler.SendSysMessage(CypherStrings.BanlistAccounts); + handler.SendSysMessage(" ==============================================================================="); + handler.SendSysMessage(CypherStrings.BanlistAccountsHeader); + do + { + handler.SendSysMessage("-------------------------------------------------------------------------------"); + + uint accountId = result.Read(0); + + string accountName; + + // "account" case, name can be get in same query + if (result.GetRowCount() > 1) + accountName = result.Read(1); + // "character" case, name need extract from another DB + else + Global.AccountMgr.GetName(accountId, out accountName); + + // No SQL injection. id is uint32. + SQLResult banInfo = DB.Login.Query("SELECT bandate, unbandate, bannedby, banreason FROM account_banned WHERE id = {0} ORDER BY unbandate", accountId); + if (!banInfo.IsEmpty()) + { + do + { + long timeBan = banInfo.Read(0); + DateTime tmBan; + tmBan = Time.UnixTimeToDateTime(timeBan); + string bannedby = banInfo.Read(2).Substring(0, 15); + string banreason = banInfo.Read(3).Substring(0, 15); + + if (banInfo.Read(0) == banInfo.Read(1)) + { + handler.SendSysMessage("|{0}|{1:D2}-{2:D2}-{3:D2} {4:D2}:{5:D2}| permanent |{6}|{7}|", + accountName.Substring(0, 15), tmBan.Year % 100, tmBan.Month + 1, tmBan.Day, tmBan.Hour, tmBan.Minute, + bannedby, banreason); + } + else + { + long timeUnban = banInfo.Read (1); + DateTime tmUnban; + tmUnban = Time.UnixTimeToDateTime(timeUnban); + handler.SendSysMessage("|{0}|{1:D2}-{2:D2}-{3:D2} {4:D2}:{5:D2}|{6:D2}-{7:D2}-{8:D2} {9:D2}:{10:D2}|{11}|{12}|", + accountName.Substring(0, 15), tmBan.Year % 100, tmBan.Month + 1, tmBan.Day, tmBan.Hour, tmBan.Minute, + tmUnban.Year % 100, tmUnban.Month + 1, tmUnban.Day, tmUnban.Hour, tmUnban.Minute, + bannedby, banreason); + } + } + while (banInfo.NextRow()); + } + } + while (result.NextRow()); + + handler.SendSysMessage(" ==============================================================================="); + } + + return true; + } + } + + [CommandGroup("unban", RBACPermissions.CommandUnban, true)] + class UnBanCommands + { + [Command("account", RBACPermissions.CommandUnbanAccount, true)] + static bool HandleUnBanAccountCommand(StringArguments args, CommandHandler handler) + { + return HandleUnBanHelper(BanMode.Account, args, handler); + } + + [Command("character", RBACPermissions.CommandUnbanCharacter, true)] + static bool HandleUnBanCharacterCommand(StringArguments args, CommandHandler handler) + { + if (args.Empty()) + return false; + + string name = args.NextString(); + if (!ObjectManager.NormalizePlayerName(ref name)) + { + handler.SendSysMessage(CypherStrings.PlayerNotFound); + return false; + } + + if (!Global.WorldMgr.RemoveBanCharacter(name)) + { + handler.SendSysMessage(CypherStrings.PlayerNotFound); + return false; + } + + return true; + } + + [Command("playeraccount", RBACPermissions.CommandUnbanPlayeraccount, true)] + static bool HandleUnBanAccountByCharCommand(StringArguments args, CommandHandler handler) + { + return HandleUnBanHelper(BanMode.Character, args, handler); + } + + [Command("ip", RBACPermissions.CommandUnbanIp, true)] + static bool HandleUnBanIPCommand(StringArguments args, CommandHandler handler) + { + return HandleUnBanHelper(BanMode.IP, args, handler); + } + + static bool HandleUnBanHelper(BanMode mode, StringArguments args, CommandHandler handler) + { + if (args.Empty()) + return false; + + string nameOrIP = args.NextString(); + if (string.IsNullOrEmpty(nameOrIP)) + return false; + + switch (mode) + { + case BanMode.Character: + if (!ObjectManager.NormalizePlayerName(ref nameOrIP)) + { + handler.SendSysMessage(CypherStrings.PlayerNotFound); + return false; + } + break; + case BanMode.IP: + IPAddress address; + if (!IPAddress.TryParse(nameOrIP, out address)) + return false; + break; + } + + if (Global.WorldMgr.RemoveBanAccount(mode, nameOrIP)) + handler.SendSysMessage(CypherStrings.UnbanUnbanned, nameOrIP); + else + handler.SendSysMessage(CypherStrings.UnbanError, nameOrIP); + + return true; + } + } +} diff --git a/Game/Chat/Commands/BattleFieldCommands.cs b/Game/Chat/Commands/BattleFieldCommands.cs new file mode 100644 index 000000000..798260e68 --- /dev/null +++ b/Game/Chat/Commands/BattleFieldCommands.cs @@ -0,0 +1,121 @@ +/* + * Copyright (C) 2012-2017 CypherCore + * + * 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, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +using Framework.Constants; +using Framework.IO; +using Game.BattleFields; + +namespace Game.Chat +{ + [CommandGroup("bf", RBACPermissions.CommandBf)] + class BattleFieldCommands + { + [Command("enable", RBACPermissions.CommandBfEnable)] + static bool HandleBattlefieldEnable(StringArguments args, CommandHandler handler) + { + uint battleid = args.NextUInt32(); + BattleField bf = Global.BattleFieldMgr.GetBattlefieldByBattleId(battleid); + + if (bf == null) + return false; + + if (bf.IsEnabled()) + { + bf.ToggleBattlefield(false); + if (battleid == 1) + handler.SendGlobalGMSysMessage("Wintergrasp is disabled"); + } + else + { + bf.ToggleBattlefield(true); + if (battleid == 1) + handler.SendGlobalGMSysMessage("Wintergrasp is enabled"); + } + + return true; + } + + [Command("start", RBACPermissions.CommandBfStart)] + static bool HandleBattlefieldStart(StringArguments args, CommandHandler handler) + { + uint battleid = args.NextUInt32(); + BattleField bf = Global.BattleFieldMgr.GetBattlefieldByBattleId(battleid); + + if (bf == null) + return false; + + bf.StartBattle(); + + if (battleid == 1) + handler.SendGlobalGMSysMessage("Wintergrasp (Command start used)"); + + return true; + } + + [Command("stop", RBACPermissions.CommandBfStop)] + static bool HandleBattlefieldEnd(StringArguments args, CommandHandler handler) + { + uint battleid = args.NextUInt32(); + BattleField bf = Global.BattleFieldMgr.GetBattlefieldByBattleId(battleid); + + if (bf == null) + return false; + + bf.EndBattle(true); + + if (battleid == 1) + handler.SendGlobalGMSysMessage("Wintergrasp (Command stop used)"); + + return true; + } + + [Command("switch", RBACPermissions.CommandBfSwitch)] + static bool HandleBattlefieldSwitch(StringArguments args, CommandHandler handler) + { + uint battleid = args.NextUInt32(); + BattleField bf = Global.BattleFieldMgr.GetBattlefieldByBattleId(battleid); + + if (bf == null) + return false; + + bf.EndBattle(false); + if (battleid == 1) + handler.SendGlobalGMSysMessage("Wintergrasp (Command switch used)"); + + return true; + } + + [Command("timer", RBACPermissions.CommandBfTimer)] + static bool HandleBattlefieldTimer(StringArguments args, CommandHandler handler) + { + uint battleid = args.NextUInt32(); + BattleField bf = Global.BattleFieldMgr.GetBattlefieldByBattleId(battleid); + + if (bf == null) + return false; + + uint time = args.NextUInt32(); + + bf.SetTimer(time * Time.InMilliseconds); + bf.SendInitWorldStatesToAll(); + if (battleid == 1) + handler.SendGlobalGMSysMessage("Wintergrasp (Command timer used)"); + + return true; + } + } +} diff --git a/Game/Chat/Commands/CastCommands.cs b/Game/Chat/Commands/CastCommands.cs new file mode 100644 index 000000000..f9f975428 --- /dev/null +++ b/Game/Chat/Commands/CastCommands.cs @@ -0,0 +1,248 @@ +/* + * Copyright (C) 2012-2017 CypherCore + * + * 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, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +using Framework.Constants; +using Framework.IO; +using Game.Entities; +using Game.Spells; + +namespace Game.Chat +{ + [CommandGroup("cast", RBACPermissions.CommandCast)] + class CastCommands + { + [Command("", RBACPermissions.CommandCast)] + static bool HandleCastCommand(StringArguments args, CommandHandler handler) + { + if (args.Empty()) + return false; + + Unit target = handler.getSelectedUnit(); + if (!target) + { + handler.SendSysMessage(CypherStrings.SelectCharOrCreature); + return false; + } + + // number or [name] Shift-click form |color|Hspell:spell_id|h[name]|h|r or Htalent form + uint spellId = handler.extractSpellIdFromLink(args); + if (spellId == 0) + return false; + + if (!CheckSpellExistsAndIsValid(handler, spellId)) + return false; + + string triggeredStr = args.NextString(); + if (!string.IsNullOrEmpty(triggeredStr)) + { + if (triggeredStr != "triggered") + return false; + } + + bool triggered = (triggeredStr != null); + + handler.GetSession().GetPlayer().CastSpell(target, spellId, triggered); + + return true; + } + + [Command("back", RBACPermissions.CommandCastBack)] + static bool HandleCastBackCommand(StringArguments args, CommandHandler handler) + { + Creature caster = handler.getSelectedCreature(); + if (!caster) + { + handler.SendSysMessage(CypherStrings.SelectCharOrCreature); + return false; + } + + // number or [name] Shift-click form |color|Hspell:spell_id|h[name]|h|r or Htalent form + uint spellId = handler.extractSpellIdFromLink(args); + if (spellId == 0) + return false; + + if (CheckSpellExistsAndIsValid(handler, spellId)) + return false; + + string triggeredStr = args.NextString(); + if (!string.IsNullOrEmpty(triggeredStr)) + { + if (triggeredStr != "triggered") + return false; + } + + bool triggered = (triggeredStr != null); + + caster.CastSpell(handler.GetSession().GetPlayer(), spellId, triggered); + + return true; + } + + [Command("dist", RBACPermissions.CommandCastDist)] + static bool HandleCastDistCommand(StringArguments args, CommandHandler handler) + { + if (args.Empty()) + return false; + + // number or [name] Shift-click form |color|Hspell:spell_id|h[name]|h|r or Htalent form + uint spellId = handler.extractSpellIdFromLink(args); + if (spellId == 0) + return false; + + if (CheckSpellExistsAndIsValid(handler, spellId)) + return false; + + float dist = args.NextSingle(); + + string triggeredStr = args.NextString(); + if (!string.IsNullOrEmpty(triggeredStr)) + { + if (triggeredStr != "triggered") + return false; + } + + bool triggered = (triggeredStr != null); + + float x, y, z; + handler.GetSession().GetPlayer().GetClosePoint(out x, out y, out z, dist); + + handler.GetSession().GetPlayer().CastSpell(x, y, z, spellId, triggered); + + return true; + } + + [Command("self", RBACPermissions.CommandCastSelf)] + static bool HandleCastSelfCommand(StringArguments args, CommandHandler handler) + { + if (args.Empty()) + return false; + + Unit target = handler.getSelectedUnit(); + if (!target) + { + handler.SendSysMessage(CypherStrings.SelectCharOrCreature); + return false; + } + + // number or [name] Shift-click form |color|Hspell:spell_id|h[name]|h|r or Htalent form + uint spellId = handler.extractSpellIdFromLink(args); + if (spellId == 0) + return false; + + if (CheckSpellExistsAndIsValid(handler, spellId)) + return false; + + target.CastSpell(target, spellId, false); + + return true; + } + + [Command("target", RBACPermissions.CommandCastTarget)] + static bool HandleCastTargetCommad(StringArguments args, CommandHandler handler) + { + Creature caster = handler.getSelectedCreature(); + if (!caster) + { + handler.SendSysMessage(CypherStrings.SelectCharOrCreature); + return false; + } + + if (!caster.GetVictim()) + { + handler.SendSysMessage(CypherStrings.SelectedTargetNotHaveVictim); + return false; + } + + // number or [name] Shift-click form |color|Hspell:spell_id|h[name]|h|r or Htalent form + uint spellId = handler.extractSpellIdFromLink(args); + if (spellId == 0) + return false; + + if (CheckSpellExistsAndIsValid(handler, spellId)) + return false; + + string triggeredStr = args.NextString(); + if (!string.IsNullOrEmpty(triggeredStr)) + { + if (triggeredStr != "triggered") + return false; + } + + bool triggered = (triggeredStr != null); + + caster.CastSpell(caster.GetVictim(), spellId, triggered); + + return true; + } + + [Command("dest", RBACPermissions.CommandCastDest)] + static bool HandleCastDestCommand(StringArguments args, CommandHandler handler) + { + Unit caster = handler.getSelectedUnit(); + if (!caster) + { + handler.SendSysMessage(CypherStrings.SelectCharOrCreature); + return false; + } + + // number or [name] Shift-click form |color|Hspell:spell_id|h[name]|h|r or Htalent form + uint spellId = handler.extractSpellIdFromLink(args); + if (spellId == 0) + return false; + + if (CheckSpellExistsAndIsValid(handler, spellId)) + return false; + + float x = args.NextSingle(); + float y = args.NextSingle(); + float z = args.NextSingle(); + + if (x == 0f || y == 0f || z == 0f) + return false; + + string triggeredStr = args.NextString(); + if (!string.IsNullOrEmpty(triggeredStr)) + { + if (triggeredStr != "triggered") + return false; + } + + bool triggered = (triggeredStr != null); + + caster.CastSpell(x, y, z, spellId, triggered); + + return true; + } + + static bool CheckSpellExistsAndIsValid(CommandHandler handler, uint spellId) + { + SpellInfo spellInfo = Global.SpellMgr.GetSpellInfo(spellId); + if (spellInfo == null) + { + handler.SendSysMessage(CypherStrings.CommandNospellfound); + return false; + } + + if (!Global.SpellMgr.IsSpellValid(spellInfo, handler.GetPlayer())) + { + handler.SendSysMessage(CypherStrings.CommandSpellBroken, spellId); + return false; + } + return true; + } + } +} diff --git a/Game/Chat/Commands/CharacterCommands.cs b/Game/Chat/Commands/CharacterCommands.cs new file mode 100644 index 000000000..28efe8a23 --- /dev/null +++ b/Game/Chat/Commands/CharacterCommands.cs @@ -0,0 +1,895 @@ +/* + * Copyright (C) 2012-2017 CypherCore + * + * 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, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +using Framework.Constants; +using Framework.Database; +using Framework.IO; +using Game.DataStorage; +using Game.Entities; +using System; +using System.Collections.Generic; +using System.Text; + +namespace Game.Chat +{ + [CommandGroup("character", RBACPermissions.CommandCharacter, true)] + class CharacterCommands + { + [Command("titles", RBACPermissions.CommandCharacterTitles, true)] + static bool HandleCharacterTitlesCommand(StringArguments args, CommandHandler handler) + { + if (args.Empty()) + return false; + + Player target; + if (!handler.extractPlayerTarget(args, out target)) + return false; + + LocaleConstant loc = handler.GetSessionDbcLocale(); + string targetName = target.GetName(); + string knownStr = handler.GetCypherString(CypherStrings.Known); + + // Search in CharTitles.dbc + foreach (var titleInfo in CliDB.CharTitlesStorage.Values) + { + if (target.HasTitle(titleInfo)) + { + string name = (target.GetGender() == Gender.Male ? titleInfo.NameMale : titleInfo.NameFemale)[handler.GetSessionDbcLocale()]; + if (string.IsNullOrEmpty(name)) + continue; + + string activeStr = target.GetUInt32Value(PlayerFields.ChosenTitle) == titleInfo.MaskID + ? handler.GetCypherString(CypherStrings.Active) : ""; + + // send title in "id (idx:idx) - [namedlink locale]" format + if (handler.GetSession() != null) + handler.SendSysMessage(CypherStrings.TitleListChat, titleInfo.Id, titleInfo.MaskID, titleInfo.Id, name, loc, knownStr, activeStr); + else + handler.SendSysMessage(CypherStrings.TitleListConsole, titleInfo.Id, titleInfo.MaskID, name, loc, knownStr, activeStr); + } + } + + return true; + } + + //rename characters + [Command("rename", RBACPermissions.CommandCharacterRename, true)] + static bool HandleCharacterRenameCommand(StringArguments args, CommandHandler handler) + { + Player target; + ObjectGuid targetGuid; + string targetName; + if (!handler.extractPlayerTarget(args, out target, out targetGuid, out targetName)) + return false; + + string newNameStr = args.NextString(); + + if (!string.IsNullOrEmpty(newNameStr)) + { + string playerOldName; + string newName = newNameStr; + + if (target) + { + // check online security + if (handler.HasLowerSecurity(target, ObjectGuid.Empty)) + return false; + + playerOldName = target.GetName(); + } + else + { + // check offline security + if (handler.HasLowerSecurity(null, targetGuid)) + return false; + + ObjectManager.GetPlayerNameByGUID(targetGuid, out playerOldName); + } + + if (!ObjectManager.NormalizePlayerName(ref newName)) + { + handler.SendSysMessage(CypherStrings.BadValue); + return false; + } + + if (ObjectManager.CheckPlayerName(newName, target ? target.GetSession().GetSessionDbcLocale() : Global.WorldMgr.GetDefaultDbcLocale(), true) != ResponseCodes.CharNameSuccess) + { + handler.SendSysMessage(CypherStrings.BadValue); + return false; + } + + WorldSession session = handler.GetSession(); + if (session != null) + { + if (!session.HasPermission(RBACPermissions.SkipCheckCharacterCreationReservedname) && Global.ObjectMgr.IsReservedName(newName)) + { + handler.SendSysMessage(CypherStrings.ReservedName); + return false; + } + } + + PreparedStatement stmt = DB.Characters.GetPreparedStatement(CharStatements.SEL_CHECK_NAME); + stmt.AddValue(0, newName); + SQLResult result = DB.Characters.Query(stmt); + if (!result.IsEmpty()) + { + handler.SendSysMessage(CypherStrings.RenamePlayerAlreadyExists, newName); + return false; + } + + // Remove declined name from db + stmt = DB.Characters.GetPreparedStatement(CharStatements.DEL_CHAR_DECLINED_NAME); + stmt.AddValue(0, targetGuid.GetCounter()); + DB.Characters.Execute(stmt); + + if (target) + { + target.SetName(newName); + session = target.GetSession(); + if (session != null) + session.KickPlayer(); + } + else + { + stmt = DB.Characters.GetPreparedStatement(CharStatements.UPD_NAME_BY_GUID); + stmt.AddValue(0, newName); + stmt.AddValue(1, targetGuid.GetCounter()); + DB.Characters.Execute(stmt); + } + + Global.WorldMgr.UpdateCharacterInfo(targetGuid, newName); + + handler.SendSysMessage(CypherStrings.RenamePlayerWithNewName, playerOldName, newName); + + Player player = handler.GetPlayer(); + if (player) + Log.outCommand(session.GetAccountId(), "GM {0} (Account: {1}) forced rename {2} to player {3} (Account: {4})", player.GetName(), session.GetAccountId(), newName, playerOldName, ObjectManager.GetPlayerAccountIdByGUID(targetGuid)); + else + Log.outCommand(0, "CONSOLE forced rename '{0}' to '{1}' ({2})", playerOldName, newName, targetGuid.ToString()); + } + else + { + if (target) + { + // check online security + if (handler.HasLowerSecurity(target, ObjectGuid.Empty)) + return false; + + handler.SendSysMessage(CypherStrings.RenamePlayer, handler.GetNameLink(target)); + target.SetAtLoginFlag(AtLoginFlags.Rename); + } + else + { + // check offline security + if (handler.HasLowerSecurity(null, targetGuid)) + return false; + + string oldNameLink = handler.playerLink(targetName); + handler.SendSysMessage(CypherStrings.RenamePlayerGuid, oldNameLink, targetGuid.ToString()); + + PreparedStatement stmt = DB.Characters.GetPreparedStatement(CharStatements.UPD_ADD_AT_LOGIN_FLAG); + stmt.AddValue(0, AtLoginFlags.Rename); + stmt.AddValue(1, targetGuid.GetCounter()); + DB.Characters.Execute(stmt); + } + } + + return true; + } + + [Command("level", RBACPermissions.CommandCharacterLevel, true)] + static bool HandleCharacterLevelCommand(StringArguments args, CommandHandler handler) + { + string nameStr; + string levelStr; + handler.extractOptFirstArg(args, out nameStr, out levelStr); + if (string.IsNullOrEmpty(levelStr)) + return false; + + // exception opt second arg: .character level $name + if (!levelStr.IsNumber()) + { + nameStr = levelStr; + levelStr = null; // current level will used + } + + Player target; + ObjectGuid targetGuid; + string targetName; + if (!handler.extractPlayerTarget(new StringArguments(nameStr), out target, out targetGuid, out targetName)) + return false; + + int oldlevel = (int)(target ? target.getLevel() : Player.GetLevelFromDB(targetGuid)); + int newlevel = !string.IsNullOrEmpty(levelStr) ? int.Parse(levelStr) : oldlevel; + + if (newlevel < 1) + return false; // invalid level + + if (newlevel > SharedConst.StrongMaxLevel) // hardcoded maximum level + newlevel = SharedConst.StrongMaxLevel; + + HandleCharacterLevel(target, targetGuid, oldlevel, newlevel, handler); + if (handler.GetSession() == null || handler.GetSession().GetPlayer() != target) // including player == NULL + { + string nameLink = handler.playerLink(targetName); + handler.SendSysMessage(CypherStrings.YouChangeLvl, nameLink, newlevel); + } + + return true; + } + + // customize characters + [Command("customize", RBACPermissions.CommandCharacterCustomize, true)] + static bool HandleCharacterCustomizeCommand(StringArguments args, CommandHandler handler) + { + Player target; + ObjectGuid targetGuid; + string targetName; + if (!handler.extractPlayerTarget(args, out target, out targetGuid, out targetName)) + return false; + + PreparedStatement stmt = DB.Characters.GetPreparedStatement(CharStatements.UPD_ADD_AT_LOGIN_FLAG); + stmt.AddValue(0, AtLoginFlags.Customize); + if (target) + { + handler.SendSysMessage(CypherStrings.CustomizePlayer, handler.GetNameLink(target)); + target.SetAtLoginFlag(AtLoginFlags.Customize); + stmt.AddValue(1, target.GetGUID().GetCounter()); + } + else + { + string oldNameLink = handler.playerLink(targetName); + stmt.AddValue(1, targetGuid.GetCounter()); + handler.SendSysMessage(CypherStrings.CustomizePlayerGuid, oldNameLink, targetGuid.ToString()); + } + DB.Characters.Execute(stmt); + + return true; + } + + [Command("changefaction", RBACPermissions.CommandCharacterChangefaction, true)] + static bool HandleCharacterChangeFactionCommand(StringArguments args, CommandHandler handler) + { + Player target; + ObjectGuid targetGuid; + string targetName; + if (!handler.extractPlayerTarget(args, out target, out targetGuid, out targetName)) + return false; + + PreparedStatement stmt = DB.Characters.GetPreparedStatement(CharStatements.UPD_ADD_AT_LOGIN_FLAG); + stmt.AddValue(0, AtLoginFlags.ChangeFaction); + if (target) + { + handler.SendSysMessage(CypherStrings.CustomizePlayer, handler.GetNameLink(target)); + target.SetAtLoginFlag(AtLoginFlags.ChangeFaction); + stmt.AddValue(1, target.GetGUID().GetCounter()); + } + else + { + string oldNameLink = handler.playerLink(targetName); + handler.SendSysMessage(CypherStrings.CustomizePlayerGuid, oldNameLink, targetGuid.ToString()); + stmt.AddValue(1, targetGuid.GetCounter()); + } + DB.Characters.Execute(stmt); + + return true; + } + + [Command("changerace", RBACPermissions.CommandCharacterChangerace, true)] + static bool HandleCharacterChangeRaceCommand(StringArguments args, CommandHandler handler) + { + Player target; + ObjectGuid targetGuid; + string targetName; + if (!handler.extractPlayerTarget(args, out target, out targetGuid, out targetName)) + return false; + + PreparedStatement stmt = DB.Characters.GetPreparedStatement(CharStatements.UPD_ADD_AT_LOGIN_FLAG); + stmt.AddValue(0, AtLoginFlags.ChangeRace); + if (target) + { + /// @todo add text into database + handler.SendSysMessage(CypherStrings.CustomizePlayer, handler.GetNameLink(target)); + target.SetAtLoginFlag(AtLoginFlags.ChangeRace); + stmt.AddValue(1, target.GetGUID().GetCounter()); + } + else + { + string oldNameLink = handler.playerLink(targetName); + /// @todo add text into database + handler.SendSysMessage(CypherStrings.CustomizePlayerGuid, oldNameLink, targetGuid.ToString()); + stmt.AddValue(1, targetGuid.GetCounter()); + } + DB.Characters.Execute(stmt); + + return true; + } + + [Command("reputation", RBACPermissions.CommandCharacterReputation, true)] + static bool HandleCharacterReputationCommand(StringArguments args, CommandHandler handler) + { + Player target; + if (!handler.extractPlayerTarget(args, out target)) + return false; + + LocaleConstant loc = handler.GetSessionDbcLocale(); + + var targetFSL = target.GetReputationMgr().GetStateList(); + foreach (var pair in targetFSL) + { + FactionState faction = pair.Value; + FactionRecord factionEntry = CliDB.FactionStorage.LookupByKey(faction.ID); + string factionName = factionEntry != null ? factionEntry.Name[loc] : "#Not found#"; + ReputationRank rank = target.GetReputationMgr().GetRank(factionEntry); + string rankName = handler.GetCypherString(ReputationMgr.ReputationRankStrIndex[(int)rank]); + StringBuilder ss = new StringBuilder(); + if (handler.GetSession() != null) + ss.AppendFormat("{0} - |cffffffff|Hfaction:{0}|h[{1} {2}]|h|r", faction.ID, factionName, loc); + else + ss.AppendFormat("{0} - {1} {2}", faction.ID, factionName, loc); + + ss.AppendFormat(" {0} ({1})", rankName, target.GetReputationMgr().GetReputation(factionEntry)); + + if (faction.Flags.HasAnyFlag(FactionFlags.Visible)) + ss.Append(handler.GetCypherString(CypherStrings.FactionVisible)); + if (faction.Flags.HasAnyFlag(FactionFlags.AtWar)) + ss.Append(handler.GetCypherString(CypherStrings.FactionAtwar)); + if (faction.Flags.HasAnyFlag(FactionFlags.PeaceForced)) + ss.Append(handler.GetCypherString(CypherStrings.FactionPeaceForced)); + if (faction.Flags.HasAnyFlag(FactionFlags.Hidden)) + ss.Append(handler.GetCypherString(CypherStrings.FactionHidden)); + if (faction.Flags.HasAnyFlag(FactionFlags.InvisibleForced)) + ss.Append(handler.GetCypherString(CypherStrings.FactionInvisibleForced)); + if (faction.Flags.HasAnyFlag(FactionFlags.Inactive)) + ss.Append(handler.GetCypherString(CypherStrings.FactionInactive)); + + handler.SendSysMessage(ss.ToString()); + } + + return true; + } + + [Command("erase", RBACPermissions.CommandCharacterErase, true)] + static bool HandleCharacterEraseCommand(StringArguments args, CommandHandler handler) + { + if (args.Empty()) + return false; + + string characterName = args.NextString(); + if (string.IsNullOrEmpty(characterName)) + return false; + + if (!ObjectManager.NormalizePlayerName(ref characterName)) + return false; + + ObjectGuid characterGuid; + uint accountId; + + Player player = Global.ObjAccessor.FindPlayerByName(characterName); + if (player) + { + characterGuid = player.GetGUID(); + accountId = player.GetSession().GetAccountId(); + player.GetSession().KickPlayer(); + } + else + { + characterGuid = ObjectManager.GetPlayerGUIDByName(characterName); + if (characterGuid.IsEmpty()) + { + handler.SendSysMessage(CypherStrings.NoPlayer, characterName); + return false; + } + accountId = ObjectManager.GetPlayerAccountIdByGUID(characterGuid); + } + + string accountName; + Global.AccountMgr.GetName(accountId, out accountName); + + Player.DeleteFromDB(characterGuid, accountId, true, true); + handler.SendSysMessage(CypherStrings.CharacterDeleted, characterName, characterGuid.ToString(), accountName, accountId); + + return true; + } + + [CommandGroup("deleted", RBACPermissions.CommandCharacterDeleted, true)] + class DeletedCommands + { + [Command("delete", RBACPermissions.CommandCharacterDeletedDelete, true)] + static bool HandleCharacterDeletedDeleteCommand(StringArguments args, CommandHandler handler) + { + // It is required to submit at least one argument + if (args.Empty()) + return false; + + List foundList = new List(); + if (!GetDeletedCharacterInfoList(foundList, args.NextString())) + return false; + + if (foundList.Empty()) + { + handler.SendSysMessage(CypherStrings.CharacterDeletedListEmpty); + return false; + } + + handler.SendSysMessage(CypherStrings.CharacterDeletedDelete); + HandleCharacterDeletedListHelper(foundList, handler); + + // Call the appropriate function to delete them (current account for deleted characters is 0) + foreach (var info in foundList) + Player.DeleteFromDB(info.guid, 0, false, true); + + return true; + } + + [Command("list", RBACPermissions.CommandCharacterDeletedList, true)] + static bool HandleCharacterDeletedListCommand(StringArguments args, CommandHandler handler) + { + List foundList = new List(); + if (!GetDeletedCharacterInfoList(foundList, args.NextString())) + return false; + + // if no characters have been found, output a warning + if (foundList.Empty()) + { + handler.SendSysMessage(CypherStrings.CharacterDeletedListEmpty); + return false; + } + + HandleCharacterDeletedListHelper(foundList, handler); + + return true; + } + + [Command("restore", RBACPermissions.CommandCharacterDeletedRestore, true)] + static bool HandleCharacterDeletedRestoreCommand(StringArguments args, CommandHandler handler) + { + // It is required to submit at least one argument + if (args.Empty()) + return false; + + string searchString = args.NextString(); + string newCharName = args.NextString(); + uint newAccount = args.NextUInt32(); + + List foundList = new List(); + if (!GetDeletedCharacterInfoList(foundList, searchString)) + return false; + + if (foundList.Empty()) + { + handler.SendSysMessage(CypherStrings.CharacterDeletedListEmpty); + return false; + } + + handler.SendSysMessage(CypherStrings.CharacterDeletedRestore); + HandleCharacterDeletedListHelper(foundList, handler); + + if (newCharName.IsEmpty()) + { + // Drop not existed account cases + foreach (var info in foundList) + HandleCharacterDeletedRestoreHelper(info, handler); + } + else if (foundList.Count == 1 && ObjectManager.NormalizePlayerName(ref newCharName)) + { + DeletedInfo delInfo = foundList[0]; + + // update name + delInfo.name = newCharName; + + // if new account provided update deleted info + if (newAccount != 0 && newAccount != delInfo.accountId) + { + delInfo.accountId = newAccount; + Global.AccountMgr.GetName(newAccount, out delInfo.accountName); + } + + HandleCharacterDeletedRestoreHelper(delInfo, handler); + } + else + handler.SendSysMessage(CypherStrings.CharacterDeletedErrRename); + + return true; + } + + [Command("old", RBACPermissions.CommandCharacterDeletedOld, true)] + static bool HandleCharacterDeletedOldCommand(StringArguments args, CommandHandler handler) + { + int keepDays = WorldConfig.GetIntValue(WorldCfg.ChardeleteKeepDays); + + string daysStr = args.NextString(); + if (!daysStr.IsEmpty()) + { + if (!daysStr.IsNumber()) + return false; + + keepDays = int.Parse(daysStr); + if (keepDays < 0) + return false; + } + // config option value 0 -> disabled and can't be used + else if (keepDays <= 0) + return false; + + Player.DeleteOldCharacters(keepDays); + + return true; + } + + static bool GetDeletedCharacterInfoList(List foundList, string searchString) + { + SQLResult result; + PreparedStatement stmt; + if (!searchString.IsEmpty()) + { + // search by GUID + if (searchString.IsNumber()) + { + stmt = DB.Characters.GetPreparedStatement(CharStatements.SEL_CHAR_DEL_INFO_BY_GUID); + stmt.AddValue(0, ulong.Parse(searchString)); + result = DB.Characters.Query(stmt); + } + // search by name + else + { + if (!ObjectManager.NormalizePlayerName(ref searchString)) + return false; + + stmt = DB.Characters.GetPreparedStatement(CharStatements.SEL_CHAR_DEL_INFO_BY_NAME); + stmt.AddValue(0, searchString); + result = DB.Characters.Query(stmt); + } + } + else + { + stmt = DB.Characters.GetPreparedStatement(CharStatements.SEL_CHAR_DEL_INFO); + result = DB.Characters.Query(stmt); + } + + if (!result.IsEmpty()) + { + do + { + DeletedInfo info; + + info.guid = ObjectGuid.Create(HighGuid.Player, result.Read(0)); + info.name = result.Read(1); + info.accountId = result.Read(2); + + // account name will be empty for not existed account + Global.AccountMgr.GetName(info.accountId, out info.accountName); + info.deleteDate = result.Read(3); + foundList.Add(info); + } + while (result.NextRow()); + } + + return true; + } + static void HandleCharacterDeletedListHelper(List foundList, CommandHandler handler) + { + if (handler.GetSession() == null) + { + handler.SendSysMessage(CypherStrings.CharacterDeletedListBar); + handler.SendSysMessage(CypherStrings.CharacterDeletedListHeader); + handler.SendSysMessage(CypherStrings.CharacterDeletedListBar); + } + + foreach (var info in foundList) + { + string dateStr = Time.UnixTimeToDateTime(info.deleteDate).ToShortDateString(); + + if (!handler.GetSession()) + handler.SendSysMessage(CypherStrings.CharacterDeletedListLineConsole, + info.guid.ToString(), info.name, info.accountName.IsEmpty() ? "" : info.accountName, + info.accountId, dateStr); + else + handler.SendSysMessage(CypherStrings.CharacterDeletedListLineChat, + info.guid.ToString(), info.name, info.accountName.IsEmpty() ? "" : info.accountName, + info.accountId, dateStr); + } + + if (!handler.GetSession()) + handler.SendSysMessage(CypherStrings.CharacterDeletedListBar); + } + static void HandleCharacterDeletedRestoreHelper(DeletedInfo delInfo, CommandHandler handler) + { + if (delInfo.accountName.IsEmpty()) // account not exist + { + handler.SendSysMessage(CypherStrings.CharacterDeletedSkipAccount, delInfo.name, delInfo.guid.ToString(), delInfo.accountId); + return; + } + + // check character count + uint charcount = Global.AccountMgr.GetCharactersCount(delInfo.accountId); + if (charcount >= WorldConfig.GetIntValue(WorldCfg.CharactersPerRealm)) + { + handler.SendSysMessage(CypherStrings.CharacterDeletedSkipFull, delInfo.name, delInfo.guid.ToString(), delInfo.accountId); + return; + } + + if (!ObjectManager.GetPlayerGUIDByName(delInfo.name).IsEmpty()) + { + handler.SendSysMessage(CypherStrings.CharacterDeletedSkipName, delInfo.name, delInfo.guid.ToString(), delInfo.accountId); + return; + } + + PreparedStatement stmt = DB.Characters.GetPreparedStatement(CharStatements.UPD_RESTORE_DELETE_INFO); + stmt.AddValue(0, delInfo.name); + stmt.AddValue(1, delInfo.accountId); + stmt.AddValue(2, delInfo.guid.GetCounter()); + DB.Characters.Execute(stmt); + + Global.WorldMgr.UpdateCharacterInfoDeleted(delInfo.guid, false, delInfo.name); + } + + struct DeletedInfo + { + public ObjectGuid guid; // the GUID from the character + public string name; // the character name + public uint accountId; // the account id + public string accountName; // the account name + public long deleteDate; // the date at which the character has been deleted + } + } + + [CommandNonGroup("levelup", RBACPermissions.CommandLevelup)] + static bool LevelUp(StringArguments args, CommandHandler handler) + { + string nameStr; + string levelStr; + handler.extractOptFirstArg(args, out nameStr, out levelStr); + + // exception opt second arg: .character level $name + if (!string.IsNullOrEmpty(levelStr) && !levelStr.IsNumber()) + { + nameStr = levelStr; + levelStr = null; // current level will used + } + + Player target; + ObjectGuid targetGuid; + string targetName; + if (!handler.extractPlayerTarget(new StringArguments(nameStr), out target, out targetGuid, out targetName)) + return false; + + int oldlevel = (int)(target ? target.getLevel() : Player.GetLevelFromDB(targetGuid)); + int addlevel = !string.IsNullOrEmpty(levelStr) ? int.Parse(levelStr) : 1; + int newlevel = oldlevel + addlevel; + + if (newlevel < 1) + newlevel = 1; + + if (newlevel > SharedConst.StrongMaxLevel) // hardcoded maximum level + newlevel = SharedConst.StrongMaxLevel; + + HandleCharacterLevel(target, targetGuid, oldlevel, newlevel, handler); + + if (handler.GetSession() == null || handler.GetSession().GetPlayer() != target) // including chr == NULL + { + string nameLink = handler.playerLink(targetName); + handler.SendSysMessage(CypherStrings.YouChangeLvl, nameLink, newlevel); + } + + return true; + } + + public static void HandleCharacterLevel(Player player, ObjectGuid playerGuid, int oldLevel, int newLevel, CommandHandler handler) + { + if (player) + { + player.GiveLevel((uint)newLevel); + player.InitTalentForLevel(); + player.SetUInt32Value(PlayerFields.Xp, 0); + + if (handler.needReportToTarget(player)) + { + if (oldLevel == newLevel) + player.SendSysMessage(CypherStrings.YoursLevelProgressReset, handler.GetNameLink()); + else if (oldLevel < newLevel) + player.SendSysMessage(CypherStrings.YoursLevelUp, handler.GetNameLink(), newLevel); + else // if (oldlevel > newlevel) + player.SendSysMessage(CypherStrings.YoursLevelDown, handler.GetNameLink(), newLevel); + } + } + else + { + // Update level and reset XP, everything else will be updated at login + PreparedStatement stmt = DB.Characters.GetPreparedStatement(CharStatements.UPD_LEVEL); + stmt.AddValue(0, newLevel); + stmt.AddValue(1, playerGuid.GetCounter()); + DB.Characters.Execute(stmt); + } + } + } + + [CommandGroup("pdump", RBACPermissions.CommandPdump, true)] + class PdumpCommand + { + [Command("load", RBACPermissions.CommandPdumpLoad, true)] + static bool HandlePDumpLoadCommand(StringArguments args, CommandHandler handler) + { + /* + if (args.Empty()) + return false; + + string fileStr = strtok((char*)args, " "); + if (!fileStr) + return false; + + char* accountStr = strtok(NULL, " "); + if (!accountStr) + return false; + + string accountName = accountStr; + if (!AccountMgr.normalizeString(accountName)) + { + handler.SendSysMessage(LANG_ACCOUNT_NOT_EXIST, accountName); + handler.SetSentErrorMessage(true); + return false; + } + + public uint accountId = AccountMgr.GetId(accountName); + if (!accountId) + { + accountId = atoi(accountStr); // use original string + if (!accountId) + { + handler.SendSysMessage(LANG_ACCOUNT_NOT_EXIST, accountName); + + return false; + } + } + + if (!AccountMgr.GetName(accountId, accountName)) + { + handler.SendSysMessage(LANG_ACCOUNT_NOT_EXIST, accountName); + handler.SetSentErrorMessage(true); + return false; + } + + char* guidStr = NULL; + char* nameStr = strtok(NULL, " "); + + string name; + if (nameStr) + { + name = nameStr; + // normalize the name if specified and check if it exists + if (!ObjectManager.NormalizePlayerName(name)) + { + handler.SendSysMessage(LANG_INVALID_CHARACTER_NAME); + + return false; + } + + if (ObjectMgr.CheckPlayerName(name, true) != CHAR_NAME_SUCCESS) + { + handler.SendSysMessage(LANG_INVALID_CHARACTER_NAME); + + return false; + } + + guidStr = strtok(NULL, " "); + } + + public uint guid = 0; + + if (guidStr) + { + guid = uint32(atoi(guidStr)); + if (!guid) + { + handler.SendSysMessage(LANG_INVALID_CHARACTER_GUID); + + return false; + } + + if (Global.ObjectMgr.GetPlayerAccountIdByGUID(guid)) + { + handler.SendSysMessage(LANG_CHARACTER_GUID_IN_USE, guid); + + return false; + } + } + + switch (PlayerDumpReader().LoadDump(fileStr, accountId, name, guid)) + { + case DUMP_SUCCESS: + handler.SendSysMessage(LANG_COMMAND_IMPORT_SUCCESS); + break; + case DUMP_FILE_OPEN_ERROR: + handler.SendSysMessage(LANG_FILE_OPEN_FAIL, fileStr); + + return false; + case DUMP_FILE_BROKEN: + handler.SendSysMessage(LANG_DUMP_BROKEN, fileStr); + + return false; + case DUMP_TOO_MANY_CHARS: + handler.SendSysMessage(LANG_ACCOUNT_CHARACTER_LIST_FULL, accountName, accountId); + + return false; + default: + handler.SendSysMessage(LANG_COMMAND_IMPORT_FAILED); + + return false; + } + */ + return true; + } + + [Command("write", RBACPermissions.CommandPdumpWrite, true)] + static bool HandlePDumpWriteCommand(StringArguments args, CommandHandler handler) + { + if (args.Empty()) + return false; + /* + char* fileStr = strtok((char*)args, " "); + char* playerStr = strtok(NULL, " "); + + if (!fileStr || !playerStr) + return false; + + uint64 guid; + // character name can't start from number + if (isNumeric(playerStr)) + guid = MAKE_NEW_GUID(atoi(playerStr), 0, HIGHGUID_PLAYER); + else + { + string name = handler.extractPlayerNameFromLink(playerStr); + if (name.empty()) + { + handler.SendSysMessage(LANG_PLAYER_NOT_FOUND); + + return false; + } + + guid = Global.ObjectMgr.GetPlayerGUIDByName(name); + } + + if (!Global.ObjectMgr.GetPlayerAccountIdByGUID(guid)) + { + handler.SendSysMessage(LANG_PLAYER_NOT_FOUND); + handler.SetSentErrorMessage(true); + return false; + } + + switch (PlayerDumpWriter().WriteDump(fileStr, uint32(guid))) + { + case DUMP_SUCCESS: + handler.SendSysMessage(LANG_COMMAND_EXPORT_SUCCESS); + break; + case DUMP_FILE_OPEN_ERROR: + handler.SendSysMessage(LANG_FILE_OPEN_FAIL, fileStr); + + return false; + case DUMP_CHARACTER_DELETED: + handler.SendSysMessage(LANG_COMMAND_EXPORT_DELETED_CHAR); + + return false; + default: + handler.SendSysMessage(LANG_COMMAND_EXPORT_FAILED); + + return false; + } + */ + return true; + } + } +} diff --git a/Game/Chat/Commands/CheatCommands.cs b/Game/Chat/Commands/CheatCommands.cs new file mode 100644 index 000000000..93b343881 --- /dev/null +++ b/Game/Chat/Commands/CheatCommands.cs @@ -0,0 +1,256 @@ +/* + * Copyright (C) 2012-2017 CypherCore + * + * 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, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +using Framework.Constants; +using Framework.IO; +using Game.Entities; + +namespace Game.Chat.Commands +{ + [CommandGroup("cheat", RBACPermissions.CommandCheat)] + class CheatCommands + { + [Command("god", RBACPermissions.CommandCheatGod)] + static bool HandleGodModeCheat(StringArguments args, CommandHandler handler) + { + if (handler.GetSession() == null && handler.GetSession().GetPlayer()) + return false; + + string argstr = args.NextString(); + if (args.Empty()) + argstr = (handler.GetSession().GetPlayer().GetCommandStatus(PlayerCommandStates.God)) ? "off" : "on"; + + if (argstr == "off") + { + handler.GetSession().GetPlayer().SetCommandStatusOff(PlayerCommandStates.God); + handler.SendSysMessage("Godmode is OFF. You can take damage."); + return true; + } + else if (argstr == "on") + { + handler.GetSession().GetPlayer().SetCommandStatusOn(PlayerCommandStates.God); + handler.SendSysMessage("Godmode is ON. You won't take damage."); + return true; + } + + return false; + } + + [Command("casttime", RBACPermissions.CommandCheatCasttime)] + static bool HandleCasttimeCheat(StringArguments args, CommandHandler handler) + { + if (handler.GetSession() == null && handler.GetSession().GetPlayer()) + return false; + + string argstr = args.NextString(); + + if (args.Empty()) + argstr = (handler.GetSession().GetPlayer().GetCommandStatus(PlayerCommandStates.Casttime)) ? "off" : "on"; + + if (argstr == "off") + { + handler.GetSession().GetPlayer().SetCommandStatusOff(PlayerCommandStates.Casttime); + handler.SendSysMessage("CastTime Cheat is OFF. Your spells will have a casttime."); + return true; + } + else if (argstr == "on") + { + handler.GetSession().GetPlayer().SetCommandStatusOn(PlayerCommandStates.Casttime); + handler.SendSysMessage("CastTime Cheat is ON. Your spells won't have a casttime."); + return true; + } + + return false; + } + + [Command("cooldown", RBACPermissions.CommandCheatCooldown)] + static bool HandleCoolDownCheat(StringArguments args, CommandHandler handler) + { + if (handler.GetSession() == null && handler.GetSession().GetPlayer()) + return false; + + string argstr = args.NextString(); + + if (args.Empty()) + argstr = (handler.GetSession().GetPlayer().GetCommandStatus(PlayerCommandStates.Cooldown)) ? "off" : "on"; + + if (argstr == "off") + { + handler.GetSession().GetPlayer().SetCommandStatusOff(PlayerCommandStates.Cooldown); + handler.SendSysMessage("Cooldown Cheat is OFF. You are on the global cooldown."); + return true; + } + else if (argstr == "on") + { + handler.GetSession().GetPlayer().SetCommandStatusOn(PlayerCommandStates.Cooldown); + handler.SendSysMessage("Cooldown Cheat is ON. You are not on the global cooldown."); + return true; + } + + return false; + } + + [Command("power", RBACPermissions.CommandCheatPower)] + static bool HandlePowerCheat(StringArguments args, CommandHandler handler) + { + if (handler.GetSession() == null && handler.GetSession().GetPlayer()) + return false; + + string argstr = args.NextString(); + + if (args.Empty()) + argstr = (handler.GetSession().GetPlayer().GetCommandStatus(PlayerCommandStates.Power)) ? "off" : "on"; + + if (argstr == "off") + { + handler.GetSession().GetPlayer().SetCommandStatusOff(PlayerCommandStates.Power); + handler.SendSysMessage("Power Cheat is OFF. You need mana/rage/energy to use spells."); + return true; + } + else if (argstr == "on") + { + handler.GetSession().GetPlayer().SetCommandStatusOn(PlayerCommandStates.Power); + handler.SendSysMessage("Power Cheat is ON. You don't need mana/rage/energy to use spells."); + return true; + } + + return false; + } + + [Command("status", RBACPermissions.CommandCheatStatus)] + static bool HandleCheatStatus(StringArguments args, CommandHandler handler) + { + Player player = handler.GetSession().GetPlayer(); + + string enabled = "ON"; + string disabled = "OFF"; + + handler.SendSysMessage(CypherStrings.CommandCheatStatus); + handler.SendSysMessage(CypherStrings.CommandCheatGod, player.GetCommandStatus(PlayerCommandStates.God) ? enabled : disabled); + handler.SendSysMessage(CypherStrings.CommandCheatCd, player.GetCommandStatus(PlayerCommandStates.Cooldown) ? enabled : disabled); + handler.SendSysMessage(CypherStrings.CommandCheatCt, player.GetCommandStatus(PlayerCommandStates.Casttime) ? enabled : disabled); + handler.SendSysMessage(CypherStrings.CommandCheatPower, player.GetCommandStatus(PlayerCommandStates.Power) ? enabled : disabled); + handler.SendSysMessage(CypherStrings.CommandCheatWw, player.GetCommandStatus(PlayerCommandStates.Waterwalk) ? enabled : disabled); + handler.SendSysMessage(CypherStrings.CommandCheatTaxinodes, player.isTaxiCheater() ? enabled : disabled); + return true; + } + + [Command("waterwalk", RBACPermissions.CommandCheatWaterwalk)] + static bool HandleWaterWalkCheat(StringArguments args, CommandHandler handler) + { + if (handler.GetSession() == null && handler.GetSession().GetPlayer()) + return false; + + string argstr = args.NextString(); + + Player target = handler.GetSession().GetPlayer(); + if (args.Empty()) + argstr = (target.GetCommandStatus(PlayerCommandStates.Waterwalk)) ? "off" : "on"; + + if (argstr == "off") + { + target.SetCommandStatusOff(PlayerCommandStates.Waterwalk); + target.SetWaterWalking(false); + handler.SendSysMessage("Waterwalking is OFF. You can't walk on water."); + return true; + } + else if (argstr == "on") + { + target.SetCommandStatusOn(PlayerCommandStates.Waterwalk); + target.SetWaterWalking(true); + handler.SendSysMessage("Waterwalking is ON. You can walk on water."); + return true; + } + + return false; + } + + [Command("taxi", RBACPermissions.CommandCheatTaxi)] + static bool HandleTaxiCheatCommand(StringArguments args, CommandHandler handler) + { + string argstr = args.NextString(); + + Player chr = handler.getSelectedPlayer(); + if (!chr) + chr = handler.GetSession().GetPlayer(); + else if (handler.HasLowerSecurity(chr, ObjectGuid.Empty)) // check online security + return false; + + if (args.Empty()) + argstr = (chr.isTaxiCheater()) ? "off" : "on"; + + if (argstr == "off") + { + chr.SetTaxiCheater(false); + handler.SendSysMessage(CypherStrings.YouRemoveTaxis, handler.GetNameLink(chr)); + if (handler.needReportToTarget(chr)) + chr.SendSysMessage(CypherStrings.YoursTaxisRemoved, handler.GetNameLink()); + + return true; + } + else if (argstr == "on") + { + chr.SetTaxiCheater(true); + handler.SendSysMessage(CypherStrings.YouGiveTaxis, handler.GetNameLink(chr)); + if (handler.needReportToTarget(chr)) + chr.SendSysMessage(CypherStrings.YoursTaxisAdded, handler.GetNameLink()); + return true; + } + + handler.SendSysMessage(CypherStrings.UseBol); + return false; + } + + [Command("explore", RBACPermissions.CommandCheatExplore)] + static bool HandleExploreCheat(StringArguments args, CommandHandler handler) + { + if (args.Empty()) + return false; + + int flag = args.NextInt32(); + Player chr = handler.getSelectedPlayer(); + if (!chr) + { + handler.SendSysMessage(CypherStrings.NoCharSelected); + return false; + } + + if (flag != 0) + { + handler.SendSysMessage(CypherStrings.YouSetExploreAll, handler.GetNameLink(chr)); + if (handler.needReportToTarget(chr)) + chr.SendSysMessage(CypherStrings.YoursExploreSetAll, handler.GetNameLink()); + } + else + { + handler.SendSysMessage(CypherStrings.YouSetExploreNothing, handler.GetNameLink(chr)); + if (handler.needReportToTarget(chr)) + chr.SendSysMessage(CypherStrings.YoursExploreSetNothing, handler.GetNameLink()); + } + + for (ushort i = 0; i < PlayerConst.ExploredZonesSize; ++i) + { + if (flag != 0) + handler.GetSession().GetPlayer().SetFlag(PlayerFields.ExploredZones1 + i, 0xFFFFFFFF); + else + handler.GetSession().GetPlayer().SetFlag(PlayerFields.ExploredZones1 + i, 0); + } + + return true; + } + } +} diff --git a/Game/Chat/Commands/DebugCommands.cs b/Game/Chat/Commands/DebugCommands.cs new file mode 100644 index 000000000..37e6f4cba --- /dev/null +++ b/Game/Chat/Commands/DebugCommands.cs @@ -0,0 +1,1382 @@ +/* + * Copyright (C) 2012-2017 CypherCore + * + * 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, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +using Framework.Constants; +using Framework.IO; +using Game.BattleFields; +using Game.BattleGrounds; +using Game.Combat; +using Game.DataStorage; +using Game.Entities; +using Game.Maps; +using Game.Network.Packets; +using System; +using System.Collections.Generic; +using System.Text; + +namespace Game.Chat +{ + [CommandGroup("debug", RBACPermissions.CommandDebug, true)] + class DebugCommands + { + [Command("anim", RBACPermissions.CommandDebugAnim)] + static bool HandleDebugAnimCommand(StringArguments args, CommandHandler handler) + { + if (args.Empty()) + return false; + + uint animId = args.NextUInt32(); + Unit unit = handler.getSelectedUnit(); + if (unit) + unit.HandleEmoteCommand((Emote)animId); + return true; + } + + [Command("areatriggers", RBACPermissions.CommandDebugAreatriggers)] + static bool HandleDebugAreaTriggersCommand(StringArguments args, CommandHandler handler) + { + Player player = handler.GetSession().GetPlayer(); + if (!player.isDebugAreaTriggers) + { + handler.SendSysMessage(CypherStrings.DebugAreatriggerOn); + player.isDebugAreaTriggers = true; + } + else + { + handler.SendSysMessage(CypherStrings.DebugAreatriggerOff); + player.isDebugAreaTriggers = false; + } + return true; + } + + [Command("arena", RBACPermissions.CommandDebugArena, true)] + static bool HandleDebugArenaCommand(StringArguments args, CommandHandler handler) + { + Global.BattlegroundMgr.ToggleArenaTesting(); + return true; + } + + [Command("bg", RBACPermissions.CommandDebugBg, true)] + static bool HandleDebugBattlegroundCommand(StringArguments args, CommandHandler handler) + { + Global.BattlegroundMgr.ToggleTesting(); + return true; + } + + [Command("boundary", RBACPermissions.CommandDebugBoundary)] + static bool HandleDebugBoundaryCommand(StringArguments args, CommandHandler handler) + { + Player player = handler.GetSession().GetPlayer(); + if (!player) + return false; + + Creature target = handler.getSelectedCreature(); + if (!target || !target.IsAIEnabled || target.GetAI() == null) + return false; + + string fill_str = args.NextString(); + string duration_str = args.NextString(); + + int duration = duration_str.IsEmpty() ? int.Parse(duration_str) : -1; + if (duration <= 0 || duration >= 30 * Time.Minute) // arbitary upper limit + duration = 3 * Time.Minute; + + bool doFill = fill_str.ToLower().Equals("fill"); + + CypherStrings errMsg = target.GetAI().VisualizeBoundary(duration, player, doFill); + if (errMsg > 0) + handler.SendSysMessage(errMsg); + + return true; + } + + [Command("conversation", RBACPermissions.CommandDebugConversation)] + static bool HandleDebugConversationCommand(StringArguments args, CommandHandler handler) + { + if (args.Empty()) + return false; + + string conversationEntryStr = args.NextString(); + + if (conversationEntryStr.IsEmpty()) + return false; + + uint conversationEntry = uint.Parse(conversationEntryStr); + Player target = handler.getSelectedPlayerOrSelf(); + + if (!target) + { + handler.SendSysMessage(CypherStrings.PlayerNotFound); + return false; + } + + return Conversation.CreateConversation(conversationEntry, target, target, new List() { target.GetGUID() }) != null; + } + + [Command("entervehicle", RBACPermissions.CommandDebugEntervehicle)] + static bool HandleDebugEnterVehicleCommand(StringArguments args, CommandHandler handler) + { + Unit target = handler.getSelectedUnit(); + if (!target || !target.IsVehicle()) + return false; + + if (args.Empty()) + return false; + + string i = args.NextString(); + if (string.IsNullOrEmpty(i)) + return false; + + string j = args.NextString(); + + uint entry = uint.Parse(i); + sbyte seatId = !string.IsNullOrEmpty(j) ? sbyte.Parse(j) : (sbyte)-1; + + if (entry == 0) + handler.GetSession().GetPlayer().EnterVehicle(target, seatId); + else + { + var check = new AllCreaturesOfEntryInRange(handler.GetSession().GetPlayer(), entry, 20.0f); + var searcher = new CreatureSearcher(handler.GetSession().GetPlayer(), check); + Cell.VisitAllObjects(handler.GetSession().GetPlayer(), searcher, 30.0f); + var passenger = searcher.GetTarget(); + if (!passenger || passenger == target) + return false; + passenger.EnterVehicle(target, seatId); + } + + handler.SendSysMessage("Unit {0} entered vehicle {1}", entry, seatId); + return true; + } + + [Command("getitemstate", RBACPermissions.CommandDebugGetitemstate)] + static bool HandleDebugGetItemStateCommand(StringArguments args, CommandHandler handler) + { + if (args.Empty()) + return false; + + string itemState = args.NextString(); + + ItemUpdateState state = ItemUpdateState.Unchanged; + bool listQueue = false; + bool checkAll = false; + + if (itemState == "unchanged") + state = ItemUpdateState.Unchanged; + else if (itemState == "changed") + state = ItemUpdateState.Changed; + else if (itemState == "new") + state = ItemUpdateState.New; + else if (itemState == "removed") + state = ItemUpdateState.Removed; + else if (itemState == "queue") + listQueue = true; + else if (itemState == "check_all") + checkAll = true; + else + return false; + + Player player = handler.getSelectedPlayer(); + if (!player) + player = handler.GetSession().GetPlayer(); + + if (!listQueue && !checkAll) + { + itemState = "The player has the following " + itemState + " items: "; + handler.SendSysMessage(itemState); + for (byte i = (int)PlayerSlots.Start; i < (int)PlayerSlots.End; ++i) + { + if (i >= InventorySlots.BuyBackStart && i < InventorySlots.BuyBackEnd) + continue; + + Item item = player.GetItemByPos(InventorySlots.Bag0, i); + if (item) + { + Bag bag = item.ToBag(); + if (bag) + { + for (byte j = 0; j < bag.GetBagSize(); ++j) + { + Item item2 = bag.GetItemByPos(j); + if (item2) + if (item2.GetState() == state) + handler.SendSysMessage("bag: 255 slot: {0} guid: {1} owner: {2}", item2.GetSlot(), item2.GetGUID().ToString(), item2.GetOwnerGUID().ToString()); + } + } + else if (item.GetState() == state) + handler.SendSysMessage("bag: 255 slot: {0} guid: {1} owner: {2}", item.GetSlot(), item.GetGUID().ToString(), item.GetOwnerGUID().ToString()); + } + } + } + + if (listQueue) + { + List updateQueue = player.ItemUpdateQueue; + for (int i = 0; i < updateQueue.Count; ++i) + { + Item item = updateQueue[i]; + if (!item) + continue; + + Bag container = item.GetContainer(); + byte bagSlot = container ? container.GetSlot() : InventorySlots.Bag0; + + string st = ""; + switch (item.GetState()) + { + case ItemUpdateState.Unchanged: + st = "unchanged"; + break; + case ItemUpdateState.Changed: + st = "changed"; + break; + case ItemUpdateState.New: + st = "new"; + break; + case ItemUpdateState.Removed: + st = "removed"; + break; + } + + handler.SendSysMessage("bag: {0} slot: {1} guid: {2} - state: {3}", bagSlot, item.GetSlot(), item.GetGUID().ToString(), st); + } + if (updateQueue.Empty()) + handler.SendSysMessage("The player's updatequeue is empty"); + } + + if (checkAll) + { + bool error = false; + List updateQueue = player.ItemUpdateQueue; + for (byte i = (int)PlayerSlots.Start; i < (int)PlayerSlots.End; ++i) + { + if (i >= InventorySlots.BuyBackStart && i < InventorySlots.BuyBackEnd) + continue; + + Item item = player.GetItemByPos(InventorySlots.Bag0, i); + if (!item) + continue; + + if (item.GetSlot() != i) + { + handler.SendSysMessage("Item with slot {0} and guid {1} has an incorrect slot value: {2}", i, item.GetGUID().ToString(), item.GetSlot()); + error = true; + continue; + } + + if (item.GetOwnerGUID() != player.GetGUID()) + { + handler.SendSysMessage("The item with slot {0} and itemguid {1} does have non-matching owner guid ({2}) and player guid ({3}) !", item.GetSlot(), item.GetGUID().ToString(), item.GetOwnerGUID().ToString(), player.GetGUID().ToString()); + error = true; + continue; + } + + Bag container = item.GetContainer(); + if (container) + { + handler.SendSysMessage("The item with slot {0} and guid {1} has a container (slot: {2}, guid: {3}) but shouldn't!", item.GetSlot(), item.GetGUID().ToString(), container.GetSlot(), container.GetGUID().ToString()); + error = true; + continue; + } + + if (item.IsInUpdateQueue()) + { + ushort qp = (ushort)item.GetQueuePos(); + if (qp > updateQueue.Count) + { + handler.SendSysMessage("The item with slot {0} and guid {1} has its queuepos ({2}) larger than the update queue size! ", item.GetSlot(), item.GetGUID().ToString(), qp); + error = true; + continue; + } + + if (updateQueue[qp] == null) + { + handler.SendSysMessage("The item with slot {0} and guid {1} has its queuepos ({2}) pointing to NULL in the queue!", item.GetSlot(), item.GetGUID().ToString(), qp); + error = true; + continue; + } + + if (updateQueue[qp] != item) + { + handler.SendSysMessage("The item with slot {0} and guid {1} has a queuepos ({2}) that points to another item in the queue (bag: {3}, slot: {4}, guid: {5})", item.GetSlot(), item.GetGUID().ToString(), qp, updateQueue[qp].GetBagSlot(), updateQueue[qp].GetSlot(), updateQueue[qp].GetGUID().ToString()); + error = true; + continue; + } + } + else if (item.GetState() != ItemUpdateState.Unchanged) + { + handler.SendSysMessage("The item with slot {0} and guid {1} is not in queue but should be (state: {2})!", item.GetSlot(), item.GetGUID().ToString(), item.GetState()); + error = true; + continue; + } + + Bag bag = item.ToBag(); + if (bag) + { + for (byte j = 0; j < bag.GetBagSize(); ++j) + { + Item item2 = bag.GetItemByPos(j); + if (!item2) + continue; + + if (item2.GetSlot() != j) + { + handler.SendSysMessage("The item in bag {0} and slot {1} (guid: {2}) has an incorrect slot value: {3}", bag.GetSlot(), j, item2.GetGUID().ToString(), item2.GetSlot()); + error = true; + continue; + } + + if (item2.GetOwnerGUID() != player.GetGUID()) + { + handler.SendSysMessage("The item in bag {0} at slot {1} and with itemguid {2}, the owner's guid ({3}) and the player's guid ({4}) don't match!", bag.GetSlot(), item2.GetSlot(), item2.GetGUID().ToString(), item2.GetOwnerGUID().ToString(), player.GetGUID().ToString()); + error = true; + continue; + } + + Bag container1 = item2.GetContainer(); + if (!container1) + { + handler.SendSysMessage("The item in bag {0} at slot {1} with guid {2} has no container!", bag.GetSlot(), item2.GetSlot(), item2.GetGUID().ToString()); + error = true; + continue; + } + + if (container1 != bag) + { + handler.SendSysMessage("The item in bag {0} at slot {1} with guid {2} has a different container(slot {3} guid {4})!", bag.GetSlot(), item2.GetSlot(), item2.GetGUID().ToString(), container1.GetSlot(), container1.GetGUID().ToString()); + error = true; + continue; + } + + if (item2.IsInUpdateQueue()) + { + ushort qp = (ushort)item2.GetQueuePos(); + if (qp > updateQueue.Count) + { + handler.SendSysMessage("The item in bag {0} at slot {1} having guid {2} has a queuepos ({3}) larger than the update queue size! ", bag.GetSlot(), item2.GetSlot(), item2.GetGUID().ToString(), qp); + error = true; + continue; + } + + if (updateQueue[qp] == null) + { + handler.SendSysMessage("The item in bag {0} at slot {1} having guid {2} has a queuepos ({3}) that points to NULL in the queue!", bag.GetSlot(), item2.GetSlot(), item2.GetGUID().ToString(), qp); + error = true; + continue; + } + + if (updateQueue[qp] != item2) + { + handler.SendSysMessage("The item in bag {0} at slot {1} having guid {2} has a queuepos ({3}) that points to another item in the queue (bag: {4}, slot: {5}, guid: {6})", bag.GetSlot(), item2.GetSlot(), item2.GetGUID().ToString(), qp, updateQueue[qp].GetBagSlot(), updateQueue[qp].GetSlot(), updateQueue[qp].GetGUID().ToString()); + error = true; + continue; + } + } + else if (item2.GetState() != ItemUpdateState.Unchanged) + { + handler.SendSysMessage("The item in bag {0} at slot {1} having guid {2} is not in queue but should be (state: {3})!", bag.GetSlot(), item2.GetSlot(), item2.GetGUID().ToString(), item2.GetState()); + error = true; + continue; + } + } + } + } + + for (int i = 0; i < updateQueue.Count; ++i) + { + Item item = updateQueue[i]; + if (!item) + continue; + + if (item.GetOwnerGUID() != player.GetGUID()) + { + handler.SendSysMessage("queue({0}): For the item with guid {0}, the owner's guid ({1}) and the player's guid ({2}) don't match!", i, item.GetGUID().ToString(), item.GetOwnerGUID().ToString(), player.GetGUID().ToString()); + error = true; + continue; + } + + if (item.GetQueuePos() != i) + { + handler.SendSysMessage("queue({0}): For the item with guid {1}, the queuepos doesn't match it's position in the queue!", i, item.GetGUID().ToString()); + error = true; + continue; + } + + if (item.GetState() == ItemUpdateState.Removed) + continue; + + Item test = player.GetItemByPos(item.GetBagSlot(), item.GetSlot()); + + if (test == null) + { + handler.SendSysMessage("queue({0}): The bag({1}) and slot({2}) values for the item with guid {3} are incorrect, the player doesn't have any item at that position!", i, item.GetBagSlot(), item.GetSlot(), item.GetGUID().ToString()); + error = true; + continue; + } + + if (test != item) + { + handler.SendSysMessage("queue({0}): The bag({1}) and slot({2}) values for the item with guid {3} are incorrect, an item which guid is {4} is there instead!", i, item.GetBagSlot(), item.GetSlot(), item.GetGUID().ToString(), test.GetGUID().ToString()); + error = true; + continue; + } + } + if (!error) + handler.SendSysMessage("All OK!"); + } + + return true; + } + + [Command("getitemvalue", RBACPermissions.CommandDebugGetitemvalue)] + static bool HandleDebugGetItemValueCommand(StringArguments args, CommandHandler handler) + { + if (args.Empty()) + return false; + + string e = args.NextString(); + string f = args.NextString(); + + if (string.IsNullOrEmpty(e) || string.IsNullOrEmpty(f)) + return false; + + ulong guid = ulong.Parse(e); + uint index = uint.Parse(f); + + Item i = handler.GetSession().GetPlayer().GetItemByGuid(ObjectGuid.Create(HighGuid.Item, guid)); + + if (!i) + return false; + + if (index >= i.valuesCount) + return false; + + uint value = i.GetUInt32Value(index); + + handler.SendSysMessage("Item {0}: value at {1} is {2}", guid, index, value); + + return true; + } + + [Command("getvalue", RBACPermissions.CommandDebugGetvalue)] + static bool HandleDebugGetValueCommand(StringArguments args, CommandHandler handler) + { + if (args.Empty()) + return false; + + string x = args.NextString(); + string z = args.NextString(); + + if (string.IsNullOrEmpty(x)) + return false; + + Unit target = handler.getSelectedUnit(); + if (!target) + { + handler.SendSysMessage(CypherStrings.SelectCharOrCreature); + + return false; + } + + ObjectGuid guid = target.GetGUID(); + + uint opcode = uint.Parse(x); + if (opcode >= target.valuesCount) + { + handler.SendSysMessage(CypherStrings.TooBigIndex, opcode, guid.ToString(), target.valuesCount); + return false; + } + + bool isInt32 = true; + if (!string.IsNullOrEmpty(z)) + isInt32 = bool.Parse(z); + + if (isInt32) + { + uint value = target.GetUInt32Value(opcode); + handler.SendSysMessage(CypherStrings.GetUintField, guid.ToString(), opcode, value); + } + else + { + float value = target.GetFloatValue(opcode); + handler.SendSysMessage(CypherStrings.GetFloatField, guid.ToString(), opcode, value); + } + + return true; + } + + [Command("hostil", RBACPermissions.CommandDebugHostil)] + static bool HandleDebugHostileRefListCommand(StringArguments args, CommandHandler handler) + { + Unit target = handler.getSelectedUnit(); + if (!target) + target = handler.GetSession().GetPlayer(); + HostileReference refe = target.getHostileRefManager().getFirst(); + uint count = 0; + handler.SendSysMessage("Hostil reference list of {0} (guid {1})", target.GetName(), target.GetGUID().ToString()); + while (refe != null) + { + Unit unit = refe.GetSource().GetOwner(); + if (unit) + { + ++count; + handler.SendSysMessage(" {0}. {1} ({2}, SpawnId: {3}) - threat {4}", count, unit.GetName(), unit.GetGUID().ToString(), unit.IsTypeId(TypeId.Unit) ? unit.ToCreature().GetSpawnId() : 0, refe.getThreat()); + } + refe = refe.next(); + } + handler.SendSysMessage("End of hostil reference list."); + return true; + } + + [Command("itemexpire", RBACPermissions.CommandDebugItemexpire)] + static bool HandleDebugItemExpireCommand(StringArguments args, CommandHandler handler) + { + if (args.Empty()) + return false; + + string e = args.NextString(); + if (string.IsNullOrEmpty(e)) + return false; + + ulong guid = ulong.Parse(e); + + Item i = handler.GetSession().GetPlayer().GetItemByGuid(ObjectGuid.Create(HighGuid.Item, guid)); + + if (!i) + return false; + + handler.GetSession().GetPlayer().DestroyItem(i.GetBagSlot(), i.GetSlot(), true); + Global.ScriptMgr.OnItemExpire(handler.GetSession().GetPlayer(), i.GetTemplate()); + + return true; + } + + [Command("lootrecipient", RBACPermissions.CommandDebugLootrecipient)] + static bool HandleDebugGetLootRecipientCommand(StringArguments args, CommandHandler handler) + { + Creature target = handler.getSelectedCreature(); + if (!target) + return false; + + handler.SendSysMessage("Loot recipient for creature {0} (GUID {1}, DB GUID {2}) is {3}", target.GetName(), target.GetGUID().ToString(), target.GetSpawnId(), + target.hasLootRecipient() ? (target.GetLootRecipient() ? target.GetLootRecipient().GetName() : "offline") : "no loot recipient"); + return true; + } + + [Command("los", RBACPermissions.CommandDebugLos)] + static bool HandleDebugLoSCommand(StringArguments args, CommandHandler handler) + { + Unit unit = handler.getSelectedUnit(); + if (unit) + handler.SendSysMessage("Unit {0} (GuidLow: {1}) is {2}in LoS", unit.GetName(), unit.GetGUID().ToString(), handler.GetSession().GetPlayer().IsWithinLOSInMap(unit) ? "" : "not "); + return true; + } + + [Command("Mod32Value", RBACPermissions.CommandDebugMod32value)] + static bool HandleDebugMod32ValueCommand(StringArguments args, CommandHandler handler) + { + if (args.Empty()) + return false; + + string x = args.NextString(); + string y = args.NextString(); + + if (string.IsNullOrEmpty(x) || string.IsNullOrEmpty(y)) + return false; + + uint opcode = uint.Parse(x); + int value = int.Parse(y); + + if (opcode >= handler.GetSession().GetPlayer().valuesCount) + { + handler.SendSysMessage(CypherStrings.TooBigIndex, opcode, handler.GetSession().GetPlayer().GetGUID().ToString(), handler.GetSession().GetPlayer().valuesCount); + return false; + } + + int currentValue = (int)handler.GetSession().GetPlayer().GetUInt32Value(opcode); + + currentValue += value; + handler.GetSession().GetPlayer().SetUInt32Value(opcode, (uint)currentValue); + + handler.SendSysMessage(CypherStrings.Change32bitField, opcode, currentValue); + + return true; + } + + [Command("moveflags", RBACPermissions.CommandDebugMoveflags)] + static bool HandleDebugMoveflagsCommand(StringArguments args, CommandHandler handler) + { + Unit target = handler.getSelectedUnit(); + if (!target) + target = handler.GetSession().GetPlayer(); + + if (args.Empty()) + { + //! Display case + handler.SendSysMessage(CypherStrings.MoveflagsGet, target.GetUnitMovementFlags(), target.GetUnitMovementFlags2()); + } + else + { + string mask1 = args.NextString(); + if (string.IsNullOrEmpty(mask1)) + return false; + + string mask2 = args.NextString(" \n"); + + uint moveFlags = uint.Parse(mask1); + target.SetUnitMovementFlags((MovementFlag)moveFlags); + + /// @fixme: port master's HandleDebugMoveflagsCommand; flags need different handling + + if (!string.IsNullOrEmpty(mask2)) + { + uint moveFlagsExtra = uint.Parse(mask2); + target.SetUnitMovementFlags2((MovementFlag2)moveFlagsExtra); + } + + if (!target.IsTypeId(TypeId.Player)) + target.DestroyForNearbyPlayers(); // Force new SMSG_UPDATE_OBJECT:CreateObject + else + { + MoveUpdate moveUpdate = new MoveUpdate(); + moveUpdate.movementInfo = target.m_movementInfo; + target.SendMessageToSet(moveUpdate, true); + } + + handler.SendSysMessage(CypherStrings.MoveflagsSet, target.GetUnitMovementFlags(), target.GetUnitMovementFlags2()); + } + + return true; + } + + [Command("neargraveyard", RBACPermissions.CommandNearGraveyard)] + static bool HandleDebugNearGraveyard(StringArguments args, CommandHandler handler) + { + Player player = handler.GetSession().GetPlayer(); + WorldSafeLocsRecord nearestLoc = null; + + if (args.NextString().Equals("linked")) + { + Battleground bg = player.GetBattleground(); + if (bg) + nearestLoc = bg.GetClosestGraveYard(player); + else + { + BattleField bf = Global.BattleFieldMgr.GetBattlefieldToZoneId(player.GetZoneId()); + if (bf != null) + nearestLoc = bf.GetClosestGraveYard(player); + else + nearestLoc = Global.ObjectMgr.GetClosestGraveYard(player.GetPositionX(), player.GetPositionY(), player.GetPositionZ(), player.GetMapId(), player.GetTeam()); + } + } + else + { + float x = player.GetPositionX(); + float y = player.GetPositionY(); + float z = player.GetPositionZ(); + float distNearest = float.MaxValue; + + foreach (var loc in CliDB.WorldSafeLocsStorage.Values) + { + if (loc.MapID == player.GetMapId()) + { + float dist = (loc.Loc.X - x) * (loc.Loc.X - x) + (loc.Loc.Y - y) * (loc.Loc.Y - y) + (loc.Loc.Z - z) * (loc.Loc.Z - z); + if (dist < distNearest) + { + distNearest = dist; + nearestLoc = loc; + } + } + } + } + + if (nearestLoc != null) + handler.SendSysMessage(CypherStrings.CommandNearGraveyard, nearestLoc.Id, nearestLoc.Loc.X, nearestLoc.Loc.Y, nearestLoc.Loc.Z); + else + handler.SendSysMessage(CypherStrings.CommandNearGraveyardNotfound); + + return true; + } + + [Command("loadcells", RBACPermissions.CommandDebugLoadcells)] + static bool HandleDebugLoadCellsCommand(StringArguments args, CommandHandler handler) + { + Player player = handler.GetSession().GetPlayer(); + if (!player) + return false; + + Map map = null; + + if (!args.Empty()) + { + uint mapId = args.NextUInt32(); + map = Global.MapMgr.FindBaseNonInstanceMap(mapId); + } + if (!map) + map = player.GetMap(); + + map.LoadAllCells(); + + handler.SendSysMessage("Cells loaded (mapId: {0})", map.GetId()); + return true; + } + + [Command("phase", RBACPermissions.CommandDebugPhase)] + static bool HandleDebugPhaseCommand(StringArguments args, CommandHandler handler) + { + Unit target = handler.getSelectedUnit(); + if (!target) + { + handler.SendSysMessage(CypherStrings.SelectCreature); + return false; + } + + if (target.IsTypeId(TypeId.Unit)) + { + int dbPhase = target.ToCreature().GetDBPhase(); + if (dbPhase > 0) + handler.SendSysMessage("Target creature's PhaseId in DB: {0}", dbPhase); + else if (dbPhase < 0) + handler.SendSysMessage("Target creature's PhaseGroup in DB: {0}", Math.Abs(dbPhase)); + } + + string phases = ""; + foreach (uint phase in target.GetPhases()) + phases += phase + " "; + + if (!string.IsNullOrEmpty(phases)) + handler.SendSysMessage("Target's current phases: {0}", phases); + else + handler.SendSysMessage("Target is not phased"); + return true; + } + + [Command("raidreset", RBACPermissions.CommandInstanceUnbind)] + static bool HandleDebugRaidResetCommand(StringArguments args, CommandHandler handler) + { + string map_str = args.NextString(); + string difficulty_str = args.NextString(); + + int map = !map_str.IsEmpty() ? int.Parse(map_str) : -1; + if (map <= 0) + return false; + + MapRecord mEntry = CliDB.MapStorage.LookupByKey(map); + if (mEntry == null || !mEntry.IsRaid()) + return false; + + int difficulty = !difficulty_str.IsEmpty() ? int.Parse(difficulty_str) : -1; + if (difficulty >= (int)Difficulty.Max || difficulty < -1) + return false; + + if (difficulty == -1) + { + for (byte diff = 0; diff < (int)Difficulty.Max; ++diff) + { + if (Global.DB2Mgr.GetMapDifficultyData((uint)map, (Difficulty)diff) != null) + Global.InstanceSaveMgr.ForceGlobalReset((uint)map, (Difficulty)diff); + } + } + else + Global.InstanceSaveMgr.ForceGlobalReset((uint)map, (Difficulty)difficulty); + return true; + } + + [Command("setaurastate", RBACPermissions.CommandDebugSetaurastate)] + static bool HandleDebugSetAuraStateCommand(StringArguments args, CommandHandler handler) + { + if (args.Empty()) + { + handler.SendSysMessage(CypherStrings.BadValue); + return false; + } + + Unit unit = handler.getSelectedUnit(); + if (!unit) + { + handler.SendSysMessage(CypherStrings.SelectCharOrCreature); + return false; + } + + int state = args.NextInt32(); + if (state == 0) + { + // reset all states + for (int i = 1; i <= 32; ++i) + unit.ModifyAuraState((AuraStateType)i, false); + return true; + } + + unit.ModifyAuraState((AuraStateType)Math.Abs(state), state > 0); + return true; + } + + [Command("setbit", RBACPermissions.CommandDebugSetbit)] + static bool HandleDebugSet32BitCommand(StringArguments args, CommandHandler handler) + { + if (args.Empty()) + return false; + + WorldObject target = handler.GetSelectedObject(); + if (!target) + { + handler.SendSysMessage(CypherStrings.SelectCharOrCreature); + return false; + } + + string x = args.NextString(); + string y = args.NextString(); + + if (string.IsNullOrEmpty(x) || string.IsNullOrEmpty(y)) + return false; + + uint opcode = uint.Parse(x); + uint val = uint.Parse(y); + if (val > 32) //uint = 32 bits + return false; + + uint value = (uint)(val != 0 ? 1 << ((int)val - 1) : 0); + target.SetUInt32Value(opcode, value); + + handler.SendSysMessage(CypherStrings.Set32bitField, opcode, value); + return true; + } + + [Command("setitemvalue", RBACPermissions.CommandDebugSetitemvalue)] + static bool HandleDebugSetItemValueCommand(StringArguments args, CommandHandler handler) + { + if (args.Empty()) + return false; + + string e = args.NextString(); + string f = args.NextString(); + string g = args.NextString(); + + if (string.IsNullOrEmpty(e) || string.IsNullOrEmpty(f) || string.IsNullOrEmpty(g)) + return false; + + ulong guid = ulong.Parse(e); + uint index = uint.Parse(f); + uint value = uint.Parse(g); + + Item i = handler.GetSession().GetPlayer().GetItemByGuid(ObjectGuid.Create(HighGuid.Item, guid)); + + if (!i) + return false; + + if (index >= i.valuesCount) + return false; + + i.SetUInt32Value(index, value); + + return true; + } + + [Command("setvalue", RBACPermissions.CommandDebugSetvalue)] + static bool HandleDebugSetValueCommand(StringArguments args, CommandHandler handler) + { + if (args.Empty()) + return false; + + string x = args.NextString(); + string y = args.NextString(); + string z = args.NextString(); + + if (string.IsNullOrEmpty(x) || string.IsNullOrEmpty(y)) + return false; + + WorldObject target = handler.GetSelectedObject(); + if (!target) + { + handler.SendSysMessage(CypherStrings.SelectCharOrCreature); + return false; + } + + ObjectGuid guid = target.GetGUID(); + + uint opcode = uint.Parse(x); + if (opcode >= target.valuesCount) + { + handler.SendSysMessage(CypherStrings.TooBigIndex, opcode, guid.ToString(), target.valuesCount); + return false; + } + + bool isInt32 = true; + if (!string.IsNullOrEmpty(z)) + isInt32 = bool.Parse(z); + + if (isInt32) + { + uint value = uint.Parse(y); + target.SetUInt32Value(opcode, value); + handler.SendSysMessage(CypherStrings.SetUintField, guid.ToString(), opcode, value); + } + else + { + float value = float.Parse(y); + target.SetFloatValue(opcode, value); + handler.SendSysMessage(CypherStrings.SetFloatField, guid.ToString(), opcode, value); + } + + return true; + } + + [Command("setvid", RBACPermissions.CommandDebugSetvid)] + static bool HandleDebugSetVehicleIdCommand(StringArguments args, CommandHandler handler) + { + Unit target = handler.getSelectedUnit(); + if (!target || target.IsVehicle()) + return false; + + if (args.Empty()) + return false; + + string i = args.NextString(); + if (string.IsNullOrEmpty(i)) + return false; + + uint id = uint.Parse(i); + handler.SendSysMessage("Vehicle id set to {0}", id); + return true; + } + + [Command("spawnvehicle", RBACPermissions.CommandDebugSpawnvehicle)] + static bool HandleDebugSpawnVehicleCommand(StringArguments args, CommandHandler handler) + { + if (args.Empty()) + return false; + + string e = args.NextString(); + string i = args.NextString(); + + if (string.IsNullOrEmpty(e)) + return false; + + uint entry = uint.Parse(e); + + float x, y, z, o = handler.GetSession().GetPlayer().GetOrientation(); + handler.GetSession().GetPlayer().GetClosePoint(out x, out y, out z, handler.GetSession().GetPlayer().GetObjectSize()); + + if (string.IsNullOrEmpty(i)) + return handler.GetSession().GetPlayer().SummonCreature(entry, x, y, z, o); + + uint id = uint.Parse(i); + + CreatureTemplate ci = Global.ObjectMgr.GetCreatureTemplate(entry); + + if (ci == null) + return false; + + VehicleRecord ve = CliDB.VehicleStorage.LookupByKey(id); + if (ve == null) + return false; + + Creature v = new Creature(); + + Map map = handler.GetSession().GetPlayer().GetMap(); + + if (!v.Create(map.GenerateLowGuid(HighGuid.Vehicle), map, handler.GetSession().GetPlayer().GetPhaseMask(), entry, x, y, z, o, null, id)) + return false; + + map.AddToMap(v.ToCreature()); + + return true; + } + + [Command("threat", RBACPermissions.CommandDebugThreat)] + static bool HandleDebugThreatListCommand(StringArguments args, CommandHandler handler) + { + Creature target = handler.getSelectedCreature(); + if (!target || target.IsTotem() || target.IsPet()) + return false; + + var threatList = target.GetThreatManager().getThreatList(); + uint count = 0; + handler.SendSysMessage("Threat list of {0} (guid {1})", target.GetName(), target.GetGUID().ToString()); + foreach (var refe in threatList) + { + Unit unit = refe.getTarget(); + if (!unit) + continue; + ++count; + handler.SendSysMessage(" {0}. {1} (guid {2}) - threat {3}", count, unit.GetName(), unit.GetGUID().ToString(), refe.getThreat()); + } + handler.SendSysMessage("End of threat list."); + return true; + } + + [Command("transport", RBACPermissions.CommandDebugTransport)] + static bool HandleDebugTransportCommand(StringArguments args, CommandHandler handler) + { + Transport transport = handler.GetSession().GetPlayer().GetTransport(); + if (!transport) + return false; + + bool start = false; + string arg1 = args.NextString(); + if (arg1 == "stop") + transport.EnableMovement(false); + else if (arg1 == "start") + { + transport.EnableMovement(true); + start = true; + } + else + { + Position pos = transport.GetPosition(); + handler.SendSysMessage("Transport {0} is {1}", transport.GetName(), transport.GetGoState() == GameObjectState.Ready ? "stopped" : "moving"); + handler.SendSysMessage("Transport position: {0}", pos.ToString()); + return true; + } + + handler.SendSysMessage("Transport {0} {1}", transport.GetName(), start ? "started" : "stopped"); + return true; + } + + [Command("update", RBACPermissions.CommandDebugUpdate)] + static bool HandleDebugUpdateCommand(StringArguments args, CommandHandler handler) + { + if (args.Empty()) + return false; + + uint updateIndex; + uint value; + + string index = args.NextString(); + + Unit unit = handler.getSelectedUnit(); + if (!unit) + { + handler.SendSysMessage(CypherStrings.SelectCharOrCreature); + return false; + } + + if (string.IsNullOrEmpty(index)) + return true; + + updateIndex = uint.Parse(index); + //check updateIndex + if (unit.IsTypeId(TypeId.Player)) + { + if (updateIndex >= (int)PlayerFields.End) + return true; + } + else if (updateIndex >= (int)UnitFields.End) + return true; + + string val = args.NextString(); + if (string.IsNullOrEmpty(val)) + { + value = unit.GetUInt32Value(updateIndex); + + handler.SendSysMessage(CypherStrings.Update, unit.GetGUID().ToString(), updateIndex, value); + return true; + } + + value = uint.Parse(val); + + handler.SendSysMessage(CypherStrings.UpdateChange, unit.GetGUID().ToString(), updateIndex, value); + + unit.SetUInt32Value(updateIndex, value); + + return true; + } + + [Command("uws", RBACPermissions.CommandDebugUws)] + static bool HandleDebugUpdateWorldStateCommand(StringArguments args, CommandHandler handler) + { + string w = args.NextString(); + string s = args.NextString(); + + if (string.IsNullOrEmpty(w) || string.IsNullOrEmpty(s)) + return false; + + uint world = uint.Parse(w); + uint state = uint.Parse(s); + handler.GetSession().GetPlayer().SendUpdateWorldState(world, state); + return true; + } + + [CommandGroup("send", RBACPermissions.CommandDebugSend)] + class SendCommands + { + [Command("buyerror", RBACPermissions.CommandDebugSendBuyerror)] + static bool HandleDebugSendBuyErrorCommand(StringArguments args, CommandHandler handler) + { + if (args.Empty()) + return false; + + BuyResult msg = (BuyResult)args.NextUInt32(); + handler.GetSession().GetPlayer().SendBuyError(msg, null, 0); + return true; + } + + [Command("channelnotify", RBACPermissions.CommandDebugSendChannelnotify)] + static bool HandleDebugSendChannelNotifyCommand(StringArguments args, CommandHandler handler) + { + if (args.Empty()) + return false; + + string name = "test"; + byte code = args.NextByte(); + ChannelNotify packet = new ChannelNotify(); + packet.Type = (ChatNotify)code; + packet.Channel = name; + handler.GetSession().SendPacket(packet); + return true; + } + + [Command("chatmessage", RBACPermissions.CommandDebugSendChatmessage)] + static bool HandleDebugSendChatMsgCommand(StringArguments args, CommandHandler handler) + { + if (args.Empty()) + return false; + + string msg = "testtest"; + byte type = args.NextByte(); + ChatPkt data = new ChatPkt(); + data.Initialize((ChatMsg)type, Language.Universal, handler.GetSession().GetPlayer(), handler.GetSession().GetPlayer(), msg, 0, "chan"); + handler.GetSession().SendPacket(data); + return true; + } + + [Command("equiperror", RBACPermissions.CommandDebugSendEquiperror)] + static bool HandleDebugSendEquipErrorCommand(StringArguments args, CommandHandler handler) + { + if (args.Empty()) + return false; + + InventoryResult msg = (InventoryResult)args.NextUInt32(); + handler.GetSession().GetPlayer().SendEquipError(msg); + return true; + } + + [Command("largepacket", RBACPermissions.CommandDebugSendLargepacket)] + static bool HandleDebugSendLargePacketCommand(StringArguments args, CommandHandler handler) + { + const string stuffingString = "This is a dummy string to push the packet's size beyond 128000 bytes. "; + StringBuilder ss = new StringBuilder(); + while (ss.Length < 128000) + ss.Append(stuffingString); + handler.SendSysMessage(ss.ToString()); + return true; + } + + [Command("opcode", RBACPermissions.CommandDebugSendOpcode)] + static bool HandleDebugSendOpcodeCommand(StringArguments args, CommandHandler handler) + { + handler.SendSysMessage(CypherStrings.NoCmd); + return true; + } + + [Command("qpartymsg", RBACPermissions.CommandDebugSendQpartymsg)] + static bool HandleDebugSendQuestPartyMsgCommand(StringArguments args, CommandHandler handler) + { + uint msg = args.NextUInt32(); + handler.GetSession().GetPlayer().SendPushToPartyResponse(handler.GetSession().GetPlayer(), (QuestPushReason)msg); + return true; + } + + [Command("qinvalidmsg", RBACPermissions.CommandDebugSendQinvalidmsg)] + static bool HandleDebugSendQuestInvalidMsgCommand(StringArguments args, CommandHandler handler) + { + QuestFailedReasons msg = (QuestFailedReasons)args.NextUInt32(); + handler.GetSession().GetPlayer().SendCanTakeQuestResponse(msg); + return true; + } + + [Command("sellerror", RBACPermissions.CommandDebugSendSellerror)] + static bool HandleDebugSendSellErrorCommand(StringArguments args, CommandHandler handler) + { + if (args.Empty()) + return false; + + SellResult msg = (SellResult)args.NextUInt32(); + handler.GetSession().GetPlayer().SendSellError(msg, null, ObjectGuid.Empty); + return true; + } + + [Command("setphaseshift", RBACPermissions.CommandDebugSendSetphaseshift)] + static bool HandleDebugSendSetPhaseShiftCommand(StringArguments args, CommandHandler handler) + { + if (args.Empty()) + return false; + + string t = args.NextString(); + string p = args.NextString(); + + if (string.IsNullOrEmpty(t)) + return false; + + List terrainswap = new List(); + List phaseId = new List(); + List worldMapSwap = new List(); + + terrainswap.Add(uint.Parse(t)); + + if (!string.IsNullOrEmpty(p)) + phaseId.Add(uint.Parse(p)); + + handler.GetSession().SendSetPhaseShift(phaseId, terrainswap, worldMapSwap); + return true; + } + + [Command("spellfail", RBACPermissions.CommandDebugSendSpellfail)] + static bool HandleDebugSendSpellFailCommand(StringArguments args, CommandHandler handler) + { + if (args.Empty()) + return false; + + string result = args.NextString(); + if (string.IsNullOrEmpty(result)) + return false; + + byte failNum = byte.Parse(result); + if (failNum == 0) + return false; + + string fail1 = args.NextString(); + int failArg1 = !string.IsNullOrEmpty(fail1) ? int.Parse(fail1) : 0; + + string fail2 = args.NextString(); + int failArg2 = !string.IsNullOrEmpty(fail2) ? int.Parse(fail2) : 0; + + CastFailed castFailed = new CastFailed(); + castFailed.CastID = ObjectGuid.Empty; + castFailed.SpellID = 133; + castFailed.Reason = (SpellCastResult)failNum; + castFailed.FailedArg1 = failArg1; + castFailed.FailedArg2 = failArg2; + handler.GetSession().SendPacket(castFailed); + + return true; + } + } + + [CommandGroup("play", RBACPermissions.CommandDebugPlay)] + class PlayCommands + { + [Command("cinematic", RBACPermissions.CommandDebugPlayCinematic)] + static bool HandleDebugPlayCinematicCommand(StringArguments args, CommandHandler handler) + { + // USAGE: .debug play cinematic #cinematicid + // #cinematicid - ID decimal number from CinemaicSequences.dbc (1st column) + if (args.Empty()) + { + handler.SendSysMessage(CypherStrings.BadValue); + return false; + } + + uint id = args.NextUInt32(); + + CinematicSequencesRecord cineSeq = CliDB.CinematicSequencesStorage.LookupByKey(id); + if (cineSeq == null) + { + handler.SendSysMessage(CypherStrings.CinematicNotExist, id); + return false; + } + + // Dump camera locations + var list = M2Storage.GetFlyByCameras(cineSeq.Camera[0]); + if (list != null) + { + handler.SendSysMessage("Waypoints for sequence {0}, camera {1}", id, cineSeq.Camera[0]); + uint count = 1; + foreach (FlyByCamera cam in list) + { + handler.SendSysMessage("{0} - {1}ms [{2}, {3}, {4}] Facing {5} ({6} degrees)", count, cam.timeStamp, cam.locations.X, cam.locations.Y, cam.locations.Z, cam.locations.W, cam.locations.W * (180 / Math.PI)); + count++; + } + handler.SendSysMessage("{0} waypoints dumped", list.Count); + } + + handler.GetSession().GetPlayer().SendCinematicStart(id); + return true; + } + + [Command("movie", RBACPermissions.CommandDebugPlayMovie)] + static bool HandleDebugPlayMovieCommand(StringArguments args, CommandHandler handler) + { + // USAGE: .debug play movie #movieid + // #movieid - ID decimal number from Movie.dbc (1st column) + if (args.Empty()) + { + handler.SendSysMessage(CypherStrings.BadValue); + return false; + } + + uint id = args.NextUInt32(); + + if (!CliDB.MovieStorage.ContainsKey(id)) + { + handler.SendSysMessage(CypherStrings.MovieNotExist, id); + return false; + } + + handler.GetSession().GetPlayer().SendMovieStart(id); + return true; + } + + [Command("sound", RBACPermissions.CommandDebugPlaySound)] + static bool HandleDebugPlaySoundCommand(StringArguments args, CommandHandler handler) + { + // USAGE: .debug playsound #soundid + // #soundid - ID decimal number from SoundEntries.dbc (1st column) + if (args.Empty()) + { + handler.SendSysMessage(CypherStrings.BadValue); + + return false; + } + + uint soundId = args.NextUInt32(); + + if (!CliDB.SoundKitStorage.ContainsKey(soundId)) + { + handler.SendSysMessage(CypherStrings.SoundNotExist, soundId); + return false; + } + + Unit unit = handler.getSelectedUnit(); + if (!unit) + { + handler.SendSysMessage(CypherStrings.SelectCharOrCreature); + return false; + } + + if (!handler.GetSession().GetPlayer().GetTarget().IsEmpty()) + unit.PlayDistanceSound(soundId, handler.GetSession().GetPlayer()); + else + unit.PlayDirectSound(soundId, handler.GetSession().GetPlayer()); + + handler.SendSysMessage(CypherStrings.YouHearSound, soundId); + return true; + } + } + + [CommandNonGroup("wpgps", RBACPermissions.CommandWpgps)] + static bool HandleWPGPSCommand(StringArguments args, CommandHandler handler) + { + Player player = handler.GetSession().GetPlayer(); + + Log.outInfo(LogFilter.SqlDev, "(@PATH, XX, {0}, {1}, {2}, 0, 0, 0, 100, 0),", player.GetPositionX(), player.GetPositionY(), player.GetPositionZ()); + + handler.SendSysMessage("Waypoint SQL written to SQL Developer log"); + return true; + } + } +} diff --git a/Game/Chat/Commands/DeserterCommands.cs b/Game/Chat/Commands/DeserterCommands.cs new file mode 100644 index 000000000..416cc7666 --- /dev/null +++ b/Game/Chat/Commands/DeserterCommands.cs @@ -0,0 +1,116 @@ +/* + * Copyright (C) 2012-2017 CypherCore + * + * 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, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +using Framework.Constants; +using Framework.IO; +using Game.Entities; +using Game.Spells; + +namespace Game.Chat.Commands +{ + struct Spells + { + public const uint LFGDundeonDeserter = 71041; + public const uint BGDeserter = 26013; + } + + [CommandGroup("deserter", RBACPermissions.CommandDeserter)] + class DeserterCommands + { + [CommandGroup("instance", RBACPermissions.CommandDeserterInstance)] + class DeserterInstanceCommands + { + [Command("add", RBACPermissions.CommandDeserterInstanceAdd)] + static bool HandleDeserterInstanceAdd(StringArguments args, CommandHandler handler) + { + return HandleDeserterAdd(args, handler, true); + } + + [Command("remove", RBACPermissions.CommandDeserterInstanceRemove)] + static bool HandleDeserterInstanceRemove(StringArguments args, CommandHandler handler) + { + return HandleDeserterRemove(args, handler, true); + } + } + + [CommandGroup("bg", RBACPermissions.CommandDeserterBg)] + class DeserterBGCommands + { + [Command("add", RBACPermissions.CommandDeserterBgAdd)] + static bool HandleDeserterBGAdd(StringArguments args, CommandHandler handler) + { + return HandleDeserterAdd(args, handler, false); + } + + [Command("remove", RBACPermissions.CommandDeserterBgRemove)] + static bool HandleDeserterBGRemove(StringArguments args, CommandHandler handler) + { + return HandleDeserterRemove(args, handler, false); + } + } + + static bool HandleDeserterAdd(StringArguments args, CommandHandler handler, bool isInstance) + { + if (args.Empty()) + return false; + + Player player = handler.getSelectedPlayer(); + if (!player) + { + handler.SendSysMessage(CypherStrings.NoCharSelected); + return false; + } + string timeStr = args.NextString(); + if (string.IsNullOrEmpty(timeStr)) + { + handler.SendSysMessage(CypherStrings.BadValue); + return false; + } + + uint time = uint.Parse(timeStr); + if (time == 0) + { + handler.SendSysMessage(CypherStrings.BadValue); + return false; + } + + Aura aura = player.AddAura(isInstance ? Spells.LFGDundeonDeserter : Spells.BGDeserter, player); + if (aura == null) + { + handler.SendSysMessage(CypherStrings.BadValue); + return false; + } + aura.SetDuration((int)(time * Time.InMilliseconds)); + + return true; + } + + static bool HandleDeserterRemove(StringArguments args, CommandHandler handler, bool isInstance) + { + Player player = handler.getSelectedPlayer(); + if (!player) + { + handler.SendSysMessage(CypherStrings.NoCharSelected); + return false; + } + + player.RemoveAura(isInstance ? Spells.LFGDundeonDeserter : Spells.BGDeserter); + + return true; + } + } +} diff --git a/Game/Chat/Commands/DisableCommands.cs b/Game/Chat/Commands/DisableCommands.cs new file mode 100644 index 000000000..b95c223f6 --- /dev/null +++ b/Game/Chat/Commands/DisableCommands.cs @@ -0,0 +1,360 @@ +/* + * Copyright (C) 2012-2017 CypherCore + * + * 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, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +using Framework.Constants; +using Framework.Database; +using Framework.IO; +using Game.DataStorage; + +namespace Game.Chat.Commands +{ + [CommandGroup("disable", RBACPermissions.CommandDisable)] + class DisableCommands + { + [CommandGroup("add", RBACPermissions.CommandDisableAdd, true)] + class DisableAddCommands + { + static bool HandleAddDisables(StringArguments args, CommandHandler handler, DisableType disableType) + { + string entryStr = args.NextString(); + if (string.IsNullOrEmpty(entryStr) || int.Parse(entryStr) == 0) + return false; + + string flagsStr = args.NextString(); + uint flags = !string.IsNullOrEmpty(flagsStr) ? byte.Parse(flagsStr) : 0u; + + string disableComment = args.NextString(""); + if (string.IsNullOrEmpty(disableComment)) + return false; + + uint entry = uint.Parse(entryStr); + + string disableTypeStr = ""; + switch (disableType) + { + case DisableType.Spell: + { + if (!Global.SpellMgr.HasSpellInfo(entry)) + { + handler.SendSysMessage(CypherStrings.CommandNospellfound); + return false; + } + disableTypeStr = "spell"; + break; + } + case DisableType.Quest: + { + if (Global.ObjectMgr.GetQuestTemplate(entry) == null) + { + handler.SendSysMessage(CypherStrings.CommandNoquestfound, entry); + return false; + } + disableTypeStr = "quest"; + break; + } + case DisableType.Map: + { + if (!CliDB.MapStorage.ContainsKey(entry)) + { + handler.SendSysMessage(CypherStrings.CommandNomapfound); + return false; + } + disableTypeStr = "map"; + break; + } + case DisableType.Battleground: + { + if (!CliDB.BattlemasterListStorage.ContainsKey(entry)) + { + handler.SendSysMessage(CypherStrings.CommandNoBattlegroundFound); + return false; + } + disableTypeStr = "Battleground"; + break; + } + case DisableType.Criteria: + { + if (Global.CriteriaMgr.GetCriteria(entry) == null) + { + handler.SendSysMessage(CypherStrings.CommandNoAchievementCriteriaFound); + return false; + } + disableTypeStr = "criteria"; + break; + } + case DisableType.OutdoorPVP: + { + if (entry > (int)OutdoorPvPTypes.Max) + { + handler.SendSysMessage(CypherStrings.CommandNoOutdoorPvpForund); + return false; + } + disableTypeStr = "outdoorpvp"; + break; + } + case DisableType.VMAP: + { + if (!CliDB.MapStorage.ContainsKey(entry)) + { + handler.SendSysMessage(CypherStrings.CommandNomapfound); + return false; + } + disableTypeStr = "vmap"; + break; + } + case DisableType.MMAP: + { + if (!CliDB.MapStorage.ContainsKey(entry)) + { + handler.SendSysMessage(CypherStrings.CommandNomapfound); + return false; + } + disableTypeStr = "mmap"; + break; + } + default: + break; + } + + PreparedStatement stmt = DB.World.GetPreparedStatement(WorldStatements.SEL_DISABLES); + stmt.AddValue(0, entry); + stmt.AddValue(1, disableType); + SQLResult result = DB.World.Query(stmt); + if (!result.IsEmpty()) + { + handler.SendSysMessage("This {0} (Id: {1}) is already disabled.", disableTypeStr, entry); + return false; + } + + stmt = DB.World.GetPreparedStatement(WorldStatements.INS_DISABLES); + stmt.AddValue(0, entry); + stmt.AddValue(1, disableType); + stmt.AddValue(2, flags); + stmt.AddValue(3, disableComment); + DB.World.Execute(stmt); + + handler.SendSysMessage("Add Disabled {0} (Id: {1}) for reason {2}", disableTypeStr, entry, disableComment); + return true; + } + + [Command("spell", RBACPermissions.CommandDisableAddSpell, true)] + static bool HandleAddDisableSpellCommand(StringArguments args, CommandHandler handler) + { + if (args.Empty()) + return false; + + return HandleAddDisables(args, handler, DisableType.Spell); + } + + [Command("quest", RBACPermissions.CommandDisableAddQuest, true)] + static bool HandleAddDisableQuestCommand(StringArguments args, CommandHandler handler) + { + if (args.Empty()) + return false; + + return HandleAddDisables(args, handler, DisableType.Quest); + } + + [Command("map", RBACPermissions.CommandDisableAddMap, true)] + static bool HandleAddDisableMapCommand(StringArguments args, CommandHandler handler) + { + if (args.Empty()) + return false; + + return HandleAddDisables(args, handler, DisableType.Map); + } + + [Command("Battleground", RBACPermissions.CommandDisableAddBattleground, true)] + static bool HandleAddDisableBattlegroundCommand(StringArguments args, CommandHandler handler) + { + if (args.Empty()) + return false; + + return HandleAddDisables(args, handler, DisableType.Battleground); + } + + [Command("criteria", RBACPermissions.CommandDisableAddCriteria, true)] + static bool HandleAddDisableCriteriaCommand(StringArguments args, CommandHandler handler) + { + if (args.Empty()) + return false; + + return HandleAddDisables(args, handler, DisableType.Criteria); + } + + [Command("outdoorpvp", RBACPermissions.CommandDisableAddOutdoorpvp, true)] + static bool HandleAddDisableOutdoorPvPCommand(StringArguments args, CommandHandler handler) + { + if (args.Empty()) + return false; + + HandleAddDisables(args, handler, DisableType.OutdoorPVP); + return true; + } + + [Command("vmap", RBACPermissions.CommandDisableAddVmap, true)] + static bool HandleAddDisableVmapCommand(StringArguments args, CommandHandler handler) + { + if (args.Empty()) + return false; + + return HandleAddDisables(args, handler, DisableType.VMAP); + } + + [Command("mmap", RBACPermissions.CommandDisableAddMmap, true)] + static bool HandleAddDisableMMapCommand(StringArguments args, CommandHandler handler) + { + if (args.Empty()) + return false; + + return HandleAddDisables(args, handler, DisableType.MMAP); + } + } + + [CommandGroup("remove", RBACPermissions.CommandDisableRemove, true)] + class DisableRemoveCommands + { + static bool HandleRemoveDisables(StringArguments args, CommandHandler handler, DisableType disableType) + { + string entryStr = args.NextString(); + if (string.IsNullOrEmpty(entryStr) || uint.Parse(entryStr) == 0) + return false; + + uint entry = uint.Parse(entryStr); + + string disableTypeStr = ""; + switch (disableType) + { + case DisableType.Spell: + disableTypeStr = "spell"; + break; + case DisableType.Quest: + disableTypeStr = "quest"; + break; + case DisableType.Map: + disableTypeStr = "map"; + break; + case DisableType.Battleground: + disableTypeStr = "Battleground"; + break; + case DisableType.Criteria: + disableTypeStr = "criteria"; + break; + case DisableType.OutdoorPVP: + disableTypeStr = "outdoorpvp"; + break; + case DisableType.VMAP: + disableTypeStr = "vmap"; + break; + case DisableType.MMAP: + disableTypeStr = "mmap"; + break; + } + + PreparedStatement stmt = DB.World.GetPreparedStatement(WorldStatements.SEL_DISABLES); + stmt.AddValue(0, entry); + stmt.AddValue(1, disableType); + SQLResult result = DB.World.Query(stmt); + if (result.IsEmpty()) + { + handler.SendSysMessage("This {0} (Id: {1}) is not disabled.", disableTypeStr, entry); + return false; + } + + stmt = DB.World.GetPreparedStatement(WorldStatements.DEL_DISABLES); + stmt.AddValue(0, entry); + stmt.AddValue(1, disableType); + DB.World.Execute(stmt); + + handler.SendSysMessage("Remove Disabled {0} (Id: {1})", disableTypeStr, entry); + return true; + } + + [Command("spell", RBACPermissions.CommandDisableRemoveSpell, true)] + static bool HandleRemoveDisableSpellCommand(StringArguments args, CommandHandler handler) + { + if (args.Empty()) + return false; + + return HandleRemoveDisables(args, handler, DisableType.Spell); + } + + [Command("quest", RBACPermissions.CommandDisableRemoveQuest, true)] + static bool HandleRemoveDisableQuestCommand(StringArguments args, CommandHandler handler) + { + if (args.Empty()) + return false; + + return HandleRemoveDisables(args, handler, DisableType.Quest); + } + + [Command("map", RBACPermissions.CommandDisableRemoveMap, true)] + static bool HandleRemoveDisableMapCommand(StringArguments args, CommandHandler handler) + { + if (args.Empty()) + return false; + + return HandleRemoveDisables(args, handler, DisableType.Map); + } + + [Command("Battleground", RBACPermissions.CommandDisableRemoveBattleground, true)] + static bool HandleRemoveDisableBattlegroundCommand(StringArguments args, CommandHandler handler) + { + if (args.Empty()) + return false; + + return HandleRemoveDisables(args, handler, DisableType.Battleground); + } + + [Command("criteria", RBACPermissions.CommandDisableRemoveCriteria, true)] + static bool HandleRemoveDisableCriteriaCommand(StringArguments args, CommandHandler handler) + { + if (args.Empty()) + return false; + + return HandleRemoveDisables(args, handler, DisableType.Criteria); + } + + [Command("outdoorpvp", RBACPermissions.CommandDisableRemoveOutdoorpvp, true)] + static bool HandleRemoveDisableOutdoorPvPCommand(StringArguments args, CommandHandler handler) + { + if (args.Empty()) + return false; + + return HandleRemoveDisables(args, handler, DisableType.OutdoorPVP); + } + + [Command("vmap", RBACPermissions.CommandDisableRemoveVmap, true)] + static bool HandleRemoveDisableVmapCommand(StringArguments args, CommandHandler handler) + { + if (args.Empty()) + return false; + + return HandleRemoveDisables(args, handler, DisableType.VMAP); + } + + [Command("mmap", RBACPermissions.CommandDisableRemoveMmap, true)] + static bool HandleRemoveDisableMMapCommand(StringArguments args, CommandHandler handler) + { + if (args.Empty()) + return false; + + return HandleRemoveDisables(args, handler, DisableType.MMAP); + } + } + } +} diff --git a/Game/Chat/Commands/EventCommands.cs b/Game/Chat/Commands/EventCommands.cs new file mode 100644 index 000000000..f9cc59b14 --- /dev/null +++ b/Game/Chat/Commands/EventCommands.cs @@ -0,0 +1,180 @@ +/* + * Copyright (C) 2012-2017 CypherCore + * + * 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, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +using Framework.Constants; +using Framework.IO; + +namespace Game.Chat +{ + [CommandGroup("event", RBACPermissions.CommandEvent)] + class EventCommands + { + [Command("info", RBACPermissions.CommandEvent, true)] + static bool HandleEventInfoCommand(StringArguments args, CommandHandler handler) + { + if (args.Empty()) + return false; + + // id or [name] Shift-click form |color|Hgameevent:id|h[name]|h|r + string id = handler.extractKeyFromLink(args, "Hgameevent"); + if (string.IsNullOrEmpty(id)) + return false; + + ushort eventId = ushort.Parse(id); + + var events = Global.GameEventMgr.GetEventMap(); + + if (eventId >= events.Length) + { + handler.SendSysMessage(CypherStrings.EventNotExist); + return false; + } + + GameEventData eventData = events[eventId]; + if (!eventData.isValid()) + { + handler.SendSysMessage(CypherStrings.EventNotExist); + return false; + } + + var activeEvents = Global.GameEventMgr.GetActiveEventList(); + bool active = activeEvents.Contains(eventId); + string activeStr = active ? Global.ObjectMgr.GetCypherString(CypherStrings.Active) : ""; + + string startTimeStr = Time.UnixTimeToDateTime(eventData.start).ToLongDateString(); + string endTimeStr = Time.UnixTimeToDateTime(eventData.end).ToLongDateString(); + + uint delay = Global.GameEventMgr.NextCheck(eventId); + long nextTime = Time.UnixTime + delay; + string nextStr = nextTime >= eventData.start && nextTime < eventData.end ? Time.UnixTimeToDateTime(Time.UnixTime + delay).ToShortTimeString() : "-"; + + string occurenceStr = Time.secsToTimeString(eventData.occurence * Time.Minute); + string lengthStr = Time.secsToTimeString(eventData.length * Time.Minute); + + handler.SendSysMessage(CypherStrings.EventInfo, eventId, eventData.description, activeStr, + startTimeStr, endTimeStr, occurenceStr, lengthStr, nextStr); + return true; + } + + [Command("activelist", RBACPermissions.CommandEventActivelist, true)] + static bool HandleEventActiveListCommand(StringArguments args, CommandHandler handler) + { + uint counter = 0; + + var events = Global.GameEventMgr.GetEventMap(); + var activeEvents = Global.GameEventMgr.GetActiveEventList(); + + string active = Global.ObjectMgr.GetCypherString(CypherStrings.Active); + + foreach (var eventId in activeEvents) + { + GameEventData eventData = events[eventId]; + + if (handler.GetSession() != null) + handler.SendSysMessage(CypherStrings.EventEntryListChat, eventId, eventId, eventData.description, active); + else + handler.SendSysMessage(CypherStrings.EventEntryListConsole, eventId, eventData.description, active); + + ++counter; + } + + if (counter == 0) + handler.SendSysMessage(CypherStrings.Noeventfound); + + return true; + } + + [Command("start", RBACPermissions.CommandEventStart, true)] + static bool HandleEventStartCommand(StringArguments args, CommandHandler handler) + { + if (args.Empty()) + return false; + + // id or [name] Shift-click form |color|Hgameevent:id|h[name]|h|r + string id = handler.extractKeyFromLink(args, "Hgameevent"); + if (string.IsNullOrEmpty(id)) + return false; + + ushort eventId = ushort.Parse(id); + + var events = Global.GameEventMgr.GetEventMap(); + + if (eventId < 1 || eventId >= events.Length) + { + handler.SendSysMessage(CypherStrings.EventNotExist); + return false; + } + + GameEventData eventData = events[eventId]; + if (!eventData.isValid()) + { + handler.SendSysMessage(CypherStrings.EventNotExist); + return false; + } + + var activeEvents = Global.GameEventMgr.GetActiveEventList(); + if (activeEvents.Contains(eventId)) + { + handler.SendSysMessage(CypherStrings.EventAlreadyActive, eventId); + return false; + } + + Global.GameEventMgr.StartEvent(eventId, true); + return true; + } + + [Command("stop", RBACPermissions.CommandEventStop, true)] + static bool HandleEventStopCommand(StringArguments args, CommandHandler handler) + { + if (args.Empty()) + return false; + + // id or [name] Shift-click form |color|Hgameevent:id|h[name]|h|r + string id = handler.extractKeyFromLink(args, "Hgameevent"); + if (string.IsNullOrEmpty(id)) + return false; + + ushort eventId = ushort.Parse(id); + + var events = Global.GameEventMgr.GetEventMap(); + + if (eventId < 1 || eventId >= events.Length) + { + handler.SendSysMessage(CypherStrings.EventNotExist); + return false; + } + + GameEventData eventData = events[eventId]; + if (!eventData.isValid()) + { + handler.SendSysMessage(CypherStrings.EventNotExist); + return false; + } + + var activeEvents = Global.GameEventMgr.GetActiveEventList(); + + if (!activeEvents.Contains(eventId)) + { + handler.SendSysMessage(CypherStrings.EventNotActive, eventId); + return false; + } + + Global.GameEventMgr.StopEvent(eventId, true); + return true; + } + } +} diff --git a/Game/Chat/Commands/GMCommands.cs b/Game/Chat/Commands/GMCommands.cs new file mode 100644 index 000000000..79f2aa7d4 --- /dev/null +++ b/Game/Chat/Commands/GMCommands.cs @@ -0,0 +1,234 @@ +/* + * Copyright (C) 2012-2017 CypherCore + * + * 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, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +using Framework.Constants; +using Framework.Database; +using Framework.IO; +using Game.Entities; + +namespace Game.Chat +{ + [CommandGroup("gm", RBACPermissions.CommandGm)] + class GMCommands + { + [Command("", RBACPermissions.CommandGm)] + static bool HandleGMCommand(StringArguments args, CommandHandler handler) + { + Player _player = handler.GetSession().GetPlayer(); + + if (args.Empty()) + { + handler.SendNotification(_player.IsGameMaster() ? CypherStrings.GmOn : CypherStrings.GmOff); + return true; + } + + string param = args.NextString(); + if (param == "on") + { + _player.SetGameMaster(true); + handler.SendNotification(CypherStrings.GmOn); + _player.UpdateTriggerVisibility(); + return true; + } + if (param == "off") + { + _player.SetGameMaster(false); + handler.SendNotification(CypherStrings.GmOff); + _player.UpdateTriggerVisibility(); + return true; + } + + handler.SendSysMessage(CypherStrings.UseBol); + return false; + } + + [Command("chat", RBACPermissions.CommandGmChat)] + static bool HandleGMChatCommand(StringArguments args, CommandHandler handler) + { + WorldSession session = handler.GetSession(); + if (session != null) + { + if (args.Empty()) + { + if (session.HasPermission(RBACPermissions.ChatUseStaffBadge) && session.GetPlayer().isGMChat()) + session.SendNotification(CypherStrings.GmChatOn); + else + session.SendNotification(CypherStrings.GmChatOff); + return true; + } + + string param = args.NextString(); + + if (param == "on") + { + session.GetPlayer().SetGMChat(true); + session.SendNotification(CypherStrings.GmChatOn); + return true; + } + + if (param == "off") + { + session.GetPlayer().SetGMChat(false); + session.SendNotification(CypherStrings.GmChatOff); + return true; + } + } + + handler.SendSysMessage(CypherStrings.UseBol); + return false; + } + + [Command("fly", RBACPermissions.CommandGmFly)] + static bool HandleGMFlyCommand(StringArguments args, CommandHandler handler) + { + if (args.Empty()) + return false; + + Player target = handler.getSelectedPlayer(); + if (target == null) + target = handler.GetPlayer(); + + string arg = args.NextString().ToLower(); + + if (arg == "on") + target.SetCanFly(true); + else if (arg == "off") + target.SetCanFly(false); + else + { + handler.SendSysMessage(CypherStrings.UseBol); + return false; + } + handler.SendSysMessage(CypherStrings.CommandFlymodeStatus, handler.GetNameLink(target), arg); + return true; + } + + [Command("ingame", RBACPermissions.CommandGmIngame, true)] + static bool HandleGMListIngameCommand(StringArguments args, CommandHandler handler) + { + bool first = true; + bool footer = false; + + var m = Global.ObjAccessor.GetPlayers(); + foreach (var pl in m) + { + AccountTypes accountType = pl.GetSession().GetSecurity(); + if ((pl.IsGameMaster() || + (pl.GetSession().HasPermission(RBACPermissions.CommandsAppearInGmList) && + accountType <= (AccountTypes)WorldConfig.GetIntValue(WorldCfg.GmLevelInGmList))) && + (handler.GetSession() == null || pl.IsVisibleGloballyFor(handler.GetSession().GetPlayer()))) + { + if (first) + { + first = false; + footer = true; + handler.SendSysMessage(CypherStrings.GmsOnSrv); + handler.SendSysMessage("========================"); + } + int size = pl.GetName().Length; + byte security = (byte)accountType; + int max = ((16 - size) / 2); + int max2 = max; + if ((max + max2 + size) == 16) + max2 = max - 1; + if (handler.GetSession() != null) + handler.SendSysMessage("| {0} GMLevel {1}", pl.GetName(), security); + else + handler.SendSysMessage("|{0}{1}{2}| {3} |", max, " ", pl.GetName(), max2, " ", security); + } + } + if (footer) + handler.SendSysMessage("========================"); + if (first) + handler.SendSysMessage(CypherStrings.GmsNotLogged); + return true; + } + + [Command("list", RBACPermissions.CommandGmList, true)] + static bool HandleGMListFullCommand(StringArguments args, CommandHandler handler) + { + // Get the accounts with GM Level >0 + PreparedStatement stmt = DB.Login.GetPreparedStatement(LoginStatements.SEL_GM_ACCOUNTS); + stmt.AddValue(0, AccountTypes.Moderator); + stmt.AddValue(1, Global.WorldMgr.GetRealm().Id.Realm); + SQLResult result = DB.Login.Query(stmt); + + if (!result.IsEmpty()) + { + handler.SendSysMessage( CypherStrings.Gmlist); + handler.SendSysMessage("========================"); + // Cycle through them. Display username and GM level + do + { + string name = result.Read(0); + byte security = result.Read(1); + int max = (16 - name.Length) / 2; + int max2 = max; + if ((max + max2 + name.Length) == 16) + max2 = max - 1; + string padding = ""; + if (handler.GetSession() != null) + handler.SendSysMessage("| {0} GMLevel {1}", name, security); + else + handler.SendSysMessage("|{0}{1}{2}| {3} |", padding.PadRight(max), name, padding.PadRight(max2), security); + } while (result.NextRow()); + handler.SendSysMessage("========================"); + } + else + handler.SendSysMessage( CypherStrings.GmlistEmpty); + return true; + } + + [Command("visible", RBACPermissions.CommandGmVisible)] + static bool HandleGMVisibleCommand(StringArguments args, CommandHandler handler) + { + Player _player = handler.GetSession().GetPlayer(); + + if (args.Empty()) + { + handler.SendSysMessage(CypherStrings.YouAre, _player.isGMVisible() ? Global.ObjectMgr.GetCypherString(CypherStrings.Visible) : Global.ObjectMgr.GetCypherString(CypherStrings.Invisible)); + return true; + } + + uint VISUAL_AURA = 37800; + string param = args.NextString(); + + if (param == "on") + { + if (_player.HasAura(VISUAL_AURA, ObjectGuid.Empty)) + _player.RemoveAurasDueToSpell(VISUAL_AURA); + + _player.SetGMVisible(true); + _player.UpdateObjectVisibility(); + handler.GetSession().SendNotification(CypherStrings.InvisibleVisible); + return true; + } + + if (param == "off") + { + _player.AddAura(VISUAL_AURA, _player); + _player.SetGMVisible(false); + _player.UpdateObjectVisibility(); + handler.GetSession().SendNotification(CypherStrings.InvisibleInvisible); + return true; + } + + handler.SendSysMessage(CypherStrings.UseBol); + return false; + } + } +} diff --git a/Game/Chat/Commands/GameObjectCommands.cs b/Game/Chat/Commands/GameObjectCommands.cs new file mode 100644 index 000000000..bff9fe04a --- /dev/null +++ b/Game/Chat/Commands/GameObjectCommands.cs @@ -0,0 +1,617 @@ +/* + * Copyright (C) 2012-2017 CypherCore + * + * 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, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +using Framework.Constants; +using Framework.Database; +using Framework.GameMath; +using Framework.IO; +using Game.DataStorage; +using Game.Entities; +using Game.Maps; +using System; +using System.Collections.Generic; +using System.Text; + +namespace Game.Chat +{ + [CommandGroup("gobject", RBACPermissions.CommandGobject)] + class GameObjectCommands + { + [Command("activate", RBACPermissions.CommandGobjectActivate)] + static bool HandleGameObjectActivateCommand(StringArguments args, CommandHandler handler) + { + if (args.Empty()) + return false; + + string id = handler.extractKeyFromLink(args, "Hgameobject"); + if (string.IsNullOrEmpty(id)) + return false; + + ulong guidLow = ulong.Parse(id); + if (guidLow == 0) + return false; + + GameObject obj = handler.GetObjectFromPlayerMapByDbGuid(guidLow); + if (!obj) + { + handler.SendSysMessage(CypherStrings.CommandObjnotfound, guidLow); + return false; + } + + // Activate + obj.SetLootState(LootState.Ready); + obj.UseDoorOrButton(10000, false, handler.GetSession().GetPlayer()); + + handler.SendSysMessage("Object activated!"); + return true; + } + + [Command("delete", RBACPermissions.CommandGobjectDelete)] + static bool HandleGameObjectDeleteCommand(StringArguments args, CommandHandler handler) + { + // number or [name] Shift-click form |color|Hgameobject:go_guid|h[name]|h|r + string id = handler.extractKeyFromLink(args, "Hgameobject"); + if (string.IsNullOrEmpty(id)) + return false; + + ulong guidLow = ulong.Parse(id); + if (guidLow == 0) + return false; + + GameObject obj = handler.GetObjectFromPlayerMapByDbGuid(guidLow); + if (!obj) + { + handler.SendSysMessage(CypherStrings.CommandObjnotfound, guidLow); + return false; + } + + ObjectGuid ownerGuid = obj.GetOwnerGUID(); + if (ownerGuid.IsEmpty()) + { + Unit owner = Global.ObjAccessor.GetUnit(handler.GetPlayer(), ownerGuid); + if (!owner || !ownerGuid.IsPlayer()) + { + handler.SendSysMessage(CypherStrings.CommandDelobjrefercreature, ownerGuid.ToString(), obj.GetGUID().ToString()); + return false; + } + + owner.RemoveGameObject(obj, false); + } + + obj.SetRespawnTime(0); // not save respawn time + obj.Delete(); + obj.DeleteFromDB(); + + handler.SendSysMessage(CypherStrings.CommandDelobjmessage, obj.GetGUID().ToString()); + + return true; + } + + [Command("move", RBACPermissions.CommandGobjectMove)] + static bool HandleGameObjectMoveCommand(StringArguments args, CommandHandler handler) + { + // number or [name] Shift-click form |color|Hgameobject:go_guid|h[name]|h|r + string id = handler.extractKeyFromLink(args, "Hgameobject"); + if (string.IsNullOrEmpty(id)) + return false; + + ulong guidLow = ulong.Parse(id); + if (guidLow == 0) + return false; + + GameObject obj = handler.GetObjectFromPlayerMapByDbGuid(guidLow); + if (!obj) + { + handler.SendSysMessage(CypherStrings.CommandObjnotfound, guidLow); + return false; + } + + string toX = args.NextString(); + string toY = args.NextString(); + string toZ = args.NextString(); + + float x, y, z; + if (string.IsNullOrEmpty(toX)) + { + Player player = handler.GetSession().GetPlayer(); + player.GetPosition(out x, out y, out z); + } + else + { + if (string.IsNullOrEmpty(toY) || string.IsNullOrEmpty(toZ)) + return false; + + x = float.Parse(toX); + y = float.Parse(toY); + z = float.Parse(toZ); + + if (!GridDefines.IsValidMapCoord(obj.GetMapId(), x, y, z)) + { + handler.SendSysMessage(CypherStrings.InvalidTargetCoord, x, y, obj.GetMapId()); + return false; + } + } + + obj.DestroyForNearbyPlayers(); + obj.RelocateStationaryPosition(x, y, z, obj.GetOrientation()); + obj.GetMap().GameObjectRelocation(obj, x, y, z, obj.GetOrientation()); + + obj.SaveToDB(); + + handler.SendSysMessage(CypherStrings.CommandMoveobjmessage, obj.GetSpawnId(), obj.GetGoInfo().name, obj.GetGUID().ToString()); + + return true; + } + + [Command("near", RBACPermissions.CommandGobjectNear)] + static bool HandleGameObjectNearCommand(StringArguments args, CommandHandler handler) + { + float distance = args.Empty() ? 10.0f : args.NextSingle(); + uint count = 0; + + Player player = handler.GetPlayer(); + + PreparedStatement stmt = DB.World.GetPreparedStatement(WorldStatements.SEL_GAMEOBJECT_NEAREST); + stmt.AddValue(0, player.GetPositionX()); + stmt.AddValue(1, player.GetPositionY()); + stmt.AddValue(2, player.GetPositionZ()); + stmt.AddValue(3, player.GetMapId()); + stmt.AddValue(4, player.GetPositionX()); + stmt.AddValue(5, player.GetPositionY()); + stmt.AddValue(6, player.GetPositionZ()); + stmt.AddValue(7, distance * distance); + SQLResult result = DB.World.Query(stmt); + + if (!result.IsEmpty()) + { + do + { + ulong guid = result.Read(0); + uint entry = result.Read(1); + float x = result.Read(2); + float y = result.Read(3); + float z = result.Read(4); + ushort mapId = result.Read(5); + + GameObjectTemplate gameObjectInfo = Global.ObjectMgr.GetGameObjectTemplate(entry); + if (gameObjectInfo == null) + continue; + + handler.SendSysMessage(CypherStrings.GoListChat, guid, entry, guid, gameObjectInfo.name, x, y, z, mapId); + + ++count; + } while (result.NextRow()); + } + + handler.SendSysMessage(CypherStrings.CommandNearobjmessage, distance, count); + return true; + } + + [Command("target", RBACPermissions.CommandGobjectTarget)] + static bool HandleGameObjectTargetCommand(StringArguments args, CommandHandler handler) + { + Player player = handler.GetSession().GetPlayer(); + SQLResult result; + var activeEventsList = Global.GameEventMgr.GetActiveEventList(); + + if (!args.Empty()) + { + // number or [name] Shift-click form |color|Hgameobject_entry:go_id|h[name]|h|r + string idStr = handler.extractKeyFromLink(args, "Hgameobject_entry"); + if (string.IsNullOrEmpty(idStr)) + return false; + + uint objectId = uint.Parse(idStr); + if (objectId != 0) + result = DB.World.Query("SELECT guid, id, position_x, position_y, position_z, orientation, map, PhaseId, PhaseGroup, (POW(position_x - '{0}', 2) + POW(position_y - '{1}', 2) + POW(position_z - '{2}', 2)) AS order_ FROM gameobject WHERE map = '{3}' AND id = '{4}' ORDER BY order_ ASC LIMIT 1", + player.GetPositionX(), player.GetPositionY(), player.GetPositionZ(), player.GetMapId(), objectId); + else + { + result = DB.World.Query( + "SELECT guid, id, position_x, position_y, position_z, orientation, map, PhaseId, PhaseGroup, (POW(position_x - {0}, 2) + POW(position_y - {1}, 2) + POW(position_z - {2}, 2)) AS order_ " + + "FROM gameobject LEFT JOIN gameobject_template ON gameobject_template.entry = gameobject.id WHERE map = {3} AND name LIKE CONCAT('%%', '{4}', '%%') ORDER BY order_ ASC LIMIT 1", + player.GetPositionX(), player.GetPositionY(), player.GetPositionZ(), player.GetMapId(), objectId); + } + } + else + { + StringBuilder eventFilter = new StringBuilder(); + eventFilter.Append(" AND (eventEntry IS NULL "); + bool initString = true; + + foreach (var entry in activeEventsList) + { + if (initString) + { + eventFilter.Append("OR eventEntry IN (" + entry); + initString = false; + } + else + eventFilter.Append(',' + entry); + } + + if (!initString) + eventFilter.Append("))"); + else + eventFilter.Append(')'); + + result = DB.World.Query("SELECT gameobject.guid, id, position_x, position_y, position_z, orientation, map, PhaseId, PhaseGroup, " + + "(POW(position_x - {0}, 2) + POW(position_y - {1}, 2) + POW(position_z - {2}, 2)) AS order_ FROM gameobject " + + "LEFT OUTER JOIN game_event_gameobject on gameobject.guid = game_event_gameobject.guid WHERE map = '{3}' {4} ORDER BY order_ ASC LIMIT 10", + handler.GetSession().GetPlayer().GetPositionX(), handler.GetSession().GetPlayer().GetPositionY(), handler.GetSession().GetPlayer().GetPositionZ(), + handler.GetSession().GetPlayer().GetMapId(), eventFilter.ToString()); + } + + if (result.IsEmpty()) + { + handler.SendSysMessage(CypherStrings.CommandTargetobjnotfound); + return true; + } + + bool found = false; + float x, y, z, o; + ulong guidLow; + uint id, phaseId, phaseGroup; + ushort mapId; + uint poolId; + + do + { + guidLow = result.Read(0); + id = result.Read(1); + x = result.Read(2); + y = result.Read(3); + z = result.Read(4); + o = result.Read(5); + mapId = result.Read(6); + phaseId = result.Read(7); + phaseGroup = result.Read(8); + poolId = Global.PoolMgr.IsPartOfAPool(guidLow); + if (poolId == 0 || Global.PoolMgr.IsSpawnedObject(guidLow)) + found = true; + } while (result.NextRow() && !found); + + if (!found) + { + handler.SendSysMessage(CypherStrings.GameobjectNotExist, id); + return false; + } + + GameObjectTemplate objectInfo = Global.ObjectMgr.GetGameObjectTemplate(id); + + if (objectInfo == null) + { + handler.SendSysMessage(CypherStrings.GameobjectNotExist, id); + return false; + } + + GameObject target = handler.GetObjectFromPlayerMapByDbGuid(guidLow); + + handler.SendSysMessage(CypherStrings.GameobjectDetail, guidLow, objectInfo.name, guidLow, id, x, y, z, mapId, o, phaseId, phaseGroup); + + if (target) + { + int curRespawnDelay = (int)(target.GetRespawnTimeEx() - Time.UnixTime); + if (curRespawnDelay < 0) + curRespawnDelay = 0; + + string curRespawnDelayStr = Time.secsToTimeString((uint)curRespawnDelay, true); + string defRespawnDelayStr = Time.secsToTimeString(target.GetRespawnDelay(), true); + + handler.SendSysMessage(CypherStrings.CommandRawpawntimes, defRespawnDelayStr, curRespawnDelayStr); + } + return true; + } + + [Command("turn", RBACPermissions.CommandGobjectTurn)] + static bool HandleGameObjectTurnCommand(StringArguments args, CommandHandler handler) + { + // number or [name] Shift-click form |color|Hgameobject:go_id|h[name]|h|r + string id = handler.extractKeyFromLink(args, "Hgameobject"); + if (string.IsNullOrEmpty(id)) + return false; + + ulong guidLow = ulong.Parse(id); + if (guidLow == 0) + return false; + + GameObject obj = handler.GetObjectFromPlayerMapByDbGuid(guidLow); + if (!obj) + { + handler.SendSysMessage(CypherStrings.CommandObjnotfound, guidLow); + return false; + } + + string orientation = args.NextString(); + float oz = 0.0f, oy = 0.0f, ox = 0.0f; + if (!orientation.IsEmpty()) + { + oz = float.Parse(orientation); + + orientation = args.NextString(); + if (!orientation.IsEmpty()) + { + oy = float.Parse(orientation); + orientation = args.NextString(); + if (!orientation.IsEmpty()) + ox = float.Parse(orientation); + } + } + else + { + Player player = handler.GetPlayer(); + oz = player.GetOrientation(); + } + + obj.Relocate(obj.GetPositionX(), obj.GetPositionY(), obj.GetPositionZ()); + obj.RelocateStationaryPosition(obj.GetPositionX(), obj.GetPositionY(), obj.GetPositionZ(), obj.GetOrientation()); + obj.SetWorldRotationAngles(oz, oy, ox); + obj.DestroyForNearbyPlayers(); + obj.UpdateObjectVisibility(); + + obj.SaveToDB(); + + handler.SendSysMessage(CypherStrings.CommandTurnobjmessage, obj.GetSpawnId(), obj.GetGoInfo().name, obj.GetGUID().ToString(), obj.GetOrientation()); + + return true; + } + + [Command("info", RBACPermissions.CommandGobjectInfo)] + static bool HandleGameObjectInfoCommand(StringArguments args, CommandHandler handler) + { + uint entry = 0; + GameObjectTypes type = 0; + uint displayId = 0; + string name; + uint lootId = 0; + + if (args.Empty()) + return false; + + string param1 = handler.extractKeyFromLink(args, "Hgameobject_entry"); + if (param1.IsEmpty()) + return false; + + if (param1.Equals("guid")) + { + string cValue = handler.extractKeyFromLink(args, "Hgameobject"); + if (cValue.IsEmpty()) + return false; + + ulong guidLow = ulong.Parse(cValue); + GameObjectData data = Global.ObjectMgr.GetGOData(guidLow); + if (data == null) + return false; + entry = data.id; + } + else + entry = uint.Parse(param1); + + GameObjectTemplate gameObjectInfo = Global.ObjectMgr.GetGameObjectTemplate(entry); + if (gameObjectInfo == null) + return false; + + type = gameObjectInfo.type; + displayId = gameObjectInfo.displayId; + name = gameObjectInfo.name; + lootId = gameObjectInfo.GetLootId(); + + handler.SendSysMessage(CypherStrings.GoinfoEntry, entry); + handler.SendSysMessage(CypherStrings.GoinfoType, type); + handler.SendSysMessage(CypherStrings.GoinfoLootid, lootId); + handler.SendSysMessage(CypherStrings.GoinfoDisplayid, displayId); + handler.SendSysMessage(CypherStrings.GoinfoName, name); + handler.SendSysMessage(CypherStrings.GoinfoSize, gameObjectInfo.size); + + GameObjectTemplateAddon addon = Global.ObjectMgr.GetGameObjectTemplateAddon(entry); + if (addon != null) + handler.SendSysMessage(CypherStrings.GoinfoAddon, addon.faction, addon.flags); + + GameObjectDisplayInfoRecord modelInfo = CliDB.GameObjectDisplayInfoStorage.LookupByKey(displayId); + if (modelInfo != null) + handler.SendSysMessage(CypherStrings.GoinfoModel, modelInfo.GeoBoxMax.X, modelInfo.GeoBoxMax.Y, modelInfo.GeoBoxMax.Z, modelInfo.GeoBoxMin.X, modelInfo.GeoBoxMin.Y, modelInfo.GeoBoxMin.Z); + + return true; + } + + [CommandGroup("add", RBACPermissions.CommandGobjectAdd)] + class AddCommands + { + [Command("", RBACPermissions.CommandGobjectAdd)] + static bool HandleGameObjectAddCommand(StringArguments args, CommandHandler handler) + { + if (args.Empty()) + return false; + + // number or [name] Shift-click form |color|Hgameobject_entry:go_id|h[name]|h|r + string id = handler.extractKeyFromLink(args, "Hgameobject_entry"); + if (string.IsNullOrEmpty(id)) + return false; + + uint objectId = uint.Parse(id); + if (objectId == 0) + return false; + + uint spawntimeSecs = args.NextUInt32(); + + GameObjectTemplate objectInfo = Global.ObjectMgr.GetGameObjectTemplate(objectId); + if (objectInfo == null) + { + handler.SendSysMessage(CypherStrings.GameobjectNotExist, objectId); + return false; + } + + if (objectInfo.displayId != 0 && !CliDB.GameObjectDisplayInfoStorage.ContainsKey(objectInfo.displayId)) + { + // report to DB errors log as in loading case + Log.outError(LogFilter.Sql, "Gameobject (Entry {0} GoType: {1}) have invalid displayId ({2}), not spawned.", objectId, objectInfo.type, objectInfo.displayId); + handler.SendSysMessage(CypherStrings.GameobjectHaveInvalidData, objectId); + return false; + } + + Player player = handler.GetPlayer(); + Map map = player.GetMap(); + + GameObject obj = new GameObject(); + + Quaternion rotation = new Quaternion(Matrix3.fromEulerAnglesZYX(player.GetOrientation(), 0.0f, 0.0f)); + if (!obj.Create(objectInfo.entry, map, 0, player, rotation, 255, GameObjectState.Ready)) + return false; + + obj.CopyPhaseFrom(player); + + if (spawntimeSecs != 0) + { + obj.SetRespawnTime((int)spawntimeSecs); + } + + // fill the gameobject data and save to the db + obj.SaveToDB(map.GetId(), (byte)(1 << (int)map.GetSpawnMode()), player.GetPhaseMask()); + ulong spawnId = obj.GetSpawnId(); + + // this will generate a new guid if the object is in an instance + if (!obj.LoadGameObjectFromDB(spawnId, map)) + return false; + + // TODO: is it really necessary to add both the real and DB table guid here ? + Global.ObjectMgr.AddGameObjectToGrid(spawnId, Global.ObjectMgr.GetGOData(spawnId)); + handler.SendSysMessage(CypherStrings.GameobjectAdd, objectId, objectInfo.name, spawnId, player.GetPositionX(), player.GetPositionY(), player.GetPositionZ()); + return true; + } + + [Command("temp", RBACPermissions.CommandGobjectAddTemp)] + static bool HandleGameObjectAddTempCommand(StringArguments args, CommandHandler handler) + { + if (args.Empty()) + return false; + + uint id = args.NextUInt32(); + if (id == 0) + return false; + + Player player = handler.GetPlayer(); + + uint spawntime = args.NextUInt32(); + uint spawntm = 300; + + if (spawntime != 0) + spawntm = spawntime; + + Quaternion rotation = new Quaternion(Matrix3.fromEulerAnglesZYX(player.GetOrientation(), 0.0f, 0.0f)); + + if (Global.ObjectMgr.GetGameObjectTemplate(id) == null) + { + handler.SendSysMessage(CypherStrings.GameobjectNotExist, id); + return false; + } + + player.SummonGameObject(id, player, rotation, spawntm); + + return true; + } + } + + [CommandGroup("set", RBACPermissions.CommandGobjectSet)] + class SetCommands + { + [Command("phase", RBACPermissions.CommandGobjectSetPhase)] + static bool HandleGameObjectSetPhaseCommand(StringArguments args, CommandHandler handler) + { + /*// number or [name] Shift-click form |color|Hgameobject:go_id|h[name]|h|r + string id = handler.extractKeyFromLink(args, "Hgameobject"); + if (string.IsNullOrEmpty(id)) + return false; + + ulong guidLow = ulong.Parse(id); + if (guidLow == 0) + return false; + + GameObject obj = null; + + // by DB guid + GameObjectData gameObjectData = Global.ObjectMgr.GetGOData(guidLow); + if (gameObjectData != null) + obj = handler.GetObjectGlobalyWithGuidOrNearWithDbGuid(guidLow, gameObjectData.id); + + if (!obj) + { + handler.SendSysMessage(CypherStrings.CommandObjnotfound, guidLow); + return false; + } + + uint phaseMask = args.NextUInt32(); + if (phaseMask == 0) + { + handler.SendSysMessage(CypherStrings.BadValue); + return false; + } + + obj.SetPhaseMask(phaseMask, true); + obj.SaveToDB();*/ + return true; + } + + [Command("state", RBACPermissions.CommandGobjectSetState)] + static bool HandleGameObjectSetStateCommand(StringArguments args, CommandHandler handler) + { + // number or [name] Shift-click form |color|Hgameobject:go_id|h[name]|h|r + string id = handler.extractKeyFromLink(args, "Hgameobject"); + if (string.IsNullOrEmpty(id)) + return false; + + ulong guidLow = ulong.Parse(id); + if (guidLow == 0) + return false; + + GameObject obj = handler.GetObjectFromPlayerMapByDbGuid(guidLow); + if (!obj) + { + handler.SendSysMessage(CypherStrings.CommandObjnotfound, guidLow); + return false; + } + + string type = args.NextString(); + if (string.IsNullOrEmpty(type)) + return false; + + int objectType = int.Parse(type); + if (objectType < 0) + { + if (objectType == -1) + obj.SendGameObjectDespawn(); + else if (objectType == -2) + return false; + return true; + } + + string state = args.NextString(); + if (string.IsNullOrEmpty(state)) + return false; + + uint objectState = uint.Parse(state); + + if (objectType < 4) + obj.SetByteValue(GameObjectFields.Bytes1, (byte)objectType, (byte)objectState); + else if (objectType == 4) + obj.SendCustomAnim(objectState); + + handler.SendSysMessage("Set gobject type {0} state {1}", objectType, objectState); + return true; + } + } + } +} diff --git a/Game/Chat/Commands/GoCommands.cs b/Game/Chat/Commands/GoCommands.cs new file mode 100644 index 000000000..5417f5ccf --- /dev/null +++ b/Game/Chat/Commands/GoCommands.cs @@ -0,0 +1,622 @@ +/* + * Copyright (C) 2012-2017 CypherCore + * + * 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, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +using Framework.Constants; +using Framework.Database; +using Framework.IO; +using Game.DataStorage; +using Game.Entities; +using Game.Maps; +using Game.SupportSystem; +using System; +using System.Collections.Generic; +using System.Diagnostics.Contracts; + +namespace Game.Chat.Commands +{ + [CommandGroup("go", RBACPermissions.CommandGo)] + class GoCommands + { + [Command("creature", RBACPermissions.CommandGoCreature)] + static bool HandleGoCreatureCommand(StringArguments args, CommandHandler handler) + { + if (args.Empty()) + return false; + + Player player = handler.GetSession().GetPlayer(); + + // "id" or number or [name] Shift-click form |color|Hcreature_entry:creature_id|h[name]|h|r + string param1 = handler.extractKeyFromLink(args, "Hcreature"); + if (string.IsNullOrEmpty(param1)) + return false; + + string whereClause = ""; + + // User wants to teleport to the NPC's template entry + if (param1.Equals("id")) + { + // Get the "creature_template.entry" + // number or [name] Shift-click form |color|Hcreature_entry:creature_id|h[name]|h|r + string idStr = handler.extractKeyFromLink(args, "Hcreature_entry"); + if (string.IsNullOrEmpty(idStr)) + return false; + + int entry = int.Parse(idStr); + if (entry == 0) + return false; + + whereClause += "WHERE id = '" + entry + '\''; + } + else + { + ulong guidLow = ulong.Parse(param1); + + // Number is invalid - maybe the user specified the mob's name + if (guidLow == 0) + { + string name = param1; + whereClause += ", creature_template WHERE creature.id = creature_template.entry AND creature_template.name LIKE '" + name + '\''; + } + else + whereClause += "WHERE guid = '" + guidLow + '\''; + } + + SQLResult result = DB.World.Query("SELECT position_x, position_y, position_z, orientation, map FROM creature {0}", whereClause); + if (result.IsEmpty()) + { + handler.SendSysMessage(CypherStrings.CommandGocreatnotfound); + return false; + } + if (result.GetRowCount() > 1) + handler.SendSysMessage(CypherStrings.CommandGocreatmultiple); + + float x = result.Read(0); + float y = result.Read(1); + float z = result.Read(2); + float o = result.Read(3); + uint mapId = result.Read(4); + + if (!GridDefines.IsValidMapCoord(mapId, x, y, z, o) || Global.ObjectMgr.IsTransportMap(mapId)) + { + handler.SendSysMessage(CypherStrings.InvalidTargetCoord, x, y, mapId); + return false; + } + + // stop flight if need + if (player.IsInFlight()) + { + player.GetMotionMaster().MovementExpired(); + player.CleanupAfterTaxiFlight(); + } + // save only in non-flight case + else + player.SaveRecallPosition(); + + player.TeleportTo(mapId, x, y, z, o); + + return true; + } + + [Command("graveyard", RBACPermissions.CommandGoGraveyard)] + static bool HandleGoGraveyardCommand(StringArguments args, CommandHandler handler) + { + Player player = handler.GetSession().GetPlayer(); + + if (args.Empty()) + return false; + + uint graveyardId = args.NextUInt32(); + if (graveyardId == 0) + return false; + + WorldSafeLocsRecord gy = CliDB.WorldSafeLocsStorage.LookupByKey(graveyardId); + if (gy == null) + { + handler.SendSysMessage(CypherStrings.CommandGraveyardnoexist, graveyardId); + return false; + } + + if (!GridDefines.IsValidMapCoord(gy.MapID, gy.Loc.X, gy.Loc.Y, gy.Loc.Z)) + { + handler.SendSysMessage(CypherStrings.InvalidTargetCoord, gy.Loc.X, gy.Loc.Y, gy.MapID); + return false; + } + + // stop flight if need + if (player.IsInFlight()) + { + player.GetMotionMaster().MovementExpired(); + player.CleanupAfterTaxiFlight(); + } + // save only in non-flight case + else + player.SaveRecallPosition(); + + player.TeleportTo(gy.MapID, gy.Loc.X, gy.Loc.Y, gy.Loc.Z, (gy.Facing * MathFunctions.PI) / 180); // Orientation is initially in degrees + return true; + } + + //teleport to grid + [Command("grid", RBACPermissions.CommandGoGrid)] + static bool HandleGoGridCommand(StringArguments args, CommandHandler handler) + { + if (args.Empty()) + return false; + + Player player = handler.GetSession().GetPlayer(); + + string gridX = args.NextString(); + string gridY = args.NextString(); + string id = args.NextString(); + + if (string.IsNullOrEmpty(gridX) || string.IsNullOrEmpty(gridY)) + return false; + + uint mapId = !string.IsNullOrEmpty(id) ? uint.Parse(id) : player.GetMapId(); + + // center of grid + float x = (float.Parse(gridX) - MapConst.CenterGridId + 0.5f) * MapConst.SizeofGrids; + float y = (float.Parse(gridY) - MapConst.CenterGridId + 0.5f) * MapConst.SizeofGrids; + + if (!GridDefines.IsValidMapCoord(mapId, x, y)) + { + handler.SendSysMessage(CypherStrings.InvalidTargetCoord, x, y, mapId); + return false; + } + + // stop flight if need + if (player.IsInFlight()) + { + player.GetMotionMaster().MovementExpired(); + player.CleanupAfterTaxiFlight(); + } + // save only in non-flight case + else + player.SaveRecallPosition(); + + Map map = Global.MapMgr.CreateBaseMap(mapId); + float z = Math.Max(map.GetHeight(x, y, MapConst.MaxHeight), map.GetWaterLevel(x, y)); + + player.TeleportTo(mapId, x, y, z, player.GetOrientation()); + return true; + } + + //teleport to gameobject + [Command("object", RBACPermissions.CommandGoObject)] + static bool HandleGoObjectCommand(StringArguments args, CommandHandler handler) + { + if (args.Empty()) + return false; + + Player player = handler.GetSession().GetPlayer(); + + // number or [name] Shift-click form |color|Hgameobject:go_guid|h[name]|h|r + string id = handler.extractKeyFromLink(args, "Hgameobject"); + if (string.IsNullOrEmpty(id)) + return false; + + ulong guidLow = ulong.Parse(id); + if (guidLow == 0) + return false; + + float x, y, z, o; + uint mapId; + + // by DB guid + GameObjectData goData = Global.ObjectMgr.GetGOData(guidLow); + if (goData != null) + { + x = goData.posX; + y = goData.posY; + z = goData.posZ; + o = goData.orientation; + mapId = goData.mapid; + } + else + { + handler.SendSysMessage(CypherStrings.CommandGoobjnotfound); + return false; + } + + if (!GridDefines.IsValidMapCoord(mapId, x, y, z, o) || Global.ObjectMgr.IsTransportMap(mapId)) + { + handler.SendSysMessage(CypherStrings.InvalidTargetCoord, x, y, mapId); + return false; + } + + // stop flight if need + if (player.IsInFlight()) + { + player.GetMotionMaster().MovementExpired(); + player.CleanupAfterTaxiFlight(); + } + // save only in non-flight case + else + player.SaveRecallPosition(); + + player.TeleportTo(mapId, x, y, z, o); + return true; + } + + [Command("quest", RBACPermissions.CommandGoQuest)] + static bool HandleGoQuestCommand(StringArguments args, CommandHandler handler) + { + if (args.Empty()) + return false; + + Player player = handler.GetSession().GetPlayer(); + + string id = handler.extractKeyFromLink(args, "Hquest"); + if (string.IsNullOrEmpty(id)) + return false; + + uint questID = uint.Parse(id); + if (questID == 0) + return false; + + if (Global.ObjectMgr.GetQuestTemplate(questID) == null) + { + handler.SendSysMessage(CypherStrings.CommandQuestNotfound, questID); + return false; + } + + float x, y, z = 0; + uint mapId = 0; + + var poiData = Global.ObjectMgr.GetQuestPOIList(questID); + if (poiData != null) + { + var data = poiData[0]; + + mapId = (uint)data.MapID; + + x = data.points[0].X; + y = data.points[0].Y; + } + else + { + handler.SendSysMessage(CypherStrings.CommandQuestNotfound, questID); + return false; + } + + if (!GridDefines.IsValidMapCoord(mapId, x, y) || Global.ObjectMgr.IsTransportMap(mapId)) + { + handler.SendSysMessage(CypherStrings.InvalidTargetCoord, x, y, mapId); + return false; + } + + // stop flight if need + if (player.IsInFlight()) + { + player.GetMotionMaster().MovementExpired(); + player.CleanupAfterTaxiFlight(); + } + // save only in non-flight case + else + player.SaveRecallPosition(); + + Map map = Global.MapMgr.CreateBaseMap(mapId); + z = Math.Max(map.GetHeight(x, y, MapConst.MaxHeight), map.GetWaterLevel(x, y)); + + player.TeleportTo(mapId, x, y, z, 0.0f); + return true; + } + + [Command("taxinode", RBACPermissions.CommandGoTaxinode)] + static bool HandleGoTaxinodeCommand(StringArguments args, CommandHandler handler) + { + Player player = handler.GetSession().GetPlayer(); + + if (args.Empty()) + return false; + + string id = handler.extractKeyFromLink(args, "Htaxinode"); + if (string.IsNullOrEmpty(id)) + return false; + + uint nodeId = uint.Parse(id); + if (nodeId == 0) + return false; + + TaxiNodesRecord node = CliDB.TaxiNodesStorage.LookupByKey(nodeId); + if (node == null) + { + handler.SendSysMessage(CypherStrings.CommandGotaxinodenotfound, nodeId); + return false; + } + + if ((node.Pos.X == 0.0f && node.Pos.Y == 0.0f && node.Pos.Z == 0.0f) || + !GridDefines.IsValidMapCoord(node.MapID, node.Pos.X, node.Pos.Y, node.Pos.Z)) + { + handler.SendSysMessage(CypherStrings.InvalidTargetCoord, node.Pos.X, node.Pos.Y, node.MapID); + return false; + } + + // stop flight if need + if (player.IsInFlight()) + { + player.GetMotionMaster().MovementExpired(); + player.CleanupAfterTaxiFlight(); + } + // save only in non-flight case + else + player.SaveRecallPosition(); + + player.TeleportTo(node.MapID, node.Pos.X, node.Pos.Y, node.Pos.Z, player.GetOrientation()); + return true; + } + + [Command("trigger", RBACPermissions.CommandGoTrigger)] + static bool HandleGoTriggerCommand(StringArguments args, CommandHandler handler) + { + Player player = handler.GetSession().GetPlayer(); + + if (args.Empty()) + return false; + + uint areaTriggerId = args.NextUInt32(); + if (areaTriggerId == 0) + return false; + + AreaTriggerRecord at = CliDB.AreaTriggerStorage.LookupByKey(areaTriggerId); + if (at == null) + { + handler.SendSysMessage(CypherStrings.CommandGoareatrnotfound, areaTriggerId); + return false; + } + + if (!GridDefines.IsValidMapCoord(at.MapID, at.Pos.X, at.Pos.Y, at.Pos.Z)) + { + handler.SendSysMessage(CypherStrings.InvalidTargetCoord, at.Pos.X, at.Pos.Y, at.MapID); + return false; + } + + // stop flight if need + if (player.IsInFlight()) + { + player.GetMotionMaster().MovementExpired(); + player.CleanupAfterTaxiFlight(); + } + // save only in non-flight case + else + player.SaveRecallPosition(); + + player.TeleportTo(at.MapID, at.Pos.X, at.Pos.Y, at.Pos.Z, player.GetOrientation()); + return true; + } + + //teleport at coordinates + [Command("zonexy", RBACPermissions.CommandGoZonexy)] + static bool HandleGoZoneXYCommand(StringArguments args, CommandHandler handler) + { + if (args.Empty()) + return false; + + Player player = handler.GetSession().GetPlayer(); + + string zoneX = args.NextString(); + string zoneY = args.NextString(); + + string id = handler.extractKeyFromLink(args, "Harea"); // string or [name] Shift-click form |color|Harea:area_id|h[name]|h|r + + if (string.IsNullOrEmpty(zoneX) || string.IsNullOrEmpty(zoneY)) + return false; + + float x = float.Parse(zoneX); + float y = float.Parse(zoneY); + + // prevent accept wrong numeric args + if ((x == 0.0f && zoneX[0] != '0') || (y == 0.0f && zoneY[0] != '0')) + return false; + + uint areaId = !string.IsNullOrEmpty(id) ? uint.Parse(id) : player.GetZoneId(); + + AreaTableRecord areaEntry = CliDB.AreaTableStorage.LookupByKey(areaId); + + if (x < 0 || x > 100 || y < 0 || y > 100 || areaEntry == null) + { + handler.SendSysMessage(CypherStrings.InvalidZoneCoord, x, y, areaId); + return false; + } + + // update to parent zone if exist (client map show only zones without parents) + AreaTableRecord zoneEntry = areaEntry.ParentAreaID != 0 ? CliDB.AreaTableStorage.LookupByKey(areaEntry.ParentAreaID) : areaEntry; + Contract.Assert(zoneEntry != null); + + Map map = Global.MapMgr.CreateBaseMap(zoneEntry.MapId); + + if (map.Instanceable()) + { + handler.SendSysMessage(CypherStrings.InvalidZoneMap, areaId, areaEntry.AreaName[handler.GetSessionDbcLocale()], map.GetId(), map.GetMapName()); + return false; + } + + Global.DB2Mgr.Zone2MapCoordinates(areaEntry.ParentAreaID != 0 ? areaEntry.ParentAreaID : areaId, ref x, ref y); + + if (!GridDefines.IsValidMapCoord(zoneEntry.MapId, x, y)) + { + handler.SendSysMessage(CypherStrings.InvalidTargetCoord, x, y, zoneEntry.MapId); + return false; + } + + // stop flight if need + if (player.IsInFlight()) + { + player.GetMotionMaster().MovementExpired(); + player.CleanupAfterTaxiFlight(); + } + // save only in non-flight case + else + player.SaveRecallPosition(); + + float z = Math.Max(map.GetHeight(x, y, MapConst.MaxHeight), map.GetWaterLevel(x, y)); + + player.TeleportTo(zoneEntry.MapId, x, y, z, player.GetOrientation()); + return true; + } + + //teleport at coordinates, including Z and orientation + [Command("xyz", RBACPermissions.CommandGoXyz)] + static bool HandleGoXYZCommand(StringArguments args, CommandHandler handler) + { + if (args.Empty()) + return false; + + Player player = handler.GetSession().GetPlayer(); + + string goX = args.NextString(); + string goY = args.NextString(); + string goZ = args.NextString(); + string id = args.NextString(); + string port = args.NextString(); + + if (goX.IsEmpty() || goY.IsEmpty()) + return false; + + float x = float.Parse(goX); + float y = float.Parse(goY); + float z; + float ort = !port.IsEmpty() ? float.Parse(port) : player.GetOrientation(); + uint mapId = !id.IsEmpty() ? uint.Parse(id) : player.GetMapId(); + + if (!goZ.IsEmpty()) + { + z = float.Parse(goZ); + if (!GridDefines.IsValidMapCoord(mapId, x, y, z)) + { + handler.SendSysMessage(CypherStrings.InvalidTargetCoord, x, y, mapId); + return false; + } + } + else + { + if (!GridDefines.IsValidMapCoord(mapId, x, y)) + { + handler.SendSysMessage(CypherStrings.InvalidTargetCoord, x, y, mapId); + return false; + } + Map map = Global.MapMgr.CreateBaseMap(mapId); + z = Math.Max(map.GetHeight(x, y, MapConst.MaxHeight), map.GetWaterLevel(x, y)); + } + + // stop flight if need + if (player.IsInFlight()) + { + player.GetMotionMaster().MovementExpired(); + player.CleanupAfterTaxiFlight(); + } + // save only in non-flight case + else + player.SaveRecallPosition(); + + player.TeleportTo(mapId, x, y, z, ort); + return true; + } + + [Command("bugticket", RBACPermissions.CommandGoBugTicket)] + static bool HandleGoBugTicketCommand(StringArguments args, CommandHandler handler) + { + return HandleGoTicketCommand(args, handler); + } + + [Command("complaintticket", RBACPermissions.CommandGoComplaintTicket)] + static bool HandleGoComplaintTicketCommand(StringArguments args, CommandHandler handler) + { + return HandleGoTicketCommand(args, handler); + } + + [Command("suggestionticket", RBACPermissions.CommandGoSuggestionTicket)] + static bool HandleGoSuggestionTicketCommand(StringArguments args, CommandHandler handler) + { + return HandleGoTicketCommand(args, handler); + } + + static bool HandleGoTicketCommand(StringArguments args, CommandHandler handler)where T : Ticket + { + if (args.Empty()) + return false; + + uint ticketId = args.NextUInt32(); + if (ticketId == 0) + return false; + + T ticket = Global.SupportMgr.GetTicket(ticketId); + if (ticket == null) + { + handler.SendSysMessage(CypherStrings.CommandTicketnotexist); + return true; + } + + Player player = handler.GetSession().GetPlayer(); + if (player.IsInFlight()) + { + player.GetMotionMaster().MovementExpired(); + player.CleanupAfterTaxiFlight(); + } + else + player.SaveRecallPosition(); + + ticket.TeleportTo(player); + return true; + } + + [Command("offset", RBACPermissions.CommandGoOffset)] + static bool HandleGoOffsetCommand(StringArguments args, CommandHandler handler) + { + if (args.Empty()) + return false; + + Player player = handler.GetSession().GetPlayer(); + + string goX = args.NextString(); + string goY = args.NextString(); + string goZ = args.NextString(); + string id = args.NextString(); + string port = args.NextString(); + + float x, y, z, o; + player.GetPosition(out x, out y, out z, out o); + if (!goX.IsEmpty()) + x += float.Parse(goX); + if (!goY.IsEmpty()) + y += float.Parse(goY); + if (!goZ.IsEmpty()) + z += float.Parse(goZ); + if (!port.IsEmpty()) + o += float.Parse(port); + + if (!GridDefines.IsValidMapCoord(x, y, z, o)) + { + handler.SendSysMessage(CypherStrings.InvalidTargetCoord, x, y, player.GetMapId()); + return false; + } + + // stop flight if need + if (player.IsInFlight()) + { + player.GetMotionMaster().MovementExpired(); + player.CleanupAfterTaxiFlight(); + } + // save only in non-flight case + else + player.SaveRecallPosition(); + + player.TeleportTo(player.GetMapId(), x, y, z, o); + return true; + } + } +} diff --git a/Game/Chat/Commands/GroupCommands.cs b/Game/Chat/Commands/GroupCommands.cs new file mode 100644 index 000000000..c8b1db37b --- /dev/null +++ b/Game/Chat/Commands/GroupCommands.cs @@ -0,0 +1,351 @@ +/* + * Copyright (C) 2012-2017 CypherCore + * + * 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, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +using Framework.Constants; +using Framework.Database; +using Framework.IO; +using Game.DataStorage; +using Game.DungeonFinding; +using Game.Entities; +using Game.Groups; +using Game.Maps; +using System; +using System.Collections.Generic; + +namespace Game.Chat +{ + [CommandGroup("group", RBACPermissions.CommandGroup)] + class GroupCommands + { + // Summon group of player + [Command("summon", RBACPermissions.CommandGroupSummon)] + static bool HandleGroupSummonCommand(StringArguments args, CommandHandler handler) + { + Player target; + if (!handler.extractPlayerTarget(args, out target)) + return false; + + // check online security + if (handler.HasLowerSecurity(target, ObjectGuid.Empty)) + return false; + + Group group = target.GetGroup(); + + string nameLink = handler.GetNameLink(target); + + if (!group) + { + handler.SendSysMessage(CypherStrings.NotInGroup, nameLink); + return false; + } + + Player gmPlayer = handler.GetSession().GetPlayer(); + Group gmGroup = gmPlayer.GetGroup(); + Map gmMap = gmPlayer.GetMap(); + bool toInstance = gmMap.Instanceable(); + + // we are in instance, and can summon only player in our group with us as lead + if (toInstance && ( + !gmGroup || group.GetLeaderGUID() != gmPlayer.GetGUID() || + gmGroup.GetLeaderGUID() != gmPlayer.GetGUID())) + // the last check is a bit excessive, but let it be, just in case + { + handler.SendSysMessage(CypherStrings.CannotSummonToInst); + return false; + } + + for (GroupReference refe = group.GetFirstMember(); refe != null; refe = refe.next()) + { + Player player = refe.GetSource(); + + if (!player || player == gmPlayer || player.GetSession() == null) + continue; + + // check online security + if (handler.HasLowerSecurity(player, ObjectGuid.Empty)) + return false; + + string plNameLink = handler.GetNameLink(player); + + if (player.IsBeingTeleported()) + { + handler.SendSysMessage(CypherStrings.IsTeleported, plNameLink); + return false; + } + + if (toInstance) + { + Map playerMap = player.GetMap(); + + if (playerMap.Instanceable() && playerMap.GetInstanceId() != gmMap.GetInstanceId()) + { + // cannot summon from instance to instance + handler.SendSysMessage(CypherStrings.CannotSummonToInst, plNameLink); + return false; + } + } + + handler.SendSysMessage(CypherStrings.Summoning, plNameLink, ""); + if (handler.needReportToTarget(player)) + player.SendSysMessage(CypherStrings.SummonedBy, handler.GetNameLink()); + + // stop flight if need + if (player.IsInFlight()) + { + player.GetMotionMaster().MovementExpired(); + player.CleanupAfterTaxiFlight(); + } + // save only in non-flight case + else + player.SaveRecallPosition(); + + // before GM + float x, y, z; + gmPlayer.GetClosePoint(out x, out y, out z, player.GetObjectSize()); + player.TeleportTo(gmPlayer.GetMapId(), x, y, z, player.GetOrientation()); + } + + return true; + } + + [Command("leader", RBACPermissions.CommandGroupLeader)] + static bool HandleGroupLeaderCommand(StringArguments args, CommandHandler handler) + { + Player player = null; + Group group = null; + ObjectGuid guid = ObjectGuid.Empty; + string nameStr = args.NextString(); + + if (!handler.GetPlayerGroupAndGUIDByName(nameStr, out player, out group, out guid)) + return false; + + if (!group) + { + handler.SendSysMessage(CypherStrings.GroupNotInGroup, player.GetName()); + return false; + } + + if (group.GetLeaderGUID() != guid) + { + group.ChangeLeader(guid); + group.SendUpdate(); + } + + return true; + } + + [Command("disband", RBACPermissions.CommandGroupDisband)] + static bool HandleGroupDisbandCommand(StringArguments args, CommandHandler handler) + { + Player player = null; + Group group = null; + ObjectGuid guid = ObjectGuid.Empty; + string nameStr = args.NextString(); + + if (!handler.GetPlayerGroupAndGUIDByName(nameStr, out player, out group, out guid)) + return false; + + if (!group) + { + handler.SendSysMessage(CypherStrings.GroupNotInGroup, player.GetName()); + return false; + } + + group.Disband(); + return true; + } + + [Command("remove", RBACPermissions.CommandGroupRemove)] + static bool HandleGroupRemoveCommand(StringArguments args, CommandHandler handler) + { + Player player = null; + Group group = null; + ObjectGuid guid = ObjectGuid.Empty; + string nameStr = args.NextString(); + + if (!handler.GetPlayerGroupAndGUIDByName(nameStr, out player, out group, out guid)) + return false; + + if (!group) + { + handler.SendSysMessage(CypherStrings.GroupNotInGroup, player.GetName()); + return false; + } + + group.RemoveMember(guid); + return true; + } + + [Command("join", RBACPermissions.CommandGroupJoin)] + static bool HandleGroupJoinCommand(StringArguments args, CommandHandler handler) + { + if (args.Empty()) + return false; + + Player playerSource = null; + Player playerTarget = null; + Group groupSource = null; + Group groupTarget = null; + ObjectGuid guidSource = ObjectGuid.Empty; + ObjectGuid guidTarget = ObjectGuid.Empty; + string nameplgrStr = args.NextString(); + string nameplStr = args.NextString(); + + if (!handler.GetPlayerGroupAndGUIDByName(nameplgrStr, out playerSource, out groupSource, out guidSource, true)) + return false; + + if (!groupSource) + { + handler.SendSysMessage(CypherStrings.GroupNotInGroup, playerSource.GetName()); + return false; + } + + if (!handler.GetPlayerGroupAndGUIDByName(nameplStr, out playerTarget, out groupTarget, out guidTarget, true)) + return false; + + if (groupTarget || playerTarget.GetGroup() == groupSource) + { + handler.SendSysMessage(CypherStrings.GroupAlreadyInGroup, playerTarget.GetName()); + return false; + } + + if (groupSource.IsFull()) + { + handler.SendSysMessage(CypherStrings.GroupFull); + return false; + } + + groupSource.AddMember(playerTarget); + groupSource.BroadcastGroupUpdate(); + handler.SendSysMessage(CypherStrings.GroupPlayerJoined, playerTarget.GetName(), playerSource.GetName()); + return true; + } + + [Command("list", RBACPermissions.CommandGroupList)] + static bool HandleGroupListCommand(StringArguments args, CommandHandler handler) + { + // Get ALL the variables! + Player playerTarget; + uint phase = 0; + ObjectGuid guidTarget; + string nameTarget; + string zoneName = ""; + string onlineState = ""; + + // Parse the guid to uint32... + ObjectGuid parseGUID = ObjectGuid.Create(HighGuid.Player, args.NextUInt64()); + + // ... and try to extract a player out of it. + if (ObjectManager.GetPlayerNameByGUID(parseGUID, out nameTarget)) + { + playerTarget = Global.ObjAccessor.FindPlayer(parseGUID); + guidTarget = parseGUID; + } + // If not, we return false and end right away. + else if (!handler.extractPlayerTarget(args, out playerTarget, out guidTarget, out nameTarget)) + return false; + + // Next, we need a group. So we define a group variable. + Group groupTarget = null; + + // We try to extract a group from an online player. + if (playerTarget) + groupTarget = playerTarget.GetGroup(); + + // If not, we extract it from the SQL. + if (!groupTarget) + { + PreparedStatement stmt = DB.Characters.GetPreparedStatement(CharStatements.SEL_GROUP_MEMBER); + stmt.AddValue(0, guidTarget.GetCounter()); + SQLResult resultGroup = DB.Characters.Query(stmt); + if (!resultGroup.IsEmpty()) + groupTarget = Global.GroupMgr.GetGroupByDbStoreId(resultGroup.Read(0)); + } + + // If both fails, players simply has no party. Return false. + if (!groupTarget) + { + handler.SendSysMessage(CypherStrings.GroupNotInGroup, nameTarget); + return false; + } + + // We get the group members after successfully detecting a group. + var members = groupTarget.GetMemberSlots(); + + // To avoid a cluster fuck, namely trying multiple queries to simply get a group member count... + handler.SendSysMessage(CypherStrings.GroupType, (groupTarget.isRaidGroup() ? "raid" : "party"), members.Count); + // ... we simply move the group type and member count print after retrieving the slots and simply output it's size. + + // While rather dirty codestyle-wise, it saves space (if only a little). For each member, we look several informations up. + foreach (var slot in members) + { + // Check for given flag and assign it to that iterator + string flags = ""; + if (slot.flags.HasAnyFlag(GroupMemberFlags.Assistant)) + flags = "Assistant"; + + if (slot.flags.HasAnyFlag(GroupMemberFlags.MainTank)) + { + if (!string.IsNullOrEmpty(flags)) + flags += ", "; + flags += "MainTank"; + } + + if (slot.flags.HasAnyFlag(GroupMemberFlags.MainAssist)) + { + if (!string.IsNullOrEmpty(flags)) + flags += ", "; + flags += "MainAssist"; + } + + if (string.IsNullOrEmpty(flags)) + flags = "None"; + + // Check if iterator is online. If is... + Player p = Global.ObjAccessor.FindPlayer(slot.guid); + if (p && p.IsInWorld) + { + // ... than, it prints information like "is online", where he is, etc... + onlineState = "online"; + phase = (!p.IsGameMaster() ? p.GetPhaseMask() : uint.MaxValue); + + AreaTableRecord area = CliDB.AreaTableStorage.LookupByKey(p.GetAreaId()); + if (area != null) + { + AreaTableRecord zone = CliDB.AreaTableStorage.LookupByKey(area.ParentAreaID); + if (zone != null) + zoneName = zone.AreaName[handler.GetSessionDbcLocale()]; + } + } + else + { + // ... else, everything is set to offline or neutral values. + zoneName = ""; + onlineState = "Offline"; + phase = 0; + } + + // Now we can print those informations for every single member of each group! + handler.SendSysMessage(CypherStrings.GroupPlayerNameGuid, slot.name, onlineState, + zoneName, phase, slot.guid.ToString(), flags, LFGQueue.GetRolesString(slot.roles)); + } + + // And finish after every iterator is done. + return true; + } + } +} diff --git a/Game/Chat/Commands/GuildCommands.cs b/Game/Chat/Commands/GuildCommands.cs new file mode 100644 index 000000000..43edf59ea --- /dev/null +++ b/Game/Chat/Commands/GuildCommands.cs @@ -0,0 +1,226 @@ +/* + * Copyright (C) 2012-2017 CypherCore + * + * 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, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +using Framework.Constants; +using Framework.IO; +using Game.Entities; +using Game.Guilds; + +namespace Game.Chat +{ + [CommandGroup("guild", RBACPermissions.CommandGuild, true)] + class GuildCommands + { + [Command("create", RBACPermissions.CommandGuildCreate, true)] + static bool HandleGuildCreateCommand(StringArguments args, CommandHandler handler) + { + if (args.Empty()) + return false; + + Player target; + if (!handler.extractPlayerTarget(args[0] != '"' ? args : null, out target)) + return false; + + string guildname = handler.extractQuotedArg(args.NextString()); + if (string.IsNullOrEmpty(guildname)) + return false; + + if (target.GetGuildId() != 0) + { + handler.SendSysMessage(CypherStrings.PlayerInGuild); + return true; + } + + Guild guild = new Guild(); + if (!guild.Create(target, guildname)) + { + handler.SendSysMessage(CypherStrings.GuildNotCreated); + return false; + } + + Global.GuildMgr.AddGuild(guild); + + return true; + } + + [Command("delete", RBACPermissions.CommandGuildDelete, true)] + static bool HandleGuildDeleteCommand(StringArguments args, CommandHandler handler) + { + string guildName = handler.extractQuotedArg(args.NextString()); + if (string.IsNullOrEmpty(guildName)) + return false; + + Guild guild = Global.GuildMgr.GetGuildByName(guildName); + if (guild == null) + return false; + + guild.Disband(); + return true; + } + + [Command("invite", RBACPermissions.CommandGuildInvite, true)] + static bool HandleGuildInviteCommand(StringArguments args, CommandHandler handler) + { + if (args.Empty()) + return false; + + Player target; + if (!handler.extractPlayerTarget(args[0] != '"' ? args : null, out target)) + return false; + + string guildName = handler.extractQuotedArg(args.NextString()); + if (string.IsNullOrEmpty(guildName)) + return false; + + Guild targetGuild = Global.GuildMgr.GetGuildByName(guildName); + if (targetGuild == null) + return false; + + targetGuild.AddMember(target.GetGUID()); + + return true; + } + + [Command("uninvite", RBACPermissions.CommandGuildUninvite, true)] + static bool HandleGuildUninviteCommand(StringArguments args, CommandHandler handler) + { + Player target; + ObjectGuid targetGuid = ObjectGuid.Empty; + if (!handler.extractPlayerTarget(args, out target, out targetGuid)) + return false; + + uint guildId = target != null ? target.GetGuildId() : Player.GetGuildIdFromDB(targetGuid); + if (guildId == 0) + return false; + + Guild targetGuild = Global.GuildMgr.GetGuildById(guildId); + if (targetGuild == null) + return false; + + targetGuild.DeleteMember(targetGuid, false, true, true); + return true; + } + + [Command("rank", RBACPermissions.CommandGuildRank, true)] + static bool HandleGuildRankCommand(StringArguments args, CommandHandler handler) + { + string nameStr; + string rankStr; + handler.extractOptFirstArg(args, out nameStr, out rankStr); + if (string.IsNullOrEmpty(rankStr)) + return false; + + Player target; + ObjectGuid targetGuid; + string target_name; + if (!handler.extractPlayerTarget(new StringArguments(nameStr), out target, out targetGuid, out target_name)) + return false; + + ulong guildId = target ? target.GetGuildId() : Player.GetGuildIdFromDB(targetGuid); + if (guildId == 0) + return false; + + Guild targetGuild = Global.GuildMgr.GetGuildById(guildId); + if (!targetGuild) + return false; + + byte newRank = byte.Parse(rankStr); + return targetGuild.ChangeMemberRank(targetGuid, newRank); + } + + [Command("rename", RBACPermissions.CommandGuildRename, true)] + static bool HandleGuildRenameCommand(StringArguments args, CommandHandler handler) + { + if (args.Empty()) + return false; + + string oldGuildStr = handler.extractQuotedArg(args.NextString()); + if (string.IsNullOrEmpty(oldGuildStr)) + { + handler.SendSysMessage(CypherStrings.BadValue); + return false; + } + + string newGuildStr = handler.extractQuotedArg(args.NextString()); + if (string.IsNullOrEmpty(newGuildStr)) + { + handler.SendSysMessage(CypherStrings.InsertGuildName); + return false; + } + + Guild guild = Global.GuildMgr.GetGuildByName(oldGuildStr); + if (!guild) + { + handler.SendSysMessage(CypherStrings.CommandCouldnotfind, oldGuildStr); + return false; + } + + if (Global.GuildMgr.GetGuildByName(newGuildStr)) + { + handler.SendSysMessage(CypherStrings.GuildRenameAlreadyExists, newGuildStr); + return false; + } + + if (!guild.SetName(newGuildStr)) + { + handler.SendSysMessage(CypherStrings.BadValue); + return false; + } + + handler.SendSysMessage(CypherStrings.GuildRenameDone, oldGuildStr, newGuildStr); + return true; + } + + [Command("info", RBACPermissions.CommandGuildInfo, true)] + static bool HandleGuildInfoCommand(StringArguments args, CommandHandler handler) + { + Guild guild = null; + Player target = handler.getSelectedPlayerOrSelf(); + + if (!args.Empty() && args[0] != '\0') + { + if (char.IsDigit(args[0])) + guild = Global.GuildMgr.GetGuildById(args.NextUInt64()); + else + guild = Global.GuildMgr.GetGuildByName(args.NextString()); + } + else if (target) + guild = target.GetGuild(); + + if (!guild) + return false; + + // Display Guild Information + handler.SendSysMessage(CypherStrings.GuildInfoName, guild.GetName(), guild.GetId()); // Guild Id + Name + + string guildMasterName; + if (ObjectManager.GetPlayerNameByGUID(guild.GetLeaderGUID(), out guildMasterName)) + handler.SendSysMessage(CypherStrings.GuildInfoGuildMaster, guildMasterName, guild.GetLeaderGUID().ToString()); // Guild Master + + // Format creation date + + var createdDateTime = Time.UnixTimeToDateTime(guild.GetCreatedDate()); + handler.SendSysMessage(CypherStrings.GuildInfoCreationDate, createdDateTime.ToLongDateString()); // Creation Date + handler.SendSysMessage(CypherStrings.GuildInfoMemberCount, guild.GetMembersCount()); // Number of Members + handler.SendSysMessage(CypherStrings.GuildInfoBankGold, guild.GetBankMoney() / 100 / 100); // Bank Gold (in gold coins) + handler.SendSysMessage(CypherStrings.GuildInfoLevel, guild.GetLevel()); // Level + handler.SendSysMessage(CypherStrings.GuildInfoMotd, guild.GetMOTD()); // Message of the Day + handler.SendSysMessage(CypherStrings.GuildInfoExtraInfo, guild.GetInfo()); // Extra Information + return true; + } + } +} diff --git a/Game/Chat/Commands/HonorCommands.cs b/Game/Chat/Commands/HonorCommands.cs new file mode 100644 index 000000000..619b32b87 --- /dev/null +++ b/Game/Chat/Commands/HonorCommands.cs @@ -0,0 +1,91 @@ +/* + * Copyright (C) 2012-2017 CypherCore + * + * 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, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +using Framework.Constants; +using Framework.IO; +using Game.Entities; + +namespace Game.Chat.Commands +{ + [CommandGroup("honor", RBACPermissions.CommandHonor)] + class HonorCommands + { + [Command("update", RBACPermissions.CommandHonorUpdate)] + static bool HandleHonorUpdateCommand(StringArguments args, CommandHandler handler) + { + Player target = handler.getSelectedPlayer(); + if (!target) + { + handler.SendSysMessage(CypherStrings.PlayerNotFound); + return false; + } + + // check online security + if (handler.HasLowerSecurity(target, ObjectGuid.Empty)) + return false; + + target.UpdateHonorFields(); + return true; + } + + [CommandGroup("add", RBACPermissions.CommandHonorAdd)] + class HonorAddCommands + { + [Command("", RBACPermissions.CommandHonorAdd)] + static bool HandleHonorAddCommand(StringArguments args, CommandHandler handler) + { + if (args.Empty()) + return false; + + Player target = handler.getSelectedPlayer(); + if (!target) + { + handler.SendSysMessage(CypherStrings.PlayerNotFound); + return false; + } + + // check online security + if (handler.HasLowerSecurity(target, ObjectGuid.Empty)) + return false; + + int amount = args.NextInt32(); + target.RewardHonor(null, 1, amount); + return true; + } + + [Command("kill", RBACPermissions.CommandHonorAddKill)] + static bool HandleHonorAddKillCommand(StringArguments args, CommandHandler handler) + { + Unit target = handler.getSelectedUnit(); + if (!target) + { + handler.SendSysMessage(CypherStrings.PlayerNotFound); + return false; + } + + // check online security + Player player = target.ToPlayer(); + if (player) + if (handler.HasLowerSecurity(player, ObjectGuid.Empty)) + return false; + + handler.GetPlayer().RewardHonor(target, 1); + return true; + } + } + } +} diff --git a/Game/Chat/Commands/InstanceCommands.cs b/Game/Chat/Commands/InstanceCommands.cs new file mode 100644 index 000000000..f2b80f233 --- /dev/null +++ b/Game/Chat/Commands/InstanceCommands.cs @@ -0,0 +1,273 @@ +/* + * Copyright (C) 2012-2017 CypherCore + * + * 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, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +using Framework.Constants; +using Framework.IO; +using Game.Entities; +using Game.Groups; +using Game.Maps; + +namespace Game.Chat +{ + [CommandGroup("instance", RBACPermissions.CommandInstance, true)] + class InstanceCommands + { + [Command("listbinds", RBACPermissions.CommandInstanceListbinds)] + static bool HandleInstanceListBinds(StringArguments args, CommandHandler handler) + { + Player player = handler.getSelectedPlayer(); + if (!player) + player = handler.GetSession().GetPlayer(); + + string format = "map: {0} inst: {1} perm: {2} diff: {3} canReset: {4} TTR: {5}"; + + uint counter = 0; + for (byte i = 0; i < (int)Difficulty.Max; ++i) + { + var binds = player.GetBoundInstances((Difficulty)i); + foreach (var pair in binds) + { + InstanceSave save = pair.Value.save; + string timeleft = Time.GetTimeString(save.GetResetTime() - Time.UnixTime); + handler.SendSysMessage(format, pair.Key, save.GetInstanceId(), pair.Value.perm ? "yes" : "no", save.GetDifficultyID(), save.CanReset() ? "yes" : "no", timeleft); + counter++; + } + } + handler.SendSysMessage("player binds: {0}", counter); + + counter = 0; + Group group = player.GetGroup(); + if (group) + { + for (byte i = 0; i < (int)Difficulty.Max; ++i) + { + var binds = group.GetBoundInstances((Difficulty)i); + foreach (var pair in binds) + { + InstanceSave save = pair.Value.save; + string timeleft = Time.GetTimeString(save.GetResetTime() - Time.UnixTime); + handler.SendSysMessage(format, pair.Key, save.GetInstanceId(), pair.Value.perm ? "yes" : "no", save.GetDifficultyID(), save.CanReset() ? "yes" : "no", timeleft); + counter++; + } + } + } + handler.SendSysMessage("group binds: {0}", counter); + + return true; + } + + [Command("unbind", RBACPermissions.CommandInstanceUnbind)] + static bool HandleInstanceUnbind(StringArguments args, CommandHandler handler) + { + if (args.Empty()) + return false; + + Player player = handler.getSelectedPlayer(); + if (!player) + player = handler.GetSession().GetPlayer(); + + string map = args.NextString(); + string pDiff = args.NextString(); + sbyte diff = -1; + if (string.IsNullOrEmpty(pDiff)) + diff = sbyte.Parse(pDiff); + ushort counter = 0; + ushort MapId = 0; + + if (map != "all") + { + MapId = ushort.Parse(map); + if (MapId == 0) + return false; + } + + for (byte i = 0; i < (int)Difficulty.Max; ++i) + { + var binds = player.GetBoundInstances((Difficulty)i); + foreach (var pair in binds) + { + InstanceSave save = pair.Value.save; + if (pair.Key != player.GetMapId() && (MapId == 0 || MapId == pair.Key) && (diff == -1 || diff == (sbyte)save.GetDifficultyID())) + { + string timeleft = Time.GetTimeString(save.GetResetTime() - Time.UnixTime); + handler.SendSysMessage("unbinding map: {0} inst: {1} perm: {2} diff: {3} canReset: {4} TTR: {5}", pair.Key, save.GetInstanceId(), + pair.Value.perm ? "yes" : "no", save.GetDifficultyID(), save.CanReset() ? "yes" : "no", timeleft); + player.UnbindInstance(pair.Key, (Difficulty)i); + counter++; + } + } + } + handler.SendSysMessage("instances unbound: {0}", counter); + + return true; + } + + [Command("stats", RBACPermissions.CommandInstanceStats, true)] + static bool HandleInstanceStats(StringArguments args, CommandHandler handler) + { + handler.SendSysMessage("instances loaded: {0}", Global.MapMgr.GetNumInstances()); + handler.SendSysMessage("players in instances: {0}", Global.MapMgr.GetNumPlayersInInstances()); + handler.SendSysMessage("instance saves: {0}", Global.InstanceSaveMgr.GetNumInstanceSaves()); + handler.SendSysMessage("players bound: {0}", Global.InstanceSaveMgr.GetNumBoundPlayersTotal()); + handler.SendSysMessage("groups bound: {0}", Global.InstanceSaveMgr.GetNumBoundGroupsTotal()); + + return true; + } + + [Command("savedata", RBACPermissions.CommandInstanceSavedata)] + static bool HandleInstanceSaveData(StringArguments args, CommandHandler handler) + { + Player player = handler.GetSession().GetPlayer(); + InstanceMap map = player.GetMap().ToInstanceMap(); + if (map == null) + { + handler.SendSysMessage("Map is not a dungeon."); + return false; + } + + if (map.GetInstanceScript() == null) + { + handler.SendSysMessage("Map has no instance data."); + return false; + } + + map.GetInstanceScript().SaveToDB(); + + return true; + } + + [Command("setbossstate", RBACPermissions.CommandInstanceSetBossState)] + static bool HandleInstanceSetBossState(StringArguments args, CommandHandler handler) + { + if (args.Empty()) + return false; + + string param1 = args.NextString(); + string param2 = args.NextString(); + string param3 = args.NextString(); + uint encounterId = 0; + EncounterState state = 0; + Player player = null; + + // Character name must be provided when using this from console. + if (string.IsNullOrEmpty(param2) || (string.IsNullOrEmpty(param3) && handler.GetSession() == null)) + { + handler.SendSysMessage(CypherStrings.CmdSyntax); + return false; + } + + if (string.IsNullOrEmpty(param3)) + player = handler.GetSession().GetPlayer(); + else + { + if (ObjectManager.NormalizePlayerName(ref param3)) + player = Global.ObjAccessor.FindPlayerByName(param3); + } + + if (!player) + { + handler.SendSysMessage(CypherStrings.PlayerNotFound); + return false; + } + + InstanceMap map = player.GetMap().ToInstanceMap(); + if (map == null) + { + handler.SendSysMessage(CypherStrings.NotDungeon); + return false; + } + + if (map.GetInstanceScript() == null) + { + handler.SendSysMessage(CypherStrings.NoInstanceData); + return false; + } + + encounterId = uint.Parse(param1); + state = (EncounterState)int.Parse(param2); + + // Reject improper values. + if (state > EncounterState.ToBeDecided || encounterId > map.GetInstanceScript().GetEncounterCount()) + { + handler.SendSysMessage(CypherStrings.BadValue); + return false; + } + + map.GetInstanceScript().SetBossState(encounterId, state); + handler.SendSysMessage(CypherStrings.CommandInstSetBossState, encounterId, state); + return true; + } + + [Command("getbossstate", RBACPermissions.CommandInstanceGetBossState)] + static bool HandleInstanceGetBossState(StringArguments args, CommandHandler handler) + { + if (args.Empty()) + return false; + + string param1 = args.NextString(); + string param2 = args.NextString(); + uint encounterId = 0; + Player player = null; + + // Character name must be provided when using this from console. + if (string.IsNullOrEmpty(param1) || (string.IsNullOrEmpty(param2) && handler.GetSession() == null)) + { + handler.SendSysMessage(CypherStrings.CmdSyntax); + return false; + } + + if (string.IsNullOrEmpty(param2)) + player = handler.GetSession().GetPlayer(); + else + { + if (ObjectManager.NormalizePlayerName(ref param2)) + player = Global.ObjAccessor.FindPlayerByName(param2); + } + + if (!player) + { + handler.SendSysMessage(CypherStrings.PlayerNotFound); + return false; + } + + InstanceMap map = player.GetMap().ToInstanceMap(); + if (map == null) + { + handler.SendSysMessage(CypherStrings.NotDungeon); + return false; + } + + if (map.GetInstanceScript() == null) + { + handler.SendSysMessage(CypherStrings.NoInstanceData); + return false; + } + + encounterId = uint.Parse(param1); + + if (encounterId > map.GetInstanceScript().GetEncounterCount()) + { + handler.SendSysMessage(CypherStrings.BadValue); + return false; + } + + EncounterState state = map.GetInstanceScript().GetBossState(encounterId); + handler.SendSysMessage(CypherStrings.CommandInstGetBossState, encounterId, state); + return true; + } + } +} diff --git a/Game/Chat/Commands/LFGCommands.cs b/Game/Chat/Commands/LFGCommands.cs new file mode 100644 index 000000000..65c3e759c --- /dev/null +++ b/Game/Chat/Commands/LFGCommands.cs @@ -0,0 +1,143 @@ +/* + * Copyright (C) 2012-2017 CypherCore + * + * 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, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +using Framework.Constants; +using Framework.Database; +using Framework.IO; +using Game.DungeonFinding; +using Game.Entities; +using Game.Groups; + +namespace Game.Chat +{ + [CommandGroup("lfg", RBACPermissions.CommandLfg, true)] + class LFGCommands + { + [Command("player", RBACPermissions.CommandLfgPlayer, true)] + static bool HandleLfgPlayerInfoCommand(StringArguments args, CommandHandler handler) + { + Player target = null; + string playerName; + ObjectGuid guid; + if (!handler.extractPlayerTarget(args, out target, out guid, out playerName)) + return false; + + GetPlayerInfo(handler, target); + return true; + } + + [Command("group", RBACPermissions.CommandLfgGroup, true)] + static bool HandleLfgGroupInfoCommand(StringArguments args, CommandHandler handler) + { + if (args.Empty()) + return false; + + Player playerTarget = null; + ObjectGuid guidTarget; + string nameTarget; + + ObjectGuid parseGUID = ObjectGuid.Create(HighGuid.Player, args.NextUInt64()); + if (ObjectManager.GetPlayerNameByGUID(parseGUID, out nameTarget)) + { + playerTarget = Global.ObjAccessor.FindPlayer(parseGUID); + guidTarget = parseGUID; + } + else if (!handler.extractPlayerTarget(args, out playerTarget, out guidTarget, out nameTarget)) + return false; + + Group groupTarget = null; + if (playerTarget) + groupTarget = playerTarget.GetGroup(); + else + { + PreparedStatement stmt = DB.Characters.GetPreparedStatement(CharStatements.SEL_GROUP_MEMBER); + stmt.AddValue(0, guidTarget.GetCounter()); + SQLResult resultGroup = DB.Characters.Query(stmt); + if (!resultGroup.IsEmpty()) + groupTarget = Global.GroupMgr.GetGroupByDbStoreId(resultGroup.Read(0)); + } + + if (!groupTarget) + { + handler.SendSysMessage(CypherStrings.LfgNotInGroup, nameTarget); + return false; + } + + ObjectGuid guid = groupTarget.GetGUID(); + handler.SendSysMessage(CypherStrings.LfgGroupInfo, groupTarget.isLFGGroup(), Global.LFGMgr.GetState(guid), Global.LFGMgr.GetDungeon(guid)); + + foreach (var slot in groupTarget.GetMemberSlots()) + { + Player p = Global.ObjAccessor.FindPlayer(slot.guid); + if (p) + GetPlayerInfo(handler, p); + else + handler.SendSysMessage("{0} is offline.", slot.name); + } + + return true; + } + + [Command("options", RBACPermissions.CommandLfgOptions, true)] + static bool HandleLfgOptionsCommand(StringArguments args, CommandHandler handler) + { + int options = -1; + string str = args.NextString(); + if (!string.IsNullOrEmpty(str)) + { + int tmp = int.Parse(str); + if (tmp > -1) + options = tmp; + } + + if (options != -1) + { + Global.LFGMgr.SetOptions((LfgOptions)options); + handler.SendSysMessage(CypherStrings.LfgOptionsChanged); + } + handler.SendSysMessage(CypherStrings.LfgOptions, Global.LFGMgr.GetOptions()); + return true; + } + + [Command("queue", RBACPermissions.CommandLfgQueue, true)] + static bool HandleLfgQueueInfoCommand(StringArguments args, CommandHandler handler) + { + handler.SendSysMessage(Global.LFGMgr.DumpQueueInfo(args.NextBoolean())); + return true; + } + + [Command("clean", RBACPermissions.CommandLfgClean, true)] + static bool HandleLfgCleanCommand(StringArguments args, CommandHandler handler) + { + handler.SendSysMessage(CypherStrings.LfgClean); + Global.LFGMgr.Clean(); + return true; + } + + static void GetPlayerInfo(CommandHandler handler, Player player) + { + if (!player) + return; + + ObjectGuid guid = player.GetGUID(); + var dungeons = Global.LFGMgr.GetSelectedDungeons(guid); + + handler.SendSysMessage(CypherStrings.LfgPlayerInfo, player.GetName(), Global.LFGMgr.GetState(guid), dungeons.Count, LFGQueue.ConcatenateDungeons(dungeons), + LFGQueue.GetRolesString(Global.LFGMgr.GetRoles(guid)), Global.LFGMgr.GetComment(guid)); + } + } +} diff --git a/Game/Chat/Commands/LearnCommands.cs b/Game/Chat/Commands/LearnCommands.cs new file mode 100644 index 000000000..194556d5c --- /dev/null +++ b/Game/Chat/Commands/LearnCommands.cs @@ -0,0 +1,348 @@ +/* + * Copyright (C) 2012-2017 CypherCore + * + * 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, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +using Framework.Constants; +using Framework.IO; +using Game.DataStorage; +using Game.Entities; +using Game.Spells; +using System; +using System.Collections.Generic; + +namespace Game.Chat.Commands +{ + [CommandGroup("learn", RBACPermissions.CommandLearn)] + class LearnCommands + { + [Command("", RBACPermissions.CommandLearn)] + static bool HandleLearnCommand(StringArguments args, CommandHandler handler) + { + Player targetPlayer = handler.getSelectedPlayerOrSelf(); + + if (!targetPlayer) + { + handler.SendSysMessage(CypherStrings.PlayerNotFound); + return false; + } + + // number or [name] Shift-click form |color|Hspell:spell_id|h[name]|h|r or Htalent form + uint spell = handler.extractSpellIdFromLink(args); + if (spell == 0 || !Global.SpellMgr.HasSpellInfo(spell)) + return false; + + string all = args.NextString(); + bool allRanks = !string.IsNullOrEmpty(all) ? all == "all" : false; + + SpellInfo spellInfo = Global.SpellMgr.GetSpellInfo(spell); + if (spellInfo == null || !Global.SpellMgr.IsSpellValid(spellInfo, handler.GetSession().GetPlayer())) + { + handler.SendSysMessage(CypherStrings.CommandSpellBroken, spell); + return false; + } + + if (!allRanks && targetPlayer.HasSpell(spell)) + { + if (targetPlayer == handler.GetSession().GetPlayer()) + handler.SendSysMessage(CypherStrings.YouKnownSpell); + else + handler.SendSysMessage(CypherStrings.TargetKnownSpell, handler.GetNameLink(targetPlayer)); + + return false; + } + + if (allRanks) + targetPlayer.LearnSpellHighestRank(spell); + else + targetPlayer.LearnSpell(spell, false); + + return true; + } + + [CommandGroup("all", RBACPermissions.CommandLearnAll)] + class LearnAllCommands + { + [Command("gm", RBACPermissions.CommandLearnAllGm)] + static bool HandleLearnAllGMCommand(StringArguments args, CommandHandler handler) + { + foreach (var spellInfo in Global.SpellMgr.GetSpellInfoStorage().Values) + { + if (spellInfo == null || !Global.SpellMgr.IsSpellValid(spellInfo, handler.GetSession().GetPlayer(), false)) + continue; + + if (!spellInfo.IsAbilityOfSkillType(SkillType.Internal)) + continue; + + handler.GetSession().GetPlayer().LearnSpell(spellInfo.Id, false); + } + + handler.SendSysMessage(CypherStrings.LearningGmSkills); + return true; + } + + [Command("lang", RBACPermissions.CommandLearnAllLang)] + static bool HandleLearnAllLangCommand(StringArguments args, CommandHandler handler) + { + // skipping UNIVERSAL language (0) + for (byte i = 1; i < Enum.GetValues(typeof(Language)).Length; ++i) + handler.GetSession().GetPlayer().LearnSpell(ObjectManager.lang_description[i].spell_id, false); + + handler.SendSysMessage(CypherStrings.CommandLearnAllLang); + return true; + } + + [Command("default", RBACPermissions.CommandLearnAllDefault)] + static bool HandleLearnAllDefaultCommand(StringArguments args, CommandHandler handler) + { + Player target; + if (!handler.extractPlayerTarget(args, out target)) + return false; + + target.LearnDefaultSkills(); + target.LearnCustomSpells(); + target.LearnQuestRewardedSpells(); + + handler.SendSysMessage(CypherStrings.CommandLearnAllDefaultAndQuest, handler.GetNameLink(target)); + return true; + } + + [Command("crafts", RBACPermissions.CommandLearnAllCrafts)] + static bool HandleLearnAllCraftsCommand(StringArguments args, CommandHandler handler) + { + Player target; + if (!handler.extractPlayerTarget(args, out target)) + return false; + + foreach (var skillInfo in CliDB.SkillLineStorage.Values) + { + if ((skillInfo.CategoryID == SkillCategory.Profession || skillInfo.CategoryID == SkillCategory.Secondary) && skillInfo.CanLink != 0) // only prof. with recipes have + { + HandleLearnSkillRecipesHelper(target, skillInfo.Id); + } + } + + handler.SendSysMessage(CypherStrings.CommandLearnAllCraft); + return true; + } + + [Command("recipes", RBACPermissions.CommandLearnAllRecipes)] + static bool HandleLearnAllRecipesCommand(StringArguments args, CommandHandler handler) + { + // Learns all recipes of specified profession and sets skill to max + // Example: .learn all_recipes enchanting + + Player target = handler.getSelectedPlayer(); + if (!target) + { + handler.SendSysMessage(CypherStrings.PlayerNotFound); + return false; + } + + if (args.Empty()) + return false; + + // converting string that we try to find to lower case + string namePart = args.NextString().ToLower(); + + string name = ""; + uint skillId = 0; + foreach (var skillInfo in CliDB.SkillLineStorage.Values) + { + if ((skillInfo.CategoryID != SkillCategory.Profession && + skillInfo.CategoryID != SkillCategory.Secondary) || + skillInfo.CanLink == 0) // only prof with recipes have set + continue; + + LocaleConstant locale = handler.GetSessionDbcLocale(); + name = skillInfo.DisplayName[locale]; + if (string.IsNullOrEmpty(name)) + continue; + + if (!name.Like(namePart)) + { + locale = 0; + for (; locale < LocaleConstant.Total; ++locale) + { + if (locale == handler.GetSessionDbcLocale()) + continue; + + name = skillInfo.DisplayName[locale]; + if (name.IsEmpty()) + continue; + + if (name.Like(namePart)) + break; + } + } + + if (locale < LocaleConstant.Total) + { + skillId = skillInfo.Id; + break; + } + } + + if (skillId == 0) + return false; + + HandleLearnSkillRecipesHelper(target, skillId); + + ushort maxLevel = target.GetPureMaxSkillValue((SkillType)skillId); + target.SetSkill(skillId, target.GetSkillStep((SkillType)skillId), maxLevel, maxLevel); + handler.SendSysMessage(CypherStrings.CommandLearnAllRecipes, name); + return true; + } + + static void HandleLearnSkillRecipesHelper(Player player, uint skillId) + { + uint classmask = player.getClassMask(); + + foreach (var skillLine in CliDB.SkillLineAbilityStorage.Values) + { + // wrong skill + if (skillLine.SkillLine != skillId) + continue; + + // not high rank + if (skillLine.SupercedesSpell != 0) + continue; + + // skip racial skills + if (skillLine.RaceMask != 0) + continue; + + // skip wrong class skills + if (skillLine.ClassMask != 0 && (skillLine.ClassMask & classmask) == 0) + continue; + + SpellInfo spellInfo = Global.SpellMgr.GetSpellInfo(skillLine.SpellID); + if (spellInfo == null || !Global.SpellMgr.IsSpellValid(spellInfo, player, false)) + continue; + + player.LearnSpell(skillLine.SpellID, false); + } + } + + [CommandGroup("my", RBACPermissions.CommandLearnAllMy)] + class LearnAllMyCommands + { + [Command("class", RBACPermissions.CommandLearnAllMyClass)] + static bool HandleLearnAllMyClassCommand(StringArguments args, CommandHandler handler) + { + HandleLearnAllMySpellsCommand(args, handler); + HandleLearnAllMyTalentsCommand(args, handler); + return true; + } + + [Command("spells", RBACPermissions.CommandLearnAllMySpells)] + static bool HandleLearnAllMySpellsCommand(StringArguments args, CommandHandler handler) + { + ChrClassesRecord classEntry = CliDB.ChrClassesStorage.LookupByKey(handler.GetSession().GetPlayer().GetClass()); + if (classEntry == null) + return true; + uint family = classEntry.SpellClassSet; + + foreach (var entry in CliDB.SkillLineAbilityStorage.Values) + { + SpellInfo spellInfo = Global.SpellMgr.GetSpellInfo(entry.SpellID); + if (spellInfo == null) + continue; + + // skip server-side/triggered spells + if (spellInfo.SpellLevel == 0) + continue; + + // skip wrong class/race skills + if (!handler.GetSession().GetPlayer().IsSpellFitByClassAndRace(spellInfo.Id)) + continue; + + // skip other spell families + if ((uint)spellInfo.SpellFamilyName != family) + continue; + + // skip broken spells + if (!Global.SpellMgr.IsSpellValid(spellInfo, handler.GetSession().GetPlayer(), false)) + continue; + + handler.GetSession().GetPlayer().LearnSpell(spellInfo.Id, false); + } + + handler.SendSysMessage(CypherStrings.CommandLearnClassSpells); + return true; + } + + [Command("talents", RBACPermissions.CommandLearnAllMyTalents)] + static bool HandleLearnAllMyTalentsCommand(StringArguments args, CommandHandler handler) + { + Player player = handler.GetSession().GetPlayer(); + uint playerClass = (uint)player.GetClass(); + + foreach (var talentInfo in CliDB.TalentStorage.Values) + { + if (playerClass != talentInfo.ClassID) + continue; + + SpellInfo spellInfo = Global.SpellMgr.GetSpellInfo(talentInfo.SpellID); + if (spellInfo == null || !Global.SpellMgr.IsSpellValid(spellInfo, handler.GetSession().GetPlayer(), false)) + continue; + + // learn highest rank of talent and learn all non-talent spell ranks (recursive by tree) + player.LearnSpellHighestRank(talentInfo.SpellID); + player.AddTalent(talentInfo, player.GetActiveTalentGroup(), true); + } + + handler.SendSysMessage(CypherStrings.CommandLearnClassTalents); + return true; + } + + [Command("pettalents", RBACPermissions.CommandLearnAllMyPettalents)] + static bool HandleLearnAllMyPetTalentsCommand(StringArguments args, CommandHandler handler) { return true; } + } + } + + [CommandNonGroup("unlearn", RBACPermissions.CommandUnlearn)] + static bool HandleUnLearnCommand(StringArguments args, CommandHandler handler) + { + if (args.Empty()) + return false; + + // number or [name] Shift-click form |color|Hspell:spell_id|h[name]|h|r + uint spellId = handler.extractSpellIdFromLink(args); + if (spellId == 0) + return false; + + string allStr = args.NextString(); + bool allRanks = !string.IsNullOrEmpty(allStr) ? allStr == "all" : false; + + Player target = handler.getSelectedPlayer(); + if (!target) + { + handler.SendSysMessage(CypherStrings.NoCharSelected); + return false; + } + + if (allRanks) + spellId = Global.SpellMgr.GetFirstSpellInChain(spellId); + + if (target.HasSpell(spellId)) + target.RemoveSpell(spellId, false, !allRanks); + else + handler.SendSysMessage(CypherStrings.ForgetSpell); + + return true; + } + } +} diff --git a/Game/Chat/Commands/ListCommands.cs b/Game/Chat/Commands/ListCommands.cs new file mode 100644 index 000000000..9bb226402 --- /dev/null +++ b/Game/Chat/Commands/ListCommands.cs @@ -0,0 +1,555 @@ +/* + * Copyright (C) 2012-2017 CypherCore + * + * 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, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +using Framework.Constants; +using Framework.Database; +using Framework.IO; +using Game.Entities; +using Game.Spells; +using System.Collections.Generic; + +namespace Game.Chat.Commands +{ + [CommandGroup("list", RBACPermissions.CommandList, true)] + class ListCommands + { + [Command("auras", RBACPermissions.CommandListAuras)] + static bool HandleListAurasCommand(StringArguments args, CommandHandler handler) + { + Unit unit = handler.getSelectedUnit(); + if (!unit) + { + handler.SendSysMessage(CypherStrings.SelectCharOrCreature); + return false; + } + + string talentStr = handler.GetCypherString(CypherStrings.Talent); + string passiveStr = handler.GetCypherString(CypherStrings.Passive); + + var auras = unit.GetAppliedAuras(); + handler.SendSysMessage(CypherStrings.CommandTargetListauras, auras.Count); + foreach (var pair in auras) + { + + AuraApplication aurApp = pair.Value; + Aura aura = aurApp.GetBase(); + string name = aura.GetSpellInfo().SpellName[handler.GetSessionDbcLocale()]; + bool talent = aura.GetSpellInfo().HasAttribute(SpellCustomAttributes.IsTalent); + + string ss_name = "|cffffffff|Hspell:" + aura.GetId() + "|h[" + name + "]|h|r"; + + handler.SendSysMessage(CypherStrings.CommandTargetAuradetail, aura.GetId(), (handler.GetSession() != null ? ss_name : name), + aurApp.GetEffectMask(), aura.GetCharges(), aura.GetStackAmount(), aurApp.GetSlot(), + aura.GetDuration(), aura.GetMaxDuration(), (aura.IsPassive() ? passiveStr : ""), + (talent ? talentStr : ""), aura.GetCasterGUID().IsPlayer() ? "player" : "creature", + aura.GetCasterGUID().ToString()); + } + + for (ushort i = 0; i < (int)AuraType.Total; ++i) + { + var auraList = unit.GetAuraEffectsByType((AuraType)i); + if (auraList.Empty()) + continue; + + handler.SendSysMessage(CypherStrings.CommandTargetListauratype, auraList.Count, i); + + foreach (var eff in auraList) + handler.SendSysMessage(CypherStrings.CommandTargetAurasimple, eff.GetId(), eff.GetEffIndex(), eff.GetAmount()); + } + + return true; + } + + [Command("creature", RBACPermissions.CommandListCreature, true)] + static bool HandleListCreatureCommand(StringArguments args, CommandHandler handler) + { + if (args.Empty()) + return false; + + // number or [name] Shift-click form |color|Hcreature_entry:creature_id|h[name]|h|r + string id = handler.extractKeyFromLink(args, "Hcreature_entry"); + if (string.IsNullOrEmpty(id)) + return false; + + uint creatureId = uint.Parse(id); + if (creatureId == 0) + { + handler.SendSysMessage(CypherStrings.CommandInvalidcreatureid, creatureId); + return false; + } + + CreatureTemplate cInfo = Global.ObjectMgr.GetCreatureTemplate(creatureId); + if (cInfo == null) + { + handler.SendSysMessage(CypherStrings.CommandInvalidcreatureid, creatureId); + return false; + } + + string countStr = args.NextString(); + uint count = !string.IsNullOrEmpty(countStr) ? uint.Parse(countStr) : 10; + + if (count == 0) + return false; + + uint creatureCount = 0; + SQLResult result = DB.World.Query("SELECT COUNT(guid) FROM creature WHERE id='{0}'", creatureId); + if (!result.IsEmpty()) + creatureCount = result.Read(0); + + if (handler.GetSession() != null) + { + Player player = handler.GetSession().GetPlayer(); + result = DB.World.Query("SELECT guid, position_x, position_y, position_z, map, (POW(position_x - '{0}', 2) + POW(position_y - '{1}', 2) + POW(position_z - '{2}', 2)) AS order_ FROM creature WHERE id = '{3}' ORDER BY order_ ASC LIMIT {4}", + player.GetPositionX(), player.GetPositionY(), player.GetPositionZ(), creatureId, count); + } + else + result = DB.World.Query("SELECT guid, position_x, position_y, position_z, map FROM creature WHERE id = '{0}' LIMIT {1}", + creatureId, count); + + if (!result.IsEmpty()) + { + do + { + ulong guid = result.Read(0); + float x = result.Read(1); + float y = result.Read(2); + float z = result.Read(3); + ushort mapId = result.Read(4); + + if (handler.GetSession() != null) + handler.SendSysMessage(CypherStrings.CreatureListChat, guid, guid, cInfo.Name, x, y, z, mapId); + else + handler.SendSysMessage(CypherStrings.CreatureListConsole, guid, cInfo.Name, x, y, z, mapId); + } + while (result.NextRow()); + } + + handler.SendSysMessage(CypherStrings.CommandListcreaturemessage, creatureId, creatureCount); + + return true; + } + + [Command("item", RBACPermissions.CommandListItem, true)] + static bool HandleListItemCommand(StringArguments args, CommandHandler handler) + { + if (args.Empty()) + return false; + + string id = handler.extractKeyFromLink(args, "Hitem"); + if (string.IsNullOrEmpty(id)) + return false; + + uint itemId = uint.Parse(id); + if (itemId == 0) + { + handler.SendSysMessage(CypherStrings.CommandItemidinvalid, itemId); + return false; + } + + ItemTemplate itemTemplate = Global.ObjectMgr.GetItemTemplate(itemId); + if (itemTemplate == null) + { + handler.SendSysMessage(CypherStrings.CommandItemidinvalid, itemId); + return false; + } + + string countStr = args.NextString(); + uint count = !string.IsNullOrEmpty(countStr) ? uint.Parse(countStr) : 10; + + if (count == 0) + return false; + + SQLResult result; + + // inventory case + uint inventoryCount = 0; + + PreparedStatement stmt = DB.Characters.GetPreparedStatement(CharStatements.SEL_CHAR_INVENTORY_COUNT_ITEM); + stmt.AddValue(0, itemId); + result = DB.Characters.Query(stmt); + + if (!result.IsEmpty()) + inventoryCount = result.Read(0); + + stmt = DB.Characters.GetPreparedStatement(CharStatements.SEL_CHAR_INVENTORY_ITEM_BY_ENTRY); + stmt.AddValue(0, itemId); + stmt.AddValue(1, count); + result = DB.Characters.Query(stmt); + + if (!result.IsEmpty()) + { + do + { + ObjectGuid itemGuid = ObjectGuid.Create(HighGuid.Item, result.Read(0)); + uint itemBag = result.Read(1); + byte itemSlot = result.Read(2); + ObjectGuid ownerGuid = ObjectGuid.Create(HighGuid.Player, result.Read(3)); + uint ownerAccountId = result.Read(4); + string ownerName = result.Read(5); + + string itemPos = ""; + if (Player.IsEquipmentPos((byte)itemBag, itemSlot)) + itemPos = "[equipped]"; + else if (Player.IsInventoryPos((byte)itemBag, itemSlot)) + itemPos = "[in inventory]"; + else if (Player.IsBankPos((byte)itemBag, itemSlot)) + itemPos = "[in bank]"; + else + itemPos = ""; + + handler.SendSysMessage(CypherStrings.ItemlistSlot, itemGuid.ToString(), ownerName, ownerGuid.ToString(), ownerAccountId, itemPos); + } + while (result.NextRow()); + + uint resultCount = (uint)result.GetRowCount(); + + if (count > resultCount) + count -= resultCount; + else if (count != 0) + count = 0; + } + + // mail case + uint mailCount = 0; + + stmt = DB.Characters.GetPreparedStatement(CharStatements.SEL_MAIL_COUNT_ITEM); + stmt.AddValue(0, itemId); + result = DB.Characters.Query(stmt); + + if (!result.IsEmpty()) + mailCount = result.Read(0); + + if (count > 0) + { + stmt = DB.Characters.GetPreparedStatement(CharStatements.SEL_MAIL_ITEMS_BY_ENTRY); + stmt.AddValue(0, itemId); + stmt.AddValue(1, count); + result = DB.Characters.Query(stmt); + } + else + result = null; + + if (result != null && !result.IsEmpty()) + { + do + { + ulong itemGuid = result.Read(0); + ulong itemSender = result.Read(1); + ulong itemReceiver = result.Read(2); + uint itemSenderAccountId = result.Read(3); + string itemSenderName = result.Read(4); + uint itemReceiverAccount = result.Read(5); + string itemReceiverName = result.Read(6); + + string itemPos = "[in mail]"; + + handler.SendSysMessage(CypherStrings.ItemlistMail, itemGuid, itemSenderName, itemSender, itemSenderAccountId, itemReceiverName, itemReceiver, itemReceiverAccount, itemPos); + } + while (result.NextRow()); + + uint resultCount = (uint)result.GetRowCount(); + + if (count > resultCount) + count -= resultCount; + else if (count != 0) + count = 0; + } + + // auction case + uint auctionCount = 0; + + stmt = DB.Characters.GetPreparedStatement(CharStatements.SEL_AUCTIONHOUSE_COUNT_ITEM); + stmt.AddValue(0, itemId); + result = DB.Characters.Query(stmt); + + if (!result.IsEmpty()) + auctionCount = result.Read(0); + + if (count > 0) + { + stmt = DB.Characters.GetPreparedStatement(CharStatements.SEL_AUCTIONHOUSE_ITEM_BY_ENTRY); + stmt.AddValue(0, itemId); + stmt.AddValue(1, count); + result = DB.Characters.Query(stmt); + } + else + result = null; + + if (result != null && !result.IsEmpty()) + { + do + { + ObjectGuid itemGuid = ObjectGuid.Create(HighGuid.Item, result.Read(0)); + ObjectGuid owner = ObjectGuid.Create(HighGuid.Player, result.Read(1)); + uint ownerAccountId = result.Read(2); + string ownerName = result.Read(3); + + string itemPos = "[in auction]"; + + handler.SendSysMessage(CypherStrings.ItemlistAuction, itemGuid.ToString(), ownerName, owner.ToString(), ownerAccountId, itemPos); + } + while (result.NextRow()); + } + + // guild bank case + uint guildCount = 0; + + stmt = DB.Characters.GetPreparedStatement(CharStatements.SEL_GUILD_BANK_COUNT_ITEM); + stmt.AddValue(0, itemId); + result = DB.Characters.Query(stmt); + + if (!result.IsEmpty()) + guildCount = result.Read(0); + + stmt = DB.Characters.GetPreparedStatement(CharStatements.SEL_GUILD_BANK_ITEM_BY_ENTRY); + stmt.AddValue(0, itemId); + stmt.AddValue(1, count); + result = DB.Characters.Query(stmt); + + if (!result.IsEmpty()) + { + do + { + ObjectGuid itemGuid = ObjectGuid.Create(HighGuid.Item, result.Read(0)); + ObjectGuid guildGuid = ObjectGuid.Create(HighGuid.Guild, result.Read(1)); + string guildName = result.Read(2); + + string itemPos = "[in guild bank]"; + + handler.SendSysMessage(CypherStrings.ItemlistGuild, itemGuid.ToString(), guildName, guildGuid.ToString(), itemPos); + } + while (result.NextRow()); + + uint resultCount = (uint)result.GetRowCount(); + + if (count > resultCount) + count -= resultCount; + else if (count != 0) + count = 0; + } + + if (inventoryCount + mailCount + auctionCount + guildCount == 0) + { + handler.SendSysMessage(CypherStrings.CommandNoitemfound); + return false; + } + + handler.SendSysMessage(CypherStrings.CommandListitemmessage, itemId, inventoryCount + mailCount + auctionCount + guildCount, inventoryCount, mailCount, auctionCount, guildCount); + return true; + } + + [Command("mail", RBACPermissions.CommandListMail, true)] + static bool HandleListMailCommand(StringArguments args, CommandHandler handler) + { + if (args.Empty()) + return false; + + Player target; + ObjectGuid targetGuid; + string targetName; + PreparedStatement stmt = null; + + ObjectGuid parseGUID = ObjectGuid.Create(HighGuid.Player, args.NextUInt64()); + if (ObjectManager.GetPlayerNameByGUID(parseGUID, out targetName)) + { + target = Global.ObjAccessor.FindPlayer(parseGUID); + targetGuid = parseGUID; + } + else if (!handler.extractPlayerTarget(args, out target, out targetGuid, out targetName)) + return false; + + stmt = DB.Characters.GetPreparedStatement(CharStatements.SEL_MAIL_LIST_COUNT); + stmt.AddValue(0, targetGuid.GetCounter()); + SQLResult result = DB.Characters.Query(stmt); + if (!result.IsEmpty()) + { + uint countMail = result.Read(0); + + string nameLink = handler.playerLink(targetName); + handler.SendSysMessage(CypherStrings.ListMailHeader, countMail, nameLink, targetGuid.ToString()); + handler.SendSysMessage(CypherStrings.AccountListBar); + + stmt = DB.Characters.GetPreparedStatement(CharStatements.SEL_MAIL_LIST_INFO); + stmt.AddValue(0, targetGuid.GetCounter()); + SQLResult result1 = DB.Characters.Query(stmt); + + if (!result1.IsEmpty()) + { + do + { + uint messageId = result1.Read(0); + ulong senderId = result1.Read(1); + string sender = result1.Read(2); + ulong receiverId = result1.Read(3); + string receiver = result1.Read(4); + string subject = result1.Read(5); + long deliverTime = result1.Read(6); + long expireTime = result1.Read(7); + uint money = result1.Read(8); + byte hasItem = result1.Read(9); + uint gold = money / MoneyConstants.Gold; + uint silv = (money % MoneyConstants.Gold) / MoneyConstants.Silver; + uint copp = (money % MoneyConstants.Gold) % MoneyConstants.Silver; + string receiverStr = handler.playerLink(receiver); + string senderStr = handler.playerLink(sender); + handler.SendSysMessage(CypherStrings.ListMailInfo1, messageId, subject, gold, silv, copp); + handler.SendSysMessage(CypherStrings.ListMailInfo2, senderStr, senderId, receiverStr, receiverId); + handler.SendSysMessage(CypherStrings.ListMailInfo3, Time.UnixTimeToDateTime(deliverTime).ToLongDateString(), Time.UnixTimeToDateTime(expireTime).ToLongDateString()); + + if (hasItem == 1) + { + SQLResult result2 = DB.Characters.Query("SELECT item_guid FROM mail_items WHERE mail_id = '{0}'", messageId); + if (!result2.IsEmpty()) + { + do + { + uint item_guid = result2.Read(0); + stmt = DB.Characters.GetPreparedStatement(CharStatements.SEL_MAIL_LIST_ITEMS); + stmt.AddValue(0, item_guid); + SQLResult result3 = DB.Characters.Query(stmt); + if (!result3.IsEmpty()) + { + do + { + uint item_entry = result3.Read(0); + uint item_count = result3.Read(1); + SQLResult result4 = DB.World.Query("SELECT name, quality FROM item_template WHERE entry = '{0}'", item_entry); + + string item_name = result4.Read(0); + int item_quality = result4.Read(1); + if (handler.GetSession() != null) + { + uint color = ItemConst.ItemQualityColors[item_quality]; + string itemStr = "|c" + color + "|Hitem:" + item_entry + ":0:0:0:0:0:0:0:0:0|h[" + item_name + "]|h|r"; + handler.SendSysMessage(CypherStrings.ListMailInfoItem, itemStr, item_entry, item_guid, item_count); + } + else + handler.SendSysMessage(CypherStrings.ListMailInfoItem, item_name, item_entry, item_guid, item_count); + } + while (result3.NextRow()); + } + } + while (result2.NextRow()); + } + } + handler.SendSysMessage(CypherStrings.AccountListBar); + } + while (result1.NextRow()); + } + else + handler.SendSysMessage(CypherStrings.ListMailNotFound); + return true; + } + else + handler.SendSysMessage(CypherStrings.ListMailNotFound); + return true; + } + + [Command("object", RBACPermissions.CommandListObject, true)] + static bool HandleListObjectCommand(StringArguments args, CommandHandler handler) + { + if (args.Empty()) + return false; + + // number or [name] Shift-click form |color|Hgameobject_entry:go_id|h[name]|h|r + string id = handler.extractKeyFromLink(args, "Hgameobject_entry"); + if (string.IsNullOrEmpty(id)) + return false; + + uint gameObjectId = uint.Parse(id); + if (gameObjectId == 0) + { + handler.SendSysMessage(CypherStrings.CommandListobjinvalidid, gameObjectId); + return false; + } + + GameObjectTemplate gInfo = Global.ObjectMgr.GetGameObjectTemplate(gameObjectId); + if (gInfo == null) + { + handler.SendSysMessage(CypherStrings.CommandListobjinvalidid, gameObjectId); + return false; + } + + string countStr = args.NextString(); + uint count = !string.IsNullOrEmpty(countStr) ? uint.Parse(countStr) : 10; + + if (count == 0) + return false; + + uint objectCount = 0; + SQLResult result = DB.World.Query("SELECT COUNT(guid) FROM gameobject WHERE id='{0}'", gameObjectId); + if (!result.IsEmpty()) + objectCount = result.Read(0); + + if (handler.GetSession() != null) + { + Player player = handler.GetSession().GetPlayer(); + result = DB.World.Query("SELECT guid, position_x, position_y, position_z, map, id, (POW(position_x - '{0}', 2) + POW(position_y - '{1}', 2) + POW(position_z - '{2}', 2)) AS order_ FROM gameobject WHERE id = '{3}' ORDER BY order_ ASC LIMIT {4}", + player.GetPositionX(), player.GetPositionY(), player.GetPositionZ(), gameObjectId, count); + } + else + result = DB.World.Query("SELECT guid, position_x, position_y, position_z, map, id FROM gameobject WHERE id = '{0}' LIMIT {1}", + gameObjectId, count); + + if (!result.IsEmpty()) + { + do + { + ulong guid = result.Read(0); + float x = result.Read(1); + float y = result.Read(2); + float z = result.Read(3); + ushort mapId = result.Read(4); + uint entry = result.Read(5); + + if (handler.GetSession() != null) + handler.SendSysMessage(CypherStrings.GoListChat, guid, entry, guid, gInfo.name, x, y, z, mapId); + else + handler.SendSysMessage(CypherStrings.GoListConsole, guid, gInfo.name, x, y, z, mapId); + } + while (result.NextRow()); + } + + handler.SendSysMessage(CypherStrings.CommandListobjmessage, gameObjectId, objectCount); + + return true; + } + + [Command("scenes", RBACPermissions.CommandListScenes)] + static bool HandleListScenesCommand(StringArguments args, CommandHandler handler) + { + Player target = handler.getSelectedPlayer(); + if (!target) + target = handler.GetSession().GetPlayer(); + + if (!target) + { + handler.SendSysMessage(CypherStrings.PlayerNotFound); + return false; + } + + var instanceByPackageMap = target.GetSceneMgr().GetSceneTemplateByInstanceMap(); + + handler.SendSysMessage(CypherStrings.DebugSceneObjectList, target.GetSceneMgr().GetActiveSceneCount()); + + foreach (var instanceByPackage in instanceByPackageMap) + handler.SendSysMessage(CypherStrings.DebugSceneObjectDetail, instanceByPackage.Value.ScenePackageId, instanceByPackage.Key); + + return true; + } + } +} diff --git a/Game/Chat/Commands/LookupCommands.cs b/Game/Chat/Commands/LookupCommands.cs new file mode 100644 index 000000000..833843c9c --- /dev/null +++ b/Game/Chat/Commands/LookupCommands.cs @@ -0,0 +1,1212 @@ +/* + * Copyright (C) 2012-2017 CypherCore + * + * 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, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +using Framework.Constants; +using Framework.Database; +using Framework.IO; +using Game.DataStorage; +using Game.Entities; +using Game.Spells; +using System; +using System.Text; + +namespace Game.Chat +{ + [CommandGroup("lookup", RBACPermissions.CommandLookup, true)] + class LookupCommands + { + static int maxlookup = 50; + + [Command("area", RBACPermissions.CommandLookupArea, true)] + static bool Area(StringArguments args, CommandHandler handler) + { + if (args.Empty()) + return false; + + string namePart = args.NextString().ToLower(); + + bool found = false; + uint count = 0; + + // Search in AreaTable.dbc + foreach (var areaEntry in CliDB.AreaTableStorage.Values) + { + LocaleConstant locale = handler.GetSessionDbcLocale(); + string name = areaEntry.AreaName[locale]; + if (string.IsNullOrEmpty(name)) + continue; + + if (!name.Like(namePart)) + { + locale = 0; + for (; locale < LocaleConstant.Total; ++locale) + { + if (locale == handler.GetSessionDbcLocale()) + continue; + + name = areaEntry.AreaName[locale]; + if (name.IsEmpty()) + continue; + + if (name.Like(namePart)) + break; + } + } + + if (locale < LocaleConstant.Total) + { + if (maxlookup != 0 && count++ == maxlookup) + { + handler.SendSysMessage(CypherStrings.CommandLookupMaxResults, maxlookup); + return true; + } + + // send area in "id - [name]" format + string ss = ""; + if (handler.GetSession() != null) + ss += areaEntry.Id + " - |cffffffff|Harea:" + areaEntry.Id + "|h[" + name + "]|h|r"; + else + ss += areaEntry.Id + " - " + name; + + handler.SendSysMessage(ss); + + if (!found) + found = true; + } + } + + if (!found) + handler.SendSysMessage(CypherStrings.CommandNoareafound); + + return true; + } + + [Command("creature", RBACPermissions.CommandLookupCreature, true)] + static bool Creature(StringArguments args, CommandHandler handler) + { + if (args.Empty()) + return false; + + string namePart = args.NextString().ToLower(); + + bool found = false; + uint count = 0; + + var ctc = Global.ObjectMgr.GetCreatureTemplates(); + foreach (var template in ctc) + { + uint id = template.Value.Entry; + byte localeIndex = handler.GetSessionDbLocaleIndex(); + CreatureLocale creatureLocale = Global.ObjectMgr.GetCreatureLocale(id); + if (creatureLocale != null) + { + if (creatureLocale.Name.Length > localeIndex && !string.IsNullOrEmpty(creatureLocale.Name[localeIndex])) + { + string name = creatureLocale.Name[localeIndex]; + + if (name.Like(namePart)) + { + if (maxlookup != 0 && count++ == maxlookup) + { + handler.SendSysMessage(CypherStrings.CommandLookupMaxResults, maxlookup); + return true; + } + + if (handler.GetSession() != null) + handler.SendSysMessage(CypherStrings.CreatureEntryListChat, id, id, name); + else + handler.SendSysMessage(CypherStrings.CreatureEntryListConsole, id, name); + + if (!found) + found = true; + + continue; + } + } + } + + string _name = template.Value.Name; + if (string.IsNullOrEmpty(_name)) + continue; + + if (_name.Like(namePart)) + { + if (maxlookup != 0 && count++ == maxlookup) + { + handler.SendSysMessage(CypherStrings.CommandLookupMaxResults, maxlookup); + return true; + } + + if (handler.GetSession() != null) + handler.SendSysMessage(CypherStrings.CreatureEntryListChat, id, id, _name); + else + handler.SendSysMessage(CypherStrings.CreatureEntryListConsole, id, _name); + + if (!found) + found = true; + } + } + + if (!found) + handler.SendSysMessage(CypherStrings.CommandNocreaturefound); + + return true; + } + + [Command("event", RBACPermissions.CommandLookupEvent, true)] + static bool Event(StringArguments args, CommandHandler handler) + { + if (args.Empty()) + return false; + + string namePart = args.NextString().ToLower(); + + bool found = false; + uint count = 0; + + var events = Global.GameEventMgr.GetEventMap(); + var activeEvents = Global.GameEventMgr.GetActiveEventList(); + + for (ushort id = 0; id < events.Length; ++id) + { + GameEventData eventData = events[id]; + + string descr = eventData.description; + if (string.IsNullOrEmpty(descr)) + continue; + + if (descr.Like(namePart)) + { + if (maxlookup != 0 && count++ == maxlookup) + { + handler.SendSysMessage(CypherStrings.CommandLookupMaxResults, maxlookup); + return true; + } + + string active = activeEvents.Contains(id) ? handler.GetCypherString(CypherStrings.Active) : ""; + + if (handler.GetSession() != null) + handler.SendSysMessage(CypherStrings.EventEntryListChat, id, id, eventData.description, active); + else + handler.SendSysMessage(CypherStrings.EventEntryListConsole, id, eventData.description, active); + + if (!found) + found = true; + } + } + + if (!found) + handler.SendSysMessage(CypherStrings.Noeventfound); + + return true; + } + + [Command("faction", RBACPermissions.CommandLookupFaction, true)] + static bool Faction(StringArguments args, CommandHandler handler) + { + if (args.Empty()) + return false; + + // Can be NULL at console call + Player target = handler.getSelectedPlayer(); + + string namePart = args.NextString().ToLower(); + + bool found = false; + uint count = 0; + + + foreach (var factionEntry in CliDB.FactionStorage.Values) + { + FactionState factionState = target ? target.GetReputationMgr().GetState(factionEntry) : null; + + LocaleConstant locale = handler.GetSessionDbcLocale(); + string name = factionEntry.Name[locale]; + if (string.IsNullOrEmpty(name)) + continue; + + if (!name.Like(namePart)) + { + locale = 0; + for (; locale < LocaleConstant.Total; ++locale) + { + if (locale == handler.GetSessionDbcLocale()) + continue; + + name = factionEntry.Name[locale]; + if (name.IsEmpty()) + continue; + + if (name.Like(namePart)) + break; + } + } + + if (locale < LocaleConstant.Total) + { + if (maxlookup != 0 && count++ == maxlookup) + { + handler.SendSysMessage(CypherStrings.CommandLookupMaxResults, maxlookup); + return true; + } + + // send faction in "id - [faction] rank reputation [visible] [at war] [own team] [unknown] [invisible] [inactive]" format + // or "id - [faction] [no reputation]" format + StringBuilder ss = new StringBuilder(); + if (handler.GetSession() != null) + ss.AppendFormat("{0} - |cffffffff|Hfaction:{0}|h[{1}]|h|r", factionEntry.Id, name); + else + ss.Append(factionEntry.Id + " - " + name); + + if (factionState != null) // and then target != NULL also + { + uint index = target.GetReputationMgr().GetReputationRankStrIndex(factionEntry); + string rankName = handler.GetCypherString((CypherStrings)index); + + ss.AppendFormat(" {0}|h|r ({1})", rankName, target.GetReputationMgr().GetReputation(factionEntry)); + + if (factionState.Flags.HasAnyFlag(FactionFlags.Visible)) + ss.Append(handler.GetCypherString(CypherStrings.FactionVisible)); + if (factionState.Flags.HasAnyFlag(FactionFlags.AtWar)) + ss.Append(handler.GetCypherString(CypherStrings.FactionAtwar)); + if (factionState.Flags.HasAnyFlag(FactionFlags.PeaceForced)) + ss.Append(handler.GetCypherString(CypherStrings.FactionPeaceForced)); + if (factionState.Flags.HasAnyFlag(FactionFlags.Hidden)) + ss.Append(handler.GetCypherString(CypherStrings.FactionHidden)); + if (factionState.Flags.HasAnyFlag(FactionFlags.InvisibleForced)) + ss.Append(handler.GetCypherString(CypherStrings.FactionInvisibleForced)); + if (factionState.Flags.HasAnyFlag(FactionFlags.Inactive)) + ss.Append(handler.GetCypherString(CypherStrings.FactionInactive)); + } + else + ss.Append(handler.GetCypherString(CypherStrings.FactionNoreputation)); + + handler.SendSysMessage(ss.ToString()); + + if (!found) + found = true; + } + + } + + if (!found) + handler.SendSysMessage(CypherStrings.CommandFactionNotfound); + return true; + } + + [Command("item", RBACPermissions.CommandLookupItem, true)] + static bool Item(StringArguments args, CommandHandler handler) + { + if (args.Empty()) + return false; + + string namePart = args.NextString(""); + + bool found = false; + uint count = 0; + + // Search in `item_template` + var its = Global.ObjectMgr.GetItemTemplates(); + foreach (var template in its.Values) + { + string name = template.GetName(handler.GetSessionDbcLocale()); + if (string.IsNullOrEmpty(name)) + continue; + + if (name.Like(namePart)) + { + if (maxlookup != 0 && count++ == maxlookup) + { + handler.SendSysMessage(CypherStrings.CommandLookupMaxResults, maxlookup); + return true; + } + + if (handler.GetSession() != null) + handler.SendSysMessage(CypherStrings.ItemListChat, template.GetId(), template.GetId(), name); + else + handler.SendSysMessage(CypherStrings.ItemListConsole, template.GetId(), name); + + if (!found) + found = true; + } + } + + if (!found) + handler.SendSysMessage(CypherStrings.CommandNoitemfound); + + return true; + } + + [Command("itemset", RBACPermissions.CommandLookupItemset, true)] + static bool ItemSet(StringArguments args, CommandHandler handler) + { + if (args.Empty()) + return false; + + string namePart = args.NextString().ToLower(); + + bool found = false; + uint count = 0; + + // Search in ItemSet.dbc + foreach (var set in CliDB.ItemSetStorage.Values) + { + LocaleConstant locale = handler.GetSessionDbcLocale(); + string name = set.Name[locale]; + if (name.IsEmpty()) + continue; + + if (!name.Like(namePart)) + { + locale = 0; + for (; locale < LocaleConstant.Total; ++locale) + { + if (locale == handler.GetSessionDbcLocale()) + continue; + + name = set.Name[locale]; + if (name.IsEmpty()) + continue; + + if (name.Like(namePart)) + break; + } + } + + if (locale < LocaleConstant.Total) + { + if (maxlookup != 0 && count++ == maxlookup) + { + handler.SendSysMessage(CypherStrings.CommandLookupMaxResults, maxlookup); + return true; + } + + // send item set in "id - [namedlink locale]" format + if (handler.GetSession() != null) + handler.SendSysMessage(CypherStrings.ItemsetListChat, set.Id, set.Id, name, ""); + else + handler.SendSysMessage(CypherStrings.ItemsetListConsole, set.Id, name, ""); + + if (!found) + found = true; + } + } + if (!found) + handler.SendSysMessage(CypherStrings.CommandNoitemsetfound); + + return true; + } + + [Command("object", RBACPermissions.CommandLookupObject, true)] + static bool Object(StringArguments args, CommandHandler handler) + { + if (args.Empty()) + return false; + + string namePart = args.NextString(); + + bool found = false; + uint count = 0; + + var gotc = Global.ObjectMgr.GetGameObjectTemplates(); + foreach (var template in gotc.Values) + { + byte localeIndex = handler.GetSessionDbLocaleIndex(); + + GameObjectLocale objectLocalte = Global.ObjectMgr.GetGameObjectLocale(template.entry); + if (objectLocalte != null) + { + if (objectLocalte.Name.Length > localeIndex && !string.IsNullOrEmpty(objectLocalte.Name[localeIndex])) + { + string name = objectLocalte.Name[localeIndex]; + + if (name.Like(namePart)) + { + if (maxlookup != 0 && count++ == maxlookup) + { + handler.SendSysMessage(CypherStrings.CommandLookupMaxResults, maxlookup); + return true; + } + + if (handler.GetSession() != null) + handler.SendSysMessage(CypherStrings.GoEntryListChat, template.entry, template.entry, name); + else + handler.SendSysMessage(CypherStrings.GoEntryListConsole, template.entry, name); + + if (!found) + found = true; + + continue; + } + } + } + + string _name = template.name; + if (string.IsNullOrEmpty(_name)) + continue; + + if (_name.Like(namePart)) + { + if (maxlookup != 0 && count++ == maxlookup) + { + handler.SendSysMessage(CypherStrings.CommandLookupMaxResults, maxlookup); + return true; + } + + if (handler.GetSession() != null) + handler.SendSysMessage(CypherStrings.GoEntryListChat, template.entry, template.entry, _name); + else + handler.SendSysMessage(CypherStrings.GoEntryListConsole, template.entry, _name); + + if (!found) + found = true; + } + } + + if (!found) + handler.SendSysMessage(CypherStrings.CommandNogameobjectfound); + + return true; + } + + [Command("quest", RBACPermissions.CommandLookupQuest, true)] + static bool Quest(StringArguments args, CommandHandler handler) + { + if (args.Empty()) + return false; + + // can be NULL at console call + Player target = handler.getSelectedPlayer(); + + string namePart = args.NextString().ToLower(); + + bool found = false; + uint count = 0; + + var qTemplates = Global.ObjectMgr.GetQuestStorage(); + foreach (var qInfo in qTemplates.Values) + { + int localeIndex = handler.GetSessionDbLocaleIndex(); + if (localeIndex >= 0) + { + byte ulocaleIndex = (byte)localeIndex; + QuestTemplateLocale questLocale = Global.ObjectMgr.GetQuestLocale(qInfo.Id); + if (questLocale != null) + { + if (questLocale.LogTitle.Length > ulocaleIndex && !string.IsNullOrEmpty(questLocale.LogTitle[ulocaleIndex])) + { + string title = questLocale.LogTitle[ulocaleIndex]; + + if (title.Like(namePart)) + { + if (maxlookup != 0 && count++ == maxlookup) + { + handler.SendSysMessage(CypherStrings.CommandLookupMaxResults, maxlookup); + return true; + } + + string statusStr = ""; + + if (target) + { + QuestStatus status = target.GetQuestStatus(qInfo.Id); + + switch (status) + { + case QuestStatus.Complete: + statusStr = handler.GetCypherString(CypherStrings.CommandQuestComplete); + break; + case QuestStatus.Incomplete: + statusStr = handler.GetCypherString(CypherStrings.CommandQuestActive); + break; + case QuestStatus.Rewarded: + statusStr = handler.GetCypherString(CypherStrings.CommandQuestRewarded); + break; + default: + break; + } + } + + if (handler.GetSession() != null) + handler.SendSysMessage(CypherStrings.QuestListChat, qInfo.Id, qInfo.Id, qInfo.Level, title, statusStr); + else + handler.SendSysMessage(CypherStrings.QuestListConsole, qInfo.Id, title, statusStr); + + if (!found) + found = true; + + continue; + } + } + } + } + + string _title = qInfo.LogTitle; + if (string.IsNullOrEmpty(_title)) + continue; + + if (_title.Like(namePart)) + { + if (maxlookup != 0 && count++ == maxlookup) + { + handler.SendSysMessage(CypherStrings.CommandLookupMaxResults, maxlookup); + return true; + } + + string statusStr = ""; + + if (target) + { + QuestStatus status = target.GetQuestStatus(qInfo.Id); + + switch (status) + { + case QuestStatus.Complete: + statusStr = handler.GetCypherString(CypherStrings.CommandQuestComplete); + break; + case QuestStatus.Incomplete: + statusStr = handler.GetCypherString(CypherStrings.CommandQuestActive); + break; + case QuestStatus.Rewarded: + statusStr = handler.GetCypherString(CypherStrings.CommandQuestRewarded); + break; + default: + break; + } + } + + if (handler.GetSession() != null) + handler.SendSysMessage(CypherStrings.QuestListChat, qInfo.Id, qInfo.Id, qInfo.Level, _title, statusStr); + else + handler.SendSysMessage(CypherStrings.QuestListConsole, qInfo.Id, _title, statusStr); + + if (!found) + found = true; + } + } + + if (!found) + handler.SendSysMessage(CypherStrings.CommandNoquestfound); + + return true; + } + + [Command("skill", RBACPermissions.CommandLookupSkill, true)] + static bool Skill(StringArguments args, CommandHandler handler) + { + if (args.Empty()) + return false; + + // can be NULL in console call + Player target = handler.getSelectedPlayer(); + + string namePart = args.NextString(); + + bool found = false; + uint count = 0; + // Search in SkillLine.dbc + foreach (var skillInfo in CliDB.SkillLineStorage.Values) + { + LocaleConstant locale = handler.GetSessionDbcLocale(); + string name = skillInfo.DisplayName[locale]; + if (string.IsNullOrEmpty(name)) + continue; + + if (!name.Like(namePart)) + { + locale = 0; + for (; locale < LocaleConstant.Total; ++locale) + { + if (locale == handler.GetSessionDbcLocale()) + continue; + + name = skillInfo.DisplayName[locale]; + if (name.IsEmpty()) + continue; + + if (name.Like(namePart)) + break; + } + } + + if (locale < LocaleConstant.Total) + { + if (maxlookup != 0 && count++ == maxlookup) + { + handler.SendSysMessage(CypherStrings.CommandLookupMaxResults, maxlookup); + return true; + } + + string valStr = ""; + string knownStr = ""; + if (target && target.HasSkill((SkillType)skillInfo.Id)) + { + knownStr = handler.GetCypherString(CypherStrings.Known); + uint curValue = target.GetPureSkillValue((SkillType)skillInfo.Id); + uint maxValue = target.GetPureMaxSkillValue((SkillType)skillInfo.Id); + uint permValue = target.GetSkillPermBonusValue(skillInfo.Id); + uint tempValue = target.GetSkillTempBonusValue(skillInfo.Id); + + string valFormat = handler.GetCypherString(CypherStrings.SkillValues); + valStr = string.Format(valFormat, curValue, maxValue, permValue, tempValue); + } + + // send skill in "id - [namedlink locale]" format + if (handler.GetSession() != null) + handler.SendSysMessage(CypherStrings.SkillListChat, skillInfo.Id, skillInfo.Id, name, "", knownStr, valStr); + else + handler.SendSysMessage(CypherStrings.SkillListConsole, skillInfo.Id, name, "", knownStr, valStr); + + if (!found) + found = true; + } + + } + if (!found) + handler.SendSysMessage(CypherStrings.CommandNoskillfound); + + return true; + } + + [Command("taxinode", RBACPermissions.CommandLookupTaxinode, true)] + static bool TaxiNode(StringArguments args, CommandHandler handler) + { + if (args.Empty()) + return false; + + string namePart = args.NextString(); + + bool found = false; + uint count = 0; + LocaleConstant locale = handler.GetSessionDbcLocale(); + + // Search in TaxiNodes.dbc + foreach (var nodeEntry in CliDB.TaxiNodesStorage.Values) + { + string name = nodeEntry.Name[locale]; + if (string.IsNullOrEmpty(name)) + continue; + + if (!name.Like(namePart)) + continue; + + if (maxlookup != 0 && count++ == maxlookup) + { + handler.SendSysMessage(CypherStrings.CommandLookupMaxResults, maxlookup); + return true; + } + + // send taxinode in "id - [name] (Map:m X:x Y:y Z:z)" format + if (handler.GetSession() != null) + handler.SendSysMessage(CypherStrings.TaxinodeEntryListChat, nodeEntry.Id, nodeEntry.Id, name, "", + nodeEntry.MapID, nodeEntry.Pos.X, nodeEntry.Pos.Y, nodeEntry.Pos.Z); + else + handler.SendSysMessage(CypherStrings.TaxinodeEntryListConsole, nodeEntry.Id, name, "", + nodeEntry.MapID, nodeEntry.Pos.X, nodeEntry.Pos.Y, nodeEntry.Pos.Z); + + if (!found) + found = true; + + } + if (!found) + handler.SendSysMessage(CypherStrings.CommandNotaxinodefound); + + return true; + } + + [Command("tele", RBACPermissions.CommandLookupTele, true)] + static bool Tele(StringArguments args, CommandHandler handler) + { + if (args.Empty()) + { + handler.SendSysMessage(CypherStrings.CommandTeleParameter); + return false; + } + + string namePart = args.NextString().ToLower(); + + StringBuilder reply = new StringBuilder(); + uint count = 0; + bool limitReached = false; + + foreach (var tele in Global.ObjectMgr.gameTeleStorage) + { + if (!tele.Value.name.Like(namePart)) + continue; + + if (maxlookup != 0 && count++ == maxlookup) + { + limitReached = true; + break; + } + + if (handler.GetPlayer() != null) + reply.AppendFormat(" |cffffffff|Htele:{0}|h[{1}]|h|r\n", tele.Key, tele.Value.name); + else + reply.AppendFormat(" {0} : {1}\n", tele.Key, tele.Value.name); + } + + if (reply.Capacity == 0) + handler.SendSysMessage(CypherStrings.CommandTeleNolocation); + else + handler.SendSysMessage(CypherStrings.CommandTeleLocation, reply.ToString()); + + if (limitReached) + handler.SendSysMessage(CypherStrings.CommandLookupMaxResults, maxlookup); + + return true; + } + + [Command("title", RBACPermissions.CommandLookupTitle, true)] + static bool Title(StringArguments args, CommandHandler handler) + { + if (args.Empty()) + return false; + + // can be NULL in console call + Player target = handler.getSelectedPlayer(); + + // title name have single string arg for player name + string targetName = target ? target.GetName() : "NAME"; + + string namePart = args.NextString(); + + uint counter = 0; // Counter for figure out that we found smth. + // Search in CharTitles.dbc + foreach (var titleInfo in CliDB.CharTitlesStorage.Values) + { + for (Gender gender = Gender.Male; gender <= Gender.Female; ++gender) + { + if (target && target.GetGender() != gender) + continue; + + LocaleConstant locale = handler.GetSessionDbcLocale(); + string name = gender == Gender.Male ? titleInfo.NameMale[locale]: titleInfo.NameFemale[locale]; + if (string.IsNullOrEmpty(name)) + continue; + + if (!name.Like(namePart)) + { + locale = 0; + for (; locale < LocaleConstant.Total; ++locale) + { + if (locale == handler.GetSessionDbcLocale()) + continue; + + name = (gender == Gender.Male ? titleInfo.NameMale : titleInfo.NameFemale)[locale]; + if (name.IsEmpty()) + continue; + + if (name.Like(namePart)) + break; + } + } + + if (locale < LocaleConstant.Total) + { + if (maxlookup != 0 && counter == maxlookup) + { + handler.SendSysMessage(CypherStrings.CommandLookupMaxResults, maxlookup); + return true; + } + + string knownStr = target && target.HasTitle(titleInfo) ? handler.GetCypherString(CypherStrings.Known) : ""; + + string activeStr = target && target.GetUInt32Value(PlayerFields.ChosenTitle) == titleInfo.MaskID + ? handler.GetCypherString(CypherStrings.Active) : ""; + + string titleNameStr = name + targetName; + + // send title in "id (idx:idx) - [namedlink locale]" format + if (handler.GetSession() != null) + handler.SendSysMessage(CypherStrings.TitleListChat, titleInfo.Id, titleInfo.MaskID, titleInfo.Id, titleNameStr, "", knownStr, activeStr); + else + handler.SendSysMessage(CypherStrings.TitleListConsole, titleInfo.Id, titleInfo.MaskID, titleNameStr, "", knownStr, activeStr); + + ++counter; + } + } + + } + if (counter == 0) // if counter == 0 then we found nth + handler.SendSysMessage(CypherStrings.CommandNotitlefound); + + return true; + } + + [Command("map", RBACPermissions.CommandLookupMap, true)] + static bool Map(StringArguments args, CommandHandler handler) + { + if (args.Empty()) + return false; + + string namePart = args.NextString(); + + uint counter = 0; + + // search in Map.dbc + foreach (var mapInfo in CliDB.MapStorage.Values) + { + LocaleConstant locale = handler.GetSessionDbcLocale(); + string name = mapInfo.MapName[locale]; + if (string.IsNullOrEmpty(name)) + continue; + + if (!name.Like(namePart) && handler.GetSession()) + { + locale = 0; + for (; locale < LocaleConstant.Total; ++locale) + { + if (locale == handler.GetSessionDbcLocale()) + continue; + + name = mapInfo.MapName[locale]; + if (name.IsEmpty()) + continue; + + if (name.Like(namePart)) + break; + } + } + + if (locale < LocaleConstant.Total) + { + if (maxlookup != 0 && counter == maxlookup) + { + handler.SendSysMessage(CypherStrings.CommandLookupMaxResults, maxlookup); + return true; + } + + StringBuilder ss = new StringBuilder(); + ss.Append(mapInfo.Id + " - [" + name + ']'); + + if (mapInfo.IsContinent()) + ss.Append(handler.GetCypherString(CypherStrings.Continent)); + + switch (mapInfo.InstanceType) + { + case MapTypes.Instance: + ss.Append(handler.GetCypherString(CypherStrings.Instance)); + break; + case MapTypes.Raid: + ss.Append(handler.GetCypherString(CypherStrings.Raid)); + break; + case MapTypes.Battleground: + ss.Append(handler.GetCypherString(CypherStrings.Battleground)); + break; + case MapTypes.Arena: + ss.Append(handler.GetCypherString(CypherStrings.Arena)); + break; + } + + handler.SendSysMessage(ss.ToString()); + + ++counter; + } + } + + if (counter == 0) + handler.SendSysMessage(CypherStrings.CommandNomapfound); + + return true; + } + + [CommandGroup("player", RBACPermissions.CommandLookupPlayer, true)] + class PlayerCommandGroup + { + [Command("ip", RBACPermissions.CommandLookupPlayerIp)] + static bool IP(StringArguments args, CommandHandler handler) + { + string ip; + int limit; + string limitStr; + + Player target = handler.getSelectedPlayer(); + if (args.Empty()) + { + // NULL only if used from console + if (!target || target == handler.GetSession().GetPlayer()) + return false; + + ip = target.GetSession().GetRemoteAddress(); + limit = -1; + } + else + { + ip = args.NextString(); + limitStr = args.NextString(); + limit = string.IsNullOrEmpty(limitStr) ? -1 : int.Parse(limitStr); + } + + PreparedStatement stmt = DB.Login.GetPreparedStatement(LoginStatements.SEL_ACCOUNT_BY_IP); + stmt.AddValue(0, ip); + return LookupPlayerSearchCommand(DB.Login.Query(stmt), limit, handler); + } + + [Command("account", RBACPermissions.CommandLookupPlayerAccount)] + static bool Account(StringArguments args, CommandHandler handler) + { + if (args.Empty()) + return false; + + string account = args.NextString(); + string limitStr = args.NextString(); + int limit = string.IsNullOrEmpty(limitStr) ? -1 : int.Parse(limitStr); + + PreparedStatement stmt = DB.Login.GetPreparedStatement(LoginStatements.SEL_ACCOUNT_LIST_BY_NAME); + stmt.AddValue(0, account); + return LookupPlayerSearchCommand(DB.Login.Query(stmt), limit, handler); + } + + [Command("email", RBACPermissions.CommandLookupPlayerEmail)] + static bool Email(StringArguments args, CommandHandler handler) + { + if (args.Empty()) + return false; + + string email = args.NextString(); + string limitStr = args.NextString(); + int limit = string.IsNullOrEmpty(limitStr) ? -1 : int.Parse(limitStr); + + PreparedStatement stmt = DB.Login.GetPreparedStatement(LoginStatements.SEL_ACCOUNT_LIST_BY_EMAIL); + stmt.AddValue(0, email); + return LookupPlayerSearchCommand(DB.Login.Query(stmt), limit, handler); + } + + static bool LookupPlayerSearchCommand(SQLResult result, int limit, CommandHandler handler) + { + if (result.IsEmpty()) + { + handler.SendSysMessage(CypherStrings.NoPlayersFound); + return false; + } + + int counter = 0; + uint count = 0; + do + { + if (maxlookup != 0 && count++ == maxlookup) + { + handler.SendSysMessage(CypherStrings.CommandLookupMaxResults, maxlookup); + return true; + } + uint accountId = result.Read(0); + string accountName = result.Read(1); + + PreparedStatement stmt = DB.Characters.GetPreparedStatement(CharStatements.SEL_CHAR_GUID_NAME_BY_ACC); + stmt.AddValue(0, accountId); + SQLResult result2 = DB.Characters.Query(stmt); + + if (!result2.IsEmpty()) + { + handler.SendSysMessage(CypherStrings.LookupPlayerAccount, accountName, accountId); + + do + { + ObjectGuid guid = ObjectGuid.Create(HighGuid.Player, result2.Read(0)); + string name = result2.Read(1); + + handler.SendSysMessage(CypherStrings.LookupPlayerCharacter, name, guid.ToString()); + ++counter; + } + while (result2.NextRow() && (limit == -1 || counter < limit)); + } + } + while (result.NextRow()); + + if (counter == 0) // empty accounts only + { + handler.SendSysMessage(CypherStrings.NoPlayersFound); + return false; + } + return true; + } + } + + [CommandGroup("spell",RBACPermissions.CommandLookupSpell)] + class SpellCommandGroup + { + [Command("", RBACPermissions.CommandLookupSpell)] + static bool Default(StringArguments args, CommandHandler handler) + { + if (args.Empty()) + return false; + + // can be NULL at console call + Player target = handler.getSelectedPlayer(); + + string namePart = args.NextString(); + + bool found = false; + uint count = 0; + + // Search in Spell.dbc + foreach (var spellInfo in Global.SpellMgr.GetSpellInfoStorage().Values) + { + LocaleConstant locale = handler.GetSessionDbcLocale(); + string name = spellInfo.SpellName[locale]; + if (name.IsEmpty()) + continue; + + if (!name.Like(namePart)) + { + locale = 0; + for (; locale < LocaleConstant.Total; ++locale) + { + if (locale == handler.GetSessionDbcLocale()) + continue; + + name = spellInfo.SpellName[locale]; + if (name.IsEmpty()) + continue; + + if (name.Like(namePart)) + break; + } + } + + if (locale < LocaleConstant.Total) + { + if (maxlookup != 0 && count++ == maxlookup) + { + handler.SendSysMessage(CypherStrings.CommandLookupMaxResults, maxlookup); + return true; + } + + bool known = target && target.HasSpell(spellInfo.Id); + SpellEffectInfo effect = spellInfo.GetEffect(0); + bool learn = (effect.Effect == SpellEffectName.LearnSpell); + + SpellInfo learnSpellInfo = Global.SpellMgr.GetSpellInfo(effect.TriggerSpell); + + bool talent = spellInfo.HasAttribute(SpellCustomAttributes.IsTalent); + bool passive = spellInfo.IsPassive(); + bool active = target && target.HasAura(spellInfo.Id); + + // unit32 used to prevent interpreting public byte as char at output + // find rank of learned spell for learning spell, or talent rank + uint rank = learn && learnSpellInfo != null ? learnSpellInfo.GetRank() : spellInfo.GetRank(); + + // send spell in "id - [name, rank N] [talent] [passive] [learn] [known]" format + StringBuilder ss = new StringBuilder(); + if (handler.GetSession() != null) + ss.Append(spellInfo.Id + " - |cffffffff|Hspell:" + spellInfo.Id + "|h[" + spellInfo.SpellName); + else + ss.Append(spellInfo.Id + " - " + spellInfo.SpellName); + + // include rank in link name + if (rank != 0) + ss.Append(handler.GetCypherString(CypherStrings.SpellRank) + rank); + + if (handler.GetSession() != null) + ss.Append("]|h|r"); + + if (talent) + ss.Append(handler.GetCypherString(CypherStrings.Talent)); + if (passive) + ss.Append(handler.GetCypherString(CypherStrings.Passive)); + if (learn) + ss.Append(handler.GetCypherString(CypherStrings.Learn)); + if (known) + ss.Append(handler.GetCypherString(CypherStrings.Known)); + if (active) + ss.Append(handler.GetCypherString(CypherStrings.Active)); + + handler.SendSysMessage(ss.ToString()); + + if (!found) + found = true; + } + + } + if (!found) + handler.SendSysMessage(CypherStrings.CommandNospellfound); + + return true; + } + + [Command("id", RBACPermissions.CommandLookupSpellId)] + static bool SpellId(StringArguments args, CommandHandler handler) + { + if (args.Empty()) + return false; + + // can be NULL at console call + Player target = handler.getSelectedPlayer(); + + uint id = args.NextUInt32(); + + SpellInfo spellInfo = Global.SpellMgr.GetSpellInfo(id); + if (spellInfo != null) + { + LocaleConstant locale = handler.GetSessionDbcLocale(); + string name = spellInfo.SpellName[locale]; + if (string.IsNullOrEmpty(name)) + { + handler.SendSysMessage(CypherStrings.CommandNospellfound); + return true; + } + + bool known = target && target.HasSpell(id); + SpellEffectInfo effect = spellInfo.GetEffect(0); + bool learn = (effect.Effect == SpellEffectName.LearnSpell); + + SpellInfo learnSpellInfo = Global.SpellMgr.GetSpellInfo(effect.TriggerSpell); + + bool talent = spellInfo.HasAttribute(SpellCustomAttributes.IsTalent); + bool passive = spellInfo.IsPassive(); + bool active = target && target.HasAura(id); + + // unit32 used to prevent interpreting public byte as char at output + // find rank of learned spell for learning spell, or talent rank + uint rank = learn && learnSpellInfo != null ? learnSpellInfo.GetRank() : spellInfo.GetRank(); + + // send spell in "id - [name, rank N] [talent] [passive] [learn] [known]" format + StringBuilder ss = new StringBuilder(); + if (handler.GetSession() != null) + ss.Append(id + " - |cffffffff|Hspell:" + id + "|h[" + name); + else + ss.Append(id + " - " + name); + + // include rank in link name + if (rank != 0) + ss.Append(handler.GetCypherString(CypherStrings.SpellRank) + rank); + + if (handler.GetSession() != null) + ss.Append("]|h|r"); + + if (talent) + ss.Append(handler.GetCypherString(CypherStrings.Talent)); + if (passive) + ss.Append(handler.GetCypherString(CypherStrings.Passive)); + if (learn) + ss.Append(handler.GetCypherString(CypherStrings.Learn)); + if (known) + ss.Append(handler.GetCypherString(CypherStrings.Known)); + if (active) + ss.Append(handler.GetCypherString(CypherStrings.Active)); + + handler.SendSysMessage(ss.ToString()); + } + else + handler.SendSysMessage(CypherStrings.CommandNospellfound); + + return true; + } + } + } +} diff --git a/Game/Chat/Commands/MMapsCommands.cs b/Game/Chat/Commands/MMapsCommands.cs new file mode 100644 index 000000000..1241f90f2 --- /dev/null +++ b/Game/Chat/Commands/MMapsCommands.cs @@ -0,0 +1,261 @@ +/* + * Copyright (C) 2012-2017 CypherCore + * + * 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, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +using Framework.Constants; +using Framework.IO; +using Game.Entities; +using Game.Maps; +using Game.Movement; +using System.Collections.Generic; + +namespace Game.Chat +{ + [CommandGroup("mmap", RBACPermissions.CommandMmap, true)] + class MMapsCommands + { + [Command("path", RBACPermissions.CommandMmapPath)] + static bool PathCommand(StringArguments args, CommandHandler handler) + { + if (Global.MMapMgr.GetNavMesh(handler.GetPlayer().GetMapId(), handler.GetSession().GetPlayer().GetTerrainSwaps()) == null) + { + handler.SendSysMessage("NavMesh not loaded for current map."); + return true; + } + + handler.SendSysMessage("mmap path:"); + + // units + Player player = handler.GetPlayer(); + Unit target = handler.getSelectedUnit(); + if (player == null || target == null) + { + handler.SendSysMessage("Invalid target/source selection."); + return true; + } + + string para = args.NextString(); + + bool useStraightPath = false; + if (para.Equals("true")) + useStraightPath = true; + + bool useStraightLine = false; + if (para.Equals("line")) + useStraightLine = true; + + // unit locations + float x, y, z; + player.GetPosition(out x, out y, out z); + + // path + PathGenerator path = new PathGenerator(target); + path.SetUseStraightPath(useStraightPath); + bool result = path.CalculatePath(x, y, z, false, useStraightLine); + + var pointPath = path.GetPath(); + handler.SendSysMessage("{0}'s path to {1}:", target.GetName(), player.GetName()); + handler.SendSysMessage("Building: {0}", useStraightPath ? "StraightPath" : useStraightLine ? "Raycast" : "SmoothPath"); + handler.SendSysMessage("Result: {0} - Length: {1} - Type: {2}", (result ? "true" : "false"), pointPath.Length, path.GetPathType()); + + var start = path.GetStartPosition(); + var end = path.GetEndPosition(); + var actualEnd = path.GetActualEndPosition(); + + handler.SendSysMessage("StartPosition ({0:F3}, {1:F3}, {2:F3})", start.X, start.Y, start.Z); + handler.SendSysMessage("EndPosition ({0:F3}, {1:F3}, {2:F3})", end.X, end.Y, end.Z); + handler.SendSysMessage("ActualEndPosition ({0:F3}, {1:F3}, {2:F3})", actualEnd.X, actualEnd.Y, actualEnd.Z); + + if (!player.IsGameMaster()) + handler.SendSysMessage("Enable GM mode to see the path points."); + + for (uint i = 0; i < pointPath.Length; ++i) + player.SummonCreature(1, pointPath[i].X, pointPath[i].Y, pointPath[i].Z, 0, TempSummonType.TimedDespawn, 9000); + + return true; + } + + [Command("loc", RBACPermissions.CommandMmapLoc)] + static bool LocCommand(StringArguments args, CommandHandler handler) + { + handler.SendSysMessage("mmap tileloc:"); + + // grid tile location + Player player = handler.GetPlayer(); + + int gx = (int)(32 - player.GetPositionX() / MapConst.SizeofGrids); + int gy = (int)(32 - player.GetPositionY() / MapConst.SizeofGrids); + + handler.SendSysMessage("{0:D4}{1:D2}{2:D2}.mmtile", player.GetMapId(), gy, gx); + handler.SendSysMessage("gridloc [{0}, {1}]", gx, gy); + + // calculate navmesh tile location + Detour.dtNavMesh navmesh = Global.MMapMgr.GetNavMesh(handler.GetPlayer().GetMapId(), handler.GetSession().GetPlayer().GetTerrainSwaps()); + Detour.dtNavMeshQuery navmeshquery = Global.MMapMgr.GetNavMeshQuery(handler.GetPlayer().GetMapId(), player.GetInstanceId(), handler.GetSession().GetPlayer().GetTerrainSwaps()); + if (navmesh == null || navmeshquery == null) + { + handler.SendSysMessage("NavMesh not loaded for current map."); + return true; + } + + float[] min = navmesh.getParams().orig; + float x, y, z; + player.GetPosition(out x, out y, out z); + float[] location = { y, z, x }; + float[] extents = { 3.0f, 5.0f, 3.0f }; + + int tilex = (int)((y - min[0]) / MapConst.SizeofGrids); + int tiley = (int)((x - min[2]) / MapConst.SizeofGrids); + + handler.SendSysMessage("Calc [{0:D2}, {1:D2}]", tilex, tiley); + + // navmesh poly . navmesh tile location + Detour.dtQueryFilter filter = new Detour.dtQueryFilter(); + float[] nothing = new float[3]; + ulong polyRef = 0; + if (Detour.dtStatusFailed(navmeshquery.findNearestPoly(location, extents, filter, ref polyRef, ref nothing))) + { + handler.SendSysMessage("Dt [??,??] (invalid poly, probably no tile loaded)"); + return true; + } + + if (polyRef == 0) + handler.SendSysMessage("Dt [??, ??] (invalid poly, probably no tile loaded)"); + else + { + Detour.dtMeshTile tile = new Detour.dtMeshTile(); + Detour.dtPoly poly = new Detour.dtPoly(); + if (Detour.dtStatusSucceed(navmesh.getTileAndPolyByRef(polyRef, ref tile, ref poly))) + { + if (tile != null) + { + handler.SendSysMessage("Dt [{0:D2},{1:D2}]", tile.header.x, tile.header.y); + return false; + } + } + + handler.SendSysMessage("Dt [??,??] (no tile loaded)"); + } + return true; + } + + [Command("loadedtiles", RBACPermissions.CommandMmapLoadedtiles)] + static bool LoadedTilesCommand(StringArguments args, CommandHandler handler) + { + uint mapid = handler.GetPlayer().GetMapId(); + Detour.dtNavMesh navmesh = Global.MMapMgr.GetNavMesh(mapid, handler.GetSession().GetPlayer().GetTerrainSwaps()); + Detour.dtNavMeshQuery navmeshquery = Global.MMapMgr.GetNavMeshQuery(mapid, handler.GetPlayer().GetInstanceId(), handler.GetSession().GetPlayer().GetTerrainSwaps()); + if (navmesh == null || navmeshquery == null) + { + handler.SendSysMessage("NavMesh not loaded for current map."); + return true; + } + + handler.SendSysMessage("mmap loadedtiles:"); + + for (int i = 0; i < navmesh.getMaxTiles(); ++i) + { + Detour.dtMeshTile tile = navmesh.getTile(i); + if (tile == null) + continue; + + handler.SendSysMessage("[{0:D2}, {1:D2}]", tile.header.x, tile.header.y); + } + return true; + } + + [Command("stats", RBACPermissions.CommandMmapStats)] + static bool HandleMmapStatsCommand(StringArguments args, CommandHandler handler) + { + uint mapId = handler.GetPlayer().GetMapId(); + handler.SendSysMessage("mmap stats:"); + handler.SendSysMessage(" global mmap pathfinding is {0}abled", Global.DisableMgr.IsPathfindingEnabled(mapId) ? "En" : "Dis"); + handler.SendSysMessage(" {0} maps loaded with {1} tiles overall", Global.MMapMgr.getLoadedMapsCount(), Global.MMapMgr.getLoadedTilesCount()); + + Detour.dtNavMesh navmesh = Global.MMapMgr.GetNavMesh(handler.GetPlayer().GetMapId(), handler.GetSession().GetPlayer().GetTerrainSwaps()); + if (navmesh == null) + { + handler.SendSysMessage("NavMesh not loaded for current map."); + return true; + } + + uint tileCount = 0; + int nodeCount = 0; + int polyCount = 0; + int vertCount = 0; + int triCount = 0; + int triVertCount = 0; + for (int i = 0; i < navmesh.getMaxTiles(); ++i) + { + Detour.dtMeshTile tile = navmesh.getTile(i); + if (tile == null) + continue; + + tileCount++; + nodeCount += tile.header.bvNodeCount; + polyCount += tile.header.polyCount; + vertCount += tile.header.vertCount; + triCount += tile.header.detailTriCount; + triVertCount += tile.header.detailVertCount; + } + + handler.SendSysMessage("Navmesh stats:"); + handler.SendSysMessage(" {0} tiles loaded", tileCount); + handler.SendSysMessage(" {0} BVTree nodes", nodeCount); + handler.SendSysMessage(" {0} polygons ({1} vertices)", polyCount, vertCount); + handler.SendSysMessage(" {0} triangles ({1} vertices)", triCount, triVertCount); + return true; + } + + [Command("testarea", RBACPermissions.CommandMmapTestarea)] + static bool TestArea(StringArguments args, CommandHandler handler) + { + float radius = 40.0f; + WorldObject obj = handler.GetPlayer(); + + // Get Creatures + List creatureList = new List(); + + var go_check = new AnyUnitInObjectRangeCheck(obj, radius); + var go_search = new UnitListSearcher(obj, creatureList, go_check); + + Cell.VisitGridObjects(obj, go_search, radius); + if (!creatureList.Empty()) + { + handler.SendSysMessage("Found {0} Creatures.", creatureList.Count); + + uint paths = 0; + uint uStartTime = Time.GetMSTime(); + + float gx, gy, gz; + obj.GetPosition(out gx, out gy, out gz); + foreach (var creature in creatureList) + { + PathGenerator path = new PathGenerator(creature); + path.CalculatePath(gx, gy, gz); + ++paths; + } + + uint uPathLoadTime = Time.GetMSTimeDiffToNow(uStartTime); + handler.SendSysMessage("Generated {0} paths in {1} ms", paths, uPathLoadTime); + } + else + handler.SendSysMessage("No creatures in {0} yard range.", radius); + + return true; + } + } +} diff --git a/Game/Chat/Commands/MessageCommands.cs b/Game/Chat/Commands/MessageCommands.cs new file mode 100644 index 000000000..c7504bd0e --- /dev/null +++ b/Game/Chat/Commands/MessageCommands.cs @@ -0,0 +1,233 @@ +/* + * Copyright (C) 2012-2017 CypherCore + * + * 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, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +using Framework.Constants; +using Framework.Database; +using Framework.IO; +using Game.DataStorage; +using Game.Entities; +using Game.Network.Packets; +using System; + +namespace Game.Chat +{ + class MessageCommands + { + [CommandNonGroup("nameannounce", RBACPermissions.CommandNameannounce, true)] + static bool HandleNameAnnounceCommand(StringArguments args, CommandHandler handler) + { + if (args.Empty()) + return false; + + string name = "Console"; + WorldSession session = handler.GetSession(); + if (session) + name = session.GetPlayer().GetName(); + + Global.WorldMgr.SendWorldText(CypherStrings.AnnounceColor, name, args); + return true; + } + + [CommandNonGroup("gmnameannounce", RBACPermissions.CommandGmnameannounce, true)] + static bool HandleGMNameAnnounceCommand(StringArguments args, CommandHandler handler) + { + if (args.Empty()) + return false; + + string name = "Console"; + WorldSession session = handler.GetSession(); + if (session) + name = session.GetPlayer().GetName(); + + Global.WorldMgr.SendGMText(CypherStrings.AnnounceColor, name, args); + return true; + } + + [CommandNonGroup("announce", RBACPermissions.CommandAnnounce, true)] + static bool HandleAnnounceCommand(StringArguments args, CommandHandler handler) + { + if (args.Empty()) + return false; + + string str = handler.GetParsedString(CypherStrings.Systemmessage, args.NextString("")); + Global.WorldMgr.SendServerMessage(ServerMessageType.String, str); + return true; + } + + [CommandNonGroup("gmannounce", RBACPermissions.CommandGmannounce, true)] + static bool HandleGMAnnounceCommand(StringArguments args, CommandHandler handler) + { + if (args.Empty()) + return false; + + Global.WorldMgr.SendGMText(CypherStrings.GmBroadcast, args.NextString("")); + return true; + } + + [CommandNonGroup("notify", RBACPermissions.CommandNotify, true)] + static bool HandleNotifyCommand(StringArguments args, CommandHandler handler) + { + if (args.Empty()) + return false; + + string str = handler.GetCypherString(CypherStrings.GlobalNotify); + str += args.NextString(""); + + Global.WorldMgr.SendGlobalMessage(new PrintNotification(str)); + + return true; + } + + [CommandNonGroup("gmnotify", RBACPermissions.CommandGmnotify, true)] + static bool HandleGMNotifyCommand(StringArguments args, CommandHandler handler) + { + if (args.Empty()) + return false; + + string str = handler.GetCypherString(CypherStrings.GmNotify); + str += args.NextString(""); + + Global.WorldMgr.SendGlobalGMMessage(new PrintNotification(str)); + + return true; + } + + [CommandNonGroup("whispers", RBACPermissions.CommandWhispers)] + static bool HandleWhispersCommand(StringArguments args, CommandHandler handler) + { + if (args.Empty()) + { + handler.SendSysMessage(CypherStrings.CommandWhisperaccepting, handler.GetSession().GetPlayer().isAcceptWhispers() ? handler.GetCypherString(CypherStrings.On) : handler.GetCypherString(CypherStrings.Off)); + return true; + } + + string argStr = args.NextString(); + // whisper on + if (argStr == "on") + { + handler.GetSession().GetPlayer().SetAcceptWhispers(true); + handler.SendSysMessage(CypherStrings.CommandWhisperon); + return true; + } + + // whisper off + if (argStr == "off") + { + // Remove all players from the Gamemaster's whisper whitelist + handler.GetSession().GetPlayer().ClearWhisperWhiteList(); + handler.GetSession().GetPlayer().SetAcceptWhispers(false); + handler.SendSysMessage(CypherStrings.CommandWhisperoff); + return true; + } + + if (argStr == "remove") + { + string name = args.NextString(); + if (ObjectManager.NormalizePlayerName(ref name)) + { + Player player = Global.ObjAccessor.FindPlayerByName(name); + if (player) + { + handler.GetSession().GetPlayer().RemoveFromWhisperWhiteList(player.GetGUID()); + handler.SendSysMessage(CypherStrings.CommandWhisperoffplayer, name); + return true; + } + else + { + handler.SendSysMessage(CypherStrings.PlayerNotFound, name); + return false; + } + } + } + handler.SendSysMessage(CypherStrings.UseBol); + return false; + } + } + + [CommandGroup("channel", RBACPermissions.CommandChannel, true)] + class ChannelCommands + { + [CommandGroup("set", RBACPermissions.CommandChannelSet, true)] + class ChannelSetCommands + { + [Command("ownership", RBACPermissions.CommandChannelSetOwnership)] + static bool HandleChannelSetOwnership(StringArguments args, CommandHandler handler) + { + if (args.Empty()) + return false; + + string channelStr = args.NextString(); + string argStr = args.NextString(""); + + if (channelStr.IsEmpty() || argStr.IsEmpty()) + return false; + + uint channelId = 0; + foreach (var channelEntry in CliDB.ChatChannelsStorage.Values) + { + if (channelEntry.Name[handler.GetSessionDbcLocale()].Equals(channelStr)) + { + channelId = channelEntry.Id; + break; + } + } + + AreaTableRecord zoneEntry = null; + foreach (var entry in CliDB.AreaTableStorage.Values) + { + if (entry.AreaName[handler.GetSessionDbcLocale()].Equals(channelStr)) + { + zoneEntry = entry; + break; + } + } + + Player player = handler.GetSession().GetPlayer(); + Channel channel = null; + + ChannelManager cMgr = ChannelManager.ForTeam(player.GetTeam()); + if (cMgr != null) + channel = cMgr.GetChannel(channelId, channelStr, player, false, zoneEntry); + + if (argStr.ToLower() == "on") + { + if (channel != null) + channel.SetOwnership(true); + PreparedStatement stmt = DB.Characters.GetPreparedStatement(CharStatements.UPD_CHANNEL_OWNERSHIP); + stmt.AddValue(0, 1); + stmt.AddValue(1, channelStr); + DB.Characters.Execute(stmt); + handler.SendSysMessage(CypherStrings.ChannelEnableOwnership, channelStr); + } + else if (argStr.ToLower() == "off") + { + if (channel != null) + channel.SetOwnership(false); + PreparedStatement stmt = DB.Characters.GetPreparedStatement(CharStatements.UPD_CHANNEL_OWNERSHIP); + stmt.AddValue(0, 0); + stmt.AddValue(1, channelStr); + DB.Characters.Execute(stmt); + handler.SendSysMessage(CypherStrings.ChannelDisableOwnership, channelStr); + } + else + return false; + + return true; + } + } + } +} diff --git a/Game/Chat/Commands/MiscCommands.cs b/Game/Chat/Commands/MiscCommands.cs new file mode 100644 index 000000000..2979556ca --- /dev/null +++ b/Game/Chat/Commands/MiscCommands.cs @@ -0,0 +1,2463 @@ +/* + * Copyright (C) 2012-2017 CypherCore + * + * 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, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +using Framework.Collections; +using Framework.Constants; +using Framework.Database; +using Framework.IO; +using Game.DataStorage; +using Game.Entities; +using Game.Groups; +using Game.Maps; +using Game.Movement; +using Game.Network.Packets; +using Game.Spells; +using System; +using System.Collections.Generic; +using System.Linq; + +namespace Game.Chat +{ + class MiscCommands + { + [CommandNonGroup("commands", RBACPermissions.CommandCommands, true)] + static bool Commands(StringArguments args, CommandHandler handler) + { + string list = ""; + foreach (var command in CommandManager.GetCommands()) + { + if (handler.IsAvailable(command)) + { + if (handler.GetSession() != null) + list += "\n "; + else + list += "\n\r "; + + list += command.Name; + + if (!command.ChildCommands.Empty()) + list += " ..."; + } + } + + if (list.IsEmpty()) + return false; + + handler.SendSysMessage(CypherStrings.AvailableCmd); + handler.SendSysMessage("{0}", list); + return true; + } + + [CommandNonGroup("help", RBACPermissions.CommandHelp, true)] + static bool Help(StringArguments args, CommandHandler handler) + { + string cmd = args.NextString(""); + if (cmd.IsEmpty()) + { + handler.ShowHelpForCommand(CommandManager.GetCommands(), "help"); + handler.ShowHelpForCommand(CommandManager.GetCommands(), ""); + } + else + { + if (!handler.ShowHelpForCommand(CommandManager.GetCommands(), cmd)) + handler.SendSysMessage(CypherStrings.NoHelpCmd); + } + + return true; + } + + [CommandNonGroup("revive", RBACPermissions.CommandRevive, true)] + static bool Revive(StringArguments args, CommandHandler handler) + { + Player target; + ObjectGuid targetGuid; + if (!handler.extractPlayerTarget(args, out target, out targetGuid)) + return false; + + if (target != null) + { + target.ResurrectPlayer(0.5f); + target.SpawnCorpseBones(); + target.SaveToDB(); + } + else + Player.OfflineResurrect(targetGuid, null); + + return true; + } + + [CommandNonGroup("die", RBACPermissions.CommandDie)] + static bool Die(StringArguments args, CommandHandler handler) + { + Unit target = handler.getSelectedUnit(); + + if (!target && handler.GetPlayer().GetTarget().IsEmpty()) + { + handler.SendSysMessage(CypherStrings.SelectCharOrCreature); + return false; + } + + Player player = target.ToPlayer(); + if (player) + if (handler.HasLowerSecurity(player, ObjectGuid.Empty, false)) + return false; + + if (target.IsAlive()) + handler.GetSession().GetPlayer().Kill(target); + + return true; + } + + [CommandNonGroup("gps", RBACPermissions.CommandGps)] + static bool HandleGPSCommand(StringArguments args, CommandHandler handler) + { + WorldObject obj = null; + if (!args.Empty()) + { + HighGuid guidHigh = 0; + ulong guidLow = handler.extractLowGuidFromLink(args, ref guidHigh); + if (guidLow == 0) + return false; + switch (guidHigh) + { + case HighGuid.Player: + { + obj = Global.ObjAccessor.FindPlayer(ObjectGuid.Create(HighGuid.Player, guidLow)); + if (!obj) + { + handler.SendSysMessage(CypherStrings.PlayerNotFound); + } + break; + } + case HighGuid.Creature: + { + obj = handler.GetCreatureFromPlayerMapByDbGuid(guidLow); + if (!obj) + { + handler.SendSysMessage(CypherStrings.CommandNocreaturefound); + } + break; + } + case HighGuid.GameObject: + { + obj = handler.GetObjectFromPlayerMapByDbGuid(guidLow); + if (!obj) + { + handler.SendSysMessage(CypherStrings.CommandNogameobjectfound); + } + break; + } + default: + return false; + } + if (!obj) + return false; + } + else + { + obj = handler.getSelectedUnit(); + + if (!obj) + { + handler.SendSysMessage(CypherStrings.SelectCharOrCreature); + return false; + } + } + + CellCoord cellCoord = GridDefines.ComputeCellCoord(obj.GetPositionX(), obj.GetPositionY()); + Cell cell = new Cell(cellCoord); + + uint zoneId, areaId; + obj.GetZoneAndAreaId(out zoneId, out areaId); + uint mapId = obj.GetMapId(); + + MapRecord mapEntry = CliDB.MapStorage.LookupByKey(mapId); + AreaTableRecord zoneEntry = CliDB.AreaTableStorage.LookupByKey(zoneId); + AreaTableRecord areaEntry = CliDB.AreaTableStorage.LookupByKey(areaId); + + float zoneX = obj.GetPositionX(); + float zoneY = obj.GetPositionY(); + + Global.DB2Mgr.Map2ZoneCoordinates(zoneId, ref zoneX, ref zoneY); + + Map map = obj.GetMap(); + float groundZ = map.GetHeight(obj.GetPhases(), obj.GetPositionX(), obj.GetPositionY(), MapConst.MaxHeight); + float floorZ = map.GetHeight(obj.GetPhases(), obj.GetPositionX(), obj.GetPositionY(), obj.GetPositionZ()); + + GridCoord gridCoord = GridDefines.ComputeGridCoord(obj.GetPositionX(), obj.GetPositionY()); + + // 63? WHY? + uint gridX = (MapConst.MaxGrids - 1) - gridCoord.x_coord; + uint gridY = (MapConst.MaxGrids - 1) - gridCoord.y_coord; + + bool haveMap = Map.ExistMap(mapId, gridX, gridY); + bool haveVMap = Map.ExistVMap(mapId, gridX, gridY); + bool haveMMap = (Global.DisableMgr.IsPathfindingEnabled(mapId) && Global.MMapMgr.GetNavMesh(handler.GetSession().GetPlayer().GetMapId(), handler.GetSession().GetPlayer().GetTerrainSwaps()) != null); + + if (haveVMap) + { + if (map.IsOutdoors(obj.GetPositionX(), obj.GetPositionY(), obj.GetPositionZ())) + handler.SendSysMessage("You are outdoors"); + else + handler.SendSysMessage("You are indoors"); + } + else + handler.SendSysMessage("no VMAP available for area info"); + + string unknown = handler.GetCypherString(CypherStrings.Unknown); + + handler.SendSysMessage(CypherStrings.MapPosition, + mapId, (mapEntry != null ? mapEntry.MapName[handler.GetSessionDbcLocale()] : unknown), + zoneId, (zoneEntry != null ? zoneEntry.AreaName[handler.GetSessionDbcLocale()] : unknown), + areaId, (areaEntry != null ? areaEntry.AreaName[handler.GetSessionDbcLocale()] : unknown), + obj.GetPhaseMask(), string.Join(", ", obj.GetPhases()), obj.GetPositionX(), obj.GetPositionY(), obj.GetPositionZ(), obj.GetOrientation()); + + Transport transport = obj.GetTransport(); + if (transport) + { + handler.SendSysMessage(CypherStrings.TransportPosition, transport.GetGoInfo().MoTransport.SpawnMap, obj.GetTransOffsetX(), obj.GetTransOffsetY(), obj.GetTransOffsetZ(), obj.GetTransOffsetO(), + transport.GetEntry(), transport.GetName()); + } + + handler.SendSysMessage(CypherStrings.GridPosition, cell.GetGridX(), cell.GetGridY(), cell.GetCellX(), cell.GetCellY(), obj.GetInstanceId(), + zoneX, zoneY, groundZ, floorZ, haveMap, haveVMap, haveMMap); + + LiquidData liquidStatus; + ZLiquidStatus status = map.getLiquidStatus(obj.GetPositionX(), obj.GetPositionY(), obj.GetPositionZ(), MapConst.MapAllLiquidTypes, out liquidStatus); + + if (liquidStatus != null) + handler.SendSysMessage(CypherStrings.LiquidStatus, liquidStatus.level, liquidStatus.depth_level, liquidStatus.entry, liquidStatus.type_flags, status); + + if (!obj.GetTerrainSwaps().Empty()) + { + string ss = ""; + foreach (uint swap in obj.GetTerrainSwaps()) + ss += swap + " "; + handler.SendSysMessage("Target's active terrain swaps: {0}", ss); + } + if (!obj.GetWorldMapAreaSwaps().Empty()) + { + string ss = ""; + foreach (uint swap in obj.GetWorldMapAreaSwaps()) + ss += swap + " "; + handler.SendSysMessage("Target's active world map area swaps: {0}", ss); + } + + return true; + } + + [CommandNonGroup("additem", RBACPermissions.CommandAdditem)] + static bool AddItem(StringArguments args, CommandHandler handler) + { + if (args.Empty()) + return false; + + uint itemId = 0; + + if (args[0] == '[') // [name] manual form + { + string itemName = args.NextString("]"); + + if (!string.IsNullOrEmpty(itemName)) + { + var record = CliDB.ItemSparseStorage.Values.FirstOrDefault(itemSparse => + { + for (LocaleConstant i = 0; i < LocaleConstant.Max; ++i) + if (itemName == itemSparse.Name[i]) + return true; + return false; + }); + + if (record == null) + { + handler.SendSysMessage(CypherStrings.CommandCouldnotfind, itemName); + return false; + } + itemId = record.Id; + } + else + return false; + } + else // item_id or [name] Shift-click form |color|Hitem:item_id:0:0:0|h[name]|h|r + { + string id = handler.extractKeyFromLink(args, "Hitem"); + if (string.IsNullOrEmpty(id)) + return false; + + if (!uint.TryParse(id, out itemId)) + return false; + } + + int count = args.NextInt32(); + if (count == 0) + count = 1; + + List bonusListIDs = new List(); + var bonuses = args.NextString(); + + // semicolon separated bonuslist ids (parse them after all arguments are extracted by strtok!) + if (!bonuses.IsEmpty()) + { + var tokens = new StringArray(bonuses, ';'); + for (var i = 0; i < tokens.Length; ++i) + bonusListIDs.Add(uint.Parse(tokens[i])); + } + + Player player = handler.GetSession().GetPlayer(); + Player playerTarget = handler.getSelectedPlayer(); + if (!playerTarget) + playerTarget = player; + + Log.outDebug(LogFilter.Server, Global.ObjectMgr.GetCypherString(CypherStrings.Additem), itemId, count); + + ItemTemplate itemTemplate = Global.ObjectMgr.GetItemTemplate(itemId); + if (itemTemplate == null) + { + handler.SendSysMessage(CypherStrings.CommandItemidinvalid, itemId); + return false; + } + + // Subtract + if (count < 0) + { + playerTarget.DestroyItemCount(itemId, (uint)-count, true, false); + handler.SendSysMessage(CypherStrings.Removeitem, itemId, -count, handler.GetNameLink(playerTarget)); + return true; + } + + // Adding items + uint noSpaceForCount = 0; + + // check space and find places + List dest = new List(); + InventoryResult msg = playerTarget.CanStoreNewItem(ItemConst.NullBag, ItemConst.NullSlot, dest, itemId, (uint)count, out noSpaceForCount); + if (msg != InventoryResult.Ok) // convert to possible store amount + count -= (int)noSpaceForCount; + + if (count == 0 || dest.Empty()) // can't add any + { + handler.SendSysMessage(CypherStrings.ItemCannotCreate, itemId, noSpaceForCount); + return false; + } + + Item item = playerTarget.StoreNewItem(dest, itemId, true, ItemEnchantment.GenerateItemRandomPropertyId(itemId), null, 0, bonusListIDs); + + // remove binding (let GM give it to another player later) + if (player == playerTarget) + { + foreach (var posCount in dest) + { + Item item1 = player.GetItemByPos(posCount.pos); + if (item1) + item1.SetBinding(false); + } + } + + if (count > 0 && item) + { + player.SendNewItem(item, (uint)count, false, true); + if (player != playerTarget) + playerTarget.SendNewItem(item, (uint)count, true, false); + } + + if (noSpaceForCount > 0) + handler.SendSysMessage(CypherStrings.ItemCannotCreate, itemId, noSpaceForCount); + + return true; + } + + [CommandNonGroup("additemset", RBACPermissions.CommandAdditemset)] + static bool AddItemset(StringArguments args, CommandHandler handler) + { + if (args.Empty()) + return false; + + string id = handler.extractKeyFromLink(args, "Hitemset"); // number or [name] Shift-click form |color|Hitemset:itemset_id|h[name]|h|r + if (string.IsNullOrEmpty(id)) + return false; + + uint itemSetId = uint.Parse(id); + + // prevent generation all items with itemset field value '0' + if (itemSetId == 0) + { + handler.SendSysMessage(CypherStrings.NoItemsFromItemsetFound, itemSetId); + return false; + } + + List bonusListIDs = new List(); + var bonuses = args.NextString(); + + // semicolon separated bonuslist ids (parse them after all arguments are extracted by strtok!) + if (!bonuses.IsEmpty()) + { + var tokens = new StringArray(bonuses, ';'); + for (var i = 0; i < tokens.Length; ++i) + bonusListIDs.Add(uint.Parse(tokens[i])); + } + + Player player = handler.GetSession().GetPlayer(); + Player playerTarget = handler.getSelectedPlayer(); + if (!playerTarget) + playerTarget = player; + + Log.outDebug(LogFilter.Server, Global.ObjectMgr.GetCypherString(CypherStrings.Additemset), itemSetId); + + bool found = false; + var its = Global.ObjectMgr.GetItemTemplates(); + foreach (var template in its) + { + if (template.Value.GetItemSet() == itemSetId) + { + found = true; + List dest = new List(); + InventoryResult msg = playerTarget.CanStoreNewItem(ItemConst.NullBag, ItemConst.NullSlot, dest, template.Value.GetId(), 1); + if (msg == InventoryResult.Ok) + { + Item item = playerTarget.StoreNewItem(dest, template.Value.GetId(), true, ItemRandomEnchantmentId.Empty, null, 0, bonusListIDs); + + // remove binding (let GM give it to another player later) + if (player == playerTarget) + item.SetBinding(false); + + player.SendNewItem(item, 1, false, true); + if (player != playerTarget) + playerTarget.SendNewItem(item, 1, true, false); + } + else + { + player.SendEquipError(msg, null, null, template.Value.GetId()); + handler.SendSysMessage(CypherStrings.ItemCannotCreate, template.Value.GetId(), 1); + } + } + } + + if (!found) + { + handler.SendSysMessage(CypherStrings.CommandNoitemsetfound, itemSetId); + return false; + } + return true; + } + + [CommandNonGroup("dev", RBACPermissions.CommandDev)] + static bool HandleDevCommand(StringArguments args, CommandHandler handler) + { + if (args.Empty()) + { + if (handler.GetSession().GetPlayer().HasFlag(PlayerFields.Flags, PlayerFlags.Developer)) + handler.GetSession().SendNotification(CypherStrings.DevOn); + else + handler.GetSession().SendNotification(CypherStrings.DevOff); + return true; + } + + string argstr = args.NextString(); + + if (argstr == "on") + { + handler.GetSession().GetPlayer().SetFlag(PlayerFields.Flags, PlayerFlags.Developer); + handler.GetSession().SendNotification(CypherStrings.DevOn); + return true; + } + + if (argstr == "off") + { + handler.GetSession().GetPlayer().RemoveFlag(PlayerFields.Flags, PlayerFlags.Developer); + handler.GetSession().SendNotification(CypherStrings.DevOff); + return true; + } + + handler.SendSysMessage(CypherStrings.UseBol); + return false; + } + + // Teleport to Player + [CommandNonGroup("appear", RBACPermissions.CommandAppear)] + static bool Appear(StringArguments args, CommandHandler handler) + { + Player target; + ObjectGuid targetGuid; + string targetName; + if (!handler.extractPlayerTarget(args, out target, out targetGuid, out targetName)) + return false; + + Player _player = handler.GetSession().GetPlayer(); + if (target == _player || targetGuid == _player.GetGUID()) + { + handler.SendSysMessage(CypherStrings.CantTeleportSelf); + return false; + } + + if (target) + { + // check online security + if (handler.HasLowerSecurity(target, ObjectGuid.Empty)) + return false; + + string chrNameLink = handler.playerLink(targetName); + + Map map = target.GetMap(); + if (map.IsBattlegroundOrArena()) + { + // only allow if gm mode is on + if (!_player.IsGameMaster()) + { + handler.SendSysMessage(CypherStrings.CannotGoToBgGm, chrNameLink); + return false; + } + // if both players are in different bgs + else if (_player.GetBattlegroundId() != 0 && _player.GetBattlegroundId() != target.GetBattlegroundId()) + _player.LeaveBattleground(false); // Note: should be changed so _player gets no Deserter debuff + + // all's well, set bg id + // when porting out from the bg, it will be reset to 0 + _player.SetBattlegroundId(target.GetBattlegroundId(), target.GetBattlegroundTypeId()); + // remember current position as entry point for return at bg end teleportation + if (!_player.GetMap().IsBattlegroundOrArena()) + _player.SetBattlegroundEntryPoint(); + } + else if (map.IsDungeon()) + { + // we have to go to instance, and can go to player only if: + // 1) we are in his group (either as leader or as member) + // 2) we are not bound to any group and have GM mode on + if (_player.GetGroup()) + { + // we are in group, we can go only if we are in the player group + if (_player.GetGroup() != target.GetGroup()) + { + handler.SendSysMessage(CypherStrings.CannotGoToInstParty, chrNameLink); + return false; + } + } + else + { + // we are not in group, let's verify our GM mode + if (!_player.IsGameMaster()) + { + handler.SendSysMessage(CypherStrings.CannotGoToInstGm, chrNameLink); + return false; + } + } + + // if the player or the player's group is bound to another instance + // the player will not be bound to another one + InstanceBind bind = _player.GetBoundInstance(target.GetMapId(), target.GetDifficultyID(map.GetEntry())); + if (bind == null) + { + Group group = _player.GetGroup(); + // if no bind exists, create a solo bind + InstanceBind gBind = group ? group.GetBoundInstance(target) : null; // if no bind exists, create a solo bind + if (gBind == null) + { + InstanceSave save = Global.InstanceSaveMgr.GetInstanceSave(target.GetInstanceId()); + if (save != null) + _player.BindToInstance(save, !save.CanReset()); + } + } + + if (map.IsRaid()) + { + _player.SetRaidDifficultyID(target.GetRaidDifficultyID()); + _player.SetLegacyRaidDifficultyID(target.GetLegacyRaidDifficultyID()); + } + else + _player.SetDungeonDifficultyID(target.GetDungeonDifficultyID()); + } + + handler.SendSysMessage(CypherStrings.AppearingAt, chrNameLink); + + // stop flight if need + if (_player.IsInFlight()) + { + _player.GetMotionMaster().MovementExpired(); + _player.CleanupAfterTaxiFlight(); + } + // save only in non-flight case + else + _player.SaveRecallPosition(); + + // to point to see at target with same orientation + float x, y, z; + target.GetContactPoint(_player, out x, out y, out z); + + _player.TeleportTo(target.GetMapId(), x, y, z, _player.GetAngle(target), TeleportToOptions.GMMode); + _player.CopyPhaseFrom(target, true); + } + else + { + // check offline security + if (handler.HasLowerSecurity(null, targetGuid)) + return false; + + string nameLink = handler.playerLink(targetName); + + handler.SendSysMessage(CypherStrings.AppearingAt, nameLink); + + // to point where player stay (if loaded) + WorldLocation loc; + bool in_flight; + if (!Player.LoadPositionFromDB(out loc, out in_flight, targetGuid)) + return false; + + // stop flight if need + if (_player.IsInFlight()) + { + _player.GetMotionMaster().MovementExpired(); + _player.CleanupAfterTaxiFlight(); + } + // save only in non-flight case + else + _player.SaveRecallPosition(); + + loc.SetOrientation(_player.GetOrientation()); + _player.TeleportTo(loc); + } + + return true; + } + + // Summon Player + [CommandNonGroup("summon", RBACPermissions.CommandSummon)] + static bool Summon(StringArguments args, CommandHandler handler) + { + Player target; + ObjectGuid targetGuid; + string targetName; + if (!handler.extractPlayerTarget(args, out target, out targetGuid, out targetName)) + return false; + + Player _player = handler.GetSession().GetPlayer(); + if (target == _player || targetGuid == _player.GetGUID()) + { + handler.SendSysMessage(CypherStrings.CantTeleportSelf); + + return false; + } + + if (target) + { + string nameLink = handler.playerLink(targetName); + // check online security + if (handler.HasLowerSecurity(target, ObjectGuid.Empty)) + return false; + + if (target.IsBeingTeleported()) + { + handler.SendSysMessage(CypherStrings.IsTeleported, nameLink); + + return false; + } + + Map map = handler.GetSession().GetPlayer().GetMap(); + + if (map.IsBattlegroundOrArena()) + { + // only allow if gm mode is on + if (!_player.IsGameMaster()) + { + handler.SendSysMessage(CypherStrings.CannotGoToBgGm, nameLink); + return false; + } + // if both players are in different bgs + else if (target.GetBattlegroundId() != 0 && handler.GetSession().GetPlayer().GetBattlegroundId() != target.GetBattlegroundId()) + target.LeaveBattleground(false); // Note: should be changed so target gets no Deserter debuff + + // all's well, set bg id + // when porting out from the bg, it will be reset to 0 + target.SetBattlegroundId(handler.GetSession().GetPlayer().GetBattlegroundId(), handler.GetSession().GetPlayer().GetBattlegroundTypeId()); + // remember current position as entry point for return at bg end teleportation + if (!target.GetMap().IsBattlegroundOrArena()) + target.SetBattlegroundEntryPoint(); + } + else if (map.IsDungeon()) + { + Map destMap = target.GetMap(); + + if (destMap.Instanceable() && destMap.GetInstanceId() != map.GetInstanceId()) + target.UnbindInstance(map.GetInstanceId(), target.GetDungeonDifficultyID(), true); + + // we are in an instance, and can only summon players in our group with us as leader + if (handler.GetSession().GetPlayer().GetGroup() || !target.GetGroup() || + (target.GetGroup().GetLeaderGUID() != handler.GetSession().GetPlayer().GetGUID()) || + (handler.GetSession().GetPlayer().GetGroup().GetLeaderGUID() != handler.GetSession().GetPlayer().GetGUID())) + // the last check is a bit excessive, but let it be, just in case + { + handler.SendSysMessage(CypherStrings.CannotSummonToInst, nameLink); + + return false; + } + } + + handler.SendSysMessage(CypherStrings.Summoning, nameLink, ""); + if (handler.needReportToTarget(target)) + target.SendSysMessage(CypherStrings.SummonedBy, handler.playerLink(_player.GetName())); + + // stop flight if need + if (target.IsInFlight()) + { + target.GetMotionMaster().MovementExpired(); + target.CleanupAfterTaxiFlight(); + } + // save only in non-flight case + else + target.SaveRecallPosition(); + + // before GM + float x, y, z; + handler.GetSession().GetPlayer().GetClosePoint(out x, out y, out z, target.GetObjectSize()); + target.TeleportTo(handler.GetSession().GetPlayer().GetMapId(), x, y, z, target.GetOrientation()); + target.CopyPhaseFrom(handler.GetSession().GetPlayer(), true); + } + else + { + // check offline security + if (handler.HasLowerSecurity(null, targetGuid)) + return false; + + string nameLink = handler.playerLink(targetName); + + handler.SendSysMessage(CypherStrings.Summoning, nameLink, handler.GetCypherString(CypherStrings.Offline)); + + // in point where GM stay + Player.SavePositionInDB(new WorldLocation(handler.GetSession().GetPlayer().GetMapId(), + handler.GetSession().GetPlayer().GetPositionX(), + handler.GetSession().GetPlayer().GetPositionY(), + handler.GetSession().GetPlayer().GetPositionZ(), + handler.GetSession().GetPlayer().GetOrientation()), + handler.GetSession().GetPlayer().GetZoneId(), + targetGuid); + } + + return true; + } + + [CommandNonGroup("dismount", RBACPermissions.CommandDismount)] + static bool Dismount(StringArguments args, CommandHandler handler) + { + Player player = handler.GetSession().GetPlayer(); + + // If player is not mounted, so go out :) + if (!player.IsMounted()) + { + handler.SendSysMessage(CypherStrings.CharNonMounted); + return false; + } + + if (player.IsInFlight()) + { + handler.SendSysMessage(CypherStrings.YouInFlight); + return false; + } + + player.Dismount(); + player.RemoveAurasByType(AuraType.Mounted); + return true; + } + + [CommandNonGroup("guid", RBACPermissions.CommandGuid)] + static bool Guid(StringArguments args, CommandHandler handler) + { + ObjectGuid guid = handler.GetSession().GetPlayer().GetTarget(); + + if (guid.IsEmpty()) + { + handler.SendSysMessage(CypherStrings.NoSelection); + return false; + } + + handler.SendSysMessage(CypherStrings.ObjectGuid, guid.ToString(), guid.GetHigh()); + return true; + } + + // move item to other slot + [CommandNonGroup("itemmove", RBACPermissions.CommandItemmove)] + static bool ItemMove(StringArguments args, CommandHandler handler) + { + if (args.Empty()) + return false; + + byte srcSlot = args.NextByte(); + byte dstSlot = args.NextByte(); + + if (srcSlot == dstSlot) + return true; + + if (handler.GetSession().GetPlayer().IsValidPos(InventorySlots.Bag0, srcSlot, true)) + return false; + + if (handler.GetSession().GetPlayer().IsValidPos(InventorySlots.Bag0, dstSlot, false)) + return false; + + ushort src = (ushort)((InventorySlots.Bag0 << 8) | srcSlot); + ushort dst = (ushort)((InventorySlots.Bag0 << 8) | dstSlot); + + handler.GetSession().GetPlayer().SwapItem(src, dst); + + return true; + } + + [CommandNonGroup("distance", RBACPermissions.CommandDistance)] + static bool HandleGetDistanceCommand(StringArguments args, CommandHandler handler) + { + WorldObject obj = null; + + if (!args.Empty()) + { + HighGuid guidHigh = 0; + ulong guidLow = handler.extractLowGuidFromLink(args, ref guidHigh); + if (guidLow == 0) + return false; + switch (guidHigh) + { + case HighGuid.Player: + { + obj = Global.ObjAccessor.FindPlayer(ObjectGuid.Create(HighGuid.Player, guidLow)); + if (!obj) + { + handler.SendSysMessage(CypherStrings.PlayerNotFound); + } + break; + } + case HighGuid.Creature: + { + obj = handler.GetCreatureFromPlayerMapByDbGuid(guidLow); + if (!obj) + { + handler.SendSysMessage(CypherStrings.CommandNocreaturefound); + } + break; + } + case HighGuid.GameObject: + { + obj = handler.GetObjectFromPlayerMapByDbGuid(guidLow); + if (!obj) + { + handler.SendSysMessage(CypherStrings.CommandNogameobjectfound); + } + break; + } + default: + return false; + } + if (!obj) + return false; + } + else + { + obj = handler.getSelectedUnit(); + + if (!obj) + { + handler.SendSysMessage(CypherStrings.SelectCharOrCreature); + return false; + } + } + + handler.SendSysMessage(CypherStrings.Distance, handler.GetSession().GetPlayer().GetDistance(obj), handler.GetSession().GetPlayer().GetDistance2d(obj), handler.GetSession().GetPlayer().GetExactDist(obj), handler.GetSession().GetPlayer().GetExactDist2d(obj)); + return true; + } + + // Teleport player to last position + [CommandNonGroup("recall", RBACPermissions.CommandRecall)] + static bool Recall(StringArguments args, CommandHandler handler) + { + Player target; + if (!handler.extractPlayerTarget(args, out target)) + return false; + + // check online security + if (handler.HasLowerSecurity(target, ObjectGuid.Empty)) + return false; + + if (target.IsBeingTeleported()) + { + handler.SendSysMessage(CypherStrings.IsTeleported, handler.GetNameLink(target)); + + return false; + } + + // stop flight if need + if (target.IsInFlight()) + { + target.GetMotionMaster().MovementExpired(); + target.CleanupAfterTaxiFlight(); + } + + target.Recall(); + return true; + } + + [CommandNonGroup("save", RBACPermissions.CommandSave)] + static bool Save(StringArguments args, CommandHandler handler) + { + Player player = handler.GetSession().GetPlayer(); + + // save GM account without delay and output message + if (handler.GetSession().HasPermission(RBACPermissions.CommandsSaveWithoutDelay)) + { + Player target = handler.getSelectedPlayer(); + if (target) + target.SaveToDB(); + else + player.SaveToDB(); + handler.SendSysMessage(CypherStrings.PlayerSaved); + return true; + } + + // save if the player has last been saved over 20 seconds ago + uint saveInterval = WorldConfig.GetUIntValue(WorldCfg.IntervalSave); + if (saveInterval == 0 || (saveInterval > 20 * Time.InMilliseconds && player.GetSaveTimer() <= saveInterval - 20 * Time.InMilliseconds)) + player.SaveToDB(); + + return true; + } + + // Save all players in the world + [CommandNonGroup("saveall", RBACPermissions.CommandSaveall, true)] + static bool SaveAll(StringArguments args, CommandHandler handler) + { + Global.ObjAccessor.SaveAllPlayers(); + handler.SendSysMessage(CypherStrings.PlayersSaved); + return true; + } + + // kick player + [CommandNonGroup("kick", RBACPermissions.CommandKick, true)] + static bool Kick(StringArguments args, CommandHandler handler) + { + Player target = null; + string playerName; + ObjectGuid guid; + if (!handler.extractPlayerTarget(args, out target, out guid, out playerName)) + return false; + + if (handler.GetSession() != null && target == handler.GetSession().GetPlayer()) + { + handler.SendSysMessage(CypherStrings.CommandKickself); + return false; + } + + // check online security + if (handler.HasLowerSecurity(target, ObjectGuid.Empty)) + return false; + + string kickReason = args.NextString(""); + string kickReasonStr = "No reason"; + if (kickReason != null) + kickReasonStr = kickReason; + + if (WorldConfig.GetBoolValue(WorldCfg.ShowKickInWorld)) + Global.WorldMgr.SendWorldText(CypherStrings.CommandKickmessageWorld, (handler.GetSession() != null ? handler.GetSession().GetPlayerName() : "Server"), playerName, kickReasonStr); + else + handler.SendSysMessage(CypherStrings.CommandKickmessage, playerName); + + target.GetSession().KickPlayer(); + + return true; + } + + [CommandNonGroup("unstuck", RBACPermissions.CommandUnstuck, true)] + static bool HandleUnstuckCommand(StringArguments args, CommandHandler handler) + { + uint SPELL_UNSTUCK_ID = 7355; + uint SPELL_UNSTUCK_VISUAL = 2683; + + // No args required for players + if (handler.GetSession() != null && handler.GetSession().HasPermission(RBACPermissions.CommandsUseUnstuckWithArgs)) + { + // 7355: "Stuck" + var player1 = handler.GetSession().GetPlayer(); + if (player1) + player1.CastSpell(player1, SPELL_UNSTUCK_ID, false); + return true; + } + + if (args.Empty()) + return false; + + string location_str = "inn"; + string loc = args.NextString(); + if (string.IsNullOrEmpty(loc)) + location_str = loc; + + Player player = null; + ObjectGuid targetGUID; + if (!handler.extractPlayerTarget(args, out player, out targetGUID)) + return false; + + if (!player) + { + PreparedStatement stmt = DB.Characters.GetPreparedStatement(CharStatements.SEL_CHAR_HOMEBIND); + stmt.AddValue(0, targetGUID.GetCounter()); + SQLResult result = DB.Characters.Query(stmt); + if (!result.IsEmpty()) + { + Player.SavePositionInDB(new WorldLocation(result.Read(0), result.Read(2), result.Read(3), result.Read(4), 0.0f), result.Read(1), targetGUID); + return true; + } + + return false; + } + + if (player.IsInFlight() || player.IsInCombat()) + { + SpellInfo spellInfo = Global.SpellMgr.GetSpellInfo(SPELL_UNSTUCK_ID); + if (spellInfo == null) + return false; + + Player caster = handler.GetSession().GetPlayer(); + if (caster) + { + ObjectGuid castId = ObjectGuid.Create(HighGuid.Cast, SpellCastSource.Normal, player.GetMapId(), SPELL_UNSTUCK_ID, player.GetMap().GenerateLowGuid(HighGuid.Cast)); + Spell.SendCastResult(caster, spellInfo, SPELL_UNSTUCK_VISUAL, castId, SpellCastResult.CantDoThatRightNow); + } + + return false; + } + + if (location_str == "inn") + { + var home = player.GetHomebind(); + player.TeleportTo(home.GetMapId(), home.GetPositionX(), home.GetPositionY(), home.GetPositionZ(), player.GetOrientation()); + return true; + } + + if (location_str == "graveyard") + { + player.RepopAtGraveyard(); + return true; + } + + if (location_str == "startzone") + { + player.TeleportTo(player.GetStartPosition()); + return true; + } + + //Not a supported argument + return false; + } + + [CommandNonGroup("linkgrave", RBACPermissions.CommandLinkgrave)] + static bool LinkGrave(StringArguments args, CommandHandler handler) + { + if (args.Empty()) + return false; + + uint graveyardId = args.NextUInt32(); + if (graveyardId == 0) + return false; + + string px2 = args.NextString(); + Team team; + if (string.IsNullOrEmpty(px2)) + team = 0; + else if (px2 == "horde") + team = Team.Horde; + else if (px2 == "alliance") + team = Team.Alliance; + else + return false; + + WorldSafeLocsRecord graveyard = CliDB.WorldSafeLocsStorage.LookupByKey(graveyardId); + if (graveyard == null) + { + handler.SendSysMessage(CypherStrings.CommandGraveyardnoexist, graveyardId); + return false; + } + + Player player = handler.GetSession().GetPlayer(); + + uint zoneId = player.GetZoneId(); + + AreaTableRecord areaEntry = CliDB.AreaTableStorage.LookupByKey(zoneId); + if (areaEntry == null || areaEntry.ParentAreaID != 0) + { + handler.SendSysMessage(CypherStrings.CommandGraveyardwrongzone, graveyardId, zoneId); + return false; + } + + if (Global.ObjectMgr.AddGraveYardLink(graveyardId, zoneId, team)) + handler.SendSysMessage(CypherStrings.CommandGraveyardlinked, graveyardId, zoneId); + else + handler.SendSysMessage(CypherStrings.CommandGraveyardalrlinked, graveyardId, zoneId); + + return true; + } + + [CommandNonGroup("neargrave", RBACPermissions.CommandNeargrave)] + static bool NearGrave(StringArguments args, CommandHandler handler) + { + string px2 = args.NextString(); + Team team; + if (string.IsNullOrEmpty(px2)) + team = 0; + else if (px2 == "horde") + team = Team.Horde; + else if (px2 == "alliance") + team = Team.Alliance; + else + return false; + + Player player = handler.GetSession().GetPlayer(); + uint zone_id = player.GetZoneId(); + + WorldSafeLocsRecord graveyard = Global.ObjectMgr.GetClosestGraveYard(player.GetPositionX(), player.GetPositionY(), player.GetPositionZ(), player.GetMapId(), team); + if (graveyard != null) + { + uint graveyardId = graveyard.Id; + + GraveYardData data = Global.ObjectMgr.FindGraveYardData(graveyardId, zone_id); + if (data == null) + { + handler.SendSysMessage(CypherStrings.CommandGraveyarderror, graveyardId); + return false; + } + + team = (Team)data.team; + + string team_name = handler.GetCypherString(CypherStrings.CommandGraveyardNoteam); + + if (team == 0) + team_name = handler.GetCypherString(CypherStrings.CommandGraveyardAny); + else if (team == Team.Horde) + team_name = handler.GetCypherString(CypherStrings.CommandGraveyardHorde); + else if (team == Team.Alliance) + team_name = handler.GetCypherString(CypherStrings.CommandGraveyardAlliance); + + handler.SendSysMessage(CypherStrings.CommandGraveyardnearest, graveyardId, team_name, zone_id); + } + else + { + string team_name = ""; + + if (team == Team.Horde) + team_name = handler.GetCypherString(CypherStrings.CommandGraveyardHorde); + else if (team == Team.Alliance) + team_name = handler.GetCypherString(CypherStrings.CommandGraveyardAlliance); + + if (team == 0) + handler.SendSysMessage(CypherStrings.CommandZonenograveyards, zone_id); + else + handler.SendSysMessage(CypherStrings.CommandZonenografaction, zone_id, team_name); + } + + return true; + } + + [CommandNonGroup("showarea", RBACPermissions.CommandShowarea)] + static bool ShowArea(StringArguments args, CommandHandler handler) + { + if (args.Empty()) + return false; + + Player playerTarget = handler.getSelectedPlayer(); + if (!playerTarget) + { + handler.SendSysMessage(CypherStrings.NoCharSelected); + return false; + } + + AreaTableRecord area = CliDB.AreaTableStorage.LookupByKey(args.NextUInt32()); + if (area == null) + { + handler.SendSysMessage(CypherStrings.BadValue); + return false; + } + + if (area.AreaBit < 0) + { + handler.SendSysMessage(CypherStrings.BadValue); + return false; + } + + int offset = area.AreaBit / 32; + if (offset >= PlayerConst.ExploredZonesSize) + { + handler.SendSysMessage(CypherStrings.BadValue); + return false; + } + + uint val = (1u << (area.AreaBit % 32)); + uint currFields = playerTarget.GetUInt32Value(PlayerFields.ExploredZones1 + offset); + playerTarget.SetUInt32Value(PlayerFields.ExploredZones1 + offset, (currFields | val)); + + handler.SendSysMessage(CypherStrings.ExploreArea); + return true; + } + + [CommandNonGroup("hidearea", RBACPermissions.CommandHidearea)] + static bool HideArea(StringArguments args, CommandHandler handler) + { + if (args.Empty()) + return false; + + Player playerTarget = handler.getSelectedPlayer(); + if (!playerTarget) + { + handler.SendSysMessage(CypherStrings.NoCharSelected); + return false; + } + + AreaTableRecord area = CliDB.AreaTableStorage.LookupByKey(args.NextUInt32()); + if (area == null) + { + handler.SendSysMessage(CypherStrings.BadValue); + return false; + } + + if (area.AreaBit < 0) + { + handler.SendSysMessage(CypherStrings.BadValue); + return false; + } + + int offset = area.AreaBit / 32; + if (offset >= PlayerConst.ExploredZonesSize) + { + handler.SendSysMessage(CypherStrings.BadValue); + return false; + } + + uint val = (1u << (area.AreaBit % 32)); + uint currFields = playerTarget.GetUInt32Value(PlayerFields.ExploredZones1 + offset); + playerTarget.SetUInt32Value(PlayerFields.ExploredZones1 + offset, (currFields ^ val)); + + handler.SendSysMessage(CypherStrings.UnexploreArea); + return true; + } + + [CommandNonGroup("bank", RBACPermissions.CommandBank)] + static bool Bank(StringArguments args, CommandHandler handler) + { + handler.GetSession().SendShowBank(handler.GetSession().GetPlayer().GetGUID()); + return true; + } + + [CommandNonGroup("wchange", RBACPermissions.CommandWchange)] + static bool WChange(StringArguments args, CommandHandler handler) + { + if (args.Empty()) + return false; + + // Weather is OFF + if (!WorldConfig.GetBoolValue(WorldCfg.Weather)) + { + handler.SendSysMessage(CypherStrings.WeatherDisabled); + return false; + } + + // *Change the weather of a cell + string px = args.NextString(); + string py = args.NextString(); + + if (string.IsNullOrEmpty(px) || string.IsNullOrEmpty(py)) + return false; + + uint type = uint.Parse(px); //0 to 3, 0: fine, 1: rain, 2: snow, 3: sand + float grade = float.Parse(py); //0 to 1, sending -1 is instand good weather + + Player player = handler.GetSession().GetPlayer(); + uint zoneid = player.GetZoneId(); + + Weather weather = Global.WeatherMgr.FindWeather(zoneid); + + if (weather == null) + weather = Global.WeatherMgr.AddWeather(zoneid); + if (weather == null) + { + handler.SendSysMessage(CypherStrings.NoWeather); + return false; + } + + weather.SetWeather((WeatherType)type, grade); + return true; + } + + [CommandNonGroup("pinfo", RBACPermissions.CommandPinfo, true)] + static bool HandlePInfoCommand(StringArguments args, CommandHandler handler) + { + // Define ALL the player variables! + Player target; + ObjectGuid targetGuid; + string targetName; + PreparedStatement stmt = null; + + // To make sure we get a target, we convert our guid to an omniversal... + ObjectGuid parseGUID = ObjectGuid.Create(HighGuid.Player, args.NextUInt64()); + + // ... and make sure we get a target, somehow. + if (ObjectManager.GetPlayerNameByGUID(parseGUID, out targetName)) + { + target = Global.ObjAccessor.FindPlayer(parseGUID); + targetGuid = parseGUID; + } + // if not, then return false. Which shouldn't happen, now should it ? + else if (!handler.extractPlayerTarget(args, out target, out targetGuid, out targetName)) + return false; + + /* The variables we extract for the command. They are + * default as "does not exist" to prevent problems + * The output is printed in the follow manner: + * + * Player %s %s (guid: %u) - I. LANG_PINFO_PLAYER + * ** GM Mode active, Phase: -1 - II. LANG_PINFO_GM_ACTIVE (if GM) + * ** Banned: (Type, Reason, Time, By) - III. LANG_PINFO_BANNED (if banned) + * ** Muted: (Reason, Time, By) - IV. LANG_PINFO_MUTED (if muted) + * * Account: %s (id: %u), GM Level: %u - V. LANG_PINFO_ACC_ACCOUNT + * * Last Login: %u (Failed Logins: %u) - VI. LANG_PINFO_ACC_LASTLOGIN + * * Uses OS: %s - Latency: %u ms - VII. LANG_PINFO_ACC_OS + * * Registration Email: %s - Email: %s - VIII. LANG_PINFO_ACC_REGMAILS + * * Last IP: %u (Locked: %s) - IX. LANG_PINFO_ACC_IP + * * Level: %u (%u/%u XP (%u XP left) - X. LANG_PINFO_CHR_LEVEL + * * Race: %s %s, Class %s - XI. LANG_PINFO_CHR_RACE + * * Alive ?: %s - XII. LANG_PINFO_CHR_ALIVE + * * Phase: %s - XIII. LANG_PINFO_CHR_PHASE (if not GM) + * * Money: %ug%us%uc - XIV. LANG_PINFO_CHR_MONEY + * * Map: %s, Area: %s - XV. LANG_PINFO_CHR_MAP + * * Guild: %s (Id: %u) - XVI. LANG_PINFO_CHR_GUILD (if in guild) + * ** Rank: %s - XVII. LANG_PINFO_CHR_GUILD_RANK (if in guild) + * ** Note: %s - XVIII.LANG_PINFO_CHR_GUILD_NOTE (if in guild and has note) + * ** O. Note: %s - XVIX. LANG_PINFO_CHR_GUILD_ONOTE (if in guild and has officer note) + * * Played time: %s - XX. LANG_PINFO_CHR_PLAYEDTIME + * * Mails: %u Read/%u Total - XXI. LANG_PINFO_CHR_MAILS (if has mails) + * + * Not all of them can be moved to the top. These should + * place the most important ones to the head, though. + * + * For a cleaner overview, I segment each output in Roman numerals + */ + + // Account data print variables + string userName = handler.GetCypherString(CypherStrings.Error); + uint accId = 0; + ulong lowguid = targetGuid.GetCounter(); + string eMail = handler.GetCypherString(CypherStrings.Error); + string regMail = handler.GetCypherString(CypherStrings.Error); + uint security = 0; + string lastIp = handler.GetCypherString(CypherStrings.Error); + byte locked = 0; + string lastLogin = handler.GetCypherString(CypherStrings.Error); + uint failedLogins = 0; + uint latency = 0; + string OS = handler.GetCypherString(CypherStrings.Unknown); + + // Mute data print variables + long muteTime = -1; + string muteReason = handler.GetCypherString(CypherStrings.NoReason); + string muteBy = handler.GetCypherString(CypherStrings.Unknown); + + // Ban data print variables + long banTime = -1; + string banType = handler.GetCypherString(CypherStrings.Unknown); + string banReason = handler.GetCypherString(CypherStrings.NoReason); + string bannedBy = handler.GetCypherString(CypherStrings.Unknown); + + // Character data print variables + Race raceid; + Class classid; + Gender gender; + LocaleConstant locale = handler.GetSessionDbcLocale(); + uint totalPlayerTime = 0; + uint level = 0; + string alive = handler.GetCypherString(CypherStrings.Error); + ulong money = 0; + uint xp = 0; + uint xptotal = 0; + + // Position data print + uint mapId; + uint areaId; + List phases = new List(); + string areaName = handler.GetCypherString(CypherStrings.Unknown); + string zoneName = handler.GetCypherString(CypherStrings.Unknown); + + // Guild data print variables defined so that they exist, but are not necessarily used + ulong guildId = 0; + byte guildRankId = 0; + string guildName = ""; + string guildRank = ""; + string note = ""; + string officeNote = ""; + + // Mail data print is only defined if you have a mail + + if (target) + { + // check online security + if (handler.HasLowerSecurity(target, ObjectGuid.Empty)) + return false; + + accId = target.GetSession().GetAccountId(); + money = target.GetMoney(); + totalPlayerTime = target.GetTotalPlayedTime(); + level = target.getLevel(); + latency = target.GetSession().GetLatency(); + raceid = target.GetRace(); + classid = target.GetClass(); + muteTime = target.GetSession().m_muteTime; + mapId = target.GetMapId(); + areaId = target.GetAreaId(); + alive = target.IsAlive() ? handler.GetCypherString(CypherStrings.Yes) : handler.GetCypherString(CypherStrings.No); + gender = (Gender)target.GetByteValue(PlayerFields.Bytes3, PlayerFieldOffsets.Bytes3OffsetGender); + phases = target.GetPhases(); + } + // get additional information from DB + else + { + // check offline security + if (handler.HasLowerSecurity(null, targetGuid)) + return false; + + // Query informations from the DB + stmt = DB.Characters.GetPreparedStatement(CharStatements.SEL_CHAR_PINFO); + stmt.AddValue(0, lowguid); + SQLResult result = DB.Characters.Query(stmt); + + if (result.IsEmpty()) + return false; + + totalPlayerTime = result.Read(0); + level = result.Read(1); + money = result.Read(2); + accId = result.Read(3); + raceid = (Race)result.Read(4); + classid = (Class)result.Read(5); + mapId = result.Read(6); + areaId = result.Read(7); + gender = (Gender)result.Read(8); + uint health = result.Read(9); + PlayerFlags playerFlags = (PlayerFlags)result.Read(10); + + if (health == 0 || playerFlags.HasAnyFlag(PlayerFlags.Ghost)) + alive = handler.GetCypherString(CypherStrings.No); + else + alive = handler.GetCypherString(CypherStrings.Yes); + } + + // Query the prepared statement for login data + stmt = DB.Login.GetPreparedStatement(LoginStatements.SEL_PINFO); + stmt.AddValue(0, Global.WorldMgr.GetRealm().Id.Realm); + stmt.AddValue(1, accId); + SQLResult result0 = DB.Login.Query(stmt); + + if (!result0.IsEmpty()) + { + userName = result0.Read(0); + security = result0.Read(1); + + // Only fetch these fields if commander has sufficient rights) + if (handler.HasPermission(RBACPermissions.CommandsPinfoCheckPersonalData) && // RBAC Perm. 48, Role 39 + (!handler.GetSession() || handler.GetSession().GetSecurity() >= (AccountTypes)security)) + { + eMail = result0.Read (2); + regMail = result0.Read (3); + lastIp = result0.Read (4); + lastLogin = result0.Read (5); + } + else + { + eMail = handler.GetCypherString(CypherStrings.Unauthorized); + regMail = handler.GetCypherString(CypherStrings.Unauthorized); + lastIp = handler.GetCypherString(CypherStrings.Unauthorized); + lastLogin = handler.GetCypherString(CypherStrings.Unauthorized); + } + muteTime = (long)result0.Read (6); + muteReason = result0.Read (7); + muteBy = result0.Read (8); + failedLogins = result0.Read (9); + locked = result0.Read (10); + OS = result0.Read (11); + } + + // Creates a chat link to the character. Returns nameLink + string nameLink = handler.playerLink(targetName); + + // Returns banType, banTime, bannedBy, banreason + PreparedStatement stmt2 = DB.Login.GetPreparedStatement(LoginStatements.SEL_PINFO_BANS); + stmt2.AddValue(0, accId); + SQLResult result2 = DB.Login.Query(stmt2); + if (result2.IsEmpty()) + { + banType = handler.GetCypherString(CypherStrings.Character); + stmt = DB.Characters.GetPreparedStatement(CharStatements.SEL_PINFO_BANS); + stmt.AddValue(0, lowguid); + result2 = DB.Characters.Query(stmt); + } + + if (!result2.IsEmpty()) + { + banTime = (result2.Read(1) != 0 ? 0 : result2.Read(0)); + bannedBy = result2.Read(2); + banReason = result2.Read(3); + } + + // Can be used to query data from Characters database + stmt2 = DB.Characters.GetPreparedStatement(CharStatements.SEL_PINFO_XP); + stmt2.AddValue(0, lowguid); + SQLResult result4 = DB.Characters.Query(stmt2); + + if (!result4.IsEmpty()) + { + xp = result4.Read(0); // Used for "current xp" output and "%u XP Left" calculation + ulong gguid = result4.Read(1); // We check if have a guild for the person, so we might not require to query it at all + xptotal = Global.ObjectMgr.GetXPForLevel(level); + + if (gguid != 0) + { + // Guild Data - an own query, because it may not happen. + PreparedStatement stmt3 = DB.Characters.GetPreparedStatement(CharStatements.SEL_GUILD_MEMBER_EXTENDED); + stmt3.AddValue(0, lowguid); + SQLResult result5 = DB.Characters.Query(stmt3); + if (!result5.IsEmpty()) + { + guildId = result5.Read(0); + guildName = result5.Read(1); + guildRank = result5.Read(2); + guildRankId = result5.Read(3); + note = result5.Read(4); + officeNote = result5.Read(5); + } + } + } + + // Initiate output + // Output I. LANG_PINFO_PLAYER + handler.SendSysMessage(CypherStrings.PinfoPlayer, target ? "" : handler.GetCypherString(CypherStrings.Offline), nameLink, targetGuid.ToString()); + + // Output II. LANG_PINFO_GM_ACTIVE if character is gamemaster + if (target && target.IsGameMaster()) + handler.SendSysMessage(CypherStrings.PinfoGmActive); + + // Output III. LANG_PINFO_BANNED if ban exists and is applied + if (banTime >= 0) + handler.SendSysMessage(CypherStrings.PinfoBanned, banType, banReason, banTime > 0 ? Time.secsToTimeString((ulong)(banTime - Time.UnixTime), true) : handler.GetCypherString(CypherStrings.Permanently), bannedBy); + + // Output IV. LANG_PINFO_MUTED if mute is applied + if (muteTime > 0) + handler.SendSysMessage(CypherStrings.PinfoMuted, muteReason, Time.secsToTimeString((ulong)(muteTime - Time.UnixTime), true), muteBy); + + // Output V. LANG_PINFO_ACC_ACCOUNT + handler.SendSysMessage(CypherStrings.PinfoAccAccount, userName, accId, security); + + // Output VI. LANG_PINFO_ACC_LASTLOGIN + handler.SendSysMessage(CypherStrings.PinfoAccLastlogin, lastLogin, failedLogins); + + // Output VII. LANG_PINFO_ACC_OS + handler.SendSysMessage(CypherStrings.PinfoAccOs, OS, latency); + + // Output VIII. LANG_PINFO_ACC_REGMAILS + handler.SendSysMessage(CypherStrings.PinfoAccRegmails, regMail, eMail); + + // Output IX. LANG_PINFO_ACC_IP + handler.SendSysMessage(CypherStrings.PinfoAccIp, lastIp, locked != 0 ? handler.GetCypherString(CypherStrings.Yes) : handler.GetCypherString(CypherStrings.No)); + + // Output X. LANG_PINFO_CHR_LEVEL + if (level != WorldConfig.GetIntValue(WorldCfg.MaxPlayerLevel)) + handler.SendSysMessage(CypherStrings.PinfoChrLevelLow, level, xp, xptotal, (xptotal - xp)); + else + handler.SendSysMessage(CypherStrings.PinfoChrLevelHigh, level); + + // Output XI. LANG_PINFO_CHR_RACE + handler.SendSysMessage(CypherStrings.PinfoChrRace, (gender == 0 ? handler.GetCypherString(CypherStrings.CharacterGenderMale) : handler.GetCypherString(CypherStrings.CharacterGenderFemale)), + Global.DB2Mgr.GetChrRaceName(raceid, locale), Global.DB2Mgr.GetClassName(classid, locale)); + + // Output XII. LANG_PINFO_CHR_ALIVE + handler.SendSysMessage(CypherStrings.PinfoChrAlive, alive); + + // Output XIII. LANG_PINFO_CHR_PHASES + if (target && !phases.Empty()) + handler.SendSysMessage(CypherStrings.PinfoChrPhases, string.Join(", ", phases)); + + // Output XIV. LANG_PINFO_CHR_MONEY + ulong gold = money / MoneyConstants.Gold; + ulong silv = (money % MoneyConstants.Gold) / MoneyConstants.Silver; + ulong copp = (money % MoneyConstants.Gold) % MoneyConstants.Silver; + handler.SendSysMessage(CypherStrings.PinfoChrMoney, gold, silv, copp); + + // Position data + MapRecord map = CliDB.MapStorage.LookupByKey(mapId); + AreaTableRecord area = CliDB.AreaTableStorage.LookupByKey(areaId); + if (area != null) + { + areaName = area.AreaName[locale]; + + AreaTableRecord zone = CliDB.AreaTableStorage.LookupByKey(area.ParentAreaID); + if (zone != null) + zoneName = zone.AreaName[locale]; + } + + if (target) + handler.SendSysMessage(CypherStrings.PinfoChrMap, map.MapName[locale], + (!zoneName.IsEmpty() ? zoneName : handler.GetCypherString(CypherStrings.Unknown)), + (!areaName.IsEmpty() ? areaName : handler.GetCypherString(CypherStrings.Unknown))); + + // Output XVII. - XVIX. if they are not empty + if (!guildName.IsEmpty()) + { + handler.SendSysMessage(CypherStrings.PinfoChrGuild, guildName, guildId); + handler.SendSysMessage(CypherStrings.PinfoChrGuildRank, guildRank, guildRankId); + if (!note.IsEmpty()) + handler.SendSysMessage(CypherStrings.PinfoChrGuildNote, note); + if (!officeNote.IsEmpty()) + handler.SendSysMessage(CypherStrings.PinfoChrGuildOnote, officeNote); + } + + // Output XX. LANG_PINFO_CHR_PLAYEDTIME + handler.SendSysMessage(CypherStrings.PinfoChrPlayedtime, (Time.secsToTimeString(totalPlayerTime, true, true))); + + // Mail Data - an own query, because it may or may not be useful. + // SQL: "SELECT SUM(CASE WHEN (checked & 1) THEN 1 ELSE 0 END) AS 'readmail', COUNT(*) AS 'totalmail' FROM mail WHERE `receiver` = ?" + PreparedStatement stmt4 = DB.Characters.GetPreparedStatement(CharStatements.SEL_PINFO_MAILS); + stmt4.AddValue(0, lowguid); + SQLResult result6 = DB.Characters.Query(stmt4); + if (!result6.IsEmpty()) + { + uint readmail = (uint)result6.Read(0); + uint totalmail = (uint)result6.Read(1); + + // Output XXI. LANG_INFO_CHR_MAILS if at least one mail is given + if (totalmail >= 1) + handler.SendSysMessage(CypherStrings.PinfoChrMails, readmail, totalmail); + } + + return true; + } + + [CommandNonGroup("respawn", RBACPermissions.CommandRespawn)] + static bool Respawn(StringArguments args, CommandHandler handler) + { + Player player = handler.GetSession().GetPlayer(); + + // accept only explicitly selected target (not implicitly self targeting case) + Creature target = !player.GetTarget().IsEmpty() ? handler.getSelectedCreature() : null; + if (target) + { + if (target.IsPet()) + { + handler.SendSysMessage(CypherStrings.SelectCreature); + return false; + } + + if (target.IsDead()) + target.Respawn(); + return true; + } + + var worker = new WorldObjectWorker(player, new RespawnDo()); + Cell.VisitGridObjects(player, worker, player.GetGridActivationRange()); + + return true; + } + + // mute player for some times + [CommandNonGroup("mute", RBACPermissions.CommandMute, true)] + static bool Mute(StringArguments args, CommandHandler handler) + { + string nameStr; + string delayStr; + handler.extractOptFirstArg(args, out nameStr, out delayStr); + if (string.IsNullOrEmpty(delayStr)) + return false; + + string muteReason = args.NextString(); + string muteReasonStr = "No reason"; + if (muteReason != null) + muteReasonStr = muteReason; + + Player target; + ObjectGuid targetGuid; + string targetName; + if (!handler.extractPlayerTarget(new StringArguments(nameStr), out target, out targetGuid, out targetName)) + return false; + + uint accountId = target ? target.GetSession().GetAccountId() : ObjectManager.GetPlayerAccountIdByGUID(targetGuid); + + // find only player from same account if any + if (!target) + { + WorldSession session = Global.WorldMgr.FindSession(accountId); + if (session != null) + target = session.GetPlayer(); + } + + uint notSpeakTime = uint.Parse(delayStr); + + // must have strong lesser security level + if (handler.HasLowerSecurity(target, targetGuid, true)) + return false; + + PreparedStatement stmt = DB.Login.GetPreparedStatement(LoginStatements.UPD_MUTE_TIME); + string muteBy = ""; + if (handler.GetSession() != null) + muteBy = handler.GetSession().GetPlayerName(); + else + muteBy = "Console"; + + if (target) + { + // Target is online, mute will be in effect right away. + long muteTime = Time.UnixTime + notSpeakTime * Time.Minute; + target.GetSession().m_muteTime = muteTime; + stmt.AddValue(0, muteTime); + string nameLink = handler.playerLink(targetName); + + if (WorldConfig.GetBoolValue(WorldCfg.ShowMuteInWorld)) + { + Global.WorldMgr.SendWorldText(CypherStrings.CommandMutemessageWorld, (handler.GetSession() != null ? handler.GetSession().GetPlayerName() : "Server"), nameLink, notSpeakTime, muteReasonStr); + target.SendSysMessage(CypherStrings.YourChatDisabled, notSpeakTime, muteBy, muteReasonStr); + } + else + { + target.SendSysMessage(CypherStrings.YourChatDisabled, notSpeakTime, muteBy, muteReasonStr); + } + } + else + { + // Target is offline, mute will be in effect starting from the next login. + int muteTime = -(int)(notSpeakTime * Time.Minute); + stmt.AddValue(0, muteTime); + } + + stmt.AddValue(1, muteReasonStr); + stmt.AddValue(2, muteBy); + stmt.AddValue(3, accountId); + DB.Login.Execute(stmt); + string nameLink_ = handler.playerLink(targetName); + + if (WorldConfig.GetBoolValue(WorldCfg.ShowMuteInWorld) && !target) + Global.WorldMgr.SendWorldText(CypherStrings.CommandMutemessageWorld, handler.GetSession().GetPlayerName(), nameLink_, notSpeakTime, muteReasonStr); + else + handler.SendSysMessage(target ? CypherStrings.YouDisableChat : CypherStrings.CommandDisableChatDelayed, nameLink_, notSpeakTime, muteReasonStr); + return true; + } + + // unmute player + [CommandNonGroup("unmute", RBACPermissions.CommandUnmute, true)] + static bool UnMute(StringArguments args, CommandHandler handler) + { + Player target; + ObjectGuid targetGuid; + string targetName; + if (!handler.extractPlayerTarget(args, out target, out targetGuid, out targetName)) + return false; + + uint accountId = target ? target.GetSession().GetAccountId() : ObjectManager.GetPlayerAccountIdByGUID(targetGuid); + + // find only player from same account if any + if (!target) + { + WorldSession session = Global.WorldMgr.FindSession(accountId); + if (session != null) + target = session.GetPlayer(); + } + + // must have strong lesser security level + if (handler.HasLowerSecurity(target, targetGuid, true)) + return false; + + if (target) + { + if (target.CanSpeak()) + { + handler.SendSysMessage(CypherStrings.ChatAlreadyEnabled); + return false; + } + + target.GetSession().m_muteTime = 0; + } + + PreparedStatement stmt = DB.Login.GetPreparedStatement(LoginStatements.UPD_MUTE_TIME); + stmt.AddValue(0, 0); + stmt.AddValue(1, ""); + stmt.AddValue(2, ""); + stmt.AddValue(3, accountId); + DB.Login.Execute(stmt); + + if (target) + target.SendSysMessage(CypherStrings.YourChatEnabled); + + string nameLink = handler.playerLink(targetName); + + handler.SendSysMessage(CypherStrings.YouEnableChat, nameLink); + + return true; + } + + // mutehistory command + [CommandNonGroup("mutehistory", RBACPermissions.CommandMutehistory, true)] + static bool HandleMuteInfoCommand(StringArguments args, CommandHandler handler) + { + if (args.Empty()) + return false; + + string accountName = args.NextString(""); + if (accountName.IsEmpty()) + return false; + + uint accountId = Global.AccountMgr.GetId(accountName); + if (accountId == 0) + { + handler.SendSysMessage(CypherStrings.AccountNotExist, accountName); + return false; + } + + PreparedStatement stmt = DB.Login.GetPreparedStatement(LoginStatements.SEL_ACCOUNT_MUTE_INFO); + stmt.AddValue(0, accountId); + + SQLResult result = DB.Login.Query(stmt); + if (result.IsEmpty()) + { + handler.SendSysMessage(CypherStrings.CommandMutehistoryEmpty, accountName); + return true; + } + + handler.SendSysMessage(CypherStrings.CommandMutehistory, accountName); + do + { + // we have to manually set the string for mutedate + long sqlTime = result.Read(0); + + // set it to string + string buffer = Time.UnixTimeToDateTime(sqlTime).ToShortTimeString(); + + handler.SendSysMessage(CypherStrings.CommandMutehistoryOutput, buffer, result.Read(1), result.Read(2), result.Read(3)); + } while (result.NextRow()); + + return true; + } + + [CommandNonGroup("movegens", RBACPermissions.CommandMovegens)] + static bool MoveGens(StringArguments args, CommandHandler handler) + { + Unit unit = handler.getSelectedUnit(); + if (!unit) + { + handler.SendSysMessage(CypherStrings.SelectCharOrCreature); + + return false; + } + + handler.SendSysMessage(CypherStrings.MovegensList, (unit.IsTypeId(TypeId.Player) ? "Player" : "Creature"), unit.GetGUID().ToString()); + + MotionMaster motionMaster = unit.GetMotionMaster(); + float x, y, z; + motionMaster.GetDestination(out x, out y, out z); + + for (byte i = 0; i < (int)MovementSlot.Max; ++i) + { + IMovementGenerator movementGenerator = motionMaster.GetMotionSlot(i); + if (movementGenerator == null) + { + handler.SendSysMessage("Empty"); + continue; + } + + switch (movementGenerator.GetMovementGeneratorType()) + { + case MovementGeneratorType.Idle: + handler.SendSysMessage(CypherStrings.MovegensIdle); + break; + case MovementGeneratorType.Random: + handler.SendSysMessage(CypherStrings.MovegensRandom); + break; + case MovementGeneratorType.Waypoint: + handler.SendSysMessage(CypherStrings.MovegensWaypoint); + break; + case MovementGeneratorType.AnimalRandom: + handler.SendSysMessage(CypherStrings.MovegensAnimalRandom); + break; + case MovementGeneratorType.Confused: + handler.SendSysMessage(CypherStrings.MovegensConfused); + break; + case MovementGeneratorType.Chase: + { + Unit target = null; + if (unit.IsTypeId(TypeId.Player)) + target = ((ChaseMovementGenerator)movementGenerator).target; + else + target = ((ChaseMovementGenerator)movementGenerator).target; + + if (!target) + handler.SendSysMessage(CypherStrings.MovegensChaseNull); + else if (target.IsTypeId(TypeId.Player)) + handler.SendSysMessage(CypherStrings.MovegensChasePlayer, target.GetName(), target.GetGUID().ToString()); + else + handler.SendSysMessage(CypherStrings.MovegensChaseCreature, target.GetName(), target.GetGUID().ToString()); + break; + } + case MovementGeneratorType.Follow: + { + Unit target = null; + if (unit.IsTypeId(TypeId.Player)) + target = ((FollowMovementGenerator)movementGenerator).target; + else + target = ((FollowMovementGenerator)movementGenerator).target; + + if (!target) + handler.SendSysMessage(CypherStrings.MovegensFollowNull); + else if (target.IsTypeId(TypeId.Player)) + handler.SendSysMessage(CypherStrings.MovegensFollowPlayer, target.GetName(), target.GetGUID().ToString()); + else + handler.SendSysMessage(CypherStrings.MovegensFollowCreature, target.GetName(), target.GetGUID().ToString()); + break; + } + case MovementGeneratorType.Home: + { + if (unit.IsTypeId(TypeId.Unit)) + handler.SendSysMessage(CypherStrings.MovegensHomeCreature, x, y, z); + else + handler.SendSysMessage(CypherStrings.MovegensHomePlayer); + break; + } + case MovementGeneratorType.Flight: + handler.SendSysMessage(CypherStrings.MovegensFlight); + break; + case MovementGeneratorType.Point: + { + handler.SendSysMessage(CypherStrings.MovegensPoint, x, y, z); + break; + } + case MovementGeneratorType.Fleeing: + handler.SendSysMessage(CypherStrings.MovegensFear); + break; + case MovementGeneratorType.Distract: + handler.SendSysMessage(CypherStrings.MovegensDistract); + break; + case MovementGeneratorType.Effect: + handler.SendSysMessage(CypherStrings.MovegensEffect); + break; + default: + handler.SendSysMessage(CypherStrings.MovegensUnknown, movementGenerator.GetMovementGeneratorType()); + break; + } + } + return true; + } + + [CommandNonGroup("cometome", RBACPermissions.CommandCometome)] + static bool HandleComeToMeCommand(StringArguments args, CommandHandler handler) + { + Creature caster = handler.getSelectedCreature(); + if (!caster) + { + handler.SendSysMessage(CypherStrings.SelectCreature); + return false; + } + + Player player = handler.GetSession().GetPlayer(); + caster.GetMotionMaster().MovePoint(0, player.GetPositionX(), player.GetPositionY(), player.GetPositionZ()); + + return true; + } + + [CommandNonGroup("damage", RBACPermissions.CommandDamage)] + static bool Damage(StringArguments args, CommandHandler handler) + { + if (args.Empty()) + return false; + + string str = args.NextString(); + + if (str == "go") + { + ulong guidLow = args.NextUInt64(); + if (guidLow == 0) + { + handler.SendSysMessage(CypherStrings.BadValue); + return false; + } + + int damage = args.NextInt32(); + if (damage == 0) + { + handler.SendSysMessage(CypherStrings.BadValue); + return false; + } + Player player = handler.GetSession().GetPlayer(); + if (player) + { + GameObject go = handler.GetObjectFromPlayerMapByDbGuid(guidLow); + if (!go) + { + handler.SendSysMessage(CypherStrings.CommandObjnotfound, guidLow); + return false; + } + + if (!go.IsDestructibleBuilding()) + { + handler.SendSysMessage(CypherStrings.InvalidGameobjectType); + return false; + } + + go.ModifyHealth(-damage, player); + handler.SendSysMessage(CypherStrings.GameobjectDamaged, go.GetName(), guidLow, -damage, go.m_goValue.Building.Health); + } + + return true; + } + + Unit target = handler.getSelectedUnit(); + if (!target || handler.GetSession().GetPlayer().GetTarget().IsEmpty()) + { + handler.SendSysMessage(CypherStrings.SelectCharOrCreature); + return false; + } + Player player_ = target.ToPlayer(); + if (player_) + if (handler.HasLowerSecurity(player_, ObjectGuid.Empty, false)) + return false; + + if (!target.IsAlive()) + return true; + + int damage_int = int.Parse(str); + if (damage_int <= 0) + return true; + + uint damage_ = (uint)damage_int; + + string schoolStr = args.NextString(); + + // flat melee damage without resistence/etc reduction + if (string.IsNullOrEmpty(schoolStr)) + { + handler.GetSession().GetPlayer().DealDamage(target, damage_, null, DamageEffectType.Direct, SpellSchoolMask.Normal, null, false); + if (target != handler.GetSession().GetPlayer()) + handler.GetSession().GetPlayer().SendAttackStateUpdate(HitInfo.AffectsVictim, target, SpellSchoolMask.Normal, damage_, 0, 0, VictimState.Hit, 0); + return true; + } + + int school = int.Parse(schoolStr); + if (school >= (int)SpellSchools.Max) + return false; + + SpellSchoolMask schoolmask = (SpellSchoolMask)(1 << school); + + if (handler.GetSession().GetPlayer().IsDamageReducedByArmor(schoolmask)) + damage_ = handler.GetSession().GetPlayer().CalcArmorReducedDamage(handler.GetPlayer(), target, damage_, null, WeaponAttackType.BaseAttack); + + string spellStr = args.NextString(); + + // melee damage by specific school + if (string.IsNullOrEmpty(spellStr)) + { + uint absorb = 0; + uint resist = 0; + + handler.GetSession().GetPlayer().CalcAbsorbResist(target, schoolmask, DamageEffectType.SpellDirect, damage_, ref absorb, ref resist); + + if (damage_ <= absorb + resist) + return true; + + damage_ -= absorb + resist; + + handler.GetSession().GetPlayer().DealDamageMods(target, ref damage_, ref absorb); + handler.GetSession().GetPlayer().DealDamage(target, damage_, null, DamageEffectType.Direct, schoolmask, null, false); + handler.GetSession().GetPlayer().SendAttackStateUpdate(HitInfo.AffectsVictim, target, schoolmask, damage_, absorb, resist, VictimState.Hit, 0); + return true; + } + + // non-melee damage + // number or [name] Shift-click form |color|Hspell:spell_id|h[name]|h|r or Htalent form + uint spellid = handler.extractSpellIdFromLink(args); + SpellInfo spellInfo = Global.SpellMgr.GetSpellInfo(spellid); + if (spellInfo == null) + return false; + + SpellNonMeleeDamage damageInfo = new SpellNonMeleeDamage(handler.GetSession().GetPlayer(), target, spellid, spellInfo.GetSpellXSpellVisualId(handler.GetSession().GetPlayer()), spellInfo.SchoolMask); + damageInfo.damage = damage_; + handler.GetSession().GetPlayer().DealDamageMods(damageInfo.target, ref damageInfo.damage, ref damageInfo.absorb); + target.DealSpellDamage(damageInfo, true); + target.SendSpellNonMeleeDamageLog(damageInfo); + return true; + } + + [CommandNonGroup("combatstop", RBACPermissions.CommandCombatstop, true)] + static bool CombatStop(StringArguments args, CommandHandler handler) + { + Player target = null; + + if (!args.Empty()) + { + target = Global.ObjAccessor.FindPlayerByName(args.NextString()); + if (!target) + { + handler.SendSysMessage(CypherStrings.PlayerNotFound); + return false; + } + } + + if (!target) + { + if (!handler.extractPlayerTarget(args, out target)) + return false; + } + + // check online security + if (handler.HasLowerSecurity(target, ObjectGuid.Empty)) + return false; + + target.CombatStop(); + target.getHostileRefManager().deleteReferences(); + return true; + } + + [CommandNonGroup("repairitems", RBACPermissions.CommandRepairitems, true)] + static bool RepairItems(StringArguments args, CommandHandler handler) + { + Player target; + if (!handler.extractPlayerTarget(args, out target)) + return false; + + // check online security + if (handler.HasLowerSecurity(target, ObjectGuid.Empty)) + return false; + + // Repair items + target.DurabilityRepairAll(false, 0, false); + + handler.SendSysMessage(CypherStrings.YouRepairItems, handler.GetNameLink(target)); + if (handler.needReportToTarget(target)) + target.SendSysMessage(CypherStrings.YourItemsRepaired, handler.GetNameLink()); + + return true; + } + + [CommandNonGroup("freeze", RBACPermissions.CommandFreeze)] + static bool HandleFreezeCommand(StringArguments args, CommandHandler handler) + { + Player player = handler.getSelectedPlayer(); // Selected player, if any. Might be null. + int freezeDuration = 0; // Freeze Duration (in seconds) + bool canApplyFreeze = false; // Determines if every possible argument is set so Freeze can be applied + bool getDurationFromConfig = false; // If there's no given duration, we'll retrieve the world cfg value later + + if (args.Empty()) + { + // Might have a selected player. We'll check it later + // Get the duration from world cfg + getDurationFromConfig = true; + } + else + { + // Get the args that we might have (up to 2) + string arg1 = args.NextString(); + string arg2 = args.NextString(); + + // Analyze them to see if we got either a playerName or duration or both + if (!arg1.IsEmpty()) + { + if (arg1.IsNumber()) + { + // case 2: .freeze duration + // We have a selected player. We'll check him later + freezeDuration = int.Parse(arg1); + canApplyFreeze = true; + } + else + { + // case 3 or 4: .freeze player duration | .freeze player + // find the player + string name = arg1; + ObjectManager.NormalizePlayerName(ref name); + player = Global.ObjAccessor.FindPlayerByName(name); + // Check if we have duration set + if (!arg2.IsEmpty() && arg2.IsNumber()) + { + freezeDuration = int.Parse(arg2); + canApplyFreeze = true; + } + else + getDurationFromConfig = true; + } + } + } + + // Check if duration needs to be retrieved from config + if (getDurationFromConfig) + { + freezeDuration = WorldConfig.GetIntValue(WorldCfg.GmFreezeDuration); + canApplyFreeze = true; + } + + // Player and duration retrieval is over + if (canApplyFreeze) + { + if (!player) // can be null if some previous selection failed + { + handler.SendSysMessage(CypherStrings.CommandFreezeWrong); + return true; + } + else if (player == handler.GetSession().GetPlayer()) + { + // Can't freeze himself + handler.SendSysMessage(CypherStrings.CommandFreezeError); + return true; + } + else // Apply the effect + { + // Add the freeze aura and set the proper duration + // Player combat status and flags are now handled + // in Freeze Spell AuraScript (OnApply) + Aura freeze = player.AddAura(9454, player); + if (freeze != null) + { + if (freezeDuration != 0) + freeze.SetDuration(freezeDuration * Time.InMilliseconds); + handler.SendSysMessage(CypherStrings.CommandFreeze, player.GetName()); + // save player + player.SaveToDB(); + return true; + } + } + } + return false; + } + + [CommandNonGroup("unfreeze", RBACPermissions.CommandUnfreeze)] + static bool HandleUnFreezeCommand(StringArguments args, CommandHandler handler) + { + string name = ""; + Player player; + string targetName = args.NextString(); // Get entered name + + if (!string.IsNullOrEmpty(targetName)) + { + name = targetName; + ObjectManager.NormalizePlayerName(ref name); + player = Global.ObjAccessor.FindPlayerByName(name); + } + else // If no name was entered - use target + { + player = handler.getSelectedPlayer(); + if (player) + name = player.GetName(); + } + + if (player) + { + handler.SendSysMessage(CypherStrings.CommandUnfreeze, name); + + // Remove Freeze spell (allowing movement and spells) + // Player Flags + Neutral faction removal is now + // handled on the Freeze Spell AuraScript (OnRemove) + player.RemoveAurasDueToSpell(9454); + } + else + { + if (!string.IsNullOrEmpty(targetName)) + { + // Check for offline players + ObjectGuid guid = ObjectManager.GetPlayerGUIDByName(name); + if (guid.IsEmpty()) + { + handler.SendSysMessage(CypherStrings.CommandFreezeWrong); + return true; + } + + // If player found: delete his freeze aura + PreparedStatement stmt = DB.Characters.GetPreparedStatement(CharStatements.DEL_CHAR_AURA_FROZEN); + stmt.AddValue(0, guid.GetCounter()); + DB.Characters.Execute(stmt); + + handler.SendSysMessage(CypherStrings.CommandUnfreeze, name); + return true; + } + else + { + handler.SendSysMessage(CypherStrings.CommandFreezeWrong); + return true; + } + } + + return true; + } + + [CommandNonGroup("listfreeze", RBACPermissions.CommandListfreeze)] + static bool ListFreeze(StringArguments args, CommandHandler handler) + { + // Get names from DB + PreparedStatement stmt = DB.Characters.GetPreparedStatement(CharStatements.SEL_CHARACTER_AURA_FROZEN); + SQLResult result = DB.Characters.Query(stmt); + if (result.IsEmpty()) + { + handler.SendSysMessage(CypherStrings.CommandNoFrozenPlayers); + return true; + } + + // Header of the names + handler.SendSysMessage(CypherStrings.CommandListFreeze); + + // Output of the results + do + { + string player = result.Read(0); + int remaintime = result.Read(1); + // Save the frozen player to update remaining time in case of future .listfreeze uses + // before the frozen state expires + Player frozen = Global.ObjAccessor.FindPlayerByName(player); + if (frozen) + frozen.SaveToDB(); + // Notify the freeze duration + if (remaintime == -1) // Permanent duration + handler.SendSysMessage(CypherStrings.CommandPermaFrozenPlayer, player); + else + // show time left (seconds) + handler.SendSysMessage(CypherStrings.CommandTempFrozenPlayer, player, remaintime / Time.InMilliseconds); + } + while (result.NextRow()); + + return true; + } + + [CommandNonGroup("playall", RBACPermissions.CommandPlayall)] + static bool PlayAll(StringArguments args, CommandHandler handler) + { + if (args.Empty()) + return false; + + uint soundId = args.NextUInt32(); + + if (!CliDB.SoundKitStorage.ContainsKey(soundId)) + { + handler.SendSysMessage(CypherStrings.SoundNotExist, soundId); + return false; + } + + Global.WorldMgr.SendGlobalMessage(new PlaySound(handler.GetSession().GetPlayer().GetGUID(), soundId)); + + handler.SendSysMessage(CypherStrings.CommandPlayedToAll, soundId); + return true; + } + + [CommandNonGroup("possess", RBACPermissions.CommandPossess)] + static bool Possess(StringArguments args, CommandHandler handler) + { + Unit unit = handler.getSelectedUnit(); + if (!unit) + return false; + + handler.GetSession().GetPlayer().CastSpell(unit, 530, true); + return true; + } + + [CommandNonGroup("unpossess", RBACPermissions.CommandUnpossess)] + static bool UnPossess(StringArguments args, CommandHandler handler) + { + Unit unit = handler.getSelectedUnit(); + if (!unit) + unit = handler.GetSession().GetPlayer(); + + unit.RemoveCharmAuras(); + + return true; + } + + [CommandNonGroup("bindsight", RBACPermissions.CommandBindsight)] + static bool BindSight(StringArguments args, CommandHandler handler) + { + Unit unit = handler.getSelectedUnit(); + if (!unit) + return false; + + handler.GetSession().GetPlayer().CastSpell(unit, 6277, true); + return true; + } + + [CommandNonGroup("unbindsight", RBACPermissions.CommandUnbindsight)] + static bool UnbindSight(StringArguments args, CommandHandler handler) + { + Player player = handler.GetSession().GetPlayer(); + + if (player.isPossessing()) + return false; + + player.StopCastingBindSight(); + return true; + } + + [CommandNonGroup("mailbox", RBACPermissions.CommandMailbox)] + static bool MailBox(StringArguments args, CommandHandler handler) + { + Player player = handler.GetSession().GetPlayer(); + + handler.GetSession().SendShowMailBox(player.GetGUID()); + return true; + } + + [CommandNonGroup("pvpstats", RBACPermissions.CommandPvpstats, true)] + static bool HandlePvPstatsCommand(StringArguments args, CommandHandler handler) + { + if (WorldConfig.GetBoolValue(WorldCfg.BattlegroundStoreStatisticsEnable)) + { + PreparedStatement stmt = DB.Characters.GetPreparedStatement(CharStatements.SEL_PVPSTATS_FACTIONS_OVERALL); + SQLResult result = DB.Characters.Query(stmt); + + if (!result.IsEmpty()) + { + uint horde_victories = result.Read(1); + + if (!(result.NextRow())) + return false; + + uint alliance_victories = result.Read(1); + + handler.SendSysMessage(CypherStrings.Pvpstats, alliance_victories, horde_victories); + } + else + return false; + } + else + handler.SendSysMessage(CypherStrings.PvpstatsDisabled); + + return true; + } + } + + [CommandGroup("achievement", RBACPermissions.CommandAchievement)] + class AchievementCommand + { + [Command("add", RBACPermissions.CommandAchievementAdd)] + static bool Add(StringArguments args, CommandHandler handler) + { + if (args.Empty()) + return false; + + string idStr = handler.extractKeyFromLink(args, "Hachievement"); + if (string.IsNullOrEmpty(idStr)) + return false; + + uint achievementId = uint.Parse(idStr); + if (achievementId == 0) + return false; + + Player target = handler.getSelectedPlayer(); + if (!target) + { + handler.SendSysMessage(CypherStrings.NoCharSelected); + return false; + } + AchievementRecord achievementEntry = CliDB.AchievementStorage.LookupByKey(achievementId); + if (achievementEntry != null) + target.CompletedAchievement(achievementEntry); + + return true; + } + } +} \ No newline at end of file diff --git a/Game/Chat/Commands/ModifyCommands.cs b/Game/Chat/Commands/ModifyCommands.cs new file mode 100644 index 000000000..ea4b2791a --- /dev/null +++ b/Game/Chat/Commands/ModifyCommands.cs @@ -0,0 +1,1083 @@ +/* + * Copyright (C) 2012-2017 CypherCore + * + * 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, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +using Framework.Constants; +using Framework.IO; +using Game.DataStorage; +using Game.Entities; +using Game.Network.Packets; +using System; +using System.Collections.Generic; + +namespace Game.Chat +{ + [CommandGroup("modify", RBACPermissions.CommandModify)] + class ModifyCommand + { + [Command("hp", RBACPermissions.CommandModifyHp)] + static bool HP(StringArguments args, CommandHandler handler) + { + int hp, hpmax = 0; + Player target = handler.getSelectedPlayerOrSelf(); + if (CheckModifyResources(args, handler, target, out hp, out hpmax)) + { + NotifyModification(handler, target, CypherStrings.YouChangeHp, CypherStrings.YoursHpChanged, hp, hpmax); + target.SetMaxHealth((uint)hpmax); + target.SetHealth((uint)hp); + return true; + } + return false; + } + + [Command("mana", RBACPermissions.CommandModifyMana)] + static bool Mana(StringArguments args, CommandHandler handler) + { + int mana, manamax; + Player target = handler.getSelectedPlayerOrSelf(); + + if (CheckModifyResources(args, handler, target, out mana, out manamax)) + { + NotifyModification(handler, target, CypherStrings.YouChangeMana, CypherStrings.YoursManaChanged, mana, manamax); + target.SetMaxPower(PowerType.Mana, manamax); + target.SetPower(PowerType.Mana, mana); + return true; + } + + return false; + } + + [Command("energy", RBACPermissions.CommandModifyEnergy)] + static bool Energy(StringArguments args, CommandHandler handler) + { + int energy, energymax; + Player target = handler.getSelectedPlayerOrSelf(); + byte energyMultiplier = 10; + if (CheckModifyResources(args, handler, target, out energy, out energymax, energyMultiplier)) + { + NotifyModification(handler, target, CypherStrings.YouChangeEnergy, CypherStrings.YoursEnergyChanged, energy / energyMultiplier, energymax / energyMultiplier); + target.SetMaxPower(PowerType.Energy, energymax); + target.SetPower(PowerType.Energy, energy); + Log.outDebug(LogFilter.Misc, handler.GetCypherString(CypherStrings.CurrentEnergy), target.GetMaxPower(PowerType.Energy)); + return true; + } + return false; + } + + [Command("rage", RBACPermissions.CommandModifyRage)] + static bool Rage(StringArguments args, CommandHandler handler) + { + int rage, ragemax; + Player target = handler.getSelectedPlayerOrSelf(); + byte rageMultiplier = 10; + if (CheckModifyResources(args, handler, target, out rage, out ragemax, rageMultiplier)) + { + NotifyModification(handler, target, CypherStrings.YouChangeRage, CypherStrings.YoursRageChanged, rage / rageMultiplier, ragemax / rageMultiplier); + target.SetMaxPower(PowerType.Rage, ragemax); + target.SetPower(PowerType.Rage, rage); + return true; + } + return false; + } + + [Command("runicpower", RBACPermissions.CommandModifyRunicpower)] + static bool RunicPower(StringArguments args, CommandHandler handler) + { + int rune, runemax; + Player target = handler.getSelectedPlayerOrSelf(); + byte runeMultiplier = 10; + if (CheckModifyResources(args, handler, target, out rune, out runemax, runeMultiplier)) + { + NotifyModification(handler, target, CypherStrings.YouChangeRunicPower, CypherStrings.YoursRunicPowerChanged, rune / runeMultiplier, runemax / runeMultiplier); + target.SetMaxPower(PowerType.RunicPower, runemax); + target.SetPower(PowerType.RunicPower, rune); + return true; + } + return false; + } + + [Command("faction", RBACPermissions.CommandModifyFaction)] + static bool Faction(StringArguments args, CommandHandler handler) + { + if (args.Empty()) + return false; + + string pfactionid = handler.extractKeyFromLink(args, "Hfaction"); + + Creature target = handler.getSelectedCreature(); + if (!target) + { + handler.SendSysMessage(CypherStrings.SelectCreature); + return false; + } + + if (string.IsNullOrEmpty(pfactionid)) + { + uint _factionid = target.getFaction(); + uint _flag = target.GetUInt32Value(UnitFields.Flags); + ulong _npcflag = target.GetUInt64Value(UnitFields.NpcFlags); + uint _dyflag = target.GetUInt32Value(ObjectFields.DynamicFlags); + handler.SendSysMessage(CypherStrings.CurrentFaction, target.GetGUID().ToString(), _factionid, _flag, _npcflag, _dyflag); + return true; + } + + uint factionid = uint.Parse(pfactionid); + uint flag; + + string pflag = args.NextString(); + if (string.IsNullOrEmpty(pflag)) + flag = target.GetUInt32Value(UnitFields.Flags); + else + flag = uint.Parse(pflag); + + string pnpcflag = args.NextString(); + + ulong npcflag; + if (string.IsNullOrEmpty(pnpcflag)) + npcflag = target.GetUInt64Value(UnitFields.NpcFlags); + else + npcflag = ulong.Parse(pnpcflag); + + string pdyflag = args.NextString(); + + uint dyflag; + if (string.IsNullOrEmpty(pdyflag)) + dyflag = target.GetUInt32Value(ObjectFields.DynamicFlags); + else + dyflag = uint.Parse(pdyflag); + + if (!CliDB.FactionTemplateStorage.ContainsKey(factionid)) + { + handler.SendSysMessage(CypherStrings.WrongFaction, factionid); + + return false; + } + + handler.SendSysMessage(CypherStrings.YouChangeFaction, target.GetGUID().ToString(), factionid, flag, npcflag, dyflag); + + target.SetFaction(factionid); + target.SetUInt32Value(UnitFields.Flags, flag); + target.SetUInt64Value(UnitFields.NpcFlags, npcflag); + target.SetUInt32Value(ObjectFields.DynamicFlags, dyflag); + + return true; + } + + [Command("spell", RBACPermissions.CommandModifySpell)] + static bool HandleModifySpellCommand(StringArguments args, CommandHandler handler) + { + if (args.Empty()) + return false; + + byte spellflatid = args.NextByte(); + if (spellflatid == 0) + return false; + + byte op = args.NextByte(); + if (op == 0) + return false; + + ushort val = args.NextUInt16(); + if (val == 0) + return false; + + ushort mark; + + string pmark = args.NextString(); + if (string.IsNullOrEmpty(pmark)) + mark = 65535; + else + mark = ushort.Parse(pmark); + + Player target = handler.getSelectedPlayerOrSelf(); + if (!target) + { + handler.SendSysMessage(CypherStrings.NoCharSelected); + return false; + } + + // check online security + if (handler.HasLowerSecurity(target, ObjectGuid.Empty)) + return false; + + handler.SendSysMessage(CypherStrings.YouChangeSpellflatid, spellflatid, val, mark, handler.GetNameLink(target)); + if (handler.needReportToTarget(target)) + target.SendSysMessage(CypherStrings.YoursSpellflatidChanged, handler.GetNameLink(), spellflatid, val, mark); + + SetSpellModifier packet = new SetSpellModifier(ServerOpcodes.SetFlatSpellModifier); + SpellModifierInfo spellMod = new SpellModifierInfo(); + spellMod.ModIndex = op; + SpellModifierData modData; + modData.ClassIndex = spellflatid; + modData.ModifierValue = val; + spellMod.ModifierData.Add(modData); + packet.Modifiers.Add(spellMod); + target.SendPacket(packet); + + return true; + } + + [Command("talentpoints", RBACPermissions.CommandModifyTalentpoints)] + static bool HandleModifyTalentCommand(StringArguments args, CommandHandler handler) { return false; } + + [Command("scale", RBACPermissions.CommandModifyScale)] + static bool Scale(StringArguments args, CommandHandler handler) + { + float Scale; + Unit target = handler.getSelectedUnit(); + if (CheckModifySpeed(args, handler, target, out Scale, 0.1f, 10.0f, false)) + { + NotifyModification(handler, target, CypherStrings.YouChangeSize, CypherStrings.YoursSizeChanged, Scale); + target.SetObjectScale(Scale); + return true; + } + return false; + } + + [Command("mount", RBACPermissions.CommandModifyMount)] + static bool Mount(StringArguments args, CommandHandler handler) + { + if (args.Empty()) + return false; + + ushort mId = 1147; + float speed = 15f; + + uint num = args.NextUInt32(); + switch (num) + { + case 1: + mId = 14340; + break; + case 2: + mId = 4806; + break; + case 3: + mId = 6471; + break; + case 4: + mId = 12345; + break; + case 5: + mId = 6472; + break; + case 6: + mId = 6473; + break; + case 7: + mId = 10670; + break; + case 8: + mId = 10719; + break; + case 9: + mId = 10671; + break; + case 10: + mId = 10672; + break; + case 11: + mId = 10720; + break; + case 12: + mId = 14349; + break; + case 13: + mId = 11641; + break; + case 14: + mId = 12244; + break; + case 15: + mId = 12242; + break; + case 16: + mId = 14578; + break; + case 17: + mId = 14579; + break; + case 18: + mId = 14349; + break; + case 19: + mId = 12245; + break; + case 20: + mId = 14335; + break; + case 21: + mId = 207; + break; + case 22: + mId = 2328; + break; + case 23: + mId = 2327; + break; + case 24: + mId = 2326; + break; + case 25: + mId = 14573; + break; + case 26: + mId = 14574; + break; + case 27: + mId = 14575; + break; + case 28: + mId = 604; + break; + case 29: + mId = 1166; + break; + case 30: + mId = 2402; + break; + case 31: + mId = 2410; + break; + case 32: + mId = 2409; + break; + case 33: + mId = 2408; + break; + case 34: + mId = 2405; + break; + case 35: + mId = 14337; + break; + case 36: + mId = 6569; + break; + case 37: + mId = 10661; + break; + case 38: + mId = 10666; + break; + case 39: + mId = 9473; + break; + case 40: + mId = 9476; + break; + case 41: + mId = 9474; + break; + case 42: + mId = 14374; + break; + case 43: + mId = 14376; + break; + case 44: + mId = 14377; + break; + case 45: + mId = 2404; + break; + case 46: + mId = 2784; + break; + case 47: + mId = 2787; + break; + case 48: + mId = 2785; + break; + case 49: + mId = 2736; + break; + case 50: + mId = 2786; + break; + case 51: + mId = 14347; + break; + case 52: + mId = 14346; + break; + case 53: + mId = 14576; + break; + case 54: + mId = 9695; + break; + case 55: + mId = 9991; + break; + case 56: + mId = 6448; + break; + case 57: + mId = 6444; + break; + case 58: + mId = 6080; + break; + case 59: + mId = 6447; + break; + case 60: + mId = 4805; + break; + case 61: + mId = 9714; + break; + case 62: + mId = 6448; + break; + case 63: + mId = 6442; + break; + case 64: + mId = 14632; + break; + case 65: + mId = 14332; + break; + case 66: + mId = 14331; + break; + case 67: + mId = 8469; + break; + case 68: + mId = 2830; + break; + case 69: + mId = 2346; + break; + default: + handler.SendSysMessage(CypherStrings.NoMount); + return false; + } + + Player target = handler.getSelectedPlayerOrSelf(); + if (!target) + { + handler.SendSysMessage(CypherStrings.NoCharSelected); + + return false; + } + + // check online security + if (handler.HasLowerSecurity(target, ObjectGuid.Empty)) + return false; + + NotifyModification(handler, target, CypherStrings.YouGiveMount, CypherStrings.MountGived); + + target.SetUInt32Value(UnitFields.Flags, (uint)UnitFlags.Pvp); + target.Mount(mId); + + var packet = new MoveSetSpeed(ServerOpcodes.MoveSetRunSpeed); + packet.MoverGUID = target.GetGUID(); + packet.SequenceIndex = 0; + packet.Speed = speed; + target.SendMessageToSet(packet, true); + + packet = new MoveSetSpeed(ServerOpcodes.MoveSetSwimSpeed); + packet.MoverGUID = target.GetGUID(); + packet.SequenceIndex = 0; + packet.Speed = speed; + target.SendMessageToSet(packet, true); + + return true; + } + + [Command("money", RBACPermissions.CommandModifyMoney)] + static bool Money(StringArguments args, CommandHandler handler) + { + Player target = handler.getSelectedPlayerOrSelf(); + if (!target) + { + handler.SendSysMessage(CypherStrings.NoCharSelected); + return false; + } + + // check online security + if (handler.HasLowerSecurity(target, ObjectGuid.Empty)) + return false; + + long addmoney = args.NextInt64(); + long moneyuser = (long)target.GetMoney(); + + if (addmoney < 0) + { + ulong newmoney = (ulong)(moneyuser + addmoney); + + Log.outDebug(LogFilter.ChatSystem, Global.ObjectMgr.GetCypherString(CypherStrings.CurrentMoney), moneyuser, addmoney, newmoney); + if (newmoney <= 0) + { + handler.SendSysMessage(CypherStrings.YouTakeAllMoney, handler.GetNameLink(target)); + if (handler.needReportToTarget(target)) + target.SendSysMessage(CypherStrings.YoursAllMoneyGone, handler.GetNameLink()); + + target.SetMoney(0); + } + else + { + if (newmoney > PlayerConst.MaxMoneyAmount) + newmoney = PlayerConst.MaxMoneyAmount; + + handler.SendSysMessage(CypherStrings.YouTakeMoney, Math.Abs(addmoney), handler.GetNameLink(target)); + if (handler.needReportToTarget(target)) + target.SendSysMessage(CypherStrings.YoursMoneyTaken, handler.GetNameLink(), Math.Abs(addmoney)); + target.SetMoney(newmoney); + } + } + else + { + handler.SendSysMessage(CypherStrings.YouGiveMoney, addmoney, handler.GetNameLink(target)); + if (handler.needReportToTarget(target)) + target.SendSysMessage(CypherStrings.YoursMoneyGiven, handler.GetNameLink(), addmoney); + + if (addmoney >= PlayerConst.MaxMoneyAmount) + target.SetMoney(PlayerConst.MaxMoneyAmount); + else + target.ModifyMoney(addmoney); + } + + Log.outDebug(LogFilter.ChatSystem, Global.ObjectMgr.GetCypherString(CypherStrings.NewMoney), moneyuser, addmoney, target.GetMoney()); + return true; + } + + [Command("bit", RBACPermissions.CommandModifyBit)] + static bool Bit(StringArguments args, CommandHandler handler) + { + if (args.Empty()) + return false; + + Unit target = handler.getSelectedUnit(); + if (!target) + { + handler.SendSysMessage(CypherStrings.NoCharSelected); + + return false; + } + + // check online security + if (target.IsTypeId(TypeId.Player) && handler.HasLowerSecurity(target.ToPlayer(), ObjectGuid.Empty)) + return false; + + ushort field = args.NextUInt16(); + int bit = args.NextInt32(); + + if (field < (int)ObjectFields.End || field >= target.valuesCount) + { + handler.SendSysMessage(CypherStrings.BadValue); + return false; + } + if (bit < 1 || bit > 32) + { + handler.SendSysMessage(CypherStrings.BadValue); + return false; + } + + if (target.HasFlag(field, (1 << (bit - 1)))) + { + target.RemoveFlag(field, (1 << (bit - 1))); + handler.SendSysMessage(CypherStrings.RemoveBit, bit, field); + } + else + { + target.SetFlag(field, (1 << (bit - 1))); + handler.SendSysMessage(CypherStrings.SetBit, bit, field); + } + return true; + } + + [Command("honor", RBACPermissions.CommandModifyHonor)] + static bool Honor(StringArguments args, CommandHandler handler) + { + if (args.Empty()) + return false; + + Player target = handler.getSelectedPlayerOrSelf(); + if (!target) + { + handler.SendSysMessage(CypherStrings.PlayerNotFound); + return false; + } + + // check online security + if (handler.HasLowerSecurity(target, ObjectGuid.Empty)) + return false; + + int amount = args.NextInt32(); + + //target.ModifyCurrency(CurrencyTypes.HonorPoints, amount, true, true); + handler.SendSysMessage("NOT IMPLEMENTED: {0} honor NOT added.", amount); + + //handler.SendSysMessage(CypherStrings.CommandModifyHonor, handler.GetNameLink(target), target.GetCurrency((uint)CurrencyTypes.HonorPoints)); + return true; + } + + [Command("drunk", RBACPermissions.CommandModifyDrunk)] + static bool Drunk(StringArguments args, CommandHandler handler) + { + if (args.Empty()) + return false; + + byte drunklevel = args.NextByte(); + if (drunklevel > 100) + drunklevel = 100; + + Player target = handler.getSelectedPlayerOrSelf(); + if (target) + target.SetDrunkValue(drunklevel); + + return true; + } + + [Command("reputation", RBACPermissions.CommandModifyReputation)] + static bool Rep(StringArguments args, CommandHandler handler) + { + if (args.Empty()) + return false; + + Player target = handler.getSelectedPlayerOrSelf(); + if (!target) + { + handler.SendSysMessage(CypherStrings.PlayerNotFound); + return false; + } + + // check online security + if (handler.HasLowerSecurity(target, ObjectGuid.Empty)) + return false; + + string factionTxt = handler.extractKeyFromLink(args, "Hfaction"); + if (string.IsNullOrEmpty(factionTxt)) + return false; + + uint factionId = uint.Parse(factionTxt); + + int amount = 0; + string rankTxt = args.NextString(); + if (factionId == 0 || rankTxt.IsEmpty()) + return false; + + amount = int.Parse(rankTxt); + if ((amount == 0) && !(amount < 0) && !rankTxt.IsNumber()) + { + string rankStr = rankTxt.ToLower(); + + int r = 0; + amount = -42000; + for (; r < (int)ReputationRank.Max; ++r) + { + string rank = handler.GetCypherString(ReputationMgr.ReputationRankStrIndex[r]); + if (string.IsNullOrEmpty(rank)) + continue; + + if (rank.Equals(rankStr)) + { + string deltaTxt = args.NextString(); + if (!string.IsNullOrEmpty(deltaTxt)) + { + int delta = int.Parse(deltaTxt); + if ((delta < 0) || (delta > ReputationMgr.PointsInRank[r] - 1)) + { + handler.SendSysMessage(CypherStrings.CommandFactionDelta, (ReputationMgr.PointsInRank[r] - 1)); + return false; + } + amount += delta; + } + break; + } + amount += ReputationMgr.PointsInRank[r]; + } + if (r >= (int)ReputationRank.Max) + { + handler.SendSysMessage(CypherStrings.CommandFactionInvparam, rankTxt); + return false; + } + } + + FactionRecord factionEntry = CliDB.FactionStorage.LookupByKey(factionId); + if (factionEntry == null) + { + handler.SendSysMessage(CypherStrings.CommandFactionUnknown, factionId); + return false; + } + + if (factionEntry.ReputationIndex < 0) + { + handler.SendSysMessage(CypherStrings.CommandFactionNorepError, factionEntry.Name[handler.GetSessionDbcLocale()], factionId); + return false; + } + + target.GetReputationMgr().SetOneFactionReputation(factionEntry, amount, false); + target.GetReputationMgr().SendState(target.GetReputationMgr().GetState(factionEntry)); + handler.SendSysMessage(CypherStrings.CommandModifyRep, factionEntry.Name[handler.GetSessionDbcLocale()], factionId, handler.GetNameLink(target), target.GetReputationMgr().GetReputation(factionEntry)); + + return true; + } + + [Command("phase", RBACPermissions.CommandModifyPhase)] + static bool HandleModifyPhaseCommand(StringArguments args, CommandHandler handler) + { + if (args.Empty()) + return false; + + uint phaseId = args.NextUInt32(); + if (!CliDB.PhaseStorage.ContainsKey(phaseId)) + { + handler.SendSysMessage(CypherStrings.PhaseNotfound); + return false; + } + + Unit target = handler.getSelectedUnit(); + if (!target) + target = handler.GetSession().GetPlayer(); + + target.SetInPhase(phaseId, true, !target.IsInPhase(phaseId)); + + if (target.IsTypeId(TypeId.Player)) + target.ToPlayer().SendUpdatePhasing(); + + return true; + } + + [Command("standstate", RBACPermissions.CommandModifyStandstate)] + static bool HandleModifyStandStateCommand(StringArguments args, CommandHandler handler) + { + if (args.Empty()) + return false; + + uint anim_id = args.NextUInt32(); + handler.GetSession().GetPlayer().SetUInt32Value(UnitFields.NpcEmotestate, anim_id); + + return true; + } + + [Command("gender", RBACPermissions.CommandModifyGender)] + static bool HandleModifyGenderCommand(StringArguments args, CommandHandler handler) + { + if (args.Empty()) + return false; + + Player target = handler.getSelectedPlayerOrSelf(); + if (!target) + { + handler.SendSysMessage(CypherStrings.PlayerNotFound); + return false; + } + + PlayerInfo info = Global.ObjectMgr.GetPlayerInfo(target.GetRace(), target.GetClass()); + if (info == null) + return false; + + string gender_str = args.NextString(); + Gender gender; + + if (gender_str == "male") // MALE + { + if (target.GetGender() == Gender.Male) + return true; + + gender = Gender.Male; + } + else if (gender_str == "female") // FEMALE + { + if (target.GetGender() == Gender.Female) + return true; + + gender = Gender.Female; + } + else + { + handler.SendSysMessage(CypherStrings.MustMaleOrFemale); + return false; + } + + // Set gender + target.SetByteValue(UnitFields.Bytes0, 3, (byte)gender); + target.SetByteValue(PlayerFields.Bytes3, PlayerFieldOffsets.Bytes3OffsetGender, (byte)gender); + + // Change display ID + target.InitDisplayIds(); + + handler.SendSysMessage(CypherStrings.YouChangeGender, handler.GetNameLink(target), gender); + + if (handler.needReportToTarget(target)) + target.SendSysMessage(CypherStrings.YourGenderChanged, gender, handler.GetNameLink()); + + return true; + } + + [Command("currency", RBACPermissions.CommandModifyCurrency)] + static bool HandleModifyCurrencyCommand(StringArguments args, CommandHandler handler) + { + if (args.Empty()) + return false; + + Player target = handler.getSelectedPlayerOrSelf(); + if (!target) + { + handler.SendSysMessage(CypherStrings.PlayerNotFound); + + return false; + } + + uint currencyId = args.NextUInt32(); + if (!CliDB.CurrencyTypesStorage.ContainsKey(currencyId)) + return false; + + uint amount = args.NextUInt32(); + if (amount == 0) + return false; + + target.ModifyCurrency((CurrencyTypes)currencyId, (int)amount, true, true); + + return true; + } + + [CommandNonGroup("morph", RBACPermissions.CommandMorph)] + static bool HandleModifyMorphCommand(StringArguments args, CommandHandler handler) + { + if (args.Empty()) + return false; + + uint display_id = args.NextUInt32(); + + Unit target = handler.getSelectedUnit(); + if (!target) + target = handler.GetSession().GetPlayer(); + + // check online security + else if (target.IsTypeId(TypeId.Player) && handler.HasLowerSecurity(target.ToPlayer(), ObjectGuid.Empty)) + return false; + + target.SetDisplayId(display_id); + + return true; + } + + [CommandNonGroup("demorph", RBACPermissions.CommandDemorph)] + static bool HandleDeMorphCommand(StringArguments args, CommandHandler handler) + { + Unit target = handler.getSelectedUnit(); + if (!target) + target = handler.GetSession().GetPlayer(); + + // check online security + else if (target.IsTypeId(TypeId.Player) && handler.HasLowerSecurity(target.ToPlayer(), ObjectGuid.Empty)) + return false; + + target.DeMorph(); + + return true; + } + + [Command("xp", RBACPermissions.CommandModifyXp)] + static bool HandleModifyXPCommand(StringArguments args, CommandHandler handler) + { + if (args.Empty()) + return false; + + int xp = args.NextInt32(); + + if (xp < 1) + { + handler.SendSysMessage(CypherStrings.BadValue); + return false; + } + + Player target = handler.getSelectedPlayerOrSelf(); + if (!target) + { + handler.SendSysMessage(CypherStrings.NoCharSelected); + return false; + } + + if (handler.HasLowerSecurity(target, ObjectGuid.Empty)) + return false; + + // we can run the command + target.GiveXP((uint)xp, null); + return true; + } + + [CommandGroup("speed", RBACPermissions.CommandModifySpeed)] + class ModifySpeed + { + [Command("", RBACPermissions.CommandModifySpeed)] + static bool HandleSpeed(StringArguments args, CommandHandler handler) + { + return All(args, handler); + } + + [Command("all", RBACPermissions.CommandModifySpeedAll)] + static bool All(StringArguments args, CommandHandler handler) + { + float allSpeed; + Player target = handler.getSelectedPlayerOrSelf(); + if (CheckModifySpeed(args, handler, target, out allSpeed, 0.1f, 50.0f)) + { + NotifyModification(handler, target, CypherStrings.YouChangeAspeed, CypherStrings.YoursAspeedChanged, allSpeed); + target.SetSpeedRate(UnitMoveType.Walk, allSpeed); + target.SetSpeedRate(UnitMoveType.Run, allSpeed); + target.SetSpeedRate(UnitMoveType.Swim, allSpeed); + target.SetSpeedRate(UnitMoveType.Flight, allSpeed); + return true; + } + return false; + } + + [Command("swim", RBACPermissions.CommandModifySpeedSwim)] + static bool Swim(StringArguments args, CommandHandler handler) + { + float swimSpeed; + Player target = handler.getSelectedPlayerOrSelf(); + if (CheckModifySpeed(args, handler, target, out swimSpeed, 0.1f, 50.0f)) + { + NotifyModification(handler, target, CypherStrings.YouChangeSwimSpeed, CypherStrings.YoursSwimSpeedChanged, swimSpeed); + target.SetSpeedRate(UnitMoveType.Swim, swimSpeed); + return true; + } + return false; + } + + [Command("backwalk", RBACPermissions.CommandModifySpeedBackwalk)] + static bool BackWalk(StringArguments args, CommandHandler handler) + { + float backSpeed; + Player target = handler.getSelectedPlayerOrSelf(); + if (CheckModifySpeed(args, handler, target, out backSpeed, 0.1f, 50.0f)) + { + NotifyModification(handler, target, CypherStrings.YouChangeBackSpeed, CypherStrings.YoursBackSpeedChanged, backSpeed); + target.SetSpeedRate(UnitMoveType.RunBack, backSpeed); + return true; + } + return false; + } + + [Command("fly", RBACPermissions.CommandModifySpeedFly)] + static bool Fly(StringArguments args, CommandHandler handler) + { + float flySpeed; + Player target = handler.getSelectedPlayerOrSelf(); + if (CheckModifySpeed(args, handler, target, out flySpeed, 0.1f, 50.0f, false)) + { + NotifyModification(handler, target, CypherStrings.YouChangeFlySpeed, CypherStrings.YoursFlySpeedChanged, flySpeed); + target.SetSpeedRate(UnitMoveType.Flight, flySpeed); + return true; + } + return false; + } + + [Command("walk", RBACPermissions.CommandModifySpeedWalk)] + static bool Walk(StringArguments args, CommandHandler handler) + { + float Speed; + Player target = handler.getSelectedPlayerOrSelf(); + if (CheckModifySpeed(args, handler, target, out Speed, 0.1f, 50.0f)) + { + NotifyModification(handler, target, CypherStrings.YouChangeSpeed, CypherStrings.YoursSpeedChanged, Speed); + target.SetSpeedRate(UnitMoveType.Run, Speed); + return true; + } + return false; + } + } + + static void NotifyModification(CommandHandler handler, Unit target, CypherStrings resourceMessage, CypherStrings resourceReportMessage, params object[] args) + { + Player player = target.ToPlayer(); + if (player) + { + handler.SendSysMessage(resourceMessage, new object[] { handler.GetNameLink(player) }.Combine(args)); + if (handler.needReportToTarget(player)) + player.SendSysMessage(resourceReportMessage, new object[] { handler.GetNameLink() }.Combine(args)); + } + } + + static bool CheckModifyResources(StringArguments args, CommandHandler handler, Player target, out int res, out int resmax, byte multiplier = 1) + { + res = 0; + resmax = 0; + + if (args.Empty()) + return false; + + res = args.NextInt32() * multiplier; + resmax = args.NextInt32() * multiplier; + + if (resmax == 0) + resmax = res; + + if (res < 1 || resmax < 1 || resmax < res) + { + handler.SendSysMessage(CypherStrings.BadValue); + return false; + } + + if (!target) + { + handler.SendSysMessage(CypherStrings.NoCharSelected); + return false; + } + + if (handler.HasLowerSecurity(target, ObjectGuid.Empty)) + return false; + + return true; + } + + static bool CheckModifySpeed(StringArguments args, CommandHandler handler, Unit target, out float speed, float minimumBound, float maximumBound, bool checkInFlight = true) + { + speed = 0f; + if (args.Empty()) + return false; + + speed = args.NextSingle(); + + if (speed > maximumBound || speed < minimumBound) + { + handler.SendSysMessage(CypherStrings.BadValue); + return false; + } + + if (!target) + { + handler.SendSysMessage(CypherStrings.NoCharSelected); + return false; + } + + Player player = target.ToPlayer(); + if (player) + { + // check online security + if (handler.HasLowerSecurity(player, ObjectGuid.Empty)) + return false; + + if (player.IsInFlight() && checkInFlight) + { + handler.SendSysMessage(CypherStrings.CharInFlight, handler.GetNameLink(player)); + return false; + } + } + return true; + } + } +} diff --git a/Game/Chat/Commands/NPCCommands.cs b/Game/Chat/Commands/NPCCommands.cs new file mode 100644 index 000000000..1e1cdba85 --- /dev/null +++ b/Game/Chat/Commands/NPCCommands.cs @@ -0,0 +1,1312 @@ +/* + * Copyright (C) 2012-2017 CypherCore + * + * 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, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +using Framework.Constants; +using Framework.Database; +using Framework.IO; +using Game.DataStorage; +using Game.Entities; +using Game.Maps; +using Game.Movement; +using System; +using System.Collections.Generic; +using System.Linq; + +namespace Game.Chat +{ + [CommandGroup("npc", RBACPermissions.CommandNpc)] + class NPCCommands + { + [Command("info", RBACPermissions.CommandNpcInfo)] + static bool Info(StringArguments args, CommandHandler handler) + { + Creature target = handler.getSelectedCreature(); + if (!target) + { + handler.SendSysMessage(CypherStrings.SelectCreature); + return false; + } + + CreatureTemplate cInfo = target.GetCreatureTemplate(); + + uint faction = target.getFaction(); + ulong npcflags = target.GetUInt64Value(UnitFields.NpcFlags); + uint mechanicImmuneMask = cInfo.MechanicImmuneMask; + uint displayid = target.GetDisplayId(); + uint nativeid = target.GetNativeDisplayId(); + uint Entry = target.GetEntry(); + + long curRespawnDelay = target.GetRespawnTimeEx() - Time.UnixTime; + if (curRespawnDelay < 0) + curRespawnDelay = 0; + string curRespawnDelayStr = Time.secsToTimeString((ulong)curRespawnDelay, true); + string defRespawnDelayStr = Time.secsToTimeString(target.GetRespawnDelay(), true); + + handler.SendSysMessage(CypherStrings.NpcinfoChar, target.GetSpawnId(), target.GetGUID().ToString(), faction, npcflags, Entry, displayid, nativeid); + handler.SendSysMessage(CypherStrings.NpcinfoLevel, target.getLevel()); + handler.SendSysMessage(CypherStrings.NpcinfoEquipment, target.GetCurrentEquipmentId(), target.GetOriginalEquipmentId()); + handler.SendSysMessage(CypherStrings.NpcinfoHealth, target.GetCreateHealth(), target.GetMaxHealth(), target.GetHealth()); + handler.SendSysMessage(CypherStrings.NpcinfoInhabitType, cInfo.InhabitType); + + handler.SendSysMessage(CypherStrings.NpcinfoUnitFieldFlags, target.GetUInt32Value(UnitFields.Flags)); + foreach (uint value in Enum.GetValues(typeof(UnitFlags))) + if (target.GetUInt32Value(UnitFields.Flags).HasAnyFlag(value)) + handler.SendSysMessage("{0} (0x{1:X})", (UnitFlags)value, value); + + handler.SendSysMessage(CypherStrings.NpcinfoUnitFieldFlags2, target.GetUInt32Value(UnitFields.Flags2)); + foreach (uint value in Enum.GetValues(typeof(UnitFlags2))) + if (target.GetUInt32Value(UnitFields.Flags2).HasAnyFlag(value)) + handler.SendSysMessage("{0} (0x{1:X})", (UnitFlags2)value, value); + + handler.SendSysMessage(CypherStrings.NpcinfoUnitFieldFlags3, target.GetUInt32Value(UnitFields.Flags3)); + foreach (uint value in Enum.GetValues(typeof(UnitFlags3))) + if (target.GetUInt32Value(UnitFields.Flags3).HasAnyFlag(value)) + handler.SendSysMessage("{0} (0x{1:X})", (UnitFlags3)value, value); + + handler.SendSysMessage(CypherStrings.NpcinfoDynamicFlags, target.GetUInt32Value(ObjectFields.DynamicFlags)); + handler.SendSysMessage(CypherStrings.CommandRawpawntimes, defRespawnDelayStr, curRespawnDelayStr); + handler.SendSysMessage(CypherStrings.NpcinfoLoot, cInfo.LootId, cInfo.PickPocketId, cInfo.SkinLootId); + handler.SendSysMessage(CypherStrings.NpcinfoDungeonId, target.GetInstanceId()); + + CreatureData data = Global.ObjectMgr.GetCreatureData(target.GetSpawnId()); + if (data != null) + { + handler.SendSysMessage(CypherStrings.NpcinfoPhases, data.phaseid, data.phaseGroup); + if (data.phaseGroup != 0) + { + var _phases = target.GetPhases(); + if (!_phases.Empty()) + { + handler.SendSysMessage(CypherStrings.NpcinfoPhaseIds); + foreach (uint phaseId in _phases) + handler.SendSysMessage("{0}", phaseId); + } + } + } + + handler.SendSysMessage(CypherStrings.NpcinfoArmor, target.GetArmor()); + handler.SendSysMessage(CypherStrings.NpcinfoPosition, target.GetPositionX(), target.GetPositionY(), target.GetPositionZ()); + handler.SendSysMessage(CypherStrings.NpcinfoAiinfo, target.GetAIName(), target.GetScriptName()); + handler.SendSysMessage(CypherStrings.NpcinfoFlagsExtra, cInfo.FlagsExtra); + foreach (uint value in Enum.GetValues(typeof(CreatureFlagsExtra))) + if (cInfo.FlagsExtra.HasAnyFlag((CreatureFlagsExtra)value)) + handler.SendSysMessage("{0} (0x{1:X})", (CreatureFlagsExtra)value, value); + + handler.SendSysMessage(CypherStrings.NpcinfoNpcFlags, npcflags); + foreach (ulong value in Enum.GetValues(typeof(NPCFlags))) + if (npcflags.HasAnyFlag(value)) + handler.SendSysMessage("{0} (0x{1:X})", (NPCFlags)value, value); + + handler.SendSysMessage(CypherStrings.NpcinfoMechanicImmune, mechanicImmuneMask); + foreach (int value in Enum.GetValues(typeof(Mechanics))) + if (Convert.ToBoolean(mechanicImmuneMask & (1 << (value - 1)))) + handler.SendSysMessage("{0} (0x{1:X})", (Mechanics)value, value); + + return true; + } + + [Command("move", RBACPermissions.CommandNpcMove)] + static bool Move(StringArguments args, CommandHandler handler) + { + ulong lowguid = 0; + + Creature creature = handler.getSelectedCreature(); + if (!creature) + { + // number or [name] Shift-click form |color|Hcreature:creature_guid|h[name]|h|r + string cId = handler.extractKeyFromLink(args, "Hcreature"); + if (string.IsNullOrEmpty(cId)) + return false; + + lowguid = uint.Parse(cId); + + // Attempting creature load from DB data + CreatureData data = Global.ObjectMgr.GetCreatureData(lowguid); + if (data == null) + { + handler.SendSysMessage(CypherStrings.CommandCreatguidnotfound, lowguid); + return false; + } + + uint map_id = data.mapid; + if (handler.GetSession().GetPlayer().GetMapId() != map_id) + { + handler.SendSysMessage(CypherStrings.CommandCreatureatsamemap, lowguid); + return false; + } + + } + else + { + lowguid = creature.GetSpawnId(); + } + + float x = handler.GetPlayer().GetPositionX(); + float y = handler.GetPlayer().GetPositionY(); + float z = handler.GetPlayer().GetPositionZ(); + float o = handler.GetPlayer().GetOrientation(); + + if (creature) + { + CreatureData data = Global.ObjectMgr.GetCreatureData(creature.GetSpawnId()); + if (data != null) + { + data.posX = x; + data.posY = y; + data.posZ = z; + data.orientation = o; + } + creature.SetPosition(x, y, z, o); + creature.GetMotionMaster().Initialize(); + if (creature.IsAlive()) // dead creature will reset movement generator at respawn + { + creature.setDeathState(DeathState.JustDied); + creature.Respawn(); + } + } + + PreparedStatement stmt = DB.World.GetPreparedStatement(WorldStatements.UPD_CREATURE_POSITION); + + stmt.AddValue(0, x); + stmt.AddValue(1, y); + stmt.AddValue(2, z); + stmt.AddValue(3, o); + stmt.AddValue(4, lowguid); + + DB.World.Execute(stmt); + + handler.SendSysMessage(CypherStrings.CommandCreaturemoved); + return true; + } + + [Command("near", RBACPermissions.CommandNpcNear)] + static bool Near(StringArguments args, CommandHandler handler) + { + float distance = args.Empty() ? 10.0f : args.NextSingle(); + uint count = 0; + + Player player = handler.GetPlayer(); + + PreparedStatement stmt = DB.World.GetPreparedStatement(WorldStatements.SEL_CREATURE_NEAREST); + stmt.AddValue(0, player.GetPositionX()); + stmt.AddValue(1, player.GetPositionY()); + stmt.AddValue(2, player.GetPositionZ()); + stmt.AddValue(3, player.GetMapId()); + stmt.AddValue(4, player.GetPositionX()); + stmt.AddValue(5, player.GetPositionY()); + stmt.AddValue(6, player.GetPositionZ()); + stmt.AddValue(7, distance * distance); + SQLResult result = DB.World.Query(stmt); + + if (!result.IsEmpty()) + { + do + { + ulong guid = result.Read(0); + uint entry = result.Read(1); + float x = result.Read(2); + float y = result.Read(3); + float z = result.Read(4); + ushort mapId = result.Read(5); + + CreatureTemplate creatureTemplate = Global.ObjectMgr.GetCreatureTemplate(entry); + if (creatureTemplate == null) + continue; + + handler.SendSysMessage(CypherStrings.CreatureListChat, guid, guid, creatureTemplate.Name, x, y, z, mapId); + + ++count; + } + while (result.NextRow()); + } + + handler.SendSysMessage(CypherStrings.CommandNearNpcMessage, distance, count); + + return true; + } + + [Command("playemote", RBACPermissions.CommandNpcPlayemote)] + static bool PlayEmote(StringArguments args, CommandHandler handler) + { + uint emote = args.NextUInt32(); + + Creature target = handler.getSelectedCreature(); + if (!target) + { + handler.SendSysMessage(CypherStrings.SelectCreature); + return false; + } + + target.SetUInt32Value(UnitFields.NpcEmotestate, emote); + + return true; + } + + [Command("textemote", RBACPermissions.CommandNpcTextemote)] + static bool TextEmote(StringArguments args, CommandHandler handler) + { + if (args.Empty()) + return false; + + Creature creature = handler.getSelectedCreature(); + + if (!creature) + { + handler.SendSysMessage(CypherStrings.SelectCreature); + return false; + } + + creature.TextEmote(args.NextString()); + + return true; + } + + [Command("say", RBACPermissions.CommandNpcSay)] + static bool Say(StringArguments args, CommandHandler handler) + { + if (args.Empty()) + return false; + + Creature creature = handler.getSelectedCreature(); + if (!creature) + { + handler.SendSysMessage(CypherStrings.SelectCreature); + return false; + } + string text = args.NextString(); + creature.Say(text, Language.Universal); + + // make some emotes + switch (text.LastOrDefault()) + { + case '?': + creature.HandleEmoteCommand(Emote.OneshotQuestion); + break; + case '!': + creature.HandleEmoteCommand(Emote.OneshotExclamation); + break; + default: + creature.HandleEmoteCommand(Emote.OneshotTalk); + break; + } + + return true; + } + + [Command("whisper", RBACPermissions.CommandNpcWhisper)] + static bool Whisper(StringArguments args, CommandHandler handler) + { + if (args.Empty()) + return false; + + string receiver_str = args.NextString(); + string text = args.NextString(); + + if (string.IsNullOrEmpty(receiver_str) || string.IsNullOrEmpty(text)) + { + handler.SendSysMessage(CypherStrings.CmdSyntax); + return false; + } + + Creature creature = handler.getSelectedCreature(); + if (!creature) + { + handler.SendSysMessage(CypherStrings.SelectCreature); + return false; + } + + ObjectGuid receiver_guid = ObjectGuid.Create(HighGuid.Player, ulong.Parse(receiver_str)); + + // check online security + Player receiver = Global.ObjAccessor.FindPlayer(receiver_guid); + if (handler.HasLowerSecurity(receiver, ObjectGuid.Empty)) + return false; + + creature.Whisper(text, Language.Universal, receiver); + return true; + } + + [Command("yell", RBACPermissions.CommandNpcYell)] + static bool Yell(StringArguments args, CommandHandler handler) + { + if (args.Empty()) + return false; + + Creature creature = handler.getSelectedCreature(); + if (!creature) + { + handler.SendSysMessage(CypherStrings.SelectCreature); + return false; + } + + creature.Yell(args.NextString(), Language.Universal); + + // make an emote + creature.HandleEmoteCommand(Emote.OneshotShout); + + return true; + } + + [Command("tame", RBACPermissions.CommandNpcTame)] + static bool Tame(StringArguments args, CommandHandler handler) + { + Creature creatureTarget = handler.getSelectedCreature(); + if (!creatureTarget || creatureTarget.IsPet()) + { + handler.SendSysMessage(CypherStrings.SelectCreature); + return false; + } + + Player player = handler.GetSession().GetPlayer(); + + if (!player.GetPetGUID().IsEmpty()) + { + handler.SendSysMessage(CypherStrings.YouAlreadyHavePet); + return false; + } + + CreatureTemplate cInfo = creatureTarget.GetCreatureTemplate(); + + if (!cInfo.IsTameable(player.CanTameExoticPets())) + { + handler.SendSysMessage(CypherStrings.CreatureNonTameable, cInfo.Entry); + return false; + } + + // Everything looks OK, create new pet + Pet pet = player.CreateTamedPetFrom(creatureTarget); + if (!pet) + { + handler.SendSysMessage(CypherStrings.CreatureNonTameable, cInfo.Entry); + return false; + } + + // place pet before player + float x, y, z; + player.GetClosePoint(out x, out y, out z, creatureTarget.GetObjectSize(), SharedConst.ContactDistance); + pet.Relocate(x, y, z, MathFunctions.PI - player.GetOrientation()); + + // set pet to defensive mode by default (some classes can't control controlled pets in fact). + pet.SetReactState(ReactStates.Defensive); + + // calculate proper level + uint level = (creatureTarget.getLevel() < (player.getLevel() - 5)) ? (player.getLevel() - 5) : creatureTarget.getLevel(); + + // prepare visual effect for levelup + pet.SetUInt32Value(UnitFields.Level, level - 1); + + // add to world + pet.GetMap().AddToMap(pet.ToCreature()); + + // visual effect for levelup + pet.SetUInt32Value(UnitFields.Level, level); + + // caster have pet now + player.SetMinion(pet, true); + + pet.SavePetToDB(PetSaveMode.AsCurrent); + player.PetSpellInitialize(); + + return true; + } + + [Command("evade", RBACPermissions.CommandNpcEvade)] + static bool HandleNpcEvadeCommand(StringArguments args, CommandHandler handler) + { + Creature creatureTarget = handler.getSelectedCreature(); + if (!creatureTarget || creatureTarget.IsPet()) + { + handler.SendSysMessage(CypherStrings.SelectCreature); + return false; + } + + if (!creatureTarget.IsAIEnabled) + { + handler.SendSysMessage(CypherStrings.CreatureNotAiEnabled); + return false; + } + + string type_str = args.NextString(); + string force_str = args.NextString(); + + EvadeReason why = EvadeReason.Other; + bool force = false; + if (!type_str.IsEmpty()) + { + if (type_str.Equals("NO_HOSTILES") || type_str.Equals("EVADE_REASON_NO_HOSTILES")) + why = EvadeReason.NoHostiles; + else if (type_str.Equals("BOUNDARY") || type_str.Equals("EVADE_REASON_BOUNDARY")) + why = EvadeReason.Boundary; + else if (type_str.Equals("SEQUENCE_BREAK") || type_str.Equals("EVADE_REASON_SEQUENCE_BREAK")) + why = EvadeReason.SequenceBreak; + else if (type_str.Equals("FORCE")) + force = true; + + if (!force && !force_str.IsEmpty()) + if (force_str.Equals("FORCE")) + force = true; + } + + if (force) + creatureTarget.ClearUnitState(UnitState.Evade); + creatureTarget.GetAI().EnterEvadeMode(why); + + return true; + } + + [CommandGroup("follow", RBACPermissions.CommandNpcFollow)] + class FollowCommands + { + [Command("", RBACPermissions.CommandNpcFollow)] + static bool HandleNpcFollowCommand(StringArguments args, CommandHandler handler) + { + Player player = handler.GetSession().GetPlayer(); + Creature creature = handler.getSelectedCreature(); + + if (!creature) + { + handler.SendSysMessage(CypherStrings.SelectCreature); + return false; + } + + // Follow player - Using pet's default dist and angle + creature.GetMotionMaster().MoveFollow(player, SharedConst.PetFollowDist, creature.GetFollowAngle()); + + handler.SendSysMessage(CypherStrings.CreatureFollowYouNow, creature.GetName()); + return true; + } + + [Command("stop", RBACPermissions.CommandNpcFollowStop)] + static bool HandleNpcUnFollowCommand(StringArguments args, CommandHandler handler) + { + Player player = handler.GetPlayer(); + Creature creature = handler.getSelectedCreature(); + + if (!creature) + { + handler.SendSysMessage(CypherStrings.SelectCreature); + return false; + } + + if (/*creature.GetMotionMaster().empty() ||*/ + creature.GetMotionMaster().GetCurrentMovementGeneratorType() != MovementGeneratorType.Follow) + { + handler.SendSysMessage(CypherStrings.CreatureNotFollowYou, creature.GetName()); + return false; + } + + FollowMovementGenerator mgen = (FollowMovementGenerator)creature.GetMotionMaster().top(); + + if (mgen.target != player) + { + handler.SendSysMessage(CypherStrings.CreatureNotFollowYou, creature.GetName()); + return false; + } + + // reset movement + creature.GetMotionMaster().MovementExpired(true); + + handler.SendSysMessage(CypherStrings.CreatureNotFollowYouNow, creature.GetName()); + return true; + } + } + + [CommandGroup("delete", RBACPermissions.CommandNpcDelete)] + class DeleteCommands + { + [Command("delete", RBACPermissions.CommandNpcDelete)] + static bool HandleNpcDeleteCommand(StringArguments args, CommandHandler handler) + { + Creature unit = null; + + if (!args.Empty()) + { + // number or [name] Shift-click form |color|Hcreature:creature_guid|h[name]|h|r + string cId = handler.extractKeyFromLink(args, "Hcreature"); + if (string.IsNullOrEmpty(cId)) + return false; + + ulong lowguid = ulong.Parse(cId); + if (lowguid == 0) + return false; + + unit = handler.GetCreatureFromPlayerMapByDbGuid(lowguid); + } + else + unit = handler.getSelectedCreature(); + + if (!unit || unit.IsPet() || unit.IsTotem()) + { + handler.SendSysMessage(CypherStrings.SelectCreature); + return false; + } + + // Delete the creature + unit.CombatStop(); + unit.DeleteFromDB(); + unit.AddObjectToRemoveList(); + + handler.SendSysMessage(CypherStrings.CommandDelcreatmessage); + + return true; + } + + [Command("item", RBACPermissions.CommandNpcDeleteItem)] + static bool HandleNpcDeleteVendorItemCommand(StringArguments args, CommandHandler handler) + { + if (args.Empty()) + return false; + + Creature vendor = handler.getSelectedCreature(); + if (!vendor || !vendor.IsVendor()) + { + handler.SendSysMessage(CypherStrings.CommandVendorselection); + return false; + } + + string pitem = handler.extractKeyFromLink(args, "Hitem"); + if (string.IsNullOrEmpty(pitem)) + { + handler.SendSysMessage(CypherStrings.CommandNeeditemsend); + return false; + } + uint itemId = uint.Parse(pitem); + + ItemVendorType type = ItemVendorType.Item; // FIXME: make type (1 item, 2 currency) an argument + + if (!Global.ObjectMgr.RemoveVendorItem(vendor.GetEntry(), itemId, type)) + { + handler.SendSysMessage(CypherStrings.ItemNotInList, itemId); + return false; + } + + ItemTemplate itemTemplate = Global.ObjectMgr.GetItemTemplate(itemId); + + handler.SendSysMessage(CypherStrings.ItemDeletedFromList, itemId, itemTemplate.GetName()); + return true; + } + } + + [CommandGroup("set", RBACPermissions.CommandNpcSet, true)] + class SetCommands + { + [Command("allowmove", RBACPermissions.CommandNpcSetAllowmove)] + static bool HandleNpcSetAllowMovementCommand(StringArguments args, CommandHandler handler) + { + /* + if (Global.WorldMgr.getAllowMovement()) + { + Global.WorldMgr.SetAllowMovement(false); + handler.SendSysMessage(LANG_CREATURE_MOVE_DISABLED); + } + else + { + Global.WorldMgr.SetAllowMovement(true); + handler.SendSysMessage(LANG_CREATURE_MOVE_ENABLED); + } + */ + return true; + } + + [Command("entry", RBACPermissions.CommandNpcSetEntry)] + static bool SetEntry(StringArguments args, CommandHandler handler) + { + if (args.Empty()) + return false; + + uint newEntryNum = args.NextUInt32(); + if (newEntryNum == 0) + return false; + + Unit unit = handler.getSelectedUnit(); + if (!unit || !unit.IsTypeId(TypeId.Unit)) + { + handler.SendSysMessage(CypherStrings.SelectCreature); + return false; + } + Creature creature = unit.ToCreature(); + if (creature.UpdateEntry(newEntryNum)) + handler.SendSysMessage(CypherStrings.Done); + else + handler.SendSysMessage(CypherStrings.Error); + return true; + + } + + [Command("level", RBACPermissions.CommandNpcSetLevel)] + static bool SetLevel(StringArguments args, CommandHandler handler) + { + if (args.Empty()) + return false; + + byte lvl = args.NextByte(); + if (lvl < 1 || lvl > WorldConfig.GetIntValue(WorldCfg.MaxPlayerLevel) + 3) + { + handler.SendSysMessage(CypherStrings.BadValue); + return false; + } + + Creature creature = handler.getSelectedCreature(); + if (!creature || creature.IsPet()) + { + handler.SendSysMessage(CypherStrings.SelectCreature); + return false; + } + + creature.SetMaxHealth((uint)(100 + 30 * lvl)); + creature.SetHealth((uint)(100 + 30 * lvl)); + creature.SetLevel(lvl); + creature.SaveToDB(); + + return true; + } + + [Command("flag", RBACPermissions.CommandNpcSetFlag)] + static bool SetFlag(StringArguments args, CommandHandler handler) + { + if (args.Empty()) + return false; + + ulong npcFlags = args.NextUInt64(); + + Creature creature = handler.getSelectedCreature(); + if (!creature) + { + handler.SendSysMessage(CypherStrings.SelectCreature); + return false; + } + + creature.SetUInt64Value(UnitFields.NpcFlags, npcFlags); + + PreparedStatement stmt = DB.World.GetPreparedStatement(WorldStatements.UPD_CREATURE_NPCFLAG); + stmt.AddValue(0, npcFlags); + stmt.AddValue(1, creature.GetEntry()); + DB.World.Execute(stmt); + + handler.SendSysMessage(CypherStrings.ValueSavedRejoin); + + return true; + } + + [Command("factionid", RBACPermissions.CommandNpcSetFactionid)] + static bool SetFaction(StringArguments args, CommandHandler handler) + { + if (args.Empty()) + return false; + + uint factionId = args.NextUInt32(); + + if (!CliDB.FactionTemplateStorage.ContainsKey(factionId)) + { + handler.SendSysMessage(CypherStrings.WrongFaction, factionId); + return false; + } + + Creature creature = handler.getSelectedCreature(); + + if (!creature) + { + handler.SendSysMessage(CypherStrings.SelectCreature); + return false; + } + + creature.SetFaction(factionId); + + // Faction is set in creature_template - not inside creature + + // Update in memory.. + CreatureTemplate cinfo = creature.GetCreatureTemplate(); + if (cinfo != null) + cinfo.Faction = factionId; + + // ..and DB + PreparedStatement stmt = DB.World.GetPreparedStatement(WorldStatements.UPD_CREATURE_FACTION); + + stmt.AddValue(0, factionId); + stmt.AddValue(1, factionId); + stmt.AddValue(2, creature.GetEntry()); + + DB.World.Execute(stmt); + + return true; + } + + [Command("data", RBACPermissions.CommandNpcSetData)] + static bool SetData(StringArguments args, CommandHandler handler) + { + if (args.Empty()) + return false; + + string arg1 = args.NextString(); + string arg2 = args.NextString(); + + if (string.IsNullOrEmpty(arg1) || string.IsNullOrEmpty(arg2)) + return false; + + uint data_1 = uint.Parse(arg1); + uint data_2 = uint.Parse(arg2); + + if (data_1 == 0 || data_2 == 0) + return false; + + Creature creature = handler.getSelectedCreature(); + + if (!creature) + { + handler.SendSysMessage(CypherStrings.SelectCreature); + return false; + } + + creature.GetAI().SetData(data_1, data_2); + string AIorScript = creature.GetAIName() != "" ? "AI type: " + creature.GetAIName() : (creature.GetScriptName() != "" ? "Script Name: " + creature.GetScriptName() : "No AI or Script Name Set"); + handler.SendSysMessage(CypherStrings.NpcSetdata, creature.GetGUID(), creature.GetEntry(), creature.GetName(), data_1, data_2, AIorScript); + return true; + } + + [Command("model", RBACPermissions.CommandNpcSetModel)] + static bool SetModel(StringArguments args, CommandHandler handler) + { + if (args.Empty()) + return false; + + uint displayId = args.NextUInt32(); + + Creature creature = handler.getSelectedCreature(); + + if (!creature || creature.IsPet()) + { + handler.SendSysMessage(CypherStrings.SelectCreature); + return false; + } + + creature.SetDisplayId(displayId); + creature.SetNativeDisplayId(displayId); + + creature.SaveToDB(); + + return true; + } + + [Command("movetype", RBACPermissions.CommandNpcSetMovetype)] + static bool SetMoveType(StringArguments args, CommandHandler handler) + { + if (args.Empty()) + return false; + + // 3 arguments: + // GUID (optional - you can also select the creature) + // stay|random|way (determines the kind of movement) + // NODEL (optional - tells the system NOT to delete any waypoints) + // this is very handy if you want to do waypoints, that are + // later switched on/off according to special events (like escort + // quests, etc) + string guid_str = args.NextString(); + string type = args.NextString(); + string dontdel_str = args.NextString(); + + bool doNotDelete = false; + + if (string.IsNullOrEmpty(guid_str)) + return false; + + ulong lowguid = 0; + Creature creature = null; + + if (!string.IsNullOrEmpty(dontdel_str)) + { + Log.outDebug(LogFilter.Misc, "DEBUG: All 3 params are set"); + + // All 3 params are set + // GUID + // type + // doNotDEL + if (dontdel_str == "NODEL") + doNotDelete = true; + } + else + { + // Only 2 params - but maybe NODEL is set + if (!string.IsNullOrEmpty(type)) + { + Log.outDebug(LogFilter.Server, "DEBUG: Only 2 params "); + if (type == "NODEL") + { + doNotDelete = true; + type = null; + } + } + } + + if (string.IsNullOrEmpty(type)) // case .setmovetype $move_type (with selected creature) + { + type = guid_str; + creature = handler.getSelectedCreature(); + if (!creature || creature.IsPet()) + return false; + lowguid = creature.GetSpawnId(); + } + else // case .setmovetype #creature_guid $move_type (with selected creature) + { + lowguid = uint.Parse(guid_str); + + if (lowguid != 0) + creature = handler.GetCreatureFromPlayerMapByDbGuid(lowguid); + + // attempt check creature existence by DB data + if (!creature) + { + CreatureData data = Global.ObjectMgr.GetCreatureData(lowguid); + if (data == null) + { + handler.SendSysMessage(CypherStrings.CommandCreatguidnotfound, lowguid); + return false; + } + } + else + { + lowguid = creature.GetSpawnId(); + } + } + + // now lowguid is low guid really existed creature + // and creature point (maybe) to this creature or NULL + + MovementGeneratorType move_type; + + if (type == "stay") + move_type = MovementGeneratorType.Idle; + else if (type == "random") + move_type = MovementGeneratorType.Random; + else if (type == "way") + move_type = MovementGeneratorType.Waypoint; + else + return false; + + if (creature) + { + // update movement type + if (doNotDelete == false) + creature.LoadPath(0); + + creature.SetDefaultMovementType(move_type); + creature.GetMotionMaster().Initialize(); + if (creature.IsAlive()) // dead creature will reset movement generator at respawn + { + creature.setDeathState(DeathState.JustDied); + creature.Respawn(); + } + creature.SaveToDB(); + } + if (doNotDelete == false) + { + handler.SendSysMessage(CypherStrings.MoveTypeSet, type); + } + else + { + handler.SendSysMessage(CypherStrings.MoveTypeSetNodel, type); + } + + return true; + } + + [Command("phase", RBACPermissions.CommandNpcSetPhase)] + static bool HandleNpcSetPhaseCommand(StringArguments args, CommandHandler handler) + { + if (args.Empty()) + return false; + + uint phaseId = args.NextUInt32(); + if (!CliDB.PhaseStorage.ContainsKey(phaseId)) + { + handler.SendSysMessage(CypherStrings.PhaseNotfound); + return false; + } + + Creature creature = handler.getSelectedCreature(); + if (!creature || creature.IsPet()) + { + handler.SendSysMessage(CypherStrings.SelectCreature); + return false; + } + + creature.ClearPhases(); + creature.SetInPhase(phaseId, true, true); + creature.SetDBPhase((int)phaseId); + + creature.SaveToDB(); + return true; + } + + [Command("phasegroup", RBACPermissions.CommandNpcSetPhase)] + static bool HandleNpcSetPhaseGroup(StringArguments args, CommandHandler handler) + { + if (args.Empty()) + return false; + + uint phaseGroupId = args.NextUInt32(); + + Creature creature = handler.getSelectedCreature(); + if (!creature || creature.IsPet()) + { + handler.SendSysMessage(CypherStrings.SelectCreature); + return false; + } + + creature.ClearPhases(); + + foreach (uint id in Global.DB2Mgr.GetPhasesForGroup(phaseGroupId)) + creature.SetInPhase(id, false, true); // don't send update here for multiple phases, only send it once after adding all phases + + creature.UpdateObjectVisibility(); + creature.SetDBPhase(-(int)phaseGroupId); + + creature.SaveToDB(); + + return true; + } + + [Command("spawndist", RBACPermissions.CommandNpcSetSpawndist)] + static bool SetSpawnDist(StringArguments args, CommandHandler handler) + { + if (args.Empty()) + return false; + + float option = args.NextSingle(); + if (option < 0.0f) + { + handler.SendSysMessage(CypherStrings.BadValue); + return false; + } + + MovementGeneratorType mtype = MovementGeneratorType.Idle; + if (option > 0.0f) + mtype = MovementGeneratorType.Random; + + Creature creature = handler.getSelectedCreature(); + ulong guidLow = 0; + + if (creature) + guidLow = creature.GetSpawnId(); + else + return false; + + creature.SetRespawnRadius(option); + creature.SetDefaultMovementType(mtype); + creature.GetMotionMaster().Initialize(); + if (creature.IsAlive()) // dead creature will reset movement generator at respawn + { + creature.setDeathState(DeathState.JustDied); + creature.Respawn(); + } + + PreparedStatement stmt = DB.World.GetPreparedStatement(WorldStatements.UPD_CREATURE_SPAWN_DISTANCE); + stmt.AddValue(0, option); + stmt.AddValue(1, mtype); + stmt.AddValue(2, guidLow); + + DB.World.Execute(stmt); + + handler.SendSysMessage(CypherStrings.CommandSpawndist, option); + return true; + } + + [Command("spawntime", RBACPermissions.CommandNpcSetSpawntime)] + static bool SetSpawnTime(StringArguments args, CommandHandler handler) + { + if (args.Empty()) + return false; + + int spawnTime = args.NextInt32(); + + if (spawnTime < 0) + { + handler.SendSysMessage(CypherStrings.BadValue); + return false; + } + + Creature creature = handler.getSelectedCreature(); + ulong guidLow = 0; + + if (creature) + guidLow = creature.GetSpawnId(); + else + return false; + + PreparedStatement stmt = DB.World.GetPreparedStatement(WorldStatements.UPD_CREATURE_SPAWN_TIME_SECS); + stmt.AddValue(0, spawnTime); + stmt.AddValue(1, guidLow); + DB.World.Execute(stmt); + + creature.SetRespawnDelay((uint)spawnTime); + handler.SendSysMessage(CypherStrings.CommandSpawntime, spawnTime); + + return true; + } + + [Command("link", RBACPermissions.CommandNpcSetLink)] + static bool SetLink(StringArguments args, CommandHandler handler) + { + if (args.Empty()) + return false; + + ulong linkguid = args.NextUInt64(); + + Creature creature = handler.getSelectedCreature(); + if (!creature) + { + handler.SendSysMessage(CypherStrings.SelectCreature); + return false; + } + + if (creature.GetSpawnId() == 0) + { + handler.SendSysMessage("Selected creature {0} isn't in creature table", creature.GetGUID().ToString()); + return false; + } + + if (!Global.ObjectMgr.SetCreatureLinkedRespawn(creature.GetSpawnId(), linkguid)) + { + handler.SendSysMessage("Selected creature can't link with guid '{0}'", linkguid); + return false; + } + + handler.SendSysMessage("LinkGUID '{0}' added to creature with DBTableGUID: '{1}'", linkguid, creature.GetSpawnId()); + return true; + } + } + + [CommandGroup("add", RBACPermissions.CommandNpcAdd)] + class AddCommands + { + [Command("", RBACPermissions.CommandNpcAdd)] + static bool Default(StringArguments args, CommandHandler handler) + { + if (args.Empty()) + return false; + + string charID = handler.extractKeyFromLink(args, "Hcreature_entry"); + if (string.IsNullOrEmpty(charID)) + return false; + + uint id = uint.Parse(charID); + if (Global.ObjectMgr.GetCreatureTemplate(id) == null) + return false; + + Player chr = handler.GetSession().GetPlayer(); + float x = chr.GetPositionX(); + float y = chr.GetPositionY(); + float z = chr.GetPositionZ(); + float o = chr.GetOrientation(); + Map map = chr.GetMap(); + + Transport trans = chr.GetTransport(); + if (trans) + { + ulong guid = map.GenerateLowGuid(HighGuid.Creature); + CreatureData data = Global.ObjectMgr.NewOrExistCreatureData(guid); + data.id = id; + data.phaseMask = chr.GetPhaseMask(); + data.posX = chr.GetTransOffsetX(); + data.posY = chr.GetTransOffsetY(); + data.posZ = chr.GetTransOffsetZ(); + data.orientation = chr.GetTransOffsetO(); + + Creature _creature = trans.CreateNPCPassenger(guid, data); + + _creature.SaveToDB((uint)trans.GetGoInfo().MoTransport.SpawnMap, (byte)(1 << (int)map.GetSpawnMode()), chr.GetPhaseMask()); + + Global.ObjectMgr.AddCreatureToGrid(guid, data); + return true; + } + + Creature creature = new Creature(); + if (!creature.Create(map.GenerateLowGuid(HighGuid.Creature), map, chr.GetPhaseMask(), id, x, y, z, o)) + return false; + + creature.SaveToDB(map.GetId(), (byte)(1 << (int)map.GetSpawnMode()), chr.GetPhaseMask()); + + ulong db_guid = creature.GetSpawnId(); + + // To call _LoadGoods(); _LoadQuests(); CreateTrainerSpells() + // current "creature" variable is deleted and created fresh new, otherwise old values might trigger asserts or cause undefined behavior + creature.CleanupsBeforeDelete(); + creature = new Creature(); + if (!creature.LoadCreatureFromDB(db_guid, map)) + return false; + + Global.ObjectMgr.AddCreatureToGrid(db_guid, Global.ObjectMgr.GetCreatureData(db_guid)); + return true; + } + + [Command("item", RBACPermissions.CommandNpcAddItem)] + static bool AddItem(StringArguments args, CommandHandler handler) + { + if (args.Empty()) + return false; + + byte type = 1; // FIXME: make type (1 item, 2 currency) an argument + + string pitem = handler.extractKeyFromLink(args, "Hitem"); + if (string.IsNullOrEmpty(pitem)) + { + handler.SendSysMessage(CypherStrings.CommandNeeditemsend); + return false; + } + + int item_int = int.Parse(pitem); + if (item_int <= 0) + return false; + + uint itemId = (uint)item_int; + uint maxcount = args.NextUInt32(); + uint incrtime = args.NextUInt32(); + uint extendedcost = args.NextUInt32(); + + Creature vendor = handler.getSelectedCreature(); + if (!vendor) + { + handler.SendSysMessage(CypherStrings.SelectCreature); + return false; + } + + uint vendor_entry = vendor.GetEntry(); + + if (!Global.ObjectMgr.IsVendorItemValid(vendor_entry, itemId, (int)maxcount, incrtime, extendedcost, (ItemVendorType)type, handler.GetSession().GetPlayer())) + return false; + + Global.ObjectMgr.AddVendorItem(vendor_entry, itemId, (int)maxcount, incrtime, extendedcost, (ItemVendorType)type); + + ItemTemplate itemTemplate = Global.ObjectMgr.GetItemTemplate(itemId); + + handler.SendSysMessage(CypherStrings.ItemAddedToList, itemId, itemTemplate.GetName(), maxcount, incrtime, extendedcost); + return true; + } + + [Command("move", RBACPermissions.CommandNpcAddMove)] + static bool AddMove(StringArguments args, CommandHandler handler) + { + if (args.Empty()) + return false; + + ulong lowGuid = args.NextUInt64(); + string waitStr = args.NextString(); + + // attempt check creature existence by DB data + CreatureData data = Global.ObjectMgr.GetCreatureData(lowGuid); + if (data == null) + { + handler.SendSysMessage(CypherStrings.CommandCreatguidnotfound, lowGuid); + return false; + } + + int wait = !string.IsNullOrEmpty(waitStr) ? int.Parse(waitStr) : 0; + if (wait < 0) + wait = 0; + + // Update movement type + PreparedStatement stmt = DB.World.GetPreparedStatement(WorldStatements.UPD_CREATURE_MOVEMENT_TYPE); + stmt.AddValue(0, MovementGeneratorType.Waypoint); + stmt.AddValue(1, lowGuid); + DB.World.Execute(stmt); + + handler.SendSysMessage(CypherStrings.WaypointAdded); + + return true; + } + + [Command("formation", RBACPermissions.CommandNpcAddFormation)] + static bool AddFormation(StringArguments args, CommandHandler handler) + { + if (args.Empty()) + return false; + + uint leaderGUID = args.NextUInt32(); + Creature creature = handler.getSelectedCreature(); + + if (!creature || creature.GetSpawnId() == 0) + { + handler.SendSysMessage(CypherStrings.SelectCreature); + return false; + } + + ulong lowguid = creature.GetSpawnId(); + if (creature.GetFormation() != null) + { + handler.SendSysMessage("Selected creature is already member of group {0}", creature.GetFormation().GetId()); + return false; + } + + if (lowguid == 0) + return false; + + Player chr = handler.GetSession().GetPlayer(); + FormationInfo group_member = new FormationInfo(); + group_member.follow_angle = (creature.GetAngle(chr) - chr.GetOrientation()) * 180 / MathFunctions.PI; + group_member.follow_dist = (float)Math.Sqrt(Math.Pow(chr.GetPositionX() - creature.GetPositionX(), 2) + Math.Pow(chr.GetPositionY() - creature.GetPositionY(), 2)); + group_member.leaderGUID = leaderGUID; + group_member.groupAI = 0; + + FormationMgr.CreatureGroupMap[lowguid] = group_member; + creature.SearchFormation(); + + PreparedStatement stmt = DB.World.GetPreparedStatement(WorldStatements.INS_CREATURE_FORMATION); + stmt.AddValue(0, leaderGUID); + stmt.AddValue(1, lowguid); + stmt.AddValue(2, group_member.follow_dist); + stmt.AddValue(3, group_member.follow_angle); + stmt.AddValue(4, group_member.groupAI); + + DB.World.Execute(stmt); + + handler.SendSysMessage("Creature {0} added to formation with leader {1}", lowguid, leaderGUID); + + return true; + } + + [Command("temp", RBACPermissions.CommandNpcAddTemp)] + static bool HandleNpcAddTempSpawnCommand(StringArguments args, CommandHandler handler) + { + if (args.Empty()) + return false; + + bool loot = false; + string spawntype_str = args.NextString(); + if (spawntype_str.Equals("LOOT")) + loot = true; + else if (spawntype_str.Equals("NOLOOT")) + loot = false; + + string charID = handler.extractKeyFromLink(args, "Hcreature_entry"); + if (string.IsNullOrEmpty(charID)) + return false; + + Player chr = handler.GetSession().GetPlayer(); + + uint id = uint.Parse(charID); + if (id == 0) + return false; + + if (Global.ObjectMgr.GetCreatureTemplate(id) == null) + return false; + + chr.SummonCreature(id, chr, loot ? TempSummonType.CorpseTimedDespawn : TempSummonType.CorpseDespawn, 30 * Time.InMilliseconds); + + return true; + } + } + } +} diff --git a/Game/Chat/Commands/PetCommands.cs b/Game/Chat/Commands/PetCommands.cs new file mode 100644 index 000000000..f84e4bca0 --- /dev/null +++ b/Game/Chat/Commands/PetCommands.cs @@ -0,0 +1,203 @@ +/* + * Copyright (C) 2012-2017 CypherCore + * + * 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, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +using Framework.Constants; +using Framework.IO; +using Game.Entities; +using Game.Spells; + +namespace Game.Chat +{ + [CommandGroup("pet", RBACPermissions.CommandPet)] + class PetCommands + { + [Command("create", RBACPermissions.CommandPetCreate)] + static bool HandlePetCreateCommand(StringArguments args, CommandHandler handler) + { + Player player = handler.GetSession().GetPlayer(); + Creature creatureTarget = handler.getSelectedCreature(); + + if (!creatureTarget || creatureTarget.IsPet() || creatureTarget.IsTypeId(TypeId.Player)) + { + handler.SendSysMessage(CypherStrings.SelectCreature); + return false; + } + + CreatureTemplate creatureTemplate = creatureTarget.GetCreatureTemplate(); + // Creatures with family CreatureFamily.None crashes the server + if (creatureTemplate.Family == CreatureFamily.None) + { + handler.SendSysMessage("This creature cannot be tamed. (Family id: 0)."); + return false; + } + + if (!player.GetPetGUID().IsEmpty()) + { + handler.SendSysMessage("You already have a pet"); + return false; + } + + // Everything looks OK, create new pet + Pet pet = new Pet(player, PetType.Hunter); + if (!pet.CreateBaseAtCreature(creatureTarget)) + { + handler.SendSysMessage("Error 1"); + return false; + } + + creatureTarget.setDeathState(DeathState.JustDied); + creatureTarget.RemoveCorpse(); + creatureTarget.SetHealth(0); // just for nice GM-mode view + + pet.SetGuidValue(UnitFields.CreatedBy, player.GetGUID()); + pet.SetUInt32Value(UnitFields.FactionTemplate, player.getFaction()); + + if (!pet.InitStatsForLevel(creatureTarget.getLevel())) + { + Log.outError(LogFilter.ChatSystem, "InitStatsForLevel() in EffectTameCreature failed! Pet deleted."); + handler.SendSysMessage("Error 2"); + return false; + } + + // prepare visual effect for levelup + pet.SetUInt32Value(UnitFields.Level, creatureTarget.getLevel() - 1); + + pet.GetCharmInfo().SetPetNumber(Global.ObjectMgr.GeneratePetNumber(), true); + // this enables pet details window (Shift+P) + pet.InitPetCreateSpells(); + pet.SetFullHealth(); + + pet.GetMap().AddToMap(pet.ToCreature()); + + // visual effect for levelup + pet.SetUInt32Value(UnitFields.Level, creatureTarget.getLevel()); + + player.SetMinion(pet, true); + pet.SavePetToDB(PetSaveMode.AsCurrent); + player.PetSpellInitialize(); + + return true; + } + + [Command("learn", RBACPermissions.CommandPetLearn)] + static bool HandlePetLearnCommand(StringArguments args, CommandHandler handler) + { + if (args.Empty()) + return false; + + Pet pet = GetSelectedPlayerPetOrOwn(handler); + if (!pet) + { + handler.SendSysMessage(CypherStrings.SelectPlayerOrPet); + return false; + } + + uint spellId = handler.extractSpellIdFromLink(args); + if (spellId == 0 || !Global.SpellMgr.HasSpellInfo(spellId)) + return false; + + // Check if pet already has it + if (pet.HasSpell(spellId)) + { + handler.SendSysMessage("Pet already has spell: {0}", spellId); + return false; + } + + // Check if spell is valid + SpellInfo spellInfo = Global.SpellMgr.GetSpellInfo(spellId); + if (spellInfo == null || !Global.SpellMgr.IsSpellValid(spellInfo)) + { + handler.SendSysMessage(CypherStrings.CommandSpellBroken, spellId); + return false; + } + + pet.learnSpell(spellId); + + handler.SendSysMessage("Pet has learned spell {0}", spellId); + return true; + } + + [Command("unlearn", RBACPermissions.CommandPetUnlearn)] + static bool HandlePetUnlearnCommand(StringArguments args, CommandHandler handler) + { + if (args.Empty()) + return false; + + Pet pet = GetSelectedPlayerPetOrOwn(handler); + if (!pet) + { + handler.SendSysMessage(CypherStrings.SelectPlayerOrPet); + return false; + } + + uint spellId = handler.extractSpellIdFromLink(args); + + if (pet.HasSpell(spellId)) + pet.removeSpell(spellId, false); + else + handler.SendSysMessage("Pet doesn't have that spell"); + + return true; + } + + [Command("level", RBACPermissions.CommandPetLevel)] + static bool HandlePetLevelCommand(StringArguments args, CommandHandler handler) + { + Pet pet = GetSelectedPlayerPetOrOwn(handler); + Player owner = pet ? pet.GetOwner() : null; + if (!pet || !owner) + { + handler.SendSysMessage(CypherStrings.SelectPlayerOrPet); + return false; + } + + int level = args.NextInt32(); + if (level == 0) + level = (int)(owner.getLevel() - pet.getLevel()); + if (level == 0 || level < -SharedConst.StrongMaxLevel || level > SharedConst.StrongMaxLevel) + { + handler.SendSysMessage(CypherStrings.BadValue); + return false; + } + + int newLevel = (int)pet.getLevel() + level; + if (newLevel < 1) + newLevel = 1; + else if (newLevel > owner.getLevel()) + newLevel = (int)owner.getLevel(); + + pet.GivePetLevel(newLevel); + return true; + } + + static Pet GetSelectedPlayerPetOrOwn(CommandHandler handler) + { + Unit target = handler.getSelectedUnit(); + if (target) + { + if (target.IsTypeId(TypeId.Player)) + return target.ToPlayer().GetPet(); + if (target.IsPet()) + return target.ToPet(); + return null; + } + + Player player = handler.GetSession().GetPlayer(); + return player ? player.GetPet() : null; + } + } +} diff --git a/Game/Chat/Commands/QuestCommands.cs b/Game/Chat/Commands/QuestCommands.cs new file mode 100644 index 000000000..e04713ac5 --- /dev/null +++ b/Game/Chat/Commands/QuestCommands.cs @@ -0,0 +1,250 @@ +/* + * Copyright (C) 2012-2017 CypherCore + * + * 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, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +using Framework.Constants; +using Framework.IO; +using Game.DataStorage; +using Game.Entities; +using System.Collections.Generic; +using System.Linq; + +namespace Game.Chat +{ + [CommandGroup("quest", RBACPermissions.CommandQuest)] + class QuestCommands + { + [Command("add", RBACPermissions.CommandQuestAdd)] + static bool Add(StringArguments args, CommandHandler handler) + { + Player player = handler.getSelectedPlayer(); + if (!player) + { + handler.SendSysMessage(CypherStrings.NoCharSelected); + return false; + } + + // .addquest #entry' + // number or [name] Shift-click form |color|Hquest:quest_id:quest_level|h[name]|h|r + string cId = handler.extractKeyFromLink(args, "Hquest"); + if (string.IsNullOrEmpty(cId)) + return false; + + uint entry = uint.Parse(cId); + + Quest quest = Global.ObjectMgr.GetQuestTemplate(entry); + if (quest == null) + { + handler.SendSysMessage(CypherStrings.CommandQuestNotfound, entry); + return false; + } + + // check item starting quest (it can work incorrectly if added without item in inventory) + var itc = Global.ObjectMgr.GetItemTemplates(); + var result = itc.Values.FirstOrDefault(p => p.GetStartQuest() == entry); + + if (result != null) + { + handler.SendSysMessage(CypherStrings.CommandQuestStartfromitem, entry, result.GetId()); + return false; + } + + // ok, normal (creature/GO starting) quest + if (player.CanAddQuest(quest, true)) + player.AddQuestAndCheckCompletion(quest, null); + + return true; + } + + [Command("complete", RBACPermissions.CommandQuestComplete)] + static bool Complete(StringArguments args, CommandHandler handler) + { + Player player = handler.getSelectedPlayer(); + if (!player) + { + handler.SendSysMessage(CypherStrings.NoCharSelected); + return false; + } + + // .quest complete #entry + // number or [name] Shift-click form |color|Hquest:quest_id:quest_level|h[name]|h|r + string cId = handler.extractKeyFromLink(args, "Hquest"); + if (string.IsNullOrEmpty(cId)) + return false; + + uint entry = uint.Parse(cId); + + Quest quest = Global.ObjectMgr.GetQuestTemplate(entry); + + // If player doesn't have the quest + if (quest == null || player.GetQuestStatus(entry) == QuestStatus.None) + { + handler.SendSysMessage(CypherStrings.CommandQuestNotfound, entry); + return false; + } + + for (int i = 0; i < quest.Objectives.Count; ++i) + { + QuestObjective obj = quest.Objectives[i]; + + switch (obj.Type) + { + case QuestObjectiveType.Item: + { + uint curItemCount = player.GetItemCount((uint)obj.ObjectID, true); + List dest = new List(); + InventoryResult msg = player.CanStoreNewItem(ItemConst.NullBag, ItemConst.NullSlot, dest, (uint)obj.ObjectID, (uint)(obj.Amount - curItemCount)); + if (msg == InventoryResult.Ok) + { + Item item = player.StoreNewItem(dest, (uint)obj.ObjectID, true); + player.SendNewItem(item, (uint)(obj.Amount - curItemCount), true, false); + } + break; + } + case QuestObjectiveType.Monster: + { + CreatureTemplate creatureInfo = Global.ObjectMgr.GetCreatureTemplate((uint)obj.ObjectID); + if (creatureInfo != null) + for (int z = 0; z < obj.Amount; ++z) + player.KilledMonster(creatureInfo, ObjectGuid.Empty); + break; + } + case QuestObjectiveType.GameObject: + { + for (int z = 0; z < obj.Amount; ++z) + player.KillCreditGO((uint)obj.ObjectID); + break; + } + case QuestObjectiveType.MinReputation: + { + int curRep = player.GetReputationMgr().GetReputation((uint)obj.ObjectID); + if (curRep < obj.Amount) + { + FactionRecord factionEntry = CliDB.FactionStorage.LookupByKey(obj.ObjectID); + if (factionEntry != null) + player.GetReputationMgr().SetReputation(factionEntry, obj.Amount); + } + break; + } + case QuestObjectiveType.MaxReputation: + { + int curRep = player.GetReputationMgr().GetReputation((uint)obj.ObjectID); + if (curRep > obj.Amount) + { + FactionRecord factionEntry = CliDB.FactionStorage.LookupByKey(obj.ObjectID); + if (factionEntry != null) + player.GetReputationMgr().SetReputation(factionEntry, obj.Amount); + } + break; + } + case QuestObjectiveType.Money: + { + player.ModifyMoney(obj.Amount); + break; + } + } + + } + + player.CompleteQuest(entry); + return true; + } + + [Command("remove", RBACPermissions.CommandQuestRemove)] + static bool Remove(StringArguments args, CommandHandler handler) + { + Player player = handler.getSelectedPlayer(); + if (!player) + { + handler.SendSysMessage(CypherStrings.NoCharSelected); + return false; + } + + // .removequest #entry' + // number or [name] Shift-click form |color|Hquest:quest_id:quest_level|h[name]|h|r + string cId = handler.extractKeyFromLink(args, "Hquest"); + if (string.IsNullOrEmpty(cId)) + return false; + + uint entry = uint.Parse(cId); + + Quest quest = Global.ObjectMgr.GetQuestTemplate(entry); + if (quest == null) + { + handler.SendSysMessage(CypherStrings.CommandQuestNotfound, entry); + return false; + } + + // remove all quest entries for 'entry' from quest log + for (byte slot = 0; slot < SharedConst.MaxQuestLogSize; ++slot) + { + uint logQuest = player.GetQuestSlotQuestId(slot); + if (logQuest == entry) + { + player.SetQuestSlot(slot, 0); + + // we ignore unequippable quest items in this case, its' still be equipped + player.TakeQuestSourceItem(logQuest, false); + + if (quest.HasFlag(QuestFlags.Pvp)) + { + player.pvpInfo.IsHostile = player.pvpInfo.IsInHostileArea || player.HasPvPForcingQuest(); + player.UpdatePvPState(); + } + } + } + + player.RemoveActiveQuest(entry, false); + player.RemoveRewardedQuest(entry); + + Global.ScriptMgr.OnQuestStatusChange(player, entry); + + handler.SendSysMessage(CypherStrings.CommandQuestRemoved); + return true; + } + + [Command("reward", RBACPermissions.CommandQuestReward)] + static bool Reward(StringArguments args, CommandHandler handler) + { + Player player = handler.getSelectedPlayer(); + if (!player) + { + handler.SendSysMessage(CypherStrings.NoCharSelected); + return false; + } + + // .quest reward #entry + // number or [name] Shift-click form |color|Hquest:quest_id:quest_level|h[name]|h|r + string cId = handler.extractKeyFromLink(args, "Hquest"); + if (string.IsNullOrEmpty(cId)) + return false; + + uint entry = uint.Parse(cId); + + Quest quest = Global.ObjectMgr.GetQuestTemplate(entry); + + // If player doesn't have the quest + if (quest == null || player.GetQuestStatus(entry) != QuestStatus.Complete) + { + handler.SendSysMessage(CypherStrings.CommandQuestNotfound, entry); + return false; + } + + player.RewardQuest(quest, 0, player); + return true; + } + } +} diff --git a/Game/Chat/Commands/RbacCommands.cs b/Game/Chat/Commands/RbacCommands.cs new file mode 100644 index 000000000..f407b515e --- /dev/null +++ b/Game/Chat/Commands/RbacCommands.cs @@ -0,0 +1,317 @@ +/* + * Copyright (C) 2012-2017 CypherCore + * + * 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, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +using Framework.Constants; +using Framework.IO; +using Game.Accounts; +using Game.Entities; +using System.Collections.Generic; + +namespace Game.Chat.Commands +{ + [CommandGroup("rbac", RBACPermissions.CommandRbac, true)] + class RbacComands + { + [Command("list", RBACPermissions.CommandRbacList, true)] + static bool HandleRBACListPermissionsCommand(StringArguments args, CommandHandler handler) + { + uint id = args.NextUInt32(); + + if (id == 0) + { + var permissions = Global.AccountMgr.GetRBACPermissionList(); + handler.SendSysMessage(CypherStrings.RbacListPermissionsHeader); + foreach (var permission in permissions.Values) + handler.SendSysMessage(CypherStrings.RbacListElement, permission.GetId(), permission.GetName()); + } + else + { + RBACPermission permission = Global.AccountMgr.GetRBACPermission(id); + if (permission == null) + { + handler.SendSysMessage(CypherStrings.RbacWrongParameterId, id); + return false; + } + + handler.SendSysMessage(CypherStrings.RbacListPermissionsHeader); + handler.SendSysMessage(CypherStrings.RbacListElement, permission.GetId(), permission.GetName()); + handler.SendSysMessage(CypherStrings.RbacListPermsLinkedHeader); + var permissions = permission.GetLinkedPermissions(); + foreach (var permissionId in permissions) + { + RBACPermission rbacPermission = Global.AccountMgr.GetRBACPermission(permissionId); + if (rbacPermission != null) + handler.SendSysMessage(CypherStrings.RbacListElement, rbacPermission.GetId(), rbacPermission.GetName()); + } + } + + return true; + } + + [CommandGroup("account", RBACPermissions.CommandRbacAcc, true)] + class RbacAccountCommands + { + [Command("grant", RBACPermissions.CommandRbacAccPermGrant, true)] + static bool HandleRBACPermGrantCommand(StringArguments args, CommandHandler handler) + { + RBACCommandData command = ReadParams(args, handler); + if (command == null) + return false; + + RBACCommandResult result = command.rbac.GrantPermission(command.id, command.realmId); + RBACPermission permission = Global.AccountMgr.GetRBACPermission(command.id); + + switch (result) + { + case RBACCommandResult.CantAddAlreadyAdded: + handler.SendSysMessage(CypherStrings.RbacPermGrantedInList, command.id, permission.GetName(), + command.realmId, command.rbac.GetId(), command.rbac.GetName()); + break; + case RBACCommandResult.InDeniedList: + handler.SendSysMessage(CypherStrings.RbacPermGrantedInDeniedList, command.id, permission.GetName(), + command.realmId, command.rbac.GetId(), command.rbac.GetName()); + break; + case RBACCommandResult.OK: + handler.SendSysMessage(CypherStrings.RbacPermGranted, command.id, permission.GetName(), + command.realmId, command.rbac.GetId(), command.rbac.GetName()); + break; + case RBACCommandResult.IdDoesNotExists: + handler.SendSysMessage(CypherStrings.RbacWrongParameterId, command.id); + break; + default: + break; + } + + return true; + } + + [Command("deny", RBACPermissions.CommandRbacAccPermDeny, true)] + static bool HandleRBACPermDenyCommand(StringArguments args, CommandHandler handler) + { + RBACCommandData command = ReadParams(args, handler); + + if (command == null) + return false; + + RBACCommandResult result = command.rbac.DenyPermission(command.id, command.realmId); + RBACPermission permission = Global.AccountMgr.GetRBACPermission(command.id); + + switch (result) + { + case RBACCommandResult.CantAddAlreadyAdded: + handler.SendSysMessage(CypherStrings.RbacPermDeniedInList, command.id, permission.GetName(), + command.realmId, command.rbac.GetId(), command.rbac.GetName()); + break; + case RBACCommandResult.InGrantedList: + handler.SendSysMessage(CypherStrings.RbacPermDeniedInGrantedList, command.id, permission.GetName(), + command.realmId, command.rbac.GetId(), command.rbac.GetName()); + break; + case RBACCommandResult.OK: + handler.SendSysMessage(CypherStrings.RbacPermDenied, command.id, permission.GetName(), + command.realmId, command.rbac.GetId(), command.rbac.GetName()); + break; + case RBACCommandResult.IdDoesNotExists: + handler.SendSysMessage(CypherStrings.RbacWrongParameterId, command.id); + break; + default: + break; + } + + return true; + } + + [Command("revoke", RBACPermissions.CommandRbacAccPermRevoke, true)] + static bool HandleRBACPermRevokeCommand(StringArguments args, CommandHandler handler) + { + RBACCommandData command = ReadParams(args, handler); + + if (command == null) + return false; + + RBACCommandResult result = command.rbac.RevokePermission(command.id, command.realmId); + RBACPermission permission = Global.AccountMgr.GetRBACPermission(command.id); + + switch (result) + { + case RBACCommandResult.CantRevokeNotInList: + handler.SendSysMessage(CypherStrings.RbacPermRevokedNotInList, command.id, permission.GetName(), + command.realmId, command.rbac.GetId(), command.rbac.GetName()); + break; + case RBACCommandResult.OK: + handler.SendSysMessage(CypherStrings.RbacPermRevoked, command.id, permission.GetName(), + command.realmId, command.rbac.GetId(), command.rbac.GetName()); + break; + case RBACCommandResult.IdDoesNotExists: + handler.SendSysMessage(CypherStrings.RbacWrongParameterId, command.id); + break; + default: + break; + } + + return true; + } + + [Command("list", RBACPermissions.CommandRbacAccPermList, true)] + static bool HandleRBACPermListCommand(StringArguments args, CommandHandler handler) + { + RBACCommandData command = ReadParams(args, handler, false); + + if (command == null) + return false; + + handler.SendSysMessage(CypherStrings.RbacListHeaderGranted, command.rbac.GetId(), command.rbac.GetName()); + var granted = command.rbac.GetGrantedPermissions(); + if (granted.Empty()) + handler.SendSysMessage(CypherStrings.RbacListEmpty); + else + { + foreach (var id in granted) + { + RBACPermission permission = Global.AccountMgr.GetRBACPermission(id); + handler.SendSysMessage(CypherStrings.RbacListElement, permission.GetId(), permission.GetName()); + } + } + + handler.SendSysMessage(CypherStrings.RbacListHeaderDenied, command.rbac.GetId(), command.rbac.GetName()); + var denied = command.rbac.GetDeniedPermissions(); + if (denied.Empty()) + handler.SendSysMessage(CypherStrings.RbacListEmpty); + else + { + foreach (var id in denied) + { + RBACPermission permission = Global.AccountMgr.GetRBACPermission(id); + handler.SendSysMessage(CypherStrings.RbacListElement, permission.GetId(), permission.GetName()); + } + } + handler.SendSysMessage(CypherStrings.RbacListHeaderBySecLevel, command.rbac.GetId(), command.rbac.GetName(), command.rbac.GetSecurityLevel()); + var defaultPermissions = Global.AccountMgr.GetRBACDefaultPermissions(command.rbac.GetSecurityLevel()); + if (defaultPermissions.Empty()) + handler.SendSysMessage(CypherStrings.RbacListEmpty); + else + { + foreach (var id in defaultPermissions) + { + RBACPermission permission = Global.AccountMgr.GetRBACPermission(id); + handler.SendSysMessage(CypherStrings.RbacListElement, permission.GetId(), permission.GetName()); + } + } + + return true; + } + } + + static RBACCommandData ReadParams(StringArguments args, CommandHandler handler, bool checkParams = true) + { + if (args.Empty()) + return null; + + string param1 = args.NextString(); + string param2 = args.NextString(); + string param3 = args.NextString(); + + int realmId = -1; + uint accountId = 0; + string accountName; + uint id = 0; + RBACCommandData data; + RBACData rdata = null; + bool useSelectedPlayer = false; + + if (checkParams) + { + if (string.IsNullOrEmpty(param3)) + { + if (!string.IsNullOrEmpty(param2)) + realmId = int.Parse(param2); + + if (!string.IsNullOrEmpty(param1)) + id = uint.Parse(param1); + + useSelectedPlayer = true; + } + else + { + id = uint.Parse(param2); + realmId = int.Parse(param3); + } + + if (id == 0) + { + handler.SendSysMessage(CypherStrings.RbacWrongParameterId, id); + return null; + } + + if (realmId < -1 || realmId == 0) + { + handler.SendSysMessage(CypherStrings.RbacWrongParameterRealm, realmId); + return null; + } + } + else if (string.IsNullOrEmpty(param1)) + useSelectedPlayer = true; + + if (useSelectedPlayer) + { + Player player = handler.getSelectedPlayer(); + if (!player) + return null; + + rdata = player.GetSession().GetRBACData(); + accountId = rdata.GetId(); + Global.AccountMgr.GetName(accountId, out accountName); + } + else + { + accountName = param1; + accountId = Global.AccountMgr.GetId(accountName); + + if (accountId == 0) + { + handler.SendSysMessage(CypherStrings.AccountNotExist, accountName); + return null; + } + } + + if (checkParams && handler.HasLowerSecurityAccount(null, accountId, true)) + return null; + + data = new RBACCommandData(); + + if (rdata == null) + { + data.rbac = new RBACData(accountId, accountName, (int)Global.WorldMgr.GetRealm().Id.Realm, (byte)Global.AccountMgr.GetSecurity(accountId, (int)Global.WorldMgr.GetRealm().Id.Realm)); + data.rbac.LoadFromDB(); + data.needDelete = true; + } + else + data.rbac = rdata; + + data.id = id; + data.realmId = realmId; + return data; + } + + class RBACCommandData + { + public uint id; + public int realmId; + public RBACData rbac; + public bool needDelete; + } + } +} diff --git a/Game/Chat/Commands/ReloadCommand.cs b/Game/Chat/Commands/ReloadCommand.cs new file mode 100644 index 000000000..838d8a9f1 --- /dev/null +++ b/Game/Chat/Commands/ReloadCommand.cs @@ -0,0 +1,1114 @@ +/* + * Copyright (C) 2012-2017 CypherCore + * + * 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, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +using Framework.Constants; +using Framework.Database; +using Framework.IO; +using Game.Entities; +using Game.Loots; +using Game.Spells; + +namespace Game.Chat +{ + [CommandGroup("reload", RBACPermissions.CommandReload, true)] + class ReloadCommand + { + [Command("access_requirement", RBACPermissions.CommandReloadAccessRequirement, true)] + static bool HandleReloadAccessRequirementCommand(StringArguments args, CommandHandler handler) + { + Log.outInfo(LogFilter.Server, "Re-Loading Access Requirement definitions..."); + Global.ObjectMgr.LoadAccessRequirements(); + handler.SendGlobalGMSysMessage("DB table `access_requirement` reloaded."); + return true; + } + + [Command("achievement_reward", RBACPermissions.CommandReloadAchievementReward, true)] + static bool HandleReloadAchievementRewardCommand(StringArguments args, CommandHandler handler) + { + Log.outInfo(LogFilter.Server, "Re-Loading Achievement Reward Data..."); + Global.AchievementMgr.LoadRewards(); + handler.SendGlobalGMSysMessage("DB table `achievement_reward` reloaded."); + return true; + } + + [Command("areatrigger_involvedrelation", RBACPermissions.CommandReloadAreatriggerInvolvedrelation, true)] + static bool HandleReloadQuestAreaTriggersCommand(StringArguments args, CommandHandler handler) + { + Log.outInfo(LogFilter.Server, "Re-Loading Quest Area Triggers..."); + Global.ObjectMgr.LoadQuestAreaTriggers(); + handler.SendGlobalGMSysMessage("DB table `areatrigger_involvedrelation` (quest area triggers) reloaded."); + return true; + } + + [Command("areatrigger_tavern", RBACPermissions.CommandReloadAreatriggerTavern, true)] + static bool HandleReloadAreaTriggerTavernCommand(StringArguments args, CommandHandler handler) + { + Log.outInfo(LogFilter.Server, "Re-Loading Tavern Area Triggers..."); + Global.ObjectMgr.LoadTavernAreaTriggers(); + handler.SendGlobalGMSysMessage("DB table `areatrigger_tavern` reloaded."); + return true; + } + + [Command("areatrigger_teleport", RBACPermissions.CommandReloadAreatriggerTeleport, true)] + static bool HandleReloadAreaTriggerTeleportCommand(StringArguments args, CommandHandler handler) + { + Log.outInfo(LogFilter.Server, "Re-Loading AreaTrigger teleport definitions..."); + Global.ObjectMgr.LoadAreaTriggerTeleports(); + handler.SendGlobalGMSysMessage("DB table `areatrigger_teleport` reloaded."); + return true; + } + + [Command("areatrigger_template", RBACPermissions.CommandReloacSceneTemplate, true)] + static bool HandleReloadAreaTriggerTemplateCommand(StringArguments args, CommandHandler handler) + { + Log.outInfo(LogFilter.Server, "Reloading areatrigger_template table..."); + Global.AreaTriggerDataStorage.LoadAreaTriggerTemplates(); + handler.SendGlobalGMSysMessage("AreaTrigger templates reloaded. Already spawned AT won't be affected. New scriptname need a reboot."); + return true; + } + + [Command("auctions", RBACPermissions.CommandReloadAuctions, true)] + static bool HandleReloadAuctionsCommand(StringArguments args, CommandHandler handler) + { + // Reload dynamic data tables from the database + Log.outInfo(LogFilter.Server, "Re-Loading Auctions..."); + Global.AuctionMgr.LoadAuctionItems(); + Global.AuctionMgr.LoadAuctions(); + handler.SendGlobalGMSysMessage("Auctions reloaded."); + return true; + } + + [Command("autobroadcast", RBACPermissions.CommandReloadAutobroadcast, true)] + static bool HandleReloadAutobroadcastCommand(StringArguments args, CommandHandler handler) + { + Log.outInfo(LogFilter.Server, "Re-Loading Autobroadcasts..."); + Global.WorldMgr.LoadAutobroadcasts(); + handler.SendGlobalGMSysMessage("DB table `autobroadcast` reloaded."); + return true; + } + + [Command("battleground_template", RBACPermissions.CommandReloadBattlegroundTemplate, true)] + static bool HandleReloadBattlegroundTemplate(StringArguments args, CommandHandler handler) + { + Log.outInfo(LogFilter.Misc, "Re-Loading Battleground Templates..."); + Global.BattlegroundMgr.LoadBattlegroundTemplates(); + handler.SendGlobalGMSysMessage("DB table `battleground_template` reloaded."); + return true; + } + + [Command("character_template", RBACPermissions.CommandReloadCharacterTemplate, true)] + static bool HandleReloadCharacterTemplate(StringArguments args, CommandHandler handler) + { + Log.outInfo(LogFilter.Server, "Re-Loading Character Templates..."); + Global.CharacterTemplateDataStorage.LoadCharacterTemplates(); + handler.SendGlobalGMSysMessage("DB table `character_template` and `character_template_class` reloaded."); + return true; + } + + [Command("command", RBACPermissions.CommandReloadCommand, true)] + static bool HandleReloadCommandCommand(StringArguments args, CommandHandler handler) { return true; } + + [Command("conditions", RBACPermissions.CommandReloadConditions, true)] + static bool HandleReloadConditions(StringArguments args, CommandHandler handler) + { + Log.outInfo(LogFilter.Server, "Re-Loading Conditions..."); + Global.ConditionMgr.LoadConditions(true); + handler.SendGlobalGMSysMessage("Conditions reloaded."); + return true; + } + + [Command("config", RBACPermissions.CommandReloadConfig, true)] + static bool HandleReloadConfigCommand(StringArguments args, CommandHandler handler) + { + Log.outInfo(LogFilter.Server, "Re-Loading config settings..."); + Global.WorldMgr.LoadConfigSettings(true); + Global.MapMgr.InitializeVisibilityDistanceInfo(); + handler.SendGlobalGMSysMessage("World config settings reloaded."); + return true; + } + + [Command("conversation_template", RBACPermissions.CommandReloadConversationTemplate, true)] + static bool HandleReloadConversationTemplateCommand(StringArguments args, CommandHandler handler) + { + Log.outInfo(LogFilter.Server, "Reloading conversation_* tables..."); + Global.ConversationDataStorage.LoadConversationTemplates(); + handler.SendGlobalGMSysMessage("Conversation templates reloaded."); + return true; + } + + [Command("creature_linked_respawn", RBACPermissions.CommandReloadCreatureLinkedRespawn, true)] + static bool HandleReloadLinkedRespawnCommand(StringArguments args, CommandHandler handler) + { + Log.outInfo(LogFilter.Server, "Loading Linked Respawns... (`creature_linked_respawn`)"); + Global.ObjectMgr.LoadLinkedRespawn(); + handler.SendGlobalGMSysMessage("DB table `creature_linked_respawn` (creature linked respawns) reloaded."); + return true; + } + + [Command("creature_loot_template", RBACPermissions.CommandReloadCreatureLootTemplate, true)] + static bool HandleReloadLootTemplatesCreatureCommand(StringArguments args, CommandHandler handler) + { + Log.outInfo(LogFilter.Server, "Re-Loading Loot Tables... (`creature_loot_template`)"); + LootManager.LoadLootTemplates_Creature(); + LootStorage.Creature.CheckLootRefs(); + handler.SendGlobalGMSysMessage("DB table `creature_loot_template` reloaded."); + Global.ConditionMgr.LoadConditions(true); + return true; + } + + [Command("creature_onkill_reputation", RBACPermissions.CommandReloadCreatureOnkillReputation, true)] + static bool HandleReloadOnKillReputationCommand(StringArguments args, CommandHandler handler) + { + Log.outInfo(LogFilter.Server, "Re-Loading creature award reputation definitions..."); + Global.ObjectMgr.LoadReputationOnKill(); + handler.SendGlobalGMSysMessage("DB table `creature_onkill_reputation` reloaded."); + return true; + } + + [Command("creature_questender", RBACPermissions.CommandReloadCreatureQuestender, true)] + static bool HandleReloadCreatureQuestEnderCommand(StringArguments args, CommandHandler handler) + { + Log.outInfo(LogFilter.Server, "Loading Quests Relations... (`creature_questender`)"); + Global.ObjectMgr.LoadCreatureQuestEnders(); + handler.SendGlobalGMSysMessage("DB table `creature_questender` reloaded."); + return true; + } + + [Command("creature_queststarter", RBACPermissions.CommandReloadCreatureQueststarter, true)] + static bool HandleReloadCreatureQuestStarterCommand(StringArguments args, CommandHandler handler) + { + Log.outInfo(LogFilter.Server, "Loading Quests Relations... (`creature_queststarter`)"); + Global.ObjectMgr.LoadCreatureQuestStarters(); + handler.SendGlobalGMSysMessage("DB table `creature_queststarter` reloaded."); + return true; + } + + [Command("creature_summon_groups", RBACPermissions.CommandReloadCreatureSummonGroups, true)] + static bool HandleReloadCreatureSummonGroupsCommand(StringArguments args, CommandHandler handler) + { + Log.outInfo(LogFilter.Server, "Reloading creature summon groups..."); + Global.ObjectMgr.LoadTempSummons(); + handler.SendGlobalGMSysMessage("DB table `creature_summon_groups` reloaded."); + return true; + } + + [Command("creature_template", RBACPermissions.CommandReloadCreatureTemplate, true)] + static bool HandleReloadCreatureTemplateCommand(StringArguments args, CommandHandler handler) + { + if (args.Empty()) + return false; + + uint entry = 0; + while ((entry = args.NextUInt32()) != 0) + { + PreparedStatement stmt = DB.World.GetPreparedStatement(WorldStatements.SEL_CREATURE_TEMPLATE); + stmt.AddValue(0, entry); + SQLResult result = DB.World.Query(stmt); + + if (result.IsEmpty()) + { + handler.SendSysMessage(CypherStrings.CommandCreaturetemplateNotfound, entry); + continue; + } + + CreatureTemplate cInfo = Global.ObjectMgr.GetCreatureTemplate(entry); + if (cInfo == null) + { + handler.SendSysMessage(CypherStrings.CommandCreaturestorageNotfound, entry); + continue; + } + + Log.outInfo(LogFilter.Server, "Reloading creature template entry {0}", entry); + + Global.ObjectMgr.LoadCreatureTemplate(result.GetFields()); + Global.ObjectMgr.CheckCreatureTemplate(cInfo); + } + + handler.SendGlobalGMSysMessage("Creature template reloaded."); + return true; + } + + [Command("creature_text", RBACPermissions.CommandReloadCreatureText, true)] + static bool HandleReloadCreatureText(StringArguments args, CommandHandler handler) + { + Log.outInfo(LogFilter.Server, "Re-Loading Creature Texts..."); + Global.CreatureTextMgr.LoadCreatureTexts(); + handler.SendGlobalGMSysMessage("Creature Texts reloaded."); + return true; + } + + [Command("trinity_string", RBACPermissions.CommandReloadCypherString, true)] + static bool HandleReloadCypherStringCommand(StringArguments args, CommandHandler handler) + { + Log.outInfo(LogFilter.Server, "Re-Loading trinity_string Table!"); + Global.ObjectMgr.LoadCypherStrings(); + handler.SendGlobalGMSysMessage("DB table `trinity_string` reloaded."); + return true; + } + + [Command("criteria_data", RBACPermissions.CommandReloadCriteriaData, true)] + static bool HandleReloadCriteriaDataCommand(StringArguments args, CommandHandler handler) + { + Log.outInfo(LogFilter.Server, "Re-Loading Additional Criteria Data..."); + Global.CriteriaMgr.LoadCriteriaData(); + handler.SendGlobalGMSysMessage("DB table `criteria_data` reloaded."); + return true; + } + + [Command("disables", RBACPermissions.CommandReloadDisables, true)] + static bool HandleReloadDisablesCommand(StringArguments args, CommandHandler handler) + { + Log.outInfo(LogFilter.Server, "Re-Loading disables table..."); + Global.DisableMgr.LoadDisables(); + Log.outInfo(LogFilter.Server, "Checking quest disables..."); + Global.DisableMgr.CheckQuestDisables(); + handler.SendGlobalGMSysMessage("DB table `disables` reloaded."); + return true; + } + + [Command("disenchant_loot_template", RBACPermissions.CommandReloadDisenchantLootTemplate, true)] + static bool HandleReloadLootTemplatesDisenchantCommand(StringArguments args, CommandHandler handler) + { + Log.outInfo(LogFilter.Server, "Re-Loading Loot Tables... (`disenchant_loot_template`)"); + LootManager.LoadLootTemplates_Disenchant(); + LootStorage.Disenchant.CheckLootRefs(); + handler.SendGlobalGMSysMessage("DB table `disenchant_loot_template` reloaded."); + Global.ConditionMgr.LoadConditions(true); + return true; + } + + [Command("event_scripts", RBACPermissions.CommandReloadEventScripts, true)] + static bool HandleReloadEventScriptsCommand(StringArguments args, CommandHandler handler) + { + if (Global.MapMgr.IsScriptScheduled()) + { + handler.SendSysMessage("DB scripts used currently, please attempt reload later."); + return false; + } + + if (args != null) + Log.outInfo(LogFilter.Server, "Re-Loading Scripts from `event_scripts`..."); + + Global.ObjectMgr.LoadEventScripts(); + + if (args != null) + handler.SendGlobalGMSysMessage("DB table `event_scripts` reloaded."); + + return true; + } + + [Command("fishing_loot_template", RBACPermissions.CommandReloadFishingLootTemplate, true)] + static bool HandleReloadLootTemplatesFishingCommand(StringArguments args, CommandHandler handler) + { + Log.outInfo(LogFilter.Server, "Re-Loading Loot Tables... (`fishing_loot_template`)"); + LootManager.LoadLootTemplates_Fishing(); + LootStorage.Fishing.CheckLootRefs(); + handler.SendGlobalGMSysMessage("DB table `fishing_loot_template` reloaded."); + Global.ConditionMgr.LoadConditions(true); + return true; + } + + [Command("graveyard_zone", RBACPermissions.CommandReloadGraveyardZone, true)] + static bool HandleReloadGameGraveyardZoneCommand(StringArguments args, CommandHandler handler) + { + Log.outInfo(LogFilter.Server, "Re-Loading Graveyard-zone links..."); + + Global.ObjectMgr.LoadGraveyardZones(); + + handler.SendGlobalGMSysMessage("DB table `game_graveyard_zone` reloaded."); + + return true; + } + + [Command("game_tele", RBACPermissions.CommandReloadGameTele, true)] + static bool HandleReloadGameTeleCommand(StringArguments args, CommandHandler handler) + { + Log.outInfo(LogFilter.Server, "Re-Loading Game Tele coordinates..."); + + Global.ObjectMgr.LoadGameTele(); + + handler.SendGlobalGMSysMessage("DB table `game_tele` reloaded."); + + return true; + } + + [Command("gameobject_loot_template", RBACPermissions.CommandReloadGameobjectQuestLootTemplate, true)] + static bool HandleReloadLootTemplatesGameobjectCommand(StringArguments args, CommandHandler handler) + { + Log.outInfo(LogFilter.Server, "Re-Loading Loot Tables... (`gameobject_loot_template`)"); + LootManager.LoadLootTemplates_Gameobject(); + LootStorage.Gameobject.CheckLootRefs(); + handler.SendGlobalGMSysMessage("DB table `gameobject_loot_template` reloaded."); + Global.ConditionMgr.LoadConditions(true); + return true; + } + + [Command("gameobject_questender", RBACPermissions.CommandReloadGameobjectQuestender, true)] + static bool HandleReloadGOQuestEnderCommand(StringArguments args, CommandHandler handler) + { + Log.outInfo(LogFilter.Server, "Loading Quests Relations... (`gameobject_questender`)"); + Global.ObjectMgr.LoadGameobjectQuestEnders(); + handler.SendGlobalGMSysMessage("DB table `gameobject_questender` reloaded."); + return true; + } + + [Command("gameobject_queststarter", RBACPermissions.CommandReloadGameobjectQueststarter, true)] + static bool HandleReloadGOQuestStarterCommand(StringArguments args, CommandHandler handler) + { + Log.outInfo(LogFilter.Server, "Loading Quests Relations... (`gameobject_queststarter`)"); + Global.ObjectMgr.LoadGameobjectQuestStarters(); + handler.SendGlobalGMSysMessage("DB table `gameobject_queststarter` reloaded."); + return true; + } + + [Command("gossip_menu", RBACPermissions.CommandReloadGossipMenu, true)] + static bool HandleReloadGossipMenuCommand(StringArguments args, CommandHandler handler) + { + Log.outInfo(LogFilter.Server, "Re-Loading `gossip_menu` Table!"); + Global.ObjectMgr.LoadGossipMenu(); + handler.SendGlobalGMSysMessage("DB table `gossip_menu` reloaded."); + Global.ConditionMgr.LoadConditions(true); + return true; + } + + [Command("gossip_menu_option", RBACPermissions.CommandReloadGossipMenuOption, true)] + static bool HandleReloadGossipMenuOptionCommand(StringArguments args, CommandHandler handler) + { + Log.outInfo(LogFilter.Server, "Re-Loading `gossip_menu_option` Table!"); + Global.ObjectMgr.LoadGossipMenuItems(); + handler.SendGlobalGMSysMessage("DB table `gossip_menu_option` reloaded."); + Global.ConditionMgr.LoadConditions(true); + return true; + } + + [Command("item_enchantment_template", RBACPermissions.CommandReloadItemEnchantmentTemplate, true)] + static bool HandleReloadItemEnchantementsCommand(StringArguments args, CommandHandler handler) + { + Log.outInfo(LogFilter.Server, "Re-Loading Item Random Enchantments Table..."); + ItemEnchantment.LoadRandomEnchantmentsTable(); + handler.SendGlobalGMSysMessage("DB table `item_enchantment_template` reloaded."); + return true; + } + + [Command("item_loot_template", RBACPermissions.CommandReloadItemLootTemplate, true)] + static bool HandleReloadLootTemplatesItemCommand(StringArguments args, CommandHandler handler) + { + Log.outInfo(LogFilter.Server, "Re-Loading Loot Tables... (`item_loot_template`)"); + LootManager.LoadLootTemplates_Item(); + LootStorage.Items.CheckLootRefs(); + handler.SendGlobalGMSysMessage("DB table `item_loot_template` reloaded."); + Global.ConditionMgr.LoadConditions(true); + return true; + } + + [Command("lfg_dungeon_rewards", RBACPermissions.CommandReloadLfgDungeonRewards, true)] + static bool HandleReloadLfgRewardsCommand(StringArguments args, CommandHandler handler) + { + Log.outInfo(LogFilter.Server, "Re-Loading lfg dungeon rewards..."); + Global.LFGMgr.LoadRewards(); + handler.SendGlobalGMSysMessage("DB table `lfg_dungeon_rewards` reloaded."); + return true; + } + + [Command("locales_achievement_reward", RBACPermissions.CommandReloadLocalesAchievementReward, true)] + static bool HandleReloadLocalesAchievementRewardCommand(StringArguments args, CommandHandler handler) + { + Log.outInfo(LogFilter.Server, "Re-Loading Locales Achievement Reward Data..."); + Global.AchievementMgr.LoadRewardLocales(); + handler.SendGlobalGMSysMessage("DB table `locales_achievement_reward` reloaded."); + return true; + } + + [Command("locales_creature", RBACPermissions.CommandReloadLocalesCreature, true)] + static bool HandleReloadLocalesCreatureCommand(StringArguments args, CommandHandler handler) + { + Log.outInfo(LogFilter.Server, "Re-Loading Locales Creature ..."); + Global.ObjectMgr.LoadCreatureLocales(); + handler.SendGlobalGMSysMessage("DB table `locales_creature` reloaded."); + return true; + } + + [Command("locales_creature_text", RBACPermissions.CommandReloadLocalesCreatureText, true)] + static bool HandleReloadLocalesCreatureTextCommand(StringArguments args, CommandHandler handler) + { + Log.outInfo(LogFilter.Server, "Re-Loading Locales Creature Texts..."); + Global.CreatureTextMgr.LoadCreatureTextLocales(); + handler.SendGlobalGMSysMessage("DB table `locales_creature_text` reloaded."); + return true; + } + + [Command("locales_gameobject", RBACPermissions.CommandReloadLocalesGameobject, true)] + static bool HandleReloadLocalesGameobjectCommand(StringArguments args, CommandHandler handler) + { + Log.outInfo(LogFilter.Server, "Re-Loading Locales Gameobject ... "); + Global.ObjectMgr.LoadGameObjectLocales(); + handler.SendGlobalGMSysMessage("DB table `locales_gameobject` reloaded."); + return true; + } + + [Command("locales_gossip_menu_option", RBACPermissions.CommandReloadLocalesGossipMenuOption, true)] + static bool HandleReloadLocalesGossipMenuOptionCommand(StringArguments args, CommandHandler handler) + { + Log.outInfo(LogFilter.Server, "Re-Loading Locales Gossip Menu Option ... "); + Global.ObjectMgr.LoadGossipMenuItemsLocales(); + handler.SendGlobalGMSysMessage("DB table `locales_gossip_menu_option` reloaded."); + return true; + } + + [Command("locales_page_text", RBACPermissions.CommandReloadLocalesPageText, true)] + static bool HandleReloadLocalesPageTextCommand(StringArguments args, CommandHandler handler) + { + Log.outInfo(LogFilter.Server, "Re-Loading Locales Page Text ... "); + Global.ObjectMgr.LoadPageTextLocales(); + handler.SendGlobalGMSysMessage("DB table `locales_page_text` reloaded."); + return true; + } + + [Command("locales_points_of_interest", RBACPermissions.CommandReloadLocalesPointsOfInterest, true)] + static bool HandleReloadLocalesPointsOfInterestCommand(StringArguments args, CommandHandler handler) + { + Log.outInfo(LogFilter.Server, "Re-Loading Locales Points Of Interest ... "); + Global.ObjectMgr.LoadPointOfInterestLocales(); + handler.SendGlobalGMSysMessage("DB table `locales_points_of_interest` reloaded."); + return true; + } + + [Command("mail_level_reward", RBACPermissions.CommandReloadMailLevelReward, true)] + static bool HandleReloadMailLevelRewardCommand(StringArguments args, CommandHandler handler) + { + Log.outInfo(LogFilter.Server, "Re-Loading Player level dependent mail rewards..."); + Global.ObjectMgr.LoadMailLevelRewards(); + handler.SendGlobalGMSysMessage("DB table `mail_level_reward` reloaded."); + return true; + } + + [Command("mail_loot_template", RBACPermissions.CommandReloadMailLootTemplate, true)] + static bool HandleReloadLootTemplatesMailCommand(StringArguments args, CommandHandler handler) + { + Log.outInfo(LogFilter.Server, "Re-Loading Loot Tables... (`mail_loot_template`)"); + LootManager.LoadLootTemplates_Mail(); + LootStorage.Mail.CheckLootRefs(); + handler.SendGlobalGMSysMessage("DB table `mail_loot_template` reloaded."); + Global.ConditionMgr.LoadConditions(true); + return true; + } + + [Command("milling_loot_template", RBACPermissions.CommandReloadMillingLootTemplate, true)] + static bool HandleReloadLootTemplatesMillingCommand(StringArguments args, CommandHandler handler) + { + Log.outInfo(LogFilter.Server, "Re-Loading Loot Tables... (`milling_loot_template`)"); + LootManager.LoadLootTemplates_Milling(); + LootStorage.Milling.CheckLootRefs(); + handler.SendGlobalGMSysMessage("DB table `milling_loot_template` reloaded."); + Global.ConditionMgr.LoadConditions(true); + return true; + } + + [Command("npc_spellclick_spells", RBACPermissions.CommandReloadNpcSpellclickSpells, true)] + static bool HandleReloadSpellClickSpellsCommand(StringArguments args, CommandHandler handler) + { + Log.outInfo(LogFilter.Server, "Re-Loading `npc_spellclick_spells` Table!"); + Global.ObjectMgr.LoadNPCSpellClickSpells(); + handler.SendGlobalGMSysMessage("DB table `npc_spellclick_spells` reloaded."); + return true; + } + + [Command("npc_trainer", RBACPermissions.CommandReloadNpcTrainer, true)] + static bool HandleReloadNpcTrainerCommand(StringArguments args, CommandHandler handler) + { + Log.outInfo(LogFilter.Server, "Re-Loading `npc_trainer` Table!"); + Global.ObjectMgr.LoadTrainerSpell(); + handler.SendGlobalGMSysMessage("DB table `npc_trainer` reloaded."); + return true; + } + + [Command("npc_vendor", RBACPermissions.CommandReloadNpcVendor, true)] + static bool HandleReloadNpcVendorCommand(StringArguments args, CommandHandler handler) + { + Log.outInfo(LogFilter.Server, "Re-Loading `npc_vendor` Table!"); + Global.ObjectMgr.LoadVendors(); + handler.SendGlobalGMSysMessage("DB table `npc_vendor` reloaded."); + return true; + } + + [Command("page_text", RBACPermissions.CommandReloadPageText, true)] + static bool HandleReloadPageTextsCommand(StringArguments args, CommandHandler handler) + { + Log.outInfo(LogFilter.Server, "Re-Loading Page Text..."); + Global.ObjectMgr.LoadPageTexts(); + handler.SendGlobalGMSysMessage("DB table `page_text` reloaded."); + return true; + } + + [Command("pickpocketing_loot_template", RBACPermissions.CommandReloadPickpocketingLootTemplate, true)] + static bool HandleReloadLootTemplatesPickpocketingCommand(StringArguments args, CommandHandler handler) + { + Log.outInfo(LogFilter.Server, "Re-Loading Loot Tables... (`pickpocketing_loot_template`)"); + LootManager.LoadLootTemplates_Pickpocketing(); + LootStorage.Pickpocketing.CheckLootRefs(); + handler.SendGlobalGMSysMessage("DB table `pickpocketing_loot_template` reloaded."); + Global.ConditionMgr.LoadConditions(true); + return true; + } + + [Command("points_of_interest", RBACPermissions.CommandReloadPointsOfInterest, true)] + static bool HandleReloadPointsOfInterestCommand(StringArguments args, CommandHandler handler) + { + Log.outInfo(LogFilter.Server, "Re-Loading `points_of_interest` Table!"); + Global.ObjectMgr.LoadPointsOfInterest(); + handler.SendGlobalGMSysMessage("DB table `points_of_interest` reloaded."); + return true; + } + + [Command("prospecting_loot_template", RBACPermissions.CommandReloadProspectingLootTemplate, true)] + static bool HandleReloadLootTemplatesProspectingCommand(StringArguments args, CommandHandler handler) + { + Log.outInfo(LogFilter.Server, "Re-Loading Loot Tables... (`prospecting_loot_template`)"); + LootManager.LoadLootTemplates_Prospecting(); + LootStorage.Prospecting.CheckLootRefs(); + handler.SendGlobalGMSysMessage("DB table `prospecting_loot_template` reloaded."); + Global.ConditionMgr.LoadConditions(true); + return true; + } + + [Command("quest_greeting", RBACPermissions.CommandReloadQuestGreeting, true)] + static bool HandleReloadQuestGreetingCommand(StringArguments args, CommandHandler handler) + { + Log.outInfo(LogFilter.Server, "Re-Loading Quest Greeting ... "); + Global.ObjectMgr.LoadQuestGreetings(); + handler.SendGlobalGMSysMessage("DB table `quest_greeting` reloaded."); + return true; + } + + [Command("quest_locale", RBACPermissions.CommandReloadQuestLocale, true)] + static bool HandleReloadQuestLocaleCommand(StringArguments args, CommandHandler handler) + { + Log.outInfo(LogFilter.Server, "Re-Loading Quest Locale ... "); + Global.ObjectMgr.LoadQuestTemplateLocale(); + Global.ObjectMgr.LoadQuestObjectivesLocale(); + Global.ObjectMgr.LoadQuestOfferRewardLocale(); + Global.ObjectMgr.LoadQuestRequestItemsLocale(); + handler.SendGlobalGMSysMessage("DB table `quest_template_locale` reloaded."); + handler.SendGlobalGMSysMessage("DB table `quest_objectives_locale` reloaded."); + handler.SendGlobalGMSysMessage("DB table `quest_offer_reward_locale` reloaded."); + handler.SendGlobalGMSysMessage("DB table `quest_request_items_locale` reloaded."); + return true; + } + + [Command("quest_poi", RBACPermissions.CommandReloadQuestPoi, true)] + static bool HandleReloadQuestPOICommand(StringArguments args, CommandHandler handler) + { + Log.outInfo(LogFilter.Server, "Re-Loading Quest POI ..."); + Global.ObjectMgr.LoadQuestPOI(); + handler.SendGlobalGMSysMessage("DB Table `quest_poi` and `quest_poi_points` reloaded."); + return true; + } + + [Command("quest_template", RBACPermissions.CommandReloadQuestTemplate, true)] + static bool HandleReloadQuestTemplateCommand(StringArguments args, CommandHandler handler) + { + Log.outInfo(LogFilter.Server, "Re-Loading Quest Templates..."); + Global.ObjectMgr.LoadQuests(); + handler.SendGlobalGMSysMessage("DB table `quest_template` (quest definitions) reloaded."); + + /// dependent also from `gameobject` but this table not reloaded anyway + Log.outInfo(LogFilter.Server, "Re-Loading GameObjects for quests..."); + Global.ObjectMgr.LoadGameObjectForQuests(); + handler.SendGlobalGMSysMessage("Data GameObjects for quests reloaded."); + return true; + } + + [Command("rbac", RBACPermissions.CommandReloadRbac, true)] + static bool HandleReloadRBACCommand(StringArguments args, CommandHandler handler) + { + Log.outInfo(LogFilter.Server, "Reloading RBAC tables..."); + Global.AccountMgr.LoadRBAC(); + Global.WorldMgr.ReloadRBAC(); + handler.SendGlobalGMSysMessage("RBAC data reloaded."); + return true; + } + + [Command("reference_loot_template", RBACPermissions.CommandReloadReferenceLootTemplate, true)] + static bool HandleReloadLootTemplatesReferenceCommand(StringArguments args, CommandHandler handler) + { + Log.outInfo(LogFilter.Server, "Re-Loading Loot Tables... (`reference_loot_template`)"); + LootManager.LoadLootTemplates_Reference(); + handler.SendGlobalGMSysMessage("DB table `reference_loot_template` reloaded."); + Global.ConditionMgr.LoadConditions(true); + return true; + } + + [Command("reputation_reward_rate", RBACPermissions.CommandReloadReputationRewardRate, true)] + static bool HandleReloadReputationRewardRateCommand(StringArguments args, CommandHandler handler) + { + Log.outInfo(LogFilter.Server, "Re-Loading `reputation_reward_rate` Table!"); + Global.ObjectMgr.LoadReputationRewardRate(); + handler.SendGlobalSysMessage("DB table `reputation_reward_rate` reloaded."); + return true; + } + + [Command("reputation_spillover_template", RBACPermissions.CommandReloadSpilloverTemplate, true)] + static bool HandleReloadReputationSpilloverTemplateCommand(StringArguments args, CommandHandler handler) + { + Log.outInfo(LogFilter.Server, "Re-Loading `reputation_spillover_template` Table!"); + Global.ObjectMgr.LoadReputationSpilloverTemplate(); + handler.SendGlobalSysMessage("DB table `reputation_spillover_template` reloaded."); + return true; + } + + [Command("reserved_name", RBACPermissions.CommandReloadReservedName, true)] + static bool HandleReloadReservedNameCommand(StringArguments args, CommandHandler handler) + { + Log.outInfo(LogFilter.Server, "Loading ReservedNames... (`reserved_name`)"); + Global.ObjectMgr.LoadReservedPlayersNames(); + handler.SendGlobalGMSysMessage("DB table `reserved_name` (player reserved names) reloaded."); + return true; + } + + [Command("scene_template", RBACPermissions.CommandReloacSceneTemplate, true)] + static bool HandleReloadSceneTemplateCommand(StringArguments args, CommandHandler handler) + { + Log.outInfo(LogFilter.Misc, "Reloading scene_template table..."); + Global.ObjectMgr.LoadSceneTemplates(); + handler.SendGlobalGMSysMessage("Scenes templates reloaded. New scriptname need a reboot."); + return true; + } + + [Command("skill_discovery_template", RBACPermissions.CommandReloadSkillDiscoveryTemplate, true)] + static bool HandleReloadSkillDiscoveryTemplateCommand(StringArguments args, CommandHandler handler) + { + Log.outInfo(LogFilter.Server, "Re-Loading Skill Discovery Table..."); + SkillDiscovery.LoadSkillDiscoveryTable(); + handler.SendGlobalGMSysMessage("DB table `skill_discovery_template` (recipes discovered at crafting) reloaded."); + return true; + } + + static bool HandleReloadSkillPerfectItemTemplateCommand(StringArguments args, CommandHandler handler) + { // latched onto HandleReloadSkillExtraItemTemplateCommand as it's part of that table group (and i don't want to chance all the command IDs) + Log.outInfo(LogFilter.Misc, "Re-Loading Skill Perfection Data Table..."); + SkillPerfectItems.LoadSkillPerfectItemTable(); + handler.SendGlobalGMSysMessage("DB table `skill_perfect_item_template` (perfect item procs when crafting) reloaded."); + return true; + } + + [Command("skill_extra_item_template", RBACPermissions.CommandReloadSkillExtraItemTemplate, true)] + static bool HandleReloadSkillExtraItemTemplateCommand(StringArguments args, CommandHandler handler) + { + Log.outInfo(LogFilter.Server, "Re-Loading Skill Extra Item Table..."); + SkillExtraItems.LoadSkillExtraItemTable(); + handler.SendGlobalGMSysMessage("DB table `skill_extra_item_template` (extra item creation when crafting) reloaded."); + + return HandleReloadSkillPerfectItemTemplateCommand(args, handler); + } + + [Command("skill_fishing_base_level", RBACPermissions.CommandReloadSkillFishingBaseLevel, true)] + static bool HandleReloadSkillFishingBaseLevelCommand(StringArguments args, CommandHandler handler) + { + Log.outInfo(LogFilter.Server, "Re-Loading Skill Fishing base level requirements..."); + Global.ObjectMgr.LoadFishingBaseSkillLevel(); + handler.SendGlobalGMSysMessage("DB table `skill_fishing_base_level` (fishing base level for zone/subzone) reloaded."); + return true; + } + + [Command("skinning_loot_template", RBACPermissions.CommandReloadSkinningLootTemplate, true)] + static bool HandleReloadLootTemplatesSkinningCommand(StringArguments args, CommandHandler handler) + { + Log.outInfo(LogFilter.Server, "Re-Loading Loot Tables... (`skinning_loot_template`)"); + LootManager.LoadLootTemplates_Skinning(); + LootStorage.Skinning.CheckLootRefs(); + handler.SendGlobalGMSysMessage("DB table `skinning_loot_template` reloaded."); + Global.ConditionMgr.LoadConditions(true); + return true; + } + + [Command("smart_scripts", RBACPermissions.CommandReloadSmartScripts, true)] + static bool HandleReloadSmartScripts(StringArguments args, CommandHandler handler) + { + Log.outInfo(LogFilter.Server, "Re-Loading Smart Scripts..."); + Global.SmartAIMgr.LoadFromDB(); + handler.SendGlobalGMSysMessage("Smart Scripts reloaded."); + return true; + } + + [Command("spell_area", RBACPermissions.CommandReloadSpellArea, true)] + static bool HandleReloadSpellAreaCommand(StringArguments args, CommandHandler handler) + { + Log.outInfo(LogFilter.Server, "Re-Loading SpellArea Data..."); + Global.SpellMgr.LoadSpellAreas(); + handler.SendGlobalGMSysMessage("DB table `spell_area` (spell dependences from area/quest/auras state) reloaded."); + return true; + } + + [Command("spell_group", RBACPermissions.CommandReloadSpellGroup, true)] + static bool HandleReloadSpellGroupsCommand(StringArguments args, CommandHandler handler) + { + Log.outInfo(LogFilter.Server, "Re-Loading Spell Groups..."); + Global.SpellMgr.LoadSpellGroups(); + handler.SendGlobalGMSysMessage("DB table `spell_group` (spell groups) reloaded."); + return true; + } + + [Command("spell_group_stack_rules", RBACPermissions.CommandReloadSpellGroupStackRules, true)] + static bool HandleReloadSpellGroupStackRulesCommand(StringArguments args, CommandHandler handler) + { + Log.outInfo(LogFilter.Server, "Re-Loading Spell Group Stack Rules..."); + Global.SpellMgr.LoadSpellGroupStackRules(); + handler.SendGlobalGMSysMessage("DB table `spell_group_stack_rules` (spell stacking definitions) reloaded."); + return true; + } + + [Command("spell_learn_spell", RBACPermissions.CommandReloadSpellLearnSpell, true)] + static bool HandleReloadSpellLearnSpellCommand(StringArguments args, CommandHandler handler) + { + Log.outInfo(LogFilter.Server, "Re-Loading Spell Learn Spells..."); + Global.SpellMgr.LoadSpellLearnSpells(); + handler.SendGlobalGMSysMessage("DB table `spell_learn_spell` reloaded."); + return true; + } + + [Command("spell_linked_spell", RBACPermissions.CommandReloadSpellLinkedSpell, true)] + static bool HandleReloadSpellLinkedSpellCommand(StringArguments args, CommandHandler handler) + { + Log.outInfo(LogFilter.Server, "Re-Loading Spell Linked Spells..."); + Global.SpellMgr.LoadSpellLinked(); + handler.SendGlobalGMSysMessage("DB table `spell_linked_spell` reloaded."); + return true; + } + + [Command("spell_loot_template", RBACPermissions.CommandReloadSpellLootTemplate, true)] + static bool HandleReloadLootTemplatesSpellCommand(StringArguments args, CommandHandler handler) + { + Log.outInfo(LogFilter.Server, "Re-Loading Loot Tables... (`spell_loot_template`)"); + LootManager.LoadLootTemplates_Spell(); + LootStorage.Spell.CheckLootRefs(); + handler.SendGlobalGMSysMessage("DB table `spell_loot_template` reloaded."); + Global.ConditionMgr.LoadConditions(true); + return true; + } + + [Command("spell_pet_auras", RBACPermissions.CommandReloadSpellPetAuras, true)] + static bool HandleReloadSpellPetAurasCommand(StringArguments args, CommandHandler handler) + { + Log.outInfo(LogFilter.Server, "Re-Loading Spell pet auras..."); + Global.SpellMgr.LoadSpellPetAuras(); + handler.SendGlobalGMSysMessage("DB table `spell_pet_auras` reloaded."); + return true; + } + + [Command("spell_proc_event", RBACPermissions.CommandReloadSpellLinkedSpell, true)] + static bool HandleReloadSpellProcEventCommand(StringArguments args, CommandHandler handler) + { + Log.outInfo(LogFilter.Server, "Re-Loading Spell Proc Event conditions..."); + Global.SpellMgr.LoadSpellProcEvents(); + handler.SendGlobalGMSysMessage("DB table `spell_proc_event` (spell proc trigger requirements) reloaded."); + return true; + } + + [Command("spell_proc", RBACPermissions.CommandReloadSpellProc, true)] + static bool HandleReloadSpellProcsCommand(StringArguments args, CommandHandler handler) + { + Log.outInfo(LogFilter.Server, "Re-Loading Spell Proc conditions and data..."); + Global.SpellMgr.LoadSpellProcs(); + handler.SendGlobalGMSysMessage("DB table `spell_proc` (spell proc conditions and data) reloaded."); + return true; + } + + [Command("spell_required", RBACPermissions.CommandReloadSpellRequired, true)] + static bool HandleReloadSpellRequiredCommand(StringArguments args, CommandHandler handler) + { + Log.outInfo(LogFilter.Server, "Re-Loading Spell Required Data... "); + Global.SpellMgr.LoadSpellRequired(); + handler.SendGlobalGMSysMessage("DB table `spell_required` reloaded."); + return true; + } + + [Command("spell_scripts", RBACPermissions.CommandReloadSpellScripts, true)] + static bool HandleReloadSpellScriptsCommand(StringArguments args, CommandHandler handler) + { + if (Global.MapMgr.IsScriptScheduled()) + { + handler.SendSysMessage("DB scripts used currently, please attempt reload later."); + return false; + } + + if (args != null) + Log.outInfo(LogFilter.Server, "Re-Loading Scripts from `spell_scripts`..."); + + Global.ObjectMgr.LoadSpellScripts(); + + if (args != null) + handler.SendGlobalGMSysMessage("DB table `spell_scripts` reloaded."); + + return true; + } + + [Command("spell_target_position", RBACPermissions.CommandReloadSpellTargetPosition, true)] + static bool HandleReloadSpellTargetPositionCommand(StringArguments args, CommandHandler handler) + { + Log.outInfo(LogFilter.Server, "Re-Loading Spell target coordinates..."); + Global.SpellMgr.LoadSpellTargetPositions(); + handler.SendGlobalGMSysMessage("DB table `spell_target_position` (destination coordinates for spell targets) reloaded."); + return true; + } + + [Command("spell_threats", RBACPermissions.CommandReloadSpellThreats, true)] + static bool HandleReloadSpellThreatsCommand(StringArguments args, CommandHandler handler) + { + Log.outInfo(LogFilter.Server, "Re-Loading Aggro Spells Definitions..."); + Global.SpellMgr.LoadSpellThreats(); + handler.SendGlobalGMSysMessage("DB table `spell_threat` (spell aggro definitions) reloaded."); + return true; + } + + [Command("support", RBACPermissions.CommandReloadSupportSystem, true)] + static bool HandleReloadSupportSystemCommand(StringArguments args, CommandHandler handler) + { + Log.outInfo(LogFilter.Server, "Re-Loading Support System Tables..."); + Global.SupportMgr.LoadBugTickets(); + Global.SupportMgr.LoadComplaintTickets(); + Global.SupportMgr.LoadSuggestionTickets(); + handler.SendGlobalGMSysMessage("DB tables `gm_*` reloaded."); + return true; + } + + [Command("vehicle_accessory", RBACPermissions.CommandReloadVehicleAccesory, true)] + static bool HandleReloadVehicleAccessoryCommand(StringArguments args, CommandHandler handler) + { + Log.outInfo(LogFilter.Server, "Reloading vehicle_accessory table..."); + Global.ObjectMgr.LoadVehicleAccessories(); + handler.SendGlobalGMSysMessage("Vehicle accessories reloaded."); + return true; + } + + [Command("vehicle_template_accessory", RBACPermissions.CommandReloadVehicleTemplateAccessory, true)] + static bool HandleReloadVehicleTemplateAccessoryCommand(StringArguments args, CommandHandler handler) + { + Log.outInfo(LogFilter.Server, "Reloading vehicle_template_accessory table..."); + Global.ObjectMgr.LoadVehicleTemplateAccessories(); + handler.SendGlobalGMSysMessage("Vehicle template accessories reloaded."); + return true; + } + + [Command("warden_action", RBACPermissions.CommandReloadWardenAction, true)] + static bool HandleReloadWardenactionCommand(StringArguments args, CommandHandler handler) + { + if (!WorldConfig.GetBoolValue(WorldCfg.WardenEnabled)) + { + handler.SendSysMessage("Warden system disabled by config - reloading warden_action skipped."); + return false; + } + + //Log.outInfo(LogFilter.Misc, "Re-Loading warden_action Table!"); + //Global.WardenCheckMgr.LoadWardenOverrides(); + //handler.SendGlobalGMSysMessage("DB table `warden_action` reloaded."); + return true; + } + + [Command("waypoint_data", RBACPermissions.CommandReloadWaypointData, true)] + static bool HandleReloadWpCommand(StringArguments args, CommandHandler handler) + { + if (args != null) + Log.outInfo(LogFilter.Server, "Re-Loading Waypoints data from 'waypoints_data'"); + + Global.WaypointMgr.Load(); + + if (args != null) + handler.SendGlobalGMSysMessage("DB Table 'waypoint_data' reloaded."); + + return true; + } + + [Command("waypoint_scripts", RBACPermissions.CommandReloadWaypointScripts, true)] + static bool HandleReloadWpScriptsCommand(StringArguments args, CommandHandler handler) + { + if (Global.MapMgr.IsScriptScheduled()) + { + handler.SendSysMessage("DB scripts used currently, please attempt reload later."); + return false; + } + + if (args != null) + Log.outInfo(LogFilter.Server, "Re-Loading Scripts from `waypoint_scripts`..."); + + Global.ObjectMgr.LoadWaypointScripts(); + + if (args != null) + handler.SendGlobalGMSysMessage("DB table `waypoint_scripts` reloaded."); + + return true; + } + + [CommandGroup("all", RBACPermissions.CommandReloadAll, true)] + class AllCommand + { + [Command("", RBACPermissions.CommandReloadAll, true)] + static bool HandleReloadAllCommand(StringArguments args, CommandHandler handler) + { + HandleReloadSkillFishingBaseLevelCommand(args, handler); + + HandleReloadAllAchievementCommand(args, handler); + HandleReloadAllAreaCommand(args, handler); + HandleReloadAllLootCommand(args, handler); + HandleReloadAllNpcCommand(args, handler); + HandleReloadAllQuestCommand(args, handler); + HandleReloadAllSpellCommand(args, handler); + HandleReloadAllItemCommand(args, handler); + HandleReloadAllGossipsCommand(args, handler); + HandleReloadAllLocalesCommand(args, handler); + + HandleReloadAccessRequirementCommand(args, handler); + HandleReloadMailLevelRewardCommand(args, handler); + HandleReloadReservedNameCommand(args, handler); + HandleReloadCypherStringCommand(args, handler); + HandleReloadGameTeleCommand(args, handler); + + HandleReloadCreatureSummonGroupsCommand(args, handler); + + HandleReloadVehicleAccessoryCommand(args, handler); + HandleReloadVehicleTemplateAccessoryCommand(args, handler); + + HandleReloadAutobroadcastCommand(args, handler); + HandleReloadBattlegroundTemplate(args, handler); + HandleReloadCharacterTemplate(args, handler); + return true; + } + + [Command("achievement", RBACPermissions.CommandReloadAllAchievement, true)] + static bool HandleReloadAllAchievementCommand(StringArguments args, CommandHandler handler) + { + HandleReloadCriteriaDataCommand(args, handler); + HandleReloadAchievementRewardCommand(args, handler); + return true; + } + + [Command("area", RBACPermissions.CommandReloadAllArea, true)] + static bool HandleReloadAllAreaCommand(StringArguments args, CommandHandler handler) + { + HandleReloadAreaTriggerTeleportCommand(args, handler); + HandleReloadAreaTriggerTavernCommand(args, handler); + HandleReloadGameGraveyardZoneCommand(args, handler); + return true; + } + + [Command("gossips", RBACPermissions.CommandReloadAllGossip, true)] + static bool HandleReloadAllGossipsCommand(StringArguments args, CommandHandler handler) + { + HandleReloadGossipMenuCommand(null, handler); + HandleReloadGossipMenuOptionCommand(null, handler); + if (args == null) // already reload from all_scripts + HandleReloadPointsOfInterestCommand(null, handler); + return true; + } + + [Command("item", RBACPermissions.CommandReloadAllItem, true)] + static bool HandleReloadAllItemCommand(StringArguments args, CommandHandler handler) + { + HandleReloadPageTextsCommand(null, handler); + HandleReloadItemEnchantementsCommand(null, handler); + return true; + } + + [Command("locales", RBACPermissions.CommandReloadAllLocales, true)] + static bool HandleReloadAllLocalesCommand(StringArguments args, CommandHandler handler) + { + HandleReloadLocalesAchievementRewardCommand(null, handler); + HandleReloadLocalesCreatureCommand(null, handler); + HandleReloadLocalesCreatureTextCommand(null, handler); + HandleReloadLocalesGameobjectCommand(null, handler); + HandleReloadLocalesGossipMenuOptionCommand(null, handler); + HandleReloadLocalesPageTextCommand(null, handler); + HandleReloadLocalesPointsOfInterestCommand(null, handler); + HandleReloadQuestLocaleCommand(null, handler); + return true; + } + + [Command("loot", RBACPermissions.CommandReloadAllLoot, true)] + static bool HandleReloadAllLootCommand(StringArguments args, CommandHandler handler) + { + Log.outInfo(LogFilter.Server, "Re-Loading Loot Tables..."); + LootManager.LoadLootTables(); + handler.SendGlobalGMSysMessage("DB tables `*_loot_template` reloaded."); + Global.ConditionMgr.LoadConditions(true); + return true; + } + + [Command("npc", RBACPermissions.CommandReloadAllNpc, true)] + static bool HandleReloadAllNpcCommand(StringArguments args, CommandHandler handler) + { + if (args != null) // will be reloaded from all_gossips + { + HandleReloadNpcTrainerCommand(null, handler); + HandleReloadNpcVendorCommand(null, handler); + HandleReloadPointsOfInterestCommand(null, handler); + HandleReloadSpellClickSpellsCommand(null, handler); + } + return true; + } + + [Command("quest", RBACPermissions.CommandReloadAllQuest, true)] + static bool HandleReloadAllQuestCommand(StringArguments args, CommandHandler handler) + { + HandleReloadQuestAreaTriggersCommand(null, handler); + HandleReloadQuestGreetingCommand(null, handler); + HandleReloadQuestPOICommand(null, handler); + HandleReloadQuestTemplateCommand(null, handler); + + Log.outInfo(LogFilter.Server, "Re-Loading Quests Relations..."); + Global.ObjectMgr.LoadQuestStartersAndEnders(); + handler.SendGlobalGMSysMessage("DB tables `*_queststarter` and `*_questender` reloaded."); + return true; + } + + [Command("scripts", RBACPermissions.CommandReloadAllScripts, true)] + static bool HandleReloadAllScriptsCommand(StringArguments args, CommandHandler handler) + { + if (Global.MapMgr.IsScriptScheduled()) + { + handler.SendSysMessage("DB scripts used currently, please attempt reload later."); + return false; + } + + Log.outInfo(LogFilter.Server, "Re-Loading Scripts..."); + HandleReloadEventScriptsCommand(null, handler); + HandleReloadSpellScriptsCommand(null, handler); + handler.SendGlobalGMSysMessage("DB tables `*_scripts` reloaded."); + HandleReloadWpScriptsCommand(null, handler); + HandleReloadWpCommand(null, handler); + return true; + } + + [Command("spell", RBACPermissions.CommandReloadAllSpell, true)] + static bool HandleReloadAllSpellCommand(StringArguments args, CommandHandler handler) + { + HandleReloadSkillDiscoveryTemplateCommand(null, handler); + HandleReloadSkillExtraItemTemplateCommand(null, handler); + HandleReloadSpellRequiredCommand(null, handler); + HandleReloadSpellAreaCommand(null, handler); + HandleReloadSpellGroupsCommand(null, handler); + HandleReloadSpellLearnSpellCommand(null, handler); + HandleReloadSpellLinkedSpellCommand(null, handler); + HandleReloadSpellProcEventCommand(null, handler); + HandleReloadSpellProcsCommand(null, handler); + HandleReloadSpellTargetPositionCommand(null, handler); + HandleReloadSpellThreatsCommand(null, handler); + HandleReloadSpellGroupStackRulesCommand(null, handler); + HandleReloadSpellPetAurasCommand(null, handler); + return true; + } + } + } +} diff --git a/Game/Chat/Commands/ResetCommands.cs b/Game/Chat/Commands/ResetCommands.cs new file mode 100644 index 000000000..95b666650 --- /dev/null +++ b/Game/Chat/Commands/ResetCommands.cs @@ -0,0 +1,282 @@ +/* + * Copyright (C) 2012-2017 CypherCore + * + * 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, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +using Framework.Constants; +using Framework.Database; +using Framework.IO; +using Game.Achievements; +using Game.DataStorage; +using Game.Entities; +using System.Collections.Generic; + +namespace Game.Chat +{ + [CommandGroup("reset", RBACPermissions.CommandReset, true)] + class ResetCommands + { + [Command("achievements", RBACPermissions.CommandResetAchievements, true)] + static bool HandleResetAchievementsCommand(StringArguments args, CommandHandler handler) + { + Player target; + ObjectGuid targetGuid; + if (!handler.extractPlayerTarget(args, out target, out targetGuid)) + return false; + + if (target) + target.ResetAchievements(); + else + PlayerAchievementMgr.DeleteFromDB(targetGuid); + + return true; + } + + [Command("honor", RBACPermissions.CommandResetHonor, true)] + static bool HandleResetHonorCommand(StringArguments args, CommandHandler handler) + { + Player target; + if (!handler.extractPlayerTarget(args, out target)) + return false; + + target.SetUInt32Value(PlayerFields.Kills, 0); + target.SetUInt32Value(PlayerFields.LifetimeHonorableKills, 0); + target.UpdateCriteria(CriteriaTypes.EarnHonorableKill); + + return true; + } + + static bool HandleResetStatsOrLevelHelper(Player player) + { + ChrClassesRecord classEntry = CliDB.ChrClassesStorage.LookupByKey(player.GetClass()); + if (classEntry == null) + { + Log.outError(LogFilter.Server, "Class {0} not found in DBC (Wrong DBC files?)", player.GetClass()); + return false; + } + + PowerType powerType = classEntry.PowerType; + + // reset m_form if no aura + if (!player.HasAuraType(AuraType.ModShapeshift)) + player.SetShapeshiftForm(ShapeShiftForm.None); + + player.setFactionForRace(player.GetRace()); + player.SetUInt32Value(UnitFields.DisplayPower, (uint)powerType); + + // reset only if player not in some form; + if (player.GetShapeshiftForm() == ShapeShiftForm.None) + player.InitDisplayIds(); + + player.SetByteValue(UnitFields.Bytes2, UnitBytes2Offsets.PvpFlag, (byte)UnitBytes2Flags.PvP); + + player.SetUInt32Value(UnitFields.Flags, (uint)UnitFlags.PvpAttackable); + + //-1 is default value + player.SetUInt32Value(PlayerFields.WatchedFactionIndex, 0xFFFFFFFF); + return true; + } + + [Command("level", RBACPermissions.CommandResetLevel, true)] + static bool HandleResetLevelCommand(StringArguments args, CommandHandler handler) + { + Player target; + if (!handler.extractPlayerTarget(args, out target)) + return false; + + if (!HandleResetStatsOrLevelHelper(target)) + return false; + + byte oldLevel = (byte)target.getLevel(); + + // set starting level + uint startLevel = (uint)(target.GetClass() != Class.Deathknight ? WorldConfig.GetIntValue(WorldCfg.StartPlayerLevel) : WorldConfig.GetIntValue(WorldCfg.StartDeathKnightPlayerLevel)); + + target._ApplyAllLevelScaleItemMods(false); + target.SetLevel(startLevel); + target.InitRunes(); + target.InitStatsForLevel(true); + target.InitTaxiNodesForLevel(); + target.InitTalentForLevel(); + target.SetUInt32Value(PlayerFields.Xp, 0); + + target._ApplyAllLevelScaleItemMods(true); + + // reset level for pet + Pet pet = target.GetPet(); + if (pet) + pet.SynchronizeLevelWithOwner(); + + Global.ScriptMgr.OnPlayerLevelChanged(target, oldLevel); + + return true; + } + + [Command("spells", RBACPermissions.CommandResetSpells, true)] + static bool HandleResetSpellsCommand(StringArguments args, CommandHandler handler) + { + Player target; + ObjectGuid targetGuid; + string targetName; + if (!handler.extractPlayerTarget(args, out target, out targetGuid, out targetName)) + return false; + + if (target) + { + target.ResetSpells(); + + target.SendSysMessage(CypherStrings.ResetSpells); + if (handler.GetSession() == null || handler.GetSession().GetPlayer() != target) + handler.SendSysMessage(CypherStrings.ResetSpellsOnline, handler.GetNameLink(target)); + } + else + { + PreparedStatement stmt = DB.Characters.GetPreparedStatement(CharStatements.UPD_ADD_AT_LOGIN_FLAG); + stmt.AddValue(0, (ushort)AtLoginFlags.ResetSpells); + stmt.AddValue(1, targetGuid.GetCounter()); + DB.Characters.Execute(stmt); + + handler.SendSysMessage(CypherStrings.ResetSpellsOffline, targetName); + } + + return true; + } + + [Command("stats", RBACPermissions.CommandResetStats, true)] + static bool HandleResetStatsCommand(StringArguments args, CommandHandler handler) + { + Player target; + if (!handler.extractPlayerTarget(args, out target)) + return false; + + if (!HandleResetStatsOrLevelHelper(target)) + return false; + + target.InitRunes(); + target.InitStatsForLevel(true); + target.InitTaxiNodesForLevel(); + target.InitTalentForLevel(); + + return true; + } + + [Command("talents", RBACPermissions.CommandResetTalents, true)] + static bool HandleResetTalentsCommand(StringArguments args, CommandHandler handler) + { + Player target; + ObjectGuid targetGuid; + string targetName; + + if (!handler.extractPlayerTarget(args, out target, out targetGuid, out targetName)) + { + /* TODO: 6.x remove/update pet talents + // Try reset talents as Hunter Pet + Creature* creature = handler.getSelectedCreature(); + if (!*args && creature && creature.IsPet()) + { + Unit* owner = creature.GetOwner(); + if (owner && owner.GetTypeId() == TYPEID_PLAYER && creature.ToPet().IsPermanentPetFor(owner.ToPlayer())) + { + creature.ToPet().resetTalents(); + owner.ToPlayer().SendTalentsInfoData(true); + + ChatHandler(owner.ToPlayer().GetSession()).SendSysMessage(LANG_RESET_PET_TALENTS); + if (!handler.GetSession() || handler.GetSession().GetPlayer() != owner.ToPlayer()) + handler.PSendSysMessage(LANG_RESET_PET_TALENTS_ONLINE, handler.GetNameLink(owner.ToPlayer()).c_str()); + } + return true; + } + */ + + handler.SendSysMessage(CypherStrings.NoCharSelected); + return false; + } + + if (target) + { + target.ResetTalents(true); + target.ResetTalentSpecialization(); + target.SendTalentsInfoData(); + target.SendSysMessage(CypherStrings.ResetTalents); + if (handler.GetSession() == null || handler.GetSession().GetPlayer() != target) + handler.SendSysMessage(CypherStrings.ResetTalentsOnline, handler.GetNameLink(target)); + + /* TODO: 6.x remove/update pet talents + Pet* pet = target.GetPet(); + Pet.resetTalentsForAllPetsOf(target, pet); + if (pet) + target.SendTalentsInfoData(true); + */ + return true; + } + else if (!targetGuid.IsEmpty()) + { + PreparedStatement stmt = DB.Characters.GetPreparedStatement(CharStatements.UPD_ADD_AT_LOGIN_FLAG); + stmt.AddValue(0, (ushort)(AtLoginFlags.None | AtLoginFlags.ResetPetTalents)); + stmt.AddValue(1, targetGuid.GetCounter()); + DB.Characters.Execute(stmt); + + string nameLink = handler.playerLink(targetName); + handler.SendSysMessage(CypherStrings.ResetTalentsOffline, nameLink); + return true; + } + + handler.SendSysMessage(CypherStrings.NoCharSelected); + return false; + } + + [Command("all", RBACPermissions.CommandResetAll, true)] + static bool HandleResetAllCommand(StringArguments args, CommandHandler handler) + { + if (args.Empty()) + return false; + + string caseName = args.NextString(); + + AtLoginFlags atLogin; + + // Command specially created as single command to prevent using short case names + if (caseName == "spells") + { + atLogin = AtLoginFlags.ResetSpells; + Global.WorldMgr.SendWorldText(CypherStrings.ResetallSpells); + if (handler.GetSession() == null) + handler.SendSysMessage(CypherStrings.ResetallSpells); + } + else if (caseName == "talents") + { + atLogin = AtLoginFlags.ResetTalents | AtLoginFlags.ResetPetTalents; + Global.WorldMgr.SendWorldText(CypherStrings.ResetallTalents); + if (handler.GetSession() == null) + handler.SendSysMessage(CypherStrings.ResetallTalents); + } + else + { + handler.SendSysMessage(CypherStrings.ResetallUnknownCase, args); + return false; + } + + PreparedStatement stmt = DB.Characters.GetPreparedStatement(CharStatements.UPD_ALL_AT_LOGIN_FLAGS); + stmt.AddValue(0, (ushort)atLogin); + DB.Characters.Execute(stmt); + + var plist = Global.ObjAccessor.GetPlayers(); + foreach (var player in plist) + player.SetAtLoginFlag(atLogin); + + return true; + } + } +} diff --git a/Game/Chat/Commands/SceneCommands.cs b/Game/Chat/Commands/SceneCommands.cs new file mode 100644 index 000000000..fcf606f26 --- /dev/null +++ b/Game/Chat/Commands/SceneCommands.cs @@ -0,0 +1,118 @@ +/* + * Copyright (C) 2012-2017 CypherCore + * + * 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, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +using Framework.Constants; +using Framework.IO; +using Game.DataStorage; +using Game.Entities; +using System; + +namespace Game.Chat +{ + [CommandGroup("scene", RBACPermissions.CommandScene)] + class SceneCommands + { + [Command("cancel", RBACPermissions.CommandSceneCancel)] + static bool HandleCancelSceneCommand(StringArguments args, CommandHandler handler) + { + if (args.Empty()) + return false; + + Player target = handler.getSelectedPlayerOrSelf(); + if (!target) + { + handler.SendSysMessage(CypherStrings.PlayerNotFound); + return false; + } + + uint id = args.NextUInt32(); + + if (!CliDB.SceneScriptPackageStorage.HasRecord(id)) + return false; + + target.GetSceneMgr().CancelSceneByPackageId(id); + return true; + } + + [Command("debug", RBACPermissions.CommandSceneDedug)] + static bool HandleDebugSceneCommand(StringArguments args, CommandHandler handler) + { + Player player = handler.GetSession().GetPlayer(); + if (player) + { + player.GetSceneMgr().ToggleDebugSceneMode(); + handler.SendSysMessage(player.GetSceneMgr().IsInDebugSceneMode() ? CypherStrings.CommandSceneDebugOn : CypherStrings.CommandSceneDebugOff); + } + + return true; + } + + [Command("play", RBACPermissions.CommandScenePlay)] + static bool HandlePlaySceneCommand(StringArguments args, CommandHandler handler) + { + if (args.Empty()) + return false; + + string sceneIdStr = args.NextString(); + if (sceneIdStr.IsEmpty()) + return false; + + uint sceneId = uint.Parse(sceneIdStr); + Player target = handler.getSelectedPlayerOrSelf(); + if (!target) + { + handler.SendSysMessage(CypherStrings.PlayerNotFound); + return false; + } + + if (Global.ObjectMgr.GetSceneTemplate(sceneId) == null) + return false; + + target.GetSceneMgr().PlayScene(sceneId); + return true; + } + + [Command("playpackage", RBACPermissions.CommandScenePlayPackage)] + static bool HandlePlayScenePackageCommand(StringArguments args, CommandHandler handler) + { + if (args.Empty()) + return false; + + string scenePackageIdStr = args.NextString(); + string flagsStr = args.NextString(""); + + if (scenePackageIdStr.IsEmpty()) + return false; + + uint scenePackageId = uint.Parse(scenePackageIdStr); + uint flags = !flagsStr.IsEmpty() ? uint.Parse(flagsStr) : (uint)SceneFlags.Unk16; + Player target = handler.getSelectedPlayerOrSelf(); + + if (!target) + { + handler.SendSysMessage(CypherStrings.PlayerNotFound); + return false; + } + + if (!CliDB.SceneScriptPackageStorage.HasRecord(scenePackageId)) + return false; + + target.GetSceneMgr().PlaySceneByPackageId(scenePackageId, (SceneFlags)flags); + return true; + } + } +} diff --git a/Game/Chat/Commands/SendCommands.cs b/Game/Chat/Commands/SendCommands.cs new file mode 100644 index 000000000..37ac433a5 --- /dev/null +++ b/Game/Chat/Commands/SendCommands.cs @@ -0,0 +1,248 @@ +/* + * Copyright (C) 2012-2017 CypherCore + * + * 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, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +using Framework.Constants; +using Framework.Database; +using Framework.IO; +using Game.Entities; +using Game.Mails; +using System.Collections.Generic; + +namespace Game.Chat.Commands +{ + [CommandGroup("send", RBACPermissions.CommandSend, false)] + class SendCommands + { + [Command("mail", RBACPermissions.CommandSendMail, true)] + static bool HandleSendMailCommand(StringArguments args, CommandHandler handler) + { + // format: name "subject text" "mail text" + Player target; + ObjectGuid targetGuid; + string targetName; + if (!handler.extractPlayerTarget(args, out target, out targetGuid, out targetName)) + return false; + + string tail1 = args.NextString(""); + if (string.IsNullOrEmpty(tail1)) + return false; + + string subject = handler.extractQuotedArg(tail1); + if (string.IsNullOrEmpty(subject)) + return false; + + string tail2 = args.NextString(""); + if (string.IsNullOrEmpty(tail2)) + return false; + + string text = handler.extractQuotedArg(tail2); + if (string.IsNullOrEmpty(text)) + return false; + + // from console show not existed sender + MailSender sender = new MailSender(MailMessageType.Normal, handler.GetSession() ? handler.GetSession().GetPlayer().GetGUID().GetCounter() : 0, MailStationery.Gm); + + /// @todo Fix poor design + SQLTransaction trans = new SQLTransaction(); + new MailDraft(subject, text) + .SendMailTo(trans, new MailReceiver(target, targetGuid.GetCounter()), sender); + + DB.Characters.CommitTransaction(trans); + + string nameLink = handler.playerLink(targetName); + handler.SendSysMessage(CypherStrings.MailSent, nameLink); + return true; + } + + [Command("items", RBACPermissions.CommandSendItems, true)] + static bool HandleSendItemsCommand(StringArguments args, CommandHandler handler) + { + // format: name "subject text" "mail text" item1[:count1] item2[:count2] ... item12[:count12] + Player receiver; + ObjectGuid receiverGuid; + string receiverName; + if (!handler.extractPlayerTarget(args, out receiver, out receiverGuid, out receiverName)) + return false; + + string tail1 = args.NextString(""); + if (string.IsNullOrEmpty(tail1)) + return false; + + string subject = handler.extractQuotedArg(tail1); + if (string.IsNullOrEmpty(subject)) + return false; + + string tail2 = args.NextString(""); + if (string.IsNullOrEmpty(tail2)) + return false; + + string text = handler.extractQuotedArg(tail2); + if (string.IsNullOrEmpty(text)) + return false; + + // extract items + List> items = new List>(); + + // get all tail string + StringArguments tail = new StringArguments(args.NextString("")); + + // get from tail next item str + StringArguments itemStr; + while ((itemStr = new StringArguments(tail.NextString(" "))) != null) + { + // parse item str + string itemIdStr = itemStr.NextString(":"); + string itemCountStr = itemStr.NextString(" "); + + uint itemId = uint.Parse(itemIdStr); + if (itemId == 0) + return false; + + ItemTemplate item_proto = Global.ObjectMgr.GetItemTemplate(itemId); + if (item_proto == null) + { + handler.SendSysMessage(CypherStrings.CommandItemidinvalid, itemId); + return false; + } + + uint itemCount = !string.IsNullOrEmpty(itemCountStr) ? uint.Parse(itemCountStr) : 1; + if (itemCount < 1 || (item_proto.GetMaxCount() > 0 && itemCount > item_proto.GetMaxCount())) + { + handler.SendSysMessage(CypherStrings.CommandInvalidItemCount, itemCount, itemId); + return false; + } + + while (itemCount > item_proto.GetMaxStackSize()) + { + items.Add(new KeyValuePair(itemId, item_proto.GetMaxStackSize())); + itemCount -= item_proto.GetMaxStackSize(); + } + + items.Add(new KeyValuePair(itemId, itemCount)); + + if (items.Count > SharedConst.MaxMailItems) + { + handler.SendSysMessage(CypherStrings.CommandMailItemsLimit, SharedConst.MaxMailItems); + return false; + } + } + + // from console show not existed sender + MailSender sender = new MailSender(MailMessageType.Normal, handler.GetSession() ? handler.GetSession().GetPlayer().GetGUID().GetCounter() : 0, MailStationery.Gm); + + // fill mail + MailDraft draft = new MailDraft(subject, text); + + SQLTransaction trans = new SQLTransaction(); + + foreach (var pair in items) + { + Item item = Item.CreateItem(pair.Key, pair.Value, handler.GetSession() ? handler.GetSession().GetPlayer() : null); + if (item) + { + item.SaveToDB(trans); // save for prevent lost at next mail load, if send fail then item will deleted + draft.AddItem(item); + } + } + + draft.SendMailTo(trans, new MailReceiver(receiver, receiverGuid.GetCounter()), sender); + DB.Characters.CommitTransaction(trans); + + string nameLink = handler.playerLink(receiverName); + handler.SendSysMessage(CypherStrings.MailSent, nameLink); + return true; + } + + [Command("money", RBACPermissions.CommandSendMoney, true)] + static bool HandleSendMoneyCommand(StringArguments args, CommandHandler handler) + { + /// format: name "subject text" "mail text" money + + Player receiver; + ObjectGuid receiverGuid; + string receiverName; + if (!handler.extractPlayerTarget(args, out receiver, out receiverGuid, out receiverName)) + return false; + + string tail1 = args.NextString(""); + if (string.IsNullOrEmpty(tail1)) + return false; + + string subject = handler.extractQuotedArg(tail1); + if (string.IsNullOrEmpty(subject)) + return false; + + string tail2 = args.NextString(""); + if (string.IsNullOrEmpty(tail2)) + return false; + + string text = handler.extractQuotedArg(tail2); + if (string.IsNullOrEmpty(text)) + return false; + + string moneyStr = args.NextString(""); + int money = !string.IsNullOrEmpty(moneyStr) ? int.Parse(moneyStr) : 0; + if (money <= 0) + return false; + + // from console show not existed sender + MailSender sender = new MailSender(MailMessageType.Normal, handler.GetSession() ? handler.GetSession().GetPlayer().GetGUID().GetCounter() : 0, MailStationery.Gm); + + SQLTransaction trans = new SQLTransaction(); + + new MailDraft(subject, text) + .AddMoney((uint)money) + .SendMailTo(trans, new MailReceiver(receiver, receiverGuid.GetCounter()), sender); + + DB.Characters.CommitTransaction(trans); + + string nameLink = handler.playerLink(receiverName); + handler.SendSysMessage(CypherStrings.MailSent, nameLink); + return true; + } + + [Command("message", RBACPermissions.CommandSendMessage, true)] + static bool HandleSendMessageCommand(StringArguments args, CommandHandler handler) + { + /// - Find the player + Player player; + if (!handler.extractPlayerTarget(args, out player)) + return false; + + string msgStr = args.NextString(""); + if (string.IsNullOrEmpty(msgStr)) + return false; + + // Check that he is not logging out. + if (player.GetSession().isLogingOut()) + { + handler.SendSysMessage(CypherStrings.PlayerNotFound); + return false; + } + + /// - Send the message + player.GetSession().SendNotification("{0}", msgStr); + player.GetSession().SendNotification("|cffff0000[Message from administrator]:|r"); + + // Confirmation message + string nameLink = handler.GetNameLink(player); + handler.SendSysMessage(CypherStrings.Sendmessage, nameLink, msgStr); + + return true; + } + } +} diff --git a/Game/Chat/Commands/ServerCommands.cs b/Game/Chat/Commands/ServerCommands.cs new file mode 100644 index 000000000..264281793 --- /dev/null +++ b/Game/Chat/Commands/ServerCommands.cs @@ -0,0 +1,376 @@ +/* + * Copyright (C) 2012-2017 CypherCore + * + * 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, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +using Framework.Configuration; +using Framework.Constants; +using Framework.IO; +using System; + +namespace Game.Chat +{ + [CommandGroup("server", RBACPermissions.CommandServer, true)] + class ServerCommands + { + [Command("corpses", RBACPermissions.CommandServerCorpses, true)] + static bool Corpses(StringArguments args, CommandHandler handler) + { + Global.WorldMgr.RemoveOldCorpses(); + return true; + } + + [Command("exit", RBACPermissions.CommandServerExit, true)] + static bool Exit(StringArguments args, CommandHandler handler) + { + handler.SendSysMessage(CypherStrings.CommandExit); + Global.WorldMgr.StopNow(ShutdownExitCode.Shutdown); + return true; + } + + [Command("info", RBACPermissions.CommandServerInfo, true)] + static bool Info(StringArguments args, CommandHandler handler) + { + uint playersNum = Global.WorldMgr.GetPlayerCount(); + uint maxPlayersNum = Global.WorldMgr.GetMaxPlayerCount(); + int activeClientsNum = Global.WorldMgr.GetActiveSessionCount(); + int queuedClientsNum = Global.WorldMgr.GetQueuedSessionCount(); + uint maxActiveClientsNum = Global.WorldMgr.GetMaxActiveSessionCount(); + uint maxQueuedClientsNum = Global.WorldMgr.GetMaxQueuedSessionCount(); + string uptime = Time.secsToTimeString(Global.WorldMgr.GetUptime()); + uint updateTime = Global.WorldMgr.GetUpdateTime(); + + handler.SendSysMessage(CypherStrings.ConnectedPlayers, playersNum, maxPlayersNum); + handler.SendSysMessage(CypherStrings.ConnectedUsers, activeClientsNum, maxActiveClientsNum, queuedClientsNum, maxQueuedClientsNum); + handler.SendSysMessage(CypherStrings.Uptime, uptime); + handler.SendSysMessage(CypherStrings.UpdateDiff, updateTime); + // Can't use Global.WorldMgr.ShutdownMsg here in case of console command + if (Global.WorldMgr.IsShuttingDown()) + handler.SendSysMessage(CypherStrings.ShutdownTimeleft, Time.secsToTimeString(Global.WorldMgr.GetShutDownTimeLeft())); + + return true; + } + + [Command("motd", RBACPermissions.CommandServerMotd, true)] + static bool Motd(StringArguments args, CommandHandler handler) + { + handler.SendSysMessage(CypherStrings.MotdCurrent, Global.WorldMgr.GetMotd()); + return true; + } + + [Command("plimit", RBACPermissions.CommandServerPlimit, true)] + static bool PLimit(StringArguments args, CommandHandler handler) + { + if (!args.Empty()) + { + string paramStr = args.NextString(); + if (string.IsNullOrEmpty(paramStr)) + return false; + + int limit = paramStr.Length; + + switch (paramStr.ToLower()) + { + case "player": + Global.WorldMgr.SetPlayerSecurityLimit(AccountTypes.Player); + break; + case "moderator": + Global.WorldMgr.SetPlayerSecurityLimit(AccountTypes.Moderator); + break; + case "gamemaster": + Global.WorldMgr.SetPlayerSecurityLimit(AccountTypes.GameMaster); + break; + case "administrator": + Global.WorldMgr.SetPlayerSecurityLimit(AccountTypes.Administrator); + break; + case "reset": + Global.WorldMgr.SetPlayerAmountLimit(ConfigMgr.GetDefaultValue("PlayerLimit", 100)); + Global.WorldMgr.LoadDBAllowedSecurityLevel(); + break; + default: + int value = int.Parse(paramStr); + if (value < 0) + Global.WorldMgr.SetPlayerSecurityLimit((AccountTypes)(-value)); + else + Global.WorldMgr.SetPlayerAmountLimit((uint)value); + break; + } + } + + uint playerAmountLimit = Global.WorldMgr.GetPlayerAmountLimit(); + AccountTypes allowedAccountType = Global.WorldMgr.GetPlayerSecurityLimit(); + string secName = ""; + switch (allowedAccountType) + { + case AccountTypes.Player: + secName = "Player"; + break; + case AccountTypes.Moderator: + secName = "Moderator"; + break; + case AccountTypes.GameMaster: + secName = "Gamemaster"; + break; + case AccountTypes.Administrator: + secName = "Administrator"; + break; + default: + secName = ""; + break; + } + handler.SendSysMessage("Player limits: amount {0}, min. security level {1}.", playerAmountLimit, secName); + + return true; + } + + static bool IsOnlyUser(WorldSession mySession) + { + // check if there is any session connected from a different address + string myAddr = mySession ? mySession.GetRemoteAddress() : ""; + var sessions = Global.WorldMgr.GetAllSessions(); + foreach (var session in sessions) + if (session && myAddr != session.GetRemoteAddress()) + return false; + return true; + } + + static bool ParseExitCode(string exitCodeStr, out int exitCode) + { + exitCode = int.Parse(exitCodeStr); + + // Handle atoi() errors + if (exitCode == 0 && (exitCodeStr[0] != '0' || (exitCodeStr.Length > 1 && exitCodeStr[1] != '\0'))) + return false; + + // Exit code should be in range of 0-125, 126-255 is used + // in many shells for their own return codes and code > 255 + // is not supported in many others + if (exitCode < 0 || exitCode > 125) + return false; + + return true; + } + + static bool ShutdownServer(StringArguments args,CommandHandler handler, ShutdownMask shutdownMask, ShutdownExitCode defaultExitCode) + { + if (args.Empty()) + return false; + + string delayStr = args.NextString(); + if (delayStr.IsEmpty()) + return false; + + int delay; + if (int.TryParse(delayStr, out delay)) + { + // Prevent interpret wrong arg value as 0 secs shutdown time + if ((delay == 0 && (delayStr[0] != '0' || delayStr.Length > 1 && delayStr[1] != '\0')) || delay < 0) + return false; + } + else + { + delay = (int)Time.TimeStringToSecs(delayStr); + + if (delay == 0) + return false; + } + + string reason = ""; + string exitCodeStr = ""; + string nextToken; + while (!(nextToken = args.NextString()).IsEmpty()) + { + if (nextToken.IsNumber()) + exitCodeStr = nextToken; + else + { + reason = nextToken; + reason += args.NextString("\0"); + break; + } + } + + int exitCode = (int)defaultExitCode; + if (!exitCodeStr.IsEmpty()) + if (!ParseExitCode(exitCodeStr, out exitCode)) + return false; + + // Override parameter "delay" with the configuration value if there are still players connected and "force" parameter was not specified + if (delay < WorldConfig.GetIntValue(WorldCfg.ForceShutdownThreshold) && !shutdownMask.HasAnyFlag(ShutdownMask.Force) && !IsOnlyUser(handler.GetSession())) + { + delay = WorldConfig.GetIntValue(WorldCfg.ForceShutdownThreshold); + handler.SendSysMessage(CypherStrings.ShutdownDelayed, delay); + } + + Global.WorldMgr.ShutdownServ((uint)delay, shutdownMask, (ShutdownExitCode)exitCode, reason); + + return true; + } + + [CommandGroup("idleRestart", RBACPermissions.CommandServerIdlerestart, true)] + class IdleRestartCommands + { + [Command("", RBACPermissions.CommandServerIdlerestart, true)] + static bool HandleServerIdleRestartCommand(StringArguments args, CommandHandler handler) + { + return ShutdownServer(args, handler, ShutdownMask.Restart | ShutdownMask.Idle, ShutdownExitCode.Restart); + } + + [Command("cancel", RBACPermissions.CommandServerIdlerestartCancel, true)] + static bool Cancel(StringArguments args, CommandHandler handler) + { + Global.WorldMgr.ShutdownCancel(); + return true; + } + } + + [CommandGroup("idleshutdown", RBACPermissions.CommandServerIdleshutdown, true)] + class IdleshutdownCommands + { + [Command("", RBACPermissions.CommandServerIdleshutdown, true)] + static bool HandleServerIdleShutDownCommand(StringArguments args, CommandHandler handler) + { + return ShutdownServer(args, handler, ShutdownMask.Idle, ShutdownExitCode.Shutdown); + } + + [Command("cancel", RBACPermissions.CommandServerIdleshutdownCancel, true)] + static bool Cancel(StringArguments args, CommandHandler handler) + { + Global.WorldMgr.ShutdownCancel(); + + return true; + } + } + + [CommandGroup("restart", RBACPermissions.CommandServerInfo, true)] + class RestartCommands + { + [Command("", RBACPermissions.CommandServerRestart, true)] + static bool HandleServerRestartCommand(StringArguments args, CommandHandler handler) + { + return ShutdownServer(args, handler, ShutdownMask.Restart, ShutdownExitCode.Restart); + } + + [Command("cancel", RBACPermissions.CommandServerRestartCancel, true)] + static bool HandleServerRestartCancelCommand(StringArguments args, CommandHandler handler) + { + Global.WorldMgr.ShutdownCancel(); + + return true; + } + + [Command("force", RBACPermissions.CommandServerRestartCancel, true)] + static bool HandleServerForceRestartCommand(StringArguments args, CommandHandler handler) + { + return ShutdownServer(args, handler, ShutdownMask.Force | ShutdownMask.Restart, ShutdownExitCode.Restart); + } + } + + [CommandGroup("shutdown", RBACPermissions.CommandServerMotd, true)] + class ShutdownCommands + { + [Command("", RBACPermissions.CommandServerShutdown, true)] + static bool HandleServerShutDownCommand(StringArguments args, CommandHandler handler) + { + return ShutdownServer(args, handler, 0, ShutdownExitCode.Shutdown); + } + + [Command("cancel", RBACPermissions.CommandServerShutdownCancel, true)] + static bool HandleServerShutDownCancelCommand(StringArguments args, CommandHandler handler) + { + uint timer = Global.WorldMgr.ShutdownCancel(); + if (timer != 0) + handler.SendSysMessage(CypherStrings.ShutdownCancelled, timer); + + return true; + } + + [Command("force", RBACPermissions.CommandServerShutdownCancel, true)] + static bool HandleServerForceShutDownCommand(StringArguments args, CommandHandler handler) + { + return ShutdownServer(args, handler, ShutdownMask.Force, ShutdownExitCode.Shutdown); + } + } + + [CommandGroup("set", RBACPermissions.CommandServerSet, true)] + class SetCommands + { + [Command("difftime", RBACPermissions.CommandServerSetDifftime, true)] + static bool HandleServerSetDiffTimeCommand(StringArguments args, CommandHandler handler) + { + if (args.Empty()) + return false; + + string newTimeStr = args.NextString(); + if (newTimeStr.IsEmpty()) + return false; + + int newTime = int.Parse(newTimeStr); + if (newTime < 0) + return false; + + //Global.WorldMgr.SetRecordDiffInterval(newTime); + //printf("Record diff every %i ms\n", newTime); + + return true; + } + + [Command("loglevel", RBACPermissions.CommandServerSetLoglevel, true)] + static bool HandleServerSetLogLevelCommand(StringArguments args, CommandHandler handler) + { + if (args.Empty()) + return false; + + string type = args.NextString(); + string name = args.NextString(); + string level = args.NextString(); + + if (string.IsNullOrEmpty(type) || string.IsNullOrEmpty(name) || string.IsNullOrEmpty(level) || name.IsEmpty() || level.IsEmpty() || (type[0] != 'a' && type[0] != 'l')) + return false; + + return Log.SetLogLevel(name, level, type[0] == 'l'); + } + + [Command("motd", RBACPermissions.CommandServerSetMotd, true)] + static bool SetMotd(StringArguments args, CommandHandler handler) + { + Global.WorldMgr.SetMotd(args.NextString("")); + handler.SendSysMessage(CypherStrings.MotdNew, args); + return true; + } + + [Command("closed", RBACPermissions.CommandServerSetClosed, true)] + static bool SetClosed(StringArguments args, CommandHandler handler) + { + string arg1 = args.NextString(); + if (arg1.Equals("on")) + { + handler.SendSysMessage(CypherStrings.WorldClosed); + Global.WorldMgr.SetClosed(true); + return true; + } + else if (arg1.Equals("off")) + { + handler.SendSysMessage(CypherStrings.WorldOpened); + Global.WorldMgr.SetClosed(false); + return true; + } + + handler.SendSysMessage(CypherStrings.UseBol); + return false; + } + } + } +} diff --git a/Game/Chat/Commands/SpellCommands.cs b/Game/Chat/Commands/SpellCommands.cs new file mode 100644 index 000000000..6e6a2cafb --- /dev/null +++ b/Game/Chat/Commands/SpellCommands.cs @@ -0,0 +1,194 @@ +/* + * Copyright (C) 2012-2017 CypherCore + * + * 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, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +using Framework.Constants; +using Framework.IO; +using Game.DataStorage; +using Game.Entities; +using Game.Spells; +using System.Collections.Generic; + +namespace Game.Chat +{ + class SpellCommands + { + [CommandNonGroup("cooldown", RBACPermissions.CommandCooldown)] + static bool HandleCooldownCommand(StringArguments args, CommandHandler handler) + { + Unit target = handler.getSelectedUnit(); + if (!target) + { + handler.SendSysMessage(CypherStrings.PlayerNotFound); + return false; + } + + Player owner = target.GetCharmerOrOwnerPlayerOrPlayerItself(); + if (!owner) + { + owner = handler.GetSession().GetPlayer(); + target = owner; + } + + string nameLink = handler.GetNameLink(owner); + if (args.Empty()) + { + target.GetSpellHistory().ResetAllCooldowns(); + target.GetSpellHistory().ResetAllCharges(); + handler.SendSysMessage(CypherStrings.RemoveallCooldown, nameLink); + } + else + { + // number or [name] Shift-click form |color|Hspell:spell_id|h[name]|h|r or Htalent form + uint spellIid = handler.extractSpellIdFromLink(args); + if (spellIid == 0) + return false; + + SpellInfo spellInfo = Global.SpellMgr.GetSpellInfo(spellIid); + if (spellInfo == null) + { + handler.SendSysMessage(CypherStrings.UnknownSpell, owner == handler.GetSession().GetPlayer() ? handler.GetCypherString(CypherStrings.You) : nameLink); + return false; + } + + target.GetSpellHistory().ResetCooldown(spellIid, true); + target.GetSpellHistory().ResetCharges(spellInfo.ChargeCategoryId); + handler.SendSysMessage(CypherStrings.RemoveallCooldown, spellIid, owner == handler.GetSession().GetPlayer() ? handler.GetCypherString(CypherStrings.You) : nameLink); + } + return true; + } + + [CommandNonGroup("aura", RBACPermissions.CommandAura)] + static bool Auracommand(StringArguments args, CommandHandler handler) + { + Unit target = handler.getSelectedUnit(); + if (!target) + { + handler.SendSysMessage(CypherStrings.SelectCharOrCreature); + + return false; + } + + // number or [name] Shift-click form |color|Hspell:spell_id|h[name]|h|r or Htalent form + uint spellId = handler.extractSpellIdFromLink(args); + + SpellInfo spellInfo = Global.SpellMgr.GetSpellInfo(spellId); + if (spellInfo != null) + { + ObjectGuid castId = ObjectGuid.Create(HighGuid.Cast, SpellCastSource.Normal, target.GetMapId(), spellId, target.GetMap().GenerateLowGuid(HighGuid.Cast)); + Aura.TryRefreshStackOrCreate(spellInfo, castId, SpellConst.MaxEffectMask, target, target); + } + + return true; + } + + [CommandNonGroup("unaura", RBACPermissions.CommandUnaura)] + static bool UnAura(StringArguments args, CommandHandler handler) + { + Unit target = handler.getSelectedUnit(); + if (!target) + { + handler.SendSysMessage(CypherStrings.SelectCharOrCreature); + + return false; + } + + string argstr = args.NextString(); + if (argstr == "all") + { + target.RemoveAllAuras(); + return true; + } + + // number or [name] Shift-click form |color|Hspell:spell_id|h[name]|h|r or Htalent form + uint spellId = handler.extractSpellIdFromLink(args); + if (spellId == 0) + return false; + + target.RemoveAurasDueToSpell(spellId); + + return true; + } + + [CommandNonGroup("maxskill", RBACPermissions.CommandMaxskill)] + static bool HandleMaxSkillCommand(StringArguments args, CommandHandler handler) + { + Player player = handler.getSelectedPlayerOrSelf(); + if (!player) + { + handler.SendSysMessage(CypherStrings.NoCharSelected); + return false; + } + + // each skills that have max skill value dependent from level seted to current level max skill value + player.UpdateSkillsToMaxSkillsForLevel(); + return true; + } + + [CommandNonGroup("setskill", RBACPermissions.CommandSetskill)] + static bool SetSkill(StringArguments args, CommandHandler handler) + { + // number or [name] Shift-click form |color|Hskill:skill_id|h[name]|h|r + string skillStr = handler.extractKeyFromLink(args, "Hskill"); + if (string.IsNullOrEmpty(skillStr)) + return false; + + string levelStr = args.NextString(); + if (string.IsNullOrEmpty(levelStr)) + return false; + + string maxPureSkill = args.NextString(); + + int skill = int.Parse(skillStr); + if (skill <= 0) + { + handler.SendSysMessage(CypherStrings.InvalidSkillId, skill); + return false; + } + + int level = int.Parse(levelStr); + Player target = handler.getSelectedPlayerOrSelf(); + if (!target) + { + handler.SendSysMessage(CypherStrings.NoCharSelected); + return false; + } + + SkillLineRecord skillLine = CliDB.SkillLineStorage.LookupByKey(skill); + if (skillLine == null) + { + handler.SendSysMessage(CypherStrings.InvalidSkillId, skill); + return false; + } + + bool targetHasSkill = target.GetSkillValue((SkillType)skill) != 0; + + // If our target does not yet have the skill they are trying to add to them, the chosen level also becomes + // the max level of the new profession. + ushort max = !string.IsNullOrEmpty(maxPureSkill) ? ushort.Parse(maxPureSkill) : targetHasSkill ? target.GetPureMaxSkillValue((SkillType)skill) : (ushort)level; + + if (level <= 0 || level > max || max <= 0) + return false; + + // If the player has the skill, we get the current skill step. If they don't have the skill, we + // add the skill to the player's book with step 1 (which is the first rank, in most cases something + // like 'Apprentice '. + target.SetSkill((SkillType)skill, (uint)(targetHasSkill ? target.GetSkillStep((SkillType)skill) : 1), (uint)level, max); + handler.SendSysMessage(CypherStrings.SetSkill, skill, skillLine.DisplayName[handler.GetSessionDbcLocale()], handler.GetNameLink(target), level, max); + return true; + } + } +} diff --git a/Game/Chat/Commands/TeleCommands.cs b/Game/Chat/Commands/TeleCommands.cs new file mode 100644 index 000000000..0db93dd90 --- /dev/null +++ b/Game/Chat/Commands/TeleCommands.cs @@ -0,0 +1,170 @@ +/* + * Copyright (C) 2012-2017 CypherCore + * + * 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, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +using Framework.Constants; +using Framework.IO; +using Game.DataStorage; +using Game.Entities; +using Game.Groups; +using System.Collections.Generic; + +namespace Game.Chat +{ + [CommandGroup("tele", RBACPermissions.CommandTele)] + class TeleCommands + { + [Command("", RBACPermissions.CommandTele)] + static bool HandleTeleCommand(StringArguments args, CommandHandler handler) + { + if (args.Empty()) + return false; + + Player me = handler.GetPlayer(); + + GameTele tele = handler.extractGameTeleFromLink(args); + + if (tele == null) + { + handler.SendSysMessage(CypherStrings.CommandTeleNotfound); + return false; + } + + if (me.IsInCombat()) + { + handler.SendSysMessage(CypherStrings.YouInCombat); + return false; + } + + var map = CliDB.MapStorage.LookupByKey(tele.mapId); + if (map == null || (map.IsBattlegroundOrArena() && (me.GetMapId() != tele.mapId || !me.IsGameMaster()))) + { + handler.SendSysMessage(CypherStrings.CannotTeleToBg); + return false; + } + + // stop flight if need + if (me.IsInFlight()) + { + me.GetMotionMaster().MovementExpired(); + me.CleanupAfterTaxiFlight(); + } + // save only in non-flight case + else + me.SaveRecallPosition(); + + me.TeleportTo(tele.mapId, tele.posX, tele.posY, tele.posZ, tele.orientation); + return true; + } + + [Command("add", RBACPermissions.CommandTeleAdd)] + static bool HandleTeleAddCommand(StringArguments args, CommandHandler handler) + { + return false; + } + + [Command("del", RBACPermissions.CommandTeleDel, true)] + static bool HandleTeleDelCommand(StringArguments args, CommandHandler handler) + { + return false; + } + + [Command("group", RBACPermissions.CommandTeleGroup)] + static bool HandleTeleGroupCommand(StringArguments args, CommandHandler handler) + { + if (args.Empty()) + return false; + + Player target = handler.getSelectedPlayer(); + if (!target) + { + handler.SendSysMessage(CypherStrings.NoCharSelected); + return false; + } + + // check online security + if (handler.HasLowerSecurity(target, ObjectGuid.Empty)) + return false; + + // id, or string, or [name] Shift-click form |color|Htele:id|h[name]|h|r + GameTele tele = handler.extractGameTeleFromLink(args); + if (tele == null) + { + handler.SendSysMessage(CypherStrings.CommandTeleNotfound); + return false; + } + + MapRecord map = CliDB.MapStorage.LookupByKey(tele.mapId); + if (map == null || map.IsBattlegroundOrArena()) + { + handler.SendSysMessage(CypherStrings.CannotTeleToBg); + return false; + } + + string nameLink = handler.GetNameLink(target); + + Group grp = target.GetGroup(); + if (!grp) + { + handler.SendSysMessage(CypherStrings.NotInGroup, nameLink); + return false; + } + + for (GroupReference refe = grp.GetFirstMember(); refe != null; refe = refe.next()) + { + Player player = refe.GetSource(); + if (!player || !player.GetSession()) + continue; + + // check online security + if (handler.HasLowerSecurity(player, ObjectGuid.Empty)) + return false; + + string plNameLink = handler.GetNameLink(player); + + if (player.IsBeingTeleported()) + { + handler.SendSysMessage(CypherStrings.IsTeleported, plNameLink); + continue; + } + + handler.SendSysMessage(CypherStrings.TeleportingTo, plNameLink, "", tele.name); + if (handler.needReportToTarget(player)) + player.SendSysMessage(CypherStrings.TeleportedToBy, nameLink); + + // stop flight if need + if (player.IsInFlight()) + { + player.GetMotionMaster().MovementExpired(); + player.CleanupAfterTaxiFlight(); + } + // save only in non-flight case + else + player.SaveRecallPosition(); + + player.TeleportTo(tele.mapId, tele.posX, tele.posY, tele.posZ, tele.orientation); + } + + return true; + } + + [Command("name", RBACPermissions.CommandTeleName, true)] + static bool HandleTeleNameCommand(StringArguments args, CommandHandler handler) + { + return false; + } + } +} diff --git a/Game/Chat/Commands/TicketCommands.cs b/Game/Chat/Commands/TicketCommands.cs new file mode 100644 index 000000000..1e42626dd --- /dev/null +++ b/Game/Chat/Commands/TicketCommands.cs @@ -0,0 +1,490 @@ +/* + * Copyright (C) 2012-2017 CypherCore + * + * 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, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +using Framework.Constants; +using Framework.IO; +using Game.Entities; +using Game.SupportSystem; + +namespace Game.Chat.Commands +{ + [CommandGroup("ticket", RBACPermissions.CommandTicket, true)] + class TicketCommands + { + [Command("togglesystem", RBACPermissions.CommandTicketTogglesystem, true)] + static bool HandleTicketToggleSystem(StringArguments args, CommandHandler handler) + { + if (!WorldConfig.GetBoolValue(WorldCfg.SupportTicketsEnabled)) + { + handler.SendSysMessage(CypherStrings.DisallowTicketsConfig); + return true; + } + + bool status = !Global.SupportMgr.GetSupportSystemStatus(); + Global.SupportMgr.SetSupportSystemStatus(status); + handler.SendSysMessage(status ? CypherStrings.AllowTickets : CypherStrings.DisallowTickets); + return true; + } + + [CommandGroup("bug", RBACPermissions.CommandTicketBug, true)] + class TicketBugCommands + { + [Command("assign", RBACPermissions.CommandTicketBugAssign, true)] + static bool HandleTicketBugAssignCommand(StringArguments args, CommandHandler handler) + { + return HandleTicketAssignToCommand(args, handler); + } + + [Command("close", RBACPermissions.CommandTicketBugClose, true)] + static bool HandleTicketBugCloseCommand(StringArguments args, CommandHandler handler) + { + return HandleCloseByIdCommand(args, handler); + } + + [Command("closedlist", RBACPermissions.CommandTicketBugClosedlist, true)] + static bool HandleTicketBugClosedListCommand(StringArguments args, CommandHandler handler) + { + return HandleClosedListCommand(args, handler); + } + + [Command("comment", RBACPermissions.CommandTicketBugComment, true)] + static bool HandleTicketBugCommentCommand(StringArguments args, CommandHandler handler) + { + return HandleCommentCommand(args, handler); + } + + [Command("delete", RBACPermissions.CommandTicketBugDelete, true)] + static bool HandleTicketBugDeleteCommand(StringArguments args, CommandHandler handler) + { + return HandleDeleteByIdCommand(args, handler); + } + + [Command("list", RBACPermissions.CommandTicketBugList, true)] + static bool HandleTicketBugListCommand(StringArguments args, CommandHandler handler) + { + return HandleListCommand(args, handler); + } + + [Command("unassign", RBACPermissions.CommandTicketBugUnassign, true)] + static bool HandleTicketBugUnAssignCommand(StringArguments args, CommandHandler handler) + { + return HandleUnAssignCommand(args, handler); + } + + [Command("view", RBACPermissions.CommandTicketBugView, true)] + static bool HandleTicketBugViewCommand(StringArguments args, CommandHandler handler) + { + return HandleGetByIdCommand(args, handler); + } + } + + [CommandGroup("complaint", RBACPermissions.CommandTicketComplaint, true)] + class TicketComplaintCommands + { + [Command("assign", RBACPermissions.CommandTicketComplaintAssign, true)] + static bool HandleTicketComplaintAssignCommand(StringArguments args, CommandHandler handler) + { + return HandleTicketAssignToCommand(args, handler); + } + + [Command("close", RBACPermissions.CommandTicketComplaintClose, true)] + static bool HandleTicketComplaintCloseCommand(StringArguments args, CommandHandler handler) + { + return HandleCloseByIdCommand(args, handler); + } + + [Command("closedlist", RBACPermissions.CommandTicketComplaintClosedlist, true)] + static bool HandleTicketComplaintClosedListCommand(StringArguments args, CommandHandler handler) + { + return HandleClosedListCommand(args, handler); + } + + [Command("comment", RBACPermissions.CommandTicketComplaintComment, true)] + static bool HandleTicketComplaintCommentCommand(StringArguments args, CommandHandler handler) + { + return HandleCommentCommand(args, handler); + } + + [Command("delete", RBACPermissions.CommandTicketComplaintDelete, true)] + static bool HandleTicketComplaintDeleteCommand(StringArguments args, CommandHandler handler) + { + return HandleDeleteByIdCommand(args, handler); + } + + [Command("list", RBACPermissions.CommandTicketComplaintList, true)] + static bool HandleTicketComplaintListCommand(StringArguments args, CommandHandler handler) + { + return HandleListCommand(args, handler); + } + + [Command("unassign", RBACPermissions.CommandTicketComplaintUnassign, true)] + static bool HandleTicketComplaintUnAssignCommand(StringArguments args, CommandHandler handler) + { + return HandleUnAssignCommand(args, handler); + } + + [Command("view", RBACPermissions.CommandTicketComplaintView, true)] + static bool HandleTicketComplaintViewCommand(StringArguments args, CommandHandler handler) + { + return HandleGetByIdCommand(args, handler); + } + } + + [CommandGroup("suggestion", RBACPermissions.CommandTicketSuggestion, true)] + class TicketSuggestionCommands + { + [Command("assign", RBACPermissions.CommandTicketSuggestionAssign, true)] + static bool HandleTicketSuggestionAssignCommand(StringArguments args, CommandHandler handler) + { + return HandleTicketAssignToCommand(args, handler); + } + + [Command("close", RBACPermissions.CommandTicketSuggestionClose, true)] + static bool HandleTicketSuggestionCloseCommand(StringArguments args, CommandHandler handler) + { + return HandleCloseByIdCommand(args, handler); + } + + [Command("closedlist", RBACPermissions.CommandTicketSuggestionClosedlist, true)] + static bool HandleTicketSuggestionClosedListCommand(StringArguments args, CommandHandler handler) + { + return HandleClosedListCommand(args, handler); + } + + [Command("comment", RBACPermissions.CommandTicketSuggestionComment, true)] + static bool HandleTicketSuggestionCommentCommand(StringArguments args, CommandHandler handler) + { + return HandleCommentCommand(args, handler); + } + + [Command("delete", RBACPermissions.CommandTicketSuggestionDelete, true)] + static bool HandleTicketSuggestionDeleteCommand(StringArguments args, CommandHandler handler) + { + return HandleDeleteByIdCommand(args, handler); + } + + [Command("list", RBACPermissions.CommandTicketSuggestionList, true)] + static bool HandleTicketSuggestionListCommand(StringArguments args, CommandHandler handler) + { + return HandleListCommand(args, handler); + } + + [Command("unassign", RBACPermissions.CommandTicketSuggestionUnassign, true)] + static bool HandleTicketSuggestionUnAssignCommand(StringArguments args, CommandHandler handler) + { + return HandleUnAssignCommand(args, handler); + } + + [Command("view", RBACPermissions.CommandTicketSuggestionView, true)] + static bool HandleTicketSuggestionViewCommand(StringArguments args, CommandHandler handler) + { + return HandleGetByIdCommand(args, handler); + } + } + + [CommandGroup("reset", RBACPermissions.CommandTicketReset, true)] + class TicketResetCommands + { + [Command("all", RBACPermissions.CommandTicketResetAll, true)] + static bool HandleTicketResetAllCommand(StringArguments args, CommandHandler handler) + { + if (Global.SupportMgr.GetOpenTicketCount() != 0 || Global.SupportMgr.GetOpenTicketCount() != 0 || Global.SupportMgr.GetOpenTicketCount() != 0) + { + handler.SendSysMessage(CypherStrings.CommandTicketpending); + return true; + } + else + { + Global.SupportMgr.ResetTickets(); + Global.SupportMgr.ResetTickets(); + Global.SupportMgr.ResetTickets(); + handler.SendSysMessage(CypherStrings.CommandTicketreset); + } + return true; + } + + [Command("bug", RBACPermissions.CommandTicketResetBug, true)] + static bool HandleTicketResetBugCommand(StringArguments args, CommandHandler handler) + { + return HandleResetCommand(args, handler); + } + + [Command("complaint", RBACPermissions.CommandTicketResetComplaint, true)] + static bool HandleTicketResetComplaintCommand(StringArguments args, CommandHandler handler) + { + return HandleResetCommand(args, handler); + } + + [Command("suggestion", RBACPermissions.CommandTicketResetSuggestion, true)] + static bool HandleTicketResetSuggestionCommand(StringArguments args, CommandHandler handler) + { + return HandleResetCommand(args, handler); + } + } + + static bool HandleTicketAssignToCommand(StringArguments args, CommandHandler handler) where T : Ticket + { + if (args.Empty()) + return false; + + uint ticketId = args.NextUInt32(); + + string target = args.NextString(); + if (string.IsNullOrEmpty(target)) + return false; + + if (!ObjectManager.NormalizePlayerName(ref target)) + return false; + + T ticket = Global.SupportMgr.GetTicket(ticketId); + if (ticket == null || ticket.IsClosed()) + { + handler.SendSysMessage(CypherStrings.CommandTicketnotexist); + return true; + } + + ObjectGuid targetGuid = ObjectManager.GetPlayerGUIDByName(target); + uint accountId = ObjectManager.GetPlayerAccountIdByGUID(targetGuid); + // Target must exist and have administrative rights + if (!Global.AccountMgr.HasPermission(accountId, RBACPermissions.CommandsBeAssignedTicket, Global.WorldMgr.GetRealm().Id.Realm)) + { + handler.SendSysMessage(CypherStrings.CommandTicketassignerrorA); + return true; + } + + // If already assigned, leave + if (ticket.IsAssignedTo(targetGuid)) + { + handler.SendSysMessage(CypherStrings.CommandTicketassignerrorB, ticket.GetId()); + return true; + } + + // If assigned to different player other than current, leave + //! Console can override though + Player player = handler.GetSession() != null ? handler.GetSession().GetPlayer() : null; + if (player && ticket.IsAssignedNotTo(player.GetGUID())) + { + handler.SendSysMessage(CypherStrings.CommandTicketalreadyassigned, ticket.GetId(), target); + return true; + } + + // Assign ticket + ticket.SetAssignedTo(targetGuid, Global.AccountMgr.IsAdminAccount(Global.AccountMgr.GetSecurity(accountId, (int)Global.WorldMgr.GetRealm().Id.Realm))); + ticket.SaveToDB(); + + string msg = ticket.FormatViewMessageString(handler, null, target, null, null); + handler.SendGlobalGMSysMessage(msg); + return true; + } + + static bool HandleCloseByIdCommand(StringArguments args, CommandHandler handler) where T : Ticket + { + if (args.Empty()) + return false; + + uint ticketId = args.NextUInt32(); + T ticket = Global.SupportMgr.GetTicket(ticketId); + if (ticket == null || ticket.IsClosed()) + { + handler.SendSysMessage(CypherStrings.CommandTicketnotexist); + return true; + } + + // Ticket should be assigned to the player who tries to close it. + // Console can override though + Player player = handler.GetSession() != null ? handler.GetSession().GetPlayer() : null; + if (player && ticket.IsAssignedNotTo(player.GetGUID())) + { + handler.SendSysMessage(CypherStrings.CommandTicketcannotclose, ticket.GetId()); + return true; + } + + ObjectGuid closedByGuid = ObjectGuid.Empty; + if (player) + closedByGuid = player.GetGUID(); + else + closedByGuid.SetRawValue(0, ulong.MaxValue); + + Global.SupportMgr.CloseTicket(ticket.GetId(), closedByGuid); + + string msg = ticket.FormatViewMessageString(handler, player ? player.GetName() : "Console", null, null, null); + handler.SendGlobalGMSysMessage(msg); + + return true; + } + + static bool HandleClosedListCommand(StringArguments args, CommandHandler handler) where T : Ticket + { + Global.SupportMgr.ShowClosedList(handler); + return true; + } + + static bool HandleCommentCommand(StringArguments args, CommandHandler handler) where T : Ticket + { + if (args.Empty()) + return false; + + uint ticketId = args.NextUInt32(); + + string comment = args.NextString("\n"); + if (string.IsNullOrEmpty(comment)) + return false; + + T ticket = Global.SupportMgr.GetTicket(ticketId); + if (ticket == null || ticket.IsClosed()) + { + handler.SendSysMessage(CypherStrings.CommandTicketnotexist); + return true; + } + + // Cannot comment ticket assigned to someone else + //! Console excluded + Player player = handler.GetSession() != null ? handler.GetSession().GetPlayer() : null; + if (player && ticket.IsAssignedNotTo(player.GetGUID())) + { + handler.SendSysMessage(CypherStrings.CommandTicketalreadyassigned, ticket.GetId()); + return true; + } + + ticket.SetComment(comment); + ticket.SaveToDB(); + Global.SupportMgr.UpdateLastChange(); + + string msg = ticket.FormatViewMessageString(handler, null, ticket.GetAssignedToName(), null, null); + msg += string.Format(handler.GetCypherString(CypherStrings.CommandTicketlistaddcomment), player ? player.GetName() : "Console", comment); + handler.SendGlobalGMSysMessage(msg); + + return true; + } + + static bool HandleDeleteByIdCommand(StringArguments args, CommandHandler handler) where T : Ticket + { + if (args.Empty()) + return false; + + uint ticketId = args.NextUInt32(); + T ticket = Global.SupportMgr.GetTicket(ticketId); + if (ticket == null) + { + handler.SendSysMessage(CypherStrings.CommandTicketnotexist); + return true; + } + + if (!ticket.IsClosed()) + { + handler.SendSysMessage(CypherStrings.CommandTicketclosefirst); + return true; + } + + string msg = ticket.FormatViewMessageString(handler, null, null, null, handler.GetSession() != null ? handler.GetSession().GetPlayer().GetName() : "Console"); + handler.SendGlobalGMSysMessage(msg); + + Global.SupportMgr.RemoveTicket(ticket.GetId()); + + return true; + } + + static bool HandleListCommand(StringArguments args, CommandHandler handler) where T : Ticket + { + Global.SupportMgr.ShowList(handler); + return true; + } + + static bool HandleResetCommand(StringArguments args, CommandHandler handler) where T : Ticket + { + if (Global.SupportMgr.GetOpenTicketCount() != 0) + { + handler.SendSysMessage(CypherStrings.CommandTicketpending); + return true; + } + else + { + Global.SupportMgr.ResetTickets(); + handler.SendSysMessage(CypherStrings.CommandTicketreset); + } + + return true; + } + + static bool HandleUnAssignCommand(StringArguments args, CommandHandler handler) where T : Ticket + { + if (args.Empty()) + return false; + + uint ticketId = args.NextUInt32(); + T ticket = Global.SupportMgr.GetTicket(ticketId); + if (ticket == null || ticket.IsClosed()) + { + handler.SendSysMessage(CypherStrings.CommandTicketnotexist); + return true; + } + // Ticket must be assigned + if (!ticket.IsAssigned()) + { + handler.SendSysMessage(CypherStrings.CommandTicketnotassigned, ticket.GetId()); + return true; + } + + // Get security level of player, whom this ticket is assigned to + AccountTypes security = AccountTypes.Player; + Player assignedPlayer = ticket.GetAssignedPlayer(); + if (assignedPlayer && assignedPlayer.IsInWorld) + security = assignedPlayer.GetSession().GetSecurity(); + else + { + ObjectGuid guid = ticket.GetAssignedToGUID(); + uint accountId = ObjectManager.GetPlayerAccountIdByGUID(guid); + security = Global.AccountMgr.GetSecurity(accountId, (int)Global.WorldMgr.GetRealm().Id.Realm); + } + + // Check security + //! If no m_session present it means we're issuing this command from the console + AccountTypes mySecurity = handler.GetSession() != null ? handler.GetSession().GetSecurity() : AccountTypes.Console; + if (security > mySecurity) + { + handler.SendSysMessage(CypherStrings.CommandTicketunassignsecurity); + return true; + } + + string assignedTo = ticket.GetAssignedToName(); // copy assignedto name because we need it after the ticket has been unnassigned + + ticket.SetUnassigned(); + ticket.SaveToDB(); + string msg = ticket.FormatViewMessageString(handler, null, assignedTo, handler.GetSession() != null ? handler.GetSession().GetPlayer().GetName() : "Console", null); + handler.SendGlobalGMSysMessage(msg); + + return true; + } + + static bool HandleGetByIdCommand(StringArguments args, CommandHandler handler) where T : Ticket + { + if (args.Empty()) + return false; + + uint ticketId = args.NextUInt32(); + T ticket = Global.SupportMgr.GetTicket(ticketId); + if (ticket == null || ticket.IsClosed()) + { + handler.SendSysMessage(CypherStrings.CommandTicketnotexist); + return true; + } + + handler.SendSysMessage(ticket.FormatViewMessageString(handler, true)); + return true; + } + } +} diff --git a/Game/Chat/Commands/TitleCommands.cs b/Game/Chat/Commands/TitleCommands.cs new file mode 100644 index 000000000..b0f89de3c --- /dev/null +++ b/Game/Chat/Commands/TitleCommands.cs @@ -0,0 +1,207 @@ +/* + * Copyright (C) 2012-2017 CypherCore + * + * 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, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +using Framework.Constants; +using Framework.IO; +using Game.DataStorage; +using Game.Entities; +using System.Collections.Generic; + +namespace Game.Chat.Commands +{ + [CommandGroup("titles", RBACPermissions.CommandTitles)] + class TitleCommands + { + [Command("current", RBACPermissions.CommandTitlesCurrent)] + static bool HandleTitlesCurrentCommand(StringArguments args, CommandHandler handler) + { + // number or [name] Shift-click form |color|Htitle:title_id|h[name]|h|r + string id_p = handler.extractKeyFromLink(args, "Htitle"); + if (string.IsNullOrEmpty(id_p)) + return false; + + int id = int.Parse(id_p); + if (id <= 0) + { + handler.SendSysMessage(CypherStrings.InvalidTitleId, id); + return false; + } + + Player target = handler.getSelectedPlayer(); + if (!target) + { + handler.SendSysMessage(CypherStrings.NoCharSelected); + return false; + } + + // check online security + if (handler.HasLowerSecurity(target, ObjectGuid.Empty)) + return false; + + CharTitlesRecord titleInfo = CliDB.CharTitlesStorage.LookupByKey(id); + if (titleInfo == null) + { + handler.SendSysMessage(CypherStrings.InvalidTitleId, id); + return false; + } + + string tNameLink = handler.GetNameLink(target); + + target.SetTitle(titleInfo); // to be sure that title now known + target.SetUInt32Value(PlayerFields.ChosenTitle, titleInfo.MaskID); + + handler.SendSysMessage(CypherStrings.TitleCurrentRes, id, (target.GetGender() == Gender.Male ? titleInfo.NameMale : titleInfo.NameFemale)[handler.GetSessionDbcLocale()], tNameLink); + return true; + } + + [Command("add", RBACPermissions.CommandTitlesAdd)] + static bool HandleTitlesAddCommand(StringArguments args, CommandHandler handler) + { + // number or [name] Shift-click form |color|Htitle:title_id|h[name]|h|r + string id_p = handler.extractKeyFromLink(args, "Htitle"); + if (string.IsNullOrEmpty(id_p)) + return false; + + int id = int.Parse(id_p); + if (id <= 0) + { + handler.SendSysMessage(CypherStrings.InvalidTitleId, id); + return false; + } + + Player target = handler.getSelectedPlayer(); + if (!target) + { + handler.SendSysMessage(CypherStrings.NoCharSelected); + return false; + } + + // check online security + if (handler.HasLowerSecurity(target, ObjectGuid.Empty)) + return false; + + CharTitlesRecord titleInfo = CliDB.CharTitlesStorage.LookupByKey(id); + if (titleInfo == null) + { + handler.SendSysMessage(CypherStrings.InvalidTitleId, id); + return false; + } + + string tNameLink = handler.GetNameLink(target); + + string titleNameStr = (target.GetGender() == Gender.Male ? titleInfo.NameMale : titleInfo.NameFemale)[handler.GetSessionDbcLocale()] + target.GetName(); + + target.SetTitle(titleInfo); + handler.SendSysMessage(CypherStrings.TitleAddRes, id, titleNameStr, tNameLink); + + return true; + } + + [Command("remove", RBACPermissions.CommandTitlesRemove)] + static bool HandleTitlesRemoveCommand(StringArguments args, CommandHandler handler) + { + // number or [name] Shift-click form |color|Htitle:title_id|h[name]|h|r + string id_p = handler.extractKeyFromLink(args, "Htitle"); + if (string.IsNullOrEmpty(id_p)) + return false; + + int id = int.Parse(id_p); + if (id <= 0) + { + handler.SendSysMessage(CypherStrings.InvalidTitleId, id); + return false; + } + + Player target = handler.getSelectedPlayer(); + if (!target) + { + handler.SendSysMessage(CypherStrings.NoCharSelected); + return false; + } + + // check online security + if (handler.HasLowerSecurity(target, ObjectGuid.Empty)) + return false; + + CharTitlesRecord titleInfo = CliDB.CharTitlesStorage.LookupByKey(id); + if (titleInfo == null) + { + handler.SendSysMessage(CypherStrings.InvalidTitleId, id); + return false; + } + + target.SetTitle(titleInfo, true); + + string tNameLink = handler.GetNameLink(target); + + string titleNameStr = (target.GetGender() == Gender.Male ? titleInfo.NameMale : titleInfo.NameFemale)[handler.GetSessionDbcLocale()] + target.GetName(); + + handler.SendSysMessage(CypherStrings.TitleRemoveRes, id, titleNameStr, tNameLink); + + if (!target.HasTitle(target.GetUInt32Value(PlayerFields.ChosenTitle))) + { + target.SetUInt32Value(PlayerFields.ChosenTitle, 0); + handler.SendSysMessage(CypherStrings.CurrentTitleReset, tNameLink); + } + + return true; + } + + [CommandGroup("set", RBACPermissions.CommandTitlesSet)] + class TitleSetCommands + { + //Edit Player KnownTitles + [Command("mask", RBACPermissions.CommandTitlesSetMask)] + static bool HandleTitlesSetMaskCommand(StringArguments args, CommandHandler handler) + { + if (args.Empty()) + return false; + + ulong titles = args.NextUInt64(); + + Player target = handler.getSelectedPlayer(); + if (!target) + { + handler.SendSysMessage(CypherStrings.NoCharSelected); + return false; + } + + // check online security + if (handler.HasLowerSecurity(target, ObjectGuid.Empty)) + return false; + + ulong titles2 = titles; + + foreach (CharTitlesRecord tEntry in CliDB.CharTitlesStorage.Values) + titles2 &= ~(1ul << (int)tEntry.MaskID); + + titles &= ~titles2; // remove not existed titles + + target.SetUInt64Value(PlayerFields.KnownTitles, titles); + handler.SendSysMessage(CypherStrings.Done); + + if (!target.HasTitle(target.GetUInt32Value(PlayerFields.ChosenTitle))) + { + target.SetUInt32Value(PlayerFields.ChosenTitle, 0); + handler.SendSysMessage(CypherStrings.CurrentTitleReset, handler.GetNameLink(target)); + } + + return true; + } + } + } +} diff --git a/Game/Chat/Commands/WPCommands.cs b/Game/Chat/Commands/WPCommands.cs new file mode 100644 index 000000000..455f352d6 --- /dev/null +++ b/Game/Chat/Commands/WPCommands.cs @@ -0,0 +1,969 @@ +/* + * Copyright (C) 2012-2017 CypherCore + * + * 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, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +using Framework.Constants; +using Framework.Database; +using Framework.IO; +using Game.Entities; +using Game.Maps; +using System; + +namespace Game.Chat.Commands +{ + [CommandGroup("wp", RBACPermissions.CommandWp)] + class WPCommands + { + [Command("add", RBACPermissions.CommandWpAdd)] + static bool HandleWpAddCommand(StringArguments args, CommandHandler handler) + { + // optional + string path_number = null; + uint pathid = 0; + + if (!args.Empty()) + path_number = args.NextString(); + + uint point = 0; + Creature target = handler.getSelectedCreature(); + + PreparedStatement stmt; + + if (string.IsNullOrEmpty(path_number)) + { + if (target) + pathid = target.GetWaypointPath(); + else + { + stmt = DB.World.GetPreparedStatement(WorldStatements.SEL_WAYPOINT_DATA_MAX_ID); + SQLResult result1 = DB.World.Query(stmt); + + uint maxpathid = result1.Read(0); + pathid = maxpathid + 1; + handler.SendSysMessage("|cff00ff00New path started.|r"); + } + } + else + pathid = uint.Parse(path_number); + + // path_id . ID of the Path + // point . number of the waypoint (if not 0) + + if (pathid == 0) + { + handler.SendSysMessage("|cffff33ffCurrent creature haven't loaded path.|r"); + return true; + } + + stmt = DB.World.GetPreparedStatement(WorldStatements.SEL_WAYPOINT_DATA_MAX_POINT); + stmt.AddValue(0, pathid); + SQLResult result = DB.World.Query(stmt); + + if (result.IsEmpty()) + point = result.Read(0); + + Player player = handler.GetSession().GetPlayer(); + + stmt = DB.World.GetPreparedStatement(WorldStatements.INS_WAYPOINT_DATA); + stmt.AddValue(0, pathid); + stmt.AddValue(1, point + 1); + stmt.AddValue(2, player.GetPositionX()); + stmt.AddValue(3, player.GetPositionY()); + stmt.AddValue(4, player.GetPositionZ()); + + DB.World.Execute(stmt); + + handler.SendSysMessage("|cff00ff00PathID: |r|cff00ffff{0} |r|cff00ff00: Waypoint |r|cff00ffff{1}|r|cff00ff00 created.|r", pathid, point + 1); + return true; + } + + [Command("event", RBACPermissions.CommandWpEvent)] + static bool HandleWpEventCommand(StringArguments args, CommandHandler handler) + { + if (args.Empty()) + return false; + + string show = args.NextString(); + PreparedStatement stmt; + + // Check + if ((show != "add") && (show != "mod") && (show != "del") && (show != "listid")) + return false; + + string arg_id = args.NextString(); + uint id = 0; + if (show == "add") + { + if (!string.IsNullOrEmpty(arg_id)) + id = uint.Parse(arg_id); + + if (id != 0) + { + stmt = DB.World.GetPreparedStatement(WorldStatements.SEL_WAYPOINT_SCRIPT_ID_BY_GUID); + stmt.AddValue(0, id); + SQLResult result = DB.World.Query(stmt); + + if (result.IsEmpty()) + { + stmt = DB.World.GetPreparedStatement(WorldStatements.INS_WAYPOINT_SCRIPT); + stmt.AddValue(0, id); + DB.World.Execute(stmt); + + handler.SendSysMessage("|cff00ff00Wp Event: New waypoint event added: {0}|r", "", id); + } + else + handler.SendSysMessage("|cff00ff00Wp Event: You have choosed an existing waypoint script guid: {0}|r", id); + } + else + { + stmt = DB.World.GetPreparedStatement(WorldStatements.SEL_WAYPOINT_SCRIPTS_MAX_ID); + SQLResult result = DB.World.Query(stmt); + id = result.Read(0); + + stmt = DB.World.GetPreparedStatement(WorldStatements.INS_WAYPOINT_SCRIPT); + stmt.AddValue(0, id + 1); + DB.World.Execute(stmt); + + handler.SendSysMessage("|cff00ff00Wp Event: New waypoint event added: |r|cff00ffff{0}|r", id + 1); + } + + return true; + } + + if (show == "listid") + { + if (string.IsNullOrEmpty(arg_id)) + { + handler.SendSysMessage("|cff33ffffWp Event: You must provide waypoint script id.|r"); + return true; + } + + id = uint.Parse(arg_id); + + uint a2, a3, a4, a5, a6; + float a8, a9, a10, a11; + string a7; + + stmt = DB.World.GetPreparedStatement(WorldStatements.SEL_WAYPOINT_SCRIPT_BY_ID); + stmt.AddValue(0, id); + SQLResult result = DB.World.Query(stmt); + + if (result.IsEmpty()) + { + handler.SendSysMessage("|cff33ffffWp Event: No waypoint scripts found on id: {0}|r", id); + return true; + } + + do + { + a2 = result.Read(0); + a3 = result.Read(1); + a4 = result.Read(2); + a5 = result.Read(3); + a6 = result.Read(4); + a7 = result.Read(5); + a8 = result.Read(6); + a9 = result.Read(7); + a10 = result.Read(8); + a11 = result.Read(9); + + handler.SendSysMessage("|cffff33ffid:|r|cff00ffff {0}|r|cff00ff00, guid: |r|cff00ffff{1}|r|cff00ff00, delay: |r|cff00ffff{2}|r|cff00ff00, command: |r|cff00ffff{3}|r|cff00ff00," + + "datalong: |r|cff00ffff{4}|r|cff00ff00, datalong2: |r|cff00ffff{5}|r|cff00ff00, datatext: |r|cff00ffff{6}|r|cff00ff00, posx: |r|cff00ffff{7}|r|cff00ff00, " + + "posy: |r|cff00ffff{8}|r|cff00ff00, posz: |r|cff00ffff{9}|r|cff00ff00, orientation: |r|cff00ffff{10}|r", id, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11); + } + while (result.NextRow()); + } + + if (show == "del") + { + if (arg_id.IsEmpty()) + { + handler.SendSysMessage("|cffff33ffERROR: Waypoint script guid not present.|r"); + return true; + } + + id = uint.Parse(arg_id); + + stmt = DB.World.GetPreparedStatement(WorldStatements.SEL_WAYPOINT_SCRIPT_ID_BY_GUID); + stmt.AddValue(0, id); + SQLResult result = DB.World.Query(stmt); + + if (!result.IsEmpty()) + { + stmt = DB.World.GetPreparedStatement(WorldStatements.DEL_WAYPOINT_SCRIPT); + stmt.AddValue(0, id); + DB.World.Execute(stmt); + + handler.SendSysMessage("|cff00ff00{0}{1}|r", "Wp Event: Waypoint script removed: ", id); + } + else + handler.SendSysMessage("|cffff33ffWp Event: ERROR: you have selected a non existing script: {0}|r", id); + + return true; + } + + if (show == "mod") + { + if (string.IsNullOrEmpty(arg_id)) + { + handler.SendSysMessage("|cffff33ffERROR: Waypoint script guid not present.|r"); + return true; + } + + id = uint.Parse(arg_id); + + if (id == 0) + { + handler.SendSysMessage("|cffff33ffERROR: No vallid waypoint script id not present.|r"); + return true; + } + + string arg_string = args.NextString(); + if (string.IsNullOrEmpty(arg_string)) + { + handler.SendSysMessage("|cffff33ffERROR: No argument present.|r"); + return true; + } + + if ((arg_string != "setid") && (arg_string != "delay") && (arg_string != "command") + && (arg_string != "datalong") && (arg_string != "datalong2") && (arg_string != "dataint") && (arg_string != "posx") + && (arg_string != "posy") && (arg_string != "posz") && (arg_string != "orientation")) + { + handler.SendSysMessage("|cffff33ffERROR: No valid argument present.|r"); + return true; + } + + string arg_3 = args.NextString(); + if (string.IsNullOrEmpty(arg_3)) + { + handler.SendSysMessage("|cffff33ffERROR: No additional argument present.|r"); + return true; + } + + if (arg_string == "setid") + { + uint newid = uint.Parse(arg_3); + handler.SendSysMessage("|cff00ff00Wp Event: Wypoint scipt guid: {0}|r|cff00ffff id changed: |r|cff00ff00{1}|r", newid, id); + + stmt = DB.World.GetPreparedStatement(WorldStatements.UPD_WAYPOINT_SCRIPT_ID); + stmt.AddValue(0, newid); + stmt.AddValue(1, id); + + DB.World.Execute(stmt); + + return true; + } + else + { + stmt = DB.World.GetPreparedStatement(WorldStatements.SEL_WAYPOINT_SCRIPT_ID_BY_GUID); + stmt.AddValue(0, id); + SQLResult result = DB.World.Query(stmt); + + if (result.IsEmpty()) + { + handler.SendSysMessage("|cffff33ffERROR: You have selected an non existing waypoint script guid.|r"); + return true; + } + + if (arg_string == "posx") + { + stmt = DB.World.GetPreparedStatement(WorldStatements.UPD_WAYPOINT_SCRIPT_X); + stmt.AddValue(0, float.Parse(arg_3)); + stmt.AddValue(1, id); + DB.World.Execute(stmt); + + handler.SendSysMessage("|cff00ff00Waypoint script:|r|cff00ffff {0}|r|cff00ff00 position_x updated.|r", id); + return true; + } + else if (arg_string == "posy") + { + stmt = DB.World.GetPreparedStatement(WorldStatements.UPD_WAYPOINT_SCRIPT_Y); + stmt.AddValue(0, float.Parse(arg_3)); + stmt.AddValue(1, id); + DB.World.Execute(stmt); + + handler.SendSysMessage("|cff00ff00Waypoint script: {0} position_y updated.|r", id); + return true; + } + else if (arg_string == "posz") + { + stmt = DB.World.GetPreparedStatement(WorldStatements.UPD_WAYPOINT_SCRIPT_Z); + stmt.AddValue(0, float.Parse(arg_3)); + stmt.AddValue(1, id); + DB.World.Execute(stmt); + + handler.SendSysMessage("|cff00ff00Waypoint script: |r|cff00ffff{0}|r|cff00ff00 position_z updated.|r", id); + return true; + } + else if (arg_string == "orientation") + { + stmt = DB.World.GetPreparedStatement(WorldStatements.UPD_WAYPOINT_SCRIPT_O); + stmt.AddValue(0, float.Parse(arg_3)); + stmt.AddValue(1, id); + DB.World.Execute(stmt); + + handler.SendSysMessage("|cff00ff00Waypoint script: |r|cff00ffff{0}|r|cff00ff00 orientation updated.|r", id); + return true; + } + else if (arg_string == "dataint") + { + DB.World.Execute("UPDATE waypoint_scripts SET {0}='{1}' WHERE guid='{2}'", arg_string, uint.Parse(arg_3), id); // Query can't be a prepared statement + + handler.SendSysMessage("|cff00ff00Waypoint script: |r|cff00ffff{0}|r|cff00ff00 dataint updated.|r", id); + return true; + } + else + { + DB.World.Execute("UPDATE waypoint_scripts SET {0}='{1}' WHERE guid='{2}'", arg_string, arg_string, id); // Query can't be a prepared statement + } + } + handler.SendSysMessage("|cff00ff00Waypoint script:|r|cff00ffff{0}:|r|cff00ff00 {1} updated.|r", id, arg_string); + } + return true; + } + + [Command("load", RBACPermissions.CommandWpLoad)] + static bool HandleWpLoadCommand(StringArguments args, CommandHandler handler) + { + if (args.Empty()) + return false; + + // optional + string path_number = args.NextString(); + + uint pathid; + ulong guidLow; + Creature target = handler.getSelectedCreature(); + + // Did player provide a path_id? + if (string.IsNullOrEmpty(path_number)) + return false; + + if (!target) + { + handler.SendSysMessage(CypherStrings.SelectCreature); + return false; + } + + if (target.GetEntry() == 1) + { + handler.SendSysMessage("|cffff33ffYou want to load path to a waypoint? Aren't you?|r"); + return false; + } + + pathid = uint.Parse(path_number); + if (pathid == 0) + { + handler.SendSysMessage("|cffff33ffNo valid path number provided.|r"); + return true; + } + + guidLow = target.GetSpawnId(); + + PreparedStatement stmt = DB.World.GetPreparedStatement(WorldStatements.SEL_CREATURE_ADDON_BY_GUID); + stmt.AddValue(0, guidLow); + SQLResult result = DB.World.Query(stmt); + + if (!result.IsEmpty()) + { + stmt = DB.World.GetPreparedStatement(WorldStatements.UPD_CREATURE_ADDON_PATH); + stmt.AddValue(0, pathid); + stmt.AddValue(1, guidLow); + } + else + { + stmt = DB.World.GetPreparedStatement(WorldStatements.INS_CREATURE_ADDON); + stmt.AddValue(0, guidLow); + stmt.AddValue(1, pathid); + } + + DB.World.Execute(stmt); + + stmt = DB.World.GetPreparedStatement(WorldStatements.UPD_CREATURE_MOVEMENT_TYPE); + stmt.AddValue(0, (byte)MovementGeneratorType.Waypoint); + stmt.AddValue(1, guidLow); + + DB.World.Execute(stmt); + + target.LoadPath(pathid); + target.SetDefaultMovementType(MovementGeneratorType.Waypoint); + target.GetMotionMaster().Initialize(); + target.Say("Path loaded.", Language.Universal); + + return true; + } + + [Command("modify", RBACPermissions.CommandWpModify)] + static bool HandleWpModifyCommand(StringArguments args, CommandHandler handler) + { + if (args.Empty()) + return false; + + // first arg: add del text emote spell waittime move + string show = args.NextString(); + if (string.IsNullOrEmpty(show)) + { + return false; + } + + // Check + // Remember: "show" must also be the name of a column! + if ((show != "delay") && (show != "action") && (show != "action_chance") + && (show != "move_flag") && (show != "del") && (show != "move")) + { + return false; + } + + // Next arg is: + string arg_str; + + // Did user provide a GUID + // or did the user select a creature? + // . variable lowguid is filled with the GUID of the NPC + uint pathid; + uint point; + Creature target = handler.getSelectedCreature(); + + // User did select a visual waypoint? + if (!target || target.GetEntry() != 1) + { + handler.SendSysMessage("|cffff33ffERROR: You must select a waypoint.|r"); + return false; + } + + // Check the creature + PreparedStatement stmt = DB.World.GetPreparedStatement(WorldStatements.SEL_WAYPOINT_DATA_BY_WPGUID); + stmt.AddValue(0, target.GetSpawnId()); + SQLResult result = DB.World.Query(stmt); + + if (result.IsEmpty()) + { + handler.SendSysMessage(CypherStrings.WaypointNotfoundsearch, target.GetGUID().ToString()); + // Select waypoint number from database + // Since we compare float values, we have to deal with + // some difficulties. + // Here we search for all waypoints that only differ in one from 1 thousand + // See also: http://dev.mysql.com/doc/refman/5.0/en/problems-with-float.html + string maxDiff = "0.01"; + + stmt = DB.World.GetPreparedStatement(WorldStatements.SEL_WAYPOINT_DATA_BY_POS); + stmt.AddValue(0, target.GetPositionX()); + stmt.AddValue(1, maxDiff); + stmt.AddValue(2, target.GetPositionY()); + stmt.AddValue(3, maxDiff); + stmt.AddValue(4, target.GetPositionZ()); + stmt.AddValue(5, maxDiff); + result = DB.World.Query(stmt); + + if (result.IsEmpty()) + { + handler.SendSysMessage(CypherStrings.WaypointNotfounddbproblem, target.GetGUID().ToString()); + return true; + } + } + + do + { + pathid = result.Read(0); + point = result.Read(1); + } + while (result.NextRow()); + + // We have the waypoint number and the GUID of the "master npc" + // Text is enclosed in "<>", all other arguments not + arg_str = args.NextString(); + + // Check for argument + if (show != "del" && show != "move" && arg_str == null) + { + handler.SendSysMessage(CypherStrings.WaypointArgumentreq, show); + return false; + } + + if (show == "del") + { + handler.SendSysMessage("|cff00ff00DEBUG: wp modify del, PathID: |r|cff00ffff{0}|r", pathid); + + target.DeleteFromDB(); + target.AddObjectToRemoveList(); + + stmt = DB.World.GetPreparedStatement(WorldStatements.DEL_WAYPOINT_DATA); + stmt.AddValue(0, pathid); + stmt.AddValue(1, point); + DB.World.Execute(stmt); + + stmt = DB.World.GetPreparedStatement(WorldStatements.UPD_WAYPOINT_DATA_POINT); + stmt.AddValue(0, pathid); + stmt.AddValue(1, point); + DB.World.Execute(stmt); + + handler.SendSysMessage(CypherStrings.WaypointRemoved); + return true; + } // del + + if (show == "move") + { + handler.SendSysMessage("|cff00ff00DEBUG: wp move, PathID: |r|cff00ffff{0}|r", pathid); + + Player chr = handler.GetSession().GetPlayer(); + Map map = chr.GetMap(); + // What to do: + // Move the visual spawnpoint + // Respawn the owner of the waypoints + target.DeleteFromDB(); + target.AddObjectToRemoveList(); + + // re-create + Creature wpCreature = new Creature(); + if (!wpCreature.Create(map.GenerateLowGuid(HighGuid.Creature), map, chr.GetPhaseMask(), 1, chr.GetPositionX(), chr.GetPositionY(), chr.GetPositionZ(), chr.GetOrientation())) + { + wpCreature.CopyPhaseFrom(chr); + wpCreature.SaveToDB(map.GetId(), (1u << (int)map.GetSpawnMode()), chr.GetPhaseMask()); + // To call _LoadGoods(); _LoadQuests(); CreateTrainerSpells(); + /// @todo Should we first use "Create" then use "LoadFromDB"? + if (!wpCreature.LoadCreatureFromDB(wpCreature.GetSpawnId(), map)) + { + handler.SendSysMessage(CypherStrings.WaypointVpNotcreated, 1); + return false; + } + + stmt = DB.World.GetPreparedStatement(WorldStatements.UPD_WAYPOINT_DATA_POSITION); + stmt.AddValue(0, chr.GetPositionX()); + stmt.AddValue(1, chr.GetPositionY()); + stmt.AddValue(2, chr.GetPositionZ()); + stmt.AddValue(3, pathid); + stmt.AddValue(4, point); + DB.World.Execute(stmt); + + handler.SendSysMessage(CypherStrings.WaypointChanged); + } + return true; + } // move + + if (string.IsNullOrEmpty(arg_str)) + { + // show_str check for present in list of correct values, no sql injection possible + DB.World.Execute("UPDATE waypoint_data SET {0}=null WHERE id='{1}' AND point='{2}'", show, pathid, point); // Query can't be a prepared statement + } + else + { + // show_str check for present in list of correct values, no sql injection possible + DB.World.Execute("UPDATE waypoint_data SET {0}='{1}' WHERE id='{2}' AND point='{3}'", show, arg_str, pathid, point); // Query can't be a prepared statement + } + + handler.SendSysMessage(CypherStrings.WaypointChangedNo, show); + return true; + } + + [Command("reload", RBACPermissions.CommandWpReload)] + static bool HandleWpReloadCommand(StringArguments args, CommandHandler handler) + { + if (args.Empty()) + return false; + + uint id = args.NextUInt32(); + + if (id == 0) + return false; + + handler.SendSysMessage("|cff00ff00Loading Path: |r|cff00ffff{0}|r", id); + Global.WaypointMgr.ReloadPath(id); + return true; + } + + [Command("show", RBACPermissions.CommandWpShow)] + static bool HandleWpShowCommand(StringArguments args, CommandHandler handler) + { + if (args.Empty()) + return false; + + // first arg: on, off, first, last + string show = args.NextString(); + if (string.IsNullOrEmpty(show)) + return false; + + // second arg: GUID (optional, if a creature is selected) + string guid_str = args.NextString(); + + uint pathid = 0; + Creature target = handler.getSelectedCreature(); + + // Did player provide a PathID? + + if (string.IsNullOrEmpty(guid_str)) + { + // No PathID provided + // . Player must have selected a creature + + if (!target) + { + handler.SendSysMessage(CypherStrings.SelectCreature); + return false; + } + + pathid = target.GetWaypointPath(); + } + else + { + // PathID provided + // Warn if player also selected a creature + // . Creature selection is ignored <- + if (target) + handler.SendSysMessage(CypherStrings.WaypointCreatselected); + + pathid = uint.Parse(guid_str); + } + + // Show info for the selected waypoint + if (show == "info") + { + // Check if the user did specify a visual waypoint + if (!target || target.GetEntry() != 1) + { + handler.SendSysMessage(CypherStrings.WaypointVpSelect); + return false; + } + + PreparedStatement stmt = DB.World.GetPreparedStatement(WorldStatements.SEL_WAYPOINT_DATA_ALL_BY_WPGUID); + stmt.AddValue(0, target.GetSpawnId()); + SQLResult result = DB.World.Query(stmt); + + if (result.IsEmpty()) + { + handler.SendSysMessage(CypherStrings.WaypointNotfounddbproblem, target.GetSpawnId()); + return true; + } + + handler.SendSysMessage("|cff00ffffDEBUG: wp show info:|r"); + do + { + pathid = result.Read(0); + uint point = result.Read(1); + uint delay = result.Read(2); + uint flag = result.Read(3); + uint ev_id = result.Read(4); + uint ev_chance = result.Read(5); + + handler.SendSysMessage("|cff00ff00Show info: for current point: |r|cff00ffff{0}|r|cff00ff00, Path ID: |r|cff00ffff{1}|r", point, pathid); + handler.SendSysMessage("|cff00ff00Show info: delay: |r|cff00ffff{0}|r", delay); + handler.SendSysMessage("|cff00ff00Show info: Move flag: |r|cff00ffff{0}|r", flag); + handler.SendSysMessage("|cff00ff00Show info: Waypoint event: |r|cff00ffff{0}|r", ev_id); + handler.SendSysMessage("|cff00ff00Show info: Event chance: |r|cff00ffff{0}|r", ev_chance); + } + while (result.NextRow()); + + return true; + } + + if (show == "on") + { + PreparedStatement stmt = DB.World.GetPreparedStatement(WorldStatements.SEL_WAYPOINT_DATA_POS_BY_ID); + stmt.AddValue(0, pathid); + SQLResult result = DB.World.Query(stmt); + + if (result.IsEmpty()) + { + handler.SendSysMessage("|cffff33ffPath no found.|r"); + return false; + } + + handler.SendSysMessage("|cff00ff00DEBUG: wp on, PathID: |cff00ffff{0}|r", pathid); + + // Delete all visuals for this NPC + stmt = DB.World.GetPreparedStatement(WorldStatements.SEL_WAYPOINT_DATA_WPGUID_BY_ID); + stmt.AddValue(0, pathid); + SQLResult result2 = DB.World.Query(stmt); + + if (!result2.IsEmpty()) + { + bool hasError = false; + do + { + ulong wpguid = result2.Read(0); + + Creature creature = handler.GetCreatureFromPlayerMapByDbGuid(wpguid); + if (!creature) + { + handler.SendSysMessage(CypherStrings.WaypointNotremoved, wpguid); + hasError = true; + + stmt = DB.World.GetPreparedStatement(WorldStatements.DEL_CREATURE); + stmt.AddValue(0, wpguid); + DB.World.Execute(stmt); + } + else + { + creature.CombatStop(); + creature.DeleteFromDB(); + creature.AddObjectToRemoveList(); + } + + } + while (result2.NextRow()); + + if (hasError) + { + handler.SendSysMessage(CypherStrings.WaypointToofar1); + handler.SendSysMessage(CypherStrings.WaypointToofar2); + handler.SendSysMessage(CypherStrings.WaypointToofar3); + } + } + + do + { + uint point = result.Read(0); + float x = result.Read(1); + float y = result.Read(2); + float z = result.Read(3); + + uint id = 1; + + Player chr = handler.GetSession().GetPlayer(); + Map map = chr.GetMap(); + float o = chr.GetOrientation(); + + Creature wpCreature = new Creature(); + if (!wpCreature.Create(map.GenerateLowGuid(HighGuid.Creature), map, chr.GetPhaseMask(), id, x, y, z, o)) + { + handler.SendSysMessage(CypherStrings.WaypointVpNotcreated, id); + return false; + } + + wpCreature.CopyPhaseFrom(chr); + wpCreature.SaveToDB(map.GetId(), (1u << (int)map.GetSpawnMode()), chr.GetPhaseMask()); + + // Set "wpguid" column to the visual waypoint + stmt = DB.World.GetPreparedStatement(WorldStatements.UPD_WAYPOINT_DATA_WPGUID); + stmt.AddValue(0, wpCreature.GetSpawnId()); + stmt.AddValue(1, pathid); + stmt.AddValue(2, point); + DB.World.Execute(stmt); + + // To call _LoadGoods(); _LoadQuests(); CreateTrainerSpells(); + if (!wpCreature.LoadCreatureFromDB(wpCreature.GetSpawnId(), map)) + { + handler.SendSysMessage(CypherStrings.WaypointVpNotcreated, id); + return false; + } + + if (target) + { + wpCreature.SetDisplayId(target.GetDisplayId()); + wpCreature.SetObjectScale(0.5f); + wpCreature.SetLevel(Math.Min(point, SharedConst.StrongMaxLevel)); + } + } + while (result.NextRow()); + + handler.SendSysMessage("|cff00ff00Showing the current creature's path.|r"); + return true; + } + + if (show == "first") + { + handler.SendSysMessage("|cff00ff00DEBUG: wp first, GUID: {0}|r", pathid); + + PreparedStatement stmt = DB.World.GetPreparedStatement(WorldStatements.SEL_WAYPOINT_DATA_POS_FIRST_BY_ID); + stmt.AddValue(0, pathid); + SQLResult result = DB.World.Query(stmt); + + if (result.IsEmpty()) + { + handler.SendSysMessage(CypherStrings.WaypointNotfound, pathid); + return false; + } + + float x = result.Read(0); + float y = result.Read(1); + float z = result.Read(2); + uint id = 1; + + Player chr = handler.GetSession().GetPlayer(); + float o = chr.GetOrientation(); + Map map = chr.GetMap(); + + Creature creature = new Creature(); + if (!creature.Create(map.GenerateLowGuid(HighGuid.Creature), map, chr.GetPhaseMask(), id, x, y, z, o)) + { + handler.SendSysMessage(CypherStrings.WaypointVpNotcreated, id); + return false; + } + + creature.CopyPhaseFrom(chr); + + creature.SaveToDB(map.GetId(), (uint)(1 << (int)map.GetSpawnMode()), chr.GetPhaseMask()); + if (!creature.LoadCreatureFromDB(creature.GetSpawnId(), map)) + { + handler.SendSysMessage(CypherStrings.WaypointVpNotcreated, id); + return false; + } + + if (target) + { + creature.SetDisplayId(target.GetDisplayId()); + creature.SetObjectScale(0.5f); + } + + return true; + } + + if (show == "last") + { + handler.SendSysMessage("|cff00ff00DEBUG: wp last, PathID: |r|cff00ffff{0}|r", pathid); + + PreparedStatement stmt = DB.World.GetPreparedStatement(WorldStatements.SEL_WAYPOINT_DATA_POS_LAST_BY_ID); + stmt.AddValue(0, pathid); + SQLResult result = DB.World.Query(stmt); + + if (result.IsEmpty()) + { + handler.SendSysMessage(CypherStrings.WaypointNotfoundlast, pathid); + return false; + } + + float x = result.Read(0); + float y = result.Read(1); + float z = result.Read(2); + float o = result.Read(3); + uint id = 1; + + Player chr = handler.GetSession().GetPlayer(); + Map map = chr.GetMap(); + + Creature creature = new Creature(); + if (!creature.Create(map.GenerateLowGuid(HighGuid.Creature), map, chr.GetPhaseMask(), id, x, y, z, o)) + { + handler.SendSysMessage(CypherStrings.WaypointNotcreated, id); + return false; + } + + creature.CopyPhaseFrom(chr); + + creature.SaveToDB(map.GetId(), (uint)(1 << (int)map.GetSpawnMode()), chr.GetPhaseMask()); + if (!creature.LoadCreatureFromDB(creature.GetSpawnId(), map)) + { + handler.SendSysMessage(CypherStrings.WaypointNotcreated, id); + return false; + } + + if (target) + { + creature.SetDisplayId(target.GetDisplayId()); + creature.SetObjectScale(0.5f); + } + + return true; + } + + if (show == "off") + { + PreparedStatement stmt = DB.World.GetPreparedStatement(WorldStatements.SEL_CREATURE_BY_ID); + stmt.AddValue(0, 1); + SQLResult result = DB.World.Query(stmt); + + if (result.IsEmpty()) + { + handler.SendSysMessage(CypherStrings.WaypointVpNotfound); + return false; + } + bool hasError = false; + do + { + ulong lowguid = result.Read(0); + + Creature creature = handler.GetCreatureFromPlayerMapByDbGuid(lowguid); + if (!creature) + { + handler.SendSysMessage(CypherStrings.WaypointNotremoved, lowguid); + hasError = true; + + stmt = DB.World.GetPreparedStatement(WorldStatements.DEL_CREATURE); + stmt.AddValue(0, lowguid); + DB.World.Execute(stmt); + } + else + { + creature.CombatStop(); + creature.DeleteFromDB(); + creature.AddObjectToRemoveList(); + } + } + while (result.NextRow()); + // set "wpguid" column to "empty" - no visual waypoint spawned + stmt = DB.World.GetPreparedStatement(WorldStatements.UPD_WAYPOINT_DATA_ALL_WPGUID); + + DB.World.Execute(stmt); + //DB.World.PExecute("UPDATE creature_movement SET wpguid = '0' WHERE wpguid <> '0'"); + + if (hasError) + { + handler.SendSysMessage(CypherStrings.WaypointToofar1); + handler.SendSysMessage(CypherStrings.WaypointToofar2); + handler.SendSysMessage(CypherStrings.WaypointToofar3); + } + + handler.SendSysMessage(CypherStrings.WaypointVpAllremoved); + return true; + } + + handler.SendSysMessage("|cffff33ffDEBUG: wpshow - no valid command found|r"); + return true; + } + + [Command("unload", RBACPermissions.CommandWpUnload)] + static bool HandleWpUnLoadCommand(StringArguments args, CommandHandler handler) + { + Creature target = handler.getSelectedCreature(); + if (!target) + { + handler.SendSysMessage("|cff33ffffYou must select a target.|r"); + return true; + } + + ulong guidLow = target.GetSpawnId(); + if (guidLow == 0) + { + handler.SendSysMessage("|cffff33ffTarget is not saved to DB.|r"); + return true; + } + + CreatureAddon addon = Global.ObjectMgr.GetCreatureAddon(guidLow); + if (addon == null || addon.path_id == 0) + { + handler.SendSysMessage("|cffff33ffTarget does not have a loaded path.|r"); + return true; + } + + PreparedStatement stmt = DB.World.GetPreparedStatement(WorldStatements.DEL_CREATURE_ADDON); + stmt.AddValue(0, guidLow); + DB.World.Execute(stmt); + + target.UpdateWaypointID(0); + + stmt = DB.World.GetPreparedStatement(WorldStatements.UPD_CREATURE_MOVEMENT_TYPE); + stmt.AddValue(0, (byte)MovementGeneratorType.Idle); + stmt.AddValue(1, guidLow); + DB.World.Execute(stmt); + + target.LoadPath(0); + target.SetDefaultMovementType(MovementGeneratorType.Idle); + target.GetMotionMaster().MoveTargetedHome(); + target.GetMotionMaster().Initialize(); + target.Say("Path unloaded.", Language.Universal); + return true; + } + } +} diff --git a/Game/Collision/BoundingIntervalHierarchy.cs b/Game/Collision/BoundingIntervalHierarchy.cs new file mode 100644 index 000000000..ff39e95ac --- /dev/null +++ b/Game/Collision/BoundingIntervalHierarchy.cs @@ -0,0 +1,636 @@ +/* + * Copyright (C) 2012-2017 CypherCore + * + * 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, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +using Framework.GameMath; +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Runtime.InteropServices; + +namespace Game.Collision +{ + public class BIH + { + public BIH() + { + init_empty(); + } + + void init_empty() + { + tree.Clear(); + objects.Clear(); + // create space for the first node + tree.Add(3u << 30); // dummy leaf + tree.Add(0); + tree.Add(0); + } + + public void build(List primitives, uint leafSize = 3, bool printStats = false) where T : IModel + { + if (primitives.Count == 0) + { + init_empty(); + return; + } + + buildData dat; + dat.maxPrims = (int)leafSize; + dat.numPrims = (uint)primitives.Count; + dat.indices = new uint[dat.numPrims]; + dat.primBound = new AxisAlignedBox[dat.numPrims]; + bounds = primitives[0].getBounds(); + for (int i = 0; i < dat.numPrims; ++i) + { + dat.indices[i] = (uint)i; + dat.primBound[i] = primitives[i].getBounds(); + bounds.merge(dat.primBound[i]); + } + List tempTree = new List(); + BuildStats stats = new BuildStats(); + buildHierarchy(tempTree, dat, stats); + if (printStats) + stats.printStats(); + + for (int i = 0; i < dat.numPrims; ++i) + objects.Add(dat.indices[i]); + tree = tempTree; + } + public uint primCount() { return (uint)objects.Count; } + + public bool readFromFile(BinaryReader reader) + { + var lo = reader.ReadStruct(); + var hi = reader.ReadStruct(); + bounds = new AxisAlignedBox(lo, hi); + + uint treeSize = reader.ReadUInt32(); + tree.Clear(); + for (var i = 0; i < treeSize; i++) + tree.Add(reader.ReadUInt32()); + + var count = reader.ReadUInt32(); + objects.Clear(); + for (var i = 0; i < count; i++) + objects.Add(reader.ReadUInt32()); + + return true; + } + + public void intersectRay(Ray r, WorkerCallback intersectCallback, ref float maxDist, bool stopAtFirst = false) + { + float intervalMin = -1.0f; + float intervalMax = -1.0f; + Vector3 org = r.Origin; + Vector3 dir = r.Direction; + Vector3 invDir = new Vector3(); + for (int i = 0; i < 3; ++i) + { + invDir[i] = 1.0f / dir[i]; + if (MathFunctions.fuzzyNe(dir[i], 0.0f)) + { + float t1 = (bounds.Lo[i] - org[i]) * invDir[i]; + float t2 = (bounds.Hi[i] - org[i]) * invDir[i]; + if (t1 > t2) + MathFunctions.Swap(ref t1, ref t2); + if (t1 > intervalMin) + intervalMin = t1; + if (t2 < intervalMax || intervalMax < 0.0f) + intervalMax = t2; + // intervalMax can only become smaller for other axis, + // and intervalMin only larger respectively, so stop early + if (intervalMax <= 0 || intervalMin >= maxDist) + return; + } + } + + if (intervalMin > intervalMax) + return; + intervalMin = Math.Max(intervalMin, 0.0f); + intervalMax = Math.Min(intervalMax, maxDist); + + uint[] offsetFront = new uint[3]; + uint[] offsetBack = new uint[3]; + uint[] offsetFront3 = new uint[3]; + uint[] offsetBack3 = new uint[3]; + // compute custom offsets from direction sign bit + + for (int i = 0; i < 3; ++i) + { + offsetFront[i] = floatToRawIntBits(dir[i]) >> 31; + offsetBack[i] = offsetFront[i] ^ 1; + offsetFront3[i] = offsetFront[i] * 3; + offsetBack3[i] = offsetBack[i] * 3; + + // avoid always adding 1 during the inner loop + ++offsetFront[i]; + ++offsetBack[i]; + } + + StackNode[] stack = new StackNode[64]; + int stackPos = 0; + int node = 0; + + while (true) + { + while (true) + { + uint tn = tree[node]; + uint axis = (uint)(tn & (3 << 30)) >> 30; + bool BVH2 = Convert.ToBoolean(tn & (1 << 29)); + int offset = (int)(tn & ~(7 << 29)); + if (!BVH2) + { + if (axis < 3) + { + // "normal" interior node + float tf = (intBitsToFloat(tree[(int)(node + offsetFront[axis])]) - org[axis]) * invDir[axis]; + float tb = (intBitsToFloat(tree[(int)(node + offsetBack[axis])]) - org[axis]) * invDir[axis]; + // ray passes between clip zones + if (tf < intervalMin && tb > intervalMax) + break; + int back = (int)(offset + offsetBack3[axis]); + node = back; + // ray passes through far node only + if (tf < intervalMin) + { + intervalMin = (tb >= intervalMin) ? tb : intervalMin; + continue; + } + node = offset + (int)offsetFront3[axis]; // front + // ray passes through near node only + if (tb > intervalMax) + { + intervalMax = (tf <= intervalMax) ? tf : intervalMax; + continue; + } + // ray passes through both nodes + // push back node + stack[stackPos].node = (uint)back; + stack[stackPos].tnear = (tb >= intervalMin) ? tb : intervalMin; + stack[stackPos].tfar = intervalMax; + stackPos++; + // update ray interval for front node + intervalMax = (tf <= intervalMax) ? tf : intervalMax; + continue; + } + else + { + // leaf - test some objects + int n = (int)tree[node + 1]; + while (n > 0) + { + bool hit = intersectCallback.Invoke(r, objects[offset], ref maxDist, stopAtFirst); + if (stopAtFirst && hit) + return; + --n; + ++offset; + } + break; + } + } + else + { + if (axis > 2) + return; // should not happen + float tf = (intBitsToFloat(tree[(int)(node + offsetFront[axis])]) - org[axis]) * invDir[axis]; + float tb = (intBitsToFloat(tree[(int)(node + offsetBack[axis])]) - org[axis]) * invDir[axis]; + node = offset; + intervalMin = (tf >= intervalMin) ? tf : intervalMin; + intervalMax = (tb <= intervalMax) ? tb : intervalMax; + if (intervalMin > intervalMax) + break; + continue; + } + } // traversal loop + do + { + // stack is empty? + if (stackPos == 0) + return; + // move back up the stack + stackPos--; + intervalMin = stack[stackPos].tnear; + if (maxDist < intervalMin) + continue; + node = (int)stack[stackPos].node; + intervalMax = stack[stackPos].tfar; + break; + } while (true); + } + } + + public void intersectPoint(Vector3 p, WorkerCallback intersectCallback) + { + if (!bounds.contains(p)) + return; + + StackNode[] stack = new StackNode[64]; + int stackPos = 0; + int node = 0; + + while (true) + { + while (true) + { + uint tn = tree[node]; + uint axis = (uint)(tn & (3 << 30)) >> 30; + bool BVH2 = Convert.ToBoolean(tn & (1 << 29)); + int offset = (int)(tn & ~(7 << 29)); + if (!BVH2) + { + if (axis < 3) + { + // "normal" interior node + float tl = intBitsToFloat(tree[node + 1]); + float tr = intBitsToFloat(tree[node + 2]); + // point is between clip zones + if (tl < p[(int)axis] && tr > p[axis]) + break; + int right = offset + 3; + node = right; + // point is in right node only + if (tl < p[(int)axis]) + { + continue; + } + node = offset; // left + // point is in left node only + if (tr > p[axis]) + { + continue; + } + // point is in both nodes + // push back right node + stack[stackPos].node = (uint)right; + stackPos++; + continue; + } + else + { + // leaf - test some objects + uint n = tree[node + 1]; + while (n > 0) + { + intersectCallback.Invoke(p, objects[offset]); // !!! + --n; + ++offset; + } + break; + } + } + else // BVH2 node (empty space cut off left and right) + { + if (axis > 2) + return; // should not happen + float tl = intBitsToFloat(tree[node + 1]); + float tr = intBitsToFloat(tree[node + 2]); + node = offset; + if (tl > p[axis] || tr < p[axis]) + break; + continue; + } + } // traversal loop + + // stack is empty? + if (stackPos == 0) + return; + // move back up the stack + stackPos--; + node = (int)stack[stackPos].node; + } + } + + void buildHierarchy(List tempTree, buildData dat, BuildStats stats) + { + // create space for the first node + tempTree.Add(3u << 30); // dummy leaf + tempTree.Add(0); + tempTree.Add(0); + + // seed bbox + AABound gridBox = new AABound(); + gridBox.lo = bounds.Lo; + gridBox.hi = bounds.Hi; + AABound nodeBox = gridBox; + // seed subdivide function + subdivide(0, (int)(dat.numPrims - 1), tempTree, dat, gridBox, nodeBox, 0, 1, stats); + } + + void subdivide(int left, int right, List tempTree, buildData dat, AABound gridBox, AABound nodeBox, int nodeIndex, int depth, BuildStats stats) + { + if ((right - left + 1) <= dat.maxPrims || depth >= 64) + { + // write leaf node + stats.updateLeaf(depth, right - left + 1); + createNode(tempTree, nodeIndex, left, right); + return; + } + // calculate extents + int axis = -1, prevAxis, rightOrig; + float clipL = float.NaN, clipR = float.NaN, prevClip = float.NaN; + float split = float.NaN, prevSplit; + bool wasLeft = true; + while (true) + { + prevAxis = axis; + prevSplit = split; + // perform quick consistency checks + Vector3 d = gridBox.hi - gridBox.lo; + for (int i = 0; i < 3; i++) + { + if (nodeBox.hi[i] < gridBox.lo[i] || nodeBox.lo[i] > gridBox.hi[i]) + Log.outError(LogFilter.Server, "Reached tree area in error - discarding node with: {0} objects", right - left + 1); + } + // find longest axis + axis = (int)d.primaryAxis(); + split = 0.5f * (gridBox.lo[axis] + gridBox.hi[axis]); + // partition L/R subsets + clipL = float.NegativeInfinity; + clipR = float.PositiveInfinity; + rightOrig = right; // save this for later + float nodeL = float.PositiveInfinity; + float nodeR = float.NegativeInfinity; + for (int i = left; i <= right; ) + { + int obj = (int)dat.indices[i]; + float minb = dat.primBound[obj].Lo[axis]; + float maxb = dat.primBound[obj].Hi[axis]; + float center = (minb + maxb) * 0.5f; + if (center <= split) + { + // stay left + i++; + if (clipL < maxb) + clipL = maxb; + } + else + { + // move to the right most + int t = (int)dat.indices[i]; + dat.indices[i] = dat.indices[right]; + dat.indices[right] = (uint)t; + right--; + if (clipR > minb) + clipR = minb; + } + nodeL = Math.Min(nodeL, minb); + nodeR = Math.Max(nodeR, maxb); + } + // check for empty space + if (nodeL > nodeBox.lo[axis] && nodeR < nodeBox.hi[axis]) + { + float nodeBoxW = nodeBox.hi[axis] - nodeBox.lo[axis]; + float nodeNewW = nodeR - nodeL; + // node box is too big compare to space occupied by primitives? + if (1.3f * nodeNewW < nodeBoxW) + { + stats.updateBVH2(); + int nextIndex1 = tempTree.Count(); + // allocate child + tempTree.Add(0); + tempTree.Add(0); + tempTree.Add(0); + // write bvh2 clip node + stats.updateInner(); + tempTree[nodeIndex + 0] = (uint)((axis << 30) | (1 << 29) | nextIndex1); + tempTree[nodeIndex + 1] = floatToRawIntBits(nodeL); + tempTree[nodeIndex + 2] = floatToRawIntBits(nodeR); + // update nodebox and recurse + nodeBox.lo[axis] = nodeL; + nodeBox.hi[axis] = nodeR; + subdivide(left, rightOrig, tempTree, dat, gridBox, nodeBox, nextIndex1, depth + 1, stats); + return; + } + } + // ensure we are making progress in the subdivision + if (right == rightOrig) + { + // all left + if (prevAxis == axis && MathFunctions.fuzzyEq(prevSplit, split)) + { + // we are stuck here - create a leaf + stats.updateLeaf(depth, right - left + 1); + createNode(tempTree, nodeIndex, left, right); + return; + } + if (clipL <= split) + { + // keep looping on left half + gridBox.hi[axis] = split; + prevClip = clipL; + wasLeft = true; + continue; + } + gridBox.hi[axis] = split; + prevClip = float.NaN; + } + else if (left > right) + { + // all right + right = rightOrig; + if (prevAxis == axis && MathFunctions.fuzzyEq(prevSplit, split)) + { + // we are stuck here - create a leaf + stats.updateLeaf(depth, right - left + 1); + createNode(tempTree, nodeIndex, left, right); + return; + } + + if (clipR >= split) + { + // keep looping on right half + gridBox.lo[axis] = split; + prevClip = clipR; + wasLeft = false; + continue; + } + gridBox.lo[axis] = split; + prevClip = float.NaN; + } + else + { + // we are actually splitting stuff + if (prevAxis != -1 && !float.IsNaN(prevClip)) + { + // second time through - lets create the previous split + // since it produced empty space + int nextIndex0 = tempTree.Count; + // allocate child node + tempTree.Add(0); + tempTree.Add(0); + tempTree.Add(0); + if (wasLeft) + { + // create a node with a left child + // write leaf node + stats.updateInner(); + tempTree[nodeIndex + 0] = (uint)((prevAxis << 30) | nextIndex0); + tempTree[nodeIndex + 1] = floatToRawIntBits(prevClip); + tempTree[nodeIndex + 2] = floatToRawIntBits(float.PositiveInfinity); + } + else + { + // create a node with a right child + // write leaf node + stats.updateInner(); + tempTree[nodeIndex + 0] = (uint)((prevAxis << 30) | (nextIndex0 - 3)); + tempTree[nodeIndex + 1] = floatToRawIntBits(float.NegativeInfinity); + tempTree[nodeIndex + 2] = floatToRawIntBits(prevClip); + } + // count stats for the unused leaf + depth++; + stats.updateLeaf(depth, 0); + // now we keep going as we are, with a new nodeIndex: + nodeIndex = nextIndex0; + } + break; + } + } + // compute index of child nodes + int nextIndex = tempTree.Count; + // allocate left node + int nl = right - left + 1; + int nr = rightOrig - (right + 1) + 1; + if (nl > 0) + { + tempTree.Add(0); + tempTree.Add(0); + tempTree.Add(0); + } + else + nextIndex -= 3; + // allocate right node + if (nr > 0) + { + tempTree.Add(0); + tempTree.Add(0); + tempTree.Add(0); + } + // write leaf node + stats.updateInner(); + tempTree[nodeIndex + 0] = (uint)((axis << 30) | nextIndex); + tempTree[nodeIndex + 1] = floatToRawIntBits(clipL); + tempTree[nodeIndex + 2] = floatToRawIntBits(clipR); + // prepare L/R child boxes + AABound gridBoxL = gridBox; + AABound gridBoxR = gridBox; + AABound nodeBoxL = nodeBox; + AABound nodeBoxR = nodeBox; + gridBoxL.hi[axis] = gridBoxR.lo[axis] = split; + nodeBoxL.hi[axis] = clipL; + nodeBoxR.lo[axis] = clipR; + // recurse + if (nl > 0) + subdivide(left, right, tempTree, dat, gridBoxL, nodeBoxL, nextIndex, depth + 1, stats); + else + stats.updateLeaf(depth + 1, 0); + if (nr > 0) + subdivide(right + 1, rightOrig, tempTree, dat, gridBoxR, nodeBoxR, nextIndex + 3, depth + 1, stats); + else + stats.updateLeaf(depth + 1, 0); + } + + void createNode(List tempTree, int nodeIndex, int left, int right) + { + // write leaf node + tempTree[nodeIndex + 0] = (uint)((3 << 30) | left); + tempTree[nodeIndex + 1] = (uint)(right - left + 1); + } + + struct buildData + { + public uint[] indices; + public AxisAlignedBox[] primBound; + public uint numPrims; + public int maxPrims; + } + struct StackNode + { + public uint node; + public float tnear; + public float tfar; + } + public class BuildStats + { + public int numNodes; + public int numLeaves; + public int sumObjects; + public int minObjects; + public int maxObjects; + public int sumDepth; + public int minDepth; + public int maxDepth; + int[] numLeavesN = new int[6]; + int numBVH2; + + public BuildStats() + { + numNodes = 0; + numLeaves = 0; + sumObjects = 0; + minObjects = 0x0FFFFFFF; + maxObjects = -1; + sumDepth = 0; + minDepth = 0x0FFFFFFF; + maxDepth = -1; + numBVH2 = 0; + + for (int i = 0; i < 6; ++i) + numLeavesN[i] = 0; + } + + public void updateInner() { numNodes++; } + public void updateBVH2() { numBVH2++; } + public void updateLeaf(int depth, int n) { } + public void printStats() { } + } + + + AxisAlignedBox bounds; + List tree = new List(); + List objects = new List(); + + [StructLayout(LayoutKind.Explicit)] + public struct FloatToIntConverter + { + [FieldOffset(0)] + public uint IntValue; + [FieldOffset(0)] + public float FloatValue; + } + + uint floatToRawIntBits(float f) + { + FloatToIntConverter converter = new FloatToIntConverter(); + converter.FloatValue = f; + return converter.IntValue; + } + float intBitsToFloat(uint i) + { + FloatToIntConverter converter = new FloatToIntConverter(); + converter.IntValue = i; + return converter.FloatValue; + } + + } + public struct AABound + { + public Vector3 lo, hi; + } +} diff --git a/Game/Collision/BoundingIntervalHierarchyWrap.cs b/Game/Collision/BoundingIntervalHierarchyWrap.cs new file mode 100644 index 000000000..f3731a614 --- /dev/null +++ b/Game/Collision/BoundingIntervalHierarchyWrap.cs @@ -0,0 +1,111 @@ +/* + * Copyright (C) 2012-2017 CypherCore + * + * 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, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +using Framework.GameMath; +using System; +using System.Collections.Generic; + +namespace Game.Collision +{ + public class BIHWrap where T : IModel + { + public void insert(T obj) + { + ++unbalanced_times; + m_objects_to_push.Add(obj); + } + public void remove(T obj) + { + ++unbalanced_times; + uint Idx = 0; + if (m_obj2Idx.TryGetValue(obj, out Idx)) + m_objects[(int)Idx] = null; + else + m_objects_to_push.Remove(obj); + } + + public void balance() + { + if (unbalanced_times == 0) + return; + + unbalanced_times = 0; + m_objects.Clear(); + m_objects.AddRange(m_obj2Idx.Keys); + m_objects.AddRange(m_objects_to_push); + + m_tree.build(m_objects); + } + + public void intersectRay(Ray ray, WorkerCallback intersectCallback, ref float maxDist) + { + balance(); + MDLCallback temp_cb = new MDLCallback(intersectCallback, m_objects.ToArray(), (uint)m_objects.Count); + m_tree.intersectRay(ray, temp_cb, ref maxDist, true); + } + + public void intersectPoint(Vector3 point, WorkerCallback intersectCallback) + { + balance(); + MDLCallback callback = new MDLCallback(intersectCallback, m_objects.ToArray(), (uint)m_objects.Count); + m_tree.intersectPoint(point, callback); + } + + BIH m_tree = new BIH(); + List m_objects = new List(); + Dictionary m_obj2Idx = new Dictionary(); + HashSet m_objects_to_push = new HashSet(); + int unbalanced_times; + + public class MDLCallback : WorkerCallback + { + T[] objects; + WorkerCallback _callback; + uint objects_size; + + public MDLCallback(WorkerCallback callback, T[] objects_array, uint size) + { + objects = objects_array; + _callback = callback; + objects_size = size; + } + + /// Intersect ray + public override bool Invoke(Ray ray, uint idx, ref float maxDist) + { + if (idx >= objects_size) + return false; + + T obj = objects[idx]; + if (obj != null) + return _callback.Invoke(ray, obj, ref maxDist); + return false; + } + + /// Intersect point + public override void Invoke(Vector3 p, uint idx) + { + if (idx >= objects_size) + return; + + T obj = objects[idx]; + if (obj != null) + _callback.Invoke(p, Convert.ToUInt32(obj)); + } + } + } +} diff --git a/Game/Collision/Callbacks.cs b/Game/Collision/Callbacks.cs new file mode 100644 index 000000000..1716afea2 --- /dev/null +++ b/Game/Collision/Callbacks.cs @@ -0,0 +1,250 @@ +/* + * Copyright (C) 2012-2017 CypherCore + * + * 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, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +using Framework.GameMath; +using System; +using System.Collections.Generic; + +namespace Game.Collision +{ + public class WorkerCallback + { + public virtual void Invoke(Vector3 point, uint entry) { } + public virtual bool Invoke(Ray ray, uint entry, ref float distance, bool pStopAtFirstHit) { return false; } + public virtual bool Invoke(Ray r, GameObjectModel obj, ref float distance) { return false; } + public virtual bool Invoke(Ray r, IModel obj, ref float distance) { return false; } + public virtual bool Invoke(Ray ray, uint idx, ref float maxDist) { return false; } + } + + public class TriBoundFunc + { + public TriBoundFunc(List vert) + { + vertices = vert; + } + + public void Invoke(MeshTriangle tri, out AxisAlignedBox value) + { + Vector3 lo = vertices[(int)tri.idx0]; + Vector3 hi = lo; + + lo = (lo.Min(vertices[(int)tri.idx1])).Min(vertices[(int)tri.idx2]); + hi = (hi.Max(vertices[(int)tri.idx1])).Max(vertices[(int)tri.idx2]); + + value = new AxisAlignedBox(lo, hi); + } + + List vertices; + } + + public class WModelAreaCallback : WorkerCallback + { + public WModelAreaCallback(List vals, Vector3 down) + { + prims = vals; + hit = null; + zDist = float.PositiveInfinity; + zVec = down; + } + + List prims; + public GroupModel hit; + public float zDist; + Vector3 zVec; + public override void Invoke(Vector3 point, uint entry) + { + float group_Z; + if (prims[(int)entry].IsInsideObject(point, zVec, out group_Z)) + { + if (group_Z < zDist) + { + zDist = group_Z; + hit = prims[(int)entry]; + } + } + } + } + + public class WModelRayCallBack : WorkerCallback + { + public WModelRayCallBack(List mod) + { + models = mod; + hit = false; + } + public override bool Invoke(Ray ray, uint entry, ref float distance, bool pStopAtFirstHit) + { + bool result = models[(int)entry].IntersectRay(ray, ref distance, pStopAtFirstHit); + if (result) hit = true; + return hit; + } + List models; + public bool hit; + } + + public class GModelRayCallback : WorkerCallback + { + public GModelRayCallback(List tris, List vert) + { + vertices = vert; + triangles = tris; + hit = false; + } + public override bool Invoke(Ray ray, uint entry, ref float distance, bool pStopAtFirstHit) + { + bool result = IntersectTriangle(triangles[(int)entry], vertices, ray, ref distance); + if (result) + hit = true; + + return hit; + } + + bool IntersectTriangle(MeshTriangle tri, List points, Ray ray, ref float distance) + { + const float EPS = 1e-5f; + + // See RTR2 ch. 13.7 for the algorithm. + + Vector3 e1 = points[(int)tri.idx1] - points[(int)tri.idx0]; + Vector3 e2 = points[(int)tri.idx2] - points[(int)tri.idx0]; + Vector3 p = new Vector3(ray.Direction.cross(e2)); + float a = e1.dot(p); + + if (Math.Abs(a) < EPS) + { + // Determinant is ill-conditioned; abort early + return false; + } + + float f = 1.0f / a; + Vector3 s = new Vector3(ray.Origin - points[(int)tri.idx0]); + float u = f * s.dot(p); + + if ((u < 0.0f) || (u > 1.0f)) + { + // We hit the plane of the m_geometry, but outside the m_geometry + return false; + } + + Vector3 q = new Vector3(s.cross(e1)); + float v = f * ray.Direction.dot(q); + + if ((v < 0.0f) || ((u + v) > 1.0f)) + { + // We hit the plane of the triangle, but outside the triangle + return false; + } + + float t = f * e2.dot(q); + + if ((t > 0.0f) && (t < distance)) + { + // This is a new hit, closer than the previous one + distance = t; + return true; + } + // This hit is after the previous hit, so ignore it + return false; + } + + List vertices; + List triangles; + public bool hit; + } + + public class MapRayCallback : WorkerCallback + { + public MapRayCallback(ModelInstance[] val) + { + prims = val; + hit = false; + } + public override bool Invoke(Ray ray, uint entry, ref float distance, bool pStopAtFirstHit = true) + { + if (prims[entry] == null) + return false; + bool result = prims[entry].intersectRay(ray, ref distance, pStopAtFirstHit); + if (result) + hit = true; + return result; + } + public bool didHit() { return hit; } + + ModelInstance[] prims; + bool hit; + } + + public class AreaInfoCallback : WorkerCallback + { + public AreaInfoCallback(ModelInstance[] val) + { + prims = val; + } + public override void Invoke(Vector3 point, uint entry) + { + if (prims[entry] == null) + return; + + prims[entry].intersectPoint(point, aInfo); + } + + ModelInstance[] prims; + public AreaInfo aInfo = new AreaInfo(); + } + + public class LocationInfoCallback : WorkerCallback + { + public LocationInfoCallback(ModelInstance[] val, LocationInfo info) + { + prims = val; + locInfo = info; + result = false; + } + + public override void Invoke(Vector3 point, uint entry) + { + if (prims[entry] != null && prims[entry].GetLocationInfo(point, locInfo)) + result = true; + } + + ModelInstance[] prims; + LocationInfo locInfo; + public bool result; + } + + public class DynamicTreeIntersectionCallback : WorkerCallback + { + public DynamicTreeIntersectionCallback(List phases) + { + _didHit = false; + _phases = phases; + } + + public override bool Invoke(Ray r, GameObjectModel obj, ref float distance) + { + _didHit = obj.intersectRay(r, ref distance, true, _phases); + return _didHit; + } + + public bool didHit() { return _didHit; } + + bool _didHit; + List _phases; + } + + +} diff --git a/Game/Collision/DynamicTree.cs b/Game/Collision/DynamicTree.cs new file mode 100644 index 000000000..f55cb7ca6 --- /dev/null +++ b/Game/Collision/DynamicTree.cs @@ -0,0 +1,179 @@ +/* + * Copyright (C) 2012-2017 CypherCore + * + * 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, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +using Framework.GameMath; +using System.Collections.Generic; +using System.Diagnostics.Contracts; + +namespace Game.Collision +{ + public class DynamicMapTree + { + DynTreeImpl impl; + + public DynamicMapTree() + { + impl = new DynTreeImpl(); + } + public void insert(GameObjectModel mdl) + { + impl.insert(mdl); + } + + public void remove(GameObjectModel mdl) + { + impl.remove(mdl); + } + + public bool contains(GameObjectModel mdl) + { + return impl.contains(mdl); + } + + public void balance() + { + impl.balance(); + } + int size() + { + return impl.size(); + } + public void update(uint diff) + { + impl.update(diff); + } + + public bool getIntersectionTime(List phases, Ray ray, Vector3 endPos, float maxDist) + { + float distance = maxDist; + DynamicTreeIntersectionCallback callback = new DynamicTreeIntersectionCallback(phases); + impl.intersectRay(ray, callback, ref distance, endPos); + if (callback.didHit()) + maxDist = distance; + return callback.didHit(); + } + + public bool getObjectHitPos(List phases, Vector3 startPos, Vector3 endPos, ref Vector3 resultHitPos, float modifyDist) + { + bool result = false; + float maxDist = (endPos - startPos).magnitude(); + // valid map coords should *never ever* produce float overflow, but this would produce NaNs too + Contract.Assert(maxDist < float.MaxValue); + // prevent NaN values which can cause BIH intersection to enter infinite loop + if (maxDist < 1e-10f) + { + resultHitPos = endPos; + return false; + } + Vector3 dir = (endPos - startPos) / maxDist; // direction with length of 1 + Ray ray = new Ray(startPos, dir); + float dist = maxDist; + if (getIntersectionTime(phases, ray, endPos, dist)) + { + resultHitPos = startPos + dir * dist; + if (modifyDist < 0) + { + if ((resultHitPos - startPos).magnitude() > -modifyDist) + resultHitPos += dir * modifyDist; + else + resultHitPos = startPos; + } + else + resultHitPos += dir * modifyDist; + + result = true; + } + else + { + resultHitPos = endPos; + result = false; + } + return result; + } + + public bool isInLineOfSight(Vector3 startPos, Vector3 endPos, List phases) + { + float maxDist = (endPos - startPos).magnitude(); + + if (!MathFunctions.fuzzyGt(maxDist, 0)) + return true; + + Ray r = new Ray(startPos, (endPos - startPos) / maxDist); + DynamicTreeIntersectionCallback callback = new DynamicTreeIntersectionCallback(phases); + impl.intersectRay(r, callback, ref maxDist, endPos); + + return !callback.didHit(); + } + + public float getHeight(float x, float y, float z, float maxSearchDist, List phases) + { + Vector3 v = new Vector3(x, y, z + 0.5f); + Ray r = new Ray(v, new Vector3(0, 0, -1)); + DynamicTreeIntersectionCallback callback = new DynamicTreeIntersectionCallback(phases); + impl.intersectZAllignedRay(r, callback, ref maxSearchDist); + + if (callback.didHit()) + return v.Z - maxSearchDist; + else + return float.NegativeInfinity; + } + } + + public class DynTreeImpl : RegularGrid2D> + { + public DynTreeImpl() + { + rebalance_timer = new TimeTrackerSmall(200); + unbalanced_times = 0; + } + + public override void insert(GameObjectModel mdl) + { + base.insert(mdl); + ++unbalanced_times; + } + + public override void remove(GameObjectModel mdl) + { + base.remove(mdl); + ++unbalanced_times; + } + + public override void balance() + { + base.balance(); + unbalanced_times = 0; + } + + public void update(uint difftime) + { + if (size() == 0) + return; + + rebalance_timer.Update((int)difftime); + if (rebalance_timer.Passed()) + { + rebalance_timer.Reset(200); + if (unbalanced_times > 0) + balance(); + } + } + + TimeTrackerSmall rebalance_timer; + int unbalanced_times; + } +} diff --git a/Game/Collision/Management/VMapManager.cs b/Game/Collision/Management/VMapManager.cs new file mode 100644 index 000000000..3fa16807f --- /dev/null +++ b/Game/Collision/Management/VMapManager.cs @@ -0,0 +1,289 @@ +/* + * Copyright (C) 2012-2017 CypherCore + * + * 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, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +using Framework.Constants; +using Framework.GameMath; +using System; +using System.Collections.Generic; +using System.Diagnostics.Contracts; + +namespace Game.Collision +{ + public enum VMAPLoadResult + { + Error, + OK, + Ignored + } + + public class VMapManager : Singleton + { + VMapManager() { } + + public static string VMapPath = Global.WorldMgr.GetDataPath() + "/vmaps/"; + + public VMAPLoadResult loadMap(uint mapId, uint x, uint y) + { + var result = VMAPLoadResult.Ignored; + if (_loadMap(mapId, x, y)) + result = VMAPLoadResult.OK; + else + result = VMAPLoadResult.Error; + + return result; + } + + bool _loadMap(uint mapId, uint tileX, uint tileY) + { + var instanceTree = iInstanceMapTrees.LookupByKey(mapId); + if (instanceTree == null) + { + string filename = string.Format("{0}{1:D4}.vmtree", VMapPath, mapId); + StaticMapTree newTree = new StaticMapTree(mapId); + if (!newTree.InitMap(filename, this)) + return false; + + iInstanceMapTrees.Add(mapId, newTree); + + instanceTree = newTree; + } + + return instanceTree.LoadMapTile(tileX, tileY, this); + } + + public WorldModel acquireModelInstance(string filename) + { + var model = iLoadedModelFiles.LookupByKey(filename); + if (model == null) + { + WorldModel worldmodel = new WorldModel(); + if (!worldmodel.readFile(VMapPath + filename + ".vmo")) + { + Log.outError(LogFilter.Server, "VMapManager: could not load '{0}.vmo'", filename); + return null; + } + Log.outDebug(LogFilter.Maps, "VMapManager: loading file '{0}'", filename); + iLoadedModelFiles.Add(filename, new ManagedModel()); + model = iLoadedModelFiles.LookupByKey(filename); + model.setModel(worldmodel); + } + model.incRefCount(); + return model.getModel(); + } + + public void releaseModelInstance(string filename) + { + var model = iLoadedModelFiles.LookupByKey(filename); + if (model == null) + { + Log.outError(LogFilter.Server, "VMapManager: trying to unload non-loaded file '{0}'", filename); + return; + } + if (model.decRefCount() == 0) + { + Log.outDebug(LogFilter.Maps, "VMapManager: unloading file '{0}'", filename); + iLoadedModelFiles.Remove(filename); + } + } + + public bool existsMap(uint mapId, uint x, uint y) + { + return StaticMapTree.CanLoadMap(VMapPath, mapId, x, y); + } + + public static string getMapFileName(uint mapId) + { + return string.Format("{0:D4}.vmtree", mapId); + } + + public bool GetLiquidLevel(uint mapId, float x, float y, float z, byte reqLiquidType, ref float level, ref float floor, ref uint type) + { + if (!Global.DisableMgr.IsDisabledFor(DisableType.VMAP, mapId, null, DisableFlags.VmapLiquidStatus)) + { + var instanceTree = iInstanceMapTrees.LookupByKey(mapId); + if (instanceTree != null) + { + LocationInfo info = new LocationInfo(); + Vector3 pos = convertPositionToInternalRep(x, y, z); + if (instanceTree.GetLocationInfo(pos, info)) + { + floor = info.ground_Z; + Contract.Assert(floor < float.MaxValue); + type = info.hitModel.GetLiquidType(); // entry from LiquidType.dbc + if (reqLiquidType != 0 && !Convert.ToBoolean(Global.DB2Mgr.GetLiquidFlags(type) & reqLiquidType)) + return false; + if (info.hitInstance.GetLiquidLevel(pos, info, ref level)) + return true; + } + } + } + return false; + } + + public float getHeight(uint mapId, float x, float y, float z, float maxSearchDist) + { + if (isHeightCalcEnabled() && !Global.DisableMgr.IsDisabledFor(DisableType.VMAP, mapId, null, DisableFlags.VmapHeight)) + { + var instanceTree = iInstanceMapTrees.LookupByKey(mapId); + if (instanceTree != null) + { + Vector3 pos = convertPositionToInternalRep(x, y, z); + float height = instanceTree.getHeight(pos, maxSearchDist); + if (float.IsInfinity(height)) + height = MapConst.VMAPInvalidHeightValue; // No height + + return height; + } + } + + return MapConst.VMAPInvalidHeightValue; + } + + public bool getAreaInfo(uint mapId, float x, float y, ref float z, out uint flags, out int adtId, out int rootId, out int groupId) + { + flags = 0; + adtId = 0; + rootId = 0; + groupId = 0; + if (!Global.DisableMgr.IsDisabledFor(DisableType.VMAP, mapId, null, DisableFlags.VmapAreaFlag)) + { + var instanceTree = iInstanceMapTrees.LookupByKey(mapId); + if (instanceTree != null) + { + Vector3 pos = convertPositionToInternalRep(x, y, z); + bool result = instanceTree.getAreaInfo(ref pos, out flags, out adtId, out rootId, out groupId); + // z is not touched by convertPositionToInternalRep(), so just copy + z = pos.Z; + return result; + } + } + + return false; + } + + Vector3 convertPositionToInternalRep(float x, float y, float z) + { + Vector3 pos = new Vector3(); + float mid = 0.5f * 64.0f * 533.33333333f; + pos.X = mid - x; + pos.Y = mid - y; + pos.Z = z; + + return pos; + } + + public void setEnableLineOfSightCalc(bool pVal) { _enableLineOfSightCalc = pVal; } + public void setEnableHeightCalc(bool pVal) { _enableHeightCalc = pVal; } + + public bool isLineOfSightCalcEnabled() { return _enableLineOfSightCalc; } + public bool isHeightCalcEnabled() { return _enableHeightCalc; } + public bool isMapLoadingEnabled() { return _enableLineOfSightCalc || _enableHeightCalc; } + + public bool getObjectHitPos(uint mapId, float x1, float y1, float z1, float x2, float y2, float z2, out float rx, out float ry, out float rz, float modifyDist) + { + if (isLineOfSightCalcEnabled() && !Global.DisableMgr.IsDisabledFor(DisableType.VMAP, mapId, null, DisableFlags.VmapLOS)) + { + var instanceTree = iInstanceMapTrees.LookupByKey(mapId); + if (instanceTree != null) + { + Vector3 resultPos; + Vector3 pos1 = convertPositionToInternalRep(x1, y1, z1); + Vector3 pos2 = convertPositionToInternalRep(x2, y2, z2); + bool result = instanceTree.getObjectHitPos(pos1, pos2, out resultPos, modifyDist); + resultPos = convertPositionToInternalRep(resultPos.X, resultPos.Y, resultPos.Z); + rx = resultPos.X; + ry = resultPos.Y; + rz = resultPos.Z; + return result; + } + } + + rx = x2; + ry = y2; + rz = z2; + + return false; + } + + public bool isInLineOfSight(uint mapId, float x1, float y1, float z1, float x2, float y2, float z2) + { + if (!isLineOfSightCalcEnabled() || Global.DisableMgr.IsDisabledFor(DisableType.VMAP, mapId, null, DisableFlags.VmapLOS)) + return true; + + var instanceTree = iInstanceMapTrees.LookupByKey(mapId); + if (instanceTree != null) + { + Vector3 pos1 = convertPositionToInternalRep(x1, y1, z1); + Vector3 pos2 = convertPositionToInternalRep(x2, y2, z2); + if (pos1 != pos2) + { + return instanceTree.isInLineOfSight(pos1, pos2); + } + } + + return true; + } + + public void unloadMap(uint mapId) + { + var instanceTree = iInstanceMapTrees.LookupByKey(mapId); + if (instanceTree != null) + { + instanceTree.UnloadMap(this); + if (instanceTree.numLoadedTiles() == 0) + { + iInstanceMapTrees.Remove(mapId); + } + } + } + + public void unloadMap(uint mapId, uint x, uint y) + { + var instanceTree = iInstanceMapTrees.LookupByKey(mapId); + if (instanceTree != null) + { + instanceTree.UnloadMapTile(x, y, this); + if (instanceTree.numLoadedTiles() == 0) + { + iInstanceMapTrees.Remove(mapId); + } + } + } + + Dictionary iLoadedModelFiles = new Dictionary(); + Dictionary iInstanceMapTrees = new Dictionary(); + bool _enableLineOfSightCalc; + bool _enableHeightCalc; + } + + public class ManagedModel + { + public ManagedModel() + { + iModel = null; + iRefCount = 0; + } + + public void setModel(WorldModel model) { iModel = model; } + public WorldModel getModel() { return iModel; } + public void incRefCount() { ++iRefCount; } + public int decRefCount() { return --iRefCount; } + + WorldModel iModel; + int iRefCount; + } +} diff --git a/Game/Collision/Maps/MapTree.cs b/Game/Collision/Maps/MapTree.cs new file mode 100644 index 000000000..8ea1bd286 --- /dev/null +++ b/Game/Collision/Maps/MapTree.cs @@ -0,0 +1,387 @@ +/* + * Copyright (C) 2012-2017 CypherCore + * + * 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, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +using Framework.Constants; +using Framework.GameMath; +using System; +using System.Collections.Generic; +using System.Diagnostics.Contracts; +using System.IO; + +namespace Game.Collision +{ + public class LocationInfo + { + public LocationInfo() + { + ground_Z = float.NegativeInfinity; + } + + public ModelInstance hitInstance; + public GroupModel hitModel; + public float ground_Z; + } + + public class AreaInfo + { + public AreaInfo() + { + ground_Z = float.NegativeInfinity; + } + + public bool result; + public float ground_Z; + public uint flags; + public int adtId; + public int rootId; + public int groupId; + } + + public class StaticMapTree + { + public StaticMapTree(uint mapId) + { + iMapID = mapId; + } + + public bool InitMap(string fname, VMapManager vm) + { + Log.outDebug(LogFilter.Maps, "StaticMapTree.InitMap() : initializing StaticMapTree '{0}'", fname); + bool success = false; + if (!File.Exists(fname)) + return false; + char tiled = '0'; + using (BinaryReader reader = new BinaryReader(new FileStream(fname, FileMode.Open, FileAccess.Read))) + { + var magic = reader.ReadStringFromChars(8); + tiled = reader.ReadChar(); + var node = reader.ReadStringFromChars(4); + + if (magic == MapConst.VMapMagic && node == "NODE" && iTree.readFromFile(reader)) + { + iNTreeValues = iTree.primCount(); + iTreeValues = new ModelInstance[iNTreeValues]; + success = reader.ReadStringFromChars(4) == "GOBJ"; + } + + iIsTiled = (tiled == 1); + + // global model spawns + // only non-tiled maps have them, and if so exactly one (so far at least...) + ModelSpawn spawn; + if (!iIsTiled && ModelSpawn.readFromFile(reader, out spawn)) + { + WorldModel model = vm.acquireModelInstance(spawn.name); + Log.outDebug(LogFilter.Maps, "StaticMapTree.InitMap() : loading {0}", spawn.name); + if (model != null) + { + // assume that global model always is the first and only tree value (could be improved...) + iTreeValues[0] = new ModelInstance(spawn, model); + iLoadedSpawns[0] = 1; + } + else + { + success = false; + Log.outError(LogFilter.Server, "StaticMapTree.InitMap() : could not acquire WorldModel for '{0}'", spawn.name); + } + } + } + return success; + } + + public void UnloadMap(VMapManager vm) + { + foreach (var id in iLoadedSpawns) + { + iTreeValues[id.Key].setUnloaded(); + for (uint refCount = 0; refCount < id.Key; ++refCount) + vm.releaseModelInstance(iTreeValues[id.Key].name); + } + iLoadedSpawns.Clear(); + iLoadedTiles.Clear(); + } + + public bool LoadMapTile(uint tileX, uint tileY, VMapManager vm) + { + if (!iIsTiled) + { + // currently, core creates grids for all maps, whether it has terrain tiles or not + // so we need "fake" tile loads to know when we can unload map geometry + iLoadedTiles[packTileID(tileX, tileY)] = false; + return true; + } + if (iTreeValues == null) + { + Log.outError(LogFilter.Server, "StaticMapTree.LoadMapTile() : tree has not been initialized [{0}, {1}]", tileX, tileY); + return false; + } + bool result = true; + + string tilefile = VMapManager.VMapPath + getTileFileName(iMapID, tileX, tileY); + + if (!File.Exists(tilefile)) + { + iLoadedTiles[packTileID(tileX, tileY)] = false; + } + else + { + using (BinaryReader reader = new BinaryReader(new FileStream(tilefile, FileMode.Open, FileAccess.Read))) + { + if (reader.ReadStringFromChars(8) != MapConst.VMapMagic) + return false; + + uint numSpawns = reader.ReadUInt32(); + + for (uint i = 0; i < numSpawns && result; ++i) + { + // read model spawns + ModelSpawn spawn; + result = ModelSpawn.readFromFile(reader, out spawn); + if (result) + { + // acquire model instance + WorldModel model = vm.acquireModelInstance(spawn.name); + if (model == null) + Log.outError(LogFilter.Server, "StaticMapTree.LoadMapTile() : could not acquire WorldModel [{0}, {1}]", tileX, tileY); + + // update tree + uint referencedVal = reader.ReadUInt32(); + if (!iLoadedSpawns.ContainsKey(referencedVal)) + { + iTreeValues[referencedVal] = new ModelInstance(spawn, model); + iLoadedSpawns[referencedVal] = 1; + } + else + ++iLoadedSpawns[referencedVal]; + + } + else + result = false; + } + } + iLoadedTiles[packTileID(tileX, tileY)] = true; + } + return result; + } + + public void UnloadMapTile(uint tileX, uint tileY, VMapManager vm) + { + uint tileID = packTileID(tileX, tileY); + var tile = iLoadedTiles.LookupByKey(tileID); + if (!iLoadedTiles.ContainsKey(tileID)) + { + Log.outError(LogFilter.Server, "StaticMapTree.UnloadMapTile() : trying to unload non-loaded tile - Map:{0} X:{1} Y:{2}", iMapID, tileX, tileY); + return; + } + if (tile) // file associated with tile + { + string tilefile = VMapManager.VMapPath + getTileFileName(iMapID, tileX, tileY); + using (BinaryReader reader = new BinaryReader(new FileStream(tilefile, FileMode.Open, FileAccess.Read))) + { + bool result = true; + if (reader.ReadStringFromChars(8) != MapConst.VMapMagic) + result = false; + + uint numSpawns = reader.ReadUInt32(); + for (uint i = 0; i < numSpawns && result; ++i) + { + // read model spawns + ModelSpawn spawn; + result = ModelSpawn.readFromFile(reader, out spawn); + if (result) + { + // release model instance + vm.releaseModelInstance(spawn.name); + + // update tree + uint referencedNode = reader.ReadUInt32(); + + + if (!iLoadedSpawns.ContainsKey(referencedNode)) + Log.outError(LogFilter.Server, "StaticMapTree.UnloadMapTile() : trying to unload non-referenced model '{0}' (ID:{1})", spawn.name, spawn.ID); + else if (--iLoadedSpawns[referencedNode] == 0) + { + iTreeValues[referencedNode].setUnloaded(); + iLoadedSpawns.Remove(referencedNode); + } + + } + } + } + } + iLoadedTiles.Remove(tileID); + } + + static uint packTileID(uint tileX, uint tileY) { return tileX << 16 | tileY; } + static void unpackTileID(uint ID, uint tileX, uint tileY) { tileX = ID >> 16; tileY = ID & 0xFF; } + public static bool CanLoadMap(string vmapPath, uint mapID, uint tileX, uint tileY) + { + string fullname = vmapPath + VMapManager.getMapFileName(mapID); + bool success = true; + if (!File.Exists(fullname)) + return false; + + using (BinaryReader reader = new BinaryReader(new FileStream(fullname, FileMode.Open, FileAccess.Read))) + { + if (reader.ReadStringFromChars(8) != MapConst.VMapMagic) + return false; + char tiled = reader.ReadChar(); + if (tiled == 1) + { + string tilefile = vmapPath + getTileFileName(mapID, tileX, tileY); + if (!File.Exists(tilefile)) + return false; + + using (BinaryReader reader1 = new BinaryReader(new FileStream(tilefile, FileMode.Open, FileAccess.Read))) + { + if (reader1.ReadStringFromChars(8) != MapConst.VMapMagic) + success = false; + } + } + } + return success; + } + public static string getTileFileName(uint mapID, uint tileX, uint tileY) + { + return string.Format("{0:D4}_{1:D2}_{2:D2}.vmtile", mapID, tileY, tileX); + } + + public bool getAreaInfo(ref Vector3 pos, out uint flags, out int adtId, out int rootId, out int groupId) + { + flags = 0; + adtId = 0; + rootId = 0; + groupId = 0; + + AreaInfoCallback intersectionCallBack = new AreaInfoCallback(iTreeValues); + iTree.intersectPoint(pos, intersectionCallBack); + if (intersectionCallBack.aInfo.result) + { + flags = intersectionCallBack.aInfo.flags; + adtId = intersectionCallBack.aInfo.adtId; + rootId = intersectionCallBack.aInfo.rootId; + groupId = intersectionCallBack.aInfo.groupId; + pos.Z = intersectionCallBack.aInfo.ground_Z; + return true; + } + return false; + } + + public bool GetLocationInfo(Vector3 pos, LocationInfo info) + { + LocationInfoCallback intersectionCallBack = new LocationInfoCallback(iTreeValues, info); + iTree.intersectPoint(pos, intersectionCallBack); + return intersectionCallBack.result; + } + + public float getHeight(Vector3 pPos, float maxSearchDist) + { + float height = float.PositiveInfinity; + Vector3 dir = new Vector3(0, 0, -1); + Ray ray = new Ray(pPos, dir); // direction with length of 1 + float maxDist = maxSearchDist; + if (getIntersectionTime(ray, ref maxDist, false)) + height = pPos.Z - maxDist; + + return height; + } + bool getIntersectionTime(Ray pRay, ref float pMaxDist, bool pStopAtFirstHit) + { + float distance = pMaxDist; + MapRayCallback intersectionCallBack = new MapRayCallback(iTreeValues); + iTree.intersectRay(pRay, intersectionCallBack, ref distance, pStopAtFirstHit); + if (intersectionCallBack.didHit()) + pMaxDist = distance; + return intersectionCallBack.didHit(); + } + + public bool getObjectHitPos(Vector3 pPos1, Vector3 pPos2, out Vector3 pResultHitPos, float pModifyDist) + { + bool result = false; + float maxDist = (pPos2 - pPos1).magnitude(); + // valid map coords should *never ever* produce float overflow, but this would produce NaNs too + Contract.Assert(maxDist < float.MaxValue); + // prevent NaN values which can cause BIH intersection to enter infinite loop + if (maxDist < 1e-10f) + { + pResultHitPos = pPos2; + return false; + } + Vector3 dir = (pPos2 - pPos1) / maxDist; // direction with length of 1 + Ray ray = new Ray(pPos1, dir); + float dist = maxDist; + if (getIntersectionTime(ray, ref dist, false)) + { + pResultHitPos = pPos1 + dir * dist; + if (pModifyDist < 0) + { + if ((pResultHitPos - pPos1).magnitude() > -pModifyDist) + { + pResultHitPos = pResultHitPos + dir * pModifyDist; + } + else + { + pResultHitPos = pPos1; + } + } + else + { + pResultHitPos = pResultHitPos + dir * pModifyDist; + } + result = true; + } + else + { + pResultHitPos = pPos2; + result = false; + } + return result; + } + + public bool isInLineOfSight(Vector3 pos1, Vector3 pos2) + { + float maxDist = (pos2 - pos1).magnitude(); + // return false if distance is over max float, in case of cheater teleporting to the end of the universe + if (maxDist == float.MaxValue || + maxDist == float.PositiveInfinity) + return false; + + // valid map coords should *never ever* produce float overflow, but this would produce NaNs too + Contract.Assert(maxDist < float.MaxValue); + // prevent NaN values which can cause BIH intersection to enter infinite loop + if (maxDist < 1e-10f) + return true; + // direction with length of 1 + Ray ray = new Ray(pos1, (pos2 - pos1) / maxDist); + if (getIntersectionTime(ray, ref maxDist, true)) + return false; + + return true; + } + + public int numLoadedTiles() { return iLoadedTiles.Count; } + + uint iMapID; + bool iIsTiled; + BIH iTree = new BIH(); + ModelInstance[] iTreeValues; + uint iNTreeValues; + + Dictionary iLoadedTiles = new Dictionary(); + Dictionary iLoadedSpawns = new Dictionary(); + } +} diff --git a/Game/Collision/Models/GameObjectModel.cs b/Game/Collision/Models/GameObjectModel.cs new file mode 100644 index 000000000..af40f0a85 --- /dev/null +++ b/Game/Collision/Models/GameObjectModel.cs @@ -0,0 +1,213 @@ +/* + * Copyright (C) 2012-2017 CypherCore + * + * 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, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +using Framework.GameMath; +using System; +using System.Collections.Generic; +using System.IO; + +namespace Game.Collision +{ + public class StaticModelList + { + public static Dictionary models = new Dictionary(); + } + + public class GameObjectModelOwnerBase + { + public virtual bool IsSpawned() { return false; } + public virtual uint GetDisplayId() { return 0; } + public virtual bool IsInPhase(List phases) { return false; } + public virtual Vector3 GetPosition() { return Vector3.Zero; } + public virtual float GetOrientation() { return 0.0f; } + public virtual float GetScale() { return 1.0f; } + public virtual void DebugVisualizeCorner(Vector3 corner) { } + } + + public class GameObjectModel : IModel + { + bool initialize(GameObjectModelOwnerBase modelOwner) + { + var it = StaticModelList.models.LookupByKey(modelOwner.GetDisplayId()); + if (it == null) + return false; + + AxisAlignedBox mdl_box = new AxisAlignedBox(it.bound); + // ignore models with no bounds + if (mdl_box == AxisAlignedBox.Zero()) + { + Log.outError(LogFilter.Server, "GameObject model {0} has zero bounds, loading skipped", it.name); + return false; + } + + iModel = Global.VMapMgr.acquireModelInstance(it.name); + + if (iModel == null) + return false; + + name = it.name; + iPos = modelOwner.GetPosition(); + iScale = modelOwner.GetScale(); + iInvScale = 1.0f / iScale; + + Matrix3 iRotation = Matrix3.fromEulerAnglesZYX(modelOwner.GetOrientation(), 0, 0); + iInvRot = iRotation.inverse(); + // transform bounding box: + mdl_box = new AxisAlignedBox(mdl_box.Lo * iScale, mdl_box.Hi * iScale); + AxisAlignedBox rotated_bounds = new AxisAlignedBox(); + for (int i = 0; i < 8; ++i) + rotated_bounds.merge(iRotation * mdl_box.corner(i)); + + iBound = rotated_bounds + iPos; + owner = modelOwner; + return true; + } + + public static GameObjectModel Create(GameObjectModelOwnerBase modelOwner) + { + GameObjectModel mdl = new GameObjectModel(); + if (!mdl.initialize(modelOwner)) + return null; + + return mdl; + } + + public bool intersectRay(Ray ray, ref float maxDist, bool stopAtFirstHit, List phases) + { + if (!isCollisionEnabled() || !owner.IsSpawned()) + return false; + + if (!owner.IsInPhase(phases)) + return false; + + float time = ray.intersectionTime(iBound); + if (time == float.PositiveInfinity) + return false; + + // child bounds are defined in object space: + Vector3 p = iInvRot * (ray.Origin - iPos) * iInvScale; + Ray modRay = new Ray(p, iInvRot * ray.Direction); + float distance = maxDist * iInvScale; + bool hit = iModel.IntersectRay(modRay, ref distance, stopAtFirstHit); + if (hit) + { + distance *= iScale; + maxDist = distance; + } + return hit; + } + + public bool UpdatePosition() + { + if (iModel == null) + return false; + + var it = StaticModelList.models.LookupByKey(owner.GetDisplayId()); + if (it == null) + return false; + + AxisAlignedBox mdl_box = new AxisAlignedBox(it.bound); + // ignore models with no bounds + if (mdl_box == AxisAlignedBox.Zero()) + { + Log.outError(LogFilter.Server, "GameObject model {0} has zero bounds, loading skipped", it.name); + return false; + } + + iPos = owner.GetPosition(); + + Matrix3 iRotation = Matrix3.fromEulerAnglesZYX(owner.GetOrientation(), 0, 0); + iInvRot = iRotation.inverse(); + // transform bounding box: + mdl_box = new AxisAlignedBox(mdl_box.Lo * iScale, mdl_box.Hi * iScale); + AxisAlignedBox rotated_bounds = new AxisAlignedBox(); + for (int i = 0; i < 8; ++i) + rotated_bounds.merge(iRotation * mdl_box.corner(i)); + + iBound = rotated_bounds + iPos; + + return true; + } + + public override Vector3 getPosition() { return iPos; } + public override AxisAlignedBox getBounds() { return iBound; } + + public void enableCollision(bool enable) { _collisionEnabled = enable; } + bool isCollisionEnabled() { return _collisionEnabled; } + + public static void LoadGameObjectModelList() + { + uint oldMSTime = Time.GetMSTime(); + var filename = Global.WorldMgr.GetDataPath() + "/vmaps/GameObjectModels.dtree"; + if (!File.Exists(filename)) + { + Log.outError(LogFilter.Server, "Unable to open '{0}' file.", filename); + return; + } + try + { + using (BinaryReader reader = new BinaryReader(new FileStream(filename, FileMode.Open, FileAccess.Read))) + { + uint name_length, displayId; + string name; + while (true) + { + if (reader.BaseStream.Position >= reader.BaseStream.Length) + break; + + Vector3 v1, v2; + displayId = reader.ReadUInt32(); + name_length = reader.ReadUInt32(); + name = reader.ReadString((int)name_length); + v1 = reader.ReadStruct(); + v2 = reader.ReadStruct(); + + StaticModelList.models.Add(displayId, new GameobjectModelData(name, new AxisAlignedBox(v1, v2))); + } + } + } + catch (EndOfStreamException ex) + { + Log.outException(ex); + } + + Log.outInfo(LogFilter.ServerLoading, "Loaded {0} GameObject models in {1} ms", StaticModelList.models.Count, Time.GetMSTimeDiffToNow(oldMSTime)); + } + + string name; + bool _collisionEnabled; + AxisAlignedBox iBound; + Matrix3 iInvRot; + Vector3 iPos; + float iInvScale; + float iScale; + WorldModel iModel; + GameObjectModelOwnerBase owner; + } + public class GameobjectModelData + { + public GameobjectModelData(string name_, AxisAlignedBox box) + { + bound = box; + name = name_; + } + + public AxisAlignedBox bound; + public string name; + } +} + diff --git a/Game/Collision/Models/IModel.cs b/Game/Collision/Models/IModel.cs new file mode 100644 index 000000000..930c3e0b0 --- /dev/null +++ b/Game/Collision/Models/IModel.cs @@ -0,0 +1,27 @@ +/* + * Copyright (C) 2012-2017 CypherCore + * + * 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, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +using Framework.GameMath; + +namespace Game.Collision +{ + public class IModel + { + public virtual Vector3 getPosition() { return default(Vector3); } + public virtual AxisAlignedBox getBounds() { return default(AxisAlignedBox); } + } +} diff --git a/Game/Collision/Models/ModelInstance.cs b/Game/Collision/Models/ModelInstance.cs new file mode 100644 index 000000000..b49261c75 --- /dev/null +++ b/Game/Collision/Models/ModelInstance.cs @@ -0,0 +1,201 @@ +/* + * Copyright (C) 2012-2017 CypherCore + * + * 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, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +using Framework.GameMath; +using System; +using System.IO; + +namespace Game.Collision +{ + public enum ModelFlags + { + M2 = 1, + WorldSpawn = 1 << 1, + HasBound = 1 << 2 + } + + public class ModelSpawn + { + public ModelSpawn() { } + public ModelSpawn(ModelSpawn spawn) + { + flags = spawn.flags; + adtId = spawn.adtId; + ID = spawn.ID; + iPos = spawn.iPos; + iRot = spawn.iRot; + iScale = spawn.iScale; + iBound = spawn.iBound; + name = spawn.name; + } + + public static bool readFromFile(BinaryReader reader, out ModelSpawn spawn) + { + spawn = new ModelSpawn(); + + spawn.flags = reader.ReadUInt32(); + spawn.adtId = reader.ReadUInt16(); + spawn.ID = reader.ReadUInt32(); + spawn.iPos = reader.ReadStruct(); + spawn.iRot = reader.ReadStruct(); + spawn.iScale = reader.ReadSingle(); + + bool has_bound = Convert.ToBoolean(spawn.flags & (uint)ModelFlags.HasBound); + if (has_bound) // only WMOs have bound in MPQ, only available after computation + { + Vector3 bLow = reader.ReadStruct(); + Vector3 bHigh = reader.ReadStruct(); + spawn.iBound = new AxisAlignedBox(bLow, bHigh); + } + uint nameLen = reader.ReadUInt32(); + + spawn.name = reader.ReadString((int)nameLen); + return true; + } + + public uint flags; + public ushort adtId; + public uint ID; + public Vector3 iPos; + public Vector3 iRot; + public float iScale; + public AxisAlignedBox iBound; + public string name; + } + + public class ModelInstance : ModelSpawn + { + public ModelInstance() + { + iInvScale = 0.0f; + iModel = null; + } + public ModelInstance(ModelSpawn spawn, WorldModel model) + : base(spawn) + { + iModel = model; + + iInvRot = Matrix3.fromEulerAnglesZYX(MathFunctions.PI * iRot.Y / 180.0f, MathFunctions.PI * iRot.X / 180.0f, MathFunctions.PI * iRot.Z / 180.0f).inverse(); + iInvScale = 1.0f / iScale; + } + + public bool intersectRay(Ray pRay, ref float pMaxDist, bool pStopAtFirstHit) + { + if (iModel == null) + return false; + + float time = pRay.intersectionTime(iBound); + if (float.IsInfinity(time)) + return false; + + // child bounds are defined in object space: + Vector3 p = iInvRot * (pRay.Origin - iPos) * iInvScale; + Ray modRay = new Ray(p, iInvRot * pRay.Direction); + float distance = pMaxDist * iInvScale; + bool hit = iModel.IntersectRay(modRay, ref distance, pStopAtFirstHit); + if (hit) + { + distance *= iScale; + pMaxDist = distance; + } + return hit; + } + + public void intersectPoint(Vector3 p, AreaInfo info) + { + if (iModel == null) + return; + + // M2 files don't contain area info, only WMO files + if (Convert.ToBoolean(flags & (uint)ModelFlags.M2)) + return; + if (!iBound.contains(p)) + return; + // child bounds are defined in object space: + Vector3 pModel = iInvRot * (p - iPos) * iInvScale; + Vector3 zDirModel = iInvRot * new Vector3(0.0f, 0.0f, -1.0f); + float zDist; + if (iModel.IntersectPoint(pModel, zDirModel, out zDist, info)) + { + Vector3 modelGround = pModel + zDist * zDirModel; + // Transform back to world space. Note that: + // Mat * vec == vec * Mat.transpose() + // and for rotation matrices: Mat.inverse() == Mat.transpose() + float world_Z = ((modelGround * iInvRot) * iScale + iPos).Z; + if (info.ground_Z < world_Z) + { + info.ground_Z = world_Z; + info.adtId = adtId; + } + } + } + + public bool GetLiquidLevel(Vector3 p, LocationInfo info, ref float liqHeight) + { + // child bounds are defined in object space: + Vector3 pModel = iInvRot * (p - iPos) * iInvScale; + //Vector3 zDirModel = iInvRot * Vector3(0.f, 0.f, -1.f); + float zDist; + if (info.hitModel.GetLiquidLevel(pModel, out zDist)) + { + // calculate world height (zDist in model coords): + // assume WMO not tilted (wouldn't make much sense anyway) + liqHeight = zDist * iScale + iPos.Z; + return true; + } + return false; + } + + public bool GetLocationInfo(Vector3 p, LocationInfo info) + { + if (iModel == null) + return false; + + // M2 files don't contain area info, only WMO files + if (Convert.ToBoolean(flags & (uint)ModelFlags.M2)) + return false; + if (!iBound.contains(p)) + return false; + // child bounds are defined in object space: + Vector3 pModel = iInvRot * (p - iPos) * iInvScale; + Vector3 zDirModel = iInvRot * new Vector3(0.0f, 0.0f, -1.0f); + float zDist; + if (iModel.GetLocationInfo(pModel, zDirModel, out zDist, info)) + { + Vector3 modelGround = pModel + zDist * zDirModel; + // Transform back to world space. Note that: + // Mat * vec == vec * Mat.transpose() + // and for rotation matrices: Mat.inverse() == Mat.transpose() + float world_Z = ((modelGround * iInvRot) * iScale + iPos).Z; + if (info.ground_Z < world_Z) // hm...could it be handled automatically with zDist at intersection? + { + info.ground_Z = world_Z; + info.hitInstance = this; + return true; + } + } + return false; + } + + + public void setUnloaded() { iModel = null; } + + Matrix3 iInvRot; + float iInvScale; + WorldModel iModel; + } +} diff --git a/Game/Collision/Models/WorldModel.cs b/Game/Collision/Models/WorldModel.cs new file mode 100644 index 000000000..af5f87dc6 --- /dev/null +++ b/Game/Collision/Models/WorldModel.cs @@ -0,0 +1,405 @@ +/* + * Copyright (C) 2012-2017 CypherCore + * + * 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, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +using Framework.Constants; +using Framework.GameMath; +using System; +using System.Collections.Generic; +using System.IO; + +namespace Game.Collision +{ + public struct MeshTriangle + { + public MeshTriangle(uint na, uint nb, uint nc) + { + idx0 = na; + idx1 = nb; + idx2 = nc; + } + + public uint idx0; + public uint idx1; + public uint idx2; + } + + public class WmoLiquid + { + public WmoLiquid() { } + public WmoLiquid(uint width, uint height, Vector3 corner, uint type) + { + iTilesX = width; + iTilesY = height; + iCorner = corner; + iType = type; + + iHeight = new float[(width + 1) * (height + 1)]; + iFlags = new byte[width * height]; + } + public WmoLiquid(WmoLiquid other) + { + if (this == other) + return; + + iTilesX = other.iTilesX; + iTilesY = other.iTilesY; + iCorner = other.iCorner; + iType = other.iType; + if (other.iHeight != null) + { + iHeight = new float[(iTilesX + 1) * (iTilesY + 1)]; + Buffer.BlockCopy(other.iHeight, 0, iHeight, 0, (int)((iTilesX + 1) * (iTilesY + 1))); + } + else + iHeight = null; + if (other.iFlags != null) + { + iFlags = new byte[iTilesX * iTilesY]; + Buffer.BlockCopy(other.iFlags, 0, iFlags, 0, (int)(iTilesX * iTilesY)); + } + else + iFlags = null; + } + + public bool GetLiquidHeight(Vector3 pos, out float liqHeight) + { + liqHeight = 0f; + float tx_f = (pos.X - iCorner.X) / MapConst.LiquidTileSize; + uint tx = (uint)tx_f; + if (tx_f < 0.0f || tx >= iTilesX) + return false; + float ty_f = (pos.Y - iCorner.Y) / MapConst.LiquidTileSize; + uint ty = (uint)ty_f; + if (ty_f < 0.0f || ty >= iTilesY) + return false; + + // check if tile shall be used for liquid level + // checking for 0x08 *might* be enough, but disabled tiles always are 0x?F: + if ((iFlags[tx + ty * iTilesX] & 0x0F) == 0x0F) + return false; + + // (dx, dy) coordinates inside tile, in [0, 1]^2 + float dx = tx_f - tx; + float dy = ty_f - ty; + + uint rowOffset = iTilesX + 1; + if (dx > dy) // case (a) + { + float sx = iHeight[tx + 1 + ty * rowOffset] - iHeight[tx + ty * rowOffset]; + float sy = iHeight[tx + 1 + (ty + 1) * rowOffset] - iHeight[tx + 1 + ty * rowOffset]; + liqHeight = iHeight[tx + ty * rowOffset] + dx * sx + dy * sy; + } + else // case (b) + { + float sx = iHeight[tx + 1 + (ty + 1) * rowOffset] - iHeight[tx + (ty + 1) * rowOffset]; + float sy = iHeight[tx + (ty + 1) * rowOffset] - iHeight[tx + ty * rowOffset]; + liqHeight = iHeight[tx + ty * rowOffset] + dx * sx + dy * sy; + } + return true; + } + + bool writeToFile(BinaryWriter writer) + { + writer.Write(iTilesX); + writer.Write(iTilesY); + + writer.Write(iCorner.X); + writer.Write(iCorner.Y); + writer.Write(iCorner.Z); + writer.Write(iType); + + uint size = (iTilesX + 1) * (iTilesY + 1); + for (var i = 0; i < size; i++) + writer.Write(iHeight[i]); + + size = iTilesX * iTilesY; + for (var i = 0; i < size; i++) + writer.Write(iFlags[0]); + + return true; + } + + public static WmoLiquid readFromFile(BinaryReader reader) + { + WmoLiquid liquid = new WmoLiquid(); + + liquid.iTilesX = reader.ReadUInt32(); + liquid.iTilesY = reader.ReadUInt32(); + liquid.iCorner = reader.ReadStruct(); + liquid.iType = reader.ReadUInt32(); + + uint size = (liquid.iTilesX + 1) * (liquid.iTilesY + 1); + liquid.iHeight = new float[size]; + for (var i = 0; i < size; i++) + liquid.iHeight[i] = reader.ReadSingle(); + + size = liquid.iTilesX * liquid.iTilesY; + liquid.iFlags = new byte[size]; + for (var i = 0; i < size; i++) + liquid.iFlags[i] = reader.ReadByte(); + + return liquid; + } + + public uint GetLiquidType() { return iType; } + float[] GetHeightStorage() { return iHeight; } + byte[] GetFlagsStorage() { return iFlags; } + + uint iTilesX; + uint iTilesY; + Vector3 iCorner; + uint iType; + float[] iHeight; + byte[] iFlags; + } + + public class GroupModel : IModel + { + public GroupModel() + { + iLiquid = null; + } + public GroupModel(GroupModel other) + { + iBound = other.iBound; + iMogpFlags = other.iMogpFlags; + iGroupWMOID = other.iGroupWMOID; + vertices = other.vertices; + triangles = other.triangles; + meshTree = other.meshTree; + iLiquid = null; + + if (other.iLiquid != null) + iLiquid = new WmoLiquid(other.iLiquid); + } + public GroupModel(uint mogpFlags, uint groupWMOID, AxisAlignedBox bound) + { + iBound = bound; + iMogpFlags = mogpFlags; + iGroupWMOID = groupWMOID; + iLiquid = null; + } + + void setLiquidData(WmoLiquid liquid) + { + iLiquid = liquid; + liquid = null; + } + + public bool readFromFile(BinaryReader reader) + { + uint chunkSize = 0; + uint count = 0; + triangles.Clear(); + vertices.Clear(); + iLiquid = null; + + iBound = reader.ReadStruct(); + iMogpFlags = reader.ReadUInt32(); + iGroupWMOID = reader.ReadUInt32(); + + // read vertices + if (reader.ReadStringFromChars(4) != "VERT") + return false; + + chunkSize = reader.ReadUInt32(); + count = reader.ReadUInt32(); + if (count == 0) + return false; + + for (var i = 0; i < count; ++i) + vertices.Add(reader.ReadStruct()); + + // read triangle mesh + if (reader.ReadStringFromChars(4) != "TRIM") + return false; + + chunkSize = reader.ReadUInt32(); + count = reader.ReadUInt32(); + + for (var i = 0; i < count; ++i) + triangles.Add(reader.ReadStruct()); + + // read mesh BIH + if (reader.ReadStringFromChars(4) != "MBIH") + return false; + meshTree.readFromFile(reader); + + // write liquid data + if (reader.ReadStringFromChars(4).ToString() != "LIQU") + return false; + chunkSize = reader.ReadUInt32(); + if (chunkSize > 0) + iLiquid = WmoLiquid.readFromFile(reader); + + return true; + } + + public bool IntersectRay(Ray ray, ref float distance, bool stopAtFirstHit) + { + if (triangles.Empty()) + return false; + + GModelRayCallback callback = new GModelRayCallback(triangles, vertices); + meshTree.intersectRay(ray, callback, ref distance, stopAtFirstHit); + return callback.hit; + } + + public bool IsInsideObject(Vector3 pos, Vector3 down, out float z_dist) + { + z_dist = 0f; + if (triangles.Empty() || !iBound.contains(pos)) + return false; + GModelRayCallback callback = new GModelRayCallback(triangles, vertices); + Vector3 rPos = pos - 0.1f * down; + float dist = float.PositiveInfinity; + Ray ray = new Ray(rPos, down); + bool hit = IntersectRay(ray, ref dist, false); + if (hit) + z_dist = dist - 0.1f; + return hit; + } + + public bool GetLiquidLevel(Vector3 pos, out float liqHeight) + { + liqHeight = 0f; + if (iLiquid != null) + return iLiquid.GetLiquidHeight(pos, out liqHeight); + return false; + } + + public uint GetLiquidType() + { + if (iLiquid != null) + return iLiquid.GetLiquidType(); + return 0; + } + + public override AxisAlignedBox getBounds() { return iBound; } + + public uint GetMogpFlags() { return iMogpFlags; } + + public uint GetWmoID() { return iGroupWMOID; } + + AxisAlignedBox iBound; + uint iMogpFlags; + uint iGroupWMOID; + List vertices = new List(); + List triangles = new List(); + BIH meshTree = new BIH(); + WmoLiquid iLiquid; + } + + public class WorldModel : IModel + { + public WorldModel() + { + RootWMOID = 0; + } + + public bool IntersectRay(Ray ray, ref float distance, bool stopAtFirstHit) + { + // small M2 workaround, maybe better make separate class with virtual intersection funcs + // in any case, there's no need to use a bound tree if we only have one submodel + if (groupModels.Count == 1) + return groupModels[0].IntersectRay(ray, ref distance, stopAtFirstHit); + + WModelRayCallBack isc = new WModelRayCallBack(groupModels); + groupTree.intersectRay(ray, isc, ref distance, stopAtFirstHit); + return isc.hit; + } + + public bool IntersectPoint(Vector3 p, Vector3 down, out float dist, AreaInfo info) + { + dist = 0f; + if (groupModels.Empty()) + return false; + + WModelAreaCallback callback = new WModelAreaCallback(groupModels, down); + groupTree.intersectPoint(p, callback); + if (callback.hit != null) + { + info.rootId = (int)RootWMOID; + info.groupId = (int)callback.hit.GetWmoID(); + info.flags = callback.hit.GetMogpFlags(); + info.result = true; + dist = callback.zDist; + return true; + } + return false; + } + + public bool GetLocationInfo(Vector3 p, Vector3 down, out float dist, LocationInfo info) + { + dist = 0f; + if (groupModels.Empty()) + return false; + + WModelAreaCallback callback = new WModelAreaCallback(groupModels, down); + groupTree.intersectPoint(p, callback); + if (callback.hit != null) + { + info.hitModel = callback.hit; + dist = callback.zDist; + return true; + } + return false; + } + + public bool readFile(string filename) + { + if (!File.Exists(filename)) + return false; + using (BinaryReader reader = new BinaryReader(new FileStream(filename, FileMode.Open, FileAccess.Read))) + { + uint chunkSize = 0; + uint count = 0; + if (reader.ReadStringFromChars(8) != MapConst.VMapMagic) + return false; + + if (reader.ReadStringFromChars(4) != "WMOD") + return false; + chunkSize = reader.ReadUInt32(); + RootWMOID = reader.ReadUInt32(); + + // read group models + if (reader.ReadStringFromChars(4) != "GMOD") + return false; + + count = reader.ReadUInt32(); + for (var i = 0; i < count; ++i) + { + GroupModel group = new GroupModel(); + group.readFromFile(reader); + groupModels.Add(group); + } + + // read group BIH + if (reader.ReadStringFromChars(4) != "GBIH") + return false; + groupTree.readFromFile(reader); + + + return true; + } + } + + List groupModels = new List(); + BIH groupTree = new BIH(); + uint RootWMOID; + } +} diff --git a/Game/Collision/RegularGrid2D.cs b/Game/Collision/RegularGrid2D.cs new file mode 100644 index 000000000..994367ebf --- /dev/null +++ b/Game/Collision/RegularGrid2D.cs @@ -0,0 +1,211 @@ +/* + * Copyright (C) 2012-2017 CypherCore + * + * 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, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +using Framework.GameMath; +using System; +using System.Collections.Generic; +using System.Diagnostics.Contracts; + +namespace Game.Collision +{ + public class RegularGrid2D where T : IModel where Node : BIHWrap, new() + { + public const int CELL_NUMBER = 64; + public const float HGRID_MAP_SIZE = (533.33333f * 64.0f); // shouldn't be changed + public const float CELL_SIZE = HGRID_MAP_SIZE / CELL_NUMBER; + + public RegularGrid2D() + { + for (int x = 0; x < CELL_NUMBER; ++x) + nodes[x] = new Node[CELL_NUMBER]; + } + + public virtual void insert(T value) + { + Vector3 pos = value.getPosition(); + Node node = getGridFor(pos.X, pos.Y); + node.insert(value); + memberTable.Add(value, node); + } + + public virtual void remove(T value) + { + memberTable[value].remove(value); + // Remove the member + memberTable.Remove(value); + } + + public virtual void balance() + { + for (int x = 0; x < CELL_NUMBER; ++x) + { + for (int y = 0; y < CELL_NUMBER; ++y) + { + Node n = nodes[x][y]; + if (n != null) + n.balance(); + } + } + } + + public bool contains(T value) { return memberTable.ContainsKey(value); } + public int size() { return memberTable.Count; } + + public struct Cell + { + public int x, y; + public static bool operator ==(Cell c1, Cell c2) { return c1.x == c2.x && c1.y == c2.y; } + public static bool operator !=(Cell c1, Cell c2) { return !(c1 == c2); } + + public override bool Equals(object obj) + { + return base.Equals(obj); + } + public override int GetHashCode() + { + return base.GetHashCode(); + } + + public static Cell ComputeCell(float fx, float fy) + { + Cell c = new Cell(); + c.x = (int)(fx * (1.0f / CELL_SIZE) + (CELL_NUMBER / 2)); + c.y = (int)(fy * (1.0f / CELL_SIZE) + (CELL_NUMBER / 2)); + return c; + } + + public bool isValid() { return x >= 0 && x < CELL_NUMBER && y >= 0 && y < CELL_NUMBER; } + } + + Node getGridFor(float fx, float fy) + { + Cell c = Cell.ComputeCell(fx, fy); + return getGrid(c.x, c.y); + } + + Node getGrid(int x, int y) + { + Contract.Assert(x < CELL_NUMBER && y < CELL_NUMBER); + if (nodes[x][y] == null) + nodes[x][y] = new Node(); + return nodes[x][y]; + } + + public void intersectRay(Ray ray, WorkerCallback intersectCallback, ref float max_dist) + { + intersectRay(ray, intersectCallback, ref max_dist, ray.Origin + ray.Direction * max_dist); + } + + public void intersectRay(Ray ray, WorkerCallback intersectCallback, ref float max_dist, Vector3 end) + { + Cell cell = Cell.ComputeCell(ray.Origin.X, ray.Origin.Y); + if (!cell.isValid()) + return; + + Cell last_cell = Cell.ComputeCell(end.X, end.Y); + + if (cell == last_cell) + { + Node node = nodes[cell.x][cell.y]; + if (node != null) + node.intersectRay(ray, intersectCallback, ref max_dist); + return; + } + + float voxel = CELL_SIZE; + float kx_inv = ray.invDirection().X, bx = ray.Origin.X; + float ky_inv = ray.invDirection().Y, by = ray.Origin.Y; + + int stepX, stepY; + float tMaxX, tMaxY; + if (kx_inv >= 0) + { + stepX = 1; + float x_border = (cell.x + 1) * voxel; + tMaxX = (x_border - bx) * kx_inv; + } + else + { + stepX = -1; + float x_border = (cell.x - 1) * voxel; + tMaxX = (x_border - bx) * kx_inv; + } + + if (ky_inv >= 0) + { + stepY = 1; + float y_border = (cell.y + 1) * voxel; + tMaxY = (y_border - by) * ky_inv; + } + else + { + stepY = -1; + float y_border = (cell.y - 1) * voxel; + tMaxY = (y_border - by) * ky_inv; + } + + float tDeltaX = voxel * Math.Abs(kx_inv); + float tDeltaY = voxel * Math.Abs(ky_inv); + do + { + Node node = nodes[cell.x][cell.y]; + if (node != null) + { + node.intersectRay(ray, intersectCallback, ref max_dist); + } + if (cell == last_cell) + break; + if (tMaxX < tMaxY) + { + tMaxX += tDeltaX; + cell.x += stepX; + } + else + { + tMaxY += tDeltaY; + cell.y += stepY; + } + } while (cell.isValid()); + } + + void intersectPoint(Vector3 point, WorkerCallback intersectCallback) + { + Cell cell = Cell.ComputeCell(point.X, point.Y); + if (!cell.isValid()) + return; + + Node node = nodes[cell.x][cell.y]; + if (node != null) + node.intersectPoint(point, intersectCallback); + } + + // Optimized verson of intersectRay function for rays with vertical directions + public void intersectZAllignedRay(Ray ray, WorkerCallback intersectCallback, ref float max_dist) + { + Cell cell = Cell.ComputeCell(ray.Origin.X, ray.Origin.Y); + if (!cell.isValid()) + return; + + Node node = nodes[cell.x][cell.y]; + if (node != null) + node.intersectRay(ray, intersectCallback, ref max_dist); + } + + Dictionary memberTable = new Dictionary(); + Node[][] nodes = new Node[CELL_NUMBER][]; + } +} diff --git a/Game/Combat/Events.cs b/Game/Combat/Events.cs new file mode 100644 index 000000000..b59bcbe5d --- /dev/null +++ b/Game/Combat/Events.cs @@ -0,0 +1,161 @@ +/* + * Copyright (C) 2012-2017 CypherCore + * + * 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, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +using System; + +namespace Game.Combat +{ + public class UnitBaseEvent + { + public UnitBaseEvent(UnitEventTypes pType) + { + iType = pType; + } + public UnitEventTypes getType() + { + return iType; + } + bool matchesTypeMask(uint pMask) + { + return Convert.ToBoolean((uint)iType & pMask); + } + + void setType(UnitEventTypes pType) + { + iType = pType; + } + + UnitEventTypes iType; + } + + public class ThreatRefStatusChangeEvent : UnitBaseEvent + { + public ThreatRefStatusChangeEvent(UnitEventTypes pType) + : base(pType) + { + iHostileReference = null; + } + public ThreatRefStatusChangeEvent(UnitEventTypes pType, HostileReference pHostileReference) + : base(pType) + { + iHostileReference = pHostileReference; + } + public ThreatRefStatusChangeEvent(UnitEventTypes pType, HostileReference pHostileReference, float pValue) + : base(pType) + { + iHostileReference = pHostileReference; + iFValue = pValue; + } + public ThreatRefStatusChangeEvent(UnitEventTypes pType, HostileReference pHostileReference, bool pValue) + : base(pType) + { + iHostileReference = pHostileReference; + iBValue = pValue; + } + + public float getFValue() + { + return iFValue; + } + + bool getBValue() + { + return iBValue; + } + + void setBValue(bool pValue) + { + iBValue = pValue; + } + + public HostileReference getReference() + { + return iHostileReference; + } + + public void setThreatManager(ThreatManager pThreatManager) + { + iThreatManager = pThreatManager; + } + + ThreatManager getThreatManager() + { + return iThreatManager; + } + + float iFValue; + bool iBValue; + HostileReference iHostileReference; + ThreatManager iThreatManager; + } + + public class ThreatManagerEvent : ThreatRefStatusChangeEvent + { + ThreatManagerEvent(UnitEventTypes pType) + : base(pType) + { + iThreatContainer = null; + } + ThreatManagerEvent(UnitEventTypes pType, HostileReference pHostileReference) + : base(pType, pHostileReference) + { + iThreatContainer = null; + } + + void setThreatContainer(ThreatContainer pThreatContainer) + { + iThreatContainer = pThreatContainer; + } + + ThreatContainer getThreatContainer() + { + return iThreatContainer; + } + + ThreatContainer iThreatContainer; + } + + public enum UnitEventTypes + { + // Player/Pet Changed On/Offline Status + ThreatRefOnlineStatus = 1 << 0, + + // Threat For Player/Pet Changed + ThreatRefThreatChange = 1 << 1, + + // Player/Pet Will Be Removed From List (Dead) [For Internal Use] + ThreatRefRemoveFromList = 1 << 2, + + // Player/Pet Entered/Left Water Or Some Other Place Where It Is/Was Not Accessible For The Creature + ThreatRefAsseccibleStatus = 1 << 3, + + // Threat List Is Going To Be Sorted (If Dirty Flag Is Set) + ThreatSortList = 1 << 4, + + // New Target Should Be Fetched, Could Tbe The Current Target As Well + ThreatSetNextTarget = 1 << 5, + + // A New Victim (Target) Was Set. Could Be Null + ThreatVictimChanged = 1 << 6 + + // Future Use + //Unit_Killed = 1<<7, + + //Future Use + //Unit_Health_Change = 1<<8, + } +} diff --git a/Game/Combat/HostileRegManager.cs b/Game/Combat/HostileRegManager.cs new file mode 100644 index 000000000..72d05b40a --- /dev/null +++ b/Game/Combat/HostileRegManager.cs @@ -0,0 +1,377 @@ +/* + * Copyright (C) 2012-2017 CypherCore + * + * 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, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +using Framework.Constants; +using Framework.Dynamic; +using Game.Entities; +using Game.Spells; + +namespace Game.Combat +{ + public class HostileRefManager : RefManager + { + Unit Owner; + + public HostileRefManager(Unit owner) + { + Owner = owner; + } + + Unit getOwner() { return Owner; } + + // send threat to all my hateres for the victim + // The victim is hated than by them as well + // use for buffs and healing threat functionality + public void threatAssist(Unit victim, float baseThreat, SpellInfo threatSpell = null) + { + HostileReference refe = getFirst(); + float threat = ThreatManager.calcThreat(victim, Owner, baseThreat, (threatSpell != null ? threatSpell.GetSchoolMask() : SpellSchoolMask.Normal), threatSpell); + threat /= getSize(); + while (refe != null) + { + if (ThreatManager.isValidProcess(victim, refe.GetSource().GetOwner(), threatSpell)) + refe.GetSource().doAddThreat(victim, threat); + + refe = refe.next(); + } + } + + public void addTempThreat(float threat, bool apply) + { + HostileReference refe = getFirst(); + + while (refe != null) + { + if (apply) + { + if (refe.getTempThreatModifier() == 0.0f) + refe.addTempThreat(threat); + } + else + refe.resetTempThreat(); + + refe = refe.next(); + } + } + + void addThreatPercent(int percent) + { + HostileReference refe = getFirst(); + while (refe != null) + { + refe.addThreatPercent(percent); + refe = refe.next(); + } + } + + // The references are not needed anymore + // tell the source to remove them from the list and free the mem + public void deleteReferences() + { + HostileReference refe = getFirst(); + while (refe != null) + { + HostileReference nextRef = refe.next(); + refe.removeReference(); + refe = nextRef; + } + } + + // Remove specific faction references + public void deleteReferencesForFaction(uint faction) + { + HostileReference refe = getFirst(); + while (refe != null) + { + HostileReference nextRef = refe.next(); + if (refe.GetSource().GetOwner().GetFactionTemplateEntry().Faction == faction) + { + refe.removeReference(); + } + refe = nextRef; + } + } + + public new HostileReference getFirst() { return ((HostileReference)base.getFirst()); } + + public void updateThreatTables() + { + HostileReference refe = getFirst(); + while (refe != null) + { + refe.updateOnlineStatus(); + refe = refe.next(); + } + } + + public void setOnlineOfflineState(bool isOnline) + { + HostileReference refe = getFirst(); + while (refe != null) + { + refe.setOnlineOfflineState(isOnline); + refe = refe.next(); + } + } + + // set state for one reference, defined by Unit + public void setOnlineOfflineState(Unit creature, bool isOnline) + { + HostileReference refe = getFirst(); + while (refe != null) + { + HostileReference nextRef = refe.next(); + if (refe.GetSource().GetOwner() == creature) + { + refe.setOnlineOfflineState(isOnline); + break; + } + refe = nextRef; + } + } + + // delete one reference, defined by Unit + public void deleteReference(Unit creature) + { + HostileReference refe = getFirst(); + while (refe != null) + { + HostileReference nextRef = refe.next(); + if (refe.GetSource().GetOwner() == creature) + { + refe.removeReference(); + break; + } + refe = nextRef; + } + } + + public void UpdateVisibility() + { + HostileReference refe = getFirst(); + while (refe != null) + { + HostileReference nextRef = refe.next(); + if (!refe.GetSource().GetOwner().CanSeeOrDetect(getOwner())) + { + nextRef = refe.next(); + refe.removeReference(); + } + refe = nextRef; + } + } + } + + public class HostileReference : Reference + { + public HostileReference(Unit refUnit, ThreatManager threatManager, float threat) + { + iThreat = threat; + iTempThreatModifier = 0.0f; + link(refUnit, threatManager); + iUnitGuid = refUnit.GetGUID(); + iOnline = true; + iAccessible = true; + } + + public override void targetObjectBuildLink() + { + getTarget().addHatedBy(this); + } + public override void targetObjectDestroyLink() + { + getTarget().removeHatedBy(this); + } + public override void sourceObjectDestroyLink() + { + setOnlineOfflineState(false); + } + + void fireStatusChanged(ThreatRefStatusChangeEvent threatRefStatusChangeEvent) + { + if (GetSource() != null) + GetSource().processThreatEvent(threatRefStatusChangeEvent); + } + + public void addThreat(float modThreat) + { + iThreat += modThreat; + // the threat is changed. Source and target unit have to be available + // if the link was cut before relink it again + if (!isOnline()) + updateOnlineStatus(); + if (modThreat != 0.0f) + { + ThreatRefStatusChangeEvent Event = new ThreatRefStatusChangeEvent(UnitEventTypes.ThreatRefThreatChange, this, modThreat); + fireStatusChanged(Event); + } + + if (modThreat >= 0.0f) + { + Unit victimOwner = getTarget().GetCharmerOrOwner(); + if (victimOwner != null && victimOwner.IsAlive()) + GetSource().addThreat(victimOwner, 0.0f); // create a threat to the owner of a pet, if the pet attacks + } + } + + public void addThreatPercent(int percent) + { + float tmpThreat = iThreat; + MathFunctions.AddPct(ref tmpThreat, percent); + addThreat(tmpThreat - iThreat); + } + + // check, if source can reach target and set the status + public void updateOnlineStatus() + { + bool online = false; + bool accessible = false; + + if (!isValid()) + { + Unit target = Global.ObjAccessor.GetUnit(getSourceUnit(), getUnitGuid()); + if (target != null) + link(target, GetSource()); + } + + // only check for online status if + // ref is valid + // target is no player or not gamemaster + // target is not in flight + if (isValid() + && (getTarget().IsTypeId(TypeId.Player) || !getTarget().ToPlayer().IsGameMaster()) + && !getTarget().HasUnitState(UnitState.InFlight) + && getTarget().IsInMap(getSourceUnit()) + && getTarget().IsInPhase(getSourceUnit()) + ) + { + Creature creature = getSourceUnit().ToCreature(); + online = getTarget().isInAccessiblePlaceFor(creature); + if (!online) + { + if (creature.IsWithinCombatRange(getTarget(), creature.m_CombatDistance)) + online = true; // not accessible but stays online + } + else + accessible = true; + } + setAccessibleState(accessible); + setOnlineOfflineState(online); + } + + public void setOnlineOfflineState(bool isOnline) + { + if (iOnline != isOnline) + { + iOnline = isOnline; + if (!iOnline) + setAccessibleState(false); // if not online that not accessable as well + + ThreatRefStatusChangeEvent Event = new ThreatRefStatusChangeEvent(UnitEventTypes.ThreatRefOnlineStatus, this); + fireStatusChanged(Event); + } + } + + void setAccessibleState(bool isAccessible) + { + if (iAccessible != isAccessible) + { + iAccessible = isAccessible; + + ThreatRefStatusChangeEvent Event = new ThreatRefStatusChangeEvent(UnitEventTypes.ThreatRefAsseccibleStatus, this); + fireStatusChanged(Event); + } + } + + // reference is not needed anymore. realy delete it ! + public void removeReference() + { + invalidate(); + + ThreatRefStatusChangeEvent Event = new ThreatRefStatusChangeEvent(UnitEventTypes.ThreatRefRemoveFromList, this); + fireStatusChanged(Event); + } + + Unit getSourceUnit() + { + return GetSource().GetOwner(); + } + + public void setThreat(float threat) + { + addThreat(threat - getThreat()); + } + + public float getThreat() + { + return iThreat; + } + + public bool isOnline() + { + return iOnline; + } + + // The Unit might be in water and the creature can not enter the water, but has range attack + // in this case online = true, but accessible = false + bool isAccessible() + { + return iAccessible; + } + + // used for temporary setting a threat and reducting it later again. + // the threat modification is stored + public void setTempThreat(float threat) + { + addTempThreat(threat - getThreat()); + } + + public void addTempThreat(float threat) + { + iTempThreatModifier = threat; + if (iTempThreatModifier != 0.0f) + addThreat(iTempThreatModifier); + } + + public void resetTempThreat() + { + if (iTempThreatModifier != 0.0f) + { + addThreat(-iTempThreatModifier); + iTempThreatModifier = 0.0f; + } + } + + public float getTempThreatModifier() + { + return iTempThreatModifier; + } + + public ObjectGuid getUnitGuid() + { + return iUnitGuid; + } + + public new HostileReference next() { return (HostileReference)base.next(); } + + float iThreat; + float iTempThreatModifier; // used for taunt + ObjectGuid iUnitGuid; + bool iOnline; + bool iAccessible; + } +} diff --git a/Game/Combat/ThreatManager.cs b/Game/Combat/ThreatManager.cs new file mode 100644 index 000000000..087c4b80b --- /dev/null +++ b/Game/Combat/ThreatManager.cs @@ -0,0 +1,470 @@ +/* + * Copyright (C) 2012-2017 CypherCore + * + * 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, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +using Framework.Constants; +using Game.Entities; +using Game.Spells; +using System.Collections.Generic; +using System.Diagnostics.Contracts; +using System.Linq; + +namespace Game.Combat +{ + public class ThreatManager + { + public ThreatManager(Unit owner) + { + currentVictim = null; + Owner = owner; + updateTimer = ThreatUpdateInternal; + threatContainer = new ThreatContainer(); + threatOfflineContainer = new ThreatContainer(); + } + + const int ThreatUpdateInternal = 1 * Time.InMilliseconds; + + public void clearReferences() + { + threatContainer.clearReferences(); + threatOfflineContainer.clearReferences(); + currentVictim = null; + updateTimer = ThreatUpdateInternal; + } + + public void addThreat(Unit victim, float threat, SpellSchoolMask schoolMask = SpellSchoolMask.Normal, SpellInfo threatSpell = null) + { + if (!isValidProcess(victim, Owner, threatSpell)) + return; + + doAddThreat(victim, calcThreat(victim, Owner, threat, schoolMask, threatSpell)); + } + + public void doAddThreat(Unit victim, float threat) + { + uint redirectThreadPct = victim.GetRedirectThreatPercent(); + + // must check > 0.0f, otherwise dead loop + if (threat > 0.0f && redirectThreadPct != 0) + { + Unit redirectTarget = victim.GetRedirectThreatTarget(); + if (redirectTarget != null) + { + float redirectThreat = MathFunctions.CalculatePct(threat, redirectThreadPct); + threat -= redirectThreat; + _addThreat(redirectTarget, redirectThreat); + } + } + + _addThreat(victim, threat); + } + + void _addThreat(Unit victim, float threat) + { + var reff = threatContainer.addThreat(victim, threat); + // Ref is not in the online refs, search the offline refs next + if (reff == null) + reff = threatOfflineContainer.addThreat(victim, threat); + + if (reff == null) // there was no ref => create a new one + { + bool isFirst = threatContainer.empty(); + + // threat has to be 0 here + var hostileRef = new HostileReference(victim, this, 0); + threatContainer.addReference(hostileRef); + hostileRef.addThreat(threat); // now we add the real threat + if (victim.IsTypeId(TypeId.Player) && victim.ToPlayer().IsGameMaster()) + hostileRef.setOnlineOfflineState(false); // GM is always offline + else if (isFirst) + setCurrentVictim(hostileRef); + } + } + + public void modifyThreatPercent(Unit victim, int percent) + { + threatContainer.modifyThreatPercent(victim, percent); + } + + public Unit getHostilTarget() + { + threatContainer.update(); + HostileReference nextVictim = threatContainer.selectNextVictim(GetOwner().ToCreature(), getCurrentVictim()); + setCurrentVictim(nextVictim); + return getCurrentVictim() != null ? getCurrentVictim().getTarget() : null; + } + + public float getThreat(Unit victim, bool alsoSearchOfflineList = false) + { + float threat = 0.0f; + HostileReference refe = threatContainer.getReferenceByTarget(victim); + if (refe == null && alsoSearchOfflineList) + refe = threatOfflineContainer.getReferenceByTarget(victim); + if (refe != null) + threat = refe.getThreat(); + return threat; + } + + void tauntApply(Unit taunter) + { + HostileReference refe = threatContainer.getReferenceByTarget(taunter); + if (getCurrentVictim() != null && refe != null && (refe.getThreat() < getCurrentVictim().getThreat())) + { + if (refe.getTempThreatModifier() == 0.0f) // Ok, temp threat is unused + refe.setTempThreat(getCurrentVictim().getThreat()); + } + } + + void tauntFadeOut(Unit taunter) + { + HostileReference refe = threatContainer.getReferenceByTarget(taunter); + if (refe != null) + refe.resetTempThreat(); + } + + public void setCurrentVictim(HostileReference pHostileReference) + { + if (pHostileReference != null && pHostileReference != currentVictim) + { + Owner.SendChangeCurrentVictim(pHostileReference); + } + currentVictim = pHostileReference; + } + + public void processThreatEvent(ThreatRefStatusChangeEvent threatRefStatusChangeEvent) + { + threatRefStatusChangeEvent.setThreatManager(this); // now we can set the threat manager + + HostileReference hostilRef = threatRefStatusChangeEvent.getReference(); + + switch (threatRefStatusChangeEvent.getType()) + { + case UnitEventTypes.ThreatRefThreatChange: + if ((getCurrentVictim() == hostilRef && threatRefStatusChangeEvent.getFValue() < 0.0f) || + (getCurrentVictim() != hostilRef && threatRefStatusChangeEvent.getFValue() > 0.0f)) + setDirty(true); // the order in the threat list might have changed + break; + case UnitEventTypes.ThreatRefOnlineStatus: + if (!hostilRef.isOnline()) + { + if (hostilRef == getCurrentVictim()) + { + setCurrentVictim(null); + setDirty(true); + } + Owner.SendRemoveFromThreatList(hostilRef); + threatContainer.remove(hostilRef); + threatOfflineContainer.addReference(hostilRef); + } + else + { + if (getCurrentVictim() != null && hostilRef.getThreat() > (1.1f * getCurrentVictim().getThreat())) + setDirty(true); + threatContainer.addReference(hostilRef); + threatOfflineContainer.remove(hostilRef); + } + break; + case UnitEventTypes.ThreatRefRemoveFromList: + if (hostilRef == getCurrentVictim()) + { + setCurrentVictim(null); + setDirty(true); + } + Owner.SendRemoveFromThreatList(hostilRef); + if (hostilRef.isOnline()) + threatContainer.remove(hostilRef); + else + threatOfflineContainer.remove(hostilRef); + break; + } + } + + + public bool isNeedUpdateToClient(uint time) + { + if (isThreatListEmpty()) + return false; + + if (time >= updateTimer) + { + updateTimer = ThreatUpdateInternal; + return true; + } + updateTimer -= time; + return false; + } + + // Reset all aggro without modifying the threatlist. + void resetAllAggro() + { + var threatList = threatContainer.threatList; + if (threatList.Empty()) + return; + + foreach (var refe in threatList) + refe.setThreat(0); + + setDirty(true); + } + public bool isThreatListEmpty() + { + return threatContainer.empty(); + } + + public HostileReference getCurrentVictim() + { + return currentVictim; + } + + public Unit GetOwner() + { + return Owner; + } + + void setDirty(bool isDirty) + { + threatContainer.setDirty(isDirty); + } + + public List getThreatList() { return threatContainer.getThreatList(); } + public List getOfflineThreatList() { return threatOfflineContainer.getThreatList(); } + public ThreatContainer getOnlineContainer() { return threatContainer; } + + // The hatingUnit is not used yet + public static float calcThreat(Unit hatedUnit, Unit hatingUnit, float threat, SpellSchoolMask schoolMask = SpellSchoolMask.Normal, SpellInfo threatSpell = null) + { + if (threatSpell != null) + { + var threatEntry = Global.SpellMgr.GetSpellThreatEntry(threatSpell.Id); + if (threatEntry != null) + if (threatEntry.pctMod != 1.0f) + threat *= threatEntry.pctMod; + + // Energize is not affected by Mods + foreach (SpellEffectInfo effect in threatSpell.GetEffectsForDifficulty(hatedUnit.GetMap().GetDifficultyID())) + if (effect != null && (effect.Effect == SpellEffectName.Energize || effect.ApplyAuraName == AuraType.PeriodicEnergize)) + return threat; + + Player modOwner = hatedUnit.GetSpellModOwner(); + if (modOwner != null) + modOwner.ApplySpellMod(threatSpell.Id, SpellModOp.Threat, ref threat); + } + + return hatedUnit.ApplyTotalThreatModifier(threat, schoolMask); + } + + public static bool isValidProcess(Unit hatedUnit, Unit hatingUnit, SpellInfo threatSpell) + { + //function deals with adding threat and adding players and pets into ThreatList + //mobs, NPCs, guards have ThreatList and HateOfflineList + //players and pets have only InHateListOf + //HateOfflineList is used co contain unattackable victims (in-flight, in-water, GM etc.) + + if (hatedUnit == null || hatingUnit == null) + return false; + + // not to self + if (hatedUnit == hatingUnit) + return false; + + // not to GM + if (hatedUnit.IsTypeId(TypeId.Player) && hatedUnit.ToPlayer().IsGameMaster()) + return false; + + // not to dead and not for dead + if (!hatedUnit.IsAlive() || !hatingUnit.IsAlive()) + return false; + + // not in same map or phase + if (!hatedUnit.IsInMap(hatingUnit) || !hatedUnit.IsInPhase(hatingUnit)) + return false; + + // spell not causing threat + if (threatSpell != null && threatSpell.HasAttribute(SpellAttr1.NoThreat)) + return false; + + Contract.Assert(hatingUnit.IsTypeId(TypeId.Unit)); + + return true; + } + + Unit Owner; + HostileReference currentVictim; + uint updateTimer; + ThreatContainer threatContainer; + ThreatContainer threatOfflineContainer; + } + + public class ThreatContainer + { + public ThreatContainer() + { + threatList = new List(); + iDirty = false; + } + + public void clearReferences() + { + foreach (var reff in threatList) + { + reff.unlink(); + } + + threatList.Clear(); + } + + public HostileReference getReferenceByTarget(Unit victim) + { + if (victim == null) + return null; + + ObjectGuid guid = victim.GetGUID(); + foreach (var reff in threatList) + { + if (reff != null && reff.getUnitGuid() == guid) + return reff; + } + + return null; + } + + public HostileReference addThreat(Unit victim, float threat) + { + var reff = getReferenceByTarget(victim); + if (reff != null) + reff.addThreat(threat); + return reff; + } + + public void modifyThreatPercent(Unit victim, int percent) + { + HostileReference refe = getReferenceByTarget(victim); + if (refe != null) + refe.addThreatPercent(percent); + } + + public void update() + { + if (iDirty && threatList.Count > 1) + threatList.OrderByDescending(p => p.getThreat()); + + iDirty = false; + } + + public HostileReference selectNextVictim(Creature attacker, HostileReference currentVictim) + { + HostileReference currentRef = null; + bool found = false; + bool noPriorityTargetFound = false; + + for (var i = 0; i < threatList.Count; i++) + { + if (found) + break; + + currentRef = threatList[i]; + + Unit target = currentRef.getTarget(); + Contract.Assert(target); // if the ref has status online the target must be there ! + + // some units are prefered in comparison to others + if (!noPriorityTargetFound && (target.IsImmunedToDamage(attacker.GetMeleeDamageSchoolMask()) || target.HasNegativeAuraWithInterruptFlag((uint)SpellAuraInterruptFlags.TakeDamage))) + { + if (i != threatList.Count - 1) + { + // current victim is a second choice target, so don't compare threat with it below + if (currentRef == currentVictim) + currentVictim = null; + continue; + } + else + { + // if we reached to this point, everyone in the threatlist is a second choice target. In such a situation the target with the highest threat should be attacked. + noPriorityTargetFound = true; + i = 0; + continue; + } + } + + if (attacker.CanCreatureAttack(target)) // skip non attackable currently targets + { + if (currentVictim != null) // select 1.3/1.1 better target in comparison current target + { + // list sorted and and we check current target, then this is best case + if (currentVictim == currentRef || currentRef.getThreat() <= 1.1f * currentVictim.getThreat()) + { + if (currentVictim != currentRef && attacker.CanCreatureAttack(currentVictim.getTarget())) + currentRef = currentVictim; // for second case, if currentvictim is attackable + + found = true; + break; + } + + if (currentRef.getThreat() > 1.3f * currentVictim.getThreat() || + (currentRef.getThreat() > 1.1f * currentVictim.getThreat() && + attacker.IsWithinMeleeRange(target))) + { //implement 110% threat rule for targets in melee range + found = true; //and 130% rule for targets in ranged distances + break; //for selecting alive targets + } + } + else // select any + { + found = true; + break; + } + } + } + if (!found) + currentRef = null; + + return currentRef; + } + + public void setDirty(bool isDirty) + { + iDirty = isDirty; + } + + bool isDirty() + { + return iDirty; + } + + public bool empty() + { + return threatList.Empty(); + } + public HostileReference getMostHated() + { + return threatList.Count() == 0 ? null : threatList[0]; + } + + public void remove(HostileReference hostileRef) + { + threatList.Remove(hostileRef); + } + public void addReference(HostileReference hostileRef) + { + threatList.Add(hostileRef); + } + + public List getThreatList() { return threatList; } + + public List threatList { get; set; } + bool iDirty; + } +} diff --git a/Game/Conditions/Condition.cs b/Game/Conditions/Condition.cs new file mode 100644 index 000000000..84d7f96f2 --- /dev/null +++ b/Game/Conditions/Condition.cs @@ -0,0 +1,578 @@ +/* + * Copyright (C) 2012-2017 CypherCore + * + * 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, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +using Framework.Constants; +using Game.DataStorage; +using Game.Entities; +using Game.Maps; +using System; +using System.Collections.Generic; +using System.Diagnostics.Contracts; +using System.Text; + +namespace Game.Conditions +{ + public class Condition + { + public Condition() + { + SourceType = ConditionSourceType.None; + ConditionType = ConditionTypes.None; + } + + public bool Meets(ConditionSourceInfo sourceInfo) + { + Contract.Assert(ConditionTarget < SharedConst.MaxConditionTargets); + WorldObject obj = sourceInfo.mConditionTargets[ConditionTarget]; + // object not present, return false + if (obj == null) + { + Log.outDebug(LogFilter.Condition, "Condition object not found for condition (Entry: {0} Type: {1} Group: {2})", SourceEntry, SourceType, SourceGroup); + return false; + } + bool condMeets = false; + + Player player = obj.ToPlayer(); + Unit unit = obj.ToUnit(); + + switch (ConditionType) + { + case ConditionTypes.None: + condMeets = true; // empty condition, always met + break; + case ConditionTypes.Aura: + if (unit != null) + condMeets = unit.HasAuraEffect(ConditionValue1, (byte)ConditionValue2); + break; + case ConditionTypes.Item: + if (player != null) + { + // don't allow 0 items (it's checked during table load) + Contract.Assert(ConditionValue2 != 0); + bool checkBank = ConditionValue3 != 0 ? true : false; + condMeets = player.HasItemCount(ConditionValue1, ConditionValue2, checkBank); + } + break; + case ConditionTypes.ItemEquipped: + if (player != null) + condMeets = player.HasItemOrGemWithIdEquipped(ConditionValue1, 1); + break; + case ConditionTypes.Zoneid: + condMeets = obj.GetZoneId() == ConditionValue1; + break; + case ConditionTypes.ReputationRank: + if (player != null) + { + var faction = CliDB.FactionStorage.LookupByKey(ConditionValue1); + if (faction != null) + condMeets = Convert.ToBoolean(ConditionValue2 & (1 << (int)player.GetReputationMgr().GetRank(faction))); + } + break; + case ConditionTypes.Achievement: + if (player != null) + condMeets = player.HasAchieved(ConditionValue1); + break; + case ConditionTypes.Team: + if (player != null) + condMeets = (uint)player.GetTeam() == ConditionValue1; + break; + case ConditionTypes.Class: + if (unit != null) + condMeets = Convert.ToBoolean(unit.getClassMask() & ConditionValue1); + break; + case ConditionTypes.Race: + if (unit != null) + condMeets = Convert.ToBoolean(unit.getRaceMask() & ConditionValue1); + break; + case ConditionTypes.Gender: + if (player != null) + condMeets = player.GetGender() == (Gender)ConditionValue1; + break; + case ConditionTypes.Skill: + if (player != null) + condMeets = player.HasSkill((SkillType)ConditionValue1) && player.GetBaseSkillValue((SkillType)ConditionValue1) >= ConditionValue2; + break; + case ConditionTypes.QuestRewarded: + if (player != null) + condMeets = player.GetQuestRewardStatus(ConditionValue1); + break; + case ConditionTypes.QuestTaken: + if (player != null) + { + QuestStatus status = player.GetQuestStatus(ConditionValue1); + condMeets = (status == QuestStatus.Incomplete); + } + break; + case ConditionTypes.QuestComplete: + if (player != null) + { + QuestStatus status = player.GetQuestStatus(ConditionValue1); + condMeets = (status == QuestStatus.Complete && !player.GetQuestRewardStatus(ConditionValue1)); + } + break; + case ConditionTypes.QuestNone: + if (player != null) + { + QuestStatus status = player.GetQuestStatus(ConditionValue1); + condMeets = (status == QuestStatus.None); + } + break; + case ConditionTypes.ActiveEvent: + condMeets = Global.GameEventMgr.IsActiveEvent((ushort)ConditionValue1); + break; + case ConditionTypes.InstanceInfo: + { + var map = obj.GetMap(); + if (map.IsDungeon()) + { + InstanceScript instance = ((InstanceMap)map).GetInstanceScript(); + if (instance != null) + { + switch ((InstanceInfo)ConditionValue3) + { + case InstanceInfo.Data: + condMeets = instance.GetData(ConditionValue1) == ConditionValue2; + break; + case InstanceInfo.Data64: + condMeets = instance.GetData64(ConditionValue1) == ConditionValue2; + break; + case InstanceInfo.BossState: + condMeets = instance.GetBossState(ConditionValue1) == (EncounterState)ConditionValue2; + break; + } + } + } + break; + } + case ConditionTypes.Mapid: + condMeets = obj.GetMapId() == ConditionValue1; + break; + case ConditionTypes.Areaid: + condMeets = obj.GetAreaId() == ConditionValue1; + break; + case ConditionTypes.Spell: + if (player != null) + condMeets = player.HasSpell(ConditionValue1); + break; + case ConditionTypes.Level: + if (unit != null) + condMeets = MathFunctions.CompareValues((ComparisionType)ConditionValue2, unit.getLevel(), ConditionValue1); + break; + case ConditionTypes.DrunkenState: + if (player != null) + condMeets = (uint)Player.GetDrunkenstateByValue(player.GetDrunkValue()) >= ConditionValue1; + break; + case ConditionTypes.NearCreature: + condMeets = obj.FindNearestCreature(ConditionValue1, ConditionValue2, ConditionValue3 == 0 ? true : false) != null; + break; + case ConditionTypes.NearGameobject: + condMeets = obj.FindNearestGameObject(ConditionValue1, ConditionValue2) != null; + break; + case ConditionTypes.ObjectEntryGuid: + if ((uint)obj.GetTypeId() == ConditionValue1) + { + condMeets = ConditionValue2 == 0 || (obj.GetEntry() == ConditionValue2); + + if (ConditionValue3 != 0) + { + switch (obj.GetTypeId()) + { + case TypeId.Unit: + condMeets &= obj.ToCreature().GetSpawnId() == ConditionValue3; + break; + case TypeId.GameObject: + condMeets &= obj.ToGameObject().GetSpawnId() == ConditionValue3; + break; + } + } + } + break; + case ConditionTypes.TypeMask: + condMeets = Convert.ToBoolean((TypeMask)ConditionValue1 & obj.objectTypeMask); + break; + case ConditionTypes.RelationTo: + { + WorldObject toObject = sourceInfo.mConditionTargets[ConditionValue1]; + if (toObject != null) + { + Unit toUnit = toObject.ToUnit(); + if (toUnit != null && unit != null) + { + switch ((RelationType)ConditionValue2) + { + case RelationType.Self: + condMeets = unit == toUnit; + break; + case RelationType.InParty: + condMeets = unit.IsInPartyWith(toUnit); + break; + case RelationType.InRaidOrParty: + condMeets = unit.IsInRaidWith(toUnit); + break; + case RelationType.OwnedBy: + condMeets = unit.GetOwnerGUID() == toUnit.GetGUID(); + break; + case RelationType.PassengerOf: + condMeets = unit.IsOnVehicle(toUnit); + break; + case RelationType.CreatedBy: + condMeets = unit.GetCreatorGUID() == toUnit.GetGUID(); + break; + } + } + } + break; + } + case ConditionTypes.ReactionTo: + { + WorldObject toObject = sourceInfo.mConditionTargets[ConditionValue1]; + if (toObject != null) + { + Unit toUnit = toObject.ToUnit(); + if (toUnit != null && unit != null) + condMeets = Convert.ToBoolean((1 << (int)unit.GetReactionTo(toUnit)) & ConditionValue2); + } + break; + } + case ConditionTypes.DistanceTo: + { + WorldObject toObject = sourceInfo.mConditionTargets[ConditionValue1]; + if (toObject != null) + condMeets = MathFunctions.CompareValues((ComparisionType)ConditionValue3, obj.GetDistance(toObject), ConditionValue2); + break; + } + case ConditionTypes.Alive: + if (unit != null) + condMeets = unit.IsAlive(); + break; + case ConditionTypes.HpVal: + if (unit != null) + condMeets = MathFunctions.CompareValues((ComparisionType)ConditionValue2, unit.GetHealth(), ConditionValue1); + break; + case ConditionTypes.HpPct: + if (unit != null) + condMeets = MathFunctions.CompareValues((ComparisionType)ConditionValue2, unit.GetHealthPct(), ConditionValue1); + break; + case ConditionTypes.WorldState: + condMeets = (ConditionValue2 == Global.WorldMgr.getWorldState((WorldStates)ConditionValue1)); + break; + case ConditionTypes.PhaseId: + condMeets = obj.IsInPhase(ConditionValue1); + break; + case ConditionTypes.Title: + if (player != null) + condMeets = player.HasTitle(ConditionValue1); + break; + case ConditionTypes.Spawnmask: + condMeets = Convert.ToBoolean((1 << (int)obj.GetMap().GetSpawnMode()) & ConditionValue1); + break; + case ConditionTypes.UnitState: + if (unit != null) + condMeets = unit.HasUnitState((UnitState)ConditionValue1); + break; + case ConditionTypes.CreatureType: + { + Creature creature = obj.ToCreature(); + if (creature) + condMeets = (uint)creature.GetCreatureTemplate().CreatureType == ConditionValue1; + break; + } + case ConditionTypes.RealmAchievement: + { + AchievementRecord achievement = CliDB.AchievementStorage.LookupByKey(ConditionValue1); + if (achievement != null && Global.AchievementMgr.IsRealmCompleted(achievement)) + condMeets = true; + break; + } + case ConditionTypes.InWater: + if (unit) + condMeets = unit.IsInWater(); + break; + case ConditionTypes.TerrainSwap: + condMeets = obj.IsInTerrainSwap(ConditionValue1); + break; + case ConditionTypes.StandState: + { + if (unit) + { + if (ConditionValue1 == 0) + condMeets = (unit.GetStandState() == (UnitStandStateType)ConditionValue2); + else if (ConditionValue2 == 0) + condMeets = unit.IsStandState(); + else if (ConditionValue2 == 1) + condMeets = unit.IsSitState(); + } + break; + } + case ConditionTypes.DailyQuestDone: + { + if (player) + condMeets = player.IsDailyQuestDone(ConditionValue1); + break; + } + case ConditionTypes.Charmed: + { + if (unit) + condMeets = unit.IsCharmed(); + break; + } + case ConditionTypes.PetType: + { + if (player) + { + Pet pet = player.GetPet(); + if (pet) + condMeets = (((1 << (int)pet.getPetType()) & ConditionValue1) != 0); + } + break; + } + case ConditionTypes.Taxi: + { + if (player) + condMeets = player.IsInFlight(); + break; + } + case ConditionTypes.Queststate: + { + if (player) + { + if ( + (Convert.ToBoolean(ConditionValue2 & (1 << (int)QuestStatus.None)) && (player.GetQuestStatus(ConditionValue1) == QuestStatus.None)) || + (Convert.ToBoolean(ConditionValue2 & (1 << (int)QuestStatus.Complete)) && (player.GetQuestStatus(ConditionValue1) == QuestStatus.Complete)) || + (Convert.ToBoolean(ConditionValue2 & (1 << (int)QuestStatus.Incomplete)) && (player.GetQuestStatus(ConditionValue1) == QuestStatus.Incomplete)) || + (Convert.ToBoolean(ConditionValue2 & (1 << (int)QuestStatus.Failed)) && (player.GetQuestStatus(ConditionValue1) == QuestStatus.Failed)) || + (Convert.ToBoolean(ConditionValue2 & (1 << (int)QuestStatus.Rewarded)) && player.GetQuestRewardStatus(ConditionValue1)) + ) + condMeets = true; + } + break; + } + case ConditionTypes.ObjectiveComplete: + { + if (player) + { + QuestObjective questObj = Global.ObjectMgr.GetQuestObjective(ConditionValue1); + if (questObj == null) + break; + + condMeets = (!player.GetQuestRewardStatus(questObj.QuestID) && player.IsQuestObjectiveComplete(questObj)); + } + break; + } + default: + condMeets = false; + break; + } + + if (NegativeCondition) + condMeets = !condMeets; + + if (!condMeets) + sourceInfo.mLastFailedCondition = this; + + bool script = Global.ScriptMgr.OnConditionCheck(this, sourceInfo); // Returns true by default. + return condMeets && script; + } + + public GridMapTypeMask GetSearcherTypeMaskForCondition() + { + // build mask of types for which condition can return true + // this is used for speeding up gridsearches + if (NegativeCondition) + return GridMapTypeMask.All; + + GridMapTypeMask mask = 0; + switch (ConditionType) + { + case ConditionTypes.DistanceTo: + case ConditionTypes.WorldState: + case ConditionTypes.PhaseId: + case ConditionTypes.Spawnmask: + case ConditionTypes.NearCreature: + case ConditionTypes.NearGameobject: + case ConditionTypes.ActiveEvent: + case ConditionTypes.InstanceInfo: + case ConditionTypes.Mapid: + case ConditionTypes.Areaid: + case ConditionTypes.None: + case ConditionTypes.Zoneid: + case ConditionTypes.TerrainSwap: + case ConditionTypes.RealmAchievement: + mask |= GridMapTypeMask.All; + break; + case ConditionTypes.Gender: + case ConditionTypes.Title: + case ConditionTypes.DrunkenState: + case ConditionTypes.Spell: + case ConditionTypes.QuestTaken: + case ConditionTypes.QuestComplete: + case ConditionTypes.QuestNone: + case ConditionTypes.Skill: + case ConditionTypes.QuestRewarded: + case ConditionTypes.ReputationRank: + case ConditionTypes.Achievement: + case ConditionTypes.Team: + case ConditionTypes.Item: + case ConditionTypes.ItemEquipped: + case ConditionTypes.PetType: + case ConditionTypes.Taxi: + case ConditionTypes.Queststate: + mask |= GridMapTypeMask.Player; + break; + case ConditionTypes.UnitState: + case ConditionTypes.Alive: + case ConditionTypes.HpVal: + case ConditionTypes.HpPct: + case ConditionTypes.RelationTo: + case ConditionTypes.ReactionTo: + case ConditionTypes.Level: + case ConditionTypes.Class: + case ConditionTypes.Race: + case ConditionTypes.Aura: + case ConditionTypes.InWater: + case ConditionTypes.StandState: + mask |= GridMapTypeMask.Creature | GridMapTypeMask.Player; + break; + case ConditionTypes.ObjectEntryGuid: + switch ((TypeId)ConditionValue1) + { + case TypeId.Unit: + mask |= GridMapTypeMask.Creature; + break; + case TypeId.Player: + mask |= GridMapTypeMask.Player; + break; + case TypeId.GameObject: + mask |= GridMapTypeMask.GameObject; + break; + case TypeId.Corpse: + mask |= GridMapTypeMask.Corpse; + break; + case TypeId.AreaTrigger: + mask |= GridMapTypeMask.AreaTrigger; + break; + default: + break; + } + break; + case ConditionTypes.TypeMask: + if (Convert.ToBoolean((TypeMask)ConditionValue1 & TypeMask.Unit)) + mask |= GridMapTypeMask.Creature | GridMapTypeMask.Player; + if (Convert.ToBoolean((TypeMask)ConditionValue1 & TypeMask.Player)) + mask |= GridMapTypeMask.Player; + if (Convert.ToBoolean((TypeMask)ConditionValue1 & TypeMask.GameObject)) + mask |= GridMapTypeMask.GameObject; + if (Convert.ToBoolean((TypeMask)ConditionValue1 & TypeMask.Corpse)) + mask |= GridMapTypeMask.Corpse; + if (Convert.ToBoolean((TypeMask)ConditionValue1 & TypeMask.AreaTrigger)) + mask |= GridMapTypeMask.AreaTrigger; + break; + case ConditionTypes.DailyQuestDone: + case ConditionTypes.ObjectiveComplete: + mask |= GridMapTypeMask.Player; + break; + default: + Contract.Assert(false, "Condition.GetSearcherTypeMaskForCondition - missing condition handling!"); + break; + } + return mask; + } + + public bool isLoaded() + { + return ConditionType > ConditionTypes.None || ReferenceId != 0; + } + + public uint GetMaxAvailableConditionTargets() + { + // returns number of targets which are available for given source type + switch (SourceType) + { + case ConditionSourceType.Spell: + case ConditionSourceType.SpellImplicitTarget: + case ConditionSourceType.CreatureTemplateVehicle: + case ConditionSourceType.VehicleSpell: + case ConditionSourceType.SpellClickEvent: + case ConditionSourceType.GossipMenu: + case ConditionSourceType.GossipMenuOption: + case ConditionSourceType.SmartEvent: + case ConditionSourceType.NpcVendor: + case ConditionSourceType.SpellProc: + return 2; + default: + return 1; + } + } + + public string ToString(bool ext = false) + { + StringBuilder ss = new StringBuilder(); + ss.AppendFormat("[Condition SourceType: {0}", SourceType); + if (SourceType < ConditionSourceType.Max) + ss.AppendFormat(" ({0})", Global.ConditionMgr.StaticSourceTypeData[(int)SourceType]); + else + ss.Append(" (Unknown)"); + if (Global.ConditionMgr.CanHaveSourceGroupSet(SourceType)) + ss.AppendFormat(", SourceGroup: {0}", SourceGroup); + ss.AppendFormat(", SourceEntry: {0}", SourceEntry); + if (Global.ConditionMgr.CanHaveSourceIdSet(SourceType)) + ss.AppendFormat(", SourceId: {0}", SourceId); + + if (ext) + { + ss.AppendFormat(", ConditionType: {0}", ConditionType); + if (ConditionType < ConditionTypes.Max) + ss.AppendFormat(" ({0})", Global.ConditionMgr.StaticConditionTypeData[(int)ConditionType].Name); + else + ss.Append(" (Unknown)"); + } + + ss.Append("]"); + return ss.ToString(); + } + + public ConditionSourceType SourceType; //SourceTypeOrReferenceId + public uint SourceGroup; + public int SourceEntry; + public uint SourceId; // So far, only used in CONDITION_SOURCE_TYPE_SMART_EVENT + public uint ElseGroup; + public ConditionTypes ConditionType; //ConditionTypeOrReference + public uint ConditionValue1; + public uint ConditionValue2; + public uint ConditionValue3; + public uint ErrorType; + public uint ErrorTextId; + public uint ReferenceId; + public uint ScriptId; + public byte ConditionTarget; + public bool NegativeCondition; + } + + public class ConditionSourceInfo + { + public ConditionSourceInfo(WorldObject target0, WorldObject target1 = null, WorldObject target2 = null) + { + mConditionTargets[0] = target0; + mConditionTargets[1] = target1; + mConditionTargets[2] = target2; + mLastFailedCondition = null; + } + + public WorldObject[] mConditionTargets = new WorldObject[SharedConst.MaxConditionTargets]; // an array of targets available for conditions + public Condition mLastFailedCondition; + } +} diff --git a/Game/Conditions/ConditionManager.cs b/Game/Conditions/ConditionManager.cs new file mode 100644 index 000000000..bf6d00491 --- /dev/null +++ b/Game/Conditions/ConditionManager.cs @@ -0,0 +1,2153 @@ +/* + * Copyright (C) 2012-2017 CypherCore + * + * 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, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +using Framework.Constants; +using Framework.Database; +using Game.Conditions; +using Game.DataStorage; +using Game.Entities; +using Game.Groups; +using Game.Loots; +using Game.Spells; +using System; +using System.Collections.Generic; +using System.Diagnostics.Contracts; +using System.Linq; + +namespace Game +{ + public sealed class ConditionManager : Singleton + { + ConditionManager() { } + + public GridMapTypeMask GetSearcherTypeMaskForConditionList(List conditions) + { + if (conditions.Empty()) + return GridMapTypeMask.All; + // groupId, typeMask + Dictionary elseGroupSearcherTypeMasks = new Dictionary(); + foreach (var i in conditions) + { + // no point of having not loaded conditions in list + Contract.Assert(i.isLoaded(), "ConditionMgr.GetSearcherTypeMaskForConditionList - not yet loaded condition found in list"); + // group not filled yet, fill with widest mask possible + if (!elseGroupSearcherTypeMasks.ContainsKey(i.ElseGroup)) + elseGroupSearcherTypeMasks[i.ElseGroup] = GridMapTypeMask.All; + // no point of checking anymore, empty mask + else if (elseGroupSearcherTypeMasks[i.ElseGroup] == 0) + continue; + + if (i.ReferenceId != 0) // handle reference + { + var refe = ConditionReferenceStore.LookupByKey(i.ReferenceId); + Contract.Assert(refe.Empty(), "ConditionMgr.GetSearcherTypeMaskForConditionList - incorrect reference"); + elseGroupSearcherTypeMasks[i.ElseGroup] &= GetSearcherTypeMaskForConditionList(refe); + } + else // handle normal condition + { + // object will match conditions in one ElseGroupStore only when it matches all of them + // so, let's find a smallest possible mask which satisfies all conditions + elseGroupSearcherTypeMasks[i.ElseGroup] &= i.GetSearcherTypeMaskForCondition(); + } + } + // object will match condition when one of the checks in ElseGroupStore is matching + // so, let's include all possible masks + GridMapTypeMask mask = 0; + foreach (var i in elseGroupSearcherTypeMasks) + mask |= i.Value; + + return mask; + } + + public bool IsObjectMeetToConditionList(ConditionSourceInfo sourceInfo, List conditions) + { + // groupId, groupCheckPassed + Dictionary elseGroupStore = new Dictionary(); + foreach (var condition in conditions) + { + Log.outDebug(LogFilter.Condition, "ConditionMgr.IsPlayerMeetToConditionList condType: {0} val1: {1}", condition.ConditionType, condition.ConditionValue1); + if (condition.isLoaded()) + { + //! Find ElseGroup in ElseGroupStore + //! If not found, add an entry in the store and set to true (placeholder) + if (!elseGroupStore.ContainsKey(condition.ElseGroup)) + elseGroupStore[condition.ElseGroup] = true; + else if (!elseGroupStore[condition.ElseGroup]) //! If another condition in this group was unmatched before this, don't bother checking (the group is false anyway) + continue; + + if (condition.ReferenceId != 0)//handle reference + { + var refe = ConditionReferenceStore.LookupByKey(condition.ReferenceId); + if (!refe.Empty()) + { + if (!IsObjectMeetToConditionList(sourceInfo, refe)) + elseGroupStore[condition.ElseGroup] = false; + } + else + { + Log.outDebug(LogFilter.Condition, "IsPlayerMeetToConditionList: Reference template -{0} not found", + condition.ReferenceId);//checked at loading, should never happen + } + + } + else //handle normal condition + { + if (!condition.Meets(sourceInfo)) + elseGroupStore[condition.ElseGroup] = false; + } + } + } + foreach (var i in elseGroupStore) + if (i.Value) + return true; + + return false; + } + + public bool IsObjectMeetToConditions(WorldObject obj, List conditions) + { + ConditionSourceInfo srcInfo = new ConditionSourceInfo(obj); + return IsObjectMeetToConditions(srcInfo, conditions); + } + + public bool IsObjectMeetToConditions(WorldObject obj1, WorldObject obj2, List conditions) + { + ConditionSourceInfo srcInfo = new ConditionSourceInfo(obj1, obj2); + return IsObjectMeetToConditions(srcInfo, conditions); + } + + public bool IsObjectMeetToConditions(ConditionSourceInfo sourceInfo, List conditions) + { + if (conditions.Empty()) + return true; + + Log.outDebug(LogFilter.Condition, "ConditionMgr.IsObjectMeetToConditions"); + return IsObjectMeetToConditionList(sourceInfo, conditions); + } + + public bool CanHaveSourceGroupSet(ConditionSourceType sourceType) + { + return (sourceType == ConditionSourceType.CreatureLootTemplate || + sourceType == ConditionSourceType.DisenchantLootTemplate || + sourceType == ConditionSourceType.FishingLootTemplate || + sourceType == ConditionSourceType.GameobjectLootTemplate || + sourceType == ConditionSourceType.ItemLootTemplate || + sourceType == ConditionSourceType.MailLootTemplate || + sourceType == ConditionSourceType.MillingLootTemplate || + sourceType == ConditionSourceType.PickpocketingLootTemplate || + sourceType == ConditionSourceType.ProspectingLootTemplate || + sourceType == ConditionSourceType.ReferenceLootTemplate || + sourceType == ConditionSourceType.SkinningLootTemplate || + sourceType == ConditionSourceType.SpellLootTemplate || + sourceType == ConditionSourceType.GossipMenu || + sourceType == ConditionSourceType.GossipMenuOption || + sourceType == ConditionSourceType.VehicleSpell || + sourceType == ConditionSourceType.SpellImplicitTarget || + sourceType == ConditionSourceType.SpellClickEvent || + sourceType == ConditionSourceType.SmartEvent || + sourceType == ConditionSourceType.NpcVendor || + sourceType == ConditionSourceType.Phase); + } + + public bool CanHaveSourceIdSet(ConditionSourceType sourceType) + { + return (sourceType == ConditionSourceType.SmartEvent); + } + + public bool IsObjectMeetingNotGroupedConditions(ConditionSourceType sourceType, uint entry, ConditionSourceInfo sourceInfo) + { + if (sourceType > ConditionSourceType.None && sourceType < ConditionSourceType.Max) + { + var conditions = ConditionStore[sourceType].LookupByKey(entry); + if (!conditions.Empty()) + { + Log.outDebug(LogFilter.Condition, "GetConditionsForNotGroupedEntry: found conditions for type {0} and entry {1}", sourceType, entry); + return IsObjectMeetToConditions(sourceInfo, conditions); + } + } + + return true; + } + + public bool IsObjectMeetingNotGroupedConditions(ConditionSourceType sourceType, uint entry, WorldObject target0, WorldObject target1 = null, WorldObject target2 = null) + { + ConditionSourceInfo conditionSource = new ConditionSourceInfo(target0, target1, target2); + return IsObjectMeetingNotGroupedConditions(sourceType, entry, conditionSource); + } + + public bool HasConditionsForNotGroupedEntry(ConditionSourceType sourceType, uint entry) + { + if (sourceType > ConditionSourceType.None && sourceType < ConditionSourceType.Max) + if (ConditionStore[sourceType].ContainsKey(entry)) + return true; + + return false; + } + + public bool IsObjectMeetingSpellClickConditions(uint creatureId, uint spellId, WorldObject clicker, WorldObject target) + { + var multiMap = SpellClickEventConditionStore.LookupByKey(creatureId); + if (multiMap != null) + { + var conditions = multiMap.LookupByKey(spellId); + if (!conditions.Empty()) + { + Log.outDebug(LogFilter.Condition, "GetConditionsForSpellClickEvent: found conditions for SpellClickEvent entry {0} spell {1}", creatureId, spellId); + ConditionSourceInfo sourceInfo = new ConditionSourceInfo(clicker, target); + return IsObjectMeetToConditions(sourceInfo, conditions); + } + } + return true; + } + + public List GetConditionsForSpellClickEvent(uint creatureId, uint spellId) + { + var multiMap = SpellClickEventConditionStore.LookupByKey(creatureId); + if (multiMap != null) + { + var conditions = multiMap.LookupByKey(spellId); + if (!conditions.Empty()) + { + Log.outDebug(LogFilter.Condition, "GetConditionsForSpellClickEvent: found conditions for SpellClickEvent entry {0} spell {1}", creatureId, spellId); + return conditions; + } + } + return null; + } + + public bool IsObjectMeetingVehicleSpellConditions(uint creatureId, uint spellId, Player player, Unit vehicle) + { + var multiMap = VehicleSpellConditionStore.LookupByKey(creatureId); + if (multiMap != null) + { + var conditions = multiMap.LookupByKey(spellId); + if (!conditions.Empty()) + { + Log.outDebug(LogFilter.Condition, "GetConditionsForVehicleSpell: found conditions for Vehicle entry {0} spell {1}", creatureId, spellId); + ConditionSourceInfo sourceInfo = new ConditionSourceInfo(player, vehicle); + return IsObjectMeetToConditions(sourceInfo, conditions); + } + } + return true; + } + + public bool IsObjectMeetingSmartEventConditions(long entryOrGuid, uint eventId, SmartScriptType sourceType, Unit unit, WorldObject baseObject) + { + var multiMap = SmartEventConditionStore.LookupByKey(Tuple.Create((int)entryOrGuid, (uint)sourceType)); + if (multiMap != null) + { + var conditions = multiMap.LookupByKey(eventId + 1); + if (!conditions.Empty()) + { + Log.outDebug(LogFilter.Condition, "GetConditionsForSmartEvent: found conditions for Smart Event entry or guid {0} eventId {1}", entryOrGuid, eventId); + ConditionSourceInfo sourceInfo = new ConditionSourceInfo(unit, baseObject); + return IsObjectMeetToConditions(sourceInfo, conditions); + } + } + return true; + } + + public bool IsObjectMeetingVendorItemConditions(uint creatureId, uint itemId, Player player, Creature vendor) + { + var multiMap = NpcVendorConditionContainerStore.LookupByKey(creatureId); + if (multiMap != null) + { + var conditions = multiMap.LookupByKey(itemId); + if (!conditions.Empty()) + { + Log.outDebug(LogFilter.Condition, "GetConditionsForNpcVendorEvent: found conditions for creature entry {0} item {1}", creatureId, itemId); + ConditionSourceInfo sourceInfo = new ConditionSourceInfo(player, vendor); + return IsObjectMeetToConditions(sourceInfo, conditions); + } + } + return true; + } + + public void LoadConditions(bool isReload = false) + { + uint oldMSTime = Time.GetMSTime(); + + Clean(); + + //must clear all custom handled cases (groupped types) before reload + if (isReload) + { + Log.outInfo(LogFilter.Server, "Reseting Loot Conditions..."); + LootStorage.Creature.ResetConditions(); + LootStorage.Fishing.ResetConditions(); + LootStorage.Gameobject.ResetConditions(); + LootStorage.Items.ResetConditions(); + LootStorage.Mail.ResetConditions(); + LootStorage.Milling.ResetConditions(); + LootStorage.Pickpocketing.ResetConditions(); + LootStorage.Reference.ResetConditions(); + LootStorage.Skinning.ResetConditions(); + LootStorage.Disenchant.ResetConditions(); + LootStorage.Prospecting.ResetConditions(); + LootStorage.Spell.ResetConditions(); + + Log.outInfo(LogFilter.Server, "Re-Loading `gossip_menu` Table for Conditions!"); + Global.ObjectMgr.LoadGossipMenu(); + + Log.outInfo(LogFilter.Server, "Re-Loading `gossip_menu_option` Table for Conditions!"); + Global.ObjectMgr.LoadGossipMenuItems(); + Global.SpellMgr.UnloadSpellInfoImplicitTargetConditionLists(); + + Log.outInfo(LogFilter.Server, "Re-Loading `terrain_phase_info` Table for Conditions!"); + Global.ObjectMgr.LoadTerrainPhaseInfo(); + + Log.outInfo(LogFilter.Server, "Re-Loading `terrain_swap_defaults` Table for Conditions!"); + Global.ObjectMgr.LoadTerrainSwapDefaults(); + + Log.outInfo(LogFilter.Server, "Re-Loading `terrain_worldmap` Table for Conditions!"); + Global.ObjectMgr.LoadTerrainWorldMaps(); + + Log.outInfo(LogFilter.Server, "Re-Loading `phase_area` Table for Conditions!"); + Global.ObjectMgr.LoadAreaPhases(); + } + + SQLResult result = DB.World.Query("SELECT SourceTypeOrReferenceId, SourceGroup, SourceEntry, SourceId, ElseGroup, ConditionTypeOrReference, ConditionTarget, " + + " ConditionValue1, ConditionValue2, ConditionValue3, NegativeCondition, ErrorType, ErrorTextId, ScriptName FROM conditions"); + + if (result.IsEmpty()) + { + Log.outError(LogFilter.ServerLoading, "Loaded 0 conditions. DB table `conditions` is empty!"); + return; + } + + uint count = 0; + do + { + Condition cond = new Condition(); + int iSourceTypeOrReferenceId = result.Read(0); + cond.SourceGroup = result.Read(1); + cond.SourceEntry = result.Read(2); + cond.SourceId = result.Read(3); + cond.ElseGroup = result.Read(4); + int iConditionTypeOrReference = result.Read(5); + cond.ConditionTarget = result.Read(6); + cond.ConditionValue1 = result.Read(7); + cond.ConditionValue2 = result.Read(8); + cond.ConditionValue3 = result.Read(9); + cond.NegativeCondition = result.Read(10) != 0; + cond.ErrorType = result.Read(11); + cond.ErrorTextId = result.Read(12); + cond.ScriptId = Global.ObjectMgr.GetScriptId(result.Read(13)); + + if (iConditionTypeOrReference >= 0) + cond.ConditionType = (ConditionTypes)iConditionTypeOrReference; + + if (iSourceTypeOrReferenceId >= 0) + cond.SourceType = (ConditionSourceType)iSourceTypeOrReferenceId; + + if (iConditionTypeOrReference < 0)//it has a reference + { + if (iConditionTypeOrReference == iSourceTypeOrReferenceId)//self referencing, skip + { + Log.outError(LogFilter.Sql, "Condition reference {1} is referencing self, skipped", iSourceTypeOrReferenceId); + continue; + } + cond.ReferenceId = (uint)Math.Abs(iConditionTypeOrReference); + + string rowType = "reference template"; + if (iSourceTypeOrReferenceId >= 0) + rowType = "reference"; + //check for useless data + if (cond.ConditionTarget != 0) + Log.outError(LogFilter.Sql, "Condition {0} {1} has useless data in ConditionTarget ({2})!", rowType, iSourceTypeOrReferenceId, cond.ConditionTarget); + if (cond.ConditionValue1 != 0) + Log.outError(LogFilter.Sql, "Condition {0} {1} has useless data in value1 ({2})!", rowType, iSourceTypeOrReferenceId, cond.ConditionValue1); + if (cond.ConditionValue2 != 0) + Log.outError(LogFilter.Sql, "Condition {0} {1} has useless data in value2 ({2})!", rowType, iSourceTypeOrReferenceId, cond.ConditionValue2); + if (cond.ConditionValue3 != 0) + Log.outError(LogFilter.Sql, "Condition {0} {1} has useless data in value3 ({2})!", rowType, iSourceTypeOrReferenceId, cond.ConditionValue3); + if (cond.NegativeCondition) + Log.outError(LogFilter.Sql, "Condition {0} {1} has useless data in NegativeCondition ({2})!", rowType, iSourceTypeOrReferenceId, cond.NegativeCondition); + if (cond.SourceGroup != 0 && iSourceTypeOrReferenceId < 0) + Log.outError(LogFilter.Sql, "Condition {0} {1} has useless data in SourceGroup ({2})!", rowType, iSourceTypeOrReferenceId, cond.SourceGroup); + if (cond.SourceEntry != 0 && iSourceTypeOrReferenceId < 0) + Log.outError(LogFilter.Sql, "Condition {0} {1} has useless data in SourceEntry ({2})!", rowType, iSourceTypeOrReferenceId, cond.SourceEntry); + } + else if (!isConditionTypeValid(cond))//doesn't have reference, validate ConditionType + continue; + + if (iSourceTypeOrReferenceId < 0)//it is a reference template + { + ConditionReferenceStore.Add((uint)Math.Abs(iSourceTypeOrReferenceId), cond);//add to reference storage + count++; + continue; + }//end of reference templates + + //if not a reference and SourceType is invalid, skip + if (iConditionTypeOrReference >= 0 && !isSourceTypeValid(cond)) + continue; + + //Grouping is only allowed for some types (loot templates, gossip menus, gossip items) + if (cond.SourceGroup != 0 && !CanHaveSourceGroupSet(cond.SourceType)) + { + Log.outError(LogFilter.Sql, "{0} has not allowed value of SourceGroup = {1}!", cond.ToString(), cond.SourceGroup); + continue; + } + if (cond.SourceId != 0 && !CanHaveSourceIdSet(cond.SourceType)) + { + Log.outError(LogFilter.Sql, "{0} has not allowed value of SourceId = {1}!", cond.ToString(), cond.SourceId); + continue; + } + + if (cond.ErrorType != 0 && cond.SourceType != ConditionSourceType.Spell) + { + Log.outError(LogFilter.Sql, "{0} can't have ErrorType ({1}), set to 0!", cond.ToString(), cond.ErrorType); + cond.ErrorType = 0; + } + + if (cond.ErrorTextId != 0 && cond.ErrorType == 0) + { + Log.outError(LogFilter.Sql, "{0} has any ErrorType, ErrorTextId ({1}) is set, set to 0!", cond.ToString(), cond.ErrorTextId); + cond.ErrorTextId = 0; + } + + if (cond.SourceGroup != 0) + { + bool valid = false; + // handle grouped conditions + switch (cond.SourceType) + { + case ConditionSourceType.CreatureLootTemplate: + valid = addToLootTemplate(cond, LootStorage.Creature.GetLootForConditionFill(cond.SourceGroup)); + break; + case ConditionSourceType.DisenchantLootTemplate: + valid = addToLootTemplate(cond, LootStorage.Disenchant.GetLootForConditionFill(cond.SourceGroup)); + break; + case ConditionSourceType.FishingLootTemplate: + valid = addToLootTemplate(cond, LootStorage.Fishing.GetLootForConditionFill(cond.SourceGroup)); + break; + case ConditionSourceType.GameobjectLootTemplate: + valid = addToLootTemplate(cond, LootStorage.Gameobject.GetLootForConditionFill(cond.SourceGroup)); + break; + case ConditionSourceType.ItemLootTemplate: + valid = addToLootTemplate(cond, LootStorage.Items.GetLootForConditionFill(cond.SourceGroup)); + break; + case ConditionSourceType.MailLootTemplate: + valid = addToLootTemplate(cond, LootStorage.Mail.GetLootForConditionFill(cond.SourceGroup)); + break; + case ConditionSourceType.MillingLootTemplate: + valid = addToLootTemplate(cond, LootStorage.Milling.GetLootForConditionFill(cond.SourceGroup)); + break; + case ConditionSourceType.PickpocketingLootTemplate: + valid = addToLootTemplate(cond, LootStorage.Pickpocketing.GetLootForConditionFill(cond.SourceGroup)); + break; + case ConditionSourceType.ProspectingLootTemplate: + valid = addToLootTemplate(cond, LootStorage.Prospecting.GetLootForConditionFill(cond.SourceGroup)); + break; + case ConditionSourceType.ReferenceLootTemplate: + valid = addToLootTemplate(cond, LootStorage.Reference.GetLootForConditionFill(cond.SourceGroup)); + break; + case ConditionSourceType.SkinningLootTemplate: + valid = addToLootTemplate(cond, LootStorage.Skinning.GetLootForConditionFill(cond.SourceGroup)); + break; + case ConditionSourceType.SpellLootTemplate: + valid = addToLootTemplate(cond, LootStorage.Spell.GetLootForConditionFill(cond.SourceGroup)); + break; + case ConditionSourceType.GossipMenu: + valid = addToGossipMenus(cond); + break; + case ConditionSourceType.GossipMenuOption: + valid = addToGossipMenuItems(cond); + break; + case ConditionSourceType.SpellClickEvent: + { + if (!SpellClickEventConditionStore.ContainsKey(cond.SourceGroup)) + SpellClickEventConditionStore[cond.SourceGroup] = new MultiMap(); + + SpellClickEventConditionStore[cond.SourceGroup].Add((uint)cond.SourceEntry, cond); + valid = true; + ++count; + continue; // do not add to m_AllocatedMemory to avoid double deleting + } + case ConditionSourceType.SpellImplicitTarget: + valid = addToSpellImplicitTargetConditions(cond); + break; + case ConditionSourceType.VehicleSpell: + { + if (!VehicleSpellConditionStore.ContainsKey(cond.SourceGroup)) + VehicleSpellConditionStore[cond.SourceGroup] = new MultiMap(); + + VehicleSpellConditionStore[cond.SourceGroup].Add((uint)cond.SourceEntry, cond); + valid = true; + ++count; + continue; // do not add to m_AllocatedMemory to avoid double deleting + } + case ConditionSourceType.SmartEvent: + { + //! TODO: PAIR_32 ? + var key = Tuple.Create(cond.SourceEntry, cond.SourceId); + if (!SmartEventConditionStore.ContainsKey(key)) + SmartEventConditionStore[key] = new MultiMap(); + + SmartEventConditionStore[key].Add(cond.SourceGroup, cond); + valid = true; + ++count; + continue; + } + case ConditionSourceType.NpcVendor: + { + if (!NpcVendorConditionContainerStore.ContainsKey(cond.SourceGroup)) + NpcVendorConditionContainerStore[cond.SourceGroup] = new MultiMap(); + + NpcVendorConditionContainerStore[cond.SourceGroup].Add((uint)cond.SourceEntry, cond); + valid = true; + ++count; + continue; + } + case ConditionSourceType.Phase: + valid = addToPhases(cond); + break; + default: + break; + } + + if (!valid) + Log.outError(LogFilter.Sql, "{0} Not handled grouped condition.", cond.ToString()); + else + ++count; + + continue; + } + + //add new Condition to storage based on Type/Entry + ConditionStore[cond.SourceType].Add((uint)cond.SourceEntry, cond); + ++count; + } + while (result.NextRow()); + + Log.outInfo(LogFilter.ServerLoading, "Loaded {0} conditions in {1} ms", count, Time.GetMSTimeDiffToNow(oldMSTime)); + } + + bool addToLootTemplate(Condition cond, LootTemplate loot) + { + if (loot == null) + { + Log.outError(LogFilter.Sql, "{0} LootTemplate {1} not found.", cond.ToString(), cond.SourceGroup); + return false; + } + + if (loot.addConditionItem(cond)) + return true; + + Log.outError(LogFilter.Sql, "{0} Item {1} not found in LootTemplate {2}.", cond.ToString(), cond.SourceEntry, cond.SourceGroup); + return false; + } + + bool addToGossipMenus(Condition cond) + { + var pMenuBounds = Global.ObjectMgr.GetGossipMenusMapBounds(cond.SourceGroup); + + foreach (var menu in pMenuBounds) + { + if (menu.entry == cond.SourceGroup && menu.text_id == cond.SourceEntry) + { + menu.conditions.Add(cond); + return true; + } + } + + Log.outError(LogFilter.Sql, "{0} GossipMenu {1} not found.", cond.ToString(), cond.SourceGroup); + return false; + } + + bool addToGossipMenuItems(Condition cond) + { + var pMenuItemBounds = Global.ObjectMgr.GetGossipMenuItemsMapBounds(cond.SourceGroup); + foreach (var menuItems in pMenuItemBounds) + { + if (menuItems.MenuId == cond.SourceGroup && menuItems.OptionIndex == cond.SourceEntry) + { + menuItems.Conditions.Add(cond); + return true; + } + } + + Log.outError(LogFilter.Sql, "{0} GossipMenuId {1} Item {2} not found.", cond.ToString(), cond.SourceGroup, cond.SourceEntry); + return false; + } + + bool addToSpellImplicitTargetConditions(Condition cond) + { + uint conditionEffMask = cond.SourceGroup; + SpellInfo spellInfo = Global.SpellMgr.GetSpellInfo((uint)cond.SourceEntry); + Contract.Assert(spellInfo != null); + List sharedMasks = new List(); + for (byte i = 0; i < SpellConst.MaxEffects; ++i) + { + SpellEffectInfo effect = spellInfo.GetEffect(i); + if (effect == null) + continue; + + // check if effect is already a part of some shared mask + bool found = false; + foreach (var value in sharedMasks) + { + if (((1 << i) & value) != 0) + { + found = true; + break; + } + } + if (found) + continue; + + // build new shared mask with found effect + uint sharedMask = (uint)(1 << i); + List cmp = effect.ImplicitTargetConditions; + for (byte effIndex = (byte)(i + 1); effIndex < SpellConst.MaxEffects; ++effIndex) + { + SpellEffectInfo inner = spellInfo.GetEffect(effIndex); + if (inner == null) + continue; + if (inner.ImplicitTargetConditions == cmp) + sharedMask |= (uint)(1 << effIndex); + } + sharedMasks.Add(sharedMask); + } + + foreach (var value in sharedMasks) + { + // some effect indexes should have same data + uint commonMask = (value & conditionEffMask); + if (commonMask != 0) + { + byte firstEffIndex = 0; + for (; firstEffIndex < SpellConst.MaxEffects; ++firstEffIndex) + if (((1 << firstEffIndex) & value) != 0) + break; + + if (firstEffIndex >= SpellConst.MaxEffects) + return false; + + SpellEffectInfo effect = spellInfo.GetEffect(firstEffIndex); + if (effect == null) + continue; + + // get shared data + List sharedList = effect.ImplicitTargetConditions; + + // there's already data entry for that sharedMask + if (sharedList != null) + { + // we have overlapping masks in db + if (conditionEffMask != value) + { + Log.outError(LogFilter.Sql, "{0} in `condition` table, has incorrect SourceGroup {1} (spell effectMask) set - " + + "effect masks are overlapping (all SourceGroup values having given bit set must be equal) - ignoring.", cond.ToString(), cond.SourceGroup); + return false; + } + } + // no data for shared mask, we can create new submask + else + { + // add new list, create new shared mask + sharedList = new List(); + bool assigned = false; + for (byte i = firstEffIndex; i < SpellConst.MaxEffects; ++i) + { + SpellEffectInfo eff = spellInfo.GetEffect(i); + if (eff == null) + continue; + + if (((1 << i) & commonMask) != 0) + { + eff.ImplicitTargetConditions = sharedList; + assigned = true; + } + } + + if (!assigned) + break; + } + sharedList.Add(cond); + break; + } + } + return true; + } + + bool addToPhases(Condition cond) + { + if (cond.SourceEntry == 0) + { + bool found = false; + var map = Global.ObjectMgr.GetAreaAndZonePhases(); + foreach (var key in map.Keys) + { + foreach (PhaseInfoStruct phase in map[key]) + { + if (phase.Id == cond.SourceGroup) + { + phase.Conditions.Add(cond); + found = true; + } + } + } + + if (found) + return true; + } + else + { + var phases = Global.ObjectMgr.GetPhasesForAreaOrZoneForLoading((uint)cond.SourceEntry); + foreach (PhaseInfoStruct phase in phases) + { + if (phase.Id == cond.SourceGroup) + { + phase.Conditions.Add(cond); + return true; + } + } + } + + Log.outError(LogFilter.Sql, "{0} Area {1} does not have phase {2}.", cond.ToString(), cond.SourceGroup, cond.SourceEntry); + return false; + } + + bool isSourceTypeValid(Condition cond) + { + if (cond.SourceType == ConditionSourceType.None || cond.SourceType >= ConditionSourceType.Max) + { + Log.outError(LogFilter.Sql, "{0} Invalid ConditionSourceType in `condition` table, ignoring.", cond.ToString()); + return false; + } + + switch (cond.SourceType) + { + case ConditionSourceType.CreatureLootTemplate: + { + if (!LootStorage.Creature.HaveLootFor(cond.SourceGroup)) + { + Log.outError(LogFilter.Sql, "{0} SourceGroup in `condition` table, does not exist in `creature_loot_template`, ignoring.", cond.ToString()); + return false; + } + + LootTemplate loot = LootStorage.Creature.GetLootForConditionFill(cond.SourceGroup); + ItemTemplate pItemProto = Global.ObjectMgr.GetItemTemplate((uint)cond.SourceEntry); + if (pItemProto == null && !loot.isReference((uint)cond.SourceEntry)) + { + Log.outError(LogFilter.Sql, "{0} SourceType, SourceEntry in `condition` table, does not exist in `item_template`, ignoring.", cond.ToString()); + return false; + } + break; + } + case ConditionSourceType.DisenchantLootTemplate: + { + if (!LootStorage.Disenchant.HaveLootFor(cond.SourceGroup)) + { + Log.outError(LogFilter.Sql, "{0} SourceGroup in `condition` table, does not exist in `disenchant_loot_template`, ignoring.", cond.ToString()); + return false; + } + + LootTemplate loot = LootStorage.Disenchant.GetLootForConditionFill(cond.SourceGroup); + ItemTemplate pItemProto = Global.ObjectMgr.GetItemTemplate((uint)cond.SourceEntry); + if (pItemProto == null && !loot.isReference((uint)cond.SourceEntry)) + { + Log.outError(LogFilter.Sql, "{0} SourceType, SourceEntry in `condition` table, does not exist in `item_template`, ignoring.", cond.ToString()); + return false; + } + break; + } + case ConditionSourceType.FishingLootTemplate: + { + if (!LootStorage.Fishing.HaveLootFor(cond.SourceGroup)) + { + Log.outError(LogFilter.Sql, "{0} SourceGroup in `condition` table, does not exist in `fishing_loot_template`, ignoring.", cond.ToString()); + return false; + } + + LootTemplate loot = LootStorage.Fishing.GetLootForConditionFill(cond.SourceGroup); + ItemTemplate pItemProto = Global.ObjectMgr.GetItemTemplate((uint)cond.SourceEntry); + if (pItemProto == null && !loot.isReference((uint)cond.SourceEntry)) + { + Log.outError(LogFilter.Sql, "{0} SourceType, SourceEntry in `condition` table, does not exist in `item_template`, ignoring.", cond.ToString()); + return false; + } + break; + } + case ConditionSourceType.GameobjectLootTemplate: + { + if (!LootStorage.Gameobject.HaveLootFor(cond.SourceGroup)) + { + Log.outError(LogFilter.Sql, "{0} SourceGroup in `condition` table, does not exist in `gameobject_loot_template`, ignoring.", cond.ToString()); + return false; + } + + LootTemplate loot = LootStorage.Gameobject.GetLootForConditionFill(cond.SourceGroup); + ItemTemplate pItemProto = Global.ObjectMgr.GetItemTemplate((uint)cond.SourceEntry); + if (pItemProto == null && !loot.isReference((uint)cond.SourceEntry)) + { + Log.outError(LogFilter.Sql, "{0} SourceType, SourceEntry in `condition` table, does not exist in `item_template`, ignoring.", cond.ToString()); + return false; + } + break; + } + case ConditionSourceType.ItemLootTemplate: + { + if (!LootStorage.Items.HaveLootFor(cond.SourceGroup)) + { + Log.outError(LogFilter.Sql, "{0} SourceGroup in `condition` table, does not exist in `item_loot_template`, ignoring.", cond.ToString()); + return false; + } + + LootTemplate loot = LootStorage.Items.GetLootForConditionFill(cond.SourceGroup); + ItemTemplate pItemProto = Global.ObjectMgr.GetItemTemplate((uint)cond.SourceEntry); + if (pItemProto == null && !loot.isReference((uint)cond.SourceEntry)) + { + Log.outError(LogFilter.Sql, "{0} SourceType, SourceEntry in `condition` table, does not exist in `item_template`, ignoring.", cond.ToString()); + return false; + } + break; + } + case ConditionSourceType.MailLootTemplate: + { + if (!LootStorage.Mail.HaveLootFor(cond.SourceGroup)) + { + Log.outError(LogFilter.Sql, "{0} SourceGroup in `condition` table, does not exist in `mail_loot_template`, ignoring.", cond.ToString()); + return false; + } + + LootTemplate loot = LootStorage.Mail.GetLootForConditionFill(cond.SourceGroup); + ItemTemplate pItemProto = Global.ObjectMgr.GetItemTemplate((uint)cond.SourceEntry); + if (pItemProto == null && !loot.isReference((uint)cond.SourceEntry)) + { + Log.outError(LogFilter.Sql, "{0} SourceType, SourceEntry in `condition` table, does not exist in `item_template`, ignoring.", cond.ToString()); + return false; + } + break; + } + case ConditionSourceType.MillingLootTemplate: + { + if (!LootStorage.Milling.HaveLootFor(cond.SourceGroup)) + { + Log.outError(LogFilter.Sql, "{0} SourceGroup in `condition` table, does not exist in `milling_loot_template`, ignoring.", cond.ToString()); + return false; + } + + LootTemplate loot = LootStorage.Milling.GetLootForConditionFill(cond.SourceGroup); + ItemTemplate pItemProto = Global.ObjectMgr.GetItemTemplate((uint)cond.SourceEntry); + if (pItemProto == null && !loot.isReference((uint)cond.SourceEntry)) + { + Log.outError(LogFilter.Sql, "{0} SourceType, SourceEntry in `condition` table, does not exist in `item_template`, ignoring.", cond.ToString()); + return false; + } + break; + } + case ConditionSourceType.PickpocketingLootTemplate: + { + if (!LootStorage.Pickpocketing.HaveLootFor(cond.SourceGroup)) + { + Log.outError(LogFilter.Sql, "{0} SourceGroup in `condition` table, does not exist in `pickpocketing_loot_template`, ignoring.", cond.ToString()); + return false; + } + + LootTemplate loot = LootStorage.Pickpocketing.GetLootForConditionFill(cond.SourceGroup); + ItemTemplate pItemProto = Global.ObjectMgr.GetItemTemplate((uint)cond.SourceEntry); + if (pItemProto == null && !loot.isReference((uint)cond.SourceEntry)) + { + Log.outError(LogFilter.Sql, "{0} SourceType, SourceEntry in `condition` table, does not exist in `item_template`, ignoring.", cond.ToString()); + return false; + } + break; + } + case ConditionSourceType.ProspectingLootTemplate: + { + if (!LootStorage.Prospecting.HaveLootFor(cond.SourceGroup)) + { + Log.outError(LogFilter.Sql, "{0} SourceGroup in `condition` table, does not exist in `prospecting_loot_template`, ignoring.", cond.ToString()); + return false; + } + + LootTemplate loot = LootStorage.Prospecting.GetLootForConditionFill(cond.SourceGroup); + ItemTemplate pItemProto = Global.ObjectMgr.GetItemTemplate((uint)cond.SourceEntry); + if (pItemProto == null && !loot.isReference((uint)cond.SourceEntry)) + { + Log.outError(LogFilter.Sql, "{0} SourceType, SourceEntry in `condition` table, does not exist in `item_template`, ignoring.", cond.ToString()); + return false; + } + break; + } + case ConditionSourceType.ReferenceLootTemplate: + { + if (!LootStorage.Reference.HaveLootFor(cond.SourceGroup)) + { + Log.outError(LogFilter.Sql, "{0} SourceGroup in `condition` table, does not exist in `reference_loot_template`, ignoring.", cond.ToString()); + return false; + } + + LootTemplate loot = LootStorage.Reference.GetLootForConditionFill(cond.SourceGroup); + ItemTemplate pItemProto = Global.ObjectMgr.GetItemTemplate((uint)cond.SourceEntry); + if (pItemProto == null && !loot.isReference((uint)cond.SourceEntry)) + { + Log.outError(LogFilter.Sql, "{0} SourceType, SourceEntry in `condition` table, does not exist in `item_template`, ignoring.", cond.ToString()); + return false; + } + break; + } + case ConditionSourceType.SkinningLootTemplate: + { + if (!LootStorage.Skinning.HaveLootFor(cond.SourceGroup)) + { + Log.outError(LogFilter.Sql, "{0} SourceGroup in `condition` table, does not exist in `skinning_loot_template`, ignoring.", cond.ToString()); + return false; + } + + LootTemplate loot = LootStorage.Skinning.GetLootForConditionFill(cond.SourceGroup); + ItemTemplate pItemProto = Global.ObjectMgr.GetItemTemplate((uint)cond.SourceEntry); + if (pItemProto == null && !loot.isReference((uint)cond.SourceEntry)) + { + Log.outError(LogFilter.Sql, "{0} SourceType, SourceEntry in `condition` table, does not exist in `item_template`, ignoring.", cond.ToString()); + return false; + } + break; + } + case ConditionSourceType.SpellLootTemplate: + { + if (!LootStorage.Spell.HaveLootFor(cond.SourceGroup)) + { + Log.outError(LogFilter.Sql, "{0} SourceGroup in `condition` table, does not exist in `spell_loot_template`, ignoring.", cond.ToString()); + return false; + } + + LootTemplate loot = LootStorage.Spell.GetLootForConditionFill(cond.SourceGroup); + ItemTemplate pItemProto = Global.ObjectMgr.GetItemTemplate((uint)cond.SourceEntry); + if (pItemProto == null && !loot.isReference((uint)cond.SourceEntry)) + { + Log.outError(LogFilter.Sql, "{0} SourceType, SourceEntry in `condition` table, does not exist in `item_template`, ignoring.", cond.ToString()); + return false; + } + break; + } + case ConditionSourceType.SpellImplicitTarget: + { + SpellInfo spellInfo = Global.SpellMgr.GetSpellInfo((uint)cond.SourceEntry); + if (spellInfo == null) + { + Log.outError(LogFilter.Sql, "{0} SourceEntry in `condition` table does not exist in `spell.dbc`, ignoring.", cond.ToString()); + return false; + } + + if ((cond.SourceGroup > SpellConst.MaxEffectMask) || cond.SourceGroup == 0) + { + Log.outError(LogFilter.Sql, "{0} in `condition` table, has incorrect SourceGroup (spell effectMask) set, ignoring.", cond.ToString()); + return false; + } + + uint origGroup = cond.SourceGroup; + + for (byte i = 0; i < SpellConst.MaxEffects; ++i) + { + if (((1 << i) & cond.SourceGroup) == 0) + continue; + + SpellEffectInfo effect = spellInfo.GetEffect(Difficulty.None, i); + if (effect == null) + continue; + + if (effect.ChainTargets > 0) + continue; + + switch (effect.TargetA.GetSelectionCategory()) + { + case SpellTargetSelectionCategories.Nearby: + case SpellTargetSelectionCategories.Cone: + case SpellTargetSelectionCategories.Area: + continue; + default: + break; + } + + switch (effect.TargetB.GetSelectionCategory()) + { + case SpellTargetSelectionCategories.Nearby: + case SpellTargetSelectionCategories.Cone: + case SpellTargetSelectionCategories.Area: + continue; + default: + break; + } + + Log.outError(LogFilter.Sql, "SourceEntry {0} SourceGroup {1} in `condition` table - spell {2} does not have implicit targets of types: _AREA_, _CONE_, _NEARBY_, _CHAIN_ for effect {3}, SourceGroup needs correction, ignoring.", cond.SourceEntry, origGroup, cond.SourceEntry, i); + cond.SourceGroup &= ~(uint)(1 << i); + } + // all effects were removed, no need to add the condition at all + if (cond.SourceGroup == 0) + return false; + break; + } + case ConditionSourceType.CreatureTemplateVehicle: + { + if (Global.ObjectMgr.GetCreatureTemplate((uint)cond.SourceEntry) == null) + { + Log.outError(LogFilter.Sql, "{0} SourceEntry in `condition` table does not exist in `creature_template`, ignoring.", cond.ToString()); + return false; + } + break; + } + case ConditionSourceType.Spell: + case ConditionSourceType.SpellProc: + { + SpellInfo spellProto = Global.SpellMgr.GetSpellInfo((uint)cond.SourceEntry); + if (spellProto == null) + { + Log.outError(LogFilter.Sql, "{0} SourceEntry in `condition` table does not exist in `spell.dbc`, ignoring.", cond.ToString()); + return false; + } + break; + } + case ConditionSourceType.QuestAccept: + if (Global.ObjectMgr.GetQuestTemplate((uint)cond.SourceEntry) == null) + { + Log.outError(LogFilter.Sql, "{0} SourceEntry specifies non-existing quest, skipped.", cond.ToString()); + return false; + } + break; + case ConditionSourceType.VehicleSpell: + if (Global.ObjectMgr.GetCreatureTemplate(cond.SourceGroup) == null) + { + Log.outError(LogFilter.Sql, "{0} SourceEntry in `condition` table does not exist in `creature_template`, ignoring.", cond.ToString()); + return false; + } + + if (!Global.SpellMgr.HasSpellInfo((uint)cond.SourceEntry)) + { + Log.outError(LogFilter.Sql, "{0} SourceEntry in `condition` table does not exist in `spell.dbc`, ignoring.", cond.ToString()); + return false; + } + break; + case ConditionSourceType.SpellClickEvent: + if (Global.ObjectMgr.GetCreatureTemplate(cond.SourceGroup) == null) + { + Log.outError(LogFilter.Sql, "{0} SourceEntry in `condition` table does not exist in `creature_template`, ignoring.", cond.ToString()); + return false; + } + + if (!Global.SpellMgr.HasSpellInfo((uint)cond.SourceEntry)) + { + Log.outError(LogFilter.Sql, "{0} SourceEntry in `condition` table does not exist in `spell.dbc`, ignoring.", cond.ToString()); + return false; + } + break; + case ConditionSourceType.NpcVendor: + { + if (Global.ObjectMgr.GetCreatureTemplate(cond.SourceGroup) == null) + { + Log.outError(LogFilter.Sql, "{0} SourceEntry in `condition` table does not exist in `creature_template`, ignoring.", cond.ToString()); + return false; + } + ItemTemplate itemTemplate = Global.ObjectMgr.GetItemTemplate((uint)cond.SourceEntry); + if (itemTemplate == null) + { + Log.outError(LogFilter.Sql, "{0} SourceEntry in `condition` table does not exist in `item_template`, ignoring.", cond.ToString()); + return false; + } + break; + } + case ConditionSourceType.TerrainSwap: + if (!CliDB.MapStorage.ContainsKey((uint)cond.SourceEntry)) + { + Log.outError(LogFilter.Sql, "{0} SourceEntry in `condition` table does not exist in Map.dbc, ignoring.", cond.ToString()); + return false; + } + break; + case ConditionSourceType.Phase: + if (cond.SourceEntry != 0 && !CliDB.AreaTableStorage.ContainsKey(cond.SourceEntry)) + { + Log.outError(LogFilter.Sql, "{0} SourceEntry in `condition` table does not exist in AreaTable.dbc, ignoring.", cond.ToString()); + return false; + } + break; + case ConditionSourceType.GossipMenu: + case ConditionSourceType.GossipMenuOption: + case ConditionSourceType.SmartEvent: + case ConditionSourceType.None: + default: + break; + } + + return true; + } + + bool isConditionTypeValid(Condition cond) + { + if (cond.ConditionType == ConditionTypes.None || cond.ConditionType >= ConditionTypes.Max) + { + Log.outError(LogFilter.Sql, "{0} Invalid ConditionType in `condition` table, ignoring.", cond.ToString()); + return false; + } + + if (cond.ConditionTarget >= cond.GetMaxAvailableConditionTargets()) + { + Log.outError(LogFilter.Sql, "{0} in `condition` table, has incorrect ConditionTarget set, ignoring.", cond.ToString()); + return false; + } + + switch (cond.ConditionType) + { + case ConditionTypes.Aura: + { + if (!Global.SpellMgr.HasSpellInfo(cond.ConditionValue1)) + { + Log.outError(LogFilter.Sql, "{0} has non existing spell (Id: {1}), skipped", cond.ToString(), cond.ConditionValue1); + return false; + } + + if (cond.ConditionValue2 > 2) + { + Log.outError(LogFilter.Sql, "{0} has non existing effect index ({1}) (must be 0..2), skipped", cond.ToString(), cond.ConditionValue2); + return false; + } + break; + } + case ConditionTypes.Item: + { + ItemTemplate proto = Global.ObjectMgr.GetItemTemplate(cond.ConditionValue1); + if (proto == null) + { + Log.outError(LogFilter.Sql, "{0} Item ({1}) does not exist, skipped", cond.ToString(), cond.ConditionValue1); + return false; + } + + if (cond.ConditionValue2 == 0) + { + Log.outError(LogFilter.Sql, "{0} Zero item count in ConditionValue2, skipped", cond.ToString()); + return false; + } + break; + } + case ConditionTypes.ItemEquipped: + { + ItemTemplate proto = Global.ObjectMgr.GetItemTemplate(cond.ConditionValue1); + if (proto == null) + { + Log.outError(LogFilter.Sql, "{0} Item ({1}) does not exist, skipped.", cond.ToString(true), cond.ConditionValue1); + return false; + } + break; + } + case ConditionTypes.Zoneid: + { + AreaTableRecord areaEntry = CliDB.AreaTableStorage.LookupByKey(cond.ConditionValue1); + if (areaEntry == null) + { + Log.outError(LogFilter.Sql, "{0} Area ({1}) does not exist, skipped.", cond.ToString(true), cond.ConditionValue1); + return false; + } + + if (areaEntry.ParentAreaID != 0) + { + Log.outError(LogFilter.Sql, "{0} requires to be in area ({1}) which is a subzone but zone expected, skipped.", cond.ToString(true), cond.ConditionValue1); + return false; + } + break; + } + case ConditionTypes.ReputationRank: + { + FactionRecord factionEntry = CliDB.FactionStorage.LookupByKey(cond.ConditionValue1); + if (factionEntry == null) + { + Log.outError(LogFilter.Sql, "{0} has non existing faction ({1}), skipped.", cond.ToString(true), cond.ConditionValue1); + return false; + } + break; + } + case ConditionTypes.Team: + { + if (cond.ConditionValue1 != (uint)Team.Alliance && cond.ConditionValue1 != (uint)Team.Horde) + { + Log.outError(LogFilter.Sql, "{0} specifies unknown team ({1}), skipped.", cond.ToString(true), cond.ConditionValue1); + return false; + } + break; + } + case ConditionTypes.Skill: + { + SkillLineRecord pSkill = CliDB.SkillLineStorage.LookupByKey(cond.ConditionValue1); + if (pSkill == null) + { + Log.outError(LogFilter.Sql, "{0} specifies non-existing skill ({1}), skipped.", cond.ToString(true), cond.ConditionValue1); + return false; + } + + if (cond.ConditionValue2 < 1 || cond.ConditionValue2 > Global.WorldMgr.GetConfigMaxSkillValue()) + { + Log.outError(LogFilter.Sql, "{0} specifies skill ({1}) with invalid value ({1}), skipped.", cond.ToString(true), cond.ConditionValue1, cond.ConditionValue2); + return false; + } + break; + } + case ConditionTypes.Queststate: + if (cond.ConditionValue2 >= (1 << (int)QuestStatus.Max)) + { + Log.outError(LogFilter.Sql, "{0} has invalid state mask ({1}), skipped.", cond.ToString(true), cond.ConditionValue2); + return false; + } + + if (Global.ObjectMgr.GetQuestTemplate(cond.ConditionValue1) == null) + { + Log.outError(LogFilter.Sql, "{0} points to non-existing quest ({1}), skipped.", cond.ToString(true), cond.ConditionValue1); + return false; + } + break; + case ConditionTypes.QuestRewarded: + case ConditionTypes.QuestTaken: + case ConditionTypes.QuestNone: + case ConditionTypes.QuestComplete: + case ConditionTypes.DailyQuestDone: + { + if (Global.ObjectMgr.GetQuestTemplate(cond.ConditionValue1) == null) + { + Log.outError(LogFilter.Sql, "{0} points to non-existing quest ({1}), skipped.", cond.ToString(true), cond.ConditionValue1); + return false; + } + break; + } + case ConditionTypes.ActiveEvent: + { + var events = Global.GameEventMgr.GetEventMap(); + if (cond.ConditionValue1 >= events.Length || !events[cond.ConditionValue1].isValid()) + { + Log.outError(LogFilter.Sql, "{0} has non existing event id ({1}), skipped.", cond.ToString(true), cond.ConditionValue1); + return false; + } + break; + } + case ConditionTypes.Achievement: + { + AchievementRecord achievement = CliDB.AchievementStorage.LookupByKey(cond.ConditionValue1); + if (achievement == null) + { + Log.outError(LogFilter.Sql, "{0} has non existing achivement id ({1}), skipped.", cond.ToString(true), cond.ConditionValue1); + return false; + } + break; + } + case ConditionTypes.Class: + { + if (!Convert.ToBoolean(cond.ConditionValue1 & (uint)Class.ClassMaskAllPlayable)) + { + Log.outError(LogFilter.Sql, "{0} has non existing classmask ({1}), skipped.", cond.ToString(true), cond.ConditionValue1 & ~(uint)Class.ClassMaskAllPlayable); + return false; + } + break; + } + case ConditionTypes.Race: + { + if (!Convert.ToBoolean(cond.ConditionValue1 & (uint)Race.RaceMaskAllPlayable)) + { + Log.outError(LogFilter.Sql, "{0} has non existing racemask ({1}), skipped.", cond.ToString(true), cond.ConditionValue1 & ~(uint)Race.RaceMaskAllPlayable); + return false; + } + break; + } + case ConditionTypes.Gender: + { + if (!Player.IsValidGender((Gender)cond.ConditionValue1)) + { + Log.outError(LogFilter.Sql, "{0} has invalid gender ({1}), skipped.", cond.ToString(true), cond.ConditionValue1); + return false; + } + break; + } + case ConditionTypes.Mapid: + { + MapRecord me = CliDB.MapStorage.LookupByKey(cond.ConditionValue1); + if (me == null) + { + Log.outError(LogFilter.Sql, "{0} has non existing map ({1}), skipped", cond.ToString(true), cond.ConditionValue1); + return false; + } + break; + } + case ConditionTypes.Spell: + { + if (!Global.SpellMgr.HasSpellInfo(cond.ConditionValue1)) + { + Log.outError(LogFilter.Sql, "{0} has non existing spell (Id: {1}), skipped", cond.ToString(true), cond.ConditionValue1); + return false; + } + break; + } + case ConditionTypes.Level: + { + if (cond.ConditionValue2 >= (uint)ComparisionType.Max) + { + Log.outError(LogFilter.Sql, "{0} has invalid ComparisionType ({1}), skipped.", cond.ToString(true), cond.ConditionValue2); + return false; + } + break; + } + case ConditionTypes.DrunkenState: + { + if (cond.ConditionValue1 > (uint)DrunkenState.Smashed) + { + Log.outError(LogFilter.Sql, "{0} has invalid state ({1}), skipped.", cond.ToString(true), cond.ConditionValue1); + return false; + } + break; + } + case ConditionTypes.NearCreature: + { + if (Global.ObjectMgr.GetCreatureTemplate(cond.ConditionValue1) == null) + { + Log.outError(LogFilter.Sql, "{0} has non existing creature template entry ({1}), skipped", cond.ToString(true), cond.ConditionValue1); + return false; + } + break; + } + case ConditionTypes.NearGameobject: + { + if (Global.ObjectMgr.GetGameObjectTemplate(cond.ConditionValue1) == null) + { + Log.outError(LogFilter.Sql, "{0} has non existing gameobject template entry ({1}), skipped.", cond.ToString(true), cond.ConditionValue1); + return false; + } + break; + } + case ConditionTypes.ObjectEntryGuid: + { + switch ((TypeId)cond.ConditionValue1) + { + case TypeId.Unit: + if (cond.ConditionValue2 != 0 && Global.ObjectMgr.GetCreatureTemplate(cond.ConditionValue2) == null) + { + Log.outError(LogFilter.Sql, "{0} has non existing creature template entry ({1}), skipped.", cond.ToString(true), cond.ConditionValue2); + return false; + } + if (cond.ConditionValue3 != 0) + { + CreatureData creatureData = Global.ObjectMgr.GetCreatureData(cond.ConditionValue3); + if (creatureData != null) + { + if (cond.ConditionValue2 != 0 && creatureData.id != cond.ConditionValue2) + { + Log.outError(LogFilter.Sql, "{0} has guid {1} set but does not match creature entry ({1}), skipped.", cond.ToString(true), cond.ConditionValue3, cond.ConditionValue2); + return false; + } + } + else + { + Log.outError(LogFilter.Sql, "{0} has non existing creature guid ({1}), skipped.", cond.ToString(true), cond.ConditionValue3); + return false; + } + } + break; + case TypeId.GameObject: + if (cond.ConditionValue2 != 0 && Global.ObjectMgr.GetGameObjectTemplate(cond.ConditionValue2) == null) + { + Log.outError(LogFilter.Sql, "{0} has non existing gameobject template entry ({1}), skipped.", cond.ToString(true), cond.ConditionValue2); + return false; + } + if (cond.ConditionValue3 != 0) + { + GameObjectData goData = Global.ObjectMgr.GetGOData(cond.ConditionValue3); + if (goData != null) + { + if (cond.ConditionValue2 != 0 && goData.id != cond.ConditionValue2) + { + Log.outError(LogFilter.Sql, "{0} has guid {1} set but does not match gameobject entry ({1}), skipped.", cond.ToString(true), cond.ConditionValue3, cond.ConditionValue2); + return false; + } + } + else + { + Log.outError(LogFilter.Sql, "{0} has non existing gameobject guid ({1}), skipped.", cond.ToString(true), cond.ConditionValue3); + return false; + } + } + break; + case TypeId.Player: + case TypeId.Corpse: + if (cond.ConditionValue2 != 0) + LogUselessConditionValue(cond, 2, cond.ConditionValue2); + if (cond.ConditionValue3 != 0) + LogUselessConditionValue(cond, 3, cond.ConditionValue3); + break; + default: + Log.outError(LogFilter.Sql, "{0} has wrong typeid set ({1}), skipped", cond.ToString(true), cond.ConditionValue1); + return false; + } + break; + } + case ConditionTypes.TypeMask: + { + if (cond.ConditionValue1 == 0 || Convert.ToBoolean(cond.ConditionValue1 & ~(uint)(TypeMask.Unit | TypeMask.Player | TypeMask.GameObject | TypeMask.Corpse))) + { + Log.outError(LogFilter.Sql, "{0} has invalid typemask set ({1}), skipped.", cond.ToString(true), cond.ConditionValue2); + return false; + } + break; + } + case ConditionTypes.RelationTo: + { + if (cond.ConditionValue1 >= cond.GetMaxAvailableConditionTargets()) + { + Log.outError(LogFilter.Sql, "{0} has invalid ConditionValue1(ConditionTarget selection) ({1}), skipped.", cond.ToString(true), cond.ConditionValue1); + return false; + } + if (cond.ConditionValue1 == cond.ConditionTarget) + { + Log.outError(LogFilter.Sql, "{0} has ConditionValue1(ConditionTarget selection) set to self ({1}), skipped.", cond.ToString(true), cond.ConditionValue1); + return false; + } + if (cond.ConditionValue2 >= (uint)RelationType.Max) + { + Log.outError(LogFilter.Sql, "{0} has invalid ConditionValue2(RelationType) ({1}), skipped.", cond.ToString(true), cond.ConditionValue2); + return false; + } + break; + } + case ConditionTypes.ReactionTo: + { + if (cond.ConditionValue1 >= cond.GetMaxAvailableConditionTargets()) + { + Log.outError(LogFilter.Sql, "{0} has invalid ConditionValue1(ConditionTarget selection) ({1}), skipped.", cond.ToString(true), cond.ConditionValue1); + return false; + } + if (cond.ConditionValue1 == cond.ConditionTarget) + { + Log.outError(LogFilter.Sql, "{0} has ConditionValue1(ConditionTarget selection) set to self ({1}), skipped.", cond.ToString(true), cond.ConditionValue1); + return false; + } + if (cond.ConditionValue2 == 0) + { + Log.outError(LogFilter.Sql, "{0} has invalid ConditionValue2(rankMask) ({1}), skipped.", cond.ToString(true), cond.ConditionValue2); + return false; + } + break; + } + case ConditionTypes.DistanceTo: + { + if (cond.ConditionValue1 >= cond.GetMaxAvailableConditionTargets()) + { + Log.outError(LogFilter.Sql, "{0} has invalid ConditionValue1(ConditionTarget selection) ({1}), skipped.", cond.ToString(true), cond.ConditionValue1); + return false; + } + if (cond.ConditionValue1 == cond.ConditionTarget) + { + Log.outError(LogFilter.Sql, "{0} has ConditionValue1(ConditionTarget selection) set to self ({1}), skipped.", cond.ToString(true), cond.ConditionValue1); + return false; + } + if (cond.ConditionValue3 >= (uint)ComparisionType.Max) + { + Log.outError(LogFilter.Sql, "{0} has invalid ComparisionType ({1}), skipped.", cond.ToString(true), cond.ConditionValue3); + return false; + } + break; + } + case ConditionTypes.HpVal: + { + if (cond.ConditionValue2 >= (uint)ComparisionType.Max) + { + Log.outError(LogFilter.Sql, "{0} has invalid ComparisionType ({1}), skipped.", cond.ToString(true), cond.ConditionValue2); + return false; + } + break; + } + case ConditionTypes.HpPct: + { + if (cond.ConditionValue1 > 100) + { + Log.outError(LogFilter.Sql, "{0} has too big percent value ({1}), skipped.", cond.ToString(true), cond.ConditionValue1); + return false; + } + if (cond.ConditionValue2 >= (uint)ComparisionType.Max) + { + Log.outError(LogFilter.Sql, "{0} has invalid ComparisionType ({1}), skipped.", cond.ToString(true), cond.ConditionValue2); + return false; + } + break; + } + case ConditionTypes.WorldState: + { + if (Global.WorldMgr.getWorldState((WorldStates)cond.ConditionValue1) == 0) + { + Log.outError(LogFilter.Sql, "{0} has non existing world state in value1 ({1}), skipped.", cond.ToString(true), cond.ConditionValue1); + return false; + } + break; + } + case ConditionTypes.PhaseId: + { + if (!CliDB.PhaseStorage.ContainsKey(cond.ConditionValue1)) + { + Log.outError(LogFilter.Sql, "{0} has nonexistent phaseid in value1 ({1}), skipped", cond.ToString(true), cond.ConditionValue1); + return false; + } + break; + } + case ConditionTypes.Title: + { + CharTitlesRecord titleEntry = CliDB.CharTitlesStorage.LookupByKey(cond.ConditionValue1); + if (titleEntry == null) + { + Log.outError(LogFilter.Sql, "{0} has non existing title in value1 ({1}), skipped.", cond.ToString(true), cond.ConditionValue1); + return false; + } + break; + } + case ConditionTypes.Spawnmask: + { + if (cond.ConditionValue1 > (uint)SpawnMask.RaidAll) + { + Log.outError(LogFilter.Sql, "{0} has non existing SpawnMask in value1 ({1}), skipped.", cond.ToString(true), cond.ConditionValue1); + return false; + } + break; + } + case ConditionTypes.UnitState: + { + if (cond.ConditionValue1 > (uint)UnitState.AllState) + { + Log.outError(LogFilter.Sql, "{0} has non existing UnitState in value1 ({1}), skipped.", cond.ToString(true), cond.ConditionValue1); + return false; + } + break; + } + case ConditionTypes.CreatureType: + { + if (cond.ConditionValue1 == 0 || cond.ConditionValue1 > (uint)CreatureType.GasCloud) + { + Log.outError(LogFilter.Sql, "{0} has non existing CreatureType in value1 ({1}), skipped.", cond.ToString(true), cond.ConditionValue1); + return false; + } + break; + } + case ConditionTypes.RealmAchievement: + { + AchievementRecord achievement = CliDB.AchievementStorage.LookupByKey(cond.ConditionValue1); + if (achievement == null) + { + Log.outError(LogFilter.Sql, "{0} has non existing realm first achivement id ({1}), skipped.", cond.ToString(), cond.ConditionValue1); + return false; + } + break; + } + case ConditionTypes.StandState: + { + bool valid = false; + switch (cond.ConditionValue1) + { + case 0: + valid = cond.ConditionValue2 <= (uint)UnitStandStateType.Submerged; + break; + case 1: + valid = cond.ConditionValue2 <= 1; + break; + default: + valid = false; + break; + } + if (!valid) + { + Log.outError(LogFilter.Sql, "{0} has non-existing stand state ({1},{2}), skipped.", cond.ToString(true), cond.ConditionValue1, cond.ConditionValue2); + return false; + } + break; + } + case ConditionTypes.ObjectiveComplete: + { + QuestObjective obj = Global.ObjectMgr.GetQuestObjective(cond.ConditionValue1); + if (obj == null) + { + Log.outError(LogFilter.Sql, "{0} points to non-existing quest objective ({1}), skipped.", cond.ToString(true), cond.ConditionValue1); + return false; + } + break; + } + case ConditionTypes.PetType: + if (cond.ConditionValue1 >= (1 << (int)PetType.Max)) + { + Log.outError(LogFilter.Sql, "{0} has non-existing pet type {1}, skipped.", cond.ToString(true), cond.ConditionValue1); + return false; + } + break; + case ConditionTypes.Alive: + case ConditionTypes.Areaid: + case ConditionTypes.InstanceInfo: + case ConditionTypes.InWater: + case ConditionTypes.Charmed: + case ConditionTypes.Taxi: + default: + break; + } + + if (cond.ConditionValue1 != 0 && !StaticConditionTypeData[(int)cond.ConditionType].HasConditionValue1) + LogUselessConditionValue(cond, 1, cond.ConditionValue1); + if (cond.ConditionValue2 != 0 && !StaticConditionTypeData[(int)cond.ConditionType].HasConditionValue2) + LogUselessConditionValue(cond, 2, cond.ConditionValue2); + if (cond.ConditionValue3 != 0 && !StaticConditionTypeData[(int)cond.ConditionType].HasConditionValue3) + LogUselessConditionValue(cond, 3, cond.ConditionValue3); + + return true; + } + + void LogUselessConditionValue(Condition cond, byte index, uint value) + { + Log.outError(LogFilter.Sql, "{0} has useless data in ConditionValue{1} ({2})!", cond.ToString(true), index, value); + } + + void Clean() + { + ConditionReferenceStore.Clear(); + + ConditionStore.Clear(); + for (ConditionSourceType i = 0; i < ConditionSourceType.Max; ++i) + ConditionStore[i] = new MultiMap();//add new empty list for SourceType + + VehicleSpellConditionStore.Clear(); + + SmartEventConditionStore.Clear(); + + SpellClickEventConditionStore.Clear(); + + NpcVendorConditionContainerStore.Clear(); + } + + static bool PlayerConditionCompare(int comparisonType, int value1, int value2) + { + switch (comparisonType) + { + case 1: + return value1 == value2; + case 2: + return value1 != value2; + case 3: + return value1 > value2; + case 4: + return value1 >= value2; + case 5: + return value1 < value2; + case 6: + return value1 <= value2; + default: + break; + } + return false; + } + + static bool PlayerConditionLogic(uint logic, bool[] results) + { + Contract.Assert(results.Length < 16, "Logic array size must be equal to or less than 16"); + + for (var i = 0; i < results.Length; ++i) + { + if (Convert.ToBoolean((logic >> (16 + i)) & 1)) + results[i] ^= true; + } + + bool result = results[0]; + for (var i = 1; i < results.Length; ++i) + { + switch ((logic >> (2 * (i - 1))) & 3) + { + case 1: + result = result && results[i]; + break; + case 2: + result = result || results[i]; + break; + default: + break; + } + } + + return result; + } + + public static bool IsPlayerMeetingCondition(Player player, PlayerConditionRecord condition) + { + if (condition.MinLevel != 0 && player.getLevel() < condition.MinLevel) + return false; + + if (condition.MaxLevel != 0 && player.getLevel() > condition.MaxLevel) + return false; + + if (condition.RaceMask != 0 && !Convert.ToBoolean(player.getRaceMask() & condition.RaceMask)) + return false; + + if (condition.ClassMask != 0 && !Convert.ToBoolean(player.getClassMask() & condition.ClassMask)) + return false; + + if (condition.Gender >= 0 && (int)player.GetGender() != condition.Gender) + return false; + + if (condition.NativeGender >= 0 && player.GetByteValue(PlayerFields.Bytes3, PlayerFieldOffsets.Bytes3OffsetGender) != condition.NativeGender) + return false; + + if (condition.PowerType != -1 && condition.PowerTypeComp != 0) + { + int requiredPowerValue = Convert.ToBoolean(condition.Flags & 4) ? player.GetMaxPower((PowerType)condition.PowerType) : condition.PowerTypeValue; + if (!PlayerConditionCompare(condition.PowerTypeComp, player.GetPower((PowerType)condition.PowerType), requiredPowerValue)) + return false; + } + + if (condition.ChrSpecializationIndex >= 0 || condition.ChrSpecializationRole >= 0) + { + ChrSpecializationRecord spec = CliDB.ChrSpecializationStorage.LookupByKey(player.GetUInt32Value(PlayerFields.CurrentSpecId)); + if (spec != null) + { + if (condition.ChrSpecializationIndex >= 0 && spec.OrderIndex != condition.ChrSpecializationIndex) + return false; + + if (condition.ChrSpecializationRole >= 0 && spec.Role != condition.ChrSpecializationRole) + return false; + } + } + + bool[] results; + + if (condition.SkillID[0] != 0 || condition.SkillID[1] != 0 || condition.SkillID[2] != 0 || condition.SkillID[3] != 0) + { + results = new bool[condition.SkillID.Length]; + for (var i = 0; i < condition.SkillID.Length; ++i) + { + if (condition.SkillID[i] != 0) + { + ushort skillValue = player.GetSkillValue((SkillType)condition.SkillID[i]); + results[i] = skillValue != 0 && skillValue > condition.MinSkill[i] && skillValue < condition.MaxSkill[i]; + } + } + + if (!PlayerConditionLogic(condition.SkillLogic, results)) + return false; + } + + if (condition.LanguageID != 0) + { + LanguageDesc lang = ObjectManager.GetLanguageDescByID((Language)condition.LanguageID); + if (lang != null) + { + uint languageSkill = player.GetSkillValue((SkillType)lang.skill_id); + if (languageSkill == 0 && player.HasAuraTypeWithMiscvalue(AuraType.ComprehendLanguage, (int)condition.LanguageID)) + languageSkill = 300; + + if (condition.MinLanguage != 0 && languageSkill < condition.MinLanguage) + return false; + + if (condition.MaxLanguage != 0 && languageSkill > condition.MaxLanguage) + return false; + } + } + + if (condition.MinFactionID[0] != 0 && condition.MinFactionID[1] != 0 && condition.MinFactionID[2] != 0 && condition.MaxFactionID != 0) + { + if (condition.MinFactionID[0] == 0 && condition.MinFactionID[1] == 0 && condition.MinFactionID[2] == 0) + { + ReputationRank forcedRank = player.GetReputationMgr().GetForcedRankIfAny(condition.MaxFactionID); + if (forcedRank != 0) + { + if ((uint)forcedRank > condition.MaxReputation) + return false; + } + else if ((uint)player.GetReputationRank(condition.MaxFactionID) > condition.MaxReputation) + return false; + } + else + { + results = new bool[condition.MinFactionID.Length + 1]; + for (var i = 0; i < condition.MinFactionID.Length; ++i) + { + if (condition.MinFactionID[i] != 0) + { + ReputationRank forcedRank = player.GetReputationMgr().GetForcedRankIfAny(condition.MinFactionID[i]); + if (forcedRank != 0) + results[i] = (uint)forcedRank >= condition.MinReputation[i]; + else + results[i] = (uint)player.GetReputationRank(condition.MinFactionID[i]) >= condition.MinReputation[i]; + } + } + ReputationRank forcedRank1 = player.GetReputationMgr().GetForcedRankIfAny(condition.MaxFactionID); + if (forcedRank1 != 0) + results[3] = (uint)forcedRank1 <= condition.MaxReputation; + else + results[3] = (uint)player.GetReputationRank(condition.MaxFactionID) <= condition.MaxReputation; + + if (!PlayerConditionLogic(condition.ReputationLogic, results)) + return false; + } + } + + if (condition.PvpMedal != 0 && !Convert.ToBoolean((1 << (int)(condition.PvpMedal - 1)) & player.GetUInt32Value(PlayerFields.PvpMedals))) + return false; + + if (condition.LifetimeMaxPVPRank != 0 && player.GetByteValue(PlayerFields.FieldBytes, PlayerFieldOffsets.FieldBytesOffsetLifetimeMaxPvpRank) != condition.LifetimeMaxPVPRank) + return false; + + if (condition.MovementFlags[0] != 0 && !Convert.ToBoolean((uint)player.GetUnitMovementFlags() & condition.MovementFlags[0])) + return false; + + if (condition.MovementFlags[1] != 0 && !Convert.ToBoolean((uint)player.GetUnitMovementFlags2() & condition.MovementFlags[1])) + return false; + + if (condition.MainHandItemSubclassMask != 0) + { + Item mainHand = player.GetItemByPos(InventorySlots.Bag0, EquipmentSlot.MainHand); + if (!mainHand || !Convert.ToBoolean((1 << (int)mainHand.GetTemplate().GetSubClass()) & condition.MainHandItemSubclassMask)) + return false; + } + + if (condition.PartyStatus != 0) + { + Group group = player.GetGroup(); + switch (condition.PartyStatus) + { + case 1: + if (group) + return false; + break; + case 2: + if (!group) + return false; + break; + case 3: + if (!group || group.isRaidGroup()) + return false; + break; + case 4: + if (!group || !group.isRaidGroup()) + return false; + break; + case 5: + if (group && group.isRaidGroup()) + return false; + break; + default: + break; + } + } + + if (condition.PrevQuestID[0] != 0) + { + results = new bool[condition.PrevQuestID.Length]; + for (var i = 0; i < condition.PrevQuestID.Length; ++i) + { + uint questBit = Global.DB2Mgr.GetQuestUniqueBitFlag(condition.PrevQuestID[i]); + if (questBit != 0) + results[i] = (player.GetUInt32Value(PlayerFields.QuestCompleted + (int)((questBit - 1) >> 5)) & (1 << (int)((questBit - 1) & 31))) != 0; + } + + if (!PlayerConditionLogic(condition.PrevQuestLogic, results)) + return false; + } + + if (condition.CurrQuestID[0] != 0) + { + results = new bool[condition.CurrQuestID.Length]; + for (var i = 0; i < condition.CurrQuestID.Length; ++i) + { + if (condition.CurrQuestID[i] != 0) + results[i] = player.FindQuestSlot(condition.CurrQuestID[i]) != SharedConst.MaxQuestLogSize; + } + + if (!PlayerConditionLogic(condition.CurrQuestLogic, results)) + return false; + } + + if (condition.CurrentCompletedQuestID[0] != 0) + { + results = new bool[condition.CurrentCompletedQuestID.Length]; + for (var i = 0; i < condition.CurrentCompletedQuestID.Length; ++i) + { + if (condition.CurrentCompletedQuestID[i] != 0) + results[i] = player.GetQuestStatus(condition.CurrentCompletedQuestID[i]) == QuestStatus.Complete; + } + + if (!PlayerConditionLogic(condition.CurrentCompletedQuestLogic, results)) + return false; + } + + + if (condition.SpellID[0] != 0) + { + results = new bool[condition.SpellID.Length]; + for (var i = 0; i < condition.SpellID.Length; ++i) + { + if (condition.SpellID[i] != 0) + results[i] = player.HasSpell(condition.SpellID[i]); + } + + if (!PlayerConditionLogic(condition.SpellLogic, results)) + return false; + } + + if (condition.ItemID[0] != 0) + { + results = new bool[condition.ItemID.Length]; + for (var i = 0; i < condition.ItemID.Length; ++i) + { + if (condition.ItemID[i] != 0) + results[i] = player.GetItemCount(condition.ItemID[i], condition.ItemFlags != 0) >= condition.ItemCount[i]; + } + + if (!PlayerConditionLogic(condition.ItemLogic, results)) + return false; + } + + if (condition.CurrencyID[0] != 0) + { + results = new bool[condition.CurrencyID.Length]; + for (var i = 0; i < condition.CurrencyID.Length; ++i) + { + if (condition.CurrencyID[i] != 0) + results[i] = player.GetCurrency(condition.CurrencyID[i]) >= condition.CurrencyCount[i]; + } + + if (!PlayerConditionLogic(condition.CurrencyLogic, results)) + return false; + } + + if (condition.Explored[0] != 0 || condition.Explored[1] != 0) + { + for (var i = 0; i < condition.Explored.Length; ++i) + { + AreaTableRecord area = CliDB.AreaTableStorage.LookupByKey(condition.Explored[i]); + if (area != null) + if (area.AreaBit != -1 && !Convert.ToBoolean(player.GetUInt32Value(PlayerFields.ExploredZones1 + area.AreaBit / 32) & (1 << (area.AreaBit % 32)))) + return false; + } + } + + if (condition.AuraSpellID[0] != 0) + { + results = new bool[condition.AuraSpellID.Length]; + for (var i = 0; i < condition.AuraSpellID.Length; ++i) + { + if (condition.AuraSpellID[i] != 0) + { + if (condition.AuraCount[i] != 0) + results[i] = player.GetAuraCount(condition.AuraSpellID[i]) >= condition.AuraCount[i]; + else + results[i] = player.HasAura(condition.AuraSpellID[i]); + } + } + + if (!PlayerConditionLogic(condition.AuraSpellLogic, results)) + return false; + } + + // TODO: time condition + // TODO (or not): world state expression condition + // TODO: weather condition + + if (condition.Achievement[0] != 0) + { + results = new bool[condition.Achievement.Length]; + for (var i = 0; i < condition.Achievement.Length; ++i) + { + if (condition.Achievement[i] != 0) + { + // if (condition.Flags & 2) { any character on account completed it } else { current character only } + // TODO: part of accountwide achievements + results[i] = player.HasAchieved(condition.Achievement[i]); + } + } + + if (!PlayerConditionLogic(condition.AchievementLogic, results)) + return false; + } + + // TODO: research lfg status for player conditions + + if (condition.AreaID[0] != 0) + { + results = new bool[condition.AreaID.Length]; + for (var i = 0; i < condition.AreaID.Length; ++i) + if (condition.AreaID[i] != 0) + results[i] = player.GetAreaId() == condition.AreaID[i] || player.GetZoneId() == condition.AreaID[i]; + + if (!PlayerConditionLogic(condition.AreaLogic, results)) + return false; + } + + if (condition.MinExpansionLevel != -1 && (int)player.GetSession().GetExpansion() < condition.MinExpansionLevel) + return false; + + if (condition.MaxExpansionLevel != -1 && (int)player.GetSession().GetExpansion() > condition.MaxExpansionLevel) + return false; + + if (condition.MinExpansionLevel != -1 && condition.MinExpansionTier != -1 && !player.IsGameMaster() + && ((condition.MinExpansionLevel == WorldConfig.GetIntValue(WorldCfg.Expansion) && condition.MinExpansionTier > 0) /*TODO: implement tier*/ + || condition.MinExpansionLevel > WorldConfig.GetIntValue(WorldCfg.Expansion))) + return false; + + if (condition.PhaseID != 0 && !player.IsInPhase(condition.PhaseID)) + return false; + + if (condition.PhaseGroupID != 0) + { + var phases = Global.DB2Mgr.GetPhasesForGroup(condition.PhaseGroupID); + if (!phases.Intersect(player.GetPhases()).Any()) + return false; + } + + if (condition.QuestKillID != 0) + { + Quest quest = Global.ObjectMgr.GetQuestTemplate(condition.QuestKillID); + if (quest != null && player.GetQuestStatus(condition.QuestKillID) != QuestStatus.Complete) + { + results = new bool[condition.QuestKillMonster.Length]; + for (var i = 0; i < condition.QuestKillMonster.Length; ++i) + { + if (condition.QuestKillMonster[i] != 0) + { + var questObjective = quest.Objectives.Find(objective => + { + return objective.Type == QuestObjectiveType.Monster && objective.ObjectID == condition.QuestKillMonster[i]; + }); + + if (questObjective != null) + results[i] = player.GetQuestObjectiveData(quest, questObjective.StorageIndex) >= questObjective.Amount; + } + } + + if (!PlayerConditionLogic(condition.QuestKillLogic, results)) + return false; + } + } + + if (condition.MinAvgItemLevel != 0 && Math.Floor(player.GetFloatValue(PlayerFields.AvgItemLevel)) < condition.MinAvgItemLevel) + return false; + + if (condition.MaxAvgItemLevel != 0 && Math.Floor(player.GetFloatValue(PlayerFields.AvgItemLevel)) > condition.MaxAvgItemLevel) + return false; + + if (condition.MinAvgEquippedItemLevel != 0 && Math.Floor(player.GetFloatValue(PlayerFields.AvgItemLevel + 1)) < condition.MinAvgEquippedItemLevel) + return false; + + if (condition.MaxAvgEquippedItemLevel != 0 && Math.Floor(player.GetFloatValue(PlayerFields.AvgItemLevel + 1)) > condition.MaxAvgEquippedItemLevel) + return false; + + if (condition.ModifierTreeID != 0 && !player.ModifierTreeSatisfied(condition.ModifierTreeID)) + return false; + + return true; + } + + Dictionary> ConditionStore = new Dictionary>(); + MultiMap ConditionReferenceStore = new MultiMap(); + Dictionary> VehicleSpellConditionStore = new Dictionary>(); + Dictionary> SpellClickEventConditionStore = new Dictionary>(); + Dictionary> NpcVendorConditionContainerStore = new Dictionary>(); + Dictionary, MultiMap> SmartEventConditionStore = new Dictionary, MultiMap>(); + + public string[] StaticSourceTypeData = + { + "None", + "Creature Loot", + "Disenchant Loot", + "Fishing Loot", + "GameObject Loot", + "Item Loot", + "Mail Loot", + "Milling Loot", + "Pickpocketing Loot", + "Prospecting Loot", + "Reference Loot", + "Skinning Loot", + "Spell Loot", + "Spell Impl. Target", + "Gossip Menu", + "Gossip Menu Option", + "Creature Vehicle", + "Spell Expl. Target", + "Spell Click Event", + "Quest Accept", + "Quest Show Mark", + "Vehicle Spell", + "SmartScript", + "Npc Vendor", + "Spell Proc", + "Terrain Swap", + "Phase" + }; + + public ConditionTypeInfo[] StaticConditionTypeData = + { + new ConditionTypeInfo("None", false,false, false), + new ConditionTypeInfo("Aura", true, true, true ), + new ConditionTypeInfo("Item Stored", true, true, true ), + new ConditionTypeInfo("Item Equipped", true, false, false), + new ConditionTypeInfo("Zone", true, false, false), + new ConditionTypeInfo("Reputation", true, true, false), + new ConditionTypeInfo("Team", true, false, false), + new ConditionTypeInfo("Skill", true, true, false), + new ConditionTypeInfo("Quest Rewarded", true, false, false), + new ConditionTypeInfo("Quest Taken", true, false, false), + new ConditionTypeInfo("Drunken", true, false, false), + new ConditionTypeInfo("WorldState", true, true, false), + new ConditionTypeInfo("Active Event", true, false, false), + new ConditionTypeInfo("Instance Info", true, true, true ), + new ConditionTypeInfo("Quest None", true, false, false), + new ConditionTypeInfo("Class", true, false, false), + new ConditionTypeInfo("Race", true, false, false), + new ConditionTypeInfo("Achievement", true, false, false), + new ConditionTypeInfo("Title", true, false, false), + new ConditionTypeInfo("SpawnMask", true, false, false), + new ConditionTypeInfo("Gender", true, false, false), + new ConditionTypeInfo("Unit State", true, false, false), + new ConditionTypeInfo("Map", true, false, false), + new ConditionTypeInfo("Area", true, false, false), + new ConditionTypeInfo("CreatureType", true, false, false), + new ConditionTypeInfo("Spell Known", true, false, false), + new ConditionTypeInfo("Phase", true, false, false), + new ConditionTypeInfo("Level", true, true, false), + new ConditionTypeInfo("Quest Completed", true, false, false), + new ConditionTypeInfo("Near Creature", true, true, true), + new ConditionTypeInfo("Near GameObject", true, true, false), + new ConditionTypeInfo("Object Entry or Guid", true, true, true ), + new ConditionTypeInfo("Object TypeMask", true, false, false), + new ConditionTypeInfo("Relation", true, true, false), + new ConditionTypeInfo("Reaction", true, true, false), + new ConditionTypeInfo("Distance", true, true, true ), + new ConditionTypeInfo("Alive", false,false, false), + new ConditionTypeInfo("Health Value", true, true, false), + new ConditionTypeInfo("Health Pct", true, true, false), + new ConditionTypeInfo("Realm Achievement", true, false, false), + new ConditionTypeInfo("In Water", false,false, false), + new ConditionTypeInfo("Terrain Swap", true, false, false), + new ConditionTypeInfo("Sit/stand state", true, true, false), + new ConditionTypeInfo("Daily Quest Completed",true, false, false), + new ConditionTypeInfo("Charmed", false,false, false), + new ConditionTypeInfo("Pet type", true, false, false), + new ConditionTypeInfo("On Taxi", false, false, false), + new ConditionTypeInfo("Quest state mask", true, true, false), + new ConditionTypeInfo("Objective Complete", true, false, false) + }; + + public struct ConditionTypeInfo + { + public ConditionTypeInfo(string name, params bool[] args) + { + Name = name; + HasConditionValue1 = args[0]; + HasConditionValue2 = args[1]; + HasConditionValue3 = args[2]; + } + + public string Name; + public bool HasConditionValue1; + public bool HasConditionValue2; + public bool HasConditionValue3; + } + } +} diff --git a/Game/Conditions/DisableManager.cs b/Game/Conditions/DisableManager.cs new file mode 100644 index 000000000..6502c895d --- /dev/null +++ b/Game/Conditions/DisableManager.cs @@ -0,0 +1,433 @@ +/* + * Copyright (C) 2012-2017 CypherCore + * + * 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, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +using Framework.Collections; +using Framework.Constants; +using Framework.Database; +using Game.DataStorage; +using Game.Entities; +using System; +using System.Collections.Generic; +using System.Diagnostics.Contracts; + +namespace Game +{ + public class DisableManager : Singleton + { + DisableManager() { } + + public class DisableData + { + public byte flags; + public List param0 = new List(); + public List param1 = new List(); + } + + Dictionary> m_DisableMap = new Dictionary>(); + + public void LoadDisables() + { + uint oldMSTime = Time.GetMSTime(); + + // reload case + m_DisableMap.Clear(); + + SQLResult result = DB.World.Query("SELECT sourceType, entry, flags, params_0, params_1 FROM disables"); + if (result.IsEmpty()) + { + Log.outError(LogFilter.ServerLoading, "Loaded 0 disables. DB table `disables` is empty!"); + return; + } + + uint total_count = 0; + do + { + DisableType type = (DisableType)result.Read(0); + if (type >= DisableType.Max) + { + Log.outError(LogFilter.Sql, "Invalid type {0} specified in `disables` table, skipped.", type); + continue; + } + + uint entry = result.Read(1); + byte flags = result.Read(2); + string params_0 = result.Read(3); + string params_1 = result.Read(4); + + DisableData data = new DisableData(); + data.flags = flags; + + switch (type) + { + case DisableType.Spell: + if (!(Global.SpellMgr.HasSpellInfo(entry) || flags.HasAnyFlag(DisableFlags.SpellDeprecatedSpell))) + { + Log.outError(LogFilter.Sql, "Spell entry {0} from `disables` doesn't exist in dbc, skipped.", entry); + continue; + } + + if (flags == 0 || flags > DisableFlags.MaxSpell) + { + Log.outError(LogFilter.Sql, "Disable flags for spell {0} are invalid, skipped.", entry); + continue; + } + + if (flags.HasAnyFlag(DisableFlags.SpellMap)) + { + var array = new StringArray(params_0, ','); + for (byte i = 0; i < array.Length;) + data.param0.Add(uint.Parse(array[i++])); + } + + if (flags.HasAnyFlag(DisableFlags.SpellArea)) + { + var array = new StringArray(params_1, ','); + for (byte i = 0; i < array.Length;) + data.param1.Add(uint.Parse(array[i++])); + } + + break; + // checked later + case DisableType.Quest: + break; + case DisableType.Map: + { + MapRecord mapEntry = CliDB.MapStorage.LookupByKey(entry); + if (mapEntry == null) + { + Log.outError(LogFilter.Sql, "Map entry {0} from `disables` doesn't exist in dbc, skipped.", entry); + continue; + } + bool isFlagInvalid = false; + switch (mapEntry.InstanceType) + { + case MapTypes.Common: + if (flags != 0) + isFlagInvalid = true; + break; + case MapTypes.Instance: + case MapTypes.Raid: + if (flags.HasAnyFlag(DisableFlags.DungeonStatusHeroic) && Global.DB2Mgr.GetMapDifficultyData(entry, Difficulty.Heroic) == null) + flags -= DisableFlags.DungeonStatusHeroic; + if (flags.HasAnyFlag(DisableFlags.DungeonStatusHeroic10Man) && Global.DB2Mgr.GetMapDifficultyData(entry, Difficulty.Raid10HC) == null) + flags -= DisableFlags.DungeonStatusHeroic10Man; + if (flags.HasAnyFlag(DisableFlags.DungeonStatusHeroic25Man) && Global.DB2Mgr.GetMapDifficultyData(entry, Difficulty.Raid25HC) == null) + flags -= DisableFlags.DungeonStatusHeroic25Man; + if (flags == 0) + isFlagInvalid = true; + break; + case MapTypes.Battleground: + case MapTypes.Arena: + Log.outError(LogFilter.Sql, "Battlegroundmap {0} specified to be disabled in map case, skipped.", entry); + continue; + } + if (isFlagInvalid) + { + Log.outError(LogFilter.Sql, "Disable flags for map {0} are invalid, skipped.", entry); + continue; + } + break; + } + case DisableType.Battleground: + if (!CliDB.BattlemasterListStorage.ContainsKey(entry)) + { + Log.outError(LogFilter.Sql, "Battlegroundentry {0} from `disables` doesn't exist in dbc, skipped.", entry); + continue; + } + if (flags != 0) + Log.outError(LogFilter.Sql, "Disable flags specified for Battleground{0}, useless data.", entry); + break; + case DisableType.OutdoorPVP: + if (entry > (int)OutdoorPvPTypes.Max) + { + Log.outError(LogFilter.Sql, "OutdoorPvPTypes value {0} from `disables` is invalid, skipped.", entry); + continue; + } + if (flags != 0) + Log.outError(LogFilter.Sql, "Disable flags specified for outdoor PvP {0}, useless data.", entry); + break; + case DisableType.Criteria: + if (Global.CriteriaMgr.GetCriteria(entry) == null) + { + Log.outError(LogFilter.Sql, "Criteria entry {0} from `disables` doesn't exist in dbc, skipped.", entry); + continue; + } + if (flags != 0) + Log.outError(LogFilter.Sql, "Disable flags specified for Criteria {0}, useless data.", entry); + break; + case DisableType.VMAP: + { + MapRecord mapEntry = CliDB.MapStorage.LookupByKey(entry); + if (mapEntry == null) + { + Log.outError(LogFilter.Sql, "Map entry {0} from `disables` doesn't exist in dbc, skipped.", entry); + continue; + } + switch (mapEntry.InstanceType) + { + case MapTypes.Common: + if (flags.HasAnyFlag(DisableFlags.VmapAreaFlag)) + Log.outInfo(LogFilter.Server, "Areaflag disabled for world map {0}.", entry); + if (flags.HasAnyFlag(DisableFlags.VmapLiquidStatus)) + Log.outInfo(LogFilter.Server, "Liquid status disabled for world map {0}.", entry); + break; + case MapTypes.Instance: + case MapTypes.Raid: + if (flags.HasAnyFlag(DisableFlags.VmapHeight)) + Log.outInfo(LogFilter.Server, "Height disabled for instance map {0}.", entry); + if (flags.HasAnyFlag(DisableFlags.VmapLOS)) + Log.outInfo(LogFilter.Server, "LoS disabled for instance map {0}.", entry); + break; + case MapTypes.Battleground: + if (flags.HasAnyFlag(DisableFlags.VmapHeight)) + Log.outInfo(LogFilter.Server, "Height disabled for Battlegroundmap {0}.", entry); + if (flags.HasAnyFlag(DisableFlags.VmapLOS)) + Log.outInfo(LogFilter.Server, "LoS disabled for Battlegroundmap {0}.", entry); + break; + case MapTypes.Arena: + if (flags.HasAnyFlag(DisableFlags.VmapHeight)) + Log.outInfo(LogFilter.Server, "Height disabled for arena map {0}.", entry); + if (flags.HasAnyFlag(DisableFlags.VmapLOS)) + Log.outInfo(LogFilter.Server, "LoS disabled for arena map {0}.", entry); + break; + default: + break; + } + break; + } + case DisableType.MMAP: + { + MapRecord mapEntry = CliDB.MapStorage.LookupByKey(entry); + if (mapEntry == null) + { + Log.outError(LogFilter.Sql, "Map entry {0} from `disables` doesn't exist in dbc, skipped.", entry); + continue; + } + switch (mapEntry.InstanceType) + { + case MapTypes.Common: + Log.outInfo(LogFilter.Server, "Pathfinding disabled for world map {0}.", entry); + break; + case MapTypes.Instance: + case MapTypes.Raid: + Log.outInfo(LogFilter.Server, "Pathfinding disabled for instance map {0}.", entry); + break; + case MapTypes.Battleground: + Log.outInfo(LogFilter.Server, "Pathfinding disabled for Battlegroundmap {0}.", entry); + break; + case MapTypes.Arena: + Log.outInfo(LogFilter.Server, "Pathfinding disabled for arena map {0}.", entry); + break; + default: + break; + } + break; + } + default: + break; + } + if (!m_DisableMap.ContainsKey(type)) + m_DisableMap[type] = new Dictionary(); + + m_DisableMap[type].Add(entry, data); + ++total_count; + } + while (result.NextRow()); + + Log.outInfo(LogFilter.ServerLoading, "Loaded {0} disables in {1} ms", total_count, Time.GetMSTimeDiffToNow(oldMSTime)); + } + + public void CheckQuestDisables() + { + uint oldMSTime = Time.GetMSTime(); + + int count = m_DisableMap[DisableType.Quest].Count; + if (count == 0) + { + Log.outInfo(LogFilter.ServerLoading, "Checked 0 quest disables."); + return; + } + + // check only quests, rest already done at startup + foreach (var pair in m_DisableMap[DisableType.Quest]) + { + uint entry = pair.Key; + if (Global.ObjectMgr.GetQuestTemplate(entry) == null) + { + Log.outError(LogFilter.Sql, "Quest entry {0} from `disables` doesn't exist, skipped.", entry); + m_DisableMap[DisableType.Quest].Remove(entry); + continue; + } + if (pair.Value.flags != 0) + Log.outError(LogFilter.Sql, "Disable flags specified for quest {0}, useless data.", entry); + } + + Log.outInfo(LogFilter.ServerLoading, "Checked {0} quest disables in {1} ms", count, Time.GetMSTimeDiffToNow(oldMSTime)); + } + + public bool IsDisabledFor(DisableType type, uint entry, Unit unit, byte flags = 0) + { + Contract.Assert(type < DisableType.Max); + if (!m_DisableMap.ContainsKey(type) || m_DisableMap[type].Empty()) + return false; + + var data = m_DisableMap[type].LookupByKey(entry); + if (data == null) // not disabled + return false; + + switch (type) + { + case DisableType.Spell: + { + byte spellFlags = data.flags; + if (unit != null) + { + if ((spellFlags.HasAnyFlag(DisableFlags.SpellPlayer) && unit.IsTypeId(TypeId.Player)) || + (unit.IsTypeId(TypeId.Unit) && ((unit.IsPet() && spellFlags.HasAnyFlag(DisableFlags.SpellPet)) || spellFlags.HasAnyFlag(DisableFlags.SpellCreature)))) + { + if (spellFlags.HasAnyFlag(DisableFlags.SpellMap)) + { + List mapIds = data.param0; + if (mapIds.Contains(unit.GetMapId())) + return true; // Spell is disabled on current map + + if (!spellFlags.HasAnyFlag(DisableFlags.SpellArea)) + return false; // Spell is disabled on another map, but not this one, return false + + // Spell is disabled in an area, but not explicitly our current mapId. Continue processing. + } + + if (spellFlags.HasAnyFlag(DisableFlags.SpellArea)) + { + var areaIds = data.param1; + if (areaIds.Contains(unit.GetAreaId())) + return true; // Spell is disabled in this area + return false; // Spell is disabled in another area, but not this one, return false + } + else + return true; // Spell disabled for all maps + } + + return false; + } + else if (spellFlags.HasAnyFlag(DisableFlags.SpellDeprecatedSpell)) // call not from spellcast + return true; + else if (flags.HasAnyFlag(DisableFlags.SpellLOS)) + return spellFlags.HasAnyFlag(DisableFlags.SpellLOS); + + break; + } + case DisableType.Map: + Player player = unit.ToPlayer(); + if (player != null) + { + MapRecord mapEntry = CliDB.MapStorage.LookupByKey(entry); + if (mapEntry.IsDungeon()) + { + byte disabledModes = data.flags; + Difficulty targetDifficulty = player.GetDifficultyID(mapEntry); + Global.DB2Mgr.GetDownscaledMapDifficultyData(entry, ref targetDifficulty); + switch (targetDifficulty) + { + case Difficulty.Normal: + return disabledModes.HasAnyFlag(DisableFlags.DungeonStatusNormal); + case Difficulty.Heroic: + return disabledModes.HasAnyFlag(DisableFlags.DungeonStatusHeroic); + case Difficulty.Raid10HC: + return disabledModes.HasAnyFlag(DisableFlags.DungeonStatusHeroic10Man); + case Difficulty.Raid25HC: + return disabledModes.HasAnyFlag(DisableFlags.DungeonStatusHeroic25Man); + default: + return false; + } + } + else if (mapEntry.InstanceType == MapTypes.Common) + return true; + } + return false; + case DisableType.Quest: + if (unit == null) + return true; + Player player1 = unit.ToPlayer(); + if (player1 != null) + if (player1.IsGameMaster()) + return false; + return true; + case DisableType.Battleground: + case DisableType.OutdoorPVP: + case DisableType.Criteria: + case DisableType.MMAP: + return true; + case DisableType.VMAP: + return flags.HasAnyFlag(data.flags); + } + + return false; + } + + public bool IsVMAPDisabledFor(uint entry, byte flags) + { + return IsDisabledFor(DisableType.VMAP, entry, null, flags); + } + + public bool IsPathfindingEnabled(uint mapId) + { + return WorldConfig.GetBoolValue(WorldCfg.EnableMmaps) && !Global.DisableMgr.IsDisabledFor(DisableType.MMAP, mapId, null); + } + } + + public enum DisableType + { + Spell = 0, + Quest = 1, + Map = 2, + Battleground= 3, + Criteria = 4, + OutdoorPVP = 5, + VMAP = 6, + MMAP = 7, + Max = 8 + } + + public struct DisableFlags + { + public const byte SpellPlayer = 0x1; + public const byte SpellCreature = 0x2; + public const byte SpellPet = 0x4; + public const byte SpellDeprecatedSpell = 0x8; + public const byte SpellMap = 0x10; + public const byte SpellArea = 0x20; + public const byte SpellLOS = 0x40; + public const byte MaxSpell = (SpellPlayer | SpellCreature | SpellPet | SpellDeprecatedSpell | SpellMap | SpellArea | SpellLOS); + + public const byte VmapAreaFlag = 0x1; + public const byte VmapHeight = 0x2; + public const byte VmapLOS = 0x4; + public const byte VmapLiquidStatus = 0x8; + + public const byte MMapPathFinding = 0x0; + + public const byte DungeonStatusNormal = 0x01; + public const byte DungeonStatusHeroic = 0x02; + + public const byte DungeonStatusNormal10Man = 0x01; + public const byte DungeonStatusNormal25Man = 0x02; + public const byte DungeonStatusHeroic10Man = 0x04; + public const byte DungeonStatusHeroic25Man = 0x08; + + } +} diff --git a/Game/DataStorage/AreaTriggerDataStorage.cs b/Game/DataStorage/AreaTriggerDataStorage.cs new file mode 100644 index 000000000..668beb2e2 --- /dev/null +++ b/Game/DataStorage/AreaTriggerDataStorage.cs @@ -0,0 +1,218 @@ +/* + * Copyright (C) 2012-2017 CypherCore + * + * 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, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +using Framework.Constants; +using Framework.Database; +using Framework.GameMath; +using Game.Entities; +using System; +using System.Collections.Generic; + +namespace Game.DataStorage +{ + public class AreaTriggerDataStorage : Singleton + { + AreaTriggerDataStorage() { } + + public void LoadAreaTriggerTemplates() + { + uint oldMSTime = Time.GetMSTime(); + MultiMap verticesByAreaTrigger = new MultiMap(); + MultiMap verticesTargetByAreaTrigger = new MultiMap(); + MultiMap splinesBySpellMisc = new MultiMap(); + MultiMap actionsByAreaTrigger = new MultiMap(); + + // 0 1 2 3 + SQLResult templateActions = DB.World.Query("SELECT AreaTriggerId, ActionType, ActionParam, TargetType FROM `areatrigger_template_actions`"); + if (!templateActions.IsEmpty()) + { + do + { + uint areaTriggerId = templateActions.Read(0); + + AreaTriggerAction action; + action.Param = templateActions.Read(2); + action.ActionType = (AreaTriggerActionTypes)templateActions.Read(1); + action.TargetType = (AreaTriggerActionUserTypes)templateActions.Read(3); + + if (action.ActionType >= AreaTriggerActionTypes.Max) + { + Log.outError(LogFilter.Sql, "Table `areatrigger_template_actions` has invalid ActionType ({0}) for AreaTriggerId {1} and Param {2}", action.ActionType, areaTriggerId, action.Param); + continue; + } + + if (action.TargetType >= AreaTriggerActionUserTypes.Max) + { + Log.outError(LogFilter.Sql, "Table `areatrigger_template_actions` has invalid TargetType ({0}) for AreaTriggerId {1} and Param {2}", action.TargetType, areaTriggerId, action.Param); + continue; + } + + actionsByAreaTrigger.Add(areaTriggerId, action); + } + while (templateActions.NextRow()); + } + else + { + Log.outInfo(LogFilter.ServerLoading, "Loaded 0 AreaTrigger templates actions. DB table `areatrigger_template_actions` is empty."); + } + + // 0 1 2 3 4 5 + SQLResult vertices = DB.World.Query("SELECT AreaTriggerId, Idx, VerticeX, VerticeY, VerticeTargetX, VerticeTargetY FROM `areatrigger_template_polygon_vertices` ORDER BY `AreaTriggerId`, `Idx`"); + if (!vertices.IsEmpty()) + { + do + { + uint areaTriggerId = vertices.Read(0); + + verticesByAreaTrigger.Add(areaTriggerId, new Vector2(vertices.Read(2), vertices.Read(3))); + + if (!vertices.IsNull(4) && !vertices.IsNull(5)) + verticesTargetByAreaTrigger.Add(areaTriggerId, new Vector2(vertices.Read(4), vertices.Read(5))); + else if (vertices.IsNull(4) != vertices.IsNull(5)) + Log.outError(LogFilter.Sql, "Table `areatrigger_template_polygon_vertices` has listed invalid target vertices (AreaTrigger: {0}, Index: {1}).", areaTriggerId, vertices.Read(1)); + } + while (vertices.NextRow()); + } + else + { + Log.outInfo(LogFilter.ServerLoading, "Loaded 0 AreaTrigger templates polygon vertices. DB table `areatrigger_template_polygon_vertices` is empty."); + } + + // 0 1 2 3 + SQLResult splines = DB.World.Query("SELECT SpellMiscId, X, Y, Z FROM `spell_areatrigger_splines` ORDER BY `SpellMiscId`, `Idx`"); + if (!splines.IsEmpty()) + { + do + { + uint spellMiscId = splines.Read(0); + + Vector3 spline = new Vector3(splines.Read(1), splines.Read(2), splines.Read(3)); + + splinesBySpellMisc.Add(spellMiscId, spline); + } + while (splines.NextRow()); + } + else + { + Log.outInfo(LogFilter.ServerLoading, "Loaded 0 AreaTrigger templates splines. DB table `spell_areatrigger_splines` is empty."); + } + + // 0 1 2 3 4 5 6 7 8 9 + SQLResult templates = DB.World.Query("SELECT Id, Type, Flags, Data0, Data1, Data2, Data3, Data4, Data5, ScriptName FROM `areatrigger_template`"); + if (!templates.IsEmpty()) + { + do + { + AreaTriggerTemplate areaTriggerTemplate = new AreaTriggerTemplate(); + areaTriggerTemplate.Id = templates.Read(0); + AreaTriggerTypes type = (AreaTriggerTypes)templates.Read(1); + + if (type >= AreaTriggerTypes.Max) + { + Log.outError(LogFilter.Sql, "Table `areatrigger_template` has listed areatrigger (Id: {0}) with invalid type {1}.", areaTriggerTemplate.Id, type); + continue; + } + + areaTriggerTemplate.TriggerType = type; + areaTriggerTemplate.Flags = (AreaTriggerFlags)templates.Read(2); + + unsafe + { + fixed (float* b = areaTriggerTemplate.DefaultDatas.Data) + { + for (byte i = 0; i < SharedConst.MaxAreatriggerEntityData; ++i) + b[i] = templates.Read(3 + i); + } + } + + areaTriggerTemplate.ScriptId = Global.ObjectMgr.GetScriptId(templates.Read(9)); + areaTriggerTemplate.PolygonVertices = verticesByAreaTrigger[areaTriggerTemplate.Id]; + areaTriggerTemplate.PolygonVerticesTarget = verticesTargetByAreaTrigger[areaTriggerTemplate.Id]; + areaTriggerTemplate.Actions = actionsByAreaTrigger[areaTriggerTemplate.Id]; + + areaTriggerTemplate.InitMaxSearchRadius(); + _areaTriggerTemplateStore[areaTriggerTemplate.Id] = areaTriggerTemplate; + } + while (templates.NextRow()); + } + + // 0 1 2 3 4 5 6 7 8 + SQLResult areatriggerSpellMiscs = DB.World.Query("SELECT SpellMiscId, AreaTriggerId, MoveCurveId, ScaleCurveId, MorphCurveId, FacingCurveId, DecalPropertiesId, TimeToTarget, TimeToTargetScale FROM `spell_areatrigger`"); + if (!areatriggerSpellMiscs.IsEmpty()) + { + do + { + AreaTriggerMiscTemplate miscTemplate = new AreaTriggerMiscTemplate(); + miscTemplate.MiscId = areatriggerSpellMiscs.Read(0); + + uint areatriggerId = areatriggerSpellMiscs.Read(1); + miscTemplate.Template = GetAreaTriggerTemplate(areatriggerId); + + if (miscTemplate.Template == null) + { + Log.outError(LogFilter.Sql, "Table `spell_areatrigger` reference invalid AreaTriggerId {0} for miscId {1}", areatriggerId, miscTemplate.MiscId); + continue; + } + + Func ValidateAndSetCurve = value => + { + if (value != 0 && !CliDB.CurveStorage.ContainsKey(value)) + { + Log.outError(LogFilter.Sql, "Table `spell_areatrigger` has listed areatrigger (MiscId: {0}, Id: {1}) with invalid Curve ({2}), set to 0!", miscTemplate.MiscId, areatriggerId, value); + return 0; + } + return value; + }; + + miscTemplate.MoveCurveId = ValidateAndSetCurve(areatriggerSpellMiscs.Read(2)); + miscTemplate.ScaleCurveId = ValidateAndSetCurve(areatriggerSpellMiscs.Read(3)); + miscTemplate.MorphCurveId = ValidateAndSetCurve(areatriggerSpellMiscs.Read(4)); + miscTemplate.FacingCurveId = ValidateAndSetCurve(areatriggerSpellMiscs.Read(5)); + + miscTemplate.DecalPropertiesId = areatriggerSpellMiscs.Read(6); + + miscTemplate.TimeToTarget = areatriggerSpellMiscs.Read(7); + miscTemplate.TimeToTargetScale = areatriggerSpellMiscs.Read(8); + + miscTemplate.SplinePoints = splinesBySpellMisc[miscTemplate.MiscId]; + + _areaTriggerTemplateSpellMisc[miscTemplate.MiscId] = miscTemplate; + } + while (areatriggerSpellMiscs.NextRow()); + } + else + { + Log.outInfo(LogFilter.ServerLoading, "Loaded 0 Spell AreaTrigger templates. DB table `spell_areatrigger` is empty."); + } + + Log.outInfo(LogFilter.ServerLoading, "Loaded {0} spell areatrigger templates in {1} ms.", _areaTriggerTemplateStore.Count, Time.GetMSTimeDiffToNow(oldMSTime)); + } + + public AreaTriggerTemplate GetAreaTriggerTemplate(uint areaTriggerId) + { + return _areaTriggerTemplateStore.LookupByKey(areaTriggerId); + } + + public AreaTriggerMiscTemplate GetAreaTriggerMiscTemplate(uint spellMiscValue) + { + return _areaTriggerTemplateSpellMisc.LookupByKey(spellMiscValue); + } + + Dictionary _areaTriggerTemplateStore = new Dictionary(); + Dictionary _areaTriggerTemplateSpellMisc = new Dictionary(); + } +} diff --git a/Game/DataStorage/CharacterTemplateDataStorage.cs b/Game/DataStorage/CharacterTemplateDataStorage.cs new file mode 100644 index 000000000..cd72fb94c --- /dev/null +++ b/Game/DataStorage/CharacterTemplateDataStorage.cs @@ -0,0 +1,127 @@ +/* + * Copyright (C) 2012-2017 CypherCore + * + * 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, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +using Framework.Constants; +using Framework.Database; +using System.Collections.Generic; + +namespace Game.DataStorage +{ + public class CharacterTemplateDataStorage : Singleton + { + CharacterTemplateDataStorage() { } + + public void LoadCharacterTemplates() + { + uint oldMSTime = Time.GetMSTime(); + _characterTemplateStore.Clear(); + + MultiMap characterTemplateClasses = new MultiMap(); + SQLResult classesResult = DB.World.Query("SELECT TemplateId, FactionGroup, Class FROM character_template_class"); + if (!classesResult.IsEmpty()) + { + do + { + uint templateId = classesResult.Read(0); + FactionMasks factionGroup = (FactionMasks)classesResult.Read(1); + byte classID = classesResult.Read(2); + + if (!((factionGroup & (FactionMasks.Player | FactionMasks.Alliance)) == (FactionMasks.Player | FactionMasks.Alliance)) && + !((factionGroup & (FactionMasks.Player | FactionMasks.Horde)) == (FactionMasks.Player | FactionMasks.Horde))) + { + Log.outError(LogFilter.Sql, "Faction group {0} defined for character template {1} in `character_template_class` is invalid. Skipped.", factionGroup, templateId); + continue; + } + + if (!CliDB.ChrClassesStorage.ContainsKey(classID)) + { + Log.outError(LogFilter.Sql, "Class {0} defined for character template {1} in `character_template_class` does not exists, skipped.", classID, templateId); + continue; + } + + characterTemplateClasses.Add(templateId, new CharacterTemplateClass(factionGroup, classID)); + } + while (classesResult.NextRow()); + } + else + { + Log.outInfo(LogFilter.ServerLoading, "Loaded 0 character template classes. DB table `character_template_class` is empty."); + } + + SQLResult templates = DB.World.Query("SELECT Id, Name, Description, Level FROM character_template"); + if (templates.IsEmpty()) + { + Log.outInfo(LogFilter.ServerLoading, "Loaded 0 character templates. DB table `character_template` is empty."); + return; + } + + do + { + CharacterTemplate templ = new CharacterTemplate(); + templ.TemplateSetId = templates.Read(0); + templ.Name = templates.Read(1); + templ.Description = templates.Read(2); + templ.Level = templates.Read(3); + templ.Classes = characterTemplateClasses[templ.TemplateSetId]; + + if (templ.Classes.Empty()) + { + Log.outError(LogFilter.Sql, "Character template {0} does not have any classes defined in `character_template_class`. Skipped.", templ.TemplateSetId); + continue; + } + + _characterTemplateStore[templ.TemplateSetId] = templ; + } + while (templates.NextRow()); + + Log.outInfo(LogFilter.ServerLoading, "Loaded {0} character templates in {1} ms.", _characterTemplateStore.Count, Time.GetMSTimeDiffToNow(oldMSTime)); + } + + public Dictionary GetCharacterTemplates() + { + return _characterTemplateStore; + } + + public CharacterTemplate GetCharacterTemplate(uint templateId) + { + return _characterTemplateStore.LookupByKey(templateId); + } + + Dictionary _characterTemplateStore = new Dictionary(); + } + + public struct CharacterTemplateClass + { + public CharacterTemplateClass(FactionMasks factionGroup, byte classID) + { + FactionGroup = factionGroup; + ClassID = classID; + } + + public FactionMasks FactionGroup; + public byte ClassID; + } + + public class CharacterTemplate + { + public uint TemplateSetId; + public List Classes; + public string Name; + public string Description; + public byte Level; + } +} diff --git a/Game/DataStorage/CliDB.cs b/Game/DataStorage/CliDB.cs new file mode 100644 index 000000000..0f9fcf18e --- /dev/null +++ b/Game/DataStorage/CliDB.cs @@ -0,0 +1,683 @@ +/* + * Copyright (C) 2012-2017 CypherCore + * + * 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, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +using Framework.Constants; +using Framework.Database; +using System; +using System.Collections.Generic; +using System.Linq; + +namespace Game.DataStorage +{ + public class CliDB + { + internal static int LoadedFileCount; + internal static string DataPath; + + public static void LoadStores(string dataPath, LocaleConstant defaultLocale) + { + uint oldMSTime = Time.GetMSTime(); + LoadedFileCount = 0; + + DataPath = dataPath + "/dbc/" + defaultLocale + "/"; + + AchievementStorage = DB6Reader.Read("Achievement.db2", DB6Metas.AchievementMeta, HotfixStatements.SEL_ACHIEVEMENT, HotfixStatements.SEL_ACHIEVEMENT_LOCALE); + AnimKitStorage = DB6Reader.Read("AnimKit.db2", DB6Metas.AnimKitMeta, HotfixStatements.SEL_ANIM_KIT); + AreaGroupMemberStorage = DB6Reader.Read("AreaGroupMember.db2", DB6Metas.AreaGroupMemberMeta, HotfixStatements.SEL_AREA_GROUP_MEMBER); + AreaTableStorage = DB6Reader.Read("AreaTable.db2", DB6Metas.AreaTableMeta, HotfixStatements.SEL_AREA_TABLE, HotfixStatements.SEL_AREA_TABLE_LOCALE); + AreaTriggerStorage = DB6Reader.Read("AreaTrigger.db2", DB6Metas.AreaTriggerMeta, HotfixStatements.SEL_AREA_TRIGGER); + ArmorLocationStorage = DB6Reader.Read("ArmorLocation.db2", DB6Metas.ArmorLocationMeta, HotfixStatements.SEL_ARMOR_LOCATION); + ArtifactStorage = DB6Reader.Read("Artifact.db2", DB6Metas.ArtifactMeta, HotfixStatements.SEL_ARTIFACT, HotfixStatements.SEL_ARTIFACT_APPEARANCE_LOCALE); + ArtifactAppearanceStorage = DB6Reader.Read("ArtifactAppearance.db2", DB6Metas.ArtifactAppearanceMeta, HotfixStatements.SEL_ARTIFACT_APPEARANCE, HotfixStatements.SEL_ARTIFACT_APPEARANCE_LOCALE); + ArtifactAppearanceSetStorage = DB6Reader.Read("ArtifactAppearanceSet.db2", DB6Metas.ArtifactAppearanceSetMeta, HotfixStatements.SEL_ARTIFACT_APPEARANCE_SET, HotfixStatements.SEL_ARTIFACT_APPEARANCE_SET_LOCALE); + ArtifactCategoryStorage = DB6Reader.Read("ArtifactCategory.db2", DB6Metas.ArtifactCategoryMeta, HotfixStatements.SEL_ARTIFACT_CATEGORY); + ArtifactPowerStorage = DB6Reader.Read("ArtifactPower.db2", DB6Metas.ArtifactPowerMeta, HotfixStatements.SEL_ARTIFACT_POWER); + ArtifactPowerLinkStorage = DB6Reader.Read("ArtifactPowerLink.db2", DB6Metas.ArtifactPowerLinkMeta, HotfixStatements.SEL_ARTIFACT_POWER_LINK); + ArtifactPowerPickerStorage = DB6Reader.Read("ArtifactPowerPicker.db2", DB6Metas.ArtifactPowerPickerMeta, HotfixStatements.SEL_ARTIFACT_POWER_PICKER); + ArtifactPowerRankStorage = DB6Reader.Read("ArtifactPowerRank.db2", DB6Metas.ArtifactPowerRankMeta, HotfixStatements.SEL_ARTIFACT_POWER_RANK); + ArtifactQuestXPStorage = DB6Reader.Read("ArtifactQuestXP.db2", DB6Metas.ArtifactQuestXPMeta, HotfixStatements.SEL_ARTIFACT_QUEST_XP); + AuctionHouseStorage = DB6Reader.Read("AuctionHouse.db2", DB6Metas.AuctionHouseMeta, HotfixStatements.SEL_AUCTION_HOUSE, HotfixStatements.SEL_AUCTION_HOUSE_LOCALE); + BankBagSlotPricesStorage = DB6Reader.Read("BankBagSlotPrices.db2", DB6Metas.BankBagSlotPricesMeta, HotfixStatements.SEL_BANK_BAG_SLOT_PRICES); + BannedAddOnsStorage = DB6Reader.Read("BannedAddOns.db2", DB6Metas.BannedAddOnsMeta, HotfixStatements.SEL_BANNED_ADDONS); + BarberShopStyleStorage = DB6Reader.Read("BarberShopStyle.db2", DB6Metas.BarberShopStyleMeta, HotfixStatements.SEL_BARBER_SHOP_STYLE, HotfixStatements.SEL_BARBER_SHOP_STYLE_LOCALE); + BattlePetBreedQualityStorage = DB6Reader.Read("BattlePetBreedQuality.db2", DB6Metas.BattlePetBreedQualityMeta, HotfixStatements.SEL_BATTLE_PET_BREED_QUALITY); + BattlePetBreedStateStorage = DB6Reader.Read("BattlePetBreedState.db2", DB6Metas.BattlePetBreedStateMeta, HotfixStatements.SEL_BATTLE_PET_BREED_STATE); + BattlePetSpeciesStorage = DB6Reader.Read("BattlePetSpecies.db2", DB6Metas.BattlePetSpeciesMeta, HotfixStatements.SEL_BATTLE_PET_SPECIES, HotfixStatements.SEL_BATTLE_PET_SPECIES_LOCALE); + BattlePetSpeciesStateStorage = DB6Reader.Read("BattlePetSpeciesState.db2", DB6Metas.BattlePetSpeciesStateMeta, HotfixStatements.SEL_BATTLE_PET_SPECIES_STATE); + BattlemasterListStorage = DB6Reader.Read("BattlemasterList.db2", DB6Metas.BattlemasterListMeta, HotfixStatements.SEL_BATTLEMASTER_LIST, HotfixStatements.SEL_BATTLEMASTER_LIST_LOCALE); + BroadcastTextStorage = DB6Reader.Read("BroadcastText.db2", DB6Metas.BroadcastTextMeta, HotfixStatements.SEL_BROADCAST_TEXT, HotfixStatements.SEL_BROADCAST_TEXT_LOCALE); + CharSectionsStorage = DB6Reader.Read("CharSections.db2", DB6Metas.CharSectionsMeta, HotfixStatements.SEL_CHAR_SECTIONS); + CharStartOutfitStorage = DB6Reader.Read("CharStartOutfit.db2", DB6Metas.CharStartOutfitMeta, HotfixStatements.SEL_CHAR_START_OUTFIT); + CharTitlesStorage = DB6Reader.Read("CharTitles.db2", DB6Metas.CharTitlesMeta, HotfixStatements.SEL_CHAR_TITLES, HotfixStatements.SEL_CHAR_TITLES_LOCALE); + ChatChannelsStorage = DB6Reader.Read("ChatChannels.db2", DB6Metas.ChatChannelsMeta, HotfixStatements.SEL_CHAT_CHANNELS, HotfixStatements.SEL_CHAT_CHANNELS_LOCALE); + ChrClassesStorage = DB6Reader.Read("ChrClasses.db2", DB6Metas.ChrClassesMeta, HotfixStatements.SEL_CHR_CLASSES, HotfixStatements.SEL_CHR_CLASSES_LOCALE); + ChrClassesXPowerTypesStorage = DB6Reader.Read("ChrClassesXPowerTypes.db2", DB6Metas.ChrClassesXPowerTypesMeta, HotfixStatements.SEL_CHR_CLASSES_X_POWER_TYPES); + ChrRacesStorage = DB6Reader.Read("ChrRaces.db2", DB6Metas.ChrRacesMeta, HotfixStatements.SEL_CHR_RACES, HotfixStatements.SEL_CHR_RACES_LOCALE); + ChrSpecializationStorage = DB6Reader.Read("ChrSpecialization.db2", DB6Metas.ChrSpecializationMeta, HotfixStatements.SEL_CHR_SPECIALIZATION, HotfixStatements.SEL_CHR_SPECIALIZATION_LOCALE); + CinematicCameraStorage = DB6Reader.Read("CinematicCamera.db2", DB6Metas.CinematicCameraMeta, HotfixStatements.SEL_CINEMATIC_CAMERA); + CinematicSequencesStorage = DB6Reader.Read("CinematicSequences.db2", DB6Metas.CinematicSequencesMeta, HotfixStatements.SEL_CINEMATIC_SEQUENCES); + ConversationLineStorage = DB6Reader.Read("ConversationLine.db2", DB6Metas.ConversationLineMeta, HotfixStatements.SEL_CONVERSATION_LINE); + CreatureDisplayInfoStorage = DB6Reader.Read("CreatureDisplayInfo.db2", DB6Metas.CreatureDisplayInfoMeta, HotfixStatements.SEL_CREATURE_DISPLAY_INFO); + CreatureDisplayInfoExtraStorage = DB6Reader.Read("CreatureDisplayInfoExtra.db2", DB6Metas.CreatureDisplayInfoExtraMeta, HotfixStatements.SEL_CREATURE_DISPLAY_INFO_EXTRA); + CreatureFamilyStorage = DB6Reader.Read("CreatureFamily.db2", DB6Metas.CreatureFamilyMeta, HotfixStatements.SEL_CREATURE_FAMILY, HotfixStatements.SEL_CREATURE_FAMILY_LOCALE); + CreatureModelDataStorage = DB6Reader.Read("CreatureModelData.db2", DB6Metas.CreatureModelDataMeta, HotfixStatements.SEL_CREATURE_MODEL_DATA); + CreatureTypeStorage = DB6Reader.Read("CreatureType.db2", DB6Metas.CreatureTypeMeta, HotfixStatements.SEL_CREATURE_TYPE, HotfixStatements.SEL_CREATURE_TYPE_LOCALE); + CriteriaStorage = DB6Reader.Read("Criteria.db2", DB6Metas.CriteriaMeta, HotfixStatements.SEL_CRITERIA); + CriteriaTreeStorage = DB6Reader.Read("CriteriaTree.db2", DB6Metas.CriteriaTreeMeta, HotfixStatements.SEL_CRITERIA_TREE, HotfixStatements.SEL_CRITERIA_TREE_LOCALE); + CurrencyTypesStorage = DB6Reader.Read("CurrencyTypes.db2", DB6Metas.CurrencyTypesMeta, HotfixStatements.SEL_CURRENCY_TYPES, HotfixStatements.SEL_CURRENCY_TYPES_LOCALE); + CurveStorage = DB6Reader.Read("Curve.db2", DB6Metas.CurveMeta, HotfixStatements.SEL_CURVE); + CurvePointStorage = DB6Reader.Read("CurvePoint.db2", DB6Metas.CurvePointMeta, HotfixStatements.SEL_CURVE_POINT); + DestructibleModelDataStorage = DB6Reader.Read("DestructibleModelData.db2", DB6Metas.DestructibleModelDataMeta, HotfixStatements.SEL_DESTRUCTIBLE_MODEL_DATA); + DifficultyStorage = DB6Reader.Read("Difficulty.db2", DB6Metas.DifficultyMeta, HotfixStatements.SEL_DIFFICULTY, HotfixStatements.SEL_DIFFICULTY_LOCALE); + DungeonEncounterStorage = DB6Reader.Read("DungeonEncounter.db2", DB6Metas.DungeonEncounterMeta, HotfixStatements.SEL_DUNGEON_ENCOUNTER, HotfixStatements.SEL_DUNGEON_ENCOUNTER_LOCALE); + DurabilityCostsStorage = DB6Reader.Read("DurabilityCosts.db2", DB6Metas.DurabilityCostsMeta, HotfixStatements.SEL_DURABILITY_COSTS); + DurabilityQualityStorage = DB6Reader.Read("DurabilityQuality.db2", DB6Metas.DurabilityQualityMeta, HotfixStatements.SEL_DURABILITY_QUALITY); + EmotesStorage = DB6Reader.Read("Emotes.db2", DB6Metas.EmotesMeta, HotfixStatements.SEL_EMOTES); + EmotesTextStorage = DB6Reader.Read("EmotesText.db2", DB6Metas.EmotesTextMeta, HotfixStatements.SEL_EMOTES_TEXT, HotfixStatements.SEL_EMOTES_TEXT_LOCALE); + EmotesTextSoundStorage = DB6Reader.Read("EmotesTextSound.db2", DB6Metas.EmotesTextSoundMeta, HotfixStatements.SEL_EMOTES_TEXT_SOUND); + FactionStorage = DB6Reader.Read("Faction.db2", DB6Metas.FactionMeta, HotfixStatements.SEL_FACTION, HotfixStatements.SEL_FACTION_LOCALE); + FactionTemplateStorage = DB6Reader.Read("FactionTemplate.db2", DB6Metas.FactionTemplateMeta, HotfixStatements.SEL_FACTION_TEMPLATE); + GameObjectsStorage = DB6Reader.Read("GameObjects.db2", DB6Metas.GameObjectsMeta, HotfixStatements.SEL_GAMEOBJECTS, HotfixStatements.SEL_GAMEOBJECTS_LOCALE); + GameObjectDisplayInfoStorage = DB6Reader.Read("GameObjectDisplayInfo.db2", DB6Metas.GameObjectDisplayInfoMeta, HotfixStatements.SEL_GAMEOBJECT_DISPLAY_INFO); + GarrAbilityStorage = DB6Reader.Read("GarrAbility.db2", DB6Metas.GarrAbilityMeta, HotfixStatements.SEL_GARR_ABILITY, HotfixStatements.SEL_GARR_ABILITY_LOCALE); + GarrBuildingStorage = DB6Reader.Read("GarrBuilding.db2", DB6Metas.GarrBuildingMeta, HotfixStatements.SEL_GARR_BUILDING, HotfixStatements.SEL_GARR_BUILDING_LOCALE); + GarrBuildingPlotInstStorage = DB6Reader.Read("GarrBuildingPlotInst.db2", DB6Metas.GarrBuildingPlotInstMeta, HotfixStatements.SEL_GARR_BUILDING_PLOT_INST); + GarrClassSpecStorage = DB6Reader.Read("GarrClassSpec.db2", DB6Metas.GarrClassSpecMeta, HotfixStatements.SEL_GARR_CLASS_SPEC, HotfixStatements.SEL_GARR_CLASS_SPEC_LOCALE); + GarrFollowerStorage = DB6Reader.Read("GarrFollower.db2", DB6Metas.GarrFollowerMeta, HotfixStatements.SEL_GARR_FOLLOWER, HotfixStatements.SEL_GARR_FOLLOWER_LOCALE); + GarrFollowerXAbilityStorage = DB6Reader.Read("GarrFollowerXAbility.db2", DB6Metas.GarrFollowerXAbilityMeta, HotfixStatements.SEL_GARR_FOLLOWER_X_ABILITY); + GarrPlotBuildingStorage = DB6Reader.Read("GarrPlotBuilding.db2", DB6Metas.GarrPlotBuildingMeta, HotfixStatements.SEL_GARR_PLOT_BUILDING); + GarrPlotStorage = DB6Reader.Read("GarrPlot.db2", DB6Metas.GarrPlotMeta, HotfixStatements.SEL_GARR_PLOT, HotfixStatements.SEL_GARR_PLOT_LOCALE); + GarrPlotInstanceStorage = DB6Reader.Read("GarrPlotInstance.db2", DB6Metas.GarrPlotInstanceMeta, HotfixStatements.SEL_GARR_PLOT_INSTANCE, HotfixStatements.SEL_GARR_PLOT_INSTANCE_LOCALE); + GarrSiteLevelStorage = DB6Reader.Read("GarrSiteLevel.db2", DB6Metas.GarrSiteLevelMeta, HotfixStatements.SEL_GARR_SITE_LEVEL); + GarrSiteLevelPlotInstStorage = DB6Reader.Read("GarrSiteLevelPlotInst.db2", DB6Metas.GarrSiteLevelPlotInstMeta, HotfixStatements.SEL_GARR_SITE_LEVEL_PLOT_INST); + GemPropertiesStorage = DB6Reader.Read("GemProperties.db2", DB6Metas.GemPropertiesMeta, HotfixStatements.SEL_GEM_PROPERTIES); + GlyphBindableSpellStorage = DB6Reader.Read("GlyphBindableSpell.db2", DB6Metas.GlyphBindableSpellMeta, HotfixStatements.SEL_GLYPH_BINDABLE_SPELL); + GlyphPropertiesStorage = DB6Reader.Read("GlyphProperties.db2", DB6Metas.GlyphPropertiesMeta, HotfixStatements.SEL_GLYPH_PROPERTIES); + GlyphRequiredSpecStorage = DB6Reader.Read("GlyphRequiredSpec.db2", DB6Metas.GlyphRequiredSpecMeta, HotfixStatements.SEL_GLYPH_REQUIRED_SPEC); + GuildColorBackgroundStorage = DB6Reader.Read("GuildColorBackground.db2", DB6Metas.GuildColorBackgroundMeta, HotfixStatements.SEL_GUILD_COLOR_BACKGROUND); + GuildColorBorderStorage = DB6Reader.Read("GuildColorBorder.db2", DB6Metas.GuildColorBorderMeta, HotfixStatements.SEL_GUILD_COLOR_BORDER); + GuildColorEmblemStorage = DB6Reader.Read("GuildColorEmblem.db2", DB6Metas.GuildColorEmblemMeta, HotfixStatements.SEL_GUILD_COLOR_EMBLEM); + GuildPerkSpellsStorage = DB6Reader.Read("GuildPerkSpells.db2", DB6Metas.GuildPerkSpellsMeta, HotfixStatements.SEL_GUILD_PERK_SPELLS); + HeirloomStorage = DB6Reader.Read("Heirloom.db2", DB6Metas.HeirloomMeta, HotfixStatements.SEL_HEIRLOOM, HotfixStatements.SEL_HEIRLOOM_LOCALE); + HolidaysStorage = DB6Reader.Read("Holidays.db2", DB6Metas.HolidaysMeta, HotfixStatements.SEL_HOLIDAYS); + ImportPriceArmorStorage = DB6Reader.Read("ImportPriceArmor.db2", DB6Metas.ImportPriceArmorMeta, HotfixStatements.SEL_IMPORT_PRICE_ARMOR); + ImportPriceQualityStorage = DB6Reader.Read("ImportPriceQuality.db2", DB6Metas.ImportPriceQualityMeta, HotfixStatements.SEL_IMPORT_PRICE_QUALITY); + ImportPriceShieldStorage = DB6Reader.Read("ImportPriceShield.db2", DB6Metas.ImportPriceShieldMeta, HotfixStatements.SEL_IMPORT_PRICE_SHIELD); + ImportPriceWeaponStorage = DB6Reader.Read("ImportPriceWeapon.db2", DB6Metas.ImportPriceWeaponMeta, HotfixStatements.SEL_IMPORT_PRICE_WEAPON); + ItemAppearanceStorage = DB6Reader.Read("ItemAppearance.db2", DB6Metas.ItemAppearanceMeta, HotfixStatements.SEL_ITEM_APPEARANCE); + ItemArmorQualityStorage = DB6Reader.Read("ItemArmorQuality.db2", DB6Metas.ItemArmorQualityMeta, HotfixStatements.SEL_ITEM_ARMOR_QUALITY); + ItemArmorShieldStorage = DB6Reader.Read("ItemArmorShield.db2", DB6Metas.ItemArmorShieldMeta, HotfixStatements.SEL_ITEM_ARMOR_SHIELD); + ItemArmorTotalStorage = DB6Reader.Read("ItemArmorTotal.db2", DB6Metas.ItemArmorTotalMeta, HotfixStatements.SEL_ITEM_ARMOR_TOTAL); + ItemBagFamilyStorage = DB6Reader.Read("ItemBagFamily.db2", DB6Metas.ItemBagFamilyMeta, HotfixStatements.SEL_ITEM_BAG_FAMILY, HotfixStatements.SEL_ITEM_BAG_FAMILY_LOCALE); + ItemBonusStorage = DB6Reader.Read("ItemBonus.db2", DB6Metas.ItemBonusMeta, HotfixStatements.SEL_ITEM_BONUS); + ItemBonusListLevelDeltaStorage = DB6Reader.Read("ItemBonusListLevelDelta.db2", DB6Metas.ItemBonusListLevelDeltaMeta, HotfixStatements.SEL_ITEM_BONUS_LIST_LEVEL_DELTA); + ItemBonusTreeNodeStorage = DB6Reader.Read("ItemBonusTreeNode.db2", DB6Metas.ItemBonusTreeNodeMeta, HotfixStatements.SEL_ITEM_BONUS_TREE_NODE); + ItemChildEquipmentStorage = DB6Reader.Read("ItemChildEquipment.db2", DB6Metas.ItemChildEquipmentMeta, HotfixStatements.SEL_ITEM_CHILD_EQUIPMENT); + ItemClassStorage = DB6Reader.Read("ItemClass.db2", DB6Metas.ItemClassMeta, HotfixStatements.SEL_ITEM_CLASS, HotfixStatements.SEL_ITEM_CLASS_LOCALE); + ItemCurrencyCostStorage = DB6Reader.Read("ItemCurrencyCost.db2", DB6Metas.ItemCurrencyCostMeta, HotfixStatements.SEL_ITEM_CURRENCY_COST); + ItemDamageAmmoStorage = DB6Reader.Read("ItemDamageAmmo.db2", DB6Metas.ItemDamageAmmoMeta, HotfixStatements.SEL_ITEM_DAMAGE_AMMO); + ItemDamageOneHandStorage = DB6Reader.Read("ItemDamageOneHand.db2", DB6Metas.ItemDamageOneHandMeta, HotfixStatements.SEL_ITEM_DAMAGE_ONE_HAND); + ItemDamageOneHandCasterStorage = DB6Reader.Read("ItemDamageOneHandCaster.db2", DB6Metas.ItemDamageOneHandCasterMeta, HotfixStatements.SEL_ITEM_DAMAGE_ONE_HAND_CASTER); + ItemDamageTwoHandStorage = DB6Reader.Read("ItemDamageTwoHand.db2", DB6Metas.ItemDamageTwoHandMeta, HotfixStatements.SEL_ITEM_DAMAGE_TWO_HAND); + ItemDamageTwoHandCasterStorage = DB6Reader.Read("ItemDamageTwoHandCaster.db2", DB6Metas.ItemDamageTwoHandCasterMeta, HotfixStatements.SEL_ITEM_DAMAGE_TWO_HAND_CASTER); + ItemDisenchantLootStorage = DB6Reader.Read("ItemDisenchantLoot.db2", DB6Metas.ItemDisenchantLootMeta, HotfixStatements.SEL_ITEM_DISENCHANT_LOOT); + ItemEffectStorage = DB6Reader.Read("ItemEffect.db2", DB6Metas.ItemEffectMeta, HotfixStatements.SEL_ITEM_EFFECT); + ItemStorage = DB6Reader.Read("Item.db2", DB6Metas.ItemMeta, HotfixStatements.SEL_ITEM); + ItemExtendedCostStorage = DB6Reader.Read("ItemExtendedCost.db2", DB6Metas.ItemExtendedCostMeta, HotfixStatements.SEL_ITEM_EXTENDED_COST); + ItemLimitCategoryStorage = DB6Reader.Read("ItemLimitCategory.db2", DB6Metas.ItemLimitCategoryMeta, HotfixStatements.SEL_ITEM_LIMIT_CATEGORY, HotfixStatements.SEL_ITEM_LIMIT_CATEGORY_LOCALE); + ItemModifiedAppearanceStorage = DB6Reader.Read("ItemModifiedAppearance.db2", DB6Metas.ItemModifiedAppearanceMeta, HotfixStatements.SEL_ITEM_MODIFIED_APPEARANCE); + ItemPriceBaseStorage = DB6Reader.Read("ItemPriceBase.db2", DB6Metas.ItemPriceBaseMeta, HotfixStatements.SEL_ITEM_PRICE_BASE); + ItemRandomPropertiesStorage = DB6Reader.Read("ItemRandomProperties.db2", DB6Metas.ItemRandomPropertiesMeta, HotfixStatements.SEL_ITEM_RANDOM_PROPERTIES, HotfixStatements.SEL_ITEM_RANDOM_PROPERTIES_LOCALE); + ItemRandomSuffixStorage = DB6Reader.Read("ItemRandomSuffix.db2", DB6Metas.ItemRandomSuffixMeta, HotfixStatements.SEL_ITEM_RANDOM_SUFFIX, HotfixStatements.SEL_ITEM_RANDOM_SUFFIX_LOCALE); + ItemSearchNameStorage = DB6Reader.Read("ItemSearchName.db2", DB6Metas.ItemSearchNameMeta, HotfixStatements.SEL_ITEM_SEARCH_NAME, HotfixStatements.SEL_ITEM_SEARCH_NAME_LOCALE); + ItemSetStorage = DB6Reader.Read("ItemSet.db2", DB6Metas.ItemSetMeta, HotfixStatements.SEL_ITEM_SET, HotfixStatements.SEL_ITEM_SET_LOCALE); + ItemSetSpellStorage = DB6Reader.Read("ItemSetSpell.db2", DB6Metas.ItemSetSpellMeta, HotfixStatements.SEL_ITEM_SET_SPELL); + ItemSparseStorage = DB6Reader.Read("ItemSparse.db2", DB6Metas.ItemSparseMeta, HotfixStatements.SEL_ITEM_SPARSE, HotfixStatements.SEL_ITEM_SPARSE_LOCALE); + ItemSpecStorage = DB6Reader.Read("ItemSpec.db2", DB6Metas.ItemSpecMeta, HotfixStatements.SEL_ITEM_SPEC); + ItemSpecOverrideStorage = DB6Reader.Read("ItemSpecOverride.db2", DB6Metas.ItemSpecOverrideMeta, HotfixStatements.SEL_ITEM_SPEC_OVERRIDE); + ItemUpgradeStorage = DB6Reader.Read("ItemUpgrade.db2", DB6Metas.ItemUpgradeMeta, HotfixStatements.SEL_ITEM_UPGRADE); + ItemXBonusTreeStorage = DB6Reader.Read("ItemXBonusTree.db2", DB6Metas.ItemXBonusTreeMeta, HotfixStatements.SEL_ITEM_X_BONUS_TREE); + KeyChainStorage = DB6Reader.Read("KeyChain.db2", DB6Metas.KeyChainMeta, HotfixStatements.SEL_KEY_CHAIN); + LfgDungeonsStorage = DB6Reader.Read("LfgDungeons.db2", DB6Metas.LfgDungeonsMeta, HotfixStatements.SEL_LFG_DUNGEONS, HotfixStatements.SEL_LFG_DUNGEONS_LOCALE); + LightStorage = DB6Reader.Read("Light.db2", DB6Metas.LightMeta, HotfixStatements.SEL_LIGHT); + LiquidTypeStorage = DB6Reader.Read("LiquidType.db2", DB6Metas.LiquidTypeMeta, HotfixStatements.SEL_LIQUID_TYPE, HotfixStatements.SEL_LIQUID_TYPE_LOCALE); + LockStorage = DB6Reader.Read("Lock.db2", DB6Metas.LockMeta, HotfixStatements.SEL_LOCK); + MailTemplateStorage = DB6Reader.Read("MailTemplate.db2", DB6Metas.MailTemplateMeta, HotfixStatements.SEL_MAIL_TEMPLATE, HotfixStatements.SEL_MAIL_TEMPLATE_LOCALE); + MapStorage = DB6Reader.Read("Map.db2", DB6Metas.MapMeta, HotfixStatements.SEL_MAP, HotfixStatements.SEL_MAP_LOCALE); + MapDifficultyStorage = DB6Reader.Read("MapDifficulty.db2", DB6Metas.MapDifficultyMeta, HotfixStatements.SEL_MAP_DIFFICULTY, HotfixStatements.SEL_MAP_DIFFICULTY_LOCALE); + ModifierTreeStorage = DB6Reader.Read("ModifierTree.db2", DB6Metas.ModifierTreeMeta, HotfixStatements.SEL_MODIFIER_TREE); + MountCapabilityStorage = DB6Reader.Read("MountCapability.db2", DB6Metas.MountCapabilityMeta, HotfixStatements.SEL_MOUNT_CAPABILITY); + MountStorage = DB6Reader.Read("Mount.db2", DB6Metas.MountMeta, HotfixStatements.SEL_MOUNT, HotfixStatements.SEL_MOUNT_LOCALE); + MountTypeXCapabilityStorage = DB6Reader.Read("MountTypeXCapability.db2", DB6Metas.MountTypeXCapabilityMeta, HotfixStatements.SEL_MOUNT_TYPE_X_CAPABILITY); + MountXDisplayStorage = DB6Reader.Read("MountXDisplay.db2", DB6Metas.MountXDisplayMeta, HotfixStatements.SEL_MOUNT_X_DISPLAY); + MovieStorage = DB6Reader.Read("Movie.db2", DB6Metas.MovieMeta, HotfixStatements.SEL_MOVIE); + NameGenStorage = DB6Reader.Read("NameGen.db2", DB6Metas.NameGenMeta, HotfixStatements.SEL_NAME_GEN, HotfixStatements.SEL_NAME_GEN_LOCALE); + NamesProfanityStorage = DB6Reader.Read("NamesProfanity.db2", DB6Metas.NamesProfanityMeta, HotfixStatements.SEL_NAMES_PROFANITY); + NamesReservedStorage = DB6Reader.Read("NamesReserved.db2", DB6Metas.NamesReservedMeta, HotfixStatements.SEL_NAMES_RESERVED, HotfixStatements.SEL_NAMES_RESERVED_LOCALE); + NamesReservedLocaleStorage = DB6Reader.Read("NamesReservedLocale.db2", DB6Metas.NamesReservedLocaleMeta, HotfixStatements.SEL_NAMES_RESERVED_LOCALE); + OverrideSpellDataStorage = DB6Reader.Read("OverrideSpellData.db2", DB6Metas.OverrideSpellDataMeta, HotfixStatements.SEL_OVERRIDE_SPELL_DATA); + PhaseStorage = DB6Reader.Read("Phase.db2", DB6Metas.PhaseMeta, HotfixStatements.SEL_PHASE); + PhaseXPhaseGroupStorage = DB6Reader.Read("PhaseXPhaseGroup.db2", DB6Metas.PhaseXPhaseGroupMeta, HotfixStatements.SEL_PHASE_X_PHASE_GROUP); + PlayerConditionStorage = DB6Reader.Read("PlayerCondition.db2", DB6Metas.PlayerConditionMeta, HotfixStatements.SEL_PLAYER_CONDITION, HotfixStatements.SEL_PLAYER_CONDITION_LOCALE); + PowerDisplayStorage = DB6Reader.Read("PowerDisplay.db2", DB6Metas.PowerDisplayMeta, HotfixStatements.SEL_POWER_DISPLAY); + PowerTypeStorage = DB6Reader.Read("PowerType.db2", DB6Metas.PowerTypeMeta, HotfixStatements.SEL_POWER_TYPE); + PrestigeLevelInfoStorage = DB6Reader.Read("PrestigeLevelInfo.db2", DB6Metas.PrestigeLevelInfoMeta, HotfixStatements.SEL_PRESTIGE_LEVEL_INFO, HotfixStatements.SEL_PRESTIGE_LEVEL_INFO_LOCALE); + PvpDifficultyStorage = DB6Reader.Read("PvpDifficulty.db2", DB6Metas.PvpDifficultyMeta, HotfixStatements.SEL_PVP_DIFFICULTY); + PvpRewardStorage = DB6Reader.Read("PvpReward.db2", DB6Metas.PvpRewardMeta, HotfixStatements.SEL_PVP_REWARD); + QuestFactionRewardStorage = DB6Reader.Read("QuestFactionReward.db2", DB6Metas.QuestFactionRewardMeta, HotfixStatements.SEL_QUEST_FACTION_REWARD); + QuestMoneyRewardStorage = DB6Reader.Read("QuestMoneyReward.db2", DB6Metas.QuestMoneyRewardMeta, HotfixStatements.SEL_QUEST_MONEY_REWARD); + QuestPackageItemStorage = DB6Reader.Read("QuestPackageItem.db2", DB6Metas.QuestPackageItemMeta, HotfixStatements.SEL_QUEST_PACKAGE_ITEM); + QuestSortStorage = DB6Reader.Read("QuestSort.db2", DB6Metas.QuestSortMeta, HotfixStatements.SEL_QUEST_SORT, HotfixStatements.SEL_QUEST_SORT_LOCALE); + QuestV2Storage = DB6Reader.Read("QuestV2.db2", DB6Metas.QuestV2Meta, HotfixStatements.SEL_QUEST_V2); + QuestXPStorage = DB6Reader.Read("QuestXP.db2", DB6Metas.QuestXPMeta, HotfixStatements.SEL_QUEST_XP); + RandPropPointsStorage = DB6Reader.Read("RandPropPoints.db2", DB6Metas.RandPropPointsMeta, HotfixStatements.SEL_RAND_PROP_POINTS); + RewardPackStorage = DB6Reader.Read("RewardPack.db2", DB6Metas.RewardPackMeta, HotfixStatements.SEL_REWARD_PACK); + RewardPackXItemStorage = DB6Reader.Read("RewardPackXItem.db2", DB6Metas.RewardPackXItemMeta, HotfixStatements.SEL_REWARD_PACK_X_ITEM); + RulesetItemUpgradeStorage = DB6Reader.Read("RulesetItemUpgrade.db2", DB6Metas.RulesetItemUpgradeMeta, HotfixStatements.SEL_RULESET_ITEM_UPGRADE); + ScalingStatDistributionStorage = DB6Reader.Read("ScalingStatDistribution.db2", DB6Metas.ScalingStatDistributionMeta, HotfixStatements.SEL_SCALING_STAT_DISTRIBUTION); + ScenarioStorage = DB6Reader.Read("Scenario.db2", DB6Metas.ScenarioMeta, HotfixStatements.SEL_SCENARIO, HotfixStatements.SEL_SCENARIO_LOCALE); + ScenarioStepStorage = DB6Reader.Read("ScenarioStep.db2", DB6Metas.ScenarioStepMeta, HotfixStatements.SEL_SCENARIO_STEP, HotfixStatements.SEL_SCENARIO_STEP_LOCALE); + SceneScriptStorage = DB6Reader.Read("SceneScript.db2", DB6Metas.SceneScriptMeta, HotfixStatements.SEL_SCENE_SCRIPT); + SceneScriptPackageStorage = DB6Reader.Read("SceneScriptPackage.db2", DB6Metas.SceneScriptPackageMeta, HotfixStatements.SEL_SCENE_SCRIPT_PACKAGE); + SkillLineStorage = DB6Reader.Read("SkillLine.db2", DB6Metas.SkillLineMeta, HotfixStatements.SEL_SKILL_LINE, HotfixStatements.SEL_SKILL_LINE_LOCALE); + SkillLineAbilityStorage = DB6Reader.Read("SkillLineAbility.db2", DB6Metas.SkillLineAbilityMeta, HotfixStatements.SEL_SKILL_LINE_ABILITY); + SkillRaceClassInfoStorage = DB6Reader.Read("SkillRaceClassInfo.db2", DB6Metas.SkillRaceClassInfoMeta, HotfixStatements.SEL_SKILL_RACE_CLASS_INFO); + SoundKitStorage = DB6Reader.Read("SoundKit.db2", DB6Metas.SoundKitMeta, HotfixStatements.SEL_SOUND_KIT, HotfixStatements.SEL_SOUND_KIT_LOCALE); + SpecializationSpellsStorage = DB6Reader.Read("SpecializationSpells.db2", DB6Metas.SpecializationSpellsMeta, HotfixStatements.SEL_SPECIALIZATION_SPELLS, HotfixStatements.SEL_SPECIALIZATION_SPELLS_LOCALE); + SpellStorage = DB6Reader.Read("Spell.db2", DB6Metas.SpellMeta, HotfixStatements.SEL_SPELL, HotfixStatements.SEL_SPELL_LOCALE); + SpellAuraOptionsStorage = DB6Reader.Read("SpellAuraOptions.db2", DB6Metas.SpellAuraOptionsMeta, HotfixStatements.SEL_SPELL_AURA_OPTIONS); + SpellAuraRestrictionsStorage = DB6Reader.Read("SpellAuraRestrictions.db2", DB6Metas.SpellAuraRestrictionsMeta, HotfixStatements.SEL_SPELL_AURA_RESTRICTIONS); + SpellCastTimesStorage = DB6Reader.Read("SpellCastTimes.db2", DB6Metas.SpellCastTimesMeta, HotfixStatements.SEL_SPELL_CAST_TIMES); + SpellCastingRequirementsStorage = DB6Reader.Read("SpellCastingRequirements.db2", DB6Metas.SpellCastingRequirementsMeta, HotfixStatements.SEL_SPELL_CASTING_REQUIREMENTS); + SpellCategoriesStorage = DB6Reader.Read("SpellCategories.db2", DB6Metas.SpellCategoriesMeta, HotfixStatements.SEL_SPELL_CATEGORIES); + SpellCategoryStorage = DB6Reader.Read("SpellCategory.db2", DB6Metas.SpellCategoryMeta, HotfixStatements.SEL_SPELL_CATEGORY, HotfixStatements.SEL_SPELL_CATEGORY_LOCALE); + SpellClassOptionsStorage = DB6Reader.Read("SpellClassOptions.db2", DB6Metas.SpellClassOptionsMeta, HotfixStatements.SEL_SPELL_CLASS_OPTIONS); + SpellCooldownsStorage = DB6Reader.Read("SpellCooldowns.db2", DB6Metas.SpellCooldownsMeta, HotfixStatements.SEL_SPELL_COOLDOWNS); + SpellDurationStorage = DB6Reader.Read("SpellDuration.db2", DB6Metas.SpellDurationMeta, HotfixStatements.SEL_SPELL_DURATION); + SpellEffectStorage = DB6Reader.Read("SpellEffect.db2", DB6Metas.SpellEffectMeta, HotfixStatements.SEL_SPELL_EFFECT); + SpellEffectScalingStorage = DB6Reader.Read("SpellEffectScaling.db2", DB6Metas.SpellEffectScalingMeta, HotfixStatements.SEL_SPELL_EFFECT_SCALING); + SpellEquippedItemsStorage = DB6Reader.Read("SpellEquippedItems.db2", DB6Metas.SpellEquippedItemsMeta, HotfixStatements.SEL_SPELL_EQUIPPED_ITEMS); + SpellFocusObjectStorage = DB6Reader.Read("SpellFocusObject.db2", DB6Metas.SpellFocusObjectMeta, HotfixStatements.SEL_SPELL_FOCUS_OBJECT, HotfixStatements.SEL_SPELL_FOCUS_OBJECT_LOCALE); + SpellInterruptsStorage = DB6Reader.Read("SpellInterrupts.db2", DB6Metas.SpellInterruptsMeta, HotfixStatements.SEL_SPELL_INTERRUPTS); + SpellItemEnchantmentStorage = DB6Reader.Read("SpellItemEnchantment.db2", DB6Metas.SpellItemEnchantmentMeta, HotfixStatements.SEL_SPELL_ITEM_ENCHANTMENT, HotfixStatements.SEL_SPELL_ITEM_ENCHANTMENT_LOCALE); + SpellItemEnchantmentConditionStorage = DB6Reader.Read("SpellItemEnchantmentCondition.db2", DB6Metas.SpellItemEnchantmentConditionMeta, HotfixStatements.SEL_SPELL_ITEM_ENCHANTMENT_CONDITION); + SpellLearnSpellStorage = DB6Reader.Read("SpellLearnSpell.db2", DB6Metas.SpellLearnSpellMeta, HotfixStatements.SEL_SPELL_LEARN_SPELL); + SpellLevelsStorage = DB6Reader.Read("SpellLevels.db2", DB6Metas.SpellLevelsMeta, HotfixStatements.SEL_SPELL_LEVELS); + SpellMiscStorage = DB6Reader.Read("SpellMisc.db2", DB6Metas.SpellMiscMeta, HotfixStatements.SEL_SPELL_MISC); + SpellPowerStorage = DB6Reader.Read("SpellPower.db2", DB6Metas.SpellPowerMeta, HotfixStatements.SEL_SPELL_POWER); + SpellPowerDifficultyStorage = DB6Reader.Read("SpellPowerDifficulty.db2", DB6Metas.SpellPowerDifficultyMeta, HotfixStatements.SEL_SPELL_POWER_DIFFICULTY); + SpellProcsPerMinuteStorage = DB6Reader.Read("SpellProcsPerMinute.db2", DB6Metas.SpellProcsPerMinuteMeta, HotfixStatements.SEL_SPELL_PROCS_PER_MINUTE); + SpellProcsPerMinuteModStorage = DB6Reader.Read("SpellProcsPerMinuteMod.db2", DB6Metas.SpellProcsPerMinuteModMeta, HotfixStatements.SEL_SPELL_PROCS_PER_MINUTE_MOD); + SpellRadiusStorage = DB6Reader.Read("SpellRadius.db2", DB6Metas.SpellRadiusMeta, HotfixStatements.SEL_SPELL_RADIUS); + SpellRangeStorage = DB6Reader.Read("SpellRange.db2", DB6Metas.SpellRangeMeta, HotfixStatements.SEL_SPELL_RANGE, HotfixStatements.SEL_SPELL_RANGE_LOCALE); + SpellReagentsStorage = DB6Reader.Read("SpellReagents.db2", DB6Metas.SpellReagentsMeta, HotfixStatements.SEL_SPELL_REAGENTS); + SpellScalingStorage = DB6Reader.Read("SpellScaling.db2", DB6Metas.SpellScalingMeta, HotfixStatements.SEL_SPELL_SCALING); + SpellShapeshiftStorage = DB6Reader.Read("SpellShapeshift.db2", DB6Metas.SpellShapeshiftMeta, HotfixStatements.SEL_SPELL_SHAPESHIFT); + SpellShapeshiftFormStorage = DB6Reader.Read("SpellShapeshiftForm.db2", DB6Metas.SpellShapeshiftFormMeta, HotfixStatements.SEL_SPELL_SHAPESHIFT_FORM, HotfixStatements.SEL_SPELL_SHAPESHIFT_FORM_LOCALE); + SpellTargetRestrictionsStorage = DB6Reader.Read("SpellTargetRestrictions.db2", DB6Metas.SpellTargetRestrictionsMeta, HotfixStatements.SEL_SPELL_TARGET_RESTRICTIONS); + SpellTotemsStorage = DB6Reader.Read("SpellTotems.db2", DB6Metas.SpellTotemsMeta, HotfixStatements.SEL_SPELL_TOTEMS); + SpellXSpellVisualStorage = DB6Reader.Read("SpellXSpellVisual.db2", DB6Metas.SpellXSpellVisualMeta, HotfixStatements.SEL_SPELL_X_SPELL_VISUAL); + SummonPropertiesStorage = DB6Reader.Read("SummonProperties.db2", DB6Metas.SummonPropertiesMeta, HotfixStatements.SEL_SUMMON_PROPERTIES); + TactKeyStorage = DB6Reader.Read("TactKey.db2", DB6Metas.TactKeyMeta, HotfixStatements.SEL_TACT_KEY); + TalentStorage = DB6Reader.Read("Talent.db2", DB6Metas.TalentMeta, HotfixStatements.SEL_TALENT, HotfixStatements.SEL_TALENT_LOCALE); + TaxiNodesStorage = DB6Reader.Read("TaxiNodes.db2", DB6Metas.TaxiNodesMeta, HotfixStatements.SEL_TAXI_NODES, HotfixStatements.SEL_TAXI_NODES_LOCALE); + TaxiPathStorage = DB6Reader.Read("TaxiPath.db2", DB6Metas.TaxiPathMeta, HotfixStatements.SEL_TAXI_PATH); + TaxiPathNodeStorage = DB6Reader.Read("TaxiPathNode.db2", DB6Metas.TaxiPathNodeMeta, HotfixStatements.SEL_TAXI_PATH_NODE); + TotemCategoryStorage = DB6Reader.Read("TotemCategory.db2", DB6Metas.TotemCategoryMeta, HotfixStatements.SEL_TOTEM_CATEGORY, HotfixStatements.SEL_TOTEM_CATEGORY_LOCALE); + ToyStorage = DB6Reader.Read("Toy.db2", DB6Metas.ToyMeta, HotfixStatements.SEL_TOY, HotfixStatements.SEL_TOY_LOCALE); + TransportAnimationStorage = DB6Reader.Read("TransportAnimation.db2", DB6Metas.TransportAnimationMeta, HotfixStatements.SEL_TRANSPORT_ANIMATION); + TransportRotationStorage = DB6Reader.Read("TransportRotation.db2", DB6Metas.TransportRotationMeta, HotfixStatements.SEL_TRANSPORT_ROTATION); + UnitPowerBarStorage = DB6Reader.Read("UnitPowerBar.db2", DB6Metas.UnitPowerBarMeta, HotfixStatements.SEL_UNIT_POWER_BAR, HotfixStatements.SEL_UNIT_POWER_BAR_LOCALE); + VehicleStorage = DB6Reader.Read("Vehicle.db2", DB6Metas.VehicleMeta, HotfixStatements.SEL_VEHICLE); + VehicleSeatStorage = DB6Reader.Read("VehicleSeat.db2", DB6Metas.VehicleSeatMeta, HotfixStatements.SEL_VEHICLE_SEAT); + WMOAreaTableStorage = DB6Reader.Read("WMOAreaTable.db2", DB6Metas.WMOAreaTableMeta, HotfixStatements.SEL_WMO_AREA_TABLE, HotfixStatements.SEL_WMO_AREA_TABLE_LOCALE); + WorldMapAreaStorage = DB6Reader.Read("WorldMapArea.db2", DB6Metas.WorldMapAreaMeta, HotfixStatements.SEL_WORLD_MAP_AREA); + WorldMapOverlayStorage = DB6Reader.Read("WorldMapOverlay.db2", DB6Metas.WorldMapOverlayMeta, HotfixStatements.SEL_WORLD_MAP_OVERLAY); + WorldMapTransformsStorage = DB6Reader.Read("WorldMapTransforms.db2", DB6Metas.WorldMapTransformsMeta, HotfixStatements.SEL_WORLD_MAP_TRANSFORMS); + WorldSafeLocsStorage = DB6Reader.Read("WorldSafeLocs.db2", DB6Metas.WorldSafeLocsMeta, HotfixStatements.SEL_WORLD_SAFE_LOCS, HotfixStatements.SEL_WORLD_SAFE_LOCS_LOCALE); + + foreach (var entry in TaxiPathStorage.Values) + { + if (!TaxiPathSetBySource.ContainsKey(entry.From)) + TaxiPathSetBySource.Add(entry.From, new Dictionary()); + TaxiPathSetBySource[entry.From][entry.To] = new TaxiPathBySourceAndDestination(entry.Id, entry.Cost); + } + + uint pathCount = TaxiPathStorage.Keys.Max() + 1; + + // Calculate path nodes count + uint[] pathLength = new uint[pathCount]; // 0 and some other indexes not used + foreach (TaxiPathNodeRecord entry in CliDB.TaxiPathNodeStorage.Values) + if (pathLength[entry.PathID] < entry.NodeIndex + 1) + pathLength[entry.PathID] = entry.NodeIndex + 1u; + + // Set path length + for (uint i = 0; i < pathCount; ++i) + TaxiPathNodesByPath[i] = new TaxiPathNodeRecord[pathLength[i]]; + + // fill data + foreach (var entry in TaxiPathNodeStorage.Values) + TaxiPathNodesByPath[entry.PathID][entry.NodeIndex] = entry; + + TaxiPathNodeStorage.Clear(); + + foreach (var node in TaxiNodesStorage.Values) + { + if (!node.Flags.HasAnyFlag(TaxiNodeFlags.Alliance | TaxiNodeFlags.Horde)) + continue; + + // valid taxi network node + byte field = (byte)((node.Id - 1) / 8); + byte submask = (byte)(1 << (int)((node.Id - 1) % 8)); + + TaxiNodesMask[field] |= submask; + if (node.Flags.HasAnyFlag(TaxiNodeFlags.Horde)) + HordeTaxiNodesMask[field] |= submask; + if (node.Flags.HasAnyFlag(TaxiNodeFlags.Alliance)) + AllianceTaxiNodesMask[field] |= submask; + + uint nodeMap; + Global.DB2Mgr.DeterminaAlternateMapPosition(node.MapID, node.Pos.X, node.Pos.Y, node.Pos.Z, out nodeMap); + if (nodeMap < 2) + OldContinentsNodesMask[field] |= submask; + } + + Global.DB2Mgr.LoadStores(); + + // Check loaded DB2 files proper version + if (!AreaTableStorage.ContainsKey(8485) || // last area (areaflag) added in 7.0.3 (22594) + !CharTitlesStorage.ContainsKey(486) || // last char title added in 7.0.3 (22594) + !GemPropertiesStorage.ContainsKey(3363) || // last gem property added in 7.0.3 (22594) + !ItemStorage.ContainsKey(142526) || // last item added in 7.0.3 (22594) + !ItemExtendedCostStorage.ContainsKey(6125) || // last item extended cost added in 7.0.3 (22594) + !MapStorage.ContainsKey(1670) || // last map added in 7.0.3 (22594) + !SpellStorage.ContainsKey(231371)) // last spell added in 7.0.3 (22594) + { + Log.outError(LogFilter.Misc, "You have _outdated_ DB2 files. Please extract correct versions from current using client."); + Global.WorldMgr.ShutdownServ(10, ShutdownMask.Force, ShutdownExitCode.Error); + } + + Log.outInfo(LogFilter.ServerLoading, "Initialized {0} DB2 data storages in {1} ms", LoadedFileCount, Time.GetMSTimeDiffToNow(oldMSTime)); + } + + public static void LoadGameTables(string dataPath) + { + uint oldMSTime = Time.GetMSTime(); + LoadedFileCount = 0; + + DataPath = dataPath + "/gt/"; + + ArmorMitigationByLvlGameTable = GameTableReader.Read("ArmorMitigationByLvl.txt"); + ArtifactKnowledgeMultiplierGameTable = GameTableReader.Read("ArtifactKnowledgeMultiplier.txt"); + ArtifactLevelXPGameTable = GameTableReader.Read("artifactLevelXP.txt"); + BarberShopCostBaseGameTable = GameTableReader.Read("BarberShopCostBase.txt"); + BaseMPGameTable = GameTableReader.Read("BaseMp.txt"); + CombatRatingsGameTable = GameTableReader.Read("CombatRatings.txt"); + CombatRatingsMultByILvlGameTable = GameTableReader.Read("CombatRatingsMultByILvl.txt"); + ItemSocketCostPerLevelGameTable = GameTableReader.Read("ItemSocketCostPerLevel.txt"); + HonorLevelGameTable = GameTableReader.Read("HonorLevel.txt"); + HpPerStaGameTable = GameTableReader.Read("HpPerSta.txt"); + NpcDamageByClassGameTable[0] = GameTableReader.Read("NpcDamageByClass.txt"); + NpcDamageByClassGameTable[1] = GameTableReader.Read("NpcDamageByClassExp1.txt"); + NpcDamageByClassGameTable[2] = GameTableReader.Read("NpcDamageByClassExp2.txt"); + NpcDamageByClassGameTable[3] = GameTableReader.Read("NpcDamageByClassExp3.txt"); + NpcDamageByClassGameTable[4] = GameTableReader.Read("NpcDamageByClassExp4.txt"); + NpcDamageByClassGameTable[5] = GameTableReader.Read("NpcDamageByClassExp5.txt"); + NpcDamageByClassGameTable[6] = GameTableReader.Read("NpcDamageByClassExp6.txt"); + NpcManaCostScalerGameTable = GameTableReader.Read("NPCManaCostScaler.txt"); + NpcTotalHpGameTable[0] = GameTableReader.Read("NpcTotalHp.txt"); + NpcTotalHpGameTable[1] = GameTableReader.Read("NpcTotalHpExp1.txt"); + NpcTotalHpGameTable[2] = GameTableReader.Read("NpcTotalHpExp2.txt"); + NpcTotalHpGameTable[3] = GameTableReader.Read("NpcTotalHpExp3.txt"); + NpcTotalHpGameTable[4] = GameTableReader.Read("NpcTotalHpExp4.txt"); + NpcTotalHpGameTable[5] = GameTableReader.Read("NpcTotalHpExp5.txt"); + NpcTotalHpGameTable[6] = GameTableReader.Read("NpcTotalHpExp6.txt"); + SpellScalingGameTable = GameTableReader.Read("SpellScaling.txt"); + XpGameTable = GameTableReader.Read("xp.txt"); + + Log.outInfo(LogFilter.ServerLoading, "Initialized {0} DBC GameTables data stores in {1} ms", LoadedFileCount, Time.GetMSTimeDiffToNow(oldMSTime)); + } + + #region Main Collections + public static DB6Storage AchievementStorage; + public static DB6Storage AnimKitStorage; + public static DB6Storage AreaGroupMemberStorage; + public static DB6Storage AreaTableStorage; + public static DB6Storage AreaTriggerStorage; + public static DB6Storage ArmorLocationStorage; + public static DB6Storage ArtifactStorage; + public static DB6Storage ArtifactAppearanceStorage; + public static DB6Storage ArtifactAppearanceSetStorage; + public static DB6Storage ArtifactCategoryStorage; + public static DB6Storage ArtifactPowerStorage; + public static DB6Storage ArtifactPowerLinkStorage; + public static DB6Storage ArtifactPowerPickerStorage; + public static DB6Storage ArtifactPowerRankStorage; + public static DB6Storage ArtifactQuestXPStorage; + public static DB6Storage AuctionHouseStorage; + public static DB6Storage BankBagSlotPricesStorage; + public static DB6Storage BannedAddOnsStorage; + public static DB6Storage BarberShopStyleStorage; + public static DB6Storage BattlePetBreedQualityStorage; + public static DB6Storage BattlePetBreedStateStorage; + public static DB6Storage BattlePetSpeciesStorage; + public static DB6Storage BattlePetSpeciesStateStorage; + public static DB6Storage BattlemasterListStorage; + public static DB6Storage BroadcastTextStorage; + public static DB6Storage CharSectionsStorage; + public static DB6Storage CharStartOutfitStorage; + public static DB6Storage CharTitlesStorage; + public static DB6Storage ChatChannelsStorage; + public static DB6Storage ChrClassesStorage; + public static DB6Storage ChrClassesXPowerTypesStorage; + public static DB6Storage ChrRacesStorage; + public static DB6Storage ChrSpecializationStorage; + public static DB6Storage CinematicCameraStorage; + public static DB6Storage CinematicSequencesStorage; + public static DB6Storage ConversationLineStorage; + public static DB6Storage CreatureDisplayInfoStorage; + public static DB6Storage CreatureDisplayInfoExtraStorage; + public static DB6Storage CreatureFamilyStorage; + public static DB6Storage CreatureModelDataStorage; + public static DB6Storage CreatureTypeStorage; + public static DB6Storage CriteriaStorage; + public static DB6Storage CriteriaTreeStorage; + public static DB6Storage CurrencyTypesStorage; + public static DB6Storage CurveStorage; + public static DB6Storage CurvePointStorage; + public static DB6Storage DestructibleModelDataStorage; + public static DB6Storage DifficultyStorage; + public static DB6Storage DungeonEncounterStorage; + public static DB6Storage DurabilityCostsStorage; + public static DB6Storage DurabilityQualityStorage; + public static DB6Storage EmotesStorage; + public static DB6Storage EmotesTextStorage; + public static DB6Storage EmotesTextSoundStorage; + public static DB6Storage FactionStorage; + public static DB6Storage FactionTemplateStorage; + public static DB6Storage GameObjectsStorage; + public static DB6Storage GameObjectDisplayInfoStorage; + public static DB6Storage GarrAbilityStorage; + public static DB6Storage GarrBuildingStorage; + public static DB6Storage GarrBuildingPlotInstStorage; + public static DB6Storage GarrClassSpecStorage; + public static DB6Storage GarrFollowerStorage; + public static DB6Storage GarrFollowerXAbilityStorage; + public static DB6Storage GarrPlotBuildingStorage; + public static DB6Storage GarrPlotStorage; + public static DB6Storage GarrPlotInstanceStorage; + public static DB6Storage GarrSiteLevelStorage; + public static DB6Storage GarrSiteLevelPlotInstStorage; + public static DB6Storage GemPropertiesStorage; + public static DB6Storage GlyphBindableSpellStorage; + public static DB6Storage GlyphPropertiesStorage; + public static DB6Storage GlyphRequiredSpecStorage; + public static DB6Storage GuildColorBackgroundStorage; + public static DB6Storage GuildColorBorderStorage; + public static DB6Storage GuildColorEmblemStorage; + public static DB6Storage GuildPerkSpellsStorage; + public static DB6Storage HeirloomStorage; + public static DB6Storage HolidaysStorage; + public static DB6Storage ImportPriceArmorStorage; + public static DB6Storage ImportPriceQualityStorage; + public static DB6Storage ImportPriceShieldStorage; + public static DB6Storage ImportPriceWeaponStorage; + public static DB6Storage ItemAppearanceStorage; + public static DB6Storage ItemArmorQualityStorage; + public static DB6Storage ItemArmorShieldStorage; + public static DB6Storage ItemArmorTotalStorage; + public static DB6Storage ItemBagFamilyStorage; + public static DB6Storage ItemBonusStorage; + public static DB6Storage ItemBonusListLevelDeltaStorage; + public static DB6Storage ItemBonusTreeNodeStorage; + public static DB6Storage ItemClassStorage; + public static DB6Storage ItemChildEquipmentStorage; + public static DB6Storage ItemCurrencyCostStorage; + public static DB6Storage ItemDamageAmmoStorage; + public static DB6Storage ItemDamageOneHandStorage; + public static DB6Storage ItemDamageOneHandCasterStorage; + public static DB6Storage ItemDamageTwoHandStorage; + public static DB6Storage ItemDamageTwoHandCasterStorage; + public static DB6Storage ItemDisenchantLootStorage; + public static DB6Storage ItemEffectStorage; + public static DB6Storage ItemStorage; + public static DB6Storage ItemExtendedCostStorage; + public static DB6Storage ItemLimitCategoryStorage; + public static DB6Storage ItemModifiedAppearanceStorage; + public static DB6Storage ItemPriceBaseStorage; + public static DB6Storage ItemRandomPropertiesStorage; + public static DB6Storage ItemRandomSuffixStorage; + public static DB6Storage ItemSearchNameStorage; + public static DB6Storage ItemSetStorage; + public static DB6Storage ItemSetSpellStorage; + public static DB6Storage ItemSparseStorage; + public static DB6Storage ItemSpecStorage; + public static DB6Storage ItemSpecOverrideStorage; + public static DB6Storage ItemUpgradeStorage; + public static DB6Storage ItemXBonusTreeStorage; + public static DB6Storage KeyChainStorage; + public static DB6Storage LfgDungeonsStorage; + public static DB6Storage LightStorage; + public static DB6Storage LiquidTypeStorage; + public static DB6Storage LockStorage; + public static DB6Storage MailTemplateStorage; + public static DB6Storage MapStorage; + public static DB6Storage MapDifficultyStorage; + public static DB6Storage ModifierTreeStorage; + public static DB6Storage MountCapabilityStorage; + public static DB6Storage MountStorage; + public static DB6Storage MountTypeXCapabilityStorage; + public static DB6Storage MountXDisplayStorage; + public static DB6Storage MovieStorage; + public static DB6Storage NameGenStorage; + public static DB6Storage NamesProfanityStorage; + public static DB6Storage NamesReservedStorage; + public static DB6Storage NamesReservedLocaleStorage; + public static DB6Storage OverrideSpellDataStorage; + public static DB6Storage PhaseStorage; + public static DB6Storage PhaseXPhaseGroupStorage; + public static DB6Storage PlayerConditionStorage; + public static DB6Storage PowerDisplayStorage; + public static DB6Storage PowerTypeStorage; + public static DB6Storage PrestigeLevelInfoStorage; + public static DB6Storage PvpDifficultyStorage; + public static DB6Storage PvpRewardStorage; + public static DB6Storage QuestFactionRewardStorage; + public static DB6Storage QuestMoneyRewardStorage; + public static DB6Storage QuestPackageItemStorage; + public static DB6Storage QuestSortStorage; + public static DB6Storage QuestV2Storage; + public static DB6Storage QuestXPStorage; + public static DB6Storage RandPropPointsStorage; + public static DB6Storage RewardPackStorage; + public static DB6Storage RewardPackXItemStorage; + public static DB6Storage RulesetItemUpgradeStorage; + public static DB6Storage ScalingStatDistributionStorage; + public static DB6Storage ScenarioStorage; + public static DB6Storage ScenarioStepStorage; + public static DB6Storage SceneScriptStorage; + public static DB6Storage SceneScriptPackageStorage; + public static DB6Storage SkillLineStorage; + public static DB6Storage SkillLineAbilityStorage; + public static DB6Storage SkillRaceClassInfoStorage; + public static DB6Storage SoundKitStorage; + public static DB6Storage SpecializationSpellsStorage; + public static DB6Storage SpellStorage; + public static DB6Storage SpellAuraOptionsStorage; + public static DB6Storage SpellAuraRestrictionsStorage; + public static DB6Storage SpellCastTimesStorage; + public static DB6Storage SpellCastingRequirementsStorage; + public static DB6Storage SpellCategoriesStorage; + public static DB6Storage SpellCategoryStorage; + public static DB6Storage SpellClassOptionsStorage; + public static DB6Storage SpellCooldownsStorage; + public static DB6Storage SpellDurationStorage; + public static DB6Storage SpellEffectStorage; + public static DB6Storage SpellEffectScalingStorage; + public static DB6Storage SpellEquippedItemsStorage; + public static DB6Storage SpellFocusObjectStorage; + public static DB6Storage SpellInterruptsStorage; + public static DB6Storage SpellItemEnchantmentStorage; + public static DB6Storage SpellItemEnchantmentConditionStorage; + public static DB6Storage SpellLearnSpellStorage; + public static DB6Storage SpellLevelsStorage; + public static DB6Storage SpellMiscStorage; + public static DB6Storage SpellPowerStorage; + public static DB6Storage SpellPowerDifficultyStorage; + public static DB6Storage SpellProcsPerMinuteStorage; + public static DB6Storage SpellProcsPerMinuteModStorage; + public static DB6Storage SpellRadiusStorage; + public static DB6Storage SpellRangeStorage; + public static DB6Storage SpellReagentsStorage; + public static DB6Storage SpellScalingStorage; + public static DB6Storage SpellShapeshiftStorage; + public static DB6Storage SpellShapeshiftFormStorage; + public static DB6Storage SpellTargetRestrictionsStorage; + public static DB6Storage SpellTotemsStorage; + public static DB6Storage SpellXSpellVisualStorage; + public static DB6Storage SummonPropertiesStorage; + public static DB6Storage TactKeyStorage; + public static DB6Storage TalentStorage; + public static DB6Storage TaxiNodesStorage; + public static DB6Storage TaxiPathStorage; + public static DB6Storage TaxiPathNodeStorage; + public static DB6Storage TotemCategoryStorage; + public static DB6Storage ToyStorage; + public static DB6Storage TransportAnimationStorage; + public static DB6Storage TransportRotationStorage; + public static DB6Storage UnitPowerBarStorage; + public static DB6Storage VehicleStorage; + public static DB6Storage VehicleSeatStorage; + public static DB6Storage WMOAreaTableStorage; + public static DB6Storage WorldMapAreaStorage; + public static DB6Storage WorldMapOverlayStorage; + public static DB6Storage WorldMapTransformsStorage; + public static DB6Storage WorldSafeLocsStorage; + #endregion + + #region GameTables + public static GameTable ArmorMitigationByLvlGameTable; + public static GameTable ArtifactKnowledgeMultiplierGameTable; + public static GameTable ArtifactLevelXPGameTable; + public static GameTable BarberShopCostBaseGameTable; + public static GameTable BaseMPGameTable; + public static GameTable CombatRatingsGameTable; + public static GameTable CombatRatingsMultByILvlGameTable; + public static GameTable HonorLevelGameTable; + public static GameTable HpPerStaGameTable; + public static GameTable ItemSocketCostPerLevelGameTable; + public static GameTable[] NpcDamageByClassGameTable = new GameTable[(int)Expansion.Max]; + public static GameTable NpcManaCostScalerGameTable; + public static GameTable[] NpcTotalHpGameTable = new GameTable[(int)Expansion.Max]; + public static GameTable SpellScalingGameTable; + public static GameTable XpGameTable; + #endregion + + #region Taxi Collections + public static byte[] TaxiNodesMask = new byte[PlayerConst.TaxiMaskSize]; + public static byte[] OldContinentsNodesMask = new byte[PlayerConst.TaxiMaskSize]; + public static byte[] HordeTaxiNodesMask = new byte[PlayerConst.TaxiMaskSize]; + public static byte[] AllianceTaxiNodesMask = new byte[PlayerConst.TaxiMaskSize]; + public static Dictionary> TaxiPathSetBySource = new Dictionary>(); + public static Dictionary TaxiPathNodesByPath = new Dictionary(); + #endregion + + #region Helper Methods + public static float GetGameTableColumnForClass(dynamic row, Class class_) + { + switch (class_) + { + case Class.Warrior: + return row.Warrior; + case Class.Paladin: + return row.Paladin; + case Class.Hunter: + return row.Hunter; + case Class.Rogue: + return row.Rogue; + case Class.Priest: + return row.Priest; + case Class.Deathknight: + return row.DeathKnight; + case Class.Shaman: + return row.Shaman; + case Class.Mage: + return row.Mage; + case Class.Warlock: + return row.Warlock; + case Class.Monk: + return row.Monk; + case Class.Druid: + return row.Druid; + case Class.DemonHunter: + return row.DemonHunter; + default: + break; + } + + return 0.0f; + } + + public static float GetSpellScalingColumnForClass(GtSpellScalingRecord row, int class_) + { + switch (class_) + { + case (int)Class.Warrior: + return row.Warrior; + case (int)Class.Paladin: + return row.Paladin; + case (int)Class.Hunter: + return row.Hunter; + case (int)Class.Rogue: + return row.Rogue; + case (int)Class.Priest: + return row.Priest; + case (int)Class.Deathknight: + return row.DeathKnight; + case (int)Class.Shaman: + return row.Shaman; + case (int)Class.Mage: + return row.Mage; + case (int)Class.Warlock: + return row.Warlock; + case (int)Class.Monk: + return row.Monk; + case (int)Class.Druid: + return row.Druid; + case (int)Class.DemonHunter: + return row.DemonHunter; + case -1: + return row.Item; + case -2: + return row.Consumable; + case -3: + return row.Gem1; + case -4: + return row.Gem2; + case -5: + return row.Gem3; + case -6: + return row.Health; + default: + break; + } + + return 0.0f; + } + #endregion + } +} diff --git a/Game/DataStorage/ClientReader/CliDBReader.cs b/Game/DataStorage/ClientReader/CliDBReader.cs new file mode 100644 index 000000000..d98055c03 --- /dev/null +++ b/Game/DataStorage/ClientReader/CliDBReader.cs @@ -0,0 +1,759 @@ +/* + * Copyright (C) 2012-2017 CypherCore + * + * 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, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +using Framework.Collections; +using Framework.Constants; +using Framework.Database; +using Framework.Dynamic; +using Framework.GameMath; +using System; +using System.Collections.Generic; +using System.IO; +using System.Reflection; + +namespace Game.DataStorage +{ + public class DB6Reader + { + internal static DB6Storage Read(string fileName, DB6Meta meta, HotfixStatements preparedStatement, HotfixStatements preparedStatementLocale = 0) where T : new() + { + DB6Storage storage = new DB6Storage(); + + if (!File.Exists(CliDB.DataPath + fileName)) + { + Log.outError(LogFilter.ServerLoading, "File {0} not found.", fileName); + return storage; + } + + //First lets load field Info + var fields = typeof(T).GetFields(); + DBClientHelper[] fieldsInfo = new DBClientHelper[fields.Length]; + for (var i = 0; i < fields.Length; ++i) + fieldsInfo[i] = new DBClientHelper(fields[i]); + + using (var fileReader = new BinaryReader(new MemoryStream(File.ReadAllBytes(CliDB.DataPath + fileName)))) + { + _header = ReadHeader(fileReader); + var data = LoadData(fileReader); + + int commonDataFieldIndex = 0; + foreach (var pair in data) + { + var dataReader = new DB6BinaryReader(pair.Value); + var obj = new T(); + + int fieldIndex = 0; + for (var x = 0; x < _header.FieldCount; ++x) + { + int arrayLength = meta.ArraySizes[x]; + if (arrayLength > 1) + { + for (var z = 0; z < arrayLength; ++z) + { + var fieldInfo = fieldsInfo[fieldIndex++]; + if (fieldInfo.IsArray) + { + //Field is Array + Array array = (Array)fieldInfo.Getter(obj); + for (var y = 0; y < array.Length; ++y) + SetArrayValue(array, y, fieldInfo, dataReader, x); + + arrayLength -= array.Length; + } + else + { + //Only Data is Array + if (Type.GetTypeCode(fieldInfo.FieldType) == TypeCode.Object) + { + switch (fieldInfo.FieldType.Name) + { + case "Vector2": + fieldInfo.SetValue(obj, new Vector2(dataReader.ReadSingle(), dataReader.ReadSingle())); + arrayLength -= 2; + break; + case "Vector3": + fieldInfo.SetValue(obj, new Vector3(dataReader.ReadSingle(), dataReader.ReadSingle(), dataReader.ReadSingle())); + arrayLength -= 3; + break; + case "LocalizedString": + LocalizedString locString = new LocalizedString(); + locString[Global.WorldMgr.GetDefaultDbcLocale()] = GetString(dataReader, x); + fieldInfo.SetValue(obj, locString); + arrayLength -= 1; + break; + case "FlagArray128": + FlagArray128 flagArray128 = new FlagArray128(dataReader.GetUInt32(_header.GetFieldBytes(x)), dataReader.GetUInt32(_header.GetFieldBytes(x)), dataReader.GetUInt32(_header.GetFieldBytes(x)), dataReader.GetUInt32(_header.GetFieldBytes(x))); + fieldInfo.SetValue(obj, flagArray128); + arrayLength -= 4; + break; + default: + Log.outError(LogFilter.ServerLoading, "Unknown Array Type {0} in DBClient File", fieldInfo.FieldType.Name, nameof(T)); + break; + } + } + else + SetValue(obj, fieldInfo, dataReader, x); + } + } + } + else + { + var fieldInfo = fieldsInfo[fieldIndex++]; + if (fieldInfo.IsArray) + { + Array array = (Array)fieldInfo.Getter(obj); + for (var y = 0; y < array.Length; ++y) + SetArrayValue(array, y, fieldInfo, dataReader, x + y); + + x += array.Length - 1; + } + else + SetValue(obj, fieldInfo, dataReader, x); + } + } + + commonDataFieldIndex = fieldIndex; + + storage.Add((uint)pair.Key, obj); + } + + //Get DB field Index + uint index = 0; + for (uint i = 0; i < _header.FieldCount && i < _header.IndexField; ++i) + index += meta.ArraySizes[i]; + + ReadCommonData(commonDataFieldIndex, storage, meta, fieldsInfo); + storage.LoadData(index, fieldsInfo, preparedStatement, preparedStatementLocale); + } + + CliDB.LoadedFileCount++; + return storage; + } + + static void ReadCommonData(int fieldIndex, DB6Storage storage, DB6Meta meta, DBClientHelper[] helper) where T : new() + { + for (int x = (int)_header.FieldCount; x < _header.TotalFieldCount; ++x) + { + var fieldInfo = helper[fieldIndex++]; + int arrayLength = meta.ArraySizes[x]; + + foreach (var recordId in _commandData[x]) + { + var dataReader = new DB6BinaryReader(recordId.Value); + var record = storage.LookupByKey(recordId.Key); + + if (arrayLength > 1) + { + for (var z = 0; z < arrayLength; ++z) + { + if (fieldInfo.IsArray) + { + //Field is Array + Array array = (Array)fieldInfo.Getter(record); + for (var y = 0; y < array.Length; ++y) + SetArrayValue(array, y, fieldInfo, dataReader, x); + + arrayLength -= array.Length; + } + else + { + //Only Data is Array + if (Type.GetTypeCode(fieldInfo.FieldType) == TypeCode.Object) + { + Log.outError(LogFilter.Server, "We should not have custom classes in common data"); + switch (fieldInfo.FieldType.Name) + { + case "Vector2": + fieldInfo.SetValue(record, new Vector2(dataReader.ReadSingle(), dataReader.ReadSingle())); + arrayLength -= 2; + break; + case "Vector3": + fieldInfo.SetValue(record, new Vector3(dataReader.ReadSingle(), dataReader.ReadSingle(), dataReader.ReadSingle())); + arrayLength -= 3; + break; + case "LocalizedString": + LocalizedString locString = new LocalizedString(); + locString[Global.WorldMgr.GetDefaultDbcLocale()] = GetString(dataReader, x); + fieldInfo.SetValue(record, locString); + arrayLength -= 1; + break; + case "FlagArray128": + FlagArray128 flagArray128 = new FlagArray128(dataReader.ReadUInt32(), dataReader.ReadUInt32(), dataReader.ReadUInt32(), dataReader.ReadUInt32()); + fieldInfo.SetValue(record, flagArray128); + arrayLength -= 4; + break; + default: + Log.outError(LogFilter.ServerLoading, "Unknown Array Type {0} in DBClient File", fieldInfo.FieldType.Name, nameof(T)); + break; + } + } + else + SetValue(record, fieldInfo, dataReader, x); + } + } + } + else + { + if (fieldInfo.IsArray) + { + Array array = (Array)fieldInfo.Getter(record); + for (var y = 0; y < array.Length; ++y) + SetArrayValue(array, y, fieldInfo, dataReader, x + y); + + x += array.Length - 1; + } + else + SetValue(record, fieldInfo, dataReader, x); + } + } + } + } + + static void SetArrayValue(Array array, int arrayIndex, DBClientHelper helper, DB6BinaryReader reader, int field) + { + switch (Type.GetTypeCode(helper.RealType)) + { + case TypeCode.SByte: + helper.SetValue(array, reader.ReadSByte(), arrayIndex); + break; + case TypeCode.Byte: + helper.SetValue(array, reader.ReadByte(), arrayIndex); + break; + case TypeCode.Int16: + helper.SetValue(array, reader.ReadInt16(), arrayIndex); + break; + case TypeCode.UInt16: + helper.SetValue(array, reader.ReadUInt16(), arrayIndex); + break; + case TypeCode.Int32: + helper.SetValue(array, reader.GetInt32(_header.GetFieldBytes(field)), arrayIndex); + break; + case TypeCode.UInt32: + helper.SetValue(array, reader.GetUInt32(_header.GetFieldBytes(field)), arrayIndex); + break; + case TypeCode.Single: + helper.SetValue(array, reader.ReadSingle(), arrayIndex); + break; + case TypeCode.String: + helper.SetValue(array, GetString(reader, field), arrayIndex); + break; + default: + Log.outError(LogFilter.ServerLoading, "Wrong Array Type: {0}", helper.FieldType.Name); + break; + } + } + + static void SetValue(object obj, DBClientHelper helper, DB6BinaryReader reader, int field) + { + switch (Type.GetTypeCode(helper.RealType)) + { + case TypeCode.SByte: + helper.SetValue(obj, reader.ReadSByte()); + break; + case TypeCode.Byte: + helper.SetValue(obj, reader.ReadByte()); + break; + case TypeCode.Int16: + helper.SetValue(obj, reader.ReadInt16()); + break; + case TypeCode.UInt16: + helper.SetValue(obj, reader.ReadUInt16()); + break; + case TypeCode.Int32: + helper.SetValue(obj, reader.GetInt32(_header.GetFieldBytes(field))); + break; + case TypeCode.UInt32: + helper.SetValue(obj, reader.GetUInt32(_header.GetFieldBytes(field))); + break; + case TypeCode.Single: + helper.SetValue(obj, reader.ReadSingle()); + break; + case TypeCode.String: + string str = GetString(reader, field); + helper.SetValue(obj, str); + break; + case TypeCode.Object: + switch (helper.FieldType.Name) + { + case "Vector2": + helper.SetValue(obj, new Vector2(reader.ReadSingle(), reader.ReadSingle())); + break; + case "Vector3": + helper.SetValue(obj, new Vector3(reader.ReadSingle(), reader.ReadSingle(), reader.ReadSingle())); + break; + case "LocalizedString": + LocalizedString locString = new LocalizedString(); + locString[Global.WorldMgr.GetDefaultDbcLocale()] = GetString(reader, field); + helper.SetValue(obj, locString); + break; + default: + Log.outError(LogFilter.ServerLoading, "Wrong Array Type: {0}", helper.FieldType.Name); + break; + } + break; + default: + Log.outError(LogFilter.ServerLoading, "Wrong Array Type: {0}", helper.FieldType.Name); + break; + } + } + + static string GetString(DB6BinaryReader reader, int field) + { + if (_stringTable != null) + return _stringTable.LookupByKey(reader.GetUInt32(_header.GetFieldBytes(field))); + + return reader.ReadCString(); + } + + static DB6Header ReadHeader(BinaryReader reader) + { + DB6Header header = new DB6Header(); + header.Signature = reader.ReadStringFromChars(4); + header.RecordCount = reader.ReadUInt32(); + header.FieldCount = reader.ReadUInt32(); + header.RecordSize = reader.ReadUInt32(); + header.StringTableSize = reader.ReadUInt32(); // also offset for sparse table + + header.TableHash = reader.ReadUInt32(); + header.LayoutHash = reader.ReadUInt32(); // 21737: changed from build number to layoutHash + + header.MinId = reader.ReadInt32(); + header.MaxId = reader.ReadInt32(); + header.Locale = reader.ReadInt32(); + header.CopyTableSize = reader.ReadInt32(); + header.Flags = reader.ReadUInt16(); + header.IndexField = reader.ReadUInt16(); + + header.TotalFieldCount = reader.ReadUInt32(); + header.CommonDataSize = reader.ReadUInt32(); + + for (int i = 0; i < header.FieldCount; i++) + { + header.columnMeta.Add(new DB6Header.FieldEntry() { UnusedBits = reader.ReadInt16(), Offset = (short)(reader.ReadInt16() + (header.HasIndexTable() ? 4 : 0)) }); + } + + if (header.HasIndexTable()) + { + header.FieldCount++; + header.columnMeta.Insert(0, new DB6Header.FieldEntry()); + } + + return header; + } + + static Dictionary LoadData(BinaryReader reader) + { + Dictionary Data = new Dictionary(); + _stringTable = null; + + // headerSize + long recordsOffset = 56 + (_header.HasIndexTable() ? _header.FieldCount - 1 : _header.FieldCount) * 4; + long eof = reader.BaseStream.Length; + + long commonDataPos = eof - _header.CommonDataSize; + long copyTablePos = commonDataPos - _header.CopyTableSize; + long indexTablePos = copyTablePos - (_header.HasIndexTable() ? _header.RecordCount * 4 : 0); + long stringTablePos = indexTablePos - (_header.IsSparseTable() ? 0 : _header.StringTableSize); + + // Index table + int[] m_indexes = null; + + if (_header.HasIndexTable()) + { + reader.BaseStream.Position = indexTablePos; + + m_indexes = new int[_header.RecordCount]; + + for (int i = 0; i < _header.RecordCount; i++) + m_indexes[i] = reader.ReadInt32(); + } + + if (_header.IsSparseTable()) + { + // Records table + reader.BaseStream.Position = _header.StringTableSize; + + int ofsTableSize = _header.MaxId - _header.MinId + 1; + + for (int i = 0; i < ofsTableSize; i++) + { + int offset = reader.ReadInt32(); + int length = reader.ReadInt16(); + + if (offset == 0 || length == 0) + continue; + + int id = _header.MinId + i; + + long oldPos = reader.BaseStream.Position; + + reader.BaseStream.Position = offset; + + byte[] recordBytes = reader.ReadBytes(length); + + byte[] newRecordBytes = new byte[recordBytes.Length + 4]; + + Array.Copy(BitConverter.GetBytes(id), newRecordBytes, 4); + Array.Copy(recordBytes, 0, newRecordBytes, 4, recordBytes.Length); + + Data.Add(id, newRecordBytes); + + reader.BaseStream.Position = oldPos; + } + } + else + { + // Records table + reader.BaseStream.Position = recordsOffset; + + for (int i = 0; i < _header.RecordCount; i++) + { + reader.BaseStream.Position = recordsOffset + i * _header.RecordSize; + + byte[] recordBytes = reader.ReadBytes((int)_header.RecordSize); + + if (_header.HasIndexTable()) + { + byte[] newRecordBytes = new byte[_header.RecordSize + 4]; + + Array.Copy(BitConverter.GetBytes(m_indexes[i]), newRecordBytes, 4); + Array.Copy(recordBytes, 0, newRecordBytes, 4, recordBytes.Length); + + Data.Add(m_indexes[i], newRecordBytes); + } + else + { + int numBytes = (32 - _header.columnMeta[_header.IndexField].UnusedBits) >> 3; + int offset = _header.columnMeta[_header.IndexField].Offset; + int id = 0; + + for (int j = 0; j < numBytes; j++) + id |= (recordBytes[offset + j] << (j * 8)); + + Data.Add(id, recordBytes); + } + } + + // Strings table + reader.BaseStream.Position = stringTablePos; + + _stringTable = new Dictionary(); + while (reader.BaseStream.Position != stringTablePos + _header.StringTableSize) + { + int index = (int)(reader.BaseStream.Position - stringTablePos); + _stringTable[index] = reader.ReadCString(); + } + } + + // Copy index table + if (copyTablePos != reader.BaseStream.Length && _header.CopyTableSize != 0) + { + reader.BaseStream.Position = copyTablePos; + + while (reader.BaseStream.Position != reader.BaseStream.Length) + { + int id = reader.ReadInt32(); + int idcopy = reader.ReadInt32(); + + byte[] copyRow = Data[idcopy]; + byte[] newRow = new byte[copyRow.Length]; + + Array.Copy(copyRow, newRow, newRow.Length); + Array.Copy(BitConverter.GetBytes(id), newRow, 4); + + Data.Add(id, newRow); + } + } + + if (_header.CommonDataSize != 0) + { + reader.BaseStream.Position = commonDataPos; + + int fieldsCount = reader.ReadInt32(); + + _commandData = new Dictionary[fieldsCount]; + + for (int i = 0; i < fieldsCount; i++) + { + int count = reader.ReadInt32(); + byte type = reader.ReadByte(); + + _commandData[i] = new Dictionary(); + + for (int j = 0; j < count; j++) + { + int id = reader.ReadInt32(); + + switch (type) + { + case 1: // 2 bytes + _commandData[i].Add(id, reader.ReadBytes(2)); + break; + case 2: // 1 bytes + _commandData[i].Add(id, reader.ReadBytes(1)); + break; + case 3: // 4 bytes + case 4: + _commandData[i].Add(id, reader.ReadBytes(4)); + break; + default: + throw new Exception("Invalid data type " + type); + } + } + } + } + + return Data; + } + + static DB6Header _header; + static Dictionary _stringTable; + static Dictionary[] _commandData; + } + + public class GameTableReader + { + internal static GameTable Read(string fileName) where T : new() + { + GameTable storage = new GameTable(); + + if (!File.Exists(CliDB.DataPath + fileName)) + { + Log.outError(LogFilter.ServerLoading, "File {0} not found.", fileName); + return storage; + } + using (var reader = new StreamReader(CliDB.DataPath + fileName)) + { + string headers = reader.ReadLine(); + if (headers.IsEmpty()) + { + Log.outError(LogFilter.ServerLoading, "GameTable file {0} is empty.", fileName); + return storage; + } + + var columnDefs = new StringArray(headers, '\t'); + + List data = new List(); + data.Add(new T()); // row id 0, unused + + string line; + while (!(line = reader.ReadLine()).IsEmpty()) + { + var values = new StringArray(line, '\t'); + if (values.Length == 0) + break; + + var obj = new T(); + var fields = obj.GetType().GetFields(); + for (int fieldIndex = 0, valueIndex = 1; fieldIndex < fields.Length && valueIndex < values.Length; ++fieldIndex, ++valueIndex) + { + var field = fields[fieldIndex]; + if (field.FieldType.IsArray) + { + Array array = (Array)field.GetValue(obj); + for (var i = 0; i < array.Length; ++i) + array.SetValue(float.Parse(values[valueIndex++]), i); + } + else + fields[fieldIndex].SetValue(obj, float.Parse(values[valueIndex])); + } + + data.Add(obj); + } + + storage.SetData(data); + } + + CliDB.LoadedFileCount++; + return storage; + } + } + + public struct DBClientHelper + { + public DBClientHelper(FieldInfo fieldInfo) + { + IsArray = false; + FieldType = RealType = fieldInfo.FieldType; + + if (fieldInfo.FieldType.IsArray) + { + FieldType = RealType = fieldInfo.FieldType.GetElementType(); + IsArray = true; + } + + IsEnum = FieldType.IsEnum; + if (IsEnum) + { + IsEnum = FieldType.IsEnum; + RealType = FieldType.GetEnumUnderlyingType(); + } + + Setter = fieldInfo.CompileSetter(); + Getter = fieldInfo.CompileGetter(); + } + + public void SetValue(Array array, object value, int arrayIndex) + { + if (!IsEnum) + array.SetValue(Convert.ChangeType(value, FieldType), arrayIndex % array.Length); + else + array.SetValue(Enum.ToObject(FieldType, value), arrayIndex % array.Length); + } + + public void SetValue(object obj, object value) + { + if (!IsEnum) + Setter(obj, Convert.ChangeType(value, FieldType)); + else + Setter(obj, Enum.ToObject(FieldType, value)); + } + + public Type FieldType; + public Type RealType; + public bool IsArray; + bool IsEnum; + Action Setter; + public Func Getter; + } + + class DB6Header + { + public bool IsValidDB6File() + { + return Signature == "WDB6"; + } + + public bool IsSparseTable() + { + return Convert.ToBoolean(Flags & 0x1); + } + + public bool HasIndexTable() + { + return Convert.ToBoolean(Flags & 0x4); + } + + public int GetFieldBytes(int field) + { + if (columnMeta.Count <= field) + return 4; + + return 4 - columnMeta[field].UnusedBits / 8; + } + + public string Signature; + public uint RecordCount; + public uint FieldCount; + public uint RecordSize; + public uint StringTableSize; + + public uint TableHash; + public uint LayoutHash; + public int MinId; + public int MaxId; + public int Locale; + public int CopyTableSize; + public uint Flags; + public int IndexField; + public uint TotalFieldCount; + public uint CommonDataSize; + + public List columnMeta = new List(); + + public struct FieldEntry + { + public short UnusedBits; + public short Offset; + } + } + + class DB6BinaryReader : BinaryReader + { + public DB6BinaryReader(byte[] data) : base(new MemoryStream(data)) { } + + public int GetInt32(int fieldBytes) + { + switch (fieldBytes) + { + case 1: + return ReadSByte(); + case 2: + return ReadInt16(); + case 3: + byte[] bytes = ReadBytes(fieldBytes); + return bytes[0] | (bytes[1] << 8) | (bytes[2] << 16); + default: + return ReadInt32(); + } + } + + public uint GetUInt32(int fieldBytes) + { + switch (fieldBytes) + { + case 1: + return ReadByte(); + case 2: + return ReadUInt16(); + case 3: + byte[] bytes = ReadBytes(fieldBytes); + return bytes[0] | ((uint)bytes[1] << 8) | ((uint)bytes[2] << 16); + default: + return ReadUInt32(); + } + } + + public float GetSingle(int fieldBytes) + { + switch (fieldBytes) + { + case 1: + return ReadByte(); + case 2: + return ReadUInt16(); + case 3: + byte[] bytes = ReadBytes(fieldBytes); + return bytes[0] | ((uint)bytes[1] << 8) | ((uint)bytes[2] << 16); + default: + return ReadSingle(); + } + } + } + + public class LocalizedString + { + public bool HasString(LocaleConstant locale = SharedConst.DefaultLocale) + { + return !string.IsNullOrEmpty(stringStorage[(int)locale]); + } + + public string this[LocaleConstant locale] + { + get + { + return stringStorage[(int)locale] ?? ""; + } + set + { + stringStorage[(int)locale] = value; + } + } + + StringArray stringStorage = new StringArray((int)LocaleConstant.Total); + } +} diff --git a/Game/DataStorage/ClientReader/DB6Meta.cs b/Game/DataStorage/ClientReader/DB6Meta.cs new file mode 100644 index 000000000..ee4c22e44 --- /dev/null +++ b/Game/DataStorage/ClientReader/DB6Meta.cs @@ -0,0 +1,630 @@ +/* + * Copyright (C) 2012-2017 CypherCore + * + * 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, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +namespace Game.DataStorage +{ + public struct DB6Meta + { + public DB6Meta(int indexField, byte[] arraySizes) + { + IndexField = indexField; + ArraySizes = arraySizes; + } + + int IndexField; + public byte[] ArraySizes; + } + + public struct DB6Metas + { + public static DB6Meta AchievementMeta = new DB6Meta(13, new byte[] { 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1 }); + public static DB6Meta AchievementCategoryMeta = new DB6Meta(3, new byte[] { 1, 1, 1, 1, 1 }); + public static DB6Meta AdventureJournalMeta = new DB6Meta(-1, new byte[] { 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2, 1, 1, 1, 1, 1, 1, 1, 2, 1, 1, 1 }); + public static DB6Meta AdventureMapPOIMeta = new DB6Meta(-1, new byte[] { 1, 2, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1 }); + public static DB6Meta AnimKitMeta = new DB6Meta(-1, new byte[] { 1, 1, 1, 1 }); + public static DB6Meta AnimKitBoneSetMeta = new DB6Meta(-1, new byte[] { 1, 1, 1, 1, 1, 1 }); + public static DB6Meta AnimKitBoneSetAliasMeta = new DB6Meta(-1, new byte[] { 1, 1, 1 }); + public static DB6Meta AnimKitConfigMeta = new DB6Meta(-1, new byte[] { 1, 1 }); + public static DB6Meta AnimKitConfigBoneSetMeta = new DB6Meta(-1, new byte[] { 1, 1, 1, 1 }); + public static DB6Meta AnimKitPriorityMeta = new DB6Meta(-1, new byte[] { 1, 1 }); + public static DB6Meta AnimKitReplacementMeta = new DB6Meta(4, new byte[] { 1, 1, 1, 1 }); + public static DB6Meta AnimKitSegmentMeta = new DB6Meta(-1, new byte[] { 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1 }); + public static DB6Meta AnimReplacementMeta = new DB6Meta(4, new byte[] { 1, 1, 1, 1, 1 }); + public static DB6Meta AnimReplacementSetMeta = new DB6Meta(-1, new byte[] { 1, 1 }); + public static DB6Meta AnimationDataMeta = new DB6Meta(0, new byte[] { 1, 1, 1, 1, 1, 1 }); + public static DB6Meta AreaGroupMemberMeta = new DB6Meta(-1, new byte[] { 1, 1, 1 }); + public static DB6Meta AreaPOIMeta = new DB6Meta(-1, new byte[] { 1, 1, 2, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1 }); + public static DB6Meta AreaPOIStateMeta = new DB6Meta(-1, new byte[] { 1, 1, 1, 1, 1, 1 }); + public static DB6Meta AreaTableMeta = new DB6Meta(-1, new byte[] { 1, 2, 1, 1, 1, 1, 1, 1, 1, 1, 1, 4, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1 }); + public static DB6Meta AreaTriggerMeta = new DB6Meta(14, new byte[] { 3, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1 }); + public static DB6Meta AreaTriggerActionSetMeta = new DB6Meta(-1, new byte[] { 1, 1 }); + public static DB6Meta AreaTriggerBoxMeta = new DB6Meta(-1, new byte[] { 1, 3 }); + public static DB6Meta AreaTriggerCylinderMeta = new DB6Meta(-1, new byte[] { 1, 1, 1, 1 }); + public static DB6Meta AreaTriggerSphereMeta = new DB6Meta(-1, new byte[] { 1, 1 }); + public static DB6Meta ArmorLocationMeta = new DB6Meta(-1, new byte[] { 1, 1, 1, 1, 1, 1 }); + public static DB6Meta ArtifactMeta = new DB6Meta(-1, new byte[] { 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1 }); + public static DB6Meta ArtifactAppearanceMeta = new DB6Meta(11, new byte[] { 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1 }); + public static DB6Meta ArtifactAppearanceSetMeta = new DB6Meta(8, new byte[] { 1, 1, 1, 1, 1, 1, 1, 1, 1 }); + public static DB6Meta ArtifactCategoryMeta = new DB6Meta(-1, new byte[] { 1, 1, 1 }); + public static DB6Meta ArtifactPowerMeta = new DB6Meta(5, new byte[] { 2, 1, 1, 1, 1, 1, 1 }); + public static DB6Meta ArtifactPowerLinkMeta = new DB6Meta(-1, new byte[] { 1, 1, 1 }); + public static DB6Meta ArtifactPowerPickerMeta = new DB6Meta(-1, new byte[] { 1, 1 }); + public static DB6Meta ArtifactPowerRankMeta = new DB6Meta(-1, new byte[] { 1, 1, 1, 1, 1, 1 }); + public static DB6Meta ArtifactQuestXPMeta = new DB6Meta(-1, new byte[] { 1, 10 }); + public static DB6Meta ArtifactTierMeta = new DB6Meta(-1, new byte[] { 1, 1, 1, 1, 1, 1 }); + public static DB6Meta ArtifactUnlockMeta = new DB6Meta(-1, new byte[] { 1, 1, 1, 1, 1, 1 }); + public static DB6Meta AuctionHouseMeta = new DB6Meta(-1, new byte[] { 1, 1, 1, 1, 1 }); + public static DB6Meta BankBagSlotPricesMeta = new DB6Meta(-1, new byte[] { 1, 1 }); + public static DB6Meta BannedAddOnsMeta = new DB6Meta(-1, new byte[] { 1, 1, 1, 1 }); + public static DB6Meta BarberShopStyleMeta = new DB6Meta(7, new byte[] { 1, 1, 1, 1, 1, 1, 1, 1 }); + public static DB6Meta BattlePetAbilityMeta = new DB6Meta(-1, new byte[] { 1, 1, 1, 1, 1, 1, 1, 1 }); + public static DB6Meta BattlePetAbilityEffectMeta = new DB6Meta(6, new byte[] { 1, 1, 1, 1, 6, 1, 1 }); + public static DB6Meta BattlePetAbilityStateMeta = new DB6Meta(-1, new byte[] { 1, 1, 1, 1 }); + public static DB6Meta BattlePetAbilityTurnMeta = new DB6Meta(5, new byte[] { 1, 1, 1, 1, 1, 1 }); + public static DB6Meta BattlePetBreedQualityMeta = new DB6Meta(-1, new byte[] { 1, 1, 1 }); + public static DB6Meta BattlePetBreedStateMeta = new DB6Meta(-1, new byte[] { 1, 1, 1, 1 }); + public static DB6Meta BattlePetEffectPropertiesMeta = new DB6Meta(-1, new byte[] { 1, 6, 1, 6 }); + public static DB6Meta BattlePetNPCTeamMemberMeta = new DB6Meta(-1, new byte[] { 1, 1 }); + public static DB6Meta BattlePetSpeciesMeta = new DB6Meta(8, new byte[] { 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1 }); + public static DB6Meta BattlePetSpeciesStateMeta = new DB6Meta(-1, new byte[] { 1, 1, 1, 1 }); + public static DB6Meta BattlePetSpeciesXAbilityMeta = new DB6Meta(-1, new byte[] { 1, 1, 1, 1, 1 }); + public static DB6Meta BattlePetStateMeta = new DB6Meta(-1, new byte[] { 1, 1, 1, 1 }); + public static DB6Meta BattlePetVisualMeta = new DB6Meta(-1, new byte[] { 1, 1, 1, 1, 1, 1, 1, 1 }); + public static DB6Meta BattlemasterListMeta = new DB6Meta(-1, new byte[] { 1, 1, 1, 1, 1, 1, 16, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1 }); + public static DB6Meta BeamEffectMeta = new DB6Meta(-1, new byte[] { 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1 }); + public static DB6Meta BoneWindModifierModelMeta = new DB6Meta(-1, new byte[] { 1, 1, 1 }); + public static DB6Meta BoneWindModifiersMeta = new DB6Meta(-1, new byte[] { 1, 3, 1 }); + public static DB6Meta BountyMeta = new DB6Meta(-1, new byte[] { 1, 1, 1, 1, 1 }); + public static DB6Meta BountySetMeta = new DB6Meta(-1, new byte[] { 1, 1, 1 }); + public static DB6Meta BroadcastTextMeta = new DB6Meta(-1, new byte[] { 1, 1, 1, 3, 3, 1, 1, 1, 2, 1 }); + public static DB6Meta CameraEffectMeta = new DB6Meta(-1, new byte[] { 1, 1 }); + public static DB6Meta CameraEffectEntryMeta = new DB6Meta(-1, new byte[] { 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1 }); + public static DB6Meta CameraModeMeta = new DB6Meta(-1, new byte[] { 1, 3, 3, 1, 1, 1, 1, 1, 1, 1, 1, 1 }); + public static DB6Meta CameraShakesMeta = new DB6Meta(-1, new byte[] { 1, 1, 1, 1, 1, 1, 1, 1, 1, 1 }); + public static DB6Meta CastableRaidBuffsMeta = new DB6Meta(-1, new byte[] { 1, 1, 1 }); + public static DB6Meta CelestialBodyMeta = new DB6Meta(14, new byte[] { 1, 1, 2, 1, 1, 2, 2, 2, 1, 2, 1, 3, 1, 1, 1 }); + public static DB6Meta Cfg_CategoriesMeta = new DB6Meta(-1, new byte[] { 1, 1, 1, 1, 1, 1 }); + public static DB6Meta Cfg_ConfigsMeta = new DB6Meta(-1, new byte[] { 1, 1, 1, 1, 1 }); + public static DB6Meta Cfg_RegionsMeta = new DB6Meta(-1, new byte[] { 1, 1, 1, 1, 1, 1 }); + public static DB6Meta CharBaseInfoMeta = new DB6Meta(-1, new byte[] { 1, 1, 1 }); + public static DB6Meta CharBaseSectionMeta = new DB6Meta(-1, new byte[] { 1, 1, 1, 1 }); + public static DB6Meta CharComponentTextureLayoutsMeta = new DB6Meta(-1, new byte[] { 1, 1, 1 }); + public static DB6Meta CharComponentTextureSectionsMeta = new DB6Meta(-1, new byte[] { 1, 1, 1, 1, 1, 1, 1, 1 }); + public static DB6Meta CharHairGeosetsMeta = new DB6Meta(-1, new byte[] { 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1 }); + public static DB6Meta CharSectionsMeta = new DB6Meta(-1, new byte[] { 1, 3, 1, 1, 1, 1, 1, 1 }); + public static DB6Meta CharShipmentMeta = new DB6Meta(-1, new byte[] { 1, 1, 1, 1, 1, 1, 1, 1, 1, 1 }); + public static DB6Meta CharShipmentContainerMeta = new DB6Meta(-1, new byte[] { 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1 }); + public static DB6Meta CharStartOutfitMeta = new DB6Meta(-1, new byte[] { 1, 24, 1, 1, 1, 1, 1, 1 }); + public static DB6Meta CharTitlesMeta = new DB6Meta(-1, new byte[] { 1, 1, 1, 1, 1 }); + public static DB6Meta CharacterFaceBoneSetMeta = new DB6Meta(-1, new byte[] { 1, 1, 1, 1, 1 }); + public static DB6Meta CharacterFacialHairStylesMeta = new DB6Meta(-1, new byte[] { 1, 5, 1, 1, 1 }); + public static DB6Meta CharacterLoadoutMeta = new DB6Meta(-1, new byte[] { 1, 1, 1, 1 }); + public static DB6Meta CharacterLoadoutItemMeta = new DB6Meta(-1, new byte[] { 1, 1, 1 }); + public static DB6Meta ChatChannelsMeta = new DB6Meta(-1, new byte[] { 1, 1, 1, 1, 1 }); + public static DB6Meta ChatProfanityMeta = new DB6Meta(-1, new byte[] { 1, 1, 1 }); + public static DB6Meta ChrClassRaceSexMeta = new DB6Meta(-1, new byte[] { 1, 1, 1, 1, 1, 1, 1 }); + public static DB6Meta ChrClassTitleMeta = new DB6Meta(-1, new byte[] { 1, 1, 1, 1 }); + public static DB6Meta ChrClassUIDisplayMeta = new DB6Meta(-1, new byte[] { 1, 1, 1, 1 }); + public static DB6Meta ChrClassVillainMeta = new DB6Meta(-1, new byte[] { 1, 1, 1, 1 }); + public static DB6Meta ChrClassesMeta = new DB6Meta(18, new byte[] { 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1 }); + public static DB6Meta ChrClassesXPowerTypesMeta = new DB6Meta(-1, new byte[] { 1, 1, 1 }); + public static DB6Meta ChrRacesMeta = new DB6Meta(-1, new byte[] { 1, 1, 1, 1, 1, 1, 1, 2, 1, 1, 1, 3, 3, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 3 }); + public static DB6Meta ChrSpecializationMeta = new DB6Meta(9, new byte[] { 2, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1 }); + public static DB6Meta ChrUpgradeBucketMeta = new DB6Meta(2, new byte[] { 1, 1, 1 }); + public static DB6Meta ChrUpgradeBucketSpellMeta = new DB6Meta(-1, new byte[] { 1, 1, 1 }); + public static DB6Meta ChrUpgradeTierMeta = new DB6Meta(3, new byte[] { 1, 1, 1, 1 }); + public static DB6Meta CinematicCameraMeta = new DB6Meta(-1, new byte[] { 1, 1, 3, 1, 1 }); + public static DB6Meta CinematicSequencesMeta = new DB6Meta(-1, new byte[] { 1, 1, 8 }); + public static DB6Meta CloakDampeningMeta = new DB6Meta(-1, new byte[] { 1, 5, 5, 2, 2, 1, 1, 1 }); + public static DB6Meta CombatConditionMeta = new DB6Meta(-1, new byte[] { 1, 1, 1, 1, 2, 2, 2, 2, 1, 2, 2, 1 }); + public static DB6Meta CommentatorStartLocationMeta = new DB6Meta(-1, new byte[] { 1, 3, 1 }); + public static DB6Meta CommentatorTrackedCooldownMeta = new DB6Meta(-1, new byte[] { 1, 1, 1, 1, 1 }); + public static DB6Meta ComponentModelFileDataMeta = new DB6Meta(-1, new byte[] { 1, 1, 1, 1, 1 }); + public static DB6Meta ComponentTextureFileDataMeta = new DB6Meta(-1, new byte[] { 1, 1, 1, 1 }); + public static DB6Meta ContributionMeta = new DB6Meta(0, new byte[] { 1, 1, 1, 1, 4, 1 }); + public static DB6Meta ConversationLineMeta = new DB6Meta(-1, new byte[] { 1, 1, 1, 1, 1, 1, 1, 1, 1 }); + public static DB6Meta CreatureMeta = new DB6Meta(-1, new byte[] { 1, 3, 1, 4, 4, 1, 1, 1, 1, 1, 1, 1, 1 }); + public static DB6Meta CreatureDifficultyMeta = new DB6Meta(-1, new byte[] { 1, 1, 7, 1, 1, 1, 1 }); + public static DB6Meta CreatureDispXUiCameraMeta = new DB6Meta(-1, new byte[] { 1, 1, 1 }); + public static DB6Meta CreatureDisplayInfoMeta = new DB6Meta(0, new byte[] { 1, 1, 1, 1, 1, 1, 1, 1, 3, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1 }); + public static DB6Meta CreatureDisplayInfoCondMeta = new DB6Meta(-1, new byte[] { 1, 1, 1, 2, 2, 2, 1, 1, 1, 1, 1, 1, 1, 1, 1, 3 }); + public static DB6Meta CreatureDisplayInfoEvtMeta = new DB6Meta(-1, new byte[] { 1, 1, 1, 1 }); + public static DB6Meta CreatureDisplayInfoExtraMeta = new DB6Meta(-1, new byte[] { 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 3, 1 }); + public static DB6Meta CreatureDisplayInfoTrnMeta = new DB6Meta(-1, new byte[] { 1, 1, 1, 1, 1 }); + public static DB6Meta CreatureFamilyMeta = new DB6Meta(-1, new byte[] { 1, 1, 1, 1, 1, 2, 1, 1, 1, 1 }); + public static DB6Meta CreatureImmunitiesMeta = new DB6Meta(-1, new byte[] { 1, 2, 1, 1, 1, 1, 1, 1, 8, 16 }); + public static DB6Meta CreatureModelDataMeta = new DB6Meta(-1, new byte[] { 1, 1, 1, 1, 1, 1, 1, 1, 6, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1 }); + public static DB6Meta CreatureMovementInfoMeta = new DB6Meta(-1, new byte[] { 1, 1 }); + public static DB6Meta CreatureSoundDataMeta = new DB6Meta(-1, new byte[] { 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 5, 4, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1 }); + public static DB6Meta CreatureTypeMeta = new DB6Meta(-1, new byte[] { 1, 1, 1 }); + public static DB6Meta CreatureXContributionMeta = new DB6Meta(0, new byte[] { 1, 1, 1 }); + public static DB6Meta CriteriaMeta = new DB6Meta(-1, new byte[] { 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1 }); + public static DB6Meta CriteriaTreeMeta = new DB6Meta(-1, new byte[] { 1, 1, 1, 1, 1, 1, 1, 1 }); + public static DB6Meta CriteriaTreeXEffectMeta = new DB6Meta(-1, new byte[] { 1, 1, 1 }); + public static DB6Meta CurrencyCategoryMeta = new DB6Meta(-1, new byte[] { 1, 1, 1, 1 }); + public static DB6Meta CurrencyTypesMeta = new DB6Meta(-1, new byte[] { 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1 }); + public static DB6Meta CurveMeta = new DB6Meta(-1, new byte[] { 1, 1, 1 }); + public static DB6Meta CurvePointMeta = new DB6Meta(-1, new byte[] { 1, 2, 1, 1 }); + public static DB6Meta DeathThudLookupsMeta = new DB6Meta(-1, new byte[] { 1, 1, 1, 1, 1 }); + public static DB6Meta DecalPropertiesMeta = new DB6Meta(0, new byte[] { 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1 }); + public static DB6Meta DeclinedWordMeta = new DB6Meta(1, new byte[] { 1, 1 }); + public static DB6Meta DeclinedWordCasesMeta = new DB6Meta(-1, new byte[] { 1, 1, 1, 1 }); + public static DB6Meta DestructibleModelDataMeta = new DB6Meta(-1, new byte[] { 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1 }); + public static DB6Meta DeviceBlacklistMeta = new DB6Meta(-1, new byte[] { 1, 1, 1 }); + public static DB6Meta DeviceDefaultSettingsMeta = new DB6Meta(-1, new byte[] { 1, 1, 1, 1 }); + public static DB6Meta DifficultyMeta = new DB6Meta(-1, new byte[] { 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1 }); + public static DB6Meta DissolveEffectMeta = new DB6Meta(-1, new byte[] { 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1 }); + public static DB6Meta DriverBlacklistMeta = new DB6Meta(-1, new byte[] { 1, 1, 1, 1, 1, 1, 1, 1 }); + public static DB6Meta DungeonEncounterMeta = new DB6Meta(6, new byte[] { 1, 1, 1, 1, 1, 1, 1, 1, 1 }); + public static DB6Meta DungeonMapMeta = new DB6Meta(7, new byte[] { 2, 2, 1, 1, 1, 1, 1 }); + public static DB6Meta DungeonMapChunkMeta = new DB6Meta(-1, new byte[] { 1, 1, 1, 1, 1, 1 }); + public static DB6Meta DurabilityCostsMeta = new DB6Meta(-1, new byte[] { 1, 21, 8 }); + public static DB6Meta DurabilityQualityMeta = new DB6Meta(-1, new byte[] { 1, 1 }); + public static DB6Meta EdgeGlowEffectMeta = new DB6Meta(-1, new byte[] { 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1 }); + public static DB6Meta EmotesMeta = new DB6Meta(-1, new byte[] { 1, 1, 1, 1, 1, 1, 1, 1, 1, 1 }); + public static DB6Meta EmotesTextMeta = new DB6Meta(-1, new byte[] { 1, 1, 1 }); + public static DB6Meta EmotesTextDataMeta = new DB6Meta(-1, new byte[] { 1, 1, 1, 1 }); + public static DB6Meta EmotesTextSoundMeta = new DB6Meta(-1, new byte[] { 1, 1, 1, 1, 1, 1 }); + public static DB6Meta EnvironmentalDamageMeta = new DB6Meta(-1, new byte[] { 1, 1, 1 }); + public static DB6Meta ExhaustionMeta = new DB6Meta(7, new byte[] { 1, 1, 1, 1, 1, 1, 1, 1 }); + public static DB6Meta FactionMeta = new DB6Meta(0, new byte[] { 1, 4, 4, 2, 1, 1, 4, 1, 4, 4, 1, 1, 2, 1, 1, 1 }); + public static DB6Meta FactionGroupMeta = new DB6Meta(-1, new byte[] { 1, 1, 1, 1, 1, 1 }); + public static DB6Meta FactionTemplateMeta = new DB6Meta(-1, new byte[] { 1, 1, 1, 4, 4, 1, 1, 1 }); + public static DB6Meta FootprintTexturesMeta = new DB6Meta(-1, new byte[] { 1, 1, 1, 1 }); + public static DB6Meta FootstepTerrainLookupMeta = new DB6Meta(-1, new byte[] { 1, 1, 1, 1, 1 }); + public static DB6Meta FriendshipRepReactionMeta = new DB6Meta(-1, new byte[] { 1, 1, 1, 1 }); + public static DB6Meta FriendshipReputationMeta = new DB6Meta(3, new byte[] { 1, 1, 1, 1 }); + public static DB6Meta FullScreenEffectMeta = new DB6Meta(-1, new byte[] { 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1 }); + public static DB6Meta GMSurveyAnswersMeta = new DB6Meta(-1, new byte[] { 1, 1, 1, 1 }); + public static DB6Meta GMSurveyCurrentSurveyMeta = new DB6Meta(-1, new byte[] { 1, 1 }); + public static DB6Meta GMSurveyQuestionsMeta = new DB6Meta(-1, new byte[] { 1, 1 }); + public static DB6Meta GMSurveySurveysMeta = new DB6Meta(-1, new byte[] { 1, 15 }); + public static DB6Meta GameObjectArtKitMeta = new DB6Meta(-1, new byte[] { 1, 3, 4 }); + public static DB6Meta GameObjectDiffAnimMapMeta = new DB6Meta(-1, new byte[] { 1, 1, 1, 1, 1 }); + public static DB6Meta GameObjectDisplayInfoMeta = new DB6Meta(-1, new byte[] { 1, 1, 6, 1, 1, 1 }); + public static DB6Meta GameObjectDisplayInfoXSoundKitMeta = new DB6Meta(-1, new byte[] { 1, 1, 1, 1 }); + public static DB6Meta GameObjectsMeta = new DB6Meta(11, new byte[] { 3, 4, 1, 8, 1, 1, 1, 1, 1, 1, 1, 1 }); + public static DB6Meta GameTipsMeta = new DB6Meta(-1, new byte[] { 1, 1, 1, 1, 1 }); + public static DB6Meta GarrAbilityMeta = new DB6Meta(7, new byte[] { 1, 1, 1, 1, 1, 1, 1, 1 }); + public static DB6Meta GarrAbilityCategoryMeta = new DB6Meta(-1, new byte[] { 1, 1 }); + public static DB6Meta GarrAbilityEffectMeta = new DB6Meta(11, new byte[] { 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1 }); + public static DB6Meta GarrBuildingMeta = new DB6Meta(-1, new byte[] { 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1 }); + public static DB6Meta GarrBuildingDoodadSetMeta = new DB6Meta(-1, new byte[] { 1, 1, 1, 1, 1, 1 }); + public static DB6Meta GarrBuildingPlotInstMeta = new DB6Meta(4, new byte[] { 2, 1, 1, 1, 1 }); + public static DB6Meta GarrClassSpecMeta = new DB6Meta(7, new byte[] { 1, 1, 1, 1, 1, 1, 1, 1 }); + public static DB6Meta GarrClassSpecPlayerCondMeta = new DB6Meta(-1, new byte[] { 1, 1, 1, 1, 1, 1, 1 }); + public static DB6Meta GarrEncounterMeta = new DB6Meta(5, new byte[] { 1, 1, 1, 1, 1, 1, 1 }); + public static DB6Meta GarrEncounterSetXEncounterMeta = new DB6Meta(-1, new byte[] { 1, 1, 1 }); + public static DB6Meta GarrEncounterXMechanicMeta = new DB6Meta(-1, new byte[] { 1, 1, 1, 1 }); + public static DB6Meta GarrFollItemSetMemberMeta = new DB6Meta(-1, new byte[] { 1, 1, 1, 1, 1 }); + public static DB6Meta GarrFollSupportSpellMeta = new DB6Meta(-1, new byte[] { 1, 1, 1, 1, 1 }); + public static DB6Meta GarrFollowerMeta = new DB6Meta(31, new byte[] { 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1 }); + public static DB6Meta GarrFollowerLevelXPMeta = new DB6Meta(-1, new byte[] { 1, 1, 1, 1, 1 }); + public static DB6Meta GarrFollowerQualityMeta = new DB6Meta(-1, new byte[] { 1, 1, 1, 1, 1, 1, 1, 1 }); + public static DB6Meta GarrFollowerSetXFollowerMeta = new DB6Meta(-1, new byte[] { 1, 1, 1 }); + public static DB6Meta GarrFollowerTypeMeta = new DB6Meta(-1, new byte[] { 1, 1, 1, 1, 1, 1, 1, 1 }); + public static DB6Meta GarrFollowerUICreatureMeta = new DB6Meta(-1, new byte[] { 1, 1, 1, 1, 1, 1, 1 }); + public static DB6Meta GarrFollowerXAbilityMeta = new DB6Meta(-1, new byte[] { 1, 1, 1, 1 }); + public static DB6Meta GarrItemLevelUpgradeDataMeta = new DB6Meta(0, new byte[] { 1, 1, 1, 1, 1 }); + public static DB6Meta GarrMechanicMeta = new DB6Meta(-1, new byte[] { 1, 1, 1, 1 }); + public static DB6Meta GarrMechanicSetXMechanicMeta = new DB6Meta(1, new byte[] { 1, 1, 1 }); + public static DB6Meta GarrMechanicTypeMeta = new DB6Meta(4, new byte[] { 1, 1, 1, 1, 1 }); + public static DB6Meta GarrMissionMeta = new DB6Meta(19, new byte[] { 1, 1, 1, 1, 1, 1, 1, 2, 2, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1 }); + public static DB6Meta GarrMissionTextureMeta = new DB6Meta(-1, new byte[] { 1, 2, 1 }); + public static DB6Meta GarrMissionTypeMeta = new DB6Meta(-1, new byte[] { 1, 1, 1, 1 }); + public static DB6Meta GarrMissionXEncounterMeta = new DB6Meta(1, new byte[] { 1, 1, 1, 1, 1 }); + public static DB6Meta GarrMissionXFollowerMeta = new DB6Meta(-1, new byte[] { 1, 1, 1, 1 }); + public static DB6Meta GarrMssnBonusAbilityMeta = new DB6Meta(-1, new byte[] { 1, 1, 1, 1, 1, 1 }); + public static DB6Meta GarrPlotMeta = new DB6Meta(-1, new byte[] { 1, 1, 1, 1, 1, 1, 1, 2 }); + public static DB6Meta GarrPlotBuildingMeta = new DB6Meta(-1, new byte[] { 1, 1, 1 }); + public static DB6Meta GarrPlotInstanceMeta = new DB6Meta(-1, new byte[] { 1, 1, 1 }); + public static DB6Meta GarrPlotUICategoryMeta = new DB6Meta(-1, new byte[] { 1, 1, 1 }); + public static DB6Meta GarrSiteLevelMeta = new DB6Meta(-1, new byte[] { 1, 2, 1, 1, 1, 1, 1, 1, 1, 1 }); + public static DB6Meta GarrSiteLevelPlotInstMeta = new DB6Meta(-1, new byte[] { 1, 2, 1, 1, 1 }); + public static DB6Meta GarrSpecializationMeta = new DB6Meta(-1, new byte[] { 1, 1, 2, 1, 1, 1, 1, 1 }); + public static DB6Meta GarrStringMeta = new DB6Meta(-1, new byte[] { 1, 1 }); + public static DB6Meta GarrTalentMeta = new DB6Meta(7, new byte[] { 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1 }); + public static DB6Meta GarrTalentTreeMeta = new DB6Meta(-1, new byte[] { 1, 1, 1, 1, 1, 1 }); + public static DB6Meta GarrTypeMeta = new DB6Meta(-1, new byte[] { 1, 1, 1, 1, 1, 1 }); + public static DB6Meta GarrUiAnimClassInfoMeta = new DB6Meta(-1, new byte[] { 1, 1, 1, 1, 1 }); + public static DB6Meta GarrUiAnimRaceInfoMeta = new DB6Meta(-1, new byte[] { 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1 }); + public static DB6Meta GemPropertiesMeta = new DB6Meta(-1, new byte[] { 1, 1, 1, 1 }); + public static DB6Meta GlobalStringsMeta = new DB6Meta(-1, new byte[] { 1, 1, 1, 1 }); + public static DB6Meta GlyphBindableSpellMeta = new DB6Meta(-1, new byte[] { 1, 1, 1 }); + public static DB6Meta GlyphExclusiveCategoryMeta = new DB6Meta(-1, new byte[] { 1, 1 }); + public static DB6Meta GlyphPropertiesMeta = new DB6Meta(-1, new byte[] { 1, 1, 1, 1, 1 }); + public static DB6Meta GlyphRequiredSpecMeta = new DB6Meta(-1, new byte[] { 1, 1, 1 }); + public static DB6Meta GroundEffectDoodadMeta = new DB6Meta(-1, new byte[] { 1, 1, 1, 1, 1 }); + public static DB6Meta GroundEffectTextureMeta = new DB6Meta(-1, new byte[] { 1, 4, 4, 1, 1 }); + public static DB6Meta GroupFinderActivityMeta = new DB6Meta(-1, new byte[] { 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1 }); + public static DB6Meta GroupFinderActivityGrpMeta = new DB6Meta(-1, new byte[] { 1, 1, 1 }); + public static DB6Meta GroupFinderCategoryMeta = new DB6Meta(-1, new byte[] { 1, 1, 1, 1 }); + public static DB6Meta GuildColorBackgroundMeta = new DB6Meta(-1, new byte[] { 1, 1, 1, 1 }); + public static DB6Meta GuildColorBorderMeta = new DB6Meta(-1, new byte[] { 1, 1, 1, 1 }); + public static DB6Meta GuildColorEmblemMeta = new DB6Meta(-1, new byte[] { 1, 1, 1, 1 }); + public static DB6Meta GuildPerkSpellsMeta = new DB6Meta(-1, new byte[] { 1, 1 }); + public static DB6Meta HeirloomMeta = new DB6Meta(9, new byte[] { 1, 1, 1, 1, 1, 3, 3, 1, 1, 1 }); + public static DB6Meta HelmetAnimScalingMeta = new DB6Meta(-1, new byte[] { 1, 1, 1, 1 }); + public static DB6Meta HelmetGeosetVisDataMeta = new DB6Meta(-1, new byte[] { 1, 9 }); + public static DB6Meta HighlightColorMeta = new DB6Meta(-1, new byte[] { 1, 1, 1, 1, 1, 1 }); + public static DB6Meta HolidayDescriptionsMeta = new DB6Meta(-1, new byte[] { 1, 1 }); + public static DB6Meta HolidayNamesMeta = new DB6Meta(-1, new byte[] { 1, 1 }); + public static DB6Meta HolidaysMeta = new DB6Meta(-1, new byte[] { 1, 16, 10, 1, 1, 10, 1, 1, 1, 1, 1, 3 }); + public static DB6Meta HotfixMeta = new DB6Meta(0, new byte[] { 1, 1, 1 }); + public static DB6Meta ImportPriceArmorMeta = new DB6Meta(-1, new byte[] { 1, 1, 1, 1, 1 }); + public static DB6Meta ImportPriceQualityMeta = new DB6Meta(-1, new byte[] { 1, 1 }); + public static DB6Meta ImportPriceShieldMeta = new DB6Meta(-1, new byte[] { 1, 1 }); + public static DB6Meta ImportPriceWeaponMeta = new DB6Meta(-1, new byte[] { 1, 1 }); + public static DB6Meta InvasionClientDataMeta = new DB6Meta(2, new byte[] { 1, 2, 1, 1, 1, 1, 1, 1, 1, 1 }); + public static DB6Meta ItemMeta = new DB6Meta(-1, new byte[] { 1, 1, 1, 1, 1, 1, 1, 1, 1 }); + public static DB6Meta ItemAppearanceMeta = new DB6Meta(-1, new byte[] { 1, 1, 1, 1, 1 }); + public static DB6Meta ItemAppearanceXUiCameraMeta = new DB6Meta(-1, new byte[] { 1, 1, 1 }); + public static DB6Meta ItemArmorQualityMeta = new DB6Meta(-1, new byte[] { 1, 7, 1 }); + public static DB6Meta ItemArmorShieldMeta = new DB6Meta(-1, new byte[] { 1, 7, 1 }); + public static DB6Meta ItemArmorTotalMeta = new DB6Meta(-1, new byte[] { 1, 1, 1, 1, 1, 1 }); + public static DB6Meta ItemBagFamilyMeta = new DB6Meta(-1, new byte[] { 1, 1 }); + public static DB6Meta ItemBonusMeta = new DB6Meta(-1, new byte[] { 1, 2, 1, 1, 1 }); + public static DB6Meta ItemBonusListLevelDeltaMeta = new DB6Meta(1, new byte[] { 1, 1 }); + public static DB6Meta ItemBonusTreeNodeMeta = new DB6Meta(-1, new byte[] { 1, 1, 1, 1, 1, 1 }); + public static DB6Meta ItemChildEquipmentMeta = new DB6Meta(-1, new byte[] { 1, 1, 1, 1 }); + public static DB6Meta ItemClassMeta = new DB6Meta(-1, new byte[] { 1, 1, 1, 1, 1 }); + public static DB6Meta ItemContextPickerEntryMeta = new DB6Meta(-1, new byte[] { 1, 1, 1, 1, 1, 1 }); + public static DB6Meta ItemCurrencyCostMeta = new DB6Meta(-1, new byte[] { 1, 1 }); + public static DB6Meta ItemDamageAmmoMeta = new DB6Meta(-1, new byte[] { 1, 7, 1 }); + public static DB6Meta ItemDamageOneHandMeta = new DB6Meta(-1, new byte[] { 1, 7, 1 }); + public static DB6Meta ItemDamageOneHandCasterMeta = new DB6Meta(-1, new byte[] { 1, 7, 1 }); + public static DB6Meta ItemDamageTwoHandMeta = new DB6Meta(-1, new byte[] { 1, 7, 1 }); + public static DB6Meta ItemDamageTwoHandCasterMeta = new DB6Meta(-1, new byte[] { 1, 7, 1 }); + public static DB6Meta ItemDisenchantLootMeta = new DB6Meta(-1, new byte[] { 1, 1, 1, 1, 1, 1, 1 }); + public static DB6Meta ItemDisplayInfoMeta = new DB6Meta(-1, new byte[] { 1, 2, 2, 3, 3, 1, 1, 2, 1, 1, 1, 1, 1, 1, 1, 1 }); + public static DB6Meta ItemDisplayInfoMaterialResMeta = new DB6Meta(-1, new byte[] { 1, 1, 1, 1 }); + public static DB6Meta ItemDisplayXUiCameraMeta = new DB6Meta(-1, new byte[] { 1, 1, 1 }); + public static DB6Meta ItemEffectMeta = new DB6Meta(-1, new byte[] { 1, 1, 1, 1, 1, 1, 1, 1, 1, 1 }); + public static DB6Meta ItemExtendedCostMeta = new DB6Meta(-1, new byte[] { 1, 5, 5, 5, 1, 5, 1, 1, 1, 1, 1 }); + public static DB6Meta ItemGroupSoundsMeta = new DB6Meta(-1, new byte[] { 1, 4 }); + public static DB6Meta ItemLevelSelectorMeta = new DB6Meta(-1, new byte[] { 1, 1 }); + public static DB6Meta ItemLimitCategoryMeta = new DB6Meta(-1, new byte[] { 1, 1, 1, 1 }); + public static DB6Meta ItemLimitCategoryConditionMeta = new DB6Meta(-1, new byte[] { 1, 1, 1, 1 }); + public static DB6Meta ItemModifiedAppearanceMeta = new DB6Meta(5, new byte[] { 1, 1, 1, 1, 1, 1 }); + public static DB6Meta ItemModifiedAppearanceExtraMeta = new DB6Meta(-1, new byte[] { 1, 1, 1, 1, 1, 1 }); + public static DB6Meta ItemNameDescriptionMeta = new DB6Meta(-1, new byte[] { 1, 1, 1 }); + public static DB6Meta ItemPetFoodMeta = new DB6Meta(-1, new byte[] { 1, 1 }); + public static DB6Meta ItemPriceBaseMeta = new DB6Meta(-1, new byte[] { 1, 1, 1, 1 }); + public static DB6Meta ItemRandomPropertiesMeta = new DB6Meta(-1, new byte[] { 1, 1, 5 }); + public static DB6Meta ItemRandomSuffixMeta = new DB6Meta(-1, new byte[] { 1, 1, 5, 5 }); + public static DB6Meta ItemRangedDisplayInfoMeta = new DB6Meta(-1, new byte[] { 1, 1, 1, 1, 1 }); + public static DB6Meta ItemSearchNameMeta = new DB6Meta(-1, new byte[] { 1, 1, 3, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1 }); + public static DB6Meta ItemSetMeta = new DB6Meta(-1, new byte[] { 1, 1, 17, 1, 1, 1 }); + public static DB6Meta ItemSetSpellMeta = new DB6Meta(-1, new byte[] { 1, 1, 1, 1, 1 }); + public static DB6Meta ItemSparseMeta = new DB6Meta(-1, new byte[] { 1, 3, 1, 1, 1, 1, 1, 1, 1, 1, 1, 10, 10, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 10, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 10, 1, 1, 1, 1, 1, 1, 3, 1, 1, 1, 1 }); + public static DB6Meta ItemSpecMeta = new DB6Meta(-1, new byte[] { 1, 1, 1, 1, 1, 1, 1 }); + public static DB6Meta ItemSpecOverrideMeta = new DB6Meta(-1, new byte[] { 1, 1, 1 }); + public static DB6Meta ItemSubClassMeta = new DB6Meta(-1, new byte[] { 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1 }); + public static DB6Meta ItemSubClassMaskMeta = new DB6Meta(-1, new byte[] { 1, 1, 1, 1 }); + public static DB6Meta ItemUpgradeMeta = new DB6Meta(-1, new byte[] { 1, 1, 1, 1, 1, 1 }); + public static DB6Meta ItemVisualEffectsMeta = new DB6Meta(-1, new byte[] { 1, 1 }); + public static DB6Meta ItemVisualsMeta = new DB6Meta(-1, new byte[] { 1, 5 }); + public static DB6Meta ItemXBonusTreeMeta = new DB6Meta(-1, new byte[] { 1, 1, 1 }); + public static DB6Meta JournalEncounterMeta = new DB6Meta(-1, new byte[] { 1, 2, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1 }); + public static DB6Meta JournalEncounterCreatureMeta = new DB6Meta(6, new byte[] { 1, 1, 1, 1, 1, 1, 1 }); + public static DB6Meta JournalEncounterItemMeta = new DB6Meta(5, new byte[] { 1, 1, 1, 1, 1, 1 }); + public static DB6Meta JournalEncounterSectionMeta = new DB6Meta(-1, new byte[] { 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1 }); + public static DB6Meta JournalEncounterXDifficultyMeta = new DB6Meta(-1, new byte[] { 1, 1, 1 }); + public static DB6Meta JournalInstanceMeta = new DB6Meta(10, new byte[] { 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1 }); + public static DB6Meta JournalItemXDifficultyMeta = new DB6Meta(-1, new byte[] { 1, 1, 1 }); + public static DB6Meta JournalSectionXDifficultyMeta = new DB6Meta(-1, new byte[] { 1, 1, 1 }); + public static DB6Meta JournalTierMeta = new DB6Meta(-1, new byte[] { 1, 1 }); + public static DB6Meta JournalTierXInstanceMeta = new DB6Meta(-1, new byte[] { 1, 1, 1 }); + public static DB6Meta KeyChainMeta = new DB6Meta(-1, new byte[] { 1, 32 }); + public static DB6Meta KeystoneAffixMeta = new DB6Meta(-1, new byte[] { 1, 1, 1, 1 }); + public static DB6Meta LanguageWordsMeta = new DB6Meta(-1, new byte[] { 1, 1, 1 }); + public static DB6Meta LanguagesMeta = new DB6Meta(1, new byte[] { 1, 1 }); + public static DB6Meta LfgDungeonExpansionMeta = new DB6Meta(-1, new byte[] { 1, 1, 1, 1, 1, 1, 1, 1 }); + public static DB6Meta LfgDungeonGroupMeta = new DB6Meta(-1, new byte[] { 1, 1, 1, 1, 1 }); + public static DB6Meta LfgDungeonsMeta = new DB6Meta(-1, new byte[] { 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1 }); + public static DB6Meta LfgDungeonsGroupingMapMeta = new DB6Meta(-1, new byte[] { 1, 1, 1, 1 }); + public static DB6Meta LfgRoleRequirementMeta = new DB6Meta(-1, new byte[] { 1, 1, 1, 1 }); + public static DB6Meta LightMeta = new DB6Meta(-1, new byte[] { 1, 3, 1, 1, 1, 8 }); + public static DB6Meta LightDataMeta = new DB6Meta(-1, new byte[] { 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1 }); + public static DB6Meta LightParamsMeta = new DB6Meta(10, new byte[] { 1, 1, 1, 1, 1, 3, 1, 1, 1, 1, 1 }); + public static DB6Meta LightSkyboxMeta = new DB6Meta(-1, new byte[] { 1, 1, 1, 1, 1 }); + public static DB6Meta LiquidMaterialMeta = new DB6Meta(-1, new byte[] { 1, 1, 1 }); + public static DB6Meta LiquidObjectMeta = new DB6Meta(-1, new byte[] { 1, 1, 1, 1, 1, 1 }); + public static DB6Meta LiquidTypeMeta = new DB6Meta(-1, new byte[] { 1, 1, 1, 1, 1, 1, 1, 1, 6, 2, 18, 4, 1, 1, 1, 1, 1, 1, 6, 1 }); + public static DB6Meta LoadingScreenTaxiSplinesMeta = new DB6Meta(-1, new byte[] { 1, 10, 10, 1, 1, 1 }); + public static DB6Meta LoadingScreensMeta = new DB6Meta(-1, new byte[] { 1, 1, 1, 1 }); + public static DB6Meta LocaleMeta = new DB6Meta(-1, new byte[] { 1, 1, 1, 1, 1 }); + public static DB6Meta LocationMeta = new DB6Meta(-1, new byte[] { 1, 3, 3 }); + public static DB6Meta LockMeta = new DB6Meta(-1, new byte[] { 1, 8, 8, 8, 8 }); + public static DB6Meta LockTypeMeta = new DB6Meta(4, new byte[] { 1, 1, 1, 1, 1 }); + public static DB6Meta LookAtControllerMeta = new DB6Meta(-1, new byte[] { 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1 }); + public static DB6Meta MailTemplateMeta = new DB6Meta(-1, new byte[] { 1, 1 }); + public static DB6Meta ManagedWorldStateMeta = new DB6Meta(9, new byte[] { 1, 1, 1, 1, 1, 1, 1, 1, 1, 1 }); + public static DB6Meta ManagedWorldStateBuffMeta = new DB6Meta(-1, new byte[] { 1, 1, 1, 1, 1 }); + public static DB6Meta ManagedWorldStateInputMeta = new DB6Meta(-1, new byte[] { 1, 1, 1, 1 }); + public static DB6Meta ManifestInterfaceActionIconMeta = new DB6Meta(0, new byte[] { 1 }); + public static DB6Meta ManifestInterfaceDataMeta = new DB6Meta(-1, new byte[] { 1, 1, 1 }); + public static DB6Meta ManifestInterfaceItemIconMeta = new DB6Meta(0, new byte[] { 1 }); + public static DB6Meta ManifestInterfaceTOCDataMeta = new DB6Meta(-1, new byte[] { 1, 1 }); + public static DB6Meta ManifestMP3Meta = new DB6Meta(0, new byte[] { 1 }); + public static DB6Meta MapMeta = new DB6Meta(-1, new byte[] { 1, 1, 2, 1, 2, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1 }); + public static DB6Meta MapCelestialBodyMeta = new DB6Meta(-1, new byte[] { 1, 1, 1, 1 }); + public static DB6Meta MapChallengeModeMeta = new DB6Meta(0, new byte[] { 1, 1, 1, 3, 1 }); + public static DB6Meta MapDifficultyMeta = new DB6Meta(-1, new byte[] { 1, 1, 1, 1, 1, 1, 1, 1, 1, 1 }); + public static DB6Meta MapDifficultyXConditionMeta = new DB6Meta(-1, new byte[] { 1, 1, 1, 1, 1 }); + public static DB6Meta MarketingPromotionsXLocaleMeta = new DB6Meta(-1, new byte[] { 1, 1, 1, 1, 1, 1, 1, 1 }); + public static DB6Meta MaterialMeta = new DB6Meta(-1, new byte[] { 1, 1, 1, 1, 1 }); + public static DB6Meta MinorTalentMeta = new DB6Meta(-1, new byte[] { 1, 1, 1, 1 }); + public static DB6Meta ModelAnimCloakDampeningMeta = new DB6Meta(-1, new byte[] { 1, 1, 1, 1 }); + public static DB6Meta ModelFileDataMeta = new DB6Meta(1, new byte[] { 1, 1, 1 }); + public static DB6Meta ModelRibbonQualityMeta = new DB6Meta(-1, new byte[] { 1, 1, 1 }); + public static DB6Meta ModifierTreeMeta = new DB6Meta(-1, new byte[] { 1, 1, 1, 1, 1, 1, 1, 1 }); + public static DB6Meta MountMeta = new DB6Meta(8, new byte[] { 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1 }); + public static DB6Meta MountCapabilityMeta = new DB6Meta(6, new byte[] { 1, 1, 1, 1, 1, 1, 1, 1 }); + public static DB6Meta MountTypeXCapabilityMeta = new DB6Meta(-1, new byte[] { 1, 1, 1, 1 }); + public static DB6Meta MountXDisplayMeta = new DB6Meta(-1, new byte[] { 1, 1, 1, 1 }); + public static DB6Meta MovieMeta = new DB6Meta(-1, new byte[] { 1, 1, 1, 1, 1 }); + public static DB6Meta MovieFileDataMeta = new DB6Meta(-1, new byte[] { 1, 1 }); + public static DB6Meta MovieVariationMeta = new DB6Meta(-1, new byte[] { 1, 1, 1, 1 }); + public static DB6Meta NPCSoundsMeta = new DB6Meta(-1, new byte[] { 1, 4 }); + public static DB6Meta NameGenMeta = new DB6Meta(-1, new byte[] { 1, 1, 1, 1 }); + public static DB6Meta NamesProfanityMeta = new DB6Meta(-1, new byte[] { 1, 1, 1 }); + public static DB6Meta NamesReservedMeta = new DB6Meta(-1, new byte[] { 1, 1 }); + public static DB6Meta NamesReservedLocaleMeta = new DB6Meta(-1, new byte[] { 1, 1, 1 }); + public static DB6Meta NpcModelItemSlotDisplayInfoMeta = new DB6Meta(-1, new byte[] { 1, 1, 1, 1 }); + public static DB6Meta ObjectEffectMeta = new DB6Meta(-1, new byte[] { 1, 1, 3, 1, 1, 1, 1, 1, 1, 1 }); + public static DB6Meta ObjectEffectGroupMeta = new DB6Meta(-1, new byte[] { 1, 1 }); + public static DB6Meta ObjectEffectModifierMeta = new DB6Meta(-1, new byte[] { 1, 4, 1, 1, 1 }); + public static DB6Meta ObjectEffectPackageMeta = new DB6Meta(-1, new byte[] { 1, 1 }); + public static DB6Meta ObjectEffectPackageElemMeta = new DB6Meta(-1, new byte[] { 1, 1, 1, 1 }); + public static DB6Meta OutlineEffectMeta = new DB6Meta(-1, new byte[] { 1, 1, 1, 1, 1, 1, 1 }); + public static DB6Meta OverrideSpellDataMeta = new DB6Meta(-1, new byte[] { 1, 10, 1, 1 }); + public static DB6Meta PageTextMaterialMeta = new DB6Meta(-1, new byte[] { 1, 1 }); + public static DB6Meta PaperDollItemFrameMeta = new DB6Meta(-1, new byte[] { 1, 1, 1, 1 }); + public static DB6Meta ParagonReputationMeta = new DB6Meta(-1, new byte[] { 1, 1, 1, 1 }); + public static DB6Meta ParticleColorMeta = new DB6Meta(-1, new byte[] { 1, 3, 3, 3 }); + public static DB6Meta PathMeta = new DB6Meta(-1, new byte[] { 1, 1, 1, 1, 1, 1, 1, 1 }); + public static DB6Meta PathNodeMeta = new DB6Meta(0, new byte[] { 1, 1, 1, 1 }); + public static DB6Meta PathNodePropertyMeta = new DB6Meta(3, new byte[] { 1, 1, 1, 1, 1 }); + public static DB6Meta PathPropertyMeta = new DB6Meta(3, new byte[] { 1, 1, 1, 1 }); + public static DB6Meta PhaseMeta = new DB6Meta(-1, new byte[] { 1, 1 }); + public static DB6Meta PhaseShiftZoneSoundsMeta = new DB6Meta(-1, new byte[] { 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1 }); + public static DB6Meta PhaseXPhaseGroupMeta = new DB6Meta(-1, new byte[] { 1, 1, 1 }); + public static DB6Meta PlayerConditionMeta = new DB6Meta(-1, new byte[] { 1, 1, 1, 1, 1, 1, 1, 1, 1, 2, 1, 4, 1, 1, 1, 1, 1, 1, 4, 4, 4, 1, 4, 4, 4, 2, 1, 4, 4, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 3, 1, 1, 1, 1, 1, 1, 4, 1, 1, 1, 4, 4, 4, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 3, 4, 4, 4, 1, 4, 1, 4, 6, 1, 1, 1, 2, 1 }); + public static DB6Meta PositionerMeta = new DB6Meta(-1, new byte[] { 1, 1, 1, 1, 1 }); + public static DB6Meta PositionerStateMeta = new DB6Meta(-1, new byte[] { 1, 1, 1, 1, 1, 1, 1, 1, 1 }); + public static DB6Meta PositionerStateEntryMeta = new DB6Meta(-1, new byte[] { 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1 }); + public static DB6Meta PowerDisplayMeta = new DB6Meta(-1, new byte[] { 1, 1, 1, 1, 1, 1 }); + public static DB6Meta PowerTypeMeta = new DB6Meta(-1, new byte[] { 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1 }); + public static DB6Meta PrestigeLevelInfoMeta = new DB6Meta(-1, new byte[] { 1, 1, 1, 1, 1 }); + public static DB6Meta PvpBracketTypesMeta = new DB6Meta(-1, new byte[] { 1, 1, 4 }); + public static DB6Meta PvpDifficultyMeta = new DB6Meta(-1, new byte[] { 1, 1, 1, 1, 1 }); + public static DB6Meta PvpItemMeta = new DB6Meta(-1, new byte[] { 1, 1, 1 }); + public static DB6Meta PvpRewardMeta = new DB6Meta(-1, new byte[] { 1, 1, 1, 1 }); + public static DB6Meta PvpScalingEffectMeta = new DB6Meta(-1, new byte[] { 1, 1, 1, 1 }); + public static DB6Meta PvpScalingEffectTypeMeta = new DB6Meta(-1, new byte[] { 1, 1 }); + public static DB6Meta PvpTalentMeta = new DB6Meta(-1, new byte[] { 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1 }); + public static DB6Meta PvpTalentUnlockMeta = new DB6Meta(-1, new byte[] { 1, 1, 1, 1 }); + public static DB6Meta QuestFactionRewardMeta = new DB6Meta(-1, new byte[] { 1, 10 }); + public static DB6Meta QuestFeedbackEffectMeta = new DB6Meta(-1, new byte[] { 1, 1, 1, 1, 1, 1, 1 }); + public static DB6Meta QuestInfoMeta = new DB6Meta(-1, new byte[] { 1, 1, 1, 1, 1 }); + public static DB6Meta QuestLineMeta = new DB6Meta(-1, new byte[] { 1, 1 }); + public static DB6Meta QuestLineXQuestMeta = new DB6Meta(-1, new byte[] { 1, 1, 1, 1 }); + public static DB6Meta QuestMoneyRewardMeta = new DB6Meta(-1, new byte[] { 1, 10 }); + public static DB6Meta QuestObjectiveMeta = new DB6Meta(-1, new byte[] { 1, 1, 1, 1, 1, 1, 1, 1, 1 }); + public static DB6Meta QuestPOIBlobMeta = new DB6Meta(0, new byte[] { 1, 1, 1, 1, 1 }); + public static DB6Meta QuestPOIPointMeta = new DB6Meta(3, new byte[] { 1, 1, 1, 1 }); + public static DB6Meta QuestPOIPointCliTaskMeta = new DB6Meta(-1, new byte[] { 1, 1, 1, 1, 1, 1, 1, 1 }); + public static DB6Meta QuestPackageItemMeta = new DB6Meta(-1, new byte[] { 1, 1, 1, 1, 1 }); + public static DB6Meta QuestSortMeta = new DB6Meta(-1, new byte[] { 1, 1, 1 }); + public static DB6Meta QuestV2Meta = new DB6Meta(-1, new byte[] { 1, 1 }); + public static DB6Meta QuestV2CliTaskMeta = new DB6Meta(20, new byte[] { 1, 1, 1, 1, 1, 1, 1, 3, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1 }); + public static DB6Meta QuestXGroupActivityMeta = new DB6Meta(-1, new byte[] { 1, 1 }); + public static DB6Meta QuestXPMeta = new DB6Meta(-1, new byte[] { 1, 10 }); + public static DB6Meta RandPropPointsMeta = new DB6Meta(-1, new byte[] { 1, 5, 5, 5 }); + public static DB6Meta ResearchBranchMeta = new DB6Meta(-1, new byte[] { 1, 1, 1, 1, 1, 1 }); + public static DB6Meta ResearchFieldMeta = new DB6Meta(2, new byte[] { 1, 1, 1 }); + public static DB6Meta ResearchProjectMeta = new DB6Meta(7, new byte[] { 1, 1, 1, 1, 1, 1, 1, 1, 1 }); + public static DB6Meta ResearchSiteMeta = new DB6Meta(-1, new byte[] { 1, 1, 1, 1, 1 }); + public static DB6Meta ResistancesMeta = new DB6Meta(-1, new byte[] { 1, 1, 1, 1 }); + public static DB6Meta RewardPackMeta = new DB6Meta(-1, new byte[] { 1, 1, 1, 1, 1, 1, 1 }); + public static DB6Meta RewardPackXCurrencyTypeMeta = new DB6Meta(-1, new byte[] { 1, 1, 1, 1 }); + public static DB6Meta RewardPackXItemMeta = new DB6Meta(-1, new byte[] { 1, 1, 1, 1 }); + public static DB6Meta RibbonQualityMeta = new DB6Meta(-1, new byte[] { 1, 1, 1, 1, 1 }); + public static DB6Meta RulesetItemUpgradeMeta = new DB6Meta(-1, new byte[] { 1, 1, 1 }); + public static DB6Meta ScalingStatDistributionMeta = new DB6Meta(-1, new byte[] { 1, 1, 1, 1 }); + public static DB6Meta ScenarioMeta = new DB6Meta(-1, new byte[] { 1, 1, 1, 1, 1 }); + public static DB6Meta ScenarioEventEntryMeta = new DB6Meta(-1, new byte[] { 1, 1, 1 }); + public static DB6Meta ScenarioStepMeta = new DB6Meta(-1, new byte[] { 1, 1, 1, 1, 1, 1, 1, 1, 1, 1 }); + public static DB6Meta SceneScriptMeta = new DB6Meta(-1, new byte[] { 1, 1, 1, 1, 1 }); + public static DB6Meta SceneScriptPackageMeta = new DB6Meta(-1, new byte[] { 1, 1 }); + public static DB6Meta SceneScriptPackageMemberMeta = new DB6Meta(-1, new byte[] { 1, 1, 1, 1, 1 }); + public static DB6Meta ScheduledIntervalMeta = new DB6Meta(-1, new byte[] { 1, 1, 1, 1, 1, 1 }); + public static DB6Meta ScheduledWorldStateMeta = new DB6Meta(-1, new byte[] { 1, 1, 1, 1, 1, 1, 1, 1, 1 }); + public static DB6Meta ScheduledWorldStateGroupMeta = new DB6Meta(-1, new byte[] { 1, 1, 1, 1, 1, 1 }); + public static DB6Meta ScheduledWorldStateXUniqCatMeta = new DB6Meta(0, new byte[] { 1, 1, 1 }); + public static DB6Meta ScreenEffectMeta = new DB6Meta(-1, new byte[] { 1, 1, 4, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1 }); + public static DB6Meta ScreenLocationMeta = new DB6Meta(-1, new byte[] { 1, 1 }); + public static DB6Meta SeamlessSiteMeta = new DB6Meta(-1, new byte[] { 1, 1 }); + public static DB6Meta ServerMessagesMeta = new DB6Meta(-1, new byte[] { 1, 1 }); + public static DB6Meta ShadowyEffectMeta = new DB6Meta(-1, new byte[] { 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1 }); + public static DB6Meta SkillLineMeta = new DB6Meta(-1, new byte[] { 1, 1, 1, 1, 1, 1, 1, 1, 1 }); + public static DB6Meta SkillLineAbilityMeta = new DB6Meta(-1, new byte[] { 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1 }); + public static DB6Meta SkillRaceClassInfoMeta = new DB6Meta(-1, new byte[] { 1, 1, 1, 1, 1, 1, 1, 1 }); + public static DB6Meta SoundAmbienceMeta = new DB6Meta(-1, new byte[] { 1, 1, 2, 1, 1 }); + public static DB6Meta SoundAmbienceFlavorMeta = new DB6Meta(-1, new byte[] { 1, 1, 1, 1 }); + public static DB6Meta SoundBusMeta = new DB6Meta(7, new byte[] { 1, 1, 1, 1, 1, 1, 1, 1 }); + public static DB6Meta SoundBusOverrideMeta = new DB6Meta(0, new byte[] { 1, 1, 1, 1, 1, 1, 1 }); + public static DB6Meta SoundEmitterPillPointsMeta = new DB6Meta(-1, new byte[] { 1, 3, 1 }); + public static DB6Meta SoundEmittersMeta = new DB6Meta(9, new byte[] { 3, 3, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1 }); + public static DB6Meta SoundFilterMeta = new DB6Meta(-1, new byte[] { 1, 1 }); + public static DB6Meta SoundFilterElemMeta = new DB6Meta(-1, new byte[] { 1, 9, 1, 1 }); + public static DB6Meta SoundKitMeta = new DB6Meta(16, new byte[] { 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1 }); + public static DB6Meta SoundKitAdvancedMeta = new DB6Meta(-1, new byte[] { 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1 }); + public static DB6Meta SoundKitChildMeta = new DB6Meta(-1, new byte[] { 1, 1, 1 }); + public static DB6Meta SoundKitEntryMeta = new DB6Meta(-1, new byte[] { 1, 1, 1, 1, 1 }); + public static DB6Meta SoundKitFallbackMeta = new DB6Meta(-1, new byte[] { 1, 1, 1 }); + public static DB6Meta SoundOverrideMeta = new DB6Meta(-1, new byte[] { 1, 1, 1, 1, 1 }); + public static DB6Meta SoundProviderPreferencesMeta = new DB6Meta(-1, new byte[] { 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1 }); + public static DB6Meta SourceInfoMeta = new DB6Meta(-1, new byte[] { 1, 1, 1, 1, 1 }); + public static DB6Meta SpamMessagesMeta = new DB6Meta(-1, new byte[] { 1, 1 }); + public static DB6Meta SpecializationSpellsMeta = new DB6Meta(5, new byte[] { 1, 1, 1, 1, 1, 1 }); + public static DB6Meta SpellMeta = new DB6Meta(5, new byte[] { 1, 1, 1, 1, 1, 1, 1 }); + public static DB6Meta SpellActionBarPrefMeta = new DB6Meta(-1, new byte[] { 1, 1, 1 }); + public static DB6Meta SpellActivationOverlayMeta = new DB6Meta(-1, new byte[] { 1, 1, 1, 1, 1, 4, 1, 1, 1 }); + public static DB6Meta SpellAuraOptionsMeta = new DB6Meta(-1, new byte[] { 1, 1, 1, 1, 1, 1, 1, 1, 1 }); + public static DB6Meta SpellAuraRestrictionsMeta = new DB6Meta(-1, new byte[] { 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1 }); + public static DB6Meta SpellAuraVisXChrSpecMeta = new DB6Meta(-1, new byte[] { 1, 1, 1 }); + public static DB6Meta SpellAuraVisibilityMeta = new DB6Meta(3, new byte[] { 1, 1, 1, 1 }); + public static DB6Meta SpellCastTimesMeta = new DB6Meta(-1, new byte[] { 1, 1, 1, 1 }); + public static DB6Meta SpellCastingRequirementsMeta = new DB6Meta(-1, new byte[] { 1, 1, 1, 1, 1, 1, 1, 1 }); + public static DB6Meta SpellCategoriesMeta = new DB6Meta(-1, new byte[] { 1, 1, 1, 1, 1, 1, 1, 1, 1, 1 }); + public static DB6Meta SpellCategoryMeta = new DB6Meta(-1, new byte[] { 1, 1, 1, 1, 1, 1, 1 }); + public static DB6Meta SpellChainEffectsMeta = new DB6Meta(-1, new byte[] { 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 3, 3, 3, 3, 1, 1, 1, 1, 1, 1, 11, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 3 }); + public static DB6Meta SpellClassOptionsMeta = new DB6Meta(-1, new byte[] { 1, 1, 4, 1, 1 }); + public static DB6Meta SpellCooldownsMeta = new DB6Meta(-1, new byte[] { 1, 1, 1, 1, 1, 1 }); + public static DB6Meta SpellDescriptionVariablesMeta = new DB6Meta(-1, new byte[] { 1, 1 }); + public static DB6Meta SpellDispelTypeMeta = new DB6Meta(-1, new byte[] { 1, 1, 1, 1, 1 }); + public static DB6Meta SpellDurationMeta = new DB6Meta(-1, new byte[] { 1, 1, 1, 1 }); + public static DB6Meta SpellEffectMeta = new DB6Meta(1, new byte[] { 4, 1, 1, 1, 1, 1, 1, 2, 2, 2, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1 }); + public static DB6Meta SpellEffectEmissionMeta = new DB6Meta(-1, new byte[] { 1, 1, 1, 1, 1 }); + public static DB6Meta SpellEffectGroupSizeMeta = new DB6Meta(-1, new byte[] { 1, 1, 1 }); + public static DB6Meta SpellEffectScalingMeta = new DB6Meta(-1, new byte[] { 1, 1, 1, 1, 1 }); + public static DB6Meta SpellEquippedItemsMeta = new DB6Meta(-1, new byte[] { 1, 1, 1, 1, 1 }); + public static DB6Meta SpellFlyoutMeta = new DB6Meta(-1, new byte[] { 1, 1, 1, 1, 1, 1, 1 }); + public static DB6Meta SpellFlyoutItemMeta = new DB6Meta(-1, new byte[] { 1, 1, 1, 1 }); + public static DB6Meta SpellFocusObjectMeta = new DB6Meta(-1, new byte[] { 1, 1 }); + public static DB6Meta SpellInterruptsMeta = new DB6Meta(-1, new byte[] { 1, 1, 2, 2, 1, 1 }); + public static DB6Meta SpellItemEnchantmentMeta = new DB6Meta(-1, new byte[] { 1, 3, 1, 3, 1, 1, 3, 1, 1, 1, 1, 1, 1, 3, 1, 1, 1, 1, 1, 1 }); + public static DB6Meta SpellItemEnchantmentConditionMeta = new DB6Meta(-1, new byte[] { 1, 5, 5, 5, 5, 5, 5 }); + public static DB6Meta SpellKeyboundOverrideMeta = new DB6Meta(-1, new byte[] { 1, 1, 1, 1 }); + public static DB6Meta SpellLabelMeta = new DB6Meta(-1, new byte[] { 1, 1, 1 }); + public static DB6Meta SpellLearnSpellMeta = new DB6Meta(-1, new byte[] { 1, 1, 1, 1 }); + public static DB6Meta SpellLevelsMeta = new DB6Meta(-1, new byte[] { 1, 1, 1, 1, 1, 1, 1 }); + public static DB6Meta SpellMechanicMeta = new DB6Meta(-1, new byte[] { 1, 1 }); + public static DB6Meta SpellMiscMeta = new DB6Meta(-1, new byte[] { 1, 14, 1, 1, 1, 1, 1, 1, 1, 1 }); + public static DB6Meta SpellMiscDifficultyMeta = new DB6Meta(2, new byte[] { 1, 1, 1 }); + public static DB6Meta SpellMissileMeta = new DB6Meta(-1, new byte[] { 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1 }); + public static DB6Meta SpellMissileMotionMeta = new DB6Meta(-1, new byte[] { 1, 1, 1, 1, 1 }); + public static DB6Meta SpellPowerMeta = new DB6Meta(8, new byte[] { 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1 }); + public static DB6Meta SpellPowerDifficultyMeta = new DB6Meta(2, new byte[] { 1, 1, 1 }); + public static DB6Meta SpellProceduralEffectMeta = new DB6Meta(2, new byte[] { 4, 1, 1 }); + public static DB6Meta SpellProcsPerMinuteMeta = new DB6Meta(-1, new byte[] { 1, 1, 1 }); + public static DB6Meta SpellProcsPerMinuteModMeta = new DB6Meta(-1, new byte[] { 1, 1, 1, 1, 1 }); + public static DB6Meta SpellRadiusMeta = new DB6Meta(-1, new byte[] { 1, 1, 1, 1, 1 }); + public static DB6Meta SpellRangeMeta = new DB6Meta(-1, new byte[] { 1, 2, 2, 1, 1, 1 }); + public static DB6Meta SpellReagentsMeta = new DB6Meta(-1, new byte[] { 1, 1, 8, 8 }); + public static DB6Meta SpellReagentsCurrencyMeta = new DB6Meta(-1, new byte[] { 1, 1, 1, 1 }); + public static DB6Meta SpellScalingMeta = new DB6Meta(-1, new byte[] { 1, 1, 1, 1, 1, 1 }); + public static DB6Meta SpellShapeshiftMeta = new DB6Meta(-1, new byte[] { 1, 1, 2, 2, 1 }); + public static DB6Meta SpellShapeshiftFormMeta = new DB6Meta(-1, new byte[] { 1, 1, 1, 1, 1, 1, 1, 1, 1, 4, 8 }); + public static DB6Meta SpellSpecialUnitEffectMeta = new DB6Meta(-1, new byte[] { 1, 1, 1 }); + public static DB6Meta SpellTargetRestrictionsMeta = new DB6Meta(-1, new byte[] { 1, 1, 1, 1, 1, 1, 1, 1, 1 }); + public static DB6Meta SpellTotemsMeta = new DB6Meta(-1, new byte[] { 1, 1, 2, 2 }); + public static DB6Meta SpellVisualMeta = new DB6Meta(7, new byte[] { 1, 3, 3, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1 }); + public static DB6Meta SpellVisualAnimMeta = new DB6Meta(-1, new byte[] { 1, 1, 1, 1 }); + public static DB6Meta SpellVisualColorEffectMeta = new DB6Meta(-1, new byte[] { 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1 }); + public static DB6Meta SpellVisualEffectNameMeta = new DB6Meta(-1, new byte[] { 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1 }); + public static DB6Meta SpellVisualKitMeta = new DB6Meta(-1, new byte[] { 1, 1, 1, 1, 1, 1 }); + public static DB6Meta SpellVisualKitAreaModelMeta = new DB6Meta(-1, new byte[] { 1, 1, 1, 1, 1, 1, 1 }); + public static DB6Meta SpellVisualKitEffectMeta = new DB6Meta(-1, new byte[] { 1, 1, 1, 1 }); + public static DB6Meta SpellVisualKitModelAttachMeta = new DB6Meta(6, new byte[] { 1, 3, 3, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1 }); + public static DB6Meta SpellVisualMissileMeta = new DB6Meta(13, new byte[] { 1, 1, 1, 3, 3, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1 }); + public static DB6Meta SpellXSpellVisualMeta = new DB6Meta(2, new byte[] { 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1 }); + public static DB6Meta StartupFilesMeta = new DB6Meta(-1, new byte[] { 1, 1, 1, 1 }); + public static DB6Meta Startup_StringsMeta = new DB6Meta(-1, new byte[] { 1, 1, 1 }); + public static DB6Meta StationeryMeta = new DB6Meta(-1, new byte[] { 1, 1, 1, 1 }); + public static DB6Meta StringLookupsMeta = new DB6Meta(-1, new byte[] { 1, 1 }); + public static DB6Meta SummonPropertiesMeta = new DB6Meta(-1, new byte[] { 1, 1, 1, 1, 1, 1 }); + public static DB6Meta TactKeyMeta = new DB6Meta(-1, new byte[] { 1, 16 }); + public static DB6Meta TactKeyLookupMeta = new DB6Meta(-1, new byte[] { 1, 8 }); + public static DB6Meta TalentMeta = new DB6Meta(-1, new byte[] { 1, 1, 1, 1, 1, 1, 1, 1, 2, 1 }); + public static DB6Meta TaxiNodesMeta = new DB6Meta(8, new byte[] { 3, 1, 2, 2, 1, 1, 1, 1, 1 }); + public static DB6Meta TaxiPathMeta = new DB6Meta(2, new byte[] { 1, 1, 1, 1 }); + public static DB6Meta TaxiPathNodeMeta = new DB6Meta(8, new byte[] { 3, 1, 1, 1, 1, 1, 1, 1, 1 }); + public static DB6Meta TerrainMaterialMeta = new DB6Meta(-1, new byte[] { 1, 1, 1, 1 }); + public static DB6Meta TerrainTypeMeta = new DB6Meta(-1, new byte[] { 1, 1, 1, 1, 1, 1 }); + public static DB6Meta TerrainTypeSoundsMeta = new DB6Meta(-1, new byte[] { 1, 1 }); + public static DB6Meta TextureBlendSetMeta = new DB6Meta(-1, new byte[] { 1, 3, 3, 3, 3, 3, 4, 1, 1, 1, 1 }); + public static DB6Meta TextureFileDataMeta = new DB6Meta(2, new byte[] { 1, 1 }); + public static DB6Meta TotemCategoryMeta = new DB6Meta(-1, new byte[] { 1, 1, 1, 1 }); + public static DB6Meta ToyMeta = new DB6Meta(4, new byte[] { 1, 1, 1, 1, 1 }); + public static DB6Meta TradeSkillCategoryMeta = new DB6Meta(-1, new byte[] { 1, 1, 1, 1, 1, 1 }); + public static DB6Meta TradeSkillItemMeta = new DB6Meta(-1, new byte[] { 1, 1, 1 }); + public static DB6Meta TransformMatrixMeta = new DB6Meta(-1, new byte[] { 1, 3, 1, 1, 1, 1 }); + public static DB6Meta TransmogHolidayMeta = new DB6Meta(0, new byte[] { 1, 1 }); + public static DB6Meta TransmogSetMeta = new DB6Meta(4, new byte[] { 1, 1, 1, 1, 1, 1, 1, 1, 1, 1 }); + public static DB6Meta TransmogSetGroupMeta = new DB6Meta(1, new byte[] { 1, 1 }); + public static DB6Meta TransmogSetItemMeta = new DB6Meta(0, new byte[] { 1, 1, 1, 1 }); + public static DB6Meta TransportAnimationMeta = new DB6Meta(-1, new byte[] { 1, 1, 1, 3, 1 }); + public static DB6Meta TransportPhysicsMeta = new DB6Meta(-1, new byte[] { 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1 }); + public static DB6Meta TransportRotationMeta = new DB6Meta(-1, new byte[] { 1, 1, 1, 4 }); + public static DB6Meta TrophyMeta = new DB6Meta(-1, new byte[] { 1, 1, 1, 1, 1 }); + public static DB6Meta UiCamFbackTransmogChrRaceMeta = new DB6Meta(-1, new byte[] { 1, 1, 1, 1, 1, 1 }); + public static DB6Meta UiCamFbackTransmogWeaponMeta = new DB6Meta(-1, new byte[] { 1, 1, 1, 1, 1 }); + public static DB6Meta UiCameraMeta = new DB6Meta(-1, new byte[] { 1, 1, 3, 3, 3, 1, 1, 1, 1, 1 }); + public static DB6Meta UiCameraTypeMeta = new DB6Meta(-1, new byte[] { 1, 1, 1, 1 }); + public static DB6Meta UiMapPOIMeta = new DB6Meta(6, new byte[] { 1, 3, 1, 1, 1, 1, 1 }); + public static DB6Meta UiModelSceneMeta = new DB6Meta(-1, new byte[] { 1, 1, 1 }); + public static DB6Meta UiModelSceneActorMeta = new DB6Meta(7, new byte[] { 1, 3, 1, 1, 1, 1, 1, 1, 1, 1 }); + public static DB6Meta UiModelSceneActorDisplayMeta = new DB6Meta(-1, new byte[] { 1, 1, 1, 1, 1, 1 }); + public static DB6Meta UiModelSceneCameraMeta = new DB6Meta(14, new byte[] { 1, 3, 3, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1 }); + public static DB6Meta UiTextureAtlasMeta = new DB6Meta(-1, new byte[] { 1, 1, 1, 1 }); + public static DB6Meta UiTextureAtlasMemberMeta = new DB6Meta(-1, new byte[] { 1, 1, 1, 1, 1, 1, 1, 1, 1 }); + public static DB6Meta UiTextureKitMeta = new DB6Meta(-1, new byte[] { 1, 1 }); + public static DB6Meta UnitBloodMeta = new DB6Meta(-1, new byte[] { 1, 5, 1, 1, 1, 1, 1, 1 }); + public static DB6Meta UnitBloodLevelsMeta = new DB6Meta(-1, new byte[] { 1, 3 }); + public static DB6Meta UnitConditionMeta = new DB6Meta(-1, new byte[] { 1, 8, 1, 8, 8 }); + public static DB6Meta UnitPowerBarMeta = new DB6Meta(-1, new byte[] { 1, 1, 1, 6, 6, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1 }); + public static DB6Meta UnitTestMeta = new DB6Meta(2, new byte[] { 1, 1, 1, 1, 1 }); + public static DB6Meta VehicleMeta = new DB6Meta(-1, new byte[] { 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2, 1, 1, 2, 1, 1, 8, 1, 3, 1, 1 }); + public static DB6Meta VehicleSeatMeta = new DB6Meta(-1, new byte[] { 1, 1, 1, 1, 3, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 3, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1 }); + public static DB6Meta VehicleUIIndSeatMeta = new DB6Meta(-1, new byte[] { 1, 1, 1, 1, 1 }); + public static DB6Meta VehicleUIIndicatorMeta = new DB6Meta(-1, new byte[] { 1, 1 }); + public static DB6Meta VideoHardwareMeta = new DB6Meta(14, new byte[] { 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1 }); + public static DB6Meta VignetteMeta = new DB6Meta(-1, new byte[] { 1, 1, 1, 1, 1, 1, 1, 1 }); + public static DB6Meta VocalUISoundsMeta = new DB6Meta(-1, new byte[] { 1, 1, 1, 1, 2 }); + public static DB6Meta WMOAreaTableMeta = new DB6Meta(13, new byte[] { 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1 }); + public static DB6Meta WbAccessControlListMeta = new DB6Meta(-1, new byte[] { 1, 1, 1, 1, 1, 1 }); + public static DB6Meta WbCertWhitelistMeta = new DB6Meta(-1, new byte[] { 1, 1, 1, 1, 1 }); + public static DB6Meta WeaponImpactSoundsMeta = new DB6Meta(-1, new byte[] { 1, 1, 1, 1, 11, 11, 11, 11 }); + public static DB6Meta WeaponSwingSounds2Meta = new DB6Meta(-1, new byte[] { 1, 1, 1, 1 }); + public static DB6Meta WeaponTrailMeta = new DB6Meta(-1, new byte[] { 1, 1, 1, 1, 1, 3, 3, 3, 3, 3 }); + public static DB6Meta WeaponTrailModelDefMeta = new DB6Meta(-1, new byte[] { 1, 1, 1, 1 }); + public static DB6Meta WeaponTrailParamMeta = new DB6Meta(-1, new byte[] { 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1 }); + public static DB6Meta WeatherMeta = new DB6Meta(-1, new byte[] { 1, 2, 1, 3, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1 }); + public static DB6Meta WindSettingsMeta = new DB6Meta(-1, new byte[] { 1, 1, 3, 1, 1, 3, 1, 3, 1, 1, 1 }); + public static DB6Meta WmoMinimapTextureMeta = new DB6Meta(-1, new byte[] { 1, 1, 1, 1, 1, 1 }); + public static DB6Meta WorldBossLockoutMeta = new DB6Meta(-1, new byte[] { 1, 1, 1 }); + public static DB6Meta WorldChunkSoundsMeta = new DB6Meta(-1, new byte[] { 1, 1, 1, 1, 1, 1, 1 }); + public static DB6Meta WorldEffectMeta = new DB6Meta(-1, new byte[] { 1, 1, 1, 1, 1, 1, 1 }); + public static DB6Meta WorldElapsedTimerMeta = new DB6Meta(-1, new byte[] { 1, 1, 1, 1 }); + public static DB6Meta WorldMapAreaMeta = new DB6Meta(15, new byte[] { 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1 }); + public static DB6Meta WorldMapContinentMeta = new DB6Meta(-1, new byte[] { 1, 2, 1, 2, 2, 1, 1, 1, 1, 1, 1, 1 }); + public static DB6Meta WorldMapOverlayMeta = new DB6Meta(0, new byte[] { 1, 1, 1, 1, 1, 4, 1, 1, 1, 1, 1, 1, 1, 1 }); + public static DB6Meta WorldMapTransformsMeta = new DB6Meta(-1, new byte[] { 1, 6, 2, 1, 1, 1, 1, 1, 1, 1 }); + public static DB6Meta WorldSafeLocsMeta = new DB6Meta(-1, new byte[] { 1, 3, 1, 1, 1 }); + public static DB6Meta WorldStateExpressionMeta = new DB6Meta(-1, new byte[] { 1, 1 }); + public static DB6Meta WorldStateUIMeta = new DB6Meta(15, new byte[] { 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 3, 1, 1, 1, 1 }); + public static DB6Meta WorldStateZoneSoundsMeta = new DB6Meta(-1, new byte[] { 1, 1, 1, 1, 1, 1, 1, 1, 1 }); + public static DB6Meta World_PVP_AreaMeta = new DB6Meta(-1, new byte[] { 1, 1, 1, 1, 1, 1, 1, 1 }); + public static DB6Meta ZoneIntroMusicTableMeta = new DB6Meta(-1, new byte[] { 1, 1, 1, 1, 1 }); + public static DB6Meta ZoneLightMeta = new DB6Meta(-1, new byte[] { 1, 1, 1, 1 }); + public static DB6Meta ZoneLightPointMeta = new DB6Meta(-1, new byte[] { 1, 2, 1, 1 }); + public static DB6Meta ZoneMusicMeta = new DB6Meta(-1, new byte[] { 1, 1, 2, 2, 2 }); + } +} diff --git a/Game/DataStorage/ClientReader/DB6MetaOld.cs b/Game/DataStorage/ClientReader/DB6MetaOld.cs new file mode 100644 index 000000000..c9b0a7883 --- /dev/null +++ b/Game/DataStorage/ClientReader/DB6MetaOld.cs @@ -0,0 +1,5985 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Game.DataStorage +{ + public struct DB6Meta + { + public DB6Meta(int indexField, byte[] arraySizes, object[] fieldDefaults) + { + IndexField = indexField; + ArraySizes = arraySizes; + FieldDefaults = fieldDefaults; + } + + bool HasIndexFieldInData() + { + return IndexField != -1; + } + + uint GetIndexField() + { + return (uint)(IndexField == -1 ? 0 : IndexField); + } + + uint GetDbIndexField() + { + if (IndexField == -1) + return 0; + + uint index = 0; + for (uint i = 0; i < ArraySizes.Length && i < IndexField; ++i) + index += ArraySizes[i]; + + return index; + } + + uint GetDbFieldCount() + { + uint fields = 0; + for (uint i = 0; i < ArraySizes.Length; ++i) + fields += ArraySizes[i]; + + if (!HasIndexFieldInData()) + ++fields; + + return fields; + } + + int IndexField; + public byte[] ArraySizes; + public object[] FieldDefaults; + } + + struct AchievementMeta + { + public static DB6Meta Instance() + { + byte[] arraySizes = { 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1 }; + object[] fieldDefaults = { "", "", 0, "", 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }; + return new DB6Meta(13, arraySizes, fieldDefaults); } + } + + struct Achievement_CategoryMeta + { + public static DB6Meta Instance() + { + byte[] arraySizes = { 1, 1, 1, 1 }; + object[] fieldDefaults = { "", 0, 0, 0 }; + return new DB6Meta(3, arraySizes, fieldDefaults); + } + } + + struct AdventureJournalMeta + { + public static DB6Meta Instance() + { + byte[] arraySizes = { 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2, 1, 1, 1, 1, 1, 1, 1, 2, 1, 1, 1 }; + object[] fieldDefaults = { "", "", "", 0, 0, "", "", 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }; + return new DB6Meta(-1, arraySizes, fieldDefaults); + } + } + + struct AdventureMapPOIMeta + { + public static DB6Meta Instance() + { + byte[] arraySizes = { 2, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1 }; + object[] fieldDefaults = { 0.0f, 0, "", "", 0, 0, 0, 0, 0, 0, 0, 0, 0 }; + return new DB6Meta(-1, arraySizes, fieldDefaults); + } + } + + struct AnimKitMeta + { + public static DB6Meta Instance() + { + byte[] arraySizes = { 1, 1, 1 }; + object[] fieldDefaults = { 0, 0, 0 }; + return new DB6Meta(-1, arraySizes, fieldDefaults); + } + } + + struct AnimKitBoneSetMeta + { + public static DB6Meta Instance() + { + byte[] arraySizes = { 1, 1, 1, 1, 1 }; + object[] fieldDefaults = { "", 0, 0, 0, 0 }; + return new DB6Meta(-1, arraySizes, fieldDefaults); + } + } + + struct AnimKitBoneSetAliasMeta + { + public static DB6Meta Instance() + { + byte[] arraySizes = { 1, 1 }; + object[] fieldDefaults = { 0, 0 }; + return new DB6Meta(-1, arraySizes, fieldDefaults); + } + } + + struct AnimKitConfigMeta + { + public static DB6Meta Instance() + { + byte[] arraySizes = { 1 }; + object[] fieldDefaults = { 0 }; + return new DB6Meta(-1, arraySizes, fieldDefaults); + } + } + + struct AnimKitConfigBoneSetMeta + { + public static DB6Meta Instance() + { + byte[] arraySizes = { 1, 1, 1 }; + object[] fieldDefaults = { 0, 0, 0 }; + return new DB6Meta(-1, arraySizes, fieldDefaults); + } + } + + struct AnimKitPriorityMeta + { + public static DB6Meta Instance() + { + byte[] arraySizes = { 1 }; + object[] fieldDefaults = { 0 }; + return new DB6Meta(-1, arraySizes, fieldDefaults); + } + } + + struct AnimKitReplacementMeta + { + public static DB6Meta Instance() + { + byte[] arraySizes = { 1, 1, 1, 1, 1 }; + object[] fieldDefaults = { 0, 0, 0, 0, 0 }; + return new DB6Meta(4, arraySizes, fieldDefaults); + } + } + + struct AnimKitSegmentMeta + { + public static DB6Meta Instance() + { + byte[] arraySizes = { 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1 }; + object[] fieldDefaults = { 0, 0, 0, 0.0f, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }; + return new DB6Meta(-1, arraySizes, fieldDefaults); + } + } + + struct AnimReplacementMeta + { + public static DB6Meta Instance() + { + byte[] arraySizes = { 1, 1, 1, 1, 1 }; + object[] fieldDefaults = { 0, 0, 0, 0, 0 }; + return new DB6Meta(4, arraySizes, fieldDefaults); + } + } + + struct AnimReplacementSetMeta + { + public static DB6Meta Instance() + { + byte[] arraySizes = { 1 }; + object[] fieldDefaults = { 0 }; + return new DB6Meta(-1, arraySizes, fieldDefaults); + } + } + + struct AnimationDataMeta + { + public static DB6Meta Instance() + { + byte[] arraySizes = { 1, 1, 1, 1, 1, 1 }; + object[] fieldDefaults = { 0, "", 0, 0, 0, 0 }; + return new DB6Meta(0, arraySizes, fieldDefaults); + } + } + + struct AreaFarClipOverrideMeta + { + public static DB6Meta Instance() + { + byte[] arraySizes = { 1, 1, 1, 1, 1 }; + object[] fieldDefaults = { 0, 0.0f, 0.0f, 0, 0 }; + return new DB6Meta(4, arraySizes, fieldDefaults); + } + } + + struct AreaGroupMemberMeta + { + public static DB6Meta Instance() + { + byte[] arraySizes = { 1, 1 }; + object[] fieldDefaults = { 0, 0 }; + return new DB6Meta(-1, arraySizes, fieldDefaults); + } + } + + struct AreaPOIMeta + { + public static DB6Meta Instance() + { + byte[] arraySizes = { 1, 2, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1 }; + object[] fieldDefaults = { 0, 0.0f, "", "", 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }; + return new DB6Meta(-1, arraySizes, fieldDefaults); + } + } + + struct AreaPOIStateMeta + { + public static DB6Meta Instance() + { + byte[] arraySizes = { 1, 1, 1, 1, 1 }; + object[] fieldDefaults = { "", 0, 0, 0, 0 }; + return new DB6Meta(-1, arraySizes, fieldDefaults); + } + } + + struct AreaTableMeta + { + public static DB6Meta Instance() + { + byte[] arraySizes = { 2, 1, 1, 1, 1, 1, 1, 1, 1, 1, 4, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1 }; + object[] fieldDefaults = { 0, "", 0.0f, "", 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }; + return new DB6Meta(-1, arraySizes, fieldDefaults); + } + } + + struct AreaTriggerMeta + { + public static DB6Meta Instance() + { + byte[] arraySizes = { 3, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1 }; + object[] fieldDefaults = { 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0, 0, 0, 0, 0, 0, 0, 0, 0 }; + return new DB6Meta(14, arraySizes, fieldDefaults); + } + } + + struct AreaTriggerActionSetMeta + { + public static DB6Meta Instance() + { + byte[] arraySizes = { 1 }; + object[] fieldDefaults = { 0 }; + return new DB6Meta(-1, arraySizes, fieldDefaults); + } + } + + struct AreaTriggerBoxMeta + { + public static DB6Meta Instance() + { + byte[] arraySizes = { 3 }; + object[] fieldDefaults = { 0.0f }; + return new DB6Meta(-1, arraySizes, fieldDefaults); + } + } + + struct AreaTriggerCylinderMeta + { + public static DB6Meta Instance() + { + byte[] arraySizes = { 1, 1, 1 }; + object[] fieldDefaults = { 0.0f, 0.0f, 0.0f }; + return new DB6Meta(-1, arraySizes, fieldDefaults); + } + } + + struct AreaTriggerSphereMeta + { + public static DB6Meta Instance() + { + byte[] arraySizes = { 1 }; + object[] fieldDefaults = { 0.0f }; + return new DB6Meta(-1, arraySizes, fieldDefaults); + } + } + + struct ArmorLocationMeta + { + public static DB6Meta Instance() + { + byte[] arraySizes = { 1, 1, 1, 1, 1 }; + object[] fieldDefaults = { 0.0f, 0.0f, 0.0f, 0.0f, 0.0f }; + return new DB6Meta(-1, arraySizes, fieldDefaults); + } + } + + struct ArtifactMeta + { + public static DB6Meta Instance() + { + byte[] arraySizes = { 1, 1, 1, 1, 1, 1, 1, 1, 1, 1 }; + object[] fieldDefaults = { "", 0, 0, 0, 0, 0, 0, 0, 0, 0 }; + return new DB6Meta(-1, arraySizes, fieldDefaults); + } + } + + struct ArtifactAppearanceMeta + { + public static DB6Meta Instance() + { + byte[] arraySizes = { 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1 }; + object[] fieldDefaults = { "", 0, 0.0f, 0.0f, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }; + return new DB6Meta(11, arraySizes, fieldDefaults); + } + } + + struct ArtifactAppearanceSetMeta + { + public static DB6Meta Instance() + { + byte[] arraySizes = { 1, 1, 1, 1, 1, 1, 1, 1, 1 }; + object[] fieldDefaults = { "", "", 0, 0, 0, 0, 0, 0, 0 }; + return new DB6Meta(8, arraySizes, fieldDefaults); + } + } + + struct ArtifactCategoryMeta + { + public static DB6Meta Instance() + { + byte[] arraySizes = { 1, 1 }; + object[] fieldDefaults = { 0, 0 }; + return new DB6Meta(-1, arraySizes, fieldDefaults); + } + } + + struct ArtifactPowerMeta + { + public static DB6Meta Instance() + { + byte[] arraySizes = { 2, 1, 1, 1, 1, 1, 1 }; + object[] fieldDefaults = { 0.0f, 0, 0, 0, 0, 0, 0 }; + return new DB6Meta(5, arraySizes, fieldDefaults); + } + } + + struct ArtifactPowerLinkMeta + { + public static DB6Meta Instance() + { + byte[] arraySizes = { 1, 1 }; + object[] fieldDefaults = { 0, 0 }; + return new DB6Meta(-1, arraySizes, fieldDefaults); + } + } + + struct ArtifactPowerPickerMeta + { + public static DB6Meta Instance() + { + byte[] arraySizes = { 1 }; + object[] fieldDefaults = { 0 }; + return new DB6Meta(-1, arraySizes, fieldDefaults); + } + } + + struct ArtifactPowerRankMeta + { + public static DB6Meta Instance() + { + byte[] arraySizes = { 1, 1, 1, 1, 1 }; + object[] fieldDefaults = { 0, 0.0f, 0, 0, 0 }; + return new DB6Meta(-1, arraySizes, fieldDefaults); + } + } + + struct ArtifactQuestXPMeta + { + public static DB6Meta Instance() + { + byte[] arraySizes = { 10 }; + object[] fieldDefaults = { 0 }; + return new DB6Meta(-1, arraySizes, fieldDefaults); + } + } + + struct ArtifactTierMeta + { + public static DB6Meta Instance() + { + byte[] arraySizes = { 1, 1, 1, 1 }; + object[] fieldDefaults = { 0, 0, 0, 0 }; + return new DB6Meta(-1, arraySizes, fieldDefaults); + } + } + + struct ArtifactUnlockMeta + { + public static DB6Meta Instance() + { + byte[] arraySizes = { 1, 1, 1, 1, 1 }; + object[] fieldDefaults = { 0, 0, 0, 0, 0 }; + return new DB6Meta(-1, arraySizes, fieldDefaults); + } + } + + struct AuctionHouseMeta + { + public static DB6Meta Instance() + { + byte[] arraySizes = { 1, 1, 1, 1 }; + object[] fieldDefaults = { "", 0, 0, 0 }; + return new DB6Meta(-1, arraySizes, fieldDefaults); + } + } + + struct BankBagSlotPricesMeta + { + public static DB6Meta Instance() + { + byte[] arraySizes = { 1 }; + object[] fieldDefaults = { 0 }; + return new DB6Meta(-1, arraySizes, fieldDefaults); + } + } + + struct BannedAddOnsMeta + { + public static DB6Meta Instance() + { + byte[] arraySizes = { 1, 1, 1 }; + object[] fieldDefaults = { "", "", 0 }; + return new DB6Meta(-1, arraySizes, fieldDefaults); + } + } + + struct BarberShopStyleMeta + { + public static DB6Meta Instance() + { + byte[] arraySizes = { 1, 1, 1, 1, 1, 1, 1, 1 }; + object[] fieldDefaults = { "", "", 0.0f, 0, 0, 0, 0, 0 }; + return new DB6Meta(7, arraySizes, fieldDefaults); + } + } + + struct BattlePetAbilityMeta + { + public static DB6Meta Instance() + { + byte[] arraySizes = { 1, 1, 1, 1, 1, 1, 1 }; + object[] fieldDefaults = { 0, "", "", 0, 0, 0, 0 }; + return new DB6Meta(-1, arraySizes, fieldDefaults); + } + } + + struct BattlePetAbilityEffectMeta + { + public static DB6Meta Instance() + { + byte[] arraySizes = { 1, 1, 1, 1, 6, 1, 1 }; + object[] fieldDefaults = { 0, 0, 0, 0, 0, 0, 0 }; + return new DB6Meta(6, arraySizes, fieldDefaults); + } + } + + struct BattlePetAbilityStateMeta + { + public static DB6Meta Instance() + { + byte[] arraySizes = { 1, 1, 1 }; + object[] fieldDefaults = { 0, 0, 0 }; + return new DB6Meta(-1, arraySizes, fieldDefaults); + } + } + + struct BattlePetAbilityTurnMeta + { + public static DB6Meta Instance() + { + byte[] arraySizes = { 1, 1, 1, 1, 1, 1 }; + object[] fieldDefaults = { 0, 0, 0, 0, 0, 0 }; + return new DB6Meta(5, arraySizes, fieldDefaults); + } + } + + struct BattlePetBreedQualityMeta + { + public static DB6Meta Instance() + { + byte[] arraySizes = { 1, 1 }; + object[] fieldDefaults = { 0.0f, 0 }; + return new DB6Meta(-1, arraySizes, fieldDefaults); + } + } + + struct BattlePetBreedStateMeta + { + public static DB6Meta Instance() + { + byte[] arraySizes = { 1, 1, 1 }; + object[] fieldDefaults = { 0, 0, 0 }; + return new DB6Meta(-1, arraySizes, fieldDefaults); + } + } + + struct BattlePetEffectPropertiesMeta + { + public static DB6Meta Instance() + { + byte[] arraySizes = { 6, 1, 6 }; + object[] fieldDefaults = { "", 0, 0 }; + return new DB6Meta(-1, arraySizes, fieldDefaults); + } + } + + struct BattlePetNPCTeamMemberMeta + { + public static DB6Meta Instance() + { + byte[] arraySizes = { 1 }; + object[] fieldDefaults = { "" }; + return new DB6Meta(-1, arraySizes, fieldDefaults); + } + } + + struct BattlePetSpeciesMeta + { + public static DB6Meta Instance() + { + byte[] arraySizes = { 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1 }; + object[] fieldDefaults = { 0, 0, 0, "", "", 0, 0, 0, 0, 0, 0 }; + return new DB6Meta(8, arraySizes, fieldDefaults); + } + } + + struct BattlePetSpeciesStateMeta + { + public static DB6Meta Instance() + { + byte[] arraySizes = { 1, 1, 1 }; + object[] fieldDefaults = { 0, 0, 0 }; + return new DB6Meta(-1, arraySizes, fieldDefaults); + } + } + + struct BattlePetSpeciesXAbilityMeta + { + public static DB6Meta Instance() + { + byte[] arraySizes = { 1, 1, 1, 1 }; + object[] fieldDefaults = { 0, 0, 0, 0 }; + return new DB6Meta(-1, arraySizes, fieldDefaults); + } + } + + struct BattlePetStateMeta + { + public static DB6Meta Instance() + { + byte[] arraySizes = { 1, 1, 1 }; + object[] fieldDefaults = { "", 0, 0 }; + return new DB6Meta(-1, arraySizes, fieldDefaults); + } + } + + struct BattlePetVisualMeta + { + public static DB6Meta Instance() + { + byte[] arraySizes = { 1, 1, 1, 1, 1, 1, 1 }; + object[] fieldDefaults = { 0, "", 0, 0, 0, 0, 0 }; + return new DB6Meta(-1, arraySizes, fieldDefaults); + } + } + + struct BattlemasterListMeta + { + public static DB6Meta Instance() + { + byte[] arraySizes = { 1, 1, 1, 1, 1, 16, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1 }; + object[] fieldDefaults = { "", 0, "", "", "", 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }; + return new DB6Meta(-1, arraySizes, fieldDefaults); + } + } + + struct BeamEffectMeta + { + public static DB6Meta Instance() + { + byte[] arraySizes = { 1, 1, 1, 1, 1, 1, 1, 1, 1, 1 }; + object[] fieldDefaults = { 0, 0.0f, 0.0f, 0, 0, 0, 0, 0, 0, 0 }; + return new DB6Meta(-1, arraySizes, fieldDefaults); + } + } + + struct BoneWindModifierModelMeta + { + public static DB6Meta Instance() + { + byte[] arraySizes = { 1, 1 }; + object[] fieldDefaults = { 0, 0 }; + return new DB6Meta(-1, arraySizes, fieldDefaults); + } + } + + struct BoneWindModifiersMeta + { + public static DB6Meta Instance() + { + byte[] arraySizes = { 3, 1 }; + object[] fieldDefaults = { 0.0f, 0.0f }; + return new DB6Meta(-1, arraySizes, fieldDefaults); + } + } + + struct BountyMeta + { + public static DB6Meta Instance() + { + byte[] arraySizes = { 1, 1, 1, 1 }; + object[] fieldDefaults = { 0, 0, 0, 0 }; + return new DB6Meta(-1, arraySizes, fieldDefaults); + } + } + + struct BountySetMeta + { + public static DB6Meta Instance() + { + byte[] arraySizes = { 1, 1 }; + object[] fieldDefaults = { 0, 0 }; + return new DB6Meta(-1, arraySizes, fieldDefaults); + } + } + + struct BroadcastTextMeta + { + public static DB6Meta Instance() + { + byte[] arraySizes = { 1, 1, 3, 3, 1, 1, 1, 2, 1 }; + object[] fieldDefaults = { "", "", 0, 0, 0, 0, 0, 0, 0 }; + return new DB6Meta(-1, arraySizes, fieldDefaults); + } + } + + struct CameraEffectMeta + { + public static DB6Meta Instance() + { + byte[] arraySizes = { 1 }; + object[] fieldDefaults = { 0 }; + return new DB6Meta(-1, arraySizes, fieldDefaults); + } + } + + struct CameraEffectEntryMeta + { + public static DB6Meta Instance() + { + byte[] arraySizes = { 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1 }; + object[] fieldDefaults = { 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0, 0, 0, 0, 0, 0, 0, 0 }; + return new DB6Meta(-1, arraySizes, fieldDefaults); + } + } + + struct CameraModeMeta + { + public static DB6Meta Instance() + { + byte[] arraySizes = { 3, 3, 1, 1, 1, 1, 1, 1, 1, 1, 1 }; + object[] fieldDefaults = { 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0, 0, 0, 0, 0, 0 }; + return new DB6Meta(-1, arraySizes, fieldDefaults); + } + } + + struct CastableRaidBuffsMeta + { + public static DB6Meta Instance() + { + byte[] arraySizes = { 1, 1 }; + object[] fieldDefaults = { 0, 0 }; + return new DB6Meta(-1, arraySizes, fieldDefaults); + } + } + + struct CelestialBodyMeta + { + public static DB6Meta Instance() + { + byte[] arraySizes = { 1, 1, 2, 1, 1, 2, 2, 2, 1, 2, 1, 3, 1, 1, 1 }; + object[] fieldDefaults = { 0, 0, 0, 0, 0, 0, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0, 0 }; + return new DB6Meta(14, arraySizes, fieldDefaults); + } + } + + struct Cfg_CategoriesMeta + { + public static DB6Meta Instance() + { + byte[] arraySizes = { 1, 1, 1, 1, 1 }; + object[] fieldDefaults = { "", 0, 0, 0, 0 }; + return new DB6Meta(-1, arraySizes, fieldDefaults); + } + } + + struct Cfg_ConfigsMeta + { + public static DB6Meta Instance() + { + byte[] arraySizes = { 1, 1, 1, 1 }; + object[] fieldDefaults = { 0.0f, 0, 0, 0 }; + return new DB6Meta(-1, arraySizes, fieldDefaults); + } + } + + struct Cfg_RegionsMeta + { + public static DB6Meta Instance() + { + byte[] arraySizes = { 1, 1, 1, 1, 1 }; + object[] fieldDefaults = { "", 0, 0, 0, 0 }; + return new DB6Meta(-1, arraySizes, fieldDefaults); + } + } + + struct CharBaseInfoMeta + { + public static DB6Meta Instance() + { + byte[] arraySizes = { 1, 1 }; + object[] fieldDefaults = { 0, 0 }; + return new DB6Meta(-1, arraySizes, fieldDefaults); + } + } + + struct CharBaseSectionMeta + { + public static DB6Meta Instance() + { + byte[] arraySizes = { 1, 1, 1 }; + object[] fieldDefaults = { 0, 0, 0 }; + return new DB6Meta(-1, arraySizes, fieldDefaults); + } + } + + struct CharComponentTextureLayoutsMeta + { + public static DB6Meta Instance() + { + byte[] arraySizes = { 1, 1 }; + object[] fieldDefaults = { 0, 0 }; + return new DB6Meta(-1, arraySizes, fieldDefaults); + } + } + + struct CharComponentTextureSectionsMeta + { + public static DB6Meta Instance() + { + byte[] arraySizes = { 1, 1, 1, 1, 1, 1, 1 }; + object[] fieldDefaults = { 0, 0, 0, 0, 0, 0, 0 }; + return new DB6Meta(-1, arraySizes, fieldDefaults); + } + } + + struct CharHairGeosetsMeta + { + public static DB6Meta Instance() + { + byte[] arraySizes = { 1, 1, 1, 1, 1, 1, 1, 1, 1, 1 }; + object[] fieldDefaults = { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }; + return new DB6Meta(-1, arraySizes, fieldDefaults); + } + } + + struct CharSectionsMeta + { + public static DB6Meta Instance() + { + byte[] arraySizes = { 3, 1, 1, 1, 1, 1, 1 }; + object[] fieldDefaults = { 0, 0, 0, 0, 0, 0, 0 }; + return new DB6Meta(-1, arraySizes, fieldDefaults); + } + } + + struct CharShipmentMeta + { + public static DB6Meta Instance() + { + byte[] arraySizes = { 1, 1, 1, 1, 1, 1, 1, 1, 1 }; + object[] fieldDefaults = { 0, 0, 0, 0, 0, 0, 0, 0, 0 }; + return new DB6Meta(-1, arraySizes, fieldDefaults); + } + } + + struct CharShipmentContainerMeta + { + public static DB6Meta Instance() + { + byte[] arraySizes = { 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1 }; + object[] fieldDefaults = { "", "", 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }; + return new DB6Meta(-1, arraySizes, fieldDefaults); + } + } + + struct CharStartOutfitMeta + { + public static DB6Meta Instance() + { + byte[] arraySizes = { 24, 1, 1, 1, 1, 1, 1 }; + object[] fieldDefaults = { 0, 0, 0, 0, 0, 0, 0 }; + return new DB6Meta(-1, arraySizes, fieldDefaults); + } + } + + struct CharTitlesMeta + { + public static DB6Meta Instance() + { + byte[] arraySizes = { 1, 1, 1, 1 }; + object[] fieldDefaults = { "", "", 0, 0 }; + return new DB6Meta(-1, arraySizes, fieldDefaults); + } + } + + struct CharacterFaceBoneSetMeta + { + public static DB6Meta Instance() + { + byte[] arraySizes = { 1, 1, 1, 1 }; + object[] fieldDefaults = { 0, 0, 0, 0 }; + return new DB6Meta(-1, arraySizes, fieldDefaults); + } + } + + struct CharacterFacialHairStylesMeta + { + public static DB6Meta Instance() + { + byte[] arraySizes = { 5, 1, 1, 1 }; + object[] fieldDefaults = { 0, 0, 0, 0 }; + return new DB6Meta(-1, arraySizes, fieldDefaults); + } + } + + struct CharacterLoadoutMeta + { + public static DB6Meta Instance() + { + byte[] arraySizes = { 1, 1, 1 }; + object[] fieldDefaults = { 0, 0, 0 }; + return new DB6Meta(-1, arraySizes, fieldDefaults); + } + } + + struct CharacterLoadoutItemMeta + { + public static DB6Meta Instance() + { + byte[] arraySizes = { 1, 1 }; + object[] fieldDefaults = { 0, 0 }; + return new DB6Meta(-1, arraySizes, fieldDefaults); + } + } + + struct ChatChannelsMeta + { + public static DB6Meta Instance() + { + byte[] arraySizes = { 1, 1, 1, 1 }; + object[] fieldDefaults = { 0, "", "", 0 }; + return new DB6Meta(-1, arraySizes, fieldDefaults); + } + } + + struct ChatProfanityMeta + { + public static DB6Meta Instance() + { + byte[] arraySizes = { 1, 1 }; + object[] fieldDefaults = { "", 0 }; + return new DB6Meta(-1, arraySizes, fieldDefaults); + } + } + + struct ChrClassRaceSexMeta + { + public static DB6Meta Instance() + { + byte[] arraySizes = { 1, 1, 1, 1, 1, 1 }; + object[] fieldDefaults = { 0, 0, 0, 0, 0, 0 }; + return new DB6Meta(-1, arraySizes, fieldDefaults); + } + } + + struct ChrClassTitleMeta + { + public static DB6Meta Instance() + { + byte[] arraySizes = { 1, 1, 1 }; + object[] fieldDefaults = { "", "", 0 }; + return new DB6Meta(-1, arraySizes, fieldDefaults); + } + } + + struct ChrClassUIDisplayMeta + { + public static DB6Meta Instance() + { + byte[] arraySizes = { 1, 1, 1 }; + object[] fieldDefaults = { 0, 0, 0 }; + return new DB6Meta(-1, arraySizes, fieldDefaults); + } + } + + struct ChrClassVillainMeta + { + public static DB6Meta Instance() + { + byte[] arraySizes = { 1, 1, 1 }; + object[] fieldDefaults = { "", 0, 0 }; + return new DB6Meta(-1, arraySizes, fieldDefaults); + } + } + + struct ChrClassesMeta + { + public static DB6Meta Instance() + { + byte[] arraySizes = { 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1 }; + object[] fieldDefaults = { "", "", "", "", "", 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }; + return new DB6Meta(18, arraySizes, fieldDefaults); + } + } + + struct ChrClassesXPowerTypesMeta + { + public static DB6Meta Instance() + { + byte[] arraySizes = { 1, 1 }; + object[] fieldDefaults = { 0, 0 }; + return new DB6Meta(-1, arraySizes, fieldDefaults); + } + } + + struct ChrRacesMeta + { + public static DB6Meta Instance() + { + byte[] arraySizes = { 1, 1, 1, 1, 1, 1, 2, 1, 1, 1, 3, 3, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 3 }; + object[] fieldDefaults = { 0, "", "", "", "", "", "", "", 0, 0, 0.0f, 0.0f, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }; + return new DB6Meta(-1, arraySizes, fieldDefaults); + } + } + + struct ChrSpecializationMeta + { + public static DB6Meta Instance() + { + byte[] arraySizes = { 2, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1 }; + object[] fieldDefaults = { 0, "", "", "", 0, 0, 0, 0, 0, 0, 0, 0, 0 }; + return new DB6Meta(9, arraySizes, fieldDefaults); + } + } + + struct ChrUpgradeBucketMeta + { + public static DB6Meta Instance() + { + byte[] arraySizes = { 1, 1, 1 }; + object[] fieldDefaults = { 0, 0, 0 }; + return new DB6Meta(2, arraySizes, fieldDefaults); + } + } + + struct ChrUpgradeBucketSpellMeta + { + public static DB6Meta Instance() + { + byte[] arraySizes = { 1, 1 }; + object[] fieldDefaults = { 0, 0 }; + return new DB6Meta(-1, arraySizes, fieldDefaults); + } + } + + struct ChrUpgradeTierMeta + { + public static DB6Meta Instance() + { + byte[] arraySizes = { 1, 1, 1, 1 }; + object[] fieldDefaults = { "", 0, 0, 0 }; + return new DB6Meta(3, arraySizes, fieldDefaults); + } + } + + struct CinematicCameraMeta + { + public static DB6Meta Instance() + { + byte[] arraySizes = { 1, 3, 1, 1 }; + object[] fieldDefaults = { 0, 0.0f, 0.0f, 0 }; + return new DB6Meta(-1, arraySizes, fieldDefaults); + } + } + + struct CinematicSequencesMeta + { + public static DB6Meta Instance() + { + byte[] arraySizes = { 1, 8 }; + object[] fieldDefaults = { 0, 0 }; + return new DB6Meta(-1, arraySizes, fieldDefaults); + } + } + + struct CloakDampeningMeta + { + public static DB6Meta Instance() + { + byte[] arraySizes = { 5, 5, 2, 2, 1, 1, 1 }; + object[] fieldDefaults = { 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f }; + return new DB6Meta(-1, arraySizes, fieldDefaults); + } + } + + struct CombatConditionMeta + { + public static DB6Meta Instance() + { + byte[] arraySizes = { 1, 1, 1, 2, 2, 2, 2, 1, 2, 2, 1 }; + object[] fieldDefaults = { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }; + return new DB6Meta(-1, arraySizes, fieldDefaults); + } + } + + struct ComponentModelFileDataMeta + { + public static DB6Meta Instance() + { + byte[] arraySizes = { 1, 1, 1, 1 }; + object[] fieldDefaults = { 0, 0, 0, 0 }; + return new DB6Meta(-1, arraySizes, fieldDefaults); + } + } + + struct ComponentTextureFileDataMeta + { + public static DB6Meta Instance() + { + byte[] arraySizes = { 1, 1, 1 }; + object[] fieldDefaults = { 0, 0, 0 }; + return new DB6Meta(-1, arraySizes, fieldDefaults); + } + } + + struct ContributionMeta + { + public static DB6Meta Instance() + { + byte[] arraySizes = { 1, 1, 1, 1, 4, 1 }; + object[] fieldDefaults = { 0, 0, "", "", 0, 0 }; + return new DB6Meta(0, arraySizes, fieldDefaults); + } + } + + struct ConversationLineMeta + { + public static DB6Meta Instance() + { + byte[] arraySizes = { 1, 1, 1, 1, 1, 1, 1, 1 }; + object[] fieldDefaults = { 0, 0, 0, 0, 0, 0, 0, 0 }; + return new DB6Meta(-1, arraySizes, fieldDefaults); + } + } + + struct CreatureMeta + { + public static DB6Meta Instance() + { + byte[] arraySizes = { 3, 1, 4, 4, 1, 1, 1, 1, 1, 1, 1, 1 }; + object[] fieldDefaults = { 0, 0, 0, 0.0f, "", "", "", "", 0, 0, 0, 0 }; + return new DB6Meta(-1, arraySizes, fieldDefaults); + } + } + + struct CreatureDifficultyMeta + { + public static DB6Meta Instance() + { + byte[] arraySizes = { 1, 7, 1, 1, 1, 1 }; + object[] fieldDefaults = { 0, 0, 0, 0, 0, 0 }; + return new DB6Meta(-1, arraySizes, fieldDefaults); + } + } + + struct CreatureDispXUiCameraMeta + { + public static DB6Meta Instance() + { + byte[] arraySizes = { 1, 1 }; + object[] fieldDefaults = { 0, 0 }; + return new DB6Meta(-1, arraySizes, fieldDefaults); + } + } + + struct CreatureDisplayInfoMeta + { + public static DB6Meta Instance() + { + byte[] arraySizes = { 1, 1, 1, 1, 1, 1, 1, 1, 3, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1 }; + object[] fieldDefaults = { 0, 0.0f, 0, 0, 0, 0, 0, 0, 0, 0, 255, 0, 0.0f, 0, 0, 0, 0, 0, 0, 255, 0, 1.0f, 0 }; + return new DB6Meta(0, arraySizes, fieldDefaults); + } + } + + struct CreatureDisplayInfoCondMeta + { + public static DB6Meta Instance() + { + byte[] arraySizes = { 1, 2, 2, 2, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 3 }; + object[] fieldDefaults = { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }; + return new DB6Meta(-1, arraySizes, fieldDefaults); + } + } + + struct CreatureDisplayInfoEvtMeta + { + public static DB6Meta Instance() + { + byte[] arraySizes = { 1, 1, 1 }; + object[] fieldDefaults = { 0, 0, 0 }; + return new DB6Meta(-1, arraySizes, fieldDefaults); + } + } + + struct CreatureDisplayInfoExtraMeta + { + public static DB6Meta Instance() + { + byte[] arraySizes = { 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 3, 1 }; + object[] fieldDefaults = { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }; + return new DB6Meta(-1, arraySizes, fieldDefaults); + } + } + + struct CreatureDisplayInfoTrnMeta + { + public static DB6Meta Instance() + { + byte[] arraySizes = { 1, 1, 1, 1 }; + object[] fieldDefaults = { 0, 0, 0, 0.0f }; + return new DB6Meta(-1, arraySizes, fieldDefaults); + } + } + + struct CreatureFamilyMeta + { + public static DB6Meta Instance() + { + byte[] arraySizes = { 1, 1, 1, 1, 2, 1, 1, 1, 1 }; + object[] fieldDefaults = { 0.0f, 0.0f, "", 0, 0, 0, 0, 0, 0 }; + return new DB6Meta(-1, arraySizes, fieldDefaults); + } + } + + struct CreatureImmunitiesMeta + { + public static DB6Meta Instance() + { + byte[] arraySizes = { 2, 1, 1, 1, 1, 1, 1, 8, 16 }; + object[] fieldDefaults = { 0, 0, 0, 0, 0, 0, 0, 0, 0 }; + return new DB6Meta(-1, arraySizes, fieldDefaults); + } + } + + struct CreatureModelDataMeta + { + public static DB6Meta Instance() + { + byte[] arraySizes = { 1, 1, 1, 1, 1, 1, 1, 6, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1 }; + object[] fieldDefaults = { 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }; + return new DB6Meta(-1, arraySizes, fieldDefaults); + } + } + + struct CreatureMovementInfoMeta + { + public static DB6Meta Instance() + { + byte[] arraySizes = { 1 }; + object[] fieldDefaults = { 0.0f }; + return new DB6Meta(-1, arraySizes, fieldDefaults); + } + } + + struct CreatureSoundDataMeta + { + public static DB6Meta Instance() + { + byte[] arraySizes = { 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 5, 4, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1 }; + object[] fieldDefaults = { 0.0f, 0.0f, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }; + return new DB6Meta(-1, arraySizes, fieldDefaults); + } + } + + struct CreatureTypeMeta + { + public static DB6Meta Instance() + { + byte[] arraySizes = { 1, 1 }; + object[] fieldDefaults = { "", 0 }; + return new DB6Meta(-1, arraySizes, fieldDefaults); + } + } + + struct CreatureXContributionMeta + { + public static DB6Meta Instance() + { + byte[] arraySizes = { 1, 1, 1 }; + object[] fieldDefaults = { 0, 0, 0 }; + return new DB6Meta(0, arraySizes, fieldDefaults); + } + } + + struct CriteriaMeta + { + public static DB6Meta Instance() + { + byte[] arraySizes = { 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1 }; + object[] fieldDefaults = { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }; + return new DB6Meta(-1, arraySizes, fieldDefaults); + } + } + + struct CriteriaTreeMeta + { + public static DB6Meta Instance() + { + byte[] arraySizes = { 1, 1, 1, 1, 1, 1, 1 }; + object[] fieldDefaults = { 0, "", 0, 0, 0, 0, 0 }; + return new DB6Meta(-1, arraySizes, fieldDefaults); + } + } + + struct CriteriaTreeXEffectMeta + { + public static DB6Meta Instance() + { + byte[] arraySizes = { 1, 1 }; + object[] fieldDefaults = { 0, 0 }; + return new DB6Meta(-1, arraySizes, fieldDefaults); + } + } + + struct CurrencyCategoryMeta + { + public static DB6Meta Instance() + { + byte[] arraySizes = { 1, 1, 1 }; + object[] fieldDefaults = { "", 0, 0 }; + return new DB6Meta(-1, arraySizes, fieldDefaults); + } + } + + struct CurrencyTypesMeta + { + public static DB6Meta Instance() + { + byte[] arraySizes = { 1, 2, 1, 1, 1, 1, 1, 1, 1, 1 }; + object[] fieldDefaults = { "", "", 0, 0, 0, "", 0, 0, 0, 0 }; + return new DB6Meta(-1, arraySizes, fieldDefaults); + } + } + + struct CurveMeta + { + public static DB6Meta Instance() + { + byte[] arraySizes = { 1, 1 }; + object[] fieldDefaults = { 0, 0 }; + return new DB6Meta(-1, arraySizes, fieldDefaults); + } + } + + struct CurvePointMeta + { + public static DB6Meta Instance() + { + byte[] arraySizes = { 2, 1, 1 }; + object[] fieldDefaults = { 0.0f, 0, 0 }; + return new DB6Meta(-1, arraySizes, fieldDefaults); + } + } + + struct DeathThudLookupsMeta + { + public static DB6Meta Instance() + { + byte[] arraySizes = { 1, 1, 1, 1 }; + object[] fieldDefaults = { 0, 0, 0, 0 }; + return new DB6Meta(-1, arraySizes, fieldDefaults); + } + } + + struct DecalPropertiesMeta + { + public static DB6Meta Instance() + { + byte[] arraySizes = { 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1 }; + object[] fieldDefaults = { 0, 0, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0, 0, 0, 0, 0, 0, 0 }; + return new DB6Meta(0, arraySizes, fieldDefaults); + } + } + + struct DeclinedWordMeta + { + public static DB6Meta Instance() + { + byte[] arraySizes = { 1, 1 }; + object[] fieldDefaults = { "", 0 }; + return new DB6Meta(1, arraySizes, fieldDefaults); + } + } + + struct DeclinedWordCasesMeta + { + public static DB6Meta Instance() + { + byte[] arraySizes = { 1, 1, 1 }; + object[] fieldDefaults = { "", 0, 0 }; + return new DB6Meta(-1, arraySizes, fieldDefaults); + } + } + + struct DestructibleModelDataMeta + { + public static DB6Meta Instance() + { + byte[] arraySizes = { 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1 }; + object[] fieldDefaults = { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }; + return new DB6Meta(-1, arraySizes, fieldDefaults); + } + } + + struct DeviceBlacklistMeta + { + public static DB6Meta Instance() + { + byte[] arraySizes = { 1, 1 }; + object[] fieldDefaults = { 0, 0 }; + return new DB6Meta(-1, arraySizes, fieldDefaults); + } + } + + struct DeviceDefaultSettingsMeta + { + public static DB6Meta Instance() + { + byte[] arraySizes = { 1, 1, 1 }; + object[] fieldDefaults = { 0, 0, 0 }; + return new DB6Meta(-1, arraySizes, fieldDefaults); + } + } + + struct DifficultyMeta + { + public static DB6Meta Instance() + { + byte[] arraySizes = { 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1 }; + object[] fieldDefaults = { "", 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }; + return new DB6Meta(-1, arraySizes, fieldDefaults); + } + } + + struct DissolveEffectMeta + { + public static DB6Meta Instance() + { + byte[] arraySizes = { 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1 }; + object[] fieldDefaults = { 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0, 0, 0, 0 }; + return new DB6Meta(-1, arraySizes, fieldDefaults); + } + } + + struct DriverBlacklistMeta + { + public static DB6Meta Instance() + { + byte[] arraySizes = { 1, 1, 1, 1, 1, 1, 1 }; + object[] fieldDefaults = { 0, 0, 0, 0, 0, 0, 0 }; + return new DB6Meta(-1, arraySizes, fieldDefaults); + } + } + + struct DungeonEncounterMeta + { + public static DB6Meta Instance() + { + byte[] arraySizes = { 1, 1, 1, 1, 1, 1, 1, 1, 1 }; + object[] fieldDefaults = { "", 0, 0, 0, 0, 0, 0, 0, 0 }; + return new DB6Meta(6, arraySizes, fieldDefaults); + } + } + + struct DungeonMapMeta + { + public static DB6Meta Instance() + { + byte[] arraySizes = { 2, 2, 1, 1, 1, 1, 1, 1 }; + object[] fieldDefaults = { 0.0f, 0.0f, 0, 0, 0, 0, 0, 0 }; + return new DB6Meta(7, arraySizes, fieldDefaults); + } + } + + struct DungeonMapChunkMeta + { + public static DB6Meta Instance() + { + byte[] arraySizes = { 1, 1, 1, 1, 1 }; + object[] fieldDefaults = { 0.0f, 0, 0, 0, 0 }; + return new DB6Meta(-1, arraySizes, fieldDefaults); + } + } + + struct DurabilityCostsMeta + { + public static DB6Meta Instance() + { + byte[] arraySizes = { 21, 8 }; + object[] fieldDefaults = { 0, 0 }; + return new DB6Meta(-1, arraySizes, fieldDefaults); + } + } + + struct DurabilityQualityMeta + { + public static DB6Meta Instance() + { + byte[] arraySizes = { 1 }; + object[] fieldDefaults = { 0.0f }; + return new DB6Meta(-1, arraySizes, fieldDefaults); + } + } + + struct EdgeGlowEffectMeta + { + public static DB6Meta Instance() + { + byte[] arraySizes = { 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1 }; + object[] fieldDefaults = { 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0 }; + return new DB6Meta(-1, arraySizes, fieldDefaults); + } + } + + struct EmotesMeta + { + public static DB6Meta Instance() + { + byte[] arraySizes = { 1, 1, 1, 1, 1, 1, 1, 1, 1 }; + object[] fieldDefaults = { "", 0, 0, 0, 0, 0, 0, 0, 0 }; + return new DB6Meta(-1, arraySizes, fieldDefaults); + } + } + + struct EmotesTextMeta + { + public static DB6Meta Instance() + { + byte[] arraySizes = { 1, 1 }; + object[] fieldDefaults = { "", 0 }; + return new DB6Meta(-1, arraySizes, fieldDefaults); + } + } + + struct EmotesTextDataMeta + { + public static DB6Meta Instance() + { + byte[] arraySizes = { 1, 1, 1 }; + object[] fieldDefaults = { "", 0, 0 }; + return new DB6Meta(-1, arraySizes, fieldDefaults); + } + } + + struct EmotesTextSoundMeta + { + public static DB6Meta Instance() + { + byte[] arraySizes = { 1, 1, 1, 1, 1 }; + object[] fieldDefaults = { 0, 0, 0, 0, 0 }; + return new DB6Meta(-1, arraySizes, fieldDefaults); + } + } + + struct EnvironmentalDamageMeta + { + public static DB6Meta Instance() + { + byte[] arraySizes = { 1, 1 }; + object[] fieldDefaults = { 0, 0 }; + return new DB6Meta(-1, arraySizes, fieldDefaults); + } + } + + struct ExhaustionMeta + { + public static DB6Meta Instance() + { + byte[] arraySizes = { 1, 1, 1, 1, 1, 1, 1, 1 }; + object[] fieldDefaults = { 0, 0.0f, 0.0f, 0.0f, "", 0.0f, "", 0 }; + return new DB6Meta(7, arraySizes, fieldDefaults); + } + } + + struct FactionMeta + { + public static DB6Meta Instance() + { + byte[] arraySizes = { 1, 4, 4, 2, 1, 1, 4, 1, 4, 4, 1, 1, 2, 1, 1, 1 }; + object[] fieldDefaults = { 0, 0, 0, 0.0f, "", "", 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }; + return new DB6Meta(0, arraySizes, fieldDefaults); + } + } + + struct FactionGroupMeta + { + public static DB6Meta Instance() + { + byte[] arraySizes = { 1, 1, 1 }; + object[] fieldDefaults = { "", "", 0 }; + return new DB6Meta(-1, arraySizes, fieldDefaults); + } + } + + struct FactionTemplateMeta + { + public static DB6Meta Instance() + { + byte[] arraySizes = { 1, 1, 4, 4, 1, 1, 1 }; + object[] fieldDefaults = { 0, 0, 0, 0, 0, 0, 0 }; + return new DB6Meta(-1, arraySizes, fieldDefaults); + } + } + + struct FootprintTexturesMeta + { + public static DB6Meta Instance() + { + byte[] arraySizes = { 1, 1, 1 }; + object[] fieldDefaults = { 0, 0, 0 }; + return new DB6Meta(-1, arraySizes, fieldDefaults); + } + } + + struct FootstepTerrainLookupMeta + { + public static DB6Meta Instance() + { + byte[] arraySizes = { 1, 1, 1, 1 }; + object[] fieldDefaults = { 0, 0, 0, 0 }; + return new DB6Meta(-1, arraySizes, fieldDefaults); + } + } + + struct FriendshipRepReactionMeta + { + public static DB6Meta Instance() + { + byte[] arraySizes = { 1, 1, 1 }; + object[] fieldDefaults = { "", 0, 0 }; + return new DB6Meta(-1, arraySizes, fieldDefaults); + } + } + + struct FriendshipReputationMeta + { + public static DB6Meta Instance() + { + byte[] arraySizes = { 1, 1, 1, 1 }; + object[] fieldDefaults = { 0, "", 0, 0 }; + return new DB6Meta(3, arraySizes, fieldDefaults); + } + } + + struct FullScreenEffectMeta + { + public static DB6Meta Instance() + { + byte[] arraySizes = { 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1 }; + object[] fieldDefaults = { 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0, 0, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0, 0, 0, 0 }; + return new DB6Meta(-1, arraySizes, fieldDefaults); + } + } + + struct GMSurveyAnswersMeta + { + public static DB6Meta Instance() + { + byte[] arraySizes = { 1, 1, 1 }; + object[] fieldDefaults = { "", 0, 0 }; + return new DB6Meta(-1, arraySizes, fieldDefaults); + } + } + + struct GMSurveyCurrentSurveyMeta + { + public static DB6Meta Instance() + { + byte[] arraySizes = { 1 }; + object[] fieldDefaults = { 0 }; + return new DB6Meta(-1, arraySizes, fieldDefaults); + } + } + + struct GMSurveyQuestionsMeta + { + public static DB6Meta Instance() + { + byte[] arraySizes = { 1 }; + object[] fieldDefaults = { "" }; + return new DB6Meta(-1, arraySizes, fieldDefaults); + } + } + + struct GMSurveySurveysMeta + { + public static DB6Meta Instance() + { + byte[] arraySizes = { 15 }; + object[] fieldDefaults = { 0 }; + return new DB6Meta(-1, arraySizes, fieldDefaults); + } + } + + struct GameObjectArtKitMeta + { + public static DB6Meta Instance() + { + byte[] arraySizes = { 3, 4 }; + object[] fieldDefaults = { "", "" }; + return new DB6Meta(-1, arraySizes, fieldDefaults); + } + } + + struct GameObjectDiffAnimMapMeta + { + public static DB6Meta Instance() + { + byte[] arraySizes = { 1, 1, 1, 1 }; + object[] fieldDefaults = { 0, 0, 0, 0 }; + return new DB6Meta(-1, arraySizes, fieldDefaults); + } + } + + struct GameObjectDisplayInfoMeta + { + public static DB6Meta Instance() + { + byte[] arraySizes = { 1, 6, 1, 1, 1 }; + object[] fieldDefaults = { 0, 0.0f, 0.0f, 0.0f, 0 }; + return new DB6Meta(-1, arraySizes, fieldDefaults); + } + } + + struct GameObjectDisplayInfoXSoundKitMeta + { + public static DB6Meta Instance() + { + byte[] arraySizes = { 1, 1, 1 }; + object[] fieldDefaults = { 0, 0, 0 }; + return new DB6Meta(-1, arraySizes, fieldDefaults); + } + } + + struct GameObjectsMeta + { + public static DB6Meta Instance() + { + byte[] arraySizes = { 3, 4, 1, 8, 1, 1, 1, 1, 1, 1, 1, 1 }; + object[] fieldDefaults = { 0.0f, 0.0f, 0.0f, 0, "", 0, 0, 0, 0, 0, 0, 0 }; + return new DB6Meta(11, arraySizes, fieldDefaults); + } + } + + struct GameTipsMeta + { + public static DB6Meta Instance() + { + byte[] arraySizes = { 1, 1, 1, 1 }; + object[] fieldDefaults = { "", 0, 0, 0 }; + return new DB6Meta(-1, arraySizes, fieldDefaults); + } + } + + struct GarrAbilityMeta + { + public static DB6Meta Instance() + { + byte[] arraySizes = { 1, 1, 1, 1, 1, 1, 1, 1 }; + object[] fieldDefaults = { "", "", 0, 0, 0, 0, 0, 0 }; + return new DB6Meta(7, arraySizes, fieldDefaults); + } + } + + struct GarrAbilityCategoryMeta + { + public static DB6Meta Instance() + { + byte[] arraySizes = { 1 }; + object[] fieldDefaults = { "" }; + return new DB6Meta(-1, arraySizes, fieldDefaults); + } + } + + struct GarrAbilityEffectMeta + { + public static DB6Meta Instance() + { + byte[] arraySizes = { 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1 }; + object[] fieldDefaults = { 0.0f, 0.0f, 0.0f, 0, 0, 0, 0, 0, 0, 0, 0, 0 }; + return new DB6Meta(11, arraySizes, fieldDefaults); + } + } + + struct GarrBuildingMeta + { + public static DB6Meta Instance() + { + byte[] arraySizes = { 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1 }; + object[] fieldDefaults = { 0, 0, "", "", "", "", 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }; + return new DB6Meta(-1, arraySizes, fieldDefaults); + } + } + + struct GarrBuildingDoodadSetMeta + { + public static DB6Meta Instance() + { + byte[] arraySizes = { 1, 1, 1, 1, 1 }; + object[] fieldDefaults = { 0, 0, 0, 0, 0 }; + return new DB6Meta(-1, arraySizes, fieldDefaults); + } + } + + struct GarrBuildingPlotInstMeta + { + public static DB6Meta Instance() + { + byte[] arraySizes = { 2, 1, 1, 1, 1 }; + object[] fieldDefaults = { 0.0f, 0, 0, 0, 0 }; + return new DB6Meta(4, arraySizes, fieldDefaults); + } + } + + struct GarrClassSpecMeta + { + public static DB6Meta Instance() + { + byte[] arraySizes = { 1, 1, 1, 1, 1, 1, 1, 1 }; + object[] fieldDefaults = { "", "", "", 0, 0, 0, 0, 0 }; + return new DB6Meta(7, arraySizes, fieldDefaults); + } + } + + struct GarrClassSpecPlayerCondMeta + { + public static DB6Meta Instance() + { + byte[] arraySizes = { 1, 1, 1, 1, 1, 1 }; + object[] fieldDefaults = { 0, "", 0, 0, 0, 0 }; + return new DB6Meta(-1, arraySizes, fieldDefaults); + } + } + + struct GarrEncounterMeta + { + public static DB6Meta Instance() + { + byte[] arraySizes = { 1, 1, 1, 1, 1, 1, 1 }; + object[] fieldDefaults = { 0, "", 0.0f, 0.0f, 0, 0, 0 }; + return new DB6Meta(5, arraySizes, fieldDefaults); + } + } + + struct GarrEncounterSetXEncounterMeta + { + public static DB6Meta Instance() + { + byte[] arraySizes = { 1, 1 }; + object[] fieldDefaults = { 0, 0 }; + return new DB6Meta(-1, arraySizes, fieldDefaults); + } + } + + struct GarrEncounterXMechanicMeta + { + public static DB6Meta Instance() + { + byte[] arraySizes = { 1, 1, 1 }; + object[] fieldDefaults = { 0, 0, 0 }; + return new DB6Meta(-1, arraySizes, fieldDefaults); + } + } + + struct GarrFollItemSetMemberMeta + { + public static DB6Meta Instance() + { + byte[] arraySizes = { 1, 1, 1, 1 }; + object[] fieldDefaults = { 0, 0, 0, 0 }; + return new DB6Meta(-1, arraySizes, fieldDefaults); + } + } + + struct GarrFollSupportSpellMeta + { + public static DB6Meta Instance() + { + byte[] arraySizes = { 1, 1, 1, 1 }; + object[] fieldDefaults = { 0, 0, 0, 0 }; + return new DB6Meta(-1, arraySizes, fieldDefaults); + } + } + + struct GarrFollowerMeta + { + public static DB6Meta Instance() + { + byte[] arraySizes = { 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1 }; + object[] fieldDefaults = { 0, 0, "", "", 0, 0, 0, 0, "", 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }; + return new DB6Meta(31, arraySizes, fieldDefaults); + } + } + + struct GarrFollowerLevelXPMeta + { + public static DB6Meta Instance() + { + byte[] arraySizes = { 1, 1, 1, 1 }; + object[] fieldDefaults = { 0, 0, 0, 0 }; + return new DB6Meta(-1, arraySizes, fieldDefaults); + } + } + + struct GarrFollowerQualityMeta + { + public static DB6Meta Instance() + { + byte[] arraySizes = { 1, 1, 1, 1, 1, 1, 1 }; + object[] fieldDefaults = { 0, 0, 0, 0, 0, 0, 0 }; + return new DB6Meta(-1, arraySizes, fieldDefaults); + } + } + + struct GarrFollowerSetXFollowerMeta + { + public static DB6Meta Instance() + { + byte[] arraySizes = { 1, 1 }; + object[] fieldDefaults = { 0, 0 }; + return new DB6Meta(-1, arraySizes, fieldDefaults); + } + } + + struct GarrFollowerTypeMeta + { + public static DB6Meta Instance() + { + byte[] arraySizes = { 1, 1, 1, 1, 1, 1, 1 }; + object[] fieldDefaults = { 0, 0, 0, 0, 0, 0, 0 }; + return new DB6Meta(-1, arraySizes, fieldDefaults); + } + } + + struct GarrFollowerUICreatureMeta + { + public static DB6Meta Instance() + { + byte[] arraySizes = { 1, 1, 1, 1, 1, 1 }; + object[] fieldDefaults = { 0, 0.0f, 0, 0, 0, 0 }; + return new DB6Meta(-1, arraySizes, fieldDefaults); + } + } + + struct GarrFollowerXAbilityMeta + { + public static DB6Meta Instance() + { + byte[] arraySizes = { 1, 1, 1 }; + object[] fieldDefaults = { 0, 0, 0 }; + return new DB6Meta(-1, arraySizes, fieldDefaults); + } + } + + struct GarrItemLevelUpgradeDataMeta + { + public static DB6Meta Instance() + { + byte[] arraySizes = { 1, 1, 1, 1, 1 }; + object[] fieldDefaults = { 0, 0, 0, 0, 0 }; + return new DB6Meta(0, arraySizes, fieldDefaults); + } + } + + struct GarrMechanicMeta + { + public static DB6Meta Instance() + { + byte[] arraySizes = { 1, 1, 1 }; + object[] fieldDefaults = { 0.0f, 0, 0 }; + return new DB6Meta(-1, arraySizes, fieldDefaults); + } + } + + struct GarrMechanicSetXMechanicMeta + { + public static DB6Meta Instance() + { + byte[] arraySizes = { 1, 1, 1 }; + object[] fieldDefaults = { 0, 0, 0 }; + return new DB6Meta(1, arraySizes, fieldDefaults); + } + } + + struct GarrMechanicTypeMeta + { + public static DB6Meta Instance() + { + byte[] arraySizes = { 1, 1, 1, 1, 1 }; + object[] fieldDefaults = { "", "", 0, 0, 0 }; + return new DB6Meta(4, arraySizes, fieldDefaults); + } + } + + struct GarrMissionMeta + { + public static DB6Meta Instance() + { + byte[] arraySizes = { 1, 1, 1, 1, 1, 2, 2, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1 }; + object[] fieldDefaults = { 0, 0, "", "", "", 0.0f, 0.0f, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }; + return new DB6Meta(19, arraySizes, fieldDefaults); + } + } + + struct GarrMissionTextureMeta + { + public static DB6Meta Instance() + { + byte[] arraySizes = { 2, 1 }; + object[] fieldDefaults = { 0.0f, 0 }; + return new DB6Meta(-1, arraySizes, fieldDefaults); + } + } + + struct GarrMissionTypeMeta + { + public static DB6Meta Instance() + { + byte[] arraySizes = { 1, 1, 1 }; + object[] fieldDefaults = { "", 0, 0 }; + return new DB6Meta(-1, arraySizes, fieldDefaults); + } + } + + struct GarrMissionXEncounterMeta + { + public static DB6Meta Instance() + { + byte[] arraySizes = { 1, 1, 1, 1, 1 }; + object[] fieldDefaults = { 0, 0, 0, 0, 0 }; + return new DB6Meta(1, arraySizes, fieldDefaults); + } + } + + struct GarrMissionXFollowerMeta + { + public static DB6Meta Instance() + { + byte[] arraySizes = { 1, 1, 1 }; + object[] fieldDefaults = { 0, 0, 0 }; + return new DB6Meta(-1, arraySizes, fieldDefaults); + } + } + + struct GarrMssnBonusAbilityMeta + { + public static DB6Meta Instance() + { + byte[] arraySizes = { 1, 1, 1, 1, 1 }; + object[] fieldDefaults = { 0.0f, 0, 0, 0, 0 }; + return new DB6Meta(-1, arraySizes, fieldDefaults); + } + } + + struct GarrPlotMeta + { + public static DB6Meta Instance() + { + byte[] arraySizes = { 1, 1, 1, 1, 1, 1, 2 }; + object[] fieldDefaults = { "", 0, 0, 0, 0, 0, 0 }; + return new DB6Meta(-1, arraySizes, fieldDefaults); + } + } + + struct GarrPlotBuildingMeta + { + public static DB6Meta Instance() + { + byte[] arraySizes = { 1, 1 }; + object[] fieldDefaults = { 0, 0 }; + return new DB6Meta(-1, arraySizes, fieldDefaults); + } + } + + struct GarrPlotInstanceMeta + { + public static DB6Meta Instance() + { + byte[] arraySizes = { 1, 1 }; + object[] fieldDefaults = { "", 0 }; + return new DB6Meta(-1, arraySizes, fieldDefaults); + } + } + + struct GarrPlotUICategoryMeta + { + public static DB6Meta Instance() + { + byte[] arraySizes = { 1, 1 }; + object[] fieldDefaults = { "", 0 }; + return new DB6Meta(-1, arraySizes, fieldDefaults); + } + } + + struct GarrSiteLevelMeta + { + public static DB6Meta Instance() + { + byte[] arraySizes = { 2, 1, 1, 1, 1, 1, 1, 1, 1 }; + object[] fieldDefaults = { 0.0f, 0, 0, 0, 0, 0, 0, 0, 0 }; + return new DB6Meta(-1, arraySizes, fieldDefaults); + } + } + + struct GarrSiteLevelPlotInstMeta + { + public static DB6Meta Instance() + { + byte[] arraySizes = { 2, 1, 1, 1 }; + object[] fieldDefaults = { 0.0f, 0, 0, 0 }; + return new DB6Meta(-1, arraySizes, fieldDefaults); + } + } + + struct GarrSpecializationMeta + { + public static DB6Meta Instance() + { + byte[] arraySizes = { 1, 2, 1, 1, 1, 1, 1 }; + object[] fieldDefaults = { 0, 0.0f, "", "", 0, 0, 0 }; + return new DB6Meta(-1, arraySizes, fieldDefaults); + } + } + + struct GarrStringMeta + { + public static DB6Meta Instance() + { + byte[] arraySizes = { 1 }; + object[] fieldDefaults = { "" }; + return new DB6Meta(-1, arraySizes, fieldDefaults); + } + } + + struct GarrTalentMeta + { + public static DB6Meta Instance() + { + byte[] arraySizes = { 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1 }; + object[] fieldDefaults = { 0, "", "", 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }; + return new DB6Meta(7, arraySizes, fieldDefaults); + } + } + + struct GarrTalentTreeMeta + { + public static DB6Meta Instance() + { + byte[] arraySizes = { 1, 1, 1, 1 }; + object[] fieldDefaults = { 0, 0, 0, 0 }; + return new DB6Meta(-1, arraySizes, fieldDefaults); + } + } + + struct GarrTypeMeta + { + public static DB6Meta Instance() + { + byte[] arraySizes = { 1, 1, 1, 1, 1 }; + object[] fieldDefaults = { 0, 0, 0, 0, 0 }; + return new DB6Meta(-1, arraySizes, fieldDefaults); + } + } + + struct GarrUiAnimClassInfoMeta + { + public static DB6Meta Instance() + { + byte[] arraySizes = { 1, 1, 1, 1 }; + object[] fieldDefaults = { 0, 0.0f, 0, 0 }; + return new DB6Meta(-1, arraySizes, fieldDefaults); + } + } + + struct GarrUiAnimRaceInfoMeta + { + public static DB6Meta Instance() + { + byte[] arraySizes = { 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1 }; + object[] fieldDefaults = { 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0 }; + return new DB6Meta(-1, arraySizes, fieldDefaults); + } + } + + struct GemPropertiesMeta + { + public static DB6Meta Instance() + { + byte[] arraySizes = { 1, 1, 1 }; + object[] fieldDefaults = { 0, 0, 0 }; + return new DB6Meta(-1, arraySizes, fieldDefaults); + } + } + + struct GlobalStringsMeta + { + public static DB6Meta Instance() + { + byte[] arraySizes = { 1, 1, 1 }; + object[] fieldDefaults = { "", "", 0 }; + return new DB6Meta(-1, arraySizes, fieldDefaults); + } + } + + struct GlyphBindableSpellMeta + { + public static DB6Meta Instance() + { + byte[] arraySizes = { 1, 1 }; + object[] fieldDefaults = { 0, 0 }; + return new DB6Meta(-1, arraySizes, fieldDefaults); + } + } + + struct GlyphExclusiveCategoryMeta + { + public static DB6Meta Instance() + { + byte[] arraySizes = { 1 }; + object[] fieldDefaults = { "" }; + return new DB6Meta(-1, arraySizes, fieldDefaults); + } + } + + struct GlyphPropertiesMeta + { + public static DB6Meta Instance() + { + byte[] arraySizes = { 1, 1, 1, 1 }; + object[] fieldDefaults = { 0, 0, 0, 0 }; + return new DB6Meta(-1, arraySizes, fieldDefaults); + } + } + + struct GlyphRequiredSpecMeta + { + public static DB6Meta Instance() + { + byte[] arraySizes = { 1, 1 }; + object[] fieldDefaults = { 0, 0 }; + return new DB6Meta(-1, arraySizes, fieldDefaults); + } + } + + struct GroundEffectDoodadMeta + { + public static DB6Meta Instance() + { + byte[] arraySizes = { 1, 1, 1, 1 }; + object[] fieldDefaults = { "", 0.0f, 0.0f, 0 }; + return new DB6Meta(-1, arraySizes, fieldDefaults); + } + } + + struct GroundEffectTextureMeta + { + public static DB6Meta Instance() + { + byte[] arraySizes = { 4, 4, 1, 1 }; + object[] fieldDefaults = { 0, 0, 0, 0 }; + return new DB6Meta(-1, arraySizes, fieldDefaults); + } + } + + struct GroupFinderActivityMeta + { + public static DB6Meta Instance() + { + byte[] arraySizes = { 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1 }; + object[] fieldDefaults = { "", "", 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }; + return new DB6Meta(-1, arraySizes, fieldDefaults); + } + } + + struct GroupFinderActivityGrpMeta + { + public static DB6Meta Instance() + { + byte[] arraySizes = { 1, 1 }; + object[] fieldDefaults = { "", 0 }; + return new DB6Meta(-1, arraySizes, fieldDefaults); + } + } + + struct GroupFinderCategoryMeta + { + public static DB6Meta Instance() + { + byte[] arraySizes = { 1, 1, 1 }; + object[] fieldDefaults = { "", 0, 0 }; + return new DB6Meta(-1, arraySizes, fieldDefaults); + } + } + + struct GuildColorBackgroundMeta + { + public static DB6Meta Instance() + { + byte[] arraySizes = { 1, 1, 1 }; + object[] fieldDefaults = { 0, 0, 0 }; + return new DB6Meta(-1, arraySizes, fieldDefaults); + } + } + + struct GuildColorBorderMeta + { + public static DB6Meta Instance() + { + byte[] arraySizes = { 1, 1, 1 }; + object[] fieldDefaults = { 0, 0, 0 }; + return new DB6Meta(-1, arraySizes, fieldDefaults); + } + } + + struct GuildColorEmblemMeta + { + public static DB6Meta Instance() + { + byte[] arraySizes = { 1, 1, 1 }; + object[] fieldDefaults = { 0, 0, 0 }; + return new DB6Meta(-1, arraySizes, fieldDefaults); + } + } + + struct GuildPerkSpellsMeta + { + public static DB6Meta Instance() + { + byte[] arraySizes = { 1 }; + object[] fieldDefaults = { 0 }; + return new DB6Meta(-1, arraySizes, fieldDefaults); + } + } + + struct HeirloomMeta + { + public static DB6Meta Instance() + { + byte[] arraySizes = { 1, 1, 1, 1, 1, 2, 2, 1, 1, 1 }; + object[] fieldDefaults = { 0, "", 0, 0, 0, 0, 0, 0, 0, 0 }; + return new DB6Meta(9, arraySizes, fieldDefaults); + } + } + + struct HelmetAnimScalingMeta + { + public static DB6Meta Instance() + { + byte[] arraySizes = { 1, 1, 1 }; + object[] fieldDefaults = { 0.0f, 0, 0 }; + return new DB6Meta(-1, arraySizes, fieldDefaults); + } + } + + struct HelmetGeosetVisDataMeta + { + public static DB6Meta Instance() + { + byte[] arraySizes = { 9 }; + object[] fieldDefaults = { 0 }; + return new DB6Meta(-1, arraySizes, fieldDefaults); + } + } + + struct HighlightColorMeta + { + public static DB6Meta Instance() + { + byte[] arraySizes = { 1, 1, 1, 1, 1 }; + object[] fieldDefaults = { 0, 0, 0, 0, 0 }; + return new DB6Meta(-1, arraySizes, fieldDefaults); + } + } + + struct HolidayDescriptionsMeta + { + public static DB6Meta Instance() + { + byte[] arraySizes = { 1 }; + object[] fieldDefaults = { "" }; + return new DB6Meta(-1, arraySizes, fieldDefaults); + } + } + + struct HolidayNamesMeta + { + public static DB6Meta Instance() + { + byte[] arraySizes = { 1 }; + object[] fieldDefaults = { "" }; + return new DB6Meta(-1, arraySizes, fieldDefaults); + } + } + + struct HolidaysMeta + { + public static DB6Meta Instance() + { + byte[] arraySizes = { 1, 16, 1, 10, 1, 1, 10, 1, 1, 1, 1, 1 }; + object[] fieldDefaults = { 0, 0, "", 0, 0, 0, 0, 0, 0, 0, 0, 0 }; + return new DB6Meta(0, arraySizes, fieldDefaults); + } + } + + struct HotfixMeta + { + public static DB6Meta Instance() + { + byte[] arraySizes = { 1, 1, 1 }; + object[] fieldDefaults = { "", 0, 0 }; + return new DB6Meta(-1, arraySizes, fieldDefaults); + } + } + + struct ImportPriceArmorMeta + { + public static DB6Meta Instance() + { + byte[] arraySizes = { 1, 1, 1, 1 }; + object[] fieldDefaults = { 0.0f, 0.0f, 0.0f, 0.0f }; + return new DB6Meta(-1, arraySizes, fieldDefaults); + } + } + + struct ImportPriceQualityMeta + { + public static DB6Meta Instance() + { + byte[] arraySizes = { 1 }; + object[] fieldDefaults = { 0.0f }; + return new DB6Meta(-1, arraySizes, fieldDefaults); + } + } + + struct ImportPriceShieldMeta + { + public static DB6Meta Instance() + { + byte[] arraySizes = { 1 }; + object[] fieldDefaults = { 0.0f }; + return new DB6Meta(-1, arraySizes, fieldDefaults); + } + } + + struct ImportPriceWeaponMeta + { + public static DB6Meta Instance() + { + byte[] arraySizes = { 1 }; + object[] fieldDefaults = { 0.0f }; + return new DB6Meta(-1, arraySizes, fieldDefaults); + } + } + + struct InvasionClientDataMeta + { + public static DB6Meta Instance() + { + byte[] arraySizes = { 1, 2, 1, 1, 1, 1, 1, 1, 1, 1 }; + object[] fieldDefaults = { "", 0.0f, 0, 0, 0, 0, 0, 0, 0, 0 }; + return new DB6Meta(2, arraySizes, fieldDefaults); + } + } + + struct ItemMeta + { + public static DB6Meta Instance() + { + byte[] arraySizes = { 1, 1, 1, 1, 1, 1, 1, 1 }; + object[] fieldDefaults = { 0, 0, 0, 0, 0, 0, 0, 0 }; + return new DB6Meta(-1, arraySizes, fieldDefaults); + } + } + + struct ItemAppearanceMeta + { + public static DB6Meta Instance() + { + byte[] arraySizes = { 1, 1, 1, 1 }; + object[] fieldDefaults = { 0, 0, 0, 0 }; + return new DB6Meta(-1, arraySizes, fieldDefaults); + } + } + + struct ItemAppearanceXUiCameraMeta + { + public static DB6Meta Instance() + { + byte[] arraySizes = { 1, 1 }; + object[] fieldDefaults = { 0, 0 }; + return new DB6Meta(-1, arraySizes, fieldDefaults); + } + } + + struct ItemArmorQualityMeta + { + public static DB6Meta Instance() + { + byte[] arraySizes = { 7, 1 }; + object[] fieldDefaults = { 0.0f, 0 }; + return new DB6Meta(-1, arraySizes, fieldDefaults); + } + } + + struct ItemArmorShieldMeta + { + public static DB6Meta Instance() + { + byte[] arraySizes = { 7, 1 }; + object[] fieldDefaults = { 0.0f, 0 }; + return new DB6Meta(-1, arraySizes, fieldDefaults); + } + } + + struct ItemArmorTotalMeta + { + public static DB6Meta Instance() + { + byte[] arraySizes = { 1, 1, 1, 1, 1 }; + object[] fieldDefaults = { 0.0f, 0.0f, 0.0f, 0.0f, 0 }; + return new DB6Meta(-1, arraySizes, fieldDefaults); + } + } + + struct ItemBagFamilyMeta + { + public static DB6Meta Instance() + { + byte[] arraySizes = { 1 }; + object[] fieldDefaults = { "" }; + return new DB6Meta(-1, arraySizes, fieldDefaults); + } + } + + struct ItemBonusMeta + { + public static DB6Meta Instance() + { + byte[] arraySizes = { 2, 1, 1, 1 }; + object[] fieldDefaults = { 0, 0, 0, 0 }; + return new DB6Meta(-1, arraySizes, fieldDefaults); + } + } + + struct ItemBonusListLevelDeltaMeta + { + public static DB6Meta Instance() + { + byte[] arraySizes = { 1, 1 }; + object[] fieldDefaults = { 0, 0 }; + return new DB6Meta(1, arraySizes, fieldDefaults); + } + } + + struct ItemBonusTreeNodeMeta + { + public static DB6Meta Instance() + { + byte[] arraySizes = { 1, 1, 1, 1, 1 }; + object[] fieldDefaults = { 0, 0, 0, 0, 0 }; + return new DB6Meta(-1, arraySizes, fieldDefaults); + } + } + + struct ItemChildEquipmentMeta + { + public static DB6Meta Instance() + { + byte[] arraySizes = { 1, 1, 1 }; + object[] fieldDefaults = { 0, 0, 0 }; + return new DB6Meta(-1, arraySizes, fieldDefaults); + } + } + + struct ItemClassMeta + { + public static DB6Meta Instance() + { + byte[] arraySizes = { 1, 1, 1, 1 }; + object[] fieldDefaults = { 0.0f, "", 0, 0 }; + return new DB6Meta(-1, arraySizes, fieldDefaults); + } + } + + struct ItemContextPickerEntryMeta + { + public static DB6Meta Instance() + { + byte[] arraySizes = { 1, 1, 1, 1, 1 }; + object[] fieldDefaults = { 0, 0, 0, 0, 0 }; + return new DB6Meta(-1, arraySizes, fieldDefaults); + } + } + + struct ItemCurrencyCostMeta + { + public static DB6Meta Instance() + { + byte[] arraySizes = { 1 }; + object[] fieldDefaults = { 0 }; + return new DB6Meta(-1, arraySizes, fieldDefaults); + } + } + + struct ItemDamageAmmoMeta + { + public static DB6Meta Instance() + { + byte[] arraySizes = { 7, 1 }; + object[] fieldDefaults = { 0.0f, 0 }; + return new DB6Meta(-1, arraySizes, fieldDefaults); + } + } + + struct ItemDamageOneHandMeta + { + public static DB6Meta Instance() + { + byte[] arraySizes = { 7, 1 }; + object[] fieldDefaults = { 0.0f, 0 }; + return new DB6Meta(-1, arraySizes, fieldDefaults); + } + } + + struct ItemDamageOneHandCasterMeta + { + public static DB6Meta Instance() + { + byte[] arraySizes = { 7, 1 }; + object[] fieldDefaults = { 0.0f, 0 }; + return new DB6Meta(-1, arraySizes, fieldDefaults); + } + } + + struct ItemDamageTwoHandMeta + { + public static DB6Meta Instance() + { + byte[] arraySizes = { 7, 1 }; + object[] fieldDefaults = { 0.0f, 0 }; + return new DB6Meta(-1, arraySizes, fieldDefaults); + } + } + + struct ItemDamageTwoHandCasterMeta + { + public static DB6Meta Instance() + { + byte[] arraySizes = { 7, 1 }; + object[] fieldDefaults = { 0.0f, 0 }; + return new DB6Meta(-1, arraySizes, fieldDefaults); + } + } + + struct ItemDisenchantLootMeta + { + public static DB6Meta Instance() + { + byte[] arraySizes = { 1, 1, 1, 1, 1, 1 }; + object[] fieldDefaults = { 0, 0, 0, 0, 0, 0 }; + return new DB6Meta(-1, arraySizes, fieldDefaults); + } + } + + struct ItemDisplayInfoMeta + { + public static DB6Meta Instance() + { + byte[] arraySizes = { 2, 2, 3, 3, 1, 1, 2, 1, 1, 1, 1, 1, 1, 1, 1 }; + object[] fieldDefaults = { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }; + return new DB6Meta(-1, arraySizes, fieldDefaults); + } + } + + struct ItemDisplayInfoMaterialResMeta + { + public static DB6Meta Instance() + { + byte[] arraySizes = { 1, 1, 1 }; + object[] fieldDefaults = { 0, 0, 0 }; + return new DB6Meta(-1, arraySizes, fieldDefaults); + } + } + + struct ItemDisplayXUiCameraMeta + { + public static DB6Meta Instance() + { + byte[] arraySizes = { 1, 1 }; + object[] fieldDefaults = { 0, 0 }; + return new DB6Meta(-1, arraySizes, fieldDefaults); + } + } + + struct ItemEffectMeta + { + public static DB6Meta Instance() + { + byte[] arraySizes = { 1, 1, 1, 1, 1, 1, 1, 1, 1 }; + object[] fieldDefaults = { 0, 0, 0, 0, 0, 0, 0, 0, 0 }; + return new DB6Meta(-1, arraySizes, fieldDefaults); + } + } + + struct ItemExtendedCostMeta + { + public static DB6Meta Instance() + { + byte[] arraySizes = { 5, 5, 5, 1, 5, 1, 1, 1, 1, 1 }; + object[] fieldDefaults = { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }; + return new DB6Meta(-1, arraySizes, fieldDefaults); + } + } + + struct ItemGroupSoundsMeta + { + public static DB6Meta Instance() + { + byte[] arraySizes = { 4 }; + object[] fieldDefaults = { 0 }; + return new DB6Meta(-1, arraySizes, fieldDefaults); + } + } + + struct ItemLevelSelectorMeta + { + public static DB6Meta Instance() + { + byte[] arraySizes = { 1 }; + object[] fieldDefaults = { 0 }; + return new DB6Meta(-1, arraySizes, fieldDefaults); + } + } + + struct ItemLimitCategoryMeta + { + public static DB6Meta Instance() + { + byte[] arraySizes = { 1, 1, 1 }; + object[] fieldDefaults = { "", 0, 0 }; + return new DB6Meta(-1, arraySizes, fieldDefaults); + } + } + + struct ItemLimitCategoryConditionMeta + { + public static DB6Meta Instance() + { + byte[] arraySizes = { 1, 1, 1 }; + object[] fieldDefaults = { 0, 0, 0 }; + return new DB6Meta(-1, arraySizes, fieldDefaults); + } + } + + struct ItemModifiedAppearanceMeta + { + public static DB6Meta Instance() + { + byte[] arraySizes = { 1, 1, 1, 1, 1, 1 }; + object[] fieldDefaults = { 0, 0, 0, 0, 0, 0 }; + return new DB6Meta(5, arraySizes, fieldDefaults); + } + } + + struct ItemModifiedAppearanceExtraMeta + { + public static DB6Meta Instance() + { + byte[] arraySizes = { 1, 1, 1, 1, 1 }; + object[] fieldDefaults = { 0, 0, 0, 0, 0 }; + return new DB6Meta(-1, arraySizes, fieldDefaults); + } + } + + struct ItemNameDescriptionMeta + { + public static DB6Meta Instance() + { + byte[] arraySizes = { 1, 1 }; + object[] fieldDefaults = { "", 0 }; + return new DB6Meta(-1, arraySizes, fieldDefaults); + } + } + + struct ItemPetFoodMeta + { + public static DB6Meta Instance() + { + byte[] arraySizes = { 1 }; + object[] fieldDefaults = { "" }; + return new DB6Meta(-1, arraySizes, fieldDefaults); + } + } + + struct ItemPriceBaseMeta + { + public static DB6Meta Instance() + { + byte[] arraySizes = { 1, 1, 1 }; + object[] fieldDefaults = { 0.0f, 0.0f, 0 }; + return new DB6Meta(-1, arraySizes, fieldDefaults); + } + } + + struct ItemRandomPropertiesMeta + { + public static DB6Meta Instance() + { + byte[] arraySizes = { 1, 5 }; + object[] fieldDefaults = { "", 0 }; + return new DB6Meta(-1, arraySizes, fieldDefaults); + } + } + + struct ItemRandomSuffixMeta + { + public static DB6Meta Instance() + { + byte[] arraySizes = { 1, 5, 5 }; + object[] fieldDefaults = { "", 0, 0 }; + return new DB6Meta(-1, arraySizes, fieldDefaults); + } + } + + struct ItemRangedDisplayInfoMeta + { + public static DB6Meta Instance() + { + byte[] arraySizes = { 1, 1, 1, 1 }; + object[] fieldDefaults = { 0, 0, 0, 0 }; + return new DB6Meta(-1, arraySizes, fieldDefaults); + } + } + + struct ItemSearchNameMeta + { + public static DB6Meta Instance() + { + byte[] arraySizes = { 1, 3, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1 }; + object[] fieldDefaults = { "", 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }; + return new DB6Meta(-1, arraySizes, fieldDefaults); + } + } + + struct ItemSetMeta + { + public static DB6Meta Instance() + { + byte[] arraySizes = { 1, 17, 1, 1, 1 }; + object[] fieldDefaults = { "", 0, 0, 0, 0 }; + return new DB6Meta(-1, arraySizes, fieldDefaults); + } + } + + struct ItemSetSpellMeta + { + public static DB6Meta Instance() + { + byte[] arraySizes = { 1, 1, 1, 1 }; + object[] fieldDefaults = { 0, 0, 0, 0 }; + return new DB6Meta(-1, arraySizes, fieldDefaults); + } + } + + struct ItemSparseMeta + { + public static DB6Meta Instance() + { + byte[] arraySizes = { 3, 1, 1, 1, 1, 1, 1, 1, 1, 1, 10, 10, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 10, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 10, 1, 1, 1, 1, 1, 1, 3, 1, 1, 1, 1 }; + object[] fieldDefaults = { 0, 0.0f, 0.0f, 0, 0, 0, 0, 0, 0, 0, 0, 0.0f, 0.0f, "", "", "", "", "", 0, 0.0f, 0, 0.0f, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }; + return new DB6Meta(-1, arraySizes, fieldDefaults); + } + } + + struct ItemSpecMeta + { + public static DB6Meta Instance() + { + byte[] arraySizes = { 1, 1, 1, 1, 1, 1 }; + object[] fieldDefaults = { 0, 0, 0, 0, 0, 0 }; + return new DB6Meta(-1, arraySizes, fieldDefaults); + } + } + + struct ItemSpecOverrideMeta + { + public static DB6Meta Instance() + { + byte[] arraySizes = { 1, 1 }; + object[] fieldDefaults = { 0, 0 }; + return new DB6Meta(-1, arraySizes, fieldDefaults); + } + } + + struct ItemSubClassMeta + { + public static DB6Meta Instance() + { + byte[] arraySizes = { 1, 1, 1, 1, 1, 1, 1, 1, 1, 1 }; + object[] fieldDefaults = { "", "", 0, 0, 0, 0, 0, 0, 0, 0 }; + return new DB6Meta(-1, arraySizes, fieldDefaults); + } + } + + struct ItemSubClassMaskMeta + { + public static DB6Meta Instance() + { + byte[] arraySizes = { 1, 1, 1 }; + object[] fieldDefaults = { 0, "", 0 }; + return new DB6Meta(-1, arraySizes, fieldDefaults); + } + } + + struct ItemUpgradeMeta + { + public static DB6Meta Instance() + { + byte[] arraySizes = { 1, 1, 1, 1, 1 }; + object[] fieldDefaults = { 0, 0, 0, 0, 0 }; + return new DB6Meta(-1, arraySizes, fieldDefaults); + } + } + + struct ItemVisualEffectsMeta + { + public static DB6Meta Instance() + { + byte[] arraySizes = { 1 }; + object[] fieldDefaults = { "" }; + return new DB6Meta(-1, arraySizes, fieldDefaults); + } + } + + struct ItemVisualsMeta + { + public static DB6Meta Instance() + { + byte[] arraySizes = { 5 }; + object[] fieldDefaults = { 0 }; + return new DB6Meta(-1, arraySizes, fieldDefaults); + } + } + + struct ItemXBonusTreeMeta + { + public static DB6Meta Instance() + { + byte[] arraySizes = { 1, 1 }; + object[] fieldDefaults = { 0, 0 }; + return new DB6Meta(-1, arraySizes, fieldDefaults); + } + } + + struct JournalEncounterMeta + { + public static DB6Meta Instance() + { + byte[] arraySizes = { 2, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1 }; + object[] fieldDefaults = { 0.0f, "", "", 0, 0, 0, 0, 0, 0, 0, 0 }; + return new DB6Meta(-1, arraySizes, fieldDefaults); + } + } + + struct JournalEncounterCreatureMeta + { + public static DB6Meta Instance() + { + byte[] arraySizes = { 1, 1, 1, 1, 1, 1, 1 }; + object[] fieldDefaults = { 0, 0, "", "", 0, 0, 0 }; + return new DB6Meta(6, arraySizes, fieldDefaults); + } + } + + struct JournalEncounterItemMeta + { + public static DB6Meta Instance() + { + byte[] arraySizes = { 1, 1, 1, 1, 1, 1 }; + object[] fieldDefaults = { 0, 0, 0, 0, 0, 0 }; + return new DB6Meta(5, arraySizes, fieldDefaults); + } + } + + struct JournalEncounterSectionMeta + { + public static DB6Meta Instance() + { + byte[] arraySizes = { 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1 }; + object[] fieldDefaults = { "", "", 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }; + return new DB6Meta(-1, arraySizes, fieldDefaults); + } + } + + struct JournalEncounterXDifficultyMeta + { + public static DB6Meta Instance() + { + byte[] arraySizes = { 1, 1 }; + object[] fieldDefaults = { 0, 0 }; + return new DB6Meta(-1, arraySizes, fieldDefaults); + } + } + + struct JournalInstanceMeta + { + public static DB6Meta Instance() + { + byte[] arraySizes = { 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1 }; + object[] fieldDefaults = { 0, 0, 0, 0, "", "", 0, 0, 0, 0, 0 }; + return new DB6Meta(10, arraySizes, fieldDefaults); + } + } + + struct JournalItemXDifficultyMeta + { + public static DB6Meta Instance() + { + byte[] arraySizes = { 1, 1 }; + object[] fieldDefaults = { 0, 0 }; + return new DB6Meta(-1, arraySizes, fieldDefaults); + } + } + + struct JournalSectionXDifficultyMeta + { + public static DB6Meta Instance() + { + byte[] arraySizes = { 1, 1 }; + object[] fieldDefaults = { 0, 0 }; + return new DB6Meta(-1, arraySizes, fieldDefaults); + } + } + + struct JournalTierMeta + { + public static DB6Meta Instance() + { + byte[] arraySizes = { 1 }; + object[] fieldDefaults = { "" }; + return new DB6Meta(-1, arraySizes, fieldDefaults); + } + } + + struct JournalTierXInstanceMeta + { + public static DB6Meta Instance() + { + byte[] arraySizes = { 1, 1 }; + object[] fieldDefaults = { 0, 0 }; + return new DB6Meta(-1, arraySizes, fieldDefaults); + } + } + + struct KeyChainMeta + { + public static DB6Meta Instance() + { + byte[] arraySizes = { 32 }; + object[] fieldDefaults = { 0 }; + return new DB6Meta(-1, arraySizes, fieldDefaults); + } + } + + struct KeystoneAffixMeta + { + public static DB6Meta Instance() + { + byte[] arraySizes = { 1, 1, 1 }; + object[] fieldDefaults = { "", "", 0 }; + return new DB6Meta(-1, arraySizes, fieldDefaults); + } + } + + struct LanguageWordsMeta + { + public static DB6Meta Instance() + { + byte[] arraySizes = { 1, 1 }; + object[] fieldDefaults = { "", 0 }; + return new DB6Meta(-1, arraySizes, fieldDefaults); + } + } + + struct LanguagesMeta + { + public static DB6Meta Instance() + { + byte[] arraySizes = { 1, 1 }; + object[] fieldDefaults = { "", 0 }; + return new DB6Meta(1, arraySizes, fieldDefaults); + } + } + + struct LfgDungeonExpansionMeta + { + public static DB6Meta Instance() + { + byte[] arraySizes = { 1, 1, 1, 1, 1, 1, 1 }; + object[] fieldDefaults = { 0, 0, 0, 0, 0, 0, 0 }; + return new DB6Meta(-1, arraySizes, fieldDefaults); + } + } + + struct LfgDungeonGroupMeta + { + public static DB6Meta Instance() + { + byte[] arraySizes = { 1, 1, 1, 1 }; + object[] fieldDefaults = { "", 0, 0, 0 }; + return new DB6Meta(-1, arraySizes, fieldDefaults); + } + } + + struct LfgDungeonsMeta + { + public static DB6Meta Instance() + { + byte[] arraySizes = { 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1 }; + object[] fieldDefaults = { "", 0, "", "", 0.0f, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }; + return new DB6Meta(31, arraySizes, fieldDefaults); + } + } + + struct LfgDungeonsGroupingMapMeta + { + public static DB6Meta Instance() + { + byte[] arraySizes = { 1, 1, 1 }; + object[] fieldDefaults = { 0, 0, 0 }; + return new DB6Meta(-1, arraySizes, fieldDefaults); + } + } + + struct LfgRoleRequirementMeta + { + public static DB6Meta Instance() + { + byte[] arraySizes = { 1, 1, 1 }; + object[] fieldDefaults = { 0, 0, 0 }; + return new DB6Meta(-1, arraySizes, fieldDefaults); + } + } + + struct LightMeta + { + public static DB6Meta Instance() + { + byte[] arraySizes = { 3, 1, 1, 1, 8 }; + object[] fieldDefaults = { 0.0f, 0.0f, 0.0f, 0, 0 }; + return new DB6Meta(-1, arraySizes, fieldDefaults); + } + } + + struct LightDataMeta + { + public static DB6Meta Instance() + { + byte[] arraySizes = { 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1 }; + object[] fieldDefaults = { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0, 0, 0, 0, 0, 0, 0, 0 }; + return new DB6Meta(-1, arraySizes, fieldDefaults); + } + } + + struct LightParamsMeta + { + public static DB6Meta Instance() + { + byte[] arraySizes = { 1, 1, 1, 1, 1, 3, 1, 1, 1, 1, 1 }; + object[] fieldDefaults = { 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0, 0, 0, 0, 0 }; + return new DB6Meta(10, arraySizes, fieldDefaults); + } + } + + struct LightSkyboxMeta + { + public static DB6Meta Instance() + { + byte[] arraySizes = { 1, 1, 1 }; + object[] fieldDefaults = { "", 0, 0 }; + return new DB6Meta(-1, arraySizes, fieldDefaults); + } + } + + struct LiquidMaterialMeta + { + public static DB6Meta Instance() + { + byte[] arraySizes = { 1, 1 }; + object[] fieldDefaults = { 0, 0 }; + return new DB6Meta(-1, arraySizes, fieldDefaults); + } + } + + struct LiquidObjectMeta + { + public static DB6Meta Instance() + { + byte[] arraySizes = { 1, 1, 1, 1, 1 }; + object[] fieldDefaults = { 0.0f, 0.0f, 0, 0, 0 }; + return new DB6Meta(-1, arraySizes, fieldDefaults); + } + } + + struct LiquidTypeMeta + { + public static DB6Meta Instance() + { + byte[] arraySizes = { 1, 1, 1, 1, 1, 1, 1, 6, 2, 18, 4, 1, 1, 1, 1, 1, 1, 6, 1 }; + object[] fieldDefaults = { "", 0, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, "", 0, 0.0f, 0, 0, 0, 0, 0, 0, 0, 0, 0 }; + return new DB6Meta(-1, arraySizes, fieldDefaults); + } + } + + struct LoadingScreenTaxiSplinesMeta + { + public static DB6Meta Instance() + { + byte[] arraySizes = { 10, 10, 1, 1, 1 }; + object[] fieldDefaults = { 0.0f, 0.0f, 0, 0, 0 }; + return new DB6Meta(-1, arraySizes, fieldDefaults); + } + } + + struct LoadingScreensMeta + { + public static DB6Meta Instance() + { + byte[] arraySizes = { 1, 1, 1 }; + object[] fieldDefaults = { 0, 0, 0 }; + return new DB6Meta(-1, arraySizes, fieldDefaults); + } + } + + struct LocaleMeta + { + public static DB6Meta Instance() + { + byte[] arraySizes = { 1, 1, 1, 1 }; + object[] fieldDefaults = { 0, 0, 0, 0 }; + return new DB6Meta(-1, arraySizes, fieldDefaults); + } + } + + struct LocationMeta + { + public static DB6Meta Instance() + { + byte[] arraySizes = { 3, 3 }; + object[] fieldDefaults = { 0.0f, 0.0f }; + return new DB6Meta(-1, arraySizes, fieldDefaults); + } + } + + struct LockMeta + { + public static DB6Meta Instance() + { + byte[] arraySizes = { 8, 8, 8, 8 }; + object[] fieldDefaults = { 0, 0, 0, 0 }; + return new DB6Meta(-1, arraySizes, fieldDefaults); + } + } + + struct LockTypeMeta + { + public static DB6Meta Instance() + { + byte[] arraySizes = { 1, 1, 1, 1, 1 }; + object[] fieldDefaults = { "", "", "", "", 0 }; + return new DB6Meta(4, arraySizes, fieldDefaults); + } + } + + struct LookAtControllerMeta + { + public static DB6Meta Instance() + { + byte[] arraySizes = { 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1 }; + object[] fieldDefaults = { 0.0f, 0.0f, 0.0f, 0.0f, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }; + return new DB6Meta(-1, arraySizes, fieldDefaults); + } + } + + struct MailTemplateMeta + { + public static DB6Meta Instance() + { + byte[] arraySizes = { 1 }; + object[] fieldDefaults = { "" }; + return new DB6Meta(-1, arraySizes, fieldDefaults); + } + } + + struct ManagedWorldStateMeta + { + public static DB6Meta Instance() + { + byte[] arraySizes = { 1, 1, 1, 1, 1, 1, 1, 1, 1, 1 }; + object[] fieldDefaults = { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }; + return new DB6Meta(9, arraySizes, fieldDefaults); + } + } + + struct ManagedWorldStateBuffMeta + { + public static DB6Meta Instance() + { + byte[] arraySizes = { 1, 1, 1, 1 }; + object[] fieldDefaults = { 0, 0, 0, 0 }; + return new DB6Meta(-1, arraySizes, fieldDefaults); + } + } + + struct ManagedWorldStateInputMeta + { + public static DB6Meta Instance() + { + byte[] arraySizes = { 1, 1, 1 }; + object[] fieldDefaults = { 0, 0, 0 }; + return new DB6Meta(-1, arraySizes, fieldDefaults); + } + } + + struct ManifestInterfaceActionIconMeta + { + public static DB6Meta Instance() + { + byte[] arraySizes = { 1 }; + object[] fieldDefaults = { 0 }; + return new DB6Meta(0, arraySizes, fieldDefaults); + } + } + + struct ManifestInterfaceDataMeta + { + public static DB6Meta Instance() + { + byte[] arraySizes = { 1, 1 }; + object[] fieldDefaults = { "", "" }; + return new DB6Meta(-1, arraySizes, fieldDefaults); + } + } + + struct ManifestInterfaceItemIconMeta + { + public static DB6Meta Instance() + { + byte[] arraySizes = { 1 }; + object[] fieldDefaults = { 0 }; + return new DB6Meta(0, arraySizes, fieldDefaults); + } + } + + struct ManifestInterfaceTOCDataMeta + { + public static DB6Meta Instance() + { + byte[] arraySizes = { 1 }; + object[] fieldDefaults = { "" }; + return new DB6Meta(-1, arraySizes, fieldDefaults); + } + } + + struct ManifestMP3Meta + { + public static DB6Meta Instance() + { + byte[] arraySizes = { 1 }; + object[] fieldDefaults = { 0 }; + return new DB6Meta(0, arraySizes, fieldDefaults); + } + } + + struct MapMeta + { + public static DB6Meta Instance() + { + byte[] arraySizes = { 1, 2, 1, 2, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1 }; + object[] fieldDefaults = { "", 0, 0.0f, 0.0f, "", "", "", "", "", 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }; + return new DB6Meta(-1, arraySizes, fieldDefaults); + } + } + + struct MapCelestialBodyMeta + { + public static DB6Meta Instance() + { + byte[] arraySizes = { 1, 1, 1 }; + object[] fieldDefaults = { 0, 0, 0 }; + return new DB6Meta(-1, arraySizes, fieldDefaults); + } + } + + struct MapChallengeModeMeta + { + public static DB6Meta Instance() + { + byte[] arraySizes = { 1, 1, 1, 3, 1 }; + object[] fieldDefaults = { 0, "", 0, 0, 0 }; + return new DB6Meta(0, arraySizes, fieldDefaults); + } + } + + struct MapDifficultyMeta + { + public static DB6Meta Instance() + { + byte[] arraySizes = { 1, 1, 1, 1, 1, 1, 1, 1 }; + object[] fieldDefaults = { "", 0, 0, 0, 0, 0, 0, 0 }; + return new DB6Meta(-1, arraySizes, fieldDefaults); + } + } + + struct MapDifficultyXConditionMeta + { + public static DB6Meta Instance() + { + byte[] arraySizes = { 1, 1, 1, 1 }; + object[] fieldDefaults = { "", 0, 0, 0 }; + return new DB6Meta(-1, arraySizes, fieldDefaults); + } + } + + struct MarketingPromotionsXLocaleMeta + { + public static DB6Meta Instance() + { + byte[] arraySizes = { 1, 1, 1, 1, 1, 1, 1 }; + object[] fieldDefaults = { 0, 0, 0, 0, "", 0, 0 }; + return new DB6Meta(-1, arraySizes, fieldDefaults); + } + } + + struct MaterialMeta + { + public static DB6Meta Instance() + { + byte[] arraySizes = { 1, 1, 1, 1 }; + object[] fieldDefaults = { 0, 0, 0, 0 }; + return new DB6Meta(-1, arraySizes, fieldDefaults); + } + } + + struct MinorTalentMeta + { + public static DB6Meta Instance() + { + byte[] arraySizes = { 1, 1, 1 }; + object[] fieldDefaults = { 0, 0, 0 }; + return new DB6Meta(-1, arraySizes, fieldDefaults); + } + } + + struct ModelAnimCloakDampeningMeta + { + public static DB6Meta Instance() + { + byte[] arraySizes = { 1, 1, 1 }; + object[] fieldDefaults = { 0, 0, 0 }; + return new DB6Meta(-1, arraySizes, fieldDefaults); + } + } + + struct ModelFileDataMeta + { + public static DB6Meta Instance() + { + byte[] arraySizes = { 1, 1, 1 }; + object[] fieldDefaults = { 0, 0, 0 }; + return new DB6Meta(1, arraySizes, fieldDefaults); + } + } + + struct ModelRibbonQualityMeta + { + public static DB6Meta Instance() + { + byte[] arraySizes = { 1, 1 }; + object[] fieldDefaults = { 0, 0 }; + return new DB6Meta(-1, arraySizes, fieldDefaults); + } + } + + struct ModifierTreeMeta + { + public static DB6Meta Instance() + { + byte[] arraySizes = { 1, 1, 1, 1, 1, 1, 1 }; + object[] fieldDefaults = { 0, 0, 0, 0, 0, 0, 0 }; + return new DB6Meta(-1, arraySizes, fieldDefaults); + } + } + + struct MountMeta + { + public static DB6Meta Instance() + { + byte[] arraySizes = { 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1 }; + object[] fieldDefaults = { 0, "", "", "", 0.0f, 0, 0, 0, 0, 0, 0 }; + return new DB6Meta(8, arraySizes, fieldDefaults); + } + } + + struct MountCapabilityMeta + { + public static DB6Meta Instance() + { + byte[] arraySizes = { 1, 1, 1, 1, 1, 1, 1, 1 }; + object[] fieldDefaults = { 0, 0, 0, 0, 0, 0, 0, 0 }; + return new DB6Meta(6, arraySizes, fieldDefaults); + } + } + + struct MountTypeXCapabilityMeta + { + public static DB6Meta Instance() + { + byte[] arraySizes = { 1, 1, 1 }; + object[] fieldDefaults = { 0, 0, 0 }; + return new DB6Meta(-1, arraySizes, fieldDefaults); + } + } + + struct MountXDisplayMeta + { + public static DB6Meta Instance() + { + byte[] arraySizes = { 1, 1, 1 }; + object[] fieldDefaults = { 0, 0, 0 }; + return new DB6Meta(-1, arraySizes, fieldDefaults); + } + } + + struct MovieMeta + { + public static DB6Meta Instance() + { + byte[] arraySizes = { 1, 1, 1, 1 }; + object[] fieldDefaults = { 0, 0, 0, 0 }; + return new DB6Meta(-1, arraySizes, fieldDefaults); + } + } + + struct MovieFileDataMeta + { + public static DB6Meta Instance() + { + byte[] arraySizes = { 1 }; + object[] fieldDefaults = { 0 }; + return new DB6Meta(-1, arraySizes, fieldDefaults); + } + } + + struct MovieVariationMeta + { + public static DB6Meta Instance() + { + byte[] arraySizes = { 1, 1, 1 }; + object[] fieldDefaults = { 0, 0, 0 }; + return new DB6Meta(-1, arraySizes, fieldDefaults); + } + } + + struct NPCSoundsMeta + { + public static DB6Meta Instance() + { + byte[] arraySizes = { 4 }; + object[] fieldDefaults = { 0 }; + return new DB6Meta(-1, arraySizes, fieldDefaults); + } + } + + struct NameGenMeta + { + public static DB6Meta Instance() + { + byte[] arraySizes = { 1, 1, 1 }; + object[] fieldDefaults = { "", 0, 0 }; + return new DB6Meta(-1, arraySizes, fieldDefaults); + } + } + + struct NamesProfanityMeta + { + public static DB6Meta Instance() + { + byte[] arraySizes = { 1, 1 }; + object[] fieldDefaults = { "", 0 }; + return new DB6Meta(-1, arraySizes, fieldDefaults); + } + } + + struct NamesReservedMeta + { + public static DB6Meta Instance() + { + byte[] arraySizes = { 1 }; + object[] fieldDefaults = { "" }; + return new DB6Meta(-1, arraySizes, fieldDefaults); + } + } + + struct NamesReservedLocaleMeta + { + public static DB6Meta Instance() + { + byte[] arraySizes = { 1, 1 }; + object[] fieldDefaults = { "", 0 }; + return new DB6Meta(-1, arraySizes, fieldDefaults); + } + } + + struct NpcModelItemSlotDisplayInfoMeta + { + public static DB6Meta Instance() + { + byte[] arraySizes = { 1, 1, 1 }; + object[] fieldDefaults = { 0, 0, 0 }; + return new DB6Meta(-1, arraySizes, fieldDefaults); + } + } + + struct ObjectEffectMeta + { + public static DB6Meta Instance() + { + byte[] arraySizes = { 1, 3, 1, 1, 1, 1, 1, 1, 1 }; + object[] fieldDefaults = { "", 0.0f, 0, 0, 0, 0, 0, 0, 0 }; + return new DB6Meta(-1, arraySizes, fieldDefaults); + } + } + + struct ObjectEffectGroupMeta + { + public static DB6Meta Instance() + { + byte[] arraySizes = { 1 }; + object[] fieldDefaults = { "" }; + return new DB6Meta(-1, arraySizes, fieldDefaults); + } + } + + struct ObjectEffectModifierMeta + { + public static DB6Meta Instance() + { + byte[] arraySizes = { 4, 1, 1, 1 }; + object[] fieldDefaults = { 0.0f, 0, 0, 0 }; + return new DB6Meta(-1, arraySizes, fieldDefaults); + } + } + + struct ObjectEffectPackageMeta + { + public static DB6Meta Instance() + { + byte[] arraySizes = { 1 }; + object[] fieldDefaults = { "" }; + return new DB6Meta(-1, arraySizes, fieldDefaults); + } + } + + struct ObjectEffectPackageElemMeta + { + public static DB6Meta Instance() + { + byte[] arraySizes = { 1, 1, 1 }; + object[] fieldDefaults = { 0, 0, 0 }; + return new DB6Meta(-1, arraySizes, fieldDefaults); + } + } + + struct OutlineEffectMeta + { + public static DB6Meta Instance() + { + byte[] arraySizes = { 1, 1, 1, 1, 1, 1 }; + object[] fieldDefaults = { 0.0f, 0, 0, 0, 0, 0 }; + return new DB6Meta(-1, arraySizes, fieldDefaults); + } + } + + struct OverrideSpellDataMeta + { + public static DB6Meta Instance() + { + byte[] arraySizes = { 10, 1, 1 }; + object[] fieldDefaults = { 0, 0, 0 }; + return new DB6Meta(-1, arraySizes, fieldDefaults); + } + } + + struct PageTextMaterialMeta + { + public static DB6Meta Instance() + { + byte[] arraySizes = { 1 }; + object[] fieldDefaults = { "" }; + return new DB6Meta(-1, arraySizes, fieldDefaults); + } + } + + struct PaperDollItemFrameMeta + { + public static DB6Meta Instance() + { + byte[] arraySizes = { 1, 1, 1 }; + object[] fieldDefaults = { "", "", 0 }; + return new DB6Meta(-1, arraySizes, fieldDefaults); + } + } + + struct ParagonReputationMeta + { + public static DB6Meta Instance() + { + byte[] arraySizes = { 1, 1, 1 }; + object[] fieldDefaults = { 0, 0, 0 }; + return new DB6Meta(-1, arraySizes, fieldDefaults); + } + } + + struct ParticleColorMeta + { + public static DB6Meta Instance() + { + byte[] arraySizes = { 3, 3, 3 }; + object[] fieldDefaults = { 0, 0, 0 }; + return new DB6Meta(-1, arraySizes, fieldDefaults); + } + } + + struct PathMeta + { + public static DB6Meta Instance() + { + byte[] arraySizes = { 1, 1, 1, 1, 1, 1, 1 }; + object[] fieldDefaults = { 0, 0, 0, 0, 0, 0, 0 }; + return new DB6Meta(-1, arraySizes, fieldDefaults); + } + } + + struct PathNodeMeta + { + public static DB6Meta Instance() + { + byte[] arraySizes = { 1, 1, 1, 1 }; + object[] fieldDefaults = { 0, 0, 0, 0 }; + return new DB6Meta(0, arraySizes, fieldDefaults); + } + } + + struct PathNodePropertyMeta + { + public static DB6Meta Instance() + { + byte[] arraySizes = { 1, 1, 1, 1, 1 }; + object[] fieldDefaults = { 0, 0, 0, 0, 0 }; + return new DB6Meta(3, arraySizes, fieldDefaults); + } + } + + struct PathPropertyMeta + { + public static DB6Meta Instance() + { + byte[] arraySizes = { 1, 1, 1, 1 }; + object[] fieldDefaults = { 0, 0, 0, 0 }; + return new DB6Meta(3, arraySizes, fieldDefaults); + } + } + + struct PhaseMeta + { + public static DB6Meta Instance() + { + byte[] arraySizes = { 1 }; + object[] fieldDefaults = { 0 }; + return new DB6Meta(-1, arraySizes, fieldDefaults); + } + } + + struct PhaseShiftZoneSoundsMeta + { + public static DB6Meta Instance() + { + byte[] arraySizes = { 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1 }; + object[] fieldDefaults = { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }; + return new DB6Meta(-1, arraySizes, fieldDefaults); + } + } + + struct PhaseXPhaseGroupMeta + { + public static DB6Meta Instance() + { + byte[] arraySizes = { 1, 1 }; + object[] fieldDefaults = { 0, 0 }; + return new DB6Meta(-1, arraySizes, fieldDefaults); + } + } + + struct PlayerConditionMeta + { + public static DB6Meta Instance() + { + byte[] arraySizes = { 1, 1, 1, 1, 1, 1, 1, 1, 2, 1, 4, 1, 1, 1, 1, 1, 1, 4, 4, 4, 1, 4, 4, 4, 2, 1, 4, 4, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 3, 1, 1, 1, 1, 1, 1, 4, 1, 1, 1, 4, 4, 4, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 3, 4, 4, 4, 1, 4, 1, 4, 6, 1, 1, 1, 2, 1 }; + object[] fieldDefaults = { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, "", 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }; + return new DB6Meta(-1, arraySizes, fieldDefaults); + } + } + + struct PositionerMeta + { + public static DB6Meta Instance() + { + byte[] arraySizes = { 1, 1, 1, 1 }; + object[] fieldDefaults = { 0.0f, 0, 0, 0 }; + return new DB6Meta(-1, arraySizes, fieldDefaults); + } + } + + struct PositionerStateMeta + { + public static DB6Meta Instance() + { + byte[] arraySizes = { 1, 1, 1, 1, 1, 1, 1, 1 }; + object[] fieldDefaults = { 0.0f, 0, 0, 0, 0, 0, 0, 0 }; + return new DB6Meta(-1, arraySizes, fieldDefaults); + } + } + + struct PositionerStateEntryMeta + { + public static DB6Meta Instance() + { + byte[] arraySizes = { 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1 }; + object[] fieldDefaults = { 0.0f, 0.0f, 0, 0, 0, 0, 0, 0, 0, 0, 0 }; + return new DB6Meta(-1, arraySizes, fieldDefaults); + } + } + + struct PowerDisplayMeta + { + public static DB6Meta Instance() + { + byte[] arraySizes = { 1, 1, 1, 1, 1 }; + object[] fieldDefaults = { "", 0, 0, 0, 0 }; + return new DB6Meta(-1, arraySizes, fieldDefaults); + } + } + + struct PowerTypeMeta + { + public static DB6Meta Instance() + { + byte[] arraySizes = { 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1 }; + object[] fieldDefaults = { "", "", 0.0f, 0.0f, 0, 0, 0, 0, 0, 0, 0, 0 }; + return new DB6Meta(-1, arraySizes, fieldDefaults); + } + } + + struct PrestigeLevelInfoMeta + { + public static DB6Meta Instance() + { + byte[] arraySizes = { 1, 1, 1, 1 }; + object[] fieldDefaults = { 0, "", 0, 0 }; + return new DB6Meta(-1, arraySizes, fieldDefaults); + } + } + + struct PvpBracketTypesMeta + { + public static DB6Meta Instance() + { + byte[] arraySizes = { 1, 4 }; + object[] fieldDefaults = { 0, 0 }; + return new DB6Meta(-1, arraySizes, fieldDefaults); + } + } + + struct PvpDifficultyMeta + { + public static DB6Meta Instance() + { + byte[] arraySizes = { 1, 1, 1, 1 }; + object[] fieldDefaults = { 0, 0, 0, 0 }; + return new DB6Meta(-1, arraySizes, fieldDefaults); + } + } + + struct PvpItemMeta + { + public static DB6Meta Instance() + { + byte[] arraySizes = { 1, 1 }; + object[] fieldDefaults = { 0, 0 }; + return new DB6Meta(-1, arraySizes, fieldDefaults); + } + } + + struct PvpRewardMeta + { + public static DB6Meta Instance() + { + byte[] arraySizes = { 1, 1, 1 }; + object[] fieldDefaults = { 0, 0, 0 }; + return new DB6Meta(-1, arraySizes, fieldDefaults); + } + } + + struct PvpScalingEffectMeta + { + public static DB6Meta Instance() + { + byte[] arraySizes = { 1, 1, 1 }; + object[] fieldDefaults = { 0.0f, 0, 0 }; + return new DB6Meta(-1, arraySizes, fieldDefaults); + } + } + + struct PvpScalingEffectTypeMeta + { + public static DB6Meta Instance() + { + byte[] arraySizes = { 1 }; + object[] fieldDefaults = { "" }; + return new DB6Meta(-1, arraySizes, fieldDefaults); + } + } + + struct PvpTalentMeta + { + public static DB6Meta Instance() + { + byte[] arraySizes = { 1, 1, 1, 1, 1, 1, 1, 1, 1 }; + object[] fieldDefaults = { 0, 0, "", 0, 0, 0, 0, 0, 0 }; + return new DB6Meta(-1, arraySizes, fieldDefaults); + } + } + + struct PvpTalentUnlockMeta + { + public static DB6Meta Instance() + { + byte[] arraySizes = { 1, 1, 1 }; + object[] fieldDefaults = { 0, 0, 0 }; + return new DB6Meta(-1, arraySizes, fieldDefaults); + } + } + + struct QuestFactionRewardMeta + { + public static DB6Meta Instance() + { + byte[] arraySizes = { 10 }; + object[] fieldDefaults = { 0 }; + return new DB6Meta(-1, arraySizes, fieldDefaults); + } + } + + struct QuestFeedbackEffectMeta + { + public static DB6Meta Instance() + { + byte[] arraySizes = { 1, 1, 1, 1, 1, 1 }; + object[] fieldDefaults = { 0, 0, 0, 0, 0, 0 }; + return new DB6Meta(-1, arraySizes, fieldDefaults); + } + } + + struct QuestInfoMeta + { + public static DB6Meta Instance() + { + byte[] arraySizes = { 1, 1, 1, 1 }; + object[] fieldDefaults = { "", 0, 0, 0 }; + return new DB6Meta(-1, arraySizes, fieldDefaults); + } + } + + struct QuestLineMeta + { + public static DB6Meta Instance() + { + byte[] arraySizes = { 1 }; + object[] fieldDefaults = { "" }; + return new DB6Meta(-1, arraySizes, fieldDefaults); + } + } + + struct QuestLineXQuestMeta + { + public static DB6Meta Instance() + { + byte[] arraySizes = { 1, 1, 1 }; + object[] fieldDefaults = { 0, 0, 0 }; + return new DB6Meta(-1, arraySizes, fieldDefaults); + } + } + + struct QuestMoneyRewardMeta + { + public static DB6Meta Instance() + { + byte[] arraySizes = { 10 }; + object[] fieldDefaults = { 0 }; + return new DB6Meta(-1, arraySizes, fieldDefaults); + } + } + + struct QuestObjectiveMeta + { + public static DB6Meta Instance() + { + byte[] arraySizes = { 1, 1, 1, 1, 1, 1, 1, 1 }; + object[] fieldDefaults = { 0, 0, "", 0, 0, 0, 0, 0 }; + return new DB6Meta(-1, arraySizes, fieldDefaults); + } + } + + struct QuestPOIBlobMeta + { + public static DB6Meta Instance() + { + byte[] arraySizes = { 1, 1, 1, 1, 1, 1, 1, 1 }; + object[] fieldDefaults = { 0, 0, 0, 0, 0, 0, 0, 0 }; + return new DB6Meta(0, arraySizes, fieldDefaults); + } + } + + struct QuestPOIPointMeta + { + public static DB6Meta Instance() + { + byte[] arraySizes = { 1, 1, 1, 1 }; + object[] fieldDefaults = { 0, 0, 0, 0 }; + return new DB6Meta(3, arraySizes, fieldDefaults); + } + } + + struct QuestPackageItemMeta + { + public static DB6Meta Instance() + { + byte[] arraySizes = { 1, 1, 1, 1 }; + object[] fieldDefaults = { 0, 0, 0, 0 }; + return new DB6Meta(-1, arraySizes, fieldDefaults); + } + } + + struct QuestSortMeta + { + public static DB6Meta Instance() + { + byte[] arraySizes = { 1, 1 }; + object[] fieldDefaults = { "", 0 }; + return new DB6Meta(-1, arraySizes, fieldDefaults); + } + } + + struct QuestV2Meta + { + public static DB6Meta Instance() + { + byte[] arraySizes = { 1 }; + object[] fieldDefaults = { 0 }; + return new DB6Meta(-1, arraySizes, fieldDefaults); + } + } + + struct QuestV2CliTaskMeta + { + public static DB6Meta Instance() + { + byte[] arraySizes = { 1, 1, 1, 1, 1, 1, 1, 3, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1 }; + object[] fieldDefaults = { 0, "", "", 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }; + return new DB6Meta(20, arraySizes, fieldDefaults); + } + } + + struct QuestXGroupActivityMeta + { + public static DB6Meta Instance() + { + byte[] arraySizes = { 1, 1 }; + object[] fieldDefaults = { 0, 0 }; + return new DB6Meta(-1, arraySizes, fieldDefaults); + } + } + + struct QuestXPMeta + { + public static DB6Meta Instance() + { + byte[] arraySizes = { 10 }; + object[] fieldDefaults = { 0 }; + return new DB6Meta(-1, arraySizes, fieldDefaults); + } + } + + struct RacialMountsMeta + { + public static DB6Meta Instance() + { + byte[] arraySizes = { 1, 1 }; + object[] fieldDefaults = { 0, 0 }; + return new DB6Meta(-1, arraySizes, fieldDefaults); + } + } + + struct RandPropPointsMeta + { + public static DB6Meta Instance() + { + byte[] arraySizes = { 5, 5, 5 }; + object[] fieldDefaults = { 0, 0, 0 }; + return new DB6Meta(-1, arraySizes, fieldDefaults); + } + } + + struct ResearchBranchMeta + { + public static DB6Meta Instance() + { + byte[] arraySizes = { 1, 1, 1, 1, 1 }; + object[] fieldDefaults = { "", "", 0, 0, 0 }; + return new DB6Meta(-1, arraySizes, fieldDefaults); + } + } + + struct ResearchFieldMeta + { + public static DB6Meta Instance() + { + byte[] arraySizes = { 1, 1, 1 }; + object[] fieldDefaults = { "", 0, 0 }; + return new DB6Meta(2, arraySizes, fieldDefaults); + } + } + + struct ResearchProjectMeta + { + public static DB6Meta Instance() + { + byte[] arraySizes = { 1, 1, 1, 1, 1, 1, 1, 1, 1 }; + object[] fieldDefaults = { "", "", 0, "", 0, 0, 0, 0, 0 }; + return new DB6Meta(7, arraySizes, fieldDefaults); + } + } + + struct ResearchSiteMeta + { + public static DB6Meta Instance() + { + byte[] arraySizes = { 1, 1, 1, 1 }; + object[] fieldDefaults = { 0, "", 0, 0 }; + return new DB6Meta(-1, arraySizes, fieldDefaults); + } + } + + struct ResistancesMeta + { + public static DB6Meta Instance() + { + byte[] arraySizes = { 1, 1, 1 }; + object[] fieldDefaults = { "", 0, 0 }; + return new DB6Meta(-1, arraySizes, fieldDefaults); + } + } + + struct RewardPackMeta + { + public static DB6Meta Instance() + { + byte[] arraySizes = { 1, 1, 1, 1, 1, 1 }; + object[] fieldDefaults = { 0, 0.0f, 0, 0, 0, 0 }; + return new DB6Meta(-1, arraySizes, fieldDefaults); + } + } + + struct RewardPackXCurrencyTypeMeta + { + public static DB6Meta Instance() + { + byte[] arraySizes = { 1, 1, 1 }; + object[] fieldDefaults = { 0, 0, 0 }; + return new DB6Meta(-1, arraySizes, fieldDefaults); + } + } + + struct RewardPackXItemMeta + { + public static DB6Meta Instance() + { + byte[] arraySizes = { 1, 1, 1 }; + object[] fieldDefaults = { 0, 0, 0 }; + return new DB6Meta(-1, arraySizes, fieldDefaults); + } + } + + struct RibbonQualityMeta + { + public static DB6Meta Instance() + { + byte[] arraySizes = { 1, 1, 1, 1, 1 }; + object[] fieldDefaults = { 0.0f, 0.0f, 0.0f, 0, 0 }; + return new DB6Meta(-1, arraySizes, fieldDefaults); + } + } + + struct RulesetItemUpgradeMeta + { + public static DB6Meta Instance() + { + byte[] arraySizes = { 1, 1 }; + object[] fieldDefaults = { 0, 0 }; + return new DB6Meta(-1, arraySizes, fieldDefaults); + } + } + + struct ScalingStatDistributionMeta + { + public static DB6Meta Instance() + { + byte[] arraySizes = { 1, 1, 1 }; + object[] fieldDefaults = { 0, 0, 0 }; + return new DB6Meta(-1, arraySizes, fieldDefaults); + } + } + + struct ScenarioMeta + { + public static DB6Meta Instance() + { + byte[] arraySizes = { 1, 1, 1, 1 }; + object[] fieldDefaults = { "", 0, 0, 0 }; + return new DB6Meta(-1, arraySizes, fieldDefaults); + } + } + + struct ScenarioEventEntryMeta + { + public static DB6Meta Instance() + { + byte[] arraySizes = { 1, 1 }; + object[] fieldDefaults = { 0, 0 }; + return new DB6Meta(-1, arraySizes, fieldDefaults); + } + } + + struct ScenarioStepMeta + { + public static DB6Meta Instance() + { + byte[] arraySizes = { 1, 1, 1, 1, 1, 1, 1, 1, 1 }; + object[] fieldDefaults = { "", "", 0, 0, 0, 0, 0, 0, 0 }; + return new DB6Meta(-1, arraySizes, fieldDefaults); + } + } + + struct SceneScriptMeta + { + public static DB6Meta Instance() + { + byte[] arraySizes = { 1, 1, 1, 1 }; + object[] fieldDefaults = { "", "", 0, 0 }; + return new DB6Meta(-1, arraySizes, fieldDefaults); + } + } + + struct SceneScriptPackageMeta + { + public static DB6Meta Instance() + { + byte[] arraySizes = { 1 }; + object[] fieldDefaults = { "" }; + return new DB6Meta(-1, arraySizes, fieldDefaults); + } + } + + struct SceneScriptPackageMemberMeta + { + public static DB6Meta Instance() + { + byte[] arraySizes = { 1, 1, 1, 1 }; + object[] fieldDefaults = { 0, 0, 0, 0 }; + return new DB6Meta(-1, arraySizes, fieldDefaults); + } + } + + struct ScheduledIntervalMeta + { + public static DB6Meta Instance() + { + byte[] arraySizes = { 1, 1, 1, 1, 1 }; + object[] fieldDefaults = { 0, 0, 0, 0, 0 }; + return new DB6Meta(-1, arraySizes, fieldDefaults); + } + } + + struct ScheduledWorldStateMeta + { + public static DB6Meta Instance() + { + byte[] arraySizes = { 1, 1, 1, 1, 1, 1, 1, 1 }; + object[] fieldDefaults = { 0, 0, 0, 0, 0, 0, 0, 0 }; + return new DB6Meta(-1, arraySizes, fieldDefaults); + } + } + + struct ScheduledWorldStateGroupMeta + { + public static DB6Meta Instance() + { + byte[] arraySizes = { 1, 1, 1, 1, 1 }; + object[] fieldDefaults = { 0, 0, 0, 0, 0 }; + return new DB6Meta(-1, arraySizes, fieldDefaults); + } + } + + struct ScheduledWorldStateXUniqCatMeta + { + public static DB6Meta Instance() + { + byte[] arraySizes = { 1, 1, 1 }; + object[] fieldDefaults = { 0, 0, 0 }; + return new DB6Meta(0, arraySizes, fieldDefaults); + } + } + + struct ScreenEffectMeta + { + public static DB6Meta Instance() + { + byte[] arraySizes = { 1, 4, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1 }; + object[] fieldDefaults = { "", 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }; + return new DB6Meta(-1, arraySizes, fieldDefaults); + } + } + + struct ScreenLocationMeta + { + public static DB6Meta Instance() + { + byte[] arraySizes = { 1 }; + object[] fieldDefaults = { "" }; + return new DB6Meta(-1, arraySizes, fieldDefaults); + } + } + + struct SeamlessSiteMeta + { + public static DB6Meta Instance() + { + byte[] arraySizes = { 1 }; + object[] fieldDefaults = { 0 }; + return new DB6Meta(-1, arraySizes, fieldDefaults); + } + } + + struct ServerMessagesMeta + { + public static DB6Meta Instance() + { + byte[] arraySizes = { 1 }; + object[] fieldDefaults = { "" }; + return new DB6Meta(-1, arraySizes, fieldDefaults); + } + } + + struct ShadowyEffectMeta + { + public static DB6Meta Instance() + { + byte[] arraySizes = { 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1 }; + object[] fieldDefaults = { 0, 0, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0, 0 }; + return new DB6Meta(-1, arraySizes, fieldDefaults); + } + } + + struct SkillLineMeta + { + public static DB6Meta Instance() + { + byte[] arraySizes = { 1, 1, 1, 1, 1, 1, 1, 1 }; + object[] fieldDefaults = { "", "", "", 0, 0, 0, 0, 0 }; + return new DB6Meta(-1, arraySizes, fieldDefaults); + } + } + + struct SkillLineAbilityMeta + { + public static DB6Meta Instance() + { + byte[] arraySizes = { 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1 }; + object[] fieldDefaults = { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }; + return new DB6Meta(-1, arraySizes, fieldDefaults); + } + } + + struct SkillRaceClassInfoMeta + { + public static DB6Meta Instance() + { + byte[] arraySizes = { 1, 1, 1, 1, 1, 1, 1 }; + object[] fieldDefaults = { 0, 0, 0, 0, 0, 0, 0 }; + return new DB6Meta(-1, arraySizes, fieldDefaults); + } + } + + struct SoundAmbienceMeta + { + public static DB6Meta Instance() + { + byte[] arraySizes = { 1, 2, 1, 1 }; + object[] fieldDefaults = { 0, 0, 0, 0 }; + return new DB6Meta(-1, arraySizes, fieldDefaults); + } + } + + struct SoundAmbienceFlavorMeta + { + public static DB6Meta Instance() + { + byte[] arraySizes = { 1, 1, 1 }; + object[] fieldDefaults = { 0, 0, 0 }; + return new DB6Meta(-1, arraySizes, fieldDefaults); + } + } + + struct SoundBusMeta + { + public static DB6Meta Instance() + { + byte[] arraySizes = { 1, 1, 1, 1, 1, 1, 1, 1 }; + object[] fieldDefaults = { 0.0f, 0, 0, 0, 0, 0, 0, 0 }; + return new DB6Meta(7, arraySizes, fieldDefaults); + } + } + + struct SoundBusOverrideMeta + { + public static DB6Meta Instance() + { + byte[] arraySizes = { 1, 1, 1, 1, 1, 1, 1 }; + object[] fieldDefaults = { 0, 0.0f, 0, 0, 0, 0, 0 }; + return new DB6Meta(0, arraySizes, fieldDefaults); + } + } + + struct SoundEmitterPillPointsMeta + { + public static DB6Meta Instance() + { + byte[] arraySizes = { 3, 1 }; + object[] fieldDefaults = { 0.0f, 0 }; + return new DB6Meta(-1, arraySizes, fieldDefaults); + } + } + + struct SoundEmittersMeta + { + public static DB6Meta Instance() + { + byte[] arraySizes = { 3, 3, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1 }; + object[] fieldDefaults = { 0.0f, 0.0f, "", 0, 0, 0, 0, 0, 0, 0, 0, 0 }; + return new DB6Meta(9, arraySizes, fieldDefaults); + } + } + + struct SoundFilterMeta + { + public static DB6Meta Instance() + { + byte[] arraySizes = { 1 }; + object[] fieldDefaults = { "" }; + return new DB6Meta(-1, arraySizes, fieldDefaults); + } + } + + struct SoundFilterElemMeta + { + public static DB6Meta Instance() + { + byte[] arraySizes = { 9, 1, 1 }; + object[] fieldDefaults = { 0.0f, 0, 0 }; + return new DB6Meta(-1, arraySizes, fieldDefaults); + } + } + + struct SoundKitMeta + { + public static DB6Meta Instance() + { + byte[] arraySizes = { 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1 }; + object[] fieldDefaults = { "", 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0, 0, 0, 0, 0, 0, 0, 0 }; + return new DB6Meta(16, arraySizes, fieldDefaults); + } + } + + struct SoundKitAdvancedMeta + { + public static DB6Meta Instance() + { + byte[] arraySizes = { 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1 }; + object[] fieldDefaults = { 0.0f, 0, 0, 0, 0, 0, 0, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0, 0, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }; + return new DB6Meta(-1, arraySizes, fieldDefaults); + } + } + + struct SoundKitChildMeta + { + public static DB6Meta Instance() + { + byte[] arraySizes = { 1, 1 }; + object[] fieldDefaults = { 0, 0 }; + return new DB6Meta(-1, arraySizes, fieldDefaults); + } + } + + struct SoundKitEntryMeta + { + public static DB6Meta Instance() + { + byte[] arraySizes = { 1, 1, 1, 1 }; + object[] fieldDefaults = { 0, 0.0f, 0, 0 }; + return new DB6Meta(-1, arraySizes, fieldDefaults); + } + } + + struct SoundKitFallbackMeta + { + public static DB6Meta Instance() + { + byte[] arraySizes = { 1, 1 }; + object[] fieldDefaults = { 0, 0 }; + return new DB6Meta(-1, arraySizes, fieldDefaults); + } + } + + struct SoundOverrideMeta + { + public static DB6Meta Instance() + { + byte[] arraySizes = { 1, 1, 1, 1 }; + object[] fieldDefaults = { 0, 0, 0, 0 }; + return new DB6Meta(-1, arraySizes, fieldDefaults); + } + } + + struct SoundProviderPreferencesMeta + { + public static DB6Meta Instance() + { + byte[] arraySizes = { 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1 }; + object[] fieldDefaults = { "", 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0, 0, 0, 0, 0, 0, 0 }; + return new DB6Meta(-1, arraySizes, fieldDefaults); + } + } + + struct SourceInfoMeta + { + public static DB6Meta Instance() + { + byte[] arraySizes = { 1, 1, 1, 1 }; + object[] fieldDefaults = { 0, "", 0, 0 }; + return new DB6Meta(-1, arraySizes, fieldDefaults); + } + } + + struct SpamMessagesMeta + { + public static DB6Meta Instance() + { + byte[] arraySizes = { 1 }; + object[] fieldDefaults = { "" }; + return new DB6Meta(-1, arraySizes, fieldDefaults); + } + } + + struct SpecializationSpellsMeta + { + public static DB6Meta Instance() + { + byte[] arraySizes = { 1, 1, 1, 1, 1, 1 }; + object[] fieldDefaults = { 0, 0, "", 0, 0, 0 }; + return new DB6Meta(5, arraySizes, fieldDefaults); + } + } + + struct SpellMeta + { + public static DB6Meta Instance() + { + byte[] arraySizes = { 1, 1, 1, 1, 1, 1, 1 }; + object[] fieldDefaults = { "", "", "", "", 0, 0, 0 }; + return new DB6Meta(5, arraySizes, fieldDefaults); + } + } + + struct SpellActionBarPrefMeta + { + public static DB6Meta Instance() + { + byte[] arraySizes = { 1, 1 }; + object[] fieldDefaults = { 0, 0 }; + return new DB6Meta(-1, arraySizes, fieldDefaults); + } + } + + struct SpellActivationOverlayMeta + { + public static DB6Meta Instance() + { + byte[] arraySizes = { 1, 1, 1, 1, 4, 1, 1, 1 }; + object[] fieldDefaults = { 0, 0, 0, 0.0f, 0, 0, 0, 0 }; + return new DB6Meta(-1, arraySizes, fieldDefaults); + } + } + + struct SpellAuraOptionsMeta + { + public static DB6Meta Instance() + { + byte[] arraySizes = { 1, 1, 1, 1, 1, 1, 1, 1 }; + object[] fieldDefaults = { 0, 0, 0, 0, 0, 0, 0, 0 }; + return new DB6Meta(-1, arraySizes, fieldDefaults); + } + } + + struct SpellAuraRestrictionsMeta + { + public static DB6Meta Instance() + { + byte[] arraySizes = { 1, 1, 1, 1, 1, 1, 1, 1, 1, 1 }; + object[] fieldDefaults = { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }; + return new DB6Meta(-1, arraySizes, fieldDefaults); + } + } + + struct SpellAuraVisXChrSpecMeta + { + public static DB6Meta Instance() + { + byte[] arraySizes = { 1, 1 }; + object[] fieldDefaults = { 0, 0 }; + return new DB6Meta(-1, arraySizes, fieldDefaults); + } + } + + struct SpellAuraVisibilityMeta + { + public static DB6Meta Instance() + { + byte[] arraySizes = { 1, 1, 1, 1 }; + object[] fieldDefaults = { 0, 0, 0, 0 }; + return new DB6Meta(3, arraySizes, fieldDefaults); + } + } + + struct SpellCastTimesMeta + { + public static DB6Meta Instance() + { + byte[] arraySizes = { 1, 1, 1 }; + object[] fieldDefaults = { 0, 0, 0 }; + return new DB6Meta(-1, arraySizes, fieldDefaults); + } + } + + struct SpellCastingRequirementsMeta + { + public static DB6Meta Instance() + { + byte[] arraySizes = { 1, 1, 1, 1, 1, 1, 1 }; + object[] fieldDefaults = { 0, 0, 0, 0, 0, 0, 0 }; + return new DB6Meta(-1, arraySizes, fieldDefaults); + } + } + + struct SpellCategoriesMeta + { + public static DB6Meta Instance() + { + byte[] arraySizes = { 1, 1, 1, 1, 1, 1, 1, 1, 1 }; + object[] fieldDefaults = { 0, 0, 0, 0, 0, 0, 0, 0, 0 }; + return new DB6Meta(-1, arraySizes, fieldDefaults); + } + } + + struct SpellCategoryMeta + { + public static DB6Meta Instance() + { + byte[] arraySizes = { 1, 1, 1, 1, 1, 1 }; + object[] fieldDefaults = { "", 0, 0, 0, 0, 0 }; + return new DB6Meta(-1, arraySizes, fieldDefaults); + } + } + + struct SpellChainEffectsMeta + { + public static DB6Meta Instance() + { + byte[] arraySizes = { 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 3, 3, 3, 3, 1, 1, 1, 3, 1, 1, 1, 11, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1 }; + object[] fieldDefaults = { 0.0f, 0.0f, 0.0f, 0, 0, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0, 0.0f, 0.0f, "", "", 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }; + return new DB6Meta(-1, arraySizes, fieldDefaults); + } + } + + struct SpellClassOptionsMeta + { + public static DB6Meta Instance() + { + byte[] arraySizes = { 1, 4, 1, 1 }; + object[] fieldDefaults = { 0, 0, 0, 0 }; + return new DB6Meta(-1, arraySizes, fieldDefaults); + } + } + + struct SpellCooldownsMeta + { + public static DB6Meta Instance() + { + byte[] arraySizes = { 1, 1, 1, 1, 1 }; + object[] fieldDefaults = { 0, 0, 0, 0, 0 }; + return new DB6Meta(-1, arraySizes, fieldDefaults); + } + } + + struct SpellDescriptionVariablesMeta + { + public static DB6Meta Instance() + { + byte[] arraySizes = { 1 }; + object[] fieldDefaults = { "" }; + return new DB6Meta(-1, arraySizes, fieldDefaults); + } + } + + struct SpellDispelTypeMeta + { + public static DB6Meta Instance() + { + byte[] arraySizes = { 1, 1, 1, 1 }; + object[] fieldDefaults = { "", "", 0, 0 }; + return new DB6Meta(-1, arraySizes, fieldDefaults); + } + } + + struct SpellDurationMeta + { + public static DB6Meta Instance() + { + byte[] arraySizes = { 1, 1, 1 }; + object[] fieldDefaults = { 0, 0, 0 }; + return new DB6Meta(-1, arraySizes, fieldDefaults); + } + } + + struct SpellEffectMeta + { + public static DB6Meta Instance() + { + byte[] arraySizes = { 4, 1, 1, 1, 1, 1, 1, 2, 2, 2, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1 }; + object[] fieldDefaults = { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0.0f, 0, 0.0f, 1.0f, 0, 0, 0, 0, 0.0f, 0.0f, 0, 0.0f, 0, 0.0f, 1.0f }; + return new DB6Meta(1, arraySizes, fieldDefaults); + } + } + + struct SpellEffectEmissionMeta + { + public static DB6Meta Instance() + { + byte[] arraySizes = { 1, 1, 1, 1 }; + object[] fieldDefaults = { 0.0f, 0.0f, 0, 0 }; + return new DB6Meta(-1, arraySizes, fieldDefaults); + } + } + + struct SpellEffectGroupSizeMeta + { + public static DB6Meta Instance() + { + byte[] arraySizes = { 1, 1 }; + object[] fieldDefaults = { 0, 0.0f }; + return new DB6Meta(-1, arraySizes, fieldDefaults); + } + } + + struct SpellEffectScalingMeta + { + public static DB6Meta Instance() + { + byte[] arraySizes = { 1, 1, 1, 1 }; + object[] fieldDefaults = { 0.0f, 0.0f, 0.0f, 0 }; + return new DB6Meta(-1, arraySizes, fieldDefaults); + } + } + + struct SpellEquippedItemsMeta + { + public static DB6Meta Instance() + { + byte[] arraySizes = { 1, 1, 1, 1 }; + object[] fieldDefaults = { 0, 0, 0, 0 }; + return new DB6Meta(-1, arraySizes, fieldDefaults); + } + } + + struct SpellFlyoutMeta + { + public static DB6Meta Instance() + { + byte[] arraySizes = { 1, 1, 1, 1, 1, 1 }; + object[] fieldDefaults = { 0, "", "", 0, 0, 0 }; + return new DB6Meta(-1, arraySizes, fieldDefaults); + } + } + + struct SpellFlyoutItemMeta + { + public static DB6Meta Instance() + { + byte[] arraySizes = { 1, 1, 1 }; + object[] fieldDefaults = { 0, 0, 0 }; + return new DB6Meta(-1, arraySizes, fieldDefaults); + } + } + + struct SpellFocusObjectMeta + { + public static DB6Meta Instance() + { + byte[] arraySizes = { 1 }; + object[] fieldDefaults = { "" }; + return new DB6Meta(-1, arraySizes, fieldDefaults); + } + } + + struct SpellInterruptsMeta + { + public static DB6Meta Instance() + { + byte[] arraySizes = { 1, 2, 2, 1, 1 }; + object[] fieldDefaults = { 0, 0, 0, 0, 0 }; + return new DB6Meta(-1, arraySizes, fieldDefaults); + } + } + + struct SpellItemEnchantmentMeta + { + public static DB6Meta Instance() + { + byte[] arraySizes = { 3, 1, 3, 1, 1, 3, 1, 1, 1, 1, 1, 1, 3, 1, 1, 1, 1, 1, 1 }; + object[] fieldDefaults = { 0, "", 0.0f, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }; + return new DB6Meta(-1, arraySizes, fieldDefaults); + } + } + + struct SpellItemEnchantmentConditionMeta + { + public static DB6Meta Instance() + { + byte[] arraySizes = { 5, 5, 5, 5, 5, 5 }; + object[] fieldDefaults = { 0, 0, 0, 0, 0, 0 }; + return new DB6Meta(-1, arraySizes, fieldDefaults); + } + } + + struct SpellKeyboundOverrideMeta + { + public static DB6Meta Instance() + { + byte[] arraySizes = { 1, 1, 1 }; + object[] fieldDefaults = { 0, "", 0 }; + return new DB6Meta(-1, arraySizes, fieldDefaults); + } + } + + struct SpellLabelMeta + { + public static DB6Meta Instance() + { + byte[] arraySizes = { 1, 1 }; + object[] fieldDefaults = { 0, 0 }; + return new DB6Meta(-1, arraySizes, fieldDefaults); + } + } + + struct SpellLearnSpellMeta + { + public static DB6Meta Instance() + { + byte[] arraySizes = { 1, 1, 1 }; + object[] fieldDefaults = { 0, 0, 0 }; + return new DB6Meta(-1, arraySizes, fieldDefaults); + } + } + + struct SpellLevelsMeta + { + public static DB6Meta Instance() + { + byte[] arraySizes = { 1, 1, 1, 1, 1, 1 }; + object[] fieldDefaults = { 0, 0, 0, 0, 0, 0 }; + return new DB6Meta(-1, arraySizes, fieldDefaults); + } + } + + struct SpellMechanicMeta + { + public static DB6Meta Instance() + { + byte[] arraySizes = { 1 }; + object[] fieldDefaults = { "" }; + return new DB6Meta(-1, arraySizes, fieldDefaults); + } + } + + struct SpellMiscMeta + { + public static DB6Meta Instance() + { + byte[] arraySizes = { 14, 1, 1, 1, 1, 1, 1, 1, 1 }; + object[] fieldDefaults = { 0, 0.0f, 0.0f, 0, 0, 0, 0, 0, 0 }; + return new DB6Meta(-1, arraySizes, fieldDefaults); + } + } + + struct SpellMiscDifficultyMeta + { + public static DB6Meta Instance() + { + byte[] arraySizes = { 1, 1, 1 }; + object[] fieldDefaults = { 0, 0, 0 }; + return new DB6Meta(2, arraySizes, fieldDefaults); + } + } + + struct SpellMissileMeta + { + public static DB6Meta Instance() + { + byte[] arraySizes = { 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1 }; + object[] fieldDefaults = { 0, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0 }; + return new DB6Meta(-1, arraySizes, fieldDefaults); + } + } + + struct SpellMissileMotionMeta + { + public static DB6Meta Instance() + { + byte[] arraySizes = { 1, 1, 1, 1 }; + object[] fieldDefaults = { "", "", 0, 0 }; + return new DB6Meta(-1, arraySizes, fieldDefaults); + } + } + + struct SpellPowerMeta + { + public static DB6Meta Instance() + { + byte[] arraySizes = { 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1 }; + object[] fieldDefaults = { 0, 0, 0.0f, 0.0f, 0, 0.0f, 0, 0, 0, 0, 0, 0, 0, 0 }; + return new DB6Meta(8, arraySizes, fieldDefaults); + } + } + + struct SpellPowerDifficultyMeta + { + public static DB6Meta Instance() + { + byte[] arraySizes = { 1, 1, 1 }; + object[] fieldDefaults = { 0, 0, 0 }; + return new DB6Meta(2, arraySizes, fieldDefaults); + } + } + + struct SpellProceduralEffectMeta + { + public static DB6Meta Instance() + { + byte[] arraySizes = { 4, 1, 1 }; + object[] fieldDefaults = { 0.0f, 0, 0 }; + return new DB6Meta(2, arraySizes, fieldDefaults); + } + } + + struct SpellProcsPerMinuteMeta + { + public static DB6Meta Instance() + { + byte[] arraySizes = { 1, 1 }; + object[] fieldDefaults = { 0.0f, 0 }; + return new DB6Meta(-1, arraySizes, fieldDefaults); + } + } + + struct SpellProcsPerMinuteModMeta + { + public static DB6Meta Instance() + { + byte[] arraySizes = { 1, 1, 1, 1 }; + object[] fieldDefaults = { 0.0f, 0, 0, 0 }; + return new DB6Meta(-1, arraySizes, fieldDefaults); + } + } + + struct SpellRadiusMeta + { + public static DB6Meta Instance() + { + byte[] arraySizes = { 1, 1, 1, 1 }; + object[] fieldDefaults = { 0.0f, 0.0f, 0.0f, 0.0f }; + return new DB6Meta(-1, arraySizes, fieldDefaults); + } + } + + struct SpellRangeMeta + { + public static DB6Meta Instance() + { + byte[] arraySizes = { 2, 2, 1, 1, 1 }; + object[] fieldDefaults = { 0.0f, 0.0f, "", "", 0 }; + return new DB6Meta(-1, arraySizes, fieldDefaults); + } + } + + struct SpellReagentsMeta + { + public static DB6Meta Instance() + { + byte[] arraySizes = { 1, 8, 8 }; + object[] fieldDefaults = { 0, 0, 0 }; + return new DB6Meta(-1, arraySizes, fieldDefaults); + } + } + + struct SpellReagentsCurrencyMeta + { + public static DB6Meta Instance() + { + byte[] arraySizes = { 1, 1, 1 }; + object[] fieldDefaults = { 0, 0, 0 }; + return new DB6Meta(-1, arraySizes, fieldDefaults); + } + } + + struct SpellScalingMeta + { + public static DB6Meta Instance() + { + byte[] arraySizes = { 1, 1, 1, 1, 1 }; + object[] fieldDefaults = { 0, 0, 0, 0, 0 }; + return new DB6Meta(-1, arraySizes, fieldDefaults); + } + } + + struct SpellShapeshiftMeta + { + public static DB6Meta Instance() + { + byte[] arraySizes = { 1, 2, 2, 1 }; + object[] fieldDefaults = { 0, 0, 0, 0 }; + return new DB6Meta(-1, arraySizes, fieldDefaults); + } + } + + struct SpellShapeshiftFormMeta + { + public static DB6Meta Instance() + { + byte[] arraySizes = { 1, 1, 1, 1, 1, 1, 1, 1, 4, 8 }; + object[] fieldDefaults = { "", 0.0f, 0, 0, 0, 0, 0, 0, 0, 0 }; + return new DB6Meta(-1, arraySizes, fieldDefaults); + } + } + + struct SpellSpecialUnitEffectMeta + { + public static DB6Meta Instance() + { + byte[] arraySizes = { 1, 1 }; + object[] fieldDefaults = { 0, 0 }; + return new DB6Meta(-1, arraySizes, fieldDefaults); + } + } + + struct SpellTargetRestrictionsMeta + { + public static DB6Meta Instance() + { + byte[] arraySizes = { 1, 1, 1, 1, 1, 1, 1, 1 }; + object[] fieldDefaults = { 0, 0.0f, 0.0f, 0, 0, 0, 0, 0 }; + return new DB6Meta(-1, arraySizes, fieldDefaults); + } + } + + struct SpellTotemsMeta + { + public static DB6Meta Instance() + { + byte[] arraySizes = { 1, 2, 2 }; + object[] fieldDefaults = { 0, 0, 0 }; + return new DB6Meta(-1, arraySizes, fieldDefaults); + } + } + + struct SpellVisualMeta + { + public static DB6Meta Instance() + { + byte[] arraySizes = { 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 3, 3, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1 }; + object[] fieldDefaults = { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0.0f, 0.0f, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }; + return new DB6Meta(26, arraySizes, fieldDefaults); + } + } + + struct SpellVisualAnimMeta + { + public static DB6Meta Instance() + { + byte[] arraySizes = { 1, 1, 1 }; + object[] fieldDefaults = { 0, 0, 0 }; + return new DB6Meta(-1, arraySizes, fieldDefaults); + } + } + + struct SpellVisualColorEffectMeta + { + public static DB6Meta Instance() + { + byte[] arraySizes = { 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1 }; + object[] fieldDefaults = { 0.0f, 0, 0.0f, 0, 0, 0, 0, 0, 0, 0, 0 }; + return new DB6Meta(-1, arraySizes, fieldDefaults); + } + } + + struct SpellVisualEffectNameMeta + { + public static DB6Meta Instance() + { + byte[] arraySizes = { 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1 }; + object[] fieldDefaults = { "", 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0, 0, 0, 0, 0, 0 }; + return new DB6Meta(-1, arraySizes, fieldDefaults); + } + } + + struct SpellVisualKitMeta + { + public static DB6Meta Instance() + { + byte[] arraySizes = { 1, 1, 1, 1, 1, 1 }; + object[] fieldDefaults = { 0, 0.0f, 0, 0, 0, 0 }; + return new DB6Meta(4, arraySizes, fieldDefaults); + } + } + + struct SpellVisualKitAreaModelMeta + { + public static DB6Meta Instance() + { + byte[] arraySizes = { 1, 1, 1, 1, 1, 1 }; + object[] fieldDefaults = { 0, 0.0f, 0.0f, 0.0f, 0, 0 }; + return new DB6Meta(-1, arraySizes, fieldDefaults); + } + } + + struct SpellVisualKitEffectMeta + { + public static DB6Meta Instance() + { + byte[] arraySizes = { 1, 1, 1 }; + object[] fieldDefaults = { 0, 0, 0 }; + return new DB6Meta(-1, arraySizes, fieldDefaults); + } + } + + struct SpellVisualKitModelAttachMeta + { + public static DB6Meta Instance() + { + byte[] arraySizes = { 1, 3, 3, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1 }; + object[] fieldDefaults = { 0, 0.0f, 0.0f, 0, 0, 0, 0, 0, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 1.0f, 0.0f, 65535, 65535, 65535, 0, 0, 0.0f }; + return new DB6Meta(6, arraySizes, fieldDefaults); + } + } + + struct SpellVisualMissileMeta + { + public static DB6Meta Instance() + { + byte[] arraySizes = { 1, 1, 1, 3, 3, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1 }; + object[] fieldDefaults = { 0, 0, 0, 0.0f, 0.0f, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }; + return new DB6Meta(13, arraySizes, fieldDefaults); + } + } + + struct SpellXSpellVisualMeta + { + public static DB6Meta Instance() + { + byte[] arraySizes = { 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1 }; + object[] fieldDefaults = { 0, 0, 0, 1.0f, 0, 0, 0, 0, 0, 0, 0, 0, 0 }; + return new DB6Meta(2, arraySizes, fieldDefaults); + } + } + + struct StartupFilesMeta + { + public static DB6Meta Instance() + { + byte[] arraySizes = { 1, 1 }; + object[] fieldDefaults = { 0, 0 }; + return new DB6Meta(-1, arraySizes, fieldDefaults); + } + } + + struct Startup_StringsMeta + { + public static DB6Meta Instance() + { + byte[] arraySizes = { 1, 1 }; + object[] fieldDefaults = { "", "" }; + return new DB6Meta(-1, arraySizes, fieldDefaults); + } + } + + struct StationeryMeta + { + public static DB6Meta Instance() + { + byte[] arraySizes = { 1, 1, 1 }; + object[] fieldDefaults = { "", 0, 0 }; + return new DB6Meta(-1, arraySizes, fieldDefaults); + } + } + + struct StringLookupsMeta + { + public static DB6Meta Instance() + { + byte[] arraySizes = { 1 }; + object[] fieldDefaults = { "" }; + return new DB6Meta(-1, arraySizes, fieldDefaults); + } + } + + struct SummonPropertiesMeta + { + public static DB6Meta Instance() + { + byte[] arraySizes = { 1, 1, 1, 1, 1 }; + object[] fieldDefaults = { 0, 0, 0, 0, 0 }; + return new DB6Meta(-1, arraySizes, fieldDefaults); + } + } + + struct TactKeyMeta + { + public static DB6Meta Instance() + { + byte[] arraySizes = { 16 }; + object[] fieldDefaults = { 0 }; + return new DB6Meta(-1, arraySizes, fieldDefaults); + } + } + + struct TactKeyLookupMeta + { + public static DB6Meta Instance() + { + byte[] arraySizes = { 8 }; + object[] fieldDefaults = { 0 }; + return new DB6Meta(-1, arraySizes, fieldDefaults); + } + } + + struct TalentMeta + { + public static DB6Meta Instance() + { + byte[] arraySizes = { 1, 1, 1, 1, 1, 1, 1, 2, 1 }; + object[] fieldDefaults = { 0, 0, "", 0, 0, 0, 0, 0, 0 }; + return new DB6Meta(-1, arraySizes, fieldDefaults); + } + } + + struct TaxiNodesMeta + { + public static DB6Meta Instance() + { + byte[] arraySizes = { 3, 1, 2, 2, 1, 1, 1, 1, 1 }; + object[] fieldDefaults = { 0.0f, "", 0, 0.0f, 0, 0, 0, 0, 0 }; + return new DB6Meta(8, arraySizes, fieldDefaults); + } + } + + struct TaxiPathMeta + { + public static DB6Meta Instance() + { + byte[] arraySizes = { 1, 1, 1, 1 }; + object[] fieldDefaults = { 0, 0, 0, 0 }; + return new DB6Meta(2, arraySizes, fieldDefaults); + } + } + + struct TaxiPathNodeMeta + { + public static DB6Meta Instance() + { + byte[] arraySizes = { 3, 1, 1, 1, 1, 1, 1, 1, 1 }; + object[] fieldDefaults = { 0.0f, 0, 0, 0, 0, 0, 0, 0, 0 }; + return new DB6Meta(8, arraySizes, fieldDefaults); + } + } + + struct TerrainMaterialMeta + { + public static DB6Meta Instance() + { + byte[] arraySizes = { 1, 1, 1 }; + object[] fieldDefaults = { "", "", 0 }; + return new DB6Meta(-1, arraySizes, fieldDefaults); + } + } + + struct TerrainTypeMeta + { + public static DB6Meta Instance() + { + byte[] arraySizes = { 1, 1, 1, 1, 1 }; + object[] fieldDefaults = { "", 0, 0, 0, 0 }; + return new DB6Meta(-1, arraySizes, fieldDefaults); + } + } + + struct TerrainTypeSoundsMeta + { + public static DB6Meta Instance() + { + byte[] arraySizes = { 1 }; + object[] fieldDefaults = { "" }; + return new DB6Meta(-1, arraySizes, fieldDefaults); + } + } + + struct TextureBlendSetMeta + { + public static DB6Meta Instance() + { + byte[] arraySizes = { 3, 3, 3, 3, 3, 4, 1, 1, 1, 1 }; + object[] fieldDefaults = { 0, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0, 0, 0, 0 }; + return new DB6Meta(-1, arraySizes, fieldDefaults); + } + } + + struct TextureFileDataMeta + { + public static DB6Meta Instance() + { + byte[] arraySizes = { 1, 1, 1 }; + object[] fieldDefaults = { 0, 0, 0 }; + return new DB6Meta(2, arraySizes, fieldDefaults); + } + } + + struct TotemCategoryMeta + { + public static DB6Meta Instance() + { + byte[] arraySizes = { 1, 1, 1 }; + object[] fieldDefaults = { "", 0, 0 }; + return new DB6Meta(-1, arraySizes, fieldDefaults); + } + } + + struct ToyMeta + { + public static DB6Meta Instance() + { + byte[] arraySizes = { 1, 1, 1, 1, 1 }; + object[] fieldDefaults = { 0, "", 0, 0, 0 }; + return new DB6Meta(4, arraySizes, fieldDefaults); + } + } + + struct TradeSkillCategoryMeta + { + public static DB6Meta Instance() + { + byte[] arraySizes = { 1, 1, 1, 1, 1 }; + object[] fieldDefaults = { "", 0, 0, 0, 0 }; + return new DB6Meta(-1, arraySizes, fieldDefaults); + } + } + + struct TradeSkillItemMeta + { + public static DB6Meta Instance() + { + byte[] arraySizes = { 1, 1 }; + object[] fieldDefaults = { 0, 0 }; + return new DB6Meta(-1, arraySizes, fieldDefaults); + } + } + + struct TransformMatrixMeta + { + public static DB6Meta Instance() + { + byte[] arraySizes = { 3, 1, 1, 1, 1 }; + object[] fieldDefaults = { 0.0f, 0.0f, 0.0f, 0.0f, 0.0f }; + return new DB6Meta(-1, arraySizes, fieldDefaults); + } + } + + struct TransmogHolidayMeta + { + public static DB6Meta Instance() + { + byte[] arraySizes = { 1, 1 }; + object[] fieldDefaults = { 0, 0 }; + return new DB6Meta(0, arraySizes, fieldDefaults); + } + } + + struct TransmogSetMeta + { + public static DB6Meta Instance() + { + byte[] arraySizes = { 1, 1, 1, 1, 1, 1, 1, 1, 1, 1 }; + object[] fieldDefaults = { "", 0, 0, 0, 0, 0, 0, 0, 0, 0 }; + return new DB6Meta(4, arraySizes, fieldDefaults); + } + } + + struct TransmogSetGroupMeta + { + public static DB6Meta Instance() + { + byte[] arraySizes = { 1, 1 }; + object[] fieldDefaults = { "", 0 }; + return new DB6Meta(1, arraySizes, fieldDefaults); + } + } + + struct TransmogSetItemMeta + { + public static DB6Meta Instance() + { + byte[] arraySizes = { 1, 1, 1, 1 }; + object[] fieldDefaults = { 0, 0, 0, 0 }; + return new DB6Meta(0, arraySizes, fieldDefaults); + } + } + + struct TransportAnimationMeta + { + public static DB6Meta Instance() + { + byte[] arraySizes = { 1, 1, 3, 1 }; + object[] fieldDefaults = { 0, 0, 0.0f, 0 }; + return new DB6Meta(-1, arraySizes, fieldDefaults); + } + } + + struct TransportPhysicsMeta + { + public static DB6Meta Instance() + { + byte[] arraySizes = { 1, 1, 1, 1, 1, 1, 1, 1, 1, 1 }; + object[] fieldDefaults = { 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f }; + return new DB6Meta(-1, arraySizes, fieldDefaults); + } + } + + struct TransportRotationMeta + { + public static DB6Meta Instance() + { + byte[] arraySizes = { 1, 1, 4 }; + object[] fieldDefaults = { 0, 0, 0.0f }; + return new DB6Meta(-1, arraySizes, fieldDefaults); + } + } + + struct TrophyMeta + { + public static DB6Meta Instance() + { + byte[] arraySizes = { 1, 1, 1, 1 }; + object[] fieldDefaults = { "", 0, 0, 0 }; + return new DB6Meta(-1, arraySizes, fieldDefaults); + } + } + + struct UiCamFbackTransmogChrRaceMeta + { + public static DB6Meta Instance() + { + byte[] arraySizes = { 1, 1, 1, 1, 1 }; + object[] fieldDefaults = { 0, 0, 0, 0, 0 }; + return new DB6Meta(-1, arraySizes, fieldDefaults); + } + } + + struct UiCamFbackTransmogWeaponMeta + { + public static DB6Meta Instance() + { + byte[] arraySizes = { 1, 1, 1, 1 }; + object[] fieldDefaults = { 0, 0, 0, 0 }; + return new DB6Meta(-1, arraySizes, fieldDefaults); + } + } + + struct UiCameraMeta + { + public static DB6Meta Instance() + { + byte[] arraySizes = { 1, 3, 3, 3, 1, 1, 1, 1, 1 }; + object[] fieldDefaults = { "", 0.0f, 0.0f, 0.0f, 0, 0, 0, 0, 0 }; + return new DB6Meta(-1, arraySizes, fieldDefaults); + } + } + + struct UiCameraTypeMeta + { + public static DB6Meta Instance() + { + byte[] arraySizes = { 1, 1, 1 }; + object[] fieldDefaults = { "", 0, 0 }; + return new DB6Meta(-1, arraySizes, fieldDefaults); + } + } + + struct UiMapPOIMeta + { + public static DB6Meta Instance() + { + byte[] arraySizes = { 1, 3, 1, 1, 1, 1, 1 }; + object[] fieldDefaults = { 0, 0.0f, 0, 0, 0, 0, 0 }; + return new DB6Meta(6, arraySizes, fieldDefaults); + } + } + + struct UiModelSceneMeta + { + public static DB6Meta Instance() + { + byte[] arraySizes = { 1, 1 }; + object[] fieldDefaults = { 0, 0 }; + return new DB6Meta(-1, arraySizes, fieldDefaults); + } + } + + struct UiModelSceneActorMeta + { + public static DB6Meta Instance() + { + byte[] arraySizes = { 1, 3, 1, 1, 1, 1, 1, 1, 1, 1 }; + object[] fieldDefaults = { "", 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0, 0, 0, 0 }; + return new DB6Meta(7, arraySizes, fieldDefaults); + } + } + + struct UiModelSceneActorDisplayMeta + { + public static DB6Meta Instance() + { + byte[] arraySizes = { 1, 1, 1, 1, 1 }; + object[] fieldDefaults = { 0.0f, 0.0f, 0.0f, 0, 0 }; + return new DB6Meta(-1, arraySizes, fieldDefaults); + } + } + + struct UiModelSceneCameraMeta + { + public static DB6Meta Instance() + { + byte[] arraySizes = { 1, 3, 3, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1 }; + object[] fieldDefaults = { "", 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0, 0, 0, 0 }; + return new DB6Meta(14, arraySizes, fieldDefaults); + } + } + + struct UiTextureAtlasMeta + { + public static DB6Meta Instance() + { + byte[] arraySizes = { 1, 1, 1 }; + object[] fieldDefaults = { 0, 0, 0 }; + return new DB6Meta(-1, arraySizes, fieldDefaults); + } + } + + struct UiTextureAtlasMemberMeta + { + public static DB6Meta Instance() + { + byte[] arraySizes = { 1, 1, 1, 1, 1, 1, 1 }; + object[] fieldDefaults = { "", 0, 0, 0, 0, 0, 0 }; + return new DB6Meta(-1, arraySizes, fieldDefaults); + } + } + + struct UiTextureKitMeta + { + public static DB6Meta Instance() + { + byte[] arraySizes = { 1 }; + object[] fieldDefaults = { "" }; + return new DB6Meta(-1, arraySizes, fieldDefaults); + } + } + + struct UnitBloodMeta + { + public static DB6Meta Instance() + { + byte[] arraySizes = { 5, 1, 1, 1, 1, 1, 1 }; + object[] fieldDefaults = { "", 0, 0, 0, 0, 0, 0 }; + return new DB6Meta(-1, arraySizes, fieldDefaults); + } + } + + struct UnitBloodLevelsMeta + { + public static DB6Meta Instance() + { + byte[] arraySizes = { 3 }; + object[] fieldDefaults = { 0 }; + return new DB6Meta(-1, arraySizes, fieldDefaults); + } + } + + struct UnitConditionMeta + { + public static DB6Meta Instance() + { + byte[] arraySizes = { 8, 1, 8, 8 }; + object[] fieldDefaults = { 0, 0, 0, 0 }; + return new DB6Meta(-1, arraySizes, fieldDefaults); + } + } + + struct UnitPowerBarMeta + { + public static DB6Meta Instance() + { + byte[] arraySizes = { 1, 1, 6, 6, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1 }; + object[] fieldDefaults = { 0.0f, 0.0f, 0, 0, "", "", "", "", 0.0f, 0.0f, 0, 0, 0, 0, 0, 0 }; + return new DB6Meta(-1, arraySizes, fieldDefaults); + } + } + + struct UnitTestMeta + { + public static DB6Meta Instance() + { + byte[] arraySizes = { 1, 1, 1, 1, 1 }; + object[] fieldDefaults = { "", "", 0, 0, 0 }; + return new DB6Meta(2, arraySizes, fieldDefaults); + } + } + + struct VehicleMeta + { + public static DB6Meta Instance() + { + byte[] arraySizes = { 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2, 1, 1, 2, 1, 1, 8, 1, 3, 1, 1 }; + object[] fieldDefaults = { 0, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, "", "", "", 0.0f, 0.0f, 0, 0, 0, 0, 0 }; + return new DB6Meta(-1, arraySizes, fieldDefaults); + } + } + + struct VehicleSeatMeta + { + public static DB6Meta Instance() + { + byte[] arraySizes = { 1, 1, 1, 3, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 3, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1 }; + object[] fieldDefaults = { 0, 0, 0, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }; + return new DB6Meta(-1, arraySizes, fieldDefaults); + } + } + + struct VehicleUIIndSeatMeta + { + public static DB6Meta Instance() + { + byte[] arraySizes = { 1, 1, 1, 1 }; + object[] fieldDefaults = { 0.0f, 0.0f, 0, 0 }; + return new DB6Meta(-1, arraySizes, fieldDefaults); + } + } + + struct VehicleUIIndicatorMeta + { + public static DB6Meta Instance() + { + byte[] arraySizes = { 1 }; + object[] fieldDefaults = { "" }; + return new DB6Meta(-1, arraySizes, fieldDefaults); + } + } + + struct VideoHardwareMeta + { + public static DB6Meta Instance() + { + byte[] arraySizes = { 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1 }; + object[] fieldDefaults = { "", "", 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }; + return new DB6Meta(14, arraySizes, fieldDefaults); + } + } + + struct VignetteMeta + { + public static DB6Meta Instance() + { + byte[] arraySizes = { 1, 1, 1, 1, 1, 1, 1 }; + object[] fieldDefaults = { "", 0.0f, 0.0f, 0, 0, 0, 0 }; + return new DB6Meta(-1, arraySizes, fieldDefaults); + } + } + + struct VocalUISoundsMeta + { + public static DB6Meta Instance() + { + byte[] arraySizes = { 1, 1, 1, 2 }; + object[] fieldDefaults = { 0, 0, 0, 0 }; + return new DB6Meta(-1, arraySizes, fieldDefaults); + } + } + + struct WMOAreaTableMeta + { + public static DB6Meta Instance() + { + byte[] arraySizes = { 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1 }; + object[] fieldDefaults = { 0, "", 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }; + return new DB6Meta(13, arraySizes, fieldDefaults); + } + } + + struct WbAccessControlListMeta + { + public static DB6Meta Instance() + { + byte[] arraySizes = { 1, 1, 1, 1, 1 }; + object[] fieldDefaults = { "", 0, 0, 0, 0 }; + return new DB6Meta(-1, arraySizes, fieldDefaults); + } + } + + struct WbCertWhitelistMeta + { + public static DB6Meta Instance() + { + byte[] arraySizes = { 1, 1, 1, 1 }; + object[] fieldDefaults = { "", 0, 0, 0 }; + return new DB6Meta(-1, arraySizes, fieldDefaults); + } + } + + struct WeaponImpactSoundsMeta + { + public static DB6Meta Instance() + { + byte[] arraySizes = { 1, 1, 1, 11, 11, 11, 11 }; + object[] fieldDefaults = { 0, 0, 0, 0, 0, 0, 0 }; + return new DB6Meta(-1, arraySizes, fieldDefaults); + } + } + + struct WeaponSwingSounds2Meta + { + public static DB6Meta Instance() + { + byte[] arraySizes = { 1, 1, 1 }; + object[] fieldDefaults = { 0, 0, 0 }; + return new DB6Meta(-1, arraySizes, fieldDefaults); + } + } + + struct WeaponTrailMeta + { + public static DB6Meta Instance() + { + byte[] arraySizes = { 1, 1, 1, 1, 3, 3, 3, 3, 3 }; + object[] fieldDefaults = { 0, 0.0f, 0.0f, 0.0f, 0, 0.0f, 0.0f, 0.0f, 0.0f }; + return new DB6Meta(-1, arraySizes, fieldDefaults); + } + } + + struct WeaponTrailModelDefMeta + { + public static DB6Meta Instance() + { + byte[] arraySizes = { 1, 1, 1 }; + object[] fieldDefaults = { 0, 0, 0 }; + return new DB6Meta(-1, arraySizes, fieldDefaults); + } + } + + struct WeaponTrailParamMeta + { + public static DB6Meta Instance() + { + byte[] arraySizes = { 1, 1, 1, 1, 1, 1, 1, 1, 1, 1 }; + object[] fieldDefaults = { 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0, 0, 0, 0, 0 }; + return new DB6Meta(-1, arraySizes, fieldDefaults); + } + } + + struct WeatherMeta + { + public static DB6Meta Instance() + { + byte[] arraySizes = { 2, 1, 3, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1 }; + object[] fieldDefaults = { 0.0f, 0.0f, 0.0f, "", 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0, 0, 0, 0, 0 }; + return new DB6Meta(-1, arraySizes, fieldDefaults); + } + } + + struct WindSettingsMeta + { + public static DB6Meta Instance() + { + byte[] arraySizes = { 1, 3, 1, 1, 3, 1, 3, 1, 1, 1 }; + object[] fieldDefaults = { 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0 }; + return new DB6Meta(-1, arraySizes, fieldDefaults); + } + } + + struct WmoMinimapTextureMeta + { + public static DB6Meta Instance() + { + byte[] arraySizes = { 1, 1, 1, 1, 1 }; + object[] fieldDefaults = { 0, 0, 0, 0, 0 }; + return new DB6Meta(-1, arraySizes, fieldDefaults); + } + } + + struct WorldBossLockoutMeta + { + public static DB6Meta Instance() + { + byte[] arraySizes = { 1, 1 }; + object[] fieldDefaults = { "", 0 }; + return new DB6Meta(-1, arraySizes, fieldDefaults); + } + } + + struct WorldChunkSoundsMeta + { + public static DB6Meta Instance() + { + byte[] arraySizes = { 1, 1, 1, 1, 1, 1 }; + object[] fieldDefaults = { 0, 0, 0, 0, 0, 0 }; + return new DB6Meta(-1, arraySizes, fieldDefaults); + } + } + + struct WorldEffectMeta + { + public static DB6Meta Instance() + { + byte[] arraySizes = { 1, 1, 1, 1, 1, 1 }; + object[] fieldDefaults = { 0, 0, 0, 0, 0, 0 }; + return new DB6Meta(-1, arraySizes, fieldDefaults); + } + } + + struct WorldElapsedTimerMeta + { + public static DB6Meta Instance() + { + byte[] arraySizes = { 1, 1, 1 }; + object[] fieldDefaults = { "", 0, 0 }; + return new DB6Meta(-1, arraySizes, fieldDefaults); + } + } + + struct WorldMapAreaMeta + { + public static DB6Meta Instance() + { + byte[] arraySizes = { 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1 }; + object[] fieldDefaults = { "", 0.0f, 0.0f, 0.0f, 0.0f, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }; + return new DB6Meta(15, arraySizes, fieldDefaults); + } + } + + struct WorldMapContinentMeta + { + public static DB6Meta Instance() + { + byte[] arraySizes = { 2, 1, 2, 2, 1, 1, 1, 1, 1, 1, 1 }; + object[] fieldDefaults = { 0.0f, 0.0f, 0.0f, 0.0f, 0, 0, 0, 0, 0, 0, 0 }; + return new DB6Meta(-1, arraySizes, fieldDefaults); + } + } + + struct WorldMapOverlayMeta + { + public static DB6Meta Instance() + { + byte[] arraySizes = { 1, 1, 1, 1, 1, 4, 1, 1, 1, 1, 1, 1, 1, 1 }; + object[] fieldDefaults = { 0, "", 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }; + return new DB6Meta(0, arraySizes, fieldDefaults); + } + } + + struct WorldMapTransformsMeta + { + public static DB6Meta Instance() + { + byte[] arraySizes = { 6, 2, 1, 1, 1, 1, 1, 1, 1 }; + object[] fieldDefaults = { 0.0f, 0.0f, 0.0f, 0, 0, 0, 0, 0, 0 }; + return new DB6Meta(-1, arraySizes, fieldDefaults); + } + } + + struct WorldSafeLocsMeta + { + public static DB6Meta Instance() + { + byte[] arraySizes = { 3, 1, 1, 1 }; + object[] fieldDefaults = { 0.0f, 0.0f, "", 0 }; + return new DB6Meta(-1, arraySizes, fieldDefaults); + } + } + + struct WorldStateExpressionMeta + { + public static DB6Meta Instance() + { + byte[] arraySizes = { 1 }; + object[] fieldDefaults = { "" }; + return new DB6Meta(-1, arraySizes, fieldDefaults); + } + } + + struct WorldStateUIMeta + { + public static DB6Meta Instance() + { + byte[] arraySizes = { 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 3, 1, 1, 1, 1 }; + object[] fieldDefaults = { "", "", "", "", "", "", 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }; + return new DB6Meta(15, arraySizes, fieldDefaults); + } + } + + struct WorldStateZoneSoundsMeta + { + public static DB6Meta Instance() + { + byte[] arraySizes = { 1, 1, 1, 1, 1, 1, 1, 1 }; + object[] fieldDefaults = { 0, 0, 0, 0, 0, 0, 0, 0 }; + return new DB6Meta(-1, arraySizes, fieldDefaults); + } + } + + struct World_PVP_AreaMeta + { + public static DB6Meta Instance() + { + byte[] arraySizes = { 1, 1, 1, 1, 1, 1, 1 }; + object[] fieldDefaults = { 0, 0, 0, 0, 0, 0, 0 }; + return new DB6Meta(-1, arraySizes, fieldDefaults); + } + } + + struct ZoneIntroMusicTableMeta + { + public static DB6Meta Instance() + { + byte[] arraySizes = { 1, 1, 1, 1 }; + object[] fieldDefaults = { "", 0, 0, 0 }; + return new DB6Meta(-1, arraySizes, fieldDefaults); + } + } + + struct ZoneLightMeta + { + public static DB6Meta Instance() + { + byte[] arraySizes = { 1, 1, 1 }; + object[] fieldDefaults = { "", 0, 0 }; + return new DB6Meta(-1, arraySizes, fieldDefaults); + } + } + + struct ZoneLightPointMeta + { + public static DB6Meta Instance() + { + byte[] arraySizes = { 2, 1, 1 }; + object[] fieldDefaults = { 0.0f, 0, 0 }; + return new DB6Meta(-1, arraySizes, fieldDefaults); + } + } + + struct ZoneMusicMeta + { + public static DB6Meta Instance() + { + byte[] arraySizes = { 1, 2, 2, 2 }; + object[] fieldDefaults = { "", 0, 0, 0 }; + return new DB6Meta(-1, arraySizes, fieldDefaults); + } + } +} diff --git a/Game/DataStorage/ClientReader/DB6Storage.cs b/Game/DataStorage/ClientReader/DB6Storage.cs new file mode 100644 index 000000000..0f82fb114 --- /dev/null +++ b/Game/DataStorage/ClientReader/DB6Storage.cs @@ -0,0 +1,359 @@ +/* + * Copyright (C) 2012-2017 CypherCore + * + * 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, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +using Framework.Constants; +using Framework.Database; +using Framework.GameMath; +using Framework.IO; +using System; +using System.Collections.Generic; +using System.Reflection; + +namespace Game.DataStorage +{ + public interface IDB2Storage + { + bool HasRecord(uint id); + + void WriteRecord(uint id, LocaleConstant locale, ByteBuffer buffer); + + void EraseRecord(uint id); + } + + [Serializable] + public class DB6Storage : Dictionary, IDB2Storage where T : new() + { + public void LoadData(uint indexField, DBClientHelper[] helpers, HotfixStatements preparedStatement, HotfixStatements preparedStatementLocale) + { + SQLResult result = DB.Hotfix.Query(DB.Hotfix.GetPreparedStatement(preparedStatement)); + if (!result.IsEmpty()) + { + do + { + var idValue = result.Read((int)indexField); + + var obj = new T(); + int index = 0; + for (var fieldIndex = 0; fieldIndex < helpers.Length; fieldIndex++) + { + var helper = helpers[fieldIndex]; + if (helper.IsArray) + { + Array array = (Array)helper.Getter(obj); + for (var i = 0; i < array.Length; ++i) + { + switch (Type.GetTypeCode(helper.RealType)) + { + case TypeCode.SByte: + helper.SetValue(array, result.Read(index++), i); + break; + case TypeCode.Byte: + helper.SetValue(array, result.Read(index++), i); + break; + case TypeCode.Int16: + helper.SetValue(array, result.Read(index++), i); + break; + case TypeCode.UInt16: + helper.SetValue(array, result.Read(index++), i); + break; + case TypeCode.Int32: + helper.SetValue(array, result.Read(index++), i); + break; + case TypeCode.UInt32: + helper.SetValue(array, result.Read(index++), i); + break; + case TypeCode.Single: + helper.SetValue(array, result.Read(index++), i); + break; + case TypeCode.String: + helper.SetValue(array, result.Read(index++), i); + break; + case TypeCode.Object: + switch (helper.FieldType.Name) + { + case "Vector2": + var vector2 = new Vector2(); + vector2.X = result.Read(index++); + vector2.Y = result.Read(index++); + helper.SetValue(array, vector2, i); + break; + case "Vector3": + var vector3 = new Vector3(); + vector3.X = result.Read(index++); + vector3.Y = result.Read(index++); + vector3.Z = result.Read(index++); + helper.SetValue(array, vector3, i); + break; + case "LocalizedString": + LocalizedString locString = new LocalizedString(); + locString[Global.WorldMgr.GetDefaultDbcLocale()] = result.Read(index++); + helper.SetValue(array, locString, i); + break; + default: + Log.outError(LogFilter.ServerLoading, "Wrong Array Type: {0}", helper.FieldType.Name); + break; + } + break; + default: + Log.outError(LogFilter.ServerLoading, "Wrong Array Type: {0}", helper.FieldType.Name); + break; + } + } + } + else + { + switch (Type.GetTypeCode(helper.RealType)) + { + case TypeCode.SByte: + helper.SetValue(obj, result.Read(index++)); + break; + case TypeCode.Byte: + helper.SetValue(obj, result.Read(index++)); + break; + case TypeCode.Int16: + helper.SetValue(obj, result.Read(index++)); + break; + case TypeCode.UInt16: + helper.SetValue(obj, result.Read(index++)); + break; + case TypeCode.Int32: + helper.SetValue(obj, result.Read(index++)); + break; + case TypeCode.UInt32: + helper.SetValue(obj, result.Read(index++)); + break; + case TypeCode.Single: + helper.SetValue(obj, result.Read(index++)); + break; + case TypeCode.String: + string str = result.Read(index++); + helper.SetValue(obj, str); + break; + case TypeCode.Object: + switch (helper.FieldType.Name) + { + case "Vector2": + var vector2 = new Vector2(); + vector2.X = result.Read(index++); + vector2.Y = result.Read(index++); + helper.SetValue(obj, vector2); + break; + case "Vector3": + var vector3 = new Vector3(); + vector3.X = result.Read(index++); + vector3.Y = result.Read(index++); + vector3.Z = result.Read(index++); + helper.SetValue(obj, vector3); + break; + case "LocalizedString": + LocalizedString locString = new LocalizedString(); + locString[Global.WorldMgr.GetDefaultDbcLocale()] = result.Read(index++); + helper.SetValue(obj, locString); + break; + default: + Log.outError(LogFilter.ServerLoading, "Wrong Array Type: {0}", helper.FieldType.Name); + break; + } + break; + default: + Log.outError(LogFilter.ServerLoading, "Wrong Array Type: {0}", helper.FieldType.Name); + break; + } + } + } + + base[idValue] = obj; + } + while (result.NextRow()); + } + + if (preparedStatementLocale == 0) + return; + + for (LocaleConstant locale = 0; locale < LocaleConstant.OldTotal; ++locale) + { + if (Global.WorldMgr.GetDefaultDbcLocale() == locale || locale == LocaleConstant.None) + continue; + + PreparedStatement stmt = DB.Hotfix.GetPreparedStatement(preparedStatementLocale); + stmt.AddValue(0, locale.ToString()); + SQLResult localeResult = DB.Hotfix.Query(stmt); + if (localeResult.IsEmpty()) + continue; + + do + { + int index = 0; + var obj = this.LookupByKey(localeResult.Read(index++)); + if (obj == null) + continue; + + for (var i = 0; i < helpers.Length; i++) + { + var fieldInfo = helpers[i]; + if (fieldInfo.FieldType != typeof(LocalizedString)) + continue; + + LocalizedString locString = (LocalizedString)fieldInfo.Getter(obj); + locString[locale] = localeResult.Read(index++); + } + } while (localeResult.NextRow()); + } + } + + public bool HasRecord(uint id) + { + return ContainsKey(id); + } + + public void WriteRecord(uint id, LocaleConstant locale, ByteBuffer buffer) + { + T entry = this.LookupByKey(id); + + foreach (var fieldInfo in entry.GetType().GetFields()) + { + if (fieldInfo.Name == "Id") + continue; + + var type = fieldInfo.FieldType; + if (type.IsArray) + { + WriteArrayValues(entry, fieldInfo, buffer); + continue; + } + + switch (Type.GetTypeCode(type)) + { + case TypeCode.Boolean: + buffer.WriteUInt8(fieldInfo.GetValue(entry)); + break; + case TypeCode.SByte: + buffer.WriteInt8(fieldInfo.GetValue(entry)); + break; + case TypeCode.Byte: + buffer.WriteUInt8(fieldInfo.GetValue(entry)); + break; + case TypeCode.Int16: + buffer.WriteInt16(fieldInfo.GetValue(entry)); + break; + case TypeCode.UInt16: + buffer.WriteUInt16(fieldInfo.GetValue(entry)); + break; + case TypeCode.Int32: + buffer.WriteInt32(fieldInfo.GetValue(entry)); + break; + case TypeCode.UInt32: + buffer.WriteUInt32(fieldInfo.GetValue(entry)); + break; + case TypeCode.Int64: + buffer.WriteInt64(fieldInfo.GetValue(entry)); + break; + case TypeCode.UInt64: + buffer.WriteUInt64(fieldInfo.GetValue(entry)); + break; + case TypeCode.Single: + buffer.WriteFloat(fieldInfo.GetValue(entry)); + break; + case TypeCode.Object: + switch (type.Name) + { + case "LocalizedString": + LocalizedString locStr = (LocalizedString)fieldInfo.GetValue(entry); + if (!locStr.HasString(locale)) + { + locale = 0; + if (!locStr.HasString(locale)) + { + buffer.WriteUInt16(0); + break; + } + } + + string str = locStr[locale]; + buffer.WriteUInt16(str.Length + 1); + buffer.WriteCString(str); + break; + } + break; + } + } + } + + void WriteArrayValues(object entry, FieldInfo fieldInfo, ByteBuffer buffer) + { + var type = fieldInfo.FieldType.GetElementType(); + var length = ((Array)fieldInfo.GetValue(entry)).Length; + for (var i = 0; i < length; ++i) + { + var value = (Array)fieldInfo.GetValue(entry); + switch (Type.GetTypeCode(type)) + { + case TypeCode.Boolean: + buffer.WriteUInt8(value.GetValue(i)); + break; + case TypeCode.SByte: + buffer.WriteInt8(value.GetValue(i)); + break; + case TypeCode.Byte: + buffer.WriteUInt8(value.GetValue(i)); + break; + case TypeCode.Int16: + buffer.WriteInt16(value.GetValue(i)); + break; + case TypeCode.UInt16: + buffer.WriteUInt16(value.GetValue(i)); + break; + case TypeCode.Int32: + buffer.WriteInt32(value.GetValue(i)); + break; + case TypeCode.UInt32: + buffer.WriteUInt32(value.GetValue(i)); + break; + case TypeCode.Int64: + buffer.WriteInt64(value.GetValue(i)); + break; + case TypeCode.UInt64: + buffer.WriteUInt64(value.GetValue(i)); + break; + case TypeCode.Single: + buffer.WriteFloat(value.GetValue(i)); + break; + case TypeCode.String: + var str = (string)value.GetValue(i); + buffer.WriteInt32(str.Length); + buffer.WriteString(str); + break; + case TypeCode.Object: + switch (type.Name) + { + case "Unused": + buffer.WriteUInt32(0u); + break; + } + break; + } + } + } + + public void EraseRecord(uint id) + { + Remove(id); + } + + public uint tableHash; + } +} diff --git a/Game/DataStorage/ClientReader/GameTables.cs b/Game/DataStorage/ClientReader/GameTables.cs new file mode 100644 index 000000000..7cd9e0e83 --- /dev/null +++ b/Game/DataStorage/ClientReader/GameTables.cs @@ -0,0 +1,38 @@ +/* + * Copyright (C) 2012-2017 CypherCore + * + * 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, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +using System.Collections.Generic; + +namespace Game.DataStorage +{ + public class GameTable where T : new() + { + public T GetRow(uint row) + { + if (row >= _data.Count) + return default(T); + + return _data[(int)row]; + } + + public int GetTableRowCount() { return _data.Count; } + + public void SetData(List data) { _data = data; } + + List _data = new List(); + } +} diff --git a/Game/DataStorage/ConversationDataStorage.cs b/Game/DataStorage/ConversationDataStorage.cs new file mode 100644 index 000000000..481e48014 --- /dev/null +++ b/Game/DataStorage/ConversationDataStorage.cs @@ -0,0 +1,212 @@ +/* + * Copyright (C) 2012-2017 CypherCore + * + * 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, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +using Framework.Database; +using System; +using System.Collections.Generic; +using System.Linq; + +namespace Game.DataStorage +{ + public class ConversationDataStorage : Singleton + { + ConversationDataStorage() { } + + public void LoadConversationTemplates() + { + _conversationActorTemplateStorage.Clear(); + _conversationLineTemplateStorage.Clear(); + _conversationTemplateStorage.Clear(); + + Dictionary actorsByConversation = new Dictionary(); + + SQLResult actorTemplates = DB.World.Query("SELECT Id, CreatureId, CreatureModelId FROM conversation_actor_template"); + if (!actorTemplates.IsEmpty()) + { + uint oldMSTime = Time.GetMSTime(); + + do + { + uint id = actorTemplates.Read(0); + ConversationActorTemplate conversationActor = new ConversationActorTemplate(); + conversationActor.Id = id; + conversationActor.CreatureId = actorTemplates.Read(1); + conversationActor.CreatureModelId = actorTemplates.Read(2); + + _conversationActorTemplateStorage[id] = conversationActor; + } + while (actorTemplates.NextRow()); + + Log.outInfo(LogFilter.ServerLoading, "Loaded {0} Conversation actor templates in {1} ms", _conversationActorTemplateStorage.Count, Time.GetMSTimeDiffToNow(oldMSTime)); + } + else + { + Log.outInfo(LogFilter.ServerLoading, "Loaded 0 Conversation actor templates. DB table `conversation_actor_template` is empty."); + } + + SQLResult lineTemplates = DB.World.Query("SELECT Id, StartTime, UiCameraID, ActorIdx, Unk FROM conversation_line_template"); + if (!lineTemplates.IsEmpty()) + { + uint oldMSTime = Time.GetMSTime(); + + do + { + uint id = lineTemplates.Read(0); + + if (!CliDB.ConversationLineStorage.ContainsKey(id)) + { + Log.outError(LogFilter.Sql, "Table `conversation_line_template` has template for non existing ConversationLine (ID: {0}), skipped", id); + continue; + } + + ConversationLineTemplate conversationLine = new ConversationLineTemplate(); + conversationLine.Id = id; + conversationLine.StartTime = lineTemplates.Read(1); + conversationLine.UiCameraID = lineTemplates.Read(2); + conversationLine.ActorIdx = lineTemplates.Read(3); + conversationLine.Unk = lineTemplates.Read(4); + + _conversationLineTemplateStorage[id] = conversationLine; + } + while (lineTemplates.NextRow()); + + Log.outInfo(LogFilter.ServerLoading, "Loaded {0} Conversation line templates in {1} ms", _conversationLineTemplateStorage.Count, Time.GetMSTimeDiffToNow(oldMSTime)); + } + else + { + Log.outInfo(LogFilter.ServerLoading, "Loaded 0 Conversation line templates. DB table `conversation_line_template` is empty."); + } + + SQLResult actorResult = DB.World.Query("SELECT ConversationId, ConversationActorId, Idx FROM conversation_actors"); + if (!actorResult.IsEmpty()) + { + uint oldMSTime = Time.GetMSTime(); + uint count = 0; + + do + { + uint conversationId = actorResult.Read(0); + uint actorId = actorResult.Read(1); + ushort idx = actorResult.Read(2); + + ConversationActorTemplate conversationActorTemplate = _conversationActorTemplateStorage.LookupByKey(actorId); + if (conversationActorTemplate != null) + { + if (!actorsByConversation.ContainsKey(conversationId)) + actorsByConversation[conversationId] = new ConversationActorTemplate[idx + 1]; + + ConversationActorTemplate[] actors = actorsByConversation[conversationId]; + if (actors.Length <= idx) + Array.Resize(ref actors, idx + 1); + + actors[idx] = conversationActorTemplate; + ++count; + } + else + Log.outError(LogFilter.Sql, "Table `conversation_actors` references an invalid actor (ID: {0}) for Conversation {1}, skipped", actorId, conversationId); + } + while (actorResult.NextRow()); + + Log.outInfo(LogFilter.ServerLoading, "Loaded {0} Conversation actors in {1} ms", count, Time.GetMSTimeDiffToNow(oldMSTime)); + } + else + { + Log.outInfo(LogFilter.ServerLoading, "Loaded 0 Conversation actors. DB table `conversation_actors` is empty."); + } + + SQLResult templateResult = DB.World.Query("SELECT Id, FirstLineId, LastLineEndTime, VerifiedBuild FROM conversation_template"); + if (!templateResult.IsEmpty()) + { + uint oldMSTime = Time.GetMSTime(); + + do + { + ConversationTemplate conversationTemplate = new ConversationTemplate(); + conversationTemplate.Id = templateResult.Read(0); + conversationTemplate.FirstLineId = templateResult.Read(1); + conversationTemplate.LastLineEndTime = templateResult.Read(2); + + conversationTemplate.Actors = actorsByConversation[conversationTemplate.Id].ToList(); + + ConversationLineRecord currentConversationLine = CliDB.ConversationLineStorage.LookupByKey(conversationTemplate.FirstLineId); + if (currentConversationLine == null) + Log.outError(LogFilter.Sql, "Table `conversation_template` references an invalid line (ID: {0}) for Conversation {1}, skipped", conversationTemplate.FirstLineId, conversationTemplate.Id); + + while (currentConversationLine != null) + { + ConversationLineTemplate conversationLineTemplate = _conversationLineTemplateStorage.LookupByKey(currentConversationLine.Id); + if (conversationLineTemplate != null) + conversationTemplate.Lines.Add(conversationLineTemplate); + else + Log.outError(LogFilter.Sql, "Table `conversation_line_template` has missing template for line (ID: {0}) in Conversation {1}, skipped", currentConversationLine.Id, conversationTemplate.Id); + + if (currentConversationLine.NextLineID == 0) + break; + + currentConversationLine = CliDB.ConversationLineStorage.LookupByKey(currentConversationLine.NextLineID); + } + + _conversationTemplateStorage[conversationTemplate.Id] = conversationTemplate; + } + while (templateResult.NextRow()); + + Log.outInfo(LogFilter.ServerLoading, "Loaded {0} Conversation templates in {1} ms", _conversationTemplateStorage.Count, Time.GetMSTimeDiffToNow(oldMSTime)); + } + else + { + Log.outInfo(LogFilter.ServerLoading, "Loaded 0 Conversation templates. DB table `conversation_template` is empty."); + } + } + + public ConversationTemplate GetConversationTemplate(uint conversationId) + { + return _conversationTemplateStorage.LookupByKey(conversationId); + } + + Dictionary _conversationTemplateStorage = new Dictionary(); + Dictionary _conversationActorTemplateStorage = new Dictionary(); + Dictionary _conversationLineTemplateStorage = new Dictionary(); + } + + public class ConversationActorTemplate + { + public uint Id; + public uint CreatureId; + public uint CreatureModelId; + } + + public class ConversationLineTemplate + { + public uint Id; // Link to ConversationLine.db2 + public uint StartTime; // Time in ms after conversation creation the line is displayed + public uint UiCameraID; // Link to UiCamera.db2 + public ushort ActorIdx; // Index from conversation_actors + public ushort Unk; + } + + public class ConversationTemplate + { + public uint Id; + public uint FirstLineId; // Link to ConversationLine.db2 + public uint LastLineEndTime; // Time in ms after conversation creation the last line fades out + + public List Actors = new List(); + public List Lines = new List(); + } + + +} diff --git a/Game/DataStorage/DB2Manager.cs b/Game/DataStorage/DB2Manager.cs new file mode 100644 index 000000000..a615aa8be --- /dev/null +++ b/Game/DataStorage/DB2Manager.cs @@ -0,0 +1,1366 @@ +/* + * Copyright (C) 2012-2017 CypherCore + * + * 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, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +using Framework.Constants; +using Framework.Database; +using Framework.GameMath; +using System; +using System.Collections.Generic; +using System.Diagnostics.Contracts; +using System.Linq; +using System.Text.RegularExpressions; + +namespace Game.DataStorage +{ + public class DB2Manager : Singleton + { + DB2Manager() + { + for (uint i = 0; i < (int)Class.Max; ++i) + { + _powersByClass[i] = new uint[(int)PowerType.Max]; + + for (uint j = 0; j < (int)PowerType.Max; ++j) + _powersByClass[i][j] = (uint)PowerType.Max; + } + + for (uint i = 0; i < (int)LocaleConstant.Total + 1; ++i) + _nameValidators[i] = new List(); + } + + public void LoadStores() + { + foreach (var areaGroupMember in CliDB.AreaGroupMemberStorage.Values) + _areaGroupMembers.Add(areaGroupMember.AreaGroupID, areaGroupMember.AreaID); + + foreach (ArtifactPowerRecord artifactPower in CliDB.ArtifactPowerStorage.Values) + _artifactPowers.Add(artifactPower.ArtifactID, artifactPower); + + foreach (ArtifactPowerLinkRecord artifactPowerLink in CliDB.ArtifactPowerLinkStorage.Values) + { + _artifactPowerLinks.Add(artifactPowerLink.FromArtifactPowerID, artifactPowerLink.ToArtifactPowerID); + _artifactPowerLinks.Add(artifactPowerLink.ToArtifactPowerID, artifactPowerLink.FromArtifactPowerID); + } + + foreach (ArtifactPowerRankRecord artifactPowerRank in CliDB.ArtifactPowerRankStorage.Values) + _artifactPowerRanks[Tuple.Create((uint)artifactPowerRank.ArtifactPowerID, artifactPowerRank.Rank)] = artifactPowerRank; + + MultiMap> addedSections = new MultiMap>(); + foreach (CharSectionsRecord charSection in CliDB.CharSectionsStorage.Values) + { + if (charSection.Race == 0 || !Convert.ToBoolean((1 << (charSection.Race - 1)) & (int)Race.RaceMaskAllPlayable)) //ignore Nonplayable races + continue; + + // Not all sections are used for low-res models but we need to get all sections for validation since its viewer dependent + byte baseSection = charSection.GenType; + switch ((CharSectionType)baseSection) + { + case CharSectionType.SkinLowRes: + case CharSectionType.FaceLowRes: + case CharSectionType.FacialHairLowRes: + case CharSectionType.HairLowRes: + case CharSectionType.UnderwearLowRes: + baseSection = (byte)(baseSection + (byte)CharSectionType.Skin); + break; + case CharSectionType.Skin: + case CharSectionType.Face: + case CharSectionType.FacialHair: + case CharSectionType.Hair: + case CharSectionType.Underwear: + break; + case CharSectionType.CustomDisplay1LowRes: + case CharSectionType.CustomDisplay2LowRes: + case CharSectionType.CustomDisplay3LowRes: + ++baseSection; + break; + case CharSectionType.CustomDisplay1: + case CharSectionType.CustomDisplay2: + case CharSectionType.CustomDisplay3: + break; + default: + break; + } + + uint sectionKey = (uint)(baseSection | (charSection.Gender << 8) | (charSection.Race << 16)); + Tuple sectionCombination = Tuple.Create(charSection.Type, charSection.Color); + if (addedSections.Contains(sectionKey, sectionCombination)) + continue; + + addedSections.Add(sectionKey, sectionCombination); + _charSections.Add(sectionKey, charSection); + } + + foreach (var outfit in CliDB.CharStartOutfitStorage.Values) + _charStartOutfits[(uint)(outfit.RaceID | (outfit.ClassID << 8) | (outfit.GenderID << 16))] = outfit; + + var powers = new List(); + foreach (var chrClasses in CliDB.ChrClassesXPowerTypesStorage.Values) + powers.Add(chrClasses); + + powers.Sort(new ChrClassesXPowerTypesRecordComparer()); + foreach (var power in powers) + { + uint index = 0; + for (uint j = 0; j < (int)PowerType.Max; ++j) + if (_powersByClass[power.ClassID][j] != (int)PowerType.Max) + ++index; + + _powersByClass[power.ClassID][power.PowerType] = index; + } + CliDB.ChrClassesXPowerTypesStorage.Clear(); + + foreach (ChrSpecializationRecord chrSpec in CliDB.ChrSpecializationStorage.Values) + { + //ASSERT(chrSpec.ClassID < MAX_CLASSES); + //ASSERT(chrSpec.OrderIndex < MAX_SPECIALIZATIONS); + + uint storageIndex = chrSpec.ClassID; + if (chrSpec.Flags.HasAnyFlag(ChrSpecializationFlag.PetOverrideSpec)) + { + //ASSERT(!chrSpec.ClassID); + storageIndex = (int)Class.Max; + } + if (_chrSpecializationsByIndex[storageIndex] == null) + _chrSpecializationsByIndex[storageIndex] = new ChrSpecializationRecord[PlayerConst.MaxSpecializations]; + + _chrSpecializationsByIndex[storageIndex][chrSpec.OrderIndex] = chrSpec; + + if (chrSpec.Flags.HasAnyFlag(ChrSpecializationFlag.Recommended)) + _defaultChrSpecializationsByClass[chrSpec.ClassID] = chrSpec; + } + + foreach (CurvePointRecord curvePoint in CliDB.CurvePointStorage.Values) + { + if (CliDB.CurveStorage.ContainsKey(curvePoint.CurveID)) + _curvePoints[curvePoint.CurveID].Add(curvePoint); + } + + foreach (var key in _curvePoints.Keys) + _curvePoints[key].OrderBy(point => point.Index); + + foreach (EmotesTextSoundRecord emoteTextSound in CliDB.EmotesTextSoundStorage.Values) + _emoteTextSounds[Tuple.Create((uint)emoteTextSound.EmotesTextId, emoteTextSound.RaceId, emoteTextSound.SexId, emoteTextSound.ClassId)] = emoteTextSound; + + foreach (FactionRecord faction in CliDB.FactionStorage.Values) + if (faction.ParentFactionID != 0) + _factionTeams.Add(faction.ParentFactionID, faction.Id); + + foreach (GameObjectDisplayInfoRecord gameObjectDisplayInfo in CliDB.GameObjectDisplayInfoStorage.Values) + { + if (gameObjectDisplayInfo.GeoBoxMax.X < gameObjectDisplayInfo.GeoBoxMin.X) + Extensions.Swap(ref gameObjectDisplayInfo.GeoBoxMax.X, ref gameObjectDisplayInfo.GeoBoxMin.X); + if (gameObjectDisplayInfo.GeoBoxMax.Y < gameObjectDisplayInfo.GeoBoxMin.Y) + Extensions.Swap(ref gameObjectDisplayInfo.GeoBoxMax.Y, ref gameObjectDisplayInfo.GeoBoxMin.Y); + if (gameObjectDisplayInfo.GeoBoxMax.Z < gameObjectDisplayInfo.GeoBoxMin.Z) + Extensions.Swap(ref gameObjectDisplayInfo.GeoBoxMax.Z, ref gameObjectDisplayInfo.GeoBoxMin.Z); + } + + foreach (HeirloomRecord heirloom in CliDB.HeirloomStorage.Values) + _heirlooms[heirloom.ItemID] = heirloom; + + foreach (GlyphBindableSpellRecord glyphBindableSpell in CliDB.GlyphBindableSpellStorage.Values) + _glyphBindableSpells.Add(glyphBindableSpell.GlyphPropertiesID, glyphBindableSpell.SpellID); + + foreach (GlyphRequiredSpecRecord glyphRequiredSpec in CliDB.GlyphRequiredSpecStorage.Values) + _glyphRequiredSpecs.Add(glyphRequiredSpec.GlyphPropertiesID, glyphRequiredSpec.ChrSpecializationID); + + foreach (var bonus in CliDB.ItemBonusStorage.Values) + _itemBonusLists.Add(bonus.BonusListID, bonus); + + foreach (ItemBonusListLevelDeltaRecord itemBonusListLevelDelta in CliDB.ItemBonusListLevelDeltaStorage.Values) + _itemLevelDeltaToBonusListContainer[itemBonusListLevelDelta.Delta] = itemBonusListLevelDelta.Id; + + foreach (var key in CliDB.ItemBonusTreeNodeStorage.Keys) + { + ItemBonusTreeNodeRecord bonusTreeNode = CliDB.ItemBonusTreeNodeStorage[key]; + uint bonusTreeId = bonusTreeNode.BonusTreeID; + while (bonusTreeNode != null) + { + _itemBonusTrees.Add(bonusTreeId, bonusTreeNode); + bonusTreeNode = CliDB.ItemBonusTreeNodeStorage.LookupByKey(bonusTreeNode.SubTreeID); + } + } + + foreach (ItemChildEquipmentRecord itemChildEquipment in CliDB.ItemChildEquipmentStorage.Values) + { + //ASSERT(_itemChildEquipment.find(itemChildEquipment.ItemID) == _itemChildEquipment.end(), "Item must have max 1 child item."); + _itemChildEquipment[itemChildEquipment.ItemID] = itemChildEquipment; + } + + foreach (ItemClassRecord itemClass in CliDB.ItemClassStorage.Values) + { + //ASSERT(itemClass->OldEnumValue < _itemClassByOldEnum.size()); + //ASSERT(!_itemClassByOldEnum[itemClass->OldEnumValue]); + _itemClassByOldEnum[itemClass.OldEnumValue] = itemClass; + } + + foreach (ItemCurrencyCostRecord itemCurrencyCost in CliDB.ItemCurrencyCostStorage.Values) + _itemsWithCurrencyCost.Add(itemCurrencyCost.ItemId); + + foreach (var appearanceMod in CliDB.ItemModifiedAppearanceStorage.Values) + { + //ASSERT(appearanceMod.ItemID <= 0xFFFFFF); + _itemModifiedAppearancesByItem[(uint)((int)appearanceMod.ItemID | (appearanceMod.AppearanceModID << 24))] = appearanceMod; + } + + foreach (ItemSetSpellRecord itemSetSpell in CliDB.ItemSetSpellStorage.Values) + _itemSetSpells.Add(itemSetSpell.ItemSetID, itemSetSpell); + + foreach (var itemSpecOverride in CliDB.ItemSpecOverrideStorage.Values) + _itemSpecOverrides.Add(itemSpecOverride.ItemID, itemSpecOverride); + + foreach (var itemBonusTreeAssignment in CliDB.ItemXBonusTreeStorage.Values) + _itemToBonusTree.Add(itemBonusTreeAssignment.ItemID, itemBonusTreeAssignment.BonusTreeID); + + foreach (MapDifficultyRecord entry in CliDB.MapDifficultyStorage.Values) + { + if (!_mapDifficulties.ContainsKey(entry.MapID)) + _mapDifficulties[entry.MapID] = new Dictionary(); + + _mapDifficulties[entry.MapID][entry.DifficultyID] = entry; + } + _mapDifficulties[0][0] = _mapDifficulties[1][0]; // map 0 is missing from MapDifficulty.dbc so we cheat a bit + + foreach (var mount in CliDB.MountStorage.Values) + _mountsBySpellId[mount.SpellId] = mount; + + foreach (MountTypeXCapabilityRecord mountTypeCapability in CliDB.MountTypeXCapabilityStorage.Values) + _mountCapabilitiesByType.Add(mountTypeCapability.MountTypeID, mountTypeCapability); + + foreach (var key in _mountCapabilitiesByType.Keys) + _mountCapabilitiesByType[key].Sort(new MountTypeXCapabilityRecordComparer()); + + foreach (MountXDisplayRecord mountDisplay in CliDB.MountXDisplayStorage.Values) + _mountDisplays.Add(mountDisplay.MountID, mountDisplay); + + foreach (var entry in CliDB.NameGenStorage.Values) + { + if (!_nameGenData.ContainsKey(entry.Race)) + { + _nameGenData[entry.Race] = new List[2]; + for (var i = 0; i < 2; ++i) + _nameGenData[entry.Race][i] = new List(); + } + + _nameGenData[entry.Race][entry.Sex].Add(entry); + } + + foreach (var namesProfanity in CliDB.NamesProfanityStorage.Values) + { + Contract.Assert(namesProfanity.Language < (int)LocaleConstant.Total || namesProfanity.Language == -1); + if (namesProfanity.Language != -1) + _nameValidators[namesProfanity.Language].Add(new Regex(namesProfanity.Name, RegexOptions.IgnoreCase | RegexOptions.Compiled)); + else + for (uint i = 0; i < (int)LocaleConstant.Total; ++i) + { + if (i == (int)LocaleConstant.None) + continue; + + _nameValidators[i].Add(new Regex(namesProfanity.Name, RegexOptions.IgnoreCase | RegexOptions.Compiled)); + } + } + + foreach (var namesReserved in CliDB.NamesReservedStorage.Values) + _nameValidators[(int)LocaleConstant.Total].Add(new Regex(namesReserved.Name, RegexOptions.IgnoreCase | RegexOptions.Compiled)); + + foreach (var namesReserved in CliDB.NamesReservedLocaleStorage.Values) + { + Contract.Assert(!Convert.ToBoolean(namesReserved.LocaleMask & ~((1 << (int)LocaleConstant.Total) - 1))); + for (int i = 0; i < (int)LocaleConstant.Total; ++i) + { + if (i == (int)LocaleConstant.None) + continue; + + if (Convert.ToBoolean(namesReserved.LocaleMask & (1 << i))) + _nameValidators[i].Add(new Regex(namesReserved.Name, RegexOptions.IgnoreCase | RegexOptions.Compiled)); + } + } + + foreach (var group in CliDB.PhaseXPhaseGroupStorage.Values) + { + PhaseRecord phase = CliDB.PhaseStorage.LookupByKey(group.PhaseId); + if (phase != null) + _phasesByGroup.Add(group.PhaseGroupID, phase.Id); + } + + foreach (PowerTypeRecord powerType in CliDB.PowerTypeStorage.Values) + { + Contract.Assert(powerType.PowerTypeEnum < PowerType.Max); + + _powerTypes[powerType.PowerTypeEnum] = powerType; + } + + foreach (PvpRewardRecord pvpReward in CliDB.PvpRewardStorage.Values) + _pvpRewardPack[Tuple.Create(pvpReward.Prestige, pvpReward.HonorLevel)] = pvpReward.RewardPackID; + + foreach (QuestPackageItemRecord questPackageItem in CliDB.QuestPackageItemStorage.Values) + { + if (!_questPackages.ContainsKey(questPackageItem.QuestPackageID)) + _questPackages[questPackageItem.QuestPackageID] = Tuple.Create(new List(), new List()); + + if (questPackageItem.FilterType != QuestPackageFilter.Unmatched) + _questPackages[questPackageItem.QuestPackageID].Item1.Add(questPackageItem); + else + _questPackages[questPackageItem.QuestPackageID].Item2.Add(questPackageItem); + } + + foreach (RewardPackXItemRecord rewardPackXItem in CliDB.RewardPackXItemStorage.Values) + _rewardPackItems.Add(rewardPackXItem.RewardPackID, rewardPackXItem); + + foreach (RulesetItemUpgradeRecord rulesetItemUpgrade in CliDB.RulesetItemUpgradeStorage.Values) + _rulesetItemUpgrade[rulesetItemUpgrade.ItemID] = rulesetItemUpgrade.ItemUpgradeID; + + foreach (SkillRaceClassInfoRecord entry in CliDB.SkillRaceClassInfoStorage.Values) + { + if (CliDB.SkillLineStorage.ContainsKey(entry.SkillID)) + _skillRaceClassInfoBySkill.Add(entry.SkillID, entry); + } + + foreach (var specSpells in CliDB.SpecializationSpellsStorage.Values) + _specializationSpellsBySpec.Add(specSpells.SpecID, specSpells); + + foreach (var power in CliDB.SpellPowerStorage.Values) + { + SpellPowerDifficultyRecord powerDifficulty = CliDB.SpellPowerDifficultyStorage.LookupByKey(power.Id); + if (powerDifficulty != null) + { + if (!_spellPowerDifficulties.ContainsKey(power.SpellID)) + _spellPowerDifficulties[power.SpellID] = new Dictionary>(); + + if (!_spellPowerDifficulties[power.SpellID].ContainsKey(powerDifficulty.DifficultyID)) + _spellPowerDifficulties[power.SpellID][powerDifficulty.DifficultyID] = new List(); + + _spellPowerDifficulties[power.SpellID][powerDifficulty.DifficultyID].Insert(powerDifficulty.PowerIndex, power); + } + else + { + if (!_spellPowers.ContainsKey(power.SpellID)) + _spellPowers[power.SpellID] = new List(); + + _spellPowers[power.SpellID].Insert(power.PowerIndex, power); + } + } + + foreach (SpellProcsPerMinuteModRecord ppmMod in CliDB.SpellProcsPerMinuteModStorage.Values) + _spellProcsPerMinuteMods.Add(ppmMod.SpellProcsPerMinuteID, ppmMod); + + for (var i = 0; i < (int)Class.Max; ++i) + { + _talentsByPosition[i] = new List[PlayerConst.MaxTalentTiers][]; + for (var x = 0; x < PlayerConst.MaxTalentTiers; ++x) + { + _talentsByPosition[i][x] = new List[PlayerConst.MaxTalentColumns]; + + for (var c = 0; c < PlayerConst.MaxTalentColumns; ++c) + _talentsByPosition[i][x][c] = new List(); + } + } + foreach (TalentRecord talentInfo in CliDB.TalentStorage.Values) + { + //ASSERT(talentInfo.ClassID < MAX_CLASSES); + //ASSERT(talentInfo.TierID < MAX_TALENT_TIERS, "MAX_TALENT_TIERS must be at least {0}", talentInfo.TierID); + //ASSERT(talentInfo.ColumnIndex < MAX_TALENT_COLUMNS, "MAX_TALENT_COLUMNS must be at least {0}", talentInfo.ColumnIndex); + _talentsByPosition[talentInfo.ClassID][talentInfo.TierID][talentInfo.ColumnIndex].Add(talentInfo); + } + + foreach (ToyRecord toy in CliDB.ToyStorage.Values) + _toys.Add(toy.ItemID); + + foreach (WMOAreaTableRecord entry in CliDB.WMOAreaTableStorage.Values) + _wmoAreaTableLookup[Tuple.Create(entry.WMOID, entry.NameSet, entry.WMOGroupID)] = entry; + + foreach (WorldMapAreaRecord worldMapArea in CliDB.WorldMapAreaStorage.Values) + _worldMapAreaByAreaID[worldMapArea.AreaID] = worldMapArea; + + ClearNotUsedTables(); + } + + void ClearNotUsedTables() + { + CliDB.AreaGroupMemberStorage.Clear(); + CliDB.CharSectionsStorage.Clear(); + CliDB.ChrClassesXPowerTypesStorage.Clear(); + CliDB.EmotesTextSoundStorage.Clear(); + CliDB.HeirloomStorage.Clear(); + CliDB.ItemBonusStorage.Clear(); + CliDB.ItemBonusTreeNodeStorage.Clear(); + CliDB.ItemChildEquipmentStorage.Clear(); + CliDB.ItemModifiedAppearanceStorage.Clear(); + CliDB.ItemSetSpellStorage.Clear(); + CliDB.ItemSpecOverrideStorage.Clear(); + CliDB.ItemXBonusTreeStorage.Clear(); + CliDB.CurvePointStorage.Clear(); + CliDB.MountTypeXCapabilityStorage.Clear(); + CliDB.NameGenStorage.Clear(); + CliDB.NamesProfanityStorage.Clear(); + CliDB.NamesReservedStorage.Clear(); + CliDB.NamesReservedLocaleStorage.Clear(); + CliDB.PhaseXPhaseGroupStorage.Clear(); + CliDB.QuestPackageItemStorage.Clear(); + CliDB.SpecializationSpellsStorage.Clear(); + CliDB.SpellPowerDifficultyStorage.Clear(); + CliDB.SpellPowerStorage.Clear(); + CliDB.ToyStorage.Clear(); + CliDB.WMOAreaTableStorage.Clear(); + } + + public IDB2Storage GetStorage(uint type) + { + return _storage.LookupByKey(type); + } + + public void LoadHotfixData() + { + uint oldMSTime = Time.GetMSTime(); + + SQLResult result = DB.Hotfix.Query("SELECT Id, TableHash, RecordId, Deleted FROM hotfix_data ORDER BY Id"); + if (result.IsEmpty()) + { + Log.outInfo(LogFilter.ServerLoading, "Loaded 0 hotfix info entries."); + return; + } + + Dictionary, bool> deletedRecords = new Dictionary, bool>(); + + uint count = 0; + do + { + uint id = result.Read(0); + uint tableHash = result.Read(1); + int recordId = result.Read(2); + bool deleted = result.Read(3); + if (!_storage.ContainsKey(tableHash)) + { + Log.outError(LogFilter.Sql, "Table `hotfix_data` references unknown DB2 store by hash 0x{0:X} in hotfix id {1}", tableHash, id); + continue; + } + + _hotfixData[MathFunctions.MakePair64(id, tableHash)] = recordId; + deletedRecords[Tuple.Create(tableHash, recordId)] = deleted; + + ++count; + } while (result.NextRow()); + + foreach (var itr in deletedRecords) + { + if (itr.Value) + { + var store = _storage.LookupByKey(itr.Key.Item1); + if (store != null) + store.EraseRecord((uint)itr.Key.Item2); + } + } + + Log.outInfo(LogFilter.Server, "Loaded {0} hotfix info entries in {1} ms", count, Time.GetMSTimeDiffToNow(oldMSTime)); + } + + public Dictionary GetHotfixData() { return _hotfixData; } + + public List GetAreasForGroup(uint areaGroupId) + { + return _areaGroupMembers.LookupByKey(areaGroupId); + } + + public List GetArtifactPowers(byte artifactId) + { + return _artifactPowers.LookupByKey(artifactId); + } + + public List GetArtifactPowerLinks(uint artifactPowerId) + { + return _artifactPowerLinks.LookupByKey(artifactPowerId); + } + + public ArtifactPowerRankRecord GetArtifactPowerRank(uint artifactPowerId, byte rank) + { + return _artifactPowerRanks.LookupByKey(Tuple.Create(artifactPowerId, rank)); + } + + public string GetBroadcastTextValue(BroadcastTextRecord broadcastText, LocaleConstant locale = LocaleConstant.enUS, Gender gender = Gender.Male, bool forceGender = false) + { + if (gender == Gender.Female && (forceGender || broadcastText.FemaleText.HasString(SharedConst.DefaultLocale))) + { + if (broadcastText.FemaleText.HasString(locale)) + return broadcastText.FemaleText[locale]; + + return broadcastText.FemaleText[SharedConst.DefaultLocale]; + } + + + if (broadcastText.MaleText.HasString(locale)) + return broadcastText.MaleText[locale]; + + return broadcastText.MaleText[SharedConst.DefaultLocale]; + } + + public CharSectionsRecord GetCharSectionEntry(Race race, CharSectionType genType, Gender gender, byte type, byte color) + { + var list = _charSections.LookupByKey((uint)genType | (uint)((int)gender << 8) | (uint)((int)race << 16)); + foreach (var charSection in list) + if (charSection.Type == type && charSection.Color == color) + return charSection; + + return null; + } + + public CharStartOutfitRecord GetCharStartOutfitEntry(uint race, uint class_, uint gender) + { + return _charStartOutfits.LookupByKey(race | (class_ << 8) | (gender << 16)); + } + + public string GetClassName(Class class_, LocaleConstant locale = LocaleConstant.enUS) + { + ChrClassesRecord classEntry = CliDB.ChrClassesStorage.LookupByKey(class_); + if (classEntry == null) + return ""; + + if (classEntry.Name[locale][0] != '\0') + return classEntry.Name[locale]; + + return classEntry.Name[LocaleConstant.enUS]; + } + + public uint GetPowerIndexByClass(PowerType powerType, Class classId) + { + return _powersByClass[(int)classId][(int)powerType]; + } + + public string GetChrRaceName(Race race, LocaleConstant locale = LocaleConstant.enUS) + { + ChrRacesRecord raceEntry = CliDB.ChrRacesStorage.LookupByKey(race); + if (raceEntry == null) + return ""; + + if (raceEntry.Name[locale][0] != '\0') + return raceEntry.Name[locale]; + + return raceEntry.Name[LocaleConstant.enUS]; + } + + public ChrSpecializationRecord GetChrSpecializationByIndex(Class class_, uint index) + { + return _chrSpecializationsByIndex[(int)class_][index]; + } + + public ChrSpecializationRecord GetDefaultChrSpecializationForClass(Class class_) + { + return _defaultChrSpecializationsByClass.LookupByKey(class_); + } + + public string GetCreatureFamilyPetName(CreatureFamily petfamily, LocaleConstant locale) + { + if (petfamily == CreatureFamily.None) + return null; + + CreatureFamilyRecord petFamily = CliDB.CreatureFamilyStorage.LookupByKey(petfamily); + if (petFamily == null) + return ""; + + return petFamily.Name[locale][0] != '\0' ? petFamily.Name[locale] : ""; + } + + static CurveInterpolationMode DetermineCurveType(CurveRecord curve, List points) + { + switch (curve.Type) + { + case 1: + return points.Count < 4 ? CurveInterpolationMode.Cosine : CurveInterpolationMode.CatmullRom; + case 2: + { + switch (points.Count) + { + case 1: + return CurveInterpolationMode.Constant; + case 2: + return CurveInterpolationMode.Linear; + case 3: + return CurveInterpolationMode.Bezier3; + case 4: + return CurveInterpolationMode.Bezier4; + default: + break; + } + return CurveInterpolationMode.Bezier; + } + case 3: + return CurveInterpolationMode.Cosine; + default: + break; + } + + return points.Count != 1 ? CurveInterpolationMode.Linear : CurveInterpolationMode.Constant; + } + + public float GetCurveValueAt(uint curveId, float x) + { + var points = _curvePoints.LookupByKey(curveId); + if (points.Empty()) + return 0.0f; + + CurveRecord curve = CliDB.CurveStorage.LookupByKey(curveId); + if (points.Empty()) + return 0.0f; + + switch (DetermineCurveType(curve, points)) + { + case CurveInterpolationMode.Linear: + { + int pointIndex = 0; + while (pointIndex < points.Count && points[pointIndex].X <= x) + ++pointIndex; + if (pointIndex == 0) + return points[0].Y; + if (pointIndex >= points.Count) + return points.Last().Y; + float xDiff = points[pointIndex].X - points[pointIndex - 1].X; + if (xDiff == 0.0) + return points[pointIndex].Y; + return (((x - points[pointIndex - 1].X) / xDiff) * (points[pointIndex].Y - points[pointIndex - 1].Y)) + points[pointIndex - 1].Y; + } + case CurveInterpolationMode.Cosine: + { + int pointIndex = 0; + while (pointIndex < points.Count && points[pointIndex].X <= x) + ++pointIndex; + if (pointIndex == 0) + return points[0].Y; + if (pointIndex >= points.Count) + return points.Last().Y; + float xDiff = points[pointIndex].X - points[pointIndex - 1].X; + if (xDiff == 0.0) + return points[pointIndex].Y; + return (float)((points[pointIndex].Y - points[pointIndex - 1].Y) * (1.0f - Math.Cos((x - points[pointIndex - 1].X) / xDiff * Math.PI)) * 0.5f) + points[pointIndex - 1].Y; + } + case CurveInterpolationMode.CatmullRom: + { + int pointIndex = 1; + while (pointIndex < points.Count && points[pointIndex].X <= x) + ++pointIndex; + if (pointIndex == 1) + return points[1].Y; + if (pointIndex >= points.Count - 1) + return points[points.Count - 2].Y; + float xDiff = points[pointIndex].X - points[pointIndex - 1].X; + if (xDiff == 0.0) + return points[pointIndex].Y; + + float mu = (x - points[pointIndex - 1].X) / xDiff; + float a0 = -0.5f * points[pointIndex - 2].Y + 1.5f * points[pointIndex - 1].Y - 1.5f * points[pointIndex].Y + 0.5f * points[pointIndex + 1].Y; + float a1 = points[pointIndex - 2].Y - 2.5f * points[pointIndex - 1].Y + 2.0f * points[pointIndex].Y - 0.5f * points[pointIndex + 1].Y; + float a2 = -0.5f * points[pointIndex - 2].Y + 0.5f * points[pointIndex].Y; + float a3 = points[pointIndex - 1].Y; + + return a0 * mu * mu * mu + a1 * mu * mu + a2 * mu + a3; + } + case CurveInterpolationMode.Bezier3: + { + float xDiff = points[2].X - points[0].X; + if (xDiff == 0.0) + return points[1].Y; + float mu = (x - points[0].X) / xDiff; + return ((1.0f - mu) * (1.0f - mu) * points[0].Y) + (1.0f - mu) * 2.0f * mu * points[1].Y + mu * mu * points[2].Y; + } + case CurveInterpolationMode.Bezier4: + { + float xDiff = points[3].X - points[0].X; + if (xDiff == 0.0) + return points[1].Y; + float mu = (x - points[0].X) / xDiff; + return (1.0f - mu) * (1.0f - mu) * (1.0f - mu) * points[0].Y + + 3.0f * mu * (1.0f - mu) * (1.0f - mu) * points[1].Y + + 3.0f * mu * mu * (1.0f - mu) * points[2].Y + + mu * mu * mu * points[3].Y; + } + case CurveInterpolationMode.Bezier: + { + float xDiff = points.Last().X - points[0].X; + if (xDiff == 0.0f) + return points.Last().Y; + + float[] tmp = new float[points.Count]; + for (int c = 0; c < points.Count; ++c) + tmp[c] = points[c].Y; + + float mu = (x - points[0].X) / xDiff; + int i = points.Count - 1; + while (i > 0) + { + for (int k = 0; k < i; ++k) + { + float val = tmp[k] + mu * (tmp[k + 1] - tmp[k]); + tmp[k] = val; + } + --i; + } + return tmp[0]; + } + case CurveInterpolationMode.Constant: + return points[0].Y; + default: + break; + } + + return 0.0f; + } + + public EmotesTextSoundRecord GetTextSoundEmoteFor(uint emote, Race race, Gender gender, Class class_) + { + var emoteTextSound = _emoteTextSounds.LookupByKey(Tuple.Create(emote, (byte)race, (byte)gender, (byte)class_)); + if (emoteTextSound != null) + return emoteTextSound; + + emoteTextSound = _emoteTextSounds.LookupByKey(Tuple.Create(emote, (byte)race, (byte)gender, 0)); + if (emoteTextSound != null) + return emoteTextSound; + + return null; + } + + public List GetFactionTeamList(uint faction) + { + return _factionTeams.LookupByKey(faction); + } + + public HeirloomRecord GetHeirloomByItemId(uint itemId) + { + return _heirlooms.LookupByKey(itemId); + } + + public List GetGlyphBindableSpells(uint glyphPropertiesId) + { + return _glyphBindableSpells.LookupByKey(glyphPropertiesId); + } + + public List GetGlyphRequiredSpecs(uint glyphPropertiesId) + { + return _glyphRequiredSpecs.LookupByKey(glyphPropertiesId); + } + + public List GetItemBonusList(uint bonusListId) + { + return _itemBonusLists.LookupByKey(bonusListId); + } + + public uint GetItemBonusListForItemLevelDelta(short delta) + { + return _itemLevelDeltaToBonusListContainer.LookupByKey(delta); + } + + public List GetItemBonusTree(uint itemId, uint itemBonusTreeMod) + { + List bonusListIDs = new List(); + var itemIdRange = _itemToBonusTree.LookupByKey(itemId); + if (itemIdRange.Empty()) + return bonusListIDs; + + foreach (var itemTreeId in itemIdRange) + { + var treeList = _itemBonusTrees.LookupByKey(itemTreeId); + if (treeList.Empty()) + continue; + + foreach (ItemBonusTreeNodeRecord bonusTreeNode in treeList) + if (bonusTreeNode.BonusTreeModID == itemBonusTreeMod) + bonusListIDs.Add(bonusTreeNode.BonusListID); + } + + return bonusListIDs; + } + + public ItemChildEquipmentRecord GetItemChildEquipment(uint itemId) + { + return _itemChildEquipment.LookupByKey(itemId); + } + + public ItemClassRecord GetItemClassByOldEnum(ItemClass itemClass) + { + return _itemClassByOldEnum[(int)itemClass]; + } + + public uint GetItemDisplayId(uint itemId, uint appearanceModId) + { + ItemModifiedAppearanceRecord modifiedAppearance = GetItemModifiedAppearance(itemId, appearanceModId); + if (modifiedAppearance != null) + { + ItemAppearanceRecord itemAppearance = CliDB.ItemAppearanceStorage.LookupByKey(modifiedAppearance.AppearanceID); + if (itemAppearance != null) + return itemAppearance.DisplayID; + } + + return 0; + } + + public ItemModifiedAppearanceRecord GetItemModifiedAppearance(uint itemId, uint appearanceModId) + { + var itemModifiedAppearance = _itemModifiedAppearancesByItem.LookupByKey(itemId | (appearanceModId << 24)); + if (itemModifiedAppearance != null) + return itemModifiedAppearance; + + // Fall back to unmodified appearance + if (appearanceModId != 0) + { + itemModifiedAppearance = _itemModifiedAppearancesByItem.LookupByKey(itemId); + if (itemModifiedAppearance != null) + return itemModifiedAppearance; + } + + return null; + } + + public ItemModifiedAppearanceRecord GetDefaultItemModifiedAppearance(uint itemId) + { + return _itemModifiedAppearancesByItem.LookupByKey(itemId); + } + + public List GetItemSetSpells(uint itemSetId) + { + return _itemSetSpells.LookupByKey(itemSetId); + } + + public List GetItemSpecOverrides(uint itemId) + { + return _itemSpecOverrides.LookupByKey(itemId); + } + + public LfgDungeonsRecord GetLfgDungeon(uint mapId, Difficulty difficulty) + { + foreach (LfgDungeonsRecord dungeon in CliDB.LfgDungeonsStorage.Values) + if (dungeon.MapID == mapId && (Difficulty)dungeon.DifficultyID == difficulty) + return dungeon; + + return null; + } + + public uint GetDefaultMapLight(uint mapId) + { + foreach (var light in CliDB.LightStorage.Values.Reverse()) + { + if (light.MapID == mapId && light.Pos.X == 0.0f && light.Pos.Y == 0.0f && light.Pos.Z == 0.0f) + return light.Id; + } + + return 0; + } + + public uint GetLiquidFlags(uint liquidType) + { + LiquidTypeRecord liq = CliDB.LiquidTypeStorage.LookupByKey(liquidType); + if (liq != null) + return 1u << liq.LiquidType; + + return 0; + } + + public MapDifficultyRecord GetDefaultMapDifficulty(uint mapId) + { + Difficulty NotUsed = Difficulty.None; + return GetDefaultMapDifficulty(mapId, ref NotUsed); + } + public MapDifficultyRecord GetDefaultMapDifficulty(uint mapId, ref Difficulty difficulty) + { + var dicMapDiff = _mapDifficulties.LookupByKey(mapId); + if (dicMapDiff == null) + return null; + + if (dicMapDiff.Empty()) + return null; + + foreach (var pair in dicMapDiff) + { + DifficultyRecord difficultyEntry = CliDB.DifficultyStorage.LookupByKey(pair.Key); + if (difficultyEntry == null) + continue; + + if (difficultyEntry.Flags.HasAnyFlag(DifficultyFlags.Default)) + { + difficulty = (Difficulty)pair.Key; + return pair.Value; + } + } + + difficulty = (Difficulty)dicMapDiff.First().Key; + + return dicMapDiff.First().Value; + } + + public MapDifficultyRecord GetMapDifficultyData(uint mapId, Difficulty difficulty) + { + var dictionaryMapDiff = _mapDifficulties.LookupByKey(mapId); + if (dictionaryMapDiff == null) + return null; + + var mapDifficulty = dictionaryMapDiff.LookupByKey(difficulty); + if (mapDifficulty == null) + return null; + + return mapDifficulty; + } + + public MapDifficultyRecord GetDownscaledMapDifficultyData(uint mapId, ref Difficulty difficulty) + { + DifficultyRecord diffEntry = CliDB.DifficultyStorage.LookupByKey(difficulty); + if (diffEntry == null) + return GetDefaultMapDifficulty(mapId, ref difficulty); + + Difficulty tmpDiff = difficulty; + MapDifficultyRecord mapDiff = GetMapDifficultyData(mapId, tmpDiff); + while (mapDiff == null) + { + tmpDiff = (Difficulty)diffEntry.FallbackDifficultyID; + diffEntry = CliDB.DifficultyStorage.LookupByKey(tmpDiff); + if (diffEntry == null) + return GetDefaultMapDifficulty(mapId, ref difficulty); + + // pull new data + mapDiff = GetMapDifficultyData(mapId, tmpDiff); // we are 10 normal or 25 normal + } + + difficulty = tmpDiff; + return mapDiff; + } + + public MountRecord GetMount(uint spellId) + { + return _mountsBySpellId.LookupByKey(spellId); + } + + MountRecord GetMountById(uint id) + { + return CliDB.MountStorage.LookupByKey(id); + } + + public List GetMountCapabilities(uint mountType) + { + return _mountCapabilitiesByType.LookupByKey(mountType); + } + + public List GetMountDisplays(uint mountId) + { + return _mountDisplays.LookupByKey(mountId); + } + + public string GetNameGenEntry(uint race, uint gender, LocaleConstant locale, LocaleConstant defaultLocale) + { + Contract.Assert(gender < (int)Gender.None); + var listNameGen = _nameGenData.LookupByKey(race); + if (listNameGen == null) + return ""; + + if (listNameGen[gender].Empty()) + return ""; + + LocalizedString data = listNameGen[gender].PickRandom().Name; + if (data.HasString(locale)) + return data[locale]; + + return data[defaultLocale]; + } + + public ResponseCodes ValidateName(string name, LocaleConstant locale) + { + foreach (var regex in _nameValidators[(int)locale]) + if (regex.IsMatch(name)) + return ResponseCodes.CharNameProfane; + + // regexes at TOTAL_LOCALES are loaded from NamesReserved which is not locale specific + foreach (var regex in _nameValidators[(int)LocaleConstant.Total]) + if (regex.IsMatch(name)) + return ResponseCodes.CharNameReserved; + + return ResponseCodes.CharNameSuccess; + } + + public byte GetMaxPrestige() + { + byte max = 0; + foreach (PrestigeLevelInfoRecord prestigeLevelInfo in CliDB.PrestigeLevelInfoStorage.Values) + if (!prestigeLevelInfo.IsDisabled()) + max = Math.Max(prestigeLevelInfo.PrestigeLevel, max); + + return max; + } + + public PvpDifficultyRecord GetBattlegroundBracketByLevel(uint mapid, uint level) + { + PvpDifficultyRecord maxEntry = null; // used for level > max listed level case + foreach (var entry in CliDB.PvpDifficultyStorage.Values) + { + // skip unrelated and too-high brackets + if (entry.MapID != mapid || entry.MinLevel > level) + continue; + + // exactly fit + if (entry.MaxLevel >= level) + return entry; + + // remember for possible out-of-range case (search higher from existed) + if (maxEntry == null || maxEntry.MaxLevel < entry.MaxLevel) + maxEntry = entry; + } + + return maxEntry; + } + + public PvpDifficultyRecord GetBattlegroundBracketById(uint mapid, BattlegroundBracketId id) + { + foreach (var entry in CliDB.PvpDifficultyStorage.Values) + if (entry.MapID == mapid && entry.GetBracketId() == id) + return entry; + + return null; + } + + public uint GetRewardPackIDForPvpRewardByHonorLevelAndPrestige(byte honorLevel, byte prestige) + { + var value = _pvpRewardPack.LookupByKey(Tuple.Create(prestige, honorLevel)); + if (value == 0) + value = _pvpRewardPack.LookupByKey(Tuple.Create(0, honorLevel)); + + return value; + } + + public List GetQuestPackageItems(uint questPackageID) + { + if( _questPackages.ContainsKey(questPackageID)) + return _questPackages[questPackageID].Item1; + + return null; + } + + public List GetQuestPackageItemsFallback(uint questPackageID) + { + return _questPackages.LookupByKey(questPackageID).Item2; + } + + public uint GetQuestUniqueBitFlag(uint questId) + { + QuestV2Record v2 = CliDB.QuestV2Storage.LookupByKey(questId); + if (v2 == null) + return 0; + + return v2.UniqueBitFlag; + } + + public List GetPhasesForGroup(uint group) + { + return _phasesByGroup.LookupByKey(group); + } + + public PowerTypeRecord GetPowerTypeEntry(PowerType power) + { + if (!_powerTypes.ContainsKey(power)) + return null; + + return _powerTypes[power]; + } + + public List GetRewardPackItemsByRewardID(uint rewardPackID) + { + return _rewardPackItems.LookupByKey(rewardPackID); + } + + public uint GetRulesetItemUpgrade(uint itemId) + { + return _rulesetItemUpgrade.LookupByKey(itemId); + } + + public SkillRaceClassInfoRecord GetSkillRaceClassInfo(uint skill, Race race, Class class_) + { + var bounds = _skillRaceClassInfoBySkill.LookupByKey(skill); + foreach (var record in bounds) + { + if (record.RaceMask != 0 && !Convert.ToBoolean(record.RaceMask & (1 << ((byte)race - 1)))) + continue; + if (record.ClassMask != 0 && !Convert.ToBoolean(record.ClassMask & (1 << ((byte)class_ - 1)))) + continue; + + return record; + } + + return null; + } + + public List GetSpecializationSpells(uint specId) + { + return _specializationSpellsBySpec.LookupByKey(specId); + } + + public List GetSpellPowers(uint spellId, Difficulty difficulty = Difficulty.None) + { + bool notUsed; + return GetSpellPowers(spellId, difficulty, out notUsed); + } + + public List GetSpellPowers(uint spellId, Difficulty difficulty, out bool hasDifficultyPowers) + { + SpellPowerRecord[] powers = new SpellPowerRecord[0]; + hasDifficultyPowers = false; + + var difficultyDic = _spellPowerDifficulties.LookupByKey(spellId); + if (difficultyDic != null) + { + hasDifficultyPowers = true; + + DifficultyRecord difficultyEntry = CliDB.DifficultyStorage.LookupByKey(difficulty); + while (difficultyEntry != null) + { + var powerDifficultyList = difficultyDic.LookupByKey(difficultyEntry.Id); + if (powerDifficultyList != null) + { + if (powerDifficultyList.Count > powers.Length) + Array.Resize(ref powers, powerDifficultyList.Count); + + foreach (SpellPowerRecord difficultyPower in powerDifficultyList) + if (powers[difficultyPower.PowerIndex] == null) + powers[difficultyPower.PowerIndex] = difficultyPower; + } + + difficultyEntry = CliDB.DifficultyStorage.LookupByKey(difficultyEntry.FallbackDifficultyID); + } + } + + var record = _spellPowers.LookupByKey(spellId); + if (record != null) + { + if (record.Count > powers.Length) + Array.Resize(ref powers, record.Count); + + foreach (SpellPowerRecord power in record) + if (powers[power.PowerIndex] == null) + powers[power.PowerIndex] = power; + } + + return powers.ToList(); + } + + public List GetSpellProcsPerMinuteMods(uint spellprocsPerMinuteId) + { + return _spellProcsPerMinuteMods.LookupByKey(spellprocsPerMinuteId); + } + + public List GetTalentsByPosition(Class class_, uint tier, uint column) + { + return _talentsByPosition[(int)class_][tier][column]; + } + + public bool IsTotemCategoryCompatibleWith(uint itemTotemCategoryId, uint requiredTotemCategoryId) + { + if (requiredTotemCategoryId == 0) + return true; + if (itemTotemCategoryId == 0) + return false; + + TotemCategoryRecord itemEntry = CliDB.TotemCategoryStorage.LookupByKey(itemTotemCategoryId); + if (itemEntry == null) + return false; + TotemCategoryRecord reqEntry = CliDB.TotemCategoryStorage.LookupByKey(requiredTotemCategoryId); + if (reqEntry == null) + return false; + + if (itemEntry.CategoryType != reqEntry.CategoryType) + return false; + + return (itemEntry.CategoryMask & reqEntry.CategoryMask) == reqEntry.CategoryMask; + } + + public bool IsToyItem(uint toy) + { + return _toys.Contains(toy); + } + + public WMOAreaTableRecord GetWMOAreaTable(int rootId, int adtId, int groupId) + { + var wmoAreaTable = _wmoAreaTableLookup.LookupByKey(Tuple.Create((short)rootId, (sbyte)adtId, groupId)); + if (wmoAreaTable != null) + return wmoAreaTable; + + return null; + } + + public uint GetVirtualMapForMapAndZone(uint mapId, uint zoneId) + { + if (mapId != 530 && mapId != 571 && mapId != 732) // speed for most cases + return mapId; + + var worldMapArea = _worldMapAreaByAreaID.LookupByKey(zoneId); + if (worldMapArea != null) + return worldMapArea.DisplayMapID >= 0 ? (uint)worldMapArea.DisplayMapID : worldMapArea.MapID; + + return mapId; + } + + public void Zone2MapCoordinates(uint areaId, ref float x, ref float y) + { + var record = _worldMapAreaByAreaID.LookupByKey(areaId); + if (record == null) + return; + + Extensions.Swap(ref x, ref y); // at client map coords swapped + x = x * ((record.LocBottom - record.LocTop) / 100) + record.LocTop; + y = y * ((record.LocRight - record.LocLeft) / 100) + record.LocLeft; // client y coord from top to down + } + + public void Map2ZoneCoordinates(uint areaId, ref float x, ref float y) + { + var record = _worldMapAreaByAreaID.LookupByKey(areaId); + if (record == null) + return; + + x = (x - record.LocTop) / ((record.LocBottom - record.LocTop) / 100); + y = (y - record.LocLeft) / ((record.LocRight - record.LocLeft) / 100); // client y coord from top to down + Extensions.Swap(ref x, ref y); // client have map coords swapped + } + + public void DeterminaAlternateMapPosition(uint mapId, float x, float y, float z, out uint newMapId) + { + Vector2 pos; + DeterminaAlternateMapPosition(mapId, x, y, z, out newMapId, out pos); + } + public void DeterminaAlternateMapPosition(uint mapId, float x, float y, float z, out uint newMapId, out Vector2 newPos) + { + newPos = new Vector2(); + + //Contract.Assert(newMapId != 0 || newPos != ); + WorldMapTransformsRecord transformation = null; + foreach (WorldMapTransformsRecord transform in CliDB.WorldMapTransformsStorage.Values) + { + if (transform.MapID != mapId) + continue; + + if (transform.RegionMin.X > x || transform.RegionMax.X < x) + continue; + if (transform.RegionMin.Y > y || transform.RegionMax.Y < y) + continue; + if (transform.RegionMin.Z > z || transform.RegionMax.Z < z) + continue; + + transformation = transform; + break; + } + + if (transformation == null) + { + newMapId = mapId; + + newPos.X = x; + newPos.Y = y; + return; + } + + newMapId = transformation.NewMapID; + + if (transformation.RegionScale > 0.0f && transformation.RegionScale < 1.0f) + { + x = (x - transformation.RegionMin.X) * transformation.RegionScale + transformation.RegionMin.X; + y = (y - transformation.RegionMin.Y) * transformation.RegionScale + transformation.RegionMin.Y; + } + + newPos.X = x + transformation.RegionOffset.X; + newPos.Y = y + transformation.RegionOffset.Y; + } + + public bool HasItemCurrencyCost(uint itemId) { return _itemsWithCurrencyCost.Contains(itemId); } + + public Dictionary> GetMapDifficulties() { return _mapDifficulties; } + + public void AddDB2(DB6Storage store) where T : new() + { + _storage[store.tableHash] = store; + } + + Dictionary _storage = new Dictionary(); + Dictionary _hotfixData = new Dictionary(); + + MultiMap _areaGroupMembers = new MultiMap(); + MultiMap _artifactPowers = new MultiMap(); + MultiMap _artifactPowerLinks = new MultiMap(); + Dictionary, ArtifactPowerRankRecord> _artifactPowerRanks = new Dictionary, ArtifactPowerRankRecord>(); + MultiMap _charSections = new MultiMap(); + Dictionary _charStartOutfits = new Dictionary(); + uint[][] _powersByClass = new uint[(int)Class.Max][]; + ChrSpecializationRecord[][] _chrSpecializationsByIndex = new ChrSpecializationRecord[(int)Class.Max + 1][]; + Dictionary _defaultChrSpecializationsByClass = new Dictionary(); + MultiMap _curvePoints = new MultiMap(); + Dictionary, EmotesTextSoundRecord> _emoteTextSounds = new Dictionary, EmotesTextSoundRecord>(); + MultiMap _factionTeams = new MultiMap(); + Dictionary _heirlooms = new Dictionary(); + MultiMap _glyphBindableSpells = new MultiMap(); + MultiMap _glyphRequiredSpecs = new MultiMap(); + MultiMap _itemBonusLists = new MultiMap(); + Dictionary _itemLevelDeltaToBonusListContainer = new Dictionary(); + MultiMap _itemBonusTrees = new MultiMap(); + Dictionary _itemChildEquipment = new Dictionary(); + Array _itemClassByOldEnum = new Array(19); + List _itemsWithCurrencyCost = new List(); + Dictionary _itemModifiedAppearancesByItem = new Dictionary(); + MultiMap _itemToBonusTree = new MultiMap(); + MultiMap _itemSetSpells = new MultiMap(); + MultiMap _itemSpecOverrides = new MultiMap(); + Dictionary> _mapDifficulties = new Dictionary>(); + Dictionary _mountsBySpellId = new Dictionary(); + MultiMap _mountCapabilitiesByType = new MultiMap(); + MultiMap _mountDisplays = new MultiMap(); + Dictionary[]> _nameGenData = new Dictionary[]>(); + List[] _nameValidators = new List[(int)LocaleConstant.Total + 1]; + MultiMap _phasesByGroup = new MultiMap(); + Dictionary _powerTypes = new Dictionary(); + Dictionary, uint> _pvpRewardPack = new Dictionary, uint>(); + Dictionary, List>> _questPackages = new Dictionary, List>>(); + MultiMap _rewardPackItems = new MultiMap(); + Dictionary _rulesetItemUpgrade = new Dictionary(); + MultiMap _skillRaceClassInfoBySkill = new MultiMap(); + MultiMap _specializationSpellsBySpec = new MultiMap(); + Dictionary> _spellPowers = new Dictionary>(); + Dictionary>> _spellPowerDifficulties = new Dictionary>>(); + MultiMap _spellProcsPerMinuteMods = new MultiMap(); + List[][][] _talentsByPosition = new List[(int)Class.Max][][]; + List _toys = new List(); + Dictionary, WMOAreaTableRecord> _wmoAreaTableLookup = new Dictionary, WMOAreaTableRecord>(); + Dictionary _worldMapAreaByAreaID = new Dictionary(); + } + + class ChrClassesXPowerTypesRecordComparer : IComparer + { + public int Compare(ChrClassesXPowerTypesRecord left, ChrClassesXPowerTypesRecord right) + { + if (left.ClassID != right.ClassID) + return left.ClassID.CompareTo(right.ClassID); + return left.PowerType.CompareTo(right.PowerType); + } + } + + class MountTypeXCapabilityRecordComparer : IComparer + { + public int Compare(MountTypeXCapabilityRecord left, MountTypeXCapabilityRecord right) + { + if (left.MountTypeID == right.MountTypeID) + return left.OrderIndex.CompareTo(right.OrderIndex); + return left.Id.CompareTo(right.Id); + } + } + + enum CurveInterpolationMode + { + Linear = 0, + Cosine = 1, + CatmullRom = 2, + Bezier3 = 3, + Bezier4 = 4, + Bezier = 5, + Constant = 6, + } +} diff --git a/Game/DataStorage/M2Storage.cs b/Game/DataStorage/M2Storage.cs new file mode 100644 index 000000000..4c26a6a66 --- /dev/null +++ b/Game/DataStorage/M2Storage.cs @@ -0,0 +1,227 @@ +/* + * Copyright (C) 2012-2017 CypherCore + * + * 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, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +using Framework.GameMath; +using System; +using System.Collections.Generic; +using System.IO; +using System.Runtime.InteropServices; + +namespace Game.DataStorage +{ + public class M2Storage + { + // Convert the geomoetry from a spline value, to an actual WoW XYZ + static Vector3 translateLocation(Vector4 dbcLocation, Vector3 basePosition, Vector3 splineVector) + { + Vector3 work = new Vector3(); + float x = basePosition.X + splineVector.X; + float y = basePosition.Y + splineVector.Y; + float z = basePosition.Z + splineVector.Z; + float distance = (float)Math.Sqrt((x * x) + (y * y)); + float angle = (float)Math.Atan2(x, y) - dbcLocation.W; + + if (angle < 0) + angle += 2 * MathFunctions.PI; + + work.X = dbcLocation.X + (distance * (float)Math.Sin(angle)); + work.Y = dbcLocation.Y + (distance * (float)Math.Cos(angle)); + work.Z = dbcLocation.Z + z; + return work; + } + + // Number of cameras not used. Multiple cameras never used in 7.1.5 + static void readCamera(M2Camera cam, uint buffSize, BinaryReader buffer, CinematicCameraRecord dbcentry) + { + List cameras = new List(); + List targetcam = new List(); + + Vector4 dbcData = new Vector4(dbcentry.Origin.X, dbcentry.Origin.Y, dbcentry.Origin.Z, dbcentry.OriginFacing); + + // Read target locations, only so that we can calculate orientation + for (uint k = 0; k < cam.target_positions.timestamps.number; ++k) + { + // Extract Target positions + M2Array targTsArray = new M2Array(buffer, cam.target_positions.timestamps.offset_elements); + + buffer.BaseStream.Position = targTsArray.offset_elements; + uint[] targTimestamps = new uint[targTsArray.number]; + for (var i = 0; i < targTsArray.number; ++i) + targTimestamps[i] = buffer.ReadUInt32(); + + M2Array targArray = new M2Array(buffer, cam.target_positions.values.offset_elements); + + buffer.BaseStream.Position = targArray.offset_elements; + M2SplineKey[] targPositions = new M2SplineKey[targArray.number]; + for (var i = 0; i < targArray.number; ++i) + targPositions[i] = new M2SplineKey(buffer); + + // Read the data for this set + uint currPos = targArray.offset_elements; + for (uint i = 0; i < targTsArray.number; ++i) + { + // Translate co-ordinates + Vector3 newPos = translateLocation(dbcData, cam.target_position_base, targPositions[i].p0); + + // Add to vector + FlyByCamera thisCam = new FlyByCamera(); + thisCam.timeStamp = targTimestamps[i]; + thisCam.locations = new Vector4(newPos.X, newPos.Y, newPos.Z, 0.0f); + targetcam.Add(thisCam); + currPos += (uint)Marshal.SizeOf(); + } + } + + // Read camera positions and timestamps (translating first position of 3 only, we don't need to translate the whole spline) + for (uint k = 0; k < cam.positions.timestamps.number; ++k) + { + // Extract Camera positions for this set + M2Array posTsArray = buffer.ReadStruct(cam.positions.timestamps.offset_elements); + + buffer.BaseStream.Position = posTsArray.offset_elements; + uint[] posTimestamps = new uint[posTsArray.number]; + for (var i = 0; i < posTsArray.number; ++i) + posTimestamps[i] = buffer.ReadUInt32(); + + M2Array posArray = new M2Array(buffer, cam.positions.values.offset_elements); + + buffer.BaseStream.Position = posArray.offset_elements; + M2SplineKey[] positions = new M2SplineKey[posTsArray.number]; + for (var i = 0; i < posTsArray.number; ++i) + positions[i] = new M2SplineKey(buffer); + + // Read the data for this set + uint currPos = posArray.offset_elements; + for (uint i = 0; i < posTsArray.number; ++i) + { + // Translate co-ordinates + Vector3 newPos = translateLocation(dbcData, cam.position_base, positions[i].p0); + + // Add to vector + FlyByCamera thisCam = new FlyByCamera(); + thisCam.timeStamp = posTimestamps[i]; + thisCam.locations = new Vector4(newPos.X, newPos.Y, newPos.Z, 0); + + if (targetcam.Count > 0) + { + // Find the target camera before and after this camera + FlyByCamera lastTarget; + FlyByCamera nextTarget; + + // Pre-load first item + lastTarget = targetcam[0]; + nextTarget = targetcam[0]; + for (int j = 0; j < targetcam.Count; ++j) + { + nextTarget = targetcam[j]; + if (targetcam[j].timeStamp > posTimestamps[i]) + break; + + lastTarget = targetcam[j]; + } + + float x = lastTarget.locations.X; + float y = lastTarget.locations.Y; + float z = lastTarget.locations.Z; + + // Now, the timestamps for target cam and position can be different. So, if they differ we interpolate + if (lastTarget.timeStamp != posTimestamps[i]) + { + uint timeDiffTarget = nextTarget.timeStamp - lastTarget.timeStamp; + uint timeDiffThis = posTimestamps[i] - lastTarget.timeStamp; + float xDiff = nextTarget.locations.X - lastTarget.locations.X; + float yDiff = nextTarget.locations.Y - lastTarget.locations.Y; + float zDiff = nextTarget.locations.Z - lastTarget.locations.Z; + x = lastTarget.locations.X + (xDiff * (timeDiffThis / timeDiffTarget)); + y = lastTarget.locations.Y + (yDiff * (timeDiffThis / timeDiffTarget)); + z = lastTarget.locations.Z + (zDiff * (timeDiffThis / timeDiffTarget)); + } + float xDiff1 = x - thisCam.locations.X; + float yDiff1 = y - thisCam.locations.Y; + thisCam.locations.W = (float)Math.Atan2(yDiff1, xDiff1); + + if (thisCam.locations.W < 0) + thisCam.locations.W += 2 * MathFunctions.PI; + } + + cameras.Add(thisCam); + currPos += (uint)Marshal.SizeOf(); + } + } + + FlyByCameraStorage[dbcentry.ID] = cameras; + } + + public static void LoadM2Cameras(string dataPath) + { + FlyByCameraStorage.Clear(); + Log.outInfo(LogFilter.ServerLoading, "Loading Cinematic Camera files"); + + uint oldMSTime = Time.GetMSTime(); + foreach (CinematicCameraRecord cameraEntry in CliDB.CinematicCameraStorage.Values) + { + string filename = dataPath + "/cameras/" + string.Format("FILE{0:x8}.xxx", cameraEntry.ModelFileDataID); + + try + { + using (BinaryReader m2file = new BinaryReader(new FileStream(filename, FileMode.Open, FileAccess.Read))) + { + // Check file has correct magic (MD21) + if (m2file.ReadStringFromChars(4) != "MD21") + { + Log.outError(LogFilter.ServerLoading, "Camera file {0} is damaged. File identifier not found.", filename); + continue; + } + + var unknownSize = m2file.ReadUInt32(); //unknown size + + // Read header + M2Header header = m2file.ReadStruct(); + var buffer = new BinaryReader(new MemoryStream(m2file.ToByteArray(), 8, (int)(m2file.BaseStream.Length - 8))); + + // Get camera(s) - Main header, then dump them. + M2Camera cam = m2file.ReadStruct(8 + header.ofsCameras); + + readCamera(cam, (uint)m2file.BaseStream.Length - 8, buffer, cameraEntry); + } + } + catch (EndOfStreamException) + { + Log.outError(LogFilter.ServerLoading, "Camera file {0} is damaged. Camera references position beyond file end", filename); + } + catch (FileNotFoundException) + { + Log.outError(LogFilter.ServerLoading, "File {0} not found!!!!", filename); + } + } + Log.outInfo(LogFilter.ServerLoading, "Loaded {0} cinematic waypoint sets in {1} ms", FlyByCameraStorage.Count, Time.GetMSTimeDiffToNow(oldMSTime)); + } + + public static List GetFlyByCameras(uint cameraId) + { + return FlyByCameraStorage.LookupByKey(cameraId); + } + + static MultiMap FlyByCameraStorage = new MultiMap(); + } + + public class FlyByCamera + { + public uint timeStamp; + public Vector4 locations; + } +} diff --git a/Game/DataStorage/Structs/A_Records.cs b/Game/DataStorage/Structs/A_Records.cs new file mode 100644 index 000000000..97363c80f --- /dev/null +++ b/Game/DataStorage/Structs/A_Records.cs @@ -0,0 +1,221 @@ +/* + * Copyright (C) 2012-2017 CypherCore + * + * 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, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +using Framework.Constants; +using Framework.GameMath; +using System; + +namespace Game.DataStorage +{ + public class AchievementRecord + { + public LocalizedString Title; + public LocalizedString Description; + public AchievementFlags Flags; + public LocalizedString Reward; + public short MapID; + public ushort Supercedes; + public ushort Category; + public ushort UiOrder; + public ushort SharesCriteria; + public ushort CriteriaTree; + public AchievementFaction Faction; + public byte Points; + public byte MinimumCriteria; + public uint Id; + public uint IconFileDataID; + } + + public sealed class AnimKitRecord + { + public uint Id; + public uint OneShotDuration; + public ushort OneShotStopAnimKitID; + public ushort LowDefAnimKitID; + } + + public sealed class AreaGroupMemberRecord + { + public uint Id; + public ushort AreaGroupID; + public ushort AreaID; + } + + public sealed class AreaTableRecord + { + public uint Id; + public AreaFlags[] Flags = new AreaFlags[2]; + public string ZoneName; + public float AmbientMultiplier; + public LocalizedString AreaName; + public ushort MapId; + public ushort ParentAreaID; + public short AreaBit; + public ushort AmbienceID; + public ushort ZoneMusic; + public ushort IntroSound; + public ushort[] LiquidTypeID = new ushort[4]; + public ushort UWZoneMusic; + public ushort UWAmbience; + public ushort PvPCombastWorldStateID; + public byte SoundProviderPref; + public byte SoundProviderPrefUnderwater; + public byte ExplorationLevel; + public byte FactionGroupMask; + public byte MountFlags; + public byte WildBattlePetLevelMin; + public byte WildBattlePetLevelMax; + public byte WindSettingsID; + public byte[] UWIntroSound = new byte[2]; + + public bool IsSanctuary() + { + if (MapId == 609) + return true; + + return Flags[0].HasAnyFlag(AreaFlags.Sanctuary); + } + } + + public sealed class AreaTriggerRecord + { + public Vector3 Pos; + public float Radius; + public float BoxLength; + public float BoxWidth; + public float BoxHeight; + public float BoxYaw; + public ushort MapID; + public ushort PhaseID; + public ushort PhaseGroupID; + public ushort ShapeID; + public ushort AreaTriggerActionSetID; + public byte PhaseUseFlags; + public byte ShapeType; + public byte Flags; + public uint Id; + } + + public sealed class ArmorLocationRecord + { + public uint Id; + public float[] Modifier = new float[5]; + } + + public sealed class ArtifactRecord + { + public uint Id; + public LocalizedString Name; + public uint BarConnectedColor; + public uint BarDisconnectedColor; + public uint TitleColor; + public ushort ClassUiTextureKitID; + public ushort SpecID; + public byte ArtifactCategoryID; + public byte Flags; + public uint UiModelSceneID; + public uint SpellVisualKitID; + } + + public sealed class ArtifactAppearanceRecord + { + public LocalizedString Name; + public uint SwatchColor; + public float ModelDesaturation; + public float ModelAlpha; + public uint ShapeshiftDisplayID; + public ushort ArtifactAppearanceSetID; + public ushort Unknown; + public byte DisplayIndex; + public byte AppearanceModID; + public byte Flags; + public byte ModifiesShapeshiftFormDisplay; + public uint Id; + public uint PlayerConditionID; + public uint ItemAppearanceID; + public uint AltItemAppearanceID; + } + + public sealed class ArtifactAppearanceSetRecord + { + public LocalizedString Name; + public LocalizedString Name2; + public ushort UiCameraID; + public ushort AltHandUICameraID; + public byte ArtifactID; + public byte DisplayIndex; + public byte AttachmentPoint; + public byte Flags; + public uint Id; + } + + public sealed class ArtifactCategoryRecord + { + public uint Id; + public ushort ArtifactKnowledgeCurrencyID; + public ushort ArtifactKnowledgeMultiplierCurveID; + } + + public sealed class ArtifactPowerRecord + { + public Vector2 Pos; + public byte ArtifactID; + public ArtifactPowerFlag Flags; + public byte MaxRank; + public byte ArtifactTier; + public uint Id; + public int RelicType; + } + + public sealed class ArtifactPowerLinkRecord + { + public uint Id; + public ushort FromArtifactPowerID; + public ushort ToArtifactPowerID; + } + + public sealed class ArtifactPowerPickerRecord + { + public uint Id; + public uint PlayerConditionID; + } + + public sealed class ArtifactPowerRankRecord + { + public uint Id; + public uint SpellID; + public float Value; + public ushort ArtifactPowerID; + public ushort Unknown; + public byte Rank; + } + + public sealed class ArtifactQuestXPRecord + { + public uint Id; + public uint[] Exp = new uint[10]; + } + + public sealed class AuctionHouseRecord + { + public uint Id; + public LocalizedString Name; + public ushort FactionID; + public byte DepositRate; + public byte ConsignmentRate; + } +} diff --git a/Game/DataStorage/Structs/B_Records.cs b/Game/DataStorage/Structs/B_Records.cs new file mode 100644 index 000000000..cd5380455 --- /dev/null +++ b/Game/DataStorage/Structs/B_Records.cs @@ -0,0 +1,119 @@ +/* + * Copyright (C) 2012-2017 CypherCore + * + * 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, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +namespace Game.DataStorage +{ + public sealed class BankBagSlotPricesRecord + { + public uint Id; + public uint Cost; + } + + public sealed class BannedAddOnsRecord + { + public uint Id; + public string Name; + public string Version; + public byte[] Flags = new byte[4]; + } + + public sealed class BarberShopStyleRecord + { + public LocalizedString DisplayName; + public LocalizedString Description; + public float CostModifier; + public byte Type; + public byte Race; + public byte Sex; + public byte Data; + public uint Id; + } + + public sealed class BattlePetBreedQualityRecord + { + public uint Id; + public float Modifier; + public byte Quality; + } + + public sealed class BattlePetBreedStateRecord + { + public uint Id; + public short Value; + public byte BreedID; + public byte State; + } + + public sealed class BattlePetSpeciesRecord + { + public uint CreatureID; + public uint IconFileID; + public uint SummonSpellID; + public LocalizedString SourceText; + public LocalizedString Description; + public ushort Flags; + public byte PetType; + public sbyte Source; + public uint Id; + public uint CardModelSceneID; + public uint LoadoutModelSceneID; + } + + public sealed class BattlePetSpeciesStateRecord + { + public uint Id; + public int Value; + public ushort SpeciesID; + public byte State; + } + + public sealed class BattlemasterListRecord + { + public uint Id; + public LocalizedString Name; + public uint IconFileDataID; + public LocalizedString GameType; + public LocalizedString ShortDescription; + public LocalizedString LongDescription; + public short[] MapId = new short[16]; + public ushort HolidayWorldState; + public ushort PlayerConditionId; + public byte InstanceType; + public byte GroupsAllowed; + public byte MaxGroupSize; + public byte MinLevel; + public byte MaxLevel; + public byte RatedPlayers; + public byte MinPlayers; + public byte MaxPlayers; + public byte Flags; + } + + public sealed class BroadcastTextRecord + { + public uint Id; + public LocalizedString MaleText; + public LocalizedString FemaleText; + public ushort[] EmoteID = new ushort[3]; + public ushort[] EmoteDelay = new ushort[3]; + public ushort UnkEmoteID; + public byte Language; + public byte Type; + public uint[] SoundID = new uint[2]; + public uint PlayerConditionID; + } +} diff --git a/Game/DataStorage/Structs/C_Records.cs b/Game/DataStorage/Structs/C_Records.cs new file mode 100644 index 000000000..9ba9cbe53 --- /dev/null +++ b/Game/DataStorage/Structs/C_Records.cs @@ -0,0 +1,340 @@ +/* + * Copyright (C) 2012-2017 CypherCore + * + * 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, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +using Framework.Constants; +using Framework.GameMath; + +namespace Game.DataStorage +{ + public sealed class CharSectionsRecord + { + public uint Id; + public uint[] TextureFileDataID = new uint[3]; + public ushort Flags; + public byte Race; + public byte Gender; + public byte GenType; + public byte Type; + public byte Color; + } + + public sealed class CharStartOutfitRecord + { + public uint Id; + public int[] ItemID = new int[24]; + public uint PetDisplayID; + public byte RaceID; + public byte ClassID; + public byte GenderID; + public byte OutfitID; + public byte PetFamilyID; + } + + public sealed class CharTitlesRecord + { + public uint Id; + public LocalizedString NameMale; + public LocalizedString NameFemale; + public ushort MaskID; + public byte Flags; + } + + public sealed class ChatChannelsRecord + { + public uint Id; + public ChannelDBCFlags Flags; + public LocalizedString Name; + public LocalizedString Shortcut; + public byte FactionGroup; + } + + public sealed class ChrClassesRecord + { + public string PetNameToken; + public LocalizedString Name; + public LocalizedString NameFemale; + public LocalizedString NameMale; + public uint FileName; + public uint CreateScreenFileDataID; + public uint SelectScreenFileDataID; + public uint IconFileDataID; + public uint LowResScreenFileDataID; + public ushort Flags; + public ushort CinematicSequenceID; + public ushort DefaultSpec; + public PowerType PowerType; + public byte SpellClassSet; + public byte AttackPowerPerStrength; + public byte AttackPowerPerAgility; + public byte RangedAttackPowerPerAgility; + public byte Unk1; + public uint Id; + } + + public sealed class ChrClassesXPowerTypesRecord + { + public uint Id; + public byte ClassID; + public byte PowerType; + } + + public sealed class ChrRacesRecord + { + public uint Id; + public uint Flags; + public uint ClientPrefix; + public uint ClientFileString; + public LocalizedString Name; + public LocalizedString NameFemale; + public LocalizedString NameMale; + public string[] FacialHairCustomization = new string[2]; + public string HairCustomization; + public uint CreateScreenFileDataID; + public uint SelectScreenFileDataID; + public uint[] MaleCustomizeOffset = new uint[3]; + public uint[] FemaleCustomizeOffset = new uint[3]; + public uint LowResScreenFileDataID; + public ushort FactionID; + public ushort MaleDisplayID; + public ushort FemaleDisplayID; + public ushort ResSicknessSpellID; + public ushort SplashSoundID; + public ushort CinematicSequenceID; + public byte BaseLanguage; + public byte CreatureType; + public byte TeamID; + public byte RaceRelated; + public byte UnalteredVisualRaceID; + public byte CharComponentTextureLayoutID; + public byte DefaultClassID; + public byte NeutralRaceID; + public byte ItemAppearanceFrameRaceID; + public byte CharComponentTexLayoutHiResID; + public uint HighResMaleDisplayID; + public uint HighResFemaleDisplayID; + public uint[] Unk = new uint[3]; + } + + public sealed class ChrSpecializationRecord + { + public uint[] MasterySpellID = new uint[PlayerConst.MaxMasterySpells]; + public LocalizedString Name; + public LocalizedString Name2; + public LocalizedString Description; + public byte ClassID; + public byte OrderIndex; + public byte PetTalentType; + public byte Role; + public byte PrimaryStatOrder; + public uint Id; + public uint IconFileDataID; + public ChrSpecializationFlag Flags; + public uint AnimReplacementSetID; + + public bool IsPetSpecialization() + { + return ClassID == 0; + } + } + + public sealed class CinematicCameraRecord + { + public uint ID; + public uint SoundID; + public Vector3 Origin; + public float OriginFacing; + public uint ModelFileDataID; + } + + public sealed class CinematicSequencesRecord + { + public uint Id; + public uint SoundID; + public ushort[] Camera = new ushort[8]; + } + + public sealed class ConversationLineRecord + { + public uint Id; + public uint BroadcastTextID; + public uint SpellVisualKitID; + public uint Duration; + public ushort NextLineID; + public ushort Unk1; + public byte Yell; + public byte Unk2; + public byte Unk3; + } + + public sealed class CreatureDisplayInfoRecord + { + public uint Id; + public float CreatureModelScale; + public ushort ModelID; + public ushort NPCSoundID; + public byte SizeClass; + public byte Flags; + public sbyte Gender; + public uint ExtendedDisplayInfoID; + public uint[] TextureVariation = new uint[3]; + public uint PortraitTextureFileDataID; + public byte CreatureModelAlpha; + public ushort SoundID; + public float PlayerModelScale; + public uint PortraitCreatureDisplayInfoID; + public byte BloodID; + public ushort ParticleColorID; + public uint CreatureGeosetData; + public ushort ObjectEffectPackageID; + public ushort AnimReplacementSetID; + public sbyte UnarmedWeaponSubclass; + public uint StateSpellVisualKitID; + public float InstanceOtherPlayerPetScale; // scale of not own player pets inside dungeons/raids/scenarios + public uint MountSpellVisualKitID; + } + + public sealed class CreatureDisplayInfoExtraRecord + { + public uint Id; + public uint FileDataID; + public uint HDFileDataID; + public byte DisplayRaceID; + public byte DisplaySexID; + public byte DisplayClassID; + public byte SkinID; + public byte FaceID; + public byte HairStyleID; + public byte HairColorID; + public byte FacialHairID; + public byte[] CustomDisplayOption = new byte[3]; + public byte Flags; + } + + public sealed class CreatureFamilyRecord + { + public uint Id; + public float MinScale; + public float MaxScale; + public LocalizedString Name; + public uint IconFileDataID; + public ushort[] SkillLine = new ushort[2]; + public ushort PetFoodMask; + public byte MinScaleLevel; + public byte MaxScaleLevel; + public byte PetTalentType; + } + + public sealed class CreatureModelDataRecord + { + public uint Id; + public float ModelScale; + public float FootprintTextureLength; + public float FootprintTextureWidth; + public float FootprintParticleScale; + public float CollisionWidth; + public float CollisionHeight; + public float MountHeight; + public float[] GeoBoxMin = new float[3]; + public float[] GeoBoxMax = new float[3]; + public float WorldEffectScale; + public float AttachedEffectScale; + public float MissileCollisionRadius; + public float MissileCollisionPush; + public float MissileCollisionRaise; + public float OverrideLootEffectScale; + public float OverrideNameScale; + public float OverrideSelectionRadius; + public float TamedPetBaseScale; + public float HoverHeight; + public uint Flags; + public uint FileDataID; + public uint SizeClass; + public uint BloodID; + public uint FootprintTextureID; + public uint FoleyMaterialID; + public uint FootstepEffectID; + public uint DeathThudEffectID; + public uint SoundID; + public uint CreatureGeosetDataID; + } + + public sealed class CreatureTypeRecord + { + public uint Id; + public LocalizedString Name; + public byte Flags; + } + + public sealed class CriteriaRecord + { + public uint Id; + public uint Asset; + public uint StartAsset; + public uint FailAsset; + public ushort StartTimer; + public ushort ModifierTreeId; + public ushort EligibilityWorldStateID; + public CriteriaTypes Type; + public CriteriaTimedTypes StartEvent; + public byte FailEvent; + public byte Flags; + public byte EligibilityWorldStateValue; + } + + public sealed class CriteriaTreeRecord + { + public uint Id; + public uint Amount; + public LocalizedString Description; + public ushort Parent; + public CriteriaTreeFlags Flags; + public byte Operator; + public uint CriteriaID; + public int OrderIndex; + } + + public sealed class CurrencyTypesRecord + { + public uint Id; + public LocalizedString Name; + public uint MaxQty; + public uint MaxEarnablePerWeek; + public CurrencyFlags Flags; + public LocalizedString Description; + public byte CategoryID; + public byte SpellCategory; + public byte Quality; + public uint InventoryIconFileDataID; + public uint SpellWeight; + } + + public sealed class CurveRecord + { + public uint ID; + public byte Type; + public byte Unused; + } + + public sealed class CurvePointRecord + { + public uint Id; + public float X; + public float Y; + public ushort CurveID; + public byte Index; + } +} diff --git a/Game/DataStorage/Structs/D_Records.cs b/Game/DataStorage/Structs/D_Records.cs new file mode 100644 index 000000000..f08489bef --- /dev/null +++ b/Game/DataStorage/Structs/D_Records.cs @@ -0,0 +1,92 @@ +/* + * Copyright (C) 2012-2017 CypherCore + * + * 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, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +using Framework.Constants; + +namespace Game.DataStorage +{ + public sealed class DestructibleModelDataRecord + { + public uint Id; + public ushort StateDamagedDisplayID; + public ushort StateDestroyedDisplayID; + public ushort StateRebuildingDisplayID; + public ushort StateSmokeDisplayID; + public ushort HealEffectSpeed; + public byte StateDamagedImpactEffectDoodadSet; + public byte StateDamagedAmbientDoodadSet; + public byte StateDamagedNameSet; + public byte StateDestroyedDestructionDoodadSet; + public byte StateDestroyedImpactEffectDoodadSet; + public byte StateDestroyedAmbientDoodadSet; + public byte StateDestroyedNameSet; + public byte StateRebuildingDestructionDoodadSet; + public byte StateRebuildingImpactEffectDoodadSet; + public byte StateRebuildingAmbientDoodadSet; + public byte StateRebuildingNameSet; + public byte StateSmokeInitDoodadSet; + public byte StateSmokeAmbientDoodadSet; + public byte StateSmokeNameSet; + public byte EjectDirection; + public byte DoNotHighlight; + public byte HealEffect; + } + + public sealed class DifficultyRecord + { + public uint Id; + public LocalizedString Name; + public ushort GroupSizeHealthCurveID; + public ushort GroupSizeDmgCurveID; + public ushort GroupSizeSpellPointsCurveID; + public byte FallbackDifficultyID; + public MapTypes InstanceType; + public byte MinPlayers; + public byte MaxPlayers; + public sbyte OldEnumValue; + public DifficultyFlags Flags; + public byte ToggleDifficultyID; + public byte ItemBonusTreeModID; + public byte OrderIndex; + } + + public sealed class DungeonEncounterRecord + { + public LocalizedString Name; + public uint CreatureDisplayID; + public ushort MapID; + public byte DifficultyID; + public byte Bit; + public byte Flags; + public uint Id; + public uint OrderIndex; + public uint TextureFileDataID; + } + + public sealed class DurabilityCostsRecord + { + public uint Id; + public ushort[] WeaponSubClassCost = new ushort[21]; + public ushort[] ArmorSubClassCost = new ushort[8]; + } + + public sealed class DurabilityQualityRecord + { + public uint Id; + public float QualityMod; + } +} diff --git a/Game/DataStorage/Structs/E_Records.cs b/Game/DataStorage/Structs/E_Records.cs new file mode 100644 index 000000000..687296bd4 --- /dev/null +++ b/Game/DataStorage/Structs/E_Records.cs @@ -0,0 +1,50 @@ +/* + * Copyright (C) 2012-2017 CypherCore + * + * 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, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +namespace Game.DataStorage +{ + public sealed class EmotesRecord + { + public uint Id; + public uint EmoteSlashCommand; + public uint SpellVisualKitID; + public uint EmoteFlags; + public int RaceMask; + public ushort AnimID; + public byte EmoteSpecProc; + public uint EmoteSpecProcParam; + public uint EmoteSoundID; + public int ClassMask; + } + + public sealed class EmotesTextRecord + { + public uint Id; + public LocalizedString Name; + public ushort EmoteID; + } + + public sealed class EmotesTextSoundRecord + { + public uint Id; + public ushort EmotesTextId; + public byte RaceId; + public byte SexId; + public byte ClassId; + public uint SoundId; + } +} diff --git a/Game/DataStorage/Structs/F_Records.cs b/Game/DataStorage/Structs/F_Records.cs new file mode 100644 index 000000000..c1f7dc1c9 --- /dev/null +++ b/Game/DataStorage/Structs/F_Records.cs @@ -0,0 +1,102 @@ +/* + * Copyright (C) 2012-2017 CypherCore + * + * 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, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +using Framework.Constants; +using System; + +namespace Game.DataStorage +{ + public sealed class FactionRecord + { + public uint Id; + public uint[] ReputationRaceMask = new uint[4]; + public int[] ReputationBase = new int[4]; + public float ParentFactionModIn; // Faction gains incoming rep * ParentFactionModIn + public float ParentFactionModOut; // Faction outputs rep * ParentFactionModOut as spillover reputation + public LocalizedString Name; + public LocalizedString Description; + public uint[] ReputationMax = new uint[4]; + public short ReputationIndex; + public ushort[] ReputationClassMask = new ushort[4]; + public ushort[] ReputationFlags = new ushort[4]; + public ushort ParentFactionID; + public ushort ParagonFactionID; + public byte ParentFactionCapIn; // The highest rank the faction will profit from incoming spillover + public byte ParentFactionCapOut; + public byte Expansion; + public byte Flags; + public byte FriendshipRepID; + + public bool CanHaveReputation() + { + return ReputationIndex >= 0; + } + } + + public sealed class FactionTemplateRecord + { + public uint Id; + public ushort Faction; + public ushort Flags; + public ushort[] Enemies = new ushort[4]; + public ushort[] Friends = new ushort[4]; + public byte Mask; + public byte FriendMask; + public byte EnemyMask; + + public bool IsFriendlyTo(FactionTemplateRecord entry) + { + if (Id == entry.Id) + return true; + if (entry.Faction != 0) + { + for (int i = 0; i < 4; ++i) + if (Enemies[i] == entry.Faction) + return false; + for (int i = 0; i < 4; ++i) + if (Friends[i] == entry.Faction) + return true; + } + return Convert.ToBoolean(FriendMask & entry.Mask) || Convert.ToBoolean(Mask & entry.FriendMask); + } + public bool IsHostileTo(FactionTemplateRecord entry) + { + if (Id == entry.Id) + return false; + if (entry.Faction != 0) + { + for (int i = 0; i < 4; ++i) + if (Enemies[i] == entry.Faction) + return true; + for (int i = 0; i < 4; ++i) + if (Friends[i] == entry.Faction) + return false; + } + return (EnemyMask & entry.Mask) != 0; + } + public bool IsHostileToPlayers() { return (EnemyMask & (uint)FactionMasks.Player) != 0; } + public bool IsNeutralToAll() + { + for (int i = 0; i < 4; ++i) + if (Enemies[i] != 0) + return false; + + return EnemyMask == 0 && FriendMask == 0; + } + public bool IsContestedGuardFaction() { return Flags.HasAnyFlag((ushort)FactionTemplateFlags.ContestedGuard); } + } +} diff --git a/Game/DataStorage/Structs/G_Records.cs b/Game/DataStorage/Structs/G_Records.cs new file mode 100644 index 000000000..5407c27bb --- /dev/null +++ b/Game/DataStorage/Structs/G_Records.cs @@ -0,0 +1,270 @@ +/* + * Copyright (C) 2012-2017 CypherCore + * + * 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, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +using Framework.Constants; +using Framework.GameMath; + +namespace Game.DataStorage +{ + public sealed class GameObjectsRecord + { + public Vector3 Position; + public float RotationX; + public float RotationY; + public float RotationZ; + public float RotationW; + public float Size; + public int[] Data = new int[8]; + public LocalizedString Name; + public ushort MapID; + public ushort DisplayID; + public ushort PhaseID; + public ushort PhaseGroupID; + public byte PhaseUseFlags; + public GameObjectTypes Type; + public uint Id; + } + + public sealed class GameObjectDisplayInfoRecord + { + public uint Id; + public uint FileDataID; + public Vector3 GeoBoxMin; + public Vector3 GeoBoxMax; + public float OverrideLootEffectScale; + public float OverrideNameScale; + public ushort ObjectEffectPackageID; + } + + public sealed class GarrAbilityRecord + { + public LocalizedString Name; + public LocalizedString Description; + public uint IconFileDataID; + public GarrisonAbilityFlags Flags; + public ushort OtherFactionGarrAbilityID; + public byte GarrAbilityCategoryID; + public byte FollowerTypeID; + public uint Id; + } + + public sealed class GarrBuildingRecord + { + public uint Id; + public uint HordeGameObjectID; + public uint AllianceGameObjectID; + public LocalizedString NameAlliance; + public LocalizedString NameHorde; + public LocalizedString Description; + public LocalizedString Tooltip; + public uint IconFileDataID; + public ushort CostCurrencyID; + public ushort HordeTexPrefixKitID; + public ushort AllianceTexPrefixKitID; + public ushort AllianceActivationScenePackageID; + public ushort HordeActivationScenePackageID; + public ushort FollowerRequiredGarrAbilityID; + public ushort FollowerGarrAbilityEffectID; + public short CostMoney; + public byte Unknown; + public byte Type; + public byte Level; + public GarrisonBuildingFlags Flags; + public byte MaxShipments; + public byte GarrTypeID; + public int BuildDuration; + public int CostCurrencyAmount; + public int BonusAmount; + } + + public sealed class GarrBuildingPlotInstRecord + { + public Vector2 LandmarkOffset; + public ushort UiTextureAtlasMemberID; + public ushort GarrSiteLevelPlotInstID; + public byte GarrBuildingID; + public uint Id; + } + + public sealed class GarrClassSpecRecord + { + public LocalizedString NameMale; + public LocalizedString NameFemale; + public LocalizedString NameGenderless; + public ushort ClassAtlasID; // UiTextureAtlasMember.db2 ref + public ushort GarrFollItemSetID; + public byte Limit; + public byte Flags; + public uint Id; + } + + public sealed class GarrFollowerRecord + { + public uint HordeCreatureID; + public uint AllianceCreatureID; + public LocalizedString HordeSourceText; + public LocalizedString AllianceSourceText; + public uint HordePortraitIconID; + public uint AlliancePortraitIconID; + public uint HordeAddedBroadcastTextID; + public uint AllianceAddedBroadcastTextID; + public LocalizedString Name; + public ushort HordeGarrFollItemSetID; + public ushort AllianceGarrFollItemSetID; + public ushort ItemLevelWeapon; + public ushort ItemLevelArmor; + public ushort HordeListPortraitTextureKitID; + public ushort AllianceListPortraitTextureKitID; + public byte FollowerTypeID; + public byte HordeUiAnimRaceInfoID; + public byte AllianceUiAnimRaceInfoID; + public byte Quality; + public byte HordeGarrClassSpecID; + public byte AllianceGarrClassSpecID; + public byte Level; + public byte Unknown1; + public byte Flags; + public sbyte Unknown2; + public sbyte Unknown3; + public byte GarrTypeID; + public byte MaxDurability; + public byte Class; + public byte HordeFlavorTextGarrStringID; + public byte AllianceFlavorTextGarrStringID; + public uint Id; + } + + public sealed class GarrFollowerXAbilityRecord + { + public uint Id; + public ushort GarrFollowerID; + public ushort GarrAbilityID; + public byte FactionIndex; + } + + public sealed class GarrPlotRecord + { + public uint Id; + public LocalizedString Name; + public uint AllianceConstructionGameObjectID; + public uint HordeConstructionGameObjectID; + public byte GarrPlotUICategoryID; + public byte PlotType; + public byte Flags; + public uint MinCount; + public uint MaxCount; + } + + public sealed class GarrPlotBuildingRecord + { + public uint Id; + public byte GarrPlotID; + public byte GarrBuildingID; + } + + public sealed class GarrPlotInstanceRecord + { + public uint Id; + public LocalizedString Name; + public byte GarrPlotID; + } + + public sealed class GarrSiteLevelRecord + { + public uint Id; + public Vector2 TownHall; + public ushort MapID; + public ushort SiteID; + public ushort MovieID; + public ushort UpgradeResourceCost; + public ushort UpgradeMoneyCost; + public byte Level; + public byte UITextureKitID; + public byte Level2; + } + + public sealed class GarrSiteLevelPlotInstRecord + { + public uint Id; + public Vector2 Landmark; + public ushort GarrSiteLevelID; + public byte GarrPlotInstanceID; + public byte Unknown; + } + + public sealed class GemPropertiesRecord + { + public uint Id; + public SocketColor Type; + public ushort EnchantID; + public ushort MinItemLevel; + } + + public sealed class GlyphBindableSpellRecord + { + public uint Id; + public uint SpellID; + public ushort GlyphPropertiesID; + } + + public sealed class GlyphPropertiesRecord + { + public uint Id; + public uint SpellID; + public ushort SpellIconID; + public byte Type; + public byte GlyphExclusiveCategoryID; + } + + public sealed class GlyphRequiredSpecRecord + { + public uint Id; + public ushort GlyphPropertiesID; + public ushort ChrSpecializationID; + } + + public sealed class GuildColorBackgroundRecord + { + public uint Id; + + public byte Red; + public byte Green; + public byte Blue; + } + + public sealed class GuildColorBorderRecord + { + public uint Id; + public byte Red; + public byte Green; + public byte Blue; + } + + public sealed class GuildColorEmblemRecord + { + public uint Id; + public byte Red; + public byte Green; + public byte Blue; + } + + public sealed class GuildPerkSpellsRecord + { + public uint Id; + public uint SpellID; + } +} diff --git a/Game/DataStorage/Structs/GameTablesRecords.cs b/Game/DataStorage/Structs/GameTablesRecords.cs new file mode 100644 index 000000000..23cfcc78e --- /dev/null +++ b/Game/DataStorage/Structs/GameTablesRecords.cs @@ -0,0 +1,183 @@ +/* + * Copyright (C) 2012-2017 CypherCore + * + * 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, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +namespace Game.DataStorage +{ + public sealed class GtArmorMitigationByLvlRecord + { + public float Mitigation; + } + + public sealed class GtArtifactKnowledgeMultiplierRecord + { + public float Multiplier; + } + + public sealed class GtArtifactLevelXPRecord + { + public float XP; + public float XP2; + } + + public sealed class GtBarberShopCostBaseRecord + { + public float Cost; + } + + public sealed class GtBaseMPRecord + { + public float Rogue; + public float Druid; + public float Hunter; + public float Mage; + public float Paladin; + public float Priest; + public float Shaman; + public float Warlock; + public float Warrior; + public float DeathKnight; + public float Monk; + public float DemonHunter; + } + + public sealed class GtCombatRatingsRecord + { + public float Amplify; + public float DefenseSkill; + public float Dodge; + public float Parry; + public float Block; + public float HitMelee; + public float HitRanged; + public float HitSpell; + public float CritMelee; + public float CritRanged; + public float CritSpell; + public float MultiStrike; + public float Readiness; + public float Speed; + public float ResilienceCritTaken; + public float ResiliencePlayerDamage; + public float Lifesteal; + public float HasteMelee; + public float HasteRanged; + public float HasteSpell; + public float Avoidance; + public float Sturdiness; + public float Unused7; + public float Expertise; + public float ArmorPenetration; + public float Mastery; + public float PvPPower; + public float Cleave; + public float VersatilityDamageDone; + public float VersatilityHealingDone; + public float VersatilityDamageTaken; + public float Unused12; + } + + public sealed class GtCombatRatingsMultByILvlRecord + { + public float ArmorMultiplier; + public float WeaponMultiplier; + public float TrinketMultiplier; + public float JewelryMultiplier; + } + + public sealed class GtHonorLevelRecord + { + public float[] Prestige = new float[33]; + } + + public sealed class GtHpPerStaRecord + { + public float Health; + } + + public sealed class GtItemSocketCostPerLevelRecord + { + public float SocketCost; + } + + public sealed class GtNpcDamageByClassRecord + { + public float Rogue; + public float Druid; + public float Hunter; + public float Mage; + public float Paladin; + public float Priest; + public float Shaman; + public float Warlock; + public float Warrior; + public float DeathKnight; + public float Monk; + public float DemonHunter; + } + + public sealed class GtNpcManaCostScalerRecord + { + public float Scaler; + } + + public sealed class GtNpcTotalHpRecord + { + public float Rogue; + public float Druid; + public float Hunter; + public float Mage; + public float Paladin; + public float Priest; + public float Shaman; + public float Warlock; + public float Warrior; + public float DeathKnight; + public float Monk; + public float DemonHunter; + } + + public sealed class GtSpellScalingRecord + { + public float Rogue; + public float Druid; + public float Hunter; + public float Mage; + public float Paladin; + public float Priest; + public float Shaman; + public float Warlock; + public float Warrior; + public float DeathKnight; + public float Monk; + public float DemonHunter; + public float Item; + public float Consumable; + public float Gem1; + public float Gem2; + public float Gem3; + public float Health; + } + + public sealed class GtXpRecord + { + public float Total; + public float PerKill; + public float Junk; + public float Stats; + public float Divisor; + } +} diff --git a/Game/DataStorage/Structs/H_Records.cs b/Game/DataStorage/Structs/H_Records.cs new file mode 100644 index 000000000..88131d70c --- /dev/null +++ b/Game/DataStorage/Structs/H_Records.cs @@ -0,0 +1,50 @@ +/* + * Copyright (C) 2012-2017 CypherCore + * + * 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, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +using Framework.Constants; + +namespace Game.DataStorage +{ + public sealed class HeirloomRecord + { + public uint ItemID; + public LocalizedString SourceText; + public uint[] OldItem = new uint[2]; + public uint NextDifficultyItemID; + public uint[] UpgradeItemID = new uint[3]; + public ushort[] ItemBonusListID = new ushort[3]; + public byte Flags; + public byte Source; + public uint Id; + } + + public sealed class HolidaysRecord + { + public uint Id; + public uint[] Date = new uint[SharedConst.MaxHolidayDates]; // dates in unix time starting at January, 1, 2000 + public ushort[] Duration = new ushort[SharedConst.MaxHolidayDurations]; + public ushort Region; + public byte Looping; + public byte[] CalendarFlags = new byte[SharedConst.MaxHolidayFlags]; + public byte Priority; + public sbyte CalendarFilterType; + public byte Flags; + public uint HolidayNameID; + public uint HolidayDescriptionID; + public int[] TextureFileDataID = new int[3]; + } +} diff --git a/Game/DataStorage/Structs/I_Records.cs b/Game/DataStorage/Structs/I_Records.cs new file mode 100644 index 000000000..be6b5ac2a --- /dev/null +++ b/Game/DataStorage/Structs/I_Records.cs @@ -0,0 +1,383 @@ +/* + * Copyright (C) 2012-2017 CypherCore + * + * 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, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +using Framework.Constants; + +namespace Game.DataStorage +{ + public sealed class ImportPriceArmorRecord + { + public uint Id; + public float ClothFactor; + public float LeatherFactor; + public float MailFactor; + public float PlateFactor; + } + + public sealed class ImportPriceQualityRecord + { + public uint Id; + public float Factor; + } + + public sealed class ImportPriceShieldRecord + { + public uint Id; + public float Factor; + } + + public sealed class ImportPriceWeaponRecord + { + public uint Id; + public float Factor; + } + + public sealed class ItemRecord + { + public uint Id; + public uint FileDataID; + public ItemClass Class; + public byte SubClass; + public sbyte SoundOverrideSubclass; + public sbyte Material; + public InventoryType inventoryType; + public byte Sheath; + public byte GroupSoundsID; + } + + public sealed class ItemAppearanceRecord + { + public uint Id; + public uint DisplayID; + public uint IconFileDataID; + public uint UIOrder; + public byte ObjectComponentSlot; + } + + public sealed class ItemArmorQualityRecord + { + public uint Id; + public float[] QualityMod = new float[7]; + public ushort ItemLevel; + } + + public sealed class ItemArmorShieldRecord + { + public uint Id; + public float[] Quality = new float[7]; + public ushort ItemLevel; + } + + public sealed class ItemArmorTotalRecord + { + public uint Id; + public float[] Value = new float[4]; + public ushort ItemLevel; + } + + public sealed class ItemBagFamilyRecord + { + public uint Id; + public uint Name; + } + + public sealed class ItemBonusRecord + { + public uint Id; + public int[] Value = new int[2]; + public ushort BonusListID; + public ItemBonusType Type; + public byte Index; + } + + public sealed class ItemBonusListLevelDeltaRecord + { + public short Delta; + public uint Id; + } + + public sealed class ItemBonusTreeNodeRecord + { + public uint Id; + public ushort BonusTreeID; + public ushort SubTreeID; + public ushort BonusListID; + public ushort ItemLevelSelectorID; + public byte BonusTreeModID; + } + + public sealed class ItemChildEquipmentRecord + { + public uint Id; + public uint ItemID; + public uint AltItemID; + public byte AltEquipmentSlot; + } + + public sealed class ItemClassRecord + { + public uint Id; + public float PriceMod; + public LocalizedString Name; + public byte OldEnumValue; + public byte Flags; + } + + public sealed class ItemCurrencyCostRecord + { + public uint Id; + public uint ItemId; + } + + // common struct for: + // ItemDamageAmmo.dbc + // ItemDamageOneHand.dbc + // ItemDamageOneHandCaster.dbc + // ItemDamageRanged.dbc + // ItemDamageThrown.dbc + // ItemDamageTwoHand.dbc + // ItemDamageTwoHandCaster.dbc + // ItemDamageWand.dbc + public sealed class ItemDamageRecord + { + public uint Id; + public float[] DPS = new float[7]; + public ushort ItemLevel; + } + + public sealed class ItemDisenchantLootRecord + { + public uint Id; + public ushort MinItemLevel; + public ushort MaxItemLevel; + public ushort RequiredDisenchantSkill; + public byte ItemClass; + public sbyte ItemSubClass; + public byte ItemQuality; + } + + public sealed class ItemEffectRecord + { + public uint Id; + public uint ItemID; + public uint SpellID; + public int Cooldown; + public int CategoryCooldown; + public short Charges; + public ushort Category; + public ushort ChrSpecializationID; + public byte OrderIndex; + public ItemSpelltriggerType Trigger; + } + + public sealed class ItemExtendedCostRecord + { + public uint Id; + public uint[] RequiredItem = new uint[ItemConst.MaxItemExtCostItems]; // required item id + public uint[] RequiredCurrencyCount = new uint[ItemConst.MaxItemExtCostCurrencies]; // required curency count + public ushort[] RequiredItemCount = new ushort[ItemConst.MaxItemExtCostItems]; // required count of 1st item + public ushort RequiredPersonalArenaRating; // required personal arena rating + public ushort[] RequiredCurrency = new ushort[ItemConst.MaxItemExtCostCurrencies]; // required curency id + public byte RequiredArenaSlot; // arena slot restrictions (min slot value) + public byte RequiredFactionId; + public byte RequiredFactionStanding; + public byte RequirementFlags; + public byte RequiredAchievement; + } + + public sealed class ItemLimitCategoryRecord + { + public uint Id; + public LocalizedString Name; + public byte Quantity; + public byte Flags; + } + + public sealed class ItemModifiedAppearanceRecord + { + public uint ItemID; + public ushort AppearanceID; + public byte AppearanceModID; + public byte Index; + public byte SourceType; + public uint Id; + } + + public sealed class ItemPriceBaseRecord + { + public uint Id; + public float ArmorFactor; + public float WeaponFactor; + public ushort ItemLevel; + } + + public sealed class ItemRandomPropertiesRecord + { + public uint Id; + public LocalizedString Name; + public ushort[] Enchantment = new ushort[ItemConst.MaxItemRandomProperties]; + } + + public sealed class ItemRandomSuffixRecord + { + public uint Id; + public LocalizedString Name; + public ushort[] Enchantment = new ushort[ItemConst.MaxItemRandomProperties]; + public ushort[] AllocationPct = new ushort[ItemConst.MaxItemRandomProperties]; + } + + public sealed class ItemSearchNameRecord + { + public uint Id; + public LocalizedString Name; + public uint[] Flags = new uint[3]; + public uint AllowableRace; + public uint RequiredSpell; + public ushort RequiredReputationFaction; + public ushort RequiredSkill; + public ushort RequiredSkillRank; + public ushort ItemLevel; + public byte Quality; + public byte RequiredExpansion; + public byte RequiredReputationRank; + public byte RequiredLevel; + public int AllowableClass; + } + + public sealed class ItemSetRecord + { + public uint Id; + public LocalizedString Name; + public uint[] ItemID = new uint[17]; + public ushort RequiredSkillRank; + public uint RequiredSkill; + public ItemSetFlags Flags; + } + + public sealed class ItemSetSpellRecord + { + public uint Id; + public uint SpellID; + public ushort ItemSetID; + public ushort ChrSpecID; + public byte Threshold; + } + + public sealed class ItemSparseRecord + { + public uint Id; + public uint[] Flags = new uint[3]; + public float Unk1; + public float Unk2; + public uint BuyCount; + public uint BuyPrice; + public uint SellPrice; + public int AllowableRace; + public uint RequiredSpell; + public uint MaxCount; + public uint Stackable; + public int[] ItemStatAllocation = new int[ItemConst.MaxStats]; + public float[] ItemStatSocketCostMultiplier = new float[ItemConst.MaxStats]; + public float RangedModRange; + public LocalizedString Name; + public LocalizedString Name2; + public LocalizedString Name3; + public LocalizedString Name4; + public LocalizedString Description; + public uint BagFamily; + public float ArmorDamageModifier; + public uint Duration; + public float StatScalingFactor; + public short AllowableClass; + public ushort ItemLevel; + public ushort RequiredSkill; + public ushort RequiredSkillRank; + public ushort RequiredReputationFaction; + public short[] ItemStatValue = new short[ItemConst.MaxStats]; + public ushort ScalingStatDistribution; + public ushort Delay; + public ushort PageText; + public ushort StartQuest; + public ushort LockID; + public ushort RandomProperty; + public ushort RandomSuffix; + public ushort ItemSet; + public ushort Area; + public ushort Map; + public ushort TotemCategory; + public ushort SocketBonus; + public ushort GemProperties; + public ushort ItemLimitCategory; + public ushort HolidayID; + public ushort RequiredTransmogHolidayID; + public ushort ItemNameDescriptionID; + public byte Quality; + public InventoryType inventoryType; + public sbyte RequiredLevel; + public byte RequiredHonorRank; + public byte RequiredCityRank; + public byte RequiredReputationRank; + public byte ContainerSlots; + public sbyte[] ItemStatType = new sbyte[ItemConst.MaxStats]; + public byte DamageType; + public byte Bonding; + public byte LanguageID; + public byte PageMaterial; + public sbyte Material; + public byte Sheath; + public byte[] SocketColor = new byte[ItemConst.MaxGemSockets]; + public byte CurrencySubstitutionID; + public byte CurrencySubstitutionCount; + public byte ArtifactID; + public byte RequiredExpansion; + } + + public sealed class ItemSpecRecord + { + public uint Id; + public ushort SpecID; + public byte MinLevel; + public byte MaxLevel; + public byte ItemType; + public ItemSpecStat PrimaryStat; + public ItemSpecStat SecondaryStat; + } + + public sealed class ItemSpecOverrideRecord + { + public uint Id; + public uint ItemID; + public ushort SpecID; + } + + public sealed class ItemUpgradeRecord + { + public uint Id; + public uint CurrencyCost; + public ushort PrevItemUpgradeID; + public ushort CurrencyID; + public byte ItemUpgradePathID; + public byte ItemLevelBonus; + } + + public sealed class ItemXBonusTreeRecord + { + public uint Id; + public uint ItemID; + public ushort BonusTreeID; + } +} diff --git a/Game/DataStorage/Structs/K_Records.cs b/Game/DataStorage/Structs/K_Records.cs new file mode 100644 index 000000000..418e65404 --- /dev/null +++ b/Game/DataStorage/Structs/K_Records.cs @@ -0,0 +1,25 @@ +/* + * Copyright (C) 2012-2017 CypherCore + * + * 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, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +namespace Game.DataStorage +{ + public sealed class KeyChainRecord + { + public uint Id; + public byte[] Key = new byte[32]; + } +} diff --git a/Game/DataStorage/Structs/L_Records.cs b/Game/DataStorage/Structs/L_Records.cs new file mode 100644 index 000000000..74e49ce96 --- /dev/null +++ b/Game/DataStorage/Structs/L_Records.cs @@ -0,0 +1,106 @@ +/* + * Copyright (C) 2012-2017 CypherCore + * + * 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, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +using Framework.Constants; +using Framework.GameMath; + +namespace Game.DataStorage +{ + public sealed class LfgDungeonsRecord + { + public uint Id; + public LocalizedString Name; + public LfgFlags Flags; + public LocalizedString Description; + public float MinItemLevel; + public ushort MaxLevel; + public ushort TargetLevelMax; + public short MapID; + public ushort RandomID; + public ushort ScenarioID; + public ushort LastBossJournalEncounterID; + public ushort BonusReputationAmount; + public ushort MentorItemLevel; + public uint PlayerConditionID; + public byte MinLevel; + public byte TargetLevel; + public byte TargetLevelMin; + public Difficulty DifficultyID; + public LfgType Type; + public byte Faction; + public byte Expansion; + public byte OrderIndex; + public byte GroupID; + public byte CountTank; + public byte CountHealer; + public byte CountDamage; + public byte MinCountTank; + public byte MinCountHealer; + public byte MinCountDamage; + public byte SubType; + public byte MentorCharLevel; + public int TextureFileDataID; + public int RewardIconFileDataID; + public int ProposalTextureFileDataID; + + // Helpers + public uint Entry() { return (uint)(Id + ((int)Type << 24)); } + } + + public sealed class LightRecord + { + public uint Id; + public Vector3 Pos; + public float FalloffStart; + public float FalloffEnd; + public ushort MapID; + public ushort[] LightParamsID = new ushort[8]; + } + + public sealed class LiquidTypeRecord + { + public uint Id; + public LocalizedString Name; + public uint SpellID; + public float MaxDarkenDepth; + public float FogDarkenIntensity; + public float AmbDarkenIntensity; + public float DirDarkenIntensity; + public float ParticleScale; + public string[] Texture = new string[6]; + public uint[] Color = new uint[2]; + public float[] Float = new float[18]; + public uint[] Int = new uint[4]; + public ushort Flags; + public ushort LightID; + public byte LiquidType; + public byte ParticleMovement; + public byte ParticleTexSlots; + public byte MaterialID; + public byte[] DepthTexCount = new byte[6]; + public uint SoundID; + } + + public sealed class LockRecord + { + public uint Id; + public uint[] Index = new uint[SharedConst.MaxLockCase]; + public ushort[] Skill = new ushort[SharedConst.MaxLockCase]; + public byte[] LockType = new byte[SharedConst.MaxLockCase]; + public byte[] Action = new byte[SharedConst.MaxLockCase]; + } +} diff --git a/Game/DataStorage/Structs/M2Structure.cs b/Game/DataStorage/Structs/M2Structure.cs new file mode 100644 index 000000000..ff628d2ae --- /dev/null +++ b/Game/DataStorage/Structs/M2Structure.cs @@ -0,0 +1,240 @@ +/* + * Copyright (C) 2012-2017 CypherCore + * + * 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, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +using Framework.GameMath; +using System.IO; +using System.Runtime.InteropServices; + +namespace Game.DataStorage +{ + struct M2SplineKey + { + public M2SplineKey(BinaryReader reader) + { + p0 = new Vector3(reader.ReadSingle(), reader.ReadSingle(), reader.ReadSingle()); + p1 = new Vector3(reader.ReadSingle(), reader.ReadSingle(), reader.ReadSingle()); + p2 = new Vector3(reader.ReadSingle(), reader.ReadSingle(), reader.ReadSingle()); + } + + public Vector3 p0; + public Vector3 p1; + public Vector3 p2; + } + + struct M2Header + { + public M2Header(BinaryReader reader) + { + Magic = null;/// reader.ReadStringFromChars(4); + Version = reader.ReadUInt32(); + lName = reader.ReadUInt32(); + ofsName = reader.ReadUInt32(); + GlobalModelFlags = reader.ReadUInt32(); + nGlobalSequences = reader.ReadUInt32(); + ofsGlobalSequences = reader.ReadUInt32(); + nAnimations = reader.ReadUInt32(); + ofsAnimations = reader.ReadUInt32(); + nAnimationLookup = reader.ReadUInt32(); + ofsAnimationLookup = reader.ReadUInt32(); + nBones = reader.ReadUInt32(); + ofsBones = reader.ReadUInt32(); + nKeyBoneLookup = reader.ReadUInt32(); + ofsKeyBoneLookup = reader.ReadUInt32(); + nVertices = reader.ReadUInt32(); + ofsVertices = reader.ReadUInt32(); + nViews = reader.ReadUInt32(); + nSubmeshAnimations = reader.ReadUInt32(); + ofsSubmeshAnimations = reader.ReadUInt32(); + nTextures = reader.ReadUInt32(); + ofsTextures = reader.ReadUInt32(); + nTransparency = reader.ReadUInt32(); + ofsTransparency = reader.ReadUInt32(); + nUVAnimation = reader.ReadUInt32(); + ofsUVAnimation = reader.ReadUInt32(); + nTexReplace = reader.ReadUInt32(); + ofsTexReplace = reader.ReadUInt32(); + nRenderFlags = reader.ReadUInt32(); + ofsRenderFlags = reader.ReadUInt32(); + nBoneLookupTable = reader.ReadUInt32(); + ofsBoneLookupTable = reader.ReadUInt32(); + nTexLookup = reader.ReadUInt32(); + ofsTexLookup = reader.ReadUInt32(); + nTexUnits = reader.ReadUInt32(); + ofsTexUnits = reader.ReadUInt32(); + nTransLookup = reader.ReadUInt32(); + ofsTransLookup = reader.ReadUInt32(); + nUVAnimLookup = reader.ReadUInt32(); + ofsUVAnimLookup = reader.ReadUInt32(); + BoundingBox = new AxisAlignedBox(new Vector3(reader.ReadSingle(), reader.ReadSingle(), reader.ReadSingle()) , new Vector3(reader.ReadSingle(), reader.ReadSingle(), reader.ReadSingle())); + BoundingSphereRadius = reader.ReadSingle(); + CollisionBox = new AxisAlignedBox(new Vector3(reader.ReadSingle(), reader.ReadSingle(), reader.ReadSingle()), new Vector3(reader.ReadSingle(), reader.ReadSingle(), reader.ReadSingle())); + CollisionSphereRadius = reader.ReadSingle(); + nBoundingTriangles = reader.ReadUInt32(); + ofsBoundingTriangles = reader.ReadUInt32(); + nBoundingVertices = reader.ReadUInt32(); + ofsBoundingVertices = reader.ReadUInt32(); + nBoundingNormals = reader.ReadUInt32(); + ofsBoundingNormals = reader.ReadUInt32(); + nAttachments = reader.ReadUInt32(); + ofsAttachments = reader.ReadUInt32(); + nAttachLookup = reader.ReadUInt32(); + ofsAttachLookup = reader.ReadUInt32(); + nEvents = reader.ReadUInt32(); + ofsEvents = reader.ReadUInt32(); + nLights = reader.ReadUInt32(); + ofsLights = reader.ReadUInt32(); + nCameras = reader.ReadUInt32(); + ofsCameras = reader.ReadUInt32(); + nCameraLookup = reader.ReadUInt32(); + ofsCameraLookup = reader.ReadUInt32(); + nRibbonEmitters = reader.ReadUInt32(); + ofsRibbonEmitters = reader.ReadUInt32(); + nParticleEmitters = reader.ReadUInt32(); + ofsParticleEmitters = reader.ReadUInt32(); + nBlendMaps = reader.ReadUInt32(); + ofsBlendMaps = reader.ReadUInt32(); + } + + [MarshalAs(UnmanagedType.ByValArray, SizeConst = 4)] + public char[] Magic; // "MD20" + public uint Version; // The version of the format. + public uint lName; // Length of the model's name including the trailing \0 + public uint ofsName; // Offset to the name, it seems like models can get reloaded by this name.should be unique, i guess. + public uint GlobalModelFlags; // 0x0001: tilt x, 0x0002: tilt y, 0x0008: add 2 fields in header, 0x0020: load .phys data (MoP+), 0x0080: has _lod .skin files (MoP?+), 0x0100: is camera related. + public uint nGlobalSequences; + public uint ofsGlobalSequences; // A list of timestamps. + public uint nAnimations; + public uint ofsAnimations; // Information about the animations in the model. + public uint nAnimationLookup; + public uint ofsAnimationLookup; // Mapping of global IDs to the entries in the Animation sequences block. + public uint nBones; // MAX_BONES = 0x100 + public uint ofsBones; // Information about the bones in this model. + public uint nKeyBoneLookup; + public uint ofsKeyBoneLookup; // Lookup table for key skeletal bones. + public uint nVertices; + public uint ofsVertices; // Vertices of the model. + public uint nViews; // Views (LOD) are now in .skins. + public uint nSubmeshAnimations; + public uint ofsSubmeshAnimations; // Submesh color and alpha animations definitions. + public uint nTextures; + public uint ofsTextures; // Textures of this model. + public uint nTransparency; + public uint ofsTransparency; // Transparency of textures. + public uint nUVAnimation; + public uint ofsUVAnimation; + public uint nTexReplace; + public uint ofsTexReplace; // Replaceable Textures. + public uint nRenderFlags; + public uint ofsRenderFlags; // Blending modes / render flags. + public uint nBoneLookupTable; + public uint ofsBoneLookupTable; // A bone lookup table. + public uint nTexLookup; + public uint ofsTexLookup; // The same for textures. + public uint nTexUnits; // possibly removed with cata?! + public uint ofsTexUnits; // And texture units. Somewhere they have to be too. + public uint nTransLookup; + public uint ofsTransLookup; // Everything needs its lookup. Here are the transparencies. + public uint nUVAnimLookup; + public uint ofsUVAnimLookup; + public AxisAlignedBox BoundingBox; // min/max( [1].z, 2.0277779f ) - 0.16f seems to be the maximum camera height + public float BoundingSphereRadius; + public AxisAlignedBox CollisionBox; + public float CollisionSphereRadius; + public uint nBoundingTriangles; + public uint ofsBoundingTriangles; // Our bounding volumes. Similar structure like in the old ofsViews. + public uint nBoundingVertices; + public uint ofsBoundingVertices; + public uint nBoundingNormals; + public uint ofsBoundingNormals; + public uint nAttachments; + public uint ofsAttachments; // Attachments are for weapons etc. + public uint nAttachLookup; + public uint ofsAttachLookup; // Of course with a lookup. + public uint nEvents; + public uint ofsEvents; // Used for playing sounds when dying and a lot else. + public uint nLights; + public uint ofsLights; // Lights are mainly used in loginscreens but in wands and some doodads too. + public uint nCameras; // Format of Cameras changed with version 271! + public uint ofsCameras; // The cameras are present in most models for having a model in the Character-Tab. + public uint nCameraLookup; + public uint ofsCameraLookup; // And lookup-time again. + public uint nRibbonEmitters; + public uint ofsRibbonEmitters; // Things swirling around. See the CoT-entrance for light-trails. + public uint nParticleEmitters; + public uint ofsParticleEmitters; // Spells and weapons, doodads and loginscreens use them. Blood dripping of a blade? Particles. + public uint nBlendMaps; // This has to deal with blending. Exists IFF (flags & 0x8) != 0. When set, textures blending is overriden by the associated array. See M2/WotLK#Blend_mode_overrides + public uint ofsBlendMaps; // Same as above. Points to an array of uint16 of nBlendMaps entries -- From WoD information.}; + } + + struct M2Array + { + public M2Array(BinaryReader reader, uint offset) + { + if (offset != 0) + reader.BaseStream.Position = offset; + + number = reader.ReadUInt32(); + offset_elements = reader.ReadUInt32(); + } + + public uint number; + public uint offset_elements; + } + + struct M2Track + { + public void Read(BinaryReader reader) + { + interpolation_type = reader.ReadUInt16(); + global_sequence = reader.ReadUInt16(); + timestamps = new M2Array(reader, 0); + values = new M2Array(reader, 0); + } + + public ushort interpolation_type; + public ushort global_sequence; + public M2Array timestamps; + public M2Array values; + } + + struct M2Camera + { + public void Read(BinaryReader reader, M2Header header) + { + reader.BaseStream.Position = header.ofsCameras; + type = reader.ReadUInt32(); + far_clip = reader.ReadSingle(); + near_clip = reader.ReadSingle(); + positions.Read(reader); + position_base = new Vector3(reader.ReadSingle(), reader.ReadSingle(), reader.ReadSingle()); + target_positions.Read(reader); + target_position_base = new Vector3(reader.ReadSingle(), reader.ReadSingle(), reader.ReadSingle()); + rolldata.Read(reader); + fovdata.Read(reader); + } + + public uint type; // 0: portrait, 1: characterinfo; -1: else (flyby etc.); referenced backwards in the lookup table. + public float far_clip; + public float near_clip; + public M2Track positions; // How the camera's position moves. Should be 3*3 floats. + public Vector3 position_base; + public M2Track target_positions; // How the target moves. Should be 3*3 floats. + public Vector3 target_position_base; + public M2Track rolldata; // The camera can have some roll-effect. Its 0 to 2*Pi. + public M2Track fovdata; // FoV for this segment + } +} diff --git a/Game/DataStorage/Structs/M_Records.cs b/Game/DataStorage/Structs/M_Records.cs new file mode 100644 index 000000000..c45486207 --- /dev/null +++ b/Game/DataStorage/Structs/M_Records.cs @@ -0,0 +1,179 @@ +/* + * Copyright (C) 2012-2017 CypherCore + * + * 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, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +using Framework.Constants; +using Framework.GameMath; +using System; + +namespace Game.DataStorage +{ + public sealed class MailTemplateRecord + { + public uint Id; + public LocalizedString Body; + } + + public sealed class MapRecord + { + public uint Id; + + public uint Directory; + public MapFlags[] Flags = new MapFlags[2]; + public float MinimapIconScale; + public Vector2 CorpsePos; // entrance coordinates in ghost mode (in most cases = normal entrance) + public LocalizedString MapName; + public LocalizedString MapDescription0; // Horde + public LocalizedString MapDescription1; // Alliance + public LocalizedString ShortDescription; + public LocalizedString LongDescription; + public ushort AreaTableID; + public ushort LoadingScreenID; + public short CorpseMapID; // map_id of entrance map in ghost mode (continent always and in most cases = normal entrance) + public ushort TimeOfDayOverride; + public short ParentMapID; + public short CosmeticParentMapID; + public ushort WindSettingsID; + public MapTypes InstanceType; + public byte unk5; + public byte ExpansionID; + public byte MaxPlayers; + public byte TimeOffset; + + //Helpers + public Expansion Expansion() { return (Expansion)ExpansionID; } + + public bool IsDungeon() { return (InstanceType == MapTypes.Instance || InstanceType == MapTypes.Raid) && !IsGarrison(); } + public bool IsNonRaidDungeon() { return InstanceType == MapTypes.Instance; } + public bool Instanceable() + { + return InstanceType == MapTypes.Instance || InstanceType == MapTypes.Raid + || InstanceType == MapTypes.Battleground || InstanceType == MapTypes.Arena; + } + public bool IsRaid() { return InstanceType == MapTypes.Raid; } + public bool IsBattleground() { return InstanceType == MapTypes.Battleground; } + public bool IsBattleArena() { return InstanceType == MapTypes.Arena; } + public bool IsBattlegroundOrArena() { return InstanceType == MapTypes.Battleground || InstanceType == MapTypes.Arena; } + public bool IsWorldMap() { return InstanceType == MapTypes.Common; } + + public bool GetEntrancePos(out uint mapid, out float x, out float y) + { + mapid = 0; + x = 0; + y = 0; + + if (CorpseMapID < 0) + return false; + mapid = (uint)CorpseMapID; + x = CorpsePos.X; + y = CorpsePos.Y; + return true; + } + + public bool IsContinent() + { + return Id == 0 || Id == 1 || Id == 530 || Id == 571 || Id == 870 || Id == 1116 || Id == 1220; + } + + public bool IsDynamicDifficultyMap() { return Flags[0].HasAnyFlag(MapFlags.CanToggleDifficulty); } + public bool IsGarrison() { return Flags[0].HasAnyFlag(MapFlags.Garrison); } + } + + public sealed class MapDifficultyRecord + { + public uint Id; + public LocalizedString Message_lang; // m_message_lang (text showed when transfer to map failed) + public ushort MapID; + public byte DifficultyID; + public byte RaidDurationType; // 1 means daily reset, 2 means weekly + public byte MaxPlayers; // m_maxPlayers some heroic versions have 0 when expected same amount as in normal version + public byte LockID; + public byte Flags; + public byte ItemBonusTreeModID; + public uint Context; + + public uint GetRaidDuration() + { + if (RaidDurationType == 1) + return 86400; + if (RaidDurationType == 2) + return 604800; + return 0; + } + } + + public sealed class ModifierTreeRecord + { + public uint Id; + public uint[] Asset = new uint[2]; + public ushort Parent; + public byte Type; + public byte Unk700; + public byte Operator; + public byte Amount; + } + + public sealed class MountRecord + { + public uint SpellId; + public LocalizedString Name; + public LocalizedString Description; + public LocalizedString SourceDescription; + public float CameraPivotMultiplier; + public ushort MountTypeId; + public ushort Flags; + public byte Source; + public uint Id; + public uint PlayerConditionId; + public int UiModelSceneID; + } + + public sealed class MountCapabilityRecord + { + public uint RequiredSpell; + public uint SpeedModSpell; + public ushort RequiredRidingSkill; + public ushort RequiredArea; + public short RequiredMap; + public MountCapabilityFlags Flags; + public uint Id; + public uint RequiredAura; + } + + public sealed class MountTypeXCapabilityRecord + { + public uint Id; + public ushort MountTypeID; + public ushort MountCapabilityID; + public byte OrderIndex; + } + + public sealed class MountXDisplayRecord + { + public uint ID; + public uint MountID; + public uint DisplayID; + public uint PlayerConditionID; + } + + public sealed class MovieRecord + { + public uint Id; + public uint AudioFileDataID; + public uint SubtitleFileDataID; + public byte Volume; + public byte KeyID; + } +} diff --git a/Game/DataStorage/Structs/MiscStructs.cs b/Game/DataStorage/Structs/MiscStructs.cs new file mode 100644 index 000000000..c90955568 --- /dev/null +++ b/Game/DataStorage/Structs/MiscStructs.cs @@ -0,0 +1,46 @@ +/* + * Copyright (C) 2012-2017 CypherCore + * + * 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, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +namespace Game.DataStorage +{ + public struct WMOAreaTableTripple + { + public WMOAreaTableTripple(int r, int a, int g) + { + groupId = g; + rootId = r; + adtId = a; + } + + // ordered by entropy; that way memcmp will have a minimal medium runtime + int groupId; + int rootId; + int adtId; + } + + public class TaxiPathBySourceAndDestination + { + public TaxiPathBySourceAndDestination(uint _id, uint _price) + { + ID = _id; + price = _price; + } + + public uint ID; + public uint price; + } +} diff --git a/Game/DataStorage/Structs/N_Records.cs b/Game/DataStorage/Structs/N_Records.cs new file mode 100644 index 000000000..e8fe16528 --- /dev/null +++ b/Game/DataStorage/Structs/N_Records.cs @@ -0,0 +1,47 @@ +/* + * Copyright (C) 2012-2017 CypherCore + * + * 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, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +namespace Game.DataStorage +{ + public sealed class NameGenRecord + { + public uint Id; + public LocalizedString Name; + public byte Race; + public byte Sex; + } + + public sealed class NamesProfanityRecord + { + public uint Id; + public string Name; + public sbyte Language; + } + + public sealed class NamesReservedRecord + { + public uint Id; + public string Name; + } + + public sealed class NamesReservedLocaleRecord + { + public uint Id; + public string Name; + public byte LocaleMask; + } +} diff --git a/Game/DataStorage/Structs/O_Records.cs b/Game/DataStorage/Structs/O_Records.cs new file mode 100644 index 000000000..d737c1f04 --- /dev/null +++ b/Game/DataStorage/Structs/O_Records.cs @@ -0,0 +1,29 @@ +/* + * Copyright (C) 2012-2017 CypherCore + * + * 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, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +using Framework.Constants; + +namespace Game.DataStorage +{ + public sealed class OverrideSpellDataRecord + { + public uint Id; + public uint[] SpellID = new uint[SharedConst.MaxOverrideSpell]; + public uint PlayerActionbarFileDataID; + public byte Flags; + } +} diff --git a/Game/DataStorage/Structs/P_Records.cs b/Game/DataStorage/Structs/P_Records.cs new file mode 100644 index 000000000..1bd5548d1 --- /dev/null +++ b/Game/DataStorage/Structs/P_Records.cs @@ -0,0 +1,178 @@ +/* + * Copyright (C) 2012-2017 CypherCore + * + * 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, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +using Framework.Constants; +using System; + +namespace Game.DataStorage +{ + public sealed class PhaseRecord + { + public uint Id; + public ushort Flags; + } + + public sealed class PhaseXPhaseGroupRecord + { + public uint Id; + public ushort PhaseId; + public ushort PhaseGroupID; + } + + public sealed class PlayerConditionRecord + { + public uint Id; + public uint RaceMask; + public uint SkillLogic; + public uint ReputationLogic; + public uint PrevQuestLogic; + public uint CurrQuestLogic; + public uint CurrentCompletedQuestLogic; + public uint SpellLogic; + public uint ItemLogic; + public uint[] Time = new uint[2]; + public uint AuraSpellLogic; + public uint[] AuraSpellID = new uint[4]; + public uint AchievementLogic; + public uint AreaLogic; + public uint QuestKillLogic; + public LocalizedString FailureDescription; + public ushort MinLevel; + public ushort MaxLevel; + public ushort[] SkillID = new ushort[4]; + public short[] MinSkill = new short[4]; + public short[] MaxSkill = new short[4]; + public ushort MaxFactionID; + public ushort[] PrevQuestID = new ushort[4]; + public ushort[] CurrQuestID = new ushort[4]; + public ushort[] CurrentCompletedQuestID = new ushort[4]; + public ushort[] Explored = new ushort[2]; + public ushort WorldStateExpressionID; + public ushort[] Achievement = new ushort[4]; + public ushort[] AreaID = new ushort[4]; + public ushort QuestKillID; + public ushort PhaseID; + public ushort MinAvgEquippedItemLevel; + public ushort MaxAvgEquippedItemLevel; + public ushort ModifierTreeID; + public byte Flags; + public sbyte Gender; + public sbyte NativeGender; + public byte MinLanguage; + public byte MaxLanguage; + public byte[] MinReputation = new byte[3]; + public byte MaxReputation; + public byte Unknown1; + public byte MinPVPRank; + public byte MaxPVPRank; + public byte PvpMedal; + public byte ItemFlags; + public byte[] AuraCount = new byte[4]; + public byte WeatherID; + public byte PartyStatus; + public byte LifetimeMaxPVPRank; + public byte[] LfgStatus = new byte[4]; + public byte[] LfgCompare = new byte[4]; + public byte[] CurrencyCount = new byte[4]; + public sbyte MinExpansionLevel; + public sbyte MaxExpansionLevel; + public sbyte MinExpansionTier; + public sbyte MaxExpansionTier; + public byte MinGuildLevel; + public byte MaxGuildLevel; + public byte PhaseUseFlags; + public sbyte ChrSpecializationIndex; + public sbyte ChrSpecializationRole; + public sbyte PowerType; + public sbyte PowerTypeComp; + public sbyte PowerTypeValue; + public int ClassMask; + public uint LanguageID; + public uint[] MinFactionID = new uint[3]; + public uint[] SpellID = new uint[4]; + public uint[] ItemID = new uint[4]; + public uint[] ItemCount = new uint[4]; + public uint LfgLogic; + public uint[] LfgValue = new uint[4]; + public uint CurrencyLogic; + public uint[] CurrencyID = new uint[4]; + public uint[] QuestKillMonster = new uint[6]; + public uint PhaseGroupID; + public uint MinAvgItemLevel; + public uint MaxAvgItemLevel; + public int[] MovementFlags = new int[2]; + public uint MainHandItemSubclassMask; + } + + public sealed class PowerDisplayRecord + { + public uint Id; + public uint GlobalStringBaseTag; + public byte PowerType; + public byte Red; + public byte Green; + public byte Blue; + } + + public sealed class PowerTypeRecord + { + public uint Id; + public string PowerTypeToken; + public string PowerCostToken; + public float RegenerationPeace; + public float RegenerationCombat; + public short MaxPower; + public ushort RegenerationDelay; + public ushort Flags; + public PowerType PowerTypeEnum; + public sbyte RegenerationMin; + public sbyte RegenerationCenter; + public sbyte RegenerationMax; + public byte UIModifier; + } + + public sealed class PrestigeLevelInfoRecord + { + public uint ID; + public uint IconID; + public LocalizedString PrestigeText; + public byte PrestigeLevel; + public PrestigeLevelInfoFlags Flags; + + public bool IsDisabled() { return Flags.HasAnyFlag(PrestigeLevelInfoFlags.Disabled); } + } + + public sealed class PvpDifficultyRecord + { + public uint Id; + public ushort MapID; + public byte BracketID; + public byte MinLevel; + public byte MaxLevel; + + // helpers + public BattlegroundBracketId GetBracketId() { return (BattlegroundBracketId)BracketID; } + } + + public sealed class PvpRewardRecord + { + public uint Id; + public uint HonorLevel; + public uint Prestige; + public uint RewardPackID; + } +} diff --git a/Game/DataStorage/Structs/Q_Records.cs b/Game/DataStorage/Structs/Q_Records.cs new file mode 100644 index 000000000..a0140d2a5 --- /dev/null +++ b/Game/DataStorage/Structs/Q_Records.cs @@ -0,0 +1,61 @@ +/* + * Copyright (C) 2012-2017 CypherCore + * + * 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, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +using Framework.Constants; + +namespace Game.DataStorage +{ + public sealed class QuestFactionRewardRecord + { + public uint Id; + public short[] QuestRewFactionValue = new short[10]; + } + + public sealed class QuestMoneyRewardRecord + { + public uint Id; + public uint[] Money = new uint[10]; + } + + public sealed class QuestPackageItemRecord + { + public uint Id; + public uint ItemID; + public ushort QuestPackageID; + public QuestPackageFilter FilterType; + public byte ItemCount; + } + + public sealed class QuestSortRecord + { + public uint Id; + public LocalizedString SortName; + public byte SortOrder; + } + + public sealed class QuestV2Record + { + public uint Id; + public ushort UniqueBitFlag; + } + + public sealed class QuestXPRecord + { + public uint Id; + public ushort[] Exp = new ushort[10]; + } +} diff --git a/Game/DataStorage/Structs/R_Records.cs b/Game/DataStorage/Structs/R_Records.cs new file mode 100644 index 000000000..334707bb8 --- /dev/null +++ b/Game/DataStorage/Structs/R_Records.cs @@ -0,0 +1,53 @@ +/* + * Copyright (C) 2012-2017 CypherCore + * + * 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, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +namespace Game.DataStorage +{ + public sealed class RandPropPointsRecord + { + public uint Id; + public uint[] EpicPropertiesPoints = new uint[5]; + public uint[] RarePropertiesPoints = new uint[5]; + public uint[] UncommonPropertiesPoints = new uint[5]; + } + + public sealed class RewardPackRecord + { + public uint Id; + public uint Money; + public float ArtifactXPMultiplier; + public byte ArtifactXPDifficulty; + public byte ArtifactCategoryID; + public uint TitleID; + public uint Unused; + } + + public sealed class RewardPackXItemRecord + { + public uint Id; + public uint ItemID; + public uint RewardPackID; + public uint Amount; + } + + public sealed class RulesetItemUpgradeRecord + { + public uint Id; + public uint ItemID; + public ushort ItemUpgradeID; + } +} diff --git a/Game/DataStorage/Structs/S_Records.cs b/Game/DataStorage/Structs/S_Records.cs new file mode 100644 index 000000000..b16071ddb --- /dev/null +++ b/Game/DataStorage/Structs/S_Records.cs @@ -0,0 +1,560 @@ +/* + * Copyright (C) 2012-2017 CypherCore + * + * 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, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +using Framework.Constants; +using Framework.Dynamic; +using System; + +namespace Game.DataStorage +{ + public sealed class ScalingStatDistributionRecord + { + public uint Id; + public ushort ItemLevelCurveID; + public uint MinLevel; + public uint MaxLevel; + } + + public sealed class ScenarioRecord + { + public uint Id; + public LocalizedString Name; + public ushort Data; // Seems to indicate different things, for zone invasions, this is the area id + public byte Flags; + public byte Type; + } + + public sealed class ScenarioStepRecord + { + public uint Id; + public LocalizedString Description; + public LocalizedString Name; + public ushort CriteriaTreeID; + public ushort ScenarioID; + public ushort PreviousStepID; // Used in conjunction with Proving Grounds scenarios, when sequencing steps (Not using step order?) + public ushort QuestRewardID; + public byte Step; + public ScenarioStepFlags Flags; + public uint BonusRequiredStepID; // Bonus step can only be completed if scenario is in the step specified in this field + + // helpers + public bool IsBonusObjective() + { + return Flags.HasAnyFlag(ScenarioStepFlags.BonusObjective); + } + } + + public sealed class SceneScriptRecord + { + public uint Id; + public string Name; + public string Script; + public ushort PrevScriptId; + public ushort NextScriptId; + } + + public sealed class SceneScriptPackageRecord + { + public uint Id; + public string Name; + } + + public sealed class SkillLineRecord + { + public uint Id; + public LocalizedString DisplayName; + public LocalizedString Description; + public LocalizedString AlternateVerb; + public ushort Flags; + public SkillCategory CategoryID; + public byte CanLink; + public uint IconFileDataID; + public uint ParentSkillLineID; + } + + public sealed class SkillLineAbilityRecord + { + public uint Id; + public uint SpellID; + public uint RaceMask; + public uint SupercedesSpell; + public ushort SkillLine; + public ushort MinSkillLineRank; + public ushort TrivialSkillLineRankHigh; + public ushort TrivialSkillLineRankLow; + public ushort UniqueBit; + public ushort TradeSkillCategoryID; + public AbilytyLearnType AcquireMethod; + public byte NumSkillUps; + public byte Unknown703; + public int ClassMask; + } + + public sealed class SkillRaceClassInfoRecord + { + public uint Id; + public int RaceMask; + public ushort SkillID; + public SkillRaceClassInfoFlags Flags; + public ushort SkillTierID; + public byte Availability; + public byte MinLevel; + public int ClassMask; + } + + public sealed class SoundKitRecord + { + public LocalizedString Name; + public float VolumeFloat; + public float MinDistance; + public float DistanceCutoff; + public float VolumeVariationPlus; + public float VolumeVariationMinus; + public float PitchVariationPlus; + public float PitchVariationMinus; + public float PitchAdjust; + public ushort Flags; + public ushort SoundEntriesAdvancedID; + public ushort BusOverwriteID; + public byte SoundType; + public byte EAXDef; + public byte DialogType; + public byte Unk700; + public uint Id; + } + + public sealed class SpecializationSpellsRecord + { + public uint SpellID; + public uint OverridesSpellID; + public LocalizedString Description; + public ushort SpecID; + public byte OrderIndex; + public uint Id; + } + + public sealed class SpellRecord + { + public LocalizedString Name; + public LocalizedString NameSubtext; + public LocalizedString Description; + public LocalizedString AuraDescription; + public uint MiscID; + public uint Id; + public uint DescriptionVariablesID; + } + + public sealed class SpellAuraOptionsRecord + { + public uint Id; + public uint SpellID; + public uint ProcCharges; + public uint ProcTypeMask; + public uint ProcCategoryRecovery; + public ushort CumulativeAura; + public byte DifficultyID; + public byte ProcChance; + public byte SpellProcsPerMinuteID; + } + + public sealed class SpellAuraRestrictionsRecord + { + public uint Id; + public uint SpellID; + public uint CasterAuraSpell; + public uint TargetAuraSpell; + public uint ExcludeCasterAuraSpell; + public uint ExcludeTargetAuraSpell; + public byte DifficultyID; + public byte CasterAuraState; + public byte TargetAuraState; + public byte ExcludeCasterAuraState; + public byte ExcludeTargetAuraState; + } + + public sealed class SpellCastTimesRecord + { + public uint Id; + public int CastTime; + public int MinCastTime; + public short CastTimePerLevel; + } + + public sealed class SpellCastingRequirementsRecord + { + public uint Id; + public uint SpellID; + public ushort MinFactionID; + public ushort RequiredAreasID; + public ushort RequiresSpellFocus; + public byte FacingCasterFlags; + public byte MinReputation; + public byte RequiredAuraVision; + } + + public sealed class SpellCategoriesRecord + { + public uint Id; + public uint SpellID; + public ushort Category; + public ushort StartRecoveryCategory; + public ushort ChargeCategory; + public byte DifficultyID; + public byte DefenseType; + public byte DispelType; + public byte Mechanic; + public byte PreventionType; + } + + public sealed class SpellCategoryRecord + { + public uint Id; + public LocalizedString Name; + public int ChargeRecoveryTime; + public SpellCategoryFlags Flags; + public byte UsesPerWeek; + public byte MaxCharges; + public uint ChargeCategoryType; + } + + public sealed class SpellClassOptionsRecord + { + public uint Id; + public uint SpellID; + public FlagArray128 SpellClassMask; + public byte SpellClassSet; + public uint ModalNextSpell; + } + + public sealed class SpellCooldownsRecord + { + public uint Id; + public uint SpellID; + public uint CategoryRecoveryTime; + public uint RecoveryTime; + public uint StartRecoveryTime; + public byte DifficultyID; + } + + public sealed class SpellDurationRecord + { + public uint Id; + public int Duration; + public int MaxDuration; + public int DurationPerLevel; + } + + public sealed class SpellEffectRecord + { + public FlagArray128 EffectSpellClassMask; + public uint Id; + public uint SpellID; + public uint Effect; + public uint EffectAura; + public int EffectBasePoints; + public uint EffectIndex; + public int EffectMiscValue; + public int EffectMiscValueB; + public uint EffectRadiusIndex; + public uint EffectRadiusMaxIndex; + public uint[] ImplicitTarget = new uint[2]; + public uint DifficultyID; + public float EffectAmplitude; + public uint EffectAuraPeriod; + public float EffectBonusCoefficient; + public float EffectChainAmplitude; + public uint EffectChainTargets; + public int EffectDieSides; + public uint EffectItemType; + public uint EffectMechanic; + public float EffectPointsPerResource; + public float EffectRealPointsPerLevel; + public uint EffectTriggerSpell; + public float EffectPosFacing; + public uint EffectAttributes; + public float BonusCoefficientFromAP; + public float PvPMultiplier; + } + + public sealed class SpellEffectScalingRecord + { + public uint Id; + public float Coefficient; + public float Variance; + public float ResourceCoefficient; + public uint SpellEffectID; + } + + public sealed class SpellEquippedItemsRecord + { + public uint Id; + public uint SpellID; + public int EquippedItemInventoryTypeMask; + public int EquippedItemSubClassMask; + public sbyte EquippedItemClass; + } + + public sealed class SpellFocusObjectRecord + { + public uint Id; + public LocalizedString Name; + } + + public sealed class SpellInterruptsRecord + { + public uint Id; + public uint SpellID; + public uint[] AuraInterruptFlags = new uint[2]; + public uint[] ChannelInterruptFlags = new uint[2]; + public ushort InterruptFlags; + public byte DifficultyID; + } + + public sealed class SpellItemEnchantmentRecord + { + public uint Id; + public uint[] EffectSpellID = new uint[ItemConst.MaxItemEnchantmentEffects]; + public LocalizedString Name; + public float[] EffectScalingPoints = new float[ItemConst.MaxItemEnchantmentEffects]; + public uint TransmogCost; + public uint TextureFileDataID; + public ushort[] EffectPointsMin = new ushort[ItemConst.MaxItemEnchantmentEffects]; + public ushort ItemVisual; + public EnchantmentSlotMask Flags; + public ushort RequiredSkillID; + public ushort RequiredSkillRank; + public ushort ItemLevel; + public byte Charges; + public ItemEnchantmentType[] Effect = new ItemEnchantmentType[ItemConst.MaxItemEnchantmentEffects]; + public byte ConditionID; + public byte MinLevel; + public byte MaxLevel; + public sbyte ScalingClass; + public sbyte ScalingClassRestricted; + public uint PlayerConditionID; + } + + public sealed class SpellItemEnchantmentConditionRecord + { + public uint Id; + public byte[] LTOperandType = new byte[5]; + public byte[] Operator = new byte[5]; + public byte[] RTOperandType = new byte[5]; + public byte[] RTOperand = new byte[5]; + public byte[] Logic = new byte[5]; + public uint[] LTOperand = new uint[5]; + } + + public sealed class SpellLearnSpellRecord + { + public uint Id; + public uint LearnSpellID; + public uint SpellID; + public uint OverridesSpellID; + } + + public sealed class SpellLevelsRecord + { + public uint Id; + public uint SpellID; + public ushort BaseLevel; + public ushort MaxLevel; + public ushort SpellLevel; + public byte DifficultyID; + public byte MaxUsableLevel; + } + + public sealed class SpellMiscRecord + { + public uint Id; + public uint Attributes; + public uint AttributesEx; + public uint AttributesExB; + public uint AttributesExC; + public uint AttributesExD; + public uint AttributesExE; + public uint AttributesExF; + public uint AttributesExG; + public uint AttributesExH; + public uint AttributesExI; + public uint AttributesExJ; + public uint AttributesExK; + public uint AttributesExL; + public uint AttributesExM; + public float Speed; + public float MultistrikeSpeedMod; + public ushort CastingTimeIndex; + public ushort DurationIndex; + public ushort RangeIndex; + public byte SchoolMask; + public uint IconFileDataID; + public uint ActiveIconFileDataID; + } + + public sealed class SpellPowerRecord + { + public uint SpellID; + public uint ManaCost; + public float ManaCostPercentage; + public float ManaCostPercentagePerSecond; + public uint RequiredAura; + public float HealthCostPercentage; + public byte PowerIndex; + public PowerType PowerType; + public uint Id; + public int ManaCostPerLevel; + public int ManaCostPerSecond; + public uint ManaCostAdditional; // Spell uses [ManaCost, ManaCost+ManaCostAdditional] power - affects tooltip parsing as multiplier on SpellEffectEntry::EffectPointsPerResource + // only SPELL_EFFECT_WEAPON_DAMAGE_NOSCHOOL, SPELL_EFFECT_WEAPON_PERCENT_DAMAGE, SPELL_EFFECT_WEAPON_DAMAGE, SPELL_EFFECT_NORMALIZED_WEAPON_DMG + public uint PowerDisplayID; + public uint UnitPowerBarID; + } + + public sealed class SpellPowerDifficultyRecord + { + public byte DifficultyID; + public byte PowerIndex; + public uint Id; + } + + public sealed class SpellProcsPerMinuteRecord + { + public uint Id; + public float BaseProcRate; + public byte Flags; + } + + public sealed class SpellProcsPerMinuteModRecord + { + public uint Id; + public float Coeff; + public ushort Param; + public SpellProcsPerMinuteModType Type; + public byte SpellProcsPerMinuteID; + } + + public sealed class SpellRadiusRecord + { + public uint Id; + public float Radius; + public float RadiusPerLevel; + public float RadiusMin; + public float RadiusMax; + } + + public sealed class SpellRangeRecord + { + public uint Id; + public float MinRangeHostile; + public float MinRangeFriend; + public float MaxRangeHostile; + public float MaxRangeFriend; + public LocalizedString DisplayName; + public LocalizedString DisplayNameShort; + public SpellRangeFlag Flags; + } + + public sealed class SpellReagentsRecord + { + public uint Id; + public uint SpellID; + public int[] Reagent = new int[SpellConst.MaxReagents]; + public ushort[] ReagentCount = new ushort[SpellConst.MaxReagents]; + } + + public sealed class SpellScalingRecord + { + public uint Id; + public uint SpellID; + public ushort ScalesFromItemLevel; + public int ScalingClass; + public uint MinScalingLevel; + public uint MaxScalingLevel; + } + + public sealed class SpellShapeshiftRecord + { + public uint Id; + public uint SpellID; + public uint[] ShapeshiftExclude = new uint[2]; + public uint[] ShapeshiftMask = new uint[2]; + public byte StanceBarOrder; + } + + public sealed class SpellShapeshiftFormRecord + { + public uint Id; + public LocalizedString Name; + public float WeaponDamageVariance; + public SpellShapeshiftFormFlags Flags; + public ushort CombatRoundTime; + public ushort MountTypeID; + public sbyte CreatureType; + public byte BonusActionBar; + public uint AttackIconFileDataID; + public ushort[] CreatureDisplayID = new ushort[4]; + public ushort[] PresetSpellID = new ushort[SpellConst.MaxShapeshift]; + } + + public sealed class SpellTargetRestrictionsRecord + { + public uint Id; + public uint SpellID; + public float ConeAngle; + public float Width; + public uint Targets; + public ushort TargetCreatureType; + public byte DifficultyID; + public byte MaxAffectedTargets; + public uint MaxTargetLevel; + } + + public sealed class SpellTotemsRecord + { + public uint Id; + public uint SpellID; + public uint[] Totem = new uint[SpellConst.MaxTotems]; + public ushort[] RequiredTotemCategoryID = new ushort[SpellConst.MaxTotems]; + } + + public sealed class SpellXSpellVisualRecord + { + public uint SpellID; + public uint SpellVisualID; + public uint Id; + public float Chance; + public ushort CasterPlayerConditionID; + public ushort CasterUnitConditionID; + public ushort PlayerConditionID; + public ushort UnitConditionID; + public uint IconFileDataID; + public uint ActiveIconFileDataID; + public byte Flags; + public byte DifficultyID; + public byte Priority; + } + + public sealed class SummonPropertiesRecord + { + public uint Id; + public uint Flags; + public SummonCategory Category; + public uint Faction; + public SummonType Type; + public int Slot; + } +} diff --git a/Game/DataStorage/Structs/T_Records.cs b/Game/DataStorage/Structs/T_Records.cs new file mode 100644 index 000000000..d11e79658 --- /dev/null +++ b/Game/DataStorage/Structs/T_Records.cs @@ -0,0 +1,113 @@ +/* + * Copyright (C) 2012-2017 CypherCore + * + * 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, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +using Framework.Constants; +using Framework.GameMath; + +namespace Game.DataStorage +{ + public sealed class TactKeyRecord + { + public uint Id; + public byte[] Key = new byte[16]; + } + + public sealed class TalentRecord + { + public uint Id; + public uint SpellID; + public uint OverridesSpellID; + public LocalizedString Description; + public ushort SpecID; + public byte TierID; + public byte ColumnIndex; + public byte Flags; + public byte[] CategoryMask = new byte[2]; + public byte ClassID; + } + + public sealed class TaxiNodesRecord + { + public Vector3 Pos; + public LocalizedString Name; + public uint[] MountCreatureID = new uint[2]; + public Vector2 MapOffset; + public ushort MapID; + public ushort ConditionID; + public ushort LearnableIndex; + public TaxiNodeFlags Flags; + public uint Id; + } + + public sealed class TaxiPathRecord + { + public ushort From; + public ushort To; + public uint Id; + public uint Cost; + } + + public sealed class TaxiPathNodeRecord + { + public Vector3 Loc; + public uint Delay; + public ushort PathID; + public ushort MapID; + public ushort ArrivalEventID; + public ushort DepartureEventID; + public byte NodeIndex; + public TaxiPathNodeFlags Flags; + public uint Id; + } + + public sealed class TotemCategoryRecord + { + public uint Id; + public LocalizedString Name; + public uint CategoryMask; + public byte CategoryType; + } + + public sealed class ToyRecord + { + public uint ItemID; + public LocalizedString Description; + public byte Flags; + public byte CategoryFilter; + public uint Id; + } + + public sealed class TransportAnimationRecord + { + public uint Id; + public uint TransportID; + public uint TimeIndex; + public Vector3 Pos; + public byte SequenceID; + } + + public sealed class TransportRotationRecord + { + public uint Id; + public uint TransportID; + public uint TimeIndex; + public float X; + public float Y; + public float Z; + public float W; + } +} diff --git a/Game/DataStorage/Structs/U_Records.cs b/Game/DataStorage/Structs/U_Records.cs new file mode 100644 index 000000000..081fbcc18 --- /dev/null +++ b/Game/DataStorage/Structs/U_Records.cs @@ -0,0 +1,40 @@ +/* + * Copyright (C) 2012-2017 CypherCore + * + * 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, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +namespace Game.DataStorage +{ + public sealed class UnitPowerBarRecord + { + public uint Id; + public float RegenerationPeace; + public float RegenerationCombat; + public uint[] FileDataID = new uint[6]; + public uint[] Color = new uint[6]; + public LocalizedString Name; + public LocalizedString Cost; + public LocalizedString OutOfError; + public LocalizedString ToolTip; + public float StartInset; + public float EndInset; + public ushort StartPower; + public ushort Flags; + public byte CenterPower; + public byte BarType; + public uint MinPower; + public uint MaxPower; + } +} diff --git a/Game/DataStorage/Structs/V_Records.cs b/Game/DataStorage/Structs/V_Records.cs new file mode 100644 index 000000000..ffa4ef79d --- /dev/null +++ b/Game/DataStorage/Structs/V_Records.cs @@ -0,0 +1,137 @@ +/* + * Copyright (C) 2012-2017 CypherCore + * + * 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, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +using Framework.Constants; +using Framework.GameMath; +using System; + +namespace Game.DataStorage +{ + public sealed class VehicleRecord + { + public uint Id; + public VehicleFlags Flags; + public float TurnSpeed; + public float PitchSpeed; + public float PitchMin; + public float PitchMax; + public float MouseLookOffsetPitch; + public float CameraFadeDistScalarMin; + public float CameraFadeDistScalarMax; + public float CameraPitchOffset; + public float FacingLimitRight; + public float FacingLimitLeft; + public float MsslTrgtTurnLingering; + public float MsslTrgtPitchLingering; + public float MsslTrgtMouseLingering; + public float MsslTrgtEndOpacity; + public float MsslTrgtArcSpeed; + public float MsslTrgtArcRepeat; + public float MsslTrgtArcWidth; + public float[] MsslTrgtImpactRadius = new float[2]; + public string MsslTrgtArcTexture; + public string MsslTrgtImpactTexture; + public string[] MsslTrgtImpactModel = new string[2]; + public float CameraYawOffset; + public float MsslTrgtImpactTexRadius; + public ushort[] SeatID = new ushort[SharedConst.MaxVehicleSeats]; + public ushort VehicleUIIndicatorID; + public ushort[] PowerDisplayID = new ushort[3]; + public byte FlagsB; + public byte UILocomotionType; + } + + public sealed class VehicleSeatRecord + { + public uint Id; + public uint[] Flags = new uint[3]; + public Vector3 AttachmentOffset; + public float EnterPreDelay; + public float EnterSpeed; + public float EnterGravity; + public float EnterMinDuration; + public float EnterMaxDuration; + public float EnterMinArcHeight; + public float EnterMaxArcHeight; + public float ExitPreDelay; + public float ExitSpeed; + public float ExitGravity; + public float ExitMinDuration; + public float ExitMaxDuration; + public float ExitMinArcHeight; + public float ExitMaxArcHeight; + public float PassengerYaw; + public float PassengerPitch; + public float PassengerRoll; + public float VehicleEnterAnimDelay; + public float VehicleExitAnimDelay; + public float CameraEnteringDelay; + public float CameraEnteringDuration; + public float CameraExitingDelay; + public float CameraExitingDuration; + public Vector3 CameraOffset; + public float CameraPosChaseRate; + public float CameraFacingChaseRate; + public float CameraEnteringZoom; + public float CameraSeatZoomMin; + public float CameraSeatZoomMax; + public uint UISkinFileDataID; + public short EnterAnimStart; + public short EnterAnimLoop; + public short RideAnimStart; + public short RideAnimLoop; + public short RideUpperAnimStart; + public short RideUpperAnimLoop; + public short ExitAnimStart; + public short ExitAnimLoop; + public short ExitAnimEnd; + public short VehicleEnterAnim; + public short VehicleExitAnim; + public short VehicleRideAnimLoop; + public ushort EnterAnimKitID; + public ushort RideAnimKitID; + public ushort ExitAnimKitID; + public ushort VehicleEnterAnimKitID; + public ushort VehicleRideAnimKitID; + public ushort VehicleExitAnimKitID; + public ushort CameraModeID; + public sbyte AttachmentID; + public sbyte PassengerAttachmentID; + public sbyte VehicleEnterAnimBone; + public sbyte VehicleExitAnimBone; + public sbyte VehicleRideAnimLoopBone; + public byte VehicleAbilityDisplay; + public uint EnterUISoundID; + public uint ExitUISoundID; + + + public bool CanEnterOrExit() + { + return (Flags[0].HasAnyFlag((uint)VehicleSeatFlags.CanEnterOrExit) || + //If it has anmation for enter/ride, means it can be entered/exited by logic + Flags[0].HasAnyFlag((uint)VehicleSeatFlags.HasLowerAnimForEnter | (uint)VehicleSeatFlags.HasLowerAnimForRide)); + } + public bool CanSwitchFromSeat() { return Flags[0].HasAnyFlag((uint)VehicleSeatFlags.CanSwitch); } + public bool IsUsableByOverride() + { + return Flags[0].HasAnyFlag((uint)VehicleSeatFlags.Uncontrolled | (uint)VehicleSeatFlags.Unk18) + || Flags[1].HasAnyFlag((uint)VehicleSeatFlagsB.UsableForced | (uint)VehicleSeatFlagsB.UsableForced2 | + (uint)VehicleSeatFlagsB.UsableForced3 | (uint)VehicleSeatFlagsB.UsableForced4); + } + public bool IsEjectable() { return Flags[1].HasAnyFlag((uint)VehicleSeatFlagsB.Ejectable); } + } +} diff --git a/Game/DataStorage/Structs/W_Records.cs b/Game/DataStorage/Structs/W_Records.cs new file mode 100644 index 000000000..cf1b4b10a --- /dev/null +++ b/Game/DataStorage/Structs/W_Records.cs @@ -0,0 +1,104 @@ +/* + * Copyright (C) 2012-2017 CypherCore + * + * 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, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +using Framework.Constants; +using Framework.GameMath; + +namespace Game.DataStorage +{ + public sealed class WMOAreaTableRecord + { + public int WMOGroupID; // used in group WMO + public LocalizedString AreaName; + public short WMOID; // used in root WMO + public ushort AmbienceID; + public ushort ZoneMusic; + public ushort IntroSound; + public ushort AreaTableID; + public ushort UWIntroSound; + public ushort UWAmbience; + public sbyte NameSet; // used in adt file + public byte SoundProviderPref; + public byte SoundProviderPrefUnderwater; + public byte Flags; + public uint I; + public uint UWZoneMusic; + } + + public sealed class WorldMapAreaRecord + { + public string AreaName; + public float LocLeft; + public float LocRight; + public float LocTop; + public float LocBottom; + public ushort MapID; + public ushort AreaID; + public short DisplayMapID; + public short DefaultDungeonFloor; + public ushort ParentWorldMapID; + public ushort Flags; + public byte LevelRangeMin; + public byte LevelRangeMax; + public byte BountySetID; + public byte BountyBoardLocation; + public uint Id; + public uint PlayerConditionID; + } + + public sealed class WorldMapOverlayRecord + { + public uint Id; + public string TextureName; + public ushort TextureWidth; + public ushort TextureHeight; + public uint MapAreaID; // idx in WorldMapArea.dbc + public uint[] AreaID = new uint[SharedConst.MaxWorldMapOverlayArea]; + public int OffsetX; + public int OffsetY; + public int HitRectTop; + public int HitRectLeft; + public int HitRectBottom; + public int HitRectRight; + public uint PlayerConditionID; + public uint Flags; + } + + public sealed class WorldMapTransformsRecord + { + public uint Id; + public Vector3 RegionMin; + public Vector3 RegionMax; + public Vector2 RegionOffset; + public float RegionScale; + public ushort MapID; + public ushort AreaID; + public ushort NewMapID; + public ushort NewDungeonMapID; + public ushort NewAreaID; + public byte Flags; + } + + public sealed class WorldSafeLocsRecord + { + public uint Id; + public Vector3 Loc; + public float Facing; + public LocalizedString AreaName; + public ushort MapID; + } +} diff --git a/Game/DungeonFinding/LFGGroupData.cs b/Game/DungeonFinding/LFGGroupData.cs new file mode 100644 index 000000000..92453d751 --- /dev/null +++ b/Game/DungeonFinding/LFGGroupData.cs @@ -0,0 +1,151 @@ +/* + * Copyright (C) 2012-2017 CypherCore + * + * 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, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +using Framework.Constants; +using Game.Entities; +using System.Collections.Generic; + +namespace Game.DungeonFinding +{ + public class LFGGroupData + { + public LFGGroupData() + { + m_State = LfgState.None; + m_OldState = LfgState.None; + m_KicksLeft = SharedConst.LFGMaxKicks; + } + + public bool IsLfgGroup() + { + return m_OldState != LfgState.None; + } + + public void SetState(LfgState state) + { + switch (state) + { + case LfgState.None: + m_Dungeon = 0; + m_KicksLeft = SharedConst.LFGMaxKicks; + m_OldState = state; + break; + case LfgState.FinishedDungeon: + case LfgState.Dungeon: + m_OldState = state; + break; + } + m_State = state; + } + + public void RestoreState() + { + m_State = m_OldState; + } + + public void AddPlayer(ObjectGuid guid) + { + m_Players.Add(guid); + } + + public byte RemovePlayer(ObjectGuid guid) + { + m_Players.Remove(guid); + return (byte)m_Players.Count; + } + + public void RemoveAllPlayers() + { + m_Players.Clear(); + } + + public void SetLeader(ObjectGuid guid) + { + m_Leader = guid; + } + + public void SetDungeon(uint dungeon) + { + m_Dungeon = dungeon; + } + + public void DecreaseKicksLeft() + { + if (m_KicksLeft != 0) + --m_KicksLeft; + } + + public LfgState GetState() + { + return m_State; + } + + public LfgState GetOldState() + { + return m_OldState; + } + + public List GetPlayers() + { + return m_Players; + } + + public byte GetPlayerCount() + { + return (byte)m_Players.Count; + } + + public ObjectGuid GetLeader() + { + return m_Leader; + } + + public uint GetDungeon(bool asId = true) + { + if (asId) + return (m_Dungeon & 0x00FFFFFF); + else + return m_Dungeon; + } + + public byte GetKicksLeft() + { + return m_KicksLeft; + } + + public void SetVoteKick(bool active) + { + m_VoteKickActive = active; + } + + public bool IsVoteKickActive() + { + return m_VoteKickActive; + } + + // General + LfgState m_State; + LfgState m_OldState; + ObjectGuid m_Leader; + List m_Players = new List(); + // Dungeon + uint m_Dungeon; + // Vote Kick + byte m_KicksLeft; + bool m_VoteKickActive; + } +} diff --git a/Game/DungeonFinding/LFGManager.cs b/Game/DungeonFinding/LFGManager.cs new file mode 100644 index 000000000..86fae2d4f --- /dev/null +++ b/Game/DungeonFinding/LFGManager.cs @@ -0,0 +1,2192 @@ +/* + * Copyright (C) 2012-2017 CypherCore + * + * 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, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +using Framework.Configuration; +using Framework.Constants; +using Framework.Database; +using Game.DataStorage; +using Game.Entities; +using Game.Groups; +using Game.Maps; +using System; +using System.Collections.Generic; +using System.Diagnostics.Contracts; +using System.Linq; +using System.Text; + +namespace Game.DungeonFinding +{ + public class LFGManager : Singleton + { + LFGManager() + { + m_lfgProposalId = 1; + m_options = (LfgOptions)ConfigMgr.GetDefaultValue("DungeonFinder.OptionsMask", 1); + + new LFGPlayerScript(); + new LFGGroupScript(); + } + + public string ConcatenateDungeons(List dungeons) + { + StringBuilder dungeonstr = new StringBuilder(); + if (!dungeons.Empty()) + { + foreach (var id in dungeons) + { + if (dungeonstr.Capacity != 0) + dungeonstr.AppendFormat(", {0}", id); + else + dungeonstr.AppendFormat("{0}", id); + } + } + return dungeonstr.ToString(); + } + + public void _LoadFromDB(SQLFields field, ObjectGuid guid) + { + if (field == null) + return; + + if (!guid.IsParty()) + return; + + SetLeader(guid, ObjectGuid.Create(HighGuid.Player, field.Read(0))); + + uint dungeon = field.Read(18); + LfgState state = (LfgState)field.Read(19); + + if (dungeon == 0 || state == 0) + return; + + SetDungeon(guid, dungeon); + + switch (state) + { + case LfgState.Dungeon: + case LfgState.FinishedDungeon: + SetState(guid, state); + break; + default: + break; + } + } + + void _SaveToDB(ObjectGuid guid, uint db_guid) + { + if (!guid.IsParty()) + return; + + SQLTransaction trans = new SQLTransaction(); + + PreparedStatement stmt = DB.Characters.GetPreparedStatement(CharStatements.DEL_LFG_DATA); + stmt.AddValue(0, db_guid); + trans.Append(stmt); + + stmt = DB.Characters.GetPreparedStatement(CharStatements.INS_LFG_DATA); + stmt.AddValue(0, db_guid); + stmt.AddValue(1, GetDungeon(guid)); + stmt.AddValue(2, GetState(guid)); + trans.Append(stmt); + + DB.Characters.CommitTransaction(trans); + } + + public void LoadRewards() + { + uint oldMSTime = Time.GetMSTime(); + + RewardMapStore.Clear(); + + // ORDER BY is very important for GetRandomDungeonReward! + SQLResult result = DB.World.Query("SELECT dungeonId, maxLevel, firstQuestId, otherQuestId FROM lfg_dungeon_rewards ORDER BY dungeonId, maxLevel ASC"); + + if (result.IsEmpty()) + { + Log.outError(LogFilter.ServerLoading, "Loaded 0 lfg dungeon rewards. DB table `lfg_dungeon_rewards` is empty!"); + return; + } + + uint count = 0; + + do + { + uint dungeonId = result.Read(0); + uint maxLevel = result.Read(1); + uint firstQuestId = result.Read(2); + uint otherQuestId = result.Read(3); + + if (GetLFGDungeonEntry(dungeonId) == 0) + { + Log.outError(LogFilter.Sql, "Dungeon {0} specified in table `lfg_dungeon_rewards` does not exist!", dungeonId); + continue; + } + + if (maxLevel == 0 || maxLevel > WorldConfig.GetIntValue(WorldCfg.MaxPlayerLevel)) + { + Log.outError(LogFilter.Sql, "Level {0} specified for dungeon {1} in table `lfg_dungeon_rewards` can never be reached!", maxLevel, dungeonId); + maxLevel = WorldConfig.GetUIntValue(WorldCfg.MaxPlayerLevel); + } + + if (firstQuestId == 0 || Global.ObjectMgr.GetQuestTemplate(firstQuestId) == null) + { + Log.outError(LogFilter.Sql, "First quest {0} specified for dungeon {1} in table `lfg_dungeon_rewards` does not exist!", firstQuestId, dungeonId); + continue; + } + + if (otherQuestId != 0 && Global.ObjectMgr.GetQuestTemplate(otherQuestId) == null) + { + Log.outError(LogFilter.Sql, "Other quest {0} specified for dungeon {1} in table `lfg_dungeon_rewards` does not exist!", otherQuestId, dungeonId); + otherQuestId = 0; + } + + RewardMapStore.Add(dungeonId, new LfgReward(maxLevel, firstQuestId, otherQuestId)); + ++count; + } + while (result.NextRow()); + + Log.outInfo(LogFilter.ServerLoading, "Loaded {0} lfg dungeon rewards in {1} ms", count, Time.GetMSTimeDiffToNow(oldMSTime)); + } + + LFGDungeonData GetLFGDungeon(uint id) + { + return LfgDungeonStore.LookupByKey(id); + } + + public void LoadLFGDungeons(bool reload = false) + { + uint oldMSTime = Time.GetMSTime(); + + LfgDungeonStore.Clear(); + + // Initialize Dungeon map with data from dbcs + foreach (var dungeon in CliDB.LfgDungeonsStorage.Values) + { + switch (dungeon.Type) + { + case LfgType.Dungeon: + case LfgType.Raid: + case LfgType.RandomDungeon: + case LfgType.Zone: + LfgDungeonStore[dungeon.Id] = new LFGDungeonData(dungeon); + break; + } + } + + // Fill teleport locations from DB + SQLResult result = DB.World.Query("SELECT dungeonId, position_x, position_y, position_z, orientation, requiredItemLevel FROM lfg_dungeon_template"); + if (result.IsEmpty()) + { + Log.outError(LogFilter.ServerLoading, "Loaded 0 lfg dungeon templates. DB table `lfg_dungeon_template` is empty!"); + return; + } + + uint count = 0; + + do + { + uint dungeonId = result.Read(0); + if (!LfgDungeonStore.ContainsKey(dungeonId)) + { + Log.outError(LogFilter.Sql, "table `lfg_entrances` contains coordinates for wrong dungeon {0}", dungeonId); + continue; + } + + var data = LfgDungeonStore[dungeonId]; + data.x = result.Read(1); + data.y = result.Read(2); + data.z = result.Read(3); + data.o = result.Read(4); + data.requiredItemLevel = result.Read(5); + + ++count; + } + while (result.NextRow()); + + Log.outInfo(LogFilter.ServerLoading, "Loaded {0} lfg dungeon templates in {1} ms", count, Time.GetMSTimeDiffToNow(oldMSTime)); + + // Fill all other teleport coords from areatriggers + foreach (var pair in LfgDungeonStore) + { + LFGDungeonData dungeon = pair.Value; + + // No teleport coords in database, load from areatriggers + if (dungeon.type != LfgType.RandomDungeon && dungeon.x == 0.0f && dungeon.y == 0.0f && dungeon.z == 0.0f) + { + AreaTriggerStruct at = Global.ObjectMgr.GetMapEntranceTrigger(dungeon.map); + if (at == null) + { + Log.outError(LogFilter.Lfg, "LoadLFGDungeons: Failed to load dungeon {0} (Id: {1}), cant find areatrigger for map {2}", dungeon.name, dungeon.id, dungeon.map); + continue; + } + + dungeon.map = at.target_mapId; + dungeon.x = at.target_X; + dungeon.y = at.target_Y; + dungeon.z = at.target_Z; + dungeon.o = at.target_Orientation; + } + + if (dungeon.type != LfgType.RandomDungeon) + CachedDungeonMapStore.Add((byte)dungeon.group, dungeon.id); + CachedDungeonMapStore.Add(0, dungeon.id); + } + + if (reload) + CachedDungeonMapStore.Clear(); + } + + public void Update(uint diff) + { + if (!isOptionEnabled(LfgOptions.EnableDungeonFinder | LfgOptions.EnableRaidBrowser)) + return; + + long currTime = Time.UnixTime; + + // Remove obsolete role checks + foreach (var pairCheck in RoleChecksStore) + { + LfgRoleCheck roleCheck = pairCheck.Value; + if (currTime < roleCheck.cancelTime) + continue; + roleCheck.state = LfgRoleCheckState.MissingRole; + + foreach (var pairRole in roleCheck.roles) + { + ObjectGuid guid = pairRole.Key; + RestoreState(guid, "Remove Obsolete RoleCheck"); + SendLfgRoleCheckUpdate(guid, roleCheck); + if (guid == roleCheck.leader) + SendLfgJoinResult(guid, new LfgJoinResultData(LfgJoinResult.RoleCheckFailed, LfgRoleCheckState.MissingRole)); + } + + RestoreState(pairCheck.Key, "Remove Obsolete RoleCheck"); + RoleChecksStore.Remove(pairCheck.Key); + } + + // Remove obsolete proposals + foreach (var removePair in ProposalsStore.ToList()) + { + if (removePair.Value.cancelTime < currTime) + RemoveProposal(removePair, LfgUpdateType.ProposalFailed); + } + + // Remove obsolete kicks + foreach (var itBoot in BootsStore) + { + LfgPlayerBoot boot = itBoot.Value; + if (boot.cancelTime < currTime) + { + boot.inProgress = false; + foreach (var itVotes in boot.votes) + { + ObjectGuid pguid = itVotes.Key; + if (pguid != boot.victim) + SendLfgBootProposalUpdate(pguid, boot); + } + SetVoteKick(itBoot.Key, false); + BootsStore.Remove(itBoot.Key); + } + } + + uint lastProposalId = m_lfgProposalId; + // Check if a proposal can be formed with the new groups being added + foreach (var it in QueuesStore) + { + byte newProposals = it.Value.FindGroups(); + if (newProposals != 0) + Log.outDebug(LogFilter.Lfg, "Update: Found {0} new groups in queue {1}", newProposals, it.Key); + } + + if (lastProposalId != m_lfgProposalId) + { + // FIXME lastProposalId ? lastProposalId +1 ? + foreach (var itProposal in ProposalsStore.SkipWhile(p => p.Key == m_lfgProposalId)) + { + uint proposalId = itProposal.Key; + LfgProposal proposal = ProposalsStore[proposalId]; + + ObjectGuid guid = ObjectGuid.Empty; + foreach (var itPlayers in proposal.players) + { + guid = itPlayers.Key; + SetState(guid, LfgState.Proposal); + ObjectGuid gguid = GetGroup(guid); + if (!gguid.IsEmpty()) + { + SetState(gguid, LfgState.Proposal); + SendLfgUpdateStatus(guid, new LfgUpdateData(LfgUpdateType.ProposalBegin, GetSelectedDungeons(guid), GetComment(guid)), true); + } + else + SendLfgUpdateStatus(guid, new LfgUpdateData(LfgUpdateType.ProposalBegin, GetSelectedDungeons(guid), GetComment(guid)), false); + SendLfgUpdateProposal(guid, proposal); + } + + if (proposal.state == LfgProposalState.Success) + UpdateProposal(proposalId, guid, true); + } + } + + // Update all players status queue info + if (m_QueueTimer > SharedConst.LFGQueueUpdateInterval) + { + m_QueueTimer = 0; + foreach (var it in QueuesStore) + it.Value.UpdateQueueTimers(it.Key, currTime); + } + else + m_QueueTimer += diff; + } + + public void JoinLfg(Player player, LfgRoles roles, List dungeons, string comment) + { + if (!player || player.GetSession() == null || dungeons.Empty()) + return; + + Group grp = player.GetGroup(); + ObjectGuid guid = player.GetGUID(); + ObjectGuid gguid = grp ? grp.GetGUID() : guid; + LfgJoinResultData joinData = new LfgJoinResultData(); + List players = new List(); + uint rDungeonId = 0; + bool isContinue = grp && grp.isLFGGroup() && GetState(gguid) != LfgState.FinishedDungeon; + + // Do not allow to change dungeon in the middle of a current dungeon + if (isContinue) + { + dungeons.Clear(); + dungeons.Add(GetDungeon(gguid)); + } + + // Already in queue? + LfgState state = GetState(gguid); + if (state == LfgState.Queued) + { + LFGQueue queue = GetQueue(gguid); + queue.RemoveFromQueue(gguid); + } + + // Check player or group member restrictions + if (!player.GetSession().HasPermission(RBACPermissions.JoinDungeonFinder)) + joinData.result = LfgJoinResult.NotMeetReqs; + else if (player.InBattleground() || player.InArena() || player.InBattlegroundQueue()) + joinData.result = LfgJoinResult.UsingBgSystem; + else if (player.HasAura(SharedConst.LFGSpellDungeonDeserter)) + joinData.result = LfgJoinResult.Deserter; + else if (player.HasAura(SharedConst.LFGSpellDungeonCooldown)) + joinData.result = LfgJoinResult.RandomCooldown; + else if (dungeons.Empty()) + joinData.result = LfgJoinResult.NotMeetReqs; + else if (player.HasAura(9454)) // check Freeze debuff + joinData.result = LfgJoinResult.NotMeetReqs; + else if (grp) + { + if (grp.GetMembersCount() > MapConst.MaxGroupSize) + joinData.result = LfgJoinResult.TooMuchMembers; + else + { + byte memberCount = 0; + for (GroupReference refe = grp.GetFirstMember(); refe != null && joinData.result == LfgJoinResult.Ok; refe = refe.next()) + { + Player plrg = refe.GetSource(); + if (plrg) + { + if (!plrg.GetSession().HasPermission(RBACPermissions.JoinDungeonFinder)) + joinData.result = LfgJoinResult.InternalError; + if (plrg.HasAura(SharedConst.LFGSpellDungeonDeserter)) + joinData.result = LfgJoinResult.PartyDeserter; + else if (plrg.HasAura(SharedConst.LFGSpellDungeonCooldown)) + joinData.result = LfgJoinResult.PartyRandomCooldown; + else if (plrg.InBattleground() || plrg.InArena() || plrg.InBattlegroundQueue()) + joinData.result = LfgJoinResult.UsingBgSystem; + else if (plrg.HasAura(9454)) // check Freeze debuff + joinData.result = LfgJoinResult.PartyNotMeetReqs; + ++memberCount; + players.Add(plrg.GetGUID()); + } + } + + if (joinData.result == LfgJoinResult.Ok && memberCount != grp.GetMembersCount()) + joinData.result = LfgJoinResult.Disconnected; + } + } + else + players.Add(player.GetGUID()); + + // Check if all dungeons are valid + bool isRaid = false; + if (joinData.result == LfgJoinResult.Ok) + { + bool isDungeon = false; + foreach (var it in dungeons) + { + if (joinData.result != LfgJoinResult.Ok) + break; + + LfgType type = (LfgType)(it >> 24); + switch (type) + { + case LfgType.RandomDungeon: + if (dungeons.Count > 1) // Only allow 1 random dungeon + joinData.result = LfgJoinResult.DungeonInvalid; + else + rDungeonId = dungeons.First(); + goto case LfgType.Dungeon; + case LfgType.Dungeon: + if (isRaid) + joinData.result = LfgJoinResult.MixedRaidDungeon; + isDungeon = true; + break; + case LfgType.Raid: + if (isDungeon) + joinData.result = LfgJoinResult.MixedRaidDungeon; + isRaid = true; + break; + default: + Log.outError(LogFilter.Lfg, "Wrong dungeon type {0} for dungeon {1}", type, it); + joinData.result = LfgJoinResult.DungeonInvalid; + break; + } + } + + // it could be changed + if (joinData.result == LfgJoinResult.Ok) + { + // Expand random dungeons and check restrictions + if (rDungeonId != 0) + dungeons = GetDungeonsByRandom(rDungeonId); + + // if we have lockmap then there are no compatible dungeons + GetCompatibleDungeons(dungeons, players, joinData.lockmap, isContinue); + if (dungeons.Empty()) + joinData.result = grp ? LfgJoinResult.InternalError : LfgJoinResult.NotMeetReqs; + } + } + + // Can't join. Send result + if (joinData.result != LfgJoinResult.Ok) + { + Log.outDebug(LogFilter.Lfg, "Join: [{0}] joining with {1} members. result: {2}", guid, grp ? grp.GetMembersCount() : 1, joinData.result); + if (!dungeons.Empty()) // Only should show lockmap when have no dungeons available + joinData.lockmap.Clear(); + player.GetSession().SendLfgJoinResult(joinData); + return; + } + + SetComment(guid, comment); + + if (isRaid) + { + Log.outDebug(LogFilter.Lfg, "Join: [{0}] trying to join raid browser and it's disabled.", guid); + return; + } + + string debugNames = ""; + if (grp) // Begin rolecheck + { + // Create new rolecheck + LfgRoleCheck roleCheck = new LfgRoleCheck(); + roleCheck.cancelTime = Time.UnixTime + SharedConst.LFGTimeRolecheck; + roleCheck.state = LfgRoleCheckState.Initialiting; + roleCheck.leader = guid; + roleCheck.dungeons = dungeons; + roleCheck.rDungeonId = rDungeonId; + + RoleChecksStore[gguid] = roleCheck; + + if (rDungeonId != 0) + { + dungeons.Clear(); + dungeons.Add(rDungeonId); + } + + SetState(gguid, LfgState.Rolecheck); + // Send update to player + LfgUpdateData updateData = new LfgUpdateData(LfgUpdateType.JoinQueue, dungeons, comment); + for (GroupReference refe = grp.GetFirstMember(); refe != null; refe = refe.next()) + { + Player plrg = refe.GetSource(); + if (plrg) + { + ObjectGuid pguid = plrg.GetGUID(); + plrg.GetSession().SendLfgUpdateStatus(updateData, true); + SetState(pguid, LfgState.Rolecheck); + if (!isContinue) + SetSelectedDungeons(pguid, dungeons); + roleCheck.roles[pguid] = 0; + if (!string.IsNullOrEmpty(debugNames)) + debugNames += ", "; + debugNames += plrg.GetName(); + } + } + // Update leader role + UpdateRoleCheck(gguid, guid, roles); + } + else // Add player to queue + { + Dictionary rolesMap = new Dictionary(); + rolesMap[guid] = roles; + LFGQueue queue = GetQueue(guid); + queue.AddQueueData(guid, Time.UnixTime, dungeons, rolesMap); + + if (!isContinue) + { + if (rDungeonId != 0) + { + dungeons.Clear(); + dungeons.Add(rDungeonId); + } + SetSelectedDungeons(guid, dungeons); + } + // Send update to player + SetRoles(guid, roles); + player.GetSession().SendLfgUpdateStatus(new LfgUpdateData(LfgUpdateType.JoinQueue, dungeons, comment), false); + player.GetSession().SendLfgJoinResult(joinData); + SetState(gguid, LfgState.Queued); + debugNames += player.GetName(); + } + StringBuilder o = new StringBuilder(); + o.AppendFormat("Join: [{0}] joined ({1}{2}) Members: {3}. Dungeons ({4}): ", guid, (grp ? "group" : "player"), debugNames, dungeons.Count, ConcatenateDungeons(dungeons)); + Log.outDebug(LogFilter.Lfg, o.ToString()); + } + + public void LeaveLfg(ObjectGuid guid, bool disconnected = false) + { + Log.outDebug(LogFilter.Lfg, "LeaveLfg: [{0}]", guid); + + ObjectGuid gguid = guid.IsParty() ? guid : GetGroup(guid); + LfgState state = GetState(guid); + switch (state) + { + case LfgState.Queued: + if (!gguid.IsEmpty()) + { + LFGQueue queue = GetQueue(gguid); + queue.RemoveFromQueue(gguid); + SetState(gguid, LfgState.None); + List players = GetPlayers(gguid); + foreach (var it in players) + { + SetState(it, LfgState.None); + SendLfgUpdateStatus(it, new LfgUpdateData(LfgUpdateType.RemovedFromQueue), true); + } + } + else + { + SendLfgUpdateStatus(guid, new LfgUpdateData(LfgUpdateType.RemovedFromQueue), false); + LFGQueue queue = GetQueue(guid); + queue.RemoveFromQueue(guid); + SetState(guid, LfgState.None); + } + break; + case LfgState.Rolecheck: + if (!gguid.IsEmpty()) + UpdateRoleCheck(gguid); // No player to update role = LFG_ROLECHECK_ABORTED + break; + case LfgState.Proposal: + { + // Remove from Proposals + KeyValuePair it = new KeyValuePair(); + ObjectGuid pguid = gguid == guid ? GetLeader(gguid) : guid; + foreach (var test in ProposalsStore) + { + it = test; + var itPlayer = it.Value.players.LookupByKey(pguid); + if (itPlayer != null) + { + // Mark the player/leader of group who left as didn't accept the proposal + itPlayer.accept = LfgAnswer.Deny; + break; + } + } + + // Remove from queue - if proposal is found, RemoveProposal will call RemoveFromQueue + if (it.Value != null) + RemoveProposal(it, LfgUpdateType.ProposalDeclined); + break; + } + case LfgState.None: + case LfgState.Raidbrowser: + break; + case LfgState.Dungeon: + case LfgState.FinishedDungeon: + if (guid != gguid && !disconnected) // Player + SetState(guid, LfgState.None); + break; + } + } + + public void UpdateRoleCheck(ObjectGuid gguid, ObjectGuid guid = default(ObjectGuid), LfgRoles roles = LfgRoles.None) + { + if (gguid.IsEmpty()) + return; + + Dictionary check_roles; + var roleCheck = RoleChecksStore.LookupByKey(gguid); + if (roleCheck == null) + return; + + bool sendRoleChosen = roleCheck.state != LfgRoleCheckState.Default && !guid.IsEmpty(); + + if (guid.IsEmpty()) + roleCheck.state = LfgRoleCheckState.Aborted; + else if (roles < LfgRoles.Tank) // Player selected no role. + roleCheck.state = LfgRoleCheckState.NoRole; + else + { + roleCheck.roles[guid] = roles; + + // Check if all players have selected a role + bool done = false; + foreach (var rolePair in roleCheck.roles) + { + if (rolePair.Value != LfgRoles.None) + continue; + done = true; + } + + if (done) + { + // use temporal var to check roles, CheckGroupRoles modifies the roles + check_roles = roleCheck.roles; + roleCheck.state = CheckGroupRoles(check_roles) ? LfgRoleCheckState.Finished : LfgRoleCheckState.WrongRoles; + } + } + + List dungeons = new List(); + if (roleCheck.rDungeonId != 0) + dungeons.Add(roleCheck.rDungeonId); + else + dungeons = roleCheck.dungeons; + + LfgJoinResult joinResult = LfgJoinResult.Failed; + if (roleCheck.state == LfgRoleCheckState.MissingRole || roleCheck.state == LfgRoleCheckState.WrongRoles) + joinResult = LfgJoinResult.RoleCheckFailed; + + LfgJoinResultData joinData = new LfgJoinResultData(joinResult, roleCheck.state); + foreach (var it in roleCheck.roles) + { + ObjectGuid pguid = it.Key; + + if (sendRoleChosen) + SendLfgRoleChosen(pguid, guid, roles); + + SendLfgRoleCheckUpdate(pguid, roleCheck); + switch (roleCheck.state) + { + case LfgRoleCheckState.Initialiting: + continue; + case LfgRoleCheckState.Finished: + SetState(pguid, LfgState.Queued); + SetRoles(pguid, it.Value); + SendLfgUpdateStatus(pguid, new LfgUpdateData(LfgUpdateType.AddedToQueue, dungeons, GetComment(pguid)), true); + break; + default: + if (roleCheck.leader == pguid) + SendLfgJoinResult(pguid, joinData); + SendLfgUpdateStatus(pguid, new LfgUpdateData(LfgUpdateType.RolecheckFailed), true); + RestoreState(pguid, "Rolecheck Failed"); + break; + } + } + + if (roleCheck.state == LfgRoleCheckState.Finished) + { + SetState(gguid, LfgState.Queued); + LFGQueue queue = GetQueue(gguid); + queue.AddQueueData(gguid, Time.UnixTime, roleCheck.dungeons, roleCheck.roles); + RoleChecksStore.Remove(gguid); + } + else if (roleCheck.state != LfgRoleCheckState.Initialiting) + { + RestoreState(gguid, "Rolecheck Failed"); + RoleChecksStore.Remove(gguid); + } + } + + void GetCompatibleDungeons(List dungeons, List players, Dictionary> lockMap, bool isContinue) + { + lockMap.Clear(); + Dictionary lockedDungeons = new Dictionary(); + + foreach (var guid in players) + { + if (dungeons.Empty()) + break; + + var cachedLockMap = GetLockedDungeons(guid); + Player player = Global.ObjAccessor.FindConnectedPlayer(guid); + foreach (var it2 in cachedLockMap) + { + if (dungeons.Empty()) + break; + + uint dungeonId = (it2.Key & 0x00FFFFFF); // Compare dungeon ids + if (dungeons.Contains(dungeonId)) + { + bool eraseDungeon = true; + + // Don't remove the dungeon if team members are trying to continue a locked instance + if (it2.Value.lockStatus == LfgLockStatusType.RaidLocked && isContinue) + { + LFGDungeonData dungeon = GetLFGDungeon(dungeonId); + Contract.Assert(dungeon != null); + Contract.Assert(player); + InstanceBind playerBind = player.GetBoundInstance(dungeon.map, dungeon.difficulty); + if (playerBind != null) + { + InstanceSave playerSave = playerBind.save; + if (playerSave != null) + { + uint dungeonInstanceId = playerSave.GetInstanceId(); + var itLockedDungeon = lockedDungeons.LookupByKey(dungeonId); + if (itLockedDungeon == 0 || itLockedDungeon == dungeonInstanceId) + eraseDungeon = false; + + lockedDungeons[dungeonId] = dungeonInstanceId; + } + } + } + + if (eraseDungeon) + dungeons.Remove(dungeonId); + + if (!lockMap.ContainsKey(guid)) + lockMap[guid] = new Dictionary(); + + lockMap[guid][it2.Key] = it2.Value; + } + } + } + if (!dungeons.Empty()) + lockMap.Clear(); + } + + public bool CheckGroupRoles(Dictionary groles) + { + if (groles.Empty()) + return false; + + byte damage = 0; + byte tank = 0; + byte healer = 0; + + List keys = new List(groles.Keys); + for (int i = 0; i < keys.Count; i++) + { + var it = groles[keys[i]]; + LfgRoles role = it & ~LfgRoles.Leader; + if (role == LfgRoles.None) + return false; + + if (role.HasAnyFlag(LfgRoles.Damage)) + { + if (role != LfgRoles.Damage) + { + it -= (byte)LfgRoles.Damage; + if (CheckGroupRoles(groles)) + return true; + it += (byte)LfgRoles.Damage; + } + else if (damage == SharedConst.LFGDPSNeeded) + return false; + else + damage++; + } + + if (role.HasAnyFlag(LfgRoles.Healer)) + { + if (role != LfgRoles.Healer) + { + it -= (byte)LfgRoles.Healer; + if (CheckGroupRoles(groles)) + return true; + it += (byte)LfgRoles.Healer; + } + else if (healer == SharedConst.LFGHealersNeeded) + return false; + else + healer++; + } + + if (role.HasAnyFlag(LfgRoles.Tank)) + { + if (role != LfgRoles.Tank) + { + it -= (byte)LfgRoles.Tank; + if (CheckGroupRoles(groles)) + return true; + it += (byte)LfgRoles.Tank; + } + else if (tank == SharedConst.LFGTanksNeeded) + return false; + else + tank++; + } + } + return (tank + healer + damage) == (byte)groles.Count; + } + + void MakeNewGroup(LfgProposal proposal) + { + List players = new List(); + List playersToTeleport = new List(); + + foreach (var it in proposal.players) + { + ObjectGuid guid = it.Key; + if (guid == proposal.leader) + players.Insert(0, guid); + else + players.Add(guid); + + if (proposal.isNew || GetGroup(guid) != proposal.group) + playersToTeleport.Add(guid); + } + + // Set the dungeon difficulty + LFGDungeonData dungeon = GetLFGDungeon(proposal.dungeonId); + Contract.Assert(dungeon != null); + + Group grp = !proposal.group.IsEmpty() ? Global.GroupMgr.GetGroupByGUID(proposal.group) : null; + foreach (var pguid in players) + { + Player player = Global.ObjAccessor.FindConnectedPlayer(pguid); + if (!player) + continue; + + Group group = player.GetGroup(); + if (group && group != grp) + group.RemoveMember(player.GetGUID()); + + if (!grp) + { + grp = new Group(); + grp.ConvertToLFG(); + grp.Create(player); + ObjectGuid gguid = grp.GetGUID(); + SetState(gguid, LfgState.Proposal); + Global.GroupMgr.AddGroup(grp); + } + else if (group != grp) + grp.AddMember(player); + + grp.SetLfgRoles(pguid, proposal.players.LookupByKey(pguid).role); + + // Add the cooldown spell if queued for a random dungeon + if (dungeon.type == LfgType.RandomDungeon) + player.CastSpell(player, SharedConst.LFGSpellDungeonCooldown, false); + } + + grp.SetDungeonDifficultyID(dungeon.difficulty); + ObjectGuid _guid = grp.GetGUID(); + SetDungeon(_guid, dungeon.Entry()); + SetState(_guid, LfgState.Dungeon); + + _SaveToDB(_guid, grp.GetDbStoreId()); + + // Teleport Player + foreach (var it in playersToTeleport) + { + Player player = Global.ObjAccessor.FindPlayer(it); + if (player) + TeleportPlayer(player, false); + } + + // Update group info + grp.SendUpdate(); + } + + public uint AddProposal(LfgProposal proposal) + { + proposal.id = ++m_lfgProposalId; + ProposalsStore[m_lfgProposalId] = proposal; + return m_lfgProposalId; + } + + public void UpdateProposal(uint proposalId, ObjectGuid guid, bool accept) + { + // Check if the proposal exists + var proposal = ProposalsStore.LookupByKey(proposalId); + if (proposal == null) + return; + + // Check if proposal have the current player + var player = proposal.players.LookupByKey(guid); + if (player == null) + return; + + player.accept = (LfgAnswer)Convert.ToInt32(accept); + + Log.outDebug(LogFilter.Lfg, "UpdateProposal: Player [{0}] of proposal {1} selected: {2}", guid, proposalId, accept); + if (!accept) + { + RemoveProposal(new KeyValuePair(proposalId, proposal), LfgUpdateType.ProposalDeclined); + return; + } + + // check if all have answered and reorder players (leader first) + bool allAnswered = true; + foreach (var itPlayers in proposal.players) + if (itPlayers.Value.accept != LfgAnswer.Agree) // No answer (-1) or not accepted (0) + allAnswered = false; + + if (!allAnswered) + { + foreach (var it in proposal.players) + SendLfgUpdateProposal(it.Key, proposal); + + return; + } + + bool sendUpdate = proposal.state != LfgProposalState.Success; + proposal.state = LfgProposalState.Success; + long joinTime = Time.UnixTime; + + LFGQueue queue = GetQueue(guid); + LfgUpdateData updateData = new LfgUpdateData(LfgUpdateType.GroupFound); + foreach (var it in proposal.players) + { + ObjectGuid pguid = it.Key; + ObjectGuid gguid = it.Value.group; + uint dungeonId = GetSelectedDungeons(pguid).First(); + int waitTime = -1; + if (sendUpdate) + SendLfgUpdateProposal(pguid, proposal); + + if (!gguid.IsEmpty()) + { + waitTime = (int)((joinTime - queue.GetJoinTime(gguid)) / Time.InMilliseconds); + SendLfgUpdateStatus(pguid, updateData, false); + } + else + { + waitTime = (int)((joinTime - queue.GetJoinTime(pguid)) / Time.InMilliseconds); + SendLfgUpdateStatus(pguid, updateData, false); + } + updateData.updateType = LfgUpdateType.RemovedFromQueue; + SendLfgUpdateStatus(pguid, updateData, true); + SendLfgUpdateStatus(pguid, updateData, false); + + // Update timers + LfgRoles role = GetRoles(pguid); + role &= ~LfgRoles.Leader; + switch (role) + { + case LfgRoles.Damage: + queue.UpdateWaitTimeDps(waitTime, dungeonId); + break; + case LfgRoles.Healer: + queue.UpdateWaitTimeHealer(waitTime, dungeonId); + break; + case LfgRoles.Tank: + queue.UpdateWaitTimeTank(waitTime, dungeonId); + break; + default: + queue.UpdateWaitTimeAvg(waitTime, dungeonId); + break; + } + + SetState(pguid, LfgState.Dungeon); + } + + // Remove players/groups from Queue + foreach (var it in proposal.queues) + queue.RemoveFromQueue(it); + + MakeNewGroup(proposal); + ProposalsStore.Remove(proposalId); + } + + void RemoveProposal(KeyValuePair itProposal, LfgUpdateType type) + { + LfgProposal proposal = itProposal.Value; + proposal.state = LfgProposalState.Failed; + + Log.outDebug(LogFilter.Lfg, "RemoveProposal: Proposal {0}, state FAILED, UpdateType {1}", itProposal.Key, type); + // Mark all people that didn't answered as no accept + if (type == LfgUpdateType.ProposalFailed) + foreach (var it in proposal.players) + if (it.Value.accept == LfgAnswer.Pending) + it.Value.accept = LfgAnswer.Deny; + + // Mark players/groups to be removed + List toRemove = new List(); + foreach (var it in proposal.players) + { + if (it.Value.accept == LfgAnswer.Agree) + continue; + + ObjectGuid guid = !it.Value.group.IsEmpty() ? it.Value.group : it.Key; + // Player didn't accept or still pending when no secs left + if (it.Value.accept == LfgAnswer.Deny || type == LfgUpdateType.ProposalFailed) + { + it.Value.accept = LfgAnswer.Deny; + toRemove.Add(guid); + } + } + + // Notify players + foreach (var it in proposal.players) + { + ObjectGuid guid = it.Key; + ObjectGuid gguid = !it.Value.group.IsEmpty() ? it.Value.group : guid; + + SendLfgUpdateProposal(guid, proposal); + + if (toRemove.Contains(gguid)) // Didn't accept or in same group that someone that didn't accept + { + LfgUpdateData updateData = new LfgUpdateData(); + if (it.Value.accept == LfgAnswer.Deny) + { + updateData.updateType = type; + Log.outDebug(LogFilter.Lfg, "RemoveProposal: [{0}] didn't accept. Removing from queue and compatible cache", guid); + } + else + { + updateData.updateType = LfgUpdateType.RemovedFromQueue; + Log.outDebug(LogFilter.Lfg, "RemoveProposal: [{0}] in same group that someone that didn't accept. Removing from queue and compatible cache", guid); + } + + RestoreState(guid, "Proposal Fail (didn't accepted or in group with someone that didn't accept"); + if (gguid != guid) + { + RestoreState(it.Value.group, "Proposal Fail (someone in group didn't accepted)"); + SendLfgUpdateStatus(guid, updateData, true); + } + else + SendLfgUpdateStatus(guid, updateData, false); + } + else + { + Log.outDebug(LogFilter.Lfg, "RemoveProposal: Readding [{0}] to queue.", guid); + SetState(guid, LfgState.Queued); + if (gguid != guid) + { + SetState(gguid, LfgState.Queued); + SendLfgUpdateStatus(guid, new LfgUpdateData(LfgUpdateType.AddedToQueue, GetSelectedDungeons(guid), GetComment(guid)), true); + } + else + SendLfgUpdateStatus(guid, new LfgUpdateData(LfgUpdateType.AddedToQueue, GetSelectedDungeons(guid), GetComment(guid)), false); + } + } + + LFGQueue queue = GetQueue(proposal.players.First().Key); + // Remove players/groups from queue + foreach (var guid in toRemove) + { + queue.RemoveFromQueue(guid); + proposal.queues.Remove(guid); + } + + // Readd to queue + foreach (var guid in proposal.queues) + queue.AddToQueue(guid, true); + + ProposalsStore.Remove(itProposal.Key); + } + + public void InitBoot(ObjectGuid gguid, ObjectGuid kicker, ObjectGuid victim, string reason) + { + SetVoteKick(gguid, true); + + LfgPlayerBoot boot = BootsStore[gguid]; + boot.inProgress = true; + boot.cancelTime = Time.UnixTime + SharedConst.LFGTimeBoot; + boot.reason = reason; + boot.victim = victim; + + List players = GetPlayers(gguid); + + // Set votes + foreach (var guid in players) + boot.votes[guid] = LfgAnswer.Pending; + + boot.votes[victim] = LfgAnswer.Deny; // Victim auto vote NO + boot.votes[kicker] = LfgAnswer.Agree; // Kicker auto vote YES + + // Notify players + foreach (var it in players) + SendLfgBootProposalUpdate(it, boot); + } + + public void UpdateBoot(ObjectGuid guid, bool accept) + { + ObjectGuid gguid = GetGroup(guid); + if (gguid.IsEmpty()) + return; + + var boot = BootsStore.LookupByKey(gguid); + if (boot == null) + return; + + if (boot.votes[guid] != LfgAnswer.Pending) // Cheat check: Player can't vote twice + return; + + boot.votes[guid] = (LfgAnswer)Convert.ToInt32(accept); + + byte votesNum = 0; + byte agreeNum = 0; + foreach (var itVotes in boot.votes) + { + if (itVotes.Value != LfgAnswer.Pending) + { + ++votesNum; + if (itVotes.Value == LfgAnswer.Agree) + ++agreeNum; + } + } + + // if we don't have enough votes (agree or deny) do nothing + if (agreeNum < SharedConst.LFGKickVotesNeeded && (votesNum - agreeNum) < SharedConst.LFGKickVotesNeeded) + return; + + // Send update info to all players + boot.inProgress = false; + foreach (var itVotes in boot.votes) + { + ObjectGuid pguid = itVotes.Key; + if (pguid != boot.victim) + SendLfgBootProposalUpdate(pguid, boot); + } + + SetVoteKick(gguid, false); + if (agreeNum == SharedConst.LFGKickVotesNeeded) // Vote passed - Kick player + { + Group group = Global.GroupMgr.GetGroupByGUID(gguid); + if (group) + Player.RemoveFromGroup(group, boot.victim, RemoveMethod.KickLFG); + DecreaseKicksLeft(gguid); + } + BootsStore.Remove(gguid); + } + + public void TeleportPlayer(Player player, bool outt, bool fromOpcode = false) + { + LFGDungeonData dungeon = null; + Group group = player.GetGroup(); + + if (group && group.isLFGGroup()) + dungeon = GetLFGDungeon(GetDungeon(group.GetGUID())); + + if (dungeon == null) + { + Log.outDebug(LogFilter.Lfg, "TeleportPlayer: Player {0} not in group/lfggroup or dungeon not found!", player.GetName()); + player.GetSession().SendLfgTeleportError(LfgTeleportResult.InvalidLocation); + return; + } + + if (outt) + { + Log.outDebug(LogFilter.Lfg, "TeleportPlayer: Player {0} is being teleported out. Current Map {1} - Expected Map {2}", player.GetName(), player.GetMapId(), dungeon.map); + if (player.GetMapId() == dungeon.map) + player.TeleportToBGEntryPoint(); + + return; + } + + LfgTeleportResult error = LfgTeleportResult.Ok; + if (!player.IsAlive()) + error = LfgTeleportResult.PlayerDead; + else if (player.IsFalling() || player.HasUnitState(UnitState.Jumping)) + error = LfgTeleportResult.Falling; + else if (player.IsMirrorTimerActive(MirrorTimerType.Fatigue)) + error = LfgTeleportResult.Fatigue; + else if (player.GetVehicle()) + error = LfgTeleportResult.InVehicle; + else if (!player.GetCharmGUID().IsEmpty()) + error = LfgTeleportResult.Charming; + else if (player.HasAura(9454)) // check Freeze debuff + error = LfgTeleportResult.InvalidLocation; + else if (player.GetMapId() != dungeon.map) // Do not teleport players in dungeon to the entrance + { + uint mapid = dungeon.map; + float x = dungeon.x; + float y = dungeon.y; + float z = dungeon.z; + float orientation = dungeon.o; + + if (!fromOpcode) + { + // Select a player inside to be teleported to + for (GroupReference refe = group.GetFirstMember(); refe != null && mapid == 0; refe = refe.next()) + { + Player plrg = refe.GetSource(); + if (plrg && plrg != player && plrg.GetMapId() == dungeon.map) + { + mapid = plrg.GetMapId(); + x = plrg.GetPositionX(); + y = plrg.GetPositionY(); + z = plrg.GetPositionZ(); + orientation = plrg.GetOrientation(); + break; + } + } + } + + if (!player.GetMap().IsDungeon()) + player.SetBattlegroundEntryPoint(); + + if (player.IsInFlight()) + { + player.GetMotionMaster().MovementExpired(); + player.CleanupAfterTaxiFlight(); + } + + if (!player.TeleportTo(mapid, x, y, z, orientation)) + error = LfgTeleportResult.InvalidLocation; + } + else + error = LfgTeleportResult.InvalidLocation; + + if (error != LfgTeleportResult.Ok) + player.GetSession().SendLfgTeleportError(error); + + Log.outDebug(LogFilter.Lfg, "TeleportPlayer: Player {0} is being teleported in to map {1} (x: {2}, y: {3}, z: {4}) Result: {5}", player.GetName(), dungeon.map, dungeon.x, dungeon.y, dungeon.z, error); + } + + public void FinishDungeon(ObjectGuid gguid, uint dungeonId) + { + uint gDungeonId = GetDungeon(gguid); + if (gDungeonId != dungeonId) + { + Log.outDebug(LogFilter.Lfg, "FinishDungeon: [{0}] Finished dungeon {1} but group queued for {2}. Ignoring", gguid, dungeonId, gDungeonId); + return; + } + + if (GetState(gguid) == LfgState.FinishedDungeon) // Shouldn't happen. Do not reward multiple times + { + Log.outDebug(LogFilter.Lfg, "FinishDungeon: [{0}] Already rewarded group. Ignoring", gguid); + return; + } + + SetState(gguid, LfgState.FinishedDungeon); + + List players = GetPlayers(gguid); + foreach (var guid in players) + { + if (GetState(guid) == LfgState.FinishedDungeon) + { + Log.outDebug(LogFilter.Lfg, "FinishDungeon: [{0}] Already rewarded player. Ignoring", guid); + continue; + } + + uint rDungeonId = 0; + List dungeons = GetSelectedDungeons(guid); + if (!dungeons.Empty()) + rDungeonId = dungeons.First(); + + SetState(guid, LfgState.FinishedDungeon); + + // Give rewards only if its a random dungeon + LFGDungeonData dungeon = GetLFGDungeon(rDungeonId); + + if (dungeon == null || (dungeon.type != LfgType.RandomDungeon && !dungeon.seasonal)) + { + Log.outDebug(LogFilter.Lfg, "FinishDungeon: [{0}] dungeon {1} is not random or seasonal", guid, rDungeonId); + continue; + } + + Player player = Global.ObjAccessor.FindPlayer(guid); + if (!player || !player.IsInWorld) + { + Log.outDebug(LogFilter.Lfg, "FinishDungeon: [{0}] not found in world", guid); + continue; + } + + LFGDungeonData dungeonDone = GetLFGDungeon(dungeonId); + uint mapId = dungeonDone != null ? dungeonDone.map : 0; + + if (player.GetMapId() != mapId) + { + Log.outDebug(LogFilter.Lfg, "FinishDungeon: [{0}] is in map {1} and should be in {2} to get reward", guid, player.GetMapId(), mapId); + continue; + } + + // Update achievements + if (dungeon.difficulty == Difficulty.Heroic) + player.UpdateCriteria(CriteriaTypes.UseLfdToGroupWithPlayers, 1); + + LfgReward reward = GetRandomDungeonReward(rDungeonId, player.getLevel()); + if (reward == null) + continue; + + bool done = false; + Quest quest = Global.ObjectMgr.GetQuestTemplate(reward.firstQuest); + if (quest == null) + continue; + + // if we can take the quest, means that we haven't done this kind of "run", IE: First Heroic Random of Day. + if (player.CanRewardQuest(quest, false)) + player.RewardQuest(quest, 0, null, false); + else + { + done = true; + quest = Global.ObjectMgr.GetQuestTemplate(reward.otherQuest); + if (quest == null) + continue; + // we give reward without informing client (retail does this) + player.RewardQuest(quest, 0, null, false); + } + + // Give rewards + Log.outDebug(LogFilter.Lfg, "FinishDungeon: [{0}] done dungeon {1}, {2} previously done.", player.GetGUID(), GetDungeon(gguid), done ? " " : " not"); + LfgPlayerRewardData data = new LfgPlayerRewardData(dungeon.Entry(), GetDungeon(gguid, false), done, quest); + player.GetSession().SendLfgPlayerReward(data); + } + } + + List GetDungeonsByRandom(uint randomdungeon) + { + LFGDungeonData dungeon = GetLFGDungeon(randomdungeon); + byte group = (byte)(dungeon != null ? dungeon.group : 0); + return CachedDungeonMapStore.LookupByKey(group); + } + + public LfgReward GetRandomDungeonReward(uint dungeon, uint level) + { + LfgReward reward = null; + var bounds = RewardMapStore.LookupByKey(dungeon & 0x00FFFFFF); + foreach (var rew in bounds) + { + reward = rew; + // ordered properly at loading + if (rew.maxLevel >= level) + break; + } + + return reward; + } + + public LfgType GetDungeonType(uint dungeonId) + { + LFGDungeonData dungeon = GetLFGDungeon(dungeonId); + if (dungeon == null) + return LfgType.None; + + return dungeon.type; + } + + public LfgState GetState(ObjectGuid guid) + { + LfgState state; + if (guid.IsParty()) + { + if (!GroupsStore.ContainsKey(guid)) + return LfgState.None; + + state = GroupsStore[guid].GetState(); + } + else + { + AddPlayerData(guid); + state = PlayersStore[guid].GetState(); + } + + Log.outDebug(LogFilter.Lfg, "GetState: [{0}] = {1}", guid, state); + return state; + } + + public LfgState GetOldState(ObjectGuid guid) + { + LfgState state; + if (guid.IsParty()) + state = GroupsStore[guid].GetOldState(); + else + { + AddPlayerData(guid); + state = PlayersStore[guid].GetOldState(); + } + + Log.outDebug(LogFilter.Lfg, "GetOldState: [{0}] = {1}", guid, state); + return state; + } + + public bool IsVoteKickActive(ObjectGuid gguid) + { + Contract.Assert(gguid.IsParty()); + + bool active = GroupsStore[gguid].IsVoteKickActive(); + Log.outInfo(LogFilter.Lfg, "Group: {0}, Active: {1}", gguid.ToString(), active); + + return active; + } + + public uint GetDungeon(ObjectGuid guid, bool asId = true) + { + if (!GroupsStore.ContainsKey(guid)) + return 0; + + uint dungeon = GroupsStore[guid].GetDungeon(asId); + Log.outDebug(LogFilter.Lfg, "GetDungeon: [{0}] asId: {1} = {2}", guid, asId, dungeon); + return dungeon; + } + + public uint GetDungeonMapId(ObjectGuid guid) + { + uint dungeonId = GroupsStore[guid].GetDungeon(true); + uint mapId = 0; + if (dungeonId != 0) + { + LFGDungeonData dungeon = GetLFGDungeon(dungeonId); + if (dungeon != null) + mapId = dungeon.map; + } + + Log.outError(LogFilter.Lfg, "GetDungeonMapId: [{0}] = {1} (DungeonId = {2})", guid, mapId, dungeonId); + return mapId; + } + + public LfgRoles GetRoles(ObjectGuid guid) + { + LfgRoles roles = PlayersStore[guid].GetRoles(); + Log.outDebug(LogFilter.Lfg, "GetRoles: [{0}] = {1}", guid, roles); + return roles; + } + + public string GetComment(ObjectGuid guid) + { + Log.outDebug(LogFilter.Lfg, "GetComment: [{0}] = {1}", guid, PlayersStore[guid].GetComment()); + return PlayersStore[guid].GetComment(); + } + + public List GetSelectedDungeons(ObjectGuid guid) + { + Log.outDebug(LogFilter.Lfg, "GetSelectedDungeons: [{0}]", guid); + return PlayersStore[guid].GetSelectedDungeons(); + } + + public Dictionary GetLockedDungeons(ObjectGuid guid) + { + Dictionary lockDic = new Dictionary(); + Player player = Global.ObjAccessor.FindConnectedPlayer(guid); + if (!player) + { + Log.outWarn(LogFilter.Lfg, "{0} not ingame while retrieving his LockedDungeons.", guid.ToString()); + return lockDic; + } + + uint level = player.getLevel(); + Expansion expansion = player.GetSession().GetExpansion(); + var dungeons = GetDungeonsByRandom(0); + bool denyJoin = !player.GetSession().HasPermission(RBACPermissions.JoinDungeonFinder); + + foreach (var it in dungeons) + { + LFGDungeonData dungeon = GetLFGDungeon(it); + if (dungeon == null) // should never happen - We provide a list from sLFGDungeonStore + continue; + + LfgLockStatusType lockStatus = 0; + AccessRequirement ar; + if (denyJoin) + lockStatus = LfgLockStatusType.RaidLocked; + else if (dungeon.expansion > (uint)expansion) + lockStatus = LfgLockStatusType.InsufficientExpansion; + else if (Global.DisableMgr.IsDisabledFor(DisableType.Map, dungeon.map, player)) + lockStatus = LfgLockStatusType.RaidLocked; + else if (dungeon.difficulty > Difficulty.Normal && player.GetBoundInstance(dungeon.map, dungeon.difficulty) != null) + lockStatus = LfgLockStatusType.RaidLocked; + else if (dungeon.minlevel > level) + lockStatus = LfgLockStatusType.TooLowLevel; + else if (dungeon.maxlevel < level) + lockStatus = LfgLockStatusType.TooHighLevel; + else if (dungeon.seasonal && !IsSeasonActive(dungeon.id)) + lockStatus = LfgLockStatusType.NotInSeason; + else if (dungeon.requiredItemLevel > player.GetAverageItemLevel()) + lockStatus = LfgLockStatusType.TooLowGearScore; + else if ((ar = Global.ObjectMgr.GetAccessRequirement(dungeon.map, dungeon.difficulty)) != null) + { + if (ar.achievement != 0 && !player.HasAchieved(ar.achievement)) + lockStatus = LfgLockStatusType.MissingAchievement; + else if (player.GetTeam() == Team.Alliance && ar.quest_A != 0 && !player.GetQuestRewardStatus(ar.quest_A)) + lockStatus = LfgLockStatusType.QuestNotCompleted; + else if (player.GetTeam() == Team.Horde && ar.quest_H != 0 && !player.GetQuestRewardStatus(ar.quest_H)) + lockStatus = LfgLockStatusType.QuestNotCompleted; + else + if (ar.item != 0) + { + if (!player.HasItemCount(ar.item) && (ar.item2 == 0 || !player.HasItemCount(ar.item2))) + lockStatus = LfgLockStatusType.MissingItem; + } + else if (ar.item2 != 0 && !player.HasItemCount(ar.item2)) + lockStatus = LfgLockStatusType.MissingItem; + } + + /* @todo VoA closed if WG is not under team control (LFG_LOCKSTATUS_RAID_LOCKED) + lockData = LFG_LOCKSTATUS_TOO_HIGH_GEAR_SCORE; + lockData = LFG_LOCKSTATUS_ATTUNEMENT_TOO_LOW_LEVEL; + lockData = LFG_LOCKSTATUS_ATTUNEMENT_TOO_HIGH_LEVEL; + */ + if (lockStatus == 0) + { + + } + if (lockStatus != 0) + lockDic[dungeon.Entry()] = new LfgLockInfoData(lockStatus, dungeon.requiredItemLevel, player.GetAverageItemLevel()); + } + + return lockDic; + } + + public byte GetKicksLeft(ObjectGuid guid) + { + byte kicks = GroupsStore[guid].GetKicksLeft(); + Log.outDebug(LogFilter.Lfg, "GetKicksLeft: [{0}] = {1}", guid, kicks); + return kicks; + } + + void RestoreState(ObjectGuid guid, string debugMsg) + { + if (guid.IsParty()) + { + var data = GroupsStore[guid]; + data.RestoreState(); + } + else + { + var data = PlayersStore[guid]; + data.RestoreState(); + } + } + + public void SetState(ObjectGuid guid, LfgState state) + { + if (guid.IsParty()) + { + if (!GroupsStore.ContainsKey(guid)) + GroupsStore[guid] = new LFGGroupData(); + var data = GroupsStore[guid]; + data.SetState(state); + } + else + { + var data = PlayersStore[guid]; + data.SetState(state); + } + } + + void SetVoteKick(ObjectGuid gguid, bool active) + { + Contract.Assert(gguid.IsParty()); + + var data = GroupsStore[gguid]; + Log.outInfo(LogFilter.Lfg, "Group: {0}, New state: {1}, Previous: {2}", gguid.ToString(), active, data.IsVoteKickActive()); + + data.SetVoteKick(active); + } + + void SetDungeon(ObjectGuid guid, uint dungeon) + { + AddPlayerData(guid); + Log.outDebug(LogFilter.Lfg, "SetDungeon: [{0}] dungeon {1}", guid, dungeon); + GroupsStore[guid].SetDungeon(dungeon); + } + + void SetRoles(ObjectGuid guid, LfgRoles roles) + { + AddPlayerData(guid); + Log.outDebug(LogFilter.Lfg, "SetRoles: [{0}] roles: {1}", guid, roles); + PlayersStore[guid].SetRoles(roles); + } + + public void SetComment(ObjectGuid guid, string comment) + { + AddPlayerData(guid); + Log.outDebug(LogFilter.Lfg, "SetComment: [{0}] comment: {1}", guid, comment); + PlayersStore[guid].SetComment(comment); + } + + public void SetSelectedDungeons(ObjectGuid guid, List dungeons) + { + AddPlayerData(guid); + Log.outDebug(LogFilter.Lfg, "SetSelectedDungeons: [{0}] Dungeons: {1}", guid, ConcatenateDungeons(dungeons)); + PlayersStore[guid].SetSelectedDungeons(dungeons); + } + + void DecreaseKicksLeft(ObjectGuid guid) + { + Log.outDebug(LogFilter.Lfg, "DecreaseKicksLeft: [{0}]", guid); + GroupsStore[guid].DecreaseKicksLeft(); + } + + void AddPlayerData(ObjectGuid guid) + { + if (PlayersStore.ContainsKey(guid)) + return; + + PlayersStore[guid] = new LFGPlayerData(); + } + + void RemovePlayerData(ObjectGuid guid) + { + Log.outDebug(LogFilter.Lfg, "RemovePlayerData: [{0}]", guid); + PlayersStore.Remove(guid); + } + + public void RemoveGroupData(ObjectGuid guid) + { + Log.outDebug(LogFilter.Lfg, "RemoveGroupData: [{0}]", guid); + var it = GroupsStore.LookupByKey(guid); + if (it == null) + return; + + LfgState state = GetState(guid); + // If group is being formed after proposal success do nothing more + List players = it.GetPlayers(); + foreach (var _guid in players) + { + SetGroup(_guid, ObjectGuid.Empty); + if (state != LfgState.Proposal) + { + SetState(_guid, LfgState.None); + SendLfgUpdateStatus(_guid, new LfgUpdateData(LfgUpdateType.RemovedFromQueue), true); + } + } + GroupsStore.Remove(guid); + } + + Team GetTeam(ObjectGuid guid) + { + return PlayersStore[guid].GetTeam(); + } + + public byte RemovePlayerFromGroup(ObjectGuid gguid, ObjectGuid guid) + { + return GroupsStore[gguid].RemovePlayer(guid); + } + + public void AddPlayerToGroup(ObjectGuid gguid, ObjectGuid guid) + { + GroupsStore[gguid].AddPlayer(guid); + } + + public void SetLeader(ObjectGuid gguid, ObjectGuid leader) + { + if (!GroupsStore.ContainsKey(gguid)) + GroupsStore[gguid] = new LFGGroupData(); + GroupsStore[gguid].SetLeader(leader); + } + + public void SetTeam(ObjectGuid guid, Team team) + { + if (WorldConfig.GetBoolValue(WorldCfg.AllowTwoSideInteractionGroup)) + team = 0; + + PlayersStore[guid].SetTeam(team); + } + + public ObjectGuid GetGroup(ObjectGuid guid) + { + AddPlayerData(guid); + return PlayersStore[guid].GetGroup(); + } + + public void SetGroup(ObjectGuid guid, ObjectGuid group) + { + AddPlayerData(guid); + PlayersStore[guid].SetGroup(group); + } + + List GetPlayers(ObjectGuid guid) + { + return GroupsStore[guid].GetPlayers(); + } + + public byte GetPlayerCount(ObjectGuid guid) + { + return GroupsStore[guid].GetPlayerCount(); + } + + public ObjectGuid GetLeader(ObjectGuid guid) + { + return GroupsStore[guid].GetLeader(); + } + + public bool HasIgnore(ObjectGuid guid1, ObjectGuid guid2) + { + Player plr1 = Global.ObjAccessor.FindPlayer(guid1); + Player plr2 = Global.ObjAccessor.FindPlayer(guid2); + return plr1 && plr2 && (plr1.GetSocial().HasIgnore(guid2) || plr2.GetSocial().HasIgnore(guid1)); + } + + public void SendLfgRoleChosen(ObjectGuid guid, ObjectGuid pguid, LfgRoles roles) + { + Player player = Global.ObjAccessor.FindPlayer(guid); + if (player) + player.GetSession().SendLfgRoleChosen(pguid, roles); + } + + public void SendLfgRoleCheckUpdate(ObjectGuid guid, LfgRoleCheck roleCheck) + { + Player player = Global.ObjAccessor.FindPlayer(guid); + if (player) + player.GetSession().SendLfgRoleCheckUpdate(roleCheck); + } + + public void SendLfgUpdateStatus(ObjectGuid guid, LfgUpdateData data, bool party) + { + Player player = Global.ObjAccessor.FindPlayer(guid); + if (player) + player.GetSession().SendLfgUpdateStatus(data, party); + } + + public void SendLfgJoinResult(ObjectGuid guid, LfgJoinResultData data) + { + Player player = Global.ObjAccessor.FindPlayer(guid); + if (player) + player.GetSession().SendLfgJoinResult(data); + } + + public void SendLfgBootProposalUpdate(ObjectGuid guid, LfgPlayerBoot boot) + { + Player player = Global.ObjAccessor.FindPlayer(guid); + if (player) + player.GetSession().SendLfgBootProposalUpdate(boot); + } + + public void SendLfgUpdateProposal(ObjectGuid guid, LfgProposal proposal) + { + Player player = Global.ObjAccessor.FindPlayer(guid); + if (player) + player.GetSession().SendLfgProposalUpdate(proposal); + } + + public void SendLfgQueueStatus(ObjectGuid guid, LfgQueueStatusData data) + { + Player player = Global.ObjAccessor.FindPlayer(guid); + if (player) + player.GetSession().SendLfgQueueStatus(data); + } + + public bool IsLfgGroup(ObjectGuid guid) + { + return !guid.IsEmpty() && guid.IsParty() && GroupsStore[guid].IsLfgGroup(); + } + + public byte GetQueueId(ObjectGuid guid) + { + if (guid.IsParty()) + { + List players = GetPlayers(guid); + ObjectGuid pguid = players.Empty() ? ObjectGuid.Empty : players.First(); + if (!pguid.IsEmpty()) + return (byte)GetTeam(pguid); + } + + return (byte)GetTeam(guid); + } + + public LFGQueue GetQueue(ObjectGuid guid) + { + byte queueId = GetQueueId(guid); + if (!QueuesStore.ContainsKey(queueId)) + QueuesStore[queueId] = new LFGQueue(); + + return QueuesStore[queueId]; + } + + public bool AllQueued(List check) + { + if (check.Empty()) + return false; + + foreach (var guid in check) + { + LfgState state = GetState(guid); + if (state != LfgState.Queued) + { + if (state != LfgState.Proposal) + Log.outDebug(LogFilter.Lfg, "Unexpected state found while trying to form new group. Guid: {0}, State: {1}", guid.ToString(), state); + + return false; + } + } + return true; + } + + public long GetQueueJoinTime(ObjectGuid guid) + { + byte queueId = GetQueueId(guid); + var lfgQueue = QueuesStore.LookupByKey(queueId); + if (lfgQueue != null) + return lfgQueue.GetJoinTime(guid); + + return 0; + } + + // Only for debugging purposes + public void Clean() + { + QueuesStore.Clear(); + } + + public bool isOptionEnabled(LfgOptions option) + { + return m_options.HasAnyFlag(option); + } + + public LfgOptions GetOptions() + { + return m_options; + } + + public void SetOptions(LfgOptions options) + { + m_options = options; + } + + public LfgUpdateData GetLfgStatus(ObjectGuid guid) + { + var playerData = PlayersStore[guid]; + return new LfgUpdateData(LfgUpdateType.UpdateStatus, playerData.GetState(), playerData.GetSelectedDungeons()); + } + + bool IsSeasonActive(uint dungeonId) + { + switch (dungeonId) + { + case 285: // The Headless Horseman + return Global.GameEventMgr.IsHolidayActive(HolidayIds.HallowsEnd); + case 286: // The Frost Lord Ahune + return Global.GameEventMgr.IsHolidayActive(HolidayIds.FireFestival); + case 287: // Coren Direbrew + return Global.GameEventMgr.IsHolidayActive(HolidayIds.Brewfest); + case 288: // The Crown Chemical Co. + return Global.GameEventMgr.IsHolidayActive(HolidayIds.LoveIsInTheAir); + case 744: // Random Timewalking Dungeon (Burning Crusade) + return Global.GameEventMgr.IsHolidayActive(HolidayIds.TimewalkingOutlands); + case 995: // Random Timewalking Dungeon (Wrath of the Lich King) + return Global.GameEventMgr.IsHolidayActive(HolidayIds.TimewalkingNorthrend); + case 1146: // Random Timewalking Dungeon (Cataclysm) + return Global.GameEventMgr.IsHolidayActive(HolidayIds.TimewalkingCataclysm); + } + return false; + } + + public string DumpQueueInfo(bool full) + { + + uint size = (uint)QueuesStore.Count; + + string str = "Number of Queues: " + size + "\n"; + foreach (var pair in QueuesStore) + { + string queued = pair.Value.DumpQueueInfo(); + string compatibles = pair.Value.DumpCompatibleInfo(full); + str += queued + compatibles; + } + + return str; + } + + public void SetupGroupMember(ObjectGuid guid, ObjectGuid gguid) + { + List dungeons = new List(); + dungeons.Add(GetDungeon(gguid)); + SetSelectedDungeons(guid, dungeons); + SetState(guid, GetState(gguid)); + SetGroup(guid, gguid); + AddPlayerToGroup(gguid, guid); + } + + public bool selectedRandomLfgDungeon(ObjectGuid guid) + { + if (GetState(guid) != LfgState.None) + { + List dungeons = GetSelectedDungeons(guid); + if (!dungeons.Empty()) + { + LFGDungeonData dungeon = GetLFGDungeon(dungeons.First()); + if (dungeon != null && (dungeon.type == LfgType.RandomDungeon || dungeon.seasonal)) + return true; + } + } + + return false; + } + + public bool inLfgDungeonMap(ObjectGuid guid, uint map, Difficulty difficulty) + { + if (!guid.IsParty()) + guid = GetGroup(guid); + + uint dungeonId = GetDungeon(guid, true); + if (dungeonId != 0) + { + LFGDungeonData dungeon = GetLFGDungeon(dungeonId); + if (dungeon != null) + if (dungeon.map == map && dungeon.difficulty == difficulty) + return true; + } + + return false; + } + + public uint GetLFGDungeonEntry(uint id) + { + if (id != 0) + { + LFGDungeonData dungeon = GetLFGDungeon(id); + if (dungeon != null) + return dungeon.Entry(); + } + + return 0; + } + + public List GetRandomAndSeasonalDungeons(uint level, uint expansion) + { + List randomDungeons = new List(); + foreach (var dungeon in LfgDungeonStore.Values) + { + if ((dungeon.seasonal && IsSeasonActive(dungeon.id) || !dungeon.seasonal && dungeon.type == LfgType.RandomDungeon) && dungeon.expansion <= expansion && dungeon.minlevel <= level && level <= dungeon.maxlevel) + randomDungeons.Add(dungeon.Entry()); + } + return randomDungeons; + } + + // General variables + uint m_QueueTimer; //< used to check interval of update + uint m_lfgProposalId; //< used as internal counter for proposals + LfgOptions m_options; //< Stores config options + + Dictionary QueuesStore = new Dictionary(); //< Queues + MultiMap CachedDungeonMapStore = new MultiMap(); //< Stores all dungeons by groupType + // Reward System + MultiMap RewardMapStore = new MultiMap(); //< Stores rewards for random dungeons + Dictionary LfgDungeonStore = new Dictionary(); + // Rolecheck - Proposal - Vote Kicks + Dictionary RoleChecksStore = new Dictionary(); //< Current Role checks + Dictionary ProposalsStore = new Dictionary(); //< Current Proposals + Dictionary BootsStore = new Dictionary(); //< Current player kicks + Dictionary PlayersStore = new Dictionary(); //< Player data + Dictionary GroupsStore = new Dictionary(); //< Group data + } + + public class LfgJoinResultData + { + public LfgJoinResultData(LfgJoinResult _result = LfgJoinResult.Ok, LfgRoleCheckState _state = LfgRoleCheckState.Default) + { + result = _result; + state = _state; + } + + public LfgJoinResult result; + public LfgRoleCheckState state; + public Dictionary> lockmap = new Dictionary>(); + } + + public class LfgUpdateData + { + public LfgUpdateData(LfgUpdateType _type = LfgUpdateType.Default) + { + updateType = _type; + state = LfgState.None; + comment = ""; + } + public LfgUpdateData(LfgUpdateType _type, List _dungeons, string _comment) + { + updateType = _type; + state = LfgState.None; + dungeons = _dungeons; + comment = _comment; + } + public LfgUpdateData(LfgUpdateType _type, LfgState _state, List _dungeons, string _comment = "") + { + updateType = _type; + state = _state; + dungeons = _dungeons; + comment = _comment; + } + + public LfgUpdateType updateType; + public LfgState state; + public List dungeons = new List(); + public string comment; + } + + public class LfgQueueStatusData + { + public LfgQueueStatusData(byte _queueId = 0, uint _dungeonId = 0, long _joinTime = 0, int _waitTime = -1, int _waitTimeAvg = -1, int _waitTimeTank = -1, int _waitTimeHealer = -1, + int _waitTimeDps = -1, uint _queuedTime = 0, byte _tanks = 0, byte _healers = 0, byte _dps = 0) + { + queueId = _queueId; + dungeonId = _dungeonId; + joinTime = _joinTime; + waitTime = _waitTime; + waitTimeAvg = _waitTimeAvg; + waitTimeTank = _waitTimeTank; + waitTimeHealer = _waitTimeHealer; + waitTimeDps = _waitTimeDps; + queuedTime = _queuedTime; + tanks = _tanks; + healers = _healers; + dps = _dps; + } + + public byte queueId; + public uint dungeonId; + public long joinTime; + public int waitTime; + public int waitTimeAvg; + public int waitTimeTank; + public int waitTimeHealer; + public int waitTimeDps; + public uint queuedTime; + public byte tanks; + public byte healers; + public byte dps; + } + + public class LfgPlayerRewardData + { + public LfgPlayerRewardData(uint random, uint current, bool _done, Quest _quest) + { + rdungeonEntry = random; + sdungeonEntry = current; + done = _done; + quest = _quest; + } + + public uint rdungeonEntry; + public uint sdungeonEntry; + public bool done; + public Quest quest; + } + + public class LfgReward + { + public LfgReward(uint _maxLevel = 0, uint _firstQuest = 0, uint _otherQuest = 0) + { + maxLevel = _maxLevel; + firstQuest = _firstQuest; + otherQuest = _otherQuest; + } + + public uint maxLevel; + public uint firstQuest; + public uint otherQuest; + } + + public class LfgProposalPlayer + { + public LfgProposalPlayer() + { + role = 0; + accept = LfgAnswer.Pending; + group = ObjectGuid.Empty; + } + + public LfgRoles role; + public LfgAnswer accept; + public ObjectGuid group; + } + + public class LfgProposal + { + public LfgProposal(uint dungeon = 0) + { + id = 0; + dungeonId = dungeon; + state = LfgProposalState.Initiating; + group = ObjectGuid.Empty; + leader = ObjectGuid.Empty; + cancelTime = 0; + encounters = 0; + isNew = true; + } + + public uint id; + public uint dungeonId; + public LfgProposalState state; + public ObjectGuid group; + public ObjectGuid leader; + public long cancelTime; + public uint encounters; + public bool isNew; + public List queues = new List(); + public List showorder = new List(); + public Dictionary players = new Dictionary(); ///< Players data + } + + public class LfgRoleCheck + { + public long cancelTime; + public Dictionary roles = new Dictionary(); + public LfgRoleCheckState state; + public List dungeons = new List(); + public uint rDungeonId; + public ObjectGuid leader; + } + + public class LfgPlayerBoot + { + public long cancelTime; + public bool inProgress; + public Dictionary votes = new Dictionary(); + public ObjectGuid victim; + public string reason; + } + + public class LFGDungeonData + { + public LFGDungeonData(LfgDungeonsRecord dbc) + { + id = dbc.Id; + name = dbc.Name[Global.WorldMgr.GetDefaultDbcLocale()]; + map = (uint)dbc.MapID; + type = dbc.Type; + expansion = dbc.Expansion; + group = dbc.GroupID; + minlevel = dbc.MinLevel; + maxlevel = dbc.MaxLevel; + difficulty = dbc.DifficultyID; + seasonal = dbc.Flags.HasAnyFlag(LfgFlags.Seasonal); + } + + public uint id; + public string name; + public uint map; + public LfgType type; + public uint expansion; + public uint group; + public uint minlevel; + public uint maxlevel; + public Difficulty difficulty; + public bool seasonal; + public float x, y, z, o; + public ushort requiredItemLevel; + + // Helpers + public uint Entry() { return (uint)(id + ((int)type << 24)); } + } + + public class LfgLockInfoData + { + public LfgLockInfoData(LfgLockStatusType _lockStatus = 0, ushort _requiredItemLevel = 0, float _currentItemLevel = 0) + { + lockStatus = _lockStatus; + requiredItemLevel = _requiredItemLevel; + currentItemLevel = _currentItemLevel; + } + + public LfgLockStatusType lockStatus; + public ushort requiredItemLevel; + public float currentItemLevel; + } +} \ No newline at end of file diff --git a/Game/DungeonFinding/LFGPlayerData.cs b/Game/DungeonFinding/LFGPlayerData.cs new file mode 100644 index 000000000..bbbbd0f5c --- /dev/null +++ b/Game/DungeonFinding/LFGPlayerData.cs @@ -0,0 +1,132 @@ +/* + * Copyright (C) 2012-2017 CypherCore + * + * 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, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +using Framework.Constants; +using Game.Entities; +using System.Collections.Generic; + +namespace Game.DungeonFinding +{ + public class LFGPlayerData + { + public LFGPlayerData() + { + m_State = LfgState.None; + m_OldState = LfgState.None; + m_Comment = ""; + } + + public void SetState(LfgState state) + { + switch (state) + { + case LfgState.None: + case LfgState.FinishedDungeon: + m_Roles = 0; + m_SelectedDungeons.Clear(); + m_Comment = ""; + goto case LfgState.Dungeon; + case LfgState.Dungeon: + m_OldState = state; + break; + } + m_State = state; + } + + public void RestoreState() + { + if (m_OldState == LfgState.None) + { + m_SelectedDungeons.Clear(); + m_Roles = 0; + } + m_State = m_OldState; + } + + public void SetTeam(Team team) + { + m_Team = team; + } + + public void SetGroup(ObjectGuid group) + { + m_Group = group; + } + + public void SetRoles(LfgRoles roles) + { + m_Roles = roles; + } + + public void SetComment(string comment) + { + m_Comment = comment; + } + + public void SetSelectedDungeons(List dungeons) + { + m_SelectedDungeons = dungeons; + } + + public LfgState GetState() + { + return m_State; + } + + public LfgState GetOldState() + { + return m_OldState; + } + + public Team GetTeam() + { + return m_Team; + } + + public ObjectGuid GetGroup() + { + return m_Group; + } + + public LfgRoles GetRoles() + { + return m_Roles; + } + + public string GetComment() + { + return m_Comment; + } + + public List GetSelectedDungeons() + { + return m_SelectedDungeons; + } + + // General + LfgState m_State; + LfgState m_OldState; + // Player + Team m_Team; + ObjectGuid m_Group; + + // Queue + LfgRoles m_Roles; + string m_Comment; + List m_SelectedDungeons = new List(); + } +} diff --git a/Game/DungeonFinding/LFGQueue.cs b/Game/DungeonFinding/LFGQueue.cs new file mode 100644 index 000000000..1605f2d05 --- /dev/null +++ b/Game/DungeonFinding/LFGQueue.cs @@ -0,0 +1,780 @@ +/* + * Copyright (C) 2012-2017 CypherCore + * + * 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, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +using Framework.Constants; +using Game.Entities; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; + +namespace Game.DungeonFinding +{ + public class LFGQueue + { + public static string ConcatenateGuids(List guids) + { + if (guids.Empty()) + return ""; + + // need the guids in order to avoid duplicates + StringBuilder val = new StringBuilder(); + guids.Sort(); + var it = guids.First(); + val.Append(it); + foreach (var guid in guids) + { + if (guid == it) + continue; + val.AppendFormat("|{0}", guid); + } + + return val.ToString(); + } + + public static string GetRolesString(LfgRoles roles) + { + StringBuilder rolesstr = new StringBuilder(); + + if (roles.HasAnyFlag(LfgRoles.Tank)) + rolesstr.Append("Tank"); + + if (roles.HasAnyFlag(LfgRoles.Healer)) + { + if (rolesstr.Capacity != 0) + rolesstr.Append(", "); + rolesstr.Append("Healer"); + } + + if (roles.HasAnyFlag(LfgRoles.Damage)) + { + if (rolesstr.Capacity != 0) + rolesstr.Append(", "); + rolesstr.Append("Damage"); + } + + if (roles.HasAnyFlag(LfgRoles.Leader)) + { + if (rolesstr.Capacity != 0) + rolesstr.Append(", "); + rolesstr.Append("Leader"); + } + + if (rolesstr.Capacity == 0) + rolesstr.Append("None"); + + return rolesstr.ToString(); + } + + public static string ConcatenateDungeons(List dungeons) + { + string str = ""; + if (!dungeons.Empty()) + { + foreach (var it in dungeons) + { + if (!string.IsNullOrEmpty(str)) + str += ", "; + str += it; + } + } + return str; + } + + string GetCompatibleString(LfgCompatibility compatibles) + { + switch (compatibles) + { + case LfgCompatibility.Pending: + return "Pending"; + case LfgCompatibility.BadStates: + return "Compatibles (Bad States)"; + case LfgCompatibility.Match: + return "Match"; + case LfgCompatibility.WithLessPlayers: + return "Compatibles (Not enough players)"; + case LfgCompatibility.HasIgnores: + return "Has ignores"; + case LfgCompatibility.MultipleLfgGroups: + return "Multiple Lfg Groups"; + case LfgCompatibility.NoDungeons: + return "Incompatible dungeons"; + case LfgCompatibility.NoRoles: + return "Incompatible roles"; + case LfgCompatibility.TooMuchPlayers: + return "Too much players"; + case LfgCompatibility.WrongGroupSize: + return "Wrong group size"; + default: + return "Unknown"; + } + } + + public void AddToQueue(ObjectGuid guid, bool reAdd = false) + { + if (!QueueDataStore.ContainsKey(guid)) + { + Log.outError(LogFilter.Lfg, "AddToQueue: Queue data not found for [{0}]", guid); + return; + } + + if (reAdd) + AddToFrontCurrentQueue(guid); + else + AddToNewQueue(guid); + } + + public void RemoveFromQueue(ObjectGuid guid) + { + RemoveFromNewQueue(guid); + RemoveFromCurrentQueue(guid); + RemoveFromCompatibles(guid); + + string sguid = guid.ToString(); + + var itDelete = QueueDataStore.LastOrDefault().Key; + foreach (var key in QueueDataStore.Keys.ToList()) + { + var data = QueueDataStore[key]; + if (key != guid) + { + if (data.bestCompatible.Contains(sguid)) + { + data.bestCompatible = ""; + FindBestCompatibleInQueue(key, data); + } + } + else + itDelete = key; + } + + if (!itDelete.IsEmpty()) + QueueDataStore.Remove(itDelete); + } + + public void AddToNewQueue(ObjectGuid guid) + { + newToQueueStore.Add(guid); + } + + public void RemoveFromNewQueue(ObjectGuid guid) + { + newToQueueStore.Remove(guid); + } + + public void AddToCurrentQueue(ObjectGuid guid) + { + currentQueueStore.Add(guid); + } + + void AddToFrontCurrentQueue(ObjectGuid guid) + { + currentQueueStore.Insert(0, guid); + } + + public void RemoveFromCurrentQueue(ObjectGuid guid) + { + currentQueueStore.Remove(guid); + } + + public void AddQueueData(ObjectGuid guid, long joinTime, List dungeons, Dictionary rolesMap) + { + QueueDataStore[guid] = new LfgQueueData(joinTime, dungeons, rolesMap); + AddToQueue(guid); + } + + public void RemoveQueueData(ObjectGuid guid) + { + QueueDataStore.Remove(guid); + } + + public void UpdateWaitTimeAvg(int waitTime, uint dungeonId) + { + LfgWaitTime wt = waitTimesAvgStore[dungeonId]; + uint old_number = wt.number++; + wt.time = (int)((wt.time * old_number + waitTime) / wt.number); + } + + public void UpdateWaitTimeTank(int waitTime, uint dungeonId) + { + LfgWaitTime wt = waitTimesTankStore[dungeonId]; + uint old_number = wt.number++; + wt.time = (int)((wt.time * old_number + waitTime) / wt.number); + } + + public void UpdateWaitTimeHealer(int waitTime, uint dungeonId) + { + LfgWaitTime wt = waitTimesHealerStore[dungeonId]; + uint old_number = wt.number++; + wt.time = (int)((wt.time * old_number + waitTime) / wt.number); + } + + public void UpdateWaitTimeDps(int waitTime, uint dungeonId) + { + LfgWaitTime wt = waitTimesDpsStore[dungeonId]; + uint old_number = wt.number++; + wt.time = (int)((wt.time * old_number + waitTime) / wt.number); + } + + void RemoveFromCompatibles(ObjectGuid guid) + { + string strGuid = guid.ToString(); + + Log.outDebug(LogFilter.Lfg, "RemoveFromCompatibles: Removing [{0}]", guid); + foreach (var itNext in CompatibleMapStore.ToList()) + { + if (itNext.Key.Contains(strGuid)) + CompatibleMapStore.Remove(itNext.Key); + } + } + + void SetCompatibles(string key, LfgCompatibility compatibles) + { + if (!CompatibleMapStore.ContainsKey(key)) + CompatibleMapStore[key] = new LfgCompatibilityData(); + + CompatibleMapStore[key].compatibility = compatibles; + } + + void SetCompatibilityData(string key, LfgCompatibilityData data) + { + CompatibleMapStore[key] = data; + } + + LfgCompatibility GetCompatibles(string key) + { + var compatibilityData = CompatibleMapStore.LookupByKey(key); + if (compatibilityData != null) + return compatibilityData.compatibility; + + return LfgCompatibility.Pending; + } + + LfgCompatibilityData GetCompatibilityData(string key) + { + var compatibilityData = CompatibleMapStore.LookupByKey(key); + if (compatibilityData != null) + return compatibilityData; + + return null; + } + + public byte FindGroups() + { + byte proposals = 0; + List firstNew = new List(); + while (!newToQueueStore.Empty()) + { + ObjectGuid frontguid = newToQueueStore.First(); + Log.outDebug(LogFilter.Lfg, "FindGroups: checking [{0}] newToQueue({1}), currentQueue({2})", frontguid, newToQueueStore.Count, currentQueueStore.Count); + firstNew.Clear(); + firstNew.Add(frontguid); + RemoveFromNewQueue(frontguid); + + List temporalList = new List(currentQueueStore); + LfgCompatibility compatibles = FindNewGroups(firstNew, temporalList); + + if (compatibles == LfgCompatibility.Match) + ++proposals; + else + AddToCurrentQueue(frontguid); // Lfg group not found, add this group to the queue. + } + return proposals; + } + + LfgCompatibility FindNewGroups(List check, List all) + { + string strGuids = ConcatenateGuids(check); + LfgCompatibility compatibles = GetCompatibles(strGuids); + + Log.outDebug(LogFilter.Lfg, "FindNewGroup: ({0}): {1} - all({2})", strGuids, GetCompatibleString(compatibles), ConcatenateGuids(all)); + if (compatibles == LfgCompatibility.Pending) // Not previously cached, calculate + compatibles = CheckCompatibility(check); + + if (compatibles == LfgCompatibility.BadStates && Global.LFGMgr.AllQueued(check)) + { + Log.outDebug(LogFilter.Lfg, "FindNewGroup: ({0}) compatibles (cached) changed from bad states to match", strGuids); + SetCompatibles(strGuids, LfgCompatibility.Match); + return LfgCompatibility.Match; + } + + if (compatibles != LfgCompatibility.WithLessPlayers) + return compatibles; + + // Try to match with queued groups + while (!all.Empty()) + { + check.Add(all.First()); + all.RemoveAt(0); + LfgCompatibility subcompatibility = FindNewGroups(check, all); + if (subcompatibility == LfgCompatibility.Match) + return LfgCompatibility.Match; + check.RemoveAt(check.Count - 1); + } + return compatibles; + } + + LfgCompatibility CheckCompatibility(List check) + { + string strGuids = ConcatenateGuids(check); + LfgProposal proposal = new LfgProposal(); + List proposalDungeons; + Dictionary proposalGroups = new Dictionary(); + Dictionary proposalRoles = new Dictionary(); + + // Check for correct size + if (check.Count > MapConst.MaxGroupSize || check.Empty()) + { + Log.outDebug(LogFilter.Lfg, "CheckCompatibility: ({0}): Size wrong - Not compatibles", strGuids); + return LfgCompatibility.WrongGroupSize; + } + + // Check all-but-new compatiblitity + if (check.Count > 2) + { + ObjectGuid frontGuid = check.First(); + check.RemoveAt(0); + + // Check all-but-new compatibilities (New, A, B, C, D) -. check(A, B, C, D) + LfgCompatibility child_compatibles = CheckCompatibility(check); + if (child_compatibles < LfgCompatibility.WithLessPlayers) // Group not compatible + { + Log.outDebug(LogFilter.Lfg, "CheckCompatibility: ({0}) child {1} not compatibles", strGuids, ConcatenateGuids(check)); + SetCompatibles(strGuids, child_compatibles); + return child_compatibles; + } + check.Insert(0, frontGuid); + } + + // Check if more than one LFG group and number of players joining + byte numPlayers = 0; + byte numLfgGroups = 0; + foreach (var guid in check) + { + if (!(numLfgGroups < 2) && !(numPlayers <= MapConst.MaxGroupSize)) + break; ; + + var itQueue = QueueDataStore.LookupByKey(guid); + if (itQueue == null) + { + Log.outError(LogFilter.Lfg, "CheckCompatibility: [{0}] is not queued but listed as queued!", guid); + RemoveFromQueue(guid); + return LfgCompatibility.Pending; + } + + // Store group so we don't need to call Mgr to get it later (if it's player group will be 0 otherwise would have joined as group) + foreach (var it2 in itQueue.roles) + proposalGroups[it2.Key] = guid.IsPlayer() ? guid : ObjectGuid.Empty; + + numPlayers += (byte)itQueue.roles.Count; + + if (Global.LFGMgr.IsLfgGroup(guid)) + { + if (numLfgGroups == 0) + proposal.group = guid; + ++numLfgGroups; + } + } + + // Group with less that MAXGROUPSIZE members always compatible + if (check.Count == 1 && numPlayers != MapConst.MaxGroupSize) + { + Log.outDebug(LogFilter.Lfg, "CheckCompatibility: ({0}) sigle group. Compatibles", strGuids); + var guid = check.First(); + var itQueue = QueueDataStore.LookupByKey(guid); + + LfgCompatibilityData data = new LfgCompatibilityData(LfgCompatibility.WithLessPlayers); + data.roles = itQueue.roles; + Global.LFGMgr.CheckGroupRoles(data.roles); + + UpdateBestCompatibleInQueue(guid, itQueue, strGuids, data.roles); + SetCompatibilityData(strGuids, data); + return LfgCompatibility.WithLessPlayers; + } + + if (numLfgGroups > 1) + { + Log.outDebug(LogFilter.Lfg, "CheckCompatibility: ({0}) More than one Lfggroup ({1})", strGuids, numLfgGroups); + SetCompatibles(strGuids, LfgCompatibility.MultipleLfgGroups); + return LfgCompatibility.MultipleLfgGroups; + } + + if (numPlayers > MapConst.MaxGroupSize) + { + Log.outDebug(LogFilter.Lfg, "CheckCompatibility: ({0}) Too much players ({1})", strGuids, numPlayers); + SetCompatibles(strGuids, LfgCompatibility.TooMuchPlayers); + return LfgCompatibility.TooMuchPlayers; + } + + // If it's single group no need to check for duplicate players, ignores, bad roles or bad dungeons as it's been checked before joining + if (check.Count > 1) + { + foreach (var it in check) + { + Dictionary roles = QueueDataStore[it].roles; + foreach (var rolePair in roles) + { + KeyValuePair itPlayer = new KeyValuePair(); + foreach (var _player in proposalRoles) + { + itPlayer = _player; + if (rolePair.Key == itPlayer.Key) + Log.outError(LogFilter.Lfg, "CheckCompatibility: ERROR! Player multiple times in queue! [{0}]", rolePair.Key); + else if (Global.LFGMgr.HasIgnore(rolePair.Key, itPlayer.Key)) + break; + } + if (itPlayer.Key == proposalRoles.LastOrDefault().Key) + proposalRoles[rolePair.Key] = rolePair.Value; + } + } + + byte playersize = (byte)(numPlayers - proposalRoles.Count); + if (playersize != 0) + { + Log.outDebug(LogFilter.Lfg, "CheckCompatibility: ({0}) not compatible, {1} players are ignoring each other", strGuids, playersize); + SetCompatibles(strGuids, LfgCompatibility.HasIgnores); + return LfgCompatibility.HasIgnores; + } + StringBuilder o; + Dictionary debugRoles = proposalRoles; + if (!Global.LFGMgr.CheckGroupRoles(proposalRoles)) + { + o = new StringBuilder(); + foreach (var it in debugRoles) + o.AppendFormat(", {0}: {1}", it.Key, GetRolesString(it.Value)); + + Log.outDebug(LogFilter.Lfg, "CheckCompatibility: ({0}) Roles not compatible{1}", strGuids, o.ToString()); + SetCompatibles(strGuids, LfgCompatibility.NoRoles); + return LfgCompatibility.NoRoles; + } + + var itguid = check.First(); + proposalDungeons = QueueDataStore[itguid].dungeons; + o = new StringBuilder(); + o.AppendFormat(", {0}: ({1})", itguid, Global.LFGMgr.ConcatenateDungeons(proposalDungeons)); + foreach (var guid in check) + { + if (guid == itguid) + continue; + + List temporal; + List dungeons = QueueDataStore[itguid].dungeons; + o.AppendFormat(", {0}: ({1})", guid, Global.LFGMgr.ConcatenateDungeons(dungeons)); + temporal = proposalDungeons.Intersect(dungeons).ToList(); + proposalDungeons = temporal; + } + + if (proposalDungeons.Empty()) + { + Log.outDebug(LogFilter.Lfg, "CheckCompatibility: ({0}) No compatible dungeons{1}", strGuids, o.ToString()); + SetCompatibles(strGuids, LfgCompatibility.NoDungeons); + return LfgCompatibility.NoDungeons; + } + } + else + { + ObjectGuid gguid = check.First(); + LfgQueueData queue = QueueDataStore[gguid]; + proposalDungeons = queue.dungeons; + proposalRoles = queue.roles; + Global.LFGMgr.CheckGroupRoles(proposalRoles); // assing new roles + } + + // Enough players? + if (numPlayers != MapConst.MaxGroupSize) + { + Log.outDebug(LogFilter.Lfg, "CheckCompatibility: ({0}) Compatibles but not enough players({1})", strGuids, numPlayers); + LfgCompatibilityData data = new LfgCompatibilityData(LfgCompatibility.WithLessPlayers); + data.roles = proposalRoles; + + foreach (var guid in check) + { + var queueData = QueueDataStore.LookupByKey(guid); + UpdateBestCompatibleInQueue(guid, queueData, strGuids, data.roles); + } + + SetCompatibilityData(strGuids, data); + return LfgCompatibility.WithLessPlayers; + } + + ObjectGuid _guid = check.First(); + proposal.queues = check; + proposal.isNew = numLfgGroups != 1 || Global.LFGMgr.GetOldState(_guid) != LfgState.Dungeon; + + if (!Global.LFGMgr.AllQueued(check)) + { + Log.outDebug(LogFilter.Lfg, "CheckCompatibility: ({0}) Group MATCH but can't create proposal!", strGuids); + SetCompatibles(strGuids, LfgCompatibility.BadStates); + return LfgCompatibility.BadStates; + } + + // Create a new proposal + proposal.cancelTime = Time.UnixTime + SharedConst.LFGTimeProposal; + proposal.state = LfgProposalState.Initiating; + proposal.leader = ObjectGuid.Empty; + proposal.dungeonId = proposalDungeons.PickRandom(); + + bool leader = false; + foreach (var rolePair in proposalRoles) + { + // Assing new leader + if (rolePair.Value.HasAnyFlag(LfgRoles.Leader)) + { + if (!leader || proposal.leader.IsEmpty() || Convert.ToBoolean(RandomHelper.IRand(0, 1))) + proposal.leader = rolePair.Key; + leader = true; + } + else if (!leader && (proposal.leader.IsEmpty() || Convert.ToBoolean(RandomHelper.IRand(0, 1)))) + proposal.leader = rolePair.Key; + + // Assing player data and roles + LfgProposalPlayer data = new LfgProposalPlayer(); + data.role = rolePair.Value; + data.group = proposalGroups.LookupByKey(rolePair.Key); + if (!proposal.isNew && !data.group.IsEmpty() && data.group == proposal.group) // Player from existing group, autoaccept + data.accept = LfgAnswer.Agree; + + proposal.players[rolePair.Key] = data; + } + + // Mark proposal members as not queued (but not remove queue data) + foreach (var guid in proposal.queues) + { + RemoveFromNewQueue(guid); + RemoveFromCurrentQueue(guid); + } + + Global.LFGMgr.AddProposal(proposal); + + Log.outDebug(LogFilter.Lfg, "CheckCompatibility: ({0}) MATCH! Group formed", strGuids); + SetCompatibles(strGuids, LfgCompatibility.Match); + return LfgCompatibility.Match; + } + + public void UpdateQueueTimers(byte queueId, long currTime) + { + Log.outDebug(LogFilter.Lfg, "Updating queue timers..."); + foreach (var itQueue in QueueDataStore) + { + LfgQueueData queueinfo = itQueue.Value; + uint dungeonId = queueinfo.dungeons.FirstOrDefault(); + uint queuedTime = (uint)(currTime - queueinfo.joinTime); + LfgRoles role = LfgRoles.None; + int waitTime = -1; + + if (!waitTimesTankStore.ContainsKey(dungeonId)) + waitTimesTankStore[dungeonId] = new LfgWaitTime(); + if (!waitTimesHealerStore.ContainsKey(dungeonId)) + waitTimesHealerStore[dungeonId] = new LfgWaitTime(); + if (!waitTimesDpsStore.ContainsKey(dungeonId)) + waitTimesDpsStore[dungeonId] = new LfgWaitTime(); + if (!waitTimesAvgStore.ContainsKey(dungeonId)) + waitTimesAvgStore[dungeonId] = new LfgWaitTime(); + + int wtTank = waitTimesTankStore[dungeonId].time; + int wtHealer = waitTimesHealerStore[dungeonId].time; + int wtDps = waitTimesDpsStore[dungeonId].time; + int wtAvg = waitTimesAvgStore[dungeonId].time; + + foreach (var itPlayer in queueinfo.roles) + role |= itPlayer.Value; + role &= ~LfgRoles.Leader; + + switch (role) + { + case LfgRoles.None: // Should not happen - just in case + waitTime = -1; + break; + case LfgRoles.Tank: + waitTime = wtTank; + break; + case LfgRoles.Healer: + waitTime = wtHealer; + break; + case LfgRoles.Damage: + waitTime = wtDps; + break; + default: + waitTime = wtAvg; + break; + } + + if (string.IsNullOrEmpty(queueinfo.bestCompatible)) + FindBestCompatibleInQueue(itQueue.Key, itQueue.Value); + + LfgQueueStatusData queueData = new LfgQueueStatusData(queueId, dungeonId, queueinfo.joinTime, waitTime, wtAvg, wtTank, wtHealer, wtDps, queuedTime, queueinfo.tanks, queueinfo.healers, queueinfo.dps); + foreach (var itPlayer in queueinfo.roles) + { + ObjectGuid pguid = itPlayer.Key; + Global.LFGMgr.SendLfgQueueStatus(pguid, queueData); + } + } + } + + public long GetJoinTime(ObjectGuid guid) + { + var queueData = QueueDataStore.LookupByKey(guid); + if (queueData != null) + return queueData.joinTime; + + return 0; + } + + public string DumpQueueInfo() + { + uint players = 0; + uint groups = 0; + uint playersInGroup = 0; + + for (byte i = 0; i < 2; ++i) + { + List queue = i != 0 ? newToQueueStore : currentQueueStore; + foreach (var guid in queue) + { + if (guid.IsParty()) + { + groups++; + playersInGroup += Global.LFGMgr.GetPlayerCount(guid); + } + else + players++; + } + } + + return string.Format("Queued Players: {0} (in group: {1}) Groups: {2}\n", players, playersInGroup, groups); + } + + public string DumpCompatibleInfo(bool full = false) + { + string str = "Compatible Map size: " + CompatibleMapStore.Count + "\n"; + if (full) + { + foreach (var pair in CompatibleMapStore) + str += "(" + pair.Key + "): " + GetCompatibleString(pair.Value.compatibility) + "\n"; + } + return str; + } + + void FindBestCompatibleInQueue(ObjectGuid guid, LfgQueueData data) + { + Log.outDebug(LogFilter.Lfg, "FindBestCompatibleInQueue: {0}", guid); + + foreach (var pair in CompatibleMapStore) + { + if (pair.Value.compatibility == LfgCompatibility.WithLessPlayers && pair.Key.Contains(guid.ToString())) + UpdateBestCompatibleInQueue(guid, data, pair.Key, pair.Value.roles); + } + } + + public void UpdateBestCompatibleInQueue(ObjectGuid guid, LfgQueueData queueData, string key, Dictionary roles) + { + byte storedSize = (byte)(string.IsNullOrEmpty(queueData.bestCompatible) ? 0 : queueData.bestCompatible.Count(p => p == '|') + 1); + + byte size = (byte)(key.Count(p => p == '|') + 1); + + if (size <= storedSize) + return; + + Log.outDebug(LogFilter.Lfg, "UpdateBestCompatibleInQueue: Changed ({0}) to ({1}) as best compatible group for {2}", + queueData.bestCompatible, key, guid); + + queueData.bestCompatible = key; + queueData.tanks = SharedConst.LFGTanksNeeded; + queueData.healers = SharedConst.LFGHealersNeeded; + queueData.dps = SharedConst.LFGDPSNeeded; + foreach (var it in roles) + { + LfgRoles role = it.Value; + if (role.HasAnyFlag(LfgRoles.Tank)) + --queueData.tanks; + else if (role.HasAnyFlag(LfgRoles.Healer)) + --queueData.healers; + else + --queueData.dps; + } + } + + // Queue + Dictionary QueueDataStore = new Dictionary(); + Dictionary CompatibleMapStore = new Dictionary(); + + Dictionary waitTimesAvgStore = new Dictionary(); + Dictionary waitTimesTankStore = new Dictionary(); + Dictionary waitTimesHealerStore = new Dictionary(); + Dictionary waitTimesDpsStore = new Dictionary(); + List currentQueueStore = new List(); + List newToQueueStore = new List(); + } + + public class LfgCompatibilityData + { + public LfgCompatibilityData() + { + compatibility = LfgCompatibility.Pending; + } + + public LfgCompatibilityData(LfgCompatibility _compatibility) + { + compatibility = _compatibility; + } + + public LfgCompatibilityData(LfgCompatibility _compatibility, Dictionary _roles) + { + compatibility = _compatibility; + roles = _roles; + } + + public LfgCompatibility compatibility; + public Dictionary roles; + } + + // Stores player or group queue info + public class LfgQueueData + { + public LfgQueueData() + { + joinTime = Time.UnixTime; + tanks = SharedConst.LFGTanksNeeded; + healers = SharedConst.LFGHealersNeeded; + dps = SharedConst.LFGDPSNeeded; + } + + public LfgQueueData(long _joinTime, List _dungeons, Dictionary _roles) + { + joinTime = _joinTime; + tanks = SharedConst.LFGTanksNeeded; + healers = SharedConst.LFGHealersNeeded; + dps = SharedConst.LFGDPSNeeded; + dungeons = _dungeons; + roles = _roles; + } + + public long joinTime; + public byte tanks; + public byte healers; + public byte dps; + public List dungeons; + public Dictionary roles; + public string bestCompatible = ""; + } + + public struct LfgWaitTime + { + public int time; + public uint number; + } +} diff --git a/Game/DungeonFinding/LFGScripts.cs b/Game/DungeonFinding/LFGScripts.cs new file mode 100644 index 000000000..efef8fb8e --- /dev/null +++ b/Game/DungeonFinding/LFGScripts.cs @@ -0,0 +1,245 @@ +/* + * Copyright (C) 2012-2017 CypherCore + * + * 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, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +using Framework.Constants; +using Game.Entities; +using Game.Groups; +using Game.Maps; +using Game.Scripting; + +namespace Game.DungeonFinding +{ + class LFGPlayerScript : PlayerScript + { + public LFGPlayerScript() : base("LFGPlayerScript") { } + + // Player Hooks + public override void OnLogout(Player player) + { + if (!Global.LFGMgr.isOptionEnabled(LfgOptions.EnableDungeonFinder | LfgOptions.EnableRaidBrowser)) + return; + + if (!player.GetGroup()) + { + player.GetSession().SendLfgLfrList(false); + Global.LFGMgr.LeaveLfg(player.GetGUID()); + } + else if (player.GetSession().PlayerDisconnected()) + Global.LFGMgr.LeaveLfg(player.GetGUID(), true); + } + + public override void OnLogin(Player player) + { + if (!Global.LFGMgr.isOptionEnabled(LfgOptions.EnableDungeonFinder | LfgOptions.EnableRaidBrowser)) + return; + + // Temporal: Trying to determine when group data and LFG data gets desynched + ObjectGuid guid = player.GetGUID(); + ObjectGuid gguid = Global.LFGMgr.GetGroup(guid); + + Group group = player.GetGroup(); + if (group) + { + ObjectGuid gguid2 = group.GetGUID(); + if (gguid != gguid2) + { + Log.outError(LogFilter.Lfg, "{0} on group {1} but LFG has group {2} saved... Fixing.", player.GetSession().GetPlayerInfo(), gguid2.ToString(), gguid.ToString()); + Global.LFGMgr.SetupGroupMember(guid, group.GetGUID()); + } + } + + Global.LFGMgr.SetTeam(player.GetGUID(), player.GetTeam()); + /// @todo - Restore LfgPlayerData and send proper status to player if it was in a group + } + + public override void OnMapChanged(Player player) + { + Map map = player.GetMap(); + + if (Global.LFGMgr.inLfgDungeonMap(player.GetGUID(), map.GetId(), map.GetDifficultyID())) + { + Group group = player.GetGroup(); + // This function is also called when players log in + // if for some reason the LFG system recognises the player as being in a LFG dungeon, + // but the player was loaded without a valid group, we'll teleport to homebind to prevent + // crashes or other undefined behaviour + if (!group) + { + Global.LFGMgr.LeaveLfg(player.GetGUID()); + player.RemoveAurasDueToSpell(SharedConst.LFGSpellLuckOfTheDraw); + player.TeleportTo(player.GetHomebind()); + Log.outError(LogFilter.Lfg, "LFGPlayerScript.OnMapChanged, Player {0} ({1}) is in LFG dungeon map but does not have a valid group! Teleporting to homebind.", + player.GetName(), player.GetGUID().ToString()); + return; + } + + for (GroupReference refe = group.GetFirstMember(); refe != null; refe = refe.next()) + { + Player member = refe.GetSource(); + if (member) + player.GetSession().SendNameQuery(member.GetGUID()); + } + + if (Global.LFGMgr.selectedRandomLfgDungeon(player.GetGUID())) + player.CastSpell(player, SharedConst.LFGSpellLuckOfTheDraw, true); + } + else + { + Group group = player.GetGroup(); + if (group && group.GetMembersCount() == 1) + { + Global.LFGMgr.LeaveLfg(group.GetGUID()); + group.Disband(); + Log.outDebug(LogFilter.Lfg, "LFGPlayerScript::OnMapChanged, Player {0}({1}) is last in the lfggroup so we disband the group.", + player.GetName(), player.GetGUID().ToString()); + } + + player.RemoveAurasDueToSpell(SharedConst.LFGSpellLuckOfTheDraw); + } + } + } + + class LFGGroupScript : GroupScript + { + public LFGGroupScript() : base("LFGGroupScript") { } + + // Group Hooks + public override void OnAddMember(Group group, ObjectGuid guid) + { + if (!Global.LFGMgr.isOptionEnabled(LfgOptions.EnableDungeonFinder | LfgOptions.EnableRaidBrowser)) + return; + + ObjectGuid gguid = group.GetGUID(); + ObjectGuid leader = group.GetLeaderGUID(); + + if (leader == guid) + { + Log.outDebug(LogFilter.Lfg, "LFGScripts.OnAddMember [{0}]: added [{1} leader {2}]", gguid, guid, leader); + Global.LFGMgr.SetLeader(gguid, guid); + } + else + { + LfgState gstate = Global.LFGMgr.GetState(gguid); + LfgState state = Global.LFGMgr.GetState(guid); + Log.outDebug(LogFilter.Lfg, "LFGScripts.OnAddMember [{0}]: added [{1} leader {2}] gstate: {3}, state: {4}", gguid, guid, leader, gstate, state); + + if (state == LfgState.Queued) + Global.LFGMgr.LeaveLfg(guid); + + if (gstate == LfgState.Queued) + Global.LFGMgr.LeaveLfg(gguid); + } + + Global.LFGMgr.SetGroup(guid, gguid); + Global.LFGMgr.AddPlayerToGroup(gguid, guid); + } + + public override void OnRemoveMember(Group group, ObjectGuid guid, RemoveMethod method, ObjectGuid kicker, string reason) + { + if (!Global.LFGMgr.isOptionEnabled(LfgOptions.EnableDungeonFinder | LfgOptions.EnableRaidBrowser)) + return; + + ObjectGuid gguid = group.GetGUID(); + Log.outDebug(LogFilter.Lfg, "LFGScripts.OnRemoveMember [{0}]: remove [{1}] Method: {2} Kicker: {3} Reason: {4}", gguid, guid, method, kicker, reason); + + bool isLFG = group.isLFGGroup(); + + if (isLFG && method == RemoveMethod.Kick) // Player have been kicked + { + /// @todo - Update internal kick cooldown of kicker + string str_reason = ""; + if (!string.IsNullOrEmpty(reason)) + str_reason = reason; + Global.LFGMgr.InitBoot(gguid, kicker, guid, str_reason); + return; + } + + LfgState state = Global.LFGMgr.GetState(gguid); + + // If group is being formed after proposal success do nothing more + if (state == LfgState.Proposal && method == RemoveMethod.Default) + { + // LfgData: Remove player from group + Global.LFGMgr.SetGroup(guid, ObjectGuid.Empty); + Global.LFGMgr.RemovePlayerFromGroup(gguid, guid); + return; + } + + Global.LFGMgr.LeaveLfg(guid); + Global.LFGMgr.SetGroup(guid, ObjectGuid.Empty); + byte players = Global.LFGMgr.RemovePlayerFromGroup(gguid, guid); + + Player player = Global.ObjAccessor.FindPlayer(guid); + if (player) + { + if (method == RemoveMethod.Leave && state == LfgState.Dungeon && + players >= SharedConst.LFGKickVotesNeeded) + player.CastSpell(player, SharedConst.LFGSpellDungeonDeserter, true); + //else if (state == LFG_STATE_BOOT) + // Update internal kick cooldown of kicked + + player.GetSession().SendLfgUpdateStatus(new LfgUpdateData(LfgUpdateType.LeaderUnk1), true); + if (isLFG && player.GetMap().IsDungeon()) // Teleport player out the dungeon + Global.LFGMgr.TeleportPlayer(player, true); + } + + if (isLFG && state != LfgState.FinishedDungeon) // Need more players to finish the dungeon + { + Player leader = Global.ObjAccessor.FindPlayer(Global.LFGMgr.GetLeader(gguid)); + if (leader) + leader.GetSession().SendLfgOfferContinue(Global.LFGMgr.GetDungeon(gguid, false)); + } + } + + public override void OnDisband(Group group) + { + if (!Global.LFGMgr.isOptionEnabled(LfgOptions.EnableDungeonFinder | LfgOptions.EnableRaidBrowser)) + return; + + ObjectGuid gguid = group.GetGUID(); + Log.outDebug(LogFilter.Lfg, "LFGScripts.OnDisband {0}", gguid); + + Global.LFGMgr.RemoveGroupData(gguid); + } + + public override void OnChangeLeader(Group group, ObjectGuid newLeaderGuid, ObjectGuid oldLeaderGuid) + { + if (!Global.LFGMgr.isOptionEnabled(LfgOptions.EnableDungeonFinder | LfgOptions.EnableRaidBrowser)) + return; + + ObjectGuid gguid = group.GetGUID(); + + Log.outDebug(LogFilter.Lfg, "LFGScripts.OnChangeLeader {0}: old {0} new {0}", gguid, newLeaderGuid, oldLeaderGuid); + Global.LFGMgr.SetLeader(gguid, newLeaderGuid); + } + + public override void OnInviteMember(Group group, ObjectGuid guid) + { + if (!Global.LFGMgr.isOptionEnabled(LfgOptions.EnableDungeonFinder | LfgOptions.EnableRaidBrowser)) + return; + + ObjectGuid gguid = group.GetGUID(); + ObjectGuid leader = group.GetLeaderGUID(); + Log.outDebug(LogFilter.Lfg, "LFGScripts.OnInviteMember {0}: invite {0} leader {0}", gguid, guid, leader); + // No gguid == new group being formed + // No leader == after group creation first invite is new leader + // leader and no gguid == first invite after leader is added to new group (this is the real invite) + if (!leader.IsEmpty() && gguid.IsEmpty()) + Global.LFGMgr.LeaveLfg(leader); + } + } +} diff --git a/Game/Entities/AreaTrigger/AreaTrigger.cs b/Game/Entities/AreaTrigger/AreaTrigger.cs new file mode 100644 index 000000000..8d7cc0cac --- /dev/null +++ b/Game/Entities/AreaTrigger/AreaTrigger.cs @@ -0,0 +1,770 @@ +/* + * Copyright (C) 2012-2017 CypherCore + * + * 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, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +using Framework.Constants; +using Framework.GameMath; +using Game.AI; +using Game.Maps; +using Game.Movement; +using Game.Network.Packets; +using Game.Spells; +using System; +using System.Collections.Generic; + +namespace Game.Entities +{ + public class AreaTrigger : WorldObject + { + public AreaTrigger() : base(false) + { + _previousCheckOrientation = float.PositiveInfinity; + _reachedDestination = true; + + objectTypeMask |= TypeMask.AreaTrigger; + objectTypeId = TypeId.AreaTrigger; + + m_updateFlag = UpdateFlag.StationaryPosition | UpdateFlag.Areatrigger; + + valuesCount = (int)AreaTriggerFields.End; + + _spline = new Spline(); + } + + public override void AddToWorld() + { + // Register the AreaTrigger for guid lookup and for caster + if (!IsInWorld) + { + GetMap().GetObjectsStore().Add(GetGUID(), this); + base.AddToWorld(); + } + } + + public override void RemoveFromWorld() + { + // Remove the AreaTrigger from the accessor and from all lists of objects in world + if (IsInWorld) + { + _isRemoved = true; + + Unit caster = GetCaster(); + if (caster) + caster._UnregisterAreaTrigger(this); + + // Handle removal of all units, calling OnUnitExit & deleting auras if needed + HandleUnitEnterExit(new List()); + + _ai.OnRemove(); + + base.RemoveFromWorld(); + GetMap().GetObjectsStore().Remove(GetGUID()); + } + } + + public bool CreateAreaTrigger(uint spellMiscId, Unit caster, Unit target, SpellInfo spell, Position pos, int duration, uint spellXSpellVisualId, ObjectGuid castId = default(ObjectGuid), AuraEffect aurEff = null) + { + _targetGuid = target ? target.GetGUID() : ObjectGuid.Empty; + _aurEff = aurEff; + + SetMap(caster.GetMap()); + Relocate(pos); + if (!IsPositionValid()) + { + Log.outError(LogFilter.AreaTrigger, "AreaTrigger (spell {0}) not created. Invalid coordinates (X: {0} Y: {1})", spell.Id, GetPositionX(), GetPositionY()); + return false; + } + + _areaTriggerMiscTemplate = Global.AreaTriggerDataStorage.GetAreaTriggerMiscTemplate(spellMiscId); + if (_areaTriggerMiscTemplate == null) + { + Log.outError(LogFilter.AreaTrigger, "AreaTrigger (spellMiscId {0}) not created. Invalid areatrigger miscid ({1})", spellMiscId, spellMiscId); + return false; + } + + _Create(ObjectGuid.Create(HighGuid.AreaTrigger, GetMapId(), GetTemplate().Id, caster.GetMap().GenerateLowGuid(HighGuid.AreaTrigger))); + SetPhaseMask(caster.GetPhaseMask(), false); + + SetEntry(GetTemplate().Id); + SetDuration(duration); + + SetObjectScale(1.0f); + + SetGuidValue(AreaTriggerFields.Caster, caster.GetGUID()); + SetGuidValue(AreaTriggerFields.CreatingEffectGuid, castId); + + SetUInt32Value(AreaTriggerFields.SpellId, spell.Id); + SetUInt32Value(AreaTriggerFields.SpellForVisuals, spell.Id); + SetUInt32Value(AreaTriggerFields.SpellXSpellVisualId, spellXSpellVisualId); + SetUInt32Value(AreaTriggerFields.TimeToTargetScale, GetMiscTemplate().TimeToTargetScale != 0 ? GetMiscTemplate().TimeToTargetScale : GetUInt32Value(AreaTriggerFields.Duration)); + SetFloatValue(AreaTriggerFields.BoundsRadius2d, GetTemplate().MaxSearchRadius); + SetUInt32Value(AreaTriggerFields.DecalPropertiesId, GetMiscTemplate().DecalPropertiesId); + + for (byte scaleCurveIndex = 0; scaleCurveIndex < SharedConst.MaxAreatriggerScale; ++scaleCurveIndex) + if (GetMiscTemplate().ScaleInfo.ExtraScale[scaleCurveIndex].AsInt32 != 0) + SetUInt32Value(AreaTriggerFields.ExtraScaleCurve + scaleCurveIndex, (uint)GetMiscTemplate().ScaleInfo.ExtraScale[scaleCurveIndex].AsInt32); + + CopyPhaseFrom(caster); + + if (target && GetTemplate().HasFlag(AreaTriggerFlags.HasAttached)) + { + m_movementInfo.transport.guid = target.GetGUID(); + } + + UpdateShape(); + + if (GetMiscTemplate().HasSplines()) + { + uint timeToTarget = GetMiscTemplate().TimeToTarget != 0 ? GetMiscTemplate().TimeToTarget : GetUInt32Value(AreaTriggerFields.Duration); + InitSplineOffsets(GetMiscTemplate().SplinePoints, timeToTarget); + } + + // movement on transport of areatriggers on unit is handled by themself + Transport transport = m_movementInfo.transport.guid.IsEmpty() ? caster.GetTransport() : null; + if (transport) + { + float x, y, z, o; + pos.GetPosition(out x, out y, out z, out o); + transport.CalculatePassengerOffset(ref x, ref y, ref z, ref o); + m_movementInfo.transport.pos.Relocate(x, y, z, o); + + // This object must be added to transport before adding to map for the client to properly display it + transport.AddPassenger(this); + } + + AI_Initialize(); + + if (!GetMap().AddToMap(this)) + { // Returning false will cause the object to be deleted - remove from transport + if (transport) + transport.RemovePassenger(this); + return false; + } + + caster._RegisterAreaTrigger(this); + + _ai.OnCreate(); + + return true; + } + + public override void Update(uint diff) + { + base.Update(diff); + _timeSinceCreated += diff; + + if (GetTemplate().HasFlag(AreaTriggerFlags.HasAttached)) + { + Unit target = GetTarget(); + if (target) + GetMap().AreaTriggerRelocation(this, target.GetPositionX(), target.GetPositionY(), target.GetPositionZ(), target.GetOrientation()); + } + else + UpdateSplinePosition(diff); + + if (GetDuration() != -1) + { + if (GetDuration() > diff) + _UpdateDuration((int)(_duration - diff)); + else + { + Remove(); // expired + return; + } + } + + _ai.OnUpdate(diff); + + UpdateTargetList(); + } + + public void Remove() + { + if (IsInWorld) + AddObjectToRemoveList(); + } + + public void SetDuration(int newDuration) + { + _duration = newDuration; + _totalDuration = newDuration; + + // negative duration (permanent areatrigger) sent as 0 + SetUInt32Value(AreaTriggerFields.Duration, (uint)Math.Max(newDuration, 0)); + } + + void _UpdateDuration(int newDuration) + { + _duration = newDuration; + + // should be sent in object create packets only + UpdateData[(int)AreaTriggerFields.Duration] = _duration; + } + + float GetProgress() + { + return GetTimeSinceCreated() < GetTimeToTargetScale() ? GetTimeSinceCreated() / GetTimeToTargetScale() : 1.0f; + } + + void UpdateTargetList() + { + List targetList = new List(); + + switch (GetTemplate().TriggerType) + { + case AreaTriggerTypes.Sphere: + SearchUnitInSphere(targetList); + break; + case AreaTriggerTypes.Box: + SearchUnitInBox(targetList); + break; + case AreaTriggerTypes.Polygon: + SearchUnitInPolygon(targetList); + break; + case AreaTriggerTypes.Cylinder: + SearchUnitInCylinder(targetList); + break; + default: + break; + } + + HandleUnitEnterExit(targetList); + } + + void SearchUnitInSphere(List targetList) + { + float radius = GetTemplate().SphereDatas.Radius; + if (GetTemplate().HasFlag(AreaTriggerFlags.HasDynamicShape)) + { + if (GetMiscTemplate().MorphCurveId != 0) + { + radius = MathFunctions.lerp(GetTemplate().SphereDatas.Radius, GetTemplate().SphereDatas.RadiusTarget, Global.DB2Mgr.GetCurveValueAt(GetMiscTemplate().MorphCurveId, GetProgress())); + } + } + + var check = new AnyUnitInObjectRangeCheck(this, radius); + var searcher = new UnitListSearcher(this, targetList, check); + Cell.VisitAllObjects(this, searcher, GetTemplate().MaxSearchRadius); + } + + void SearchUnitInBox(List targetList) + { + float extentsX, extentsY, extentsZ; + + unsafe + { + fixed (float* ptr = GetTemplate().BoxDatas.Extents) + { + extentsX = ptr[0]; + extentsY = ptr[1]; + extentsZ = ptr[2]; + } + } + + var check = new AnyUnitInObjectRangeCheck(this, GetTemplate().MaxSearchRadius, false); + var searcher = new UnitListSearcher(this, targetList, check); + Cell.VisitAllObjects(this, searcher, GetTemplate().MaxSearchRadius); + + float halfExtentsX = extentsX / 2.0f; + float halfExtentsY = extentsY / 2.0f; + float halfExtentsZ = extentsZ / 2.0f; + + float minX = GetPositionX() - halfExtentsX; + float maxX = GetPositionX() + halfExtentsX; + + float minY = GetPositionY() - halfExtentsY; + float maxY = GetPositionY() + halfExtentsY; + + float minZ = GetPositionZ() - halfExtentsZ; + float maxZ = GetPositionZ() + halfExtentsZ; + + AxisAlignedBox box = new AxisAlignedBox(new Vector3(minX, minY, minZ), new Vector3(maxX, maxY, maxZ)); + + targetList.RemoveAll(unit => + { + return !box.contains(new Vector3(unit.GetPositionX(), unit.GetPositionY(), unit.GetPositionZ())); + }); + } + + void SearchUnitInPolygon(List targetList) + { + var check = new AnyUnitInObjectRangeCheck(this, GetTemplate().MaxSearchRadius, false); + var searcher = new UnitListSearcher(this, targetList, check); + Cell.VisitAllObjects(this, searcher, GetTemplate().MaxSearchRadius); + + float height = GetTemplate().PolygonDatas.Height; + float minZ = GetPositionZ() - height; + float maxZ = GetPositionZ() + height; + + targetList.RemoveAll(unit => + { + return !CheckIsInPolygon2D(unit) + || unit.GetPositionZ() < minZ + || unit.GetPositionZ() > maxZ; + }); + } + + void SearchUnitInCylinder(List targetList) + { + var check = new AnyUnitInObjectRangeCheck(this, GetTemplate().MaxSearchRadius, false); + var searcher = new UnitListSearcher(this, targetList, check); + Cell.VisitAllObjects(this, searcher, GetTemplate().MaxSearchRadius); + + float height = GetTemplate().CylinderDatas.Height; + float minZ = GetPositionZ() - height; + float maxZ = GetPositionZ() + height; + + targetList.RemoveAll(unit => + { + return unit.GetPositionZ() < minZ + || unit.GetPositionZ() > maxZ; + }); + } + + void HandleUnitEnterExit(List newTargetList) + { + List exitUnits = _insideUnits; + _insideUnits.Clear(); + + List enteringUnits = new List(); + + foreach (Unit unit in newTargetList) + { + if (!exitUnits.Remove(unit.GetGUID())) // erase(key_type) returns number of elements erased + enteringUnits.Add(unit); + + _insideUnits.Add(unit.GetGUID()); + } + + // Handle after _insideUnits have been reinserted so we can use GetInsideUnits() in hooks + foreach (Unit unit in enteringUnits) + { + Player player = unit.ToPlayer(); + if (player) + if (player.isDebugAreaTriggers) + player.SendSysMessage(CypherStrings.DebugAreatriggerEntered, GetTemplate().Id); + + DoActions(unit); + + _ai.OnUnitEnter(unit); + } + + foreach (ObjectGuid exitUnitGuid in exitUnits) + { + Unit leavingUnit = Global.ObjAccessor.GetUnit(this, exitUnitGuid); + if (leavingUnit) + { + Player player = leavingUnit.ToPlayer(); + if (player) + if (player.isDebugAreaTriggers) + player.SendSysMessage(CypherStrings.DebugAreatriggerLeft, GetTemplate().Id); + + UndoActions(leavingUnit); + + _ai.OnUnitExit(leavingUnit); + } + } + } + + public AreaTriggerTemplate GetTemplate() + { + return _areaTriggerMiscTemplate.Template; + } + + public uint GetScriptId() + { + return GetTemplate().ScriptId; + } + + public Unit GetCaster() + { + return Global.ObjAccessor.GetUnit(this, GetCasterGuid()); + } + + Unit GetTarget() + { + return Global.ObjAccessor.GetUnit(this, _targetGuid); + } + + void UpdatePolygonOrientation() + { + float newOrientation = GetOrientation(); + + // No need to recalculate, orientation didn't change + if (MathFunctions.fuzzyEq(_previousCheckOrientation, newOrientation)) + return; + + _polygonVertices = GetTemplate().PolygonVertices; + + float angleSin = (float)Math.Sin(newOrientation); + float angleCos = (float)Math.Cos(newOrientation); + + // This is needed to rotate the vertices, following orientation + for (var i = 0; i < _polygonVertices.Count; ++i) + { + Vector2 vertice = _polygonVertices[i]; + + vertice.X = vertice.X * angleCos - vertice.Y * angleSin; + vertice.Y = vertice.Y * angleCos + vertice.X * angleSin; + } + + _previousCheckOrientation = newOrientation; + } + + bool CheckIsInPolygon2D(Position pos) + { + float testX = pos.GetPositionX(); + float testY = pos.GetPositionY(); + + //this method uses the ray tracing algorithm to determine if the point is in the polygon + bool locatedInPolygon = false; + + for (int vertex = 0; vertex < _polygonVertices.Count; ++vertex) + { + int nextVertex; + + //repeat loop for all sets of points + if (vertex == (_polygonVertices.Count - 1)) + { + //if i is the last vertex, let j be the first vertex + nextVertex = 0; + } + else + { + //for all-else, let j=(i+1)th vertex + nextVertex = vertex + 1; + } + + float vertX_i = GetPositionX() + _polygonVertices[vertex].X; + float vertY_i = GetPositionY() + _polygonVertices[vertex].Y; + float vertX_j = GetPositionX() + _polygonVertices[nextVertex].X; + float vertY_j = GetPositionY() + _polygonVertices[nextVertex].Y; + + // following statement checks if testPoint.Y is below Y-coord of i-th vertex + bool belowLowY = vertY_i > testY; + // following statement checks if testPoint.Y is below Y-coord of i+1-th vertex + bool belowHighY = vertY_j > testY; + + /* following statement is true if testPoint.Y satisfies either (only one is possible) + -.(i).Y < testPoint.Y < (i+1).Y OR + -.(i).Y > testPoint.Y > (i+1).Y + + (Note) + Both of the conditions indicate that a point is located within the edges of the Y-th coordinate + of the (i)-th and the (i+1)- th vertices of the polygon. If neither of the above + conditions is satisfied, then it is assured that a semi-infinite horizontal line draw + to the right from the testpoint will NOT cross the line that connects vertices i and i+1 + of the polygon + */ + bool withinYsEdges = belowLowY != belowHighY; + + if (withinYsEdges) + { + // this is the slope of the line that connects vertices i and i+1 of the polygon + float slopeOfLine = (vertX_j - vertX_i) / (vertY_j - vertY_i); + + // this looks up the x-coord of a point lying on the above line, given its y-coord + float pointOnLine = (slopeOfLine * (testY - vertY_i)) + vertX_i; + + //checks to see if x-coord of testPoint is smaller than the point on the line with the same y-coord + bool isLeftToLine = testX < pointOnLine; + + if (isLeftToLine) + { + //this statement changes true to false (and vice-versa) + locatedInPolygon = !locatedInPolygon; + }//end if (isLeftToLine) + }//end if (withinYsEdges + } + + return locatedInPolygon; + } + + public void UpdateShape() + { + if (GetTemplate().IsPolygon()) + UpdatePolygonOrientation(); + } + + bool UnitFitToActionRequirement(Unit unit, Unit caster, AreaTriggerActionUserTypes targetType) + { + switch (targetType) + { + case AreaTriggerActionUserTypes.Friend: + return caster.IsFriendlyTo(unit); + case AreaTriggerActionUserTypes.Enemy: + return !caster.IsFriendlyTo(unit); + case AreaTriggerActionUserTypes.Raid: + return caster.IsInRaidWith(unit); + case AreaTriggerActionUserTypes.Party: + return caster.IsInPartyWith(unit); + case AreaTriggerActionUserTypes.Caster: + return unit.GetGUID() == caster.GetGUID(); + case AreaTriggerActionUserTypes.Any: + default: + break; + } + + return true; + } + + void DoActions(Unit unit) + { + Unit caster = GetCaster(); + if (caster) + { + foreach (AreaTriggerAction action in GetTemplate().Actions) + { + if (UnitFitToActionRequirement(unit, caster, action.TargetType)) + { + switch (action.ActionType) + { + case AreaTriggerActionTypes.Cast: + caster.CastSpell(unit, action.Param, true); + break; + case AreaTriggerActionTypes.AddAura: + caster.AddAura(action.Param, unit); + break; + default: + break; + } + } + } + } + } + + void UndoActions(Unit unit) + { + foreach (AreaTriggerAction action in GetTemplate().Actions) + { + if (action.ActionType == AreaTriggerActionTypes.Cast || action.ActionType == AreaTriggerActionTypes.AddAura) + unit.RemoveAurasDueToSpell(action.Param, GetCasterGuid()); + } + } + + void InitSplineOffsets(List offsets, uint timeToTarget) + { + float angleSin = (float)Math.Sin(GetOrientation()); + float angleCos = (float)Math.Cos(GetOrientation()); + + // This is needed to rotate the spline, following caster orientation + List rotatedPoints = new List(); + for (var i = 0; i < offsets.Count; ++i) + { + Vector3 offset = offsets[i]; + float tempX = offset.X; + float tempY = offset.Y; + float tempZ = GetPositionZ(); + + offset.X = (tempX * angleCos - tempY * angleSin) + GetPositionX(); + offset.Y = (tempX * angleSin + tempY * angleCos) + GetPositionY(); + UpdateAllowedPositionZ(offset.X, offset.Y, ref tempZ); + offset.Z += tempZ; + + float x = GetPositionX() + (offset.X * angleCos - offset.Y * angleSin); + float y = GetPositionY() + (offset.Y * angleCos + offset.X * angleSin); + float z = GetPositionZ(); + + UpdateAllowedPositionZ(x, y, ref z); + z += offset.Z; + + rotatedPoints.Add(new Vector3(x, y, z)); + } + + InitSplines(rotatedPoints, timeToTarget); + } + + void InitSplines(List splinePoints, uint timeToTarget) + { + if (splinePoints.Count < 2) + return; + + _movementTime = 0; + + _spline.Init_Spline(splinePoints.ToArray(), splinePoints.Count, Spline.EvaluationMode.Linear); + _spline.initLengths(); + + // should be sent in object create packets only + UpdateData[(int)AreaTriggerFields.TimeToTarget] = timeToTarget; + + if (IsInWorld) + { + if (_reachedDestination) + { + AreaTriggerReShape reshapeDest = new AreaTriggerReShape(); + reshapeDest.TriggerGUID = GetGUID(); + SendMessageToSet(reshapeDest, true); + } + + AreaTriggerReShape reshape = new AreaTriggerReShape(); + reshape.TriggerGUID = GetGUID(); + reshape.AreaTriggerSpline.HasValue = true; + reshape.AreaTriggerSpline.Value.ElapsedTimeForMovement = GetElapsedTimeForMovement(); + reshape.AreaTriggerSpline.Value.TimeToTarget = timeToTarget; + reshape.AreaTriggerSpline.Value.Points = splinePoints; + SendMessageToSet(reshape, true); + } + + _reachedDestination = false; + } + + void UpdateSplinePosition(uint diff) + { + if (_reachedDestination) + return; + + if (!HasSplines()) + return; + + _movementTime += diff; + + if (_movementTime >= GetTimeToTarget()) + { + _reachedDestination = true; + _lastSplineIndex = _spline.last(); + + Vector3 lastSplinePosition = _spline.getPoint(_lastSplineIndex); + GetMap().AreaTriggerRelocation(this, lastSplinePosition.X, lastSplinePosition.Y, lastSplinePosition.Z, GetOrientation()); + + DebugVisualizePosition(); + + _ai.OnSplineIndexReached(_lastSplineIndex); + _ai.OnDestinationReached(); + return; + } + + float currentTimePercent = _movementTime / GetTimeToTarget(); + + if (currentTimePercent <= 0.0f) + return; + + if (GetMiscTemplate().MoveCurveId != 0) + { + float progress = Global.DB2Mgr.GetCurveValueAt(GetMiscTemplate().MoveCurveId, currentTimePercent); + if (progress < 0.0f || progress > 1.0f) + { + Log.outError(LogFilter.AreaTrigger, "AreaTrigger (Id: {0}, SpellMiscId: {1}) has wrong progress ({2}) caused by curve calculation (MoveCurveId: {3})", + GetTemplate().Id, GetMiscTemplate().MiscId, progress, GetMiscTemplate().MorphCurveId); + } + else + currentTimePercent = progress; + } + + int lastPositionIndex = 0; + float percentFromLastPoint = 0; + _spline.computeIndex(currentTimePercent, ref lastPositionIndex, ref percentFromLastPoint); + + Vector3 currentPosition; + _spline.Evaluate_Percent(lastPositionIndex, percentFromLastPoint, out currentPosition); + + float orientation = GetOrientation(); + if (GetTemplate().HasFlag(AreaTriggerFlags.HasFaceMovementDir)) + { + Vector3 nextPoint = _spline.getPoint(lastPositionIndex + 1); + orientation = GetAngle(nextPoint.X, nextPoint.Y); + } + + GetMap().AreaTriggerRelocation(this, currentPosition.X, currentPosition.Y, currentPosition.Z, orientation); + + DebugVisualizePosition(); + + if (_lastSplineIndex != lastPositionIndex) + { + _lastSplineIndex = lastPositionIndex; + _ai.OnSplineIndexReached(_lastSplineIndex); + } + } + + void AI_Initialize() + { + AI_Destroy(); + AreaTriggerAI ai = Global.ScriptMgr.GetAreaTriggerAI(this); + if (ai == null) + ai = new NullAreaTriggerAI(this); + + _ai = ai; + _ai.OnInitialize(); + } + + void AI_Destroy() + { + _ai = null; + } + + AreaTriggerAI GetAI() { return _ai; } + + [System.Diagnostics.Conditional("DEBUG")] + void DebugVisualizePosition() + { + Unit caster = GetCaster(); + if (caster) + { + Player player = caster.ToPlayer(); + if (player) + if (player.isDebugAreaTriggers) + player.SummonCreature(1, this, TempSummonType.TimedDespawn, GetTimeToTarget()); + } + } + + public bool IsRemoved() { return _isRemoved; } + public uint GetSpellId() { return GetUInt32Value(AreaTriggerFields.SpellId); } + public AuraEffect GetAuraEffect() { return _aurEff; } + public uint GetTimeSinceCreated() { return _timeSinceCreated; } + public uint GetTimeToTarget() { return GetUInt32Value(AreaTriggerFields.TimeToTarget); } + public uint GetTimeToTargetScale() { return GetUInt32Value(AreaTriggerFields.TimeToTargetScale); } + public int GetDuration() { return _duration; } + public int GetTotalDuration() { return _totalDuration; } + + public void Delay(int delaytime) { SetDuration(GetDuration() - delaytime); } + + public List GetInsideUnits() { return _insideUnits; } + + public AreaTriggerMiscTemplate GetMiscTemplate() { return _areaTriggerMiscTemplate; } + + public ObjectGuid GetCasterGuid() { return GetGuidValue(AreaTriggerFields.Caster); } + + public Vector3 GetRollPitchYaw() { return _rollPitchYaw; } + public Vector3 GetTargetRollPitchYaw() { return _targetRollPitchYaw; } + + public bool HasSplines() { return !_spline.empty(); } + public Spline GetSpline() { return _spline; } + public uint GetElapsedTimeForMovement() { return GetTimeSinceCreated(); } /// @todo: research the right value, in sniffs both timers are nearly identical + + ObjectGuid _targetGuid; + + AuraEffect _aurEff; + + int _duration; + int _totalDuration; + uint _timeSinceCreated; + float _previousCheckOrientation; + bool _isRemoved; + + Vector3 _rollPitchYaw; + Vector3 _targetRollPitchYaw; + List _polygonVertices; + Spline _spline; + + bool _reachedDestination; + int _lastSplineIndex; + uint _movementTime; + + AreaTriggerMiscTemplate _areaTriggerMiscTemplate; + List _insideUnits = new List(); + + AreaTriggerAI _ai; + } +} diff --git a/Game/Entities/AreaTrigger/AreaTriggerTemplate.cs b/Game/Entities/AreaTrigger/AreaTriggerTemplate.cs new file mode 100644 index 000000000..d71574e1d --- /dev/null +++ b/Game/Entities/AreaTrigger/AreaTriggerTemplate.cs @@ -0,0 +1,211 @@ +/* + * Copyright (C) 2012-2017 CypherCore + * + * 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, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +using Framework.Constants; +using Framework.GameMath; +using System; +using System.Collections.Generic; +using System.Runtime.InteropServices; + +namespace Game.Entities +{ + [StructLayout(LayoutKind.Explicit)] + public unsafe class AreaTriggerData + { + [FieldOffset(0)] + public defaultdatas DefaultDatas; + + [FieldOffset(0)] + public spheredatas SphereDatas; + + [FieldOffset(0)] + public boxdatas BoxDatas; + + [FieldOffset(0)] + public polygondatas PolygonDatas; + + [FieldOffset(0)] + public cylinderdatas CylinderDatas; + + public struct defaultdatas + { + public fixed float Data[SharedConst.MaxAreatriggerEntityData]; + } + + // AREATRIGGER_TYPE_SPHERE + public struct spheredatas + { + public float Radius; + public float RadiusTarget; + } + + // AREATRIGGER_TYPE_BOX + public struct boxdatas + { + public fixed float Extents[3]; + public fixed float ExtentsTarget[3]; + } + + // AREATRIGGER_TYPE_POLYGON + public struct polygondatas + { + public float Height; + public float HeightTarget; + } + + // AREATRIGGER_TYPE_CYLINDER + public struct cylinderdatas + { + public float Radius; + public float RadiusTarget; + public float Height; + public float HeightTarget; + public float LocationZOffset; + public float LocationZOffsetTarget; + } + } + + public class AreaTriggerScaleInfo + { + public AreaTriggerScaleInfo() + { + OverrideScale = new OverrideScaleStruct[SharedConst.MaxAreatriggerScale]; + ExtraScale = new ExtraScaleStruct[SharedConst.MaxAreatriggerScale]; + + ExtraScale[5].AsFloat = 1.0000001f; + ExtraScale[6].AsInt32 = 1; + } + + public OverrideScaleStruct[] OverrideScale; + public ExtraScaleStruct[] ExtraScale; + + [StructLayout(LayoutKind.Explicit)] + public struct OverrideScaleStruct + { + [FieldOffset(0)] + public int AsInt32; + + [FieldOffset(0)] + public float AsFloat; + } + + [StructLayout(LayoutKind.Explicit)] + public struct ExtraScaleStruct + { + [FieldOffset(0)] + public int AsInt32; + + [FieldOffset(0)] + public float AsFloat; + } + } + + public class AreaTriggerTemplate : AreaTriggerData + { + public unsafe void InitMaxSearchRadius() + { + switch (TriggerType) + { + case AreaTriggerTypes.Sphere: + { + MaxSearchRadius = Math.Max(SphereDatas.Radius, SphereDatas.RadiusTarget); + break; + } + case AreaTriggerTypes.Box: + { + unsafe + { + fixed (float* ptr = BoxDatas.Extents) + { + MaxSearchRadius = (float)Math.Sqrt(ptr[0] * ptr[0] / 4 + ptr[1] * ptr[1] / 4); + } + } + break; + } + case AreaTriggerTypes.Polygon: + { + if (PolygonDatas.Height <= 0.0f) + PolygonDatas.Height = 1.0f; + + foreach (Vector2 vertice in PolygonVertices) + { + float pointDist = vertice.GetLength(); + + if (pointDist > MaxSearchRadius) + MaxSearchRadius = pointDist; + } + + break; + } + case AreaTriggerTypes.Cylinder: + { + MaxSearchRadius = CylinderDatas.Radius; + break; + } + default: + break; + } + } + + public bool HasFlag(AreaTriggerFlags flag) { return Flags.HasAnyFlag(flag); } + + public bool IsSphere() { return TriggerType == AreaTriggerTypes.Sphere; } + public bool IsBox() { return TriggerType == AreaTriggerTypes.Box; } + public bool IsPolygon() { return TriggerType == AreaTriggerTypes.Polygon; } + public bool IsCylinder() { return TriggerType == AreaTriggerTypes.Cylinder; } + + public uint Id; + public AreaTriggerTypes TriggerType; + public AreaTriggerFlags Flags; + public uint ScriptId; + public float MaxSearchRadius; + + public List PolygonVertices = new List(); + public List PolygonVerticesTarget = new List(); + public List Actions = new List(); + } + + public class AreaTriggerMiscTemplate + { + public bool HasSplines() { return SplinePoints.Count >= 2; } + + public uint MiscId; + public uint AreaTriggerEntry; + + public uint MoveCurveId; + public uint ScaleCurveId; + public uint MorphCurveId; + public uint FacingCurveId; + + public uint DecalPropertiesId; + + public uint TimeToTarget; + public uint TimeToTargetScale; + + public AreaTriggerScaleInfo ScaleInfo = new AreaTriggerScaleInfo(); + + public AreaTriggerTemplate Template; + public List SplinePoints = new List(); + } + + public struct AreaTriggerAction + { + public uint Param; + public AreaTriggerActionTypes ActionType; + public AreaTriggerActionUserTypes TargetType; + } +} diff --git a/Game/Entities/Conversation.cs b/Game/Entities/Conversation.cs new file mode 100644 index 000000000..5726212c9 --- /dev/null +++ b/Game/Entities/Conversation.cs @@ -0,0 +1,189 @@ +/* + * Copyright (C) 2012-2017 CypherCore + * + * 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, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +using Framework.Constants; +using Game.DataStorage; +using Game.Maps; +using Game.Spells; +using System.Collections.Generic; + +namespace Game.Entities +{ + public class Conversation : WorldObject + { + public Conversation() : base(false) + { + _duration = 0; + + objectTypeMask |= TypeMask.Conversation; + objectTypeId = TypeId.Conversation; + + m_updateFlag = UpdateFlag.StationaryPosition; + + valuesCount = (int)ConversationFields.End; + _dynamicValuesCount = (int)ConversationDynamicFields.End; + } + + public override void AddToWorld() + { + ///- Register the Conversation for guid lookup and for caster + if (!IsInWorld) + { + GetMap().GetObjectsStore().Add(GetGUID(), this); + base.AddToWorld(); + } + } + + public override void RemoveFromWorld() + { + ///- Remove the Conversation from the accessor and from all lists of objects in world + if (IsInWorld) + { + base.RemoveFromWorld(); + GetMap().GetObjectsStore().Remove(GetGUID()); + } + } + + public override bool IsNeverVisibleFor(WorldObject seer) + { + if (!_participants.Contains(seer.GetGUID())) + return true; + + return base.IsNeverVisibleFor(seer); + } + + public override void Update(uint diff) + { + if (GetDuration() > diff) + _duration -= diff; + else + Remove(); // expired + + base.Update(diff); + } + + void Remove() + { + if (IsInWorld) + { + AddObjectToRemoveList(); // calls RemoveFromWorld + } + } + + public static Conversation CreateConversation(uint conversationEntry, Unit creator, Position pos, List participants, SpellInfo spellInfo = null) + { + ConversationTemplate conversationTemplate = Global.ConversationDataStorage.GetConversationTemplate(conversationEntry); + if (conversationTemplate == null) + return null; + + ulong lowGuid = creator.GetMap().GenerateLowGuid(HighGuid.Conversation); + + Conversation conversation = new Conversation(); + if (!conversation.Create(lowGuid, conversationEntry, creator.GetMap(), creator, pos, participants, spellInfo)) + return null; + + return conversation; + } + + bool Create(ulong lowGuid, uint conversationEntry, Map map, Unit creator, Position pos, List participants, SpellInfo spellInfo = null) + { + ConversationTemplate conversationTemplate = Global.ConversationDataStorage.GetConversationTemplate(conversationEntry); + //ASSERT(conversationTemplate); + + _creatorGuid = creator.GetGUID(); + _participants = participants; + + SetMap(map); + Relocate(pos); + + base._Create(ObjectGuid.Create(HighGuid.Conversation, GetMapId(), conversationEntry, lowGuid)); + SetPhaseMask(creator.GetPhaseMask(), false); + CopyPhaseFrom(creator); + + SetEntry(conversationEntry); + SetObjectScale(1.0f); + + SetUInt32Value(ConversationFields.LastLineEndTime, conversationTemplate.LastLineEndTime); + _duration = conversationTemplate.LastLineEndTime; + + ushort actorsIndex = 0; + foreach (ConversationActorTemplate actor in conversationTemplate.Actors) + { + if (actor != null) + { + ConversationDynamicFieldActor actorField = new ConversationDynamicFieldActor(); + actorField.ActorTemplate = actor; + actorField.Type = ConversationDynamicFieldActor.ActorType.CreatureActor; + SetDynamicStructuredValue(ConversationDynamicFields.Actors, actorsIndex++, actorField); + } + else + ++actorsIndex; + } + + ushort linesIndex = 0; + foreach (ConversationLineTemplate line in conversationTemplate.Lines) + SetDynamicStructuredValue(ConversationDynamicFields.Lines, linesIndex++, line); + + if (!GetMap().AddToMap(this)) + return false; + + return true; + } + + void AddActor(ObjectGuid actorGuid, ushort actorIdx) + { + ConversationDynamicFieldActor actorField = new ConversationDynamicFieldActor(); + actorField.ActorGuid = actorGuid; + actorField.Type = ConversationDynamicFieldActor.ActorType.WorldObjectActor; + SetDynamicStructuredValue(ConversationDynamicFields.Actors, actorIdx, actorField); + } + + void AddParticipant(ObjectGuid participantGuid) + { + _participants.Add(participantGuid); + } + + uint GetDuration() { return _duration; } + + public ObjectGuid GetCreatorGuid() { return _creatorGuid; } + + public override float GetStationaryX() { return _stationaryPosition.GetPositionX(); } + public override float GetStationaryY() { return _stationaryPosition.GetPositionY(); } + public override float GetStationaryZ() { return _stationaryPosition.GetPositionZ(); } + public override float GetStationaryO() { return _stationaryPosition.GetOrientation(); } + void RelocateStationaryPosition(Position pos) { _stationaryPosition.Relocate(pos); } + + Position _stationaryPosition = new Position(); + ObjectGuid _creatorGuid; + uint _duration; + List _participants = new List(); + } + + class ConversationDynamicFieldActor + { + public enum ActorType + { + WorldObjectActor = 0, + CreatureActor = 1 + } + + public ObjectGuid ActorGuid; + public ConversationActorTemplate ActorTemplate; + + public ActorType Type; + } +} diff --git a/Game/Entities/Corpse.cs b/Game/Entities/Corpse.cs new file mode 100644 index 000000000..8e129d175 --- /dev/null +++ b/Game/Entities/Corpse.cs @@ -0,0 +1,218 @@ +/* + * Copyright (C) 2012-2017 CypherCore + * + * 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, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +using Framework.Constants; +using Framework.Database; +using Game.DataStorage; +using Game.Loots; +using Game.Maps; +using System.Collections.Generic; +using System.Diagnostics.Contracts; + +namespace Game.Entities +{ + public class Corpse : WorldObject + { + public Corpse(CorpseType type = CorpseType.Bones) : base(type != CorpseType.Bones) + { + m_type = type; + objectTypeId = TypeId.Corpse; + objectTypeMask |= TypeMask.Corpse; + + m_updateFlag = UpdateFlag.StationaryPosition; + + valuesCount = (int)CorpseFields.End; + + m_time = Time.UnixTime; + } + + public override void AddToWorld() + { + // Register the corpse for guid lookup + if (!IsInWorld) + GetMap().GetObjectsStore().Add(GetGUID(), this); + + base.AddToWorld(); + } + + public override void RemoveFromWorld() + { + // Remove the corpse from the accessor + if (IsInWorld) + GetMap().GetObjectsStore().Remove(GetGUID()); + + base.RemoveFromWorld(); + } + + public bool Create(ulong guidlow, Map map) + { + _Create(ObjectGuid.Create(HighGuid.Corpse, map.GetId(), 0, guidlow)); + return true; + } + + public bool Create(ulong guidlow, Player owner) + { + Contract.Assert(owner != null); + + Relocate(owner.GetPositionX(), owner.GetPositionY(), owner.GetPositionZ(), owner.GetOrientation()); + + if (!IsPositionValid()) + { + Log.outError(LogFilter.Player, "Corpse (guidlow {0}, owner {1}) not created. Suggested coordinates isn't valid (X: {2} Y: {3})", + guidlow, owner.GetName(), owner.GetPositionX(), owner.GetPositionY()); + return false; + } + + _Create(ObjectGuid.Create(HighGuid.Corpse, owner.GetMapId(), 0, guidlow)); + SetPhaseMask(owner.GetPhaseMask(), false); + + SetObjectScale(1); + SetGuidValue(CorpseFields.Owner, owner.GetGUID()); + + _cellCoord = GridDefines.ComputeCellCoord(GetPositionX(), GetPositionY()); + + CopyPhaseFrom(owner); + + return true; + } + + public void SaveToDB() + { + // prevent DB data inconsistence problems and duplicates + SQLTransaction trans = new SQLTransaction(); + DeleteFromDB(trans); + + byte index = 0; + PreparedStatement stmt = DB.Characters.GetPreparedStatement(CharStatements.INS_CORPSE); + stmt.AddValue(index++, GetOwnerGUID().GetCounter()); // guid + stmt.AddValue(index++, GetPositionX()); // posX + stmt.AddValue(index++, GetPositionY()); // posY + stmt.AddValue(index++, GetPositionZ()); // posZ + stmt.AddValue(index++, GetOrientation()); // orientation + stmt.AddValue(index++, GetMapId()); // mapId + stmt.AddValue(index++, GetUInt32Value(CorpseFields.DisplayId)); // displayId + stmt.AddValue(index++, _ConcatFields(CorpseFields.Item, EquipmentSlot.End)); // itemCache + stmt.AddValue(index++, GetUInt32Value(CorpseFields.Bytes1)); // bytes1 + stmt.AddValue(index++, GetUInt32Value(CorpseFields.Bytes2)); // bytes2 + stmt.AddValue(index++, GetUInt32Value(CorpseFields.Flags)); // flags + stmt.AddValue(index++, GetUInt32Value(CorpseFields.DynamicFlags)); // dynFlags + stmt.AddValue(index++, (uint)m_time); // time + stmt.AddValue(index++, (uint)GetCorpseType()); // corpseType + stmt.AddValue(index++, GetInstanceId()); // instanceId + trans.Append(stmt); + + foreach (uint phaseId in GetPhases()) + { + index = 0; + stmt = DB.Characters.GetPreparedStatement(CharStatements.INS_CORPSE_PHASES); + stmt.AddValue(index++, GetOwnerGUID().GetCounter()); // OwnerGuid + stmt.AddValue(index++, phaseId); // PhaseId + trans.Append(stmt); + } + } + + public void DeleteFromDB(SQLTransaction trans) + { + DeleteFromDB(GetOwnerGUID(), trans); + } + + public static void DeleteFromDB(ObjectGuid ownerGuid, SQLTransaction trans) + { + PreparedStatement stmt = DB.Characters.GetPreparedStatement(CharStatements.DEL_CORPSE); + stmt.AddValue(0, ownerGuid.GetCounter()); + DB.Characters.ExecuteOrAppend(trans, stmt); + + stmt = DB.Characters.GetPreparedStatement(CharStatements.DEL_CORPSE_PHASES); + stmt.AddValue(0, ownerGuid.GetCounter()); + DB.Characters.ExecuteOrAppend(trans, stmt); + } + + public bool LoadCorpseFromDB(ulong guid, SQLFields field) + { + // 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 + // SELECT posX, posY, posZ, orientation, mapId, displayId, itemCache, bytes1, bytes2, flags, dynFlags, time, corpseType, instanceId, guid FROM corpse WHERE mapId = ? AND instanceId = ? + + float posX = field.Read(0); + float posY = field.Read(1); + float posZ = field.Read(2); + float o = field.Read(3); + ushort mapId = field.Read(4); + + _Create(ObjectGuid.Create(HighGuid.Corpse, mapId, 0, guid)); + + SetObjectScale(1.0f); + SetUInt32Value(CorpseFields.DisplayId, field.Read(5)); + _LoadIntoDataField(field.Read(6), (int)CorpseFields.Item, EquipmentSlot.End); + SetUInt32Value(CorpseFields.Bytes1, field.Read(7)); + SetUInt32Value(CorpseFields.Bytes2, field.Read(8)); + SetUInt32Value(CorpseFields.Flags, field.Read(9)); + SetUInt32Value(CorpseFields.DynamicFlags, field.Read(10)); + SetGuidValue(CorpseFields.Owner, ObjectGuid.Create(HighGuid.Player, field.Read(14))); + CharacterInfo characterInfo = Global.WorldMgr.GetCharacterInfo(GetGuidValue(CorpseFields.Owner)); + if (characterInfo != null) + SetUInt32Value(CorpseFields.FactionTemplate, CliDB.ChrRacesStorage.LookupByKey(characterInfo.RaceID).FactionID); + + m_time = field.Read(11); + + uint instanceId = field.Read(13); + + // place + SetLocationInstanceId(instanceId); + SetMapId(mapId); + Relocate(posX, posY, posZ, o); + + if (!IsPositionValid()) + { + Log.outError(LogFilter.Player, "Corpse ({0}, owner: {1}) is not created, given coordinates are not valid (X: {2}, Y: {3}, Z: {4})", + GetGUID().ToString(), GetOwnerGUID().ToString(), posX, posY, posZ); + return false; + } + + _cellCoord = GridDefines.ComputeCellCoord(GetPositionX(), GetPositionY()); + return true; + } + + public bool IsExpired(long t) + { + // Deleted character + if (Global.WorldMgr.GetCharacterInfo(GetOwnerGUID()) == null) + return true; + + if (m_type == CorpseType.Bones) + return m_time < t - 60 * Time.Minute; + else + return m_time < t - 3 * Time.Day; + } + + public ObjectGuid GetOwnerGUID() { return GetGuidValue(CorpseFields.Owner); } + + public long GetGhostTime() { return m_time; } + public void ResetGhostTime() { m_time = Time.UnixTime; } + public CorpseType GetCorpseType() { return m_type; } + + public CellCoord GetCellCoord() { return _cellCoord; } + public void SetCellCoord(CellCoord cellCoord) { _cellCoord = cellCoord; } + + public Loot loot = new Loot(); + public Player lootRecipient; + public bool lootForBody; + + CorpseType m_type; + long m_time; + CellCoord _cellCoord; // gride for corpse position for fast search + } +} diff --git a/Game/Entities/Creature/Creature.Fields.cs b/Game/Entities/Creature/Creature.Fields.cs new file mode 100644 index 000000000..b2f732124 --- /dev/null +++ b/Game/Entities/Creature/Creature.Fields.cs @@ -0,0 +1,106 @@ +/* + * Copyright (C) 2012-2017 CypherCore + * + * 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, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +using Framework.Constants; +using Framework.Dynamic; +using Game.Loots; +using Game.Spells; +using System; +using System.Collections.Generic; + +namespace Game.Entities +{ + public partial class Creature + { + CreatureTemplate m_creatureInfo; + CreatureData m_creatureData; + + Spell _focusSpell; // Locks the target during spell cast for proper facing + uint _focusDelay; + bool m_shouldReacquireTarget; + ObjectGuid m_suppressedTarget; // Stores the creature's "real" target while casting + float m_suppressedOrientation; // Stores the creature's "real" orientation while casting + + MultiMap m_textRepeat = new MultiMap(); + + public ulong m_PlayerDamageReq; + public float m_SightDistance; + public float m_CombatDistance; + public bool m_isTempWorldObject; //true when possessed + + ReactStates reactState; // for AI, not charmInfo + public MovementGeneratorType m_defaultMovementType { get; set; } + public ulong m_spawnId; + byte m_equipmentId; + sbyte m_originalEquipmentId; // can be -1 + + bool m_AlreadyCallAssistance; + bool m_AlreadySearchedAssistance; + bool m_regenHealth; + bool m_cannotReachTarget; + uint m_cannotReachTimer; + bool m_AI_locked; + + SpellSchoolMask m_meleeDamageSchoolMask; + public uint m_originalEntry; + + Position m_homePosition; + Position m_transportHomePosition = new Position(); + + bool DisableReputationGain; + + LootModes m_LootMode; // Bitmask (default: LOOT_MODE_DEFAULT) that determines what loot will be lootable + + //WaypointMovementGenerator vars + uint m_waypointID; + uint m_path_id; + + //Formation var + CreatureGroup m_formation; + bool TriggerJustRespawned; + + public uint[] m_spells = new uint[SharedConst.MaxCreatureSpells]; + + // Timers + long _pickpocketLootRestore; + public long m_corpseRemoveTime; // (msecs)timer for death or corpse disappearance + long m_respawnTime; // (secs) time of next respawn + uint m_respawnDelay; // (secs) delay between corpse disappearance and respawning + uint m_corpseDelay; // (secs) delay between death and corpse disappearance + float m_respawnradius; + uint m_boundaryCheckTime; // (msecs) remaining time for next evade boundary check + uint m_combatPulseTime; // (msecs) remaining time for next zone-in-combat pulse + uint m_combatPulseDelay; // (secs) how often the creature puts the entire zone in combat (only works in dungeons) + + // vendor items + List m_vendorItemCounts = new List(); + + public Loot loot = new Loot(); + public uint m_groupLootTimer; // (msecs)timer used for group loot + public ObjectGuid lootingGroupLowGUID; // used to find group which is looting corpse + ObjectGuid m_lootRecipient; + ObjectGuid m_lootRecipientGroup; + ObjectGuid _skinner; + } + + public enum ObjectCellMoveState + { + None, // not in move list + Active, // in move list + Inactive // in move list but should not move + } +} diff --git a/Game/Entities/Creature/Creature.cs b/Game/Entities/Creature/Creature.cs new file mode 100644 index 000000000..51e606317 --- /dev/null +++ b/Game/Entities/Creature/Creature.cs @@ -0,0 +1,3102 @@ +/* + * Copyright (C) 2012-2017 CypherCore + * + * 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, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +using Framework.Constants; +using Framework.Database; +using Framework.Dynamic; +using Game.AI; +using Game.DataStorage; +using Game.Groups; +using Game.Loots; +using Game.Maps; +using Game.Network.Packets; +using Game.Spells; +using System; +using System.Collections.Generic; +using System.Diagnostics.Contracts; +using System.Linq; + +namespace Game.Entities +{ + public partial class Creature : Unit + { + public Creature() : this(false) { } + + public Creature(bool worldObject) : base(worldObject) + { + m_respawnDelay = 300; + m_corpseDelay = 60; + m_boundaryCheckTime = 2500; + reactState = ReactStates.Aggressive; + m_defaultMovementType = MovementGeneratorType.Idle; + m_regenHealth = true; + m_meleeDamageSchoolMask = SpellSchoolMask.Normal; + + m_regenTimer = SharedConst.CreatureRegenInterval; + valuesCount = (int)UnitFields.End; + _dynamicValuesCount = (int)UnitDynamicFields.End; + + m_SightDistance = SharedConst.SightRangeUnit; + + ResetLootMode(); // restore default loot mode + + m_homePosition = new WorldLocation(); + } + + public override void Dispose() + { + i_AI = null; + + base.Dispose(); + } + + public override void AddToWorld() + { + // Register the creature for guid lookup + if (!IsInWorld) + { + if (m_zoneScript != null) + m_zoneScript.OnCreatureCreate(this); + + GetMap().GetObjectsStore().Add(GetGUID(), this); + if (m_spawnId != 0) + GetMap().GetCreatureBySpawnIdStore().Add(m_spawnId, this); + + base.AddToWorld(); + SearchFormation(); + InitializeAI(); + if (IsVehicle()) + GetVehicleKit().Install(); + } + } + + public override void RemoveFromWorld() + { + if (IsInWorld) + { + if (m_zoneScript != null) + m_zoneScript.OnCreatureRemove(this); + + if (m_formation != null) + FormationMgr.RemoveCreatureFromGroup(m_formation, this); + + base.RemoveFromWorld(); + + if (m_spawnId != 0) + GetMap().GetCreatureBySpawnIdStore().Remove(m_spawnId, this); + GetMap().GetObjectsStore().Remove(GetGUID()); + } + } + + public void DisappearAndDie() + { + DestroyForNearbyPlayers(); + if (IsAlive()) + setDeathState(DeathState.JustDied); + RemoveCorpse(false); + } + + public void SearchFormation() + { + if (IsSummon()) + return; + + ulong lowguid = GetSpawnId(); + if (lowguid == 0) + return; + + var frmdata = FormationMgr.CreatureGroupMap.LookupByKey(lowguid); + if (frmdata != null) + FormationMgr.AddCreatureToGroup(frmdata.leaderGUID, this); + } + + public void RemoveCorpse(bool setSpawnTime = true) + { + if (getDeathState() != DeathState.Corpse) + return; + + m_corpseRemoveTime = Time.UnixTime; + setDeathState(DeathState.Dead); + RemoveAllAuras(); + UpdateObjectVisibility(); + loot.clear(); + uint respawnDelay = m_respawnDelay; + if (IsAIEnabled) + GetAI().CorpseRemoved(respawnDelay); + + // Should get removed later, just keep "compatibility" with scripts + if (setSpawnTime) + m_respawnTime = Time.UnixTime + respawnDelay; + + float x, y, z, o; + GetRespawnPosition(out x, out y, out z, out o); + SetHomePosition(x, y, z, o); + GetMap().CreatureRelocation(this, x, y, z, o); + } + + public bool InitEntry(uint entry, CreatureData data = null) + { + CreatureTemplate normalInfo = Global.ObjectMgr.GetCreatureTemplate(entry); + if (normalInfo == null) + { + Log.outError(LogFilter.Sql, "Creature.InitEntry creature entry {0} does not exist.", entry); + return false; + } + + // get difficulty 1 mode entry + CreatureTemplate cinfo = null; + DifficultyRecord difficultyEntry = CliDB.DifficultyStorage.LookupByKey(GetMap().GetSpawnMode()); + while (cinfo == null && difficultyEntry != null) + { + int idx = CreatureTemplate.DifficultyIDToDifficultyEntryIndex(difficultyEntry.Id); + if (idx == -1) + break; + + if (normalInfo.DifficultyEntry[idx] != 0) + { + cinfo = Global.ObjectMgr.GetCreatureTemplate(normalInfo.DifficultyEntry[idx]); + break; + } + + if (difficultyEntry.FallbackDifficultyID == 0) + break; + + difficultyEntry = CliDB.DifficultyStorage.LookupByKey(difficultyEntry.FallbackDifficultyID); + } + + if (cinfo == null) + cinfo = normalInfo; + + // Initialize loot duplicate count depending on raid difficulty + if (GetMap().Is25ManRaid()) + loot.maxDuplicates = 3; + + SetEntry(entry); // normal entry always + m_creatureInfo = cinfo; // map mode related always + + // equal to player Race field, but creature does not have race + SetByteValue(UnitFields.Bytes0, 0, 0); + + SetByteValue(UnitFields.Bytes0, 1, (byte)cinfo.UnitClass); + + // Cancel load if no model defined + if (cinfo.GetFirstValidModelId() == 0) + { + Log.outError(LogFilter.Sql, "Creature (Entry: {0}) has no model defined in table `creature_template`, can't load. ", entry); + return false; + } + + uint displayID = ObjectManager.ChooseDisplayId(GetCreatureTemplate(), data); + CreatureModelInfo minfo = Global.ObjectMgr.GetCreatureModelRandomGender(ref displayID); + if (minfo == null) // Cancel load if no model defined + { + Log.outError(LogFilter.Sql, "Creature (Entry: {0}) has no model defined in table `creature_template`, can't load. ", entry); + return false; + } + + SetDisplayId(displayID); + SetNativeDisplayId(displayID); + SetByteValue(UnitFields.Bytes0, 3, (byte)minfo.gender); + + // Load creature equipment + if (data == null || data.equipmentId == 0) + LoadEquipment(); // use default equipment (if available) + else if(data != null && data.equipmentId != 0) // override, 0 means no equipment + { + m_originalEquipmentId = (sbyte)data.equipmentId; + LoadEquipment(data.equipmentId); + } + + SetName(normalInfo.Name); // at normal entry always + + SetFloatValue(UnitFields.ModCastSpeed, 1.0f); + SetFloatValue(UnitFields.ModCastHaste, 1.0f); + SetFloatValue(UnitFields.ModHaste, 1.0f); + SetFloatValue(UnitFields.ModRangedHaste, 1.0f); + SetFloatValue(UnitFields.ModHasteRegen, 1.0f); + SetFloatValue(UnitFields.ModTimeRate, 1.0f); + + SetSpeedRate(UnitMoveType.Walk, cinfo.SpeedWalk); + SetSpeedRate(UnitMoveType.Run, cinfo.SpeedRun); + SetSpeedRate(UnitMoveType.Swim, 1.0f); // using 1.0 rate + SetSpeedRate(UnitMoveType.Flight, 1.0f); // using 1.0 rate + + SetObjectScale(cinfo.Scale); + + SetFloatValue(UnitFields.HoverHeight, cinfo.HoverHeight); + + // checked at loading + m_defaultMovementType = (MovementGeneratorType)cinfo.MovementType; + if (m_respawnradius == 0 && m_defaultMovementType == MovementGeneratorType.Random) + m_defaultMovementType = MovementGeneratorType.Idle; + + for (byte i = 0; i < SharedConst.MaxCreatureSpells; ++i) + m_spells[i] = GetCreatureTemplate().Spells[i]; + + return true; + } + + public bool UpdateEntry(uint entry, CreatureData data = null, bool updateLevel = true) + { + if (!InitEntry(entry, data)) + return false; + + CreatureTemplate cInfo = GetCreatureTemplate(); + + m_regenHealth = cInfo.RegenHealth; + + // creatures always have melee weapon ready if any unless specified otherwise + if (GetCreatureAddon() == null) + SetSheath(SheathState.Melee); + + SetFaction(cInfo.Faction); + + ulong npcFlags; + uint unitFlags, unitFlags2, unitFlags3, dynamicFlags; + ObjectManager.ChooseCreatureFlags(cInfo, out npcFlags, out unitFlags, out unitFlags2, out unitFlags3, out dynamicFlags, data); + + if (cInfo.FlagsExtra.HasAnyFlag(CreatureFlagsExtra.Worldevent)) + SetUInt64Value(UnitFields.NpcFlags, npcFlags | Global.GameEventMgr.GetNPCFlag(this)); + else + SetUInt64Value(UnitFields.NpcFlags, npcFlags); + + SetUInt32Value(UnitFields.Flags, unitFlags); + SetUInt32Value(UnitFields.Flags2, unitFlags2); + SetUInt32Value(UnitFields.Flags3, unitFlags3); + + SetUInt32Value(ObjectFields.DynamicFlags, dynamicFlags); + + RemoveFlag(UnitFields.Flags, UnitFlags.InCombat); + + SetBaseAttackTime(WeaponAttackType.BaseAttack, cInfo.BaseAttackTime); + SetBaseAttackTime(WeaponAttackType.OffAttack, cInfo.BaseAttackTime); + SetBaseAttackTime(WeaponAttackType.RangedAttack, cInfo.RangeAttackTime); + + if (updateLevel) + SelectLevel(); + + UpdateLevelDependantStats(); + + SetMeleeDamageSchool((SpellSchools)cInfo.DmgSchool); + SetModifierValue(UnitMods.ResistanceHoly, UnitModifierType.BaseValue, cInfo.Resistance[(int)SpellSchools.Holy]); + SetModifierValue(UnitMods.ResistanceFire, UnitModifierType.BaseValue, cInfo.Resistance[(int)SpellSchools.Fire]); + SetModifierValue(UnitMods.ResistanceNature, UnitModifierType.BaseValue, cInfo.Resistance[(int)SpellSchools.Nature]); + SetModifierValue(UnitMods.ResistanceFrost, UnitModifierType.BaseValue, cInfo.Resistance[(int)SpellSchools.Frost]); + SetModifierValue(UnitMods.ResistanceShadow, UnitModifierType.BaseValue, cInfo.Resistance[(int)SpellSchools.Shadow]); + SetModifierValue(UnitMods.ResistanceArcane, UnitModifierType.BaseValue, cInfo.Resistance[(int)SpellSchools.Arcane]); + + SetCanModifyStats(true); + UpdateAllStats(); + + // checked and error show at loading templates + var factionTemplate = CliDB.FactionTemplateStorage.LookupByKey(cInfo.Faction); + if (factionTemplate != null) + { + if (Convert.ToBoolean(factionTemplate.Flags & (uint)FactionTemplateFlags.PVP)) + SetPvP(true); + else + SetPvP(false); + } + + // updates spell bars for vehicles and set player's faction - should be called here, to overwrite faction that is set from the new template + if (IsVehicle()) + { + Player owner = GetCharmerOrOwnerPlayerOrPlayerItself(); + if (owner != null) // this check comes in case we don't have a player + { + SetFaction(owner.getFaction()); // vehicles should have same as owner faction + owner.VehicleSpellInitialize(); + } + } + + // trigger creature is always not selectable and can not be attacked + if (IsTrigger()) + SetFlag(UnitFields.Flags, UnitFlags.NotSelectable); + + InitializeReactState(); + + if (Convert.ToBoolean(cInfo.FlagsExtra & CreatureFlagsExtra.NoTaunt)) + { + ApplySpellImmune(0, SpellImmunity.State, AuraType.ModTaunt, true); + ApplySpellImmune(0, SpellImmunity.Effect, SpellEffectName.AttackMe, true); + } + + if (cInfo.InhabitType.HasAnyFlag(InhabitType.Root)) + SetControlled(true, UnitState.Root); + + UpdateMovementFlags(); + LoadCreaturesAddon(); + return true; + } + + public override void Update(uint diff) + { + if (IsAIEnabled && TriggerJustRespawned) + { + TriggerJustRespawned = false; + GetAI().JustRespawned(); + if (m_vehicleKit != null) + m_vehicleKit.Reset(); + } + + UpdateMovementFlags(); + + switch (m_deathState) + { + case DeathState.JustRespawned: + case DeathState.JustDied: + Log.outError(LogFilter.Unit, "Creature ({0}) in wrong state: {2}", GetGUID().ToString(), m_deathState); + break; + case DeathState.Dead: + { + long now = Time.UnixTime; + if (m_respawnTime <= now) + { + // First check if there are any scripts that object to us respawning + if (!Global.ScriptMgr.CanSpawn(GetSpawnId(), GetEntry(), GetCreatureTemplate(), GetCreatureData(), GetMap())) + break; // Will be rechecked on next Update call + + ObjectGuid dbtableHighGuid = ObjectGuid.Create(HighGuid.Creature, GetMapId(), GetEntry(), m_spawnId); + long linkedRespawntime = GetMap().GetLinkedRespawnTime(dbtableHighGuid); + if (linkedRespawntime == 0) // Can respawn + Respawn(); + else // the master is dead + { + ObjectGuid targetGuid = Global.ObjectMgr.GetLinkedRespawnGuid(dbtableHighGuid); + if (targetGuid == dbtableHighGuid) // if linking self, never respawn (check delayed to next day) + SetRespawnTime(Time.Day); + else + m_respawnTime = (now > linkedRespawntime ? now : linkedRespawntime) + RandomHelper.IRand(5, Time.Minute); // else copy time from master and add a little + SaveRespawnTime(); // also save to DB immediately + } + } + break; + } + case DeathState.Corpse: + base.Update(diff); + if (m_deathState != DeathState.Corpse) + break; + + if (m_groupLootTimer != 0 && !lootingGroupLowGUID.IsEmpty()) + { + if (m_groupLootTimer <= diff) + { + Group group = Global.GroupMgr.GetGroupByGUID(lootingGroupLowGUID); + if (group) + group.EndRoll(loot); + m_groupLootTimer = 0; + lootingGroupLowGUID.Clear(); + } + else m_groupLootTimer -= diff; + } + else if (m_corpseRemoveTime <= Time.UnixTime) + { + RemoveCorpse(false); + Log.outDebug(LogFilter.Unit, "Removing corpse... {0} ", GetUInt32Value(ObjectFields.Entry)); + } + break; + case DeathState.Alive: + base.Update(diff); + + if (!IsAlive()) + break; + + if (m_shouldReacquireTarget && !IsFocusing(null, true)) + { + SetTarget(m_suppressedTarget); + if (!m_suppressedTarget.IsEmpty()) + { + WorldObject objTarget = Global.ObjAccessor.GetWorldObject(this, m_suppressedTarget); + if (objTarget) + SetFacingToObject(objTarget); + } + else + SetFacingTo(m_suppressedOrientation); + m_shouldReacquireTarget = false; + } + + // if creature is charmed, switch to charmed AI (and back) + if (NeedChangeAI) + { + UpdateCharmAI(); + NeedChangeAI = false; + IsAIEnabled = true; + if (!IsInEvadeMode() && !LastCharmerGUID.IsEmpty()) + { + Unit charmer = Global.ObjAccessor.GetUnit(this, LastCharmerGUID); + if (charmer) + if (CanStartAttack(charmer, true)) + i_AI.AttackStart(charmer); + } + + LastCharmerGUID.Clear(); + } + + // periodic check to see if the creature has passed an evade boundary + if (IsAIEnabled && !IsInEvadeMode() && IsInCombat()) + { + if (diff >= m_boundaryCheckTime) + { + GetAI().CheckInRoom(); + m_boundaryCheckTime = 2500; + } + else + m_boundaryCheckTime -= diff; + } + + // if periodic combat pulse is enabled and we are both in combat and in a dungeon, do this now + if (m_combatPulseDelay > 0 && IsInCombat() && GetMap().IsDungeon()) + { + if (diff > m_combatPulseTime) + m_combatPulseTime = 0; + else + m_combatPulseTime -= diff; + + if (m_combatPulseTime == 0) + { + var players = GetMap().GetPlayers(); + foreach (var player in players) + { + if (player.IsGameMaster()) + continue; + + if (player.IsAlive() && IsHostileTo(player)) + { + if (CanHaveThreatList()) + AddThreat(player, 0.0f); + SetInCombatWith(player); + player.SetInCombatWith(this); + } + } + m_combatPulseTime = m_combatPulseDelay * Time.InMilliseconds; + } + } + + if (!IsInEvadeMode() && IsAIEnabled) + { + // do not allow the AI to be changed during update + m_AI_locked = true; + i_AI.UpdateAI(diff); + m_AI_locked = false; + } + + if (!IsAlive()) + break; + + if (m_regenTimer > 0) + { + if (diff >= m_regenTimer) + m_regenTimer = 0; + else + m_regenTimer -= diff; + } + + if (m_regenTimer == 0) + { + bool bInCombat = IsInCombat() && (!GetVictim() || // if IsInCombat() is true and this has no victim + !GetVictim().GetCharmerOrOwnerPlayerOrPlayerItself() || // or the victim/owner/charmer is not a player + !GetVictim().GetCharmerOrOwnerPlayerOrPlayerItself().IsGameMaster()); // or the victim/owner/charmer is not a GameMaster + + if (!IsInEvadeMode() && (!bInCombat || IsPolymorphed() || CanNotReachTarget())) // regenerate health if not in combat or if polymorphed + RegenerateHealth(); + + if (HasFlag(UnitFields.Flags2, UnitFlags2.RegeneratePower)) + { + if (getPowerType() == PowerType.Energy) + Regenerate(PowerType.Energy); + else + RegenerateMana(); + } + m_regenTimer = SharedConst.CreatureRegenInterval; + } + + if (CanNotReachTarget() && !IsInEvadeMode() && !GetMap().IsRaid()) + { + m_cannotReachTimer += diff; + if (m_cannotReachTimer >= SharedConst.CreatureNoPathEvadeTime) + if (IsAIEnabled) + GetAI().EnterEvadeMode(EvadeReason.NoPath); + } + break; + } + Global.ScriptMgr.OnCreatureUpdate(this, diff); + + } + + void RegenerateMana() + { + int curValue = GetPower(PowerType.Mana); + int maxValue = GetMaxPower(PowerType.Mana); + + if (curValue >= maxValue) + return; + + int addvalue = 0; + + // Combat and any controlled creature + if (IsInCombat() || !GetCharmerOrOwnerGUID().IsEmpty()) + { + float ManaIncreaseRate = WorldConfig.GetFloatValue(WorldCfg.RatePowerMana); + addvalue = (int)((27.0f / 5.0f + 17.0f) * ManaIncreaseRate); + } + else + addvalue = maxValue / 3; + + // Apply modifiers (if any). + var ModPowerRegenPCTAuras = GetAuraEffectsByType(AuraType.ModPowerRegenPercent); + foreach (var eff in ModPowerRegenPCTAuras) + if (eff.GetMiscValue() == (int)PowerType.Mana) + MathFunctions.AddPct(ref addvalue, eff.GetAmount()); + + addvalue += GetTotalAuraModifierByMiscValue(AuraType.ModPowerRegen, (int)PowerType.Mana) * SharedConst.CreatureRegenInterval / (5 * Time.InMilliseconds); + + ModifyPower(PowerType.Mana, addvalue); + } + + void RegenerateHealth() + { + if (!isRegeneratingHealth()) + return; + + ulong curValue = GetHealth(); + ulong maxValue = GetMaxHealth(); + + if (curValue >= maxValue) + return; + + long addvalue = 0; + + // Not only pet, but any controlled creature + if (!GetCharmerOrOwnerGUID().IsEmpty()) + { + float HealthIncreaseRate = WorldConfig.GetFloatValue(WorldCfg.RateHealth); + addvalue = (uint)(0.015f * GetMaxHealth() * HealthIncreaseRate); + } + else + addvalue = (long)maxValue / 3; + + // Apply modifiers (if any). + var ModPowerRegenPCTAuras = GetAuraEffectsByType(AuraType.ModHealthRegenPercent); + foreach (var eff in ModPowerRegenPCTAuras) + MathFunctions.AddPct(ref addvalue, eff.GetAmount()); + + addvalue += (uint)GetTotalAuraModifier(AuraType.ModRegen) * SharedConst.CreatureRegenInterval / (5 * Time.InMilliseconds); + + ModifyHealth(addvalue); + } + + public void Regenerate(PowerType power) + { + int curValue = GetPower(power); + int maxValue = GetMaxPower(power); + + if (curValue >= maxValue) + return; + + float addvalue = 0.0f; + + switch (power) + { + case PowerType.Focus: + { + // For hunter pets. + addvalue = 24 * WorldConfig.GetFloatValue(WorldCfg.RatePowerFocus); + break; + } + case PowerType.Energy: + { + // For deathknight's ghoul. + addvalue = 20; + break; + } + default: + return; + } + + // Apply modifiers (if any). + var ModPowerRegenPCTAuras = GetAuraEffectsByType(AuraType.ModPowerRegenPercent); + foreach (var i in ModPowerRegenPCTAuras) + if ((PowerType)i.GetMiscValue() == power) + MathFunctions.AddPct(ref addvalue, i.GetAmount()); + + addvalue += GetTotalAuraModifierByMiscValue(AuraType.ModPowerRegen, (int)power) * (IsHunterPet() ? SharedConst.PetFocusRegenInterval : SharedConst.CreatureRegenInterval) / (5 * Time.InMilliseconds); + + ModifyPower(power, (int)addvalue); + } + + public void DoFleeToGetAssistance() + { + if (!GetVictim()) + return; + + if (HasAuraType(AuraType.PreventsFleeing)) + return; + + float radius = WorldConfig.GetFloatValue(WorldCfg.CreatureFamilyFleeAssistanceRadius); + if (radius > 0) + { + var u_check = new NearestAssistCreatureInCreatureRangeCheck(this, GetVictim(), radius); + var searcher = new CreatureLastSearcher(this, u_check); + Cell.VisitGridObjects(this, searcher, radius); + + var creature = searcher.GetTarget(); + + SetNoSearchAssistance(true); + UpdateSpeed(UnitMoveType.Run); + + if (!creature) + SetControlled(true, UnitState.Fleeing); + else + GetMotionMaster().MoveSeekAssistance(creature.GetPositionX(), creature.GetPositionY(), creature.GetPositionZ()); + } + } + + bool AIDestory() + { + if (m_AI_locked) + { + Log.outDebug(LogFilter.Scripts, "AIM_Destroy: failed to destroy, locked."); + return false; + } + + Contract.Assert(i_disabledAI == null, "The disabled AI wasn't cleared!"); + + i_AI = null; + + IsAIEnabled = false; + return true; + } + + public bool InitializeAI(CreatureAI ai = null) + { + // make sure nothing can change the AI during AI update + if (m_AI_locked) + { + Log.outDebug(LogFilter.Scripts, "InitializeAI: failed to init, locked."); + return false; + } + + AIDestory(); + + InitializeMovementAI(); + + i_AI = ai ?? AISelector.SelectAI(this); + + IsAIEnabled = true; + i_AI.InitializeAI(); + // Initialize vehicle + if (GetVehicleKit() != null) + GetVehicleKit().Reset(); + + return true; + } + + void InitializeMovementAI() + { + if (m_formation == null) + GetMotionMaster().Initialize(); + else if (m_formation.getLeader() == this) + { + m_formation.FormationReset(false); + GetMotionMaster().Initialize(); + } + else if (m_formation.isFormed()) + GetMotionMaster().MoveIdle(); //wait the order of leader + else + GetMotionMaster().Initialize(); + } + + public bool Create(ulong guidlow, Map map, uint phaseMask, uint entry, float x, float y, float z, float ang, CreatureData data = null, uint vehId = 0) + { + SetMap(map); + + if (data != null && data.phaseid != 0) + SetInPhase(data.phaseid, false, true); + + if (data != null && data.phaseGroup != 0) + foreach (var ph in Global.DB2Mgr.GetPhasesForGroup(data.phaseGroup)) + SetInPhase(ph, false, true); + + CreatureTemplate cinfo = Global.ObjectMgr.GetCreatureTemplate(entry); + if (cinfo == null) + { + Log.outError(LogFilter.Sql, "Creature.Create: creature template (guidlow: {0}, entry: {1}) does not exist.", guidlow, entry); + return false; + } + + //! Relocate before CreateFromProto, to initialize coords and allow + //! returning correct zone id for selecting OutdoorPvP/Battlefield script + Relocate(x, y, z, ang); + + // Check if the position is valid before calling CreateFromProto(), otherwise we might add Auras to Creatures at + // invalid position, triggering a crash about Auras not removed in the destructor + if (!IsPositionValid()) + { + Log.outError(LogFilter.Unit, "Creature.Create: given coordinates for creature (guidlow {0}, entry {1}) are not valid (X: {2}, Y: {3}, Z: {4}, O: {5})", guidlow, entry, x, y, z, ang); + return false; + } + + if (!CreateFromProto(guidlow, entry, data, vehId)) + return false; + + switch (GetCreatureTemplate().Rank) + { + case CreatureEliteType.Rare: + m_corpseDelay = WorldConfig.GetUIntValue(WorldCfg.CorpseDecayRare); + break; + case CreatureEliteType.Elite: + m_corpseDelay = WorldConfig.GetUIntValue(WorldCfg.CorpseDecayElite); + break; + case CreatureEliteType.RareElite: + m_corpseDelay = WorldConfig.GetUIntValue(WorldCfg.CorpseDecayRareelite); + break; + case CreatureEliteType.WorldBoss: + m_corpseDelay = WorldConfig.GetUIntValue(WorldCfg.CorpseDecayWorldboss); + break; + default: + m_corpseDelay = WorldConfig.GetUIntValue(WorldCfg.CorpseDecayNormal); + break; + } + LoadCreaturesAddon(); + + //! Need to be called after LoadCreaturesAddon - MOVEMENTFLAG_HOVER is set there + if (HasUnitMovementFlag(MovementFlag.Hover)) + { + z += GetFloatValue(UnitFields.HoverHeight); + + //! Relocate again with updated Z coord + Relocate(x, y, z, ang); + } + + uint displayID = GetNativeDisplayId(); + CreatureModelInfo minfo = Global.ObjectMgr.GetCreatureModelRandomGender(ref displayID); + if (minfo != null && !IsTotem()) // Cancel load if no model defined or if totem + { + SetDisplayId(displayID); + SetNativeDisplayId(displayID); + SetByteValue(UnitFields.Bytes0, 3, (byte)minfo.gender); + } + + LastUsedScriptID = GetScriptId(); + + // TODO: Replace with spell, handle from DB + if (IsSpiritHealer() || IsSpiritGuide()) + { + m_serverSideVisibility.SetValue(ServerSideVisibilityType.Ghost, GhostVisibilityType.Ghost); + m_serverSideVisibilityDetect.SetValue(ServerSideVisibilityType.Ghost, GhostVisibilityType.Ghost); + } + + if (GetCreatureTemplate().FlagsExtra.HasAnyFlag(CreatureFlagsExtra.IgnorePathfinding)) + AddUnitState(UnitState.IgnorePathfinding); + + if (GetCreatureTemplate().FlagsExtra.HasAnyFlag(CreatureFlagsExtra.ImmunityKnockback)) + { + ApplySpellImmune(0, SpellImmunity.Effect, SpellEffectName.KnockBack, true); + ApplySpellImmune(0, SpellImmunity.Effect, SpellEffectName.KnockBackDest, true); + } + + return true; + } + + void InitializeReactState() + { + if (IsTotem() || IsTrigger() || IsCritter() || IsSpiritService()) + SetReactState(ReactStates.Passive); + else + SetReactState(ReactStates.Aggressive); + } + + public bool isCanInteractWithBattleMaster(Player player, bool msg) + { + if (!IsBattleMaster()) + return false; + + BattlegroundTypeId bgTypeId = Global.BattlegroundMgr.GetBattleMasterBG(GetEntry()); + if (!msg) + return player.GetBGAccessByLevel(bgTypeId); + + if (!player.GetBGAccessByLevel(bgTypeId)) + { + player.PlayerTalkClass.ClearMenus(); + switch (bgTypeId) + { + case BattlegroundTypeId.AV: + player.PlayerTalkClass.SendGossipMenu(7616, GetGUID()); + break; + case BattlegroundTypeId.WS: + player.PlayerTalkClass.SendGossipMenu(7599, GetGUID()); + break; + case BattlegroundTypeId.AB: + player.PlayerTalkClass.SendGossipMenu(7642, GetGUID()); + break; + case BattlegroundTypeId.EY: + case BattlegroundTypeId.NA: + case BattlegroundTypeId.BE: + case BattlegroundTypeId.AA: + case BattlegroundTypeId.RL: + case BattlegroundTypeId.SA: + case BattlegroundTypeId.DS: + case BattlegroundTypeId.RV: + player.PlayerTalkClass.SendGossipMenu(10024, GetGUID()); + break; + default: break; + } + return false; + } + return true; + } + + public bool isCanTrainingAndResetTalentsOf(Player player) + { + return player.getLevel() >= 10 + && GetCreatureTemplate().TrainerType == TrainerType.Class + && player.GetClass() == GetCreatureTemplate().TrainerClass; + } + + public void SetTextRepeatId(byte textGroup, byte id) + { + if (!m_textRepeat.ContainsKey(textGroup)) + { + m_textRepeat.Add(textGroup, id); + return; + } + + var repeats = m_textRepeat[textGroup]; + if (!repeats.Contains(id)) + repeats.Add(id); + else + Log.outError(LogFilter.Sql, "CreatureTextMgr: TextGroup {0} for ({1}) {2}, id {3} already added", textGroup, GetName(), GetGUID().ToString(), id); + } + + public List GetTextRepeatGroup(byte textGroup) + { + return m_textRepeat.LookupByKey(textGroup); + } + + public void ClearTextRepeatGroup(byte textGroup) + { + var groupList = m_textRepeat[textGroup]; + if (groupList != null) + groupList.Clear(); + } + + public bool CanGiveExperience() + { + return !IsCritter() + && !IsPet() + && !IsTotem() + && !GetCreatureTemplate().FlagsExtra.HasAnyFlag(CreatureFlagsExtra.NoXpAtKill); + } + + public void StartPickPocketRefillTimer() + { + _pickpocketLootRestore = Time.UnixTime + WorldConfig.GetIntValue(WorldCfg.CreaturePickpocketRefill); + } + public void ResetPickPocketRefillTimer() { _pickpocketLootRestore = 0; } + public bool CanGeneratePickPocketLoot() { return _pickpocketLootRestore <= Time.UnixTime; } + public void SetSkinner(ObjectGuid guid) { _skinner = guid; } + public ObjectGuid GetSkinner() { return _skinner; } // Returns the player who skinned this creature + + public Player GetLootRecipient() + { + if (m_lootRecipient.IsEmpty()) + return null; + return Global.ObjAccessor.FindPlayer(m_lootRecipient); + } + + public Group GetLootRecipientGroup() + { + if (m_lootRecipientGroup.IsEmpty()) + return null; + + return Global.GroupMgr.GetGroupByGUID(m_lootRecipientGroup); + } + + public void SetLootRecipient(Unit unit) + { + // set the player whose group should receive the right + // to loot the creature after it dies + // should be set to NULL after the loot disappears + + if (unit == null) + { + m_lootRecipient.Clear(); + m_lootRecipientGroup.Clear(); + RemoveFlag(ObjectFields.DynamicFlags, UnitDynFlags.Lootable | UnitDynFlags.Tapped); + return; + } + + if (!unit.IsTypeId(TypeId.Player) && !unit.IsVehicle()) + return; + + Player player = unit.GetCharmerOrOwnerPlayerOrPlayerItself(); + if (player == null) // normal creature, no player involved + return; + + m_lootRecipient = player.GetGUID(); + Group group = player.GetGroup(); + if (group) + m_lootRecipientGroup = group.GetGUID(); + + SetFlag(ObjectFields.DynamicFlags, UnitDynFlags.Tapped); + } + + public bool isTappedBy(Player player) + { + if (player.GetGUID() == m_lootRecipient) + return true; + + Group playerGroup = player.GetGroup(); + if (!playerGroup || playerGroup != GetLootRecipientGroup()) // if we dont have a group we arent the recipient + return false; // if creature doesnt have group bound it means it was solo killed by someone else + + return true; + } + + public void SaveToDB() + { + // this should only be used when the creature has already been loaded + // preferably after adding to map, because mapid may not be valid otherwise + CreatureData data = Global.ObjectMgr.GetCreatureData(m_spawnId); + if (data == null) + { + Log.outError(LogFilter.Unit, "Creature.SaveToDB failed, cannot get creature data!"); + return; + } + + uint mapId = GetTransport() ? (uint)GetTransport().GetGoInfo().MoTransport.SpawnMap : GetMapId(); + SaveToDB(mapId, data.spawnMask, GetPhaseMask()); + } + + public virtual void SaveToDB(uint mapid, uint spawnMask, uint phaseMask) + { + // update in loaded data + if (m_spawnId == 0) + m_spawnId = Global.ObjectMgr.GenerateCreatureSpawnId(); + + CreatureData data = Global.ObjectMgr.NewOrExistCreatureData(m_spawnId); + + uint displayId = GetNativeDisplayId(); + ulong npcflag = GetUInt64Value(UnitFields.NpcFlags); + uint unitFlags = GetUInt32Value(UnitFields.Flags); + uint unitFlags2 = GetUInt32Value(UnitFields.Flags2); + uint unitFlags3 = GetUInt32Value(UnitFields.Flags3); + uint dynamicflags = GetUInt32Value(ObjectFields.DynamicFlags); + + // check if it's a custom model and if not, use 0 for displayId + CreatureTemplate cinfo = GetCreatureTemplate(); + if (cinfo != null) + { + if (displayId == cinfo.ModelId1 || displayId == cinfo.ModelId2 || + displayId == cinfo.ModelId3 || displayId == cinfo.ModelId4) + displayId = 0; + + if (npcflag == (uint)cinfo.Npcflag) + npcflag = 0; + + if (unitFlags == (uint)cinfo.UnitFlags) + unitFlags = 0; + + if (unitFlags2 == cinfo.UnitFlags2) + unitFlags2 = 0; + + if (unitFlags3 == cinfo.UnitFlags3) + unitFlags3 = 0; + + if (dynamicflags == cinfo.DynamicFlags) + dynamicflags = 0; + } + + // data.guid = guid must not be updated at save + data.id = GetEntry(); + data.mapid = (ushort)mapid; + data.phaseMask = phaseMask; + data.displayid = displayId; + data.equipmentId = GetCurrentEquipmentId(); + data.posX = GetPositionX(); + data.posY = GetPositionY(); + data.posZ = GetPositionZMinusOffset(); + data.orientation = GetOrientation(); + data.spawntimesecs = m_respawnDelay; + // prevent add data integrity problems + data.spawndist = GetDefaultMovementType() == MovementGeneratorType.Idle ? 0.0f : m_respawnradius; + data.currentwaypoint = 0; + data.curhealth = (uint)GetHealth(); + data.curmana = (uint)GetPower(PowerType.Mana); + // prevent add data integrity problems + data.movementType = (byte)(m_respawnradius == 0 && GetDefaultMovementType() == MovementGeneratorType.Random + ? MovementGeneratorType.Idle : GetDefaultMovementType()); + data.spawnMask = spawnMask; + data.npcflag = npcflag; + data.unit_flags = unitFlags; + data.unit_flags2 = unitFlags2; + data.unit_flags3 = unitFlags3; + data.dynamicflags = dynamicflags; + + data.phaseid = (uint)(GetDBPhase() > 0 ? GetDBPhase() : 0); + data.phaseGroup = (uint)(GetDBPhase() < 0 ? Math.Abs(GetDBPhase()) : 0); + + // update in DB + SQLTransaction trans = new SQLTransaction(); + + PreparedStatement stmt = DB.World.GetPreparedStatement(WorldStatements.DEL_CREATURE); + stmt.AddValue(0, m_spawnId); + trans.Append(stmt); + + byte index = 0; + + stmt = DB.World.GetPreparedStatement(WorldStatements.INS_CREATURE); + stmt.AddValue(index++, m_spawnId); + stmt.AddValue(index++, GetEntry()); + stmt.AddValue(index++, mapid); + stmt.AddValue(index++, spawnMask); + stmt.AddValue(index++, data.phaseid); + stmt.AddValue(index++, data.phaseGroup); + stmt.AddValue(index++, displayId); + stmt.AddValue(index++, GetCurrentEquipmentId()); + stmt.AddValue(index++, GetPositionX()); + stmt.AddValue(index++, GetPositionY()); + stmt.AddValue(index++, GetPositionZ()); + stmt.AddValue(index++, GetOrientation()); + stmt.AddValue(index++, m_respawnDelay); + stmt.AddValue(index++, m_respawnradius); + stmt.AddValue(index++, 0); + stmt.AddValue(index++, GetHealth()); + stmt.AddValue(index++, GetPower(PowerType.Mana)); + stmt.AddValue(index++, GetDefaultMovementType()); + stmt.AddValue(index++, npcflag); + stmt.AddValue(index++, unitFlags); + stmt.AddValue(index++, unitFlags2); + stmt.AddValue(index++, unitFlags3); + stmt.AddValue(index++, dynamicflags); + trans.Append(stmt); + + DB.World.CommitTransaction(trans); + } + + public void SelectLevel() + { + CreatureTemplate cInfo = GetCreatureTemplate(); + + // level + byte minlevel = (byte)Math.Min(cInfo.Maxlevel, cInfo.Minlevel); + byte maxlevel = (byte)Math.Max(cInfo.Maxlevel, cInfo.Minlevel); + byte level = (byte)(minlevel == maxlevel ? minlevel : RandomHelper.URand(minlevel, maxlevel)); + SetLevel(level); + } + + void UpdateLevelDependantStats() + { + CreatureTemplate cInfo = GetCreatureTemplate(); + CreatureEliteType rank = IsPet() ? 0 : cInfo.Rank; + CreatureBaseStats stats = Global.ObjectMgr.GetCreatureBaseStats(getLevel(), cInfo.UnitClass); + + // health + float healthmod = _GetHealthMod(rank); + + uint basehp = stats.GenerateHealth(cInfo); + uint health = (uint)(basehp * healthmod); + + SetCreateHealth(health); + SetMaxHealth(health); + SetHealth(health); + ResetPlayerDamageReq(); + + // mana + uint mana = stats.GenerateMana(cInfo); + SetCreateMana(mana); + + switch (GetClass()) + { + case Class.Warrior: + setPowerType(PowerType.Rage); + break; + case Class.Rogue: + setPowerType(PowerType.Energy); + break; + default: + SetMaxPower(PowerType.Mana, (int)mana); + SetPower(PowerType.Mana, (int)mana); + break; + } + + SetModifierValue(UnitMods.Health, UnitModifierType.BaseValue, health); + SetModifierValue(UnitMods.Mana, UnitModifierType.BaseValue, mana); + + //Damage + float basedamage = stats.GenerateBaseDamage(cInfo); + + float weaponBaseMinDamage = basedamage; + float weaponBaseMaxDamage = basedamage * 1.5f; + + + SetBaseWeaponDamage(WeaponAttackType.BaseAttack, WeaponDamageRange.MinDamage, weaponBaseMinDamage); + SetBaseWeaponDamage(WeaponAttackType.BaseAttack, WeaponDamageRange.MaxDamage, weaponBaseMaxDamage); + + SetBaseWeaponDamage(WeaponAttackType.OffAttack, WeaponDamageRange.MinDamage, weaponBaseMinDamage); + SetBaseWeaponDamage(WeaponAttackType.OffAttack, WeaponDamageRange.MaxDamage, weaponBaseMaxDamage); + + SetBaseWeaponDamage(WeaponAttackType.RangedAttack, WeaponDamageRange.MinDamage, weaponBaseMinDamage); + SetBaseWeaponDamage(WeaponAttackType.RangedAttack, WeaponDamageRange.MaxDamage, weaponBaseMaxDamage); + + SetModifierValue(UnitMods.AttackPower, UnitModifierType.BaseValue, stats.AttackPower); + SetModifierValue(UnitMods.AttackPowerRanged, UnitModifierType.BaseValue, stats.RangedAttackPower); + + float armor = stats.GenerateArmor(cInfo); /// @todo Why is this treated as uint32 when it's a float? + SetModifierValue(UnitMods.Armor, UnitModifierType.BaseValue, armor); + } + + float _GetHealthMod(CreatureEliteType Rank) + { + switch (Rank) // define rates for each elite rank + { + case CreatureEliteType.Normal: + return WorldConfig.GetFloatValue(WorldCfg.RateCreatureNormalHp); + case CreatureEliteType.Elite: + return WorldConfig.GetFloatValue(WorldCfg.RateCreatureEliteEliteHp); + case CreatureEliteType.RareElite: + return WorldConfig.GetFloatValue(WorldCfg.RateCreatureEliteRareeliteHp); + case CreatureEliteType.WorldBoss: + return WorldConfig.GetFloatValue(WorldCfg.RateCreatureEliteWorldbossHp); + case CreatureEliteType.Rare: + return WorldConfig.GetFloatValue(WorldCfg.RateCreatureEliteRareHp); + default: + return WorldConfig.GetFloatValue(WorldCfg.RateCreatureEliteRareeliteHp); + } + } + + public void LowerPlayerDamageReq(ulong unDamage) + { + if (m_PlayerDamageReq != 0) + { + if (m_PlayerDamageReq > unDamage) + m_PlayerDamageReq -= unDamage; + else + m_PlayerDamageReq = 0; + } + } + + public static float _GetDamageMod(CreatureEliteType Rank) + { + switch (Rank) // define rates for each elite rank + { + case CreatureEliteType.Normal: + return WorldConfig.GetFloatValue(WorldCfg.RateCreatureNormalDamage); + case CreatureEliteType.Elite: + return WorldConfig.GetFloatValue(WorldCfg.RateCreatureEliteEliteDamage); + case CreatureEliteType.RareElite: + return WorldConfig.GetFloatValue(WorldCfg.RateCreatureEliteRareeliteDamage); + case CreatureEliteType.WorldBoss: + return WorldConfig.GetFloatValue(WorldCfg.RateCreatureEliteWorldbossDamage); + case CreatureEliteType.Rare: + return WorldConfig.GetFloatValue(WorldCfg.RateCreatureEliteRareDamage); + default: + return WorldConfig.GetFloatValue(WorldCfg.RateCreatureEliteEliteDamage); + } + } + + public float GetSpellDamageMod(CreatureEliteType Rank) + { + switch (Rank) // define rates for each elite rank + { + case CreatureEliteType.Normal: + return WorldConfig.GetFloatValue(WorldCfg.RateCreatureNormalSpelldamage); + case CreatureEliteType.Elite: + return WorldConfig.GetFloatValue(WorldCfg.RateCreatureEliteEliteSpelldamage); + case CreatureEliteType.RareElite: + return WorldConfig.GetFloatValue(WorldCfg.RateCreatureEliteRareeliteSpelldamage); + case CreatureEliteType.WorldBoss: + return WorldConfig.GetFloatValue(WorldCfg.RateCreatureEliteWorldbossSpelldamage); + case CreatureEliteType.Rare: + return WorldConfig.GetFloatValue(WorldCfg.RateCreatureEliteRareSpelldamage); + default: + return WorldConfig.GetFloatValue(WorldCfg.RateCreatureEliteEliteSpelldamage); + } + } + + bool CreateFromProto(ulong guidlow, uint entry, CreatureData data = null, uint vehId = 0) + { + SetZoneScript(); + if (m_zoneScript != null && data != null) + { + entry = m_zoneScript.GetCreatureEntry(guidlow, data); + if (entry == 0) + return false; + } + + CreatureTemplate cinfo = Global.ObjectMgr.GetCreatureTemplate(entry); + if (cinfo == null) + { + Log.outError(LogFilter.Sql, "Creature.CreateFromProto: creature template (guidlow: {0}, entry: {1}) does not exist.", guidlow, entry); + return false; + } + + SetOriginalEntry(entry); + + if (vehId != 0 || cinfo.VehicleId != 0) + _Create(ObjectGuid.Create(HighGuid.Vehicle, GetMapId(), entry, guidlow)); + else + _Create(ObjectGuid.Create(HighGuid.Creature, GetMapId(), entry, guidlow)); + + if (!UpdateEntry(entry, data)) + return false; + + if (vehId == 0) + { + if (GetCreatureTemplate().VehicleId != 0) + { + vehId = GetCreatureTemplate().VehicleId; + entry = GetCreatureTemplate().Entry; + } + else + vehId = cinfo.VehicleId; + } + + if (vehId != 0) + CreateVehicleKit(vehId, entry, true); + + return true; + } + + public bool LoadCreatureFromDB(ulong spawnId, Map map, bool addToMap = true, bool allowDuplicate = false) + { + if (!allowDuplicate) + { + // If an alive instance of this spawnId is already found, skip creation + // If only dead instance(s) exist, despawn them and spawn a new (maybe also dead) version + var creatureBounds = map.GetCreatureBySpawnIdStore().LookupByKey(spawnId); + List despawnList = new List(); + + foreach (var creature in creatureBounds) + { + if (creature.IsAlive()) + { + Log.outDebug(LogFilter.Maps, "Would have spawned {0} but {1} already exists", spawnId, creature.GetGUID().ToString()); + return false; + } + else + { + despawnList.Add(creature); + Log.outDebug(LogFilter.Maps, "Despawned dead instance of spawn {0} ({1})", spawnId, creature.GetGUID().ToString()); + } + } + + foreach (Creature despawnCreature in despawnList) + { + despawnCreature.AddObjectToRemoveList(); + } + } + + CreatureData data = Global.ObjectMgr.GetCreatureData(spawnId); + if (data == null) + { + Log.outError(LogFilter.Sql, "Creature (GUID: {0}) not found in table `creature`, can't load. ", spawnId); + return false; + } + + m_spawnId = spawnId; + m_creatureData = data; + if (!Create(map.GenerateLowGuid(HighGuid.Creature), map, data.phaseMask, data.id, data.posX, data.posY, data.posZ, data.orientation, data)) + return false; + + //We should set first home position, because then AI calls home movement + SetHomePosition(data.posX, data.posY, data.posZ, data.orientation); + + m_respawnradius = data.spawndist; + + m_respawnDelay = data.spawntimesecs; + m_deathState = DeathState.Alive; + + m_respawnTime = GetMap().GetCreatureRespawnTime(m_spawnId); + + // Is the creature script objecting to us spawning? If yes, delay by one second (then re-check in ::Update) + if (m_respawnTime == 0 && !Global.ScriptMgr.CanSpawn(spawnId, GetEntry(), GetCreatureTemplate(), GetCreatureData(), map)) + m_respawnTime = Time.UnixTime + 1; + + if (m_respawnTime != 0) // respawn on Update + { + m_deathState = DeathState.Dead; + if (CanFly()) + { + float tz = map.GetHeight(GetPhases(), data.posX, data.posY, data.posZ, true, MapConst.MaxFallDistance); + if (data.posZ - tz > 0.1f && GridDefines.IsValidMapCoord(tz)) + Relocate(data.posX, data.posY, tz); + } + } + + SetSpawnHealth(); + + m_defaultMovementType = (MovementGeneratorType)data.movementType; + + loot.SetGUID(ObjectGuid.Create(HighGuid.LootObject, data.mapid, data.id, GetMap().GenerateLowGuid(HighGuid.LootObject))); + + if (addToMap && !GetMap().AddToMap(this)) + return false; + return true; + } + + public override void SetCanDualWield(bool value) + { + base.SetCanDualWield(value); + UpdateDamagePhysical(WeaponAttackType.OffAttack); + } + + public void LoadEquipment(int id = 1, bool force = true) + { + if (id == 0) + { + if (force) + { + for (byte i = 0; i < SharedConst.MaxEquipmentItems; ++i) + SetVirtualItem(i, 0); + m_equipmentId = 0; + } + return; + } + + EquipmentInfo einfo = Global.ObjectMgr.GetEquipmentInfo(GetEntry(), id); + if (einfo == null) + return; + + m_equipmentId = (byte)id; + for (byte i = 0; i < SharedConst.MaxEquipmentItems; ++i) + SetVirtualItem(i, einfo.Items[i].ItemId, einfo.Items[i].AppearanceModId, einfo.Items[i].ItemVisual); + } + + public void SetSpawnHealth() + { + ulong curhealth; + + if (!m_regenHealth) + { + if (m_creatureData != null) + { + curhealth = m_creatureData.curhealth; + if (curhealth != 0) + { + curhealth = (uint)(curhealth * _GetHealthMod(GetCreatureTemplate().Rank)); + if (curhealth < 1) + curhealth = 1; + } + SetPower(PowerType.Mana, (int)m_creatureData.curmana); + } + else + curhealth = GetHealth(); + } + else + { + curhealth = GetMaxHealth(); + SetPower(PowerType.Mana, GetMaxPower(PowerType.Mana)); + } + + SetHealth((m_deathState == DeathState.Alive || m_deathState == DeathState.JustRespawned) ? curhealth : 0); + } + + public override bool hasQuest(uint quest_id) + { + var qr = Global.ObjectMgr.GetCreatureQuestRelationBounds(GetEntry()); + foreach (var id in qr) + { + if (id == quest_id) + return true; + } + return false; + } + + public override bool hasInvolvedQuest(uint quest_id) + { + var qir = Global.ObjectMgr.GetCreatureQuestInvolvedRelationBounds(GetEntry()); + foreach (var id in qir) + { + if (id == quest_id) + return true; + } + return false; + } + + public void DeleteFromDB() + { + if (m_spawnId == 0) + { + Log.outError(LogFilter.Unit, "Trying to delete not saved {0}", GetGUID().ToString(), GetEntry()); + return; + } + + GetMap().RemoveCreatureRespawnTime(m_spawnId); + Global.ObjectMgr.DeleteCreatureData(m_spawnId); + + SQLTransaction trans = new SQLTransaction(); + + PreparedStatement stmt = DB.World.GetPreparedStatement(WorldStatements.DEL_CREATURE); + stmt.AddValue(0, m_spawnId); + trans.Append(stmt); + + stmt = DB.World.GetPreparedStatement(WorldStatements.DEL_CREATURE_ADDON); + stmt.AddValue(0, m_spawnId); + trans.Append(stmt); + + stmt = DB.World.GetPreparedStatement(WorldStatements.DEL_GAME_EVENT_CREATURE); + stmt.AddValue(0, m_spawnId); + trans.Append(stmt); + + stmt = DB.World.GetPreparedStatement(WorldStatements.DEL_GAME_EVENT_MODEL_EQUIP); + stmt.AddValue(0, m_spawnId); + trans.Append(stmt); + + DB.World.CommitTransaction(trans); + } + + public override bool IsInvisibleDueToDespawn() + { + if (base.IsInvisibleDueToDespawn()) + return true; + + if (IsAlive() || m_corpseRemoveTime > Time.UnixTime) + return false; + + return true; + } + + public override bool CanAlwaysSee(WorldObject obj) + { + if (IsAIEnabled && GetAI().CanSeeAlways(obj)) + return true; + + return false; + } + + public bool CanStartAttack(Unit who, bool force) + { + if (IsCivilian()) + return false; + + // This set of checks is should be done only for creatures + if ((HasFlag(UnitFields.Flags, UnitFlags.ImmuneToNpc) && !who.IsTypeId(TypeId.Player)) // flag is valid only for non player characters + || (HasFlag(UnitFields.Flags, UnitFlags.ImmuneToPc) && who.IsTypeId(TypeId.Player)) // immune to PC and target is a player, return false + || (who.GetOwner() && who.GetOwner().IsTypeId(TypeId.Player) && HasFlag(UnitFields.Flags, UnitFlags.ImmuneToPc))) // player pets are immune to pc as well + return false; + + // Do not attack non-combat pets + if (who.IsTypeId(TypeId.Unit) && who.GetCreatureType() == CreatureType.NonCombatPet) + return false; + + if (!CanFly() && (GetDistanceZ(who) > SharedConst.CreatureAttackRangeZ + m_CombatDistance)) + return false; + + if (!force) + { + if (!_IsTargetAcceptable(who)) + return false; + + if (who.IsInCombat() && IsWithinDist(who, SharedConst.AttackDistance)) + { + Unit victim = who.getAttackerForHelper(); + if (victim != null) + if (IsWithinDistInMap(victim, WorldConfig.GetFloatValue(WorldCfg.CreatureFamilyAssistanceRadius))) + force = true; + } + + if (!force && (IsNeutralToAll() || !IsWithinDistInMap(who, GetAttackDistance(who) + m_CombatDistance))) + return false; + } + + if (!CanCreatureAttack(who, force)) + return false; + + return IsWithinLOSInMap(who); + } + + public float GetAttackDistance(Unit player) + { + float aggroRate = WorldConfig.GetFloatValue(WorldCfg.RateCreatureAggro); + if (aggroRate == 0) + return 0.0f; + + uint playerlevel = player.GetLevelForTarget(this); + uint creaturelevel = GetLevelForTarget(player); + + int leveldif = (int)(playerlevel - creaturelevel); + + // "The maximum Aggro Radius has a cap of 25 levels under. Example: A level 30 char has the same Aggro Radius of a level 5 char on a level 60 mob." + if (leveldif < -25) + leveldif = -25; + + // "The aggro radius of a mob having the same level as the player is roughly 20 yards" + float RetDistance = 20; + + // "Aggro Radius varies with level difference at a rate of roughly 1 yard/level" + // radius grow if playlevel < creaturelevel + RetDistance -= leveldif; + + if (creaturelevel + 5 <= WorldConfig.GetIntValue(WorldCfg.MaxPlayerLevel)) + { + // detect range auras + RetDistance += GetTotalAuraModifier(AuraType.ModDetectRange); + + // detected range auras + RetDistance += player.GetTotalAuraModifier(AuraType.ModDetectedRange); + } + + // "Minimum Aggro Radius for a mob seems to be combat range (5 yards)" + if (RetDistance < 5) + RetDistance = 5; + + return (RetDistance * aggroRate); + } + + public override void setDeathState(DeathState s) + { + base.setDeathState(s); + + if (s == DeathState.JustDied) + { + m_corpseRemoveTime = Time.UnixTime + m_corpseDelay; + m_respawnTime = Time.UnixTime + m_respawnDelay + m_corpseDelay; + + // always save boss respawn time at death to prevent crash cheating + if (WorldConfig.GetBoolValue(WorldCfg.SaveRespawnTimeImmediately) || isWorldBoss()) + SaveRespawnTime(); + + ReleaseFocus(null, false); // remove spellcast focus + SetTarget(ObjectGuid.Empty); // drop target - dead mobs shouldn't ever target things + + SetUInt64Value(UnitFields.NpcFlags, (ulong)NPCFlags.None); + + SetUInt32Value(UnitFields.MountDisplayId, 0); // if creature is mounted on a virtual mount, remove it at death + + setActive(false); + + if (HasSearchedAssistance()) + { + SetNoSearchAssistance(false); + UpdateSpeed(UnitMoveType.Run); + } + + //Dismiss group if is leader + if (m_formation != null && m_formation.getLeader() == this) + m_formation.FormationReset(true); + + if ((CanFly() || IsFlying())) + GetMotionMaster().MoveFall(); + + base.setDeathState(DeathState.Corpse); + } + else if (s == DeathState.JustRespawned) + { + if (IsPet()) + SetFullHealth(); + else + SetSpawnHealth(); + + SetLootRecipient(null); + ResetPlayerDamageReq(); + + UpdateMovementFlags(); + + ClearUnitState(UnitState.AllState & ~UnitState.IgnorePathfinding); + + if (!IsPet()) + { + CreatureData creatureData = GetCreatureData(); + CreatureTemplate cInfo = GetCreatureTemplate(); + + ulong npcFlags; + uint unitFlags, unitFlags2, unitFlags3, dynamicFlags; + ObjectManager.ChooseCreatureFlags(cInfo, out npcFlags, out unitFlags, out unitFlags2, out unitFlags3, out dynamicFlags, creatureData); + + if (cInfo.FlagsExtra.HasAnyFlag(CreatureFlagsExtra.Worldevent)) + SetUInt64Value(UnitFields.NpcFlags, npcFlags | Global.GameEventMgr.GetNPCFlag(this)); + else + SetUInt64Value(UnitFields.NpcFlags, npcFlags); + + SetUInt32Value(UnitFields.Flags, unitFlags); + SetUInt32Value(UnitFields.Flags2, unitFlags2); + SetUInt32Value(UnitFields.Flags3, unitFlags3); + SetUInt32Value(ObjectFields.DynamicFlags, dynamicFlags); + + RemoveFlag(UnitFields.Flags, UnitFlags.InCombat); + + SetMeleeDamageSchool((SpellSchools)cInfo.DmgSchool); + } + + InitializeMovementAI(); + base.setDeathState(DeathState.Alive); + LoadCreaturesAddon(); + } + } + + public void Respawn(bool force = false) + { + DestroyForNearbyPlayers(); + + if (force) + { + if (IsAlive()) + setDeathState(DeathState.JustDied); + else if (getDeathState() != DeathState.Corpse) + setDeathState(DeathState.Corpse); + } + + RemoveCorpse(false); + + if (getDeathState() == DeathState.Dead) + { + if (m_spawnId != 0) + GetMap().RemoveCreatureRespawnTime(m_spawnId); + + Log.outDebug(LogFilter.Unit, "Respawning creature {0} ({1})", GetName(), GetGUID().ToString()); + m_respawnTime = 0; + ResetPickPocketRefillTimer(); + loot.clear(); + + if (m_originalEntry != GetEntry()) + UpdateEntry(m_originalEntry); + + SelectLevel(); + + setDeathState(DeathState.JustRespawned); + + uint displayID = GetNativeDisplayId(); + CreatureModelInfo minfo = Global.ObjectMgr.GetCreatureModelRandomGender(ref displayID); + if (minfo != null) // Cancel load if no model defined + { + SetDisplayId(displayID); + SetNativeDisplayId(displayID); + SetByteValue(UnitFields.Bytes0, 3, (byte)minfo.gender); + } + + GetMotionMaster().InitDefault(); + + //Call AI respawn virtual function + if (IsAIEnabled) + { + GetAI().Reset(); + TriggerJustRespawned = true;//delay event to next tick so all creatures are created on the map before processing + } + + uint poolid = GetSpawnId() != 0 ? Global.PoolMgr.IsPartOfAPool(GetSpawnId()) : 0; + if (poolid != 0) + Global.PoolMgr.UpdatePool(poolid, GetSpawnId()); + + //Re-initialize reactstate that could be altered by movementgenerators + InitializeReactState(); + } + + UpdateObjectVisibility(); + } + + public void ForcedDespawn(uint timeMSToDespawn = 0, TimeSpan forceRespawnTimer = default(TimeSpan)) + { + if (timeMSToDespawn != 0) + { + ForcedDespawnDelayEvent pEvent = new ForcedDespawnDelayEvent(this, forceRespawnTimer); + + m_Events.AddEvent(pEvent, m_Events.CalculateTime(timeMSToDespawn)); + return; + } + + if (forceRespawnTimer > TimeSpan.Zero) + { + if (IsAlive()) + { + uint respawnDelay = m_respawnDelay; + uint corpseDelay = m_corpseDelay; + m_respawnDelay = (uint)forceRespawnTimer.TotalSeconds; + m_corpseDelay = 0; + setDeathState(DeathState.JustDied); + + m_respawnDelay = respawnDelay; + m_corpseDelay = corpseDelay; + } + else + { + m_corpseRemoveTime = Time.UnixTime; + m_respawnTime = Time.UnixTime + (long)forceRespawnTimer.TotalMilliseconds; + } + + } + else + { + if (IsAlive()) + setDeathState(DeathState.JustDied); + } + + RemoveCorpse(false); + } + + public void DespawnOrUnsummon(TimeSpan time, TimeSpan forceRespawnTimer = default(TimeSpan)) { DespawnOrUnsummon((uint)time.TotalMilliseconds, forceRespawnTimer); } + + public void DespawnOrUnsummon(uint msTimeToDespawn = 0, TimeSpan forceRespawnTimer = default(TimeSpan)) + { + TempSummon summon = ToTempSummon(); + if (summon != null) + summon.UnSummon(msTimeToDespawn); + else + ForcedDespawn(msTimeToDespawn, forceRespawnTimer); + } + + public override bool IsImmunedToSpell(SpellInfo spellInfo) + { + if (spellInfo == null) + return false; + + // Creature is immune to main mechanic of the spell + if (Convert.ToBoolean(GetCreatureTemplate().MechanicImmuneMask & (1 << ((int)spellInfo.Mechanic - 1)))) + return true; + + // This check must be done instead of 'if (GetCreatureTemplate().MechanicImmuneMask & (1 << (spellInfo.Mechanic - 1)))' for not break + // the check of mechanic immunity on DB (tested) because GetCreatureTemplate().MechanicImmuneMask and m_spellImmune[IMMUNITY_MECHANIC] don't have same data. + bool immunedToAllEffects = true; + foreach (SpellEffectInfo effect in spellInfo.GetEffectsForDifficulty(GetMap().GetDifficultyID())) + { + if (effect == null || !effect.IsEffect()) + continue; + + if (!IsImmunedToSpellEffect(spellInfo, effect.EffectIndex)) + { + immunedToAllEffects = false; + break; + } + } + if (immunedToAllEffects) + return true; + + return base.IsImmunedToSpell(spellInfo); + } + + public override bool IsImmunedToSpellEffect(SpellInfo spellInfo, uint index) + { + SpellEffectInfo effect = spellInfo.GetEffect(GetMap().GetDifficultyID(), index); + if (effect == null) + return true; + if (Convert.ToBoolean(GetCreatureTemplate().MechanicImmuneMask & (1 << ((int)effect.Mechanic - 1)))) + return true; + + if (GetCreatureTemplate().CreatureType == CreatureType.Mechanical && effect.Effect == SpellEffectName.Heal) + return true; + + return base.IsImmunedToSpellEffect(spellInfo, index); + } + + public bool isElite() + { + if (IsPet()) + return false; + + var rank = GetCreatureTemplate().Rank; + return rank != CreatureEliteType.Elite && rank != CreatureEliteType.RareElite; + } + + public bool isWorldBoss() + { + if (IsPet()) + return false; + + return Convert.ToBoolean(GetCreatureTemplate().TypeFlags & CreatureTypeFlags.BossMob); + } + + public SpellInfo reachWithSpellAttack(Unit victim) + { + if (victim == null) + return null; + + for (uint i = 0; i < SharedConst.MaxCreatureSpells; ++i) + { + if (m_spells[i] == 0) + continue; + SpellInfo spellInfo = Global.SpellMgr.GetSpellInfo(m_spells[i]); + if (spellInfo == null) + { + Log.outError(LogFilter.Unit, "WORLD: unknown spell id {0}", m_spells[i]); + continue; + } + + bool bcontinue = true; + foreach (SpellEffectInfo effect in spellInfo.GetEffectsForDifficulty(GetMap().GetDifficultyID())) + { + if (effect != null && ((effect.Effect == SpellEffectName.SchoolDamage) || (effect.Effect == SpellEffectName.Instakill) + || (effect.Effect == SpellEffectName.EnvironmentalDamage) || (effect.Effect == SpellEffectName.HealthLeech))) + { + bcontinue = false; + break; + } + } + if (bcontinue) + continue; + + var costs = spellInfo.CalcPowerCost(this, spellInfo.SchoolMask); + var m = costs.Find(cost => cost.Power == PowerType.Mana); + if (m != null) + if (m.Amount > GetPower(PowerType.Mana)) + continue; + + float range = spellInfo.GetMaxRange(false); + float minrange = spellInfo.GetMinRange(false); + float dist = GetDistance(victim); + if (dist > range || dist < minrange) + continue; + if (spellInfo.PreventionType.HasAnyFlag(SpellPreventionType.Silence) && HasFlag(UnitFields.Flags, UnitFlags.Silenced)) + continue; + if (spellInfo.PreventionType.HasAnyFlag(SpellPreventionType.Pacify) && HasFlag(UnitFields.Flags, UnitFlags.Pacified)) + continue; + return spellInfo; + } + return null; + } + + SpellInfo reachWithSpellCure(Unit victim) + { + if (victim == null) + return null; + + for (uint i = 0; i < SharedConst.MaxCreatureSpells; ++i) + { + if (m_spells[i] == 0) + continue; + SpellInfo spellInfo = Global.SpellMgr.GetSpellInfo(m_spells[i]); + if (spellInfo == null) + { + Log.outError(LogFilter.Unit, "WORLD: unknown spell id {0}", m_spells[i]); + continue; + } + + bool bcontinue = true; + foreach (SpellEffectInfo effect in spellInfo.GetEffectsForDifficulty(GetMap().GetDifficultyID())) + { + if (effect != null && effect.Effect == SpellEffectName.Heal) + { + bcontinue = false; + break; + } + } + if (bcontinue) + continue; + + var costs = spellInfo.CalcPowerCost(this, spellInfo.SchoolMask); + var m = costs.Find(cost => cost.Power == PowerType.Mana); + if (m != null) + if (m.Amount > GetPower(PowerType.Mana)) + continue; + + float range = spellInfo.GetMaxRange(true); + float minrange = spellInfo.GetMinRange(true); + float dist = GetDistance(victim); + + if (dist > range || dist < minrange) + continue; + if (spellInfo.PreventionType.HasAnyFlag(SpellPreventionType.Silence) && HasFlag(UnitFields.Flags, UnitFlags.Silenced)) + continue; + if (spellInfo.PreventionType.HasAnyFlag(SpellPreventionType.Pacify) && HasFlag(UnitFields.Flags, UnitFlags.Pacified)) + continue; + return spellInfo; + } + return null; + } + + // select nearest hostile unit within the given distance (regardless of threat list). + public Unit SelectNearestTarget(float dist = 0) + { + if (dist == 0.0f) + dist = SharedConst.MaxVisibilityDistance; + + var u_check = new NearestHostileUnitCheck(this, dist); + var searcher = new UnitLastSearcher(this, u_check); + Cell.VisitAllObjects(this, searcher, dist); + + return searcher.GetTarget(); + } + + // select nearest hostile unit within the given attack distance (i.e. distance is ignored if > than ATTACK_DISTANCE), regardless of threat list. + public Unit SelectNearestTargetInAttackDistance(float dist = 0) + { + if (dist > SharedConst.MaxVisibilityDistance) + { + Log.outError(LogFilter.Unit, "Creature ({0}) SelectNearestTargetInAttackDistance called with dist > MAX_VISIBILITY_DISTANCE. Distance set to ATTACK_DISTANCE.", GetGUID().ToString()); + dist = SharedConst.AttackDistance; + } + + var u_check = new NearestHostileUnitInAttackDistanceCheck(this, dist); + var searcher = new UnitLastSearcher(this, u_check); + + Cell.VisitAllObjects(this, searcher, Math.Max(dist, SharedConst.AttackDistance)); + + return searcher.GetTarget(); + } + + public Player SelectNearestPlayer(float distance) + { + var checker = new NearestPlayerInObjectRangeCheck(this, distance); + var searcher = new PlayerLastSearcher(this, checker); + Cell.VisitAllObjects(this, searcher, distance); + return searcher.GetTarget(); + } + + public void SendAIReaction(AiReaction reactionType) + { + AIReaction packet = new AIReaction(); + + packet.UnitGUID = GetGUID(); + packet.Reaction = reactionType; + + SendMessageToSet(packet, true); + } + + public void CallAssistance() + { + if (!m_AlreadyCallAssistance && GetVictim() != null && !IsPet() && !IsCharmed()) + { + SetNoCallAssistance(true); + + float radius = WorldConfig.GetFloatValue(WorldCfg.CreatureFamilyAssistanceRadius); + + if (radius > 0) + { + List assistList = new List(); + + var u_check = new AnyAssistCreatureInRangeCheck(this, GetVictim(), radius); + var searcher = new CreatureListSearcher(this, assistList, u_check); + Cell.VisitGridObjects(this, searcher, radius); + + if (!assistList.Empty()) + { + AssistDelayEvent e = new AssistDelayEvent(GetVictim().GetGUID(), this); + while (!assistList.Empty()) + { + // Pushing guids because in delay can happen some creature gets despawned + e.AddAssistant(assistList.First().GetGUID()); + assistList.Remove(assistList.First()); + } + m_Events.AddEvent(e, m_Events.CalculateTime(WorldConfig.GetUIntValue(WorldCfg.CreatureFamilyAssistanceDelay))); + } + } + } + } + + public void CallForHelp(float radius) + { + if (radius <= 0.0f || GetVictim() == null || IsPet() || IsCharmed()) + return; + + var u_do = new CallOfHelpCreatureInRangeDo(this, GetVictim(), radius); + var worker = new CreatureWorker(this, u_do); + Cell.VisitGridObjects(this, worker, radius); + } + + public bool CanAssistTo(Unit u, Unit enemy, bool checkfaction = true) + { + if (IsInEvadeMode()) + return false; + + // is it true? + if (!HasReactState(ReactStates.Aggressive)) + return false; + + // we don't need help from zombies :) + if (!IsAlive()) + return false; + + // we don't need help from non-combatant ;) + if (IsCivilian()) + return false; + + if (HasFlag(UnitFields.Flags, UnitFlags.NonAttackable | UnitFlags.NotSelectable | UnitFlags.ImmuneToPc)) + return false; + + // skip fighting creature + if (IsInCombat()) + return false; + + // only free creature + if (!GetCharmerOrOwnerGUID().IsEmpty()) + return false; + + // only from same creature faction + if (checkfaction) + { + if (getFaction() != u.getFaction()) + return false; + } + else + { + if (!IsFriendlyTo(u)) + return false; + } + + // skip non hostile to caster enemy creatures + if (!IsHostileTo(enemy)) + return false; + + return true; + } + + public bool _IsTargetAcceptable(Unit target) + { + // if the target cannot be attacked, the target is not acceptable + if (IsFriendlyTo(target) || !target.isTargetableForAttack(false) + || (m_vehicle != null && (IsOnVehicle(target) || m_vehicle.GetBase().IsOnVehicle(target)))) + return false; + + if (target.HasUnitState(UnitState.Died)) + { + // guards can detect fake death + if (IsGuard() && target.HasFlag(UnitFields.Flags2, UnitFlags2.FeignDeath)) + return true; + else + return false; + } + + Unit myVictim = getAttackerForHelper(); + Unit targetVictim = target.getAttackerForHelper(); + + // if I'm already fighting target, or I'm hostile towards the target, the target is acceptable + if (myVictim == target || targetVictim == this || IsHostileTo(target)) + return true; + + // if the target's victim is friendly, and the target is neutral, the target is acceptable + if (targetVictim != null && IsFriendlyTo(targetVictim)) + return true; + + // if the target's victim is not friendly, or the target is friendly, the target is not acceptable + return false; + } + + public override void SaveRespawnTime() + { + if (IsSummon() || m_spawnId == 0 || (m_creatureData != null && !m_creatureData.dbData)) + return; + + GetMap().SaveCreatureRespawnTime(m_spawnId, m_respawnTime); + } + + public bool CanCreatureAttack(Unit victim, bool force = true) + { + if (!victim.IsInMap(this)) + return false; + + if (!IsValidAttackTarget(victim)) + return false; + + if (!victim.isInAccessiblePlaceFor(this)) + return false; + + if (IsAIEnabled && !GetAI().CanAIAttack(victim)) + return false; + + if (GetMap().IsDungeon()) + return true; + + // if the mob is actively being damaged, do not reset due to distance unless it's a world boss + if (!isWorldBoss()) + if (Time.UnixTime - GetLastDamagedTime() <= SharedConst.MaxAggroResetTime) + return true; + + //Use AttackDistance in distance check if threat radius is lower. This prevents creature bounce in and out of combat every update tick. + float dist = Math.Max(GetAttackDistance(victim), (WorldConfig.GetFloatValue(WorldCfg.ThreatRadius) + m_CombatDistance)); + + Unit unit = GetCharmerOrOwner(); + if (unit != null) + return victim.IsWithinDist(unit, dist); + else + return victim.IsInDist(m_homePosition, dist); + } + + CreatureAddon GetCreatureAddon() + { + if (m_spawnId != 0) + { + CreatureAddon addon = Global.ObjectMgr.GetCreatureAddon(m_spawnId); + if (addon != null) + return addon; + } + + // dependent from difficulty mode entry + return Global.ObjectMgr.GetCreatureTemplateAddon(GetCreatureTemplate().Entry); + } + + public bool LoadCreaturesAddon() + { + CreatureAddon cainfo = GetCreatureAddon(); + if (cainfo == null) + return false; + + if (cainfo.mount != 0) + Mount(cainfo.mount); + + if (cainfo.bytes1 != 0) + { + // 0 StandState + // 1 FreeTalentPoints Pet only, so always 0 for default creature + // 2 StandFlags + // 3 StandMiscFlags + + SetByteValue(UnitFields.Bytes1, UnitBytes1Offsets.StandState, (byte)(cainfo.bytes1 & 0xFF)); + //SetByteValue(UnitFields.Bytes1, UnitBytes1Offsets.PetTalent, (byte)((cainfo.bytes1 >> 8) & 0xFF)); + SetByteValue(UnitFields.Bytes1, UnitBytes1Offsets.PetTalents, 0); + SetByteValue(UnitFields.Bytes1, UnitBytes1Offsets.VisFlag, (byte)((cainfo.bytes1 >> 16) & 0xFF)); + SetByteValue(UnitFields.Bytes1, UnitBytes1Offsets.AnimTier, (byte)((cainfo.bytes1 >> 24) & 0xFF)); + + //! Suspected correlation between UNIT_FIELD_BYTES_1, offset 3, value 0x2: + //! If no inhabittype_fly (if no MovementFlag_DisableGravity or MovementFlag_CanFly flag found in sniffs) + //! Check using InhabitType as movement flags are assigned dynamically + //! basing on whether the creature is in air or not + //! Set MovementFlag_Hover. Otherwise do nothing. + if (Convert.ToBoolean(GetByteValue(UnitFields.Bytes1, UnitBytes1Offsets.AnimTier) & (byte)UnitBytes1Flags.Hover) && !Convert.ToBoolean(GetCreatureTemplate().InhabitType & InhabitType.Air)) + AddUnitMovementFlag(MovementFlag.Hover); + } + + if (cainfo.bytes2 != 0) + { + // 0 SheathState + // 1 PvpFlags + // 2 PetFlags Pet only, so always 0 for default creature + // 3 ShapeshiftForm Must be determined/set by shapeshift spell/aura + + SetByteValue(UnitFields.Bytes2, UnitBytes2Offsets.SheathState, (byte)(cainfo.bytes2 & 0xFF)); + //SetByteValue(UnitFields.Bytes2, UnitBytes2Offsets.PvpFlag, uint8((cainfo->bytes2 >> 8) & 0xFF)); + //SetByteValue(UnitFields.Bytes2, UnitBytes2Offsets.PetFlags, uint8((cainfo->bytes2 >> 16) & 0xFF)); + SetByteValue(UnitFields.Bytes2, UnitBytes2Offsets.PetFlags, 0); + //SetByteValue(UnitFields.Bytes2, UNIT_BYTES_2_OFFSET_SHAPESHIFT_FORM, uint8((cainfo->bytes2 >> 24) & 0xFF)); + SetByteValue(UnitFields.Bytes2, UnitBytes2Offsets.ShapeshiftForm, 0); + } + + if (cainfo.emote != 0) + SetUInt32Value(UnitFields.NpcEmotestate, cainfo.emote); + + SetAIAnimKitId(cainfo.aiAnimKit); + SetMovementAnimKitId(cainfo.movementAnimKit); + SetMeleeAnimKitId(cainfo.meleeAnimKit); + + //Load Path + if (cainfo.path_id != 0) + m_path_id = cainfo.path_id; + + if (!cainfo.auras.Empty()) + { + foreach (var id in cainfo.auras) + { + SpellInfo AdditionalSpellInfo = Global.SpellMgr.GetSpellInfo(id); + if (AdditionalSpellInfo == null) + { + Log.outError(LogFilter.Sql, "Creature ({0}) has wrong spell {1} defined in `auras` field.", GetGUID().ToString(), id); + continue; + } + + // skip already applied aura + if (HasAura(id)) + continue; + + AddAura(id, this); + Log.outError(LogFilter.Unit, "Spell: {0} added to creature ({1})", id, GetGUID().ToString()); + } + } + return true; + } + + // Send a message to LocalDefense channel for players opposition team in the zone + public void SendZoneUnderAttackMessage(Player attacker) + { + Team enemy_team = attacker.GetTeam(); + + ZoneUnderAttack packet = new ZoneUnderAttack(); + packet.AreaID = (int)GetAreaId(); + Global.WorldMgr.SendGlobalMessage(packet, null, (enemy_team == Team.Alliance ? Team.Horde : Team.Alliance)); + } + + public void SetInCombatWithZone() + { + if (!CanHaveThreatList()) + { + Log.outError(LogFilter.Unit, "Creature entry {0} call SetInCombatWithZone but creature cannot have threat list.", GetEntry()); + return; + } + + Map map = GetMap(); + + if (!map.IsDungeon()) + { + Log.outError(LogFilter.Unit, "Creature entry {0} call SetInCombatWithZone for map (id: {1}) that isn't an instance.", GetEntry(), map.GetId()); + return; + } + + var PlList = map.GetPlayers(); + + if (PlList.Empty()) + return; + + foreach (var player in PlList) + { + if (player.IsGameMaster()) + continue; + + if (player.IsAlive()) + { + SetInCombatWith(player); + player.SetInCombatWith(this); + AddThreat(player, 0.0f); + } + + } + } + + public override bool HasSpell(uint spellId) + { + byte i; + for (i = 0; i < SharedConst.MaxCreatureSpells; ++i) + if (spellId == m_spells[i]) + break; + return i < SharedConst.MaxCreatureSpells; //broke before end of iteration of known spells + } + + public long GetRespawnTimeEx() + { + long now = Time.UnixTime; + if (m_respawnTime > now) + return m_respawnTime; + else + return now; + } + + public void GetRespawnPosition(out float x, out float y, out float z) + { + if (m_spawnId != 0) + { + CreatureData data = Global.ObjectMgr.GetCreatureData(GetSpawnId()); + if (data != null) + { + x = data.posX; + y = data.posY; + z = data.posZ; + return; + } + } + + GetPosition(out x, out y, out z); + } + public void GetRespawnPosition(out float x, out float y, out float z, out float ori) + { + if (m_spawnId != 0) + { + CreatureData data = Global.ObjectMgr.GetCreatureData(GetSpawnId()); + if (data != null) + { + x = data.posX; + y = data.posY; + z = data.posZ; + ori = data.orientation; + + return; + } + } + + GetPosition(out x, out y, out z, out ori); + } + public void GetRespawnPosition(out float x, out float y, out float z, out float ori, out float dist) + { + if (m_spawnId != 0) + { + CreatureData data = Global.ObjectMgr.GetCreatureData(GetSpawnId()); + if (data != null) + { + x = data.posX; + y = data.posY; + z = data.posZ; + ori = data.orientation; + dist = data.spawndist; + + return; + } + } + + GetPosition(out x, out y, out z, out ori); + dist = 0; + } + + public void AllLootRemovedFromCorpse() + { + if (loot.loot_type != LootType.Skinning && !IsPet() && GetCreatureTemplate().SkinLootId != 0 && hasLootRecipient()) + if (LootStorage.Skinning.HaveLootFor(GetCreatureTemplate().SkinLootId)) + SetFlag(UnitFields.Flags, UnitFlags.Skinnable); + + long now = Time.UnixTime; + // Do not reset corpse remove time if corpse is already removed + if (m_corpseRemoveTime <= now) + return; + + float decayRate = WorldConfig.GetFloatValue(WorldCfg.RateCorpseDecayLooted); + // corpse skinnable, but without skinning flag, and then skinned, corpse will despawn next update + if (loot.loot_type == LootType.Skinning) + m_corpseRemoveTime = now; + else + m_corpseRemoveTime = now + (uint)(m_corpseDelay * decayRate); + + m_respawnTime = m_corpseRemoveTime + m_respawnDelay; + } + + public override uint GetLevelForTarget(WorldObject target) + { + if (!isWorldBoss() || !target.IsTypeId(TypeId.Unit)) + return base.GetLevelForTarget(target); + + uint level = target.ToUnit().getLevel() + WorldConfig.GetUIntValue(WorldCfg.WorldBossLevelDiff); + if (level < 1) + return 1; + if (level > 255) + return 255; + return level; + } + + public string GetAIName() + { + return Global.ObjectMgr.GetCreatureTemplate(GetEntry()).AIName; + } + + public string GetScriptName() + { + return Global.ObjectMgr.GetScriptName(GetScriptId()); + } + + public uint GetScriptId() + { + CreatureData creatureData = GetCreatureData(); + if (creatureData != null) + return creatureData.ScriptId; + + return Global.ObjectMgr.GetCreatureTemplate(GetEntry()).ScriptID; + } + + public VendorItemData GetVendorItems() + { + return Global.ObjectMgr.GetNpcVendorItemList(GetEntry()); + } + + public uint GetVendorItemCurrentCount(VendorItem vItem) + { + if (vItem.maxcount == 0) + return vItem.maxcount; + + VendorItemCount vCount = null; + for (var i = 0; i < m_vendorItemCounts.Count; i++) + { + vCount = m_vendorItemCounts[i]; + if (vCount.itemId == vItem.item) + break; + } + + if (vCount == null) + return vItem.maxcount; + + long ptime = Time.UnixTime; + + if (vCount.lastIncrementTime + vItem.incrtime <= ptime) + { + ItemTemplate pProto = Global.ObjectMgr.GetItemTemplate(vItem.item); + + uint diff = (uint)((ptime - vCount.lastIncrementTime) / vItem.incrtime); + if ((vCount.count + diff * pProto.GetBuyCount()) >= vItem.maxcount) + { + m_vendorItemCounts.Remove(vCount); + return vItem.maxcount; + } + + vCount.count += diff * pProto.GetBuyCount(); + vCount.lastIncrementTime = ptime; + } + + return vCount.count; + } + + public uint UpdateVendorItemCurrentCount(VendorItem vItem, uint used_count) + { + if (vItem.maxcount == 0) + return 0; + + VendorItemCount vCount = null; ; + for (var i = 0; i < m_vendorItemCounts.Count; i++) + { + vCount = m_vendorItemCounts[i]; + if (vCount.itemId == vItem.item) + break; + } + + if (vCount == null) + { + uint new_count = vItem.maxcount > used_count ? vItem.maxcount - used_count : 0; + m_vendorItemCounts.Add(new VendorItemCount(vItem.item, new_count)); + return new_count; + } + + long ptime = Time.UnixTime; + + if (vCount.lastIncrementTime + vItem.incrtime <= ptime) + { + ItemTemplate pProto = Global.ObjectMgr.GetItemTemplate(vItem.item); + + uint diff = (uint)((ptime - vCount.lastIncrementTime) / vItem.incrtime); + if ((vCount.count + diff * pProto.GetBuyCount()) < vItem.maxcount) + vCount.count += diff * pProto.GetBuyCount(); + else + vCount.count = vItem.maxcount; + } + + vCount.count = vCount.count > used_count ? vCount.count - used_count : 0; + vCount.lastIncrementTime = ptime; + return vCount.count; + } + + public TrainerSpellData GetTrainerSpells() + { + return Global.ObjectMgr.GetNpcTrainerSpells(GetEntry()); + } + + public override string GetName(LocaleConstant locale_idx = LocaleConstant.enUS) + { + if (locale_idx != LocaleConstant.enUS) + { + CreatureLocale cl = Global.ObjectMgr.GetCreatureLocale(GetEntry()); + if (cl != null) + { + if (cl.Name.Length > (byte)locale_idx && !string.IsNullOrEmpty(cl.Name[(byte)locale_idx])) + return cl.Name[(byte)locale_idx]; + } + } + + return base.GetName(locale_idx); + } + + public virtual byte GetPetAutoSpellSize() { return 4; } + public virtual uint GetPetAutoSpellOnPos(byte pos) + { + if (pos >= SharedConst.MaxSpellCharm || GetCharmInfo().GetCharmSpell(pos).GetActiveState() != ActiveStates.Enabled) + return 0; + else + return GetCharmInfo().GetCharmSpell(pos).GetAction(); + } + + public void SetCannotReachTarget(bool cannotReach) + { + if (cannotReach == m_cannotReachTarget) + return; + + m_cannotReachTarget = cannotReach; + m_cannotReachTimer = 0; + } + bool CanNotReachTarget() { return m_cannotReachTarget; } + + public void SetPosition(float x, float y, float z, float o) + { + // prevent crash when a bad coord is sent by the client + if (!GridDefines.IsValidMapCoord(x, y, z, o)) + { + Log.outDebug(LogFilter.Unit, "Creature.SetPosition({0}, {1}, {2}) .. bad coordinates!", x, y, z); + return; + } + + GetMap().CreatureRelocation(ToCreature(), x, y, z, o); + if (IsVehicle()) + GetVehicleKit().RelocatePassengers(); + } + + public bool IsDungeonBoss() + { + CreatureTemplate cinfo = Global.ObjectMgr.GetCreatureTemplate(GetEntry()); + return cinfo != null && (cinfo.FlagsExtra.HasAnyFlag(CreatureFlagsExtra.DungeonBoss)); + } + + public float GetAggroRange(Unit target) + { + // Determines the aggro range for creatures (usually pets), used mainly for aggressive pet target selection. + // Based on data from wowwiki due to lack of 3.3.5a data + + if (target != null && IsPet()) + { + uint targetLevel = 0; + + if (target.IsTypeId(TypeId.Player)) + targetLevel = target.GetLevelForTarget(this); + else if (target.IsTypeId(TypeId.Unit)) + targetLevel = target.ToCreature().GetLevelForTarget(this); + + uint myLevel = GetLevelForTarget(target); + int levelDiff = (int)(targetLevel - myLevel); + + // The maximum Aggro Radius is capped at 45 yards (25 level difference) + if (levelDiff < -25) + levelDiff = -25; + + // The base aggro radius for mob of same level + float aggroRadius = 20; + + // Aggro Radius varies with level difference at a rate of roughly 1 yard/level + aggroRadius -= levelDiff; + + // detect range auras + aggroRadius += GetTotalAuraModifier(AuraType.ModDetectRange); + + // detected range auras + aggroRadius += target.GetTotalAuraModifier(AuraType.ModDetectedRange); + + // Just in case, we don't want pets running all over the map + if (aggroRadius > SharedConst.MaxAggroRadius) + aggroRadius = SharedConst.MaxAggroRadius; + + // Minimum Aggro Radius for a mob seems to be combat range (5 yards) + // hunter pets seem to ignore minimum aggro radius so we'll default it a little higher + if (aggroRadius < 10) + aggroRadius = 10; + + return (aggroRadius); + } + + // Default + return 0.0f; + } + + public Unit SelectNearestHostileUnitInAggroRange(bool useLOS = false) + { + // Selects nearest hostile target within creature's aggro range. Used primarily by + // pets set to aggressive. Will not return neutral or friendly targets + var u_check = new NearestHostileUnitInAggroRangeCheck(this, useLOS); + var searcher = new UnitSearcher(this, u_check); + Cell.VisitGridObjects(this, searcher, SharedConst.MaxAggroRadius); + return searcher.GetTarget(); + } + + public void UpdateMovementFlags() + { + // Do not update movement flags if creature is controlled by a player (charm/vehicle) + if (m_playerMovingMe != null) + return; + + // Set the movement flags if the creature is in that mode. (Only fly if actually in air, only swim if in water, etc) + float ground = GetMap().GetHeight(GetPhases(), GetPositionX(), GetPositionY(), GetPositionZMinusOffset()); + + bool isInAir = (MathFunctions.fuzzyGt(GetPositionZMinusOffset(), ground + 0.05f) || MathFunctions.fuzzyLt(GetPositionZMinusOffset(), ground - 0.05f)); // Can be underground too, prevent the falling + + if (GetCreatureTemplate().InhabitType.HasAnyFlag(InhabitType.Air) && isInAir && !IsFalling()) + { + if (GetCreatureTemplate().InhabitType.HasAnyFlag(InhabitType.Ground)) + SetCanFly(true); + else + SetDisableGravity(true); + } + else + { + SetCanFly(false); + SetDisableGravity(false); + } + + if (!isInAir) + SetFall(false); + + SetSwim(GetCreatureTemplate().InhabitType.HasAnyFlag(InhabitType.Water) && IsInWater()); + } + + public override void SetObjectScale(float scale) + { + base.SetObjectScale(scale); + + CreatureModelInfo minfo = Global.ObjectMgr.GetCreatureModelInfo(GetDisplayId()); + if (minfo != null) + { + SetFloatValue(UnitFields.BoundingRadius, minfo.BoundingRadius * scale); + SetFloatValue(UnitFields.CombatReach, minfo.CombatReach * scale); + } + } + + public override void SetDisplayId(uint modelId) + { + base.SetDisplayId(modelId); + + CreatureModelInfo minfo = Global.ObjectMgr.GetCreatureModelInfo(modelId); + if (minfo != null) + { + SetFloatValue(UnitFields.BoundingRadius, minfo.BoundingRadius * GetObjectScale()); + SetFloatValue(UnitFields.CombatReach, minfo.CombatReach * GetObjectScale()); + } + } + + public override void SetTarget(ObjectGuid guid) + { + if (IsFocusing(null, true)) + m_suppressedTarget = guid; + else + SetGuidValue(UnitFields.Target, guid); + } + + public void FocusTarget(Spell focusSpell, WorldObject target) + { + // already focused + if (_focusSpell != null) + return; + + // don't use spell focus for vehicle spells + if (focusSpell.GetSpellInfo().HasAura(Difficulty.None, AuraType.ControlVehicle)) + return; + + if ((!target || target == this) && focusSpell.GetCastTime() == 0) // instant cast, untargeted (or self-targeted) spell doesn't need any facing updates + return; + + // store pre-cast values for target and orientation (used to later restore) + if (!IsFocusing(null, true)) + { // only overwrite these fields if we aren't transitioning from one spell focus to another + m_suppressedTarget = GetGuidValue(UnitFields.Target); + m_suppressedOrientation = GetOrientation(); + } + + _focusSpell = focusSpell; + + // set target, then force send update packet to players if it changed to provide appropriate facing + ObjectGuid newTarget = target ? target.GetGUID() : ObjectGuid.Empty; + if (GetGuidValue(UnitFields.Target) != newTarget) + { + SetGuidValue(UnitFields.Target, newTarget); + + // here we determine if the (relatively expensive) forced update is worth it, or whether we can afford to wait until the scheduled update tick + // only require instant update for spells that actually have a visual + if (focusSpell.GetSpellInfo().GetSpellVisual() != 0 && (focusSpell.GetCastTime() == 0 || // if the spell is instant cast + focusSpell.GetSpellInfo().HasAttribute(SpellAttr5.DontTurnDuringCast))) // client gets confused if we attempt to turn at the regularly scheduled update packet + { + List playersNearby = GetPlayerListInGrid(GetVisibilityRange()); + foreach (var player in playersNearby) + { + // only update players that are known to the client (have already been created) + if (player.HaveAtClient(this)) + SendUpdateToPlayer(player); + } + } + } + + bool canTurnDuringCast = !focusSpell.GetSpellInfo().HasAttribute(SpellAttr5.DontTurnDuringCast); + // Face the target - we need to do this before the unit state is modified for no-turn spells + if (target) + SetFacingToObject(target); + else if (!canTurnDuringCast) + { + Unit victim = GetVictim(); + if (victim) + SetFacingToObject(victim); // ensure server-side orientation is correct at beginning of cast + } + + if (!canTurnDuringCast) + AddUnitState(UnitState.CannotTurn); + } + + public bool IsFocusing(Spell focusSpell = null, bool withDelay = false) + { + if (!IsAlive()) // dead creatures cannot focus + { + ReleaseFocus(null, false); + return false; + } + + if (focusSpell && (focusSpell != _focusSpell)) + return false; + + if (!_focusSpell) + { + if (!withDelay || _focusDelay == 0) + return false; + if (Time.GetMSTimeDiffToNow(_focusDelay) > 1000) // @todo figure out if we can get rid of this magic number somehow + { + _focusDelay = 0; // save checks in the future + return false; + } + } + + return true; + } + + public void ReleaseFocus(Spell focusSpell = null, bool withDelay = true) + { + if (_focusSpell == null) + return; + + // focused to something else + if (focusSpell && focusSpell != _focusSpell) + return; + + if (IsPet())// player pets do not use delay system + { + SetGuidValue(UnitFields.Target, m_suppressedTarget); + if (!m_suppressedTarget.IsEmpty()) + { + WorldObject objTarget = Global.ObjAccessor.GetWorldObject(this, m_suppressedTarget); + if (objTarget) + SetFacingToObject(objTarget); + } + else + SetFacingTo(m_suppressedOrientation); + } + else + // tell the creature that it should reacquire its actual target after the delay expires (this is handled in ::Update) + // player pets don't need to do this, as they automatically reacquire their target on focus release + MustReacquireTarget(); + + if (_focusSpell.GetSpellInfo().HasAttribute(SpellAttr5.DontTurnDuringCast)) + ClearUnitState(UnitState.CannotTurn); + + _focusSpell = null; + _focusDelay = (!IsPet() && withDelay) ? Time.GetMSTime() : 0; // don't allow re-target right away to prevent visual bugs + } + + public void MustReacquireTarget() { m_shouldReacquireTarget = true; } // flags the Creature for forced (client displayed) target reacquisition in the next Update call + + public ulong GetSpawnId() { return m_spawnId; } + + public void SetCorpseDelay(uint delay) { m_corpseDelay = delay; } + public uint GetCorpseDelay() { return m_corpseDelay; } + public bool IsRacialLeader() { return GetCreatureTemplate().RacialLeader; } + public bool IsCivilian() + { + return GetCreatureTemplate().FlagsExtra.HasAnyFlag(CreatureFlagsExtra.Civilian); + } + public bool IsTrigger() + { + return GetCreatureTemplate().FlagsExtra.HasAnyFlag(CreatureFlagsExtra.Trigger); + } + public bool IsGuard() + { + return GetCreatureTemplate().FlagsExtra.HasAnyFlag(CreatureFlagsExtra.Guard); + } + public bool CanWalk() + { + return GetCreatureTemplate().InhabitType.HasAnyFlag(InhabitType.Ground); + } + public override bool CanSwim() + { + return GetCreatureTemplate().InhabitType.HasAnyFlag(InhabitType.Water); + } + public override bool CanFly() + { + return GetCreatureTemplate().InhabitType.HasAnyFlag(InhabitType.Air); + } + + public void SetReactState(ReactStates st) + { + reactState = st; + } + public ReactStates GetReactState() + { + return reactState; + } + public bool HasReactState(ReactStates state) + { + return (reactState == state); + } + + public bool IsInEvadeMode() { return HasUnitState(UnitState.Evade); } + public bool IsEvadingAttacks() { return IsInEvadeMode() || CanNotReachTarget(); } + + public new CreatureAI GetAI() + { + return (CreatureAI)i_AI; + } + + public T GetAI() where T : UnitAI + { + return (T)i_AI; + } + + public override SpellSchoolMask GetMeleeDamageSchoolMask() { return m_meleeDamageSchoolMask; } + public void SetMeleeDamageSchool(SpellSchools school) { m_meleeDamageSchoolMask = (SpellSchoolMask)(1 << (int)school); } + + public sbyte GetOriginalEquipmentId() { return m_originalEquipmentId; } + public byte GetCurrentEquipmentId() { return m_equipmentId; } + public void SetCurrentEquipmentId(byte id) { m_equipmentId = id; } + + public CreatureTemplate GetCreatureTemplate() { return m_creatureInfo; } + public CreatureData GetCreatureData() { return m_creatureData; } + + public override bool LoadFromDB(ulong spawnId, Map map) + { + return LoadCreatureFromDB(spawnId, map, false); + } + + public bool hasLootRecipient() { return !m_lootRecipient.IsEmpty() || !m_lootRecipientGroup.IsEmpty(); } + + public LootModes GetLootMode() { return m_LootMode; } + public bool HasLootMode(LootModes lootMode) { return Convert.ToBoolean(m_LootMode & lootMode); } + public void SetLootMode(LootModes lootMode) { m_LootMode = lootMode; } + public void AddLootMode(LootModes lootMode) { m_LootMode |= lootMode; } + public void RemoveLootMode(LootModes lootMode) { m_LootMode &= ~lootMode; } + public void ResetLootMode() { m_LootMode = LootModes.Default; } + + public void SetNoCallAssistance(bool val) { m_AlreadyCallAssistance = val; } + public void SetNoSearchAssistance(bool val) { m_AlreadySearchedAssistance = val; } + public bool HasSearchedAssistance() { return m_AlreadySearchedAssistance; } + + MovementGeneratorType GetDefaultMovementType() { return m_defaultMovementType; } + public void SetDefaultMovementType(MovementGeneratorType mgt) { m_defaultMovementType = mgt; } + + public long GetRespawnTime() { return m_respawnTime; } + public void SetRespawnTime(uint respawn) { m_respawnTime = respawn != 0 ? Time.UnixTime + respawn : 0; } + + public uint GetRespawnDelay() { return m_respawnDelay; } + public void SetRespawnDelay(uint delay) { m_respawnDelay = delay; } + + public float GetRespawnRadius() { return m_respawnradius; } + public void SetRespawnRadius(float dist) { m_respawnradius = dist; } + + public void DoImmediateBoundaryCheck() { m_boundaryCheckTime = 0; } + uint GetCombatPulseDelay() { return m_combatPulseDelay; } + public void SetCombatPulseDelay(uint delay) // (secs) interval at which the creature pulses the entire zone into combat (only works in dungeons) + { + m_combatPulseDelay = delay; + if (m_combatPulseTime == 0 || m_combatPulseTime > delay) + m_combatPulseTime = delay; + } + + bool isRegeneratingHealth() { return m_regenHealth; } + public void setRegeneratingHealth(bool regenHealth) { m_regenHealth = regenHealth; } + + public void SetHomePosition(float x, float y, float z, float o) + { + m_homePosition.Relocate(x, y, z, o); + } + public void SetHomePosition(Position pos) + { + m_homePosition.Relocate(pos); + } + public void GetHomePosition(out float x, out float y, out float z, out float ori) + { + m_homePosition.GetPosition(out x, out y, out z, out ori); + } + public Position GetHomePosition() + { + return m_homePosition; + } + + public void SetTransportHomePosition(float x, float y, float z, float o) { m_transportHomePosition.Relocate(x, y, z, o); } + public void SetTransportHomePosition(Position pos) { m_transportHomePosition.Relocate(pos); } + public void GetTransportHomePosition(out float x, out float y, out float z, out float ori) { m_transportHomePosition.GetPosition(out x, out y, out z, out ori); } + Position GetTransportHomePosition() { return m_transportHomePosition; } + + public uint GetWaypointPath() { return m_path_id; } + public void LoadPath(uint pathid) { m_path_id = pathid; } + + public uint GetCurrentWaypointID() { return m_waypointID; } + public void UpdateWaypointID(uint wpID) { m_waypointID = wpID; } + + public CreatureGroup GetFormation() { return m_formation; } + public void SetFormation(CreatureGroup formation) { m_formation = formation; } + + void SetDisableReputationGain(bool disable) { DisableReputationGain = disable; } + public bool IsReputationGainDisabled() { return DisableReputationGain; } + public bool IsDamageEnoughForLootingAndReward() { return m_creatureInfo.FlagsExtra.HasAnyFlag(CreatureFlagsExtra.NoPlayerDamageReq) || m_PlayerDamageReq == 0; } + + public void ResetPlayerDamageReq() { m_PlayerDamageReq = (uint)(GetHealth() / 2); } + + public uint GetOriginalEntry() + { + return m_originalEntry; + } + void SetOriginalEntry(uint entry) + { + m_originalEntry = entry; + } + + public Unit SelectVictim() + { + // function provides main threat functionality + // next-victim-selection algorithm and evade mode are called + // threat list sorting etc. + + Unit target = null; + + // First checking if we have some taunt on us + var tauntAuras = GetAuraEffectsByType(AuraType.ModTaunt); + if (!tauntAuras.Empty()) + { + Unit caster = tauntAuras.Last().GetCaster(); + + // The last taunt aura caster is alive an we are happy to attack him + if (caster != null && caster.IsAlive()) + return GetVictim(); + else if (tauntAuras.Count() > 1) + { + // We do not have last taunt aura caster but we have more taunt auras, + // so find first available target + + // Auras are pushed_back, last caster will be on the end + for (var i = tauntAuras.Count - 1; i >= 0; i--) + { + caster = tauntAuras[i].GetCaster(); + if (caster != null && CanSeeOrDetect(caster, true) && IsValidAttackTarget(caster) && caster.isInAccessiblePlaceFor(ToCreature())) + { + target = caster; + break; + } + } + } + else + target = GetVictim(); + } + + if (CanHaveThreatList()) + { + if (target == null && !GetThreatManager().isThreatListEmpty()) + // No taunt aura or taunt aura caster is dead standard target selection + target = GetThreatManager().getHostilTarget(); + } + else if (!HasReactState(ReactStates.Passive)) + { + // We have player pet probably + target = getAttackerForHelper(); + if (target == null && IsSummon()) + { + Unit owner = ToTempSummon().GetOwner(); + if (owner != null) + { + if (owner.IsInCombat()) + target = owner.getAttackerForHelper(); + if (target == null) + { + foreach (var unit in owner.m_Controlled) + { + if (unit.IsInCombat()) + { + target = unit.getAttackerForHelper(); + if (target) + break; + } + } + } + } + } + } + else + return null; + + if (target != null && _IsTargetAcceptable(target) && CanCreatureAttack(target)) + { + if (!IsFocusing()) + SetInFront(target); + return target; + } + + // last case when creature must not go to evade mode: + // it in combat but attacker not make any damage and not enter to aggro radius to have record in threat list + // for example at owner command to pet attack some far away creature + // Note: creature does not have targeted movement generator but has attacker in this case + foreach (var unit in attackerList) + { + if (!CanCreatureAttack(unit) && !unit.IsTypeId(TypeId.Player) + && !unit.ToCreature().HasUnitTypeMask(UnitTypeMask.ControlableGuardian)) + return null; + } + + // @todo a vehicle may eat some mob, so mob should not evade + if (GetVehicle() != null) + return null; + + // search nearby enemy before enter evade mode + if (HasReactState(ReactStates.Aggressive)) + { + target = SelectNearestTargetInAttackDistance(m_CombatDistance != 0 ? m_CombatDistance : SharedConst.AttackDistance); + + if (target != null && _IsTargetAcceptable(target) && CanCreatureAttack(target)) + return target; + } + + var iAuras = GetAuraEffectsByType(AuraType.ModInvisibility); + if (!iAuras.Empty()) + { + foreach (var aura in iAuras) + { + if (aura.GetBase().IsPermanent()) + { + GetAI().EnterEvadeMode(); + break; + } + } + return null; + } + + // enter in evade mode in other case + GetAI().EnterEvadeMode(EvadeReason.NoHostiles); + return null; + } + } + + public class VendorItemCount + { + public VendorItemCount(uint _item, uint _count) + { + itemId = _item; + count = _count; + lastIncrementTime = Time.UnixTime; + } + public uint itemId; + public uint count; + public long lastIncrementTime; + } + + public class AssistDelayEvent : BasicEvent + { + AssistDelayEvent() { } + public AssistDelayEvent(ObjectGuid victim, Unit owner) + { + m_victim = victim; + m_owner = owner; + } + + public override bool Execute(ulong e_time, uint p_time) + { + Unit victim = Global.ObjAccessor.GetUnit(m_owner, m_victim); + if (victim != null) + { + while (!m_assistants.Empty()) + { + Creature assistant = m_owner.GetMap().GetCreature(m_assistants[0]); + m_assistants.RemoveAt(0); + + if (assistant != null && assistant.CanAssistTo(m_owner, victim)) + { + assistant.SetNoCallAssistance(true); + assistant.CombatStart(victim); + if (assistant.IsAIEnabled) + assistant.GetAI().AttackStart(victim); + } + } + } + return true; + } + public void AddAssistant(ObjectGuid guid) { m_assistants.Add(guid); } + + + ObjectGuid m_victim; + List m_assistants = new List(); + Unit m_owner; + } + + public class ForcedDespawnDelayEvent : BasicEvent + { + public ForcedDespawnDelayEvent(Creature owner, TimeSpan respawnTimer = default(TimeSpan)) + { + m_owner = owner; + m_respawnTimer = respawnTimer; + } + public override bool Execute(ulong e_time, uint p_time) + { + m_owner.DespawnOrUnsummon(0, m_respawnTimer); // since we are here, we are not TempSummon as object type cannot change during runtime + return true; + } + + Creature m_owner; + TimeSpan m_respawnTimer; + } +} diff --git a/Game/Entities/Creature/CreatureData.cs b/Game/Entities/Creature/CreatureData.cs new file mode 100644 index 000000000..2c66e0e12 --- /dev/null +++ b/Game/Entities/Creature/CreatureData.cs @@ -0,0 +1,423 @@ +/* + * Copyright (C) 2012-2017 CypherCore + * + * 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, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +using Framework.Collections; +using Framework.Constants; +using System; +using System.Collections.Generic; + +namespace Game.Entities +{ + public class CreatureTemplate + { + public uint Entry; + public uint[] DifficultyEntry = new uint[SharedConst.MaxCreatureDifficulties]; + public uint[] KillCredit = new uint[SharedConst.MaxCreatureKillCredit]; + public uint ModelId1; + public uint ModelId2; + public uint ModelId3; + public uint ModelId4; + public string Name; + public string FemaleName; + public string SubName; + public string IconName; + public uint GossipMenuId; + public short Minlevel; + public short Maxlevel; + public int HealthScalingExpansion; + public uint RequiredExpansion; + public uint VignetteID; // @todo Read Vignette.db2 + public uint Faction; + public NPCFlags Npcflag; + public float SpeedWalk; + public float SpeedRun; + public float Scale; + public CreatureEliteType Rank; + public uint DmgSchool; + public uint BaseAttackTime; + public uint RangeAttackTime; + public float BaseVariance; + public float RangeVariance; + public uint UnitClass; + public UnitFlags UnitFlags; + public uint UnitFlags2; + public uint UnitFlags3; + public uint DynamicFlags; + public CreatureFamily Family; + public TrainerType TrainerType; + public Class TrainerClass; + public Race TrainerRace; + public CreatureType CreatureType; + public CreatureTypeFlags TypeFlags; + public uint TypeFlags2; + public uint LootId; + public uint PickPocketId; + public uint SkinLootId; + public int[] Resistance = new int[7]; + public uint[] Spells = new uint[8]; + public uint VehicleId; + public uint MinGold; + public uint MaxGold; + public string AIName; + public uint MovementType; + public InhabitType InhabitType; + public float HoverHeight; + public float ModHealth; + public float ModHealthExtra; + public float ModMana; + public float ModManaExtra; + public float ModArmor; + public float ModDamage; + public float ModExperience; + public bool RacialLeader; + public uint MovementId; + public bool RegenHealth; + public uint MechanicImmuneMask; + public CreatureFlagsExtra FlagsExtra; + public uint ScriptID; + + public uint GetRandomValidModelId() + { + byte c = 0; + uint[] modelIDs = new uint[4]; + + if (ModelId1 != 0) + modelIDs[c++] = ModelId1; + if (ModelId2 != 0) + modelIDs[c++] = ModelId2; + if (ModelId3 != 0) + modelIDs[c++] = ModelId3; + if (ModelId4 != 0) + modelIDs[c++] = ModelId4; + + return c > 0 ? modelIDs[RandomHelper.IRand(0, c - 1)] : 0; + } + public uint GetFirstValidModelId() + { + if (ModelId1 != 0) + return ModelId1; + if (ModelId2 != 0) + return ModelId2; + if (ModelId3 != 0) + return ModelId3; + if (ModelId4 != 0) + return ModelId4; + return 0; + } + + public uint GetFirstInvisibleModel() + { + CreatureModelInfo modelInfo = Global.ObjectMgr.GetCreatureModelInfo(ModelId1); + if (modelInfo != null && modelInfo.IsTrigger) + return ModelId1; + + modelInfo = Global.ObjectMgr.GetCreatureModelInfo(ModelId2); + if (modelInfo != null && modelInfo.IsTrigger) + return ModelId2; + + modelInfo = Global.ObjectMgr.GetCreatureModelInfo(ModelId3); + if (modelInfo != null && modelInfo.IsTrigger) + return ModelId3; + + modelInfo = Global.ObjectMgr.GetCreatureModelInfo(ModelId4); + if (modelInfo != null && modelInfo.IsTrigger) + return ModelId4; + + return 11686; + } + + public uint GetFirstVisibleModel() + { + CreatureModelInfo modelInfo = Global.ObjectMgr.GetCreatureModelInfo(ModelId1); + if (modelInfo != null && !modelInfo.IsTrigger) + return ModelId1; + + modelInfo = Global.ObjectMgr.GetCreatureModelInfo(ModelId2); + if (modelInfo != null && !modelInfo.IsTrigger) + return ModelId2; + + modelInfo = Global.ObjectMgr.GetCreatureModelInfo(ModelId3); + if (modelInfo != null && !modelInfo.IsTrigger) + return ModelId3; + + modelInfo = Global.ObjectMgr.GetCreatureModelInfo(ModelId4); + if (modelInfo != null && !modelInfo.IsTrigger) + return ModelId4; + + return 17519; + } + + public SkillType GetRequiredLootSkill() + { + if (TypeFlags.HasAnyFlag(CreatureTypeFlags.HerbSkinningSkill)) + return SkillType.Herbalism; + else if (TypeFlags.HasAnyFlag(CreatureTypeFlags.MiningSkinningSkill)) + return SkillType.Mining; + else if (TypeFlags.HasAnyFlag(CreatureTypeFlags.EngineeringSkinningSkill)) + return SkillType.Engineering; + else + return SkillType.Skinning; // normal case + } + + public bool IsExotic() + { + return (TypeFlags & CreatureTypeFlags.ExoticPet) != 0; + } + public bool IsTameable(bool canTameExotic) + { + if (CreatureType != CreatureType.Beast || Family == CreatureFamily.None || !TypeFlags.HasAnyFlag(CreatureTypeFlags.TameablePet)) + return false; + + // if can tame exotic then can tame any tameable + return canTameExotic || !IsExotic(); + } + + public static int DifficultyIDToDifficultyEntryIndex(uint difficulty) + { + switch ((Difficulty)difficulty) + { + case Difficulty.None: + case Difficulty.Normal: + case Difficulty.Raid10N: + case Difficulty.Raid40: + case Difficulty.Scenario3ManN: + case Difficulty.NormalRaid: + return -1; + case Difficulty.Heroic: + case Difficulty.Raid25N: + case Difficulty.Scenario3ManHC: + case Difficulty.HeroicRaid: + return 0; + case Difficulty.Raid10HC: + case Difficulty.MythicKeystone: + case Difficulty.MythicRaid: + return 1; + case Difficulty.Raid25HC: + return 2; + case Difficulty.LFR: + case Difficulty.LFRNew: + case Difficulty.EventRaid: + case Difficulty.EventDungeon: + case Difficulty.EventScenario: + default: + return -1; + } + } + } + + public class CreatureBaseStats + { + public uint[] BaseHealth = new uint[(int)Expansion.Max]; + public uint BaseMana; + public uint BaseArmor; + public uint AttackPower; + public uint RangedAttackPower; + public float[] BaseDamage = new float[(int)Expansion.Max]; + + // Helpers + public uint GenerateHealth(CreatureTemplate info) + { + return (uint)Math.Ceiling(BaseHealth[info.HealthScalingExpansion] * info.ModHealth * info.ModHealthExtra); + } + + public uint GenerateMana(CreatureTemplate info) + { + // Mana can be 0. + if (BaseMana == 0) + return 0; + + return (uint)Math.Ceiling(BaseMana * info.ModMana * info.ModManaExtra); + } + + public float GenerateArmor(CreatureTemplate info) + { + return (float)Math.Ceiling(BaseArmor * info.ModArmor); + } + + public float GenerateBaseDamage(CreatureTemplate info) + { + return BaseDamage[info.HealthScalingExpansion]; + } + } + + public class CreatureLocale + { + public StringArray Name = new StringArray((int)LocaleConstant.Total); + public StringArray NameAlt = new StringArray((int)LocaleConstant.Total); + public StringArray Title = new StringArray((int)LocaleConstant.Total); + public StringArray TitleAlt = new StringArray((int)LocaleConstant.Total); + } + + public struct EquipmentItem + { + public uint ItemId; + public ushort AppearanceModId; + public ushort ItemVisual; + } + + public class EquipmentInfo + { + public EquipmentItem[] Items = new EquipmentItem[SharedConst.MaxEquipmentItems]; + } + + public class CreatureData + { + public uint id; // entry in creature_template + public ushort mapid; + public uint phaseMask; + public uint displayid; + public int equipmentId; + public float posX; + public float posY; + public float posZ; + public float orientation; + public uint spawntimesecs; + public float spawndist; + public uint currentwaypoint; + public uint curhealth; + public uint curmana; + public byte movementType; + public uint spawnMask; + public ulong npcflag; + public uint unit_flags; // enum UnitFlags mask values + public uint unit_flags2; // enum UnitFlags2 mask values + public uint unit_flags3; // enum UnitFlags3 mask values + public uint dynamicflags; + public uint phaseid; + public uint phaseGroup; + public uint ScriptId; + public bool dbData; + } + + public class CreatureModelInfo + { + public float BoundingRadius; + public float CombatReach; + public sbyte gender; + public uint DisplayIdOtherGender; + public bool IsTrigger; + } + + public class CreatureAddon + { + public uint path_id; + public uint mount; + public uint bytes1; + public uint bytes2; + public uint emote; + public ushort aiAnimKit; + public ushort movementAnimKit; + public ushort meleeAnimKit; + public uint[] auras; + } + + public class VendorItem + { + public VendorItem(uint _item, int _maxcount, uint _incrtime, uint _ExtendedCost, ItemVendorType _Type) + { + item = _item; + maxcount = (uint)_maxcount; + incrtime = _incrtime; + ExtendedCost = _ExtendedCost; + Type = _Type; + } + + public uint item; + public uint maxcount; // 0 for infinity item amount + public uint incrtime; // time for restore items amount if maxcount != 0 + public uint ExtendedCost; + public ItemVendorType Type; + + //helpers + public bool IsGoldRequired(ItemTemplate pProto) { return Convert.ToBoolean(pProto.GetFlags2() & ItemFlags2.DontIgnoreBuyPrice) || ExtendedCost == 0; } + } + + public class VendorItemData + { + List m_items = new List(); + + public VendorItem GetItem(uint slot) + { + if (slot >= m_items.Count) + return null; + + return m_items[(int)slot]; + } + public bool Empty() + { + return m_items.Count == 0; + } + public int GetItemCount() + { + return m_items.Count; + } + public void AddItem(uint item, int maxcount, uint ptime, uint ExtendedCost, ItemVendorType type) + { + m_items.Add(new VendorItem(item, maxcount, ptime, ExtendedCost, type)); + } + public bool RemoveItem(uint item_id, ItemVendorType type) + { + int i = m_items.RemoveAll(p => p.item == item_id && p.Type == type); + if (i == 0) + return false; + else + return true; + } + public VendorItem FindItemCostPair(uint item_id, uint extendedCost, ItemVendorType type) + { + return m_items.Find(p => p.item == item_id && p.ExtendedCost == extendedCost && p.Type == type); + } + public void Clear() + { + m_items.Clear(); + } + } + + public class TrainerSpell + { + public uint SpellID; + public uint MoneyCost; + public uint ReqSkillLine; + public uint ReqSkillRank; + public uint ReqLevel; + public uint[] ReqAbility = new uint[SharedConst.MaxTrainerspellAbilityReqs]; + public uint Index; + + // helpers + public bool IsCastable() + { + return ReqAbility[0] != SpellID; + } + } + + public class TrainerSpellData + { + public TrainerSpellData() + { + trainerType = 0; + } + + public Dictionary spellList = new Dictionary(); + public uint trainerType; // trainer type based at trainer spells, can be different from creature_template value. + // req. for correct show non-prof. trainers like weaponmaster, allowed values 0 and 2. + + public TrainerSpell Find(uint spell_id) + { + return spellList.LookupByKey(spell_id); + } + } +} diff --git a/Game/Entities/Creature/CreatureGroups.cs b/Game/Entities/Creature/CreatureGroups.cs new file mode 100644 index 000000000..afe08fe0c --- /dev/null +++ b/Game/Entities/Creature/CreatureGroups.cs @@ -0,0 +1,274 @@ +/* + * Copyright (C) 2012-2017 CypherCore + * + * 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, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +using Framework.Database; +using Game.Maps; +using System; +using System.Collections.Generic; + +namespace Game.Entities +{ + public class FormationMgr + { + public FormationMgr() { } + + public static void AddCreatureToGroup(uint groupId, Creature member) + { + Map map = member.GetMap(); + if (!map) + return; + + var creatureGroup = map.CreatureGroupHolder.LookupByKey(groupId); + + //Add member to an existing group + if (creatureGroup != null) + { + Log.outDebug(LogFilter.Unit, "Group found: {0}, inserting creature GUID: {1}, Group InstanceID {2}", groupId, member.GetGUID().ToString(), member.GetInstanceId()); + creatureGroup.AddMember(member); + } + //Create new group + else + { + Log.outDebug(LogFilter.Unit, "Group not found: {0}. Creating new group.", groupId); + CreatureGroup group = new CreatureGroup(groupId); + map.CreatureGroupHolder[groupId] = group; + group.AddMember(member); + } + } + + public static void RemoveCreatureFromGroup(CreatureGroup group, Creature member) + { + Log.outDebug(LogFilter.Unit, "Deleting member GUID: {0} from group {1}", group.GetId(), member.GetSpawnId()); + group.RemoveMember(member); + + if (group.isEmpty()) + { + Map map = member.GetMap(); + if (!map) + return; + + Log.outDebug(LogFilter.Unit, "Deleting group with InstanceID {0}", member.GetInstanceId()); + map.CreatureGroupHolder.Remove(group.GetId()); + } + } + + public static void LoadCreatureFormations() + { + uint oldMSTime = Time.GetMSTime(); + + CreatureGroupMap.Clear(); + + //Get group data + SQLResult result = DB.World.Query("SELECT leaderGUID, memberGUID, dist, angle, groupAI, point_1, point_2 FROM creature_formations ORDER BY leaderGUID"); + + if (result.IsEmpty()) + { + Log.outError(LogFilter.ServerLoading, "Loaded 0 creatures in formations. DB table `creature_formations` is empty!"); + return; + } + + uint count = 0; + FormationInfo group_member; + do + { + //Load group member data + group_member = new FormationInfo(); + group_member.leaderGUID = result.Read(0); + uint memberGUID = result.Read(1); + group_member.groupAI = (byte)result.Read(4); + group_member.point_1 = result.Read(5); + group_member.point_2 = result.Read(6); + //If creature is group leader we may skip loading of dist/angle + if (group_member.leaderGUID != memberGUID) + { + group_member.follow_dist = result.Read(2); + group_member.follow_angle = result.Read(3) * MathFunctions.PI / 180; + } + else + { + group_member.follow_dist = 0; + group_member.follow_angle = 0; + } + + // check data correctness + { + if (Global.ObjectMgr.GetCreatureData(group_member.leaderGUID) == null) + { + Log.outError(LogFilter.Sql, "creature_formations table leader guid {0} incorrect (not exist)", group_member.leaderGUID); + continue; + } + + if (Global.ObjectMgr.GetCreatureData(memberGUID) == null) + { + Log.outError(LogFilter.Sql, "creature_formations table member guid {0} incorrect (not exist)", memberGUID); + continue; + } + } + + CreatureGroupMap[memberGUID] = group_member; + ++count; + } + while (result.NextRow()); + + Log.outInfo(LogFilter.ServerLoading, "Loaded {0} creatures in formations in {1} ms", count, Time.GetMSTimeDiffToNow(oldMSTime)); + } + + public static Dictionary CreatureGroupMap = new Dictionary(); + } + + public class FormationInfo + { + public uint leaderGUID; + public float follow_dist; + public float follow_angle; + public byte groupAI; + public ushort point_1; + public ushort point_2; + } + + public class CreatureGroup + { + public CreatureGroup(uint id) + { + m_groupID = id; + } + + public void AddMember(Creature member) + { + Log.outDebug(LogFilter.Unit, "CreatureGroup.AddMember: Adding unit GUID: {0}.", member.GetGUID().ToString()); + + //Check if it is a leader + if (member.GetSpawnId() == m_groupID) + { + Log.outDebug(LogFilter.Unit, "Unit GUID: {0} is formation leader. Adding group.", member.GetGUID().ToString()); + m_leader = member; + } + + m_members[member] = FormationMgr.CreatureGroupMap.LookupByKey(member.GetSpawnId()); + member.SetFormation(this); + } + + public void RemoveMember(Creature member) + { + if (m_leader == member) + m_leader = null; + + m_members.Remove(member); + member.SetFormation(null); + } + + public void MemberAttackStart(Creature member, Unit target) + { + byte groupAI = FormationMgr.CreatureGroupMap[member.GetSpawnId()].groupAI; + if (groupAI == 0) + return; + + if (groupAI == 1 && member != m_leader) + return; + + foreach (var pair in m_members) + { + if (m_leader) // avoid crash if leader was killed and reset. + Log.outDebug(LogFilter.Unit, "GROUP ATTACK: group instance id {0} calls member instid {1}", m_leader.GetInstanceId(), member.GetInstanceId()); + + Creature other = pair.Key; + + // Skip self + if (other == member) + continue; + + if (!other.IsAlive()) + continue; + + if (other.GetVictim()) + continue; + + if (other.IsValidAttackTarget(target)) + other.GetAI().AttackStart(target); + } + } + + public void FormationReset(bool dismiss) + { + foreach (var creature in m_members.Keys) + { + if (creature != m_leader && creature.IsAlive()) + { + if (dismiss) + creature.GetMotionMaster().Initialize(); + else + creature.GetMotionMaster().MoveIdle(); + Log.outDebug(LogFilter.Unit, "Set {0} movement for member GUID: {1}", dismiss ? "default" : "idle", creature.GetGUID().ToString()); + } + } + m_Formed = !dismiss; + } + + public void LeaderMoveTo(float x, float y, float z) + { + //! To do: This should probably get its own movement generator or use WaypointMovementGenerator. + //! If the leader's path is known, member's path can be plotted as well using formation offsets. + if (!m_leader) + return; + + float pathangle = (float)Math.Atan2(m_leader.GetPositionY() - y, m_leader.GetPositionX() - x); + + foreach (var pair in m_members) + { + Creature member = pair.Key; + if (member == m_leader || !member.IsAlive() || member.GetVictim()) + continue; + + if (pair.Value.point_1 != 0) + if (m_leader.GetCurrentWaypointID() == pair.Value.point_1 - 1 || m_leader.GetCurrentWaypointID() == pair.Value.point_2 - 1) + pair.Value.follow_angle = (float)Math.PI * 2 - pair.Value.follow_angle; + + float angle = pair.Value.follow_angle; + float dist = pair.Value.follow_dist; + + float dx = x + (float)Math.Cos(angle + pathangle) * dist; + float dy = y + (float)Math.Sin(angle + pathangle) * dist; + float dz = z; + + GridDefines.NormalizeMapCoord(ref dx); + GridDefines.NormalizeMapCoord(ref dy); + + if (!member.IsFlying()) + member.UpdateGroundPositionZ(dx, dy, ref dz); + + if (member.IsWithinDist(m_leader, dist + 5.0f)) + member.SetUnitMovementFlags(m_leader.GetUnitMovementFlags()); + else + member.SetWalk(false); + + member.GetMotionMaster().MovePoint(0, dx, dy, dz); + member.SetHomePosition(dx, dy, dz, pathangle); + } + } + + public Creature getLeader() { return m_leader; } + public uint GetId() { return m_groupID; } + public bool isEmpty() { return m_members.Empty(); } + public bool isFormed() { return m_Formed; } + + Creature m_leader; + Dictionary m_members = new Dictionary(); + + uint m_groupID; + bool m_Formed; + } +} diff --git a/Game/Entities/Creature/Gossip.cs b/Game/Entities/Creature/Gossip.cs new file mode 100644 index 000000000..f1581ff4e --- /dev/null +++ b/Game/Entities/Creature/Gossip.cs @@ -0,0 +1,852 @@ +/* + * Copyright (C) 2012-2017 CypherCore + * + * 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, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +using Framework.Collections; +using Framework.Constants; +using Framework.GameMath; +using Game.Conditions; +using Game.DataStorage; +using Game.Entities; +using Game.Network.Packets; +using System.Collections.Generic; +using System.Diagnostics.Contracts; +using System.Linq; + +namespace Game.Misc +{ + public class GossipMenu + { + public void AddMenuItem(int menuItemId, GossipOptionIcon icon, string message, uint sender, uint action, string boxMessage, uint boxMoney, bool coded = false) + { + Contract.Assert(_menuItems.Count <= SharedConst.MaxGossipMenuItems); + + // Find a free new id - script case + if (menuItemId == -1) + { + menuItemId = 0; + if (!_menuItems.Empty()) + { + foreach (var item in _menuItems) + { + if (item.Key > menuItemId) + break; + + menuItemId = (int)item.Key + 1; + } + } + } + + GossipMenuItem menuItem = new GossipMenuItem(); + + menuItem.MenuItemIcon = (byte)icon; + menuItem.Message = message; + menuItem.IsCoded = coded; + menuItem.Sender = sender; + menuItem.OptionType = action; + menuItem.BoxMessage = boxMessage; + menuItem.BoxMoney = boxMoney; + + _menuItems[(uint)menuItemId] = menuItem; + } + + /// + /// Adds a localized gossip menu item from db by menu id and menu item id. + /// + /// menuId Gossip menu id. + /// menuItemId Gossip menu item id. + /// sender Identifier of the current menu. + /// action Custom action given to OnGossipHello. + public void AddMenuItem(uint menuId, uint menuItemId, uint sender, uint action) + { + /// Find items for given menu id. + var bounds = Global.ObjectMgr.GetGossipMenuItemsMapBounds(menuId); + /// Return if there are none. + if (bounds.Empty()) + return; + + /// Iterate over each of them. + foreach (var item in bounds) + { + // Find the one with the given menu item id. + if (item.OptionIndex != menuItemId) + continue; + + /// Store texts for localization. + string strOptionText = "", strBoxText = ""; + BroadcastTextRecord optionBroadcastText = CliDB.BroadcastTextStorage.LookupByKey(item.OptionBroadcastTextId); + BroadcastTextRecord boxBroadcastText = CliDB.BroadcastTextStorage.LookupByKey(item.BoxBroadcastTextId); + + // OptionText + if (optionBroadcastText != null) + strOptionText = Global.DB2Mgr.GetBroadcastTextValue(optionBroadcastText, GetLocale()); + else + strOptionText = item.OptionText; + + // BoxText + if (boxBroadcastText != null) + strBoxText = Global.DB2Mgr.GetBroadcastTextValue(boxBroadcastText, GetLocale()); + else + strBoxText = item.BoxText; + + // Check need of localization. + if (GetLocale() != LocaleConstant.enUS) + { + if (optionBroadcastText == null) + { + // Find localizations from database. + GossipMenuItemsLocale gossipMenuLocale = Global.ObjectMgr.GetGossipMenuItemsLocale(menuId, menuItemId); + if (gossipMenuLocale != null) + ObjectManager.GetLocaleString(gossipMenuLocale.OptionText, GetLocale(), ref strOptionText); + } + + if (boxBroadcastText == null) + { + // Find localizations from database. + GossipMenuItemsLocale gossipMenuLocale = Global.ObjectMgr.GetGossipMenuItemsLocale(menuId, menuItemId); + if (gossipMenuLocale != null) + ObjectManager.GetLocaleString(gossipMenuLocale.BoxText, GetLocale(), ref strBoxText); + } + + } + + // Add menu item with existing method. Menu item id -1 is also used in ADD_GOSSIP_ITEM macro. + AddMenuItem(-1, item.OptionIcon, strOptionText, sender, action, strBoxText, item.BoxMoney, item.BoxCoded); + } + } + + public void AddGossipMenuItemData(uint menuItemId, uint gossipActionMenuId, uint gossipActionPoi) + { + GossipMenuItemData itemData = new GossipMenuItemData(); + + itemData.GossipActionMenuId = gossipActionMenuId; + itemData.GossipActionPoi = gossipActionPoi; + + _menuItemData[menuItemId] = itemData; + } + + public uint GetMenuItemSender(uint menuItemId) + { + if (_menuItems.ContainsKey(menuItemId)) + return _menuItems.LookupByKey(menuItemId).Sender; + else + return 0; + } + + public uint GetMenuItemAction(uint menuItemId) + { + if (_menuItems.ContainsKey(menuItemId)) + return _menuItems.LookupByKey(menuItemId).OptionType; + else + return 0; + } + + public bool IsMenuItemCoded(uint menuItemId) + { + if (_menuItems.ContainsKey(menuItemId)) + return _menuItems.LookupByKey(menuItemId).IsCoded; + else + return false; + } + + public void ClearMenu() + { + _menuItems.Clear(); + _menuItemData.Clear(); + } + + public void SetMenuId(uint menu_id) { _menuId = menu_id; } + public uint GetMenuId() { return _menuId; } + public void SetSenderGUID(ObjectGuid guid) { _senderGUID = guid; } + public ObjectGuid GetSenderGUID() { return _senderGUID; } + public void SetLocale(LocaleConstant locale) { _locale = locale; } + LocaleConstant GetLocale() { return _locale; } + + public int GetMenuItemCount() + { + return _menuItems.Count; + } + + public bool IsEmpty() + { + return _menuItems.Empty(); + } + + public GossipMenuItem GetItem(uint id) + { + return _menuItems.LookupByKey(id); + } + + public GossipMenuItemData GetItemData(uint indexId) + { + return _menuItemData.LookupByKey(indexId); + } + + public Dictionary GetMenuItems() + { + return _menuItems; + } + + Dictionary _menuItems = new Dictionary(); + Dictionary _menuItemData = new Dictionary(); + uint _menuId; + ObjectGuid _senderGUID; + LocaleConstant _locale; + } + + public class PlayerMenu + { + public PlayerMenu(WorldSession session) + { + _session = session; + if (_session != null) + _gossipMenu.SetLocale(_session.GetSessionDbLocaleIndex()); + } + + public void ClearMenus() + { + _gossipMenu.ClearMenu(); + _questMenu.ClearMenu(); + } + + public void SendGossipMenu(uint titleTextId, ObjectGuid objectGUID) + { + _gossipMenu.SetSenderGUID(objectGUID); + + GossipMessagePkt packet = new GossipMessagePkt(); + packet.GossipGUID = objectGUID; + packet.GossipID = (int)_gossipMenu.GetMenuId(); + packet.TextID = (int)titleTextId; + + uint count = 0; + foreach (var pair in _gossipMenu.GetMenuItems()) + { + ClientGossipOptions opt = new ClientGossipOptions(); + GossipMenuItem item = pair.Value; + opt.ClientOption = (int)pair.Key; + opt.OptionNPC = item.MenuItemIcon; + opt.OptionFlags = (byte)(item.IsCoded ? 1 : 0); // makes pop up box password + opt.OptionCost = (int)item.BoxMoney; // money required to open menu, 2.0.3 + opt.Text = item.Message; // text for gossip item + opt.Confirm = item.BoxMessage; // accept text (related to money) pop up box, 2.0.3 + packet.GossipOptions.Add(opt); + + ++count; + } + + count = 0; + for (byte i = 0; i < _questMenu.GetMenuItemCount(); ++i) + { + QuestMenuItem item = _questMenu.GetItem(i); + uint questID = item.QuestId; + Quest quest = Global.ObjectMgr.GetQuestTemplate(questID); + if (quest != null) + { + ClientGossipText text = new ClientGossipText(); + text.QuestID = (int)questID; + text.QuestType = item.QuestIcon; + text.QuestLevel = quest.Level; + text.QuestFlags = (int)quest.Flags; + text.QuestFlagsEx = (int)quest.FlagsEx; + text.Repeatable = quest.IsRepeatable(); + + text.QuestTitle = quest.LogTitle; + LocaleConstant locale = _session.GetSessionDbLocaleIndex(); + if (locale != LocaleConstant.enUS) + { + QuestTemplateLocale localeData = Global.ObjectMgr.GetQuestLocale(quest.Id); + if (localeData != null) + ObjectManager.GetLocaleString(localeData.LogTitle, locale, ref text.QuestTitle); + } + + packet.GossipText.Add(text); + ++count; + } + } + + _session.SendPacket(packet); + } + + public void SendCloseGossip() + { + _gossipMenu.SetSenderGUID(ObjectGuid.Empty); + + _session.SendPacket(new GossipComplete()); + } + + public void SendPointOfInterest(uint id) + { + PointOfInterest pointOfInterest = Global.ObjectMgr.GetPointOfInterest(id); + if (pointOfInterest == null) + { + Log.outError(LogFilter.Sql, "Request to send non-existing PointOfInterest (Id: {0}), ignored.", id); + return; + } + + GossipPOI packet = new GossipPOI(); + packet.Name = pointOfInterest.Name; + + LocaleConstant locale = _session.GetSessionDbLocaleIndex(); + if (locale != LocaleConstant.enUS) + { + PointOfInterestLocale localeData = Global.ObjectMgr.GetPointOfInterestLocale(id); + if (localeData != null) + ObjectManager.GetLocaleString(localeData.Name, locale, ref packet.Name); + } + + packet.Flags = pointOfInterest.Flags; + packet.Pos = pointOfInterest.Pos; + packet.Icon = pointOfInterest.Icon; + packet.Importance = pointOfInterest.Importance; + + _session.SendPacket(packet); + } + + public void SendQuestGiverQuestList(ObjectGuid guid) + { + QuestGiverQuestList questList = new QuestGiverQuestList(); + questList.QuestGiverGUID = guid; + + QuestGreeting questGreeting = Global.ObjectMgr.GetQuestGreeting(guid); + if (questGreeting != null) + { + questList.GreetEmoteDelay = questGreeting.greetEmoteDelay; + questList.GreetEmoteType = questGreeting.greetEmoteType; + questList.Greeting = questGreeting.greeting; + } + else + Log.outError(LogFilter.Server, "Guid: {0} - No quest greeting found.", guid.ToString()); + + for (var i = 0; i < _questMenu.GetMenuItemCount(); ++i) + { + QuestMenuItem questMenuItem = _questMenu.GetItem(i); + + uint questID = questMenuItem.QuestId; + Quest quest = Global.ObjectMgr.GetQuestTemplate(questID); + if (quest != null) + { + string title = quest.LogTitle; + + LocaleConstant locale = _session.GetSessionDbLocaleIndex(); + if (locale != LocaleConstant.enUS) + { + QuestTemplateLocale localeData = Global.ObjectMgr.GetQuestLocale(quest.Id); + if (localeData != null) + ObjectManager.GetLocaleString(localeData.LogTitle, locale, ref title); + } + + GossipTextData text = new GossipTextData(); + text.QuestID = questID; + text.QuestType = questMenuItem.QuestIcon; + text.QuestLevel = (uint)quest.Level; + text.QuestFlags = (uint)quest.Flags; + text.QuestFlagsEx = (uint)quest.FlagsEx; + text.Repeatable = false; // NYI + text.QuestTitle = title; + questList.GossipTexts.Add(text); + } + } + + _session.SendPacket(questList); + } + + public void SendQuestGiverStatus(QuestGiverStatus questStatus, ObjectGuid npcGUID) + { + var packet = new QuestGiverStatusPkt(); + packet.QuestGiver.Guid = npcGUID; + packet.QuestGiver.Status = questStatus; + + _session.SendPacket(packet); + } + + public void SendQuestGiverQuestDetails(Quest quest, ObjectGuid npcGUID, bool activateAccept) + { + QuestGiverQuestDetails packet = new QuestGiverQuestDetails(); + + packet.QuestTitle = quest.LogTitle; + packet.LogDescription = quest.LogDescription; + packet.DescriptionText = quest.QuestDescription; + packet.PortraitGiverText = quest.PortraitGiverText; + packet.PortraitGiverName = quest.PortraitGiverName; + packet.PortraitTurnInText = quest.PortraitTurnInText; + packet.PortraitTurnInName = quest.PortraitTurnInName; + + LocaleConstant locale = _session.GetSessionDbLocaleIndex(); + if (locale != LocaleConstant.enUS) + { + QuestTemplateLocale localeData = Global.ObjectMgr.GetQuestLocale(quest.Id); + if (localeData != null) + { + ObjectManager.GetLocaleString(localeData.LogTitle, locale, ref packet.QuestTitle); + ObjectManager.GetLocaleString(localeData.LogDescription, locale, ref packet.LogDescription); + ObjectManager.GetLocaleString(localeData.QuestDescription, locale, ref packet.DescriptionText); + ObjectManager.GetLocaleString(localeData.PortraitGiverText, locale, ref packet.PortraitGiverText); + ObjectManager.GetLocaleString(localeData.PortraitGiverName, locale, ref packet.PortraitGiverName); + ObjectManager.GetLocaleString(localeData.PortraitTurnInText, locale, ref packet.PortraitTurnInText); + ObjectManager.GetLocaleString(localeData.PortraitTurnInName, locale, ref packet.PortraitTurnInName); + } + } + + packet.QuestGiverGUID = npcGUID; + packet.InformUnit = _session.GetPlayer().GetDivider(); + packet.QuestID = quest.Id; + packet.PortraitGiver = quest.QuestGiverPortrait; + packet.PortraitTurnIn = quest.QuestTurnInPortrait; + packet.AutoLaunched = activateAccept; + packet.QuestFlags[0] = (uint)quest.Flags; + packet.QuestFlags[1] = (uint)quest.FlagsEx; + packet.SuggestedPartyMembers = quest.SuggestedPlayers; + + if (quest.SourceSpellID != 0) + packet.LearnSpells.Add(quest.SourceSpellID); + + quest.BuildQuestRewards(packet.Rewards, _session.GetPlayer()); + + for (int i = 0; i < SharedConst.QuestEmoteCount; ++i) + { + var emote = new QuestDescEmote(quest.DetailsEmote[i], quest.DetailsEmoteDelay[i]); + packet.DescEmotes.Add(emote); + } + + var objs = quest.Objectives; + for (int i = 0; i < objs.Count; ++i) + { + var obj = new QuestObjectiveSimple(); + obj.ID = objs[i].ID; + obj.ObjectID = objs[i].ObjectID; + obj.Amount = objs[i].Amount; + obj.Type = (byte)objs[i].Type; + packet.Objectives.Add(obj); + } + + _session.SendPacket(packet); + } + + public void SendQuestQueryResponse(Quest quest) + { + QueryQuestInfoResponse packet = new QueryQuestInfoResponse(); + + packet.Allow = true; + packet.QuestID = quest.Id; + + packet.Info.LogTitle = quest.LogTitle; + packet.Info.LogDescription = quest.LogDescription; + packet.Info.QuestDescription = quest.QuestDescription; + packet.Info.AreaDescription = quest.AreaDescription; + packet.Info.QuestCompletionLog = quest.QuestCompletionLog; + packet.Info.PortraitGiverText = quest.PortraitGiverText; + packet.Info.PortraitGiverName = quest.PortraitGiverName; + packet.Info.PortraitTurnInText = quest.PortraitTurnInText; + packet.Info.PortraitTurnInName = quest.PortraitTurnInName; + + LocaleConstant locale = _session.GetSessionDbLocaleIndex(); + if (locale != LocaleConstant.enUS) + { + QuestTemplateLocale questTemplateLocale = Global.ObjectMgr.GetQuestLocale(quest.Id); + if (questTemplateLocale != null) + { + ObjectManager.GetLocaleString(questTemplateLocale.LogTitle, locale, ref packet.Info.LogTitle); + ObjectManager.GetLocaleString(questTemplateLocale.LogDescription, locale, ref packet.Info.LogDescription); + ObjectManager.GetLocaleString(questTemplateLocale.QuestDescription, locale, ref packet.Info.QuestDescription); + ObjectManager.GetLocaleString(questTemplateLocale.AreaDescription, locale, ref packet.Info.AreaDescription); + ObjectManager.GetLocaleString(questTemplateLocale.QuestCompletionLog, locale, ref packet.Info.QuestCompletionLog); + ObjectManager.GetLocaleString(questTemplateLocale.PortraitGiverText, locale, ref packet.Info.PortraitGiverText); + ObjectManager.GetLocaleString(questTemplateLocale.PortraitGiverName, locale, ref packet.Info.PortraitGiverName); + ObjectManager.GetLocaleString(questTemplateLocale.PortraitTurnInText, locale, ref packet.Info.PortraitTurnInText); + ObjectManager.GetLocaleString(questTemplateLocale.PortraitTurnInName, locale, ref packet.Info.PortraitTurnInName); + } + } + + packet.Info.QuestType = (int)quest.Type; + packet.Info.QuestLevel = quest.Level; + packet.Info.QuestPackageID = quest.PackageID; + packet.Info.QuestMinLevel = quest.MinLevel; + packet.Info.QuestSortID = quest.QuestSortID; + packet.Info.QuestInfoID = quest.QuestInfoID; + packet.Info.SuggestedGroupNum = quest.SuggestedPlayers; + packet.Info.RewardNextQuest = quest.NextQuestInChain; + packet.Info.RewardXPDifficulty = quest.RewardXPDifficulty; + packet.Info.RewardXPMultiplier = quest.RewardXPMultiplier; + + if (!quest.HasFlag(QuestFlags.HiddenRewards)) + packet.Info.RewardMoney = quest.RewardMoney < 0 ? quest.RewardMoney : (int)_session.GetPlayer().GetQuestMoneyReward(quest); + + packet.Info.RewardMoneyDifficulty = quest.RewardMoneyDifficulty; + packet.Info.RewardMoneyMultiplier = quest.RewardMoneyMultiplier; + packet.Info.RewardBonusMoney = quest.RewardBonusMoney; + for (byte i = 0; i < SharedConst.QuestRewardDisplaySpellCount; ++i) + packet.Info.RewardDisplaySpell[i] = quest.RewardDisplaySpell[i]; + + packet.Info.RewardSpell = quest.RewardSpell; + + packet.Info.RewardHonor = quest.RewardHonor; + packet.Info.RewardKillHonor = quest.RewardKillHonor; + + packet.Info.RewardArtifactXPDifficulty = (int)quest.RewardArtifactXPDifficulty; + packet.Info.RewardArtifactXPMultiplier = quest.RewardArtifactXPMultiplier; + packet.Info.RewardArtifactCategoryID = (int)quest.RewardArtifactCategoryID; + + packet.Info.StartItem = quest.SourceItemId; + packet.Info.Flags = (uint)quest.Flags; + packet.Info.FlagsEx = (uint)quest.FlagsEx; + packet.Info.RewardTitle = quest.RewardTitleId; + packet.Info.RewardArenaPoints = quest.RewardArenaPoints; + packet.Info.RewardSkillLineID = quest.RewardSkillId; + packet.Info.RewardNumSkillUps = quest.RewardSkillPoints; + packet.Info.RewardFactionFlags = quest.RewardReputationMask; + packet.Info.PortraitGiver = quest.QuestGiverPortrait; + packet.Info.PortraitTurnIn = quest.QuestTurnInPortrait; + + for (byte i = 0; i < SharedConst.QuestItemDropCount; ++i) + { + packet.Info.ItemDrop[i] = (int)quest.ItemDrop[i]; + packet.Info.ItemDropQuantity[i] = (int)quest.ItemDropQuantity[i]; + } + + if (!quest.HasFlag(QuestFlags.HiddenRewards)) + { + for (byte i = 0; i < SharedConst.QuestRewardItemCount; ++i) + { + packet.Info.RewardItems[i] = quest.RewardItemId[i]; + packet.Info.RewardAmount[i] = quest.RewardItemCount[i]; + } + for (byte i = 0; i < SharedConst.QuestRewardChoicesCount; ++i) + { + packet.Info.UnfilteredChoiceItems[i].ItemID = quest.RewardChoiceItemId[i]; + packet.Info.UnfilteredChoiceItems[i].Quantity = quest.RewardChoiceItemCount[i]; + } + } + + for (byte i = 0; i < SharedConst.QuestRewardReputationsCount; ++i) + { + packet.Info.RewardFactionID[i] = quest.RewardFactionId[i]; + packet.Info.RewardFactionValue[i] = quest.RewardFactionValue[i]; + packet.Info.RewardFactionOverride[i] = quest.RewardFactionOverride[i]; + packet.Info.RewardFactionCapIn[i] = (int)quest.RewardFactionCapIn[i]; + } + + packet.Info.POIContinent = quest.POIContinent; + packet.Info.POIx = quest.POIx; + packet.Info.POIy = quest.POIy; + packet.Info.POIPriority = quest.POIPriority; + + packet.Info.AllowableRaces = quest.AllowableRaces; + packet.Info.QuestRewardID = (int)quest.QuestRewardID; + packet.Info.Expansion = quest.Expansion; + + foreach (QuestObjective questObjective in quest.Objectives) + { + if (locale != LocaleConstant.enUS) + { + QuestObjectivesLocale questObjectivesLocaleData = Global.ObjectMgr.GetQuestObjectivesLocale(questObjective.ID); + if (questObjectivesLocaleData != null) + ObjectManager.GetLocaleString(questObjectivesLocaleData.Description, locale, ref questObjective.Description); + } + + packet.Info.Objectives.Add(questObjective); + } + + for (int i = 0; i < SharedConst.QuestRewardCurrencyCount; ++i) + { + packet.Info.RewardCurrencyID[i] = quest.RewardCurrencyId[i]; + packet.Info.RewardCurrencyQty[i] = quest.RewardCurrencyCount[i]; + } + + packet.Info.AcceptedSoundKitID = quest.SoundAccept; + packet.Info.CompleteSoundKitID = quest.SoundTurnIn; + packet.Info.AreaGroupID = quest.AreaGroupID; + packet.Info.TimeAllowed = quest.LimitTime; + + _session.SendPacket(packet); + } + + public void SendQuestGiverOfferReward(Quest quest, ObjectGuid npcGUID, bool enableNext) + { + QuestGiverOfferRewardMessage packet = new QuestGiverOfferRewardMessage(); + + packet.QuestTitle = quest.LogTitle; + packet.RewardText = quest.OfferRewardText; + packet.PortraitGiverText = quest.PortraitGiverText; + packet.PortraitGiverName = quest.PortraitGiverName; + packet.PortraitTurnInText = quest.PortraitTurnInText; + packet.PortraitTurnInName = quest.PortraitTurnInName; + + LocaleConstant locale = _session.GetSessionDbLocaleIndex(); + if (locale != LocaleConstant.enUS) + { + QuestTemplateLocale localeData = Global.ObjectMgr.GetQuestLocale(quest.Id); + if (localeData != null) + { + ObjectManager.GetLocaleString(localeData.LogTitle, locale, ref packet.QuestTitle); + ObjectManager.GetLocaleString(localeData.PortraitGiverText, locale, ref packet.PortraitGiverText); + ObjectManager.GetLocaleString(localeData.PortraitGiverName, locale, ref packet.PortraitGiverName); + ObjectManager.GetLocaleString(localeData.PortraitTurnInText, locale, ref packet.PortraitTurnInText); + ObjectManager.GetLocaleString(localeData.PortraitTurnInName, locale, ref packet.PortraitTurnInName); + } + + QuestOfferRewardLocale questOfferRewardLocale = Global.ObjectMgr.GetQuestOfferRewardLocale(quest.Id); + if (questOfferRewardLocale != null) + ObjectManager.GetLocaleString(questOfferRewardLocale.RewardText, locale, ref packet.RewardText); + } + + QuestGiverOfferReward offer = new QuestGiverOfferReward(); + + quest.BuildQuestRewards(offer.Rewards, _session.GetPlayer()); + offer.QuestGiverGUID = npcGUID; + + // Is there a better way? what about game objects? + Creature creature = ObjectAccessor.GetCreature(_session.GetPlayer(), npcGUID); + if (creature) + offer.QuestGiverCreatureID = creature.GetCreatureTemplate().Entry; + + offer.QuestID = quest.Id; + offer.AutoLaunched = enableNext; + offer.SuggestedPartyMembers = quest.SuggestedPlayers; + + for (uint i = 0; i < SharedConst.QuestEmoteCount && quest.OfferRewardEmote[i] != 0; ++i) + offer.Emotes.Add(new QuestDescEmote(quest.OfferRewardEmote[i], quest.OfferRewardEmoteDelay[i])); + + offer.QuestFlags[0] = (uint)quest.Flags; + offer.QuestFlags[1] = (uint)quest.FlagsEx; + + packet.PortraitTurnIn = quest.QuestTurnInPortrait; + packet.PortraitGiver = quest.QuestGiverPortrait; + packet.QuestPackageID = quest.PackageID; + + packet.QuestData = offer; + + _session.SendPacket(packet); + } + + public void SendQuestGiverRequestItems(Quest quest, ObjectGuid npcGUID, bool canComplete, bool closeOnCancel) + { + // We can always call to RequestItems, but this packet only goes out if there are actually + // items. Otherwise, we'll skip straight to the OfferReward + + if (!quest.HasSpecialFlag(QuestSpecialFlags.Deliver) && canComplete) + { + SendQuestGiverOfferReward(quest, npcGUID, true); + return; + } + + QuestGiverRequestItems packet = new QuestGiverRequestItems(); + + packet.QuestTitle = quest.LogTitle; + packet.CompletionText = quest.RequestItemsText; + + LocaleConstant locale = _session.GetSessionDbLocaleIndex(); + if (locale != LocaleConstant.enUS) + { + QuestTemplateLocale localeData = Global.ObjectMgr.GetQuestLocale(quest.Id); + if (localeData != null) + ObjectManager.GetLocaleString(localeData.LogTitle, locale, ref packet.QuestTitle); + + QuestRequestItemsLocale questRequestItemsLocale = Global.ObjectMgr.GetQuestRequestItemsLocale(quest.Id); + if (questRequestItemsLocale != null) + ObjectManager.GetLocaleString(questRequestItemsLocale.CompletionText, locale, ref packet.CompletionText); + } + + packet.QuestGiverGUID = npcGUID; + + // Is there a better way? what about game objects? + Creature creature = ObjectAccessor.GetCreature(_session.GetPlayer(), npcGUID); + if (creature) + packet.QuestGiverCreatureID = creature.GetCreatureTemplate().Entry; + + packet.QuestID = quest.Id; + + if (canComplete) + { + packet.CompEmoteDelay = quest.EmoteOnCompleteDelay; + packet.CompEmoteType = quest.EmoteOnComplete; + } + else + { + packet.CompEmoteDelay = quest.EmoteOnIncompleteDelay; + packet.CompEmoteType = quest.EmoteOnIncomplete; + } + + packet.QuestFlags[0] = (uint)quest.Flags; + packet.QuestFlags[1] = (uint)quest.FlagsEx; + packet.SuggestPartyMembers = quest.SuggestedPlayers; + packet.StatusFlags = 0xDF; // Unk, send common value + + packet.MoneyToGet = 0; + foreach (QuestObjective obj in quest.Objectives) + { + switch (obj.Type) + { + case QuestObjectiveType.Item: + packet.Collect.Add(new QuestObjectiveCollect((uint)obj.ObjectID, obj.Amount, (uint)obj.Flags)); + break; + case QuestObjectiveType.Currency: + packet.Currency.Add(new QuestCurrency((uint)obj.ObjectID, obj.Amount)); + break; + case QuestObjectiveType.Money: + packet.MoneyToGet += obj.Amount; + break; + default: + break; + } + } + + packet.AutoLaunched = closeOnCancel; + + _session.SendPacket(packet); + } + + public GossipMenu GetGossipMenu() { return _gossipMenu; } + public QuestMenu GetQuestMenu() { return _questMenu; } + + bool IsEmpty() { return _gossipMenu.IsEmpty() && _questMenu.IsEmpty(); } + + public uint GetGossipOptionSender(uint selection) { return _gossipMenu.GetMenuItemSender(selection); } + public uint GetGossipOptionAction(uint selection) { return _gossipMenu.GetMenuItemAction(selection); } + public bool IsGossipOptionCoded(uint selection) { return _gossipMenu.IsMenuItemCoded(selection); } + + GossipMenu _gossipMenu = new GossipMenu(); + QuestMenu _questMenu = new QuestMenu(); + WorldSession _session; + } + + public class QuestMenu + { + public QuestMenu() { } + + public void AddMenuItem(uint QuestId, byte Icon) + { + if (Global.ObjectMgr.GetQuestTemplate(QuestId) == null) + return; + + QuestMenuItem questMenuItem = new QuestMenuItem(); + + questMenuItem.QuestId = QuestId; + questMenuItem.QuestIcon = Icon; + + _questMenuItems.Add(questMenuItem); + } + + bool HasItem(uint questId) + { + foreach (var item in _questMenuItems) + if (item.QuestId == questId) + return true; + + return false; + } + + public void ClearMenu() + { + _questMenuItems.Clear(); + } + + public int GetMenuItemCount() + { + return _questMenuItems.Count(); + } + + public bool IsEmpty() + { + return _questMenuItems.Empty(); + } + + public QuestMenuItem GetItem(int index) + { + return _questMenuItems.LookupByIndex(index); + } + + List _questMenuItems = new List(); + } + + public struct QuestMenuItem + { + public uint QuestId; + public byte QuestIcon; + } + + public class GossipMenuItem + { + public byte MenuItemIcon; + public bool IsCoded; + public string Message; + public uint Sender; + public uint OptionType; + public string BoxMessage; + public uint BoxMoney; + } + + public class GossipMenuItemData + { + public uint GossipActionMenuId; // MenuId of the gossip triggered by this action + public uint GossipActionPoi; + } + + public struct NpcTextData + { + public float Probability; + public uint BroadcastTextID; + } + + public class NpcText + { + public NpcTextData[] Data = new NpcTextData[SharedConst.MaxNpcTextOptions]; + } + + public class PageTextLocale + { + public StringArray Text = new StringArray((int)LocaleConstant.Total); + } + + public class GossipMenuItems + { + public uint MenuId; + public uint OptionIndex; + public GossipOptionIcon OptionIcon; + public string OptionText; + public uint OptionBroadcastTextId; + public GossipOption OptionType; + public NPCFlags OptionNpcflag; + public uint ActionMenuId; + public uint ActionPoiId; + public bool BoxCoded; + public uint BoxMoney; + public string BoxText; + public uint BoxBroadcastTextId; + public List Conditions = new List(); + } + + public class PointOfInterest + { + public uint ID; + public Vector2 Pos; + public uint Icon; + public uint Flags; + public uint Importance; + public string Name; + } + + public class PointOfInterestLocale + { + public StringArray Name = new StringArray((int)LocaleConstant.Total); + } + + public class GossipMenus + { + public uint entry; + public uint text_id; + public List conditions = new List(); + } +} diff --git a/Game/Entities/DynamicObject.cs b/Game/Entities/DynamicObject.cs new file mode 100644 index 000000000..9270d9036 --- /dev/null +++ b/Game/Entities/DynamicObject.cs @@ -0,0 +1,265 @@ +/* + * Copyright (C) 2012-2017 CypherCore + * + * 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, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +using Framework.Constants; +using Game.Spells; +using System.Diagnostics.Contracts; + +namespace Game.Entities +{ + public class DynamicObject : WorldObject + { + public DynamicObject(bool isWorldObject) : base(isWorldObject) + { + objectTypeMask |= TypeMask.DynamicObject; + objectTypeId = TypeId.DynamicObject; + + m_updateFlag = UpdateFlag.StationaryPosition; + + valuesCount = (int)DynamicObjectFields.End; + } + + public override void Dispose() + { + // make sure all references were properly removed + Contract.Assert(_aura == null); + Contract.Assert(!_caster); + Contract.Assert(!_isViewpoint); + _removedAura = null; + + base.Dispose(); + } + + public override void AddToWorld() + { + // Register the dynamicObject for guid lookup and for caster + if (!IsInWorld) + { + GetMap().GetObjectsStore().Add(GetGUID(), this); + base.AddToWorld(); + BindToCaster(); + } + } + + public override void RemoveFromWorld() + { + // Remove the dynamicObject from the accessor and from all lists of objects in world + if (IsInWorld) + { + if (_isViewpoint) + RemoveCasterViewpoint(); + + if (_aura != null) + RemoveAura(); + + // dynobj could get removed in Aura.RemoveAura + if (!IsInWorld) + return; + + UnbindFromCaster(); + base.RemoveFromWorld(); + GetMap().GetObjectsStore().Remove(GetGUID()); + } + } + + public bool CreateDynamicObject(ulong guidlow, Unit caster, SpellInfo spell, Position pos, float radius, DynamicObjectType type, uint spellXSpellVisualId) + { + _spellXSpellVisualId = spellXSpellVisualId; + SetMap(caster.GetMap()); + Relocate(pos); + if (!IsPositionValid()) + { + Log.outError(LogFilter.Server, "DynamicObject (spell {0}) not created. Suggested coordinates isn't valid (X: {1} Y: {2})", spell.Id, GetPositionX(), GetPositionY()); + return false; + } + + _Create(ObjectGuid.Create(HighGuid.DynamicObject, GetMapId(), spell.Id, guidlow)); + SetPhaseMask(caster.GetPhaseMask(), false); + + SetEntry(spell.Id); + SetObjectScale(1f); + SetGuidValue(DynamicObjectFields.Caster, caster.GetGUID()); + + SetUInt32Value(DynamicObjectFields.Type, (uint)type); + SetUInt32Value(DynamicObjectFields.SpellXSpellVisualId, spellXSpellVisualId); + SetUInt32Value(DynamicObjectFields.SpellId, spell.Id); + SetFloatValue(DynamicObjectFields.Radius, radius); + SetUInt32Value(DynamicObjectFields.CastTime, Time.GetMSTime()); + + if (IsWorldObject()) + setActive(true); //must before add to map to be put in world container + + Transport transport = caster.GetTransport(); + if (transport) + { + float x, y, z, o; + pos.GetPosition(out x, out y, out z, out o); + transport.CalculatePassengerOffset(ref x, ref y, ref z, ref o); + m_movementInfo.transport.pos.Relocate(x, y, z, o); + + // This object must be added to transport before adding to map for the client to properly display it + transport.AddPassenger(this); + } + + if (!GetMap().AddToMap(this)) + { + // Returning false will cause the object to be deleted - remove from transport + if (transport) + transport.RemovePassenger(this); + return false; + } + + return true; + } + + public override void Update(uint diff) + { + // caster has to be always available and in the same map + Contract.Assert(_caster != null); + Contract.Assert(_caster.GetMap() == GetMap()); + + bool expired = false; + + if (_aura != null) + { + if (!_aura.IsRemoved()) + _aura.UpdateOwner(diff, this); + + // _aura may be set to null in Aura.UpdateOwner call + if (_aura != null && (_aura.IsRemoved() || _aura.IsExpired())) + expired = true; + } + else + { + if (GetDuration() > diff) + _duration -= (int)diff; + else + expired = true; + } + + if (expired) + Remove(); + else + Global.ScriptMgr.OnDynamicObjectUpdate(this, diff); + } + + public void Remove() + { + if (IsInWorld) + { + RemoveFromWorld(); + AddObjectToRemoveList(); + } + } + + int GetDuration() + { + if (_aura == null) + return _duration; + else + return _aura.GetDuration(); + } + + public void SetDuration(int newDuration) + { + if (_aura == null) + _duration = newDuration; + else + _aura.SetDuration(newDuration); + } + + public void Delay(int delaytime) + { + SetDuration(GetDuration() - delaytime); + } + + public void SetAura(Aura aura) + { + Contract.Assert(_aura == null && aura != null); + _aura = aura; + } + + void RemoveAura() + { + Contract.Assert(_aura != null && _removedAura == null); + _removedAura = _aura; + _aura = null; + if (!_removedAura.IsRemoved()) + _removedAura._Remove(AuraRemoveMode.Default); + } + + public void SetCasterViewpoint() + { + Player caster = _caster.ToPlayer(); + if (caster != null) + { + caster.SetViewpoint(this, true); + _isViewpoint = true; + } + } + + void RemoveCasterViewpoint() + { + Player caster = _caster.ToPlayer(); + if (caster != null) + { + caster.SetViewpoint(this, false); + _isViewpoint = false; + } + } + + void BindToCaster() + { + Contract.Assert(_caster == null); + _caster = Global.ObjAccessor.GetUnit(this, GetCasterGUID()); + Contract.Assert(_caster != null); + Contract.Assert(_caster.GetMap() == GetMap()); + _caster._RegisterDynObject(this); + } + + void UnbindFromCaster() + { + Contract.Assert(_caster != null); + _caster._UnregisterDynObject(this); + _caster = null; + } + + public SpellInfo GetSpellInfo() + { + return Global.SpellMgr.GetSpellInfo(GetSpellId()); + } + + public Unit GetCaster() { return _caster; } + public uint GetSpellId() { return GetUInt32Value(DynamicObjectFields.SpellId); } + public ObjectGuid GetCasterGUID() { return GetGuidValue(DynamicObjectFields.Caster); } + public float GetRadius() { return GetFloatValue(DynamicObjectFields.Radius); } + + Aura _aura; + Aura _removedAura; + Unit _caster; + int _duration; // for non-aura dynobjects + uint _spellXSpellVisualId; + bool _isViewpoint; + } + + public enum DynamicObjectType + { + Portal = 0x0, // unused + AreaSpell = 0x1, + FarsightFocus = 0x2 + } +} diff --git a/Game/Entities/GameObject/GameObject.cs b/Game/Entities/GameObject/GameObject.cs new file mode 100644 index 000000000..952dfdd84 --- /dev/null +++ b/Game/Entities/GameObject/GameObject.cs @@ -0,0 +1,2729 @@ +/* + * Copyright (C) 2012-2017 CypherCore + * + * 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, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +using Framework.Constants; +using Framework.Database; +using Framework.GameMath; +using Framework.IO; +using Game.AI; +using Game.BattleGrounds; +using Game.Collision; +using Game.DataStorage; +using Game.Groups; +using Game.Loots; +using Game.Maps; +using Game.Network.Packets; +using Game.Spells; +using System; +using System.Collections.Generic; +using System.Diagnostics.Contracts; +using System.Linq; + +namespace Game.Entities +{ + public class GameObject : WorldObject + { + public GameObject() : base(false) + { + objectTypeMask |= TypeMask.GameObject; + objectTypeId = TypeId.GameObject; + + m_updateFlag = UpdateFlag.StationaryPosition | UpdateFlag.Rotation; + + valuesCount = (int)GameObjectFields.End; + m_respawnDelayTime = 300; + m_lootState = LootState.NotReady; + m_spawnedByDefault = true; + + ResetLootMode(); // restore default loot mode + m_stationaryPosition = new Position(); + } + + public override void Dispose() + { + m_AI = null; + m_model = null; + if (m_goInfo != null && m_goInfo.type == GameObjectTypes.Transport) + m_goValue.Transport.StopFrames.Clear(); + + base.Dispose(); + } + + public bool AIM_Initialize() + { + m_AI = AISelector.SelectGameObjectAI(this); + + if (m_AI == null) + return false; + + m_AI.InitializeAI(); + return true; + } + + public string GetAIName() + { + GameObjectTemplate got = Global.ObjectMgr.GetGameObjectTemplate(GetEntry()); + if (got != null) + return got.AIName; + + return ""; + } + + public override void CleanupsBeforeDelete(bool finalCleanup) + { + base.CleanupsBeforeDelete(finalCleanup); + + RemoveFromOwner(); + } + + void RemoveFromOwner() + { + ObjectGuid ownerGUID = GetOwnerGUID(); + if (ownerGUID.IsEmpty()) + return; + + Unit owner = Global.ObjAccessor.GetUnit(this, ownerGUID); + if (owner) + { + owner.RemoveGameObject(this, false); + Contract.Assert(GetOwnerGUID().IsEmpty()); + return; + } + + // This happens when a mage portal is despawned after the caster changes map (for example using the portal) + Log.outDebug(LogFilter.Server, "Removed GameObject (GUID: {0} Entry: {1} SpellId: {2} LinkedGO: {3}) that just lost any reference to the owner {4} GO list", + GetGUID().ToString(), GetGoInfo().entry, m_spellId, GetGoInfo().GetLinkedGameObjectEntry(), ownerGUID.ToString()); + SetOwnerGUID(ObjectGuid.Empty); + } + + public override void AddToWorld() + { + //- Register the gameobject for guid lookup + if (!IsInWorld) + { + if (m_zoneScript != null) + m_zoneScript.OnGameObjectCreate(this); + + GetMap().GetObjectsStore().Add(GetGUID(), this); + if (m_spawnId != 0) + GetMap().GetGameObjectBySpawnIdStore().Add(m_spawnId, this); + + // The state can be changed after GameObject.Create but before GameObject.AddToWorld + bool toggledState = GetGoType() == GameObjectTypes.Chest ? getLootState() == LootState.Ready : (GetGoState() == GameObjectState.Ready || IsTransport()); + if (m_model != null) + { + Transport trans = ToTransport(); + if (trans) + trans.SetDelayedAddModelToMap(); + else + GetMap().InsertGameObjectModel(m_model); + } + + EnableCollision(toggledState); + base.AddToWorld(); + } + } + + public override void RemoveFromWorld() + { + //- Remove the gameobject from the accessor + if (IsInWorld) + { + if (m_zoneScript != null) + m_zoneScript.OnGameObjectRemove(this); + + RemoveFromOwner(); + if (m_model != null) + if (GetMap().ContainsGameObjectModel(m_model)) + GetMap().RemoveGameObjectModel(m_model); + base.RemoveFromWorld(); + + if (m_spawnId != 0) + GetMap().GetGameObjectBySpawnIdStore().Remove(m_spawnId, this); + GetMap().GetObjectsStore().Remove(GetGUID()); + } + } + + public bool Create(uint name_id, Map map, uint phaseMask, Position pos, Quaternion rotation, uint animprogress, GameObjectState go_state, uint artKit = 0) + { + Contract.Assert(map); + SetMap(map); + + Relocate(pos); + m_stationaryPosition.Relocate(pos); + if (!IsPositionValid()) + { + Log.outError(LogFilter.Server, "Gameobject (Spawn id: {0} Entry: {1}) not created. Suggested coordinates isn't valid (X: {2} Y: {3})", GetSpawnId(), name_id, pos.GetPositionX(), pos.GetPositionY()); + return false; + } + + SetZoneScript(); + if (m_zoneScript != null) + { + name_id = m_zoneScript.GetGameObjectEntry(m_spawnId, name_id); + if (name_id == 0) + return false; + } + + GameObjectTemplate goinfo = Global.ObjectMgr.GetGameObjectTemplate(name_id); + if (goinfo == null) + { + Log.outError(LogFilter.Sql, "Gameobject (Spawn id: {0} Entry: {1}) not created: non-existing entry in `gameobject_template`. Map: {2} (X: {3} Y: {4} Z: {5})", GetSpawnId(), name_id, map.GetId(), pos.GetPositionX(), pos.GetPositionY(), pos.GetPositionZ()); + return false; + } + + if (goinfo.type == GameObjectTypes.MapObjTransport) + { + Log.outError(LogFilter.Sql, "Gameobject (Spawn id: {0} Entry: {1}) not created: gameobject type GAMEOBJECT_TYPE_MAP_OBJ_TRANSPORT cannot be manually created.", GetSpawnId(), name_id); + return false; + } + + ObjectGuid guid; + if (goinfo.type != GameObjectTypes.Transport) + guid = ObjectGuid.Create(HighGuid.GameObject, map.GetId(), goinfo.entry, map.GenerateLowGuid(HighGuid.GameObject)); + else + { + guid = ObjectGuid.Create(HighGuid.Transport, map.GenerateLowGuid(HighGuid.Transport)); + m_updateFlag |= UpdateFlag.Transport; + } + + _Create(guid); + + m_goInfo = goinfo; + m_goTemplateAddon = Global.ObjectMgr.GetGameObjectTemplateAddon(name_id); + + if (goinfo.type >= GameObjectTypes.Max) + { + Log.outError(LogFilter.Sql, "Gameobject (Spawn id: {0} Entry: {1}) not created: non-existing GO type '{2}' in `gameobject_template`. It will crash client if created.", GetSpawnId(), name_id, goinfo.type); + return false; + } + + SetWorldRotation(rotation); + GameObjectAddon gameObjectAddon = Global.ObjectMgr.GetGameObjectAddon(GetSpawnId()); + + // For most of gameobjects is (0, 0, 0, 1) quaternion, there are only some transports with not standard rotation + Quaternion parentRotation = Quaternion.WAxis; + if (gameObjectAddon != null) + parentRotation = gameObjectAddon.ParentRotation; + + SetParentRotation(parentRotation); + + SetObjectScale(goinfo.size); + + if (m_goTemplateAddon != null) + { + SetUInt32Value(GameObjectFields.Faction, m_goTemplateAddon.faction); + SetUInt32Value(GameObjectFields.Flags, m_goTemplateAddon.flags); + } + + SetEntry(goinfo.entry); + + // set name for logs usage, doesn't affect anything ingame + SetName(goinfo.name); + + SetDisplayId(goinfo.displayId); + + m_model = CreateModel(); + // GAMEOBJECT_BYTES_1, index at 0, 1, 2 and 3 + SetGoType(goinfo.type); + m_prevGoState = go_state; + SetGoState(go_state); + SetGoArtKit((byte)artKit); + + switch (goinfo.type) + { + case GameObjectTypes.FishingHole: + SetGoAnimProgress(animprogress); + m_goValue.FishingHole.MaxOpens = RandomHelper.URand(GetGoInfo().FishingHole.minRestock, GetGoInfo().FishingHole.maxRestock); + break; + case GameObjectTypes.DestructibleBuilding: + m_goValue.Building.Health = 20000;//goinfo.DestructibleBuilding.intactNumHits + goinfo.DestructibleBuilding.damagedNumHits; + m_goValue.Building.MaxHealth = m_goValue.Building.Health; + SetGoAnimProgress(255); + SetUInt32Value(GameObjectFields.ParentRotation, goinfo.DestructibleBuilding.DestructibleModelRec); + break; + case GameObjectTypes.Transport: + m_goValue.Transport.AnimationInfo = Global.TransportMgr.GetTransportAnimInfo(goinfo.entry); + m_goValue.Transport.PathProgress = Time.GetMSTime(); + if (m_goValue.Transport.AnimationInfo.Path != null) + m_goValue.Transport.PathProgress -= m_goValue.Transport.PathProgress % GetTransportPeriod(); // align to period + m_goValue.Transport.CurrentSeg = 0; + m_goValue.Transport.StateUpdateTimer = 0; + m_goValue.Transport.StopFrames = new List(); + if (goinfo.Transport.Timeto2ndfloor > 0) + m_goValue.Transport.StopFrames.Add(goinfo.Transport.Timeto2ndfloor); + if (goinfo.Transport.Timeto3rdfloor > 0) + m_goValue.Transport.StopFrames.Add(goinfo.Transport.Timeto3rdfloor); + if (goinfo.Transport.Timeto4thfloor > 0) + m_goValue.Transport.StopFrames.Add(goinfo.Transport.Timeto4thfloor); + if (goinfo.Transport.Timeto5thfloor > 0) + m_goValue.Transport.StopFrames.Add(goinfo.Transport.Timeto5thfloor); + if (goinfo.Transport.Timeto6thfloor > 0) + m_goValue.Transport.StopFrames.Add(goinfo.Transport.Timeto6thfloor); + if (goinfo.Transport.Timeto7thfloor > 0) + m_goValue.Transport.StopFrames.Add(goinfo.Transport.Timeto7thfloor); + if (goinfo.Transport.Timeto8thfloor > 0) + m_goValue.Transport.StopFrames.Add(goinfo.Transport.Timeto8thfloor); + if (goinfo.Transport.Timeto9thfloor > 0) + m_goValue.Transport.StopFrames.Add(goinfo.Transport.Timeto9thfloor); + if (goinfo.Transport.Timeto10thfloor > 0) + m_goValue.Transport.StopFrames.Add(goinfo.Transport.Timeto10thfloor); + + if (goinfo.Transport.startOpen != 0) + SetTransportState(GameObjectState.TransportStopped, goinfo.Transport.startOpen - 1); + else + SetTransportState(GameObjectState.TransportActive); + + SetGoAnimProgress(animprogress); + break; + case GameObjectTypes.FishingNode: + SetGoAnimProgress(0); + break; + case GameObjectTypes.Trap: + if (goinfo.Trap.stealthed != 0) + { + m_stealth.AddFlag(StealthType.Trap); + m_stealth.AddValue(StealthType.Trap, 70); + } + + if (goinfo.Trap.stealthAffected != 0) + { + m_invisibility.AddFlag(InvisibilityType.Trap); + m_invisibility.AddValue(InvisibilityType.Trap, 300); + } + break; + default: + SetGoAnimProgress(animprogress); + break; + } + + if (gameObjectAddon != null && gameObjectAddon.invisibilityValue != 0) + { + m_invisibility.AddFlag(gameObjectAddon.invisibilityType); + m_invisibility.AddValue(gameObjectAddon.invisibilityType, gameObjectAddon.invisibilityValue); + } + + LastUsedScriptID = GetGoInfo().ScriptId; + AIM_Initialize(); + + // Initialize loot duplicate count depending on raid difficulty + if (map.Is25ManRaid()) + loot.maxDuplicates = 3; + + return true; + } + + public override void Update(uint diff) + { + if (GetAI() != null) + GetAI().UpdateAI(diff); + else if (!AIM_Initialize()) + Log.outError(LogFilter.Server, "Could not initialize GameObjectAI"); + + switch (m_lootState) + { + case LootState.NotReady: + { + switch (GetGoType()) + { + case GameObjectTypes.Trap: + { + // Arming Time for GAMEOBJECT_TYPE_TRAP (6) + GameObjectTemplate m_goInfo = GetGoInfo(); + + // Bombs + Unit owner = GetOwner(); + if (m_goInfo.Trap.charges == 2) + m_cooldownTime = (uint)Time.UnixTime + 10; // Hardcoded tooltip value + else if (owner) + { + if (owner.IsInCombat()) + m_cooldownTime = (uint)Time.UnixTime + m_goInfo.Trap.startDelay; + } + m_lootState = LootState.Ready; + break; + } + case GameObjectTypes.Transport: + if (m_goValue.Transport.AnimationInfo.Path == null) + break; + + m_goValue.Transport.PathProgress += diff; + + if (GetGoState() == GameObjectState.TransportActive) + { + m_goValue.Transport.PathProgress += diff; + /* TODO: Fix movement in unloaded grid - currently GO will just disappear + public uint timer = m_goValue.Transport.PathProgress % GetTransportPeriod(); + TransportAnimationEntry const* node = m_goValue.Transport.AnimationInfo->GetAnimNode(timer); + if (node && m_goValue.Transport.CurrentSeg != node->TimeSeg) + { + m_goValue.Transport.CurrentSeg = node->TimeSeg; + + G3D.Quat rotation = m_goValue.Transport.AnimationInfo->GetAnimRotation(timer); + G3D.Vector3 pos = rotation.toRotationMatrix() + G3D.Matrix3.fromEulerAnglesZYX(GetOrientation(), 0.0f, 0.0f) + G3D.Vector3(node->X, node->Y, node->Z); + + pos += G3D.Vector3(GetStationaryX(), GetStationaryY(), GetStationaryZ()); + + G3D.Vector3 src(GetPositionX(), GetPositionY(), GetPositionZ()); + + TC_LOG_DEBUG("misc", "Src: {0} Dest: {1}", src.toString().c_str(), pos.toString().c_str()); + + GetMap()->GameObjectRelocation(this, pos.x, pos.y, pos.z, GetOrientation()); + } + */ + if (!m_goValue.Transport.StopFrames.Empty()) + { + uint visualStateBefore = (m_goValue.Transport.StateUpdateTimer / 20000) & 1; + m_goValue.Transport.StateUpdateTimer += diff; + uint visualStateAfter = (m_goValue.Transport.StateUpdateTimer / 20000) & 1; + if (visualStateBefore != visualStateAfter) + { + ForceValuesUpdateAtIndex(GameObjectFields.Level); + ForceValuesUpdateAtIndex(GameObjectFields.Bytes1); + } + } + } + break; + case GameObjectTypes.FishingNode: + { + // fishing code (bobber ready) + if (Time.UnixTime > m_respawnTime - 5) + { + // splash bobber (bobber ready now) + Unit caster = GetOwner(); + if (caster != null && caster.IsTypeId(TypeId.Player)) + { + SetGoState(GameObjectState.Active); + SetUInt32Value(GameObjectFields.Flags, (uint)GameObjectFlags.NoDespawn); + + UpdateData udata = new UpdateData(caster.GetMapId()); + UpdateObject packet; + BuildValuesUpdateBlockForPlayer(udata, caster.ToPlayer()); + udata.BuildPacket(out packet); + caster.ToPlayer().SendPacket(packet); + + SendCustomAnim(GetGoAnimProgress()); + } + + m_lootState = LootState.Ready; // can be successfully open with some chance + } + return; + } + default: + m_lootState = LootState.Ready; // for other GOis same switched without delay to GO_READY + break; + } + } + goto case LootState.Ready; + case LootState.Ready: + { + if (m_respawnTime > 0) // timer on + { + long now = Time.UnixTime; + if (m_respawnTime <= now) // timer expired + { + ObjectGuid dbtableHighGuid = ObjectGuid.Create(HighGuid.GameObject, GetMapId(), GetEntry(), m_spawnId); + long linkedRespawntime = GetMap().GetLinkedRespawnTime(dbtableHighGuid); + if (linkedRespawntime != 0) // Can't respawn, the master is dead + { + ObjectGuid targetGuid = Global.ObjectMgr.GetLinkedRespawnGuid(dbtableHighGuid); + if (targetGuid == dbtableHighGuid) // if linking self, never respawn (check delayed to next day) + SetRespawnTime(Time.Day); + else + m_respawnTime = (now > linkedRespawntime ? now : linkedRespawntime) + RandomHelper.IRand(5, Time.Minute); // else copy time from master and add a little + SaveRespawnTime(); // also save to DB immediately + return; + } + + m_respawnTime = 0; + m_SkillupList.Clear(); + m_usetimes = 0; + + switch (GetGoType()) + { + case GameObjectTypes.FishingNode: // can't fish now + { + Unit caster = GetOwner(); + if (caster != null && caster.IsTypeId(TypeId.Player)) + { + caster.ToPlayer().RemoveGameObject(this, false); + caster.ToPlayer().SendPacket(new FishEscaped()); + } + // can be delete + m_lootState = LootState.JustDeactivated; + return; + } + case GameObjectTypes.Door: + case GameObjectTypes.Button: + //we need to open doors if they are closed (add there another condition if this code breaks some usage, but it need to be here for Battlegrounds) + if (GetGoState() != GameObjectState.Ready) + ResetDoorOrButton(); + break; + case GameObjectTypes.FishingHole: + // Initialize a new max fish count on respawn + m_goValue.FishingHole.MaxOpens = RandomHelper.URand(GetGoInfo().FishingHole.minRestock, GetGoInfo().FishingHole.maxRestock); + break; + default: + break; + } + + if (!m_spawnedByDefault) // despawn timer + { + // can be despawned or destroyed + SetLootState(LootState.JustDeactivated); + return; + } + // respawn timer + uint poolid = GetSpawnId() != 0 ? Global.PoolMgr.IsPartOfAPool(GetSpawnId()) : 0; + if (poolid != 0) + Global.PoolMgr.UpdatePool(poolid, GetSpawnId()); + else + GetMap().AddToMap(this); + } + } + + if (isSpawned()) + { + GameObjectTemplate goInfo = GetGoInfo(); + uint max_charges; + if (goInfo.type == GameObjectTypes.Trap) + { + if (m_cooldownTime >= Time.UnixTime) + break; + + // Type 2 (bomb) does not need to be triggered by a unit and despawns after casting its spell. + if (goInfo.Trap.charges == 2) + { + SetLootState(LootState.Activated); + break; + } + + // Type 0 despawns after being triggered, type 1 does not. + // @todo This is activation radius. Casting radius must be selected from spell + float radius; + if (goInfo.Trap.radius == 0f) + { + // Battlegroundgameobjects have data2 == 0 && data5 == 3 + if (goInfo.Trap.cooldown != 3) + break; + + radius = 3.0f; + } + else + radius = goInfo.Trap.radius / 2.0f; + + Unit target = null; + // @todo this hack with search required until GO casting not implemented + Unit owner = GetOwner(); + if (owner) + { + // Hunter trap: Search units which are unfriendly to the trap's owner + var checker = new AnyUnfriendlyNoTotemUnitInObjectRangeCheck(this, owner, radius); + var searcher = new UnitSearcher(this, checker); + Cell.VisitGridObjects(this, searcher, radius); + target = searcher.GetTarget(); + if (target == null) + Cell.VisitWorldObjects(this, searcher, radius); + } + else + { + // Environmental trap: Any player + var check = new AnyPlayerInObjectRangeCheck(this, radius); + var searcher = new PlayerSearcher(this, check); + Cell.VisitWorldObjects(this, searcher, radius); + target = searcher.GetTarget(); + } + + if (target) + SetLootState(LootState.Activated, target); + } + else if ((max_charges = goInfo.GetCharges()) != 0) + { + if (m_usetimes >= max_charges) + { + m_usetimes = 0; + SetLootState(LootState.JustDeactivated); // can be despawned or destroyed + } + } + } + + break; + } + case LootState.Activated: + { + switch (GetGoType()) + { + case GameObjectTypes.Door: + case GameObjectTypes.Button: + if (GetGoInfo().GetAutoCloseTime() != 0 && (m_cooldownTime < Time.UnixTime)) + ResetDoorOrButton(); + break; + case GameObjectTypes.Goober: + if (m_cooldownTime < Time.UnixTime) + { + RemoveFlag(GameObjectFields.Flags, GameObjectFlags.InUse); + + SetLootState(LootState.JustDeactivated); + m_cooldownTime = 0; + } + break; + case GameObjectTypes.Chest: + if (m_groupLootTimer != 0) + { + if (m_groupLootTimer <= diff) + { + Group group = Global.GroupMgr.GetGroupByGUID(lootingGroupLowGUID); + if (group) + group.EndRoll(loot); + + m_groupLootTimer = 0; + lootingGroupLowGUID.Clear(); + } + else + m_groupLootTimer -= diff; + } + break; + case GameObjectTypes.Trap: + { + GameObjectTemplate goInfo = GetGoInfo(); + Unit target = Global.ObjAccessor.GetUnit(this, m_lootStateUnitGUID); + if (goInfo.Trap.charges == 2 && goInfo.Trap.spell != 0) + { + //todo NULL target won't work for target type 1 + CastSpell(null, goInfo.Trap.spell); + SetLootState(LootState.JustDeactivated); + } + else if (target) + { + // Some traps do not have a spell but should be triggered + if (goInfo.Trap.spell != 0) + CastSpell(target, goInfo.Trap.spell); + + // Template value or 4 seconds + m_cooldownTime = (uint)(Time.UnixTime + (goInfo.Trap.cooldown != 0 ? goInfo.Trap.cooldown : 4u)); + + if (goInfo.Trap.charges == 1) + SetLootState(LootState.JustDeactivated); + else if (goInfo.Trap.charges == 0) + SetLootState(LootState.Ready); + + // Battleground gameobjects have data2 == 0 && data5 == 3 + if (goInfo.Trap.radius == 0 && goInfo.Trap.cooldown == 3) + { + Player player = target.ToPlayer(); + if (player) + { + Battleground bg = player.GetBattleground(); + if (bg) + bg.HandleTriggerBuff(GetGUID()); + } + } + } + break; + } + default: + break; + } + break; + } + case LootState.JustDeactivated: + { + //if Gameobject should cast spell, then this, but some GOs (type = 10) should be destroyed + if (GetGoType() == GameObjectTypes.Goober) + { + uint spellId = GetGoInfo().Goober.spell; + + if (spellId != 0) + { + foreach (var id in m_unique_users) + { + // m_unique_users can contain only player GUIDs + Player owner = Global.ObjAccessor.GetPlayer(this, id); + if (owner != null) + owner.CastSpell(owner, spellId, false); + } + + m_unique_users.Clear(); + m_usetimes = 0; + } + + SetGoState(GameObjectState.Ready); + + //any return here in case Battleground traps + GameObjectTemplateAddon addon = GetTemplateAddon(); + if (addon != null) + if (addon.flags.HasAnyFlag((uint)GameObjectFlags.NoDespawn)) + return; + } + + loot.clear(); + + //! If this is summoned by a spell with ie. SPELL_EFFECT_SUMMON_OBJECT_WILD, with or without owner, we check respawn criteria based on spell + //! The GetOwnerGUID() check is mostly for compatibility with hacky scripts - 99% of the time summoning should be done trough spells. + if (GetSpellId() != 0 || !GetOwnerGUID().IsEmpty()) + { + SetRespawnTime(0); + Delete(); + return; + } + + SetLootState(LootState.Ready); + + //burning flags in some Battlegrounds, if you find better condition, just add it + if (GetGoInfo().IsDespawnAtAction() || GetGoAnimProgress() > 0) + { + SendGameObjectDespawn(); + //reset flags + GameObjectTemplateAddon addon = GetTemplateAddon(); + if (addon != null) + SetUInt32Value(GameObjectFields.Flags, addon.flags); + } + + if (m_respawnDelayTime == 0) + return; + + if (!m_spawnedByDefault) + { + m_respawnTime = 0; + UpdateObjectVisibility(); + return; + } + + m_respawnTime = Time.UnixTime + m_respawnDelayTime; + + // if option not set then object will be saved at grid unload + if (WorldConfig.GetBoolValue(WorldCfg.SaveRespawnTimeImmediately)) + SaveRespawnTime(); + + UpdateObjectVisibility(); + + break; + } + } + Global.ScriptMgr.OnGameObjectUpdate(this, diff); + } + + public void Refresh() + { + // not refresh despawned not casted GO (despawned casted GO destroyed in all cases anyway) + if (m_respawnTime > 0 && m_spawnedByDefault) + return; + + if (isSpawned()) + GetMap().AddToMap(this); + } + + public void AddUniqueUse(Player player) + { + AddUse(); + m_unique_users.Add(player.GetGUID()); + } + + public void Delete() + { + SetLootState(LootState.NotReady); + RemoveFromOwner(); + + SendGameObjectDespawn(); + + SetGoState(GameObjectState.Ready); + GameObjectTemplateAddon addon = GetTemplateAddon(); + if (addon != null) + SetUInt32Value(GameObjectFields.Flags, addon.flags); + + uint poolid = GetSpawnId() != 0 ? Global.PoolMgr.IsPartOfAPool(GetSpawnId()) : 0; + if (poolid != 0) + Global.PoolMgr.UpdatePool(poolid, GetSpawnId()); + else + AddObjectToRemoveList(); + } + + public void SendGameObjectDespawn() + { + GameObjectDespawn packet = new GameObjectDespawn(); + packet.ObjectGUID = GetGUID(); + SendMessageToSet(packet, true); + } + + public void getFishLoot(Loot fishloot, Player loot_owner) + { + fishloot.clear(); + + uint zone, subzone; + uint defaultzone = 1; + GetZoneAndAreaId(out zone, out subzone); + + // if subzone loot exist use it + fishloot.FillLoot(subzone, LootStorage.Fishing, loot_owner, true, true); + if (fishloot.empty()) + { + //subzone no result,use zone loot + fishloot.FillLoot(zone, LootStorage.Fishing, loot_owner, true); + //use zone 1 as default, somewhere fishing got nothing,becase subzone and zone not set, like Off the coast of Storm Peaks. + if (fishloot.empty()) + fishloot.FillLoot(defaultzone, LootStorage.Fishing, loot_owner, true, true); + } + } + + public void getFishLootJunk(Loot fishloot, Player loot_owner) + { + fishloot.clear(); + + uint zone, subzone; + uint defaultzone = 1; + GetZoneAndAreaId(out zone, out subzone); + + // if subzone loot exist use it + fishloot.FillLoot(subzone, LootStorage.Fishing, loot_owner, true, true, LootModes.JunkFish); + if (fishloot.empty()) //use this becase if zone or subzone has normal mask drop, then fishloot.FillLoot return true. + { + //use zone loot + fishloot.FillLoot(zone, LootStorage.Fishing, loot_owner, true, true, LootModes.JunkFish); + if (fishloot.empty()) + //use zone 1 as default + fishloot.FillLoot(defaultzone, LootStorage.Fishing, loot_owner, true, true, LootModes.JunkFish); + } + } + + public void SaveToDB() + { + // this should only be used when the gameobject has already been loaded + // preferably after adding to map, because mapid may not be valid otherwise + GameObjectData data = Global.ObjectMgr.GetGOData(m_spawnId); + if (data == null) + { + Log.outError(LogFilter.Maps, "GameObject.SaveToDB failed, cannot get gameobject data!"); + return; + } + + SaveToDB(GetMapId(), data.spawnMask, data.phaseMask); + } + + public void SaveToDB(uint mapid, uint spawnMask, uint phaseMask) + { + GameObjectTemplate goI = GetGoInfo(); + + if (goI == null) + return; + + if (m_spawnId == 0) + m_spawnId = Global.ObjectMgr.GenerateGameObjectSpawnId(); + + // update in loaded data (changing data only in this place) + GameObjectData data = new GameObjectData(); + + // guid = guid must not be updated at save + data.id = GetEntry(); + data.mapid = (ushort)mapid; + data.phaseMask = (ushort)phaseMask; + data.posX = GetPositionX(); + data.posY = GetPositionY(); + data.posZ = GetPositionZ(); + data.orientation = GetOrientation(); + data.rotation = m_worldRotation; + data.spawntimesecs = (int)(m_spawnedByDefault ? m_respawnDelayTime : -m_respawnDelayTime); + data.animprogress = GetGoAnimProgress(); + data.go_state = GetGoState(); + data.spawnMask = spawnMask; + data.artKit = GetGoArtKit(); + Global.ObjectMgr.NewGOData(m_spawnId, data); + + // Update in DB + byte index = 0; + PreparedStatement stmt = DB.World.GetPreparedStatement(WorldStatements.DEL_GAMEOBJECT); + stmt.AddValue(0, m_spawnId); + DB.World.Execute(stmt); + + stmt = DB.World.GetPreparedStatement(WorldStatements.INS_GAMEOBJECT); + stmt.AddValue(index++, m_spawnId); + stmt.AddValue(index++, GetEntry()); + stmt.AddValue(index++, mapid); + stmt.AddValue(index++, spawnMask); + stmt.AddValue(index++, GetPositionX()); + stmt.AddValue(index++, GetPositionY()); + stmt.AddValue(index++, GetPositionZ()); + stmt.AddValue(index++, GetOrientation()); + stmt.AddValue(index++, m_worldRotation.X); + stmt.AddValue(index++, m_worldRotation.Y); + stmt.AddValue(index++, m_worldRotation.Z); + stmt.AddValue(index++, m_worldRotation.W); + stmt.AddValue(index++, m_respawnDelayTime); + stmt.AddValue(index++, GetGoAnimProgress()); + stmt.AddValue(index++, GetGoState()); + DB.World.Execute(stmt); + } + + public bool LoadGameObjectFromDB(ulong spawnId, Map map, bool addToMap = true) + { + GameObjectData data = Global.ObjectMgr.GetGOData(spawnId); + + if (data == null) + { + Log.outError(LogFilter.Maps, "Gameobject (SpawnId: {0}) not found in table `gameobject`, can't load. ", spawnId); + return false; + } + + uint entry = data.id; + uint phaseMask = data.phaseMask; + Position pos = new Position(data.posX, data.posY, data.posZ, data.orientation); + + uint animprogress = data.animprogress; + GameObjectState go_state = data.go_state; + uint artKit = data.artKit; + + m_spawnId = spawnId; + if (!Create(entry, map, phaseMask, pos, data.rotation, animprogress, go_state, artKit)) + return false; + + if (data.phaseid != 0) + SetInPhase(data.phaseid, false, true); + + if (data.phaseGroup != 0) + { + // Set the gameobject in all the phases of the phasegroup + foreach (var ph in Global.DB2Mgr.GetPhasesForGroup(data.phaseGroup)) + SetInPhase(ph, false, true); + } + + if (data.spawntimesecs >= 0) + { + m_spawnedByDefault = true; + + if (!GetGoInfo().GetDespawnPossibility() && !GetGoInfo().IsDespawnAtAction()) + { + SetFlag(GameObjectFields.Flags, GameObjectFlags.NoDespawn); + m_respawnDelayTime = 0; + m_respawnTime = 0; + } + else + { + m_respawnDelayTime = (uint)data.spawntimesecs; + m_respawnTime = GetMap().GetGORespawnTime(m_spawnId); + + // ready to respawn + if (m_respawnTime != 0 && m_respawnTime <= Time.UnixTime) + { + m_respawnTime = 0; + GetMap().RemoveGORespawnTime(m_spawnId); + } + } + } + else + { + m_spawnedByDefault = false; + m_respawnDelayTime = (uint)-data.spawntimesecs; + m_respawnTime = 0; + } + + m_goData = data; + + if (addToMap && !GetMap().AddToMap(this)) + return false; + + return true; + } + + public void DeleteFromDB() + { + GetMap().RemoveGORespawnTime(m_spawnId); + Global.ObjectMgr.DeleteGOData(m_spawnId); + + PreparedStatement stmt = DB.World.GetPreparedStatement(WorldStatements.DEL_GAMEOBJECT); + + stmt.AddValue(0, m_spawnId); + + DB.World.Execute(stmt); + + stmt = DB.World.GetPreparedStatement(WorldStatements.DEL_EVENT_GAMEOBJECT); + + stmt.AddValue(0, m_spawnId); + + DB.World.Execute(stmt); + } + + public override bool hasQuest(uint quest_id) + { + + var qr = Global.ObjectMgr.GetGOQuestRelationBounds(GetEntry()); + foreach (var id in qr) + { + if (id == quest_id) + return true; + } + return false; + } + + public override bool hasInvolvedQuest(uint quest_id) + { + var qir = Global.ObjectMgr.GetGOQuestInvolvedRelationBounds(GetEntry()); + foreach (var id in qir) + { + if (id == quest_id) + return true; + } + return false; + } + + public bool IsTransport() + { + // If something is marked as a transport, don't transmit an out of range packet for it. + GameObjectTemplate gInfo = GetGoInfo(); + if (gInfo == null) + return false; + + return gInfo.type == GameObjectTypes.Transport || gInfo.type == GameObjectTypes.MapObjTransport; + } + + // is Dynamic transport = non-stop Transport + public bool IsDynTransport() + { + // If something is marked as a transport, don't transmit an out of range packet for it. + GameObjectTemplate gInfo = GetGoInfo(); + if (gInfo == null) + return false; + + return gInfo.type == GameObjectTypes.MapObjTransport || (gInfo.type == GameObjectTypes.Transport && m_goValue.Transport.StopFrames.Empty()); + } + + public bool IsDestructibleBuilding() + { + GameObjectTemplate gInfo = GetGoInfo(); + if (gInfo == null) + return false; + + return gInfo.type == GameObjectTypes.DestructibleBuilding; + } + + public Transport ToTransport() { return GetGoInfo().type == GameObjectTypes.MapObjTransport ? (this as Transport) : null; } + + public Unit GetOwner() + { + return Global.ObjAccessor.GetUnit(this, GetOwnerGUID()); + } + + public override void SaveRespawnTime() + { + if (m_goData != null && m_goData.dbData && m_respawnTime > Time.UnixTime && m_spawnedByDefault) + GetMap().SaveGORespawnTime(m_spawnId, m_respawnTime); + } + + public override bool IsNeverVisibleFor(WorldObject seer) + { + if (base.IsNeverVisibleFor(seer)) + return true; + + if (GetGoType() == GameObjectTypes.SpellFocus && GetGoInfo().SpellFocus.serverOnly == 1) + return true; + + if (GetUInt32Value(GameObjectFields.DisplayId) == 0) + return true; + + return false; + } + + public override bool IsAlwaysVisibleFor(WorldObject seer) + { + if (base.IsAlwaysVisibleFor(seer)) + return true; + + if (IsTransport() || IsDestructibleBuilding()) + return true; + + if (seer == null) + return false; + + // Always seen by owner and friendly units + ObjectGuid guid = GetOwnerGUID(); + if (!guid.IsEmpty()) + { + if (seer.GetGUID() == guid) + return true; + + Unit owner = GetOwner(); + if (owner != null && seer.isTypeMask(TypeMask.Unit) && owner.IsFriendlyTo(seer.ToUnit())) + return true; + } + + return false; + } + + public override bool IsInvisibleDueToDespawn() + { + if (base.IsInvisibleDueToDespawn()) + return true; + + // Despawned + if (!isSpawned()) + return true; + + return false; + } + + public void Respawn() + { + if (m_spawnedByDefault && m_respawnTime > 0) + { + m_respawnTime = Time.UnixTime; + GetMap().RemoveGORespawnTime(m_spawnId); + } + } + + bool ActivateToQuest(Player target) + { + if (target.HasQuestForGO((int)GetEntry())) + return true; + + if (!Global.ObjectMgr.IsGameObjectForQuests(GetEntry())) + return false; + + switch (GetGoType()) + { + case GameObjectTypes.QuestGiver: + QuestGiverStatus questStatus = target.GetQuestDialogStatus(this); + if (questStatus > QuestGiverStatus.Unavailable) + return true; + break; + // scan GO chest with loot including quest items + case GameObjectTypes.Chest: + { + if (LootStorage.Gameobject.HaveQuestLootForPlayer(GetGoInfo().GetLootId(), target)) + { + Battleground bg = target.GetBattleground(); + if (bg) + return bg.CanActivateGO((int)GetEntry(), (uint)target.GetTeam()); + return true; + } + break; + } + case GameObjectTypes.Generic: + { + if (target.GetQuestStatus(GetGoInfo().Generic.questID) == QuestStatus.Incomplete) + return true; + break; + } + case GameObjectTypes.Goober: + { + if (target.GetQuestStatus(GetGoInfo().Goober.questID) == QuestStatus.Incomplete) + return true; + break; + } + default: + break; + } + return false; + } + + public void TriggeringLinkedGameObject(uint trapEntry, Unit target) + { + GameObjectTemplate trapInfo = Global.ObjectMgr.GetGameObjectTemplate(trapEntry); + if (trapInfo == null || trapInfo.type != GameObjectTypes.Trap) + return; + + SpellInfo trapSpell = Global.SpellMgr.GetSpellInfo(trapInfo.Trap.spell); + if (trapSpell == null) // checked at load already + return; + + float range = target.GetSpellMaxRangeForTarget(GetOwner(), trapSpell); + + // using original GO distance + var go_check = new NearestGameObjectEntryInObjectRangeCheck(target, trapEntry, range); + var checker = new GameObjectLastSearcher(this, go_check); + Cell.VisitGridObjects(this, checker, range); + + // found correct GO + if (checker.GetTarget() != null) + checker.GetTarget().CastSpell(target, trapInfo.Trap.spell); + } + + GameObject LookupFishingHoleAround(float range) + { + var u_check = new NearestGameObjectFishingHole(this, range); + var checker = new GameObjectSearcher(this, u_check); + + Cell.VisitGridObjects(this, checker, range); + return checker.GetTarget(); + } + + public void ResetDoorOrButton() + { + if (m_lootState == LootState.Ready || m_lootState == LootState.JustDeactivated) + return; + + RemoveFlag(GameObjectFields.Flags, GameObjectFlags.InUse); + SetGoState(m_prevGoState); + + SetLootState(LootState.JustDeactivated); + m_cooldownTime = 0; + } + + public void UseDoorOrButton(uint time_to_restore = 0, bool alternative = false, Unit user = null) + { + if (m_lootState != LootState.Ready) + return; + + if (time_to_restore == 0) + time_to_restore = GetGoInfo().GetAutoCloseTime(); + + SwitchDoorOrButton(true, alternative); + SetLootState(LootState.Activated, user); + + m_cooldownTime = time_to_restore != 0 ? (uint)Time.UnixTime + time_to_restore : 0; + } + + public void SetGoArtKit(byte kit) + { + SetByteValue(GameObjectFields.Bytes1, 2, kit); + GameObjectData data = Global.ObjectMgr.GetGOData(m_spawnId); + if (data != null) + data.artKit = kit; + } + + public void SetGoArtKit(byte artkit, GameObject go, uint lowguid) + { + GameObjectData data = null; + if (go != null) + { + go.SetGoArtKit(artkit); + data = go.GetGoData(); + } + else if (lowguid != 0) + data = Global.ObjectMgr.GetGOData(lowguid); + + if (data != null) + data.artKit = artkit; + } + + void SwitchDoorOrButton(bool activate, bool alternative = false) + { + if (activate) + SetFlag(GameObjectFields.Flags, GameObjectFlags.InUse); + else + RemoveFlag(GameObjectFields.Flags, GameObjectFlags.InUse); + + if (GetGoState() == GameObjectState.Ready) //if closed . open + SetGoState(alternative ? GameObjectState.ActiveAlternative : GameObjectState.Active); + else //if open . close + SetGoState(GameObjectState.Ready); + } + + public void Use(Unit user) + { + // by default spell caster is user + Unit spellCaster = user; + uint spellId = 0; + bool triggered = false; + + Player playerUser = user.ToPlayer(); + if (playerUser != null) + { + if (Global.ScriptMgr.OnGossipHello(playerUser, this)) + return; + + if (GetAI().GossipHello(playerUser, true)) + return; + } + + // If cooldown data present in template + uint cooldown = GetGoInfo().GetCooldown(); + if (cooldown != 0) + { + if (m_cooldownTime > Global.WorldMgr.GetGameTime()) + return; + + m_cooldownTime = (uint)(Global.WorldMgr.GetGameTime() + cooldown); + } + + switch (GetGoType()) + { + case GameObjectTypes.Door: //0 + case GameObjectTypes.Button: //1 + //doors/buttons never really despawn, only reset to default state/flags + UseDoorOrButton(0, false, user); + return; + case GameObjectTypes.QuestGiver: //2 + { + if (!user.IsTypeId(TypeId.Player)) + return; + + Player player = user.ToPlayer(); + + player.PrepareGossipMenu(this, GetGoInfo().QuestGiver.gossipID, true); + player.SendPreparedGossip(this); + return; + } + case GameObjectTypes.Trap: //6 + { + GameObjectTemplate goInfo = GetGoInfo(); + if (goInfo.Trap.spell != 0) + CastSpell(user, goInfo.Trap.spell); + + m_cooldownTime = (uint)Time.UnixTime + (goInfo.Trap.cooldown != 0 ? goInfo.Trap.cooldown : 4); // template or 4 seconds + + if (goInfo.Trap.charges == 1) // Deactivate after trigger + SetLootState(LootState.JustDeactivated); + + return; + } + //Sitting: Wooden bench, chairs enzz + case GameObjectTypes.Chair: //7 + { + GameObjectTemplate info = GetGoInfo(); + if (info == null) + return; + + if (!user.IsTypeId(TypeId.Player)) + return; + + if (ChairListSlots.Empty()) // this is called once at first chair use to make list of available slots + { + if (info.Chair.chairslots > 0) // sometimes chairs in DB have error in fields and we dont know number of slots + { + for (uint i = 0; i < info.Chair.chairslots; ++i) + ChairListSlots[i].Clear(); // Last user of current slot set to 0 (none sit here yet) + } + else + ChairListSlots[0].Clear(); // error in DB, make one default slot + } + + Player player = user.ToPlayer(); + + // a chair may have n slots. we have to calculate their positions and teleport the player to the nearest one + float lowestDist = SharedConst.DefaultVisibilityDistance; + + uint nearest_slot = 0; + float x_lowest = GetPositionX(); + float y_lowest = GetPositionY(); + + // the object orientation + 1/2 pi + // every slot will be on that straight line + float orthogonalOrientation = GetOrientation() + MathFunctions.PI * 0.5f; + // find nearest slot + bool found_free_slot = false; + + foreach (var slot in ChairListSlots.ToList()) + { + // the distance between this slot and the center of the go - imagine a 1D space + float relativeDistance = (info.size * slot.Key) - (info.size * (info.Chair.chairslots - 1) / 2.0f); + + float x_i = (float)(GetPositionX() + relativeDistance * Math.Cos(orthogonalOrientation)); + float y_i = (float)(GetPositionY() + relativeDistance * Math.Sin(orthogonalOrientation)); + + if (!slot.Value.IsEmpty()) + { + Player ChairUser = Global.ObjAccessor.FindPlayer(slot.Value); + if (ChairUser != null) + if (ChairUser.IsSitState() && ChairUser.GetStandState() != UnitStandStateType.Sit && ChairUser.GetExactDist2d(x_i, y_i) < 0.1f) + continue; // This seat is already occupied by ChairUser. NOTE: Not sure if the ChairUser.getStandState() != UNIT_STAND_STATE_SIT check is required. + else + ChairListSlots[slot.Key].Clear(); // This seat is unoccupied. + else + ChairListSlots[slot.Key].Clear(); // The seat may of had an occupant, but they're offline. + } + + found_free_slot = true; + + // calculate the distance between the player and this slot + float thisDistance = player.GetDistance2d(x_i, y_i); + + if (thisDistance <= lowestDist) + { + nearest_slot = slot.Key; + lowestDist = thisDistance; + x_lowest = x_i; + y_lowest = y_i; + } + } + + if (found_free_slot) + { + var guid = ChairListSlots.LookupByKey(nearest_slot); + if (!guid.IsEmpty()) + { + guid = player.GetGUID(); //this slot in now used by player + player.TeleportTo(GetMapId(), x_lowest, y_lowest, GetPositionZ(), GetOrientation(), (TeleportToOptions.NotLeaveTransport | TeleportToOptions.NotLeaveCombat | TeleportToOptions.NotUnSummonPet)); + player.SetStandState(UnitStandStateType.SitLowChair + (int)info.Chair.chairheight); + return; + } + } + else + player.GetSession().SendNotification("There's nowhere left for you to sit."); + + return; + } + //big gun, its a spell/aura + case GameObjectTypes.Goober: //10 + { + GameObjectTemplate info = GetGoInfo(); + + if (user.IsTypeId(TypeId.Player)) + { + Player player = user.ToPlayer(); + + if (info.Goober.pageID != 0) // show page... + { + PageTextPkt data = new PageTextPkt(); + data.GameObjectGUID = GetGUID(); + player.SendPacket(data); + } + else if (info.Goober.gossipID != 0) + { + player.PrepareGossipMenu(this, info.Goober.gossipID); + player.SendPreparedGossip(this); + } + + if (info.Goober.eventID != 0) + { + Log.outDebug(LogFilter.Scripts, "Goober ScriptStart id {0} for GO entry {1} (GUID {2}).", info.Goober.eventID, GetEntry(), GetSpawnId()); + GetMap().ScriptsStart(ScriptsType.Event, info.Goober.eventID, player, this); + EventInform(info.Goober.eventID, user); + } + + // possible quest objective for active quests + if (info.Goober.questID != 0 && Global.ObjectMgr.GetQuestTemplate(info.Goober.questID) != null) + { + //Quest require to be active for GO using + if (player.GetQuestStatus(info.Goober.questID) != QuestStatus.Incomplete) + break; + } + + Group group = player.GetGroup(); + if (group) + { + for (GroupReference refe = group.GetFirstMember(); refe != null; refe = refe.next()) + { + Player member = refe.GetSource(); + if (member) + if (member.IsAtGroupRewardDistance(this)) + member.KillCreditGO(info.entry, GetGUID()); + } + } + else + player.KillCreditGO(info.entry, GetGUID()); + } + + uint trapEntry = info.Goober.linkedTrap; + if (trapEntry != 0) + TriggeringLinkedGameObject(trapEntry, user); + + SetFlag(GameObjectFields.Flags, GameObjectFlags.InUse); + SetLootState(LootState.Activated, user); + + // this appear to be ok, however others exist in addition to this that should have custom (ex: 190510, 188692, 187389) + if (info.Goober.customAnim != 0) + SendCustomAnim(GetGoAnimProgress()); + else + SetGoState(GameObjectState.Active); + + m_cooldownTime = (uint)Time.UnixTime + info.GetAutoCloseTime(); + + // cast this spell later if provided + spellId = info.Goober.spell; + spellCaster = null; + + break; + } + case GameObjectTypes.Camera: //13 + { + GameObjectTemplate info = GetGoInfo(); + if (info == null) + return; + + if (!user.IsTypeId(TypeId.Player)) + return; + + Player player = user.ToPlayer(); + + if (info.Camera._camera != 0) + player.SendCinematicStart(info.Camera._camera); + + if (info.Camera.eventID != 0) + { + GetMap().ScriptsStart(ScriptsType.Event, info.Camera.eventID, player, this); + EventInform(info.Camera.eventID, user); + } + + return; + } + //fishing bobber + case GameObjectTypes.FishingNode: //17 + { + Player player = user.ToPlayer(); + if (player == null) + return; + + if (player.GetGUID() != GetOwnerGUID()) + return; + + switch (getLootState()) + { + case LootState.Ready: // ready for loot + { + uint zone, subzone; + GetZoneAndAreaId(out zone, out subzone); + + int zone_skill = Global.ObjectMgr.GetFishingBaseSkillLevel(subzone); + if (zone_skill == 0) + zone_skill = Global.ObjectMgr.GetFishingBaseSkillLevel(zone); + + //provide error, no fishable zone or area should be 0 + if (zone_skill == 0) + Log.outError(LogFilter.Sql, "Fishable areaId {0} are not properly defined in `skill_fishing_base_level`.", subzone); + + int skill = player.GetSkillValue(SkillType.Fishing); + + int chance; + if (skill < zone_skill) + { + chance = (int)(Math.Pow((double)skill / zone_skill, 2) * 100); + if (chance < 1) + chance = 1; + } + else + chance = 100; + + int roll = RandomHelper.IRand(1, 100); + + Log.outDebug(LogFilter.Server, "Fishing check (skill: {0} zone min skill: {1} chance {2} roll: {3}", skill, zone_skill, chance, roll); + + player.UpdateFishingSkill(); + + /// @todo find reasonable value for fishing hole search + GameObject fishingPool = LookupFishingHoleAround(20.0f + SharedConst.ContactDistance); + + // If fishing skill is high enough, or if fishing on a pool, send correct loot. + // Fishing pools have no skill requirement as of patch 3.3.0 (undocumented change). + if (chance >= roll || fishingPool) + { + // @todo I do not understand this hack. Need some explanation. + // prevent removing GO at spell cancel + RemoveFromOwner(); + SetOwnerGUID(player.GetGUID()); + + if (fishingPool) + { + fishingPool.Use(player); + SetLootState(LootState.JustDeactivated); + } + else + player.SendLoot(GetGUID(), LootType.Fishing); + } + else// If fishing skill is too low, send junk loot. + player.SendLoot(GetGUID(), LootType.FishingJunk); + break; + } + case LootState.JustDeactivated: // nothing to do, will be deleted at next update + break; + default: + { + SetLootState(LootState.JustDeactivated); + player.SendPacket(new FishNotHooked()); + break; + } + } + + player.FinishSpell(CurrentSpellTypes.Channeled); + return; + } + + case GameObjectTypes.Ritual: //18 + { + if (!user.IsTypeId(TypeId.Player)) + return; + + Player player = user.ToPlayer(); + + Unit owner = GetOwner(); + + GameObjectTemplate info = GetGoInfo(); + + // ritual owner is set for GO's without owner (not summoned) + if (m_ritualOwner == null && owner == null) + m_ritualOwner = player; + + if (owner != null) + { + if (!owner.IsTypeId(TypeId.Player)) + return; + + // accept only use by player from same group as owner, excluding owner itself (unique use already added in spell effect) + if (player == owner.ToPlayer() || (info.Ritual.castersGrouped != 0 && !player.IsInSameRaidWith(owner.ToPlayer()))) + return; + + // expect owner to already be channeling, so if not... + if (owner.GetCurrentSpell(CurrentSpellTypes.Channeled) == null) + return; + + // in case summoning ritual caster is GO creator + spellCaster = owner; + } + else + { + if (player != m_ritualOwner && (info.Ritual.castersGrouped != 0 && !player.IsInSameRaidWith(m_ritualOwner))) + return; + + spellCaster = player; + } + + AddUniqueUse(player); + + if (info.Ritual.animSpell != 0) + { + player.CastSpell(player, info.Ritual.animSpell, true); + + // for this case, summoningRitual.spellId is always triggered + triggered = true; + } + + // full amount unique participants including original summoner + if (GetUniqueUseCount() == info.Ritual.casters) + { + if (m_ritualOwner != null) + spellCaster = m_ritualOwner; + + spellId = info.Ritual.spell; + + if (spellId == 62330) // GO store nonexistent spell, replace by expected + { + // spell have reagent and mana cost but it not expected use its + // it triggered spell in fact casted at currently channeled GO + spellId = 61993; + triggered = true; + } + + // Cast casterTargetSpell at a random GO user + // on the current DB there is only one gameobject that uses this (Ritual of Doom) + // and its required target number is 1 (outter for loop will run once) + if (info.Ritual.casterTargetSpell != 0 && info.Ritual.casterTargetSpell != 1) // No idea why this field is a bool in some cases + for (uint i = 0; i < info.Ritual.casterTargetSpellTargets; i++) + { + // m_unique_users can contain only player GUIDs + Player target = Global.ObjAccessor.GetPlayer(this, m_unique_users.PickRandom()); + if (target != null) + spellCaster.CastSpell(target, info.Ritual.casterTargetSpell, true); + } + + // finish owners spell + if (owner != null) + owner.FinishSpell(CurrentSpellTypes.Channeled); + + // can be deleted now, if + if (info.Ritual.ritualPersistent == 0) + SetLootState(LootState.JustDeactivated); + else + { + // reset ritual for this GO + m_ritualOwner = null; + m_unique_users.Clear(); + m_usetimes = 0; + } + } + else + return; + + // go to end function to spell casting + break; + } + case GameObjectTypes.SpellCaster: //22 + { + GameObjectTemplate info = GetGoInfo(); + if (info == null) + return; + + if (info.SpellCaster.partyOnly != 0) + { + Unit caster = GetOwner(); + if (caster == null || !caster.IsTypeId(TypeId.Player)) + return; + + if (!user.IsTypeId(TypeId.Player) || !user.ToPlayer().IsInSameRaidWith(caster.ToPlayer())) + return; + } + + user.RemoveAurasByType(AuraType.Mounted); + spellId = info.SpellCaster.spell; + + AddUse(); + break; + } + case GameObjectTypes.MeetingStone: //23 + { + GameObjectTemplate info = GetGoInfo(); + + if (!user.IsTypeId(TypeId.Player)) + return; + + Player player = user.ToPlayer(); + + Player targetPlayer = Global.ObjAccessor.FindPlayer(player.GetTarget()); + + // accept only use by player from same raid as caster, except caster itself + if (targetPlayer == null || targetPlayer == player || !targetPlayer.IsInSameRaidWith(player)) + return; + + //required lvl checks! + uint level = player.getLevel(); + if (level < info.MeetingStone.minLevel) + return; + level = targetPlayer.getLevel(); + if (level < info.MeetingStone.minLevel) + return; + + if (info.entry == 194097) + spellId = 61994; // Ritual of Summoning + else + spellId = 23598;// 59782; // Summoning Stone Effect + + break; + } + + case GameObjectTypes.FlagStand: // 24 + { + if (!user.IsTypeId(TypeId.Player)) + return; + + Player player = user.ToPlayer(); + + if (player.CanUseBattlegroundObject(this)) + { + // in Battlegroundcheck + Battleground bg = player.GetBattleground(); + if (!bg) + return; + + if (player.GetVehicle() != null) + return; + + player.RemoveAurasByType(AuraType.ModStealth); + player.RemoveAurasByType(AuraType.ModInvisibility); + // BG flag click + // AB: + // 15001 + // 15002 + // 15003 + // 15004 + // 15005 + bg.EventPlayerClickedOnFlag(player, this); + return; //we don;t need to delete flag ... it is despawned! + } + break; + } + + case GameObjectTypes.FishingHole: // 25 + { + if (!user.IsTypeId(TypeId.Player)) + return; + + Player player = user.ToPlayer(); + + player.SendLoot(GetGUID(), LootType.Fishinghole); + player.UpdateCriteria(CriteriaTypes.FishInGameobject, GetGoInfo().entry); + return; + } + + case GameObjectTypes.FlagDrop: // 26 + { + if (!user.IsTypeId(TypeId.Player)) + return; + + Player player = user.ToPlayer(); + + if (player.CanUseBattlegroundObject(this)) + { + // in Battlegroundcheck + Battleground bg = player.GetBattleground(); + if (!bg) + return; + + if (player.GetVehicle() != null) + return; + + player.RemoveAurasByType(AuraType.ModStealth); + player.RemoveAurasByType(AuraType.ModInvisibility); + // BG flag dropped + // WS: + // 179785 - Silverwing Flag + // 179786 - Warsong Flag + // EotS: + // 184142 - Netherstorm Flag + GameObjectTemplate info = GetGoInfo(); + if (info != null) + { + switch (info.entry) + { + case 179785: // Silverwing Flag + case 179786: // Warsong Flag + if (bg.GetTypeID(true) == BattlegroundTypeId.WS) + bg.EventPlayerClickedOnFlag(player, this); + break; + case 184142: // Netherstorm Flag + if (bg.GetTypeID(true) == BattlegroundTypeId.EY) + bg.EventPlayerClickedOnFlag(player, this); + break; + } + } + //this cause to call return, all flags must be deleted here!! + spellId = 0; + Delete(); + } + break; + } + case GameObjectTypes.BarberChair: //32 + { + GameObjectTemplate info = GetGoInfo(); + if (info == null) + return; + + if (!user.IsTypeId(TypeId.Player)) + return; + + Player player = user.ToPlayer(); + + player.SendPacket(new EnableBarberShop()); + + // fallback, will always work + player.TeleportTo(GetMapId(), GetPositionX(), GetPositionY(), GetPositionZ(), GetOrientation(), (TeleportToOptions.NotLeaveTransport | TeleportToOptions.NotLeaveCombat | TeleportToOptions.NotUnSummonPet)); + player.SetStandState((UnitStandStateType)(UnitStandStateType.SitLowChair + (int)info.BarberChair.chairheight), info.BarberChair.SitAnimKit); + return; + } + case GameObjectTypes.ArtifactForge: + { + GameObjectTemplate info = GetGoInfo(); + if (info == null) + return; + + if (!user.IsTypeId(TypeId.Player)) + return; + + Player player = user.ToPlayer(); + PlayerConditionRecord playerCondition = CliDB.PlayerConditionStorage.LookupByKey(info.artifactForge.conditionID1); + if (playerCondition != null) + if (!ConditionManager.IsPlayerMeetingCondition(player, playerCondition)) + return; + + Aura artifactAura = player.GetAura(PlayerConst.ArtifactsAllWeaponsGeneralWeaponEquippedPassive); + Item item = artifactAura != null ? player.GetItemByGuid(artifactAura.GetCastItemGUID()) : null; + if (!item) + { + player.SendPacket(new DisplayGameError(GameError.MustEquipArtifact)); + return; + } + + ArtifactForgeOpened artifactForgeOpened = new ArtifactForgeOpened(); + artifactForgeOpened.ArtifactGUID = item.GetGUID(); + artifactForgeOpened.ForgeGUID = GetGUID(); + player.SendPacket(artifactForgeOpened); + return; + } + default: + if (GetGoType() >= GameObjectTypes.Max) + Log.outError(LogFilter.Server, "GameObject.Use(): unit (type: {0}, guid: {1}, name: {2}) tries to use object (guid: {3}, entry: {4}, name: {5}) of unknown type ({6})", + user.GetTypeId(), user.GetGUID().ToString(), user.GetName(), GetGUID().ToString(), GetEntry(), GetGoInfo().name, GetGoType()); + break; + } + + if (spellId == 0) + return; + + SpellInfo spellInfo = Global.SpellMgr.GetSpellInfo(spellId); + if (spellInfo == null) + { + if (!user.IsTypeId(TypeId.Player) || !Global.OutdoorPvPMgr.HandleCustomSpell(user.ToPlayer(), spellId, this)) + Log.outError(LogFilter.Server, "WORLD: unknown spell id {0} at use action for gameobject (Entry: {1} GoType: {2})", spellId, GetEntry(), GetGoType()); + else + Log.outDebug(LogFilter.Outdoorpvp, "WORLD: {0} non-dbc spell was handled by OutdoorPvP", spellId); + return; + } + + Player player1 = user.ToPlayer(); + if (player1) + Global.OutdoorPvPMgr.HandleCustomSpell(player1, spellId, this); + + if (spellCaster != null) + spellCaster.CastSpell(user, spellInfo, triggered); + else + CastSpell(user, spellId); + } + + public void CastSpell(Unit target, uint spellId, bool triggered = true) + { + CastSpell(target, spellId, triggered ? TriggerCastFlags.FullMask : TriggerCastFlags.None); + } + + public void CastSpell(Unit target, uint spellId, TriggerCastFlags triggered) + { + SpellInfo spellInfo = Global.SpellMgr.GetSpellInfo(spellId); + if (spellInfo == null) + return; + + bool self = false; + foreach (SpellEffectInfo effect in spellInfo.GetEffectsForDifficulty(GetMap().GetDifficultyID())) + { + if (effect != null && effect.TargetA.GetTarget() == Targets.UnitCaster) + { + self = true; + break; + } + } + + if (self) + { + if (target != null) + target.CastSpell(target, spellInfo, triggered); + return; + } + + //summon world trigger + Creature trigger = SummonTrigger(GetPositionX(), GetPositionY(), GetPositionZ(), 0, (uint)(spellInfo.CalcCastTime() + 100)); + if (!trigger) + return; + + Unit owner = GetOwner(); + if (owner) + { + trigger.SetFaction(owner.getFaction()); + if (owner.HasFlag(UnitFields.Flags, UnitFlags.PvpAttackable)) + trigger.SetFlag(UnitFields.Flags, UnitFlags.PvpAttackable); + // needed for GO casts for proper target validation checks + trigger.SetOwnerGUID(owner.GetGUID()); + trigger.CastSpell(target != null ? target : trigger, spellInfo, triggered, null, null, owner.GetGUID()); + } + else + { + trigger.SetFaction(14); + // Set owner guid for target if no owner available - needed by trigger auras + // - trigger gets despawned and there's no caster avalible (see AuraEffect.TriggerSpell()) + trigger.CastSpell(target != null ? target : trigger, spellInfo, triggered, null, null, target ? target.GetGUID() : ObjectGuid.Empty); + } + } + + public void SendCustomAnim(uint anim) + { + GameObjectCustomAnim customAnim = new GameObjectCustomAnim(); + customAnim.ObjectGUID = GetGUID(); + customAnim.CustomAnim = anim; + SendMessageToSet(customAnim, true); + } + + public bool IsInRange(float x, float y, float z, float radius) + { + GameObjectDisplayInfoRecord info = CliDB.GameObjectDisplayInfoStorage.LookupByKey(m_goInfo.displayId); + if (info == null) + return IsWithinDist3d(x, y, z, radius); + + float sinA = (float)Math.Sin(GetOrientation()); + float cosA = (float)Math.Cos(GetOrientation()); + float dx = x - GetPositionX(); + float dy = y - GetPositionY(); + float dz = z - GetPositionZ(); + float dist = (float)Math.Sqrt(dx * dx + dy * dy); + //! Check if the distance between the 2 objects is 0, can happen if both objects are on the same position. + //! The code below this check wont crash if dist is 0 because 0/0 in float operations is valid, and returns infinite + if (MathFunctions.fuzzyEq(dist, 0.0f)) + return true; + + float sinB = dx / dist; + float cosB = dy / dist; + dx = dist * (cosA * cosB + sinA * sinB); + dy = dist * (cosA * sinB - sinA * cosB); + return dx < info.GeoBoxMax.X + radius && dx > info.GeoBoxMin.X - radius + && dy < info.GeoBoxMax.Y + radius && dy > info.GeoBoxMin.Y - radius + && dz < info.GeoBoxMax.Z + radius && dz > info.GeoBoxMin.Z - radius; + } + + public void EventInform(uint eventId, WorldObject invoker = null) + { + if (eventId == 0) + return; + + if (GetAI() != null) + GetAI().EventInform(eventId); + + if (m_zoneScript != null) + m_zoneScript.ProcessEvent(this, eventId); + + BattlegroundMap bgMap = GetMap().ToBattlegroundMap(); + if (bgMap) + if (bgMap.GetBG()) + bgMap.GetBG().ProcessEvent(this, eventId, invoker); + } + + public virtual uint GetScriptId() + { + GameObjectData gameObjectData = GetGoData(); + if (gameObjectData != null) + return gameObjectData.ScriptId; + + return GetGoInfo().ScriptId; + } + + public override string GetName(LocaleConstant loc_idx = LocaleConstant.enUS) + { + if (loc_idx != LocaleConstant.enUS) + { + byte uloc_idx = (byte)loc_idx; + GameObjectLocale cl = Global.ObjectMgr.GetGameObjectLocale(GetEntry()); + if (cl != null) + if (cl.Name.Length > uloc_idx && !string.IsNullOrEmpty(cl.Name[uloc_idx])) + return cl.Name[uloc_idx]; + } + + return base.GetName(loc_idx); + } + + public void UpdatePackedRotation() + { + const int PACK_YZ = 1 << 20; + const int PACK_X = PACK_YZ << 1; + + const int PACK_YZ_MASK = (PACK_YZ << 1) - 1; + const int PACK_X_MASK = (PACK_X << 1) - 1; + + sbyte w_sign = (sbyte)(m_worldRotation.W >= 0.0f ? 1 : -1); + long x = (int)(m_worldRotation.X * PACK_X) * w_sign & PACK_X_MASK; + long y = (int)(m_worldRotation.Y * PACK_YZ) * w_sign & PACK_YZ_MASK; + long z = (int)(m_worldRotation.Z * PACK_YZ) * w_sign & PACK_YZ_MASK; + m_packedRotation = z | (y << 21) | (x << 42); + } + + public void SetWorldRotation(Quaternion quaternion) + { + m_worldRotation = quaternion.ToUnit(); + UpdatePackedRotation(); + } + + public void SetParentRotation(Quaternion rotation) + { + SetFloatValue(GameObjectFields.ParentRotation + 0, (float)rotation.X); + SetFloatValue(GameObjectFields.ParentRotation + 1, (float)rotation.Y); + SetFloatValue(GameObjectFields.ParentRotation + 2, (float)rotation.Z); + SetFloatValue(GameObjectFields.ParentRotation + 3, (float)rotation.W); + } + + public void SetWorldRotationAngles(float z_rot, float y_rot, float x_rot) + { + Quaternion quat = new Quaternion(Matrix3.fromEulerAnglesZYX(z_rot, y_rot, x_rot)); + SetWorldRotation(quat); + } + + public void ModifyHealth(int change, Unit attackerOrHealer = null, uint spellId = 0) + { + if (m_goValue.Building.MaxHealth == 0 || change == 0) + return; + + // prevent double destructions of the same object + if (change < 0 && m_goValue.Building.Health == 0) + return; + + if (m_goValue.Building.Health + change <= 0) + m_goValue.Building.Health = 0; + else if (m_goValue.Building.Health + change >= m_goValue.Building.MaxHealth) + m_goValue.Building.Health = m_goValue.Building.MaxHealth; + else + m_goValue.Building.Health += (uint)change; + + // Set the health bar, value = 255 * healthPct; + SetGoAnimProgress(m_goValue.Building.Health * 255 / m_goValue.Building.MaxHealth); + + Player player = attackerOrHealer.GetCharmerOrOwnerPlayerOrPlayerItself(); + + // dealing damage, send packet + if (player != null) + { + DestructibleBuildingDamage packet = new DestructibleBuildingDamage(); + packet.Caster = attackerOrHealer.GetGUID(); // todo: this can be a GameObject + packet.Target = GetGUID(); + packet.Damage = -change; + packet.Owner = player.GetGUID(); + packet.SpellID = spellId; + player.SendPacket(packet); + } + + GameObjectDestructibleState newState = GetDestructibleState(); + + if (m_goValue.Building.Health == 0) + newState = GameObjectDestructibleState.Destroyed; + else if (m_goValue.Building.Health < m_goValue.Building.MaxHealth) + newState = GameObjectDestructibleState.Damaged; + else if (m_goValue.Building.Health == m_goValue.Building.MaxHealth) + newState = GameObjectDestructibleState.Intact; + + if (newState == GetDestructibleState()) + return; + + SetDestructibleState(newState, player, false); + } + + public void SetDestructibleState(GameObjectDestructibleState state, Player eventInvoker = null, bool setHealth = false) + { + // the user calling this must know he is already operating on destructible gameobject + Contract.Assert(GetGoType() == GameObjectTypes.DestructibleBuilding); + + switch (state) + { + case GameObjectDestructibleState.Intact: + RemoveFlag(GameObjectFields.Flags, GameObjectFlags.Damaged | GameObjectFlags.Destroyed); + SetDisplayId(m_goInfo.displayId); + if (setHealth) + { + m_goValue.Building.Health = m_goValue.Building.MaxHealth; + SetGoAnimProgress(255); + } + EnableCollision(true); + break; + case GameObjectDestructibleState.Damaged: + { + EventInform(m_goInfo.DestructibleBuilding.DamagedEvent, eventInvoker); + Global.ScriptMgr.OnGameObjectDamaged(this, eventInvoker); + + RemoveFlag(GameObjectFields.Flags, GameObjectFlags.Destroyed); + SetFlag(GameObjectFields.Flags, GameObjectFlags.Damaged); + + uint modelId = m_goInfo.displayId; + DestructibleModelDataRecord modelData = CliDB.DestructibleModelDataStorage.LookupByKey(m_goInfo.DestructibleBuilding.DestructibleModelRec); + if (modelData != null) + if (modelData.StateDamagedDisplayID != 0) + modelId = modelData.StateDamagedDisplayID; + SetDisplayId(modelId); + + if (setHealth) + { + m_goValue.Building.Health = 10000;//m_goInfo.DestructibleBuilding.damagedNumHits; + uint maxHealth = m_goValue.Building.MaxHealth; + // in this case current health is 0 anyway so just prevent crashing here + if (maxHealth == 0) + maxHealth = 1; + SetGoAnimProgress(m_goValue.Building.Health * 255 / maxHealth); + } + break; + } + case GameObjectDestructibleState.Destroyed: + { + Global.ScriptMgr.OnGameObjectDestroyed(this, eventInvoker); + EventInform(m_goInfo.DestructibleBuilding.DestroyedEvent, eventInvoker); + if (eventInvoker != null) + { + Battleground bg = eventInvoker.GetBattleground(); + if (bg) + bg.DestroyGate(eventInvoker, this); + } + + RemoveFlag(GameObjectFields.Flags, GameObjectFlags.Damaged); + SetFlag(GameObjectFields.Flags, GameObjectFlags.Destroyed); + + uint modelId = m_goInfo.displayId; + DestructibleModelDataRecord modelData = CliDB.DestructibleModelDataStorage.LookupByKey(m_goInfo.DestructibleBuilding.DestructibleModelRec); + if (modelData != null) + if (modelData.StateDestroyedDisplayID != 0) + modelId = modelData.StateDestroyedDisplayID; + SetDisplayId(modelId); + + if (setHealth) + { + m_goValue.Building.Health = 0; + SetGoAnimProgress(0); + } + EnableCollision(false); + break; + } + case GameObjectDestructibleState.Rebuilding: + { + EventInform(m_goInfo.DestructibleBuilding.RebuildingEvent, eventInvoker); + RemoveFlag(GameObjectFields.Flags, GameObjectFlags.Damaged | GameObjectFlags.Destroyed); + + uint modelId = m_goInfo.displayId; + DestructibleModelDataRecord modelData = CliDB.DestructibleModelDataStorage.LookupByKey(m_goInfo.DestructibleBuilding.DestructibleModelRec); + if (modelData != null) + if (modelData.StateRebuildingDisplayID != 0) + modelId = modelData.StateRebuildingDisplayID; + SetDisplayId(modelId); + + // restores to full health + if (setHealth) + { + m_goValue.Building.Health = m_goValue.Building.MaxHealth; + SetGoAnimProgress(255); + } + EnableCollision(true); + break; + } + } + } + + public void SetLootState(LootState state, Unit unit = null) + { + m_lootState = state; + m_lootStateUnitGUID = unit ? unit.GetGUID() : ObjectGuid.Empty; + GetAI().OnStateChanged((uint)state, unit); + Global.ScriptMgr.OnGameObjectLootStateChanged(this, (uint)state, unit); + + // only set collision for doors on SetGoState + if (GetGoType() == GameObjectTypes.Door) + return; + + if (m_model != null) + { + bool collision = false; + // Use the current go state + if ((GetGoState() != GameObjectState.Ready && (state == LootState.Activated || state == LootState.JustDeactivated)) || state == LootState.Ready) + collision = !collision; + + EnableCollision(collision); + } + } + + public void SetGoState(GameObjectState state) + { + SetByteValue(GameObjectFields.Bytes1, 0, (byte)state); + Global.ScriptMgr.OnGameObjectStateChanged(this, state); + if (m_model != null && !IsTransport()) + { + if (!IsInWorld) + return; + + // startOpen determines whether we are going to add or remove the LoS on activation + bool collision = false; + if (state == GameObjectState.Ready) + collision = !collision; + + EnableCollision(collision); + } + } + + public virtual uint GetTransportPeriod() + { + Contract.Assert(GetGoInfo().type == GameObjectTypes.Transport); + if (m_goValue.Transport.AnimationInfo.Path != null) + return m_goValue.Transport.AnimationInfo.TotalTime; + + return 0; + } + + public void SetTransportState(GameObjectState state, uint stopFrame = 0) + { + if (GetGoState() == state) + return; + + Contract.Assert(GetGoInfo().type == GameObjectTypes.Transport); + Contract.Assert(state >= GameObjectState.TransportActive); + if (state == GameObjectState.TransportActive) + { + m_goValue.Transport.StateUpdateTimer = 0; + m_goValue.Transport.PathProgress = Time.GetMSTime(); + if (GetGoState() >= GameObjectState.TransportStopped) + m_goValue.Transport.PathProgress += m_goValue.Transport.StopFrames.LookupByIndex(GetGoState() - GameObjectState.TransportStopped); + SetGoState(GameObjectState.TransportActive); + } + else + { + Contract.Assert(stopFrame < m_goValue.Transport.StopFrames.Count); + m_goValue.Transport.PathProgress = Time.GetMSTime() + m_goValue.Transport.StopFrames[(int)stopFrame]; + SetGoState((GameObjectState)((int)GameObjectState.TransportStopped + stopFrame)); + } + } + + public void SetDisplayId(uint displayid) + { + SetUInt32Value(GameObjectFields.DisplayId, displayid); + UpdateModel(); + } + + void EnableCollision(bool enable) + { + if (m_model == null) + return; + + m_model.enableCollision(enable); + } + + void UpdateModel() + { + if (!IsInWorld) + return; + + if (m_model != null) + if (GetMap().ContainsGameObjectModel(m_model)) + GetMap().RemoveGameObjectModel(m_model); + + m_model = CreateModel(); + if (m_model != null) + GetMap().InsertGameObjectModel(m_model); + } + + Player GetLootRecipient() + { + if (m_lootRecipient.IsEmpty()) + return null; + return Global.ObjAccessor.FindPlayer(m_lootRecipient); + } + + Group GetLootRecipientGroup() + { + if (m_lootRecipientGroup.IsEmpty()) + return Global.GroupMgr.GetGroupByGUID(m_lootRecipientGroup); + + return null; + } + + public void SetLootRecipient(Unit unit, Group group) + { + // set the player whose group should receive the right + // to loot the creature after it dies + // should be set to null after the loot disappears + + if (unit == null) + { + m_lootRecipient.Clear(); + m_lootRecipientGroup = group ? group.GetGUID() : ObjectGuid.Empty; + return; + } + + if (!unit.IsTypeId(TypeId.Player) && !unit.IsVehicle()) + return; + + Player player = unit.GetCharmerOrOwnerPlayerOrPlayerItself(); + if (player == null) // normal creature, no player involved + return; + + m_lootRecipient = player.GetGUID(); + + // either get the group from the passed parameter or from unit's one + Group unitGroup = player.GetGroup(); + if (group) + m_lootRecipientGroup = group.GetGUID(); + else if (unitGroup) + m_lootRecipientGroup = unitGroup.GetGUID(); + } + + bool IsLootAllowedFor(Player player) + { + if (m_lootRecipient.IsEmpty() && m_lootRecipientGroup.IsEmpty()) + return true; + + if (player.GetGUID() == m_lootRecipient) + return true; + + Group playerGroup = player.GetGroup(); + if (!playerGroup || playerGroup != GetLootRecipientGroup()) // if we dont have a group we arent the recipient + return false; // if go doesnt have group bound it means it was solo killed by someone else + + return true; + } + + public override void BuildValuesUpdate(UpdateType updateType, ByteBuffer data, Player target) + { + if (!target) + return; + + bool isStoppableTransport = GetGoType() == GameObjectTypes.Transport && !m_goValue.Transport.StopFrames.Empty(); + bool forcedFlags = GetGoType() == GameObjectTypes.Chest && GetGoInfo().Chest.usegrouplootrules != 0 && HasLootRecipient(); + bool targetIsGM = target.IsGameMaster(); + + ByteBuffer fieldBuffer = new ByteBuffer(); + UpdateMask updateMask = new UpdateMask(valuesCount); + + uint[] flags = UpdateFieldFlags.GameObjectUpdateFieldFlags; + uint visibleFlag = UpdateFieldFlags.Public; + if (GetOwnerGUID() == target.GetGUID()) + visibleFlag |= UpdateFieldFlags.Owner; + + for (int index = 0; index < valuesCount; ++index) + { + if (_fieldNotifyFlags.HasAnyFlag(flags[index]) || + ((updateType == UpdateType.Values ? _changesMask.Get(index) : HasUpdateValue(index)) && flags[index].HasAnyFlag(visibleFlag)) || + (index == (int)GameObjectFields.Flags && forcedFlags)) + { + updateMask.SetBit(index); + + if (index == (int)ObjectFields.DynamicFlags) + { + GameObjectDynamicLowFlags dynFlags = 0; + short pathProgress = -1; + switch (GetGoType()) + { + case GameObjectTypes.QuestGiver: + if (ActivateToQuest(target)) + dynFlags |= GameObjectDynamicLowFlags.Activate; + break; + case GameObjectTypes.Chest: + case GameObjectTypes.Goober: + if (ActivateToQuest(target) || targetIsGM) + dynFlags |= GameObjectDynamicLowFlags.Activate | GameObjectDynamicLowFlags.Sparkle; + break; + case GameObjectTypes.Generic: + if (ActivateToQuest(target)) + dynFlags |= GameObjectDynamicLowFlags.Sparkle; + break; + case GameObjectTypes.Transport: + case GameObjectTypes.MapObjTransport: + uint transportPeriod = GetTransportPeriod(); + if (transportPeriod != 0) + { + float timer = m_goValue.Transport.PathProgress % transportPeriod; + pathProgress = (short)(timer / transportPeriod * 65535.0f); + } + break; + default: + break; + } + + fieldBuffer.WriteUInt16(dynFlags); + fieldBuffer.WriteInt16(pathProgress); + } + else if (index == (int)GameObjectFields.Flags) + { + GameObjectFlags _flags = (GameObjectFlags)GetUInt32Value(GameObjectFields.Flags); + if (GetGoType() == GameObjectTypes.Chest) + if (GetGoInfo().Chest.usegrouplootrules != 0 && !IsLootAllowedFor(target)) + _flags |= GameObjectFlags.Locked | GameObjectFlags.NotSelectable; + + fieldBuffer.WriteUInt32(_flags); + } + else if (index == (int)GameObjectFields.Level) + { + if (isStoppableTransport) + fieldBuffer.WriteUInt32(m_goValue.Transport.PathProgress); + else + WriteValue(fieldBuffer, index); + } + else if (index == (int)GameObjectFields.Bytes1) + { + uint bytes1 = GetUInt32Value(index); + if (isStoppableTransport && GetGoState() == GameObjectState.TransportActive) + { + if (Convert.ToBoolean((m_goValue.Transport.StateUpdateTimer / 20000) & 1)) + { + bytes1 &= 0xFFFFFF00; + bytes1 |= (int)GameObjectState.TransportStopped; + } + } + + fieldBuffer.WriteUInt32(bytes1); + } + else + WriteValue(fieldBuffer, index); // other cases + } + } + + updateMask.AppendToPacket(data); + data.WriteBytes(fieldBuffer); + } + + public void GetRespawnPosition(out float x, out float y, out float z, out float ori) + { + if (m_spawnId != 0) + { + GameObjectData data = Global.ObjectMgr.GetGOData(GetSpawnId()); + if (data != null) + { + x = posX; + y = posY; + z = posZ; + ori = data.orientation; + return; + } + } + + x = GetPositionX(); + y = GetPositionY(); + z = GetPositionZ(); + ori = GetOrientation(); + } + + public float GetInteractionDistance() + { + switch (GetGoType()) + { + // @todo find out how the client calculates the maximal usage distance to spellless working + // gameobjects like guildbanks and mailboxes - 10.0 is a just an abitrary choosen number + case GameObjectTypes.GuildBank: + case GameObjectTypes.Mailbox: + return 10.0f; + case GameObjectTypes.FishingHole: + case GameObjectTypes.FishingNode: + return 20.0f + SharedConst.ContactDistance; // max spell range + default: + return SharedConst.InteractionDistance; + } + } + + public void UpdateModelPosition() + { + if (m_model == null) + return; + + if (GetMap().ContainsGameObjectModel(m_model)) + { + GetMap().RemoveGameObjectModel(m_model); + m_model.UpdatePosition(); + GetMap().InsertGameObjectModel(m_model); + } + } + + public void SetAnimKitId(ushort animKitId, bool oneshot) + { + if (_animKitId == animKitId) + return; + + if (animKitId != 0 && !CliDB.AnimKitStorage.ContainsKey(animKitId)) + return; + + if (!oneshot) + _animKitId = animKitId; + else + _animKitId = 0; + + GameObjectActivateAnimKit activateAnimKit = new GameObjectActivateAnimKit(); + activateAnimKit.ObjectGUID = GetGUID(); + activateAnimKit.AnimKitID = animKitId; + activateAnimKit.Maintain = !oneshot; + SendMessageToSet(activateAnimKit, true); + } + + public override ushort GetAIAnimKitId() { return _animKitId; } + + public GameObjectTemplate GetGoInfo() { return m_goInfo; } + public GameObjectTemplateAddon GetTemplateAddon() { return m_goTemplateAddon; } + GameObjectData GetGoData() { return m_goData; } + + public ulong GetSpawnId() { return m_spawnId; } + + public long GetPackedWorldRotation() { return m_packedRotation; } + + public override bool LoadFromDB(ulong spawnId, Map map) + { + return LoadGameObjectFromDB(spawnId, map, false); + } + + public void SetOwnerGUID(ObjectGuid owner) + { + // Owner already found and different than expected owner - remove object from old owner + if (!owner.IsEmpty() && !GetOwnerGUID().IsEmpty() && GetOwnerGUID() != owner) + { + Contract.Assert(false); + } + m_spawnedByDefault = false; // all object with owner is despawned after delay + SetGuidValue(GameObjectFields.CreatedBy, owner); + } + public ObjectGuid GetOwnerGUID() { return GetGuidValue(GameObjectFields.CreatedBy); } + + public void SetSpellId(uint id) + { + m_spawnedByDefault = false; // all summoned object is despawned after delay + m_spellId = id; + } + public uint GetSpellId() { return m_spellId; } + + public long GetRespawnTime() { return m_respawnTime; } + public long GetRespawnTimeEx() + { + long now = Time.UnixTime; + if (m_respawnTime > now) + return m_respawnTime; + else + return now; + } + + public void SetRespawnTime(int respawn) + { + m_respawnTime = respawn > 0 ? Time.UnixTime + respawn : 0; + m_respawnDelayTime = (uint)(respawn > 0 ? respawn : 0); + } + + public bool isSpawned() + { + return m_respawnDelayTime == 0 || + (m_respawnTime > 0 && !m_spawnedByDefault) || + (m_respawnTime == 0 && m_spawnedByDefault); + } + public bool isSpawnedByDefault() { return m_spawnedByDefault; } + public void SetSpawnedByDefault(bool b) { m_spawnedByDefault = b; } + public uint GetRespawnDelay() { return m_respawnDelayTime; } + + public GameObjectTypes GetGoType() { return (GameObjectTypes)GetByteValue(GameObjectFields.Bytes1, 1); } + public void SetGoType(GameObjectTypes type) { SetByteValue(GameObjectFields.Bytes1, 1, (byte)type); } + public GameObjectState GetGoState() { return (GameObjectState)GetByteValue(GameObjectFields.Bytes1, 0); } + byte GetGoArtKit() { return GetByteValue(GameObjectFields.Bytes1, 2); } + byte GetGoAnimProgress() { return GetByteValue(GameObjectFields.Bytes1, 3); } + public void SetGoAnimProgress(uint animprogress) { SetByteValue(GameObjectFields.Bytes1, 3, (byte)animprogress); } + + public LootState getLootState() { return m_lootState; } + public LootModes GetLootMode() { return m_LootMode; } + bool HasLootMode(LootModes lootMode) { return Convert.ToBoolean(m_LootMode & lootMode); } + void SetLootMode(LootModes lootMode) { m_LootMode = lootMode; } + void AddLootMode(LootModes lootMode) { m_LootMode |= lootMode; } + void RemoveLootMode(LootModes lootMode) { m_LootMode &= ~lootMode; } + void ResetLootMode() { m_LootMode = LootModes.Default; } + + public void AddToSkillupList(ObjectGuid PlayerGuid) { m_SkillupList.Add(PlayerGuid); } + public bool IsInSkillupList(ObjectGuid PlayerGuid) + { + foreach (var i in m_SkillupList) + if (i == PlayerGuid) + return true; + + return false; + } + void ClearSkillupList() { m_SkillupList.Clear(); } + + public void AddUse() { ++m_usetimes; } + + public uint GetUseCount() { return m_usetimes; } + uint GetUniqueUseCount() { return (uint)m_unique_users.Count; } + + bool HasLootRecipient() { return !m_lootRecipient.IsEmpty() || !m_lootRecipientGroup.IsEmpty(); } + + public override uint GetLevelForTarget(WorldObject target) + { + Unit owner = GetOwner(); + if (owner != null) + return owner.GetLevelForTarget(target); + + return 1; + } + + GameObjectDestructibleState GetDestructibleState() + { + if (HasFlag(GameObjectFields.Flags, GameObjectFlags.Destroyed)) + return GameObjectDestructibleState.Destroyed; + if (HasFlag(GameObjectFields.Flags, GameObjectFlags.Damaged)) + return GameObjectDestructibleState.Damaged; + return GameObjectDestructibleState.Intact; + } + + public GameObjectAI GetAI() { return m_AI; } + + public uint GetDisplayId() { return GetUInt32Value(GameObjectFields.DisplayId); } + + uint GetFaction() { return GetUInt32Value(GameObjectFields.Faction); } + public void SetFaction(uint faction) { SetUInt32Value(GameObjectFields.Faction, faction); } + + public override float GetStationaryX() { return m_stationaryPosition.GetPositionX(); } + public override float GetStationaryY() { return m_stationaryPosition.GetPositionY(); } + public override float GetStationaryZ() { return m_stationaryPosition.GetPositionZ(); } + public override float GetStationaryO() { return m_stationaryPosition.GetOrientation(); } + + public void RelocateStationaryPosition(float x, float y, float z, float o) { m_stationaryPosition.Relocate(x, y, z, o); } + + //! Object distance/size - overridden from Object._IsWithinDist. Needs to take in account proper GO size. + public override bool _IsWithinDist(WorldObject obj, float dist2compare, bool is3D) + { + //! Following check does check 3d distance + return IsInRange(obj.GetPositionX(), obj.GetPositionY(), obj.GetPositionZ(), dist2compare); + } + + public GameObjectModel CreateModel() + { + return GameObjectModel.Create(new GameObjectModelOwnerImpl(this)); + } + + #region Fields + public GameObjectValue m_goValue; + protected GameObjectTemplate m_goInfo; + protected GameObjectTemplateAddon m_goTemplateAddon; + GameObjectData m_goData; + ulong m_spawnId; + uint m_spellId; + long m_respawnTime; // (secs) time of next respawn (or despawn if GO have owner()), + uint m_respawnDelayTime; // (secs) if 0 then current GO state no dependent from timer + LootState m_lootState; + ObjectGuid m_lootStateUnitGUID; // GUID of the unit passed with SetLootState(LootState, Unit*) + bool m_spawnedByDefault; + uint m_cooldownTime; // used as internal reaction delay time store (not state change reaction). + // For traps this: spell casting cooldown, for doors/buttons: reset time. + + Player m_ritualOwner; // used for GAMEOBJECT_TYPE_SUMMONING_RITUAL where GO is not summoned (no owner) + List m_unique_users = new List(); + uint m_usetimes; + + ObjectGuid m_lootRecipient; + ObjectGuid m_lootRecipientGroup; + LootModes m_LootMode; // bitmask, default LOOT_MODE_DEFAULT, determines what loot will be lootable + public uint m_groupLootTimer; // (msecs)timer used for group loot + public ObjectGuid lootingGroupLowGUID; // used to find group which is looting + long m_packedRotation; + Quaternion m_worldRotation; + Position m_stationaryPosition; + + GameObjectAI m_AI; + ushort _animKitId; + + GameObjectState m_prevGoState; // What state to set whenever resetting + + Dictionary ChairListSlots = new Dictionary(); + List m_SkillupList = new List(); + + public Loot loot = new Loot(); + + public GameObjectModel m_model; + #endregion + } + + class GameObjectModelOwnerImpl : GameObjectModelOwnerBase + { + public GameObjectModelOwnerImpl(GameObject owner) + { + _owner = owner; + } + + public override bool IsSpawned() + { + return _owner.isSpawned(); + } + + public override uint GetDisplayId() { return _owner.GetDisplayId(); } + public override bool IsInPhase(List phases) { return _owner.IsInPhase(phases); } + public override Vector3 GetPosition() { return new Vector3(_owner.GetPositionX(), _owner.GetPositionY(), _owner.GetPositionZ()); } + public override float GetOrientation() { return _owner.GetOrientation(); } + public override float GetScale() { return _owner.GetObjectScale(); } + + GameObject _owner; + } + + public struct GameObjectValue + { + public transport Transport; + + public fishinghole FishingHole; + + public building Building; + + //11 GAMEOBJECT_TYPE_TRANSPORT + public struct transport + { + public uint PathProgress; + public TransportAnimation AnimationInfo; + public uint CurrentSeg; + public List StopFrames; + public uint StateUpdateTimer; + } + + //25 GAMEOBJECT_TYPE_FISHINGHOLE + public struct fishinghole + { + public uint MaxOpens; + } + + //33 GAMEOBJECT_TYPE_DESTRUCTIBLE_BUILDING + public struct building + { + public uint Health; + public uint MaxHealth; + } + } +} \ No newline at end of file diff --git a/Game/Entities/GameObject/GameObjectData.cs b/Game/Entities/GameObject/GameObjectData.cs new file mode 100644 index 000000000..b4cb9a583 --- /dev/null +++ b/Game/Entities/GameObject/GameObjectData.cs @@ -0,0 +1,1069 @@ +/* + * Copyright (C) 2012-2017 CypherCore + * + * 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, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +using Framework.Collections; +using Framework.Constants; +using Framework.GameMath; +using System.Runtime.InteropServices; + +namespace Game.Entities +{ + [StructLayout(LayoutKind.Explicit)] + public class GameObjectTemplate + { + [FieldOffset(0)] + public uint entry; + + [FieldOffset(4)] + public GameObjectTypes type; + + [FieldOffset(8)] + public uint displayId; + + [FieldOffset(16)] + public string name; + + [FieldOffset(24)] + public string IconName; + + [FieldOffset(32)] + public string castBarCaption; + + [FieldOffset(40)] + public string unk1; + + [FieldOffset(48)] + public float size; + + [FieldOffset(52)] + public int RequiredLevel; + + [FieldOffset(56)] + public string AIName; + + [FieldOffset(64)] + public uint ScriptId; + + [FieldOffset(68)] + public door Door; + + [FieldOffset(68)] + public button Button; + + [FieldOffset(68)] + public questgiver QuestGiver; + + [FieldOffset(68)] + public chest Chest; + + [FieldOffset(68)] + public generic Generic; + + [FieldOffset(68)] + public trap Trap; + + [FieldOffset(68)] + public chair Chair; + + [FieldOffset(68)] + public spellFocus SpellFocus; + + [FieldOffset(68)] + public text Text; + + [FieldOffset(68)] + public goober Goober; + + [FieldOffset(68)] + public transport Transport; + + [FieldOffset(68)] + public areadamage AreaDamage; + + [FieldOffset(68)] + public camera Camera; + + [FieldOffset(68)] + public moTransport MoTransport; + + [FieldOffset(68)] + public ritual Ritual; + + [FieldOffset(68)] + public mailbox MailBox; + + [FieldOffset(68)] + public guardpost GuardPost; + + [FieldOffset(68)] + public spellcaster SpellCaster; + + [FieldOffset(68)] + public meetingstone MeetingStone; + + [FieldOffset(68)] + public flagstand FlagStand; + + [FieldOffset(68)] + public fishinghole FishingHole; + + [FieldOffset(68)] + public flagdrop FlagDrop; + + [FieldOffset(68)] + public controlzone ControlZone; + + [FieldOffset(68)] + public auraGenerator AuraGenerator; + + [FieldOffset(68)] + public dungeonDifficulty DungeonDifficulty; + + [FieldOffset(68)] + public barberChair BarberChair; + + [FieldOffset(68)] + public destructiblebuilding DestructibleBuilding; + + [FieldOffset(68)] + public guildbank GuildBank; + + [FieldOffset(68)] + public trapDoor TrapDoor; + + [FieldOffset(68)] + public newflag NewFlag; + + [FieldOffset(68)] + public newflagdrop NewFlagDrop; + + [FieldOffset(68)] + public Garrisonbuilding garrisonBuilding; + + [FieldOffset(68)] + public garrisonplot GarrisonPlot; + + [FieldOffset(68)] + public clientcreature ClientCreature; + + [FieldOffset(68)] + public clientitem ClientItem; + + [FieldOffset(68)] + public capturepoint CapturePoint; + + [FieldOffset(68)] + public phaseablemo PhaseableMO; + + [FieldOffset(68)] + public garrisonmonument GarrisonMonument; + + [FieldOffset(68)] + public garrisonshipment GarrisonShipment; + + [FieldOffset(68)] + public garrisonmonumentplaque GarrisonMonumentPlaque; + + [FieldOffset(68)] + public artifactforge artifactForge; + + [FieldOffset(68)] + public uilink UILink; + + [FieldOffset(68)] + public keystonereceptacle KeystoneReceptacle; + + [FieldOffset(68)] + public gatheringnode GatheringNode; + + [FieldOffset(68)] + public challengemodereward ChallengeModeReward; + + [FieldOffset(68)] + public raw Raw; + + // helpers + public bool IsDespawnAtAction() + { + switch (type) + { + case GameObjectTypes.Chest: + return Chest.consumable != 0; + case GameObjectTypes.Goober: + return Goober.consumable != 0; + default: + return false; + } + } + + public bool IsUsableMounted() + { + switch (type) + { + case GameObjectTypes.QuestGiver: + return QuestGiver.allowMounted != 0; + case GameObjectTypes.Text: + return Text.allowMounted != 0; + case GameObjectTypes.Goober: + return Goober.allowMounted != 0; + case GameObjectTypes.SpellCaster: + return SpellCaster.allowMounted != 0; + default: + return false; + } + } + + public uint GetLockId() + { + switch (type) + { + case GameObjectTypes.Door: + return Door.open; + case GameObjectTypes.Button: + return Button.open; + case GameObjectTypes.QuestGiver: + return QuestGiver.open; + case GameObjectTypes.Chest: + return Chest.open; + case GameObjectTypes.Trap: + return Trap.open; + case GameObjectTypes.Goober: + return Goober.open; + case GameObjectTypes.AreaDamage: + return AreaDamage.open; + case GameObjectTypes.Camera: + return Camera.open; + case GameObjectTypes.FlagStand: + return FlagStand.open; + case GameObjectTypes.FishingHole: + return FishingHole.open; + case GameObjectTypes.FlagDrop: + return FlagDrop.open; + default: + return 0; + } + } + + // despawn at targeting of cast? + public bool GetDespawnPossibility() + { + switch (type) + { + case GameObjectTypes.Door: + return Door.noDamageImmune != 0; + case GameObjectTypes.Button: + return Button.noDamageImmune != 0; + case GameObjectTypes.QuestGiver: + return QuestGiver.noDamageImmune != 0; + case GameObjectTypes.Goober: + return Goober.noDamageImmune != 0; + case GameObjectTypes.FlagStand: + return FlagStand.noDamageImmune != 0; + case GameObjectTypes.FlagDrop: + return FlagDrop.noDamageImmune != 0; + default: + return true; + } + } + + // despawn at uses amount + public uint GetCharges() + { + switch (type) + { + case GameObjectTypes.GuardPost: + return GuardPost.charges; + case GameObjectTypes.SpellCaster: + return (uint)SpellCaster.charges; + default: + return 0; + } + } + + public uint GetLinkedGameObjectEntry() + { + switch (type) + { + case GameObjectTypes.Chest: + return Chest.linkedTrap; + case GameObjectTypes.SpellFocus: + return SpellFocus.linkedTrap; + case GameObjectTypes.Goober: + return Goober.linkedTrap; + default: return 0; + } + } + + public uint GetAutoCloseTime() + { + uint autoCloseTime = 0; + switch (type) + { + case GameObjectTypes.Door: + autoCloseTime = Door.autoClose; + break; + case GameObjectTypes.Button: + autoCloseTime = Button.autoClose; + break; + case GameObjectTypes.Trap: + autoCloseTime = Trap.autoClose; + break; + case GameObjectTypes.Goober: + autoCloseTime = Goober.autoClose; + break; + case GameObjectTypes.Transport: + autoCloseTime = Transport.autoClose; + break; + case GameObjectTypes.AreaDamage: + autoCloseTime = AreaDamage.autoClose; + break; + default: break; + } + return autoCloseTime / Time.InMilliseconds; // prior to 3.0.3, conversion was / 0x10000; + } + + public uint GetLootId() + { + switch (type) + { + case GameObjectTypes.Chest: + return Chest.chestLoot; + case GameObjectTypes.FishingHole: + return FishingHole.chestLoot; + default: return 0; + } + } + + public uint GetGossipMenuId() + { + switch (type) + { + case GameObjectTypes.QuestGiver: + return QuestGiver.gossipID; + case GameObjectTypes.Goober: + return Goober.gossipID; + default: + return 0; + } + } + + public uint GetEventScriptId() + { + switch (type) + { + case GameObjectTypes.Goober: + return Goober.eventID; + case GameObjectTypes.Chest: + return Chest.triggeredEvent; + case GameObjectTypes.Camera: + return Camera.eventID; + default: + return 0; + } + } + + // Cooldown preventing goober and traps to cast spell + public uint GetCooldown() + { + switch (type) + { + case GameObjectTypes.Trap: + return Trap.cooldown; + case GameObjectTypes.Goober: + return Goober.cooldown; + default: + return 0; + } + } + + #region TypeStructs + public unsafe struct raw + { + public fixed int data[SharedConst.MaxGOData]; + } + + public struct door + { + public uint startOpen; // 0 startOpen, enum { false, true, }; Default: false + public uint open; // 1 open, References: Lock_, NoValue = 0 + public uint autoClose; // 2 autoClose (ms), int, Min value: 0, Max value: 2147483647, Default value: 3000 + public uint noDamageImmune; // 3 noDamageImmune, enum { false, true, }; Default: false + public uint openTextID; // 4 openTextID, References: BroadcastText, NoValue = 0 + public uint closeTextID; // 5 closeTextID, References: BroadcastText, NoValue = 0 + public uint ignoredByPathing; // 6 Ignored By Pathing, enum { false, true, }; Default: false + public uint conditionID1; // 7 conditionID1, References: PlayerCondition, NoValue = 0 + public uint DoorisOpaque; // 8 Door is Opaque (Disable portal on close), enum { false, true, }; Default: true + public uint GiganticAOI; // 9 Gigantic AOI, enum { false, true, }; Default: false + public uint InfiniteAOI; // 10 Infinite AOI, enum { false, true, }; Default: false + } + + + public struct button + { + public uint startOpen; // 0 startOpen, enum { false, true, }; Default: false + public uint open; // 1 open, References: Lock_, NoValue = 0 + public uint autoClose; // 2 autoClose (ms), int, Min value: 0, Max value: 2147483647, Default value: 3000 + public uint linkedTrap; // 3 linkedTrap, References: GameObjects, NoValue = 0 + public uint noDamageImmune; // 4 noDamageImmune, enum { false, true, }; Default: false + public uint GiganticAOI; // 5 Gigantic AOI, enum { false, true, }; Default: false + public uint openTextID; // 6 openTextID, References: BroadcastText, NoValue = 0 + public uint closeTextID; // 7 closeTextID, References: BroadcastText, NoValue = 0 + public uint requireLOS; // 8 require LOS, enum { false, true, }; Default: false + public uint conditionID1; // 9 conditionID1, References: PlayerCondition, NoValue = 0 + } + + + public struct questgiver + { + public uint open; // 0 open, References: Lock_, NoValue = 0 + public uint questGiver; // 1 questGiver, References: QuestGiver, NoValue = 0 + public uint pageMaterial; // 2 pageMaterial, References: PageTextMaterial, NoValue = 0 + public uint gossipID; // 3 gossipID, References: Gossip, NoValue = 0 + public uint customAnim; // 4 customAnim, int, Min value: 0, Max value: 4, Default value: 0 + public uint noDamageImmune; // 5 noDamageImmune, enum { false, true, }; Default: false + public uint openTextID; // 6 openTextID, References: BroadcastText, NoValue = 0 + public uint requireLOS; // 7 require LOS, enum { false, true, }; Default: false + public uint allowMounted; // 8 allowMounted, enum { false, true, }; Default: false + public uint GiganticAOI; // 9 Gigantic AOI, enum { false, true, }; Default: false + public uint conditionID1; // 10 conditionID1, References: PlayerCondition, NoValue = 0 + public uint NeverUsableWhileMounted; // 11 Never Usable While Mounted, enum { false, true, }; Default: false + } + + + public struct chest + { + public uint open; // 0 open, References: Lock_, NoValue = 0 + public uint chestLoot; // 1 chestLoot, References: Treasure, NoValue = 0 + public uint chestRestockTime; // 2 chestRestockTime, int, Min value: 0, Max value: 1800000, Default value: 0 + public uint consumable; // 3 consumable, enum { false, true, }; Default: false + public uint minRestock; // 4 minRestock, int, Min value: 0, Max value: 65535, Default value: 0 + public uint maxRestock; // 5 maxRestock, int, Min value: 0, Max value: 65535, Default value: 0 + public uint triggeredEvent; // 6 triggeredEvent, References: GameEvents, NoValue = 0 + public uint linkedTrap; // 7 linkedTrap, References: GameObjects, NoValue = 0 + public uint questID; // 8 questID, References: QuestV2, NoValue = 0 + public uint level; // 9 level, int, Min value: 0, Max value: 65535, Default value: 0 + public uint requireLOS; // 10 require LOS, enum { false, true, }; Default: false + public uint leaveLoot; // 11 leaveLoot, enum { false, true, }; Default: false + public uint notInCombat; // 12 notInCombat, enum { false, true, }; Default: false + public uint logloot; // 13 log loot, enum { false, true, }; Default: false + public uint openTextID; // 14 openTextID, References: BroadcastText, NoValue = 0 + public uint usegrouplootrules; // 15 use group loot rules, enum { false, true, }; Default: false + public uint floatingTooltip; // 16 floatingTooltip, enum { false, true, }; Default: false + public uint conditionID1; // 17 conditionID1, References: PlayerCondition, NoValue = 0 + public int xpLevel; // 18 xpLevel, int, Min value: -1, Max value: 123, Default value: 0 + public uint xpDifficulty; // 19 xpDifficulty, enum { No Exp, Trivial, Very Small, Small, Substandard, Standard, High, Epic, Dungeon, 5, }; Default: No Exp + public uint lootLevel; // 20 lootLevel, int, Min value: 0, Max value: 123, Default value: 0 + public uint GroupXP; // 21 Group XP, enum { false, true, }; Default: false + public uint DamageImmuneOK; // 22 Damage Immune OK, enum { false, true, }; Default: false + public uint trivialSkillLow; // 23 trivialSkillLow, int, Min value: 0, Max value: 65535, Default value: 0 + public uint trivialSkillHigh; // 24 trivialSkillHigh, int, Min value: 0, Max value: 65535, Default value: 0 + public uint DungeonEncounter; // 25 Dungeon Encounter, References: DungeonEncounter, NoValue = 0 + public uint spell; // 26 spell, References: Spell, NoValue = 0 + public uint GiganticAOI; // 27 Gigantic AOI, enum { false, true, }; Default: false + public uint LargeAOI; // 28 Large AOI, enum { false, true, }; Default: false + public uint SpawnVignette; // 29 Spawn Vignette, References: vignette, NoValue = 0 + public uint chestPersonalLoot; // 30 chest Personal Loot, References: Treasure, NoValue = 0 + public uint turnpersonallootsecurityoff; // 31 turn personal loot security off, enum { false, true, }; Default: false + public uint ChestProperties; // 32 Chest Properties, References: ChestProperties, NoValue = 0 + } + + + public struct generic + { + public uint floatingTooltip; // 0 floatingTooltip, enum { false, true, }; Default: false + public uint highlight; // 1 highlight, enum { false, true, }; Default: true + public uint serverOnly; // 2 serverOnly, enum { false, true, }; Default: false + public uint GiganticAOI; // 3 Gigantic AOI, enum { false, true, }; Default: false + public uint floatOnWater; // 4 floatOnWater, enum { false, true, }; Default: false + public uint questID; // 5 questID, References: QuestV2, NoValue = 0 + public uint conditionID1; // 6 conditionID1, References: PlayerCondition, NoValue = 0 + public uint LargeAOI; // 7 Large AOI, enum { false, true, }; Default: false + public uint UseGarrisonOwnerGuildColors; // 8 Use Garrison Owner Guild Colors, enum { false, true, }; Default: false + } + + + public struct trap + { + public uint open; // 0 open, References: Lock_, NoValue = 0 + public uint level; // 1 level, int, Min value: 0, Max value: 65535, Default value: 0 + public uint radius; // 2 radius, int, Min value: 0, Max value: 100, Default value: 0 + public uint spell; // 3 spell, References: Spell, NoValue = 0 + public uint charges; // 4 charges, int, Min value: 0, Max value: 65535, Default value: 1 + public uint cooldown; // 5 cooldown, int, Min value: 0, Max value: 65535, Default value: 0 + public uint autoClose; // 6 autoClose (ms), int, Min value: 0, Max value: 2147483647, Default value: 0 + public uint startDelay; // 7 startDelay, int, Min value: 0, Max value: 65535, Default value: 0 + public uint serverOnly; // 8 serverOnly, enum { false, true, }; Default: false + public uint stealthed; // 9 stealthed, enum { false, true, }; Default: false + public uint GiganticAOI; // 10 Gigantic AOI, enum { false, true, }; Default: false + public uint stealthAffected; // 11 stealthAffected, enum { false, true, }; Default: false + public uint openTextID; // 12 openTextID, References: BroadcastText, NoValue = 0 + public uint closeTextID; // 13 closeTextID, References: BroadcastText, NoValue = 0 + public uint IgnoreTotems; // 14 Ignore Totems, enum { false, true, }; Default: false + public uint conditionID1; // 15 conditionID1, References: PlayerCondition, NoValue = 0 + public uint playerCast; // 16 playerCast, enum { false, true, }; Default: false + public uint SummonerTriggered; // 17 Summoner Triggered, enum { false, true, }; Default: false + public uint requireLOS; // 18 require LOS, enum { false, true, }; Default: false + } + + + public struct chair + { + public uint chairslots; // 0 chairslots, int, Min value: 1, Max value: 5, Default value: 1 + public uint chairheight; // 1 chairheight, int, Min value: 0, Max value: 2, Default value: 1 + public uint onlyCreatorUse; // 2 onlyCreatorUse, enum { false, true, }; Default: false + public uint triggeredEvent; // 3 triggeredEvent, References: GameEvents, NoValue = 0 + public uint conditionID1; // 4 conditionID1, References: PlayerCondition, NoValue = 0 + } + + + public struct spellFocus + { + public uint spellFocusType; // 0 spellFocusType, References: SpellFocusObject, NoValue = 0 + public uint radius; // 1 radius, int, Min value: 0, Max value: 50, Default value: 10 + public uint linkedTrap; // 2 linkedTrap, References: GameObjects, NoValue = 0 + public uint serverOnly; // 3 serverOnly, enum { false, true, }; Default: false + public uint questID; // 4 questID, References: QuestV2, NoValue = 0 + public uint GiganticAOI; // 5 Gigantic AOI, enum { false, true, }; Default: false + public uint floatingTooltip; // 6 floatingTooltip, enum { false, true, }; Default: false + public uint floatOnWater; // 7 floatOnWater, enum { false, true, }; Default: false + public uint conditionID1; // 8 conditionID1, References: PlayerCondition, NoValue = 0 + } + + + public struct text + { + public uint pageID; // 0 pageID, References: PageText, NoValue = 0 + public uint language; // 1 language, References: Languages, NoValue = 0 + public uint pageMaterial; // 2 pageMaterial, References: PageTextMaterial, NoValue = 0 + public uint allowMounted; // 3 allowMounted, enum { false, true, }; Default: false + public uint conditionID1; // 4 conditionID1, References: PlayerCondition, NoValue = 0 + public uint NeverUsableWhileMounted; // 5 Never Usable While Mounted, enum { false, true, }; Default: false + } + + + public struct goober + { + public uint open; // 0 open, References: Lock_, NoValue = 0 + public uint questID; // 1 questID, References: QuestV2, NoValue = 0 + public uint eventID; // 2 eventID, References: GameEvents, NoValue = 0 + public uint autoClose; // 3 autoClose (ms), int, Min value: 0, Max value: 2147483647, Default value: 3000 + public uint customAnim; // 4 customAnim, int, Min value: 0, Max value: 4, Default value: 0 + public uint consumable; // 5 consumable, enum { false, true, }; Default: false + public uint cooldown; // 6 cooldown, int, Min value: 0, Max value: 65535, Default value: 0 + public uint pageID; // 7 pageID, References: PageText, NoValue = 0 + public uint language; // 8 language, References: Languages, NoValue = 0 + public uint pageMaterial; // 9 pageMaterial, References: PageTextMaterial, NoValue = 0 + public uint spell; // 10 spell, References: Spell, NoValue = 0 + public uint noDamageImmune; // 11 noDamageImmune, enum { false, true, }; Default: false + public uint linkedTrap; // 12 linkedTrap, References: GameObjects, NoValue = 0 + public uint GiganticAOI; // 13 Gigantic AOI, enum { false, true, }; Default: false + public uint openTextID; // 14 openTextID, References: BroadcastText, NoValue = 0 + public uint closeTextID; // 15 closeTextID, References: BroadcastText, NoValue = 0 + public uint requireLOS; // 16 require LOS, enum { false, true, }; Default: false + public uint allowMounted; // 17 allowMounted, enum { false, true, }; Default: false + public uint floatingTooltip; // 18 floatingTooltip, enum { false, true, }; Default: false + public uint gossipID; // 19 gossipID, References: Gossip, NoValue = 0 + public uint AllowMultiInteract; // 20 Allow Multi-Interact, enum { false, true, }; Default: false + public uint floatOnWater; // 21 floatOnWater, enum { false, true, }; Default: false + public uint conditionID1; // 22 conditionID1, References: PlayerCondition, NoValue = 0 + public uint playerCast; // 23 playerCast, enum { false, true, }; Default: false + public uint SpawnVignette; // 24 Spawn Vignette, References: vignette, NoValue = 0 + public uint startOpen; // 25 startOpen, enum { false, true, }; Default: false + public uint DontPlayOpenAnim; // 26 Dont Play Open Anim, enum { false, true, }; Default: false + public uint IgnoreBoundingBox; // 27 Ignore Bounding Box, enum { false, true, }; Default: false + public uint NeverUsableWhileMounted; // 28 Never Usable While Mounted, enum { false, true, }; Default: false + public uint SortFarZ; // 29 Sort Far Z, enum { false, true, }; Default: false + public uint SyncAnimationtoObjectLifetime; // 30 Sync Animation to Object Lifetime (global track only), enum { false, true, }; Default: false + public uint NoFuzzyHit; // 31 No Fuzzy Hit, enum { false, true, }; Default: false + } + + + public struct transport + { + public uint Timeto2ndfloor; // 0 Time to 2nd floor (ms), int, Min value: 0, Max value: 2147483647, Default value: 0 + public uint startOpen; // 1 startOpen, enum { false, true, }; Default: false + public uint autoClose; // 2 autoClose (ms), int, Min value: 0, Max value: 2147483647, Default value: 0 + public uint Reached1stfloor; // 3 Reached 1st floor, References: GameEvents, NoValue = 0 + public uint Reached2ndfloor; // 4 Reached 2nd floor, References: GameEvents, NoValue = 0 + public int SpawnMap; // 5 Spawn Map, References: Map, NoValue = -1 + public uint Timeto3rdfloor; // 6 Time to 3rd floor (ms), int, Min value: 0, Max value: 2147483647, Default value: 0 + public uint Reached3rdfloor; // 7 Reached 3rd floor, References: GameEvents, NoValue = 0 + public uint Timeto4thfloor; // 8 Time to 4th floor (ms), int, Min value: 0, Max value: 2147483647, Default value: 0 + public uint Reached4thfloor; // 9 Reached 4th floor, References: GameEvents, NoValue = 0 + public uint Timeto5thfloor; // 10 Time to 5th floor (ms), int, Min value: 0, Max value: 2147483647, Default value: 0 + public uint Reached5thfloor; // 11 Reached 5th floor, References: GameEvents, NoValue = 0 + public uint Timeto6thfloor; // 12 Time to 6th floor (ms), int, Min value: 0, Max value: 2147483647, Default value: 0 + public uint Reached6thfloor; // 13 Reached 6th floor, References: GameEvents, NoValue = 0 + public uint Timeto7thfloor; // 14 Time to 7th floor (ms), int, Min value: 0, Max value: 2147483647, Default value: 0 + public uint Reached7thfloor; // 15 Reached 7th floor, References: GameEvents, NoValue = 0 + public uint Timeto8thfloor; // 16 Time to 8th floor (ms), int, Min value: 0, Max value: 2147483647, Default value: 0 + public uint Reached8thfloor; // 17 Reached 8th floor, References: GameEvents, NoValue = 0 + public uint Timeto9thfloor; // 18 Time to 9th floor (ms), int, Min value: 0, Max value: 2147483647, Default value: 0 + public uint Reached9thfloor; // 19 Reached 9th floor, References: GameEvents, NoValue = 0 + public uint Timeto10thfloor; // 20 Time to 10th floor (ms), int, Min value: 0, Max value: 2147483647, Default value: 0 + public uint Reached10thfloor; // 21 Reached 10th floor, References: GameEvents, NoValue = 0 + public uint onlychargeheightcheck; // 22 only charge height check. (yards), int, Min value: 0, Max value: 65535, Default value: 0 + public uint onlychargetimecheck; // 23 only charge time check, int, Min value: 0, Max value: 65535, Default value: 0 + } + + + public struct areadamage + { + public uint open; // 0 open, References: Lock_, NoValue = 0 + public uint radius; // 1 radius, int, Min value: 0, Max value: 50, Default value: 3 + public uint damageMin; // 2 damageMin, int, Min value: 0, Max value: 65535, Default value: 0 + public uint damageMax; // 3 damageMax, int, Min value: 0, Max value: 65535, Default value: 0 + public uint damageSchool; // 4 damageSchool, int, Min value: 0, Max value: 65535, Default value: 0 + public uint autoClose; // 5 autoClose (ms), int, Min value: 0, Max value: 2147483647, Default value: 0 + public uint openTextID; // 6 openTextID, References: BroadcastText, NoValue = 0 + public uint closeTextID; // 7 closeTextID, References: BroadcastText, NoValue = 0 + } + + + public struct camera + { + public uint open; // 0 open, References: Lock_, NoValue = 0 + public uint _camera; // 1 camera, References: CinematicSequences, NoValue = 0 + public uint eventID; // 2 eventID, References: GameEvents, NoValue = 0 + public uint openTextID; // 3 openTextID, References: BroadcastText, NoValue = 0 + public uint conditionID1; // 4 conditionID1, References: PlayerCondition, NoValue = 0 + } + + + public struct moTransport + { + public uint taxiPathID; // 0 taxiPathID, References: TaxiPath, NoValue = 0 + public uint moveSpeed; // 1 moveSpeed, int, Min value: 1, Max value: 60, Default value: 1 + public uint accelRate; // 2 accelRate, int, Min value: 1, Max value: 20, Default value: 1 + public uint startEventID; // 3 startEventID, References: GameEvents, NoValue = 0 + public uint stopEventID; // 4 stopEventID, References: GameEvents, NoValue = 0 + public uint transportPhysics; // 5 transportPhysics, References: TransportPhysics, NoValue = 0 + public int SpawnMap; // 6 Spawn Map, References: Map, NoValue = -1 + public uint worldState1; // 7 worldState1, References: WorldState, NoValue = 0 + public uint allowstopping; // 8 allow stopping, enum { false, true, }; Default: false + public uint InitStopped; // 9 Init Stopped, enum { false, true, }; Default: false + public uint TrueInfiniteAOI; // 10 True Infinite AOI (programmer only!), enum { false, true, }; Default: false + } + + + public struct ritual + { + public uint casters; // 0 casters, int, Min value: 1, Max value: 10, Default value: 1 + public uint spell; // 1 spell, References: Spell, NoValue = 0 + public uint animSpell; // 2 animSpell, References: Spell, NoValue = 0 + public uint ritualPersistent; // 3 ritualPersistent, enum { false, true, }; Default: false + public uint casterTargetSpell; // 4 casterTargetSpell, References: Spell, NoValue = 0 + public uint casterTargetSpellTargets; // 5 casterTargetSpellTargets, int, Min value: 1, Max value: 10, Default value: 1 + public uint castersGrouped; // 6 castersGrouped, enum { false, true, }; Default: true + public uint ritualNoTargetCheck; // 7 ritualNoTargetCheck, enum { false, true, }; Default: true + public uint conditionID1; // 8 conditionID1, References: PlayerCondition, NoValue = 0 + } + + + public struct mailbox + { + public uint conditionID1; // 0 conditionID1, References: PlayerCondition, NoValue = 0 + } + + + public struct guardpost + { + public uint creatureID; // 0 creatureID, References: Creature, NoValue = 0 + public uint charges; // 1 charges, int, Min value: 0, Max value: 65535, Default value: 1 + } + + + public struct spellcaster + { + public uint spell; // 0 spell, References: Spell, NoValue = 0 + public int charges; // 1 charges, int, Min value: -1, Max value: 65535, Default value: 1 + public uint partyOnly; // 2 partyOnly, enum { false, true, }; Default: false + public uint allowMounted; // 3 allowMounted, enum { false, true, }; Default: false + public uint GiganticAOI; // 4 Gigantic AOI, enum { false, true, }; Default: false + public uint conditionID1; // 5 conditionID1, References: PlayerCondition, NoValue = 0 + public uint playerCast; // 6 playerCast, enum { false, true, }; Default: false + public uint NeverUsableWhileMounted; // 7 Never Usable While Mounted, enum { false, true, }; Default: false + } + + + public struct meetingstone + { + public uint minLevel; // 0 minLevel, int, Min value: 0, Max value: 65535, Default value: 1 + public uint maxLevel; // 1 maxLevel, int, Min value: 1, Max value: 65535, Default value: 60 + public uint areaID; // 2 areaID, References: AreaTable, NoValue = 0 + } + + + public struct flagstand + { + public uint open; // 0 open, References: Lock_, NoValue = 0 + public uint pickupSpell; // 1 pickupSpell, References: Spell, NoValue = 0 + public uint radius; // 2 radius, int, Min value: 0, Max value: 50, Default value: 0 + public uint returnAura; // 3 returnAura, References: Spell, NoValue = 0 + public uint returnSpell; // 4 returnSpell, References: Spell, NoValue = 0 + public uint noDamageImmune; // 5 noDamageImmune, enum { false, true, }; Default: false + public uint openTextID; // 6 openTextID, References: BroadcastText, NoValue = 0 + public uint requireLOS; // 7 require LOS, enum { false, true, }; Default: true + public uint conditionID1; // 8 conditionID1, References: PlayerCondition, NoValue = 0 + public uint playerCast; // 9 playerCast, enum { false, true, }; Default: false + public uint GiganticAOI; // 10 Gigantic AOI, enum { false, true, }; Default: false + public uint InfiniteAOI; // 11 Infinite AOI, enum { false, true, }; Default: false + public uint cooldown; // 12 cooldown, int, Min value: 0, Max value: 2147483647, Default value: 3000 + } + + + public struct fishinghole + { + public uint radius; // 0 radius, int, Min value: 0, Max value: 50, Default value: 0 + public uint chestLoot; // 1 chestLoot, References: Treasure, NoValue = 0 + public uint minRestock; // 2 minRestock, int, Min value: 0, Max value: 65535, Default value: 0 + public uint maxRestock; // 3 maxRestock, int, Min value: 0, Max value: 65535, Default value: 0 + public uint open; // 4 open, References: Lock_, NoValue = 0 + } + + + public struct flagdrop + { + public uint open; // 0 open, References: Lock_, NoValue = 0 + public uint eventID; // 1 eventID, References: GameEvents, NoValue = 0 + public uint pickupSpell; // 2 pickupSpell, References: Spell, NoValue = 0 + public uint noDamageImmune; // 3 noDamageImmune, enum { false, true, }; Default: false + public uint openTextID; // 4 openTextID, References: BroadcastText, NoValue = 0 + public uint playerCast; // 5 playerCast, enum { false, true, }; Default: false + public uint ExpireDuration; // 6 Expire Duration, int, Min value: 0, Max value: 60000, Default value: 10000 + public uint GiganticAOI; // 7 Gigantic AOI, enum { false, true, }; Default: false + public uint InfiniteAOI; // 8 Infinite AOI, enum { false, true, }; Default: false + public uint cooldown; // 9 cooldown, int, Min value: 0, Max value: 2147483647, Default value: 3000 + } + + + public struct controlzone + { + public uint radius; // 0 radius, int, Min value: 0, Max value: 100, Default value: 10 + public uint spell; // 1 spell, References: Spell, NoValue = 0 + public uint worldState1; // 2 worldState1, References: WorldState, NoValue = 0 + public uint worldstate2; // 3 worldstate2, References: WorldState, NoValue = 0 + public uint CaptureEventHorde; // 4 Capture Event (Horde), References: GameEvents, NoValue = 0 + public uint CaptureEventAlliance; // 5 Capture Event (Alliance), References: GameEvents, NoValue = 0 + public uint ContestedEventHorde; // 6 Contested Event (Horde), References: GameEvents, NoValue = 0 + public uint ContestedEventAlliance; // 7 Contested Event (Alliance), References: GameEvents, NoValue = 0 + public uint ProgressEventHorde; // 8 Progress Event (Horde), References: GameEvents, NoValue = 0 + public uint ProgressEventAlliance; // 9 Progress Event (Alliance), References: GameEvents, NoValue = 0 + public uint NeutralEventHorde; // 10 Neutral Event (Horde), References: GameEvents, NoValue = 0 + public uint NeutralEventAlliance; // 11 Neutral Event (Alliance), References: GameEvents, NoValue = 0 + public uint neutralPercent; // 12 neutralPercent, int, Min value: 0, Max value: 100, Default value: 0 + public uint worldstate3; // 13 worldstate3, References: WorldState, NoValue = 0 + public uint minSuperiority; // 14 minSuperiority, int, Min value: 1, Max value: 65535, Default value: 1 + public uint maxSuperiority; // 15 maxSuperiority, int, Min value: 1, Max value: 65535, Default value: 1 + public uint minTime; // 16 minTime, int, Min value: 1, Max value: 65535, Default value: 1 + public uint maxTime; // 17 maxTime, int, Min value: 1, Max value: 65535, Default value: 1 + public uint GiganticAOI; // 18 Gigantic AOI, enum { false, true, }; Default: false + public uint highlight; // 19 highlight, enum { false, true, }; Default: true + public uint startingValue; // 20 startingValue, int, Min value: 0, Max value: 100, Default value: 50 + public uint unidirectional; // 21 unidirectional, enum { false, true, }; Default: false + public uint killbonustime; // 22 kill bonus time %, int, Min value: 0, Max value: 100, Default value: 0 + public uint speedWorldState1; // 23 speedWorldState1, References: WorldState, NoValue = 0 + public uint speedWorldState2; // 24 speedWorldState2, References: WorldState, NoValue = 0 + public uint UncontestedTime; // 25 Uncontested Time, int, Min value: 0, Max value: 65535, Default value: 0 + public uint FrequentHeartbeat; // 26 Frequent Heartbeat, enum { false, true, }; Default: false + } + + + public struct auraGenerator + { + public uint startOpen; // 0 startOpen, enum { false, true, }; Default: true + public uint radius; // 1 radius, int, Min value: 0, Max value: 100, Default value: 10 + public uint auraID1; // 2 auraID1, References: Spell, NoValue = 0 + public uint conditionID1; // 3 conditionID1, References: PlayerCondition, NoValue = 0 + public uint auraID2; // 4 auraID2, References: Spell, NoValue = 0 + public uint conditionID2; // 5 conditionID2, References: PlayerCondition, NoValue = 0 + public uint serverOnly; // 6 serverOnly, enum { false, true, }; Default: false + } + + + public struct dungeonDifficulty + { + public uint InstanceType; // 0 Instance Type, enum { Not Instanced, Party Dungeon, Raid Dungeon, PVP Battlefield, Arena Battlefield, Scenario, }; Default: Party Dungeon + public uint DifficultyNormal; // 1 Difficulty Normal, References: animationdata, NoValue = 0 + public uint DifficultyHeroic; // 2 Difficulty Heroic, References: animationdata, NoValue = 0 + public uint DifficultyEpic; // 3 Difficulty Epic, References: animationdata, NoValue = 0 + public uint DifficultyLegendary; // 4 Difficulty Legendary, References: animationdata, NoValue = 0 + public uint HeroicAttachment; // 5 Heroic Attachment, References: gameobjectdisplayinfo, NoValue = 0 + public uint ChallengeAttachment; // 6 Challenge Attachment, References: gameobjectdisplayinfo, NoValue = 0 + public uint DifficultyAnimations; // 7 Difficulty Animations, References: GameObjectDiffAnim, NoValue = 0 + public uint LargeAOI; // 8 Large AOI, enum { false, true, }; Default: false + public uint GiganticAOI; // 9 Gigantic AOI, enum { false, true, }; Default: false + public uint Legacy; // 10 Legacy, enum { false, true, }; Default: false + } + + + public struct barberChair + { + public uint chairheight; // 0 chairheight, int, Min value: 0, Max value: 2, Default value: 1 + public int HeightOffset; // 1 Height Offset (inches), int, Min value: -100, Max value: 100, Default value: 0 + public uint SitAnimKit; // 2 Sit Anim Kit, References: AnimKit, NoValue = 0 + } + + + public struct destructiblebuilding + { + public int Unused; // 0 Unused, int, Min value: -2147483648, Max value: 2147483647, Default value: 0 + public uint CreditProxyCreature; // 1 Credit Proxy Creature, References: Creature, NoValue = 0 + public uint HealthRec; // 2 Health Rec, References: DestructibleHitpoint, NoValue = 0 + public uint IntactEvent; // 3 Intact Event, References: GameEvents, NoValue = 0 + public uint PVPEnabling; // 4 PVP Enabling, enum { false, true, }; Default: false + public uint InteriorVisible; // 5 Interior Visible, enum { false, true, }; Default: false + public uint InteriorLight; // 6 Interior Light, enum { false, true, }; Default: false + public int Unused1; // 7 Unused, int, Min value: -2147483648, Max value: 2147483647, Default value: 0 + public int Unused2; // 8 Unused, int, Min value: -2147483648, Max value: 2147483647, Default value: 0 + public uint DamagedEvent; // 9 Damaged Event, References: GameEvents, NoValue = 0 + public int Unused3; // 10 Unused, int, Min value: -2147483648, Max value: 2147483647, Default value: 0 + public int Unused4; // 11 Unused, int, Min value: -2147483648, Max value: 2147483647, Default value: 0 + public int Unused5; // 12 Unused, int, Min value: -2147483648, Max value: 2147483647, Default value: 0 + public int Unused6; // 13 Unused, int, Min value: -2147483648, Max value: 2147483647, Default value: 0 + public uint DestroyedEvent; // 14 Destroyed Event, References: GameEvents, NoValue = 0 + public int Unused7; // 15 Unused, int, Min value: -2147483648, Max value: 2147483647, Default value: 0 + public uint RebuildingTime; // 16 Rebuilding: Time (secs), int, Min value: 0, Max value: 65535, Default value: 0 + public int Unused8; // 17 Unused, int, Min value: -2147483648, Max value: 2147483647, Default value: 0 + public uint DestructibleModelRec; // 18 Destructible Model Rec, References: DestructibleModelData, NoValue = 0 + public uint RebuildingEvent; // 19 Rebuilding: Event, References: GameEvents, NoValue = 0 + public int Unused9; // 20 Unused, int, Min value: -2147483648, Max value: 2147483647, Default value: 0 + public int Unused10; // 21 Unused, int, Min value: -2147483648, Max value: 2147483647, Default value: 0 + public uint DamageEvent; // 22 Damage Event, References: GameEvents, NoValue = 0 + } + + + public struct guildbank + { + public uint conditionID1; // 0 conditionID1, References: PlayerCondition, NoValue = 0 + } + + + public struct trapDoor + { + public uint AutoLink; // 0 Auto Link, enum { false, true, }; Default: false + public uint startOpen; // 1 startOpen, enum { false, true, }; Default: false + public uint autoClose; // 2 autoClose (ms), int, Min value: 0, Max value: 2147483647, Default value: 0 + public uint BlocksPathsDown; // 3 Blocks Paths Down, enum { false, true, }; Default: false + public uint PathBlockerBump; // 4 Path Blocker Bump (ft), int, Min value: -2147483648, Max value: 2147483647, Default value: 0 + } + + + public struct newflag + { + public uint open; // 0 open, References: Lock_, NoValue = 0 + public uint pickupSpell; // 1 pickupSpell, References: Spell, NoValue = 0 + public uint openTextID; // 2 openTextID, References: BroadcastText, NoValue = 0 + public uint requireLOS; // 3 require LOS, enum { false, true, }; Default: true + public uint conditionID1; // 4 conditionID1, References: PlayerCondition, NoValue = 0 + public uint GiganticAOI; // 5 Gigantic AOI, enum { false, true, }; Default: false + public uint InfiniteAOI; // 6 Infinite AOI, enum { false, true, }; Default: false + public uint ExpireDuration; // 7 Expire Duration, int, Min value: 0, Max value: 3600000, Default value: 10000 + public uint RespawnTime; // 8 Respawn Time, int, Min value: 0, Max value: 3600000, Default value: 20000 + public uint FlagDrop; // 9 Flag Drop, References: GameObjects, NoValue = 0 + public int ExclusiveCategory; // 10 Exclusive Category (BGs Only), int, Min value: -2147483648, Max value: 2147483647, Default value: 0 + public uint worldState1; // 11 worldState1, References: WorldState, NoValue = 0 + public uint ReturnonDefenderInteract; // 12 Return on Defender Interact, enum { false, true, }; Default: false + } + + + public struct newflagdrop + { + public uint open; // 0 open, References: Lock_, NoValue = 0 + } + + + public struct Garrisonbuilding + { + public int SpawnMap; // 0 Spawn Map, References: Map, NoValue = -1 + } + + + public struct garrisonplot + { + public uint PlotInstance; // 0 Plot Instance, References: GarrPlotInstance, NoValue = 0 + public int SpawnMap; // 1 Spawn Map, References: Map, NoValue = -1 + } + + + public struct clientcreature + { + public uint CreatureDisplayInfo; // 0 Creature Display Info, References: CreatureDisplayInfo, NoValue = 0 + public uint AnimKit; // 1 Anim Kit, References: AnimKit, NoValue = 0 + public uint creatureID; // 2 creatureID, References: Creature, NoValue = 0 + } + + + public struct clientitem + { + public uint Item; // 0 Item, References: Item, NoValue = 0 + } + + + public struct capturepoint + { + public uint CaptureTime; // 0 Capture Time (ms), int, Min value: 0, Max value: 2147483647, Default value: 60000 + public uint GiganticAOI; // 1 Gigantic AOI, enum { false, true, }; Default: false + public uint highlight; // 2 highlight, enum { false, true, }; Default: true + public uint open; // 3 open, References: Lock_, NoValue = 0 + public uint AssaultBroadcastHorde; // 4 Assault Broadcast (Horde), References: BroadcastText, NoValue = 0 + public uint CaptureBroadcastHorde; // 5 Capture Broadcast (Horde), References: BroadcastText, NoValue = 0 + public uint DefendedBroadcastHorde; // 6 Defended Broadcast (Horde), References: BroadcastText, NoValue = 0 + public uint AssaultBroadcastAlliance; // 7 Assault Broadcast (Alliance), References: BroadcastText, NoValue = 0 + public uint CaptureBroadcastAlliance; // 8 Capture Broadcast (Alliance), References: BroadcastText, NoValue = 0 + public uint DefendedBroadcastAlliance; // 9 Defended Broadcast (Alliance), References: BroadcastText, NoValue = 0 + public uint worldState1; // 10 worldState1, References: WorldState, NoValue = 0 + public uint ContestedEventHorde; // 11 Contested Event (Horde), References: GameEvents, NoValue = 0 + public uint CaptureEventHorde; // 12 Capture Event (Horde), References: GameEvents, NoValue = 0 + public uint DefendedEventHorde; // 13 Defended Event (Horde), References: GameEvents, NoValue = 0 + public uint ContestedEventAlliance; // 14 Contested Event (Alliance), References: GameEvents, NoValue = 0 + public uint CaptureEventAlliance; // 15 Capture Event (Alliance), References: GameEvents, NoValue = 0 + public uint DefendedEventAlliance; // 16 Defended Event (Alliance), References: GameEvents, NoValue = 0 + public uint SpellVisual1; // 17 Spell Visual 1, References: SpellVisual, NoValue = 0 + public uint SpellVisual2; // 18 Spell Visual 2, References: SpellVisual, NoValue = 0 + public uint SpellVisual3; // 19 Spell Visual 3, References: SpellVisual, NoValue = 0 + public uint SpellVisual4; // 20 Spell Visual 4, References: SpellVisual, NoValue = 0 + public uint SpellVisual5; // 21 Spell Visual 5, References: SpellVisual, NoValue = 0 + } + + + public struct phaseablemo + { + public int SpawnMap; // 0 Spawn Map, References: Map, NoValue = -1 + public uint AreaNameSet; // 1 Area Name Set (Index), int, Min value: -2147483648, Max value: 2147483647, Default value: 0 + public uint DoodadSetA; // 2 Doodad Set A, int, Min value: 0, Max value: 2147483647, Default value: 0 + public uint DoodadSetB; // 3 Doodad Set B, int, Min value: 0, Max value: 2147483647, Default value: 0 + } + + + public struct garrisonmonument + { + public uint TrophyTypeID; // 0 Trophy Type ID, References: TrophyType, NoValue = 0 + public uint TrophyInstanceID; // 1 Trophy Instance ID, References: TrophyInstance, NoValue = 0 + } + + + public struct garrisonshipment + { + public uint ShipmentContainer; // 0 Shipment Container, References: CharShipmentContainer, NoValue = 0 + public uint GiganticAOI; // 1 Gigantic AOI, enum { false, true, }; Default: false + public uint LargeAOI; // 2 Large AOI, enum { false, true, }; Default: false + } + + + public struct garrisonmonumentplaque + { + public uint TrophyInstanceID; // 0 Trophy Instance ID, References: TrophyInstance, NoValue = 0 + } + + public struct artifactforge + { + public uint conditionID1; // 0 conditionID1, References: PlayerCondition, NoValue = 0 + public uint LargeAOI; // 1 Large AOI, enum { false, true, }; Default: false + public uint IgnoreBoundingBox; // 2 Ignore Bounding Box, enum { false, true, }; Default: false + public uint CameraMode; // 3 Camera Mode, References: CameraMode, NoValue = 0 + public uint FadeRegionRadius; // 4 Fade Region Radius, int, Min value: 0, Max value: 2147483647, Default value: 0 + } + + public struct uilink + { + public uint UILinkType; // 0 UI Link Type, enum { Adventure Journal, Obliterum Forge, }; Default: Adventure Journal + public uint allowMounted; // 1 allowMounted, enum { false, true, }; Default: false + public uint GiganticAOI; // 2 Gigantic AOI, enum { false, true, }; Default: false + public uint spellFocusType; // 3 spellFocusType, References: SpellFocusObject, NoValue = 0 + public uint radius; // 4 radius, int, Min value: 0, Max value: 50, Default value: 10 + } + + public struct keystonereceptacle { } + + public struct gatheringnode + { + public uint open; // 0 open, References: Lock_, NoValue = 0 + public uint chestLoot; // 1 chestLoot, References: Treasure, NoValue = 0 + public uint level; // 2 level, int, Min value: 0, Max value: 65535, Default value: 0 + public uint notInCombat; // 3 notInCombat, enum { false, true, }; Default: false + public uint trivialSkillLow; // 4 trivialSkillLow, int, Min value: 0, Max value: 65535, Default value: 0 + public uint trivialSkillHigh; // 5 trivialSkillHigh, int, Min value: 0, Max value: 65535, Default value: 0 + public uint ObjectDespawnDelay; // 6 Object Despawn Delay, int, Min value: 0, Max value: 600, Default value: 15 + public uint triggeredEvent; // 7 triggeredEvent, References: GameEvents, NoValue = 0 + public uint requireLOS; // 8 require LOS, enum { false, true, }; Default: false + public uint openTextID; // 9 openTextID, References: BroadcastText, NoValue = 0 + public uint floatingTooltip; // 10 floatingTooltip, enum { false, true, }; Default: false + public uint conditionID1; // 11 conditionID1, References: PlayerCondition, NoValue = 0 + public uint xpLevel; // 12 xpLevel, int, Min value: -1, Max value: 123, Default value: 0 + public uint xpDifficulty; // 13 xpDifficulty, enum { No Exp, Trivial, Very Small, Small, Substandard, Standard, High, Epic, Dungeon, 5, }; Default: No Exp + public uint spell; // 14 spell, References: Spell, NoValue = 0 + public uint GiganticAOI; // 15 Gigantic AOI, enum { false, true, }; Default: false + public uint LargeAOI; // 16 Large AOI, enum { false, true, }; Default: false + public uint SpawnVignette; // 17 Spawn Vignette, References: vignette, NoValue = 0 + public uint MaxNumberofLoots; // 18 Max Number of Loots, int, Min value: 1, Max value: 40, Default value: 10 + public uint logloot; // 19 log loot, enum { false, true, }; Default: false + public uint linkedTrap; // 20 linkedTrap, References: GameObjects, NoValue = 0 + } + + public struct challengemodereward + { + public uint chestLoot; // 0 chestLoot, References: Treasure, NoValue = 0 + public uint WhenAvailable; // 1 When Available, References: GameObjectDisplayInfo, NoValue = 0 + } + #endregion + } + + public class GameObjectTemplateAddon + { + public uint entry; + public uint faction; + public uint flags; + public uint mingold; + public uint maxgold; + } + + public class GameObjectLocale + { + public StringArray Name = new StringArray((int)LocaleConstant.Total); + public StringArray CastBarCaption = new StringArray((int)LocaleConstant.Total); + public StringArray Unk1 = new StringArray((int)LocaleConstant.Total); + } + + public class GameObjectAddon + { + public Quaternion ParentRotation; + public InvisibilityType invisibilityType; + public uint invisibilityValue; + } + + public class GameObjectData + { + public uint id; // entry in gamobject_template + public ushort mapid; + public ushort phaseMask; + public float posX; + public float posY; + public float posZ; + public float orientation; + public Quaternion rotation; + public int spawntimesecs; + public uint animprogress; + public GameObjectState go_state; + public uint spawnMask; + public byte artKit; + public uint phaseid; + public uint phaseGroup; + public uint ScriptId; + public bool dbData = true; + } +} diff --git a/Game/Entities/Item/Bag.cs b/Game/Entities/Item/Bag.cs new file mode 100644 index 000000000..e3946f5b8 --- /dev/null +++ b/Game/Entities/Item/Bag.cs @@ -0,0 +1,261 @@ +/* + * Copyright (C) 2012-2017 CypherCore + * + * 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, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +using Framework.Constants; +using Framework.Database; + +namespace Game.Entities +{ + public class Bag : Item + { + public Bag() + { + objectTypeMask |= TypeMask.Container; + objectTypeId = TypeId.Container; + + valuesCount = (int)ContainerFields.End; + _dynamicValuesCount = (int)ItemDynamicFields.End; + } + + public override void Dispose() + { + for (byte i = 0; i < ItemConst.MaxBagSize; ++i) + { + Item item = m_bagslot[i]; + if (item) + { + if (item.IsInWorld) + { + Log.outFatal(LogFilter.PlayerItems, "Item {0} (slot {1}, bag slot {2}) in bag {3} (slot {4}, bag slot {5}, m_bagslot {6}) is to be deleted but is still in world.", + item.GetEntry(), item.GetSlot(), item.GetBagSlot(), + GetEntry(), GetSlot(), GetBagSlot(), i); + item.RemoveFromWorld(); + } + m_bagslot[i].Dispose(); + } + } + + base.Dispose(); + } + + public override void AddToWorld() + { + base.AddToWorld(); + + for (uint i = 0; i < GetBagSize(); ++i) + if (m_bagslot[i] != null) + m_bagslot[i].AddToWorld(); + } + + public override void RemoveFromWorld() + { + for (uint i = 0; i < GetBagSize(); ++i) + if (m_bagslot[i] != null) + m_bagslot[i].RemoveFromWorld(); + + base.RemoveFromWorld(); + } + + public override bool Create(ulong guidlow, uint itemid, Player owner) + { + var itemProto = Global.ObjectMgr.GetItemTemplate(itemid); + + if (itemProto == null || itemProto.GetContainerSlots() > ItemConst.MaxBagSize) + return false; + + _Create(ObjectGuid.Create(HighGuid.Item, guidlow)); + + _bonusData = new BonusData(itemProto); + + SetEntry(itemid); + SetObjectScale(1.0f); + + if (owner) + { + SetGuidValue(ItemFields.Owner, owner.GetGUID()); + SetGuidValue(ItemFields.Contained, owner.GetGUID()); + } + + SetUInt32Value(ItemFields.MaxDurability, itemProto.MaxDurability); + SetUInt32Value(ItemFields.Durability, itemProto.MaxDurability); + SetUInt32Value(ItemFields.StackCount, 1); + + // Setting the number of Slots the Container has + SetUInt32Value(ContainerFields.NumSlots, itemProto.GetContainerSlots()); + + // Cleaning 20 slots + for (byte i = 0; i < ItemConst.MaxBagSize; ++i) + SetGuidValue(ContainerFields.Slot1 + (i * 4), ObjectGuid.Empty); + + m_bagslot = new Item[ItemConst.MaxBagSize]; + return true; + } + + public override void SaveToDB(SQLTransaction trans) + { + base.SaveToDB(trans); + + } + + public override bool LoadFromDB(ulong guid, ObjectGuid owner_guid, SQLFields fields, uint entry) + { + if (!base.LoadFromDB(guid, owner_guid, fields, entry)) + return false; + + ItemTemplate itemProto = GetTemplate(); // checked in Item.LoadFromDB + SetUInt32Value(ContainerFields.NumSlots, itemProto.GetContainerSlots()); + // cleanup bag content related item value fields (its will be filled correctly from `character_inventory`) + for (byte i = 0; i < ItemConst.MaxBagSize; ++i) + { + SetGuidValue(ContainerFields.Slot1 + (i * 4), ObjectGuid.Empty); + m_bagslot[i] = null; + } + return true; + } + + public override void DeleteFromDB(SQLTransaction trans) + { + for (byte i = 0; i < ItemConst.MaxBagSize; ++i) + if (m_bagslot[i] != null) + m_bagslot[i].DeleteFromDB(trans); + + base.DeleteFromDB(trans); + } + + public uint GetFreeSlots() + { + uint slots = 0; + for (uint i = 0; i < GetBagSize(); ++i) + if (m_bagslot[i] == null) + ++slots; + + return slots; + } + + public void RemoveItem(byte slot, bool update) + { + if (m_bagslot[slot] != null) + m_bagslot[slot].SetContainer(null); + + m_bagslot[slot] = null; + SetGuidValue(ContainerFields.Slot1 + (slot * 4), ObjectGuid.Empty); + } + + public void StoreItem(byte slot, Item pItem, bool update) + { + if (pItem != null && pItem.GetGUID() != GetGUID()) + { + m_bagslot[slot] = pItem; + SetGuidValue(ContainerFields.Slot1 + (slot * 4), pItem.GetGUID()); + pItem.SetGuidValue(ItemFields.Contained, GetGUID()); + pItem.SetGuidValue(ItemFields.Owner, GetOwnerGUID()); + pItem.SetContainer(this); + pItem.SetSlot(slot); + } + } + + public override void BuildCreateUpdateBlockForPlayer(UpdateData data, Player target) + { + base.BuildCreateUpdateBlockForPlayer(data, target); + + for (int i = 0; i < GetBagSize(); ++i) + if (m_bagslot[i] != null) + m_bagslot[i].BuildCreateUpdateBlockForPlayer(data, target); + } + + public bool IsEmpty() + { + for (var i = 0; i < GetBagSize(); ++i) + if (m_bagslot[i] != null) + return false; + + return true; + } + + public uint GetItemCount(uint item, Item eItem) + { + Item pItem; + uint count = 0; + for (var i = 0; i < GetBagSize(); ++i) + { + pItem = m_bagslot[i]; + if (pItem != null && pItem != eItem && pItem.GetEntry() == item) + count += pItem.GetCount(); + } + + if (eItem != null && eItem.GetTemplate().GetGemProperties() != 0) + { + for (var i = 0; i < GetBagSize(); ++i) + { + pItem = m_bagslot[i]; + if (pItem != null && pItem != eItem && pItem.GetSocketColor(0) != 0) + count += pItem.GetGemCountWithID(item); + } + } + + return count; + } + + public uint GetItemCountWithLimitCategory(uint limitCategory, Item skipItem) + { + uint count = 0; + for (uint i = 0; i < GetBagSize(); ++i) + { + Item pItem = m_bagslot[i]; + if (pItem != null) + { + if (pItem != skipItem) + { + ItemTemplate pProto = pItem.GetTemplate(); + if (pProto != null) + if (pProto.GetItemLimitCategory() == limitCategory) + count += m_bagslot[i].GetCount(); + } + } + } + + return count; + } + + byte GetSlotByItemGUID(ObjectGuid guid) + { + for (byte i = 0; i < GetBagSize(); ++i) + if (m_bagslot[i] != null) + if (m_bagslot[i].GetGUID() == guid) + return i; + + return ItemConst.NullSlot; + } + + public Item GetItemByPos(byte slot) + { + if (slot < GetBagSize()) + return m_bagslot[slot]; + + return null; + } + + public uint GetBagSize() { return GetUInt32Value(ContainerFields.NumSlots); } + + public static Item NewItemOrBag(ItemTemplate proto) + { + return (proto.GetInventoryType() == InventoryType.Bag) ? new Bag() : new Item(); + } + + Item[] m_bagslot = new Item[36]; + } +} diff --git a/Game/Entities/Item/Item.cs b/Game/Entities/Item/Item.cs new file mode 100644 index 000000000..a81764d2f --- /dev/null +++ b/Game/Entities/Item/Item.cs @@ -0,0 +1,2881 @@ +/* + * Copyright (C) 2012-2017 CypherCore + * + * 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, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +using Framework.Collections; +using Framework.Constants; +using Framework.Database; +using Framework.IO; +using Game.DataStorage; +using Game.Loots; +using Game.Network; +using Game.Network.Packets; +using Game.Spells; +using System; +using System.Collections.Generic; +using System.Diagnostics.Contracts; +using System.Linq; +using System.Runtime.InteropServices; +using System.Text; + +namespace Game.Entities +{ + public class Item : WorldObject + { + public Item() : base(false) + { + objectTypeMask |= TypeMask.Item; + objectTypeId = TypeId.Item; + + m_updateFlag = UpdateFlag.None; + valuesCount = (int)ItemFields.End; + _dynamicValuesCount = (int)ItemDynamicFields.End; + uState = ItemUpdateState.New; + uQueuePos = -1; + m_lastPlayedTimeUpdate = Time.UnixTime; + + loot = new Loot(); + } + + public virtual bool Create(ulong guidlow, uint itemid, Player owner) + { + _Create(ObjectGuid.Create(HighGuid.Item, guidlow)); + + SetEntry(itemid); + SetObjectScale(1.0f); + + if (owner) + { + SetOwnerGUID(owner.GetGUID()); + SetGuidValue(ItemFields.Contained, owner.GetGUID()); + } + + ItemTemplate itemProto = Global.ObjectMgr.GetItemTemplate(itemid); + if (itemProto == null) + return false; + + _bonusData = new BonusData(itemProto); + SetUInt32Value(ItemFields.StackCount, 1); + SetUInt32Value(ItemFields.MaxDurability, itemProto.MaxDurability); + SetUInt32Value(ItemFields.Durability, itemProto.MaxDurability); + + for (var i = 0; i < itemProto.Effects.Count; ++i) + { + if (i < 5) + SetSpellCharges(i, itemProto.Effects[i].Charges); + + SpellInfo spellInfo = Global.SpellMgr.GetSpellInfo(itemProto.Effects[i].SpellID); + if (spellInfo != null) + { + if (spellInfo.HasEffect(SpellEffectName.GiveArtifactPower)) + { + uint artifactKnowledgeLevel = owner.GetCurrency((uint)CurrencyTypes.ArtifactKnowledge); + if (artifactKnowledgeLevel != 0) + SetModifier(ItemModifier.ArtifactKnowledgeLevel, artifactKnowledgeLevel + 1); + } + } + } + + SetUInt32Value(ItemFields.Duration, itemProto.GetDuration()); + SetUInt32Value(ItemFields.CreatePlayedTime, 0); + + if (itemProto.GetArtifactID() != 0) + { + InitArtifactPowers(itemProto.GetArtifactID(), 0); + foreach (ArtifactAppearanceRecord artifactAppearance in CliDB.ArtifactAppearanceStorage.Values) + { + ArtifactAppearanceSetRecord artifactAppearanceSet = CliDB.ArtifactAppearanceSetStorage.LookupByKey(artifactAppearance.ArtifactAppearanceSetID); + if (artifactAppearanceSet != null) + { + if (itemProto.GetArtifactID() != artifactAppearanceSet.ArtifactID) + continue; + + PlayerConditionRecord playerCondition = CliDB.PlayerConditionStorage.LookupByKey(artifactAppearance.PlayerConditionID); + if (playerCondition != null) + if (!owner || !ConditionManager.IsPlayerMeetingCondition(owner, playerCondition)) + continue; + + SetModifier(ItemModifier.ArtifactAppearanceId, artifactAppearance.Id); + SetAppearanceModId(artifactAppearance.AppearanceModID); + break; + } + } + } + return true; + } + + public bool IsNotEmptyBag() + { + Bag bag = ToBag(); + if (bag != null) + return !bag.IsEmpty(); + + return false; + } + + public void UpdateDuration(Player owner, uint diff) + { + uint dur = GetUInt32Value(ItemFields.Duration); + + Log.outDebug(LogFilter.Player, "Item.UpdateDuration Item (Entry: {0} Duration {1} Diff {2})", GetEntry(), dur, diff); + + if (dur <= diff) + { + Global.ScriptMgr.OnItemExpire(owner, GetTemplate()); + owner.DestroyItem(GetBagSlot(), GetSlot(), true); + return; + } + + SetUInt32Value(ItemFields.Duration, dur - diff); + SetState(ItemUpdateState.Changed, owner); // save new time in database + } + + public virtual void SaveToDB(SQLTransaction trans) + { + PreparedStatement stmt; + switch (uState) + { + case ItemUpdateState.New: + case ItemUpdateState.Changed: + { + byte index = 0; + stmt = DB.Characters.GetPreparedStatement(uState == ItemUpdateState.New ? CharStatements.REP_ITEM_INSTANCE : CharStatements.UPD_ITEM_INSTANCE); + stmt.AddValue(index, GetEntry()); + stmt.AddValue(++index, GetOwnerGUID().GetCounter()); + stmt.AddValue(++index, GetGuidValue(ItemFields.Creator).GetCounter()); + stmt.AddValue(++index, GetGuidValue(ItemFields.GiftCreator).GetCounter()); + stmt.AddValue(++index, GetCount()); + stmt.AddValue(++index, GetUInt32Value(ItemFields.Duration)); + + StringBuilder ss = new StringBuilder(); + for (byte i = 0; i < ItemConst.MaxSpells; ++i) + ss.AppendFormat("{0} ", GetSpellCharges(i)); + + stmt.AddValue(++index, ss.ToString()); + stmt.AddValue(++index, GetUInt32Value(ItemFields.Flags)); + + ss.Clear(); + for (EnchantmentSlot slot = 0; slot < EnchantmentSlot.Max; ++slot) + ss.AppendFormat("{0} {1} {2} ", GetEnchantmentId(slot), GetEnchantmentDuration(slot), GetEnchantmentCharges(slot)); + + stmt.AddValue(++index, ss.ToString()); + stmt.AddValue(++index, (byte)GetItemRandomEnchantmentId().Type); + stmt.AddValue(++index, GetItemRandomEnchantmentId().Id); + stmt.AddValue(++index, GetUInt32Value(ItemFields.Durability)); + stmt.AddValue(++index, GetUInt32Value(ItemFields.CreatePlayedTime)); + stmt.AddValue(++index, m_text); + stmt.AddValue(++index, GetModifier(ItemModifier.UpgradeId)); + stmt.AddValue(++index, GetModifier(ItemModifier.BattlePetSpeciesId)); + stmt.AddValue(++index, GetModifier(ItemModifier.BattlePetBreedData)); + stmt.AddValue(++index, GetModifier(ItemModifier.BattlePetLevel)); + stmt.AddValue(++index, GetModifier(ItemModifier.BattlePetDisplayId)); + stmt.AddValue(++index, (byte)GetUInt32Value(ItemFields.Context)); + + ss.Clear(); + foreach (uint bonusListID in GetDynamicValues(ItemDynamicFields.BonusListIds)) + ss.Append(bonusListID + ' '); + + stmt.AddValue(++index, ss.ToString()); + stmt.AddValue(++index, GetGUID().GetCounter()); + + DB.Characters.Execute(stmt); + + if ((uState == ItemUpdateState.Changed) && HasFlag(ItemFields.Flags, ItemFieldFlags.Wrapped)) + { + stmt = DB.Characters.GetPreparedStatement(CharStatements.UPD_GIFT_OWNER); + stmt.AddValue(0, GetOwnerGUID().GetCounter()); + stmt.AddValue(1, GetGUID().GetCounter()); + DB.Characters.Execute(stmt); + } + + stmt = DB.Characters.GetPreparedStatement(CharStatements.DEL_ITEM_INSTANCE_GEMS); + stmt.AddValue(0, GetGUID().GetCounter()); + trans.Append(stmt); + + if (!GetGems().Empty()) + { + stmt = DB.Characters.GetPreparedStatement(CharStatements.INS_ITEM_INSTANCE_GEMS); + stmt.AddValue(0, GetGUID().GetCounter()); + int i = 0; + int gemFields = 4; + foreach (ItemDynamicFieldGems gemData in GetGems()) + { + if (gemData.ItemId != 0) + { + stmt.AddValue(1 + i * gemFields, gemData.ItemId); + StringBuilder gemBonusListIDs = new StringBuilder(); + foreach (ushort bonusListID in gemData.BonusListIDs) + { + if (bonusListID != 0) + gemBonusListIDs.AppendFormat("{0} ", bonusListID); + } + + stmt.AddValue(2 + i * gemFields, gemBonusListIDs.ToString()); + stmt.AddValue(3 + i * gemFields, gemData.Context); + stmt.AddValue(4 + i * gemFields, m_gemScalingLevels[i]); + } + else + { + stmt.AddValue(1 + i * gemFields, 0); + stmt.AddValue(2 + i * gemFields, ""); + stmt.AddValue(3 + i * gemFields, 0); + stmt.AddValue(4 + i * gemFields, 0); + } + ++i; + } + for (; i < ItemConst.MaxGemSockets; ++i) + { + stmt.AddValue(1 + i * gemFields, 0); + stmt.AddValue(2 + i * gemFields, ""); + stmt.AddValue(3 + i * gemFields, 0); + stmt.AddValue(4 + i * gemFields, 0); + } + trans.Append(stmt); + } + + ItemModifier[] transmogMods = + { + ItemModifier.TransmogAppearanceAllSpecs, + ItemModifier.TransmogAppearanceSpec1, + ItemModifier.TransmogAppearanceSpec2, + ItemModifier.TransmogAppearanceSpec3, + ItemModifier.TransmogAppearanceSpec4, + + ItemModifier.EnchantIllusionAllSpecs, + ItemModifier.EnchantIllusionSpec1, + ItemModifier.EnchantIllusionSpec2, + ItemModifier.EnchantIllusionSpec3, + ItemModifier.EnchantIllusionSpec4, + }; + + stmt = DB.Characters.GetPreparedStatement(CharStatements.DEL_ITEM_INSTANCE_TRANSMOG); + stmt.AddValue(0, GetGUID().GetCounter()); + trans.Append(stmt); + + if (transmogMods.Any(modifier => { return GetModifier(modifier) != 0; })) + { + stmt = DB.Characters.GetPreparedStatement(CharStatements.INS_ITEM_INSTANCE_TRANSMOG); + stmt.AddValue(0, GetGUID().GetCounter()); + stmt.AddValue(1, GetModifier(ItemModifier.TransmogAppearanceAllSpecs)); + stmt.AddValue(2, GetModifier(ItemModifier.TransmogAppearanceSpec1)); + stmt.AddValue(3, GetModifier(ItemModifier.TransmogAppearanceSpec2)); + stmt.AddValue(4, GetModifier(ItemModifier.TransmogAppearanceSpec3)); + stmt.AddValue(5, GetModifier(ItemModifier.TransmogAppearanceSpec4)); + stmt.AddValue(6, GetModifier(ItemModifier.EnchantIllusionAllSpecs)); + stmt.AddValue(7, GetModifier(ItemModifier.EnchantIllusionSpec1)); + stmt.AddValue(8, GetModifier(ItemModifier.EnchantIllusionSpec2)); + stmt.AddValue(9, GetModifier(ItemModifier.EnchantIllusionSpec3)); + stmt.AddValue(10, GetModifier(ItemModifier.EnchantIllusionSpec4)); + trans.Append(stmt); + } + + stmt = DB.Characters.GetPreparedStatement(CharStatements.DEL_ITEM_INSTANCE_ARTIFACT); + stmt.AddValue(0, GetGUID().GetCounter()); + trans.Append(stmt); + + stmt = DB.Characters.GetPreparedStatement(CharStatements.DEL_ITEM_INSTANCE_ARTIFACT_POWERS); + stmt.AddValue(0, GetGUID().GetCounter()); + trans.Append(stmt); + + if (GetTemplate().GetArtifactID() != 0) + { + stmt = DB.Characters.GetPreparedStatement(CharStatements.INS_ITEM_INSTANCE_ARTIFACT); + stmt.AddValue(0, GetGUID().GetCounter()); + stmt.AddValue(1, GetUInt64Value(ItemFields.ArtifactXp)); + stmt.AddValue(2, GetModifier(ItemModifier.ArtifactAppearanceId)); + trans.Append(stmt); + + foreach (ItemDynamicFieldArtifactPowers artifactPower in GetArtifactPowers()) + { + stmt = DB.Characters.GetPreparedStatement(CharStatements.INS_ITEM_INSTANCE_ARTIFACT_POWERS); + stmt.AddValue(0, GetGUID().GetCounter()); + stmt.AddValue(1, artifactPower.ArtifactPowerId); + stmt.AddValue(2, artifactPower.PurchasedRank); + trans.Append(stmt); + } + } + + ItemModifier[] modifiersTable = + { + ItemModifier.ScalingStatDistributionFixedLevel, + ItemModifier.ArtifactKnowledgeLevel + }; + + stmt = DB.Characters.GetPreparedStatement(CharStatements.DEL_ITEM_INSTANCE_MODIFIERS); + stmt.AddValue(0, GetGUID().GetCounter()); + trans.Append(stmt); + + if (modifiersTable.Any(modifier => { return GetModifier(modifier) != 0; })) + { + stmt = DB.Characters.GetPreparedStatement(CharStatements.INS_ITEM_INSTANCE_MODIFIERS); + stmt.AddValue(0, GetGUID().GetCounter()); + stmt.AddValue(1, GetModifier(ItemModifier.ScalingStatDistributionFixedLevel)); + stmt.AddValue(2, GetModifier(ItemModifier.ArtifactKnowledgeLevel)); + trans.Append(stmt); + } + break; + } + case ItemUpdateState.Removed: + { + stmt = DB.Characters.GetPreparedStatement(CharStatements.DEL_ITEM_INSTANCE); + stmt.AddValue(0, GetGUID().GetCounter()); + trans.Append(stmt); + + stmt = DB.Characters.GetPreparedStatement(CharStatements.DEL_ITEM_INSTANCE_GEMS); + stmt.AddValue(0, GetGUID().GetCounter()); + trans.Append(stmt); + + stmt = DB.Characters.GetPreparedStatement(CharStatements.DEL_ITEM_INSTANCE_TRANSMOG); + stmt.AddValue(0, GetGUID().GetCounter()); + trans.Append(stmt); + + stmt = DB.Characters.GetPreparedStatement(CharStatements.DEL_ITEM_INSTANCE_ARTIFACT); + stmt.AddValue(0, GetGUID().GetCounter()); + trans.Append(stmt); + + stmt = DB.Characters.GetPreparedStatement(CharStatements.DEL_ITEM_INSTANCE_ARTIFACT_POWERS); + stmt.AddValue(0, GetGUID().GetCounter()); + trans.Append(stmt); + + stmt = DB.Characters.GetPreparedStatement(CharStatements.DEL_ITEM_INSTANCE_MODIFIERS); + stmt.AddValue(0, GetGUID().GetCounter()); + trans.Append(stmt); + + if (HasFlag(ItemFields.Flags, ItemFieldFlags.Wrapped)) + { + stmt = DB.Characters.GetPreparedStatement(CharStatements.DEL_GIFT); + stmt.AddValue(0, GetGUID().GetCounter()); + trans.Append(stmt); + } + + // Delete the items if this is a container + if (!loot.isLooted()) + ItemContainerDeleteLootMoneyAndLootItemsFromDB(); + + Dispose(); + return; + } + case ItemUpdateState.Unchanged: + break; + } + + SetState(ItemUpdateState.Unchanged); + } + + public virtual bool LoadFromDB(ulong guid, ObjectGuid ownerGuid, SQLFields fields, uint entry) + { + // create item before any checks for store correct guid + // and allow use "FSetState(ITEM_REMOVED); SaveToDB();" for deleting item from DB + _Create(ObjectGuid.Create(HighGuid.Item, guid)); + + SetEntry(entry); + SetObjectScale(1.0f); + + ItemTemplate proto = GetTemplate(); + if (proto == null) + return false; + + _bonusData = new BonusData(proto); + + // set owner (not if item is only loaded for gbank/auction/mail + if (!ownerGuid.IsEmpty()) + SetOwnerGUID(ownerGuid); + + uint itemFlags = fields.Read(7); + bool need_save = false; + ulong creator = fields.Read(2); + if (creator != 0) + { + if (!Convert.ToBoolean(itemFlags & (int)ItemFieldFlags.Child)) + SetGuidValue(ItemFields.Creator, ObjectGuid.Create(HighGuid.Player, creator)); + else + SetGuidValue(ItemFields.Creator, ObjectGuid.Create(HighGuid.Item, creator)); + } + + ulong giftCreator = fields.Read(3); + if (giftCreator != 0) + SetGuidValue(ItemFields.GiftCreator, ObjectGuid.Create(HighGuid.Player, giftCreator)); + SetCount(fields.Read(4)); + + uint duration = fields.Read(5); + SetUInt32Value(ItemFields.Duration, duration); + // update duration if need, and remove if not need + if (proto.GetDuration() != duration) + { + SetUInt32Value(ItemFields.Duration, proto.GetDuration()); + need_save = true; + } + + var tokens = new StringArray(fields.Read(6), ' '); + if (tokens.Length == ItemConst.MaxProtoSpells) + for (byte i = 0; i < ItemConst.MaxProtoSpells; ++i) + SetSpellCharges(i, int.Parse(tokens[i])); + + SetUInt32Value(ItemFields.Flags, itemFlags); + + _LoadIntoDataField(fields.Read(8), (uint)ItemFields.Enchantment, (uint)EnchantmentSlot.Max * (uint)EnchantmentOffset.Max); + m_randomEnchantment.Type = (ItemRandomEnchantmentType)fields.Read(9); + m_randomEnchantment.Id = fields.Read(10); + if (m_randomEnchantment.Type == ItemRandomEnchantmentType.Property) + SetUInt32Value(ItemFields.RandomPropertiesId, m_randomEnchantment.Id); + else if (m_randomEnchantment.Type == ItemRandomEnchantmentType.Suffix) + { + SetInt32Value(ItemFields.RandomPropertiesId, -(int)m_randomEnchantment.Id); + // recalculate suffix factor + UpdateItemSuffixFactor(); + } + + uint durability = fields.Read(11); + SetUInt32Value(ItemFields.Durability, durability); + // update max durability (and durability) if need + SetUInt32Value(ItemFields.MaxDurability, proto.MaxDurability); + if (durability > proto.MaxDurability) + { + SetUInt32Value(ItemFields.Durability, proto.MaxDurability); + need_save = true; + } + + SetUInt32Value(ItemFields.CreatePlayedTime, fields.Read(12)); + SetText(fields.Read(13)); + + uint upgradeId = fields.Read(14); + ItemUpgradeRecord rulesetUpgrade = CliDB.ItemUpgradeStorage.LookupByKey(Global.DB2Mgr.GetRulesetItemUpgrade(entry)); + ItemUpgradeRecord upgrade = CliDB.ItemUpgradeStorage.LookupByKey(upgradeId); + if (rulesetUpgrade == null || upgrade == null || rulesetUpgrade.ItemUpgradePathID != upgrade.ItemUpgradePathID) + { + upgradeId = 0; + need_save = true; + } + + if (rulesetUpgrade != null && upgradeId == 0) + { + upgradeId = rulesetUpgrade.Id; + need_save = true; + } + + SetModifier(ItemModifier.UpgradeId, upgradeId); + SetModifier(ItemModifier.BattlePetSpeciesId, fields.Read(15)); + SetModifier(ItemModifier.BattlePetBreedData, fields.Read(16)); + SetModifier(ItemModifier.BattlePetLevel, fields.Read(17)); + SetModifier(ItemModifier.BattlePetDisplayId, fields.Read(18)); + + SetUInt32Value(ItemFields.Context, fields.Read(19)); + + var bonusListIDs = new StringArray(fields.Read(20), ' '); + for (var i = 0; i < bonusListIDs.Length; ++i) + { + uint bonusListID = uint.Parse(tokens[i]); + AddBonuses(bonusListID); + } + + SetModifier(ItemModifier.TransmogAppearanceAllSpecs, fields.Read(21)); + SetModifier(ItemModifier.TransmogAppearanceSpec1, fields.Read(22)); + SetModifier(ItemModifier.TransmogAppearanceSpec2, fields.Read(23)); + SetModifier(ItemModifier.TransmogAppearanceSpec3, fields.Read(24)); + SetModifier(ItemModifier.TransmogAppearanceSpec4, fields.Read(25)); + + SetModifier(ItemModifier.EnchantIllusionAllSpecs, fields.Read(26)); + SetModifier(ItemModifier.EnchantIllusionSpec1, fields.Read(27)); + SetModifier(ItemModifier.EnchantIllusionSpec2, fields.Read(28)); + SetModifier(ItemModifier.EnchantIllusionSpec3, fields.Read(29)); + SetModifier(ItemModifier.EnchantIllusionSpec4, fields.Read(30)); + + int gemFields = 4; + ItemDynamicFieldGems[] gemData = new ItemDynamicFieldGems[ItemConst.MaxGemSockets]; + for (int i = 0; i < ItemConst.MaxGemSockets; ++i) + { + gemData[i] = new ItemDynamicFieldGems(); + gemData[i].ItemId = fields.Read(31 + i * gemFields); + var gemBonusListIDs = new StringArray(fields.Read(32 + i * gemFields), ' '); + uint b = 0; + foreach (string token in gemBonusListIDs) + { + uint bonusListID = uint.Parse(token); + if (bonusListID != 0) + gemData[i].BonusListIDs[b++] = (ushort)bonusListID; + } + + gemData[i].Context = fields.Read(33 + i * gemFields); + if (gemData[i].ItemId != 0) + SetGem((ushort)i, gemData[i], fields.Read(34 + i * gemFields)); + } + + SetModifier(ItemModifier.ScalingStatDistributionFixedLevel, fields.Read(43)); + SetModifier(ItemModifier.ArtifactKnowledgeLevel, fields.Read(44)); + + // Remove bind flag for items vs NO_BIND set + if (IsSoulBound() && GetBonding() == ItemBondingType.None) + { + ApplyModFlag(ItemFields.Flags, ItemFieldFlags.Soulbound, false); + need_save = true; + } + + if (need_save) // normal item changed state set not work at loading + { + byte index = 0; + PreparedStatement stmt = DB.Characters.GetPreparedStatement(CharStatements.UPD_ITEM_INSTANCE_ON_LOAD); + stmt.AddValue(index++, GetUInt32Value(ItemFields.Duration)); + stmt.AddValue(index++, GetUInt32Value(ItemFields.Flags)); + stmt.AddValue(index++, GetUInt32Value(ItemFields.Durability)); + stmt.AddValue(index++, GetModifier(ItemModifier.UpgradeId)); + stmt.AddValue(index++, guid); + DB.Characters.Execute(stmt); + } + return true; + } + + public void LoadArtifactData(Player owner, ulong xp, uint artifactAppearanceId, List powers) + { + InitArtifactPowers(GetTemplate().GetArtifactID(), 0); + SetUInt64Value(ItemFields.ArtifactXp, xp); + SetModifier(ItemModifier.ArtifactAppearanceId, artifactAppearanceId); + ArtifactAppearanceRecord artifactAppearance = CliDB.ArtifactAppearanceStorage.LookupByKey(artifactAppearanceId); + if (artifactAppearance != null) + SetAppearanceModId(artifactAppearance.AppearanceModID); + + byte totalPurchasedRanks = 0; + foreach (ItemDynamicFieldArtifactPowers power in powers) + { + power.CurrentRankWithBonus += power.PurchasedRank; + totalPurchasedRanks += power.PurchasedRank; + + ArtifactPowerRecord artifactPower = CliDB.ArtifactPowerStorage.LookupByKey(power.ArtifactPowerId); + for (var e = EnchantmentSlot.Sock1; e <= EnchantmentSlot.Sock3; ++e) + { + SpellItemEnchantmentRecord enchant = CliDB.SpellItemEnchantmentStorage.LookupByKey(GetEnchantmentId(e)); + if (enchant != null) + { + for (uint i = 0; i < ItemConst.MaxItemEnchantmentEffects; ++i) + { + switch (enchant.Effect[i]) + { + case ItemEnchantmentType.ArtifactPowerBonusRankByType: + if (artifactPower.RelicType == enchant.EffectSpellID[i]) + power.CurrentRankWithBonus += (byte)enchant.EffectPointsMin[i]; + break; + case ItemEnchantmentType.ArtifactPowerBonusRankByID: + if (artifactPower.Id == enchant.EffectSpellID[i]) + power.CurrentRankWithBonus += (byte)enchant.EffectPointsMin[i]; + break; + case ItemEnchantmentType.ArtifactPowerBonusRankPicker: + if (_bonusData.GemRelicType[e - EnchantmentSlot.Sock1] != -1) + { + ArtifactPowerPickerRecord artifactPowerPicker = CliDB.ArtifactPowerPickerStorage.LookupByKey(enchant.EffectSpellID[i]); + if (artifactPowerPicker != null) + { + PlayerConditionRecord playerCondition = CliDB.PlayerConditionStorage.LookupByKey(artifactPowerPicker.PlayerConditionID); + if (playerCondition == null || ConditionManager.IsPlayerMeetingCondition(owner, playerCondition)) + if (artifactPower.RelicType == _bonusData.GemRelicType[e - EnchantmentSlot.Sock1]) + power.CurrentRankWithBonus += (byte)enchant.EffectPointsMin[i]; + } + } + break; + default: + break; + } + } + } + } + + SetArtifactPower(power); + } + + foreach (ItemDynamicFieldArtifactPowers power in powers) + { + ArtifactPowerRecord scaledArtifactPowerEntry = CliDB.ArtifactPowerStorage.LookupByKey(power.ArtifactPowerId); + if (!scaledArtifactPowerEntry.Flags.HasAnyFlag(ArtifactPowerFlag.ScalesWithNumPowers)) + continue; + + power.CurrentRankWithBonus = (byte)(totalPurchasedRanks + 1); + SetArtifactPower(power); + } + } + + public static void DeleteFromDB(SQLTransaction trans, ulong itemGuid) + { + PreparedStatement stmt = DB.Characters.GetPreparedStatement(CharStatements.DEL_ITEM_INSTANCE); + stmt.AddValue(0, itemGuid); + trans.Append(stmt); + } + + public virtual void DeleteFromDB(SQLTransaction trans) + { + DeleteFromDB(trans, GetGUID().GetCounter()); + + // Delete the items if this is a container + if (!loot.isLooted()) + ItemContainerDeleteLootMoneyAndLootItemsFromDB(); + } + + public static void DeleteFromInventoryDB(SQLTransaction trans, ulong itemGuid) + { + PreparedStatement stmt = DB.Characters.GetPreparedStatement(CharStatements.DEL_CHAR_INVENTORY_BY_ITEM); + stmt.AddValue(0, itemGuid); + trans.Append(stmt); + } + + public void DeleteFromInventoryDB(SQLTransaction trans) + { + DeleteFromInventoryDB(trans, GetGUID().GetCounter()); + } + + public ItemTemplate GetTemplate() + { + return Global.ObjectMgr.GetItemTemplate(GetEntry()); + } + + public Player GetOwner() + { + return Global.ObjAccessor.FindPlayer(GetOwnerGUID()); + } + + public SkillType GetSkill() + { + ItemTemplate proto = GetTemplate(); + return proto.GetSkill(); + } + + public void SetItemRandomProperties(ItemRandomEnchantmentId randomPropId) + { + if (randomPropId.Id == 0) + return; + + switch (randomPropId.Type) + { + case ItemRandomEnchantmentType.Property: + { + ItemRandomPropertiesRecord item_rand = CliDB.ItemRandomPropertiesStorage.LookupByKey(randomPropId.Id); + if (item_rand != null) + { + if (GetUInt32Value(ItemFields.RandomPropertiesId) != randomPropId.Id) + { + SetUInt32Value(ItemFields.RandomPropertiesId, randomPropId.Id); + SetState(ItemUpdateState.Changed, GetOwner()); + } + for (EnchantmentSlot i = EnchantmentSlot.Prop0; i <= EnchantmentSlot.Prop4; ++i) + SetEnchantment(i, item_rand.Enchantment[i - EnchantmentSlot.Prop0], 0, 0); + } + } + break; + case ItemRandomEnchantmentType.Suffix: + { + ItemRandomSuffixRecord item_rand = CliDB.ItemRandomSuffixStorage.LookupByKey(randomPropId.Id); + if (item_rand != null) + { + if (GetInt32Value(ItemFields.RandomPropertiesId) != -(int)randomPropId.Id || GetItemSuffixFactor() == 0) + { + SetInt32Value(ItemFields.RandomPropertiesId, -(int)randomPropId.Id); + UpdateItemSuffixFactor(); + SetState(ItemUpdateState.Changed, GetOwner()); + } + + for (var i = EnchantmentSlot.Prop0; i <= EnchantmentSlot.Prop4; ++i) + SetEnchantment(i, item_rand.Enchantment[i - EnchantmentSlot.Prop0], 0, 0); + } + } + break; + case ItemRandomEnchantmentType.BonusList: + AddBonuses(randomPropId.Id); + break; + default: + break; + } + } + + void UpdateItemSuffixFactor() + { + uint suffixFactor = ItemEnchantment.GenerateEnchSuffixFactor(GetEntry()); + if (GetItemSuffixFactor() == suffixFactor) + return; + SetUInt32Value(ItemFields.PropertySeed, suffixFactor); + } + + public void SetState(ItemUpdateState state, Player forplayer = null) + { + if (uState == ItemUpdateState.New && uState == ItemUpdateState.Removed) + { + // pretend the item never existed + if (forplayer) + { + RemoveItemFromUpdateQueueOf(this, forplayer); + forplayer.DeleteRefundReference(GetGUID()); + } + return; + } + if (state != ItemUpdateState.Unchanged) + { + // new items must stay in new state until saved + if (uState != ItemUpdateState.New) + uState = state; + + if (forplayer) + AddItemToUpdateQueueOf(this, forplayer); + } + else + { + // unset in queue + // the item must be removed from the queue manually + uQueuePos = -1; + uState = ItemUpdateState.Unchanged; + } + } + + static void AddItemToUpdateQueueOf(Item item, Player player) + { + if (item.IsInUpdateQueue()) + return; + + Contract.Assert(player != null); + + if (player.GetGUID() != item.GetOwnerGUID()) + { + Log.outError(LogFilter.Player, "Item.AddToUpdateQueueOf - Owner's guid ({0}) and player's guid ({1}) don't match!", item.GetOwnerGUID(), player.GetGUID().ToString()); + return; + } + + if (player.m_itemUpdateQueueBlocked) + return; + + player.ItemUpdateQueue.Add(item); + item.uQueuePos = player.ItemUpdateQueue.Count - 1; + } + + public static void RemoveItemFromUpdateQueueOf(Item item, Player player) + { + if (!item.IsInUpdateQueue()) + return; + + Contract.Assert(player != null); + + if (player.GetGUID() != item.GetOwnerGUID()) + { + Log.outError(LogFilter.Player, "Item.RemoveFromUpdateQueueOf - Owner's guid ({0}) and player's guid ({1}) don't match!", item.GetOwnerGUID().ToString(), player.GetGUID().ToString()); + return; + } + + if (player.m_itemUpdateQueueBlocked) + return; + + player.ItemUpdateQueue[item.uQueuePos] = null; + item.uQueuePos = -1; + } + + public byte GetBagSlot() + { + return m_container != null ? m_container.GetSlot() : InventorySlots.Bag0; + } + + public bool IsEquipped() { return !IsInBag() && m_slot < EquipmentSlot.End; } + + public bool CanBeTraded(bool mail = false, bool trade = false) + { + if (m_lootGenerated) + return false; + + if ((!mail || !IsBoundAccountWide()) && (IsSoulBound() && (!HasFlag(ItemFields.Flags, ItemFieldFlags.BopTradeable) || !trade))) + return false; + + if (IsBag() && (Player.IsBagPos(GetPos()) || !ToBag().IsEmpty())) + return false; + + Player owner = GetOwner(); + if (owner != null) + { + if (owner.CanUnequipItem(GetPos(), false) != InventoryResult.Ok) + return false; + if (owner.GetLootGUID() == GetGUID()) + return false; + } + + if (IsBoundByEnchant()) + return false; + + return true; + } + + public void SetCount(uint value) + { + SetUInt32Value(ItemFields.StackCount, value); + + Player player = GetOwner(); + if (player) + { + TradeData tradeData = player.GetTradeData(); + if (tradeData != null) + { + TradeSlots slot = tradeData.GetTradeSlotForItem(GetGUID()); + + if (slot != TradeSlots.Invalid) + tradeData.SetItem(slot, this, true); + } + } + } + + bool HasEnchantRequiredSkill(Player player) + { + // Check all enchants for required skill + for (var enchant_slot = EnchantmentSlot.Perm; enchant_slot < EnchantmentSlot.Max; ++enchant_slot) + { + uint enchant_id = GetEnchantmentId(enchant_slot); + if (enchant_id != 0) + { + SpellItemEnchantmentRecord enchantEntry = CliDB.SpellItemEnchantmentStorage.LookupByKey(enchant_id); + if (enchantEntry != null) + if (enchantEntry.RequiredSkillID != 0 && player.GetSkillValue((SkillType)enchantEntry.RequiredSkillID) < enchantEntry.RequiredSkillRank) + return false; + } + } + + return true; + } + + uint GetEnchantRequiredLevel() + { + uint level = 0; + + // Check all enchants for required level + for (var enchant_slot = EnchantmentSlot.Perm; enchant_slot < EnchantmentSlot.Max; ++enchant_slot) + { + uint enchant_id = GetEnchantmentId(enchant_slot); + if (enchant_id != 0) + { + var enchantEntry = CliDB.SpellItemEnchantmentStorage.LookupByKey(enchant_id); + if (enchantEntry != null) + if (enchantEntry.MinLevel > level) + level = enchantEntry.MinLevel; + } + } + + return level; + } + + bool IsBoundByEnchant() + { + // Check all enchants for soulbound + for (var enchant_slot = EnchantmentSlot.Perm; enchant_slot < EnchantmentSlot.Max; ++enchant_slot) + { + uint enchant_id = GetEnchantmentId(enchant_slot); + if (enchant_id != 0) + { + var enchantEntry = CliDB.SpellItemEnchantmentStorage.LookupByKey(enchant_id); + if (enchantEntry != null) + if (enchantEntry.Flags.HasAnyFlag(EnchantmentSlotMask.CanSouldBound)) + return true; + } + } + + return false; + } + + public InventoryResult CanBeMergedPartlyWith(ItemTemplate proto) + { + // not allow merge looting currently items + if (m_lootGenerated) + return InventoryResult.LootGone; + + // check item type + if (GetEntry() != proto.GetId()) + return InventoryResult.CantStack; + + // check free space (full stacks can't be target of merge + if (GetCount() >= proto.GetMaxStackSize()) + return InventoryResult.CantStack; + + return InventoryResult.Ok; + } + + public bool IsFitToSpellRequirements(SpellInfo spellInfo) + { + ItemTemplate proto = GetTemplate(); + + bool isEnchantSpell = spellInfo.HasEffect(SpellEffectName.EnchantItem) || spellInfo.HasEffect(SpellEffectName.EnchantItemTemporary) || spellInfo.HasEffect(SpellEffectName.EnchantItemPrismatic); + if ((int)spellInfo.EquippedItemClass != -1) // -1 == any item class + { + if (isEnchantSpell && proto.GetFlags3().HasAnyFlag(ItemFlags3.CanStoreEnchants)) + return true; + + if (spellInfo.EquippedItemClass != proto.GetClass()) + return false; // wrong item class + + if (spellInfo.EquippedItemSubClassMask != 0) // 0 == any subclass + { + if ((spellInfo.EquippedItemSubClassMask & (1 << (int)proto.GetSubClass())) == 0) + return false; // subclass not present in mask + } + } + + if (isEnchantSpell && spellInfo.EquippedItemInventoryTypeMask != 0) // 0 == any inventory type + { + // Special case - accept weapon type for main and offhand requirements + if (proto.GetInventoryType() == InventoryType.Weapon && + Convert.ToBoolean(spellInfo.EquippedItemInventoryTypeMask & (1 << (int)InventoryType.WeaponMainhand)) || + Convert.ToBoolean(spellInfo.EquippedItemInventoryTypeMask & (1 << (int)InventoryType.WeaponOffhand))) + return true; + else if ((spellInfo.EquippedItemInventoryTypeMask & (1 << (int)proto.GetInventoryType())) == 0) + return false; // inventory type not present in mask + } + + return true; + } + + public void SetEnchantment(EnchantmentSlot slot, uint id, uint duration, uint charges, ObjectGuid caster = default(ObjectGuid)) + { + // Better lost small time at check in comparison lost time at item save to DB. + if ((GetEnchantmentId(slot) == id) && (GetEnchantmentDuration(slot) == duration) && (GetEnchantmentCharges(slot) == charges)) + return; + + Player owner = GetOwner(); + if (slot < EnchantmentSlot.MaxInspected) + { + uint oldEnchant = GetEnchantmentId(slot); + if (oldEnchant != 0) + owner.GetSession().SendEnchantmentLog(GetOwnerGUID(), ObjectGuid.Empty, GetGUID(), GetEntry(), oldEnchant, (uint)slot); + + if (id != 0) + owner.GetSession().SendEnchantmentLog(GetOwnerGUID(), caster, GetGUID(), GetEntry(), id, (uint)slot); + } + + ApplyArtifactPowerEnchantmentBonuses(slot, GetEnchantmentId(slot), false, owner); + ApplyArtifactPowerEnchantmentBonuses(slot, id, true, owner); + + SetUInt32Value(ItemFields.Enchantment + (int)slot * (int)EnchantmentOffset.Max + (int)EnchantmentOffset.Id, id); + SetUInt32Value(ItemFields.Enchantment + (int)slot * (int)EnchantmentOffset.Max + (int)EnchantmentOffset.Duration, duration); + SetUInt32Value(ItemFields.Enchantment + (int)slot * (int)EnchantmentOffset.Max + (int)EnchantmentOffset.Charges, charges); + SetState(ItemUpdateState.Changed, owner); + } + + public void SetEnchantmentDuration(EnchantmentSlot slot, uint duration, Player owner) + { + if (GetEnchantmentDuration(slot) == duration) + return; + + SetUInt32Value(ItemFields.Enchantment + (int)slot * (int)EnchantmentOffset.Max + (int)EnchantmentOffset.Duration, duration); + SetState(ItemUpdateState.Changed, owner); + // Cannot use GetOwner() here, has to be passed as an argument to avoid freeze due to hashtable locking + } + + public void SetEnchantmentCharges(EnchantmentSlot slot, uint charges) + { + if (GetEnchantmentCharges(slot) == charges) + return; + + SetUInt32Value(ItemFields.Enchantment + (int)slot * (int)EnchantmentOffset.Max + (int)EnchantmentOffset.Charges, charges); + SetState(ItemUpdateState.Changed, GetOwner()); + } + + public void ClearEnchantment(EnchantmentSlot slot) + { + if (GetEnchantmentId(slot) == 0) + return; + + for (byte x = 0; x < ItemConst.MaxItemEnchantmentEffects; ++x) + SetUInt32Value(ItemFields.Enchantment + (int)slot * (int)EnchantmentOffset.Max + x, 0); + SetState(ItemUpdateState.Changed, GetOwner()); + } + + public List GetGems() + { + return GetDynamicStructuredValues(ItemDynamicFields.Gems); + } + + public ItemDynamicFieldGems GetGem(ushort slot) + { + //ASSERT(slot < MAX_GEM_SOCKETS); + return GetDynamicStructuredValue(ItemDynamicFields.Gems, slot); + } + + public void SetGem(ushort slot, ItemDynamicFieldGems gem, uint gemScalingLevel) + { + //ASSERT(slot < MAX_GEM_SOCKETS); + m_gemScalingLevels[slot] = gemScalingLevel; + _bonusData.GemItemLevelBonus[slot] = 0; + ItemTemplate gemTemplate = Global.ObjectMgr.GetItemTemplate(gem.ItemId); + if (gemTemplate != null) + { + GemPropertiesRecord gemProperties = CliDB.GemPropertiesStorage.LookupByKey(gemTemplate.GetGemProperties()); + if (gemProperties != null) + { + SpellItemEnchantmentRecord gemEnchant = CliDB.SpellItemEnchantmentStorage.LookupByKey(gemProperties.EnchantID); + if (gemEnchant != null) + { + BonusData gemBonus = new BonusData(gemTemplate); + foreach (var bonusListId in gem.BonusListIDs) + { + + var bonuses = Global.DB2Mgr.GetItemBonusList(bonusListId); + if (bonuses != null) + { + + foreach (ItemBonusRecord itemBonus in bonuses) + gemBonus.AddBonus(itemBonus.Type, itemBonus.Value); + } + + uint gemBaseItemLevel = gemTemplate.GetBaseItemLevel(); + ScalingStatDistributionRecord ssd = CliDB.ScalingStatDistributionStorage.LookupByKey(gemBonus.ScalingStatDistribution); + if (ssd != null) + { + uint scaledIlvl = (uint)Global.DB2Mgr.GetCurveValueAt(ssd.ItemLevelCurveID, gemScalingLevel); + if (scaledIlvl != 0) + gemBaseItemLevel = scaledIlvl; + } + + _bonusData.GemRelicType[slot] = gemBonus.RelicType; + + for (uint i = 0; i < ItemConst.MaxItemEnchantmentEffects; ++i) + { + switch (gemEnchant.Effect[i]) + { + case ItemEnchantmentType.BonusListID: + { + var bonusesEffect = Global.DB2Mgr.GetItemBonusList(gemEnchant.EffectSpellID[i]); + if (bonusesEffect != null) + { + foreach (ItemBonusRecord itemBonus in bonusesEffect) + if (itemBonus.Type == ItemBonusType.ItemLevel) + + _bonusData.GemItemLevelBonus[slot] += (uint)itemBonus.Value[0]; + } + break; + } + case ItemEnchantmentType.BonusListCurve: + { + uint artifactrBonusListId = Global.DB2Mgr.GetItemBonusListForItemLevelDelta((short)Global.DB2Mgr.GetCurveValueAt((uint)Curves.ArtifactRelicItemLevelBonus, gemBaseItemLevel + gemBonus.ItemLevelBonus)); + if (artifactrBonusListId != 0) + { + var bonusesEffect = Global.DB2Mgr.GetItemBonusList(artifactrBonusListId); + if (bonusesEffect != null) + foreach (ItemBonusRecord itemBonus in bonusesEffect) + if (itemBonus.Type == ItemBonusType.ItemLevel) + _bonusData.GemItemLevelBonus[slot] += (uint)itemBonus.Value[0]; + } + break; + } + default: + break; + } + } + } + } + } + } + + SetDynamicStructuredValue(ItemDynamicFields.Gems, slot, gem); + } + + public bool GemsFitSockets() + { + uint gemSlot = 0; + foreach (ItemDynamicFieldGems gemData in GetGems()) + { + SocketColor SocketColor = GetTemplate().GetSocketColor(gemSlot); + if (SocketColor == 0) // no socket slot + continue; + + SocketColor GemColor = 0; + + ItemTemplate gemProto = Global.ObjectMgr.GetItemTemplate(gemData.ItemId); + if (gemProto != null) + { + GemPropertiesRecord gemProperty = CliDB.GemPropertiesStorage.LookupByKey(gemProto.GetGemProperties()); + if (gemProperty != null) + GemColor = gemProperty.Type; + } + + if (!GemColor.HasAnyFlag(ItemConst.SocketColorToGemTypeMask[(int)SocketColor])) // bad gem color on this socket + return false; + } + return true; + } + + public byte GetGemCountWithID(uint GemID) + { + return (byte)GetGems().Count(gemData => + { + return gemData.ItemId == GemID; + }); + } + + public byte GetGemCountWithLimitCategory(uint limitCategory) + { + return (byte)GetGems().Count(gemData => + { + ItemTemplate gemProto = Global.ObjectMgr.GetItemTemplate(gemData.ItemId); + if (gemProto == null) + return false; + + return gemProto.GetItemLimitCategory() == limitCategory; + }); + } + + public bool IsLimitedToAnotherMapOrZone(uint cur_mapId, uint cur_zoneId) + { + ItemTemplate proto = GetTemplate(); + return proto != null && ((proto.GetMap() != 0 && proto.GetMap() != cur_mapId) || (proto.GetArea() != 0 && proto.GetArea() != cur_zoneId)); + } + + public void SendUpdateSockets() + { + SocketGemsResult socketGems = new SocketGemsResult(); + socketGems.Item = GetGUID(); + + GetOwner().SendPacket(socketGems); + } + + public void SendTimeUpdate(Player owner) + { + uint duration = GetUInt32Value(ItemFields.Duration); + if (duration == 0) + return; + + ItemTimeUpdate itemTimeUpdate = new ItemTimeUpdate(); + itemTimeUpdate.ItemGuid = GetGUID(); + itemTimeUpdate.DurationLeft = duration; + owner.SendPacket(itemTimeUpdate); + } + + public static Item CreateItem(uint item, uint count, Player player = null) + { + if (count < 1) + return null; //don't create item at zero count + + var pProto = Global.ObjectMgr.GetItemTemplate(item); + if (pProto != null) + { + if (count > pProto.GetMaxStackSize()) + count = pProto.GetMaxStackSize(); + + Item pItem = Bag.NewItemOrBag(pProto); + if (pItem.Create(Global.ObjectMgr.GetGenerator(HighGuid.Item).Generate(), item, player)) + { + pItem.SetCount(count); + return pItem; + } + } + + return null; + } + + public Item CloneItem(uint count, Player player = null) + { + Item newItem = CreateItem(GetEntry(), count, player); + if (newItem == null) + return null; + + newItem.SetUInt32Value(ItemFields.Creator, GetUInt32Value(ItemFields.Creator)); + newItem.SetUInt32Value(ItemFields.GiftCreator, GetUInt32Value(ItemFields.GiftCreator)); + newItem.SetUInt32Value(ItemFields.Flags, GetUInt32Value(ItemFields.Flags)); + newItem.SetUInt32Value(ItemFields.Duration, GetUInt32Value(ItemFields.Duration)); + // player CAN be NULL in which case we must not update random properties because that accesses player's item update queue + if (player != null) + newItem.SetItemRandomProperties(GetItemRandomEnchantmentId()); + return newItem; + } + + public bool IsBindedNotWith(Player player) + { + // not binded item + if (!IsSoulBound()) + return false; + + // own item + if (GetOwnerGUID() == player.GetGUID()) + return false; + + if (HasFlag(ItemFields.Flags, ItemFieldFlags.BopTradeable)) + if (allowedGUIDs.Contains(player.GetGUID())) + return false; + + // BOA item case + if (IsBoundAccountWide()) + return false; + + return true; + } + + public override void BuildUpdate(Dictionary data) + { + Player owner = GetOwner(); + if (owner != null) + BuildFieldsUpdate(owner, data); + ClearUpdateMask(false); + } + + public override void BuildDynamicValuesUpdate(UpdateType updateType, WorldPacket data, Player target) + { + if (!target) + return; + + ByteBuffer fieldBuffer = new ByteBuffer(); + UpdateMask fieldMask = new UpdateMask(_dynamicValuesCount); + + uint[] flags = null; + uint visibleFlag = GetDynamicUpdateFieldData(target, out flags); + + for (ushort index = 0; index < _dynamicValuesCount; ++index) + { + var values = _dynamicValues[index]; + if (_fieldNotifyFlags.HasAnyFlag(flags[index]) || + ((updateType == UpdateType.Values ? _dynamicChangesMask[index] != DynamicFieldChangeType.Unchanged : !values.Empty()) && flags[index].HasAnyFlag(visibleFlag))) + { + ByteBuffer arrayValuesBuffer = new ByteBuffer(); + fieldMask.SetBit(index); + + DynamicUpdateMask arrayValuesMask = new DynamicUpdateMask((uint)values.Count); + arrayValuesMask.EncodeDynamicFieldChangeType(_dynamicChangesMask[index], updateType); + + if (updateType == UpdateType.Values && _dynamicChangesMask[index] == DynamicFieldChangeType.ValueAndSizeChanged) + arrayValuesMask.ValueCount = values.Count; + + if (index != (int)ItemDynamicFields.Modifiers) + { + foreach (var pair in values) + { + if (updateType != UpdateType.Values || _dynamicChangesArrayMask[index].Get(pair.Key)) + { + arrayValuesMask.SetBit(pair.Key); + arrayValuesBuffer.WriteUInt32(pair.Value); + } + } + } + else + { + int m = 0; + + if (updateType == UpdateType.Values && _dynamicChangesMask[index] != DynamicFieldChangeType.ValueAndSizeChanged && _changesMask.Get((int)ItemFields.ModifiersMask)) + { + arrayValuesMask.DynamicFieldChangeType |= (int)DynamicFieldChangeType.ValueAndSizeChanged; + arrayValuesMask.ValueCount = m; + } + + // in case of ITEM_DYNAMIC_FIELD_MODIFIERS it is ITEM_FIELD_MODIFIERS_MASK that controls index of each value, not updatemask + // so we just have to write this starting from 0 index + foreach (var pair in values) + { + if (pair.Value != 0) + { + arrayValuesMask.SetBit(m++); + arrayValuesBuffer.WriteUInt32(pair.Value); + } + } + + if (updateType == UpdateType.Values && _changesMask.Get((int)ItemFields.ModifiersMask)) + arrayValuesMask.ValueCount = m; + } + + arrayValuesMask.AppendToPacket(fieldBuffer); + fieldBuffer.WriteBytes(arrayValuesBuffer); + } + } + + fieldMask.AppendToPacket(data); + data.WriteBytes(fieldBuffer); + } + + public override void AddToObjectUpdate() + { + Player owner = GetOwner(); + if (owner) + owner.GetMap().AddUpdateObject(this); + } + + public override void RemoveFromObjectUpdate() + { + Player owner = GetOwner(); + if (owner) + owner.GetMap().RemoveUpdateObject(this); + } + + public void SaveRefundDataToDB() + { + DeleteRefundDataFromDB(); + + PreparedStatement stmt = DB.Characters.GetPreparedStatement(CharStatements.INS_ITEM_REFUND_INSTANCE); + stmt.AddValue(0, GetGUID().GetCounter()); + stmt.AddValue(1, GetRefundRecipient().GetCounter()); + stmt.AddValue(2, GetPaidMoney()); + stmt.AddValue(3, (ushort)GetPaidExtendedCost()); + DB.Characters.Execute(stmt); + } + + public void DeleteRefundDataFromDB(SQLTransaction trans = null) + { + PreparedStatement stmt = DB.Characters.GetPreparedStatement(CharStatements.DEL_ITEM_REFUND_INSTANCE); + stmt.AddValue(0, GetGUID().GetCounter()); + if (trans != null) + trans.Append(stmt); + else + DB.Characters.Execute(stmt); + } + + public void SetNotRefundable(Player owner, bool changestate = true, SQLTransaction trans = null, bool addToCollection = true) + { + if (!HasFlag(ItemFields.Flags, ItemFieldFlags.Refundable)) + return; + + ItemExpirePurchaseRefund itemExpirePurchaseRefund = new ItemExpirePurchaseRefund(); + itemExpirePurchaseRefund.ItemGUID = GetGUID(); + owner.SendPacket(itemExpirePurchaseRefund); + + RemoveFlag(ItemFields.Flags, ItemFieldFlags.Refundable); + // Following is not applicable in the trading procedure + if (changestate) + SetState(ItemUpdateState.Changed, owner); + + SetRefundRecipient(ObjectGuid.Empty); + SetPaidMoney(0); + SetPaidExtendedCost(0); + DeleteRefundDataFromDB(trans); + + owner.DeleteRefundReference(GetGUID()); + if (addToCollection) + owner.GetSession().GetCollectionMgr().AddItemAppearance(this); + } + + public void UpdatePlayedTime(Player owner) + { + // Get current played time + uint current_playtime = GetUInt32Value(ItemFields.CreatePlayedTime); + // Calculate time elapsed since last played time update + long curtime = Time.UnixTime; + uint elapsed = (uint)(curtime - m_lastPlayedTimeUpdate); + uint new_playtime = current_playtime + elapsed; + // Check if the refund timer has expired yet + if (new_playtime <= 2 * Time.Hour) + { + // No? Proceed. + // Update the data field + SetUInt32Value(ItemFields.CreatePlayedTime, new_playtime); + // Flag as changed to get saved to DB + SetState(ItemUpdateState.Changed, owner); + // Speaks for itself + m_lastPlayedTimeUpdate = curtime; + return; + } + // Yes + SetNotRefundable(owner); + } + + public uint GetPlayedTime() + { + long curtime = Time.UnixTime; + uint elapsed = (uint)(curtime - m_lastPlayedTimeUpdate); + return GetUInt32Value(ItemFields.CreatePlayedTime) + elapsed; + } + + public bool IsRefundExpired() + { + return (GetPlayedTime() > 2 * Time.Hour); + } + + public void SetSoulboundTradeable(List allowedLooters) + { + SetFlag(ItemFields.Flags, ItemFieldFlags.BopTradeable); + allowedGUIDs = allowedLooters; + } + + public void ClearSoulboundTradeable(Player currentOwner) + { + RemoveFlag(ItemFields.Flags, ItemFieldFlags.BopTradeable); + if (allowedGUIDs.Empty()) + return; + + currentOwner.GetSession().GetCollectionMgr().AddItemAppearance(this); + allowedGUIDs.Clear(); + SetState(ItemUpdateState.Changed, currentOwner); + PreparedStatement stmt = DB.Characters.GetPreparedStatement(CharStatements.DEL_ITEM_BOP_TRADE); + stmt.AddValue(0, GetGUID().GetCounter()); + DB.Characters.Execute(stmt); + } + + public bool CheckSoulboundTradeExpire() + { + // called from owner's update - GetOwner() MUST be valid + if (GetUInt32Value(ItemFields.CreatePlayedTime) + 2 * Time.Hour < GetOwner().GetTotalPlayedTime()) + { + ClearSoulboundTradeable(GetOwner()); + return true; // remove from tradeable list + } + + return false; + } + + bool IsValidTransmogrificationTarget() + { + ItemTemplate proto = GetTemplate(); + if (proto == null) + return false; + + if (proto.GetClass() != ItemClass.Armor && + proto.GetClass() != ItemClass.Weapon) + return false; + + if (proto.GetClass() == ItemClass.Weapon && proto.GetSubClass() == (uint)ItemSubClassWeapon.FishingPole) + return false; + + if (proto.GetFlags2().HasAnyFlag(ItemFlags2.NoAlterItemVisual)) + return false; + + if (!HasStats()) + return false; + + return true; + } + + bool HasStats() + { + if (GetItemRandomPropertyId() != 0) + return true; + + ItemTemplate proto = GetTemplate(); + Player owner = GetOwner(); + for (byte i = 0; i < ItemConst.MaxStats; ++i) + { + if ((owner ? GetItemStatValue(i, owner) : proto.GetItemStatValue(i)) != 0) + return true; + } + + return false; + } + + static bool HasStats(ItemInstance itemInstance, BonusData bonus) + { + if (itemInstance.RandomPropertiesID != 0) + return true; + + for (byte i = 0; i < ItemConst.MaxStats; ++i) + { + if (bonus.ItemStatValue[i] != 0) + return true; + } + + return false; + } + + static ItemTransmogrificationWeaponCategory GetTransmogrificationWeaponCategory(ItemTemplate proto) + { + if (proto.GetClass() == ItemClass.Weapon) + { + switch ((ItemSubClassWeapon)proto.GetSubClass()) + { + case ItemSubClassWeapon.Axe2: + case ItemSubClassWeapon.Mace2: + case ItemSubClassWeapon.Sword2: + case ItemSubClassWeapon.Staff: + case ItemSubClassWeapon.Polearm: + return ItemTransmogrificationWeaponCategory.Melee2H; + case ItemSubClassWeapon.Bow: + case ItemSubClassWeapon.Gun: + case ItemSubClassWeapon.Crossbow: + return ItemTransmogrificationWeaponCategory.Ranged; + case ItemSubClassWeapon.Axe: + case ItemSubClassWeapon.Mace: + case ItemSubClassWeapon.Sword: + case ItemSubClassWeapon.Warglaives: + return ItemTransmogrificationWeaponCategory.AxeMaceSword1H; + case ItemSubClassWeapon.Dagger: + return ItemTransmogrificationWeaponCategory.Dagger; + case ItemSubClassWeapon.Fist: + return ItemTransmogrificationWeaponCategory.Fist; + default: + break; + } + } + + return ItemTransmogrificationWeaponCategory.Invalid; + } + + static int[] ItemTransmogrificationSlots = + { + -1, // INVTYPE_NON_EQUIP + EquipmentSlot.Head, // INVTYPE_HEAD + EquipmentSlot.Neck, // INVTYPE_NECK + EquipmentSlot.Shoulders, // INVTYPE_SHOULDERS + EquipmentSlot.Shirt, // INVTYPE_BODY + EquipmentSlot.Chest, // INVTYPE_CHEST + EquipmentSlot.Waist, // INVTYPE_WAIST + EquipmentSlot.Legs, // INVTYPE_LEGS + EquipmentSlot.Feet, // INVTYPE_FEET + EquipmentSlot.Wrist, // INVTYPE_WRISTS + EquipmentSlot.Hands, // INVTYPE_HANDS + -1, // INVTYPE_FINGER + -1, // INVTYPE_TRINKET + -1, // INVTYPE_WEAPON + EquipmentSlot.OffHand, // INVTYPE_SHIELD + EquipmentSlot.MainHand, // INVTYPE_RANGED + EquipmentSlot.Cloak, // INVTYPE_CLOAK + -1, // INVTYPE_2HWEAPON + -1, // INVTYPE_BAG + EquipmentSlot.Tabard, // INVTYPE_TABARD + EquipmentSlot.Chest, // INVTYPE_ROBE + EquipmentSlot.MainHand, // INVTYPE_WEAPONMAINHAND + EquipmentSlot.OffHand, // INVTYPE_WEAPONOFFHAND + EquipmentSlot.OffHand, // INVTYPE_HOLDABLE + -1, // INVTYPE_AMMO + EquipmentSlot.MainHand, // INVTYPE_THROWN + EquipmentSlot.MainHand, // INVTYPE_RANGEDRIGHT + -1, // INVTYPE_QUIVER + -1 // INVTYPE_RELIC + }; + + public static bool CanTransmogrifyItemWithItem(Item item, ItemModifiedAppearanceRecord itemModifiedAppearance) + { + ItemTemplate source = Global.ObjectMgr.GetItemTemplate(itemModifiedAppearance.ItemID); // source + ItemTemplate target = item.GetTemplate(); // dest + + if (source == null || target == null) + return false; + + if (itemModifiedAppearance == item.GetItemModifiedAppearance()) + return false; + + if (!item.IsValidTransmogrificationTarget()) + return false; + + if (source.GetClass() != target.GetClass()) + return false; + + if (source.GetInventoryType() == InventoryType.Bag || + source.GetInventoryType() == InventoryType.Relic || + source.GetInventoryType() == InventoryType.Finger || + source.GetInventoryType() == InventoryType.Trinket || + source.GetInventoryType() == InventoryType.Ammo || + source.GetInventoryType() == InventoryType.Quiver) + return false; + + if (source.GetSubClass() != target.GetSubClass()) + { + switch (source.GetClass()) + { + case ItemClass.Weapon: + if (GetTransmogrificationWeaponCategory(source) != GetTransmogrificationWeaponCategory(target)) + return false; + break; + case ItemClass.Armor: + if ((ItemSubClassArmor)source.GetSubClass() != ItemSubClassArmor.Cosmetic) + return false; + break; + default: + return false; + } + } + + if (source.GetInventoryType() != target.GetInventoryType()) + { + int sourceSlot = ItemTransmogrificationSlots[(int)source.GetInventoryType()]; + if (sourceSlot == -1 && source.GetInventoryType() == InventoryType.Weapon && (target.GetInventoryType() == InventoryType.WeaponMainhand || target.GetInventoryType() == InventoryType.WeaponOffhand)) + sourceSlot = ItemTransmogrificationSlots[(int)target.GetInventoryType()]; + + if (sourceSlot != ItemTransmogrificationSlots[(int)target.GetInventoryType()]) + return false; + } + + return true; + } + + // used by mail items, transmog cost, stationeryinfo and others + static uint GetSellPrice(ItemTemplate proto, out bool normalSellPrice) + { + normalSellPrice = true; + + if (proto.GetFlags2().HasAnyFlag(ItemFlags2.OverrideGoldCost)) + { + return proto.GetBuyPrice(); + } + else + { + var qualityPrice = CliDB.ImportPriceQualityStorage.LookupByKey(proto.GetQuality() + 1); + var basePrice = CliDB.ItemPriceBaseStorage.LookupByKey(proto.GetBaseItemLevel()); + + if (qualityPrice == null || basePrice == null) + return 0; + + float qualityFactor = qualityPrice.Factor; + float baseFactor = 0.0f; + + var inventoryType = proto.GetInventoryType(); + + if (inventoryType == InventoryType.Weapon || + inventoryType == InventoryType.Weapon2Hand || + inventoryType == InventoryType.WeaponMainhand || + inventoryType == InventoryType.WeaponOffhand || + inventoryType == InventoryType.Ranged || + inventoryType == InventoryType.Thrown || + inventoryType == InventoryType.RangedRight) + baseFactor = basePrice.WeaponFactor; + else + baseFactor = basePrice.ArmorFactor; + + if (inventoryType == InventoryType.Robe) + inventoryType = InventoryType.Chest; + + float typeFactor = 0.0f; + sbyte weapType = -1; + + switch (inventoryType) + { + case InventoryType.Head: + case InventoryType.Shoulders: + case InventoryType.Chest: + case InventoryType.Waist: + case InventoryType.Legs: + case InventoryType.Feet: + case InventoryType.Wrists: + case InventoryType.Hands: + case InventoryType.Cloak: + { + var armorPrice = CliDB.ImportPriceArmorStorage.LookupByKey(inventoryType); + if (armorPrice == null) + return 0; + + switch ((ItemSubClassArmor)proto.GetSubClass()) + { + case ItemSubClassArmor.Miscellaneous: + case ItemSubClassArmor.Cloth: + typeFactor = armorPrice.ClothFactor; + break; + case ItemSubClassArmor.Leather: + typeFactor = armorPrice.LeatherFactor; + break; + case ItemSubClassArmor.Mail: + typeFactor = armorPrice.MailFactor; + break; + case ItemSubClassArmor.Plate: + typeFactor = armorPrice.PlateFactor; + break; + default: + return 0; + } + + break; + } + case InventoryType.Shield: + { + var shieldPrice = CliDB.ImportPriceShieldStorage.LookupByKey(1); // it only has two rows, it's unclear which is the one used + if (shieldPrice == null) + return 0; + + typeFactor = shieldPrice.Factor; + break; + } + case InventoryType.WeaponMainhand: + weapType = 0; + break; + case InventoryType.WeaponOffhand: + weapType = 1; + break; + case InventoryType.Weapon: + weapType = 2; + break; + case InventoryType.Weapon2Hand: + weapType = 3; + break; + case InventoryType.Ranged: + case InventoryType.RangedRight: + case InventoryType.Relic: + weapType = 4; + break; + default: + return proto.GetBuyPrice(); + } + + if (weapType != -1) + { + var weaponPrice = CliDB.ImportPriceWeaponStorage.LookupByKey(weapType + 1); + if (weaponPrice == null) + return 0; + + typeFactor = weaponPrice.Factor; + } + + normalSellPrice = false; + return (uint)(qualityFactor * proto.GetUnk2() * proto.GetUnk1() * typeFactor * baseFactor); + } + } + + public static uint GetSpecialPrice(ItemTemplate proto, uint minimumPrice = 10000) + { + uint cost = 0; + + if (proto.GetFlags2().HasAnyFlag(ItemFlags2.OverrideGoldCost)) + cost = proto.GetSellPrice(); + else + { + bool normalPrice; + cost = GetSellPrice(proto, out normalPrice); + + if (!normalPrice) + { + if (proto.GetBuyCount() <= 1) + { + var classEntry = Global.DB2Mgr.GetItemClassByOldEnum(proto.GetClass()); + if (classEntry != null) + cost *= (uint)classEntry.PriceMod; + else + cost = 0; + } + else + cost /= 4 * proto.GetBuyCount(); + } + else + cost = proto.GetSellPrice(); + } + + if (cost < minimumPrice) + cost = minimumPrice; + + return cost; + } + + public int GetReforgableStat(ItemModType statType) + { + ItemTemplate proto = GetTemplate(); + for (uint i = 0; i < ItemConst.MaxStats; ++i) + if ((ItemModType)proto.GetItemStatType(i) == statType) + return proto.GetItemStatValue(i); + + int randomPropId = GetItemRandomPropertyId(); + if (randomPropId == 0) + return 0; + + if (randomPropId < 0) + { + ItemRandomSuffixRecord randomSuffix = CliDB.ItemRandomSuffixStorage.LookupByKey(-randomPropId); + if (randomSuffix == null) + return 0; + + for (var e = EnchantmentSlot.Prop0; e <= EnchantmentSlot.Prop4; ++e) + { + var enchant = CliDB.SpellItemEnchantmentStorage.LookupByKey(GetEnchantmentId(e)); + if (enchant != null) + for (uint f = 0; f < ItemConst.MaxItemEnchantmentEffects; ++f) + if (enchant.Effect[f] == ItemEnchantmentType.Stat && (ItemModType)enchant.EffectSpellID[f] == statType) + for (int k = 0; k < 5; ++k) + if (randomSuffix.Enchantment[k] == enchant.Id) + return (int)((randomSuffix.AllocationPct[k] * GetItemSuffixFactor()) / 10000); + } + } + else + { + var randomProp = CliDB.ItemRandomPropertiesStorage.LookupByKey(randomPropId); + if (randomProp == null) + return 0; + + for (var e = EnchantmentSlot.Prop0; e <= EnchantmentSlot.Prop4; ++e) + { + var enchant = CliDB.SpellItemEnchantmentStorage.LookupByKey(GetEnchantmentId(e)); + if (enchant != null) + for (uint f = 0; f < ItemConst.MaxItemEnchantmentEffects; ++f) + if (enchant.Effect[f] == ItemEnchantmentType.Stat && (ItemModType)enchant.EffectSpellID[f] == statType) + for (int k = 0; k < ItemConst.MaxItemRandomProperties; ++k) + if (randomProp.Enchantment[k] == enchant.Id) + return (int)(enchant.EffectPointsMin[k]); + } + } + + return 0; + } + + public void ItemContainerSaveLootToDB() + { + // Saves the money and item loot associated with an openable item to the DB + if (loot.isLooted()) // no money and no loot + return; + + SQLTransaction trans = new SQLTransaction(); + + loot.containerID = GetGUID(); // Save this for when a LootItem is removed + + // Save money + if (loot.gold > 0) + { + PreparedStatement stmt_money = DB.Characters.GetPreparedStatement(CharStatements.DEL_ITEMCONTAINER_MONEY); + stmt_money.AddValue(0, loot.containerID.GetCounter()); + trans.Append(stmt_money); + + stmt_money = DB.Characters.GetPreparedStatement(CharStatements.INS_ITEMCONTAINER_MONEY); + stmt_money.AddValue(0, loot.containerID.GetCounter()); + stmt_money.AddValue(1, loot.gold); + trans.Append(stmt_money); + } + + // Save items + if (!loot.isLooted()) + { + PreparedStatement stmt_items = DB.Characters.GetPreparedStatement(CharStatements.DEL_ITEMCONTAINER_ITEMS); + stmt_items.AddValue(0, loot.containerID.GetCounter()); + trans.Append(stmt_items); + + // Now insert the items + foreach (var _li in loot.items) + { + // When an item is looted, it doesn't get removed from the items collection + // but we don't want to resave it. + if (!_li.canSave) + continue; + + Player guid = GetOwner(); + if (!_li.AllowedForPlayer(guid)) + continue; + + stmt_items = DB.Characters.GetPreparedStatement(CharStatements.INS_ITEMCONTAINER_ITEMS); + + // container_id, item_id, item_count, follow_rules, ffa, blocked, counted, under_threshold, needs_quest, rnd_prop, rnd_suffix + stmt_items.AddValue(0, loot.containerID.GetCounter()); + stmt_items.AddValue(1, _li.itemid); + stmt_items.AddValue(2, _li.count); + stmt_items.AddValue(3, _li.follow_loot_rules); + stmt_items.AddValue(4, _li.freeforall); + stmt_items.AddValue(5, _li.is_blocked); + stmt_items.AddValue(6, _li.is_counted); + stmt_items.AddValue(7, _li.is_underthreshold); + stmt_items.AddValue(8, _li.needs_quest); + stmt_items.AddValue(9, (byte)_li.randomPropertyId.Type); + stmt_items.AddValue(10, _li.randomPropertyId.Id); + stmt_items.AddValue(11, _li.randomSuffix); + stmt_items.AddValue(12, _li.context); + + string bonusListIDs = ""; + foreach (int bonusListID in _li.BonusListIDs) + bonusListIDs += bonusListID + ' '; + + stmt_items.AddValue(13, bonusListIDs); + trans.Append(stmt_items); + } + } + DB.Characters.CommitTransaction(trans); + } + + public bool ItemContainerLoadLootFromDB() + { + // Loads the money and item loot associated with an openable item from the DB + // Default. If there are no records for this item then it will be rolled for in Player.SendLoot() + m_lootGenerated = false; + + // Save this for later use + loot.containerID = GetGUID(); + + // First, see if there was any money loot. This gets added directly to the container. + PreparedStatement stmt = DB.Characters.GetPreparedStatement(CharStatements.SEL_ITEMCONTAINER_MONEY); + stmt.AddValue(0, loot.containerID.GetCounter()); + SQLResult money_result = DB.Characters.Query(stmt); + + if (!money_result.IsEmpty()) + { + loot.gold = money_result.Read(0); + } + + // Next, load any items that were saved + stmt = DB.Characters.GetPreparedStatement(CharStatements.SEL_ITEMCONTAINER_ITEMS); + stmt.AddValue(0, loot.containerID.GetCounter()); + SQLResult item_result = DB.Characters.Query(stmt); + + if (!item_result.IsEmpty()) + { + // Get a LootTemplate for the container item. This is where + // the saved loot was originally rolled from, we will copy conditions from it + LootTemplate lt = LootStorage.Items.GetLootFor(GetEntry()); + if (lt != null) + { + do + { + // Create an empty LootItem + LootItem loot_item = new LootItem(); + + // item_id, itm_count, follow_rules, ffa, blocked, counted, under_threshold, needs_quest, rnd_prop, rnd_suffix + loot_item.itemid = item_result.Read(0); + loot_item.count = item_result.Read(1); + loot_item.follow_loot_rules = item_result.Read(2); + loot_item.freeforall = item_result.Read(3); + loot_item.is_blocked = item_result.Read(4); + loot_item.is_counted = item_result.Read(5); + loot_item.canSave = true; + loot_item.is_underthreshold = item_result.Read(6); + loot_item.needs_quest = item_result.Read(7); + loot_item.randomPropertyId = new ItemRandomEnchantmentId((ItemRandomEnchantmentType)item_result.Read(8), item_result.Read(9)); + loot_item.randomSuffix = item_result.Read(10); + loot_item.context = item_result.Read(11); + + StringArray bonusLists = new StringArray(item_result.Read(12), ' '); + foreach (string line in bonusLists) + { + loot_item.BonusListIDs.Add(uint.Parse(line)); + } + + // Copy the extra loot conditions from the item in the loot template + lt.CopyConditions(loot_item); + + // If container item is in a bag, add that player as an allowed looter + if (GetBagSlot() != 0) + loot_item.allowedGUIDs.Add(GetOwner().GetGUID()); + + // Finally add the LootItem to the container + loot.items.Add(loot_item); + + // Increment unlooted count + loot.unlootedCount++; + } + while (item_result.NextRow()); + } + } + + // Mark the item if it has loot so it won't be generated again on open + m_lootGenerated = !loot.isLooted(); + + return m_lootGenerated; + } + + void ItemContainerDeleteLootItemsFromDB() + { + // Deletes items associated with an openable item from the DB + PreparedStatement stmt = DB.Characters.GetPreparedStatement(CharStatements.DEL_ITEMCONTAINER_ITEMS); + stmt.AddValue(0, GetGUID().GetCounter()); + DB.Characters.Execute(stmt); + } + + void ItemContainerDeleteLootItemFromDB(uint itemID) + { + // Deletes a single item associated with an openable item from the DB + PreparedStatement stmt = DB.Characters.GetPreparedStatement(CharStatements.DEL_ITEMCONTAINER_ITEM); + stmt.AddValue(0, GetGUID().GetCounter()); + stmt.AddValue(1, itemID); + DB.Characters.Execute(stmt); + } + + void ItemContainerDeleteLootMoneyFromDB() + { + // Deletes the money loot associated with an openable item from the DB + PreparedStatement stmt = DB.Characters.GetPreparedStatement(CharStatements.DEL_ITEMCONTAINER_MONEY); + stmt.AddValue(0, GetGUID().GetCounter()); + DB.Characters.Execute(stmt); + } + + public void ItemContainerDeleteLootMoneyAndLootItemsFromDB() + { + // Deletes money and items associated with an openable item from the DB + ItemContainerDeleteLootMoneyFromDB(); + ItemContainerDeleteLootItemsFromDB(); + } + + public uint GetItemLevel(Player owner) + { + ItemTemplate stats = GetTemplate(); + if (stats == null) + return 1; + + uint itemLevel = stats.GetBaseItemLevel(); + if (_bonusData.HasItemLevelBonus || _bonusData.ItemLevelOverride == 0) + { + ScalingStatDistributionRecord ssd = CliDB.ScalingStatDistributionStorage.LookupByKey(GetScalingStatDistribution()); + if (ssd != null) + { + uint level = owner.getLevel(); + uint fixedLevel = GetModifier(ItemModifier.ScalingStatDistributionFixedLevel); + if (fixedLevel != 0) + level = fixedLevel; + uint heirloomIlvl = (uint)Global.DB2Mgr.GetCurveValueAt(ssd.ItemLevelCurveID, level); + if (heirloomIlvl != 0) + itemLevel = heirloomIlvl; + } + + itemLevel += (uint)_bonusData.ItemLevelBonus; + } + else + itemLevel = _bonusData.ItemLevelOverride; + + ItemUpgradeRecord upgrade = CliDB.ItemUpgradeStorage.LookupByKey(GetModifier(ItemModifier.UpgradeId)); + if (upgrade != null) + itemLevel += upgrade.ItemLevelBonus; + + for (uint i = 0; i < ItemConst.MaxGemSockets; ++i) + itemLevel += _bonusData.GemItemLevelBonus[i]; + + return Math.Min(Math.Max(itemLevel, 1), 1300); + } + + public int GetItemStatValue(uint index, Player owner) + { + Contract.Assert(index < ItemConst.MaxStats); + uint itemLevel = GetItemLevel(owner); + uint randomPropPoints = ItemEnchantment.GetRandomPropertyPoints(itemLevel, GetQuality(), GetTemplate().GetInventoryType(), GetTemplate().GetSubClass()); + if (randomPropPoints != 0) + { + float statValue = (_bonusData.ItemStatAllocation[index] * randomPropPoints) * 0.0001f; + GtItemSocketCostPerLevelRecord gtCost = CliDB.ItemSocketCostPerLevelGameTable.GetRow(itemLevel); + if (gtCost != null) + statValue -= (_bonusData.ItemStatSocketCostMultiplier[index] * gtCost.SocketCost); + + return (int)(Math.Floor(statValue + 0.5f)); + } + + return _bonusData.ItemStatValue[index]; + } + + public uint GetDisplayId(Player owner) + { + ItemModifier transmogModifier = ItemModifier.TransmogAppearanceAllSpecs; + if (HasFlag(ItemFields.ModifiersMask, ItemConst.AppearanceModifierMaskSpecSpecific)) + transmogModifier = ItemConst.AppearanceModifierSlotBySpec[owner.GetActiveTalentGroup()]; + + ItemModifiedAppearanceRecord transmog = CliDB.ItemModifiedAppearanceStorage.LookupByKey(GetModifier(transmogModifier)); + if (transmog != null) + { + ItemAppearanceRecord itemAppearance = CliDB.ItemAppearanceStorage.LookupByKey(transmog.AppearanceID); + if (itemAppearance != null) + return itemAppearance.DisplayID; + } + + return Global.DB2Mgr.GetItemDisplayId(GetEntry(), GetAppearanceModId()); + } + + public ItemModifiedAppearanceRecord GetItemModifiedAppearance() + { + return Global.DB2Mgr.GetItemModifiedAppearance(GetEntry(), _bonusData.AppearanceModID); + } + + public uint GetModifier(ItemModifier modifier) + { + return GetDynamicValue(ItemDynamicFields.Modifiers, (byte)modifier); + } + + public void SetModifier(ItemModifier modifier, uint value) + { + ApplyModFlag(ItemFields.ModifiersMask, 1 << (int)modifier, value != 0); + SetDynamicValue(ItemDynamicFields.Modifiers, (byte)modifier, value); + } + + public uint GetVisibleEntry(Player owner) + { + ItemModifier transmogModifier = ItemModifier.TransmogAppearanceAllSpecs; + if (HasFlag(ItemFields.ModifiersMask, ItemConst.AppearanceModifierMaskSpecSpecific)) + transmogModifier = ItemConst.AppearanceModifierSlotBySpec[owner.GetActiveTalentGroup()]; + + ItemModifiedAppearanceRecord transmog = CliDB.ItemModifiedAppearanceStorage.LookupByKey(GetModifier(transmogModifier)); + if (transmog != null) + return transmog.ItemID; + + return GetEntry(); + } + + public ushort GetVisibleAppearanceModId(Player owner) + { + ItemModifier transmogModifier = ItemModifier.TransmogAppearanceAllSpecs; + if (HasFlag(ItemFields.ModifiersMask, ItemConst.AppearanceModifierMaskSpecSpecific)) + transmogModifier = ItemConst.AppearanceModifierSlotBySpec[owner.GetActiveTalentGroup()]; + + ItemModifiedAppearanceRecord transmog = CliDB.ItemModifiedAppearanceStorage.LookupByKey(GetModifier(transmogModifier)); + if (transmog != null) + return transmog.AppearanceModID; + + return (ushort)GetAppearanceModId(); + } + + public uint GetVisibleEnchantmentId(Player owner) + { + ItemModifier illusionModifier = ItemModifier.TransmogAppearanceAllSpecs; + if (HasFlag(ItemFields.ModifiersMask, ItemConst.IllusionModifierMaskSpecSpecific)) + illusionModifier = ItemConst.IllusionModifierSlotBySpec[owner.GetActiveTalentGroup()]; + + uint enchantIllusion = GetModifier(illusionModifier); + if (enchantIllusion != 0) + return enchantIllusion; + + return GetEnchantmentId(EnchantmentSlot.Perm); + } + + public ushort GetVisibleItemVisual(Player owner) + { + SpellItemEnchantmentRecord enchant = CliDB.SpellItemEnchantmentStorage.LookupByKey(GetVisibleEnchantmentId(owner)); + if (enchant != null) + return enchant.ItemVisual; + + return 0; + } + + public void AddBonuses(uint bonusListID) + { + var bonuses = Global.DB2Mgr.GetItemBonusList(bonusListID); + if (bonuses != null) + { + AddDynamicValue(ItemDynamicFields.BonusListIds, bonusListID); + foreach (ItemBonusRecord bonus in bonuses) + _bonusData.AddBonus((ItemBonusType)bonus.Type, bonus.Value); + + SetUInt32Value(ItemFields.AppearanceModId, _bonusData.AppearanceModID); + } + } + + public List GetArtifactPowers() + { + return GetDynamicStructuredValues(ItemDynamicFields.ArtifactPowers); + } + + public ItemDynamicFieldArtifactPowers GetArtifactPower(uint artifactPowerId) + { + var index = m_artifactPowerIdToIndex.LookupByKey(artifactPowerId); + if (index != 0) + return GetDynamicStructuredValue(ItemDynamicFields.ArtifactPowers, index); + + return null; + } + + public void SetArtifactPower(ItemDynamicFieldArtifactPowers artifactPower, bool createIfMissing = false) + { + var foundIndex = m_artifactPowerIdToIndex.LookupByKey(artifactPower.ArtifactPowerId); + ushort index; + if (foundIndex != 0) + index = foundIndex; + else + { + if (!createIfMissing) + return; + + index = (ushort)m_artifactPowerIdToIndex.Count; + m_artifactPowerIdToIndex[artifactPower.ArtifactPowerId] = index; + } + + SetDynamicStructuredValue(ItemDynamicFields.ArtifactPowers, index, artifactPower); + } + + void InitArtifactPowers(byte artifactId, byte artifactTier) + { + foreach (ArtifactPowerRecord artifactPower in Global.DB2Mgr.GetArtifactPowers(artifactId)) + { + if (artifactPower.ArtifactTier != artifactTier) + continue; + + if (m_artifactPowerIdToIndex.ContainsKey(artifactPower.Id)) + continue; + + ItemDynamicFieldArtifactPowers powerData = new ItemDynamicFieldArtifactPowers(); + powerData.ArtifactPowerId = artifactPower.Id; + powerData.PurchasedRank = 0; + powerData.CurrentRankWithBonus = (byte)(artifactPower.Flags.HasAnyFlag(ArtifactPowerFlag.First) ? 1 : 0); + SetArtifactPower(powerData, true); + } + } + + public uint GetTotalPurchasedArtifactPowers() + { + uint purchasedRanks = 0; + foreach (ItemDynamicFieldArtifactPowers power in GetArtifactPowers()) + purchasedRanks += power.PurchasedRank; + + return purchasedRanks; + } + + void ApplyArtifactPowerEnchantmentBonuses(EnchantmentSlot slot, uint enchantId, bool apply, Player owner) + { + SpellItemEnchantmentRecord enchant = CliDB.SpellItemEnchantmentStorage.LookupByKey(enchantId); + if (enchant != null) + { + for (uint i = 0; i < ItemConst.MaxItemEnchantmentEffects; ++i) + { + switch (enchant.Effect[i]) + { + case ItemEnchantmentType.ArtifactPowerBonusRankByType: + { + foreach (ItemDynamicFieldArtifactPowers artifactPower in GetArtifactPowers()) + { + if (CliDB.ArtifactPowerStorage.LookupByKey(artifactPower.ArtifactPowerId).RelicType == enchant.EffectSpellID[i]) + { + ItemDynamicFieldArtifactPowers newPower = artifactPower; + if (apply) + newPower.CurrentRankWithBonus += (byte)enchant.EffectPointsMin[i]; + else + newPower.CurrentRankWithBonus -= (byte)enchant.EffectPointsMin[i]; + + if (IsEquipped()) + { + ArtifactPowerRankRecord artifactPowerRank = Global.DB2Mgr.GetArtifactPowerRank(artifactPower.ArtifactPowerId, (byte)(newPower.CurrentRankWithBonus != 0 ? newPower.CurrentRankWithBonus - 1 : 0)); + if (artifactPowerRank != null) + owner.ApplyArtifactPowerRank(this, artifactPowerRank, newPower.CurrentRankWithBonus != 0); + } + + SetArtifactPower(newPower); + } + } + } + break; + case ItemEnchantmentType.ArtifactPowerBonusRankByID: + { + ItemDynamicFieldArtifactPowers artifactPower = GetArtifactPower(enchant.EffectSpellID[i]); + if (artifactPower != null) + { + ItemDynamicFieldArtifactPowers newPower = artifactPower; + if (apply) + newPower.CurrentRankWithBonus += (byte)enchant.EffectPointsMin[i]; + else + newPower.CurrentRankWithBonus -= (byte)enchant.EffectPointsMin[i]; + + if (IsEquipped()) + { + ArtifactPowerRankRecord artifactPowerRank = Global.DB2Mgr.GetArtifactPowerRank(artifactPower.ArtifactPowerId, (byte)(newPower.CurrentRankWithBonus != 0 ? newPower.CurrentRankWithBonus - 1 : 0)); + if (artifactPowerRank != null) + owner.ApplyArtifactPowerRank(this, artifactPowerRank, newPower.CurrentRankWithBonus != 0); + } + + SetArtifactPower(newPower); + } + } + break; + case ItemEnchantmentType.ArtifactPowerBonusRankPicker: + if (slot >= EnchantmentSlot.Sock1 && slot <= EnchantmentSlot.Sock3 && _bonusData.GemRelicType[slot - EnchantmentSlot.Sock1] != -1) + { + ArtifactPowerPickerRecord artifactPowerPicker = CliDB.ArtifactPowerPickerStorage.LookupByKey(enchant.EffectSpellID[i]); + if (artifactPowerPicker != null) + { + PlayerConditionRecord playerCondition = CliDB.PlayerConditionStorage.LookupByKey(artifactPowerPicker.PlayerConditionID); + if (playerCondition == null || ConditionManager.IsPlayerMeetingCondition(owner, playerCondition)) + { + foreach (ItemDynamicFieldArtifactPowers artifactPower in GetArtifactPowers()) + { + if (CliDB.ArtifactPowerStorage.LookupByKey(artifactPower.ArtifactPowerId).RelicType == _bonusData.GemRelicType[slot - EnchantmentSlot.Sock1]) + { + ItemDynamicFieldArtifactPowers newPower = artifactPower; + if (apply) + newPower.CurrentRankWithBonus += (byte)enchant.EffectPointsMin[i]; + else + newPower.CurrentRankWithBonus -= (byte)enchant.EffectPointsMin[i]; + + if (IsEquipped()) + { + ArtifactPowerRankRecord artifactPowerRank = Global.DB2Mgr.GetArtifactPowerRank(artifactPower.ArtifactPowerId, (byte)(newPower.CurrentRankWithBonus != 0 ? newPower.CurrentRankWithBonus - 1 : 0)); + if (artifactPowerRank != null) + owner.ApplyArtifactPowerRank(this, artifactPowerRank, newPower.CurrentRankWithBonus != 0); + } + + SetArtifactPower(newPower); + } + } + } + } + } + break; + default: + break; + } + } + } + } + + public void CopyArtifactDataFromParent(Item parent) + { + Array.Copy(parent.GetBonus().GemItemLevelBonus, _bonusData.GemItemLevelBonus, _bonusData.GemItemLevelBonus.Length); + SetModifier(ItemModifier.ArtifactAppearanceId, parent.GetModifier(ItemModifier.ArtifactAppearanceId)); + SetAppearanceModId(parent.GetAppearanceModId()); + } + + public void GiveArtifactXp(ulong amount, Item sourceItem, uint artifactCategoryId) + { + Player owner = GetOwner(); + if (!owner) + return; + + if (artifactCategoryId != 0) + { + ArtifactCategoryRecord artifactCategory = CliDB.ArtifactCategoryStorage.LookupByKey(artifactCategoryId); + if (artifactCategory != null) + { + uint artifactKnowledgeLevel = 1; + if (sourceItem && sourceItem.GetModifier(ItemModifier.ArtifactKnowledgeLevel) != 0) + artifactKnowledgeLevel = sourceItem.GetModifier(ItemModifier.ArtifactKnowledgeLevel); + else + artifactKnowledgeLevel = owner.GetCurrency(artifactCategory.ArtifactKnowledgeCurrencyID) + 1; + + GtArtifactKnowledgeMultiplierRecord artifactKnowledge = CliDB.ArtifactKnowledgeMultiplierGameTable.GetRow(artifactKnowledgeLevel); + if (artifactKnowledge != null) + amount = (ulong)(amount * artifactKnowledge.Multiplier); + + if (amount >= 5000) + amount = 50 * (amount / 50); + else if (amount >= 1000) + amount = 25 * (amount / 25); + else if (amount >= 50) + amount = 5 * (amount / 5); + } + } + + SetUInt64Value(ItemFields.ArtifactXp, GetUInt64Value(ItemFields.ArtifactXp) + amount); + + ArtifactXpGain artifactXpGain = new ArtifactXpGain(); + artifactXpGain.ArtifactGUID = GetGUID(); + artifactXpGain.Amount = amount; + owner.SendPacket(artifactXpGain); + + SetState(ItemUpdateState.Changed, owner); + } + + public static void AddItemsSetItem(Player player, Item item) + { + ItemTemplate proto = item.GetTemplate(); + uint setid = proto.GetItemSet(); + + ItemSetRecord set = CliDB.ItemSetStorage.LookupByKey(setid); + if (set == null) + { + Log.outError(LogFilter.Sql, "Item set {0} for item (id {1}) not found, mods not applied.", setid, proto.GetId()); + return; + } + + if (set.RequiredSkill != 0 && player.GetSkillValue((SkillType)set.RequiredSkill) < set.RequiredSkillRank) + return; + + if (set.Flags.HasAnyFlag(ItemSetFlags.LegacyInactive)) + return; + + ItemSetEffect eff = null; + for (int x = 0; x < player.ItemSetEff.Count; ++x) + { + if (player.ItemSetEff[x]?.ItemSetID == setid) + { + eff = player.ItemSetEff[x]; + break; + } + } + + if (eff == null) + { + eff = new ItemSetEffect(); + eff.ItemSetID = setid; + + int x = 0; + for (; x < player.ItemSetEff.Count; ++x) + if (player.ItemSetEff[x] == null) + break; + + if (x < player.ItemSetEff.Count) + player.ItemSetEff[x] = eff; + else + player.ItemSetEff.Add(eff); + } + + ++eff.EquippedItemCount; + + List itemSetSpells = Global.DB2Mgr.GetItemSetSpells(setid); + foreach (var itemSetSpell in itemSetSpells) + { + //not enough for spell + if (itemSetSpell.Threshold > eff.EquippedItemCount) + continue; + + if (eff.SetBonuses.Contains(itemSetSpell)) + continue; + + SpellInfo spellInfo = Global.SpellMgr.GetSpellInfo(itemSetSpell.SpellID); + if (spellInfo == null) + { + Log.outError(LogFilter.Player, "WORLD: unknown spell id {0} in items set {1} effects", itemSetSpell.SpellID, setid); + continue; + } + + eff.SetBonuses.Add(itemSetSpell); + // spell cast only if fit form requirement, in other case will cast at form change + if (itemSetSpell.ChrSpecID == 0 || itemSetSpell.ChrSpecID == player.GetUInt32Value(PlayerFields.CurrentSpecId)) + player.ApplyEquipSpell(spellInfo, null, true); + } + } + + public static void RemoveItemsSetItem(Player player, ItemTemplate proto) + { + uint setid = proto.GetItemSet(); + + ItemSetRecord set = CliDB.ItemSetStorage.LookupByKey(setid); + if (set == null) + { + Log.outError(LogFilter.Sql, "Item set {0} for item {1} not found, mods not removed.", setid, proto.GetId()); + return; + } + + ItemSetEffect eff = null; + int setindex = 0; + for (; setindex < player.ItemSetEff.Count; setindex++) + { + if (player.ItemSetEff[setindex] != null && player.ItemSetEff[setindex].ItemSetID == setid) + { + eff = player.ItemSetEff[setindex]; + break; + } + } + + // can be in case now enough skill requirement for set appling but set has been appliend when skill requirement not enough + if (eff == null) + return; + + --eff.EquippedItemCount; + + List itemSetSpells = Global.DB2Mgr.GetItemSetSpells(setid); + foreach (ItemSetSpellRecord itemSetSpell in itemSetSpells) + { + // enough for spell + if (itemSetSpell.Threshold <= eff.EquippedItemCount) + continue; + + if (!eff.SetBonuses.Contains(itemSetSpell)) + continue; + + player.ApplyEquipSpell(Global.SpellMgr.GetSpellInfo(itemSetSpell.SpellID), null, false); + eff.SetBonuses.Remove(itemSetSpell); + } + + if (eff.EquippedItemCount == 0) //all items of a set were removed + { + Contract.Assert(eff == player.ItemSetEff[setindex]); + player.ItemSetEff[setindex] = null; + } + } + + public BonusData GetBonus() { return _bonusData; } + + public ObjectGuid GetOwnerGUID() { return GetGuidValue(ItemFields.Owner); } + public void SetOwnerGUID(ObjectGuid guid) { SetGuidValue(ItemFields.Owner, guid); } + + public ItemBondingType GetBonding() { return _bonusData.Bonding; } + public void SetBinding(bool val) { ApplyModFlag(ItemFields.Flags, (uint)ItemFieldFlags.Soulbound, val); } + public bool IsSoulBound() { return HasFlag(ItemFields.Flags, ItemFieldFlags.Soulbound); } + public bool IsBoundAccountWide() { return GetTemplate().GetFlags().HasAnyFlag(ItemFlags.IsBoundToAccount); } + bool IsBattlenetAccountBound() { return GetTemplate().GetFlags2().HasAnyFlag(ItemFlags2.BnetAccountTradeOk); } + + public Bag ToBag() + { + if (IsBag()) + return (this as Bag); + else + return null; + } + + public bool IsLocked() { return !HasFlag(ItemFields.Flags, ItemFieldFlags.Unlocked); } + public bool IsBag() { return GetTemplate().GetInventoryType() == InventoryType.Bag; } + public bool IsCurrencyToken() { return GetTemplate().IsCurrencyToken(); } + public bool IsBroken() { return GetUInt32Value(ItemFields.MaxDurability) > 0 && GetUInt32Value(ItemFields.Durability) == 0; } + public void SetInTrade(bool b = true) { mb_in_trade = b; } + public bool IsInTrade() { return mb_in_trade; } + + public uint GetCount() { return GetUInt32Value(ItemFields.StackCount); } + public uint GetMaxStackCount() { return GetTemplate().GetMaxStackSize(); } + + public byte GetSlot() { return m_slot; } + public Bag GetContainer() { return m_container; } + public void SetSlot(byte slot) { m_slot = slot; } + public ushort GetPos() { return (ushort)(GetBagSlot() << 8 | GetSlot()); } + public void SetContainer(Bag container) { m_container = container; } + + bool IsInBag() { return m_container != null; } + + public int GetItemRandomPropertyId() { return GetInt32Value(ItemFields.RandomPropertiesId); } + public uint GetItemSuffixFactor() { return GetUInt32Value(ItemFields.PropertySeed); } + public ItemRandomEnchantmentId GetItemRandomEnchantmentId() { return m_randomEnchantment; } + public uint GetEnchantmentId(EnchantmentSlot slot) + { + return GetUInt32Value(ItemFields.Enchantment + (int)slot * (int)EnchantmentOffset.Max + (int)EnchantmentOffset.Id); + } + public uint GetEnchantmentDuration(EnchantmentSlot slot) + { + return GetUInt32Value(ItemFields.Enchantment + (int)slot * (int)EnchantmentOffset.Max + (int)EnchantmentOffset.Duration); + } + public uint GetEnchantmentCharges(EnchantmentSlot slot) + { + return GetUInt32Value(ItemFields.Enchantment + (int)slot * (int)EnchantmentOffset.Max + (int)EnchantmentOffset.Charges); + } + + public string GetText() { return m_text; } + public void SetText(string text) { m_text = text; } + + public int GetSpellCharges(int index = 0) { return GetInt32Value(ItemFields.SpellCharges + index); } + public void SetSpellCharges(int index, int value) { SetInt32Value(ItemFields.SpellCharges + index, value); } + + public ItemUpdateState GetState() { return uState; } + + public bool IsInUpdateQueue() { return uQueuePos != -1; } + public int GetQueuePos() { return uQueuePos; } + public void FSetState(ItemUpdateState state)// forced + { + uState = state; + } + + public override bool hasQuest(uint quest_id) { return GetTemplate().GetStartQuest() == quest_id; } + public override bool hasInvolvedQuest(uint quest_id) { return false; } + public bool IsPotion() { return GetTemplate().IsPotion(); } + public bool IsVellum() { return GetTemplate().IsVellum(); } + public bool IsConjuredConsumable() { return GetTemplate().IsConjuredConsumable(); } + public bool IsRangedWeapon() { return GetTemplate().IsRangedWeapon(); } + public ItemQuality GetQuality() { return _bonusData.Quality; } + public int GetRequiredLevel() { return _bonusData.RequiredLevel; } + public int GetItemStatType(uint index) + { + Contract.Assert(index < ItemConst.MaxStats); + return _bonusData.ItemStatType[index]; + } + public SocketColor GetSocketColor(uint index) + { + Contract.Assert(index < ItemConst.MaxGemSockets); + return _bonusData.socketColor[index]; + } + public uint GetAppearanceModId() { return GetUInt32Value(ItemFields.AppearanceModId); } + public void SetAppearanceModId(uint appearanceModId) { SetUInt32Value(ItemFields.AppearanceModId, appearanceModId); } + public uint GetArmor(Player owner) { return GetTemplate().GetArmor(GetItemLevel(owner)); } + public void GetDamage(Player owner, out float minDamage, out float maxDamage) { GetTemplate().GetDamage(GetItemLevel(owner), out minDamage, out maxDamage); } + public float GetRepairCostMultiplier() { return _bonusData.RepairCostMultiplier; } + public uint GetScalingStatDistribution() { return _bonusData.ScalingStatDistribution; } + + public void SetRefundRecipient(ObjectGuid guid) { m_refundRecipient = guid; } + public void SetPaidMoney(uint money) { m_paidMoney = money; } + public void SetPaidExtendedCost(uint iece) { m_paidExtendedCost = iece; } + + public ObjectGuid GetRefundRecipient() { return m_refundRecipient; } + public uint GetPaidMoney() { return m_paidMoney; } + public uint GetPaidExtendedCost() { return m_paidExtendedCost; } + + public uint GetScriptId() { return GetTemplate().ScriptId; } + + public uint GetSpecialPrice(uint minimumPrice = 10000) { return GetSpecialPrice(GetTemplate(), minimumPrice); } + + public ObjectGuid GetChildItem() { return m_childItem; } + public void SetChildItem(ObjectGuid childItem) { m_childItem = childItem; } + + //Static + public static bool ItemCanGoIntoBag(ItemTemplate pProto, ItemTemplate pBagProto) + { + if (pProto == null || pBagProto == null) + return false; + + switch (pBagProto.GetClass()) + { + case ItemClass.Container: + switch ((ItemSubClassContainer)pBagProto.GetSubClass()) + { + case ItemSubClassContainer.Container: + return true; + case ItemSubClassContainer.SoulContainer: + if (!Convert.ToBoolean(pProto.GetBagFamily() & BagFamilyMask.SoulShards)) + return false; + return true; + case ItemSubClassContainer.HerbContainer: + if (!Convert.ToBoolean(pProto.GetBagFamily() & BagFamilyMask.Herbs)) + return false; + return true; + case ItemSubClassContainer.EnchantingContainer: + if (!Convert.ToBoolean(pProto.GetBagFamily() & BagFamilyMask.EnchantingSupp)) + return false; + return true; + case ItemSubClassContainer.MiningContainer: + if (!Convert.ToBoolean(pProto.GetBagFamily() & BagFamilyMask.MiningSupp)) + return false; + return true; + case ItemSubClassContainer.EngineeringContainer: + if (!Convert.ToBoolean(pProto.GetBagFamily() & BagFamilyMask.EngineeringSupp)) + return false; + return true; + case ItemSubClassContainer.GemContainer: + if (!Convert.ToBoolean(pProto.GetBagFamily() & BagFamilyMask.Gems)) + return false; + return true; + case ItemSubClassContainer.LeatherworkingContainer: + if (!Convert.ToBoolean(pProto.GetBagFamily() & BagFamilyMask.LeatherworkingSupp)) + return false; + return true; + case ItemSubClassContainer.InscriptionContainer: + if (!Convert.ToBoolean(pProto.GetBagFamily() & BagFamilyMask.InscriptionSupp)) + return false; + return true; + case ItemSubClassContainer.TackleContainer: + if (!Convert.ToBoolean(pProto.GetBagFamily() & BagFamilyMask.FishingSupp)) + return false; + return true; + case ItemSubClassContainer.CookingContainer: + if (!pProto.GetBagFamily().HasAnyFlag(BagFamilyMask.CookingSupp)) + return false; + return true; + default: + return false; + } + //can remove? + case ItemClass.Quiver: + switch ((ItemSubClassQuiver)pBagProto.GetSubClass()) + { + case ItemSubClassQuiver.Quiver: + if (!Convert.ToBoolean(pProto.GetBagFamily() & BagFamilyMask.Arrows)) + return false; + return true; + case ItemSubClassQuiver.AmmoPouch: + if (!Convert.ToBoolean(pProto.GetBagFamily() & BagFamilyMask.Bullets)) + return false; + return true; + default: + return false; + } + } + return false; + } + + public static uint ItemSubClassToDurabilityMultiplierId(ItemClass ItemClass, uint ItemSubClass) + { + switch (ItemClass) + { + case ItemClass.Weapon: return ItemSubClass; + case ItemClass.Armor: return ItemSubClass + 21; + } + return 0; + } + + #region Fields + public bool m_lootGenerated; + public Loot loot; + internal BonusData _bonusData; + + ItemUpdateState uState; + uint m_paidExtendedCost; + uint m_paidMoney; + ObjectGuid m_refundRecipient; + byte m_slot; + Bag m_container; + int uQueuePos; + string m_text; + bool mb_in_trade; + long m_lastPlayedTimeUpdate; + List allowedGUIDs = new List(); + ItemRandomEnchantmentId m_randomEnchantment; // store separately to easily find which bonus list is the one randomly given for stat rerolling + ObjectGuid m_childItem; + Dictionary m_artifactPowerIdToIndex = new Dictionary(); + Array m_gemScalingLevels = new Array(ItemConst.MaxGemSockets); + #endregion + } + + public class ItemPosCount + { + public ItemPosCount(ushort _pos, uint _count) + { + pos = _pos; + count = _count; + } + + public bool isContainedIn(List vec) + { + foreach (var posCount in vec) + if (posCount.pos == pos) + return true; + return false; + } + + public ushort pos; + public uint count; + } + + public enum EnchantmentOffset + { + Id = 0, + Duration = 1, + Charges = 2, // now here not only charges, but something new in wotlk + Max = 3 + } + + public class ItemSetEffect + { + public uint ItemSetID; + public uint EquippedItemCount; + public List SetBonuses = new List(); + } + + public class BonusData + { + public BonusData(ItemTemplate proto) + { + if (proto == null) + return; + + Quality = proto.GetQuality(); + ItemLevelBonus = 0; + RequiredLevel = proto.GetBaseRequiredLevel(); + for (uint i = 0; i < ItemConst.MaxStats; ++i) + ItemStatType[i] = proto.GetItemStatType(i); + + for (uint i = 0; i < ItemConst.MaxStats; ++i) + ItemStatValue[i] = proto.GetItemStatValue(i); + + for (uint i = 0; i < ItemConst.MaxStats; ++i) + ItemStatAllocation[i] = proto.GetItemStatAllocation(i); + + for (uint i = 0; i < ItemConst.MaxStats; ++i) + ItemStatSocketCostMultiplier[i] = proto.GetItemStatSocketCostMultiplier(i); + + for (uint i = 0; i < ItemConst.MaxGemSockets; ++i) + { + socketColor[i] = proto.GetSocketColor(i); + GemItemLevelBonus[i] = 0; + GemRelicType[i] = -1; + GemRelicRankBonus[i] = 0; + } + + Bonding = proto.GetBonding(); + + AppearanceModID = 0; + RepairCostMultiplier = 1.0f; + ScalingStatDistribution = proto.GetScalingStatDistribution(); + ItemLevelOverride = 0; + RelicType = -1; + HasItemLevelBonus = false; + + _state.AppearanceModPriority = int.MaxValue; + _state.ScalingStatDistributionPriority = int.MaxValue; + _state.ItemLevelOverridePriority = int.MaxValue; + _state.HasQualityBonus = false; + } + + public BonusData(ItemInstance itemInstance) : this(Global.ObjectMgr.GetItemTemplate(itemInstance.ItemID)) + { + if (itemInstance.ItemBonus.HasValue) + { + foreach (uint bonusListID in itemInstance.ItemBonus.Value.BonusListIDs) + { + var bonuses = Global.DB2Mgr.GetItemBonusList(bonusListID); + if (bonuses != null) + { + foreach (ItemBonusRecord bonus in bonuses) + AddBonus(bonus.Type, bonus.Value); + } + } + } + } + + public void AddBonus(ItemBonusType type, int[] values) + { + switch (type) + { + case ItemBonusType.ItemLevel: + ItemLevelBonus += values[0]; + HasItemLevelBonus = true; + break; + case ItemBonusType.Stat: + { + uint statIndex = 0; + for (statIndex = 0; statIndex < ItemConst.MaxStats; ++statIndex) + if (ItemStatType[statIndex] == values[0] || ItemStatType[statIndex] == -1) + break; + + if (statIndex < ItemConst.MaxStats) + { + ItemStatType[statIndex] = values[0]; + ItemStatAllocation[statIndex] += values[1]; + } + break; + } + case ItemBonusType.Quality: + if (!_state.HasQualityBonus) + { + Quality = (ItemQuality)values[0]; + _state.HasQualityBonus = true; + } + else if ((uint)Quality < values[0]) + Quality = (ItemQuality)values[0]; + break; + case ItemBonusType.Socket: + { + uint socketCount = (uint)values[0]; + for (uint i = 0; i < ItemConst.MaxGemSockets && socketCount != 0; ++i) + { + if (socketColor[i] == 0) + { + socketColor[i] = (SocketColor)values[1]; + --socketCount; + } + } + break; + } + case ItemBonusType.Appearance: + if (values[1] < _state.AppearanceModPriority) + { + AppearanceModID = Convert.ToUInt32(values[0]); + _state.AppearanceModPriority = values[1]; + } + break; + case ItemBonusType.RequiredLevel: + RequiredLevel += values[0]; + break; + case ItemBonusType.RepairCostMuliplier: + RepairCostMultiplier *= Convert.ToSingle(values[0]) * 0.01f; + break; + case ItemBonusType.ScalingStatDistribution: + case ItemBonusType.ScalingStatDistribution2: + if (values[1] < _state.ScalingStatDistributionPriority) + { + ScalingStatDistribution = (uint)values[0]; + _state.ScalingStatDistributionPriority = values[1]; + } + break; + case ItemBonusType.ItemLevelOverride: + if (values[1] < _state.ItemLevelOverridePriority) + { + ItemLevelOverride = (uint)values[0]; + _state.ItemLevelOverridePriority = values[1]; + } + break; + case ItemBonusType.Bounding: + Bonding = (ItemBondingType)values[0]; + break; + case ItemBonusType.RelicType: + RelicType = values[0]; + break; + } + } + + public ItemQuality Quality; + public int ItemLevelBonus; + public int RequiredLevel; + public int[] ItemStatType = new int[ItemConst.MaxStats]; + public int[] ItemStatValue = new int[ItemConst.MaxStats]; + public int[] ItemStatAllocation = new int[ItemConst.MaxStats]; + public float[] ItemStatSocketCostMultiplier = new float[ItemConst.MaxStats]; + public SocketColor[] socketColor = new SocketColor[ItemConst.MaxGemSockets]; + public ItemBondingType Bonding; + public uint AppearanceModID; + public float RepairCostMultiplier; + public uint ScalingStatDistribution; + public uint ItemLevelOverride; + public uint[] GemItemLevelBonus = new uint[ItemConst.MaxGemSockets]; + public int[] GemRelicType = new int[ItemConst.MaxGemSockets]; + public ushort[] GemRelicRankBonus = new ushort[ItemConst.MaxGemSockets]; + public int RelicType; + public bool HasItemLevelBonus; + State _state; + + struct State + { + public int AppearanceModPriority; + public int ScalingStatDistributionPriority; + public int ItemLevelOverridePriority; + public bool HasQualityBonus; + } + } + + [StructLayout(LayoutKind.Sequential)] + public class ItemDynamicFieldArtifactPowers + { + public uint ArtifactPowerId; + public byte PurchasedRank; + public byte CurrentRankWithBonus; + public ushort Padding; + } + + [StructLayout(LayoutKind.Sequential)] + public class ItemDynamicFieldGems + { + public uint ItemId; + [MarshalAs(UnmanagedType.ByValArray, SizeConst = 16)] + public ushort[] BonusListIDs = new ushort[16]; + public byte Context; + [MarshalAs(UnmanagedType.ByValArray, SizeConst = 3)] + public byte[] Padding = new byte[3]; + } +} diff --git a/Game/Entities/Item/ItemEnchantment.cs b/Game/Entities/Item/ItemEnchantment.cs new file mode 100644 index 000000000..3aa5a7cef --- /dev/null +++ b/Game/Entities/Item/ItemEnchantment.cs @@ -0,0 +1,293 @@ +/* + * Copyright (C) 2012-2017 CypherCore + * + * 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, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +using Framework.Constants; +using Framework.Database; +using Game.DataStorage; +using System.Collections.Generic; + +namespace Game.Entities +{ + public class ItemEnchantment + { + static ItemEnchantment() + { + RandomItemEnch = new EnchantmentStore(); + } + + public static void LoadRandomEnchantmentsTable() + { + // for reload case + RandomItemEnch[ItemRandomEnchantmentType.Property].Clear(); + RandomItemEnch[ItemRandomEnchantmentType.Suffix].Clear(); + + // 0 1 2 3 + SQLResult result = DB.World.Query("SELECT entry, type, ench, chance FROM item_enchantment_template"); + + if (result.IsEmpty()) + { + Log.outError(LogFilter.Player, "Loaded 0 Item Enchantment definitions. DB table `item_enchantment_template` is empty."); + } + uint count = 0; + + do + { + uint entry = result.Read(0); + ItemRandomEnchantmentType type = (ItemRandomEnchantmentType)result.Read(1); + uint ench = result.Read(2); + float chance = result.Read(3); + + switch (type) + { + case ItemRandomEnchantmentType.Property: + if (!CliDB.ItemRandomPropertiesStorage.ContainsKey(ench)) + { + Log.outError(LogFilter.Sql, "Property {0} used in `item_enchantment_template` by entry {1} doesn't have exist in ItemRandomProperties.db2", ench, entry); + continue; + } + break; + case ItemRandomEnchantmentType.Suffix: + if (!CliDB.ItemRandomSuffixStorage.ContainsKey(ench)) + { + Log.outError(LogFilter.Sql, "Suffix {0} used in `item_enchantment_template` by entry {1} doesn't have exist in ItemRandomSuffix.db2", ench, entry); + continue; + } + break; + case ItemRandomEnchantmentType.BonusList: + if (Global.DB2Mgr.GetItemBonusList(ench) == null) + { + Log.outError(LogFilter.Sql, "Bonus list {0} used in `item_enchantment_template` by entry {1} doesn't have exist in ItemBonus.db2", ench, entry); + continue; + } + break; + default: + Log.outError(LogFilter.Sql, "Invalid random enchantment type specified in `item_enchantment_template` table for `entry` {0} `ench` {1}", entry, ench); + break; + } + + if (chance < 0.000001f || chance > 100.0f) + { + Log.outError(LogFilter.Sql, "Random item enchantment for entry {0} type {1} ench {2} has invalid chance {3}", entry, type, ench, chance); + continue; + } + + switch (type) + { + case ItemRandomEnchantmentType.Property: + RandomItemEnch[ItemRandomEnchantmentType.Property].Add(entry, new EnchStoreItem(type, ench, chance)); + break; + case ItemRandomEnchantmentType.Suffix: + case ItemRandomEnchantmentType.BonusList: // random bonus lists use RandomSuffix field in Item-sparse.db2 + RandomItemEnch[ItemRandomEnchantmentType.Suffix].Add(entry, new EnchStoreItem(type, ench, chance)); + break; + default: + break; + } + + ++count; + } while (result.NextRow()); + + Log.outInfo(LogFilter.Player, "Loaded {0} Item Enchantment definitions", count); + } + + public static ItemRandomEnchantmentId GetItemEnchantMod(int entry, ItemRandomEnchantmentType type) + { + if (entry == 0) + return ItemRandomEnchantmentId.Empty; + + if (entry == -1) + return ItemRandomEnchantmentId.Empty; + + var tab = RandomItemEnch[type].LookupByKey(entry); + if (tab == null) + { + Log.outError(LogFilter.Player, "Item RandomProperty / RandomSuffix id #{0} used in `item_template` but it does not have records in `item_enchantment_template` table.", entry); + return ItemRandomEnchantmentId.Empty; + } + + var selectedItem = tab.SelectRandomElementByWeight(enchant => enchant.chance); + + return new ItemRandomEnchantmentId(selectedItem.type, selectedItem.ench); + } + + public static ItemRandomEnchantmentId GenerateItemRandomPropertyId(uint item_id) + { + ItemTemplate itemProto = Global.ObjectMgr.GetItemTemplate(item_id); + if (itemProto == null) + return ItemRandomEnchantmentId.Empty; + + // item must have one from this field values not null if it can have random enchantments + if (itemProto.GetRandomProperty() == 0 && itemProto.GetRandomSuffix() == 0) + return ItemRandomEnchantmentId.Empty; + + // item can have not null only one from field values + if (itemProto.GetRandomProperty() != 0 && itemProto.GetRandomSuffix() != 0) + { + Log.outError(LogFilter.Sql, "Item template {0} have RandomProperty == {1} and RandomSuffix == {2}, but must have one from field =0", itemProto.GetId(), itemProto.GetRandomProperty(), itemProto.GetRandomSuffix()); + return ItemRandomEnchantmentId.Empty; + } + + // RandomProperty case + if (itemProto.GetRandomProperty() != 0) + return GetItemEnchantMod((int)itemProto.GetRandomProperty(), ItemRandomEnchantmentType.Property); + // RandomSuffix case + else + return GetItemEnchantMod((int)itemProto.GetRandomSuffix(), ItemRandomEnchantmentType.Suffix); + } + + public static uint GenerateEnchSuffixFactor(uint item_id) + { + ItemTemplate itemProto = Global.ObjectMgr.GetItemTemplate(item_id); + + if (itemProto == null) + return 0; + if (itemProto.GetRandomSuffix() == 0) + return 0; + + return GetRandomPropertyPoints(itemProto.GetBaseItemLevel(), itemProto.GetQuality(), itemProto.GetInventoryType(), itemProto.GetSubClass()); + } + + public static uint GetRandomPropertyPoints(uint itemLevel, ItemQuality quality, InventoryType inventoryType, uint subClass) + { + uint propIndex; + switch (inventoryType) + { + case InventoryType.Head: + case InventoryType.Body: + case InventoryType.Chest: + case InventoryType.Legs: + case InventoryType.Ranged: + case InventoryType.Weapon2Hand: + case InventoryType.Robe: + case InventoryType.Thrown: + propIndex = 0; + break; + case InventoryType.RangedRight: + if ((ItemSubClassWeapon)subClass == ItemSubClassWeapon.Wand) + propIndex = 3; + else + propIndex = 0; + break; + case InventoryType.Weapon: + case InventoryType.WeaponMainhand: + case InventoryType.WeaponOffhand: + propIndex = 3; + break; + case InventoryType.Shoulders: + case InventoryType.Waist: + case InventoryType.Feet: + case InventoryType.Hands: + case InventoryType.Trinket: + propIndex = 1; + break; + case InventoryType.Neck: + case InventoryType.Wrists: + case InventoryType.Finger: + case InventoryType.Shield: + case InventoryType.Cloak: + case InventoryType.Holdable: + propIndex = 2; + break; + case InventoryType.Relic: + propIndex = 4; + break; + default: + return 0; + } + RandPropPointsRecord randPropPointsEntry = CliDB.RandPropPointsStorage.LookupByKey(itemLevel); + if (randPropPointsEntry == null) + return 0; + + // Select rare/epic modifier + switch (quality) + { + case ItemQuality.Uncommon: + return randPropPointsEntry.UncommonPropertiesPoints[propIndex]; + case ItemQuality.Rare: + case ItemQuality.Heirloom: + return randPropPointsEntry.RarePropertiesPoints[propIndex]; + case ItemQuality.Epic: + case ItemQuality.Legendary: + case ItemQuality.Artifact: + return randPropPointsEntry.EpicPropertiesPoints[propIndex]; + } + return 0; + } + + static EnchantmentStore RandomItemEnch; + + public class EnchStoreItem + { + public EnchStoreItem() + { + ench = 0; + chance = 0; + } + public EnchStoreItem(ItemRandomEnchantmentType _type, uint _ench, float _chance) + { + type = _type; + ench = _ench; + chance = _chance; + } + + public ItemRandomEnchantmentType type; + public uint ench; + public float chance; + } + + class EnchantmentStore + { + public EnchantmentStore() + { + _data[(byte)ItemRandomEnchantmentType.Property] = new MultiMap(); + _data[(byte)ItemRandomEnchantmentType.Suffix] = new MultiMap(); + } + + public MultiMap this[ItemRandomEnchantmentType type] + { + get + { + //(type != ItemRandomEnchantmentType.BonusList, "Random bonus lists do not have their own storage, use Suffix for them"); + return _data[(byte)type]; + } + } + + MultiMap[] _data = new MultiMap[2]; + } + } + + public struct ItemRandomEnchantmentId + { + public static ItemRandomEnchantmentId Empty = default(ItemRandomEnchantmentId); + + public ItemRandomEnchantmentId(ItemRandomEnchantmentType type, uint id) + { + Type = type; + Id = id; + } + + public ItemRandomEnchantmentType Type; + public uint Id; + } + + public enum ItemRandomEnchantmentType + { + Property = 0, + Suffix = 1, + BonusList = 2 + } +} diff --git a/Game/Entities/Item/ItemTemplate.cs b/Game/Entities/Item/ItemTemplate.cs new file mode 100644 index 000000000..2c90a3d75 --- /dev/null +++ b/Game/Entities/Item/ItemTemplate.cs @@ -0,0 +1,333 @@ +/* + * Copyright (C) 2012-2017 CypherCore + * + * 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, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +using Framework.Constants; +using Game.DataStorage; +using System; +using System.Collections; +using System.Collections.Generic; +using System.Diagnostics.Contracts; + +namespace Game.Entities +{ + public class ItemTemplate + { + public ItemTemplate(ItemRecord item, ItemSparseRecord sparse) + { + BasicData = item; + ExtendedData = sparse; + + Specializations[0] = new BitArray((int)Class.Max * PlayerConst.MaxSpecializations); + Specializations[1] = new BitArray((int)Class.Max * PlayerConst.MaxSpecializations); + Specializations[2] = new BitArray((int)Class.Max * PlayerConst.MaxSpecializations); + } + + public string GetName(LocaleConstant locale = SharedConst.DefaultLocale) + { + return ExtendedData.Name[locale]; + } + + public bool CanChangeEquipStateInCombat() + { + switch (GetInventoryType()) + { + case InventoryType.Relic: + case InventoryType.Shield: + case InventoryType.Holdable: + return true; + default: + break; + } + + switch (GetClass()) + { + case ItemClass.Weapon: + case ItemClass.Projectile: + return true; + } + + return false; + } + + public SkillType GetSkill() + { + SkillType[] item_weapon_skills = + { + SkillType.Axes, SkillType.TwoHandedAxes, SkillType.Bows, SkillType.Guns, SkillType.Maces, + SkillType.TwoHandedMaces, SkillType.Polearms, SkillType.Swords, SkillType.TwoHandedSwords, SkillType.Warglaives, + SkillType.Staves, 0, 0, SkillType.FistWeapons, 0, + SkillType.Daggers, 0, 0, SkillType.Crossbows, SkillType.Wands, + SkillType.Fishing + }; + + SkillType[] item_armor_skills = + { + 0, SkillType.Cloth, SkillType.Leather, SkillType.Mail, SkillType.PlateMail, 0, SkillType.Shield, 0, 0, 0, 0, 0 + }; + + + switch (GetClass()) + { + case ItemClass.Weapon: + if (GetSubClass() >= (int)ItemSubClassWeapon.Max) + return 0; + else + return item_weapon_skills[GetSubClass()]; + + case ItemClass.Armor: + if (GetSubClass() >= (int)ItemSubClassArmor.Max) + return 0; + else + return item_armor_skills[GetSubClass()]; + + default: + return 0; + } + } + + public uint GetArmor(uint itemLevel) + { + ItemQuality quality = GetQuality() != ItemQuality.Heirloom ? GetQuality() : ItemQuality.Rare; + if (quality > ItemQuality.Artifact) + return 0; + + // all items but shields + if (GetClass() != ItemClass.Armor || GetSubClass() != (uint)ItemSubClassArmor.Shield) + { + ItemArmorQualityRecord armorQuality = CliDB.ItemArmorQualityStorage.LookupByKey(itemLevel); + ItemArmorTotalRecord armorTotal = CliDB.ItemArmorTotalStorage.LookupByKey(itemLevel); + if (armorQuality == null || armorTotal == null) + return 0; + + InventoryType inventoryType = GetInventoryType(); + if (inventoryType == InventoryType.Robe) + inventoryType = InventoryType.Chest; + + ArmorLocationRecord location = CliDB.ArmorLocationStorage.LookupByKey(inventoryType); + if (location == null) + return 0; + + if (GetSubClass() < (uint)ItemSubClassArmor.Cloth || GetSubClass() > (uint)ItemSubClassArmor.Plate) + return 0; + + return (uint)(armorQuality.QualityMod[(int)quality] * armorTotal.Value[GetSubClass() - 1] * location.Modifier[GetSubClass() - 1] + 0.5f); + } + + // shields + ItemArmorShieldRecord shield = CliDB.ItemArmorShieldStorage.LookupByKey(itemLevel); + if (shield == null) + return 0; + + return (uint)(shield.Quality[(int)quality] + 0.5f); + } + + public void GetDamage(uint itemLevel, out float minDamage, out float maxDamage) + { + minDamage = maxDamage = 0.0f; + ItemQuality quality = GetQuality() != ItemQuality.Heirloom ? GetQuality() : ItemQuality.Rare; + if (GetClass() != ItemClass.Weapon || quality > ItemQuality.Artifact) + return; + + // get the right store here + if (GetInventoryType() > InventoryType.RangedRight) + return; + + float dps = 0.0f; + switch (GetInventoryType()) + { + case InventoryType.Ammo: + dps = CliDB.ItemDamageAmmoStorage.LookupByKey(itemLevel).DPS[(int)quality]; + break; + case InventoryType.Weapon2Hand: + if (GetFlags2().HasAnyFlag(ItemFlags2.CasterWeapon)) + dps = CliDB.ItemDamageTwoHandCasterStorage.LookupByKey(itemLevel).DPS[(int)quality]; + else + dps = CliDB.ItemDamageTwoHandStorage.LookupByKey(itemLevel).DPS[(int)quality]; + break; + case InventoryType.Ranged: + case InventoryType.Thrown: + case InventoryType.RangedRight: + switch ((ItemSubClassWeapon)GetSubClass()) + { + case ItemSubClassWeapon.Wand: + dps = CliDB.ItemDamageOneHandCasterStorage.LookupByKey(itemLevel).DPS[(int)quality]; + break; + case ItemSubClassWeapon.Bow: + case ItemSubClassWeapon.Gun: + case ItemSubClassWeapon.Crossbow: + if (GetFlags2().HasAnyFlag(ItemFlags2.CasterWeapon)) + dps = CliDB.ItemDamageTwoHandCasterStorage.LookupByKey(itemLevel).DPS[(int)quality]; + else + dps = CliDB.ItemDamageTwoHandStorage.LookupByKey(itemLevel).DPS[(int)quality]; + break; + default: + return; + } + break; + case InventoryType.Weapon: + case InventoryType.WeaponMainhand: + case InventoryType.WeaponOffhand: + if (GetFlags2().HasAnyFlag(ItemFlags2.CasterWeapon)) + dps = CliDB.ItemDamageOneHandCasterStorage.LookupByKey(itemLevel).DPS[(int)quality]; + else + dps = CliDB.ItemDamageOneHandStorage.LookupByKey(itemLevel).DPS[(int)quality]; + break; + default: + return; + } + + float avgDamage = dps * GetDelay() * 0.001f; + minDamage = (GetStatScalingFactor() * -0.5f + 1.0f) * avgDamage; + maxDamage = (float)Math.Floor((avgDamage * (GetStatScalingFactor() * 0.5f + 1.0f) + 0.5f)); + } + + public bool IsUsableByLootSpecialization(Player player) + { + uint spec = player.GetUInt32Value(PlayerFields.LootSpecId); + if (spec == 0) + spec = player.GetUInt32Value(PlayerFields.CurrentSpecId); + if (spec == 0) + spec = player.GetDefaultSpecId(); + + ChrSpecializationRecord chrSpecialization = CliDB.ChrSpecializationStorage.LookupByKey(spec); + if (chrSpecialization == null) + return false; + + int levelIndex = 0; + if (player.getLevel() >= 110) + levelIndex = 2; + else if (player.getLevel() > 40) + levelIndex = 1; + + return Specializations[levelIndex].Get(CalculateItemSpecBit(chrSpecialization)); + } + + public static int CalculateItemSpecBit(ChrSpecializationRecord spec) + { + return (int)((spec.ClassID - 1) * PlayerConst.MaxSpecializations + spec.OrderIndex); + } + + public uint GetId() { return BasicData.Id; } + public ItemClass GetClass() { return (ItemClass)BasicData.Class; } + public uint GetSubClass() { return BasicData.SubClass; } + public ItemQuality GetQuality() { return (ItemQuality)ExtendedData.Quality; } + public ItemFlags GetFlags() { return (ItemFlags)ExtendedData.Flags[0]; } + public ItemFlags2 GetFlags2() { return (ItemFlags2)ExtendedData.Flags[1]; } + public ItemFlags3 GetFlags3() { return (ItemFlags3)ExtendedData.Flags[2]; } + public float GetUnk1() { return ExtendedData.Unk1; } + public float GetUnk2() { return ExtendedData.Unk2; } + public uint GetBuyCount() { return Math.Max(ExtendedData.BuyCount, 1u); } + public uint GetBuyPrice() { return ExtendedData.BuyPrice; } + public uint GetSellPrice() { return ExtendedData.SellPrice; } + public InventoryType GetInventoryType() { return (InventoryType)ExtendedData.inventoryType; } + public int GetAllowableClass() { return ExtendedData.AllowableClass; } + public int GetAllowableRace() { return ExtendedData.AllowableRace; } + public uint GetBaseItemLevel() { return ExtendedData.ItemLevel; } + public int GetBaseRequiredLevel() { return ExtendedData.RequiredLevel; } + public uint GetRequiredSkill() { return ExtendedData.RequiredSkill; } + public uint GetRequiredSkillRank() { return ExtendedData.RequiredSkillRank; } + public uint GetRequiredSpell() { return ExtendedData.RequiredSpell; } + public uint GetRequiredReputationFaction() { return ExtendedData.RequiredReputationFaction; } + public uint GetRequiredReputationRank() { return ExtendedData.RequiredReputationRank; } + public uint GetMaxCount() { return ExtendedData.MaxCount; } + public uint GetContainerSlots() { return ExtendedData.ContainerSlots; } + public int GetItemStatType(uint index) + { + Contract.Assert(index < ItemConst.MaxStats); + return ExtendedData.ItemStatType[index]; + } + public int GetItemStatValue(uint index) + { + Contract.Assert(index < ItemConst.MaxStats); + return ExtendedData.ItemStatValue[index]; + } + public int GetItemStatAllocation(uint index) + { + Contract.Assert(index < ItemConst.MaxStats); + return ExtendedData.ItemStatAllocation[index]; + } + public float GetItemStatSocketCostMultiplier(uint index) + { + Contract.Assert(index < ItemConst.MaxStats); + return ExtendedData.ItemStatSocketCostMultiplier[index]; + } + public uint GetScalingStatDistribution() { return ExtendedData.ScalingStatDistribution; } + public uint GetDamageType() { return ExtendedData.DamageType; } + public uint GetDelay() { return ExtendedData.Delay; } + public float GetRangedModRange() { return ExtendedData.RangedModRange; } + public ItemBondingType GetBonding() { return (ItemBondingType)ExtendedData.Bonding; } + public uint GetPageText() { return ExtendedData.PageText; } + public uint GetStartQuest() { return ExtendedData.StartQuest; } + public uint GetLockID() { return ExtendedData.LockID; } + public uint GetRandomProperty() { return ExtendedData.RandomProperty; } + public uint GetRandomSuffix() { return ExtendedData.RandomSuffix; } + public uint GetItemSet() { return ExtendedData.ItemSet; } + public uint GetArea() { return ExtendedData.Area; } + public uint GetMap() { return ExtendedData.Map; } + public BagFamilyMask GetBagFamily() { return (BagFamilyMask)ExtendedData.BagFamily; } + public uint GetTotemCategory() { return ExtendedData.TotemCategory; } + public SocketColor GetSocketColor(uint index) + { + Contract.Assert(index < ItemConst.MaxGemSockets); + return (SocketColor)ExtendedData.SocketColor[index]; + } + public uint GetSocketBonus() { return ExtendedData.SocketBonus; } + public uint GetGemProperties() { return ExtendedData.GemProperties; } + public float GetArmorDamageModifier() { return ExtendedData.ArmorDamageModifier; } + public uint GetDuration() { return ExtendedData.Duration; } + public uint GetItemLimitCategory() { return ExtendedData.ItemLimitCategory; } + public HolidayIds GetHolidayID() { return (HolidayIds)ExtendedData.HolidayID; } + public float GetStatScalingFactor() { return ExtendedData.StatScalingFactor; } + public byte GetArtifactID() { return ExtendedData.ArtifactID; } + + public bool IsCurrencyToken() { return (GetBagFamily() & BagFamilyMask.CurrencyTokens) != 0; } + + public uint GetMaxStackSize() + { + return (ExtendedData.Stackable == 2147483647 || ExtendedData.Stackable <= 0) ? (0x7FFFFFFF - 1) : ExtendedData.Stackable; + } + + public bool IsPotion() { return GetClass() == ItemClass.Consumable && GetSubClass() == (uint)ItemSubClassConsumable.Potion; } + public bool IsVellum() { return GetClass() == ItemClass.TradeGoods && GetSubClass() == (uint)ItemSubClassTradeGoods.Enchantment; } + public bool IsConjuredConsumable() { return GetClass() == ItemClass.Consumable && GetFlags().HasAnyFlag(ItemFlags.Conjured); } + public bool IsCraftingReagent() { return GetFlags2().HasAnyFlag(ItemFlags2.UsedInATradeskill); } + + public bool IsRangedWeapon() + { + return GetClass() == ItemClass.Weapon || GetSubClass() == (uint)ItemSubClassWeapon.Bow || + GetSubClass() == (uint)ItemSubClassWeapon.Gun || GetSubClass() == (uint)ItemSubClassWeapon.Crossbow; + } + + public uint MaxDurability; + public List Effects = new List(); + + // extra fields, not part of db2 files + public uint ScriptId; + public uint DisenchantID; + public uint RequiredDisenchantSkill; + public uint FoodType; + public uint MinMoneyLoot; + public uint MaxMoneyLoot; + public ItemFlagsCustom FlagsCu; + public float SpellPPMRate; + public BitArray[] Specializations = new BitArray[3]; + public uint ItemSpecClassMask; + + protected ItemRecord BasicData; + protected ItemSparseRecord ExtendedData; + } +} diff --git a/Game/Entities/Object/ObjectGuid.cs b/Game/Entities/Object/ObjectGuid.cs new file mode 100644 index 000000000..11bc4ef41 --- /dev/null +++ b/Game/Entities/Object/ObjectGuid.cs @@ -0,0 +1,349 @@ +/* + * Copyright (C) 2012-2017 CypherCore + * + * 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, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +using Framework.Constants; +using System; + +namespace Game.Entities +{ + public struct ObjectGuid : IEquatable + { + public static ObjectGuid Empty = new ObjectGuid(); + public static ObjectGuid TradeItem = Create(HighGuid.Uniq, 10ul); + + public ObjectGuid(ulong high, ulong low) + { + _low = low; + _high = high; + } + + public static ObjectGuid Create(HighGuid type, ulong counter) + { + switch (type) + { + case HighGuid.Uniq: + case HighGuid.Party: + case HighGuid.WowAccount: + case HighGuid.BNetAccount: + case HighGuid.GMTask: + case HighGuid.RaidGroup: + case HighGuid.Spell: + case HighGuid.Mail: + case HighGuid.UserRouter: + case HighGuid.PVPQueueGroup: + case HighGuid.UserClient: + case HighGuid.UniqUserClient: + case HighGuid.BattlePet: + case HighGuid.CommerceObj: + case HighGuid.ClientSession: + return GlobalCreate(type, counter); + case HighGuid.Player: + case HighGuid.Item: // This is not exactly correct, there are 2 more unknown parts in highguid: (high >> 10 & 0xFF), (high >> 18 & 0xFFFFFF) + case HighGuid.Guild: + case HighGuid.Transport: + return RealmSpecificCreate(type, counter); + default: + Log.outError(LogFilter.Server, "This guid type cannot be constructed using Create(HighGuid: {0} ulong counter).", type); + break; + } + return ObjectGuid.Empty; + } + public static ObjectGuid Create(HighGuid type, uint mapId, uint entry, ulong counter) + { + if (type == HighGuid.Transport) + return ObjectGuid.Empty; + + return MapSpecificCreate(type, 0, (ushort)mapId, 0, entry, counter); + } + + public static ObjectGuid Create(HighGuid type, SpellCastSource subType, uint mapId, uint entry, ulong counter) { return MapSpecificCreate(type, (byte)subType, (ushort)mapId, 0, entry, counter); } + + static ObjectGuid GlobalCreate(HighGuid type, ulong counter) + { + return new ObjectGuid(((ulong)type << 58), counter); + } + static ObjectGuid RealmSpecificCreate(HighGuid type, ulong counter) + { + return new ObjectGuid(((ulong)type << 58 | (ulong)Global.WorldMgr.GetRealm().Id.Realm << 42), counter); + } + static ObjectGuid MapSpecificCreate(HighGuid type, byte subType, ushort mapId, uint serverId, uint entry, ulong counter) + { + return new ObjectGuid((((ulong)type << 58) | ((ulong)(Global.WorldMgr.GetRealm().Id.Realm & 0x1FFF) << 42) | ((ulong)(mapId & 0x1FFF) << 29) | ((ulong)(entry & 0x7FFFFF) << 6) | ((ulong)subType & 0x3F)), + (((ulong)(serverId & 0xFFFFFF) << 40) | (counter & 0xFFFFFFFFFF))); + } + + public byte[] GetRawValue() + { + byte[] temp = new byte[16]; + var hiBytes = BitConverter.GetBytes(_high); + var lowBytes = BitConverter.GetBytes(_low); + for (var i = 0; i < temp.Length / 2; ++i) + { + temp[i] = hiBytes[i]; + temp[8 + i] = lowBytes[i]; + } + + return temp; + } + public void SetRawValue(byte[] bytes) + { + _high = BitConverter.ToUInt64(bytes, 0); + _low = BitConverter.ToUInt64(bytes, 8); + } + + public void SetRawValue(ulong high, ulong low) { _high = high; _low = low; } + public void Clear() { _high = 0; _low = 0; } + public ulong GetHighValue() + { + return _high; + } + public ulong GetLowValue() + { + return _low; + } + + public HighGuid GetHigh() { return (HighGuid)(_high >> 58); } + byte GetSubType() { return (byte)(_high & 0x3F); } + uint GetRealmId() { return (uint)((_high >> 42) & 0x1FFF); } + uint GetServerId() { return (uint)((_low >> 40) & 0x1FFF); } + uint GetMapId() { return (uint)((_high >> 29) & 0x1FFF); } + public uint GetEntry() { return (uint)((_high >> 6) & 0x7FFFFF); } + public ulong GetCounter() { return _low & 0xFFFFFFFFFF; } + + public bool IsEmpty() { return _low == 0 && _high == 0; } + public bool IsCreature() { return GetHigh() == HighGuid.Creature; } + public bool IsPet() { return GetHigh() == HighGuid.Pet; } + public bool IsVehicle() { return GetHigh() == HighGuid.Vehicle; } + public bool IsCreatureOrPet() { return IsCreature() || IsPet(); } + public bool IsCreatureOrVehicle() { return IsCreature() || IsVehicle(); } + public bool IsAnyTypeCreature() { return IsCreature() || IsPet() || IsVehicle(); } + public bool IsPlayer() { return !IsEmpty() && GetHigh() == HighGuid.Player; } + public bool IsUnit() { return IsAnyTypeCreature() || IsPlayer(); } + public bool IsItem() { return GetHigh() == HighGuid.Item; } + public bool IsGameObject() { return GetHigh() == HighGuid.GameObject; } + public bool IsDynamicObject() { return GetHigh() == HighGuid.DynamicObject; } + public bool IsCorpse() { return GetHigh() == HighGuid.Corpse; } + public bool IsAreaTrigger() { return GetHigh() == HighGuid.AreaTrigger; } + public bool IsMOTransport() { return GetHigh() == HighGuid.Transport; } + public bool IsAnyTypeGameObject() { return IsGameObject() || IsMOTransport(); } + public bool IsParty() { return GetHigh() == HighGuid.Party; } + public bool IsGuild() { return GetHigh() == HighGuid.Guild; } + bool IsSceneObject() { return GetHigh() == HighGuid.SceneObject; } + bool IsConversation() { return GetHigh() == HighGuid.Conversation; } + bool IsCast() { return GetHigh() == HighGuid.Cast; } + + public TypeId GetTypeId() { return GetTypeId(GetHigh()); } + bool HasEntry() { return HasEntry(GetHigh()); } + + public static bool operator <(ObjectGuid left, ObjectGuid right) + { + if (left._high < right._high) + return true; + else if (left._high > right._high) + return false; + + return left._low < right._low; + } + public static bool operator >(ObjectGuid left, ObjectGuid right) + { + if (left._high > right._high) + return true; + else if (left._high < right._high) + return false; + + return left._low > right._low; + } + + public override string ToString() + { + string str = string.Format("GUID Full: 0x{0}, Type: {1}", _low, GetHigh()); + if (HasEntry()) + str += (IsPet() ? " Pet number: " : " Entry: ") + GetEntry() + " "; + + str += " Low: " + GetCounter(); + return str; + } + + public static bool operator ==(ObjectGuid first, ObjectGuid other) + { + if (ReferenceEquals(first, other)) + return true; + + if ((object)first == null || (object)other == null) + return false; + + return first.Equals(other); + } + + public static bool operator !=(ObjectGuid first, ObjectGuid other) + { + return !(first == other); + } + + public override bool Equals(object obj) + { + return obj != null && obj is ObjectGuid && Equals((ObjectGuid)obj); + } + + public bool Equals(ObjectGuid other) + { + return other._high == _high && other._low == _low; + } + + public override int GetHashCode() + { + return new { _high, _low }.GetHashCode(); + } + + //Static Methods + static TypeId GetTypeId(HighGuid high) + { + switch (high) + { + case HighGuid.Item: + return TypeId.Item; + case HighGuid.Creature: + case HighGuid.Pet: + case HighGuid.Vehicle: + return TypeId.Unit; + case HighGuid.Player: + return TypeId.Player; + case HighGuid.GameObject: + case HighGuid.Transport: + return TypeId.GameObject; + case HighGuid.DynamicObject: + return TypeId.DynamicObject; + case HighGuid.Corpse: + return TypeId.Corpse; + case HighGuid.AreaTrigger: + return TypeId.AreaTrigger; + case HighGuid.SceneObject: + return TypeId.SceneObject; + case HighGuid.Conversation: + return TypeId.Conversation; + default: + return TypeId.Object; + } + } + static bool HasEntry(HighGuid high) + { + switch (high) + { + case HighGuid.GameObject: + case HighGuid.Creature: + case HighGuid.Pet: + case HighGuid.Vehicle: + default: + return true; + } + } + public static bool IsMapSpecific(HighGuid high) + { + switch (high) + { + case HighGuid.Conversation: + case HighGuid.Creature: + case HighGuid.Vehicle: + case HighGuid.Pet: + case HighGuid.GameObject: + case HighGuid.DynamicObject: + case HighGuid.AreaTrigger: + case HighGuid.Corpse: + case HighGuid.LootObject: + case HighGuid.SceneObject: + case HighGuid.Scenario: + case HighGuid.AIGroup: + case HighGuid.DynamicDoor: + case HighGuid.Vignette: + case HighGuid.CallForHelp: + case HighGuid.AIResource: + case HighGuid.AILock: + case HighGuid.AILockTicket: + return true; + default: + return false; + } + } + public static bool IsRealmSpecific(HighGuid high) + { + switch (high) + { + case HighGuid.Player: + case HighGuid.Item: + case HighGuid.Transport: + case HighGuid.Guild: + return true; + default: + return false; + } + } + public static bool IsGlobal(HighGuid high) + { + switch (high) + { + case HighGuid.Uniq: + case HighGuid.Party: + case HighGuid.WowAccount: + case HighGuid.BNetAccount: + case HighGuid.GMTask: + case HighGuid.RaidGroup: + case HighGuid.Spell: + case HighGuid.Mail: + case HighGuid.UserRouter: + case HighGuid.PVPQueueGroup: + case HighGuid.UserClient: + case HighGuid.UniqUserClient: + case HighGuid.BattlePet: + return true; + default: + return false; + } + } + + ulong _low; + ulong _high; + } + + public class ObjectGuidGenerator + { + public ObjectGuidGenerator(HighGuid highGuid, ulong start = 1) + { + _highGuid = highGuid; + _nextGuid = start; + } + + public void Set(ulong val) { _nextGuid = val; } + + public ulong Generate() + { + if (_nextGuid >= ulong.MaxValue - 1) + HandleCounterOverflow(); + return _nextGuid++; + } + + public ulong GetNextAfterMaxUsed() { return _nextGuid; } + + void HandleCounterOverflow() + { + Log.outFatal(LogFilter.Server, "{0} guid overflow!! Can't continue, shutting down server. ", _highGuid); + Global.WorldMgr.StopNow(); + } + + ulong _nextGuid; + HighGuid _highGuid; + } +} diff --git a/Game/Entities/Object/Position.cs b/Game/Entities/Object/Position.cs new file mode 100644 index 000000000..947599e38 --- /dev/null +++ b/Game/Entities/Object/Position.cs @@ -0,0 +1,363 @@ +/* + * Copyright (C) 2012-2017 CypherCore + * + * 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, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +using Framework.GameMath; +using Game.Maps; +using System; + +namespace Game.Entities +{ + public class Position + { + public Position(float x = 0f, float y = 0f, float z = 0f, float o = 0f) + { + posX = x; + posY = y; + posZ = z; + Orientation = o; + } + + public float GetPositionX() + { + return posX; + } + public float GetPositionY() + { + return posY; + } + public float GetPositionZ() + { + return posZ; + } + public float GetOrientation() + { + return Orientation; + } + + public void Relocate(float x, float y, float z, float o = 0.0f) + { + posX = x; + posY = y; + posZ = z; + SetOrientation(o); + } + public void Relocate(Position loc) + { + Relocate(loc.posX, loc.posY, loc.posZ, loc.Orientation); + } + public void Relocate(Vector3 pos) + { + posX = pos.X; + posY = pos.Y; + posZ = pos.Z; + } + public void RelocateOffset(Position offset) + { + posX = (float)(posX + (offset.posX * Math.Cos(Orientation) + offset.posX * Math.Sin(Orientation + MathFunctions.PI))); + posY = (float)(posY + (offset.posY * Math.Cos(Orientation) + offset.posY * Math.Sin(Orientation))); + posZ = posZ + offset.posZ; + Orientation = (Orientation + offset.Orientation); + } + + public bool IsPositionValid() + { + return GridDefines.IsValidMapCoord(posX, posY, posZ, Orientation); + } + + public float GetRelativeAngle(Position pos) + { + return GetAngle(pos) - Orientation; + } + public float GetRelativeAngle(float x, float y) + { + return GetAngle(x, y) - Orientation; + } + + public void GetPosition(out float x, out float y) + { + x = posX; y = posY; + } + public void GetPosition(out float x, out float y, out float z) + { + x = posX; y = posY; z = posZ; + } + public void GetPosition(out float x, out float y, out float z, out float o) + { + x = posX; + y = posY; + z = posZ; + o = Orientation; + } + public Position GetPosition() + { + return this; + } + public void GetPositionOffsetTo(Position endPos, out Position retOffset) + { + retOffset = new Position(); + + float dx = endPos.GetPositionX() - GetPositionX(); + float dy = endPos.GetPositionY() - GetPositionY(); + + retOffset.posX = (float)(dx * Math.Cos(GetOrientation()) + dy * Math.Sin(GetOrientation())); + retOffset.posY = (float)(dy * Math.Cos(GetOrientation()) - dx * Math.Sin(GetOrientation())); + retOffset.posZ = endPos.GetPositionZ() - GetPositionZ(); + retOffset.SetOrientation(endPos.GetOrientation() - GetOrientation()); + } + + public Position GetPositionWithOffset(Position offset) + { + Position ret = this; + ret.RelocateOffset(offset); + return ret; + } + + public static float NormalizeOrientation(float o) + { + // fmod only supports positive numbers. Thus we have + // to emulate negative numbers + if (o < 0) + { + float mod = o * -1; + mod = mod % (2.0f * MathFunctions.PI); + mod = -mod + 2.0f * MathFunctions.PI; + return mod; + } + return o % (2.0f * MathFunctions.PI); + } + + public float GetExactDist(float x, float y, float z) + { + return (float)Math.Sqrt(GetExactDistSq(x, y, z)); + } + public float GetExactDist(Position pos) + { + return (float)Math.Sqrt(GetExactDistSq(pos)); + } + public float GetExactDistSq(float x, float y, float z) + { + float dz = posZ - z; + + return GetExactDist2dSq(x, y) + dz * dz; + } + public float GetExactDistSq(Position pos) + { + float dx = posX - pos.posX; + float dy = posY - pos.posY; + float dz = posZ - pos.posZ; + + return dx * dx + dy * dy + dz * dz; + } + public float GetExactDist2d(float x, float y) + { + return (float)Math.Sqrt(GetExactDist2dSq(x, y)); + } + public float GetExactDist2d(Position pos) + { + return (float)Math.Sqrt(GetExactDist2dSq(pos)); + } + public float GetExactDist2dSq(float x, float y) + { + float dx = posX - x; + float dy = posY - y; + + return dx * dx + dy * dy; + } + public float GetExactDist2dSq(Position pos) + { + float dx = posX - pos.posX; + float dy = posY - pos.posY; + + return dx * dx + dy * dy; + } + + public float GetAngle(float x, float y) + { + float dx = x - GetPositionX(); + float dy = y - GetPositionY(); + + float ang = (float)Math.Atan2(dy, dx); + ang = ang >= 0 ? ang : 2 * MathFunctions.PI + ang; + return ang; + } + public float GetAngle(Position pos) + { + if (pos == null) + return 0; + + return GetAngle(pos.GetPositionX(), pos.GetPositionY()); + } + + public bool IsInDist(float x, float y, float z, float dist) + { + return GetExactDistSq(x, y, z) < dist * dist; + } + public bool IsInDist(Position pos, float dist) + { + return GetExactDistSq(pos) < dist * dist; + } + + public bool IsInDist2d(float x, float y, float dist) + { + return GetExactDist2dSq(x, y) < dist * dist; + } + public bool IsInDist2d(Position pos, float dist) + { + return GetExactDist2dSq(pos) < dist * dist; + } + + public void SetOrientation(float orientation) + { + Orientation = NormalizeOrientation(orientation); + } + + public bool IsWithinBox(Position center, float xradius, float yradius, float zradius) + { + // rotate the WorldObject position instead of rotating the whole cube, that way we can make a simplified + // is-in-cube check and we have to calculate only one point instead of 4 + + // 2PI = 360*, keep in mind that ingame orientation is counter-clockwise + double rotation = 2 * Math.PI - center.GetOrientation(); + double sinVal = Math.Sin(rotation); + double cosVal = Math.Cos(rotation); + + float BoxDistX = GetPositionX() - center.GetPositionX(); + float BoxDistY = GetPositionY() - center.GetPositionY(); + + float rotX = (float)(center.GetPositionX() + BoxDistX * cosVal - BoxDistY * sinVal); + float rotY = (float)(center.GetPositionY() + BoxDistY * cosVal + BoxDistX * sinVal); + + // box edges are parallel to coordiante axis, so we can treat every dimension independently :D + float dz = GetPositionZ() - center.GetPositionZ(); + float dx = rotX - center.GetPositionX(); + float dy = rotY - center.GetPositionY(); + if ((Math.Abs(dx) > xradius) || (Math.Abs(dy) > yradius) || (Math.Abs(dz) > zradius)) + return false; + + return true; + } + public bool HasInArc(float arc, Position obj, float border = 2.0f) + { + // always have self in arc + if (obj == this) + return true; + + float angle = GetAngle(obj); + angle -= GetOrientation(); + + // move angle to range -pi ... +pi + if (angle > MathFunctions.PI) + angle -= 2.0f * MathFunctions.PI; + + float lborder = -1 * (arc / border); // in range -pi..0 + float rborder = (arc / border); // in range 0..pi + return ((angle >= lborder) && (angle <= rborder)); + } + public void GetSinCos(float x, float y, out float vsin, out float vcos) + { + float dx = GetPositionX() - x; + float dy = GetPositionY() - y; + + if (Math.Abs(dx) < 0.001f && Math.Abs(dy) < 0.001f) + { + float angle = (float)RandomHelper.NextDouble() * MathFunctions.TwoPi; + vcos = (float)Math.Cos(angle); + vsin = (float)Math.Sin(angle); + } + else + { + float dist = (float)Math.Sqrt((dx * dx) + (dy * dy)); + vcos = dx / dist; + vsin = dy / dist; + } + } + + public override string ToString() + { + return string.Format("X: {0} Y: {1} Z: {2} O: {3}", posX, posY, posZ, Orientation); + } + + public float posX; + public float posY; + public float posZ; + public float Orientation; + } + + public class WorldLocation : Position + { + public WorldLocation(uint mapId = 0xFFFFFFFF, float x = 0, float y = 0, float z = 0, float o = 0) + { + _mapId = mapId; + Relocate(x, y, z, o); + } + public WorldLocation(uint mapId, Position pos) + { + _mapId = mapId; + Relocate(pos); + } + public WorldLocation(WorldLocation loc) + { + _mapId = loc._mapId; + Relocate(loc); + } + public WorldLocation(Position pos) + { + _mapId = 0xFFFFFFFF; + Relocate(pos); + } + + public void WorldRelocate(WorldLocation loc) + { + _mapId = loc._mapId; + Relocate(loc); + } + + public void WorldRelocate(uint mapId = 0xFFFFFFFF, float x = 0.0f, float y = 0.0f, float z = 0.0f, float o = 0.0f) + { + _mapId = mapId; + Relocate(x, y, z, o); + } + + public uint GetMapId() { return _mapId; } + public void SetMapId(uint _mapId) { this._mapId = _mapId; } + + public Cell GetCurrentCell() + { + if (currentCell == null) + Log.outError(LogFilter.Server, "Calling currentCell but its null"); + + return currentCell; + } + public void SetCurrentCell(Cell cell) { currentCell = cell; } + public void SetNewCellPosition(float x, float y, float z, float o) + { + _moveState = ObjectCellMoveState.Active; + _newPosition.Relocate(x, y, z, o); + } + + public WorldLocation GetWorldLocation() + { + return this; + } + + uint _mapId; + Cell currentCell; + public ObjectCellMoveState _moveState; + + public Position _newPosition = new Position(); + } +} diff --git a/Game/Entities/Object/Update/UpdateData.cs b/Game/Entities/Object/Update/UpdateData.cs new file mode 100644 index 000000000..85ba12f3f --- /dev/null +++ b/Game/Entities/Object/Update/UpdateData.cs @@ -0,0 +1,91 @@ +/* + * Copyright (C) 2012-2017 CypherCore + * + * 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, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +using Framework.IO; +using Game.Network; +using Game.Network.Packets; +using System.Collections.Generic; + +namespace Game.Entities +{ + public class UpdateData + { + public UpdateData(uint mapId) + { + MapId = mapId; + } + + public void AddOutOfRangeGUID(List guids) + { + outOfRangeGUIDs.AddRange(guids); + } + + public void AddOutOfRangeGUID(ObjectGuid guid) + { + outOfRangeGUIDs.Add(guid); + } + + public void AddUpdateBlock(ByteBuffer block) + { + data.WriteBytes(block.GetData()); + ++BlockCount; + } + + public bool BuildPacket(out UpdateObject packet) + { + packet = new UpdateObject(); + + packet.NumObjUpdates = BlockCount; + packet.MapID = (ushort)MapId; + + WorldPacket buffer = new WorldPacket(); + if (buffer.WriteBit(!outOfRangeGUIDs.Empty())) + { + buffer.WriteUInt16(0); // object limit to instantly destroy - objects before this index on m_outOfRangeGUIDs list get "smoothly phased out" + buffer.WriteUInt32(outOfRangeGUIDs.Count); + + foreach (var guid in outOfRangeGUIDs) + buffer.WritePackedGuid(guid); + } + var bytes = data.GetData(); + buffer.WriteUInt32(bytes.Length); + buffer.WriteBytes(bytes); + + packet.Data = buffer.GetData(); + return true; + } + + public void Clear() + { + data.Clear(); + outOfRangeGUIDs.Clear(); + BlockCount = 0; + MapId = 0; + } + + public bool HasData() { return BlockCount > 0 || outOfRangeGUIDs.Count != 0; } + + public List GetOutOfRangeGUIDs() { return outOfRangeGUIDs; } + + public void SetMapId(ushort mapId) { MapId = mapId; } + + uint MapId; + uint BlockCount; + List outOfRangeGUIDs = new List(); + ByteBuffer data = new ByteBuffer(); + } +} diff --git a/Game/Entities/Object/Update/UpdateMask.cs b/Game/Entities/Object/Update/UpdateMask.cs new file mode 100644 index 000000000..bd87da645 --- /dev/null +++ b/Game/Entities/Object/Update/UpdateMask.cs @@ -0,0 +1,101 @@ +/* + * Copyright (C) 2012-2017 CypherCore + * + * 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, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +using Framework.Constants; +using Framework.IO; +using System.Collections; + +namespace Game.Entities +{ + public class UpdateMask + { + public UpdateMask(uint valuesCount = 0) + { + _fieldCount = valuesCount; + _blockCount = (valuesCount + 32 - 1) / 32; + + _mask = new BitArray((int)valuesCount, false); + } + + public uint GetCount() { return _fieldCount; } + + public virtual void AppendToPacket(ByteBuffer data) + { + data.WriteUInt8(_blockCount); + var maskArray = new byte[_blockCount << 2]; + + _mask.CopyTo(maskArray, 0); + data.WriteBytes(maskArray); + } + + public bool GetBit(int index) + { + return _mask.Get(index); + } + + public void SetBit(int index) + { + _mask.Set(index, true); + } + + void UnsetBit(int index) + { + _mask.Set(index, false); + } + + public void Clear() + { + _mask.SetAll(false); + } + + uint _fieldCount; + protected uint _blockCount; + protected BitArray _mask; + } + + public class DynamicUpdateMask : UpdateMask + { + public DynamicUpdateMask(uint valuesCount) : base(valuesCount) { } + + public void EncodeDynamicFieldChangeType(DynamicFieldChangeType changeType, UpdateType updateType) + { + DynamicFieldChangeType = (uint)(_blockCount | ((uint)(changeType & Entities.DynamicFieldChangeType.ValueAndSizeChanged) * ((3 - (int)updateType /*this part evaluates to 0 if update type is not VALUES*/) / 3))); + } + + public override void AppendToPacket(ByteBuffer data) + { + data.WriteUInt16(DynamicFieldChangeType); + if (ValueCount != 0) + data.WriteUInt32(ValueCount); + + var maskArray = new byte[_blockCount << 2]; + + _mask.CopyTo(maskArray, 0); + data.WriteBytes(maskArray); + } + + public uint DynamicFieldChangeType; + public int ValueCount; + } + + public enum DynamicFieldChangeType + { + Unchanged = 0, + ValueChanged = 0x7FFF, + ValueAndSizeChanged = 0x8000 + } +} diff --git a/Game/Entities/Object/WorldObject.cs b/Game/Entities/Object/WorldObject.cs new file mode 100644 index 000000000..3851690c4 --- /dev/null +++ b/Game/Entities/Object/WorldObject.cs @@ -0,0 +1,3265 @@ +/* + * Copyright (C) 2012-2017 CypherCore + * + * 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, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +using Framework.Collections; +using Framework.Constants; +using Framework.Dynamic; +using Framework.GameMath; +using Framework.IO; +using Game.AI; +using Game.BattleFields; +using Game.DataStorage; +using Game.Maps; +using Game.Network; +using Game.Network.Packets; +using Game.Scenarios; +using System; +using System.Collections; +using System.Collections.Generic; +using System.Diagnostics.Contracts; +using System.Linq; +using System.Runtime.InteropServices; +using System.Text; + +namespace Game.Entities +{ + public class WorldObject : WorldLocation, IDisposable + { + public WorldObject(bool isWorldObject) + { + _name = ""; + m_isWorldObject = isWorldObject; + phaseMask = PhaseMasks.Normal; + + m_serverSideVisibility.SetValue(ServerSideVisibilityType.Ghost, GhostVisibilityType.Alive | GhostVisibilityType.Ghost); + m_serverSideVisibilityDetect.SetValue(ServerSideVisibilityType.Ghost, GhostVisibilityType.Alive); + + objectTypeId = TypeId.Object; + objectTypeMask = TypeMask.Object; + + _fieldNotifyFlags = UpdateFieldFlags.Dynamic; + + m_movementInfo = new MovementInfo(); + m_updateFlag = UpdateFlag.None; + } + + public virtual void Dispose() + { + // this may happen because there are many !create/delete + if (IsWorldObject() && _currMap) + { + if (IsTypeId(TypeId.Corpse)) + { + Log.outFatal(LogFilter.Misc, "WorldObject.Dispose() Corpse Type: {0} ({1}) deleted but still in map!!", + ToCorpse().GetCorpseType(), GetGUID().ToString()); + Contract.Assert(false); + } + ResetMap(); + } + + if (IsInWorld) + { + Log.outFatal(LogFilter.Misc, "WorldObject.Dispose() {0} deleted but still in world!!", GetGUID().ToString()); + if (isTypeMask(TypeMask.Item)) + Log.outFatal(LogFilter.Misc, "Item slot {0}", ((Item)this).GetSlot()); + Contract.Assert(false); + } + + if (m_objectUpdated) + { + Log.outFatal(LogFilter.Misc, "WorldObject.Dispose() {0} deleted but still in update list!!", GetGUID().ToString()); + Contract.Assert(false); + } + } + + void InitValues() + { + UpdateData = new Hashtable((int)valuesCount); + for (int i = 0; i < valuesCount; ++i) + UpdateData[i] = 0u; + + _changesMask = new BitArray((int)valuesCount); + + if (_dynamicValuesCount != 0) + { + _dynamicValues = new Dictionary[_dynamicValuesCount]; + _dynamicChangesArrayMask = new BitArray[_dynamicValuesCount]; + + for (var i = 0; i < _dynamicValuesCount; ++i) + { + _dynamicValues[i] = new Dictionary(); + _dynamicChangesArrayMask[i] = new BitArray(0); + _dynamicChangesMask[i] = DynamicFieldChangeType.Unchanged; + } + } + + m_objectUpdated = false; + } + + public void _Create(ObjectGuid guid) + { + if (UpdateData == null) + InitValues(); + + SetGuidValue(ObjectFields.Guid, guid); + SetUInt16Value(ObjectFields.Type, 0, (ushort)objectTypeMask); + } + + public string _ConcatFields(object startIndex, uint size) + { + StringBuilder sb = new StringBuilder(); + for (int index = 0; index < size; ++index) + sb.AppendFormat("{0} ", GetUInt32Value(index + (int)startIndex)); + return sb.ToString(); + } + + public virtual void AddToWorld() + { + if (IsInWorld) + return; + + IsInWorld = true; + ClearUpdateMask(true); + } + + public virtual void RemoveFromWorld() + { + if (!IsInWorld) + return; + + if (!objectTypeMask.HasAnyFlag(TypeMask.Item | TypeMask.Container)) + DestroyForNearbyPlayers(); + + IsInWorld = false; + ClearUpdateMask(true); + } + + public virtual void BuildCreateUpdateBlockForPlayer(UpdateData data, Player target) + { + if (!target) + return; + + UpdateType updateType = UpdateType.CreateObject; + UpdateFlag flags = m_updateFlag; + + if (target == this) + flags |= UpdateFlag.Self; + + switch (GetGUID().GetHigh()) + { + case HighGuid.Player: + case HighGuid.Pet: + case HighGuid.Corpse: + case HighGuid.DynamicObject: + case HighGuid.AreaTrigger: + case HighGuid.Conversation: + updateType = UpdateType.CreateObject2; + break; + case HighGuid.Creature: + case HighGuid.Vehicle: + TempSummon summon = ToUnit().ToTempSummon(); + if (summon) + if (summon.GetSummonerGUID().IsPlayer()) + updateType = UpdateType.CreateObject2; + break; + case HighGuid.GameObject: + if (ToGameObject().GetOwnerGUID().IsPlayer()) + updateType = UpdateType.CreateObject2; + break; + } + + if (!flags.HasAnyFlag(UpdateFlag.Living)) + { + if (!m_movementInfo.transport.guid.IsEmpty()) + flags |= UpdateFlag.TransportPosition; + + if (GetAIAnimKitId() != 0 || GetMovementAnimKitId() != 0 || GetMeleeAnimKitId() != 0) + flags |= UpdateFlag.AnimKits; + } + + if (flags.HasAnyFlag(UpdateFlag.StationaryPosition)) + { + // UPDATETYPE_CREATE_OBJECT2 for some gameobject types... + if (isTypeMask(TypeMask.GameObject)) + { + switch (ToGameObject().GetGoType()) + { + case GameObjectTypes.Trap: + case GameObjectTypes.DuelArbiter: + case GameObjectTypes.FlagStand: + case GameObjectTypes.FlagDrop: + updateType = UpdateType.CreateObject2; + break; + default: + break; + } + } + } + Unit unit = ToUnit(); + if (unit) + if (unit.GetVictim()) + flags |= UpdateFlag.HasTarget; + + if (target.m_clientGUIDs.Contains(GetGUID())) + { + + } + + WorldPacket buffer = new WorldPacket(); + buffer.WriteUInt8(updateType); + buffer.WritePackedGuid(GetGUID()); + buffer.WriteUInt8(objectTypeId); + + BuildMovementUpdate(buffer, flags); + BuildValuesUpdate(updateType, buffer, target); + BuildDynamicValuesUpdate(updateType, buffer, target); + data.AddUpdateBlock(buffer); + } + + public void SendUpdateToPlayer(Player player) + { + // send create update to player + UpdateData upd = new UpdateData(player.GetMapId()); + UpdateObject packet; + + if (player.HaveAtClient(this)) + BuildValuesUpdateBlockForPlayer(upd, player); + else + BuildCreateUpdateBlockForPlayer(upd, player); + + upd.BuildPacket(out packet); + player.SendPacket(packet); + } + + public void BuildValuesUpdateBlockForPlayer(UpdateData data, Player target) + { + WorldPacket buffer = new WorldPacket(); + + buffer.WriteUInt8(UpdateType.Values); + buffer.WritePackedGuid(GetGUID()); + + BuildValuesUpdate(UpdateType.Values, buffer, target); + BuildDynamicValuesUpdate(UpdateType.Values, buffer, target); + + data.AddUpdateBlock(buffer); + } + + public void BuildOutOfRangeUpdateBlock(UpdateData data) + { + data.AddOutOfRangeGUID(GetGUID()); + } + + public virtual void DestroyForPlayer(Player target) + { + UpdateData updateData = new UpdateData(target.GetMapId()); + BuildOutOfRangeUpdateBlock(updateData); + UpdateObject packet; + updateData.BuildPacket(out packet); + target.SendPacket(packet); + } + + public int GetInt32Value(object index) + { + Contract.Assert((int)index < valuesCount || PrintIndexError(index, false)); + return Convert.ToInt32(UpdateData[(int)index]); + } + + public uint GetUInt32Value(object index) + { + Contract.Assert(Convert.ToInt32(index) < valuesCount || PrintIndexError(index, false)); + return Convert.ToUInt32(UpdateData[Convert.ToInt32(index)]); + } + + public ulong GetUInt64Value(object index) + { + Contract.Assert((int)index + 1 < valuesCount || PrintIndexError(index, false)); + return (Convert.ToUInt64(UpdateData[(int)index + 1]) << 32 | (uint)UpdateData[(int)index]); + } + + public float GetFloatValue(object index) + { + Contract.Assert((int)index < valuesCount || PrintIndexError(index, false)); + return Convert.ToSingle(UpdateData[(int)index]); + } + + public byte GetByteValue(object index, byte offset) + { + Contract.Assert((int)index < valuesCount || PrintIndexError(index, false)); + Contract.Assert(offset < 4); + return Convert.ToByte(((uint)UpdateData[(int)index] >> (offset * 8)) & 0xFF); + } + + public ushort GetUInt16Value(object index, byte offset) + { + Contract.Assert((int)index < valuesCount || PrintIndexError(index, false)); + Contract.Assert(offset < 2); + return Convert.ToUInt16(((uint)UpdateData[(int)index] >> (offset * 16)) & 0xFFFF); + } + + public ObjectGuid GetGuidValue(object index) + { + Contract.Assert((int)index + 3 < valuesCount || PrintIndexError(index, false)); + return new ObjectGuid(GetUInt64Value((int)index + 2), GetUInt64Value(index)); + } + + public void BuildMovementUpdate(WorldPacket data, UpdateFlag flags) + { + bool NoBirthAnim = false; + bool EnablePortals = false; + bool PlayHoverAnim = false; + bool HasMovementUpdate = flags.HasAnyFlag(UpdateFlag.Living); + bool HasMovementTransport = flags.HasAnyFlag(UpdateFlag.TransportPosition); + bool Stationary = flags.HasAnyFlag(UpdateFlag.StationaryPosition); + bool CombatVictim = flags.HasAnyFlag(UpdateFlag.HasTarget); + bool ServerTime = flags.HasAnyFlag(UpdateFlag.Transport); + bool VehicleCreate = flags.HasAnyFlag(UpdateFlag.Vehicle); + bool AnimKitCreate = flags.HasAnyFlag(UpdateFlag.AnimKits); + bool Rotation = flags.HasAnyFlag(UpdateFlag.Rotation); + bool HasAreaTrigger = flags.HasAnyFlag(UpdateFlag.Areatrigger); + bool HasGameObject = false; + bool ThisIsYou = flags.HasAnyFlag(UpdateFlag.Self); + bool SmoothPhasing = false; + bool SceneObjCreate = false; + bool PlayerCreateData = false; + int PauseTimesCount = 0; + + GameObject go = ToGameObject(); + if (go) + { + if (go.GetGoType() == GameObjectTypes.Transport) + PauseTimesCount = go.m_goValue.Transport.StopFrames.Count; + } + + data.WriteBit(NoBirthAnim); + data.WriteBit(EnablePortals); + data.WriteBit(PlayHoverAnim); + data.WriteBit(HasMovementUpdate); + data.WriteBit(HasMovementTransport); + data.WriteBit(Stationary); + data.WriteBit(CombatVictim); + data.WriteBit(ServerTime); + data.WriteBit(VehicleCreate); + data.WriteBit(AnimKitCreate); + data.WriteBit(Rotation); + data.WriteBit(HasAreaTrigger); + data.WriteBit(HasGameObject); + data.WriteBit(SmoothPhasing); + data.WriteBit(ThisIsYou); + data.WriteBit(SceneObjCreate); + data.WriteBit(PlayerCreateData); + data.FlushBits(); + + if (HasMovementUpdate) + { + Unit unit = ToUnit(); + bool HasFallDirection = unit.HasUnitMovementFlag(MovementFlag.Falling); + bool HasFall = HasFallDirection || unit.m_movementInfo.jump.fallTime != 0; + bool HasSpline = unit.IsSplineEnabled(); + + data.WritePackedGuid(GetGUID()); // MoverGUID + + data.WriteUInt32(unit.m_movementInfo.Time); // MoveTime + data.WriteFloat(unit.GetPositionX()); + data.WriteFloat(unit.GetPositionY()); + data.WriteFloat(unit.GetPositionZ()); + data.WriteFloat(unit.GetOrientation()); + + data.WriteFloat(unit.m_movementInfo.Pitch); // Pitch + data.WriteFloat(unit.m_movementInfo.SplineElevation); // StepUpStartElevation + + data.WriteUInt32(0); // RemoveForcesIDs.size() + data.WriteUInt32(0); // MoveIndex + + //for (public uint i = 0; i < RemoveForcesIDs.Count; ++i) + // *data << ObjectGuid(RemoveForcesIDs); + + data.WriteBits((uint)unit.GetUnitMovementFlags(), 30); + data.WriteBits((uint)unit.GetUnitMovementFlags2(), 18); + data.WriteBit(!unit.m_movementInfo.transport.guid.IsEmpty()); // HasTransport + data.WriteBit(HasFall); // HasFall + data.WriteBit(HasSpline); // HasSpline - marks that the unit uses spline movement + data.WriteBit(0); // HeightChangeFailed + data.WriteBit(0); // RemoteTimeValid + + if (!unit.m_movementInfo.transport.guid.IsEmpty()) + MovementExtensions.WriteTransportInfo(data, unit.m_movementInfo.transport); + + if (HasFall) + { + data.WriteUInt32(unit.m_movementInfo.jump.fallTime); // Time + data.WriteFloat(unit.m_movementInfo.jump.zspeed); // JumpVelocity + + if (data.WriteBit(HasFallDirection)) + { + data.WriteFloat(unit.m_movementInfo.jump.sinAngle); // Direction + data.WriteFloat(unit.m_movementInfo.jump.cosAngle); + data.WriteFloat(unit.m_movementInfo.jump.xyspeed); // Speed + } + } + + data.WriteFloat(unit.GetSpeed(UnitMoveType.Walk)); + data.WriteFloat(unit.GetSpeed(UnitMoveType.Run)); + data.WriteFloat(unit.GetSpeed(UnitMoveType.RunBack)); + data.WriteFloat(unit.GetSpeed(UnitMoveType.Swim)); + data.WriteFloat(unit.GetSpeed(UnitMoveType.SwimBack)); + data.WriteFloat(unit.GetSpeed(UnitMoveType.Flight)); + data.WriteFloat(unit.GetSpeed(UnitMoveType.FlightBack)); + data.WriteFloat(unit.GetSpeed(UnitMoveType.TurnRate)); + data.WriteFloat(unit.GetSpeed(UnitMoveType.PitchRate)); + + data.WriteUInt32(0); // unit.m_movementInfo.forces.size() + + data.WriteBit(HasSpline); + data.FlushBits(); + + //for (public uint i = 0; i < unit.m_movementInfo.forces.Count; ++i) + //{ + // *data << ObjectGuid(ID); + // *data << Vector3(Origin); + // *data << Vector3(Direction); + // *data << uint32(TransportID); + // *data.WriteFloat(Magnitude); + // *data.WriteBits(Type, 2); + //} + + // HasMovementSpline - marks that spline data is present in packet + if (HasSpline) + MovementExtensions.WriteCreateObjectSplineDataBlock(unit.moveSpline, data); + } + + data.WriteUInt32(PauseTimesCount); + + if (Stationary) + { + WorldObject self = this; + data.WriteFloat(self.GetStationaryX()); + data.WriteFloat(self.GetStationaryY()); + data.WriteFloat(self.GetStationaryZ()); + data.WriteFloat(self.GetStationaryO()); + } + + if (CombatVictim) + data.WritePackedGuid(ToUnit().GetVictim().GetGUID()); // CombatVictim + + if (ServerTime) + { + GameObject go1 = ToGameObject(); + /** @TODO Use IsTransport() to also handle type 11 (TRANSPORT) + Currently grid objects are not updated if there are no nearby players, + this causes clients to receive different PathProgress + resulting in players seeing the object in a different position + */ + if (go1 && go1.ToTransport()) // ServerTime + data.WriteUInt32(go1.m_goValue.Transport.PathProgress); + else + data.WriteUInt32(Time.GetMSTime()); + } + + if (VehicleCreate) + { + Unit unit = ToUnit(); + data.WriteUInt32(unit.GetVehicleKit().GetVehicleInfo().Id); // RecID + data.WriteFloat(unit.GetOrientation()); // InitialRawFacing + } + + if (AnimKitCreate) + { + data.WriteUInt16(GetAIAnimKitId()); // AiID + data.WriteUInt16(GetMovementAnimKitId()); // MovementID + data.WriteUInt16(GetMeleeAnimKitId()); // MeleeID + } + + if (Rotation) + data.WriteInt64(ToGameObject().GetPackedWorldRotation()); // Rotation + + if (go) + { + for (int i = 0; i < PauseTimesCount; ++i) + data.WriteUInt32(go.m_goValue.Transport.StopFrames[i]); + } + + if (HasMovementTransport) + { + WorldObject self = this; + MovementExtensions.WriteTransportInfo(data, self.m_movementInfo.transport); + } + + if (HasAreaTrigger) + { + AreaTrigger areaTrigger = ToAreaTrigger(); + AreaTriggerMiscTemplate areaTriggerMiscTemplate = areaTrigger.GetMiscTemplate(); + AreaTriggerTemplate areaTriggerTemplate = areaTrigger.GetTemplate(); + + data.WriteUInt32(areaTrigger.GetTimeSinceCreated()); + + data.WriteVector3(areaTrigger.GetRollPitchYaw()); + + bool hasAbsoluteOrientation = areaTriggerTemplate.HasFlag(AreaTriggerFlags.HasAbsoluteOrientation); + bool hasDynamicShape = areaTriggerTemplate.HasFlag(AreaTriggerFlags.HasDynamicShape); + bool hasAttached = areaTriggerTemplate.HasFlag(AreaTriggerFlags.HasAttached); + bool hasFaceMovementDir = areaTriggerTemplate.HasFlag(AreaTriggerFlags.HasFaceMovementDir); + bool hasFollowsTerrain = areaTriggerTemplate.HasFlag(AreaTriggerFlags.HasFollowsTerrain); + bool hasUnk1 = areaTriggerTemplate.HasFlag(AreaTriggerFlags.Unk1); + bool hasTargetRollPitchYaw = areaTriggerTemplate.HasFlag(AreaTriggerFlags.HasTargetRollPitchYaw); + bool hasScaleCurveID = areaTriggerMiscTemplate.ScaleCurveId != 0; + bool hasMorphCurveID = areaTriggerMiscTemplate.MorphCurveId != 0; + bool hasFacingCurveID = areaTriggerMiscTemplate.FacingCurveId != 0; + bool hasMoveCurveID = areaTriggerMiscTemplate.MoveCurveId != 0; + bool hasUnk2 = areaTriggerTemplate.HasFlag(AreaTriggerFlags.Unk2); + bool hasUnk3 = areaTriggerTemplate.HasFlag(AreaTriggerFlags.Unk3); + bool hasUnk4 = areaTriggerTemplate.HasFlag(AreaTriggerFlags.Unk4); + bool hasAreaTriggerSphere = areaTriggerTemplate.IsSphere(); + bool hasAreaTriggerBox = areaTriggerTemplate.IsBox(); + bool hasAreaTriggerPolygon = areaTriggerTemplate.IsPolygon(); + bool hasAreaTriggerCylinder = areaTriggerTemplate.IsCylinder(); + bool hasAreaTriggerSpline = areaTrigger.HasSplines(); + bool hasAreaTriggerUnkType = false; // areaTriggerTemplate.HasFlag(AREATRIGGER_FLAG_UNK5); + + data.WriteBit(hasAbsoluteOrientation); + data.WriteBit(hasDynamicShape); + data.WriteBit(hasAttached); + data.WriteBit(hasFaceMovementDir); + data.WriteBit(hasFollowsTerrain); + data.WriteBit(hasUnk1); + data.WriteBit(hasTargetRollPitchYaw); + data.WriteBit(hasScaleCurveID); + data.WriteBit(hasMorphCurveID); + data.WriteBit(hasFacingCurveID); + data.WriteBit(hasMoveCurveID); + data.WriteBit(hasUnk2); + data.WriteBit(hasUnk3); + data.WriteBit(hasUnk4); + data.WriteBit(hasAreaTriggerSphere); + data.WriteBit(hasAreaTriggerBox); + data.WriteBit(hasAreaTriggerPolygon); + data.WriteBit(hasAreaTriggerCylinder); + data.WriteBit(hasAreaTriggerSpline); + data.WriteBit(hasAreaTriggerUnkType); + + if (hasUnk3) + data.WriteBit(0); + + data.FlushBits(); + + if (hasAreaTriggerSpline) + { + data.WriteUInt32(areaTrigger.GetTimeToTarget()); + data.WriteUInt32(areaTrigger.GetElapsedTimeForMovement()); + + MovementExtensions.WriteCreateObjectAreaTriggerSpline(areaTrigger.GetSpline(), data); + } + + if (hasTargetRollPitchYaw) + data.WriteVector3(areaTrigger.GetTargetRollPitchYaw()); + + if (hasScaleCurveID) + data.WriteUInt32(areaTriggerMiscTemplate.ScaleCurveId); + + if (hasMorphCurveID) + data.WriteUInt32(areaTriggerMiscTemplate.MorphCurveId); + + if (hasFacingCurveID) + data.WriteUInt32(areaTriggerMiscTemplate.FacingCurveId); + + if (hasMoveCurveID) + data.WriteUInt32(areaTriggerMiscTemplate.MoveCurveId); + + if (hasUnk2) + data.WriteInt32(0); + + if (hasUnk4) + data.WriteUInt32(0); + + if (hasAreaTriggerSphere) + { + data.WriteFloat(areaTriggerTemplate.SphereDatas.Radius); + data.WriteFloat(areaTriggerTemplate.SphereDatas.RadiusTarget); + } + + if (hasAreaTriggerBox) + { + unsafe + { + fixed (float* ptr = areaTriggerTemplate.BoxDatas.Extents) + { + data.WriteFloat(ptr[0]); + data.WriteFloat(ptr[1]); + data.WriteFloat(ptr[2]); + } + + fixed (float* ptr = areaTriggerTemplate.BoxDatas.ExtentsTarget) + { + data.WriteFloat(ptr[0]); + data.WriteFloat(ptr[1]); + data.WriteFloat(ptr[2]); + } + } + } + + if (hasAreaTriggerPolygon) + { + data.WriteInt32(areaTriggerTemplate.PolygonVertices.Count); + data.WriteInt32(areaTriggerTemplate.PolygonVerticesTarget.Count); + data.WriteFloat(areaTriggerTemplate.PolygonDatas.Height); + data.WriteFloat(areaTriggerTemplate.PolygonDatas.HeightTarget); + + foreach (var vertice in areaTriggerTemplate.PolygonVertices) + data.WriteVector2(vertice); + + foreach (var vertice in areaTriggerTemplate.PolygonVerticesTarget) + data.WriteVector2(vertice); + } + + if (hasAreaTriggerCylinder) + { + data.WriteFloat(areaTriggerTemplate.CylinderDatas.Radius); + data.WriteFloat(areaTriggerTemplate.CylinderDatas.RadiusTarget); + data.WriteFloat(areaTriggerTemplate.CylinderDatas.Height); + data.WriteFloat(areaTriggerTemplate.CylinderDatas.HeightTarget); + data.WriteFloat(areaTriggerTemplate.CylinderDatas.LocationZOffset); + data.WriteFloat(areaTriggerTemplate.CylinderDatas.LocationZOffsetTarget); + } + + if (hasAreaTriggerUnkType) + { + /*packet.ResetBitReader(); + var unk1 = packet.ReadBit("AreaTriggerUnk1"); + var hasCenter = packet.ReadBit("HasCenter", index); + packet.ReadBit("Unk bit 703 1", index); + packet.ReadBit("Unk bit 703 2", index); + + packet.ReadUInt32(); + packet.ReadInt32(); + packet.ReadUInt32(); + packet.ReadSingle("Radius", index); + packet.ReadSingle("BlendFromRadius", index); + packet.ReadSingle("InitialAngel", index); + packet.ReadSingle("ZOffset", index); + + if (unk1) + packet.ReadPackedGuid128("AreaTriggerUnkGUID", index); + + if (hasCenter) + packet.ReadVector3("Center", index);*/ + } + } + + //if (GameObject) + //{ + // *data << uint32(WorldEffectID); + + // data.WriteBit(bit8); + // if (bit8) + // *data << uint32(Int1); + //} + + //if (SmoothPhasing) + //{ + // data.WriteBit(ReplaceActive); + // data.WriteBit(HasReplaceObjectt); + // if (HasReplaceObject) + // *data << ObjectGuid(ReplaceObject); + //} + + //if (SceneObjCreate) + //{ + // data.WriteBit(HasLocalScriptData); + // data.WriteBit(HasPetBattleFullUpdate); + + // if (HasLocalScriptData) + // { + // data.WriteBits(Data.length(), 7); + // data.WriteString(Data); + // } + + // if (HasPetBattleFullUpdate) + // { + // for (std::size_t i = 0; i < 2; ++i) + // { + // *data << ObjectGuid(Players[i].CharacterID); + // *data << int32(Players[i].TrapAbilityID); + // *data << int32(Players[i].TrapStatus); + // *data << uint16(Players[i].RoundTimeSecs); + // *data << int8(Players[i].FrontPet); + // *data << uint8(Players[i].InputFlags); + + // data.WriteBits(Players[i].Pets.size(), 2); + // for (std::size_t j = 0; j < Players[i].Pets.size(); ++j) + // { + // *data << ObjectGuid(Players[i].Pets[j].BattlePetGUID); + // *data << int32(Players[i].Pets[j].SpeciesID); + // *data << int32(Players[i].Pets[j].DisplayID); + // *data << int32(Players[i].Pets[j].CollarID); + // *data << int16(Players[i].Pets[j].Level); + // *data << int16(Players[i].Pets[j].Xp); + // *data << int32(Players[i].Pets[j].CurHealth); + // *data << int32(Players[i].Pets[j].MaxHealth); + // *data << int32(Players[i].Pets[j].Power); + // *data << int32(Players[i].Pets[j].Speed); + // *data << int32(Players[i].Pets[j].NpcTeamMemberID); + // *data << uint16(Players[i].Pets[j].BreedQuality); + // *data << uint16(Players[i].Pets[j].StatusFlags); + // *data << int8(Players[i].Pets[j].Slot); + + // *data << uint32(Players[i].Pets[j].Abilities.size()); + // *data << uint32(Players[i].Pets[j].Auras.size()); + // *data << uint32(Players[i].Pets[j].States.size()); + // for (std::size_t k = 0; k < Players[i].Pets[j].Abilities.size(); ++k) + // { + // *data << int32(Players[i].Pets[j].Abilities[k].AbilityID); + // *data << int16(Players[i].Pets[j].Abilities[k].CooldownRemaining); + // *data << int16(Players[i].Pets[j].Abilities[k].LockdownRemaining); + // *data << int8(Players[i].Pets[j].Abilities[k].AbilityIndex); + // *data << uint8(Players[i].Pets[j].Abilities[k].Pboid); + // } + + // for (std::size_t k = 0; k < Players[i].Pets[j].Auras.size(); ++k) + // { + // *data << int32(Players[i].Pets[j].Auras[k].AbilityID); + // *data << uint32(Players[i].Pets[j].Auras[k].InstanceID); + // *data << int32(Players[i].Pets[j].Auras[k].RoundsRemaining); + // *data << int32(Players[i].Pets[j].Auras[k].CurrentRound); + // *data << uint8(Players[i].Pets[j].Auras[k].CasterPBOID); + // } + + // for (std::size_t k = 0; k < Players[i].Pets[j].States.size(); ++k) + // { + // *data << uint32(Players[i].Pets[j].States[k].StateID); + // *data << int32(Players[i].Pets[j].States[k].StateValue); + // } + + // data.WriteBits(Players[i].Pets[j].CustomName.length(), 7); + // data.WriteString(Players[i].Pets[j].CustomName); + // } + // } + + // for (std::size_t i = 0; i < 3; ++i) + // { + // *data << uint32(Enviros[j].Auras.size()); + // *data << uint32(Enviros[j].States.size()); + // for (std::size_t j = 0; j < Enviros[j].Auras.size(); ++j) + // { + // *data << int32(Enviros[j].Auras[j].AbilityID); + // *data << uint32(Enviros[j].Auras[j].InstanceID); + // *data << int32(Enviros[j].Auras[j].RoundsRemaining); + // *data << int32(Enviros[j].Auras[j].CurrentRound); + // *data << uint8(Enviros[j].Auras[j].CasterPBOID); + // } + + // for (std::size_t j = 0; j < Enviros[j].States.size(); ++j) + // { + // *data << uint32(Enviros[i].States[j].StateID); + // *data << int32(Enviros[i].States[j].StateValue); + // } + // } + + // *data << uint16(WaitingForFrontPetsMaxSecs); + // *data << uint16(PvpMaxRoundTime); + // *data << int32(CurRound); + // *data << uint32(NpcCreatureID); + // *data << uint32(NpcDisplayID); + // *data << int8(CurPetBattleState); + // *data << uint8(ForfeitPenalty); + // *data << ObjectGuid(InitialWildPetGUID); + // data.WriteBit(IsPVP); + // data.WriteBit(CanAwardXP); + // } + //} + + //if (PlayerCreateData) + //{ + // data.WriteBit(HasSceneInstanceIDs); + // data.WriteBit(HasRuneState); + // if (HasSceneInstanceIDs) + // { + // *data << uint32(SceneInstanceIDs.size()); + // for (std::size_t i = 0; i < SceneInstanceIDs.size(); ++i) + // *data << uint32(SceneInstanceIDs[i]); + // } + // if (HasRuneState) + // { + // *data << uint8(RechargingRuneMask); + // *data << uint8(UsableRuneMask); + // *data << uint32(ToUnit().GetMaxPower(POWER_RUNES)); + // for (uint32 i = 0; i < ToUnit().GetMaxPower(POWER_RUNES); ++i) + // *data << uint8(255 - (ToUnit().ToPlayer().GetRuneCooldown(i) * 51)); + // } + //} + } + + public virtual void BuildValuesUpdate(UpdateType updatetype, ByteBuffer data, Player target) + { + if (!target) + return; + + ByteBuffer fieldBuffer = new ByteBuffer(); + UpdateMask updateMask = new UpdateMask(valuesCount); + + uint[] flags; + uint visibleFlag = GetUpdateFieldData(target, out flags); + for (int index = 0; index < valuesCount; ++index) + { + if (Convert.ToBoolean(_fieldNotifyFlags & flags[index]) || + ((updatetype == UpdateType.Values ? _changesMask.Get(index) : HasUpdateValue(index)) && Convert.ToBoolean(flags[index] & visibleFlag))) + { + updateMask.SetBit(index); + WriteValue(fieldBuffer, index); + } + } + + updateMask.AppendToPacket(data); + data.WriteBytes(fieldBuffer); + } + + public virtual void BuildDynamicValuesUpdate(UpdateType updateType, WorldPacket data, Player target) + { + if (!target) + return; + + ByteBuffer fieldBuffer = new ByteBuffer(); + UpdateMask fieldMask = new UpdateMask(_dynamicValuesCount); + + uint[] flags; + uint visibleFlag = GetDynamicUpdateFieldData(target, out flags); + + for (var index = 0; index < _dynamicValuesCount; ++index) + { + ByteBuffer valueBuffer = new ByteBuffer(); + var values = _dynamicValues[index]; + if (_fieldNotifyFlags.HasAnyFlag(flags[index]) || + ((updateType == UpdateType.Values ? _dynamicChangesMask[index] != DynamicFieldChangeType.Unchanged : !values.Empty()) && flags[index].HasAnyFlag(visibleFlag))) + { + fieldMask.SetBit(index); + + DynamicUpdateMask arrayMask = new DynamicUpdateMask((uint)(!values.Empty() ? values.Keys.Max() + 1 : 0)); + + arrayMask.EncodeDynamicFieldChangeType(_dynamicChangesMask[index], updateType); + if (updateType == UpdateType.Values && _dynamicChangesMask[index] == DynamicFieldChangeType.ValueAndSizeChanged) + arrayMask.ValueCount = values.Keys.Max() + 1; + + foreach (var pair in values) + { + if (updateType != UpdateType.Values || _dynamicChangesArrayMask[index].Get(pair.Key)) + { + arrayMask.SetBit(pair.Key); + valueBuffer.WriteUInt32(pair.Value); + } + } + + arrayMask.AppendToPacket(fieldBuffer); + fieldBuffer.WriteBytes(valueBuffer); + } + } + + fieldMask.AppendToPacket(data); + data.WriteBytes(fieldBuffer); + } + + public void WriteValue(ByteBuffer data, int index) + { + switch (UpdateData[index].GetType().Name) + { + default: + case "UInt32": + data.WriteUInt32(UpdateData[index]); + break; + case "Single": + data.WriteFloat((float)UpdateData[index]); + break; + case "Int32": + data.WriteInt32((int)UpdateData[index]); + break; + } + } + + public bool HasUpdateValue(int index) + { + return UpdateData.ContainsKey(index); + } + + public void AddToObjectUpdateIfNeeded() + { + if (IsInWorld && !m_objectUpdated) + { + AddToObjectUpdate(); + m_objectUpdated = true; + } + } + + public void ClearUpdateMask(bool remove) + { + _changesMask.SetAll(false); + for (int i = 0; i < _dynamicValuesCount; ++i) + { + _dynamicChangesMask[i] = DynamicFieldChangeType.Unchanged; + _dynamicChangesArrayMask[i].SetAll(false); + } + + if (m_objectUpdated) + { + if (remove) + RemoveFromObjectUpdate(); + + m_objectUpdated = false; + } + } + + public void BuildFieldsUpdate(Player player, Dictionary data_map) + { + var data = data_map.LookupByKey(player); + + if (data == null) + { + data_map.Add(player, new UpdateData(player.GetMapId())); + data = data_map.LookupByKey(player); + } + + BuildValuesUpdateBlockForPlayer(data, player); + } + + uint GetUpdateFieldData(Player target, out uint[] flags) + { + var visibleFlag = UpdateFieldFlags.Public; + flags = null; + + if (target == this) + visibleFlag |= UpdateFieldFlags.Private; + + switch (GetTypeId()) + { + case TypeId.Item: + case TypeId.Container: + flags = UpdateFieldFlags.ItemUpdateFieldFlags; + if (((Item)this).GetOwnerGUID() == target.GetGUID()) + visibleFlag |= UpdateFieldFlags.Owner | UpdateFieldFlags.ItemOwner; + break; + case TypeId.Unit: + case TypeId.Player: + { + Player plr = ToUnit().GetCharmerOrOwnerPlayerOrPlayerItself(); + flags = UpdateFieldFlags.UnitUpdateFieldFlags; + if (ToUnit().GetOwnerGUID() == target.GetGUID()) + visibleFlag |= UpdateFieldFlags.Owner; + + if (HasFlag(ObjectFields.DynamicFlags, UnitDynFlags.SpecialInfo)) + if (ToUnit().HasAuraTypeWithCaster(AuraType.Empathy, target.GetGUID())) + visibleFlag |= UpdateFieldFlags.SpecialInfo; + + if (plr != null && plr.IsInSameGroupWith(target)) + visibleFlag |= UpdateFieldFlags.PartyMember; + break; + } + case TypeId.GameObject: + flags = UpdateFieldFlags.GameObjectUpdateFieldFlags; + if (ToGameObject().GetOwnerGUID() == target.GetGUID()) + visibleFlag |= UpdateFieldFlags.Owner; + break; + case TypeId.DynamicObject: + flags = UpdateFieldFlags.DynamicObjectUpdateFieldFlags; + if (ToDynamicObject().GetCasterGUID() == target.GetGUID()) + visibleFlag |= UpdateFieldFlags.Owner; + break; + case TypeId.Corpse: + flags = UpdateFieldFlags.CorpseUpdateFieldFlags; + if (ToCorpse().GetOwnerGUID() == target.GetGUID()) + visibleFlag |= UpdateFieldFlags.Owner; + break; + case TypeId.AreaTrigger: + flags = UpdateFieldFlags.AreaTriggerUpdateFieldFlags; + break; + case TypeId.SceneObject: + flags = UpdateFieldFlags.SceneObjectUpdateFieldFlags; + break; + case TypeId.Conversation: + flags = UpdateFieldFlags.ConversationUpdateFieldFlags; + break; + case TypeId.Object: + Contract.Assert(false); + break; + } + return visibleFlag; + } + + public uint GetDynamicUpdateFieldData(Player target, out uint[] flags) + { + uint visibleFlag = UpdateFieldFlags.Public; + + if (target == this) + visibleFlag |= UpdateFieldFlags.Private; + + switch (GetTypeId()) + { + case TypeId.Item: + case TypeId.Container: + flags = UpdateFieldFlags.ItemDynamicUpdateFieldFlags; + if (((Item)this).GetOwnerGUID() == target.GetGUID()) + visibleFlag |= UpdateFieldFlags.Owner | UpdateFieldFlags.ItemOwner; + break; + case TypeId.Unit: + case TypeId.Player: + { + Player plr = ToUnit().GetCharmerOrOwnerPlayerOrPlayerItself(); + flags = UpdateFieldFlags.UnitDynamicUpdateFieldFlags; + if (ToUnit().GetOwnerGUID() == target.GetGUID()) + visibleFlag |= UpdateFieldFlags.Owner; + + if (HasFlag(ObjectFields.DynamicFlags, UnitDynFlags.SpecialInfo)) + if (ToUnit().HasAuraTypeWithCaster(AuraType.Empathy, target.GetGUID())) + visibleFlag |= UpdateFieldFlags.SpecialInfo; + + if (plr && plr.IsInSameRaidWith(target)) + visibleFlag |= UpdateFieldFlags.PartyMember; + break; + } + case TypeId.GameObject: + flags = UpdateFieldFlags.GameObjectDynamicUpdateFieldFlags; + break; + case TypeId.Conversation: + flags = UpdateFieldFlags.ConversationDynamicUpdateFieldFlags; + + if (ToConversation().GetCreatorGuid() == target.GetGUID()) + visibleFlag |= UpdateFieldFlags.Unknownx100; + break; + default: + flags = null; + break; + } + + return visibleFlag; + } + + public void _LoadIntoDataField(string data, uint startOffset, uint count) + { + if (string.IsNullOrEmpty(data)) + return; + + var lines = new StringArray(data, ' '); + if (lines.Length != count) + return; + + for (var index = 0; index < count; ++index) + { + UpdateData[(int)startOffset + index] = uint.Parse(lines[index]); + _changesMask.Set((int)(startOffset + index), true); + } + } + + public void SetInt32Value(object index, int value) + { + Contract.Assert((int)index < valuesCount || PrintIndexError(index, true)); + + if (Convert.ToInt32(UpdateData[(int)index]) != value) + { + UpdateData[(int)index] = value; + _changesMask.Set((int)index, true); + + AddToObjectUpdateIfNeeded(); + } + } + + public void SetUInt32Value(object index, uint value) + { + int _index = Convert.ToInt32(index); + Contract.Assert(_index < valuesCount || PrintIndexError(index, true)); + + if (Convert.ToUInt32(UpdateData[_index]) != value) + { + UpdateData[_index] = value; + _changesMask.Set(_index, true); + + AddToObjectUpdateIfNeeded(); + } + } + + public void UpdateUInt32Value(object index, uint value) + { + UpdateData[(int)index] = value; + _changesMask.Set((int)index, true); + } + + public void SetUInt64Value(object index, ulong value) + { + Contract.Assert((int)index + 1 < valuesCount || PrintIndexError(index, true)); + if (GetUInt64Value(index) != value) + { + UpdateData[(int)index] = MathFunctions.Pair64_LoPart(value); + UpdateData[(int)index + 1] = MathFunctions.Pair64_HiPart(value); + _changesMask.Set((int)index, true); + _changesMask.Set((int)index + 1, true); + + AddToObjectUpdateIfNeeded(); + } + } + + public bool AddUInt64Value(object index, ulong value) + { + if (value != 0 && GetUInt64Value(index) == 0) + { + SetUInt64Value(index, value); + return true; + } + + return false; + } + + public bool RemoveUInt64Value(object index, ulong value) + { + if (value != 0 && GetUInt64Value(index) == value) + { + SetUInt64Value(index, 0); + return true; + } + + return false; + } + + public bool AddGuidValue(object index, ObjectGuid value) + { + if (!value.IsEmpty() && GetGuidValue(index).IsEmpty()) + { + SetGuidValue(index, value); + return true; + } + + return false; + } + + public bool RemoveGuidValue(object index, ObjectGuid value) + { + if (!value.IsEmpty() && GetGuidValue(index) == value) + { + SetGuidValue(index, ObjectGuid.Empty); + return true; + } + + return false; + } + + public void SetFloatValue(object index, float value) + { + Contract.Assert((int)index < valuesCount || PrintIndexError(index, true)); + + if (GetFloatValue(index) != value) + { + UpdateData[(int)index] = value; + _changesMask.Set((int)index, true); + + AddToObjectUpdateIfNeeded(); + } + } + + public void SetByteValue(object index, byte offset, byte value) + { + Contract.Assert((int)index < valuesCount || PrintIndexError(index, true)); + + if (offset > 3) + { + Log.outError(LogFilter.Server, "Object.SetByteValue: wrong offset {0}", offset); + return; + } + + if ((byte)(GetUInt32Value(index) >> (offset * 8)) != value) + { + UpdateData[(int)index] = (uint)UpdateData[(int)index] & ~(uint)(0xFF << (offset * 8)); + UpdateData[(int)index] = (uint)UpdateData[(int)index] | (uint)value << (offset * 8); + _changesMask.Set((int)index, true); + + AddToObjectUpdateIfNeeded(); + } + } + + public void SetUInt16Value(object index, byte offset, ushort value) + { + Contract.Assert((int)index < valuesCount || PrintIndexError(index, true)); + + if (offset > 2) + { + Log.outError(LogFilter.Server, "WorldObject.SetUInt16Value: wrong offset {0}", offset); + return; + } + + if ((ushort)(GetUInt32Value(index) >> (offset * 16)) != value) + { + UpdateData[(int)index] = (uint)UpdateData[(int)index] & ~((uint)0xFFFF << (offset * 16)); + UpdateData[(int)index] = (uint)UpdateData[(int)index] | (uint)value << (offset * 16); + _changesMask.Set((int)index, true); + + AddToObjectUpdateIfNeeded(); + } + } + + public void SetGuidValue(object index, ObjectGuid value) + { + Contract.Assert((int)index + 3 < valuesCount || PrintIndexError(index, true)); + if (new ObjectGuid(GetUInt64Value((int)index + 2), GetUInt64Value(index)) != value) + { + SetUInt64Value(index, value.GetLowValue()); + SetUInt64Value((int)index + 2, value.GetHighValue()); + + AddToObjectUpdateIfNeeded(); + } + } + + public void SetStatFloatValue(object index, float value) + { + if (value < 0) + value = 0.0f; + + SetFloatValue(index, value); + } + + public void SetStatInt32Value(object index, int value) + { + if (value < 0) + value = 0; + + SetUInt32Value(index, (uint)value); + } + + public void ApplyModInt32Value(object index, int val, bool apply) + { + int cur = GetInt32Value(index); + cur += (apply ? val : -val); + if (cur < 0) + cur = 0; + SetInt32Value(index, cur); + } + + public void ApplyModUInt32Value(object index, int val, bool apply) + { + int cur = (int)GetUInt32Value(index); + cur += (apply ? val : -val); + if (cur < 0) + cur = 0; + SetUInt32Value(index, (uint)cur); + } + + public void ApplyModUInt16Value(object index, byte offset, short val, bool apply) + { + short cur = (short)GetUInt16Value(index, offset); + cur += (short)(apply ? val : -val); + if (cur < 0) + cur = 0; + SetUInt16Value(index, offset, (ushort)cur); + } + + public void ApplyModSignedFloatValue(object index, float val, bool apply) + { + float cur = GetFloatValue(index); + cur += (apply ? val : -val); + SetFloatValue(index, cur); + } + + public void ApplyPercentModFloatValue(object index, float val, bool apply) + { + float value = GetFloatValue(index); + MathFunctions.ApplyPercentModFloatVar(ref value, val, apply); + SetFloatValue(index, value); + } + + public void SetFlag(object index, object newflag) + { + Contract.Assert((int)index < valuesCount || PrintIndexError(index, true)); + uint oldval = (uint)UpdateData[(int)index]; + uint newval = oldval | Convert.ToUInt32(newflag); + + if (oldval != newval) + { + UpdateData[(int)index] = newval; + _changesMask.Set((int)index, true); + + AddToObjectUpdateIfNeeded(); + } + } + + public void RemoveFlag(object index, object oldFlag) + { + Contract.Assert((int)index < valuesCount || PrintIndexError(index, true)); + Contract.Assert(UpdateData != null); + + uint oldval = (uint)UpdateData[(int)index]; + uint newval = (oldval & ~Convert.ToUInt32(oldFlag)); + + if (oldval != newval) + { + UpdateData[(int)index] = newval; + _changesMask.Set((int)index, true); + AddToObjectUpdateIfNeeded(); + } + } + + public void ToggleFlag(object index, object flag) + { + if (HasFlag(index, flag)) + RemoveFlag(index, flag); + else + SetFlag(index, flag); + } + + public bool HasFlag(object index, object flag) + { + if ((int)index >= valuesCount && !PrintIndexError(index, false)) + return false; + + return (GetUInt32Value(index) & Convert.ToUInt32(flag)) != 0; + } + + public void ApplyModFlag(object index, T flag, bool apply) + { + if (apply) + SetFlag(index, flag); + else + RemoveFlag(index, flag); + } + + public void SetByteFlag(object index, byte offset, object newFlag) + { + Contract.Assert((int)index < valuesCount || PrintIndexError(index, true)); + + if (offset > 4) + { + Log.outError(LogFilter.Server, "Object.SetByteFlag: wrong offset {0}", offset); + return; + } + + if (!Convert.ToBoolean((uint)UpdateData[(int)index] >> (offset * 8) & Convert.ToUInt32(newFlag))) + { + UpdateData[(int)index] = (uint)UpdateData[(int)index] | Convert.ToUInt32(newFlag) << (offset * 8); + _changesMask.Set((int)index, true); + + AddToObjectUpdateIfNeeded(); + } + } + + public void RemoveByteFlag(object index, byte offset, object oldFlag) + { + Contract.Assert((int)index < valuesCount || PrintIndexError(index, true)); + + if (offset > 4) + { + Log.outError(LogFilter.Server, "Object.RemoveByteFlag: wrong offset {0}", offset); + return; + } + + if (Convert.ToBoolean((uint)UpdateData[(int)index] >> (offset * 8) & Convert.ToUInt32(oldFlag))) + { + UpdateData[(int)index] = (uint)UpdateData[(int)index] & ~(Convert.ToUInt32(oldFlag) << (offset * 8)); + _changesMask.Set((int)index, true); + + AddToObjectUpdateIfNeeded(); + } + } + + public void ToggleByteFlag(object index, byte offset, byte flag) + { + if (HasByteFlag(index, offset, flag)) + RemoveByteFlag(index, offset, flag); + else + SetByteFlag(index, offset, flag); + } + + public bool HasByteFlag(object index, byte offset, object flag) + { + Contract.Assert((int)index < valuesCount || PrintIndexError(index, false)); + Contract.Assert(offset < 4); + return Convert.ToBoolean((uint)UpdateData[(int)index] >> (offset * 8) & Convert.ToUInt32(flag)); + } + + public void SetFlag64(object index, object newFlag) + { + ulong oldval = GetUInt64Value(index); + ulong newval = oldval | Convert.ToUInt64(newFlag); + SetUInt64Value(index, newval); + } + + public void RemoveFlag64(object index, object oldFlag) + { + ulong oldval = GetUInt64Value(index); + ulong newval = oldval & ~Convert.ToUInt64(oldFlag); + SetUInt64Value(index, newval); + } + + public void ToggleFlag64(object index, object flag) + { + if (HasFlag64(index, flag)) + RemoveFlag64(index, flag); + else + SetFlag64(index, flag); + } + + public bool HasFlag64(object index, object flag) + { + Contract.Assert((int)index < valuesCount || PrintIndexError(index, false)); + return (GetUInt64Value(index) & Convert.ToUInt64(flag)) != 0; + } + + void ApplyModFlag64(object index, ulong flag, bool apply) + { + if (apply) + SetFlag64(index, flag); + else + RemoveFlag64(index, flag); + } + + public Dictionary GetDynamicKeyAndValues(object index) + { + Contract.Assert((int)index < _dynamicValuesCount || PrintIndexError(index, false)); + return _dynamicValues[(int)index]; + } + + public ICollection GetDynamicValues(object index) + { + Contract.Assert((int)index < _dynamicValuesCount || PrintIndexError(index, false)); + return _dynamicValues[(int)index].Values; + } + + public uint GetDynamicValue(object index, ushort offset) + { + Contract.Assert((int)index < _dynamicValuesCount || PrintIndexError(index, false)); + if (!_dynamicValues[(int)index].ContainsKey(offset)) + return 0; + + return _dynamicValues[(int)index][offset]; + } + + public void AddDynamicValue(object index, uint value) + { + Contract.Assert((int)index < _dynamicValuesCount || PrintIndexError(index, true)); + + SetDynamicValue(index, (byte)_dynamicValues[(int)index].Count, value); + } + + public void RemoveDynamicValue(object index, uint value) + { + Contract.Assert((int)index < _dynamicValuesCount || PrintIndexError(index, false)); + + // TODO: Research if this is blizzlike to just set value to 0 + var values = _dynamicValues[(int)index]; + foreach (var pair in values.ToList()) + { + if (pair.Value == value) + { + values[pair.Key] = 0; + _dynamicChangesMask[(int)index] = DynamicFieldChangeType.ValueChanged; + _dynamicChangesArrayMask[(int)index].Set(pair.Key, true); + + AddToObjectUpdateIfNeeded(); + } + } + } + + public void ClearDynamicValue(object index) + { + Contract.Assert((int)index < _dynamicValuesCount || PrintIndexError(index, false)); + + if (!_dynamicValues[(int)index].Empty()) + { + _dynamicValues[(int)index].Clear(); + _dynamicChangesMask[(int)index] = DynamicFieldChangeType.ValueAndSizeChanged; + _dynamicChangesArrayMask[(int)index].SetAll(false); + + AddToObjectUpdateIfNeeded(); + } + } + + public void SetDynamicValue(object index, ushort offset, uint value) + { + Contract.Assert((int)index < _dynamicValuesCount || PrintIndexError(index, true)); + + DynamicFieldChangeType changeType = DynamicFieldChangeType.ValueChanged; + var values = _dynamicValues[(int)index]; + if (!values.ContainsKey(offset)) + { + values.Add(offset, 0u); + changeType = DynamicFieldChangeType.ValueAndSizeChanged; + } + + if (_dynamicChangesArrayMask[(int)index].Count <= offset) + _dynamicChangesArrayMask[(int)index].Length = offset + 1; + + if (values[offset] != value || changeType == DynamicFieldChangeType.ValueAndSizeChanged) + { + values[offset] = value; + _dynamicChangesMask[(int)index] = changeType; + _dynamicChangesArrayMask[(int)index].Set(offset, true); + + AddToObjectUpdateIfNeeded(); + } + } + + public List GetDynamicStructuredValues(object index) + { + var values = _dynamicValues[(int)index].Values; + return new List(values.DeserializeObjects()); + } + + public T GetDynamicStructuredValue(object index, ushort offset) + { + return GetDynamicStructuredValues(index)[offset]; + } + + public void SetDynamicStructuredValue(object index, ushort offset, T value) + { + int BlockCount = Marshal.SizeOf() / sizeof(uint); + SetDynamicValue(index, (ushort)((offset + 1) * BlockCount - 1), 0); // reserve space + + for (ushort i = 0; i < BlockCount; ++i) + SetDynamicValue(index, (ushort)(offset * BlockCount + i), Extensions.SerializeObject(value)[i]); + } + + public void AddDynamicStructuredValue(object index, T value) + { + int BlockCount = Marshal.SizeOf() / sizeof(uint); + var values = _dynamicValues[(int)index]; + ushort offset = (ushort)(values.Count / BlockCount); + SetDynamicValue(index, (ushort)((offset + 1) * BlockCount - 1), 0); // reserve space + for (ushort i = 0; i < BlockCount; ++i) + SetDynamicValue(index, (ushort)(offset * BlockCount + i), Extensions.SerializeObject(value)[i]); + } + + bool PrintIndexError(object index, bool set) + { + Log.outError(LogFilter.Server, "Attempt {0} non-existed value field: {1} (count: {2}) for object typeid: {3} type mask: {4}", (set ? "set value to" : "get value from"), index, valuesCount, GetTypeId(), objectTypeId); + + // ASSERT must fail after function call + return false; + } + + public bool IsWorldObject() + { + if (m_isWorldObject) + return true; + + if (IsTypeId(TypeId.Unit) && ToCreature().m_isTempWorldObject) + return true; + + return false; + } + public void SetWorldObject(bool on) + { + if (!IsInWorld) + return; + + GetMap().AddObjectToSwitchList(this, on); + } + public void setActive(bool on) + { + if (m_isActive == on) + return; + + if (IsTypeId(TypeId.Player)) + return; + + m_isActive = on; + + if (!IsInWorld) + return; + + Map map = GetMap(); + if (map == null) + return; + + if (on) + { + if (IsTypeId(TypeId.Unit)) + map.AddToActive(ToCreature()); + else if (IsTypeId(TypeId.DynamicObject)) + map.AddToActive(ToDynamicObject()); + } + else + { + if (IsTypeId(TypeId.Unit)) + map.RemoveFromActive(ToCreature()); + else if (IsTypeId(TypeId.DynamicObject)) + map.RemoveFromActive(ToDynamicObject()); + } + } + + public virtual void CleanupsBeforeDelete(bool finalCleanup = true) + { + if (IsInWorld) + RemoveFromWorld(); + + Transport transport = GetTransport(); + if (transport) + transport.RemovePassenger(this); + } + + public uint GetZoneId() + { + return GetMap().GetZoneId(GetPositionX(), GetPositionY(), GetPositionZ()); + } + + public uint GetAreaId() + { + return GetMap().GetAreaId(GetPositionX(), GetPositionY(), GetPositionZ()); + } + + public void GetZoneAndAreaId(out uint zoneid, out uint areaid) + { + GetMap().GetZoneAndAreaId(out zoneid, out areaid, posX, posY, posZ); + } + + public InstanceScript GetInstanceScript() + { + Map map = GetMap(); + return map.IsDungeon() ? ((InstanceMap)map).GetInstanceScript() : null; + } + + public float GetGridActivationRange() + { + if (IsTypeId(TypeId.Player)) + { + if (ToPlayer().GetCinematicMgr().IsOnCinematic()) + return SharedConst.DefaultVisibilityInstance; + + return GetMap().GetVisibilityRange(); + } + else if (IsTypeId(TypeId.Unit)) + return ToCreature().m_SightDistance; + else if (IsTypeId(TypeId.DynamicObject)) + { + if (isActiveObject()) + return GetMap().GetVisibilityRange(); + else + return 0.0f; + } + else + return 0.0f; + } + + public float GetVisibilityRange() + { + if (isActiveObject() && !IsTypeId(TypeId.Player)) + return SharedConst.MaxVisibilityDistance; + else + return GetMap().GetVisibilityRange(); + } + + public float GetSightRange(WorldObject target = null) + { + if (IsTypeId(TypeId.Player) || IsTypeId(TypeId.Unit)) + { + if (IsTypeId(TypeId.Player)) + { + if (target != null && target.isActiveObject() && target.ToPlayer() == null) + return SharedConst.MaxVisibilityDistance; + else if (ToPlayer().GetCinematicMgr().IsOnCinematic()) + return SharedConst.DefaultVisibilityInstance; + else + return GetMap().GetVisibilityRange(); + } + else if (IsTypeId(TypeId.Unit)) + return ToCreature().m_SightDistance; + else + return SharedConst.SightRangeUnit; + } + + if (IsTypeId(TypeId.DynamicObject) && isActiveObject()) + { + return GetMap().GetVisibilityRange(); + } + + return 0.0f; + } + + public bool CanSeeOrDetect(WorldObject obj, bool ignoreStealth = false, bool distanceCheck = false, bool checkAlert = false) + { + if (this == obj) + return true; + + if (obj.IsNeverVisibleFor(this) || CanNeverSee(obj)) + return false; + + if (obj.IsAlwaysVisibleFor(this) || CanAlwaysSee(obj)) + return true; + + bool corpseVisibility = false; + if (distanceCheck) + { + bool corpseCheck = false; + Player thisPlayer = ToPlayer(); + if (thisPlayer != null) + { + if (thisPlayer.IsDead() && thisPlayer.GetHealth() > 0 && // Cheap way to check for ghost state + !Convert.ToBoolean(obj.m_serverSideVisibility.GetValue(ServerSideVisibilityType.Ghost) & m_serverSideVisibility.GetValue(ServerSideVisibilityType.Ghost) & (uint)GhostVisibilityType.Ghost)) + { + Corpse corpse = thisPlayer.GetCorpse(); + if (corpse != null) + { + corpseCheck = true; + if (corpse.IsWithinDist(thisPlayer, GetSightRange(obj), false)) + if (corpse.IsWithinDist(obj, GetSightRange(obj), false)) + corpseVisibility = true; + } + } + } + + WorldObject viewpoint = this; + Player player = ToPlayer(); + if (player != null) + viewpoint = player.GetViewpoint(); + + if (viewpoint == null) + viewpoint = this; + + if (!corpseCheck && !viewpoint.IsWithinDist(obj, GetSightRange(obj), false)) + return false; + } + + // GM visibility off or hidden NPC + if (obj.m_serverSideVisibility.GetValue(ServerSideVisibilityType.GM) == 0) + { + // Stop checking other things for GMs + if (m_serverSideVisibilityDetect.GetValue(ServerSideVisibilityType.GM) != 0) + return true; + } + else + return m_serverSideVisibilityDetect.GetValue(ServerSideVisibilityType.GM) >= obj.m_serverSideVisibility.GetValue(ServerSideVisibilityType.GM); + + // Ghost players, Spirit Healers, and some other NPCs + if (!corpseVisibility && !Convert.ToBoolean(obj.m_serverSideVisibility.GetValue(ServerSideVisibilityType.Ghost) & m_serverSideVisibilityDetect.GetValue(ServerSideVisibilityType.Ghost))) + { + // Alive players can see dead players in some cases, but other objects can't do that + Player thisPlayer = ToPlayer(); + if (thisPlayer != null) + { + Player objPlayer = obj.ToPlayer(); + if (objPlayer != null) + { + if (thisPlayer.GetTeam() != objPlayer.GetTeam() || !thisPlayer.IsGroupVisibleFor(objPlayer)) + return false; + } + else + return false; + } + else + return false; + } + + if (obj.IsInvisibleDueToDespawn()) + return false; + + if (!CanDetect(obj, ignoreStealth, checkAlert)) + return false; + + return true; + } + + bool CanNeverSee(WorldObject obj) + { + return GetMap() != obj.GetMap() || !IsInPhase(obj); + } + + public virtual bool CanAlwaysSee(WorldObject obj) { return false; } + + bool CanDetect(WorldObject obj, bool ignoreStealth, bool checkAlert = false) + { + WorldObject seer = this; + + // Pets don't have detection, they use the detection of their masters + Unit thisUnit = ToUnit(); + if (thisUnit != null) + { + Unit controller = thisUnit.GetCharmerOrOwner(); + if (controller != null) + seer = controller; + } + + if (obj.IsAlwaysDetectableFor(seer)) + return true; + + if (!ignoreStealth && !seer.CanDetectInvisibilityOf(obj)) + return false; + + if (!ignoreStealth && !seer.CanDetectStealthOf(obj, checkAlert)) + return false; + + return true; + } + + bool CanDetectInvisibilityOf(WorldObject obj) + { + uint mask = obj.m_invisibility.GetFlags() & m_invisibilityDetect.GetFlags(); + + // Check for not detected types + if (mask != obj.m_invisibility.GetFlags()) + return false; + + for (int i = 0; i < (int)InvisibilityType.Max; ++i) + { + if (!Convert.ToBoolean(mask & (1 << i))) + continue; + + int objInvisibilityValue = obj.m_invisibility.GetValue((InvisibilityType)i); + int ownInvisibilityDetectValue = m_invisibilityDetect.GetValue((InvisibilityType)i); + + // Too low value to detect + if (ownInvisibilityDetectValue < objInvisibilityValue) + return false; + } + + return true; + } + + bool CanDetectStealthOf(WorldObject obj, bool checkAlert = false) + { + // Combat reach is the minimal distance (both in front and behind), + // and it is also used in the range calculation. + // One stealth point increases the visibility range by 0.3 yard. + + if (obj.m_stealth.GetFlags() == 0) + return true; + + float distance = GetExactDist(obj); + float combatReach = 0.0f; + + Unit unit = ToUnit(); + if (unit != null) + combatReach = unit.GetCombatReach(); + + if (distance < combatReach) + return true; + + if (!HasInArc(MathFunctions.PI, obj)) + return false; + + GameObject go = ToGameObject(); + for (int i = 0; i < (int)StealthType.Max; ++i) + { + if (!Convert.ToBoolean(obj.m_stealth.GetFlags() & (1 << i))) + continue; + + if (unit != null && unit.HasAuraTypeWithMiscvalue(AuraType.DetectStealth, i)) + return true; + + // Starting points + int detectionValue = 30; + + // Level difference: 5 point / level, starting from level 1. + // There may be spells for this and the starting points too, but + // not in the DBCs of the client. + detectionValue += (int)(GetLevelForTarget(obj) - 1) * 5; + + // Apply modifiers + detectionValue += m_stealthDetect.GetValue((StealthType)i); + if (go != null) + { + Unit owner = go.GetOwner(); + if (owner != null) + detectionValue -= (int)(owner.GetLevelForTarget(this) - 1) * 5; + } + + detectionValue -= obj.m_stealth.GetValue((StealthType)i); + + // Calculate max distance + float visibilityRange = detectionValue * 0.3f + combatReach; + + // If this unit is an NPC then player detect range doesn't apply + if (unit && unit.IsTypeId(TypeId.Player) && visibilityRange > SharedConst.MaxPlayerStealthDetectRange) + visibilityRange = SharedConst.MaxPlayerStealthDetectRange; + + // When checking for alert state, look 8% further, and then 1.5 yards more than that. + if (checkAlert) + visibilityRange += (visibilityRange * 0.08f) + 1.5f; + + // If checking for alert, and creature's visibility range is greater than aggro distance, No alert + Unit tunit = obj.ToUnit(); + if (checkAlert && unit && unit.ToCreature() && visibilityRange >= unit.ToCreature().GetAttackDistance(tunit) + unit.ToCreature().m_CombatDistance) + return false; + + if (distance > visibilityRange) + return false; + } + + return true; + } + + public void ForceValuesUpdateAtIndex(object _index) + { + int index = (int)_index; + _changesMask.Set(index, true); + + AddToObjectUpdateIfNeeded(); + } + + public virtual void SendMessageToSet(ServerPacket packet, bool self) + { + if (IsInWorld) + SendMessageToSetInRange(packet, GetVisibilityRange(), self); + } + + public virtual void SendMessageToSetInRange(ServerPacket data, float dist, bool self) + { + MessageDistDeliverer notifier = new MessageDistDeliverer(this, data, dist); + Cell.VisitWorldObjects(this, notifier, dist); + } + + public virtual void SendMessageToSet(ServerPacket data, Player skip) + { + var notifier = new MessageDistDeliverer(this, data, GetVisibilityRange(), false, skip); + Cell.VisitWorldObjects(this, notifier, GetVisibilityRange()); + } + + public virtual void SetMap(Map map) + { + Contract.Assert(map != null); + Contract.Assert(!IsInWorld); + + if (_currMap == map) + return; + + _currMap = map; + SetMapId(map.GetId()); + instanceId = map.GetInstanceId(); + if (IsWorldObject()) + _currMap.AddWorldObject(this); + } + + public virtual void ResetMap() + { + Contract.Assert(_currMap != null); + Contract.Assert(!IsInWorld); + if (IsWorldObject()) + _currMap.RemoveWorldObject(this); + _currMap = null; + } + + public Map GetMap() { return _currMap; } + + public void AddObjectToRemoveList() + { + Map map = GetMap(); + if (map == null) + { + Log.outError(LogFilter.Server, "Object (TypeId: {0} Entry: {1} GUID: {2}) at attempt add to move list not have valid map (Id: {3}).", GetTypeId(), GetEntry(), GetGUID().ToString(), GetMapId()); + return; + } + + map.AddObjectToRemoveList(this); + } + + public void SetZoneScript() + { + Map map = GetMap(); + if (map != null) + { + if (map.IsDungeon()) + m_zoneScript = ((InstanceMap)map).GetInstanceScript(); + else if (!map.IsBattlegroundOrArena()) + { + BattleField bf = Global.BattleFieldMgr.GetBattlefieldToZoneId(GetZoneId()); + if (bf != null) + m_zoneScript = bf; + else + m_zoneScript = Global.OutdoorPvPMgr.GetZoneScript(GetZoneId()); + } + } + } + + public Scenario GetScenario() + { + if (IsInWorld) + { + InstanceMap instanceMap = GetMap().ToInstanceMap(); + if (instanceMap != null) + return instanceMap.GetInstanceScenario(); + } + + return null; + } + + public TempSummon SummonCreature(uint id, float x, float y, float z, float ang = 0, TempSummonType spwtype = TempSummonType.ManualDespawn, uint despwtime = 0) + { + if (x == 0.0f && y == 0.0f && z == 0.0f) + { + GetClosePoint(out x, out y, out z, GetObjectSize()); + ang = GetOrientation(); + } + Position pos = new Position(); + pos.Relocate(x, y, z, ang); + return SummonCreature(id, pos, spwtype, despwtime, 0); + } + + public TempSummon SummonCreature(uint entry, Position pos, TempSummonType spwtype = TempSummonType.ManualDespawn, uint duration = 0, uint vehId = 0) + { + Map map = GetMap(); + if (map != null) + { + TempSummon summon = map.SummonCreature(entry, pos, null, duration, isTypeMask(TypeMask.Unit) ? ToUnit() : null); + if (summon != null) + { + summon.SetTempSummonType(spwtype); + return summon; + } + } + + return null; + } + + public GameObject SummonGameObject(uint entry, float x, float y, float z, float ang, Quaternion rotation, uint respawnTime) + { + if (x == 0 && y == 0 && z == 0) + { + GetClosePoint(out x, out y, out z, GetObjectSize()); + ang = GetOrientation(); + } + + Position pos = new Position(x, y, z, ang); + return SummonGameObject(entry, pos, rotation, respawnTime); + } + + public GameObject SummonGameObject(uint entry, Position pos, Quaternion rotation, uint respawnTime) + { + if (!IsInWorld) + return null; + + GameObjectTemplate goinfo = Global.ObjectMgr.GetGameObjectTemplate(entry); + if (goinfo == null) + { + Log.outError(LogFilter.Sql, "Gameobject template {0} not found in database!", entry); + return null; + } + + Map map = GetMap(); + GameObject go = new GameObject(); + if (!go.Create(entry, map, GetPhaseMask(), pos, rotation, 255, GameObjectState.Ready)) + return null; + + go.CopyPhaseFrom(this); + + go.SetRespawnTime((int)respawnTime); + if (IsTypeId(TypeId.Player) || IsTypeId(TypeId.Unit)) //not sure how to handle this + ToUnit().AddGameObject(go); + else + go.SetSpawnedByDefault(false); + + map.AddToMap(go); + return go; + } + + public Creature SummonTrigger(float x, float y, float z, float ang, uint duration, CreatureAI AI = null) + { + TempSummonType summonType = (duration == 0) ? TempSummonType.DeadDespawn : TempSummonType.TimedDespawn; + Creature summon = SummonCreature(SharedConst.WorldTrigger, x, y, z, ang, summonType, duration); + if (summon == null) + return null; + + if (IsTypeId(TypeId.Player) || IsTypeId(TypeId.Unit)) + { + summon.SetFaction(ToUnit().getFaction()); + summon.SetLevel(ToUnit().getLevel()); + } + + if (AI != null) + summon.InitializeAI(new CreatureAI(summon)); + return summon; + } + + public void SummonCreatureGroup(byte group, out List list) + { + Contract.Assert((IsTypeId(TypeId.GameObject) || IsTypeId(TypeId.Unit)), "Only GOs and creatures can summon npc groups!"); + list = new List(); + var data = Global.ObjectMgr.GetSummonGroup(GetEntry(), IsTypeId(TypeId.GameObject) ? SummonerType.GameObject : SummonerType.Creature, group); + if (data.Empty()) + { + Log.outWarn(LogFilter.Scripts, "{0} ({1}) tried to summon non-existing summon group {2}.", GetName(), GetGUID().ToString(), group); + return; + } + + foreach (var tempSummonData in data) + { + TempSummon summon = SummonCreature(tempSummonData.entry, tempSummonData.pos, tempSummonData.type, tempSummonData.time); + if (summon) + list.Add(summon); + } + } + + public Creature FindNearestCreature(uint entry, float range, bool alive = true) + { + var checker = new NearestCreatureEntryWithLiveStateInObjectRangeCheck(this, entry, alive, range); + var searcher = new CreatureLastSearcher(this, checker); + + Cell.VisitAllObjects(this, searcher, range); + return searcher.GetTarget(); + } + + public GameObject FindNearestGameObject(uint entry, float range) + { + var checker = new NearestGameObjectEntryInObjectRangeCheck(this, entry, range); + var searcher = new GameObjectLastSearcher(this, checker); + + Cell.VisitGridObjects(this, searcher, range); + return searcher.GetTarget(); + } + + public GameObject FindNearestGameObjectOfType(GameObjectTypes type, float range) + { + var checker = new NearestGameObjectTypeInObjectRangeCheck(this, type, range); + var searcher = new GameObjectLastSearcher(this, checker); + + Cell.VisitGridObjects(this, searcher, range); + return searcher.GetTarget(); + } + + public void GetGameObjectListWithEntryInGrid(List gameobjectList, uint entry = 0, float maxSearchRange = 250.0f) + { + var check = new AllGameObjectsWithEntryInRange(this, entry, maxSearchRange); + var searcher = new GameObjectListSearcher(this, gameobjectList, check); + + Cell.VisitGridObjects(this, searcher, maxSearchRange); + } + + public void GetCreatureListWithEntryInGrid(List creatureList, uint entry = 0, float maxSearchRange = 250.0f) + { + var check = new AllCreaturesOfEntryInRange(this, entry, maxSearchRange); + var searcher = new CreatureListSearcher(this, creatureList, check); + + Cell.VisitGridObjects(this, searcher, maxSearchRange); + } + + public List GetPlayerListInGrid(float maxSearchRange) + { + List playerList = new List(); + var checker = new AnyPlayerInObjectRangeCheck(this, maxSearchRange); + var searcher = new PlayerListSearcher(this, playerList, checker); + + Cell.VisitWorldObjects(this, searcher, maxSearchRange); + return playerList; + } + + public float GetObjectSize() + { + return (valuesCount > (uint)UnitFields.CombatReach) ? GetFloatValue(UnitFields.CombatReach) : SharedConst.DefaultWorldObjectSize; + } + + public virtual void SetPhaseMask(uint newPhaseMask, bool update) + { + phaseMask = newPhaseMask; + + if (update && IsInWorld) + UpdateObjectVisibility(); + } + + bool HasInPhaseList(uint phase) + { + return _phases.Contains(phase); + } + + /// + /// Updates Area based phases, does not remove phases from auras + /// Phases from gm commands are not taken into calculations, they can be lost!! + /// + public void UpdateAreaAndZonePhase() + { + bool updateNeeded = false; + var phases = Global.ObjectMgr.GetAreaAndZonePhases(); + foreach (var areaOrZoneId in phases.Keys) + { + foreach (var phase in phases[areaOrZoneId]) + { + if (areaOrZoneId == GetAreaId() || areaOrZoneId == GetZoneId()) + { + if (Global.ConditionMgr.IsObjectMeetToConditions(this, phase.Conditions)) + { + // add new phase if condition passed, true if it wasnt added before + bool up = SetInPhase(phase.Id, false, true); + if (!updateNeeded && up) + updateNeeded = true; + } + else + { + // condition failed, remove phase, true if there was something removed + bool up = SetInPhase(phase.Id, false, false); + if (!updateNeeded && up) + updateNeeded = true; + } + } + else + { + // not in area, remove phase, true if there was something removed + bool up = SetInPhase(phase.Id, false, false); + if (!updateNeeded && up) + updateNeeded = true; + } + } + } + + // do not remove a phase if it would be removed by an area but we have the same phase from an aura + Unit unit = ToUnit(); + if (unit) + { + var auraPhaseList = unit.GetAuraEffectsByType(AuraType.Phase); + foreach (var eff in auraPhaseList) + { + uint phase = (uint)eff.GetMiscValueB(); + bool up = SetInPhase(phase, false, true); + if (!updateNeeded && up) + updateNeeded = true; + } + var auraPhaseGroupList = unit.GetAuraEffectsByType(AuraType.PhaseGroup); + foreach (var eff in auraPhaseGroupList) + { + bool up = false; + uint phaseGroup = (uint)eff.GetMiscValueB(); + foreach (uint phase in Global.DB2Mgr.GetPhasesForGroup(phaseGroup)) + up = SetInPhase(phase, false, true); + + if (!updateNeeded && up) + updateNeeded = true; + } + } + + // only update visibility and send packets if there was a change in the phase list + + if (updateNeeded && IsTypeId(TypeId.Player) && IsInWorld) + ToPlayer().GetSession().SendSetPhaseShift(GetPhases(), GetTerrainSwaps(), GetWorldMapAreaSwaps()); + + // only update visibilty once, to prevent objects appearing for a moment while adding in multiple phases + if (updateNeeded && IsInWorld) + UpdateObjectVisibility(); + } + + public virtual bool SetInPhase(uint id, bool update, bool apply) + { + if (id != 0) + { + if (apply) + { + if (HasInPhaseList(id)) // do not run the updates if we are already in this phase + return false; + + _phases.Add(id); + } + else + { + // if area phase passes the condition we should not remove it (ie: if remove called from aura remove) + // this however breaks the .mod phase command, you wont be able to remove any area based phases with it + var phases = Global.ObjectMgr.GetPhasesForArea(GetAreaId()); + if (phases != null) + { + foreach (PhaseInfoStruct phase in phases) + { + if (id == phase.Id) + if (Global.ConditionMgr.IsObjectMeetToConditions(this, phase.Conditions)) + return false; + } + } + + if (!HasInPhaseList(id)) // do not run the updates if we are not in this phase + return false; + _phases.Remove(id); + } + } + RebuildTerrainSwaps(); + + if (update && IsInWorld) + UpdateObjectVisibility(); + return true; + } + + public void CopyPhaseFrom(WorldObject obj, bool update = false) + { + if (!obj) + return; + + foreach (uint phase in obj.GetPhases()) + SetInPhase(phase, false, true); + + if (update && IsInWorld) + UpdateObjectVisibility(); + } + + public void ClearPhases(bool update = false) + { + _phases.Clear(); + + RebuildTerrainSwaps(); + + if (update && IsInWorld) + UpdateObjectVisibility(); + } + + public bool IsInPhase(uint phase) { return _phases.Contains(phase); } + + public bool IsInPhase(List phases) + { + // PhaseId 169 is the default fallback phase + if (_phases.Empty() && phases.Empty()) + return true; + + if (_phases.Empty() && phases.Contains(169)) + return true; + + if (phases.Empty() && _phases.Contains(169)) + return true; + + return _phases.Intersect(phases).Any(); + } + + public bool IsInPhase(WorldObject obj) + { + // PhaseId 169 is the default fallback phase + if (_phases.Empty() && obj.GetPhases().Empty()) + return true; + + if (_phases.Empty() && obj.IsInPhase(169)) + return true; + + if (obj.GetPhases().Empty() && IsInPhase(169)) + return true; + + if (IsTypeId(TypeId.Player) && ToPlayer().IsGameMaster()) + return true; + + return IsInPhase(obj.GetPhases()); + } + + public bool IsInTerrainSwap(uint terrainSwap) { return _terrainSwaps.Contains(terrainSwap); } + + public void RebuildTerrainSwaps() + { + // Clear all terrain swaps, will be rebuilt below + // Reason for this is, multiple phases can have the same terrain swap, we should not remove the swap if another phase still use it + _terrainSwaps.Clear(); + + // Check all applied phases for terrain swap and add it only once + foreach (uint phaseId in _phases) + { + var swaps = Global.ObjectMgr.GetPhaseTerrainSwaps(phaseId); + foreach (uint swap in swaps) + { + // only add terrain swaps for current map + MapRecord mapEntry = CliDB.MapStorage.LookupByKey(swap); + if (mapEntry == null || mapEntry.ParentMapID != GetMapId()) + continue; + + if (Global.ConditionMgr.IsObjectMeetingNotGroupedConditions(ConditionSourceType.TerrainSwap, swap, this)) + _terrainSwaps.Add(swap); + } + } + + // get default terrain swaps, only for current map always + var mapSwaps = Global.ObjectMgr.GetDefaultTerrainSwaps(GetMapId()); + foreach (uint swap in mapSwaps) + { + if (Global.ConditionMgr.IsObjectMeetingNotGroupedConditions(ConditionSourceType.TerrainSwap, swap, this)) + _terrainSwaps.Add(swap); + } + + // online players have a game client with world map display + if (IsTypeId(TypeId.Player)) + RebuildWorldMapAreaSwaps(); + } + + void RebuildWorldMapAreaSwaps() + { + // Clear all world map area swaps, will be rebuilt below + _worldMapAreaSwaps.Clear(); + + // get ALL default terrain swaps, if we are using it (condition is true) + // send the worldmaparea for it, to see swapped worldmaparea in client from other maps too, not just from our current + var defaults = Global.ObjectMgr.GetDefaultTerrainSwapStore(); + foreach (uint swap in defaults.Values) + { + var uiMapSwaps = Global.ObjectMgr.GetTerrainWorldMaps(swap); + if (Global.ConditionMgr.IsObjectMeetingNotGroupedConditions(ConditionSourceType.TerrainSwap, swap, this)) + { + foreach (uint worldMapAreaId in uiMapSwaps) + _worldMapAreaSwaps.Add(worldMapAreaId); + } + } + + // Check all applied phases for world map area swaps + foreach (uint phaseId in _phases) + { + var swaps = Global.ObjectMgr.GetPhaseTerrainSwaps(phaseId); + foreach (uint swap in swaps) + { + var uiMapSwaps = Global.ObjectMgr.GetTerrainWorldMaps(swap); + if (Global.ConditionMgr.IsObjectMeetingNotGroupedConditions(ConditionSourceType.TerrainSwap, swap, this)) + foreach (uint worldMapAreaId in uiMapSwaps) + _worldMapAreaSwaps.Add(worldMapAreaId); + } + } + } + + public uint GetPhaseMask() { return phaseMask; } + public List GetPhases() { return _phases; } + public List GetTerrainSwaps() { return _terrainSwaps; } + public List GetWorldMapAreaSwaps() { return _worldMapAreaSwaps; } + public int GetDBPhase() { return _dbPhase; } + + // if negative it is used as PhaseGroupId + public void SetDBPhase(int p) { _dbPhase = p; } + + public void PlayDistanceSound(uint soundId, Player target = null) + { + PlaySpeakerBoxSound playSpeakerBoxSound = new PlaySpeakerBoxSound(GetGUID(), soundId); + if (target != null) + target.SendPacket(playSpeakerBoxSound); + else + SendMessageToSet(playSpeakerBoxSound, true); + } + + public void PlayDirectSound(uint soundId, Player target = null) + { + PlaySound sound = new PlaySound(GetGUID(), soundId); + if (target) + target.SendPacket(sound); + else + SendMessageToSet(sound, true); + } + + public void DestroyForNearbyPlayers() + { + if (!IsInWorld) + return; + List targets = new List(); + var check = new AnyPlayerInObjectRangeCheck(this, GetVisibilityRange(), false); + var searcher = new PlayerListSearcher(this, targets, check); + + Cell.VisitWorldObjects(this, searcher, GetVisibilityRange()); + foreach (var player in targets) + { + if (player == this) + continue; + + if (!player.HaveAtClient(this)) + continue; + + if (isTypeMask(TypeMask.Unit) && (ToUnit().GetCharmerGUID() == player.GetGUID()))// @todo this is for puppet + continue; + + DestroyForPlayer(player); + player.m_clientGUIDs.Remove(GetGUID()); + } + } + + public virtual void UpdateObjectVisibility(bool force = true) + { + //updates object's visibility for nearby players + var notifier = new VisibleChangesNotifier(this); + Cell.VisitWorldObjects(this, notifier, GetVisibilityRange()); + } + + public virtual void UpdateObjectVisibilityOnCreate() + { + UpdateObjectVisibility(true); + } + + public virtual void BuildUpdate(Dictionary data) + { + var notifier = new WorldObjectChangeAccumulator(this, data); + Cell.VisitWorldObjects(this, notifier, GetVisibilityRange()); + + ClearUpdateMask(false); + } + + public virtual void AddToObjectUpdate() + { + GetMap().AddUpdateObject(this); + } + + public virtual void RemoveFromObjectUpdate() + { + GetMap().RemoveUpdateObject(this); + } + + public uint GetInstanceId() { return instanceId; } + + public virtual ushort GetAIAnimKitId() { return 0; } + public virtual ushort GetMovementAnimKitId() { return 0; } + public virtual ushort GetMeleeAnimKitId() { return 0; } + + public virtual string GetName(LocaleConstant locale_idx = LocaleConstant.enUS) { return _name; } + public void SetName(string name) { _name = name; } + + public ObjectGuid GetGUID() { return GetGuidValue(ObjectFields.Guid); } + public uint GetEntry() { return GetUInt32Value(ObjectFields.Entry); } + public void SetEntry(uint entry) { SetUInt32Value(ObjectFields.Entry, entry); } + + public float GetObjectScale() { return GetFloatValue(ObjectFields.ScaleX); } + public virtual void SetObjectScale(float scale) { SetFloatValue(ObjectFields.ScaleX, scale); } + + public TypeId GetTypeId() { return objectTypeId; } + public bool IsTypeId(TypeId typeId) { return GetTypeId() == typeId; } + public bool isTypeMask(TypeMask mask) { return Convert.ToBoolean(mask & objectTypeMask); } + + public virtual bool hasQuest(uint questId) { return false; } + public virtual bool hasInvolvedQuest(uint questId) { return false; } + + public void SetFieldNotifyFlag(uint flag) { _fieldNotifyFlags |= flag; } + public void RemoveFieldNotifyFlag(uint flag) { _fieldNotifyFlags &= ~flag; } + + public Creature ToCreature() { return IsTypeId(TypeId.Unit) ? (this as Creature) : null; } + public Player ToPlayer() { return IsTypeId(TypeId.Player) ? (this as Player) : null; } + public GameObject ToGameObject() { return IsTypeId(TypeId.GameObject) ? (this as GameObject) : null; } + public Unit ToUnit() { return IsTypeId(TypeId.Unit) || IsTypeId(TypeId.Player) ? (this as Unit) : null; } + public Corpse ToCorpse() { return IsTypeId(TypeId.Corpse) ? (this as Corpse) : null; } + public DynamicObject ToDynamicObject() { return IsTypeId(TypeId.DynamicObject) ? (this as DynamicObject) : null; } + public AreaTrigger ToAreaTrigger() { return IsTypeId(TypeId.AreaTrigger) ? (this as AreaTrigger) : null; } + public Conversation ToConversation() { return IsTypeId(TypeId.Conversation) ? (this as Conversation) : null; } + + public virtual void Update(uint diff) { } + + public virtual uint GetLevelForTarget(WorldObject target) { return 1; } + + public virtual void SaveRespawnTime() { } + + public ZoneScript GetZoneScript() { return m_zoneScript; } + + public void AddToNotify(NotifyFlags f) { m_notifyflags |= f; } + public bool isNeedNotify(NotifyFlags f) { return Convert.ToBoolean(m_notifyflags & f); } + NotifyFlags GetNotifyFlags() { return m_notifyflags; } + bool NotifyExecuted(NotifyFlags f) { return Convert.ToBoolean(m_executed_notifies & f); } + void SetNotified(NotifyFlags f) { m_executed_notifies |= f; } + public void ResetAllNotifies() { m_notifyflags = 0; m_executed_notifies = 0; } + + public bool isActiveObject() { return m_isActive; } + public bool IsPermanentWorldObject() { return m_isWorldObject; } + + public Transport GetTransport() { return m_transport; } + public float GetTransOffsetX() { return m_movementInfo.transport.pos.GetPositionX(); } + public float GetTransOffsetY() { return m_movementInfo.transport.pos.GetPositionY(); } + public float GetTransOffsetZ() { return m_movementInfo.transport.pos.GetPositionZ(); } + public float GetTransOffsetO() { return m_movementInfo.transport.pos.GetOrientation(); } + Position GetTransOffset() { return m_movementInfo.transport.pos; } + public uint GetTransTime() { return m_movementInfo.transport.time; } + public sbyte GetTransSeat() { return m_movementInfo.transport.seat; } + public virtual ObjectGuid GetTransGUID() + { + if (GetTransport()) + return GetTransport().GetGUID(); + + return ObjectGuid.Empty; + } + public void SetTransport(Transport t) { m_transport = t; } + + public virtual float GetStationaryX() { return GetPositionX(); } + public virtual float GetStationaryY() { return GetPositionY(); } + public virtual float GetStationaryZ() { return GetPositionZ(); } + public virtual float GetStationaryO() { return GetOrientation(); } + + public virtual bool IsNeverVisibleFor(WorldObject seer) { return !IsInWorld; } + public virtual bool IsAlwaysVisibleFor(WorldObject seer) { return false; } + public virtual bool IsInvisibleDueToDespawn() { return false; } + public virtual bool IsAlwaysDetectableFor(WorldObject seer) { return false; } + + public virtual bool LoadFromDB(ulong guid, Map map) { return true; } + + //Position + public bool HasInLine(WorldObject target, float width) + { + if (!HasInArc(MathFunctions.PI, target)) + return false; + width += target.GetObjectSize(); + float angle = GetRelativeAngle(target); + return Math.Abs(Math.Sin(angle)) * GetExactDist2d(target.GetPositionX(), target.GetPositionY()) < width; + } + + public float GetDistanceZ(WorldObject obj) + { + float dz = Math.Abs(GetPositionZ() - obj.GetPositionZ()); + float sizefactor = GetObjectSize() + obj.GetObjectSize(); + float dist = dz - sizefactor; + return (dist > 0 ? dist : 0); + } + + public virtual bool _IsWithinDist(WorldObject obj, float dist2compare, bool is3D) + { + float sizefactor = GetObjectSize() + obj.GetObjectSize(); + float maxdist = dist2compare + sizefactor; + + if (GetTransport() && obj.GetTransport() != null && obj.GetTransport().GetGUID() == GetTransport().GetGUID()) + { + float dtx = m_movementInfo.transport.pos.posX - obj.m_movementInfo.transport.pos.posX; + float dty = m_movementInfo.transport.pos.posY - obj.m_movementInfo.transport.pos.posY; + float disttsq = dtx * dtx + dty * dty; + if (is3D) + { + float dtz = m_movementInfo.transport.pos.posZ - obj.m_movementInfo.transport.pos.posZ; + disttsq += dtz * dtz; + } + return disttsq < (maxdist * maxdist); + } + + float dx = GetPositionX() - obj.GetPositionX(); + float dy = GetPositionY() - obj.GetPositionY(); + float distsq = dx * dx + dy * dy; + if (is3D) + { + float dz = GetPositionZ() - obj.GetPositionZ(); + distsq += dz * dz; + } + + return distsq < maxdist * maxdist; + } + + public bool IsWithinLOSInMap(WorldObject obj) + { + if (!IsInMap(obj)) + return false; + + float x, y, z; + if (obj.IsTypeId(TypeId.Player)) + obj.GetPosition(out x, out y, out z); + else + obj.GetHitSpherePointFor(GetPosition(), out x, out y, out z); + + return IsWithinLOS(x, y, z); + } + + public float GetDistance(WorldObject obj) + { + float d = GetExactDist(obj.GetPosition()) - GetObjectSize() - obj.GetObjectSize(); + return d > 0.0f ? d : 0.0f; + } + + public float GetDistance(Position pos) + { + float d = GetExactDist(pos) - GetObjectSize(); + return d > 0.0f ? d : 0.0f; + } + + public float GetDistance(float x, float y, float z) + { + float d = GetExactDist(x, y, z) - GetObjectSize(); + return d > 0.0f ? d : 0.0f; + } + + public float GetDistance2d(WorldObject obj) + { + float d = GetExactDist2d(obj.GetPosition()) - GetObjectSize() - obj.GetObjectSize(); + return d > 0.0f ? d : 0.0f; + } + + public float GetDistance2d(float x, float y) + { + float d = GetExactDist2d(x, y) - GetObjectSize(); + return d > 0.0f ? d : 0.0f; + } + + public bool IsSelfOrInSameMap(WorldObject obj) + { + if (this == obj) + return true; + return IsInMap(obj); + } + + public bool IsInMap(WorldObject obj) + { + var m = GetMap().GetId(); + var b = obj.GetMap().GetId(); + if (obj != null) + return IsInWorld && obj.IsInWorld && m == b; + return false; + } + + public bool IsWithinDist3d(float x, float y, float z, float dist) + { + return IsInDist(x, y, z, dist + GetObjectSize()); + } + + public bool IsWithinDist3d(Position pos, float dist) + { + return IsInDist(pos, dist + GetObjectSize()); + } + + public bool IsWithinDist2d(float x, float y, float dist) + { + return IsInDist2d(x, y, dist + GetObjectSize()); + } + + public bool IsWithinDist2d(Position pos, float dist) + { + return IsInDist2d(pos, dist + GetObjectSize()); + } + + public bool IsWithinDist(WorldObject obj, float dist2compare, bool is3D = true) + { + return obj != null && _IsWithinDist(obj, dist2compare, is3D); + } + + public bool IsWithinDistInMap(WorldObject obj, float dist2compare, bool is3D = true) + { + return obj && IsInMap(obj) && IsInPhase(obj) && _IsWithinDist(obj, dist2compare, is3D); + } + + public bool IsWithinLOS(float ox, float oy, float oz) + { + if (IsInWorld) + { + float x, y, z; + if (IsTypeId(TypeId.Player)) + GetPosition(out x, out y, out z); + else + GetHitSpherePointFor(new Position(ox, oy, oz), out x, out y, out z); + + return GetMap().isInLineOfSight(x, y, z + 2.0f, ox, oy, oz + 2.0f, GetPhases()); + } + + return true; + } + + Position GetHitSpherePointFor(Position dest) + { + Vector3 vThis = new Vector3(GetPositionX(), GetPositionY(), GetPositionZ()); + Vector3 vObj = new Vector3(dest.GetPositionX(), dest.GetPositionY(), dest.GetPositionZ()); + Vector3 contactPoint = vThis + (vObj - vThis).directionOrZero() * GetObjectSize(); + + return new Position(contactPoint.X, contactPoint.Y, contactPoint.Z, GetAngle(contactPoint.X, contactPoint.Y)); + } + + void GetHitSpherePointFor(Position dest, out float x, out float y, out float z) + { + Position pos = GetHitSpherePointFor(dest); + x = pos.GetPositionX(); + y = pos.GetPositionY(); + z = pos.GetPositionZ(); + } + + public bool GetDistanceOrder(WorldObject obj1, WorldObject obj2, bool is3D = true) + { + float dx1 = GetPositionX() - obj1.GetPositionX(); + float dy1 = GetPositionY() - obj1.GetPositionY(); + float distsq1 = dx1 * dx1 + dy1 * dy1; + if (is3D) + { + float dz1 = GetPositionZ() - obj1.GetPositionZ(); + distsq1 += dz1 * dz1; + } + + float dx2 = GetPositionX() - obj2.GetPositionX(); + float dy2 = GetPositionY() - obj2.GetPositionY(); + float distsq2 = dx2 * dx2 + dy2 * dy2; + if (is3D) + { + float dz2 = GetPositionZ() - obj2.GetPositionZ(); + distsq2 += dz2 * dz2; + } + + return distsq1 < distsq2; + } + + public bool IsInRange(WorldObject obj, float minRange, float maxRange, bool is3D = true) + { + float dx = GetPositionX() - obj.GetPositionX(); + float dy = GetPositionY() - obj.GetPositionY(); + float distsq = dx * dx + dy * dy; + if (is3D) + { + float dz = GetPositionZ() - obj.GetPositionZ(); + distsq += dz * dz; + } + + float sizefactor = GetObjectSize() + obj.GetObjectSize(); + + // check only for real range + if (minRange > 0.0f) + { + float mindist = minRange + sizefactor; + if (distsq < mindist * mindist) + return false; + } + + float maxdist = maxRange + sizefactor; + return distsq < maxdist * maxdist; + } + + bool IsInBetween(WorldObject obj1, WorldObject obj2, float size = 0) { return obj1 && obj2 && IsInBetween(obj1.GetPosition(), obj2.GetPosition(), size); } + bool IsInBetween(Position pos1, Position pos2, float size) + { + float dist = GetExactDist2d(pos1); + + // not using sqrt() for performance + if ((dist * dist) >= pos1.GetExactDist2dSq(pos2)) + return false; + + if (size == 0) + size = GetObjectSize() / 2; + + float angle = pos1.GetAngle(pos2); + + // not using sqrt() for performance + return (size * size) >= GetExactDist2dSq(pos1.GetPositionX() + (float)Math.Cos(angle) * dist, pos1.GetPositionY() + (float)Math.Sin(angle) * dist); + } + + public bool isInFront(WorldObject target, float arc = MathFunctions.PI) + { + return HasInArc(arc, target); + } + + public bool isInBack(WorldObject target, float arc = MathFunctions.PI) + { + return !HasInArc(2 * MathFunctions.PI - arc, target); + } + + public void GetRandomPoint(Position pos, float distance, out float rand_x, out float rand_y, out float rand_z) + { + if (distance == 0) + { + pos.GetPosition(out rand_x, out rand_y, out rand_z); + return; + } + + // angle to face `obj` to `this` + float angle = (float)RandomHelper.NextDouble() * (2 * MathFunctions.PI); + float new_dist = (float)RandomHelper.NextDouble() + (float)RandomHelper.NextDouble(); + new_dist = distance * (new_dist > 1 ? new_dist - 2 : new_dist); + + rand_x = (float)(pos.posX + new_dist * Math.Cos(angle)); + rand_y = (float)(pos.posY + new_dist * Math.Sin(angle)); + rand_z = pos.posZ; + + GridDefines.NormalizeMapCoord(ref rand_x); + GridDefines.NormalizeMapCoord(ref rand_y); + UpdateGroundPositionZ(rand_x, rand_y, ref rand_z); // update to LOS height if available + } + + public void GetRandomPoint(Position srcPos, float distance, out Position pos) + { + pos = new Position(); + float x, y, z; + GetRandomPoint(srcPos, distance, out x, out y, out z); + pos.Relocate(x, y, z, GetOrientation()); + } + + public void UpdateGroundPositionZ(float x, float y, ref float z) + { + float new_z = GetMap().GetHeight(GetPhases(), x, y, z, true); + if (new_z > MapConst.InvalidHeight) + z = new_z + 0.05f; // just to be sure that we are not a few pixel under the surface + } + + public void UpdateAllowedPositionZ(float x, float y, ref float z) + { + // TODO: Allow transports to be part of dynamic vmap tree + if (GetTransport()) + return; + + switch (GetTypeId()) + { + case TypeId.Unit: + { + // non fly unit don't must be in air + // non swim unit must be at ground (mostly speedup, because it don't must be in water and water level check less fast + if (!ToCreature().CanFly()) + { + bool canSwim = ToCreature().CanSwim(); + float ground_z = z; + float max_z = canSwim + ? GetMap().GetWaterOrGroundLevel(GetPhases(), x, y, z, ref ground_z, !ToUnit().HasAuraType(AuraType.WaterWalk)) + : ((ground_z = GetMap().GetHeight(GetPhases(), x, y, z, true))); + if (max_z > MapConst.InvalidHeight) + { + if (z > max_z) + z = max_z; + else if (z < ground_z) + z = ground_z; + } + } + else + { + float ground_z = GetMap().GetHeight(GetPhases(), x, y, z, true); + if (z < ground_z) + z = ground_z; + } + break; + } + case TypeId.Player: + { + // for server controlled moves playr work same as creature (but it can always swim) + if (!ToPlayer().CanFly()) + { + float ground_z = z; + float max_z = GetMap().GetWaterOrGroundLevel(GetPhases(), x, y, z, ref ground_z, !ToUnit().HasAuraType(AuraType.WaterWalk)); + if (max_z > MapConst.InvalidHeight) + { + if (z > max_z) + z = max_z; + else if (z < ground_z) + z = ground_z; + } + } + else + { + float ground_z = GetMap().GetHeight(GetPhases(), x, y, z, true); + if (z < ground_z) + z = ground_z; + } + break; + } + default: + { + float ground_z = GetMap().GetHeight(GetPhases(), x, y, z, true); + if (ground_z > MapConst.InvalidHeight) + z = ground_z; + break; + } + } + } + + public void GetNearPoint2D(out float x, out float y, float distance2d, float absAngle) + { + x = (float)(GetPositionX() + (GetObjectSize() + distance2d) * Math.Cos(absAngle)); + y = (float)(GetPositionY() + (GetObjectSize() + distance2d) * Math.Sin(absAngle)); + + GridDefines.NormalizeMapCoord(ref x); + GridDefines.NormalizeMapCoord(ref y); + } + + public void GetNearPoint(WorldObject searcher, out float x, out float y, out float z, float searcher_size, float distance2d, float absAngle) + { + GetNearPoint2D(out x, out y, distance2d + searcher_size, absAngle); + z = GetPositionZ(); + UpdateAllowedPositionZ(x, y, ref z); + + // if detection disabled, return first point + if (!WorldConfig.GetBoolValue(WorldCfg.DetectPosCollision)) + return; + + // return if the point is already in LoS + if (IsWithinLOS(x, y, z)) + return; + + // remember first point + float first_x = x; + float first_y = y; + float first_z = z; + + // loop in a circle to look for a point in LoS using small steps + for (float angle = MathFunctions.PI / 8; angle < Math.PI * 2; angle += MathFunctions.PI / 8) + { + GetNearPoint2D(out x, out y, distance2d + searcher_size, absAngle + angle); + z = GetPositionZ(); + UpdateAllowedPositionZ(x, y, ref z); + if (IsWithinLOS(x, y, z)) + return; + } + + // still not in LoS, give up and return first position found + x = first_x; + y = first_y; + z = first_z; + } + + public void GetClosePoint(out float x, out float y, out float z, float size, float distance2d = 0, float angle = 0) + { + // angle calculated from current orientation + GetNearPoint(null, out x, out y, out z, size, distance2d, GetOrientation() + angle); + } + + public Position GetNearPosition(float dist, float angle) + { + var pos = GetPosition(); + MovePosition(ref pos, dist, angle); + return pos; + } + + public Position GetFirstCollisionPosition(float dist, float angle) + { + var pos = GetPosition(); + MovePositionToFirstCollision(ref pos, dist, angle); + return pos; + } + + public Position GetRandomNearPosition(float radius) + { + var pos = GetPosition(); + MovePosition(ref pos, radius * (float)RandomHelper.NextDouble(), (float)RandomHelper.NextDouble() * MathFunctions.PI * 2); + return pos; + } + + public void GetContactPoint(WorldObject obj, out float x, out float y, out float z, float distance2d = 0.5f) + { + // angle to face `obj` to `this` using distance includes size of `obj` + GetNearPoint(obj, out x, out y, out z, obj.GetObjectSize(), distance2d, GetAngle(obj)); + } + + public void MovePosition(ref Position pos, float dist, float angle) + { + angle += GetOrientation(); + float destx, desty, destz, ground, floor; + destx = pos.posX + dist * (float)Math.Cos(angle); + desty = pos.posY + dist * (float)Math.Sin(angle); + + // Prevent invalid coordinates here, position is unchanged + if (!GridDefines.IsValidMapCoord(destx, desty, pos.posZ)) + { + Log.outError(LogFilter.Server, "WorldObject.MovePosition invalid coordinates X: {0} and Y: {1} were passed!", destx, desty); + return; + } + + ground = GetMap().GetHeight(GetPhases(), destx, desty, MapConst.MaxHeight, true); + floor = GetMap().GetHeight(GetPhases(), destx, desty, pos.posZ, true); + destz = Math.Abs(ground - pos.posZ) <= Math.Abs(floor - pos.posZ) ? ground : floor; + + float step = dist / 10.0f; + + for (byte j = 0; j < 10; ++j) + { + // do not allow too big z changes + if (Math.Abs(pos.posZ - destz) > 6) + { + destx -= step * (float)Math.Cos(angle); + desty -= step * (float)Math.Sin(angle); + ground = GetMap().GetHeight(GetPhases(), destx, desty, MapConst.MaxHeight, true); + floor = GetMap().GetHeight(GetPhases(), destx, desty, pos.posZ, true); + destz = Math.Abs(ground - pos.posZ) <= Math.Abs(floor - pos.posZ) ? ground : floor; + } + // we have correct destz now + else + { + pos.Relocate(destx, desty, destz); + break; + } + } + + GridDefines.NormalizeMapCoord(ref pos.posX); + GridDefines.NormalizeMapCoord(ref pos.posY); + UpdateGroundPositionZ(pos.posX, pos.posY, ref pos.posZ); + pos.SetOrientation(GetOrientation()); + } + + float NormalizeZforCollision(WorldObject obj, float x, float y, float z) + { + float ground = obj.GetMap().GetHeight(obj.GetPhases(), x, y, MapConst.MaxHeight, true); + float floor = obj.GetMap().GetHeight(obj.GetPhases(), x, y, z + 2.0f, true); + float helper = Math.Abs(ground - z) <= Math.Abs(floor - z) ? ground : floor; + if (z > helper) // must be above ground + { + Unit unit = obj.ToUnit(); + if (unit) + { + if (unit.CanFly()) + return z; + } + LiquidData liquid_status; + ZLiquidStatus res = obj.GetMap().getLiquidStatus(x, y, z, MapConst.MapAllLiquidTypes, out liquid_status); + if (res != 0 && liquid_status.level > helper) // water must be above ground + { + if (liquid_status.level > z) // z is underwater + return z; + else + return Math.Abs(liquid_status.level - z) <= Math.Abs(helper - z) ? liquid_status.level : helper; + } + } + return helper; + } + + public void MovePositionToFirstCollision(ref Position pos, float dist, float angle) + { + angle += GetOrientation(); + float destx, desty, destz; + destx = pos.posX + dist * (float)Math.Cos(angle); + desty = pos.posY + dist * (float)Math.Sin(angle); + + // Prevent invalid coordinates here, position is unchanged + if (!GridDefines.IsValidMapCoord(destx, desty)) + { + Log.outError(LogFilter.Server, "WorldObject.MovePositionToFirstCollision invalid coordinates X: {0} and Y: {1} were passed!", destx, desty); + return; + } + + destz = NormalizeZforCollision(this, destx, desty, pos.GetPositionZ()); + bool col = Global.VMapMgr.getObjectHitPos(GetMapId(), pos.posX, pos.posY, pos.posZ + 0.5f, destx, desty, destz + 0.5f, out destx, out desty, out destz, -0.5f); + + // collision occured + if (col) + { + // move back a bit + destx -= SharedConst.ContactDistance * (float)Math.Cos(angle); + desty -= SharedConst.ContactDistance * (float)Math.Sin(angle); + dist = (float)Math.Sqrt((pos.posX - destx) * (pos.posX - destx) + (pos.posY - desty) * (pos.posY - desty)); + } + + // check dynamic collision + col = GetMap().getObjectHitPos(GetPhases(), pos.posX, pos.posY, pos.posZ + 0.5f, destx, desty, destz + 0.5f, out destx, out desty, out destz, -0.5f); + + // Collided with a gameobject + if (col) + { + destx -= SharedConst.ContactDistance * (float)Math.Cos(angle); + desty -= SharedConst.ContactDistance * (float)Math.Sin(angle); + dist = (float)Math.Sqrt((pos.posX - destx) * (pos.posX - destx) + (pos.posY - desty) * (pos.posY - desty)); + } + + float step = dist / 10.0f; + + for (byte j = 0; j < 10; ++j) + { + // do not allow too big z changes + if (Math.Abs(pos.posZ - destz) > 6f) + { + destx -= step * (float)Math.Cos(angle); + desty -= step * (float)Math.Sin(angle); + destz = NormalizeZforCollision(this, destx, desty, pos.GetPositionZ()); + } + // we have correct destz now + else + { + pos.Relocate(destx, desty, destz); + break; + } + } + + GridDefines.NormalizeMapCoord(ref pos.posX); + GridDefines.NormalizeMapCoord(ref pos.posY); + pos.posZ = NormalizeZforCollision(this, destx, desty, pos.GetPositionZ()); + pos.SetOrientation(GetOrientation()); + } + + public void SetLocationInstanceId(uint _instanceId) { instanceId = _instanceId; } + + #region Fields + public TypeMask objectTypeMask { get; set; } + protected TypeId objectTypeId { get; set; } + protected UpdateFlag m_updateFlag { get; set; } + + protected Hashtable UpdateData; + protected Dictionary[] _dynamicValues; + protected BitArray _changesMask { get; set; } + protected Dictionary _dynamicChangesMask = new Dictionary(); + protected BitArray[] _dynamicChangesArrayMask; + + public uint valuesCount; + protected uint _dynamicValuesCount; + public uint LastUsedScriptID; + + protected uint _fieldNotifyFlags { get; set; } + bool m_objectUpdated; + + public MovementInfo m_movementInfo; + string _name; + protected bool m_isActive; + bool m_isWorldObject; + public ZoneScript m_zoneScript; + + Transport m_transport; + Map _currMap; + uint instanceId; + uint phaseMask; + List _phases = new List(); + List _terrainSwaps = new List(); + List _worldMapAreaSwaps = new List(); + int _dbPhase; + public bool IsInWorld { get; set; } + + NotifyFlags m_notifyflags; + NotifyFlags m_executed_notifies; + + public FlaggedArray m_stealth = new FlaggedArray(2); + public FlaggedArray m_stealthDetect = new FlaggedArray(2); + + public FlaggedArray m_invisibility = new FlaggedArray((int)InvisibilityType.Max); + public FlaggedArray m_invisibilityDetect = new FlaggedArray((int)InvisibilityType.Max); + + public FlaggedArray m_serverSideVisibility = new FlaggedArray(2); + public FlaggedArray m_serverSideVisibilityDetect = new FlaggedArray(2); + #endregion + + public static implicit operator bool(WorldObject obj) + { + return obj != null; + } + } + + public class MovementInfo + { + public MovementInfo() + { + Guid = ObjectGuid.Empty; + Flags = MovementFlag.None; + Flags2 = MovementFlag2.None; + Time = 0; + Pitch = 0.0f; + + Pos = new Position(); + transport.Reset(); + jump.Reset(); + } + + public MovementFlag GetMovementFlags() { return Flags; } + public void SetMovementFlags(MovementFlag f) { Flags = f; } + public void AddMovementFlag(MovementFlag f) { Flags |= f; } + public void RemoveMovementFlag(MovementFlag f) { Flags &= ~f; } + public bool HasMovementFlag(MovementFlag f) { return Convert.ToBoolean(Flags & f); } + + public MovementFlag2 GetMovementFlags2() { return Flags2; } + public void SetMovementFlags2(MovementFlag2 f) { Flags2 = f; } + public void AddMovementFlag2(MovementFlag2 f) { Flags2 |= f; } + public void RemoveMovementFlag2(MovementFlag2 f) { Flags2 &= ~f; } + public bool HasMovementFlag2(MovementFlag2 f) { return Convert.ToBoolean(Flags2 & f); } + + public void SetFallTime(uint time) { jump.fallTime = time; } + + public void ResetTransport() + { + transport.Reset(); + } + + public void ResetJump() + { + jump.Reset(); + } + + public ObjectGuid Guid { get; set; } + MovementFlag Flags { get; set; } + MovementFlag2 Flags2 { get; set; } + public Position Pos { get; set; } + public uint Time { get; set; } + public TransportInfo transport; + public float Pitch { get; set; } + public JumpInfo jump; + public float SplineElevation { get; set; } + + public struct TransportInfo + { + public void Reset() + { + guid = ObjectGuid.Empty; + pos = new Position(); + seat = -1; + time = 0; + prevTime = 0; + vehicleId = 0; + } + + public ObjectGuid guid; + public Position pos; + public sbyte seat; + public uint time; + public uint prevTime; + public uint vehicleId; + } + public struct JumpInfo + { + public void Reset() + { + fallTime = 0; + zspeed = sinAngle = cosAngle = xyspeed = 0.0f; + } + + public uint fallTime; + public float zspeed; + public float sinAngle; + public float cosAngle; + public float xyspeed; + } + } +} \ No newline at end of file diff --git a/Game/Entities/Pet.cs b/Game/Entities/Pet.cs new file mode 100644 index 000000000..26b9cd34e --- /dev/null +++ b/Game/Entities/Pet.cs @@ -0,0 +1,1626 @@ +/* + * Copyright (C) 2012-2017 CypherCore + * + * 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, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +using Framework.Constants; +using Framework.Database; +using Game.DataStorage; +using Game.Maps; +using Game.Network.Packets; +using Game.Spells; +using System; +using System.Collections.Generic; +using System.Diagnostics.Contracts; +using System.Linq; +using System.Text; + +namespace Game.Entities +{ + public class Pet : Guardian + { + const int PetFocusRegenInterval = 4 * Time.InMilliseconds; + const int HappinessLevelSize = 333000; + const float PetXPFactor = 0.05f; + + public Pet(Player owner, PetType type = PetType.Max) : base(null, owner, true) + { + m_petType = type; + + Contract.Assert(GetOwner().IsTypeId(TypeId.Player)); + + m_unitTypeMask |= UnitTypeMask.Pet; + if (type == PetType.Hunter) + m_unitTypeMask |= UnitTypeMask.HunterPet; + + if (!m_unitTypeMask.HasAnyFlag(UnitTypeMask.ControlableGuardian)) + { + m_unitTypeMask |= UnitTypeMask.ControlableGuardian; + InitCharmInfo(); + } + + SetName("Pet"); + m_focusRegenTimer = PetFocusRegenInterval; + } + + public override void Dispose() + { + _declinedname = null; + base.Dispose(); + } + + public override void AddToWorld() + { + //- Register the pet for guid lookup + if (!IsInWorld) + { + // Register the pet for guid lookup + base.AddToWorld(); + InitializeAI(); + } + + // Prevent stuck pets when zoning. Pets default to "follow" when added to world + // so we'll reset flags and let the AI handle things + if (GetCharmInfo() != null && GetCharmInfo().HasCommandState(CommandStates.Follow)) + { + GetCharmInfo().SetIsCommandAttack(false); + GetCharmInfo().SetIsCommandFollow(false); + GetCharmInfo().SetIsAtStay(false); + GetCharmInfo().SetIsFollowing(false); + GetCharmInfo().SetIsReturning(false); + } + } + + public override void RemoveFromWorld() + { + // Remove the pet from the accessor + if (IsInWorld) + { + // Don't call the function for Creature, normal mobs + totems go in a different storage + base.RemoveFromWorld(); + GetMap().GetObjectsStore().Remove(GetGUID()); + } + } + + public bool LoadPetFromDB(Player owner, uint petEntry = 0, uint petnumber = 0, bool current = false) + { + m_loading = true; + + ulong ownerid = owner.GetGUID().GetCounter(); + + PreparedStatement stmt = null; + + if (petnumber != 0) + { + // Known petnumber entry + stmt = DB.Characters.GetPreparedStatement(CharStatements.SEL_CHAR_PET_BY_ENTRY); + stmt.AddValue(0, ownerid); + stmt.AddValue(1, petnumber); + } + else if (current) + { + // Current pet (slot 0) + stmt = DB.Characters.GetPreparedStatement(CharStatements.SEL_CHAR_PET_BY_ENTRY_AND_SLOT); + stmt.AddValue(0, ownerid); + stmt.AddValue(1, PetSaveMode.AsCurrent); + } + else if (petEntry != 0) + { + // known petEntry entry (unique for summoned pet, but non unique for hunter pet (only from current or not stabled pets) + stmt = DB.Characters.GetPreparedStatement(CharStatements.SEL_CHAR_PET_BY_ENTRY_AND_SLOT_2); + stmt.AddValue(0, ownerid); + stmt.AddValue(1, petEntry); + stmt.AddValue(2, PetSaveMode.AsCurrent); + stmt.AddValue(3, PetSaveMode.LastStableSlot); + } + else + { + // Any current or other non-stabled pet (for hunter "call pet") + stmt = DB.Characters.GetPreparedStatement(CharStatements.SEL_CHAR_PET_BY_SLOT); + stmt.AddValue(0, ownerid); + stmt.AddValue(1, PetSaveMode.AsCurrent); + stmt.AddValue(2, PetSaveMode.LastStableSlot); + } + + SQLResult result = DB.Characters.Query(stmt); + if (result.IsEmpty()) + { + m_loading = false; + return false; + } + + // update for case of current pet "slot = 0" + petEntry = result.Read(1); + if (petEntry == 0) + return false; + + uint summonSpellId = result.Read(14); + SpellInfo spellInfo = Global.SpellMgr.GetSpellInfo(summonSpellId); + + bool isTemporarySummon = spellInfo != null && spellInfo.GetDuration() > 0; + if (current && isTemporarySummon) + return false; + + PetType petType = (PetType)result.Read(15); + if (petType == PetType.Hunter) + { + CreatureTemplate creatureInfo = Global.ObjectMgr.GetCreatureTemplate(petEntry); + if (creatureInfo == null || !creatureInfo.IsTameable(owner.CanTameExoticPets())) + return false; + } + + uint petId = result.Read(0); + if (current && owner.IsPetNeedBeTemporaryUnsummoned()) + { + owner.SetTemporaryUnsummonedPetNumber(petId); + return false; + } + + Map map = owner.GetMap(); + if (!Create(map.GenerateLowGuid(HighGuid.Pet), map, petEntry)) + return false; + + CopyPhaseFrom(owner); + + setPetType(petType); + SetFaction(owner.getFaction()); + SetUInt32Value(UnitFields.CreatedBySpell, summonSpellId); + + if (IsCritter()) + { + float px, py, pz; + owner.GetClosePoint(out px, out py, out pz, GetObjectSize(), SharedConst.PetFollowDist, GetFollowAngle()); + Relocate(px, py, pz, owner.GetOrientation()); + + if (!IsPositionValid()) + { + Log.outError(LogFilter.Pet, "Pet (guidlow {0}, entry {1}) not loaded. Suggested coordinates isn't valid (X: {2} Y: {3})", + GetGUID().ToString(), GetEntry(), GetPositionX(), GetPositionY()); + return false; + } + + map.AddToMap(ToCreature()); + return true; + } + + GetCharmInfo().SetPetNumber(petId, IsPermanentPetFor(owner)); + + SetDisplayId(result.Read(3)); + SetNativeDisplayId(result.Read(3)); + uint petlevel = result.Read(4); + SetUInt64Value(UnitFields.NpcFlags, (ulong)NPCFlags.None); + SetName(result.Read(8)); + + switch (getPetType()) + { + case PetType.Summon: + petlevel = owner.getLevel(); + + SetByteValue(UnitFields.Bytes0, 1, (byte)Class.Mage); + SetUInt32Value(UnitFields.Flags, (uint)UnitFlags.PvpAttackable); // this enables popup window (pet dismiss, cancel) + break; + case PetType.Hunter: + SetByteValue(UnitFields.Bytes0, 1, (byte)Class.Warrior); + SetByteValue(UnitFields.Bytes0, 3, (byte)Gender.None); + SetSheath(SheathState.Melee); + SetByteFlag(UnitFields.Bytes2, UnitBytes2Offsets.PetFlags, (result.Read(9) ? UnitPetFlags.CanBeAbandoned : UnitPetFlags.CanBeRenamed | UnitPetFlags.CanBeAbandoned)); + + SetUInt32Value(UnitFields.Flags, (uint)UnitFlags.PvpAttackable); // this enables popup window (pet abandon, cancel) + setPowerType(PowerType.Focus); + break; + default: + if (!IsPetGhoul()) + Log.outError(LogFilter.Pet, "Pet have incorrect type ({0}) for pet loading.", getPetType()); + break; + } + + SetUInt32Value(UnitFields.PetNameTimestamp, (uint)Time.UnixTime); // cast can't be helped here + SetCreatorGUID(owner.GetGUID()); + + InitStatsForLevel(petlevel); + SetUInt32Value(UnitFields.PetExperience, result.Read(5)); + + SynchronizeLevelWithOwner(); + + SetReactState((ReactStates)result.Read(6)); + SetCanModifyStats(true); + + if (getPetType() == PetType.Summon && !current) //all (?) summon pets come with full health when called, but not when they are current + SetPower(PowerType.Mana, GetMaxPower(PowerType.Mana)); + else + { + uint savedhealth = result.Read(10); + uint savedmana = result.Read(11); + if (savedhealth == 0 && getPetType() == PetType.Hunter) + setDeathState(DeathState.JustDied); + else + { + SetHealth(savedhealth > GetMaxHealth() ? GetMaxHealth() : savedhealth); + SetPower(PowerType.Mana, savedmana > GetMaxPower(PowerType.Mana) ? GetMaxPower(PowerType.Mana) : (int)savedmana); + } + } + + // set current pet as current + // 0=current + // 1..MAX_PET_STABLES in stable slot + // PET_SAVE_NOT_IN_SLOT(100) = not stable slot (summoning)) + if (result.Read(7) != 0) + { + stmt = DB.Characters.GetPreparedStatement(CharStatements.UPD_CHAR_PET_SLOT_BY_SLOT_EXCLUDE_ID); + stmt.AddValue(0, PetSaveMode.NotInSlot); + stmt.AddValue(1, ownerid); + stmt.AddValue(2, PetSaveMode.AsCurrent); + stmt.AddValue(3, GetCharmInfo().GetPetNumber()); + DB.Characters.Execute(stmt); + + stmt = DB.Characters.GetPreparedStatement(CharStatements.UPD_CHAR_PET_SLOT_BY_ID); + stmt.AddValue(0, PetSaveMode.AsCurrent); + stmt.AddValue(1, ownerid); + stmt.AddValue(2, GetCharmInfo().GetPetNumber()); + DB.Characters.Execute(stmt); + } + + // Send fake summon spell cast - this is needed for correct cooldown application for spells + // Example: 46584 - without this cooldown (which should be set always when pet is loaded) isn't set clientside + // @todo pets should be summoned from real cast instead of just faking it? + if (summonSpellId != 0) + { + SpellGo spellGo = new SpellGo(); + SpellCastData castData = spellGo.Cast; + + castData.CasterGUID = owner.GetGUID(); + castData.CasterUnit = owner.GetGUID(); + castData.CastID = ObjectGuid.Create(HighGuid.Cast, SpellCastSource.Normal, owner.GetMapId(), summonSpellId, map.GenerateLowGuid(HighGuid.Cast)); + castData.SpellID = (int)summonSpellId; + castData.CastFlags = SpellCastFlags.Unk9; + castData.CastTime = Time.GetMSTime(); + owner.SendMessageToSet(spellGo, true); + } + + owner.SetMinion(this, true); + map.AddToMap(ToCreature()); + + uint timediff = (uint)(Time.UnixTime - result.Read(13)); + _LoadAuras(timediff); + + // load action bar, if data broken will fill later by default spells. + if (!isTemporarySummon) + { + GetCharmInfo().LoadPetActionBar(result.Read(12)); + + _LoadSpells(); + _LoadSpellCooldowns(); + LearnPetPassives(); + InitLevelupSpellsForLevel(); + if (map.IsBattleArena()) + RemoveArenaAuras(); + + CastPetAuras(current); + } + + Log.outDebug(LogFilter.Pet, "New Pet has guid {0}", GetGUID().ToString()); + + ushort specId = result.Read(16); + ChrSpecializationRecord petSpec = CliDB.ChrSpecializationStorage.LookupByKey(specId); + if (petSpec != null) + specId = (ushort)Global.DB2Mgr.GetChrSpecializationByIndex(owner.HasAuraType(AuraType.OverridePetSpecs) ? Class.Max : 0, petSpec.OrderIndex).Id; + + SetSpecialization(specId); + + // The SetSpecialization function will run these functions if the pet's spec is not 0 + if (GetSpecialization() == 0) + { + CleanupActionBar(); // remove unknown spells from action bar after load + owner.PetSpellInitialize(); + } + + SetGroupUpdateFlag(GroupUpdatePetFlags.Full); + + if (getPetType() == PetType.Hunter) + { + stmt = DB.Characters.GetPreparedStatement(CharStatements.SEL_PET_DECLINED_NAME); + stmt.AddValue(0, owner.GetGUID().GetCounter()); + stmt.AddValue(1, GetCharmInfo().GetPetNumber()); + result = DB.Characters.Query(stmt); + + if (!result.IsEmpty()) + { + _declinedname = new DeclinedName(); + for (byte i = 0; i < SharedConst.MaxDeclinedNameCases; ++i) + { + _declinedname.name[i] = result.Read(i); + } + } + } + + //set last used pet number (for use in BG's) + if (owner.IsTypeId(TypeId.Player) && isControlled() && !isTemporarySummoned() && (getPetType() == PetType.Summon || getPetType() == PetType.Hunter)) + owner.ToPlayer().SetLastPetNumber(petId); + + m_loading = false; + + return true; + } + + public void SavePetToDB(PetSaveMode mode) + { + if (GetEntry() == 0) + return; + + // save only fully controlled creature + if (!isControlled()) + return; + + // not save not player pets + if (!GetOwnerGUID().IsPlayer()) + return; + + Player owner = GetOwner(); + if (owner == null) + return; + + // not save pet as current if another pet temporary unsummoned + if (mode == PetSaveMode.AsCurrent && owner.GetTemporaryUnsummonedPetNumber() != 0 && + owner.GetTemporaryUnsummonedPetNumber() != GetCharmInfo().GetPetNumber()) + { + // pet will lost anyway at restore temporary unsummoned + if (getPetType() == PetType.Hunter) + return; + + // for warlock case + mode = PetSaveMode.NotInSlot; + } + + uint curhealth = (uint)GetHealth(); + int curmana = GetPower(PowerType.Mana); + + SQLTransaction trans = new SQLTransaction(); + // save auras before possibly removing them + _SaveAuras(trans); + + // stable and not in slot saves + if (mode > PetSaveMode.AsCurrent) + RemoveAllAuras(); + + _SaveSpells(trans); + GetSpellHistory().SaveToDB(trans); + DB.Characters.CommitTransaction(trans); + + // current/stable/not_in_slot + if (mode >= PetSaveMode.AsCurrent) + { + ulong ownerLowGUID = GetOwnerGUID().GetCounter(); + trans = new SQLTransaction(); + + // remove current data + PreparedStatement stmt = DB.Characters.GetPreparedStatement(CharStatements.DEL_CHAR_PET_BY_ID); + stmt.AddValue(0, GetCharmInfo().GetPetNumber()); + trans.Append(stmt); + + // prevent duplicate using slot (except PET_SAVE_NOT_IN_SLOT) + if (mode <= PetSaveMode.LastStableSlot) + { + stmt = DB.Characters.GetPreparedStatement(CharStatements.UPD_CHAR_PET_SLOT_BY_SLOT); + stmt.AddValue(0, PetSaveMode.NotInSlot); + stmt.AddValue(1, ownerLowGUID); + stmt.AddValue(2, mode); + trans.Append(stmt); + } + + // prevent existence another hunter pet in PET_SAVE_AS_CURRENT and PET_SAVE_NOT_IN_SLOT + if (getPetType() == PetType.Hunter && (mode == PetSaveMode.AsCurrent || mode > PetSaveMode.LastStableSlot)) + { + stmt = DB.Characters.GetPreparedStatement(CharStatements.DEL_CHAR_PET_BY_SLOT); + stmt.AddValue(0, ownerLowGUID); + stmt.AddValue(1, PetSaveMode.AsCurrent); + stmt.AddValue(2, PetSaveMode.LastStableSlot); + trans.Append(stmt); + } + + // save pet + stmt = DB.Characters.GetPreparedStatement(CharStatements.INS_PET); + stmt.AddValue(0, GetCharmInfo().GetPetNumber()); + stmt.AddValue(1, GetEntry()); + stmt.AddValue(2, ownerLowGUID); + stmt.AddValue(3, GetNativeDisplayId()); + stmt.AddValue(4, getLevel()); + stmt.AddValue(5, GetUInt32Value(UnitFields.PetExperience)); + stmt.AddValue(6, GetReactState()); + stmt.AddValue(7, mode); + stmt.AddValue(8, GetName()); + stmt.AddValue(9, HasByteFlag(UnitFields.Bytes2, UnitBytes2Offsets.PetFlags, UnitPetFlags.CanBeRenamed) ? 0 : 1); + stmt.AddValue(10, curhealth); + stmt.AddValue(11, curmana); + stmt.AddValue(12, GenerateActionBarData()); + stmt.AddValue(13, Time.UnixTime); + stmt.AddValue(14, GetUInt32Value(UnitFields.CreatedBySpell)); + stmt.AddValue(15, getPetType()); + stmt.AddValue(16, m_petSpecialization); + trans.Append(stmt); + + DB.Characters.CommitTransaction(trans); + } + // delete + else + { + RemoveAllAuras(); + DeleteFromDB(GetCharmInfo().GetPetNumber()); + } + } + + public static void DeleteFromDB(uint guidlow) + { + SQLTransaction trans = new SQLTransaction(); + + PreparedStatement stmt = DB.Characters.GetPreparedStatement(CharStatements.DEL_CHAR_PET_BY_ID); + stmt.AddValue(0, guidlow); + trans.Append(stmt); + + stmt = DB.Characters.GetPreparedStatement(CharStatements.DEL_CHAR_PET_DECLINEDNAME); + stmt.AddValue(0, guidlow); + trans.Append(stmt); + + stmt = DB.Characters.GetPreparedStatement(CharStatements.DEL_PET_AURA_EFFECTS); + stmt.AddValue(0, guidlow); + trans.Append(stmt); + + stmt = DB.Characters.GetPreparedStatement(CharStatements.DEL_PET_AURAS); + stmt.AddValue(0, guidlow); + trans.Append(stmt); + + stmt = DB.Characters.GetPreparedStatement(CharStatements.DEL_PET_SPELLS); + stmt.AddValue(0, guidlow); + trans.Append(stmt); + + stmt = DB.Characters.GetPreparedStatement(CharStatements.DEL_PET_SPELL_COOLDOWNS); + stmt.AddValue(0, guidlow); + trans.Append(stmt); + + stmt = DB.Characters.GetPreparedStatement(CharStatements.DEL_PET_SPELL_CHARGES); + stmt.AddValue(0, guidlow); + trans.Append(stmt); + + DB.Characters.CommitTransaction(trans); + } + + public override void setDeathState(DeathState s) + { + base.setDeathState(s); + if (getDeathState() == DeathState.Corpse) + { + if (getPetType() == PetType.Hunter) + { + // pet corpse non lootable and non skinnable + SetUInt32Value(ObjectFields.DynamicFlags, (uint)UnitDynFlags.None); + RemoveFlag(UnitFields.Flags, UnitFlags.Skinnable); + } + } + else if (getDeathState() == DeathState.Alive) + { + CastPetAuras(true); + } + } + + public override void Update(uint diff) + { + if (m_removed) // pet already removed, just wait in remove queue, no updates + return; + + if (m_loading) + return; + + switch (m_deathState) + { + case DeathState.Corpse: + { + if (getPetType() != PetType.Hunter || m_corpseRemoveTime <= Time.UnixTime) + { + Remove(PetSaveMode.NotInSlot); //hunters' pets never get removed because of death, NEVER! + return; + } + break; + } + case DeathState.Alive: + { + // unsummon pet that lost owner + Player owner = GetOwner(); + if (owner == null || (!IsWithinDistInMap(owner, GetMap().GetVisibilityRange()) && !isPossessed()) || (isControlled() && owner.GetPetGUID().IsEmpty())) + { + Remove(PetSaveMode.NotInSlot, true); + return; + } + + if (isControlled()) + { + if (owner.GetPetGUID() != GetGUID()) + { + Log.outError(LogFilter.Pet, "Pet {0} is not pet of owner {1}, removed", GetEntry(), GetOwner().GetName()); + Remove(getPetType() == PetType.Hunter ? PetSaveMode.AsDeleted : PetSaveMode.NotInSlot); + return; + } + } + + if (m_duration > 0) + { + if (m_duration > diff) + m_duration -= (int)diff; + else + { + Remove(getPetType() != PetType.Summon ? PetSaveMode.AsDeleted : PetSaveMode.NotInSlot); + return; + } + } + + //regenerate focus for hunter pets or energy for deathknight's ghoul + if (m_focusRegenTimer != 0) + { + if (m_focusRegenTimer > diff) + m_focusRegenTimer -= diff; + else + { + switch (getPowerType()) + { + case PowerType.Focus: + Regenerate(PowerType.Focus); + m_focusRegenTimer += PetFocusRegenInterval - diff; + if (m_focusRegenTimer == 0) + ++m_focusRegenTimer; + + // Reset if large diff (lag) causes focus to get 'stuck' + if (m_focusRegenTimer > PetFocusRegenInterval) + m_focusRegenTimer = PetFocusRegenInterval; + break; + default: + m_focusRegenTimer = 0; + break; + } + } + } + break; + } + default: + break; + } + base.Update(diff); + } + + public void Remove(PetSaveMode mode, bool returnreagent = false) + { + GetOwner().RemovePet(this, mode, returnreagent); + } + + public void GivePetXP(uint xp) + { + if (getPetType() != PetType.Hunter) + return; + + if (xp < 1) + return; + + if (!IsAlive()) + return; + + uint maxlevel = Math.Min(WorldConfig.GetUIntValue(WorldCfg.MaxPlayerLevel), GetOwner().getLevel()); + uint petlevel = getLevel(); + + // If pet is detected to be at, or above(?) the players level, don't hand out XP + if (petlevel >= maxlevel) + return; + + uint nextLvlXP = GetUInt32Value(UnitFields.PetNextLevelExp); + uint curXP = GetUInt32Value(UnitFields.PetExperience); + uint newXP = curXP + xp; + + // Check how much XP the pet should receive, and hand off have any left from previous levelups + while (newXP >= nextLvlXP && petlevel < maxlevel) + { + // Subtract newXP from amount needed for nextlevel, and give pet the level + newXP -= nextLvlXP; + ++petlevel; + + GivePetLevel((int)petlevel); + + nextLvlXP = GetUInt32Value(UnitFields.PetNextLevelExp); + } + // Not affected by special conditions - give it new XP + SetUInt32Value(UnitFields.PetExperience, petlevel < maxlevel ? newXP : 0); + } + + public void GivePetLevel(int level) + { + if (level == 0 || level == getLevel()) + return; + + if (getPetType() == PetType.Hunter) + { + SetUInt32Value(UnitFields.PetExperience, 0); + SetUInt32Value(UnitFields.PetNextLevelExp, (uint)(Global.ObjectMgr.GetXPForLevel((uint)level) * PetXPFactor)); + } + + InitStatsForLevel((uint)level); + InitLevelupSpellsForLevel(); + } + + public bool CreateBaseAtCreature(Creature creature) + { + Contract.Assert(creature); + + if (!CreateBaseAtTamed(creature.GetCreatureTemplate(), creature.GetMap())) + return false; + + Relocate(creature.GetPositionX(), creature.GetPositionY(), creature.GetPositionZ(), creature.GetOrientation()); + + if (!IsPositionValid()) + { + Log.outError(LogFilter.Pet, "Pet (guidlow {0}, entry {1}) not created base at creature. Suggested coordinates isn't valid (X: {2} Y: {3})", + GetGUID().ToString(), GetEntry(), GetPositionX(), GetPositionY()); + return false; + } + + CreatureTemplate cinfo = GetCreatureTemplate(); + if (cinfo == null) + { + Log.outError(LogFilter.Pet, "CreateBaseAtCreature() failed, creatureInfo is missing!"); + return false; + } + + SetDisplayId(creature.GetDisplayId()); + CreatureFamilyRecord cFamily = CliDB.CreatureFamilyStorage.LookupByKey(cinfo.Family); + if (cFamily != null) + SetName(cFamily.Name[GetOwner().GetSession().GetSessionDbcLocale()]); + else + SetName(creature.GetName(Global.WorldMgr.GetDefaultDbcLocale())); + + return true; + } + + public bool CreateBaseAtCreatureInfo(CreatureTemplate cinfo, Unit owner) + { + if (!CreateBaseAtTamed(cinfo, owner.GetMap())) + return false; + + CreatureFamilyRecord cFamily = CliDB.CreatureFamilyStorage.LookupByKey(cinfo.Family); + if (cFamily != null) + SetName(cFamily.Name[GetOwner().GetSession().GetSessionDbcLocale()]); + + Relocate(owner.GetPositionX(), owner.GetPositionY(), owner.GetPositionZ(), owner.GetOrientation()); + return true; + } + + bool CreateBaseAtTamed(CreatureTemplate cinfo, Map map) + { + Log.outDebug(LogFilter.Pet, "CreateBaseForTamed"); + if (!Create(map.GenerateLowGuid(HighGuid.Pet), map, cinfo.Entry)) + return false; + + setPowerType(PowerType.Focus); + SetUInt32Value(UnitFields.PetNameTimestamp, 0); + SetUInt32Value(UnitFields.PetExperience, 0); + SetUInt32Value(UnitFields.PetNextLevelExp, (uint)(Global.ObjectMgr.GetXPForLevel(getLevel() + 1) * PetXPFactor)); + SetUInt64Value(UnitFields.NpcFlags, (ulong)NPCFlags.None); + + if (cinfo.CreatureType == CreatureType.Beast) + { + SetByteValue(UnitFields.Bytes0, 1, (byte)Class.Warrior); + SetByteValue(UnitFields.Bytes0, 3, (byte)Gender.None); + SetUInt32Value(UnitFields.DisplayPower, (uint)PowerType.Focus); + SetSheath(SheathState.Melee); + SetByteFlag(UnitFields.Bytes2, UnitBytes2Offsets.PetFlags, (UnitPetFlags.CanBeRenamed | UnitPetFlags.CanBeAbandoned)); + } + + return true; + } + + public bool HaveInDiet(ItemTemplate item) + { + if (item.FoodType == 0) + return false; + + CreatureTemplate cInfo = GetCreatureTemplate(); + if (cInfo == null) + return false; + + CreatureFamilyRecord cFamily = CliDB.CreatureFamilyStorage.LookupByKey(cInfo.Family); + if (cFamily == null) + return false; + + uint diet = cFamily.PetFoodMask; + uint FoodMask = (uint)(1 << ((int)item.FoodType - 1)); + return diet.HasAnyFlag(FoodMask); + } + + public uint GetCurrentFoodBenefitLevel(uint itemlevel) + { + // -5 or greater food level + if (getLevel() <= itemlevel + 5) //possible to feed level 60 pet with level 55 level food for full effect + return 35000; + // -10..-6 + else if (getLevel() <= itemlevel + 10) //pure guess, but sounds good + return 17000; + // -14..-11 + else if (getLevel() <= itemlevel + 14) //level 55 food gets green on 70, makes sense to me + return 8000; + // -15 or less + else + return 0; //food too low level + } + + void _LoadSpellCooldowns() + { + PreparedStatement stmt = DB.Characters.GetPreparedStatement(CharStatements.SEL_PET_SPELL_COOLDOWN); + stmt.AddValue(0, GetCharmInfo().GetPetNumber()); + SQLResult cooldownsResult = DB.Characters.Query(stmt); + + stmt = DB.Characters.GetPreparedStatement(CharStatements.SEL_PET_SPELL_CHARGES); + SQLResult chargesResult = DB.Characters.Query(stmt); + + GetSpellHistory().LoadFromDB(cooldownsResult, chargesResult); + } + + void _LoadSpells() + { + PreparedStatement stmt = DB.Characters.GetPreparedStatement(CharStatements.SEL_PET_SPELL); + stmt.AddValue(0, GetCharmInfo().GetPetNumber()); + SQLResult result = DB.Characters.Query(stmt); + + if (!result.IsEmpty()) + { + do + { + addSpell(result.Read(0), (ActiveStates)result.Read(1), PetSpellState.Unchanged); + } + while (result.NextRow()); + } + } + + void _SaveSpells(SQLTransaction trans) + { + foreach (var pair in m_spells.ToList()) + { + // prevent saving family passives to DB + if (pair.Value.type == PetSpellType.Family) + continue; + + PreparedStatement stmt; + + switch (pair.Value.state) + { + case PetSpellState.Removed: + stmt = DB.Characters.GetPreparedStatement(CharStatements.DEL_PET_SPELL_BY_SPELL); + stmt.AddValue(0, GetCharmInfo().GetPetNumber()); + stmt.AddValue(1, pair.Key); + trans.Append(stmt); + + m_spells.Remove(pair.Key); + continue; + case PetSpellState.Changed: + stmt = DB.Characters.GetPreparedStatement(CharStatements.DEL_PET_SPELL_BY_SPELL); + stmt.AddValue(0, GetCharmInfo().GetPetNumber()); + stmt.AddValue(1, pair.Key); + trans.Append(stmt); + + stmt = DB.Characters.GetPreparedStatement(CharStatements.INS_PET_SPELL); + stmt.AddValue(0, GetCharmInfo().GetPetNumber()); + stmt.AddValue(1, pair.Key); + stmt.AddValue(2, pair.Value.active); + trans.Append(stmt); + break; + case PetSpellState.New: + stmt = DB.Characters.GetPreparedStatement(CharStatements.INS_PET_SPELL); + stmt.AddValue(0, GetCharmInfo().GetPetNumber()); + stmt.AddValue(1, pair.Key); + stmt.AddValue(2, pair.Value.active); + trans.Append(stmt); + break; + case PetSpellState.Unchanged: + continue; + } + pair.Value.state = PetSpellState.Unchanged; + } + } + + void _LoadAuras(uint timediff) + { + Log.outDebug(LogFilter.Pet, "Loading auras for {0}", GetGUID().ToString()); + + PreparedStatement stmt = DB.Characters.GetPreparedStatement(CharStatements.SEL_PET_AURA_EFFECT); + stmt.AddValue(0, GetCharmInfo().GetPetNumber()); + + ObjectGuid casterGuid = new ObjectGuid(); + ObjectGuid itemGuid = new ObjectGuid(); + Dictionary effectInfo = new Dictionary(); + SQLResult result = DB.Characters.Query(stmt); + if (!result.IsEmpty()) + { + do + { + uint effectIndex = result.Read(3); + if (effectIndex < SpellConst.MaxEffects) + { + casterGuid.SetRawValue(result.Read(0).ToByteArray()); + if (casterGuid.IsEmpty()) + casterGuid = GetGUID(); + + AuraKey key = new AuraKey(casterGuid, itemGuid, result.Read(1), result.Read(2)); + if (!effectInfo.ContainsKey(key)) + effectInfo[key] = new AuraLoadEffectInfo(); + + var info = effectInfo[key]; + info.Amounts[effectIndex] = result.Read(4); + info.BaseAmounts[effectIndex] = result.Read(5); + } + } while (result.NextRow()); + } + + stmt = DB.Characters.GetPreparedStatement(CharStatements.SEL_PET_AURA); + stmt.AddValue(0, GetCharmInfo().GetPetNumber()); + result = DB.Characters.Query(stmt); + if (!result.IsEmpty()) + { + do + { + // NULL guid stored - pet is the caster of the spell - see Pet._SaveAuras + casterGuid.SetRawValue(result.Read(0).ToByteArray()); + if (casterGuid.IsEmpty()) + casterGuid = GetGUID(); + + AuraKey key = new AuraKey(casterGuid, itemGuid, result.Read(1), result.Read(2)); + uint recalculateMask = result.Read(3); + byte stackCount = result.Read(4); + int maxDuration = result.Read(5); + int remainTime = result.Read(6); + byte remainCharges = result.Read(7); + + SpellInfo spellInfo = Global.SpellMgr.GetSpellInfo(key.SpellId); + if (spellInfo == null) + { + Log.outError(LogFilter.Pet, "Unknown aura (spellid {0}), ignore.", key.SpellId); + continue; + } + + // negative effects should continue counting down after logout + if (remainTime != -1 && !spellInfo.IsPositive()) + { + if (remainTime / Time.InMilliseconds <= timediff) + continue; + + remainTime -= (int)timediff * Time.InMilliseconds; + } + + // prevent wrong values of remaincharges + if (spellInfo.ProcCharges != 0) + { + if (remainCharges <= 0) + remainCharges = (byte)spellInfo.ProcCharges; + } + else + remainCharges = 0; + + var info = effectInfo[key]; + ObjectGuid castId = ObjectGuid.Create(HighGuid.Cast, SpellCastSource.Normal, GetMapId(), spellInfo.Id, GetMap().GenerateLowGuid(HighGuid.Cast)); + Aura aura = Aura.TryCreate(spellInfo, castId, key.EffectMask, this, null, info.BaseAmounts, null, casterGuid); + if (aura != null) + { + if (!aura.CanBeSaved()) + { + aura.Remove(); + continue; + } + aura.SetLoadedState(maxDuration, remainTime, remainCharges, stackCount, recalculateMask, info.Amounts); + aura.ApplyForTargets(); + Log.outInfo(LogFilter.Pet, "Added aura spellid {0}, effectmask {1}", spellInfo.Id, key.EffectMask); + } + } + while (result.NextRow()); + } + } + + void _SaveAuras(SQLTransaction trans) + { + PreparedStatement stmt = DB.Characters.GetPreparedStatement(CharStatements.DEL_PET_AURA_EFFECTS); + stmt.AddValue(0, GetCharmInfo().GetPetNumber()); + trans.Append(stmt); + + stmt = DB.Characters.GetPreparedStatement(CharStatements.DEL_PET_AURAS); + stmt.AddValue(0, GetCharmInfo().GetPetNumber()); + trans.Append(stmt); + + byte index; + foreach (var pair in GetOwnedAuras()) + { + Aura aura = pair.Value; + + // check if the aura has to be saved + if (!aura.CanBeSaved() || IsPetAura(aura)) + continue; + + uint recalculateMask; + AuraKey key = aura.GenerateKey(out recalculateMask); + + // don't save guid of caster in case we are caster of the spell - guid for pet is generated every pet load, so it won't match saved guid anyways + if (key.Caster == GetGUID()) + key.Caster.Clear(); + + index = 0; + stmt = DB.Characters.GetPreparedStatement(CharStatements.INS_PET_AURA); + stmt.AddValue(index++, GetCharmInfo().GetPetNumber()); + stmt.AddValue(index++, key.Caster.GetRawValue()); + stmt.AddValue(index++, key.SpellId); + stmt.AddValue(index++, key.EffectMask); + stmt.AddValue(index++, recalculateMask); + stmt.AddValue(index++, aura.GetStackAmount()); + stmt.AddValue(index++, aura.GetMaxDuration()); + stmt.AddValue(index++, aura.GetDuration()); + stmt.AddValue(index++, aura.GetCharges()); + trans.Append(stmt); + + foreach (AuraEffect effect in aura.GetAuraEffects()) + { + if (effect != null) + { + index = 0; + stmt = DB.Characters.GetPreparedStatement(CharStatements.INS_PET_AURA_EFFECT); + stmt.AddValue(index++, GetCharmInfo().GetPetNumber()); + stmt.AddValue(index++, key.Caster.GetRawValue()); + stmt.AddValue(index++, key.SpellId); + stmt.AddValue(index++, key.EffectMask); + stmt.AddValue(index++, effect.GetEffIndex()); + stmt.AddValue(index++, effect.GetAmount()); + stmt.AddValue(index++, effect.GetBaseAmount()); + trans.Append(stmt); + } + } + } + } + + bool addSpell(uint spellId, ActiveStates active = ActiveStates.Decide, PetSpellState state = PetSpellState.New, PetSpellType type = PetSpellType.Normal) + { + SpellInfo spellInfo = Global.SpellMgr.GetSpellInfo(spellId); + if (spellInfo == null) + { + // do pet spell book cleanup + if (state == PetSpellState.Unchanged) // spell load case + { + Log.outError(LogFilter.Pet, "addSpell: Non-existed in SpellStore spell #{0} request, deleting for all pets in `pet_spell`.", spellId); + + PreparedStatement stmt = DB.Characters.GetPreparedStatement(CharStatements.DEL_INVALID_PET_SPELL); + + stmt.AddValue(0, spellId); + + DB.Characters.Execute(stmt); + } + else + Log.outError(LogFilter.Pet, "addSpell: Non-existed in SpellStore spell #{0} request.", spellId); + + return false; + } + + var petSpell = m_spells.LookupByKey(spellId); + if (petSpell != null) + { + if (petSpell.state == PetSpellState.Removed) + { + m_spells.Remove(spellId); + state = PetSpellState.Changed; + } + else if (state == PetSpellState.Unchanged && petSpell.state != PetSpellState.Unchanged) + { + // can be in case spell loading but learned at some previous spell loading + petSpell.state = PetSpellState.Unchanged; + + if (active == ActiveStates.Enabled) + ToggleAutocast(spellInfo, true); + else if (active == ActiveStates.Disabled) + ToggleAutocast(spellInfo, false); + + return false; + } + else + return false; + } + + PetSpell newspell = new PetSpell(); + newspell.state = state; + newspell.type = type; + + if (active == ActiveStates.Decide) // active was not used before, so we save it's autocast/passive state here + { + if (spellInfo.IsAutocastable()) + newspell.active = ActiveStates.Disabled; + else + newspell.active = ActiveStates.Passive; + } + else + newspell.active = active; + + // talent: unlearn all other talent ranks (high and low) + if (spellInfo.IsRanked()) + { + foreach (var pair in m_spells) + { + if (pair.Value.state == PetSpellState.Removed) + continue; + + SpellInfo oldRankSpellInfo = Global.SpellMgr.GetSpellInfo(pair.Key); + + if (oldRankSpellInfo == null) + continue; + + if (spellInfo.IsDifferentRankOf(oldRankSpellInfo)) + { + // replace by new high rank + if (spellInfo.IsHighRankOf(oldRankSpellInfo)) + { + newspell.active = pair.Value.active; + + if (newspell.active == ActiveStates.Enabled) + ToggleAutocast(oldRankSpellInfo, false); + + unlearnSpell(pair.Key, false, false); + break; + } + // ignore new lesser rank + else + return false; + } + } + } + + m_spells[spellId] = newspell; + + if (spellInfo.IsPassive() && (spellInfo.CasterAuraState == 0 || HasAuraState(spellInfo.CasterAuraState))) + CastSpell(this, spellId, true); + else + GetCharmInfo().AddSpellToActionBar(spellInfo); + + if (newspell.active == ActiveStates.Enabled) + ToggleAutocast(spellInfo, true); + + return true; + } + + public bool learnSpell(uint spell_id) + { + // prevent duplicated entires in spell book + if (!addSpell(spell_id)) + return false; + + if (!m_loading) + { + PetLearnedSpells packet = new PetLearnedSpells(); + packet.Spells.Add(spell_id); + GetOwner().SendPacket(packet); + GetOwner().PetSpellInitialize(); + } + return true; + } + + void learnSpells(List spellIds) + { + PetLearnedSpells packet = new PetLearnedSpells(); + + foreach (uint spell in spellIds) + { + if (!addSpell(spell)) + continue; + + packet.Spells.Add(spell); + } + + if (!m_loading) + GetOwner().SendPacket(packet); + } + + void InitLevelupSpellsForLevel() + { + uint level = getLevel(); + var levelupSpells = GetCreatureTemplate().Family != 0 ? Global.SpellMgr.GetPetLevelupSpellList(GetCreatureTemplate().Family) : null; + if (levelupSpells != null) + { + // PetLevelupSpellSet ordered by levels, process in reversed order + foreach (var pair in levelupSpells) + { + // will called first if level down + if (pair.Key > level) + unlearnSpell(pair.Value, true); // will learn prev rank if any + // will called if level up + else + learnSpell(pair.Value); // will unlearn prev rank if any + } + } + + // default spells (can be not learned if pet level (as owner level decrease result for example) less first possible in normal game) + PetDefaultSpellsEntry defSpells = Global.SpellMgr.GetPetDefaultSpellsEntry((int)GetEntry()); + if (defSpells != null) + { + for (byte i = 0; i < SharedConst.MaxCreatureSpellDataSlots; ++i) + { + SpellInfo spellInfo = Global.SpellMgr.GetSpellInfo(defSpells.spellid[i]); + if (spellInfo == null) + continue; + + // will called first if level down + if (spellInfo.SpellLevel > level) + unlearnSpell(spellInfo.Id, true); + // will called if level up + else + learnSpell(spellInfo.Id); + } + } + } + + bool unlearnSpell(uint spell_id, bool learn_prev, bool clear_ab = true) + { + if (removeSpell(spell_id, learn_prev, clear_ab)) + { + if (!m_loading) + { + PetUnlearnedSpells packet = new PetUnlearnedSpells(); + packet.Spells.Add(spell_id); + GetOwner().SendPacket(packet); + } + return true; + } + return false; + } + + void unlearnSpells(List spellIds, bool learn_prev, bool clear_ab) + { + PetUnlearnedSpells packet = new PetUnlearnedSpells(); + + foreach (uint spell in spellIds) + { + if (!removeSpell(spell, learn_prev, clear_ab)) + continue; + + packet.Spells.Add(spell); + } + + if (!m_loading) + GetOwner().SendPacket(packet); + } + + public bool removeSpell(uint spell_id, bool learn_prev, bool clear_ab = true) + { + var petSpell = m_spells.LookupByKey(spell_id); + if (petSpell == null) + return false; + + if (petSpell.state == PetSpellState.Removed) + return false; + + if (petSpell.state == PetSpellState.New) + m_spells.Remove(spell_id); + else + petSpell.state = PetSpellState.Removed; + + RemoveAurasDueToSpell(spell_id); + + if (learn_prev) + { + uint prev_id = Global.SpellMgr.GetPrevSpellInChain(spell_id); + if (prev_id != 0) + learnSpell(prev_id); + else + learn_prev = false; + } + + // if remove last rank or non-ranked then update action bar at server and client if need + if (GetCharmInfo().RemoveSpellFromActionBar(spell_id) && !learn_prev && clear_ab) + { + if (!m_loading) + { + // need update action bar for last removed rank + Unit owner = GetOwner(); + if (owner) + if (owner.IsTypeId(TypeId.Player)) + owner.ToPlayer().PetSpellInitialize(); + } + } + + return true; + } + + void CleanupActionBar() + { + for (byte i = 0; i < SharedConst.ActionBarIndexMax; ++i) + { + UnitActionBarEntry ab = GetCharmInfo().GetActionBarEntry(i); + if (ab != null) + if (ab.GetAction() != 0 && ab.IsActionBarForSpell()) + { + if (!HasSpell(ab.GetAction())) + GetCharmInfo().SetActionBar(i, 0, ActiveStates.Passive); + else if (ab.GetActiveState() == ActiveStates.Enabled) + { + SpellInfo spellInfo = Global.SpellMgr.GetSpellInfo(ab.GetAction()); + if (spellInfo != null) + ToggleAutocast(spellInfo, true); + } + } + } + } + + public void InitPetCreateSpells() + { + GetCharmInfo().InitPetActionBar(); + m_spells.Clear(); + + LearnPetPassives(); + InitLevelupSpellsForLevel(); + + CastPetAuras(false); + } + + public void ToggleAutocast(SpellInfo spellInfo, bool apply) + { + if (!spellInfo.IsAutocastable()) + return; + + var petSpell = m_spells.LookupByKey(spellInfo.Id); + if (petSpell == null) + return; + + var hasSpell = m_autospells.Contains(spellInfo.Id); + + if (apply) + { + if (!hasSpell) + { + m_autospells.Add(spellInfo.Id); + + if (petSpell.active != ActiveStates.Enabled) + { + petSpell.active = ActiveStates.Enabled; + if (petSpell.state != PetSpellState.New) + petSpell.state = PetSpellState.Changed; + } + } + } + else + { + if (hasSpell) + { + m_autospells.Remove(spellInfo.Id); + if (petSpell.active != ActiveStates.Disabled) + { + petSpell.active = ActiveStates.Disabled; + if (petSpell.state != PetSpellState.New) + petSpell.state = PetSpellState.Changed; + } + } + } + } + + public bool IsPermanentPetFor(Player owner) + { + switch (getPetType()) + { + case PetType.Summon: + switch (owner.GetClass()) + { + case Class.Warlock: + return GetCreatureTemplate().CreatureType == CreatureType.Demon; + case Class.Deathknight: + return GetCreatureTemplate().CreatureType == CreatureType.Undead; + case Class.Mage: + return GetCreatureTemplate().CreatureType == CreatureType.Elemental; + default: + return false; + } + case PetType.Hunter: + return true; + default: + return false; + } + } + + public bool Create(ulong guidlow, Map map, uint Entry) + { + Contract.Assert(map); + SetMap(map); + + _Create(ObjectGuid.Create(HighGuid.Pet, map.GetId(), Entry, guidlow)); + + m_spawnId = guidlow; + m_originalEntry = Entry; + + if (!InitEntry(Entry)) + return false; + + // Force regen flag for player pets, just like we do for players themselves + SetFlag(UnitFields.Flags2, UnitFlags2.RegeneratePower); + SetSheath(SheathState.Melee); + + return true; + } + + public override bool HasSpell(uint spell) + { + var petSpell = m_spells.LookupByKey(spell); + return petSpell != null && petSpell.state != PetSpellState.Removed; + } + + // Get all passive spells in our skill line + void LearnPetPassives() + { + CreatureTemplate cInfo = GetCreatureTemplate(); + if (cInfo == null) + return; + + CreatureFamilyRecord cFamily = CliDB.CreatureFamilyStorage.LookupByKey(cInfo.Family); + if (cFamily == null) + return; + + var petStore = Global.SpellMgr.PetFamilySpellsStorage.LookupByKey(cInfo.Family); + if (petStore != null) + { + // For general hunter pets skill 270 + // Passive 01~10, Passive 00 (20782, not used), Ferocious Inspiration (34457) + // Scale 01~03 (34902~34904, bonus from owner, not used) + foreach (var petSet in petStore) + addSpell(petSet, ActiveStates.Decide, PetSpellState.New, PetSpellType.Family); + } + } + + void CastPetAuras(bool current) + { + Unit owner = GetOwner(); + if (!owner || !owner.IsTypeId(TypeId.Player)) + return; + + if (!IsPermanentPetFor(owner.ToPlayer())) + return; + + foreach (var pa in owner.m_petAuras) + { + if (!current && pa.IsRemovedOnChangePet()) + owner.RemovePetAura(pa); + else + CastPetAura(pa); + } + } + + public void CastPetAura(PetAura aura) + { + uint auraId = aura.GetAura(GetEntry()); + if (auraId == 0) + return; + + if (auraId == 35696) // Demonic Knowledge + { + int basePoints = MathFunctions.CalculatePct(aura.GetDamage(), GetStat(Stats.Stamina) + GetStat(Stats.Intellect)); + CastCustomSpell(this, auraId, basePoints, 0, 0, true); + } + else + CastSpell(this, auraId, true); + } + + bool IsPetAura(Aura aura) + { + Unit owner = GetOwner(); + + if (!owner || !owner.IsTypeId(TypeId.Player)) + return false; + + // if the owner has that pet aura, return true + foreach (var petAura in owner.m_petAuras) + { + if (petAura.GetAura(GetEntry()) == aura.GetId()) + return true; + } + return false; + } + + void learnSpellHighRank(uint spellid) + { + learnSpell(spellid); + uint next = Global.SpellMgr.GetNextSpellInChain(spellid); + if (next != 0) + learnSpellHighRank(next); + } + + public void SynchronizeLevelWithOwner() + { + Unit owner = GetOwner(); + if (!owner || !owner.IsTypeId(TypeId.Player)) + return; + + switch (getPetType()) + { + // always same level + case PetType.Summon: + case PetType.Hunter: + GivePetLevel((int)owner.getLevel()); + break; + default: + break; + } + } + + public new Player GetOwner() + { + return base.GetOwner().ToPlayer(); + } + + public override void SetDisplayId(uint modelId) + { + base.SetDisplayId(modelId); + + if (!isControlled()) + return; + + SetGroupUpdateFlag(GroupUpdatePetFlags.ModelId); + } + + public PetType getPetType() { return m_petType; } + void setPetType(PetType type) { m_petType = type; } + public bool isControlled() { return getPetType() == PetType.Summon || getPetType() == PetType.Hunter; } + public bool isTemporarySummoned() { return m_duration > 0; } + + public override bool IsLoading() { return m_loading; } + + public override byte GetPetAutoSpellSize() { return (byte)m_autospells.Count; } + public override uint GetPetAutoSpellOnPos(byte pos) + { + if (pos >= m_autospells.Count) + return 0; + else + return m_autospells[pos]; + } + + public void SetDuration(uint dur) { m_duration = (int)dur; } + public int GetDuration() { return m_duration; } + + public ushort GetSpecialization() { return m_petSpecialization; } + + public GroupUpdatePetFlags GetGroupUpdateFlag() { return m_groupUpdateMask; } + public void SetGroupUpdateFlag(GroupUpdatePetFlags flag) + { + if (GetOwner().GetGroup()) + { + m_groupUpdateMask |= flag; + GetOwner().SetGroupUpdateFlag(GroupUpdateFlags.Pet); + } + } + public void ResetGroupUpdateFlag() + { + m_groupUpdateMask = GroupUpdatePetFlags.None; + if (GetOwner().GetGroup()) + GetOwner().RemoveGroupUpdateFlag(GroupUpdateFlags.Pet); + } + + void LearnSpecializationSpells() + { + List learnedSpells = new List(); + + List specSpells = Global.DB2Mgr.GetSpecializationSpells(m_petSpecialization); + if (specSpells != null) + { + foreach (var specSpell in specSpells) + { + SpellInfo spellInfo = Global.SpellMgr.GetSpellInfo(specSpell.SpellID); + if (spellInfo == null || spellInfo.SpellLevel > getLevel()) + continue; + + learnedSpells.Add(specSpell.SpellID); + } + } + + learnSpells(learnedSpells); + } + + void RemoveSpecializationSpells(bool clearActionBar) + { + List unlearnedSpells = new List(); + + for (uint i = 0; i < PlayerConst.MaxSpecializations; ++i) + { + ChrSpecializationRecord specialization = Global.DB2Mgr.GetChrSpecializationByIndex(0, i); + if (specialization != null) + { + List specSpells = Global.DB2Mgr.GetSpecializationSpells(specialization.Id); + if (specSpells != null) + { + foreach (var specSpell in specSpells) + unlearnedSpells.Add(specSpell.SpellID); + } + } + + ChrSpecializationRecord specialization1 = Global.DB2Mgr.GetChrSpecializationByIndex(Class.Max, i); + if (specialization1 != null) + { + List specSpells = Global.DB2Mgr.GetSpecializationSpells(specialization1.Id); + if (specSpells != null) + { + foreach (var specSpell in specSpells) + unlearnedSpells.Add(specSpell.SpellID); + } + } + } + + unlearnSpells(unlearnedSpells, true, clearActionBar); + } + + public void SetSpecialization(uint spec) + { + if (m_petSpecialization == spec) + return; + + // remove all the old spec's specalization spells, set the new spec, then add the new spec's spells + // clearActionBars is false because we'll be updating the pet actionbar later so we don't have to do it now + RemoveSpecializationSpells(false); + if (!CliDB.ChrSpecializationStorage.ContainsKey(spec)) + { + m_petSpecialization = 0; + return; + } + + m_petSpecialization = (ushort)spec; + LearnSpecializationSpells(); + + // resend SMSG_PET_SPELLS_MESSAGE to remove old specialization spells from the pet action bar + CleanupActionBar(); + GetOwner().PetSpellInitialize(); + + SetPetSpecialization setPetSpecialization = new SetPetSpecialization(); + setPetSpecialization.SpecID = m_petSpecialization; + GetOwner().SendPacket(setPetSpecialization); + } + + string GenerateActionBarData() + { + StringBuilder ss = new StringBuilder(); + + for (byte i = SharedConst.ActionBarIndexStart; i < SharedConst.ActionBarIndexEnd; ++i) + { + ss.AppendFormat("{0} {1} ", (uint)GetCharmInfo().GetActionBarEntry(i).GetActiveState(), (uint)GetCharmInfo().GetActionBarEntry(i).GetAction()); + } + + return ss.ToString(); + } + + public DeclinedName GetDeclinedNames() { return _declinedname; } + + public new Dictionary m_spells = new Dictionary(); + List m_autospells = new List(); + public bool m_removed; + + PetType m_petType; + int m_duration; // time until unsummon (used mostly for summoned guardians and not used for controlled pets) + bool m_loading; + uint m_focusRegenTimer; + GroupUpdatePetFlags m_groupUpdateMask; + + DeclinedName _declinedname; + ushort m_petSpecialization; + } + public class PetSpell + { + public ActiveStates active; + public PetSpellState state; + public PetSpellType type; + } + + public enum ActiveStates + { + Passive = 0x01, // 0x01 - passive + Disabled = 0x81, // 0x80 - castable + Enabled = 0xC1, // 0x40 | 0x80 - auto cast + castable + Command = 0x07, // 0x01 | 0x02 | 0x04 + Reaction = 0x06, // 0x02 | 0x04 + Decide = 0x00 // custom + } +} diff --git a/Game/Entities/Player/CinematicManager.cs b/Game/Entities/Player/CinematicManager.cs new file mode 100644 index 000000000..18d18fd36 --- /dev/null +++ b/Game/Entities/Player/CinematicManager.cs @@ -0,0 +1,192 @@ +/* + * Copyright (C) 2012-2017 CypherCore + * + * 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, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +using Framework.Constants; +using Game.DataStorage; +using System; +using System.Collections.Generic; +using System.Linq; + +namespace Game.Entities +{ + public class CinematicManager : IDisposable + { + public CinematicManager(Player playerref) + { + player = playerref; + m_cinematicDiff = 0; + m_lastCinematicCheck = 0; + m_activeCinematicCameraId = 0; + m_cinematicLength = 0; + m_cinematicCamera = null; + m_remoteSightPosition = new Position(0.0f, 0.0f, 0.0f); + m_CinematicObject = null; + } + + public virtual void Dispose() + { + if (m_cinematicCamera != null && m_activeCinematicCameraId != 0) + EndCinematic(); + } + + public void BeginCinematic() + { + // Sanity check for active camera set + if (m_activeCinematicCameraId == 0) + return; + + var list = M2Storage.GetFlyByCameras(m_activeCinematicCameraId); + if (!list.Empty()) + { + // Initialize diff, and set camera + m_cinematicDiff = 0; + m_cinematicCamera = list; + + if (!m_cinematicCamera.Empty()) + { + FlyByCamera firstCamera = m_cinematicCamera.FirstOrDefault(); + Position pos = new Position(firstCamera.locations.X, firstCamera.locations.Y, firstCamera.locations.Z, firstCamera.locations.W); + if (!pos.IsPositionValid()) + return; + + player.GetMap().LoadGrid(firstCamera.locations.X, firstCamera.locations.Y); + m_CinematicObject = player.SummonCreature(1, pos.posX, pos.posY, pos.posZ, 0.0f, TempSummonType.TimedDespawn, 5 * Time.Minute * Time.InMilliseconds); + if (m_CinematicObject) + { + m_CinematicObject.setActive(true); + player.SetViewpoint(m_CinematicObject, true); + } + + // Get cinematic length + m_cinematicLength = m_cinematicCamera.LastOrDefault().timeStamp; + } + } + } + + public void EndCinematic() + { + if (m_activeCinematicCameraId == 0) + return; + + m_cinematicDiff = 0; + m_cinematicCamera = null; + m_activeCinematicCameraId = 0; + if (m_CinematicObject) + { + WorldObject vpObject = player.GetViewpoint(); + if (vpObject) + if (vpObject == m_CinematicObject) + player.SetViewpoint(m_CinematicObject, false); + + m_CinematicObject.AddObjectToRemoveList(); + } + } + + public void UpdateCinematicLocation(uint diff) + { + if (m_activeCinematicCameraId == 0 || m_cinematicCamera == null || m_cinematicCamera.Count == 0) + return; + + Position lastPosition = new Position(); + uint lastTimestamp = 0; + Position nextPosition = new Position(); + uint nextTimestamp = 0; + + // Obtain direction of travel + foreach (FlyByCamera cam in m_cinematicCamera) + { + if (cam.timeStamp > m_cinematicDiff) + { + nextPosition = new Position(cam.locations.X, cam.locations.Y, cam.locations.Z, cam.locations.W); + nextTimestamp = cam.timeStamp; + break; + } + lastPosition = new Position(cam.locations.X, cam.locations.Y, cam.locations.Z, cam.locations.W); + lastTimestamp = cam.timeStamp; + } + float angle = lastPosition.GetAngle(nextPosition); + angle -= lastPosition.GetOrientation(); + if (angle < 0) + angle += 2 * MathFunctions.PI; + + // Look for position around 2 second ahead of us. + uint workDiff = m_cinematicDiff; + + // Modify result based on camera direction (Humans for example, have the camera point behind) + workDiff += (uint)((2 * Time.InMilliseconds) * Math.Cos(angle)); + + // Get an iterator to the last entry in the cameras, to make sure we don't go beyond the end + var endItr = m_cinematicCamera.LastOrDefault(); + if (endItr != null && workDiff > endItr.timeStamp) + workDiff = endItr.timeStamp; + + // Never try to go back in time before the start of cinematic! + if (workDiff < 0) + workDiff = m_cinematicDiff; + + // Obtain the previous and next waypoint based on timestamp + foreach (FlyByCamera cam in m_cinematicCamera) + { + if (cam.timeStamp >= workDiff) + { + nextPosition = new Position(cam.locations.X, cam.locations.Y, cam.locations.Z, cam.locations.W); + nextTimestamp = cam.timeStamp; + break; + } + lastPosition = new Position(cam.locations.X, cam.locations.Y, cam.locations.Z, cam.locations.W); + lastTimestamp = cam.timeStamp; + } + + // Never try to go beyond the end of the cinematic + if (workDiff > nextTimestamp) + workDiff = nextTimestamp; + + // Interpolate the position for this moment in time (or the adjusted moment in time) + uint timeDiff = nextTimestamp - lastTimestamp; + uint interDiff = workDiff - lastTimestamp; + float xDiff = nextPosition.posX - lastPosition.posX; + float yDiff = nextPosition.posY - lastPosition.posY; + float zDiff = nextPosition.posZ - lastPosition.posZ; + Position interPosition = new Position(lastPosition.posX + (xDiff * (interDiff / timeDiff)), lastPosition.posY + + (yDiff * (interDiff / timeDiff)), lastPosition.posZ + (zDiff * (interDiff / timeDiff))); + + // Advance (at speed) to this position. The remote sight object is used + // to send update information to player in cinematic + if (m_CinematicObject && interPosition.IsPositionValid()) + m_CinematicObject.MonsterMoveWithSpeed(interPosition.posX, interPosition.posY, interPosition.posZ, 500.0f, false, true); + + // If we never received an end packet 10 seconds after the final timestamp then force an end + if (m_cinematicDiff > m_cinematicLength + 10 * Time.InMilliseconds) + EndCinematic(); + } + + uint GetActiveCinematicCamera() { return m_activeCinematicCameraId; } + public void SetActiveCinematicCamera(uint cinematicCameraId = 0) { m_activeCinematicCameraId = cinematicCameraId; } + public bool IsOnCinematic() { return (m_cinematicCamera != null); } + + // Remote location information + Player player; + + public uint m_cinematicDiff; + public uint m_lastCinematicCheck; + public uint m_activeCinematicCameraId; + public uint m_cinematicLength; + List m_cinematicCamera; + Position m_remoteSightPosition; + TempSummon m_CinematicObject; + } +} diff --git a/Game/Entities/Player/CollectionManager.cs b/Game/Entities/Player/CollectionManager.cs new file mode 100644 index 000000000..03c801dea --- /dev/null +++ b/Game/Entities/Player/CollectionManager.cs @@ -0,0 +1,801 @@ +/* + * Copyright (C) 2012-2017 CypherCore + * + * 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, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +using Framework.Constants; +using Framework.Database; +using Game.DataStorage; +using Game.Network.Packets; +using System; +using System.Collections.Generic; +using System.Linq; + +namespace Game.Entities +{ + public class CollectionMgr + { + public static void LoadMountDefinitions() + { + uint oldMSTime = Time.GetMSTime(); + + SQLResult result = DB.World.Query("SELECT spellId, otherFactionSpellId FROM mount_definitions"); + if (result.IsEmpty()) + { + Log.outInfo(LogFilter.ServerLoading, "Loaded 0 mount definitions. DB table `mount_definitions` is empty."); + return; + } + + do + { + uint spellId = result.Read(0); + uint otherFactionSpellId = result.Read(1); + + if (Global.DB2Mgr.GetMount(spellId) == null) + { + Log.outError(LogFilter.Sql, "Mount spell {0} defined in `mount_definitions` does not exist in Mount.db2, skipped", spellId); + continue; + } + + if (otherFactionSpellId != 0 && Global.DB2Mgr.GetMount(otherFactionSpellId) == null) + { + Log.outError(LogFilter.Sql, "otherFactionSpellId {0} defined in `mount_definitions` for spell {1} does not exist in Mount.db2, skipped", otherFactionSpellId, spellId); + continue; + } + + FactionSpecificMounts[spellId] = otherFactionSpellId; + } while (result.NextRow()); + + Log.outInfo(LogFilter.ServerLoading, "Loaded {0} mount definitions in {1} ms", FactionSpecificMounts.Count, Time.GetMSTimeDiffToNow(oldMSTime)); + } + + public CollectionMgr(WorldSession owner) + { + _owner = owner; + _appearances = new System.Collections.BitSet(0); + } + + public void LoadToys() + { + foreach (var value in _toys.Keys) + _owner.GetPlayer().AddDynamicValue(PlayerDynamicFields.Toys, value); + } + + public bool AddToy(uint itemId, bool isFavourite = false) + { + if (UpdateAccountToys(itemId, isFavourite)) + { + _owner.GetPlayer().AddDynamicValue(PlayerDynamicFields.Toys, itemId); + return true; + } + + return false; + } + + public void LoadAccountToys(SQLResult result) + { + if (result.IsEmpty()) + return; + + do + { + uint itemId = result.Read(0); + bool isFavourite = result.Read(1); + + _toys[itemId] = isFavourite; + } while (result.NextRow()); + } + + public void SaveAccountToys(SQLTransaction trans) + { + PreparedStatement stmt = null; + foreach (var pair in _toys) + { + stmt = DB.Login.GetPreparedStatement(LoginStatements.REP_ACCOUNT_TOYS); + stmt.AddValue(0, _owner.GetBattlenetAccountId()); + stmt.AddValue(1, pair.Key); + stmt.AddValue(2, pair.Value); + trans.Append(stmt); + } + } + + bool UpdateAccountToys(uint itemId, bool isFavourite = false) + { + if (_toys.ContainsKey(itemId)) + return false; + + _toys.Add(itemId, isFavourite); + return true; + } + + public void ToySetFavorite(uint itemId, bool favorite) + { + if (!_toys.ContainsKey(itemId)) + return; + + _toys[itemId] = favorite; + } + + public void OnItemAdded(Item item) + { + if (Global.DB2Mgr.GetHeirloomByItemId(item.GetEntry()) != null) + AddHeirloom(item.GetEntry(), 0); + + AddItemAppearance(item); + } + + public void LoadAccountHeirlooms(SQLResult result) + { + if (result.IsEmpty()) + return; + + do + { + uint itemId = result.Read(0); + HeirloomPlayerFlags flags = (HeirloomPlayerFlags)result.Read(1); + + HeirloomRecord heirloom = Global.DB2Mgr.GetHeirloomByItemId(itemId); + if (heirloom == null) + continue; + + uint bonusId = 0; + + if (flags.HasAnyFlag(HeirloomPlayerFlags.BonusLevel110)) + bonusId = heirloom.ItemBonusListID[2]; + else if (flags.HasAnyFlag(HeirloomPlayerFlags.BonusLevel100)) + bonusId = heirloom.ItemBonusListID[1]; + else if (flags.HasAnyFlag(HeirloomPlayerFlags.BonusLevel90)) + bonusId = heirloom.ItemBonusListID[0]; + + _heirlooms[itemId] = new HeirloomData(flags, bonusId); + } while (result.NextRow()); + } + + public void SaveAccountHeirlooms(SQLTransaction trans) + { + PreparedStatement stmt; + foreach (var heirloom in _heirlooms) + { + stmt = DB.Login.GetPreparedStatement(LoginStatements.REP_ACCOUNT_HEIRLOOMS); + stmt.AddValue(0, _owner.GetBattlenetAccountId()); + stmt.AddValue(1, heirloom.Key); + stmt.AddValue(2, heirloom.Value.flags); + trans.Append(stmt); + } + } + + bool UpdateAccountHeirlooms(uint itemId, HeirloomPlayerFlags flags) + { + if (_heirlooms.ContainsKey(itemId)) + return false; + + _heirlooms.Add(itemId, new HeirloomData(flags, 0)); + return true; + } + + public uint GetHeirloomBonus(uint itemId) + { + var data = _heirlooms.LookupByKey(itemId); + if (data != null) + return data.bonusId; + + return 0; + } + + public void LoadHeirlooms() + { + foreach (var item in _heirlooms) + { + _owner.GetPlayer().AddDynamicValue(PlayerDynamicFields.Heirlooms, item.Key); + _owner.GetPlayer().AddDynamicValue(PlayerDynamicFields.HeirloomsFlags, (uint)item.Value.flags); + } + } + + public void AddHeirloom(uint itemId, HeirloomPlayerFlags flags) + { + if (UpdateAccountHeirlooms(itemId, flags)) + { + _owner.GetPlayer().AddDynamicValue(PlayerDynamicFields.Heirlooms, itemId); + _owner.GetPlayer().AddDynamicValue(PlayerDynamicFields.HeirloomsFlags, (uint)flags); + } + } + + public void UpgradeHeirloom(uint itemId, uint castItem) + { + Player player = _owner.GetPlayer(); + if (!player) + return; + + HeirloomRecord heirloom = Global.DB2Mgr.GetHeirloomByItemId(itemId); + if (heirloom == null) + return; + + var data = _heirlooms.LookupByKey(itemId); + if (data == null) + return; + + HeirloomPlayerFlags flags = data.flags; + uint bonusId = 0; + + if (heirloom.UpgradeItemID[0] == castItem) + { + flags |= HeirloomPlayerFlags.BonusLevel90; + bonusId = heirloom.ItemBonusListID[0]; + } + if (heirloom.UpgradeItemID[1] == castItem) + { + flags |= HeirloomPlayerFlags.BonusLevel100; + bonusId = heirloom.ItemBonusListID[1]; + } + if (heirloom.UpgradeItemID[2] == castItem) + { + flags |= HeirloomPlayerFlags.BonusLevel110; + bonusId = heirloom.ItemBonusListID[2]; + } + + foreach (Item item in player.GetItemListByEntry(itemId, true)) + item.AddBonuses(bonusId); + + // Get heirloom offset to update only one part of dynamic field + var heirloomsFields = player.GetDynamicKeyAndValues(PlayerDynamicFields.Heirlooms); + ushort offset = (ushort)heirloomsFields.FirstOrDefault(p => p.Value == itemId).Key; + + player.SetDynamicValue(PlayerDynamicFields.HeirloomsFlags, offset, (uint)flags); + data.flags = flags; + data.bonusId = bonusId; + } + + public void CheckHeirloomUpgrades(Item item) + { + Player player = _owner.GetPlayer(); + if (!player) + return; + + // Check already owned heirloom for upgrade kits + HeirloomRecord heirloom = Global.DB2Mgr.GetHeirloomByItemId(item.GetEntry()); + if (heirloom != null) + { + var data = _heirlooms.LookupByKey(item.GetEntry()); + if (data == null) + return; + + // Check for heirloom pairs (normal - heroic, heroic - mythic) + uint heirloomItemId = heirloom.NextDifficultyItemID; + uint newItemId = 0; + HeirloomRecord heirloomDiff; + while ((heirloomDiff = Global.DB2Mgr.GetHeirloomByItemId(heirloomItemId)) != null) + { + if (player.GetItemByEntry(heirloomDiff.ItemID)) + newItemId = heirloomDiff.ItemID; + + HeirloomRecord heirloomSub = Global.DB2Mgr.GetHeirloomByItemId(heirloomDiff.NextDifficultyItemID); + if (heirloomSub != null) + { + heirloomItemId = heirloomSub.ItemID; + continue; + } + + break; + } + + if (newItemId != 0) + { + var heirloomsFields = player.GetDynamicKeyAndValues(PlayerDynamicFields.Heirlooms); + ushort offset = (ushort)heirloomsFields.FirstOrDefault(p => p.Value == item.GetEntry()).Key; + + player.SetDynamicValue(PlayerDynamicFields.Heirlooms, offset, newItemId); + player.SetDynamicValue(PlayerDynamicFields.HeirloomsFlags, offset, 0); + + _heirlooms.Remove(item.GetEntry()); + _heirlooms[newItemId] = null; + + return; + } + + var fields = item.GetDynamicValues(ItemDynamicFields.BonusListIds); + foreach (uint bonusId in fields) + if (bonusId != data.bonusId) + item.ClearDynamicValue(ItemDynamicFields.BonusListIds); + + if (!fields.Contains(data.bonusId)) + item.AddBonuses(data.bonusId); + } + } + + public bool CanApplyHeirloomXpBonus(uint itemId, uint level) + { + if (Global.DB2Mgr.GetHeirloomByItemId(itemId) == null) + return false; + + var data = _heirlooms.LookupByKey(itemId); + if (data == null) + return false; + + if (data.flags.HasAnyFlag(HeirloomPlayerFlags.BonusLevel110)) + return level <= 110; + if (data.flags.HasAnyFlag(HeirloomPlayerFlags.BonusLevel100)) + return level <= 100; + if (data.flags.HasAnyFlag(HeirloomPlayerFlags.BonusLevel90)) + return level <= 90; + + return level <= 60; + } + + public void LoadMounts() + { + foreach (var m in _mounts.ToList()) + AddMount(m.Key, m.Value, false, false); + } + + public void LoadAccountMounts(SQLResult result) + { + if (result.IsEmpty()) + return; + + do + { + uint mountSpellId = result.Read(0); + MountStatusFlags flags = (MountStatusFlags)result.Read(1); + + if (Global.DB2Mgr.GetMount(mountSpellId) == null) + continue; + + _mounts[mountSpellId] = flags; + } while (result.NextRow()); + } + + public void SaveAccountMounts(SQLTransaction trans) + { + foreach (var mount in _mounts) + { + PreparedStatement stmt = DB.Login.GetPreparedStatement(LoginStatements.REP_ACCOUNT_MOUNTS); + stmt.AddValue(0, _owner.GetBattlenetAccountId()); + stmt.AddValue(1, mount.Key); + stmt.AddValue(2, mount.Value); + trans.Append(stmt); + } + } + + public bool AddMount(uint spellId, MountStatusFlags flags, bool factionMount = false, bool learned = false) + { + Player player = _owner.GetPlayer(); + if (!player) + return false; + + MountRecord mount = Global.DB2Mgr.GetMount(spellId); + if (mount == null) + return false; + + var value = FactionSpecificMounts.LookupByKey(spellId); + if (value != 0 && !factionMount) + AddMount(value, flags, true, learned); + + _mounts[spellId] = flags; + + // Mount condition only applies to using it, should still learn it. + if (mount.PlayerConditionId != 0) + { + PlayerConditionRecord playerCondition = CliDB.PlayerConditionStorage.LookupByKey(mount.PlayerConditionId); + if (!ConditionManager.IsPlayerMeetingCondition(player, playerCondition)) + return false; + } + + if (!learned) + { + if (!factionMount) + SendSingleMountUpdate(spellId, flags); + if (!player.HasSpell(spellId)) + player.LearnSpell(spellId, true); + } + + return true; + } + + public void MountSetFavorite(uint spellId, bool favorite) + { + if (!_mounts.ContainsKey(spellId)) + return; + + if (favorite) + _mounts[spellId] |= MountStatusFlags.IsFavorite; + else + _mounts[spellId] &= ~MountStatusFlags.IsFavorite; + + SendSingleMountUpdate(spellId, _mounts[spellId]); + } + + void SendSingleMountUpdate(uint spellId, MountStatusFlags mountStatusFlags) + { + Player player = _owner.GetPlayer(); + if (!player) + return; + + AccountMountUpdate mountUpdate = new AccountMountUpdate(); + mountUpdate.IsFullUpdate = false; + mountUpdate.Mounts.Add(spellId, mountStatusFlags); + player.SendPacket(mountUpdate); + } + + public void LoadItemAppearances() + { + foreach (uint blockValue in _appearances.ToBlockRange()) + { + _owner.GetPlayer().AddDynamicValue(PlayerDynamicFields.Transmog, blockValue); + } + + foreach (var value in _temporaryAppearances.Keys) + _owner.GetPlayer().AddDynamicValue(PlayerDynamicFields.ConditionalTransmog, value); + } + + public void LoadAccountItemAppearances(SQLResult knownAppearances, SQLResult favoriteAppearances) + { + if (!knownAppearances.IsEmpty()) + { + uint[] blocks = new uint[1]; + do + { + ushort blobIndex = knownAppearances.Read(0); + if (blobIndex >= blocks.Length) + Array.Resize(ref blocks, blobIndex + 1); + + blocks[blobIndex] = knownAppearances.Read(1); + + } while (knownAppearances.NextRow()); + + _appearances = new System.Collections.BitSet(blocks); + } + + if (!favoriteAppearances.IsEmpty()) + { + do + { + _favoriteAppearances[favoriteAppearances.Read(0)] = FavoriteAppearanceState.Unchanged; + } while (favoriteAppearances.NextRow()); + } + + // Static item appearances known by every player + uint[] hiddenAppearanceItems = + { + 134110, // Hidden Helm + 134111, // Hidden Cloak + 134112, // Hidden Shoulder + 142503, // Hidden Shirt + 142504, // Hidden Tabard + 143539 // Hidden Belt + }; + + foreach (uint hiddenItem in hiddenAppearanceItems) + { + ItemModifiedAppearanceRecord hiddenAppearance = Global.DB2Mgr.GetItemModifiedAppearance(hiddenItem, 0); + //ASSERT(hiddenAppearance); + if (_appearances.Length <= hiddenAppearance.Id) + _appearances.Length = (int)hiddenAppearance.Id + 1; + + _appearances.Set((int)hiddenAppearance.Id, true); + } + } + + public void SaveAccountItemAppearances(SQLTransaction trans) + { + PreparedStatement stmt; + ushort blockIndex = 0; + foreach (uint blockValue in _appearances.ToBlockRange()) + { + if (blockValue != 0) // this table is only appended/bits are set (never cleared) so don't save empty blocks + { + stmt = DB.Login.GetPreparedStatement(LoginStatements.INS_BNET_ITEM_APPEARANCES); + stmt.AddValue(0, _owner.GetBattlenetAccountId()); + stmt.AddValue(1, blockIndex); + stmt.AddValue(2, blockValue); + trans.Append(stmt); + } + + ++blockIndex; + } + + foreach (var key in _favoriteAppearances.Keys) + { + var appearanceState = _favoriteAppearances[key]; + switch (appearanceState) + { + case FavoriteAppearanceState.New: + stmt = DB.Login.GetPreparedStatement(LoginStatements.INS_BNET_ITEM_FAVORITE_APPEARANCE); + stmt.AddValue(0, _owner.GetBattlenetAccountId()); + stmt.AddValue(1, key); + trans.Append(stmt); + appearanceState = FavoriteAppearanceState.Unchanged; + break; + case FavoriteAppearanceState.Removed: + stmt = DB.Login.GetPreparedStatement(LoginStatements.DEL_BNET_ITEM_FAVORITE_APPEARANCE); + stmt.AddValue(0, _owner.GetBattlenetAccountId()); + stmt.AddValue(1, key); + trans.Append(stmt); + _favoriteAppearances.Remove(key); + break; + case FavoriteAppearanceState.Unchanged: + break; + } + } + } + + uint[] PlayerClassByArmorSubclass = + { + (int)Class.ClassMaskAllPlayable, //ITEM_SUBCLASS_ARMOR_MISCELLANEOUS + (1 << ((int)Class.Priest - 1)) | (1 << ((int)Class.Mage - 1)) | (1 << ((int)Class.Warlock - 1)), //ITEM_SUBCLASS_ARMOR_CLOTH + (1 << ((int)Class.Rogue - 1)) | (1 << ((int)Class.Monk - 1)) | (1 << ((int)Class.Druid - 1)) | (1 << ((int)Class.DemonHunter - 1)), //ITEM_SUBCLASS_ARMOR_LEATHER + (1 << ((int)Class.Hunter - 1)) | (1 << ((int)Class.Shaman - 1)), //ITEM_SUBCLASS_ARMOR_MAIL + (1 << ((int)Class.Warrior - 1)) | (1 << ((int)Class.Paladin - 1)) | (1 << ((int)Class.Deathknight - 1)), //ITEM_SUBCLASS_ARMOR_PLATE + (int)Class.ClassMaskAllPlayable, //ITEM_SUBCLASS_ARMOR_BUCKLER + (1 << ((int)Class.Warrior - 1)) | (1 << ((int)Class.Paladin - 1)) | (1 << ((int)Class.Shaman - 1)), //ITEM_SUBCLASS_ARMOR_SHIELD + 1 << ((int)Class.Paladin - 1), //ITEM_SUBCLASS_ARMOR_LIBRAM + 1 << ((int)Class.Druid - 1), //ITEM_SUBCLASS_ARMOR_IDOL + 1 << ((int)Class.Shaman - 1), //ITEM_SUBCLASS_ARMOR_TOTEM + 1 << ((int)Class.Deathknight - 1), //ITEM_SUBCLASS_ARMOR_SIGIL + (1 << ((int)Class.Paladin - 1)) | (1 << ((int)Class.Deathknight - 1)) | (1 << ((int)Class.Shaman - 1)) | (1 << ((int)Class.Druid - 1)), //ITEM_SUBCLASS_ARMOR_RELIC + }; + + public void AddItemAppearance(Item item) + { + if (!item.IsSoulBound()) + return; + + ItemModifiedAppearanceRecord itemModifiedAppearance = item.GetItemModifiedAppearance(); + if (!CanAddAppearance(itemModifiedAppearance)) + return; + + if (Convert.ToBoolean(item.GetUInt32Value(ItemFields.Flags) & (uint)(ItemFieldFlags.BopTradeable | ItemFieldFlags.Refundable))) + { + AddTemporaryAppearance(item.GetGUID(), itemModifiedAppearance); + return; + } + + AddItemAppearance(itemModifiedAppearance); + } + + public void AddItemAppearance(uint itemId, uint appearanceModId = 0) + { + ItemModifiedAppearanceRecord itemModifiedAppearance = Global.DB2Mgr.GetItemModifiedAppearance(itemId, appearanceModId); + if (!CanAddAppearance(itemModifiedAppearance)) + return; + + AddItemAppearance(itemModifiedAppearance); + } + + bool CanAddAppearance(ItemModifiedAppearanceRecord itemModifiedAppearance) + { + if (itemModifiedAppearance == null) + return false; + + if (itemModifiedAppearance.SourceType == 6 || itemModifiedAppearance.SourceType == 9) + return false; + + if (!CliDB.ItemSearchNameStorage.ContainsKey(itemModifiedAppearance.ItemID)) + return false; + + ItemTemplate itemTemplate = Global.ObjectMgr.GetItemTemplate(itemModifiedAppearance.ItemID); + if (itemTemplate == null) + return false; + + if (_owner.GetPlayer().CanUseItem(itemTemplate) != InventoryResult.Ok) + return false; + + if (itemTemplate.GetFlags2().HasAnyFlag(ItemFlags2.NoSourceForItemVisual) || itemTemplate.GetQuality() == ItemQuality.Artifact) + return false; + + switch (itemTemplate.GetClass()) + { + case ItemClass.Weapon: + { + if (!Convert.ToBoolean(_owner.GetPlayer().GetWeaponProficiency() & (1 << (int)itemTemplate.GetSubClass()))) + return false; + if (itemTemplate.GetSubClass() == (int)ItemSubClassWeapon.Exotic || + itemTemplate.GetSubClass() == (int)ItemSubClassWeapon.Exotic2 || + itemTemplate.GetSubClass() == (int)ItemSubClassWeapon.Miscellaneous || + itemTemplate.GetSubClass() == (int)ItemSubClassWeapon.Thrown || + itemTemplate.GetSubClass() == (int)ItemSubClassWeapon.Spear || + itemTemplate.GetSubClass() == (int)ItemSubClassWeapon.FishingPole) + return false; + break; + } + case ItemClass.Armor: + { + switch (itemTemplate.GetInventoryType()) + { + case InventoryType.Body: + case InventoryType.Shield: + case InventoryType.Cloak: + case InventoryType.Tabard: + case InventoryType.Holdable: + break; + case InventoryType.Head: + case InventoryType.Shoulders: + case InventoryType.Chest: + case InventoryType.Waist: + case InventoryType.Legs: + case InventoryType.Feet: + case InventoryType.Wrists: + case InventoryType.Hands: + case InventoryType.Robe: + if ((ItemSubClassArmor)itemTemplate.GetSubClass() == ItemSubClassArmor.Miscellaneous) + return false; + break; + default: + return false; + } + if (itemTemplate.GetInventoryType() != InventoryType.Cloak) + if (!Convert.ToBoolean(PlayerClassByArmorSubclass[itemTemplate.GetSubClass()] & _owner.GetPlayer().getClassMask())) + return false; + break; + } + default: + return false; + } + + if (itemTemplate.GetQuality() < ItemQuality.Uncommon) + if (!itemTemplate.GetFlags2().HasAnyFlag(ItemFlags2.IgnoreQualityForItemVisualSource) || !itemTemplate.GetFlags3().HasAnyFlag(ItemFlags3.ActsAsTransmogHiddenVisualOption)) + return false; + + if (itemModifiedAppearance.Id < _appearances.Count && _appearances.Get((int)itemModifiedAppearance.Id)) + return false; + + return true; + } + //todo check this + void AddItemAppearance(ItemModifiedAppearanceRecord itemModifiedAppearance) + { + if (_appearances.Count <= itemModifiedAppearance.Id) + { + uint numBlocks = (uint)(_appearances.Count << 2); + _appearances.Length = (int)itemModifiedAppearance.Id + 1; + numBlocks = (uint)(_appearances.Count << 2) - numBlocks; + while (numBlocks-- != 0) + _owner.GetPlayer().AddDynamicValue(PlayerDynamicFields.Transmog, 0); + } + + _appearances.Set((int)itemModifiedAppearance.Id, true); + uint blockIndex = itemModifiedAppearance.Id / 32; + uint bitIndex = itemModifiedAppearance.Id % 32; + uint currentMask = _owner.GetPlayer().GetDynamicValue(PlayerDynamicFields.Transmog, (ushort)blockIndex); + _owner.GetPlayer().SetDynamicValue(PlayerDynamicFields.Transmog, (ushort)blockIndex, (uint)((int)currentMask | (1 << (int)bitIndex))); + var temporaryAppearance = _temporaryAppearances.LookupByKey(itemModifiedAppearance.Id); + if (!temporaryAppearance.Empty()) + { + _owner.GetPlayer().RemoveDynamicValue(PlayerDynamicFields.ConditionalTransmog, itemModifiedAppearance.Id); + _temporaryAppearances.Remove(itemModifiedAppearance.Id); + } + } + + void AddTemporaryAppearance(ObjectGuid itemGuid, ItemModifiedAppearanceRecord itemModifiedAppearance) + { + var itemsWithAppearance = _temporaryAppearances[itemModifiedAppearance.Id]; + if (itemsWithAppearance.Empty()) + _owner.GetPlayer().AddDynamicValue(PlayerDynamicFields.ConditionalTransmog, itemModifiedAppearance.Id); + + itemsWithAppearance.Add(itemGuid); + } + + public void RemoveTemporaryAppearance(Item item) + { + ItemModifiedAppearanceRecord itemModifiedAppearance = item.GetItemModifiedAppearance(); + if (itemModifiedAppearance == null) + return; + + var guid = _temporaryAppearances.LookupByKey(itemModifiedAppearance.Id); + if (guid.Empty()) + return; + + guid.Remove(item.GetGUID()); + if (guid.Empty()) + { + _owner.GetPlayer().RemoveDynamicValue(PlayerDynamicFields.ConditionalTransmog, itemModifiedAppearance.Id); + _temporaryAppearances.Remove(itemModifiedAppearance.Id); + } + } + + public Tuple HasItemAppearance(uint itemModifiedAppearanceId) + { + if (itemModifiedAppearanceId < _appearances.Count && _appearances.Get((int)itemModifiedAppearanceId)) + return Tuple.Create(true, false); + + if (_temporaryAppearances.ContainsKey(itemModifiedAppearanceId)) + return Tuple.Create(true, true); + + return Tuple.Create(false, false); + } + + public List GetItemsProvidingTemporaryAppearance(uint itemModifiedAppearanceId) + { + return _temporaryAppearances.LookupByKey(itemModifiedAppearanceId); + } + + public void SetAppearanceIsFavorite(uint itemModifiedAppearanceId, bool apply) + { + var apperanceState = _favoriteAppearances.LookupByKey(itemModifiedAppearanceId); + if (apply) + { + if (!_favoriteAppearances.ContainsKey(itemModifiedAppearanceId)) + _favoriteAppearances[itemModifiedAppearanceId] = FavoriteAppearanceState.New; + else if (apperanceState == FavoriteAppearanceState.Removed) + apperanceState = FavoriteAppearanceState.Unchanged; + else + return; + } + else if (_favoriteAppearances.ContainsKey(itemModifiedAppearanceId)) + { + if (apperanceState == FavoriteAppearanceState.New) + _favoriteAppearances.Remove(itemModifiedAppearanceId); + else + apperanceState = FavoriteAppearanceState.Removed; + } + else + return; + + _favoriteAppearances[itemModifiedAppearanceId] = apperanceState; + + TransmogCollectionUpdate transmogCollectionUpdate = new TransmogCollectionUpdate(); + transmogCollectionUpdate.IsFullUpdate = false; + transmogCollectionUpdate.IsSetFavorite = apply; + transmogCollectionUpdate.FavoriteAppearances.Add(itemModifiedAppearanceId); + + _owner.SendPacket(transmogCollectionUpdate); + } + + public void SendFavoriteAppearances() + { + TransmogCollectionUpdate transmogCollectionUpdate = new TransmogCollectionUpdate(); + transmogCollectionUpdate.IsFullUpdate = true; + foreach (var pair in _favoriteAppearances) + if (pair.Value != FavoriteAppearanceState.Removed) + transmogCollectionUpdate.FavoriteAppearances.Add(pair.Key); + + _owner.SendPacket(transmogCollectionUpdate); + } + + public bool HasToy(uint itemId) { return _toys.ContainsKey(itemId); } + public Dictionary GetAccountToys() { return _toys; } + public Dictionary GetAccountHeirlooms() { return _heirlooms; } + public Dictionary GetAccountMounts() { return _mounts; } + + WorldSession _owner; + + Dictionary _toys = new Dictionary(); + Dictionary _heirlooms = new Dictionary(); + Dictionary _mounts = new Dictionary(); + System.Collections.BitSet _appearances; + MultiMap _temporaryAppearances = new MultiMap(); + Dictionary _favoriteAppearances = new Dictionary(); + + static Dictionary FactionSpecificMounts = new Dictionary(); + } + + enum FavoriteAppearanceState + { + New, + Removed, + Unchanged + } + + public class HeirloomData + { + public HeirloomData(HeirloomPlayerFlags _flags = 0, uint _bonusId = 0) + { + flags = _flags; + bonusId = _bonusId; + } + + public HeirloomPlayerFlags flags; + public uint bonusId; + } +} diff --git a/Game/Entities/Player/KillRewarder.cs b/Game/Entities/Player/KillRewarder.cs new file mode 100644 index 000000000..3521f27d6 --- /dev/null +++ b/Game/Entities/Player/KillRewarder.cs @@ -0,0 +1,281 @@ +/* + * Copyright (C) 2012-2017 CypherCore + * + * 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, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +using Framework.Constants; +using Game.DataStorage; +using Game.Groups; +using Game.Maps; +using Game.Scenarios; +using System.Collections.Generic; + +namespace Game.Entities +{ + public class KillRewarder + { + public KillRewarder(Player killer, Unit victim, bool isBattleground) + { + _killer = killer; + _victim = victim; + _group = killer.GetGroup(); + _groupRate = 1.0f; + _maxNotGrayMember = null; + _count = 0; + _sumLevel = 0; + _xp = 0; + _isFullXP = false; + _maxLevel = 0; + _isBattleground = isBattleground; + _isPvP = false; + + // mark the credit as pvp if victim is player + if (victim.IsTypeId(TypeId.Player)) + _isPvP = true; + // or if its owned by player and its not a vehicle + else if (victim.GetCharmerOrOwnerGUID().IsPlayer()) + _isPvP = !victim.IsVehicle(); + + _InitGroupData(); + } + + public void Reward() + { + // 3. Reward killer (and group, if necessary). + if (_group) + // 3.1. If killer is in group, reward group. + _RewardGroup(); + else + { + // 3.2. Reward single killer (not group case). + // 3.2.1. Initialize initial XP amount based on killer's level. + _InitXP(_killer); + // To avoid unnecessary calculations and calls, + // proceed only if XP is not ZERO or player is not on Battleground + // (Battlegroundrewards only XP, that's why). + if (!_isBattleground || _xp != 0) + // 3.2.2. Reward killer. + _RewardPlayer(_killer, false); + } + + // 5. Credit instance encounter. + // 6. Update guild achievements. + // 7. Credit scenario criterias + Creature victim = _victim.ToCreature(); + if (victim != null) + { + if (victim.IsDungeonBoss()) + { + InstanceScript instance = _victim.GetInstanceScript(); + if (instance != null) + instance.UpdateEncounterStateForKilledCreature(_victim.GetEntry(), _victim); + } + + uint guildId = victim.GetMap().GetOwnerGuildId(); + var guild = Global.GuildMgr.GetGuildById(guildId); + if (guild != null) + guild.UpdateCriteria(CriteriaTypes.KillCreature, victim.GetEntry(), 1, 0, victim, _killer); + + Scenario scenario = victim.GetScenario(); + if (scenario != null) + scenario.UpdateCriteria(CriteriaTypes.KillCreature, victim.GetEntry(), 1, 0, victim, _killer); + } + } + + void _InitGroupData() + { + if (_group) + { + // 2. In case when player is in group, initialize variables necessary for group calculations: + for (GroupReference refe = _group.GetFirstMember(); refe != null; refe = refe.next()) + { + Player member = refe.GetSource(); + if (member) + { + if (member.IsAlive() && member.IsAtGroupRewardDistance(_victim)) + { + uint lvl = member.getLevel(); + // 2.1. _count - number of alive group members within reward distance; + ++_count; + // 2.2. _sumLevel - sum of levels of alive group members within reward distance; + _sumLevel += lvl; + // 2.3. _maxLevel - maximum level of alive group member within reward distance; + if (_maxLevel < lvl) + _maxLevel = (byte)lvl; + // 2.4. _maxNotGrayMember - maximum level of alive group member within reward distance, + // for whom victim is not gray; + uint grayLevel = Formulas.GetGrayLevel(lvl); + if (_victim.getLevel() > grayLevel && (!_maxNotGrayMember || _maxNotGrayMember.getLevel() < lvl)) + _maxNotGrayMember = member; + } + } + } + // 2.5. _isFullXP - flag identifying that for all group members victim is not gray, + // so 100% XP will be rewarded (50% otherwise). + _isFullXP = _maxNotGrayMember && (_maxLevel == _maxNotGrayMember.getLevel()); + } + else + _count = 1; + } + + void _InitXP(Player player) + { + // Get initial value of XP for kill. + // XP is given: + // * on Battlegrounds; + // * otherwise, not in PvP; + // * not if killer is on vehicle. + if (_isBattleground || (!_isPvP && _killer.GetVehicle() == null)) + _xp = Formulas.XPGain(player, _victim, _isBattleground); + } + + void _RewardHonor(Player player) + { + // Rewarded player must be alive. + if (player.IsAlive()) + player.RewardHonor(_victim, _count, -1, true); + } + + void _RewardXP(Player player, float rate) + { + uint xp = _xp; + if (_group) + { + // 4.2.1. If player is in group, adjust XP: + // * set to 0 if player's level is more than maximum level of not gray member; + // * cut XP in half if _isFullXP is false. + if (_maxNotGrayMember != null && player.IsAlive() && + _maxNotGrayMember.getLevel() >= player.getLevel()) + xp = _isFullXP ? + (uint)(xp * rate) : // Reward FULL XP if all group members are not gray. + (uint)(xp * rate / 2) + 1; // Reward only HALF of XP if some of group members are gray. + else + xp = 0; + } + if (xp != 0) + { + // 4.2.2. Apply auras modifying rewarded XP (SPELL_AURA_MOD_XP_PCT and SPELL_AURA_MOD_XP_FROM_CREATURE_TYPE). + xp *= (uint)player.GetTotalAuraMultiplier(AuraType.ModXpPct); + xp *= (uint)player.GetTotalAuraMultiplierByMiscValue(AuraType.ModXpFromCreatureType, (int)_victim.GetCreatureType()); + + // 4.2.3. Give XP to player. + player.GiveXP(xp, _victim, _groupRate); + Pet pet = player.GetPet(); + if (pet) + // 4.2.4. If player has pet, reward pet with XP (100% for single player, 50% for group case). + pet.GivePetXP(_group ? xp / 2 : xp); + } + } + + void _RewardReputation(Player player, float rate) + { + // 4.3. Give reputation (player must not be on BG). + // Even dead players and corpses are rewarded. + player.RewardReputation(_victim, rate); + } + + void _RewardKillCredit(Player player) + { + // 4.4. Give kill credit (player must not be in group, or he must be alive or without corpse). + if (!_group || player.IsAlive() || player.GetCorpse() == null) + { + Creature target = _victim.ToCreature(); + if (target != null) + { + player.KilledMonster(target.GetCreatureTemplate(), target.GetGUID()); + player.UpdateCriteria(CriteriaTypes.KillCreatureType, (ulong)target.GetCreatureType(), 1, 0, target); + } + } + } + + void _RewardPlayer(Player player, bool isDungeon) + { + // 4. Reward player. + if (!_isBattleground) + { + // 4.1. Give honor (player must be alive and not on BG). + _RewardHonor(player); + // 4.1.1 Send player killcredit for quests with PlayerSlain + if (_victim.IsTypeId(TypeId.Player)) + player.KilledPlayerCredit(); + } + // Give XP only in PvE or in Battlegrounds. + // Give reputation and kill credit only in PvE. + if (!_isPvP || _isBattleground) + { + float rate = _group ? _groupRate * player.getLevel() / _sumLevel : 1.0f; + if (_xp != 0) + // 4.2. Give XP. + _RewardXP(player, rate); + if (!_isBattleground) + { + // If killer is in dungeon then all members receive full reputation at kill. + _RewardReputation(player, isDungeon ? 1.0f : rate); + _RewardKillCredit(player); + } + } + } + + void _RewardGroup() + { + if (_maxLevel != 0) + { + if (_maxNotGrayMember != null) + // 3.1.1. Initialize initial XP amount based on maximum level of group member, + // for whom victim is not gray. + _InitXP(_maxNotGrayMember); + // To avoid unnecessary calculations and calls, + // proceed only if XP is not ZERO or player is not on Battleground + // (Battlegroundrewards only XP, that's why). + if (!_isBattleground || _xp != 0) + { + bool isDungeon = !_isPvP && CliDB.MapStorage.LookupByKey(_killer.GetMapId()).IsDungeon(); + if (!_isBattleground) + { + // 3.1.2. Alter group rate if group is in raid (not for Battlegrounds). + bool isRaid = !_isPvP && CliDB.MapStorage.LookupByKey(_killer.GetMapId()).IsRaid() && _group.isRaidGroup(); + _groupRate = Formulas.XPInGroupRate(_count, isRaid); + } + // 3.1.3. Reward each group member (even dead or corpse) within reward distance. + for (GroupReference refe = _group.GetFirstMember(); refe != null; refe = refe.next()) + { + Player member = refe.GetSource(); + if (member) + { + if (member.IsAtGroupRewardDistance(_victim)) + { + _RewardPlayer(member, isDungeon); + member.UpdateCriteria(CriteriaTypes.SpecialPvpKill, 1, 0, 0, _victim); + } + } + } + } + } + } + + Player _killer; + Unit _victim; + Group _group; + float _groupRate; + Player _maxNotGrayMember; + uint _count; + uint _sumLevel; + uint _xp; + bool _isFullXP; + byte _maxLevel; + bool _isBattleground; + bool _isPvP; + } +} diff --git a/Game/Entities/Player/Player.Achievement.cs b/Game/Entities/Player/Player.Achievement.cs new file mode 100644 index 000000000..ba01431f4 --- /dev/null +++ b/Game/Entities/Player/Player.Achievement.cs @@ -0,0 +1,89 @@ +/* + * Copyright (C) 2012-2017 CypherCore + * + * 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, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +using Framework.Constants; +using Game.Achievements; +using Game.DataStorage; +using Game.Guilds; +using Game.Scenarios; + +namespace Game.Entities +{ + public partial class Player + { + public void ResetAchievements() + { + m_achievementSys.Reset(); + } + + public void SendRespondInspectAchievements(Player player) + { + m_achievementSys.SendAchievementInfo(player); + } + + public uint GetAchievementPoints() + { + return m_achievementSys.GetAchievementPoints(); + } + public bool HasAchieved(uint achievementId) + { + return m_achievementSys.HasAchieved(achievementId); + } + public void StartCriteriaTimer(CriteriaTimedTypes type, uint entry, uint timeLost = 0) + { + m_achievementSys.StartCriteriaTimer(type, entry, timeLost); + } + + public void RemoveCriteriaTimer(CriteriaTimedTypes type, uint entry) + { + m_achievementSys.RemoveCriteriaTimer(type, entry); + } + + public void ResetCriteria(CriteriaTypes type, ulong miscValue1 = 0, ulong miscValue2 = 0, bool evenIfCriteriaComplete = false) + { + m_achievementSys.ResetCriteria(type, miscValue1, miscValue2, evenIfCriteriaComplete); + } + + public void UpdateCriteria(CriteriaTypes type, ulong miscValue1 = 0, ulong miscValue2 = 0, ulong miscValue3 = 0, Unit unit = null) + { + m_achievementSys.UpdateCriteria(type, miscValue1, miscValue2, miscValue3, unit, this); + + // Update only individual achievement criteria here, otherwise we may get multiple updates + // from a single boss kill + if (CriteriaManager.IsGroupCriteriaType(type)) + return; + + Scenario scenario = GetScenario(); + if (scenario != null) + scenario.UpdateCriteria(type, miscValue1, miscValue2, miscValue3, unit, this); + + Guild guild = Global.GuildMgr.GetGuildById(GetGuildId()); + if (guild) + guild.UpdateCriteria(type, miscValue1, miscValue2, miscValue3, unit, this); + } + + public void CompletedAchievement(AchievementRecord entry) + { + m_achievementSys.CompletedAchievement(entry, this); + } + + public bool ModifierTreeSatisfied(uint modifierTreeId) + { + return m_achievementSys.ModifierTreeSatisfied(modifierTreeId); + } + } +} diff --git a/Game/Entities/Player/Player.Combat.cs b/Game/Entities/Player/Player.Combat.cs new file mode 100644 index 000000000..3cb8fa928 --- /dev/null +++ b/Game/Entities/Player/Player.Combat.cs @@ -0,0 +1,582 @@ +/* + * Copyright (C) 2012-2017 CypherCore + * + * 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, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +using Framework.Constants; +using Game.DataStorage; +using Game.Groups; +using Game.Network.Packets; +using Game.Spells; +using System; +using System.Collections.Generic; + +namespace Game.Entities +{ + public partial class Player + { + void SetRegularAttackTime() + { + for (WeaponAttackType weaponAttackType = 0; weaponAttackType < WeaponAttackType.Max; ++weaponAttackType) + { + Item tmpitem = GetWeaponForAttack(weaponAttackType, true); + if (tmpitem != null && !tmpitem.IsBroken()) + { + ItemTemplate proto = tmpitem.GetTemplate(); + if (proto.GetDelay() != 0) + SetBaseAttackTime(weaponAttackType, proto.GetDelay()); + } + else + SetBaseAttackTime(weaponAttackType, SharedConst.BaseAttackTime); // If there is no weapon reset attack time to base (might have been changed from forms) + } + } + + public void RewardPlayerAndGroupAtKill(Unit victim, bool isBattleground) + { + new KillRewarder(this, victim, isBattleground).Reward(); + } + + public void RewardPlayerAndGroupAtEvent(uint creature_id, WorldObject pRewardSource) + { + if (pRewardSource == null) + return; + ObjectGuid creature_guid = pRewardSource.IsTypeId(TypeId.Unit) ? pRewardSource.GetGUID() : ObjectGuid.Empty; + + // prepare data for near group iteration + Group group = GetGroup(); + if (group) + { + for (GroupReference refe = group.GetFirstMember(); refe != null; refe = refe.next()) + { + Player player = refe.GetSource(); + if (!player) + continue; + + if (!player.IsAtGroupRewardDistance(pRewardSource)) + continue; // member (alive or dead) or his corpse at req. distance + + // quest objectives updated only for alive group member or dead but with not released body + if (player.IsAlive() || !player.GetCorpse()) + player.KilledMonsterCredit(creature_id, creature_guid); + } + } + else + KilledMonsterCredit(creature_id, creature_guid); + } + + public void AddWeaponProficiency(uint newflag) { m_WeaponProficiency |= newflag; } + public void AddArmorProficiency(uint newflag) { m_ArmorProficiency |= newflag; } + public uint GetWeaponProficiency() { return m_WeaponProficiency; } + public uint GetArmorProficiency() { return m_ArmorProficiency; } + public void SendProficiency(ItemClass itemClass, uint itemSubclassMask) + { + SetProficiency packet = new SetProficiency(); + packet.ProficiencyMask = itemSubclassMask; + packet.ProficiencyClass = (byte)itemClass; + SendPacket(packet); + } + + bool CanTitanGrip() { return m_canTitanGrip; } + + public override bool CanUseAttackType(WeaponAttackType attacktype) + { + switch (attacktype) + { + case WeaponAttackType.BaseAttack: + return !HasFlag(UnitFields.Flags, UnitFlags.Disarmed); + case WeaponAttackType.OffAttack: + return !HasFlag(UnitFields.Flags2, UnitFlags2.DisarmOffhand); + case WeaponAttackType.RangedAttack: + return !HasFlag(UnitFields.Flags2, UnitFlags2.DisarmRanged); + } + return true; + } + + float GetRatingMultiplier(CombatRating cr) + { + GtCombatRatingsRecord Rating = CliDB.CombatRatingsGameTable.GetRow(getLevel()); + if (Rating == null) + return 1.0f; + + float value = GetGameTableColumnForCombatRating(Rating, cr); + if (value == 0) + return 1.0f; // By default use minimum coefficient (not must be called) + + return 1.0f / value; + } + public float GetRatingBonusValue(CombatRating cr) + { + float baseResult = GetFloatValue(PlayerFields.CombatRating1 + (int)cr) * GetRatingMultiplier(cr); + if (cr != CombatRating.ResiliencePlayerDamage) + return baseResult; + return (float)(1.0f - Math.Pow(0.99f, baseResult)) * 100.0f; + } + + void GetDodgeFromAgility(float diminishing, float nondiminishing) + { + /*// Table for base dodge values + float[] dodge_base = + { + 0.037580f, // Warrior + 0.036520f, // Paladin + -0.054500f, // Hunter + -0.005900f, // Rogue + 0.031830f, // Priest + 0.036640f, // DK + 0.016750f, // Shaman + 0.034575f, // Mage + 0.020350f, // Warlock + 0.0f, // ?? + 0.049510f // Druid + }; + // Crit/agility to dodge/agility coefficient multipliers; 3.2.0 increased required agility by 15% + float[] crit_to_dodge = + { + 0.85f/1.15f, // Warrior + 1.00f/1.15f, // Paladin + 1.11f/1.15f, // Hunter + 2.00f/1.15f, // Rogue + 1.00f/1.15f, // Priest + 0.85f/1.15f, // DK + 1.60f/1.15f, // Shaman + 1.00f/1.15f, // Mage + 0.97f/1.15f, // Warlock (?) + 0.0f, // ?? + 2.00f/1.15f // Druid + }; + + uint level = getLevel(); + uint pclass = (uint)GetClass(); + + if (level > CliDB.GtChanceToMeleeCritStorage.GetTableRowCount()) + level = CliDB.GtChanceToMeleeCritStorage.GetTableRowCount() - 1; + + // Dodge per agility is proportional to crit per agility, which is available from DBC files + var dodgeRatio = CliDB.GtChanceToMeleeCritStorage.EvaluateTable(level - 1, pclass - 1); + if (dodgeRatio == null || pclass > (int)Class.Max) + return; + + // @todo research if talents/effects that increase total agility by x% should increase non-diminishing part + float base_agility = GetCreateStat(Stats.Agility) * m_auraModifiersGroup[(int)UnitMods.StatAgility][(int)UnitModifierType.BasePCT]; + float bonus_agility = GetStat(Stats.Agility) - base_agility; + + // calculate diminishing (green in char screen) and non-diminishing (white) contribution + diminishing = 100.0f * bonus_agility * dodgeRatio.Value * crit_to_dodge[(int)pclass - 1]; + nondiminishing = 100.0f * (dodge_base[(int)pclass - 1] + base_agility * dodgeRatio.Value * crit_to_dodge[pclass - 1]); + */ + } + + float GetTotalPercentageModValue(BaseModGroup modGroup) + { + return m_auraBaseMod[(int)modGroup][0] + m_auraBaseMod[(int)modGroup][1]; + } + + public float GetExpertiseDodgeOrParryReduction(WeaponAttackType attType) + { + float baseExpertise = 7.5f; + switch (attType) + { + case WeaponAttackType.BaseAttack: + return baseExpertise + GetUInt32Value(PlayerFields.Expertise) / 4.0f; + case WeaponAttackType.OffAttack: + return baseExpertise + GetUInt32Value(PlayerFields.OffhandExpertise) / 4.0f; + default: + break; + } + return 0.0f; + } + + public bool IsUseEquipedWeapon(bool mainhand) + { + // disarm applied only to mainhand weapon + return !IsInFeralForm() && (!mainhand || !HasFlag(UnitFields.Flags, UnitFlags.Disarmed)); + } + + public void SetCanTitanGrip(bool value, uint penaltySpellId = 0) + { + if (value == m_canTitanGrip) + return; + + m_canTitanGrip = value; + m_titanGripPenaltySpellId = penaltySpellId; + } + + void CheckTitanGripPenalty() + { + if (!CanTitanGrip()) + return; + + bool apply = IsUsingTwoHandedWeaponInOneHand(); + if (apply) + { + if (!HasAura(m_titanGripPenaltySpellId)) + CastSpell((Unit)null, m_titanGripPenaltySpellId, true); + } + else + RemoveAurasDueToSpell(m_titanGripPenaltySpellId); + } + + bool IsTwoHandUsed() + { + Item mainItem = GetItemByPos(InventorySlots.Bag0, EquipmentSlot.MainHand); + if (!mainItem) + return false; + + ItemTemplate itemTemplate = mainItem.GetTemplate(); + return (itemTemplate.GetInventoryType() == InventoryType.Weapon2Hand && !CanTitanGrip()) || + itemTemplate.GetInventoryType() == InventoryType.Ranged || + (itemTemplate.GetInventoryType() == InventoryType.RangedRight && itemTemplate.GetClass() == ItemClass.Weapon && (ItemSubClassWeapon)itemTemplate.GetSubClass() != ItemSubClassWeapon.Wand); + } + + bool IsUsingTwoHandedWeaponInOneHand() + { + Item offItem = GetItemByPos(InventorySlots.Bag0, EquipmentSlot.OffHand); + if (offItem && offItem.GetTemplate().GetInventoryType() == InventoryType.Weapon2Hand) + return true; + + Item mainItem = GetItemByPos(InventorySlots.Bag0, EquipmentSlot.MainHand); + if (!mainItem || mainItem.GetTemplate().GetInventoryType() == InventoryType.Weapon2Hand) + return false; + + if (!offItem) + return false; + + return true; + } + + public void _ApplyWeaponDamage(uint slot, Item item, bool apply) + { + ItemTemplate proto = item.GetTemplate(); + WeaponAttackType attType = WeaponAttackType.BaseAttack; + float damage = 0.0f; + + if (slot == EquipmentSlot.MainHand && (proto.GetInventoryType() == InventoryType.Ranged || proto.GetInventoryType() == InventoryType.RangedRight)) + attType = WeaponAttackType.RangedAttack; + else if (slot == EquipmentSlot.OffHand) + attType = WeaponAttackType.OffAttack; + + float minDamage, maxDamage; + item.GetDamage(this, out minDamage, out maxDamage); + + if (minDamage > 0) + { + damage = apply ? minDamage : SharedConst.BaseMinDamage; + SetBaseWeaponDamage(attType, WeaponDamageRange.MinDamage, damage); + } + + if (maxDamage > 0) + { + damage = apply ? maxDamage : SharedConst.BaseMaxDamage; + SetBaseWeaponDamage(attType, WeaponDamageRange.MaxDamage, damage); + } + + SpellShapeshiftFormRecord shapeshift = CliDB.SpellShapeshiftFormStorage.LookupByKey(GetShapeshiftForm()); + if (proto.GetDelay() != 0 && !(shapeshift != null && shapeshift.CombatRoundTime != 0)) + SetBaseAttackTime(attType, apply ? proto.GetDelay() : SharedConst.BaseAttackTime); + + if (CanModifyStats() && (damage != 0 || proto.GetDelay() != 0)) + UpdateDamagePhysical(attType); + } + + public void SetCanParry(bool value) + { + if (m_canParry == value) + return; + + m_canParry = value; + UpdateParryPercentage(); + } + + public void SetCanBlock(bool value) + { + if (m_canBlock == value) + return; + + m_canBlock = value; + UpdateBlockPercentage(); + } + + // duel health and mana reset methods + public void SaveHealthBeforeDuel() { healthBeforeDuel = (uint)GetHealth(); } + public void SaveManaBeforeDuel() { manaBeforeDuel = (uint)GetPower(PowerType.Mana); } + public void RestoreHealthAfterDuel() { SetHealth(healthBeforeDuel); } + public void RestoreManaAfterDuel() { SetPower(PowerType.Mana, (int)manaBeforeDuel); } + + void UpdateDuelFlag(long currTime) + { + if (duel == null || duel.startTimer == 0 || currTime < duel.startTimer + 3) + return; + + Global.ScriptMgr.OnPlayerDuelStart(this, duel.opponent); + + SetUInt32Value(PlayerFields.DuelTeam, 1); + duel.opponent.SetUInt32Value(PlayerFields.DuelTeam, 2); + + duel.startTimer = 0; + duel.startTime = currTime; + duel.opponent.duel.startTimer = 0; + duel.opponent.duel.startTime = currTime; + } + + void CheckDuelDistance(long currTime) + { + if (duel == null) + return; + + ObjectGuid duelFlagGUID = GetGuidValue(PlayerFields.DuelArbiter); + GameObject obj = GetMap().GetGameObject(duelFlagGUID); + if (!obj) + return; + + if (duel.outOfBound == 0) + { + if (!IsWithinDistInMap(obj, 50)) + { + duel.outOfBound = currTime; + SendPacket(new DuelOutOfBounds()); + } + } + else + { + if (IsWithinDistInMap(obj, 40)) + { + duel.outOfBound = 0; + SendPacket(new DuelInBounds()); + } + else if (currTime >= (duel.outOfBound + 10)) + DuelComplete(DuelCompleteType.Fled); + } + } + public void DuelComplete(DuelCompleteType type) + { + // duel not requested + if (duel == null) + return; + + // Check if DuelComplete() has been called already up in the stack and in that case don't do anything else here + if (duel.isCompleted || duel.opponent.duel.isCompleted) + return; + + duel.isCompleted = true; + duel.opponent.duel.isCompleted = true; + + Log.outDebug(LogFilter.Player, "Duel Complete {0} {1}", GetName(), duel.opponent.GetName()); + + DuelComplete duelCompleted = new DuelComplete(); + duelCompleted.Started = type != DuelCompleteType.Interrupted; + duelCompleted.Write(); + SendPacket(duelCompleted, false); + + if (duel.opponent.GetSession() != null) + duel.opponent.SendPacket(duelCompleted, false); + + if (type != DuelCompleteType.Interrupted) + { + DuelWinner duelWinner = new DuelWinner(); + duelWinner.BeatenName = (type == DuelCompleteType.Won ? duel.opponent.GetName() : GetName()); + duelWinner.WinnerName = (type == DuelCompleteType.Won ? GetName() : duel.opponent.GetName()); + duelWinner.BeatenVirtualRealmAddress = Global.WorldMgr.GetVirtualRealmAddress(); + duelWinner.WinnerVirtualRealmAddress = Global.WorldMgr.GetVirtualRealmAddress(); + duelWinner.Fled = type != DuelCompleteType.Won; + + SendMessageToSet(duelWinner, true); + } + + Global.ScriptMgr.OnPlayerDuelEnd(duel.opponent, this, type); + + switch (type) + { + case DuelCompleteType.Fled: + // if initiator and opponent are on the same team + // or initiator and opponent are not PvP enabled, forcibly stop attacking + if (duel.initiator.GetTeam() == duel.opponent.GetTeam()) + { + duel.initiator.AttackStop(); + duel.opponent.AttackStop(); + } + else + { + if (!duel.initiator.IsPvP()) + duel.initiator.AttackStop(); + if (!duel.opponent.IsPvP()) + duel.opponent.AttackStop(); + } + break; + case DuelCompleteType.Won: + UpdateCriteria(CriteriaTypes.LoseDuel, 1); + duel.opponent.UpdateCriteria(CriteriaTypes.WinDuel, 1); + + // Credit for quest Death's Challenge + if (GetClass() == Class.Deathknight && duel.opponent.GetQuestStatus(12733) == QuestStatus.Incomplete) + duel.opponent.CastSpell(duel.opponent, 52994, true); + + // Honor points after duel (the winner) - ImpConfig + int amount = WorldConfig.GetIntValue(WorldCfg.HonorAfterDuel); + if (amount != 0) + duel.opponent.RewardHonor(null, 1, amount); + + break; + default: + break; + } + + // Victory emote spell + if (type != DuelCompleteType.Interrupted) + duel.opponent.CastSpell(duel.opponent, 52852, true); + + //Remove Duel Flag object + GameObject obj = GetMap().GetGameObject(GetGuidValue(PlayerFields.DuelArbiter)); + if (obj) + duel.initiator.RemoveGameObject(obj, true); + + //remove auras + var itsAuras = duel.opponent.GetAppliedAuras(); + foreach (var pair in itsAuras) + { + Aura aura = pair.Value.GetBase(); + if (!pair.Value.IsPositive() && aura.GetCasterGUID() == GetGUID() && aura.GetApplyTime() >= duel.startTime) + duel.opponent.RemoveAura(pair); + } + + var myAuras = GetAppliedAuras(); + foreach (var pair in myAuras) + { + Aura aura = pair.Value.GetBase(); + if (!pair.Value.IsPositive() && aura.GetCasterGUID() == duel.opponent.GetGUID() && aura.GetApplyTime() >= duel.startTime) + RemoveAura(pair); + } + + // cleanup combo points + ClearComboPoints(); + duel.opponent.ClearComboPoints(); + + //cleanups + SetGuidValue(PlayerFields.DuelArbiter, ObjectGuid.Empty); + SetUInt32Value(PlayerFields.DuelTeam, 0); + duel.opponent.SetGuidValue(PlayerFields.DuelArbiter, ObjectGuid.Empty); + duel.opponent.SetUInt32Value(PlayerFields.DuelTeam, 0); + + duel.opponent.duel = null; + duel = null; + } + + //PVP + public void SetPvPDeath(bool on) + { + if (on) + m_ExtraFlags |= PlayerExtraFlags.PVPDeath; + else + m_ExtraFlags &= ~PlayerExtraFlags.PVPDeath; + } + + public void SetContestedPvPTimer(uint newTime) { m_contestedPvPTimer = newTime; } + + public void ResetContestedPvP() + { + ClearUnitState(UnitState.AttackPlayer); + RemoveFlag(PlayerFields.Flags, PlayerFlags.ContestedPVP); + m_contestedPvPTimer = 0; + } + void UpdateAfkReport(long currTime) + { + if (m_bgData.bgAfkReportedTimer <= currTime) + { + m_bgData.bgAfkReportedCount = 0; + m_bgData.bgAfkReportedTimer = currTime + 5 * Time.Minute; + } + } + + public void UpdateContestedPvP(uint diff) + { + if (m_contestedPvPTimer == 0 || IsInCombat()) + return; + + if (m_contestedPvPTimer <= diff) + { + ResetContestedPvP(); + } + else + m_contestedPvPTimer -= diff; + } + + public void UpdatePvPFlag(long currTime) + { + if (!IsPvP()) + return; + + if (pvpInfo.EndTimer == 0 || currTime < (pvpInfo.EndTimer + 300) || pvpInfo.IsHostile) + return; + + UpdatePvP(false); + } + + public void UpdatePvP(bool state, bool Override = false) + { + if (!state || Override) + { + SetPvP(state); + pvpInfo.EndTimer = 0; + } + else + { + pvpInfo.EndTimer = Time.UnixTime; + SetPvP(state); + } + } + + public void UpdatePvPState(bool onlyFFA = false) + { + // @todo should we always synchronize UNIT_FIELD_BYTES_2, 1 of controller and controlled? + // no, we shouldn't, those are checked for affecting player by client + if (!pvpInfo.IsInNoPvPArea && !IsGameMaster() + && (pvpInfo.IsInFFAPvPArea || Global.WorldMgr.IsFFAPvPRealm())) + { + if (!IsFFAPvP()) + { + SetByteFlag(UnitFields.Bytes2, UnitBytes2Offsets.PvpFlag, UnitBytes2Flags.FFAPvp); + foreach (var unit in m_Controlled) + unit.SetByteValue(UnitFields.Bytes2, UnitBytes2Offsets.PvpFlag, (byte)UnitBytes2Flags.FFAPvp); + } + } + else if (IsFFAPvP()) + { + RemoveByteFlag(UnitFields.Bytes2, UnitBytes2Offsets.PvpFlag, UnitBytes2Flags.FFAPvp); + foreach (var unit in m_Controlled) + unit.RemoveByteFlag(UnitFields.Bytes2, UnitBytes2Offsets.PvpFlag, UnitBytes2Flags.FFAPvp); + } + + if (onlyFFA) + return; + + if (pvpInfo.IsHostile) // in hostile area + { + if (!IsPvP() || pvpInfo.EndTimer != 0) + UpdatePvP(true, true); + } + else // in friendly area + { + if (IsPvP() && !HasFlag(PlayerFields.Flags, PlayerFlags.InPVP) && pvpInfo.EndTimer == 0) + pvpInfo.EndTimer = Time.UnixTime; // start toggle-off + } + } + + public override void SetPvP(bool state) + { + base.SetPvP(state); + foreach (var unit in m_Controlled) + unit.SetPvP(state); + } + } +} diff --git a/Game/Entities/Player/Player.DB.cs b/Game/Entities/Player/Player.DB.cs new file mode 100644 index 000000000..5ab61c167 --- /dev/null +++ b/Game/Entities/Player/Player.DB.cs @@ -0,0 +1,4026 @@ +/* + * Copyright (C) 2012-2017 CypherCore + * + * 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, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +using Framework.Collections; +using Framework.Constants; +using Framework.Database; +using Game.Arenas; +using Game.BattleGrounds; +using Game.DataStorage; +using Game.Garrisons; +using Game.Groups; +using Game.Guilds; +using Game.Mails; +using Game.Maps; +using Game.Network.Packets; +using Game.Spells; +using System; +using System.Collections.Generic; +using System.Diagnostics.Contracts; +using System.Linq; +using System.Text; + +namespace Game.Entities +{ + public partial class Player + { + void _LoadInventory(SQLResult result, SQLResult artifactsResult, uint timeDiff) + { + // 0 1 2 3 4 + // SELECT a.itemGuid, a.xp, a.artifactAppearanceId, ap.artifactPowerId, ap.purchasedRank FROM item_instance_artifact_powers ap LEFT JOIN item_instance_artifact a ON ap.itemGuid = a.itemGuid INNER JOIN character_inventory ci ON ci.item = ap.guid WHERE ci.guid = ? + Dictionary>> artifactData = new Dictionary>>(); + //MultiMap artifactPowerData = new MultiMap(); + if (!artifactsResult.IsEmpty()) + { + do + { + var artifactDataEntry = Tuple.Create(artifactsResult.Read(1), artifactsResult.Read(2), new List()); + ItemDynamicFieldArtifactPowers artifactPowerData = new ItemDynamicFieldArtifactPowers(); + artifactPowerData.ArtifactPowerId = artifactsResult.Read(3); + artifactPowerData.PurchasedRank = artifactsResult.Read(4); + + ArtifactPowerRecord artifactPower = CliDB.ArtifactPowerStorage.LookupByKey(artifactPowerData.ArtifactPowerId); + if (artifactPower != null) + { + if (artifactPowerData.PurchasedRank > artifactPower.MaxRank) + artifactPowerData.PurchasedRank = artifactPower.MaxRank; + + artifactPowerData.CurrentRankWithBonus = (byte)(artifactPower.Flags.HasAnyFlag(ArtifactPowerFlag.First) ? 1 : 0); + + artifactDataEntry.Item3.Add(artifactPowerData); + } + + artifactData[ObjectGuid.Create(HighGuid.Item, artifactsResult.Read(0))] = artifactDataEntry; + + } while (artifactsResult.NextRow()); + } + + if (!result.IsEmpty()) + { + uint zoneId = GetZoneId(); + Dictionary bagMap = new Dictionary(); // fast guid lookup for bags + Dictionary invalidBagMap = new Dictionary(); // fast guid lookup for bags + Queue problematicItems = new Queue(); + SQLTransaction trans = new SQLTransaction(); + + // Prevent items from being added to the queue while loading + m_itemUpdateQueueBlocked = true; + do + { + Item item = _LoadItem(trans, zoneId, timeDiff, result.GetFields()); + if (item != null) + { + var artifactDataPair = artifactData.LookupByKey(item.GetGUID()); + if (item.GetTemplate().GetArtifactID() != 0 && artifactDataPair != null) + item.LoadArtifactData(this, artifactDataPair.Item1, artifactDataPair.Item2, artifactDataPair.Item3); + + ulong counter = result.Read(45); + ObjectGuid bagGuid = counter != 0 ? ObjectGuid.Create(HighGuid.Item, counter) : ObjectGuid.Empty; + byte slot = result.Read(46); + + GetSession().GetCollectionMgr().CheckHeirloomUpgrades(item); + GetSession().GetCollectionMgr().AddItemAppearance(item); + + InventoryResult err = InventoryResult.Ok; + if (item.HasFlag(ItemFields.Flags, ItemFieldFlags.Child)) + { + Item parent = GetItemByGuid(item.GetGuidValue(ItemFields.Creator)); + if (parent) + { + parent.SetChildItem(item.GetGUID()); + item.CopyArtifactDataFromParent(parent); + } + else + err = InventoryResult.WrongBagType3; // send by mail + } + + // Item is not in bag + if (bagGuid.IsEmpty()) + { + item.SetContainer(null); + item.SetSlot(slot); + + if (IsInventoryPos(InventorySlots.Bag0, slot)) + { + List dest = new List(); + err = CanStoreItem(InventorySlots.Bag0, slot, dest, item, false); + if (err == InventoryResult.Ok) + item = StoreItem(dest, item, true); + } + else if (IsEquipmentPos(InventorySlots.Bag0, slot)) + { + ushort dest; + + err = CanEquipItem(slot, out dest, item, false, false); + if (err == InventoryResult.Ok) + QuickEquipItem(dest, item); + } + else if (IsBankPos(InventorySlots.Bag0, slot)) + { + List dest = new List(); + err = CanBankItem(InventorySlots.Bag0, slot, dest, item, false, false); + if (err == InventoryResult.Ok) + item = BankItem(dest, item, true); + } + + // Remember bags that may contain items in them + if (err == InventoryResult.Ok) + { + if (IsBagPos(item.GetPos())) + { + Bag pBag = item.ToBag(); + if (pBag != null) + bagMap.Add(item.GetGUID(), pBag); + } + } + else if (IsBagPos(item.GetPos())) + if (item.IsBag()) + invalidBagMap.Add(item.GetGUID(), item); + } + else + { + item.SetSlot(ItemConst.NullSlot); + // Item is in the bag, find the bag + var bag = bagMap.LookupByKey(bagGuid); + if (bag != null) + { + List dest = new List(); + err = CanStoreItem(bag.GetSlot(), slot, dest, item); + if (err == InventoryResult.Ok) + item = StoreItem(dest, item, true); + } + else if (invalidBagMap.ContainsKey(bagGuid)) + { + var invalidBag = invalidBagMap.LookupByKey(bagGuid); + if (problematicItems.Contains(invalidBag)) + err = InventoryResult.InternalBagError; + } + else + { + Log.outError(LogFilter.Player, "LoadInventory: player (GUID: {0}, name: '{1}') has item (GUID: {2}, entry: {3}) which doesnt have a valid bag (Bag GUID: {4}, slot: {5}). Possible cheat?", + GetGUID().ToString(), GetName(), item.GetGUID().ToString(), item.GetEntry(), bagGuid, slot); + item.DeleteFromInventoryDB(trans); + continue; + } + + } + + // Item's state may have changed after storing + if (err == InventoryResult.Ok) + item.SetState(ItemUpdateState.Unchanged, this); + else + { + Log.outError(LogFilter.Player, "LoadInventory: player (GUID: {0}, name: '{1}') has item (GUID: {2}, entry: {3}) which can't be loaded into inventory (Bag GUID: {4}, slot: {5}) by reason {6}. " + + "Item will be sent by mail.", GetGUID().ToString(), GetName(), item.GetGUID().ToString(), item.GetEntry(), bagGuid, slot, err); + item.DeleteFromInventoryDB(trans); + problematicItems.Enqueue(item); + } + } + } while (result.NextRow()); + + m_itemUpdateQueueBlocked = false; + + // Send problematic items by mail + while (problematicItems.Count != 0) + { + string subject = Global.ObjectMgr.GetCypherString(CypherStrings.NotEquippedItem); + MailDraft draft = new MailDraft(subject, "There were problems with equipping item(s)."); + for (int i = 0; problematicItems.Count != 0 && i < SharedConst.MaxMailItems; ++i) + { + draft.AddItem(problematicItems.Dequeue()); + } + draft.SendMailTo(trans, this, new MailSender(this, MailStationery.Gm), MailCheckMask.Copied); + } + + DB.Characters.CommitTransaction(trans); + } + + _ApplyAllItemMods(); + } + Item _LoadItem(SQLTransaction trans, uint zoneId, uint timeDiff, SQLFields fields) + { + Item item = null; + ulong itemGuid = fields.Read(0); + uint itemEntry = fields.Read(1); + ItemTemplate proto = Global.ObjectMgr.GetItemTemplate(itemEntry); + if (proto != null) + { + bool remove = false; + item = Bag.NewItemOrBag(proto); + if (item.LoadFromDB(itemGuid, GetGUID(), fields, itemEntry)) + { + PreparedStatement stmt = null; + + // Do not allow to have item limited to another map/zone in alive state + if (IsAlive() && item.IsLimitedToAnotherMapOrZone(GetMapId(), zoneId)) + { + Log.outDebug(LogFilter.Player, "LoadInventory: player (GUID: {0}, name: '{1}', map: {2}) has item (GUID: {3}, entry: {4}) limited to another map ({5}). Deleting item.", + GetGUID().ToString(), GetName(), GetMapId(), item.GetGUID().ToString(), item.GetEntry(), zoneId); + remove = true; + } + // "Conjured items disappear if you are logged out for more than 15 minutes" + else if (timeDiff > 15 * Time.Minute && proto.GetFlags().HasAnyFlag(ItemFlags.Conjured)) + { + Log.outDebug(LogFilter.Player, "LoadInventory: player (GUID: {0}, name: {1}, diff: {2}) has conjured item (GUID: {3}, entry: {4}) with expired lifetime (15 minutes). Deleting item.", + GetGUID().ToString(), GetName(), timeDiff, item.GetGUID().ToString(), item.GetEntry()); + remove = true; + } + if (item.HasFlag((int)ItemFields.Flags, ItemFieldFlags.Refundable)) + { + if (item.GetPlayedTime() > (2 * Time.Hour)) + { + Log.outDebug(LogFilter.Player, "LoadInventory: player (GUID: {0}, name: {1}) has item (GUID: {2}, entry: {3}) with expired refund time ({4}). Deleting refund data and removing " + + "efundable flag.", GetGUID().ToString(), GetName(), item.GetGUID().ToString(), item.GetEntry(), item.GetPlayedTime()); + + stmt = DB.Characters.GetPreparedStatement(CharStatements.DEL_ITEM_REFUND_INSTANCE); + stmt.AddValue(0, item.GetGUID().ToString()); + trans.Append(stmt); + + item.RemoveFlag(ItemFields.Flags, ItemFieldFlags.Refundable); + } + else + { + stmt = DB.Characters.GetPreparedStatement(CharStatements.SEL_ITEM_REFUNDS); + stmt.AddValue(0, item.GetGUID().GetCounter()); + stmt.AddValue(1, GetGUID().GetCounter()); + SQLResult result = DB.Characters.Query(stmt); + if (!result.IsEmpty()) + { + item.SetRefundRecipient(GetGUID()); + item.SetPaidMoney(result.Read(0)); + item.SetPaidExtendedCost(result.Read(1)); + AddRefundReference(item.GetGUID()); + } + else + { + Log.outDebug(LogFilter.Player, "LoadInventory: player (GUID: {0}, name: {1}) has item (GUID: {2}, entry: {3}) with refundable flags, but without data in item_refund_instance. Removing flag.", + GetGUID().ToString(), GetName(), item.GetGUID().ToString(), item.GetEntry()); + item.RemoveFlag(ItemFields.Flags, ItemFieldFlags.Refundable); + } + } + } + else if (item.HasFlag(ItemFields.Flags, ItemFieldFlags.BopTradeable)) + { + stmt = DB.Characters.GetPreparedStatement(CharStatements.SEL_ITEM_BOP_TRADE); + stmt.AddValue(0, item.GetGUID().ToString()); + SQLResult result = DB.Characters.Query(stmt); + if (!result.IsEmpty()) + { + string strGUID = result.Read(0); + var GUIDlist = new StringArray(strGUID, ' '); + List looters = new List(); + for (var i = 0; i < GUIDlist.Length; ++i) + looters.Add(ObjectGuid.Create(HighGuid.Item, ulong.Parse(GUIDlist[i]))); + + + if (looters.Count > 1 && item.GetTemplate().GetMaxStackSize() == 1 && item.IsSoulBound()) + { + item.SetSoulboundTradeable(looters); + AddTradeableItem(item); + } + else + item.ClearSoulboundTradeable(this); + } + else + { + Log.outDebug(LogFilter.ServerLoading, "LoadInventory: player ({0}, name: {1}) has item ({2}, entry: {3}) with ITEM_FLAG_BOP_TRADEABLE flag, " + + "but without data in item_soulbound_trade_data. Removing flag.", GetGUID().ToString(), GetName(), item.GetGUID().ToString(), item.GetEntry()); + item.RemoveFlag(ItemFields.Flags, ItemFieldFlags.BopTradeable); + } + } + else if (proto.GetHolidayID() != 0) + { + remove = true; + var events = Global.GameEventMgr.GetEventMap(); + var activeEventsList = Global.GameEventMgr.GetActiveEventList(); + foreach (var id in activeEventsList) + { + if (events[id].holiday_id == proto.GetHolidayID()) + { + remove = false; + break; + } + } + } + } + else + { + Log.outError(LogFilter.Player, "LoadInventory: player (GUID: {0}, name: {1}) has broken item (GUID: {2}, entry: {3}) in inventory. Deleting item.", + GetGUID().ToString(), GetName(), itemGuid, itemEntry); + remove = true; + } + // Remove item from inventory if necessary + if (remove) + { + Item.DeleteFromInventoryDB(trans, itemGuid); + item.FSetState(ItemUpdateState.Removed); + item.SaveToDB(trans); // it also deletes item object! + item = null; + } + } + else + { + Log.outError(LogFilter.Player, "LoadInventory: player (GUID: {0}, name: {1}) has unknown item (entry: {2}) in inventory. Deleting item.", + GetGUID().ToString(), GetName(), itemEntry); + Item.DeleteFromInventoryDB(trans, itemGuid); + Item.DeleteFromDB(trans, itemGuid); + } + return item; + } + void _LoadSkills(SQLResult result) + { + var professionCount = 0; + var count = 0; + Dictionary loadedSkillValues = new Dictionary(); + if (!result.IsEmpty()) + { + do + { + var skill = result.Read(0); + var value = result.Read(1); + var max = result.Read(2); + + SkillRaceClassInfoRecord rcEntry = Global.DB2Mgr.GetSkillRaceClassInfo(skill, GetRace(), GetClass()); + if (rcEntry == null) + { + Log.outError(LogFilter.Player, "Character: {0}(GUID: {1} Race: {2} Class: {3}) has skill {4} not allowed for his race/class combination", + GetName(), GetGUID().ToString(), GetRace(), GetClass(), skill); + mSkillStatus.Add(skill, new SkillStatusData(0, SkillState.Deleted)); + continue; + } + + // set fixed skill ranges + switch (Global.SpellMgr.GetSkillRangeType(rcEntry)) + { + case SkillRangeType.Language: + value = max = 300; + break; + case SkillRangeType.Mono: + value = max = 1; + break; + case SkillRangeType.Level: + max = GetMaxSkillValueForLevel(); + break; + default: + break; + } + if (value == 0) + { + Log.outError(LogFilter.Player, "Character {0} has skill {1} with value 0. Will be deleted.", GetGUID().ToString(), skill); + + PreparedStatement stmt = DB.Characters.GetPreparedStatement(CharStatements.DEL_CHARACTER_SKILL); + stmt.AddValue(0, GetGUID().GetCounter()); + stmt.AddValue(1, skill); + DB.Characters.Execute(stmt); + continue; + } + var field = (ushort)(count / 2); + var offset = (byte)(count & 1); + + SetUInt16Value(PlayerFields.SkillLineId + field, offset, skill); + ushort step = 0; + + SkillLineRecord skillLine = CliDB.SkillLineStorage.LookupByKey(rcEntry.SkillID); + if (skillLine != null) + { + if (skillLine.CategoryID == SkillCategory.Secondary) + step = (ushort)(max / 75); + + if (skillLine.CategoryID == SkillCategory.Profession) + { + step = (ushort)(max / 75); + + if (professionCount < 2) + SetUInt32Value(PlayerFields.ProfessionSkillLine1 + professionCount++, skill); + } + } + + SetUInt16Value(PlayerFields.SkillLineStep + field, offset, step); + SetUInt16Value(PlayerFields.SkillLineRank + field, offset, value); + SetUInt16Value(PlayerFields.SkillLineMaxRank + field, offset, max); + SetUInt16Value(PlayerFields.SkillLineTempBonus + field, offset, 0); + SetUInt16Value(PlayerFields.SkillLinePermBonus + field, offset, 0); + + mSkillStatus.Add(skill, new SkillStatusData((uint)count, SkillState.Unchanged)); + + loadedSkillValues[skill] = value; + count++; + + if (count >= SkillConst.MaxPlayerSkills) // client limit + { + Log.outError(LogFilter.Player, "Character {0} has more than {1} skills.", GetGUID().ToString(), SkillConst.MaxPlayerSkills); + break; + } + } + while (result.NextRow()); + } + // Learn skill rewarded spells after all skills have been loaded to prevent learning a skill from them before its loaded with proper value from DB + foreach (var skill in loadedSkillValues) + LearnSkillRewardedSpells(skill.Key, skill.Value); + + if (HasSkill(SkillType.FistWeapons)) + SetSkill(SkillType.FistWeapons, 0, GetSkillValue(SkillType.Unarmed), GetMaxSkillValueForLevel()); + + for (; count < SkillConst.MaxPlayerSkills; count++) + { + var field = (ushort)(count / 2); + var offset = (byte)(count & 1); + + SetUInt16Value(PlayerFields.SkillLineId + field, offset, 0); + SetUInt16Value(PlayerFields.SkillLineStep + field, offset, 0); + SetUInt16Value(PlayerFields.SkillLineRank + field, offset, 0); + SetUInt16Value(PlayerFields.SkillLineMaxRank + field, offset, 0); + SetUInt16Value(PlayerFields.SkillLineTempBonus + field, offset, 0); + SetUInt16Value(PlayerFields.SkillLinePermBonus + field, offset, 0); + } + } + void _LoadSpells(SQLResult result) + { + if (!result.IsEmpty()) + { + do + { + AddSpell(result.Read(0), result.Read(1), false, false, result.Read(2), true); + } + while (result.NextRow()); + } + } + + void _LoadAuras(SQLResult auraResult, SQLResult effectResult, uint timediff) + { + Log.outDebug(LogFilter.Player, "Loading auras for player {0}", GetGUID().ToString()); + + ObjectGuid casterGuid = new ObjectGuid(); + ObjectGuid itemGuid = new ObjectGuid(); + Dictionary effectInfo = new Dictionary(); + if (!effectResult.IsEmpty()) + { + do + { + uint effectIndex = effectResult.Read(4); + if (effectIndex < SpellConst.MaxEffects) + { + casterGuid.SetRawValue(effectResult.Read(0)); + itemGuid.SetRawValue(effectResult.Read(1)); + + AuraKey key = new AuraKey(casterGuid, itemGuid, effectResult.Read(2), effectResult.Read(3)); + if (!effectInfo.ContainsKey(key)) + effectInfo[key] = new AuraLoadEffectInfo(); + + AuraLoadEffectInfo info = effectInfo[key]; + info.Amounts[effectIndex] = effectResult.Read(5); + info.BaseAmounts[effectIndex] = effectResult.Read(6); + } + } + while (effectResult.NextRow()); + } + + if (!auraResult.IsEmpty()) + { + do + { + casterGuid.SetRawValue(auraResult.Read(0)); + itemGuid.SetRawValue(auraResult.Read(1)); + AuraKey key = new AuraKey(casterGuid, itemGuid, auraResult.Read(2), auraResult.Read(3)); + uint recalculateMask = auraResult.Read(4); + byte stackCount = auraResult.Read(5); + int maxDuration = auraResult.Read(6); + int remainTime = auraResult.Read(7); + byte remainCharges = auraResult.Read(8); + int castItemLevel = auraResult.Read(9); + + SpellInfo spellInfo = Global.SpellMgr.GetSpellInfo(key.SpellId); + if (spellInfo == null) + { + Log.outError(LogFilter.Player, "Unknown aura (spellid {0}), ignore.", key.SpellId); + continue; + } + + // negative effects should continue counting down after logout + if (remainTime != -1 && !spellInfo.IsPositive()) + { + if (remainTime / Time.InMilliseconds <= timediff) + continue; + + remainTime -= (int)(timediff * Time.InMilliseconds); + } + + // prevent wrong values of remaincharges + if (spellInfo.ProcCharges != 0) + { + // we have no control over the order of applying auras and modifiers allow auras + // to have more charges than value in SpellInfo + if (remainCharges <= 0) + remainCharges = (byte)spellInfo.ProcCharges; + } + else + remainCharges = 0; + + AuraLoadEffectInfo info = effectInfo[key]; + ObjectGuid castId = ObjectGuid.Create(HighGuid.Cast, SpellCastSource.Normal, GetMapId(), spellInfo.Id, GetMap().GenerateLowGuid(HighGuid.Cast)); + Aura aura = Aura.TryCreate(spellInfo, castId, key.EffectMask, this, null, info.BaseAmounts, null, casterGuid, castItemLevel); + if (aura != null) + { + if (!aura.CanBeSaved()) + { + aura.Remove(); + continue; + } + + aura.SetLoadedState(maxDuration, remainTime, remainCharges, stackCount, recalculateMask, info.Amounts); + aura.ApplyForTargets(); + Log.outInfo(LogFilter.Player, "Added aura spellid {0}, effectmask {1}", spellInfo.Id, key.EffectMask); + } + } + while (auraResult.NextRow()); + } + } + bool _LoadHomeBind(SQLResult result) + { + PlayerInfo info = Global.ObjectMgr.GetPlayerInfo(GetRace(), GetClass()); + if (info == null) + { + Log.outError(LogFilter.Player, "Player (Name {0}) has incorrect race/class ({1}/{2}) pair. Can't be loaded.", GetName(), GetRace(), GetClass()); + return false; + } + + bool ok = false; + if (!result.IsEmpty()) + { + homebind = new WorldLocation(); + + homebind.SetMapId(result.Read(0)); + homebindAreaId = result.Read(1); + homebind.posX = result.Read(2); + homebind.posY = result.Read(3); + homebind.posZ = result.Read(4); + + var map = CliDB.MapStorage.LookupByKey(homebind.GetMapId()); + + // accept saved data only for valid position (and non instanceable), and accessable + if (GridDefines.IsValidMapCoord(homebind.GetMapId(), homebind.posX, homebind.posY, homebind.posZ) && + !map.Instanceable() && GetSession().GetExpansion() >= map.Expansion()) + ok = true; + else + { + PreparedStatement stmt = DB.Characters.GetPreparedStatement(CharStatements.DEL_PLAYER_HOMEBIND); + stmt.AddValue(0, GetGUID().GetCounter()); + DB.Characters.Execute(stmt); + } + } + + if (!ok) + { + homebind = new WorldLocation(info.MapId, info.PositionX, info.PositionY, info.PositionZ, info.Orientation); + homebindAreaId = info.ZoneId; + + PreparedStatement stmt = DB.Characters.GetPreparedStatement(CharStatements.INS_PLAYER_HOMEBIND); + stmt.AddValue(0, GetGUID().GetCounter()); + stmt.AddValue(1, homebind.GetMapId()); + stmt.AddValue(2, homebindAreaId); + stmt.AddValue(3, homebind.posX); + stmt.AddValue(4, homebind.posY); + stmt.AddValue(5, homebind.posZ); + DB.Characters.Execute(stmt); + } + + Log.outDebug(LogFilter.Player, "Setting player home position - mapid: {0}, areaid: {1}, {2}", + homebind.GetMapId(), homebindAreaId, homebind); + + return true; + } + void _LoadCurrency(SQLResult result) + { + if (result.IsEmpty()) + return; + + do + { + ushort currencyID = result.Read(0); + + var currency = CliDB.CurrencyTypesStorage.LookupByKey(currencyID); + if (currency == null) + continue; + + PlayerCurrency cur = new PlayerCurrency(); + cur.state = PlayerCurrencyState.Unchanged; + cur.Quantity = result.Read(1); + cur.WeeklyQuantity = result.Read(2); + cur.TrackedQuantity = result.Read(3); + cur.Flags = result.Read(4); + + _currencyStorage.Add(currencyID, cur); + } while (result.NextRow()); + } + void _LoadActions(SQLResult result) + { + m_actionButtons.Clear(); + if (!result.IsEmpty()) + { + do + { + byte button = result.Read(0); + uint action = result.Read(1); + byte type = result.Read(2); + + ActionButton ab = AddActionButton(button, action, type); + if (ab != null) + ab.uState = ActionButtonUpdateState.UnChanged; + else + { + Log.outError(LogFilter.Player, " ...at loading, and will deleted in DB also"); + + // Will deleted in DB at next save (it can create data until save but marked as deleted) + m_actionButtons[button] = new ActionButton(); + m_actionButtons[button].uState = ActionButtonUpdateState.Deleted; + } + } while (result.NextRow()); + } + } + void _LoadQuestStatus(SQLResult result) + { + ushort slot = 0; + if (!result.IsEmpty()) + { + do + { + uint quest_id = result.Read(0); + // used to be new, no delete? + Quest quest = Global.ObjectMgr.GetQuestTemplate(quest_id); + if (quest != null) + { + // find or create + QuestStatusData questStatusData = new QuestStatusData(); + + byte qstatus = result.Read(1); + if (qstatus < (byte)QuestStatus.Max) + questStatusData.Status = (QuestStatus)qstatus; + else + { + questStatusData.Status = QuestStatus.Incomplete; + Log.outError(LogFilter.Player, "Player {0} (GUID: {1}) has invalid quest {2} status ({3}), replaced by QUEST_STATUS_INCOMPLETE(3).", + GetName(), GetGUID().ToString(), quest_id, qstatus); + } + + long quest_time = result.Read(2); + + if (quest.HasSpecialFlag(QuestSpecialFlags.Timed) && !GetQuestRewardStatus(quest_id)) + { + AddTimedQuest(quest_id); + + if (quest_time <= Global.WorldMgr.GetGameTime()) + questStatusData.Timer = 1; + else + questStatusData.Timer = (uint)((quest_time - Global.WorldMgr.GetGameTime()) * Time.InMilliseconds); + } + else + quest_time = 0; + + // add to quest log + if (slot < SharedConst.MaxQuestLogSize && questStatusData.Status != QuestStatus.None) + { + SetQuestSlot(slot, quest_id, (uint)quest_time); // cast can't be helped + + if (questStatusData.Status == QuestStatus.Complete) + SetQuestSlotState(slot, QuestSlotStateMask.Complete); + else if (questStatusData.Status == QuestStatus.Failed) + SetQuestSlotState(slot, QuestSlotStateMask.Fail); + + ++slot; + } + + // Resize quest objective data to proper size + int maxStorageIndex = 0; + foreach (QuestObjective obj in quest.Objectives) + if (obj.StorageIndex > maxStorageIndex) + maxStorageIndex = obj.StorageIndex; + + questStatusData.ObjectiveData = new int[maxStorageIndex + 1]; + + m_QuestStatus[quest_id] = questStatusData; + Log.outDebug(LogFilter.ServerLoading, "Quest status is {0} for quest {1} for player (GUID: {2})", questStatusData.Status, quest_id, GetGUID().ToString()); + } + } + while (result.NextRow()); + } + + // clear quest log tail + for (ushort i = slot; i < SharedConst.MaxQuestLogSize; ++i) + SetQuestSlot(i, 0); + } + void _LoadQuestStatusObjectives(SQLResult result) + { + if (!result.IsEmpty()) + { + do + { + uint questID = result.Read(0); + + Quest quest = Global.ObjectMgr.GetQuestTemplate(questID); + ushort slot = FindQuestSlot(questID); + + var questStatusData = m_QuestStatus.LookupByKey(questID); + if (questStatusData != null && slot < SharedConst.MaxQuestLogSize && quest != null) + { + byte objectiveIndex = result.Read(1); + + var objectiveItr = quest.Objectives.First(objective => { return objective.StorageIndex == objectiveIndex; }); + if (objectiveIndex < questStatusData.ObjectiveData.Length && objectiveItr != null) + { + int data = result.Read(2); + questStatusData.ObjectiveData[objectiveIndex] = data; + if (!objectiveItr.IsStoringFlag()) + SetQuestSlotCounter(slot, objectiveIndex, (ushort)data); + else if (data != 0) + SetQuestSlotState(slot, (QuestSlotStateMask)(256 << objectiveIndex)); + } + else + Log.outError(LogFilter.Player, "Player {0} ({1}) has quest {2} out of range objective index {3}.", GetName(), GetGUID().ToString(), questID, objectiveIndex); + } + else + Log.outError(LogFilter.Player, "Player {0} ({1}) does not have quest {2} but has objective data for it.", GetName(), GetGUID().ToString(), questID); + } + while (result.NextRow()); + } + } + void _LoadQuestStatusRewarded(SQLResult result) + { + if (!result.IsEmpty()) + { + do + { + uint quest_id = result.Read(0); + // used to be new, no delete? + Quest quest = Global.ObjectMgr.GetQuestTemplate(quest_id); + if (quest != null) + { + // learn rewarded spell if unknown + LearnQuestRewardedSpells(quest); + + // set rewarded title if any + if (quest.RewardTitleId != 0) + { + CharTitlesRecord titleEntry = CliDB.CharTitlesStorage.LookupByKey(quest.RewardTitleId); + if (titleEntry != null) + SetTitle(titleEntry); + } + + // Skip loading special quests - they are also added to rewarded quests but only once and remain there forever + // instead add them separately from load daily/weekly/monthly/seasonal + if (!quest.IsDailyOrWeekly() && !quest.IsMonthly() && !quest.IsSeasonal()) + { + uint questBit = Global.DB2Mgr.GetQuestUniqueBitFlag(quest_id); + if (questBit != 0) + SetQuestCompletedBit(questBit, true); + } + + for (uint i = 0; i < quest.GetRewChoiceItemsCount(); ++i) + GetSession().GetCollectionMgr().AddItemAppearance(quest.RewardChoiceItemId[i]); + + for (uint i = 0; i < quest.GetRewItemsCount(); ++i) + GetSession().GetCollectionMgr().AddItemAppearance(quest.RewardItemId[i]); + + var questPackageItems = Global.DB2Mgr.GetQuestPackageItems(quest.PackageID); + if (questPackageItems != null) + { + foreach (QuestPackageItemRecord questPackageItem in questPackageItems) + { + ItemTemplate rewardProto = Global.ObjectMgr.GetItemTemplate(questPackageItem.ItemID); + if (rewardProto != null) + if (rewardProto.ItemSpecClassMask.HasAnyFlag(getClassMask())) + GetSession().GetCollectionMgr().AddItemAppearance(questPackageItem.ItemID); + } + } + + if (quest.CanIncreaseRewardedQuestCounters()) + m_RewardedQuests.Add(quest_id); + } + } + while (result.NextRow()); + } + } + void _LoadDailyQuestStatus(SQLResult result) + { + m_DFQuests.Clear(); + + //QueryResult* result = CharacterDatabase.PQuery("SELECT quest, time FROM character_queststatus_daily WHERE guid = '{0}'"); + if (!result.IsEmpty()) + { + do + { + uint quest_id = result.Read(0); + Quest qQuest = Global.ObjectMgr.GetQuestTemplate(quest_id); + if (qQuest != null) + { + if (qQuest.IsDFQuest()) + { + m_DFQuests.Add(qQuest.Id); + m_lastDailyQuestTime = result.Read(1); + continue; + } + } + + // save _any_ from daily quest times (it must be after last reset anyway) + m_lastDailyQuestTime = result.Read(1); + + Quest quest = Global.ObjectMgr.GetQuestTemplate(quest_id); + if (quest == null) + continue; + + AddDynamicValue(PlayerDynamicFields.DailyQuests, quest_id); + uint questBit = Global.DB2Mgr.GetQuestUniqueBitFlag(quest_id); + if (questBit != 0) + SetQuestCompletedBit(questBit, true); + + Log.outDebug(LogFilter.Player, "Daily quest ({0}) cooldown for player (GUID: {1})", quest_id, GetGUID().ToString()); + } + while (result.NextRow()); + } + + m_DailyQuestChanged = false; + } + void _LoadWeeklyQuestStatus(SQLResult result) + { + m_weeklyquests.Clear(); + + if (!result.IsEmpty()) + { + do + { + uint quest_id = result.Read(0); + Quest quest = Global.ObjectMgr.GetQuestTemplate(quest_id); + if (quest == null) + continue; + + m_weeklyquests.Add(quest_id); + uint questBit = Global.DB2Mgr.GetQuestUniqueBitFlag(quest_id); + if (questBit != 0) + SetQuestCompletedBit(questBit, true); + + Log.outDebug(LogFilter.Player, "Weekly quest {{0}} cooldown for player (GUID: {1})", quest_id, GetGUID().ToString()); + } + while (result.NextRow()); + } + + m_WeeklyQuestChanged = false; + } + void _LoadSeasonalQuestStatus(SQLResult result) + { + m_seasonalquests.Clear(); + + if (!result.IsEmpty()) + { + do + { + uint quest_id = result.Read(0); + uint event_id = result.Read(1); + Quest quest = Global.ObjectMgr.GetQuestTemplate(quest_id); + if (quest == null) + continue; + + m_seasonalquests.Add(event_id, quest_id); + uint questBit = Global.DB2Mgr.GetQuestUniqueBitFlag(quest_id); + if (questBit != 0) + SetQuestCompletedBit(questBit, true); + + Log.outDebug(LogFilter.Player, "Seasonal quest {{0}} cooldown for player (GUID: {1})", quest_id, GetGUID().ToString()); + } + while (result.NextRow()); + } + + m_SeasonalQuestChanged = false; + } + void _LoadMonthlyQuestStatus() + { + PreparedStatement stmt = DB.Characters.GetPreparedStatement(CharStatements.SEL_CHARACTER_QUESTSTATUS_MONTHLY); + stmt.AddValue(0, GetGUID().GetCounter()); + SQLResult result = DB.Characters.Query(stmt); + + m_monthlyquests.Clear(); + + if (!result.IsEmpty()) + { + do + { + uint quest_id = result.Read(0); + Quest quest = Global.ObjectMgr.GetQuestTemplate(quest_id); + if (quest == null) + continue; + + m_monthlyquests.Add(quest_id); + uint questBit = Global.DB2Mgr.GetQuestUniqueBitFlag(quest_id); + if (questBit != 0) + SetQuestCompletedBit(questBit, true); + + Log.outDebug(LogFilter.Player, "Monthly quest {{0}} cooldown for player (GUID: {1})", quest_id, GetGUID().ToString()); + } + while (result.NextRow()); + } + + m_MonthlyQuestChanged = false; + } + void _LoadTalents(SQLResult result) + { + if (!result.IsEmpty()) + { + do + { + TalentRecord talent = CliDB.TalentStorage.LookupByKey(result.Read(0)); + if (talent != null) + AddTalent(talent, result.Read(1), false); + } + while (result.NextRow()); + } + } + void _LoadGlyphs(SQLResult result) + { + // SELECT talentGroup, glyphId from character_glyphs WHERE guid = ? + if (result.IsEmpty()) + return; + + do + { + byte spec = result.Read(0); + if (spec >= PlayerConst.MaxSpecializations || Global.DB2Mgr.GetChrSpecializationByIndex(GetClass(), spec) == null) + continue; + + ushort glyphId = result.Read(1); + if (!CliDB.GlyphPropertiesStorage.ContainsKey(glyphId)) + continue; + + GetGlyphs(spec).Add(glyphId); + + } while (result.NextRow()); + } + void _LoadGlyphAuras() + { + foreach (uint glyphId in GetGlyphs(GetActiveTalentGroup())) + CastSpell(this, CliDB.GlyphPropertiesStorage.LookupByKey(glyphId).SpellID, true); + } + public void LoadCorpse(SQLResult result) + { + if (IsAlive() || HasAtLoginFlag(AtLoginFlags.Resurrect)) + SpawnCorpseBones(false); + + if (!IsAlive()) + { + if (!result.IsEmpty() && !HasAtLoginFlag(AtLoginFlags.Resurrect)) + { + _corpseLocation = new WorldLocation(result.Read(0), result.Read(1), result.Read(2), result.Read(3), result.Read(4)); + ApplyModFlag(PlayerFields.LocalFlags, PlayerLocalFlags.ReleaseTimer, !CliDB.MapStorage.LookupByKey(_corpseLocation.GetMapId()).Instanceable()); + } + else + ResurrectPlayer(0.5f); + } + + RemoveAtLoginFlag(AtLoginFlags.Resurrect); + } + void _LoadBoundInstances(SQLResult result) + { + for (byte i = 0; i < (int)Difficulty.Max; ++i) + m_boundInstances[i].Clear(); + + Group group = GetGroup(); + + if (!result.IsEmpty()) + { + do + { + bool perm = result.Read(1); + uint mapId = result.Read(2); + uint instanceId = result.Read(0); + byte difficulty = result.Read(3); + BindExtensionState extendState = (BindExtensionState)result.Read(4); + + long resetTime = result.Read(5); + // the resettime for normal instances is only saved when the InstanceSave is unloaded + // so the value read from the DB may be wrong here but only if the InstanceSave is loaded + // and in that case it is not used + + uint entranceId = result.Read(6); + + bool deleteInstance = false; + + MapRecord mapEntry = CliDB.MapStorage.LookupByKey(mapId); + string mapname = mapEntry != null ? mapEntry.MapName[Global.WorldMgr.GetDefaultDbcLocale()] : "Unknown"; + + if (mapEntry == null || !mapEntry.IsDungeon()) + { + Log.outError(LogFilter.Player, "_LoadBoundInstances: player {0}({1}) has bind to not existed or not dungeon map {2} ({3})", GetName(), GetGUID().ToString(), mapId, mapname); + deleteInstance = true; + } + else if (difficulty >= (int)Difficulty.Max) + { + Log.outError(LogFilter.Player, "_LoadBoundInstances: player {0}({1}) has bind to not existed difficulty {2} instance for map {3} ({4})", GetName(), GetGUID().ToString(), difficulty, mapId, mapname); + deleteInstance = true; + } + else + { + MapDifficultyRecord mapDiff = Global.DB2Mgr.GetMapDifficultyData(mapId, (Difficulty)difficulty); + if (mapDiff == null) + { + Log.outError(LogFilter.Player, "_LoadBoundInstances: player {0}({1}) has bind to not existed difficulty {2} instance for map {3} ({4})", GetName(), GetGUID().ToString(), difficulty, mapId, mapname); + deleteInstance = true; + } + else if (!perm && group) + { + Log.outError(LogFilter.Player, "_LoadBoundInstances: player {0}({1}) is in group {2} but has a non-permanent character bind to map {3} ({4}), {5}, {6}", + GetName(), GetGUID().ToString(), group.GetGUID().ToString(), mapId, mapname, instanceId, difficulty); + deleteInstance = true; + } + } + + if (deleteInstance) + { + PreparedStatement stmt = DB.Characters.GetPreparedStatement(CharStatements.DEL_CHAR_INSTANCE_BY_INSTANCE_GUID); + stmt.AddValue(0, GetGUID().GetCounter()); + stmt.AddValue(1, instanceId); + DB.Characters.Execute(stmt); + + continue; + } + + // since non permanent binds are always solo bind, they can always be reset + InstanceSave save = Global.InstanceSaveMgr.AddInstanceSave(mapId, instanceId, (Difficulty)difficulty, resetTime, entranceId, !perm, true); + if (save != null) + BindToInstance(save, perm, extendState, true); + } + while (result.NextRow()); + } + } + void _LoadVoidStorage(SQLResult result) + { + if (result.IsEmpty()) + return; + + do + { + // SELECT itemId, itemEntry, slot, creatorGuid, randomProperty, suffixFactor, upgradeId, fixedScalingLevel, artifactKnowledgeLevel, context, bonusListIDs FROM character_void_storage WHERE playerGuid = ? + ulong itemId = result.Read(0); + uint itemEntry = result.Read(1); + byte slot = result.Read(2); + ObjectGuid creatorGuid = result.Read(3) != 0 ? ObjectGuid.Create(HighGuid.Player, result.Read(3)) : ObjectGuid.Empty; + ItemRandomEnchantmentId randomProperty = new ItemRandomEnchantmentId((ItemRandomEnchantmentType)result.Read(4), result.Read(5)); + uint suffixFactor = result.Read(6); + uint upgradeId = result.Read(7); + uint fixedScalingLevel = result.Read(8); + uint artifactKnowledgeLevel = result.Read(9); + byte context = result.Read(10); + List bonusListIDs = new List(); + var bonusListIdTokens = new StringArray(result.Read(11), ' '); + for (var i = 0; i < bonusListIdTokens.Length; ++i) + bonusListIDs.Add(uint.Parse(bonusListIdTokens[i])); + + if (itemId == 0) + { + Log.outError(LogFilter.Player, "Player:_LoadVoidStorage - Player (GUID: {0}, name: {1}) has an item with an invalid id (item id: item id: {2}, entry: {3}).", GetGUID().ToString(), GetName(), itemId, itemEntry); + continue; + } + + if (Global.ObjectMgr.GetItemTemplate(itemEntry) == null) + { + Log.outError(LogFilter.Player, "Player:_LoadVoidStorage - Player (GUID: {0}, name: {1}) has an item with an invalid entry (item id: item id: {2}, entry: {3}).", GetGUID().ToString(), GetName(), itemId, itemEntry); + continue; + } + + if (slot >= SharedConst.VoidStorageMaxSlot) + { + Log.outError(LogFilter.Player, "Player:_LoadVoidStorage - Player (GUID: {0}, name: {1}) has an item with an invalid slot (item id: item id: {2}, entry: {3}, slot: {4}).", GetGUID().ToString(), GetName(), itemId, itemEntry, slot); + continue; + } + + _voidStorageItems[slot] = new VoidStorageItem(itemId, itemEntry, creatorGuid, randomProperty, suffixFactor, upgradeId, fixedScalingLevel, artifactKnowledgeLevel, context, bonusListIDs); + + BonusData bonus = new BonusData(new ItemInstance(_voidStorageItems[slot])); + + GetSession().GetCollectionMgr().AddItemAppearance(itemEntry, bonus.AppearanceModID); + } + while (result.NextRow()); + } + void _LoadMailInit(SQLResult resultUnread, SQLResult resultDelivery) + { + if (!resultUnread.IsEmpty()) + unReadMails = (byte)resultUnread.Read(0); + + if (!resultDelivery.IsEmpty()) + m_nextMailDelivereTime = resultDelivery.Read(0); + } + public void _LoadMail() + { + m_mail.Clear(); + + PreparedStatement stmt = DB.Characters.GetPreparedStatement(CharStatements.SEL_MAIL); + stmt.AddValue(0, GetGUID().GetCounter()); + SQLResult result = DB.Characters.Query(stmt); + + if (!result.IsEmpty()) + { + do + { + Mail m = new Mail(); + + m.messageID = result.Read(0); + m.messageType = (MailMessageType)result.Read(1); + m.sender = result.Read(2); + m.receiver = result.Read(3); + m.subject = result.Read(4); + m.body = result.Read(5); + bool has_items = result.Read(6); + m.expire_time = result.Read(7); + m.deliver_time = result.Read(8); + m.money = result.Read(9); + m.COD = result.Read(10); + m.checkMask = (MailCheckMask)result.Read(11); + m.stationery = (MailStationery)result.Read(12); + m.mailTemplateId = result.Read(13); + + if (m.mailTemplateId != 0 && !CliDB.MailTemplateStorage.ContainsKey(m.mailTemplateId)) + { + Log.outError(LogFilter.Player, "Player:_LoadMail - Mail ({0}) have not existed MailTemplateId ({1}), remove at load", m.messageID, m.mailTemplateId); + m.mailTemplateId = 0; + } + + m.state = MailState.Unchanged; + + if (has_items) + _LoadMailedItems(m); + + m_mail.Add(m); + } + while (result.NextRow()); + } + m_mailsLoaded = true; + } + void _LoadMailedItems(Mail mail) + { + // data needs to be at first place for Item.LoadFromDB + PreparedStatement stmt = DB.Characters.GetPreparedStatement(CharStatements.SEL_MAILITEMS); + stmt.AddValue(0, mail.messageID); + SQLResult result = DB.Characters.Query(stmt); + if (result.IsEmpty()) + return; + + do + { + ulong itemGuid = result.Read(0); + uint itemEntry = result.Read(1); + + mail.AddItem(itemGuid, itemEntry); + + ItemTemplate proto = Global.ObjectMgr.GetItemTemplate(itemEntry); + if (proto == null) + { + Log.outError(LogFilter.Player, "Player {0} has unknown item_template (ProtoType) in mailed items(GUID: {1} template: {2}) in mail ({3}), deleted.", GetGUID().ToString(), itemGuid, itemEntry, mail.messageID); + + stmt = DB.Characters.GetPreparedStatement(CharStatements.DEL_INVALID_MAIL_ITEM); + stmt.AddValue(0, itemGuid); + DB.Characters.Execute(stmt); + + stmt = DB.Characters.GetPreparedStatement(CharStatements.DEL_ITEM_INSTANCE); + stmt.AddValue(0, itemGuid); + DB.Characters.Execute(stmt); + continue; + } + + Item item = Bag.NewItemOrBag(proto); + ObjectGuid ownerGuid = result.Read(45) != 0 ? ObjectGuid.Create(HighGuid.Player, result.Read(45)) : ObjectGuid.Empty; + if (!item.LoadFromDB(itemGuid, ownerGuid, result.GetFields(), itemEntry)) + { + Log.outError(LogFilter.Player, "Player:_LoadMailedItems - Item in mail ({0}) doesn't exist !!!! - item guid: {1}, deleted from mail", mail.messageID, itemGuid); + + stmt = DB.Characters.GetPreparedStatement(CharStatements.DEL_MAIL_ITEM); + stmt.AddValue(0, itemGuid); + DB.Characters.Execute(stmt); + + item.FSetState(ItemUpdateState.Removed); + + item.SaveToDB(null); // it also deletes item object ! + continue; + } + + AddMItem(item); + } + while (result.NextRow()); + } + void _LoadDeclinedNames(SQLResult result) + { + if (result.IsEmpty()) + return; + + _declinedname = new DeclinedName(); + for (byte i = 0; i < SharedConst.MaxDeclinedNameCases; ++i) + _declinedname.name[i] = result.Read(i); + } + void _LoadArenaTeamInfo(SQLResult result) + { + // arenateamid, played_week, played_season, personal_rating + ushort[] personalRatingCache = { 0, 0, 0 }; + + if (!result.IsEmpty()) + { + do + { + uint arenaTeamId = result.Read(0); + + ArenaTeam arenaTeam = Global.ArenaTeamMgr.GetArenaTeamById(arenaTeamId); + if (arenaTeam == null) + { + Log.outError(LogFilter.Player, "Player:_LoadArenaTeamInfo: couldn't load arenateam {0}", arenaTeamId); + continue; + } + + byte arenaSlot = arenaTeam.GetSlot(); + + personalRatingCache[arenaSlot] = result.Read(4); + + SetArenaTeamInfoField(arenaSlot, ArenaTeamInfoType.Id, arenaTeamId); + SetArenaTeamInfoField(arenaSlot, ArenaTeamInfoType.Type, arenaTeam.GetArenaType()); + SetArenaTeamInfoField(arenaSlot, ArenaTeamInfoType.Member, (uint)(arenaTeam.GetCaptain() == GetGUID() ? 0 : 1)); + SetArenaTeamInfoField(arenaSlot, ArenaTeamInfoType.GamesWeek, result.Read(1)); + SetArenaTeamInfoField(arenaSlot, ArenaTeamInfoType.GamesSeason, result.Read(2)); + SetArenaTeamInfoField(arenaSlot, ArenaTeamInfoType.WinsSeason, result.Read(3)); + } + while (result.NextRow()); + } + + for (byte slot = 0; slot <= 2; ++slot) + { + SetArenaTeamInfoField(slot, ArenaTeamInfoType.PersonalRating, personalRatingCache[slot]); + } + } + void _LoadGroup(SQLResult result) + { + if (!result.IsEmpty()) + { + Group group = Global.GroupMgr.GetGroupByDbStoreId(result.Read(0)); + if (group) + { + if (group.IsLeader(GetGUID())) + SetFlag(PlayerFields.Flags, PlayerFlags.GroupLeader); + + byte subgroup = group.GetMemberGroup(GetGUID()); + SetGroup(group, subgroup); + SetPartyType(group.GetGroupCategory(), GroupType.Normal); + ResetGroupUpdateSequenceIfNeeded(group); + + // the group leader may change the instance difficulty while the player is offline + SetDungeonDifficultyID(group.GetDungeonDifficultyID()); + SetRaidDifficultyID(group.GetRaidDifficultyID()); + SetLegacyRaidDifficultyID(group.GetLegacyRaidDifficultyID()); + } + } + + if (!GetGroup() || !GetGroup().IsLeader(GetGUID())) + RemoveFlag(PlayerFields.Flags, PlayerFlags.GroupLeader); + } + void _LoadInstanceTimeRestrictions(SQLResult result) + { + if (result.IsEmpty()) + return; + + do + { + _instanceResetTimes.Add(result.Read(0), result.Read(1)); + } while (result.NextRow()); + } + void _LoadEquipmentSets(SQLResult result) + { + if (result.IsEmpty()) + return; + + do + { + EquipmentSetInfo eqSet = new EquipmentSetInfo(); + eqSet.Data.Guid = result.Read(0); + eqSet.Data.Type = EquipmentSetInfo.EquipmentSetType.Equipment; + eqSet.Data.SetID = result.Read(1); + eqSet.Data.SetName = result.Read(2); + eqSet.Data.SetIcon = result.Read(3); + eqSet.Data.IgnoreMask = result.Read(4); + eqSet.Data.AssignedSpecIndex = result.Read(5); + eqSet.state = EquipmentSetUpdateState.Unchanged; + + for (int i = 0; i < EquipmentSlot.End; ++i) + { + ulong guid = result.Read(6 + i); + if (guid != 0) + eqSet.Data.Pieces[i] = ObjectGuid.Create(HighGuid.Item, guid); + } + + eqSet.Data.Appearances.Fill(0); + eqSet.Data.Enchants.Fill(0); + + if (eqSet.Data.SetID >= ItemConst.MaxEquipmentSetIndex) // client limit + continue; + + _equipmentSets[eqSet.Data.Guid] = eqSet; + } + while (result.NextRow()); + } + void _LoadTransmogOutfits(SQLResult result) + { + // 0 1 2 3 4 5 6 7 8 9 + //SELECT setguid, setindex, name, iconname, ignore_mask, appearance0, appearance1, appearance2, appearance3, appearance4, + // 10 11 12 13 14 15 16 17 18 19 20 21 + // appearance5, appearance6, appearance7, appearance8, appearance9, appearance10, appearance11, appearance12, appearance13, appearance14, appearance15, appearance16, + // 22 23 24 25 + // appearance17, appearance18, mainHandEnchant, offHandEnchant FROM character_transmog_outfits WHERE guid = ? ORDER BY setindex + if (result.IsEmpty()) + return; + + do + { + EquipmentSetInfo eqSet = new EquipmentSetInfo(); + + eqSet.Data.Guid = result.Read(0); + eqSet.Data.Type = EquipmentSetInfo.EquipmentSetType.Transmog; + eqSet.Data.SetID = result.Read (1); + eqSet.Data.SetName = result.Read (2); + eqSet.Data.SetIcon = result.Read (3); + eqSet.Data.IgnoreMask = result.Read (4); + eqSet.state = EquipmentSetUpdateState.Unchanged; + eqSet.Data.Pieces.Fill(ObjectGuid.Empty); + + for (int i = 0; i < EquipmentSlot.End; ++i) + eqSet.Data.Appearances[i] = result.Read(5 + i); + + for (int i = 0; i < eqSet.Data.Enchants.Count; ++i) + eqSet.Data.Enchants[i] = result.Read(24 + i); + + if (eqSet.Data.SetID >= ItemConst.MaxEquipmentSetIndex) // client limit + continue; + + _equipmentSets[eqSet.Data.Guid] = eqSet; + } while (result.NextRow()); + } + void _LoadCUFProfiles(SQLResult result) + { + if (result.IsEmpty()) + return; + + do + { + byte id = result.Read(0); + string name = result.Read(1); + ushort frameHeight = result.Read(2); + ushort frameWidth = result.Read(3); + byte sortBy = result.Read(4); + byte healthText = result.Read(5); + uint boolOptions = result.Read(6); + byte topPoint = result.Read(7); + byte bottomPoint = result.Read(8); + byte leftPoint = result.Read(9); + ushort topOffset = result.Read(10); + ushort bottomOffset = result.Read(11); + ushort leftOffset = result.Read(12); + + if (id > PlayerConst.MaxCUFProfiles) + { + Log.outError(LogFilter.Player, "Player._LoadCUFProfiles - Player (GUID: {0}, name: {1}) has an CUF profile with invalid id (id: {2}), max is {3}.", GetGUID().ToString(), GetName(), id, PlayerConst.MaxCUFProfiles); + continue; + } + + _CUFProfiles[id] = new CUFProfile(name, frameHeight, frameWidth, sortBy, healthText, boolOptions, topPoint, bottomPoint, leftPoint, topOffset, bottomOffset, leftOffset); + } + while (result.NextRow()); + } + void _LoadRandomBGStatus(SQLResult result) + { + if (!result.IsEmpty()) + m_IsBGRandomWinner = true; + } + void _LoadBGData(SQLResult result) + { + if (result.IsEmpty()) + return; + + // Expecting only one row + // 0 1 2 3 4 5 6 7 8 9 + // SELECT instanceId, team, joinX, joinY, joinZ, joinO, joinMapId, taxiStart, taxiEnd, mountSpell FROM character_Battleground_data WHERE guid = ? + m_bgData.bgInstanceID = result.Read(0); + m_bgData.bgTeam = result.Read(1); + m_bgData.joinPos = new WorldLocation(result.Read(6), result.Read(2), result.Read(3), result.Read(4), result.Read(5)); + m_bgData.taxiPath[0] = result.Read(7); + m_bgData.taxiPath[1] = result.Read(8); + m_bgData.mountSpell = result.Read(9); + } + + void _SaveInventory(SQLTransaction trans) + { + PreparedStatement stmt; + // force items in buyback slots to new state + // and remove those that aren't already + for (var i = InventorySlots.BuyBackStart; i < InventorySlots.BuyBackEnd; ++i) + { + Item item = m_items[i]; + if (item == null || item.GetState() == ItemUpdateState.New) + continue; + + stmt = DB.Characters.GetPreparedStatement(CharStatements.DEL_CHAR_INVENTORY_BY_ITEM); + stmt.AddValue(0, item.GetGUID().GetCounter()); + trans.Append(stmt); + + stmt = DB.Characters.GetPreparedStatement(CharStatements.DEL_ITEM_INSTANCE); + stmt.AddValue(0, item.GetGUID().GetCounter()); + trans.Append(stmt); + m_items[i].FSetState(ItemUpdateState.New); + } + + // Updated played time for refundable items. We don't do this in Player.Update because there's simply no need for it, + // the client auto counts down in real time after having received the initial played time on the first + // SMSG_ITEM_REFUND_INFO_RESPONSE packet. + // Item.UpdatePlayedTime is only called when needed, which is in DB saves, and item refund info requests. + foreach (var guid in m_refundableItems) + { + Item item = GetItemByGuid(guid); + if (item != null) + { + item.UpdatePlayedTime(this); + continue; + } + else + { + Log.outError(LogFilter.Player, "Can't find item guid {0} but is in refundable storage for player {1} ! Removing.", guid, GetGUID().ToString()); + m_refundableItems.Remove(guid); + } + } + + // update enchantment durations + foreach (var enchant in m_enchantDuration) + enchant.item.SetEnchantmentDuration(enchant.slot, enchant.leftduration, this); + + // if no changes + if (ItemUpdateQueue.Count == 0) + return; + + for (var i = 0; i < ItemUpdateQueue.Count; ++i) + { + Item item = ItemUpdateQueue[i]; + if (item == null) + continue; + + Bag container = item.GetContainer(); + if (item.GetState() != ItemUpdateState.Removed) + { + Item test = GetItemByPos(item.GetBagSlot(), item.GetSlot()); + if (test == null) + { + ulong bagTestGUID = 0; + Item test2 = GetItemByPos(InventorySlots.Bag0, item.GetBagSlot()); + if (test2 != null) + bagTestGUID = test2.GetGUID().GetCounter(); + Log.outError(LogFilter.Player, "Player(GUID: {0} Name: {1}).SaveInventory - the bag({2}) and slot({3}) values for the item with guid {4} (state {5}) are incorrect, " + + "the player doesn't have an item at that position!", GetGUID().ToString(), GetName(), item.GetBagSlot(), item.GetSlot(), item.GetGUID().ToString(), item.GetState()); + // according to the test that was just performed nothing should be in this slot, delete + stmt = DB.Characters.GetPreparedStatement(CharStatements.DEL_CHAR_INVENTORY_BY_BAG_SLOT); + stmt.AddValue(0, bagTestGUID); + stmt.AddValue(1, item.GetSlot()); + stmt.AddValue(2, GetGUID().GetCounter()); + trans.Append(stmt); + + // also THIS item should be somewhere else, cheat attempt + item.FSetState(ItemUpdateState.Removed); // we are IN updateQueue right now, can't use SetState which modifies the queue + DeleteRefundReference(item.GetGUID()); + } + else if (test != item) + { + Log.outError(LogFilter.Player, "Player(GUID: {0} Name: {1}).SaveInventory - the bag({2}) and slot({3}) values for the item with guid {4} are incorrect, " + + "the item with guid {5} is there instead!", GetGUID().ToString(), GetName(), item.GetBagSlot(), item.GetSlot(), item.GetGUID().ToString(), test.GetGUID().ToString()); + // save all changes to the item... + if (item.GetState() != ItemUpdateState.New) // only for existing items, no dupes + item.SaveToDB(trans); + // ...but do not save position in inventory + continue; + } + } + + switch (item.GetState()) + { + case ItemUpdateState.New: + case ItemUpdateState.Changed: + stmt = DB.Characters.GetPreparedStatement(CharStatements.REP_INVENTORY_ITEM); + stmt.AddValue(0, GetGUID().GetCounter()); + stmt.AddValue(1, container ? container.GetGUID().GetCounter() : 0); + stmt.AddValue(2, item.GetSlot()); + stmt.AddValue(3, item.GetGUID().GetCounter()); + trans.Append(stmt); + break; + case ItemUpdateState.Removed: + stmt = DB.Characters.GetPreparedStatement(CharStatements.DEL_CHAR_INVENTORY_BY_ITEM); + stmt.AddValue(0, item.GetGUID().GetCounter()); + trans.Append(stmt); + break; + case ItemUpdateState.Unchanged: + break; + } + + item.SaveToDB(trans); // item have unchanged inventory record and can be save standalone + } + ItemUpdateQueue.Clear(); + } + void _SaveSkills(SQLTransaction trans) + { + PreparedStatement stmt;// = null; + + foreach (var skill in mSkillStatus.ToList()) + { + if (skill.Value.State == SkillState.Unchanged) + continue; + + if (skill.Value.State == SkillState.Deleted) + { + stmt = DB.Characters.GetPreparedStatement(CharStatements.DEL_CHAR_SKILL_BY_SKILL); + stmt.AddValue(0, GetGUID().GetCounter()); + stmt.AddValue(1, skill.Key); + trans.Append(stmt); + + mSkillStatus.Remove(skill.Key); + continue; + } + + var field = (ushort)(skill.Value.Pos / 2); + var offset = (byte)(skill.Value.Pos & 1); + + var value = GetUInt16Value(PlayerFields.SkillLineRank + field, offset); + var max = GetUInt16Value(PlayerFields.SkillLineMaxRank + field, offset); + + switch (skill.Value.State) + { + case SkillState.New: + stmt = DB.Characters.GetPreparedStatement(CharStatements.INS_CHAR_SKILLS); + stmt.AddValue(0, GetGUID().GetCounter()); + stmt.AddValue(1, (ushort)skill.Key); + stmt.AddValue(2, value); + stmt.AddValue(3, max); + trans.Append(stmt); + break; + case SkillState.Changed: + stmt = DB.Characters.GetPreparedStatement(CharStatements.UPD_CHAR_SKILLS); + stmt.AddValue(0, value); + stmt.AddValue(1, max); + stmt.AddValue(2, GetGUID().GetCounter()); + stmt.AddValue(3, (ushort)skill.Key); + trans.Append(stmt); + break; + default: + break; + } + skill.Value.State = SkillState.Unchanged; + } + } + void _SaveSpells(SQLTransaction trans) + { + PreparedStatement stmt = null; + + foreach (var spell in m_spells) + { + if (spell.Value.State == PlayerSpellState.Removed || spell.Value.State == PlayerSpellState.Changed) + { + stmt = DB.Characters.GetPreparedStatement(CharStatements.DEL_CHAR_SPELL_BY_SPELL); + stmt.AddValue(0, spell.Key); + stmt.AddValue(1, GetGUID().GetCounter()); + trans.Append(stmt); + } + + // add only changed/new not dependent spells + if (!spell.Value.Dependent && (spell.Value.State == PlayerSpellState.New || spell.Value.State == PlayerSpellState.Changed)) + { + stmt = DB.Characters.GetPreparedStatement(CharStatements.INS_CHAR_SPELL); + stmt.AddValue(0, GetGUID().GetCounter()); + stmt.AddValue(1, spell.Key); + stmt.AddValue(2, spell.Value.Active); + stmt.AddValue(3, spell.Value.Disabled); + trans.Append(stmt); + } + + if (spell.Value.State == PlayerSpellState.Removed) + m_spells.Remove(spell.Key); + else + { + spell.Value.State = PlayerSpellState.Unchanged; + continue; + } + } + } + void _SaveAuras(SQLTransaction trans) + { + PreparedStatement stmt = DB.Characters.GetPreparedStatement(CharStatements.DEL_CHAR_AURA_EFFECT); + stmt.AddValue(0, GetGUID().GetCounter()); + trans.Append(stmt); + + stmt = DB.Characters.GetPreparedStatement(CharStatements.DEL_CHAR_AURA); + stmt.AddValue(0, GetGUID().GetCounter()); + trans.Append(stmt); + + byte index; + foreach (var pair in GetOwnedAuras()) + { + Aura aura = pair.Value; + if (!aura.CanBeSaved()) + continue; + + uint recalculateMask; + AuraKey key = aura.GenerateKey(out recalculateMask); + + index = 0; + stmt = DB.Characters.GetPreparedStatement(CharStatements.INS_AURA); + stmt.AddValue(index++, GetGUID().GetCounter()); + stmt.AddValue(index++, key.Caster.GetRawValue()); + stmt.AddValue(index++, key.Item.GetRawValue()); + stmt.AddValue(index++, key.SpellId); + stmt.AddValue(index++, key.EffectMask); + stmt.AddValue(index++, recalculateMask); + stmt.AddValue(index++, aura.GetStackAmount()); + stmt.AddValue(index++, aura.GetMaxDuration()); + stmt.AddValue(index++, aura.GetDuration()); + stmt.AddValue(index++, aura.GetCharges()); + stmt.AddValue(index, aura.GetCastItemLevel()); + trans.Append(stmt); + + foreach (AuraEffect effect in aura.GetAuraEffects()) + { + if (effect != null) + { + index = 0; + stmt = DB.Characters.GetPreparedStatement(CharStatements.INS_AURA_EFFECT); + stmt.AddValue(index++, GetGUID().GetCounter()); + stmt.AddValue(index++, key.Caster.GetRawValue()); + stmt.AddValue(index++, key.Item.GetRawValue()); + stmt.AddValue(index++, key.SpellId); + stmt.AddValue(index++, key.EffectMask); + stmt.AddValue(index++, effect.GetEffIndex()); + stmt.AddValue(index++, effect.GetAmount()); + stmt.AddValue(index++, effect.GetBaseAmount()); + trans.Append(stmt); + } + } + } + } + void _SaveGlyphs(SQLTransaction trans) + { + PreparedStatement stmt = DB.Characters.GetPreparedStatement(CharStatements.DEL_CHAR_GLYPHS); + stmt.AddValue(0, GetGUID().GetCounter()); + trans.Append(stmt); + + for (byte spec = 0; spec < PlayerConst.MaxSpecializations; ++spec) + { + foreach (uint glyphId in GetGlyphs(spec)) + { + byte index = 0; + + stmt = DB.Characters.GetPreparedStatement(CharStatements.INS_CHAR_GLYPHS); + stmt.AddValue(index++, GetGUID().GetCounter()); + stmt.AddValue(index++, spec); + stmt.AddValue(index++, glyphId); + + trans.Append(stmt); + } + } + } + void _SaveCurrency(SQLTransaction trans) + { + PreparedStatement stmt = null; + foreach (var pair in _currencyStorage) + { + CurrencyTypesRecord entry = CliDB.CurrencyTypesStorage.LookupByKey(pair.Key); + if (entry == null) // should never happen + continue; + + switch (pair.Value.state) + { + case PlayerCurrencyState.New: + stmt = DB.Characters.GetPreparedStatement(CharStatements.REP_PLAYER_CURRENCY); + stmt.AddValue(0, GetGUID().GetCounter()); + stmt.AddValue(1, pair.Key); + stmt.AddValue(2, pair.Value.Quantity); + stmt.AddValue(3, pair.Value.WeeklyQuantity); + stmt.AddValue(4, pair.Value.TrackedQuantity); + stmt.AddValue(5, pair.Value.Flags); + trans.Append(stmt); + break; + case PlayerCurrencyState.Changed: + stmt = DB.Characters.GetPreparedStatement(CharStatements.UPD_PLAYER_CURRENCY); + stmt.AddValue(0, pair.Value.Quantity); + stmt.AddValue(1, pair.Value.WeeklyQuantity); + stmt.AddValue(2, pair.Value.TrackedQuantity); + stmt.AddValue(3, pair.Value.Flags); + stmt.AddValue(4, GetGUID().GetCounter()); + stmt.AddValue(5, pair.Key); + trans.Append(stmt); + break; + default: + break; + } + + pair.Value.state = PlayerCurrencyState.Unchanged; + } + } + void _SaveActions(SQLTransaction trans) + { + PreparedStatement stmt = null; + + foreach (var pair in m_actionButtons.ToList()) + { + switch (pair.Value.uState) + { + case ActionButtonUpdateState.New: + stmt = DB.Characters.GetPreparedStatement(CharStatements.INS_CHAR_ACTION); + stmt.AddValue(0, GetGUID().GetCounter()); + stmt.AddValue(1, GetActiveTalentGroup()); + stmt.AddValue(2, pair.Key); + stmt.AddValue(3, pair.Value.GetAction()); + stmt.AddValue(4, pair.Value.GetButtonType()); + trans.Append(stmt); + + pair.Value.uState = ActionButtonUpdateState.UnChanged; + break; + case ActionButtonUpdateState.Changed: + stmt = DB.Characters.GetPreparedStatement(CharStatements.UPD_CHAR_ACTION); + stmt.AddValue(0, pair.Value.GetAction()); + stmt.AddValue(1, pair.Value.GetButtonType()); + stmt.AddValue(2, GetGUID().GetCounter()); + stmt.AddValue(3, pair.Key); + stmt.AddValue(4, GetActiveTalentGroup()); + trans.Append(stmt); + + pair.Value.uState = ActionButtonUpdateState.UnChanged; + break; + case ActionButtonUpdateState.Deleted: + stmt = DB.Characters.GetPreparedStatement(CharStatements.DEL_CHAR_ACTION_BY_BUTTON_SPEC); + stmt.AddValue(0, GetGUID().GetCounter()); + stmt.AddValue(1, pair.Key); + stmt.AddValue(2, GetActiveTalentGroup()); + trans.Append(stmt); + + m_actionButtons.Remove(pair.Key); + break; + default: + break; + } + } + } + void _SaveQuestStatus(SQLTransaction trans) + { + bool isTransaction = trans != null; + if (!isTransaction) + trans = new SQLTransaction(); + + PreparedStatement stmt = null; + bool keepAbandoned = !Global.WorldMgr.GetCleaningFlags().HasAnyFlag(CleaningFlags.Queststatus); + + foreach (var save in m_QuestStatusSave) + { + if (save.Value == QuestSaveType.Default) + { + var data = m_QuestStatus.LookupByKey(save.Key); + if (data != null && (keepAbandoned || data.Status != QuestStatus.None)) + { + stmt = DB.Characters.GetPreparedStatement(CharStatements.REP_CHAR_QUESTSTATUS); + stmt.AddValue(0, GetGUID().GetCounter()); + stmt.AddValue(1, save.Key); + stmt.AddValue(2, data.Status); + stmt.AddValue(3, data.Timer / Time.InMilliseconds + Global.WorldMgr.GetGameTime()); + trans.Append(stmt); + + // Save objectives + for (int i = 0; i < data.ObjectiveData.Length; ++i) + { + stmt = DB.Characters.GetPreparedStatement(CharStatements.REP_CHAR_QUESTSTATUS_OBJECTIVES); + stmt.AddValue(0, GetGUID().GetCounter()); + stmt.AddValue(1, save.Key); + stmt.AddValue(2, i); + stmt.AddValue(3, data.ObjectiveData[i]); + trans.Append(stmt); + } + } + } + else + { + // Delete + stmt = DB.Characters.GetPreparedStatement(CharStatements.DEL_CHAR_QUESTSTATUS_BY_QUEST); + stmt.AddValue(0, GetGUID().GetCounter()); + stmt.AddValue(1, save.Key); + trans.Append(stmt); + + stmt = DB.Characters.GetPreparedStatement(CharStatements.DEL_CHAR_QUESTSTATUS_OBJECTIVES_BY_QUEST); + stmt.AddValue(0, GetGUID().GetCounter()); + stmt.AddValue(1, save.Key); + trans.Append(stmt); + } + } + + m_QuestStatusSave.Clear(); + + foreach (var save in m_RewardedQuestsSave) + { + if (save.Value == QuestSaveType.Default) + { + stmt = DB.Characters.GetPreparedStatement(CharStatements.INS_CHAR_QUESTSTATUS_REWARDED); + stmt.AddValue(0, GetGUID().GetCounter()); + stmt.AddValue(1, save.Key); + trans.Append(stmt); + + } + else if (save.Value == QuestSaveType.ForceDelete || !keepAbandoned) + { + stmt = DB.Characters.GetPreparedStatement(CharStatements.DEL_CHAR_QUESTSTATUS_REWARDED_BY_QUEST); + stmt.AddValue(0, GetGUID().GetCounter()); + stmt.AddValue(1, save.Key); + trans.Append(stmt); + } + } + + m_RewardedQuestsSave.Clear(); + + if (!isTransaction) + DB.Characters.CommitTransaction(trans); + } + void _SaveDailyQuestStatus(SQLTransaction trans) + { + if (!m_DailyQuestChanged) + return; + + m_DailyQuestChanged = false; + + // save last daily quest time for all quests: we need only mostly reset time for reset check anyway + + // we don't need transactions here. + PreparedStatement stmt = DB.Characters.GetPreparedStatement(CharStatements.DEL_CHARACTER_QUESTSTATUS_DAILY); + stmt.AddValue(0, GetGUID().GetCounter()); + trans.Append(stmt); + + var dailies = GetDynamicValues(PlayerDynamicFields.DailyQuests); + foreach (var questId in dailies) + { + stmt = DB.Characters.GetPreparedStatement(CharStatements.INS_CHARACTER_QUESTSTATUS_DAILY); + stmt.AddValue(0, GetGUID().GetCounter()); + stmt.AddValue(1, questId); + stmt.AddValue(2, m_lastDailyQuestTime); + trans.Append(stmt); + + } + + if (!m_DFQuests.Empty()) + { + foreach (var id in m_DFQuests) + { + stmt = DB.Characters.GetPreparedStatement(CharStatements.INS_CHARACTER_QUESTSTATUS_DAILY); + stmt.AddValue(0, GetGUID().GetCounter()); + stmt.AddValue(1, id); + stmt.AddValue(2, m_lastDailyQuestTime); + trans.Append(stmt); + } + } + } + void _SaveWeeklyQuestStatus(SQLTransaction trans) + { + if (!m_WeeklyQuestChanged || m_weeklyquests.Empty()) + return; + + // we don't need transactions here. + PreparedStatement stmt = DB.Characters.GetPreparedStatement(CharStatements.DEL_CHARACTER_QUESTSTATUS_WEEKLY); + stmt.AddValue(0, GetGUID().GetCounter()); + trans.Append(stmt); + + foreach (var quest_id in m_weeklyquests) + { + stmt = DB.Characters.GetPreparedStatement(CharStatements.INS_CHARACTER_QUESTSTATUS_WEEKLY); + stmt.AddValue(0, GetGUID().GetCounter()); + stmt.AddValue(1, quest_id); + trans.Append(stmt); + } + + m_WeeklyQuestChanged = false; + } + void _SaveSeasonalQuestStatus(SQLTransaction trans) + { + if (!m_SeasonalQuestChanged || m_seasonalquests.Empty()) + return; + + // we don't need transactions here. + PreparedStatement stmt = DB.Characters.GetPreparedStatement(CharStatements.DEL_CHARACTER_QUESTSTATUS_SEASONAL); + stmt.AddValue(0, GetGUID().GetCounter()); + trans.Append(stmt); + + foreach (var iter in m_seasonalquests) + { + stmt = DB.Characters.GetPreparedStatement(CharStatements.INS_CHARACTER_QUESTSTATUS_SEASONAL); + stmt.AddValue(0, GetGUID().GetCounter()); + stmt.AddValue(1, iter.Value); + stmt.AddValue(2, iter.Key); + trans.Append(stmt); + } + + m_SeasonalQuestChanged = false; + } + void _SaveMonthlyQuestStatus(SQLTransaction trans) + { + if (!m_MonthlyQuestChanged || m_monthlyquests.Empty()) + return; + + // we don't need transactions here. + PreparedStatement stmt = DB.Characters.GetPreparedStatement(CharStatements.DEL_CHARACTER_QUESTSTATUS_MONTHLY); + stmt.AddValue(0, GetGUID().GetCounter()); + trans.Append(stmt); + + foreach (var questId in m_monthlyquests) + { + stmt = DB.Characters.GetPreparedStatement(CharStatements.INS_CHARACTER_QUESTSTATUS_MONTHLY); + stmt.AddValue(0, GetGUID().GetCounter()); + stmt.AddValue(1, questId); + trans.Append(stmt); + } + + m_MonthlyQuestChanged = false; + } + void _SaveTalents(SQLTransaction trans) + { + PreparedStatement stmt = DB.Characters.GetPreparedStatement(CharStatements.DEL_CHAR_TALENT); + stmt.AddValue(0, GetGUID().GetCounter()); + trans.Append(stmt); + + Dictionary talents; + for (byte group = 0; group < PlayerConst.MaxSpecializations; ++group) + { + talents = GetTalentMap(group); + foreach (var pair in talents.ToList()) + { + if (pair.Value == PlayerSpellState.Removed) + { + talents.Remove(pair.Key); + continue; + } + + stmt = DB.Characters.GetPreparedStatement(CharStatements.INS_CHAR_TALENT); + stmt.AddValue(0, GetGUID().GetCounter()); + stmt.AddValue(1, pair.Key); + stmt.AddValue(2, group); + trans.Append(stmt); + } + } + } + public void _SaveMail(SQLTransaction trans) + { + if (!m_mailsLoaded) + return; + + PreparedStatement stmt; + + foreach (var m in m_mail) + { + if (m.state == MailState.Changed) + { + stmt = DB.Characters.GetPreparedStatement(CharStatements.UPD_MAIL); + stmt.AddValue(0, m.HasItems() ? 1 : 0); + stmt.AddValue(1, m.expire_time); + stmt.AddValue(2, m.deliver_time); + stmt.AddValue(3, m.money); + stmt.AddValue(4, m.COD); + stmt.AddValue(5, (byte)m.checkMask); + stmt.AddValue(6, m.messageID); + + trans.Append(stmt); + + if (!m.removedItems.Empty()) + { + foreach (var id in m.removedItems) + { + stmt = DB.Characters.GetPreparedStatement(CharStatements.DEL_MAIL_ITEM); + stmt.AddValue(0, id); + trans.Append(stmt); + } + m.removedItems.Clear(); + } + m.state = MailState.Unchanged; + } + else if (m.state == MailState.Deleted) + { + if (m.HasItems()) + { + foreach (var id in m.items) + { + stmt = DB.Characters.GetPreparedStatement(CharStatements.DEL_ITEM_INSTANCE); + stmt.AddValue(0, id.item_guid); + trans.Append(stmt); + } + } + stmt = DB.Characters.GetPreparedStatement(CharStatements.DEL_MAIL_BY_ID); + stmt.AddValue(0, m.messageID); + trans.Append(stmt); + + stmt = DB.Characters.GetPreparedStatement(CharStatements.DEL_MAIL_ITEM_BY_ID); + stmt.AddValue(0, m.messageID); + trans.Append(stmt); + } + } + + //deallocate deleted mails... + foreach (var m in GetMails().ToList()) + { + if (m.state == MailState.Deleted) + m_mail.Remove(m); + } + + m_mailsUpdated = false; + } + void _SaveStats(SQLTransaction trans) + { + // check if stat saving is enabled and if char level is high enough + if (WorldConfig.GetIntValue(WorldCfg.MinLevelStatSave) == 0 || getLevel() < WorldConfig.GetIntValue(WorldCfg.MinLevelStatSave)) + return; + + PreparedStatement stmt = DB.Characters.GetPreparedStatement(CharStatements.DEL_CHAR_STATS); + stmt.AddValue(0, GetGUID().GetCounter()); + trans.Append(stmt); + + byte index = 0; + stmt = DB.Characters.GetPreparedStatement(CharStatements.INS_CHAR_STATS); + stmt.AddValue(index++, GetGUID().GetCounter()); + stmt.AddValue(index++, GetMaxHealth()); + + for (byte i = 0; i < (int)PowerType.MaxPerClass; ++i) + stmt.AddValue(index++, GetMaxPower((PowerType)i)); + + for (byte i = 0; i < (int)Stats.Max; ++i) + stmt.AddValue(index++, GetStat((Stats)i)); + + for (int i = 0; i < (int)SpellSchools.Max; ++i) + stmt.AddValue(index++, GetResistance((SpellSchools)i)); + + stmt.AddValue(index++, GetFloatValue(PlayerFields.BlockPercentage)); + stmt.AddValue(index++, GetFloatValue(PlayerFields.DodgePercentage)); + stmt.AddValue(index++, GetFloatValue(PlayerFields.ParryPercentage)); + stmt.AddValue(index++, GetFloatValue(PlayerFields.CritPercentage)); + stmt.AddValue(index++, GetFloatValue(PlayerFields.RangedCritPercentage)); + stmt.AddValue(index++, GetFloatValue(PlayerFields.SpellCritPercentage1)); + stmt.AddValue(index++, GetUInt32Value(UnitFields.AttackPower)); + stmt.AddValue(index++, GetUInt32Value(UnitFields.RangedAttackPower)); + stmt.AddValue(index++, GetBaseSpellPowerBonus()); + stmt.AddValue(index, GetUInt32Value(PlayerFields.CombatRating1 + (int)CombatRating.ResiliencePlayerDamage)); + + trans.Append(stmt); + } + public void SaveGoldToDB(SQLTransaction trans) + { + PreparedStatement stmt = DB.Characters.GetPreparedStatement(CharStatements.UPD_CHAR_MONEY); + stmt.AddValue(0, GetMoney()); + stmt.AddValue(1, GetGUID().GetCounter()); + trans.Append(stmt); + } + public void SaveInventoryAndGoldToDB(SQLTransaction trans) + { + _SaveInventory(trans); + _SaveCurrency(trans); + SaveGoldToDB(trans); + } + void _SaveEquipmentSets(SQLTransaction trans) + { + foreach (var pair in _equipmentSets) + { + EquipmentSetInfo eqSet = pair.Value; + PreparedStatement stmt = null; + byte j = 0; + switch (eqSet.state) + { + case EquipmentSetUpdateState.Unchanged: + break; // do nothing + case EquipmentSetUpdateState.Changed: + if (eqSet.Data.Type == EquipmentSetInfo.EquipmentSetType.Equipment) + { + stmt = DB.Characters.GetPreparedStatement(CharStatements.UPD_EQUIP_SET); + stmt.AddValue(j++, eqSet.Data.SetName); + stmt.AddValue(j++, eqSet.Data.SetIcon); + stmt.AddValue(j++, eqSet.Data.IgnoreMask); + stmt.AddValue(j++, eqSet.Data.AssignedSpecIndex); + + for (byte i = 0; i < EquipmentSlot.End; ++i) + stmt.AddValue(j++, eqSet.Data.Pieces[i].GetCounter()); + + stmt.AddValue(j++, GetGUID().GetCounter()); + stmt.AddValue(j++, eqSet.Data.Guid); + stmt.AddValue(j, eqSet.Data.SetID); + } + else + { + stmt = DB.Characters.GetPreparedStatement(CharStatements.UPD_TRANSMOG_OUTFIT); + stmt.AddValue(j++, eqSet.Data.SetName); + stmt.AddValue(j++, eqSet.Data.SetIcon); + stmt.AddValue(j++, eqSet.Data.IgnoreMask); + + for (byte i = 0; i < EquipmentSlot.End; ++i) + stmt.AddValue(j++, eqSet.Data.Appearances[i]); + + for (int i = 0; i < eqSet.Data.Enchants.Count; ++i) + stmt.AddValue(j++, eqSet.Data.Enchants[i]); + + stmt.AddValue(j++, GetGUID().GetCounter()); + stmt.AddValue(j++, eqSet.Data.Guid); + stmt.AddValue(j, eqSet.Data.SetID); + } + + trans.Append(stmt); + eqSet.state = EquipmentSetUpdateState.Unchanged; + break; + case EquipmentSetUpdateState.New: + if (eqSet.Data.Type == EquipmentSetInfo.EquipmentSetType.Equipment) + { + stmt = DB.Characters.GetPreparedStatement(CharStatements.INS_EQUIP_SET); + stmt.AddValue(j++, GetGUID().GetCounter()); + stmt.AddValue(j++, eqSet.Data.Guid); + stmt.AddValue(j++, eqSet.Data.SetID); + stmt.AddValue(j++, eqSet.Data.SetName); + stmt.AddValue(j++, eqSet.Data.SetIcon); + stmt.AddValue(j++, eqSet.Data.IgnoreMask); + stmt.AddValue(j++, eqSet.Data.AssignedSpecIndex); + + for (byte i = 0; i < EquipmentSlot.End; ++i) + stmt.AddValue(j++, eqSet.Data.Pieces[i].GetCounter()); + } + else + { + stmt = DB.Characters.GetPreparedStatement(CharStatements.INS_TRANSMOG_OUTFIT); + stmt.AddValue(j++, GetGUID().GetCounter()); + stmt.AddValue(j++, eqSet.Data.Guid); + stmt.AddValue(j++, eqSet.Data.SetID); + stmt.AddValue(j++, eqSet.Data.SetName); + stmt.AddValue(j++, eqSet.Data.SetIcon); + stmt.AddValue(j++, eqSet.Data.IgnoreMask); + + for (byte i = 0; i < EquipmentSlot.End; ++i) + stmt.AddValue(j++, eqSet.Data.Appearances[i]); + + for (int i = 0; i < eqSet.Data.Enchants.Count; ++i) + stmt.AddValue(j++, eqSet.Data.Enchants[i]); + } + trans.Append(stmt); + eqSet.state = EquipmentSetUpdateState.Unchanged; + break; + case EquipmentSetUpdateState.Deleted: + if (eqSet.Data.Type == EquipmentSetInfo.EquipmentSetType.Equipment) + stmt = DB.Characters.GetPreparedStatement(CharStatements.DEL_EQUIP_SET); + else + stmt = DB.Characters.GetPreparedStatement(CharStatements.DEL_TRANSMOG_OUTFIT); + stmt.AddValue(0, eqSet.Data.Guid); + trans.Append(stmt); + _equipmentSets.Remove(pair.Key); + break; + } + } + } + void _SaveVoidStorage(SQLTransaction trans) + { + PreparedStatement stmt = null; + for (byte i = 0; i < SharedConst.VoidStorageMaxSlot; ++i) + { + if (_voidStorageItems[i] == null) // unused item + { + // DELETE FROM void_storage WHERE slot = ? AND playerGuid = ? + stmt = DB.Characters.GetPreparedStatement(CharStatements.DEL_CHAR_VOID_STORAGE_ITEM_BY_SLOT); + stmt.AddValue(0, i); + stmt.AddValue(1, GetGUID().GetCounter()); + } + + else + { + // REPLACE INTO character_void_storage (itemId, playerGuid, itemEntry, slot, creatorGuid, randomProperty, suffixFactor, upgradeId, fixedScalingLevel, artifactKnowledgeLevel, bonusListIDs) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?) + stmt = DB.Characters.GetPreparedStatement(CharStatements.REP_CHAR_VOID_STORAGE_ITEM); + stmt.AddValue(0, _voidStorageItems[i].ItemId); + stmt.AddValue(1, GetGUID().GetCounter()); + stmt.AddValue(2, _voidStorageItems[i].ItemEntry); + stmt.AddValue(3, i); + stmt.AddValue(4, _voidStorageItems[i].CreatorGuid.GetCounter()); + stmt.AddValue(5, (byte)_voidStorageItems[i].ItemRandomPropertyId.Type); + stmt.AddValue(6, _voidStorageItems[i].ItemRandomPropertyId.Id); + stmt.AddValue(7, _voidStorageItems[i].ItemSuffixFactor); + stmt.AddValue(8, _voidStorageItems[i].ItemUpgradeId); + stmt.AddValue(9, _voidStorageItems[i].FixedScalingLevel); + stmt.AddValue(10, _voidStorageItems[i].ArtifactKnowledgeLevel); + stmt.AddValue(11, _voidStorageItems[i].Context); + + StringBuilder bonusListIDs = new StringBuilder(); + foreach (uint bonusListID in _voidStorageItems[i].BonusListIDs) + bonusListIDs.AppendFormat("{0} ", bonusListID); + stmt.AddValue(12, bonusListIDs.ToString()); + } + + trans.Append(stmt); + } + } + void _SaveCUFProfiles(SQLTransaction trans) + { + PreparedStatement stmt = null; + ulong lowGuid = GetGUID().GetCounter(); + + for (byte i = 0; i < PlayerConst.MaxCUFProfiles; ++i) + { + if (_CUFProfiles[i] == null) // unused profile + { + // DELETE FROM character_cuf_profiles WHERE guid = ? and id = ? + stmt = DB.Characters.GetPreparedStatement(CharStatements.DEL_CHAR_CUF_PROFILES_BY_ID); + stmt.AddValue(0, lowGuid); + stmt.AddValue(1, i); + } + else + { + // REPLACE INTO character_cuf_profiles (guid, id, name, frameHeight, frameWidth, sortBy, healthText, boolOptions, unk146, unk147, unk148, unk150, unk152, unk154) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?) + stmt = DB.Characters.GetPreparedStatement(CharStatements.REP_CHAR_CUF_PROFILES); + stmt.AddValue(0, lowGuid); + stmt.AddValue(1, i); + stmt.AddValue(2, _CUFProfiles[i].ProfileName); + stmt.AddValue(3, _CUFProfiles[i].FrameHeight); + stmt.AddValue(4, _CUFProfiles[i].FrameWidth); + stmt.AddValue(5, _CUFProfiles[i].SortBy); + stmt.AddValue(6, _CUFProfiles[i].HealthText); + stmt.AddValue(7, (uint)_CUFProfiles[i].GetUlongOptionValue()); // 25 of 32 fields used, fits in an int + stmt.AddValue(8, _CUFProfiles[i].TopPoint); + stmt.AddValue(9, _CUFProfiles[i].BottomPoint); + stmt.AddValue(10, _CUFProfiles[i].LeftPoint); + stmt.AddValue(11, _CUFProfiles[i].TopOffset); + stmt.AddValue(12, _CUFProfiles[i].BottomOffset); + stmt.AddValue(13, _CUFProfiles[i].LeftOffset); + } + + trans.Append(stmt); + } + } + void _SaveInstanceTimeRestrictions(SQLTransaction trans) + { + if (_instanceResetTimes.Empty()) + return; + + PreparedStatement stmt = DB.Characters.GetPreparedStatement(CharStatements.DEL_ACCOUNT_INSTANCE_LOCK_TIMES); + stmt.AddValue(0, GetSession().GetAccountId()); + trans.Append(stmt); + + foreach (var pair in _instanceResetTimes) + { + stmt = DB.Characters.GetPreparedStatement(CharStatements.INS_ACCOUNT_INSTANCE_LOCK_TIMES); + stmt.AddValue(0, GetSession().GetAccountId()); + stmt.AddValue(1, pair.Key); + stmt.AddValue(2, pair.Value); + trans.Append(stmt); + } + } + void _SaveBGData(SQLTransaction trans) + { + PreparedStatement stmt = DB.Characters.GetPreparedStatement(CharStatements.DEL_PLAYER_BGDATA); + stmt.AddValue(0, GetGUID().GetCounter()); + trans.Append(stmt); + + stmt = DB.Characters.GetPreparedStatement(CharStatements.INS_PLAYER_BGDATA); + stmt.AddValue(0, GetGUID().GetCounter()); + stmt.AddValue(1, m_bgData.bgInstanceID); + stmt.AddValue(2, m_bgData.bgTeam); + stmt.AddValue(3, m_bgData.joinPos.GetPositionX()); + stmt.AddValue(4, m_bgData.joinPos.GetPositionY()); + stmt.AddValue(5, m_bgData.joinPos.GetPositionZ()); + stmt.AddValue(6, m_bgData.joinPos.GetOrientation()); + stmt.AddValue(7, (ushort)m_bgData.joinPos.GetMapId()); + stmt.AddValue(8, m_bgData.taxiPath[0]); + stmt.AddValue(9, m_bgData.taxiPath[1]); + stmt.AddValue(10, m_bgData.mountSpell); + trans.Append(stmt); + } + + public bool LoadFromDB(ObjectGuid guid, SQLQueryHolder holder) + { + SQLResult result = holder.GetResult(PlayerLoginQueryLoad.From); + if (result.IsEmpty()) + { + string name; + ObjectManager.GetPlayerNameByGUID(guid, out name); + Log.outError(LogFilter.Player, "Player {0} {1} not found in table `characters`, can't load. ", name, guid.ToString()); + return false; + } + + uint dbAccountId = result.Read(1); + + // check if the character's account in the db and the logged in account match. + // player should be able to load/delete character only with correct account! + if (dbAccountId != GetSession().GetAccountId()) + { + Log.outError(LogFilter.Player, "Player (GUID: {0}) loading from wrong account (is: {1}, should be: {2})", GetGUID().ToString(), GetSession().GetAccountId(), dbAccountId); + return false; + } + + SQLResult banResult = holder.GetResult(PlayerLoginQueryLoad.Banned); + if (!banResult.IsEmpty()) + { + Log.outError(LogFilter.Player, "{0} is banned, can't load.", guid.ToString()); + return false; + } + + _Create(guid); + + SetName(result.Read(2)); + + // check name limitations + if (ObjectManager.CheckPlayerName(GetName(), GetSession().GetSessionDbcLocale()) != ResponseCodes.CharNameSuccess || + (!GetSession().HasPermission(RBACPermissions.SkipCheckCharacterCreationReservedname) && Global.ObjectMgr.IsReservedName(GetName()))) + { + PreparedStatement stmt = DB.Characters.GetPreparedStatement(CharStatements.UPD_ADD_AT_LOGIN_FLAG); + stmt.AddValue(0, (ushort)AtLoginFlags.Rename); + stmt.AddValue(1, guid.GetCounter()); + DB.Characters.Execute(stmt); + return false; + } + + // overwrite possible wrong/corrupted guid + SetGuidValue(ObjectFields.Guid, guid); + SetGuidValue(PlayerFields.WowAccount, GetSession().GetAccountGUID()); + + uint gender = result.Read(5); + if (gender >= (uint)Gender.None) + { + Log.outError(LogFilter.Player, "Player {0} has wrong gender ({1}), can't be loaded.", guid.ToString(), gender); + return false; + } + + SetByteValue(UnitFields.Bytes0, 0, result.Read(3)); + SetByteValue(UnitFields.Bytes0, 1, result.Read(4)); + SetByteValue(UnitFields.Bytes0, 3, (byte)gender); + + // check if race/class combination is valid + PlayerInfo info = Global.ObjectMgr.GetPlayerInfo(GetRace(), GetClass()); + if (info == null) + { + Log.outError(LogFilter.Player, "Player {0} has wrong race/class ({1}/{2}), can't be loaded.", guid.ToString(), GetRace(), GetClass()); + return false; + } + + SetUInt32Value(UnitFields.Level, result.Read(6)); + SetUInt32Value(PlayerFields.Xp, result.Read(7)); + + _LoadIntoDataField(result.Read(65), (int)PlayerFields.ExploredZones1, PlayerConst.ExploredZonesSize); + _LoadIntoDataField(result.Read(66), (int)PlayerFields.KnownTitles, PlayerConst.KnowTitlesSize * 2); + + SetObjectScale(1.0f); + SetFloatValue(UnitFields.HoverHeight, 1.0f); + + // load achievements before anything else to prevent multiple gains for the same achievement/criteria on every loading (as loading does call UpdateAchievementCriteria) + m_achievementSys.LoadFromDB(holder.GetResult(PlayerLoginQueryLoad.Achievements), holder.GetResult(PlayerLoginQueryLoad.CriteriaProgress)); + + ulong money = result.Read(8); + if (money > PlayerConst.MaxMoneyAmount) + money = PlayerConst.MaxMoneyAmount; + SetMoney(money); + + Array customDisplay = new Array(PlayerConst.CustomDisplaySize); + customDisplay.Add(result.Read(14)); + customDisplay.Add(result.Read(15)); + customDisplay.Add(result.Read(16)); + + SetByteValue(PlayerFields.Bytes, PlayerFieldOffsets.BytesOffsetSkinId, result.Read(9)); + SetByteValue(PlayerFields.Bytes, PlayerFieldOffsets.BytesOffsetFaceId, result.Read(10)); + SetByteValue(PlayerFields.Bytes, PlayerFieldOffsets.BytesOffsetHairStyleId, result.Read(11)); + SetByteValue(PlayerFields.Bytes, PlayerFieldOffsets.BytesOffsetHairColorId, result.Read(12)); + SetByteValue(PlayerFields.Bytes2, PlayerFieldOffsets.Bytes2OffsetFacialStyle, result.Read(13)); + for (byte i = 0; i < PlayerConst.CustomDisplaySize; ++i) + SetByteValue(PlayerFields.Bytes2, (byte)(PlayerFieldOffsets.Bytes2OffsetCustomDisplayOption + i), customDisplay[i]); + SetBankBagSlotCount(result.Read(17)); + SetByteValue(PlayerFields.Bytes3, PlayerFieldOffsets.Bytes3OffsetGender, (byte)gender); + SetByteValue(PlayerFields.Bytes3, PlayerFieldOffsets.Bytes3OffsetInebriation, result.Read(54)); + SetUInt32Value(PlayerFields.Flags, result.Read(19)); + SetUInt32Value(PlayerFields.FlagsEx, result.Read(20)); + SetInt32Value(PlayerFields.WatchedFactionIndex, (int)result.Read(53)); + + if (!ValidateAppearance((Race)result.Read(3), (Class)result.Read(4), (Gender)gender, + GetByteValue(PlayerFields.Bytes, PlayerFieldOffsets.BytesOffsetHairStyleId), + GetByteValue(PlayerFields.Bytes, PlayerFieldOffsets.BytesOffsetHairColorId), + GetByteValue(PlayerFields.Bytes, PlayerFieldOffsets.BytesOffsetFaceId), + GetByteValue(PlayerFields.Bytes2, PlayerFieldOffsets.Bytes2OffsetFacialStyle), + GetByteValue(PlayerFields.Bytes, PlayerFieldOffsets.BytesOffsetSkinId), customDisplay)) + { + Log.outError(LogFilter.Player, "Player {0} has wrong Appearance values (Hair/Skin/Color), can't be loaded.", guid.ToString()); + return false; + } + + // set which actionbars the client has active - DO NOT REMOVE EVER AGAIN (can be changed though, if it does change fieldwise) + SetByteValue(PlayerFields.FieldBytes, PlayerFieldOffsets.FieldBytesOffsetActionBarToggles, result.Read(67)); + + m_fishingSteps = result.Read(71); + + InitDisplayIds(); + + // cleanup inventory related item value fields (its will be filled correctly in _LoadInventory) + for (byte slot = EquipmentSlot.Start; slot < EquipmentSlot.End; ++slot) + { + SetGuidValue(PlayerFields.InvSlotHead + (slot * 4), ObjectGuid.Empty); + SetVisibleItemSlot(slot, null); + + m_items[slot] = null; + } + + //Need to call it to initialize m_team (m_team can be calculated from race) + //Other way is to saves m_team into characters table. + SetFactionForRace(GetRace()); + + // load home bind and check in same time class/race pair, it used later for restore broken positions + if (!_LoadHomeBind(holder.GetResult(PlayerLoginQueryLoad.HomeBind))) + return false; + + InitPrimaryProfessions(); // to max set before any spell loaded + + // init saved position, and fix it later if problematic + ulong transLowGUID = result.Read(40); + + Relocate(result.Read(21), result.Read(22), result.Read(23), result.Read(25)); + + uint mapId = result.Read(24); + uint instanceId = result.Read(62); + + var RelocateToHomebind = new Action(() => { mapId = homebind.GetMapId(); instanceId = 0; Relocate(homebind); }); + + SetDungeonDifficultyID(CheckLoadedDungeonDifficultyID((Difficulty)result.Read(48))); + SetRaidDifficultyID(CheckLoadedRaidDifficultyID((Difficulty)result.Read(69))); + SetLegacyRaidDifficultyID(CheckLoadedLegacyRaidDifficultyID((Difficulty)result.Read(70))); + + string taxi_nodes = result.Read(47); + + _LoadGroup(holder.GetResult(PlayerLoginQueryLoad.Group)); + + _LoadArenaTeamInfo(holder.GetResult(PlayerLoginQueryLoad.ArenaInfo)); + + // check arena teams integrity + for (byte arena_slot = 0; arena_slot < SharedConst.MaxArenaSlot; ++arena_slot) + { + uint arena_team_id = GetArenaTeamId(arena_slot); + if (arena_team_id == 0) + continue; + + ArenaTeam at = Global.ArenaTeamMgr.GetArenaTeamById(arena_team_id); + if (at != null) + if (at.IsMember(GetGUID())) + continue; + + // arena team not exist or not member, cleanup fields + for (int j = 0; j < 6; ++j) + SetArenaTeamInfoField(arena_slot, (ArenaTeamInfoType)j, 0); + } + + _LoadCurrency(holder.GetResult(PlayerLoginQueryLoad.Currency)); + SetUInt32Value(PlayerFields.LifetimeHonorableKills, result.Read(49)); + SetUInt16Value(PlayerFields.Kills, 0, result.Read(50)); + SetUInt16Value(PlayerFields.Kills, 1, result.Read(51)); + + _LoadBoundInstances(holder.GetResult(PlayerLoginQueryLoad.BoundInstances)); + _LoadInstanceTimeRestrictions(holder.GetResult(PlayerLoginQueryLoad.InstanceLockTimes)); + _LoadBGData(holder.GetResult(PlayerLoginQueryLoad.BgData)); + + GetSession().SetPlayer(this); + + Map map = null; + bool player_at_bg = false; + var mapEntry = CliDB.MapStorage.LookupByKey(mapId); + if (mapEntry == null || !IsPositionValid()) + { + Log.outError(LogFilter.Player, "Player (guidlow {0}) have invalid coordinates (MapId: {1} {2}). Teleport to default race/class locations.", guid.ToString(), mapId, GetPosition()); + RelocateToHomebind(); + } + else if (mapEntry != null && mapEntry.IsBattlegroundOrArena()) + { + Battleground currentBg = null; + if (m_bgData.bgInstanceID != 0) //saved in Battleground + currentBg = Global.BattlegroundMgr.GetBattleground(m_bgData.bgInstanceID, BattlegroundTypeId.None); + + player_at_bg = currentBg != null && currentBg.IsPlayerInBattleground(GetGUID()); + + if (player_at_bg && currentBg.GetStatus() != BattlegroundStatus.WaitLeave) + { + map = currentBg.GetBgMap(); + + BattlegroundQueueTypeId bgQueueTypeId = Global.BattlegroundMgr.BGQueueTypeId(currentBg.GetTypeID(), currentBg.GetArenaType()); + AddBattlegroundQueueId(bgQueueTypeId); + + m_bgData.bgTypeID = currentBg.GetTypeID(); + + //join player to Battlegroundgroup + currentBg.EventPlayerLoggedIn(this); + currentBg.AddOrSetPlayerToCorrectBgGroup(this, (Team)m_bgData.bgTeam); + + SetInviteForBattlegroundQueueType(bgQueueTypeId, currentBg.GetInstanceID()); + } + // Bg was not found - go to Entry Point + else + { + // leave bg + if (player_at_bg) + { + player_at_bg = false; + currentBg.RemovePlayerAtLeave(GetGUID(), false, true); + } + + // Do not look for instance if bg not found + WorldLocation _loc = GetBattlegroundEntryPoint(); + mapId = _loc.GetMapId(); + instanceId = 0; + + if (mapId == 0xFFFFFFFF) // BattlegroundEntry Point not found (???) + { + Log.outError(LogFilter.Player, "Player (guidlow {0}) was in BG in database, but BG was not found, and entry point was invalid! Teleport to default race/class locations.", guid.ToString()); + RelocateToHomebind(); + } + else + Relocate(_loc); + + // We are not in BG anymore + m_bgData.bgInstanceID = 0; + } + } + // currently we do not support transport in bg + else if (transLowGUID != 0) + { + ObjectGuid transGUID = ObjectGuid.Create(HighGuid.Transport, transLowGUID); + + Transport transport = null; + Transport go = Global.ObjAccessor.FindTransport(transGUID); + if (go) + transport = go; + + if (transport) + { + float x = result.Read(36); + float y = result.Read(37); + float z = result.Read(38); + float o = result.Read(39); + m_movementInfo.transport.pos = new Position(x, y, z, o); + transport.CalculatePassengerPosition(ref x, ref y, ref z, ref o); + + if (!GridDefines.IsValidMapCoord(x, y, z, o) || + // transport size limited + Math.Abs(m_movementInfo.transport.pos.posX) > 250.0f || + Math.Abs(m_movementInfo.transport.pos.posY) > 250.0f || + Math.Abs(m_movementInfo.transport.pos.posZ) > 250.0f) + { + Log.outError(LogFilter.Player, "Player (guidlow {0}) have invalid transport coordinates (X: {1} Y: {2} Z: {3} O: {4}). Teleport to bind location.", + guid.ToString(), x, y, z, o); + + m_movementInfo.transport.Reset(); + RelocateToHomebind(); + } + else + { + Relocate(x, y, z, o); + mapId = transport.GetMapId(); + + transport.AddPassenger(this); + } + } + else + { + Log.outError(LogFilter.Player, "Player (guidlow {0}) have problems with transport guid ({1}). Teleport to bind location.", + guid.ToString(), transLowGUID); + + RelocateToHomebind(); + } + } + // currently we do not support taxi in instance + else if (!string.IsNullOrEmpty(taxi_nodes)) + { + instanceId = 0; + + // Not finish taxi flight path + if (m_bgData.HasTaxiPath()) + { + for (int i = 0; i < 2; ++i) + m_taxi.AddTaxiDestination(m_bgData.taxiPath[i]); + } + if (!m_taxi.LoadTaxiDestinationsFromString(taxi_nodes, GetTeam())) + { + // problems with taxi path loading + TaxiNodesRecord nodeEntry = null; + uint node_id = m_taxi.GetTaxiSource(); + if (node_id != 0) + nodeEntry = CliDB.TaxiNodesStorage.LookupByKey(node_id); + + if (nodeEntry == null) // don't know taxi start node, to homebind + { + Log.outError(LogFilter.Player, "Character {0} have wrong data in taxi destination list, teleport to homebind.", GetGUID().ToString()); + RelocateToHomebind(); + } + else // have start node, to it + { + Log.outError(LogFilter.Player, "Character {0} have too short taxi destination list, teleport to original node.", GetGUID().ToString()); + mapId = nodeEntry.MapID; + Relocate(nodeEntry.Pos.X, nodeEntry.Pos.Y, nodeEntry.Pos.Z, 0.0f); + } + m_taxi.ClearTaxiDestinations(); + } + uint nodeid = m_taxi.GetTaxiSource(); + if (nodeid != 0) + { + // save source node as recall coord to prevent recall and fall from sky + var nodeEntry = CliDB.TaxiNodesStorage.LookupByKey(nodeid); + if (nodeEntry != null && nodeEntry.MapID == GetMapId()) + { + Contract.Assert(nodeEntry != null); // checked in m_taxi.LoadTaxiDestinationsFromString + mapId = nodeEntry.MapID; + Relocate(nodeEntry.Pos.X, nodeEntry.Pos.Y, nodeEntry.Pos.Z, 0.0f); + } + + // flight will started later + } + } + + // Map could be changed before + mapEntry = CliDB.MapStorage.LookupByKey(mapId); + // client without expansion support + if (mapEntry != null) + { + if (GetSession().GetExpansion() < mapEntry.Expansion()) + { + Log.outDebug(LogFilter.Player, "Player {0} using client without required expansion tried login at non accessible map {1}", GetName(), mapId); + RelocateToHomebind(); + } + + // fix crash (because of if (Map* map = _FindMap(instanceId)) in MapInstanced.CreateInstance) + if (instanceId != 0) + { + InstanceSave save = GetInstanceSave(mapId); + if (save != null) + if (save.GetInstanceId() != instanceId) + instanceId = 0; + } + } + + // NOW player must have valid map + // load the player's map here if it's not already loaded + if (!map) + map = Global.MapMgr.CreateMap(mapId, this, instanceId); + AreaTriggerStruct areaTrigger = null; + bool check = false; + + if (!map) + { + areaTrigger = Global.ObjectMgr.GetGoBackTrigger(mapId); + check = true; + } + else if (map.IsDungeon()) // if map is dungeon... + { + EnterState denyReason = ((InstanceMap)map).CannotEnter(this); + if (denyReason != 0) // ... and can't enter map, then look for entry point. + { + switch (denyReason) + { + case EnterState.CannotEnterDifficultyUnavailable: + SendTransferAborted(map.GetId(), TransferAbortReason.Difficulty, (byte)map.GetDifficultyID()); + break; + case EnterState.CannotEnterInstanceBindMismatch: + SendSysMessage(CypherStrings.InstanceBindMismatch, map.GetMapName()); + break; + case EnterState.CannotEnterTooManyInstances: + SendTransferAborted(map.GetId(), TransferAbortReason.TooManyInstances); + break; + case EnterState.CannotEnterMaxPlayers: + SendTransferAborted(map.GetId(), TransferAbortReason.MaxPlayers); + break; + case EnterState.CannotEnterZoneInCombat: + SendTransferAborted(map.GetId(), TransferAbortReason.ZoneInCombat); + break; + default: + break; + } + areaTrigger = Global.ObjectMgr.GetGoBackTrigger(mapId); + check = true; + } + else if (instanceId != 0 && Global.InstanceSaveMgr.GetInstanceSave(instanceId) == null) // ... and instance is reseted then look for entrance. + { + areaTrigger = Global.ObjectMgr.GetMapEntranceTrigger(mapId); + check = true; + } + } + + if (check) // in case of special event when creating map... + { + if (areaTrigger != null) // ... if we have an areatrigger, then relocate to new map/coordinates. + { + Relocate(areaTrigger.target_X, areaTrigger.target_Y, areaTrigger.target_Z, GetOrientation()); + if (mapId != areaTrigger.target_mapId) + { + mapId = areaTrigger.target_mapId; + map = Global.MapMgr.CreateMap(mapId, this); + } + } + else + { + Log.outError(LogFilter.Player, "Player {0} {1} Map: {2}, {3}. Areatrigger not found.", GetName(), guid.ToString(), mapId, GetPosition()); + RelocateToHomebind(); + map = null; + } + } + + if (!map) + { + mapId = info.MapId; + Relocate(info.PositionX, info.PositionY, info.PositionZ, 0.0f); + map = Global.MapMgr.CreateMap(mapId, this); + if (!map) + { + Log.outError(LogFilter.Player, "Player {0} {1} Map: {2}, {3}. Invalid default map coordinates or instance couldn't be created.", GetName(), guid.ToString(), mapId, GetPosition()); + return false; + } + } + + SetMap(map); + + // now that map position is determined, check instance validity + if (!CheckInstanceValidity(true) && !IsInstanceLoginGameMasterException()) + m_InstanceValid = false; + + if (player_at_bg) + map.ToBattlegroundMap().GetBG().AddPlayer(this); + + // randomize first save time in range [CONFIG_INTERVAL_SAVE] around [CONFIG_INTERVAL_SAVE] + // this must help in case next save after mass player load after server startup + m_nextSave = RandomHelper.URand(m_nextSave / 2, m_nextSave * 3 / 2); + + SaveRecallPosition(); + + long now = Time.UnixTime; + long logoutTime = result.Read(31); + + // since last logout (in seconds) + uint time_diff = (uint)(now - logoutTime); + + // set value, including drunk invisibility detection + // calculate sobering. after 15 minutes logged out, the player will be sober again + byte newDrunkValue = 0; + if (time_diff < (uint)GetDrunkValue() * 9) + newDrunkValue = (byte)(GetDrunkValue() - time_diff / 9); + + SetDrunkValue(newDrunkValue); + + m_cinematic = result.Read(27); + m_PlayedTimeTotal = result.Read(28); + m_PlayedTimeLevel = result.Read(29); + + SetTalentResetCost(result.Read(33)); + SetTalentResetTime(result.Read(34)); + + m_taxi.LoadTaxiMask(result.Read(26)); // must be before InitTaxiNodesForLevel + + PlayerExtraFlags extraflags = (PlayerExtraFlags)result.Read(41); + + m_stableSlots = result.Read(42); + if (m_stableSlots > 4) + { + Log.outError(LogFilter.Player, "Player can have not more {0} stable slots, but have in DB {1}", 4, m_stableSlots); + m_stableSlots = 4; + } + + atLoginFlags = (AtLoginFlags)result.Read(43); + + // Honor system + // Update Honor kills data + m_lastHonorUpdateTime = logoutTime; + UpdateHonorFields(); + + m_deathExpireTime = result.Read(46); + if (m_deathExpireTime > now + PlayerConst.MaxDeathCount * PlayerConst.DeathExpireStep) + m_deathExpireTime = now + PlayerConst.MaxDeathCount * PlayerConst.DeathExpireStep - 1; + + // clear charm/summon related fields + SetOwnerGUID(ObjectGuid.Empty); + SetGuidValue(UnitFields.CharmedBy, ObjectGuid.Empty); + SetGuidValue(UnitFields.Charm, ObjectGuid.Empty); + SetGuidValue(UnitFields.Summon, ObjectGuid.Empty); + SetGuidValue(PlayerFields.Farsight, ObjectGuid.Empty); + SetCreatorGUID(ObjectGuid.Empty); + + RemoveFlag(UnitFields.Flags2, UnitFlags2.ForceMove); + + // reset some aura modifiers before aura apply + SetUInt32Value(PlayerFields.TrackCreatures, 0); + SetUInt32Value(PlayerFields.TrackResources, 0); + + // make sure the unit is considered out of combat for proper loading + ClearInCombat(); + + // make sure the unit is considered not in duel for proper loading + SetGuidValue(PlayerFields.DuelArbiter, ObjectGuid.Empty); + SetUInt32Value(PlayerFields.DuelTeam, 0); + + // reset stats before loading any modifiers + InitStatsForLevel(); + InitTaxiNodesForLevel(); + InitRunes(); + + // rest bonus can only be calculated after InitStatsForLevel() + _restMgr.LoadRestBonus(RestTypes.XP, (PlayerRestState)result.Read(18), result.Read(30)); + + // load skills after InitStatsForLevel because it triggering aura apply also + _LoadSkills(holder.GetResult(PlayerLoginQueryLoad.Skills)); + UpdateSkillsForLevel(); + + SetPrimarySpecialization(result.Read(35)); + SetActiveTalentGroup(result.Read(63)); + ChrSpecializationRecord primarySpec = CliDB.ChrSpecializationStorage.LookupByKey(GetPrimarySpecialization()); + if (primarySpec == null || primarySpec.ClassID != (byte)GetClass() || GetActiveTalentGroup() >= PlayerConst.MaxSpecializations) + ResetTalentSpecialization(); + + uint lootSpecId = result.Read(64); + ChrSpecializationRecord chrSpec = CliDB.ChrSpecializationStorage.LookupByKey(lootSpecId); + if (chrSpec != null) + { + if (chrSpec.ClassID == (uint)GetClass()) + SetLootSpecId(lootSpecId); + } + + ChrSpecializationRecord spec = Global.DB2Mgr.GetChrSpecializationByIndex(GetClass(), GetActiveTalentGroup()); + if (spec != null) + SetUInt32Value(PlayerFields.CurrentSpecId, spec.Id); + + _LoadTalents(holder.GetResult(PlayerLoginQueryLoad.Talents)); + _LoadSpells(holder.GetResult(PlayerLoginQueryLoad.Spells)); + GetSession().GetCollectionMgr().LoadToys(); + GetSession().GetCollectionMgr().LoadHeirlooms(); + GetSession().GetCollectionMgr().LoadMounts(); + GetSession().GetCollectionMgr().LoadItemAppearances(); + + LearnSpecializationSpells(); + + _LoadGlyphs(holder.GetResult(PlayerLoginQueryLoad.Glyphs)); + _LoadAuras(holder.GetResult(PlayerLoginQueryLoad.Auras), holder.GetResult(PlayerLoginQueryLoad.AuraEffects), time_diff); + _LoadGlyphAuras(); + // add ghost flag (must be after aura load: PLAYER_FLAGS_GHOST set in aura) + if (HasFlag(PlayerFields.Flags, PlayerFlags.Ghost)) + m_deathState = DeathState.Dead; + + // after spell load, learn rewarded spell if need also + _LoadQuestStatus(holder.GetResult(PlayerLoginQueryLoad.QuestStatus)); + _LoadQuestStatusObjectives(holder.GetResult(PlayerLoginQueryLoad.QuestStatusObjectives)); + _LoadQuestStatusRewarded(holder.GetResult(PlayerLoginQueryLoad.QuestStatusRew)); + _LoadDailyQuestStatus(holder.GetResult(PlayerLoginQueryLoad.DailyQuestStatus)); + _LoadWeeklyQuestStatus(holder.GetResult(PlayerLoginQueryLoad.WeeklyQuestStatus)); + _LoadSeasonalQuestStatus(holder.GetResult(PlayerLoginQueryLoad.SeasonalQuestStatus)); + _LoadRandomBGStatus(holder.GetResult(PlayerLoginQueryLoad.RandomBg)); + + // after spell and quest load + InitTalentForLevel(); + LearnDefaultSkills(); + LearnCustomSpells(); + + // must be before inventory (some items required reputation check) + reputationMgr.LoadFromDB(holder.GetResult(PlayerLoginQueryLoad.Reputation)); + + _LoadInventory(holder.GetResult(PlayerLoginQueryLoad.Inventory), holder.GetResult(PlayerLoginQueryLoad.Artifacts), time_diff); + + if (IsVoidStorageUnlocked()) + _LoadVoidStorage(holder.GetResult(PlayerLoginQueryLoad.VoidStorage)); + + // update items with duration and realtime + UpdateItemDuration(time_diff, true); + + _LoadActions(holder.GetResult(PlayerLoginQueryLoad.Actions)); + + // unread mails and next delivery time, actual mails not loaded + _LoadMailInit(holder.GetResult(PlayerLoginQueryLoad.MailCount), holder.GetResult(PlayerLoginQueryLoad.MailDate)); + + m_social = Global.SocialMgr.LoadFromDB(holder.GetResult(PlayerLoginQueryLoad.SocialList), GetGUID()); + + // check PLAYER_CHOSEN_TITLE compatibility with PLAYER__FIELD_KNOWN_TITLES + // note: PLAYER__FIELD_KNOWN_TITLES updated at quest status loaded + uint curTitle = result.Read(52); + if (curTitle != 0 && !HasTitle(curTitle)) + curTitle = 0; + + SetUInt32Value(PlayerFields.ChosenTitle, curTitle); + + // has to be called after last Relocate() in Player.LoadFromDB + SetFallInformation(0, GetPositionZ()); + + GetSpellHistory().LoadFromDB(holder.GetResult(PlayerLoginQueryLoad.SpellCooldowns), holder.GetResult(PlayerLoginQueryLoad.SpellCharges)); + + // Spell code allow apply any auras to dead character in load time in aura/spell/item loading + // Do now before stats re-calculation cleanup for ghost state unexpected auras + if (!IsAlive()) + RemoveAllAurasOnDeath(); + else + RemoveAllAurasRequiringDeadTarget(); + + //apply all stat bonuses from items and auras + SetCanModifyStats(true); + UpdateAllStats(); + + // restore remembered power/health values (but not more max values) + uint savedHealth = result.Read(55); + SetHealth(savedHealth > GetMaxHealth() ? GetMaxHealth() : savedHealth); + int loadedPowers = 0; + for (PowerType i = 0; i < PowerType.Max; ++i) + { + if (Global.DB2Mgr.GetPowerIndexByClass(i, GetClass()) != (int)PowerType.Max) + { + uint savedPower = result.Read(56 + loadedPowers); + uint maxPower = GetUInt32Value(UnitFields.MaxPower + loadedPowers); + SetPower(i, (int)(savedPower > maxPower ? maxPower : savedPower)); + if (++loadedPowers >= (int)PowerType.MaxPerClass) + break; + } + } + + for (; loadedPowers < (int)PowerType.MaxPerClass; ++loadedPowers) + SetUInt32Value(UnitFields.Power + loadedPowers, 0); + + SetPower(PowerType.LunarPower, 0); + + Log.outDebug(LogFilter.Player, "The value of player {0} after load item and aura is: ", GetName()); + + // GM state + if (GetSession().HasPermission(RBACPermissions.RestoreSavedGmState)) + { + switch (WorldConfig.GetIntValue(WorldCfg.GmLoginState)) + { + default: + case 0: + break; // disable + case 1: SetGameMaster(true); + break; // enable + case 2: // save state + if (extraflags.HasAnyFlag(PlayerExtraFlags.GMOn)) + SetGameMaster(true); + break; + } + + switch (WorldConfig.GetIntValue(WorldCfg.GmVisibleState)) + { + default: + case 0: SetGMVisible(false); + break; // invisible + case 1: + break; // visible + case 2: // save state + if (extraflags.HasAnyFlag(PlayerExtraFlags.GMInvisible)) + SetGMVisible(false); + break; + } + + switch (WorldConfig.GetIntValue(WorldCfg.GmChat)) + { + default: + case 0: + break; // disable + case 1: SetGMChat(true); + break; // enable + case 2: // save state + if (extraflags.HasAnyFlag(PlayerExtraFlags.GMChat)) + SetGMChat(true); + break; + } + + switch (WorldConfig.GetIntValue(WorldCfg.GmWhisperingTo)) + { + default: + case 0: + break; // disable + case 1: SetAcceptWhispers(true); + break; // enable + case 2: // save state + if (extraflags.HasAnyFlag(PlayerExtraFlags.AcceptWhispers)) + SetAcceptWhispers(true); + break; + } + } + + // RaF stuff. + m_grantableLevels = result.Read(68); + if (GetSession().IsARecruiter() || (GetSession().GetRecruiterId() != 0)) + SetFlag(ObjectFields.DynamicFlags, UnitDynFlags.ReferAFriend); + + if (m_grantableLevels > 0) + SetByteValue(PlayerFields.FieldBytes, PlayerFieldOffsets.FieldBytesOffsetRafGrantableLevel, 0x01); + + _LoadDeclinedNames(holder.GetResult(PlayerLoginQueryLoad.DeclinedNames)); + + _LoadEquipmentSets(holder.GetResult(PlayerLoginQueryLoad.EquipmentSets)); + _LoadTransmogOutfits(holder.GetResult(PlayerLoginQueryLoad.TransmogOutfits)); + + _LoadCUFProfiles(holder.GetResult(PlayerLoginQueryLoad.CufProfiles)); + + var garrison = new Garrison(this); + if (garrison.LoadFromDB(holder.GetResult(PlayerLoginQueryLoad.Garrison), + holder.GetResult(PlayerLoginQueryLoad.GarrisonBlueprints), + holder.GetResult(PlayerLoginQueryLoad.GarrisonBuildings), + holder.GetResult(PlayerLoginQueryLoad.GarrisonFollowers), + holder.GetResult(PlayerLoginQueryLoad.GarrisonFollowerAbilities))) + _garrison = garrison; + + _InitHonorLevelOnLoadFromDB(result.Read(72), result.Read(73), result.Read(74)); + + _restMgr.LoadRestBonus(RestTypes.Honor, (PlayerRestState)result.Read(75), result.Read(76)); + if (time_diff > 0) + { + //speed collect rest bonus in offline, in logout, far from tavern, city (section/in hour) + float bubble0 = 0.031f; + //speed collect rest bonus in offline, in logout, in tavern, city (section/in hour) + float bubble1 = 0.125f; + float bubble = result.Read(32) > 0 + ? bubble1 * WorldConfig.GetFloatValue(WorldCfg.RateRestOfflineInTavernOrCity) + : bubble0 * WorldConfig.GetFloatValue(WorldCfg.RateRestOfflineInWilderness); + + _restMgr.AddRestBonus(RestTypes.XP, time_diff * _restMgr.CalcExtraPerSec(RestTypes.XP, bubble)); + } + + m_achievementSys.CheckAllAchievementCriteria(this); + + return true; + } + public void SaveToDB(bool create = false) + { + // delay auto save at any saves (manual, in code, or autosave) + m_nextSave = WorldConfig.GetUIntValue(WorldCfg.IntervalSave); + + //lets allow only players in world to be saved + if (IsBeingTeleportedFar()) + { + ScheduleDelayedOperation(PlayerDelayedOperations.SavePlayer); + return; + } + + // first save/honor gain after midnight will also update the player's honor fields + UpdateHonorFields(); + + SQLTransaction trans = new SQLTransaction(); + PreparedStatement stmt; + var index = 0; + + stmt = DB.Characters.GetPreparedStatement(CharStatements.DEL_CHAR_FISHINGSTEPS); + stmt.AddValue(0, GetGUID().GetCounter()); + trans.Append(stmt); + + if (create) + { + stmt = DB.Characters.GetPreparedStatement(CharStatements.INS_CHARACTER); + //! Insert Select + //! TO DO: Filter out more redundant fields that can take their default value at player create + stmt.AddValue(index++, GetGUID().GetCounter()); + stmt.AddValue(index++, GetSession().GetAccountId()); + stmt.AddValue(index++, GetName()); + stmt.AddValue(index++, (byte)GetRace()); + stmt.AddValue(index++, (byte)GetClass()); + stmt.AddValue(index++, GetByteValue(PlayerFields.Bytes3, PlayerFieldOffsets.Bytes3OffsetGender)); + stmt.AddValue(index++, getLevel()); + stmt.AddValue(index++, GetUInt32Value(PlayerFields.Xp)); + stmt.AddValue(index++, GetMoney()); + stmt.AddValue(index++, GetByteValue(PlayerFields.Bytes, PlayerFieldOffsets.BytesOffsetSkinId)); + stmt.AddValue(index++, GetByteValue(PlayerFields.Bytes, PlayerFieldOffsets.BytesOffsetFaceId)); + stmt.AddValue(index++, GetByteValue(PlayerFields.Bytes, PlayerFieldOffsets.BytesOffsetHairStyleId)); + stmt.AddValue(index++, GetByteValue(PlayerFields.Bytes, PlayerFieldOffsets.BytesOffsetHairColorId)); + stmt.AddValue(index++, GetByteValue(PlayerFields.Bytes2, PlayerFieldOffsets.Bytes2OffsetFacialStyle)); + for (int i = 0; i < PlayerConst.CustomDisplaySize; ++i) + stmt.AddValue(index++, GetByteValue(PlayerFields.Bytes2, (byte)(PlayerFieldOffsets.Bytes2OffsetCustomDisplayOption + i))); + stmt.AddValue(index++, GetBankBagSlotCount()); + stmt.AddValue(index++, (byte)GetUInt32Value(PlayerFields.RestInfo + PlayerFieldOffsets.RestStateXp)); + stmt.AddValue(index++, GetUInt32Value(PlayerFields.Flags)); + stmt.AddValue(index++, GetUInt32Value(PlayerFields.FlagsEx)); + stmt.AddValue(index++, (ushort)GetMapId()); + stmt.AddValue(index++, GetInstanceId()); + stmt.AddValue(index++, (byte)GetDungeonDifficultyID()); + stmt.AddValue(index++, (byte)GetRaidDifficultyID()); + stmt.AddValue(index++, (byte)GetLegacyRaidDifficultyID()); + stmt.AddValue(index++, GetPositionX()); + stmt.AddValue(index++, GetPositionY()); + stmt.AddValue(index++, GetPositionZ()); + stmt.AddValue(index++, GetOrientation()); + stmt.AddValue(index++, GetTransOffsetX()); + stmt.AddValue(index++, GetTransOffsetY()); + stmt.AddValue(index++, GetTransOffsetZ()); + stmt.AddValue(index++, GetTransOffsetO()); + ulong transLowGUID = 0; + if (GetTransport()) + transLowGUID = GetTransport().GetGUID().GetCounter(); + stmt.AddValue(index++, transLowGUID); + + StringBuilder ss = new StringBuilder(); + for (byte i = 0; i < PlayerConst.TaxiMaskSize; ++i) + ss.Append(m_taxi.m_taximask[i] + " "); + + stmt.AddValue(index++, ss.ToString()); + stmt.AddValue(index++, m_cinematic); + stmt.AddValue(index++, m_PlayedTimeTotal); + stmt.AddValue(index++, m_PlayedTimeLevel); + stmt.AddValue(index++, _restMgr.GetRestBonus(RestTypes.XP)); + stmt.AddValue(index++, (uint)Time.UnixTime); + stmt.AddValue(index++, HasFlag(PlayerFields.Flags, PlayerFlags.Resting) ? 1 : 0); + //save, far from tavern/city + //save, but in tavern/city + stmt.AddValue(index++, GetTalentResetCost()); + stmt.AddValue(index++, GetTalentResetTime()); + + stmt.AddValue(index++, GetPrimarySpecialization()); + stmt.AddValue(index++, (ushort)m_ExtraFlags); + stmt.AddValue(index++, m_stableSlots); + stmt.AddValue(index++, (ushort)atLoginFlags); + stmt.AddValue(index++, GetZoneId()); + stmt.AddValue(index++, m_deathExpireTime); + + ss.Clear(); + ss.Append(m_taxi.SaveTaxiDestinationsToString()); + + stmt.AddValue(index++, ss.ToString()); + stmt.AddValue(index++, GetUInt32Value(PlayerFields.LifetimeHonorableKills)); + stmt.AddValue(index++, GetUInt16Value(PlayerFields.Kills, 0)); + stmt.AddValue(index++, GetUInt16Value(PlayerFields.Kills, 1)); + stmt.AddValue(index++, (uint)GetInt32Value(PlayerFields.ChosenTitle)); + stmt.AddValue(index++, GetUInt32Value(PlayerFields.WatchedFactionIndex)); + stmt.AddValue(index++, GetDrunkValue()); + stmt.AddValue(index++, GetHealth()); + + int storedPowers = 0; + for (PowerType i = 0; i < PowerType.Max; ++i) + { + if (Global.DB2Mgr.GetPowerIndexByClass(i, GetClass()) != (int)PowerType.Max) + { + stmt.AddValue(index++, GetUInt32Value(UnitFields.Power + storedPowers)); + storedPowers++; + if (storedPowers >= (int)PowerType.MaxPerClass) + break; + } + } + + for (; storedPowers < (int)PowerType.MaxPerClass; ++storedPowers) + stmt.AddValue(index++, 0); + + stmt.AddValue(index++, GetSession().GetLatency()); + + stmt.AddValue(index++, GetActiveTalentGroup()); + + stmt.AddValue(index++, GetLootSpecId()); + + ss.Clear(); + for (var i = 0; i < PlayerConst.ExploredZonesSize; ++i) + ss.AppendFormat("{0} ", GetUInt32Value(PlayerFields.ExploredZones1 + i)); + stmt.AddValue(index++, ss.ToString()); + + ss.Clear(); + // cache equipment... + for (byte i = 0; i < InventorySlots.BagEnd; ++i) + { + Item item = GetItemByPos(InventorySlots.Bag0, i); + if (item != null) + { + ss.AppendFormat("{0} {1} ", (uint)item.GetTemplate().GetInventoryType(), item.GetDisplayId(this)); + SpellItemEnchantmentRecord enchant = CliDB.SpellItemEnchantmentStorage.LookupByKey(item.GetVisibleEnchantmentId(this)); + if (enchant != null) + ss.Append(enchant.ItemVisual); + else + ss.Append(0); + + ss.Append(' '); + } + else + ss.Append("0 0 0 "); + } + stmt.AddValue(index++, ss.ToString()); + + ss.Clear(); + for (var i = 0; i < PlayerConst.KnowTitlesSize * 2; ++i) + ss.AppendFormat("{0} ", GetUInt32Value(PlayerFields.KnownTitles + i)); + stmt.AddValue(index++, ss.ToString()); + + stmt.AddValue(index++, GetByteValue(PlayerFields.FieldBytes, PlayerFieldOffsets.FieldBytesOffsetActionBarToggles)); + stmt.AddValue(index++, m_grantableLevels); + } + else + { + // Update query + stmt = DB.Characters.GetPreparedStatement(CharStatements.UPD_CHARACTER); + stmt.AddValue(index++, GetName()); + stmt.AddValue(index++, (byte)GetRace()); + stmt.AddValue(index++, (byte)GetClass()); + stmt.AddValue(index++, GetByteValue(PlayerFields.Bytes3, PlayerFieldOffsets.Bytes3OffsetGender)); + stmt.AddValue(index++, getLevel()); + stmt.AddValue(index++, GetUInt32Value(PlayerFields.Xp)); + stmt.AddValue(index++, GetMoney()); + stmt.AddValue(index++, GetByteValue(PlayerFields.Bytes, PlayerFieldOffsets.BytesOffsetSkinId)); + stmt.AddValue(index++, GetByteValue(PlayerFields.Bytes, PlayerFieldOffsets.BytesOffsetFaceId)); + stmt.AddValue(index++, GetByteValue(PlayerFields.Bytes, PlayerFieldOffsets.BytesOffsetHairStyleId)); + stmt.AddValue(index++, GetByteValue(PlayerFields.Bytes, PlayerFieldOffsets.BytesOffsetHairColorId)); + stmt.AddValue(index++, GetByteValue(PlayerFields.Bytes2, PlayerFieldOffsets.Bytes2OffsetFacialStyle)); + for (int i = 0; i < PlayerConst.CustomDisplaySize; ++i) + stmt.AddValue(index++, GetByteValue(PlayerFields.Bytes2, (byte)( + PlayerFieldOffsets.Bytes2OffsetCustomDisplayOption + i))); + stmt.AddValue(index++, GetBankBagSlotCount()); + stmt.AddValue(index++, (byte)GetUInt32Value(PlayerFields.RestInfo + PlayerFieldOffsets.RestStateXp)); + stmt.AddValue(index++, GetUInt32Value(PlayerFields.Flags)); + stmt.AddValue(index++, GetUInt32Value(PlayerFields.FlagsEx)); + + if (!IsBeingTeleported()) + { + stmt.AddValue(index++, GetMapId()); + stmt.AddValue(index++, GetInstanceId()); + stmt.AddValue(index++, (byte)GetDungeonDifficultyID()); + stmt.AddValue(index++, (byte)GetRaidDifficultyID()); + stmt.AddValue(index++, (byte)GetLegacyRaidDifficultyID()); + stmt.AddValue(index++, GetPositionX()); + stmt.AddValue(index++, GetPositionY()); + stmt.AddValue(index++, GetPositionZ()); + stmt.AddValue(index++, GetOrientation()); + } + else + { + stmt.AddValue(index++, GetTeleportDest().GetMapId()); + stmt.AddValue(index++, 0); + stmt.AddValue(index++, (byte)GetDungeonDifficultyID()); + stmt.AddValue(index++, (byte)GetRaidDifficultyID()); + stmt.AddValue(index++, (byte)GetLegacyRaidDifficultyID()); + stmt.AddValue(index++, GetTeleportDest().GetPositionX()); + stmt.AddValue(index++, GetTeleportDest().GetPositionY()); + stmt.AddValue(index++, GetTeleportDest().GetPositionZ()); + stmt.AddValue(index++, GetTeleportDest().GetOrientation()); + } + stmt.AddValue(index++, GetTransOffsetX()); + stmt.AddValue(index++, GetTransOffsetY()); + stmt.AddValue(index++, GetTransOffsetZ()); + stmt.AddValue(index++, GetTransOffsetO()); + ulong transLowGUID = 0; + if (GetTransport()) + transLowGUID = GetTransport().GetGUID().GetCounter(); + stmt.AddValue(index++, transLowGUID); + + StringBuilder ss = new StringBuilder(); + for (byte i = 0; i < PlayerConst.TaxiMaskSize; ++i) + ss.Append(m_taxi.m_taximask[i] + " "); + + stmt.AddValue(index++, ss.ToString()); + stmt.AddValue(index++, m_cinematic); + stmt.AddValue(index++, m_PlayedTimeTotal); + stmt.AddValue(index++, m_PlayedTimeLevel); + stmt.AddValue(index++, _restMgr.GetRestBonus(RestTypes.XP)); + stmt.AddValue(index++, Time.UnixTime); + stmt.AddValue(index++, HasFlag(PlayerFields.Flags, PlayerFlags.Resting) ? 1 : 0); + //save, far from tavern/city + //save, but in tavern/city + stmt.AddValue(index++, GetTalentResetCost()); + stmt.AddValue(index++, GetTalentResetTime()); + + stmt.AddValue(index++, GetPrimarySpecialization()); + stmt.AddValue(index++, (ushort)m_ExtraFlags); + stmt.AddValue(index++, m_stableSlots); + stmt.AddValue(index++, (ushort)atLoginFlags); + stmt.AddValue(index++, GetZoneId()); + stmt.AddValue(index++, m_deathExpireTime); + + ss.Clear(); + ss.Append(m_taxi.SaveTaxiDestinationsToString()); + + stmt.AddValue(index++, ss.ToString()); + stmt.AddValue(index++, GetUInt32Value(PlayerFields.LifetimeHonorableKills)); + stmt.AddValue(index++, GetUInt16Value(PlayerFields.Kills, 0)); + stmt.AddValue(index++, GetUInt16Value(PlayerFields.Kills, 1)); + stmt.AddValue(index++, GetUInt32Value(PlayerFields.ChosenTitle)); + stmt.AddValue(index++, (uint)GetInt32Value(PlayerFields.WatchedFactionIndex)); + stmt.AddValue(index++, GetDrunkValue()); + stmt.AddValue(index++, GetHealth()); + + int storedPowers = 0; + for (PowerType i = 0; i < PowerType.Max; ++i) + { + if (Global.DB2Mgr.GetPowerIndexByClass(i, GetClass()) != (int)PowerType.Max) + { + stmt.AddValue(index++, GetUInt32Value(UnitFields.Power + storedPowers)); + storedPowers++; + if (storedPowers >= (int)PowerType.MaxPerClass) + break; + } + } + + for (; storedPowers < (int)PowerType.MaxPerClass; ++storedPowers) + stmt.AddValue(index++, 0); + + stmt.AddValue(index++, GetSession().GetLatency()); + + stmt.AddValue(index++, GetActiveTalentGroup()); + + stmt.AddValue(index++, GetLootSpecId()); + + ss.Clear(); + for (var i = 0; i < PlayerConst.ExploredZonesSize; ++i) + ss.AppendFormat("{0} ", GetUInt32Value(PlayerFields.ExploredZones1 + i)); + stmt.AddValue(index++, ss.ToString()); + + ss.Clear(); + // cache equipment... + for (byte i = 0; i < InventorySlots.BagEnd; ++i) + { + Item item = GetItemByPos(InventorySlots.Bag0, i); + if (item != null) + { + ss.AppendFormat("{0} {1} ", (uint)item.GetTemplate().GetInventoryType(), item.GetDisplayId(this)); + SpellItemEnchantmentRecord enchant = CliDB.SpellItemEnchantmentStorage.LookupByKey(item.GetVisibleEnchantmentId(this)); + if (enchant != null) + ss.Append(enchant.ItemVisual); + else + ss.Append(0); + + ss.Append(' '); + } + else + ss.Append("0 0 0 "); + } + stmt.AddValue(index++, ss.ToString()); + + ss.Clear(); + for (var i = 0; i < PlayerConst.KnowTitlesSize * 2; ++i) + ss.AppendFormat("{0} ", GetUInt32Value(PlayerFields.KnownTitles + i)); + + stmt.AddValue(index++, ss.ToString()); + stmt.AddValue(index++, GetByteValue(PlayerFields.FieldBytes, PlayerFieldOffsets.FieldBytesOffsetActionBarToggles)); + stmt.AddValue(index++, m_grantableLevels); + + stmt.AddValue(index++, IsInWorld && !GetSession().PlayerLogout() ? 1 : 0); + stmt.AddValue(index++, GetUInt32Value(PlayerFields.Honor)); + stmt.AddValue(index++, GetHonorLevel()); + stmt.AddValue(index++, GetPrestigeLevel()); + stmt.AddValue(index++, (byte)GetUInt32Value(PlayerFields.RestInfo + PlayerFieldOffsets.RestStateHonor)); + stmt.AddValue(index++, _restMgr.GetRestBonus(RestTypes.Honor)); + + // Index + stmt.AddValue(index, GetGUID().GetCounter()); + } + + trans.Append(stmt); + + if (m_fishingSteps != 0) + { + stmt = DB.Characters.GetPreparedStatement(CharStatements.INS_CHAR_FISHINGSTEPS); + index = 0; + stmt.AddValue(index++, GetGUID().GetCounter()); + stmt.AddValue(index++, m_fishingSteps); + trans.Append(stmt); + } + + if (m_mailsUpdated) //save mails only when needed + _SaveMail(trans); + + _SaveBGData(trans); + _SaveInventory(trans); + _SaveVoidStorage(trans); + _SaveQuestStatus(trans); + _SaveDailyQuestStatus(trans); + _SaveWeeklyQuestStatus(trans); + _SaveSeasonalQuestStatus(trans); + _SaveMonthlyQuestStatus(trans); + _SaveGlyphs(trans); + _SaveTalents(trans); + _SaveSpells(trans); + GetSpellHistory().SaveToDB(trans); + _SaveActions(trans); + _SaveAuras(trans); + _SaveSkills(trans); + m_achievementSys.SaveToDB(trans); + reputationMgr.SaveToDB(trans); + _SaveEquipmentSets(trans); + GetSession().SaveTutorialsData(trans); // changed only while character in game + _SaveInstanceTimeRestrictions(trans); + _SaveCurrency(trans); + _SaveCUFProfiles(trans); + if (_garrison != null) + _garrison.SaveToDB(trans); + + // check if stats should only be saved on logout + // save stats can be out of transaction + if (GetSession().isLogingOut() || !WorldConfig.GetBoolValue(WorldCfg.StatsSaveOnlyOnLogout)) + _SaveStats(trans); + + DB.Characters.CommitTransaction(trans); + + // TODO: Move this out + trans = new SQLTransaction(); + GetSession().GetCollectionMgr().SaveAccountToys(trans); + GetSession().GetBattlePetMgr().SaveToDB(trans); + GetSession().GetCollectionMgr().SaveAccountHeirlooms(trans); + GetSession().GetCollectionMgr().SaveAccountMounts(trans); + GetSession().GetCollectionMgr().SaveAccountItemAppearances(trans); + + stmt = DB.Login.GetPreparedStatement(LoginStatements.DEL_BNET_LAST_PLAYER_CHARACTERS); + stmt.AddValue(0, GetSession().GetAccountId()); + stmt.AddValue(1, Global.WorldMgr.GetRealmId().Region); + stmt.AddValue(2, Global.WorldMgr.GetRealmId().Site); + trans.Append(stmt); + + stmt = DB.Login.GetPreparedStatement(LoginStatements.INS_BNET_LAST_PLAYER_CHARACTERS); + stmt.AddValue(0, GetSession().GetAccountId()); + stmt.AddValue(1, Global.WorldMgr.GetRealmId().Region); + stmt.AddValue(2, Global.WorldMgr.GetRealmId().Site); + stmt.AddValue(3, Global.WorldMgr.GetRealmId().Realm); + stmt.AddValue(4, GetName()); + stmt.AddValue(5, GetGUID().GetCounter()); + stmt.AddValue(6, Time.UnixTime); + trans.Append(stmt); + + DB.Login.CommitTransaction(trans); + + // save pet (hunter pet level and experience and all type pets health/mana). + Pet pet = GetPet(); + if (pet != null) + pet.SavePetToDB(PetSaveMode.AsCurrent); + } + void DeleteSpellFromAllPlayers(uint spellId) + { + PreparedStatement stmt = DB.Characters.GetPreparedStatement(CharStatements.DEL_INVALID_SPELL_SPELLS); + stmt.AddValue(0, spellId); + DB.Characters.Execute(stmt); + } + + public static uint GetGuildIdFromDB(ObjectGuid guid) + { + PreparedStatement stmt = DB.Characters.GetPreparedStatement(CharStatements.SEL_GUILD_MEMBER); + stmt.AddValue(0, guid.GetCounter()); + SQLResult result = DB.Characters.Query(stmt); + if (!result.IsEmpty()) + return result.Read(0); + + return 0; + } + public static byte GetRankFromDB(ObjectGuid guid) + { + PreparedStatement stmt = DB.Characters.GetPreparedStatement(CharStatements.SEL_GUILD_MEMBER); + stmt.AddValue(0, guid.GetCounter()); + SQLResult result = DB.Characters.Query(stmt); + if (!result.IsEmpty()) + return result.Read(1); + + return 0; + } + public static uint GetZoneIdFromDB(ObjectGuid guid) + { + ulong guidLow = guid.GetCounter(); + PreparedStatement stmt = DB.Characters.GetPreparedStatement(CharStatements.SEL_CHAR_ZONE); + stmt.AddValue(0, guidLow); + SQLResult result = DB.Characters.Query(stmt); + + if (result.IsEmpty()) + return 0; + + uint zone = result.Read(0); + + if (zone == 0) + { + // stored zone is zero, use generic and slow zone detection + stmt = DB.Characters.GetPreparedStatement(CharStatements.SEL_CHAR_POSITION_XYZ); + stmt.AddValue(0, guidLow); + result = DB.Characters.Query(stmt); + + if (result.IsEmpty()) + return 0; + + uint map = result.Read(0); + float posx = result.Read(1); + float posy = result.Read(2); + float posz = result.Read(3); + + if (!CliDB.MapStorage.ContainsKey(map)) + return 0; + + zone = Global.MapMgr.GetZoneId(map, posx, posy, posz); + + if (zone > 0) + { + stmt = DB.Characters.GetPreparedStatement(CharStatements.UPD_ZONE); + + stmt.AddValue(0, zone); + stmt.AddValue(1, guidLow); + + DB.Characters.Execute(stmt); + } + } + + return zone; + } + public static uint GetLevelFromDB(ObjectGuid guid) + { + PreparedStatement stmt = DB.Characters.GetPreparedStatement(CharStatements.SEL_CHAR_LEVEL); + stmt.AddValue(0, guid.GetCounter()); + SQLResult result = DB.Characters.Query(stmt); + + if (result.IsEmpty()) + return 0; + + return result.Read(0); + } + public static void RemovePetitionsAndSigns(ObjectGuid guid) + { + PreparedStatement stmt = DB.Characters.GetPreparedStatement(CharStatements.SEL_PETITION_SIG_BY_GUID); + stmt.AddValue(0, guid.GetCounter()); + SQLResult result = DB.Characters.Query(stmt); + if (!result.IsEmpty()) + { + do // this part effectively does nothing, since the deletion / modification only takes place _after_ the PetitionSelect. Though I don't know if the result remains intact if I execute the delete Select beforehand. + { // and SendPetitionSelectOpcode reads data from the DB + ObjectGuid ownerguid = ObjectGuid.Create(HighGuid.Player, result.Read(0)); + ObjectGuid petitionguid = ObjectGuid.Create(HighGuid.Item, result.Read(1)); + + // send update if charter owner in game + Player owner = Global.ObjAccessor.FindPlayer(ownerguid); + if (owner != null) + owner.GetSession().SendPetitionQuery(petitionguid); + } while (result.NextRow()); + + stmt = DB.Characters.GetPreparedStatement(CharStatements.DEL_ALL_PETITION_SIGNATURES); + stmt.AddValue(0, guid.GetCounter()); + DB.Characters.Execute(stmt); + + } + + SQLTransaction trans = new SQLTransaction(); + stmt = DB.Characters.GetPreparedStatement(CharStatements.DEL_PETITION_BY_OWNER); + stmt.AddValue(0, guid.GetCounter()); + trans.Append(stmt); + + stmt = DB.Characters.GetPreparedStatement(CharStatements.DEL_PETITION_SIGNATURE_BY_OWNER); + stmt.AddValue(0, guid.GetCounter()); + trans.Append(stmt); + + DB.Characters.CommitTransaction(trans); + } + public static void DeleteFromDB(ObjectGuid playerGuid, uint accountId, bool updateRealmChars = true, bool deleteFinally = false) + { + // Avoid realm-update for non-existing account + if (accountId == 0) + updateRealmChars = false; + + // Convert guid to low GUID for CharacterNameData, but also other methods on success + ulong guid = playerGuid.GetCounter(); + CharDeleteMethod charDelete_method = (CharDeleteMethod)WorldConfig.GetIntValue(WorldCfg.ChardeleteMethod); + + CharacterInfo characterInfo; + if (deleteFinally) + charDelete_method = CharDeleteMethod.Remove; + else if ((characterInfo = Global.WorldMgr.GetCharacterInfo(playerGuid)) != null) // To avoid a Select, we select loaded data. If it doesn't exist, return. + { + // Define the required variables + uint charDeleteMinLvl; + + if (characterInfo.ClassID == Class.Deathknight) + charDeleteMinLvl = WorldConfig.GetUIntValue(WorldCfg.ChardeleteDeathKnightMinLevel); + else if (characterInfo.ClassID == Class.DemonHunter) + charDeleteMinLvl = WorldConfig.GetUIntValue(WorldCfg.ChardeleteDemonHunterMinLevel); + else + charDeleteMinLvl = WorldConfig.GetUIntValue(WorldCfg.ChardeleteMinLevel); + + // if we want to finalize the character removal or the character does not meet the level requirement of either heroic or non-heroic settings, + // we set it to mode CHAR_DELETE_REMOVE + if (characterInfo.Level < charDeleteMinLvl) + charDelete_method = CharDeleteMethod.Remove; + } + + uint guildId = GetGuildIdFromDB(playerGuid); + if (guildId != 0) + { + Guild guild = Global.GuildMgr.GetGuildById(guildId); + if (guild) + guild.DeleteMember(playerGuid, false, false, true); + } + + // remove from arena teams + LeaveAllArenaTeams(playerGuid); + + // the player was uninvited already on logout so just remove from group + PreparedStatement stmt = DB.Characters.GetPreparedStatement(CharStatements.SEL_GROUP_MEMBER); + stmt.AddValue(0, guid); + SQLResult resultGroup = DB.Characters.Query(stmt); + + if (!resultGroup.IsEmpty()) + { + Group group = Global.GroupMgr.GetGroupByDbStoreId(resultGroup.Read(0)); + if (group) + RemoveFromGroup(group, playerGuid); + } + + // Remove signs from petitions (also remove petitions if owner); + RemovePetitionsAndSigns(playerGuid); + + switch (charDelete_method) + { + // Completely remove from the database + case CharDeleteMethod.Remove: + { + SQLTransaction trans = new SQLTransaction(); + + stmt = DB.Characters.GetPreparedStatement(CharStatements.SEL_CHAR_COD_ITEM_MAIL); + stmt.AddValue(0, guid); + SQLResult resultMail = DB.Characters.Query(stmt); + if (!resultMail.IsEmpty()) + { + do + { + uint mail_id = resultMail.Read(0); + MailMessageType mailType = (MailMessageType)resultMail.Read(1); + ushort mailTemplateId = resultMail.Read(2); + uint sender = resultMail.Read(3); + string subject = resultMail.Read(4); + string body = resultMail.Read(5); + ulong money = resultMail.Read(6); + bool has_items = resultMail.Read(7); + + // We can return mail now + // So firstly delete the old one + stmt = DB.Characters.GetPreparedStatement(CharStatements.DEL_MAIL_BY_ID); + stmt.AddValue(0, mail_id); + trans.Append(stmt); + + // Mail is not from player + if (mailType != MailMessageType.Normal) + { + if (has_items) + { + stmt = DB.Characters.GetPreparedStatement(CharStatements.DEL_MAIL_ITEM_BY_ID); + stmt.AddValue(0, mail_id); + trans.Append(stmt); + } + continue; + } + + MailDraft draft = new MailDraft(subject, body); + if (mailTemplateId != 0) + draft = new MailDraft(mailTemplateId, false); // items are already included + + if (has_items) + { + // Data needs to be at first place for Item.LoadFromDB + stmt = DB.Characters.GetPreparedStatement(CharStatements.SEL_MAILITEMS); + stmt.AddValue(0, mail_id); + SQLResult resultItems = DB.Characters.Query(stmt); + if (!resultItems.IsEmpty()) + { + do + { + ulong itemGuidLow = resultItems.Read(0); + uint itemEntry = resultItems.Read(1); + + ItemTemplate itemProto = Global.ObjectMgr.GetItemTemplate(itemEntry); + if (itemProto == null) + { + stmt = DB.Characters.GetPreparedStatement(CharStatements.DEL_ITEM_INSTANCE); + stmt.AddValue(0, itemGuidLow); + trans.Append(stmt); + continue; + } + + Item item = Bag.NewItemOrBag(itemProto); + if (!item.LoadFromDB(itemGuidLow, ObjectGuid.Create(HighGuid.Player, guid), resultItems.GetFields(), itemEntry)) + { + item.FSetState(ItemUpdateState.Removed); + item.SaveToDB(trans); // it also deletes item object! + continue; + } + + draft.AddItem(item); + } + while (resultItems.NextRow()); + } + } + + stmt = DB.Characters.GetPreparedStatement(CharStatements.DEL_MAIL_ITEM_BY_ID); + stmt.AddValue(0, mail_id); + trans.Append(stmt); + + uint pl_account = ObjectManager.GetPlayerAccountIdByGUID(ObjectGuid.Create(HighGuid.Player, guid)); + + draft.AddMoney(money).SendReturnToSender(pl_account, guid, sender, trans); + } + while (resultMail.NextRow()); + } + + // Unsummon and delete for pets in world is not required: player deleted from CLI or character list with not loaded pet. + // NOW we can finally clear other DB data related to character + stmt = DB.Characters.GetPreparedStatement(CharStatements.SEL_CHAR_PETS); + stmt.AddValue(0, guid); + SQLResult resultPets = DB.Characters.Query(stmt); + + if (!resultPets.IsEmpty()) + { + do + { + uint petguidlow = resultPets.Read(0); + Pet.DeleteFromDB(petguidlow); + } while (resultPets.NextRow()); + } + + // Delete char from social list of online chars + stmt = DB.Characters.GetPreparedStatement(CharStatements.SEL_CHAR_SOCIAL); + stmt.AddValue(0, guid); + SQLResult resultFriends = DB.Characters.Query(stmt); + + if (!resultFriends.IsEmpty()) + { + do + { + Player playerFriend = Global.ObjAccessor.FindPlayer(ObjectGuid.Create(HighGuid.Player, resultFriends.Read(0))); + if (playerFriend) + { + playerFriend.GetSocial().RemoveFromSocialList(playerGuid, SocialFlag.All); + Global.SocialMgr.SendFriendStatus(playerFriend, FriendsResult.Removed, playerGuid); + } + } while (resultFriends.NextRow()); + } + + stmt = DB.Characters.GetPreparedStatement(CharStatements.DEL_CHARACTER); + stmt.AddValue(0, guid); + trans.Append(stmt); + + stmt = DB.Characters.GetPreparedStatement(CharStatements.DEL_PLAYER_ACCOUNT_DATA); + stmt.AddValue(0, guid); + trans.Append(stmt); + + stmt = DB.Characters.GetPreparedStatement(CharStatements.DEL_CHAR_DECLINED_NAME); + stmt.AddValue(0, guid); + trans.Append(stmt); + + stmt = DB.Characters.GetPreparedStatement(CharStatements.DEL_CHAR_ACTION); + stmt.AddValue(0, guid); + trans.Append(stmt); + + stmt = DB.Characters.GetPreparedStatement(CharStatements.DEL_CHARACTER_ARENA_STATS); + stmt.AddValue(0, guid); + trans.Append(stmt); + + stmt = DB.Characters.GetPreparedStatement(CharStatements.DEL_CHAR_AURA_EFFECT); + stmt.AddValue(0, guid); + trans.Append(stmt); + + stmt = DB.Characters.GetPreparedStatement(CharStatements.DEL_CHAR_AURA); + stmt.AddValue(0, guid); + trans.Append(stmt); + + stmt = DB.Characters.GetPreparedStatement(CharStatements.DEL_PLAYER_BGDATA); + stmt.AddValue(0, guid); + trans.Append(stmt); + + stmt = DB.Characters.GetPreparedStatement(CharStatements.DEL_BATTLEGROUND_RANDOM); + stmt.AddValue(0, guid); + trans.Append(stmt); + + stmt = DB.Characters.GetPreparedStatement(CharStatements.DEL_CHAR_CUF_PROFILES); + stmt.AddValue(0, guid); + trans.Append(stmt); + + stmt = DB.Characters.GetPreparedStatement(CharStatements.DEL_PLAYER_CURRENCY); + stmt.AddValue(0, guid); + trans.Append(stmt); + + stmt = DB.Characters.GetPreparedStatement(CharStatements.DEL_CHAR_GIFT); + stmt.AddValue(0, guid); + trans.Append(stmt); + + stmt = DB.Characters.GetPreparedStatement(CharStatements.DEL_PLAYER_HOMEBIND); + stmt.AddValue(0, guid); + trans.Append(stmt); + + stmt = DB.Characters.GetPreparedStatement(CharStatements.DEL_CHAR_INSTANCE); + stmt.AddValue(0, guid); + trans.Append(stmt); + + stmt = DB.Characters.GetPreparedStatement(CharStatements.DEL_CHAR_INVENTORY); + stmt.AddValue(0, guid); + trans.Append(stmt); + + stmt = DB.Characters.GetPreparedStatement(CharStatements.DEL_CHAR_QUESTSTATUS); + stmt.AddValue(0, guid); + trans.Append(stmt); + + stmt = DB.Characters.GetPreparedStatement(CharStatements.DEL_CHAR_QUESTSTATUS_OBJECTIVES); + stmt.AddValue(0, guid); + trans.Append(stmt); + + stmt = DB.Characters.GetPreparedStatement(CharStatements.DEL_CHAR_QUESTSTATUS_REWARDED); + stmt.AddValue(0, guid); + trans.Append(stmt); + + stmt = DB.Characters.GetPreparedStatement(CharStatements.DEL_CHAR_REPUTATION); + stmt.AddValue(0, guid); + trans.Append(stmt); + + stmt = DB.Characters.GetPreparedStatement(CharStatements.DEL_CHAR_SPELL); + stmt.AddValue(0, guid); + trans.Append(stmt); + + stmt = DB.Characters.GetPreparedStatement(CharStatements.DEL_CHAR_SPELL_COOLDOWNS); + stmt.AddValue(0, guid); + trans.Append(stmt); + + stmt = DB.Characters.GetPreparedStatement(CharStatements.DEL_ITEM_INSTANCE_GEMS_BY_OWNER); + stmt.AddValue(0, guid); + trans.Append(stmt); + + stmt = DB.Characters.GetPreparedStatement(CharStatements.DEL_ITEM_INSTANCE_TRANSMOG_BY_OWNER); + stmt.AddValue(0, guid); + trans.Append(stmt); + + stmt = DB.Characters.GetPreparedStatement(CharStatements.DEL_ITEM_INSTANCE_ARTIFACT_BY_OWNER); + stmt.AddValue(0, guid); + trans.Append(stmt); + + stmt = DB.Characters.GetPreparedStatement(CharStatements.DEL_ITEM_INSTANCE_ARTIFACT_POWERS_BY_OWNER); + stmt.AddValue(0, guid); + trans.Append(stmt); + + stmt = DB.Characters.GetPreparedStatement(CharStatements.DEL_ITEM_INSTANCE_MODIFIERS_BY_OWNER); + stmt.AddValue(0, guid); + trans.Append(stmt); + + stmt = DB.Characters.GetPreparedStatement(CharStatements.DEL_ITEM_INSTANCE_BY_OWNER); + stmt.AddValue(0, guid); + trans.Append(stmt); + + stmt = DB.Characters.GetPreparedStatement(CharStatements.DEL_CHAR_SOCIAL_BY_FRIEND); + stmt.AddValue(0, guid); + trans.Append(stmt); + + stmt = DB.Characters.GetPreparedStatement(CharStatements.DEL_CHAR_SOCIAL_BY_GUID); + stmt.AddValue(0, guid); + trans.Append(stmt); + + stmt = DB.Characters.GetPreparedStatement(CharStatements.DEL_MAIL); + stmt.AddValue(0, guid); + trans.Append(stmt); + + stmt = DB.Characters.GetPreparedStatement(CharStatements.DEL_MAIL_ITEMS); + stmt.AddValue(0, guid); + trans.Append(stmt); + + stmt = DB.Characters.GetPreparedStatement(CharStatements.DEL_CHAR_PET_BY_OWNER); + stmt.AddValue(0, guid); + trans.Append(stmt); + + stmt = DB.Characters.GetPreparedStatement(CharStatements.DEL_CHAR_PET_DECLINEDNAME_BY_OWNER); + stmt.AddValue(0, guid); + trans.Append(stmt); + + stmt = DB.Characters.GetPreparedStatement(CharStatements.DEL_CHAR_ACHIEVEMENTS); + stmt.AddValue(0, guid); + trans.Append(stmt); + + stmt = DB.Characters.GetPreparedStatement(CharStatements.DEL_CHAR_ACHIEVEMENT_PROGRESS); + stmt.AddValue(0, guid); + trans.Append(stmt); + + stmt = DB.Characters.GetPreparedStatement(CharStatements.DEL_CHAR_EQUIPMENTSETS); + stmt.AddValue(0, guid); + trans.Append(stmt); + + stmt = DB.Characters.GetPreparedStatement(CharStatements.DEL_CHAR_TRANSMOG_OUTFITS); + stmt.AddValue(0, guid); + trans.Append(stmt); + + stmt = DB.Characters.GetPreparedStatement(CharStatements.DEL_GUILD_EVENTLOG_BY_PLAYER); + stmt.AddValue(0, guid); + stmt.AddValue(1, guid); + trans.Append(stmt); + + stmt = DB.Characters.GetPreparedStatement(CharStatements.DEL_GUILD_BANK_EVENTLOG_BY_PLAYER); + stmt.AddValue(0, guid); + trans.Append(stmt); + + stmt = DB.Characters.GetPreparedStatement(CharStatements.DEL_CHAR_GLYPHS); + stmt.AddValue(0, guid); + trans.Append(stmt); + + stmt = DB.Characters.GetPreparedStatement(CharStatements.DEL_CHARACTER_QUESTSTATUS_DAILY); + stmt.AddValue(0, guid); + trans.Append(stmt); + + stmt = DB.Characters.GetPreparedStatement(CharStatements.DEL_CHARACTER_QUESTSTATUS_WEEKLY); + stmt.AddValue(0, guid); + trans.Append(stmt); + + stmt = DB.Characters.GetPreparedStatement(CharStatements.DEL_CHARACTER_QUESTSTATUS_MONTHLY); + stmt.AddValue(0, guid); + trans.Append(stmt); + + stmt = DB.Characters.GetPreparedStatement(CharStatements.DEL_CHARACTER_QUESTSTATUS_SEASONAL); + stmt.AddValue(0, guid); + trans.Append(stmt); + + stmt = DB.Characters.GetPreparedStatement(CharStatements.DEL_CHAR_TALENT); + stmt.AddValue(0, guid); + trans.Append(stmt); + + stmt = DB.Characters.GetPreparedStatement(CharStatements.DEL_CHAR_SKILLS); + stmt.AddValue(0, guid); + trans.Append(stmt); + + stmt = DB.Characters.GetPreparedStatement(CharStatements.DEL_CHAR_STATS); + stmt.AddValue(0, guid); + trans.Append(stmt); + + stmt = DB.Characters.GetPreparedStatement(CharStatements.DEL_CHAR_VOID_STORAGE_ITEM_BY_CHAR_GUID); + stmt.AddValue(0, guid); + trans.Append(stmt); + + stmt = DB.Characters.GetPreparedStatement(CharStatements.DEL_CHAR_FISHINGSTEPS); + stmt.AddValue(0, guid); + trans.Append(stmt); + + Corpse.DeleteFromDB(playerGuid, trans); + + Garrison.DeleteFromDB(guid, trans); + + DB.Characters.CommitTransaction(trans); + Global.WorldMgr.DeleteCharacterInfo(playerGuid); + break; + } + // The character gets unlinked from the account, the name gets freed up and appears as deleted ingame + case CharDeleteMethod.Unlink: + { + stmt = DB.Characters.GetPreparedStatement(CharStatements.UPD_DELETE_INFO); + stmt.AddValue(0, guid); + DB.Characters.Execute(stmt); + + Global.WorldMgr.UpdateCharacterInfoDeleted(playerGuid, true); + break; + } + default: + Log.outError(LogFilter.Player, "Player:DeleteFromDB: Unsupported delete method: {0}.", charDelete_method); + return; + } + + if (updateRealmChars) + Global.WorldMgr.UpdateRealmCharCount(accountId); + } + + public static void DeleteOldCharacters() + { + int keepDays = WorldConfig.GetIntValue(WorldCfg.ChardeleteKeepDays); + if (keepDays == 0) + return; + + Player.DeleteOldCharacters(keepDays); + } + + public static void DeleteOldCharacters(int keepDays) + { + Log.outInfo(LogFilter.Player, "Player:DeleteOldChars: Deleting all characters which have been deleted {0} days before...", keepDays); + + PreparedStatement stmt = DB.Characters.GetPreparedStatement(CharStatements.SEL_CHAR_OLD_CHARS); + stmt.AddValue(0, (uint)(Time.UnixTime - keepDays * Time.Day)); + SQLResult result = DB.Characters.Query(stmt); + + if (!result.IsEmpty()) + { + Log.outDebug(LogFilter.Player, "Player:DeleteOldChars: Found {0} character(s) to delete", result.GetRowCount()); + do + { + DeleteFromDB(ObjectGuid.Create(HighGuid.Player, result.Read(0)), result.Read(1), true, true); + } + while (result.NextRow()); + } + } + + public static void SavePositionInDB(WorldLocation loc, uint zoneId, ObjectGuid guid, SQLTransaction trans = null) + { + PreparedStatement stmt = DB.Characters.GetPreparedStatement(CharStatements.UPD_CHARACTER_POSITION); + stmt.AddValue(0, loc.GetPositionX()); + stmt.AddValue(1, loc.GetPositionY()); + stmt.AddValue(2, loc.GetPositionZ()); + stmt.AddValue(3, loc.GetOrientation()); + stmt.AddValue(4, (ushort)loc.GetMapId()); + stmt.AddValue(5, zoneId); + stmt.AddValue(6, guid.GetCounter()); + + DB.Characters.ExecuteOrAppend(trans, stmt); + } + public static bool LoadPositionFromDB(out WorldLocation loc, out bool inFlight, ObjectGuid guid) + { + PreparedStatement stmt = DB.Characters.GetPreparedStatement(CharStatements.SEL_CHAR_POSITION); + stmt.AddValue(0, guid.GetCounter()); + SQLResult result = DB.Characters.Query(stmt); + + loc = new WorldLocation(); + inFlight = false; + + if (result.IsEmpty()) + return false; + + loc.posX = result.Read(0); + loc.posY = result.Read(1); + loc.posZ = result.Read(2); + loc.Orientation = result.Read(3); + loc.SetMapId(result.Read(4)); + inFlight = !string.IsNullOrEmpty(result.Read(5)); + + return true; + } + } + + public enum CharDeleteMethod + { + Remove = 0, // Completely remove from the database + Unlink = 1 // The character gets unlinked from the account, + // the name gets freed up and appears as deleted ingame + } +} diff --git a/Game/Entities/Player/Player.Fields.cs b/Game/Entities/Player/Player.Fields.cs new file mode 100644 index 000000000..a3d9e0b1e --- /dev/null +++ b/Game/Entities/Player/Player.Fields.cs @@ -0,0 +1,612 @@ +/* + * Copyright (C) 2012-2017 CypherCore + * + * 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, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +using Framework.Constants; +using Game.Achievements; +using Game.Chat; +using Game.DataStorage; +using Game.Garrisons; +using Game.Groups; +using Game.Mails; +using Game.Maps; +using Game.Misc; +using Game.Spells; +using System.Collections; +using System.Collections.Generic; + +namespace Game.Entities +{ + public partial class Player + { + public WorldSession GetSession() { return Session; } + public PlayerSocial GetSocial() { return m_social; } + + //Gossip + public PlayerMenu PlayerTalkClass; + PlayerSocial m_social; + List m_channels = new List(); + List WhisperList = new List(); + public string autoReplyMsg; + + //Inventory + Dictionary _equipmentSets = new Dictionary(); + public List ItemSetEff = new List(); + List m_enchantDuration = new List(); + List m_itemDuration = new List(); + List m_itemSoulboundTradeable = new List(); + List m_refundableItems = new List(); + public List ItemUpdateQueue = new List(); + VoidStorageItem[] _voidStorageItems = new VoidStorageItem[SharedConst.VoidStorageMaxSlot]; + Item[] m_items = new Item[(int)PlayerSlots.Count]; + uint m_WeaponProficiency; + uint m_ArmorProficiency; + uint m_currentBuybackSlot; + TradeData m_trade; + + //PVP + BgBattlegroundQueueID_Rec[] m_bgBattlegroundQueueID = new BgBattlegroundQueueID_Rec[SharedConst.MaxPlayerBGQueues]; + BGData m_bgData; + bool m_IsBGRandomWinner; + public PvPInfo pvpInfo; + uint m_ArenaTeamIdInvited; + long m_lastHonorUpdateTime; + uint m_contestedPvPTimer; + + //Groups/Raids + GroupReference m_group = new GroupReference(); + GroupReference m_originalGroup = new GroupReference(); + Group m_groupInvite; + GroupUpdateFlags m_groupUpdateMask; + bool m_bPassOnGroupLoot; + GroupUpdateCounter[] m_groupUpdateSequences = new GroupUpdateCounter[2]; + + public Dictionary[] m_boundInstances = new Dictionary[(int)Difficulty.Max]; + Dictionary _instanceResetTimes = new Dictionary(); + uint _pendingBindId; + uint _pendingBindTimer; + public bool m_InstanceValid; + + Difficulty m_dungeonDifficulty; + Difficulty m_raidDifficulty; + Difficulty m_legacyRaidDifficulty; + Difficulty m_prevMapDifficulty; + + //Movement + public PlayerTaxi m_taxi = new PlayerTaxi(); + public byte[] m_forced_speed_changes = new byte[(int)UnitMoveType.Max]; + uint m_lastFallTime; + float m_lastFallZ; + WorldLocation teleportDest; + TeleportToOptions m_teleport_options; + bool mSemaphoreTeleport_Near; + bool mSemaphoreTeleport_Far; + PlayerDelayedOperations m_DelayedOperations; + bool m_bCanDelayTeleport; + bool m_bHasDelayedTeleport; + + PlayerUnderwaterState m_MirrorTimerFlags; + PlayerUnderwaterState m_MirrorTimerFlagsLast; + bool m_isInWater; + + //Stats + uint m_baseSpellPower; + uint m_baseManaRegen; + uint m_baseHealthRegen; + int m_spellPenetrationItemMod; + uint m_lastPotionId; + + //Spell + Dictionary m_spells = new Dictionary(); + Dictionary mSkillStatus = new Dictionary(); + Dictionary _currencyStorage = new Dictionary(); + List[][] m_spellMods = new List[(int)SpellModOp.Max][]; + MultiMap m_overrideSpells = new MultiMap(); + public Spell m_spellModTakingSpell; + uint m_oldpetspell; + // Rune type / Rune timer + uint[] m_runeGraceCooldown = new uint[PlayerConst.MaxRunes]; + uint[] m_lastRuneGraceTimers = new uint[PlayerConst.MaxRunes]; + + //Mail + List m_mail = new List(); + Dictionary mMitems = new Dictionary(); + public byte unReadMails; + long m_nextMailDelivereTime; + public bool m_mailsLoaded; + public bool m_mailsUpdated; + + //Pets + public uint m_stableSlots; + uint m_temporaryUnsummonedPetNumber; + uint m_lastpetnumber; + + // Player summoning + long m_summon_expire; + WorldLocation m_summon_location; + + RestMgr _restMgr; + + //Combat + int[] baseRatingValue = new int[(int)CombatRating.Max]; + public float[][] m_auraBaseMod = new float[(int)BaseModGroup.End][]; + public DuelInfo duel; + bool m_canParry; + bool m_canBlock; + bool m_canTitanGrip; + uint m_titanGripPenaltySpellId; + uint m_deathTimer; + long m_deathExpireTime; + byte m_swingErrorMsg; + uint m_combatExitTime; + uint m_regenTimerCount; + uint m_weaponChangeTimer; + + //Quest + List m_timedquests = new List(); + List m_weeklyquests = new List(); + List m_monthlyquests = new List(); + MultiMap m_seasonalquests = new MultiMap(); + Dictionary m_QuestStatus = new Dictionary(); + Dictionary m_QuestStatusSave = new Dictionary(); + List m_DFQuests = new List(); + List m_RewardedQuests = new List(); + Dictionary m_RewardedQuestsSave = new Dictionary(); + + bool m_DailyQuestChanged; + bool m_WeeklyQuestChanged; + bool m_MonthlyQuestChanged; + bool m_SeasonalQuestChanged; + long m_lastDailyQuestTime; + + Garrison _garrison; + + CinematicManager _cinematicMgr; + + // variables to save health and mana before duel and restore them after duel + ulong healthBeforeDuel; + uint manaBeforeDuel; + + bool _advancedCombatLoggingEnabled; + + WorldLocation _corpseLocation; + + //Core + WorldSession Session; + uint m_nextSave; + byte m_cinematic; + + uint m_movie; + + SpecializationInfo _specializationInfo; + public List m_clientGUIDs = new List(); + public WorldObject seerView; + // only changed for direct client control (possess, vehicle etc.), not stuff you control using pet commands + public Unit m_unitMovedByMe; + Team m_team; + public Stack m_timeSyncQueue = new Stack(); + uint m_timeSyncTimer; + public uint m_timeSyncClient; + public uint m_timeSyncServer; + ReputationMgr reputationMgr; + public AtLoginFlags atLoginFlags; + public bool m_itemUpdateQueueBlocked; + + PlayerExtraFlags m_ExtraFlags; + + public bool isDebugAreaTriggers { get; set; } + uint m_zoneUpdateId; + uint m_areaUpdateId; + uint m_zoneUpdateTimer; + + uint m_ChampioningFaction; + byte m_grantableLevels; + byte m_fishingSteps; + + // Recall position + WorldLocation m_recall_location; + WorldLocation homebind; + uint homebindAreaId; + uint m_HomebindTimer; + + ResurrectionData _resurrectionData; + + PlayerAchievementMgr m_achievementSys; + + SceneMgr m_sceneMgr; + + Dictionary m_AELootView = new Dictionary(); + + CUFProfile[] _CUFProfiles = new CUFProfile[PlayerConst.MaxCUFProfiles]; + float[] m_powerFraction = new float[(int)PowerType.MaxPerClass]; + int[] m_MirrorTimer = new int[3]; + + ulong m_GuildIdInvited; + DeclinedName _declinedname; + Runes m_runes = new Runes(); + uint m_drunkTimer; + long m_logintime; + long m_Last_tick; + uint m_PlayedTimeTotal; + uint m_PlayedTimeLevel; + + Dictionary m_actionButtons = new Dictionary(); + ObjectGuid m_divider; + uint m_ingametime; + + PlayerCommandStates _activeCheats; + } + + public class PlayerInfo + { + public uint MapId; + public uint ZoneId; + public float PositionX; + public float PositionY; + public float PositionZ; + public float Orientation; + + public uint DisplayId_m; + public uint DisplayId_f; + + public List item = new List(); + public List customSpells = new List(); + public List castSpells = new List(); + public List action = new List(); + public List skills = new List(); + + public PlayerLevelInfo[] levelInfo = new PlayerLevelInfo[WorldConfig.GetIntValue(WorldCfg.MaxPlayerLevel)]; + } + + public class PlayerCreateInfoItem + { + public PlayerCreateInfoItem(uint id, uint amount) + { + item_id = id; + item_amount = amount; + } + + public uint item_id; + public uint item_amount; + } + + public class PlayerCreateInfoAction + { + public PlayerCreateInfoAction() : this(0, 0, 0) { } + public PlayerCreateInfoAction(byte _button, uint _action, byte _type) + { + button = _button; + type = _type; + action = _action; + } + + public byte button; + public byte type; + public uint action; + } + + public class PlayerLevelInfo + { + public ushort[] stats = new ushort[(int)Stats.Max]; + } + + public class PlayerCurrency + { + public PlayerCurrencyState state; + public uint Quantity; + public uint WeeklyQuantity; + public uint TrackedQuantity; + public byte Flags; + } + + struct PlayerDynamicFieldSpellModByLabel + { + public uint Mod; + public float Value; + public uint Label; + } + + public class SpecializationInfo + { + public SpecializationInfo() + { + for (byte i = 0; i < PlayerConst.MaxSpecializations; ++i) + { + Talents[i] = new Dictionary(); + Glyphs[i] = new List(); + } + } + + public Dictionary[] Talents = new Dictionary[PlayerConst.MaxSpecializations]; + public List[] Glyphs = new List[PlayerConst.MaxSpecializations]; + public uint ResetTalentsCost; + public long ResetTalentsTime; + public uint PrimarySpecialization; + public byte ActiveGroup; + } + + public class Runes + { + public void SetRuneState(byte index, bool set = true) + { + var id = CooldownOrder.LookupByIndex(index); + if (set) + { + RuneState |= (byte)(1 << index); // usable + if (id == 0) + CooldownOrder.Add(index); + } + else + { + RuneState &= (byte)~(1 << index); // on cooldown + if (id != 0) + CooldownOrder.Remove(id); + } + } + + public List CooldownOrder = new List(); + public uint[] Cooldown = new uint[PlayerConst.MaxRunes]; + public byte RuneState; // mask of available runes + } + + public class ActionButton + { + public ActionButton() + { + packedData = 0; + uState = ActionButtonUpdateState.New; + } + + public ActionButtonType GetButtonType() { return (ActionButtonType)((packedData & 0xFFFFFFFF00000000) >> 56); } + public uint GetAction() { return (uint)(packedData & 0x00000000FFFFFFFF); } + public void SetActionAndType(ulong action, ActionButtonType type) + { + ulong newData = action | ((ulong)type << 56); + if (newData != packedData || uState == ActionButtonUpdateState.Deleted) + { + packedData = newData; + if (uState != ActionButtonUpdateState.New) + uState = ActionButtonUpdateState.Changed; + } + } + + public ulong packedData; + public ActionButtonUpdateState uState; + } + + public class ResurrectionData + { + public ObjectGuid GUID; + public WorldLocation Location = new WorldLocation(); + public uint Health; + public uint Mana; + public uint Aura; + } + + public struct PvPInfo + { + public bool IsHostile; + public bool IsInHostileArea; //> Marks if player is in an area which forces PvP flag + public bool IsInNoPvPArea; //> Marks if player is in a sanctuary or friendly capital city + public bool IsInFFAPvPArea; //> Marks if player is in an FFAPvP area (such as Gurubashi Arena) + public long EndTimer; //> Time when player unflags himself for PvP (flag removed after 5 minutes) + } + + public class DuelInfo + { + public Player initiator; + public Player opponent; + public long startTimer; + public long startTime; + public long outOfBound; + public bool isMounted; + public bool isCompleted; + + public bool IsDueling() { return opponent != null; } + } + + public class AccessRequirement + { + public byte levelMin; + public byte levelMax; + public uint item; + public uint item2; + public uint quest_A; + public uint quest_H; + public uint achievement; + public string questFailedText; + } + + public class EnchantDuration + { + public EnchantDuration(Item _item = null, EnchantmentSlot _slot = EnchantmentSlot.Max, uint _leftduration = 0) + { + item = _item; + slot = _slot; + leftduration = _leftduration; + } + + public Item item; + public EnchantmentSlot slot; + public uint leftduration; + } + + public class VoidStorageItem + { + public VoidStorageItem(ulong id, uint entry, ObjectGuid creator, ItemRandomEnchantmentId randomPropertyId, uint suffixFactor, uint upgradeId, uint fixedScalingLevel, uint artifactKnowledgeLevel, byte context, ICollection bonuses) + { + ItemId = id; + ItemEntry = entry; + CreatorGuid = creator; + ItemRandomPropertyId = randomPropertyId; + ItemSuffixFactor = suffixFactor; + ItemUpgradeId = upgradeId; + FixedScalingLevel = fixedScalingLevel; + ArtifactKnowledgeLevel = artifactKnowledgeLevel; + Context = context; + + foreach (var value in bonuses) + BonusListIDs.Add(value); + } + + public ulong ItemId; + public uint ItemEntry; + public ObjectGuid CreatorGuid; + public ItemRandomEnchantmentId ItemRandomPropertyId; + public uint ItemSuffixFactor; + public uint ItemUpgradeId; + public uint FixedScalingLevel; + public uint ArtifactKnowledgeLevel; + public byte Context; + public List BonusListIDs = new List(); + } + + public class EquipmentSetInfo + { + public EquipmentSetInfo() + { + state = EquipmentSetUpdateState.New; + Data = new EquipmentSetData(); + } + + public EquipmentSetUpdateState state; + public EquipmentSetData Data; + + // Data sent in EquipmentSet related packets + public class EquipmentSetData + { + public EquipmentSetType Type; + public ulong Guid; // Set Identifier + public uint SetID; // Index + public uint IgnoreMask ; // Mask of EquipmentSlot + public int AssignedSpecIndex = -1; // Index of character specialization that this set is automatically equipped for + public string SetName = ""; + public string SetIcon = ""; + public Array Pieces = new Array(EquipmentSlot.End); + public Array Appearances = new Array(EquipmentSlot.End); // ItemModifiedAppearanceID + public Array Enchants = new Array(2); // SpellItemEnchantmentID + } + + public enum EquipmentSetType + { + Equipment = 0, + Transmog = 1 + } + } + + public class BgBattlegroundQueueID_Rec + { + public BattlegroundQueueTypeId bgQueueTypeId; + public uint invitedToInstance; + public uint joinTime; + } + + // Holder for Battlegrounddata + public class BGData + { + public BGData() + { + bgTypeID = BattlegroundTypeId.None; + ClearTaxiPath(); + joinPos = new WorldLocation(); + } + + public uint bgInstanceID; //< This variable is set to bg.m_InstanceID, + // when player is teleported to BG - (it is Battleground's GUID) + public BattlegroundTypeId bgTypeID; + + public List bgAfkReporter = new List(); + public byte bgAfkReportedCount; + public long bgAfkReportedTimer; + + public uint bgTeam; //< What side the player will be added to + + public uint mountSpell; + public uint[] taxiPath = new uint[2]; + + public WorldLocation joinPos; //< From where player entered BG + + public void ClearTaxiPath() { taxiPath[0] = taxiPath[1] = 0; } + public bool HasTaxiPath() { return taxiPath[0] != 0 && taxiPath[1] != 0; } + } + + public class CUFProfile + { + public CUFProfile() + { + BoolOptions = new BitArray((int)CUFBoolOptions.BoolOptionsCount); + } + + public CUFProfile(string name, ushort frameHeight, ushort frameWidth, byte sortBy, byte healthText, uint boolOptions, + byte topPoint, byte bottomPoint, byte leftPoint, ushort topOffset, ushort bottomOffset, ushort leftOffset) + { + ProfileName = name; + BoolOptions = new BitArray(new int[] { (int)boolOptions }); + + FrameHeight = frameHeight; + FrameWidth = frameWidth; + SortBy = sortBy; + HealthText = healthText; + TopPoint = topPoint; + BottomPoint = bottomPoint; + LeftPoint = leftPoint; + TopOffset = topOffset; + BottomOffset = bottomOffset; + LeftOffset = leftOffset; + } + + public void SetOption(CUFBoolOptions opt, byte arg) + { + BoolOptions.Set((int)opt, arg != 0); + } + public bool GetOption(CUFBoolOptions opt) + { + return BoolOptions.Get((int)opt); + } + public ulong GetUlongOptionValue() + { + int[] array = new int[1]; + BoolOptions.CopyTo(array, 0); + return (ulong)array[0]; + } + + public string ProfileName; + public ushort FrameHeight; + public ushort FrameWidth; + public byte SortBy; + public byte HealthText; + + // LeftAlign, TopAlight, BottomAlign + public byte TopPoint; + public byte BottomPoint; + public byte LeftPoint; + + // LeftOffset, TopOffset and BottomOffset + public ushort TopOffset; + public ushort BottomOffset; + public ushort LeftOffset; + + public BitArray BoolOptions; + + // More fields can be added to BoolOptions without changing DB schema (up to 32, currently 27) + } + + struct GroupUpdateCounter + { + public ObjectGuid GroupGuid; + public int UpdateSequenceNumber; + } +} diff --git a/Game/Entities/Player/Player.Groups.cs b/Game/Entities/Player/Player.Groups.cs new file mode 100644 index 000000000..a06703482 --- /dev/null +++ b/Game/Entities/Player/Player.Groups.cs @@ -0,0 +1,290 @@ +/* + * Copyright (C) 2012-2017 CypherCore + * + * 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, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +using Framework.Constants; +using Game.Groups; +using Game.Maps; +using System.Collections.Generic; +using System.Diagnostics.Contracts; + +namespace Game.Entities +{ + public partial class Player + { + Player GetNextRandomRaidMember(float radius) + { + Group group = GetGroup(); + if (!group) + return null; + + List nearMembers = new List(); + + for (GroupReference refe = group.GetFirstMember(); refe != null; refe = refe.next()) + { + Player Target = refe.GetSource(); + + // IsHostileTo check duel and controlled by enemy + if (Target && Target != this && IsWithinDistInMap(Target, radius) && + !Target.HasInvisibilityAura() && !IsHostileTo(Target)) + nearMembers.Add(Target); + } + + if (nearMembers.Empty()) + return null; + + int randTarget = RandomHelper.IRand(0, nearMembers.Count - 1); + return nearMembers[randTarget]; + } + + public PartyResult CanUninviteFromGroup(ObjectGuid guidMember = default(ObjectGuid)) + { + Group grp = GetGroup(); + if (!grp) + return PartyResult.NotInGroup; + + if (grp.isLFGGroup()) + { + ObjectGuid gguid = grp.GetGUID(); + if (Global.LFGMgr.GetKicksLeft(gguid) == 0) + return PartyResult.PartyLfgBootLimit; + + LfgState state = Global.LFGMgr.GetState(gguid); + if (Global.LFGMgr.IsVoteKickActive(gguid)) + return PartyResult.PartyLfgBootInProgress; + + if (grp.GetMembersCount() <= SharedConst.LFGKickVotesNeeded) + return PartyResult.PartyLfgBootTooFewPlayers; + + if (state == LfgState.FinishedDungeon) + return PartyResult.PartyLfgBootDungeonComplete; + + if (grp.isRollLootActive()) + return PartyResult.PartyLfgBootLootRolls; + + // @todo Should also be sent when anyone has recently left combat, with an aprox ~5 seconds timer. + for (GroupReference refe = grp.GetFirstMember(); refe != null; refe = refe.next()) + if (refe.GetSource() && refe.GetSource().IsInCombat()) + return PartyResult.PartyLfgBootInCombat; + + /* Missing support for these types + return ERR_PARTY_LFG_BOOT_COOLDOWN_S; + return ERR_PARTY_LFG_BOOT_NOT_ELIGIBLE_S; + */ + } + else + { + if (!grp.IsLeader(GetGUID()) && !grp.IsAssistant(GetGUID())) + return PartyResult.NotLeader; + + if (InBattleground()) + return PartyResult.InviteRestricted; + + if (grp.IsLeader(guidMember)) + return PartyResult.NotLeader; + } + + return PartyResult.Ok; + } + + public bool isUsingLfg() + { + return Global.LFGMgr.GetState(GetGUID()) != LfgState.None; + } + + bool inRandomLfgDungeon() + { + if (Global.LFGMgr.selectedRandomLfgDungeon(GetGUID())) + { + Map map = GetMap(); + return Global.LFGMgr.inLfgDungeonMap(GetGUID(), map.GetId(), map.GetDifficultyID()); + } + + return false; + } + + public void SetBattlegroundOrBattlefieldRaid(Group group, byte subgroup) + { + //we must move references from m_group to m_originalGroup + SetOriginalGroup(GetGroup(), GetSubGroup()); + + m_group.unlink(); + m_group.link(group, this); + m_group.setSubGroup(subgroup); + } + + public void RemoveFromBattlegroundOrBattlefieldRaid() + { + //remove existing reference + m_group.unlink(); + Group group = GetOriginalGroup(); + if (group) + { + m_group.link(group, this); + m_group.setSubGroup(GetOriginalSubGroup()); + } + SetOriginalGroup(null); + } + + public void SetOriginalGroup(Group group, byte subgroup = 0) + { + if (!group) + m_originalGroup.unlink(); + else + { + m_originalGroup.link(group, this); + m_originalGroup.setSubGroup(subgroup); + } + } + + public void SetGroup(Group group, byte subgroup = 0) + { + if (!group) + m_group.unlink(); + else + { + m_group.link(group, this); + m_group.setSubGroup(subgroup); + } + + UpdateObjectVisibility(false); + } + + public void SetPartyType(GroupCategory category, GroupType type) + { + Contract.Assert(category < GroupCategory.Max); + byte value = GetByteValue(PlayerFields.Bytes3, PlayerFieldOffsets.Bytes3OffsetPartyType); + value &= (byte)~((byte)0xFF << ((byte)category * 4)); + value |= (byte)((byte)type << ((byte)category * 4)); + SetByteValue(PlayerFields.Bytes3, PlayerFieldOffsets.Bytes3OffsetPartyType, value); + } + + public void ResetGroupUpdateSequenceIfNeeded(Group group) + { + GroupCategory category = group.GetGroupCategory(); + // Rejoining the last group should not reset the sequence + if (m_groupUpdateSequences[(int)category].GroupGuid != group.GetGUID()) + { + var groupUpdate = m_groupUpdateSequences[(int)category]; + groupUpdate.GroupGuid = group.GetGUID(); + groupUpdate.UpdateSequenceNumber = 1; + } + } + + public int NextGroupUpdateSequenceNumber(GroupCategory category) + { + var groupUpdate = m_groupUpdateSequences[(int)category]; + return groupUpdate.UpdateSequenceNumber++; + } + + public bool IsAtGroupRewardDistance(WorldObject pRewardSource) + { + if (!pRewardSource) + return false; + WorldObject player = GetCorpse(); + if (!player || IsAlive()) + player = this; + + if (player.GetMapId() != pRewardSource.GetMapId() || player.GetInstanceId() != pRewardSource.GetInstanceId()) + return false; + + if (player.GetMap().IsDungeon()) + return true; + + return pRewardSource.GetDistance(player) <= WorldConfig.GetFloatValue(WorldCfg.GroupXpDistance); + } + + public Group GetGroupInvite() { return m_groupInvite; } + public void SetGroupInvite(Group group) { m_groupInvite = group; } + public Group GetGroup() { return m_group.getTarget(); } + public GroupReference GetGroupRef() { return m_group; } + public byte GetSubGroup() { return m_group.getSubGroup(); } + public GroupUpdateFlags GetGroupUpdateFlag() { return m_groupUpdateMask; } + public void SetGroupUpdateFlag(GroupUpdateFlags flag) { m_groupUpdateMask |= flag; } + public void RemoveGroupUpdateFlag(GroupUpdateFlags flag) { m_groupUpdateMask &= ~flag; } + + public Group GetOriginalGroup() { return m_originalGroup.getTarget(); } + public GroupReference GetOriginalGroupRef() { return m_originalGroup; } + public byte GetOriginalSubGroup() { return m_originalGroup.getSubGroup(); } + + public void SetPassOnGroupLoot(bool bPassOnGroupLoot) { m_bPassOnGroupLoot = bPassOnGroupLoot; } + public bool GetPassOnGroupLoot() { return m_bPassOnGroupLoot; } + + public bool IsGroupVisibleFor(Player p) + { + switch (WorldConfig.GetIntValue(WorldCfg.GroupVisibility)) + { + default: + return IsInSameGroupWith(p); + case 1: + return IsInSameRaidWith(p); + case 2: + return GetTeam() == p.GetTeam(); + } + } + public bool IsInSameGroupWith(Player p) + { + return p == this || (GetGroup() && + GetGroup() == p.GetGroup() && GetGroup().SameSubGroup(this, p)); + } + + public bool IsInSameRaidWith(Player p) + { + return p == this || (GetGroup() != null && GetGroup() == p.GetGroup()); + } + + public void UninviteFromGroup() + { + Group group = GetGroupInvite(); + if (!group) + return; + + group.RemoveInvite(this); + + if (group.GetMembersCount() <= 1) // group has just 1 member => disband + { + if (group.IsCreated()) + group.Disband(true); + else + group.RemoveAllInvites(); + } + } + + public void RemoveFromGroup(RemoveMethod method = RemoveMethod.Default) { RemoveFromGroup(GetGroup(), GetGUID(), method); } + public static void RemoveFromGroup(Group group, ObjectGuid guid, RemoveMethod method = RemoveMethod.Default, ObjectGuid kicker = default(ObjectGuid), string reason = null) + { + if (!group) + return; + + group.RemoveMember(guid, method, kicker, reason); + } + + void SendUpdateToOutOfRangeGroupMembers() + { + if (m_groupUpdateMask == GroupUpdateFlags.None) + return; + Group group = GetGroup(); + if (group) + group.UpdatePlayerOutOfRange(this); + + m_groupUpdateMask = GroupUpdateFlags.None; + + Pet pet = GetPet(); + if (pet) + pet.ResetGroupUpdateFlag(); + } + } +} diff --git a/Game/Entities/Player/Player.Items.cs b/Game/Entities/Player/Player.Items.cs new file mode 100644 index 000000000..714900b57 --- /dev/null +++ b/Game/Entities/Player/Player.Items.cs @@ -0,0 +1,6601 @@ +/* + * Copyright (C) 2012-2017 CypherCore + * + * 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, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +using Framework.Constants; +using Framework.Database; +using Game.Arenas; +using Game.BattleGrounds; +using Game.DataStorage; +using Game.Groups; +using Game.Guilds; +using Game.Loots; +using Game.Mails; +using Game.Maps; +using Game.Network.Packets; +using Game.Spells; +using System; +using System.Collections.Generic; +using System.Diagnostics.Contracts; +using System.Linq; +using System.Text; + +namespace Game.Entities +{ + public partial class Player + { + //Refund + void AddRefundReference(ObjectGuid it) + { + m_refundableItems.Add(it); + } + public void DeleteRefundReference(ObjectGuid it) + { + m_refundableItems.Remove(it); + } + public void RefundItem(Item item) + { + if (!item.HasFlag(ItemFields.Flags, ItemFieldFlags.Refundable)) + { + Log.outDebug(LogFilter.Player, "Item refund: item not refundable!"); + return; + } + + if (item.IsRefundExpired()) // item refund has expired + { + item.SetNotRefundable(this); + SendItemRefundResult(item, null, 10); + return; + } + + if (GetGUID() != item.GetRefundRecipient()) // Formerly refundable item got traded + { + Log.outDebug(LogFilter.Player, "Item refund: item was traded!"); + item.SetNotRefundable(this); + return; + } + + ItemExtendedCostRecord iece = CliDB.ItemExtendedCostStorage.LookupByKey(item.GetPaidExtendedCost()); + if (iece == null) + { + Log.outDebug(LogFilter.Player, "Item refund: cannot find extendedcost data."); + return; + } + + bool store_error = false; + for (byte i = 0; i < ItemConst.MaxItemExtCostItems; ++i) + { + uint count = iece.RequiredItemCount[i]; + uint itemid = iece.RequiredItem[i]; + + if (count != 0 && itemid != 0) + { + List dest = new List(); + InventoryResult msg = CanStoreNewItem(ItemConst.NullBag, ItemConst.NullSlot, dest, itemid, count); + if (msg != InventoryResult.Ok) + { + store_error = true; + break; + } + } + } + + if (store_error) + { + SendItemRefundResult(item, iece, 10); + return; + } + + SendItemRefundResult(item, iece, 0); + + uint moneyRefund = item.GetPaidMoney(); // item. will be invalidated in DestroyItem + + // Save all relevant data to DB to prevent desynchronisation exploits + SQLTransaction trans = new SQLTransaction(); + + // Delete any references to the refund data + item.SetNotRefundable(this, true, trans, false); + GetSession().GetCollectionMgr().RemoveTemporaryAppearance(item); + + // Destroy item + DestroyItem(item.GetBagSlot(), item.GetSlot(), true); + + // Grant back extendedcost items + for (byte i = 0; i < ItemConst.MaxItemExtCostItems; ++i) + { + uint count = iece.RequiredItemCount[i]; + uint itemid = iece.RequiredItem[i]; + if (count != 0 && itemid != 0) + { + List dest = new List(); + InventoryResult msg = CanStoreNewItem(ItemConst.NullBag, ItemConst.NullSlot, dest, itemid, count); + Contract.Assert(msg == InventoryResult.Ok); // Already checked before + Item it = StoreNewItem(dest, itemid, true); + SendNewItem(it, count, true, false, true); + } + } + + // Grant back currencies + for (byte i = 0; i < ItemConst.MaxItemExtCostCurrencies; ++i) + { + if (iece.RequirementFlags.HasAnyFlag((byte)((int)ItemExtendedCostFlags.RequireSeasonEarned1 << i))) + continue; + + uint count = iece.RequiredCurrencyCount[i]; + uint currencyid = iece.RequiredCurrency[i]; + if (count != 0 && currencyid != 0) + ModifyCurrency((CurrencyTypes)currencyid, (int)count, true, true); + } + + // Grant back money + if (moneyRefund != 0) + ModifyMoney(moneyRefund); // Saved in SaveInventoryAndGoldToDB + + SaveInventoryAndGoldToDB(trans); + + DB.Characters.CommitTransaction(trans); + } + public void SendRefundInfo(Item item) + { + // This function call unsets ITEM_FLAGS_REFUNDABLE if played time is over 2 hours. + item.UpdatePlayedTime(this); + + if (!item.HasFlag(ItemFields.Flags, ItemFieldFlags.Refundable)) + { + Log.outDebug(LogFilter.Player, "Item refund: item not refundable!"); + return; + } + + if (GetGUID() != item.GetRefundRecipient()) // Formerly refundable item got traded + { + Log.outDebug(LogFilter.Player, "Item refund: item was traded!"); + item.SetNotRefundable(this); + return; + } + + ItemExtendedCostRecord iece = CliDB.ItemExtendedCostStorage.LookupByKey(item.GetPaidExtendedCost()); + if (iece == null) + { + Log.outDebug(LogFilter.Player, "Item refund: cannot find extendedcost data."); + return; + } + SetItemPurchaseData setItemPurchaseData = new SetItemPurchaseData(); + setItemPurchaseData.ItemGUID = item.GetGUID(); + setItemPurchaseData.PurchaseTime = GetTotalPlayedTime() - item.GetPlayedTime(); + setItemPurchaseData.Contents.Money = item.GetPaidMoney(); + + for (byte i = 0; i < ItemConst.MaxItemExtCostItems; ++i) // item cost data + { + setItemPurchaseData.Contents.Items[i].ItemCount = iece.RequiredItemCount[i]; + setItemPurchaseData.Contents.Items[i].ItemID = iece.RequiredItem[i]; + } + + for (byte i = 0; i < ItemConst.MaxItemExtCostCurrencies; ++i) // currency cost data + { + if (iece.RequirementFlags.HasAnyFlag((byte)((int)ItemExtendedCostFlags.RequireSeasonEarned1 << i))) + continue; + + setItemPurchaseData.Contents.Currencies[i].CurrencyCount = iece.RequiredCurrencyCount[i]; + setItemPurchaseData.Contents.Currencies[i].CurrencyID = iece.RequiredCurrency[i]; + } + + SendPacket(setItemPurchaseData); + } + public void SendItemRefundResult(Item item, ItemExtendedCostRecord iece, byte error) + { + ItemPurchaseRefundResult itemPurchaseRefundResult = new ItemPurchaseRefundResult(); + itemPurchaseRefundResult.ItemGUID = item.GetGUID(); + itemPurchaseRefundResult.Result = error; + if (error == 0) + { + itemPurchaseRefundResult.Contents.HasValue = true; + itemPurchaseRefundResult.Contents.Value.Money = item.GetPaidMoney(); + for (byte i = 0; i < ItemConst.MaxItemExtCostItems; ++i) // item cost data + { + itemPurchaseRefundResult.Contents.Value.Items[i].ItemCount = iece.RequiredItemCount[i]; + itemPurchaseRefundResult.Contents.Value.Items[i].ItemID = iece.RequiredItem[i]; + } + + for (byte i = 0; i < ItemConst.MaxItemExtCostCurrencies; ++i) // currency cost data + { + if (iece.RequirementFlags.HasAnyFlag((byte)((uint)ItemExtendedCostFlags.RequireSeasonEarned1 << i))) + continue; + + itemPurchaseRefundResult.Contents.Value.Currencies[i].CurrencyCount = iece.RequiredCurrencyCount[i]; + itemPurchaseRefundResult.Contents.Value.Currencies[i].CurrencyID = iece.RequiredCurrency[i]; + } + } + + SendPacket(itemPurchaseRefundResult); + } + + //Trade + void AddTradeableItem(Item item) + { + m_itemSoulboundTradeable.Add(item.GetGUID()); + } + public void RemoveTradeableItem(Item item) + { + m_itemSoulboundTradeable.Remove(item.GetGUID()); + } + void UpdateSoulboundTradeItems() + { + // also checks for garbage data + foreach (var guid in m_itemSoulboundTradeable.ToList()) + { + Item item = GetItemByGuid(guid); + if (!item || item.GetOwnerGUID() != GetGUID() || item.CheckSoulboundTradeExpire()) + m_itemSoulboundTradeable.Remove(guid); + } + } + public void SetTradeData(TradeData data) { m_trade = data; } + public Player GetTrader() { return m_trade?.GetTrader(); } + public TradeData GetTradeData() { return m_trade; } + public void TradeCancel(bool sendback) + { + if (m_trade != null) + { + Player trader = m_trade.GetTrader(); + + // send yellow "Trade canceled" message to both traders + if (sendback) + GetSession().SendCancelTrade(); + + trader.GetSession().SendCancelTrade(); + + // cleanup + m_trade = null; + trader.m_trade = null; + } + } + + //Durability + public void DurabilityLossAll(double percent, bool inventory) + { + for (byte i = EquipmentSlot.Start; i < EquipmentSlot.End; i++) + { + Item pItem = GetItemByPos(InventorySlots.Bag0, i); + if (pItem != null) + DurabilityLoss(pItem, percent); + } + + if (inventory) + { + for (byte i = InventorySlots.ItemStart; i < InventorySlots.ItemEnd; i++) + { + Item pItem = GetItemByPos(InventorySlots.Bag0, i); + if (pItem != null) + DurabilityLoss(pItem, percent); + } + + for (byte i = InventorySlots.BagStart; i < InventorySlots.BagEnd; i++) + { + Bag pBag = GetBagByPos(i); + if (pBag != null) + { + for (byte j = 0; j < pBag.GetBagSize(); j++) + { + Item pItem = GetItemByPos(i, j); + if (pItem != null) + DurabilityLoss(pItem, percent); + } + } + } + } + } + public void DurabilityLoss(Item item, double percent) + { + if (item == null) + return; + + uint pMaxDurability = item.GetUInt32Value(ItemFields.MaxDurability); + + if (pMaxDurability == 0) + return; + + percent /= GetTotalAuraMultiplier(AuraType.ModDurabilityLoss); + + int pDurabilityLoss = (int)(pMaxDurability * percent); + + if (pDurabilityLoss < 1) + pDurabilityLoss = 1; + + DurabilityPointsLoss(item, pDurabilityLoss); + } + public void DurabilityPointsLossAll(int points, bool inventory) + { + for (byte i = EquipmentSlot.Start; i < EquipmentSlot.End; i++) + { + Item pItem = GetItemByPos(InventorySlots.Bag0, i); + if (pItem != null) + DurabilityPointsLoss(pItem, points); + } + + if (inventory) + { + for (byte i = InventorySlots.ItemStart; i < InventorySlots.ItemEnd; i++) + { + Item pItem = GetItemByPos(InventorySlots.Bag0, i); + if (pItem != null) + DurabilityPointsLoss(pItem, points); + } + + for (byte i = InventorySlots.BagStart; i < InventorySlots.BagEnd; i++) + { + Bag pBag = (Bag)GetItemByPos(InventorySlots.Bag0, i); + if (pBag != null) + for (byte j = 0; j < pBag.GetBagSize(); j++) + { + Item pItem = GetItemByPos(i, j); + if (pItem != null) + DurabilityPointsLoss(pItem, points); + } + } + } + } + public void DurabilityPointsLoss(Item item, int points) + { + if (HasAuraType(AuraType.PreventDurabilityLoss)) + return; + + int pMaxDurability = item.GetInt32Value(ItemFields.MaxDurability); + int pOldDurability = item.GetInt32Value(ItemFields.Durability); + int pNewDurability = pOldDurability - points; + + if (pNewDurability < 0) + pNewDurability = 0; + else if (pNewDurability > pMaxDurability) + pNewDurability = pMaxDurability; + + if (pOldDurability != pNewDurability) + { + // modify item stats _before_ Durability set to 0 to pass _ApplyItemMods internal check + if (pNewDurability == 0 && pOldDurability > 0 && item.IsEquipped()) + _ApplyItemMods(item, item.GetSlot(), false); + + item.SetInt32Value(ItemFields.Durability, pNewDurability); + + // modify item stats _after_ restore durability to pass _ApplyItemMods internal check + if (pNewDurability > 0 && pOldDurability == 0 && item.IsEquipped()) + _ApplyItemMods(item, item.GetSlot(), true); + + item.SetState(ItemUpdateState.Changed, this); + } + } + public void DurabilityPointLossForEquipSlot(byte slot) + { + if (HasAuraType(AuraType.PreventDurabilityLossFromCombat)) + return; + + Item pItem = GetItemByPos(InventorySlots.Bag0, slot); + if (pItem != null) + DurabilityPointsLoss(pItem, 1); + } + public uint DurabilityRepairAll(bool cost, float discountMod, bool guildBank) + { + uint TotalCost = 0; + // equipped, backpack, bags itself + for (byte i = EquipmentSlot.Start; i < InventorySlots.ItemEnd; i++) + TotalCost += DurabilityRepair((ushort)((InventorySlots.Bag0 << 8) | i), cost, discountMod, guildBank); + + // items in inventory bags + for (byte j = InventorySlots.BagStart; j < InventorySlots.BagEnd; j++) + for (byte i = 0; i < ItemConst.MaxBagSize; i++) + TotalCost += DurabilityRepair((ushort)((j << 8) | i), cost, discountMod, guildBank); + return TotalCost; + } + public uint DurabilityRepair(ushort pos, bool cost, float discountMod, bool guildBank) + { + Item item = GetItemByPos(pos); + + uint TotalCost = 0; + if (item == null) + return TotalCost; + + uint maxDurability = item.GetUInt32Value(ItemFields.MaxDurability); + if (maxDurability == 0) + return TotalCost; + + uint curDurability = item.GetUInt32Value(ItemFields.Durability); + + if (cost) + { + uint LostDurability = maxDurability - curDurability; + if (LostDurability > 0) + { + ItemTemplate ditemProto = item.GetTemplate(); + + DurabilityCostsRecord dcost = CliDB.DurabilityCostsStorage.LookupByKey(ditemProto.GetBaseItemLevel()); + if (dcost == null) + { + Log.outError(LogFilter.Player, "RepairDurability: Wrong item lvl {0}", ditemProto.GetBaseItemLevel()); + return TotalCost; + } + + uint dQualitymodEntryId = (uint)(ditemProto.GetQuality() + 1) * 2; + DurabilityQualityRecord dQualitymodEntry = CliDB.DurabilityQualityStorage.LookupByKey(dQualitymodEntryId); + if (dQualitymodEntry == null) + { + Log.outError(LogFilter.Player, "RepairDurability: Wrong dQualityModEntry {0}", dQualitymodEntryId); + return TotalCost; + } + + uint dmultiplier = 0; + if (ditemProto.GetClass() == ItemClass.Weapon) + dmultiplier = dcost.WeaponSubClassCost[ditemProto.GetSubClass()]; + else if (ditemProto.GetClass() == ItemClass.Armor) + dmultiplier = dcost.ArmorSubClassCost[ditemProto.GetSubClass()]; + + uint costs = (uint)(LostDurability * dmultiplier * (double)dQualitymodEntry.QualityMod * item.GetRepairCostMultiplier()); + costs = (uint)(costs * discountMod * WorldConfig.GetFloatValue(WorldCfg.RateRepaircost)); + + if (costs == 0) //fix for ITEM_QUALITY_ARTIFACT + costs = 1; + + if (guildBank) + { + if (GetGuildId() == 0) + { + Log.outDebug(LogFilter.Player, "You are not member of a guild"); + return TotalCost; + } + + var guild = Global.GuildMgr.GetGuildById(GetGuildId()); + if (guild == null) + return TotalCost; + + if (!guild.HandleMemberWithdrawMoney(GetSession(), costs, true)) + return TotalCost; + + TotalCost = costs; + } + else if (!HasEnoughMoney(costs)) + { + Log.outDebug(LogFilter.Player, "You do not have enough money"); + return TotalCost; + } + else + ModifyMoney(-costs); + } + } + + item.SetUInt32Value(ItemFields.Durability, maxDurability); + item.SetState(ItemUpdateState.Changed, this); + + // reapply mods for total broken and repaired item if equipped + if (IsEquipmentPos(pos) && curDurability == 0) + _ApplyItemMods(item, (byte)(pos & 255), true); + return TotalCost; + } + + + //Store Item + public InventoryResult CanStoreItem(byte bag, byte slot, List dest, Item pItem, bool swap = false) + { + if (pItem == null) + return InventoryResult.ItemNotFound; + + return CanStoreItem(bag, slot, dest, pItem.GetEntry(), pItem.GetCount(), pItem, swap); + } + InventoryResult CanStoreItem(byte bag, byte slot, List dest, uint entry, uint count, Item pItem, bool swap) + { + uint throwaway; + return CanStoreItem(bag, slot, dest, entry, count, pItem, swap, out throwaway); + } + InventoryResult CanStoreItem(byte bag, byte slot, List dest, uint entry, uint count, Item pItem, bool swap, out uint no_space_count) + { + no_space_count = 0; + Log.outDebug(LogFilter.Player, "STORAGE: CanStoreItem bag = {0}, slot = {1}, item = {2}, count = {3}", bag, slot, entry, count); + + ItemTemplate pProto = Global.ObjectMgr.GetItemTemplate(entry); + if (pProto == null) + { + no_space_count = count; + return swap ? InventoryResult.CantSwap : InventoryResult.ItemNotFound; + } + + if (pItem != null) + { + // item used + if (pItem.m_lootGenerated) + { + no_space_count = count; + return InventoryResult.LootGone; + } + + if (pItem.IsBindedNotWith(this)) + { + no_space_count = count; + return InventoryResult.NotOwner; + } + } + + // check count of items (skip for auto move for same player from bank) + uint no_similar_count = 0; // can't store this amount similar items + InventoryResult res = CanTakeMoreSimilarItems(entry, count, pItem, ref no_similar_count); + if (res != InventoryResult.Ok) + { + if (count == no_similar_count) + { + no_space_count = no_similar_count; + return res; + } + count -= no_similar_count; + } + + // in specific slot + if (bag != ItemConst.NullBag && slot != ItemConst.NullSlot) + { + res = CanStoreItem_InSpecificSlot(bag, slot, dest, pProto, ref count, swap, pItem); + if (res != InventoryResult.Ok) + { + no_space_count = count + no_similar_count; + return res; + } + + if (count == 0) + { + if (no_similar_count == 0) + return InventoryResult.Ok; + + no_space_count = count + no_similar_count; + return InventoryResult.ItemMaxCount; + } + } + + // not specific slot or have space for partly store only in specific slot + + // in specific bag + if (bag != ItemConst.NullBag) + { + // search stack in bag for merge to + if (pProto.GetMaxStackSize() != 1) + { + if (bag == InventorySlots.Bag0) // inventory + { + res = CanStoreItem_InInventorySlots(InventorySlots.ChildEquipmentStart, InventorySlots.ChildEquipmentEnd, dest, pProto, ref count, true, pItem, bag, slot); + if (res != InventoryResult.Ok) + { + no_space_count = count + no_similar_count; + return res; + } + + if (count == 0) + { + if (no_similar_count == 0) + return InventoryResult.Ok; + + no_space_count = count + no_similar_count; + return InventoryResult.ItemMaxCount; + } + + res = CanStoreItem_InInventorySlots(InventorySlots.ReagentStart, InventorySlots.ReagentEnd, dest, pProto, ref count, true, pItem, bag, slot); + if (res != InventoryResult.Ok) + { + no_space_count = count + no_similar_count; + return res; + } + + if (count == 0) + { + if (no_similar_count == 0) + return InventoryResult.Ok; + + no_space_count = count + no_similar_count; + return InventoryResult.ItemMaxCount; + } + + res = CanStoreItem_InInventorySlots(InventorySlots.ItemStart, InventorySlots.ItemEnd, dest, pProto, ref count, true, pItem, bag, slot); + if (res != InventoryResult.Ok) + { + no_space_count = count + no_similar_count; + return res; + } + + if (count == 0) + { + if (no_similar_count == 0) + return InventoryResult.Ok; + + + no_space_count = count + no_similar_count; + return InventoryResult.ItemMaxCount; + } + } + else // equipped bag + { + // we need check 2 time (specialized/non_specialized), use NULL_BAG to prevent skipping bag + res = CanStoreItem_InBag(bag, dest, pProto, ref count, true, false, pItem, ItemConst.NullBag, slot); + if (res != InventoryResult.Ok) + res = CanStoreItem_InBag(bag, dest, pProto, ref count, true, true, pItem, ItemConst.NullBag, slot); + + if (res != InventoryResult.Ok) + { + no_space_count = count + no_similar_count; + return res; + } + + if (count == 0) + { + if (no_similar_count == 0) + return InventoryResult.Ok; + + no_space_count = count + no_similar_count; + return InventoryResult.ItemMaxCount; + } + } + } + + // search free slot in bag for place to + if (bag == InventorySlots.Bag0) // inventory + { + if (pItem && pItem.HasFlag(ItemFields.Flags, ItemFieldFlags.Child)) + { + res = CanStoreItem_InInventorySlots(InventorySlots.ChildEquipmentStart, InventorySlots.ChildEquipmentEnd, dest, pProto, ref count, false, pItem, bag, slot); + if (res != InventoryResult.Ok) + { + no_space_count = count + no_similar_count; + return res; + } + + if (count == 0) + { + if (no_similar_count == 0) + return InventoryResult.Ok; + + no_space_count = count + no_similar_count; + return InventoryResult.ItemMaxCount; + } + } + else if (pProto.IsCraftingReagent() && HasFlag(PlayerFields.FlagsEx, PlayerFlagsEx.ReagentBankUnlocked)) + { + res = CanStoreItem_InInventorySlots(InventorySlots.ReagentStart, InventorySlots.ReagentEnd, dest, pProto, ref count, false, pItem, bag, slot); + if (res != InventoryResult.Ok) + { + no_space_count = count + no_similar_count; + return res; + } + + if (count == 0) + { + if (no_similar_count == 0) + return InventoryResult.Ok; + + no_space_count = count + no_similar_count; + return InventoryResult.ItemMaxCount; + } + } + + res = CanStoreItem_InInventorySlots(InventorySlots.ItemStart, InventorySlots.ItemEnd, dest, pProto, ref count, false, pItem, bag, slot); + if (res != InventoryResult.Ok) + { + no_space_count = count + no_similar_count; + return res; + } + + if (count == 0) + { + if (no_similar_count == 0) + return InventoryResult.Ok; + + no_space_count = count + no_similar_count; + return InventoryResult.ItemMaxCount; + } + } + else // equipped bag + { + res = CanStoreItem_InBag(bag, dest, pProto, ref count, false, false, pItem, ItemConst.NullBag, slot); + if (res != InventoryResult.Ok) + res = CanStoreItem_InBag(bag, dest, pProto, ref count, false, true, pItem, ItemConst.NullBag, slot); + + if (res != InventoryResult.Ok) + { + no_space_count = count + no_similar_count; + return res; + } + + if (count == 0) + { + if (no_similar_count == 0) + return InventoryResult.Ok; + + no_space_count = count + no_similar_count; + return InventoryResult.ItemMaxCount; + } + } + } + + // not specific bag or have space for partly store only in specific bag + + // search stack for merge to + if (pProto.GetMaxStackSize() != 1) + { + res = CanStoreItem_InInventorySlots(InventorySlots.ChildEquipmentStart, InventorySlots.ChildEquipmentEnd, dest, pProto, ref count, true, pItem, bag, slot); + if (res != InventoryResult.Ok) + { + no_space_count = count + no_similar_count; + return res; + } + + if (count == 0) + { + if (no_similar_count == 0) + return InventoryResult.Ok; + + no_space_count = count + no_similar_count; + return InventoryResult.ItemMaxCount; + } + + res = CanStoreItem_InInventorySlots(InventorySlots.ReagentStart, InventorySlots.ReagentEnd, dest, pProto, ref count, true, pItem, bag, slot); + if (res != InventoryResult.Ok) + { + no_space_count = count + no_similar_count; + return res; + } + + if (count == 0) + { + if (no_similar_count == 0) + return InventoryResult.Ok; + + no_space_count = count + no_similar_count; + return InventoryResult.ItemMaxCount; + } + + res = CanStoreItem_InInventorySlots(InventorySlots.ItemStart, InventorySlots.ItemEnd, dest, pProto, ref count, true, pItem, bag, slot); + if (res != InventoryResult.Ok) + { + no_space_count = count + no_similar_count; + return res; + } + + if (count == 0) + { + if (no_similar_count == 0) + return InventoryResult.Ok; + + no_space_count = count + no_similar_count; + return InventoryResult.ItemMaxCount; + } + + if (pProto.GetBagFamily() != 0) + { + for (byte i = InventorySlots.BagStart; i < InventorySlots.BagEnd; i++) + { + res = CanStoreItem_InBag(i, dest, pProto, ref count, true, false, pItem, bag, slot); + if (res != InventoryResult.Ok) + continue; + + if (count == 0) + { + if (no_similar_count == 0) + return InventoryResult.Ok; + + no_space_count = count + no_similar_count; + return InventoryResult.ItemMaxCount; + } + } + } + + for (byte i = InventorySlots.BagStart; i < InventorySlots.BagEnd; i++) + { + res = CanStoreItem_InBag(i, dest, pProto, ref count, true, true, pItem, bag, slot); + if (res != InventoryResult.Ok) + continue; + + if (count == 0) + { + if (no_similar_count == 0) + return InventoryResult.Ok; + + no_space_count = count + no_similar_count; + return InventoryResult.ItemMaxCount; + } + } + } + + // search free slot - special bag case + if (pProto.GetBagFamily() != 0) + { + for (byte i = InventorySlots.BagStart; i < InventorySlots.BagEnd; i++) + { + res = CanStoreItem_InBag(i, dest, pProto, ref count, false, false, pItem, bag, slot); + if (res != InventoryResult.Ok) + continue; + + if (count == 0) + { + if (no_similar_count == 0) + return InventoryResult.Ok; + + no_space_count = count + no_similar_count; + return InventoryResult.ItemMaxCount; + } + } + } + + if (pItem != null && pItem.IsNotEmptyBag()) + return InventoryResult.BagInBag; + + if (pItem && pItem.HasFlag(ItemFields.Flags, ItemFieldFlags.Child)) + { + res = CanStoreItem_InInventorySlots(InventorySlots.ChildEquipmentStart, InventorySlots.ChildEquipmentEnd, dest, pProto, ref count, false, pItem, bag, slot); + if (res != InventoryResult.Ok) + { + no_space_count = count + no_similar_count; + return res; + } + + if (count == 0) + { + if (no_similar_count == 0) + return InventoryResult.Ok; + + no_space_count = count + no_similar_count; + return InventoryResult.ItemMaxCount; + } + } + else if (pProto.IsCraftingReagent() && HasFlag(PlayerFields.FlagsEx, PlayerFlagsEx.ReagentBankUnlocked)) + { + res = CanStoreItem_InInventorySlots(InventorySlots.ReagentStart, InventorySlots.ReagentEnd, dest, pProto, ref count, false, pItem, bag, slot); + if (res != InventoryResult.Ok) + { + no_space_count = count + no_similar_count; + return res; + } + + if (count == 0) + { + if (no_similar_count == 0) + return InventoryResult.Ok; + + no_space_count = count + no_similar_count; + return InventoryResult.ItemMaxCount; + } + } + + // search free slot + res = CanStoreItem_InInventorySlots(InventorySlots.ItemStart, InventorySlots.ItemEnd, dest, pProto, ref count, false, pItem, bag, slot); + if (res != InventoryResult.Ok) + { + no_space_count = count + no_similar_count; + return res; + } + + if (count == 0) + { + if (no_similar_count == 0) + return InventoryResult.Ok; + + no_space_count = count + no_similar_count; + return InventoryResult.ItemMaxCount; + } + + for (var i = InventorySlots.BagStart; i < InventorySlots.BagEnd; i++) + { + res = CanStoreItem_InBag(i, dest, pProto, ref count, false, true, pItem, bag, slot); + if (res != InventoryResult.Ok) + continue; + + if (count == 0) + { + if (no_similar_count == 0) + return InventoryResult.Ok; + + no_space_count = count + no_similar_count; + return InventoryResult.ItemMaxCount; + } + } + + no_space_count = count + no_similar_count; + + return InventoryResult.InvFull; + } + public InventoryResult CanStoreItems(Item[] items, int count, ref uint offendingItemId) + { + Item item2; + + // fill space table + uint[] inventoryCounts = new uint[InventorySlots.ItemEnd - InventorySlots.ItemStart]; + uint[][] bagCounts = new uint[InventorySlots.BagEnd - InventorySlots.BagStart][]; + + for (byte i = InventorySlots.ItemStart; i < InventorySlots.ItemEnd; i++) + { + item2 = GetItemByPos(InventorySlots.Bag0, i); + if (item2 && !item2.IsInTrade()) + inventoryCounts[i - InventorySlots.ItemStart] = item2.GetCount(); + } + + for (byte i = InventorySlots.BagStart; i < InventorySlots.BagEnd; i++) + { + Bag pBag = GetBagByPos(i); + if (pBag) + { + bagCounts[i - InventorySlots.BagStart] = new uint[ItemConst.MaxBagSize]; + for (byte j = 0; j < pBag.GetBagSize(); j++) + { + item2 = GetItemByPos(i, j); + if (item2 && !item2.IsInTrade()) + bagCounts[i - InventorySlots.BagStart][j] = item2.GetCount(); + } + } + } + + // check free space for all items + for (int k = 0; k < count; ++k) + { + Item item = items[k]; + + // no item + if (!item) + continue; + + Log.outDebug(LogFilter.Player, "STORAGE: CanStoreItems {0}. item = {1}, count = {2}", k + 1, item.GetEntry(), item.GetCount()); + ItemTemplate pProto = item.GetTemplate(); + + // strange item + if (pProto == null) + return InventoryResult.ItemNotFound; + + // item used + if (item.m_lootGenerated) + return InventoryResult.LootGone; + + // item it 'bind' + if (item.IsBindedNotWith(this)) + return InventoryResult.NotOwner; + + ItemTemplate pBagProto; + + // item is 'one item only' + InventoryResult res = CanTakeMoreSimilarItems(item, ref offendingItemId); + if (res != InventoryResult.Ok) + return res; + + bool b_found = false; + // search stack for merge to + if (pProto.GetMaxStackSize() != 1) + { + for (byte t = InventorySlots.ItemStart; t < InventorySlots.ItemEnd; ++t) + { + item2 = GetItemByPos(InventorySlots.Bag0, t); + if (item2 && item2.CanBeMergedPartlyWith(pProto) == InventoryResult.Ok && inventoryCounts[t - InventorySlots.ItemStart] + item.GetCount() <= pProto.GetMaxStackSize()) + { + inventoryCounts[t - InventorySlots.ItemStart] += item.GetCount(); + b_found = true; + break; + } + } + if (b_found) + continue; + + for (byte t = InventorySlots.BagStart; !b_found && t < InventorySlots.BagEnd; ++t) + { + Bag bag = GetBagByPos(t); + if (bag) + { + if (Item.ItemCanGoIntoBag(item.GetTemplate(), bag.GetTemplate())) + { + for (byte j = 0; j < bag.GetBagSize(); j++) + { + item2 = GetItemByPos(t, j); + if (item2 && item2.CanBeMergedPartlyWith(pProto) == InventoryResult.Ok && bagCounts[t - InventorySlots.BagStart][j] + item.GetCount() <= pProto.GetMaxStackSize()) + { + bagCounts[t - InventorySlots.BagStart][j] += item.GetCount(); + b_found = true; + break; + } + } + } + } + } + if (b_found) + continue; + } + b_found = false; + // special bag case + if (pProto.GetBagFamily() != 0) + { + for (byte t = InventorySlots.BagStart; !b_found && t < InventorySlots.BagEnd; ++t) + { + Bag bag = GetBagByPos(t); + if (bag) + { + pBagProto = bag.GetTemplate(); + + // not plain container check + if (pBagProto != null && (pBagProto.GetClass() != ItemClass.Container || pBagProto.GetSubClass() != (uint)ItemSubClassContainer.Container) && + Item.ItemCanGoIntoBag(pProto, pBagProto)) + { + for (uint j = 0; j < bag.GetBagSize(); j++) + { + if (bagCounts[t - InventorySlots.BagStart][j] == 0) + { + bagCounts[t - InventorySlots.BagStart][j] = 1; + b_found = true; + break; + } + } + } + } + } + if (b_found) + continue; + } + + // search free slot + b_found = false; + for (int t = InventorySlots.ItemStart; t < InventorySlots.ItemEnd; ++t) + { + if (inventoryCounts[t - InventorySlots.ItemStart] == 0) + { + inventoryCounts[t - InventorySlots.ItemStart] = 1; + b_found = true; + break; + } + } + if (b_found) + continue; + + // search free slot in bags + for (byte t = InventorySlots.BagStart; !b_found && t < InventorySlots.BagEnd; ++t) + { + Bag bag = GetBagByPos(t); + if (bag) + { + pBagProto = bag.GetTemplate(); + + // special bag already checked + if (pBagProto != null && (pBagProto.GetClass() != ItemClass.Container || pBagProto.GetSubClass() != (uint)ItemSubClassContainer.Container)) + continue; + + for (uint j = 0; j < bag.GetBagSize(); j++) + { + if (bagCounts[t - InventorySlots.BagStart][j] == 0) + { + bagCounts[t - InventorySlots.BagStart][j] = 1; + b_found = true; + break; + } + } + } + } + + // no free slot found? + if (!b_found) + return InventoryResult.BagFull; + } + + return InventoryResult.Ok; + } + + public InventoryResult CanStoreNewItem(byte bag, byte slot, List dest, uint item, uint count, out uint no_space_count) + { + return CanStoreItem(bag, slot, dest, item, count, null, false, out no_space_count); + } + public InventoryResult CanStoreNewItem(byte bag, byte slot, List dest, uint item, uint count) + { + uint notused; + return CanStoreItem(bag, slot, dest, item, count, null, false, out notused); + } + + Item _StoreItem(ushort pos, Item pItem, uint count, bool clone, bool update) + { + if (pItem == null) + return null; + + byte bag = (byte)(pos >> 8); + byte slot = (byte)(pos & 255); + + Log.outDebug(LogFilter.Player, "STORAGE: StoreItem bag = {0}, slot = {1}, item = {2}, count = {3}, guid = {4}", bag, slot, pItem.GetEntry(), count, pItem.GetGUID().ToString()); + + Item pItem2 = GetItemByPos(bag, slot); + + if (pItem2 == null) + { + if (clone) + pItem = pItem.CloneItem(count, this); + else + pItem.SetCount(count); + + if (pItem == null) + return null; + + if (pItem.GetBonding() == ItemBondingType.OnAcquire || + pItem.GetBonding() == ItemBondingType.Quest || + (pItem.GetBonding() == ItemBondingType.OnEquip && IsBagPos(pos))) + pItem.SetBinding(true); + + Bag pBag = bag == InventorySlots.Bag0 ? null : GetBagByPos(bag); + if (pBag == null) + { + m_items[slot] = pItem; + SetGuidValue(PlayerFields.InvSlotHead + (slot * 4), pItem.GetGUID()); + pItem.SetGuidValue(ItemFields.Contained, GetGUID()); + pItem.SetGuidValue(ItemFields.Owner, GetGUID()); + + pItem.SetSlot(slot); + pItem.SetContainer(null); + } + else + pBag.StoreItem(slot, pItem, update); + + if (IsInWorld && update) + { + pItem.AddToWorld(); + pItem.SendUpdateToPlayer(this); + } + + pItem.SetState(ItemUpdateState.Changed, this); + if (pBag != null) + pBag.SetState(ItemUpdateState.Changed, this); + + AddEnchantmentDurations(pItem); + AddItemDurations(pItem); + + + ItemTemplate proto = pItem.GetTemplate(); + for (byte i = 0; i < proto.Effects.Count; ++i) + if (proto.Effects[i].Trigger == ItemSpelltriggerType.OnObtain && proto.Effects[i].SpellID > 0) // On obtain trigger + if (bag == InventorySlots.Bag0 || (bag >= InventorySlots.BagStart && bag < InventorySlots.BagEnd)) + if (!HasAura(proto.Effects[i].SpellID)) + CastSpell(this, proto.Effects[i].SpellID, true, pItem); + + return pItem; + } + else + { + if (pItem2.GetBonding() == ItemBondingType.OnAcquire || + pItem2.GetBonding() == ItemBondingType.Quest || + (pItem2.GetBonding() == ItemBondingType.OnEquip && IsBagPos(pos))) + pItem2.SetBinding(true); + + pItem2.SetCount(pItem2.GetCount() + count); + if (IsInWorld && update) + pItem2.SendUpdateToPlayer(this); + + if (!clone) + { + // delete item (it not in any slot currently) + if (IsInWorld && update) + { + pItem.RemoveFromWorld(); + pItem.DestroyForPlayer(this); + } + + RemoveEnchantmentDurations(pItem); + RemoveItemDurations(pItem); + + pItem.SetOwnerGUID(GetGUID()); // prevent error at next SetState in case trade/mail/buy from vendor + pItem.SetNotRefundable(this); + pItem.ClearSoulboundTradeable(this); + RemoveTradeableItem(pItem); + pItem.SetState(ItemUpdateState.Removed, this); + } + + AddEnchantmentDurations(pItem2); + + pItem2.SetState(ItemUpdateState.Changed, this); + + ItemTemplate proto = pItem2.GetTemplate(); + for (byte i = 0; i < proto.Effects.Count; ++i) + if (proto.Effects[i].Trigger == ItemSpelltriggerType.OnObtain && proto.Effects[i].SpellID > 0) // On obtain trigger + if (bag == InventorySlots.Bag0 || (bag >= InventorySlots.BagStart && bag < InventorySlots.BagEnd)) + if (!HasAura(proto.Effects[i].SpellID)) + CastSpell(this, proto.Effects[i].SpellID, true, pItem2); + + return pItem2; + } + } + public Item StoreItem(List dest, Item pItem, bool update) + { + if (pItem == null) + return null; + + Item lastItem = pItem; + for (var i = 0; i < dest.Count; i++) + { + var itemPosCount = dest[i]; + ushort pos = itemPosCount.pos; + uint count = itemPosCount.count; + + if (i == dest.Count() - 1) + { + lastItem = _StoreItem(pos, pItem, count, false, update); + break; + } + + lastItem = _StoreItem(pos, pItem, count, true, update); + } + + AutoUnequipChildItem(lastItem); + + return lastItem; + } + bool StoreNewItemInBestSlots(uint titem_id, uint titem_amount) + { + Log.outDebug(LogFilter.Player, "STORAGE: Creating initial item, itemId = {0}, count = {1}", titem_id, titem_amount); + InventoryResult msg; + // attempt equip by one + while (titem_amount > 0) + { + ushort eDest = 0; + msg = CanEquipNewItem(ItemConst.NullSlot, out eDest, titem_id, false); + if (msg != InventoryResult.Ok) + break; + + EquipNewItem(eDest, titem_id, true); + AutoUnequipOffhandIfNeed(); + titem_amount--; + } + + if (titem_amount == 0) + return true; // equipped + + // attempt store + List sDest = new List(); + // store in main bag to simplify second pass (special bags can be not equipped yet at this moment) + msg = CanStoreNewItem(InventorySlots.Bag0, ItemConst.NullSlot, sDest, titem_id, titem_amount); + if (msg == InventoryResult.Ok) + { + StoreNewItem(sDest, titem_id, true, ItemEnchantment.GenerateItemRandomPropertyId(titem_id)); + return true; // stored + } + + // item can't be added + Log.outError(LogFilter.Player, "STORAGE: Can't equip or store initial item {0} for race {1} class {2}, error msg = {3}", titem_id, GetRace(), GetClass(), msg); + return false; + } + public Item StoreNewItem(List pos, uint itemId, bool update, ItemRandomEnchantmentId randomPropertyId = default(ItemRandomEnchantmentId), List allowedLooters = null, byte context = 0, List bonusListIDs = null, bool addToCollection = true) + { + uint count = 0; + foreach (var itemPosCount in pos) + count += itemPosCount.count; + + Item item = Item.CreateItem(itemId, count, this); + if (item != null) + { + ItemAddedQuestCheck(itemId, count); + UpdateCriteria(CriteriaTypes.ReceiveEpicItem, itemId, count); + UpdateCriteria(CriteriaTypes.OwnItem, itemId, 1); + + item.SetItemRandomProperties(randomPropertyId); + + uint upgradeID = Global.DB2Mgr.GetRulesetItemUpgrade(itemId); + if (upgradeID != 0) + item.SetModifier(ItemModifier.UpgradeId, upgradeID); + + item.SetUInt32Value(ItemFields.Context, context); + if (bonusListIDs != null) + { + foreach (uint bonusListID in bonusListIDs) + item.AddBonuses(bonusListID); + } + + item = StoreItem(pos, item, update); + + if (allowedLooters != null && allowedLooters.Count > 1 && item.GetTemplate().GetMaxStackSize() == 1 && item.IsSoulBound()) + { + item.SetSoulboundTradeable(allowedLooters); + item.SetUInt32Value(ItemFields.CreatePlayedTime, GetTotalPlayedTime()); + AddTradeableItem(item); + + // save data + StringBuilder ss = new StringBuilder(); + foreach (var guid in allowedLooters) + ss.AppendFormat("{0} ", guid); + + PreparedStatement stmt = DB.Characters.GetPreparedStatement(CharStatements.INS_ITEM_BOP_TRADE); + stmt.AddValue(0, item.GetGUID().GetCounter()); + stmt.AddValue(1, ss.ToString()); + DB.Characters.Execute(stmt); + } + + if (addToCollection) + GetSession().GetCollectionMgr().OnItemAdded(item); + + ItemChildEquipmentRecord childItemEntry = Global.DB2Mgr.GetItemChildEquipment(itemId); + if (childItemEntry != null) + { + ItemTemplate childTemplate = Global.ObjectMgr.GetItemTemplate(childItemEntry.AltItemID); + if (childTemplate != null) + { + List childDest = new List(); + CanStoreItem_InInventorySlots(InventorySlots.ChildEquipmentStart, InventorySlots.ChildEquipmentEnd, childDest, childTemplate, ref count, false, null, ItemConst.NullBag, ItemConst.NullSlot); + Item childItem = StoreNewItem(childDest, childTemplate.GetId(), update, ItemRandomEnchantmentId.Empty, null, context, null, addToCollection); + if (childItem) + { + childItem.SetGuidValue(ItemFields.Creator, item.GetGUID()); + childItem.SetFlag(ItemFields.Flags, ItemFieldFlags.Child); + item.SetChildItem(childItem.GetGUID()); + } + } + } + } + return item; + } + + //Move Item + InventoryResult CanTakeMoreSimilarItems(Item pItem) + { + uint notused = 0; + return CanTakeMoreSimilarItems(pItem.GetEntry(), pItem.GetCount(), pItem, ref notused); + } + InventoryResult CanTakeMoreSimilarItems(Item pItem, ref uint offendingItemId) + { + uint notused = 0; + return CanTakeMoreSimilarItems(pItem.GetEntry(), pItem.GetCount(), pItem, ref notused, ref offendingItemId); + } + InventoryResult CanTakeMoreSimilarItems(uint entry, uint count, Item pItem, ref uint no_space_count) + { + uint notused = 0; + return CanTakeMoreSimilarItems(entry, count, pItem, ref no_space_count, ref notused); + } + InventoryResult CanTakeMoreSimilarItems(uint entry, uint count, Item pItem, ref uint no_space_count, ref uint offendingItemId) + { + ItemTemplate pProto = Global.ObjectMgr.GetItemTemplate(entry); + if (pProto == null) + { + no_space_count = count; + return InventoryResult.ItemMaxCount; + } + + if (pItem != null && pItem.m_lootGenerated) + return InventoryResult.LootGone; + + // no maximum + if ((pProto.GetMaxCount() <= 0 && pProto.GetItemLimitCategory() == 0) || pProto.GetMaxCount() == 2147483647) + return InventoryResult.Ok; + + if (pProto.GetMaxCount() > 0) + { + uint curcount = GetItemCount(pProto.GetId(), true, pItem); + if (curcount + count > pProto.GetMaxCount()) + { + no_space_count = count + curcount - pProto.GetMaxCount(); + return InventoryResult.ItemMaxCount; + } + } + + // check unique-equipped limit + if (pProto.GetItemLimitCategory() != 0) + { + + ItemLimitCategoryRecord limitEntry = CliDB.ItemLimitCategoryStorage.LookupByKey(pProto.GetItemLimitCategory()); + if (limitEntry == null) + { + no_space_count = count; + return InventoryResult.NotEquippable; + } + + if (limitEntry.Flags == 0) + { + uint curcount = GetItemCountWithLimitCategory(pProto.GetItemLimitCategory(), pItem); + if (curcount + count > limitEntry.Quantity) + { + no_space_count = count + curcount - limitEntry.Quantity; + offendingItemId = pProto.GetId(); + return InventoryResult.ItemMaxLimitCategoryCountExceededIs; + } + } + } + + return InventoryResult.Ok; + } + + //UseItem + public InventoryResult CanUseItem(Item pItem, bool not_loading = true) + { + if (pItem != null) + { + Log.outDebug(LogFilter.Player, "ItemStorage: CanUseItem item = {0}", pItem.GetEntry()); + + if (!IsAlive() && not_loading) + return InventoryResult.PlayerDead; + + ItemTemplate pProto = pItem.GetTemplate(); + if (pProto != null) + { + if (pItem.IsBindedNotWith(this)) + return InventoryResult.NotOwner; + + InventoryResult res = CanUseItem(pProto); + if (res != InventoryResult.Ok) + return res; + + if (pItem.GetSkill() != 0) + { + bool allowEquip = false; + SkillType itemSkill = pItem.GetSkill(); + // Armor that is binded to account can "morph" from plate to mail, etc. if skill is not learned yet. + if (pProto.GetQuality() == ItemQuality.Heirloom && pProto.GetClass() == ItemClass.Armor && !HasSkill(itemSkill)) + { + // TODO: when you right-click already equipped item it throws EQUIP_ERR_PROFICIENCY_NEEDED. + + // In fact it's a visual bug, everything works properly... I need sniffs of operations with + // binded to account items from off server. + + switch (GetClass()) + { + case Class.Hunter: + case Class.Shaman: + allowEquip = (itemSkill == SkillType.Mail); + break; + case Class.Paladin: + case Class.Warrior: + allowEquip = (itemSkill == SkillType.PlateMail); + break; + } + } + if (!allowEquip && GetSkillValue(itemSkill) == 0) + return InventoryResult.ProficiencyNeeded; + } + + return InventoryResult.Ok; + } + } + return InventoryResult.ItemNotFound; + } + public InventoryResult CanUseItem(ItemTemplate proto) + { + // Used by group, function GroupLoot, to know if a prototype can be used by a player + + if (proto == null) + return InventoryResult.ItemNotFound; + + if (proto.GetFlags2().HasAnyFlag(ItemFlags2.InternalItem)) + return InventoryResult.CantEquipEver; + + if (Convert.ToBoolean(proto.GetFlags2() & ItemFlags2.FactionHorde) && GetTeam() != Team.Horde) + return InventoryResult.CantEquipEver; + + if (Convert.ToBoolean(proto.GetFlags2() & ItemFlags2.FactionAlliance) && GetTeam() != Team.Alliance) + return InventoryResult.CantEquipEver; + + if ((proto.GetAllowableClass() & getClassMask()) == 0 || (proto.GetAllowableRace() & getRaceMask()) == 0) + return InventoryResult.CantEquipEver; + + if (proto.GetRequiredSkill() != 0) + { + if (GetSkillValue((SkillType)proto.GetRequiredSkill()) == 0) + return InventoryResult.ProficiencyNeeded; + else if (GetSkillValue((SkillType)proto.GetRequiredSkill()) < proto.GetRequiredSkillRank()) + return InventoryResult.CantEquipSkill; + } + + if (proto.GetRequiredSpell() != 0 && !HasSpell(proto.GetRequiredSpell())) + return InventoryResult.ProficiencyNeeded; + + if (getLevel() < proto.GetBaseRequiredLevel()) + return InventoryResult.CantEquipLevelI; + + // If World Event is not active, prevent using event dependant items + if (proto.GetHolidayID() != 0 && !Global.GameEventMgr.IsHolidayActive(proto.GetHolidayID())) + return InventoryResult.ClientLockedOut; + + if (proto.GetRequiredReputationFaction() != 0 && (uint)GetReputationRank(proto.GetRequiredReputationFaction()) < proto.GetRequiredReputationRank()) + return InventoryResult.CantEquipReputation; + + // learning (recipes, mounts, pets, etc.) + if (proto.Effects.Count >= 2) + { + if (proto.Effects[0].SpellID == 483 || proto.Effects[0].SpellID == 55884) + if (HasSpell(proto.Effects[1].SpellID)) + return InventoryResult.InternalBagError; + } + + ArtifactRecord artifact = CliDB.ArtifactStorage.LookupByKey(proto.GetArtifactID()); + if (artifact != null) + if (artifact.SpecID != GetUInt32Value(PlayerFields.CurrentSpecId)) + return InventoryResult.CantUseItem; + + return InventoryResult.Ok; + } + + //Equip/Unequip Item + InventoryResult CanUnequipItems(uint item, uint count) + { + uint tempcount = 0; + + InventoryResult res = InventoryResult.Ok; + + for (byte i = EquipmentSlot.Start; i < InventorySlots.BagEnd; ++i) + { + Item pItem = GetItemByPos(InventorySlots.Bag0, i); + if (pItem != null) + { + if (pItem.GetEntry() == item) + { + InventoryResult ires = CanUnequipItem((ushort)(InventorySlots.Bag0 << 8 | i), false); + if (ires == InventoryResult.Ok) + { + tempcount += pItem.GetCount(); + if (tempcount >= count) + return InventoryResult.Ok; + } + else + res = ires; + } + } + } + + for (byte i = InventorySlots.ItemStart; i < InventorySlots.ItemEnd; ++i) + { + Item pItem = GetItemByPos(InventorySlots.Bag0, i); + if (pItem != null) + { + if (pItem.GetEntry() == item) + { + tempcount += pItem.GetCount(); + if (tempcount >= count) + return InventoryResult.Ok; + } + } + } + + for (byte i = InventorySlots.ReagentStart; i < InventorySlots.ReagentEnd; ++i) + { + Item pItem = GetItemByPos(InventorySlots.Bag0, i); + if (pItem) + { + if (pItem.GetEntry() == item) + { + tempcount += pItem.GetCount(); + if (tempcount >= count) + return InventoryResult.Ok; + } + } + } + + for (byte i = InventorySlots.ChildEquipmentStart; i < InventorySlots.ChildEquipmentEnd; ++i) + { + Item pItem = GetItemByPos(InventorySlots.Bag0, i); + if (pItem) + { + if (pItem.GetEntry() == item) + { + tempcount += pItem.GetCount(); + if (tempcount >= count) + return InventoryResult.Ok; + } + } + } + + for (byte i = InventorySlots.BagStart; i < InventorySlots.BagEnd; ++i) + { + Bag pBag = GetBagByPos(i); + if (pBag != null) + { + for (byte j = 0; j < pBag.GetBagSize(); ++j) + { + Item pItem = GetItemByPos(i, j); + if (pItem != null) + { + if (pItem.GetEntry() == item) + { + tempcount += pItem.GetCount(); + if (tempcount >= count) + return InventoryResult.Ok; + } + } + } + } + } + + // not found req. item count and have unequippable items + return res; + } + Item EquipNewItem(ushort pos, uint item, bool update) + { + Item pItem = Item.CreateItem(item, 1, this); + if (pItem != null) + { + ItemAddedQuestCheck(item, 1); + UpdateCriteria(CriteriaTypes.ReceiveEpicItem, item, 1); + return EquipItem(pos, pItem, update); + } + + return null; + } + public Item EquipItem(ushort pos, Item pItem, bool update) + { + AddEnchantmentDurations(pItem); + AddItemDurations(pItem); + + byte bag = (byte)(pos >> 8); + byte slot = (byte)(pos & 255); + + Item pItem2 = GetItemByPos(bag, slot); + + if (pItem2 == null) + { + VisualizeItem(slot, pItem); + + if (IsAlive()) + { + ItemTemplate pProto = pItem.GetTemplate(); + + // item set bonuses applied only at equip and removed at unequip, and still active for broken items + if (pProto != null && pProto.GetItemSet() != 0) + Item.AddItemsSetItem(this, pItem); + + _ApplyItemMods(pItem, slot, true); + + if (pProto != null && IsInCombat() && (pProto.GetClass() == ItemClass.Weapon || pProto.GetInventoryType() == InventoryType.Relic) && m_weaponChangeTimer == 0) + { + uint cooldownSpell = (uint)(GetClass() == Class.Rogue ? 6123 : 6119); + var spellProto = Global.SpellMgr.GetSpellInfo(cooldownSpell); + + if (spellProto == null) + Log.outError(LogFilter.Player, "Weapon switch cooldown spell {0} couldn't be found in Spell.dbc", cooldownSpell); + else + { + m_weaponChangeTimer = spellProto.StartRecoveryTime; + + GetSpellHistory().AddGlobalCooldown(spellProto, m_weaponChangeTimer); + + SpellCooldownPkt spellCooldown = new SpellCooldownPkt(); + spellCooldown.Caster = GetGUID(); + spellCooldown.Flags = SpellCooldownFlags.IncludeGCD; + spellCooldown.SpellCooldowns.Add(new SpellCooldownStruct(cooldownSpell, 0)); + SendPacket(spellCooldown); + } + } + } + + if (IsInWorld && update) + { + pItem.AddToWorld(); + pItem.SendUpdateToPlayer(this); + } + + ApplyEquipCooldown(pItem); + + // update expertise and armor penetration - passive auras may need it + + if (slot == EquipmentSlot.MainHand) + UpdateExpertise(WeaponAttackType.BaseAttack); + else if (slot == EquipmentSlot.OffHand) + UpdateExpertise(WeaponAttackType.OffAttack); + + switch (slot) + { + case EquipmentSlot.MainHand: + case EquipmentSlot.OffHand: + RecalculateRating(CombatRating.ArmorPenetration); + break; + } + } + else + { + pItem2.SetCount(pItem2.GetCount() + pItem.GetCount()); + if (IsInWorld && update) + pItem2.SendUpdateToPlayer(this); + + if (IsInWorld && update) + { + pItem.RemoveFromWorld(); + pItem.DestroyForPlayer(this); + } + + RemoveEnchantmentDurations(pItem); + RemoveItemDurations(pItem); + + pItem.SetOwnerGUID(GetGUID()); // prevent error at next SetState in case trade/mail/buy from vendor + pItem.SetNotRefundable(this); + pItem.ClearSoulboundTradeable(this); + RemoveTradeableItem(pItem); + pItem.SetState(ItemUpdateState.Removed, this); + pItem2.SetState(ItemUpdateState.Changed, this); + + ApplyEquipCooldown(pItem2); + + return pItem2; + } + + if (slot == EquipmentSlot.MainHand || slot == EquipmentSlot.OffHand) + CheckTitanGripPenalty(); + + // only for full equip instead adding to stack + UpdateCriteria(CriteriaTypes.EquipItem, pItem.GetEntry()); + UpdateCriteria(CriteriaTypes.EquipEpicItem, pItem.GetEntry(), slot); + + return pItem; + } + public void EquipChildItem(byte parentBag, byte parentSlot, Item parentItem) + { + ItemChildEquipmentRecord itemChildEquipment = Global.DB2Mgr.GetItemChildEquipment(parentItem.GetEntry()); + if (itemChildEquipment != null) + { + Item childItem = GetChildItemByGuid(parentItem.GetChildItem()); + if (childItem) + { + ushort childDest = (ushort)((InventorySlots.Bag0 << 8) | itemChildEquipment.AltEquipmentSlot); + if (childItem.GetPos() != childDest) + { + Item dstItem = GetItemByPos(childDest); + if (!dstItem) // empty slot, simple case + { + RemoveItem(childItem.GetBagSlot(), childItem.GetSlot(), true); + EquipItem(childDest, childItem, true); + AutoUnequipOffhandIfNeed(); + } + else // have currently equipped item, not simple case + { + byte dstbag = dstItem.GetBagSlot(); + byte dstslot = dstItem.GetSlot(); + + InventoryResult msg = CanUnequipItem(childDest, !childItem.IsBag()); + if (msg != InventoryResult.Ok) + { + SendEquipError(msg, dstItem); + return; + } + + // check dest.src move possibility but try to store currently equipped item in the bag where the parent item is + List sSrc = new List(); + ushort eSrc = 0; + if (IsInventoryPos(parentBag, parentSlot)) + { + msg = CanStoreItem(parentBag, ItemConst.NullSlot, sSrc, dstItem, true); + if (msg != InventoryResult.Ok) + msg = CanStoreItem(ItemConst.NullBag, ItemConst.NullSlot, sSrc, dstItem, true); + } + else if (IsBankPos(parentBag, parentSlot)) + { + msg = CanBankItem(parentBag, ItemConst.NullSlot, sSrc, dstItem, true); + if (msg != InventoryResult.Ok) + msg = CanBankItem(ItemConst.NullBag, ItemConst.NullSlot, sSrc, dstItem, true); + } + else if (IsEquipmentPos(parentBag, parentSlot)) + { + msg = CanEquipItem(parentSlot, out eSrc, dstItem, true); + if (msg == InventoryResult.Ok) + msg = CanUnequipItem(eSrc, true); + } + + if (msg != InventoryResult.Ok) + { + SendEquipError(msg, dstItem, childItem); + return; + } + + // now do moves, remove... + RemoveItem(dstbag, dstslot, false); + RemoveItem(childItem.GetBagSlot(), childItem.GetSlot(), false); + + // add to dest + EquipItem(childDest, childItem, true); + + // add to src + if (IsInventoryPos(parentBag, parentSlot)) + StoreItem(sSrc, dstItem, true); + else if (IsBankPos(parentBag, parentSlot)) + BankItem(sSrc, dstItem, true); + else if (IsEquipmentPos(parentBag, parentSlot)) + EquipItem(eSrc, dstItem, true); + + AutoUnequipOffhandIfNeed(); + } + } + } + } + } + public void AutoUnequipChildItem(Item parentItem) + { + if (Global.DB2Mgr.GetItemChildEquipment(parentItem.GetEntry()) != null) + { + Item childItem = GetChildItemByGuid(parentItem.GetChildItem()); + if (childItem) + { + if (IsChildEquipmentPos(childItem.GetPos())) + return; + + List dest = new List(); + uint count = childItem.GetCount(); + InventoryResult result = CanStoreItem_InInventorySlots(InventorySlots.ChildEquipmentStart, InventorySlots.ChildEquipmentEnd, dest, childItem.GetTemplate(), ref count, false, childItem, ItemConst.NullBag, ItemConst.NullSlot); + if (result != InventoryResult.Ok) + return; + + RemoveItem(childItem.GetBagSlot(), childItem.GetSlot(), true); + StoreItem(dest, childItem, true); + } + } + } + void QuickEquipItem(ushort pos, Item pItem) + { + if (pItem != null) + { + AddEnchantmentDurations(pItem); + AddItemDurations(pItem); + + byte slot = (byte)(pos & 255); + VisualizeItem(slot, pItem); + + if (IsInWorld) + { + pItem.AddToWorld(); + pItem.SendUpdateToPlayer(this); + } + + if (slot == EquipmentSlot.MainHand || slot == EquipmentSlot.OffHand) + CheckTitanGripPenalty(); + + UpdateCriteria(CriteriaTypes.EquipItem, pItem.GetEntry()); + UpdateCriteria(CriteriaTypes.EquipEpicItem, pItem.GetEntry(), slot); + } + } + public void SendEquipError(InventoryResult msg, Item item1 = null, Item item2 = null, uint itemId = 0) + { + InventoryChangeFailure failure = new InventoryChangeFailure(); + failure.BagResult = msg; + + if (msg != InventoryResult.Ok) + { + if (item1) + failure.Item[0] = item1.GetGUID(); + + if (item2) + failure.Item[1] = item2.GetGUID(); + + failure.ContainerBSlot = 0; // bag equip slot, used with EQUIP_ERR_EVENT_AUTOEQUIP_BIND_CONFIRM and EQUIP_ERR_ITEM_DOESNT_GO_INTO_BAG2 + + switch (msg) + { + case InventoryResult.CantEquipLevelI: + case InventoryResult.PurchaseLevelTooLow: + { + failure.Level = (item1 ? item1.GetRequiredLevel() : 0); + break; + } + case InventoryResult.EventAutoequipBindConfirm: // no idea about this one... + { + //failure.SrcContainer + //failure.SrcSlot + //failure.DstContainer + break; + } + case InventoryResult.ItemMaxLimitCategoryCountExceededIs: + case InventoryResult.ItemMaxLimitCategorySocketedExceededIs: + case InventoryResult.ItemMaxLimitCategoryEquippedExceededIs: + { + ItemTemplate proto = item1 ? item1.GetTemplate() : Global.ObjectMgr.GetItemTemplate(itemId); + failure.LimitCategory = (int)(proto != null ? proto.GetItemLimitCategory() : 0u); + break; + } + default: + break; + } + } + + SendPacket(failure); + } + + //Add/Remove/Misc Item + public bool AddItem(uint itemId, uint count) + { + uint noSpaceForCount = 0; + List dest = new List(); + InventoryResult msg = CanStoreNewItem(ItemConst.NullBag, ItemConst.NullSlot, dest, itemId, count, out noSpaceForCount); + if (msg != InventoryResult.Ok) + count -= noSpaceForCount; + + if (count == 0 || dest.Empty()) + { + // @todo Send to mailbox if no space + SendSysMessage("You don't have any space in your bags."); + return false; + } + + Item item = StoreNewItem(dest, itemId, true, ItemEnchantment.GenerateItemRandomPropertyId(itemId)); + if (item != null) + SendNewItem(item, count, true, false); + else + return false; + return true; + } + public void RemoveItem(byte bag, byte slot, bool update) + { + // note: removeitem does not actually change the item + // it only takes the item out of storage temporarily + // note2: if removeitem is to be used for delinking + // the item must be removed from the player's updatequeue + + Item pItem = GetItemByPos(bag, slot); + if (pItem != null) + { + Log.outDebug(LogFilter.Player, "STORAGE: RemoveItem bag = {0}, slot = {1}, item = {2}", bag, slot, pItem.GetEntry()); + + RemoveEnchantmentDurations(pItem); + RemoveItemDurations(pItem); + RemoveTradeableItem(pItem); + + if (bag == InventorySlots.Bag0) + { + if (slot < InventorySlots.BagEnd) + { + ItemTemplate pProto = pItem.GetTemplate(); + // item set bonuses applied only at equip and removed at unequip, and still active for broken items + + if (pProto != null && pProto.GetItemSet() != 0) + Item.RemoveItemsSetItem(this, pProto); + + _ApplyItemMods(pItem, slot, false); + + // remove item dependent auras and casts (only weapon and armor slots) + if (slot < EquipmentSlot.End) + { + // remove held enchantments, update expertise + if (slot == EquipmentSlot.MainHand) + { + if (pItem.GetItemSuffixFactor() != 0) + { + pItem.ClearEnchantment(EnchantmentSlot.Prop3); + pItem.ClearEnchantment(EnchantmentSlot.Prop4); + } + else + { + pItem.ClearEnchantment(EnchantmentSlot.Prop0); + pItem.ClearEnchantment(EnchantmentSlot.Prop1); + } + + UpdateExpertise(WeaponAttackType.BaseAttack); + } + else if (slot == EquipmentSlot.OffHand) + UpdateExpertise(WeaponAttackType.OffAttack); + // update armor penetration - passive auras may need it + switch (slot) + { + case EquipmentSlot.MainHand: + case EquipmentSlot.OffHand: + RecalculateRating(CombatRating.ArmorPenetration); + break; + } + } + } + + m_items[slot] = null; + SetGuidValue(PlayerFields.InvSlotHead + (slot * 4), ObjectGuid.Empty); + + if (slot < EquipmentSlot.End) + { + SetVisibleItemSlot(slot, null); + if (slot == EquipmentSlot.MainHand || slot == EquipmentSlot.OffHand) + CheckTitanGripPenalty(); + } + } + Bag pBag = GetBagByPos(bag); + if (pBag != null) + pBag.RemoveItem(slot, update); + + pItem.SetGuidValue(ItemFields.Contained, ObjectGuid.Empty); + pItem.SetSlot(ItemConst.NullSlot); + if (IsInWorld && update) + pItem.SendUpdateToPlayer(this); + + AutoUnequipChildItem(pItem); + } + } + public void SplitItem(ushort src, ushort dst, uint count) + { + byte srcbag = (byte)(src >> 8); + byte srcslot = (byte)(src & 255); + + byte dstbag = (byte)(dst >> 8); + byte dstslot = (byte)(dst & 255); + + Item pSrcItem = GetItemByPos(srcbag, srcslot); + if (!pSrcItem) + { + SendEquipError(InventoryResult.ItemNotFound, pSrcItem); + return; + } + + if (pSrcItem.m_lootGenerated) // prevent split looting item (item + { + //best error message found for attempting to split while looting + SendEquipError(InventoryResult.SplitFailed, pSrcItem); + return; + } + + // not let split all items (can be only at cheating) + if (pSrcItem.GetCount() == count) + { + SendEquipError(InventoryResult.SplitFailed, pSrcItem); + return; + } + + // not let split more existed items (can be only at cheating) + if (pSrcItem.GetCount() < count) + { + SendEquipError(InventoryResult.TooFewToSplit, pSrcItem); + return; + } + + //! If trading + TradeData tradeData = GetTradeData(); + if (tradeData != null) + { + //! If current item is in trade window (only possible with packet spoofing - silent return) + if (tradeData.GetTradeSlotForItem(pSrcItem.GetGUID()) != TradeSlots.Invalid) + return; + } + + Log.outDebug(LogFilter.Player, "STORAGE: SplitItem bag = {0}, slot = {1}, item = {2}, count = {3}", dstbag, dstslot, pSrcItem.GetEntry(), count); + Item pNewItem = pSrcItem.CloneItem(count, this); + if (!pNewItem) + { + SendEquipError(InventoryResult.ItemNotFound, pSrcItem); + return; + } + + if (IsInventoryPos(dst)) + { + // change item amount before check (for unique max count check) + pSrcItem.SetCount(pSrcItem.GetCount() - count); + + List dest = new List(); + InventoryResult msg = CanStoreItem(dstbag, dstslot, dest, pNewItem, false); + if (msg != InventoryResult.Ok) + { + pSrcItem.SetCount(pSrcItem.GetCount() + count); + SendEquipError(msg, pSrcItem); + return; + } + + if (IsInWorld) + pSrcItem.SendUpdateToPlayer(this); + pSrcItem.SetState(ItemUpdateState.Changed, this); + StoreItem(dest, pNewItem, true); + } + else if (IsBankPos(dst)) + { + // change item amount before check (for unique max count check) + pSrcItem.SetCount(pSrcItem.GetCount() - count); + + List dest = new List(); + InventoryResult msg = CanBankItem(dstbag, dstslot, dest, pNewItem, false); + if (msg != InventoryResult.Ok) + { + pSrcItem.SetCount(pSrcItem.GetCount() + count); + SendEquipError(msg, pSrcItem); + return; + } + + if (IsInWorld) + pSrcItem.SendUpdateToPlayer(this); + pSrcItem.SetState(ItemUpdateState.Changed, this); + BankItem(dest, pNewItem, true); + } + else if (IsEquipmentPos(dst)) + { + // change item amount before check (for unique max count check), provide space for splitted items + pSrcItem.SetCount(pSrcItem.GetCount() - count); + + ushort dest; + InventoryResult msg = CanEquipItem(dstslot, out dest, pNewItem, false); + if (msg != InventoryResult.Ok) + { + pSrcItem.SetCount(pSrcItem.GetCount() + count); + SendEquipError(msg, pSrcItem); + return; + } + + if (IsInWorld) + pSrcItem.SendUpdateToPlayer(this); + pSrcItem.SetState(ItemUpdateState.Changed, this); + EquipItem(dest, pNewItem, true); + AutoUnequipOffhandIfNeed(); + } + } + public void SwapItem(ushort src, ushort dst) + { + byte srcbag = (byte)(src >> 8); + byte srcslot = (byte)(src & 255); + + byte dstbag = (byte)(dst >> 8); + byte dstslot = (byte)(dst & 255); + + Item pSrcItem = GetItemByPos(srcbag, srcslot); + Item pDstItem = GetItemByPos(dstbag, dstslot); + + if (pSrcItem == null) + return; + + if (pSrcItem.HasFlag(ItemFields.Flags, ItemFieldFlags.Child)) + { + Item parentItem = GetItemByGuid(pSrcItem.GetGuidValue(ItemFields.Creator)); + if (parentItem) + { + if (IsEquipmentPos(src)) + { + AutoUnequipChildItem(parentItem); // we need to unequip child first since it cannot go into whatever is going to happen next + SwapItem(dst, src); // src is now empty + SwapItem(parentItem.GetPos(), dst);// dst is now empty + return; + } + } + } + else if (pDstItem && pDstItem.HasFlag(ItemFields.Flags, ItemFieldFlags.Child)) + { + Item parentItem = GetItemByGuid(pDstItem.GetGuidValue(ItemFields.Creator)); + if (parentItem) + { + if (IsEquipmentPos(dst)) + { + AutoUnequipChildItem(parentItem); // we need to unequip child first since it cannot go into whatever is going to happen next + SwapItem(src, dst); // dst is now empty + SwapItem(parentItem.GetPos(), src);// src is now empty + return; + } + } + } + + Log.outDebug(LogFilter.Player, "STORAGE: SwapItem bag = {0}, slot = {1}, item = {2}", dstbag, dstslot, pSrcItem.GetEntry()); + + if (!IsAlive()) + { + SendEquipError(InventoryResult.PlayerDead, pSrcItem, pDstItem); + return; + } + + // SRC checks + + // check unequip potability for equipped items and bank bags + if (IsEquipmentPos(src) || IsBagPos(src)) + { + // bags can be swapped with empty bag slots, or with empty bag (items move possibility checked later) + InventoryResult msg = CanUnequipItem(src, !IsBagPos(src) || IsBagPos(dst) || (pDstItem != null && pDstItem.ToBag() != null && pDstItem.ToBag().IsEmpty())); + if (msg != InventoryResult.Ok) + { + SendEquipError(msg, pSrcItem, pDstItem); + return; + } + } + + // prevent put equipped/bank bag in self + if (IsBagPos(src) && srcslot == dstbag) + { + SendEquipError(InventoryResult.BagInBag, pSrcItem, pDstItem); + return; + } + + // prevent equipping bag in the same slot from its inside + if (IsBagPos(dst) && srcbag == dstslot) + { + SendEquipError(InventoryResult.CantSwap, pSrcItem, pDstItem); + return; + } + + // DST checks + if (pDstItem != null) + { + // check unequip potability for equipped items and bank bags + if (IsEquipmentPos(dst) || IsBagPos(dst)) + { + // bags can be swapped with empty bag slots, or with empty bag (items move possibility checked later) + InventoryResult msg = CanUnequipItem(dst, !IsBagPos(dst) || IsBagPos(src) || (pSrcItem.ToBag() != null && pSrcItem.ToBag().IsEmpty())); + if (msg != InventoryResult.Ok) + { + SendEquipError(msg, pSrcItem, pDstItem); + return; + } + } + } + + // NOW this is or item move (swap with empty), or swap with another item (including bags in bag possitions) + // or swap empty bag with another empty or not empty bag (with items exchange) + + // Move case + if (pDstItem == null) + { + if (IsInventoryPos(dst)) + { + List dest = new List(); + InventoryResult msg = CanStoreItem(dstbag, dstslot, dest, pSrcItem, false); + if (msg != InventoryResult.Ok) + { + SendEquipError(msg, pSrcItem); + return; + } + + RemoveItem(srcbag, srcslot, true); + StoreItem(dest, pSrcItem, true); + if (IsBankPos(src)) + ItemAddedQuestCheck(pSrcItem.GetEntry(), pSrcItem.GetCount()); + } + else if (IsBankPos(dst)) + { + List dest = new List(); + InventoryResult msg = CanBankItem(dstbag, dstslot, dest, pSrcItem, false); + if (msg != InventoryResult.Ok) + { + SendEquipError(msg, pSrcItem); + return; + } + + RemoveItem(srcbag, srcslot, true); + BankItem(dest, pSrcItem, true); + ItemRemovedQuestCheck(pSrcItem.GetEntry(), pSrcItem.GetCount()); + } + else if (IsEquipmentPos(dst)) + { + ushort _dest; + InventoryResult msg = CanEquipItem(dstslot, out _dest, pSrcItem, false); + if (msg != InventoryResult.Ok) + { + SendEquipError(msg, pSrcItem); + return; + } + + RemoveItem(srcbag, srcslot, true); + EquipItem(_dest, pSrcItem, true); + AutoUnequipOffhandIfNeed(); + } + + return; + } + + // attempt merge to / fill target item + if (!pSrcItem.IsBag() && !pDstItem.IsBag()) + { + InventoryResult msg; + List sDest = new List(); + ushort eDest = 0; + if (IsInventoryPos(dst)) + msg = CanStoreItem(dstbag, dstslot, sDest, pSrcItem, false); + else if (IsBankPos(dst)) + msg = CanBankItem(dstbag, dstslot, sDest, pSrcItem, false); + else if (IsEquipmentPos(dst)) + msg = CanEquipItem(dstslot, out eDest, pSrcItem, false); + else + return; + + if (msg == InventoryResult.Ok && IsEquipmentPos(dst) && !pSrcItem.GetChildItem().IsEmpty()) + msg = CanEquipChildItem(pSrcItem); + + // can be merge/fill + if (msg == InventoryResult.Ok) + { + if (pSrcItem.GetCount() + pDstItem.GetCount() <= pSrcItem.GetTemplate().GetMaxStackSize()) + { + RemoveItem(srcbag, srcslot, true); + + if (IsInventoryPos(dst)) + StoreItem(sDest, pSrcItem, true); + else if (IsBankPos(dst)) + BankItem(sDest, pSrcItem, true); + else if (IsEquipmentPos(dst)) + { + EquipItem(eDest, pSrcItem, true); + if (!pSrcItem.GetChildItem().IsEmpty()) + EquipChildItem(srcbag, srcslot, pSrcItem); + + AutoUnequipOffhandIfNeed(); + } + } + else + { + pSrcItem.SetCount(pSrcItem.GetCount() + pDstItem.GetCount() - pSrcItem.GetTemplate().GetMaxStackSize()); + pDstItem.SetCount(pSrcItem.GetTemplate().GetMaxStackSize()); + pSrcItem.SetState(ItemUpdateState.Changed, this); + pDstItem.SetState(ItemUpdateState.Changed, this); + if (IsInWorld) + { + pSrcItem.SendUpdateToPlayer(this); + pDstItem.SendUpdateToPlayer(this); + } + } + SendRefundInfo(pDstItem); + return; + } + } + + // impossible merge/fill, do real swap + InventoryResult _msg = InventoryResult.Ok; + + // check src.dest move possibility + List _sDest = new List(); + ushort _eDest = 0; + if (IsInventoryPos(dst)) + _msg = CanStoreItem(dstbag, dstslot, _sDest, pSrcItem, true); + else if (IsBankPos(dst)) + _msg = CanBankItem(dstbag, dstslot, _sDest, pSrcItem, true); + else if (IsEquipmentPos(dst)) + { + _msg = CanEquipItem(dstslot, out _eDest, pSrcItem, true); + if (_msg == InventoryResult.Ok) + _msg = CanUnequipItem(_eDest, true); + } + + if (_msg != InventoryResult.Ok) + { + SendEquipError(_msg, pSrcItem, pDstItem); + return; + } + + // check dest.src move possibility + List sDest2 = new List(); + ushort eDest2 = 0; + if (IsInventoryPos(src)) + _msg = CanStoreItem(srcbag, srcslot, sDest2, pDstItem, true); + else if (IsBankPos(src)) + _msg = CanBankItem(srcbag, srcslot, sDest2, pDstItem, true); + else if (IsEquipmentPos(src)) + { + _msg = CanEquipItem(srcslot, out eDest2, pDstItem, true); + if (_msg == InventoryResult.Ok) + _msg = CanUnequipItem(eDest2, true); + } + + if (_msg == InventoryResult.Ok && IsEquipmentPos(dst) && !pSrcItem.GetChildItem().IsEmpty()) + _msg = CanEquipChildItem(pSrcItem); + + if (_msg != InventoryResult.Ok) + { + SendEquipError(_msg, pDstItem, pSrcItem); + return; + } + + // Check bag swap with item exchange (one from empty in not bag possition (equipped (not possible in fact) or store) + Bag srcBag = pSrcItem.ToBag(); + if (srcBag != null) + { + Bag dstBag = pDstItem.ToBag(); + if (dstBag != null) + { + Bag emptyBag = null; + Bag fullBag = null; + if (srcBag.IsEmpty() && !IsBagPos(src)) + { + emptyBag = srcBag; + fullBag = dstBag; + } + else if (dstBag.IsEmpty() && !IsBagPos(dst)) + { + emptyBag = dstBag; + fullBag = srcBag; + } + + // bag swap (with items exchange) case + if (emptyBag != null && fullBag != null) + { + ItemTemplate emptyProto = emptyBag.GetTemplate(); + byte count = 0; + + for (byte i = 0; i < fullBag.GetBagSize(); ++i) + { + Item bagItem = fullBag.GetItemByPos(i); + if (bagItem == null) + continue; + + ItemTemplate bagItemProto = bagItem.GetTemplate(); + if (bagItemProto == null || !Item.ItemCanGoIntoBag(bagItemProto, emptyProto)) + { + // one from items not go to empty target bag + SendEquipError(InventoryResult.BagInBag, pSrcItem, pDstItem); + return; + } + + ++count; + } + + if (count > emptyBag.GetBagSize()) + { + // too small targeted bag + SendEquipError(InventoryResult.CantSwap, pSrcItem, pDstItem); + return; + } + + // Items swap + count = 0; // will pos in new bag + for (byte i = 0; i < fullBag.GetBagSize(); ++i) + { + Item bagItem = fullBag.GetItemByPos(i); + if (bagItem == null) + continue; + + fullBag.RemoveItem(i, true); + emptyBag.StoreItem(count, bagItem, true); + bagItem.SetState(ItemUpdateState.Changed, this); + + ++count; + } + } + } + } + + // now do moves, remove... + RemoveItem(dstbag, dstslot, false); + RemoveItem(srcbag, srcslot, false); + + // add to dest + if (IsInventoryPos(dst)) + StoreItem(_sDest, pSrcItem, true); + else if (IsBankPos(dst)) + BankItem(_sDest, pSrcItem, true); + else if (IsEquipmentPos(dst)) + { + EquipItem(_eDest, pSrcItem, true); + if (!pSrcItem.GetChildItem().IsEmpty()) + EquipChildItem(srcbag, srcslot, pSrcItem); + } + + // add to src + if (IsInventoryPos(src)) + StoreItem(sDest2, pDstItem, true); + else if (IsBankPos(src)) + BankItem(sDest2, pDstItem, true); + else if (IsEquipmentPos(src)) + EquipItem(eDest2, pDstItem, true); + + // if player is moving bags and is looting an item inside this bag + // release the loot + if (!GetLootGUID().IsEmpty()) + { + bool released = false; + if (IsBagPos(src)) + { + Bag bag = pSrcItem.ToBag(); + for (byte i = 0; i < bag.GetBagSize(); ++i) + { + Item bagItem = bag.GetItemByPos(i); + if (bagItem != null) + { + if (bagItem.m_lootGenerated) + { + GetSession().DoLootRelease(GetLootGUID()); + released = true; // so we don't need to look at dstBag + break; + } + } + } + } + + if (!released && IsBagPos(dst) && pDstItem != null) + { + Bag bag = pDstItem.ToBag(); + for (byte i = 0; i < bag.GetBagSize(); ++i) + { + Item bagItem = bag.GetItemByPos(i); + if (bagItem != null) + { + if (bagItem.m_lootGenerated) + { + GetSession().DoLootRelease(GetLootGUID()); + break; + } + } + } + } + } + AutoUnequipOffhandIfNeed(); + } + bool _StoreOrEquipNewItem(uint vendorslot, uint item, byte count, byte bag, byte slot, int price, ItemTemplate pProto, Creature pVendor, VendorItem crItem, bool bStore) + { + uint stacks = count / pProto.GetBuyCount(); + List vDest = new List(); + ushort uiDest = 0; + InventoryResult msg = bStore ? CanStoreNewItem(bag, slot, vDest, item, count) : CanEquipNewItem(slot, out uiDest, item, false); + if (msg != InventoryResult.Ok) + { + SendEquipError(msg, null, null, item); + return false; + } + + ModifyMoney(-price); + + if (crItem.ExtendedCost != 0) // case for new honor system + { + var iece = CliDB.ItemExtendedCostStorage.LookupByKey(crItem.ExtendedCost); + for (int i = 0; i < ItemConst.MaxItemExtCostItems; ++i) + { + if (iece.RequiredItem[i] != 0) + DestroyItemCount(iece.RequiredItem[i], iece.RequiredItemCount[i] * stacks, true); + } + + for (int i = 0; i < ItemConst.MaxItemExtCostCurrencies; ++i) + { + if (iece.RequirementFlags.HasAnyFlag((byte)((int)ItemExtendedCostFlags.RequireSeasonEarned1 << i))) + continue; + + if (iece.RequiredCurrency[i] != 0) + ModifyCurrency((CurrencyTypes)iece.RequiredCurrency[i], -(int)(iece.RequiredCurrencyCount[i] * stacks), true, true); + } + } + + Item it = bStore ? StoreNewItem(vDest, item, true, ItemEnchantment.GenerateItemRandomPropertyId(item), null, 0, null, false) : EquipNewItem(uiDest, item, true); + if (it != null) + { + uint new_count = pVendor.UpdateVendorItemCurrentCount(crItem, count); + + BuySucceeded packet = new BuySucceeded(); + packet.VendorGUID = pVendor.GetGUID(); + packet.Muid = vendorslot + 1; + packet.NewQuantity = crItem.maxcount > 0 ? new_count : 0xFFFFFFFF; + packet.QuantityBought = count; + SendPacket(packet); + + SendNewItem(it, count, true, false, false); + + if (!bStore) + AutoUnequipOffhandIfNeed(); + + if (pProto.GetFlags().HasAnyFlag(ItemFlags.ItemPurchaseRecord) && crItem.ExtendedCost != 0 && pProto.GetMaxStackSize() == 1) + { + it.SetFlag(ItemFields.Flags, ItemFieldFlags.Refundable); + it.SetRefundRecipient(GetGUID()); + it.SetPaidMoney((uint)price); + it.SetPaidExtendedCost(crItem.ExtendedCost); + it.SaveRefundDataToDB(); + AddRefundReference(it.GetGUID()); + } + + GetSession().GetCollectionMgr().OnItemAdded(it); + } + return true; + } + public void SendNewItem(Item item, uint quantity, bool pushed, bool created, bool broadcast = false) + { + if (item == null) // prevent crash + return; + + ItemPushResult packet = new ItemPushResult(); + + packet.PlayerGUID = GetGUID(); + + packet.Slot = item.GetBagSlot(); + packet.SlotInBag = item.GetCount() == quantity ? item.GetSlot() : -1; + + packet.Item = new ItemInstance(item); + + //packet.QuestLogItemID; + packet.Quantity = quantity; + packet.QuantityInInventory = GetItemCount(item.GetEntry()); + //packet.DungeonEncounterID; + packet.BattlePetBreedID = (int)item.GetModifier(ItemModifier.BattlePetBreedData) & 0xFFFFFF; + packet.BattlePetBreedQuality = (item.GetModifier(ItemModifier.BattlePetBreedData) >> 24) & 0xFF; + packet.BattlePetSpeciesID = (int)item.GetModifier(ItemModifier.BattlePetSpeciesId); + packet.BattlePetLevel = (int)item.GetModifier(ItemModifier.BattlePetLevel); + + packet.ItemGUID = item.GetGUID(); + + packet.Pushed = pushed; + packet.DisplayText = ItemPushResult.DisplayType.Normal; + packet.Created = created; + //packet.IsBonusRoll; + //packet.IsEncounterLoot; + + if (broadcast && GetGroup()) + GetGroup().BroadcastPacket(packet, true); + else + SendPacket(packet); + } + + //Item Durations + void RemoveItemDurations(Item item) + { + m_itemDuration.Remove(item); + } + void AddItemDurations(Item item) + { + if (item.GetUInt32Value(ItemFields.Duration) != 0) + { + m_itemDuration.Add(item); + item.SendTimeUpdate(this); + } + } + void UpdateItemDuration(uint time, bool realtimeonly = false) + { + if (m_itemDuration.Empty()) + return; + + Log.outDebug(LogFilter.Player, "Player:UpdateItemDuration({0}, {1})", time, realtimeonly); + + foreach (var item in m_itemDuration) + { + if (!realtimeonly || item.GetTemplate().GetFlags().HasAnyFlag(ItemFlags.RealDuration)) + item.UpdateDuration(this, time); + } + } + void SendEnchantmentDurations() + { + foreach (var enchantDuration in m_enchantDuration) + GetSession().SendItemEnchantTimeUpdate(GetGUID(), enchantDuration.item.GetGUID(), (uint)enchantDuration.slot, enchantDuration.leftduration / 1000); + } + void SendItemDurations() + { + foreach (var item in m_itemDuration) + item.SendTimeUpdate(this); + } + + public void ToggleMetaGemsActive(uint exceptslot, bool apply) + { + //cycle all equipped items + for (byte slot = EquipmentSlot.Start; slot < EquipmentSlot.End; ++slot) + { + //enchants for the slot being socketed are handled by WorldSession.HandleSocketOpcode(WorldPacket& recvData) + if (slot == exceptslot) + continue; + + Item pItem = GetItemByPos(InventorySlots.Bag0, slot); + + if (!pItem || pItem.GetSocketColor(0) == 0) //if item has no sockets or no item is equipped go to next item + continue; + + //cycle all (gem)enchants + for (EnchantmentSlot enchant_slot = EnchantmentSlot.Sock1; enchant_slot < EnchantmentSlot.Sock1 + 3; ++enchant_slot) + { + uint enchant_id = pItem.GetEnchantmentId(enchant_slot); + if (enchant_id == 0) //if no enchant go to next enchant(slot) + continue; + + SpellItemEnchantmentRecord enchantEntry = CliDB.SpellItemEnchantmentStorage.LookupByKey(enchant_id); + if (enchantEntry == null) + continue; + + //only metagems to be (de)activated, so only enchants with condition + uint condition = enchantEntry.ConditionID; + if (condition != 0) + ApplyEnchantment(pItem, enchant_slot, apply); + } + } + } + + public float GetAverageItemLevel() + { + float sum = 0; + uint count = 0; + + for (int i = EquipmentSlot.Start; i < EquipmentSlot.End; ++i) + { + // don't check tabard, ranged, offhand or shirt + if (i == EquipmentSlot.Tabard || i == EquipmentSlot.Ranged || i == EquipmentSlot.OffHand || i == EquipmentSlot.Shirt) + continue; + + if (m_items[i] != null) + sum += m_items[i].GetItemLevel(this); + + ++count; + } + + return sum / count; + } + public Item GetItemByGuid(ObjectGuid guid) + { + for (byte i = EquipmentSlot.Start; i < InventorySlots.ItemEnd; ++i) + { + Item pItem = GetItemByPos(InventorySlots.Bag0, i); + if (pItem != null) + if (pItem.GetGUID() == guid) + return pItem; + } + + for (byte i = InventorySlots.BankItemStart; i < InventorySlots.BankBagEnd; ++i) + { + Item pItem = GetItemByPos(InventorySlots.Bag0, i); + if (pItem != null) + if (pItem.GetGUID() == guid) + return pItem; + } + + for (byte i = InventorySlots.ReagentStart; i < InventorySlots.ReagentEnd; ++i) + { + Item pItem = GetItemByPos(InventorySlots.Bag0, i); + if (pItem) + if (pItem.GetGUID() == guid) + return pItem; + } + + for (byte i = InventorySlots.ChildEquipmentStart; i < InventorySlots.ChildEquipmentEnd; ++i) + { + Item pItem = GetItemByPos(InventorySlots.Bag0, i); + if (pItem) + if (pItem.GetGUID() == guid) + return pItem; + } + + for (byte i = InventorySlots.BagStart; i < InventorySlots.BagEnd; ++i) + { + Bag pBag = GetBagByPos(i); + if (pBag != null) + for (byte j = 0; j < pBag.GetBagSize(); ++j) + { + Item pItem = pBag.GetItemByPos(j); + if (pItem != null) + if (pItem.GetGUID() == guid) + return pItem; + } + } + + for (byte i = InventorySlots.BankBagStart; i < InventorySlots.BankBagEnd; ++i) + { + Bag pBag = GetBagByPos(i); + if (pBag != null) + for (byte j = 0; j < pBag.GetBagSize(); ++j) + { + Item pItem = pBag.GetItemByPos(j); + if (pItem != null) + if (pItem.GetGUID() == guid) + return pItem; + } + } + return null; + } + public uint GetItemCount(uint item, bool inBankAlso = false, Item skipItem = null) + { + uint count = 0; + for (byte i = EquipmentSlot.Start; i < InventorySlots.ItemEnd; i++) + { + Item pItem = GetItemByPos(InventorySlots.Bag0, i); + if (pItem != null) + if (pItem != skipItem && pItem.GetEntry() == item) + count += pItem.GetCount(); + } + + for (var i = InventorySlots.BagStart; i < InventorySlots.BagEnd; ++i) + { + Bag pBag = GetBagByPos(i); + if (pBag != null) + count += pBag.GetItemCount(item, skipItem); + } + + if (skipItem != null && skipItem.GetTemplate().GetGemProperties() != 0) + { + for (byte i = EquipmentSlot.Start; i < InventorySlots.ItemEnd; ++i) + { + Item pItem = GetItemByPos(InventorySlots.Bag0, i); + if (pItem != null) + if (pItem != skipItem && pItem.GetSocketColor(0) != 0) + count += pItem.GetGemCountWithID(item); + } + } + + if (inBankAlso) + { + // checking every item from 39 to 74 (including bank bags) + for (var i = InventorySlots.BankItemStart; i < InventorySlots.BankBagEnd; ++i) + { + Item pItem = GetItemByPos(InventorySlots.Bag0, i); + if (pItem != null) + if (pItem != skipItem && pItem.GetEntry() == item) + count += pItem.GetCount(); + } + + for (var i = InventorySlots.BankBagStart; i < InventorySlots.BankBagEnd; ++i) + { + Bag pBag = GetBagByPos(i); + if (pBag != null) + count += pBag.GetItemCount(item, skipItem); + } + + if (skipItem != null && skipItem.GetTemplate().GetGemProperties() != 0) + { + for (var i = InventorySlots.BankItemStart; i < InventorySlots.BankItemEnd; ++i) + { + Item pItem = GetItemByPos(InventorySlots.Bag0, i); + if (pItem != null) + if (pItem != skipItem && pItem.GetSocketColor(0) != 0) + count += pItem.GetGemCountWithID(item); + } + } + } + + for (byte i = InventorySlots.ReagentStart; i < InventorySlots.ReagentEnd; ++i) + { + Item pItem = GetItemByPos(InventorySlots.Bag0, i); + if (pItem) + if (pItem != skipItem && pItem.GetEntry() == item) + count += pItem.GetCount(); + } + + if (skipItem && skipItem.GetTemplate().GetGemProperties() != 0) + { + for (byte i = InventorySlots.ReagentStart; i < InventorySlots.ReagentEnd; ++i) + { + Item pItem = GetItemByPos(InventorySlots.Bag0, i); + if (pItem) + if (pItem != skipItem && pItem.GetSocketColor(0) != 0) + count += pItem.GetGemCountWithID(item); + } + } + + for (byte i = InventorySlots.ChildEquipmentStart; i < InventorySlots.ChildEquipmentEnd; ++i) + { + Item pItem = GetItemByPos(InventorySlots.Bag0, i); + if (pItem) + { + if (pItem != skipItem && pItem.GetEntry() == item) + count += pItem.GetCount(); + } + } + + if (skipItem && skipItem.GetTemplate().GetGemProperties() != 0) + { + for (byte i = InventorySlots.ChildEquipmentStart; i < InventorySlots.ChildEquipmentEnd; ++i) + { + Item pItem = GetItemByPos(InventorySlots.Bag0, i); + if (pItem) + if (pItem != skipItem && pItem.GetSocketColor(0) != 0) + count += pItem.GetGemCountWithID(item); + } + } + + return count; + } + public Item GetUseableItemByPos(byte bag, byte slot) + { + Item item = GetItemByPos(bag, slot); + if (!item) + return null; + + if (!CanUseAttackType(GetAttackBySlot(slot, item.GetTemplate().GetInventoryType()))) + return null; + + return item; + } + public Item GetItemByPos(ushort pos) + { + byte bag = (byte)(pos >> 8); + byte slot = (byte)(pos & 255); + + return GetItemByPos(bag, slot); + } + public Item GetItemByPos(byte bag, byte slot) + { + if (bag == InventorySlots.Bag0 && slot < (int)PlayerSlots.End && (slot < InventorySlots.BuyBackStart || slot >= InventorySlots.BuyBackEnd)) + return m_items[slot]; + + Bag pBag = GetBagByPos(bag); + if (pBag != null) + return pBag.GetItemByPos(slot); + + return null; + } + public Item GetItemByEntry(uint entry) + { + // in inventory + for (byte i = InventorySlots.ItemStart; i < InventorySlots.ItemEnd; ++i) + { + Item pItem = GetItemByPos(InventorySlots.Bag0, i); + if (pItem != null) + if (pItem.GetEntry() == entry) + return pItem; + } + + for (byte i = InventorySlots.BagStart; i < InventorySlots.BagEnd; ++i) + { + Bag pBag = GetBagByPos(i); + if (pBag != null) + for (byte j = 0; j < pBag.GetBagSize(); ++j) + { + Item pItem = pBag.GetItemByPos(j); + if (pItem != null) + { + if (pItem.GetEntry() == entry) + return pItem; + } + } + } + + for (byte i = EquipmentSlot.Start; i < InventorySlots.BagEnd; ++i) + { + Item pItem = GetItemByPos(InventorySlots.Bag0, i); + if (pItem != null) + if (pItem.GetEntry() == entry) + return pItem; + } + + for (byte i = InventorySlots.ChildEquipmentStart; i < InventorySlots.ChildEquipmentEnd; ++i) + { + Item pItem = GetItemByPos(InventorySlots.Bag0, i); + if (pItem) + if (pItem.GetEntry() == entry) + return pItem; + } + + return null; + } + public List GetItemListByEntry(uint entry, bool inBankAlso = false) + { + List itemList = new List(); + + for (byte i = InventorySlots.ItemStart; i < InventorySlots.ItemEnd; ++i) + { + Item item = GetItemByPos(InventorySlots.Bag0, i); + if (item != null) + if (item.GetEntry() == entry) + itemList.Add(item); + } + + for (byte i = InventorySlots.BagStart; i < InventorySlots.BagEnd; ++i) + { + Bag bag = GetBagByPos(i); + if (bag) + { + for (byte j = 0; j < bag.GetBagSize(); ++j) + { + Item item = bag.GetItemByPos(j); + if (item != null) + if (item.GetEntry() == entry) + itemList.Add(item); + } + } + } + + for (byte i = EquipmentSlot.Start; i < InventorySlots.BagEnd; ++i) + { + Item item = GetItemByPos(InventorySlots.Bag0, i); + if (item) + if (item.GetEntry() == entry) + itemList.Add(item); + } + + if (inBankAlso) + { + for (byte i = InventorySlots.BankItemStart; i < InventorySlots.BankBagEnd; ++i) + { + Item item = GetItemByPos(InventorySlots.Bag0, i); + if (item) + if (item.GetEntry() == entry) + itemList.Add(item); + } + } + + for (byte i = InventorySlots.ChildEquipmentStart; i < InventorySlots.ChildEquipmentEnd; ++i) + { + Item item = GetItemByPos(InventorySlots.Bag0, i); + if (item) + if (item.GetEntry() == entry) + itemList.Add(item); + } + + return itemList; + } + public bool HasItemCount(uint item, uint count = 1, bool inBankAlso = false) + { + uint tempcount = 0; + for (byte i = EquipmentSlot.Start; i < InventorySlots.ItemEnd; i++) + { + Item pItem = GetItemByPos(InventorySlots.Bag0, i); + if (pItem != null && pItem.GetEntry() == item && !pItem.IsInTrade()) + { + tempcount += pItem.GetCount(); + if (tempcount >= count) + return true; + } + } + + for (byte i = InventorySlots.BagStart; i < InventorySlots.BagEnd; i++) + { + Bag pBag = GetBagByPos(i); + if (pBag != null) + { + for (byte j = 0; j < pBag.GetBagSize(); j++) + { + Item pItem = GetItemByPos(i, j); + if (pItem != null && pItem.GetEntry() == item && !pItem.IsInTrade()) + { + tempcount += pItem.GetCount(); + if (tempcount >= count) + return true; + } + } + } + } + + if (inBankAlso) + { + for (byte i = InventorySlots.BankItemStart; i < InventorySlots.BankItemEnd; i++) + { + Item pItem = GetItemByPos(InventorySlots.Bag0, i); + if (pItem != null && pItem.GetEntry() == item && !pItem.IsInTrade()) + { + tempcount += pItem.GetCount(); + if (tempcount >= count) + return true; + } + } + for (byte i = InventorySlots.BankBagStart; i < InventorySlots.BankBagEnd; i++) + { + Bag pBag = GetBagByPos(i); + if (pBag != null) + { + for (byte j = 0; j < pBag.GetBagSize(); j++) + { + Item pItem = GetItemByPos(i, j); + if (pItem != null && pItem.GetEntry() == item && !pItem.IsInTrade()) + { + tempcount += pItem.GetCount(); + if (tempcount >= count) + return true; + } + } + } + } + } + + for (byte i = InventorySlots.ReagentStart; i < InventorySlots.ReagentEnd; i++) + { + Item pItem = GetItemByPos(InventorySlots.Bag0, i); + if (pItem && pItem.GetEntry() == item && !pItem.IsInTrade()) + { + tempcount += pItem.GetCount(); + if (tempcount >= count) + return true; + } + } + + for (byte i = InventorySlots.ChildEquipmentStart; i < InventorySlots.ChildEquipmentEnd; i++) + { + Item pItem = GetItemByPos(InventorySlots.Bag0, i); + if (pItem && pItem.GetEntry() == item && !pItem.IsInTrade()) + { + tempcount += pItem.GetCount(); + if (tempcount >= count) + return true; + } + } + + return false; + } + public static bool IsChildEquipmentPos(byte bag, byte slot) + { + return bag == InventorySlots.Bag0 && (slot >= InventorySlots.ChildEquipmentStart && slot < InventorySlots.ChildEquipmentEnd); + } + public bool IsValidPos(byte bag, byte slot, bool explicit_pos) + { + // post selected + if (bag == ItemConst.NullBag && !explicit_pos) + return true; + + if (bag == InventorySlots.Bag0) + { + // any post selected + if (slot == ItemConst.NullSlot && !explicit_pos) + return true; + + // equipment + if (slot < EquipmentSlot.End) + return true; + + // bag equip slots + if (slot >= InventorySlots.BagStart && slot < InventorySlots.BagEnd) + return true; + + // backpack slots + if (slot >= InventorySlots.ItemStart && slot < InventorySlots.ItemEnd) + return true; + + // bank main slots + if (slot >= InventorySlots.BankItemStart && slot < InventorySlots.BankItemEnd) + return true; + + // bank bag slots + if (slot >= InventorySlots.BankBagStart && slot < InventorySlots.BankBagEnd) + return true; + + return false; + } + + // bag content slots + // bank bag content slots + Bag pBag = GetBagByPos(bag); + if (pBag != null) + { + // any post selected + if (slot == ItemConst.NullSlot && !explicit_pos) + return true; + + return slot < pBag.GetBagSize(); + } + + // where this? + return false; + } + + public Item GetChildItemByGuid(ObjectGuid guid) + { + for (byte i = EquipmentSlot.Start; i < InventorySlots.ItemEnd; ++i) + { + Item pItem = GetItemByPos(InventorySlots.Bag0, i); + if (pItem) + if (pItem.GetGUID() == guid) + return pItem; + } + + for (byte i = InventorySlots.ChildEquipmentStart; i < InventorySlots.ChildEquipmentEnd; ++i) + { + Item pItem = GetItemByPos(InventorySlots.Bag0, i); + if (pItem) + if (pItem.GetGUID() == guid) + return pItem; + } + + return null; + } + uint GetItemCountWithLimitCategory(uint limitCategory, Item skipItem) + { + uint count = 0; + for (byte i = EquipmentSlot.Start; i < InventorySlots.ItemEnd; ++i) + { + Item pItem = GetItemByPos(InventorySlots.Bag0, i); + if (pItem) + { + if (pItem != skipItem) + { + ItemTemplate pProto = pItem.GetTemplate(); + if (pProto != null) + if (pProto.GetItemLimitCategory() == limitCategory) + count += pItem.GetCount(); + } + } + } + + for (byte i = InventorySlots.BagStart; i < InventorySlots.BagEnd; ++i) + { + Bag pBag = GetBagByPos(i); + if (pBag) + count += pBag.GetItemCountWithLimitCategory(limitCategory, skipItem); + } + + for (byte i = InventorySlots.BankItemStart; i < InventorySlots.BankItemEnd; ++i) + { + Item pItem = GetItemByPos(InventorySlots.Bag0, i); + if (pItem) + { + if (pItem != skipItem) + { + ItemTemplate pProto = pItem.GetTemplate(); + if (pProto != null) + if (pProto.GetItemLimitCategory() == limitCategory) + count += pItem.GetCount(); + } + } + } + + for (byte i = InventorySlots.BankBagStart; i < InventorySlots.BankBagEnd; ++i) + { + Bag pBag = GetBagByPos(i); + if (pBag) + count += pBag.GetItemCountWithLimitCategory(limitCategory, skipItem); + } + + for (byte i = InventorySlots.ReagentStart; i < InventorySlots.ReagentEnd; ++i) + { + Item pItem = GetItemByPos(InventorySlots.Bag0, i); + if (pItem) + { + if (pItem != skipItem) + { + ItemTemplate pProto = pItem.GetTemplate(); + if (pProto != null) + if (pProto.GetItemLimitCategory() == limitCategory) + count += pItem.GetCount(); + } + } + } + + for (byte i = InventorySlots.ChildEquipmentStart; i < InventorySlots.ChildEquipmentEnd; ++i) + { + Item pItem = GetItemByPos(InventorySlots.Bag0, i); + if (pItem) + { + if (pItem != skipItem) + { + ItemTemplate pProto = pItem.GetTemplate(); + if (pProto != null) + if (pProto.GetItemLimitCategory() == limitCategory) + count += pItem.GetCount(); + } + } + } + + return count; + } + + public void DestroyConjuredItems(bool update) + { + // used when entering arena + // destroys all conjured items + Log.outDebug(LogFilter.Player, "STORAGE: DestroyConjuredItems"); + + // in inventory + for (byte i = InventorySlots.ItemStart; i < InventorySlots.ItemEnd; i++) + { + Item pItem = GetItemByPos(InventorySlots.Bag0, i); + if (pItem) + { + if (pItem.IsConjuredConsumable()) + DestroyItem(InventorySlots.Bag0, i, update); + } + } + + // in inventory bags + for (byte i = InventorySlots.BagStart; i < InventorySlots.BagEnd; i++) + { + Bag pBag = GetBagByPos(i); + if (pBag) + { + for (byte j = 0; j < pBag.GetBagSize(); j++) + { + Item pItem = pBag.GetItemByPos(j); + if (pItem) + if (pItem.IsConjuredConsumable()) + DestroyItem(i, j, update); + } + } + } + + // in equipment and bag list + for (byte i = EquipmentSlot.Start; i < InventorySlots.BagEnd; i++) + { + Item pItem = GetItemByPos(InventorySlots.Bag0, i); + if (pItem) + if (pItem.IsConjuredConsumable()) + DestroyItem(InventorySlots.Bag0, i, update); + } + } + void DestroyZoneLimitedItem(bool update, uint new_zone) + { + Log.outDebug(LogFilter.Player, "STORAGE: DestroyZoneLimitedItem in map {0} and area {1}", GetMapId(), new_zone); + + // in inventory + for (byte i = InventorySlots.ItemStart; i < InventorySlots.ItemEnd; i++) + { + Item pItem = GetItemByPos(InventorySlots.Bag0, i); + if (pItem) + if (pItem.IsLimitedToAnotherMapOrZone(GetMapId(), new_zone)) + DestroyItem(InventorySlots.Bag0, i, update); + } + + // in inventory bags + for (byte i = InventorySlots.BagStart; i < InventorySlots.BagEnd; i++) + { + Bag pBag = GetBagByPos(i); + if (pBag) + { + for (byte j = 0; j < pBag.GetBagSize(); j++) + { + Item pItem = pBag.GetItemByPos(j); + if (pItem) + if (pItem.IsLimitedToAnotherMapOrZone(GetMapId(), new_zone)) + DestroyItem(i, j, update); + } + } + } + + // in equipment and bag list + for (byte i = EquipmentSlot.Start; i < InventorySlots.BagEnd; i++) + { + Item pItem = GetItemByPos(InventorySlots.Bag0, i); + if (pItem) + if (pItem.IsLimitedToAnotherMapOrZone(GetMapId(), new_zone)) + DestroyItem(InventorySlots.Bag0, i, update); + } + } + + public InventoryResult CanRollForItemInLFG(ItemTemplate proto, WorldObject lootedObject) + { + if (!GetGroup() || !GetGroup().isLFGGroup()) + return InventoryResult.Ok; // not in LFG group + + // check if looted object is inside the lfg dungeon + Map map = lootedObject.GetMap(); + if (!Global.LFGMgr.inLfgDungeonMap(GetGroup().GetGUID(), map.GetId(), map.GetDifficultyID())) + return InventoryResult.Ok; + + if (proto == null) + return InventoryResult.ItemNotFound; + + // Used by group, function GroupLoot, to know if a prototype can be used by a player + if ((proto.GetAllowableClass() & getClassMask()) == 0 || (proto.GetAllowableRace() & getRaceMask()) == 0) + return InventoryResult.CantEquipEver; + + if (proto.GetRequiredSpell() != 0 && !HasSpell(proto.GetRequiredSpell())) + return InventoryResult.ProficiencyNeeded; + + if (proto.GetRequiredSkill() != 0) + { + if (GetSkillValue((SkillType)proto.GetRequiredSkill()) == 0) + return InventoryResult.ProficiencyNeeded; + else if (GetSkillValue((SkillType)proto.GetRequiredSkill()) < proto.GetRequiredSkillRank()) + return InventoryResult.CantEquipSkill; + } + + Class _class = GetClass(); + if (proto.GetClass() == ItemClass.Weapon && GetSkillValue(proto.GetSkill()) == 0) + return InventoryResult.ProficiencyNeeded; + + if (proto.GetClass() == ItemClass.Armor && proto.GetSubClass() > (uint)ItemSubClassArmor.Miscellaneous + && proto.GetSubClass() < (uint)ItemSubClassArmor.Cosmetic && proto.GetInventoryType() != InventoryType.Cloak) + { + if (_class == Class.Warrior || _class == Class.Paladin || _class == Class.Deathknight) + { + if (getLevel() < 40) + { + if (proto.GetSubClass() != (uint)ItemSubClassArmor.Mail) + return InventoryResult.ClientLockedOut; + } + else if (proto.GetSubClass() != (uint)ItemSubClassArmor.Plate) + return InventoryResult.ClientLockedOut; + } + else if (_class == Class.Hunter || _class == Class.Shaman) + { + if (getLevel() < 40) + { + if (proto.GetSubClass() != (uint)ItemSubClassArmor.Leather) + return InventoryResult.ClientLockedOut; + } + else if (proto.GetSubClass() != (uint)ItemSubClassArmor.Mail) + return InventoryResult.ClientLockedOut; + } + + if (_class == Class.Rogue || _class == Class.Druid) + if (proto.GetSubClass() != (uint)ItemSubClassArmor.Leather) + return InventoryResult.ClientLockedOut; + + if (_class == Class.Mage || _class == Class.Priest || _class == Class.Warlock) + if (proto.GetSubClass() != (uint)ItemSubClassArmor.Cloth) + return InventoryResult.ClientLockedOut; + } + + return InventoryResult.Ok; + } + + public void AddItemToBuyBackSlot(Item pItem) + { + if (pItem != null) + { + uint slot = m_currentBuybackSlot; + // if current back slot non-empty search oldest or free + if (m_items[slot] != null) + { + uint oldest_time = GetUInt32Value(PlayerFields.BuyBackTimestamp1); + uint oldest_slot = InventorySlots.BuyBackStart; + + for (byte i = InventorySlots.BuyBackStart + 1; i < InventorySlots.BuyBackEnd; ++i) + { + // found empty + if (!m_items[i]) + { + oldest_slot = i; + break; + } + + uint i_time = GetUInt32Value(PlayerFields.BuyBackTimestamp1 + i - InventorySlots.BuyBackStart); + + if (oldest_time > i_time) + { + oldest_time = i_time; + oldest_slot = i; + } + } + + // find oldest + slot = oldest_slot; + } + + RemoveItemFromBuyBackSlot(slot, true); + Log.outDebug(LogFilter.Player, "STORAGE: AddItemToBuyBackSlot item = {0}, slot = {1}", pItem.GetEntry(), slot); + + m_items[slot] = pItem; + var time = Time.UnixTime; + uint etime = (uint)(time - m_logintime + (30 * 3600)); + int eslot = (int)slot - InventorySlots.BuyBackStart; + + SetGuidValue(PlayerFields.InvSlotHead + (eslot * 4), pItem.GetGUID()); + ItemTemplate proto = pItem.GetTemplate(); + if (proto != null) + SetUInt32Value(PlayerFields.BuyBackPrice1 + eslot, proto.GetSellPrice() * pItem.GetCount()); + else + SetUInt32Value(PlayerFields.BuyBackPrice1 + eslot, 0); + SetUInt32Value(PlayerFields.BuyBackTimestamp1 + eslot, etime); + + // move to next (for non filled list is move most optimized choice) + if (m_currentBuybackSlot < InventorySlots.BuyBackEnd - 1) + ++m_currentBuybackSlot; + } + } + + public bool BuyCurrencyFromVendorSlot(ObjectGuid vendorGuid, uint vendorSlot, uint currency, uint count) + { + // cheating attempt + if (count < 1) + count = 1; + + if (!IsAlive()) + return false; + + CurrencyTypesRecord proto = CliDB.CurrencyTypesStorage.LookupByKey(currency); + if (proto == null) + { + SendBuyError(BuyResult.CantFindItem, null, currency); + return false; + } + + Creature creature = GetNPCIfCanInteractWith(vendorGuid, NPCFlags.Vendor); + if (!creature) + { + Log.outDebug(LogFilter.Network, "WORLD: BuyCurrencyFromVendorSlot - {0} not found or you can't interact with him.", vendorGuid.ToString()); + SendBuyError(BuyResult.DistanceTooFar, null, currency); + return false; + } + + VendorItemData vItems = creature.GetVendorItems(); + if (vItems == null || vItems.Empty()) + { + SendBuyError(BuyResult.CantFindItem, creature, currency); + return false; + } + + if (vendorSlot >= vItems.GetItemCount()) + { + SendBuyError(BuyResult.CantFindItem, creature, currency); + return false; + } + + VendorItem crItem = vItems.GetItem(vendorSlot); + // store diff item (cheating) + if (crItem == null || crItem.item != currency || crItem.Type != ItemVendorType.Currency) + { + SendBuyError(BuyResult.CantFindItem, creature, currency); + return false; + } + + if ((count % crItem.maxcount) != 0) + { + SendEquipError(InventoryResult.CantBuyQuantity); + return false; + } + + uint stacks = count / crItem.maxcount; + ItemExtendedCostRecord iece = null; + if (crItem.ExtendedCost != 0) + { + iece = CliDB.ItemExtendedCostStorage.LookupByKey(crItem.ExtendedCost); + if (iece == null) + { + Log.outError(LogFilter.Player, "Currency {0} have wrong ExtendedCost field value {1}", currency, crItem.ExtendedCost); + return false; + } + + for (byte i = 0; i < ItemConst.MaxItemExtCostItems; ++i) + { + if (iece.RequiredItem[i] != 0 && !HasItemCount(iece.RequiredItem[i], (iece.RequiredItemCount[i] * stacks))) + { + SendEquipError(InventoryResult.VendorMissingTurnins); + return false; + } + } + + for (byte i = 0; i < ItemConst.MaxItemExtCostCurrencies; ++i) + { + if (iece.RequiredCurrency[i] == 0) + continue; + + CurrencyTypesRecord entry = CliDB.CurrencyTypesStorage.LookupByKey(iece.RequiredCurrency[i]); + if (entry == null) + { + SendBuyError(BuyResult.CantFindItem, creature, currency); // Find correct error + return false; + } + + if (iece.RequirementFlags.HasAnyFlag((byte)((uint)ItemExtendedCostFlags.RequireSeasonEarned1 << i))) + { + // Not implemented + SendEquipError(InventoryResult.VendorMissingTurnins); // Find correct error + return false; + } + else if (!HasCurrency(iece.RequiredCurrency[i], (iece.RequiredCurrencyCount[i] * stacks))) + { + SendEquipError(InventoryResult.VendorMissingTurnins); // Find correct error + return false; + } + } + + // check for personal arena rating requirement + if (GetMaxPersonalArenaRatingRequirement(iece.RequiredArenaSlot) < iece.RequiredPersonalArenaRating) + { + // probably not the proper equip err + SendEquipError(InventoryResult.CantEquipRank); + return false; + } + + if (iece.RequiredFactionId != 0 && (uint)GetReputationRank(iece.RequiredFactionId) < iece.RequiredFactionStanding) + { + SendBuyError(BuyResult.ReputationRequire, creature, currency); + return false; + } + + if (iece.RequirementFlags.HasAnyFlag((byte)ItemExtendedCostFlags.RequireGuild) && GetGuildId() == 0) + { + SendEquipError(InventoryResult.VendorMissingTurnins); // Find correct error + return false; + } + + if (iece.RequiredAchievement != 0 && !HasAchieved(iece.RequiredAchievement)) + { + SendEquipError(InventoryResult.VendorMissingTurnins); // Find correct error + return false; + } + } + else // currencies have no price defined, can only be bought with ExtendedCost + { + SendBuyError(BuyResult.CantFindItem, null, currency); + return false; + } + + ModifyCurrency((CurrencyTypes)currency, (int)count, true, true); + if (iece != null) + { + for (byte i = 0; i < ItemConst.MaxItemExtCostItems; ++i) + { + if (iece.RequiredItem[i] == 0) + continue; + + DestroyItemCount(iece.RequiredItem[i], iece.RequiredItemCount[i] * stacks, true); + } + + for (byte i = 0; i < ItemConst.MaxItemExtCostCurrencies; ++i) + { + if (iece.RequiredCurrency[i] == 0) + continue; + + if (iece.RequirementFlags.HasAnyFlag((byte)((uint)ItemExtendedCostFlags.RequireSeasonEarned1 << i))) + continue; + + ModifyCurrency((CurrencyTypes)iece.RequiredCurrency[i], -(int)(iece.RequiredCurrencyCount[i] * stacks), false, true); + } + } + + return true; + } + + public bool BuyItemFromVendorSlot(ObjectGuid vendorguid, uint vendorslot, uint item, byte count, byte bag, byte slot) + { + // cheating attempt + if (count < 1) + count = 1; + + // cheating attempt + if (slot > ItemConst.MaxBagSize && slot != ItemConst.NullSlot) + return false; + + if (!IsAlive()) + return false; + + ItemTemplate pProto = Global.ObjectMgr.GetItemTemplate(item); + if (pProto == null) + { + SendBuyError(BuyResult.CantFindItem, null, item); + return false; + } + + if (!Convert.ToBoolean(pProto.GetAllowableClass() & getClassMask()) && pProto.GetBonding() == ItemBondingType.OnAcquire && !IsGameMaster()) + { + SendBuyError(BuyResult.CantFindItem, null, item); + return false; + } + + if (!IsGameMaster() && ((pProto.GetFlags2().HasAnyFlag(ItemFlags2.FactionHorde) && GetTeam() == Team.Alliance) || (pProto.GetFlags2().HasAnyFlag(ItemFlags2.FactionAlliance) && GetTeam() == Team.Horde))) + return false; + + Creature creature = GetNPCIfCanInteractWith(vendorguid, NPCFlags.Vendor); + if (!creature) + { + Log.outDebug(LogFilter.Network, "WORLD: BuyItemFromVendor - {0} not found or you can't interact with him.", vendorguid.ToString()); + SendBuyError(BuyResult.DistanceTooFar, null, item); + return false; + } + + if (!Global.ConditionMgr.IsObjectMeetingVendorItemConditions(creature.GetEntry(), item, this, creature)) + { + Log.outDebug(LogFilter.Condition, "BuyItemFromVendor: conditions not met for creature entry {0} item {1}", creature.GetEntry(), item); + SendBuyError(BuyResult.CantFindItem, creature, item); + return false; + } + + VendorItemData vItems = creature.GetVendorItems(); + if (vItems == null || vItems.Empty()) + { + SendBuyError(BuyResult.CantFindItem, creature, item); + return false; + } + + if (vendorslot >= vItems.GetItemCount()) + { + SendBuyError(BuyResult.CantFindItem, creature, item); + return false; + } + + VendorItem crItem = vItems.GetItem(vendorslot); + // store diff item (cheating) + if (crItem == null || crItem.item != item) + { + SendBuyError(BuyResult.CantFindItem, creature, item); + return false; + } + + // check current item amount if it limited + if (crItem.maxcount != 0) + { + if (creature.GetVendorItemCurrentCount(crItem) < pProto.GetBuyCount() * count) + { + SendBuyError(BuyResult.ItemAlreadySold, creature, item); + return false; + } + } + + if (pProto.GetRequiredReputationFaction() != 0 && ((uint)GetReputationRank(pProto.GetRequiredReputationFaction()) < pProto.GetRequiredReputationRank())) + { + SendBuyError(BuyResult.ReputationRequire, creature, item); + return false; + } + + if (crItem.ExtendedCost != 0) + { + // Can only buy full stacks for extended cost + if ((count % pProto.GetBuyCount()) != 0) + { + SendEquipError(InventoryResult.CantBuyQuantity); + return false; + } + + uint stacks = count / pProto.GetBuyCount(); + var iece = CliDB.ItemExtendedCostStorage.LookupByKey(crItem.ExtendedCost); + if (iece == null) + { + Log.outError(LogFilter.Player, "Item {0} have wrong ExtendedCost field value {1}", pProto.GetId(), crItem.ExtendedCost); + return false; + } + + for (byte i = 0; i < ItemConst.MaxItemExtCostItems; ++i) + { + if (iece.RequiredItem[i] != 0 && !HasItemCount(iece.RequiredItem[i], iece.RequiredItemCount[i] * stacks)) + { + SendEquipError(InventoryResult.VendorMissingTurnins); + return false; + } + } + + for (byte i = 0; i < ItemConst.MaxItemExtCostCurrencies; ++i) + { + if (iece.RequiredCurrency[i] == 0) + continue; + + var entry = CliDB.CurrencyTypesStorage.LookupByKey(iece.RequiredCurrency[i]); + if (entry == null) + { + SendBuyError(BuyResult.CantFindItem, creature, item); + return false; + } + + if (iece.RequirementFlags.HasAnyFlag((byte)((uint)ItemExtendedCostFlags.RequireSeasonEarned1 << i))) + { + SendEquipError(InventoryResult.VendorMissingTurnins); // Find correct error + return false; + } + else if (!HasCurrency(iece.RequiredCurrency[i], iece.RequiredCurrencyCount[i] * stacks)) + { + SendEquipError(InventoryResult.VendorMissingTurnins); + return false; + } + } + + // check for personal arena rating requirement + if (GetMaxPersonalArenaRatingRequirement(iece.RequiredArenaSlot) < iece.RequiredPersonalArenaRating) + { + // probably not the proper equip err + SendEquipError(InventoryResult.CantEquipRank); + return false; + } + + if (iece.RequiredFactionId != 0 && (uint)GetReputationRank(iece.RequiredFactionId) < iece.RequiredFactionStanding) + { + SendBuyError(BuyResult.ReputationRequire, creature, item); + return false; + } + + if (iece.RequirementFlags.HasAnyFlag((byte)ItemExtendedCostFlags.RequireGuild) && GetGuildId() == 0) + { + SendEquipError(InventoryResult.VendorMissingTurnins); // Find correct error + return false; + } + + if (iece.RequiredAchievement != 0 && !HasAchieved(iece.RequiredAchievement)) + { + SendEquipError(InventoryResult.VendorMissingTurnins); // Find correct error + return false; + } + } + + uint price = 0; + if (crItem.IsGoldRequired(pProto) && pProto.GetBuyPrice() > 0) //Assume price cannot be negative (do not know why it is int32) + { + uint maxCount = (uint)(Int64.MaxValue / pProto.GetBuyPrice()); + if (count > maxCount) + { + Log.outError(LogFilter.Player, "Player {0} tried to buy {1} item id {2}, causing overflow", GetName(), count, pProto.GetId()); + count = (byte)maxCount; + } + price = pProto.GetBuyPrice() * count; //it should not exceed MAX_MONEY_AMOUNT + + // reputation discount + price = (uint)Math.Floor(price * GetReputationPriceDiscount(creature)); + + int priceMod = GetTotalAuraModifier(AuraType.ModVendorItemsPrices); + if (priceMod != 0) + price -= MathFunctions.CalculatePct(price, priceMod); + + if (!HasEnoughMoney(price)) + { + SendBuyError(BuyResult.NotEnoughtMoney, creature, item); + return false; + } + } + + if ((bag == ItemConst.NullBag && slot == ItemConst.NullSlot) || IsInventoryPos(bag, slot)) + { + if (!_StoreOrEquipNewItem(vendorslot, item, count, bag, slot, (int)price, pProto, creature, crItem, true)) + return false; + } + else if (IsEquipmentPos(bag, slot)) + { + if (count != 1) + { + SendEquipError(InventoryResult.NotEquippable); + return false; + } + if (!_StoreOrEquipNewItem(vendorslot, item, count, bag, slot, (int)price, pProto, creature, crItem, false)) + return false; + } + else + { + SendEquipError(InventoryResult.WrongSlot); + return false; + } + + if (crItem.maxcount != 0) // bought + { + if (pProto.GetQuality() > ItemQuality.Epic || (pProto.GetQuality() == ItemQuality.Epic && pProto.GetBaseItemLevel() >= GuildConst.MinNewsItemLevel)) + { + Guild guild = GetGuild(); + if (guild != null) + guild.AddGuildNews(GuildNews.ItemPurchased, GetGUID(), 0, item); + } + return true; + } + + return false; + } + + uint GetMaxPersonalArenaRatingRequirement(uint minarenaslot) + { + // returns the maximal personal arena rating that can be used to purchase items requiring this condition + // the personal rating of the arena team must match the required limit as well + // so return max[in arenateams](min(personalrating[teamtype], teamrating[teamtype])) + uint max_personal_rating = 0; + for (byte i = (byte)minarenaslot; i < SharedConst.MaxArenaSlot; ++i) + { + ArenaTeam at = Global.ArenaTeamMgr.GetArenaTeamById(GetArenaTeamId(i)); + if (at != null) + { + uint p_rating = GetArenaPersonalRating(i); + uint t_rating = at.GetRating(); + p_rating = p_rating < t_rating ? p_rating : t_rating; + if (max_personal_rating < p_rating) + max_personal_rating = p_rating; + } + } + return max_personal_rating; + } + + public void SendItemRetrievalMail(uint itemEntry, uint count) + { + MailSender sender = new MailSender(MailMessageType.Creature, 34337); + MailDraft draft = new MailDraft("Recovered Item", "We recovered a lost item in the twisting nether and noted that it was yours.$B$BPlease find said object enclosed."); // This is the text used in Cataclysm, it probably wasn't changed. + SQLTransaction trans = new SQLTransaction(); + + Item item = Item.CreateItem(itemEntry, count, null); + if (item) + { + item.SaveToDB(trans); + draft.AddItem(item); + } + + draft.SendMailTo(trans, new MailReceiver(this, GetGUID().GetCounter()), sender); + DB.Characters.CommitTransaction(trans); + } + + public Item GetItemFromBuyBackSlot(uint slot) + { + Log.outDebug(LogFilter.Player, "STORAGE: GetItemFromBuyBackSlot slot = {0}", slot); + if (slot >= InventorySlots.BuyBackStart && slot < InventorySlots.BuyBackEnd) + return m_items[slot]; + return null; + } + public void RemoveItemFromBuyBackSlot(uint slot, bool del) + { + Log.outDebug(LogFilter.Player, "STORAGE: RemoveItemFromBuyBackSlot slot = {0}", slot); + if (slot >= InventorySlots.BuyBackStart && slot < InventorySlots.BuyBackEnd) + { + Item pItem = m_items[slot]; + if (pItem) + { + pItem.RemoveFromWorld(); + if (del) + pItem.SetState(ItemUpdateState.Removed, this); + } + + m_items[slot] = null; + + int eslot = (int)slot - InventorySlots.BuyBackStart; + SetGuidValue(PlayerFields.InvSlotHead + (eslot * 4), ObjectGuid.Empty); + SetUInt32Value(PlayerFields.BuyBackPrice1 + eslot, 0); + SetUInt32Value(PlayerFields.BuyBackTimestamp1 + eslot, 0); + + // if current backslot is filled set to now free slot + if (m_items[m_currentBuybackSlot]) + m_currentBuybackSlot = slot; + } + } + + public bool HasItemTotemCategory(uint TotemCategory) + { + for (byte i = EquipmentSlot.Start; i < EquipmentSlot.End; ++i) + { + Item item = GetUseableItemByPos(InventorySlots.Bag0, i); + if (item && Global.DB2Mgr.IsTotemCategoryCompatibleWith(item.GetTemplate().GetTotemCategory(), TotemCategory)) + return true; + } + + for (byte i = InventorySlots.BagStart; i < InventorySlots.BagEnd; ++i) + { + Bag bag = GetBagByPos(i); + if (bag) + { + for (byte j = 0; j < bag.GetBagSize(); ++j) + { + Item item = GetUseableItemByPos(i, j); + if (item && Global.DB2Mgr.IsTotemCategoryCompatibleWith(item.GetTemplate().GetTotemCategory(), TotemCategory)) + return true; + } + } + } + + for (byte i = InventorySlots.ReagentStart; i < InventorySlots.ReagentEnd; ++i) + { + Item item = GetUseableItemByPos(InventorySlots.Bag0, i); + if (item && Global.DB2Mgr.IsTotemCategoryCompatibleWith(item.GetTemplate().GetTotemCategory(), TotemCategory)) + return true; + } + + for (byte i = InventorySlots.ChildEquipmentStart; i < InventorySlots.ChildEquipmentEnd; ++i) + { + Item item = GetUseableItemByPos(InventorySlots.Bag0, i); + if (item && Global.DB2Mgr.IsTotemCategoryCompatibleWith(item.GetTemplate().GetTotemCategory(), TotemCategory)) + return true; + } + + return false; + } + + public void _ApplyItemMods(Item item, byte slot, bool apply) + { + if (slot >= InventorySlots.BagEnd || item == null) + return; + + ItemTemplate proto = item.GetTemplate(); + + if (proto == null) + return; + + // not apply/remove mods for broken item + if (item.IsBroken()) + return; + + Log.outInfo(LogFilter.Player, "applying mods for item {0} ", item.GetGUID().ToString()); + + if (item.GetSocketColor(0) != 0) //only (un)equipping of items with sockets can influence metagems, so no need to waste time with normal items + CorrectMetaGemEnchants(slot, apply); + + _ApplyItemBonuses(item, slot, apply); + ApplyItemEquipSpell(item, apply); + ApplyItemDependentAuras(item, apply); + ApplyArtifactPowers(item, apply); + ApplyEnchantment(item, apply); + + Log.outDebug(LogFilter.Player, "_ApplyItemMods complete."); + } + public void _ApplyItemBonuses(Item item, byte slot, bool apply) + { + ItemTemplate proto = item.GetTemplate(); + if (slot >= InventorySlots.BagEnd || proto == null) + return; + + uint itemLevel = item.GetItemLevel(this); + float combatRatingMultiplier = 1.0f; + GtCombatRatingsMultByILvlRecord ratingMult = CliDB.CombatRatingsMultByILvlGameTable.GetRow(itemLevel); + if (ratingMult != null) + { + switch (proto.GetInventoryType()) + { + case InventoryType.Weapon: + case InventoryType.Shield: + case InventoryType.Ranged: + case InventoryType.Weapon2Hand: + case InventoryType.WeaponMainhand: + case InventoryType.WeaponOffhand: + case InventoryType.Holdable: + case InventoryType.RangedRight: + combatRatingMultiplier = ratingMult.WeaponMultiplier; + break; + case InventoryType.Trinket: + combatRatingMultiplier = ratingMult.TrinketMultiplier; + break; + case InventoryType.Neck: + case InventoryType.Finger: + combatRatingMultiplier = ratingMult.JewelryMultiplier; + break; + default: + combatRatingMultiplier = ratingMult.ArmorMultiplier; + break; + } + } + + // req. check at equip, but allow use for extended range if range limit max level, set proper level + for (byte i = 0; i < ItemConst.MaxStats; ++i) + { + int statType = item.GetItemStatType(i); + if (statType == -1) + continue; + + int val = item.GetItemStatValue(i, this); + if (val == 0) + continue; + + switch ((ItemModType)statType) + { + case ItemModType.Mana: + HandleStatModifier(UnitMods.Mana, UnitModifierType.BaseValue, (float)val, apply); + break; + case ItemModType.Health: // modify HP + HandleStatModifier(UnitMods.Health, UnitModifierType.BaseValue, (float)val, apply); + break; + case ItemModType.Agility: // modify agility + HandleStatModifier(UnitMods.StatAgility, UnitModifierType.BaseValue, (float)val, apply); + ApplyStatBuffMod(Stats.Agility, MathFunctions.CalculatePct(val, GetModifierValue(UnitMods.StatAgility, UnitModifierType.BasePCTExcludeCreate)), apply); + break; + case ItemModType.Strength: //modify strength + HandleStatModifier(UnitMods.StatStrength, UnitModifierType.BaseValue, (float)val, apply); + ApplyStatBuffMod(Stats.Strength, MathFunctions.CalculatePct(val, GetModifierValue(UnitMods.StatStrength, UnitModifierType.BasePCTExcludeCreate)), apply); + break; + case ItemModType.Intellect: //modify intellect + HandleStatModifier(UnitMods.StatIntellect, UnitModifierType.BaseValue, (float)val, apply); + ApplyStatBuffMod(Stats.Intellect, MathFunctions.CalculatePct(val, GetModifierValue(UnitMods.StatIntellect, UnitModifierType.BasePCTExcludeCreate)), apply); + break; + //case ItemModType.Spirit: //modify spirit + //HandleStatModifier(UnitMods.StatSpirit, UnitModifierType.BaseValue, (float)val, apply); + //ApplyStatBuffMod(Stats.Spirit, MathFunctions.CalculatePct(val, GetModifierValue(UnitMods.StatSpirit, UnitModifierType.BasePCTExcludeCreate)), apply); + //break; + case ItemModType.Stamina: //modify stamina + HandleStatModifier(UnitMods.StatStamina, UnitModifierType.BaseValue, (float)val, apply); + ApplyStatBuffMod(Stats.Stamina, MathFunctions.CalculatePct(val, GetModifierValue(UnitMods.StatStamina, UnitModifierType.BasePCTExcludeCreate)), apply); + break; + case ItemModType.DefenseSkillRating: + ApplyRatingMod(CombatRating.DefenseSkill, (int)(val * combatRatingMultiplier), apply); + break; + case ItemModType.DodgeRating: + ApplyRatingMod(CombatRating.Dodge, (int)(val * combatRatingMultiplier), apply); + break; + case ItemModType.ParryRating: + ApplyRatingMod(CombatRating.Parry, (int)(val * combatRatingMultiplier), apply); + break; + case ItemModType.BlockRating: + ApplyRatingMod(CombatRating.Block, (int)(val * combatRatingMultiplier), apply); + break; + case ItemModType.HitMeleeRating: + ApplyRatingMod(CombatRating.HitMelee, (int)(val * combatRatingMultiplier), apply); + break; + case ItemModType.HitRangedRating: + ApplyRatingMod(CombatRating.HitRanged, (int)(val * combatRatingMultiplier), apply); + break; + case ItemModType.HitSpellRating: + ApplyRatingMod(CombatRating.HitSpell, (int)(val * combatRatingMultiplier), apply); + break; + case ItemModType.CritMeleeRating: + ApplyRatingMod(CombatRating.CritMelee, (int)(val * combatRatingMultiplier), apply); + break; + case ItemModType.CritRangedRating: + ApplyRatingMod(CombatRating.CritRanged, (int)(val * combatRatingMultiplier), apply); + break; + case ItemModType.CritSpellRating: + ApplyRatingMod(CombatRating.CritSpell, (int)(val * combatRatingMultiplier), apply); + break; + case ItemModType.CritTakenRangedRating: + ApplyRatingMod(CombatRating.CritRanged, val, apply); + break; + case ItemModType.HasteMeleeRating: + ApplyRatingMod(CombatRating.HasteMelee, val, apply); + break; + case ItemModType.HasteRangedRating: + ApplyRatingMod(CombatRating.HasteRanged, val, apply); + break; + case ItemModType.HasteSpellRating: + ApplyRatingMod(CombatRating.HasteSpell, val, apply); + break; + case ItemModType.HitRating: + ApplyRatingMod(CombatRating.HitMelee, (int)(val * combatRatingMultiplier), apply); + ApplyRatingMod(CombatRating.HitRanged, (int)(val * combatRatingMultiplier), apply); + ApplyRatingMod(CombatRating.HitSpell, (int)(val * combatRatingMultiplier), apply); + break; + case ItemModType.CritRating: + ApplyRatingMod(CombatRating.CritMelee, (int)(val * combatRatingMultiplier), apply); + ApplyRatingMod(CombatRating.CritRanged, (int)(val * combatRatingMultiplier), apply); + ApplyRatingMod(CombatRating.CritSpell, (int)(val * combatRatingMultiplier), apply); + break; + case ItemModType.ResilienceRating: + ApplyRatingMod(CombatRating.ResiliencePlayerDamage, (int)(val * combatRatingMultiplier), apply); + break; + case ItemModType.HasteRating: + ApplyRatingMod(CombatRating.HasteMelee, (int)(val * combatRatingMultiplier), apply); + ApplyRatingMod(CombatRating.HasteRanged, (int)(val * combatRatingMultiplier), apply); + ApplyRatingMod(CombatRating.HasteSpell, (int)(val * combatRatingMultiplier), apply); + break; + case ItemModType.ExpertiseRating: + ApplyRatingMod(CombatRating.Expertise, (int)(val * combatRatingMultiplier), apply); + break; + case ItemModType.AttackPower: + HandleStatModifier(UnitMods.AttackPower, UnitModifierType.TotalValue, (float)val, apply); + HandleStatModifier(UnitMods.AttackPowerRanged, UnitModifierType.TotalValue, (float)val, apply); + break; + case ItemModType.RangedAttackPower: + HandleStatModifier(UnitMods.AttackPowerRanged, UnitModifierType.TotalValue, (float)val, apply); + break; + case ItemModType.Versatility: + ApplyRatingMod(CombatRating.VersatilityDamageDone, (int)(val * combatRatingMultiplier), apply); + ApplyRatingMod(CombatRating.VersatilityDamageTaken, (int)(val * combatRatingMultiplier), apply); + ApplyRatingMod(CombatRating.VersatilityHealingDone, (int)(val * combatRatingMultiplier), apply); + break; + case ItemModType.ManaRegeneration: + ApplyManaRegenBonus(val, apply); + break; + case ItemModType.ArmorPenetrationRating: + ApplyRatingMod(CombatRating.ArmorPenetration, val, apply); + break; + case ItemModType.SpellPower: + ApplySpellPowerBonus(val, apply); + break; + case ItemModType.HealthRegen: + ApplyHealthRegenBonus(val, apply); + break; + case ItemModType.SpellPenetration: + ApplySpellPenetrationBonus(val, apply); + break; + case ItemModType.MasteryRating: + ApplyRatingMod(CombatRating.Mastery, (int)(val * combatRatingMultiplier), apply); + break; + case ItemModType.FireResistance: + HandleStatModifier(UnitMods.ResistanceFire, UnitModifierType.BaseValue, (float)val, apply); + break; + case ItemModType.FrostResistance: + HandleStatModifier(UnitMods.ResistanceFrost, UnitModifierType.BaseValue, (float)val, apply); + break; + case ItemModType.HolyResistance: + HandleStatModifier(UnitMods.ResistanceHoly, UnitModifierType.BaseValue, (float)val, apply); + break; + case ItemModType.ShadowResistance: + HandleStatModifier(UnitMods.ResistanceShadow, UnitModifierType.BaseValue, (float)val, apply); + break; + case ItemModType.NatureResistance: + HandleStatModifier(UnitMods.ResistanceNature, UnitModifierType.BaseValue, (float)val, apply); + break; + case ItemModType.ArcaneResistance: + HandleStatModifier(UnitMods.ResistanceArcane, UnitModifierType.BaseValue, (float)val, apply); + break; + case ItemModType.PvpPower: + ApplyRatingMod(CombatRating.PvpPower, val, apply); + break; + case ItemModType.CrAmplify: + ApplyRatingMod(CombatRating.Amplify, val, apply); + break; + case ItemModType.CrMultistrike: + ApplyRatingMod(CombatRating.Multistrike, val, apply); + break; + case ItemModType.CrReadiness: + ApplyRatingMod(CombatRating.Readiness, (int)(val * combatRatingMultiplier), apply); + break; + case ItemModType.CrSpeed: + ApplyRatingMod(CombatRating.Speed, (int)(val * combatRatingMultiplier), apply); + break; + case ItemModType.CrLifesteal: + ApplyRatingMod(CombatRating.Lifesteal, (int)(val * combatRatingMultiplier), apply); + break; + case ItemModType.CrAvoidance: + ApplyRatingMod(CombatRating.Avoidance, (int)(val * combatRatingMultiplier), apply); + break; + case ItemModType.CrSturdiness: + ApplyRatingMod(CombatRating.Studiness, (int)(val * combatRatingMultiplier), apply); + break; + case ItemModType.CrUnused7: + ApplyRatingMod(CombatRating.Unused7, val, apply); + break; + case ItemModType.CrCleave: + ApplyRatingMod(CombatRating.Cleave, val, apply); + break; + case ItemModType.CrUnused12: + ApplyRatingMod(CombatRating.Unused12, val, apply); + break; + case ItemModType.AgiStrInt: + HandleStatModifier(UnitMods.StatAgility, UnitModifierType.BaseValue, val, apply); + HandleStatModifier(UnitMods.StatStrength, UnitModifierType.BaseValue, val, apply); + HandleStatModifier(UnitMods.StatIntellect, UnitModifierType.BaseValue, val, apply); + ApplyStatBuffMod(Stats.Agility, MathFunctions.CalculatePct(val, GetModifierValue(UnitMods.StatAgility, UnitModifierType.BasePCTExcludeCreate)), apply); + ApplyStatBuffMod(Stats.Strength, MathFunctions.CalculatePct(val, GetModifierValue(UnitMods.StatStrength, UnitModifierType.BasePCTExcludeCreate)), apply); + ApplyStatBuffMod(Stats.Intellect, MathFunctions.CalculatePct(val, GetModifierValue(UnitMods.StatIntellect, UnitModifierType.BasePCTExcludeCreate)), apply); + break; + case ItemModType.AgiStr: + HandleStatModifier(UnitMods.StatAgility, UnitModifierType.BaseValue, val, apply); + HandleStatModifier(UnitMods.StatStrength, UnitModifierType.BaseValue, val, apply); + ApplyStatBuffMod(Stats.Agility, MathFunctions.CalculatePct(val, GetModifierValue(UnitMods.StatAgility, UnitModifierType.BasePCTExcludeCreate)), apply); + ApplyStatBuffMod(Stats.Strength, MathFunctions.CalculatePct(val, GetModifierValue(UnitMods.StatStrength, UnitModifierType.BasePCTExcludeCreate)), apply); + break; + case ItemModType.AgiInt: + HandleStatModifier(UnitMods.StatAgility, UnitModifierType.BaseValue, val, apply); + HandleStatModifier(UnitMods.StatIntellect, UnitModifierType.BaseValue, val, apply); + ApplyStatBuffMod(Stats.Agility, MathFunctions.CalculatePct(val, GetModifierValue(UnitMods.StatAgility, UnitModifierType.BasePCTExcludeCreate)), apply); + ApplyStatBuffMod(Stats.Intellect, MathFunctions.CalculatePct(val, GetModifierValue(UnitMods.StatIntellect, UnitModifierType.BasePCTExcludeCreate)), apply); + break; + case ItemModType.StrInt: + HandleStatModifier(UnitMods.StatStrength, UnitModifierType.BaseValue, val, apply); + HandleStatModifier(UnitMods.StatIntellect, UnitModifierType.BaseValue, val, apply); + ApplyStatBuffMod(Stats.Strength, MathFunctions.CalculatePct(val, GetModifierValue(UnitMods.StatStrength, UnitModifierType.BasePCTExcludeCreate)), apply); + ApplyStatBuffMod(Stats.Intellect, MathFunctions.CalculatePct(val, GetModifierValue(UnitMods.StatIntellect, UnitModifierType.BasePCTExcludeCreate)), apply); + break; + } + } + + uint armor = item.GetArmor(this); + if (armor != 0) + { + UnitModifierType modType = UnitModifierType.TotalValue; + if (proto.GetClass() == ItemClass.Armor) + { + switch ((ItemSubClassArmor)proto.GetSubClass()) + { + case ItemSubClassArmor.Cloth: + case ItemSubClassArmor.Leather: + case ItemSubClassArmor.Mail: + case ItemSubClassArmor.Plate: + case ItemSubClassArmor.Shield: + modType = UnitModifierType.BaseValue; + break; + } + } + HandleStatModifier(UnitMods.Armor, modType, armor, apply); + } + + //if (proto.GetArmorDamageModifier() > 0) + // HandleStatModifier(UnitMods.Armor, UnitModifierType.TotalValue, (float)proto.GetArmorDamageModifier(), apply); + + WeaponAttackType attType = WeaponAttackType.BaseAttack; + if (slot == EquipmentSlot.MainHand && (proto.GetInventoryType() == InventoryType.Ranged || proto.GetInventoryType() == InventoryType.RangedRight)) + { + attType = WeaponAttackType.RangedAttack; + } + else if (slot == EquipmentSlot.OffHand) + { + attType = WeaponAttackType.OffAttack; + } + + if (CanUseAttackType(attType)) + _ApplyWeaponDamage(slot, item, apply); + } + + void ApplyItemEquipSpell(Item item, bool apply, bool formChange = false) + { + if (item == null) + return; + + ItemTemplate proto = item.GetTemplate(); + if (proto == null) + return; + + for (byte i = 0; i < proto.Effects.Count; ++i) + { + var spellData = proto.Effects[i]; + + // no spell + if (spellData.SpellID == 0) + continue; + + // wrong triggering type + if (apply && spellData.Trigger != ItemSpelltriggerType.OnEquip) + continue; + + // check if it is valid spell + SpellInfo spellproto = Global.SpellMgr.GetSpellInfo(spellData.SpellID); + if (spellproto == null) + continue; + + if (spellproto.HasAura(GetMap().GetDifficultyID(), AuraType.ModXpPct) || !GetSession().GetCollectionMgr().CanApplyHeirloomXpBonus(item.GetEntry(), getLevel()) + && Global.DB2Mgr.GetHeirloomByItemId(item.GetEntry()) != null) + continue; + + if (spellData.ChrSpecializationID != 0 && spellData.ChrSpecializationID != GetUInt32Value(PlayerFields.CurrentSpecId)) + continue; + + ApplyEquipSpell(spellproto, item, apply, formChange); + } + } + + public void ApplyEquipSpell(SpellInfo spellInfo, Item item, bool apply, bool formChange = false) + { + if (apply) + { + // Cannot be used in this stance/form + if (spellInfo.CheckShapeshift(GetShapeshiftForm()) != SpellCastResult.SpellCastOk) + return; + + if (formChange) // check aura active state from other form + { + var range = GetAppliedAuras(); + foreach (var pair in range) + { + if (pair.Key != spellInfo.Id) + continue; + + if (item == null || pair.Value.GetBase().GetCastItemGUID() == item.GetGUID()) + return; + } + } + + Log.outDebug(LogFilter.Player, "WORLD: cast {0} Equip spellId - {1}", (item != null ? "item" : "itemset"), spellInfo.Id); + + CastSpell(this, spellInfo, true, item); + } + else + { + if (formChange) // check aura compatibility + { + // Cannot be used in this stance/form + if (spellInfo.CheckShapeshift(GetShapeshiftForm()) == SpellCastResult.SpellCastOk) + return; // and remove only not compatible at form change + } + + if (item != null) + RemoveAurasDueToItemSpell(spellInfo.Id, item.GetGUID()); // un-apply all spells, not only at-equipped + else + RemoveAurasDueToSpell(spellInfo.Id); // un-apply spell (item set case) + } + } + + void ApplyEquipCooldown(Item pItem) + { + ItemTemplate proto = pItem.GetTemplate(); + if (proto.GetFlags().HasAnyFlag(ItemFlags.NoEquipCooldown)) + return; + + for (byte i = 0; i < proto.Effects.Count; ++i) + { + var effectData = proto.Effects[i]; + + // no spell + if (effectData.SpellID == 0) + continue; + + // wrong triggering type + if (effectData.Trigger != ItemSpelltriggerType.OnUse) + continue; + + // Don't replace longer cooldowns by equip cooldown if we have any. + if (GetSpellHistory().GetRemainingCooldown(Global.SpellMgr.GetSpellInfo(effectData.SpellID)) > 30 * Time.InMilliseconds) + continue; + + GetSpellHistory().AddCooldown(effectData.SpellID, pItem.GetEntry(), TimeSpan.FromSeconds(30)); + + ItemCooldown data = new ItemCooldown(); + data.ItemGuid = pItem.GetGUID(); + data.SpellID = effectData.SpellID; + data.Cooldown = 30 * Time.InMilliseconds; //Always 30secs? + SendPacket(data); + } + } + + void _RemoveAllItemMods() + { + Log.outDebug(LogFilter.Player, "_RemoveAllItemMods start."); + + for (byte i = 0; i < InventorySlots.BagEnd; ++i) + { + if (m_items[i] != null) + { + ItemTemplate proto = m_items[i].GetTemplate(); + if (proto == null) + continue; + + // item set bonuses not dependent from item broken state + if (proto.GetItemSet() != 0) + Item.RemoveItemsSetItem(this, proto); + + if (m_items[i].IsBroken() || !CanUseAttackType(GetAttackBySlot(i, m_items[i].GetTemplate().GetInventoryType()))) + continue; + + ApplyItemEquipSpell(m_items[i], false); + ApplyEnchantment(m_items[i], false); + ApplyArtifactPowers(m_items[i], false); + } + } + + for (byte i = 0; i < InventorySlots.BagEnd; ++i) + { + if (m_items[i] != null) + { + if (m_items[i].IsBroken() || !CanUseAttackType(GetAttackBySlot(i, m_items[i].GetTemplate().GetInventoryType()))) + continue; + + ApplyItemDependentAuras(m_items[i], false); + _ApplyItemBonuses(m_items[i], i, false); + } + } + + Log.outDebug(LogFilter.Player, "_RemoveAllItemMods complete."); + } + + void _ApplyAllItemMods() + { + Log.outDebug(LogFilter.Player, "_ApplyAllItemMods start."); + + for (byte i = 0; i < InventorySlots.BagEnd; ++i) + { + if (m_items[i] != null) + { + if (m_items[i].IsBroken() || !CanUseAttackType(GetAttackBySlot(i, m_items[i].GetTemplate().GetInventoryType()))) + continue; + + ApplyItemDependentAuras(m_items[i], true); + _ApplyItemBonuses(m_items[i], i, true); + } + } + + for (byte i = 0; i < InventorySlots.BagEnd; ++i) + { + if (m_items[i] != null) + { + ItemTemplate proto = m_items[i].GetTemplate(); + if (proto == null) + continue; + + // item set bonuses not dependent from item broken state + if (proto.GetItemSet() != 0) + Item.AddItemsSetItem(this, m_items[i]); + + if (m_items[i].IsBroken() || !CanUseAttackType(GetAttackBySlot(i, m_items[i].GetTemplate().GetInventoryType()))) + continue; + + ApplyItemEquipSpell(m_items[i], true); + ApplyArtifactPowers(m_items[i], true); + ApplyEnchantment(m_items[i], true); + } + } + + Log.outDebug(LogFilter.Player, "_ApplyAllItemMods complete."); + } + + public void _ApplyAllLevelScaleItemMods(bool apply) + { + for (byte i = 0; i < InventorySlots.BagEnd; ++i) + { + if (m_items[i] != null) + { + if (!CanUseAttackType(GetAttackBySlot(i, m_items[i].GetTemplate().GetInventoryType()))) + continue; + + _ApplyItemMods(m_items[i], i, apply); + } + } + } + + public ObjectGuid GetLootWorldObjectGUID(ObjectGuid lootObjectGuid) + { + var guid = m_AELootView.LookupByKey(lootObjectGuid); + if (guid != null) + return guid; + + return ObjectGuid.Empty; + } + + public void RemoveAELootedObject(ObjectGuid lootObjectGuid) + { + m_AELootView.Remove(lootObjectGuid); + } + + public bool HasLootWorldObjectGUID(ObjectGuid lootWorldObjectGuid) + { + return m_AELootView.Any(lootView => + { + return lootView.Value == lootWorldObjectGuid; + }); + } + + //Inventory + public bool IsInventoryPos(ushort pos) + { + return IsInventoryPos((byte)(pos >> 8), (byte)(pos & 255)); + } + public static bool IsInventoryPos(byte bag, byte slot) + { + if (bag == InventorySlots.Bag0 && slot == ItemConst.NullSlot) + return true; + if (bag == InventorySlots.Bag0 && (slot >= InventorySlots.ItemStart && slot < InventorySlots.ItemEnd)) + return true; + if (bag >= InventorySlots.BagStart && bag < InventorySlots.BagEnd) + return true; + if (bag == InventorySlots.Bag0 && (slot >= InventorySlots.ReagentStart && slot < InventorySlots.ReagentEnd)) + return true; + if (bag == InventorySlots.Bag0 && (slot >= InventorySlots.ChildEquipmentStart && slot < InventorySlots.ChildEquipmentEnd)) + return true; + return false; + } + InventoryResult CanStoreItem_InInventorySlots(byte slot_begin, byte slot_end, List dest, ItemTemplate pProto, ref uint count, bool merge, Item pSrcItem, byte skip_bag, byte skip_slot) + { + //this is never called for non-bag slots so we can do this + if (pSrcItem != null && pSrcItem.IsNotEmptyBag()) + return InventoryResult.DestroyNonemptyBag; + + for (var j = slot_begin; j < slot_end; j++) + { + // skip specific slot already processed in first called CanStoreItem_InSpecificSlot + if (InventorySlots.Bag0 == skip_bag && j == skip_slot) + continue; + + Item pItem2 = GetItemByPos(InventorySlots.Bag0, j); + + // ignore move item (this slot will be empty at move) + if (pItem2 == pSrcItem) + pItem2 = null; + + // if merge skip empty, if !merge skip non-empty + if ((pItem2 != null) != merge) + continue; + + uint need_space = pProto.GetMaxStackSize(); + + if (pItem2 != null) + { + // can be merged at least partly + InventoryResult res = pItem2.CanBeMergedPartlyWith(pProto); + if (res != InventoryResult.Ok) + continue; + + // descrease at current stacksize + need_space -= pItem2.GetCount(); + } + + if (need_space > count) + need_space = count; + + ItemPosCount newPosition = new ItemPosCount((ushort)(InventorySlots.Bag0 << 8 | j), need_space); + if (!newPosition.isContainedIn(dest)) + { + dest.Add(newPosition); + count -= need_space; + + if (count == 0) + return InventoryResult.Ok; + } + } + return InventoryResult.Ok; + } + InventoryResult CanStoreItem_InSpecificSlot(byte bag, byte slot, List dest, ItemTemplate pProto, ref uint count, bool swap, Item pSrcItem) + { + Item pItem2 = GetItemByPos(bag, slot); + + // ignore move item (this slot will be empty at move) + if (pItem2 == pSrcItem) + pItem2 = null; + + uint need_space; + + if (pSrcItem) + { + if (pSrcItem.IsNotEmptyBag() && !IsBagPos((ushort)((ushort)bag << 8 | slot))) + return InventoryResult.DestroyNonemptyBag; + + if (pSrcItem.HasFlag(ItemFields.Flags, ItemFieldFlags.Child) && !IsEquipmentPos(bag, slot) && !IsChildEquipmentPos(bag, slot)) + return InventoryResult.WrongBagType3; + + if (!pSrcItem.HasFlag(ItemFields.Flags, ItemFieldFlags.Child) && IsChildEquipmentPos(bag, slot)) + return InventoryResult.WrongBagType3; + } + + // empty specific slot - check item fit to slot + if (pItem2 == null || swap) + { + if (bag == InventorySlots.Bag0) + { + // prevent cheating + if ((slot >= InventorySlots.BuyBackStart && slot < InventorySlots.BuyBackEnd) || slot >= (byte)PlayerSlots.End) + return InventoryResult.WrongBagType; + } + else + { + Bag pBag = GetBagByPos(bag); + if (pBag == null) + return InventoryResult.WrongBagType; + + ItemTemplate pBagProto = pBag.GetTemplate(); + if (pBagProto == null) + return InventoryResult.WrongBagType; + + if (slot >= pBagProto.GetContainerSlots()) + return InventoryResult.WrongBagType; + + if (!Item.ItemCanGoIntoBag(pProto, pBagProto)) + return InventoryResult.WrongBagType; + } + + // non empty stack with space + need_space = pProto.GetMaxStackSize(); + } + // non empty slot, check item type + else + { + // can be merged at least partly + InventoryResult res = pItem2.CanBeMergedPartlyWith(pProto); + if (res != InventoryResult.Ok) + return res; + + // free stack space or infinity + need_space = pProto.GetMaxStackSize() - pItem2.GetCount(); + } + + if (need_space > count) + need_space = count; + + ItemPosCount newPosition = new ItemPosCount((ushort)(bag << 8 | slot), need_space); + if (!newPosition.isContainedIn(dest)) + { + dest.Add(newPosition); + count -= need_space; + } + return InventoryResult.Ok; + } + public void MoveItemFromInventory(byte bag, byte slot, bool update) + { + Item it = GetItemByPos(bag, slot); + if (it != null) + { + ItemRemovedQuestCheck(it.GetEntry(), it.GetCount()); + RemoveItem(bag, slot, update); + it.SetNotRefundable(this, false, null, false); + Item.RemoveItemFromUpdateQueueOf(it, this); + GetSession().GetCollectionMgr().RemoveTemporaryAppearance(it); + if (it.IsInWorld) + { + it.RemoveFromWorld(); + it.DestroyForPlayer(this); + } + } + } + public void MoveItemToInventory(List dest, Item pItem, bool update, bool in_characterInventoryDB = false) + { + // update quest counters + ItemAddedQuestCheck(pItem.GetEntry(), pItem.GetCount()); + UpdateCriteria(CriteriaTypes.ReceiveEpicItem, pItem.GetEntry(), pItem.GetCount()); + + // store item + Item pLastItem = StoreItem(dest, pItem, update); + + // only set if not merged to existed stack + if (pLastItem == pItem) + { + // update owner for last item (this can be original item with wrong owner + if (pLastItem.GetOwnerGUID() != GetGUID()) + pLastItem.SetOwnerGUID(GetGUID()); + + // if this original item then it need create record in inventory + // in case trade we already have item in other player inventory + pLastItem.SetState(in_characterInventoryDB ? ItemUpdateState.Changed : ItemUpdateState.New, this); + + if (pLastItem.HasFlag(ItemFields.Flags, ItemFieldFlags.BopTradeable)) + AddTradeableItem(pLastItem); + } + } + + //Bank + public static bool IsBankPos(ushort pos) + { + return IsBankPos((byte)(pos >> 8), (byte)(pos & 255)); + } + public static bool IsBankPos(byte bag, byte slot) + { + if (bag == InventorySlots.Bag0 && (slot >= InventorySlots.BankItemStart && slot < InventorySlots.BankItemEnd)) + return true; + if (bag == InventorySlots.Bag0 && (slot >= InventorySlots.BankBagStart && slot < InventorySlots.BankBagEnd)) + return true; + if (bag >= InventorySlots.BankBagStart && bag < InventorySlots.BankBagEnd) + return true; + return false; + } + public InventoryResult CanBankItem(byte bag, byte slot, List dest, Item pItem, bool swap, bool not_loading = true) + { + if (pItem == null) + return swap ? InventoryResult.CantSwap : InventoryResult.ItemNotFound; + + uint count = pItem.GetCount(); + + Log.outDebug(LogFilter.Player, "STORAGE: CanBankItem bag = {0}, slot = {1}, item = {2}, count = {3}", bag, slot, pItem.GetEntry(), count); + ItemTemplate pProto = pItem.GetTemplate(); + if (pProto == null) + return swap ? InventoryResult.CantSwap : InventoryResult.ItemNotFound; + + // item used + if (pItem.m_lootGenerated) + return InventoryResult.LootGone; + + if (pItem.IsBindedNotWith(this)) + return InventoryResult.NotOwner; + + // Currency tokens are not supposed to be swapped out of their hidden bag + if (pItem.IsCurrencyToken()) + { + Log.outError(LogFilter.Player, "Possible hacking attempt: Player {0} [guid: {1}] tried to move token [guid: {2}, entry: {3}] out of the currency bag!", + GetName(), GetGUID().ToString(), pItem.GetGUID().ToString(), pProto.GetId()); + return InventoryResult.CantSwap; + } + + // check count of items (skip for auto move for same player from bank) + InventoryResult res = CanTakeMoreSimilarItems(pItem); + if (res != InventoryResult.Ok) + return res; + + // in specific slot + if (bag != ItemConst.NullBag && slot != ItemConst.NullSlot) + { + if (slot >= InventorySlots.BagStart && slot < InventorySlots.BagEnd) + { + if (!pItem.IsBag()) + return InventoryResult.WrongSlot; + + if (slot - InventorySlots.BagStart >= GetBankBagSlotCount()) + return InventoryResult.NoBankSlot; + + res = CanUseItem(pItem, not_loading); + if (res != InventoryResult.Ok) + return res; + } + + res = CanStoreItem_InSpecificSlot(bag, slot, dest, pProto, ref count, swap, pItem); + if (res != InventoryResult.Ok) + return res; + + if (count == 0) + return InventoryResult.Ok; + } + + // not specific slot or have space for partly store only in specific slot + + // in specific bag + if (bag != ItemConst.NullBag) + { + if (pItem.IsNotEmptyBag()) + return InventoryResult.BagInBag; + + // search stack in bag for merge to + if (pProto.GetMaxStackSize() != 1) + { + if (bag == InventorySlots.Bag0) + { + res = CanStoreItem_InInventorySlots(InventorySlots.BankItemStart, InventorySlots.BankItemEnd, dest, pProto, ref count, true, pItem, bag, slot); + if (res != InventoryResult.Ok) + return res; + + if (count == 0) + return InventoryResult.Ok; + } + else + { + res = CanStoreItem_InBag(bag, dest, pProto, ref count, true, false, pItem, ItemConst.NullBag, slot); + if (res != InventoryResult.Ok) + res = CanStoreItem_InBag(bag, dest, pProto, ref count, true, true, pItem, ItemConst.NullBag, slot); + + if (res != InventoryResult.Ok) + return res; + + if (count == 0) + return InventoryResult.Ok; + } + } + + // search free slot in bag + if (bag == InventorySlots.Bag0) + { + res = CanStoreItem_InInventorySlots(InventorySlots.BankItemStart, InventorySlots.BankItemEnd, dest, pProto, ref count, false, pItem, bag, slot); + if (res != InventoryResult.Ok) + return res; + + if (count == 0) + return InventoryResult.Ok; + } + else + { + res = CanStoreItem_InBag(bag, dest, pProto, ref count, false, false, pItem, ItemConst.NullBag, slot); + if (res != InventoryResult.Ok) + res = CanStoreItem_InBag(bag, dest, pProto, ref count, false, true, pItem, ItemConst.NullBag, slot); + + if (res != InventoryResult.Ok) + return res; + + if (count == 0) + return InventoryResult.Ok; + } + } + + // not specific bag or have space for partly store only in specific bag + + // search stack for merge to + if (pProto.GetMaxStackSize() != 1) + { + // in slots + res = CanStoreItem_InInventorySlots(InventorySlots.BankItemStart, InventorySlots.BankItemEnd, dest, pProto, ref count, true, pItem, bag, slot); + if (res != InventoryResult.Ok) + return res; + + if (count == 0) + return InventoryResult.Ok; + + // in special bags + if (pProto.GetBagFamily() != BagFamilyMask.None) + { + for (byte i = InventorySlots.BankBagStart; i < InventorySlots.BankBagEnd; i++) + { + res = CanStoreItem_InBag(i, dest, pProto, ref count, true, false, pItem, bag, slot); + if (res != InventoryResult.Ok) + continue; + + if (count == 0) + return InventoryResult.Ok; + } + } + + for (byte i = InventorySlots.BankBagStart; i < InventorySlots.BankBagEnd; i++) + { + res = CanStoreItem_InBag(i, dest, pProto, ref count, true, true, pItem, bag, slot); + if (res != InventoryResult.Ok) + continue; + + if (count == 0) + return InventoryResult.Ok; + } + } + + // search free place in special bag + if (pProto.GetBagFamily() != BagFamilyMask.None) + { + for (byte i = InventorySlots.BagStart; i < InventorySlots.BankBagEnd; i++) + { + res = CanStoreItem_InBag(i, dest, pProto, ref count, false, false, pItem, bag, slot); + if (res != InventoryResult.Ok) + continue; + + if (count == 0) + return InventoryResult.Ok; + } + } + + // search free space + res = CanStoreItem_InInventorySlots(InventorySlots.BankItemStart, InventorySlots.BankItemEnd, dest, pProto, ref count, false, pItem, bag, slot); + if (res != InventoryResult.Ok) + return res; + + if (count == 0) + return InventoryResult.Ok; + + for (byte i = InventorySlots.BankBagStart; i < InventorySlots.BankBagEnd; i++) + { + res = CanStoreItem_InBag(i, dest, pProto, ref count, false, true, pItem, bag, slot); + if (res != InventoryResult.Ok) + continue; + + if (count == 0) + return InventoryResult.Ok; + } + return InventoryResult.BankFull; + } + public Item BankItem(List dest, Item pItem, bool update) + { + return StoreItem(dest, pItem, update); + } + + //Bags + public Bag GetBagByPos(byte bag) + { + if ((bag >= InventorySlots.BagStart && bag < InventorySlots.BagEnd) + || (bag >= InventorySlots.BankBagStart && bag < InventorySlots.BankBagEnd)) + { + Item item = GetItemByPos(InventorySlots.Bag0, bag); + if (item != null) + return item.ToBag(); + } + return null; + } + public static bool IsBagPos(ushort pos) + { + byte bag = (byte)(pos >> 8); + byte slot = (byte)(pos & 255); + if (bag == InventorySlots.Bag0 && (slot >= InventorySlots.BagStart && slot < InventorySlots.BagEnd)) + return true; + if (bag == InventorySlots.Bag0 && (slot >= InventorySlots.BankBagStart && slot < InventorySlots.BankBagEnd)) + return true; + return false; + } + InventoryResult CanStoreItem_InBag(byte bag, List dest, ItemTemplate pProto, ref uint count, bool merge, bool non_specialized, Item pSrcItem, byte skip_bag, byte skip_slot) + { + // skip specific bag already processed in first called CanStoreItem_InBag + if (bag == skip_bag) + return InventoryResult.WrongBagType; + + // skip not existed bag or self targeted bag + Bag pBag = GetBagByPos(bag); + if (pBag == null || pBag == pSrcItem) + return InventoryResult.WrongBagType; + + if (pSrcItem) + { + if (pSrcItem.IsNotEmptyBag()) + return InventoryResult.DestroyNonemptyBag; + + if (pSrcItem.HasFlag(ItemFields.Flags, ItemFieldFlags.Child)) + return InventoryResult.WrongBagType3; + } + + ItemTemplate pBagProto = pBag.GetTemplate(); + if (pBagProto == null) + return InventoryResult.WrongBagType; + + // specialized bag mode or non-specilized + if (non_specialized != (pBagProto.GetClass() == ItemClass.Container && pBagProto.GetSubClass() == (uint)ItemSubClassContainer.Container)) + return InventoryResult.WrongBagType; + + if (!Item.ItemCanGoIntoBag(pProto, pBagProto)) + return InventoryResult.WrongBagType; + + for (byte j = 0; j < pBag.GetBagSize(); j++) + { + // skip specific slot already processed in first called CanStoreItem_InSpecificSlot + if (j == skip_slot) + continue; + + Item pItem2 = GetItemByPos(bag, j); + + // ignore move item (this slot will be empty at move) + if (pItem2 == pSrcItem) + pItem2 = null; + + // if merge skip empty, if !merge skip non-empty + if ((pItem2 != null) != merge) + continue; + + uint need_space = pProto.GetMaxStackSize(); + + if (pItem2 != null) + { + // can be merged at least partly + InventoryResult res = pItem2.CanBeMergedPartlyWith(pProto); + if (res != InventoryResult.Ok) + continue; + + // descrease at current stacksize + need_space -= pItem2.GetCount(); + } + + if (need_space > count) + need_space = count; + + ItemPosCount newPosition = new ItemPosCount((ushort)(bag << 8 | j), need_space); + if (!newPosition.isContainedIn(dest)) + { + dest.Add(newPosition); + count -= need_space; + + if (count == 0) + return InventoryResult.Ok; + } + } + + return InventoryResult.Ok; + } + + //Equipment + public static bool IsEquipmentPos(ushort pos) + { + return IsEquipmentPos((byte)(pos >> 8), (byte)(pos & 255)); + } + public static bool IsEquipmentPos(byte bag, byte slot) + { + if (bag == InventorySlots.Bag0 && (slot < EquipmentSlot.End)) + return true; + if (bag == InventorySlots.Bag0 && (slot >= InventorySlots.BagStart && slot < InventorySlots.BagEnd)) + return true; + return false; + } + byte FindEquipSlot(ItemTemplate proto, uint slot, bool swap) + { + byte[] slots = new byte[4]; + slots[0] = ItemConst.NullSlot; + slots[1] = ItemConst.NullSlot; + slots[2] = ItemConst.NullSlot; + slots[3] = ItemConst.NullSlot; + switch (proto.GetInventoryType()) + { + case InventoryType.Head: + slots[0] = EquipmentSlot.Head; + break; + case InventoryType.Neck: + slots[0] = EquipmentSlot.Neck; + break; + case InventoryType.Shoulders: + slots[0] = EquipmentSlot.Shoulders; + break; + case InventoryType.Body: + slots[0] = EquipmentSlot.Shirt; + break; + case InventoryType.Chest: + slots[0] = EquipmentSlot.Chest; + break; + case InventoryType.Robe: + slots[0] = EquipmentSlot.Chest; + break; + case InventoryType.Waist: + slots[0] = EquipmentSlot.Waist; + break; + case InventoryType.Legs: + slots[0] = EquipmentSlot.Legs; + break; + case InventoryType.Feet: + slots[0] = EquipmentSlot.Feet; + break; + case InventoryType.Wrists: + slots[0] = EquipmentSlot.Wrist; + break; + case InventoryType.Hands: + slots[0] = EquipmentSlot.Hands; + break; + case InventoryType.Finger: + slots[0] = EquipmentSlot.Finger1; + slots[1] = EquipmentSlot.Finger2; + break; + case InventoryType.Trinket: + slots[0] = EquipmentSlot.Trinket1; + slots[1] = EquipmentSlot.Trinket2; + break; + case InventoryType.Cloak: + slots[0] = EquipmentSlot.Cloak; + break; + case InventoryType.Weapon: + { + slots[0] = EquipmentSlot.MainHand; + + // suggest offhand slot only if know dual wielding + // (this will be replace mainhand weapon at auto equip instead unwonted "you don't known dual wielding" ... + if (CanDualWield()) + slots[1] = EquipmentSlot.OffHand; + break; + } + case InventoryType.Shield: + slots[0] = EquipmentSlot.OffHand; + break; + case InventoryType.Ranged: + slots[0] = EquipmentSlot.MainHand; + break; + case InventoryType.Weapon2Hand: + slots[0] = EquipmentSlot.MainHand; + if (CanDualWield() && CanTitanGrip()) + slots[1] = EquipmentSlot.OffHand; + break; + case InventoryType.Tabard: + slots[0] = EquipmentSlot.Tabard; + break; + case InventoryType.WeaponMainhand: + slots[0] = EquipmentSlot.MainHand; + break; + case InventoryType.WeaponOffhand: + slots[0] = EquipmentSlot.OffHand; + break; + case InventoryType.Holdable: + slots[0] = EquipmentSlot.OffHand; + break; + case InventoryType.RangedRight: + slots[0] = EquipmentSlot.MainHand; + break; + case InventoryType.Bag: + slots[0] = InventorySlots.BagStart + 0; + slots[1] = InventorySlots.BagStart + 1; + slots[2] = InventorySlots.BagStart + 2; + slots[3] = InventorySlots.BagStart + 3; + break; + default: + return ItemConst.NullSlot; + } + + if (slot != ItemConst.NullSlot) + { + if (swap || GetItemByPos(InventorySlots.Bag0, (byte)slot) == null) + for (byte i = 0; i < 4; ++i) + if (slots[i] == slot) + return (byte)slot; + } + else + { + // search free slot at first + for (byte i = 0; i < 4; ++i) + if (slots[i] != ItemConst.NullSlot && GetItemByPos(InventorySlots.Bag0, slots[i]) == null) + // in case 2hand equipped weapon (without titan grip) offhand slot empty but not free + if (slots[i] != EquipmentSlot.OffHand || !IsTwoHandUsed()) + return slots[i]; + + // if not found free and can swap return first appropriate from used + for (byte i = 0; i < 4; ++i) + if (slots[i] != ItemConst.NullSlot && swap) + return slots[i]; + } + + // no free position + return ItemConst.NullSlot; + } + InventoryResult CanEquipNewItem(byte slot, out ushort dest, uint item, bool swap) + { + dest = 0; + Item pItem = Item.CreateItem(item, 1, this); + if (pItem != null) + { + InventoryResult result = CanEquipItem(slot, out dest, pItem, swap); + return result; + } + + return InventoryResult.ItemNotFound; + } + public InventoryResult CanEquipItem(byte slot, out ushort dest, Item pItem, bool swap, bool not_loading = true) + { + dest = 0; + if (pItem != null) + { + Log.outDebug(LogFilter.Player, "STORAGE: CanEquipItem slot = {0}, item = {1}, count = {2}", slot, pItem.GetEntry(), pItem.GetCount()); + ItemTemplate pProto = pItem.GetTemplate(); + if (pProto != null) + { + // item used + if (pItem.m_lootGenerated) + return InventoryResult.LootGone; + + if (pItem.IsBindedNotWith(this)) + return InventoryResult.NotOwner; + + // check count of items (skip for auto move for same player from bank) + InventoryResult res = CanTakeMoreSimilarItems(pItem); + if (res != InventoryResult.Ok) + return res; + + // check this only in game + if (not_loading) + { + // May be here should be more stronger checks; STUNNED checked + // ROOT, CONFUSED, DISTRACTED, FLEEING this needs to be checked. + if (HasUnitState(UnitState.Stunned)) + return InventoryResult.GenericStunned; + + // do not allow equipping gear except weapons, offhands, projectiles, relics in + // - combat + // - in-progress arenas + if (!pProto.CanChangeEquipStateInCombat()) + { + if (IsInCombat()) + return InventoryResult.NotInCombat; + Battleground bg = GetBattleground(); + if (bg) + if (bg.isArena() && bg.GetStatus() == BattlegroundStatus.InProgress) + return InventoryResult.NotDuringArenaMatch; + } + + if (IsInCombat() && (pProto.GetClass() == ItemClass.Weapon || pProto.GetInventoryType() == InventoryType.Relic) && m_weaponChangeTimer != 0) + return InventoryResult.ClientLockedOut; // maybe exist better err + + if (IsNonMeleeSpellCast(false)) + return InventoryResult.ClientLockedOut; + } + + ScalingStatDistributionRecord ssd = CliDB.ScalingStatDistributionStorage.LookupByKey(pItem.GetScalingStatDistribution()); + // check allowed level (extend range to upper values if MaxLevel more or equal max player level, this let GM set high level with 1...max range items) + if (ssd != null && ssd.MaxLevel < WorldConfig.GetIntValue(WorldCfg.MaxPlayerLevel) && ssd.MaxLevel < getLevel() && Global.DB2Mgr.GetHeirloomByItemId(pProto.GetId()) == null) + return InventoryResult.NotEquippable; + + byte eslot = FindEquipSlot(pProto, slot, swap); + if (eslot == ItemConst.NullSlot) + return InventoryResult.NotEquippable; + + res = CanUseItem(pItem, not_loading); + if (res != InventoryResult.Ok) + return res; + + if (!swap && GetItemByPos(InventorySlots.Bag0, eslot) != null) + return InventoryResult.NoSlotAvailable; + + // if swap ignore item (equipped also) + InventoryResult res2 = CanEquipUniqueItem(pItem, swap ? eslot : ItemConst.NullSlot); + if (res2 != InventoryResult.Ok) + return res2; + + // check unique-equipped special item classes + if (pProto.GetClass() == ItemClass.Quiver) + for (byte i = InventorySlots.BagStart; i < InventorySlots.BagEnd; ++i) + { + Item pBag = GetItemByPos(InventorySlots.Bag0, i); + if (pBag != null) + { + if (pBag != pItem) + { + ItemTemplate pBagProto = pBag.GetTemplate(); + if (pBagProto != null) + if (pBagProto.GetClass() == pProto.GetClass() && (!swap || pBag.GetSlot() != eslot)) + return (pBagProto.GetSubClass() == (uint)ItemSubClassQuiver.AmmoPouch) + ? InventoryResult.OnlyOneAmmo + : InventoryResult.OnlyOneQuiver; + } + } + } + + InventoryType type = pProto.GetInventoryType(); + + if (eslot == EquipmentSlot.OffHand) + { + // Do not allow polearm to be equipped in the offhand (rare case for the only 1h polearm 41750) + if (type == InventoryType.Weapon && pProto.GetSubClass() == (uint)ItemSubClassWeapon.Polearm) + return InventoryResult.TwoHandSkillNotFound; + else if (type == InventoryType.Weapon) + { + if (!CanDualWield()) + return InventoryResult.TwoHandSkillNotFound; + } + else if (type == InventoryType.WeaponOffhand) + { + if (!CanDualWield() && !pProto.GetFlags3().HasAnyFlag(ItemFlags3.AlwaysAllowDualWield)) + return InventoryResult.TwoHandSkillNotFound; + } + else if (type == InventoryType.Weapon2Hand) + { + if (!CanDualWield() || !CanTitanGrip()) + return InventoryResult.TwoHandSkillNotFound; + } + + if (IsTwoHandUsed()) + return InventoryResult.Equipped2handed; + } + + // equip two-hand weapon case (with possible unequip 2 items) + if (type == InventoryType.Weapon2Hand) + { + if (eslot == EquipmentSlot.OffHand) + { + if (!CanTitanGrip()) + return InventoryResult.NotEquippable; + } + else if (eslot != EquipmentSlot.MainHand) + return InventoryResult.NotEquippable; + + if (!CanTitanGrip()) + { + // offhand item must can be stored in inventory for offhand item and it also must be unequipped + Item offItem = GetItemByPos(InventorySlots.Bag0, EquipmentSlot.OffHand); + List off_dest = new List(); + if (offItem != null && (!not_loading || CanUnequipItem(((int)InventorySlots.Bag0 << 8) | (int)EquipmentSlot.OffHand, false) != InventoryResult.Ok || + CanStoreItem(ItemConst.NullBag, ItemConst.NullSlot, off_dest, offItem, false) != InventoryResult.Ok)) + return swap ? InventoryResult.CantSwap : InventoryResult.InvFull; + } + } + dest = (ushort)(((uint)InventorySlots.Bag0 << 8) | eslot); + return InventoryResult.Ok; + } + } + return !swap ? InventoryResult.ItemNotFound : InventoryResult.CantSwap; + } + public InventoryResult CanEquipChildItem(Item parentItem) + { + Item childItem = GetChildItemByGuid(parentItem.GetChildItem()); + if (!childItem) + return InventoryResult.Ok; + + ItemChildEquipmentRecord childEquipement = Global.DB2Mgr.GetItemChildEquipment(parentItem.GetEntry()); + if (childEquipement == null) + return InventoryResult.Ok; + + Item dstItem = GetItemByPos(InventorySlots.Bag0, childEquipement.AltEquipmentSlot); + if (!dstItem) + return InventoryResult.Ok; + + ushort childDest = (ushort)((InventorySlots.Bag0 << 8) | childEquipement.AltEquipmentSlot); + InventoryResult msg = CanUnequipItem(childDest, !childItem.IsBag()); + if (msg != InventoryResult.Ok) + return msg; + + // check dest.src move possibility + ushort src = parentItem.GetPos(); + List dest = new List(); + if (IsInventoryPos(src)) + { + msg = CanStoreItem(parentItem.GetBagSlot(), ItemConst.NullSlot, dest, dstItem, true); + if (msg != InventoryResult.Ok) + msg = CanStoreItem(ItemConst.NullBag, ItemConst.NullSlot, dest, dstItem, true); + } + else if (IsBankPos(src)) + { + msg = CanBankItem(parentItem.GetBagSlot(), ItemConst.NullSlot, dest, dstItem, true); + if (msg != InventoryResult.Ok) + msg = CanBankItem(ItemConst.NullBag, ItemConst.NullSlot, dest, dstItem, true); + } + else if (IsEquipmentPos(src)) + return InventoryResult.CantSwap; + + return msg; + } + public InventoryResult CanEquipUniqueItem(Item pItem, byte eslot, uint limit_count = 1) + { + ItemTemplate pProto = pItem.GetTemplate(); + + // proto based limitations + InventoryResult res = CanEquipUniqueItem(pProto, eslot, limit_count); + if (res != InventoryResult.Ok) + return res; + + // check unique-equipped on gems + foreach (ItemDynamicFieldGems gemData in pItem.GetGems()) + { + ItemTemplate pGem = Global.ObjectMgr.GetItemTemplate(gemData.ItemId); + if (pGem == null) + continue; + + // include for check equip another gems with same limit category for not equipped item (and then not counted) + uint gem_limit_count = (uint)(!pItem.IsEquipped() && pGem.GetItemLimitCategory() != 0 ? pItem.GetGemCountWithLimitCategory(pGem.GetItemLimitCategory()) : 1); + + InventoryResult ress = CanEquipUniqueItem(pGem, eslot, gem_limit_count); + if (ress != InventoryResult.Ok) + return ress; + } + + return InventoryResult.Ok; + } + public InventoryResult CanEquipUniqueItem(ItemTemplate itemProto, byte except_slot, uint limit_count = 1) + { + // check unique-equipped on item + if (Convert.ToBoolean(itemProto.GetFlags() & ItemFlags.UniqueEquippable)) + { + // there is an equip limit on this item + if (HasItemOrGemWithIdEquipped(itemProto.GetId(), 1, except_slot)) + return InventoryResult.ItemUniqueEquippable; + } + + // check unique-equipped limit + if (itemProto.GetItemLimitCategory() != 0) + { + ItemLimitCategoryRecord limitEntry = CliDB.ItemLimitCategoryStorage.LookupByKey(itemProto.GetItemLimitCategory()); + if (limitEntry == null) + return InventoryResult.NotEquippable; + + // NOTE: limitEntry.mode not checked because if item have have-limit then it applied and to equip case + + if (limit_count > limitEntry.Quantity) + return InventoryResult.ItemMaxLimitCategoryEquippedExceededIs; + + // there is an equip limit on this item + if (HasItemOrGemWithLimitCategoryEquipped(itemProto.GetItemLimitCategory(), limitEntry.Quantity - limit_count + 1, except_slot)) + return InventoryResult.ItemMaxCountEquippedSocketed; + } + + return InventoryResult.Ok; + } + public InventoryResult CanUnequipItem(ushort pos, bool swap) + { + // Applied only to equipped items and bank bags + if (!IsEquipmentPos(pos) && !IsBagPos(pos)) + return InventoryResult.Ok; + + Item pItem = GetItemByPos(pos); + + // Applied only to existed equipped item + if (pItem == null) + return InventoryResult.Ok; + + Log.outDebug(LogFilter.Player, "STORAGE: CanUnequipItem slot = {0}, item = {1}, count = {2}", pos, pItem.GetEntry(), pItem.GetCount()); + + ItemTemplate pProto = pItem.GetTemplate(); + if (pProto == null) + return InventoryResult.ItemNotFound; + + // item used + if (pItem.m_lootGenerated) + return InventoryResult.LootGone; + + // do not allow unequipping gear except weapons, offhands, projectiles, relics in + // - combat + // - in-progress arenas + if (!pProto.CanChangeEquipStateInCombat()) + { + if (IsInCombat()) + return InventoryResult.NotInCombat; + Battleground bg = GetBattleground(); + if (bg) + if (bg.isArena() && bg.GetStatus() == BattlegroundStatus.InProgress) + return InventoryResult.NotDuringArenaMatch; + } + + if (!swap && pItem.IsNotEmptyBag()) + return InventoryResult.DestroyNonemptyBag; + + return InventoryResult.Ok; + } + + //Child + public static bool IsChildEquipmentPos(ushort pos) { return IsChildEquipmentPos((byte)(pos >> 8), (byte)(pos & 255)); } + + //Artifact + void ApplyArtifactPowers(Item item, bool apply) + { + foreach (ItemDynamicFieldArtifactPowers artifactPower in item.GetArtifactPowers()) + { + byte rank = artifactPower.CurrentRankWithBonus; + if (rank == 0) + continue; + + if (CliDB.ArtifactPowerStorage[artifactPower.ArtifactPowerId].Flags.HasAnyFlag(ArtifactPowerFlag.ScalesWithNumPowers)) + rank = 1; + + ArtifactPowerRankRecord artifactPowerRank = Global.DB2Mgr.GetArtifactPowerRank(artifactPower.ArtifactPowerId, (byte)(rank - 1)); + if (artifactPowerRank == null) + continue; + + ApplyArtifactPowerRank(item, artifactPowerRank, apply); + } + + ArtifactAppearanceRecord artifactAppearance = CliDB.ArtifactAppearanceStorage.LookupByKey(item.GetModifier(ItemModifier.ArtifactAppearanceId)); + if (artifactAppearance != null) + if (artifactAppearance.ShapeshiftDisplayID != 0 && GetShapeshiftForm() == (ShapeShiftForm)artifactAppearance.ModifiesShapeshiftFormDisplay) + RestoreDisplayId(); + } + + public void ApplyArtifactPowerRank(Item artifact, ArtifactPowerRankRecord artifactPowerRank, bool apply) + { + SpellInfo spellInfo = Global.SpellMgr.GetSpellInfo(artifactPowerRank.SpellID); + if (spellInfo == null) + return; + + if (spellInfo.IsPassive()) + { + AuraApplication powerAura = GetAuraApplication(artifactPowerRank.SpellID, ObjectGuid.Empty, artifact.GetGUID()); + if (powerAura != null) + { + if (apply) + { + foreach (AuraEffect auraEffect in powerAura.GetBase().GetAuraEffects()) + { + if (auraEffect == null) + continue; + + if (powerAura.HasEffect(auraEffect.GetEffIndex())) + auraEffect.ChangeAmount((int)(artifactPowerRank.Value != 0 ? artifactPowerRank.Value : auraEffect.GetSpellEffectInfo().CalcValue())); + } + } + else + RemoveAura(powerAura); + } + else if (apply) + { + Dictionary csv = new Dictionary(); + if (artifactPowerRank.Value != 0) + for (int i = 0; i < SpellConst.MaxEffects; ++i) + if (spellInfo.GetEffect((uint)i) != null) + csv.Add(SpellValueMod.BasePoint0 + i, (int)artifactPowerRank.Value); + + CastCustomSpell(artifactPowerRank.SpellID, csv, this, TriggerCastFlags.FullMask, artifact); + } + } + else + { + if (apply && !HasSpell(artifactPowerRank.SpellID)) + { + AddTemporarySpell(artifactPowerRank.SpellID); + LearnedSpells learnedSpells = new LearnedSpells(); + learnedSpells.SuppressMessaging = true; + learnedSpells.SpellID.Add(artifactPowerRank.SpellID); + SendPacket(learnedSpells); + } + else if (!apply) + { + RemoveTemporarySpell(artifactPowerRank.SpellID); + UnlearnedSpells unlearnedSpells = new UnlearnedSpells(); + unlearnedSpells.SuppressMessaging = true; + unlearnedSpells.SpellID.Add(artifactPowerRank.SpellID); + SendPacket(unlearnedSpells); + } + } + } + + public bool HasItemOrGemWithIdEquipped(uint item, uint count, byte except_slot = ItemConst.NullSlot) + { + uint tempcount = 0; + for (byte i = EquipmentSlot.Start; i < EquipmentSlot.End; ++i) + { + if (i == except_slot) + continue; + + Item pItem = GetItemByPos(InventorySlots.Bag0, i); + if (pItem != null && pItem.GetEntry() == item) + { + tempcount += pItem.GetCount(); + if (tempcount >= count) + return true; + } + } + + ItemTemplate pProto = Global.ObjectMgr.GetItemTemplate(item); + if (pProto != null && pProto.GetGemProperties() != 0) + { + for (byte i = EquipmentSlot.Start; i < EquipmentSlot.End; ++i) + { + if (i == except_slot) + continue; + + Item pItem = GetItemByPos(InventorySlots.Bag0, i); + if (pItem != null && pItem.GetSocketColor(0) != 0) + { + tempcount += pItem.GetGemCountWithID(item); + if (tempcount >= count) + return true; + } + } + } + + return false; + } + bool HasItemOrGemWithLimitCategoryEquipped(uint limitCategory, uint count, byte except_slot) + { + uint tempcount = 0; + for (byte i = EquipmentSlot.Start; i < EquipmentSlot.End; ++i) + { + if (i == except_slot) + continue; + + Item pItem = GetItemByPos(InventorySlots.Bag0, i); + if (!pItem) + continue; + + ItemTemplate pProto = pItem.GetTemplate(); + if (pProto == null) + continue; + + if (pProto.GetItemLimitCategory() == limitCategory) + { + tempcount += pItem.GetCount(); + if (tempcount >= count) + return true; + } + + if (pItem.GetSocketColor(0) != 0 || pItem.GetEnchantmentId(EnchantmentSlot.Prismatic) != 0) + { + tempcount += pItem.GetGemCountWithLimitCategory(limitCategory); + if (tempcount >= count) + return true; + } + } + + return false; + } + + //Visual + public void SetVisibleItemSlot(uint slot, Item pItem) + { + if (pItem != null) + { + SetUInt32Value(PlayerFields.VisibleItem + (int)(slot * 2), pItem.GetVisibleEntry(this)); + SetUInt16Value(PlayerFields.VisibleItem + 1 + (int)(slot * 2), 0, pItem.GetVisibleAppearanceModId(this)); + SetUInt16Value(PlayerFields.VisibleItem + 1 + (int)(slot * 2), 1, pItem.GetVisibleItemVisual(this)); + } + else + { + SetUInt32Value(PlayerFields.VisibleItem + (int)(slot * 2), 0); + SetUInt32Value(PlayerFields.VisibleItem + 1 + (int)(slot * 2), 0); + } + } + void VisualizeItem(uint slot, Item pItem) + { + if (pItem == null) + return; + + // check also BIND_WHEN_PICKED_UP and BIND_QUEST_ITEM for .additem or .additemset case by GM (not binded at adding to inventory) + if (pItem.GetBonding() == ItemBondingType.OnEquip || pItem.GetBonding() == ItemBondingType.OnAcquire || pItem.GetBonding() == ItemBondingType.Quest) + { + pItem.SetBinding(true); + if (IsInWorld) + GetSession().GetCollectionMgr().AddItemAppearance(pItem); + } + + Log.outDebug(LogFilter.Player, "STORAGE: EquipItem slot = {0}, item = {1}", slot, pItem.GetEntry()); + + m_items[slot] = pItem; + SetGuidValue(PlayerFields.InvSlotHead + (int)(slot * 4), pItem.GetGUID()); + pItem.SetGuidValue(ItemFields.Contained, GetGUID()); + pItem.SetGuidValue(ItemFields.Owner, GetGUID()); + pItem.SetSlot((byte)slot); + pItem.SetContainer(null); + + if (slot < EquipmentSlot.End) + SetVisibleItemSlot(slot, pItem); + + pItem.SetState(ItemUpdateState.Changed, this); + } + + public void DestroyItem(byte bag, byte slot, bool update) + { + Item pItem = GetItemByPos(bag, slot); + if (pItem != null) + { + Log.outDebug(LogFilter.Player, "STORAGE: DestroyItem bag = {0}, slot = {1}, item = {2}", bag, slot, pItem.GetEntry()); + // Also remove all contained items if the item is a bag. + // This if () prevents item saving crashes if the condition for a bag to be empty before being destroyed was bypassed somehow. + if (pItem.IsNotEmptyBag()) + for (byte i = 0; i < ItemConst.MaxBagSize; ++i) + DestroyItem(slot, i, update); + + if (pItem.HasFlag(ItemFields.Flags, ItemFieldFlags.Wrapped)) + { + PreparedStatement stmt = DB.Characters.GetPreparedStatement(CharStatements.DEL_GIFT); + stmt.AddValue(0, pItem.GetGUID().GetCounter()); + DB.Characters.Execute(stmt); + } + + RemoveEnchantmentDurations(pItem); + RemoveItemDurations(pItem); + + pItem.SetNotRefundable(this); + pItem.ClearSoulboundTradeable(this); + RemoveTradeableItem(pItem); + + ItemTemplate proto = pItem.GetTemplate(); + for (byte i = 0; i < proto.Effects.Count; ++i) + if (proto.Effects[i].Trigger == ItemSpelltriggerType.OnObtain && proto.Effects[i].SpellID > 0) // On obtain trigger + RemoveAurasDueToSpell(proto.Effects[i].SpellID); + + ItemRemovedQuestCheck(pItem.GetEntry(), pItem.GetCount()); + Bag pBag; + if (bag == InventorySlots.Bag0) + { + SetGuidValue(PlayerFields.InvSlotHead + (slot * 4), ObjectGuid.Empty); + + // equipment and equipped bags can have applied bonuses + if (slot < InventorySlots.BagEnd) + { + ItemTemplate pProto = pItem.GetTemplate(); + + // item set bonuses applied only at equip and removed at unequip, and still active for broken items + if (pProto != null && pProto.GetItemSet() != 0) + Item.RemoveItemsSetItem(this, pProto); + + _ApplyItemMods(pItem, slot, false); + } + + if (slot < EquipmentSlot.End) + { + // update expertise and armor penetration - passive auras may need it + switch (slot) + { + case EquipmentSlot.MainHand: + case EquipmentSlot.OffHand: + RecalculateRating(CombatRating.ArmorPenetration); + break; + default: + break; + } + + if (slot == EquipmentSlot.MainHand) + UpdateExpertise(WeaponAttackType.BaseAttack); + else if (slot == EquipmentSlot.OffHand) + UpdateExpertise(WeaponAttackType.OffAttack); + + // equipment visual show + SetVisibleItemSlot(slot, null); + } + m_items[slot] = null; + } + else if ((pBag = GetBagByPos(bag)) != null) + pBag.RemoveItem(slot, update); + + // Delete rolled money / loot from db. + // MUST be done before RemoveFromWorld() or GetTemplate() fails + ItemTemplate pTmp = pItem.GetTemplate(); + if (pTmp != null) + if (Convert.ToBoolean(pTmp.GetFlags() & ItemFlags.HasLoot)) + pItem.ItemContainerDeleteLootMoneyAndLootItemsFromDB(); + + if (IsInWorld && update) + { + pItem.RemoveFromWorld(); + pItem.DestroyForPlayer(this); + } + + pItem.SetOwnerGUID(ObjectGuid.Empty); + pItem.SetGuidValue(ItemFields.Contained, ObjectGuid.Empty); + pItem.SetSlot(ItemConst.NullSlot); + pItem.SetState(ItemUpdateState.Removed, this); + } + } + + public void DestroyItemCount(uint itemEntry, uint count, bool update, bool unequip_check = true) + { + Log.outDebug(LogFilter.Player, "STORAGE: DestroyItemCount item = {0}, count = {1}", itemEntry, count); + uint remcount = 0; + + // in inventory + for (byte i = InventorySlots.ItemStart; i < InventorySlots.ItemEnd; ++i) + { + Item item = GetItemByPos(InventorySlots.Bag0, i); + if (item != null) + { + if (item.GetEntry() == itemEntry && !item.IsInTrade()) + { + if (item.GetCount() + remcount <= count) + { + // all items in inventory can unequipped + remcount += item.GetCount(); + DestroyItem(InventorySlots.Bag0, i, update); + + if (remcount >= count) + return; + } + else + { + ItemRemovedQuestCheck(item.GetEntry(), count - remcount); + item.SetCount(item.GetCount() - count + remcount); + if (IsInWorld && update) + item.SendUpdateToPlayer(this); + item.SetState(ItemUpdateState.Changed, this); + return; + } + } + } + } + + // in inventory bags + for (byte i = InventorySlots.BagStart; i < InventorySlots.BagEnd; i++) + { + Bag bag = GetBagByPos(i); + if (bag != null) + { + for (byte j = 0; j < bag.GetBagSize(); j++) + { + Item item = bag.GetItemByPos(j); + if (item != null) + { + if (item.GetEntry() == itemEntry && !item.IsInTrade()) + { + // all items in bags can be unequipped + if (item.GetCount() + remcount <= count) + { + remcount += item.GetCount(); + DestroyItem(i, j, update); + + if (remcount >= count) + return; + } + else + { + ItemRemovedQuestCheck(item.GetEntry(), count - remcount); + item.SetCount(item.GetCount() - count + remcount); + if (IsInWorld && update) + item.SendUpdateToPlayer(this); + item.SetState(ItemUpdateState.Changed, this); + return; + } + } + } + } + } + } + + // in equipment and bag list + for (byte i = EquipmentSlot.Start; i < InventorySlots.BagEnd; i++) + { + Item item = GetItemByPos(InventorySlots.Bag0, i); + if (item != null) + { + if (item.GetEntry() == itemEntry && !item.IsInTrade()) + { + if (item.GetCount() + remcount <= count) + { + if (!unequip_check || CanUnequipItem((ushort)(InventorySlots.Bag0 << 8 | i), false) == InventoryResult.Ok) + { + remcount += item.GetCount(); + DestroyItem(InventorySlots.Bag0, i, update); + + if (remcount >= count) + return; + } + } + else + { + ItemRemovedQuestCheck(item.GetEntry(), count - remcount); + item.SetCount(item.GetCount() - count + remcount); + if (IsInWorld && update) + item.SendUpdateToPlayer(this); + item.SetState(ItemUpdateState.Changed, this); + return; + } + } + } + } + + // in bank + for (byte i = InventorySlots.BankItemStart; i < InventorySlots.BankItemEnd; i++) + { + Item item = GetItemByPos(InventorySlots.Bag0, i); + if (item != null) + { + if (item.GetEntry() == itemEntry && !item.IsInTrade()) + { + if (item.GetCount() + remcount <= count) + { + remcount += item.GetCount(); + DestroyItem(InventorySlots.Bag0, i, update); + if (remcount >= count) + return; + } + else + { + ItemRemovedQuestCheck(item.GetEntry(), count - remcount); + item.SetCount(item.GetCount() - count + remcount); + if (IsInWorld && update) + item.SendUpdateToPlayer(this); + item.SetState(ItemUpdateState.Changed, this); + return; + } + } + } + } + + // in bank bags + for (byte i = InventorySlots.BankBagStart; i < InventorySlots.BankBagEnd; i++) + { + Bag bag = GetBagByPos(i); + if (bag != null) + { + for (byte j = 0; j < bag.GetBagSize(); j++) + { + Item item = bag.GetItemByPos(j); + if (item != null) + { + if (item.GetEntry() == itemEntry && !item.IsInTrade()) + { + // all items in bags can be unequipped + if (item.GetCount() + remcount <= count) + { + remcount += item.GetCount(); + DestroyItem(i, j, update); + + if (remcount >= count) + return; + } + else + { + ItemRemovedQuestCheck(item.GetEntry(), count - remcount); + item.SetCount(item.GetCount() - count + remcount); + if (IsInWorld && update) + item.SendUpdateToPlayer(this); + item.SetState(ItemUpdateState.Changed, this); + return; + } + } + } + } + } + } + + for (byte i = InventorySlots.ReagentStart; i < InventorySlots.ReagentEnd; ++i) + { + Item item = GetItemByPos(InventorySlots.Bag0, i); + if (item) + { + if (item.GetEntry() == itemEntry && !item.IsInTrade()) + { + if (item.GetCount() + remcount <= count) + { + // all keys can be unequipped + remcount += item.GetCount(); + DestroyItem(InventorySlots.Bag0, i, update); + + if (remcount >= count) + return; + } + else + { + ItemRemovedQuestCheck(item.GetEntry(), count - remcount); + item.SetCount(item.GetCount() - count + remcount); + if (IsInWorld && update) + item.SendUpdateToPlayer(this); + item.SetState(ItemUpdateState.Changed, this); + return; + } + } + } + } + + for (byte i = InventorySlots.ChildEquipmentStart; i < InventorySlots.ChildEquipmentEnd; ++i) + { + Item item = GetItemByPos(InventorySlots.Bag0, i); + if (item) + { + if (item.GetEntry() == itemEntry && !item.IsInTrade()) + { + if (item.GetCount() + remcount <= count) + { + // all keys can be unequipped + remcount += item.GetCount(); + DestroyItem(InventorySlots.Bag0, i, update); + + if (remcount >= count) + return; + } + else + { + ItemRemovedQuestCheck(item.GetEntry(), count - remcount); + item.SetCount(item.GetCount() - count + remcount); + if (IsInWorld && update) + item.SendUpdateToPlayer(this); + item.SetState(ItemUpdateState.Changed, this); + return; + } + } + } + } + } + public void DestroyItemCount(Item pItem, ref uint count, bool update) + { + if (pItem == null) + return; + + Log.outDebug(LogFilter.Player, "STORAGE: DestroyItemCount item (GUID: {0}, Entry: {1}) count = {2}", pItem.GetGUID().ToString(), pItem.GetEntry(), count); + + if (pItem.GetCount() <= count) + { + count -= pItem.GetCount(); + + DestroyItem(pItem.GetBagSlot(), pItem.GetSlot(), update); + } + else + { + ItemRemovedQuestCheck(pItem.GetEntry(), count); + pItem.SetCount(pItem.GetCount() - count); + count = 0; + if (IsInWorld && update) + pItem.SendUpdateToPlayer(this); + pItem.SetState(ItemUpdateState.Changed, this); + } + } + public void AutoStoreLoot(uint loot_id, LootStore store, bool broadcast = false) { AutoStoreLoot(ItemConst.NullBag, ItemConst.NullSlot, loot_id, store, broadcast); } + void AutoStoreLoot(byte bag, byte slot, uint loot_id, LootStore store, bool broadcast = false) + { + Loot loot = new Loot(); + loot.FillLoot(loot_id, store, this, true); + + uint max_slot = loot.GetMaxSlotInLootFor(this); + for (uint i = 0; i < max_slot; ++i) + { + LootItem lootItem = loot.LootItemInSlot(i, this); + + List dest = new List(); + InventoryResult msg = CanStoreNewItem(bag, slot, dest, lootItem.itemid, lootItem.count); + if (msg != InventoryResult.Ok && slot != ItemConst.NullSlot) + msg = CanStoreNewItem(bag, ItemConst.NullSlot, dest, lootItem.itemid, lootItem.count); + if (msg != InventoryResult.Ok && bag != ItemConst.NullBag) + msg = CanStoreNewItem(ItemConst.NullBag, ItemConst.NullSlot, dest, lootItem.itemid, lootItem.count); + if (msg != InventoryResult.Ok) + { + SendEquipError(msg, null, null, lootItem.itemid); + continue; + } + + Item pItem = StoreNewItem(dest, lootItem.itemid, true, lootItem.randomPropertyId, null, lootItem.context, lootItem.BonusListIDs); + SendNewItem(pItem, lootItem.count, false, false, broadcast); + } + } + + public byte GetBankBagSlotCount() { return GetByteValue(PlayerFields.Bytes3, PlayerFieldOffsets.Bytes3OffsetBankBagSlots); } + public void SetBankBagSlotCount(byte count) { SetByteValue(PlayerFields.Bytes3, PlayerFieldOffsets.Bytes3OffsetBankBagSlots, count); } + + //Loot + public ObjectGuid GetLootGUID() { return GetGuidValue(PlayerFields.LootTargetGuid); } + public void SetLootGUID(ObjectGuid guid) { SetGuidValue(PlayerFields.LootTargetGuid, guid); } + public void StoreLootItem(byte lootSlot, Loot loot, AELootResult aeResult = null) + { + QuestItem qitem = null; + QuestItem ffaitem = null; + QuestItem conditem = null; + + LootItem item = loot.LootItemInSlot(lootSlot, this, out qitem, out ffaitem, out conditem); + if (item == null) + { + SendEquipError(InventoryResult.LootGone); + return; + } + + if (!item.AllowedForPlayer(this)) + { + SendLootReleaseAll(); + return; + } + + // questitems use the blocked field for other purposes + if (qitem == null && item.is_blocked) + { + SendLootReleaseAll(); + return; + } + + List dest = new List(); + InventoryResult msg = CanStoreNewItem(ItemConst.NullBag, ItemConst.NullSlot, dest, item.itemid, item.count); + if (msg == InventoryResult.Ok) + { + Item newitem = StoreNewItem(dest, item.itemid, true, item.randomPropertyId, item.GetAllowedLooters(), item.context, item.BonusListIDs); + if (qitem != null) + { + qitem.is_looted = true; + //freeforall is 1 if everyone's supposed to get the quest item. + if (item.freeforall || loot.GetPlayerQuestItems().Count == 1) + SendNotifyLootItemRemoved(loot.GetGUID(), lootSlot); + else + loot.NotifyQuestItemRemoved(qitem.index); + } + else + { + if (ffaitem != null) + { + //freeforall case, notify only one player of the removal + ffaitem.is_looted = true; + SendNotifyLootItemRemoved(loot.GetGUID(), lootSlot); + } + else + { + //not freeforall, notify everyone + if (conditem != null) + conditem.is_looted = true; + loot.NotifyItemRemoved(lootSlot); + } + } + + //if only one person is supposed to loot the item, then set it to looted + if (!item.freeforall) + item.is_looted = true; + + --loot.unlootedCount; + + if (Global.ObjectMgr.GetItemTemplate(item.itemid) != null) + { + if (newitem.GetQuality() > ItemQuality.Epic || (newitem.GetQuality() == ItemQuality.Epic && newitem.GetItemLevel(this) >= GuildConst.MinNewsItemLevel)) + { + Guild guild = GetGuild(); + if (guild) + guild.AddGuildNews(GuildNews.ItemLooted, GetGUID(), 0, item.itemid); + } + } + + // if aeLooting then we must delay sending out item so that it appears properly stacked in chat + if (aeResult == null) + { + SendNewItem(newitem, item.count, false, false, true); + UpdateCriteria(CriteriaTypes.LootItem, item.itemid, item.count); + UpdateCriteria(CriteriaTypes.LootType, item.itemid, item.count, (ulong)loot.loot_type); + UpdateCriteria(CriteriaTypes.LootEpicItem, item.itemid, item.count); + } + else + aeResult.Add(newitem, item.count, loot.loot_type); + + // LootItem is being removed (looted) from the container, delete it from the DB. + if (!loot.containerID.IsEmpty()) + loot.DeleteLootItemFromContainerItemDB(item.itemid); + + } + else + SendEquipError(msg, null, null, item.itemid); + } + + public Dictionary GetAELootView() { return m_AELootView; } + + /// + /// if in a Battleground a player dies, and an enemy removes the insignia, the player's bones is lootable + /// Called by remove insignia spell effect + /// + /// + public void RemovedInsignia(Player looterPlr) + { + if (GetBattlegroundId() == 0) + return; + + // If not released spirit, do it ! + if (m_deathTimer > 0) + { + m_deathTimer = 0; + BuildPlayerRepop(); + RepopAtGraveyard(); + } + + _corpseLocation = new WorldLocation(); + + // We have to convert player corpse to bones, not to be able to resurrect there + // SpawnCorpseBones isn't handy, 'cos it saves player while he in BG + Corpse bones = GetMap().ConvertCorpseToBones(GetGUID(), true); + if (!bones) + return; + + // Now we must make bones lootable, and send player loot + bones.SetFlag(CorpseFields.DynamicFlags, 0x01); + + // We store the level of our player in the gold field + // We retrieve this information at Player.SendLoot() + bones.loot.gold = getLevel(); + bones.lootRecipient = looterPlr; + looterPlr.SendLoot(bones.GetGUID(), LootType.Insignia); + } + + public void SendLootRelease(ObjectGuid guid) + { + LootReleaseResponse packet = new LootReleaseResponse(); + packet.LootObj = guid; + packet.Owner = GetGUID(); + SendPacket(packet); + } + + public void SendLootReleaseAll() + { + SendPacket(new LootReleaseAll()); + } + + public void SendLoot(ObjectGuid guid, LootType loot_type, bool aeLooting = false) + { + ObjectGuid currentLootGuid = GetLootGUID(); + if (!currentLootGuid.IsEmpty() && !aeLooting) + Session.DoLootRelease(currentLootGuid); + + Loot loot = null; + PermissionTypes permission = PermissionTypes.All; + + Log.outDebug(LogFilter.Loot, "Player.SendLoot"); + if (guid.IsGameObject()) + { + GameObject go = GetMap().GetGameObject(guid); + + // not check distance for GO in case owned GO (fishing bobber case, for example) + // And permit out of range GO with no owner in case fishing hole + if (!go || (loot_type != LootType.Fishinghole && ((loot_type != LootType.Fishing && loot_type != LootType.FishingJunk) || go.GetOwnerGUID() != GetGUID()) + && !go.IsWithinDistInMap(this, SharedConst.InteractionDistance)) || (loot_type == LootType.Corpse && go.GetRespawnTime() != 0 && go.isSpawnedByDefault())) + { + SendLootRelease(guid); + return; + } + + loot = go.loot; + + if (go.getLootState() == LootState.Ready) + { + uint lootid = go.GetGoInfo().GetLootId(); + Battleground bg = GetBattleground(); + if (bg) + { + if (!bg.CanActivateGO((int)go.GetEntry(), (uint)GetTeam())) + { + SendLootRelease(guid); + return; + } + } + + if (lootid != 0) + { + loot.clear(); + + Group group = GetGroup(); + bool groupRules = (group && go.GetGoInfo().type == GameObjectTypes.Chest && go.GetGoInfo().Chest.usegrouplootrules != 0); + + // check current RR player and get next if necessary + if (groupRules) + group.UpdateLooterGuid(go, true); + + loot.FillLoot(lootid, LootManager.Gameobject, this, !groupRules, false, go.GetLootMode()); + + // get next RR player (for next loot) + if (groupRules) + group.UpdateLooterGuid(go); + } + + GameObjectTemplateAddon addon = go.GetTemplateAddon(); + if (addon != null) + loot.generateMoneyLoot(addon.mingold, addon.maxgold); + + if (loot_type == LootType.Fishing) + go.getFishLoot(loot, this); + else if (loot_type == LootType.FishingJunk) + go.getFishLootJunk(loot, this); + + if (go.GetGoInfo().type == GameObjectTypes.Chest && go.GetGoInfo().Chest.usegrouplootrules != 0) + { + var group = GetGroup(); + if (group) + { + switch (group.GetLootMethod()) + { + case LootMethod.GroupLoot: + // GroupLoot: rolls items over threshold. Items with quality < threshold, round robin + group.GroupLoot(loot, go); + break; + case LootMethod.MasterLoot: + group.MasterLoot(loot, go); + break; + default: + break; + } + } + } + + go.SetLootState(LootState.Activated, this); + } + + if (go.getLootState() == LootState.Activated) + { + Group group = GetGroup(); + if (group) + { + switch (group.GetLootMethod()) + { + case LootMethod.MasterLoot: + permission = PermissionTypes.Master; + break; + case LootMethod.FreeForAll: + permission = PermissionTypes.All; + break; + default: + permission = PermissionTypes.Group; + break; + } + } + else + permission = PermissionTypes.All; + } + } + else if (guid.IsItem()) + { + Item item = GetItemByGuid(guid); + + if (item == null) + { + SendLootRelease(guid); + return; + } + + permission = PermissionTypes.Owner; + + loot = item.loot; + + // If item doesn't already have loot, attempt to load it. If that + // fails then this is first time opening, generate loot + if (!item.m_lootGenerated && !item.ItemContainerLoadLootFromDB()) + { + item.m_lootGenerated = true; + loot.clear(); + + switch (loot_type) + { + case LootType.Disenchanting: + loot.FillLoot(item.GetTemplate().DisenchantID, LootStorage.Disenchant, this, true); + break; + case LootType.Prospecting: + loot.FillLoot(item.GetEntry(), LootStorage.Prospecting, this, true); + break; + case LootType.Milling: + loot.FillLoot(item.GetEntry(), LootStorage.Milling, this, true); + break; + default: + loot.generateMoneyLoot(item.GetTemplate().MinMoneyLoot, item.GetTemplate().MaxMoneyLoot); + loot.FillLoot(item.GetEntry(), LootStorage.Items, this, true, loot.gold != 0); + + // Force save the loot and money items that were just rolled + // Also saves the container item ID in Loot struct (not to DB) + if (loot.gold > 0 || loot.unlootedCount > 0) + item.ItemContainerSaveLootToDB(); + + break; + } + } + } + else if (guid.IsCorpse()) // remove insignia + { + Corpse bones = ObjectAccessor.GetCorpse(this, guid); + + if (bones == null || !(loot_type == LootType.Corpse || loot_type == LootType.Insignia) || bones.GetCorpseType() != CorpseType.Bones) + { + SendLootRelease(guid); + return; + } + + loot = bones.loot; + + if (!bones.lootForBody) + { + bones.lootForBody = true; + uint pLevel = bones.loot.gold; + bones.loot.clear(); + Battleground bg = GetBattleground(); + if (bg) + if (bg.GetTypeID(true) == BattlegroundTypeId.AV) + loot.FillLoot(1, LootStorage.Creature, this, true); + // It may need a better formula + // Now it works like this: lvl10: ~6copper, lvl70: ~9silver + bones.loot.gold = (uint)(RandomHelper.URand(50, 150) * 0.016f * Math.Pow(pLevel / 5.76f, 2.5f) * WorldConfig.GetFloatValue(WorldCfg.RateDropMoney)); + } + + if (bones.lootRecipient != this) + permission = PermissionTypes.None; + else + permission = PermissionTypes.Owner; + } + else + { + Creature creature = GetMap().GetCreature(guid); + + // must be in range and creature must be alive for pickpocket and must be dead for another loot + if (creature == null || creature.IsAlive() != (loot_type == LootType.Pickpocketing) || (!aeLooting && !creature.IsWithinDistInMap(this, SharedConst.InteractionDistance))) + { + SendLootRelease(guid); + return; + } + + if (loot_type == LootType.Pickpocketing && IsFriendlyTo(creature)) + { + SendLootRelease(guid); + return; + } + + loot = creature.loot; + + if (loot_type == LootType.Pickpocketing) + { + if (loot.loot_type != LootType.Pickpocketing) + { + if (creature.CanGeneratePickPocketLoot()) + { + creature.StartPickPocketRefillTimer(); + loot.clear(); + + uint lootid = creature.GetCreatureTemplate().PickPocketId; + if (lootid != 0) + loot.FillLoot(lootid, LootStorage.Pickpocketing, this, true); + + // Generate extra money for pick pocket loot + uint a = RandomHelper.URand(0, creature.getLevel() / 2); + uint b = RandomHelper.URand(0, getLevel() / 2); + loot.gold = (uint)(10 * (a + b) * WorldConfig.GetFloatValue(WorldCfg.RateDropMoney)); + permission = PermissionTypes.Owner; + } + else + { + SendLootError(loot.GetGUID(), guid, LootError.AlreadPickPocketed); + return; + } + } + } + else + { + if (loot.loot_type == LootType.None) + { + // for creature, loot is filled when creature is killed. + Group group = creature.GetLootRecipientGroup(); + if (group) + { + switch (group.GetLootMethod()) + { + case LootMethod.GroupLoot: + // GroupLoot: rolls items over threshold. Items with quality < threshold, round robin + group.GroupLoot(loot, creature); + break; + case LootMethod.MasterLoot: + group.MasterLoot(loot, creature); + break; + default: + break; + } + } + } + + if (loot.loot_type == LootType.Skinning) + { + loot_type = LootType.Skinning; + permission = creature.GetSkinner() == GetGUID() ? PermissionTypes.Owner : PermissionTypes.None; + } + else if (loot_type == LootType.Skinning) + { + loot.clear(); + loot.FillLoot(creature.GetCreatureTemplate().SkinLootId, LootStorage.Skinning, this, true); + creature.SetSkinner(GetGUID()); + permission = PermissionTypes.Owner; + } + // set group rights only for loot_type != LOOT_SKINNING + else + { + if (creature.GetLootRecipientGroup()) + { + Group group = GetGroup(); + if (group == creature.GetLootRecipientGroup()) + { + switch (group.GetLootMethod()) + { + case LootMethod.MasterLoot: + permission = PermissionTypes.Master; + break; + case LootMethod.FreeForAll: + permission = PermissionTypes.All; + break; + default: + permission = PermissionTypes.Group; + break; + } + } + else + permission = PermissionTypes.None; + } + else if (creature.GetLootRecipient() == this) + permission = PermissionTypes.Owner; + else + permission = PermissionTypes.None; + } + } + } + + // LOOT_INSIGNIA and LOOT_FISHINGHOLE unsupported by client + switch (loot_type) + { + case LootType.Insignia: + loot_type = LootType.Skinning; + break; + case LootType.Fishinghole: + case LootType.FishingJunk: + loot_type = LootType.Fishing; + break; + default: break; + } + + // need know merged fishing/corpse loot type for achievements + loot.loot_type = loot_type; + + if (permission != PermissionTypes.None) + { + LootMethod _lootMethod = LootMethod.FreeForAll; + Group group = GetGroup(); + if (group) + { + Creature creature = GetMap().GetCreature(guid); + if (creature) + { + Player recipient = creature.GetLootRecipient(); + if (recipient) + { + if (group == recipient.GetGroup()) + _lootMethod = group.GetLootMethod(); + } + } + } + + if (!aeLooting) + SetLootGUID(guid); + + LootResponse packet = new LootResponse(); + packet.Owner = guid; + packet.LootObj = loot.GetGUID(); + packet.LootMethod = _lootMethod; + packet.AcquireReason = (byte)loot_type; + packet.Acquired = true; // false == No Loot (this too^^) + packet.AELooting = aeLooting; + loot.BuildLootResponse(packet, this, permission); + SendPacket(packet); + + // add 'this' player as one of the players that are looting 'loot' + loot.AddLooter(GetGUID()); + m_AELootView[loot.GetGUID()] = guid; + } + else + SendLootError(loot.GetGUID(), guid, LootError.DidntKill); + + if (loot_type == LootType.Corpse && !guid.IsItem()) + SetFlag(UnitFields.Flags, UnitFlags.Looting); + } + + public void SendLootError(ObjectGuid lootObj, ObjectGuid owner, LootError error) + { + LootResponse packet = new LootResponse(); + packet.LootObj = lootObj; + packet.Owner = owner; + packet.Acquired = false; + packet.FailureReason = error; + SendPacket(packet); + } + + public void SendNotifyLootMoneyRemoved(ObjectGuid lootObj) + { + CoinRemoved packet = new CoinRemoved(); + packet.LootObj = lootObj; + SendPacket(packet); + } + + public void SendNotifyLootItemRemoved(ObjectGuid lootObj, byte lootSlot) + { + LootRemoved packet = new LootRemoved(); + packet.Owner = GetLootWorldObjectGUID(lootObj); + packet.LootObj = lootObj; + packet.LootListID = (byte)(lootSlot + 1); + SendPacket(packet); + } + + void SendEquipmentSetList() + { + LoadEquipmentSet data = new LoadEquipmentSet(); + + foreach (var pair in _equipmentSets) + { + if (pair.Value.state == EquipmentSetUpdateState.Deleted) + continue; + + data.SetData.Add(pair.Value.Data); + } + + SendPacket(data); + } + + public void SetEquipmentSet(EquipmentSetInfo.EquipmentSetData newEqSet) + { + if (newEqSet.Guid != 0) + { + // something wrong... + var equipmentSetInfo = _equipmentSets.LookupByKey(newEqSet.SetID); + if (equipmentSetInfo == null || equipmentSetInfo.Data.Guid != newEqSet.Guid) + { + Log.outError(LogFilter.Player, "Player {0} tried to save equipment set {1} (index: {2}), but that equipment set not found!", GetName(), newEqSet.Guid, newEqSet.SetID); + return; + } + } + + EquipmentSetInfo eqSlot = _equipmentSets[newEqSet.Guid]; + EquipmentSetUpdateState old_state = eqSlot.state; + eqSlot.Data = newEqSet; + + if (eqSlot.Data.Guid == 0) + { + eqSlot.Data.Guid = Global.ObjectMgr.GenerateEquipmentSetGuid(); + + EquipmentSetID data = new EquipmentSetID(); + data.GUID = eqSlot.Data.Guid; + data.Type = (int)eqSlot.Data.Type; + data.SetID = eqSlot.Data.SetID; + SendPacket(data); + } + + eqSlot.state = old_state == EquipmentSetUpdateState.New ? EquipmentSetUpdateState.New : EquipmentSetUpdateState.Changed; + } + + public void DeleteEquipmentSet(ulong id) + { + foreach (var pair in _equipmentSets) + { + if (pair.Value.Data.Guid == id) + { + if (pair.Value.state == EquipmentSetUpdateState.New) + _equipmentSets.Remove(pair.Key); + else + pair.Value.state = EquipmentSetUpdateState.Deleted; + break; + } + } + } + + //Void Storage + public bool IsVoidStorageUnlocked() { return HasFlag(PlayerFields.Flags, PlayerFlags.VoidUnlocked); } + public void UnlockVoidStorage() { SetFlag(PlayerFields.Flags, PlayerFlags.VoidUnlocked); } + public void LockVoidStorage() { RemoveFlag(PlayerFields.Flags, PlayerFlags.VoidUnlocked); } + + public byte GetNextVoidStorageFreeSlot() + { + for (byte i = 0; i < SharedConst.VoidStorageMaxSlot; ++i) + if (_voidStorageItems[i] == null) // unused item + return i; + + return SharedConst.VoidStorageMaxSlot; + } + + public byte GetNumOfVoidStorageFreeSlots() + { + byte count = 0; + + for (byte i = 0; i < SharedConst.VoidStorageMaxSlot; ++i) + if (_voidStorageItems[i] == null) + count++; + + return count; + } + + public byte AddVoidStorageItem(VoidStorageItem item) + { + byte slot = GetNextVoidStorageFreeSlot(); + + if (slot >= SharedConst.VoidStorageMaxSlot) + { + GetSession().SendVoidStorageTransferResult(VoidTransferError.Full); + return 255; + } + + _voidStorageItems[slot] = item; + return slot; + } + + public void DeleteVoidStorageItem(byte slot) + { + if (slot >= SharedConst.VoidStorageMaxSlot) + { + GetSession().SendVoidStorageTransferResult(VoidTransferError.InternalError1); + return; + } + + _voidStorageItems[slot] = null; + } + + public bool SwapVoidStorageItem(byte oldSlot, byte newSlot) + { + if (oldSlot >= SharedConst.VoidStorageMaxSlot || newSlot >= SharedConst.VoidStorageMaxSlot || oldSlot == newSlot) + return false; + + _voidStorageItems.Swap(newSlot, oldSlot); + return true; + } + + public VoidStorageItem GetVoidStorageItem(byte slot) + { + if (slot >= SharedConst.VoidStorageMaxSlot) + { + GetSession().SendVoidStorageTransferResult(VoidTransferError.InternalError1); + return null; + } + + return _voidStorageItems[slot]; + } + + public VoidStorageItem GetVoidStorageItem(ulong id, out byte slot) + { + slot = 0; + for (byte i = 0; i < SharedConst.VoidStorageMaxSlot; ++i) + { + if (_voidStorageItems[i] != null && _voidStorageItems[i].ItemId == id) + { + slot = i; + return _voidStorageItems[i]; + } + } + + return null; + } + } +} diff --git a/Game/Entities/Player/Player.Map.cs b/Game/Entities/Player/Player.Map.cs new file mode 100644 index 000000000..49e3ea3c8 --- /dev/null +++ b/Game/Entities/Player/Player.Map.cs @@ -0,0 +1,797 @@ +/* + * Copyright (C) 2012-2017 CypherCore + * + * 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, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +using Framework.Constants; +using Framework.Database; +using Game.DataStorage; +using Game.Groups; +using Game.Guilds; +using Game.Maps; +using Game.Network.Packets; +using System; +using System.Collections.Generic; +using System.Linq; + +namespace Game.Entities +{ + public partial class Player + { + public override void SetMap(Map map) + { + base.SetMap(map); + } + + public Difficulty GetDifficultyID(MapRecord mapEntry) + { + if (!mapEntry.IsRaid()) + return m_dungeonDifficulty; + + MapDifficultyRecord defaultDifficulty = Global.DB2Mgr.GetDefaultMapDifficulty(mapEntry.Id); + if (defaultDifficulty == null) + return m_legacyRaidDifficulty; + + DifficultyRecord difficulty = CliDB.DifficultyStorage.LookupByKey(defaultDifficulty.DifficultyID); + if (difficulty == null || difficulty.Flags.HasAnyFlag(DifficultyFlags.Legacy)) + return m_legacyRaidDifficulty; + + return m_raidDifficulty; + } + public Difficulty GetDungeonDifficultyID() { return m_dungeonDifficulty; } + public Difficulty GetRaidDifficultyID() { return m_raidDifficulty; } + public Difficulty GetLegacyRaidDifficultyID() { return m_legacyRaidDifficulty; } + public void SetDungeonDifficultyID(Difficulty dungeon_difficulty) { m_dungeonDifficulty = dungeon_difficulty; } + public void SetRaidDifficultyID(Difficulty raid_difficulty) { m_raidDifficulty = raid_difficulty; } + public void SetLegacyRaidDifficultyID(Difficulty raid_difficulty) { m_legacyRaidDifficulty = raid_difficulty; } + + public static Difficulty CheckLoadedDungeonDifficultyID(Difficulty difficulty) + { + DifficultyRecord difficultyEntry = CliDB.DifficultyStorage.LookupByKey(difficulty); + if (difficultyEntry == null) + return Difficulty.Normal; + + if (difficultyEntry.InstanceType != MapTypes.Instance) + return Difficulty.Normal; + + if (!difficultyEntry.Flags.HasAnyFlag(DifficultyFlags.CanSelect)) + return Difficulty.Normal; + + return difficulty; + } + public static Difficulty CheckLoadedRaidDifficultyID(Difficulty difficulty) + { + DifficultyRecord difficultyEntry = CliDB.DifficultyStorage.LookupByKey(difficulty); + if (difficultyEntry == null) + return Difficulty.NormalRaid; + + if (difficultyEntry.InstanceType != MapTypes.Raid) + return Difficulty.NormalRaid; + + if (!difficultyEntry.Flags.HasAnyFlag(DifficultyFlags.CanSelect) || difficultyEntry.Flags.HasAnyFlag(DifficultyFlags.Legacy)) + return Difficulty.NormalRaid; + + return difficulty; + } + public static Difficulty CheckLoadedLegacyRaidDifficultyID(Difficulty difficulty) + { + DifficultyRecord difficultyEntry = CliDB.DifficultyStorage.LookupByKey(difficulty); + if (difficultyEntry == null) + return Difficulty.Raid10N; + + if (difficultyEntry.InstanceType != MapTypes.Raid) + return Difficulty.Raid10N; + + if (!difficultyEntry.Flags.HasAnyFlag(DifficultyFlags.CanSelect) || !difficultyEntry.Flags.HasAnyFlag(DifficultyFlags.Legacy)) + return Difficulty.Raid10N; + + return difficulty; + } + + public void SendRaidGroupOnlyMessage(RaidGroupReason reason, int delay) + { + RaidGroupOnly raidGroupOnly = new RaidGroupOnly(); + raidGroupOnly.Delay = delay; + raidGroupOnly.Reason = reason; + + SendPacket(raidGroupOnly); + } + + void UpdateArea(uint newArea) + { + // FFA_PVP flags are area and not zone id dependent + // so apply them accordingly + m_areaUpdateId = newArea; + + AreaTableRecord area = CliDB.AreaTableStorage.LookupByKey(newArea); + pvpInfo.IsInFFAPvPArea = area != null && area.Flags[0].HasAnyFlag(AreaFlags.Arena); + UpdatePvPState(true); + + UpdateAreaDependentAuras(newArea); + UpdateAreaAndZonePhase(); + + // previously this was in UpdateZone (but after UpdateArea) so nothing will break + pvpInfo.IsInNoPvPArea = false; + if (area != null && area.IsSanctuary()) // in sanctuary + { + SetByteFlag(UnitFields.Bytes2, UnitBytes2Offsets.PvpFlag, UnitBytes2Flags.Sanctuary); + pvpInfo.IsInNoPvPArea = true; + if (duel == null) + CombatStopWithPets(); + } + else + RemoveByteFlag(UnitFields.Bytes2, UnitBytes2Offsets.PvpFlag, UnitBytes2Flags.Sanctuary); + + AreaFlags areaRestFlag = (GetTeam() == Team.Alliance) ? AreaFlags.RestZoneAlliance : AreaFlags.RestZoneHorde; + if (area != null && area.Flags[0].HasAnyFlag(areaRestFlag)) + _restMgr.SetRestFlag(RestFlag.FactionArea); + else + _restMgr.RemoveRestFlag(RestFlag.FactionArea); + } + + public void UpdateZone(uint newZone, uint newArea) + { + if (m_zoneUpdateId != newZone) + { + Global.OutdoorPvPMgr.HandlePlayerLeaveZone(this, m_zoneUpdateId); + Global.OutdoorPvPMgr.HandlePlayerEnterZone(this, newZone); + Global.BattleFieldMgr.HandlePlayerLeaveZone(this, m_zoneUpdateId); + Global.BattleFieldMgr.HandlePlayerEnterZone(this, newZone); + SendInitWorldStates(newZone, newArea); // only if really enters to new zone, not just area change, works strange... + Guild guild = GetGuild(); + if (guild) + guild.UpdateMemberData(this, GuildMemberData.ZoneId, newZone); + } + + // group update + if (GetGroup()) + { + SetGroupUpdateFlag(GroupUpdateFlags.Full); + + Pet pet = GetPet(); + if (pet) + pet.SetGroupUpdateFlag(GroupUpdatePetFlags.Full); + } + + m_zoneUpdateId = newZone; + m_zoneUpdateTimer = 1 * Time.InMilliseconds; + + // zone changed, so area changed as well, update it + UpdateArea(newArea); + + AreaTableRecord zone = CliDB.AreaTableStorage.LookupByKey(newZone); + if (zone == null) + return; + + if (WorldConfig.GetBoolValue(WorldCfg.Weather) && !HasAuraType(AuraType.ForceWeather)) + { + Weather weather = Global.WeatherMgr.FindWeather(newZone); + if (weather != null) + weather.SendWeatherUpdateToPlayer(this); + else if (Global.WeatherMgr.AddWeather(newZone) == null) + { + // send fine weather packet to remove old zone's weather + Global.WeatherMgr.SendFineWeatherUpdateToPlayer(this); + } + } + + + Global.ScriptMgr.OnPlayerUpdateZone(this, newZone, newArea); + + // in PvP, any not controlled zone (except zone.team == 6, default case) + // in PvE, only opposition team capital + switch ((AreaTeams)zone.FactionGroupMask) + { + case AreaTeams.Ally: + pvpInfo.IsInHostileArea = GetTeam() != Team.Alliance && (Global.WorldMgr.IsPvPRealm() || zone.Flags[0].HasAnyFlag(AreaFlags.Capital)); + break; + case AreaTeams.Horde: + pvpInfo.IsInHostileArea = GetTeam() != Team.Horde && (Global.WorldMgr.IsPvPRealm() || zone.Flags[0].HasAnyFlag(AreaFlags.Capital)); + break; + case AreaTeams.None: + // overwrite for Battlegrounds, maybe batter some zone flags but current known not 100% fit to this + pvpInfo.IsInHostileArea = Global.WorldMgr.IsPvPRealm() || InBattleground() || zone.Flags[0].HasAnyFlag(AreaFlags.Wintergrasp); + break; + default: // 6 in fact + pvpInfo.IsInHostileArea = false; + break; + } + + // Treat players having a quest flagging for PvP as always in hostile area + pvpInfo.IsHostile = pvpInfo.IsInHostileArea || HasPvPForcingQuest(); + + if (zone.Flags[0].HasAnyFlag(AreaFlags.Capital)) // Is in a capital city + { + if (!pvpInfo.IsInHostileArea || zone.IsSanctuary()) + _restMgr.SetRestFlag(RestFlag.City); + pvpInfo.IsInNoPvPArea = true; + } + else + _restMgr.RemoveRestFlag(RestFlag.City); + + UpdatePvPState(); + + // remove items with area/map limitations (delete only for alive player to allow back in ghost mode) + // if player resurrected at teleport this will be applied in resurrect code + if (IsAlive()) + DestroyZoneLimitedItem(true, newZone); + + // check some item equip limitations (in result lost CanTitanGrip at talent reset, for example) + AutoUnequipOffhandIfNeed(); + + // recent client version not send leave/join channel packets for built-in local channels + UpdateLocalChannels(newZone); + + UpdateZoneDependentAuras(newZone); + + UpdateAreaAndZonePhase(); + } + + public InstanceBind GetBoundInstance(uint mapid, Difficulty difficulty, bool withExpired = false) + { + // some instances only have one difficulty + MapDifficultyRecord mapDiff = Global.DB2Mgr.GetDownscaledMapDifficultyData(mapid, ref difficulty); + if (mapDiff == null) + return null; + + var bind = m_boundInstances[(int)difficulty].LookupByKey(mapid); + if (bind != null) + if (bind.extendState != 0 || withExpired) + return bind; + + return null; + } + public Dictionary GetBoundInstances(Difficulty difficulty) { return m_boundInstances[(int)difficulty]; } + + public InstanceSave GetInstanceSave(uint mapid) + { + MapRecord mapEntry = CliDB.MapStorage.LookupByKey(mapid); + InstanceBind pBind = GetBoundInstance(mapid, GetDifficultyID(mapEntry)); + InstanceSave pSave = pBind?.save; + if (pBind == null || !pBind.perm) + { + Group group = GetGroup(); + if (group) + { + InstanceBind groupBind = group.GetBoundInstance(GetDifficultyID(mapEntry), mapid); + if (groupBind != null) + pSave = groupBind.save; + } + } + + return pSave; + } + + public void UnbindInstance(uint mapid, Difficulty difficulty, bool unload = false) + { + var bound = m_boundInstances[(int)difficulty].LookupByKey(mapid); + if (bound != null) + { + if (!unload) + { + PreparedStatement stmt = DB.Characters.GetPreparedStatement(CharStatements.DEL_CHAR_INSTANCE_BY_INSTANCE_GUID); + + stmt.AddValue(0, GetGUID().GetCounter()); + stmt.AddValue(1, bound.save.GetInstanceId()); + + DB.Characters.Execute(stmt); + } + + if (bound.perm) + GetSession().SendCalendarRaidLockout(bound.save, false); + + bound.save.RemovePlayer(this); // save can become invalid + m_boundInstances[(int)difficulty].Remove(mapid); + } + } + + public void UnbindInstance(KeyValuePair pair, Difficulty difficulty, bool unload) + { + if (pair.Value != null) + { + if (!unload) + { + PreparedStatement stmt = DB.Characters.GetPreparedStatement(CharStatements.DEL_CHAR_INSTANCE_BY_INSTANCE_GUID); + + stmt.AddValue(0, GetGUID().GetCounter()); + stmt.AddValue(1, pair.Value.save.GetInstanceId()); + + DB.Characters.Execute(stmt); + } + + if (pair.Value.perm) + GetSession().SendCalendarRaidLockout(pair.Value.save, false); + + pair.Value.save.RemovePlayer(this); // save can become invalid + m_boundInstances[(int)difficulty].Remove(pair.Key); + } + } + + public InstanceBind BindToInstance(InstanceSave save, bool permanent, BindExtensionState extendState = BindExtensionState.Normal, bool load = false) + { + if (save != null) + { + InstanceBind bind = new InstanceBind(); + if (m_boundInstances[(int)save.GetDifficultyID()].ContainsKey(save.GetMapId())) + bind = m_boundInstances[(int)save.GetDifficultyID()][save.GetMapId()]; + + if (extendState == BindExtensionState.Keep) // special flag, keep the player's current extend state when updating for new boss down + { + if (save == bind.save) + extendState = bind.extendState; + else + extendState = BindExtensionState.Normal; + } + + if (!load) + { + PreparedStatement stmt; + if (bind.save != null) + { + // update the save when the group kills a boss + if (permanent != bind.perm || save != bind.save || extendState != bind.extendState) + { + stmt = DB.Characters.GetPreparedStatement(CharStatements.UPD_CHAR_INSTANCE); + + stmt.AddValue(0, save.GetInstanceId()); + stmt.AddValue(1, permanent); + stmt.AddValue(2, extendState); + stmt.AddValue(3, GetGUID().GetCounter()); + stmt.AddValue(4, bind.save.GetInstanceId()); + + DB.Characters.Execute(stmt); + } + } + else + { + stmt = DB.Characters.GetPreparedStatement(CharStatements.INS_CHAR_INSTANCE); + stmt.AddValue(0, GetGUID().GetCounter()); + stmt.AddValue(1, save.GetInstanceId()); + stmt.AddValue(2, permanent); + stmt.AddValue(3, extendState); + DB.Characters.Execute(stmt); + } + } + + if (bind.save != save) + { + if (bind.save != null) + bind.save.RemovePlayer(this); + save.AddPlayer(this); + } + + if (permanent) + save.SetCanReset(false); + + bind.save = save; + bind.perm = permanent; + bind.extendState = extendState; + if (!load) + Log.outDebug(LogFilter.Maps, "Player.BindToInstance: Player '{0}' ({1}) is now bound to map (ID: {2}, Instance {3}, Difficulty {4})", GetName(), GetGUID().ToString(), save.GetMapId(), save.GetInstanceId(), save.GetDifficultyID()); + + Global.ScriptMgr.OnPlayerBindToInstance(this, save.GetDifficultyID(), save.GetMapId(), permanent, extendState); + + m_boundInstances[(int)save.GetDifficultyID()][save.GetMapId()] = bind; + return bind; + } + + return null; + } + + public void BindToInstance() + { + InstanceSave mapSave = Global.InstanceSaveMgr.GetInstanceSave(_pendingBindId); + if (mapSave == null) //it seems sometimes mapSave is NULL, but I did not check why + return; + + InstanceSaveCreated data = new InstanceSaveCreated(); + data.Gm = IsGameMaster(); + SendPacket(data); + if (!IsGameMaster()) + { + BindToInstance(mapSave, true, BindExtensionState.Keep); + GetSession().SendCalendarRaidLockout(mapSave, true); + } + } + + public void SetPendingBind(uint instanceId, uint bindTimer) + { + _pendingBindId = instanceId; + _pendingBindTimer = bindTimer; + } + + public void SendRaidInfo() + { + InstanceInfoPkt instanceInfo = new InstanceInfoPkt(); + + long now = Time.UnixTime; + for (byte i = 0; i < (int)Difficulty.Max; ++i) + { + foreach (var pair in m_boundInstances[i]) + { + InstanceBind bind = pair.Value; + if (bind.perm) + { + InstanceSave save = pair.Value.save; + + InstanceLockInfos lockInfos; + + lockInfos.InstanceID = save.GetInstanceId(); + lockInfos.MapID = save.GetMapId(); + lockInfos.DifficultyID = (uint)save.GetDifficultyID(); + if (bind.extendState != BindExtensionState.Extended) + lockInfos.TimeRemaining = (int)(save.GetResetTime() - now); + else + lockInfos.TimeRemaining = (int)(Global.InstanceSaveMgr.GetSubsequentResetTime(save.GetMapId(), save.GetDifficultyID(), save.GetResetTime()) - now); + + lockInfos.CompletedMask = 0; + Map map = Global.MapMgr.FindMap(save.GetMapId(), save.GetInstanceId()); + if (map != null) + { + InstanceScript instanceScript = ((InstanceMap)map).GetInstanceScript(); + if (instanceScript != null) + lockInfos.CompletedMask = instanceScript.GetCompletedEncounterMask(); + } + + lockInfos.Locked = bind.extendState != BindExtensionState.Expired; + lockInfos.Extended = bind.extendState == BindExtensionState.Extended; + + instanceInfo.LockList.Add(lockInfos); + } + } + } + + SendPacket(instanceInfo); + } + + public bool Satisfy(AccessRequirement ar, uint target_map, bool report = false) + { + if (!IsGameMaster() && ar != null) + { + byte LevelMin = 0; + byte LevelMax = 0; + + MapRecord mapEntry = CliDB.MapStorage.LookupByKey(target_map); + if (mapEntry == null) + return false; + + if (!WorldConfig.GetBoolValue(WorldCfg.InstanceIgnoreLevel)) + { + if (ar.levelMin != 0 && getLevel() < ar.levelMin) + LevelMin = ar.levelMin; + if (ar.levelMax != 0 && getLevel() > ar.levelMax) + LevelMax = ar.levelMax; + } + + uint missingItem = 0; + if (ar.item != 0) + { + if (!HasItemCount(ar.item) && + (ar.item2 == 0 || !HasItemCount(ar.item2))) + missingItem = ar.item; + } + else if (ar.item2 != 0 && !HasItemCount(ar.item2)) + missingItem = ar.item2; + + if (Global.DisableMgr.IsDisabledFor(DisableType.Map, target_map, this)) + { + GetSession().SendNotification("{0}", Global.ObjectMgr.GetCypherString(CypherStrings.InstanceClosed)); + return false; + } + + uint missingQuest = 0; + if (GetTeam() == Team.Alliance && ar.quest_A != 0 && !GetQuestRewardStatus(ar.quest_A)) + missingQuest = ar.quest_A; + else if (GetTeam() == Team.Horde && ar.quest_H != 0 && !GetQuestRewardStatus(ar.quest_H)) + missingQuest = ar.quest_H; + + uint missingAchievement = 0; + Player leader = this; + ObjectGuid leaderGuid = GetGroup() != null ? GetGroup().GetLeaderGUID() : GetGUID(); + if (leaderGuid != GetGUID()) + leader = Global.ObjAccessor.FindPlayer(leaderGuid); + + if (ar.achievement != 0) + if (leader == null || !leader.HasAchieved(ar.achievement)) + missingAchievement = ar.achievement; + + Difficulty target_difficulty = GetDifficultyID(mapEntry); + MapDifficultyRecord mapDiff = Global.DB2Mgr.GetDownscaledMapDifficultyData(target_map, ref target_difficulty); + if (LevelMin != 0 || LevelMax != 0 || missingItem != 0 || missingQuest != 0 || missingAchievement != 0) + { + if (report) + { + if (missingQuest != 0 && !string.IsNullOrEmpty(ar.questFailedText)) + SendSysMessage("{0}", ar.questFailedText); + else if (mapDiff.Message_lang.HasString(Global.WorldMgr.GetDefaultDbcLocale())) // if (missingAchievement) covered by this case + SendTransferAborted(target_map, TransferAbortReason.Difficulty, (byte)target_difficulty); + else if (missingItem != 0) + GetSession().SendNotification(Global.ObjectMgr.GetCypherString(CypherStrings.LevelMinrequiredAndItem), LevelMin, Global.ObjectMgr.GetItemTemplate(missingItem).GetName()); + else if (LevelMin != 0) + GetSession().SendNotification(Global.ObjectMgr.GetCypherString(CypherStrings.LevelMinrequired), LevelMin); + } + return false; + } + } + return true; + } + + bool IsInstanceLoginGameMasterException() + { + if (!CanBeGameMaster()) + return false; + + SendSysMessage(CypherStrings.InstanceLoginGamemasterException); + return true; + } + + public bool CheckInstanceValidity(bool isLogin) + { + // game masters' instances are always valid + if (IsGameMaster()) + return true; + + // non-instances are always valid + Map map = GetMap(); + if (!map || !map.IsDungeon()) + return true; + + // raid instances require the player to be in a raid group to be valid + if (map.IsRaid() && !WorldConfig.GetBoolValue(WorldCfg.InstanceIgnoreRaid)) + if (!GetGroup() || !GetGroup().isRaidGroup()) + return false; + + Group group = GetGroup(); + if (group) + { + // check if player's group is bound to this instance + InstanceBind bind = group.GetBoundInstance(map.GetDifficultyID(), map.GetId()); + if (bind == null || bind.save == null || bind.save.GetInstanceId() != map.GetInstanceId()) + return false; + + var players = map.GetPlayers(); + if (!players.Empty()) + foreach (var otherPlayer in players) + { + if (otherPlayer.IsGameMaster()) + continue; + if (!otherPlayer.m_InstanceValid) // ignore players that currently have a homebind timer active + continue; + if (group != otherPlayer.GetGroup()) + return false; + } + } + else + { + // instance is invalid if we are not grouped and there are other players + if (map.GetPlayersCountExceptGMs() > 1) + return false; + + // check if the player is bound to this instance + InstanceBind bind = GetBoundInstance(map.GetId(), map.GetDifficultyID()); + if (bind == null || bind.save == null || bind.save.GetInstanceId() != map.GetInstanceId()) + return false; + } + + return true; + } + + public bool CheckInstanceCount(uint instanceId) + { + if (_instanceResetTimes.Count < WorldConfig.GetIntValue(WorldCfg.MaxInstancesPerHour)) + return true; + return _instanceResetTimes.ContainsKey(instanceId); + } + + public void AddInstanceEnterTime(uint instanceId, long enterTime) + { + if (!_instanceResetTimes.ContainsKey(instanceId)) + _instanceResetTimes.Add(instanceId, enterTime + Time.Hour); + } + + public void SendDungeonDifficulty(int forcedDifficulty = -1) + { + DungeonDifficultySet dungeonDifficultySet = new DungeonDifficultySet(); + dungeonDifficultySet.DifficultyID = forcedDifficulty == -1 ? (int)GetDungeonDifficultyID() : forcedDifficulty; + SendPacket(dungeonDifficultySet); + } + + public void SendRaidDifficulty(bool legacy, int forcedDifficulty = -1) + { + RaidDifficultySet raidDifficultySet = new RaidDifficultySet(); + raidDifficultySet.DifficultyID = forcedDifficulty == -1 ? (int)(legacy ? GetLegacyRaidDifficultyID() : GetRaidDifficultyID()) : forcedDifficulty; + raidDifficultySet.Legacy = legacy; + SendPacket(raidDifficultySet); + } + + public void SendResetFailedNotify(uint mapid) + { + SendPacket(new ResetFailedNotify()); + } + + // Reset all solo instances and optionally send a message on success for each + public void ResetInstances(InstanceResetMethod method, bool isRaid, bool isLegacy) + { + // method can be INSTANCE_RESET_ALL, INSTANCE_RESET_CHANGE_DIFFICULTY, INSTANCE_RESET_GROUP_JOIN + + // we assume that when the difficulty changes, all instances that can be reset will be + Difficulty diff = GetDungeonDifficultyID(); + if (isRaid) + { + if (!isLegacy) + diff = GetRaidDifficultyID(); + else + diff = GetLegacyRaidDifficultyID(); + } + + foreach (var pair in m_boundInstances[(int)diff].ToList()) + { + InstanceSave p = pair.Value.save; + MapRecord entry = CliDB.MapStorage.LookupByKey(pair.Key); + if (entry == null || entry.IsRaid() != isRaid || !p.CanReset()) + continue; + + if (method == InstanceResetMethod.All) + { + // the "reset all instances" method can only reset normal maps + if (entry.InstanceType == MapTypes.Raid || diff == Difficulty.Heroic) + continue; + } + + // if the map is loaded, reset it + Map map = Global.MapMgr.FindMap(p.GetMapId(), p.GetInstanceId()); + if (map != null && map.IsDungeon()) + if (!map.ToInstanceMap().Reset(method)) + continue; + + // since this is a solo instance there should not be any players inside + if (method == InstanceResetMethod.All || method == InstanceResetMethod.ChangeDifficulty) + SendResetInstanceSuccess(p.GetMapId()); + + p.DeleteFromDB(); + m_boundInstances[(int)diff].Remove(pair.Key); + + // the following should remove the instance save from the manager and delete it as well + p.RemovePlayer(this); + } + } + + public void SendResetInstanceSuccess(uint MapId) + { + InstanceReset data = new InstanceReset(); + data.MapID = MapId; + SendPacket(data); + } + + public void SendResetInstanceFailed(ResetFailedReason reason, uint MapId) + { + InstanceResetFailed data = new InstanceResetFailed(); + data.MapID = MapId; + data.ResetFailedReason = reason; + SendPacket(data); + } + + public void SendTransferAborted(uint mapid, TransferAbortReason reason, byte arg = 0) + { + TransferAborted transferAborted = new TransferAborted(); + transferAborted.MapID = mapid; + transferAborted.Arg = arg; + transferAborted.TransfertAbort = reason; + SendPacket(transferAborted); + } + + public void SendInstanceResetWarning(uint mapid, Difficulty difficulty, uint time, bool welcome) + { + // type of warning, based on the time remaining until reset + InstanceResetWarningType type; + if (welcome) + type = InstanceResetWarningType.Welcome; + else if (time > 21600) + type = InstanceResetWarningType.Welcome; + else if (time > 3600) + type = InstanceResetWarningType.WarningHours; + else if (time > 300) + type = InstanceResetWarningType.WarningMin; + else + type = InstanceResetWarningType.WarningMinSoon; + + RaidInstanceMessage raidInstanceMessage = new RaidInstanceMessage(); + raidInstanceMessage.Type = type; + raidInstanceMessage.MapID = mapid; + raidInstanceMessage.DifficultyID = difficulty; + + InstanceBind bind = GetBoundInstance(mapid, difficulty); + if (bind != null) + raidInstanceMessage.Locked = bind.perm; + else + raidInstanceMessage.Locked = false; + raidInstanceMessage.Extended = false; + SendPacket(raidInstanceMessage); + } + + public override void UpdateUnderwaterState(Map m, float x, float y, float z) + { + LiquidData liquid_status; + ZLiquidStatus res = m.getLiquidStatus(x, y, z, MapConst.MapAllLiquidTypes, out liquid_status); + if (res == 0) + { + m_MirrorTimerFlags &= ~(PlayerUnderwaterState.InWater | PlayerUnderwaterState.InLava | PlayerUnderwaterState.InSlime | PlayerUnderwaterState.InDarkWater); + if (_lastLiquid != null && _lastLiquid.SpellID != 0) + RemoveAurasDueToSpell(_lastLiquid.SpellID); + + _lastLiquid = null; + return; + } + uint liqEntry = liquid_status.entry; + if (liqEntry != 0) + { + LiquidTypeRecord liquid = CliDB.LiquidTypeStorage.LookupByKey(liqEntry); + if (_lastLiquid != null && _lastLiquid.SpellID != 0 && _lastLiquid != liquid) + RemoveAurasDueToSpell(_lastLiquid.SpellID); + + if (liquid != null && liquid.SpellID != 0) + { + if (res.HasAnyFlag(ZLiquidStatus.UnderWater | ZLiquidStatus.InWater)) + { + if (!HasAura(liquid.SpellID)) + CastSpell(this, liquid.SpellID, true); + } + else + RemoveAurasDueToSpell(liquid.SpellID); + } + + _lastLiquid = liquid; + } + else if (_lastLiquid != null && _lastLiquid.SpellID != 0) + { + RemoveAurasDueToSpell(_lastLiquid.SpellID); + _lastLiquid = null; + } + + + // All liquids type - check under water position + if (liquid_status.type_flags.HasAnyFlag(MapConst.MapLiquidTypeWater | MapConst.MapLiquidTypeOcean | MapConst.MapLiquidTypeMagma | MapConst.MapLiquidTypeSlime)) + { + if (res.HasAnyFlag(ZLiquidStatus.UnderWater)) + m_MirrorTimerFlags |= PlayerUnderwaterState.InWater; + else + m_MirrorTimerFlags &= ~PlayerUnderwaterState.InWater; + } + + // Allow travel in dark water on taxi or transport + if (liquid_status.type_flags.HasAnyFlag(MapConst.MapLiquidTypeDarkWater) && !IsInFlight() && GetTransport() == null) + m_MirrorTimerFlags |= PlayerUnderwaterState.InDarkWater; + else + m_MirrorTimerFlags &= ~PlayerUnderwaterState.InDarkWater; + + // in lava check, anywhere in lava level + if (liquid_status.type_flags.HasAnyFlag(MapConst.MapLiquidTypeMagma)) + { + if (res.HasAnyFlag(ZLiquidStatus.UnderWater | ZLiquidStatus.InWater | ZLiquidStatus.WaterWalk)) + m_MirrorTimerFlags |= PlayerUnderwaterState.InLava; + else + m_MirrorTimerFlags &= ~PlayerUnderwaterState.InLava; + } + // in slime check, anywhere in slime level + if (liquid_status.type_flags.HasAnyFlag(MapConst.MapLiquidTypeSlime)) + { + if (res.HasAnyFlag(ZLiquidStatus.UnderWater | ZLiquidStatus.InWater | ZLiquidStatus.WaterWalk)) + m_MirrorTimerFlags |= PlayerUnderwaterState.InSlime; + else + m_MirrorTimerFlags &= ~PlayerUnderwaterState.InSlime; + } + } + } +} diff --git a/Game/Entities/Player/Player.PvP.cs b/Game/Entities/Player/Player.PvP.cs new file mode 100644 index 000000000..21bef7d74 --- /dev/null +++ b/Game/Entities/Player/Player.PvP.cs @@ -0,0 +1,753 @@ +/* + * Copyright (C) 2012-2017 CypherCore + * + * 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, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +using Framework.Constants; +using Framework.Database; +using Game.Arenas; +using Game.BattleFields; +using Game.BattleGrounds; +using Game.DataStorage; +using Game.Network.Packets; +using Game.PvP; +using System; +using System.Collections.Generic; + +namespace Game.Entities +{ + public partial class Player + { + //PvP + public void UpdateHonorFields() + { + // called when rewarding honor and at each save + long now = Time.UnixTime; + long today = (Time.UnixTime / Time.Day) * Time.Day; + + if (m_lastHonorUpdateTime < today) + { + long yesterday = today - Time.Day; + + // update yesterday's contribution + if (m_lastHonorUpdateTime >= yesterday) + { + // this is the first update today, reset today's contribution + ushort killsToday = GetUInt16Value(PlayerFields.Kills, 0); + SetUInt16Value(PlayerFields.Kills, 0, 0); + SetUInt16Value(PlayerFields.Kills, 1, killsToday); + + } + else + { + // no honor/kills yesterday or today, reset + SetUInt32Value(PlayerFields.Kills, 0); + } + } + + m_lastHonorUpdateTime = now; + } + public bool RewardHonor(Unit victim, uint groupsize, int honor = -1, bool pvptoken = false) + { + // do not reward honor in arenas, but enable onkill spellproc + if (InArena()) + { + if (!victim || victim == this || !victim.IsTypeId(TypeId.Player)) + return false; + + if (GetBGTeam() == victim.ToPlayer().GetBGTeam()) + return false; + + return true; + } + + // 'Inactive' this aura prevents the player from gaining honor points and BattlegroundTokenizer + if (HasAura(BattlegroundConst.SpellAuraPlayerInactive)) + return false; + + ObjectGuid victim_guid = ObjectGuid.Empty; + uint victim_rank = 0; + + // need call before fields update to have chance move yesterday data to appropriate fields before today data change. + UpdateHonorFields(); + + // do not reward honor in arenas, but return true to enable onkill spellproc + if (InBattleground() && GetBattleground() && GetBattleground().isArena()) + return true; + + // Promote to float for calculations + float honor_f = honor; + + if (honor_f <= 0) + { + if (!victim || victim == this || victim.HasAuraType(AuraType.NoPvpCredit)) + return false; + + victim_guid = victim.GetGUID(); + Player plrVictim = victim.ToPlayer(); + if (plrVictim) + { + if (GetTeam() == plrVictim.GetTeam() && !Global.WorldMgr.IsFFAPvPRealm()) + return false; + + byte k_level = (byte)getLevel(); + byte k_grey = (byte)Formulas.GetGrayLevel(k_level); + byte v_level = (byte)victim.getLevel(); + + if (v_level <= k_grey) + return false; + + // PLAYER_CHOSEN_TITLE VALUES DESCRIPTION + // [0] Just name + // [1..14] Alliance honor titles and player name + // [15..28] Horde honor titles and player name + // [29..38] Other title and player name + // [39+] Nothing + uint victim_title = victim.GetUInt32Value(PlayerFields.ChosenTitle); + // Get Killer titles, CharTitlesEntry.bit_index + // Ranks: + // title[1..14] . rank[5..18] + // title[15..28] . rank[5..18] + // title[other] . 0 + if (victim_title == 0) + victim_guid.Clear(); // Don't show HK: message, only log. + else if (victim_title < 15) + victim_rank = victim_title + 4; + else if (victim_title < 29) + victim_rank = victim_title - 14 + 4; + else + victim_guid.Clear(); // Don't show HK: message, only log. + + honor_f = (float)Math.Ceiling(Formulas.hk_honor_at_level_f(k_level) * (v_level - k_grey) / (k_level - k_grey)); + + // count the number of playerkills in one day + ApplyModUInt16Value(PlayerFields.Kills, 0, 1, true); + // and those in a lifetime + ApplyModUInt32Value(PlayerFields.LifetimeHonorableKills, 1, true); + UpdateCriteria(CriteriaTypes.EarnHonorableKill); + UpdateCriteria(CriteriaTypes.HkClass, (uint)victim.GetClass()); + UpdateCriteria(CriteriaTypes.HkRace, (uint)victim.GetRace()); + UpdateCriteria(CriteriaTypes.HonorableKillAtArea, GetAreaId()); + UpdateCriteria(CriteriaTypes.HonorableKill, 1, 0, 0, victim); + } + else + { + if (!victim.ToCreature().IsRacialLeader()) + return false; + + honor_f = 100.0f; // ??? need more info + victim_rank = 19; // HK: Leader + } + } + + if (victim != null) + { + if (groupsize > 1) + honor_f /= groupsize; + + // apply honor multiplier from aura (not stacking-get highest) + MathFunctions.AddPct(ref honor_f, GetMaxPositiveAuraModifier(AuraType.ModHonorGainPct)); + honor_f += _restMgr.GetRestBonusFor(RestTypes.Honor, (uint)honor_f); + } + + honor_f *= WorldConfig.GetFloatValue(WorldCfg.RateHonor); + // Back to int now + honor = (int)honor_f; + // honor - for show honor points in log + // victim_guid - for show victim name in log + // victim_rank [1..4] HK: + // victim_rank [5..19] HK: + // victim_rank [0, 20+] HK: <> + PvPCredit data = new PvPCredit(); + data.Honor = honor; + data.OriginalHonor = honor; + data.Target = victim_guid; + data.Rank = victim_rank; + + SendPacket(data); + + AddHonorXP((uint)honor); + + if (InBattleground() && honor > 0) + { + Battleground bg = GetBattleground(); + if (bg != null) + { + bg.UpdatePlayerScore(this, ScoreType.BonusHonor, (uint)honor, false); //false: prevent looping + } + } + + if (WorldConfig.GetBoolValue(WorldCfg.PvpTokenEnable) && pvptoken) + { + if (!victim || victim == this || victim.HasAuraType(AuraType.NoPvpCredit)) + return true; + + if (victim.IsTypeId(TypeId.Player)) + { + // Check if allowed to receive it in current map + int MapType = WorldConfig.GetIntValue(WorldCfg.PvpTokenMapType); + if ((MapType == 1 && !InBattleground() && !IsFFAPvP()) + || (MapType == 2 && !IsFFAPvP()) + || (MapType == 3 && !InBattleground())) + return true; + + uint itemId = WorldConfig.GetUIntValue(WorldCfg.PvpTokenId); + uint count = WorldConfig.GetUIntValue(WorldCfg.PvpTokenCount); + + if (AddItem(itemId, count)) + SendSysMessage("You have been awarded a token for slaying another player."); + } + } + + return true; + } + + void _InitHonorLevelOnLoadFromDB(uint honor, uint honorLevel, uint prestigeLevel) + { + SetUInt32Value(PlayerFields.HonorLevel, honorLevel); + SetUInt32Value(PlayerFields.Prestige, prestigeLevel); + UpdateHonorNextLevel(); + + AddHonorXP(honor); + if (CanPrestige()) + Prestige(); + } + + void RewardPlayerWithRewardPack(uint rewardPackID) + { + RewardPlayerWithRewardPack(CliDB.RewardPackStorage.LookupByKey(rewardPackID)); + } + + void RewardPlayerWithRewardPack(RewardPackRecord rewardPackEntry) + { + if (rewardPackEntry == null) + return; + + CharTitlesRecord charTitlesEntry = CliDB.CharTitlesStorage.LookupByKey(rewardPackEntry.TitleID); + if (charTitlesEntry != null) + SetTitle(charTitlesEntry); + + ModifyMoney(rewardPackEntry.Money); + var rewardPackXItems = Global.DB2Mgr.GetRewardPackItemsByRewardID(rewardPackEntry.Id); + if (rewardPackXItems != null) + { + foreach (RewardPackXItemRecord rewardPackXItem in rewardPackXItems) + AddItem(rewardPackXItem.ItemID, rewardPackXItem.Amount); + } + } + + public void AddHonorXP(uint xp) + { + uint currentHonorXP = GetUInt32Value(PlayerFields.Honor); + uint nextHonorLevelXP = GetUInt32Value(PlayerFields.HonorNextLevel); + uint newHonorXP = currentHonorXP + xp; + uint honorLevel = GetHonorLevel(); + + if (xp < 1 || getLevel() < PlayerConst.LevelMinHonor || IsMaxHonorLevelAndPrestige()) + return; + + while (newHonorXP >= nextHonorLevelXP) + { + newHonorXP -= nextHonorLevelXP; + + if (honorLevel < PlayerConst.MaxHonorLevel) + SetHonorLevel((byte)(honorLevel + 1)); + + honorLevel = GetHonorLevel(); + nextHonorLevelXP = GetUInt32Value(PlayerFields.HonorNextLevel); + } + + SetUInt32Value(PlayerFields.Honor, IsMaxHonorLevelAndPrestige() ? 0 : newHonorXP); + } + + void SetHonorLevel(byte level) + { + byte oldHonorLevel = (byte)GetHonorLevel(); + byte prestige = (byte)GetPrestigeLevel(); + if (level == oldHonorLevel) + return; + + uint rewardPackID = Global.DB2Mgr.GetRewardPackIDForPvpRewardByHonorLevelAndPrestige(level, prestige); + RewardPlayerWithRewardPack(rewardPackID); + + SetUInt32Value(PlayerFields.HonorLevel, level); + UpdateHonorNextLevel(); + + UpdateCriteria(CriteriaTypes.HonorLevelReached); + + // This code is here because no link was found between those items and this reward condition in the db2 files. + // Interesting CriteriaTree found: Tree ids: 51140, 51156 (criteria id 31773, modifier tree id 37759) + if (level == 50 && prestige == 1) + { + if (GetTeam() == Team.Alliance) + AddItem(138992, 1); + else + AddItem(138996, 1); + } + + if (CanPrestige()) + Prestige(); + } + + public void Prestige() + { + SetUInt32Value(PlayerFields.Prestige, GetPrestigeLevel() + 1); + SetUInt32Value(PlayerFields.HonorLevel, 1); + UpdateHonorNextLevel(); + + UpdateCriteria(CriteriaTypes.PrestigeReached); + } + + public bool CanPrestige() + { + if (GetSession().GetExpansion() >= Expansion.Legion && getLevel() >= PlayerConst.LevelMinHonor && GetHonorLevel() >= PlayerConst.MaxHonorLevel && GetPrestigeLevel() < Global.DB2Mgr.GetMaxPrestige()) + return true; + + return false; + } + + bool IsMaxPrestige() + { + return GetPrestigeLevel() == Global.DB2Mgr.GetMaxPrestige(); + } + + void UpdateHonorNextLevel() + { + uint prestige = Math.Min(16 - 1, GetPrestigeLevel()); + SetUInt32Value(PlayerFields.HonorNextLevel, (uint)CliDB.HonorLevelGameTable.GetRow(GetHonorLevel()).Prestige[prestige]); + } + + public uint GetPrestigeLevel() { return GetUInt32Value(PlayerFields.Prestige); } + public uint GetHonorLevel() { return GetUInt32Value(PlayerFields.HonorLevel); } + public bool IsMaxHonorLevelAndPrestige() { return IsMaxPrestige() && GetHonorLevel() == PlayerConst.MaxHonorLevel; } + + + //BGs + public Battleground GetBattleground() + { + if (GetBattlegroundId() == 0) + return null; + + return Global.BattlegroundMgr.GetBattleground(GetBattlegroundId(), m_bgData.bgTypeID); + } + + public bool InBattlegroundQueue() + { + for (byte i = 0; i < SharedConst.MaxPlayerBGQueues; ++i) + if (m_bgBattlegroundQueueID[i].bgQueueTypeId != BattlegroundQueueTypeId.None) + return true; + return false; + } + + public BattlegroundQueueTypeId GetBattlegroundQueueTypeId(uint index) + { + if (index < SharedConst.MaxPlayerBGQueues) + return m_bgBattlegroundQueueID[index].bgQueueTypeId; + + return BattlegroundQueueTypeId.None; + } + + public uint GetBattlegroundQueueIndex(BattlegroundQueueTypeId bgQueueTypeId) + { + for (byte i = 0; i < SharedConst.MaxPlayerBGQueues; ++i) + if (m_bgBattlegroundQueueID[i].bgQueueTypeId == bgQueueTypeId) + return i; + return SharedConst.MaxPlayerBGQueues; + } + + public bool IsInvitedForBattlegroundQueueType(BattlegroundQueueTypeId bgQueueTypeId) + { + for (byte i = 0; i < SharedConst.MaxPlayerBGQueues; ++i) + if (m_bgBattlegroundQueueID[i].bgQueueTypeId == bgQueueTypeId) + return m_bgBattlegroundQueueID[i].invitedToInstance != 0; + return false; + } + + public bool InBattlegroundQueueForBattlegroundQueueType(BattlegroundQueueTypeId bgQueueTypeId) + { + return GetBattlegroundQueueIndex(bgQueueTypeId) < SharedConst.MaxPlayerBGQueues; + } + + public void SetBattlegroundId(uint val, BattlegroundTypeId bgTypeId) + { + m_bgData.bgInstanceID = val; + m_bgData.bgTypeID = bgTypeId; + } + + public uint AddBattlegroundQueueId(BattlegroundQueueTypeId val) + { + for (byte i = 0; i < SharedConst.MaxPlayerBGQueues; ++i) + { + if (m_bgBattlegroundQueueID[i].bgQueueTypeId == BattlegroundQueueTypeId.None || m_bgBattlegroundQueueID[i].bgQueueTypeId == val) + { + m_bgBattlegroundQueueID[i].bgQueueTypeId = val; + m_bgBattlegroundQueueID[i].invitedToInstance = 0; + m_bgBattlegroundQueueID[i].joinTime = Time.GetMSTime(); + return i; + } + } + return SharedConst.MaxPlayerBGQueues; + } + + public bool HasFreeBattlegroundQueueId() + { + for (byte i = 0; i < SharedConst.MaxPlayerBGQueues; ++i) + if (m_bgBattlegroundQueueID[i].bgQueueTypeId == BattlegroundQueueTypeId.None) + return true; + return false; + } + + public void RemoveBattlegroundQueueId(BattlegroundQueueTypeId val) + { + for (byte i = 0; i < SharedConst.MaxPlayerBGQueues; ++i) + { + if (m_bgBattlegroundQueueID[i].bgQueueTypeId == val) + { + m_bgBattlegroundQueueID[i].bgQueueTypeId = BattlegroundQueueTypeId.None; + m_bgBattlegroundQueueID[i].invitedToInstance = 0; + m_bgBattlegroundQueueID[i].joinTime = 0; + return; + } + } + } + + public void SetInviteForBattlegroundQueueType(BattlegroundQueueTypeId bgQueueTypeId, uint instanceId) + { + for (byte i = 0; i < SharedConst.MaxPlayerBGQueues; ++i) + if (m_bgBattlegroundQueueID[i].bgQueueTypeId == bgQueueTypeId) + m_bgBattlegroundQueueID[i].invitedToInstance = instanceId; + } + + public bool IsInvitedForBattlegroundInstance(uint instanceId) + { + for (byte i = 0; i < SharedConst.MaxPlayerBGQueues; ++i) + if (m_bgBattlegroundQueueID[i].invitedToInstance == instanceId) + return true; + return false; + } + + public WorldLocation GetBattlegroundEntryPoint() { return m_bgData.joinPos; } + + public bool InBattleground() { return m_bgData.bgInstanceID != 0; } + public uint GetBattlegroundId() { return m_bgData.bgInstanceID; } + public BattlegroundTypeId GetBattlegroundTypeId() { return m_bgData.bgTypeID; } + + public uint GetBattlegroundQueueJoinTime(BattlegroundQueueTypeId bgQueueTypeId) + { + for (byte i = 0; i < SharedConst.MaxPlayerBGQueues; ++i) + if (m_bgBattlegroundQueueID[i].bgQueueTypeId == bgQueueTypeId) + return m_bgBattlegroundQueueID[i].joinTime; + return 0; + } + + public bool CanUseBattlegroundObject(GameObject gameobject) + { + // It is possible to call this method with a null pointer, only skipping faction check. + if (gameobject) + { + FactionTemplateRecord playerFaction = GetFactionTemplateEntry(); + FactionTemplateRecord faction = CliDB.FactionTemplateStorage.LookupByKey(gameobject.GetUInt32Value(GameObjectFields.Faction)); + + if (playerFaction != null && faction != null && !playerFaction.IsFriendlyTo(faction)) + return false; + } + + // BUG: sometimes when player clicks on flag in AB - client won't send gameobject_use, only gameobject_report_use packet + // Note: Mount, stealth and invisibility will be removed when used + return (!isTotalImmune() && // Damage immune + !HasAura(BattlegroundConst.SpellRecentlyDroppedFlag) && // Still has recently held flag debuff + IsAlive()); // Alive + } + + public bool CanCaptureTowerPoint() + { + return (!HasStealthAura() && // not stealthed + !HasInvisibilityAura() && // not invisible + IsAlive()); // live player + } + + public void SetBattlegroundEntryPoint() + { + // Taxi path store + if (!m_taxi.empty()) + { + m_bgData.mountSpell = 0; + m_bgData.taxiPath[0] = m_taxi.GetTaxiSource(); + m_bgData.taxiPath[1] = m_taxi.GetTaxiDestination(); + + // On taxi we don't need check for dungeon + m_bgData.joinPos = new WorldLocation(GetMapId(), GetPositionX(), GetPositionY(), GetPositionZ(), GetOrientation()); + } + else + { + m_bgData.ClearTaxiPath(); + + // Mount spell id storing + if (IsMounted()) + { + var auras = GetAuraEffectsByType(AuraType.Mounted); + if (!auras.Empty()) + m_bgData.mountSpell = auras[0].GetId(); + } + else + m_bgData.mountSpell = 0; + + // If map is dungeon find linked graveyard + if (GetMap().IsDungeon()) + { + WorldSafeLocsRecord entry = Global.ObjectMgr.GetClosestGraveYard(GetPositionX(), GetPositionY(), GetPositionZ(), GetMapId(), GetTeam()); + if (entry != null) + m_bgData.joinPos = new WorldLocation(entry.MapID, entry.Loc.X, entry.Loc.Y, entry.Loc.Z, 0.0f); + else + Log.outError(LogFilter.Player, "SetBattlegroundEntryPoint: Dungeon map {0} has no linked graveyard, setting home location as entry point.", GetMapId()); + } + // If new entry point is not BG or arena set it + else if (!GetMap().IsBattlegroundOrArena()) + m_bgData.joinPos = new WorldLocation(GetMapId(), GetPositionX(), GetPositionY(), GetPositionZ(), GetOrientation()); + } + + if (m_bgData.joinPos.GetMapId() == 0xFFFFFFFF) // In error cases use homebind position + m_bgData.joinPos = new WorldLocation(GetHomebind()); + } + + public void SetBGTeam(Team team) + { + m_bgData.bgTeam = (uint)team; + SetByteValue(PlayerFields.Bytes4, PlayerFieldOffsets.Bytes4OffsetArenaFaction, (byte)(team == Team.Alliance ? 1 : 0)); + } + + public Team GetBGTeam() + { + return m_bgData.bgTeam != 0 ? (Team)m_bgData.bgTeam : GetTeam(); + } + + public void LeaveBattleground(bool teleportToEntryPoint = true) + { + Battleground bg = GetBattleground(); + if (bg) + { + bg.RemovePlayerAtLeave(GetGUID(), teleportToEntryPoint, true); + + // call after remove to be sure that player resurrected for correct cast + if (bg.isBattleground() && !IsGameMaster() && WorldConfig.GetBoolValue(WorldCfg.BattlegroundCastDeserter)) + { + if (bg.GetStatus() == BattlegroundStatus.InProgress || bg.GetStatus() == BattlegroundStatus.WaitJoin) + { + //lets check if player was teleported from BG and schedule delayed Deserter spell cast + if (IsBeingTeleportedFar()) + { + ScheduleDelayedOperation(PlayerDelayedOperations.SpellCastDeserter); + return; + } + + CastSpell(this, 26013, true); // Deserter + } + } + } + } + + public bool CanJoinToBattleground(Battleground bg) + { + // check Deserter debuff + if (HasAura(26013)) + return false; + + if (bg.isArena() && !GetSession().HasPermission(RBACPermissions.JoinArenas)) + return false; + + if (bg.IsRandom() && !GetSession().HasPermission(RBACPermissions.JoinRandomBg)) + return false; + + if (!GetSession().HasPermission(RBACPermissions.JoinNormalBg)) + return false; + + return true; + } + + public void ClearAfkReports() { m_bgData.bgAfkReporter.Clear(); } + + bool CanReportAfkDueToLimit() + { + // a player can complain about 15 people per 5 minutes + if (m_bgData.bgAfkReportedCount++ >= 15) + return false; + + return true; + } + + /// + /// This player has been blamed to be inactive in a Battleground + /// + /// + public void ReportedAfkBy(Player reporter) + { + ReportPvPPlayerAFKResult reportAfkResult = new ReportPvPPlayerAFKResult(); + reportAfkResult.Offender = GetGUID(); + Battleground bg = GetBattleground(); + // Battleground also must be in progress! + if (!bg || bg != reporter.GetBattleground() || GetTeam() != reporter.GetTeam() || bg.GetStatus() != BattlegroundStatus.InProgress) + { + reporter.SendPacket(reportAfkResult); + return; + } + + // check if player has 'Idle' or 'Inactive' debuff + if (!m_bgData.bgAfkReporter.Contains(reporter.GetGUID()) && !HasAura(43680) && !HasAura(43681) && reporter.CanReportAfkDueToLimit()) + { + m_bgData.bgAfkReporter.Add(reporter.GetGUID()); + // by default 3 players have to complain to apply debuff + if (m_bgData.bgAfkReporter.Count >= WorldConfig.GetIntValue(WorldCfg.BattlegroundReportAfk)) + { + // cast 'Idle' spell + CastSpell(this, 43680, true); + m_bgData.bgAfkReporter.Clear(); + reportAfkResult.NumBlackMarksOnOffender = (byte)m_bgData.bgAfkReporter.Count; + reportAfkResult.NumPlayersIHaveReported = reporter.m_bgData.bgAfkReportedCount; + reportAfkResult.Result = ReportPvPPlayerAFKResult.ResultCode.Success; + } + } + + reporter.SendPacket(reportAfkResult); + } + + public bool GetRandomWinner() { return m_IsBGRandomWinner; } + public void SetRandomWinner(bool isWinner) + { + m_IsBGRandomWinner = isWinner; + if (m_IsBGRandomWinner) + { + PreparedStatement stmt = DB.Characters.GetPreparedStatement(CharStatements.INS_BATTLEGROUND_RANDOM); + stmt.AddValue(0, GetGUID().GetCounter()); + DB.Characters.Execute(stmt); + } + } + + public bool GetBGAccessByLevel(BattlegroundTypeId bgTypeId) + { + // get a template bg instead of running one + Battleground bg = Global.BattlegroundMgr.GetBattlegroundTemplate(bgTypeId); + if (!bg) + return false; + + // limit check leel to dbc compatible level range + uint level = getLevel(); + if (level > WorldConfig.GetIntValue(WorldCfg.MaxPlayerLevel)) + level = WorldConfig.GetUIntValue(WorldCfg.MaxPlayerLevel); + + if (level < bg.GetMinLevel() || level > bg.GetMaxLevel()) + return false; + + return true; + } + + void SendBGWeekendWorldStates() + { + foreach (var bl in CliDB.BattlemasterListStorage.Values) + { + if (bl.HolidayWorldState != 0) + { + if (Global.BattlegroundMgr.IsBGWeekend((BattlegroundTypeId)bl.Id)) + SendUpdateWorldState(bl.HolidayWorldState, 1); + else + SendUpdateWorldState(bl.HolidayWorldState, 0); + } + } + } + + public void SendPvpRewards() + { + //WorldPacket packet(SMSG_REQUEST_PVP_REWARDS_RESPONSE, 24); + //SendPacket(packet); + } + + //Battlefields + void SendBattlefieldWorldStates() + { + // Send misc stuff that needs to be sent on every login, like the battle timers. + if (WorldConfig.GetBoolValue(WorldCfg.WintergraspEnable)) + { + BattleField wg = Global.BattleFieldMgr.GetBattlefieldByBattleId(1);//Wintergrasp battle + if (wg != null) + { + SendUpdateWorldState(3801, (uint)(wg.IsWarTime() ? 0 : 1)); + uint timer = wg.IsWarTime() ? 0 : (wg.GetTimer() / 1000); // 0 - Time to next battle + SendUpdateWorldState(4354, (uint)(Time.UnixTime + timer)); + } + } + } + + //Arenas + public void SetArenaTeamInfoField(byte slot, ArenaTeamInfoType type, uint value) + { + SetUInt32Value(PlayerFields.ArenaTeamInfo11 + (slot * (int)ArenaTeamInfoType.End) + (int)type, value); + } + + public void SetInArenaTeam(uint ArenaTeamId, byte slot, byte type) + { + SetArenaTeamInfoField(slot, ArenaTeamInfoType.Id, ArenaTeamId); + SetArenaTeamInfoField(slot, ArenaTeamInfoType.Type, type); + } + + public static uint GetArenaTeamIdFromDB(ObjectGuid guid, byte type) + { + PreparedStatement stmt = DB.Characters.GetPreparedStatement(CharStatements.SEL_ARENA_TEAM_ID_BY_PLAYER_GUID); + stmt.AddValue(0, guid.GetCounter()); + stmt.AddValue(1, type); + SQLResult result = DB.Characters.Query(stmt); + + if (result.IsEmpty()) + return 0; + + return result.Read(0); + } + + public static void LeaveAllArenaTeams(ObjectGuid guid) + { + PreparedStatement stmt = DB.Characters.GetPreparedStatement(CharStatements.SEL_PLAYER_ARENA_TEAMS); + stmt.AddValue(0, guid.GetCounter()); + SQLResult result = DB.Characters.Query(stmt); + + if (result.IsEmpty()) + return; + + do + { + uint arenaTeamId = result.Read(0); + if (arenaTeamId != 0) + { + ArenaTeam arenaTeam = Global.ArenaTeamMgr.GetArenaTeamById(arenaTeamId); + if (arenaTeam != null) + arenaTeam.DelMember(guid, true); + } + } + while (result.NextRow()); + } + public uint GetArenaTeamId(byte slot) { return GetUInt32Value(PlayerFields.ArenaTeamInfo11 + (slot * (int)ArenaTeamInfoType.End) + (int)ArenaTeamInfoType.Id); } + public uint GetArenaPersonalRating(byte slot) { return GetUInt32Value(PlayerFields.ArenaTeamInfo11 + (slot * (int)ArenaTeamInfoType.End) + (int)ArenaTeamInfoType.PersonalRating); } + public void SetArenaTeamIdInvited(uint ArenaTeamId) { m_ArenaTeamIdInvited = ArenaTeamId; } + public uint GetArenaTeamIdInvited() { return m_ArenaTeamIdInvited; } + public uint GetRBGPersonalRating() { return 0; } + + //OutdoorPVP + public bool IsOutdoorPvPActive() + { + return IsAlive() && !HasInvisibilityAura() && !HasStealthAura() && IsPvP() && !HasUnitMovementFlag(MovementFlag.Flying) && !IsInFlight(); + } + public OutdoorPvP GetOutdoorPvP() + { + return Global.OutdoorPvPMgr.GetOutdoorPvPToZoneId(GetZoneId()); + } + } +} diff --git a/Game/Entities/Player/Player.Quest.cs b/Game/Entities/Player/Player.Quest.cs new file mode 100644 index 000000000..6760d4620 --- /dev/null +++ b/Game/Entities/Player/Player.Quest.cs @@ -0,0 +1,2992 @@ +/* + * Copyright (C) 2012-2017 CypherCore + * + * 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, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +using Framework.Constants; +using Framework.Database; +using Game.Conditions; +using Game.DataStorage; +using Game.Groups; +using Game.Mails; +using Game.Maps; +using Game.Misc; +using Game.Network.Packets; +using Game.Spells; +using System; +using System.Collections.Generic; +using System.Diagnostics.Contracts; + +namespace Game.Entities +{ + public partial class Player + { + public ObjectGuid GetDivider() { return m_divider; } + public void SetDivider(ObjectGuid guid) { m_divider = guid; } + + uint GetInGameTime() { return m_ingametime; } + public void SetInGameTime(uint time) { m_ingametime = time; } + + void AddTimedQuest(uint questId) { m_timedquests.Add(questId); } + public void RemoveTimedQuest(uint questId) { m_timedquests.Remove(questId); } + + public List getRewardedQuests() { return m_RewardedQuests; } + Dictionary getQuestStatusMap() { return m_QuestStatus; } + + public int GetRewardedQuestCount() { return m_RewardedQuests.Count; } + + public void LearnQuestRewardedSpells(Quest quest) + { + //wtf why is rewardspell a uint if it can me -1 + int spell_id = Convert.ToInt32(quest.RewardSpell); + uint src_spell_id = quest.SourceSpellID; + + // skip quests without rewarded spell + if (spell_id == 0) + return; + + // if RewSpellCast = -1 we remove aura do to SrcSpell from player. + if (spell_id == -1 && src_spell_id != 0) + { + RemoveAurasDueToSpell(src_spell_id); + return; + } + + SpellInfo spellInfo = Global.SpellMgr.GetSpellInfo((uint)spell_id); + if (spellInfo == null) + return; + + // check learned spells state + bool found = false; + foreach (SpellEffectInfo eff in spellInfo.GetEffectsForDifficulty(Difficulty.None)) + { + if (eff != null && eff.Effect == SpellEffectName.LearnSpell && !HasSpell(eff.TriggerSpell)) + { + found = true; + break; + } + } + + // skip quests with not teaching spell or already known spell + if (!found) + return; + + SpellEffectInfo effect = spellInfo.GetEffect(0); + if (effect == null) + return; + + uint learned_0 = effect.TriggerSpell; + if (!HasSpell(learned_0)) + { + SpellInfo learnedInfo = Global.SpellMgr.GetSpellInfo(learned_0); + if (learnedInfo == null) + return; + + // profession specialization can be re-learned from npc + if (learnedInfo.GetEffect(0).Effect == SpellEffectName.TradeSkill && learnedInfo.GetEffect(1).Effect == 0 && learnedInfo.SpellLevel == 0) + return; + } + + CastSpell(this, (uint)spell_id, true); + } + + public void LearnQuestRewardedSpells() + { + // learn spells received from quest completing + foreach (var questId in m_RewardedQuests) + { + Quest quest = Global.ObjectMgr.GetQuestTemplate(questId); + if (quest == null) + continue; + + LearnQuestRewardedSpells(quest); + } + } + + public void DailyReset() + { + foreach (uint questId in GetDynamicValues(PlayerDynamicFields.DailyQuests)) + { + uint questBit = Global.DB2Mgr.GetQuestUniqueBitFlag(questId); + if (questBit != 0) + SetQuestCompletedBit(questBit, false); + } + + DailyQuestsReset dailyQuestsReset = new DailyQuestsReset(); + dailyQuestsReset.Count = GetDynamicValues(PlayerDynamicFields.DailyQuests).Count; + SendPacket(dailyQuestsReset); + + ClearDynamicValue(PlayerDynamicFields.DailyQuests); + + m_DFQuests.Clear(); // Dungeon Finder Quests. + + // DB data deleted in caller + m_DailyQuestChanged = false; + m_lastDailyQuestTime = 0; + + if (_garrison != null) + _garrison.ResetFollowerActivationLimit(); + } + + public void ResetWeeklyQuestStatus() + { + if (m_weeklyquests.Empty()) + return; + + foreach (uint questId in m_weeklyquests) + { + uint questBit = Global.DB2Mgr.GetQuestUniqueBitFlag(questId); + if (questBit != 0) + SetQuestCompletedBit(questBit, false); + } + + m_weeklyquests.Clear(); + // DB data deleted in caller + m_WeeklyQuestChanged = false; + + } + + public void ResetSeasonalQuestStatus(ushort event_id) + { + var eventList = m_seasonalquests.LookupByKey(event_id); + if (eventList.Empty()) + return; + + if (eventList.Empty()) + return; + + foreach (uint questId in eventList) + { + uint questBit = Global.DB2Mgr.GetQuestUniqueBitFlag(questId); + if (questBit != 0) + SetQuestCompletedBit(questBit, false); + } + + m_seasonalquests.Remove(event_id); + // DB data deleted in caller + m_SeasonalQuestChanged = false; + } + + public void ResetMonthlyQuestStatus() + { + if (m_monthlyquests.Empty()) + return; + + foreach (uint questId in m_monthlyquests) + { + uint questBit = Global.DB2Mgr.GetQuestUniqueBitFlag(questId); + if (questBit != 0) + SetQuestCompletedBit(questBit, false); + } + + m_monthlyquests.Clear(); + // DB data deleted in caller + m_MonthlyQuestChanged = false; + } + + public bool CanInteractWithQuestGiver(WorldObject questGiver) + { + switch (questGiver.GetTypeId()) + { + case TypeId.Unit: + return GetNPCIfCanInteractWith(questGiver.GetGUID(), NPCFlags.QuestGiver) != null; + case TypeId.GameObject: + return GetGameObjectIfCanInteractWith(questGiver.GetGUID(), GameObjectTypes.QuestGiver) != null; + case TypeId.Player: + return IsAlive() && questGiver.ToPlayer().IsAlive(); + case TypeId.Item: + return IsAlive(); + default: + break; + } + return false; + } + + public int GetQuestLevel(Quest quest) { return quest != null && (quest.Level > 0) ? quest.Level : (int)getLevel(); } + + public bool IsQuestRewarded(uint quest_id) + { + return m_RewardedQuests.Contains(quest_id); + } + + public void PrepareQuestMenu(ObjectGuid guid) + { + List objectQR; + List objectQIR; + + // pets also can have quests + Creature creature = ObjectAccessor.GetCreatureOrPetOrVehicle(this, guid); + if (creature != null) + { + objectQR = Global.ObjectMgr.GetCreatureQuestRelationBounds(creature.GetEntry()); + objectQIR = Global.ObjectMgr.GetCreatureQuestInvolvedRelationBounds(creature.GetEntry()); + } + else + { + //we should obtain map from GetMap() in 99% of cases. Special case + //only for quests which cast teleport spells on player + Map _map = IsInWorld ? GetMap() : Global.MapMgr.FindMap(GetMapId(), GetInstanceId()); + Contract.Assert(_map != null); + GameObject gameObject = _map.GetGameObject(guid); + if (gameObject != null) + { + objectQR = Global.ObjectMgr.GetGOQuestRelationBounds(gameObject.GetEntry()); + objectQIR = Global.ObjectMgr.GetGOQuestInvolvedRelationBounds(gameObject.GetEntry()); + } + else + return; + } + + QuestMenu qm = PlayerTalkClass.GetQuestMenu(); + qm.ClearMenu(); + + foreach (var quest_id in objectQIR) + { + QuestStatus status = GetQuestStatus(quest_id); + if (status == QuestStatus.Complete) + qm.AddMenuItem(quest_id, 4); + else if (status == QuestStatus.Incomplete) + qm.AddMenuItem(quest_id, 4); + } + + foreach (var quest_id in objectQR) + { + Quest quest = Global.ObjectMgr.GetQuestTemplate(quest_id); + if (quest == null) + continue; + + if (!CanTakeQuest(quest, false)) + continue; + + if (quest.IsAutoComplete()) + qm.AddMenuItem(quest_id, 4); + else if (GetQuestStatus(quest_id) == QuestStatus.None) + qm.AddMenuItem(quest_id, 2); + } + } + + public void SendPreparedQuest(ObjectGuid guid) + { + QuestMenu questMenu = PlayerTalkClass.GetQuestMenu(); + if (questMenu.IsEmpty()) + return; + + // single element case + if (questMenu.GetMenuItemCount() == 1) + { + QuestMenuItem qmi0 = questMenu.GetItem(0); + uint questId = qmi0.QuestId; + + // Auto open + Quest quest = Global.ObjectMgr.GetQuestTemplate(questId); + if (quest != null) + { + if (qmi0.QuestIcon == 4) + { + PlayerTalkClass.SendQuestGiverRequestItems(quest, guid, CanRewardQuest(quest, false), true); + return; + } + // Send completable on repeatable and autoCompletable quest if player don't have quest + // @todo verify if check for !quest.IsDaily() is really correct (possibly not) + else + { + WorldObject obj = Global.ObjAccessor.GetObjectByTypeMask(this, guid, TypeMask.Unit | TypeMask.GameObject | TypeMask.Item); + if (!obj || (!obj.hasQuest(questId) && !obj.hasInvolvedQuest(questId))) + { + PlayerTalkClass.SendCloseGossip(); + return; + } + + if (!obj.IsTypeId(TypeId.Unit) || obj.HasFlag64(UnitFields.NpcFlags, NPCFlags.Gossip)) + { + if (quest.IsAutoAccept() && CanAddQuest(quest, true) && CanTakeQuest(quest, true)) + AddQuestAndCheckCompletion(quest, obj); + + if (quest.IsAutoComplete() && quest.IsRepeatable() && !quest.IsDailyOrWeekly()) + PlayerTalkClass.SendQuestGiverRequestItems(quest, guid, CanCompleteRepeatableQuest(quest), true); + else + PlayerTalkClass.SendQuestGiverQuestDetails(quest, guid, true); + return; + } + } + } + } + + PlayerTalkClass.SendQuestGiverQuestList(guid); + } + + public bool IsActiveQuest(uint quest_id) + { + return m_QuestStatus.ContainsKey(quest_id); + } + + public Quest GetNextQuest(ObjectGuid guid, Quest quest) + { + List objectQR; + uint nextQuestID = quest.NextQuestInChain; + + switch (guid.GetHigh()) + { + case HighGuid.Player: + Contract.Assert(quest.HasFlag(QuestFlags.Unk1)); + return Global.ObjectMgr.GetQuestTemplate(nextQuestID); + case HighGuid.Creature: + case HighGuid.Pet: + case HighGuid.Vehicle: + { + Creature creature = ObjectAccessor.GetCreatureOrPetOrVehicle(this, guid); + if (creature != null) + objectQR = Global.ObjectMgr.GetCreatureQuestRelationBounds(creature.GetEntry()); + else + return null; + break; + } + case HighGuid.GameObject: + { + //we should obtain map from GetMap() in 99% of cases. Special case + //only for quests which cast teleport spells on player + Map _map = IsInWorld ? GetMap() : Global.MapMgr.FindMap(GetMapId(), GetInstanceId()); + Contract.Assert(_map != null); + GameObject gameObject = _map.GetGameObject(guid); + if (gameObject != null) + objectQR = Global.ObjectMgr.GetGOQuestRelationBounds(gameObject.GetEntry()); + else + return null; + break; + } + default: + return null; + } + + // for unit and go state + foreach (var id in objectQR) + { + if (id == nextQuestID) + return Global.ObjectMgr.GetQuestTemplate(nextQuestID); + } + + return null; + } + + public bool CanSeeStartQuest(Quest quest) + { + if (!Global.DisableMgr.IsDisabledFor(DisableType.Quest, quest.Id, this) && SatisfyQuestClass(quest, false) && SatisfyQuestRace(quest, false) && + SatisfyQuestSkill(quest, false) && SatisfyQuestExclusiveGroup(quest, false) && SatisfyQuestReputation(quest, false) && + SatisfyQuestPreviousQuest(quest, false) && SatisfyQuestNextChain(quest, false) && + SatisfyQuestPrevChain(quest, false) && SatisfyQuestDay(quest, false) && SatisfyQuestWeek(quest, false) && + SatisfyQuestMonth(quest, false) && SatisfyQuestSeasonal(quest, false)) + { + return getLevel() + WorldConfig.GetIntValue(WorldCfg.QuestHighLevelHideDiff) >= quest.MinLevel; + } + + return false; + } + + public bool CanTakeQuest(Quest quest, bool msg) + { + return !Global.DisableMgr.IsDisabledFor(DisableType.Quest, quest.Id, this) + && SatisfyQuestStatus(quest, msg) && SatisfyQuestExclusiveGroup(quest, msg) + && SatisfyQuestClass(quest, msg) && SatisfyQuestRace(quest, msg) && SatisfyQuestLevel(quest, msg) + && SatisfyQuestSkill(quest, msg) && SatisfyQuestReputation(quest, msg) + && SatisfyQuestPreviousQuest(quest, msg) && SatisfyQuestTimed(quest, msg) + && SatisfyQuestNextChain(quest, msg) && SatisfyQuestPrevChain(quest, msg) + && SatisfyQuestDay(quest, msg) && SatisfyQuestWeek(quest, msg) + && SatisfyQuestMonth(quest, msg) && SatisfyQuestSeasonal(quest, msg) + && SatisfyQuestConditions(quest, msg); + } + + public bool CanAddQuest(Quest quest, bool msg) + { + if (!SatisfyQuestLog(msg)) + return false; + + uint srcitem = quest.SourceItemId; + if (srcitem > 0) + { + uint count = quest.SourceItemIdCount; + List dest = new List(); + InventoryResult msg2 = CanStoreNewItem(ItemConst.NullBag, ItemConst.NullSlot, dest, srcitem, count); + + // player already have max number (in most case 1) source item, no additional item needed and quest can be added. + if (msg2 == InventoryResult.ItemMaxCount) + return true; + + if (msg2 != InventoryResult.Ok) + { + SendEquipError(msg2, null, null, srcitem); + return false; + } + } + return true; + } + + public bool CanCompleteQuest(uint quest_id) + { + if (quest_id != 0) + { + Quest qInfo = Global.ObjectMgr.GetQuestTemplate(quest_id); + if (qInfo == null) + return false; + + if (!qInfo.IsRepeatable() && m_RewardedQuests.Contains(quest_id)) + return false; // not allow re-complete quest + + // auto complete quest + if ((qInfo.IsAutoComplete() || qInfo.Flags.HasAnyFlag(QuestFlags.AutoComplete)) && CanTakeQuest(qInfo, false)) + return true; + + var q_status = m_QuestStatus.LookupByKey(quest_id); + if (q_status == null) + return false; + + if (q_status.Status == QuestStatus.Incomplete) + { + foreach (QuestObjective obj in qInfo.Objectives) + { + if (!obj.Flags.HasAnyFlag(QuestObjectiveFlags.Optional) && !obj.Flags.HasAnyFlag(QuestObjectiveFlags.PartOfProgressBar)) + { + if (!IsQuestObjectiveComplete(obj)) + return false; + } + } + + if (qInfo.HasSpecialFlag(QuestSpecialFlags.Timed) && q_status.Timer == 0) + return false; + + return true; + } + } + return false; + } + + public bool CanCompleteRepeatableQuest(Quest quest) + { + // Solve problem that player don't have the quest and try complete it. + // if repeatable she must be able to complete event if player don't have it. + // Seem that all repeatable quest are DELIVER Flag so, no need to add more. + if (!CanTakeQuest(quest, false)) + return false; + + if (quest.HasSpecialFlag(QuestSpecialFlags.Deliver)) + foreach (QuestObjective obj in quest.Objectives) + if (obj.Type == QuestObjectiveType.Item && !HasItemCount((uint)obj.ObjectID, (uint)obj.Amount)) + return false; + + if (!CanRewardQuest(quest, false)) + return false; + + return true; + } + + public bool CanRewardQuest(Quest quest, bool msg) + { + // not auto complete quest and not completed quest (only cheating case, then ignore without message) + if (!quest.IsDFQuest() && !quest.IsAutoComplete() && GetQuestStatus(quest.Id) != QuestStatus.Complete) + return false; + + // daily quest can't be rewarded (25 daily quest already completed) + if (!SatisfyQuestDay(quest, true) || !SatisfyQuestWeek(quest, true) || !SatisfyQuestMonth(quest, true) || !SatisfyQuestSeasonal(quest, true)) + return false; + + // rewarded and not repeatable quest (only cheating case, then ignore without message) + if (GetQuestRewardStatus(quest.Id)) + return false; + + // prevent receive reward with quest items in bank + if (quest.HasSpecialFlag(QuestSpecialFlags.Deliver)) + { + foreach (QuestObjective obj in quest.Objectives) + { + if (obj.Type != QuestObjectiveType.Item) + continue; + + if (GetItemCount((uint)obj.ObjectID) < obj.Amount) + { + if (msg) + SendEquipError(InventoryResult.ItemNotFound, null, null, (uint)obj.ObjectID); + return false; + } + } + } + + foreach (QuestObjective obj in quest.Objectives) + { + switch (obj.Type) + { + case QuestObjectiveType.Currency: + if (!HasCurrency((uint)obj.ObjectID, (uint)obj.Amount)) + return false; + break; + case QuestObjectiveType.Money: + if (!HasEnoughMoney(obj.Amount)) + return false; + break; + } + } + + return true; + } + + public void AddQuestAndCheckCompletion(Quest quest, WorldObject questGiver) + { + AddQuest(quest, questGiver); + + if (CanCompleteQuest(quest.Id)) + CompleteQuest(quest.Id); + + if (!questGiver) + return; + + switch (questGiver.GetTypeId()) + { + case TypeId.Unit: + Global.ScriptMgr.OnQuestAccept(this, (questGiver.ToCreature()), quest); + questGiver.ToCreature().GetAI().sQuestAccept(this, quest); + break; + case TypeId.Item: + case TypeId.Container: + { + Item item = (Item)questGiver; + Global.ScriptMgr.OnQuestAccept(this, item, quest); + + // destroy not required for quest finish quest starting item + bool destroyItem = true; + foreach (QuestObjective obj in quest.Objectives) + { + if (obj.Type == QuestObjectiveType.Item && obj.ObjectID == item.GetEntry() && item.GetTemplate().GetMaxCount() > 0) + { + destroyItem = false; + break; + } + } + + if (destroyItem) + DestroyItem(item.GetBagSlot(), item.GetSlot(), true); + + break; + } + case TypeId.GameObject: + Global.ScriptMgr.OnQuestAccept(this, questGiver.ToGameObject(), quest); + questGiver.ToGameObject().GetAI().QuestAccept(this, quest); + break; + default: + break; + } + } + + public bool CanRewardQuest(Quest quest, uint reward, bool msg) + { + // prevent receive reward with quest items in bank or for not completed quest + if (!CanRewardQuest(quest, msg)) + return false; + + List dest = new List(); + if (quest.GetRewChoiceItemsCount() > 0) + { + for (uint i = 0; i < quest.GetRewChoiceItemsCount(); ++i) + { + if (quest.RewardChoiceItemId[i] != 0 && quest.RewardChoiceItemId[i] == reward) + { + InventoryResult res = CanStoreNewItem(ItemConst.NullBag, ItemConst.NullSlot, dest, quest.RewardChoiceItemId[i], quest.RewardChoiceItemCount[i]); + if (res != InventoryResult.Ok) + { + SendEquipError(res, null, null, quest.RewardChoiceItemId[i]); + return false; + } + } + } + } + + if (quest.GetRewItemsCount() > 0) + { + for (uint i = 0; i < quest.GetRewItemsCount(); ++i) + { + if (quest.RewardItemId[i] != 0) + { + InventoryResult res = CanStoreNewItem(ItemConst.NullBag, ItemConst.NullSlot, dest, quest.RewardItemId[i], quest.RewardItemCount[i]); + if (res != InventoryResult.Ok) + { + SendEquipError(res, null, null, quest.RewardItemId[i]); + return false; + } + } + } + } + + // QuestPackageItem.db2 + if (quest.PackageID != 0) + { + bool hasFilteredQuestPackageReward = false; + var questPackageItems = Global.DB2Mgr.GetQuestPackageItems(quest.PackageID); + if (questPackageItems != null) + { + foreach (var questPackageItem in questPackageItems) + { + if (questPackageItem.ItemID != reward) + continue; + + if (CanSelectQuestPackageItem(questPackageItem)) + { + hasFilteredQuestPackageReward = true; + InventoryResult res = CanStoreNewItem(ItemConst.NullBag, ItemConst.NullSlot, dest, questPackageItem.ItemID, questPackageItem.ItemCount); + if (res != InventoryResult.Ok) + { + SendEquipError(res, null, null, questPackageItem.ItemID); + return false; + } + } + } + } + + if (!hasFilteredQuestPackageReward) + { + List questPackageItems1 = Global.DB2Mgr.GetQuestPackageItemsFallback(quest.PackageID); + if (questPackageItems1 != null) + { + foreach (QuestPackageItemRecord questPackageItem in questPackageItems1) + { + if (questPackageItem.ItemID != reward) + continue; + + InventoryResult res = CanStoreNewItem(ItemConst.NullBag, ItemConst.NullSlot, dest, questPackageItem.ItemID, questPackageItem.ItemCount); + if (res != InventoryResult.Ok) + { + SendEquipError(res, null, null, questPackageItem.ItemID); + return false; + } + } + } + } + } + + return true; + } + + public void AddQuest(Quest quest, WorldObject questGiver) + { + ushort log_slot = FindQuestSlot(0); + + if (log_slot >= SharedConst.MaxQuestLogSize) // Player does not have any free slot in the quest log + return; + + uint quest_id = quest.Id; + + if (!m_QuestStatus.ContainsKey(quest_id)) + m_QuestStatus[quest_id] = new QuestStatusData(); + + QuestStatusData questStatusData = m_QuestStatus.LookupByKey(quest_id); + + // check for repeatable quests status reset + questStatusData.Status = QuestStatus.Incomplete; + + int maxStorageIndex = 0; + foreach (QuestObjective obj in quest.Objectives) + if (obj.StorageIndex > maxStorageIndex) + maxStorageIndex = obj.StorageIndex; + + questStatusData.ObjectiveData = new int[maxStorageIndex + 1]; + + GiveQuestSourceItem(quest); + AdjustQuestReqItemCount(quest); + + foreach (QuestObjective obj in quest.Objectives) + { + if (obj.Type == QuestObjectiveType.MinReputation || obj.Type == QuestObjectiveType.MaxReputation) + { + FactionRecord factionEntry = CliDB.FactionStorage.LookupByKey(obj.ObjectID); + if (factionEntry != null) + GetReputationMgr().SetVisible(factionEntry); + } + } + + uint qtime = 0; + if (quest.HasSpecialFlag(QuestSpecialFlags.Timed)) + { + uint limittime = quest.LimitTime; + + // shared timed quest + if (questGiver != null && questGiver.IsTypeId(TypeId.Player)) + limittime = questGiver.ToPlayer().m_QuestStatus[quest_id].Timer / Time.InMilliseconds; + + AddTimedQuest(quest_id); + questStatusData.Timer = limittime * Time.InMilliseconds; + qtime = (uint)(Time.UnixTime + limittime); + } + else + questStatusData.Timer = 0; + + if (quest.HasFlag(QuestFlags.Pvp)) + { + pvpInfo.IsHostile = true; + UpdatePvPState(); + } + + SetQuestSlot(log_slot, quest_id, qtime); + + m_QuestStatusSave[quest_id] = QuestSaveType.Default; + + StartCriteriaTimer(CriteriaTimedTypes.Quest, quest_id); + + SendQuestUpdate(quest_id); + + Global.ScriptMgr.OnQuestStatusChange(this, quest_id); + } + + public void CompleteQuest(uint quest_id) + { + if (quest_id != 0) + { + SetQuestStatus(quest_id, QuestStatus.Complete); + + ushort log_slot = FindQuestSlot(quest_id); + if (log_slot < SharedConst.MaxQuestLogSize) + SetQuestSlotState(log_slot, QuestSlotStateMask.Complete); + + Quest qInfo = Global.ObjectMgr.GetQuestTemplate(quest_id); + if (qInfo != null) + if (qInfo.HasFlag(QuestFlags.Tracking)) + RewardQuest(qInfo, 0, this, false); + } + } + + public void IncompleteQuest(uint quest_id) + { + if (quest_id != 0) + { + SetQuestStatus(quest_id, QuestStatus.Incomplete); + + ushort log_slot = FindQuestSlot(quest_id); + if (log_slot < SharedConst.MaxQuestLogSize) + RemoveQuestSlotState(log_slot, QuestSlotStateMask.Complete); + } + } + + public uint GetQuestMoneyReward(Quest quest) + { + return (uint)(quest.MoneyValue(getLevel()) * WorldConfig.GetFloatValue(WorldCfg.RateMoneyQuest)); + } + + public uint GetQuestXPReward(Quest quest) + { + bool rewarded = m_RewardedQuests.Contains(quest.Id); + + // Not give XP in case already completed once repeatable quest + if (rewarded && !quest.IsDFQuest()) + return 0; + + uint XP = (uint)(quest.XPValue(getLevel()) * WorldConfig.GetFloatValue(WorldCfg.RateXpQuest)); + + // handle SPELL_AURA_MOD_XP_QUEST_PCT auras + var ModXPPctAuras = GetAuraEffectsByType(AuraType.ModXpQuestPct); + foreach (var eff in ModXPPctAuras) + MathFunctions.AddPct(ref XP, eff.GetAmount()); + + return XP; + } + + public bool CanSelectQuestPackageItem(QuestPackageItemRecord questPackageItem) + { + ItemTemplate rewardProto = Global.ObjectMgr.GetItemTemplate(questPackageItem.ItemID); + if (rewardProto == null) + return false; + + if ((rewardProto.GetFlags2().HasAnyFlag(ItemFlags2.FactionAlliance) && GetTeam() != Team.Alliance) || + (rewardProto.GetFlags2().HasAnyFlag(ItemFlags2.FactionHorde) && GetTeam() != Team.Horde)) + return false; + + switch (questPackageItem.FilterType) + { + case QuestPackageFilter.LootSpecialization: + return rewardProto.IsUsableByLootSpecialization(this); + case QuestPackageFilter.Class: + return rewardProto.ItemSpecClassMask == 0 || (rewardProto.ItemSpecClassMask & getClassMask()) != 0; + case QuestPackageFilter.Everyone: + return true; + default: + break; + } + + return false; + } + + public void RewardQuest(Quest quest, uint reward, WorldObject questGiver, bool announce = true) + { + //this THING should be here to protect code from quest, which cast on player far teleport as a reward + //should work fine, cause far teleport will be executed in Update() + SetCanDelayTeleport(true); + + uint quest_id = quest.Id; + + foreach (QuestObjective obj in quest.Objectives) + { + switch (obj.Type) + { + case QuestObjectiveType.Item: + DestroyItemCount((uint)obj.ObjectID, (uint)obj.Amount, true); + break; + case QuestObjectiveType.Currency: + ModifyCurrency((CurrencyTypes)obj.ObjectID, -obj.Amount, false, true); + break; + } + } + + if (!quest.FlagsEx.HasAnyFlag(QuestFlagsEx.KeepAdditionalItems)) + { + for (byte i = 0; i < SharedConst.QuestItemDropCount; ++i) + { + if (quest.ItemDrop[i] != 0) + { + uint count = quest.ItemDropQuantity[i]; + DestroyItemCount(quest.ItemDrop[i], count != 0 ? count : 9999, true); + } + } + } + + RemoveTimedQuest(quest_id); + + ItemTemplate rewardProto = Global.ObjectMgr.GetItemTemplate(reward); + if (rewardProto != null && quest.GetRewChoiceItemsCount() != 0) + { + for (uint i = 0; i < quest.GetRewChoiceItemsCount(); ++i) + { + if (quest.RewardChoiceItemId[i] != 0 && quest.RewardChoiceItemId[i] == reward) + { + List dest = new List(); + if (CanStoreNewItem(ItemConst.NullBag, ItemConst.NullSlot, dest, reward, quest.RewardChoiceItemCount[i]) == InventoryResult.Ok) + { + Item item = StoreNewItem(dest, reward, true, ItemEnchantment.GenerateItemRandomPropertyId(reward)); + SendNewItem(item, quest.RewardChoiceItemCount[i], true, false); + } + } + } + } + + // QuestPackageItem.db2 + if (rewardProto != null && quest.PackageID != 0) + { + bool hasFilteredQuestPackageReward = false; + var questPackageItems = Global.DB2Mgr.GetQuestPackageItems(quest.PackageID); + if (questPackageItems != null) + { + foreach (var questPackageItem in questPackageItems) + { + if (questPackageItem.ItemID != reward) + continue; + + if (CanSelectQuestPackageItem(questPackageItem)) + { + hasFilteredQuestPackageReward = true; + List dest = new List(); + if (CanStoreNewItem(ItemConst.NullBag, ItemConst.NullSlot, dest, questPackageItem.ItemID, questPackageItem.ItemCount) == InventoryResult.Ok) + { + Item item = StoreNewItem(dest, questPackageItem.ItemID, true, ItemEnchantment.GenerateItemRandomPropertyId(questPackageItem.ItemID)); + SendNewItem(item, questPackageItem.ItemCount, true, false); + } + } + } + } + + if (!hasFilteredQuestPackageReward) + { + List questPackageItems1 = Global.DB2Mgr.GetQuestPackageItemsFallback(quest.PackageID); + if (questPackageItems1 != null) + { + foreach (QuestPackageItemRecord questPackageItem in questPackageItems1) + { + if (questPackageItem.ItemID != reward) + continue; + + List dest = new List(); + if (CanStoreNewItem(ItemConst.NullBag, ItemConst.NullSlot, dest, questPackageItem.ItemID, questPackageItem.ItemCount) == InventoryResult.Ok) + { + Item item = StoreNewItem(dest, questPackageItem.ItemID, true, ItemEnchantment.GenerateItemRandomPropertyId(questPackageItem.ItemID)); + SendNewItem(item, questPackageItem.ItemCount, true, false); + } + } + } + } + } + + if (quest.GetRewItemsCount() > 0) + { + for (uint i = 0; i < quest.GetRewItemsCount(); ++i) + { + uint itemId = quest.RewardItemId[i]; + if (itemId != 0) + { + List dest = new List(); + if (CanStoreNewItem(ItemConst.NullBag, ItemConst.NullSlot, dest, itemId, quest.RewardItemCount[i]) == InventoryResult.Ok) + { + Item item = StoreNewItem(dest, itemId, true, ItemEnchantment.GenerateItemRandomPropertyId(itemId)); + SendNewItem(item, quest.RewardItemCount[i], true, false); + } + else if (quest.IsDFQuest()) + SendItemRetrievalMail(quest.RewardItemId[i], quest.RewardItemCount[i]); + } + } + } + + for (byte i = 0; i < SharedConst.QuestRewardCurrencyCount; ++i) + { + if (quest.RewardCurrencyId[i] != 0) + ModifyCurrency((CurrencyTypes)quest.RewardCurrencyId[i], (int)quest.RewardCurrencyCount[i]); + } + + uint skill = quest.RewardSkillId; + if (skill != 0) + UpdateSkillPro(skill, 1000, quest.RewardSkillPoints); + + RewardReputation(quest); + + ushort log_slot = FindQuestSlot(quest_id); + if (log_slot < SharedConst.MaxQuestLogSize) + SetQuestSlot(log_slot, 0); + + uint XP = GetQuestXPReward(quest); + + int moneyRew = 0; + if (getLevel() < WorldConfig.GetIntValue(WorldCfg.MaxPlayerLevel)) + GiveXP(XP, null); + else + moneyRew = (int)(quest.GetRewMoneyMaxLevel() * WorldConfig.GetFloatValue(WorldCfg.RateDropMoney)); + + moneyRew += (int)GetQuestMoneyReward(quest); + + if (moneyRew != 0) + { + ModifyMoney(moneyRew); + + if (moneyRew > 0) + UpdateCriteria(CriteriaTypes.MoneyFromQuestReward, (uint)(moneyRew)); + } + + // honor reward + uint honor = quest.CalculateHonorGain(getLevel()); + if (honor != 0) + RewardHonor(null, 0, (int)honor); + + // title reward + if (quest.RewardTitleId != 0) + { + CharTitlesRecord titleEntry = CliDB.CharTitlesStorage.LookupByKey(quest.RewardTitleId); + if (titleEntry != null) + SetTitle(titleEntry); + } + + // Send reward mail + uint mail_template_id = quest.RewardMailTemplateId; + if (mail_template_id != 0) + { + SQLTransaction trans = new SQLTransaction(); + // @todo Poor design of mail system + uint questMailSender = quest.RewardMailSenderEntry; + if (questMailSender != 0) + new MailDraft(mail_template_id).SendMailTo(trans, this, new MailSender(questMailSender), MailCheckMask.HasBody, quest.RewardMailDelay); + else + new MailDraft(mail_template_id).SendMailTo(trans, this, new MailSender(questGiver), MailCheckMask.HasBody, quest.RewardMailDelay); + DB.Characters.CommitTransaction(trans); + } + + if (quest.IsDaily() || quest.IsDFQuest()) + { + SetDailyQuestStatus(quest_id); + if (quest.IsDaily()) + { + UpdateCriteria(CriteriaTypes.CompleteDailyQuest, quest_id); + UpdateCriteria(CriteriaTypes.CompleteDailyQuestDaily, quest_id); + } + } + else if (quest.IsWeekly()) + SetWeeklyQuestStatus(quest_id); + else if (quest.IsMonthly()) + SetMonthlyQuestStatus(quest_id); + else if (quest.IsSeasonal()) + SetSeasonalQuestStatus(quest_id); + + RemoveActiveQuest(quest_id, false); + if (quest.CanIncreaseRewardedQuestCounters()) + SetRewardedQuest(quest_id); + + // StoreNewItem, mail reward, etc. save data directly to the database + // to prevent exploitable data desynchronisation we save the quest status to the database too + // (to prevent rewarding this quest another time while rewards were already given out) + _SaveQuestStatus(null); + + SendQuestReward(quest, questGiver.ToCreature(), XP, !announce); + + // cast spells after mark quest complete (some spells have quest completed state requirements in spell_area data) + if (quest.RewardSpell > 0) + { + SpellInfo spellInfo = Global.SpellMgr.GetSpellInfo(quest.RewardSpell); + if (questGiver.isTypeMask(TypeMask.Unit) && !spellInfo.HasEffect(Difficulty.None, SpellEffectName.LearnSpell) && !spellInfo.HasEffect(Difficulty.None, SpellEffectName.CreateItem) && !spellInfo.HasEffect(Difficulty.None, SpellEffectName.ApplyAura)) + { + Unit unit = questGiver.ToUnit(); + if (unit) + unit.CastSpell(this, quest.RewardSpell, true); + } + else + CastSpell(this, quest.RewardSpell, true); + } + else + { + for (uint i = 0; i < SharedConst.QuestRewardDisplaySpellCount; ++i) + { + if (quest.RewardDisplaySpell[i] > 0) + { + SpellInfo spellInfo = Global.SpellMgr.GetSpellInfo(quest.RewardDisplaySpell[i]); + if (questGiver.IsTypeId(TypeId.Unit) && !spellInfo.HasEffect(Difficulty.None, SpellEffectName.LearnSpell) && !spellInfo.HasEffect(Difficulty.None, SpellEffectName.CreateItem)) + { + Unit unit = questGiver.ToUnit(); + if (unit) + unit.CastSpell(this, quest.RewardDisplaySpell[i], true); + } + else + CastSpell(this, quest.RewardDisplaySpell[i], true); + } + } + } + + if (quest.QuestSortID > 0) + UpdateCriteria(CriteriaTypes.CompleteQuestsInZone, (ulong)quest.QuestSortID); + + UpdateCriteria(CriteriaTypes.CompleteQuestCount); + UpdateCriteria(CriteriaTypes.CompleteQuest, quest.Id); + + uint questBit = Global.DB2Mgr.GetQuestUniqueBitFlag(quest_id); + if (questBit != 0) + SetQuestCompletedBit(questBit, true); + + if (quest.HasFlag(QuestFlags.Pvp)) + { + pvpInfo.IsHostile = pvpInfo.IsInHostileArea || HasPvPForcingQuest(); + UpdatePvPState(); + } + + SendQuestUpdate(quest_id); + SendQuestGiverStatusMultiple(); + + //lets remove flag for delayed teleports + SetCanDelayTeleport(false); + + Global.ScriptMgr.OnQuestStatusChange(this, quest_id); + } + + public void SetRewardedQuest(uint quest_id) + { + m_RewardedQuests.Add(quest_id); + m_RewardedQuestsSave[quest_id] = QuestSaveType.Default; + } + + public void FailQuest(uint questId) + { + Quest quest = Global.ObjectMgr.GetQuestTemplate(questId); + if (quest != null) + { + // Already complete quests shouldn't turn failed. + if (GetQuestStatus(questId) == QuestStatus.Complete && !quest.HasSpecialFlag(QuestSpecialFlags.Timed)) + return; + + SetQuestStatus(questId, QuestStatus.Failed); + + ushort log_slot = FindQuestSlot(questId); + + if (log_slot < SharedConst.MaxQuestLogSize) + { + SetQuestSlotTimer(log_slot, 1); + SetQuestSlotState(log_slot, QuestSlotStateMask.Fail); + } + + if (quest.HasSpecialFlag(QuestSpecialFlags.Timed)) + { + QuestStatusData q_status = m_QuestStatus[questId]; + + RemoveTimedQuest(questId); + q_status.Timer = 0; + + SendQuestTimerFailed(questId); + } + else + SendQuestFailed(questId); + + // Destroy quest items on quest failure. + foreach (QuestObjective obj in quest.Objectives) + { + if (obj.Type == QuestObjectiveType.Item) + { + ItemTemplate itemTemplate = Global.ObjectMgr.GetItemTemplate((uint)obj.ObjectID); + if (itemTemplate != null) + if (itemTemplate.GetBonding() == ItemBondingType.Quest) + DestroyItemCount((uint)obj.ObjectID, (uint)obj.Amount, true, true); + } + } + + // Destroy items received during the quest. + for (byte i = 0; i < SharedConst.QuestItemDropCount; ++i) + { + ItemTemplate itemTemplate = Global.ObjectMgr.GetItemTemplate(quest.ItemDrop[i]); + if (itemTemplate != null) + if (quest.ItemDropQuantity[i] != 0 && itemTemplate.GetBonding() == ItemBondingType.Quest) + DestroyItemCount(quest.ItemDrop[i], quest.ItemDropQuantity[i], true, true); + } + } + } + + public void AbandonQuest(uint questId) + { + Quest quest = Global.ObjectMgr.GetQuestTemplate(questId); + if (quest != null) + { + // Destroy quest items on quest abandon. + foreach (QuestObjective obj in quest.Objectives) + { + if (obj.Type == QuestObjectiveType.Item) + { + ItemTemplate itemTemplate = Global.ObjectMgr.GetItemTemplate((uint)obj.ObjectID); + if (itemTemplate != null) + if (itemTemplate.GetBonding() == ItemBondingType.Quest) + DestroyItemCount((uint)obj.ObjectID, (uint)obj.Amount, true, true); + } + } + + // Destroy items received during the quest. + for (byte i = 0; i < SharedConst.QuestItemDropCount; ++i) + { + ItemTemplate itemTemplate = Global.ObjectMgr.GetItemTemplate(quest.ItemDrop[i]); + if (itemTemplate != null) + if (quest.ItemDropQuantity[i] != 0 && itemTemplate.GetBonding() == ItemBondingType.Quest) + DestroyItemCount(quest.ItemDrop[i], quest.ItemDropQuantity[i], true, true); + } + } + } + + public bool SatisfyQuestSkill(Quest qInfo, bool msg) + { + uint skill = qInfo.RequiredSkillId; + + // skip 0 case RequiredSkill + if (skill == 0) + return true; + + // check skill value + if (GetSkillValue((SkillType)skill) < qInfo.RequiredSkillPoints) + { + if (msg) + { + SendCanTakeQuestResponse(QuestFailedReasons.None); + Log.outDebug(LogFilter.Server, "SatisfyQuestSkill: Sent QuestFailedReason.None (questId: {0}) because player does not have required skill value.", qInfo.Id); + } + + return false; + } + + return true; + } + + public bool SatisfyQuestLevel(Quest qInfo, bool msg) + { + if (getLevel() < qInfo.MinLevel) + { + if (msg) + { + SendCanTakeQuestResponse(QuestFailedReasons.FailedLowLevel); + Log.outDebug(LogFilter.Server, "SatisfyQuestLevel: Sent QuestFailedReasons.FailedLowLevel (questId: {0}) because player does not have required (min) level.", qInfo.Id); + } + return false; + } + + if (qInfo.MaxLevel > 0 && getLevel() > qInfo.MaxLevel) + { + if (msg) + { + SendCanTakeQuestResponse(QuestFailedReasons.None); // There doesn't seem to be a specific response for too high player level + Log.outDebug(LogFilter.Server, "SatisfyQuestLevel: Sent QuestFailedReasons.None (questId: {0}) because player does not have required (max) level.", qInfo.Id); + } + return false; + } + return true; + } + + public bool SatisfyQuestLog(bool msg) + { + // exist free slot + if (FindQuestSlot(0) < SharedConst.MaxQuestLogSize) + return true; + + if (msg) + SendPacket(new QuestLogFull()); + + return false; + } + + public bool SatisfyQuestPreviousQuest(Quest qInfo, bool msg) + { + // No previous quest (might be first quest in a series) + if (qInfo.prevQuests.Empty()) + return true; + + foreach (var prev in qInfo.prevQuests) + { + uint prevId = (uint)Math.Abs(prev); + + Quest qPrevInfo = Global.ObjectMgr.GetQuestTemplate(prevId); + + if (qPrevInfo != null) + { + // If any of the positive previous quests completed, return true + if (prev > 0 && m_RewardedQuests.Contains(prevId)) + { + // skip one-from-all exclusive group + if (qPrevInfo.ExclusiveGroup >= 0) + return true; + + // each-from-all exclusive group (< 0) + // can be start if only all quests in prev quest exclusive group completed and rewarded + var range = Global.ObjectMgr._exclusiveQuestGroups.LookupByKey(qPrevInfo.ExclusiveGroup); + foreach (var exclude_Id in range) + { + // skip checked quest id, only state of other quests in group is interesting + if (exclude_Id == prevId) + continue; + + // alternative quest from group also must be completed and rewarded (reported) + if (!m_RewardedQuests.Contains(exclude_Id)) + { + if (msg) + { + SendCanTakeQuestResponse(QuestFailedReasons.None); + Log.outDebug(LogFilter.Server, "SatisfyQuestPreviousQuest: Sent QuestFailedReason.None (questId: {0}) because player does not have required quest (1).", qInfo.Id); + } + return false; + } + } + return true; + } + + // If any of the negative previous quests active, return true + if (prev < 0 && GetQuestStatus(prevId) != QuestStatus.None) + { + // skip one-from-all exclusive group + if (qPrevInfo.ExclusiveGroup >= 0) + return true; + + // each-from-all exclusive group (< 0) + // can be start if only all quests in prev quest exclusive group active + var range = Global.ObjectMgr._exclusiveQuestGroups.LookupByKey(qPrevInfo.ExclusiveGroup); + foreach (var exclude_Id in range) + { + // skip checked quest id, only state of other quests in group is interesting + if (exclude_Id == prevId) + continue; + + // alternative quest from group also must be active + if (GetQuestStatus(exclude_Id) != QuestStatus.None) + { + if (msg) + { + SendCanTakeQuestResponse(QuestFailedReasons.None); + Log.outDebug(LogFilter.Server, "SatisfyQuestPreviousQuest: Sent QuestFailedReason.None (questId: {0}) because player does not have required quest (2).", qInfo.Id); + + } + return false; + } + } + return true; + } + } + } + + // Has only positive prev. quests in non-rewarded state + // and negative prev. quests in non-active state + if (msg) + { + SendCanTakeQuestResponse(QuestFailedReasons.None); + Log.outDebug(LogFilter.Server, "SatisfyQuestPreviousQuest: Sent QuestFailedReason.None (questId: {0}) because player does not have required quest (3).", qInfo.Id); + } + + return false; + } + + public bool SatisfyQuestClass(Quest qInfo, bool msg) + { + uint reqClass = qInfo.AllowableClasses; + + if (reqClass == 0) + return true; + + if ((reqClass & getClassMask()) == 0) + { + if (msg) + { + SendCanTakeQuestResponse(QuestFailedReasons.None); + Log.outDebug(LogFilter.Server, "SatisfyQuestClass: Sent QuestFailedReason.None (questId: {0}) because player does not have required class.", qInfo.Id); + } + + return false; + } + + return true; + } + + public bool SatisfyQuestRace(Quest qInfo, bool msg) + { + int reqraces = qInfo.AllowableRaces; + if (reqraces == -1) + return true; + + if ((reqraces & getRaceMask()) == 0) + { + if (msg) + { + SendCanTakeQuestResponse(QuestFailedReasons.FailedWrongRace); + Log.outDebug(LogFilter.Server, "SatisfyQuestRace: Sent QuestFailedReasons.FailedWrongRace (questId: {0}) because player does not have required race.", qInfo.Id); + + } + return false; + } + return true; + } + + public bool SatisfyQuestReputation(Quest qInfo, bool msg) + { + uint fIdMin = qInfo.RequiredMinRepFaction; //Min required rep + if (fIdMin != 0 && GetReputationMgr().GetReputation(fIdMin) < qInfo.RequiredMinRepValue) + { + if (msg) + { + SendCanTakeQuestResponse(QuestFailedReasons.None); + Log.outDebug(LogFilter.Server, "SatisfyQuestReputation: Sent QuestFailedReason.None (questId: {0}) because player does not have required reputation (min).", qInfo.Id); + } + return false; + } + + uint fIdMax = qInfo.RequiredMaxRepFaction; //Max required rep + if (fIdMax != 0 && GetReputationMgr().GetReputation(fIdMax) >= qInfo.RequiredMaxRepValue) + { + if (msg) + { + SendCanTakeQuestResponse(QuestFailedReasons.None); + Log.outDebug(LogFilter.Server, "SatisfyQuestReputation: Sent QuestFailedReason.None (questId: {0}) because player does not have required reputation (max).", qInfo.Id); + } + return false; + } + + /* @todo 6.x investigate if it's still needed + // ReputationObjective2 does not seem to be an objective requirement but a requirement + // to be able to accept the quest + uint fIdObj = qInfo.RequiredFactionId2; + if (fIdObj != 0 && GetReputationMgr().GetReputation(fIdObj) >= qInfo.RequiredFactionValue2) + { + if (msg) + { + SendCanTakeQuestResponse(QuestFailedReasons.DontHaveReq); + Log.outDebug(LogFilter.Misc, "SatisfyQuestReputation: Sent QuestFailedReason.None (questId: {0}) because player does not have required reputation (ReputationObjective2).", qInfo.Id); + } + return false; + } + */ + return true; + } + + public bool SatisfyQuestStatus(Quest qInfo, bool msg) + { + if (GetQuestStatus(qInfo.Id) == QuestStatus.Rewarded) + { + if (msg) + { + SendCanTakeQuestResponse(QuestFailedReasons.AlreadyDone); + Log.outDebug(LogFilter.Misc, "Player.SatisfyQuestStatus: Sent QUEST_STATUS_REWARDED (QuestID: {0}) because player '{1}' ({2}) quest status is already REWARDED.", + qInfo.Id, GetName(), GetGUID().ToString()); + } + return false; + } + + if (GetQuestStatus(qInfo.Id) != QuestStatus.None) + { + if (msg) + { + SendCanTakeQuestResponse(QuestFailedReasons.AlreadyOn1); + Log.outDebug(LogFilter.Server, "SatisfyQuestStatus: Sent QuestFailedReasons.AlreadyOn1 (questId: {0}) because player quest status is not NONE.", qInfo.Id); + } + return false; + } + return true; + } + + public bool SatisfyQuestConditions(Quest qInfo, bool msg) + { + if (!Global.ConditionMgr.IsObjectMeetingNotGroupedConditions(ConditionSourceType.QuestAccept, qInfo.Id, this)) + { + if (msg) + { + SendCanTakeQuestResponse(QuestFailedReasons.None); + Log.outDebug(LogFilter.Server, "SatisfyQuestConditions: Sent QuestFailedReason.None (questId: {0}) because player does not meet conditions.", qInfo.Id); + } + Log.outDebug(LogFilter.Condition, "SatisfyQuestConditions: conditions not met for quest {0}", qInfo.Id); + return false; + } + return true; + } + + public bool SatisfyQuestTimed(Quest qInfo, bool msg) + { + if (!m_timedquests.Empty() && qInfo.HasSpecialFlag(QuestSpecialFlags.Timed)) + { + if (msg) + { + SendCanTakeQuestResponse(QuestFailedReasons.OnlyOneTimed); + Log.outDebug(LogFilter.Server, "SatisfyQuestTimed: Sent QuestFailedReasons.OnlyOneTimed (questId: {0}) because player is already on a timed quest.", qInfo.Id); + } + return false; + } + return true; + } + + public bool SatisfyQuestExclusiveGroup(Quest qInfo, bool msg) + { + // non positive exclusive group, if > 0 then can be start if any other quest in exclusive group already started/completed + if (qInfo.ExclusiveGroup <= 0) + return true; + + var range = Global.ObjectMgr._exclusiveQuestGroups.LookupByKey(qInfo.ExclusiveGroup); + // always must be found if qInfo.ExclusiveGroup != 0 + + foreach (var exclude_Id in range) + { + // skip checked quest id, only state of other quests in group is interesting + if (exclude_Id == qInfo.Id) + continue; + + // not allow have daily quest if daily quest from exclusive group already recently completed + Quest Nquest = Global.ObjectMgr.GetQuestTemplate(exclude_Id); + if (!SatisfyQuestDay(Nquest, false) || !SatisfyQuestWeek(Nquest, false) || !SatisfyQuestSeasonal(Nquest, false)) + { + if (msg) + { + SendCanTakeQuestResponse(QuestFailedReasons.None); + Log.outDebug(LogFilter.Server, "SatisfyQuestExclusiveGroup: Sent QuestFailedReason.None (questId: {0}) because player already did daily quests in exclusive group.", qInfo.Id); + } + + return false; + } + + // alternative quest already started or completed - but don't check rewarded states if both are repeatable + if (GetQuestStatus(exclude_Id) != QuestStatus.None || (!(qInfo.IsRepeatable() && Nquest.IsRepeatable()) && m_RewardedQuests.Contains(exclude_Id))) + { + if (msg) + { + SendCanTakeQuestResponse(QuestFailedReasons.None); + Log.outDebug(LogFilter.Server, "SatisfyQuestExclusiveGroup: Sent QuestFailedReason.None (questId: {0}) because player already did quest in exclusive group.", qInfo.Id); + } + return false; + } + } + return true; + } + + public bool SatisfyQuestNextChain(Quest qInfo, bool msg) + { + uint nextQuest = qInfo.NextQuestInChain; + if (nextQuest == 0) + return true; + + // next quest in chain already started or completed + if (GetQuestStatus(nextQuest) != QuestStatus.None) // GetQuestStatus returns QuestStatus.CompleteD for rewarded quests + { + if (msg) + { + SendCanTakeQuestResponse(QuestFailedReasons.None); + Log.outDebug(LogFilter.Server, "SatisfyQuestNextChain: Sent QuestFailedReason.None (questId: {0}) because player already did or started next quest in chain.", qInfo.Id); + } + return false; + } + return true; + } + + public bool SatisfyQuestPrevChain(Quest qInfo, bool msg) + { + // No previous quest in chain + if (qInfo.prevChainQuests.Empty()) + return true; + + foreach (var questId in qInfo.prevChainQuests) + { + var questStatusData = m_QuestStatus.LookupByKey(questId); + + // If any of the previous quests in chain active, return false + if (questStatusData != null && questStatusData.Status != QuestStatus.None) + { + if (msg) + { + SendCanTakeQuestResponse(QuestFailedReasons.None); + Log.outDebug(LogFilter.Server, "SatisfyQuestNextChain: Sent QuestFailedReason.None (questId: {0}) because player already did or started next quest in chain.", qInfo.Id); + } + return false; + } + } + + // No previous quest in chain active + return true; + } + + public bool SatisfyQuestDay(Quest qInfo, bool msg) + { + if (!qInfo.IsDaily() && !qInfo.IsDFQuest()) + return true; + + if (qInfo.IsDFQuest()) + { + if (m_DFQuests.Contains(qInfo.Id)) + return false; + + return true; + } + + var dailies = GetDynamicValues(PlayerDynamicFields.DailyQuests); + foreach (var dailyQuestId in dailies) + if (dailyQuestId == qInfo.Id) + return false; + + return true; + } + + public bool SatisfyQuestWeek(Quest qInfo, bool msg) + { + if (!qInfo.IsWeekly() || m_weeklyquests.Empty()) + return true; + + // if not found in cooldown list + return !m_weeklyquests.Contains(qInfo.Id); + } + + public bool SatisfyQuestSeasonal(Quest qInfo, bool msg) + { + if (!qInfo.IsSeasonal() || m_seasonalquests.Empty()) + return true; + + ushort eventId = Global.GameEventMgr.GetEventIdForQuest(qInfo); + if (!m_seasonalquests.ContainsKey(eventId) || m_seasonalquests[eventId].Empty()) + return true; + + // if not found in cooldown list + return !m_seasonalquests[eventId].Contains(qInfo.Id); + } + + public bool SatisfyQuestMonth(Quest qInfo, bool msg) + { + if (!qInfo.IsMonthly() || m_monthlyquests.Empty()) + return true; + + // if not found in cooldown list + return !m_monthlyquests.Contains(qInfo.Id); + } + + public bool GiveQuestSourceItem(Quest quest) + { + uint srcitem = quest.SourceItemId; + if (srcitem > 0) + { + uint count = quest.SourceItemIdCount; + if (count <= 0) + count = 1; + + List dest = new List(); + InventoryResult msg = CanStoreNewItem(ItemConst.NullBag, ItemConst.NullSlot, dest, srcitem, count); + if (msg == InventoryResult.Ok) + { + Item item = StoreNewItem(dest, srcitem, true); + SendNewItem(item, count, true, false); + return true; + } + // player already have max amount required item, just report success + if (msg == InventoryResult.ItemMaxCount) + return true; + + SendEquipError(msg, null, null, srcitem); + return false; + } + + return true; + } + + public bool TakeQuestSourceItem(uint questId, bool msg) + { + Quest quest = Global.ObjectMgr.GetQuestTemplate(questId); + if (quest != null) + { + uint srcItemId = quest.SourceItemId; + ItemTemplate item = Global.ObjectMgr.GetItemTemplate(srcItemId); + + if (srcItemId > 0) + { + uint count = quest.SourceItemIdCount; + if (count <= 0) + count = 1; + + // exist two cases when destroy source quest item not possible: + // a) non un-equippable item (equipped non-empty bag, for example) + // b) when quest is started from an item and item also is needed in + // the end as RequiredItemId + InventoryResult res = CanUnequipItems(srcItemId, count); + if (res != InventoryResult.Ok) + { + if (msg) + SendEquipError(res, null, null, srcItemId); + return false; + } + + bool destroyItem = true; + foreach (QuestObjective obj in quest.Objectives) + if (obj.Type == QuestObjectiveType.Item && srcItemId == obj.ObjectID) + destroyItem = false; + + if (destroyItem) + DestroyItemCount(srcItemId, count, true, true); + } + } + + return true; + } + + public bool GetQuestRewardStatus(uint quest_id) + { + Quest qInfo = Global.ObjectMgr.GetQuestTemplate(quest_id); + if (qInfo != null) + { + if (qInfo.IsSeasonal() && !qInfo.IsRepeatable()) + { + ushort eventId = Global.GameEventMgr.GetEventIdForQuest(qInfo); + if (m_seasonalquests.ContainsKey(eventId)) + return m_seasonalquests[eventId].Contains(quest_id); + + return false; + } + + // for repeatable quests: rewarded field is set after first reward only to prevent getting XP more than once + if (!qInfo.IsRepeatable()) + return m_RewardedQuests.Contains(quest_id); + + return false; + } + return false; + } + + public QuestStatus GetQuestStatus(uint questId) + { + if (questId != 0) + { + var questStatusData = m_QuestStatus.LookupByKey(questId); + if (questStatusData != null) + return questStatusData.Status; + + Quest quest = Global.ObjectMgr.GetQuestTemplate(questId); + if (quest != null) + { + if (quest.IsSeasonal() && !quest.IsRepeatable()) + { + ushort eventId = Global.GameEventMgr.GetEventIdForQuest(quest); + if (!m_seasonalquests.ContainsKey(eventId) || !m_seasonalquests[eventId].Contains(questId)) + return QuestStatus.None; + } + if (!quest.IsRepeatable() && m_RewardedQuests.Contains(questId)) + return QuestStatus.Rewarded; + } + } + return QuestStatus.None; + } + + public bool CanShareQuest(uint quest_id) + { + Quest qInfo = Global.ObjectMgr.GetQuestTemplate(quest_id); + return qInfo != null && qInfo.HasFlag(QuestFlags.Sharable) && IsActiveQuest(quest_id); + } + + public void SetQuestStatus(uint questId, QuestStatus status, bool update = true) + { + Quest quest = Global.ObjectMgr.GetQuestTemplate(questId); + if (quest != null) + { + if (!m_QuestStatus.ContainsKey(questId)) + m_QuestStatus[questId] = new QuestStatusData(); + + m_QuestStatus[questId].Status = status; + if (!quest.IsAutoComplete()) + m_QuestStatusSave[questId] = QuestSaveType.Default; + } + + if (update) + SendQuestUpdate(questId); + + Global.ScriptMgr.OnQuestStatusChange(this, questId); + } + + public void RemoveActiveQuest(uint quest_id, bool update = true) + { + if (m_QuestStatus.ContainsKey(quest_id)) + { + m_QuestStatus.Remove(quest_id); + m_QuestStatusSave[quest_id] = QuestSaveType.Delete; + } + + if (update) + SendQuestUpdate(quest_id); + } + + public void RemoveRewardedQuest(uint questId, bool update = true) + { + if (m_RewardedQuests.Contains(questId)) + { + m_RewardedQuests.Remove(questId); + m_RewardedQuestsSave[questId] = QuestSaveType.ForceDelete; + } + + uint questBit = Global.DB2Mgr.GetQuestUniqueBitFlag(questId); + if (questBit != 0) + SetQuestCompletedBit(questBit, false); + + if (update) + SendQuestUpdate(questId); + } + + void SendQuestUpdate(uint questid) + { + uint zone, area; + GetZoneAndAreaId(out zone, out area); + + var saBounds = Global.SpellMgr.GetSpellAreaForQuestAreaMapBounds(area, questid); + if (!saBounds.Empty()) + { + foreach (var spell in saBounds) + { + if (!spell.IsFitToRequirements(this, zone, area)) + RemoveAurasDueToSpell(spell.spellId); + else if (spell.autocast) + if (!HasAura(spell.spellId)) + CastSpell(this, spell.spellId, true); + } + } + + UpdateForQuestWorldObjects(); + SendUpdatePhasing(); + } + + public QuestGiverStatus GetQuestDialogStatus(WorldObject questgiver) + { + List qr; + List qir; + + switch (questgiver.GetTypeId()) + { + case TypeId.GameObject: + { + QuestGiverStatus questStatus = (QuestGiverStatus)Global.ScriptMgr.GetDialogStatus(this, questgiver.ToGameObject()); + if (questStatus != QuestGiverStatus.ScriptedNoStatus) + return questStatus; + qr = Global.ObjectMgr.GetGOQuestRelationBounds(questgiver.GetEntry()); + qir = Global.ObjectMgr.GetGOQuestInvolvedRelationBounds(questgiver.GetEntry()); + break; + } + case TypeId.Unit: + { + QuestGiverStatus questStatus = (QuestGiverStatus)Global.ScriptMgr.GetDialogStatus(this, questgiver.ToCreature()); + if (questStatus != QuestGiverStatus.ScriptedNoStatus) + return questStatus; + qr = Global.ObjectMgr.GetCreatureQuestRelationBounds(questgiver.GetEntry()); + qir = Global.ObjectMgr.GetCreatureQuestInvolvedRelationBounds(questgiver.GetEntry()); + break; + } + default: + // it's impossible, but check + Log.outError(LogFilter.Player, "GetQuestDialogStatus called for unexpected type {0}", questgiver.GetTypeId()); + return QuestGiverStatus.None; + } + + QuestGiverStatus result = QuestGiverStatus.None; + + foreach (var questId in qir) + { + QuestGiverStatus result2 = QuestGiverStatus.None; + Quest quest = Global.ObjectMgr.GetQuestTemplate(questId); + if (quest == null) + continue; + + if (!Global.ConditionMgr.IsObjectMeetingNotGroupedConditions(ConditionSourceType.QuestAccept, quest.Id, this)) + continue; + + QuestStatus status = GetQuestStatus(questId); + if ((status == QuestStatus.Complete && !GetQuestRewardStatus(questId)) || + (quest.IsAutoComplete() && CanTakeQuest(quest, false))) + { + if (quest.IsAutoComplete() && quest.IsRepeatable() && !quest.IsDailyOrWeekly()) + result2 = QuestGiverStatus.RewardRep; + else + result2 = QuestGiverStatus.Reward; + } + else if (status == QuestStatus.Incomplete) + result2 = QuestGiverStatus.Incomplete; + + if (result2 > result) + result = result2; + } + + foreach (var questId in qr) + { + QuestGiverStatus result2 = QuestGiverStatus.None; + Quest quest = Global.ObjectMgr.GetQuestTemplate(questId); + if (quest == null) + continue; + + if (!Global.ConditionMgr.IsObjectMeetingNotGroupedConditions(ConditionSourceType.QuestAccept, quest.Id, this)) + continue; + + QuestStatus status = GetQuestStatus(questId); + if (status == QuestStatus.None) + { + if (CanSeeStartQuest(quest)) + { + if (SatisfyQuestLevel(quest, false)) + { + if (quest.IsAutoComplete()) + result2 = QuestGiverStatus.RewardRep; + else if (getLevel() <= (GetQuestLevel(quest) + WorldConfig.GetIntValue(WorldCfg.QuestLowLevelHideDiff))) + { + if (quest.IsDaily()) + result2 = QuestGiverStatus.AvailableRep; + else + result2 = QuestGiverStatus.Available; + } + else + result2 = QuestGiverStatus.LowLevelAvailable; + } + else + result2 = QuestGiverStatus.Unavailable; + } + } + + if (result2 > result) + result = result2; + } + + return result; + } + + public ushort GetReqKillOrCastCurrentCount(uint quest_id, int entry) + { + Quest qInfo = Global.ObjectMgr.GetQuestTemplate(quest_id); + if (qInfo == null) + return 0; + + foreach (QuestObjective obj in qInfo.Objectives) + if (obj.ObjectID == entry) + return (ushort)GetQuestObjectiveData(qInfo, obj.StorageIndex); + + return 0; + } + + public void AdjustQuestReqItemCount(Quest quest) + { + if (quest.HasSpecialFlag(QuestSpecialFlags.Deliver)) + { + foreach (QuestObjective obj in quest.Objectives) + { + if (obj.Type != QuestObjectiveType.Item) + continue; + + uint reqItemCount = (uint)obj.Amount; + uint curItemCount = GetItemCount((uint)obj.ObjectID, true); + SetQuestObjectiveData(obj, (int)Math.Min(curItemCount, reqItemCount)); + } + } + } + + public ushort FindQuestSlot(uint quest_id) + { + for (ushort i = 0; i < SharedConst.MaxQuestLogSize; ++i) + if (GetQuestSlotQuestId(i) == quest_id) + return i; + + return SharedConst.MaxQuestLogSize; + } + + public uint GetQuestSlotQuestId(ushort slot) + { + return GetUInt32Value(PlayerFields.QuestLog + (slot * QuestSlotOffsets.Max) + QuestSlotOffsets.Id); + } + + public uint GetQuestSlotState(ushort slot, byte counter) + { + return GetUInt32Value(PlayerFields.QuestLog + (slot * QuestSlotOffsets.Max) + QuestSlotOffsets.State); + } + + public ushort GetQuestSlotCounter(ushort slot, byte counter) + { + if (counter < SharedConst.MaxQuestCounts) + return GetUInt16Value(PlayerFields.QuestLog + slot * QuestSlotOffsets.Max + QuestSlotOffsets.Counts + counter /2, (byte)(counter % 2)); + + return 0; + } + + public uint GetQuestSlotTime(ushort slot) + { + return GetUInt32Value(PlayerFields.QuestLog + (slot * QuestSlotOffsets.Max) + QuestSlotOffsets.Time); + } + + public void SetQuestSlot(ushort slot, uint quest_id, uint timer = 0) + { + SetUInt32Value(PlayerFields.QuestLog + (slot * QuestSlotOffsets.Max) + QuestSlotOffsets.Id, quest_id); + SetUInt32Value(PlayerFields.QuestLog + (slot * QuestSlotOffsets.Max) + QuestSlotOffsets.State, 0); + for (int i = 0; i < SharedConst.MaxQuestCounts / 2; ++i) + SetUInt32Value(PlayerFields.QuestLog + slot * QuestSlotOffsets.Max + QuestSlotOffsets.Counts + i, 0); + SetUInt32Value(PlayerFields.QuestLog + (slot * QuestSlotOffsets.Max) + QuestSlotOffsets.Time, timer); + } + + public void SetQuestSlotCounter(ushort slot, byte counter, ushort count) + { + if (counter >= SharedConst.MaxQuestCounts) + return; + + SetUInt16Value(PlayerFields.QuestLog + slot * QuestSlotOffsets.Max + QuestSlotOffsets.Counts + counter / 2, (byte)(counter % 2), count); + } + + public void SetQuestSlotState(ushort slot, QuestSlotStateMask state) + { + SetFlag(PlayerFields.QuestLog + (slot * QuestSlotOffsets.Max) + QuestSlotOffsets.State, state); + } + + public void RemoveQuestSlotState(ushort slot, QuestSlotStateMask state) + { + RemoveFlag(PlayerFields.QuestLog + (slot * QuestSlotOffsets.Max) + QuestSlotOffsets.State, state); + } + + public void SetQuestSlotTimer(ushort slot, uint timer) + { + SetUInt32Value(PlayerFields.QuestLog + (slot * QuestSlotOffsets.Max) + QuestSlotOffsets.Time, timer); + } + + void SetQuestCompletedBit(uint questBit, bool completed) + { + if (questBit == 0) + return; + + int fieldOffset = ((int)questBit - 1) >> 5; + if (fieldOffset >= PlayerConst.QuestsCompletedBitsSize) + return; + + ApplyModFlag(PlayerFields.QuestCompleted + fieldOffset, (uint)(1 << (((int)questBit - 1) & 31)), completed); + } + + public void AreaExploredOrEventHappens(uint questId) + { + if (questId != 0) + { + ushort log_slot = FindQuestSlot(questId); + if (log_slot < SharedConst.MaxQuestLogSize) + { + Log.outError(LogFilter.Player, "Deprecated function AreaExploredOrEventHappens called for quest {0}", questId); + /* @todo + This function was previously used for area triggers but now those are a part of quest objective system + Currently this function is used to complete quests with no objectives (needs verifying) so probably rename it? + + QuestStatusData& q_status = m_QuestStatus[questId]; + + if (!q_status.Explored) + { + q_status.Explored = true; + m_QuestStatusSave[questId] = QUEST_DEFAULT_SAVE_TYPE; + SetQuestSlotState(log_slot, QUEST_STATE_COMPLETE); + SendQuestComplete(questId); + }*/ + } + if (CanCompleteQuest(questId)) + CompleteQuest(questId); + } + } + + public void GroupEventHappens(uint questId, WorldObject pEventObject) + { + var group = GetGroup(); + if (group) + { + for (GroupReference refe = group.GetFirstMember(); refe != null; refe = refe.next()) + { + Player player = refe.GetSource(); + + // for any leave or dead (with not released body) group member at appropriate distance + if (player && player.IsAtGroupRewardDistance(pEventObject) && !player.GetCorpse()) + player.AreaExploredOrEventHappens(questId); + } + } + else + AreaExploredOrEventHappens(questId); + } + + public void ItemAddedQuestCheck(uint entry, uint count) + { + for (byte i = 0; i < SharedConst.MaxQuestLogSize; ++i) + { + uint questid = GetQuestSlotQuestId(i); + if (questid == 0) + continue; + + QuestStatusData q_status = m_QuestStatus[questid]; + + if (q_status.Status != QuestStatus.Incomplete) + continue; + + Quest qInfo = Global.ObjectMgr.GetQuestTemplate(questid); + if (qInfo == null || !qInfo.HasSpecialFlag(QuestSpecialFlags.Deliver)) + continue; + + foreach (QuestObjective obj in qInfo.Objectives) + { + if (obj.Type != QuestObjectiveType.Item) + continue; + + int reqItem = obj.ObjectID; + if (reqItem == entry) + { + int reqItemCount = obj.Amount; + int curItemCount = GetQuestObjectiveData(qInfo, obj.StorageIndex); + if (curItemCount < reqItemCount) + { + int newItemCount = (int)Math.Min(curItemCount + count, reqItemCount); + SetQuestObjectiveData(obj, newItemCount); + + //SendQuestUpdateAddItem(qInfo, j, additemcount); + // FIXME: verify if there's any packet sent updating item + } + + if (CanCompleteQuest(questid)) + CompleteQuest(questid); + + return; + } + } + } + UpdateForQuestWorldObjects(); + } + + public void ItemRemovedQuestCheck(uint entry, uint count) + { + for (byte i = 0; i < SharedConst.MaxQuestLogSize; ++i) + { + uint questid = GetQuestSlotQuestId(i); + if (questid == 0) + continue; + Quest qInfo = Global.ObjectMgr.GetQuestTemplate(questid); + if (qInfo == null) + continue; + if (!qInfo.HasSpecialFlag(QuestSpecialFlags.Deliver)) + continue; + + foreach (QuestObjective obj in qInfo.Objectives) + { + if (obj.Type != QuestObjectiveType.Item) + continue; + + int reqItem = obj.ObjectID; + if (reqItem == entry) + { + uint reqItemCount = (uint)obj.Amount; + int curItemCount = GetQuestObjectiveData(qInfo, obj.StorageIndex); + + if (curItemCount >= reqItemCount) // we may have more than what the status shows + curItemCount = (int)GetItemCount(entry, false); + + int newItemCount = (int)((count > curItemCount) ? 0 : curItemCount - count); + + if (newItemCount < reqItemCount) + { + SetQuestObjectiveData(obj, newItemCount); + IncompleteQuest(questid); + } + return; + } + } + } + UpdateForQuestWorldObjects(); + } + + public void KilledMonster(CreatureTemplate cInfo, ObjectGuid guid) + { + Contract.Assert(cInfo != null); + + if (cInfo.Entry != 0) + KilledMonsterCredit(cInfo.Entry, guid); + + for (byte i = 0; i < 2; ++i) + if (cInfo.KillCredit[i] != 0) + KilledMonsterCredit(cInfo.KillCredit[i]); + } + + public void KilledMonsterCredit(uint entry, ObjectGuid guid = default(ObjectGuid)) + { + ushort addKillCount = 1; + uint real_entry = entry; + Creature killed = null; + if (!guid.IsEmpty()) + { + killed = GetMap().GetCreature(guid); + if (killed != null && killed.GetEntry() != 0) + real_entry = killed.GetEntry(); + } + + StartCriteriaTimer(CriteriaTimedTypes.Creature, real_entry); // MUST BE CALLED FIRST + UpdateCriteria(CriteriaTypes.KillCreature, real_entry, addKillCount, 0, killed); + + for (byte i = 0; i < SharedConst.MaxQuestLogSize; ++i) + { + uint questid = GetQuestSlotQuestId(i); + if (questid == 0) + continue; + + Quest qInfo = Global.ObjectMgr.GetQuestTemplate(questid); + if (qInfo == null) + continue; + + // just if !ingroup || !noraidgroup || raidgroup + QuestStatusData q_status = m_QuestStatus[questid]; + if (q_status.Status == QuestStatus.Incomplete && (GetGroup() == null || !GetGroup().isRaidGroup() || qInfo.IsAllowedInRaid(GetMap().GetDifficultyID()))) + { + if (qInfo.HasSpecialFlag(QuestSpecialFlags.Kill))// && !qInfo.HasSpecialFlag(QuestSpecialFlags.Cast)) + { + foreach (QuestObjective obj in qInfo.Objectives) + { + if (obj.Type != QuestObjectiveType.Monster) + continue; + + int reqkill = obj.ObjectID; + if (reqkill == real_entry) + { + int curKillCount = GetQuestObjectiveData(qInfo, obj.StorageIndex); + if (curKillCount < obj.Amount) + { + SetQuestObjectiveData(obj, curKillCount + addKillCount); + SendQuestUpdateAddCredit(qInfo, guid, obj, (uint)(curKillCount + addKillCount)); + } + + if (CanCompleteQuest(questid)) + CompleteQuest(questid); + + // same objective target can be in many active quests, but not in 2 objectives for single quest (code optimization). + break; + } + } + } + } + } + } + + public void KilledPlayerCredit() + { + ushort addKillCount = 1; + + for (byte i = 0; i < SharedConst.MaxQuestLogSize; ++i) + { + uint questid = GetQuestSlotQuestId(i); + if (questid == 0) + continue; + + Quest qInfo = Global.ObjectMgr.GetQuestTemplate(questid); + if (qInfo == null) + continue; + + // just if !ingroup || !noraidgroup || raidgroup + QuestStatusData q_status = m_QuestStatus[questid]; + if (q_status.Status == QuestStatus.Incomplete && (GetGroup() == null || !GetGroup().isRaidGroup() || qInfo.IsAllowedInRaid(GetMap().GetDifficultyID()))) + { + foreach (QuestObjective obj in qInfo.Objectives) + { + if (obj.Type != QuestObjectiveType.PlayerKills) + continue; + + int curKillCount = GetQuestObjectiveData(qInfo, obj.StorageIndex); + if (curKillCount < obj.Amount) + { + SetQuestObjectiveData(obj, curKillCount + addKillCount); + SendQuestUpdateAddPlayer(qInfo, (uint)(curKillCount + addKillCount)); + } + + if (CanCompleteQuest(questid)) + CompleteQuest(questid); + + // Quest can't have more than one player kill objective (code optimisation) + break; + } + } + } + } + + public void KillCreditGO(uint entry, ObjectGuid guid = default(ObjectGuid)) + { + ushort addCastCount = 1; + for (byte i = 0; i < SharedConst.MaxQuestLogSize; ++i) + { + uint questid = GetQuestSlotQuestId(i); + if (questid == 0) + continue; + + Quest qInfo = Global.ObjectMgr.GetQuestTemplate(questid); + if (qInfo == null) + continue; + + QuestStatusData q_status = m_QuestStatus[questid]; + + if (q_status.Status == QuestStatus.Incomplete) + { + if (qInfo.HasSpecialFlag(QuestSpecialFlags.Cast)) + { + foreach (QuestObjective obj in qInfo.Objectives) + { + if (obj.Type != QuestObjectiveType.GameObject) + continue; + + int reqTarget = obj.ObjectID; + + // other not this creature/GO related objectives + if (reqTarget != entry) + continue; + + int curCastCount = GetQuestObjectiveData(qInfo, obj.StorageIndex); + if (curCastCount < obj.Amount) + { + SetQuestObjectiveData(obj, curCastCount + addCastCount); + SendQuestUpdateAddCredit(qInfo, guid, obj, (uint)(curCastCount + addCastCount)); + } + + if (CanCompleteQuest(questid)) + CompleteQuest(questid); + + // same objective target can be in many active quests, but not in 2 objectives for single quest (code optimization). + break; + } + } + } + } + } + + public void TalkedToCreature(uint entry, ObjectGuid guid) + { + ushort addTalkCount = 1; + for (byte i = 0; i < SharedConst.MaxQuestLogSize; ++i) + { + uint questid = GetQuestSlotQuestId(i); + if (questid == 0) + continue; + + Quest qInfo = Global.ObjectMgr.GetQuestTemplate(questid); + if (qInfo == null) + continue; + + QuestStatusData q_status = m_QuestStatus[questid]; + + if (q_status.Status == QuestStatus.Incomplete) + { + if (qInfo.HasSpecialFlag(QuestSpecialFlags.Kill | QuestSpecialFlags.Cast | QuestSpecialFlags.Speakto)) + { + foreach (QuestObjective obj in qInfo.Objectives) + { + if (obj.Type != QuestObjectiveType.TalkTo) + continue; + + int reqTarget = obj.ObjectID; + if (reqTarget == entry) + { + int curTalkCount = GetQuestObjectiveData(qInfo, obj.StorageIndex); + if (curTalkCount < obj.Amount) + { + SetQuestObjectiveData(obj, curTalkCount + addTalkCount); + SendQuestUpdateAddCredit(qInfo, guid, obj, (uint)(curTalkCount + addTalkCount)); + } + + if (CanCompleteQuest(questid)) + CompleteQuest(questid); + + // Quest can't have more than one objective for the same creature (code optimisation) + break; + } + } + } + } + } + } + + public void MoneyChanged(ulong value) + { + for (byte i = 0; i < SharedConst.MaxQuestLogSize; ++i) + { + uint questid = GetQuestSlotQuestId(i); + if (questid == 0) + continue; + + Quest qInfo = Global.ObjectMgr.GetQuestTemplate(questid); + if (qInfo == null) + continue; + + foreach (QuestObjective obj in qInfo.Objectives) + { + if (obj.Type != QuestObjectiveType.Money) + continue; + + QuestStatusData q_status = m_QuestStatus[questid]; + if (q_status.Status == QuestStatus.Incomplete) + { + if ((long)value >= obj.Amount) + { + if (CanCompleteQuest(questid)) + CompleteQuest(questid); + } + } + else if (q_status.Status == QuestStatus.Complete) + { + if ((long)value < obj.Amount) + IncompleteQuest(questid); + } + } + } + } + + public void ReputationChanged(FactionRecord FactionRecord) + { + for (byte i = 0; i < SharedConst.MaxQuestLogSize; ++i) + { + uint questid = GetQuestSlotQuestId(i); + if (questid != 0) + { + Quest qInfo = Global.ObjectMgr.GetQuestTemplate(questid); + if (qInfo != null) + { + QuestStatusData q_status = m_QuestStatus[questid]; + + foreach (QuestObjective obj in qInfo.Objectives) + { + if (obj.ObjectID != FactionRecord.Id) + continue; + + if (obj.Type == QuestObjectiveType.MinReputation) + { + if (q_status.Status == QuestStatus.Incomplete) + { + if (GetReputationMgr().GetReputation(FactionRecord) >= obj.Amount) + if (CanCompleteQuest(questid)) + CompleteQuest(questid); + } + else if (q_status.Status == QuestStatus.Complete) + { + if (GetReputationMgr().GetReputation(FactionRecord) < obj.Amount) + IncompleteQuest(questid); + } + } + else if (obj.Type == QuestObjectiveType.MaxReputation) + { + if (q_status.Status == QuestStatus.Incomplete) + { + if (GetReputationMgr().GetReputation(FactionRecord) <= obj.Amount) + if (CanCompleteQuest(questid)) + CompleteQuest(questid); + } + else if (q_status.Status == QuestStatus.Complete) + { + if (GetReputationMgr().GetReputation(FactionRecord) > obj.Amount) + IncompleteQuest(questid); + } + } + } + } + } + } + } + + void CurrencyChanged(uint currencyId, int change) + { + for (byte i = 0; i < SharedConst.MaxQuestLogSize; ++i) + { + uint questid = GetQuestSlotQuestId(i); + if (questid == 0) + continue; + + Quest qInfo = Global.ObjectMgr.GetQuestTemplate(questid); + if (qInfo == null) + continue; + + foreach (QuestObjective obj in qInfo.Objectives) + { + if (obj.ObjectID != currencyId) + continue; + + QuestStatusData q_status = m_QuestStatus[questid]; + if (obj.Type == QuestObjectiveType.Currency || obj.Type == QuestObjectiveType.HaveCurrency) + { + long value = GetCurrency(currencyId); + if (obj.Type == QuestObjectiveType.HaveCurrency) + SetQuestObjectiveData(obj, (int)Math.Min(value, obj.Amount)); + + if (q_status.Status == QuestStatus.Incomplete) + { + if (value >= obj.Amount) + { + if (CanCompleteQuest(questid)) + CompleteQuest(questid); + } + } + else if (q_status.Status == QuestStatus.Complete) + { + if (value < obj.Amount) + IncompleteQuest(questid); + } + } + else if (obj.Type == QuestObjectiveType.ObtainCurrency && change > 0) // currency losses are not accounted for in this objective type + { + long currentProgress = GetQuestObjectiveData(qInfo, obj.StorageIndex); + SetQuestObjectiveData(obj, (int)Math.Max(Math.Min(currentProgress + change, obj.Amount), 0)); + if (CanCompleteQuest(questid)) + CompleteQuest(questid); + } + } + } + } + + public bool HasQuestForItem(uint itemid) + { + for (byte i = 0; i < SharedConst.MaxQuestLogSize; ++i) + { + uint questid = GetQuestSlotQuestId(i); + if (questid == 0) + continue; + + var q_status = m_QuestStatus.LookupByKey(questid); + if (q_status == null) + continue; + + if (q_status.Status == QuestStatus.Incomplete) + { + Quest qInfo = Global.ObjectMgr.GetQuestTemplate(questid); + if (qInfo == null) + continue; + + // hide quest if player is in raid-group and quest is no raid quest + if (GetGroup() != null && GetGroup().isRaidGroup() && !qInfo.IsAllowedInRaid(GetMap().GetDifficultyID())) + if (!InBattleground()) //there are two ways.. we can make every bg-quest a raidquest, or add this code here.. i don't know if this can be exploited by other quests, but i think all other quests depend on a specific area.. but keep this in mind, if something strange happens later + continue; + + // There should be no mixed ReqItem/ReqSource drop + // This part for ReqItem drop + foreach (QuestObjective obj in qInfo.Objectives) + { + if (obj.Type == QuestObjectiveType.Item && itemid == obj.ObjectID && GetQuestObjectiveData(qInfo, obj.StorageIndex) < obj.Amount) + return true; + } + // This part - for ReqSource + for (byte j = 0; j < SharedConst.QuestItemDropCount; ++j) + { + // examined item is a source item + if (qInfo.ItemDrop[j] == itemid) + { + ItemTemplate pProto = Global.ObjectMgr.GetItemTemplate(itemid); + + // 'unique' item + if (pProto.GetMaxCount() != 0 && GetItemCount(itemid, true) < pProto.GetMaxCount()) + return true; + + // allows custom amount drop when not 0 + if (qInfo.ItemDropQuantity[j] != 0) + { + if (GetItemCount(itemid, true) < qInfo.ItemDropQuantity[j]) + return true; + } + else if (GetItemCount(itemid, true) < pProto.GetMaxStackSize()) + return true; + } + } + } + } + return false; + } + + public int GetQuestObjectiveData(Quest quest, sbyte storageIndex) + { + if (storageIndex < 0) + Log.outError(LogFilter.Player, "GetQuestObjectiveData: called for quest {0} with invalid StorageIndex {1} (objective data is not tracked)", quest.Id, storageIndex); + + var status = m_QuestStatus.LookupByKey(quest.Id); + + if (status == null) + { + Log.outError(LogFilter.Player, "GetQuestObjectiveData: player {0} ({1}) doesn't have quest status data for quest {2}", GetName(), GetGUID().ToString(), quest.Id); + return 0; + } + + if (storageIndex >= status.ObjectiveData.Length) + { + Log.outError(LogFilter.Player, "GetQuestObjectiveData: player {0} ({1}) quest {2} out of range StorageIndex {3}", GetName(), GetGUID().ToString(), quest.Id, storageIndex); + return 0; + } + + return status.ObjectiveData[storageIndex]; + } + + bool IsQuestObjectiveProgressComplete(Quest quest) + { + float progress = 0; + foreach (QuestObjective obj in quest.Objectives) + { + if (obj.Flags.HasAnyFlag(QuestObjectiveFlags.PartOfProgressBar)) + { + progress += GetQuestObjectiveData(quest, obj.StorageIndex) * obj.ProgressBarWeight; + if (progress >= 100) + return true; + } + } + return false; + } + + public bool IsQuestObjectiveComplete(QuestObjective objective) + { + Quest quest = Global.ObjectMgr.GetQuestTemplate(objective.QuestID); + //ASSERT(quest); + + switch (objective.Type) + { + case QuestObjectiveType.Monster: + case QuestObjectiveType.Item: + case QuestObjectiveType.GameObject: + case QuestObjectiveType.PlayerKills: + case QuestObjectiveType.TalkTo: + case QuestObjectiveType.WinPvpPetBattles: + case QuestObjectiveType.HaveCurrency: + case QuestObjectiveType.ObtainCurrency: + if (GetQuestObjectiveData(quest, objective.StorageIndex) < objective.Amount) + return false; + break; + case QuestObjectiveType.MinReputation: + if (GetReputationMgr().GetReputation((uint)objective.ObjectID) < objective.Amount) + return false; + break; + case QuestObjectiveType.MaxReputation: + if (GetReputationMgr().GetReputation((uint)objective.ObjectID) > objective.Amount) + return false; + break; + case QuestObjectiveType.Money: + if (!HasEnoughMoney(objective.Amount)) + return false; + break; + case QuestObjectiveType.AreaTrigger: + if (GetQuestObjectiveData(quest, objective.StorageIndex) == 0) + return false; + break; + case QuestObjectiveType.LearnSpell: + if (!HasSpell((uint)objective.ObjectID)) + return false; + break; + case QuestObjectiveType.Currency: + if (!HasCurrency((uint)objective.ObjectID, (uint)objective.Amount)) + return false; + break; + case QuestObjectiveType.ProgressBar: + if (!IsQuestObjectiveProgressComplete(quest)) + return false; + break; + default: + Log.outError(LogFilter.Player, "Player.CanCompleteQuest: Player '{0}' ({1}) tried to complete a quest (ID: {2}) with an unknown objective type {3}", + GetName(), GetGUID().ToString(), objective.QuestID, objective.Type); + return false; + } + + return true; + } + + public void SetQuestObjectiveData(QuestObjective objective, int data) + { + if (objective.StorageIndex < 0) + Log.outError(LogFilter.Player, "SetQuestObjectiveData: called for quest {0} with invalid StorageIndex {1} (objective data is not tracked)", objective.QuestID, objective.StorageIndex); + + var status = m_QuestStatus.LookupByKey(objective.QuestID); + if (status == null) + { + Log.outError(LogFilter.Player, "SetQuestObjectiveData: player {0} ({1}) doesn't have quest status data for quest {2}", GetName(), GetGUID().ToString(), objective.QuestID); + return; + } + + if (objective.StorageIndex >= status.ObjectiveData.Length) + { + Log.outError(LogFilter.Player, "SetQuestObjectiveData: player {0} ({1}) quest {2} out of range StorageIndex {3}", GetName(), GetGUID().ToString(), objective.QuestID, objective.StorageIndex); + return; + } + + // No change + if (status.ObjectiveData[objective.StorageIndex] == data) + return; + + // Set data + status.ObjectiveData[objective.StorageIndex] = data; + + // Add to save + m_QuestStatusSave[objective.QuestID] = QuestSaveType.Default; + + // Update quest fields + ushort log_slot = FindQuestSlot(objective.QuestID); + if (log_slot < SharedConst.MaxQuestLogSize) + { + if (!objective.IsStoringFlag()) + SetQuestSlotCounter(log_slot, (byte)objective.StorageIndex, (ushort)status.ObjectiveData[objective.StorageIndex]); + else if (data != 0) + SetQuestSlotState(log_slot, (QuestSlotStateMask)(256 << objective.StorageIndex)); + else + RemoveQuestSlotState(log_slot, (QuestSlotStateMask)(256 << objective.StorageIndex)); + } + } + + public void SendQuestComplete(Quest quest) + { + if (quest != null) + { + QuestUpdateComplete data = new QuestUpdateComplete(); + data.QuestID = quest.Id; + SendPacket(data); + } + } + + public void SendQuestReward(Quest quest, Creature questGiver, uint xp, bool hideChatMessage) + { + uint questId = quest.Id; + Global.GameEventMgr.HandleQuestComplete(questId); + + uint moneyReward; + + if (getLevel() < WorldConfig.GetIntValue(WorldCfg.MaxPlayerLevel)) + { + moneyReward = GetQuestMoneyReward(quest); + } + else // At max level, increase gold reward + { + xp = 0; + moneyReward = (uint)(GetQuestMoneyReward(quest) + (int)(quest.GetRewMoneyMaxLevel() * WorldConfig.GetFloatValue(WorldCfg.RateDropMoney))); + } + + QuestGiverQuestComplete packet = new QuestGiverQuestComplete(); + + packet.QuestID = questId; + packet.MoneyReward = moneyReward; + packet.XPReward = xp; + packet.SkillLineIDReward = quest.RewardSkillId; + packet.NumSkillUpsReward = quest.RewardSkillPoints; + + if (questGiver) + { + if (questGiver.IsGossip()) + packet.LaunchGossip = true; + else if (questGiver.IsQuestGiver()) + packet.LaunchQuest = true; + else if (quest.NextQuestInChain != 0 && !quest.HasFlag(QuestFlags.AutoComplete)) + packet.UseQuestReward = true; + } + + packet.HideChatMessage = hideChatMessage; + + SendPacket(packet); + } + + public void SendQuestFailed(uint questId, InventoryResult reason = InventoryResult.Ok) + { + if (questId != 0) + { + QuestGiverQuestFailed questGiverQuestFailed = new QuestGiverQuestFailed(); + questGiverQuestFailed.QuestID = questId; + questGiverQuestFailed.Reason = reason; // failed reason (valid reasons: 4, 16, 50, 17, other values show default message) + SendPacket(questGiverQuestFailed); + } + } + + public void SendQuestTimerFailed(uint questId) + { + if (questId != 0) + { + QuestUpdateFailedTimer questUpdateFailedTimer = new QuestUpdateFailedTimer(); + questUpdateFailedTimer.QuestID = questId; + SendPacket(questUpdateFailedTimer); + } + } + + public void SendCanTakeQuestResponse(QuestFailedReasons reason, bool sendErrorMessage = true, string reasonText = "") + { + QuestGiverInvalidQuest questGiverInvalidQuest = new QuestGiverInvalidQuest(); + + questGiverInvalidQuest.Reason = reason; + questGiverInvalidQuest.SendErrorMessage = sendErrorMessage; + questGiverInvalidQuest.ReasonText = reasonText; + + SendPacket(questGiverInvalidQuest); + } + + public void SendQuestConfirmAccept(Quest quest, Player receiver) + { + if (!receiver) + return; + + QuestConfirmAcceptResponse packet = new QuestConfirmAcceptResponse(); + + packet.QuestTitle = quest.LogTitle; + + LocaleConstant loc_idx = receiver.GetSession().GetSessionDbLocaleIndex(); + if (loc_idx != LocaleConstant.enUS) + { + QuestTemplateLocale questLocale = Global.ObjectMgr.GetQuestLocale(quest.Id); + if (questLocale != null) + ObjectManager.GetLocaleString(questLocale.LogTitle, loc_idx, ref packet.QuestTitle); + } + + packet.QuestID = quest.Id; + packet.InitiatedBy = GetGUID(); + + receiver.SendPacket(packet); + } + + public void SendPushToPartyResponse(Player player, QuestPushReason reason) + { + if (player != null) + { + QuestPushResultResponse data = new QuestPushResultResponse(); + data.SenderGUID = player.GetGUID(); + data.Result = reason; + SendPacket(data); + } + } + + void SendQuestUpdateAddCredit(Quest quest, ObjectGuid guid, QuestObjective obj, uint count) + { + QuestUpdateAddCredit packet = new QuestUpdateAddCredit(); + packet.VictimGUID = guid; + packet.QuestID = quest.Id; + packet.ObjectID = obj.ObjectID; + packet.Count = (ushort)count; + packet.Required = (ushort)obj.Amount; + packet.ObjectiveType = (byte)obj.Type; + SendPacket(packet); + } + + public void SendQuestUpdateAddCreditSimple(QuestObjective obj) + { + QuestUpdateAddCreditSimple packet = new QuestUpdateAddCreditSimple(); + packet.QuestID = obj.QuestID; + packet.ObjectID = obj.ObjectID; + packet.ObjectiveType = obj.Type; + SendPacket(packet); + } + + public void SendQuestUpdateAddPlayer(Quest quest, uint newCount) + { + QuestUpdateAddPvPCredit packet = new QuestUpdateAddPvPCredit(); + packet.QuestID = quest.Id; + packet.Count = (ushort)newCount; + SendPacket(packet); + } + + public void SendQuestGiverStatusMultiple() + { + QuestGiverStatusMultiple response = new QuestGiverStatusMultiple(); + + foreach (var itr in m_clientGUIDs) + { + if (itr.IsAnyTypeCreature()) + { + // need also pet quests case support + Creature questgiver = ObjectAccessor.GetCreatureOrPetOrVehicle(this, itr); + if (!questgiver || questgiver.IsHostileTo(this)) + continue; + + if (!questgiver.HasFlag64(UnitFields.NpcFlags, NPCFlags.QuestGiver)) + continue; + + response.QuestGiver.Add(new QuestGiverInfo(questgiver.GetGUID(), GetQuestDialogStatus(questgiver))); + } + else if (itr.IsGameObject()) + { + GameObject questgiver = GetMap().GetGameObject(itr); + if (!questgiver || questgiver.GetGoType() != GameObjectTypes.QuestGiver) + continue; + + response.QuestGiver.Add(new QuestGiverInfo(questgiver.GetGUID(), GetQuestDialogStatus(questgiver))); + } + } + + SendPacket(response); + } + + public bool HasPvPForcingQuest() + { + for (byte i = 0; i < SharedConst.MaxQuestLogSize; ++i) + { + uint questId = GetQuestSlotQuestId(i); + if (questId == 0) + continue; + + Quest quest = Global.ObjectMgr.GetQuestTemplate(questId); + if (quest == null) + continue; + + if (quest.HasFlag(QuestFlags.Pvp)) + return true; + } + + return false; + } + + public bool HasQuestForGO(int GOId) + { + for (byte i = 0; i < SharedConst.MaxQuestLogSize; ++i) + { + uint questid = GetQuestSlotQuestId(i); + if (questid == 0) + continue; + + var qs = m_QuestStatus.LookupByKey(questid); + if (qs == null) + continue; + + if (qs.Status == QuestStatus.Incomplete) + { + Quest qInfo = Global.ObjectMgr.GetQuestTemplate(questid); + if (qInfo == null) + continue; + + if (GetGroup() != null && GetGroup().isRaidGroup() && !qInfo.IsAllowedInRaid(GetMap().GetDifficultyID())) + continue; + + foreach (QuestObjective obj in qInfo.Objectives) + { + if (obj.Type != QuestObjectiveType.GameObject) //skip non GO case + continue; + + if (GOId == obj.ObjectID && GetQuestObjectiveData(qInfo, obj.StorageIndex) < obj.Amount) + return true; + } + } + } + return false; + } + + public void UpdateForQuestWorldObjects() + { + if (m_clientGUIDs.Empty()) + return; + + UpdateData udata = new UpdateData(GetMapId()); + UpdateObject packet; + foreach (var guid in m_clientGUIDs) + { + if (guid.IsGameObject()) + { + GameObject obj = ObjectAccessor.GetGameObject(this, guid); + if (obj != null) + obj.BuildValuesUpdateBlockForPlayer(udata, this); + } + else if (guid.IsCreatureOrVehicle()) + { + Creature obj = ObjectAccessor.GetCreatureOrPetOrVehicle(this, guid); + if (obj == null) + continue; + + // check if this unit requires quest specific flags + if (!obj.HasFlag64(UnitFields.NpcFlags, NPCFlags.SpellClick)) + continue; + + var clickPair = Global.ObjectMgr.GetSpellClickInfoMapBounds(obj.GetEntry()); + foreach (var spell in clickPair) + { + //! This code doesn't look right, but it was logically converted to condition system to do the exact + //! same thing it did before. It definitely needs to be overlooked for intended functionality. + List conds = Global.ConditionMgr.GetConditionsForSpellClickEvent(obj.GetEntry(), spell.spellId); + if (conds != null) + { + bool buildUpdateBlock = false; + for (var i = 0; i < conds.Count && !buildUpdateBlock; ++i) + if (conds[i].ConditionType == ConditionTypes.QuestRewarded || conds[i].ConditionType == ConditionTypes.QuestTaken) + buildUpdateBlock = true; + + if (buildUpdateBlock) + { + obj.BuildValuesUpdateBlockForPlayer(udata, this); + break; + } + } + } + } + } + udata.BuildPacket(out packet); + SendPacket(packet); + } + + void SetDailyQuestStatus(uint quest_id) + { + Quest qQuest = Global.ObjectMgr.GetQuestTemplate(quest_id); + if (qQuest != null) + { + if (!qQuest.IsDFQuest()) + { + AddDynamicValue(PlayerDynamicFields.DailyQuests, quest_id); + m_lastDailyQuestTime = Time.UnixTime; // last daily quest time + m_DailyQuestChanged = true; + + } + else + { + m_DFQuests.Add(quest_id); + m_lastDailyQuestTime = Time.UnixTime; + m_DailyQuestChanged = true; + } + } + } + + public bool IsDailyQuestDone(uint quest_id) + { + bool found = false; + if (Global.ObjectMgr.GetQuestTemplate(quest_id) != null) + { + var dailies = GetDynamicValues(PlayerDynamicFields.DailyQuests); + foreach (uint dailyQuestId in dailies) + { + if (dailyQuestId == quest_id) + { + found = true; + break; + } + } + } + + return found; + } + + void SetWeeklyQuestStatus(uint quest_id) + { + m_weeklyquests.Add(quest_id); + m_WeeklyQuestChanged = true; + } + + void SetSeasonalQuestStatus(uint quest_id) + { + Quest quest = Global.ObjectMgr.GetQuestTemplate(quest_id); + if (quest == null) + return; + + m_seasonalquests.Add(Global.GameEventMgr.GetEventIdForQuest(quest), quest_id); + m_SeasonalQuestChanged = true; + } + + void SetMonthlyQuestStatus(uint quest_id) + { + m_monthlyquests.Add(quest_id); + m_MonthlyQuestChanged = true; + } + } +} diff --git a/Game/Entities/Player/Player.Spells.cs b/Game/Entities/Player/Player.Spells.cs new file mode 100644 index 000000000..a0efaafc6 --- /dev/null +++ b/Game/Entities/Player/Player.Spells.cs @@ -0,0 +1,3218 @@ +/* + * Copyright (C) 2012-2017 CypherCore + * + * 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, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +using Framework.Constants; +using Framework.Dynamic; +using Game.DataStorage; +using Game.Network.Packets; +using Game.Spells; +using System; +using System.Collections.Generic; +using System.Diagnostics.Contracts; + +namespace Game.Entities +{ + public partial class Player + { + void UpdateSkillsForLevel() + { + ushort maxSkill = GetMaxSkillValueForLevel(); + + foreach (var pair in mSkillStatus) + { + if (pair.Value.State == SkillState.Deleted) + continue; + + uint pskill = pair.Key; + SkillRaceClassInfoRecord rcEntry = Global.DB2Mgr.GetSkillRaceClassInfo(pskill, GetRace(), GetClass()); + if (rcEntry == null) + continue; + + ushort field = (ushort)(pair.Value.Pos / 2); + byte offset = (byte)(pair.Value.Pos & 1); + + if (Global.SpellMgr.GetSkillRangeType(rcEntry) == SkillRangeType.Level) + { + ushort max = GetUInt16Value(PlayerFields.SkillLineMaxRank + field, offset); + + // update only level dependent max skill values + if (max != 1) + { + SetUInt16Value(PlayerFields.SkillLineRank + field, offset, maxSkill); + SetUInt16Value(PlayerFields.SkillLineMaxRank + field, offset, maxSkill); + if (pair.Value.State != SkillState.New) + pair.Value.State = SkillState.Changed; + } + } + + // Update level dependent skillline spells + LearnSkillRewardedSpells(rcEntry.SkillID, GetUInt16Value(PlayerFields.SkillLineRank + field, offset)); + } + } + + public void UpdateSkillsToMaxSkillsForLevel() + { + foreach (var skill in mSkillStatus) + { + if (skill.Value.State == SkillState.Deleted) + continue; + + uint pskill = skill.Key; + SkillRaceClassInfoRecord rcEntry = Global.DB2Mgr.GetSkillRaceClassInfo(pskill, GetRace(), GetClass()); + if (rcEntry == null) + continue; + + if (Global.SpellMgr.IsProfessionOrRidingSkill(rcEntry.SkillID)) + continue; + + if (Global.SpellMgr.IsWeaponSkill(rcEntry.SkillID)) + continue; + + ushort field = (ushort)(skill.Value.Pos / 2); + byte offset = (byte)(skill.Value.Pos & 1); + + ushort max = GetUInt16Value(PlayerFields.SkillLineMaxRank + field, offset); + if (max > 1) + { + SetUInt16Value(PlayerFields.SkillLineRank + field, offset, max); + + if (skill.Value.State != SkillState.New) + skill.Value.State = SkillState.Changed; + } + } + } + + public ushort GetSkillValue(SkillType skill) + { + if (skill == 0) + return 0; + + var skillStatusData = mSkillStatus.LookupByKey(skill); + if (skillStatusData == null || skillStatusData.State == SkillState.Deleted) + return 0; + + var field = (ushort)(skillStatusData.Pos / 2); + var offset = (byte)(skillStatusData.Pos & 1); + + int result = GetUInt16Value(PlayerFields.SkillLineRank + field, offset); + result += GetUInt16Value(PlayerFields.SkillLineTempBonus + field, offset); + result += GetUInt16Value(PlayerFields.SkillLinePermBonus + field, offset); + return (ushort)(result < 0 ? 0 : result); + } + + ushort GetMaxSkillValue(SkillType skill) + { + if (skill == 0) + return 0; + + var skillStatusData = mSkillStatus.LookupByKey(skill); + if (skillStatusData == null || skillStatusData.State == SkillState.Deleted) + return 0; + + var field = (ushort)(skillStatusData.Pos / 2); + var offset = (byte)(skillStatusData.Pos & 1); + + int result = GetUInt16Value(PlayerFields.SkillLineMaxRank + field, offset); + result += GetUInt16Value(PlayerFields.SkillLineTempBonus + field, offset); + result += GetUInt16Value(PlayerFields.SkillLinePermBonus + field, offset); + return (ushort)(result < 0 ? 0 : result); + } + + public ushort GetPureSkillValue(SkillType skill) + { + if (skill == 0) + return 0; + + var skillStatusData = mSkillStatus.LookupByKey((uint)skill); + if (skillStatusData == null || skillStatusData.State == SkillState.Deleted) + return 0; + + var field = (ushort)(skillStatusData.Pos / 2); + var offset = (byte)(skillStatusData.Pos & 1); + + return GetUInt16Value(PlayerFields.SkillLineRank + field, offset); + } + + public ushort GetSkillStep(SkillType skill) + { + if (skill == 0) + return 0; + + var skillStatusData = mSkillStatus.LookupByKey(skill); + if (skillStatusData == null || skillStatusData.State == SkillState.Deleted) + return 0; + + var field = (ushort)(skillStatusData.Pos / 2); + var offset = (byte)(skillStatusData.Pos & 1); + + return GetUInt16Value(PlayerFields.SkillLineStep + field, offset); + } + + public ushort GetPureMaxSkillValue(SkillType skill) + { + if (skill == 0) + return 0; + + var skillStatusData = mSkillStatus.LookupByKey(skill); + if (skillStatusData == null || skillStatusData.State == SkillState.Deleted) + return 0; + + var field = (ushort)(skillStatusData.Pos / 2); + var offset = (byte)(skillStatusData.Pos & 1); + + return GetUInt16Value(PlayerFields.SkillLineMaxRank + field, offset); + } + + public ushort GetBaseSkillValue(SkillType skill) + { + if (skill == 0) + return 0; + + var skillStatusData = mSkillStatus.LookupByKey(skill); + if (skillStatusData == null || skillStatusData.State == SkillState.Deleted) + return 0; + + var field = (ushort)(skillStatusData.Pos / 2); + var offset = (byte)(skillStatusData.Pos & 1); + + var result = (int)GetUInt16Value(PlayerFields.SkillLineRank + field, offset); + result += GetUInt16Value(PlayerFields.SkillLinePermBonus + field, offset); + return (ushort)(result < 0 ? 0 : result); + } + + public ushort GetSkillPermBonusValue(uint skill) + { + if (skill == 0) + return 0; + + var skillStatusData = mSkillStatus.LookupByKey(skill); + if (skillStatusData == null || skillStatusData.State == SkillState.Deleted) + return 0; + + var field = (ushort)(skillStatusData.Pos / 2); + var offset = (byte)(skillStatusData.Pos & 1); + + return GetUInt16Value(PlayerFields.SkillLinePermBonus + field, offset); + } + + public ushort GetSkillTempBonusValue(uint skill) + { + if (skill == 0) + return 0; + + var skillStatusData = mSkillStatus.LookupByKey(skill); + if (skillStatusData == null || skillStatusData.State == SkillState.Deleted) + return 0; + + var field = (ushort)(skillStatusData.Pos / 2); + var offset = (byte)(skillStatusData.Pos & 1); + + return GetUInt16Value(PlayerFields.SkillLineTempBonus + field, offset); + } + + public uint GetResurrectionSpellId() + { + // search priceless resurrection possibilities + uint prio = 0; + uint spell_id = 0; + var dummyAuras = GetAuraEffectsByType(AuraType.Dummy); + foreach (var eff in dummyAuras) + { + // Soulstone Resurrection // prio: 3 (max, non death persistent) + if (prio < 2 && eff.GetSpellInfo().SpellFamilyName == SpellFamilyNames.Warlock && eff.GetSpellInfo().SpellFamilyFlags[1].HasAnyFlag(0x1000000u)) + { + spell_id = 3026; + prio = 3; + } + // Twisting Nether // prio: 2 (max) + else if (eff.GetId() == 23701 && RandomHelper.randChance(10)) + { + prio = 2; + spell_id = 23700; + } + } + + // Reincarnation (passive spell) // prio: 1 + if (prio < 1 && HasSpell(20608) && !GetSpellHistory().HasCooldown(21169)) + spell_id = 21169; + + return spell_id; + } + + public void PetSpellInitialize() + { + Pet pet = GetPet(); + + if (!pet) + return; + + Log.outDebug(LogFilter.Pet, "Pet Spells Groups"); + + CharmInfo charmInfo = pet.GetCharmInfo(); + + PetSpells petSpellsPacket = new PetSpells(); + petSpellsPacket.PetGUID = pet.GetGUID(); + petSpellsPacket.CreatureFamily = (ushort)pet.GetCreatureTemplate().Family; // creature family (required for pet talents) + petSpellsPacket.Specialization = pet.GetSpecialization(); + petSpellsPacket.TimeLimit = (uint)pet.GetDuration(); + petSpellsPacket.ReactState = pet.GetReactState(); + petSpellsPacket.CommandState = charmInfo.GetCommandState(); + + // action bar loop + for (byte i = 0; i < SharedConst.ActionBarIndexMax; ++i) + petSpellsPacket.ActionButtons[i] = charmInfo.GetActionBarEntry(i).packedData; + + if (pet.IsPermanentPetFor(this)) + { + // spells loop + foreach (var pair in pet.m_spells) + { + if (pair.Value.state == PetSpellState.Removed) + continue; + + petSpellsPacket.Actions.Add(UnitActionBarEntry.MAKE_UNIT_ACTION_BUTTON(pair.Key, (uint)pair.Value.active)); + } + } + + // Cooldowns + pet.GetSpellHistory().WritePacket(petSpellsPacket); + + SendPacket(petSpellsPacket); + } + + public bool CanSeeSpellClickOn(Creature creature) + { + if (!creature.HasFlag64(UnitFields.NpcFlags, NPCFlags.SpellClick)) + return false; + + var clickPair = Global.ObjectMgr.GetSpellClickInfoMapBounds(creature.GetEntry()); + if (clickPair.Empty()) + return false; + + foreach (var spellClickInfo in clickPair) + { + if (!spellClickInfo.IsFitToRequirements(this, creature)) + return false; + + if (Global.ConditionMgr.IsObjectMeetingSpellClickConditions(creature.GetEntry(), spellClickInfo.spellId, this, creature)) + return true; + } + + return false; + } + + public override SpellInfo GetCastSpellInfo(SpellInfo spellInfo) + { + var overrides = m_overrideSpells.LookupByKey(spellInfo.Id); + if (!overrides.Empty()) + { + foreach (uint spellId in overrides) + { + SpellInfo newInfo = Global.SpellMgr.GetSpellInfo(spellId); + if (newInfo != null) + return base.GetCastSpellInfo(newInfo); + } + } + + return base.GetCastSpellInfo(spellInfo); + } + + void AddOverrideSpell(uint overridenSpellId, uint newSpellId) + { + m_overrideSpells.Add(overridenSpellId, newSpellId); + } + + void RemoveOverrideSpell(uint overridenSpellId, uint newSpellId) + { + m_overrideSpells.Remove(overridenSpellId, newSpellId); + } + + void LearnSpecializationSpells() + { + var specSpells = Global.DB2Mgr.GetSpecializationSpells(GetUInt32Value(PlayerFields.CurrentSpecId)); + if (specSpells != null) + { + for (int j = 0; j < specSpells.Count; ++j) + { + SpecializationSpellsRecord specSpell = specSpells[j]; + SpellInfo spellInfo = Global.SpellMgr.GetSpellInfo(specSpell.SpellID); + if (spellInfo == null || spellInfo.SpellLevel > getLevel()) + continue; + + LearnSpell(specSpell.SpellID, false); + if (specSpell.OverridesSpellID != 0) + AddOverrideSpell(specSpell.OverridesSpellID, specSpell.SpellID); + } + } + } + + void RemoveSpecializationSpells() + { + for (uint i = 0; i < PlayerConst.MaxSpecializations; ++i) + { + ChrSpecializationRecord specialization = Global.DB2Mgr.GetChrSpecializationByIndex(GetClass(), i); + if (specialization != null) + { + var specSpells = Global.DB2Mgr.GetSpecializationSpells(specialization.Id); + if (specSpells != null) + { + for (int j = 0; j < specSpells.Count; ++j) + { + SpecializationSpellsRecord specSpell = specSpells[j]; + RemoveSpell(specSpell.SpellID, true); + if (specSpell.OverridesSpellID != 0) + RemoveOverrideSpell(specSpell.OverridesSpellID, specSpell.SpellID); + } + } + + for (uint j = 0; j < PlayerConst.MaxMasterySpells; ++j) + { + uint mastery = specialization.MasterySpellID[j]; + if (mastery != 0) + RemoveAurasDueToSpell(mastery); + } + } + } + } + + public void SendSpellCategoryCooldowns() + { + SpellCategoryCooldown cooldowns = new SpellCategoryCooldown(); + + var categoryCooldownAuras = GetAuraEffectsByType(AuraType.ModSpellCategoryCooldown); + foreach (AuraEffect aurEff in categoryCooldownAuras) + { + uint categoryId = (uint)aurEff.GetMiscValue(); + var cooldownInfo = cooldowns.CategoryCooldowns.Find(p => p.Category == categoryId); + + if (cooldownInfo == null) + cooldowns.CategoryCooldowns.Add(new SpellCategoryCooldown.CategoryCooldownInfo(categoryId, -aurEff.GetAmount())); + else + cooldownInfo.ModCooldown -= aurEff.GetAmount(); + } + + SendPacket(cooldowns); + } + + bool UpdateSkillPro(SkillType skillId, int chance, uint step) + { + return UpdateSkillPro((uint)skillId, chance, step); + } + bool UpdateSkillPro(uint skillId, int chance, uint step) + { + // levels sync. with spell requirement for skill levels to learn + // bonus abilities in sSkillLineAbilityStore + // Used only to avoid scan DBC at each skill grow + uint[] bonusSkillLevels = { 75, 150, 225, 300, 375, 450, 525 }; + int bonusSkillLevelsSize = bonusSkillLevels.Length / sizeof(uint); + + Log.outDebug(LogFilter.Player, "UpdateSkillPro(SkillId {0}, Chance {0:D3}%)", skillId, chance / 10.0f); + if (skillId == 0) + return false; + + if (chance <= 0) // speedup in 0 chance case + { + Log.outDebug(LogFilter.Player, "Player:UpdateSkillPro Chance={0:D3}% missed", chance / 10.0f); + return false; + } + + var skillStatusData = mSkillStatus.LookupByKey(skillId); + if (skillStatusData == null || skillStatusData.State == SkillState.Deleted) + return false; + + ushort field = (ushort)(skillStatusData.Pos / 2); + byte offset = (byte)(skillStatusData.Pos & 1); + + ushort value = GetUInt16Value(PlayerFields.SkillLineRank + field, offset); + ushort max = GetUInt16Value(PlayerFields.SkillLineMaxRank + field, offset); + + if (max == 0 || value == 0 || value >= max) + return false; + + if (RandomHelper.IRand(1, 1000) > chance) + { + Log.outDebug(LogFilter.Player, "Player:UpdateSkillPro Chance={0:F3}% missed", chance / 10.0f); + return false; + } + + ushort new_value = (ushort)(value + step); + if (new_value > max) + new_value = max; + + SetUInt16Value(PlayerFields.SkillLineRank + field, offset, new_value); + if (skillStatusData.State != SkillState.New) + skillStatusData.State = SkillState.Changed; + + for (int i = 0; i < bonusSkillLevelsSize; ++i) + { + uint bsl = bonusSkillLevels[i]; + if (value < bsl && new_value >= bsl) + { + LearnSkillRewardedSpells(skillId, new_value); + break; + } + } + + UpdateSkillEnchantments(skillId, value, new_value); + UpdateCriteria(CriteriaTypes.ReachSkillLevel, skillId); + Log.outDebug(LogFilter.Player, "Player:UpdateSkillPro Chance={0:F3}% taken", chance / 10.0f); + return true; + } + void UpdateSkillEnchantments(uint skill_id, ushort curr_value, ushort new_value) + { + for (byte i = 0; i < InventorySlots.BagEnd; ++i) + { + if (m_items[i] != null) + { + for (EnchantmentSlot slot = 0; slot < EnchantmentSlot.Max; ++slot) + { + uint ench_id = m_items[i].GetEnchantmentId(slot); + if (ench_id == 0) + continue; + + SpellItemEnchantmentRecord Enchant = CliDB.SpellItemEnchantmentStorage.LookupByKey(ench_id); + if (Enchant == null) + return; + + if (Enchant.RequiredSkillID == skill_id) + { + // Checks if the enchantment needs to be applied or removed + if (curr_value < Enchant.RequiredSkillRank && new_value >= Enchant.RequiredSkillRank) + ApplyEnchantment(m_items[i], slot, true); + else if (new_value < Enchant.RequiredSkillRank && curr_value >= Enchant.RequiredSkillRank) + ApplyEnchantment(m_items[i], slot, false); + } + + // If we're dealing with a gem inside a prismatic socket we need to check the prismatic socket requirements + // rather than the gem requirements itself. If the socket has no color it is a prismatic socket. + if ((slot == EnchantmentSlot.Sock1 || slot == EnchantmentSlot.Sock2 || slot == EnchantmentSlot.Sock3) + && m_items[i].GetSocketColor((uint)(slot - EnchantmentSlot.Sock1)) == 0) + { + SpellItemEnchantmentRecord pPrismaticEnchant = CliDB.SpellItemEnchantmentStorage.LookupByKey(m_items[i].GetEnchantmentId(EnchantmentSlot.Prismatic)); + + if (pPrismaticEnchant != null && pPrismaticEnchant.RequiredSkillID == skill_id) + { + if (curr_value < pPrismaticEnchant.RequiredSkillRank && new_value >= pPrismaticEnchant.RequiredSkillRank) + ApplyEnchantment(m_items[i], slot, true); + else if (new_value < pPrismaticEnchant.RequiredSkillRank && curr_value >= pPrismaticEnchant.RequiredSkillRank) + ApplyEnchantment(m_items[i], slot, false); + } + } + } + } + } + } + + void UpdateEnchantTime(uint time) + { + foreach (var enchat in m_enchantDuration) + { + if (enchat.item.GetEnchantmentId(enchat.slot) == 0) + { + m_enchantDuration.Remove(enchat); + } + else if (enchat.leftduration <= time) + { + ApplyEnchantment(enchat.item, enchat.slot, false, false); + enchat.item.ClearEnchantment(enchat.slot); + m_enchantDuration.Remove(enchat); + } + else if (enchat.leftduration > time) + { + enchat.leftduration -= time; + } + } + } + + void ApplyEnchantment(Item item, bool apply) + { + for (EnchantmentSlot slot = 0; slot < EnchantmentSlot.Max; ++slot) + ApplyEnchantment(item, slot, apply); + } + public void ApplyEnchantment(Item item, EnchantmentSlot slot, bool apply, bool apply_dur = true, bool ignore_condition = false) + { + if (item == null || !item.IsEquipped()) + return; + + if (slot >= EnchantmentSlot.Max) + return; + + uint enchant_id = item.GetEnchantmentId(slot); + if (enchant_id == 0) + return; + + SpellItemEnchantmentRecord pEnchant = CliDB.SpellItemEnchantmentStorage.LookupByKey(enchant_id); + if (pEnchant == null) + return; + + if (!ignore_condition && pEnchant.ConditionID != 0 && !EnchantmentFitsRequirements(pEnchant.ConditionID, -1)) + return; + + if (pEnchant.MinLevel > getLevel()) + return; + + if (pEnchant.RequiredSkillID > 0 && pEnchant.RequiredSkillRank > GetSkillValue((SkillType)pEnchant.RequiredSkillID)) + return; + + // If we're dealing with a gem inside a prismatic socket we need to check the prismatic socket requirements + // rather than the gem requirements itself. If the socket has no color it is a prismatic socket. + if ((slot == EnchantmentSlot.Sock1 || slot == EnchantmentSlot.Sock2 || slot == EnchantmentSlot.Sock3)) + { + if (item.GetSocketColor((uint)(slot - EnchantmentSlot.Sock1)) == 0) + { + // Check if the requirements for the prismatic socket are met before applying the gem stats + SpellItemEnchantmentRecord pPrismaticEnchant = CliDB.SpellItemEnchantmentStorage.LookupByKey(item.GetEnchantmentId(EnchantmentSlot.Prismatic)); + if (pPrismaticEnchant == null || (pPrismaticEnchant.RequiredSkillID > 0 && pPrismaticEnchant.RequiredSkillRank > GetSkillValue((SkillType)pPrismaticEnchant.RequiredSkillID))) + return; + } + + // Cogwheel gems dont have requirement data set in SpellItemEnchantment.dbc, but they do have it in Item-sparse.db2 + ItemDynamicFieldGems gem = item.GetGem((ushort)(slot - EnchantmentSlot.Sock1)); + if (gem != null) + { + ItemTemplate gemTemplate = Global.ObjectMgr.GetItemTemplate(gem.ItemId); + if (gemTemplate != null) + if (gemTemplate.GetRequiredSkill() != 0 && GetSkillValue((SkillType)gemTemplate.GetRequiredSkill()) < gemTemplate.GetRequiredSkillRank()) + return; + } + } + + if (!item.IsBroken()) + { + for (int s = 0; s < ItemConst.MaxItemEnchantmentEffects; ++s) + { + ItemEnchantmentType enchant_display_type = (ItemEnchantmentType)pEnchant.Effect[s]; + uint enchant_amount = pEnchant.EffectPointsMin[s]; + uint enchant_spell_id = pEnchant.EffectSpellID[s]; + + switch (enchant_display_type) + { + case ItemEnchantmentType.None: + break; + case ItemEnchantmentType.CombatSpell: + // processed in Player.CastItemCombatSpell + break; + case ItemEnchantmentType.Damage: + if (item.GetSlot() == EquipmentSlot.MainHand) + { + if (item.GetTemplate().GetInventoryType() != InventoryType.Ranged && item.GetTemplate().GetInventoryType() != InventoryType.RangedRight) + HandleStatModifier(UnitMods.DamageMainHand, UnitModifierType.TotalValue, enchant_amount, apply); + else + HandleStatModifier(UnitMods.DamageRanged, UnitModifierType.TotalValue, enchant_amount, apply); + } + else if (item.GetSlot() == EquipmentSlot.OffHand) + HandleStatModifier(UnitMods.DamageOffHand, UnitModifierType.TotalValue, enchant_amount, apply); + break; + case ItemEnchantmentType.EquipSpell: + if (enchant_spell_id != 0) + { + if (apply) + { + int basepoints = 0; + // Random Property Exist - try found basepoints for spell (basepoints depends from item suffix factor) + if (item.GetItemRandomPropertyId() < 0) + { + ItemRandomSuffixRecord item_rand = CliDB.ItemRandomSuffixStorage.LookupByKey(Math.Abs(item.GetItemRandomPropertyId())); + if (item_rand != null) + { + // Search enchant_amount + for (int k = 0; k < ItemConst.MaxItemRandomProperties; ++k) + { + if (item_rand.Enchantment[k] == enchant_id) + { + basepoints = (int)((item_rand.AllocationPct[k] * item.GetItemSuffixFactor()) / 10000); + break; + } + } + } + } + // Cast custom spell vs all equal basepoints got from enchant_amount + if (basepoints != 0) + CastCustomSpell(this, enchant_spell_id, basepoints, basepoints, basepoints, true, item); + else + CastSpell(this, enchant_spell_id, true, item); + } + else + RemoveAurasDueToItemSpell(enchant_spell_id, item.GetGUID()); + } + break; + case ItemEnchantmentType.Resistance: + if (pEnchant.ScalingClass != 0) + { + int scalingClass = pEnchant.ScalingClass; + if ((GetUInt32Value(UnitFields.MinItemLevel) != 0 || GetUInt32Value(UnitFields.Maxitemlevel) != 0) && pEnchant.ScalingClassRestricted != 0) + scalingClass = pEnchant.ScalingClassRestricted; + + uint minLevel = ((uint)(pEnchant.Flags)).HasAnyFlag(0x20u) ? 1 : 60u; + uint scalingLevel = getLevel(); + byte maxLevel = (byte)(pEnchant.MaxLevel != 0 ? pEnchant.MaxLevel : CliDB.SpellScalingGameTable.GetTableRowCount() - 1); + + if (minLevel > getLevel()) + scalingLevel = minLevel; + else if (maxLevel < getLevel()) + scalingLevel = maxLevel; + + GtSpellScalingRecord spellScaling = CliDB.SpellScalingGameTable.GetRow(scalingLevel); + if (spellScaling != null) + enchant_amount = (uint)(pEnchant.EffectScalingPoints[s] * CliDB.GetSpellScalingColumnForClass(spellScaling, scalingClass)); + } + + if (enchant_amount == 0) + { + ItemRandomSuffixRecord item_rand = CliDB.ItemRandomSuffixStorage.LookupByKey(Math.Abs(item.GetItemRandomPropertyId())); + if (item_rand != null) + { + for (int k = 0; k < ItemConst.MaxItemRandomProperties; ++k) + { + if (item_rand.Enchantment[k] == enchant_id) + { + enchant_amount = (item_rand.AllocationPct[k] * item.GetItemSuffixFactor()) / 10000; + break; + } + } + } + } + + HandleStatModifier((UnitMods)((uint)UnitMods.ResistanceStart + enchant_spell_id), UnitModifierType.TotalValue, enchant_amount, apply); + break; + case ItemEnchantmentType.Stat: + { + if (pEnchant.ScalingClass != 0) + { + int scalingClass = pEnchant.ScalingClass; + if ((GetUInt32Value(UnitFields.MinItemLevel) != 0 || GetUInt32Value(UnitFields.Maxitemlevel) != 0) && pEnchant.ScalingClassRestricted != 0) + scalingClass = pEnchant.ScalingClassRestricted; + + uint minLevel = ((uint)(pEnchant.Flags)).HasAnyFlag(0x20u) ? 1 : 60u; + uint scalingLevel = getLevel(); + byte maxLevel = (byte)(pEnchant.MaxLevel != 0 ? pEnchant.MaxLevel : CliDB.SpellScalingGameTable.GetTableRowCount() - 1); + + if (minLevel > getLevel()) + scalingLevel = minLevel; + else if (maxLevel < getLevel()) + scalingLevel = maxLevel; + + GtSpellScalingRecord spellScaling = CliDB.SpellScalingGameTable.GetRow(scalingLevel); + if (spellScaling != null) + enchant_amount = (uint)(pEnchant.EffectScalingPoints[s] * CliDB.GetSpellScalingColumnForClass(spellScaling, scalingClass)); + } + + if (enchant_amount == 0) + { + ItemRandomSuffixRecord item_rand_suffix = CliDB.ItemRandomSuffixStorage.LookupByKey(Math.Abs(item.GetItemRandomPropertyId())); + if (item_rand_suffix != null) + { + for (int k = 0; k < ItemConst.MaxItemRandomProperties; ++k) + { + if (item_rand_suffix.Enchantment[k] == enchant_id) + { + enchant_amount = (item_rand_suffix.AllocationPct[k] * item.GetItemSuffixFactor()) / 10000; + break; + } + } + } + } + + Log.outDebug(LogFilter.Player, "Adding {0} to stat nb {1}", enchant_amount, enchant_spell_id); + switch ((ItemModType)enchant_spell_id) + { + case ItemModType.Mana: + Log.outDebug(LogFilter.Player, "+ {0} MANA", enchant_amount); + HandleStatModifier(UnitMods.Mana, UnitModifierType.BaseValue, enchant_amount, apply); + break; + case ItemModType.Health: + Log.outDebug(LogFilter.Player, "+ {0} HEALTH", enchant_amount); + HandleStatModifier(UnitMods.Health, UnitModifierType.BaseValue, enchant_amount, apply); + break; + case ItemModType.Agility: + Log.outDebug(LogFilter.Player, "+ {0} AGILITY", enchant_amount); + HandleStatModifier(UnitMods.StatAgility, UnitModifierType.TotalValue, enchant_amount, apply); + ApplyStatBuffMod(Stats.Agility, enchant_amount, apply); + break; + case ItemModType.Strength: + Log.outDebug(LogFilter.Player, "+ {0} STRENGTH", enchant_amount); + HandleStatModifier(UnitMods.StatStrength, UnitModifierType.TotalValue, enchant_amount, apply); + ApplyStatBuffMod(Stats.Strength, enchant_amount, apply); + break; + case ItemModType.Intellect: + Log.outDebug(LogFilter.Player, "+ {0} INTELLECT", enchant_amount); + HandleStatModifier(UnitMods.StatIntellect, UnitModifierType.TotalValue, enchant_amount, apply); + ApplyStatBuffMod(Stats.Intellect, enchant_amount, apply); + break; + //case ItemModType.Spirit: + //Log.outDebug(LogFilter.Player, "+ {0} SPIRIT", enchant_amount); + //HandleStatModifier(UnitMods.StatSpirit, UnitModifierType.TotalValue, enchant_amount, apply); + //ApplyStatBuffMod(Stats.Spirit, enchant_amount, apply); + //break; + case ItemModType.Stamina: + Log.outDebug(LogFilter.Player, "+ {0} STAMINA", enchant_amount); + HandleStatModifier(UnitMods.StatStamina, UnitModifierType.TotalValue, enchant_amount, apply); + ApplyStatBuffMod(Stats.Stamina, enchant_amount, apply); + break; + case ItemModType.DefenseSkillRating: + ApplyRatingMod(CombatRating.DefenseSkill, (int)enchant_amount, apply); + Log.outDebug(LogFilter.Player, "+ {0} DEFENSE", enchant_amount); + break; + case ItemModType.DodgeRating: + ApplyRatingMod(CombatRating.Dodge, (int)enchant_amount, apply); + Log.outDebug(LogFilter.Player, "+ {0} DODGE", enchant_amount); + break; + case ItemModType.ParryRating: + ApplyRatingMod(CombatRating.Parry, (int)enchant_amount, apply); + Log.outDebug(LogFilter.Player, "+ {0} PARRY", enchant_amount); + break; + case ItemModType.BlockRating: + ApplyRatingMod(CombatRating.Block, (int)enchant_amount, apply); + Log.outDebug(LogFilter.Player, "+ {0} SHIELD_BLOCK", enchant_amount); + break; + case ItemModType.HitMeleeRating: + ApplyRatingMod(CombatRating.HitMelee, (int)enchant_amount, apply); + Log.outDebug(LogFilter.Player, "+ {0} MELEE_HIT", enchant_amount); + break; + case ItemModType.HitRangedRating: + ApplyRatingMod(CombatRating.HitRanged, (int)enchant_amount, apply); + Log.outDebug(LogFilter.Player, "+ {0} RANGED_HIT", enchant_amount); + break; + case ItemModType.HitSpellRating: + ApplyRatingMod(CombatRating.HitSpell, (int)enchant_amount, apply); + Log.outDebug(LogFilter.Player, "+ {0} SPELL_HIT", enchant_amount); + break; + case ItemModType.CritMeleeRating: + ApplyRatingMod(CombatRating.CritMelee, (int)enchant_amount, apply); + Log.outDebug(LogFilter.Player, "+ {0} MELEE_CRIT", enchant_amount); + break; + case ItemModType.CritRangedRating: + ApplyRatingMod(CombatRating.CritRanged, (int)enchant_amount, apply); + Log.outDebug(LogFilter.Player, "+ {0} RANGED_CRIT", enchant_amount); + break; + case ItemModType.CritSpellRating: + ApplyRatingMod(CombatRating.CritSpell, (int)enchant_amount, apply); + Log.outDebug(LogFilter.Player, "+ {0} SPELL_CRIT", enchant_amount); + break; + case ItemModType.HasteSpellRating: + ApplyRatingMod(CombatRating.HasteSpell, (int)enchant_amount, apply); + break; + case ItemModType.HitRating: + ApplyRatingMod(CombatRating.HitMelee, (int)enchant_amount, apply); + ApplyRatingMod(CombatRating.HitRanged, (int)enchant_amount, apply); + ApplyRatingMod(CombatRating.HitSpell, (int)enchant_amount, apply); + Log.outDebug(LogFilter.Player, "+ {0} HIT", enchant_amount); + break; + case ItemModType.CritRating: + ApplyRatingMod(CombatRating.CritMelee, (int)enchant_amount, apply); + ApplyRatingMod(CombatRating.CritRanged, (int)enchant_amount, apply); + ApplyRatingMod(CombatRating.CritSpell, (int)enchant_amount, apply); + Log.outDebug(LogFilter.Player, "+ {0} CRITICAL", enchant_amount); + break; + case ItemModType.ResilienceRating: + ApplyRatingMod(CombatRating.ResiliencePlayerDamage, (int)enchant_amount, apply); + Log.outDebug(LogFilter.Player, "+ {0} RESILIENCE", enchant_amount); + break; + case ItemModType.HasteRating: + ApplyRatingMod(CombatRating.HasteMelee, (int)enchant_amount, apply); + ApplyRatingMod(CombatRating.HasteRanged, (int)enchant_amount, apply); + ApplyRatingMod(CombatRating.HasteSpell, (int)enchant_amount, apply); + Log.outDebug(LogFilter.Player, "+ {0} HASTE", enchant_amount); + break; + case ItemModType.ExpertiseRating: + ApplyRatingMod(CombatRating.Expertise, (int)enchant_amount, apply); + Log.outDebug(LogFilter.Player, "+ {0} EXPERTISE", enchant_amount); + break; + case ItemModType.AttackPower: + HandleStatModifier(UnitMods.AttackPower, UnitModifierType.TotalValue, enchant_amount, apply); + HandleStatModifier(UnitMods.AttackPowerRanged, UnitModifierType.TotalValue, enchant_amount, apply); + Log.outDebug(LogFilter.Player, "+ {0} ATTACK_POWER", enchant_amount); + break; + case ItemModType.RangedAttackPower: + HandleStatModifier(UnitMods.AttackPowerRanged, UnitModifierType.TotalValue, enchant_amount, apply); + Log.outDebug(LogFilter.Player, "+ {0} RANGED_ATTACK_POWER", enchant_amount); + break; + case ItemModType.ManaRegeneration: + ApplyManaRegenBonus((int)enchant_amount, apply); + Log.outDebug(LogFilter.Player, "+ {0} MANA_REGENERATION", enchant_amount); + break; + case ItemModType.ArmorPenetrationRating: + ApplyRatingMod(CombatRating.ArmorPenetration, (int)enchant_amount, apply); + Log.outDebug(LogFilter.Player, "+ {0} ARMOR PENETRATION", enchant_amount); + break; + case ItemModType.SpellPower: + ApplySpellPowerBonus((int)enchant_amount, apply); + Log.outDebug(LogFilter.Player, "+ {0} SPELL_POWER", enchant_amount); + break; + case ItemModType.HealthRegen: + ApplyHealthRegenBonus((int)enchant_amount, apply); + Log.outDebug(LogFilter.Player, "+ {0} HEALTH_REGENERATION", enchant_amount); + break; + case ItemModType.SpellPenetration: + ApplySpellPenetrationBonus((int)enchant_amount, apply); + Log.outDebug(LogFilter.Player, "+ {0} SPELL_PENETRATION", enchant_amount); + break; + case ItemModType.BlockValue: + HandleBaseModValue(BaseModGroup.ShieldBlockValue, BaseModType.FlatMod, enchant_amount, apply); + Log.outDebug(LogFilter.Player, "+ {0} BLOCK_VALUE", enchant_amount); + break; + case ItemModType.MasteryRating: + ApplyRatingMod(CombatRating.Mastery, (int)enchant_amount, apply); + Log.outDebug(LogFilter.Player, "+ {0} MASTERY", enchant_amount); + break; + default: + break; + } + break; + } + case ItemEnchantmentType.Totem: // Shaman Rockbiter Weapon + { + if (GetClass() == Class.Shaman) + { + float addValue = 0.0f; + if (item.GetSlot() == EquipmentSlot.MainHand) + { + addValue = enchant_amount * item.GetTemplate().GetDelay() / 1000.0f; + HandleStatModifier(UnitMods.DamageMainHand, UnitModifierType.TotalValue, addValue, apply); + } + else if (item.GetSlot() == EquipmentSlot.OffHand) + { + addValue = enchant_amount * item.GetTemplate().GetDelay() / 1000.0f; + HandleStatModifier(UnitMods.DamageOffHand, UnitModifierType.TotalValue, addValue, apply); + } + } + break; + } + case ItemEnchantmentType.UseSpell: + // processed in Player.CastItemUseSpell + break; + case ItemEnchantmentType.PrismaticSocket: + // nothing do.. + break; + default: + Log.outError(LogFilter.Player, "Unknown item enchantment (id = {0}) display type: {1}", enchant_id, enchant_display_type); + break; + } + } + } + + // visualize enchantment at player and equipped items + if (slot == EnchantmentSlot.Perm) + SetUInt16Value(PlayerFields.VisibleItem + 1 + (item.GetSlot() * 2), 1, item.GetVisibleItemVisual(this)); + + if (apply_dur) + { + if (apply) + { + // set duration + uint duration = item.GetEnchantmentDuration(slot); + if (duration > 0) + AddEnchantmentDuration(item, slot, duration); + } + else + { + // duration == 0 will remove EnchantDuration + AddEnchantmentDuration(item, slot, 0); + } + } + } + + public void ModifySkillBonus(SkillType skillid, int val, bool talent) + { + var skill = mSkillStatus.LookupByKey(skillid); + if (skill == null || skill.State == SkillState.Deleted) + return; + + ushort field = (ushort)(skill.Pos / 2 + (talent ? PlayerFields.SkillLinePermBonus : PlayerFields.SkillLineTempBonus)); + byte offset = (byte)(skill.Pos & 1); + + ushort bonus = GetUInt16Value(field, offset); + + SetUInt16Value(field, offset, (ushort)(bonus + val)); + } + + public void StopCastingBindSight() + { + WorldObject target = GetViewpoint(); + if (target) + { + if (target.isTypeMask(TypeMask.Unit)) + { + ((Unit)target).RemoveAurasByType(AuraType.BindSight, GetGUID()); + ((Unit)target).RemoveAurasByType(AuraType.ModPossess, GetGUID()); + ((Unit)target).RemoveAurasByType(AuraType.ModPossessPet, GetGUID()); + } + } + } + + void AddEnchantmentDurations(Item item) + { + for (EnchantmentSlot x = 0; x < EnchantmentSlot.Max; ++x) + { + if (item.GetEnchantmentId(x) == 0) + continue; + + uint duration = item.GetEnchantmentDuration(x); + if (duration > 0) + AddEnchantmentDuration(item, x, duration); + } + } + void AddEnchantmentDuration(Item item, EnchantmentSlot slot, uint duration) + { + if (item == null) + return; + + if (slot >= EnchantmentSlot.Max) + return; + + foreach (var enchantDuration in m_enchantDuration) + { + if (enchantDuration.item == item && enchantDuration.slot == slot) + { + enchantDuration.item.SetEnchantmentDuration(enchantDuration.slot, enchantDuration.leftduration, this); + m_enchantDuration.Remove(enchantDuration); + break; + } + } + if (item != null && duration > 0) + { + GetSession().SendItemEnchantTimeUpdate(GetGUID(), item.GetGUID(), (uint)slot, duration / 1000); + m_enchantDuration.Add(new EnchantDuration(item, slot, duration)); + } + } + void RemoveEnchantmentDurations(Item item) + { + foreach (var enchantDuration in m_enchantDuration) + { + if (enchantDuration.item == item) + { + // save duration in item + item.SetEnchantmentDuration(enchantDuration.slot, enchantDuration.leftduration, this); + m_enchantDuration.Remove(enchantDuration); + } + } + } + public void RemoveArenaEnchantments(EnchantmentSlot slot) + { + // remove enchantments from equipped items first to clean up the m_enchantDuration list + foreach (var enchantDuration in m_enchantDuration) + { + if (enchantDuration.slot == slot) + { + if (enchantDuration.item && enchantDuration.item.GetEnchantmentId(slot) != 0) + { + // Poisons and DK runes are enchants which are allowed on arenas + if (Global.SpellMgr.IsArenaAllowedEnchancment(enchantDuration.item.GetEnchantmentId(slot))) + continue; + + // remove from stats + ApplyEnchantment(enchantDuration.item, slot, false, false); + // remove visual + enchantDuration.item.ClearEnchantment(slot); + } + // remove from update list + m_enchantDuration.Remove(enchantDuration); + } + } + + // remove enchants from inventory items + // NOTE: no need to remove these from stats, since these aren't equipped + // in inventory + for (byte i = InventorySlots.ItemStart; i < InventorySlots.ItemEnd; ++i) + { + Item pItem = GetItemByPos(InventorySlots.Bag0, i); + if (pItem) + if (pItem.GetEnchantmentId(slot) != 0) + pItem.ClearEnchantment(slot); + } + + // in inventory bags + for (byte i = InventorySlots.BagStart; i < InventorySlots.BagEnd; ++i) + { + Bag pBag = GetBagByPos(i); + if (pBag) + { + for (byte j = 0; j < pBag.GetBagSize(); j++) + { + Item pItem = pBag.GetItemByPos(j); + if (pItem) + if (pItem.GetEnchantmentId(slot) != 0) + pItem.ClearEnchantment(slot); + } + } + } + } + + public void UpdatePotionCooldown(Spell spell = null) + { + // no potion used i combat or still in combat + if (m_lastPotionId == 0 || IsInCombat()) + return; + + // Call not from spell cast, send cooldown event for item spells if no in combat + if (!spell) + { + // spell/item pair let set proper cooldown (except not existed charged spell cooldown spellmods for potions) + ItemTemplate proto = Global.ObjectMgr.GetItemTemplate(m_lastPotionId); + if (proto != null) + for (byte idx = 0; idx < proto.Effects.Count; ++idx) + { + if (proto.Effects[idx].SpellID != 0 && proto.Effects[idx].Trigger == ItemSpelltriggerType.OnUse) + { + SpellInfo spellInfo = Global.SpellMgr.GetSpellInfo(proto.Effects[idx].SpellID); + if (spellInfo != null) + GetSpellHistory().SendCooldownEvent(spellInfo, m_lastPotionId); + } + } + } + // from spell cases (m_lastPotionId set in Spell.SendSpellCooldown) + else + GetSpellHistory().SendCooldownEvent(spell.m_spellInfo, m_lastPotionId, spell); + + m_lastPotionId = 0; + } + + public bool CanUseMastery() + { + ChrSpecializationRecord chrSpec = CliDB.ChrSpecializationStorage.LookupByKey(GetUInt32Value(PlayerFields.CurrentSpecId)); + if (chrSpec != null) + return HasSpell(chrSpec.MasterySpellID[0]) || HasSpell(chrSpec.MasterySpellID[1]); + + return false; + } + + public bool HasSkill(SkillType skill) + { + if (skill == 0) + return false; + + var _skill = mSkillStatus.LookupByKey((uint)skill); + return _skill != null && _skill.State != SkillState.Deleted; + } + public void SetSkill(SkillType skill, uint step, uint newVal, uint maxVal) + { + SetSkill((uint)skill, step, newVal, maxVal); + } + public void SetSkill(uint id, uint step, uint newVal, uint maxVal) + { + if (id == 0) + return; + + ushort currVal; + var skillStatusData = mSkillStatus.LookupByKey(id); + + //has skill + if (skillStatusData != null && skillStatusData.State != SkillState.Deleted) + { + var field = (ushort)(skillStatusData.Pos / 2); + var offset = (byte)(skillStatusData.Pos & 1); + currVal = GetUInt16Value(PlayerFields.SkillLineRank + field, offset); + if (newVal != 0) + { + // if skill value is going down, update enchantments before setting the new value + if (newVal < currVal) + UpdateSkillEnchantments(id, currVal, (ushort)newVal); + + // update step + SetUInt16Value(PlayerFields.SkillLineStep + field, offset, (ushort)step); + // update value + SetUInt16Value(PlayerFields.SkillLineRank + field, offset, (ushort)newVal); + SetUInt16Value(PlayerFields.SkillLineMaxRank + field, offset, (ushort)maxVal); + + if (skillStatusData.State != SkillState.New) + skillStatusData.State = SkillState.Changed; + + LearnSkillRewardedSpells(id, newVal); + // if skill value is going up, update enchantments after setting the new value + if (newVal > currVal) + UpdateSkillEnchantments(id, currVal, (ushort)newVal); + + UpdateCriteria(CriteriaTypes.ReachSkillLevel, id); + UpdateCriteria(CriteriaTypes.LearnSkillLevel, id); + } + else //remove + { + //remove enchantments needing this skill + UpdateSkillEnchantments(id, currVal, 0); + // clear skill fields + SetUInt16Value(PlayerFields.SkillLineId + field, offset, 0); + SetUInt16Value(PlayerFields.SkillLineStep + field, offset, 0); + SetUInt16Value(PlayerFields.SkillLineRank + field, offset, 0); + SetUInt16Value(PlayerFields.SkillLineMaxRank + field, offset, 0); + SetUInt16Value(PlayerFields.SkillLineTempBonus + field, offset, 0); + SetUInt16Value(PlayerFields.SkillLinePermBonus + field, offset, 0); + + // mark as deleted or simply remove from map if not saved yet + if (skillStatusData.State != SkillState.New) + skillStatusData.State = SkillState.Deleted; + else + mSkillStatus.Remove(id); + + // remove all spells that related to this skill + foreach (var pAbility in CliDB.SkillLineAbilityStorage.Values) + if (pAbility.SkillLine == id) + RemoveSpell(Global.SpellMgr.GetFirstSpellInChain(pAbility.SpellID)); + + // Clear profession lines + if (GetUInt32Value(PlayerFields.ProfessionSkillLine1) == id) + SetUInt32Value(PlayerFields.ProfessionSkillLine1, 0); + else if (GetUInt32Value(PlayerFields.ProfessionSkillLine1 + 1) == id) + SetUInt32Value(PlayerFields.ProfessionSkillLine1 + 1, 0); + } + } + else if (newVal != 0) //add + { + currVal = 0; + for (int i = 0; i < SkillConst.MaxPlayerSkills; ++i) + { + var field = (ushort)(i / 2); + var offset = (byte)(i & 1); + if (GetUInt16Value(PlayerFields.SkillLineId + field, offset) == 0) + { + var skillEntry = CliDB.SkillLineStorage.LookupByKey(id); + if (skillEntry == null) + { + Log.outError(LogFilter.Spells, "Skill not found in SkillLineStore: skill #{0}", id); + return; + } + + SetUInt16Value(PlayerFields.SkillLineId + field, offset, (ushort)id); + if (skillEntry.CategoryID == SkillCategory.Profession) + { + if (GetUInt32Value(PlayerFields.ProfessionSkillLine1) == 0) + SetUInt32Value(PlayerFields.ProfessionSkillLine1, id); + else if (GetUInt32Value(PlayerFields.ProfessionSkillLine1 + 1) == 0) + SetUInt32Value(PlayerFields.ProfessionSkillLine1 + 1, id); + } + + SetUInt16Value(PlayerFields.SkillLineStep + field, offset, (ushort)step); + SetUInt16Value(PlayerFields.SkillLineRank + field, offset, (ushort)newVal); + SetUInt16Value(PlayerFields.SkillLineMaxRank + field, offset, (ushort)maxVal); + + UpdateSkillEnchantments(id, currVal, (ushort)newVal); + UpdateCriteria(CriteriaTypes.ReachSkillLevel, id); + UpdateCriteria(CriteriaTypes.LearnSkillLevel, id); + + // insert new entry or update if not deleted old entry yet + if (skillStatusData != null) + { + skillStatusData.Pos = (byte)i; + skillStatusData.State = SkillState.Changed; + } + else + mSkillStatus.Add(id, new SkillStatusData((uint)i, SkillState.New)); + + // apply skill bonuses + SetUInt16Value(PlayerFields.SkillLineTempBonus + field, offset, 0); + SetUInt16Value(PlayerFields.SkillLinePermBonus + field, offset, 0); + + // temporary bonuses + var mModSkill = GetAuraEffectsByType(AuraType.ModSkill); + foreach (var j in mModSkill) + { + if (j.GetMiscValue() == id) + j.HandleEffect(this, AuraEffectHandleModes.Skill, true); + } + + var mModSkill2 = GetAuraEffectsByType(AuraType.ModSkill2); + foreach (var j in mModSkill2) + { + if (j.GetMiscValue() == id) + j.HandleEffect(this, AuraEffectHandleModes.Skill, true); + } + + // permanent bonuses + var mModSkillTalent = GetAuraEffectsByType(AuraType.ModSkillTalent); + foreach (var eff in mModSkillTalent) + { + if (eff.GetMiscValue() == id) + eff.HandleEffect(this, AuraEffectHandleModes.Skill, true); + } + + // Learn all spells for skill + LearnSkillRewardedSpells(id, newVal); + return; + } + } + } + } + + public bool UpdateCraftSkill(uint spellid) + { + Log.outDebug(LogFilter.Player, "UpdateCraftSkill spellid {0}", spellid); + + var bounds = Global.SpellMgr.GetSkillLineAbilityMapBounds(spellid); + + foreach (var _spell_idx in bounds) + { + if (_spell_idx.SkillLine != 0) + { + uint SkillValue = GetPureSkillValue((SkillType)_spell_idx.SkillLine); + + // Alchemy Discoveries here + SpellInfo spellEntry = Global.SpellMgr.GetSpellInfo(spellid); + if (spellEntry != null && spellEntry.Mechanic == Mechanics.Discovery) + { + uint discoveredSpell = SkillDiscovery.GetSkillDiscoverySpell(_spell_idx.SkillLine, spellid, this); + if (discoveredSpell != 0) + LearnSpell(discoveredSpell, false); + } + + uint craft_skill_gain = _spell_idx.NumSkillUps * WorldConfig.GetUIntValue(WorldCfg.SkillGainCrafting); + + return UpdateSkillPro(_spell_idx.SkillLine, SkillGainChance(SkillValue, _spell_idx.TrivialSkillLineRankHigh, + (uint)(_spell_idx.TrivialSkillLineRankHigh + _spell_idx.TrivialSkillLineRankLow) / 2, _spell_idx.TrivialSkillLineRankLow), craft_skill_gain); + } + } + return false; + } + public bool UpdateGatherSkill(uint SkillId, uint SkillValue, uint RedLevel, uint Multiplicator = 1) + { + return UpdateGatherSkill((SkillType)SkillId, SkillValue, RedLevel, Multiplicator); + } + public bool UpdateGatherSkill(SkillType SkillId, uint SkillValue, uint RedLevel, uint Multiplicator = 1) + { + Log.outDebug(LogFilter.Player, "UpdateGatherSkill(SkillId {0} SkillLevel {1} RedLevel {2})", SkillId, SkillValue, RedLevel); + + uint gathering_skill_gain = WorldConfig.GetUIntValue(WorldCfg.SkillGainGathering); + + // For skinning and Mining chance decrease with level. 1-74 - no decrease, 75-149 - 2 times, 225-299 - 8 times + switch (SkillId) + { + case SkillType.Herbalism: + case SkillType.Jewelcrafting: + case SkillType.Inscription: + return UpdateSkillPro(SkillId, SkillGainChance(SkillValue, RedLevel + 100, RedLevel + 50, RedLevel + 25) * (int)Multiplicator, gathering_skill_gain); + case SkillType.Skinning: + if (WorldConfig.GetIntValue(WorldCfg.SkillChanceSkinningSteps) == 0) + return UpdateSkillPro(SkillId, SkillGainChance(SkillValue, RedLevel + 100, RedLevel + 50, RedLevel + 25) * (int)Multiplicator, gathering_skill_gain); + else + return UpdateSkillPro(SkillId, (int)(SkillGainChance(SkillValue, RedLevel + 100, RedLevel + 50, RedLevel + 25) * Multiplicator) >> (int)(SkillValue / WorldConfig.GetIntValue(WorldCfg.SkillChanceSkinningSteps)), gathering_skill_gain); + case SkillType.Mining: + if (WorldConfig.GetIntValue(WorldCfg.SkillChanceMiningSteps) == 0) + return UpdateSkillPro(SkillId, SkillGainChance(SkillValue, RedLevel + 100, RedLevel + 50, RedLevel + 25) * (int)Multiplicator, gathering_skill_gain); + else + return UpdateSkillPro(SkillId, (int)(SkillGainChance(SkillValue, RedLevel + 100, RedLevel + 50, RedLevel + 25) * Multiplicator) >> (int)(SkillValue / WorldConfig.GetIntValue(WorldCfg.SkillChanceMiningSteps)), gathering_skill_gain); + } + return false; + } + + byte GetFishingStepsNeededToLevelUp(uint SkillValue) + { + // These formulas are guessed to be as close as possible to how the skill difficulty curve for fishing was on Retail. + if (SkillValue < 75) + return 1; + + if (SkillValue <= 300) + return (byte)(SkillValue / 44); + + return (byte)(SkillValue / 31); + } + + public bool UpdateFishingSkill() + { + Log.outDebug(LogFilter.Player, "UpdateFishingSkill"); + + uint SkillValue = GetPureSkillValue(SkillType.Fishing); + + if (SkillValue >= GetMaxSkillValue(SkillType.Fishing)) + return false; + + byte stepsNeededToLevelUp = GetFishingStepsNeededToLevelUp(SkillValue); + ++m_fishingSteps; + + if (m_fishingSteps >= stepsNeededToLevelUp) + { + m_fishingSteps = 0; + + uint gathering_skill_gain = WorldConfig.GetUIntValue(WorldCfg.SkillGainGathering); + return UpdateSkillPro(SkillType.Fishing, 100 * 10, gathering_skill_gain); + } + + return false; + } + + int SkillGainChance(uint SkillValue, uint GrayLevel, uint GreenLevel, uint YellowLevel) + { + if (SkillValue >= GrayLevel) + return WorldConfig.GetIntValue(WorldCfg.SkillChanceGrey) * 10; + if (SkillValue >= GreenLevel) + return WorldConfig.GetIntValue(WorldCfg.SkillChanceGreen) * 10; + if (SkillValue >= YellowLevel) + return WorldConfig.GetIntValue(WorldCfg.SkillChanceYellow) * 10; + return WorldConfig.GetIntValue(WorldCfg.SkillChanceOrange) * 10; + } + + bool EnchantmentFitsRequirements(uint enchantmentcondition, sbyte slot) + { + if (enchantmentcondition == 0) + return true; + + SpellItemEnchantmentConditionRecord Condition = CliDB.SpellItemEnchantmentConditionStorage.LookupByKey(enchantmentcondition); + + if (Condition == null) + return true; + + byte[] curcount = { 0, 0, 0, 0 }; + + //counting current equipped gem colors + for (byte i = EquipmentSlot.Start; i < EquipmentSlot.End; ++i) + { + if (i == slot) + continue; + + Item pItem2 = GetItemByPos(InventorySlots.Bag0, i); + if (pItem2 != null && !pItem2.IsBroken()) + { + foreach (ItemDynamicFieldGems gemData in pItem2.GetGems()) + { + ItemTemplate gemProto = Global.ObjectMgr.GetItemTemplate(gemData.ItemId); + if (gemProto == null) + continue; + + GemPropertiesRecord gemProperty = CliDB.GemPropertiesStorage.LookupByKey(gemProto.GetGemProperties()); + if (gemProperty == null) + continue; + + uint GemColor = (uint)gemProperty.Type; + + for (byte b = 0, tmpcolormask = 1; b < 4; b++, tmpcolormask <<= 1) + { + if (Convert.ToBoolean(tmpcolormask & GemColor)) + ++curcount[b]; + } + } + } + } + + bool activate = true; + + for (byte i = 0; i < 5; i++) + { + if (Condition.LTOperandType[i] == 0) + continue; + + uint _cur_gem = curcount[Condition.LTOperandType[i] - 1]; + + // if have use them as count, else use from Condition + uint _cmp_gem = Condition.RTOperandType[i] != 0 ? curcount[Condition.RTOperandType[i] - 1] : Condition.RTOperand[i]; + + switch (Condition.Operator[i]) + { + case 2: // requires less than ( || ) gems + activate &= (_cur_gem < _cmp_gem) ? true : false; + break; + case 3: // requires more than ( || ) gems + activate &= (_cur_gem > _cmp_gem) ? true : false; + break; + case 5: // requires at least than ( || ) gems + activate &= (_cur_gem >= _cmp_gem) ? true : false; + break; + } + } + + Log.outDebug(LogFilter.Player, "Checking Condition {0}, there are {1} Meta Gems, {2} Red Gems, {3} Yellow Gems and {4} Blue Gems, Activate:{5}", enchantmentcondition, curcount[0], curcount[1], curcount[2], curcount[3], activate ? "yes" : "no"); + + return activate; + } + void CorrectMetaGemEnchants(byte exceptslot, bool apply) + { + //cycle all equipped items + for (byte slot = EquipmentSlot.Start; slot < EquipmentSlot.End; ++slot) + { + //enchants for the slot being socketed are handled by Player.ApplyItemMods + if (slot == exceptslot) + continue; + + Item pItem = GetItemByPos(InventorySlots.Bag0, slot); + + if (pItem == null || pItem.GetSocketColor(0) == 0) + continue; + + for (EnchantmentSlot enchant_slot = EnchantmentSlot.Sock1; enchant_slot < EnchantmentSlot.Sock3; ++enchant_slot) + { + uint enchant_id = pItem.GetEnchantmentId(enchant_slot); + if (enchant_id == 0) + continue; + + SpellItemEnchantmentRecord enchantEntry = CliDB.SpellItemEnchantmentStorage.LookupByKey(enchant_id); + if (enchantEntry == null) + continue; + + uint condition = enchantEntry.ConditionID; + if (condition != 0) + { + //was enchant active with/without item? + bool wasactive = EnchantmentFitsRequirements(condition, (sbyte)(apply ? exceptslot : -1)); + //should it now be? + if (wasactive ^ EnchantmentFitsRequirements(condition, (sbyte)(apply ? -1 : exceptslot))) + { + // ignore item gem conditions + //if state changed, (dis)apply enchant + ApplyEnchantment(pItem, enchant_slot, !wasactive, true, true); + } + } + } + } + } + + public void CastItemUseSpell(Item item, SpellCastTargets targets, ObjectGuid castCount, uint[] misc) + { + ItemTemplate proto = item.GetTemplate(); + // special learning case + if (proto.Effects.Count >= 2) + { + if (proto.Effects[0].SpellID == 483 || proto.Effects[0].SpellID == 55884) + { + uint learn_spell_id = proto.Effects[0].SpellID; + uint learning_spell_id = proto.Effects[1].SpellID; + + SpellInfo spellInfo = Global.SpellMgr.GetSpellInfo(learn_spell_id); + if (spellInfo == null) + { + Log.outError(LogFilter.Player, "Player.CastItemUseSpell: Item (Entry: {0}) in have wrong spell id {1}, ignoring ", proto.GetId(), learn_spell_id); + SendEquipError(InventoryResult.InternalBagError, item); + return; + } + + Spell spell = new Spell(this, spellInfo, TriggerCastFlags.None); + + SpellPrepare spellPrepare = new SpellPrepare(); + spellPrepare.ClientCastID = castCount; + spellPrepare.ServerCastID = spell.m_castId; + SendPacket(spellPrepare); + + spell.m_fromClient = true; + spell.m_CastItem = item; + spell.SetSpellValue(SpellValueMod.BasePoint0, (int)learning_spell_id); + spell.prepare(targets); + return; + } + } + + // item spells casted at use + for (byte i = 0; i < proto.Effects.Count; ++i) + { + var spellData = proto.Effects[i]; + + // no spell + if (spellData.SpellID == 0) + continue; + + // wrong triggering type + if (spellData.Trigger != ItemSpelltriggerType.OnUse) + continue; + + SpellInfo spellInfo = Global.SpellMgr.GetSpellInfo(spellData.SpellID); + if (spellInfo == null) + { + Log.outError(LogFilter.Player, "Player.CastItemUseSpell: Item (Entry: {0}) in have wrong spell id {1}, ignoring", proto.GetId(), spellData.SpellID); + continue; + } + + Spell spell = new Spell(this, spellInfo, TriggerCastFlags.None); + + SpellPrepare spellPrepare = new SpellPrepare(); + spellPrepare.ClientCastID = castCount; + spellPrepare.ServerCastID = spell.m_castId; + SendPacket(spellPrepare); + + spell.m_fromClient = true; + spell.m_CastItem = item; + spell.m_misc.Data0 = misc[0]; + spell.m_misc.Data1 = misc[1]; + spell.prepare(targets); + return; + } + + // Item enchantments spells casted at use + for (EnchantmentSlot e_slot = 0; e_slot < EnchantmentSlot.Max; ++e_slot) + { + uint enchant_id = item.GetEnchantmentId(e_slot); + var pEnchant = CliDB.SpellItemEnchantmentStorage.LookupByKey(enchant_id); + if (pEnchant == null) + continue; + for (byte s = 0; s < ItemConst.MaxItemEnchantmentEffects; ++s) + { + if (pEnchant.Effect[s] != ItemEnchantmentType.UseSpell) + continue; + + SpellInfo spellInfo = Global.SpellMgr.GetSpellInfo(pEnchant.EffectSpellID[s]); + if (spellInfo == null) + { + Log.outError(LogFilter.Player, "Player.CastItemUseSpell Enchant {0}, cast unknown spell {1}", enchant_id, pEnchant.EffectSpellID[s]); + continue; + } + + Spell spell = new Spell(this, spellInfo, TriggerCastFlags.None); + + SpellPrepare spellPrepare = new SpellPrepare(); + spellPrepare.ClientCastID = castCount; + spellPrepare.ServerCastID = spell.m_castId; + SendPacket(spellPrepare); + + spell.m_fromClient = true; + spell.m_CastItem = item; + spell.m_misc.Data0 = misc[0]; + spell.m_misc.Data1 = misc[1]; + spell.prepare(targets); + return; + } + } + } + + public uint GetLastPotionId() { return m_lastPotionId; } + public void SetLastPotionId(uint item_id) { m_lastPotionId = item_id; } + + void LearnSkillRewardedSpells(uint skillId, uint skillValue) + { + // bad hack to work around data being suited only for the client - AcquireMethod == SKILL_LINE_ABILITY_LEARNED_ON_SKILL_LEARN for riding + // client uses it to show riding in spellbook as trainable + if (skillId == (uint)SkillType.Riding) + return; + + uint raceMask = getRaceMask(); + uint classMask = getClassMask(); + foreach (var ability in CliDB.SkillLineAbilityStorage.Values) + { + if (ability.SkillLine != skillId) + continue; + + SpellInfo spellInfo = Global.SpellMgr.GetSpellInfo(ability.SpellID); + if (spellInfo == null) + continue; + + if (ability.AcquireMethod != AbilytyLearnType.OnSkillValue && ability.AcquireMethod != AbilytyLearnType.OnSkillLearn) + continue; + + // Check race if set + if (ability.RaceMask != 0 && !Convert.ToBoolean(ability.RaceMask & raceMask)) + continue; + + // Check class if set + if (ability.ClassMask != 0 && !Convert.ToBoolean(ability.ClassMask & classMask)) + continue; + + // check level, skip class spells if not high enough + if (getLevel() < spellInfo.SpellLevel) + continue; + + // need unlearn spell + if (skillValue < ability.MinSkillLineRank && ability.AcquireMethod == AbilytyLearnType.OnSkillValue) + RemoveSpell(ability.SpellID); + // need learn + else if (!IsInWorld) + AddSpell(ability.SpellID, true, true, true, false, false, ability.SkillLine); + else + LearnSpell(ability.SpellID, true, ability.SkillLine); + + } + } + + void RemoveItemDependentAurasAndCasts(Item pItem) + { + foreach (var pair in GetOwnedAuras()) + { + Aura aura = pair.Value; + + // skip not self applied auras + SpellInfo spellInfo = aura.GetSpellInfo(); + if (aura.GetCasterGUID() != GetGUID()) + continue; + + // skip if not item dependent or have alternative item + if (HasItemFitToSpellRequirements(spellInfo, pItem)) + continue; + + // no alt item, remove aura, restart check + RemoveOwnedAura(pair); + } + + // currently casted spells can be dependent from item + for (CurrentSpellTypes i = 0; i < CurrentSpellTypes.Max; ++i) + { + Spell spell = GetCurrentSpell(i); + if (spell != null) + if (spell.getState() != SpellState.Delayed && !HasItemFitToSpellRequirements(spell.m_spellInfo, pItem)) + InterruptSpell(i); + } + } + + public bool HasItemFitToSpellRequirements(SpellInfo spellInfo, Item ignoreItem = null) + { + if (spellInfo.EquippedItemClass < 0) + return true; + + // scan other equipped items for same requirements (mostly 2 daggers/etc) + // for optimize check 2 used cases only + switch (spellInfo.EquippedItemClass) + { + case ItemClass.Weapon: + { + Item item = GetUseableItemByPos(InventorySlots.Bag0, EquipmentSlot.MainHand); + if (item) + if (item != ignoreItem && item.IsFitToSpellRequirements(spellInfo)) + return true; + + item = GetUseableItemByPos(InventorySlots.Bag0, EquipmentSlot.OffHand); + if (item) + if (item != ignoreItem && item.IsFitToSpellRequirements(spellInfo)) + return true; + break; + } + case ItemClass.Armor: + { + if (!spellInfo.HasAttribute(SpellAttr8.ArmorSpecialization)) + { + // tabard not have dependent spells + for (byte i = EquipmentSlot.Start; i < EquipmentSlot.MainHand; ++i) + { + Item item = GetUseableItemByPos(InventorySlots.Bag0, i); + if (item) + if (item != ignoreItem && item.IsFitToSpellRequirements(spellInfo)) + return true; + } + + // shields can be equipped to offhand slot + Item item1 = GetUseableItemByPos(InventorySlots.Bag0, EquipmentSlot.OffHand); + if (item1) + if (item1 != ignoreItem && item1.IsFitToSpellRequirements(spellInfo)) + return true; + } + else + { + foreach (byte i in new[] { EquipmentSlot.Head, EquipmentSlot.Shoulders, EquipmentSlot.Chest, EquipmentSlot.Waist, EquipmentSlot.Legs, EquipmentSlot.Feet, EquipmentSlot.Wrist, EquipmentSlot.Hands }) + { + Item item = GetUseableItemByPos(InventorySlots.Bag0, i); + if (!item || !item.IsFitToSpellRequirements(spellInfo)) + return false; + } + } + break; + } + default: + Log.outError(LogFilter.Player, "HasItemFitToSpellRequirements: Not handled spell requirement for item class {0}", spellInfo.EquippedItemClass); + break; + } + + return false; + } + + public Dictionary GetSpellMap() { return m_spells; } + + public void ApplyItemDependentAuras(Item item, bool apply) + { + if (apply) + { + var spells = GetSpellMap(); + foreach (var pair in spells) + { + if (pair.Value.State == PlayerSpellState.Removed || pair.Value.Disabled) + continue; + + SpellInfo spellInfo = Global.SpellMgr.GetSpellInfo(pair.Key); + if (spellInfo == null || !spellInfo.IsPassive() || spellInfo.EquippedItemClass < 0) + continue; + + if (!HasAura(pair.Key) && HasItemFitToSpellRequirements(spellInfo)) + AddAura(pair.Key, this); // no SMSG_SPELL_GO in sniff found + } + } + else + RemoveItemDependentAurasAndCasts(item); + } + + public void AddTemporarySpell(uint spellId) + { + var spell = m_spells.LookupByKey(spellId); + // spell already added - do not do anything + if (spell != null) + return; + + PlayerSpell newspell = new PlayerSpell(); + newspell.State = PlayerSpellState.Temporary; + newspell.Active = true; + newspell.Dependent = false; + newspell.Disabled = false; + + m_spells[spellId] = newspell; + } + + public void RemoveTemporarySpell(uint spellId) + { + var spell = m_spells.LookupByKey(spellId); + // spell already not in list - do not do anything + if (spell == null) + return; + // spell has other state than temporary - do not change it + if (spell.State != PlayerSpellState.Temporary) + return; + + m_spells.Remove(spellId); + } + + + public void UpdateZoneDependentAuras(uint newZone) + { + // Some spells applied at enter into zone (with subzones), aura removed in UpdateAreaDependentAuras that called always at zone.area update + var saBounds = Global.SpellMgr.GetSpellAreaForAreaMapBounds(newZone); + foreach (var spell in saBounds) + if (spell.autocast && spell.IsFitToRequirements(this, newZone, 0)) + if (!HasAura(spell.spellId)) + CastSpell(this, spell.spellId, true); + } + + public void UpdateAreaDependentAuras(uint newArea) + { + // remove auras from spells with area limitations + foreach (var pair in GetOwnedAuras()) + { + // use m_zoneUpdateId for speed: UpdateArea called from UpdateZone or instead UpdateZone in both cases m_zoneUpdateId up-to-date + if (pair.Value.GetSpellInfo().CheckLocation(GetMapId(), m_zoneUpdateId, newArea, this) != SpellCastResult.SpellCastOk) + RemoveOwnedAura(pair); + } + + // some auras applied at subzone enter + var saBounds = Global.SpellMgr.GetSpellAreaForAreaMapBounds(newArea); + foreach (var spell in saBounds) + if (spell.autocast && spell.IsFitToRequirements(this, m_zoneUpdateId, newArea)) + if (!HasAura(spell.spellId)) + CastSpell(this, spell.spellId, true); + } + + + // Restore spellmods in case of failed cast + public void RestoreSpellMods(Spell spell, uint ownerAuraId = 0, Aura aura = null) + { + if (spell == null || spell.m_appliedMods.Empty()) + return; + + List aurasQueue = new List(); + + for (var i = 0; i < (int)SpellModOp.Max; ++i) + { + for (var j = 0; j < (int)SpellModType.End; ++j) + { + foreach (var mod in m_spellMods[i][j]) + { + // spellmods without aura set cannot be charged + if (mod.ownerAura == null || !mod.ownerAura.IsUsingCharges()) + continue; + + // Restore only specific owner aura mods + if (ownerAuraId != 0 && (ownerAuraId != mod.ownerAura.GetSpellInfo().Id)) + continue; + + if (aura != null && mod.ownerAura != aura) + continue; + + // check if mod affected this spell + // first, check if the mod aura applied at least one spellmod to this spell + var iterMod = spell.m_appliedMods.Find(p => p == mod.ownerAura); + if (iterMod == null) + continue; + + // secondly, check if the current mod is one of the spellmods applied by the mod aura + if (!(mod.mask & spell.m_spellInfo.SpellFamilyFlags)) + continue; + + // remove from list - This will be done after all mods have been gone through + // to ensure we iterate over all mods of an aura before removing said aura + // from applied mods (Else, an aura with two mods on the current spell would + // only see the first of its modifier restored) + aurasQueue.Add(mod.ownerAura); + + // add mod charges back to mod + if (mod.charges == -1) + mod.charges = 1; + else + mod.charges++; + + // Do not set more spellmods than avalible + if (mod.ownerAura.GetCharges() < mod.charges) + mod.charges = mod.ownerAura.GetCharges(); + + // Skip this check for now - aura charges may change due to various reason + /// @todo track these changes correctly + //ASSERT (mod->ownerAura->GetCharges() <= mod->charges); + } + } + } + + foreach (var removeAura in aurasQueue) + { + var appliedMod = spell.m_appliedMods.Find(p => p == removeAura); + if (appliedMod != null) + spell.m_appliedMods.Remove(appliedMod); + } + } + + public void RestoreAllSpellMods(uint ownerAuraId, Aura aura) + { + for (CurrentSpellTypes i = 0; i < CurrentSpellTypes.Max; ++i) + if (GetCurrentSpell(i) != null) + RestoreSpellMods(m_currentSpells[i], ownerAuraId, aura); + } + + public void RemoveSpellMods(Spell spell) + { + if (spell == null) + return; + + if (spell.m_appliedMods.Empty()) + return; + + for (var i = 0; i < (int)SpellModOp.Max; ++i) + { + for (var j = 0; j < (int)SpellModType.End; ++j) + { + for (var c = 0; c < m_spellMods[i][j].Count; c++) + { + SpellModifier mod = m_spellMods[i][j][c]; + // spellmods without aura set cannot be charged + if (mod.ownerAura == null || !mod.ownerAura.IsUsingCharges()) + continue; + + // check if mod affected this spell + var iterMod = spell.m_appliedMods.Find(p => p == mod.ownerAura); + if (iterMod == null) + continue; + + // remove from list + spell.m_appliedMods.Remove(iterMod); + + if (mod.ownerAura.DropCharge(AuraRemoveMode.Expire)) + c = 0; + } + } + } + } + + public void LearnCustomSpells() + { + if (!WorldConfig.GetBoolValue(WorldCfg.StartAllSpells)) + return; + + // learn default race/class spells + PlayerInfo info = Global.ObjectMgr.GetPlayerInfo(GetRace(), GetClass()); + foreach (var tspell in info.customSpells) + { + Log.outDebug(LogFilter.Player, "PLAYER (Class: {0} Race: {1}): Adding initial spell, id = {2}", GetClass(), GetRace(), tspell); + if (!IsInWorld) // will send in INITIAL_SPELLS in list anyway at map add + AddSpell(tspell, true, true, true, false); + else // but send in normal spell in game learn case + LearnSpell(tspell, true); + } + } + + public void LearnDefaultSkills() + { + // learn default race/class skills + PlayerInfo info = Global.ObjectMgr.GetPlayerInfo(GetRace(), GetClass()); + foreach (var rcInfo in info.skills) + { + if (HasSkill((SkillType)rcInfo.SkillID)) + continue; + + if (rcInfo.MinLevel > getLevel()) + continue; + + LearnDefaultSkill(rcInfo); + } + } + + public void LearnDefaultSkill(SkillRaceClassInfoRecord rcInfo) + { + SkillType skillId = (SkillType)rcInfo.SkillID; + switch (Global.SpellMgr.GetSkillRangeType(rcInfo)) + { + case SkillRangeType.Language: + SetSkill(skillId, 0, 300, 300); + break; + case SkillRangeType.Level: + { + ushort skillValue = 1; + ushort maxValue = GetMaxSkillValueForLevel(); + if (rcInfo.Flags.HasAnyFlag(SkillRaceClassInfoFlags.AlwaysMaxValue)) + skillValue = maxValue; + else if (GetClass() == Class.Deathknight) + skillValue = (ushort)Math.Min(Math.Max(1, (getLevel() - 1) * 5), maxValue); + else if (skillId == SkillType.FistWeapons) + skillValue = Math.Max((ushort)1, GetSkillValue(SkillType.Unarmed)); + + SetSkill(skillId, 0, skillValue, maxValue); + break; + } + case SkillRangeType.Mono: + SetSkill(skillId, 0, 1, 1); + break; + case SkillRangeType.Rank: + { + ushort rank = 1; + if (GetClass() == Class.Deathknight && skillId == SkillType.FirstAid) + rank = 4; + + SkillTiersEntry tier = Global.ObjectMgr.GetSkillTier(rcInfo.SkillTierID); + ushort maxValue = (ushort)tier.Value[Math.Max(rank - 1, 0)]; + ushort skillValue = 1; + if (rcInfo.Flags.HasAnyFlag(SkillRaceClassInfoFlags.AlwaysMaxValue)) + skillValue = maxValue; + else if (GetClass() == Class.Deathknight) + skillValue = (ushort)Math.Min(Math.Max(1, (getLevel() - 1) * 5), maxValue); + + SetSkill(skillId, rank, skillValue, maxValue); + break; + } + default: + break; + } + } + + void SendKnownSpells() + { + SendKnownSpells knownSpells = new SendKnownSpells(); + knownSpells.InitialLogin = false; // @todo + + foreach (var spell in m_spells) + { + if (spell.Value.State == PlayerSpellState.Removed) + continue; + + if (!spell.Value.Active || spell.Value.Disabled) + continue; + + knownSpells.KnownSpells.Add(spell.Key); + } + + SendPacket(knownSpells); + } + + public void LearnSpellHighestRank(uint spellid) + { + LearnSpell(spellid, false); + + uint next = Global.SpellMgr.GetNextSpellInChain(spellid); + if (next != 0) + LearnSpellHighestRank(next); + } + + public void LearnSpell(uint spellId, bool dependent, uint fromSkill = 0) + { + PlayerSpell spell = m_spells.LookupByKey(spellId); + + bool disabled = (spell != null) ? spell.Disabled : false; + bool active = disabled ? spell.Active : true; + + bool learning = AddSpell(spellId, active, true, dependent, false, false, fromSkill); + + // prevent duplicated entires in spell book, also not send if not in world (loading) + if (learning && IsInWorld) + { + LearnedSpells packet = new LearnedSpells(); + packet.SpellID.Add(spellId); + SendPacket(packet); + } + + // learn all disabled higher ranks and required spells (recursive) + if (disabled) + { + uint nextSpell = Global.SpellMgr.GetNextSpellInChain(spellId); + if (nextSpell != 0) + { + var _spell = m_spells.LookupByKey(nextSpell); + if (spellId != 0 && _spell.Disabled) + LearnSpell(nextSpell, false, fromSkill); + } + + var spellsRequiringSpell = Global.SpellMgr.GetSpellsRequiringSpellBounds(spellId); + foreach (var id in spellsRequiringSpell) + { + var spell1 = m_spells.LookupByKey(id); + if (spell1 != null && spell1.Disabled) + LearnSpell(id, false, fromSkill); + } + } + + } + + public void RemoveSpell(uint spell_id, bool disabled = false, bool learn_low_rank = true) + { + var pSpell = m_spells.LookupByKey(spell_id); + if (pSpell == null) + return; + + if (pSpell.State == PlayerSpellState.Removed || (disabled && pSpell.Disabled) || pSpell.State == PlayerSpellState.Temporary) + return; + + // unlearn non talent higher ranks (recursive) + uint nextSpell = Global.SpellMgr.GetNextSpellInChain(spell_id); + if (nextSpell != 0) + { + SpellInfo spellInfo1 = Global.SpellMgr.GetSpellInfo(nextSpell); + if (HasSpell(nextSpell) && !spellInfo1.HasAttribute(SpellCustomAttributes.IsTalent)) + RemoveSpell(nextSpell, disabled, false); + } + //unlearn spells dependent from recently removed spells + var spellsRequiringSpell = Global.SpellMgr.GetSpellsRequiringSpellBounds(spell_id); + foreach (var id in spellsRequiringSpell) + RemoveSpell(id, disabled); + + // re-search, it can be corrupted in prev loop + pSpell = m_spells.LookupByKey(spell_id); + if (pSpell == null) + return; // already unleared + + bool cur_active = pSpell.Active; + bool cur_dependent = pSpell.Dependent; + + if (disabled) + { + pSpell.Disabled = disabled; + if (pSpell.State != PlayerSpellState.New) + pSpell.State = PlayerSpellState.Changed; + } + else + { + if (pSpell.State == PlayerSpellState.New) + m_spells.Remove(spell_id); + else + pSpell.State = PlayerSpellState.Removed; + } + + RemoveOwnedAura(spell_id, GetGUID()); + + // remove pet auras + for (byte i = 0; i < SpellConst.MaxEffects; ++i) + { + PetAura petSpell = Global.SpellMgr.GetPetAura(spell_id, i); + if (petSpell != null) + RemovePetAura(petSpell); + } + + // update free primary prof.points (if not overflow setting, can be in case GM use before .learn prof. learning) + SpellInfo spellInfo = Global.SpellMgr.GetSpellInfo(spell_id); + if (spellInfo != null && spellInfo.IsPrimaryProfessionFirstRank()) + { + uint freeProfs = GetFreePrimaryProfessionPoints() + 1; + if (freeProfs <= WorldConfig.GetIntValue(WorldCfg.MaxPrimaryTradeSkill)) + SetFreePrimaryProfessions(freeProfs); + } + + // remove dependent skill + var spellLearnSkill = Global.SpellMgr.GetSpellLearnSkill(spell_id); + if (spellLearnSkill != null) + { + uint prev_spell = Global.SpellMgr.GetPrevSpellInChain(spell_id); + if (prev_spell == 0) // first rank, remove skill + SetSkill(spellLearnSkill.skill, 0, 0, 0); + else + { + // search prev. skill setting by spell ranks chain + var prevSkill = Global.SpellMgr.GetSpellLearnSkill(prev_spell); + while (prevSkill == null && prev_spell != 0) + { + prev_spell = Global.SpellMgr.GetPrevSpellInChain(prev_spell); + prevSkill = Global.SpellMgr.GetSpellLearnSkill(Global.SpellMgr.GetFirstSpellInChain(prev_spell)); + } + + if (prevSkill == null) // not found prev skill setting, remove skill + SetSkill(spellLearnSkill.skill, 0, 0, 0); + else // set to prev. skill setting values + { + uint skill_value = GetPureSkillValue(prevSkill.skill); + uint skill_max_value = GetPureMaxSkillValue(prevSkill.skill); + + if (skill_value > prevSkill.value) + skill_value = prevSkill.value; + + uint new_skill_max_value = prevSkill.maxvalue == 0 ? GetMaxSkillValueForLevel() : prevSkill.maxvalue; + + if (skill_max_value > new_skill_max_value) + skill_max_value = new_skill_max_value; + + SetSkill(prevSkill.skill, prevSkill.step, skill_value, skill_max_value); + } + } + } + + // remove dependent spells + var spell_bounds = Global.SpellMgr.GetSpellLearnSpellMapBounds(spell_id); + + foreach (var spellNode in spell_bounds) + { + RemoveSpell(spellNode.Spell, disabled); + if (spellNode.OverridesSpell != 0) + RemoveOverrideSpell(spellNode.OverridesSpell, spellNode.Spell); + } + + // activate lesser rank in spellbook/action bar, and cast it if need + bool prev_activate = false; + + uint prev_id = Global.SpellMgr.GetPrevSpellInChain(spell_id); + if (prev_id != 0) + { + // if ranked non-stackable spell: need activate lesser rank and update dendence state + // No need to check for spellInfo != NULL here because if cur_active is true, then that means that the spell was already in m_spells, and only valid spells can be pushed there. + if (cur_active && spellInfo.IsRanked()) + { + // need manually update dependence state (learn spell ignore like attempts) + var prevSpell = m_spells.LookupByKey(prev_id); + if (prevSpell != null) + { + if (prevSpell.Dependent != cur_dependent) + { + prevSpell.Dependent = cur_dependent; + if (prevSpell.State != PlayerSpellState.New) + prevSpell.State = PlayerSpellState.Changed; + } + + // now re-learn if need re-activate + if (cur_active && !prevSpell.Active && learn_low_rank) + { + if (AddSpell(prev_id, true, false, prevSpell.Dependent, prevSpell.Disabled)) + { + // downgrade spell ranks in spellbook and action bar + SendSupercededSpell(spell_id, prev_id); + prev_activate = true; + } + } + } + } + } + + m_overrideSpells.Remove(spell_id); + + if (m_canTitanGrip) + { + if (spellInfo != null && spellInfo.IsPassive() && spellInfo.HasEffect(SpellEffectName.TitanGrip)) + { + RemoveAurasDueToSpell(m_titanGripPenaltySpellId); + SetCanTitanGrip(false); + } + } + + if (CanDualWield()) + { + if (spellInfo != null && spellInfo.IsPassive() && spellInfo.HasEffect(SpellEffectName.DualWield)) + SetCanDualWield(false); + } + + if (WorldConfig.GetBoolValue(WorldCfg.OffhandCheckAtSpellUnlearn)) + AutoUnequipOffhandIfNeed(); + + // remove from spell book if not replaced by lesser rank + if (!prev_activate) + { + UnlearnedSpells removedSpells = new UnlearnedSpells(); + removedSpells.SpellID.Add(spell_id); + SendPacket(removedSpells); + } + } + bool IsNeedCastPassiveSpellAtLearn(SpellInfo spellInfo) + { + // note: form passives activated with shapeshift spells be implemented by HandleShapeshiftBoosts instead of spell_learn_spell + // talent dependent passives activated at form apply have proper stance data + ShapeShiftForm form = GetShapeshiftForm(); + bool need_cast = (spellInfo.Stances == 0 || (form != 0 && Convert.ToBoolean(spellInfo.Stances & (1ul << ((int)form - 1)))) || + (form == 0 && spellInfo.HasAttribute(SpellAttr2.NotNeedShapeshift))); + + if (spellInfo.HasAttribute(SpellAttr8.MasterySpecialization)) + need_cast &= IsCurrentSpecMasterySpell(spellInfo); + + //Check CasterAuraStates + return need_cast && (spellInfo.CasterAuraState == 0 || HasAuraState(spellInfo.CasterAuraState)); + } + + public bool IsCurrentSpecMasterySpell(SpellInfo spellInfo) + { + ChrSpecializationRecord chrSpec = CliDB.ChrSpecializationStorage.LookupByKey(GetUInt32Value(PlayerFields.CurrentSpecId)); + if (chrSpec != null) + return spellInfo.Id == chrSpec.MasterySpellID[0] || spellInfo.Id == chrSpec.MasterySpellID[1]; + + return false; + } + + bool AddSpell(uint spellId, bool active, bool learning, bool dependent, bool disabled, bool loading = false, uint fromSkill = 0) + { + SpellInfo spellInfo = Global.SpellMgr.GetSpellInfo(spellId); + if (spellInfo == null) + { + // do character spell book cleanup (all characters) + if (!IsInWorld && !learning) + { + Log.outError(LogFilter.Spells, "Player.AddSpell: Spell (ID: {0}) does not exist. deleting for all characters in `character_spell`.", spellId); + + DeleteSpellFromAllPlayers(spellId); + } + else + Log.outError(LogFilter.Spells, "Player.AddSpell: Spell (ID: {0}) does not exist", spellId); + + return false; + } + + if (!Global.SpellMgr.IsSpellValid(spellInfo, this, false)) + { + // do character spell book cleanup (all characters) + if (!IsInWorld && !learning) + { + Log.outError(LogFilter.Spells, "Player.AddSpell: Spell (ID: {0}) is invalid. deleting for all characters in `character_spell`.", spellId); + + DeleteSpellFromAllPlayers(spellId); + } + else + Log.outError(LogFilter.Spells, "Player.AddSpell: Spell (ID: {0}) is invalid", spellId); + + return false; + } + + PlayerSpellState state = learning ? PlayerSpellState.New : PlayerSpellState.Unchanged; + + bool dependent_set = false; + bool disabled_case = false; + bool superceded_old = false; + + PlayerSpell spell = m_spells.LookupByKey(spellId); + if (spell != null && spell.State == PlayerSpellState.Temporary) + RemoveTemporarySpell(spellId); + if (spell != null) + { + uint next_active_spell_id = 0; + // fix activate state for non-stackable low rank (and find next spell for !active case) + if (spellInfo.IsRanked()) + { + uint next = Global.SpellMgr.GetNextSpellInChain(spellId); + if (next != 0) + { + if (HasSpell(next)) + { + // high rank already known so this must !active + active = false; + next_active_spell_id = next; + } + } + } + + // not do anything if already known in expected state + if (spell.State != PlayerSpellState.Removed && spell.Active == active && + spell.Dependent == dependent && spell.Disabled == disabled) + { + if (!IsInWorld && !learning) + spell.State = PlayerSpellState.Unchanged; + + return false; + } + + // dependent spell known as not dependent, overwrite state + if (spell.State != PlayerSpellState.Removed && !spell.Dependent && dependent) + { + spell.Dependent = dependent; + if (spell.State != PlayerSpellState.New) + spell.State = PlayerSpellState.Changed; + dependent_set = true; + } + + // update active state for known spell + if (spell.Active != active && spell.State != PlayerSpellState.Removed && !spell.Disabled) + { + spell.Active = active; + + if (!IsInWorld && !learning && !dependent_set) // explicitly load from DB and then exist in it already and set correctly + spell.State = PlayerSpellState.Unchanged; + else if (spell.State != PlayerSpellState.New) + spell.State = PlayerSpellState.Changed; + + if (active) + { + if (spellInfo.IsPassive() && IsNeedCastPassiveSpellAtLearn(spellInfo)) + CastSpell(this, spellId, true); + } + else if (IsInWorld) + { + if (next_active_spell_id != 0) + SendSupercededSpell(spellId, next_active_spell_id); + else + { + UnlearnedSpells removedSpells = new UnlearnedSpells(); + removedSpells.SpellID.Add(spellId); + SendPacket(removedSpells); + } + } + + return active; + } + + if (spell.Disabled != disabled && spell.State != PlayerSpellState.Removed) + { + if (spell.State != PlayerSpellState.New) + spell.State = PlayerSpellState.Changed; + spell.Disabled = disabled; + + if (disabled) + return false; + + disabled_case = true; + } + else + { + switch (spell.State) + { + case PlayerSpellState.Unchanged: + return false; + case PlayerSpellState.Removed: + { + m_spells.Remove(spellId); + state = PlayerSpellState.Changed; + break; + } + default: + { + // can be in case spell loading but learned at some previous spell loading + if (!IsInWorld && !learning && !dependent_set) + spell.State = PlayerSpellState.Unchanged; + return false; + } + } + } + } + + if (!disabled_case) // skip new spell adding if spell already known (disabled spells case) + { + // non talent spell: learn low ranks (recursive call) + uint prev_spell = Global.SpellMgr.GetPrevSpellInChain(spellId); + if (prev_spell != 0) + { + if (!IsInWorld || disabled) // at spells loading, no output, but allow save + AddSpell(prev_spell, active, true, true, disabled, false, fromSkill); + else // at normal learning + LearnSpell(prev_spell, true, fromSkill); + } + + PlayerSpell newspell = new PlayerSpell(); + newspell.State = state; + newspell.Active = active; + newspell.Dependent = dependent; + newspell.Disabled = disabled; + + // replace spells in action bars and spellbook to bigger rank if only one spell rank must be accessible + if (newspell.Active && !newspell.Disabled && spellInfo.IsRanked()) + { + foreach (var _spell in m_spells) + { + if (_spell.Value.State == PlayerSpellState.Removed) + continue; + + SpellInfo i_spellInfo = Global.SpellMgr.GetSpellInfo(_spell.Key); + if (i_spellInfo == null) + continue; + + if (spellInfo.IsDifferentRankOf(i_spellInfo)) + { + if (_spell.Value.Active) + { + if (spellInfo.IsHighRankOf(i_spellInfo)) + { + if (IsInWorld) // not send spell (re-/over-)learn packets at loading + SendSupercededSpell(_spell.Key, spellId); + + // mark old spell as disable (SMSG_SUPERCEDED_SPELL replace it in client by new) + _spell.Value.Active = false; + if (_spell.Value.State != PlayerSpellState.New) + _spell.Value.State = PlayerSpellState.Changed; + superceded_old = true; // new spell replace old in action bars and spell book. + } + else + { + if (IsInWorld) // not send spell (re-/over-)learn packets at loading + SendSupercededSpell(spellId, _spell.Key); + + // mark new spell as disable (not learned yet for client and will not learned) + newspell.Active = false; + if (newspell.State != PlayerSpellState.New) + newspell.State = PlayerSpellState.Changed; + } + } + } + } + } + m_spells[spellId] = newspell; + + // return false if spell disabled + if (newspell.Disabled) + return false; + } + + // cast talents with SPELL_EFFECT_LEARN_SPELL (other dependent spells will learned later as not auto-learned) + // note: all spells with SPELL_EFFECT_LEARN_SPELL isn't passive + if (!loading && spellInfo.HasAttribute(SpellCustomAttributes.IsTalent) && spellInfo.HasEffect(SpellEffectName.LearnSpell)) + { + // ignore stance requirement for talent learn spell (stance set for spell only for client spell description show) + CastSpell(this, spellId, true); + } + // also cast passive spells (including all talents without SPELL_EFFECT_LEARN_SPELL) with additional checks + else if (spellInfo.IsPassive()) + { + if (IsNeedCastPassiveSpellAtLearn(spellInfo)) + CastSpell(this, spellId, true); + } + else if (spellInfo.HasEffect(SpellEffectName.SkillStep)) + { + CastSpell(this, spellId, true); + return false; + } + + // update free primary prof.points (if any, can be none in case GM .learn prof. learning) + uint freeProfs = GetFreePrimaryProfessionPoints(); + if (freeProfs != 0) + { + if (spellInfo.IsPrimaryProfessionFirstRank()) + SetFreePrimaryProfessions(freeProfs - 1); + } + + var skill_bounds = Global.SpellMgr.GetSkillLineAbilityMapBounds(spellId); + + SpellLearnSkillNode spellLearnSkill = Global.SpellMgr.GetSpellLearnSkill(spellId); + if (spellLearnSkill != null) + { + // add dependent skills if this spell is not learned from adding skill already + if ((uint)spellLearnSkill.skill != fromSkill) + { + ushort skill_value = GetPureSkillValue(spellLearnSkill.skill); + ushort skill_max_value = GetPureMaxSkillValue(spellLearnSkill.skill); + + if (skill_value < spellLearnSkill.value) + skill_value = spellLearnSkill.value; + + ushort new_skill_max_value = spellLearnSkill.maxvalue == 0 ? GetMaxSkillValueForLevel() : spellLearnSkill.maxvalue; + + if (skill_max_value < new_skill_max_value) + skill_max_value = new_skill_max_value; + + SetSkill(spellLearnSkill.skill, spellLearnSkill.step, skill_value, skill_max_value); + } + } + else + { + // not ranked skills + foreach (var _spell_idx in skill_bounds) + { + SkillLineRecord pSkill = CliDB.SkillLineStorage.LookupByKey(_spell_idx.SkillLine); + if (pSkill == null) + continue; + + if (_spell_idx.SkillLine == fromSkill) + continue; + + // Runeforging special case + if ((_spell_idx.AcquireMethod == AbilytyLearnType.OnSkillLearn && !HasSkill((SkillType)_spell_idx.SkillLine)) + || ((_spell_idx.SkillLine == (int)SkillType.Runeforging) && _spell_idx.TrivialSkillLineRankHigh == 0)) + { + SkillRaceClassInfoRecord rcInfo = Global.DB2Mgr.GetSkillRaceClassInfo(_spell_idx.SkillLine, GetRace(), GetClass()); + if (rcInfo != null) + LearnDefaultSkill(rcInfo); + } + } + } + + + // learn dependent spells + var spell_bounds = Global.SpellMgr.GetSpellLearnSpellMapBounds(spellId); + foreach (var spellNode in spell_bounds) + { + if (!spellNode.AutoLearned) + { + if (!IsInWorld || !spellNode.Active) // at spells loading, no output, but allow save + AddSpell(spellNode.Spell, spellNode.Active, true, true, false); + else // at normal learning + LearnSpell(spellNode.Spell, true); + } + + if (spellNode.OverridesSpell != 0 && spellNode.Active) + AddOverrideSpell(spellNode.OverridesSpell, spellNode.Spell); + } + + if (!GetSession().PlayerLoading()) + { + // not ranked skills + foreach (var _spell_idx in skill_bounds) + { + UpdateCriteria(CriteriaTypes.LearnSkillLine, _spell_idx.SkillLine); + UpdateCriteria(CriteriaTypes.LearnSkilllineSpells, _spell_idx.SkillLine); + } + + UpdateCriteria(CriteriaTypes.LearnSpell, spellId); + } + + // needs to be when spell is already learned, to prevent infinite recursion crashes + if (Global.DB2Mgr.GetMount(spellId) != null) + GetSession().GetCollectionMgr().AddMount(spellId, MountStatusFlags.None, false, IsInWorld ? false : true); + + // return true (for send learn packet) only if spell active (in case ranked spells) and not replace old spell + return active && !disabled && !superceded_old; + } + public override bool HasSpell(uint spellId) + { + var spell = m_spells.LookupByKey(spellId); + if (spell != null) + return spell.State != PlayerSpellState.Removed && !spell.Disabled; + + return false; + } + public bool HasActiveSpell(uint spellId) + { + var spell = m_spells.LookupByKey(spellId); + if (spell != null) + return spell.State != PlayerSpellState.Removed && spell.Active + && !spell.Disabled; + + return false; + } + + public void AddSpellMod(SpellModifier mod, bool apply) + { + Log.outDebug(LogFilter.Spells, "Player.AddSpellMod {0}", mod.spellId); + + /// First, manipulate our spellmodifier container + if (apply) + m_spellMods[(int)mod.op][(int)mod.type].Add(mod); + else + m_spellMods[(int)mod.op][(int)mod.type].Remove(mod); + + /// Now, send spellmodifier packet + if (!IsLoading()) + { + ServerOpcodes opcode = (mod.type == SpellModType.Flat ? ServerOpcodes.SetFlatSpellModifier : ServerOpcodes.SetPctSpellModifier); + SetSpellModifier packet = new SetSpellModifier(opcode); + + // @todo Implement sending of bulk modifiers instead of single + SpellModifierInfo spellMod = new SpellModifierInfo(); + + spellMod.ModIndex = (byte)mod.op; + for (int eff = 0; eff < 128; ++eff) + { + FlagArray128 mask = new FlagArray128(); + mask[eff / 32] = 1u << (eff %32); + if (mod.mask & mask) + { + SpellModifierData modData = new SpellModifierData(); + if (mod.type == SpellModType.Flat) + { + modData.ModifierValue = 0.0f; + foreach (var spell in m_spellMods[(int)mod.op][(int)SpellModType.Flat]) + if (spell.mask & mask) + modData.ModifierValue += spell.value; + } + else + { + modData.ModifierValue = 1.0f; + foreach (var spell in m_spellMods[(int)mod.op][(int)SpellModType.Pct]) + if (spell.mask & mask) + modData.ModifierValue *= 1.0f + MathFunctions.CalculatePct(1.0f, spell.value); + } + + modData.ClassIndex = (byte)eff; + + spellMod.ModifierData.Add(modData); + } + } + packet.Modifiers.Add(spellMod); + + SendPacket(packet); + } + } + + // "the bodies of template functions must be made available in a header file" + public void ApplySpellMod(uint spellId, SpellModOp op, ref T basevalue, Spell spell = null) + { + SpellInfo spellInfo = Global.SpellMgr.GetSpellInfo(spellId); + if (spellInfo == null) + return; + + float totalmul = 1.0f; + int totalflat = 0; + + // Drop charges for triggering spells instead of triggered ones + if (m_spellModTakingSpell != null) + spell = m_spellModTakingSpell; + + foreach (var mod in m_spellMods[(int)op][(int)SpellModType.Flat]) + { + // Charges can be set only for mods with auras + if (mod.ownerAura == null) + Contract.Assert(mod.charges == 0); + + if (!IsAffectedBySpellmod(spellInfo, mod, spell)) + continue; + + totalflat += mod.value; + DropModCharge(mod, spell); + } + + foreach (var mod in m_spellMods[(int)op][(int)SpellModType.Pct]) + { + // Charges can be set only for mods with auras + if (mod.ownerAura == null) + Contract.Assert(mod.charges == 0); + + if (!IsAffectedBySpellmod(spellInfo, mod, spell)) + continue; + + // skip percent mods for null basevalue (most important for spell mods with charges) + if (Convert.ToInt64(basevalue) + totalflat == 0) + continue; + + // special case (skip > 10sec spell casts for instant cast setting) + if (mod.op == SpellModOp.CastingTime && Convert.ToInt64(basevalue) >= 10000 && mod.value <= -100) + continue; + + totalmul *= 1.0f + MathFunctions.CalculatePct(1.0f, mod.value); + DropModCharge(mod, spell); + } + + basevalue = (T)Convert.ChangeType(((Convert.ToSingle(basevalue) + totalflat) * totalmul), typeof(T)); + } + bool IsAffectedBySpellmod(SpellInfo spellInfo, SpellModifier mod, Spell spell) + { + if (mod == null || spellInfo == null) + return false; + + // Mod out of charges + if (spell != null && mod.charges == -1 && !spell.m_appliedMods.Contains(mod.ownerAura)) + return false; + + // +duration to infinite duration spells making them limited + if (mod.op == SpellModOp.Duration && spellInfo.GetDuration() == -1) + return false; + + return spellInfo.IsAffectedBySpellMod(mod); + } + void DropModCharge(SpellModifier mod, Spell spell) + { + // don't handle spells with proc_event entry defined + // this is a temporary workaround, because all spellmods should be handled like that + if (Global.SpellMgr.GetSpellProcEvent(mod.spellId) != null) + return; + + if (spell != null && mod.ownerAura != null && mod.charges > 0) + { + if (--mod.charges == 0) + mod.charges = -1; + + spell.m_appliedMods.Add(mod.ownerAura); + } + } + + public void SetSpellModTakingSpell(Spell spell, bool apply) + { + if (spell == null || (m_spellModTakingSpell != null && m_spellModTakingSpell != spell)) + return; + + if (apply && spell.getState() == SpellState.Finished) + return; + + m_spellModTakingSpell = apply ? spell : null; + } + + void SendSpellModifiers() + { + SetSpellModifier flatMods = new SetSpellModifier(ServerOpcodes.SetFlatSpellModifier); + SetSpellModifier pctMods = new SetSpellModifier(ServerOpcodes.SetPctSpellModifier); + for (var i = 0; i < (int)SpellModOp.Max; ++i) + { + SpellModifierInfo flatMod = new SpellModifierInfo(); + SpellModifierInfo pctMod = new SpellModifierInfo(); + flatMod.ModIndex = pctMod.ModIndex = (byte)i; + for (byte j = 0; j < 128; ++j) + { + FlagArray128 mask = new FlagArray128(); + mask[j / 32] = 1u << (j % 32); + + SpellModifierData flatData; + SpellModifierData pctData; + + flatData.ClassIndex = j; + flatData.ModifierValue = 0.0f; + pctData.ClassIndex = j; + pctData.ModifierValue = 1.0f; + + foreach (SpellModifier mod in m_spellMods[i][(int)SpellModType.Flat]) + { + if (mod.mask & mask) + flatData.ModifierValue += mod.value; + } + + foreach (SpellModifier mod in m_spellMods[i][(int)SpellModType.Pct]) + { + if (mod.mask & mask) + pctData.ModifierValue *= 1.0f + MathFunctions.CalculatePct(1.0f, mod.value); + + } + + flatMod.ModifierData.Add(flatData); + pctMod.ModifierData.Add(pctData); + } + + flatMod.ModifierData.RemoveAll(mod => + { + return MathFunctions.fuzzyEq(mod.ModifierValue, 0.0f); + }); + + pctMod.ModifierData.RemoveAll(mod => + { + return MathFunctions.fuzzyEq(mod.ModifierValue, 1.0f); + }); + + flatMods.Modifiers.Add(flatMod); + pctMods.Modifiers.Add(pctMod); + } + + if (!flatMods.Modifiers.Empty()) + SendPacket(flatMods); + + if (!pctMods.Modifiers.Empty()) + SendPacket(pctMods); + } + + void SendSupercededSpell(uint oldSpell, uint newSpell) + { + SupercededSpells supercededSpells = new SupercededSpells(); + supercededSpells.SpellID.Add(newSpell); + supercededSpells.Superceded.Add(oldSpell); + SendPacket(supercededSpells); + } + + public void UpdateEquipSpellsAtFormChange() + { + for (byte i = 0; i < InventorySlots.BagEnd; ++i) + { + if (m_items[i] && !m_items[i].IsBroken() && CanUseAttackType(GetAttackBySlot(i, m_items[i].GetTemplate().GetInventoryType()))) + { + ApplyItemEquipSpell(m_items[i], false, true); // remove spells that not fit to form + ApplyItemEquipSpell(m_items[i], true, true); // add spells that fit form but not active + } + } + + UpdateItemSetAuras(true); + } + + void UpdateItemSetAuras(bool formChange = false) + { + // item set bonuses not dependent from item broken state + for (int setindex = 0; setindex < ItemSetEff.Count; ++setindex) + { + ItemSetEffect eff = ItemSetEff[setindex]; + if (eff == null) + continue; + + foreach (ItemSetSpellRecord itemSetSpell in eff.SetBonuses) + { + SpellInfo spellInfo = Global.SpellMgr.GetSpellInfo(itemSetSpell.SpellID); + + if (itemSetSpell.ChrSpecID != 0 && itemSetSpell.ChrSpecID != GetUInt32Value(PlayerFields.CurrentSpecId)) + ApplyEquipSpell(spellInfo, null, false, false); // item set aura is not for current spec + else + { + ApplyEquipSpell(spellInfo, null, false, formChange); // remove spells that not fit to form - removal is skipped if shapeshift condition is satisfied + ApplyEquipSpell(spellInfo, null, true, formChange); // add spells that fit form but not active + } + } + } + } + + public int GetSpellPenetrationItemMod() { return m_spellPenetrationItemMod; } + + public void RemoveArenaSpellCooldowns(bool removeActivePetCooldowns) + { + // remove cooldowns on spells that have < 10 min CD + GetSpellHistory().ResetCooldowns(p => + { + SpellInfo spellInfo = Global.SpellMgr.GetSpellInfo(p.Key); + return spellInfo.RecoveryTime < 10 * Time.Minute * Time.InMilliseconds && spellInfo.CategoryRecoveryTime < 10 * Time.Minute * Time.InMilliseconds; + }, true); + + // pet cooldowns + if (removeActivePetCooldowns) + { + Pet pet = GetPet(); + if (pet) + pet.GetSpellHistory().ResetAllCooldowns(); + } + } + + /**********************************/ + /*************Runes****************/ + /**********************************/ + public void SetRuneCooldown(byte index, uint cooldown, bool casted = false) + { + uint gracePeriod = GetRuneTimer(index); + + if (casted && IsInCombat()) + { + if (gracePeriod < 0xFFFFFFFF && cooldown > 0) + { + uint lessCd = Math.Min(2500, gracePeriod); + cooldown = (cooldown > lessCd) ? (cooldown - lessCd) : 0; + SetLastRuneGraceTimer(index, lessCd); + } + + SetRuneTimer(index, 0); + } + + m_runes.Cooldown[index] = cooldown; + m_runes.SetRuneState(index, (cooldown == 0) ? true : false); + } + + public uint GetRuneBaseCooldown() + { + float cooldown = RuneCooldowns.Base; + + var regenAura = GetAuraEffectsByType(AuraType.ModPowerRegenPercent); + foreach (var i in regenAura) + if (i.GetMiscValue() == (int)PowerType.Runes) + cooldown *= 1.0f - i.GetAmount() / 100.0f; + + // Runes cooldown are now affected by player's haste from equipment ... + float hastePct = GetRatingBonusValue(CombatRating.HasteMelee); + + // ... and some auras. + hastePct += GetTotalAuraModifier(AuraType.ModMeleeHaste); + hastePct += GetTotalAuraModifier(AuraType.ModMeleeHaste2); + hastePct += GetTotalAuraModifier(AuraType.ModMeleeHaste3); + + cooldown *= 1.0f - (hastePct / 100.0f); + + return (uint)cooldown; + } + + public void ResyncRunes() + { + ResyncRunes data = new ResyncRunes(); + data.Runes.Start = 0; + data.Runes.Count = GetRunesState(); + + for (byte i = 0; i < PlayerConst.MaxRunes; ++i) + data.Runes.Cooldowns.Add((byte)(255 - (GetRuneCooldown(i) * 51))); + + // calculate mask of recharging runes + uint regeneratedRunes = 0; + int regenIndex = 0; + while (regeneratedRunes < PlayerConst.MaxRechargingRunes && !m_runes.CooldownOrder.Empty()) + { + byte runeToRegen = m_runes.CooldownOrder[regenIndex++]; + uint runeCooldown = GetRuneCooldown(runeToRegen); + if (runeCooldown > m_regenTimer) + { + data.Runes.Start |= (byte)(1 << runeToRegen); + ++regenIndex; + } + + ++regeneratedRunes; + } + + SendPacket(data); + } + + void AddRunePower(byte index) + { + //WorldPacket data(SMSG_ADD_RUNE_POWER, 4); + //data << uint32(1 << index); // mask (0x00-0x3F probably) + //SendPacket(&data); + } + + public void InitRunes() + { + if (GetClass() != Class.Deathknight) + return; + + int runeIndex = (int)GetPowerIndex(PowerType.Runes); + if (runeIndex == (int)PowerType.Max) + return; + + m_runes = new Runes(); + m_runes.RuneState = 0; + + for (byte i = 0; i < PlayerConst.MaxRunes; ++i) + { + SetRuneCooldown(i, 0); // reset cooldowns + SetRuneTimer(i, 0xFFFFFFFF); // Reset rune flags + SetLastRuneGraceTimer(i, 0); + } + + // set a base regen timer equal to 10 sec + SetStatFloatValue(UnitFields.PowerRegenFlatModifier + runeIndex, 0.1f); + SetStatFloatValue(UnitFields.PowerRegenInterruptedFlatModifier + runeIndex, 0.1f); + } + + public void UpdateAllRunesRegen() + { + if (GetClass() != Class.Deathknight) + return; + + int runeIndex = (int)GetPowerIndex(PowerType.Runes); + if (runeIndex == (int)PowerType.Max) + return; + + uint cooldown = GetRuneBaseCooldown(); + SetStatFloatValue(UnitFields.PowerRegenFlatModifier + runeIndex, (float)(1 * Time.InMilliseconds) / cooldown); + SetStatFloatValue(UnitFields.PowerRegenInterruptedFlatModifier + runeIndex, (float)(1 * Time.InMilliseconds) / cooldown); + } + + public byte GetRunesState() { return m_runes.RuneState; } + public uint GetRuneCooldown(byte index) { return m_runes.Cooldown[index]; } + + public uint GetRuneTimer(byte index) { return m_runeGraceCooldown[index]; } + public void SetRuneTimer(byte index, uint timer) { m_runeGraceCooldown[index] = timer; } + public uint GetLastRuneGraceTimer(byte index) { return m_lastRuneGraceTimers[index]; } + public void SetLastRuneGraceTimer(byte index, uint timer) { m_lastRuneGraceTimers[index] = timer; } + + public bool CanNoReagentCast(SpellInfo spellInfo) + { + // don't take reagents for spells with SPELL_ATTR5_NO_REAGENT_WHILE_PREP + if (spellInfo.HasAttribute(SpellAttr5.NoReagentWhilePrep) && + HasFlag(UnitFields.Flags, UnitFlags.Preparation)) + return true; + + // Check no reagent use mask + FlagArray128 noReagentMask = new FlagArray128(); + noReagentMask[0] = GetUInt32Value(PlayerFields.NoReagentCost1); + noReagentMask[1] = GetUInt32Value(PlayerFields.NoReagentCost1 + 1); + noReagentMask[2] = GetUInt32Value(PlayerFields.NoReagentCost1 + 2); + noReagentMask[3] = GetUInt32Value(PlayerFields.NoReagentCost1 + 3); + if (spellInfo.SpellFamilyFlags & noReagentMask) + return true; + + return false; + } + + public void CastItemCombatSpell(Unit target, WeaponAttackType attType, ProcFlags procVictim, ProcFlagsExLegacy procEx, Item item, ItemTemplate proto) + { + // Can do effect if any damage done to target + if (procVictim.HasAnyFlag(ProcFlags.TakenDamage)) + { + for (byte i = 0; i < proto.Effects.Count; ++i) + { + var spellData = proto.Effects[i]; + + // no spell + if (spellData.SpellID == 0) + continue; + + // wrong triggering type + if (spellData.Trigger != ItemSpelltriggerType.ChanceOnHit) + continue; + + SpellInfo spellInfo = Global.SpellMgr.GetSpellInfo(spellData.SpellID); + if (spellInfo == null) + { + Log.outError(LogFilter.Player, "WORLD: unknown Item spellid {0}", spellData.SpellID); + continue; + } + + // not allow proc extra attack spell at extra attack + if (m_extraAttacks != 0 && spellInfo.HasEffect(SpellEffectName.AddExtraAttacks)) + return; + + float chance = spellInfo.ProcChance; + + if (proto.SpellPPMRate != 0) + { + uint WeaponSpeed = GetBaseAttackTime(attType); + chance = GetPPMProcChance(WeaponSpeed, proto.SpellPPMRate, spellInfo); + } + else if (chance > 100.0f) + chance = GetWeaponProcChance(); + + if (RandomHelper.randChance(chance)) + CastSpell(target, spellInfo.Id, true, item); + } + } + + // item combat enchantments + for (byte e_slot = 0; e_slot < (byte)EnchantmentSlot.Max; ++e_slot) + { + uint enchant_id = item.GetEnchantmentId((EnchantmentSlot)e_slot); + SpellItemEnchantmentRecord pEnchant = CliDB.SpellItemEnchantmentStorage.LookupByKey(enchant_id); + if (pEnchant == null) + continue; + + for (byte s = 0; s < ItemConst.MaxItemEnchantmentEffects; ++s) + { + if (pEnchant.Effect[s] != ItemEnchantmentType.CombatSpell) + continue; + + SpellEnchantProcEntry entry = Global.SpellMgr.GetSpellEnchantProcEvent(enchant_id); + + if (entry != null && entry.procEx != 0) + { + // Check hit/crit/dodge/parry requirement + if ((entry.procEx & procEx) == 0) + continue; + } + else + { + // Can do effect if any damage done to target + if (!Convert.ToBoolean(procVictim & ProcFlags.TakenDamage)) + continue; + } + + SpellInfo spellInfo = Global.SpellMgr.GetSpellInfo(pEnchant.EffectSpellID[s]); + if (spellInfo == null) + { + Log.outError(LogFilter.Player, "Player.CastItemCombatSpell(GUID: {0}, name: {1}, enchant: {2}): unknown spell {3} is casted, ignoring...", + GetGUID().ToString(), GetName(), enchant_id, pEnchant.EffectSpellID[s]); + continue; + } + + float chance = pEnchant.EffectPointsMin[s] != 0 ? pEnchant.EffectPointsMin[s] : GetWeaponProcChance(); + + if (entry != null) + { + if (entry.PPMChance != 0) + chance = GetPPMProcChance(proto.GetDelay(), entry.PPMChance, spellInfo); + else if (entry.customChance != 0) + chance = entry.customChance; + } + + // Apply spell mods + ApplySpellMod(pEnchant.EffectSpellID[s], SpellModOp.ChanceOfSuccess, ref chance); + + // Shiv has 100% chance to apply the poison + if (FindCurrentSpellBySpellId(5938) != null && e_slot == (byte)EnchantmentSlot.Temp) + chance = 100.0f; + + if (RandomHelper.randChance(chance)) + { + if (spellInfo.IsPositive()) + CastSpell(this, spellInfo, true, item); + else + CastSpell(target, spellInfo, true, item); + } + } + } + } + + public void CastItemCombatSpell(Unit target, WeaponAttackType attType, ProcFlags procVictim, ProcFlagsExLegacy procEx) + { + if (target == null || !target.IsAlive() || target == this) + return; + + for (byte i = EquipmentSlot.Start; i < EquipmentSlot.End; ++i) + { + // If usable, try to cast item spell + Item item = GetItemByPos(InventorySlots.Bag0, i); + if (item != null) + if (!item.IsBroken() && CanUseAttackType(attType)) + { + ItemTemplate proto = item.GetTemplate(); + if (proto != null) + { + // Additional check for weapons + if (proto.GetClass() == ItemClass.Weapon) + { + // offhand item cannot proc from main hand hit etc + byte slot; + switch (attType) + { + case WeaponAttackType.BaseAttack: + case WeaponAttackType.RangedAttack: + slot = EquipmentSlot.MainHand; + break; + case WeaponAttackType.OffAttack: + slot = EquipmentSlot.OffHand; + break; + default: + slot = EquipmentSlot.End; + break; + } + if (slot != i) + continue; + // Check if item is useable (forms or disarm) + if (attType == WeaponAttackType.BaseAttack) + if (!IsUseEquipedWeapon(true) && !IsInFeralForm()) + continue; + } + CastItemCombatSpell(target, attType, procVictim, procEx, item, proto); + } + } + } + } + + float GetWeaponProcChance() + { + // normalized proc chance for weapon attack speed + // (odd formula...) + if (isAttackReady(WeaponAttackType.BaseAttack)) + return (GetBaseAttackTime(WeaponAttackType.BaseAttack) * 1.8f / 1000.0f); + else if (haveOffhandWeapon() && isAttackReady(WeaponAttackType.OffAttack)) + return (GetBaseAttackTime(WeaponAttackType.OffAttack) * 1.6f / 1000.0f); + return 0; + } + + public void ResetSpells(bool myClassOnly = false) + { + // not need after this call + if (HasAtLoginFlag(AtLoginFlags.ResetSpells)) + RemoveAtLoginFlag(AtLoginFlags.ResetSpells, true); + + // make full copy of map (spells removed and marked as deleted at another spell remove + // and we can't use original map for safe iterative with visit each spell at loop end + var smap = GetSpellMap(); + + uint family; + + if (myClassOnly) + { + ChrClassesRecord clsEntry = CliDB.ChrClassesStorage.LookupByKey(GetClass()); + if (clsEntry == null) + return; + family = clsEntry.SpellClassSet; + + foreach (var spellId in smap.Keys) + { + SpellInfo spellInfo = Global.SpellMgr.GetSpellInfo(spellId); + if (spellInfo == null) + continue; + + // skip server-side/triggered spells + if (spellInfo.SpellLevel == 0) + continue; + + // skip wrong class/race skills + if (!IsSpellFitByClassAndRace(spellInfo.Id)) + continue; + + // skip other spell families + if ((uint)spellInfo.SpellFamilyName != family) + continue; + + // skip broken spells + if (!Global.SpellMgr.IsSpellValid(spellInfo, this, false)) + continue; + } + } + else + foreach (var spellId in smap.Keys) + RemoveSpell(spellId, false, false); // only iter.first can be accessed, object by iter.second can be deleted already + + LearnDefaultSkills(); + LearnCustomSpells(); + LearnQuestRewardedSpells(); + } + } + + public class PlayerSpell + { + public PlayerSpellState State; + public bool Active; + public bool Dependent; + public bool Disabled; + } +} diff --git a/Game/Entities/Player/Player.Talents.cs b/Game/Entities/Player/Player.Talents.cs new file mode 100644 index 000000000..82cf37977 --- /dev/null +++ b/Game/Entities/Player/Player.Talents.cs @@ -0,0 +1,555 @@ +/* + * Copyright (C) 2012-2017 CypherCore + * + * 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, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +using Framework.Constants; +using Framework.Database; +using Game.DataStorage; +using Game.Network.Packets; +using Game.Spells; +using System.Collections.Generic; + +namespace Game.Entities +{ + public partial class Player + { + public void InitTalentForLevel() + { + uint level = getLevel(); + // talents base at level diff (talents = level - 9 but some can be used already) + if (level < PlayerConst.MinSpecializationLevel) + ResetTalentSpecialization(); + + uint talentTiers = CalculateTalentsTiers(); + if (level < 15) + { + // Remove all talent points + ResetTalents(true); + } + else + { + if (!GetSession().HasPermission(RBACPermissions.SkipCheckMoreTalentsThanAllowed)) + { + for (uint t = talentTiers; t < PlayerConst.MaxTalentTiers; ++t) + for (uint c = 0; c < PlayerConst.MaxTalentColumns; ++c) + foreach (TalentRecord talent in Global.DB2Mgr.GetTalentsByPosition(GetClass(), t, c)) + RemoveTalent(talent); + } + } + + SetUInt32Value(PlayerFields.MaxTalentTiers, talentTiers); + + if (!GetSession().PlayerLoading()) + SendTalentsInfoData(); // update at client + } + + public bool AddTalent(TalentRecord talent, byte spec, bool learning) + { + SpellInfo spellInfo = Global.SpellMgr.GetSpellInfo(talent.SpellID); + if (spellInfo == null) + { + Log.outError(LogFilter.Spells, "Player.AddTalent: Spell (ID: {0}) does not exist.", talent.SpellID); + return false; + } + + if (!Global.SpellMgr.IsSpellValid(spellInfo, this, false)) + { + Log.outError(LogFilter.Spells, "Player.AddTalent: Spell (ID: {0}) is invalid", talent.SpellID); + return false; + } + + if (talent.OverridesSpellID != 0) + AddOverrideSpell(talent.OverridesSpellID, talent.SpellID); + + if (GetTalentMap(spec).ContainsKey(talent.Id)) + GetTalentMap(spec)[talent.Id] = PlayerSpellState.Unchanged; + else + GetTalentMap(spec)[talent.Id] = learning ? PlayerSpellState.New : PlayerSpellState.Unchanged; + + return true; + } + + public void RemoveTalent(TalentRecord talent) + { + SpellInfo spellInfo = Global.SpellMgr.GetSpellInfo(talent.SpellID); + if (spellInfo == null) + return; + + RemoveSpell(talent.SpellID, true); + + // search for spells that the talent teaches and unlearn them + foreach (SpellEffectInfo effect in spellInfo.GetEffectsForDifficulty(Difficulty.None)) + if (effect != null && effect.TriggerSpell > 0 && effect.Effect == SpellEffectName.LearnSpell) + RemoveSpell(effect.TriggerSpell, true); + + if (talent.OverridesSpellID != 0) + RemoveOverrideSpell(talent.OverridesSpellID, talent.SpellID); + + var talentMap = GetTalentMap(GetActiveTalentGroup()); + // if this talent rank can be found in the PlayerTalentMap, mark the talent as removed so it gets deleted + if (talentMap.ContainsKey(talent.Id)) + talentMap[talent.Id] = PlayerSpellState.Removed; + } + + public TalentLearnResult LearnTalent(uint talentId, ref int spellOnCooldown) + { + if (IsInCombat()) + return TalentLearnResult.FailedAffectingCombat; + + if (IsDead() || GetMap().IsBattlegroundOrArena()) + return TalentLearnResult.FailedCantDoThatRightNow; + + if (GetUInt32Value(PlayerFields.CurrentSpecId) == 0) + return TalentLearnResult.FailedNoPrimaryTreeSelected; + + TalentRecord talentInfo = CliDB.TalentStorage.LookupByKey(talentId); + if (talentInfo == null) + return TalentLearnResult.FailedUnknown; + + if (talentInfo.SpecID != 0 && talentInfo.SpecID != GetUInt32Value(PlayerFields.CurrentSpecId)) + return TalentLearnResult.FailedUnknown; + + // prevent learn talent for different class (cheating) + if (talentInfo.ClassID != (byte)GetClass()) + return TalentLearnResult.FailedUnknown; + + // check if we have enough talent points + if (talentInfo.TierID >= GetUInt32Value(PlayerFields.MaxTalentTiers)) + return TalentLearnResult.FailedUnknown; + + // TODO: prevent changing talents that are on cooldown + + // Check if there is a different talent for us to learn in selected slot + // Example situation: + // Warrior talent row 2 slot 0 + // Talent.dbc has an entry for each specialization + // but only 2 out of 3 have SpecID != 0 + // We need to make sure that if player is in one of these defined specs he will not learn the other choice + TalentRecord bestSlotMatch = null; + foreach (TalentRecord talent in Global.DB2Mgr.GetTalentsByPosition(GetClass(), talentInfo.TierID, talentInfo.ColumnIndex)) + { + if (talent.SpecID == 0) + bestSlotMatch = talent; + else if (talent.SpecID == GetUInt32Value(PlayerFields.CurrentSpecId)) + { + bestSlotMatch = talent; + break; + } + } + + if (talentInfo != bestSlotMatch) + return TalentLearnResult.FailedUnknown; + + // Check if player doesn't have any talent in current tier + for (uint c = 0; c < PlayerConst.MaxTalentColumns; ++c) + { + foreach (TalentRecord talent in Global.DB2Mgr.GetTalentsByPosition(GetClass(), talentInfo.TierID, c)) + { + //Todo test me + if (talent.SpecID != GetUInt32Value(PlayerFields.CurrentSpecId)) + continue; + + if (HasTalent(talent.Id, GetActiveTalentGroup()) && !HasFlag(PlayerFields.Flags, PlayerFlags.Resting) && HasFlag(UnitFields.Flags, UnitFlags.ImmuneToNpc)) + return TalentLearnResult.FailedRestArea; + + if (GetSpellHistory().HasCooldown(talent.SpellID)) + { + spellOnCooldown = (int)talent.SpellID; + return TalentLearnResult.FailedCantRemoveTalent; + } + + RemoveTalent(talent); + } + } + + // spell not set in talent.dbc + uint spellid = talentInfo.SpellID; + if (spellid == 0) + { + Log.outError(LogFilter.Player, "Player.LearnTalent: Talent.dbc has no spellInfo for talent: {0} (spell id = 0)", talentId); + return TalentLearnResult.FailedUnknown; + } + + // already known + if (HasTalent(talentId, GetActiveTalentGroup()) || HasSpell(spellid)) + return TalentLearnResult.FailedUnknown; + + if (!AddTalent(talentInfo, GetActiveTalentGroup(), true)) + return TalentLearnResult.FailedUnknown; + + LearnSpell(spellid, false); + + Log.outDebug(LogFilter.Misc, "Player.LearnTalent: TalentID: {0} Spell: {1} Group: {2}", talentId, spellid, GetActiveTalentGroup()); + + return TalentLearnResult.LearnOk; + } + + public void ResetTalentSpecialization() + { + // Reset only talents that have different spells for each spec + Class class_ = GetClass(); + for (uint t = 0; t < PlayerConst.MaxTalentTiers; ++t) + { + for (uint c = 0; c < PlayerConst.MaxTalentColumns; ++c) + { + if (Global.DB2Mgr.GetTalentsByPosition(class_, t, c).Count > 1) + { + foreach (TalentRecord talent in Global.DB2Mgr.GetTalentsByPosition(class_, t, c)) + RemoveTalent(talent); + } + } + } + + RemoveSpecializationSpells(); + + ChrSpecializationRecord defaultSpec = Global.DB2Mgr.GetDefaultChrSpecializationForClass(GetClass()); + SetPrimarySpecialization(defaultSpec.Id); + SetActiveTalentGroup(defaultSpec.OrderIndex); + SetUInt32Value(PlayerFields.CurrentSpecId, defaultSpec.Id); + + LearnSpecializationSpells(); + + SendTalentsInfoData(); + UpdateItemSetAuras(false); + } + + bool HasTalent(uint talnetId, byte group) + { + return GetTalentMap(group).ContainsKey(talnetId) && GetTalentMap(group)[talnetId] != PlayerSpellState.Removed; + } + + uint GetTalentResetCost() { return _specializationInfo.ResetTalentsCost; } + void SetTalentResetCost(uint cost) { _specializationInfo.ResetTalentsCost = cost; } + long GetTalentResetTime() { return _specializationInfo.ResetTalentsTime; } + void SetTalentResetTime(long time_) { _specializationInfo.ResetTalentsTime = time_; } + uint GetPrimarySpecialization() { return _specializationInfo.PrimarySpecialization; } + void SetPrimarySpecialization(uint spec) { _specializationInfo.PrimarySpecialization = spec; } + public byte GetActiveTalentGroup() { return _specializationInfo.ActiveGroup; } + void SetActiveTalentGroup(byte group) { _specializationInfo.ActiveGroup = group; } + + // Loot Spec + public void SetLootSpecId(uint id) { SetUInt32Value(PlayerFields.LootSpecId, id); } + uint GetLootSpecId() { return GetUInt32Value(PlayerFields.LootSpecId); } + + public uint GetDefaultSpecId() + { + return Global.DB2Mgr.GetDefaultChrSpecializationForClass(GetClass()).Id; + } + + public void ActivateTalentGroup(ChrSpecializationRecord spec) + { + if (GetActiveTalentGroup() == spec.OrderIndex) + return; + + if (IsNonMeleeSpellCast(false)) + InterruptNonMeleeSpells(false); + + SQLTransaction trans = new SQLTransaction(); + _SaveActions(trans); + DB.Characters.CommitTransaction(trans); + + // TO-DO: We need more research to know what happens with warlock's reagent + Pet pet = GetPet(); + if (pet) + RemovePet(pet, PetSaveMode.NotInSlot); + + ClearAllReactives(); + UnsummonAllTotems(); + ExitVehicle(); + RemoveAllControlled(); + + // Let client clear his current Actions + SendActionButtons(2); + foreach (var talentInfo in CliDB.TalentStorage.Values) + { + // unlearn only talents for character class + // some spell learned by one class as normal spells or know at creation but another class learn it as talent, + // to prevent unexpected lost normal learned spell skip another class talents + if (talentInfo.ClassID != (int)GetClass()) + continue; + + if (talentInfo.SpellID == 0) + continue; + + SpellInfo spellInfo = Global.SpellMgr.GetSpellInfo(talentInfo.SpellID); + if (spellInfo == null) + continue; + + RemoveSpell(talentInfo.SpellID, true); + + // search for spells that the talent teaches and unlearn them + foreach (SpellEffectInfo effect in spellInfo.GetEffectsForDifficulty(Difficulty.None)) + if (effect != null && effect.TriggerSpell > 0 && effect.Effect == SpellEffectName.LearnSpell) + RemoveSpell(effect.TriggerSpell, true); + + if (talentInfo.OverridesSpellID != 0) + RemoveOverrideSpell(talentInfo.OverridesSpellID, talentInfo.SpellID); + } + + // Remove spec specific spells + RemoveSpecializationSpells(); + + foreach (uint glyphId in GetGlyphs(GetActiveTalentGroup())) + RemoveAurasDueToSpell(CliDB.GlyphPropertiesStorage.LookupByKey(glyphId).SpellID); + + SetActiveTalentGroup(spec.OrderIndex); + SetUInt32Value(PlayerFields.CurrentSpecId, spec.Id); + if (GetPrimarySpecialization() == 0) + SetPrimarySpecialization(spec.Id); + + foreach (var talentInfo in CliDB.TalentStorage.Values) + { + // learn only talents for character class + if (talentInfo.ClassID != (int)GetClass()) + continue; + + if (talentInfo.SpellID == 0) + continue; + + if (HasTalent(talentInfo.SpellID, GetActiveTalentGroup())) + { + LearnSpell(talentInfo.SpellID, false); // add the talent to the PlayerSpellMap + if (talentInfo.OverridesSpellID != 0) + AddOverrideSpell(talentInfo.OverridesSpellID, talentInfo.SpellID); + } + } + + LearnSpecializationSpells(); + + if (CanUseMastery()) + { + for (uint i = 0; i < PlayerConst.MaxMasterySpells; ++i) + { + uint mastery = spec.MasterySpellID[i]; + if (mastery != 0) + LearnSpell(mastery, false); + } + } + + InitTalentForLevel(); + + PreparedStatement stmt = DB.Characters.GetPreparedStatement(CharStatements.SEL_CHARACTER_ACTIONS_SPEC); + stmt.AddValue(0, GetGUID().GetCounter()); + stmt.AddValue(1, GetActiveTalentGroup()); + _LoadActions(DB.Characters.Query(stmt)); + + SendActionButtons(1); + + UpdateDisplayPower(); + PowerType pw = getPowerType(); + if (pw != PowerType.Mana) + SetPower(PowerType.Mana, 0); // Mana must be 0 even if it isn't the active power type. + + SetPower(pw, 0); + UpdateItemSetAuras(false); + + // update visible transmog + for (byte i = EquipmentSlot.Start; i < EquipmentSlot.End; ++i) + { + Item equippedItem = GetItemByPos(InventorySlots.Bag0, i); + if (equippedItem) + SetVisibleItemSlot(i, equippedItem); + } + + foreach (uint glyphId in GetGlyphs(spec.OrderIndex)) + CastSpell(this, CliDB.GlyphPropertiesStorage.LookupByKey(glyphId).SpellID, true); + + ActiveGlyphs activeGlyphs = new ActiveGlyphs(); + foreach (uint glyphId in GetGlyphs(spec.OrderIndex)) + { + List bindableSpells = Global.DB2Mgr.GetGlyphBindableSpells(glyphId); + foreach (uint bindableSpell in bindableSpells) + if (HasSpell(bindableSpell) && !m_overrideSpells.ContainsKey(bindableSpell)) + activeGlyphs.Glyphs.Add(new GlyphBinding(bindableSpell, (ushort)glyphId)); + } + + activeGlyphs.IsFullUpdate = true; + SendPacket(activeGlyphs); + } + + public Dictionary GetTalentMap(uint spec) { return _specializationInfo.Talents[spec]; } + public List GetGlyphs(byte spec) { return _specializationInfo.Glyphs[spec]; } + + public uint GetNextResetTalentsCost() + { + // The first time reset costs 1 gold + if (GetTalentResetCost() < 1 * MoneyConstants.Gold) + return 1 * MoneyConstants.Gold; + // then 5 gold + else if (GetTalentResetCost() < 5 * MoneyConstants.Gold) + return 5 * MoneyConstants.Gold; + // After that it increases in increments of 5 gold + else if (GetTalentResetCost() < 10 * MoneyConstants.Gold) + return 10 * MoneyConstants.Gold; + else + { + ulong months = (ulong)(Global.WorldMgr.GetGameTime() - GetTalentResetTime()) / Time.Month; + if (months > 0) + { + // This cost will be reduced by a rate of 5 gold per month + uint new_cost = (uint)(GetTalentResetCost() - 5 * MoneyConstants.Gold * months); + // to a minimum of 10 gold. + return new_cost < 10 * MoneyConstants.Gold ? 10 * MoneyConstants.Gold : new_cost; + } + else + { + // After that it increases in increments of 5 gold + uint new_cost = GetTalentResetCost() + 5 * MoneyConstants.Gold; + // until it hits a cap of 50 gold. + if (new_cost > 50 * MoneyConstants.Gold) + new_cost = 50 * MoneyConstants.Gold; + return new_cost; + } + } + } + + public bool ResetTalents(bool noCost = false) + { + Global.ScriptMgr.OnPlayerTalentsReset(this, noCost); + + // not need after this call + if (HasAtLoginFlag(AtLoginFlags.ResetTalents)) + RemoveAtLoginFlag(AtLoginFlags.ResetTalents, true); + + uint cost = 0; + if (!noCost && !WorldConfig.GetBoolValue(WorldCfg.NoResetTalentCost)) + { + cost = GetNextResetTalentsCost(); + + if (!HasEnoughMoney(cost)) + { + SendBuyError(BuyResult.NotEnoughtMoney, null, 0); + return false; + } + } + + RemovePet(null, PetSaveMode.NotInSlot, true); + + foreach (var talentInfo in CliDB.TalentStorage.Values) + { + // unlearn only talents for character class + // some spell learned by one class as normal spells or know at creation but another class learn it as talent, + // to prevent unexpected lost normal learned spell skip another class talents + if (talentInfo.ClassID != (uint)GetClass()) + continue; + + // skip non-existant talent ranks + if (talentInfo.SpellID == 0) + continue; + + RemoveTalent(talentInfo); + } + + SQLTransaction trans = new SQLTransaction(); + _SaveTalents(trans); + _SaveSpells(trans); + DB.Characters.CommitTransaction(trans); + + if (!noCost) + { + ModifyMoney(-cost); + UpdateCriteria(CriteriaTypes.GoldSpentForTalents, cost); + UpdateCriteria(CriteriaTypes.NumberOfTalentResets, 1); + + SetTalentResetCost(cost); + SetTalentResetTime(Time.UnixTime); + } + + return true; + } + + public void SendTalentsInfoData() + { + UpdateTalentData packet = new UpdateTalentData(); + packet.Info.PrimarySpecialization = GetPrimarySpecialization(); + packet.Info.ActiveGroup = GetActiveTalentGroup(); + + for (byte i = 0; i < PlayerConst.MaxSpecializations; ++i) + { + ChrSpecializationRecord spec = Global.DB2Mgr.GetChrSpecializationByIndex(GetClass(), i); + if (spec == null) + continue; + + var talents = GetTalentMap(i); + + + UpdateTalentData.TalentGroupInfo groupInfoPkt = new UpdateTalentData.TalentGroupInfo(); + groupInfoPkt.SpecID = spec.Id; + + foreach (var pair in talents) + { + if (pair.Value == PlayerSpellState.Removed) + continue; + + TalentRecord talentInfo = CliDB.TalentStorage.LookupByKey(pair.Key); + if (talentInfo == null) + { + Log.outError(LogFilter.Player, "Player {0} has unknown talent id: {1}", GetName(), pair.Key); + continue; + } + + if (talentInfo.ClassID != (uint)GetClass()) + continue; + + SpellInfo spellEntry = Global.SpellMgr.GetSpellInfo(talentInfo.SpellID); + if (spellEntry == null) + { + Log.outError(LogFilter.Player, "Player {0} has unknown talent spell: {1}", GetName(), talentInfo.SpellID); + continue; + } + + groupInfoPkt.TalentIDs.Add((ushort)pair.Key); + } + + packet.Info.TalentGroups.Add(groupInfoPkt); + } + + SendPacket(packet); + } + + public void SendRespecWipeConfirm(ObjectGuid guid, uint cost) + { + RespecWipeConfirm respecWipeConfirm = new RespecWipeConfirm(); + respecWipeConfirm.RespecMaster = guid; + respecWipeConfirm.Cost = cost; + respecWipeConfirm.RespecType = SpecResetType.Talents; + SendPacket(respecWipeConfirm); + } + + uint CalculateTalentsTiers() + { + uint[] rowLevels = new uint[0]; + switch (GetClass()) + { + case Class.Deathknight: + rowLevels = new uint[] { 57, 58, 59, 60, 75, 90, 100 }; + break; + case Class.DemonHunter: + rowLevels = new uint[] { 99, 100, 102, 104, 106, 108, 110 }; + break; + default: + rowLevels = new uint[] { 15, 30, 45, 60, 75, 90, 100 }; + break; + } + + for (uint i = PlayerConst.MaxTalentTiers; i != 0; --i) + if (getLevel() >= rowLevels[i - 1]) + return i; + + return 0; + } + } +} diff --git a/Game/Entities/Player/Player.cs b/Game/Entities/Player/Player.cs new file mode 100644 index 000000000..11b2d0af8 --- /dev/null +++ b/Game/Entities/Player/Player.cs @@ -0,0 +1,7411 @@ +/* + * Copyright (C) 2012-2017 CypherCore + * + * 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, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +using Framework.Constants; +using Framework.Database; +using Game.Achievements; +using Game.AI; +using Game.Arenas; +using Game.BattleFields; +using Game.BattleGrounds; +using Game.Chat; +using Game.DataStorage; +using Game.Garrisons; +using Game.Groups; +using Game.Guilds; +using Game.Loots; +using Game.Mails; +using Game.Maps; +using Game.Misc; +using Game.Network; +using Game.Network.Packets; +using Game.PvP; +using Game.Spells; +using System; +using System.Collections.Generic; +using System.Diagnostics.Contracts; +using System.Linq; + +namespace Game.Entities +{ + public partial class Player : Unit + { + public Player(WorldSession session) : base(true) + { + objectTypeMask |= TypeMask.Player; + objectTypeId = TypeId.Player; + + valuesCount = (int)PlayerFields.End; + _dynamicValuesCount = (int)PlayerDynamicFields.End; + Session = session; + + // players always accept + if (!GetSession().HasPermission(RBACPermissions.CanFilterWhispers)) + SetAcceptWhispers(true); + + m_zoneUpdateId = 0xffffffff; + m_nextSave = WorldConfig.GetUIntValue(WorldCfg.IntervalSave); + + SetGroupInvite(null); + + atLoginFlags = AtLoginFlags.None; + PlayerTalkClass = new PlayerMenu(session); + m_currentBuybackSlot = InventorySlots.BuyBackStart; + + // Init rune flags + for (byte i = 0; i < PlayerConst.MaxRunes; ++i) + { + SetRuneTimer(i, 0xFFFFFFFF); + SetLastRuneGraceTimer(i, 0); + } + + for (byte i = 0; i < (int)MirrorTimerType.Max; i++) + m_MirrorTimer[i] = -1; + + m_logintime = Time.UnixTime; + m_Last_tick = m_logintime; + + for (byte i = 0; i < (int)Difficulty.Max; ++i) + m_boundInstances[i] = new Dictionary(); + + m_dungeonDifficulty = Difficulty.Normal; + m_raidDifficulty = Difficulty.NormalRaid; + m_legacyRaidDifficulty = Difficulty.Raid10N; + m_prevMapDifficulty = Difficulty.NormalRaid; + m_InstanceValid = true; + + _specializationInfo = new SpecializationInfo(); + + for (byte i = 0; i < (byte)BaseModGroup.End; ++i) + m_auraBaseMod[i] = new float[] { 0.0f, 1.0f }; + + for (var i = 0; i < (int)SpellModOp.Max; ++i) + { + m_spellMods[i] = new List[(int)SpellModType.End]; + + for (var c = 0; c < (int)SpellModType.End; ++c) + m_spellMods[i][c] = new List(); + } + + // Honor System + m_lastHonorUpdateTime = Time.UnixTime; + + m_unitMovedByMe = this; + m_playerMovingMe = this; + seerView = this; + + m_isActive = true; + m_ControlledByPlayer = true; + + Global.WorldMgr.IncreasePlayerCount(); + + _cinematicMgr = new CinematicManager(this); + + m_achievementSys = new PlayerAchievementMgr(this); + reputationMgr = new ReputationMgr(this); + m_sceneMgr = new SceneMgr(this); + + m_bgBattlegroundQueueID[0] = new BgBattlegroundQueueID_Rec(); + m_bgBattlegroundQueueID[1] = new BgBattlegroundQueueID_Rec(); + + m_bgData = new BGData(); + + _restMgr = new RestMgr(this); + } + + public override void Dispose() + { + // Note: buy back item already deleted from DB when player was saved + for (byte i = 0; i < (int)PlayerSlots.Count; ++i) + { + if (m_items[i] != null) + m_items[i].Dispose(); + } + + m_spells.Clear(); + _specializationInfo = null; + m_mail.Clear(); + + foreach (var item in mMitems.Values) + item.Dispose(); + + PlayerTalkClass.ClearMenus(); + ItemSetEff.Clear(); + + _declinedname = null; + m_runes = null; + m_achievementSys = null; + reputationMgr = null; + + _cinematicMgr.Dispose(); + + for (byte i = 0; i < SharedConst.VoidStorageMaxSlot; ++i) + _voidStorageItems[i] = null; + + ClearResurrectRequestData(); + + Global.WorldMgr.DecreasePlayerCount(); + + base.Dispose(); + } + + //Core + public bool Create(ulong guidlow, CharacterCreateInfo createInfo) + { + _Create(ObjectGuid.Create(HighGuid.Player, guidlow)); + + SetName(createInfo.Name); + + PlayerInfo info = Global.ObjectMgr.GetPlayerInfo(createInfo.RaceId, createInfo.ClassId); + if (info == null) + { + Log.outError(LogFilter.Player, "PlayerCreate: Possible hacking-attempt: Account {0} tried creating a character named '{1}' with an invalid race/class pair ({2}/{3}) - refusing to do so.", + GetSession().GetAccountId(), GetName(), createInfo.RaceId, createInfo.ClassId); + return false; + } + + Relocate(info.PositionX, info.PositionY, info.PositionZ, info.Orientation); + + var cEntry = CliDB.ChrClassesStorage.LookupByKey(createInfo.ClassId); + if (cEntry == null) + { + Log.outError(LogFilter.Player, "PlayerCreate: Possible hacking-attempt: Account {0} tried creating a character named '{1}' with an invalid character class ({2}) - refusing to do so (wrong DBC-files?)", + GetSession().GetAccountId(), GetName(), createInfo.ClassId); + return false; + } + + if (!ValidateAppearance(createInfo.RaceId, createInfo.ClassId, createInfo.Sex, createInfo.HairStyle, createInfo.HairColor, createInfo.Face, createInfo.FacialHairStyle, createInfo.Skin, createInfo.CustomDisplay, true)) + { + Log.outError(LogFilter.Player, "Player.Create: Possible hacking-attempt: Account {0} tried creating a character named '{1}' with invalid appearance attributes - refusing to do so", + GetSession().GetAccountId(), GetName()); + return false; + } + + SetMap(Global.MapMgr.CreateMap(info.MapId, this)); + + int powertype = (int)cEntry.PowerType; + + SetObjectScale(1.0f); + + SetFactionForRace(createInfo.RaceId); + + if (!IsValidGender(createInfo.Sex)) + { + Log.outError(LogFilter.Player, "Player:Create: Possible hacking-attempt: Account {0} tried creating a character named '{1}' with an invalid gender ({2}) - refusing to do so", + GetSession().GetAccountId(), GetName(), createInfo.Sex); + return false; + } + + SetByteValue(UnitFields.Bytes0, 0, (byte)createInfo.RaceId); + SetByteValue(UnitFields.Bytes0, 1, (byte)createInfo.ClassId); + SetByteValue(UnitFields.Bytes0, 3, (byte)createInfo.Sex); + SetUInt32Value(UnitFields.DisplayPower, (uint)powertype); + InitDisplayIds(); + if ((RealmType)WorldConfig.GetIntValue(WorldCfg.GameType) == RealmType.PVP || (RealmType)WorldConfig.GetIntValue(WorldCfg.GameType) == RealmType.RPPVP) + { + SetByteFlag(UnitFields.Bytes2, UnitBytes2Offsets.PvpFlag, UnitBytes2Flags.PvP); + SetFlag(UnitFields.Flags, UnitFlags.PvpAttackable); + } + + SetFlag(UnitFields.Flags2, UnitFlags2.RegeneratePower); + SetFloatValue(UnitFields.HoverHeight, 1.0f); // default for players in 3.0.3 + + SetUInt32Value(PlayerFields.WatchedFactionIndex, 0xFFFFFFFF); // -1 is default value + + SetByteValue(PlayerFields.Bytes, PlayerFieldOffsets.BytesOffsetSkinId, createInfo.Skin); + SetByteValue(PlayerFields.Bytes, PlayerFieldOffsets.BytesOffsetFaceId, createInfo.Face); + SetByteValue(PlayerFields.Bytes, PlayerFieldOffsets.BytesOffsetHairStyleId, createInfo.HairStyle); + SetByteValue(PlayerFields.Bytes, PlayerFieldOffsets.BytesOffsetHairColorId, createInfo.HairColor); + SetByteValue(PlayerFields.Bytes2, PlayerFieldOffsets.Bytes2OffsetFacialStyle, createInfo.FacialHairStyle); + for (byte i = 0; i < PlayerConst.CustomDisplaySize; ++i) + SetByteValue(PlayerFields.Bytes2, (byte)(PlayerFieldOffsets.Bytes2OffsetCustomDisplayOption + i), createInfo.CustomDisplay[i]); + SetUInt32Value(PlayerFields.RestInfo + PlayerFieldOffsets.RestStateXp, (uint)((GetSession().IsARecruiter() || GetSession().GetRecruiterId() != 0) ? PlayerRestState.RAFLinked : PlayerRestState.NotRAFLinked)); + SetUInt32Value(PlayerFields.RestInfo + PlayerFieldOffsets.RestStateHonor, (uint)PlayerRestState.NotRAFLinked); + SetByteValue(PlayerFields.Bytes3, PlayerFieldOffsets.Bytes3OffsetGender, (byte)createInfo.Sex); + SetByteValue(PlayerFields.Bytes4, PlayerFieldOffsets.Bytes4OffsetArenaFaction, 0); + + SetGuidValue(ObjectFields.Data, ObjectGuid.Empty); + SetUInt32Value(PlayerFields.GuildRank, 0); + SetGuildLevel(0); + SetUInt32Value(PlayerFields.GuildTimestamp, 0); + + for (int i = 0; i < PlayerConst.KnowTitlesSize; ++i) + SetUInt64Value(PlayerFields.KnownTitles + i, 0); // 0=disabled + SetUInt32Value(PlayerFields.ChosenTitle, 0); + + SetUInt32Value(PlayerFields.Kills, 0); + SetUInt32Value(PlayerFields.LifetimeHonorableKills, 0); + + // set starting level + uint start_level = WorldConfig.GetUIntValue(WorldCfg.StartPlayerLevel); + if (GetClass() == Class.Deathknight) + start_level= WorldConfig.GetUIntValue(WorldCfg.StartDeathKnightPlayerLevel); + else if (GetClass() == Class.DemonHunter) + start_level = WorldConfig.GetUIntValue(WorldCfg.StartDemonHunterPlayerLevel); + + if (createInfo.TemplateSet.HasValue) + { + if (GetSession().HasPermission(RBACPermissions.UseCharacterTemplates)) + { + CharacterTemplate charTemplate = Global.CharacterTemplateDataStorage.GetCharacterTemplate(createInfo.TemplateSet.Value); + if (charTemplate != null) + { + if (charTemplate.Level > start_level) + start_level = charTemplate.Level; + } + } + else + Log.outWarn(LogFilter.Cheat, "Account: {0} (IP: {1}) tried to use a character template without given permission. Possible cheating attempt.", GetSession().GetAccountId(), GetSession().GetRemoteAddress()); + } + + if (GetSession().HasPermission(RBACPermissions.UseStartGmLevel)) + { + uint gm_level = WorldConfig.GetUIntValue(WorldCfg.StartGmLevel); + if (gm_level > start_level) + start_level = gm_level; + } + + SetUInt32Value(UnitFields.Level, start_level); + + InitRunes(); + + SetUInt32Value(PlayerFields.Coinage, WorldConfig.GetUIntValue(WorldCfg.StartPlayerMoney)); + SetCurrency(CurrencyTypes.ApexisCrystals, WorldConfig.GetUIntValue(WorldCfg.CurrencyStartApexisCrystals)); + SetCurrency(CurrencyTypes.JusticePoints, WorldConfig.GetUIntValue(WorldCfg.CurrencyStartJusticePoints)); + + // start with every map explored + if (WorldConfig.GetBoolValue(WorldCfg.StartAllExplored)) + { + for (ushort i = 0; i < PlayerConst.ExploredZonesSize; i++) + SetFlag(PlayerFields.ExploredZones1 + i, 0xFFFFFFFF); + } + + //Reputations if "StartAllReputation" is enabled, -- TODO: Fix this in a better way + if (WorldConfig.GetBoolValue(WorldCfg.StartAllRep)) + { + GetReputationMgr().SetReputation(CliDB.FactionStorage.LookupByKey(942), 42999); + GetReputationMgr().SetReputation(CliDB.FactionStorage.LookupByKey(935), 42999); + GetReputationMgr().SetReputation(CliDB.FactionStorage.LookupByKey(936), 42999); + GetReputationMgr().SetReputation(CliDB.FactionStorage.LookupByKey(1011), 42999); + GetReputationMgr().SetReputation(CliDB.FactionStorage.LookupByKey(970), 42999); + GetReputationMgr().SetReputation(CliDB.FactionStorage.LookupByKey(967), 42999); + GetReputationMgr().SetReputation(CliDB.FactionStorage.LookupByKey(989), 42999); + GetReputationMgr().SetReputation(CliDB.FactionStorage.LookupByKey(932), 42999); + GetReputationMgr().SetReputation(CliDB.FactionStorage.LookupByKey(934), 42999); + GetReputationMgr().SetReputation(CliDB.FactionStorage.LookupByKey(1038), 42999); + GetReputationMgr().SetReputation(CliDB.FactionStorage.LookupByKey(1077), 42999); + + // Factions depending on team, like cities and some more stuff + switch (GetTeam()) + { + case Team.Alliance: + GetReputationMgr().SetReputation(CliDB.FactionStorage.LookupByKey(72), 42999); + GetReputationMgr().SetReputation(CliDB.FactionStorage.LookupByKey(47), 42999); + GetReputationMgr().SetReputation(CliDB.FactionStorage.LookupByKey(69), 42999); + GetReputationMgr().SetReputation(CliDB.FactionStorage.LookupByKey(930), 42999); + GetReputationMgr().SetReputation(CliDB.FactionStorage.LookupByKey(730), 42999); + GetReputationMgr().SetReputation(CliDB.FactionStorage.LookupByKey(978), 42999); + GetReputationMgr().SetReputation(CliDB.FactionStorage.LookupByKey(54), 42999); + GetReputationMgr().SetReputation(CliDB.FactionStorage.LookupByKey(946), 42999); + break; + case Team.Horde: + GetReputationMgr().SetReputation(CliDB.FactionStorage.LookupByKey(76), 42999); + GetReputationMgr().SetReputation(CliDB.FactionStorage.LookupByKey(68), 42999); + GetReputationMgr().SetReputation(CliDB.FactionStorage.LookupByKey(81), 42999); + GetReputationMgr().SetReputation(CliDB.FactionStorage.LookupByKey(911), 42999); + GetReputationMgr().SetReputation(CliDB.FactionStorage.LookupByKey(729), 42999); + GetReputationMgr().SetReputation(CliDB.FactionStorage.LookupByKey(941), 42999); + GetReputationMgr().SetReputation(CliDB.FactionStorage.LookupByKey(530), 42999); + GetReputationMgr().SetReputation(CliDB.FactionStorage.LookupByKey(947), 42999); + break; + default: + break; + } + } + + // Played time + m_Last_tick = Time.UnixTime; + m_PlayedTimeTotal = 0; + m_PlayedTimeLevel = 0; + + // base stats and related field values + InitStatsForLevel(); + InitTaxiNodesForLevel(); + InitTalentForLevel(); + InitPrimaryProfessions(); // to max set before any spell added + + // apply original stats mods before spell loading or item equipment that call before equip _RemoveStatsMods() + UpdateMaxHealth(); // Update max Health (for add bonus from stamina) + SetFullHealth(); + if (getPowerType() == PowerType.Mana) + SetPower(PowerType.Mana, GetMaxPower(PowerType.Mana)); + + if (getPowerType() == PowerType.RunicPower) + { + SetPower(PowerType.Runes, 8); + SetMaxPower(PowerType.Runes, 8); + SetPower(PowerType.RunicPower, 0); + SetMaxPower(PowerType.RunicPower, 1000); + } + + // original spells + LearnDefaultSkills(); + LearnCustomSpells(); + + // Original action bar. Do not use Player.AddActionButton because we do not have skill spells loaded at this time + // but checks will still be performed later when loading character from db in Player._LoadActions + foreach (var action in info.action) + { + // create new button + ActionButton ab = new ActionButton(); + + // set data + ab.SetActionAndType(action.action, (ActionButtonType)action.type); + + m_actionButtons[action.button] = ab; + } + + // original items + CharStartOutfitRecord oEntry = Global.DB2Mgr.GetCharStartOutfitEntry((byte)createInfo.RaceId, (byte)createInfo.ClassId, (byte)createInfo.Sex); + if (oEntry != null) + { + for (int j = 0; j < ItemConst.MaxOutfitItems - 1; ++j) + { + if (oEntry.ItemID[j] <= 0) + continue; + + uint itemId = (uint)oEntry.ItemID[j]; + + ItemTemplate iProto = Global.ObjectMgr.GetItemTemplate(itemId); + if (iProto == null) + continue; + + // BuyCount by default + uint count = iProto.GetBuyCount(); + + // special amount for food/drink + if (iProto.GetClass() == ItemClass.Consumable && iProto.GetSubClass() == (uint)ItemSubClassConsumable.FoodDrink) + { + if (!iProto.Effects.Empty()) + { + switch (iProto.Effects[0].Category) + { + case 11: // food + count = (uint)(GetClass() == Class.Deathknight ? 10 : 4); + break; + case 59: // drink + count = 2; + break; + } + } + if (iProto.GetMaxStackSize() < count) + count = iProto.GetMaxStackSize(); + } + StoreNewItemInBestSlots(itemId, count); + } + } + foreach (var item in info.item) + StoreNewItemInBestSlots(item.item_id, item.item_amount); + + // bags and main-hand weapon must equipped at this moment + // now second pass for not equipped (offhand weapon/shield if it attempt equipped before main-hand weapon) + for (var i = InventorySlots.ItemStart; i < InventorySlots.ItemEnd; i++) + { + Item pItem = GetItemByPos(InventorySlots.Bag0, i); + if (pItem != null) + { + ushort eDest; + // equip offhand weapon/shield if it attempt equipped before main-hand weapon + InventoryResult msg = CanEquipItem(ItemConst.NullSlot, out eDest, pItem, false); + if (msg == InventoryResult.Ok) + { + RemoveItem(InventorySlots.Bag0, i, true); + EquipItem(eDest, pItem, true); + } + // move other items to more appropriate slots + else + { + List sDest = new List(); + msg = CanStoreItem(ItemConst.NullBag, ItemConst.NullSlot, sDest, pItem, false); + if (msg == InventoryResult.Ok) + { + RemoveItem(InventorySlots.Bag0, i, true); + pItem = StoreItem(sDest, pItem, true); + } + } + } + } + // all item positions resolved + + ChrSpecializationRecord defaultSpec = Global.DB2Mgr.GetDefaultChrSpecializationForClass(GetClass()); + if (defaultSpec != null) + { + SetActiveTalentGroup(defaultSpec.OrderIndex); + SetPrimarySpecialization(defaultSpec.Id); + } + + return true; + } + public override void Update(uint diff) + { + if (!IsInWorld) + return; + + // undelivered mail + if (m_nextMailDelivereTime != 0 && m_nextMailDelivereTime <= Time.UnixTime) + { + SendNewMail(); + ++unReadMails; + + // It will be recalculate at mailbox open (for unReadMails important non-0 until mailbox open, it also will be recalculated) + m_nextMailDelivereTime = 0; + } + // If this is set during update SetSpellModTakingSpell call is missing somewhere in the code + // Having this would prevent more aura charges to be dropped, so let's crash + if (m_spellModTakingSpell != null) + { + Log.outFatal(LogFilter.Spells, "Player has m_spellModTakingSpell {0} during update!", m_spellModTakingSpell.m_spellInfo.Id); + m_spellModTakingSpell = null; + } + + // Update cinematic location, if 500ms have passed and we're doing a cinematic now. + _cinematicMgr.m_cinematicDiff += diff; + if (_cinematicMgr.m_activeCinematicCameraId != 0 && Time.GetMSTimeDiffToNow(_cinematicMgr.m_lastCinematicCheck) > 500) + { + _cinematicMgr.m_lastCinematicCheck = Time.GetMSTime(); + _cinematicMgr.UpdateCinematicLocation(diff); + } + + //used to implement delayed far teleports + SetCanDelayTeleport(true); + base.Update(diff); + SetCanDelayTeleport(false); + + long now = Time.UnixTime; + + UpdatePvPFlag(now); + + UpdateContestedPvP(diff); + + UpdateDuelFlag(now); + + CheckDuelDistance(now); + + UpdateAfkReport(now); + + if (IsAIEnabled && GetAI() != null) + GetAI().UpdateAI(diff); + else if (NeedChangeAI) + { + UpdateCharmAI(); + NeedChangeAI = false; + IsAIEnabled = GetAI() != null; + } + + // Update items that have just a limited lifetime + if (now > m_Last_tick) + UpdateItemDuration((uint)(now - m_Last_tick)); + + // check every second + if (now > m_Last_tick + 1) + UpdateSoulboundTradeItems(); + + // If mute expired, remove it from the DB + if (GetSession().m_muteTime != 0 && GetSession().m_muteTime < now) + { + GetSession().m_muteTime = 0; + PreparedStatement stmt = DB.Login.GetPreparedStatement(LoginStatements.UPD_MUTE_TIME); + stmt.AddValue(0, 0); // Set the mute time to 0 + stmt.AddValue(1, ""); + stmt.AddValue(2, ""); + stmt.AddValue(3, GetSession().GetAccountId()); + DB.Login.Execute(stmt); + } + + if (!m_timedquests.Empty()) + { + foreach (var id in m_timedquests) + { + QuestStatusData q_status = m_QuestStatus[id]; + if (q_status.Timer <= diff) + FailQuest(id); + else + { + q_status.Timer -= diff; + m_QuestStatusSave[id] = QuestSaveType.Default; + } + } + } + + m_achievementSys.UpdateTimedCriteria(diff); + + if (HasUnitState(UnitState.MeleeAttacking) && !HasUnitState(UnitState.Casting)) + { + Unit victim = GetVictim(); + if (victim != null) + { + // default combat reach 10 + // TODO add weapon, skill check + + if (isAttackReady(WeaponAttackType.BaseAttack)) + { + if (!IsWithinMeleeRange(victim)) + { + setAttackTimer(WeaponAttackType.BaseAttack, 100); + if (m_swingErrorMsg != 1) // send single time (client auto repeat) + { + SendAttackSwingNotInRange(); + m_swingErrorMsg = 1; + } + } + //120 degrees of radiant range, if player is not in boundary radius + else if (!IsWithinBoundaryRadius(victim) && !HasInArc(2 * MathFunctions.PI / 3, victim)) + { + setAttackTimer(WeaponAttackType.BaseAttack, 100); + if (m_swingErrorMsg != 2) // send single time (client auto repeat) + { + SendAttackSwingBadFacingAttack(); + m_swingErrorMsg = 2; + } + } + else + { + m_swingErrorMsg = 0; // reset swing error state + + // prevent base and off attack in same time, delay attack at 0.2 sec + if (haveOffhandWeapon()) + if (getAttackTimer(WeaponAttackType.OffAttack) < SharedConst.AttackDisplayDelay) + setAttackTimer(WeaponAttackType.OffAttack, SharedConst.AttackDisplayDelay); + + // do attack + AttackerStateUpdate(victim, WeaponAttackType.BaseAttack); + resetAttackTimer(WeaponAttackType.BaseAttack); + } + } + + if (!IsInFeralForm() && haveOffhandWeapon() && isAttackReady(WeaponAttackType.OffAttack)) + { + if (!IsWithinMeleeRange(victim)) + setAttackTimer(WeaponAttackType.OffAttack, 100); + else if (!IsWithinBoundaryRadius(victim) && !HasInArc(2 * MathFunctions.PI / 3, victim)) + setAttackTimer(WeaponAttackType.BaseAttack, 100); + else + { + // prevent base and off attack in same time, delay attack at 0.2 sec + if (getAttackTimer(WeaponAttackType.BaseAttack) < SharedConst.AttackDisplayDelay) + setAttackTimer(WeaponAttackType.BaseAttack, SharedConst.AttackDisplayDelay); + + // do attack + AttackerStateUpdate(victim, WeaponAttackType.OffAttack); + resetAttackTimer(WeaponAttackType.OffAttack); + } + } + } + } + + if (HasFlag(PlayerFields.Flags, PlayerFlags.Resting)) + _restMgr.Update(diff); + + if (m_weaponChangeTimer > 0) + { + if (diff >= m_weaponChangeTimer) + m_weaponChangeTimer = 0; + else + m_weaponChangeTimer -= diff; + } + + if (m_zoneUpdateTimer > 0) + { + if (diff >= m_zoneUpdateTimer) + { + // On zone update tick check if we are still in an inn if we are supposed to be in one + if (_restMgr.HasRestFlag(RestFlag.Tavern)) + { + AreaTriggerRecord atEntry = CliDB.AreaTriggerStorage.LookupByKey(_restMgr.GetInnTriggerId()); + if (atEntry == null || !IsInAreaTriggerRadius(atEntry)) + _restMgr.RemoveRestFlag(RestFlag.Tavern); + } + + uint newzone, newarea; + GetZoneAndAreaId(out newzone, out newarea); + + if (m_zoneUpdateId != newzone) + UpdateZone(newzone, newarea); // also update area + else + { + // use area updates as well + // needed for free far all arenas for example + if (m_areaUpdateId != newarea) + UpdateArea(newarea); + + m_zoneUpdateTimer = 1 * Time.InMilliseconds; + } + } + else + m_zoneUpdateTimer -= diff; + } + if (m_timeSyncTimer > 0 && !IsBeingTeleportedFar()) + { + if (diff >= m_timeSyncTimer) + SendTimeSync(); + else + m_timeSyncTimer -= diff; + } + + if (IsAlive()) + { + m_regenTimer += diff; + RegenerateAll(); + } + + if (m_deathState == DeathState.JustDied) + KillPlayer(); + + if (m_nextSave > 0) + { + if (diff >= m_nextSave) + { + // m_nextSave reset in SaveToDB call + Global.ScriptMgr.OnPlayerSave(this); + SaveToDB(); + Log.outDebug(LogFilter.Player, "Player '{0}' (GUID: {1}) saved", GetName(), GetGUID().ToString()); + } + else + m_nextSave -= diff; + } + + //Handle Water/drowning + HandleDrowning(diff); + + // Played time + if (now > m_Last_tick) + { + uint elapsed = (uint)(now - m_Last_tick); + m_PlayedTimeTotal += elapsed; + m_PlayedTimeLevel += elapsed; + m_Last_tick = now; + } + + if (GetDrunkValue() != 0) + { + m_drunkTimer += diff; + if (m_drunkTimer > 9 * Time.InMilliseconds) + HandleSobering(); + } + if (HasPendingBind()) + { + if (_pendingBindTimer <= diff) + { + // Player left the instance + if (_pendingBindId == GetInstanceId()) + BindToInstance(); + SetPendingBind(0, 0); + } + else + _pendingBindTimer -= diff; + } + // not auto-free ghost from body in instances + if (m_deathTimer > 0 && !GetMap().Instanceable() && !HasAuraType(AuraType.PreventResurrection)) + { + if (diff >= m_deathTimer) + { + m_deathTimer = 0; + BuildPlayerRepop(); + RepopAtGraveyard(); + } + else + m_deathTimer -= diff; + } + + UpdateEnchantTime(diff); + UpdateHomebindTime(diff); + + if (!_instanceResetTimes.Empty()) + { + foreach (var instance in _instanceResetTimes.ToList()) + { + if (instance.Value < now) + _instanceResetTimes.Remove(instance.Key); + } + } + + if (GetClass() == Class.Deathknight) + { + // Update rune timers + for (byte i = 0; i < PlayerConst.MaxRunes; ++i) + { + uint timer = GetRuneTimer(i); + + // Don't update timer if rune is disabled + if (GetRuneCooldown(i) != 0) + continue; + + // Timer has began + if (timer < 0xFFFFFFFF) + { + timer += diff; + SetRuneTimer(i, Math.Min(2500, timer)); + } + } + } + + // group update + SendUpdateToOutOfRangeGroupMembers(); + + Pet pet = GetPet(); + if (pet != null && !pet.IsWithinDistInMap(this, GetMap().GetVisibilityRange()) && !pet.isPossessed()) + RemovePet(pet, PetSaveMode.NotInSlot, true); + + //we should execute delayed teleports only for alive(!) players + //because we don't want player's ghost teleported from graveyard + if (IsHasDelayedTeleport() && IsAlive()) + TeleportTo(teleportDest, m_teleport_options); + } + + public override void setDeathState(DeathState s) + { + uint ressSpellId = 0; + + bool cur = IsAlive(); + + if (s == DeathState.JustDied) + { + if (!cur) + { + Log.outError(LogFilter.Player, "Player.setDeathState: Attempted to kill a dead player '{0}' ({1})", GetName(), GetGUID().ToString()); + return; + } + + // drunken state is cleared on death + SetDrunkValue(0); + // lost combo points at any target (targeted combo points clear in Unit::setDeathState) + ClearComboPoints(); + + ClearResurrectRequestData(); + + //FIXME: is pet dismissed at dying or releasing spirit? if second, add setDeathState(DEAD) to HandleRepopRequestOpcode and define pet unsummon here with (s == DEAD) + RemovePet(null, PetSaveMode.NotInSlot, true); + + // save value before aura remove in Unit::setDeathState + ressSpellId = GetUInt32Value(PlayerFields.SelfResSpell); + + // passive spell + if (ressSpellId == 0) + ressSpellId = GetResurrectionSpellId(); + UpdateCriteria(CriteriaTypes.DeathAtMap, 1); + UpdateCriteria(CriteriaTypes.Death, 1); + UpdateCriteria(CriteriaTypes.DeathInDungeon, 1); + ResetCriteria(CriteriaTypes.BgObjectiveCapture, (uint)CriteriaCondition.NoDeath); + ResetCriteria(CriteriaTypes.HonorableKill, (uint)CriteriaCondition.NoDeath); + ResetCriteria(CriteriaTypes.GetKillingBlows, (uint)CriteriaCondition.NoDeath); + } + + base.setDeathState(s); + + // restore resurrection spell id for player after aura remove + if (s == DeathState.JustDied && cur && ressSpellId != 0) + SetUInt32Value(PlayerFields.SelfResSpell, ressSpellId); + + if (IsAlive() && !cur) + //clear aura case after resurrection by another way (spells will be applied before next death) + SetUInt32Value(PlayerFields.SelfResSpell, 0); + } + + public override void DestroyForPlayer(Player target) + { + base.DestroyForPlayer(target); + + for (byte i = 0; i < InventorySlots.BagEnd; ++i) + { + if (m_items[i] == null) + continue; + + m_items[i].DestroyForPlayer(target); + } + + if (target == this) + { + for (byte i = InventorySlots.BagStart; i < InventorySlots.BagEnd; ++i) + { + if (m_items[i] == null) + continue; + + m_items[i].DestroyForPlayer(target); + } + + for (byte i = InventorySlots.ReagentStart; i < InventorySlots.ReagentEnd; ++i) + { + if (m_items[i] == null) + continue; + + m_items[i].DestroyForPlayer(target); + } + + for (byte i = InventorySlots.ChildEquipmentStart; i < InventorySlots.ChildEquipmentEnd; ++i) + { + if (m_items[i] == null) + continue; + + m_items[i].DestroyForPlayer(target); + } + } + } + public override void CleanupsBeforeDelete(bool finalCleanup = true) + { + TradeCancel(false); + DuelComplete(DuelCompleteType.Interrupted); + + base.CleanupsBeforeDelete(finalCleanup); + + if (GetTransport() != null) + GetTransport().RemovePassenger(this); + + // clean up player-instance binds, may unload some instance saves + for (byte i = 0; i < (int)Difficulty.Max; ++i) + foreach (var bound in m_boundInstances[i]) + bound.Value.save.RemovePlayer(this); + } + + public override void AddToWorld() + { + // Do not add/remove the player from the object storage + // It will crash when updating the ObjectAccessor + // The player should only be added when logging in + base.AddToWorld(); + + for (byte i = (int)PlayerSlots.Start; i < (int)PlayerSlots.End; ++i) + if (m_items[i] != null) + m_items[i].AddToWorld(); + } + public override void RemoveFromWorld() + { + // cleanup + if (IsInWorld) + { + // Release charmed creatures, unsummon totems and remove pets/guardians + StopCastingCharm(); + StopCastingBindSight(); + UnsummonPetTemporaryIfAny(); + Global.OutdoorPvPMgr.HandlePlayerLeaveZone(this, m_zoneUpdateId); + Global.BattleFieldMgr.HandlePlayerLeaveZone(this, m_zoneUpdateId); + } + + // Remove items from world before self - player must be found in Item.RemoveFromObjectUpdate + for (byte i = (int)PlayerSlots.Start; i < (int)PlayerSlots.End; ++i) + if (m_items[i] != null) + m_items[i].RemoveFromWorld(); + + // Do not add/remove the player from the object storage + // It will crash when updating the ObjectAccessor + // The player should only be removed when logging out + base.RemoveFromWorld(); + + WorldObject viewpoint = GetViewpoint(); + if (viewpoint != null) + { + Log.outError(LogFilter.Player, "Player {0} has viewpoint {1} {2} when removed from world", + GetName(), viewpoint.GetEntry(), viewpoint.GetTypeId()); + SetViewpoint(viewpoint, false); + } + } + + void ScheduleDelayedOperation(PlayerDelayedOperations operation) + { + if (operation < PlayerDelayedOperations.End) + m_DelayedOperations |= operation; + } + public void ProcessDelayedOperations() + { + if (m_DelayedOperations == 0) + return; + + if (m_DelayedOperations.HasAnyFlag(PlayerDelayedOperations.ResurrectPlayer)) + ResurrectUsingRequestDataImpl(); + + if (m_DelayedOperations.HasAnyFlag(PlayerDelayedOperations.SavePlayer)) + SaveToDB(); + + if (m_DelayedOperations.HasAnyFlag(PlayerDelayedOperations.SpellCastDeserter)) + CastSpell(this, 26013, true); // Deserter + + if (m_DelayedOperations.HasAnyFlag(PlayerDelayedOperations.BGMountRestore)) + { + if (m_bgData.mountSpell != 0) + { + CastSpell(this, m_bgData.mountSpell, true); + m_bgData.mountSpell = 0; + } + } + + if (m_DelayedOperations.HasAnyFlag(PlayerDelayedOperations.BGTaxiRestore)) + { + if (m_bgData.HasTaxiPath()) + { + m_taxi.AddTaxiDestination(m_bgData.taxiPath[0]); + m_taxi.AddTaxiDestination(m_bgData.taxiPath[1]); + m_bgData.ClearTaxiPath(); + + ContinueTaxiFlight(); + } + } + + if (m_DelayedOperations.HasAnyFlag(PlayerDelayedOperations.BGGroupRestore)) + { + Group g = GetGroup(); + if (g != null) + g.SendUpdateToPlayer(GetGUID()); + } + + //we have executed ALL delayed ops, so clear the flag + m_DelayedOperations = 0; + } + + public override bool IsLoading() + { + return GetSession().PlayerLoading(); + } + + new PlayerAI GetAI() { return (PlayerAI)i_AI; } + + //Network + public void SendPacket(ServerPacket data, bool writePacket = true) + { + Session.SendPacket(data, writePacket); + } + + //Time + void ResetTimeSync() + { + m_timeSyncTimer = 0; + m_timeSyncClient = 0; + m_timeSyncServer = Time.GetMSTime(); + } + void SendTimeSync() + { + m_timeSyncQueue.Push(m_movementCounter++); + + TimeSyncRequest packet = new TimeSyncRequest(); + packet.SequenceIndex = m_timeSyncQueue.Last(); + SendPacket(packet); + + // Schedule next sync in 10 sec + m_timeSyncTimer = 10000; + m_timeSyncServer = Time.GetMSTime(); + + if (m_timeSyncQueue.Count > 3) + Log.outError(LogFilter.Network, "Not received CMSG_TIME_SYNC_RESP for over 30 seconds from player {0} ({1}), possible cheater", GetGUID().ToString(), GetName()); + } + + public DeclinedName GetDeclinedNames() { return _declinedname; } + + public void CreateGarrison(uint garrSiteId) + { + _garrison = new Garrison(this); + if (!_garrison.Create(garrSiteId)) + _garrison = null; + } + + void DeleteGarrison() + { + if (_garrison != null) + { + _garrison.Delete(); + _garrison = null; + } + } + + public Garrison GetGarrison() { return _garrison; } + + public SceneMgr GetSceneMgr() { return m_sceneMgr; } + + public RestMgr GetRestMgr() { return _restMgr; } + + public bool IsAdvancedCombatLoggingEnabled() { return _advancedCombatLoggingEnabled; } + public void SetAdvancedCombatLogging(bool enabled) { _advancedCombatLoggingEnabled = enabled; } + + //Taxi + public void InitTaxiNodesForLevel() { m_taxi.InitTaxiNodesForLevel(GetRace(), GetClass(), getLevel()); } + + //Cheat Commands + public bool GetCommandStatus(PlayerCommandStates command) { return (_activeCheats & command) != 0; } + public void SetCommandStatusOn(PlayerCommandStates command) { _activeCheats |= command; } + public void SetCommandStatusOff(PlayerCommandStates command) { _activeCheats &= ~command; } + + //Pet - Summons - Vehicles + + // last used pet number (for BG's) + public uint GetLastPetNumber() { return m_lastpetnumber; } + public void SetLastPetNumber(uint petnumber) { m_lastpetnumber = petnumber; } + public void LoadPet() + { + //fixme: the pet should still be loaded if the player is not in world + // just not added to the map + if (IsInWorld) + { + Pet pet = new Pet(this); + pet.LoadPetFromDB(this, 0, 0, true); + } + } + public uint GetTemporaryUnsummonedPetNumber() { return m_temporaryUnsummonedPetNumber; } + public void SetTemporaryUnsummonedPetNumber(uint petnumber) { m_temporaryUnsummonedPetNumber = petnumber; } + public void UnsummonPetTemporaryIfAny() + { + Pet pet = GetPet(); + if (!pet) + return; + + if (m_temporaryUnsummonedPetNumber == 0 && pet.isControlled() && !pet.isTemporarySummoned()) + { + m_temporaryUnsummonedPetNumber = pet.GetCharmInfo().GetPetNumber(); + m_oldpetspell = pet.GetUInt32Value(UnitFields.CreatedBySpell); + } + + RemovePet(pet, PetSaveMode.AsCurrent); + } + public void ResummonPetTemporaryUnSummonedIfAny() + { + if (m_temporaryUnsummonedPetNumber == 0) + return; + + // not resummon in not appropriate state + if (IsPetNeedBeTemporaryUnsummoned()) + return; + + if (!GetPetGUID().IsEmpty()) + return; + + Pet NewPet = new Pet(this); + NewPet.LoadPetFromDB(this, 0, m_temporaryUnsummonedPetNumber, true); + + m_temporaryUnsummonedPetNumber = 0; + } + public void ResetPetTalents() + { + /* ODO: 6.x remove/update pet talents + // This needs another gossip option + NPC text as a confirmation. + // The confirmation gossip listid has the text: "Yes, please do." + Pet pet = GetPet(); + + if (!pet || pet.getPetType() != PetType.Hunter || pet.m_usedTalentCount == 0) + return; + + CharmInfo charmInfo = pet.GetCharmInfo(); + if (charmInfo == null) + { + Log.outError(LogFilter.Player, "Object (GUID: {0} TypeId: {1}) is considered pet-like but doesn't have a charminfo!", pet.GetGUID().ToString(), pet.GetTypeId()); + return; + } + pet.resetTalents(); + SendTalentsInfoData(true); + */ + } + + public bool IsPetNeedBeTemporaryUnsummoned() + { + return !IsInWorld || !IsAlive() || IsMounted(); + } + + public void SendRemoveControlBar() + { + SendPacket(new PetSpells()); + } + + public void StopCastingCharm() + { + Unit charm = GetCharm(); + if (!charm) + return; + + if (charm.IsTypeId(TypeId.Unit)) + { + if (charm.ToCreature().HasUnitTypeMask(UnitTypeMask.Puppet)) + ((Puppet)charm).UnSummon(); + else if (charm.IsVehicle()) + ExitVehicle(); + } + if (!GetCharmGUID().IsEmpty()) + charm.RemoveCharmAuras(); + + if (!GetCharmGUID().IsEmpty()) + { + Log.outFatal(LogFilter.Player, "Player {0} (GUID: {1} is not able to uncharm unit (GUID: {2} Entry: {3}, Type: {4})", GetName(), GetGUID(), GetCharmGUID(), charm.GetEntry(), charm.GetTypeId()); + if (!charm.GetCharmerGUID().IsEmpty()) + { + Log.outFatal(LogFilter.Player, "Charmed unit has charmer guid {0}", charm.GetCharmerGUID()); + Contract.Assert(false); + } + + SetCharm(charm, false); + } + } + public void CharmSpellInitialize() + { + Unit charm = GetFirstControlled(); + if (!charm) + return; + + CharmInfo charmInfo = charm.GetCharmInfo(); + if (charmInfo == null) + { + Log.outError(LogFilter.Player, "Player:CharmSpellInitialize(): the player's charm ({0}) has no charminfo!", charm.GetGUID()); + return; + } + + PetSpells petSpells = new PetSpells(); + petSpells.PetGUID = charm.GetGUID(); + + if (charm.IsTypeId(TypeId.Unit)) + { + petSpells.ReactState = charm.ToCreature().GetReactState(); + petSpells.CommandState = charmInfo.GetCommandState(); + } + + for (byte i = 0; i < SharedConst.ActionBarIndexMax; ++i) + petSpells.ActionButtons[i] = charmInfo.GetActionBarEntry(i).packedData; + + for (byte i = 0; i < SharedConst.MaxSpellCharm; ++i) + { + var cspell = charmInfo.GetCharmSpell(i); + if (cspell.GetAction() != 0) + petSpells.Actions.Add(cspell.packedData); + } + + // Cooldowns + if (!charm.IsTypeId(TypeId.Player)) + charm.GetSpellHistory().WritePacket(petSpells); + + SendPacket(petSpells); + } + public void PossessSpellInitialize() + { + Unit charm = GetCharm(); + if (!charm) + return; + + CharmInfo charmInfo = charm.GetCharmInfo(); + if (charmInfo == null) + { + Log.outError(LogFilter.Player, "Player:PossessSpellInitialize(): charm ({0}) has no charminfo!", charm.GetGUID()); + return; + } + + PetSpells petSpellsPacket = new PetSpells(); + petSpellsPacket.PetGUID = charm.GetGUID(); + + for (byte i = 0; i < SharedConst.ActionBarIndexMax; ++i) + petSpellsPacket.ActionButtons[i] = charmInfo.GetActionBarEntry(i).packedData; + + // Cooldowns + charm.GetSpellHistory().WritePacket(petSpellsPacket); + + SendPacket(petSpellsPacket); + } + public void VehicleSpellInitialize() + { + Creature vehicle = GetVehicleCreatureBase(); + if (!vehicle) + return; + + PetSpells petSpells = new PetSpells(); + petSpells.PetGUID = vehicle.GetGUID(); + petSpells.CreatureFamily = 0; // Pet Family (0 for all vehicles) + petSpells.Specialization = 0; + petSpells.TimeLimit = vehicle.IsSummon() ? vehicle.ToTempSummon().GetTimer() : 0; + petSpells.ReactState = vehicle.GetReactState(); + petSpells.CommandState = CommandStates.Follow; + petSpells.Flag = 0x8; + + for (uint i = 0; i < SharedConst.MaxSpellControlBar; ++i) + petSpells.ActionButtons[i] = UnitActionBarEntry.MAKE_UNIT_ACTION_BUTTON(0, i + 8); + + for (uint i = 0; i < SharedConst.MaxCreatureSpells; ++i) + { + uint spellId = vehicle.m_spells[i]; + SpellInfo spellInfo = Global.SpellMgr.GetSpellInfo(spellId); + if (spellInfo == null) + continue; + + if (!Global.ConditionMgr.IsObjectMeetingVehicleSpellConditions(vehicle.GetEntry(), spellId, this, vehicle)) + { + Log.outDebug(LogFilter.Condition, "VehicleSpellInitialize: conditions not met for Vehicle entry {0} spell {1}", vehicle.ToCreature().GetEntry(), spellId); + continue; + } + + if (spellInfo.IsPassive()) + vehicle.CastSpell(vehicle, spellId, true); + + petSpells.ActionButtons[i] = UnitActionBarEntry.MAKE_UNIT_ACTION_BUTTON(spellId, i + 8); + } + + // Cooldowns + vehicle.GetSpellHistory().WritePacket(petSpells); + + SendPacket(petSpells); + } + + //Currency - Money + void SetCurrency(CurrencyTypes id, uint count, bool printLog = true) + { + var playerCurrency = _currencyStorage.LookupByKey(id); + if (playerCurrency == null) + { + PlayerCurrency cur = new PlayerCurrency(); + cur.state = PlayerCurrencyState.New; + cur.Quantity = count; + cur.WeeklyQuantity = 0; + cur.TrackedQuantity = 0; + cur.Flags = 0; + _currencyStorage[(uint)id] = cur; + } + } + public uint GetCurrency(uint id) + { + var playerCurrency = _currencyStorage.LookupByKey(id); + if (playerCurrency == null) + return 0; + + return playerCurrency.Quantity; + } + public void ModifyCurrency(CurrencyTypes id, int count, bool printLog = true, bool ignoreMultipliers = false) + { + if (count == 0) + return; + + CurrencyTypesRecord currency = CliDB.CurrencyTypesStorage.LookupByKey(id); + Contract.Assert(currency != null); + + if (!ignoreMultipliers) + count *= (int)GetTotalAuraMultiplierByMiscValue(AuraType.ModCurrencyGain, (int)id); + + uint oldTotalCount = 0; + uint oldWeekCount = 0; + uint oldTrackedCount = 0; + + var playerCurrency = _currencyStorage.LookupByKey(id); + if (playerCurrency == null) + { + PlayerCurrency cur = new PlayerCurrency(); + cur.state = PlayerCurrencyState.New; + cur.Quantity = 0; + cur.WeeklyQuantity = 0; + cur.TrackedQuantity = 0; + cur.Flags = 0; + _currencyStorage[(uint)id] = cur; + playerCurrency = _currencyStorage.LookupByKey(id); + } + else + { + oldTotalCount = playerCurrency.Quantity; + oldWeekCount = playerCurrency.WeeklyQuantity; + oldTrackedCount = playerCurrency.TrackedQuantity; + } + + // count can't be more then weekCap if used (weekCap > 0) + uint weekCap = GetCurrencyWeekCap(currency); + if (weekCap != 0 && count > weekCap) + count = (int)weekCap; + + // count can't be more then totalCap if used (totalCap > 0) + uint totalCap = GetCurrencyTotalCap(currency); + if (totalCap != 0 && count > totalCap) + count = (int)totalCap; + + int newTrackedCount = (int)(oldTrackedCount) + (count > 0 ? count : 0); + if (newTrackedCount < 0) + newTrackedCount = 0; + + int newTotalCount = (int)oldTotalCount + count; + if (newTotalCount < 0) + newTotalCount = 0; + + int newWeekCount = (int)oldWeekCount + (count > 0 ? count : 0); + if (newWeekCount < 0) + newWeekCount = 0; + + // if we get more then weekCap just set to limit + if (weekCap != 0 && weekCap < newWeekCount) + { + newWeekCount = (int)weekCap; + // weekCap - oldWeekCount always >= 0 as we set limit before! + newTotalCount = (int)(oldTotalCount + (weekCap - oldWeekCount)); + } + + // if we get more then totalCap set to maximum; + if (totalCap != 0 && totalCap < newTotalCount) + { + newTotalCount = (int)totalCap; + newWeekCount = (int)weekCap; + } + + if (newTotalCount != oldTotalCount) + { + if (playerCurrency.state != PlayerCurrencyState.New) + playerCurrency.state = PlayerCurrencyState.Changed; + + playerCurrency.Quantity = (uint)newTotalCount; + playerCurrency.WeeklyQuantity = (uint)newWeekCount; + playerCurrency.TrackedQuantity = (uint)newTrackedCount; + + if (count > 0) + UpdateCriteria(CriteriaTypes.Currency, (uint)id, (uint)count); + + CurrencyChanged((uint)id, count); + + _currencyStorage[(uint)id] = playerCurrency; + + SetCurrency packet = new SetCurrency(); + packet.Type = (uint)id; + packet.Quantity = newTotalCount; + packet.SuppressChatLog = !printLog; + packet.WeeklyQuantity.Set(newWeekCount); + packet.TrackedQuantity.Set(newTrackedCount); + packet.Flags = playerCurrency.Flags; + + SendPacket(packet); + } + } + public bool HasCurrency(uint id, uint count) + { + var playerCurrency = _currencyStorage.LookupByKey(id); + return playerCurrency != null && playerCurrency.Quantity >= count; + } + public uint GetCurrencyWeekCap(CurrencyTypes id) + { + CurrencyTypesRecord entry = CliDB.CurrencyTypesStorage.LookupByKey((uint)id); + if (entry == null) + return 0; + + return GetCurrencyWeekCap(entry); + } + public uint GetCurrencyWeekCap(CurrencyTypesRecord currency) + { + return currency.MaxEarnablePerWeek; + } + uint GetCurrencyTotalCap(CurrencyTypesRecord currency) + { + uint cap = currency.MaxQty; + + switch ((CurrencyTypes)currency.Id) + { + case CurrencyTypes.ApexisCrystals: + { + uint apexiscap = WorldConfig.GetUIntValue(WorldCfg.CurrencyMaxApexisCrystals); + if (apexiscap > 0) + cap = apexiscap; + break; + } + case CurrencyTypes.JusticePoints: + { + uint justicecap = WorldConfig.GetUIntValue(WorldCfg.CurrencyMaxJusticePoints); + if (justicecap > 0) + cap = justicecap; + break; + } + } + + return cap; + } + uint GetCurrencyOnWeek(CurrencyTypes id) + { + var playerCurrency = _currencyStorage.LookupByKey(id); + if (playerCurrency == null) + return 0; + + return playerCurrency.WeeklyQuantity; + } + + //Action Buttons - CUF Profile + public void SaveCUFProfile(byte id, CUFProfile profile) { _CUFProfiles[id] = profile; } + public CUFProfile GetCUFProfile(byte id) { return _CUFProfiles[id]; } + public byte GetCUFProfilesCount() + { + return (byte)_CUFProfiles.Count(p => p != null); + } + + bool IsActionButtonDataValid(byte button, uint action, uint type) + { + if (button >= PlayerConst.MaxActionButtons) + { + Log.outError(LogFilter.Player, "Action {0} not added into button {1} for player {2} (GUID: {3}): button must be < {4}", action, button, GetName(), GetGUID(), PlayerConst.MaxActionButtons); + return false; + } + + if (action >= PlayerConst.MaxActionButtonActionValue) + { + Log.outError(LogFilter.Player, "Action {0} not added into button {1} for player {2} (GUID: {3}): action must be < {4}", action, button, GetName(), GetGUID(), PlayerConst.MaxActionButtonActionValue); + return false; + } + + switch ((ActionButtonType)type) + { + case ActionButtonType.Spell: + if (Global.SpellMgr.GetSpellInfo(action) == null) + { + Log.outError(LogFilter.Player, "Spell action {0} not added into button {1} for player {2} (GUID: {3}): spell not exist", action, button, GetName(), GetGUID()); + return false; + } + + if (!HasSpell(action)) + { + Log.outError(LogFilter.Player, "Spell action {0} not added into button {1} for player {2} (GUID: {3}): player don't known this spell", action, button, GetName(), GetGUID()); + return false; + } + break; + case ActionButtonType.Item: + if (Global.ObjectMgr.GetItemTemplate(action) == null) + { + Log.outError(LogFilter.Player, "Item action {0} not added into button {1} for player {2} (GUID: {3}): item not exist", action, button, GetName(), GetGUID()); + return false; + } + break; + case ActionButtonType.Mount: + var mount = CliDB.MountStorage.LookupByKey(action); + if (mount == null) + { + Log.outError(LogFilter.Player, "Mount action {0} not added into button {1} for player {2} ({3}): mount does not exist", action, button, GetName(), GetGUID().ToString()); + return false; + } + + if (!HasSpell(mount.SpellId)) + { + Log.outError(LogFilter.Player, "Mount action {0} not added into button {1} for player {2} ({3}): Player does not know this mount", action, button, GetName(), GetGUID().ToString()); + return false; + } + break; + case ActionButtonType.C: + case ActionButtonType.CMacro: + case ActionButtonType.Macro: + case ActionButtonType.Eqset: + break; + default: + Log.outError(LogFilter.Player, "Unknown action type {0}", type); + return false; // other cases not checked at this moment + } + + return true; + } + public ActionButton AddActionButton(byte button, uint action, uint type) + { + if (!IsActionButtonDataValid(button, action, type)) + return null; + + // it create new button (NEW state) if need or return existed + if (!m_actionButtons.ContainsKey(button)) + m_actionButtons[button] = new ActionButton(); + + var ab = m_actionButtons[button]; + + // set data and update to CHANGED if not NEW + ab.SetActionAndType(action, (ActionButtonType)type); + + Log.outDebug(LogFilter.Player, "Player '{0}' Added Action '{1}' (type {2}) to Button '{3}'", GetGUID().ToString(), action, type, button); + return ab; + } + public void RemoveActionButton(byte _button) + { + var button = m_actionButtons.LookupByKey(_button); + if (button == null || button.uState == ActionButtonUpdateState.Deleted) + return; + + if (button.uState == ActionButtonUpdateState.New) + m_actionButtons.Remove(_button); // new and not saved + else + button.uState = ActionButtonUpdateState.Deleted; // saved, will deleted at next save + + Log.outDebug(LogFilter.Player, "Action Button '{0}' Removed from Player '{1}'", button, GetGUID().ToString()); + } + public ActionButton GetActionButton(byte _button) + { + var button = m_actionButtons.LookupByKey(_button); + if (button == null || button.uState == ActionButtonUpdateState.Deleted) + return null; + + return button; + } + void SendInitialActionButtons() { SendActionButtons(0); } + void SendActionButtons(uint state) + { + UpdateActionButtons packet = new UpdateActionButtons(); + + foreach (var pair in m_actionButtons) + { + if (pair.Value.uState != ActionButtonUpdateState.Deleted && pair.Key < packet.ActionButtons.Length) + packet.ActionButtons[pair.Key] = pair.Value.packedData; + } + + packet.Reason = (byte)state; + SendPacket(packet); + } + + //Repitation + public int CalculateReputationGain(ReputationSource source, uint creatureOrQuestLevel, int rep, int faction, bool noQuestBonus = false) + { + float percent = 100.0f; + + float repMod = noQuestBonus ? 0.0f : GetTotalAuraModifier(AuraType.ModReputationGain); + + // faction specific auras only seem to apply to kills + if (source == ReputationSource.Kill) + repMod += GetTotalAuraModifierByMiscValue(AuraType.ModFactionReputationGain, faction); + + percent += rep > 0 ? repMod : -repMod; + + float rate; + switch (source) + { + case ReputationSource.Kill: + rate = WorldConfig.GetFloatValue(WorldCfg.RateReputationLowLevelKill); + break; + case ReputationSource.Quest: + case ReputationSource.DailyQuest: + case ReputationSource.WeeklyQuest: + case ReputationSource.MonthlyQuest: + case ReputationSource.RepeatableQuest: + rate = WorldConfig.GetFloatValue(WorldCfg.RateReputationLowLevelQuest); + break; + case ReputationSource.Spell: + default: + rate = 1.0f; + break; + } + + if (rate != 1.0f && creatureOrQuestLevel < Formulas.GetGrayLevel(getLevel())) + percent *= rate; + + if (percent <= 0.0f) + return 0; + + // Multiply result with the faction specific rate + RepRewardRate repData = Global.ObjectMgr.GetRepRewardRate((uint)faction); + if (repData != null) + { + float repRate = 0.0f; + switch (source) + { + case ReputationSource.Kill: + repRate = repData.creatureRate; + break; + case ReputationSource.Quest: + repRate = repData.questRate; + break; + case ReputationSource.DailyQuest: + repRate = repData.questDailyRate; + break; + case ReputationSource.WeeklyQuest: + repRate = repData.questWeeklyRate; + break; + case ReputationSource.MonthlyQuest: + repRate = repData.questMonthlyRate; + break; + case ReputationSource.RepeatableQuest: + repRate = repData.questRepeatableRate; + break; + case ReputationSource.Spell: + repRate = repData.spellRate; + break; + } + + // for custom, a rate of 0.0 will totally disable reputation gain for this faction/type + if (repRate <= 0.0f) + return 0; + + percent *= repRate; + } + + if (source != ReputationSource.Spell && GetsRecruitAFriendBonus(false)) + percent *= 1.0f + WorldConfig.GetFloatValue(WorldCfg.RateReputationRecruitAFriendBonus); + + return MathFunctions.CalculatePct(rep, percent); + } + // Calculates how many reputation points player gains in victim's enemy factions + public void RewardReputation(Unit victim, float rate) + { + if (!victim || victim.IsTypeId(TypeId.Player)) + return; + + if (victim.ToCreature().IsReputationGainDisabled()) + return; + + ReputationOnKillEntry Rep = Global.ObjectMgr.GetReputationOnKilEntry(victim.ToCreature().GetCreatureTemplate().Entry); + if (Rep == null) + return; + + uint ChampioningFaction = 0; + + if (GetChampioningFaction() != 0) + { + // support for: Championing - http://www.wowwiki.com/Championing + Map map = GetMap(); + if (map.IsNonRaidDungeon()) + { + LfgDungeonsRecord dungeon = Global.DB2Mgr.GetLfgDungeon(map.GetId(), map.GetDifficultyID()); + if (dungeon != null) + if (dungeon.TargetLevel == 80) + ChampioningFaction = GetChampioningFaction(); + } + } + + Team team = GetTeam(); + + if (Rep.RepFaction1 != 0 && (!Rep.TeamDependent || team == Team.Alliance)) + { + int donerep1 = CalculateReputationGain(ReputationSource.Kill, victim.getLevel(), Rep.RepValue1, (int)(ChampioningFaction != 0 ? ChampioningFaction : Rep.RepFaction1)); + donerep1 = (int)(donerep1 * rate); + + FactionRecord factionEntry1 = CliDB.FactionStorage.LookupByKey(ChampioningFaction != 0 ? ChampioningFaction : Rep.RepFaction1); + ReputationRank current_reputation_rank1 = GetReputationMgr().GetRank(factionEntry1); + if (factionEntry1 != null && (uint)current_reputation_rank1 <= Rep.ReputationMaxCap1) + GetReputationMgr().ModifyReputation(factionEntry1, donerep1); + } + + if (Rep.RepFaction2 != 0 && (!Rep.TeamDependent || team == Team.Horde)) + { + int donerep2 = CalculateReputationGain(ReputationSource.Kill, victim.getLevel(), Rep.RepValue2, (int)(ChampioningFaction != 0 ? ChampioningFaction : Rep.RepFaction2)); + donerep2 = (int)(donerep2 * rate); + + FactionRecord factionEntry2 = CliDB.FactionStorage.LookupByKey(ChampioningFaction != 0 ? ChampioningFaction : Rep.RepFaction2); + ReputationRank current_reputation_rank2 = GetReputationMgr().GetRank(factionEntry2); + if (factionEntry2 != null && (uint)current_reputation_rank2 <= Rep.ReputationMaxCap2) + GetReputationMgr().ModifyReputation(factionEntry2, donerep2); + } + } + // Calculate how many reputation points player gain with the quest + void RewardReputation(Quest quest) + { + for (byte i = 0; i < SharedConst.QuestRewardReputationsCount; ++i) + { + if (quest.RewardFactionId[i] == 0) + continue; + + FactionRecord factionEntry = CliDB.FactionStorage.LookupByKey(quest.RewardFactionId[i]); + if (factionEntry == null) + continue; + + int rep = 0; + bool noQuestBonus = false; + + if (quest.RewardFactionOverride[i] != 0) + { + rep = quest.RewardFactionOverride[i] / 100; + noQuestBonus = true; + } + else + { + uint row = (uint)((quest.RewardFactionValue[i] < 0) ? 1 : 0) + 1; + QuestFactionRewardRecord questFactionRewEntry = CliDB.QuestFactionRewardStorage.LookupByKey(row); + if (questFactionRewEntry != null) + { + uint field = (uint)Math.Abs(quest.RewardFactionValue[i]); + rep = questFactionRewEntry.QuestRewFactionValue[field]; + } + } + + if (rep == 0) + continue; + + if (quest.RewardFactionCapIn[i] != 0 && rep > 0 && (uint)GetReputationMgr().GetRank(factionEntry) >= quest.RewardFactionCapIn[i]) + continue; + + if (quest.IsDaily()) + rep = CalculateReputationGain(ReputationSource.DailyQuest, (uint)GetQuestLevel(quest), rep, (int)quest.RewardFactionId[i], noQuestBonus); + else if (quest.IsWeekly()) + rep = CalculateReputationGain(ReputationSource.WeeklyQuest, (uint)GetQuestLevel(quest), rep, (int)quest.RewardFactionId[i], noQuestBonus); + else if (quest.IsMonthly()) + rep = CalculateReputationGain(ReputationSource.MonthlyQuest, (uint)GetQuestLevel(quest), rep, (int)quest.RewardFactionId[i], noQuestBonus); + else if (quest.IsRepeatable()) + rep = CalculateReputationGain(ReputationSource.RepeatableQuest, (uint)GetQuestLevel(quest), rep, (int)quest.RewardFactionId[i], noQuestBonus); + else + rep = CalculateReputationGain(ReputationSource.Quest, (uint)GetQuestLevel(quest), rep, (int)quest.RewardFactionId[i], noQuestBonus); + + bool noSpillover = Convert.ToBoolean(quest.RewardReputationMask & (1 << i)); + GetReputationMgr().ModifyReputation(factionEntry, rep, noSpillover); + } + } + + //Movement + bool IsCanDelayTeleport() { return m_bCanDelayTeleport; } + void SetCanDelayTeleport(bool setting) { m_bCanDelayTeleport = setting; } + bool IsHasDelayedTeleport() { return m_bHasDelayedTeleport; } + void SetDelayedTeleportFlag(bool setting) { m_bHasDelayedTeleport = setting; } + public bool TeleportTo(WorldLocation loc, TeleportToOptions options = 0) + { + return TeleportTo(loc.GetMapId(), loc.posX, loc.posY, loc.posZ, loc.Orientation, options); + } + public bool TeleportTo(uint mapid, float x, float y, float z, float orientation, TeleportToOptions options = 0) + { + if (!GridDefines.IsValidMapCoord(mapid, x, y, z, orientation)) + { + Log.outError(LogFilter.Maps, "TeleportTo: invalid map ({0}) or invalid coordinates (X: {1}, Y: {2}, Z: {3}, O: {4}) given when teleporting player (GUID: {5}, name: {6}, map: {7}, {8}).", + mapid, x, y, z, orientation, GetGUID().ToString(), GetName(), GetMapId(), GetPosition().ToString()); + return false; + } + + if (!GetSession().HasPermission(RBACPermissions.SkipCheckDisableMap) && Global.DisableMgr.IsDisabledFor(DisableType.Map, mapid, this)) + { + Log.outError(LogFilter.Maps, "Player (GUID: {0}, name: {1}) tried to enter a forbidden map {2}", GetGUID().ToString(), GetName(), mapid); + SendTransferAborted(mapid, TransferAbortReason.MapNotAllowed); + return false; + } + + // preparing unsummon pet if lost (we must get pet before teleportation or will not find it later) + Pet pet = GetPet(); + + MapRecord mEntry = CliDB.MapStorage.LookupByKey(mapid); + + // don't let enter Battlegrounds without assigned Battlegroundid (for example through areatrigger)... + // don't let gm level > 1 either + if (!InBattleground() && mEntry.IsBattlegroundOrArena()) + return false; + + // client without expansion support + if (GetSession().GetExpansion() < mEntry.Expansion()) + { + Log.outDebug(LogFilter.Maps, "Player {0} using client without required expansion tried teleport to non accessible map {1}", GetName(), mapid); + + Transport _transport = GetTransport(); + if (_transport) + { + _transport.RemovePassenger(this); + RepopAtGraveyard(); // teleport to near graveyard if on transport, looks blizz like :) + } + + SendTransferAborted(mapid, TransferAbortReason.InsufExpanLvl, (byte)mEntry.Expansion()); + return false; // normal client can't teleport to this map... + } + else + Log.outDebug(LogFilter.Maps, "Player {0} is being teleported to map {1}", GetName(), mapid); + + if (m_vehicle != null) + ExitVehicle(); + + // reset movement flags at teleport, because player will continue move with these flags after teleport + SetUnitMovementFlags(GetUnitMovementFlags() & MovementFlag.MaskHasPlayerStatusOpcode); + m_movementInfo.ResetJump(); + DisableSpline(); + + Transport transport = GetTransport(); + if (transport) + { + if (!options.HasAnyFlag(TeleportToOptions.NotLeaveTransport)) + transport.RemovePassenger(this); + } + + // The player was ported to another map and loses the duel immediately. + // We have to perform this check before the teleport, otherwise the + // ObjectAccessor won't find the flag. + if (duel != null && GetMapId() != mapid && GetMap().GetGameObject(GetGuidValue(PlayerFields.DuelArbiter))) + DuelComplete(DuelCompleteType.Fled); + + if (GetMapId() == mapid) + { + //lets reset far teleport flag if it wasn't reset during chained teleports + SetSemaphoreTeleportFar(false); + //setup delayed teleport flag + SetDelayedTeleportFlag(IsCanDelayTeleport()); + //if teleport spell is casted in Unit.Update() func + //then we need to delay it until update process will be finished + if (IsHasDelayedTeleport()) + { + SetSemaphoreTeleportNear(true); + //lets save teleport destination for player + teleportDest = new WorldLocation(mapid, x, y, z, orientation); + m_teleport_options = options; + return true; + } + + if (!options.HasAnyFlag(TeleportToOptions.NotUnSummonPet)) + { + //same map, only remove pet if out of range for new position + if (pet && !pet.IsWithinDist3d(x, y, z, GetMap().GetVisibilityRange())) + UnsummonPetTemporaryIfAny(); + } + + if (!options.HasAnyFlag(TeleportToOptions.NotLeaveCombat)) + CombatStop(); + + // this will be used instead of the current location in SaveToDB + teleportDest = new WorldLocation(mapid, x, y, z, orientation); + m_teleport_options = options; + SetFallInformation(0, z); + + // code for finish transfer called in WorldSession.HandleMovementOpcodes() + // at client packet CMSG_MOVE_TELEPORT_ACK + SetSemaphoreTeleportNear(true); + // near teleport, triggering send CMSG_MOVE_TELEPORT_ACK from client at landing + if (!GetSession().PlayerLogout()) + SendTeleportPacket(teleportDest); + } + else + { + if (GetClass() == Class.Deathknight && GetMapId() == 609 && !IsGameMaster() && !HasSpell(50977)) + return false; + + // far teleport to another map + Map oldmap = IsInWorld ? GetMap() : null; + // check if we can enter before stopping combat / removing pet / totems / interrupting spells + + // Check enter rights before map getting to avoid creating instance copy for player + // this check not dependent from map instance copy and same for all instance copies of selected map + if (Global.MapMgr.PlayerCannotEnter(mapid, this, false) != 0) + return false; + + // Seamless teleport can happen only if cosmetic maps match + if (!oldmap || (oldmap.GetEntry().CosmeticParentMapID != mapid && GetMapId() != mEntry.CosmeticParentMapID && + !((oldmap.GetEntry().CosmeticParentMapID != -1) ^ (oldmap.GetEntry().CosmeticParentMapID != mEntry.CosmeticParentMapID)))) + options &= ~TeleportToOptions.Seamless; + + //lets reset near teleport flag if it wasn't reset during chained teleports + SetSemaphoreTeleportNear(false); + //setup delayed teleport flag + SetDelayedTeleportFlag(IsCanDelayTeleport()); + //if teleport spell is casted in Unit.Update() func + //then we need to delay it until update process will be finished + if (IsHasDelayedTeleport()) + { + SetSemaphoreTeleportFar(true); + //lets save teleport destination for player + teleportDest = new WorldLocation(mapid, x, y, z, orientation); + m_teleport_options = options; + return true; + } + + SetSelection(ObjectGuid.Empty); + + CombatStop(); + + ResetContestedPvP(); + + // remove player from Battlegroundon far teleport (when changing maps) + Battleground bg = GetBattleground(); + if (bg) + { + // Note: at Battlegroundjoin Battlegroundid set before teleport + // and we already will found "current" Battleground + // just need check that this is targeted map or leave + if (bg.GetMapId() != mapid) + LeaveBattleground(false); // don't teleport to entry point + } + + // remove arena spell coldowns/buffs now to also remove pet's cooldowns before it's temporarily unsummoned + if (mEntry.IsBattleArena()) + { + RemoveArenaSpellCooldowns(true); + RemoveArenaAuras(); + if (pet) + pet.RemoveArenaAuras(); + } + + // remove pet on map change + if (pet) + UnsummonPetTemporaryIfAny(); + + // remove all areatriggers entities + RemoveAllAreaTriggers(); + + // remove all dyn objects + RemoveAllDynObjects(); + + // stop spellcasting + // not attempt interrupt teleportation spell at caster teleport + if (!options.HasAnyFlag(TeleportToOptions.Spell)) + if (IsNonMeleeSpellCast(true)) + InterruptNonMeleeSpells(true); + + //remove auras before removing from map... + RemoveAurasWithInterruptFlags(SpellAuraInterruptFlags.ChangeMap | SpellAuraInterruptFlags.Move | SpellAuraInterruptFlags.Turning); + + if (!GetSession().PlayerLogout() && !options.HasAnyFlag(TeleportToOptions.Seamless)) + { + // send transfer packets + TransferPending transferPending = new TransferPending(); + transferPending.MapID = (int)mapid; + + transport = GetTransport(); + if (transport) + { + transferPending.Ship.HasValue = true; + transferPending.Ship.Value.ID = transport.GetEntry(); + transferPending.Ship.Value.OriginMapID = (int)GetMapId(); + } + + SendPacket(transferPending); + } + + // remove from old map now + if (oldmap != null) + oldmap.RemovePlayerFromMap(this, false); + + teleportDest = new WorldLocation(mapid, x, y, z, orientation); + m_teleport_options = options; + SetFallInformation(0, z); + // if the player is saved before worldportack (at logout for example) + // this will be used instead of the current location in SaveToDB + + if (!GetSession().PlayerLogout()) + { + SuspendToken suspendToken = new SuspendToken(); + suspendToken.SequenceIndex = m_movementCounter; // not incrementing + suspendToken.Reason = options.HasAnyFlag(TeleportToOptions.Seamless) ? 2 : 1u; + SendPacket(suspendToken); + } + + // move packet sent by client always after far teleport + // code for finish transfer to new map called in WorldSession.HandleMoveWorldportAckOpcode at client packet + SetSemaphoreTeleportFar(true); + + } + return true; + } + public bool TeleportToBGEntryPoint() + { + if (m_bgData.joinPos.GetMapId() == 0xFFFFFFFF) + return false; + + ScheduleDelayedOperation(PlayerDelayedOperations.BGMountRestore); + ScheduleDelayedOperation(PlayerDelayedOperations.BGTaxiRestore); + ScheduleDelayedOperation(PlayerDelayedOperations.BGGroupRestore); + return TeleportTo(m_bgData.joinPos); + } + public WorldLocation GetStartPosition() + { + PlayerInfo info = Global.ObjectMgr.GetPlayerInfo(GetRace(), GetClass()); + uint mapId = info.MapId; + if (GetClass() == Class.Deathknight && HasSpell(50977)) + mapId = 0; + return new WorldLocation(mapId, info.PositionX, info.PositionY, info.PositionZ, 0); + } + public override bool IsUnderWater() + { + return IsInWater() && + GetPositionZ() < (GetMap().GetWaterLevel(GetPositionX(), GetPositionY()) - 2); + } + public override bool IsInWater() + { + return m_isInWater; + } + public void SetInWater(bool apply) + { + if (m_isInWater == apply) + return; + + //define player in water by opcodes + //move player's guid into HateOfflineList of those mobs + //which can't swim and move guid back into ThreatList when + //on surface. + /// @todo exist also swimming mobs, and function must be symmetric to enter/leave water + m_isInWater = apply; + + // remove auras that need water/land + RemoveAurasWithInterruptFlags((apply ? SpellAuraInterruptFlags.NotAbovewater : SpellAuraInterruptFlags.NotUnderwater)); + + getHostileRefManager().updateThreatTables(); + } + public void ValidateMovementInfo(MovementInfo mi) + { + var RemoveViolatingFlags = new Action((check, maskToRemove) => + { + if (check) + { + Log.outDebug(LogFilter.Unit, "Player.ValidateMovementInfo: Violation of MovementFlags found ({0}). MovementFlags: {1}, MovementFlags2: {2} for player {3}. Mask {4} will be removed.", + check, mi.GetMovementFlags(), mi.GetMovementFlags2(), GetGUID().ToString(), maskToRemove); + mi.RemoveMovementFlag(maskToRemove); + } + }); + + if (!GetVehicleBase() || !GetVehicle().GetVehicleInfo().Flags.HasAnyFlag(VehicleFlags.FixedPosition)) + RemoveViolatingFlags(mi.HasMovementFlag(MovementFlag.Root), MovementFlag.Root); + + /*! This must be a packet spoofing attempt. MOVEMENTFLAG_ROOT sent from the client is not valid + in conjunction with any of the moving movement flags such as MOVEMENTFLAG_FORWARD. + It will freeze clients that receive this player's movement info. + */ + RemoveViolatingFlags(mi.HasMovementFlag(MovementFlag.Root) && mi.HasMovementFlag(MovementFlag.MaskMoving), MovementFlag.MaskMoving); + + //! Cannot hover without SPELL_AURA_HOVER + RemoveViolatingFlags(mi.HasMovementFlag(MovementFlag.Hover) && !HasAuraType(AuraType.Hover), + MovementFlag.Hover); + + //! Cannot ascend and descend at the same time + RemoveViolatingFlags(mi.HasMovementFlag(MovementFlag.Ascending) && mi.HasMovementFlag(MovementFlag.Descending), + MovementFlag.Ascending | MovementFlag.Descending); + + //! Cannot move left and right at the same time + RemoveViolatingFlags(mi.HasMovementFlag(MovementFlag.Left) && mi.HasMovementFlag(MovementFlag.Right), + MovementFlag.Left | MovementFlag.Right); + + //! Cannot strafe left and right at the same time + RemoveViolatingFlags(mi.HasMovementFlag(MovementFlag.StrafeLeft) && mi.HasMovementFlag(MovementFlag.StrafeRight), + MovementFlag.StrafeLeft | MovementFlag.StrafeRight); + + //! Cannot pitch up and down at the same time + RemoveViolatingFlags(mi.HasMovementFlag(MovementFlag.PitchUp) && mi.HasMovementFlag(MovementFlag.PitchDown), + MovementFlag.PitchUp | MovementFlag.PitchDown); + + //! Cannot move forwards and backwards at the same time + RemoveViolatingFlags(mi.HasMovementFlag(MovementFlag.Forward) && mi.HasMovementFlag(MovementFlag.Backward), + MovementFlag.Forward | MovementFlag.Backward); + + //! Cannot walk on water without SPELL_AURA_WATER_WALK except for ghosts + RemoveViolatingFlags(mi.HasMovementFlag(MovementFlag.WaterWalk) && + !HasAuraType(AuraType.WaterWalk) && !HasAuraType(AuraType.Ghost), MovementFlag.WaterWalk); + + //! Cannot feather fall without SPELL_AURA_FEATHER_FALL + RemoveViolatingFlags(mi.HasMovementFlag(MovementFlag.FallingSlow) && !HasAuraType(AuraType.FeatherFall), + MovementFlag.FallingSlow); + + /*! Cannot fly if no fly auras present. Exception is being a GM. + Note that we check for account level instead of Player.IsGameMaster() because in some + situations it may be feasable to use .gm fly on as a GM without having .gm on, + e.g. aerial combat. + */ + + RemoveViolatingFlags(mi.HasMovementFlag(MovementFlag.Flying | MovementFlag.CanFly) && GetSession().GetSecurity() == AccountTypes.Player && + !m_unitMovedByMe.HasAuraType(AuraType.Fly) && + !m_unitMovedByMe.HasAuraType(AuraType.ModIncreaseMountedFlightSpeed), + MovementFlag.Flying | MovementFlag.CanFly); + + RemoveViolatingFlags(mi.HasMovementFlag(MovementFlag.DisableGravity | MovementFlag.CanFly) && mi.HasMovementFlag(MovementFlag.Falling), + MovementFlag.Falling); + + RemoveViolatingFlags(mi.HasMovementFlag(MovementFlag.SplineElevation) && MathFunctions.fuzzyEq(mi.SplineElevation, 0.0f), MovementFlag.SplineElevation); + + // Client first checks if spline elevation != 0, then verifies flag presence + if (MathFunctions.fuzzyNe(mi.SplineElevation, 0.0f)) + mi.AddMovementFlag(MovementFlag.SplineElevation); + } + public void HandleFall(MovementInfo movementInfo) + { + // calculate total z distance of the fall + float z_diff = m_lastFallZ - movementInfo.Pos.posZ; + Log.outDebug(LogFilter.Server, "zDiff = {0}", z_diff); + + //Players with low fall distance, Feather Fall or physical immunity (charges used) are ignored + // 14.57 can be calculated by resolving damageperc formula below to 0 + if (z_diff >= 14.57f && !IsDead() && !IsGameMaster() && + !HasAuraType(AuraType.Hover) && !HasAuraType(AuraType.FeatherFall) && + !HasAuraType(AuraType.Fly) && !IsImmunedToDamage(SpellSchoolMask.Normal)) + { + //Safe fall, fall height reduction + int safe_fall = GetTotalAuraModifier(AuraType.SafeFall); + + float damageperc = 0.018f * (z_diff - safe_fall) - 0.2426f; + + if (damageperc > 0) + { + uint damage = (uint)(damageperc * GetMaxHealth() * WorldConfig.GetFloatValue(WorldCfg.RateDamageFall)); + + float height = movementInfo.Pos.posZ; + UpdateGroundPositionZ(movementInfo.Pos.posX, movementInfo.Pos.posY, ref height); + + if (damage > 0) + { + //Prevent fall damage from being more than the player maximum health + if (damage > GetMaxHealth()) + damage = (uint)GetMaxHealth(); + + // Gust of Wind + if (HasAura(43621)) + damage = (uint)GetMaxHealth() / 2; + + uint original_health = (uint)GetHealth(); + uint final_damage = EnvironmentalDamage(EnviromentalDamage.Fall, damage); + + // recheck alive, might have died of EnvironmentalDamage, avoid cases when player die in fact like Spirit of Redemption case + if (IsAlive() && final_damage < original_health) + UpdateCriteria(CriteriaTypes.FallWithoutDying, (uint)z_diff * 100); + } + + //Z given by moveinfo, LastZ, FallTime, WaterZ, MapZ, Damage, Safefall reduction + Log.outDebug(LogFilter.Player, "FALLDAMAGE z={0} sz={1} pZ{2} FallTime={3} mZ={4} damage={5} SF={6}", + movementInfo.Pos.posZ, height, GetPositionZ(), movementInfo.jump.fallTime, height, damage, safe_fall); + } + } + RemoveAurasWithInterruptFlags(SpellAuraInterruptFlags.Landing); // No fly zone - Parachute + } + public void UpdateFallInformationIfNeed(MovementInfo minfo, ClientOpcodes opcode) + { + if (m_lastFallTime >= m_movementInfo.jump.fallTime || m_lastFallZ <= m_movementInfo.Pos.posZ || opcode == ClientOpcodes.MoveFallLand) + SetFallInformation(m_movementInfo.jump.fallTime, m_movementInfo.Pos.posZ); + } + + public bool HasSummonPending() + { + return m_summon_expire >= Time.UnixTime; + } + + public void SendSummonRequestFrom(Unit summoner) + { + if (!summoner) + return; + + // Player already has active summon request + if (HasSummonPending()) + return; + + // Evil Twin (ignore player summon, but hide this for summoner) + if (HasAura(23445)) + return; + + m_summon_expire = Time.UnixTime + PlayerConst.MaxPlayerSummonDelay; + m_summon_location = new WorldLocation(summoner); + + SummonRequest summonRequest = new SummonRequest(); + summonRequest.SummonerGUID = summoner.GetGUID(); + summonRequest.SummonerVirtualRealmAddress = Global.WorldMgr.GetVirtualRealmAddress(); + summonRequest.AreaID = (int)summoner.GetZoneId(); + SendPacket(summonRequest); + } + public bool IsInAreaTriggerRadius(AreaTriggerRecord trigger) + { + if (trigger == null || GetMapId() != trigger.MapID) + return false; + + if (trigger.Radius > 0.0f) + { + // if we have radius check it + float dist = GetDistance(trigger.Pos.X, trigger.Pos.Y, trigger.Pos.Z); + if (dist > trigger.Radius) + return false; + } + else + { + Position center = new Position(trigger.Pos.X, trigger.Pos.Y, trigger.Pos.Z, trigger.BoxYaw); + if (!IsWithinBox(center, trigger.BoxLength / 2.0f, trigger.BoxWidth / 2.0f, trigger.BoxHeight / 2.0f)) + return false; + } + + return true; + } + + public void SummonIfPossible(bool agree) + { + if (!agree) + { + m_summon_expire = 0; + return; + } + + // expire and auto declined + if (m_summon_expire < Time.UnixTime) + return; + + // stop taxi flight at summon + if (IsInFlight()) + { + GetMotionMaster().MovementExpired(); + CleanupAfterTaxiFlight(); + } + + // drop flag at summon + // this code can be reached only when GM is summoning player who carries flag, because player should be immune to summoning spells when he carries flag + Battleground bg = GetBattleground(); + if (bg) + bg.EventPlayerDroppedFlag(this); + + m_summon_expire = 0; + + UpdateCriteria(CriteriaTypes.AcceptedSummonings, 1); + + m_summon_location.SetOrientation(GetOrientation()); + TeleportTo(m_summon_location); + } + + //GM + public bool isAcceptWhispers() { return m_ExtraFlags.HasAnyFlag(PlayerExtraFlags.AcceptWhispers); } + public void SetAcceptWhispers(bool on) + { + if (on) + m_ExtraFlags |= PlayerExtraFlags.AcceptWhispers; + else + m_ExtraFlags &= ~PlayerExtraFlags.AcceptWhispers; + } + public bool IsGameMaster() { return Convert.ToBoolean(m_ExtraFlags & PlayerExtraFlags.GMOn); } + public bool CanBeGameMaster() { return GetSession().HasPermission(RBACPermissions.CommandGm); } + public void SetGameMaster(bool on) + { + if (on) + { + m_ExtraFlags |= PlayerExtraFlags.GMOn; + SetFaction(35); + SetFlag(PlayerFields.Flags, PlayerFlags.GM); + SetFlag(UnitFields.Flags2, UnitFlags2.AllowCheatSpells); + + Pet pet = GetPet(); + if (pet != null) + { + pet.SetFaction(35); + pet.getHostileRefManager().setOnlineOfflineState(false); + } + + RemoveByteFlag(UnitFields.Bytes2, UnitBytes2Offsets.PvpFlag, UnitBytes2Flags.FFAPvp); + ResetContestedPvP(); + + getHostileRefManager().setOnlineOfflineState(false); + CombatStopWithPets(); + + m_serverSideVisibilityDetect.SetValue(ServerSideVisibilityType.GM, GetSession().GetSecurity()); + } + else + { + m_ExtraFlags &= ~PlayerExtraFlags.GMOn; + SetFactionForRace(GetRace()); + RemoveFlag(PlayerFields.Flags, PlayerFlags.GM); + RemoveFlag(UnitFields.Flags2, UnitFlags2.AllowCheatSpells); + + Pet pet = GetPet(); + if (pet != null) + { + pet.SetFaction(getFaction()); + pet.getHostileRefManager().setOnlineOfflineState(true); + } + + // restore FFA PvP Server state + if (Global.WorldMgr.IsFFAPvPRealm()) + SetByteFlag(UnitFields.Bytes2, UnitBytes2Offsets.PvpFlag, UnitBytes2Flags.FFAPvp); + + // restore FFA PvP area state, remove not allowed for GM mounts + UpdateArea(m_areaUpdateId); + + getHostileRefManager().setOnlineOfflineState(true); + m_serverSideVisibilityDetect.SetValue(ServerSideVisibilityType.GM, AccountTypes.Player); + } + + UpdateObjectVisibility(); + } + public bool isGMChat() { return m_ExtraFlags.HasAnyFlag(PlayerExtraFlags.GMChat); } + public void SetGMChat(bool on) + { + if (on) + m_ExtraFlags |= PlayerExtraFlags.GMChat; + else + m_ExtraFlags &= ~PlayerExtraFlags.GMChat; + } + public bool isTaxiCheater() { return m_ExtraFlags.HasAnyFlag(PlayerExtraFlags.TaxiCheat); } + public void SetTaxiCheater(bool on) + { + if (on) + m_ExtraFlags |= PlayerExtraFlags.TaxiCheat; + else + m_ExtraFlags &= ~PlayerExtraFlags.TaxiCheat; + } + public bool isGMVisible() { return !m_ExtraFlags.HasAnyFlag(PlayerExtraFlags.GMInvisible); } + public void SetGMVisible(bool on) + { + if (on) + { + m_ExtraFlags &= ~PlayerExtraFlags.GMInvisible; //remove flag + m_serverSideVisibility.SetValue(ServerSideVisibilityType.GM, AccountTypes.Player); + } + else + { + m_ExtraFlags |= PlayerExtraFlags.GMInvisible; //add flag + + SetAcceptWhispers(false); + SetGameMaster(true); + + m_serverSideVisibility.SetValue(ServerSideVisibilityType.GM, GetSession().GetSecurity()); + } + + foreach (Channel channel in m_channels) + channel.SetInvisible(this, !on); + } + + //Chat - Text - Channel + public void PrepareGossipMenu(WorldObject source, uint menuId = 0, bool showQuests = false) + { + PlayerMenu menu = PlayerTalkClass; + menu.ClearMenus(); + + menu.GetGossipMenu().SetMenuId(menuId); + + var menuItemBounds = Global.ObjectMgr.GetGossipMenuItemsMapBounds(menuId); + + // if default menuId and no menu options exist for this, use options from default options + if (menuItemBounds.Empty() && menuId == GetDefaultGossipMenuForSource(source)) + menuItemBounds = Global.ObjectMgr.GetGossipMenuItemsMapBounds(0); + + NPCFlags npcflags = 0; + + if (source.IsTypeId(TypeId.Unit)) + { + npcflags = (NPCFlags)source.GetUInt64Value(UnitFields.NpcFlags); + if (Convert.ToBoolean(npcflags & NPCFlags.QuestGiver) && showQuests) + PrepareQuestMenu(source.GetGUID()); + } + else if (source.IsTypeId(TypeId.GameObject)) + if (source.ToGameObject().GetGoType() == GameObjectTypes.QuestGiver) + PrepareQuestMenu(source.GetGUID()); + + foreach (var menuItems in menuItemBounds) + { + bool canTalk = true; + if (!Global.ConditionMgr.IsObjectMeetToConditions(this, source, menuItems.Conditions)) + continue; + + GameObject go = source.ToGameObject(); + Creature creature = source.ToCreature(); + if (creature) + { + if (!menuItems.OptionNpcflag.HasAnyFlag(npcflags)) + continue; + + switch (menuItems.OptionType) + { + case GossipOption.Armorer: + canTalk = false; // added in special mode + break; + case GossipOption.Spirithealer: + if (!IsDead()) + canTalk = false; + break; + case GossipOption.Vendor: + VendorItemData vendorItems = creature.GetVendorItems(); + if (vendorItems == null || vendorItems.Empty()) + { + Log.outError(LogFilter.Sql, "Creature (GUID: {0}, Entry: {1}) have UNIT_NPC_FLAG_VENDOR but have empty trading item list.", creature.GetGUID().ToString(), creature.GetEntry()); + canTalk = false; + } + break; + case GossipOption.Learndualspec: + canTalk = false; + break; + case GossipOption.Unlearntalents: + if (!creature.isCanTrainingAndResetTalentsOf(this)) + canTalk = false; + break; + case GossipOption.Unlearnpettalents: + if (GetPet() == null || GetPet().getPetType() != PetType.Hunter || GetPet().m_spells.Count <= 1 || creature.GetCreatureTemplate().TrainerType != TrainerType.Pets || creature.GetCreatureTemplate().TrainerClass != Class.Hunter) + canTalk = false; + break; + case GossipOption.Taxivendor: + if (GetSession().SendLearnNewTaxiNode(creature)) + return; + break; + case GossipOption.Battlefield: + if (!creature.isCanInteractWithBattleMaster(this, false)) + canTalk = false; + break; + case GossipOption.Stablepet: + if (GetClass() != Class.Hunter) + canTalk = false; + break; + case GossipOption.Questgiver: + canTalk = false; + break; + case GossipOption.Trainer: + if (GetClass() != creature.GetCreatureTemplate().TrainerClass && creature.GetCreatureTemplate().TrainerType == TrainerType.Class) + Log.outError(LogFilter.Sql, "GOSSIP_OPTION_TRAINER. Player {0} (GUID: {1}) request wrong gossip menu: {2} with wrong class: {3} at Creature: {4} (Entry: {5}, Trainer Class: {6})", + GetName(), GetGUID().ToString(), menu.GetGossipMenu().GetMenuId(), GetClass(), creature.GetName(), creature.GetEntry(), creature.GetCreatureTemplate().TrainerClass); + canTalk = false; + break; + case GossipOption.Gossip: + case GossipOption.Spiritguide: + case GossipOption.Innkeeper: + case GossipOption.Banker: + case GossipOption.Petitioner: + case GossipOption.Tabarddesigner: + case GossipOption.Auctioneer: + break; // no checks + case GossipOption.Outdoorpvp: + if (!Global.OutdoorPvPMgr.CanTalkTo(this, creature, menuItems)) + canTalk = false; + break; + default: + Log.outError(LogFilter.Sql, "Creature entry {0} have unknown gossip option {1} for menu {2}", creature.GetEntry(), menuItems.OptionType, menuItems.MenuId); + canTalk = false; + break; + } + } + else if (go != null) + { + switch (menuItems.OptionType) + { + case GossipOption.Gossip: + if (go.GetGoType() != GameObjectTypes.QuestGiver && go.GetGoType() != GameObjectTypes.Goober) + canTalk = false; + break; + default: + canTalk = false; + break; + } + } + + if (canTalk) + { + string strOptionText = ""; + string strBoxText = ""; + BroadcastTextRecord optionBroadcastText = CliDB.BroadcastTextStorage.LookupByKey(menuItems.OptionBroadcastTextId); + BroadcastTextRecord boxBroadcastText = CliDB.BroadcastTextStorage.LookupByKey(menuItems.BoxBroadcastTextId); + LocaleConstant locale = GetSession().GetSessionDbLocaleIndex(); + + if (optionBroadcastText != null) + strOptionText = Global.DB2Mgr.GetBroadcastTextValue(optionBroadcastText, locale, GetGender()); + else + strOptionText = menuItems.OptionText; + + if (boxBroadcastText != null) + strBoxText = Global.DB2Mgr.GetBroadcastTextValue(boxBroadcastText, locale, GetGender()); + else + strBoxText = menuItems.BoxText; + + if (locale != LocaleConstant.enUS) + { + if (optionBroadcastText == null) + { + // Find localizations from database. + GossipMenuItemsLocale gossipMenuLocale = Global.ObjectMgr.GetGossipMenuItemsLocale(menuId, menuItems.OptionIndex); + if (gossipMenuLocale != null) + ObjectManager.GetLocaleString(gossipMenuLocale.OptionText, locale, ref strOptionText); + } + + if (boxBroadcastText == null) + { + // Find localizations from database. + GossipMenuItemsLocale gossipMenuLocale = Global.ObjectMgr.GetGossipMenuItemsLocale(menuId, menuItems.OptionIndex); + if (gossipMenuLocale != null) + ObjectManager.GetLocaleString(gossipMenuLocale.BoxText, locale, ref strBoxText); + } + } + + menu.GetGossipMenu().AddMenuItem((int)menuItems.OptionIndex, menuItems.OptionIcon, strOptionText, 0, (uint)menuItems.OptionType, strBoxText, menuItems.BoxMoney, menuItems.BoxCoded); + menu.GetGossipMenu().AddGossipMenuItemData(menuItems.OptionIndex, menuItems.ActionMenuId, menuItems.ActionPoiId); + } + } + } + public void SendPreparedGossip(WorldObject source) + { + if (!source) + return; + + if (source.IsTypeId(TypeId.Unit) || source.IsTypeId(TypeId.GameObject)) + { + if (PlayerTalkClass.GetGossipMenu().IsEmpty() && !PlayerTalkClass.GetQuestMenu().IsEmpty()) + { + SendPreparedQuest(source.GetGUID()); + return; + } + } + + // in case non empty gossip menu (that not included quests list size) show it + // (quest entries from quest menu will be included in list) + + uint textId = GetGossipTextId(source); + uint menuId = PlayerTalkClass.GetGossipMenu().GetMenuId(); + if (menuId != 0) + textId = GetGossipTextId(menuId, source); + + PlayerTalkClass.SendGossipMenu(textId, source.GetGUID()); + } + public void OnGossipSelect(WorldObject source, uint gossipListId, uint menuId) + { + GossipMenu gossipMenu = PlayerTalkClass.GetGossipMenu(); + + // if not same, then something funky is going on + if (menuId != gossipMenu.GetMenuId()) + return; + + GossipMenuItem item = gossipMenu.GetItem(gossipListId); + if (item == null) + return; + + uint gossipOptionId = item.OptionType; + ObjectGuid guid = source.GetGUID(); + + if (source.IsTypeId(TypeId.GameObject)) + { + if (gossipOptionId > (int)GossipOption.Questgiver) + { + Log.outError(LogFilter.Player, "Player guid {0} request invalid gossip option for GameObject entry {1}", GetGUID().ToString(), source.GetEntry()); + return; + } + } + + GossipMenuItemData menuItemData = gossipMenu.GetItemData(gossipListId); + if (menuItemData == null) + return; + + int cost = (int)item.BoxMoney; + if (!HasEnoughMoney(cost)) + { + SendBuyError(BuyResult.NotEnoughtMoney, null, 0); + PlayerTalkClass.SendCloseGossip(); + return; + } + + switch ((GossipOption)gossipOptionId) + { + case GossipOption.Gossip: + { + if (menuItemData.GossipActionPoi != 0) + PlayerTalkClass.SendPointOfInterest(menuItemData.GossipActionPoi); + + if (menuItemData.GossipActionMenuId != 0) + { + PrepareGossipMenu(source, menuItemData.GossipActionMenuId); + SendPreparedGossip(source); + } + + break; + } + case GossipOption.Outdoorpvp: + Global.OutdoorPvPMgr.HandleGossipOption(this, source.ToCreature(), gossipListId); + break; + case GossipOption.Spirithealer: + if (IsDead()) + source.ToCreature().CastSpell(source.ToCreature(), 17251, true, null, null, GetGUID()); + break; + case GossipOption.Questgiver: + PrepareQuestMenu(guid); + SendPreparedQuest(guid); + break; + case GossipOption.Vendor: + case GossipOption.Armorer: + GetSession().SendListInventory(guid); + break; + case GossipOption.Stablepet: + GetSession().SendStablePet(guid); + break; + case GossipOption.Trainer: + GetSession().SendTrainerList(guid); + break; + case GossipOption.Learndualspec: + break; + case GossipOption.Unlearntalents: + PlayerTalkClass.SendCloseGossip(); + SendRespecWipeConfirm(guid, GetNextResetTalentsCost()); + break; + case GossipOption.Unlearnpettalents: + PlayerTalkClass.SendCloseGossip(); + ResetPetTalents(); + break; + case GossipOption.Taxivendor: + GetSession().SendTaxiMenu(source.ToCreature()); + break; + case GossipOption.Innkeeper: + PlayerTalkClass.SendCloseGossip(); + SetBindPoint(guid); + break; + case GossipOption.Banker: + GetSession().SendShowBank(guid); + break; + case GossipOption.Petitioner: + PlayerTalkClass.SendCloseGossip(); + GetSession().SendPetitionShowList(guid); + break; + case GossipOption.Tabarddesigner: + PlayerTalkClass.SendCloseGossip(); + GetSession().SendTabardVendorActivate(guid); + break; + case GossipOption.Auctioneer: + GetSession().SendAuctionHello(guid, source.ToCreature()); + break; + case GossipOption.Spiritguide: + PrepareGossipMenu(source); + SendPreparedGossip(source); + break; + case GossipOption.Battlefield: + { + BattlegroundTypeId bgTypeId = Global.BattlegroundMgr.GetBattleMasterBG(source.GetEntry()); + + if (bgTypeId == BattlegroundTypeId.None) + { + Log.outError(LogFilter.Player, "a user (guid {0}) requested Battlegroundlist from a npc who is no battlemaster", GetGUID().ToString()); + return; + } + + Global.BattlegroundMgr.SendBattlegroundList(this, guid, bgTypeId); + break; + } + } + + ModifyMoney(-cost); + } + public uint GetGossipTextId(WorldObject source) + { + if (source == null) + return SharedConst.DefaultGossipMessage; + + return GetGossipTextId(GetDefaultGossipMenuForSource(source), source); + } + uint GetGossipTextId(uint menuId, WorldObject source) + { + uint textId = SharedConst.DefaultGossipMessage; + + if (menuId == 0) + return textId; + + var menuBounds = Global.ObjectMgr.GetGossipMenusMapBounds(menuId); + + foreach (var menu in menuBounds) + { + if (Global.ConditionMgr.IsObjectMeetToConditions(this, source, menu.conditions)) + textId = menu.text_id; + } + + return textId; + } + uint GetDefaultGossipMenuForSource(WorldObject source) + { + switch (source.GetTypeId()) + { + case TypeId.Unit: + return source.ToCreature().GetCreatureTemplate().GossipMenuId; + case TypeId.GameObject: + return source.ToGameObject().GetGoInfo().GetGossipMenuId(); + default: + break; + } + + return 0; + } + + public bool CanJoinConstantChannelInZone(ChatChannelsRecord channel, AreaTableRecord zone) + { + if (channel.Flags.HasAnyFlag(ChannelDBCFlags.ZoneDep) && zone.Flags[0].HasAnyFlag(AreaFlags.ArenaInstance)) + return false; + + if (channel.Flags.HasAnyFlag(ChannelDBCFlags.CityOnly) && !zone.Flags[0].HasAnyFlag(AreaFlags.Capital)) + return false; + + if (channel.Flags.HasAnyFlag(ChannelDBCFlags.GuildReq) && GetGuildId() != 0) + return false; + + return true; + } + public void JoinedChannel(Channel c) + { + m_channels.Add(c); + } + public void LeftChannel(Channel c) + { + m_channels.Remove(c); + } + public void CleanupChannels() + { + while (!m_channels.Empty()) + { + Channel ch = m_channels.FirstOrDefault(); + m_channels.RemoveAt(0); // remove from player's channel list + ch.LeaveChannel(this, false); // not send to client, not remove from player's channel list + + // delete channel if empty + ChannelManager cMgr = ChannelManager.ForTeam(GetTeam()); + if (cMgr != null) + { + if (ch.IsConstant()) + cMgr.LeftChannel(ch.GetChannelId(), ch.GetZoneEntry()); + else + cMgr.LeftChannel(ch.GetName()); + } + } + Log.outDebug(LogFilter.ChatSystem, "Player {0}: channels cleaned up!", GetName()); + } + void UpdateLocalChannels(uint newZone) + { + if (GetSession().PlayerLoading() && !IsBeingTeleportedFar()) + return; // The client handles it automatically after loading, but not after teleporting + + AreaTableRecord current_zone = CliDB.AreaTableStorage.LookupByKey(newZone); + if (current_zone == null) + return; + + ChannelManager cMgr = ChannelManager.ForTeam(GetTeam()); + if (cMgr == null) + return; + + foreach (var channelEntry in CliDB.ChatChannelsStorage.Values) + { + Channel usedChannel = null; + foreach (var channel in m_channels) + { + if (channel.GetChannelId() == channelEntry.Id) + { + usedChannel = channel; + break; + } + } + + Channel removeChannel = null; + Channel joinChannel = null; + bool sendRemove = true; + + if (CanJoinConstantChannelInZone(channelEntry, current_zone)) + { + if (!channelEntry.Flags.HasAnyFlag(ChannelDBCFlags.Global)) + { + if (channelEntry.Flags.HasAnyFlag(ChannelDBCFlags.CityOnly) && usedChannel != null) + continue; // Already on the channel, as city channel names are not changing + + joinChannel = cMgr.GetJoinChannel(channelEntry.Id, "", current_zone); + if (usedChannel != null) + { + if (joinChannel != usedChannel) + { + removeChannel = usedChannel; + sendRemove = false; // Do not send leave channel, it already replaced at client + } + else + joinChannel = null; + } + } + else + joinChannel = cMgr.GetJoinChannel(channelEntry.Id, ""); + } + else + removeChannel = usedChannel; + + if (joinChannel != null) + joinChannel.JoinChannel(this, ""); // Changed Channel: ... or Joined Channel: ... + + if (removeChannel != null) + { + removeChannel.LeaveChannel(this, sendRemove); // Leave old channel + + LeftChannel(removeChannel); // Remove from player's channel list + cMgr.LeftChannel(removeChannel.GetChannelId(), removeChannel.GetZoneEntry()); // Delete if empty + } + } + } + + public List GetJoinedChannels() { return m_channels; } + + //Mail + public void AddMail(Mail mail) { m_mail.Insert(0, mail); } + public void RemoveMail(uint id) + { + foreach (var mail in m_mail) + { + if (mail.messageID == id) + { + //do not delete item, because Player.removeMail() is called when returning mail to sender. + m_mail.Remove(mail); + return; + } + } + } + public void SendMailResult(uint mailId, MailResponseType mailAction, MailResponseResult mailError, InventoryResult equipError = 0, uint item_guid = 0, uint item_count = 0) + { + MailCommandResult result = new MailCommandResult(); + result.MailID = mailId; + result.Command = (uint)mailAction; + result.ErrorCode = (uint)mailError; + + if (mailError == MailResponseResult.EquipError) + result.BagResult = (uint)equipError; + else if (mailAction == MailResponseType.ItemTaken) + { + result.AttachID = item_guid; + result.QtyInInventory = item_count; + } + + SendPacket(result); + } + void SendNewMail() + { + SendPacket(new NotifyRecievedMail()); + } + public void UpdateNextMailTimeAndUnreads() + { + // calculate next delivery time (min. from non-delivered mails + // and recalculate unReadMail + long cTime = Time.UnixTime; + m_nextMailDelivereTime = 0; + unReadMails = 0; + foreach (var mail in m_mail) + { + if (mail.deliver_time > cTime) + { + if (m_nextMailDelivereTime == 0 || m_nextMailDelivereTime > mail.deliver_time) + m_nextMailDelivereTime = mail.deliver_time; + } + else if ((mail.checkMask & MailCheckMask.Read) == 0) + ++unReadMails; + } + } + public void AddNewMailDeliverTime(long deliver_time) + { + if (deliver_time <= Time.UnixTime) // ready now + { + ++unReadMails; + SendNewMail(); + } + else // not ready and no have ready mails + { + if (m_nextMailDelivereTime == 0 || m_nextMailDelivereTime > deliver_time) + m_nextMailDelivereTime = deliver_time; + } + } + public bool IsMailsLoaded() { return m_mailsLoaded; } + public void AddMItem(Item it) + { + mMitems[it.GetGUID().GetCounter()] = it; + } + public bool RemoveMItem(ulong id) + { + return mMitems.Remove(id); + } + public Item GetMItem(ulong id) { return mMitems.LookupByKey(id); } + public Mail GetMail(uint id) { return m_mail.Find(p => p.messageID == id); } + public List GetMails() { return m_mail; } + + //Binds + public bool HasPendingBind() { return _pendingBindId > 0; } + void UpdateHomebindTime(uint time) + { + // GMs never get homebind timer online + if (m_InstanceValid || IsGameMaster()) + { + if (m_HomebindTimer != 0) // instance valid, but timer not reset + SendRaidGroupOnlyMessage(RaidGroupReason.None, 0); + + // instance is valid, reset homebind timer + m_HomebindTimer = 0; + } + else if (m_HomebindTimer > 0) + { + if (time >= m_HomebindTimer) + { + // teleport to nearest graveyard + RepopAtGraveyard(); + } + else + m_HomebindTimer -= time; + } + else + { + // instance is invalid, start homebind timer + m_HomebindTimer = 60000; + // send message to player + SendRaidGroupOnlyMessage(RaidGroupReason.RequirementsUnmatch, (int)m_HomebindTimer); + Log.outDebug(LogFilter.Maps, "PLAYER: Player '{0}' (GUID: {1}) will be teleported to homebind in 60 seconds", GetName(), GetGUID().ToString()); + } + } + public void SetHomebind(WorldLocation loc, uint areaId) + { + homebind = loc; + homebindAreaId = areaId; + + // update sql homebind + PreparedStatement stmt = DB.Characters.GetPreparedStatement(CharStatements.UPD_PLAYER_HOMEBIND); + stmt.AddValue(0, homebind.GetMapId()); + stmt.AddValue(1, homebindAreaId); + stmt.AddValue(2, homebind.posX); + stmt.AddValue(3, homebind.posY); + stmt.AddValue(4, homebind.posZ); + stmt.AddValue(5, GetGUID().GetCounter()); + DB.Characters.Execute(stmt); + } + void SetBindPoint(ObjectGuid guid) + { + BinderConfirm packet = new BinderConfirm(guid); + SendPacket(packet); + } + public void SendBindPointUpdate() + { + BindPointUpdate packet = new BindPointUpdate(); + packet.BindPosition.X = homebind.GetPositionX(); + packet.BindPosition.Y = homebind.GetPositionY(); + packet.BindPosition.Z = homebind.GetPositionZ(); + packet.BindMapID = homebind.GetMapId(); + packet.BindAreaID = homebindAreaId; + SendPacket(packet); + } + + //Misc + public uint GetTotalPlayedTime() { return m_PlayedTimeTotal; } + public uint GetLevelPlayedTime() { return m_PlayedTimeLevel; } + + public CinematicManager GetCinematicMgr() { return _cinematicMgr; } + + public void SendUpdateWorldState(uint variable, uint value, bool hidden = false) + { + UpdateWorldState worldstate = new UpdateWorldState(); + worldstate.VariableID = variable; + worldstate.Value = (int)value; + worldstate.Hidden = hidden; + SendPacket(worldstate); + } + + void SendInitWorldStates(uint zoneid, uint areaid) + { + // data depends on zoneid/mapid... + Battleground bg = GetBattleground(); + uint mapid = GetMapId(); + OutdoorPvP pvp = Global.OutdoorPvPMgr.GetOutdoorPvPToZoneId(zoneid); + InstanceScript instance = GetInstanceScript(); + BattleField bf = Global.BattleFieldMgr.GetBattlefieldToZoneId(zoneid); + + InitWorldStates packet = new InitWorldStates(); + packet.MapID = mapid; + packet.AreaID = zoneid; + packet.SubareaID = areaid; + packet.AddState(2264, 0); // 1 + packet.AddState(2263, 0); // 2 + packet.AddState(2262, 0); // 3 + packet.AddState(2261, 0); // 4 + packet.AddState(2260, 0); // 5 + packet.AddState(2259, 0); // 6 + + packet.AddState(3191, WorldConfig.GetBoolValue(WorldCfg.ArenaSeasonInProgress) ? WorldConfig.GetIntValue(WorldCfg.ArenaSeasonId) : 0); // 7 Current Season - Arena season in progress + // 0 - End of season + packet.AddState(3901, WorldConfig.GetIntValue(WorldCfg.ArenaSeasonId) - (WorldConfig.GetBoolValue(WorldCfg.ArenaSeasonInProgress) ? 1 : 0)); // 8 PreviousSeason + + if (mapid == 530) // Outland + { + packet.AddState(2495, 0); // 7 + packet.AddState(2493, 0xF); // 8 + packet.AddState(2491, 0xF); // 9 + } + + // insert + switch (zoneid) + { + case 1: // Dun Morogh + case 11: // Wetlands + case 12: // Elwynn Forest + case 38: // Loch Modan + case 40: // Westfall + case 51: // Searing Gorge + case 1519: // Stormwind City + case 1537: // Ironforge + case 2257: // Deeprun Tram + case 3703: // Shattrath City}); + break; + case 1377: // Silithus + if (pvp != null && pvp.GetTypeId() == OutdoorPvPTypes.Silithus) + pvp.FillInitialWorldStates(packet); + else + { + // states are always shown + packet.AddState(2313, 0x0); // 7 ally silityst gathered + packet.AddState(2314, 0x0); // 8 horde silityst gathered + packet.AddState(2317, 0x0); // 9 max silithyst + } + // dunno about these... aq opening event maybe? + packet.AddState(2322, 0x0); // 10 sandworm N + packet.AddState(2323, 0x0); // 11 sandworm S + packet.AddState(2324, 0x0); // 12 sandworm SW + packet.AddState(2325, 0x0); // 13 sandworm E + break; + case 2597: // Alterac Valley + if (bg && bg.GetTypeID(true) == BattlegroundTypeId.AV) + bg.FillInitialWorldStates(packet); + else + { + packet.AddState(0x7ae, 0x1); // 7 snowfall n + packet.AddState(0x532, 0x1); // 8 frostwolfhut hc + packet.AddState(0x531, 0x0); // 9 frostwolfhut ac + packet.AddState(0x52e, 0x0); // 10 stormpike firstaid a_a + packet.AddState(0x571, 0x0); // 11 east frostwolf tower horde assaulted -unused + packet.AddState(0x570, 0x0); // 12 west frostwolf tower horde assaulted - unused + packet.AddState(0x567, 0x1); // 13 frostwolfe c + packet.AddState(0x566, 0x1); // 14 frostwolfw c + packet.AddState(0x550, 0x1); // 15 irondeep (N) ally + packet.AddState(0x544, 0x0); // 16 ice grave a_a + packet.AddState(0x536, 0x0); // 17 stormpike grave h_c + packet.AddState(0x535, 0x1); // 18 stormpike grave a_c + packet.AddState(0x518, 0x0); // 19 stoneheart grave a_a + packet.AddState(0x517, 0x0); // 20 stoneheart grave h_a + packet.AddState(0x574, 0x0); // 21 1396 unk + packet.AddState(0x573, 0x0); // 22 iceblood tower horde assaulted -unused + packet.AddState(0x572, 0x0); // 23 towerpoint horde assaulted - unused + packet.AddState(0x56f, 0x0); // 24 1391 unk + packet.AddState(0x56e, 0x0); // 25 iceblood a + packet.AddState(0x56d, 0x0); // 26 towerp a + packet.AddState(0x56c, 0x0); // 27 frostwolfe a + packet.AddState(0x56b, 0x0); // 28 froswolfw a + packet.AddState(0x56a, 0x1); // 29 1386 unk + packet.AddState(0x569, 0x1); // 30 iceblood c + packet.AddState(0x568, 0x1); // 31 towerp c + packet.AddState(0x565, 0x0); // 32 stoneh tower a + packet.AddState(0x564, 0x0); // 33 icewing tower a + packet.AddState(0x563, 0x0); // 34 dunn a + packet.AddState(0x562, 0x0); // 35 duns a + packet.AddState(0x561, 0x0); // 36 stoneheart bunker alliance assaulted - unused + packet.AddState(0x560, 0x0); // 37 icewing bunker alliance assaulted - unused + packet.AddState(0x55f, 0x0); // 38 dunbaldar south alliance assaulted - unused + packet.AddState(0x55e, 0x0); // 39 dunbaldar north alliance assaulted - unused + packet.AddState(0x55d, 0x0); // 40 stone tower d + packet.AddState(0x3c6, 0x0); // 41 966 unk + packet.AddState(0x3c4, 0x0); // 42 964 unk + packet.AddState(0x3c2, 0x0); // 43 962 unk + packet.AddState(0x516, 0x1); // 44 stoneheart grave a_c + packet.AddState(0x515, 0x0); // 45 stonheart grave h_c + packet.AddState(0x3b6, 0x0); // 46 950 unk + packet.AddState(0x55c, 0x0); // 47 icewing tower d + packet.AddState(0x55b, 0x0); // 48 dunn d + packet.AddState(0x55a, 0x0); // 49 duns d + packet.AddState(0x559, 0x0); // 50 1369 unk + packet.AddState(0x558, 0x0); // 51 iceblood d + packet.AddState(0x557, 0x0); // 52 towerp d + packet.AddState(0x556, 0x0); // 53 frostwolfe d + packet.AddState(0x555, 0x0); // 54 frostwolfw d + packet.AddState(0x554, 0x1); // 55 stoneh tower c + packet.AddState(0x553, 0x1); // 56 icewing tower c + packet.AddState(0x552, 0x1); // 57 dunn c + packet.AddState(0x551, 0x1); // 58 duns c + packet.AddState(0x54f, 0x0); // 59 irondeep (N) horde + packet.AddState(0x54e, 0x0); // 60 irondeep (N) ally + packet.AddState(0x54d, 0x1); // 61 mine (S) neutral + packet.AddState(0x54c, 0x0); // 62 mine (S) horde + packet.AddState(0x54b, 0x0); // 63 mine (S) ally + packet.AddState(0x545, 0x0); // 64 iceblood h_a + packet.AddState(0x543, 0x1); // 65 iceblod h_c + packet.AddState(0x542, 0x0); // 66 iceblood a_c + packet.AddState(0x540, 0x0); // 67 snowfall h_a + packet.AddState(0x53f, 0x0); // 68 snowfall a_a + packet.AddState(0x53e, 0x0); // 69 snowfall h_c + packet.AddState(0x53d, 0x0); // 70 snowfall a_c + packet.AddState(0x53c, 0x0); // 71 frostwolf g h_a + packet.AddState(0x53b, 0x0); // 72 frostwolf g a_a + packet.AddState(0x53a, 0x1); // 73 frostwolf g h_c + packet.AddState(0x539, 0x0); // 74 frostwolf g a_c + packet.AddState(0x538, 0x0); // 75 stormpike grave h_a + packet.AddState(0x537, 0x0); // 76 stormpike grave a_a + packet.AddState(0x534, 0x0); // 77 frostwolf hut h_a + packet.AddState(0x533, 0x0); // 78 frostwolf hut a_a + packet.AddState(0x530, 0x0); // 79 stormpike first aid h_a + packet.AddState(0x52f, 0x0); // 80 stormpike first aid h_c + packet.AddState(0x52d, 0x1); // 81 stormpike first aid a_c + } + break; + case 3277: // Warsong Gulch + if (bg && bg.GetTypeID(true) == BattlegroundTypeId.WS) + bg.FillInitialWorldStates(packet); + else + { + packet.AddState(0x62d, 0x0); // 7 1581 alliance flag captures + packet.AddState(0x62e, 0x0); // 8 1582 horde flag captures + packet.AddState(0x609, 0x0); // 9 1545 unk, set to 1 on alliance flag pickup... + packet.AddState(0x60a, 0x0); // 10 1546 unk, set to 1 on horde flag pickup, after drop it's -1 + packet.AddState(0x60b, 0x2); // 11 1547 unk + packet.AddState(0x641, 0x3); // 12 1601 unk (max flag captures?) + packet.AddState(0x922, 0x1); // 13 2338 horde (0 - hide, 1 - flag ok, 2 - flag picked up (flashing), 3 - flag picked up (not flashing) + packet.AddState(0x923, 0x1); // 14 2339 alliance (0 - hide, 1 - flag ok, 2 - flag picked up (flashing), 3 - flag picked up (not flashing) + } + break; + case 3358: // Arathi Basin + if (bg && bg.GetTypeID(true) == BattlegroundTypeId.AB) + bg.FillInitialWorldStates(packet); + else + { + packet.AddState(0x6e7, 0x0); // 7 1767 stables alliance + packet.AddState(0x6e8, 0x0); // 8 1768 stables horde + packet.AddState(0x6e9, 0x0); // 9 1769 unk, ST? + packet.AddState(0x6ea, 0x0); // 10 1770 stables (show/hide) + packet.AddState(0x6ec, 0x0); // 11 1772 farm (0 - horde controlled, 1 - alliance controlled) + packet.AddState(0x6ed, 0x0); // 12 1773 farm (show/hide) + packet.AddState(0x6ee, 0x0); // 13 1774 farm color + packet.AddState(0x6ef, 0x0); // 14 1775 gold mine color, may be FM? + packet.AddState(0x6f0, 0x0); // 15 1776 alliance resources + packet.AddState(0x6f1, 0x0); // 16 1777 horde resources + packet.AddState(0x6f2, 0x0); // 17 1778 horde bases + packet.AddState(0x6f3, 0x0); // 18 1779 alliance bases + packet.AddState(0x6f4, 0x7d0); // 19 1780 max resources (2000) + packet.AddState(0x6f6, 0x0); // 20 1782 blacksmith color + packet.AddState(0x6f7, 0x0); // 21 1783 blacksmith (show/hide) + packet.AddState(0x6f8, 0x0); // 22 1784 unk, bs? + packet.AddState(0x6f9, 0x0); // 23 1785 unk, bs? + packet.AddState(0x6fb, 0x0); // 24 1787 gold mine (0 - horde contr, 1 - alliance contr) + packet.AddState(0x6fc, 0x0); // 25 1788 gold mine (0 - conflict, 1 - horde) + packet.AddState(0x6fd, 0x0); // 26 1789 gold mine (1 - show/0 - hide) + packet.AddState(0x6fe, 0x0); // 27 1790 gold mine color + packet.AddState(0x700, 0x0); // 28 1792 gold mine color, wtf?, may be LM? + packet.AddState(0x701, 0x0); // 29 1793 lumber mill color (0 - conflict, 1 - horde contr) + packet.AddState(0x702, 0x0); // 30 1794 lumber mill (show/hide) + packet.AddState(0x703, 0x0); // 31 1795 lumber mill color color + packet.AddState(0x732, 0x1); // 32 1842 stables (1 - uncontrolled) + packet.AddState(0x733, 0x1); // 33 1843 gold mine (1 - uncontrolled) + packet.AddState(0x734, 0x1); // 34 1844 lumber mill (1 - uncontrolled) + packet.AddState(0x735, 0x1); // 35 1845 farm (1 - uncontrolled) + packet.AddState(0x736, 0x1); // 36 1846 blacksmith (1 - uncontrolled) + packet.AddState(0x745, 0x2); // 37 1861 unk + packet.AddState(0x7a3, 0x708); // 38 1955 warning limit (1800) + } + break; + case 3820: // Eye of the Storm + if (bg && bg.GetTypeID(true) == BattlegroundTypeId.EY) + bg.FillInitialWorldStates(packet); + else + { + packet.AddState(0xac1, 0x0); // 7 2753 Horde Bases + packet.AddState(0xac0, 0x0); // 8 2752 Alliance Bases + packet.AddState(0xab6, 0x0); // 9 2742 Mage Tower - Horde conflict + packet.AddState(0xab5, 0x0); // 10 2741 Mage Tower - Alliance conflict + packet.AddState(0xab4, 0x0); // 11 2740 Fel Reaver - Horde conflict + packet.AddState(0xab3, 0x0); // 12 2739 Fel Reaver - Alliance conflict + packet.AddState(0xab2, 0x0); // 13 2738 Draenei - Alliance conflict + packet.AddState(0xab1, 0x0); // 14 2737 Draenei - Horde conflict + packet.AddState(0xab0, 0x0); // 15 2736 unk // 0 at start + packet.AddState(0xaaf, 0x0); // 16 2735 unk // 0 at start + packet.AddState(0xaad, 0x0); // 17 2733 Draenei - Horde control + packet.AddState(0xaac, 0x0); // 18 2732 Draenei - Alliance control + packet.AddState(0xaab, 0x1); // 19 2731 Draenei uncontrolled (1 - yes, 0 - no) + packet.AddState(0xaaa, 0x0); // 20 2730 Mage Tower - Alliance control + packet.AddState(0xaa9, 0x0); // 21 2729 Mage Tower - Horde control + packet.AddState(0xaa8, 0x1); // 22 2728 Mage Tower uncontrolled (1 - yes, 0 - no) + packet.AddState(0xaa7, 0x0); // 23 2727 Fel Reaver - Horde control + packet.AddState(0xaa6, 0x0); // 24 2726 Fel Reaver - Alliance control + packet.AddState(0xaa5, 0x1); // 25 2725 Fel Reaver uncontrolled (1 - yes, 0 - no) + packet.AddState(0xaa4, 0x0); // 26 2724 Boold Elf - Horde control + packet.AddState(0xaa3, 0x0); // 27 2723 Boold Elf - Alliance control + packet.AddState(0xaa2, 0x1); // 28 2722 Boold Elf uncontrolled (1 - yes, 0 - no) + packet.AddState(0xac5, 0x1); // 29 2757 Flag (1 - show, 0 - hide) - doesn't work exactly this way! + packet.AddState(0xad2, 0x1); // 30 2770 Horde top-stats (1 - show, 0 - hide) // 02 . horde picked up the flag + packet.AddState(0xad1, 0x1); // 31 2769 Alliance top-stats (1 - show, 0 - hide) // 02 . alliance picked up the flag + packet.AddState(0xabe, 0x0); // 32 2750 Horde resources + packet.AddState(0xabd, 0x0); // 33 2749 Alliance resources + packet.AddState(0xa05, 0x8e); // 34 2565 unk, constant? + packet.AddState(0xaa0, 0x0); // 35 2720 Capturing progress-bar (100 . empty (only grey), 0 . blue|red (no grey), default 0) + packet.AddState(0xa9f, 0x0); // 36 2719 Capturing progress-bar (0 - left, 100 - right) + packet.AddState(0xa9e, 0x0); // 37 2718 Capturing progress-bar (1 - show, 0 - hide) + packet.AddState(0xc0d, 0x17b); // 38 3085 unk + // and some more ... unknown + } + break; + // any of these needs change! the client remembers the prev setting! + // ON EVERY ZONE LEAVE, RESET THE OLD ZONE'S WORLD STATE, BUT AT LEAST THE UI STUFF! + case 3483: // Hellfire Peninsula + if (pvp != null && pvp.GetTypeId() == OutdoorPvPTypes.HellfirePeninsula) + pvp.FillInitialWorldStates(packet); + else + { + packet.AddState(0x9ba, 0x1); // 10 // add ally tower main gui icon // maybe should be sent only on login? + packet.AddState(0x9b9, 0x1); // 11 // add horde tower main gui icon // maybe should be sent only on login? + packet.AddState(0x9b5, 0x0); // 12 // show neutral broken hill icon // 2485 + packet.AddState(0x9b4, 0x1); // 13 // show icon above broken hill // 2484 + packet.AddState(0x9b3, 0x0); // 14 // show ally broken hill icon // 2483 + packet.AddState(0x9b2, 0x0); // 15 // show neutral overlook icon // 2482 + packet.AddState(0x9b1, 0x1); // 16 // show the overlook arrow // 2481 + packet.AddState(0x9b0, 0x0); // 17 // show ally overlook icon // 2480 + packet.AddState(0x9ae, 0x0); // 18 // horde pvp objectives captured // 2478 + packet.AddState(0x9ac, 0x0); // 19 // ally pvp objectives captured // 2476 + packet.AddState(2475, 100); //: ally / horde slider grey area // show only in direct vicinity! + packet.AddState(2474, 50); //: ally / horde slider percentage, 100 for ally, 0 for horde // show only in direct vicinity! + packet.AddState(2473, 0); //: ally / horde slider display // show only in direct vicinity! + packet.AddState(0x9a8, 0x0); // 20 // show the neutral stadium icon // 2472 + packet.AddState(0x9a7, 0x0); // 21 // show the ally stadium icon // 2471 + packet.AddState(0x9a6, 0x1); // 22 // show the horde stadium icon // 2470 + } + break; + case 3518: // Nagrand + if (pvp != null && pvp.GetTypeId() == OutdoorPvPTypes.Nagrand) + pvp.FillInitialWorldStates(packet); + else + { + packet.AddState(2503, 0x0); // 10 + packet.AddState(2502, 0x0); // 11 + packet.AddState(2493, 0x0); // 12 + packet.AddState(2491, 0x0); // 13 + + packet.AddState(2495, 0x0); // 14 + packet.AddState(2494, 0x0); // 15 + packet.AddState(2497, 0x0); // 16 + + packet.AddState(2762, 0x0); // 17 + packet.AddState(2662, 0x0); // 18 + packet.AddState(2663, 0x0); // 19 + packet.AddState(2664, 0x0); // 20 + + packet.AddState(2760, 0x0); // 21 + packet.AddState(2670, 0x0); // 22 + packet.AddState(2668, 0x0); // 23 + packet.AddState(2669, 0x0); // 24 + + packet.AddState(2761, 0x0); // 25 + packet.AddState(2667, 0x0); // 26 + packet.AddState(2665, 0x0); // 27 + packet.AddState(2666, 0x0); // 28 + + packet.AddState(2763, 0x0); // 29 + packet.AddState(2659, 0x0); // 30 + packet.AddState(2660, 0x0); // 31 + packet.AddState(2661, 0x0); // 32 + + packet.AddState(2671, 0x0); // 33 + packet.AddState(2676, 0x0); // 34 + packet.AddState(2677, 0x0); // 35 + packet.AddState(2672, 0x0); // 36 + packet.AddState(2673, 0x0); // 37 + } + break; + case 3519: // Terokkar Forest + if (pvp != null && pvp.GetTypeId() == OutdoorPvPTypes.TerokkarForest) + pvp.FillInitialWorldStates(packet); + else + { + packet.AddState(0xa41, 0x0); // 10 // 2625 capture bar pos + packet.AddState(0xa40, 0x14); // 11 // 2624 capture bar neutral + packet.AddState(0xa3f, 0x0); // 12 // 2623 show capture bar + packet.AddState(0xa3e, 0x0); // 13 // 2622 horde towers controlled + packet.AddState(0xa3d, 0x5); // 14 // 2621 ally towers controlled + packet.AddState(0xa3c, 0x0); // 15 // 2620 show towers controlled + packet.AddState(0xa88, 0x0); // 16 // 2696 SE Neu + packet.AddState(0xa87, 0x0); // 17 // SE Horde + packet.AddState(0xa86, 0x0); // 18 // SE Ally + packet.AddState(0xa85, 0x0); // 19 //S Neu + packet.AddState(0xa84, 0x0); // 20 S Horde + packet.AddState(0xa83, 0x0); // 21 S Ally + packet.AddState(0xa82, 0x0); // 22 NE Neu + packet.AddState(0xa81, 0x0); // 23 NE Horde + packet.AddState(0xa80, 0x0); // 24 NE Ally + packet.AddState(0xa7e, 0x0); // 25 // 2686 N Neu + packet.AddState(0xa7d, 0x0); // 26 N Horde + packet.AddState(0xa7c, 0x0); // 27 N Ally + packet.AddState(0xa7b, 0x0); // 28 NW Ally + packet.AddState(0xa7a, 0x0); // 29 NW Horde + packet.AddState(0xa79, 0x0); // 30 NW Neutral + packet.AddState(0x9d0, 0x5); // 31 // 2512 locked time remaining seconds first digit + packet.AddState(0x9ce, 0x0); // 32 // 2510 locked time remaining seconds second digit + packet.AddState(0x9cd, 0x0); // 33 // 2509 locked time remaining minutes + packet.AddState(0x9cc, 0x0); // 34 // 2508 neutral locked time show + packet.AddState(0xad0, 0x0); // 35 // 2768 horde locked time show + packet.AddState(0xacf, 0x1); // 36 // 2767 ally locked time show + } + break; + case 3521: // Zangarmarsh + if (pvp != null && pvp.GetTypeId() == OutdoorPvPTypes.Zangarmarsh) + pvp.FillInitialWorldStates(packet); + else + { + packet.AddState(0x9e1, 0x0); // 10 //2529 + packet.AddState(0x9e0, 0x0); // 11 + packet.AddState(0x9df, 0x0); // 12 + packet.AddState(0xa5d, 0x1); // 13 //2653 + packet.AddState(0xa5c, 0x0); // 14 //2652 east beacon neutral + packet.AddState(0xa5b, 0x1); // 15 horde + packet.AddState(0xa5a, 0x0); // 16 ally + packet.AddState(0xa59, 0x1); // 17 // 2649 Twin spire graveyard horde 12??? + packet.AddState(0xa58, 0x0); // 18 ally 14 ??? + packet.AddState(0xa57, 0x0); // 19 neutral 7??? + packet.AddState(0xa56, 0x0); // 20 // 2646 west beacon neutral + packet.AddState(0xa55, 0x1); // 21 horde + packet.AddState(0xa54, 0x0); // 22 ally + packet.AddState(0x9e7, 0x0); // 23 // 2535 + packet.AddState(0x9e6, 0x0); // 24 + packet.AddState(0x9e5, 0x0); // 25 + packet.AddState(0xa00, 0x0); // 26 // 2560 + packet.AddState(0x9ff, 0x1); // 27 + packet.AddState(0x9fe, 0x0); // 28 + packet.AddState(0x9fd, 0x0); // 29 + packet.AddState(0x9fc, 0x1); // 30 + packet.AddState(0x9fb, 0x0); // 31 + packet.AddState(0xa62, 0x0); // 32 // 2658 + packet.AddState(0xa61, 0x1); // 33 + packet.AddState(0xa60, 0x1); // 34 + packet.AddState(0xa5f, 0x0); // 35 + } + break; + case 3698: // Nagrand Arena + if (bg && bg.GetTypeID(true) == BattlegroundTypeId.NA) + bg.FillInitialWorldStates(packet); + else + { + packet.AddState(0xa0f, 0x0); // 7 + packet.AddState(0xa10, 0x0); // 8 + packet.AddState(0xa11, 0x0); // 9 show + } + break; + case 3702: // Blade's Edge Arena + if (bg && bg.GetTypeID(true) == BattlegroundTypeId.BE) + bg.FillInitialWorldStates(packet); + else + { + packet.AddState(0x9f0, 0x0); // 7 gold + packet.AddState(0x9f1, 0x0); // 8 green + packet.AddState(0x9f3, 0x0); // 9 show + } + break; + case 3968: // Ruins of Lordaeron + if (bg && bg.GetTypeID(true) == BattlegroundTypeId.RL) + bg.FillInitialWorldStates(packet); + else + { + packet.AddState(0xbb8, 0x0); // 7 gold + packet.AddState(0xbb9, 0x0); // 8 green + packet.AddState(0xbba, 0x0); // 9 show + } + break; + case 4378: // Dalaran Sewers + if (bg && bg.GetTypeID(true) == BattlegroundTypeId.DS) + bg.FillInitialWorldStates(packet); + else + { + packet.AddState(3601, 0x0); // 7 gold + packet.AddState(3600, 0x0); // 8 green + packet.AddState(3610, 0x0); // 9 show + } + break; + case 4384: // Strand of the Ancients + if (bg && bg.GetTypeID(true) == BattlegroundTypeId.SA) + bg.FillInitialWorldStates(packet); + else + { + // 1-3 A defend, 4-6 H defend, 7-9 unk defend, 1 - ok, 2 - half destroyed, 3 - destroyed + packet.AddState(0xf09, 0x0); // 7 3849 Gate of Temple + packet.AddState(0xe36, 0x0); // 8 3638 Gate of Yellow Moon + packet.AddState(0xe27, 0x0); // 9 3623 Gate of Green Emerald + packet.AddState(0xe24, 0x0); // 10 3620 Gate of Blue Sapphire + packet.AddState(0xe21, 0x0); // 11 3617 Gate of Red Sun + packet.AddState(0xe1e, 0x0); // 12 3614 Gate of Purple Ametyst + + packet.AddState(0xdf3, 0x0); // 13 3571 bonus timer (1 - on, 0 - off) + packet.AddState(0xded, 0x0); // 14 3565 Horde Attacker + packet.AddState(0xdec, 0x0); // 15 3564 Alliance Attacker + // End Round (timer), better explain this by example, eg. ends in 19:59 . A:BC + packet.AddState(0xde9, 0x0); // 16 3561 C + packet.AddState(0xde8, 0x0); // 17 3560 B + packet.AddState(0xde7, 0x0); // 18 3559 A + packet.AddState(0xe35, 0x0); // 19 3637 East g - Horde control + packet.AddState(0xe34, 0x0); // 20 3636 West g - Horde control + packet.AddState(0xe33, 0x0); // 21 3635 South g - Horde control + packet.AddState(0xe32, 0x0); // 22 3634 East g - Alliance control + packet.AddState(0xe31, 0x0); // 23 3633 West g - Alliance control + packet.AddState(0xe30, 0x0); // 24 3632 South g - Alliance control + packet.AddState(0xe2f, 0x0); // 25 3631 Chamber of Ancients - Horde control + packet.AddState(0xe2e, 0x0); // 26 3630 Chamber of Ancients - Alliance control + packet.AddState(0xe2d, 0x0); // 27 3629 Beach1 - Horde control + packet.AddState(0xe2c, 0x0); // 28 3628 Beach2 - Horde control + packet.AddState(0xe2b, 0x0); // 29 3627 Beach1 - Alliance control + packet.AddState(0xe2a, 0x0); // 30 3626 Beach2 - Alliance control + // and many unks... + } + break; + case 4406: // Ring of Valor + if (bg && bg.GetTypeID(true) == BattlegroundTypeId.RV) + bg.FillInitialWorldStates(packet); + else + { + packet.AddState(0xe10, 0x0); // 7 gold + packet.AddState(0xe11, 0x0); // 8 green + packet.AddState(0xe1a, 0x0); // 9 show + } + break; + case 4710: + if (bg && bg.GetTypeID(true) == BattlegroundTypeId.IC) + bg.FillInitialWorldStates(packet); + else + { + packet.AddState(4221, 1); // 7 BG_IC_ALLIANCE_RENFORT_SET + packet.AddState(4222, 1); // 8 BG_IC_HORDE_RENFORT_SET + packet.AddState(4226, 300); // 9 BG_IC_ALLIANCE_RENFORT + packet.AddState(4227, 300); // 10 BG_IC_HORDE_RENFORT + packet.AddState(4322, 1); // 11 BG_IC_GATE_FRONT_H_WS_OPEN + packet.AddState(4321, 1); // 12 BG_IC_GATE_WEST_H_WS_OPEN + packet.AddState(4320, 1); // 13 BG_IC_GATE_EAST_H_WS_OPEN + packet.AddState(4323, 1); // 14 BG_IC_GATE_FRONT_A_WS_OPEN + packet.AddState(4324, 1); // 15 BG_IC_GATE_WEST_A_WS_OPEN + packet.AddState(4325, 1); // 16 BG_IC_GATE_EAST_A_WS_OPEN + packet.AddState(4317, 1); // 17 unknown + + packet.AddState(4301, 1); // 18 BG_IC_DOCKS_UNCONTROLLED + packet.AddState(4296, 1); // 19 BG_IC_HANGAR_UNCONTROLLED + packet.AddState(4306, 1); // 20 BG_IC_QUARRY_UNCONTROLLED + packet.AddState(4311, 1); // 21 BG_IC_REFINERY_UNCONTROLLED + packet.AddState(4294, 1); // 22 BG_IC_WORKSHOP_UNCONTROLLED + packet.AddState(4243, 1); // 23 unknown + packet.AddState(4345, 1); // 24 unknown + } + break; + // The Ruby Sanctum + case 4987: + if (instance != null && mapid == 724) + instance.FillInitialWorldStates(packet); + else + { + packet.AddState(5049, 50); // 9 WORLDSTATE_CORPOREALITY_MATERIAL + packet.AddState(5050, 50); // 10 WORLDSTATE_CORPOREALITY_TWILIGHT + packet.AddState(5051, 0); // 11 WORLDSTATE_CORPOREALITY_TOGGLE + } + break; + // Icecrown Citadel + case 4812: + if (instance != null && mapid == 631) + instance.FillInitialWorldStates(packet); + else + { + packet.AddState(4903, 0); // 9 WORLDSTATE_SHOW_TIMER (Blood Quickening weekly) + packet.AddState(4904, 30); // 10 WORLDSTATE_EXECUTION_TIME + packet.AddState(4940, 0); // 11 WORLDSTATE_SHOW_ATTEMPTS + packet.AddState(4941, 50); // 12 WORLDSTATE_ATTEMPTS_REMAINING + packet.AddState(4942, 50); // 13 WORLDSTATE_ATTEMPTS_MAX + } + break; + // The Culling of Stratholme + case 4100: + if (instance != null && mapid == 595) + instance.FillInitialWorldStates(packet); + else + { + packet.AddState(3479, 0); // 9 WORLDSTATE_SHOW_CRATES + packet.AddState(3480, 0); // 10 WORLDSTATE_CRATES_REVEALED + packet.AddState(3504, 0); // 11 WORLDSTATE_WAVE_COUNT + packet.AddState(3931, 25); // 12 WORLDSTATE_TIME_GUARDIAN + packet.AddState(3932, 0); // 13 WORLDSTATE_TIME_GUARDIAN_SHOW + } + break; + // The Oculus + case 4228: + if (instance != null && mapid == 578) + instance.FillInitialWorldStates(packet); + else + { + packet.AddState(3524, 0); // 9 WORLD_STATE_CENTRIFUGE_CONSTRUCT_SHOW + packet.AddState(3486, 0); // 10 WORLD_STATE_CENTRIFUGE_CONSTRUCT_AMOUNT + } + break; + // Ulduar + case 4273: + if (instance != null && mapid == 603) + instance.FillInitialWorldStates(packet); + else + { + packet.AddState(4132, 0); // 9 WORLDSTATE_ALGALON_TIMER_ENABLED + packet.AddState(4131, 0); // 10 WORLDSTATE_ALGALON_DESPAWN_TIMER + } + break; + // Halls of Refection + case 4820: + if (instance != null && mapid == 668) + instance.FillInitialWorldStates(packet); + else + { + packet.AddState(4884, 0); // 9 WORLD_STATE_HOR_WAVES_ENABLED + packet.AddState(4882, 0); // 10 WORLD_STATE_HOR_WAVE_COUNT + } + break; + // Zul Aman + case 3805: + if (instance != null && mapid == 568) + instance.FillInitialWorldStates(packet); + else + { + packet.AddState(3104, 0); // 9 WORLD_STATE_ZULAMAN_TIMER_ENABLED + packet.AddState(3106, 0); // 10 WORLD_STATE_ZULAMAN_TIMER + } + break; + // Twin Peaks + case 5031: + if (bg && bg.GetTypeID(true) == BattlegroundTypeId.TP) + bg.FillInitialWorldStates(packet); + else + { + packet.AddState(0x62d, 0x0); // 7 1581 alliance flag captures + packet.AddState(0x62e, 0x0); // 8 1582 horde flag captures + packet.AddState(0x609, 0x0); // 9 1545 unk + packet.AddState(0x60a, 0x0); // 10 1546 unk + packet.AddState(0x60b, 0x2); // 11 1547 unk + packet.AddState(0x641, 0x3); // 12 1601 unk + packet.AddState(0x922, 0x1); // 13 2338 horde (0 - hide, 1 - flag ok, 2 - flag picked up (flashing), 3 - flag picked up (not flashing) + packet.AddState(0x923, 0x1); // 14 2339 alliance (0 - hide, 1 - flag ok, 2 - flag picked up (flashing), 3 - flag picked up (not flashing) + } + break; + // Battle for Gilneas + case 5449: + if (bg && bg.GetTypeID(true) == BattlegroundTypeId.BFG) + bg.FillInitialWorldStates(packet); + break; + // Wintergrasp + case 4197: + if (bf != null && bf.GetTypeId() == (uint)BattleFieldTypes.WinterGrasp) + bf.FillInitialWorldStates(packet); + goto default; + // No break here, intended. + default: + packet.AddState(0x914, 0x0); // 7 + packet.AddState(0x913, 0x0); // 8 + packet.AddState(0x912, 0x0); // 9 + packet.AddState(0x915, 0x0); // 10 + break; + } + + SendPacket(packet); + SendBGWeekendWorldStates(); + SendBattlefieldWorldStates(); + } + + public uint GetBarberShopCost(BarberShopStyleRecord newHairStyle, uint newHairColor, BarberShopStyleRecord newFacialHair, BarberShopStyleRecord newSkin, BarberShopStyleRecord newFace, Array newCustomDisplay) + { + byte hairstyle = GetByteValue(PlayerFields.Bytes, PlayerFieldOffsets.BytesOffsetHairStyleId); + byte haircolor = GetByteValue(PlayerFields.Bytes, PlayerFieldOffsets.BytesOffsetHairColorId); + byte facialhair = GetByteValue(PlayerFields.Bytes2, PlayerFieldOffsets.Bytes2OffsetFacialStyle); + byte skincolor = GetByteValue(PlayerFields.Bytes, PlayerFieldOffsets.BytesOffsetSkinId); + byte face = GetByteValue(PlayerFields.Bytes, PlayerFieldOffsets.BytesOffsetFaceId); + + Array customDisplay = new Array(PlayerConst.CustomDisplaySize); + for (int i = 0; i < PlayerConst.CustomDisplaySize; ++i) + customDisplay[i] = GetByteValue(PlayerFields.Bytes2, (byte)(PlayerFieldOffsets.Bytes2OffsetCustomDisplayOption + i)); + + if ((hairstyle == newHairStyle.Data) && + (haircolor == newHairColor) && + (facialhair == newFacialHair.Data) && + (newSkin == null || (newSkin.Data == skincolor)) && + (newFace == null || (newFace.Data == face)) && + (newCustomDisplay[0] == null || (newCustomDisplay[0].Data == customDisplay[0])) && + (newCustomDisplay[1] == null || (newCustomDisplay[1].Data == customDisplay[1])) && + (newCustomDisplay[2] == null || (newCustomDisplay[2].Data == customDisplay[2]))) + return 0; + + GtBarberShopCostBaseRecord bsc = CliDB.BarberShopCostBaseGameTable.GetRow(getLevel()); + if (bsc == null) // shouldn't happen + return 0xFFFFFFFF; + + uint cost = 0; + if (hairstyle != newHairStyle.Data) + cost += (uint)(bsc.Cost * newHairStyle.CostModifier); + + if ((haircolor != newHairColor) && (hairstyle == newHairStyle.Data)) + cost += (uint)(bsc.Cost * 0.5f); // +1/2 of price + + if (facialhair != newFacialHair.Data) + cost += (uint)(bsc.Cost * newFacialHair.CostModifier); + + if (newSkin != null && skincolor != newSkin.Data) + cost += (uint)(bsc.Cost * newSkin.CostModifier); + + if (newFace != null && face != newFace.Data) + cost += (uint)(bsc.Cost * newFace.CostModifier); + + for (int i = 0; i < PlayerConst.CustomDisplaySize; ++i) + if (newCustomDisplay[i] != null && customDisplay[i] != newCustomDisplay[i].Data) + cost += (uint)(bsc.Cost * newCustomDisplay[i].CostModifier); + + return cost; + } + + uint GetChampioningFaction() { return m_ChampioningFaction; } + public void SetChampioningFaction(uint faction) { m_ChampioningFaction = faction; } + public void setFactionForRace(Race race) + { + m_team = TeamForRace(race); + + ChrRacesRecord rEntry = CliDB.ChrRacesStorage.LookupByKey(race); + SetFaction(rEntry != null ? (uint)rEntry.FactionID : 0); + } + + public void SetResurrectRequestData(Unit caster, uint health, uint mana, uint appliedAura) + { + Contract.Assert(!IsResurrectRequested()); + _resurrectionData = new ResurrectionData(); + _resurrectionData.GUID = caster.GetGUID(); + _resurrectionData.Location.WorldRelocate(caster); + _resurrectionData.Health = health; + _resurrectionData.Mana = mana; + _resurrectionData.Aura = appliedAura; + } + public void ClearResurrectRequestData() + { + _resurrectionData = null; + } + + public bool IsRessurectRequestedBy(ObjectGuid guid) + { + if (!IsResurrectRequested()) + return false; + + return !_resurrectionData.GUID.IsEmpty() && _resurrectionData.GUID == guid; + } + + public bool IsResurrectRequested() { return _resurrectionData != null; } + public void ResurrectUsingRequestData() + { + // Teleport before resurrecting by player, otherwise the player might get attacked from creatures near his corpse + TeleportTo(_resurrectionData.Location); + + if (IsBeingTeleported()) + { + ScheduleDelayedOperation(PlayerDelayedOperations.ResurrectPlayer); + return; + } + + ResurrectUsingRequestDataImpl(); + } + + void ResurrectUsingRequestDataImpl() + { + ResurrectPlayer(0.0f, false); + + if (GetMaxHealth() > _resurrectionData.Health) + SetHealth(_resurrectionData.Health); + else + SetFullHealth(); + + if (GetMaxPower(PowerType.Mana) > _resurrectionData.Mana) + SetPower(PowerType.Mana, (int)_resurrectionData.Mana); + else + SetPower(PowerType.Mana, GetMaxPower(PowerType.Mana)); + + SetPower(PowerType.Rage, 0); + SetPower(PowerType.Energy, GetMaxPower(PowerType.Energy)); + SetPower(PowerType.Focus, GetMaxPower(PowerType.Focus)); + SetPower(PowerType.LunarPower, 0); + + uint aura = _resurrectionData.Aura; + if (aura != 0) + CastSpell(this, aura, true, null, null, _resurrectionData.GUID); + + SpawnCorpseBones(); + } + + public void UpdateTriggerVisibility() + { + if (m_clientGUIDs.Empty()) + return; + + if (!IsInWorld) + return; + + UpdateData udata = new UpdateData(GetMapId()); + foreach (var guid in m_clientGUIDs) + { + if (guid.IsCreatureOrVehicle()) + { + Creature creature = GetMap().GetCreature(guid); + // Update fields of triggers, transformed units or unselectable units (values dependent on GM state) + if (creature == null || (!creature.IsTrigger() && !creature.HasAuraType(AuraType.Transform) && !creature.HasFlag(UnitFields.Flags, UnitFlags.NotSelectable))) + continue; + + creature.SetFieldNotifyFlag(UpdateFieldFlags.Public); + creature.BuildValuesUpdateBlockForPlayer(udata, this); + creature.RemoveFieldNotifyFlag(UpdateFieldFlags.Public); + } + else if (guid.IsAnyTypeGameObject()) + { + GameObject go = GetMap().GetGameObject(guid); + if (go == null) + continue; + + go.SetFieldNotifyFlag(UpdateFieldFlags.Public); + go.BuildValuesUpdateBlockForPlayer(udata, this); + go.RemoveFieldNotifyFlag(UpdateFieldFlags.Public); + } + } + + if (!udata.HasData()) + return; + + UpdateObject packet; + udata.BuildPacket(out packet); + SendPacket(packet); + } + + public bool isAllowedToLoot(Creature creature) + { + if (!creature.IsDead() || !creature.IsDamageEnoughForLootingAndReward()) + return false; + + if (HasPendingBind()) + return false; + + Loot loot = creature.loot; + if (loot.isLooted()) // nothing to loot or everything looted. + return false; + if (!loot.hasItemForAll() && !loot.hasItemFor(this)) // no loot in creature for this player + return false; + + if (loot.loot_type == LootType.Skinning) + return creature.GetSkinner() == GetGUID(); + + Group thisGroup = GetGroup(); + if (!thisGroup) + return this == creature.GetLootRecipient(); + else if (thisGroup != creature.GetLootRecipientGroup()) + return false; + + switch (thisGroup.GetLootMethod()) + { + case LootMethod.PersonalLoot:// @todo implement personal loot (http://wow.gamepedia.com/Loot#Personal_Loot) + return false; + case LootMethod.MasterLoot: + case LootMethod.FreeForAll: + return true; + case LootMethod.GroupLoot: + // may only loot if the player is the loot roundrobin player + // or item over threshold (so roll(s) can be launched) + // or if there are free/quest/conditional item for the player + if (loot.roundRobinPlayer.IsEmpty() || loot.roundRobinPlayer == GetGUID()) + return true; + + if (loot.hasOverThresholdItem()) + return true; + + return loot.hasItemFor(this); + } + + return false; + } + + public override bool IsImmunedToSpellEffect(SpellInfo spellInfo, uint index) + { + SpellEffectInfo effect = spellInfo.GetEffect(GetMap().GetDifficultyID(), index); + if (effect == null || !effect.IsEffect()) + return false; + + // players are immune to taunt (the aura and the spell effect). + if (effect.IsAura(AuraType.ModTaunt)) + return true; + if (effect.IsEffect(SpellEffectName.AttackMe)) + return true; + + return base.IsImmunedToSpellEffect(spellInfo, index); + } + + void RegenerateAll() + { + m_regenTimerCount += m_regenTimer; + + for (PowerType power = PowerType.Mana; power < PowerType.Max; power++)// = power + 1) + if (power != PowerType.Runes) + Regenerate(power); + + // Runes act as cooldowns, and they don't need to send any data + if (GetClass() == Class.Deathknight) + { + uint regeneratedRunes = 0; + int regenIndex = 0; + while (regeneratedRunes < PlayerConst.MaxRechargingRunes && !m_runes.CooldownOrder.Empty()) + { + byte runeToRegen = m_runes.CooldownOrder[regenIndex++]; + uint runeCooldown = GetRuneCooldown(runeToRegen); + if (runeCooldown > m_regenTimer) + { + SetRuneCooldown(runeToRegen, runeCooldown - m_regenTimer); + ++regenIndex; + } + else + SetRuneCooldown((byte)runeCooldown, 0); + + ++regeneratedRunes; + } + } + + if (m_regenTimerCount >= 2000) + { + // Not in combat or they have regeneration + if (!IsInCombat() || IsPolymorphed() || m_baseHealthRegen != 0 || HasAuraType(AuraType.ModRegenDuringCombat) || HasAuraType(AuraType.ModHealthRegenInCombat)) + RegenerateHealth(); + + m_regenTimerCount -= 2000; + } + + m_regenTimer = 0; + } + void Regenerate(PowerType power) + { + // Skip regeneration for power type we cannot have + uint powerIndex = GetPowerIndex(power); + if (powerIndex == (int)PowerType.Max || powerIndex >= (int)PowerType.MaxPerClass) + return; + + // @todo possible use of miscvalueb instead of amount + if (HasAuraTypeWithValue(AuraType.PreventRegeneratePower, (int)power)) + return; + + int curValue = GetPower(power); + + // TODO: updating haste should update UNIT_FIELD_POWER_REGEN_FLAT_MODIFIER for certain power types + PowerTypeRecord powerType = Global.DB2Mgr.GetPowerTypeEntry(power); + if (powerType == null) + return; + + float addvalue = 0.0f; + + if (!IsInCombat()) + { + if (powerType.RegenerationDelay != 0 && Time.GetMSTimeDiffToNow(m_combatExitTime) < powerType.RegenerationDelay) + return; + + addvalue = (powerType.RegenerationPeace + GetFloatValue(UnitFields.PowerRegenFlatModifier + (int)powerIndex)) * 0.001f * m_regenTimer; + } + else + addvalue = (powerType.RegenerationCombat + GetFloatValue(UnitFields.PowerRegenInterruptedFlatModifier + (int)powerIndex)) * 0.001f * m_regenTimer; + + WorldCfg[] RatesForPower = + { + WorldCfg.RatePowerMana, + WorldCfg.RatePowerRageLoss, + WorldCfg.RatePowerFocus, + WorldCfg.RatePowerEnergy, + WorldCfg.RatePowerComboPointsLoss, + 0, // runes + WorldCfg.RatePowerRunicPowerLoss, + WorldCfg.RatePowerSoulShards, + WorldCfg.RatePowerLunarPower, + WorldCfg.RatePowerHolyPower, + 0, // alternate + WorldCfg.RatePowerMaelstrom, + WorldCfg.RatePowerChi, + WorldCfg.RatePowerInsanity, + 0, // burning embers, unused + 0, // demonic fury, unused + WorldCfg.RatePowerArcaneCharges, + WorldCfg.RatePowerFury, + WorldCfg.RatePowerPain, + }; + + if (RatesForPower[(int)power] != 0) + addvalue *= WorldConfig.GetFloatValue(RatesForPower[(int)power]); + + // Mana regen calculated in Player.UpdateManaRegen() + if (power != PowerType.Mana) + { + var ModPowerRegenPCTAuras = GetAuraEffectsByType(AuraType.ModPowerRegenPercent); + foreach (var eff in ModPowerRegenPCTAuras) + if ((PowerType)eff.GetMiscValue() == power) + MathFunctions.AddPct(ref addvalue, eff.GetAmount()); + + addvalue += GetTotalAuraModifierByMiscValue(AuraType.ModPowerRegen, (int)power) * ((power != PowerType.Energy) ? m_regenTimerCount : m_regenTimer) / (5 * Time.InMilliseconds); + } + + int minPower = powerType.RegenerationMin; + int maxPower = GetMaxPower(power); + + if (addvalue < 0.0f) + { + if (curValue <= minPower) + return; + } + else if (addvalue > 0.0f) + { + if (curValue >= maxPower) + return; + } + else + return; + + addvalue += m_powerFraction[powerIndex]; + int integerValue = (int)Math.Abs(addvalue); + + if (powerType.RegenerationCenter != 0) + { + if (curValue > powerType.RegenerationCenter) + { + addvalue = -Math.Abs(addvalue); + minPower = powerType.RegenerationCenter; + } + else if (curValue < powerType.RegenerationCenter) + { + addvalue = Math.Abs(addvalue); + maxPower = powerType.RegenerationCenter; + } + else + return; + } + + if (addvalue < 0.0f) + { + if (curValue > minPower + integerValue) + { + curValue -= integerValue; + m_powerFraction[powerIndex] = addvalue + integerValue; + } + else + { + curValue = minPower; + m_powerFraction[powerIndex] = 0; + } + } + else + { + if (curValue + integerValue <= maxPower) + { + curValue += integerValue; + m_powerFraction[powerIndex] = addvalue - integerValue; + } + else + { + curValue = maxPower; + m_powerFraction[powerIndex] = 0; + } + } + + if (m_regenTimerCount >= 2000) + SetPower(power, curValue); + else + UpdateUInt32Value(UnitFields.Power + (int)powerIndex, (uint)curValue); + } + void RegenerateHealth() + { + uint curValue = (uint)GetHealth(); + uint maxValue = (uint)GetMaxHealth(); + + if (curValue >= maxValue) + return; + + float HealthIncreaseRate = WorldConfig.GetFloatValue(WorldCfg.RateHealth); + float addvalue = 0.0f; + + // polymorphed case + if (IsPolymorphed()) + addvalue = (float)GetMaxHealth() / 3; + // normal regen case (maybe partly in combat case) + else if (!IsInCombat() || HasAuraType(AuraType.ModRegenDuringCombat)) + { + addvalue = HealthIncreaseRate; + if (!IsInCombat()) + { + if (getLevel() < 15) + addvalue = (0.20f * (GetMaxHealth()) / getLevel() * HealthIncreaseRate); + else + addvalue = 0.015f * (GetMaxHealth()) * HealthIncreaseRate; + + var mModHealthRegenPct = GetAuraEffectsByType(AuraType.ModHealthRegenPercent); + foreach (var eff in mModHealthRegenPct) + MathFunctions.AddPct(ref addvalue, eff.GetAmount()); + + addvalue += GetTotalAuraModifier(AuraType.ModRegen) * 2 * Time.InMilliseconds / (5 * Time.InMilliseconds); + } + else if (HasAuraType(AuraType.ModRegenDuringCombat)) + MathFunctions.ApplyPct(ref addvalue, GetTotalAuraModifier(AuraType.ModRegenDuringCombat)); + + if (!IsStandState()) + addvalue *= 1.5f; + } + + // always regeneration bonus (including combat) + addvalue += GetTotalAuraModifier(AuraType.ModHealthRegenInCombat); + addvalue += m_baseHealthRegen / 2.5f; + + if (addvalue < 0) + addvalue = 0; + + ModifyHealth((int)addvalue); + } + public void ResetAllPowers() + { + SetHealth(GetMaxHealth()); + switch (getPowerType()) + { + case PowerType.Mana: + SetPower(PowerType.Mana, GetMaxPower(PowerType.Mana)); + break; + case PowerType.Rage: + SetPower(PowerType.Rage, 0); + break; + case PowerType.Energy: + SetPower(PowerType.Energy, GetMaxPower(PowerType.Energy)); + break; + case PowerType.RunicPower: + SetPower(PowerType.RunicPower, 0); + break; + case PowerType.LunarPower: + SetPower(PowerType.LunarPower, 0); + break; + default: + break; + } + } + + public Unit GetSelectedUnit() + { + ObjectGuid selectionGUID = GetTarget(); + if (!selectionGUID.IsEmpty()) + return Global.ObjAccessor.GetUnit(this, selectionGUID); + return null; + } + Player GetSelectedPlayer() + { + ObjectGuid selectionGUID = GetTarget(); + if (!selectionGUID.IsEmpty()) + return Global.ObjAccessor.GetPlayer(this, selectionGUID); + return null; + } + + static uint GetSelectionFromContext(uint context, Class playerClass) + { + switch (context) + { + case 1: + if (playerClass == Class.Deathknight) + return 1; + if (playerClass == Class.DemonHunter) + return 3; + return 0; + case 2: + if (playerClass == Class.Deathknight) + return 5; + if (playerClass == Class.DemonHunter) + return 6; + return 4; + case 3: + return 7; + case 4: + if (playerClass == Class.Deathknight) + return 9; + if (playerClass == Class.DemonHunter) + return 10; + return 8; + default: + if (playerClass == Class.Deathknight) + return 1; + if (playerClass == Class.DemonHunter) + return 2; + return 0; + } + } + + static bool ComponentFlagsMatch(CharSectionsRecord entry, uint selection) + { + switch (selection) + { + case 0: + if (!entry.Flags.HasAnyFlag((ushort)1)) + return false; + return !entry.Flags.HasAnyFlag((ushort)0x2C); + case 1: + if (!entry.Flags.HasAnyFlag((ushort)1)) + return false; + if (!entry.Flags.HasAnyFlag((ushort)0x94)) + return false; + return !entry.Flags.HasAnyFlag((ushort)8); + case 2: + if (!entry.Flags.HasAnyFlag((ushort)1)) + return false; + if (!entry.Flags.HasAnyFlag((ushort)0x70)) + return false; + return !entry.Flags.HasAnyFlag((ushort)8); + case 3: + if (!entry.Flags.HasAnyFlag((ushort)1)) + return false; + if (!entry.Flags.HasAnyFlag((ushort)0x20)) + return false; + return !entry.Flags.HasAnyFlag((ushort)8); + case 4: + case 8: + if (!entry.Flags.HasAnyFlag((ushort)3)) + return false; + return !entry.Flags.HasAnyFlag((ushort)0x2C); + case 5: + case 9: + if (!entry.Flags.HasAnyFlag((ushort)3)) + return false; + if (!entry.Flags.HasAnyFlag((ushort)0x94)) + return false; + return !entry.Flags.HasAnyFlag((ushort)8); + case 6: + case 10: + if (!entry.Flags.HasAnyFlag((ushort)3)) + return false; + if (!entry.Flags.HasAnyFlag((ushort)0x70)) + return false; + return !entry.Flags.HasAnyFlag((ushort)8); + case 7: + return true; + default: + break; + } + + return false; + } + + static bool IsSectionFlagValid(CharSectionsRecord entry, Class class_, bool create) + { + if (create) + return ComponentFlagsMatch(entry, GetSelectionFromContext(0, class_)); + + return ComponentFlagsMatch(entry, GetSelectionFromContext(2, class_)); + } + + public static bool IsValidGender(Gender _gender) { return _gender <= Gender.Female; } + public static bool IsValidClass(Class _class) { return Convert.ToBoolean((1 << ((int)_class - 1)) & (int)Class.ClassMaskAllPlayable); } + public static bool IsValidRace(Race _race) { return Convert.ToBoolean((1 << ((int)_race - 1)) & (int)Race.RaceMaskAllPlayable); } + public static bool ValidateAppearance(Race race, Class class_, Gender gender, byte hairID, byte hairColor, byte faceID, byte facialHairId, byte skinColor, Array customDisplay, bool create = false) + { + CharSectionsRecord skin = Global.DB2Mgr.GetCharSectionEntry(race, CharSectionType.Skin, gender, 0, skinColor); + if (skin == null) + return false; + + if (!IsSectionFlagValid(skin, class_, create)) + return false; + + CharSectionsRecord face = Global.DB2Mgr.GetCharSectionEntry(race, CharSectionType.Face, gender, faceID, skinColor); + if (face == null) + return false; + + if (!IsSectionFlagValid(face, class_, create)) + return false; + + CharSectionsRecord hair = Global.DB2Mgr.GetCharSectionEntry(race, CharSectionType.Hair, gender, hairID, hairColor); + if (hair == null) + return false; + + if (!IsSectionFlagValid(hair, class_, create)) + return false; + + CharSectionsRecord facialHair = Global.DB2Mgr.GetCharSectionEntry(race, CharSectionType.Hair, gender, facialHairId, hairColor); + if (facialHair == null) + return false; + + for (int i = 0; i < PlayerConst.CustomDisplaySize; ++i) + { + CharSectionsRecord entry = Global.DB2Mgr.GetCharSectionEntry(race, (CharSectionType.CustomDisplay1 + i * 2), gender, customDisplay[i], 0); + if (entry != null) + if (!IsSectionFlagValid(entry, class_, create)) + return false; + } + + return true; + } + + public void OnCombatExit() + { + UpdatePotionCooldown(); + m_combatExitTime = Time.GetMSTime(); + } + + void LeaveLFGChannel() + { + foreach (var i in m_channels) + { + if (i.IsLFG()) + { + i.LeaveChannel(this); + break; + } + } + } + + bool IsImmuneToEnvironmentalDamage() + { + // check for GM and death state included in isAttackableByAOE + return (!isTargetableForAttack(false)); + } + public uint EnvironmentalDamage(EnviromentalDamage type, uint damage) + { + if (IsImmuneToEnvironmentalDamage()) + return 0; + + // Absorb, resist some environmental damage type + uint absorb = 0; + uint resist = 0; + if (type == EnviromentalDamage.Lava) + CalcAbsorbResist(this, SpellSchoolMask.Fire, DamageEffectType.Direct, damage, ref absorb, ref resist); + else if (type == EnviromentalDamage.Slime) + CalcAbsorbResist(this, SpellSchoolMask.Nature, DamageEffectType.Direct, damage, ref absorb, ref resist); + + damage -= absorb + resist; + + DealDamageMods(this, ref damage, ref absorb); + + EnvironmentalDamageLog packet = new EnvironmentalDamageLog(); + packet.Victim = GetGUID(); + packet.Type = type != EnviromentalDamage.FallToVoid ? type : EnviromentalDamage.Fall; + packet.Amount = (int)damage; + packet.Absorbed = (int)absorb; + packet.Resisted = (int)resist; + + uint final_damage = DealDamage(this, damage, null, DamageEffectType.Self, SpellSchoolMask.Normal, null, false); + packet.LogData.Initialize(this); + + SendCombatLogMessage(packet); + + if (!IsAlive()) + { + if (type == EnviromentalDamage.Fall) // DealDamage not apply item durability loss at self damage + { + Log.outDebug(LogFilter.Player, "We are fall to death, loosing 10 percents durability"); + DurabilityLossAll(0.10f, false); + // durability lost message + SendDurabilityLoss(this, 10); + } + + UpdateCriteria(CriteriaTypes.DeathsFrom, 1, (ulong)type); + } + + return final_damage; + } + + bool isTotalImmune() + { + var immune = GetAuraEffectsByType(AuraType.SchoolImmunity); + + int immuneMask = 0; + foreach (var eff in immune) + { + immuneMask |= eff.GetMiscValue(); + if (Convert.ToBoolean(immuneMask & (int)SpellSchoolMask.All)) // total immunity + return true; + } + return false; + } + + public override bool CanAlwaysSee(WorldObject obj) + { + // Always can see self + if (m_unitMovedByMe == obj) + return true; + + ObjectGuid guid = GetGuidValue(PlayerFields.Farsight); + if (!guid.IsEmpty()) + if (obj.GetGUID() == guid) + return true; + + return false; + } + public override bool IsNeverVisibleFor(WorldObject seer) + { + if (base.IsNeverVisibleFor(seer)) + return true; + + if (GetSession().PlayerLogout() || GetSession().PlayerLoading()) + return true; + + return false; + } + + public void BuildPlayerRepop() + { + PreRessurect packet = new PreRessurect(); + packet.PlayerGUID = GetGUID(); + SendPacket(packet); + + if (GetRace() == Race.NightElf) + CastSpell(this, 20584, true); + CastSpell(this, 8326, true); + + // there must be SMSG.FORCE_RUN_SPEED_CHANGE, SMSG.FORCE_SWIM_SPEED_CHANGE, SMSG.MOVE_WATER_WALK + // there must be SMSG.STOP_MIRROR_TIMER + + // the player cannot have a corpse already on current map, only bones which are not returned by GetCorpse + WorldLocation corpseLocation = GetCorpseLocation(); + if (corpseLocation.GetMapId() == GetMapId()) + { + Log.outError(LogFilter.Player, "BuildPlayerRepop: player {0} ({1}) already has a corpse", GetName(), GetGUID().ToString()); + return; + } + + // create a corpse and place it at the player's location + Corpse corpse = CreateCorpse(); + if (corpse == null) + { + Log.outError(LogFilter.Player, "Error creating corpse for Player {0} ({1})", GetName(), GetGUID().ToString()); + return; + } + GetMap().AddToMap(corpse); + + // convert player body to ghost + SetHealth(1); + + SetWaterWalking(true); + if (!GetSession().isLogingOut() && !HasUnitState(UnitState.Stunned)) + SetRooted(false); + + // BG - remove insignia related + RemoveFlag(UnitFields.Flags, UnitFlags.Skinnable); + + int corpseReclaimDelay = CalculateCorpseReclaimDelay(); + + if (corpseReclaimDelay >= 0) + SendCorpseReclaimDelay(corpseReclaimDelay); + + // to prevent cheating + corpse.ResetGhostTime(); + + StopMirrorTimers(); //disable timers(bars) + + // set and clear other + SetByteValue(UnitFields.Bytes1, UnitBytes1Offsets.AnimTier, (byte)UnitBytes1Flags.AlwaysStand); + } + + public void StopMirrorTimers() + { + StopMirrorTimer(MirrorTimerType.Fatigue); + StopMirrorTimer(MirrorTimerType.Breath); + StopMirrorTimer(MirrorTimerType.Fire); + } + + public bool IsMirrorTimerActive(MirrorTimerType type) + { + return m_MirrorTimer[(int)type] == getMaxTimer(type); + } + + void HandleDrowning(uint time_diff) + { + if (m_MirrorTimerFlags == 0) + return; + + int breathTimer = (int)MirrorTimerType.Breath; + int fatigueTimer = (int)MirrorTimerType.Fatigue; + int fireTimer = (int)MirrorTimerType.Fire; + + // In water + if (m_MirrorTimerFlags.HasAnyFlag(PlayerUnderwaterState.InWater)) + { + // Breath timer not activated - activate it + if (m_MirrorTimer[breathTimer] == -1) + { + m_MirrorTimer[breathTimer] = getMaxTimer(MirrorTimerType.Breath); + SendMirrorTimer(MirrorTimerType.Breath, m_MirrorTimer[breathTimer], m_MirrorTimer[breathTimer], -1); + } + else // If activated - do tick + { + m_MirrorTimer[breathTimer] -= (int)time_diff; + // Timer limit - need deal damage + if (m_MirrorTimer[breathTimer] < 0) + { + m_MirrorTimer[breathTimer] += 1 * Time.InMilliseconds; + // Calculate and deal damage + // @todo Check this formula + uint damage = (uint)(GetMaxHealth() / 5 + RandomHelper.URand(0, getLevel() - 1)); + EnvironmentalDamage(EnviromentalDamage.Drowning, damage); + } + else if (!m_MirrorTimerFlagsLast.HasAnyFlag(PlayerUnderwaterState.InWater)) // Update time in client if need + SendMirrorTimer(MirrorTimerType.Breath, getMaxTimer(MirrorTimerType.Breath), m_MirrorTimer[breathTimer], -1); + } + } + else if (m_MirrorTimer[breathTimer] != -1) // Regen timer + { + int UnderWaterTime = getMaxTimer(MirrorTimerType.Breath); + // Need breath regen + m_MirrorTimer[breathTimer] += (int)(10 * time_diff); + if (m_MirrorTimer[breathTimer] >= UnderWaterTime || !IsAlive()) + StopMirrorTimer(MirrorTimerType.Breath); + else if (m_MirrorTimerFlagsLast.HasAnyFlag(PlayerUnderwaterState.InWater)) + SendMirrorTimer(MirrorTimerType.Breath, UnderWaterTime, m_MirrorTimer[breathTimer], 10); + } + + // In dark water + if (m_MirrorTimerFlags.HasAnyFlag(PlayerUnderwaterState.InDarkWater)) + { + // Fatigue timer not activated - activate it + if (m_MirrorTimer[fatigueTimer] == -1) + { + m_MirrorTimer[fatigueTimer] = getMaxTimer(MirrorTimerType.Fatigue); + SendMirrorTimer(MirrorTimerType.Fatigue, m_MirrorTimer[fatigueTimer], m_MirrorTimer[fatigueTimer], -1); + } + else + { + m_MirrorTimer[fatigueTimer] -= (int)time_diff; + // Timer limit - need deal damage or teleport ghost to graveyard + if (m_MirrorTimer[fatigueTimer] < 0) + { + m_MirrorTimer[fatigueTimer] += 1 * Time.InMilliseconds; + if (IsAlive()) // Calculate and deal damage + { + uint damage = (uint)(GetMaxHealth() / 5 + RandomHelper.URand(0, getLevel() - 1)); + EnvironmentalDamage(EnviromentalDamage.Exhausted, damage); + } + else if (HasFlag(PlayerFields.Flags, PlayerFlags.Ghost)) // Teleport ghost to graveyard + RepopAtGraveyard(); + } + else if (!m_MirrorTimerFlagsLast.HasAnyFlag(PlayerUnderwaterState.InDarkWater)) + SendMirrorTimer(MirrorTimerType.Fatigue, getMaxTimer(MirrorTimerType.Fatigue), m_MirrorTimer[fatigueTimer], -1); + } + } + else if (m_MirrorTimer[fatigueTimer] != -1) // Regen timer + { + int DarkWaterTime = getMaxTimer(MirrorTimerType.Fatigue); + m_MirrorTimer[fatigueTimer] += (int)(10 * time_diff); + if (m_MirrorTimer[fatigueTimer] >= DarkWaterTime || !IsAlive()) + StopMirrorTimer(MirrorTimerType.Fatigue); + else if (m_MirrorTimerFlagsLast.HasAnyFlag(PlayerUnderwaterState.InDarkWater)) + SendMirrorTimer(MirrorTimerType.Fatigue, DarkWaterTime, m_MirrorTimer[fatigueTimer], 10); + } + + if (m_MirrorTimerFlags.HasAnyFlag(PlayerUnderwaterState.InLava) && !(_lastLiquid != null && _lastLiquid.SpellID != 0)) + { + // Breath timer not activated - activate it + if (m_MirrorTimer[fireTimer] == -1) + m_MirrorTimer[fireTimer] = getMaxTimer(MirrorTimerType.Fire); + else + { + m_MirrorTimer[fireTimer] -= (int)time_diff; + if (m_MirrorTimer[fireTimer] < 0) + { + m_MirrorTimer[fireTimer] += 1 * Time.InMilliseconds; + // Calculate and deal damage + // @todo Check this formula + uint damage = RandomHelper.URand(600, 700); + if (m_MirrorTimerFlags.HasAnyFlag(PlayerUnderwaterState.InLava)) + EnvironmentalDamage(EnviromentalDamage.Lava, damage); + // need to skip Slime damage in Undercity, + // maybe someone can find better way to handle environmental damage + //else if (m_zoneUpdateId != 1497) + // EnvironmentalDamage(DAMAGE_SLIME, damage); + } + } + } + else + m_MirrorTimer[fireTimer] = -1; + + // Recheck timers flag + m_MirrorTimerFlags &= ~PlayerUnderwaterState.ExistTimers; + for (byte i = 0; i < (int)MirrorTimerType.Max; ++i) + { + if (m_MirrorTimer[i] != -1) + { + m_MirrorTimerFlags |= PlayerUnderwaterState.ExistTimers; + break; + } + } + m_MirrorTimerFlagsLast = m_MirrorTimerFlags; + } + + void HandleSobering() + { + m_drunkTimer = 0; + + byte currentDrunkValue = GetDrunkValue(); + byte drunk = (byte)(currentDrunkValue != 0 ? --currentDrunkValue : 0); + SetDrunkValue(drunk); + } + + void SendMirrorTimer(MirrorTimerType Type, int MaxValue, int CurrentValue, int Regen) + { + if (MaxValue == -1) + { + if (CurrentValue != -1) + StopMirrorTimer(Type); + return; + } + + SendPacket(new StartMirrorTimer(Type, CurrentValue, MaxValue, Regen, 0, false)); + } + + void StopMirrorTimer(MirrorTimerType Type) + { + m_MirrorTimer[(int)Type] = -1; + SendPacket(new StopMirrorTimer(Type)); + } + + int getMaxTimer(MirrorTimerType timer) + { + switch (timer) + { + case MirrorTimerType.Fatigue: + return Time.Minute * Time.InMilliseconds; + case MirrorTimerType.Breath: + { + if (!IsAlive() || HasAuraType(AuraType.WaterBreathing) || GetSession().GetSecurity() >= (AccountTypes)WorldConfig.GetIntValue(WorldCfg.DisableBreathing)) + return -1; + int UnderWaterTime = 3 * Time.Minute * Time.InMilliseconds; + var mModWaterBreathing = GetAuraEffectsByType(AuraType.WaterBreathing); + foreach (var eff in mModWaterBreathing) + MathFunctions.AddPct(ref UnderWaterTime, eff.GetAmount()); + return UnderWaterTime; + } + case MirrorTimerType.Fire: + { + if (!IsAlive()) + return -1; + return 1 * Time.InMilliseconds; + } + default: + return 0; + } + } + + public void UpdateMirrorTimers() + { + // Desync flags for update on next HandleDrowning + if (m_MirrorTimerFlags != 0) + m_MirrorTimerFlagsLast = ~m_MirrorTimerFlags; + } + + public void ResurrectPlayer(float restore_percent, bool applySickness = false) + { + DeathReleaseLoc packet = new DeathReleaseLoc(); + packet.MapID = -1; + SendPacket(packet); + + // speed change, land walk + + // remove death flag + set aura + SetByteValue(UnitFields.Bytes1, UnitBytes1Offsets.AnimTier, 0); + RemoveFlag(PlayerFields.Flags, PlayerFlags.IsOutOfBounds); + + // This must be called always even on Players with race != RACE_NIGHTELF in case of faction change + RemoveAurasDueToSpell(20584); // speed bonuses + RemoveAurasDueToSpell(8326); // SPELL_AURA_GHOST + + if (GetSession().IsARecruiter() || (GetSession().GetRecruiterId() != 0)) + SetFlag(ObjectFields.DynamicFlags, UnitDynFlags.ReferAFriend); + + setDeathState(DeathState.Alive); + + // add the flag to make sure opcode is always sent + AddUnitMovementFlag(MovementFlag.WaterWalk); + SetWaterWalking(false); + if (!HasUnitState(UnitState.Stunned)) + SetRooted(false); + + m_deathTimer = 0; + + // set health/powers (0- will be set in caller) + if (restore_percent > 0.0f) + { + SetHealth((uint)(GetMaxHealth() * restore_percent)); + SetPower(PowerType.Mana, (int)(GetMaxPower(PowerType.Mana) * restore_percent)); + SetPower(PowerType.Rage, 0); + SetPower(PowerType.Energy, (int)(GetMaxPower(PowerType.Energy) * restore_percent)); + SetPower(PowerType.Focus, (int)(GetMaxPower(PowerType.Focus) * restore_percent)); + SetPower(PowerType.LunarPower, 0); + } + + // trigger update zone for alive state zone updates + uint newzone, newarea; + GetZoneAndAreaId(out newzone, out newarea); + UpdateZone(newzone, newarea); + Global.OutdoorPvPMgr.HandlePlayerResurrects(this, newzone); + + if (InBattleground()) + { + Battleground bg = GetBattleground(); + if (bg) + bg.HandlePlayerResurrect(this); + } + + // update visibility + UpdateObjectVisibility(); + + if (!applySickness) + return; + + //Characters from level 1-10 are not affected by resurrection sickness. + //Characters from level 11-19 will suffer from one minute of sickness + //for each level they are above 10. + //Characters level 20 and up suffer from ten minutes of sickness. + int startLevel = WorldConfig.GetIntValue(WorldCfg.DeathSicknessLevel); + + if (getLevel() >= startLevel) + { + // set resurrection sickness + CastSpell(this, 15007, true); + + // not full duration + if (getLevel() < startLevel + 9) + { + int delta = (int)(getLevel() - startLevel + 1) * Time.Minute; + Aura aur = GetAura(15007, GetGUID()); + if (aur != null) + aur.SetDuration(delta * Time.InMilliseconds); + + } + } + } + + public void KillPlayer() + { + if (IsFlying() && GetTransport() == null) + GetMotionMaster().MoveFall(); + + SetRooted(true); + + StopMirrorTimers(); //disable timers(bars) + + setDeathState(DeathState.Corpse); + + SetUInt32Value(ObjectFields.DynamicFlags, 0); + ApplyModFlag(PlayerFields.LocalFlags, PlayerLocalFlags.ReleaseTimer, !CliDB.MapStorage.LookupByKey(GetMapId()).Instanceable() && !HasAuraType(AuraType.PreventResurrection)); + + // 6 minutes until repop at graveyard + m_deathTimer = 6 * Time.Minute * Time.InMilliseconds; + + UpdateCorpseReclaimDelay(); // dependent at use SetDeathPvP() call before kill + + int corpseReclaimDelay = CalculateCorpseReclaimDelay(); + + if (corpseReclaimDelay >= 0) + SendCorpseReclaimDelay(corpseReclaimDelay); + + // don't create corpse at this moment, player might be falling + + // update visibility + UpdateObjectVisibility(); + } + + public static void OfflineResurrect(ObjectGuid guid, SQLTransaction trans) + { + Corpse.DeleteFromDB(guid, trans); + PreparedStatement stmt = DB.Characters.GetPreparedStatement(CharStatements.UPD_ADD_AT_LOGIN_FLAG); + stmt.AddValue(0, (ushort)AtLoginFlags.Resurrect); + stmt.AddValue(1, guid.GetCounter()); + DB.Characters.ExecuteOrAppend(trans, stmt); + } + + Corpse CreateCorpse() + { + // prevent existence 2 corpse for player + SpawnCorpseBones(); + + uint _cfb1, _cfb2; + + Corpse corpse = new Corpse(Convert.ToBoolean(m_ExtraFlags & PlayerExtraFlags.PVPDeath) ? CorpseType.ResurrectablePVP : CorpseType.ResurrectablePVE); + SetPvPDeath(false); + + if (!corpse.Create(GetMap().GenerateLowGuid(HighGuid.Corpse), this)) + return null; + + _corpseLocation = new WorldLocation(this); + + byte skin = GetByteValue(PlayerFields.Bytes, PlayerFieldOffsets.BytesOffsetSkinId); + byte face = GetByteValue(PlayerFields.Bytes, PlayerFieldOffsets.BytesOffsetFaceId); + byte hairstyle = GetByteValue(PlayerFields.Bytes, PlayerFieldOffsets.BytesOffsetHairStyleId); + byte haircolor = GetByteValue(PlayerFields.Bytes, PlayerFieldOffsets.BytesOffsetHairColorId); + byte facialhair = GetByteValue(PlayerFields.Bytes2, PlayerFieldOffsets.Bytes2OffsetFacialStyle); + + _cfb1 = (uint)((0x00) | ((int)GetRace() << 8) | (GetByteValue(PlayerFields.Bytes3, PlayerFieldOffsets.Bytes3OffsetGender) << 16) | (skin << 24)); + _cfb2 = (uint)((face) | (hairstyle << 8) | (haircolor << 16) | (facialhair << 24)); + + corpse.SetUInt32Value(CorpseFields.Bytes1, _cfb1); + corpse.SetUInt32Value(CorpseFields.Bytes2, _cfb2); + + CorpseFlags flags = 0; + if (HasByteFlag(UnitFields.Bytes2, UnitBytes2Offsets.PvpFlag, UnitBytes2Flags.PvP)) + flags |= CorpseFlags.PvP; + if (InBattleground() && !InArena()) + flags |= CorpseFlags.Skinnable; // to be able to remove insignia + if (HasByteFlag(UnitFields.Bytes2, UnitBytes2Offsets.PvpFlag, UnitBytes2Flags.FFAPvp)) + flags |= CorpseFlags.FFAPvP; + + corpse.SetUInt32Value(CorpseFields.Flags, (uint)flags); + corpse.SetUInt32Value(CorpseFields.DisplayId, GetNativeDisplayId()); + corpse.SetUInt32Value(CorpseFields.FactionTemplate, CliDB.ChrRacesStorage.LookupByKey(GetRace()).FactionID); + + for (byte i = 0; i < EquipmentSlot.End; i++) + { + if (m_items[i] != null) + { + uint itemDisplayId = m_items[i].GetDisplayId(this); + uint itemInventoryType; + ItemRecord itemEntry = CliDB.ItemStorage.LookupByKey(m_items[i].GetVisibleEntry(this)); + if (itemEntry != null) + itemInventoryType = (uint)itemEntry.inventoryType; + else + itemInventoryType = (uint)m_items[i].GetTemplate().GetInventoryType(); + + corpse.SetUInt32Value(CorpseFields.Item + i, itemDisplayId | (itemInventoryType << 24)); + } + } + + // register for player, but not show + GetMap().AddCorpse(corpse); + + // we do not need to save corpses for BG/arenas + if (!GetMap().IsBattlegroundOrArena()) + corpse.SaveToDB(); + + return corpse; + } + + public void SpawnCorpseBones(bool triggerSave = true) + { + _corpseLocation = new WorldLocation(); + if (GetMap().ConvertCorpseToBones(GetGUID())) + if (triggerSave && !GetSession().PlayerLogoutWithSave()) // at logout we will already store the player + SaveToDB(); // prevent loading as ghost without corpse + } + + public Corpse GetCorpse() { return GetMap().GetCorpseByPlayer(GetGUID()); } + + public void RepopAtGraveyard() + { + // note: this can be called also when the player is alive + // for example from WorldSession.HandleMovementOpcodes + + AreaTableRecord zone = CliDB.AreaTableStorage.LookupByKey(GetAreaId()); + + // Such zones are considered unreachable as a ghost and the player must be automatically revived + if ((!IsAlive() && zone != null && zone.Flags[0].HasAnyFlag(AreaFlags.NeedFly)) || GetTransport() != null || GetPositionZ() < GetMap().GetMinHeight(GetPositionX(), GetPositionY())) + { + ResurrectPlayer(0.5f); + SpawnCorpseBones(); + } + + WorldSafeLocsRecord ClosestGrave = null; + + // Special handle for Battlegroundmaps + Battleground bg = GetBattleground(); + if (bg) + ClosestGrave = bg.GetClosestGraveYard(this); + else + { + BattleField bf = Global.BattleFieldMgr.GetBattlefieldToZoneId(GetZoneId()); + if (bf != null) + ClosestGrave = bf.GetClosestGraveYard(this); + else + ClosestGrave = Global.ObjectMgr.GetClosestGraveYard(GetPositionX(), GetPositionY(), GetPositionZ(), GetMapId(), GetTeam()); + } + + // stop countdown until repop + m_deathTimer = 0; + + // if no grave found, stay at the current location + // and don't show spirit healer location + if (ClosestGrave != null) + { + TeleportTo(ClosestGrave.MapID, ClosestGrave.Loc.X, ClosestGrave.Loc.Y, ClosestGrave.Loc.Z, (ClosestGrave.Facing * MathFunctions.PI) / 180); + if (IsDead()) // not send if alive, because it used in TeleportTo() + { + DeathReleaseLoc packet = new DeathReleaseLoc(); + packet.MapID = (int)ClosestGrave.MapID; + packet.Loc = ClosestGrave.Loc; + SendPacket(packet); + } + } + else if (GetPositionZ() < GetMap().GetMinHeight(GetPositionX(), GetPositionY())) + TeleportTo(homebind); + + RemoveFlag(PlayerFields.Flags, PlayerFlags.IsOutOfBounds); + } + + public bool HasCorpse() + { + return _corpseLocation.GetMapId() != 0xFFFFFFFF; + } + public WorldLocation GetCorpseLocation() { return _corpseLocation; } + + public uint GetCorpseReclaimDelay(bool pvp) + { + if (pvp) + { + if (!WorldConfig.GetBoolValue(WorldCfg.DeathCorpseReclaimDelayPvp)) + return PlayerConst.copseReclaimDelay[0]; + } + else if (!WorldConfig.GetBoolValue(WorldCfg.DeathCorpseReclaimDelayPve)) + return 0; + + long now = Time.UnixTime; + // 0..2 full period + // should be ceil(x)-1 but not floor(x) + ulong count = (ulong)((now < m_deathExpireTime - 1) ? (m_deathExpireTime - 1 - now) / PlayerConst.DeathExpireStep : 0); + return PlayerConst.copseReclaimDelay[count]; + } + void UpdateCorpseReclaimDelay() + { + bool pvp = m_ExtraFlags.HasAnyFlag(PlayerExtraFlags.PVPDeath); + + if ((pvp && !WorldConfig.GetBoolValue(WorldCfg.DeathCorpseReclaimDelayPvp)) || + (!pvp && !WorldConfig.GetBoolValue(WorldCfg.DeathCorpseReclaimDelayPve))) + return; + long now = Time.UnixTime; + if (now < m_deathExpireTime) + { + // full and partly periods 1..3 + ulong count = (ulong)(m_deathExpireTime - now) / PlayerConst.DeathExpireStep + 1; + if (count < PlayerConst.MaxDeathCount) + m_deathExpireTime = now + (long)(count + 1) * PlayerConst.DeathExpireStep; + else + m_deathExpireTime = now + PlayerConst.MaxDeathCount * PlayerConst.DeathExpireStep; + } + else + m_deathExpireTime = now + PlayerConst.DeathExpireStep; + } + int CalculateCorpseReclaimDelay(bool load = false) + { + Corpse corpse = GetCorpse(); + if (load && !corpse) + return -1; + + bool pvp = corpse ? corpse.GetCorpseType() == CorpseType.ResurrectablePVP : (m_ExtraFlags & PlayerExtraFlags.PVPDeath) != 0; + + uint delay; + if (load) + { + if (corpse.GetGhostTime() > m_deathExpireTime) + return -1; + + ulong count = 0; + if ((pvp && WorldConfig.GetBoolValue(WorldCfg.DeathCorpseReclaimDelayPvp)) || + (!pvp && WorldConfig.GetBoolValue(WorldCfg.DeathCorpseReclaimDelayPve))) + { + count = (ulong)(m_deathExpireTime - corpse.GetGhostTime()) / PlayerConst.DeathExpireStep; ; + + if (count >= PlayerConst.MaxDeathCount) + count = PlayerConst.MaxDeathCount - 1; + } + + long expected_time = corpse.GetGhostTime() + PlayerConst.copseReclaimDelay[count]; + long now = Time.UnixTime; + + if (now >= expected_time) + return -1; + + delay = (uint)(expected_time - now); + } + else + delay = GetCorpseReclaimDelay(pvp); + + return (int)(delay * Time.InMilliseconds); + } + void SendCorpseReclaimDelay(int delay) + { + CorpseReclaimDelay packet = new CorpseReclaimDelay(); + packet.Remaining = (uint)delay; + SendPacket(packet); + } + + public override bool CanFly() { return m_movementInfo.HasMovementFlag(MovementFlag.CanFly); } + + public Pet GetPet() + { + ObjectGuid petGuid = GetPetGUID(); + if (!petGuid.IsEmpty()) + { + if (!petGuid.IsPet()) + return null; + + Pet pet = ObjectAccessor.GetPet(this, petGuid); + if (pet == null) + return null; + + if (IsInWorld && pet != null) + return pet; + } + + return null; + } + + public Pet SummonPet(uint entry, float x, float y, float z, float ang, PetType petType, uint duration) + { + Pet pet = new Pet(this, petType); + if (petType == PetType.Summon && pet.LoadPetFromDB(this, entry)) + { + if (duration > 0) + pet.SetDuration(duration); + + return null; + } + + // petentry == 0 for hunter "call pet" (current pet summoned if any) + if (entry == 0) + return null; + + pet.Relocate(x, y, z, ang); + if (!pet.IsPositionValid()) + { + Log.outError(LogFilter.Server, "Pet (guidlow {0}, entry {1}) not summoned. Suggested coordinates isn't valid (X: {2} Y: {3})", + pet.GetGUID().ToString(), pet.GetEntry(), pet.GetPositionX(), pet.GetPositionY()); + return null; + } + + Map map = GetMap(); + uint pet_number = Global.ObjectMgr.GeneratePetNumber(); + if (!pet.Create(map.GenerateLowGuid(HighGuid.Pet), map, entry)) + { + Log.outError(LogFilter.Server, "no such creature entry {0}", entry); + return null; + } + + pet.CopyPhaseFrom(this); + + pet.SetCreatorGUID(GetGUID()); + pet.SetUInt32Value(UnitFields.FactionTemplate, getFaction()); + + pet.setPowerType(PowerType.Mana); + pet.SetUInt64Value(UnitFields.NpcFlags, (uint)NPCFlags.None); + pet.SetUInt32Value(UnitFields.Bytes1, 0); + pet.InitStatsForLevel(getLevel()); + + SetMinion(pet, true); + + switch (petType) + { + case PetType.Summon: + // this enables pet details window (Shift+P) + pet.GetCharmInfo().SetPetNumber(pet_number, true); + pet.SetByteValue(UnitFields.Bytes0, 1, (byte)Class.Mage); + pet.SetUInt32Value(UnitFields.PetExperience, 0); + pet.SetUInt32Value(UnitFields.PetNextLevelExp, 1000); + pet.SetFullHealth(); + pet.SetPower(PowerType.Mana, pet.GetMaxPower(PowerType.Mana)); + pet.SetUInt32Value(UnitFields.PetNameTimestamp, (uint)Time.UnixTime); // cast can't be helped in this case + break; + default: + break; + } + + map.AddToMap(pet.ToCreature()); + + switch (petType) + { + case PetType.Summon: + pet.InitPetCreateSpells(); + pet.SavePetToDB(PetSaveMode.AsCurrent); + PetSpellInitialize(); + break; + default: + break; + } + + if (duration > 0) + pet.SetDuration(duration); + + //ObjectAccessor.UpdateObjectVisibility(pet); + + return pet; + } + + public void RemovePet(Pet pet, PetSaveMode mode, bool returnreagent = false) + { + if (!pet) + pet = GetPet(); + + if (pet) + { + Log.outDebug(LogFilter.Pet, "RemovePet {0}, {1}, {2}", pet.GetEntry(), mode, returnreagent); + + if (pet.m_removed) + return; + } + + if (returnreagent && (pet || m_temporaryUnsummonedPetNumber != 0) && !InBattleground()) + { + //returning of reagents only for players, so best done here + uint spellId = pet ? pet.GetUInt32Value(UnitFields.CreatedBySpell) : m_oldpetspell; + SpellInfo spellInfo = Global.SpellMgr.GetSpellInfo(spellId); + + if (spellInfo != null) + { + for (uint i = 0; i < SpellConst.MaxReagents; ++i) + { + if (spellInfo.Reagent[i] > 0) + { + List dest = new List(); //for succubus, voidwalker, felhunter and felguard credit soulshard when despawn reason other than death (out of range, logout) + InventoryResult msg = CanStoreNewItem(ItemConst.NullBag, ItemConst.NullSlot, dest, (uint)spellInfo.Reagent[i], spellInfo.ReagentCount[i]); + if (msg == InventoryResult.Ok) + { + Item item = StoreNewItem(dest, (uint)spellInfo.Reagent[i], true); + if (IsInWorld) + SendNewItem(item, spellInfo.ReagentCount[i], true, false); + } + } + } + } + m_temporaryUnsummonedPetNumber = 0; + } + + if (!pet || pet.GetOwnerGUID() != GetGUID()) + return; + + pet.CombatStop(); + + if (returnreagent) + { + switch (pet.GetEntry()) + { + //warlock pets except imp are removed(?) when logging out + case 1860: + case 1863: + case 417: + case 17252: + mode = PetSaveMode.NotInSlot; + break; + } + } + + // only if current pet in slot + pet.SavePetToDB(mode); + + SetMinion(pet, false); + + pet.AddObjectToRemoveList(); + pet.m_removed = true; + + if (pet.isControlled()) + { + SendPacket(new PetSpells()); + + if (GetGroup()) + SetGroupUpdateFlag(GroupUpdateFlags.Pet); + } + } + + public bool InArena() + { + Battleground bg = GetBattleground(); + if (!bg || !bg.isArena()) + return false; + + return true; + } + + public void SendOnCancelExpectedVehicleRideAura() + { + SendPacket(new OnCancelExpectedRideVehicleAura()); + } + + public void SendMovementSetCollisionHeight(float height) + { + MoveSetCollisionHeight setCollisionHeight = new MoveSetCollisionHeight(); + setCollisionHeight.MoverGUID = GetGUID(); + setCollisionHeight.SequenceIndex = m_movementCounter++; + setCollisionHeight.Height = height; + setCollisionHeight.Scale = GetObjectScale(); + setCollisionHeight.MountDisplayID = GetUInt32Value(UnitFields.MountDisplayId); + setCollisionHeight.ScaleDuration = (int)GetUInt32Value(UnitFields.ScaleDuration); + setCollisionHeight.Reason = UpdateCollisionHeightReason.Mount; + SendPacket(setCollisionHeight); + + MoveUpdateCollisionHeight updateCollisionHeight = new MoveUpdateCollisionHeight(); + updateCollisionHeight.movementInfo = m_movementInfo; + updateCollisionHeight.Height = height; + updateCollisionHeight.Scale = GetObjectScale(); + SendMessageToSet(updateCollisionHeight, false); + } + + public float GetCollisionHeight(bool mounted) + { + if (mounted) + { + var mountDisplayInfo = CliDB.CreatureDisplayInfoStorage.LookupByKey(GetUInt32Value(UnitFields.MountDisplayId)); + if (mountDisplayInfo == null) + return GetCollisionHeight(false); + + var mountModelData = CliDB.CreatureModelDataStorage.LookupByKey(mountDisplayInfo.ModelID); + if (mountModelData == null) + return GetCollisionHeight(false); + + var displayInfo = CliDB.CreatureDisplayInfoStorage.LookupByKey(GetNativeDisplayId()); + Contract.Assert(displayInfo != null); + var modelData = CliDB.CreatureModelDataStorage.LookupByKey(displayInfo.ModelID); + Contract.Assert(modelData != null); + + float scaleMod = GetObjectScale(); // 99% sure about this + + return scaleMod * mountModelData.MountHeight + modelData.CollisionHeight * 0.5f; + } + else + { + //! Dismounting case - use basic default model data + var displayInfo = CliDB.CreatureDisplayInfoStorage.LookupByKey(GetNativeDisplayId()); + Contract.Assert(displayInfo != null); + var modelData = CliDB.CreatureModelDataStorage.LookupByKey(displayInfo.ModelID); + Contract.Assert(modelData != null); + + return modelData.CollisionHeight; + } + } + + // Used in triggers for check "Only to targets that grant experience or honor" req + public bool isHonorOrXPTarget(Unit victim) + { + uint v_level = victim.getLevel(); + uint k_grey = Formulas.GetGrayLevel(getLevel()); + + // Victim level less gray level + if (v_level < k_grey) + return false; + + Creature creature = victim.ToCreature(); + if (creature != null) + { + if (!creature.CanGiveExperience()) + return false; + } + return true; + } + + public void setRegenTimerCount(uint time) { m_regenTimerCount = time; } + void setWeaponChangeTimer(uint time) { m_weaponChangeTimer = time; } + + //Team + public static Team TeamForRace(Race race) + { + switch (TeamIdForRace(race)) + { + case 0: + return Team.Alliance; + case 1: + return Team.Horde; + } + + return Team.Alliance; + } + public static uint TeamIdForRace(Race race) + { + ChrRacesRecord rEntry = CliDB.ChrRacesStorage.LookupByKey((byte)race); + if (rEntry != null) + return rEntry.TeamID; + + Log.outError(LogFilter.Player, "Race ({0}) not found in DBC: wrong DBC files?", race); + return TeamId.Neutral; + } + public Team GetTeam() { return m_team; } + public int GetTeamId() { return m_team == Team.Alliance ? TeamId.Alliance : TeamId.Horde; } + + //Money + public ulong GetMoney() { return GetUInt64Value(PlayerFields.Coinage); } + public bool HasEnoughMoney(ulong amount) { return GetMoney() >= amount; } + public bool HasEnoughMoney(long amount) + { + if (amount > 0) + return (GetMoney() >= (ulong)amount); + return true; + } + public bool ModifyMoney(long amount, bool sendError = true) + { + if (amount == 0) + return true; + + Global.ScriptMgr.OnPlayerMoneyChanged(this, amount); + + if (amount < 0) + SetMoney((ulong)(GetMoney() > (ulong)-amount ? (long)GetMoney() + amount : 0)); + else + { + if (GetMoney() < (ulong)(PlayerConst.MaxMoneyAmount - amount)) + SetMoney((ulong)((long)GetMoney() + amount)); + else + { + if (sendError) + SendEquipError(InventoryResult.TooMuchGold); + return false; + } + } + return true; + } + public void SetMoney(ulong value) + { + SetUInt64Value(PlayerFields.Coinage, value); + MoneyChanged((uint)value); + UpdateCriteria(CriteriaTypes.HighestGoldValueOwned); + } + + //Target + public void SetSelection(ObjectGuid guid) + { + SetGuidValue(UnitFields.Target, guid); + } + + //LoginFlag + public bool HasAtLoginFlag(AtLoginFlags f) { return Convert.ToBoolean(atLoginFlags & f); } + public void SetAtLoginFlag(AtLoginFlags f) { atLoginFlags |= f; } + public void RemoveAtLoginFlag(AtLoginFlags flags, bool persist = false) + { + atLoginFlags &= ~flags; + if (persist) + { + PreparedStatement stmt = DB.Characters.GetPreparedStatement(CharStatements.UPD_REM_AT_LOGIN_FLAG); + stmt.AddValue(0, (ushort)flags); + stmt.AddValue(1, GetGUID().GetCounter()); + + DB.Characters.Execute(stmt); + } + } + void SetFactionForRace(Race race) + { + m_team = TeamForRace(race); + + var rEntry = CliDB.ChrRacesStorage.LookupByKey(race); + SetFaction(rEntry.FactionID); + } + + //Guild + public void SetInGuild(ulong guildId) + { + if (guildId != 0) + SetGuidValue(ObjectFields.Data, ObjectGuid.Create(HighGuid.Guild, guildId)); + else + SetGuidValue(ObjectFields.Data, ObjectGuid.Empty); + + ApplyModFlag(PlayerFields.Flags, PlayerFlags.GuildLevelEnabled, guildId != 0); + SetUInt16Value(ObjectFields.Type, 1, (ushort)(guildId != 0 ? 1 : 0)); + } + public void SetRank(uint rankId) { SetUInt32Value(PlayerFields.GuildRank, rankId); } + byte GetRank() { return (byte)GetUInt32Value(PlayerFields.GuildRank); } + public void SetGuildLevel(uint level) { SetUInt32Value(PlayerFields.GuildLevel, level); } + uint GetGuildLevel() { return GetUInt32Value(PlayerFields.GuildLevel); } + public void SetGuildIdInvited(ulong GuildId) { m_GuildIdInvited = GuildId; } + public uint GetGuildId() { return GetUInt32Value(ObjectFields.Data); } + public Guild GetGuild() + { + uint guildId = GetGuildId(); + return guildId != 0 ? Global.GuildMgr.GetGuildById(guildId) : null; + } + public ulong GetGuildIdInvited() { return m_GuildIdInvited; } + public string GetGuildName() + { + return GetGuildId() != 0 ? Global.GuildMgr.GetGuildById(GetGuildId()).GetName() : ""; + } + + public void SetFreePrimaryProfessions(uint profs) { SetUInt32Value(PlayerFields.CharacterPoints, profs); } + public void GiveLevel(uint level) + { + var oldLevel = getLevel(); + if (level == oldLevel) + return; + + Guild guild = GetGuild(); + if (guild != null) + guild.UpdateMemberData(this, GuildMemberData.Level, level); + + PlayerLevelInfo info = Global.ObjectMgr.GetPlayerLevelInfo(GetRace(), GetClass(), level); + + uint basemana = 0; + Global.ObjectMgr.GetPlayerClassLevelInfo(GetClass(), level, out basemana); + + LevelUpInfo packet = new LevelUpInfo(); + packet.Level = level; + packet.HealthDelta = 0; + + /// @todo find some better solution + packet.PowerDelta[0] = basemana - GetCreateMana(); + packet.PowerDelta[1] = 0; + packet.PowerDelta[2] = 0; + packet.PowerDelta[3] = 0; + packet.PowerDelta[4] = 0; + packet.PowerDelta[5] = 0; + + for (Stats i = Stats.Strength; i < Stats.Max; ++i) + packet.StatDelta[(int)i] = info.stats[(int)i] - (uint)GetCreateStat(i); + + uint[] rowLevels = (GetClass() != Class.Deathknight) ? PlayerConst.DefaultTalentRowLevels : PlayerConst.DKTalentRowLevels; + + packet.Cp = rowLevels.Any(p => p == level) ? 1 : 0; + + SendPacket(packet); + + SetUInt32Value(PlayerFields.NextLevelXp, Global.ObjectMgr.GetXPForLevel(level)); + + //update level, max level of skills + m_PlayedTimeLevel = 0; // Level Played Time reset + + _ApplyAllLevelScaleItemMods(false); + + SetLevel(level); + + UpdateSkillsForLevel(); + LearnDefaultSkills(); + LearnSpecializationSpells(); + + // save base values (bonuses already included in stored stats + for (var i = Stats.Strength; i < Stats.Max; ++i) + SetCreateStat(i, info.stats[(int)i]); + + SetCreateHealth(0); + SetCreateMana(basemana); + + InitTalentForLevel(); + InitTaxiNodesForLevel(); + + UpdateAllStats(); + + if (WorldConfig.GetBoolValue(WorldCfg.AlwaysMaxskill)) // Max weapon skill when leveling up + UpdateSkillsToMaxSkillsForLevel(); + + // set current level health and mana/energy to maximum after applying all mods. + SetFullHealth(); + SetPower(PowerType.Mana, GetMaxPower(PowerType.Mana)); + SetPower(PowerType.Energy, GetMaxPower(PowerType.Energy)); + if (GetPower(PowerType.Rage) > GetMaxPower(PowerType.Rage)) + SetPower(PowerType.Rage, GetMaxPower(PowerType.Rage)); + SetPower(PowerType.Focus, 0); + + _ApplyAllLevelScaleItemMods(true); + + // update level to hunter/summon pet + Pet pet = GetPet(); + if (pet) + pet.SynchronizeLevelWithOwner(); + + MailLevelReward mailReward = Global.ObjectMgr.GetMailLevelReward(level, getRaceMask()); + if (mailReward != null) + { + //- TODO: Poor design of mail system + SQLTransaction trans = new SQLTransaction(); + new MailDraft(mailReward.mailTemplateId).SendMailTo(trans, this, new MailSender(MailMessageType.Creature, mailReward.senderEntry)); + DB.Characters.CommitTransaction(trans); + } + + UpdateCriteria(CriteriaTypes.ReachLevel); + + // Refer-A-Friend + if (GetSession().GetRecruiterId() != 0) + { + if (level < WorldConfig.GetIntValue(WorldCfg.MaxRecruitAFriendBonusPlayerLevel)) + { + if (level % 2 == 0) + { + ++m_grantableLevels; + + if (!HasByteFlag(PlayerFields.FieldBytes, PlayerFieldOffsets.FieldBytesOffsetRafGrantableLevel, 0x01)) + SetByteFlag(PlayerFields.FieldBytes, PlayerFieldOffsets.FieldBytesOffsetRafGrantableLevel, 0x01); + } + } + } + + Global.ScriptMgr.OnPlayerLevelChanged(this, (byte)oldLevel); + } + + public bool CanParry() + { + return m_canParry; + } + public bool CanBlock() + { + return m_canBlock; + } + + public void ToggleAFK() + { + ToggleFlag(PlayerFields.Flags, PlayerFlags.AFK); + + // afk player not allowed in Battleground + if (!IsGameMaster() && isAFK() && InBattleground() && !InArena()) + LeaveBattleground(); + } + public void ToggleDND() + { + ToggleFlag(PlayerFields.Flags, PlayerFlags.DND); + } + public bool isAFK() { return HasFlag(PlayerFields.Flags, PlayerFlags.AFK); } + public bool isDND() { return HasFlag(PlayerFields.Flags, PlayerFlags.DND); } + public ChatFlags GetChatFlags() + { + ChatFlags tag = ChatFlags.None; + + if (isGMChat()) + tag |= ChatFlags.GM; + if (isDND()) + tag |= ChatFlags.DND; + if (isAFK()) + tag |= ChatFlags.AFK; + if (HasFlag(PlayerFields.Flags, PlayerFlags.Developer)) + tag |= ChatFlags.Dev; + + return tag; + } + + public void InitDisplayIds() + { + PlayerInfo info = Global.ObjectMgr.GetPlayerInfo(GetRace(), GetClass()); + if (info == null) + { + Log.outError(LogFilter.Player, "Player {0} has incorrect race/class pair. Can't init display ids.", GetGUID().ToString()); + return; + } + + byte gender = GetByteValue(PlayerFields.Bytes3, PlayerFieldOffsets.Bytes3OffsetGender); + switch ((Gender)gender) + { + case Gender.Female: + SetDisplayId(info.DisplayId_f); + SetNativeDisplayId(info.DisplayId_f); + break; + case Gender.Male: + SetDisplayId(info.DisplayId_m); + SetNativeDisplayId(info.DisplayId_m); + break; + default: + Log.outError(LogFilter.Player, "Player {0} ({1}) has invalid gender {2}", GetName(), GetGUID().ToString(), gender); + return; + } + } + + //Creature + public Creature GetNPCIfCanInteractWith(ObjectGuid guid, NPCFlags npcflagmask) + { + // unit checks + if (guid.IsEmpty()) + return null; + + if (!IsInWorld) + return null; + + if (IsInFlight()) + return null; + + // exist (we need look pets also for some interaction (quest/etc) + Creature creature = ObjectAccessor.GetCreatureOrPetOrVehicle(this, guid); + if (creature == null) + return null; + + // Deathstate checks + if (!IsAlive() && !Convert.ToBoolean(creature.GetCreatureTemplate().TypeFlags & CreatureTypeFlags.GhostVisible)) + return null; + + // alive or spirit healer + if (!creature.IsAlive() && !Convert.ToBoolean(creature.GetCreatureTemplate().TypeFlags & CreatureTypeFlags.CanInteractWhileDead)) + return null; + + // appropriate npc type + if (npcflagmask != 0 && !creature.HasFlag64(UnitFields.NpcFlags, npcflagmask)) + return null; + + // not allow interaction under control, but allow with own pets + if (!creature.GetCharmerGUID().IsEmpty()) + return null; + + // not unfriendly/hostile + if (creature.GetReactionTo(this) <= ReputationRank.Unfriendly) + return null; + + // not too far + if (!creature.IsWithinDistInMap(this, SharedConst.InteractionDistance)) + return null; + + return creature; + } + + public GameObject GetGameObjectIfCanInteractWith(ObjectGuid guid) + { + GameObject go = GetMap().GetGameObject(guid); + if (go) + { + if (go.IsWithinDistInMap(this, go.GetInteractionDistance())) + return go; + + Log.outDebug(LogFilter.Maps, "Player.GetGameObjectIfCanInteractWith: GameObject '{0}' ({1}) is too far away from player '{2}' ({3}) to be used by him (Distance: {4}, maximal {5} is allowed)", + go.GetName(), go.GetGUID().ToString(), GetName(), GetGUID().ToString(), go.GetDistance(this), go.GetInteractionDistance()); + } + + return null; + } + public GameObject GetGameObjectIfCanInteractWith(ObjectGuid guid, GameObjectTypes type) + { + GameObject go = GetMap().GetGameObject(guid); + if (go != null) + { + if (go.GetGoType() == type) + { + if (go.IsWithinDistInMap(this, go.GetInteractionDistance())) + return go; + + Log.outDebug(LogFilter.Maps, "Player.GetGameObjectIfCanInteractWith: GameObject '{0}' ({1}) is too far away from player '{2}' ({3}) to be used by him (Distance: {4}, maximal {5} is allowed)", + go.GetName(), go.GetGUID().ToString(), GetName(), GetGUID().ToString(), go.GetDistance(this), go.GetInteractionDistance()); + } + } + + return null; + } + + public TrainerSpellState GetTrainerSpellState(TrainerSpell trainer_spell) + { + if (trainer_spell == null) + return TrainerSpellState.Red; + + bool hasSpell = true; + for (var i = 0; i < SharedConst.MaxTrainerspellAbilityReqs; ++i) + { + if (trainer_spell.ReqAbility[i] == 0) + continue; + + if (!HasSpell(trainer_spell.ReqAbility[i])) + { + hasSpell = false; + break; + } + } + // known spell + if (hasSpell) + return TrainerSpellState.Gray; + + // check skill requirement + if (trainer_spell.ReqSkillLine != 0 && GetBaseSkillValue((SkillType)trainer_spell.ReqSkillLine) < trainer_spell.ReqSkillRank) + return TrainerSpellState.Red; + + // check level requirement + if (getLevel() < trainer_spell.ReqLevel) + return TrainerSpellState.Red; + + for (var i = 0; i < SharedConst.MaxTrainerspellAbilityReqs; ++i) + { + if (trainer_spell.ReqAbility[i] == 0) + continue; + + // check race/class requirement + if (!IsSpellFitByClassAndRace(trainer_spell.ReqAbility[i])) + return TrainerSpellState.Red; + + uint prevSpell = Global.SpellMgr.GetPrevSpellInChain(trainer_spell.ReqAbility[i]); + if (prevSpell != 0) + { + // check prev.rank requirement + if (!HasSpell(prevSpell)) + return TrainerSpellState.Red; + } + + var spellsRequired = Global.SpellMgr.GetSpellsRequiredForSpellBounds(trainer_spell.ReqAbility[i]); + foreach (var spellId in spellsRequired) + { + // check additional spell requirement + if (!HasSpell(spellId)) + return TrainerSpellState.Red; + } + } + + // check primary prof. limit + // first rank of primary profession spell when there are no proffesions avalible is disabled + for (var i = 0; i < SharedConst.MaxTrainerspellAbilityReqs; ++i) + { + if (trainer_spell.ReqAbility[i] == 0) + continue; + + SpellInfo learnedSpellInfo = Global.SpellMgr.GetSpellInfo(trainer_spell.ReqAbility[i]); + if (learnedSpellInfo != null && learnedSpellInfo.IsPrimaryProfessionFirstRank() && (GetFreePrimaryProfessionPoints() == 0)) + return TrainerSpellState.GreenDisabled; + } + + return TrainerSpellState.Green; + } + + public void SendInitialPacketsBeforeAddToMap() + { + if (!m_teleport_options.HasAnyFlag(TeleportToOptions.Seamless)) + { + m_movementCounter = 0; + ResetTimeSync(); + } + + SendTimeSync(); + + GetSocial().SendSocialList(this, SocialFlag.All); + + // SMSG_BINDPOINTUPDATE + SendBindPointUpdate(); + + // SMSG_SET_PROFICIENCY + // SMSG_SET_PCT_SPELL_MODIFIER + // SMSG_SET_FLAT_SPELL_MODIFIER + + // SMSG_TALENTS_INFO + SendTalentsInfoData(); + + // SMSG_INITIAL_SPELLS + SendKnownSpells(); + + // SMSG_SEND_UNLEARN_SPELLS + SendPacket(new SendUnlearnSpells()); + + // SMSG_SEND_SPELL_HISTORY + SendSpellHistory sendSpellHistory = new SendSpellHistory(); + GetSpellHistory().WritePacket(sendSpellHistory); + SendPacket(sendSpellHistory); + + // SMSG_SEND_SPELL_CHARGES + SendSpellCharges sendSpellCharges = new SendSpellCharges(); + GetSpellHistory().WritePacket(sendSpellCharges); + SendPacket(sendSpellCharges); + + ActiveGlyphs activeGlyphs = new ActiveGlyphs(); + //activeGlyphs.Glyphs.reserve(GetGlyphs(GetActiveTalentGroup()).size()); + foreach (uint glyphId in GetGlyphs(GetActiveTalentGroup())) + { + List bindableSpells = Global.DB2Mgr.GetGlyphBindableSpells(glyphId); + foreach (uint bindableSpell in bindableSpells) + if (HasSpell(bindableSpell) && !m_overrideSpells.ContainsKey(bindableSpell)) + activeGlyphs.Glyphs.Add(new GlyphBinding(bindableSpell, (ushort)glyphId)); + } + + activeGlyphs.IsFullUpdate = true; + SendPacket(activeGlyphs); + + // SMSG_ACTION_BUTTONS + SendInitialActionButtons(); + + // SMSG_INITIALIZE_FACTIONS + reputationMgr.SendInitialReputations(); + + // SMSG_SETUP_CURRENCY + SendCurrencies(); + + // SMSG_EQUIPMENT_SET_LIST + SendEquipmentSetList(); + + m_achievementSys.SendAllData(this); + + // SMSG_LOGIN_SETTIMESPEED + float TimeSpeed = 0.01666667f; + LoginSetTimeSpeed loginSetTimeSpeed = new LoginSetTimeSpeed(); + loginSetTimeSpeed.NewSpeed = TimeSpeed; + loginSetTimeSpeed.GameTime = (uint)Global.WorldMgr.GetGameTime(); + loginSetTimeSpeed.ServerTime = (uint)Global.WorldMgr.GetGameTime(); + loginSetTimeSpeed.GameTimeHolidayOffset = 0; /// @todo + loginSetTimeSpeed.ServerTimeHolidayOffset = 0; /// @todo + SendPacket(loginSetTimeSpeed); + + // SMSG_WORLD_SERVER_INFO + WorldServerInfo worldServerInfo = new WorldServerInfo(); + worldServerInfo.InstanceGroupSize.Set(GetMap().GetMapDifficulty().MaxPlayers); /// @todo + worldServerInfo.IsTournamentRealm = 0; /// @todo + worldServerInfo.RestrictedAccountMaxLevel.Clear(); /// @todo + worldServerInfo.RestrictedAccountMaxMoney.Clear(); /// @todo + worldServerInfo.DifficultyID = (uint)GetMap().GetDifficultyID(); + // worldServerInfo.XRealmPvpAlert; /// @todo + SendPacket(worldServerInfo); + + // Spell modifiers + SendSpellModifiers(); + + // SMSG_ACCOUNT_MOUNT_UPDATE + AccountMountUpdate mountUpdate = new AccountMountUpdate(); + mountUpdate.IsFullUpdate = true; + mountUpdate.Mounts = GetSession().GetCollectionMgr().GetAccountMounts(); + SendPacket(mountUpdate); + + // SMSG_ACCOUNT_TOYS_UPDATE + AccountToysUpdate toysUpdate = new AccountToysUpdate(); + toysUpdate.IsFullUpdate = true; + toysUpdate.Toys = GetSession().GetCollectionMgr().GetAccountToys(); + SendPacket(toysUpdate); + + // SMSG_ACCOUNT_HEIRLOOM_UPDATE + AccountHeirloomUpdate heirloomUpdate = new AccountHeirloomUpdate(); + heirloomUpdate.IsFullUpdate = true; + heirloomUpdate.Heirlooms = GetSession().GetCollectionMgr().GetAccountHeirlooms(); + SendPacket(heirloomUpdate); + + GetSession().GetCollectionMgr().SendFavoriteAppearances(); + + InitialSetup initialSetup = new InitialSetup(); + initialSetup.ServerExpansionLevel = (byte)WorldConfig.GetIntValue(WorldCfg.Expansion); + SendPacket(initialSetup); + + SetMover(this); + } + + public void SendInitialPacketsAfterAddToMap() + { + UpdateVisibilityForPlayer(); + + // update zone + uint newzone, newarea; + GetZoneAndAreaId(out newzone, out newarea); + UpdateZone(newzone, newarea); // also call SendInitWorldStates(); + + GetSession().SendLoadCUFProfiles(); + + CastSpell(this, 836, true); // LOGINEFFECT + + // set some aura effects that send packet to player client after add player to map + // SendMessageToSet not send it to player not it map, only for aura that not changed anything at re-apply + // same auras state lost at far teleport, send it one more time in this case also + AuraType[] auratypes = + { + AuraType.ModFear, AuraType.Transform, AuraType.WaterWalk, + AuraType.FeatherFall, AuraType.Hover, AuraType.SafeFall, + AuraType.Fly, AuraType.ModIncreaseMountedFlightSpeed, AuraType.None + }; + foreach (var aura in auratypes) + { + var auraList = GetAuraEffectsByType(aura); + if (!auraList.Empty()) + auraList.First().HandleEffect(this, AuraEffectHandleModes.SendForClient, true); + } + + if (HasAuraType(AuraType.ModStun)) + SetRooted(true); + + MoveSetCompoundState setCompoundState = new MoveSetCompoundState(); + // manual send package (have code in HandleEffect(this, AURA_EFFECT_HANDLE_SEND_FOR_CLIENT, true); that must not be re-applied. + if (HasAuraType(AuraType.ModRoot) || HasAuraType(AuraType.ModRoot2)) + setCompoundState.StateChanges.Add(new MoveSetCompoundState.MoveStateChange(ServerOpcodes.MoveRoot, m_movementCounter++)); + + if (HasAuraType(AuraType.FeatherFall)) + setCompoundState.StateChanges.Add(new MoveSetCompoundState.MoveStateChange(ServerOpcodes.MoveSetFeatherFall, m_movementCounter++)); + + if (HasAuraType(AuraType.WaterWalk)) + setCompoundState.StateChanges.Add(new MoveSetCompoundState.MoveStateChange(ServerOpcodes.MoveSetWaterWalk, m_movementCounter++)); + + if (HasAuraType(AuraType.Hover)) + setCompoundState.StateChanges.Add(new MoveSetCompoundState.MoveStateChange(ServerOpcodes.MoveSetHovering, m_movementCounter++)); + + if (HasAuraType(AuraType.CanTurnWhileFalling)) + setCompoundState.StateChanges.Add(new MoveSetCompoundState.MoveStateChange(ServerOpcodes.MoveSetCanTurnWhileFalling, m_movementCounter++)); + + if (HasAura(196055)) //DH DoubleJump + setCompoundState.StateChanges.Add(new MoveSetCompoundState.MoveStateChange(ServerOpcodes.MoveEnableDoubleJump, m_movementCounter++)); + + if (!setCompoundState.StateChanges.Empty()) + { + setCompoundState.MoverGUID = GetGUID(); + SendPacket(setCompoundState); + } + + SendAurasForTarget(this); + SendEnchantmentDurations(); // must be after add to map + SendItemDurations(); // must be after add to map + + // raid downscaling - send difficulty to player + if (GetMap().IsRaid()) + { + m_prevMapDifficulty = GetMap().GetDifficultyID(); + DifficultyRecord difficulty = CliDB.DifficultyStorage.LookupByKey(m_prevMapDifficulty); + SendRaidDifficulty(difficulty.Flags.HasAnyFlag(DifficultyFlags.Legacy), (int)m_prevMapDifficulty); + } + else if (GetMap().IsNonRaidDungeon()) + { + m_prevMapDifficulty = GetMap().GetDifficultyID(); + SendDungeonDifficulty((int)m_prevMapDifficulty); + } + else if (!GetMap().Instanceable()) + { + DifficultyRecord difficulty = CliDB.DifficultyStorage.LookupByKey(m_prevMapDifficulty); + SendRaidDifficulty(difficulty.Flags.HasAnyFlag(DifficultyFlags.Legacy)); + } + + if (_garrison != null) + _garrison.SendRemoteInfo(); + } + + public bool CanSpeak() + { + return GetSession().m_muteTime <= Time.UnixTime; + } + + public void RemoveSocial() + { + Global.SocialMgr.RemovePlayerSocial(GetGUID()); + m_social = null; + } + + public void SaveRecallPosition() + { + m_recall_location = new WorldLocation(this); + } + public void Recall() { TeleportTo(m_recall_location); } + + public uint GetSaveTimer() { return m_nextSave; } + void SetSaveTimer(uint timer) { m_nextSave = timer; } + + void SendAurasForTarget(Unit target) + { + if (target == null || target.GetVisibleAuras().Empty()) // speedup things + return; + + var visibleAuras = target.GetVisibleAuras(); + + AuraUpdate update = new AuraUpdate(); + update.UpdateAll = true; + update.UnitGUID = target.GetGUID(); + + foreach (var auraApp in visibleAuras) + { + AuraInfo auraInfo = new AuraInfo(); + auraApp.BuildUpdatePacket(ref auraInfo, false); + update.Auras.Add(auraInfo); + } + + SendPacket(update); + } + + public void InitStatsForLevel(bool reapplyMods = false) + { + if (reapplyMods) //reapply stats values only on .reset stats (level) command + _RemoveAllStatBonuses(); + + uint basemana; + Global.ObjectMgr.GetPlayerClassLevelInfo(GetClass(), getLevel(), out basemana); + + PlayerLevelInfo info = Global.ObjectMgr.GetPlayerLevelInfo(GetRace(), GetClass(), getLevel()); + + SetUInt32Value(PlayerFields.MaxLevel, WorldConfig.GetUIntValue(WorldCfg.MaxPlayerLevel)); + SetUInt32Value(PlayerFields.NextLevelXp, Global.ObjectMgr.GetXPForLevel(getLevel())); + + // reset before any aura state sources (health set/aura apply) + SetUInt32Value(UnitFields.AuraState, 0); + + UpdateSkillsForLevel(); + + // set default cast time multiplier + SetFloatValue(UnitFields.ModCastSpeed, 1.0f); + SetFloatValue(UnitFields.ModCastHaste, 1.0f); + SetFloatValue(UnitFields.ModHaste, 1.0f); + SetFloatValue(UnitFields.ModRangedHaste, 1.0f); + SetFloatValue(UnitFields.ModHasteRegen, 1.0f); + SetFloatValue(UnitFields.ModTimeRate, 1.0f); + + // reset size before reapply auras + SetObjectScale(1.0f); + + // save base values (bonuses already included in stored stats + for (var i = Stats.Strength; i < Stats.Max; ++i) + SetCreateStat(i, info.stats[(int)i]); + + for (var i = Stats.Strength; i < Stats.Max; ++i) + SetStat(i, info.stats[(int)i]); + + SetCreateHealth(0); + + //set create powers + SetCreateMana(basemana); + + SetArmor((int)(GetCreateStat(Stats.Agility) * 2)); + + InitStatBuffMods(); + + //reset rating fields values + for (var index = PlayerFields.CombatRating1; index < PlayerFields.CombatRating1 + (int)CombatRating.Max; ++index) + SetUInt32Value(index, 0); + + SetUInt32Value(PlayerFields.ModHealingDonePos, 0); + SetFloatValue(PlayerFields.ModHealingPct, 1.0f); + SetFloatValue(PlayerFields.ModHealingDonePct, 1.0f); + SetFloatValue(PlayerFields.ModPeriodicHealingDonePercent, 1.0f); + for (byte i = 0; i < 7; ++i) + { + SetUInt32Value(PlayerFields.ModDamageDoneNeg + i, 0); + SetUInt32Value(PlayerFields.ModDamageDonePos + i, 0); + SetFloatValue(PlayerFields.ModDamageDonePct + i, 1.0f); + } + SetFloatValue(PlayerFields.ModSpellPowerPct, 1.0f); + + //reset attack power, damage and attack speed fields + SetBaseAttackTime(WeaponAttackType.BaseAttack, SharedConst.BaseAttackTime); + SetBaseAttackTime(WeaponAttackType.OffAttack, SharedConst.BaseAttackTime); + SetBaseAttackTime(WeaponAttackType.RangedAttack, SharedConst.BaseAttackTime); + + SetFloatValue(UnitFields.MinDamage, 0.0f); + SetFloatValue(UnitFields.MaxDamage, 0.0f); + SetFloatValue(UnitFields.MinOffHandDamage, 0.0f); + SetFloatValue(UnitFields.MaxOffHandDamage, 0.0f); + SetFloatValue(UnitFields.MinRangedDamage, 0.0f); + SetFloatValue(UnitFields.MaxRangedDamage, 0.0f); + for (var i = 0; i < 3; ++i) + { + SetFloatValue(PlayerFields.WeaponDmgMultipliers + i, 1.0f); + SetFloatValue(PlayerFields.WeaponAtkSpeedMultipliers + i, 1.0f); + } + + SetInt32Value(UnitFields.AttackPower, 0); + SetFloatValue(UnitFields.AttackPowerMultiplier, 0.0f); + SetInt32Value(UnitFields.RangedAttackPower, 0); + SetFloatValue(UnitFields.RangedAttackPowerMultiplier, 0.0f); + + // Base crit values (will be recalculated in UpdateAllStats() at loading and in _ApplyAllStatBonuses() at reset + SetFloatValue(PlayerFields.CritPercentage, 0.0f); + SetFloatValue(PlayerFields.OffhandCritPercentage, 0.0f); + SetFloatValue(PlayerFields.RangedCritPercentage, 0.0f); + + // Init spell schools (will be recalculated in UpdateAllStats() at loading and in _ApplyAllStatBonuses() at reset + SetFloatValue(PlayerFields.SpellCritPercentage1, 0.0f); + + SetFloatValue(PlayerFields.ParryPercentage, 0.0f); + SetFloatValue(PlayerFields.BlockPercentage, 0.0f); + + // Static 30% damage blocked + SetUInt32Value(PlayerFields.ShieldBlock, 30); + + // Dodge percentage + SetFloatValue(PlayerFields.DodgePercentage, 0.0f); + + // set armor (resistance 0) to original value (create_agility*2) + SetArmor((int)(GetCreateStat(Stats.Agility) * 2)); + SetResistanceBuffMods(SpellSchools.Normal, true, 0.0f); + SetResistanceBuffMods(SpellSchools.Normal, false, 0.0f); + // set other resistance to original value (0) + for (var i = 1; i < (int)SpellSchools.Max; ++i) + { + SetResistance((SpellSchools)i, 0); + SetResistanceBuffMods((SpellSchools)i, true, 0.0f); + SetResistanceBuffMods((SpellSchools)i, false, 0.0f); + } + + SetUInt32Value(PlayerFields.ModTargetResistance, 0); + SetUInt32Value(PlayerFields.ModTargetPhysicalResistance, 0); + for (var i = 0; i < (int)SpellSchools.Max; ++i) + { + SetUInt32Value(UnitFields.PowerCostModifier + i, 0); + SetFloatValue(UnitFields.PowerCostMultiplier + i, 0.0f); + } + // Reset no reagent cost field + for (byte i = 0; i < 3; ++i) + SetUInt32Value(PlayerFields.NoReagentCost1 + i, 0); + // Init data for form but skip reapply item mods for form + InitDataForForm(reapplyMods); + + // save new stats + for (var i = PowerType.Mana; i < PowerType.Max; ++i) + SetMaxPower(i, GetCreatePowers(i)); + + SetMaxHealth(0); // stamina bonus will applied later + + // cleanup mounted state (it will set correctly at aura loading if player saved at mount. + SetUInt32Value(UnitFields.MountDisplayId, 0); + + // cleanup unit flags (will be re-applied if need at aura load). + RemoveFlag(UnitFields.Flags, + UnitFlags.NonAttackable | UnitFlags.RemoveClientControl | UnitFlags.NotAttackable1 | + UnitFlags.ImmuneToPc | UnitFlags.ImmuneToNpc | UnitFlags.Looting | + UnitFlags.PetInCombat | UnitFlags.Silenced | UnitFlags.Pacified | + UnitFlags.Stunned | UnitFlags.InCombat | UnitFlags.Disarmed | + UnitFlags.Confused | UnitFlags.Fleeing | UnitFlags.NotSelectable | + UnitFlags.Skinnable | UnitFlags.Mount | UnitFlags.TaxiFlight); + SetFlag(UnitFields.Flags, UnitFlags.PvpAttackable); // must be set + + SetFlag(UnitFields.Flags2, UnitFlags2.RegeneratePower);// must be set + + // cleanup player flags (will be re-applied if need at aura load), to avoid have ghost flag without ghost aura, for example. + RemoveFlag(PlayerFields.Flags, PlayerFlags.AFK | PlayerFlags.DND | PlayerFlags.GM | PlayerFlags.Ghost | PlayerFlags.AllowOnlyAbility); + + RemoveStandFlags(UnitBytes1Flags.All); // one form stealth modified bytes + RemoveByteFlag(UnitFields.Bytes2, UnitBytes2Offsets.PvpFlag, (UnitBytes2Flags.FFAPvp | UnitBytes2Flags.Sanctuary)); + + // restore if need some important flags + SetUInt32Value(PlayerFields.FieldBytes2, 0); // flags empty by default + + if (reapplyMods) // reapply stats values only on .reset stats (level) command + _ApplyAllStatBonuses(); + + // set current level health and mana/energy to maximum after applying all mods. + SetFullHealth(); + SetPower(PowerType.Mana, GetMaxPower(PowerType.Mana)); + SetPower(PowerType.Energy, GetMaxPower(PowerType.Energy)); + if (GetPower(PowerType.Rage) > GetMaxPower(PowerType.Rage)) + SetPower(PowerType.Rage, GetMaxPower(PowerType.Rage)); + SetPower(PowerType.Focus, GetMaxPower(PowerType.Focus)); + SetPower(PowerType.RunicPower, 0); + + // update level to hunter/summon pet + Pet pet = GetPet(); + if (pet) + pet.SynchronizeLevelWithOwner(); + } + public void InitDataForForm(bool reapplyMods = false) + { + ShapeShiftForm form = GetShapeshiftForm(); + + var ssEntry = CliDB.SpellShapeshiftFormStorage.LookupByKey((uint)form); + if (ssEntry != null && ssEntry.CombatRoundTime != 0) + { + SetBaseAttackTime(WeaponAttackType.BaseAttack, ssEntry.CombatRoundTime); + SetBaseAttackTime(WeaponAttackType.OffAttack, ssEntry.CombatRoundTime); + SetBaseAttackTime(WeaponAttackType.RangedAttack, SharedConst.BaseAttackTime); + } + else + SetRegularAttackTime(); + + UpdateDisplayPower(); + + // update auras at form change, ignore this at mods reapply (.reset stats/etc) when form not change. + if (!reapplyMods) + UpdateEquipSpellsAtFormChange(); + + UpdateAttackPowerAndDamage(); + UpdateAttackPowerAndDamage(true); + } + + public ReputationRank GetReputationRank(uint faction) + { + var factionEntry = CliDB.FactionStorage.LookupByKey(faction); + return GetReputationMgr().GetRank(factionEntry); + } + public ReputationMgr GetReputationMgr() + { + return reputationMgr; + } + + + + + #region Sends / Updates + void BeforeVisibilityDestroy(WorldObject obj, Player p) + { + if (!obj.IsTypeId(TypeId.Unit)) + return; + + if (p.GetPetGUID() == obj.GetGUID() && obj.ToCreature().IsPet()) + ((Pet)obj).Remove(PetSaveMode.NotInSlot, true); + } + public void UpdateVisibilityOf(WorldObject target) + { + if (HaveAtClient(target)) + { + if (!CanSeeOrDetect(target, false, true)) + { + if (target.IsTypeId(TypeId.Unit)) + BeforeVisibilityDestroy(target.ToCreature(), this); + + target.DestroyForPlayer(this); + m_clientGUIDs.Remove(target.GetGUID()); + } + } + else + { + if (CanSeeOrDetect(target, false, true)) + { + target.SendUpdateToPlayer(this); + m_clientGUIDs.Add(target.GetGUID()); + + // target aura duration for caster show only if target exist at caster client + // send data at target visibility change (adding to client) + if (target.isTypeMask(TypeMask.Unit)) + SendInitialVisiblePackets(target.ToUnit()); + } + } + } + public void UpdateVisibilityOf(T target, UpdateData data, List visibleNow) where T : WorldObject + { + if (HaveAtClient(target)) + { + if (!CanSeeOrDetect(target, false, true)) + { + BeforeVisibilityDestroy(target, this); + + target.BuildOutOfRangeUpdateBlock(data); + m_clientGUIDs.Remove(target.GetGUID()); + } + } + else + { + if (CanSeeOrDetect(target, false, true)) + { + target.BuildCreateUpdateBlockForPlayer(data, this); + UpdateVisibilityOf_helper(m_clientGUIDs, target, visibleNow); + } + } + } + void UpdateVisibilityOf_helper(List s64, GameObject target, List v) + { + // @HACK: This is to prevent objects like deeprun tram from disappearing when player moves far from its spawn point while riding it + // But exclude stoppable elevators from this hack - they would be teleporting from one end to another + // if affected transports move so far horizontally that it causes them to run out of visibility range then you are out of luck + // fix visibility instead of adding hacks here + if (target.IsDynTransport()) + s64.Add(target.GetGUID()); + } + void UpdateVisibilityOf_helper(List s64, Creature target, List v) + { + s64.Add(target.GetGUID()); + v.Add(target); + } + + void UpdateVisibilityOf_helper(List s64, Player target, List v) + { + s64.Add(target.GetGUID()); + v.Add(target); + } + + void UpdateVisibilityOf_helper(List s64, T target, List v) where T : WorldObject + { + s64.Add(target.GetGUID()); + } + + public void SendInitialVisiblePackets(Unit target) + { + SendAurasForTarget(target); + if (target.IsAlive()) + { + if (target.HasUnitState(UnitState.MeleeAttacking) && target.GetVictim() != null) + target.SendMeleeAttackStart(target.GetVictim()); + } + } + + public override void UpdateObjectVisibility(bool forced = true) + { + // Prevent updating visibility if player is not in world (example: LoadFromDB sets drunkstate which updates invisibility while player is not in map) + if (!IsInWorld) + return; + + if (!forced) + AddToNotify(NotifyFlags.VisibilityChanged); + else + { + base.UpdateObjectVisibility(true); + UpdateVisibilityForPlayer(); + } + } + + public void UpdateVisibilityForPlayer() + { + // updates visibility of all objects around point of view for current player + var notifier = new VisibleNotifier(this); + Cell.VisitAllObjects(seerView, notifier, GetSightRange()); + notifier.SendToSelf(); // send gathered data + } + + public void SetSeer(WorldObject target) { seerView = target; } + + public override void SendMessageToSetInRange(ServerPacket data, float dist, bool self) + { + if (self) + SendPacket(data); + + MessageDistDeliverer notifier = new MessageDistDeliverer(this, data, dist); + Cell.VisitWorldObjects(this, notifier, dist); + } + + void SendMessageToSetInRange(ServerPacket data, float dist, bool self, bool own_team_only) + { + if (self) + SendPacket(data); + + MessageDistDeliverer notifier = new MessageDistDeliverer(this, data, dist, own_team_only); + Cell.VisitWorldObjects(this, notifier, dist); + } + + public override void SendMessageToSet(ServerPacket data, Player skipped_rcvr) + { + if (skipped_rcvr != this) + SendPacket(data); + + // we use World.GetMaxVisibleDistance() because i cannot see why not use a distance + // update: replaced by GetMap().GetVisibilityDistance() + MessageDistDeliverer notifier = new MessageDistDeliverer(this, data, GetVisibilityRange(), false, skipped_rcvr); + Cell.VisitWorldObjects(this, notifier, GetVisibilityRange()); + } + public override void SendMessageToSet(ServerPacket data, bool self) + { + SendMessageToSetInRange(data, GetVisibilityRange(), self); + } + + public override bool UpdatePosition(Position pos, bool teleport = false) + { + return UpdatePosition(pos.GetPositionX(), pos.GetPositionY(), pos.GetPositionZ(), pos.GetOrientation(), teleport); + } + + public override bool UpdatePosition(float x, float y, float z, float orientation, bool teleport = false) + { + if (!base.UpdatePosition(x, y, z, orientation, teleport)) + return false; + + // group update + if (GetGroup()) + SetGroupUpdateFlag(GroupUpdateFlags.Position); + + if (GetTrader() && !IsWithinDistInMap(GetTrader(), SharedConst.InteractionDistance)) + GetSession().SendCancelTrade(); + + CheckAreaExploreAndOutdoor(); + + return true; + } + + void SendNewCurrency(uint id) + { + var Curr = _currencyStorage.LookupByKey(id); + if (Curr == null) + return; + + CurrencyTypesRecord entry = CliDB.CurrencyTypesStorage.LookupByKey(id); + if (entry == null) // should never happen + return; + + SetupCurrency packet = new SetupCurrency(); + SetupCurrency.Record record = new SetupCurrency.Record(); + record.Type = entry.Id; + record.Quantity = Curr.Quantity; + record.WeeklyQuantity.Set(Curr.WeeklyQuantity); + record.MaxWeeklyQuantity.Set(GetCurrencyWeekCap(entry)); + record.TrackedQuantity.Set(Curr.TrackedQuantity); + record.Flags = Curr.Flags; + + packet.Data.Add(record); + + SendPacket(packet); + } + + void SendCurrencies() + { + SetupCurrency packet = new SetupCurrency(); + + foreach (var pair in _currencyStorage) + { + CurrencyTypesRecord entry = CliDB.CurrencyTypesStorage.LookupByKey(pair.Key); + + // not send init meta currencies. + if (entry == null || entry.CategoryID == 89) //CURRENCY_CATEGORY_META_CONQUEST + continue; + + SetupCurrency.Record record = new SetupCurrency.Record(); + record.Type = entry.Id; + record.Quantity = pair.Value.Quantity; + record.WeeklyQuantity.Set(pair.Value.WeeklyQuantity); + record.MaxWeeklyQuantity.Set(GetCurrencyWeekCap(entry)); + record.TrackedQuantity.Set(pair.Value.TrackedQuantity); + record.Flags = pair.Value.Flags; + + packet.Data.Add(record); + } + + SendPacket(packet); + } + + public void ResetCurrencyWeekCap() + { + for (byte arenaSlot = 0; arenaSlot < 3; arenaSlot++) + { + uint arenaTeamId = GetArenaTeamId(arenaSlot); + if (arenaTeamId != 0) + { + ArenaTeam arenaTeam = Global.ArenaTeamMgr.GetArenaTeamById(arenaTeamId); + arenaTeam.FinishWeek(); // set played this week etc values to 0 in memory, too + arenaTeam.SaveToDB(); // save changes + arenaTeam.NotifyStatsChanged(); // notify the players of the changes + } + } + + foreach (var currency in _currencyStorage.Values) + { + + currency.WeeklyQuantity = 0; + currency.state = PlayerCurrencyState.Changed; + } + + SendPacket(new ResetWeeklyCurrency()); + } + + void CheckAreaExploreAndOutdoor() + { + if (!IsAlive()) + return; + + if (IsInFlight()) + return; + + bool isOutdoor; + uint areaId = GetMap().GetAreaId(GetPositionX(), GetPositionY(), GetPositionZ(), out isOutdoor); + AreaTableRecord areaEntry = CliDB.AreaTableStorage.LookupByKey(areaId); + + if (WorldConfig.GetBoolValue(WorldCfg.VmapIndoorCheck) && !isOutdoor) + RemoveAurasWithAttribute(SpellAttr0.OutdoorsOnly); + + if (areaId == 0) + return; + + if (areaEntry == null) + { + Log.outError(LogFilter.Player, "Player '{0}' ({1}) discovered unknown area (x: {2} y: {3} z: {4} map: {5})", + GetName(), GetGUID().ToString(), GetPositionX(), GetPositionY(), GetPositionZ(), GetMapId()); + return; + } + + int offset = areaEntry.AreaBit / 32; + if (offset >= PlayerConst.ExploredZonesSize) + { + Log.outError(LogFilter.Player, "Wrong area flag {0} in map data for (X: {1} Y: {2}) point to field PLAYER_EXPLORED_ZONES_1 + {3} ( {4} must be < {5} ).", + areaId, GetPositionX(), GetPositionY(), offset, offset, PlayerConst.ExploredZonesSize); + return; + } + + uint val = 1u << (areaEntry.AreaBit % 32); + uint currFields = GetUInt32Value(PlayerFields.ExploredZones1 + offset); + + if (!Convert.ToBoolean(currFields & val)) + { + SetUInt32Value(PlayerFields.ExploredZones1 + offset, currFields | val); + + UpdateCriteria(CriteriaTypes.ExploreArea); + + if (areaEntry.ExplorationLevel > 0) + { + if (getLevel() >= WorldConfig.GetIntValue(WorldCfg.MaxPlayerLevel)) + { + SendExplorationExperience(areaId, 0); + } + else + { + int diff = (int)(getLevel() - areaEntry.ExplorationLevel); + uint XP = 0; + if (diff < -5) + { + XP = (uint)(Global.ObjectMgr.GetBaseXP(getLevel() + 5) * WorldConfig.GetFloatValue(WorldCfg.RateXpExplore)); + } + else if (diff > 5) + { + int exploration_percent = 100 - ((diff - 5) * 5); + if (exploration_percent < 0) + exploration_percent = 0; + + XP = (uint)(Global.ObjectMgr.GetBaseXP(areaEntry.ExplorationLevel) * exploration_percent / 100 * WorldConfig.GetFloatValue(WorldCfg.RateXpExplore)); + } + else + { + XP = (uint)(Global.ObjectMgr.GetBaseXP(areaEntry.ExplorationLevel) * WorldConfig.GetFloatValue(WorldCfg.RateXpExplore)); + } + + GiveXP(XP, null); + SendExplorationExperience(areaId, XP); + } + Log.outInfo(LogFilter.Player, "Player {0} discovered a new area: {1}", GetGUID().ToString(), areaId); + } + } + } + void SendExplorationExperience(uint Area, uint Experience) + { + SendPacket(new ExplorationExperience(Experience, Area)); + } + + public void SendSysMessage(CypherStrings str, params object[] args) + { + string input = Global.ObjectMgr.GetCypherString(str); + string pattern = @"%(\d+(\.\d+)?)?(d|f|s|u)"; + + int count = 0; + string result = System.Text.RegularExpressions.Regex.Replace(input, pattern, m => + { + return String.Concat("{", count++, "}"); + }); + + SendSysMessage(result, args); + } + public void SendSysMessage(string str, params object[] args) + { + new CommandHandler(Session).SendSysMessage(str, args); + } + public void SendBuyError(BuyResult msg, Creature creature, uint item) + { + BuyFailed packet = new BuyFailed(); + packet.VendorGUID = creature ? creature.GetGUID() : ObjectGuid.Empty; + packet.Muid = item; + packet.Reason = msg; + SendPacket(packet); + } + public void SendSellError(SellResult msg, Creature creature, ObjectGuid guid) + { + SellResponse sellResponse = new SellResponse(); + sellResponse.VendorGUID = (creature ? creature.GetGUID() : ObjectGuid.Empty); + sellResponse.ItemGUID = guid; + sellResponse.Reason = msg; + SendPacket(sellResponse); + } + #endregion + + #region Chat + public override void Say(string text, Language language, WorldObject obj = null) + { + Global.ScriptMgr.OnPlayerChat(this, ChatMsg.Say, language, text); + + ChatPkt data = new ChatPkt(); + data.Initialize(ChatMsg.Say, language, this, this, text); + SendMessageToSetInRange(data, WorldConfig.GetFloatValue(WorldCfg.ListenRangeSay), true); + } + public override void Say(uint textId, WorldObject target = null) + { + Talk(textId, ChatMsg.Say, WorldConfig.GetFloatValue(WorldCfg.ListenRangeSay), target); + } + public override void Yell(string text, Language language, WorldObject obj = null) + { + Global.ScriptMgr.OnPlayerChat(this, ChatMsg.Yell, language, text); + + ChatPkt data = new ChatPkt(); + data.Initialize(ChatMsg.Yell, language, this, this, text); + SendMessageToSetInRange(data, WorldConfig.GetFloatValue(WorldCfg.ListenRangeYell), true); + } + public override void Yell(uint textId, WorldObject target = null) + { + Talk(textId, ChatMsg.Yell, WorldConfig.GetFloatValue(WorldCfg.ListenRangeYell), target); + } + public override void TextEmote(string text, WorldObject obj = null, bool something = false) + { + Global.ScriptMgr.OnPlayerChat(this, ChatMsg.Emote, Language.Universal, text); + + ChatPkt data = new ChatPkt(); + data.Initialize(ChatMsg.Emote, Language.Universal, this, this, text); + SendMessageToSetInRange(data, WorldConfig.GetFloatValue(WorldCfg.ListenRangeTextemote), !GetSession().HasPermission(RBACPermissions.TwoSideInteractionChat)); + } + public override void TextEmote(uint textId, WorldObject target = null, bool isBossEmote = false) + { + Talk(textId, ChatMsg.Emote, WorldConfig.GetFloatValue(WorldCfg.ListenRangeTextemote), target); + } + public void WhisperAddon(string text, string prefix, Player receiver) + { + Global.ScriptMgr.OnPlayerChat(this, ChatMsg.Whisper, Language.Universal, text, receiver); + + if (!receiver.GetSession().IsAddonRegistered(prefix)) + return; + + ChatPkt data = new ChatPkt(); + data.Initialize(ChatMsg.Whisper, Language.Addon, this, this, text, 0, "", LocaleConstant.enUS, prefix); + receiver.SendPacket(data); + } + public override void Whisper(string text, Language language, Player target = null, bool something = false) + { + bool isAddonMessage = language == Language.Addon; + + if (!isAddonMessage) // if not addon data + language = Language.Universal; // whispers should always be readable + + //Player rPlayer = Global.ObjAccessor.FindPlayer(receiver); + + Global.ScriptMgr.OnPlayerChat(this, ChatMsg.Whisper, language, text, target); + + ChatPkt data = new ChatPkt(); + data.Initialize(ChatMsg.Whisper, language, this, this, text); + target.SendPacket(data); + + // rest stuff shouldn't happen in case of addon message + if (isAddonMessage) + return; + + data.Initialize(ChatMsg.WhisperInform, language, target, target, text); + SendPacket(data); + + if (!isAcceptWhispers() && !IsGameMaster() && !target.IsGameMaster()) + { + SetAcceptWhispers(true); + SendSysMessage(CypherStrings.CommandWhisperon); + } + + // announce afk or dnd message + if (target.isAFK()) + SendSysMessage(CypherStrings.PlayerAfk, target.GetName(), target.autoReplyMsg); + else if (target.isDND()) + SendSysMessage(CypherStrings.PlayerDnd, target.GetName(), target.autoReplyMsg); + } + + public override void Whisper(uint textId, Player target, bool isBossWhisper = false) + { + if (!target) + return; + + BroadcastTextRecord bct = CliDB.BroadcastTextStorage.LookupByKey(textId); + if (bct == null) + { + Log.outError(LogFilter.Unit, "WorldObject.MonsterWhisper: `broadcast_text` was not {0} found", textId); + return; + } + + LocaleConstant locale = target.GetSession().GetSessionDbLocaleIndex(); + ChatPkt packet = new ChatPkt(); + packet.Initialize(ChatMsg.Whisper, Language.Universal, this, target, Global.DB2Mgr.GetBroadcastTextValue(bct, locale, GetGender())); + target.SendPacket(packet); + } + #endregion + + public void ClearWhisperWhiteList() { WhisperList.Clear(); } + public void AddWhisperWhiteList(ObjectGuid guid) { WhisperList.Add(guid); } + public bool IsInWhisperWhiteList(ObjectGuid guid) { return WhisperList.Contains(guid); } + public void RemoveFromWhisperWhiteList(ObjectGuid guid) { WhisperList.Remove(guid); } + + public void SetFallInformation(uint time, float z) + { + m_lastFallTime = time; + m_lastFallZ = z; + } + + public byte getCinematic() { return m_cinematic; } + public void setCinematic(byte cine) { m_cinematic = cine; } + + public uint GetMovie() { return m_movie; } + public void SetMovie(uint movie) { m_movie = movie; } + + public void SendCinematicStart(uint CinematicSequenceId) + { + TriggerCinematic packet = new TriggerCinematic(); + packet.CinematicID = CinematicSequenceId; + SendPacket(packet); + + CinematicSequencesRecord sequence = CliDB.CinematicSequencesStorage.LookupByKey(CinematicSequenceId); + if (sequence != null) + _cinematicMgr.SetActiveCinematicCamera(sequence.Camera[0]); + } + public void SendMovieStart(uint movieId) + { + SetMovie(movieId); + TriggerMovie packet = new TriggerMovie(); + packet.MovieID = movieId; + SendPacket(packet); + } + + public override void SetObjectScale(float scale) + { + base.SetObjectScale(scale); + SetFloatValue(UnitFields.BoundingRadius, scale * SharedConst.DefaultWorldObjectSize); + SetFloatValue(UnitFields.CombatReach, scale * SharedConst.DefaultCombatReach); + if (IsInWorld) + SendMovementSetCollisionHeight(scale * GetCollisionHeight(IsMounted())); + } + + public void GiveXP(uint xp, Unit victim, float group_rate = 1.0f) + { + if (xp < 1) + return; + + if (!IsAlive() && GetBattlegroundId() == 0) + return; + + if (HasFlag(PlayerFields.Flags, PlayerFlags.NoXPGain)) + return; + + if (victim != null && victim.IsTypeId(TypeId.Unit) && !victim.ToCreature().hasLootRecipient()) + return; + + uint level = getLevel(); + + Global.ScriptMgr.OnGivePlayerXP(this, xp, victim); + + // XP to money conversion processed in Player.RewardQuest + if (level >= WorldConfig.GetIntValue(WorldCfg.MaxPlayerLevel)) + return; + + uint bonus_xp = 0; + bool recruitAFriend = GetsRecruitAFriendBonus(true); + + // RaF does NOT stack with rested experience + if (recruitAFriend) + bonus_xp = 2 * xp; // xp + bonus_xp must add up to 3 * xp for RaF; calculation for quests done client-side + else + bonus_xp = victim != null ? _restMgr.GetRestBonusFor(RestTypes.XP, xp) : 0; // XP resting bonus + + LogXPGain packet = new LogXPGain(); + packet.Victim = victim ? victim.GetGUID() : ObjectGuid.Empty; + packet.Original = (int)(xp + bonus_xp); + packet.Reason = victim ? PlayerLogXPReason.Kill : PlayerLogXPReason.NoKill; + packet.Amount = (int)xp; + packet.GroupBonus = group_rate; + packet.ReferAFriend = recruitAFriend; + SendPacket(packet); + + uint curXP = GetUInt32Value(PlayerFields.Xp); + uint nextLvlXP = GetUInt32Value(PlayerFields.NextLevelXp); + uint newXP = curXP + xp + bonus_xp; + + while (newXP >= nextLvlXP && level < WorldConfig.GetIntValue(WorldCfg.MaxPlayerLevel)) + { + newXP -= nextLvlXP; + + if (level < WorldConfig.GetIntValue(WorldCfg.MaxPlayerLevel)) + GiveLevel(level + 1); + + level = getLevel(); + nextLvlXP = GetUInt32Value(PlayerFields.NextLevelXp); + } + + SetUInt32Value(PlayerFields.Xp, newXP); + } + + public void HandleBaseModValue(BaseModGroup modGroup, BaseModType modType, float amount, bool apply) + { + if (modGroup >= BaseModGroup.End || modType >= BaseModType.End) + { + Log.outError(LogFilter.Spells, "ERROR in HandleBaseModValue(): non existed BaseModGroup of wrong BaseModType!"); + return; + } + + switch (modType) + { + case BaseModType.FlatMod: + m_auraBaseMod[(int)modGroup][(int)modType] += apply ? amount : -amount; + break; + case BaseModType.PCTmod: + MathFunctions.ApplyPercentModFloatVar(ref m_auraBaseMod[(int)modGroup][(int)modType], amount, apply); + break; + } + + if (!CanModifyStats()) + return; + + switch (modGroup) + { + case BaseModGroup.CritPercentage: + UpdateCritPercentage(WeaponAttackType.BaseAttack); + break; + case BaseModGroup.RangedCritPercentage: + UpdateCritPercentage(WeaponAttackType.RangedAttack); + break; + case BaseModGroup.OffhandCritPercentage: + UpdateCritPercentage(WeaponAttackType.OffAttack); + break; + default: + break; + } + } + + public void AddComboPoints(sbyte count, Spell spell = null) + { + if (count == 0) + return; + + sbyte comboPoints = spell != null ? spell.m_comboPointGain : (sbyte)GetPower(PowerType.ComboPoints); + + // without combo points lost (duration checked in aura) + RemoveAurasByType(AuraType.RetainComboPoints); + + comboPoints += (sbyte)count; + + if (comboPoints > 5) + comboPoints = 5; + else if (comboPoints < 0) + comboPoints = 0; + + if (spell == null) + SetPower(PowerType.ComboPoints, comboPoints); + else + spell.m_comboPointGain = comboPoints; + } + public void GainSpellComboPoints(sbyte count) + { + if (count == 0) + return; + + sbyte cp = (sbyte)GetPower(PowerType.ComboPoints); + cp += count; + + if (cp > 5) + cp = 5; + else if (cp < 0) + cp = 0; + + SetPower(PowerType.ComboPoints, cp); + } + public void ClearComboPoints() + { + // without combopoints lost (duration checked in aura) + RemoveAurasByType(AuraType.RetainComboPoints); + + SetPower(PowerType.ComboPoints, 0); + } + public byte GetComboPoints() { return (byte)GetPower(PowerType.ComboPoints); } + + public byte GetDrunkValue() { return GetByteValue(PlayerFields.Bytes3, PlayerFieldOffsets.Bytes3OffsetInebriation); } + public void SetDrunkValue(byte newDrunkValue, uint itemId = 0) + { + bool isSobering = newDrunkValue < GetDrunkValue(); + DrunkenState oldDrunkenState = GetDrunkenstateByValue(GetDrunkValue()); + if (newDrunkValue > 100) + newDrunkValue = 100; + + // select drunk percent or total SPELL_AURA_MOD_FAKE_INEBRIATE amount, whichever is higher for visibility updates + int drunkPercent = Math.Max(newDrunkValue, GetTotalAuraModifier(AuraType.ModFakeInebriate)); + if (drunkPercent != 0) + { + m_invisibilityDetect.AddFlag(InvisibilityType.Drunk); + m_invisibilityDetect.SetValue(InvisibilityType.Drunk, drunkPercent); + } + else if (!HasAuraType(AuraType.ModFakeInebriate) && newDrunkValue == 0) + m_invisibilityDetect.DelFlag(InvisibilityType.Drunk); + + DrunkenState newDrunkenState = GetDrunkenstateByValue(newDrunkValue); + SetByteValue(PlayerFields.Bytes3, PlayerFieldOffsets.Bytes3OffsetInebriation, newDrunkValue); + UpdateObjectVisibility(); + + if (!isSobering) + m_drunkTimer = 0; // reset sobering timer + + if (newDrunkenState == oldDrunkenState) + return; + + CrossedInebriationThreshold data = new CrossedInebriationThreshold(); + data.Guid = GetGUID(); + data.Threshold = (uint)newDrunkenState; + data.ItemID = itemId; + + SendMessageToSet(data, true); + } + public static DrunkenState GetDrunkenstateByValue(byte value) + { + if (value >= 90) + return DrunkenState.Smashed; + if (value >= 50) + return DrunkenState.Drunk; + if (value != 0) + return DrunkenState.Tipsy; + return DrunkenState.Sober; + } + + public uint GetDeathTimer() { return m_deathTimer; } + public bool ActivateTaxiPathTo(List nodes, Creature npc = null, uint spellid = 0, uint preferredMountDisplay = 0) + { + if (nodes.Count < 2) + { + GetSession().SendActivateTaxiReply(ActivateTaxiReply.NoSuchPath); + return false; + } + + // not let cheating with start flight in time of logout process || while in combat || has type state: stunned || has type state: root + if (GetSession().isLogingOut() || IsInCombat() || HasUnitState(UnitState.Stunned) || HasUnitState(UnitState.Root)) + { + GetSession().SendActivateTaxiReply(ActivateTaxiReply.PlayerBusy); + return false; + } + + if (HasFlag(UnitFields.Flags, UnitFlags.RemoveClientControl)) + return false; + + // taximaster case + if (npc != null) + { + // not let cheating with start flight mounted + if (IsMounted()) + { + GetSession().SendActivateTaxiReply(ActivateTaxiReply.PlayerAlreadyMounted); + return false; + } + + if (IsInDisallowedMountForm()) + { + GetSession().SendActivateTaxiReply(ActivateTaxiReply.PlayerShapeshifted); + return false; + } + + // not let cheating with start flight in time of logout process || if casting not finished || while in combat || if not use Spell's with EffectSendTaxi + if (IsNonMeleeSpellCast(false)) + { + GetSession().SendActivateTaxiReply(ActivateTaxiReply.PlayerBusy); + return false; + } + } + // cast case or scripted call case + else + { + RemoveAurasByType(AuraType.Mounted); + + if (IsInDisallowedMountForm()) + RemoveAurasByType(AuraType.ModShapeshift); + + Spell spell = GetCurrentSpell(CurrentSpellTypes.Generic); + if (spell != null) + if (spell.m_spellInfo.Id != spellid) + InterruptSpell(CurrentSpellTypes.Generic, false); + + InterruptSpell(CurrentSpellTypes.AutoRepeat, false); + + spell = GetCurrentSpell(CurrentSpellTypes.Channeled); + if (spell != null) + if (spell.m_spellInfo.Id != spellid) + InterruptSpell(CurrentSpellTypes.Channeled, true); + } + + uint sourcenode = nodes[0]; + + // starting node too far away (cheat?) + var node = CliDB.TaxiNodesStorage.LookupByKey(sourcenode); + if (node == null) + { + GetSession().SendActivateTaxiReply(ActivateTaxiReply.NoSuchPath); + return false; + } + + // check node starting pos data set case if provided + if (node.Pos.X != 0.0f || node.Pos.Y != 0.0f || node.Pos.Z != 0.0f) + { + if (node.MapID != GetMapId() || !IsInDist(node.Pos.X, node.Pos.Y, node.Pos.Z, 2 * SharedConst.InteractionDistance)) + { + GetSession().SendActivateTaxiReply(ActivateTaxiReply.TooFarAway); + return false; + } + } + // node must have pos if taxi master case (npc != NULL) + else if (npc != null) + { + GetSession().SendActivateTaxiReply(ActivateTaxiReply.UnspecifiedServerError); + return false; + } + + // Prepare to flight start now + + // stop combat at start taxi flight if any + CombatStop(); + + StopCastingCharm(); + StopCastingBindSight(); + ExitVehicle(); + + // stop trade (client cancel trade at taxi map open but cheating tools can be used for reopen it) + TradeCancel(true); + + // clean not finished taxi path if any + m_taxi.ClearTaxiDestinations(); + + // 0 element current node + m_taxi.AddTaxiDestination(sourcenode); + + // fill destinations path tail + uint sourcepath = 0; + uint totalcost = 0; + uint firstcost = 0; + + uint prevnode = sourcenode; + uint lastnode = 0; + + for (int i = 1; i < nodes.Count; ++i) + { + uint path, cost; + + lastnode = nodes[i]; + Global.ObjectMgr.GetTaxiPath(prevnode, lastnode, out path, out cost); + + if (path == 0) + { + m_taxi.ClearTaxiDestinations(); + return false; + } + + totalcost += cost; + if (i == 1) + firstcost = cost; + + if (prevnode == sourcenode) + sourcepath = path; + + m_taxi.AddTaxiDestination(lastnode); + + prevnode = lastnode; + } + + // get mount model (in case non taximaster (npc == NULL) allow more wide lookup) + // + // Hack-Fix for Alliance not being able to use Acherus taxi. There is + // only one mount ID for both sides. Probably not good to use 315 in case DBC nodes + // change but I couldn't find a suitable alternative. OK to use class because only DK + // can use this taxi. + uint mount_display_id; + if (node.Flags.HasAnyFlag(TaxiNodeFlags.UseFavoriteMount) && preferredMountDisplay != 0) + mount_display_id = preferredMountDisplay; + else + mount_display_id = Global.ObjectMgr.GetTaxiMountDisplayId(sourcenode, GetTeam(), npc == null || (sourcenode == 315 && GetClass() == Class.Deathknight)); + + // in spell case allow 0 model + if ((mount_display_id == 0 && spellid == 0) || sourcepath == 0) + { + GetSession().SendActivateTaxiReply(ActivateTaxiReply.UnspecifiedServerError); + m_taxi.ClearTaxiDestinations(); + return false; + } + + ulong money = GetMoney(); + if (npc != null) + totalcost = (uint)Math.Ceiling(totalcost * GetReputationPriceDiscount(npc)); + + if (money < totalcost) + { + GetSession().SendActivateTaxiReply(ActivateTaxiReply.NotEnoughMoney); + m_taxi.ClearTaxiDestinations(); + return false; + } + + //Checks and preparations done, DO FLIGHT + UpdateCriteria(CriteriaTypes.FlightPathsTaken, 1); + + if (WorldConfig.GetBoolValue(WorldCfg.InstantTaxi)) + { + var lastPathNode = CliDB.TaxiNodesStorage.LookupByKey(nodes[nodes.Count - 1]); + m_taxi.ClearTaxiDestinations(); + ModifyMoney(-totalcost); + UpdateCriteria(CriteriaTypes.GoldSpentForTravelling, totalcost); + TeleportTo(lastPathNode.MapID, lastPathNode.Pos.X, lastPathNode.Pos.Y, lastPathNode.Pos.Z, GetOrientation()); + return false; + } + else + { + ModifyMoney(-firstcost); + UpdateCriteria(CriteriaTypes.GoldSpentForTravelling, firstcost); + GetSession().SendActivateTaxiReply(); + GetSession().SendDoFlight(mount_display_id, sourcepath); + } + return true; + } + + public bool ActivateTaxiPathTo(uint taxi_path_id, uint spellid = 0) + { + var entry = CliDB.TaxiPathStorage.LookupByKey(taxi_path_id); + if (entry == null) + return false; + + List nodes = new List(); + + nodes.Add(entry.From); + nodes.Add(entry.To); + + return ActivateTaxiPathTo(nodes, null, spellid); + } + + public void CleanupAfterTaxiFlight() + { + m_taxi.ClearTaxiDestinations(); // not destinations, clear source node + Dismount(); + RemoveFlag(UnitFields.Flags, UnitFlags.RemoveClientControl | UnitFlags.TaxiFlight); + getHostileRefManager().setOnlineOfflineState(true); + } + + public void ContinueTaxiFlight() + { + uint sourceNode = m_taxi.GetTaxiSource(); + if (sourceNode == 0) + return; + + Log.outDebug(LogFilter.Unit, "WORLD: Restart character {0} taxi flight", GetGUID().ToString()); + + uint mountDisplayId = Global.ObjectMgr.GetTaxiMountDisplayId(sourceNode, GetTeam(), true); + if (mountDisplayId == 0) + return; + + uint path = m_taxi.GetCurrentTaxiPath(); + + // search appropriate start path node + uint startNode = 0; + + var nodeList = CliDB.TaxiPathNodesByPath[path]; + + float distPrev = MapConst.MapSize * MapConst.MapSize; + float distNext = GetExactDistSq(nodeList[0].Loc.X, nodeList[0].Loc.Y, nodeList[0].Loc.Z); + + for (int i = 1; i < nodeList.Length; ++i) + { + var node = nodeList[i]; + var prevNode = nodeList[i - 1]; + + // skip nodes at another map + if (node.MapID != GetMapId()) + continue; + + distPrev = distNext; + + distNext = GetExactDistSq(node.Loc.X, node.Loc.Y, node.Loc.Z); + + float distNodes = + (node.Loc.X - prevNode.Loc.X) * (node.Loc.X - prevNode.Loc.X) + + (node.Loc.Y - prevNode.Loc.Y) * (node.Loc.Y - prevNode.Loc.Y) + + (node.Loc.Z - prevNode.Loc.Z) * (node.Loc.Z - prevNode.Loc.Z); + + if (distNext + distPrev < distNodes) + { + startNode = (uint)i; + break; + } + } + + GetSession().SendDoFlight(mountDisplayId, path, startNode); + } + + public byte GetGrantableLevels() { return m_grantableLevels; } + public void SetGrantableLevels(int val) { m_grantableLevels = (byte)val; } + public bool GetsRecruitAFriendBonus(bool forXP) + { + bool recruitAFriend = false; + if (getLevel() <= WorldConfig.GetIntValue(WorldCfg.MaxRecruitAFriendBonusPlayerLevel) || !forXP) + { + Group group = GetGroup(); + if (group) + { + for (GroupReference refe = group.GetFirstMember(); refe != null; refe = refe.next()) + { + Player player = refe.GetSource(); + if (!player) + continue; + + if (!player.IsAtRecruitAFriendDistance(this)) + continue; // member (alive or dead) or his corpse at req. distance + + if (forXP) + { + // level must be allowed to get RaF bonus + if (player.getLevel() > WorldConfig.GetIntValue(WorldCfg.MaxRecruitAFriendBonusPlayerLevel)) + continue; + + // level difference must be small enough to get RaF bonus, UNLESS we are lower level + if (player.getLevel() < getLevel()) + if (getLevel() - player.getLevel() > WorldConfig.GetIntValue(WorldCfg.MaxRecruitAFriendBonusPlayerLevelDifference)) + continue; + } + + bool ARecruitedB = (player.GetSession().GetRecruiterId() == GetSession().GetAccountId()); + bool BRecruitedA = (GetSession().GetRecruiterId() == player.GetSession().GetAccountId()); + if (ARecruitedB || BRecruitedA) + { + recruitAFriend = true; + break; + } + } + } + } + return recruitAFriend; + } + + bool IsAtRecruitAFriendDistance(WorldObject pOther) + { + if (!pOther) + return false; + WorldObject player = GetCorpse(); + if (!player || IsAlive()) + player = this; + + if (player.GetMapId() != pOther.GetMapId() || player.GetInstanceId() != pOther.GetInstanceId()) + return false; + + return pOther.GetDistance(player) <= WorldConfig.GetFloatValue(WorldCfg.MaxRecruitAFriendDistance); + } + + public bool IsBeingTeleported() { return mSemaphoreTeleport_Near || mSemaphoreTeleport_Far; } + public bool IsBeingTeleportedNear() { return mSemaphoreTeleport_Near; } + public bool IsBeingTeleportedFar() { return mSemaphoreTeleport_Far; } + public bool IsBeingTeleportedSeamlessly() { return IsBeingTeleportedFar() && m_teleport_options.HasAnyFlag(TeleportToOptions.Seamless); } + public void SetSemaphoreTeleportNear(bool semphsetting) { mSemaphoreTeleport_Near = semphsetting; } + public void SetSemaphoreTeleportFar(bool semphsetting) { mSemaphoreTeleport_Far = semphsetting; } + + //new + public uint DoRandomRoll(uint minimum, uint maximum) + { + Contract.Assert(maximum <= 10000); + + uint roll = RandomHelper.URand(minimum, maximum); + + RandomRoll randomRoll = new RandomRoll(); + randomRoll.Min = (int)minimum; + randomRoll.Max = (int)maximum; + randomRoll.Result = (int)roll; + randomRoll.Roller = GetGUID(); + randomRoll.RollerWowAccount = GetSession().GetAccountGUID(); + + Group group = GetGroup(); + if (group) + group.BroadcastPacket(randomRoll, false); + else + SendPacket(randomRoll); + + return roll; + } + + public bool IsVisibleGloballyFor(Player u) + { + if (u == null) + return false; + + // Always can see self + if (u.GetGUID() == GetGUID()) + return true; + + // Visible units, always are visible for all players + if (IsVisible()) + return true; + + // GMs are visible for higher gms (or players are visible for gms) + if (!Global.AccountMgr.IsPlayerAccount(u.GetSession().GetSecurity())) + return GetSession().GetSecurity() <= u.GetSession().GetSecurity(); + + // non faction visibility non-breakable for non-GMs + return false; + } + + public float GetReputationPriceDiscount(Creature creature) + { + var vendor_faction = creature.GetFactionTemplateEntry(); + if (vendor_faction == null || vendor_faction.Faction == 0) + return 1.0f; + + ReputationRank rank = GetReputationRank(vendor_faction.Faction); + if (rank <= ReputationRank.Neutral) + return 1.0f; + + return 1.0f - 0.05f * (rank - ReputationRank.Neutral); + } + public bool IsSpellFitByClassAndRace(uint spell_id) + { + uint racemask = getRaceMask(); + uint classmask = getClassMask(); + + var bounds = Global.SpellMgr.GetSkillLineAbilityMapBounds(spell_id); + + if (bounds.Empty()) + return true; + + foreach (var _spell_idx in bounds) + { + // skip wrong race skills + if (_spell_idx.RaceMask != 0 && (_spell_idx.RaceMask & racemask) == 0) + continue; + + // skip wrong class skills + if (_spell_idx.ClassMask != 0 && (_spell_idx.ClassMask & classmask) == 0) + continue; + + return true; + } + + return false; + } + + //New shit + void InitPrimaryProfessions() + { + SetFreePrimaryProfessions(WorldConfig.GetUIntValue(WorldCfg.MaxPrimaryTradeSkill)); + } + public uint GetFreePrimaryProfessionPoints() { return GetUInt32Value(PlayerFields.CharacterPoints); } + void SetFreePrimaryProfessions(ushort profs) { SetUInt32Value(PlayerFields.CharacterPoints, profs); } + public bool HaveAtClient(WorldObject u) + { + bool one = u.GetGUID() == GetGUID(); + bool two = m_clientGUIDs.Contains(u.GetGUID()); + + return one || two; + } + public bool HasTitle(CharTitlesRecord title) { return HasTitle(title.MaskID); } + public bool HasTitle(uint bitIndex) + { + if (bitIndex > PlayerConst.MaxTitleIndex) + return false; + + int fieldIndexOffset = (int)bitIndex / 32; + uint flag = (uint)(1 << ((int)bitIndex % 32)); + return HasFlag(PlayerFields.KnownTitles + fieldIndexOffset, flag); + } + public void SetTitle(CharTitlesRecord title, bool lost = false) + { + int fieldIndexOffset = (int)title.MaskID / 32; + uint flag = (uint)(1 << (int)(title.MaskID % 32)); + + if (lost) + { + if (!HasFlag(PlayerFields.KnownTitles + fieldIndexOffset, flag)) + return; + + RemoveFlag(PlayerFields.KnownTitles + fieldIndexOffset, flag); + } + else + { + if (HasFlag(PlayerFields.KnownTitles + fieldIndexOffset, flag)) + return; + + SetFlag(PlayerFields.KnownTitles + fieldIndexOffset, flag); + } + + TitleEarned packet = new TitleEarned(lost ? ServerOpcodes.TitleLost : ServerOpcodes.TitleEarned); + packet.Index = title.MaskID; + SendPacket(packet); + } + + public void SetViewpoint(WorldObject target, bool apply) + { + if (apply) + { + Log.outDebug(LogFilter.Maps, "Player.CreateViewpoint: Player {0} create seer {1} (TypeId: {2}).", GetName(), target.GetEntry(), target.GetTypeId()); + + if (!AddGuidValue(PlayerFields.Farsight, target.GetGUID())) + { + Log.outFatal(LogFilter.Player, "Player.CreateViewpoint: Player {0} cannot add new viewpoint!", GetName()); + return; + + } + + // farsight dynobj or puppet may be very far away + UpdateVisibilityOf(target); + + if (target.isTypeMask(TypeMask.Unit) && target != GetVehicleBase()) + target.ToUnit().AddPlayerToVision(this); + SetSeer(target); + } + else + { + Log.outDebug(LogFilter.Maps, "Player.CreateViewpoint: Player {0} remove seer", GetName()); + + if (!RemoveGuidValue(PlayerFields.Farsight, target.GetGUID())) + { + Log.outFatal(LogFilter.Player, "Player.CreateViewpoint: Player {0} cannot remove current viewpoint!", GetName()); + return; + } + + if (target.isTypeMask(TypeMask.Unit) && target != GetVehicleBase()) + target.ToUnit().RemovePlayerFromVision(this); + + //must immediately set seer back otherwise may crash + SetSeer(this); + } + } + public WorldObject GetViewpoint() + { + ObjectGuid guid = GetGuidValue(PlayerFields.Farsight); + if (!guid.IsEmpty()) + return Global.ObjAccessor.GetObjectByTypeMask(this, guid, TypeMask.Seer); + return null; + } + + public void SetClientControl(Unit target, bool allowMove) + { + ControlUpdate packet = new ControlUpdate(); + packet.Guid = target.GetGUID(); + packet.On = allowMove; + SendPacket(packet); + + if (this != target) + SetViewpoint(target, allowMove); + + if (allowMove) + SetMover(target); + } + public void SetMover(Unit target) + { + m_unitMovedByMe.m_playerMovingMe = null; + m_unitMovedByMe = target; + m_unitMovedByMe.m_playerMovingMe = this; + + MoveSetActiveMover packet = new MoveSetActiveMover(); + packet.MoverGUID = target.GetGUID(); + SendPacket(packet); + } + + public Item GetWeaponForAttack(WeaponAttackType attackType, bool useable = false) + { + byte slot; + switch (attackType) + { + case WeaponAttackType.BaseAttack: + slot = EquipmentSlot.MainHand; + break; + case WeaponAttackType.OffAttack: + slot = EquipmentSlot.OffHand; + break; + case WeaponAttackType.RangedAttack: + slot = EquipmentSlot.MainHand; + break; + default: + return null; + } + + Item item = null; + if (useable) + item = GetUseableItemByPos(InventorySlots.Bag0, slot); + else + item = GetItemByPos(InventorySlots.Bag0, slot); + if (item == null || item.GetTemplate().GetClass() != ItemClass.Weapon) + return null; + + if (!useable) + return item; + + if (item.IsBroken()) + return null; + + return item; + } + public static WeaponAttackType GetAttackBySlot(byte slot, InventoryType inventoryType) + { + switch (slot) + { + case EquipmentSlot.MainHand: + return inventoryType != InventoryType.Ranged && inventoryType != InventoryType.RangedRight ? WeaponAttackType.BaseAttack : WeaponAttackType.RangedAttack; + case EquipmentSlot.OffHand: + return WeaponAttackType.OffAttack; + default: + return WeaponAttackType.Max; + } + } + public void AutoUnequipOffhandIfNeed(bool force = false) + { + Item offItem = GetItemByPos(InventorySlots.Bag0, EquipmentSlot.OffHand); + if (offItem == null) + return; + + ItemTemplate offtemplate = offItem.GetTemplate(); + + // unequip offhand weapon if player doesn't have dual wield anymore + if (!CanDualWield() && ((offItem.GetTemplate().GetInventoryType() == InventoryType.WeaponOffhand && !offItem.GetTemplate().GetFlags3().HasAnyFlag(ItemFlags3.AlwaysAllowDualWield)) + || offItem.GetTemplate().GetInventoryType() == InventoryType.Weapon)) + force = true; + + // need unequip offhand for 2h-weapon without TitanGrip (in any from hands) + if (!force && (CanTitanGrip() || (offtemplate.GetInventoryType() != InventoryType.Weapon2Hand && !IsTwoHandUsed()))) + return; + + List off_dest = new List(); + InventoryResult off_msg = CanStoreItem(ItemConst.NullBag, ItemConst.NullSlot, off_dest, offItem, false); + if (off_msg == InventoryResult.Ok) + { + RemoveItem(InventorySlots.Bag0, EquipmentSlot.OffHand, true); + StoreItem(off_dest, offItem, true); + } + else + { + MoveItemFromInventory(InventorySlots.Bag0, EquipmentSlot.OffHand, true); + SQLTransaction trans = new SQLTransaction(); + offItem.DeleteFromInventoryDB(trans); // deletes item from character's inventory + offItem.SaveToDB(trans); // recursive and not have transaction guard into self, item not in inventory and can be save standalone + + string subject = Global.ObjectMgr.GetCypherString(CypherStrings.NotEquippedItem); + new MailDraft(subject, "There were problems with equipping one or several items").AddItem(offItem).SendMailTo(trans, this, new MailSender(this, MailStationery.Gm), MailCheckMask.Copied); + + DB.Characters.CommitTransaction(trans); + } + } + + public WorldLocation GetTeleportDest() + { + return teleportDest; + } + public WorldLocation GetHomebind() + { + return homebind; + } + public WorldLocation GetRecall() + { + return m_recall_location; + } + + public bool CanTameExoticPets() { return IsGameMaster() || HasAuraType(AuraType.AllowTamePetType); } + + public bool CanFlyInZone(uint mapid, uint zone) + { + // continent checked in SpellInfo.CheckLocation at cast and area update + uint v_map = Global.DB2Mgr.GetVirtualMapForMapAndZone(mapid, zone); + return v_map != 571 || HasSpell(54197); // 54197 = Cold Weather Flying + } + + void SendAttackSwingDeadTarget() { SendPacket(new AttackSwingError(AttackSwingErr.DeadTarget)); } + void SendAttackSwingCantAttack() { SendPacket(new AttackSwingError(AttackSwingErr.CantAttack)); } + public void SendAttackSwingNotInRange() { SendPacket(new AttackSwingError(AttackSwingErr.NotInRange)); } + void SendAttackSwingBadFacingAttack() { SendPacket(new AttackSwingError(AttackSwingErr.BadFacing)); } + public void SendAttackSwingCancelAttack() { SendPacket(new CancelCombat()); } + public void SendAutoRepeatCancel(Unit target) + { + CancelAutoRepeat cancelAutoRepeat = new CancelAutoRepeat(); + cancelAutoRepeat.Guid = target.GetGUID(); // may be it's target guid + SendMessageToSet(cancelAutoRepeat, false); + } + + public void SendUpdatePhasing() + { + if (!IsInWorld) + return; + + RebuildTerrainSwaps(); // to set default map swaps + + GetSession().SendSetPhaseShift(GetPhases(), GetTerrainSwaps(), GetWorldMapAreaSwaps()); + } + + public override void BuildCreateUpdateBlockForPlayer(UpdateData data, Player target) + { + if (target == this) + { + for (byte i = 0; i < EquipmentSlot.End; ++i) + { + if (m_items[i] == null) + continue; + + m_items[i].BuildCreateUpdateBlockForPlayer(data, target); + } + + for (byte i = InventorySlots.BagStart; i < InventorySlots.BankBagEnd; ++i) + { + if (m_items[i] == null) + continue; + + m_items[i].BuildCreateUpdateBlockForPlayer(data, target); + } + + for (byte i = InventorySlots.ReagentStart; i < InventorySlots.ReagentEnd; ++i) + { + if (m_items[i] == null) + continue; + + m_items[i].BuildCreateUpdateBlockForPlayer(data, target); + } + + for (byte i = InventorySlots.ChildEquipmentStart; i < InventorySlots.ChildEquipmentEnd; ++i) + { + if (m_items[i] == null) + continue; + + m_items[i].BuildCreateUpdateBlockForPlayer(data, target); + } + } + + base.BuildCreateUpdateBlockForPlayer(data, target); + } + + //Helpers + public void ADD_GOSSIP_ITEM(GossipOptionIcon icon, string message, uint sender, uint action) { PlayerTalkClass.GetGossipMenu().AddMenuItem(-1, icon, message, sender, action, "", 0); } + public void ADD_GOSSIP_ITEM_DB(uint menuId, uint menuItemId, uint sender, uint action) { PlayerTalkClass.GetGossipMenu().AddMenuItem(menuId, menuItemId, sender, action); } + public void ADD_GOSSIP_ITEM_EXTENDED(GossipOptionIcon icon, string message, uint sender, uint action, string boxmessage, uint boxmoney, bool coded) { PlayerTalkClass.GetGossipMenu().AddMenuItem(-1, icon, message, sender, action, boxmessage, boxmoney, coded); } + + // This fuction Sends the current menu to show to client, a - NPCTEXTID(uint32), b - npc guid(uint64) + public void SEND_GOSSIP_MENU(uint titleId, ObjectGuid objGUID) { PlayerTalkClass.SendGossipMenu(titleId, objGUID); } + + // Closes the Menu + public void CLOSE_GOSSIP_MENU() { PlayerTalkClass.SendCloseGossip(); } + } +} diff --git a/Game/Entities/Player/PlayerTaxi.cs b/Game/Entities/Player/PlayerTaxi.cs new file mode 100644 index 000000000..936e4ec77 --- /dev/null +++ b/Game/Entities/Player/PlayerTaxi.cs @@ -0,0 +1,244 @@ +/* + * Copyright (C) 2012-2017 CypherCore + * + * 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, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +using Framework.Collections; +using Framework.Constants; +using Game.DataStorage; +using Game.Network.Packets; +using System.Collections.Generic; +using System.Text; + +namespace Game.Entities +{ + public class PlayerTaxi + { + public void InitTaxiNodesForLevel(Race race, Class chrClass, uint level) + { + // class specific initial known nodes + var factionMask = Player.TeamForRace(race) == Team.Horde ? CliDB.HordeTaxiNodesMask : CliDB.AllianceTaxiNodesMask; + switch (chrClass) + { + case Class.Deathknight: + { + for (byte i = 0; i < PlayerConst.TaxiMaskSize; ++i) + m_taximask[i] |= (byte)(CliDB.OldContinentsNodesMask[i] & factionMask[i]); + break; + } + } + + // race specific initial known nodes: capital and taxi hub masks + switch (race) + { + case Race.Human: + case Race.Dwarf: + case Race.NightElf: + case Race.Gnome: + case Race.Draenei: + case Race.Worgen: + case Race.PandarenAlliance: + SetTaximaskNode(2); // Stormwind, Elwynn + SetTaximaskNode(6); // Ironforge, Dun Morogh + SetTaximaskNode(26); // Lor'danel, Darkshore + SetTaximaskNode(27); // Rut'theran Village, Teldrassil + SetTaximaskNode(49); // Moonglade (Alliance) + SetTaximaskNode(94); // The Exodar + SetTaximaskNode(456); // Dolanaar, Teldrassil + SetTaximaskNode(457); // Darnassus, Teldrassil + SetTaximaskNode(582); // Goldshire, Elwynn + SetTaximaskNode(589); // Eastvale Logging Camp, Elwynn + SetTaximaskNode(619); // Kharanos, Dun Morogh + SetTaximaskNode(620); // Gol'Bolar Quarry, Dun Morogh + SetTaximaskNode(624); // Azure Watch, Azuremyst Isle + break; + case Race.Orc: + case Race.Undead: + case Race.Tauren: + case Race.Troll: + case Race.BloodElf: + case Race.Goblin: + case Race.PandarenHorde: + SetTaximaskNode(11); // Undercity, Tirisfal + SetTaximaskNode(22); // Thunder Bluff, Mulgore + SetTaximaskNode(23); // Orgrimmar, Durotar + SetTaximaskNode(69); // Moonglade (Horde) + SetTaximaskNode(82); // Silvermoon City + SetTaximaskNode(384); // The Bulwark, Tirisfal + SetTaximaskNode(402); // Bloodhoof Village, Mulgore + SetTaximaskNode(460); // Brill, Tirisfal Glades + SetTaximaskNode(536); // Sen'jin Village, Durotar + SetTaximaskNode(537); // Razor Hill, Durotar + SetTaximaskNode(625); // Fairbreeze Village, Eversong Woods + SetTaximaskNode(631); // Falconwing Square, Eversong Woods + break; + } + + // new continent starting masks (It will be accessible only at new map) + switch (Player.TeamForRace(race)) + { + case Team.Alliance: + SetTaximaskNode(100); + break; + case Team.Horde: + SetTaximaskNode(99); + break; + } + // level dependent taxi hubs + if (level >= 68) + SetTaximaskNode(213); //Shattered Sun Staging Area + } + + public void LoadTaxiMask(string data) + { + var split = new StringArray(data, ' '); + + byte index = 0; + for (var i = 0; index < PlayerConst.TaxiMaskSize && i != split.Length; ++i, ++index) + { + // load and set bits only for existing taxi nodes + m_taximask[index] = (byte)(CliDB.TaxiNodesMask[index] & uint.Parse(split[i])); + } + } + + public void AppendTaximaskTo(ShowTaxiNodes data, bool all) + { + if (all) + data.Nodes = CliDB.TaxiNodesMask; // all existed nodes + else + data.Nodes = m_taximask; // known nodes + } + + public bool LoadTaxiDestinationsFromString(string values, Team team) + { + ClearTaxiDestinations(); + + var split = new StringArray(values, ' '); + for (var i = 0; i < split.Length; ++i) + { + uint node = uint.Parse(split[i]); + AddTaxiDestination(node); + } + + if (m_TaxiDestinations.Empty()) + return true; + + // Check integrity + if (m_TaxiDestinations.Count < 2) + return false; + + for (int i = 1; i < m_TaxiDestinations.Count; ++i) + { + uint cost; + uint path; + Global.ObjectMgr.GetTaxiPath(m_TaxiDestinations[i - 1], m_TaxiDestinations[i], out path, out cost); + if (path == 0) + return false; + } + + // can't load taxi path without mount set (quest taxi path?) + if (Global.ObjectMgr.GetTaxiMountDisplayId(GetTaxiSource(), team, true) == 0) + return false; + + return true; + } + + public string SaveTaxiDestinationsToString() + { + if (m_TaxiDestinations.Empty()) + return ""; + + StringBuilder ss = new StringBuilder(); + + for (int i = 0; i < m_TaxiDestinations.Count; ++i) + ss.AppendFormat("{0} ", m_TaxiDestinations[i]); + + return ss.ToString(); + } + + public uint GetCurrentTaxiPath() + { + if (m_TaxiDestinations.Count < 2) + return 0; + + uint path; + uint cost; + + Global.ObjectMgr.GetTaxiPath(m_TaxiDestinations[0], m_TaxiDestinations[1], out path, out cost); + + return path; + } + + public bool RequestEarlyLanding() + { + if (m_TaxiDestinations.Count <= 2) + return false; + + // start from first destination - m_TaxiDestinations[0] is the current starting node + for (var i = 1; i < m_TaxiDestinations.Count; ++i) + { + if (IsTaximaskNodeKnown(m_TaxiDestinations[i])) + { + if (++i == m_TaxiDestinations.Count - 1) + return false; // if we are left with only 1 known node on the path don't change the spline, its our final destination anyway + + m_TaxiDestinations.RemoveRange(i, m_TaxiDestinations.Count - 1); + return true; + } + } + + return false; + } + + public bool IsTaximaskNodeKnown(uint nodeidx) + { + byte field = (byte)((nodeidx - 1) / 8); + uint submask = (uint)(1 << (int)((nodeidx - 1) % 8)); + return (m_taximask[field] & submask) == submask; + } + public bool SetTaximaskNode(uint nodeidx) + { + byte field = (byte)((nodeidx - 1) / 8); + uint submask = (uint)(1 << (int)((nodeidx - 1) % 8)); + if ((m_taximask[field] & submask) != submask) + { + m_taximask[field] |= (byte)submask; + return true; + } + else + return false; + } + + public void ClearTaxiDestinations() { m_TaxiDestinations.Clear(); } + public void AddTaxiDestination(uint dest) { m_TaxiDestinations.Add(dest); } + void SetTaxiDestination(List nodes) + { + m_TaxiDestinations.Clear(); + m_TaxiDestinations.AddRange(nodes); + } + public uint GetTaxiSource() { return m_TaxiDestinations.Empty() ? 0 : m_TaxiDestinations[0]; } + public uint GetTaxiDestination() { return m_TaxiDestinations.Count < 2 ? 0 : m_TaxiDestinations[1]; } + public uint NextTaxiDestination() + { + m_TaxiDestinations.RemoveAt(0); + return GetTaxiDestination(); + } + public List GetPath() { return m_TaxiDestinations; } + public bool empty() { return m_TaxiDestinations.Empty(); } + + public byte[] m_taximask = new byte[PlayerConst.TaxiMaskSize]; + List m_TaxiDestinations = new List(); + } +} diff --git a/Game/Entities/Player/RestMgr.cs b/Game/Entities/Player/RestMgr.cs new file mode 100644 index 000000000..ba40cdb0a --- /dev/null +++ b/Game/Entities/Player/RestMgr.cs @@ -0,0 +1,170 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using Framework.Constants; + +namespace Game.Entities +{ + public class RestMgr + { + public RestMgr(Player player) + { + _player = player; + } + + public void SetRestBonus(RestTypes restType, float restBonus) + { + byte rest_rested_offset; + byte rest_state_offset; + PlayerFields next_level_xp_field; + bool affectedByRaF = false; + + switch (restType) + { + case RestTypes.XP: + // Reset restBonus (XP only) for max level players + if (_player.getLevel() >= WorldConfig.GetIntValue(WorldCfg.MaxPlayerLevel)) + restBonus = 0; + + rest_rested_offset = PlayerFieldOffsets.RestRestedXp; + rest_state_offset = PlayerFieldOffsets.RestStateXp; + next_level_xp_field = PlayerFields.NextLevelXp; + affectedByRaF = true; + break; + case RestTypes.Honor: + // Reset restBonus (Honor only) for players with max honor level. + if (_player.IsMaxHonorLevelAndPrestige()) + restBonus = 0; + + rest_rested_offset = PlayerFieldOffsets.RestRestedHonor; + rest_state_offset = PlayerFieldOffsets.RestStateHonor; + next_level_xp_field = PlayerFields.HonorNextLevel; + break; + default: + return; + } + + if (restBonus < 0) + restBonus = 0; + + float rest_bonus_max = (float)(_player.GetUInt32Value(next_level_xp_field)) * 1.5f / 2; + + if (restBonus > rest_bonus_max) + _restBonus[(int)restType] = rest_bonus_max; + else + _restBonus[(int)restType] = restBonus; + + // update data for client + if (affectedByRaF && _player.GetsRecruitAFriendBonus(true) && (_player.GetSession().IsARecruiter() || _player.GetSession().GetRecruiterId() != 0)) + _player.SetUInt32Value(PlayerFields.RestInfo + rest_state_offset, (uint)PlayerRestState.RAFLinked); + else + { + if (_restBonus[(int)restType] > 10) + _player.SetUInt32Value(PlayerFields.RestInfo + rest_state_offset, (uint)PlayerRestState.Rested); + else if (_restBonus[(int)restType] <= 1) + _player.SetUInt32Value(PlayerFields.RestInfo + rest_state_offset, (uint)PlayerRestState.NotRAFLinked); + } + + // RestTickUpdate + _player.SetUInt32Value(PlayerFields.RestInfo + rest_rested_offset, (uint)_restBonus[(int)restType]); + } + + public void AddRestBonus(RestTypes restType, float restBonus) + { + // Don't add extra rest bonus to max level players. Note: Might need different condition in next expansion for honor XP (PLAYER_LEVEL_MIN_HONOR perhaps). + if (_player.getLevel() >= WorldConfig.GetIntValue(WorldCfg.MaxPlayerLevel)) + restBonus = 0; + + float totalRestBonus = GetRestBonus(restType) + restBonus; + SetRestBonus(restType, totalRestBonus); + } + + public void SetRestFlag(RestFlag restFlag, uint triggerId = 0) + { + RestFlag oldRestMask = _restFlagMask; + _restFlagMask |= restFlag; + + if (oldRestMask == 0 && _restFlagMask != 0) // only set flag/time on the first rest state + { + _restTime = Time.UnixTime; + _player.SetFlag(PlayerFields.Flags, PlayerFlags.Resting); + } + + if (triggerId != 0) + _innAreaTriggerId = triggerId; + } + + public void RemoveRestFlag(RestFlag restFlag) + { + RestFlag oldRestMask = _restFlagMask; + _restFlagMask &= ~restFlag; + + if (oldRestMask != 0 && _restFlagMask == 0) // only remove flag/time on the last rest state remove + { + _restTime = 0; + _player.RemoveFlag(PlayerFields.Flags, PlayerFlags.Resting); + } + } + + public uint GetRestBonusFor(RestTypes restType, uint xp) + { + uint rested_bonus = (uint)GetRestBonus(restType); // xp for each rested bonus + + if (rested_bonus > xp) // max rested_bonus == xp or (r+x) = 200% xp + rested_bonus = xp; + + SetRestBonus(restType, GetRestBonus(restType) - rested_bonus); + + Log.outDebug(LogFilter.Player, "RestMgr.GetRestBonus: Player '{0}' ({1}) gain {2} xp (+{3} Rested Bonus). Rested points={4}", + _player.GetGUID().ToString(), _player.GetName(), xp + rested_bonus, rested_bonus, GetRestBonus(restType)); + return rested_bonus; + } + + public void Update(uint now) + { + if (RandomHelper.randChance(3) && _restTime > 0) // freeze update + { + long timeDiff = now - _restTime; + if (timeDiff >= 10) + { + _restTime = now; + + float bubble = 0.125f * WorldConfig.GetFloatValue(WorldCfg.RateRestIngame); + AddRestBonus(RestTypes.XP, timeDiff * CalcExtraPerSec(RestTypes.XP, bubble)); + } + } + } + + public void LoadRestBonus(RestTypes restType, PlayerRestState state, float restBonus) + { + _restBonus[(int)restType] = restBonus; + _player.SetUInt32Value(PlayerFields.RestInfo + (int)restType * 2, (uint)state); + _player.SetUInt32Value(PlayerFields.RestInfo + (int)restType * 2 + 1, (uint)restBonus); + } + + public float CalcExtraPerSec(RestTypes restType, float bubble) + { + switch (restType) + { + case RestTypes.Honor: + return (_player.GetUInt32Value(PlayerFields.HonorNextLevel)) / 72000.0f * bubble; + case RestTypes.XP: + return (_player.GetUInt32Value(PlayerFields.NextLevelXp)) / 72000.0f * bubble; + default: + return 0.0f; + } + } + + public float GetRestBonus(RestTypes restType) { return _restBonus[(int)restType]; } + public bool HasRestFlag(RestFlag restFlag) { return (_restFlagMask & restFlag) != 0; } + public uint GetInnTriggerId() { return _innAreaTriggerId; } + + Player _player; + long _restTime; + uint _innAreaTriggerId; + float[] _restBonus = new float[(int)RestTypes.Max]; + RestFlag _restFlagMask; + } +} diff --git a/Game/Entities/Player/SceneMgr.cs b/Game/Entities/Player/SceneMgr.cs new file mode 100644 index 000000000..0873a4c78 --- /dev/null +++ b/Game/Entities/Player/SceneMgr.cs @@ -0,0 +1,246 @@ +/* + * Copyright (C) 2012-2017 CypherCore + * + * 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, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +using Framework.Constants; +using Game.DataStorage; +using Game.Network.Packets; +using System; +using System.Collections.Generic; + +namespace Game.Entities +{ + public class SceneMgr + { + public SceneMgr(Player player) + { + _player = player; + _standaloneSceneInstanceID = 0; + _isDebuggingScenes = false; + } + + public uint PlayScene(uint sceneId, Position position = null) + { + SceneTemplate sceneTemplate = Global.ObjectMgr.GetSceneTemplate(sceneId); + return PlaySceneByTemplate(sceneTemplate, position); + } + + uint PlaySceneByTemplate(SceneTemplate sceneTemplate, Position position = null) + { + if (sceneTemplate == null) + return 0; + + SceneScriptPackageRecord entry = CliDB.SceneScriptPackageStorage.LookupByKey(sceneTemplate.ScenePackageId); + if (entry == null) + return 0; + + // By default, take player position + if (position == null) + position = GetPlayer(); + + uint sceneInstanceID = GetNewStandaloneSceneInstanceID(); + + if (_isDebuggingScenes) + GetPlayer().SendSysMessage(CypherStrings.CommandSceneDebugPlay, sceneInstanceID, sceneTemplate.ScenePackageId, sceneTemplate.PlaybackFlags); + + PlayScene playScene = new PlayScene(); + playScene.SceneID = sceneTemplate.SceneId; + playScene.PlaybackFlags = (uint)sceneTemplate.PlaybackFlags; + playScene.SceneInstanceID = sceneInstanceID; + playScene.SceneScriptPackageID = sceneTemplate.ScenePackageId; + playScene.Location = position; + playScene.TransportGUID = GetPlayer().GetTransGUID(); + + GetPlayer().SendPacket(playScene, true); + + AddInstanceIdToSceneMap(sceneInstanceID, sceneTemplate); + + Global.ScriptMgr.OnSceneStart(GetPlayer(), sceneInstanceID, sceneTemplate); + + return sceneInstanceID; + } + + public uint PlaySceneByPackageId(uint sceneScriptPackageId, SceneFlags playbackflags = SceneFlags.Unk16, Position position = null) + { + SceneTemplate sceneTemplate = new SceneTemplate(); + sceneTemplate.SceneId = 0; + sceneTemplate.ScenePackageId = sceneScriptPackageId; + sceneTemplate.PlaybackFlags = playbackflags; + sceneTemplate.ScriptId = 0; + + return PlaySceneByTemplate(sceneTemplate, position); + } + + void CancelScene(uint sceneInstanceID, bool removeFromMap = true) + { + if (removeFromMap) + RemoveSceneInstanceId(sceneInstanceID); + + CancelScene cancelScene = new CancelScene(); + cancelScene.SceneInstanceID = sceneInstanceID; + GetPlayer().SendPacket(cancelScene, true); + } + + public void OnSceneTrigger(uint sceneInstanceID, string triggerName) + { + if (!HasScene(sceneInstanceID)) + return; + + if (_isDebuggingScenes) + GetPlayer().SendSysMessage(CypherStrings.CommandSceneDebugTrigger, sceneInstanceID, triggerName); + + SceneTemplate sceneTemplate = GetSceneTemplateFromInstanceId(sceneInstanceID); + Global.ScriptMgr.OnSceneTrigger(GetPlayer(), sceneInstanceID, sceneTemplate, triggerName); + } + + public void OnSceneCancel(uint sceneInstanceID) + { + if (!HasScene(sceneInstanceID)) + return; + + if (_isDebuggingScenes) + GetPlayer().SendSysMessage(CypherStrings.CommandSceneDebugCancel, sceneInstanceID); + + SceneTemplate sceneTemplate = GetSceneTemplateFromInstanceId(sceneInstanceID); + + // Must be done before removing aura + RemoveSceneInstanceId(sceneInstanceID); + + if (sceneTemplate.SceneId != 0) + RemoveAurasDueToSceneId(sceneTemplate.SceneId); + + Global.ScriptMgr.OnSceneCancel(GetPlayer(), sceneInstanceID, sceneTemplate); + + if (sceneTemplate.PlaybackFlags.HasAnyFlag(SceneFlags.CancelAtEnd)) + CancelScene(sceneInstanceID, false); + } + + public void OnSceneComplete(uint sceneInstanceID) + { + if (!HasScene(sceneInstanceID)) + return; + + if (_isDebuggingScenes) + GetPlayer().SendSysMessage(CypherStrings.CommandSceneDebugComplete, sceneInstanceID); + + SceneTemplate sceneTemplate = GetSceneTemplateFromInstanceId(sceneInstanceID); + + // Must be done before removing aura + RemoveSceneInstanceId(sceneInstanceID); + + if (sceneTemplate.SceneId != 0) + RemoveAurasDueToSceneId(sceneTemplate.SceneId); + + Global.ScriptMgr.OnSceneComplete(GetPlayer(), sceneInstanceID, sceneTemplate); + + if (sceneTemplate.PlaybackFlags.HasAnyFlag(SceneFlags.CancelAtEnd)) + CancelScene(sceneInstanceID, false); + } + + bool HasScene(uint sceneInstanceID, uint sceneScriptPackageId = 0) + { + var sceneTempalte = _scenesByInstance.LookupByKey(sceneInstanceID); + + if (sceneTempalte != null) + return sceneScriptPackageId == 0 || sceneScriptPackageId == sceneTempalte.ScenePackageId; + + return false; + } + + void AddInstanceIdToSceneMap(uint sceneInstanceID, SceneTemplate sceneTemplate) + { + _scenesByInstance[sceneInstanceID] = sceneTemplate; + } + + public void CancelSceneBySceneId(uint sceneId) + { + List instancesIds = new List(); + + foreach (var pair in _scenesByInstance) + if (pair.Value.SceneId == sceneId) + instancesIds.Add(pair.Key); + + foreach (uint sceneInstanceID in instancesIds) + CancelScene(sceneInstanceID); + } + + public void CancelSceneByPackageId(uint sceneScriptPackageId) + { + List instancesIds = new List(); + + foreach (var sceneTemplate in _scenesByInstance) + if (sceneTemplate.Value.ScenePackageId == sceneScriptPackageId) + instancesIds.Add(sceneTemplate.Key); + + foreach (uint sceneInstanceID in instancesIds) + CancelScene(sceneInstanceID); + } + + void RemoveSceneInstanceId(uint sceneInstanceID) + { + _scenesByInstance.Remove(sceneInstanceID); + } + + void RemoveAurasDueToSceneId(uint sceneId) + { + var scenePlayAuras = GetPlayer().GetAuraEffectsByType(AuraType.PlayScene); + foreach (var scenePlayAura in scenePlayAuras) + { + if (scenePlayAura.GetMiscValue() == sceneId) + { + GetPlayer().RemoveAura(scenePlayAura.GetBase()); + break; + } + } + } + + SceneTemplate GetSceneTemplateFromInstanceId(uint sceneInstanceID) + { + return _scenesByInstance.LookupByKey(sceneInstanceID); + } + + public uint GetActiveSceneCount(uint sceneScriptPackageId = 0) + { + uint activeSceneCount = 0; + + foreach (var sceneTemplate in _scenesByInstance.Values) + if (sceneScriptPackageId == 0 || sceneTemplate.ScenePackageId == sceneScriptPackageId) + ++activeSceneCount; + + return activeSceneCount; + } + + Player GetPlayer() { return _player; } + + void RecreateScene(uint sceneScriptPackageId, SceneFlags playbackflags = SceneFlags.Unk16, Position position = null) + { + CancelSceneByPackageId(sceneScriptPackageId); + PlaySceneByPackageId(sceneScriptPackageId, playbackflags, position); + } + + public Dictionary GetSceneTemplateByInstanceMap() { return _scenesByInstance; } + + uint GetNewStandaloneSceneInstanceID() { return ++_standaloneSceneInstanceID; } + + public void ToggleDebugSceneMode() { _isDebuggingScenes = !_isDebuggingScenes; } + public bool IsInDebugSceneMode() { return _isDebuggingScenes; } + + Player _player; + Dictionary _scenesByInstance = new Dictionary(); + uint _standaloneSceneInstanceID; + bool _isDebuggingScenes; + } +} diff --git a/Game/Entities/Player/SocialMgr.cs b/Game/Entities/Player/SocialMgr.cs new file mode 100644 index 000000000..44a08c0ca --- /dev/null +++ b/Game/Entities/Player/SocialMgr.cs @@ -0,0 +1,362 @@ +/* + * Copyright (C) 2012-2017 CypherCore + * + * 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, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +using Framework.Constants; +using Framework.Database; +using Game.Network; +using Game.Network.Packets; +using System; +using System.Collections.Generic; + +namespace Game.Entities +{ + public class SocialManager : Singleton + { + SocialManager() { } + + public const int FriendLimit = 50; + public const int IgnoreLimit = 50; + + public void GetFriendInfo(Player player, ObjectGuid friendGUID, FriendInfo friendInfo) + { + if (!player) + return; + + friendInfo.Status = FriendStatus.Offline; + friendInfo.Area = 0; + friendInfo.Level = 0; + friendInfo.Class = 0; + + Player target = Global.ObjAccessor.FindPlayer(friendGUID); + if (!target) + return; + + var playerFriendInfo = player.GetSocial()._playerSocialMap.LookupByKey(friendGUID); + if (playerFriendInfo != null) + friendInfo.Note = playerFriendInfo.Note; + + // PLAYER see his team only and PLAYER can't see MODERATOR, GAME MASTER, ADMINISTRATOR characters + // MODERATOR, GAME MASTER, ADMINISTRATOR can see all + + if (!player.GetSession().HasPermission(RBACPermissions.WhoSeeAllSecLevels) && + target.GetSession().GetSecurity() > (AccountTypes)WorldConfig.GetIntValue(WorldCfg.GmLevelInWhoList)) + return; + + // player can see member of other team only if CONFIG_ALLOW_TWO_SIDE_WHO_LIST + if (target.GetTeam() != player.GetTeam() && !player.GetSession().HasPermission(RBACPermissions.TwoSideWhoList)) + return; + + if (target.IsVisibleGloballyFor(player)) + { + if (target.isDND()) + friendInfo.Status = FriendStatus.DND; + else if (target.isAFK()) + friendInfo.Status = FriendStatus.AFK; + else + friendInfo.Status = FriendStatus.Online; + + friendInfo.Area = target.GetZoneId(); + friendInfo.Level = target.getLevel(); + friendInfo.Class = target.GetClass(); + } + } + + public void SendFriendStatus(Player player, FriendsResult result, ObjectGuid friendGuid, bool broadcast = false) + { + FriendInfo fi = new FriendInfo(); + GetFriendInfo(player, friendGuid, fi); + + FriendStatusPkt friendStatus = new FriendStatusPkt(); + friendStatus.Initialize(friendGuid, result, fi); + + if (broadcast) + BroadcastToFriendListers(player, friendStatus); + else + player.SendPacket(friendStatus); + } + + void BroadcastToFriendListers(Player player, ServerPacket packet) + { + if (!player) + return; + + packet.Write(); + + AccountTypes gmSecLevel = (AccountTypes)WorldConfig.GetIntValue(WorldCfg.GmLevelInWhoList); + foreach (var pair in _socialMap) + { + var info = pair.Value._playerSocialMap.LookupByKey(player.GetGUID()); + if (info != null && info.Flags.HasAnyFlag(SocialFlag.Friend)) + { + Player target = Global.ObjAccessor.FindPlayer(pair.Key); + if (!target || !target.IsInWorld) + continue; + + WorldSession session = target.GetSession(); + if (!session.HasPermission(RBACPermissions.WhoSeeAllSecLevels) && player.GetSession().GetSecurity() > gmSecLevel) + continue; + + if (target.GetTeam() != player.GetTeam() && !session.HasPermission(RBACPermissions.TwoSideWhoList)) + continue; + + if (player.IsVisibleGloballyFor(target)) + session.SendPacket(packet, false); + } + } + } + + public PlayerSocial LoadFromDB(SQLResult result, ObjectGuid guid) + { + PlayerSocial social = new PlayerSocial(); + social.SetPlayerGUID(guid); + + if (!result.IsEmpty()) + { + do + { + ObjectGuid friendGuid = ObjectGuid.Create(HighGuid.Player, result.Read(0)); + ObjectGuid friendAccountGuid = ObjectGuid.Create(HighGuid.WowAccount, result.Read(1)); + SocialFlag flags = (SocialFlag)result.Read(2); + + social._playerSocialMap[friendGuid] = new FriendInfo(friendAccountGuid, flags, result.Read(3)); + } + while (result.NextRow()); + } + + _socialMap[guid] = social; + + return social; + } + + public void RemovePlayerSocial(ObjectGuid guid) { _socialMap.Remove(guid); } + + Dictionary _socialMap = new Dictionary(); + } + + public class PlayerSocial + { + uint GetNumberOfSocialsWithFlag(SocialFlag flag) + { + uint counter = 0; + foreach (var pair in _playerSocialMap) + if (pair.Value.Flags.HasAnyFlag(flag)) + ++counter; + + return counter; + } + + public bool AddToSocialList(ObjectGuid friendGuid, SocialFlag flag) + { + // check client limits + if (GetNumberOfSocialsWithFlag(flag) >= (((flag & SocialFlag.Friend) != 0) ? SocialManager.FriendLimit : SocialManager.IgnoreLimit)) + return false; + + var friendInfo = _playerSocialMap.LookupByKey(friendGuid); + if (friendInfo != null) + { + friendInfo.Flags |= flag; + + PreparedStatement stmt = DB.Characters.GetPreparedStatement(CharStatements.UPD_CHARACTER_SOCIAL_FLAGS); + stmt.AddValue(0, friendInfo.Flags); + stmt.AddValue(1, GetPlayerGUID().GetCounter()); + stmt.AddValue(2, friendGuid.GetCounter()); + DB.Characters.Execute(stmt); + } + else + { + FriendInfo fi = new FriendInfo(); + fi.Flags |= flag; + _playerSocialMap[friendGuid] = fi; + + PreparedStatement stmt = DB.Characters.GetPreparedStatement(CharStatements.INS_CHARACTER_SOCIAL); + stmt.AddValue(0, GetPlayerGUID().GetCounter()); + stmt.AddValue(1, friendGuid.GetCounter()); + stmt.AddValue(2, flag); + DB.Characters.Execute(stmt); + } + return true; + } + + public void RemoveFromSocialList(ObjectGuid friendGuid, SocialFlag flag) + { + var friendInfo = _playerSocialMap.LookupByKey(friendGuid); + if (friendInfo == null) // not exist + return; + + friendInfo.Flags &= ~flag; + + if (friendInfo.Flags == 0) + { + PreparedStatement stmt = DB.Characters.GetPreparedStatement(CharStatements.DEL_CHARACTER_SOCIAL); + stmt.AddValue(0, GetPlayerGUID().GetCounter()); + stmt.AddValue(1, friendGuid.GetCounter()); + DB.Characters.Execute(stmt); + + _playerSocialMap.Remove(friendGuid); + } + else + { + PreparedStatement stmt = DB.Characters.GetPreparedStatement(CharStatements.UPD_CHARACTER_SOCIAL_FLAGS); + stmt.AddValue(0, friendInfo.Flags); + stmt.AddValue(1, GetPlayerGUID().GetCounter()); + stmt.AddValue(2, friendGuid.GetCounter()); + DB.Characters.Execute(stmt); + } + } + + public void SetFriendNote(ObjectGuid friendGuid, string note) + { + if (!_playerSocialMap.ContainsKey(friendGuid)) // not exist + return; + + PreparedStatement stmt = DB.Characters.GetPreparedStatement(CharStatements.UPD_CHARACTER_SOCIAL_NOTE); + stmt.AddValue(0, note); + stmt.AddValue(1, GetPlayerGUID().GetCounter()); + stmt.AddValue(2, friendGuid.GetCounter()); + DB.Characters.Execute(stmt); + + _playerSocialMap[friendGuid].Note = note; + } + + public void SendSocialList(Player player, SocialFlag flags) + { + if (!player) + return; + + ContactList contactList = new ContactList(); + contactList.Flags = flags; + + foreach (var v in _playerSocialMap) + { + if (!v.Value.Flags.HasAnyFlag(flags)) + continue; + + Global.SocialMgr.GetFriendInfo(player, v.Key, v.Value); + + contactList.Contacts.Add(new ContactInfo(v.Key, v.Value)); + + // client's friends list and ignore list limit + if (contactList.Contacts.Count >= (((flags & SocialFlag.Friend) != 0) ? SocialManager.FriendLimit : SocialManager.IgnoreLimit)) + break; + } + + player.SendPacket(contactList); + } + + bool _HasContact(ObjectGuid guid, SocialFlag flags) + { + var friendInfo = _playerSocialMap.LookupByKey(guid); + if (friendInfo != null) + return friendInfo.Flags.HasAnyFlag(flags); + + return false; + } + + public bool HasFriend(ObjectGuid friendGuid) + { + return _HasContact(friendGuid, SocialFlag.Friend); + } + + public bool HasIgnore(ObjectGuid ignoreGuid) + { + return _HasContact(ignoreGuid, SocialFlag.Ignored); + } + + ObjectGuid GetPlayerGUID() { return m_playerGUID; } + + public void SetPlayerGUID(ObjectGuid guid) { m_playerGUID = guid; } + + public Dictionary _playerSocialMap = new Dictionary(); + ObjectGuid m_playerGUID; + } + + public class FriendInfo + { + public FriendInfo() + { + Status = FriendStatus.Offline; + Note = ""; + } + + public FriendInfo(ObjectGuid accountGuid, SocialFlag flags, string note) + { + WowAccountGuid = accountGuid; + Status = FriendStatus.Offline; + Flags = flags; + Note = note; + } + + public ObjectGuid WowAccountGuid; + public FriendStatus Status; + public SocialFlag Flags; + public uint Area; + public uint Level; + public Class Class; + public string Note; + } + + public enum FriendStatus + { + Offline = 0x00, + Online = 0x01, + AFK = 0x02, + DND = 0x04, + RAF = 0x08 + } + + public enum SocialFlag + { + Friend = 0x01, + Ignored = 0x02, + Muted = 0x04, // guessed + Unk = 0x08, // Unknown - does not appear to be RaF + All = Friend | Ignored | Muted + } + + public enum FriendsResult + { + DbError = 0x00, + ListFull = 0x01, + Online = 0x02, + Offline = 0x03, + NotFound = 0x04, + Removed = 0x05, + AddedOnline = 0x06, + AddedOffline = 0x07, + Already = 0x08, + Self = 0x09, + Enemy = 0x0a, + IgnoreFull = 0x0b, + IgnoreSelf = 0x0c, + IgnoreNotFound = 0x0d, + IgnoreAlready = 0x0e, + IgnoreAdded = 0x0f, + IgnoreRemoved = 0x10, + IgnoreAmbiguous = 0x11, // That Name Is Ambiguous, Type More Of The Player'S Server Name + MuteFull = 0x12, + MuteSelf = 0x13, + MuteNotFound = 0x14, + MuteAlready = 0x15, + MuteAdded = 0x16, + MuteRemoved = 0x17, + MuteAmbiguous = 0x18, // That Name Is Ambiguous, Type More Of The Player'S Server Name + Unk1 = 0x19, // no message at client + Unk2 = 0x1A, + Unk3 = 0x1B, + Unknown = 0x1C // Unknown friend response from server + } +} diff --git a/Game/Entities/Player/TradeData.cs b/Game/Entities/Player/TradeData.cs new file mode 100644 index 000000000..20a627998 --- /dev/null +++ b/Game/Entities/Player/TradeData.cs @@ -0,0 +1,186 @@ +/* + * Copyright (C) 2012-2017 CypherCore + * + * 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, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +using Framework.Constants; +using Game.Network.Packets; + +namespace Game.Entities +{ + public class TradeData + { + public TradeData(Player player, Player trader) + { + m_player = player; + m_trader = trader; + m_clientStateIndex = 1; + m_serverStateIndex = 1; + } + + public TradeData GetTraderData() + { + return m_trader.GetTradeData(); + } + + public Item GetItem(TradeSlots slot) + { + return !m_items[(int)slot].IsEmpty() ? m_player.GetItemByGuid(m_items[(int)slot]) : null; + } + + public bool HasItem(ObjectGuid itemGuid) + { + for (byte i = 0; i < (byte)TradeSlots.Count; ++i) + if (m_items[i] == itemGuid) + return true; + + return false; + } + + public TradeSlots GetTradeSlotForItem(ObjectGuid itemGuid) + { + for (TradeSlots i = 0; i < TradeSlots.Count; ++i) + if (m_items[(int)i] == itemGuid) + return i; + + return TradeSlots.Invalid; + } + + public Item GetSpellCastItem() + { + return !m_spellCastItem.IsEmpty() ? m_player.GetItemByGuid(m_spellCastItem) : null; + } + + public void SetItem(TradeSlots slot, Item item, bool update = false) + { + ObjectGuid itemGuid = item ? item.GetGUID() : ObjectGuid.Empty; + + if (m_items[(int)slot] == itemGuid && !update) + return; + + m_items[(int)slot] = itemGuid; + + SetAccepted(false); + GetTraderData().SetAccepted(false); + + UpdateServerStateIndex(); + + Update(); + + // need remove possible trader spell applied to changed item + if (slot == TradeSlots.NonTraded) + GetTraderData().SetSpell(0); + + // need remove possible player spell applied (possible move reagent) + SetSpell(0); + } + + public uint GetSpell() { return m_spell; } + + public void SetSpell(uint spell_id, Item castItem = null) + { + ObjectGuid itemGuid = castItem ? castItem.GetGUID() : ObjectGuid.Empty; + + if (m_spell == spell_id && m_spellCastItem == itemGuid) + return; + + m_spell = spell_id; + m_spellCastItem = itemGuid; + + SetAccepted(false); + GetTraderData().SetAccepted(false); + + UpdateServerStateIndex(); + + Update(true); // send spell info to item owner + Update(false); // send spell info to caster self + } + + public void SetMoney(ulong money) + { + if (m_money == money) + return; + + if (!m_player.HasEnoughMoney(money)) + { + TradeStatusPkt info = new TradeStatusPkt(); + info.Status = TradeStatus.Failed; + info.BagResult = InventoryResult.NotEnoughMoney; + m_player.GetSession().SendTradeStatus(info); + return; + } + m_money = money; + + SetAccepted(false); + GetTraderData().SetAccepted(false); + + UpdateServerStateIndex(); + + Update(true); + } + + void Update(bool forTarget = true) + { + if (forTarget) + m_trader.GetSession().SendUpdateTrade(true); // player state for trader + else + m_player.GetSession().SendUpdateTrade(false); // player state for player + } + + public void SetAccepted(bool state, bool crosssend = false) + { + m_accepted = state; + + if (!state) + { + TradeStatusPkt info = new TradeStatusPkt(); + info.Status = TradeStatus.Unaccepted; + if (crosssend) + m_trader.GetSession().SendTradeStatus(info); + else + m_player.GetSession().SendTradeStatus(info); + } + } + + public Player GetTrader() { return m_trader; } + + public bool HasSpellCastItem() { return !m_spellCastItem.IsEmpty(); } + + public ulong GetMoney() { return m_money; } + + public bool IsAccepted() { return m_accepted; } + + public bool IsInAcceptProcess() { return m_acceptProccess; } + + public void SetInAcceptProcess(bool state) { m_acceptProccess = state; } + + public uint GetClientStateIndex() { return m_clientStateIndex; } + public void UpdateClientStateIndex() { ++m_clientStateIndex; } + + public uint GetServerStateIndex() { return m_serverStateIndex; } + public void UpdateServerStateIndex() { m_serverStateIndex = RandomHelper.Rand32(); } + + Player m_player; + Player m_trader; + bool m_accepted; + bool m_acceptProccess; + ulong m_money; + uint m_spell; + ObjectGuid m_spellCastItem; + ObjectGuid[] m_items = new ObjectGuid[(int)TradeSlots.Count]; + uint m_clientStateIndex; + uint m_serverStateIndex; + } +} diff --git a/Game/Entities/StatSystem.cs b/Game/Entities/StatSystem.cs new file mode 100644 index 000000000..4cbd9bfe2 --- /dev/null +++ b/Game/Entities/StatSystem.cs @@ -0,0 +1,1878 @@ +/* + * Copyright (C) 2012-2017 CypherCore + * + * 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, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +using Framework.Constants; +using Game.DataStorage; +using Game.Network.Packets; +using Game.Spells; +using System; +using System.Collections.Generic; +using System.Linq; + +namespace Game.Entities +{ + public partial class Unit + { + public bool HandleStatModifier(UnitMods unitMod, UnitModifierType modifierType, int amount, bool apply) + { + return HandleStatModifier(unitMod, modifierType, (float)amount, apply); + } + public bool HandleStatModifier(UnitMods unitMod, UnitModifierType modifierType, float amount, bool apply) + { + if (unitMod >= UnitMods.End || modifierType >= UnitModifierType.End) + { + Log.outError(LogFilter.Unit, "ERROR in HandleStatModifier(): non-existing UnitMods or wrong UnitModifierType!"); + return false; + } + + switch (modifierType) + { + case UnitModifierType.BaseValue: + case UnitModifierType.BasePCTExcludeCreate: + case UnitModifierType.TotalValue: + m_auraModifiersGroup[(int)unitMod][(int)modifierType] += apply ? amount : -amount; + break; + case UnitModifierType.BasePCT: + case UnitModifierType.TotalPCT: + MathFunctions.ApplyPercentModFloatVar(ref m_auraModifiersGroup[(int)unitMod][(int)modifierType], amount, apply); + break; + default: + break; + } + + if (!CanModifyStats()) + return false; + + switch (unitMod) + { + case UnitMods.StatStrength: + case UnitMods.StatAgility: + case UnitMods.StatStamina: + case UnitMods.StatIntellect: + UpdateStats(GetStatByAuraGroup(unitMod)); + break; + case UnitMods.Armor: + UpdateArmor(); + break; + case UnitMods.Health: + UpdateMaxHealth(); + break; + case UnitMods.Mana: + case UnitMods.Rage: + case UnitMods.Focus: + case UnitMods.Energy: + case UnitMods.Rune: + case UnitMods.RunicPower: + UpdateMaxPower(GetPowerTypeByAuraGroup(unitMod)); + break; + case UnitMods.ResistanceHoly: + case UnitMods.ResistanceFire: + case UnitMods.ResistanceNature: + case UnitMods.ResistanceFrost: + case UnitMods.ResistanceShadow: + case UnitMods.ResistanceArcane: + UpdateResistances(GetSpellSchoolByAuraGroup(unitMod)); + break; + case UnitMods.AttackPower: + UpdateAttackPowerAndDamage(); + break; + case UnitMods.AttackPowerRanged: + UpdateAttackPowerAndDamage(true); + break; + case UnitMods.DamageMainHand: + UpdateDamagePhysical(WeaponAttackType.BaseAttack); + break; + case UnitMods.DamageOffHand: + UpdateDamagePhysical(WeaponAttackType.OffAttack); + break; + case UnitMods.DamageRanged: + UpdateDamagePhysical(WeaponAttackType.RangedAttack); + break; + default: + break; + } + + return true; + } + int GetMinPower(PowerType power) { return power == PowerType.LunarPower ? -100 : 0; } + // returns negative amount on power reduction + public int ModifyPowerPct(PowerType power, float pct, bool apply) + { + float amount = GetMaxPower(power); + MathFunctions.ApplyPercentModFloatVar(ref amount, pct, apply); + + return ModifyPower(power, (int)amount - GetMaxPower(power)); + } + // returns negative amount on power reduction + public int ModifyPower(PowerType power, int dVal) + { + int gain = 0; + + if (dVal == 0) + return 0; + + int curPower = GetPower(power); + + int val = (dVal + curPower); + if (val <= GetMinPower(power)) + { + SetPower(power, GetMinPower(power)); + return -curPower; + } + + int maxPower = GetMaxPower(power); + if (val < maxPower) + { + SetPower(power, val); + gain = val - curPower; + } + else if (curPower != maxPower) + { + SetPower(power, maxPower); + gain = maxPower - curPower; + } + + return gain; + } + + Stats GetStatByAuraGroup(UnitMods unitMod) + { + Stats stat = Stats.Strength; + + switch (unitMod) + { + case UnitMods.StatStrength: + stat = Stats.Strength; + break; + case UnitMods.StatAgility: + stat = Stats.Agility; + break; + case UnitMods.StatStamina: + stat = Stats.Stamina; + break; + case UnitMods.StatIntellect: + stat = Stats.Intellect; + break; + default: + break; + } + + return stat; + } + PowerType GetPowerTypeByAuraGroup(UnitMods unitMod) + { + switch (unitMod) + { + case UnitMods.Rage: + return PowerType.Rage; + case UnitMods.Focus: + return PowerType.Focus; + case UnitMods.Energy: + return PowerType.Energy; + case UnitMods.Rune: + return PowerType.Runes; + case UnitMods.RunicPower: + return PowerType.RunicPower; + default: + case UnitMods.Mana: + return PowerType.Mana; + } + } + + public void ApplyStatBuffMod(Stats stat, float val, bool apply) + { + ApplyModSignedFloatValue((val > 0 ? UnitFields.PosStat + (int)stat : UnitFields.NegStat + (int)stat), val, apply); + } + public void ApplyStatPercentBuffMod(Stats stat, float val, bool apply) + { + ApplyPercentModFloatValue(UnitFields.PosStat + (int)stat, val, apply); + ApplyPercentModFloatValue(UnitFields.NegStat + (int)stat, val, apply); + } + + public virtual bool UpdateStats(Stats stat) { return false; } + public virtual bool UpdateAllStats() { return false; } + public virtual void UpdateResistances(SpellSchools school) { } + public virtual void UpdateArmor() { } + public virtual void UpdateMaxHealth() { } + public virtual void UpdateMaxPower(PowerType power) { } + public virtual void UpdateAttackPowerAndDamage(bool ranged = false) { } + public virtual void UpdateDamagePhysical(WeaponAttackType attType) + { + float minDamage = 0.0f; + float maxDamage = 0.0f; + + CalculateMinMaxDamage(attType, false, true, out minDamage, out maxDamage); + + switch (attType) + { + case WeaponAttackType.BaseAttack: + default: + SetStatFloatValue(UnitFields.MinDamage, minDamage); + SetStatFloatValue(UnitFields.MaxDamage, maxDamage); + break; + case WeaponAttackType.OffAttack: + SetStatFloatValue(UnitFields.MinOffHandDamage, minDamage); + SetStatFloatValue(UnitFields.MaxOffHandDamage, maxDamage); + break; + case WeaponAttackType.RangedAttack: + SetStatFloatValue(UnitFields.MinRangedDamage, minDamage); + SetStatFloatValue(UnitFields.MaxRangedDamage, maxDamage); + break; + } + } + public virtual void CalculateMinMaxDamage(WeaponAttackType attType, bool normalized, bool addTotalPct, out float minDamage, out float maxDamage) + { + minDamage = 0f; + maxDamage = 0f; + } + + public void UpdateAllResistances() + { + for (var i = SpellSchools.Normal; i < SpellSchools.Max; ++i) + UpdateResistances(i); + } + + //Stats + public float GetStat(Stats stat) + { + return GetFloatValue(UnitFields.Stat + (int)stat); + } + public void SetCreateStat(Stats stat, float val) + { + CreateStats[(int)stat] = val; + } + public void SetStat(Stats stat, int val) + { + SetStatInt32Value(UnitFields.Stat + (int)stat, val); + } + public void SetCreateMana(uint val) + { + SetUInt32Value(UnitFields.BaseMana, val); + } + public uint GetCreateMana() + { + return GetUInt32Value(UnitFields.BaseMana); + } + public uint GetArmor() + { + return GetResistance(SpellSchools.Normal); + } + public void SetArmor(int val) + { + SetResistance(SpellSchools.Normal, val); + } + public uint GetResistance(SpellSchools school) + { + return GetUInt32Value(UnitFields.Resistances + (int)school); + } + public uint GetResistance(SpellSchoolMask mask) + { + int resist = -1; + for (int i = (int)SpellSchools.Normal; i < (int)SpellSchools.Max; ++i) + if (Convert.ToBoolean((int)mask & (1 << i)) && (resist < 0 || resist > GetResistance((SpellSchools)i))) + resist = (int)GetResistance((SpellSchools)i); + + // resist value will never be negative here + return (uint)resist; + } + public void SetResistance(SpellSchools school, int val) + { + SetStatInt32Value(UnitFields.Resistances + (int)school, val); + } + public float GetCreateStat(Stats stat) + { + return CreateStats[(int)stat]; + } + public void InitStatBuffMods() + { + for (var i = Stats.Strength; i < Stats.Max; ++i) + { + SetFloatValue(UnitFields.PosStat + (int)i, 0); + SetFloatValue(UnitFields.NegStat + (int)i, 0); + } + } + public float GetResistanceBuffMods(SpellSchools school, bool positive) + { + return GetFloatValue((positive ? UnitFields.ResistanceBuffModsPositive : UnitFields.ResistanceBuffModsNegative) + (int)school); + } + public void SetResistanceBuffMods(SpellSchools school, bool positive, float val) + { + SetFloatValue((positive ? UnitFields.ResistanceBuffModsPositive : UnitFields.ResistanceBuffModsNegative) + (int)school, val); + } + public void ApplyResistanceBuffModsMod(SpellSchools school, bool positive, float val, bool apply) + { + ApplyModSignedFloatValue((positive ? UnitFields.ResistanceBuffModsPositive : UnitFields.ResistanceBuffModsNegative) + (int)school, val, apply); + } + public void ApplyResistanceBuffModsPercentMod(SpellSchools school, bool positive, float val, bool apply) + { + ApplyPercentModFloatValue((positive ? UnitFields.ResistanceBuffModsPositive : UnitFields.ResistanceBuffModsNegative) + (int)school, val, apply); + } + + + public bool CanModifyStats() + { + return canModifyStats; + } + public void SetCanModifyStats(bool modifyStats) + { + canModifyStats = modifyStats; + } + public float GetTotalStatValue(Stats stat) + { + UnitMods unitMod = UnitMods.StatStart + (int)stat; + + if (m_auraModifiersGroup[(int)unitMod][(int)UnitModifierType.TotalPCT] <= 0.0f) + return 0.0f; + + float value = MathFunctions.CalculatePct(m_auraModifiersGroup[(int)unitMod][(int)UnitModifierType.BaseValue], Math.Max(m_auraModifiersGroup[(int)unitMod][(int)UnitModifierType.BasePCTExcludeCreate], -100.0f)); + value += GetCreateStat(stat); + value *= m_auraModifiersGroup[(int)unitMod][(int)UnitModifierType.BasePCT]; + value += m_auraModifiersGroup[(int)unitMod][(int)UnitModifierType.TotalValue]; + value *= m_auraModifiersGroup[(int)unitMod][(int)UnitModifierType.TotalPCT]; + + return value; + } + + //Health + public uint GetCreateHealth() { return GetUInt32Value(UnitFields.BaseHealth); } + public ulong GetHealth() { return GetUInt64Value(UnitFields.Health); } + public ulong GetMaxHealth() { return GetUInt64Value(UnitFields.MaxHealth); } + public float GetHealthPct() { return GetMaxHealth() != 0 ? 100.0f * GetHealth() / GetMaxHealth() : 0.0f; } + + public void SetCreateHealth(uint val) + { + SetUInt32Value(UnitFields.BaseHealth, val); + } + public void SetHealth(ulong val) + { + if (getDeathState() == DeathState.JustDied) + val = 0; + else if (IsTypeId(TypeId.Player) && getDeathState() == DeathState.Dead) + val = 1; + else + { + ulong maxHealth = GetMaxHealth(); + if (maxHealth < val) + val = maxHealth; + } + + SetUInt64Value(UnitFields.Health, val); + + // group update + Player player = ToPlayer(); + if (player) + { + if (player.GetGroup()) + player.SetGroupUpdateFlag(GroupUpdateFlags.CurHp); + } + else if (IsPet()) + { + Pet pet = ToCreature().ToPet(); + if (pet.isControlled()) + pet.SetGroupUpdateFlag(GroupUpdatePetFlags.CurHp); + } + } + public void SetMaxHealth(ulong val) + { + if (val == 0) + val = 1; + + ulong health = GetHealth(); + SetUInt64Value(UnitFields.MaxHealth, val); + + // group update + if (IsTypeId(TypeId.Player)) + { + if (ToPlayer().GetGroup()) + ToPlayer().SetGroupUpdateFlag(GroupUpdateFlags.MaxHp); + } + else if (IsPet()) + { + Pet pet = ToCreature().ToPet(); + if (pet.isControlled()) + pet.SetGroupUpdateFlag(GroupUpdatePetFlags.MaxHp); + } + + if (val < health) + SetHealth(val); + } + public void SetFullHealth() { SetHealth(GetMaxHealth()); } + + public bool IsFullHealth() { return GetHealth() == GetMaxHealth(); } + public bool HealthBelowPct(int pct) { return GetHealth() < CountPctFromMaxHealth(pct); } + public bool HealthBelowPctDamaged(int pct, uint damage) { return GetHealth() - damage < CountPctFromMaxHealth(pct); } + public bool HealthAbovePct(int pct) { return GetHealth() > CountPctFromMaxHealth(pct); } + bool HealthAbovePctHealed(int pct, uint heal) { return GetHealth() + heal > CountPctFromMaxHealth(pct); } + public ulong CountPctFromMaxHealth(int pct) { return MathFunctions.CalculatePct(GetMaxHealth(), pct); } + ulong CountPctFromCurHealth(int pct) { return MathFunctions.CalculatePct(GetHealth(), pct); } + + //Powers + public PowerType getPowerType() + { + return (PowerType)GetUInt32Value(UnitFields.DisplayPower); + } + public void setPowerType(PowerType newPowerType) + { + if (getPowerType() == newPowerType) + return; + + SetUInt32Value(UnitFields.DisplayPower, (uint)newPowerType); + + if (IsTypeId(TypeId.Player)) + { + if (ToPlayer().GetGroup()) + ToPlayer().SetGroupUpdateFlag(GroupUpdateFlags.PowerType); + } + /*else if (IsPet()) TODO 6.x + { + Pet pet = ToCreature().ToPet(); + if (pet.isControlled()) + pet.SetGroupUpdateFlag(GROUP_UPDATE_FLAG_PET_POWER_TYPE); + }*/ + + float powerMultiplier = 1.0f; + if (!IsPet()) + { + Creature creature = ToCreature(); + if (creature) + powerMultiplier = creature.GetCreatureTemplate().ModMana; + } + + switch (newPowerType) + { + default: + case PowerType.Mana: + break; + case PowerType.Rage: + SetMaxPower(PowerType.Rage, (int)Math.Ceiling(GetCreatePowers(PowerType.Rage) * powerMultiplier)); + SetPower(PowerType.Rage, 0); + break; + case PowerType.Focus: + SetMaxPower(PowerType.Focus, (int)Math.Ceiling(GetCreatePowers(PowerType.Focus) * powerMultiplier)); + SetPower(PowerType.Focus, (int)Math.Ceiling(GetCreatePowers(PowerType.Focus) * powerMultiplier)); + break; + case PowerType.Energy: + SetMaxPower(PowerType.Energy, (int)Math.Ceiling(GetCreatePowers(PowerType.Energy) * powerMultiplier)); + break; + } + } + public void SetMaxPower(PowerType power, int val) + { + uint powerIndex = GetPowerIndex(power); + if (powerIndex == (int)PowerType.Max || powerIndex >= (int)PowerType.MaxPerClass) + return; + + int cur_power = GetPower(power); + SetInt32Value(UnitFields.MaxPower + (int)powerIndex, val); + + // group update + if (IsTypeId(TypeId.Player)) + { + if (ToPlayer().GetGroup()) + ToPlayer().SetGroupUpdateFlag(GroupUpdateFlags.MaxPower); + } + /*else if (IsPet()) TODO 6.x + { + Pet pet = ToCreature().ToPet(); + if (pet.isControlled()) + pet.SetGroupUpdateFlag(GROUP_UPDATE_FLAG_PET_MAX_POWER); + }*/ + + if (val < cur_power) + SetPower(power, val); + } + public void SetPower(PowerType powerType, int val) + { + uint powerIndex = GetPowerIndex(powerType); + if (powerIndex == (int)PowerType.Max || powerIndex >= (int)PowerType.MaxPerClass) + return; + + int maxPower = GetMaxPower(powerType); + if (maxPower < val) + val = maxPower; + + SetInt32Value(UnitFields.Power + (int)powerIndex, val); + + if (IsInWorld) + { + PowerUpdate packet = new PowerUpdate(); + packet.Guid = GetGUID(); + packet.Powers.Add(new PowerUpdatePower(val, (byte)powerType)); + SendMessageToSet(packet, IsTypeId(TypeId.Player)); + } + + // group update + if (IsTypeId(TypeId.Player)) + { + Player player = ToPlayer(); + if (player.GetGroup()) + player.SetGroupUpdateFlag(GroupUpdateFlags.CurPower); + } + /*else if (IsPet()) TODO 6.x + { + Pet pet = ToCreature().ToPet(); + if (pet.isControlled()) + pet.SetGroupUpdateFlag(GROUP_UPDATE_FLAG_PET_CUR_POWER); + }*/ + } + public int GetPower(PowerType power) + { + uint powerIndex = GetPowerIndex(power); + if (powerIndex == (int)PowerType.Max || powerIndex >= (int)PowerType.MaxPerClass) + return 0; + + return GetInt32Value(UnitFields.Power + (int)powerIndex); + } + public int GetMaxPower(PowerType power) + { + uint powerIndex = GetPowerIndex(power); + if (powerIndex == (int)PowerType.Max || powerIndex >= (int)PowerType.MaxPerClass) + return 0; + + return GetInt32Value(UnitFields.MaxPower + (int)powerIndex); + } + public int GetCreatePowers(PowerType power) + { + if (power == PowerType.Mana) + return (int)GetCreateMana(); + + PowerTypeRecord powerType = Global.DB2Mgr.GetPowerTypeEntry(power); + if (powerType != null) + return powerType.MaxPower; + + return 0; + } + public uint GetPowerIndex(PowerType powerType) + { + // This is here because hunter pets are of the warrior class. + // With the current implementation, the core only gives them + // POWER_RAGE, so we enforce the class to hunter so that they + // effectively get focus power. + Class _class = GetClass(); + if (IsPet() && ToPet().getPetType() == PetType.Hunter) + _class = Class.Hunter; + + return Global.DB2Mgr.GetPowerIndexByClass(powerType, _class); + } + + public void ApplyResilience(Unit victim, ref uint damage) + { + // player mounted on multi-passenger mount is also classified as vehicle + if (IsVehicle() || (victim.IsVehicle() && !victim.IsTypeId(TypeId.Player))) + return; + + // Don't consider resilience if not in PvP - player or pet + if (!GetCharmerOrOwnerPlayerOrPlayerItself()) + return; + + Unit target = null; + if (victim.IsTypeId(TypeId.Player)) + target = victim; + else if (victim.IsTypeId(TypeId.Unit) && victim.GetOwner() && victim.GetOwner().IsTypeId(TypeId.Player)) + target = victim.GetOwner(); + + if (!target) + return; + + damage -= target.GetDamageReduction(damage); + } + // player or player's pet resilience (-1%) + uint GetDamageReduction(uint damage) { return GetCombatRatingDamageReduction(CombatRating.ResiliencePlayerDamage, 1.0f, 100.0f, damage); } + + float GetCombatRatingReduction(CombatRating cr) + { + Player player = ToPlayer(); + if (player) + return player.GetRatingBonusValue(cr); + // Player's pet get resilience from owner + else if (IsPet() && GetOwner()) + { + Player owner = GetOwner().ToPlayer(); + if (owner) + return owner.GetRatingBonusValue(cr); + } + + return 0.0f; + } + + uint GetCombatRatingDamageReduction(CombatRating cr, float rate, float cap, uint damage) + { + float percent = Math.Min(GetCombatRatingReduction(cr) * rate, cap); + return MathFunctions.CalculatePct(damage, percent); + } + + //Chances + float MeleeSpellMissChance(Unit victim, WeaponAttackType attType, uint spellId) + { + //calculate miss chance + float missChance = victim.GetUnitMissChance(attType); + + if (spellId == 0 && haveOffhandWeapon() && !IsInFeralForm()) + missChance += 19; + + // Calculate hit chance + float hitChance = 100.0f; + + // Spellmod from SPELLMOD_RESIST_MISS_CHANCE + if (spellId != 0) + { + Player modOwner = GetSpellModOwner(); + if (modOwner != null) + modOwner.ApplySpellMod(spellId, SpellModOp.ResistMissChance, ref hitChance); + } + + missChance += hitChance - 100.0f; + + if (attType == WeaponAttackType.RangedAttack) + missChance -= m_modRangedHitChance; + else + missChance -= m_modMeleeHitChance; + + // Limit miss chance from 0 to 77% + if (missChance < 0.0f) + return 0.0f; + if (missChance > 77.0f) + return 77.0f; + return missChance; + } + + float GetUnitCriticalChance(WeaponAttackType attackType, Unit victim) + { + float crit; + + if (IsTypeId(TypeId.Player)) + { + switch (attackType) + { + case WeaponAttackType.BaseAttack: + crit = GetFloatValue(PlayerFields.CritPercentage); + break; + case WeaponAttackType.OffAttack: + crit = GetFloatValue(PlayerFields.OffhandCritPercentage); + break; + case WeaponAttackType.RangedAttack: + crit = GetFloatValue(PlayerFields.RangedCritPercentage); + break; + default: + crit = 0.0f; + break; + } + } + else + { + crit = 5.0f; + crit += GetTotalAuraModifier(AuraType.ModWeaponCritPercent); + crit += GetTotalAuraModifier(AuraType.ModCritPct); + } + + // flat aura mods + if (attackType == WeaponAttackType.RangedAttack) + crit += victim.GetTotalAuraModifier(AuraType.ModAttackerRangedCritChance); + else + crit += victim.GetTotalAuraModifier(AuraType.ModAttackerMeleeCritChance); + + var critChanceForCaster = victim.GetAuraEffectsByType(AuraType.ModCritChanceForCaster); + foreach (AuraEffect aurEff in critChanceForCaster) + { + if (aurEff.GetCasterGUID() != GetGUID()) + continue; + + crit += aurEff.GetAmount(); + } + + crit += victim.GetTotalAuraModifier(AuraType.ModAttackerSpellAndWeaponCritChance); + + if (crit < 0.0f) + crit = 0.0f; + return crit; + } + float GetUnitDodgeChance(WeaponAttackType attType, Unit victim) + { + int levelDiff = (int)(victim.GetLevelForTarget(this) - GetLevelForTarget(victim)); + + float chance = 0.0f; + float levelBonus = 0.0f; + if (victim.IsTypeId(TypeId.Player)) + chance = victim.GetFloatValue(PlayerFields.DodgePercentage); + else + { + if (!victim.IsTotem()) + { + chance = 3.0f; + chance += victim.GetTotalAuraModifier(AuraType.ModDodgePercent); + + if (levelDiff > 0) + levelBonus = 1.5f * levelDiff; + } + } + + chance += levelBonus; + + // Reduce enemy dodge chance by SPELL_AURA_MOD_COMBAT_RESULT_CHANCE + chance += GetTotalAuraModifierByMiscValue(AuraType.ModCombatResultChance, (int)VictimState.Dodge); + + // reduce dodge by SPELL_AURA_MOD_ENEMY_DODGE + chance += GetTotalAuraModifier(AuraType.ModEnemyDodge); + + // Reduce dodge chance by attacker expertise rating + if (IsTypeId(TypeId.Player)) + chance -= ToPlayer().GetExpertiseDodgeOrParryReduction(attType); + else + chance -= GetTotalAuraModifier(AuraType.ModExpertise) / 4.0f; + return Math.Max(chance, 0.0f); + } + float GetUnitParryChance(WeaponAttackType attType, Unit victim) + { + int levelDiff = (int)(victim.GetLevelForTarget(this) - GetLevelForTarget(victim)); + + float chance = 0.0f; + float levelBonus = 0.0f; + Player playerVictim = victim.ToPlayer(); + if (playerVictim) + { + if (playerVictim.CanParry()) + { + Item tmpitem = playerVictim.GetWeaponForAttack(WeaponAttackType.BaseAttack, true); + if (!tmpitem) + tmpitem = playerVictim.GetWeaponForAttack(WeaponAttackType.OffAttack, true); + + if (tmpitem) + chance = playerVictim.GetFloatValue(PlayerFields.ParryPercentage); + } + } + else if (victim.IsTypeId(TypeId.Unit) && !(victim.ToCreature().GetCreatureTemplate().FlagsExtra.HasAnyFlag(CreatureFlagsExtra.NoParry))) + { + chance = 6.0f; + chance += victim.GetTotalAuraModifier(AuraType.ModParryPercent); + + if (levelDiff > 0) + levelBonus = 1.5f * levelDiff; + } + + chance += levelBonus; + + // Reduce parry chance by attacker expertise rating + if (IsTypeId(TypeId.Player)) + chance -= ToPlayer().GetExpertiseDodgeOrParryReduction(attType); + else + chance -= GetTotalAuraModifier(AuraType.ModExpertise) / 4.0f; + return Math.Max(chance, 0.0f); + } + float GetUnitMissChance(WeaponAttackType attType) + { + float miss_chance = 5.00f; + + if (attType == WeaponAttackType.RangedAttack) + miss_chance -= GetTotalAuraModifier(AuraType.ModAttackerRangedHitChance); + else + miss_chance -= GetTotalAuraModifier(AuraType.ModAttackerMeleeHitChance); + + return miss_chance; + } + float GetUnitBlockChance(WeaponAttackType attType, Unit victim) + { + int levelDiff = (int)(victim.GetLevelForTarget(this) - GetLevelForTarget(victim)); + + float chance = 0.0f; + float levelBonus = 0.0f; + Player playerVictim = victim.ToPlayer(); + if (playerVictim) + { + if (playerVictim.CanBlock()) + { + Item tmpitem = playerVictim.GetUseableItemByPos(InventorySlots.Bag0, EquipmentSlot.OffHand); + if (tmpitem && !tmpitem.IsBroken() && tmpitem.GetTemplate().GetInventoryType() == InventoryType.Shield) + chance = playerVictim.GetFloatValue(PlayerFields.BlockPercentage); + } + } + else + { + if (!victim.IsTotem() && !(victim.ToCreature().GetCreatureTemplate().FlagsExtra.HasAnyFlag(CreatureFlagsExtra.NoBlock))) + { + chance = 3.0f; + chance += victim.GetTotalAuraModifier(AuraType.ModBlockPercent); + + if (levelDiff > 0) + levelBonus = 1.5f * levelDiff; + } + } + + chance += levelBonus; + return Math.Max(chance, 0.0f); + } + + int GetMechanicResistChance(SpellInfo spellInfo) + { + if (spellInfo == null) + return 0; + + int resistMech = 0; + foreach (SpellEffectInfo effect in spellInfo.GetEffectsForDifficulty(GetMap().GetDifficultyID())) + { + if (effect == null || !effect.IsEffect()) + break; + + int effect_mech = (int)spellInfo.GetEffectMechanic(effect.EffectIndex, GetMap().GetDifficultyID()); + if (effect_mech != 0) + { + int temp = GetTotalAuraModifierByMiscValue(AuraType.ModMechanicResistance, effect_mech); + if (resistMech < temp) + resistMech = temp; + } + } + return Math.Max(resistMech, 0); + } + } + + public partial class Player + { + public override bool UpdateAllStats() + { + for (var i = Stats.Strength; i < Stats.Max; ++i) + { + float value = GetTotalStatValue(i); + SetStat(i, (int)value); + } + + UpdateArmor(); + // calls UpdateAttackPowerAndDamage() in UpdateArmor for SPELL_AURA_MOD_ATTACK_POWER_OF_ARMOR + UpdateAttackPowerAndDamage(true); + UpdateMaxHealth(); + + for (var i = PowerType.Mana; i < PowerType.Max; ++i) + UpdateMaxPower(i); + + UpdateAllRatings(); + UpdateAllCritPercentages(); + UpdateSpellCritChance(); + UpdateBlockPercentage(); + UpdateParryPercentage(); + UpdateDodgePercentage(); + UpdateSpellDamageAndHealingBonus(); + UpdateManaRegen(); + UpdateExpertise(WeaponAttackType.BaseAttack); + UpdateExpertise(WeaponAttackType.OffAttack); + RecalculateRating(CombatRating.ArmorPenetration); + UpdateAllResistances(); + + return true; + } + + public override bool UpdateStats(Stats stat) + { + // value = ((base_value * base_pct) + total_value) * total_pct + float value = GetTotalStatValue(stat); + + SetStat(stat, (int)value); + + if (stat == Stats.Stamina || stat == Stats.Intellect || stat == Stats.Strength) + { + Pet pet = GetPet(); + if (pet != null) + pet.UpdateStats(stat); + } + + switch (stat) + { + case Stats.Agility: + UpdateArmor(); + UpdateAllCritPercentages(); + UpdateDodgePercentage(); + break; + case Stats.Stamina: + UpdateMaxHealth(); + break; + case Stats.Intellect: + UpdateSpellCritChance(); + UpdateArmor(); //SPELL_AURA_MOD_RESISTANCE_OF_INTELLECT_PERCENT, only armor currently + break; + default: + break; + } + + if (stat == Stats.Strength) + UpdateAttackPowerAndDamage(false); + else if (stat == Stats.Agility) + { + UpdateAttackPowerAndDamage(false); + UpdateAttackPowerAndDamage(true); + } + + UpdateSpellDamageAndHealingBonus(); + UpdateManaRegen(); + + // Update ratings in exist SPELL_AURA_MOD_RATING_FROM_STAT and only depends from stat + uint mask = 0; + var modRatingFromStat = GetAuraEffectsByType(AuraType.ModRatingFromStat); + foreach (var eff in modRatingFromStat) + if ((Stats)eff.GetMiscValueB() == stat) + mask |= (uint)eff.GetMiscValue(); + if (mask != 0) + { + for (int rating = 0; rating < (int)CombatRating.Max; ++rating) + if (Convert.ToBoolean(mask & (1 << rating))) + ApplyRatingMod((CombatRating)rating, 0, true); + } + return true; + } + + public override void UpdateResistances(SpellSchools school) + { + if (school > SpellSchools.Normal) + { + float value = GetTotalAuraModValue(UnitMods.ResistanceStart + (int)school); + SetResistance(school, (int)value); + + Pet pet = GetPet(); + if (pet != null) + pet.UpdateResistances(school); + } + else + UpdateArmor(); + } + + void RecalculateRating(CombatRating cr) { ApplyRatingMod(cr, 0, true); } + + public void ApplyRatingMod(CombatRating combatRating, int value, bool apply) + { + baseRatingValue[(int)combatRating] += (apply ? value : -value); + + UpdateRating(combatRating); + } + public override void CalculateMinMaxDamage(WeaponAttackType attType, bool normalized, bool addTotalPct, out float min_damage, out float max_damage) + { + UnitMods unitMod; + + switch (attType) + { + case WeaponAttackType.BaseAttack: + default: + unitMod = UnitMods.DamageMainHand; + break; + case WeaponAttackType.OffAttack: + unitMod = UnitMods.DamageOffHand; + break; + case WeaponAttackType.RangedAttack: + unitMod = UnitMods.DamageRanged; + break; + } + + float attackPowerMod = Math.Max(GetAPMultiplier(attType, normalized), 0.25f); + + float baseValue = GetModifierValue(unitMod, UnitModifierType.BaseValue) + GetTotalAttackPowerValue(attType) / 3.5f * attackPowerMod; + float basePct = GetModifierValue(unitMod, UnitModifierType.BasePCT); + float totalValue = GetModifierValue(unitMod, UnitModifierType.TotalValue); + float totalPct = addTotalPct ? GetModifierValue(unitMod, UnitModifierType.TotalPCT) : 1.0f; + + float weaponMinDamage = GetWeaponDamageRange(attType, WeaponDamageRange.MinDamage); + float weaponMaxDamage = GetWeaponDamageRange(attType, WeaponDamageRange.MaxDamage); + + SpellShapeshiftFormRecord shapeshift = CliDB.SpellShapeshiftFormStorage.LookupByKey(GetShapeshiftForm()); + if (shapeshift != null && shapeshift.CombatRoundTime != 0) + { + weaponMinDamage = weaponMinDamage * shapeshift.CombatRoundTime / 1000.0f / attackPowerMod; + weaponMaxDamage = weaponMaxDamage * shapeshift.CombatRoundTime / 1000.0f / attackPowerMod; + } + else if (!CanUseAttackType(attType)) //check if player not in form but still can't use (disarm case) + { + //cannot use ranged/off attack, set values to 0 + if (attType != WeaponAttackType.BaseAttack) + { + min_damage = 0; + max_damage = 0; + return; + } + weaponMinDamage = SharedConst.BaseMinDamage; + weaponMaxDamage = SharedConst.BaseMaxDamage; + } + + min_damage = ((baseValue + weaponMinDamage) * basePct + totalValue) * totalPct; + max_damage = ((baseValue + weaponMaxDamage) * basePct + totalValue) * totalPct; + } + void UpdateAllCritPercentages() + { + float value = 5.0f; + + SetBaseModValue(BaseModGroup.CritPercentage, BaseModType.PCTmod, value); + SetBaseModValue(BaseModGroup.OffhandCritPercentage, BaseModType.PCTmod, value); + SetBaseModValue(BaseModGroup.RangedCritPercentage, BaseModType.PCTmod, value); + + UpdateCritPercentage(WeaponAttackType.BaseAttack); + UpdateCritPercentage(WeaponAttackType.OffAttack); + UpdateCritPercentage(WeaponAttackType.RangedAttack); + } + + public void UpdateManaRegen() + { + int manaIndex = (int)GetPowerIndex(PowerType.Mana); + if (manaIndex == (int)PowerType.Max) + return; + + // Mana regen from spirit + float spirit_regen = 0.0f; + // Apply PCT bonus from SPELL_AURA_MOD_POWER_REGEN_PERCENT aura on spirit base regen + spirit_regen *= GetTotalAuraMultiplierByMiscValue(AuraType.ModPowerRegenPercent, (int)PowerType.Mana); + + // CombatRegen = 5% of Base Mana + float base_regen = GetCreateMana() * 0.02f + GetTotalAuraModifierByMiscValue(AuraType.ModPowerRegen, (int)PowerType.Mana) / 5.0f; + + // Set regen rate in cast state apply only on spirit based regen + int modManaRegenInterrupt = GetTotalAuraModifier(AuraType.ModManaRegenInterrupt); + + SetFloatValue(UnitFields.PowerRegenInterruptedFlatModifier + manaIndex, base_regen + MathFunctions.CalculatePct(spirit_regen, modManaRegenInterrupt)); + SetFloatValue(UnitFields.PowerRegenFlatModifier + manaIndex, 0.001f + spirit_regen + base_regen); + } + + public void UpdateSpellDamageAndHealingBonus() + { + // Magic damage modifiers implemented in Unit.SpellDamageBonusDone + // This information for client side use only + // Get healing bonus for all schools + SetStatInt32Value(PlayerFields.ModHealingDonePos, (int)SpellBaseHealingBonusDone(SpellSchoolMask.All)); + // Get damage bonus for all schools + var modDamageAuras = GetAuraEffectsByType(AuraType.ModDamageDone); + for (var i = SpellSchools.Holy; i < SpellSchools.Max; ++i) + { + SetInt32Value(PlayerFields.ModDamageDoneNeg + (int)i, modDamageAuras.Aggregate(0, (negativeMod, aurEff) => + { + if (aurEff.GetAmount() < 0 && Convert.ToBoolean(aurEff.GetMiscValue() & (1 << (int)i))) + negativeMod += aurEff.GetAmount(); + return negativeMod; + })); + SetStatInt32Value(PlayerFields.ModDamageDonePos + (int)i, SpellBaseDamageBonusDone((SpellSchoolMask)(1 << (int)i)) - GetInt32Value(PlayerFields.ModDamageDoneNeg + (int)i)); + } + + if (HasAuraType(AuraType.OverrideAttackPowerBySpPct)) + { + UpdateAttackPowerAndDamage(); + UpdateAttackPowerAndDamage(true); + } + } + public uint GetBaseSpellPowerBonus() { return m_baseSpellPower; } + + public override void UpdateAttackPowerAndDamage(bool ranged = false) + { + float val2 = 0.0f; + float level = getLevel(); + + var entry = CliDB.ChrClassesStorage.LookupByKey(GetClass()); + UnitMods unitMod = ranged ? UnitMods.AttackPowerRanged : UnitMods.AttackPower; + + UnitFields index = UnitFields.AttackPower; + UnitFields index_mod = UnitFields.AttackPowerModPos; + UnitFields index_mult = UnitFields.AttackPowerMultiplier; + + if (ranged) + { + index = UnitFields.RangedAttackPower; + index_mod = UnitFields.RangedAttackPowerModPos; + index_mult = UnitFields.RangedAttackPowerMultiplier; + } + + if (!HasAuraType(AuraType.OverrideAttackPowerBySpPct)) + { + if (!ranged) + { + float strengthValue = Math.Max((GetStat(Stats.Strength)) * entry.AttackPowerPerStrength, 0.0f); + float agilityValue = Math.Max((GetStat(Stats.Agility)) * entry.AttackPowerPerAgility, 0.0f); + + var form = CliDB.SpellShapeshiftFormStorage.LookupByKey((uint)GetShapeshiftForm()); + // Directly taken from client, SHAPESHIFT_FLAG_AP_FROM_STRENGTH ? + if (form != null && Convert.ToBoolean((uint)form.Flags & 0x20)) + agilityValue += Math.Max(GetStat(Stats.Agility) * entry.AttackPowerPerStrength, 0.0f); + + val2 = strengthValue + agilityValue; + } + else + val2 = (level + Math.Max(GetStat(Stats.Agility), 0.0f)) * entry.RangedAttackPowerPerAgility; + } + else + { + int minSpellPower = GetInt32Value(PlayerFields.ModHealingDonePos); + for (var i = SpellSchools.Holy; i < SpellSchools.Max; ++i) + minSpellPower = Math.Min(minSpellPower, GetInt32Value(PlayerFields.ModDamageDonePos + (int)i)); + + val2 = MathFunctions.CalculatePct(minSpellPower, GetFloatValue(PlayerFields.OverrideApBySpellPowerPercent)); + } + + SetModifierValue(unitMod, UnitModifierType.BaseValue, val2); + + float base_attPower = GetModifierValue(unitMod, UnitModifierType.BaseValue) * GetModifierValue(unitMod, UnitModifierType.BasePCT); + float attPowerMod = GetModifierValue(unitMod, UnitModifierType.TotalValue); + float attPowerMultiplier = GetModifierValue(unitMod, UnitModifierType.TotalPCT) - 1.0f; + + //add dynamic flat mods + if (!ranged) + { + var mAPbyArmor = GetAuraEffectsByType(AuraType.ModAttackPowerOfArmor); + foreach (var iter in mAPbyArmor) + // always: ((*i).GetModifier().m_miscvalue == 1 == SPELL_SCHOOL_MASK_NORMAL) + attPowerMod += GetArmor() / iter.GetAmount(); + } + + SetUInt32Value(index, (uint)base_attPower); //UNIT_FIELD_(RANGED)_ATTACK_POWER field + SetUInt32Value(index_mod, (uint)attPowerMod); //UNIT_FIELD_(RANGED)_ATTACK_POWER_MOD_POS field + SetFloatValue(index_mult, attPowerMultiplier); //UNIT_FIELD_(RANGED)_ATTACK_POWER_MULTIPLIER field + + Pet pet = GetPet(); //update pet's AP + Guardian guardian = GetGuardianPet(); + //automatically update weapon damage after attack power modification + if (ranged) + { + UpdateDamagePhysical(WeaponAttackType.RangedAttack); + if (pet != null && pet.IsHunterPet()) // At ranged attack change for hunter pet + pet.UpdateAttackPowerAndDamage(); + } + else + { + UpdateDamagePhysical(WeaponAttackType.BaseAttack); + Item offhand = GetWeaponForAttack(WeaponAttackType.OffAttack, true); + if (offhand) + if (CanDualWield() || offhand.GetTemplate().GetFlags3().HasAnyFlag(ItemFlags3.AlwaysAllowDualWield)) + UpdateDamagePhysical(WeaponAttackType.OffAttack); + + if (HasAuraType(AuraType.ModSpellDamageOfAttackPower) || + HasAuraType(AuraType.ModSpellHealingOfAttackPower) || + HasAuraType(AuraType.OverrideSpellPowerByApPct)) + UpdateSpellDamageAndHealingBonus(); + + if (pet != null && pet.IsPetGhoul()) // At melee attack power change for DK pet + pet.UpdateAttackPowerAndDamage(); + + if (guardian != null && guardian.IsSpiritWolf()) // At melee attack power change for Shaman feral spirit + guardian.UpdateAttackPowerAndDamage(); + } + } + + public override void UpdateArmor() + { + UnitMods unitMod = UnitMods.Armor; + + float value = GetModifierValue(unitMod, UnitModifierType.BaseValue); // base armor (from items) + value *= GetModifierValue(unitMod, UnitModifierType.BasePCT); // armor percent from items + value += GetModifierValue(unitMod, UnitModifierType.TotalValue); + + //add dynamic flat mods + var mResbyIntellect = GetAuraEffectsByType(AuraType.ModResistanceOfStatPercent); + foreach (var i in mResbyIntellect) + { + if (Convert.ToBoolean(i.GetMiscValue() & (int)SpellSchoolMask.Normal)) + value += MathFunctions.CalculatePct(GetStat((Stats)i.GetMiscValueB()), i.GetAmount()); + } + + value *= GetModifierValue(unitMod, UnitModifierType.TotalPCT); + + SetArmor((int)value); + + Pet pet = GetPet(); + if (pet) + pet.UpdateArmor(); + + UpdateAttackPowerAndDamage(); // armor dependent auras update for SPELL_AURA_MOD_ATTACK_POWER_OF_ARMOR + } + + void _ApplyAllStatBonuses() + { + SetCanModifyStats(false); + + _ApplyAllAuraStatMods(); + _ApplyAllItemMods(); + + SetCanModifyStats(true); + + UpdateAllStats(); + } + void _RemoveAllStatBonuses() + { + SetCanModifyStats(false); + + _RemoveAllItemMods(); + _RemoveAllAuraStatMods(); + + SetCanModifyStats(true); + + UpdateAllStats(); + } + + void UpdateAllRatings() + { + for (CombatRating cr = 0; cr < CombatRating.Max; ++cr) + UpdateRating(cr); + } + public void UpdateRating(CombatRating cr) + { + int amount = baseRatingValue[(int)cr]; + // Apply bonus from SPELL_AURA_MOD_RATING_FROM_STAT + // stat used stored in miscValueB for this aura + var modRatingFromStat = GetAuraEffectsByType(AuraType.ModRatingFromStat); + foreach (var i in modRatingFromStat) + if (Convert.ToBoolean(i.GetMiscValue() & (1 << (int)cr))) + amount += (int)MathFunctions.CalculatePct(GetStat((Stats)i.GetMiscValueB()), i.GetAmount()); + + var modRatingPct = GetAuraEffectsByType(AuraType.ModRatingPct); + foreach (var i in modRatingPct) + if (Convert.ToBoolean(i.GetMiscValue() & (1 << (int)cr))) + amount += MathFunctions.CalculatePct(amount, i.GetAmount()); + + if (amount < 0) + amount = 0; + + uint oldRating = GetUInt32Value(PlayerFields.CombatRating1 + (int)cr); + SetUInt32Value(PlayerFields.CombatRating1 + (int)cr, (uint)amount); + + bool affectStats = CanModifyStats(); + + switch (cr) + { + case CombatRating.Amplify: + case CombatRating.DefenseSkill: + break; + case CombatRating.Dodge: + UpdateDodgePercentage(); + break; + case CombatRating.Parry: + UpdateParryPercentage(); + break; + case CombatRating.Block: + UpdateBlockPercentage(); + break; + case CombatRating.HitMelee: + UpdateMeleeHitChances(); + break; + case CombatRating.HitRanged: + UpdateRangedHitChances(); + break; + case CombatRating.HitSpell: + UpdateSpellHitChances(); + break; + case CombatRating.CritMelee: + if (affectStats) + { + UpdateCritPercentage(WeaponAttackType.BaseAttack); + UpdateCritPercentage(WeaponAttackType.OffAttack); + } + break; + case CombatRating.CritRanged: + if (affectStats) + UpdateCritPercentage(WeaponAttackType.RangedAttack); + break; + case CombatRating.CritSpell: + if (affectStats) + UpdateSpellCritChance(); + break; + case CombatRating.HasteMelee: + case CombatRating.HasteRanged: + case CombatRating.HasteSpell: + { + // explicit affected values + float multiplier = GetRatingMultiplier(cr); + float oldVal = oldRating * multiplier; + float newVal = amount * multiplier; + switch (cr) + { + case CombatRating.HasteMelee: + ApplyAttackTimePercentMod(WeaponAttackType.BaseAttack, oldVal, false); + ApplyAttackTimePercentMod(WeaponAttackType.OffAttack, oldVal, false); + ApplyAttackTimePercentMod(WeaponAttackType.BaseAttack, newVal, true); + ApplyAttackTimePercentMod(WeaponAttackType.OffAttack, newVal, true); + if (GetClass() == Class.Deathknight) + UpdateAllRunesRegen(); + break; + case CombatRating.HasteRanged: + ApplyAttackTimePercentMod(WeaponAttackType.RangedAttack, oldVal, false); + ApplyAttackTimePercentMod(WeaponAttackType.RangedAttack, newVal, true); + break; + case CombatRating.HasteSpell: + ApplyCastTimePercentMod(oldVal, false); + ApplyCastTimePercentMod(newVal, true); + break; + default: + break; + } + break; + } + case CombatRating.Expertise: + if (affectStats) + { + UpdateExpertise(WeaponAttackType.BaseAttack); + UpdateExpertise(WeaponAttackType.OffAttack); + } + break; + case CombatRating.ArmorPenetration: + if (affectStats) + UpdateArmorPenetration(amount); + break; + case CombatRating.Mastery: + UpdateMastery(); + break; + } + } + public void UpdateMastery() + { + if (!CanUseMastery()) + { + SetFloatValue(PlayerFields.Mastery, 0.0f); + return; + } + + float value = GetTotalAuraModifier(AuraType.Mastery); + value += GetRatingBonusValue(CombatRating.Mastery); + SetFloatValue(PlayerFields.Mastery, value); + + ChrSpecializationRecord chrSpec = CliDB.ChrSpecializationStorage.LookupByKey(GetUInt32Value(PlayerFields.CurrentSpecId)); + if (chrSpec == null) + return; + + for (uint i = 0; i < PlayerConst.MaxMasterySpells; ++i) + { + Aura aura = GetAura(chrSpec.MasterySpellID[i]); + if (aura != null) + { + foreach (SpellEffectInfo effect in aura.GetSpellEffectInfos()) + { + if (effect == null) + continue; + + float mult = effect.BonusCoefficient; + if (MathFunctions.fuzzyEq(mult, 0.0f)) + continue; + + aura.GetEffect(effect.EffectIndex).ChangeAmount((int)(value * mult)); + } + } + } + } + + void UpdateArmorPenetration(int amount) + { + // Store Rating Value + SetInt32Value(PlayerFields.CombatRating1 + (int)CombatRating.ArmorPenetration, amount); + } + public void UpdateParryPercentage() + { + float[] parry_cap = + { + 65.631440f, // Warrior + 65.631440f, // Paladin + 145.560408f, // Hunter + 145.560408f, // Rogue + 0.0f, // Priest + 65.631440f, // DK + 145.560408f, // Shaman + 0.0f, // Mage + 0.0f, // Warlock + 90.6425f, // Monk + 0.0f, // Druid + 65.631440f // Demon Hunter + }; + + // No parry + float value = 0.0f; + int pclass = (int)GetClass() - 1; + if (CanParry() && parry_cap[pclass] > 0.0f) + { + float nondiminishing = 5.0f; + // Parry from rating + float diminishing = GetRatingBonusValue(CombatRating.Parry); + // Parry from SPELL_AURA_MOD_PARRY_PERCENT aura + nondiminishing += GetTotalAuraModifier(AuraType.ModParryPercent); + // apply diminishing formula to diminishing parry chance + value = nondiminishing + diminishing * parry_cap[pclass] / (diminishing + parry_cap[pclass] * m_diminishing_k[pclass]); + + if (WorldConfig.GetBoolValue(WorldCfg.StatsLimitsEnable)) + value = value > WorldConfig.GetFloatValue(WorldCfg.StatsLimitsParry) ? WorldConfig.GetFloatValue(WorldCfg.StatsLimitsParry) : value; + + value = value < 0.0f ? 0.0f : value; + } + SetFloatValue(PlayerFields.ParryPercentage, value); + } + + public void UpdateDodgePercentage() + { + float[] dodge_cap = + { + 65.631440f, // Warrior + 65.631440f, // Paladin + 145.560408f, // Hunter + 145.560408f, // Rogue + 150.375940f, // Priest + 65.631440f, // DK + 145.560408f, // Shaman + 150.375940f, // Mage + 150.375940f, // Warlock + 145.560408f, // Monk + 116.890707f, // Druid + 145.560408f // Demon Hunter + }; + + float diminishing = 0.0f, nondiminishing = 0.0f; + GetDodgeFromAgility(diminishing, nondiminishing); + // Dodge from SPELL_AURA_MOD_DODGE_PERCENT aura + nondiminishing += GetTotalAuraModifier(AuraType.ModDodgePercent); + // Dodge from rating + diminishing += GetRatingBonusValue(CombatRating.Dodge); + // apply diminishing formula to diminishing dodge chance + int pclass = (int)GetClass() - 1; + float value = nondiminishing + (diminishing * dodge_cap[pclass] / (diminishing + dodge_cap[pclass] * m_diminishing_k[pclass])); + + if (WorldConfig.GetBoolValue(WorldCfg.StatsLimitsEnable)) + value = value > WorldConfig.GetFloatValue(WorldCfg.StatsLimitsDodge) ? WorldConfig.GetFloatValue(WorldCfg.StatsLimitsDodge) : value; + + value = value < 0.0f ? 0.0f : value; + SetStatFloatValue(PlayerFields.DodgePercentage, value); + } + public void UpdateBlockPercentage() + { + // No block + float value = 0.0f; + if (CanBlock()) + { + // Base value + value = 5.0f; + // Increase from SPELL_AURA_MOD_BLOCK_PERCENT aura + value += GetTotalAuraModifier(AuraType.ModBlockPercent); + // Increase from rating + value += GetRatingBonusValue(CombatRating.Block); + + if (WorldConfig.GetBoolValue(WorldCfg.StatsLimitsEnable)) + value = value > WorldConfig.GetFloatValue(WorldCfg.StatsLimitsBlock) ? WorldConfig.GetFloatValue(WorldCfg.StatsLimitsBlock) : value; + + value = value < 0.0f ? 0.0f : value; + } + SetFloatValue(PlayerFields.BlockPercentage, value); + } + + public void UpdateCritPercentage(WeaponAttackType attType) + { + BaseModGroup modGroup; + PlayerFields index; + CombatRating cr; + + switch (attType) + { + case WeaponAttackType.OffAttack: + modGroup = BaseModGroup.OffhandCritPercentage; + index = PlayerFields.OffhandCritPercentage; + cr = CombatRating.CritMelee; + break; + case WeaponAttackType.RangedAttack: + modGroup = BaseModGroup.RangedCritPercentage; + index = PlayerFields.RangedCritPercentage; + cr = CombatRating.CritRanged; + break; + case WeaponAttackType.BaseAttack: + default: + modGroup = BaseModGroup.CritPercentage; + index = PlayerFields.CritPercentage; + cr = CombatRating.CritMelee; + break; + } + + float value = GetTotalPercentageModValue(modGroup) + GetRatingBonusValue(cr); + // Modify crit from weapon skill and maximized defense skill of same level victim difference + value += GetMaxSkillValueForLevel() - GetMaxSkillValueForLevel() * 0.04f; + + if (WorldConfig.GetBoolValue(WorldCfg.StatsLimitsEnable)) + value = value > WorldConfig.GetFloatValue(WorldCfg.StatsLimitsCrit) ? WorldConfig.GetFloatValue(WorldCfg.StatsLimitsCrit) : value; + + value = value < 0.0f ? 0.0f : value; + SetFloatValue(index, value); + } + + public void UpdateExpertise(WeaponAttackType attack) + { + if (attack == WeaponAttackType.RangedAttack) + return; + + int expertise = (int)GetRatingBonusValue(CombatRating.Expertise); + + Item weapon = GetWeaponForAttack(attack, true); + + var expAuras = GetAuraEffectsByType(AuraType.ModExpertise); + foreach (var eff in expAuras) + { + // item neutral spell + if ((int)eff.GetSpellInfo().EquippedItemClass == -1) + expertise += eff.GetAmount(); + // item dependent spell + else if (weapon != null && weapon.IsFitToSpellRequirements(eff.GetSpellInfo())) + expertise += eff.GetAmount(); + } + + if (expertise < 0) + expertise = 0; + + switch (attack) + { + case WeaponAttackType.BaseAttack: + SetInt32Value(PlayerFields.Expertise, expertise); + break; + case WeaponAttackType.OffAttack: + SetInt32Value(PlayerFields.OffhandExpertise, expertise); + break; + default: break; + } + } + + float GetGameTableColumnForCombatRating(GtCombatRatingsRecord row, CombatRating rating) + { + switch (rating) + { + case CombatRating.Amplify: + return row.Amplify; + case CombatRating.DefenseSkill: + return row.DefenseSkill; + case CombatRating.Dodge: + return row.Dodge; + case CombatRating.Parry: + return row.Parry; + case CombatRating.Block: + return row.Block; + case CombatRating.HitMelee: + return row.HitMelee; + case CombatRating.HitRanged: + return row.HitRanged; + case CombatRating.HitSpell: + return row.HitSpell; + case CombatRating.CritMelee: + return row.CritMelee; + case CombatRating.CritRanged: + return row.CritRanged; + case CombatRating.CritSpell: + return row.CritSpell; + case CombatRating.Multistrike: + return row.MultiStrike; + case CombatRating.Readiness: + return row.Readiness; + case CombatRating.Speed: + return row.Speed; + case CombatRating.ResilienceCritTaken: + return row.ResilienceCritTaken; + case CombatRating.ResiliencePlayerDamage: + return row.ResiliencePlayerDamage; + case CombatRating.Lifesteal: + return row.Lifesteal; + case CombatRating.HasteMelee: + return row.HasteMelee; + case CombatRating.HasteRanged: + return row.HasteRanged; + case CombatRating.HasteSpell: + return row.HasteSpell; + case CombatRating.Avoidance: + return row.Avoidance; + case CombatRating.Studiness: + return row.Sturdiness; + case CombatRating.Unused7: + return row.Unused7; + case CombatRating.Expertise: + return row.Expertise; + case CombatRating.ArmorPenetration: + return row.ArmorPenetration; + case CombatRating.Mastery: + return row.Mastery; + case CombatRating.PvpPower: + return row.PvPPower; + case CombatRating.Cleave: + return row.Cleave; + case CombatRating.VersatilityDamageDone: + return row.VersatilityDamageDone; + case CombatRating.VersatilityHealingDone: + return row.VersatilityHealingDone; + case CombatRating.VersatilityDamageTaken: + return row.VersatilityDamageTaken; + case CombatRating.Unused12: + return row.Unused12; + default: + break; + } + return 1.0f; + } + + public void UpdateSpellCritChance() + { + // For others recalculate it from: + float crit = 5.0f; + // Increase crit from SPELL_AURA_MOD_SPELL_CRIT_CHANCE + crit += GetTotalAuraModifier(AuraType.ModSpellCritChance); + // Increase crit from SPELL_AURA_MOD_CRIT_PCT + crit += GetTotalAuraModifier(AuraType.ModCritPct); + // Increase crit from spell crit ratings + crit += GetRatingBonusValue(CombatRating.CritSpell); + + // Store crit value + SetFloatValue(PlayerFields.SpellCritPercentage1, crit); + } + + public void UpdateMeleeHitChances() + { + m_modMeleeHitChance = 7.5f + GetTotalAuraModifier(AuraType.ModHitChance); + m_modMeleeHitChance += GetRatingBonusValue(CombatRating.HitMelee); + } + + public void UpdateRangedHitChances() + { + m_modRangedHitChance = 7.5f + GetTotalAuraModifier(AuraType.ModHitChance); + m_modRangedHitChance += GetRatingBonusValue(CombatRating.HitRanged); + } + + public void UpdateSpellHitChances() + { + m_modSpellHitChance = 15.0f + GetTotalAuraModifier(AuraType.ModSpellHitChance); + m_modSpellHitChance += GetRatingBonusValue(CombatRating.HitSpell); + } + public override void UpdateMaxHealth() + { + UnitMods unitMod = UnitMods.Health; + + float value = GetModifierValue(unitMod, UnitModifierType.BaseValue) + GetCreateHealth(); + value *= GetModifierValue(unitMod, UnitModifierType.BasePCT); + value += GetModifierValue(unitMod, UnitModifierType.TotalValue) + GetHealthBonusFromStamina(); + value *= GetModifierValue(unitMod, UnitModifierType.TotalPCT); + + SetMaxHealth((uint)value); + } + float GetHealthBonusFromStamina() + { + // Taken from PaperDollFrame.lua - 6.0.3.19085 + float ratio = 10.0f; + GtHpPerStaRecord hpBase = CliDB.HpPerStaGameTable.GetRow(getLevel()); + if (hpBase != null) + ratio = hpBase.Health; + + float stamina = GetStat(Stats.Stamina); + + return stamina * ratio; + } + public override void UpdateMaxPower(PowerType power) + { + UnitMods unitMod = UnitMods.PowerStart + (int)power; + + float value = GetModifierValue(unitMod, UnitModifierType.BaseValue) + GetCreatePowers(power); + value *= GetModifierValue(unitMod, UnitModifierType.BasePCT); + value += GetModifierValue(unitMod, UnitModifierType.TotalValue); + value *= GetModifierValue(unitMod, UnitModifierType.TotalPCT); + + SetMaxPower(power, (int)value); + } + + public void ApplySpellPenetrationBonus(int amount, bool apply) + { + ApplyModInt32Value(PlayerFields.ModTargetResistance, -amount, apply); + m_spellPenetrationItemMod += apply ? amount : -amount; + } + + void ApplyManaRegenBonus(int amount, bool apply) + { + _ModifyUInt32(apply, ref m_baseManaRegen, ref amount); + UpdateManaRegen(); + } + + void ApplyHealthRegenBonus(int amount, bool apply) + { + _ModifyUInt32(apply, ref m_baseHealthRegen, ref amount); + } + + void ApplySpellPowerBonus(int amount, bool apply) + { + if (HasAuraType(AuraType.OverrideSpellPowerByApPct)) + return; + + apply = _ModifyUInt32(apply, ref m_baseSpellPower, ref amount); + + // For speed just update for client + ApplyModUInt32Value(PlayerFields.ModHealingDonePos, amount, apply); + for (int i = (int)SpellSchools.Holy; i < (int)SpellSchools.Max; ++i) + ApplyModUInt32Value(PlayerFields.ModDamageDonePos + i, amount, apply); + + if (HasAuraType(AuraType.OverrideAttackPowerBySpPct)) + { + UpdateAttackPowerAndDamage(); + UpdateAttackPowerAndDamage(true); + } + } + + public bool _ModifyUInt32(bool apply, ref uint baseValue, ref int amount) + { + // If amount is negative, change sign and value of apply. + if (amount < 0) + { + apply = !apply; + amount = -amount; + } + if (apply) + baseValue += (uint)amount; + else + { + // Make sure we do not get public uint overflow. + if (amount > baseValue) + amount = (int)baseValue; + baseValue -= (uint)amount; + } + return apply; + } + + float[] m_diminishing_k = + { + 0.9560f, // Warrior + 0.9560f, // Paladin + 0.9880f, // Hunter + 0.9880f, // Rogue + 0.9830f, // Priest + 0.9560f, // DK + 0.9880f, // Shaman + 0.9830f, // Mage + 0.9830f, // Warlock + 0.9830f, // Monk + 0.9720f, // Druid + 0.9830f // Demon Hunter + }; + + void SetBaseModValue(BaseModGroup modGroup, BaseModType modType, float value) { m_auraBaseMod[(int)modGroup][(int)modType] = value; } + } + + public partial class Creature + { + public override bool UpdateStats(Stats stat) + { + return true; + } + + public override bool UpdateAllStats() + { + UpdateMaxHealth(); + UpdateAttackPowerAndDamage(); + UpdateAttackPowerAndDamage(true); + + for (var i = PowerType.Mana; i < PowerType.Max; ++i) + UpdateMaxPower(i); + + UpdateAllResistances(); + + return true; + } + + public override void UpdateResistances(SpellSchools school) + { + if (school > SpellSchools.Normal) + { + float value = GetTotalAuraModValue(UnitMods.ResistanceStart + (int)school); + SetResistance(school, (int)value); + } + else + UpdateArmor(); + } + + public override void UpdateArmor() + { + float value = GetTotalAuraModValue(UnitMods.Armor); + SetArmor((int)value); + } + + public override void UpdateMaxHealth() + { + float value = GetTotalAuraModValue(UnitMods.Health); + SetMaxHealth((uint)value); + } + + public override void UpdateMaxPower(PowerType power) + { + UnitMods unitMod = UnitMods.PowerStart + (int)power; + + float value = GetTotalAuraModValue(unitMod); + SetMaxPower(power, (int)value); + } + + public override void UpdateAttackPowerAndDamage(bool ranged = false) + { + UnitMods unitMod = ranged ? UnitMods.AttackPowerRanged : UnitMods.AttackPower; + + UnitFields index = UnitFields.AttackPower; + UnitFields index_mult = UnitFields.AttackPowerMultiplier; + + if (ranged) + { + index = UnitFields.RangedAttackPower; + index_mult = UnitFields.RangedAttackPowerMultiplier; + } + + float base_attPower = GetModifierValue(unitMod, UnitModifierType.BaseValue) * GetModifierValue(unitMod, UnitModifierType.BasePCT); + float attPowerMultiplier = GetModifierValue(unitMod, UnitModifierType.TotalPCT) - 1.0f; + + SetInt32Value(index, (int)base_attPower); //UNIT_FIELD_(RANGED)_ATTACK_POWER field + SetFloatValue(index_mult, attPowerMultiplier); //UNIT_FIELD_(RANGED)_ATTACK_POWER_MULTIPLIER field + + //automatically update weapon damage after attack power modification + if (ranged) + UpdateDamagePhysical(WeaponAttackType.RangedAttack); + else + { + UpdateDamagePhysical(WeaponAttackType.BaseAttack); + UpdateDamagePhysical(WeaponAttackType.OffAttack); + } + } + + public override void CalculateMinMaxDamage(WeaponAttackType attType, bool normalized, bool addTotalPct, out float minDamage, out float maxDamage) + { + float variance = 1.0f; + UnitMods unitMod; + switch (attType) + { + case WeaponAttackType.BaseAttack: + default: + variance = GetCreatureTemplate().BaseVariance; + unitMod = UnitMods.DamageMainHand; + break; + case WeaponAttackType.OffAttack: + variance = GetCreatureTemplate().BaseVariance; + unitMod = UnitMods.DamageOffHand; + break; + case WeaponAttackType.RangedAttack: + variance = GetCreatureTemplate().RangeVariance; + unitMod = UnitMods.DamageRanged; + break; + } + + if (attType == WeaponAttackType.OffAttack && !haveOffhandWeapon()) + { + minDamage = 0.0f; + maxDamage = 0.0f; + return; + } + + float weaponMinDamage = GetWeaponDamageRange(attType, WeaponDamageRange.MinDamage); + float weaponMaxDamage = GetWeaponDamageRange(attType, WeaponDamageRange.MaxDamage); + + if (!CanUseAttackType(attType)) // disarm case + { + weaponMinDamage = 0.0f; + weaponMaxDamage = 0.0f; + } + + float attackPower = GetTotalAttackPowerValue(attType); + float attackSpeedMulti = Math.Max(GetAPMultiplier(attType, normalized), 0.25f); + + float baseValue = GetModifierValue(unitMod, UnitModifierType.BaseValue) + (attackPower / 3.5f) * variance; + float basePct = GetModifierValue(unitMod, UnitModifierType.BasePCT) * attackSpeedMulti; + float totalValue = GetModifierValue(unitMod, UnitModifierType.TotalValue); + float totalPct = addTotalPct ? GetModifierValue(unitMod, UnitModifierType.TotalPCT) : 1.0f; + float dmgMultiplier = GetCreatureTemplate().ModDamage; // = ModDamage * _GetDamageMod(rank); + + minDamage = ((weaponMinDamage + baseValue) * dmgMultiplier * basePct + totalValue) * totalPct; + maxDamage = ((weaponMaxDamage + baseValue) * dmgMultiplier * basePct + totalValue) * totalPct; + } + } +} diff --git a/Game/Entities/Taxi/Graph.cs b/Game/Entities/Taxi/Graph.cs new file mode 100644 index 000000000..170b5e85f --- /dev/null +++ b/Game/Entities/Taxi/Graph.cs @@ -0,0 +1,515 @@ +/* + * Copyright (C) 2012-2017 CypherCore + * + * 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, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +using System; +using System.Collections.Generic; +using System.Text; + +namespace Game.Entities +{ + /// + /// The IndexMinPriorityQueue class represents an indexed priority queue of generic keys. + /// + /// IndexMinPQ class from Princeton University's Java Algorithms + /// Type must implement IComparable interface + public class IndexMinPriorityQueue where T : IComparable + { + private readonly T[] _keys; + private readonly int _maxSize; + private readonly int[] _pq; + private readonly int[] _qp; + /// + /// Constructs an empty indexed priority queue with indices between 0 and the specified maxSize - 1 + /// + /// The maximum size of the indexed priority queue + public IndexMinPriorityQueue(int maxSize) + { + _maxSize = maxSize; + Size = 0; + _keys = new T[_maxSize + 1]; + _pq = new int[_maxSize + 1]; + _qp = new int[_maxSize + 1]; + for (int i = 0; i < _maxSize; i++) + { + _qp[i] = -1; + } + } + /// + /// The number of keys on this indexed priority queue + /// + public int Size { get; private set; } + /// + /// Is the indexed priority queue empty? + /// + /// True if the indexed priority queue is empty, false otherwise + public bool IsEmpty() + { + return Size == 0; + } + /// + /// Is the specified parameter i an index on the priority queue? + /// + /// An index to check for on the priority queue + /// True if the specified parameter i is an index on the priority queue, false otherwise + public bool Contains(int i) + { + return _qp[i] != -1; + } + /// + /// Associates the specified key with the specified index + /// + /// The index to associate the key with + /// The key to associate with the index + public void Insert(int index, T key) + { + Size++; + _qp[index] = Size; + _pq[Size] = index; + _keys[index] = key; + Swim(Size); + } + /// + /// Returns an index associated with a minimum key + /// + /// An index associated with a minimum key + public int MinIndex() + { + return _pq[1]; + } + /// + /// Returns a minimum key + /// + /// A minimum key + public T MinKey() + { + return _keys[_pq[1]]; + } + /// + /// Removes a minimum key and returns its associated index + /// + /// An index associated with a minimum key that was removed + public int DeleteMin() + { + int min = _pq[1]; + Exchange(1, Size--); + Sink(1); + _qp[min] = -1; + _keys[_pq[Size + 1]] = default(T); + _pq[Size + 1] = -1; + return min; + } + /// + /// Returns the key associated with the specified index + /// + /// The index of the key to return + /// The key associated with the specified index + public T KeyAt(int index) + { + return _keys[index]; + } + /// + /// Change the key associated with the specified index to the specified value + /// + /// The index of the key to change + /// Change the key associated with the specified index to this key + public void ChangeKey(int index, T key) + { + _keys[index] = key; + Swim(_qp[index]); + Sink(_qp[index]); + } + /// + /// Decrease the key associated with the specified index to the specified value + /// + /// The index of the key to decrease + /// Decrease the key associated with the specified index to this key + public void DecreaseKey(int index, T key) + { + _keys[index] = key; + Swim(_qp[index]); + } + /// + /// Increase the key associated with the specified index to the specified value + /// + /// The index of the key to increase + /// Increase the key associated with the specified index to this key + public void IncreaseKey(int index, T key) + { + _keys[index] = key; + Sink(_qp[index]); + } + /// + /// Remove the key associated with the specified index + /// + /// The index of the key to remove + public void Delete(int index) + { + int i = _qp[index]; + Exchange(i, Size--); + Swim(i); + Sink(i); + _keys[index] = default(T); + _qp[index] = -1; + } + private bool Greater(int i, int j) + { + return _keys[_pq[i]].CompareTo(_keys[_pq[j]]) > 0; + } + private void Exchange(int i, int j) + { + int swap = _pq[i]; + _pq[i] = _pq[j]; + _pq[j] = swap; + _qp[_pq[i]] = i; + _qp[_pq[j]] = j; + } + private void Swim(int k) + { + while (k > 1 && Greater(k / 2, k)) + { + Exchange(k, k / 2); + k = k / 2; + } + } + private void Sink(int k) + { + while (2 * k <= Size) + { + int j = 2 * k; + if (j < Size && Greater(j, j + 1)) + { + j++; + } + if (!Greater(k, j)) + { + break; + } + Exchange(k, j); + k = j; + } + } + } + + /// + /// The EdgeWeightedDigrpah class represents an edge-weighted directed graph of vertices named 0 through V-1, where each directed edge + /// is of type DirectedEdge and has real-valued weight. + /// + /// EdgeWeightedDigraph class from Princeton University's Java Algorithms + public class EdgeWeightedDigraph + { + private readonly LinkedList[] _adjacent; + /// + /// Constructs an empty edge-weighted digraph with the specified number of vertices and 0 edges + /// + /// Number of vertices in the Graph + public EdgeWeightedDigraph(int vertices) + { + NumberOfVertices = vertices; + NumberOfEdges = 0; + _adjacent = new LinkedList[NumberOfVertices]; + for (int v = 0; v < NumberOfVertices; v++) + { + _adjacent[v] = new LinkedList(); + } + } + /// + /// The number of vertices in the edge-weighted digraph + /// + public int NumberOfVertices { get; private set; } + /// + /// The number of edges in the edge-weighted digraph + /// + public int NumberOfEdges { get; private set; } + /// + /// Adds the specified directed edge to the edge-weighted digraph + /// + /// The DirectedEdge to add + /// DirectedEdge cannot be null + public void AddEdge(DirectedEdge edge) + { + if (edge == null) + { + throw new ArgumentNullException("edge", "DirectedEdge cannot be null"); + } + + _adjacent[edge.From].AddLast(edge); + } + /// + /// Returns an IEnumerable of the DirectedEdges incident from the specified vertex + /// + /// The vertex to find incident DirectedEdges from + /// IEnumerable of the DirectedEdges incident from the specified vertex + public IEnumerable Adjacent(int vertex) + { + return _adjacent[vertex]; + } + /// + /// Returns an IEnumerable of all directed edges in the edge-weighted digraph + /// + /// IEnumerable of of all directed edges in the edge-weighted digraph + public IEnumerable Edges() + { + for (int v = 0; v < NumberOfVertices; v++) + { + foreach (DirectedEdge edge in _adjacent[v]) + { + yield return edge; + } + } + } + /// + /// Returns the number of directed edges incident from the specified vertex + /// This is known as the outdegree of the vertex + /// + /// The vertex to find find the outdegree of + /// The number of directed edges incident from the specified vertex + public int OutDegree(int vertex) + { + return _adjacent[vertex].Count; + } + /// + /// Returns a string that represents the current edge-weighted digraph + /// + /// + /// A string that represents the current edge-weighted digraph + /// + public override string ToString() + { + var formattedString = new StringBuilder(); + formattedString.AppendFormat("{0} vertices, {1} edges {2}", NumberOfVertices, NumberOfEdges, Environment.NewLine); + for (int v = 0; v < NumberOfVertices; v++) + { + formattedString.AppendFormat("{0}: ", v); + foreach (DirectedEdge edge in _adjacent[v]) + { + formattedString.AppendFormat("{0} ", edge.To); + } + formattedString.AppendLine(); + } + return formattedString.ToString(); + } + } + + /// + /// The DirectedEdge class represents a weighted edge in an edge-weighted directed graph. + /// + /// DirectedEdge class from Princeton University's Java Algorithms + public class DirectedEdge + { + /// + /// Constructs a directed edge from one specified vertex to another with the given weight + /// + /// The start vertex + /// The destination vertex + /// The weight of the DirectedEdge + public DirectedEdge(uint from, uint to, double weight) + { + From = from; + To = to; + Weight = weight; + } + /// + /// Returns the destination vertex of the DirectedEdge + /// + public uint From { get; private set; } + /// + /// Returns the start vertex of the DirectedEdge + /// + public uint To { get; private set; } + /// + /// Returns the weight of the DirectedEdge + /// + public double Weight { get; private set; } + /// + /// Returns a string that represents the current DirectedEdge + /// + /// + /// A string that represents the current DirectedEdge + /// + public override string ToString() + { + return string.Format("From: {0}, To: {1}, Weight: {2}", From, To, Weight); + } + } + + /// + /// The DijkstraShortestPath class represents a data type for solving the single-source shortest paths problem + /// in edge-weighted digraphs where the edge weights are non-negative + /// + /// DijkstraSP class from Princeton University's Java Algorithms + public class DijkstraShortestPath + { + private readonly double[] _distanceTo; + private readonly DirectedEdge[] _edgeTo; + private readonly IndexMinPriorityQueue _priorityQueue; + /// + /// Computes a shortest paths tree from the specified sourceVertex to every other vertex in the edge-weighted directed graph + /// + /// The edge-weighted directed graph + /// The source vertex to compute the shortest paths tree from + /// Throws an ArgumentOutOfRangeException if an edge weight is negative + /// Thrown if EdgeWeightedDigraph is null + public DijkstraShortestPath(EdgeWeightedDigraph graph, int sourceVertex) + { + if (graph == null) + { + throw new ArgumentNullException("graph", "EdgeWeightedDigraph cannot be null"); + } + + foreach (DirectedEdge edge in graph.Edges()) + { + if (edge.Weight < 0) + { + throw new ArgumentOutOfRangeException(string.Format("Edge: '{0}' has negative weight", edge)); + } + } + + _distanceTo = new double[graph.NumberOfVertices]; + _edgeTo = new DirectedEdge[graph.NumberOfVertices]; + for (int v = 0; v < graph.NumberOfVertices; v++) + { + _distanceTo[v] = double.PositiveInfinity; + } + _distanceTo[sourceVertex] = 0.0; + + _priorityQueue = new IndexMinPriorityQueue(graph.NumberOfVertices); + _priorityQueue.Insert(sourceVertex, _distanceTo[sourceVertex]); + while (!_priorityQueue.IsEmpty()) + { + int v = _priorityQueue.DeleteMin(); + foreach (DirectedEdge edge in graph.Adjacent(v)) + { + Relax(edge); + } + } + } + private void Relax(DirectedEdge edge) + { + uint v = edge.From; + uint w = edge.To; + if (_distanceTo[w] > _distanceTo[v] + edge.Weight) + { + _distanceTo[w] = _distanceTo[v] + edge.Weight; + _edgeTo[w] = edge; + if (_priorityQueue.Contains((int)w)) + { + _priorityQueue.DecreaseKey((int)w, _distanceTo[w]); + } + else + { + _priorityQueue.Insert((int)w, _distanceTo[w]); + } + } + } + /// + /// Returns the length of a shortest path from the sourceVertex to the specified destinationVertex + /// + /// The destination vertex to find a shortest path to + /// The length of a shortest path from the sourceVertex to the specified destinationVertex or double.PositiveInfinity if no such path exists + public double DistanceTo(int destinationVertex) + { + return _distanceTo[destinationVertex]; + } + /// + /// Is there a path from the sourceVertex to the specified destinationVertex? + /// + /// The destination vertex to see if there is a path to + /// True if there is a path from the sourceVertex to the specified destinationVertex, false otherwise + public bool HasPathTo(int destinationVertex) + { + return _distanceTo[destinationVertex] < double.PositiveInfinity; + } + /// + /// Returns an IEnumerable of DirectedEdges representing a shortest path from the sourceVertex to the specified destinationVertex + /// + /// The destination vertex to find a shortest path to + /// IEnumerable of DirectedEdges representing a shortest path from the sourceVertex to the specified destinationVertex + public IEnumerable PathTo(int destinationVertex) + { + if (!HasPathTo(destinationVertex)) + { + return null; + } + var path = new Stack(); + for (DirectedEdge edge = _edgeTo[destinationVertex]; edge != null; edge = _edgeTo[edge.From]) + { + path.Push(edge); + } + return path; + } + // TODO: This method should be private and should be called from the bottom of the constructor + /// + /// check optimality conditions: + /// + /// The edge-weighted directed graph + /// The source vertex to check optimality conditions from + /// True if all optimality conditions are met, false otherwise + /// Thrown on null EdgeWeightedDigraph + public bool Check(EdgeWeightedDigraph graph, int sourceVertex) + { + if (graph == null) + { + throw new ArgumentNullException("graph", "EdgeWeightedDigraph cannot be null"); + } + + if (_distanceTo[sourceVertex] != 0.0 || _edgeTo[sourceVertex] != null) + { + return false; + } + for (int v = 0; v < graph.NumberOfVertices; v++) + { + if (v == sourceVertex) + { + continue; + } + if (_edgeTo[v] == null && _distanceTo[v] != double.PositiveInfinity) + { + return false; + } + } + for (int v = 0; v < graph.NumberOfVertices; v++) + { + foreach (DirectedEdge edge in graph.Adjacent(v)) + { + uint w = edge.To; + if (_distanceTo[v] + edge.Weight < _distanceTo[w]) + { + return false; + } + } + } + for (int w = 0; w < graph.NumberOfVertices; w++) + { + if (_edgeTo[w] == null) + { + continue; + } + DirectedEdge edge = _edgeTo[w]; + uint v = edge.From; + if (w != edge.To) + { + return false; + } + if (_distanceTo[v] + edge.Weight != _distanceTo[w]) + { + return false; + } + } + return true; + } + } +} diff --git a/Game/Entities/Taxi/TaxiPathGraph.cs b/Game/Entities/Taxi/TaxiPathGraph.cs new file mode 100644 index 000000000..140976b50 --- /dev/null +++ b/Game/Entities/Taxi/TaxiPathGraph.cs @@ -0,0 +1,187 @@ +/* + * Copyright (C) 2012-2017 CypherCore + * + * 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, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +using Framework.Constants; +using Framework.GameMath; +using Game.DataStorage; +using System; +using System.Collections.Generic; + +namespace Game.Entities +{ + public class TaxiPathGraph : Singleton + { + TaxiPathGraph() { } + + public void Initialize() + { + if (GetVertexCount() > 0) + return; + + List, uint>> edges = new List, uint>>(); + + // Initialize here + foreach (TaxiPathRecord path in CliDB.TaxiPathStorage.Values) + { + TaxiNodesRecord from = CliDB.TaxiNodesStorage.LookupByKey(path.From); + TaxiNodesRecord to = CliDB.TaxiNodesStorage.LookupByKey(path.To); + if (from != null && to != null && from.Flags.HasAnyFlag(TaxiNodeFlags.Alliance | TaxiNodeFlags.Horde) && to.Flags.HasAnyFlag(TaxiNodeFlags.Alliance | TaxiNodeFlags.Horde)) + AddVerticeAndEdgeFromNodeInfo(from, to, path.Id, edges); + } + + // create graph + m_graph = new EdgeWeightedDigraph(GetVertexCount()); + + for (int j = 0; j < edges.Count; ++j) + { + m_graph.AddEdge(new DirectedEdge(edges[j].Item1.Item1, edges[j].Item1.Item2, edges[j].Item2)); + } + } + + uint GetNodeIDFromVertexID(uint vertexID) + { + if (vertexID < m_vertices.Length) + return m_vertices[vertexID].Id; + + return uint.MaxValue; + } + + uint GetVertexIDFromNodeID(TaxiNodesRecord node) + { + return node.LearnableIndex; + } + + int GetVertexCount() + { + if (m_graph == null) + return m_vertices.Length; + + //So we can use this function for readability, we define either max defined vertices or already loaded in graph count + return m_vertices.Length;// Math.Max(m_graph.getNumberOfVertices(), m_vertices.Length); + } + + void AddVerticeAndEdgeFromNodeInfo(TaxiNodesRecord from, TaxiNodesRecord to, uint pathId, List, uint>> edges) + { + if (from.Id != to.Id) + { + uint fromVertexID = CreateVertexFromFromNodeInfoIfNeeded(from); + uint toVertexID = CreateVertexFromFromNodeInfoIfNeeded(to); + + float totalDist = 0.0f; + TaxiPathNodeRecord[] nodes = CliDB.TaxiPathNodesByPath[pathId]; + if (nodes.Length < 2) + { + edges.Add(Tuple.Create(Tuple.Create(fromVertexID, toVertexID), 0xFFFFu)); + return; + } + + int last = nodes.Length; + int first = 0; + if (nodes.Length > 2) + { + --last; + ++first; + } + + for (int i = first + 1; i < last; ++i) + { + if (nodes[i - 1].Flags.HasAnyFlag(TaxiPathNodeFlags.Teleport)) + continue; + + uint map1, map2; + Vector2 pos1, pos2; + + Global.DB2Mgr.DeterminaAlternateMapPosition(nodes[i - 1].MapID, nodes[i - 1].Loc.X, nodes[i - 1].Loc.Y, nodes[i - 1].Loc.Z, out map1, out pos1); + Global.DB2Mgr.DeterminaAlternateMapPosition(nodes[i].MapID, nodes[i].Loc.X, nodes[i].Loc.Y, nodes[i].Loc.Z, out map2, out pos2); + + if (map1 != map2) + continue; + + totalDist += (float)Math.Sqrt((float)Math.Pow(pos2.X - pos1.X, 2) + (float)Math.Pow(pos2.Y - pos1.Y, 2) + (float)Math.Pow(nodes[i].Loc.Z - nodes[i - 1].Loc.Z, 2)); + } + + uint dist = (uint)totalDist; + if (dist > 0xFFFF) + dist = 0xFFFF; + + edges.Add(Tuple.Create(Tuple.Create(fromVertexID, toVertexID), dist)); + } + } + + public int GetCompleteNodeRoute(TaxiNodesRecord from, TaxiNodesRecord to, Player player, List shortestPath) + { + /* + Information about node algorithm from client + Since client does not give information about *ALL* nodes you have to pass by when going from sourceNodeID to destinationNodeID, we need to use Dijkstra algorithm. + Examining several paths I discovered the following algorithm: + * If destinationNodeID has is the next destination, connected directly to sourceNodeID, then, client just pick up this route regardless of distance + * else we use dijkstra to find the shortest path. + * When early landing is requested, according to behavior on retail, you can never end in a node you did not discovered before + */ + + // Find if we have a direct path + uint pathId, goldCost; + Global.ObjectMgr.GetTaxiPath(from.Id, to.Id, out pathId, out goldCost); + if (pathId != 0) + { + shortestPath.Add(from.Id); + shortestPath.Add(to.Id); + } + else + { + shortestPath.Clear(); + // We want to use Dijkstra on this graph + DijkstraShortestPath g = new DijkstraShortestPath(m_graph, (int)GetVertexIDFromNodeID(from)); + var path = g.PathTo((int)GetVertexIDFromNodeID(to)); + // found a path to the goal + shortestPath.Add(from.Id); + foreach (var edge in path) + { + //todo test me No clue about this.... + var To = m_vertices[edge.To]; + TaxiNodeFlags requireFlag = (player.GetTeam() == Team.Alliance) ? TaxiNodeFlags.Alliance : TaxiNodeFlags.Horde; + if (!To.Flags.HasAnyFlag(requireFlag)) + continue; + + PlayerConditionRecord condition = CliDB.PlayerConditionStorage.LookupByKey(To.ConditionID); + if (condition != null) + if (!ConditionManager.IsPlayerMeetingCondition(player, condition)) + continue; + + shortestPath.Add(GetNodeIDFromVertexID(edge.To)); + } + } + + return shortestPath.Count; + } + + uint CreateVertexFromFromNodeInfoIfNeeded(TaxiNodesRecord node) + { + //Check if we need a new one or if it may be already created + if (m_vertices.Length <= node.LearnableIndex) + Array.Resize(ref m_vertices, (int)node.LearnableIndex + 1); + + m_vertices[node.LearnableIndex] = node; + return node.LearnableIndex; + } + + TaxiNodesRecord[] m_vertices = new TaxiNodesRecord[0]; + EdgeWeightedDigraph m_graph; + } +} + + diff --git a/Game/Entities/TemporarySummon.cs b/Game/Entities/TemporarySummon.cs new file mode 100644 index 000000000..496e255eb --- /dev/null +++ b/Game/Entities/TemporarySummon.cs @@ -0,0 +1,1044 @@ +/* + * Copyright (C) 2012-2017 CypherCore + * + * 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, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +using Framework.Constants; +using Framework.Dynamic; +using Game.DataStorage; +using System.Collections.Generic; +using System.Diagnostics.Contracts; + +namespace Game.Entities +{ + public class TempSummon : Creature + { + public TempSummon(SummonPropertiesRecord properties, Unit owner, bool isWorldObject) : base(isWorldObject) + { + m_Properties = properties; + m_type = TempSummonType.ManualDespawn; + + m_summonerGUID = owner != null ? owner.GetGUID() : ObjectGuid.Empty; + m_unitTypeMask |= UnitTypeMask.Summon; + } + + public Unit GetSummoner() + { + return !m_summonerGUID.IsEmpty() ? Global.ObjAccessor.GetUnit(this, m_summonerGUID) : null; + } + + public Creature GetSummonerCreatureBase() + { + return !m_summonerGUID.IsEmpty() ? ObjectAccessor.GetCreature(this, m_summonerGUID) : null; + } + + public override void Update(uint diff) + { + base.Update(diff); + + if (m_deathState == DeathState.Dead) + { + UnSummon(); + return; + } + switch (m_type) + { + case TempSummonType.ManualDespawn: + break; + case TempSummonType.TimedDespawn: + { + if (m_timer <= diff) + { + UnSummon(); + return; + } + + m_timer -= diff; + break; + } + case TempSummonType.TimedDespawnOOC: + { + if (!IsInCombat()) + { + if (m_timer <= diff) + { + UnSummon(); + return; + } + + m_timer -= diff; + } + else if (m_timer != m_lifetime) + m_timer = m_lifetime; + + break; + } + + case TempSummonType.CorpseTimedDespawn: + { + if (m_deathState == DeathState.Corpse) + { + if (m_timer <= diff) + { + UnSummon(); + return; + } + + m_timer -= diff; + } + break; + } + case TempSummonType.CorpseDespawn: + { + // if m_deathState is DEAD, CORPSE was skipped + if (m_deathState == DeathState.Corpse || m_deathState == DeathState.Dead) + { + UnSummon(); + return; + } + + break; + } + case TempSummonType.DeadDespawn: + { + if (m_deathState == DeathState.Dead) + { + UnSummon(); + return; + } + break; + } + case TempSummonType.TimedOrCorpseDespawn: + { + // if m_deathState is DEAD, CORPSE was skipped + if (m_deathState == DeathState.Corpse || m_deathState == DeathState.Dead) + { + UnSummon(); + return; + } + + if (!IsInCombat()) + { + if (m_timer <= diff) + { + UnSummon(); + return; + } + else + m_timer -= diff; + } + else if (m_timer != m_lifetime) + m_timer = m_lifetime; + break; + } + case TempSummonType.TimedOrDeadDespawn: + { + // if m_deathState is DEAD, CORPSE was skipped + if (m_deathState == DeathState.Dead) + { + UnSummon(); + return; + } + + if (!IsInCombat() && IsAlive()) + { + if (m_timer <= diff) + { + UnSummon(); + return; + } + else + m_timer -= diff; + } + else if (m_timer != m_lifetime) + m_timer = m_lifetime; + break; + } + default: + UnSummon(); + Log.outError(LogFilter.Unit, "Temporary summoned creature (entry: {0}) have unknown type {1} of ", GetEntry(), m_type); + break; + } + } + + public virtual void InitStats(uint duration) + { + Contract.Assert(!IsPet()); + + m_timer = duration; + m_lifetime = duration; + + if (m_type == TempSummonType.ManualDespawn) + m_type = (duration == 0) ? TempSummonType.DeadDespawn : TempSummonType.TimedDespawn; + + Unit owner = GetSummoner(); + + if (owner != null && IsTrigger() && m_spells[0] != 0) + { + SetFaction(owner.getFaction()); + SetLevel(owner.getLevel()); + if (owner.IsTypeId(TypeId.Player)) + m_ControlledByPlayer = true; + } + + + if (m_Properties == null) + return; + + if (owner != null) + { + int slot = m_Properties.Slot; + if (slot > 0) + { + if (!owner.m_SummonSlot[slot].IsEmpty() && owner.m_SummonSlot[slot] != GetGUID()) + { + Creature oldSummon = GetMap().GetCreature(owner.m_SummonSlot[slot]); + if (oldSummon != null && oldSummon.IsSummon()) + oldSummon.ToTempSummon().UnSummon(); + } + owner.m_SummonSlot[slot] = GetGUID(); + } + } + + if (m_Properties.Faction != 0) + SetFaction(m_Properties.Faction); + else if (IsVehicle() && owner != null) // properties should be vehicle + SetFaction(owner.getFaction()); + } + + public virtual void InitSummon() + { + Unit owner = GetSummoner(); + if (owner != null) + { + if (owner.IsTypeId(TypeId.Unit) && owner.ToCreature().IsAIEnabled) + owner.ToCreature().GetAI().JustSummoned(this); + if (IsAIEnabled) + GetAI().IsSummonedBy(owner); + } + } + + public override void UpdateObjectVisibilityOnCreate() + { + base.UpdateObjectVisibility(true); + } + + public void SetTempSummonType(TempSummonType type) + { + m_type = type; + } + + public virtual void UnSummon(uint msTime = 0) + { + if (msTime != 0) + { + ForcedUnsummonDelayEvent pEvent = new ForcedUnsummonDelayEvent(this); + + m_Events.AddEvent(pEvent, m_Events.CalculateTime(msTime)); + return; + } + + Contract.Assert(!IsPet()); + if (IsPet()) + { + ToPet().Remove(PetSaveMode.NotInSlot); + Contract.Assert(!IsInWorld); + return; + } + + Unit owner = GetSummoner(); + if (owner != null && owner.IsTypeId(TypeId.Unit) && owner.ToCreature().IsAIEnabled) + owner.ToCreature().GetAI().SummonedCreatureDespawn(this); + + AddObjectToRemoveList(); + } + + public override void RemoveFromWorld() + { + if (!IsInWorld) + return; + + if (m_Properties != null) + { + int slot = m_Properties.Slot; + if (slot > 0) + { + Unit owner = GetSummoner(); + if (owner != null) + if (owner.m_SummonSlot[slot] == GetGUID()) + owner.m_SummonSlot[slot].Clear(); + } + } + + if (!GetOwnerGUID().IsEmpty()) + Log.outError(LogFilter.Unit, "Unit {0} has owner guid when removed from world", GetEntry()); + + base.RemoveFromWorld(); + } + + public override void SaveToDB(uint mapid, uint spawnMask, uint phaseMask) { } + + public ObjectGuid GetSummonerGUID() { return m_summonerGUID; } + + TempSummonType GetSummonType() { return m_type; } + + public uint GetTimer() { return m_timer; } + + public SummonPropertiesRecord m_Properties; + TempSummonType m_type; + uint m_timer; + uint m_lifetime; + ObjectGuid m_summonerGUID; + } + + public class Minion : TempSummon + { + public Minion(SummonPropertiesRecord properties, Unit owner, bool isWorldObject) + : base(properties, owner, isWorldObject) + { + m_owner = owner; + Contract.Assert(m_owner); + m_unitTypeMask |= UnitTypeMask.Minion; + m_followAngle = SharedConst.PetFollowAngle; + } + + public override void InitStats(uint duration) + { + base.InitStats(duration); + + SetReactState(ReactStates.Passive); + + SetCreatorGUID(GetOwner().GetGUID()); + SetFaction(GetOwner().getFaction()); + + GetOwner().SetMinion(this, true); + } + + public override void RemoveFromWorld() + { + if (!IsInWorld) + return; + + GetOwner().SetMinion(this, false); + base.RemoveFromWorld(); + } + + public bool IsGuardianPet() + { + return IsPet() || (m_Properties != null && m_Properties.Category == SummonCategory.Pet); + } + + public override Unit GetOwner() { return m_owner; } + + public override float GetFollowAngle() { return m_followAngle; } + + public void SetFollowAngle(float angle) { m_followAngle = angle; } + + public bool IsPetGhoul() { return GetEntry() == 26125; } // Ghoul may be guardian or pet + + public bool IsSpiritWolf() { return GetEntry() == 29264; } // Spirit wolf from feral spirits + + + Unit m_owner; + float m_followAngle; + } + + public class Guardian : Minion + { + public Guardian(SummonPropertiesRecord properties, Unit owner, bool isWorldObject) + : base(properties, owner, isWorldObject) + { + m_bonusSpellDamage = 0; + + m_unitTypeMask |= UnitTypeMask.Guardian; + if (properties != null && (properties.Type == SummonType.Pet || properties.Category == SummonCategory.Pet)) + { + m_unitTypeMask |= UnitTypeMask.ControlableGuardian; + InitCharmInfo(); + } + } + + public override void InitStats(uint duration) + { + base.InitStats(duration); + + InitStatsForLevel(GetOwner().getLevel()); + + if (GetOwner().IsTypeId(TypeId.Player) && HasUnitTypeMask(UnitTypeMask.ControlableGuardian)) + GetCharmInfo().InitCharmCreateSpells(); + + SetReactState(ReactStates.Aggressive); + } + + public override void InitSummon() + { + base.InitSummon(); + + if (GetOwner().IsTypeId(TypeId.Player) && GetOwner().GetMinionGUID() == GetGUID() + && GetOwner().GetCharmGUID().IsEmpty()) + { + GetOwner().ToPlayer().CharmSpellInitialize(); + } + } + + // @todo Move stat mods code to pet passive auras + public bool InitStatsForLevel(uint petlevel) + { + CreatureTemplate cinfo = GetCreatureTemplate(); + Contract.Assert(cinfo != null); + + SetLevel(petlevel); + + //Determine pet type + PetType petType = PetType.Max; + if (IsPet() && GetOwner().IsTypeId(TypeId.Player)) + { + if (GetOwner().GetClass() == Class.Warlock + || GetOwner().GetClass() == Class.Shaman // Fire Elemental + || GetOwner().GetClass() == Class.Deathknight) // Risen Ghoul + { + petType = PetType.Summon; + } + else if (GetOwner().GetClass() == Class.Hunter) + { + petType = PetType.Hunter; + m_unitTypeMask |= UnitTypeMask.HunterPet; + } + else + { + Log.outError(LogFilter.Unit, "Unknown type pet {0} is summoned by player class {1}", GetEntry(), GetOwner().GetClass()); + } + } + + uint creature_ID = (petType == PetType.Hunter) ? 1 : cinfo.Entry; + + SetMeleeDamageSchool((SpellSchools)cinfo.DmgSchool); + + SetModifierValue(UnitMods.Armor, UnitModifierType.BaseValue, (float)petlevel * 50); + + SetBaseAttackTime(WeaponAttackType.BaseAttack, SharedConst.BaseAttackTime); + SetBaseAttackTime(WeaponAttackType.OffAttack, SharedConst.BaseAttackTime); + SetBaseAttackTime(WeaponAttackType.RangedAttack, SharedConst.BaseAttackTime); + + SetFloatValue(UnitFields.ModCastSpeed, 1.0f); + SetFloatValue(UnitFields.ModCastHaste, 1.0f); + + //scale + var cFamily = CliDB.CreatureFamilyStorage.LookupByKey(cinfo.Family); + if (cFamily != null && cFamily.MinScale > 0.0f && petType == PetType.Hunter) + { + float scale; + if (getLevel() >= cFamily.MaxScaleLevel) + scale = cFamily.MaxScale; + else if (getLevel() <= cFamily.MinScaleLevel) + scale = cFamily.MinScale; + else + scale = cFamily.MinScale + (float)(getLevel() - cFamily.MinScaleLevel) / cFamily.MaxScaleLevel * (cFamily.MaxScale - cFamily.MinScale); + + SetObjectScale(scale); + } + + // Resistance + for (int i = (int)SpellSchools.Holy; i < (int)SpellSchools.Max; ++i) + SetModifierValue(UnitMods.ResistanceStart + i, UnitModifierType.BaseValue, cinfo.Resistance[i]); + + //health, mana, armor and resistance + PetLevelInfo pInfo = Global.ObjectMgr.GetPetLevelInfo(creature_ID, petlevel); + if (pInfo != null) // exist in DB + { + SetCreateHealth(pInfo.health); + if (petType != PetType.Hunter) //hunter pet use focus + SetCreateMana(pInfo.mana); + + if (pInfo.armor > 0) + SetModifierValue(UnitMods.Armor, UnitModifierType.BaseValue, pInfo.armor); + + for (byte stat = 0; stat < (int)Stats.Max; ++stat) + SetCreateStat((Stats)stat, pInfo.stats[stat]); + } + else // not exist in DB, use some default fake data + { + // remove elite bonuses included in DB values + CreatureBaseStats stats = Global.ObjectMgr.GetCreatureBaseStats(petlevel, cinfo.UnitClass); + SetCreateHealth(stats.BaseHealth[cinfo.HealthScalingExpansion]); + SetCreateMana(stats.BaseMana); + + SetCreateStat(Stats.Strength, 22); + SetCreateStat(Stats.Agility, 22); + SetCreateStat(Stats.Stamina, 25); + SetCreateStat(Stats.Intellect, 28); + } + + SetBonusDamage(0); + switch (petType) + { + case PetType.Summon: + { + // the damage bonus used for pets is either fire or shadow damage, whatever is higher + int fire = GetOwner().GetInt32Value(PlayerFields.ModDamageDonePos + (int)SpellSchools.Fire); + int shadow = GetOwner().GetInt32Value(PlayerFields.ModDamageDonePos + (int)SpellSchools.Shadow); + int val = (fire > shadow) ? fire : shadow; + if (val < 0) + val = 0; + + SetBonusDamage((int)(val * 0.15f)); + + SetBaseWeaponDamage(WeaponAttackType.BaseAttack, WeaponDamageRange.MinDamage, petlevel - (petlevel / 4)); + SetBaseWeaponDamage(WeaponAttackType.BaseAttack, WeaponDamageRange.MaxDamage, petlevel + (petlevel / 4)); + break; + } + case PetType.Hunter: + { + SetUInt32Value(UnitFields.PetNextLevelExp, (uint)(Global.ObjectMgr.GetXPForLevel(petlevel) * 0.05f)); + //these formula may not be correct; however, it is designed to be close to what it should be + //this makes dps 0.5 of pets level + SetBaseWeaponDamage(WeaponAttackType.BaseAttack, WeaponDamageRange.MinDamage, petlevel - (petlevel / 4)); + //damage range is then petlevel / 2 + SetBaseWeaponDamage(WeaponAttackType.BaseAttack, WeaponDamageRange.MaxDamage, petlevel + (petlevel / 4)); + //damage is increased afterwards as strength and pet scaling modify attack power + break; + } + default: + { + switch (GetEntry()) + { + case 510: // mage Water Elemental + { + SetBonusDamage((int)(GetOwner().SpellBaseDamageBonusDone(SpellSchoolMask.Frost) * 0.33f)); + break; + } + case 1964: //force of nature + { + if (pInfo == null) + SetCreateHealth(30 + 30 * petlevel); + float bonusDmg = GetOwner().SpellBaseDamageBonusDone(SpellSchoolMask.Nature) * 0.15f; + SetBaseWeaponDamage(WeaponAttackType.BaseAttack, WeaponDamageRange.MinDamage, petlevel * 2.5f - (petlevel / 2) + bonusDmg); + SetBaseWeaponDamage(WeaponAttackType.BaseAttack, WeaponDamageRange.MaxDamage, petlevel * 2.5f + (petlevel / 2) + bonusDmg); + break; + } + case 15352: //earth elemental 36213 + { + if (pInfo == null) + SetCreateHealth(100 + 120 * petlevel); + SetBaseWeaponDamage(WeaponAttackType.BaseAttack, WeaponDamageRange.MinDamage, petlevel - (petlevel / 4)); + SetBaseWeaponDamage(WeaponAttackType.BaseAttack, WeaponDamageRange.MaxDamage, petlevel + (petlevel / 4)); + break; + } + case 15438: //fire elemental + { + if (pInfo == null) + { + SetCreateHealth(40 * petlevel); + SetCreateMana(28 + 10 * petlevel); + } + SetBonusDamage((int)(GetOwner().SpellBaseDamageBonusDone(SpellSchoolMask.Fire) * 0.5f)); + SetBaseWeaponDamage(WeaponAttackType.BaseAttack, WeaponDamageRange.MinDamage, petlevel * 4 - petlevel); + SetBaseWeaponDamage(WeaponAttackType.BaseAttack, WeaponDamageRange.MaxDamage, petlevel * 4 + petlevel); + break; + } + case 19668: // Shadowfiend + { + if (pInfo == null) + { + SetCreateMana(28 + 10 * petlevel); + SetCreateHealth(28 + 30 * petlevel); + } + int bonus_dmg = (int)(GetOwner().SpellBaseDamageBonusDone(SpellSchoolMask.Shadow) * 0.3f); + SetBaseWeaponDamage(WeaponAttackType.BaseAttack, WeaponDamageRange.MinDamage, (petlevel * 4 - petlevel) + bonus_dmg); + SetBaseWeaponDamage(WeaponAttackType.BaseAttack, WeaponDamageRange.MaxDamage, (petlevel * 4 + petlevel) + bonus_dmg); + + break; + } + case 19833: //Snake Trap - Venomous Snake + { + SetBaseWeaponDamage(WeaponAttackType.BaseAttack, WeaponDamageRange.MinDamage, (petlevel / 2) - 25); + SetBaseWeaponDamage(WeaponAttackType.BaseAttack, WeaponDamageRange.MaxDamage, (petlevel / 2) - 18); + break; + } + case 19921: //Snake Trap - Viper + { + SetBaseWeaponDamage(WeaponAttackType.BaseAttack, WeaponDamageRange.MinDamage, petlevel / 2 - 10); + SetBaseWeaponDamage(WeaponAttackType.BaseAttack, WeaponDamageRange.MaxDamage, petlevel / 2); + break; + } + case 29264: // Feral Spirit + { + if (pInfo == null) + SetCreateHealth(30 * petlevel); + + // wolf attack speed is 1.5s + SetBaseAttackTime(WeaponAttackType.BaseAttack, cinfo.BaseAttackTime); + + SetBaseWeaponDamage(WeaponAttackType.BaseAttack, WeaponDamageRange.MinDamage, (petlevel * 4 - petlevel)); + SetBaseWeaponDamage(WeaponAttackType.BaseAttack, WeaponDamageRange.MaxDamage, (petlevel * 4 + petlevel)); + + SetModifierValue(UnitMods.Armor, UnitModifierType.BaseValue, GetOwner().GetArmor() * 0.35f); // Bonus Armor (35% of player armor) + SetModifierValue(UnitMods.StatStamina, UnitModifierType.BaseValue, GetOwner().GetStat(Stats.Stamina) * 0.3f); // Bonus Stamina (30% of player stamina) + if (!HasAura(58877))//prevent apply twice for the 2 wolves + AddAura(58877, this);//Spirit Hunt, passive, Spirit Wolves' attacks heal them and their master for 150% of damage done. + break; + } + case 31216: // Mirror Image + { + SetBonusDamage((int)(GetOwner().SpellBaseDamageBonusDone(SpellSchoolMask.Frost) * 0.33f)); + SetDisplayId(GetOwner().GetDisplayId()); + if (pInfo == null) + { + SetCreateMana(28 + 30 * petlevel); + SetCreateHealth(28 + 10 * petlevel); + } + break; + } + case 27829: // Ebon Gargoyle + { + if (pInfo == null) + { + SetCreateMana(28 + 10 * petlevel); + SetCreateHealth(28 + 30 * petlevel); + } + SetBonusDamage((int)(GetOwner().GetTotalAttackPowerValue(WeaponAttackType.BaseAttack) * 0.5f)); + SetBaseWeaponDamage(WeaponAttackType.BaseAttack, WeaponDamageRange.MinDamage, petlevel - (petlevel / 4)); + SetBaseWeaponDamage(WeaponAttackType.BaseAttack, WeaponDamageRange.MaxDamage, petlevel + (petlevel / 4)); + break; + } + case 28017: // Bloodworms + { + SetCreateHealth(4 * petlevel); + SetBonusDamage((int)(GetOwner().GetTotalAttackPowerValue(WeaponAttackType.BaseAttack) * 0.006f)); + SetBaseWeaponDamage(WeaponAttackType.BaseAttack, WeaponDamageRange.MinDamage, petlevel - 30 - (petlevel / 4)); + SetBaseWeaponDamage(WeaponAttackType.BaseAttack, WeaponDamageRange.MaxDamage, petlevel - 30 + (petlevel / 4)); + } + break; + } + break; + } + } + + UpdateAllStats(); + + SetFullHealth(); + SetPower(PowerType.Mana, GetMaxPower(PowerType.Mana)); + return true; + } + + const int ENTRY_IMP = 416; + const int ENTRY_VOIDWALKER = 1860; + const int ENTRY_SUCCUBUS = 1863; + const int ENTRY_FELHUNTER = 417; + const int ENTRY_FELGUARD = 17252; + const int ENTRY_WATER_ELEMENTAL = 510; + const int ENTRY_TREANT = 1964; + const int ENTRY_FIRE_ELEMENTAL = 15438; + const int ENTRY_GHOUL = 26125; + const int ENTRY_BLOODWORM = 28017; + + public override bool UpdateStats(Stats stat) + { + float value = GetTotalStatValue(stat); + ApplyStatBuffMod(stat, m_statFromOwner[(int)stat], false); + float ownersBonus = 0.0f; + + Unit owner = GetOwner(); + // Handle Death Knight Glyphs and Talents + float mod = 0.75f; + if (IsPetGhoul() && (stat == Stats.Stamina || stat == Stats.Strength)) + { + switch (stat) + { + case Stats.Stamina: + mod = 0.3f; + break; // Default Owner's Stamina scale + case Stats.Strength: + mod = 0.7f; + break; // Default Owner's Strength scale + default: break; + } + + ownersBonus = owner.GetStat(stat) * mod; + value += ownersBonus; + } + else if (stat == Stats.Stamina) + { + ownersBonus = MathFunctions.CalculatePct(owner.GetStat(Stats.Stamina), 30); + value += ownersBonus; + } + //warlock's and mage's pets gain 30% of owner's intellect + else if (stat == Stats.Intellect) + { + if (owner.GetClass() == Class.Warlock || owner.GetClass() == Class.Mage) + { + ownersBonus = MathFunctions.CalculatePct(owner.GetStat(stat), 30); + value += ownersBonus; + } + } + + SetStat(stat, (int)value); + m_statFromOwner[(int)stat] = ownersBonus; + ApplyStatBuffMod(stat, m_statFromOwner[(int)stat], true); + + switch (stat) + { + case Stats.Strength: + UpdateAttackPowerAndDamage(); + break; + case Stats.Agility: + UpdateArmor(); + break; + case Stats.Stamina: + UpdateMaxHealth(); + break; + case Stats.Intellect: + UpdateMaxPower(PowerType.Mana); + break; + default: + break; + } + + return true; + } + + public override bool UpdateAllStats() + { + for (var i = Stats.Strength; i < Stats.Max; ++i) + UpdateStats(i); + + for (var i = PowerType.Mana; i < PowerType.Max; ++i) + UpdateMaxPower(i); + + UpdateAllResistances(); + + return true; + } + + public override void UpdateResistances(SpellSchools school) + { + if (school > SpellSchools.Normal) + { + float value = GetTotalAuraModValue(UnitMods.ResistanceStart + (int)school); + + // hunter and warlock pets gain 40% of owner's resistance + if (IsPet()) + value += MathFunctions.CalculatePct(GetOwner().GetResistance(school), 40); + + SetResistance(school, (int)value); + } + else + UpdateArmor(); + } + + public override void UpdateArmor() + { + float value = 0.0f; + float bonus_armor = 0.0f; + UnitMods unitMod = UnitMods.Armor; + + // hunter pets gain 35% of owner's armor value, warlock pets gain 100% of owner's armor + if (IsHunterPet()) + bonus_armor = MathFunctions.CalculatePct(GetOwner().GetArmor(), 70); + else if (IsPet()) + bonus_armor = GetOwner().GetArmor(); + + value = GetModifierValue(unitMod, UnitModifierType.BaseValue); + value *= GetModifierValue(unitMod, UnitModifierType.BasePCT); + value += GetModifierValue(unitMod, UnitModifierType.TotalValue) + bonus_armor; + value *= GetModifierValue(unitMod, UnitModifierType.TotalPCT); + + SetArmor((int)value); + } + + public override void UpdateMaxHealth() + { + UnitMods unitMod = UnitMods.Health; + float stamina = GetStat(Stats.Stamina) - GetCreateStat(Stats.Stamina); + + float multiplicator; + switch (GetEntry()) + { + case ENTRY_IMP: + multiplicator = 8.4f; + break; + case ENTRY_VOIDWALKER: + multiplicator = 11.0f; + break; + case ENTRY_SUCCUBUS: + multiplicator = 9.1f; + break; + case ENTRY_FELHUNTER: + multiplicator = 9.5f; + break; + case ENTRY_FELGUARD: + multiplicator = 11.0f; + break; + case ENTRY_BLOODWORM: + multiplicator = 1.0f; + break; + default: + multiplicator = 10.0f; + break; + } + + float value = GetModifierValue(unitMod, UnitModifierType.BaseValue) + GetCreateHealth(); + value *= GetModifierValue(unitMod, UnitModifierType.BasePCT); + value += GetModifierValue(unitMod, UnitModifierType.TotalValue) + stamina * multiplicator; + value *= GetModifierValue(unitMod, UnitModifierType.TotalPCT); + + SetMaxHealth((uint)value); + } + + public override void UpdateMaxPower(PowerType power) + { + UnitMods unitMod = UnitMods.PowerStart + (int)power; + + float addValue = (power == PowerType.Mana) ? GetStat(Stats.Intellect) - GetCreateStat(Stats.Intellect) : 0.0f; + float multiplicator = 15.0f; + + switch (GetEntry()) + { + case ENTRY_IMP: + multiplicator = 4.95f; + break; + case ENTRY_VOIDWALKER: + case ENTRY_SUCCUBUS: + case ENTRY_FELHUNTER: + case ENTRY_FELGUARD: + multiplicator = 11.5f; + break; + default: + multiplicator = 15.0f; + break; + } + + float value = GetModifierValue(unitMod, UnitModifierType.BaseValue) + GetCreatePowers(power); + value *= GetModifierValue(unitMod, UnitModifierType.BasePCT); + value += GetModifierValue(unitMod, UnitModifierType.TotalValue) + addValue * multiplicator; + value *= GetModifierValue(unitMod, UnitModifierType.TotalPCT); + + SetMaxPower(power, (int)value); + } + + public override void UpdateAttackPowerAndDamage(bool ranged = false) + { + if (ranged) + return; + + float val = 0.0f; + float bonusAP = 0.0f; + UnitMods unitMod = UnitMods.AttackPower; + + if (GetEntry() == ENTRY_IMP) // imp's attack power + val = GetStat(Stats.Strength) - 10.0f; + else + val = 2 * GetStat(Stats.Strength) - 20.0f; + + Unit owner = GetOwner(); + if (owner != null && owner.IsTypeId(TypeId.Player)) + { + if (IsHunterPet()) //hunter pets benefit from owner's attack power + { + float mod = 1.0f; //Hunter contribution modifier + bonusAP = owner.GetTotalAttackPowerValue(WeaponAttackType.RangedAttack) * 0.22f * mod; + SetBonusDamage((int)(owner.GetTotalAttackPowerValue(WeaponAttackType.RangedAttack) * 0.1287f * mod)); + } + else if (IsPetGhoul()) //ghouls benefit from deathknight's attack power (may be summon pet or not) + { + bonusAP = owner.GetTotalAttackPowerValue(WeaponAttackType.BaseAttack) * 0.22f; + SetBonusDamage((int)(owner.GetTotalAttackPowerValue(WeaponAttackType.BaseAttack) * 0.1287f)); + } + else if (IsSpiritWolf()) //wolf benefit from shaman's attack power + { + float dmg_multiplier = 0.31f; + if (GetOwner().GetAuraEffect(63271, 0) != null) // Glyph of Feral Spirit + dmg_multiplier = 0.61f; + bonusAP = owner.GetTotalAttackPowerValue(WeaponAttackType.BaseAttack) * dmg_multiplier; + SetBonusDamage((int)(owner.GetTotalAttackPowerValue(WeaponAttackType.BaseAttack) * dmg_multiplier)); + } + //demons benefit from warlocks shadow or fire damage + else if (IsPet()) + { + int fire = (int)((owner.GetUInt32Value(PlayerFields.ModDamageDonePos + (int)SpellSchools.Fire)) + owner.GetUInt32Value(PlayerFields.ModDamageDoneNeg + (int)SpellSchools.Fire)); + int shadow = (int)((owner.GetUInt32Value(PlayerFields.ModDamageDonePos + (int)SpellSchools.Shadow)) + owner.GetUInt32Value(PlayerFields.ModDamageDoneNeg + (int)SpellSchools.Shadow)); + int maximum = (fire > shadow) ? fire : shadow; + if (maximum < 0) + maximum = 0; + SetBonusDamage((int)(maximum * 0.15f)); + bonusAP = maximum * 0.57f; + } + //water elementals benefit from mage's frost damage + else if (GetEntry() == ENTRY_WATER_ELEMENTAL) + { + int frost = (int)((owner.GetUInt32Value(PlayerFields.ModDamageDonePos + (int)SpellSchools.Frost)) + owner.GetUInt32Value(PlayerFields.ModDamageDoneNeg + (int)SpellSchools.Frost)); + if (frost < 0) + frost = 0; + SetBonusDamage((int)(frost * 0.4f)); + } + } + + SetModifierValue(UnitMods.AttackPower, UnitModifierType.BaseValue, val + bonusAP); + + //in BASE_VALUE of UNIT_MOD_ATTACK_POWER for creatures we store data of meleeattackpower field in DB + float base_attPower = GetModifierValue(unitMod, UnitModifierType.BaseValue) * GetModifierValue(unitMod, UnitModifierType.BasePCT); + float attPowerMultiplier = GetModifierValue(unitMod, UnitModifierType.TotalPCT) - 1.0f; + + //UNIT_FIELD_(RANGED)_ATTACK_POWER field + SetInt32Value(UnitFields.AttackPower, (int)base_attPower); + //UNIT_FIELD_(RANGED)_ATTACK_POWER_MULTIPLIER field + SetFloatValue(UnitFields.AttackPowerMultiplier, attPowerMultiplier); + + //automatically update weapon damage after attack power modification + UpdateDamagePhysical(WeaponAttackType.BaseAttack); + } + + public override void UpdateDamagePhysical(WeaponAttackType attType) + { + if (attType > WeaponAttackType.BaseAttack) + return; + + float bonusDamage = 0.0f; + if (GetOwner().IsTypeId(TypeId.Player)) + { + //force of nature + if (GetEntry() == ENTRY_TREANT) + { + int spellDmg = (int)((GetOwner().GetUInt32Value(PlayerFields.ModDamageDonePos + (int)SpellSchools.Nature)) + GetOwner().GetUInt32Value(PlayerFields.ModDamageDoneNeg + (int)SpellSchools.Nature)); + if (spellDmg > 0) + bonusDamage = spellDmg * 0.09f; + } + //greater fire elemental + else if (GetEntry() == ENTRY_FIRE_ELEMENTAL) + { + int spellDmg = (int)((GetOwner().GetUInt32Value(PlayerFields.ModDamageDonePos + (int)SpellSchools.Fire)) + GetOwner().GetUInt32Value(PlayerFields.ModDamageDoneNeg + (int)SpellSchools.Fire)); + if (spellDmg > 0) + bonusDamage = spellDmg * 0.4f; + } + } + + UnitMods unitMod = UnitMods.DamageMainHand; + + float att_speed = GetBaseAttackTime(WeaponAttackType.BaseAttack) / 1000.0f; + + float base_value = GetModifierValue(unitMod, UnitModifierType.BaseValue) + GetTotalAttackPowerValue(attType) / 3.5f * att_speed + bonusDamage; + float base_pct = GetModifierValue(unitMod, UnitModifierType.BasePCT); + float total_value = GetModifierValue(unitMod, UnitModifierType.TotalValue); + float total_pct = GetModifierValue(unitMod, UnitModifierType.TotalPCT); + + float weapon_mindamage = GetWeaponDamageRange(WeaponAttackType.BaseAttack, WeaponDamageRange.MinDamage); + float weapon_maxdamage = GetWeaponDamageRange(WeaponAttackType.BaseAttack, WeaponDamageRange.MaxDamage); + + float mindamage = ((base_value + weapon_mindamage) * base_pct + total_value) * total_pct; + float maxdamage = ((base_value + weapon_maxdamage) * base_pct + total_value) * total_pct; + + var mDummy = GetAuraEffectsByType(AuraType.ModAttackspeed); + foreach (var eff in mDummy) + { + switch (eff.GetSpellInfo().Id) + { + case 61682: + case 61683: + MathFunctions.AddPct(ref mindamage, -eff.GetAmount()); + MathFunctions.AddPct(ref maxdamage, -eff.GetAmount()); + break; + default: + break; + } + } + + SetStatFloatValue(UnitFields.MinDamage, mindamage); + SetStatFloatValue(UnitFields.MaxDamage, maxdamage); + } + + void SetBonusDamage(int damage) + { + m_bonusSpellDamage = damage; + if (GetOwner().IsTypeId(TypeId.Player)) + GetOwner().SetUInt32Value(PlayerFields.PetSpellPower, (uint)damage); + } + + public int GetBonusDamage() { return m_bonusSpellDamage; } + + int m_bonusSpellDamage; + float[] m_statFromOwner = new float[(int)Stats.Max]; + } + + public class Puppet : Minion + { + public Puppet(SummonPropertiesRecord properties, Unit owner) + : base(properties, owner, false) + { + Contract.Assert(owner.IsTypeId(TypeId.Player)); + m_unitTypeMask |= UnitTypeMask.Puppet; + } + + public override void InitStats(uint duration) + { + base.InitStats(duration); + + SetLevel(GetOwner().getLevel()); + SetReactState(ReactStates.Passive); + } + + public override void InitSummon() + { + base.InitSummon(); + if (!SetCharmedBy(GetOwner(), CharmType.Possess)) + Contract.Assert(false); + } + + public override void Update(uint diff) + { + base.Update(diff); + //check if caster is channelling? + if (IsInWorld) + { + if (!IsAlive()) + { + UnSummon(); + // @todo why long distance .die does not remove it + } + } + } + + public override void RemoveFromWorld() + { + if (!IsInWorld) + return; + + RemoveCharmedBy(null); + base.RemoveFromWorld(); + } + } + + public class ForcedUnsummonDelayEvent : BasicEvent + { + public ForcedUnsummonDelayEvent(TempSummon owner) + { + m_owner = owner; + } + public override bool Execute(ulong e_time, uint p_time) + { + m_owner.UnSummon(); + return true; + } + + TempSummon m_owner; + } + + public class TempSummonData + { + public uint entry; // Entry of summoned creature + public Position pos; // Position, where should be creature spawned + public TempSummonType type; // Summon type, see TempSummonType for available types + public uint time; // Despawn time, usable only with certain temp summon types + } +} diff --git a/Game/Entities/Totem.cs b/Game/Entities/Totem.cs new file mode 100644 index 000000000..a5ed7bc71 --- /dev/null +++ b/Game/Entities/Totem.cs @@ -0,0 +1,211 @@ +/* + * Copyright (C) 2012-2017 CypherCore + * + * 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, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +using Framework.Constants; +using Game.DataStorage; +using Game.Groups; +using Game.Network.Packets; +using Game.Spells; +using System.Linq; + +namespace Game.Entities +{ + public class Totem : Minion + { + public Totem(SummonPropertiesRecord properties, Unit owner) : base(properties, owner, false) + { + m_unitTypeMask |= UnitTypeMask.Totem; + m_type = TotemType.Passive; + } + + public override void Update(uint diff) + { + if (!GetOwner().IsAlive() || !IsAlive()) + { + UnSummon(); // remove self + return; + } + + if (m_duration <= diff) + { + UnSummon(); // remove self + return; + } + else + m_duration -= diff; + base.Update(diff); + + } + + public override void InitStats(uint duration) + { + // client requires SMSG_TOTEM_CREATED to be sent before adding to world and before removing old totem + Player owner = GetOwner().ToPlayer(); + if (owner) + { + if (m_Properties.Slot >= (int)SummonSlot.Totem && m_Properties.Slot < SharedConst.MaxTotemSlot) + { + TotemCreated packet = new TotemCreated(); + packet.Totem = GetGUID(); + packet.Slot = (byte)(m_Properties.Slot - (int)SummonSlot.Totem); + packet.Duration = duration; + packet.SpellID = GetUInt32Value(UnitFields.CreatedBySpell); + owner.ToPlayer().SendPacket(packet); + + + } + + // set display id depending on caster's race + SpellInfo createdBySpell = Global.SpellMgr.GetSpellInfo(GetUInt32Value(UnitFields.CreatedBySpell)); + if (createdBySpell != null) + { + SpellEffectInfo[] effects = createdBySpell.GetEffectsForDifficulty(Difficulty.None); + var summonEffect = effects.FirstOrDefault(effect => + { + return effect != null && effect.IsEffect(SpellEffectName.Summon); + }); + + if (summonEffect != null) + SetDisplayId(owner.GetModelForTotem((PlayerTotemType)summonEffect.MiscValueB)); + } + } + + base.InitStats(duration); + + // Get spell cast by totem + SpellInfo totemSpell = Global.SpellMgr.GetSpellInfo(GetSpell()); + if (totemSpell != null) + if (totemSpell.CalcCastTime(getLevel()) != 0) // If spell has cast time . its an active totem + m_type = TotemType.Active; + + m_duration = duration; + + SetLevel(GetOwner().getLevel()); + } + + public override void InitSummon() + { + if (m_type == TotemType.Passive && GetSpell() != 0) + CastSpell(this, GetSpell(), true); + + // Some totems can have both instant effect and passive spell + if (GetSpell(1) != 0) + CastSpell(this, GetSpell(1), true); + } + + public override void UnSummon(uint msTime = 0) + { + if (msTime != 0) + { + m_Events.AddEvent(new ForcedUnsummonDelayEvent(this), m_Events.CalculateTime(msTime)); + return; + } + + CombatStop(); + RemoveAurasDueToSpell(GetSpell(), GetGUID()); + + // clear owner's totem slot + for (byte i = (int)SummonSlot.Totem; i < SharedConst.MaxTotemSlot; ++i) + { + if (GetOwner().m_SummonSlot[i] == GetGUID()) + { + GetOwner().m_SummonSlot[i].Clear(); + break; + } + } + + GetOwner().RemoveAurasDueToSpell(GetSpell(), GetGUID()); + + // remove aura all party members too + Player owner = GetOwner().ToPlayer(); + if (owner != null) + { + owner.SendAutoRepeatCancel(this); + + SpellInfo spell = Global.SpellMgr.GetSpellInfo(GetUInt32Value(UnitFields.CreatedBySpell)); + if (spell != null) + GetSpellHistory().SendCooldownEvent(spell, 0, null, false); + + Group group = owner.GetGroup(); + if (group) + { + for (GroupReference refe = group.GetFirstMember(); refe != null; refe = refe.next()) + { + Player target = refe.GetSource(); + if (target && group.SameSubGroup(owner, target)) + target.RemoveAurasDueToSpell(GetSpell(), GetGUID()); + } + } + } + + AddObjectToRemoveList(); + } + + public override bool IsImmunedToSpellEffect(SpellInfo spellInfo, uint index) + { + // @todo possibly all negative auras immune? + if (GetEntry() == 5925) + return false; + + SpellEffectInfo effect = spellInfo.GetEffect(GetMap().GetDifficultyID(), index); + if (effect == null) + return true; + + switch (effect.ApplyAuraName) + { + case AuraType.PeriodicDamage: + case AuraType.PeriodicLeech: + case AuraType.ModFear: + case AuraType.Transform: + return true; + default: + break; + } + + return base.IsImmunedToSpellEffect(spellInfo, index); + } + + public uint GetSpell(byte slot = 0) { return m_spells[slot]; } + + public uint GetTotemDuration() { return m_duration; } + + public void SetTotemDuration(uint duration) { m_duration = duration; } + + public TotemType GetTotemType() { return m_type; } + + public override bool UpdateStats(Stats stat) { return true; } + + public override bool UpdateAllStats() { return true; } + + public override void UpdateResistances(SpellSchools school) { } + public override void UpdateArmor() { } + public override void UpdateMaxHealth() { } + public override void UpdateMaxPower(PowerType power) { } + public override void UpdateAttackPowerAndDamage(bool ranged = false) { } + public override void UpdateDamagePhysical(WeaponAttackType attType) { } + + TotemType m_type; + uint m_duration; + } + + public enum TotemType + { + Passive = 0, + Active = 1, + Statue = 2 // copied straight from MaNGOS, may need more implementation to work + } +} diff --git a/Game/Entities/Transport.cs b/Game/Entities/Transport.cs new file mode 100644 index 000000000..17552a162 --- /dev/null +++ b/Game/Entities/Transport.cs @@ -0,0 +1,799 @@ +/* + * Copyright (C) 2012-2017 CypherCore + * + * 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, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +using Framework.Constants; +using Framework.GameMath; +using Game.DataStorage; +using Game.Maps; +using System; +using System.Collections.Generic; +using System.Diagnostics.Contracts; +using System.Linq; + +namespace Game.Entities +{ + public interface ITransport + { + // This method transforms supplied transport offsets into global coordinates + void CalculatePassengerPosition(ref float x, ref float y, ref float z, ref float o); + + // This method transforms supplied global coordinates into local offsets + void CalculatePassengerOffset(ref float x, ref float y, ref float z, ref float o); + } + + public class TransportPosHelper + { + public static void CalculatePassengerPosition(ref float x, ref float y, ref float z, ref float o, float transX, float transY, float transZ, float transO) + { + float inx = x, iny = y, inz = z; + o = Position.NormalizeOrientation(transO + o); + + x = transX + inx * (float)Math.Cos(transO) - iny * (float)Math.Sin(transO); + y = transY + iny * (float)Math.Cos(transO) + inx * (float)Math.Sin(transO); + z = transZ + inz; + } + + public static void CalculatePassengerOffset(ref float x, ref float y, ref float z, ref float o, float transX, float transY, float transZ, float transO) + { + o = Position.NormalizeOrientation(o - transO); + + z -= transZ; + y -= transY; + x -= transX; + + float inx = x, iny = y; + y = (iny - inx * (float)Math.Tan(transO)) / ((float)Math.Cos(transO) + (float)Math.Sin(transO) * (float)Math.Tan(transO)); + x = (inx + iny * (float)Math.Tan(transO)) / ((float)Math.Cos(transO) + (float)Math.Sin(transO) * (float)Math.Tan(transO)); + } + } + + public class Transport : GameObject, ITransport + { + public Transport() + { + _isMoving = true; + + m_updateFlag = UpdateFlag.Transport | UpdateFlag.StationaryPosition | UpdateFlag.Rotation; + } + + public override void Dispose() + { + Contract.Assert(_passengers.Empty()); + UnloadStaticPassengers(); + base.Dispose(); + } + + public bool Create(ulong guidlow, uint entry, uint mapid, float x, float y, float z, float ang, uint animprogress) + { + Relocate(x, y, z, ang); + + if (!IsPositionValid()) + { + Log.outError(LogFilter.Transport, "Transport (GUID: {0}) not created. Suggested coordinates isn't valid (X: {1} Y: {2})", + guidlow, x, y); + return false; + } + + _Create(ObjectGuid.Create(HighGuid.Transport, guidlow)); + + GameObjectTemplate goinfo = Global.ObjectMgr.GetGameObjectTemplate(entry); + + if (goinfo == null) + { + Log.outError(LogFilter.Sql, "Transport not created: entry in `gameobject_template` not found, guidlow: {0} map: {1} (X: {2} Y: {3} Z: {4}) ang: {5}", guidlow, mapid, x, y, z, ang); + return false; + } + + m_goInfo = goinfo; + m_goTemplateAddon = Global.ObjectMgr.GetGameObjectTemplateAddon(entry); + + TransportTemplate tInfo = Global.TransportMgr.GetTransportTemplate(entry); + if (tInfo == null) + { + Log.outError(LogFilter.Sql, "Transport {0} (name: {1}) will not be created, missing `transport_template` entry.", entry, goinfo.name); + return false; + } + + _transportInfo = tInfo; + _nextFrame = 0; + _currentFrame = tInfo.keyFrames[_nextFrame++]; + _triggeredArrivalEvent = false; + _triggeredDepartureEvent = false; + + if (m_goTemplateAddon != null) + { + SetFaction(m_goTemplateAddon.faction); + SetUInt32Value(GameObjectFields.Flags, m_goTemplateAddon.flags); + } + + m_goValue.Transport.PathProgress = 0; + SetFloatValue(ObjectFields.ScaleX, goinfo.size); + SetPeriod(tInfo.pathTime); + SetEntry(goinfo.entry); + SetDisplayId(goinfo.displayId); + SetGoState(goinfo.MoTransport.allowstopping == 0 ? GameObjectState.Ready : GameObjectState.Active); + SetGoType(GameObjectTypes.MapObjTransport); + SetGoAnimProgress(animprogress); + SetName(goinfo.name); + SetWorldRotation(Quaternion.WAxis); + SetParentRotation(Quaternion.WAxis); + + m_model = CreateModel(); + return true; + } + + public override void CleanupsBeforeDelete(bool finalCleanup) + { + UnloadStaticPassengers(); + while (!_passengers.Empty()) + { + WorldObject obj = _passengers.FirstOrDefault(); + RemovePassenger(obj); + } + + base.CleanupsBeforeDelete(finalCleanup); + } + + public override void Update(uint diff) + { + int positionUpdateDelay = 200; + + if (GetAI() != null) + GetAI().UpdateAI(diff); + else if (!AIM_Initialize()) + Log.outError(LogFilter.Transport, "Could not initialize GameObjectAI for Transport"); + + if (GetKeyFrames().Count <= 1) + return; + + if (IsMoving() || !_pendingStop) + m_goValue.Transport.PathProgress += diff; + + uint timer = m_goValue.Transport.PathProgress % GetTransportPeriod(); + + // Set current waypoint + // Desired outcome: _currentFrame.DepartureTime < timer < _nextFrame.ArriveTime + // ... arrive | ... delay ... | departure + // event / event / + for (; ; ) + { + if (timer >= _currentFrame.ArriveTime) + { + if (!_triggeredArrivalEvent) + { + DoEventIfAny(_currentFrame, false); + _triggeredArrivalEvent = true; + } + + if (timer < _currentFrame.DepartureTime) + { + SetMoving(false); + if (_pendingStop && GetGoState() != GameObjectState.Ready) + { + SetGoState(GameObjectState.Ready); + m_goValue.Transport.PathProgress = (m_goValue.Transport.PathProgress / GetTransportPeriod()); + m_goValue.Transport.PathProgress *= GetTransportPeriod(); + m_goValue.Transport.PathProgress += _currentFrame.ArriveTime; + } + break; // its a stop frame and we are waiting + } + } + + if (timer >= _currentFrame.DepartureTime && !_triggeredDepartureEvent) + { + DoEventIfAny(_currentFrame, true); // departure event + _triggeredDepartureEvent = true; + } + + // not waiting anymore + SetMoving(true); + + // Enable movement + if (GetGoInfo().MoTransport.allowstopping != 0) + SetGoState(GameObjectState.Active); + + if (timer >= _currentFrame.DepartureTime && timer < _currentFrame.NextArriveTime) + break; // found current waypoint + + MoveToNextWaypoint(); + + Global.ScriptMgr.OnRelocate(this, _currentFrame.Node.NodeIndex, _currentFrame.Node.MapID, _currentFrame.Node.Loc.X, _currentFrame.Node.Loc.Y, _currentFrame.Node.Loc.Z); + + Log.outDebug(LogFilter.Transport, "Transport {0} ({1}) moved to node {2} {3} {4} {5} {6}", GetEntry(), GetName(), _currentFrame.Node.NodeIndex, _currentFrame.Node.MapID, + _currentFrame.Node.Loc.X, _currentFrame.Node.Loc.Y, _currentFrame.Node.Loc.Z); + + // Departure event + var nextframe = GetKeyFrames()[_nextFrame]; + if (_currentFrame.IsTeleportFrame()) + if (TeleportTransport(nextframe.Node.MapID, nextframe.Node.Loc.X, nextframe.Node.Loc.Y, nextframe.Node.Loc.Z, nextframe.InitialOrientation)) + return; + } + + // Add model to map after we are fully done with moving maps + if (_delayedAddModel) + { + _delayedAddModel = false; + if (m_model != null) + GetMap().InsertGameObjectModel(m_model); + } + + // Set position + _positionChangeTimer.Update((int)diff); + if (_positionChangeTimer.Passed()) + { + _positionChangeTimer.Reset(positionUpdateDelay); + if (IsMoving()) + { + float t = CalculateSegmentPos(timer * 0.001f); + Vector3 pos, dir; + _currentFrame.Spline.Evaluate_Percent((int)_currentFrame.Index, t, out pos); + _currentFrame.Spline.Evaluate_Derivative((int)_currentFrame.Index, t, out dir); + UpdatePosition(pos.X, pos.Y, pos.Z, (float)Math.Atan2(dir.Y, dir.X) + MathFunctions.PI); + } + else + { + /* There are four possible scenarios that trigger loading/unloading passengers: + 1. transport moves from inactive to active grid + 2. the grid that transport is currently in becomes active + 3. transport moves from active to inactive grid + 4. the grid that transport is currently in unloads + */ + + if (_staticPassengers.Empty() && GetMap().IsGridLoaded(GetPositionX(), GetPositionY())) // 2. + LoadStaticPassengers(); + } + } + + Global.ScriptMgr.OnTransportUpdate(this, diff); + } + + public void DelayedUpdate(uint diff) + { + if (GetKeyFrames().Count <= 1) + return; + + DelayedTeleportTransport(); + } + + public void AddPassenger(WorldObject passenger) + { + if (!IsInWorld) + return; + + _passengers.Add(passenger); + passenger.SetTransport(this); + passenger.m_movementInfo.transport.guid = GetGUID(); + + if (passenger.IsTypeId(TypeId.Player)) + Global.ScriptMgr.OnAddPassenger(this, passenger.ToPlayer()); + } + + public void RemovePassenger(WorldObject passenger) + { + _passengers.Remove(passenger); + + if (passenger.IsTypeId(TypeId.Player)) + Global.ScriptMgr.OnRemovePassenger(this, passenger.ToPlayer()); + } + + public Creature CreateNPCPassenger(ulong guid, CreatureData data) + { + Map map = GetMap(); + Creature creature = new Creature(); + + if (!creature.LoadCreatureFromDB(guid, map, false)) + return null; + + float x = data.posX; + float y = data.posY; + float z = data.posZ; + float o = data.orientation; + + creature.SetTransport(this); + creature.m_movementInfo.transport.guid = GetGUID(); + creature.m_movementInfo.transport.pos.Relocate(x, y, z, o); + creature.m_movementInfo.transport.seat = -1; + CalculatePassengerPosition(ref x, ref y, ref z, ref o); + creature.Relocate(x, y, z, o); + creature.SetHomePosition(creature.GetPositionX(), creature.GetPositionY(), creature.GetPositionZ(), creature.GetOrientation()); + creature.SetTransportHomePosition(creature.m_movementInfo.transport.pos); + + // @HACK - transport models are not added to map's dynamic LoS calculations + // because the current GameObjectModel cannot be moved without recreating + creature.AddUnitState(UnitState.IgnorePathfinding); + + if (!creature.IsPositionValid()) + { + Log.outError(LogFilter.Transport, "Creature (guidlow {0}, entry {1}) not created. Suggested coordinates aren't valid (X: {2} Y: {3})", creature.GetGUID().ToString(), creature.GetEntry(), creature.GetPositionX(), creature.GetPositionY()); + return null; + } + + if (data.phaseid != 0) + creature.SetInPhase(data.phaseid, false, true); + else if (data.phaseGroup != 0) + { + foreach (var phase in Global.DB2Mgr.GetPhasesForGroup(data.phaseGroup)) + creature.SetInPhase(phase, false, true); + } + else + creature.CopyPhaseFrom(this); + + if (!map.AddToMap(creature)) + return null; + + _staticPassengers.Add(creature); + Global.ScriptMgr.OnAddCreaturePassenger(this, creature); + return creature; + } + + GameObject CreateGOPassenger(ulong guid, GameObjectData data) + { + Map map = GetMap(); + GameObject go = new GameObject(); + + if (!go.LoadGameObjectFromDB(guid, map, false)) + return null; + + float x = data.posX; + float y = data.posY; + float z = data.posZ; + float o = data.orientation; + + go.SetTransport(this); + go.m_movementInfo.transport.guid = GetGUID(); + go.m_movementInfo.transport.pos.Relocate(x, y, z, o); + go.m_movementInfo.transport.seat = -1; + CalculatePassengerPosition(ref x, ref y, ref z, ref o); + go.Relocate(x, y, z, o); + go.RelocateStationaryPosition(x, y, z, o); + + if (!go.IsPositionValid()) + { + Log.outError(LogFilter.Transport, "GameObject (guidlow {0}, entry {1}) not created. Suggested coordinates aren't valid (X: {2} Y: {3})", go.GetGUID().ToString(), go.GetEntry(), go.GetPositionX(), go.GetPositionY()); + return null; + } + + if (!map.AddToMap(go)) + return null; + + _staticPassengers.Add(go); + return go; + } + + public TempSummon SummonPassenger(uint entry, Position pos, TempSummonType summonType, SummonPropertiesRecord properties = null, uint duration = 0, Unit summoner = null, uint spellId = 0, uint vehId = 0) + { + Map map = GetMap(); + if (map == null) + return null; + + UnitTypeMask mask = UnitTypeMask.Summon; + if (properties != null) + { + switch (properties.Category) + { + case SummonCategory.Pet: + mask = UnitTypeMask.Guardian; + break; + case SummonCategory.Puppet: + mask = UnitTypeMask.Puppet; + break; + case SummonCategory.Vehicle: + mask = UnitTypeMask.Minion; + break; + case SummonCategory.Wild: + case SummonCategory.Ally: + case SummonCategory.Unk: + { + switch (properties.Type) + { + case SummonType.Minion: + case SummonType.Guardian: + case SummonType.Guardian2: + mask = UnitTypeMask.Guardian; + break; + case SummonType.Totem: + case SummonType.LightWell: + mask = UnitTypeMask.Totem; + break; + case SummonType.Vehicle: + case SummonType.Vehicle2: + mask = UnitTypeMask.Summon; + break; + case SummonType.Minipet: + mask = UnitTypeMask.Minion; + break; + default: + if (properties.Flags.HasAnyFlag(512)) // Mirror Image, Summon Gargoyle + mask = UnitTypeMask.Guardian; + break; + } + break; + } + default: + return null; + } + } + + List phases = new List(); + if (summoner) + phases = summoner.GetPhases(); + else + phases = GetPhases(); // If there was no summoner, try to use the transport phases + + TempSummon summon = null; + switch (mask) + { + case UnitTypeMask.Summon: + summon = new TempSummon(properties, summoner, false); + break; + case UnitTypeMask.Guardian: + summon = new Guardian(properties, summoner, false); + break; + case UnitTypeMask.Puppet: + summon = new Puppet(properties, summoner); + break; + case UnitTypeMask.Totem: + summon = new Totem(properties, summoner); + break; + case UnitTypeMask.Minion: + summon = new Minion(properties, summoner, false); + break; + } + + float x, y, z, o; + pos.GetPosition(out x, out y, out z, out o); + CalculatePassengerPosition(ref x, ref y, ref z, ref o); + + if (!summon.Create(map.GenerateLowGuid(HighGuid.Creature), map, 0, entry, x, y, z, o, null, vehId)) + return null; + + foreach (var phase in phases) + summon.SetInPhase(phase, false, true); + + summon.SetUInt32Value(UnitFields.CreatedBySpell, spellId); + + summon.SetTransport(this); + summon.m_movementInfo.transport.guid = GetGUID(); + summon.m_movementInfo.transport.pos.Relocate(pos); + summon.Relocate(x, y, z, o); + summon.SetHomePosition(x, y, z, o); + summon.SetTransportHomePosition(pos); + + // @HACK - transport models are not added to map's dynamic LoS calculations + // because the current GameObjectModel cannot be moved without recreating + summon.AddUnitState(UnitState.IgnorePathfinding); + + summon.InitStats(duration); + + if (!map.AddToMap(summon)) + return null; + + _staticPassengers.Add(summon); + + summon.InitSummon(); + summon.SetTempSummonType(summonType); + + return summon; + } + + public void CalculatePassengerPosition(ref float x, ref float y, ref float z, ref float o) + { + TransportPosHelper.CalculatePassengerPosition(ref x, ref y, ref z, ref o, GetPositionX(), GetPositionY(), GetPositionZ(), GetOrientation()); + } + + public void CalculatePassengerOffset(ref float x, ref float y, ref float z, ref float o) + { + TransportPosHelper.CalculatePassengerOffset(ref x, ref y, ref z, ref o, GetPositionX(), GetPositionY(), GetPositionZ(), GetOrientation()); + } + + public void UpdatePosition(float x, float y, float z, float o) + { + bool newActive = GetMap().IsGridLoaded(x, y); + + Relocate(x, y, z, o); + UpdateModelPosition(); + + UpdatePassengerPositions(_passengers); + + /* There are four possible scenarios that trigger loading/unloading passengers: + 1. transport moves from inactive to active grid + 2. the grid that transport is currently in becomes active + 3. transport moves from active to inactive grid + 4. the grid that transport is currently in unloads + */ + if (_staticPassengers.Empty() && newActive) // 1. and 2. + LoadStaticPassengers(); + else if (!_staticPassengers.Empty() && !newActive && new Cell(x, y).DiffGrid(new Cell(GetPositionX(), GetPositionY()))) // 3. + UnloadStaticPassengers(); + else + UpdatePassengerPositions(_staticPassengers); + // 4. is handed by grid unload + } + + void LoadStaticPassengers() + { + uint mapId = (uint)GetGoInfo().MoTransport.SpawnMap; + var cells = Global.ObjectMgr.GetMapObjectGuids(mapId, (byte)GetMap().GetSpawnMode()); + if (cells == null) + return; + foreach (var cell in cells) + { + // Creatures on transport + foreach (var npc in cell.Value.creatures) + CreateNPCPassenger(npc, Global.ObjectMgr.GetCreatureData(npc)); + + // GameObjects on transport + foreach (var go in cell.Value.gameobjects) + CreateGOPassenger(go, Global.ObjectMgr.GetGOData(go)); + } + } + + void UnloadStaticPassengers() + { + while (!_staticPassengers.Empty()) + { + WorldObject obj = _staticPassengers.First(); + _staticPassengers.RemoveAt(0); + obj.AddObjectToRemoveList(); // also removes from _staticPassengers + } + } + + public void EnableMovement(bool enabled) + { + if (GetGoInfo().MoTransport.allowstopping == 0) + return; + + _pendingStop = !enabled; + } + + public void SetDelayedAddModelToMap() { _delayedAddModel = true; } + + void MoveToNextWaypoint() + { + // Clear events flagging + _triggeredArrivalEvent = false; + _triggeredDepartureEvent = false; + + // Set frames + _currentFrame = GetKeyFrames()[_nextFrame++]; + if (_nextFrame == GetKeyFrames().Count) + _nextFrame = 0; + } + + float CalculateSegmentPos(float now) + { + KeyFrame frame = _currentFrame; + float speed = GetGoInfo().MoTransport.moveSpeed; + float accel = GetGoInfo().MoTransport.accelRate; + float timeSinceStop = frame.TimeFrom + (now - (1.0f / Time.InMilliseconds) * frame.DepartureTime); + float timeUntilStop = frame.TimeTo - (now - (1.0f / Time.InMilliseconds) * frame.DepartureTime); + float segmentPos, dist; + float accelTime = _transportInfo.accelTime; + float accelDist = _transportInfo.accelDist; + // calculate from nearest stop, less confusing calculation... + if (timeSinceStop < timeUntilStop) + { + if (timeSinceStop < accelTime) + dist = 0.5f * accel * timeSinceStop * timeSinceStop; + else + dist = accelDist + (timeSinceStop - accelTime) * speed; + segmentPos = dist - frame.DistSinceStop; + } + else + { + if (timeUntilStop < _transportInfo.accelTime) + dist = (0.5f * accel) * (timeUntilStop * timeUntilStop); + else + dist = accelDist + (timeUntilStop - accelTime) * speed; + segmentPos = frame.DistUntilStop - dist; + } + + return segmentPos / frame.NextDistFromPrev; + } + + bool TeleportTransport(uint newMapid, float x, float y, float z, float o) + { + Map oldMap = GetMap(); + + if (oldMap.GetId() != newMapid) + { + _delayedTeleport = true; + UnloadStaticPassengers(); + return true; + } + else + { + // Teleport players, they need to know it + foreach (var obj in _passengers) + { + if (obj.IsTypeId(TypeId.Player)) + { + // will be relocated in UpdatePosition of the vehicle + Unit veh = obj.ToUnit().GetVehicleBase(); + if (veh) + if (veh.GetTransport() == this) + continue; + + float destX, destY, destZ, destO; + obj.m_movementInfo.transport.pos.GetPosition(out destX, out destY, out destZ, out destO); + TransportPosHelper.CalculatePassengerPosition(ref destX, ref destY, ref destZ, ref destO, x, y, z, o); + + obj.ToUnit().NearTeleportTo(destX, destY, destZ, destO); + } + } + + UpdatePosition(x, y, z, o); + return false; + } + } + + void DelayedTeleportTransport() + { + if (!_delayedTeleport) + return; + + var nextFrame = GetKeyFrames()[_nextFrame]; + + _delayedTeleport = false; + Map newMap = Global.MapMgr.CreateBaseMap(nextFrame.Node.MapID); + GetMap().RemoveFromMap(this, false); + SetMap(newMap); + + float x = nextFrame.Node.Loc.X, + y = nextFrame.Node.Loc.Y, + z = nextFrame.Node.Loc.Z, + o = nextFrame.InitialOrientation; + + foreach (var obj in _passengers) + { + float destX, destY, destZ, destO; + obj.m_movementInfo.transport.pos.GetPosition(out destX, out destY, out destZ, out destO); + TransportPosHelper.CalculatePassengerPosition(ref destX, ref destY, ref destZ, ref destO, x, y, z, o); + + switch (obj.GetTypeId()) + { + case TypeId.Player: + if (!obj.ToPlayer().TeleportTo(nextFrame.Node.MapID, destX, destY, destZ, destO, TeleportToOptions.NotLeaveTransport)) + RemovePassenger(obj); + break; + case TypeId.DynamicObject: + case TypeId.AreaTrigger: + obj.AddObjectToRemoveList(); + break; + default: + RemovePassenger(obj); + break; + } + } + + Relocate(x, y, z, o); + GetMap().AddToMap(this); + } + + void UpdatePassengerPositions(List passengers) + { + foreach (var passenger in passengers) + { + // transport teleported but passenger not yet (can happen for players) + if (passenger.GetMap() != GetMap()) + continue; + + // if passenger is on vehicle we have to assume the vehicle is also on transport + // and its the vehicle that will be updating its passengers + Unit unit = passenger.ToUnit(); + if (unit) + if (unit.GetVehicle() != null) + continue; + + // Do not use Unit.UpdatePosition here, we don't want to remove auras + // as if regular movement occurred + float x, y, z, o; + passenger.m_movementInfo.transport.pos.GetPosition(out x, out y, out z, out o); + CalculatePassengerPosition(ref x, ref y, ref z, ref o); + switch (passenger.GetTypeId()) + { + case TypeId.Unit: + { + Creature creature = passenger.ToCreature(); + GetMap().CreatureRelocation(creature, x, y, z, o, false); + creature.GetTransportHomePosition(out x, out y, out z, out o); + CalculatePassengerPosition(ref x, ref y, ref z, ref o); + creature.SetHomePosition(x, y, z, o); + break; + } + case TypeId.Player: + if (passenger.IsInWorld) + GetMap().PlayerRelocation(passenger.ToPlayer(), x, y, z, o); + break; + case TypeId.GameObject: + GetMap().GameObjectRelocation(passenger.ToGameObject(), x, y, z, o, false); + passenger.ToGameObject().RelocateStationaryPosition(x, y, z, o); + break; + case TypeId.DynamicObject: + GetMap().DynamicObjectRelocation(passenger.ToDynamicObject(), x, y, z, o); + break; + case TypeId.AreaTrigger: + GetMap().AreaTriggerRelocation(passenger.ToAreaTrigger(), x, y, z, o); + break; + default: + break; + } + + if (unit != null) + { + Vehicle vehicle = unit.GetVehicleKit(); + if (vehicle != null) + vehicle.RelocatePassengers(); + } + } + } + + void DoEventIfAny(KeyFrame node, bool departure) + { + uint eventid = departure ? node.Node.DepartureEventID : node.Node.ArrivalEventID; + if (eventid != 0) + { + Log.outDebug(LogFilter.Scripts, "Taxi {0} event {1} of node {2} of {3} path", departure ? "departure" : "arrival", eventid, node.Node.NodeIndex, GetName()); + GetMap().ScriptsStart(ScriptsType.Event, eventid, this, this); + EventInform(eventid); + } + } + + public override void BuildUpdate(Dictionary data_map) + { + var players = GetMap().GetPlayers(); + if (players.Empty()) + return; + + foreach (var pl in players) + BuildFieldsUpdate(pl, data_map); + + ClearUpdateMask(true); + } + + public List GetPassengers() { return _passengers; } + + public override uint GetTransportPeriod() { return GetUInt32Value(GameObjectFields.Level); } + public void SetPeriod(uint period) { SetUInt32Value(GameObjectFields.Level, period); } + uint GetTimer() { return m_goValue.Transport.PathProgress; } + + public List GetKeyFrames() { return _transportInfo.keyFrames; } + public TransportTemplate GetTransportTemplate() { return _transportInfo; } + + //! Helpers to know if stop frame was reached + bool IsMoving() { return _isMoving; } + void SetMoving(bool val) { _isMoving = val; } + + TransportTemplate _transportInfo; + + KeyFrame _currentFrame; + int _nextFrame; + TimeTrackerSmall _positionChangeTimer = new TimeTrackerSmall(); + bool _isMoving; + bool _pendingStop; + + //! These are needed to properly control events triggering only once for each frame + bool _triggeredArrivalEvent; + bool _triggeredDepartureEvent; + + List _passengers = new List(); + List _staticPassengers = new List(); + + bool _delayedAddModel; + bool _delayedTeleport; + } +} diff --git a/Game/Entities/Unit/CharmInfo.cs b/Game/Entities/Unit/CharmInfo.cs new file mode 100644 index 000000000..51c03b57a --- /dev/null +++ b/Game/Entities/Unit/CharmInfo.cs @@ -0,0 +1,441 @@ +/* + * Copyright (C) 2012-2017 CypherCore + * + * 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, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +using Framework.Collections; +using Framework.Constants; +using Framework.GameMath; +using Game.Network; +using Game.Spells; +using System; + +namespace Game.Entities +{ + public class CharmInfo + { + public CharmInfo(Unit unit) + { + _unit = unit; + _CommandState = CommandStates.Follow; + _petnumber = 0; + _oldReactState = ReactStates.Passive; + for (byte i = 0; i < SharedConst.MaxSpellCharm; ++i) + { + _charmspells[i] = new UnitActionBarEntry(); + _charmspells[i].SetActionAndType(0, ActiveStates.Disabled); + } + + for (var i = 0; i < SharedConst.ActionBarIndexMax; ++i) + PetActionBar[i] = new UnitActionBarEntry(); + + if (_unit.IsTypeId(TypeId.Unit)) + { + _oldReactState = _unit.ToCreature().GetReactState(); + _unit.ToCreature().SetReactState(ReactStates.Passive); + } + } + + public void RestoreState() + { + if (_unit.IsTypeId(TypeId.Unit)) + { + Creature creature = _unit.ToCreature(); + if (creature) + creature.SetReactState(_oldReactState); + } + } + + public void InitPetActionBar() + { + // the first 3 SpellOrActions are attack, follow and stay + for (byte i = 0; i < SharedConst.ActionBarIndexPetSpellStart - SharedConst.ActionBarIndexStart; ++i) + SetActionBar((byte)(SharedConst.ActionBarIndexStart + i), (uint)CommandStates.Attack - i, ActiveStates.Command); + + // middle 4 SpellOrActions are spells/special attacks/abilities + for (byte i = 0; i < SharedConst.ActionBarIndexPetSpellEnd - SharedConst.ActionBarIndexPetSpellStart; ++i) + SetActionBar((byte)(SharedConst.ActionBarIndexPetSpellStart + i), 0, ActiveStates.Passive); + + // last 3 SpellOrActions are reactions + for (byte i = 0; i < SharedConst.ActionBarIndexEnd - SharedConst.ActionBarIndexPetSpellEnd; ++i) + SetActionBar((byte)(SharedConst.ActionBarIndexPetSpellEnd + i), (uint)CommandStates.Attack - i, ActiveStates.Reaction); + } + + public void InitEmptyActionBar(bool withAttack = true) + { + if (withAttack) + SetActionBar(SharedConst.ActionBarIndexStart, (uint)CommandStates.Attack, ActiveStates.Command); + else + SetActionBar(SharedConst.ActionBarIndexStart, 0, ActiveStates.Passive); + for (byte x = SharedConst.ActionBarIndexStart + 1; x < SharedConst.ActionBarIndexEnd; ++x) + SetActionBar(x, 0, ActiveStates.Passive); + } + + public void InitPossessCreateSpells() + { + if (_unit.IsTypeId(TypeId.Unit)) + { + // Adding switch until better way is found. Malcrom + // Adding entrys to this switch will prevent COMMAND_ATTACK being added to pet bar. + switch (_unit.GetEntry()) + { + case 23575: // Mindless Abomination + case 24783: // Trained Rock Falcon + case 27664: // Crashin' Thrashin' Racer + case 40281: // Crashin' Thrashin' Racer + break; + default: + InitEmptyActionBar(); + break; + } + + for (byte i = 0; i < SharedConst.MaxCreatureSpells; ++i) + { + uint spellId = _unit.ToCreature().m_spells[i]; + SpellInfo spellInfo = Global.SpellMgr.GetSpellInfo(spellId); + if (spellInfo != null) + { + if (spellInfo.IsPassive()) + _unit.CastSpell(_unit, spellInfo, true); + else + AddSpellToActionBar(spellInfo, ActiveStates.Passive, i % SharedConst.ActionBarIndexMax); + } + } + } + else + InitEmptyActionBar(); + } + + public void InitCharmCreateSpells() + { + if (_unit.IsTypeId(TypeId.Player)) // charmed players don't have spells + { + InitEmptyActionBar(); + return; + } + + InitPetActionBar(); + + for (uint x = 0; x < SharedConst.MaxSpellCharm; ++x) + { + uint spellId = _unit.ToCreature().m_spells[x]; + SpellInfo spellInfo = Global.SpellMgr.GetSpellInfo(spellId); + + if (spellInfo == null) + { + _charmspells[x].SetActionAndType(spellId, ActiveStates.Disabled); + continue; + } + + if (spellInfo.IsPassive()) + { + _unit.CastSpell(_unit, spellInfo, true); + _charmspells[x].SetActionAndType(spellId, ActiveStates.Passive); + } + else + { + _charmspells[x].SetActionAndType(spellId, ActiveStates.Disabled); + + ActiveStates newstate = ActiveStates.Passive; + + if (!spellInfo.IsAutocastable()) + newstate = ActiveStates.Passive; + else + { + if (spellInfo.NeedsExplicitUnitTarget()) + { + newstate = ActiveStates.Enabled; + ToggleCreatureAutocast(spellInfo, true); + } + else + newstate = ActiveStates.Disabled; + } + + AddSpellToActionBar(spellInfo, newstate); + } + } + } + + public bool AddSpellToActionBar(SpellInfo spellInfo, ActiveStates newstate = ActiveStates.Decide, int preferredSlot = 0) + { + uint spell_id = spellInfo.Id; + uint first_id = spellInfo.GetFirstRankSpell().Id; + + // new spell rank can be already listed + for (byte i = 0; i < SharedConst.ActionBarIndexMax; ++i) + { + uint action = PetActionBar[i].GetAction(); + if (action != 0) + { + if (PetActionBar[i].IsActionBarForSpell() && Global.SpellMgr.GetFirstSpellInChain(action) == first_id) + { + PetActionBar[i].SetAction(spell_id); + return true; + } + } + } + + // or use empty slot in other case + for (byte i = 0; i < SharedConst.ActionBarIndexMax; ++i) + { + byte j = (byte)((preferredSlot + i) % SharedConst.ActionBarIndexMax); + if (PetActionBar[j].GetAction() == 0 && PetActionBar[j].IsActionBarForSpell()) + { + SetActionBar(j, spell_id, newstate == ActiveStates.Decide ? spellInfo.IsAutocastable() ? ActiveStates.Disabled : ActiveStates.Passive : newstate); + return true; + } + } + return false; + } + + public bool RemoveSpellFromActionBar(uint spell_id) + { + uint first_id = Global.SpellMgr.GetFirstSpellInChain(spell_id); + + for (byte i = 0; i < SharedConst.ActionBarIndexMax; ++i) + { + uint action = PetActionBar[i].GetAction(); + if (action != 0) + { + if (PetActionBar[i].IsActionBarForSpell() && Global.SpellMgr.GetFirstSpellInChain(action) == first_id) + { + SetActionBar(i, 0, ActiveStates.Passive); + return true; + } + } + } + + return false; + } + + public void ToggleCreatureAutocast(SpellInfo spellInfo, bool apply) + { + if (spellInfo.IsPassive()) + return; + + for (uint x = 0; x < SharedConst.MaxSpellCharm; ++x) + if (spellInfo.Id == _charmspells[x].GetAction()) + _charmspells[x].SetType(apply ? ActiveStates.Enabled : ActiveStates.Disabled); + } + + public void SetPetNumber(uint petnumber, bool statwindow) + { + _petnumber = petnumber; + if (statwindow) + _unit.SetUInt32Value(UnitFields.PetNumber, _petnumber); + else + _unit.SetUInt32Value(UnitFields.PetNumber, 0); + } + + public void LoadPetActionBar(string data) + { + InitPetActionBar(); + + var tokens = new StringArray(data, ' '); + if (tokens.Length != (SharedConst.ActionBarIndexEnd - SharedConst.ActionBarIndexStart) * 2) + return; // non critical, will reset to default + + for (byte index = SharedConst.ActionBarIndexStart; index < SharedConst.ActionBarIndexEnd; ++index) + { + ActiveStates type = tokens[index++].ToEnum(); + uint action = uint.Parse(tokens[index]); + + PetActionBar[index].SetActionAndType(action, type); + + // check correctness + if (PetActionBar[index].IsActionBarForSpell()) + { + SpellInfo spelInfo = Global.SpellMgr.GetSpellInfo(PetActionBar[index].GetAction()); + if (spelInfo == null) + SetActionBar(index, 0, ActiveStates.Passive); + else if (!spelInfo.IsAutocastable()) + SetActionBar(index, PetActionBar[index].GetAction(), ActiveStates.Passive); + } + } + } + + public void BuildActionBar(WorldPacket data) + { + for (int i = 0; i < SharedConst.ActionBarIndexMax; ++i) + data.WriteUInt32(PetActionBar[i].packedData); + } + + public void SetSpellAutocast(SpellInfo spellInfo, bool state) + { + for (byte i = 0; i < SharedConst.ActionBarIndexMax; ++i) + { + if (spellInfo.Id == PetActionBar[i].GetAction() && PetActionBar[i].IsActionBarForSpell()) + { + PetActionBar[i].SetType(state ? ActiveStates.Enabled : ActiveStates.Disabled); + break; + } + } + } + + public void SetIsCommandAttack(bool val) + { + _isCommandAttack = val; + } + + public bool IsCommandAttack() + { + return _isCommandAttack; + } + + public void SetIsCommandFollow(bool val) + { + _isCommandFollow = val; + } + + public bool IsCommandFollow() + { + return _isCommandFollow; + } + + public void SaveStayPosition() + { + //! At this point a new spline destination is enabled because of Unit.StopMoving() + Vector3 stayPos = _unit.moveSpline.FinalDestination(); + + if (_unit.moveSpline.onTransport) + { + float o = 0; + ITransport transport = _unit.GetDirectTransport(); + if (transport != null) + transport.CalculatePassengerPosition(ref stayPos.X, ref stayPos.Y, ref stayPos.Z, ref o); + } + + _stayX = stayPos.X; + _stayY = stayPos.Y; + _stayZ = stayPos.Z; + } + + public void GetStayPosition(out float x, out float y, out float z) + { + x = _stayX; + y = _stayY; + z = _stayZ; + } + + public void SetIsAtStay(bool val) + { + _isAtStay = val; + } + + public bool IsAtStay() + { + return _isAtStay; + } + + public void SetIsFollowing(bool val) + { + _isFollowing = val; + } + + public bool IsFollowing() + { + return _isFollowing; + } + + public void SetIsReturning(bool val) + { + _isReturning = val; + } + + public bool IsReturning() + { + return _isReturning; + } + + public uint GetPetNumber() { return _petnumber; } + public void SetCommandState(CommandStates st) { _CommandState = st; } + public CommandStates GetCommandState() { return _CommandState; } + public bool HasCommandState(CommandStates state) { return (_CommandState == state); } + + public void SetActionBar(byte index, uint spellOrAction, ActiveStates type) + { + PetActionBar[index].SetActionAndType(spellOrAction, type); + } + public UnitActionBarEntry GetActionBarEntry(byte index) { return PetActionBar[index]; } + + public UnitActionBarEntry GetCharmSpell(byte index) { return _charmspells[index]; } + + Unit _unit; + UnitActionBarEntry[] PetActionBar = new UnitActionBarEntry[SharedConst.ActionBarIndexMax]; + UnitActionBarEntry[] _charmspells = new UnitActionBarEntry[4]; + CommandStates _CommandState; + uint _petnumber; + + ReactStates _oldReactState; + + bool _isCommandAttack; + bool _isCommandFollow; + bool _isAtStay; + bool _isFollowing; + bool _isReturning; + float _stayX; + float _stayY; + float _stayZ; + } + + public class UnitActionBarEntry + { + public UnitActionBarEntry() + { + packedData = (uint)ActiveStates.Disabled << 24; + } + + public ActiveStates GetActiveState() { return (ActiveStates)UNIT_ACTION_BUTTON_TYPE(packedData); } + + public uint GetAction() { return UNIT_ACTION_BUTTON_ACTION(packedData); } + + public bool IsActionBarForSpell() + { + ActiveStates Type = GetActiveState(); + return Type == ActiveStates.Disabled || Type == ActiveStates.Enabled || Type == ActiveStates.Passive; + } + + public void SetActionAndType(uint action, ActiveStates type) + { + packedData = MAKE_UNIT_ACTION_BUTTON(action, (uint)type); + } + + public void SetType(ActiveStates type) + { + packedData = MAKE_UNIT_ACTION_BUTTON(UNIT_ACTION_BUTTON_ACTION(packedData), (uint)type); + } + + public void SetAction(uint action) + { + packedData = (packedData & 0xFF000000) | UNIT_ACTION_BUTTON_ACTION(action); + } + + public uint packedData; + + public static uint MAKE_UNIT_ACTION_BUTTON(uint action, uint type) + { + return (action | (type << 24)); + } + public static uint UNIT_ACTION_BUTTON_ACTION(uint packedData) + { + return (packedData & 0x00FFFFFF); + } + public static uint UNIT_ACTION_BUTTON_TYPE(uint packedData) + { + return ((packedData & 0xFF000000) >> 24); + } + } + +} diff --git a/Game/Entities/Unit/Comparer.cs b/Game/Entities/Unit/Comparer.cs new file mode 100644 index 000000000..03b72095b --- /dev/null +++ b/Game/Entities/Unit/Comparer.cs @@ -0,0 +1,80 @@ +/* + * Copyright (C) 2012-2017 CypherCore + * + * 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, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +using Framework.Constants; +using System; +using System.Collections.Generic; + +namespace Game.Entities +{ + public class PowerPctOrderPred : IComparer + { + public PowerPctOrderPred(PowerType power, bool ascending = true) + { + m_power = power; + m_ascending = ascending; + } + + public int Compare(WorldObject objA, WorldObject objB) + { + Unit a = objA.ToUnit(); + Unit b = objB.ToUnit(); + float rA = a.GetMaxPower(m_power) != 0 ? a.GetPower(m_power) / (float)a.GetMaxPower(m_power) : 0.0f; + float rB = b.GetMaxPower(m_power) != 0 ? b.GetPower(m_power) / (float)b.GetMaxPower(m_power) : 0.0f; + return Convert.ToInt32(m_ascending ? rA < rB : rA > rB); + } + + PowerType m_power; + bool m_ascending; + } + + public class HealthPctOrderPred : IComparer + { + public HealthPctOrderPred(bool ascending = true) + { + m_ascending = ascending; + } + + public int Compare(WorldObject objA, WorldObject objB) + { + Unit a = objA.ToUnit(); + Unit b = objB.ToUnit(); + float rA = a.GetMaxHealth() != 0 ? a.GetHealth() / (float)a.GetMaxHealth() : 0.0f; + float rB = b.GetMaxHealth() != 0 ? b.GetHealth() / (float)b.GetMaxHealth() : 0.0f; + return Convert.ToInt32(m_ascending ? rA < rB : rA > rB); + } + + bool m_ascending; + } + + public class ObjectDistanceOrderPred : IComparer + { + public ObjectDistanceOrderPred(WorldObject pRefObj, bool ascending = true) + { + m_refObj = pRefObj; + m_ascending = ascending; + } + + public int Compare(WorldObject pLeft, WorldObject pRight) + { + return (m_ascending ? m_refObj.GetDistanceOrder(pLeft, pRight) : !m_refObj.GetDistanceOrder(pLeft, pRight)) ? 1 : 0; + } + + WorldObject m_refObj; + bool m_ascending; + } +} diff --git a/Game/Entities/Unit/Unit.Combat.cs b/Game/Entities/Unit/Unit.Combat.cs new file mode 100644 index 000000000..be8775a93 --- /dev/null +++ b/Game/Entities/Unit/Unit.Combat.cs @@ -0,0 +1,3173 @@ +/* + * Copyright (C) 2012-2017 CypherCore + * + * 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, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +using Framework.Constants; +using Game.BattleFields; +using Game.BattleGrounds; +using Game.Combat; +using Game.DataStorage; +using Game.Groups; +using Game.Loots; +using Game.Maps; +using Game.Network.Packets; +using Game.PvP; +using Game.Spells; +using System; +using System.Collections.Generic; +using System.Diagnostics.Contracts; +using System.Linq; + +namespace Game.Entities +{ + public partial class Unit + { + // Check if unit in combat with specific unit + public bool IsInCombatWith(Unit who) + { + // Check target exists + if (!who) + return false; + + // Search in threat list + ObjectGuid guid = who.GetGUID(); + foreach (var refe in GetThreatManager().getThreatList()) + { + // Return true if the unit matches + if (refe != null && refe.getUnitGuid() == guid) + return true; + } + + // Nothing found, false. + return false; + } + + public ThreatManager GetThreatManager() { return threatManager; } + + public bool CanDualWield() { return m_canDualWield; } + + public void SendChangeCurrentVictim(HostileReference pHostileReference) + { + if (!GetThreatManager().isThreatListEmpty()) + { + HighestThreatUpdate packet = new HighestThreatUpdate(); + packet.UnitGUID = GetGUID(); + packet.HighestThreatGUID = pHostileReference.getUnitGuid(); + + var refeList = GetThreatManager().getThreatList(); + foreach (var refe in refeList) + { + ThreatInfo info = new ThreatInfo(); + info.UnitGUID = refe.getUnitGuid(); + info.Threat = (long)refe.getThreat() * 100; + packet.ThreatList.Add(info); + } + SendMessageToSet(packet, false); + } + } + + public void StopAttackFaction(uint factionId) + { + Unit victim = GetVictim(); + if (victim != null) + { + if (victim.GetFactionTemplateEntry().Faction == factionId) + { + AttackStop(); + if (IsNonMeleeSpellCast(false)) + InterruptNonMeleeSpells(false); + + // melee and ranged forced attack cancel + if (IsTypeId(TypeId.Player)) + ToPlayer().SendAttackSwingCancelAttack(); + } + } + + var attackers = getAttackers(); + for (var i = 0; i < attackers.Count;) + { + var unit = attackers[i]; + if (unit.GetFactionTemplateEntry().Faction == factionId) + { + unit.AttackStop(); + i = 0; + } + else + ++i; + } + + getHostileRefManager().deleteReferencesForFaction(factionId); + + foreach (var control in m_Controlled) + control.StopAttackFaction(factionId); + } + + public void HandleProcExtraAttackFor(Unit victim) + { + while (m_extraAttacks != 0) + { + AttackerStateUpdate(victim, WeaponAttackType.BaseAttack, true); + --m_extraAttacks; + } + } + + public virtual void SetCanDualWield(bool value) { m_canDualWield = value; } + + void SendClearThreatList() + { + ThreatClear packet = new ThreatClear(); + packet.UnitGUID = GetGUID(); + SendMessageToSet(packet, false); + } + + public void SendRemoveFromThreatList(HostileReference pHostileReference) + { + ThreatRemove packet = new ThreatRemove(); + packet.UnitGUID = GetGUID(); + packet.AboutGUID = pHostileReference.getUnitGuid(); + SendMessageToSet(packet, false); + } + + void SendThreatListUpdate() + { + if (!GetThreatManager().isThreatListEmpty()) + { + int count = GetThreatManager().getThreatList().Count; + + ThreatUpdate packet = new ThreatUpdate(); + packet.UnitGUID = GetGUID(); + var tlist = GetThreatManager().getThreatList(); + foreach (var refe in tlist) + { + ThreatInfo info = new ThreatInfo(); + info.UnitGUID = refe.getUnitGuid(); + info.Threat = (long)refe.getThreat() * 100; + packet.ThreatList.Add(info); + } + SendMessageToSet(packet, false); + } + } + + public void DeleteThreatList() + { + if (CanHaveThreatList(true) && !threatManager.isThreatListEmpty()) + SendClearThreatList(); + threatManager.clearReferences(); + } + + public void TauntApply(Unit taunter) + { + Contract.Assert(IsTypeId(TypeId.Unit)); + + if (!taunter || (taunter.IsTypeId(TypeId.Player) && taunter.ToPlayer().IsGameMaster())) + return; + + if (!CanHaveThreatList()) + return; + + Creature creature = ToCreature(); + + if (creature.HasReactState(ReactStates.Passive)) + return; + + Unit target = GetVictim(); + if (target && target == taunter) + return; + + SetInFront(taunter); + if (creature.IsAIEnabled) + creature.GetAI().AttackStart(taunter); + } + + public void TauntFadeOut(Unit taunter) + { + Contract.Assert(IsTypeId(TypeId.Unit)); + + if (!taunter || (taunter.IsTypeId(TypeId.Player) && taunter.ToPlayer().IsGameMaster())) + return; + + if (!CanHaveThreatList()) + return; + + Creature creature = ToCreature(); + + if (creature.HasReactState(ReactStates.Passive)) + return; + + Unit target = GetVictim(); + if (!target || target != taunter) + return; + + if (threatManager.isThreatListEmpty()) + { + if (creature.IsAIEnabled) + creature.GetAI().EnterEvadeMode(EvadeReason.NoHostiles); + return; + } + + target = creature.SelectVictim(); // might have more taunt auras remaining + + if (target && target != taunter) + { + SetInFront(target); + if (creature.IsAIEnabled) + creature.GetAI().AttackStart(target); + } + } + + public void CombatStop(bool includingCast = false) + { + if (includingCast && IsNonMeleeSpellCast(false)) + InterruptNonMeleeSpells(false); + + AttackStop(); + RemoveAllAttackers(); + if (IsTypeId(TypeId.Player)) + ToPlayer().SendAttackSwingCancelAttack(); // melee and ranged forced attack cancel + ClearInCombat(); + } + public void CombatStopWithPets(bool includingCast = false) + { + CombatStop(includingCast); + + foreach (var control in m_Controlled) + control.CombatStop(includingCast); + } + public void ClearInCombat() + { + m_CombatTimer = 0; + RemoveFlag(UnitFields.Flags, UnitFlags.InCombat); + + // Reset rune flags after combat + if (IsTypeId(TypeId.Player) && GetClass() == Class.Deathknight) + { + for (byte i = 0; i < PlayerConst.MaxRunes; ++i) + { + ToPlayer().SetRuneTimer(i, 0xFFFFFFFF); + ToPlayer().SetLastRuneGraceTimer(i, 0); + } + } + + // Player's state will be cleared in Player.UpdateContestedPvP + Creature creature = ToCreature(); + if (creature != null) + { + ClearUnitState(UnitState.AttackPlayer); + if (HasFlag(ObjectFields.DynamicFlags, UnitDynFlags.Tapped)) + SetUInt32Value(ObjectFields.DynamicFlags, creature.GetCreatureTemplate().DynamicFlags); + + if (creature.IsPet() || creature.IsGuardian()) + { + Unit owner = GetOwner(); + if (owner) + for (UnitMoveType i = 0; i < UnitMoveType.Max; ++i) + if (owner.GetSpeedRate(i) > GetSpeedRate(i)) + SetSpeedRate(i, owner.GetSpeedRate(i)); + } + else if (!IsCharmed()) + return; + } + else + ToPlayer().OnCombatExit(); + + RemoveFlag(UnitFields.Flags, UnitFlags.PetInCombat); + RemoveAurasWithInterruptFlags(SpellAuraInterruptFlags.LeaveCombat); + } + + void RemoveAllAttackers() + { + while (!attackerList.Empty()) + { + var iter = attackerList.First(); + if (!iter.AttackStop()) + { + Log.outError(LogFilter.Unit, "WORLD: Unit has an attacker that isn't attacking it!"); + attackerList.Remove(iter); + } + } + } + + public void addHatedBy(HostileReference pHostileReference) + { + m_HostileRefManager.insertFirst(pHostileReference); + } + public void removeHatedBy(HostileReference pHostileReference) { } //nothing to do yet + + public void AddThreat(Unit victim, float fThreat, SpellSchoolMask schoolMask = SpellSchoolMask.Normal, SpellInfo threatSpell = null) + { + // Only mobs can manage threat lists + if (CanHaveThreatList() && !HasUnitState(UnitState.Evade)) + threatManager.addThreat(victim, fThreat, schoolMask, threatSpell); + } + public float ApplyTotalThreatModifier(float fThreat, SpellSchoolMask schoolMask = SpellSchoolMask.Normal) + { + if (!HasAuraType(AuraType.ModThreat) || fThreat < 0) + return fThreat; + + SpellSchools school = Global.SpellMgr.GetFirstSchoolInMask(schoolMask); + + return fThreat * m_threatModifier[(int)school]; + } + + public bool isTargetableForAttack(bool checkFakeDeath = true) + { + if (!IsAlive()) + return false; + + if (HasFlag(UnitFields.Flags, (UnitFlags.NonAttackable | UnitFlags.NotSelectable))) + return false; + + if (IsTypeId(TypeId.Player) && ToPlayer().IsGameMaster()) + return false; + + return !HasUnitState(UnitState.Unattackable) && (!checkFakeDeath || !HasUnitState(UnitState.Died)); + } + + public DeathState getDeathState() + { + return m_deathState; + } + public bool IsInCombat() + { + return HasFlag(UnitFields.Flags, UnitFlags.InCombat); + } + public bool Attack(Unit victim, bool meleeAttack) + { + if (victim == null || victim.GetGUID() == GetGUID()) + return false; + + // dead units can neither attack nor be attacked + if (!IsAlive() || !victim.IsInWorld || !victim.IsAlive()) + return false; + + // player cannot attack in mount state + if (IsTypeId(TypeId.Player) && IsMounted()) + return false; + + if (HasFlag(UnitFields.Flags, UnitFlags.Pacified)) + return false; + + // nobody can attack GM in GM-mode + if (victim.IsTypeId(TypeId.Player)) + { + if (victim.ToPlayer().IsGameMaster()) + return false; + } + else + { + if (victim.ToCreature().IsEvadingAttacks()) + return false; + } + + // remove SPELL_AURA_MOD_UNATTACKABLE at attack (in case non-interruptible spells stun aura applied also that not let attack) + if (HasAuraType(AuraType.ModUnattackable)) + RemoveAurasByType(AuraType.ModUnattackable); + + if (m_attacking != null) + { + if (m_attacking == victim) + { + // switch to melee attack from ranged/magic + if (meleeAttack) + { + if (!HasUnitState(UnitState.MeleeAttacking)) + { + AddUnitState(UnitState.MeleeAttacking); + SendMeleeAttackStart(victim); + return true; + } + } + else if (HasUnitState(UnitState.MeleeAttacking)) + { + ClearUnitState(UnitState.MeleeAttacking); + SendMeleeAttackStop(victim); + return true; + } + return false; + } + + // switch target + InterruptSpell(CurrentSpellTypes.Melee); + if (!meleeAttack) + ClearUnitState(UnitState.MeleeAttacking); + } + + if (m_attacking != null) + m_attacking._removeAttacker(this); + + m_attacking = victim; + m_attacking._addAttacker(this); + + // Set our target + SetTarget(victim.GetGUID()); + + if (meleeAttack) + AddUnitState(UnitState.MeleeAttacking); + + if (IsTypeId(TypeId.Unit) && !IsPet()) + { + // should not let player enter combat by right clicking target - doesn't helps + AddThreat(victim, 0.0f); + SetInCombatWith(victim); + + if (victim.IsTypeId(TypeId.Player)) + victim.SetInCombatWith(this); + + ToCreature().SendAIReaction(AiReaction.Hostile); + ToCreature().CallAssistance(); + + // Remove emote state - will be restored on creature reset + SetUInt32Value(UnitFields.NpcEmotestate, (uint)Emote.OneshotNone); + } + + // delay offhand weapon attack to next attack time + if (haveOffhandWeapon() && GetTypeId() != TypeId.Player) + resetAttackTimer(WeaponAttackType.OffAttack); + + if (meleeAttack) + SendMeleeAttackStart(victim); + + // Let the pet know we've started attacking someting. Handles melee attacks only + // Spells such as auto-shot and others handled in WorldSession.HandleCastSpellOpcode + if (IsTypeId(TypeId.Player)) + { + Pet playerPet = ToPlayer().GetPet(); + + if (playerPet != null && playerPet.IsAlive()) + playerPet.GetAI().OwnerAttacked(victim); + } + return true; + } + public void SendMeleeAttackStart(Unit victim) + { + AttackStart packet = new AttackStart(); + packet.Attacker = GetGUID(); + packet.Victim = victim.GetGUID(); + SendMessageToSet(packet, true); + } + public void SendMeleeAttackStop(Unit victim = null) + { + SendMessageToSet(new SAttackStop(this, victim), true); + + if (victim) + Log.outInfo(LogFilter.Unit, "{0} {1} stopped attacking {2} {3}", (IsTypeId(TypeId.Player) ? "Player" : "Creature"), GetGUID().ToString(), + (victim.IsTypeId(TypeId.Player) ? "player" : "creature"), victim.GetGUID().ToString()); + else + Log.outInfo(LogFilter.Unit, "{0} {1} stopped attacking", (IsTypeId(TypeId.Player) ? "Player" : "Creature"), GetGUID().ToString()); + } + public ObjectGuid GetTarget() { return GetGuidValue(UnitFields.Target); } + public virtual void SetTarget(ObjectGuid guid) { } + public bool AttackStop() + { + if (m_attacking == null) + return false; + + Unit victim = m_attacking; + + m_attacking._removeAttacker(this); + m_attacking = null; + + // Clear our target + SetTarget(ObjectGuid.Empty); + + ClearUnitState(UnitState.MeleeAttacking); + + InterruptSpell(CurrentSpellTypes.Melee); + + // reset only at real combat stop + Creature creature = ToCreature(); + if (creature != null) + { + creature.SetNoCallAssistance(false); + + if (creature.HasSearchedAssistance()) + { + creature.SetNoSearchAssistance(false); + UpdateSpeed(UnitMoveType.Run); + } + } + + SendMeleeAttackStop(victim); + return true; + } + void _addAttacker(Unit pAttacker) + { + attackerList.Add(pAttacker); + } + void _removeAttacker(Unit pAttacker) + { + attackerList.Remove(pAttacker); + } + public Unit GetVictim() + { + return m_attacking; + } + public Unit getAttackerForHelper() + { + if (GetVictim() != null) + return GetVictim(); + + if (attackerList.Count != 0) + return attackerList[0]; + + return null; + } + public List getAttackers() + { + return attackerList; + } + + public float GetCombatReach() { return GetFloatValue(UnitFields.CombatReach); } + float GetBoundaryRadius() { return GetFloatValue(UnitFields.BoundingRadius); } + + public bool haveOffhandWeapon() + { + if (IsTypeId(TypeId.Player)) + return ToPlayer().GetWeaponForAttack(WeaponAttackType.OffAttack, true) != null; + else + return m_canDualWield; + } + public void resetAttackTimer(WeaponAttackType type = WeaponAttackType.BaseAttack) + { + m_attackTimer[(int)type] = (uint)(GetBaseAttackTime(type) * m_modAttackSpeedPct[(int)type]); + } + public void setAttackTimer(WeaponAttackType type, uint time) + { + m_attackTimer[(int)type] = time; + } + public uint getAttackTimer(WeaponAttackType type) + { + return m_attackTimer[(int)type]; + } + public bool isAttackReady(WeaponAttackType type = WeaponAttackType.BaseAttack) + { + return m_attackTimer[(int)type] == 0; + } + public uint GetBaseAttackTime(WeaponAttackType att) + { + return m_baseAttackSpeed[(int)att]; + } + public void AttackerStateUpdate(Unit victim, WeaponAttackType attType = WeaponAttackType.BaseAttack, bool extra = false) + { + if (HasUnitState(UnitState.CannotAutoattack) || HasFlag(UnitFields.Flags, UnitFlags.Pacified)) + return; + + if (!victim.IsAlive()) + return; + + if ((attType == WeaponAttackType.BaseAttack || attType == WeaponAttackType.OffAttack) && !IsWithinLOSInMap(victim)) + return; + + CombatStart(victim); + RemoveAurasWithInterruptFlags(SpellAuraInterruptFlags.MeleeAttack); + + // ignore ranged case + if (attType != WeaponAttackType.BaseAttack && attType != WeaponAttackType.OffAttack) + return; + + if (IsTypeId(TypeId.Unit) && !HasFlag(UnitFields.Flags, UnitFlags.PlayerControlled)) + SetFacingToObject(victim); // update client side facing to face the target (prevents visual glitches when casting untargeted spells) + + // melee attack spell casted at main hand attack only - no normal melee dmg dealt + if (attType == WeaponAttackType.BaseAttack && GetCurrentSpell(CurrentSpellTypes.Melee) != null && !extra) + m_currentSpells[CurrentSpellTypes.Melee].cast(); + else + { + // attack can be redirected to another target + victim = GetMeleeHitRedirectTarget(victim); + + CalcDamageInfo damageInfo; + CalculateMeleeDamage(victim, 0, out damageInfo, attType); + // Send log damage message to client + DealDamageMods(victim, ref damageInfo.damage, ref damageInfo.absorb); + SendAttackStateUpdate(damageInfo); + + ProcDamageAndSpell(damageInfo.target, damageInfo.procAttacker, damageInfo.procVictim, damageInfo.procEx, damageInfo.damage, damageInfo.attackType); + + DealMeleeDamage(damageInfo, true); + + if (IsTypeId(TypeId.Player)) + Log.outDebug(LogFilter.Unit, "AttackerStateUpdate: (Player) {0} attacked {1} (TypeId: {2}) for {3} dmg, absorbed {4}, blocked {5}, resisted {6}.", + GetGUID().ToString(), victim.GetGUID().ToString(), victim.GetTypeId(), damageInfo.damage, damageInfo.absorb, damageInfo.blocked_amount, damageInfo.resist); + else + Log.outDebug(LogFilter.Unit, "AttackerStateUpdate: (NPC) {0} attacked {1} (TypeId: {2}) for {3} dmg, absorbed {4}, blocked {5}, resisted {6}.", + GetGUID().ToString(), victim.GetGUID().ToString(), victim.GetTypeId(), damageInfo.damage, damageInfo.absorb, damageInfo.blocked_amount, damageInfo.resist); + } + } + + public void FakeAttackerStateUpdate(Unit victim, WeaponAttackType attType = WeaponAttackType.BaseAttack) + { + if (HasUnitState(UnitState.CannotAutoattack) || HasFlag(UnitFields.Flags, UnitFlags.Pacified)) + return; + + if (!victim.IsAlive()) + return; + + if ((attType == WeaponAttackType.BaseAttack || attType == WeaponAttackType.OffAttack) && !IsWithinLOSInMap(victim)) + return; + + CombatStart(victim); + RemoveAurasWithInterruptFlags(SpellAuraInterruptFlags.MeleeAttack); + + if (attType != WeaponAttackType.BaseAttack && attType != WeaponAttackType.OffAttack) + return; // ignore ranged case + + if (IsTypeId(TypeId.Unit) && !HasFlag(UnitFields.Flags, UnitFlags.PlayerControlled)) + SetFacingToObject(victim); // update client side facing to face the target (prevents visual glitches when casting untargeted spells) + + CalcDamageInfo damageInfo = new CalcDamageInfo(); + damageInfo.attacker = this; + damageInfo.target = victim; + damageInfo.damageSchoolMask = (uint)GetMeleeDamageSchoolMask(); + damageInfo.attackType = attType; + damageInfo.damage = 0; + damageInfo.cleanDamage = 0; + damageInfo.absorb = 0; + damageInfo.resist = 0; + damageInfo.blocked_amount = 0; + + damageInfo.TargetState = VictimState.Hit; + damageInfo.HitInfo = HitInfo.AffectsVictim | HitInfo.NormalSwing | HitInfo.FakeDamage; + if (attType == WeaponAttackType.OffAttack) + damageInfo.HitInfo |= HitInfo.OffHand; + + damageInfo.procAttacker = ProcFlags.None; + damageInfo.procVictim = ProcFlags.None; + damageInfo.procEx = ProcFlagsExLegacy.None; + damageInfo.hitOutCome = MeleeHitOutcome.Normal; + + SendAttackStateUpdate(damageInfo); + } + + public void SetBaseWeaponDamage(WeaponAttackType attType, WeaponDamageRange damageRange, float value) { m_weaponDamage[(int)attType][(int)damageRange] = value; } + + void StartReactiveTimer(ReactiveType reactive) { m_reactiveTimer[reactive] = 4000; } + + public Unit GetMagicHitRedirectTarget(Unit victim, SpellInfo spellInfo) + { + // Patch 1.2 notes: Spell Reflection no longer reflects abilities + if (spellInfo.HasAttribute(SpellAttr0.Ability) || spellInfo.HasAttribute(SpellAttr1.CantBeRedirected) || spellInfo.HasAttribute(SpellAttr0.UnaffectedByInvulnerability)) + return victim; + + var magnetAuras = victim.GetAuraEffectsByType(AuraType.SpellMagnet); + foreach (var eff in magnetAuras) + { + Unit magnet = eff.GetBase().GetCaster(); + if (magnet != null) + { + if (spellInfo.CheckExplicitTarget(this, magnet) == SpellCastResult.SpellCastOk && _IsValidAttackTarget(magnet, spellInfo)) + { + // @todo handle this charge drop by proc in cast phase on explicit target + if (spellInfo.Speed > 0.0f) + { + // Set up missile speed based delay + uint delay = (uint)Math.Floor(Math.Max(victim.GetDistance(this), 5.0f) / spellInfo.Speed * 1000.0f); + // Schedule charge drop + eff.GetBase().DropChargeDelayed(delay, AuraRemoveMode.Expire); + } + else + eff.GetBase().DropCharge(AuraRemoveMode.Expire); + return magnet; + } + } + } + return victim; + } + public Unit GetMeleeHitRedirectTarget(Unit victim, SpellInfo spellInfo = null) + { + var hitTriggerAuras = victim.GetAuraEffectsByType(AuraType.AddCasterHitTrigger); + foreach (var i in hitTriggerAuras) + { + Unit magnet = i.GetCaster(); + if (magnet != null) + if (_IsValidAttackTarget(magnet, spellInfo) && magnet.IsWithinLOSInMap(this) + && (spellInfo == null || (spellInfo.CheckExplicitTarget(this, magnet) == SpellCastResult.SpellCastOk + && spellInfo.CheckTarget(this, magnet, false) == SpellCastResult.SpellCastOk))) + if (RandomHelper.randChance(i.GetAmount())) + { + i.GetBase().DropCharge(AuraRemoveMode.Expire); + return magnet; + } + } + return victim; + } + public bool IsValidAttackTarget(Unit target) + { + return _IsValidAttackTarget(target, null); + } + + void DealDamageMods(Unit victim, ref uint damage) + { + if (victim == null || !victim.IsAlive() || victim.HasUnitState(UnitState.InFlight) + || (victim.IsTypeId(TypeId.Unit) && victim.ToCreature().IsInEvadeMode())) + { + damage = 0; + } + } + public void DealDamageMods(Unit victim, ref uint damage, ref uint absorb) + { + if (victim == null || !victim.IsAlive() || victim.HasUnitState(UnitState.InFlight) + || (victim.IsTypeId(TypeId.Unit) && victim.ToCreature().IsEvadingAttacks())) + { + absorb += damage; + damage = 0; + } + } + void DealMeleeDamage(CalcDamageInfo damageInfo, bool durabilityLoss) + { + Unit victim = damageInfo.target; + + if (!victim.IsAlive() || victim.HasUnitState(UnitState.InFlight) || (victim.IsTypeId(TypeId.Unit) && victim.ToCreature().IsEvadingAttacks())) + return; + + // Hmmmm dont like this emotes client must by self do all animations + if (damageInfo.HitInfo.HasAnyFlag(HitInfo.CriticalHit)) + victim.HandleEmoteCommand(Emote.OneshotWoundCritical); + if (damageInfo.blocked_amount != 0 && damageInfo.TargetState != VictimState.Blocks) + victim.HandleEmoteCommand(Emote.OneshotParryShield); + + if (damageInfo.TargetState == VictimState.Parry && + (!IsTypeId(TypeId.Unit) || !ToCreature().GetCreatureTemplate().FlagsExtra.HasAnyFlag(CreatureFlagsExtra.NoParryHasten))) + { + // Get attack timers + float offtime = victim.getAttackTimer(WeaponAttackType.OffAttack); + float basetime = victim.getAttackTimer(WeaponAttackType.BaseAttack); + // Reduce attack time + if (victim.haveOffhandWeapon() && offtime < basetime) + { + float percent20 = victim.GetBaseAttackTime(WeaponAttackType.OffAttack) * 0.20f; + float percent60 = 3.0f * percent20; + if (offtime > percent20 && offtime <= percent60) + victim.setAttackTimer(WeaponAttackType.OffAttack, (uint)percent20); + else if (offtime > percent60) + { + offtime -= 2.0f * percent20; + victim.setAttackTimer(WeaponAttackType.OffAttack, (uint)offtime); + } + } + else + { + float percent20 = victim.GetBaseAttackTime(WeaponAttackType.BaseAttack) * 0.20f; + float percent60 = 3.0f * percent20; + if (basetime > percent20 && basetime <= percent60) + victim.setAttackTimer(WeaponAttackType.BaseAttack, (uint)percent20); + else if (basetime > percent60) + { + basetime -= 2.0f * percent20; + victim.setAttackTimer(WeaponAttackType.BaseAttack, (uint)basetime); + } + } + } + + // Call default DealDamage + CleanDamage cleanDamage = new CleanDamage(damageInfo.cleanDamage, damageInfo.absorb, damageInfo.attackType, damageInfo.hitOutCome); + DealDamage(victim, damageInfo.damage, cleanDamage, DamageEffectType.Direct, (SpellSchoolMask)damageInfo.damageSchoolMask, null, durabilityLoss); + + // If this is a creature and it attacks from behind it has a probability to daze it's victim + if ((damageInfo.hitOutCome == MeleeHitOutcome.Crit || damageInfo.hitOutCome == MeleeHitOutcome.Crushing || damageInfo.hitOutCome == MeleeHitOutcome.Normal || damageInfo.hitOutCome == MeleeHitOutcome.Glancing) && + !IsTypeId(TypeId.Player) && !ToCreature().IsControlledByPlayer() && !victim.HasInArc(MathFunctions.PI, this) + && (victim.IsTypeId(TypeId.Player) || !victim.ToCreature().isWorldBoss()) && !victim.IsVehicle()) + { + // -probability is between 0% and 40% + // 20% base chance + float Probability = 20.0f; + + // there is a newbie protection, at level 10 just 7% base chance; assuming linear function + if (victim.getLevel() < 30) + Probability = 0.65f * victim.getLevel() + 0.5f; + + uint VictimDefense = victim.GetMaxSkillValueForLevel(this); + uint AttackerMeleeSkill = GetMaxSkillValueForLevel(); + + Probability *= (float)(AttackerMeleeSkill / VictimDefense * 0.16); + + if (Probability < 0) + Probability = 0; + + if (Probability > 40.0f) + Probability = 40.0f; + + if (RandomHelper.randChance(Probability)) + CastSpell(victim, 1604, true); + } + + if (IsTypeId(TypeId.Player)) + ToPlayer().CastItemCombatSpell(victim, damageInfo.attackType, damageInfo.procVictim, damageInfo.procEx); + + // Do effect if any damage done to target + if (damageInfo.damage != 0) + { + // We're going to call functions which can modify content of the list during iteration over it's elements + // Let's copy the list so we can prevent iterator invalidation + var vDamageShieldsCopy = victim.GetAuraEffectsByType(AuraType.DamageShield); + foreach (var dmgShield in vDamageShieldsCopy) + { + SpellInfo i_spellProto = dmgShield.GetSpellInfo(); + // Damage shield can be resisted... + var missInfo = victim.SpellHitResult(this, i_spellProto, false); + if (missInfo != 0) + { + victim.SendSpellMiss(this, i_spellProto.Id, missInfo); + continue; + } + + // ...or immuned + if (IsImmunedToDamage(i_spellProto)) + { + victim.SendSpellDamageImmune(this, i_spellProto.Id, false); + continue; + } + + uint damage = (uint)dmgShield.GetAmount(); + Unit caster = dmgShield.GetCaster(); + if (caster) + { + damage = caster.SpellDamageBonusDone(this, i_spellProto, damage, DamageEffectType.SpellDirect, dmgShield.GetSpellEffectInfo()); + damage = SpellDamageBonusTaken(caster, i_spellProto, damage, DamageEffectType.SpellDirect, dmgShield.GetSpellEffectInfo()); + } + + uint absorb = 0; + uint resist = 0; + victim.CalcAbsorbResist(this, i_spellProto.SchoolMask, DamageEffectType.SpellDirect, damage, ref absorb, ref resist, i_spellProto); + // No Unit.CalcAbsorbResist here - opcode doesn't send that data - this damage is probably not affected by that + victim.DealDamageMods(this, ref damage); + + SpellDamageShield damageShield = new SpellDamageShield(); + damageShield.Attacker = victim.GetGUID(); + damageShield.Defender = GetGUID(); + damageShield.SpellID = i_spellProto.Id; + damageShield.TotalDamage = damage; + damageShield.OverKill = (uint)Math.Max(damage - GetHealth(), 0); + damageShield.SchoolMask = (uint)i_spellProto.SchoolMask; + damageShield.LogAbsorbed = absorb; + + victim.DealDamage(this, damage, null, DamageEffectType.SpellDirect, i_spellProto.GetSchoolMask(), i_spellProto, true); + damageShield.LogData.Initialize(this); + + victim.SendCombatLogMessage(damageShield); + } + } + } + public uint DealDamage(Unit victim, uint damage, CleanDamage cleanDamage = null, DamageEffectType damagetype = DamageEffectType.Direct, SpellSchoolMask damageSchoolMask = SpellSchoolMask.Normal, SpellInfo spellProto = null, bool durabilityLoss = true) + { + if (victim.IsAIEnabled) + victim.GetAI().DamageTaken(this, ref damage); + + if (IsAIEnabled) + GetAI().DamageDealt(victim, ref damage, damagetype); + + // Hook for OnDamage Event + Global.ScriptMgr.OnDamage(this, victim, ref damage); + + if (victim.IsTypeId(TypeId.Player) && this != victim) + { + // Signal to pets that their owner was attacked - except when DOT. + if (damagetype != DamageEffectType.DOT) + { + Pet pet = victim.ToPlayer().GetPet(); + + if (pet != null && pet.IsAlive()) + pet.GetAI().OwnerAttackedBy(this); + } + + if (victim.ToPlayer().GetCommandStatus(PlayerCommandStates.God)) + return 0; + } + + // Signal the pet it was attacked so the AI can respond if needed + if (victim.IsTypeId(TypeId.Unit) && this != victim && victim.IsPet() && victim.IsAlive()) + victim.ToPet().GetAI().AttackedBy(this); + + if (damagetype != DamageEffectType.NoDamage) + { + // interrupting auras with AURA_INTERRUPT_FLAG_DAMAGE before checking !damage (absorbed damage breaks that type of auras) + if (spellProto != null) + { + if (!spellProto.HasAttribute(SpellAttr4.DamageDoesntBreakAuras)) + victim.RemoveAurasWithInterruptFlags(SpellAuraInterruptFlags.TakeDamage, spellProto.Id); + } + else + victim.RemoveAurasWithInterruptFlags(SpellAuraInterruptFlags.TakeDamage, 0); + + // interrupt spells with SPELL_INTERRUPT_FLAG_ABORT_ON_DMG on absorbed damage (no dots) + if (damage == 0 && damagetype != DamageEffectType.DOT && cleanDamage != null && cleanDamage.absorbed_damage != 0) + { + if (victim != this && victim.IsTypeId(TypeId.Player)) + { + Spell spell = victim.GetCurrentSpell(CurrentSpellTypes.Generic); + if (spell) + { + if (spell.getState() == SpellState.Preparing) + { + SpellInterruptFlags interruptFlags = spell.m_spellInfo.InterruptFlags; + if (interruptFlags.HasAnyFlag(SpellInterruptFlags.AbortOnDmg)) + victim.InterruptNonMeleeSpells(false); + } + } + } + } + + // We're going to call functions which can modify content of the list during iteration over it's elements + // Let's copy the list so we can prevent iterator invalidation + var vCopyDamageCopy = victim.GetAuraEffectsByType(AuraType.ShareDamagePct); + // copy damage to casters of this aura + foreach (var aura in vCopyDamageCopy) + { + // Check if aura was removed during iteration - we don't need to work on such auras + if (!(aura.GetBase().IsAppliedOnTarget(victim.GetGUID()))) + continue; + + // check damage school mask + if ((aura.GetMiscValue() & (int)damageSchoolMask) == 0) + continue; + + Unit shareDamageTarget = aura.GetCaster(); + if (shareDamageTarget == null) + continue; + SpellInfo spell = aura.GetSpellInfo(); + + uint share = MathFunctions.CalculatePct(damage, aura.GetAmount()); + + // @todo check packets if damage is done by victim, or by attacker of victim + DealDamageMods(shareDamageTarget, ref share); + DealDamage(shareDamageTarget, share, null, DamageEffectType.NoDamage, spell.GetSchoolMask(), spell, false); + } + } + + // Rage from Damage made (only from direct weapon damage) + if (cleanDamage != null && (cleanDamage.attackType == WeaponAttackType.BaseAttack || cleanDamage.attackType == WeaponAttackType.OffAttack) && damagetype == DamageEffectType.Direct && this != victim && getPowerType() == PowerType.Rage) + { + uint rage = (uint)(GetBaseAttackTime(cleanDamage.attackType) / 1000.0f * 1.75f); + if (cleanDamage.attackType == WeaponAttackType.OffAttack) + rage /= 2; + RewardRage(rage); + } + + if (damage == 0) + return 0; + + Log.outDebug(LogFilter.Unit, "DealDamageStart"); + + uint health = (uint)victim.GetHealth(); + Log.outDebug(LogFilter.Unit, "Unit {0} dealt {1} damage to unit {2}", GetGUID(), damage, victim.GetGUID()); + + // duel ends when player has 1 or less hp + bool duel_hasEnded = false; + bool duel_wasMounted = false; + if (victim.IsTypeId(TypeId.Player) && victim.ToPlayer().duel != null && damage >= (health - 1)) + { + // prevent kill only if killed in duel and killed by opponent or opponent controlled creature + if (victim.ToPlayer().duel.opponent == this || victim.ToPlayer().duel.opponent.GetGUID() == GetOwnerGUID()) + damage = health - 1; + + duel_hasEnded = true; + } + else if (victim.IsVehicle() && damage >= (health - 1) && victim.GetCharmer() != null && victim.GetCharmer().IsTypeId(TypeId.Player)) + { + Player victimRider = victim.GetCharmer().ToPlayer(); + if (victimRider != null && victimRider.duel != null && victimRider.duel.isMounted) + { + // prevent kill only if killed in duel and killed by opponent or opponent controlled creature + if (victimRider.duel.opponent == this || victimRider.duel.opponent.GetGUID() == GetCharmerGUID()) + damage = health - 1; + + duel_wasMounted = true; + duel_hasEnded = true; + } + } + + if (IsTypeId(TypeId.Player) && this != victim) + { + Player killer = ToPlayer(); + + // in bg, count dmg if victim is also a player + if (victim.IsTypeId(TypeId.Player)) + { + Battleground bg = killer.GetBattleground(); + if (bg) + bg.UpdatePlayerScore(killer, ScoreType.DamageDone, damage); + } + + killer.UpdateCriteria(CriteriaTypes.DamageDone, health > damage ? damage : health, 0, 0, victim); + killer.UpdateCriteria(CriteriaTypes.HighestHitDealt, damage); + } + + if (victim.IsTypeId(TypeId.Player)) + { + victim.ToPlayer().UpdateCriteria(CriteriaTypes.HighestHitReceived, damage); + } + else if (!victim.IsControlledByPlayer() || victim.IsVehicle()) + { + if (!victim.ToCreature().hasLootRecipient()) + victim.ToCreature().SetLootRecipient(this); + + if (IsControlledByPlayer()) + victim.ToCreature().LowerPlayerDamageReq(health < damage ? health : damage); + } + + if (health <= damage) + { + Log.outDebug(LogFilter.Unit, "DealDamage: victim just died"); + + if (victim.IsTypeId(TypeId.Player) && victim != this) + victim.ToPlayer().UpdateCriteria(CriteriaTypes.TotalDamageReceived, health); + + Kill(victim, durabilityLoss); + } + else + { + Log.outDebug(LogFilter.Unit, "DealDamageAlive"); + + if (victim.IsTypeId(TypeId.Player)) + victim.ToPlayer().UpdateCriteria(CriteriaTypes.TotalDamageReceived, damage); + + victim.ModifyHealth(-(int)damage); + + if (damagetype == DamageEffectType.Direct || damagetype == DamageEffectType.SpellDirect) + { + victim.RemoveAurasWithInterruptFlags(SpellAuraInterruptFlags.DirectDamage, spellProto != null ? spellProto.Id : 0); + victim.UpdateLastDamagedTime(spellProto); + } + + if (!victim.IsTypeId(TypeId.Player)) + { + victim.AddThreat(this, damage, damageSchoolMask, spellProto); + } + else // victim is a player + { + // random durability for items (HIT TAKEN) + if (WorldConfig.GetFloatValue(WorldCfg.RateDurabilityLossDamage) > RandomHelper.randChance()) + { + byte slot = (byte)RandomHelper.IRand(0, EquipmentSlot.End - 1); + victim.ToPlayer().DurabilityPointLossForEquipSlot(slot); + } + } + + if (IsTypeId(TypeId.Player)) + { + // random durability for items (HIT DONE) + if (RandomHelper.randChance(WorldConfig.GetFloatValue(WorldCfg.RateDurabilityLossDamage))) + { + byte slot = (byte)RandomHelper.IRand(0, EquipmentSlot.End - 1); + ToPlayer().DurabilityPointLossForEquipSlot(slot); + } + } + + if (damagetype != DamageEffectType.NoDamage && damage != 0) + { + if (victim != this && victim.IsTypeId(TypeId.Player) && // does not support creature push_back + (spellProto == null || !(spellProto.HasAttribute(SpellAttr7.NoPushbackOnDamage)))) + { + if (damagetype != DamageEffectType.DOT) + { + Spell spell = victim.GetCurrentSpell(CurrentSpellTypes.Generic); + if (spell != null) + if (spell.getState() == SpellState.Preparing) + { + var interruptFlags = spell.m_spellInfo.InterruptFlags; + if (interruptFlags.HasAnyFlag(SpellInterruptFlags.AbortOnDmg)) + victim.InterruptNonMeleeSpells(false); + else if (interruptFlags.HasAnyFlag(SpellInterruptFlags.PushBack)) + spell.Delayed(); + } + } + Spell spell1 = victim.GetCurrentSpell(CurrentSpellTypes.Channeled); + if (spell1 != null) + if (spell1.getState() == SpellState.Casting) + { + var channelInterruptFlags = spell1.m_spellInfo.ChannelInterruptFlags; + if (((channelInterruptFlags & SpellChannelInterruptFlags.Delay) != 0) && (damagetype != DamageEffectType.DOT)) + spell1.DelayedChannel(); + } + } + } + // last damage from duel opponent + if (duel_hasEnded) + { + Player he = duel_wasMounted ? victim.GetCharmer().ToPlayer() : victim.ToPlayer(); + + Contract.Assert(he && he.duel != null); + + if (duel_wasMounted) // In this case victim==mount + victim.SetHealth(1); + else + he.SetHealth(1); + + he.duel.opponent.CombatStopWithPets(true); + he.CombatStopWithPets(true); + + he.CastSpell(he, 7267, true); // beg + he.DuelComplete(DuelCompleteType.Won); + } + } + + Log.outDebug(LogFilter.Unit, "DealDamageEnd returned {0} damage", damage); + + return damage; + } + + public long ModifyHealth(long dVal) + { + long gain = 0; + + if (dVal == 0) + return 0; + + long curHealth = (long)GetHealth(); + + long val = dVal + curHealth; + if (val <= 0) + { + SetHealth(0); + return -curHealth; + } + + long maxHealth = (long)GetMaxHealth(); + if (val < maxHealth) + { + SetHealth((ulong)val); + gain = val - curHealth; + } + else if (curHealth != maxHealth) + { + SetHealth((ulong)maxHealth); + gain = maxHealth - curHealth; + } + + if (dVal < 0) + { + HealthUpdate packet = new HealthUpdate(); + packet.Guid = GetGUID(); + packet.Health = (long)GetHealth(); + + Player player = GetCharmerOrOwnerPlayerOrPlayerItself(); + if (player) + player.SendPacket(packet); + } + + return gain; + } + public long GetHealthGain(long dVal) + { + long gain = 0; + + if (dVal == 0) + return 0; + + long curHealth = (long)GetHealth(); + + long val = dVal + curHealth; + if (val <= 0) + { + return -curHealth; + } + + long maxHealth = (long)GetMaxHealth(); + + if (val < maxHealth) + gain = dVal; + else if (curHealth != maxHealth) + gain = maxHealth - curHealth; + + return gain; + } + + public void SendAttackStateUpdate(HitInfo HitInfo, Unit target, SpellSchoolMask damageSchoolMask, uint Damage, uint AbsorbDamage, uint Resist, VictimState TargetState, uint BlockedAmount) + { + CalcDamageInfo dmgInfo = new CalcDamageInfo(); + dmgInfo.HitInfo = HitInfo; + dmgInfo.attacker = this; + dmgInfo.target = target; + dmgInfo.damage = Damage - AbsorbDamage - Resist - BlockedAmount; + dmgInfo.damageSchoolMask = (uint)damageSchoolMask; + dmgInfo.absorb = AbsorbDamage; + dmgInfo.resist = Resist; + dmgInfo.TargetState = TargetState; + dmgInfo.blocked_amount = BlockedAmount; + SendAttackStateUpdate(dmgInfo); + } + public void SendAttackStateUpdate(CalcDamageInfo damageInfo) + { + AttackerStateUpdate packet = new AttackerStateUpdate(); + packet.hitInfo = damageInfo.HitInfo; + packet.AttackerGUID = damageInfo.attacker.GetGUID(); + packet.VictimGUID = damageInfo.target.GetGUID(); + packet.Damage = (int)damageInfo.damage; + int overkill = (int)(damageInfo.damage - damageInfo.target.GetHealth()); + packet.OverDamage = (overkill < 0 ? -1 : overkill); + + SubDamage subDmg = new SubDamage(); + subDmg.SchoolMask = (int)damageInfo.damageSchoolMask; // School of sub damage + subDmg.FDamage = damageInfo.damage; // sub damage + subDmg.Damage = (int)damageInfo.damage; // Sub Damage + subDmg.Absorbed = (int)damageInfo.absorb; + subDmg.Resisted = (int)damageInfo.resist; + packet.SubDmg.Set(subDmg); + + packet.VictimState = (byte)damageInfo.TargetState; + packet.BlockAmount = (int)damageInfo.blocked_amount; + packet.LogData.Initialize(damageInfo.attacker); + + SendCombatLogMessage(packet); + } + public void CombatStart(Unit target, bool initialAggro = true) + { + if (initialAggro) + { + if (!target.IsStandState()) + target.SetStandState(UnitStandStateType.Stand); + + if (!target.IsInCombat() && !target.IsTypeId(TypeId.Player) + && !target.ToCreature().HasReactState(ReactStates.Passive) && target.ToCreature().IsAIEnabled) + { + if (target.IsPet()) + target.ToCreature().GetAI().AttackedBy(this); // PetAI has special handler before AttackStart() + else + target.ToCreature().GetAI().AttackStart(this); + } + + SetInCombatWith(target); + target.SetInCombatWith(this); + } + Unit who = target.GetCharmerOrOwnerOrSelf(); + if (who.IsTypeId(TypeId.Player)) + SetContestedPvP(who.ToPlayer()); + + Player me = GetCharmerOrOwnerPlayerOrPlayerItself(); + if (me != null && who.IsPvP() && (!who.IsTypeId(TypeId.Player) || me.duel == null || me.duel.opponent != who)) + { + me.UpdatePvP(true); + me.RemoveAurasWithInterruptFlags(SpellAuraInterruptFlags.EnterPvpCombat); + } + } + public void SetInCombatWith(Unit enemy) + { + Unit eOwner = enemy.GetCharmerOrOwnerOrSelf(); + if (eOwner.IsPvP()) + { + SetInCombatState(true, enemy); + return; + } + + // check for duel + if (eOwner.IsTypeId(TypeId.Player) && eOwner.ToPlayer().duel != null) + { + Unit myOwner = GetCharmerOrOwnerOrSelf(); + if (((Player)eOwner).duel.opponent == myOwner) + { + SetInCombatState(true, enemy); + return; + } + } + SetInCombatState(false, enemy); + } + public void SetInCombatState(bool PvP, Unit enemy = null) + { + // only alive units can be in combat + if (!IsAlive()) + return; + + if (PvP) + m_CombatTimer = 5000; + + if (IsInCombat() || HasUnitState(UnitState.Evade)) + return; + + SetFlag(UnitFields.Flags, UnitFlags.InCombat); + + Creature creature = ToCreature(); + if (creature != null) + { + // Set home position at place of engaging combat for escorted creatures + if ((IsAIEnabled && creature.GetAI().IsEscorted()) || GetMotionMaster().GetCurrentMovementGeneratorType() == MovementGeneratorType.Waypoint || + GetMotionMaster().GetCurrentMovementGeneratorType() == MovementGeneratorType.Point) + creature.SetHomePosition(GetPositionX(), GetPositionY(), GetPositionZ(), Orientation); + + if (enemy != null) + { + if (IsAIEnabled) + creature.GetAI().EnterCombat(enemy); + + if (creature.GetFormation() != null) + creature.GetFormation().MemberAttackStart(creature, enemy); + } + + if (IsPet()) + { + UpdateSpeed(UnitMoveType.Run); + UpdateSpeed(UnitMoveType.Swim); + UpdateSpeed(UnitMoveType.Flight); + } + + if (!creature.GetCreatureTemplate().TypeFlags.HasAnyFlag(CreatureTypeFlags.MountedCombatAllowed)) + Dismount(); + } + + foreach (var unit in m_Controlled) + { + unit.SetInCombatState(PvP, enemy); + unit.SetFlag(UnitFields.Flags, UnitFlags.PetInCombat); + } + } + + internal void SendCombatLogMessage(CombatLogServerPacket combatLog) + { + CombatLogSender notifier = new CombatLogSender(this, combatLog, GetVisibilityRange()); + Cell.VisitWorldObjects(this, notifier, GetVisibilityRange()); + } + + public void Kill(Unit victim, bool durabilityLoss = true) + { + // Prevent killing unit twice (and giving reward from kill twice) + if (victim.GetHealth() == 0) + return; + + // find player: owner of controlled `this` or `this` itself maybe + Player player = GetCharmerOrOwnerPlayerOrPlayerItself(); + Creature creature = victim.ToCreature(); + + bool isRewardAllowed = true; + if (creature != null) + { + isRewardAllowed = creature.IsDamageEnoughForLootingAndReward(); + if (!isRewardAllowed) + creature.SetLootRecipient(null); + } + + if (isRewardAllowed && creature != null && creature.GetLootRecipient() != null) + player = creature.GetLootRecipient(); + + // Reward player, his pets, and group/raid members + // call kill spell proc event (before real die and combat stop to triggering auras removed at death/combat stop) + if (isRewardAllowed && player != null && player != victim) + { + PartyKillLog partyKillLog = new PartyKillLog(); + partyKillLog.Player = player.GetGUID(); + partyKillLog.Victim = victim.GetGUID(); + + Player looter = player; + var group = player.GetGroup(); + bool hasLooterGuid = false; + if (group) + { + group.BroadcastPacket(partyKillLog, group.GetMemberGroup(player.GetGUID()) != 0); + + if (creature) + { + group.UpdateLooterGuid(creature, true); + if (!group.GetLooterGuid().IsEmpty()) + { + looter = Global.ObjAccessor.FindPlayer(group.GetLooterGuid()); + if (looter) + { + hasLooterGuid = true; + creature.SetLootRecipient(looter); // update creature loot recipient to the allowed looter. + } + } + } + } + else + { + player.SendPacket(partyKillLog); + + if (creature != null) + { + LootList lootList = new LootList(); + lootList.Owner = creature.GetGUID(); + lootList.LootObj = creature.loot.GetGUID(); + player.SendMessageToSet(lootList, true); + } + } + + if (creature) + { + Loot loot = creature.loot; + + loot.clear(); + uint lootid = creature.GetCreatureTemplate().LootId; + if (lootid != 0) + loot.FillLoot(lootid, LootManager.Creature, looter, false, false, creature.GetLootMode()); + + loot.generateMoneyLoot(creature.GetCreatureTemplate().MinGold, creature.GetCreatureTemplate().MaxGold); + + if (group) + { + if (hasLooterGuid) + group.SendLooter(creature, looter); + else + group.SendLooter(creature, null); + + // Update round robin looter only if the creature had loot + if (!loot.empty()) + group.UpdateLooterGuid(creature); + } + } + + player.RewardPlayerAndGroupAtKill(victim, false); + } + + // Do KILL and KILLED procs. KILL proc is called only for the unit who landed the killing blow (and its owner - for pets and totems) regardless of who tapped the victim + if (IsPet() || IsTotem()) + { + Unit owner = GetOwner(); + if (owner != null) + owner.ProcDamageAndSpell(victim, ProcFlags.Kill, ProcFlags.None, ProcFlagsExLegacy.None, 0); + } + + if (!victim.IsCritter()) + ProcDamageAndSpell(victim, ProcFlags.Kill, ProcFlags.Killed, ProcFlagsExLegacy.None, 0); + + // Proc auras on death - must be before aura/combat remove + victim.ProcDamageAndSpell(null, ProcFlags.Death, ProcFlags.None, ProcFlagsExLegacy.None, 0, WeaponAttackType.BaseAttack, null); + + // update get killing blow achievements, must be done before setDeathState to be able to require auras on target + // and before Spirit of Redemption as it also removes auras + if (player != null) + player.UpdateCriteria(CriteriaTypes.GetKillingBlows, 1, 0, 0, victim); + + // if talent known but not triggered (check priest class for speedup check) + bool spiritOfRedemption = false; + if (victim.IsTypeId(TypeId.Player) && victim.GetClass() == Class.Priest) + { + AuraEffect spiritOfRedemptionEffect = GetAuraEffect(20711, 0); + if (spiritOfRedemptionEffect != null) + { + // save value before aura remove + uint ressSpellId = victim.GetUInt32Value(PlayerFields.SelfResSpell); + if (ressSpellId == 0) + ressSpellId = victim.ToPlayer().GetResurrectionSpellId(); + // Remove all expected to remove at death auras (most important negative case like DoT or periodic triggers) + victim.RemoveAllAurasOnDeath(); + // restore for use at real death + victim.SetUInt32Value(PlayerFields.SelfResSpell, ressSpellId); + + // FORM_SPIRIT_OF_REDEMPTION and related auras + victim.CastSpell(victim, 27827, true, null, spiritOfRedemptionEffect); + spiritOfRedemption = true; + } + } + + if (!spiritOfRedemption) + { + Log.outDebug(LogFilter.Unit, "SET JUST_DIED"); + victim.setDeathState(DeathState.JustDied); + } + + // Inform pets (if any) when player kills target) + // MUST come after victim.setDeathState(JUST_DIED); or pet next target + // selection will get stuck on same target and break pet react state + if (player != null) + { + Pet pet = player.GetPet(); + if (pet != null && pet.IsAlive() && pet.isControlled()) + pet.GetAI().KilledUnit(victim); + } + + // 10% durability loss on death + // clean InHateListOf + Player plrVictim = victim.ToPlayer(); + if (plrVictim != null) + { + // remember victim PvP death for corpse type and corpse reclaim delay + // at original death (not at SpiritOfRedemtionTalent timeout) + plrVictim.SetPvPDeath(player != null); + + // only if not player and not controlled by player pet. And not at BG + if ((durabilityLoss && player == null && !victim.ToPlayer().InBattleground()) || (player != null && WorldConfig.GetBoolValue(WorldCfg.DurabilityLossInPvp))) + { + double baseLoss = WorldConfig.GetFloatValue(WorldCfg.RateDurabilityLossOnDeath); + uint loss = (uint)(baseLoss - (baseLoss * plrVictim.GetTotalAuraMultiplier(AuraType.ModDurabilityLoss))); + Log.outDebug(LogFilter.Unit, "We are dead, losing {0} percent durability", loss); + // Durability loss is calculated more accurately again for each item in Player.DurabilityLoss + plrVictim.DurabilityLossAll(baseLoss, false); + // durability lost message + SendDurabilityLoss(plrVictim, loss); + } + // Call KilledUnit for creatures + if (IsTypeId(TypeId.Unit) && IsAIEnabled) + ToCreature().GetAI().KilledUnit(victim); + + // last damage from non duel opponent or opponent controlled creature + if (plrVictim.duel != null) + { + plrVictim.duel.opponent.CombatStopWithPets(true); + plrVictim.CombatStopWithPets(true); + plrVictim.DuelComplete(DuelCompleteType.Interrupted); + } + } + else // creature died + { + Log.outDebug(LogFilter.Unit, "DealDamageNotPlayer"); + + if (!creature.IsPet()) + { + creature.DeleteThreatList(); + + // must be after setDeathState which resets dynamic flags + if (!creature.loot.isLooted()) + creature.SetFlag(ObjectFields.DynamicFlags, UnitDynFlags.Lootable); + else + creature.AllLootRemovedFromCorpse(); + } + + // Call KilledUnit for creatures, this needs to be called after the lootable flag is set + if (IsTypeId(TypeId.Unit) && IsAIEnabled) + ToCreature().GetAI().KilledUnit(victim); + + // Call creature just died function + if (creature.IsAIEnabled) + creature.GetAI().JustDied(this); + + TempSummon summon = creature.ToTempSummon(); + if (summon != null) + { + Unit summoner = summon.GetSummoner(); + if (summoner != null) + if (summoner.IsTypeId(TypeId.Unit) && summoner.IsAIEnabled) + summoner.ToCreature().GetAI().SummonedCreatureDies(creature, this); + } + + // Dungeon specific stuff, only applies to players killing creatures + if (creature.GetInstanceId() != 0) + { + Map instanceMap = creature.GetMap(); + Player creditedPlayer = GetCharmerOrOwnerPlayerOrPlayerItself(); + // @todo do instance binding anyway if the charmer/owner is offline + + if (instanceMap.IsDungeon() && (creditedPlayer || this == victim)) + { + if (instanceMap.IsRaidOrHeroicDungeon()) + { + if (creature.GetCreatureTemplate().FlagsExtra.HasAnyFlag(CreatureFlagsExtra.InstanceBind)) + ((InstanceMap)instanceMap).PermBindAllPlayers(); + } + else + { + // the reset time is set but not added to the scheduler + // until the players leave the instance + long resettime = creature.GetRespawnTimeEx() + 2 * Time.Hour; + InstanceSave save = Global.InstanceSaveMgr.GetInstanceSave(creature.GetInstanceId()); + if (save != null) + if (save.GetResetTime() < resettime) + save.SetResetTime(resettime); + } + } + } + } + + // outdoor pvp things, do these after setting the death state, else the player activity notify won't work... doh... + // handle player kill only if not suicide (spirit of redemption for example) + if (player != null && this != victim) + { + OutdoorPvP pvp = player.GetOutdoorPvP(); + if (pvp != null) + pvp.HandleKill(player, victim); + + BattleField bf = Global.BattleFieldMgr.GetBattlefieldToZoneId(player.GetZoneId()); + if (bf != null) + bf.HandleKill(player, victim); + } + + // Battlegroundthings (do this at the end, so the death state flag will be properly set to handle in the bg.handlekill) + if (player != null && player.InBattleground()) + { + Battleground bg = player.GetBattleground(); + if (bg) + { + Player playerVictim = victim.ToPlayer(); + if (playerVictim) + bg.HandleKillPlayer(playerVictim, player); + else + bg.HandleKillUnit(victim.ToCreature(), player); + } + } + + // achievement stuff + if (victim.IsTypeId(TypeId.Player)) + { + if (IsTypeId(TypeId.Unit)) + victim.ToPlayer().UpdateCriteria(CriteriaTypes.KilledByCreature, GetEntry()); + else if (IsTypeId(TypeId.Player) && victim != this) + victim.ToPlayer().UpdateCriteria(CriteriaTypes.KilledByPlayer, 1, (ulong)ToPlayer().GetTeam()); + } + + // Hook for OnPVPKill Event + Player killerPlr = ToPlayer(); + Creature killerCre = ToCreature(); + if (killerPlr != null) + { + Player killedPlr = victim.ToPlayer(); + Creature killedCre = victim.ToCreature(); + if (killedPlr != null) + Global.ScriptMgr.OnPVPKill(killerPlr, killedPlr); + else if (killedCre != null) + Global.ScriptMgr.OnCreatureKill(killerPlr, killedCre); + } + else if (killerCre != null) + { + Player killed = victim.ToPlayer(); + if (killed != null) + Global.ScriptMgr.OnPlayerKilledByCreature(killerCre, killed); + } + } + + public void KillSelf(bool durabilityLoss = true) { Kill(this, durabilityLoss); } + + public virtual uint GetBlockPercent() { return 30; } + + public void SetContestedPvP(Player attackedPlayer = null) + { + Player player = GetCharmerOrOwnerPlayerOrPlayerItself(); + + if (player == null || (attackedPlayer != null && (attackedPlayer == player || (player.duel != null && player.duel.opponent == attackedPlayer)))) + return; + + player.SetContestedPvPTimer(30000); + if (!player.HasUnitState(UnitState.AttackPlayer)) + { + player.AddUnitState(UnitState.AttackPlayer); + player.SetFlag(PlayerFields.Flags, PlayerFlags.ContestedPVP); + // call MoveInLineOfSight for nearby contested guards + UpdateObjectVisibility(); + } + if (!HasUnitState(UnitState.AttackPlayer)) + { + AddUnitState(UnitState.AttackPlayer); + // call MoveInLineOfSight for nearby contested guards + UpdateObjectVisibility(); + } + } + + void UpdateReactives(uint p_time) + { + for (ReactiveType reactive = 0; reactive < ReactiveType.Max; ++reactive) + { + if (!m_reactiveTimer.ContainsKey(reactive)) + continue; + + if (m_reactiveTimer[reactive] <= p_time) + { + m_reactiveTimer[reactive] = 0; + + switch (reactive) + { + case ReactiveType.Defense: + if (HasAuraState(AuraStateType.Defense)) + ModifyAuraState(AuraStateType.Defense, false); + break; + case ReactiveType.HunterParry: + if (GetClass() == Class.Hunter && HasAuraState(AuraStateType.HunterParry)) + ModifyAuraState(AuraStateType.HunterParry, false); + break; + case ReactiveType.OverPower: + if (GetClass() == Class.Warrior && IsTypeId(TypeId.Player)) + ToPlayer().ClearComboPoints(); + break; + } + } + else + { + m_reactiveTimer[reactive] -= p_time; + } + } + } + + public void RewardRage(uint baseRage) + { + float addRage = baseRage; + + // talent who gave more rage on attack + MathFunctions.AddPct(ref addRage, GetTotalAuraModifier(AuraType.ModRageFromDamageDealt)); + + addRage *= WorldConfig.GetFloatValue(WorldCfg.RatePowerRageIncome); + + ModifyPower(PowerType.Rage, (int)(addRage * 10)); + } + + public float GetPPMProcChance(uint WeaponSpeed, float PPM, SpellInfo spellProto) + { + // proc per minute chance calculation + if (PPM <= 0) + return 0.0f; + + // Apply chance modifer aura + if (spellProto != null) + { + Player modOwner = GetSpellModOwner(); + if (modOwner != null) + modOwner.ApplySpellMod(spellProto.Id, SpellModOp.ProcPerMinute, ref PPM); + } + + return (float)Math.Floor((WeaponSpeed * PPM) / 600.0f); // result is chance in percents (probability = Speed_in_sec * (PPM / 60)) + } + + public Unit GetNextRandomRaidMemberOrPet(float radius) + { + Player player = null; + if (IsTypeId(TypeId.Player)) + player = ToPlayer(); + // Should we enable this also for charmed units? + else if (IsTypeId(TypeId.Unit) && IsPet()) + player = GetOwner().ToPlayer(); + + if (player == null) + return null; + Group group = player.GetGroup(); + // When there is no group check pet presence + if (!group) + { + // We are pet now, return owner + if (player != this) + return IsWithinDistInMap(player, radius) ? player : null; + Unit pet = GetGuardianPet(); + // No pet, no group, nothing to return + if (pet == null) + return null; + // We are owner now, return pet + return IsWithinDistInMap(pet, radius) ? pet : null; + } + + List nearMembers = new List(); + // reserve place for players and pets because resizing vector every unit push is unefficient (vector is reallocated then) + + for (GroupReference refe = group.GetFirstMember(); refe != null; refe = refe.next()) + { + Player Target = refe.GetSource(); + if (Target) + { + // IsHostileTo check duel and controlled by enemy + if (Target != this && Target.IsAlive() && IsWithinDistInMap(Target, radius) && !IsHostileTo(Target)) + nearMembers.Add(Target); + + // Push player's pet to vector + Unit pet = Target.GetGuardianPet(); + if (pet) + if (pet != this && pet.IsAlive() && IsWithinDistInMap(pet, radius) && !IsHostileTo(pet)) + nearMembers.Add(pet); + } + } + + if (nearMembers.Empty()) + return null; + + int randTarget = RandomHelper.IRand(0, nearMembers.Count - 1); + return nearMembers[randTarget]; + } + + public void ClearAllReactives() + { + for (ReactiveType i = 0; i < ReactiveType.Max; ++i) + m_reactiveTimer[i] = 0; + + if (HasAuraState(AuraStateType.Defense)) + ModifyAuraState(AuraStateType.Defense, false); + if (GetClass() == Class.Hunter && HasAuraState(AuraStateType.HunterParry)) + ModifyAuraState(AuraStateType.HunterParry, false); + if (GetClass() == Class.Warrior && IsTypeId(TypeId.Player)) + ToPlayer().ClearComboPoints(); + } + + // TODO for melee need create structure as in + void CalculateMeleeDamage(Unit victim, uint damage, out CalcDamageInfo damageInfo, WeaponAttackType attackType) + { + damageInfo = new CalcDamageInfo(); + + damageInfo.attacker = this; + damageInfo.target = victim; + damageInfo.damageSchoolMask = (uint)SpellSchoolMask.Normal; + damageInfo.attackType = attackType; + damageInfo.damage = 0; + damageInfo.cleanDamage = 0; + damageInfo.absorb = 0; + damageInfo.resist = 0; + damageInfo.blocked_amount = 0; + + damageInfo.TargetState = 0; + damageInfo.HitInfo = 0; + damageInfo.procAttacker = ProcFlags.None; + damageInfo.procVictim = ProcFlags.None; + damageInfo.procEx = ProcFlagsExLegacy.None; + damageInfo.hitOutCome = MeleeHitOutcome.Evade; + + if (victim == null) + return; + + if (!IsAlive() || !victim.IsAlive()) + return; + + // Select HitInfo/procAttacker/procVictim flag based on attack type + switch (attackType) + { + case WeaponAttackType.BaseAttack: + damageInfo.procAttacker = ProcFlags.DoneMeleeAutoAttack | ProcFlags.DoneMainHandAttack; + damageInfo.procVictim = ProcFlags.TakenMeleeAutoAttack; + break; + case WeaponAttackType.OffAttack: + damageInfo.procAttacker = ProcFlags.DoneMeleeAutoAttack | ProcFlags.DoneOffHandAttack; + damageInfo.procVictim = ProcFlags.TakenMeleeAutoAttack; + damageInfo.HitInfo = HitInfo.OffHand; + break; + default: + return; + } + + // Physical Immune check + if (damageInfo.target.IsImmunedToDamage((SpellSchoolMask)damageInfo.damageSchoolMask)) + { + damageInfo.HitInfo |= HitInfo.NormalSwing; + damageInfo.TargetState = VictimState.Immune; + + damageInfo.procEx |= ProcFlagsExLegacy.Immune; + damageInfo.damage = 0; + damageInfo.cleanDamage = 0; + return; + } + + damage += CalculateDamage(damageInfo.attackType, false, true); + // Add melee damage bonus + damage = MeleeDamageBonusDone(damageInfo.target, damage, damageInfo.attackType); + damage = damageInfo.target.MeleeDamageBonusTaken(this, damage, damageInfo.attackType); + + // Script Hook For CalculateMeleeDamage -- Allow scripts to change the Damage pre class mitigation calculations + Global.ScriptMgr.ModifyMeleeDamage(damageInfo.target, damageInfo.attacker, ref damage); + + // Calculate armor reduction + if (IsDamageReducedByArmor((SpellSchoolMask)damageInfo.damageSchoolMask)) + { + damageInfo.damage = CalcArmorReducedDamage(damageInfo.attacker, damageInfo.target, damage, null, damageInfo.attackType); + damageInfo.cleanDamage += damage - damageInfo.damage; + } + else + damageInfo.damage = damage; + + damageInfo.hitOutCome = RollMeleeOutcomeAgainst(damageInfo.target, damageInfo.attackType); + + switch (damageInfo.hitOutCome) + { + case MeleeHitOutcome.Evade: + damageInfo.HitInfo |= HitInfo.Miss | HitInfo.SwingNoHitSound; + damageInfo.TargetState = VictimState.Evades; + damageInfo.procEx |= ProcFlagsExLegacy.Evade; + damageInfo.damage = 0; + damageInfo.cleanDamage = 0; + return; + case MeleeHitOutcome.Miss: + damageInfo.HitInfo |= HitInfo.Miss; + damageInfo.TargetState = VictimState.Intact; + damageInfo.procEx |= ProcFlagsExLegacy.Miss; + damageInfo.damage = 0; + damageInfo.cleanDamage = 0; + break; + case MeleeHitOutcome.Normal: + damageInfo.TargetState = VictimState.Hit; + damageInfo.procEx |= ProcFlagsExLegacy.NormalHit; + break; + case MeleeHitOutcome.Crit: + damageInfo.HitInfo |= HitInfo.CriticalHit; + damageInfo.TargetState = VictimState.Hit; + damageInfo.procEx |= ProcFlagsExLegacy.CriticalHit; + // Crit bonus calc + damageInfo.damage += damageInfo.damage; + float mod = 0.0f; + // Apply SPELL_AURA_MOD_ATTACKER_RANGED_CRIT_DAMAGE or SPELL_AURA_MOD_ATTACKER_MELEE_CRIT_DAMAGE + if (damageInfo.attackType == WeaponAttackType.RangedAttack) + mod += damageInfo.target.GetTotalAuraModifier(AuraType.ModAttackerRangedCritDamage); + else + mod += damageInfo.target.GetTotalAuraModifier(AuraType.ModAttackerMeleeCritDamage); + + // Increase crit damage from SPELL_AURA_MOD_CRIT_DAMAGE_BONUS + mod += (GetTotalAuraMultiplierByMiscMask(AuraType.ModCritDamageBonus, damageInfo.damageSchoolMask) - 1.0f) * 100; + + if (mod != 0) + MathFunctions.AddPct(ref damageInfo.damage, mod); + break; + case MeleeHitOutcome.Parry: + damageInfo.TargetState = VictimState.Parry; + damageInfo.procEx |= ProcFlagsExLegacy.Parry; + damageInfo.cleanDamage += damageInfo.damage; + damageInfo.damage = 0; + break; + case MeleeHitOutcome.Dodge: + damageInfo.TargetState = VictimState.Dodge; + damageInfo.procEx |= ProcFlagsExLegacy.Dodge; + damageInfo.cleanDamage += damageInfo.damage; + damageInfo.damage = 0; + break; + case MeleeHitOutcome.Block: + damageInfo.TargetState = VictimState.Hit; + damageInfo.procEx |= ProcFlagsExLegacy.Block | ProcFlagsExLegacy.NormalHit; + // 30% damage blocked, double blocked amount if block is critical + damageInfo.blocked_amount = MathFunctions.CalculatePct(damageInfo.damage, damageInfo.target.isBlockCritical() ? damageInfo.target.GetBlockPercent() * 2 : damageInfo.target.GetBlockPercent()); + damageInfo.damage -= damageInfo.blocked_amount; + damageInfo.cleanDamage += damageInfo.blocked_amount; + break; + case MeleeHitOutcome.Glancing: + damageInfo.HitInfo |= HitInfo.Glancing; + damageInfo.TargetState = VictimState.Hit; + damageInfo.procEx |= ProcFlagsExLegacy.NormalHit; + int leveldif = (int)victim.getLevel() - (int)getLevel(); + if (leveldif > 3) + leveldif = 3; + + float reducePercent = 1.0f - leveldif * 0.1f; + damageInfo.cleanDamage += damageInfo.damage - (uint)(reducePercent * damageInfo.damage); + damageInfo.damage = (uint)(reducePercent * damageInfo.damage); + break; + case MeleeHitOutcome.Crushing: + damageInfo.HitInfo |= HitInfo.Crushing; + damageInfo.TargetState = VictimState.Hit; + damageInfo.procEx |= ProcFlagsExLegacy.NormalHit; + // 150% normal damage + damageInfo.damage += (damageInfo.damage / 2); + break; + + default: + break; + } + + // Always apply HITINFO_AFFECTS_VICTIM in case its not a miss + if (!damageInfo.HitInfo.HasAnyFlag(HitInfo.Miss)) + damageInfo.HitInfo |= HitInfo.AffectsVictim; + + uint resilienceReduction = damageInfo.damage; + ApplyResilience(victim, ref resilienceReduction); + resilienceReduction = damageInfo.damage - resilienceReduction; + damageInfo.damage -= resilienceReduction; + damageInfo.cleanDamage += resilienceReduction; + + // Calculate absorb resist + if (damageInfo.damage > 0) + { + damageInfo.procVictim |= ProcFlags.TakenDamage; + // Calculate absorb & resists + CalcAbsorbResist(damageInfo.target, (SpellSchoolMask)damageInfo.damageSchoolMask, DamageEffectType.Direct, damageInfo.damage, ref damageInfo.absorb, ref damageInfo.resist); + + if (damageInfo.absorb != 0) + { + damageInfo.HitInfo |= (damageInfo.damage - damageInfo.absorb == 0 ? HitInfo.FullAbsorb : HitInfo.PartialAbsorb); + damageInfo.procEx |= ProcFlagsExLegacy.Absorb; + } + + if (damageInfo.resist != 0) + damageInfo.HitInfo |= (damageInfo.damage - damageInfo.resist == 0 ? HitInfo.FullResist : HitInfo.PartialResist); + + damageInfo.damage -= damageInfo.absorb + damageInfo.resist; + } + else // Impossible get negative result but.... + damageInfo.damage = 0; + } + MeleeHitOutcome RollMeleeOutcomeAgainst(Unit victim, WeaponAttackType attType) + { + if (victim.IsTypeId(TypeId.Unit) && victim.ToCreature().IsEvadingAttacks()) + return MeleeHitOutcome.Evade; + + // Miss chance based on melee + int miss_chance = (int)(MeleeSpellMissChance(victim, attType, 0) * 100.0f); + + // Critical hit chance + int crit_chance = (int)(GetUnitCriticalChance(attType, victim) * 100.0f); + + int dodge_chance = (int)(GetUnitDodgeChance(attType, victim) * 100.0f); + int block_chance = (int)(GetUnitBlockChance(attType, victim) * 100.0f); + int parry_chance = (int)(GetUnitParryChance(attType, victim) * 100.0f); + + // melee attack table implementation + // outcome priority: + // 1. > 2. > 3. > 4. > 5. > 6. > 7. > 8. + // MISS > DODGE > PARRY > GLANCING > BLOCK > CRIT > CRUSHING > HIT + + int sum = 0, tmp = 0; + int roll = RandomHelper.IRand(0, 9999); + + uint attackerLevel = GetLevelForTarget(victim); + uint victimLevel = GetLevelForTarget(this); + + // check if attack comes from behind, nobody can parry or block if attacker is behind + bool canParryOrBlock = victim.HasInArc((float)Math.PI, this) || victim.HasAuraType(AuraType.IgnoreHitDirection); + + // only creatures can dodge if attacker is behind + bool canDodge = !victim.IsTypeId(TypeId.Player) || canParryOrBlock; + + // if victim is casting or cc'd it can't avoid attacks + if (victim.IsNonMeleeSpellCast(false) || victim.HasUnitState(UnitState.Controlled)) + { + canDodge = false; + canParryOrBlock = false; + } + + // 1. MISS + tmp = miss_chance; + if (tmp > 0 && roll < (sum += tmp)) + return MeleeHitOutcome.Miss; + + // always crit against a sitting target (except 0 crit chance) + if (victim.IsTypeId(TypeId.Player) && crit_chance > 0 && !victim.IsStandState()) + return MeleeHitOutcome.Crit; + + // 2. DODGE + if (canDodge) + { + tmp = dodge_chance; + if (tmp > 0 // check if unit _can_ dodge + && roll < (sum += tmp)) + return MeleeHitOutcome.Dodge; + } + + // 3. PARRY + if (canParryOrBlock) + { + tmp = parry_chance; + if (tmp > 0 // check if unit _can_ parry + && roll < (sum += tmp)) + return MeleeHitOutcome.Parry; + } + + // 4. GLANCING + // Max 40% chance to score a glancing blow against mobs that are higher level (can do only players and pets and not with ranged weapon) + if ((IsTypeId(TypeId.Player) || IsPet()) && + !victim.IsTypeId(TypeId.Player) && !victim.IsPet() && + attackerLevel + 3 < victimLevel) + { + // cap possible value (with bonuses > max skill) + tmp = (int)(10 + 10 * (victimLevel - attackerLevel)) * 100; + if (tmp > 0 && roll < (sum += tmp)) + return MeleeHitOutcome.Glancing; + } + + // 5. BLOCK + if (canParryOrBlock) + { + tmp = block_chance; + if (tmp > 0 // check if unit _can_ block + && roll < (sum += tmp)) + return MeleeHitOutcome.Block; + } + + // 6.CRIT + tmp = crit_chance; + if (tmp > 0 && roll < (sum += tmp)) + { + if (GetTypeId() != TypeId.Unit || !(ToCreature().GetCreatureTemplate().FlagsExtra.HasAnyFlag(CreatureFlagsExtra.NoCrit))) + return MeleeHitOutcome.Crit; + } + + // 7. CRUSHING + // mobs can score crushing blows if they're 4 or more levels above victim + if (attackerLevel >= victimLevel + 4 && + // can be from by creature (if can) or from controlled player that considered as creature + !IsControlledByPlayer() && + !(GetTypeId() == TypeId.Unit && ToCreature().GetCreatureTemplate().FlagsExtra.HasAnyFlag(CreatureFlagsExtra.NoCrush))) + { + // add 2% chance per level, min. is 15% + tmp = (int)(attackerLevel - victimLevel * 1000 - 1500); + if (roll < (sum += tmp)) + { + Log.outDebug(LogFilter.Unit, "RollMeleeOutcomeAgainst: CRUSHING <{0}, {1})", sum - tmp, sum); + return MeleeHitOutcome.Crushing; + } + } + + // 8. HIT + return MeleeHitOutcome.Normal; + } + public uint CalculateDamage(WeaponAttackType attType, bool normalized, bool addTotalPct) + { + float minDamage, maxDamage = 0.0f; + + if (normalized || !addTotalPct) + { + CalculateMinMaxDamage(attType, normalized, addTotalPct, out minDamage, out maxDamage); + if (IsInFeralForm() && attType == WeaponAttackType.BaseAttack) + { + float minOffhandDamage = 0.0f; + float maxOffhandDamage = 0.0f; + CalculateMinMaxDamage(WeaponAttackType.OffAttack, normalized, addTotalPct, out minOffhandDamage, out maxOffhandDamage); + minDamage += minOffhandDamage; + maxDamage += maxOffhandDamage; + } + } + else + { + switch (attType) + { + case WeaponAttackType.RangedAttack: + minDamage = GetFloatValue(UnitFields.MinRangedDamage); + maxDamage = GetFloatValue(UnitFields.MaxRangedDamage); + break; + case WeaponAttackType.BaseAttack: + minDamage = GetFloatValue(UnitFields.MinDamage); + maxDamage = GetFloatValue(UnitFields.MaxDamage); + if (IsInFeralForm()) + { + minDamage += GetFloatValue(UnitFields.MinOffHandDamage); + maxDamage += GetFloatValue(UnitFields.MaxOffHandDamage); + } + break; + case WeaponAttackType.OffAttack: + minDamage = GetFloatValue(UnitFields.MinOffHandDamage); + maxDamage = GetFloatValue(UnitFields.MaxOffHandDamage); + break; + // Just for good manner + default: + minDamage = 0.0f; + maxDamage = 0.0f; + break; + } + } + minDamage = Math.Max(0.0f, minDamage); + maxDamage = Math.Max(0.0f, maxDamage); + + + if (minDamage > maxDamage) + { + minDamage = minDamage + maxDamage; + maxDamage = minDamage - maxDamage; + minDamage = minDamage - maxDamage; + } + + if (maxDamage == 0.0f) + maxDamage = 5.0f; + + return RandomHelper.URand(minDamage, maxDamage); + } + public float GetWeaponDamageRange(WeaponAttackType attType, WeaponDamageRange type) + { + if (attType == WeaponAttackType.OffAttack && !haveOffhandWeapon()) + return 0.0f; + + return m_weaponDamage[(int)attType][(int)type]; + } + public float GetAPMultiplier(WeaponAttackType attType, bool normalized) + { + if (!IsTypeId(TypeId.Player)) + return GetBaseAttackTime(attType) / 1000.0f; + + Item weapon = ToPlayer().GetWeaponForAttack(attType, true); + if (!weapon) + return 2.0f; + + if (!normalized) + return weapon.GetTemplate().GetDelay() / 1000.0f; + + switch ((ItemSubClassWeapon)weapon.GetTemplate().GetSubClass()) + { + case ItemSubClassWeapon.Axe2: + case ItemSubClassWeapon.Mace2: + case ItemSubClassWeapon.Polearm: + case ItemSubClassWeapon.Sword2: + case ItemSubClassWeapon.Staff: + case ItemSubClassWeapon.FishingPole: + return 3.3f; + case ItemSubClassWeapon.Axe: + case ItemSubClassWeapon.Mace: + case ItemSubClassWeapon.Sword: + case ItemSubClassWeapon.Warglaives: + case ItemSubClassWeapon.Exotic: + case ItemSubClassWeapon.Exotic2: + case ItemSubClassWeapon.Fist: + return 2.4f; + case ItemSubClassWeapon.Dagger: + return 1.7f; + case ItemSubClassWeapon.Thrown: + return 2.0f; + default: + return weapon.GetTemplate().GetDelay() / 1000.0f; + } + } + public float GetTotalAttackPowerValue(WeaponAttackType attType) + { + if (attType == WeaponAttackType.RangedAttack) + { + int ap = GetInt32Value(UnitFields.RangedAttackPower); + if (ap < 0) + return 0.0f; + return ap * (1.0f + GetFloatValue(UnitFields.RangedAttackPowerMultiplier)); + } + else + { + int ap = GetInt32Value(UnitFields.AttackPower); + if (ap < 0) + return 0.0f; + return ap * (1.0f + GetFloatValue(UnitFields.AttackPowerMultiplier)); + } + } + public float GetModifierValue(UnitMods unitMod, UnitModifierType modifierType) + { + if (unitMod >= UnitMods.End || modifierType >= UnitModifierType.End) + { + Log.outError(LogFilter.Unit, "attempt to access non-existing modifier value from UnitMods!"); + return 0.0f; + } + + if (modifierType == UnitModifierType.TotalPCT && m_auraModifiersGroup[(int)unitMod][(int)modifierType] <= 0.0f) + return 0.0f; + + return m_auraModifiersGroup[(int)unitMod][(int)modifierType]; + } + public bool IsWithinMeleeRange(Unit obj) + { + if (!obj || !IsInMap(obj) || !IsInPhase(obj)) + return false; + + float dx = GetPositionX() - obj.GetPositionX(); + float dy = GetPositionY() - obj.GetPositionY(); + float dz = GetPositionZMinusOffset() - obj.GetPositionZMinusOffset(); + float distsq = (dx * dx) + (dy * dy) + (dz * dz); + + float maxdist = GetMeleeRange(obj); + + return distsq <= maxdist * maxdist; + } + + public float GetMeleeRange(Unit target) + { + float range = GetCombatReach() + target.GetCombatReach() + 4.0f / 3.0f; + return Math.Max(range, SharedConst.NominalMeleeRange); + } + + public void SetBaseAttackTime(WeaponAttackType att, uint val) + { + m_baseAttackSpeed[(int)att] = val; + UpdateAttackTimeField(att); + } + void UpdateAttackTimeField(WeaponAttackType att) + { + SetUInt32Value(UnitFields.BaseAttackTime + (int)att, (uint)(m_baseAttackSpeed[(int)att] * m_modAttackSpeedPct[(int)att])); + } + public virtual void SetPvP(bool state) + { + if (state) + SetByteFlag(UnitFields.Bytes2, UnitBytes2Offsets.PvpFlag, UnitBytes2Flags.PvP); + else + RemoveByteFlag(UnitFields.Bytes2, UnitBytes2Offsets.PvpFlag, UnitBytes2Flags.PvP); + } + + public bool CanHaveThreatList(bool skipAliveCheck = false) + { + // only creatures can have threat list + if (!IsTypeId(TypeId.Unit)) + return false; + + // only alive units can have threat list + if (!skipAliveCheck && !IsAlive()) + return false; + + // totems can not have threat list + if (IsTotem()) + return false; + + // summons can not have a threat list, unless they are controlled by a creature + if (HasUnitTypeMask(UnitTypeMask.Minion | UnitTypeMask.Guardian | UnitTypeMask.ControlableGuardian) && GetOwnerGUID().IsPlayer()) + return false; + + return true; + } + + uint CalcSpellResistance(Unit victim, SpellSchoolMask schoolMask, SpellInfo spellInfo) + { + // Magic damage, check for resists + if (!Convert.ToBoolean(schoolMask & SpellSchoolMask.Spell)) + return 0; + + // Ignore spells that can't be resisted + if (spellInfo != null && spellInfo.HasAttribute(SpellAttr4.IgnoreResistances)) + return 0; + + byte bossLevel = 83; + uint bossResistanceConstant = 510; + uint resistanceConstant = 0; + uint level = victim.getLevel(); + + if (level == bossLevel) + resistanceConstant = bossResistanceConstant; + else + resistanceConstant = level * 5; + + int baseVictimResistance = (int)victim.GetResistance(schoolMask); + baseVictimResistance += GetTotalAuraModifierByMiscMask(AuraType.ModTargetResistance, (int)schoolMask); + + Player player = ToPlayer(); + if (player) + baseVictimResistance -= player.GetSpellPenetrationItemMod(); + + // Resistance can't be lower then 0 + int victimResistance = Math.Max(baseVictimResistance, 0); + + if (victimResistance > 0) + { + int ignoredResistance = 0; + + var ResIgnoreAuras = GetAuraEffectsByType(AuraType.ModIgnoreTargetResist); + foreach (var eff in ResIgnoreAuras) + if (Convert.ToBoolean(eff.GetMiscValue() & (int)schoolMask)) + ignoredResistance += eff.GetAmount(); + + ignoredResistance = Math.Min(ignoredResistance, 100); + MathFunctions.ApplyPct(ref victimResistance, 100 - ignoredResistance); + } + + if (victimResistance <= 0) + return 0; + + float averageResist = victimResistance / victimResistance + resistanceConstant; + + float[] discreteResistProbability = new float[11]; + for (uint i = 0; i < 11; ++i) + { + discreteResistProbability[i] = 0.5f - 2.5f * Math.Abs(0.1f * i - averageResist); + if (discreteResistProbability[i] < 0.0f) + discreteResistProbability[i] = 0.0f; + } + + if (averageResist <= 0.1f) + { + discreteResistProbability[0] = 1.0f - 7.5f * averageResist; + discreteResistProbability[1] = 5.0f * averageResist; + discreteResistProbability[2] = 2.5f * averageResist; + } + + uint resistance = 0; + float r = (float)RandomHelper.NextDouble(); + float probabilitySum = discreteResistProbability[0]; + + while (r >= probabilitySum && resistance < 10) + probabilitySum += discreteResistProbability[++resistance]; + + return resistance * 10; + } + + public void CalcAbsorbResist(Unit victim, SpellSchoolMask schoolMask, DamageEffectType damagetype, uint damage, ref uint absorb, ref uint resist, SpellInfo spellInfo = null) + { + if (victim == null || !victim.IsAlive() || damage == 0) + return; + + DamageInfo dmgInfo = new DamageInfo(this, victim, damage, spellInfo, schoolMask, damagetype, WeaponAttackType.BaseAttack); + + uint spellResistance = CalcSpellResistance(victim, schoolMask, spellInfo); + dmgInfo.ResistDamage(MathFunctions.CalculatePct(damage, spellResistance)); + + // Ignore Absorption Auras + float auraAbsorbMod = 0; + var AbsIgnoreAurasA = GetAuraEffectsByType(AuraType.ModTargetAbsorbSchool); + foreach (var eff in AbsIgnoreAurasA) + { + if (!Convert.ToBoolean(eff.GetMiscValue() & (int)schoolMask)) + continue; + + if (eff.GetAmount() > auraAbsorbMod) + auraAbsorbMod = eff.GetAmount(); + } + + var AbsIgnoreAurasB = GetAuraEffectsByType(AuraType.ModTargetAbilityAbsorbSchool); + foreach (var eff in AbsIgnoreAurasB) + { + if (!Convert.ToBoolean(eff.GetMiscValue() & (int)schoolMask)) + continue; + + if ((eff.GetAmount() > auraAbsorbMod) && eff.IsAffectingSpell(spellInfo)) + auraAbsorbMod = eff.GetAmount(); + } + MathFunctions.RoundToInterval(ref auraAbsorbMod, 0.0f, 100.0f); + + int absorbIgnoringDamage = (int)MathFunctions.CalculatePct(dmgInfo.GetDamage(), auraAbsorbMod); + dmgInfo.ModifyDamage(-absorbIgnoringDamage); + + // We're going to call functions which can modify content of the list during iteration over it's elements + // Let's copy the list so we can prevent iterator invalidation + var vSchoolAbsorbCopy = victim.GetAuraEffectsByType(AuraType.SchoolAbsorb); + vSchoolAbsorbCopy.Sort();//new AbsorbAuraOrderPred());todo fix me + + // absorb without mana cost + foreach (var eff in vSchoolAbsorbCopy) + { + // Check if aura was removed during iteration - we don't need to work on such auras + AuraApplication aurApp = eff.GetBase().GetApplicationOfTarget(victim.GetGUID()); + if (aurApp == null) + continue; + if (!Convert.ToBoolean(eff.GetMiscValue() & (int)schoolMask)) + continue; + + // get amount which can be still absorbed by the aura + int currentAbsorb = eff.GetAmount(); + // aura with infinite absorb amount - let the scripts handle absorbtion amount, set here to 0 for safety + if (currentAbsorb < 0) + currentAbsorb = 0; + + uint tempAbsorb = (uint)currentAbsorb; + + bool defaultPrevented = false; + + eff.GetBase().CallScriptEffectAbsorbHandlers(eff, aurApp, dmgInfo, ref tempAbsorb, ref defaultPrevented); + currentAbsorb = (int)tempAbsorb; + + if (defaultPrevented) + continue; + + // absorb must be smaller than the damage itself + MathFunctions.RoundToInterval(ref currentAbsorb, 0, dmgInfo.GetDamage()); + + dmgInfo.AbsorbDamage((uint)currentAbsorb); + + tempAbsorb = (uint)currentAbsorb; + eff.GetBase().CallScriptEffectAfterAbsorbHandlers(eff, aurApp, dmgInfo, ref tempAbsorb); + + // Check if our aura is using amount to count damage + if (eff.GetAmount() >= 0) + { + // Reduce shield amount + eff.SetAmount(eff.GetAmount() - currentAbsorb); + // Aura cannot absorb anything more - remove it + if (eff.GetAmount() <= 0) + eff.GetBase().Remove(AuraRemoveMode.EnemySpell); + } + } + + // absorb by mana cost + var vManaShieldCopy = victim.GetAuraEffectsByType(AuraType.ManaShield); + foreach (var absorbAurEff in vManaShieldCopy) + { + if (dmgInfo.GetDamage() <= 0) + break; + + // Check if aura was removed during iteration - we don't need to work on such auras + AuraApplication aurApp = absorbAurEff.GetBase().GetApplicationOfTarget(victim.GetGUID()); + if (aurApp == null) + continue; + // check damage school mask + if (!Convert.ToBoolean(absorbAurEff.GetMiscValue() & (int)schoolMask)) + continue; + + // get amount which can be still absorbed by the aura + int currentAbsorb = absorbAurEff.GetAmount(); + // aura with infinite absorb amount - let the scripts handle absorbtion amount, set here to 0 for safety + if (currentAbsorb < 0) + currentAbsorb = 0; + + uint tempAbsorb = (uint)currentAbsorb; + + bool defaultPrevented = false; + + absorbAurEff.GetBase().CallScriptEffectManaShieldHandlers(absorbAurEff, aurApp, dmgInfo, ref tempAbsorb, defaultPrevented); + currentAbsorb = (int)tempAbsorb; + + if (defaultPrevented) + continue; + + // absorb must be smaller than the damage itself + MathFunctions.RoundToInterval(ref currentAbsorb, 0, dmgInfo.GetDamage()); + + int manaReduction = currentAbsorb; + + // lower absorb amount by talents + float manaMultiplier = absorbAurEff.GetSpellEffectInfo().CalcValueMultiplier(absorbAurEff.GetCaster()); + if (manaMultiplier != 0) + manaReduction = (int)(manaReduction * manaMultiplier); + + int manaTaken = -victim.ModifyPower(PowerType.Mana, -manaReduction); + + // take case when mana has ended up into account + currentAbsorb = currentAbsorb != 0 ? currentAbsorb * manaTaken / manaReduction : 0; + + dmgInfo.AbsorbDamage((uint)currentAbsorb); + + tempAbsorb = (uint)currentAbsorb; + absorbAurEff.GetBase().CallScriptEffectAfterManaShieldHandlers(absorbAurEff, aurApp, dmgInfo, ref tempAbsorb); + + // Check if our aura is using amount to count damage + if (absorbAurEff.GetAmount() >= 0) + { + absorbAurEff.SetAmount(absorbAurEff.GetAmount() - currentAbsorb); + if ((absorbAurEff.GetAmount() <= 0)) + absorbAurEff.GetBase().Remove(AuraRemoveMode.EnemySpell); + } + } + + dmgInfo.ModifyDamage(absorbIgnoringDamage); + + // split damage auras - only when not damaging self + if (victim != this) + { + // We're going to call functions which can modify content of the list during iteration over it's elements + // Let's copy the list so we can prevent iterator invalidation + var vSplitDamagePctCopy = victim.GetAuraEffectsByType(AuraType.SplitDamagePct); + foreach (var eff in vSplitDamagePctCopy) + { + if (dmgInfo.GetDamage() <= 0) + break; + + // Check if aura was removed during iteration - we don't need to work on such auras + AuraApplication aurApp = eff.GetBase().GetApplicationOfTarget(victim.GetGUID()); + if (aurApp == null) + continue; + + // check damage school mask + if (!Convert.ToBoolean(eff.GetMiscValue() & (int)schoolMask)) + continue; + + // Damage can be splitted only if aura has an alive caster + Unit caster = eff.GetCaster(); + if (caster == null || (caster == victim) || !caster.IsInWorld || !caster.IsAlive()) + continue; + + uint splitDamage = MathFunctions.CalculatePct(dmgInfo.GetDamage(), eff.GetAmount()); + + eff.GetBase().CallScriptEffectSplitHandlers(eff, aurApp, dmgInfo, splitDamage); + + // absorb must be smaller than the damage itself + MathFunctions.RoundToInterval(ref splitDamage, 0, dmgInfo.GetDamage()); + + dmgInfo.AbsorbDamage(splitDamage); + + // check if caster is immune to damage + if (caster.IsImmunedToDamage(schoolMask)) + { + victim.SendSpellMiss(caster, eff.GetSpellInfo().Id, SpellMissInfo.Immune); + continue; + } + + uint split_absorb = 0; + DealDamageMods(caster, ref splitDamage, ref split_absorb); + + SpellNonMeleeDamage log = new SpellNonMeleeDamage(this, caster, eff.GetSpellInfo().Id, eff.GetBase().GetSpellXSpellVisualId(), schoolMask, eff.GetBase().GetCastGUID()); + CleanDamage cleanDamage = new CleanDamage(splitDamage, 0, WeaponAttackType.BaseAttack, MeleeHitOutcome.Normal); + DealDamage(caster, splitDamage, cleanDamage, DamageEffectType.Direct, schoolMask, eff.GetSpellInfo(), false); + log.damage = splitDamage; + log.absorb = split_absorb; + SendSpellNonMeleeDamageLog(log); + + // break 'Fear' and similar auras + caster.ProcDamageAndSpellFor(true, this, ProcFlags.TakenSpellMagicDmgClassNeg, ProcFlagsExLegacy.NormalHit, WeaponAttackType.BaseAttack, eff.GetSpellInfo(), splitDamage); + } + } + + resist = dmgInfo.GetResist(); + absorb = dmgInfo.GetAbsorb(); + } + + public void CalcHealAbsorb(Unit victim, SpellInfo healSpell, ref uint healAmount, ref uint absorb) + { + if (healAmount == 0) + return; + + int RemainingHeal = (int)healAmount; + + // Need remove expired auras after + bool existExpired = false; + + // absorb without mana cost + var vHealAbsorb = victim.GetAuraEffectsByType(AuraType.SchoolHealAbsorb); + foreach (var eff in vHealAbsorb) + { + if (RemainingHeal <= 0) + break; + + if (!Convert.ToBoolean(eff.GetMiscValue() & (int)healSpell.SchoolMask)) + continue; + + // Max Amount can be absorbed by this aura + int currentAbsorb = eff.GetAmount(); + + // Found empty aura (impossible but..) + if (currentAbsorb <= 0) + { + existExpired = true; + continue; + } + + // currentAbsorb - damage can be absorbed by shield + // If need absorb less damage + if (RemainingHeal < currentAbsorb) + currentAbsorb = RemainingHeal; + + RemainingHeal -= currentAbsorb; + + // Reduce shield amount + eff.SetAmount(eff.GetAmount() - currentAbsorb); + // Need remove it later + if (eff.GetAmount() <= 0) + existExpired = true; + } + + // Remove all expired absorb auras + if (existExpired) + { + for (var i = 0; i < vHealAbsorb.Count;) + { + AuraEffect auraEff = vHealAbsorb[i]; + ++i; + if (auraEff.GetAmount() <= 0) + { + uint removedAuras = victim.m_removedAurasCount; + auraEff.GetBase().Remove(AuraRemoveMode.EnemySpell); + if (removedAuras + 1 < victim.m_removedAurasCount) + i = 0; + } + } + } + + absorb = (uint)(RemainingHeal > 0 ? (healAmount - RemainingHeal) : healAmount); + healAmount = (uint)RemainingHeal; + } + + public uint CalcArmorReducedDamage(Unit attacker, Unit victim, uint damage, SpellInfo spellInfo, WeaponAttackType attackType = WeaponAttackType.Max) + { + float armor = victim.GetArmor(); + + // bypass enemy armor by SPELL_AURA_BYPASS_ARMOR_FOR_CASTER + int armorBypassPct = 0; + var reductionAuras = victim.GetAuraEffectsByType(AuraType.BypassArmorForCaster); + foreach (var eff in reductionAuras) + if (eff.GetCasterGUID() == GetGUID()) + armorBypassPct += eff.GetAmount(); + armor = MathFunctions.CalculatePct(armor, 100 - Math.Min(armorBypassPct, 100)); + + // Ignore enemy armor by SPELL_AURA_MOD_TARGET_RESISTANCE aura + armor += GetTotalAuraModifierByMiscMask(AuraType.ModTargetResistance, (int)SpellSchoolMask.Normal); + + if (spellInfo != null) + { + Player modOwner = GetSpellModOwner(); + if (modOwner != null) + modOwner.ApplySpellMod(spellInfo.Id, SpellModOp.IgnoreArmor, ref armor); + } + + var resIgnoreAuras = GetAuraEffectsByType(AuraType.ModIgnoreTargetResist); + foreach (var eff in resIgnoreAuras) + { + if (eff.GetMiscValue().HasAnyFlag((int)SpellSchoolMask.Normal)) + armor = (float)Math.Floor(MathFunctions.AddPct(ref armor, -eff.GetAmount())); + } + + // Apply Player CR_ARMOR_PENETRATION rating + if (IsTypeId(TypeId.Player)) + { + float maxArmorPen = 0; + if (victim.getLevel() < 60) + maxArmorPen = 400 + 85 * victim.getLevel(); + else + maxArmorPen = 400 + 85 * victim.getLevel() + 4.5f * 85 * (victim.getLevel() - 59); + + // Cap armor penetration to this number + maxArmorPen = Math.Min((armor + maxArmorPen) / 3, armor); + // Figure out how much armor do we ignore + float armorPen = MathFunctions.CalculatePct(maxArmorPen, ToPlayer().GetRatingBonusValue(CombatRating.ArmorPenetration)); + // Got the value, apply it + armor -= Math.Min(armorPen, maxArmorPen); + } + + if (MathFunctions.fuzzyLe(armor, 0.0f)) + return damage; + + uint attackerLevel = attacker.getLevel(); + if (attackerLevel > CliDB.ArmorMitigationByLvlGameTable.GetTableRowCount()) + attackerLevel = (uint)CliDB.ArmorMitigationByLvlGameTable.GetTableRowCount(); + + GtArmorMitigationByLvlRecord ambl = CliDB.ArmorMitigationByLvlGameTable.GetRow(attackerLevel); + if (ambl == null) + return damage; + + float mitigation = Math.Min(armor / (armor + ambl.Mitigation), 0.85f); + return Math.Max((uint)(damage * (1.0f - mitigation)), 1); + } + + public uint MeleeDamageBonusDone(Unit victim, uint pdamage, WeaponAttackType attType, SpellInfo spellProto = null) + { + if (victim == null || pdamage == 0) + return 0; + + uint creatureTypeMask = victim.GetCreatureTypeMask(); + + // Done fixed damage bonus auras + int DoneFlatBenefit = 0; + + // ..done + var mDamageDoneCreature = GetAuraEffectsByType(AuraType.ModDamageDoneCreature); + foreach (var eff in mDamageDoneCreature) + if (Convert.ToBoolean(creatureTypeMask & eff.GetMiscValue())) + DoneFlatBenefit += eff.GetAmount(); + + // ..done + // SPELL_AURA_MOD_DAMAGE_DONE included in weapon damage + + // ..done (base at attack power for marked target and base at attack power for creature type) + int APbonus = 0; + + if (attType == WeaponAttackType.RangedAttack) + { + APbonus += victim.GetTotalAuraModifier(AuraType.RangedAttackPowerAttackerBonus); + + // ..done (base at attack power and creature type) + var mCreatureAttackPower = GetAuraEffectsByType(AuraType.ModRangedAttackPowerVersus); + foreach (var eff in mCreatureAttackPower) + if (Convert.ToBoolean(creatureTypeMask & eff.GetMiscValue())) + APbonus += eff.GetAmount(); + } + else + { + APbonus += victim.GetTotalAuraModifier(AuraType.MeleeAttackPowerAttackerBonus); + + // ..done (base at attack power and creature type) + var mCreatureAttackPower = GetAuraEffectsByType(AuraType.ModMeleeAttackPowerVersus); + foreach (var eff in mCreatureAttackPower) + if (Convert.ToBoolean(creatureTypeMask & eff.GetMiscValue())) + APbonus += eff.GetAmount(); + } + + if (APbonus != 0) // Can be negative + { + bool normalized = spellProto != null && spellProto.HasEffect(GetMap().GetDifficultyID(), SpellEffectName.NormalizedWeaponDmg); + DoneFlatBenefit += (int)(APbonus / 3.5f * GetAPMultiplier(attType, normalized)); + } + + // Done total percent damage auras + float DoneTotalMod = 1.0f; + + // Some spells don't benefit from pct done mods + if (spellProto != null) + { + // mods for SPELL_SCHOOL_MASK_NORMAL are already factored in base melee damage calculation + if (!spellProto.HasAttribute(SpellAttr6.NoDonePctDamageMods) && !spellProto.GetSchoolMask().HasAnyFlag(SpellSchoolMask.Normal)) + { + float maxModDamagePercentSchool = 0.0f; + for (var i = SpellSchools.Holy; i < SpellSchools.Max; ++i) + { + if (Convert.ToBoolean((int)spellProto.GetSchoolMask() & (1 << (int)i))) + maxModDamagePercentSchool = Math.Max(maxModDamagePercentSchool, GetFloatValue(PlayerFields.ModDamageDonePct + (int)i)); + } + + DoneTotalMod *= maxModDamagePercentSchool; + } + } + + var mDamageDoneVersus = GetAuraEffectsByType(AuraType.ModDamageDoneVersus); + foreach (var eff in mDamageDoneVersus) + if (Convert.ToBoolean(creatureTypeMask & eff.GetMiscValue())) + MathFunctions.AddPct(ref DoneTotalMod, eff.GetAmount()); + + // bonus against aurastate + var mDamageDoneVersusAurastate = GetAuraEffectsByType(AuraType.ModDamageDoneVersusAurastate); + foreach (var eff in mDamageDoneVersusAurastate) + if (victim.HasAuraState((AuraStateType)eff.GetMiscValue())) + MathFunctions.AddPct(ref DoneTotalMod, eff.GetAmount()); + + // Add SPELL_AURA_MOD_DAMAGE_DONE_FOR_MECHANIC percent bonus + if (spellProto != null) + MathFunctions.AddPct(ref DoneTotalMod, GetTotalAuraModifierByMiscValue(AuraType.ModDamageDoneForMechanic, (int)spellProto.Mechanic)); + + float tmpDamage = (pdamage + DoneFlatBenefit) * DoneTotalMod; + + // apply spellmod to Done damage + if (spellProto != null) + { + Player modOwner = GetSpellModOwner(); + if (modOwner != null) + modOwner.ApplySpellMod(spellProto.Id, SpellModOp.Damage, ref tmpDamage); + } + + // bonus result can be negative + return (uint)Math.Max(tmpDamage, 0.0f); + } + public uint MeleeDamageBonusTaken(Unit attacker, uint pdamage, WeaponAttackType attType, SpellInfo spellProto = null) + { + if (pdamage == 0) + return 0; + + int TakenFlatBenefit = 0; + float TakenTotalCasterMod = 0.0f; + + // get all auras from caster that allow the spell to ignore resistance (sanctified wrath) + int attackSchoolMask = (int)(spellProto != null ? spellProto.GetSchoolMask() : SpellSchoolMask.Normal); + var IgnoreResistAuras = attacker.GetAuraEffectsByType(AuraType.ModIgnoreTargetResist); + foreach (var eff in IgnoreResistAuras) + { + if (eff.GetMiscValue().HasAnyFlag(attackSchoolMask)) + TakenTotalCasterMod += eff.GetAmount(); + } + + // ..taken + var mDamageTaken = GetAuraEffectsByType(AuraType.ModDamageTaken); + foreach (var eff in mDamageTaken) + if (Convert.ToBoolean(eff.GetMiscValue() & (int)attacker.GetMeleeDamageSchoolMask())) + TakenFlatBenefit += eff.GetAmount(); + + if (attType != WeaponAttackType.RangedAttack) + TakenFlatBenefit += GetTotalAuraModifier(AuraType.ModMeleeDamageTaken); + else + TakenFlatBenefit += GetTotalAuraModifier(AuraType.ModRangedDamageTaken); + + // Taken total percent damage auras + float TakenTotalMod = 1.0f; + + // ..taken + TakenTotalMod *= GetTotalAuraMultiplierByMiscMask(AuraType.ModDamagePercentTaken, (uint)attacker.GetMeleeDamageSchoolMask()); + + // .. taken pct (special attacks) + if (spellProto != null) + { + // From caster spells + var mOwnerTaken = GetAuraEffectsByType(AuraType.ModSpellDamageFromCaster); + foreach (var eff in mOwnerTaken) + if (eff.GetCasterGUID() == attacker.GetGUID() && eff.IsAffectingSpell(spellProto)) + MathFunctions.AddPct(ref TakenTotalMod, eff.GetAmount()); + + // Mod damage from spell mechanic + uint mechanicMask = spellProto.GetAllEffectsMechanicMask(); + + // Shred, Maul - "Effects which increase Bleed damage also increase Shred damage" + if (spellProto.SpellFamilyName == SpellFamilyNames.Druid && spellProto.SpellFamilyFlags[0].HasAnyFlag(0x00008800)) + mechanicMask |= (1 << (int)Mechanics.Bleed); + + if (mechanicMask != 0) + { + var mDamageDoneMechanic = GetAuraEffectsByType(AuraType.ModMechanicDamageTakenPercent); + foreach (var eff in mDamageDoneMechanic) + if (mechanicMask.HasAnyFlag((uint)(1 << eff.GetMiscValue()))) + MathFunctions.AddPct(ref TakenTotalMod, eff.GetAmount()); + } + } + + AuraEffect cheatDeath = GetAuraEffect(45182, 0); + if (cheatDeath != null) + MathFunctions.AddPct(ref TakenTotalMod, cheatDeath.GetAmount()); + + if (attType != WeaponAttackType.RangedAttack) + { + var mModMeleeDamageTakenPercent = GetAuraEffectsByType(AuraType.ModMeleeDamageTakenPct); + foreach (var eff in mModMeleeDamageTakenPercent) + MathFunctions.AddPct(ref TakenTotalMod, eff.GetAmount()); + } + else + { + var mModRangedDamageTakenPercent = GetAuraEffectsByType(AuraType.ModRangedDamageTakenPct); + foreach (var eff in mModRangedDamageTakenPercent) + MathFunctions.AddPct(ref TakenTotalMod, eff.GetAmount()); + } + + float tmpDamage = 0.0f; + + if (TakenTotalCasterMod != 0) + { + if (TakenFlatBenefit < 0) + { + if (TakenTotalMod < 1) + tmpDamage = (((MathFunctions.CalculatePct(pdamage, TakenTotalCasterMod) + TakenFlatBenefit) * TakenTotalMod) + MathFunctions.CalculatePct(pdamage, TakenTotalCasterMod)); + else + tmpDamage = (((MathFunctions.CalculatePct(pdamage, TakenTotalCasterMod) + TakenFlatBenefit) + MathFunctions.CalculatePct(pdamage, TakenTotalCasterMod)) * TakenTotalMod); + } + else if (TakenTotalMod < 1) + tmpDamage = ((MathFunctions.CalculatePct(pdamage + TakenFlatBenefit, TakenTotalCasterMod) * TakenTotalMod) + MathFunctions.CalculatePct(pdamage + TakenFlatBenefit, TakenTotalCasterMod)); + } + if (tmpDamage == 0) + tmpDamage = (pdamage + TakenFlatBenefit) * TakenTotalMod; + + // bonus result can be negative + return (uint)Math.Max(tmpDamage, 0.0f); + } + + bool isBlockCritical() + { + if (RandomHelper.randChance(GetTotalAuraModifier(AuraType.ModBlockCritChance))) + return true; + return false; + } + public virtual SpellSchoolMask GetMeleeDamageSchoolMask() { return SpellSchoolMask.None; } + + // Redirect Threat + public void SetRedirectThreat(ObjectGuid guid, uint pct) { _redirectThreatInfo.Set(guid, pct); } + public void ResetRedirectThreat() { SetRedirectThreat(ObjectGuid.Empty, 0); } + void ModifyRedirectThreat(int amount) { _redirectThreatInfo.ModifyThreatPct(amount); } + public uint GetRedirectThreatPercent() { return _redirectThreatInfo.GetThreatPct(); } + public Unit GetRedirectThreatTarget() + { + return Global.ObjAccessor.GetUnit(this, _redirectThreatInfo.GetTargetGUID()); + } + + float CalculateDefaultCoefficient(SpellInfo spellInfo, DamageEffectType damagetype) + { + // Damage over Time spells bonus calculation + float DotFactor = 1.0f; + if (damagetype == DamageEffectType.DOT) + { + + int DotDuration = spellInfo.GetDuration(); + if (!spellInfo.IsChanneled() && DotDuration > 0) + DotFactor = DotDuration / 15000.0f; + + uint DotTicks = spellInfo.GetMaxTicks(GetMap().GetDifficultyID()); + if (DotTicks != 0) + DotFactor /= DotTicks; + } + + uint CastingTime = (uint)(spellInfo.IsChanneled() ? spellInfo.GetDuration() : spellInfo.CalcCastTime()); + // Distribute Damage over multiple effects, reduce by AoE + CastingTime = GetCastingTimeForBonus(spellInfo, damagetype, CastingTime); + + // As wowwiki says: C = (Cast Time / 3.5) + return (CastingTime / 3500.0f) * DotFactor; + } + + public void ApplyAttackTimePercentMod(WeaponAttackType att, float val, bool apply) + { + float remainingTimePct = m_attackTimer[(int)att] / (m_baseAttackSpeed[(int)att] * m_modAttackSpeedPct[(int)att]); + if (val > 0) + { + MathFunctions.ApplyPercentModFloatVar(ref m_modAttackSpeedPct[(int)att], val, !apply); + + if (att == WeaponAttackType.BaseAttack) + ApplyPercentModFloatValue(UnitFields.ModHaste, val, !apply); + else if (att == WeaponAttackType.RangedAttack) + ApplyPercentModFloatValue(UnitFields.ModRangedHaste, val, !apply); + } + else + { + MathFunctions.ApplyPercentModFloatVar(ref m_modAttackSpeedPct[(int)att], -val, apply); + + if (att == WeaponAttackType.BaseAttack) + ApplyPercentModFloatValue(UnitFields.ModHaste, -val, apply); + else if (att == WeaponAttackType.RangedAttack) + ApplyPercentModFloatValue(UnitFields.ModRangedHaste, -val, apply); + } + + UpdateAttackTimeField(att); + m_attackTimer[(int)att] = (uint)(m_baseAttackSpeed[(int)att] * m_modAttackSpeedPct[(int)att] * remainingTimePct); + } + + // function based on function Unit.CanAttack from 13850 client + public bool _IsValidAttackTarget(Unit target, SpellInfo bySpell, WorldObject obj = null) + { + Contract.Assert(target != null); + + // can't attack self + if (this == target) + return false; + + // can't attack unattackable units or GMs + if (target.HasUnitState(UnitState.Unattackable) + || (target.IsTypeId(TypeId.Player) && target.ToPlayer().IsGameMaster())) + return false; + + // can't attack own vehicle or passenger + if (m_vehicle != null) + if (IsOnVehicle(target) || m_vehicle.GetBase().IsOnVehicle(target)) + return false; + + // can't attack invisible (ignore stealth for aoe spells) also if the area being looked at is from a spell use the dynamic object created instead of the casting unit. Ignore stealth if target is player and unit in combat with same player + if ((bySpell == null || !bySpell.HasAttribute(SpellAttr6.CanTargetInvisible)) && (obj ? !obj.CanSeeOrDetect(target, bySpell != null && bySpell.IsAffectingArea(GetMap().GetDifficultyID())) : !CanSeeOrDetect(target, (bySpell != null && bySpell.IsAffectingArea(GetMap().GetDifficultyID())) || (target.IsTypeId(TypeId.Player) && target.HasStealthAura() && target.IsInCombat() && IsInCombatWith(target))))) + return false; + + // can't attack dead + if ((bySpell == null || !bySpell.IsAllowingDeadTarget()) && !target.IsAlive()) + return false; + + // can't attack untargetable + if ((bySpell == null || !bySpell.HasAttribute(SpellAttr6.CanTargetUntargetable)) + && target.HasFlag(UnitFields.Flags, UnitFlags.NotSelectable)) + return false; + + Player playerAttacker = ToPlayer(); + if (playerAttacker != null) + { + if (playerAttacker.HasFlag(PlayerFields.Flags, PlayerFlags.Commentator2)) + return false; + } + + // check flags + if (target.HasFlag(UnitFields.Flags, (UnitFlags.NonAttackable | UnitFlags.TaxiFlight | UnitFlags.NotAttackable1 | UnitFlags.Unk16)) + || (!HasFlag(UnitFields.Flags, UnitFlags.PvpAttackable) && target.HasFlag(UnitFields.Flags, UnitFlags.ImmuneToNpc)) + || (!target.HasFlag(UnitFields.Flags, UnitFlags.PvpAttackable) && HasFlag(UnitFields.Flags, UnitFlags.ImmuneToNpc))) + return false; + + if ((bySpell == null || !bySpell.HasAttribute(SpellAttr8.AttackIgnoreImmuneToPCFlag)) + && (HasFlag(UnitFields.Flags, UnitFlags.PvpAttackable) && target.HasFlag(UnitFields.Flags, UnitFlags.ImmuneToPc)) + // check if this is a world trigger cast - GOs are using world triggers to cast their spells, so we need to ignore their immunity flag here, this is a temp workaround, needs removal when go cast is implemented properly + && GetEntry() != SharedConst.WorldTrigger) + return false; + + // CvC case - can attack each other only when one of them is hostile + if (!HasFlag(UnitFields.Flags, UnitFlags.PvpAttackable) && !target.HasFlag(UnitFields.Flags, UnitFlags.PvpAttackable)) + return GetReactionTo(target) <= ReputationRank.Hostile || target.GetReactionTo(this) <= ReputationRank.Hostile; + + // PvP, PvC, CvP case + // can't attack friendly targets + if (GetReactionTo(target) > ReputationRank.Neutral + || target.GetReactionTo(this) > ReputationRank.Neutral) + return false; + + Player playerAffectingAttacker = HasFlag(UnitFields.Flags, UnitFlags.PvpAttackable) ? GetAffectingPlayer() : null; + Player playerAffectingTarget = target.HasFlag(UnitFields.Flags, UnitFlags.PvpAttackable) ? target.GetAffectingPlayer() : null; + + // Not all neutral creatures can be attacked (even some unfriendly faction does not react aggresive to you, like Sporaggar) + if ((playerAffectingAttacker && !playerAffectingTarget) || (!playerAffectingAttacker && playerAffectingTarget)) + { + if (!(target.IsTypeId(TypeId.Player) && IsTypeId(TypeId.Player)) && + !(target.IsTypeId(TypeId.Unit) && IsTypeId(TypeId.Unit))) + { + Player player = playerAffectingAttacker ? playerAffectingAttacker : playerAffectingTarget; + Unit creature = playerAffectingAttacker ? target : this; + + var factionTemplate = creature.GetFactionTemplateEntry(); + if (factionTemplate != null) + { + if (player.GetReputationMgr().GetForcedRankIfAny(factionTemplate) == ReputationRank.None) + { + var factionEntry = CliDB.FactionStorage.LookupByKey(factionTemplate.Faction); + if (factionEntry != null) + { + var repState = player.GetReputationMgr().GetState(factionEntry); + if (repState != null) + if (!Convert.ToBoolean(repState.Flags & FactionFlags.AtWar)) + return false; + } + } + + } + } + } + + Creature creatureAttacker = ToCreature(); + if (creatureAttacker != null && Convert.ToBoolean(creatureAttacker.GetCreatureTemplate().TypeFlags & CreatureTypeFlags.TreatAsRaidUnit)) + return false; + + // check duel - before sanctuary checks + if (playerAffectingAttacker != null && playerAffectingTarget != null) + if (playerAffectingAttacker.duel != null && playerAffectingAttacker.duel.opponent == playerAffectingTarget && playerAffectingAttacker.duel.startTime != 0) + return true; + + // PvP case - can't attack when attacker or target are in sanctuary + // however, 13850 client doesn't allow to attack when one of the unit's has sanctuary flag and is pvp + if (target.HasFlag(UnitFields.Flags, UnitFlags.PvpAttackable) && HasFlag(UnitFields.Flags, UnitFlags.PvpAttackable) + && (Convert.ToBoolean(target.GetByteValue(UnitFields.Bytes2, UnitBytes2Offsets.PvpFlag) & (byte)UnitBytes2Flags.Sanctuary) + || Convert.ToBoolean(GetByteValue(UnitFields.Bytes2, UnitBytes2Offsets.PvpFlag) & (byte)UnitBytes2Flags.Sanctuary))) + return false; + + // additional checks - only PvP case + if (playerAffectingAttacker != null && playerAffectingTarget != null) + { + if (Convert.ToBoolean(target.GetByteValue(UnitFields.Bytes2, UnitBytes2Offsets.PvpFlag) & (byte)UnitBytes2Flags.PvP)) + return true; + + if (Convert.ToBoolean(GetByteValue(UnitFields.Bytes2, UnitBytes2Offsets.PvpFlag) & (byte)UnitBytes2Flags.FFAPvp) + && Convert.ToBoolean(target.GetByteValue(UnitFields.Bytes2, UnitBytes2Offsets.PvpFlag) & (byte)UnitBytes2Flags.FFAPvp)) + return true; + + return (Convert.ToBoolean(GetByteValue(UnitFields.Bytes2, UnitBytes2Offsets.PvpFlag) & (byte)UnitBytes2Flags.Unk1) + || Convert.ToBoolean(target.GetByteValue(UnitFields.Bytes2, UnitBytes2Offsets.PvpFlag) & (byte)UnitBytes2Flags.Unk1)); + } + return true; + } + + bool IsValidAssistTarget(Unit target) + { + return _IsValidAssistTarget(target, null); + } + // function based on function Unit.CanAssist from 13850 client + public bool _IsValidAssistTarget(Unit target, SpellInfo bySpell) + { + Contract.Assert(target != null); + + // can assist to self + if (this == target) + return true; + + // can't assist unattackable units or GMs + if (target.HasUnitState(UnitState.Unattackable) + || (target.IsTypeId(TypeId.Player) && target.ToPlayer().IsGameMaster())) + return false; + + // can't assist own vehicle or passenger + if (m_vehicle != null) + if (IsOnVehicle(target) || m_vehicle.GetBase().IsOnVehicle(target)) + return false; + + // can't assist invisible + if ((bySpell == null || !bySpell.HasAttribute(SpellAttr6.CanTargetInvisible)) && !CanSeeOrDetect(target, bySpell != null && bySpell.IsAffectingArea(GetMap().GetDifficultyID()))) + return false; + + // can't assist dead + if ((bySpell == null || !bySpell.IsAllowingDeadTarget()) && !target.IsAlive()) + return false; + + // can't assist untargetable + if ((bySpell == null || !bySpell.HasAttribute(SpellAttr6.CanTargetUntargetable)) + && target.HasFlag(UnitFields.Flags, UnitFlags.NotSelectable)) + return false; + + if (bySpell == null || !bySpell.HasAttribute(SpellAttr6.AssistIgnoreImmuneFlag)) + { + if (HasFlag(UnitFields.Flags, UnitFlags.PvpAttackable)) + { + if (target.HasFlag(UnitFields.Flags, UnitFlags.ImmuneToPc)) + return false; + } + else + { + if (target.HasFlag(UnitFields.Flags, UnitFlags.ImmuneToNpc)) + return false; + } + } + + // can't assist non-friendly targets + if (GetReactionTo(target) <= ReputationRank.Neutral + && target.GetReactionTo(this) <= ReputationRank.Neutral + && (!IsTypeId(TypeId.Unit) || !Convert.ToBoolean(ToCreature().GetCreatureTemplate().TypeFlags & CreatureTypeFlags.TreatAsRaidUnit))) + return false; + + // Controlled player case, we can assist creatures (reaction already checked above, our faction == charmer faction) + if (IsTypeId(TypeId.Player) && IsCharmed() && GetCharmerGUID().IsCreature()) + return true; + + // PvP case + else if (target.HasFlag(UnitFields.Flags, UnitFlags.PvpAttackable)) + { + Player targetPlayerOwner = target.GetAffectingPlayer(); + if (HasFlag(UnitFields.Flags, UnitFlags.PvpAttackable)) + { + Player selfPlayerOwner = GetAffectingPlayer(); + if (selfPlayerOwner != null && targetPlayerOwner != null) + { + // can't assist player which is dueling someone + if (selfPlayerOwner != targetPlayerOwner && targetPlayerOwner.duel != null) + return false; + } + // can't assist player in ffa_pvp zone from outside + if (Convert.ToBoolean(target.GetByteValue(UnitFields.Bytes2, UnitBytes2Offsets.PvpFlag) & (byte)UnitBytes2Flags.FFAPvp) + && !Convert.ToBoolean(GetByteValue(UnitFields.Bytes2, UnitBytes2Offsets.PvpFlag) & (byte)UnitBytes2Flags.FFAPvp)) + return false; + // can't assist player out of sanctuary from sanctuary if has pvp enabled + if (Convert.ToBoolean(target.GetByteValue(UnitFields.Bytes2, UnitBytes2Offsets.PvpFlag) & (byte)UnitBytes2Flags.PvP)) + if (Convert.ToBoolean(GetByteValue(UnitFields.Bytes2, UnitBytes2Offsets.PvpFlag) & (byte)UnitBytes2Flags.Sanctuary) + && !Convert.ToBoolean(target.GetByteValue(UnitFields.Bytes2, UnitBytes2Offsets.PvpFlag) & (byte)UnitBytes2Flags.Sanctuary)) + return false; + } + } + // PvC case - player can assist creature only if has specific type flags + else if (HasFlag(UnitFields.Flags, UnitFlags.PvpAttackable) + && (bySpell == null || !bySpell.HasAttribute(SpellAttr6.AssistIgnoreImmuneFlag)) + && !Convert.ToBoolean(target.GetByteValue(UnitFields.Bytes2, UnitBytes2Offsets.PvpFlag) & (byte)UnitBytes2Flags.PvP)) + { + Creature creatureTarget = target.ToCreature(); + if (creatureTarget != null) + return Convert.ToBoolean(creatureTarget.GetCreatureTemplate().TypeFlags & CreatureTypeFlags.TreatAsRaidUnit) + || Convert.ToBoolean(creatureTarget.GetCreatureTemplate().TypeFlags & CreatureTypeFlags.CanAssist); + } + return true; + } + + // Part of Evade mechanics + public long GetLastDamagedTime() { return _lastDamagedTime; } + public void SetLastDamagedTime(long val) { _lastDamagedTime = val; } + } +} diff --git a/Game/Entities/Unit/Unit.Fields.cs b/Game/Entities/Unit/Unit.Fields.cs new file mode 100644 index 000000000..9d3c9885b --- /dev/null +++ b/Game/Entities/Unit/Unit.Fields.cs @@ -0,0 +1,646 @@ +/* + * Copyright (C) 2012-2017 CypherCore + * + * 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, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +using Framework.Collections; +using Framework.Constants; +using Framework.Dynamic; +using Game.AI; +using Game.Combat; +using Game.DataStorage; +using Game.Maps; +using Game.Movement; +using Game.Network.Packets; +using Game.Spells; +using System; +using System.Collections.Generic; + +namespace Game.Entities +{ + public partial class Unit + { + //AI + protected UnitAI i_AI; + protected UnitAI i_disabledAI; + public bool IsAIEnabled { get; set; } + public bool NeedChangeAI { get; set; } + + //Movement + protected float[] m_speed_rate = new float[(int)UnitMoveType.Max]; + RefManager m_FollowingRefManager; + public MoveSpline moveSpline { get; set; } + MotionMaster i_motionMaster; + public uint m_movementCounter; //< Incrementing counter used in movement packets + TimeTrackerSmall movesplineTimer; + public Player m_playerMovingMe; + + //Combat + protected List attackerList = new List(); + Dictionary m_reactiveTimer = new Dictionary(); + protected float[][] m_weaponDamage = new float[(int)WeaponAttackType.Max][]; + public float[] m_threatModifier = new float[(int)SpellSchools.Max]; + + uint[] m_baseAttackSpeed = new uint[(int)WeaponAttackType.Max]; + float[] m_modAttackSpeedPct = new float[(int)WeaponAttackType.Max]; + protected uint[] m_attackTimer = new uint[(int)WeaponAttackType.Max]; + + ThreatManager threatManager; + HostileRefManager m_HostileRefManager; + RedirectThreatInfo _redirectThreatInfo; + protected Unit m_attacking; + + public float m_modMeleeHitChance { get; set; } + public float m_modRangedHitChance { get; set; } + public float m_modSpellHitChance { get; set; } + long _lastDamagedTime; + bool m_canDualWield; + public int m_baseSpellCritChance { get; set; } + public uint m_regenTimer { get; set; } + uint m_CombatTimer; + public uint m_extraAttacks { get; set; } + + //Charm + public List m_Controlled = new List(); + List m_sharedVision = new List(); + CharmInfo m_charmInfo; + protected bool m_ControlledByPlayer; + public ObjectGuid LastCharmerGUID { get; set; } + + uint _oldFactionId; // faction before charm + bool _isWalkingBeforeCharm; // Are we walking before we were charmed? + + //Spells + protected Dictionary m_currentSpells = new Dictionary((int)CurrentSpellTypes.Max); + Dictionary CustomSpellValueMod = new Dictionary(); + MultiMap m_spellImmune = new MultiMap(); + uint m_interruptMask; + protected int m_procDeep; + bool m_AutoRepeatFirstCast; + SpellHistory _spellHistory; + + //Auras + public List m_petAuras = new List(); + List AuraEffectList = new List(); + MultiMap m_modAuras = new MultiMap(); + List m_removedAuras = new List(); + List m_interruptableAuras = new List(); // auras which have interrupt mask applied on unit + MultiMap m_auraStateAuras = new MultiMap(); // Used for improve performance of aura state checks on aura apply/remove + SortedSet m_visibleAuras = new SortedSet(new VisibleAuraSlotCompare()); + SortedSet m_visibleAurasToUpdate = new SortedSet(new VisibleAuraSlotCompare()); + MultiMap m_appliedAuras = new MultiMap(); + MultiMap m_ownedAuras = new MultiMap(); + List m_scAuras = new List(); + protected float[][] m_auraModifiersGroup = new float[(int)UnitMods.End][]; + uint m_removedAurasCount; + + //General + List m_Diminishing = new List(); + protected List m_gameObj = new List(); + List m_areaTrigger = new List(); + protected List m_dynObj = new List(); + protected float[] CreateStats = new float[(int)Stats.Max]; + public ObjectGuid[] m_SummonSlot = new ObjectGuid[7]; + public ObjectGuid[] m_ObjectSlot = new ObjectGuid[4]; + public EventSystem m_Events = new EventSystem(); + public UnitTypeMask m_unitTypeMask { get; set; } + UnitState m_state; + protected LiquidTypeRecord _lastLiquid; + protected DeathState m_deathState; + public Vehicle m_vehicle { get; set; } + public Vehicle m_vehicleKit { get; set; } + bool canModifyStats; + public uint m_lastSanctuaryTime { get; set; } + uint m_transform; + bool m_cleanupDone; // lock made to not add stuff after cleanup before delete + bool m_duringRemoveFromWorld; // lock made to not add stuff after begining removing from world + + ushort _aiAnimKitId; + ushort _movementAnimKitId; + ushort _meleeAnimKitId; + } + + public struct SpellImmune + { + public uint spellType; + public uint spellId; + } + + public class DiminishingReturn + { + public DiminishingReturn(DiminishingGroup group, uint t, DiminishingLevels count) + { + DRGroup = group; + stack = 0; + hitTime = t; + hitCount = count; + } + + public DiminishingGroup DRGroup; + public uint stack; + public uint hitTime; + public DiminishingLevels hitCount; + } + + public class ProcTriggeredData + { + public ProcTriggeredData(Aura _aura) + { + aura = _aura; + effMask = 0; + spellProcEvent = null; + } + public SpellProcEventEntry spellProcEvent; + public Aura aura; + public uint effMask; + } + + public class ProcEventInfo + { + public ProcEventInfo(Unit actor, Unit actionTarget, Unit procTarget, uint typeMask, uint spellTypeMask, + uint spellPhaseMask, uint hitMask, Spell spell, DamageInfo damageInfo, HealInfo healInfo) + { + _actor = actor; + _actionTarget = actionTarget; + _procTarget = procTarget; + _typeMask = typeMask; + _spellTypeMask = spellTypeMask; + _spellPhaseMask = spellPhaseMask; + _hitMask = hitMask; + _spell = spell; + _damageInfo = damageInfo; + _healInfo = healInfo; + } + + public Unit GetActor() { return _actor; } + public Unit GetActionTarget() { return _actionTarget; } + public Unit GetProcTarget() { return _procTarget; } + + public ProcFlags GetTypeMask() { return (ProcFlags)_typeMask; } + public ProcFlagsSpellType GetSpellTypeMask() { return (ProcFlagsSpellType)_spellTypeMask; } + public ProcFlagsSpellPhase GetSpellPhaseMask() { return (ProcFlagsSpellPhase)_spellPhaseMask; } + public ProcFlagsHit GetHitMask() { return (ProcFlagsHit)_hitMask; } + + public SpellInfo GetSpellInfo() + { + /// WORKAROUND: unfinished new proc system + if (_spell) + return _spell.GetSpellInfo(); + if (_damageInfo != null) + return _damageInfo.GetSpellInfo(); + if (_healInfo != null) + return _healInfo.GetSpellInfo(); + + return null; + } + public SpellSchoolMask GetSchoolMask() + { + /// WORKAROUND: unfinished new proc system + if (_spell) + return _spell.GetSpellInfo().GetSchoolMask(); + if (_damageInfo != null) + return _damageInfo.GetSchoolMask(); + if (_healInfo != null) + return _healInfo.GetSchoolMask(); + + return SpellSchoolMask.None; + } + + public DamageInfo GetDamageInfo() { return _damageInfo; } + public HealInfo GetHealInfo() { return _healInfo; } + + Unit _actor; + Unit _actionTarget; + Unit _procTarget; + uint _typeMask; + uint _spellTypeMask; + uint _spellPhaseMask; + uint _hitMask; + Spell _spell; + DamageInfo _damageInfo; + HealInfo _healInfo; + } + + public class DamageInfo + { + public DamageInfo(Unit attacker, Unit victim, uint damage, SpellInfo spellInfo, SpellSchoolMask schoolMask, DamageEffectType damageType, WeaponAttackType attackType) + { + m_attacker = attacker; + m_victim = victim; + m_damage = damage; + m_spellInfo = spellInfo; + m_schoolMask = schoolMask; + m_damageType = damageType; + m_attackType = attackType; + } + public DamageInfo(CalcDamageInfo dmgInfo) + { + m_attacker = dmgInfo.attacker; + m_victim = dmgInfo.target; + m_damage = dmgInfo.damage; + m_spellInfo = null; + m_schoolMask = (SpellSchoolMask)dmgInfo.damageSchoolMask; + m_damageType = DamageEffectType.Direct; + m_attackType = dmgInfo.attackType; + m_absorb = dmgInfo.absorb; + m_resist = dmgInfo.resist; + m_block = dmgInfo.blocked_amount; + + switch (dmgInfo.TargetState) + { + case VictimState.Immune: + m_hitMask |= ProcFlagsHit.Immune; + break; + case VictimState.Blocks: + m_hitMask |= ProcFlagsHit.FullBlock; + break; + } + + if (m_absorb != 0) + m_hitMask |= ProcFlagsHit.Absorb; + + if (dmgInfo.HitInfo.HasAnyFlag(HitInfo.FullResist)) + m_hitMask |= ProcFlagsHit.FullResist; + + if (m_block != 0) + m_hitMask |= ProcFlagsHit.Block; + + switch (dmgInfo.hitOutCome) + { + case MeleeHitOutcome.Miss: + m_hitMask |= ProcFlagsHit.Miss; + break; + case MeleeHitOutcome.Dodge: + m_hitMask |= ProcFlagsHit.Dodge; + break; + case MeleeHitOutcome.Parry: + m_hitMask |= ProcFlagsHit.Parry; + break; + case MeleeHitOutcome.Evade: + m_hitMask |= ProcFlagsHit.Evade; + break; + default: + break; + } + } + public DamageInfo(SpellNonMeleeDamage spellNonMeleeDamage, DamageEffectType damageType, WeaponAttackType attackType) + { + m_attacker = spellNonMeleeDamage.attacker; + m_victim = spellNonMeleeDamage.target; + m_damage = spellNonMeleeDamage.damage; + m_spellInfo = Global.SpellMgr.GetSpellInfo(spellNonMeleeDamage.SpellId); + m_schoolMask = spellNonMeleeDamage.schoolMask; + m_damageType = damageType; + m_attackType = attackType; + m_absorb = spellNonMeleeDamage.absorb; + m_resist = spellNonMeleeDamage.resist; + m_block = spellNonMeleeDamage.blocked; + + if (spellNonMeleeDamage.blocked != 0) + m_hitMask |= ProcFlagsHit.Block; + if (spellNonMeleeDamage.absorb != 0) + m_hitMask |= ProcFlagsHit.Absorb; + } + + public void ModifyDamage(int amount) + { + amount = Math.Min(amount, (int)GetDamage()); + m_damage += (uint)amount; + } + public void AbsorbDamage(uint amount) + { + amount = Math.Min(amount, GetDamage()); + m_absorb += amount; + m_damage -= amount; + m_hitMask |= ProcFlagsHit.Absorb; + } + public void ResistDamage(uint amount) + { + amount = Math.Min(amount, GetDamage()); + m_resist += amount; + m_damage -= amount; + if (m_damage == 0) + m_hitMask |= ProcFlagsHit.FullResist; + } + void BlockDamage(uint amount) + { + amount = Math.Min(amount, GetDamage()); + m_block += amount; + m_damage -= amount; + m_hitMask |= ProcFlagsHit.Block; + if (m_damage == 0) + m_hitMask |= ProcFlagsHit.FullBlock; + } + + public Unit GetAttacker() { return m_attacker; } + public Unit GetVictim() { return m_victim; } + public SpellInfo GetSpellInfo() { return m_spellInfo; } + public SpellSchoolMask GetSchoolMask() { return m_schoolMask; } + DamageEffectType GetDamageType() { return m_damageType; } + public WeaponAttackType GetAttackType() { return m_attackType; } + public uint GetDamage() { return m_damage; } + public uint GetAbsorb() { return m_absorb; } + public uint GetResist() { return m_resist; } + uint GetBlock() { return m_block; } + ProcFlagsHit GetHitMask() { return m_hitMask; } + + Unit m_attacker; + Unit m_victim; + uint m_damage; + SpellInfo m_spellInfo; + SpellSchoolMask m_schoolMask; + DamageEffectType m_damageType; + WeaponAttackType m_attackType; + uint m_absorb; + uint m_resist; + uint m_block; + ProcFlagsHit m_hitMask; + } + + public class HealInfo + { + public HealInfo(Unit healer, Unit target, uint heal, SpellInfo spellInfo, SpellSchoolMask schoolMask) + { + _healer = healer; + _target = target; + _heal = heal; + _spellInfo = spellInfo; + _schoolMask = schoolMask; + } + + void AbsorbHeal(uint amount) + { + amount = Math.Min(amount, GetHeal()); + _absorb += amount; + _heal -= amount; + amount = Math.Min(amount, GetEffectiveHeal()); + _effectiveHeal -= amount; + _hitMask |= ProcFlagsHit.Absorb; + } + public void SetEffectiveHeal(uint amount) { _effectiveHeal = amount; } + + public Unit GetHealer() { return _healer; } + public Unit GetTarget() { return _target; } + public uint GetHeal() { return _heal; } + public uint GetEffectiveHeal() { return _effectiveHeal; } + public uint GetAbsorb() { return _absorb; } + public SpellInfo GetSpellInfo() { return _spellInfo; } + public SpellSchoolMask GetSchoolMask() { return _schoolMask; } + ProcFlagsHit GetHitMask() { return _hitMask; } + + Unit _healer; + Unit _target; + uint _heal; + uint _effectiveHeal; + uint _absorb; + SpellInfo _spellInfo; + SpellSchoolMask _schoolMask; + ProcFlagsHit _hitMask; + } + + public class CalcDamageInfo + { + public Unit attacker { get; set; } // Attacker + public Unit target { get; set; } // Target for damage + public uint damageSchoolMask { get; set; } + public uint damage; + public uint absorb; + public uint resist; + public uint blocked_amount { get; set; } + public HitInfo HitInfo { get; set; } + public VictimState TargetState { get; set; } + // Helper + public WeaponAttackType attackType { get; set; } + public ProcFlags procAttacker { get; set; } + public ProcFlags procVictim { get; set; } + public ProcFlagsExLegacy procEx { get; set; } + public uint cleanDamage { get; set; } // Used only for rage calculation + public MeleeHitOutcome hitOutCome { get; set; } // TODO: remove this field (need use TargetState) + } + + public class SpellNonMeleeDamage + { + public SpellNonMeleeDamage(Unit _attacker, Unit _target, uint _SpellID, uint _SpellXSpellVisualID, SpellSchoolMask _schoolMask, ObjectGuid _castId = default(ObjectGuid)) + { + target = _target; + attacker = _attacker; + SpellId = _SpellID; + SpellXSpellVisualID = _SpellXSpellVisualID; + schoolMask = _schoolMask; + castId = _castId; + preHitHealth = (uint)_target.GetHealth(); + } + + public Unit target; + public Unit attacker; + public ObjectGuid castId; + public uint SpellId; + public uint SpellXSpellVisualID; + public uint damage; + public SpellSchoolMask schoolMask; + public uint absorb; + public uint resist; + public bool periodicLog; + public uint blocked; + public SpellHitType HitInfo; + // Used for help + public uint cleanDamage; + public uint preHitHealth; + } + + public class CleanDamage + { + public CleanDamage(uint mitigated, uint absorbed, WeaponAttackType _attackType, MeleeHitOutcome _hitOutCome) + { + absorbed_damage = absorbed; + mitigated_damage = mitigated; + attackType = _attackType; + hitOutCome = _hitOutCome; + } + + public uint absorbed_damage { get; set; } + public uint mitigated_damage { get; set; } + + public WeaponAttackType attackType { get; set; } + public MeleeHitOutcome hitOutCome { get; set; } + } + + public class DispelInfo + { + public DispelInfo(Unit dispeller, uint dispellerSpellId, byte chargesRemoved) + { + _dispellerUnit = dispeller; + _dispellerSpell = dispellerSpellId; + _chargesRemoved = chargesRemoved; + } + + public Unit GetDispeller() { return _dispellerUnit; } + uint GetDispellerSpellId() { return _dispellerSpell; } + public byte GetRemovedCharges() { return _chargesRemoved; } + void SetRemovedCharges(byte amount) + { + _chargesRemoved = amount; + } + + Unit _dispellerUnit; + uint _dispellerSpell; + byte _chargesRemoved; + } + + public struct RedirectThreatInfo + { + ObjectGuid _targetGUID; + uint _threatPct; + + public ObjectGuid GetTargetGUID() { return _targetGUID; } + public uint GetThreatPct() { return _threatPct; } + + public void Set(ObjectGuid guid, uint pct) + { + _targetGUID = guid; + _threatPct = pct; + } + + public void ModifyThreatPct(int amount) + { + amount += (int)_threatPct; + _threatPct = (uint)(Math.Max(0, amount)); + } + } + + public class SpellPeriodicAuraLogInfo + { + public SpellPeriodicAuraLogInfo(AuraEffect _auraEff, uint _damage, int _overDamage, uint _absorb, uint _resist, float _multiplier, bool _critical) + { + auraEff = _auraEff; + damage = _damage; + overDamage = _overDamage; + absorb = _absorb; + resist = _resist; + multiplier = _multiplier; + critical = _critical; + } + + public AuraEffect auraEff; + public uint damage; + public int overDamage; // overkill/overheal + public uint absorb; + public uint resist; + public float multiplier; + public bool critical; + } + + class VisibleAuraSlotCompare : IComparer + { + public int Compare(AuraApplication x, AuraApplication y) + { + return x.GetSlot().CompareTo(y.GetSlot()); + } + } + + public class DeclinedName + { + public StringArray name = new StringArray(SharedConst.MaxDeclinedNameCases); + } + + class CombatLogSender : Notifier + { + public CombatLogSender(WorldObject src, CombatLogServerPacket msg, float dist) + { + i_source = src; + i_message = msg; + i_distSq = dist; + + i_message.Write(); + } + + bool IsInRangeHelper(WorldObject obj) + { + if (!obj.IsInPhase(i_source)) + return false; + + return obj.GetExactDist2dSq(i_source) <= i_distSq; + } + + public override void Visit(ICollection objs) + { + foreach (var target in objs) + { + if (!IsInRangeHelper(target)) + continue; + + // Send packet to all who are sharing the player's vision + if (target.HasSharedVision()) + { + foreach (var visionTarget in target.GetSharedVisionList()) + if (visionTarget.seerView == target) + SendPacket(visionTarget); + } + + if (target.seerView == target || target.GetVehicle()) + SendPacket(target); + } + } + + public override void Visit(ICollection objs) + { + foreach (var target in objs) + { + if (!IsInRangeHelper(target)) + continue; + + // Send packet to all who are sharing the creature's vision + if (target.HasSharedVision()) + { + foreach (var visionTarget in target.GetSharedVisionList()) + if (visionTarget.seerView == target) + SendPacket(visionTarget); + } + } + } + public override void Visit(ICollection objs) + { + foreach (var target in objs) + { + if (!IsInRangeHelper(target)) + continue; + + Unit caster = target.GetCaster(); + if (caster) + { + // Send packet back to the caster if the caster has vision of dynamic object + Player player = caster.ToPlayer(); + if (player && player.seerView == target) + SendPacket(player); + } + } + } + + void SendPacket(Player player) + { + if (!player.HaveAtClient(i_source)) + return; + + if (!player.IsAdvancedCombatLoggingEnabled()) + i_message.DisableAdvancedCombatLogging(); + + player.SendPacket(i_message, false); + } + + WorldObject i_source; + CombatLogServerPacket i_message; + float i_distSq; + } +} diff --git a/Game/Entities/Unit/Unit.Movement.cs b/Game/Entities/Unit/Unit.Movement.cs new file mode 100644 index 000000000..440031263 --- /dev/null +++ b/Game/Entities/Unit/Unit.Movement.cs @@ -0,0 +1,1531 @@ +/* + * Copyright (C) 2012-2017 CypherCore + * + * 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, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +using Framework.Constants; +using Framework.GameMath; +using Game.BattleGrounds; +using Game.DataStorage; +using Game.Maps; +using Game.Movement; +using Game.Network.Packets; +using System; +using System.Collections.Generic; + +namespace Game.Entities +{ + public partial class Unit + { + public bool IsLevitating() + { + return m_movementInfo.HasMovementFlag(MovementFlag.DisableGravity); + } + public bool IsWalking() + { + return m_movementInfo.HasMovementFlag(MovementFlag.Walking); + } + bool IsHovering() { return m_movementInfo.HasMovementFlag(MovementFlag.Hover); } + public bool isStopped() + { + return !(HasUnitState(UnitState.Moving)); + } + public bool isMoving() { return m_movementInfo.HasMovementFlag(MovementFlag.MaskMoving); } + public bool isTurning() { return m_movementInfo.HasMovementFlag(MovementFlag.MaskTurning); } + public virtual bool CanFly() { return false; } + public bool IsFlying() { return m_movementInfo.HasMovementFlag(MovementFlag.Flying | MovementFlag.DisableGravity); } + public bool IsFalling() + { + return m_movementInfo.HasMovementFlag(MovementFlag.Falling | MovementFlag.FallingFar) || moveSpline.isFalling(); + } + public virtual bool CanSwim() + { + // Mirror client behavior, if this method returns false then client will not use swimming animation and for players will apply gravity as if there was no water + if (HasFlag(UnitFields.Flags, UnitFlags.CannotSwim)) + return false; + if (HasFlag(UnitFields.Flags, UnitFlags.PvpAttackable)) // is player + return true; + if (HasFlag(UnitFields.Flags2, 0x1000000)) + return false; + return HasFlag(UnitFields.Flags, UnitFlags.PetInCombat | UnitFlags.Rename | UnitFlags.Unk15); + } + public virtual bool IsInWater() + { + return GetMap().IsInWater(GetPositionX(), GetPositionY(), GetPositionZ()); + } + public virtual bool IsUnderWater() + { + return GetMap().IsUnderWater(GetPositionX(), GetPositionY(), GetPositionZ()); + } + + void propagateSpeedChange() { GetMotionMaster().propagateSpeedChange(); } + + public float GetSpeed(UnitMoveType mtype) + { + return m_speed_rate[(int)mtype] * (IsControlledByPlayer() ? SharedConst.playerBaseMoveSpeed[(int)mtype] : SharedConst.baseMoveSpeed[(int)mtype]); + } + + public void SetSpeed(UnitMoveType mtype, float newValue) + { + SetSpeedRate(mtype, newValue / (IsControlledByPlayer() ? SharedConst.playerBaseMoveSpeed[(int)mtype] : SharedConst.baseMoveSpeed[(int)mtype])); + } + + public void SetSpeedRate(UnitMoveType mtype, float rate) + { + rate = Math.Max(rate, 0.01f); + + if (m_speed_rate[(int)mtype] == rate) + return; + + m_speed_rate[(int)mtype] = rate; + + propagateSpeedChange(); + + // Spline packets are for creatures and move_update are for players + ServerOpcodes[,] moveTypeToOpcode = new ServerOpcodes[(int)UnitMoveType.Max, 3] + { + { ServerOpcodes.MoveSplineSetWalkSpeed, ServerOpcodes.MoveSetWalkSpeed, ServerOpcodes.MoveUpdateWalkSpeed }, + { ServerOpcodes.MoveSplineSetRunSpeed, ServerOpcodes.MoveSetRunSpeed, ServerOpcodes.MoveUpdateRunSpeed }, + { ServerOpcodes.MoveSplineSetRunBackSpeed, ServerOpcodes.MoveSetRunBackSpeed, ServerOpcodes.MoveUpdateRunBackSpeed }, + { ServerOpcodes.MoveSplineSetSwimSpeed, ServerOpcodes.MoveSetSwimSpeed, ServerOpcodes.MoveUpdateSwimSpeed }, + { ServerOpcodes.MoveSplineSetSwimBackSpeed, ServerOpcodes.MoveSetSwimBackSpeed, ServerOpcodes.MoveUpdateSwimBackSpeed }, + { ServerOpcodes.MoveSplineSetTurnRate, ServerOpcodes.MoveSetTurnRate, ServerOpcodes.MoveUpdateTurnRate }, + { ServerOpcodes.MoveSplineSetFlightSpeed, ServerOpcodes.MoveSetFlightSpeed, ServerOpcodes.MoveUpdateFlightSpeed }, + { ServerOpcodes.MoveSplineSetFlightBackSpeed, ServerOpcodes.MoveSetFlightBackSpeed, ServerOpcodes.MoveUpdateFlightBackSpeed }, + { ServerOpcodes.MoveSplineSetPitchRate, ServerOpcodes.MoveSetPitchRate, ServerOpcodes.MoveUpdatePitchRate }, + }; + + if (IsTypeId(TypeId.Player)) + { + // register forced speed changes for WorldSession.HandleForceSpeedChangeAck + // and do it only for real sent packets and use run for run/mounted as client expected + ++ToPlayer().m_forced_speed_changes[(int)mtype]; + + if (!IsInCombat()) + { + Pet pet = ToPlayer().GetPet(); + if (pet) + pet.SetSpeedRate(mtype, m_speed_rate[(int)mtype]); + } + } + + Player playerMover = GetPlayerBeingMoved(); // unit controlled by a player. + if (playerMover) + { + // Send notification to self + MoveSetSpeed selfpacket = new MoveSetSpeed(moveTypeToOpcode[(int)mtype, 1]); + selfpacket.MoverGUID = GetGUID(); + selfpacket.SequenceIndex = m_movementCounter++; + selfpacket.Speed = GetSpeed(mtype); + playerMover.SendPacket(selfpacket); + + // Send notification to other players + MoveUpdateSpeed packet = new MoveUpdateSpeed(moveTypeToOpcode[(int)mtype, 2]); + packet.movementInfo = m_movementInfo; + packet.Speed = GetSpeed(mtype); + playerMover.SendMessageToSet(packet, false); + } + else + { + MoveSplineSetSpeed packet = new MoveSplineSetSpeed(moveTypeToOpcode[(int)mtype, 0]); + packet.MoverGUID = GetGUID(); + packet.Speed = GetSpeed(mtype); + SendMessageToSet(packet, true); + } + } + + public float GetSpeedRate(UnitMoveType mtype) { return m_speed_rate[(int)mtype]; } + + public void StopMoving() + { + ClearUnitState(UnitState.Moving); + + // not need send any packets if not in world or not moving + if (!IsInWorld || moveSpline.Finalized()) + return; + + MoveSplineInit init = new MoveSplineInit(this); + init.Stop(); + } + + public void SetInFront(WorldObject target) + { + if (!HasUnitState(UnitState.CannotTurn)) + Orientation = GetAngle(target.GetPosition()); + } + + public void SetFacingTo(float ori, bool force = false) + { + if (!force && !IsStopped()) + return; + + MoveSplineInit init = new MoveSplineInit(this); + init.MoveTo(GetPositionX(), GetPositionY(), GetPositionZMinusOffset(), false); + init.SetFacing(ori); + init.Launch(); + } + + public void SetFacingToObject(WorldObject obj, bool force = false) + { + // do not face when already moving + if (!force && !IsStopped()) + return; + + // @todo figure out under what conditions creature will move towards object instead of facing it where it currently is. + SetFacingTo(GetAngle(obj)); + } + + public void MonsterMoveWithSpeed(float x, float y, float z, float speed, bool generatePath = false, bool forceDestination = false) + { + MoveSplineInit init = new MoveSplineInit(this); + init.MoveTo(x, y, z, generatePath, forceDestination); + init.SetVelocity(speed); + init.Launch(); + } + + public void KnockbackFrom(float x, float y, float speedXY, float speedZ, SpellEffectExtraData spellEffectExtraData = null) + { + Player player = ToPlayer(); + if (!player) + { + Unit charmer = GetCharmer(); + if (charmer) + { + player = charmer.ToPlayer(); + if (player && player.m_unitMovedByMe != this) + player = null; + } + } + + if (!player) + GetMotionMaster().MoveKnockbackFrom(x, y, speedXY, speedZ, spellEffectExtraData); + else + { + float vcos, vsin; + GetSinCos(x, y, out vsin, out vcos); + SendMoveKnockBack(player, speedXY, -speedZ, vcos, vsin); + } + } + + void SendMoveKnockBack(Player player, float speedXY, float speedZ, float vcos, float vsin) + { + MoveKnockBack moveKnockBack = new MoveKnockBack(); + moveKnockBack.MoverGUID = GetGUID(); + moveKnockBack.SequenceIndex = m_movementCounter++; + moveKnockBack.Speeds.HorzSpeed = speedXY; + moveKnockBack.Speeds.VertSpeed = speedZ; + moveKnockBack.Direction = new Vector2(vcos, vsin); + player.SendPacket(moveKnockBack); + } + + bool SetCollision(bool disable) + { + if (disable == HasUnitMovementFlag(MovementFlag.DisableCollision)) + return false; + + if (disable) + AddUnitMovementFlag(MovementFlag.DisableCollision); + else + RemoveUnitMovementFlag(MovementFlag.DisableCollision); + + Player playerMover = GetPlayerBeingMoved(); + if (playerMover) + { + MoveSetFlag packet = new MoveSetFlag(disable ? ServerOpcodes.MoveSplineEnableCollision : ServerOpcodes.MoveEnableCollision); + packet.MoverGUID = GetGUID(); + packet.SequenceIndex = m_movementCounter++; + playerMover.SendPacket(packet); + + MoveUpdate moveUpdate = new MoveUpdate(); + moveUpdate.movementInfo = m_movementInfo; + SendMessageToSet(moveUpdate, playerMover); + } + else + { + MoveSplineSetFlag packet = new MoveSplineSetFlag(disable ? ServerOpcodes.MoveSplineDisableCollision : ServerOpcodes.MoveDisableCollision); + packet.MoverGUID = GetGUID(); + SendMessageToSet(packet, true); + } + + return true; + } + + bool SetCanTransitionBetweenSwimAndFly(bool enable) + { + if (!IsTypeId(TypeId.Player)) + return false; + + if (enable == HasUnitMovementFlag2(MovementFlag2.CanSwimToFlyTrans)) + return false; + + if (enable) + AddUnitMovementFlag2(MovementFlag2.CanSwimToFlyTrans); + else + RemoveUnitMovementFlag2(MovementFlag2.CanSwimToFlyTrans); + + Player playerMover = GetPlayerBeingMoved(); + if (playerMover) + { + MoveSetFlag packet = new MoveSetFlag(enable ? ServerOpcodes.MoveEnableTransitionBetweenSwimAndFly : ServerOpcodes.MoveDisableTransitionBetweenSwimAndFly); + packet.MoverGUID = GetGUID(); + packet.SequenceIndex = m_movementCounter++; + playerMover.SendPacket(packet); + + MoveUpdate moveUpdate = new MoveUpdate(); + moveUpdate.movementInfo = m_movementInfo; + SendMessageToSet(moveUpdate, playerMover); + } + + return true; + } + + public bool SetCanTurnWhileFalling(bool enable) + { + if (enable == HasUnitMovementFlag2(MovementFlag2.CanTurnWhileFalling)) + return false; + + if (enable) + AddUnitMovementFlag2(MovementFlag2.CanTurnWhileFalling); + else + RemoveUnitMovementFlag2(MovementFlag2.CanTurnWhileFalling); + + Player playerMover = GetPlayerBeingMoved(); + if (playerMover) + { + MoveSetFlag packet = new MoveSetFlag(enable ? ServerOpcodes.MoveSetCanTurnWhileFalling : ServerOpcodes.MoveUnsetCanTurnWhileFalling); + packet.MoverGUID = GetGUID(); + packet.SequenceIndex = m_movementCounter++; + playerMover.SendPacket(packet); + + MoveUpdate moveUpdate = new MoveUpdate(); + moveUpdate.movementInfo = m_movementInfo; + SendMessageToSet(moveUpdate, playerMover); + } + + return true; + } + + public bool SetCanDoubleJump(bool enable) + { + if (enable == HasUnitMovementFlag2(MovementFlag2.CanDoubleJump)) + return false; + + if (enable) + AddUnitMovementFlag2(MovementFlag2.CanDoubleJump); + else + RemoveUnitMovementFlag2(MovementFlag2.CanDoubleJump); + + Player playerMover = GetPlayerBeingMoved(); + if (playerMover) + { + MoveSetFlag packet = new MoveSetFlag(enable ? ServerOpcodes.MoveEnableDoubleJump : ServerOpcodes.MoveDisableDoubleJump); + packet.MoverGUID = GetGUID(); + packet.SequenceIndex = m_movementCounter++; + playerMover.SendPacket(packet); + + MoveUpdate moveUpdate = new MoveUpdate(); + moveUpdate.movementInfo = m_movementInfo; + SendMessageToSet(moveUpdate, playerMover); + } + + return true; + } + + public void JumpTo(float speedXY, float speedZ, bool forward) + { + float angle = forward ? 0 : MathFunctions.PI; + if (IsTypeId(TypeId.Unit)) + GetMotionMaster().MoveJumpTo(angle, speedXY, speedZ); + else + { + float vcos = (float)Math.Cos(angle + GetOrientation()); + float vsin = (float)Math.Sin(angle + GetOrientation()); + SendMoveKnockBack(ToPlayer(), speedXY, -speedZ, vcos, vsin); + } + } + + public void JumpTo(WorldObject obj, float speedZ, bool withOrientation = false) + { + float x, y, z; + obj.GetContactPoint(this, out x, out y, out z); + float speedXY = GetExactDist2d(x, y) * 10.0f / speedZ; + GetMotionMaster().MoveJump(x, y, z, GetAngle(obj), speedXY, speedZ, EventId.Jump, withOrientation); + } + + public void UpdateSpeed(UnitMoveType mtype) + { + int main_speed_mod = 0; + float stack_bonus = 1.0f; + float non_stack_bonus = 1.0f; + + switch (mtype) + { + // Only apply debuffs + case UnitMoveType.FlightBack: + case UnitMoveType.RunBack: + case UnitMoveType.SwimBack: + break; + case UnitMoveType.Walk: + return; + case UnitMoveType.Run: + { + if (IsMounted()) // Use on mount auras + { + main_speed_mod = GetMaxPositiveAuraModifier(AuraType.ModIncreaseMountedSpeed); + stack_bonus = GetTotalAuraMultiplier(AuraType.ModMountedSpeedAlways); + non_stack_bonus += GetMaxPositiveAuraModifier(AuraType.ModMountedSpeedNotStack) / 100.0f; + } + else + { + main_speed_mod = GetMaxPositiveAuraModifier(AuraType.ModIncreaseSpeed); + stack_bonus = GetTotalAuraMultiplier(AuraType.ModSpeedAlways); + non_stack_bonus += GetMaxPositiveAuraModifier(AuraType.ModSpeedNotStack) / 100.0f; + } + break; + } + case UnitMoveType.Swim: + { + main_speed_mod = GetMaxPositiveAuraModifier(AuraType.ModIncreaseSwimSpeed); + break; + } + case UnitMoveType.Flight: + { + if (IsTypeId(TypeId.Unit) && IsControlledByPlayer()) // not sure if good for pet + { + main_speed_mod = GetMaxPositiveAuraModifier(AuraType.ModIncreaseVehicleFlightSpeed); + stack_bonus = GetTotalAuraMultiplier(AuraType.ModVehicleSpeedAlways); + + // for some spells this mod is applied on vehicle owner + int owner_speed_mod = 0; + + Unit owner = GetCharmer(); + if (owner != null) + owner_speed_mod = owner.GetMaxPositiveAuraModifier(AuraType.ModIncreaseVehicleFlightSpeed); + + main_speed_mod = Math.Max(main_speed_mod, owner_speed_mod); + } + else if (IsMounted()) + { + main_speed_mod = GetMaxPositiveAuraModifier(AuraType.ModIncreaseMountedFlightSpeed); + stack_bonus = GetTotalAuraMultiplier(AuraType.ModMountedFlightSpeedAlways); + } + else // Use not mount (shapeshift for example) auras (should stack) + main_speed_mod = GetTotalAuraModifier(AuraType.ModIncreaseFlightSpeed) + GetTotalAuraModifier(AuraType.ModIncreaseVehicleFlightSpeed); + + non_stack_bonus += GetMaxPositiveAuraModifier(AuraType.ModFlightSpeedNotStack) / 100.0f; + + // Update speed for vehicle if available + if (IsTypeId(TypeId.Player) && GetVehicle() != null) + GetVehicleBase().UpdateSpeed(UnitMoveType.Flight); + break; + } + default: + Log.outError(LogFilter.Unit, "Unit.UpdateSpeed: Unsupported move type ({0})", mtype); + return; + } + + // now we ready for speed calculation + float speed = Math.Max(non_stack_bonus, stack_bonus); + if (main_speed_mod != 0) + MathFunctions.AddPct(ref speed, main_speed_mod); + + switch (mtype) + { + case UnitMoveType.Run: + case UnitMoveType.Swim: + case UnitMoveType.Flight: + { + // Set creature speed rate + if (IsTypeId(TypeId.Unit)) + { + Unit pOwner = GetCharmerOrOwner(); + if ((IsPet() || IsGuardian()) && !IsInCombat() && pOwner != null) // Must check for owner or crash on "Tame Beast" + { + // For every yard over 5, increase speed by 0.01 + // to help prevent pet from lagging behind and despawning + float dist = GetDistance(pOwner); + float base_rate = 1.00f; // base speed is 100% of owner speed + + if (dist < 5) + dist = 5; + + float mult = base_rate + ((dist - 5) * 0.01f); + + speed *= pOwner.GetSpeedRate(mtype) * mult; // pets derive speed from owner when not in combat + } + else + speed *= ToCreature().GetCreatureTemplate().SpeedRun; // at this point, MOVE_WALK is never reached + } + + // Normalize speed by 191 aura SPELL_AURA_USE_NORMAL_MOVEMENT_SPEED if need + // @todo possible affect only on MOVE_RUN + int normalization = GetMaxPositiveAuraModifier(AuraType.UseNormalMovementSpeed); + if (normalization != 0) + { + Creature creature = ToCreature(); + if (creature) + { + uint immuneMask = creature.GetCreatureTemplate().MechanicImmuneMask; + if (Convert.ToBoolean(immuneMask & (1 << ((int)Mechanics.Snare - 1))) || Convert.ToBoolean(immuneMask & (1 << ((int)Mechanics.Daze - 1)))) + break; + } + + // Use speed from aura + float max_speed = normalization / (IsControlledByPlayer() ? SharedConst.playerBaseMoveSpeed[(int)mtype] : SharedConst.baseMoveSpeed[(int)mtype]); + if (speed > max_speed) + speed = max_speed; + } + + if (mtype == UnitMoveType.Run) + { + // force minimum speed rate @ aura 437 SPELL_AURA_MOD_MINIMUM_SPEED_RATE + int minSpeedMod1 = GetMaxPositiveAuraModifier(AuraType.ModMinimumSpeedRate); + if (minSpeedMod1 != 0) + { + float minSpeed = minSpeedMod1 / (IsControlledByPlayer() ? SharedConst.playerBaseMoveSpeed[(int)mtype] : SharedConst.baseMoveSpeed[(int)mtype]); + if (speed < minSpeed) + speed = minSpeed; + } + } + + break; + } + default: + break; + } + + // for creature case, we check explicit if mob searched for assistance + if (IsTypeId(TypeId.Unit)) + { + if (ToCreature().HasSearchedAssistance()) + speed *= 0.66f; // best guessed value, so this will be 33% reduction. Based off initial speed, mob can then "run", "walk fast" or "walk". + } + + // Apply strongest slow aura mod to speed + int slow = GetMaxNegativeAuraModifier(AuraType.ModDecreaseSpeed); + if (slow != 0) + MathFunctions.AddPct(ref speed, slow); + + float minSpeedMod = GetMaxPositiveAuraModifier(AuraType.ModMinimumSpeed); + if (minSpeedMod != 0) + { + float min_speed = minSpeedMod / 100.0f; + if (speed < min_speed) + speed = min_speed; + } + + SetSpeedRate(mtype, speed); + } + + public virtual bool UpdatePosition(Position obj, bool teleport = false) + { + return UpdatePosition(obj.posX, obj.posY, obj.posZ, obj.Orientation, teleport); + } + + public virtual bool UpdatePosition(float x, float y, float z, float orientation, bool teleport = false) + { + if (!GridDefines.IsValidMapCoord(x, y, z, orientation)) + { + Log.outError(LogFilter.Unit, "Unit.UpdatePosition({0}, {1}, {2}) .. bad coordinates!", x, y, z); + return false; + } + + bool turn = (GetOrientation() != orientation); + bool relocated = (teleport || GetPositionX() != x || GetPositionY() != y || GetPositionZ() != z); + + if (turn) + RemoveAurasWithInterruptFlags(SpellAuraInterruptFlags.Turning); + + if (relocated) + { + RemoveAurasWithInterruptFlags(SpellAuraInterruptFlags.Move); + + // move and update visible state if need + if (IsTypeId(TypeId.Player)) + GetMap().PlayerRelocation(ToPlayer(), x, y, z, orientation); + else + GetMap().CreatureRelocation(ToCreature(), x, y, z, orientation); + } + else if (turn) + UpdateOrientation(orientation); + + // code block for underwater state update + UpdateUnderwaterState(GetMap(), x, y, z); + + return (relocated || turn); + } + + void UpdateOrientation(float orientation) + { + Orientation = orientation; + if (IsVehicle()) + GetVehicleKit().RelocatePassengers(); + } + + //! Only server-side height update, does not broadcast to client + void UpdateHeight(float newZ) + { + Relocate(GetPositionX(), GetPositionY(), newZ); + if (IsVehicle()) + GetVehicleKit().RelocatePassengers(); + } + + public bool IsWithinBoundaryRadius(Unit obj) + { + if (!obj || !IsInMap(obj) || !IsInPhase(obj)) + return false; + + float objBoundaryRadius = Math.Max(obj.GetBoundaryRadius(), SharedConst.MinMeleeReach); + + return IsInDist(obj, objBoundaryRadius); + } + + public void GetRandomContactPoint(Unit obj, out float x, out float y, out float z, float distance2dMin, float distance2dMax) + { + float combat_reach = GetCombatReach(); + if (combat_reach < 0.1f) + combat_reach = SharedConst.DefaultCombatReach; + + int attacker_number = getAttackers().Count; + if (attacker_number > 0) + --attacker_number; + GetNearPoint(obj, out x, out y, out z, obj.GetCombatReach(), distance2dMin + (distance2dMax - distance2dMin) * (float)RandomHelper.NextDouble() + , GetAngle(obj.GetPosition()) + (attacker_number != 0 ? MathFunctions.PiOver2 - MathFunctions.PI * (float)RandomHelper.NextDouble() * (float)attacker_number / combat_reach * 0.3f : 0.0f)); + } + + public bool SetDisableGravity(bool disable) + { + if (disable == IsLevitating()) + return false; + + if (disable) + { + AddUnitMovementFlag(MovementFlag.DisableGravity); + RemoveUnitMovementFlag(MovementFlag.Swimming | MovementFlag.SplineElevation); + } + else + RemoveUnitMovementFlag(MovementFlag.DisableGravity); + + + Player playerMover = GetPlayerBeingMoved(); + if (playerMover) + { + MoveSetFlag packet = new MoveSetFlag(disable ? ServerOpcodes.MoveDisableGravity : ServerOpcodes.MoveEnableGravity); + packet.MoverGUID = GetGUID(); + packet.SequenceIndex = m_movementCounter++; + playerMover.SendPacket(packet); + + MoveUpdate moveUpdate = new MoveUpdate(); + moveUpdate.movementInfo = m_movementInfo; + SendMessageToSet(moveUpdate, playerMover); + } + else + { + MoveSplineSetFlag packet = new MoveSplineSetFlag(disable ? ServerOpcodes.MoveSplineDisableGravity : ServerOpcodes.MoveSplineEnableGravity); + packet.MoverGUID = GetGUID(); + SendMessageToSet(packet, true); + } + + return true; + } + + public MountCapabilityRecord GetMountCapability(uint mountType) + { + if (mountType == 0) + return null; + + var capabilities = Global.DB2Mgr.GetMountCapabilities(mountType); + if (capabilities == null) + return null; + + uint zoneId, areaId; + GetZoneAndAreaId(out zoneId, out areaId); + uint ridingSkill = 5000; + if (IsTypeId(TypeId.Player)) + ridingSkill = ToPlayer().GetSkillValue(SkillType.Riding); + + foreach (var mountTypeXCapability in capabilities) + { + MountCapabilityRecord mountCapability = CliDB.MountCapabilityStorage.LookupByKey(mountTypeXCapability.MountCapabilityID); + if (mountCapability == null) + continue; + + if (ridingSkill < mountCapability.RequiredRidingSkill) + continue; + + if (HasUnitMovementFlag2(MovementFlag2.FullSpeedPitching)) + { + if (!mountCapability.Flags.HasAnyFlag(MountCapabilityFlags.CanPitch)) + continue; + } + else if (HasUnitMovementFlag(MovementFlag.Swimming)) + { + if (!mountCapability.Flags.HasAnyFlag(MountCapabilityFlags.CanSwim)) + continue; + } + else if (!mountCapability.Flags.HasAnyFlag(MountCapabilityFlags.Unk1)) // unknown flags, checked in 4.2.2 14545 client + { + if (!mountCapability.Flags.HasAnyFlag(MountCapabilityFlags.Unk2)) + continue; + } + + if (mountCapability.RequiredMap != -1 && (GetMapId()) != mountCapability.RequiredMap) + continue; + + if (mountCapability.RequiredArea != 0 && (mountCapability.RequiredArea != zoneId && mountCapability.RequiredArea != areaId)) + continue; + + if (mountCapability.RequiredAura != 0 && !HasAura(mountCapability.RequiredAura)) + continue; + + if (mountCapability.RequiredSpell != 0 && (!IsTypeId(TypeId.Player) || !ToPlayer().HasSpell(mountCapability.RequiredSpell))) + continue; + + return mountCapability; + } + + return null; + } + + public virtual void UpdateUnderwaterState(Map m, float x, float y, float z) + { + if (IsFlying() || (!IsPet() && !IsVehicle())) + return; + + LiquidData liquid_status; + ZLiquidStatus res = m.getLiquidStatus(x, y, z, MapConst.MapAllLiquidTypes, out liquid_status); + if (res == 0) + { + if (_lastLiquid != null && _lastLiquid.SpellID != 0) + RemoveAurasDueToSpell(_lastLiquid.SpellID); + + RemoveAurasWithInterruptFlags(SpellAuraInterruptFlags.NotUnderwater); + _lastLiquid = null; + return; + } + uint liqEntry = liquid_status.entry; + if (liqEntry != 0) + { + LiquidTypeRecord liquid = CliDB.LiquidTypeStorage.LookupByKey(liqEntry); + if (_lastLiquid != null && _lastLiquid.SpellID != 0 && _lastLiquid.Id != liqEntry) + RemoveAurasDueToSpell(_lastLiquid.SpellID); + + if (liquid != null && liquid.SpellID != 0) + { + if (res.HasAnyFlag(ZLiquidStatus.UnderWater | ZLiquidStatus.InWater)) + { + if (!HasAura(liquid.SpellID)) + CastSpell(this, liquid.SpellID, true); + } + else + RemoveAurasDueToSpell(liquid.SpellID); + } + + RemoveAurasWithInterruptFlags(SpellAuraInterruptFlags.NotAbovewater); + _lastLiquid = liquid; + } + else if (_lastLiquid != null && _lastLiquid.SpellID != 0) + { + RemoveAurasDueToSpell(_lastLiquid.SpellID); + RemoveAurasWithInterruptFlags(SpellAuraInterruptFlags.NotUnderwater); + _lastLiquid = null; + } + } + + public bool SetWalk(bool enable) + { + if (enable == IsWalking()) + return false; + + if (enable) + AddUnitMovementFlag(MovementFlag.Walking); + else + RemoveUnitMovementFlag(MovementFlag.Walking); + + MoveSplineSetFlag packet = new MoveSplineSetFlag(enable ? ServerOpcodes.MoveSplineSetWalkMode : ServerOpcodes.MoveSplineSetRunMode); + packet.MoverGUID = GetGUID(); + SendMessageToSet(packet, true); + return true; + } + + public bool SetFall(bool enable) + { + if (enable == HasUnitMovementFlag(MovementFlag.Falling)) + return false; + + if (enable) + { + AddUnitMovementFlag(MovementFlag.Falling); + m_movementInfo.SetFallTime(0); + } + else + RemoveUnitMovementFlag(MovementFlag.Falling | MovementFlag.FallingFar); + + return true; + } + + public bool SetSwim(bool enable) + { + if (enable == HasUnitMovementFlag(MovementFlag.Swimming)) + return false; + + if (enable) + AddUnitMovementFlag(MovementFlag.Swimming); + else + RemoveUnitMovementFlag(MovementFlag.Swimming); + + MoveSplineSetFlag packet = new MoveSplineSetFlag(enable ? ServerOpcodes.MoveSplineStartSwim : ServerOpcodes.MoveSplineStopSwim); + packet.MoverGUID = GetGUID(); + SendMessageToSet(packet, true); + + return true; + } + + public bool SetCanFly(bool enable) + { + if (enable == HasUnitMovementFlag(MovementFlag.CanFly)) + return false; + + if (enable) + { + AddUnitMovementFlag(MovementFlag.CanFly); + RemoveUnitMovementFlag(MovementFlag.Swimming | MovementFlag.SplineElevation); + } + else + RemoveUnitMovementFlag(MovementFlag.CanFly | MovementFlag.MaskMovingFly); + + if (!enable && IsTypeId(TypeId.Player)) + ToPlayer().SetFallInformation(0, GetPositionZ()); + + Player playerMover = GetPlayerBeingMoved(); + if (playerMover) + { + MoveSetFlag packet = new MoveSetFlag(enable ? ServerOpcodes.MoveSetCanFly : ServerOpcodes.MoveUnsetCanFly); + packet.MoverGUID = GetGUID(); + packet.SequenceIndex = m_movementCounter++; + playerMover.SendPacket(packet); + + MoveUpdate moveUpdate = new MoveUpdate(); + moveUpdate.movementInfo = m_movementInfo; + SendMessageToSet(moveUpdate, playerMover); + } + else + { + MoveSplineSetFlag packet = new MoveSplineSetFlag(enable ? ServerOpcodes.MoveSplineSetFlying : ServerOpcodes.MoveSplineUnsetFlying); + packet.MoverGUID = GetGUID(); + SendMessageToSet(packet, true); + } + + return true; + } + + public bool SetWaterWalking(bool enable) + { + if (enable == HasUnitMovementFlag(MovementFlag.WaterWalk)) + return false; + + if (enable) + AddUnitMovementFlag(MovementFlag.WaterWalk); + else + RemoveUnitMovementFlag(MovementFlag.WaterWalk); + + + Player playerMover = GetPlayerBeingMoved(); + if (playerMover) + { + MoveSetFlag packet = new MoveSetFlag(enable ? ServerOpcodes.MoveSetWaterWalk : ServerOpcodes.MoveSetLandWalk); + packet.MoverGUID = GetGUID(); + packet.SequenceIndex = m_movementCounter++; + playerMover.SendPacket(packet); + + MoveUpdate moveUpdate = new MoveUpdate(); + moveUpdate.movementInfo = m_movementInfo; + SendMessageToSet(moveUpdate, playerMover); + } + else + { + MoveSplineSetFlag packet = new MoveSplineSetFlag(enable ? ServerOpcodes.MoveSplineSetWaterWalk : ServerOpcodes.MoveSplineSetLandWalk); + packet.MoverGUID = GetGUID(); + SendMessageToSet(packet, true); + } + return true; + } + + public bool SetFeatherFall(bool enable) + { + if (enable == HasUnitMovementFlag(MovementFlag.FallingSlow)) + return false; + + if (enable) + AddUnitMovementFlag(MovementFlag.FallingSlow); + else + RemoveUnitMovementFlag(MovementFlag.FallingSlow); + + + Player playerMover = GetPlayerBeingMoved(); + if (playerMover) + { + MoveSetFlag packet = new MoveSetFlag(enable ? ServerOpcodes.MoveSetFeatherFall : ServerOpcodes.MoveSetNormalFall); + packet.MoverGUID = GetGUID(); + packet.SequenceIndex = m_movementCounter++; + playerMover.SendPacket(packet); + + MoveUpdate moveUpdate = new MoveUpdate(); + moveUpdate.movementInfo = m_movementInfo; + SendMessageToSet(moveUpdate, playerMover); + } + else + { + MoveSplineSetFlag packet = new MoveSplineSetFlag(enable ? ServerOpcodes.MoveSplineSetFeatherFall : ServerOpcodes.MoveSplineSetNormalFall); + packet.MoverGUID = GetGUID(); + SendMessageToSet(packet, true); + } + return true; + } + + public bool SetHover(bool enable) + { + if (enable == HasUnitMovementFlag(MovementFlag.Hover)) + return false; + + float hoverHeight = GetFloatValue(UnitFields.HoverHeight); + + if (enable) + { + //! No need to check height on ascent + AddUnitMovementFlag(MovementFlag.Hover); + if (hoverHeight != 0) + UpdateHeight(GetPositionZ() + hoverHeight); + } + else + { + RemoveUnitMovementFlag(MovementFlag.Hover); + if (hoverHeight != 0) + { + float newZ = GetPositionZ() - hoverHeight; + UpdateAllowedPositionZ(GetPositionX(), GetPositionY(), ref newZ); + UpdateHeight(newZ); + } + } + + Player playerMover = GetPlayerBeingMoved(); + if (playerMover) + { + MoveSetFlag packet = new MoveSetFlag(enable ? ServerOpcodes.MoveSetHovering : ServerOpcodes.MoveUnsetHovering); + packet.MoverGUID = GetGUID(); + packet.SequenceIndex = m_movementCounter++; + playerMover.SendPacket(packet); + + MoveUpdate moveUpdate = new MoveUpdate(); + moveUpdate.movementInfo = m_movementInfo; + SendMessageToSet(moveUpdate, playerMover); + } + else + { + MoveSplineSetFlag packet = new MoveSplineSetFlag(enable ? ServerOpcodes.MoveSplineSetHover : ServerOpcodes.MoveSplineUnsetHover); + packet.MoverGUID = GetGUID(); + SendMessageToSet(packet, true); + } + return true; + } + + public bool IsWithinCombatRange(Unit obj, float dist2compare) + { + if (!obj || !IsInMap(obj) || !IsInPhase(obj)) + return false; + + float dx = GetPositionX() - obj.GetPositionX(); + float dy = GetPositionY() - obj.GetPositionY(); + float dz = GetPositionZ() - obj.GetPositionZ(); + float distsq = dx * dx + dy * dy + dz * dz; + + float sizefactor = GetCombatReach() + obj.GetCombatReach(); + float maxdist = dist2compare + sizefactor; + + return distsq < maxdist * maxdist; + } + + public bool isInFrontInMap(Unit target, float distance, float arc = MathFunctions.PI) + { + return IsWithinDistInMap(target, distance) && HasInArc(arc, target); + } + + public bool isInBackInMap(Unit target, float distance, float arc = MathFunctions.PI) + { + return IsWithinDistInMap(target, distance) && !HasInArc(MathFunctions.TwoPi - arc, target); + } + public bool isInAccessiblePlaceFor(Creature c) + { + if (IsInWater()) + return c.CanSwim(); + else + return c.CanWalk() || c.CanFly(); + } + + public void NearTeleportTo(float x, float y, float z, float orientation, bool casting = false) { NearTeleportTo(new Position(x, y, z, orientation), casting); } + public void NearTeleportTo(Position pos, bool casting = false) + { + DisableSpline(); + if (IsTypeId(TypeId.Player)) + { + WorldLocation target = new WorldLocation(GetMapId(), pos); + ToPlayer().TeleportTo(target, (TeleportToOptions.NotLeaveTransport | TeleportToOptions.NotLeaveCombat | TeleportToOptions.NotUnSummonPet | (casting ? TeleportToOptions.Spell : 0))); + } + else + { + SendTeleportPacket(pos); + UpdatePosition(pos, true); + UpdateObjectVisibility(); + } + } + + public void SetControlled(bool apply, UnitState state) + { + if (apply) + { + if (HasUnitState(state)) + return; + + AddUnitState(state); + switch (state) + { + case UnitState.Stunned: + SetStunned(true); + CastStop(); + break; + case UnitState.Root: + if (!HasUnitState(UnitState.Stunned)) + SetRooted(true); + break; + case UnitState.Confused: + if (!HasUnitState(UnitState.Stunned)) + { + ClearUnitState(UnitState.MeleeAttacking); + SendMeleeAttackStop(); + // SendAutoRepeatCancel ? + SetConfused(true); + CastStop(); + } + break; + case UnitState.Fleeing: + if (!HasUnitState(UnitState.Stunned | UnitState.Confused)) + { + ClearUnitState(UnitState.MeleeAttacking); + SendMeleeAttackStop(); + // SendAutoRepeatCancel ? + SetFeared(true); + CastStop(); + } + break; + default: + break; + } + } + else + { + switch (state) + { + case UnitState.Stunned: + if (HasAuraType(AuraType.ModStun)) + return; + + SetStunned(false); + break; + case UnitState.Root: + if (HasAuraType(AuraType.ModRoot) || HasAuraType(AuraType.ModRoot2) || GetVehicle() != null) + return; + + if (!HasUnitState(UnitState.Stunned)) + SetRooted(false); + break; + case UnitState.Confused: + if (HasAuraType(AuraType.ModConfuse)) + return; + + SetConfused(false); + break; + case UnitState.Fleeing: + if (HasAuraType(AuraType.ModFear)) + return; + + SetFeared(false); + break; + default: + return; + } + + ClearUnitState(state); + + // Unit States might have been already cleared but auras still present. I need to check with HasAuraType + if (HasAuraType(AuraType.ModStun)) + SetStunned(true); + else + { + if (HasAuraType(AuraType.ModRoot) || HasAuraType(AuraType.ModRoot2)) + SetRooted(true); + + if (HasAuraType(AuraType.ModConfuse)) + SetConfused(true); + else if (HasAuraType(AuraType.ModFear)) + SetFeared(true); + } + } + } + + void SetStunned(bool apply) + { + if (apply) + { + SetTarget(ObjectGuid.Empty); + SetFlag(UnitFields.Flags, UnitFlags.Stunned); + + StopMoving(); + + if (IsTypeId(TypeId.Player)) + SetStandState(UnitStandStateType.Stand); + + SetRooted(true); + + CastStop(); + } + else + { + if (IsAlive() && GetVictim() != null) + SetTarget(GetVictim().GetGUID()); + + // don't remove UNIT_FLAG_STUNNED for pet when owner is mounted (disabled pet's interface) + Unit owner = GetOwner(); + if (owner == null || (owner.IsTypeId(TypeId.Player) && !owner.ToPlayer().IsMounted())) + RemoveFlag(UnitFields.Flags, UnitFlags.Stunned); + + if (!HasUnitState(UnitState.Root)) // prevent moving if it also has root effect + SetRooted(false); + } + } + + public void SetRooted(bool apply, bool packetOnly = false) + { + if (!packetOnly) + { + if (apply) + { + // MOVEMENTFLAG_ROOT cannot be used in conjunction with MOVEMENTFLAG_MASK_MOVING (tested 3.3.5a) + // this will freeze clients. That's why we remove MOVEMENTFLAG_MASK_MOVING before + // setting MOVEMENTFLAG_ROOT + RemoveUnitMovementFlag(MovementFlag.MaskMoving); + AddUnitMovementFlag(MovementFlag.Root); + StopMoving(); + } + else + RemoveUnitMovementFlag(MovementFlag.Root); + } + + Player playerMover = GetPlayerBeingMoved();// unit controlled by a player. + if (playerMover) + { + MoveSetFlag packet = new MoveSetFlag(apply ? ServerOpcodes.MoveRoot : ServerOpcodes.MoveUnroot); + packet.MoverGUID = GetGUID(); + packet.SequenceIndex = m_movementCounter++; + playerMover.SendPacket(packet); + + MoveUpdate moveUpdate = new MoveUpdate(); + moveUpdate.movementInfo = m_movementInfo; + SendMessageToSet(moveUpdate, playerMover); + } + else + { + MoveSplineSetFlag packet = new MoveSplineSetFlag(apply ? ServerOpcodes.MoveSplineRoot : ServerOpcodes.MoveSplineUnroot); + packet.MoverGUID = GetGUID(); + SendMessageToSet(packet, true); + } + } + + void SetFeared(bool apply) + { + if (apply) + { + SetTarget(ObjectGuid.Empty); + + Unit caster = null; + var fearAuras = GetAuraEffectsByType(AuraType.ModFear); + if (!fearAuras.Empty()) + caster = Global.ObjAccessor.GetUnit(this, fearAuras[0].GetCasterGUID()); + if (caster == null) + caster = getAttackerForHelper(); + GetMotionMaster().MoveFleeing(caster, (uint)(fearAuras.Empty() ? WorldConfig.GetIntValue(WorldCfg.CreatureFamilyFleeDelay) : 0)); // caster == NULL processed in MoveFleeing + } + else + { + if (IsAlive()) + { + if (GetMotionMaster().GetCurrentMovementGeneratorType() == MovementGeneratorType.Fleeing) + GetMotionMaster().MovementExpired(); + if (GetVictim() != null) + SetTarget(GetVictim().GetGUID()); + } + } + + Player player = ToPlayer(); + if (player) + if (player.HasUnitState(UnitState.Possessed)) + player.SetClientControl(this, !apply); + } + + void SetConfused(bool apply) + { + if (apply) + { + SetTarget(ObjectGuid.Empty); + GetMotionMaster().MoveConfused(); + } + else + { + if (IsAlive()) + { + if (GetMotionMaster().GetCurrentMovementGeneratorType() == MovementGeneratorType.Confused) + GetMotionMaster().MovementExpired(); + if (GetVictim() != null) + SetTarget(GetVictim().GetGUID()); + } + } + + Player player = ToPlayer(); + if (player) + if (player.HasUnitState(UnitState.Possessed)) + player.SetClientControl(this, !apply); + } + + public bool CanFreeMove() + { + return !HasUnitState(UnitState.Confused | UnitState.Fleeing | UnitState.InFlight | + UnitState.Root | UnitState.Stunned | UnitState.Distracted) && GetOwnerGUID().IsEmpty(); + } + + public void Mount(uint mount, uint VehicleId = 0, uint creatureEntry = 0) + { + if (mount != 0) + SetUInt32Value(UnitFields.MountDisplayId, mount); + + SetFlag(UnitFields.Flags, UnitFlags.Mount); + + Player player = ToPlayer(); + if (player != null) + { + // mount as a vehicle + if (VehicleId != 0) + { + if (CreateVehicleKit(VehicleId, creatureEntry)) + { + player.SendOnCancelExpectedVehicleRideAura(); + + // mounts can also have accessories + GetVehicleKit().InstallAllAccessories(false); + } + } + + // unsummon pet + Pet pet = player.GetPet(); + if (pet != null) + { + Battleground bg = ToPlayer().GetBattleground(); + // don't unsummon pet in arena but SetFlag UNIT_FLAG_STUNNED to disable pet's interface + if (bg && bg.isArena()) + pet.SetFlag(UnitFields.Flags, UnitFlags.Stunned); + else + player.UnsummonPetTemporaryIfAny(); + } + + player.SendMovementSetCollisionHeight(player.GetCollisionHeight(true)); + } + + RemoveAurasWithInterruptFlags(SpellAuraInterruptFlags.Mount); + } + + public void Dismount() + { + if (!IsMounted()) + return; + + SetUInt32Value(UnitFields.MountDisplayId, 0); + RemoveFlag(UnitFields.Flags, UnitFlags.Mount); + + Player thisPlayer = ToPlayer(); + if (thisPlayer != null) + thisPlayer.SendMovementSetCollisionHeight(thisPlayer.GetCollisionHeight(false)); + + Dismount data = new Dismount(); + data.Guid = GetGUID(); + SendMessageToSet(data, true); + + // dismount as a vehicle + if (IsTypeId(TypeId.Player) && GetVehicleKit() != null) + { + // Remove vehicle from player + RemoveVehicleKit(); + } + + RemoveAurasWithInterruptFlags(SpellAuraInterruptFlags.NotMounted); + + // only resummon old pet if the player is already added to a map + // this prevents adding a pet to a not created map which would otherwise cause a crash + // (it could probably happen when logging in after a previous crash) + Player player = ToPlayer(); + if (player != null) + { + Pet pPet = player.GetPet(); + if (pPet != null) + { + if (pPet.HasFlag(UnitFields.Flags, UnitFlags.Stunned) && !pPet.HasUnitState(UnitState.Stunned)) + pPet.RemoveFlag(UnitFields.Flags, UnitFlags.Stunned); + } + else + player.ResummonPetTemporaryUnSummonedIfAny(); + } + } + + public bool CreateVehicleKit(uint id, uint creatureEntry, bool loading = false) + { + VehicleRecord vehInfo = CliDB.VehicleStorage.LookupByKey(id); + if (vehInfo == null) + return false; + + m_vehicleKit = new Vehicle(this, vehInfo, creatureEntry); + m_updateFlag |= UpdateFlag.Vehicle; + m_unitTypeMask |= UnitTypeMask.Vehicle; + + if (!loading) + SendSetVehicleRecId(id); + + return true; + } + + public void RemoveVehicleKit(bool onRemoveFromWorld = false) + { + if (m_vehicleKit == null) + return; + + if (!onRemoveFromWorld) + SendSetVehicleRecId(0); + + m_vehicleKit.Uninstall(); + + m_vehicleKit = null; + + m_updateFlag &= ~UpdateFlag.Vehicle; + m_unitTypeMask &= ~UnitTypeMask.Vehicle; + RemoveFlag64(UnitFields.NpcFlags, NPCFlags.SpellClick | NPCFlags.PlayerVehicle); + } + + void SendSetVehicleRecId(uint vehicleId) + { + Player player = ToPlayer(); + if (player) + { + MoveSetVehicleRecID moveSetVehicleRec = new MoveSetVehicleRecID(); + moveSetVehicleRec.MoverGUID = GetGUID(); + moveSetVehicleRec.SequenceIndex = m_movementCounter++; + moveSetVehicleRec.VehicleRecID = vehicleId; + player.SendPacket(moveSetVehicleRec); + } + + SetVehicleRecID setVehicleRec = new SetVehicleRecID(); + setVehicleRec.VehicleGUID = GetGUID(); + setVehicleRec.VehicleRecID = vehicleId; + SendMessageToSet(setVehicleRec, true); + } + + void SendSetPlayHoverAnim(bool enable) + { + SetPlayHoverAnim data = new SetPlayHoverAnim(); + data.UnitGUID = GetGUID(); + data.PlayHoverAnim = enable; + + SendMessageToSet(data, true); + } + + public float GetPositionZMinusOffset() + { + float offset = 0.0f; + if (HasUnitMovementFlag(MovementFlag.Hover)) + offset = GetFloatValue(UnitFields.HoverHeight); + + return GetPositionZ() - offset; + } + + Unit GetUnitBeingMoved() + { + Player player = ToPlayer(); + if (player) + return player.m_unitMovedByMe; + + return null; + } + + Player GetPlayerBeingMoved() + { + Unit mover = GetUnitBeingMoved(); + if (mover) + return mover.ToPlayer(); + + return null; + } + + Player GetPlayerMovingMe() { return m_playerMovingMe; } + + public void AddUnitMovementFlag(MovementFlag f) + { + m_movementInfo.AddMovementFlag(f); + } + public void RemoveUnitMovementFlag(MovementFlag f) + { + m_movementInfo.RemoveMovementFlag(f); + } + public bool HasUnitMovementFlag(MovementFlag f) + { + return m_movementInfo.HasMovementFlag(f); + } + public MovementFlag GetUnitMovementFlags() + { + return m_movementInfo.GetMovementFlags(); + } + public void SetUnitMovementFlags(MovementFlag f) + { + m_movementInfo.SetMovementFlags(f); + } + + public void AddUnitMovementFlag2(MovementFlag2 f) + { + m_movementInfo.AddMovementFlag2(f); + } + void RemoveUnitMovementFlag2(MovementFlag2 f) + { + m_movementInfo.RemoveMovementFlag2(f); + } + public bool HasUnitMovementFlag2(MovementFlag2 f) + { + return m_movementInfo.HasMovementFlag2(f); + } + public MovementFlag2 GetUnitMovementFlags2() + { + return m_movementInfo.GetMovementFlags2(); + } + public void SetUnitMovementFlags2(MovementFlag2 f) + { + m_movementInfo.SetMovementFlags2(f); + } + + //Spline + public bool IsSplineEnabled() + { + return moveSpline.Initialized() && !moveSpline.Finalized(); + } + void UpdateSplineMovement(uint diff) + { + int positionUpdateDelay = 400; + + if (moveSpline.Finalized()) + return; + + moveSpline.updateState((int)diff); + bool arrived = moveSpline.Finalized(); + + if (arrived) + DisableSpline(); + + movesplineTimer.Update((int)diff); + if (movesplineTimer.Passed() || arrived) + { + movesplineTimer.Reset(positionUpdateDelay); + Vector4 loc = moveSpline.ComputePosition(); + float x = loc.X; + float y = loc.Y; + float z = loc.Z; + float o = loc.W; + + if (moveSpline.onTransport) + { + m_movementInfo.transport.pos.Relocate(x, y, z, o); + + ITransport transport = GetDirectTransport(); + if (transport != null) + transport.CalculatePassengerPosition(ref x, ref y, ref z, ref o); + } + if (HasUnitState(UnitState.CannotTurn)) + o = GetOrientation(); + + UpdatePosition(x, y, z, o); + } + } + public void DisableSpline() + { + m_movementInfo.RemoveMovementFlag(MovementFlag.Forward); + moveSpline.Interrupt(); + } + + //Transport + public override ObjectGuid GetTransGUID() + { + if (GetVehicle() != null) + return GetVehicleBase().GetGUID(); + if (GetTransport() != null) + return GetTransport().GetGUID(); + + return ObjectGuid.Empty; + } + + //Teleport + public void SendTeleportPacket(Position pos) + { + // SMSG_MOVE_UPDATE_TELEPORT is sent to nearby players to signal the teleport + // SMSG_MOVE_TELEPORT is sent to self in order to trigger CMSG_MOVE_TELEPORT_ACK and update the position server side + + MoveUpdateTeleport moveUpdateTeleport = new MoveUpdateTeleport(); + moveUpdateTeleport.movementInfo = m_movementInfo; + Unit broadcastSource = this; + + Player playerMover = GetPlayerBeingMoved(); + if (playerMover) + { + MoveTeleport moveTeleport = new MoveTeleport(); + moveTeleport.MoverGUID = GetGUID(); + moveTeleport.Pos = pos; + + ITransport transportBase = GetDirectTransport(); + if (transportBase != null) + { + float o = 0f; + transportBase.CalculatePassengerOffset(ref moveTeleport.Pos.posX, ref moveTeleport.Pos.posY, ref moveTeleport.Pos.posZ, ref o); + } + moveTeleport.TransportGUID.Set(GetTransGUID()); + moveTeleport.Facing = pos.GetOrientation(); + moveTeleport.SequenceIndex = m_movementCounter++; + playerMover.SendPacket(moveTeleport); + + broadcastSource = playerMover; + } + else + { + // This is the only packet sent for creatures which contains MovementInfo structure + // we do not update m_movementInfo for creatures so it needs to be done manually here + moveUpdateTeleport.movementInfo.Guid = GetGUID(); + moveUpdateTeleport.movementInfo.Pos.Relocate(pos); + moveUpdateTeleport.movementInfo.Time = Time.GetMSTime(); + } + + // Broadcast the packet to everyone except self. + broadcastSource.SendMessageToSet(moveUpdateTeleport, false); + } + } +} diff --git a/Game/Entities/Unit/Unit.Pets.cs b/Game/Entities/Unit/Unit.Pets.cs new file mode 100644 index 000000000..fd2233fbb --- /dev/null +++ b/Game/Entities/Unit/Unit.Pets.cs @@ -0,0 +1,796 @@ +/* + * Copyright (C) 2012-2017 CypherCore + * + * 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, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +using Framework.Constants; +using Game.AI; +using Game.Network.Packets; +using Game.Spells; +using System.Collections.Generic; +using System.Diagnostics.Contracts; +using System.Linq; + +namespace Game.Entities +{ + public partial class Unit + { + public void AddPetAura(PetAura petSpell) + { + if (!IsTypeId(TypeId.Player)) + return; + + m_petAuras.Add(petSpell); + Pet pet = ToPlayer().GetPet(); + if (pet) + pet.CastPetAura(petSpell); + } + + public void RemovePetAura(PetAura petSpell) + { + if (!IsTypeId(TypeId.Player)) + return; + + m_petAuras.Remove(petSpell); + Pet pet = ToPlayer().GetPet(); + if (pet) + pet.RemoveAurasDueToSpell(petSpell.GetAura(pet.GetEntry())); + } + + public CharmInfo GetCharmInfo() { return m_charmInfo; } + + public CharmInfo InitCharmInfo() + { + if (m_charmInfo == null) + m_charmInfo = new CharmInfo(this); + + return m_charmInfo; + } + + void DeleteCharmInfo() + { + if (m_charmInfo == null) + return; + + m_charmInfo.RestoreState(); + m_charmInfo = null; + } + + public void UpdateCharmAI() + { + switch (GetTypeId()) + { + case TypeId.Unit: + if (i_disabledAI != null) // disabled AI must be primary AI + { + if (!IsCharmed()) + { + i_AI = i_disabledAI; + i_disabledAI = null; + + if (IsTypeId(TypeId.Unit)) + ToCreature().GetAI().OnCharmed(false); + } + } + else + { + if (IsCharmed()) + { + i_disabledAI = i_AI; + if (isPossessed() || IsVehicle()) + i_AI = new PossessedAI(ToCreature()); + else + i_AI = new PetAI(ToCreature()); + } + } + break; + case TypeId.Player: + { + if (IsCharmed()) // if we are currently being charmed, then we should apply charm AI + { + i_disabledAI = i_AI; + + UnitAI newAI = null; + // first, we check if the creature's own AI specifies an override playerai for its owned players + Unit charmer = GetCharmer(); + if (charmer) + { + Creature creatureCharmer = charmer.ToCreature(); + if (creatureCharmer) + { + PlayerAI charmAI = creatureCharmer.IsAIEnabled ? creatureCharmer.GetAI().GetAIForCharmedPlayer(ToPlayer()) : null; + if (charmAI != null) + newAI = charmAI; + } + else + { + Log.outError(LogFilter.Misc, "Attempt to assign charm AI to player {0} who is charmed by non-creature {1}.", GetGUID().ToString(), GetCharmerGUID().ToString()); + } + } + if (newAI == null) // otherwise, we default to the generic one + newAI = new SimpleCharmedPlayerAI(ToPlayer()); + i_AI = newAI; + newAI.OnCharmed(true); + } + else + { + if (i_AI != null) + { + // we allow the charmed PlayerAI to clean up + i_AI.OnCharmed(false); + } + else + { + Log.outError(LogFilter.Misc, "Attempt to remove charm AI from player {0} who doesn't currently have charm AI.", GetGUID().ToString()); + } + // and restore our previous PlayerAI (if we had one) + i_AI = i_disabledAI; + i_disabledAI = null; + // IsAIEnabled gets handled in the caller + } + break; + } + default: + Log.outError(LogFilter.Misc, "Attempt to update charm AI for unit {0}, which is neither player nor creature.", GetGUID().ToString()); + break; + } + + } + + public void SetMinion(Minion minion, bool apply) + { + Log.outDebug(LogFilter.Unit, "SetMinion {0} for {1}, apply {2}", minion.GetEntry(), GetEntry(), apply); + + if (apply) + { + if (!minion.GetOwnerGUID().IsEmpty()) + { + Log.outFatal(LogFilter.Unit, "SetMinion: Minion {0} is not the minion of owner {1}", minion.GetEntry(), GetEntry()); + return; + } + + minion.SetOwnerGUID(GetGUID()); + + m_Controlled.Add(minion); + + if (IsTypeId(TypeId.Player)) + { + minion.m_ControlledByPlayer = true; + minion.SetFlag(UnitFields.Flags, UnitFlags.PvpAttackable); + } + + // Can only have one pet. If a new one is summoned, dismiss the old one. + if (minion.IsGuardianPet()) + { + Guardian oldPet = GetGuardianPet(); + if (oldPet) + { + if (oldPet != minion && (oldPet.IsPet() || minion.IsPet() || oldPet.GetEntry() != minion.GetEntry())) + { + // remove existing minion pet + if (oldPet.IsPet()) + ((Pet)oldPet).Remove(PetSaveMode.AsCurrent); + else + oldPet.UnSummon(); + SetPetGUID(minion.GetGUID()); + SetMinionGUID(ObjectGuid.Empty); + } + } + else + { + SetPetGUID(minion.GetGUID()); + SetMinionGUID(ObjectGuid.Empty); + } + } + + if (minion.HasUnitTypeMask(UnitTypeMask.Guardian)) + AddGuidValue(UnitFields.Summon, minion.GetGUID()); + + if (minion.m_Properties != null && minion.m_Properties.Type == SummonType.Minipet) + SetCritterGUID(minion.GetGUID()); + + // PvP, FFAPvP + minion.SetByteValue(UnitFields.Bytes2, UnitBytes2Offsets.PvpFlag, GetByteValue(UnitFields.Bytes2, UnitBytes2Offsets.PvpFlag)); + + // FIXME: hack, speed must be set only at follow + if (IsTypeId(TypeId.Player) && minion.IsPet()) + for (UnitMoveType i = 0; i < UnitMoveType.Max; ++i) + minion.SetSpeedRate(i, m_speed_rate[(int)i]); + + // Ghoul pets have energy instead of mana (is anywhere better place for this code?) + if (minion.IsPetGhoul()) + minion.setPowerType(PowerType.Energy); + + // Send infinity cooldown - client does that automatically but after relog cooldown needs to be set again + SpellInfo spellInfo = Global.SpellMgr.GetSpellInfo(minion.GetUInt32Value(UnitFields.CreatedBySpell)); + if (spellInfo != null && spellInfo.IsCooldownStartedOnEvent()) + GetSpellHistory().StartCooldown(spellInfo, 0, null, true); + } + else + { + if (minion.GetOwnerGUID() != GetGUID()) + { + Log.outFatal(LogFilter.Unit, "SetMinion: Minion {0} is not the minion of owner {1}", minion.GetEntry(), GetEntry()); + return; + } + + m_Controlled.Remove(minion); + + if (minion.m_Properties != null && minion.m_Properties.Type == SummonType.Minipet) + { + if (GetCritterGUID() == minion.GetGUID()) + SetCritterGUID(ObjectGuid.Empty); + } + + if (minion.IsGuardianPet()) + { + if (GetPetGUID() == minion.GetGUID()) + SetPetGUID(ObjectGuid.Empty); + } + else if (minion.IsTotem()) + { + // All summoned by totem minions must disappear when it is removed. + SpellInfo spInfo = Global.SpellMgr.GetSpellInfo(minion.ToTotem().GetSpell()); + if (spInfo != null) + { + foreach (SpellEffectInfo effect in spInfo.GetEffectsForDifficulty(Difficulty.None)) + { + if (effect == null || effect.Effect != SpellEffectName.Summon) + continue; + + RemoveAllMinionsByEntry((uint)effect.MiscValue); + } + } + } + + SpellInfo spellInfo = Global.SpellMgr.GetSpellInfo(minion.GetUInt32Value(UnitFields.CreatedBySpell)); + // Remove infinity cooldown + if (spellInfo != null && spellInfo.IsCooldownStartedOnEvent()) + GetSpellHistory().SendCooldownEvent(spellInfo); + + if (RemoveGuidValue(UnitFields.Summon, minion.GetGUID())) + { + // Check if there is another minion + foreach (var unit in m_Controlled) + { + // do not use this check, creature do not have charm guid + if (GetGUID() == unit.GetCharmerGUID()) + continue; + + Contract.Assert(unit.GetOwnerGUID() == GetGUID()); + if (unit.GetOwnerGUID() != GetGUID()) + { + Contract.Assert(false); + } + Contract.Assert(unit.IsTypeId(TypeId.Unit)); + + if (!unit.HasUnitTypeMask(UnitTypeMask.Guardian)) + continue; + + if (AddGuidValue(UnitFields.Summon, unit.GetGUID())) + { + // show another pet bar if there is no charm bar + if (IsTypeId(TypeId.Player) && GetCharmGUID().IsEmpty()) + { + if (unit.IsPet()) + ToPlayer().PetSpellInitialize(); + else + ToPlayer().CharmSpellInitialize(); + } + } + break; + } + } + } + } + + public bool SetCharmedBy(Unit charmer, CharmType type, AuraApplication aurApp = null) + { + if (!charmer) + return false; + + // dismount players when charmed + if (IsTypeId(TypeId.Player)) + RemoveAurasByType(AuraType.Mounted); + + if (charmer.IsTypeId(TypeId.Player)) + charmer.RemoveAurasByType(AuraType.Mounted); + + Contract.Assert(type != CharmType.Possess || charmer.IsTypeId(TypeId.Player)); + Contract.Assert((type == CharmType.Vehicle) == IsVehicle()); + + Log.outDebug(LogFilter.Unit, "SetCharmedBy: charmer {0} (GUID {1}), charmed {2} (GUID {3}), type {4}.", charmer.GetEntry(), charmer.GetGUID().ToString(), GetEntry(), GetGUID().ToString(), type); + + if (this == charmer) + { + Log.outFatal(LogFilter.Unit, "Unit:SetCharmedBy: Unit {0} (GUID {1}) is trying to charm itself!", GetEntry(), GetGUID().ToString()); + return false; + } + + if (IsTypeId(TypeId.Player) && ToPlayer().GetTransport()) + { + Log.outFatal(LogFilter.Unit, "Unit:SetCharmedBy: Player on transport is trying to charm {0} (GUID {1})", GetEntry(), GetGUID().ToString()); + return false; + } + + // Already charmed + if (!GetCharmerGUID().IsEmpty()) + { + Log.outFatal(LogFilter.Unit, "Unit:SetCharmedBy: {0} (GUID {1}) has already been charmed but {2} (GUID {3}) is trying to charm it!", GetEntry(), GetGUID().ToString(), charmer.GetEntry(), charmer.GetGUID().ToString()); + return false; + } + + CastStop(); + CombatStop(); // @todo CombatStop(true) may cause crash (interrupt spells) + DeleteThreatList(); + + Player playerCharmer = charmer.ToPlayer(); + + // Charmer stop charming + if (playerCharmer) + { + playerCharmer.StopCastingCharm(); + playerCharmer.StopCastingBindSight(); + } + + // Charmed stop charming + if (IsTypeId(TypeId.Player)) + { + ToPlayer().StopCastingCharm(); + ToPlayer().StopCastingBindSight(); + } + + // StopCastingCharm may remove a possessed pet? + if (!IsInWorld) + { + Log.outFatal(LogFilter.Unit, "Unit:SetCharmedBy: {0} (GUID {1}) is not in world but {2} (GUID {3}) is trying to charm it!", GetEntry(), GetGUID().ToString(), charmer.GetEntry(), charmer.GetGUID().ToString()); + return false; + } + + // charm is set by aura, and aura effect remove handler was called during apply handler execution + // prevent undefined behaviour + if (aurApp != null && aurApp.GetRemoveMode() != 0) + return false; + + _oldFactionId = getFaction(); + SetFaction(charmer.getFaction()); + + // Set charmed + charmer.SetCharm(this, true); + + Player player; + if (IsTypeId(TypeId.Unit)) + { + ToCreature().GetAI().OnCharmed(true); + GetMotionMaster().MoveIdle(); + } + else if (player = ToPlayer()) + { + if (player.isAFK()) + player.ToggleAFK(); + + Creature creatureCharmer = charmer.ToCreature(); + if (charmer.IsTypeId(TypeId.Unit)) // we are charmed by a creature + { + // change AI to charmed AI on next Update tick + NeedChangeAI = true; + if (IsAIEnabled) + { + IsAIEnabled = false; + player.GetAI().OnCharmed(true); + } + } + + player.SetClientControl(this, false); + } + + // charm is set by aura, and aura effect remove handler was called during apply handler execution + // prevent undefined behaviour + if (aurApp != null && aurApp.GetRemoveMode() != 0) + return false; + + // Pets already have a properly initialized CharmInfo, don't overwrite it. + if (type != CharmType.Vehicle && GetCharmInfo() == null) + { + InitCharmInfo(); + if (type == CharmType.Possess) + GetCharmInfo().InitPossessCreateSpells(); + else + GetCharmInfo().InitCharmCreateSpells(); + } + + if (playerCharmer) + { + switch (type) + { + case CharmType.Vehicle: + SetFlag(UnitFields.Flags, UnitFlags.PlayerControlled); + playerCharmer.SetClientControl(this, true); + playerCharmer.VehicleSpellInitialize(); + break; + case CharmType.Possess: + AddUnitState(UnitState.Possessed); + SetFlag(UnitFields.Flags, UnitFlags.PlayerControlled); + charmer.SetFlag(UnitFields.Flags, UnitFlags.RemoveClientControl); + playerCharmer.SetClientControl(this, true); + playerCharmer.PossessSpellInitialize(); + break; + case CharmType.Charm: + if (IsTypeId(TypeId.Unit) && charmer.GetClass() == Class.Warlock) + { + CreatureTemplate cinfo = ToCreature().GetCreatureTemplate(); + if (cinfo != null && cinfo.CreatureType == CreatureType.Demon) + { + // to prevent client crash + SetByteValue(UnitFields.Bytes0, 1, (byte)Class.Mage); + + // just to enable stat window + if (GetCharmInfo() != null) + GetCharmInfo().SetPetNumber(Global.ObjectMgr.GeneratePetNumber(), true); + + // if charmed two demons the same session, the 2nd gets the 1st one's name + SetUInt32Value(UnitFields.PetNameTimestamp, (uint)Time.UnixTime); // cast can't be helped + } + } + playerCharmer.CharmSpellInitialize(); + break; + default: + case CharmType.Convert: + break; + } + } + return true; + } + + public void RemoveCharmedBy(Unit charmer) + { + if (!IsCharmed()) + return; + + if (!charmer) + charmer = GetCharmer(); + if (charmer != GetCharmer()) // one aura overrides another? + return; + + CharmType type; + if (HasUnitState(UnitState.Possessed)) + type = CharmType.Possess; + else if (charmer && charmer.IsOnVehicle(this)) + type = CharmType.Vehicle; + else + type = CharmType.Charm; + + CastStop(); + CombatStop(); // @todo CombatStop(true) may cause crash (interrupt spells) + getHostileRefManager().deleteReferences(); + DeleteThreatList(); + + if (_oldFactionId != 0) + { + SetFaction(_oldFactionId); + _oldFactionId = 0; + } + else + RestoreFaction(); + + GetMotionMaster().InitDefault(); + + Creature creature = ToCreature(); + if (creature) + { + // Creature will restore its old AI on next update + if (creature.GetAI() != null) + creature.GetAI().OnCharmed(false); + + // Vehicle should not attack its passenger after he exists the seat + if (type != CharmType.Vehicle) + LastCharmerGUID = charmer ? charmer.GetGUID() : ObjectGuid.Empty; + } + + // If charmer still exists + if (!charmer) + return; + + Contract.Assert(type != CharmType.Possess || charmer.IsTypeId(TypeId.Player)); + Contract.Assert(type != CharmType.Vehicle || (IsTypeId(TypeId.Unit) && IsVehicle())); + + charmer.SetCharm(this, false); + + Player playerCharmer = charmer.ToPlayer(); + if (playerCharmer) + { + switch (type) + { + case CharmType.Vehicle: + playerCharmer.SetClientControl(this, false); + playerCharmer.SetClientControl(charmer, true); + RemoveFlag(UnitFields.Flags, UnitFlags.PlayerControlled); + break; + case CharmType.Possess: + playerCharmer.SetClientControl(this, false); + playerCharmer.SetClientControl(charmer, true); + charmer.RemoveFlag(UnitFields.Flags, UnitFlags.RemoveClientControl); + RemoveFlag(UnitFields.Flags, UnitFlags.PlayerControlled); + ClearUnitState(UnitState.Possessed); + break; + case CharmType.Charm: + if (IsTypeId(TypeId.Unit) && charmer.GetClass() == Class.Warlock) + { + CreatureTemplate cinfo = ToCreature().GetCreatureTemplate(); + if (cinfo != null && cinfo.CreatureType == CreatureType.Demon) + { + SetByteValue(UnitFields.Bytes0, 1, (byte)cinfo.UnitClass); + if (GetCharmInfo() != null) + GetCharmInfo().SetPetNumber(0, true); + else + Log.outError(LogFilter.Unit, "Aura:HandleModCharm: target={0} with typeid={1} has a charm aura but no charm info!", GetGUID(), GetTypeId()); + } + } + break; + case CharmType.Convert: + break; + } + } + + Player player = ToPlayer(); + if (player) + { + if (charmer.IsTypeId(TypeId.Unit)) // charmed by a creature, this means we had PlayerAI + { + NeedChangeAI = true; + IsAIEnabled = false; + } + player.SetClientControl(this, true); + } + + // a guardian should always have charminfo + if (playerCharmer && this != charmer.GetFirstControlled()) + playerCharmer.SendRemoveControlBar(); + else if (IsTypeId(TypeId.Player) || (IsTypeId(TypeId.Unit) && !IsGuardian())) + DeleteCharmInfo(); + } + + public void GetAllMinionsByEntry(List Minions, uint entry) + { + foreach (var unit in m_Controlled) + { + if (unit.GetEntry() == entry && unit.IsSummon()) // minion, actually + Minions.Add(unit.ToTempSummon()); + } + } + + void RemoveAllMinionsByEntry(uint entry) + { + foreach (var unit in m_Controlled.ToList()) + { + if (unit.GetEntry() == entry && unit.IsTypeId(TypeId.Unit) + && unit.ToCreature().IsSummon()) // minion, actually + unit.ToTempSummon().UnSummon(); + // i think this is safe because i have never heard that a despawned minion will trigger a same minion + } + } + + public void SetCharm(Unit charm, bool apply) + { + if (apply) + { + if (IsTypeId(TypeId.Player)) + { + if (!AddGuidValue(UnitFields.Charm, charm.GetGUID())) + Log.outFatal(LogFilter.Unit, "Player {0} is trying to charm unit {1}, but it already has a charmed unit {2}", GetName(), charm.GetEntry(), GetCharmGUID()); + + charm.m_ControlledByPlayer = true; + // @todo maybe we can use this flag to check if controlled by player + charm.SetFlag(UnitFields.Flags, UnitFlags.PvpAttackable); + } + else + charm.m_ControlledByPlayer = false; + + // PvP, FFAPvP + charm.SetByteValue(UnitFields.Bytes2, UnitBytes2Offsets.PvpFlag, GetByteValue(UnitFields.Bytes2, UnitBytes2Offsets.PvpFlag)); + + if (!charm.AddGuidValue(UnitFields.CharmedBy, GetGUID())) + Log.outFatal(LogFilter.Unit, "Unit {0} is being charmed, but it already has a charmer {1}", charm.GetEntry(), charm.GetCharmerGUID()); + + _isWalkingBeforeCharm = charm.IsWalking(); + if (_isWalkingBeforeCharm) + charm.SetWalk(false); + + m_Controlled.Add(charm); + } + else + { + if (IsTypeId(TypeId.Player)) + { + if (!RemoveGuidValue(UnitFields.Charm, charm.GetGUID())) + Log.outFatal(LogFilter.Unit, "Player {0} is trying to uncharm unit {1}, but it has another charmed unit {2}", GetName(), charm.GetEntry(), GetCharmGUID()); + } + + if (!charm.RemoveGuidValue(UnitFields.CharmedBy, GetGUID())) + Log.outFatal(LogFilter.Unit, "Unit {0} is being uncharmed, but it has another charmer {1}", charm.GetEntry(), charm.GetCharmerGUID()); + Player player = charm.GetCharmerOrOwnerPlayerOrPlayerItself(); + if (charm.IsTypeId(TypeId.Player)) + { + charm.m_ControlledByPlayer = true; + charm.SetFlag(UnitFields.Flags, UnitFlags.PvpAttackable); + charm.ToPlayer().UpdatePvPState(); + } + else if (player) + { + charm.m_ControlledByPlayer = true; + charm.SetFlag(UnitFields.Flags, UnitFlags.PvpAttackable); + charm.SetByteValue(UnitFields.Bytes2, UnitBytes2Offsets.PvpFlag, player.GetByteValue(UnitFields.Bytes2, UnitBytes2Offsets.PvpFlag)); + } + else + { + charm.m_ControlledByPlayer = false; + charm.RemoveFlag(UnitFields.Flags, UnitFlags.PvpAttackable); + charm.SetByteValue(UnitFields.Bytes2, UnitBytes2Offsets.PvpFlag, 0); + } + + if (charm.IsWalking() != _isWalkingBeforeCharm) + charm.SetWalk(_isWalkingBeforeCharm); + + if (charm.IsTypeId(TypeId.Player) || !charm.ToCreature().HasUnitTypeMask(UnitTypeMask.Minion) + || charm.GetOwnerGUID() != GetGUID()) + { + m_Controlled.Remove(charm); + } + } + } + + public Unit GetFirstControlled() + { + // Sequence: charmed, pet, other guardians + Unit unit = GetCharm(); + if (!unit) + { + ObjectGuid guid = GetMinionGUID(); + if (!guid.IsEmpty()) + unit = Global.ObjAccessor.GetUnit(this, guid); + } + + return unit; + } + + public void RemoveCharmAuras() + { + RemoveAurasByType(AuraType.ModCharm); + RemoveAurasByType(AuraType.ModPossessPet); + RemoveAurasByType(AuraType.ModPossess); + RemoveAurasByType(AuraType.AoeCharm); + } + + public void RemoveAllControlled() + { + // possessed pet and vehicle + if (IsTypeId(TypeId.Player)) + ToPlayer().StopCastingCharm(); + + while (!m_Controlled.Empty()) + { + Unit target = m_Controlled.First(); + m_Controlled.RemoveAt(0); + if (target.GetCharmerGUID() == GetGUID()) + target.RemoveCharmAuras(); + else if (target.GetOwnerGUID() == GetGUID() && target.IsSummon()) + target.ToTempSummon().UnSummon(); + else + Log.outError(LogFilter.Unit, "Unit {0} is trying to release unit {1} which is neither charmed nor owned by it", GetEntry(), target.GetEntry()); + } + if (!GetPetGUID().IsEmpty()) + Log.outFatal(LogFilter.Unit, "Unit {0} is not able to release its pet {1}", GetEntry(), GetPetGUID()); + if (!GetMinionGUID().IsEmpty()) + Log.outFatal(LogFilter.Unit, "Unit {0} is not able to release its minion {1}", GetEntry(), GetMinionGUID()); + if (!GetCharmGUID().IsEmpty()) + Log.outFatal(LogFilter.Unit, "Unit {0} is not able to release its charm {1}", GetEntry(), GetCharmGUID()); + } + + public void SendPetActionFeedback(uint spellId, ActionFeedback msg) + { + Unit owner = GetOwner(); + if (!owner || !owner.IsTypeId(TypeId.Player)) + return; + + PetActionFeedback petActionFeedback = new PetActionFeedback(); + petActionFeedback.SpellID = spellId; + petActionFeedback.Response = msg; + owner.ToPlayer().SendPacket(petActionFeedback); + } + + public void SendPetTalk(PetTalk pettalk) + { + Unit owner = GetOwner(); + if (!owner || !owner.IsTypeId(TypeId.Player)) + return; + + PetActionSound petActionSound = new PetActionSound(); + petActionSound.UnitGUID = GetGUID(); + petActionSound.Action = pettalk; + owner.ToPlayer().SendPacket(petActionSound); + } + + public void SendPetAIReaction(ObjectGuid guid) + { + Unit owner = GetOwner(); + if (!owner || !owner.IsTypeId(TypeId.Player)) + return; + + AIReaction packet = new AIReaction(); + packet.UnitGUID = guid; + packet.Reaction = AiReaction.Hostile; + + owner.ToPlayer().SendPacket(packet); + } + + public Pet CreateTamedPetFrom(Creature creatureTarget, uint spell_id = 0) + { + if (!IsTypeId(TypeId.Player)) + return null; + + Pet pet = new Pet(ToPlayer(), PetType.Hunter); + + if (!pet.CreateBaseAtCreature(creatureTarget)) + return null; + + uint level = creatureTarget.getLevel() + 5 < getLevel() ? (getLevel() - 5) : creatureTarget.getLevel(); + + InitTamedPet(pet, level, spell_id); + + return pet; + } + + public Pet CreateTamedPetFrom(uint creatureEntry, uint spell_id = 0) + { + if (!IsTypeId(TypeId.Player)) + return null; + + CreatureTemplate creatureInfo = Global.ObjectMgr.GetCreatureTemplate(creatureEntry); + if (creatureInfo == null) + return null; + + Pet pet = new Pet(ToPlayer(), PetType.Hunter); + + if (!pet.CreateBaseAtCreatureInfo(creatureInfo, this) || !InitTamedPet(pet, getLevel(), spell_id)) + return null; + + return pet; + } + + bool InitTamedPet(Pet pet, uint level, uint spell_id) + { + pet.SetCreatorGUID(GetGUID()); + pet.SetFaction(getFaction()); + pet.SetUInt32Value(UnitFields.CreatedBySpell, spell_id); + + if (IsTypeId(TypeId.Player)) + pet.SetUInt32Value(UnitFields.Flags, (uint)UnitFlags.PvpAttackable); + + if (!pet.InitStatsForLevel(level)) + { + Log.outError(LogFilter.Unit, "Pet:InitStatsForLevel() failed for creature (Entry: {0})!", pet.GetEntry()); + return false; + } + + pet.CopyPhaseFrom(this); + + pet.GetCharmInfo().SetPetNumber(Global.ObjectMgr.GeneratePetNumber(), true); + // this enables pet details window (Shift+P) + pet.InitPetCreateSpells(); + pet.SetFullHealth(); + return true; + } + } +} diff --git a/Game/Entities/Unit/Unit.Spells.cs b/Game/Entities/Unit/Unit.Spells.cs new file mode 100644 index 000000000..14386290c --- /dev/null +++ b/Game/Entities/Unit/Unit.Spells.cs @@ -0,0 +1,5119 @@ +/* + * Copyright (C) 2012-2017 CypherCore + * + * 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, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +using Framework.Constants; +using Framework.Dynamic; +using Game.BattleGrounds; +using Game.Network.Packets; +using Game.Spells; +using System; +using System.Collections.Generic; +using System.Diagnostics.Contracts; +using System.Linq; + +namespace Game.Entities +{ + public partial class Unit + { + public virtual bool HasSpell(uint spellId) { return false; } + + // function uses real base points (typically value - 1) + public int CalculateSpellDamage(Unit target, SpellInfo spellProto, uint effect_index, int? basePoints = null, int itemLevel = -1) + { + SpellEffectInfo effect = spellProto.GetEffect(GetMap().GetDifficultyID(), effect_index); + return effect != null ? effect.CalcValue(this, basePoints, target) : 0; + } + public int CalculateSpellDamage(Unit target, SpellInfo spellProto, uint effect_index, out float variance, int? basePoints = null, int itemLevel = -1) + { + SpellEffectInfo effect = spellProto.GetEffect(GetMap().GetDifficultyID(), effect_index); + variance = 0.0f; + return effect != null ? effect.CalcValue(out variance, this, basePoints, target, itemLevel) : 0; + } + + public int SpellBaseDamageBonusDone(SpellSchoolMask schoolMask) + { + if (IsTypeId(TypeId.Player)) + { + float overrideSP = GetFloatValue(PlayerFields.OverrideSpellPowerByApPct); + if (overrideSP > 0.0f) + return (int)(MathFunctions.CalculatePct(GetTotalAttackPowerValue(WeaponAttackType.BaseAttack), overrideSP) + 0.5f); + } + + int DoneAdvertisedBenefit = 0; + + var mDamageDone = GetAuraEffectsByType(AuraType.ModDamageDone); + foreach (var eff in mDamageDone) + { + if ((eff.GetMiscValue() & (int)schoolMask) != 0) + DoneAdvertisedBenefit += eff.GetAmount(); + } + + if (IsTypeId(TypeId.Player)) + { + // Base value + DoneAdvertisedBenefit += (int)ToPlayer().GetBaseSpellPowerBonus(); + + // Check if we are ever using mana - PaperDollFrame.lua + if (GetPowerIndex(PowerType.Mana) != (uint)PowerType.Max) + DoneAdvertisedBenefit += Math.Max(0, (int)GetStat(Stats.Intellect)); // spellpower from intellect + + // Damage bonus from stats + var mDamageDoneOfStatPercent = GetAuraEffectsByType(AuraType.ModSpellDamageOfStatPercent); + foreach (var eff in mDamageDoneOfStatPercent) + { + if (Convert.ToBoolean(eff.GetMiscValue() & (int)schoolMask)) + { + // stat used stored in miscValueB for this aura + Stats usedStat = (Stats)eff.GetMiscValueB(); + DoneAdvertisedBenefit += (int)MathFunctions.CalculatePct(GetStat(usedStat), eff.GetAmount()); + } + } + // ... and attack power + var mDamageDonebyAP = GetAuraEffectsByType(AuraType.ModSpellDamageOfAttackPower); + foreach (var eff in mDamageDonebyAP) + if (Convert.ToBoolean(eff.GetMiscValue() & (int)schoolMask)) + DoneAdvertisedBenefit += (int)MathFunctions.CalculatePct(GetTotalAttackPowerValue(WeaponAttackType.BaseAttack), eff.GetAmount()); + + } + return DoneAdvertisedBenefit; + } + + public int SpellBaseDamageBonusTaken(SpellSchoolMask schoolMask) + { + int TakenAdvertisedBenefit = 0; + + var mDamageTaken = GetAuraEffectsByType(AuraType.ModDamageTaken); + foreach (var eff in mDamageTaken) + if ((eff.GetMiscValue() & (int)schoolMask) != 0) + TakenAdvertisedBenefit += eff.GetAmount(); + + return TakenAdvertisedBenefit; + } + + public uint SpellDamageBonusDone(Unit victim, SpellInfo spellProto, uint pdamage, DamageEffectType damagetype, SpellEffectInfo effect, uint stack = 1) + { + if (spellProto == null || victim == null || damagetype == DamageEffectType.Direct) + return pdamage; + + // Some spells don't benefit from done mods + if (spellProto.HasAttribute(SpellAttr3.NoDoneBonus)) + return pdamage; + + // For totems get damage bonus from owner + if (IsTypeId(TypeId.Unit) && IsTotem()) + { + Unit owner1 = GetOwner(); + if (owner1 != null) + return owner1.SpellDamageBonusDone(victim, spellProto, pdamage, damagetype, effect, stack); + } + + int DoneTotal = 0; + + // Done fixed damage bonus auras + int DoneAdvertisedBenefit = SpellBaseDamageBonusDone(spellProto.GetSchoolMask()); + // Pets just add their bonus damage to their spell damage + // note that their spell damage is just gain of their own auras + if (HasUnitTypeMask(UnitTypeMask.Guardian)) + DoneAdvertisedBenefit += ((Guardian)this).GetBonusDamage(); + + // Check for table values + if (effect.BonusCoefficientFromAP > 0.0f) + { + float ApCoeffMod = effect.BonusCoefficientFromAP; + Player modOwner = GetSpellModOwner(); + if (modOwner) + { + ApCoeffMod *= 100.0f; + modOwner.ApplySpellMod(spellProto.Id, SpellModOp.BonusMultiplier, ref ApCoeffMod); + ApCoeffMod /= 100.0f; + } + + WeaponAttackType attType = (spellProto.IsRangedWeaponSpell() && spellProto.DmgClass != SpellDmgClass.Melee) ? WeaponAttackType.RangedAttack : WeaponAttackType.BaseAttack; + float APbonus = victim.GetTotalAuraModifier(attType == WeaponAttackType.BaseAttack ? AuraType.MeleeAttackPowerAttackerBonus : AuraType.RangedAttackPowerAttackerBonus); + APbonus += GetTotalAttackPowerValue(attType); + DoneTotal += (int)(stack * ApCoeffMod * APbonus); + } + + // Default calculation + float coeff = effect.BonusCoefficient; + if (DoneAdvertisedBenefit != 0) + { + float factorMod = CalculateLevelPenalty(spellProto) * stack; + Player modOwner = GetSpellModOwner(); + if (modOwner) + { + coeff *= 100.0f; + modOwner.ApplySpellMod(spellProto.Id, SpellModOp.BonusMultiplier, ref coeff); + coeff /= 100.0f; + } + DoneTotal += (int)(DoneAdvertisedBenefit * coeff * factorMod); + } + + // Done Percentage for DOT is already calculated, no need to do it again. The percentage mod is applied in Aura.HandleAuraSpecificMods. + float tmpDamage = ((int)pdamage + DoneTotal) * (damagetype == DamageEffectType.DOT ? 1.0f : SpellDamagePctDone(victim, spellProto, damagetype)); + // apply spellmod to Done damage (flat and pct) + Player _modOwner = GetSpellModOwner(); + if (_modOwner) + _modOwner.ApplySpellMod(spellProto.Id, damagetype == DamageEffectType.DOT ? SpellModOp.Dot : SpellModOp.Damage, ref tmpDamage); + + return (uint)Math.Max(tmpDamage, 0.0f); + } + + public float SpellDamagePctDone(Unit victim, SpellInfo spellProto, DamageEffectType damagetype) + { + if (spellProto == null || !victim || damagetype == DamageEffectType.Direct) + return 1.0f; + + // Some spells don't benefit from pct done mods + if (spellProto.HasAttribute(SpellAttr6.NoDonePctDamageMods)) + return 1.0f; + + // For totems pct done mods are calculated when its calculation is run on the player in SpellDamageBonusDone. + if (IsTypeId(TypeId.Unit) && IsTotem()) + return 1.0f; + + // Done total percent damage auras + float DoneTotalMod = 1.0f; + + // Pet damage? + if (IsTypeId(TypeId.Unit) && !IsPet()) + DoneTotalMod *= ToCreature().GetSpellDamageMod(ToCreature().GetCreatureTemplate().Rank); + + float maxModDamagePercentSchool = 0.0f; + if (IsTypeId(TypeId.Player)) + { + for (int i = 0; i < (int)SpellSchools.Max; ++i) + { + if (Convert.ToBoolean((int)spellProto.GetSchoolMask() & (1 << i))) + maxModDamagePercentSchool = Math.Max(maxModDamagePercentSchool, GetFloatValue(PlayerFields.ModDamageDonePct + i)); + } + } + else + maxModDamagePercentSchool = GetTotalAuraMultiplierByMiscMask(AuraType.ModDamagePercentDone, (uint)spellProto.GetSchoolMask()); + + DoneTotalMod *= maxModDamagePercentSchool; + + uint creatureTypeMask = victim.GetCreatureTypeMask(); + + var mDamageDoneVersus = GetAuraEffectsByType(AuraType.ModDamageDoneVersus); + foreach (var eff in mDamageDoneVersus) + { + if (creatureTypeMask.HasAnyFlag((uint)eff.GetMiscValue())) + MathFunctions.AddPct(ref DoneTotalMod, eff.GetAmount()); + } + + // bonus against aurastate + var mDamageDoneVersusAurastate = GetAuraEffectsByType(AuraType.ModDamageDoneVersusAurastate); + foreach (var eff in mDamageDoneVersusAurastate) + { + if (victim.HasAuraState((AuraStateType)eff.GetMiscValue())) + MathFunctions.AddPct(ref DoneTotalMod, eff.GetAmount()); + } + + // Add SPELL_AURA_MOD_DAMAGE_DONE_FOR_MECHANIC percent bonus + MathFunctions.AddPct(ref DoneTotalMod, GetTotalAuraModifierByMiscValue(AuraType.ModDamageDoneForMechanic, (int)spellProto.Mechanic)); + + // Custom scripted damage + switch (spellProto.SpellFamilyName) + { + case SpellFamilyNames.Mage: + // Ice Lance (no unique family flag) + if (spellProto.Id == 228598) + if (victim.HasAuraState(AuraStateType.Frozen, spellProto, this)) + DoneTotalMod *= 3.0f; + + break; + case SpellFamilyNames.Priest: + // Smite + if (spellProto.SpellFamilyFlags[0].HasAnyFlag(0x80)) + { + // Glyph of Smite + AuraEffect aurEff = GetAuraEffect(55692, 0); + if (aurEff != null) + if (victim.GetAuraEffect(AuraType.PeriodicDamage, SpellFamilyNames.Priest, new FlagArray128(0x100000, 0, 0), GetGUID()) != null) + MathFunctions.AddPct(ref DoneTotalMod, aurEff.GetAmount()); + } + break; + case SpellFamilyNames.Warlock: + // Shadow Bite (30% increase from each dot) + if (spellProto.SpellFamilyFlags[1].HasAnyFlag(0x00400000) && IsPet()) + { + uint count = victim.GetDoTsByCaster(GetOwnerGUID()); + if (count != 0) + MathFunctions.AddPct(ref DoneTotalMod, 30 * count); + } + + // Drain Soul - increased damage for targets under 25 % HP + if (spellProto.SpellFamilyFlags[0].HasAnyFlag(0x00004000)) + if (HasAura(100001)) + DoneTotalMod *= 2; + break; + case SpellFamilyNames.Deathknight: + // Sigil of the Vengeful Heart + if (spellProto.SpellFamilyFlags[0].HasAnyFlag(0x2000)) + { + AuraEffect aurEff = GetAuraEffect(64962, 1); + if (aurEff != null) + DoneTotalMod += aurEff.GetAmount(); + } + break; + } + + return DoneTotalMod; + } + + public uint SpellDamageBonusTaken(Unit caster, SpellInfo spellProto, uint pdamage, DamageEffectType damagetype, SpellEffectInfo effect, uint stack = 1) + { + if (spellProto == null || damagetype == DamageEffectType.Direct) + return pdamage; + + int TakenTotal = 0; + float TakenTotalMod = 1.0f; + float TakenTotalCasterMod = 0.0f; + + // Mod damage from spell mechanic + uint mechanicMask = spellProto.GetAllEffectsMechanicMask(); + if (mechanicMask != 0) + { + var mDamageDoneMechanic = GetAuraEffectsByType(AuraType.ModMechanicDamageTakenPercent); + foreach (var eff in mDamageDoneMechanic) + if (Convert.ToBoolean(mechanicMask & (1 << eff.GetMiscValue()))) + MathFunctions.AddPct(ref TakenTotalMod, eff.GetAmount()); + } + + AuraEffect cheatDeath = GetAuraEffect(45182, 0); + if (cheatDeath != null) + if (cheatDeath.GetMiscValue().HasAnyFlag((int)SpellSchoolMask.Normal)) + MathFunctions.AddPct(ref TakenTotalMod, cheatDeath.GetAmount()); + + // Spells with SPELL_ATTR4_FIXED_DAMAGE should only benefit from mechanic damage mod auras. + if (!spellProto.HasAttribute(SpellAttr4.FixedDamage)) + { + // get all auras from caster that allow the spell to ignore resistance (sanctified wrath) + var IgnoreResistAuras = caster.GetAuraEffectsByType(AuraType.ModIgnoreTargetResist); + foreach (var eff in IgnoreResistAuras) + { + if (Convert.ToBoolean(eff.GetMiscValue() & (int)spellProto.GetSchoolMask())) + TakenTotalCasterMod += eff.GetAmount(); + } + + // from positive and negative SPELL_AURA_MOD_DAMAGE_PERCENT_TAKEN + // multiplicative bonus, for example Dispersion + Shadowform (0.10*0.85=0.085) + TakenTotalMod *= GetTotalAuraMultiplierByMiscMask(AuraType.ModDamagePercentTaken, (uint)spellProto.GetSchoolMask()); + + // From caster spells + var mOwnerTaken = GetAuraEffectsByType(AuraType.ModSpellDamageFromCaster); + foreach (var eff in mOwnerTaken) + if (eff.GetCasterGUID() == caster.GetGUID() && eff.IsAffectingSpell(spellProto)) + MathFunctions.AddPct(ref TakenTotalMod, eff.GetAmount()); + + int TakenAdvertisedBenefit = SpellBaseDamageBonusTaken(spellProto.GetSchoolMask()); + + // Check for table values + float coeff = effect.BonusCoefficient; + + // Default calculation + if (TakenAdvertisedBenefit != 0) + { + float factorMod = CalculateLevelPenalty(spellProto) * stack; + // level penalty still applied on Taken bonus - is it blizzlike? + Player modOwner = GetSpellModOwner(); + if (modOwner) + { + coeff *= 100.0f; + modOwner.ApplySpellMod(spellProto.Id, SpellModOp.BonusMultiplier, ref coeff); + coeff /= 100.0f; + } + TakenTotal += (int)(TakenAdvertisedBenefit * coeff * factorMod); + } + } + + float tmpDamage = 0.0f; + + if (TakenTotalCasterMod != 0) + { + if (TakenTotal < 0) + { + if (TakenTotalMod < 1) + tmpDamage = (((MathFunctions.CalculatePct(pdamage, TakenTotalCasterMod) + TakenTotal) * TakenTotalMod) + MathFunctions.CalculatePct(pdamage, TakenTotalCasterMod)); + else + tmpDamage = (((float)(MathFunctions.CalculatePct(pdamage, TakenTotalCasterMod) + TakenTotal) + MathFunctions.CalculatePct(pdamage, TakenTotalCasterMod)) * TakenTotalMod); + } + else if (TakenTotalMod < 1) + tmpDamage = ((MathFunctions.CalculatePct(pdamage + TakenTotal, TakenTotalCasterMod) * TakenTotalMod) + MathFunctions.CalculatePct(pdamage + TakenTotal, TakenTotalCasterMod)); + } + if (tmpDamage == 0) + tmpDamage = (pdamage + TakenTotal) * TakenTotalMod; + + return (uint)Math.Max(tmpDamage, 0.0f); + } + + public uint SpellBaseHealingBonusDone(SpellSchoolMask schoolMask) + { + if (IsTypeId(TypeId.Player)) + { + float overrideSP = GetFloatValue(PlayerFields.OverrideSpellPowerByApPct); + if (overrideSP > 0.0f) + return (uint)(MathFunctions.CalculatePct(GetTotalAttackPowerValue(WeaponAttackType.BaseAttack), overrideSP) + 0.5f); + } + + uint advertisedBenefit = 0; + + var mHealingDone = GetAuraEffectsByType(AuraType.ModHealingDone); + foreach (var i in mHealingDone) + if (i.GetMiscValue() == 0 || (i.GetMiscValue() & (int)schoolMask) != 0) + advertisedBenefit += (uint)i.GetAmount(); + + // Healing bonus of spirit, intellect and strength + if (IsTypeId(TypeId.Player)) + { + // Base value + advertisedBenefit += ToPlayer().GetBaseSpellPowerBonus(); + + // Check if we are ever using mana - PaperDollFrame.lua + if (GetPowerIndex(PowerType.Mana) != (uint)PowerType.Max) + advertisedBenefit += Math.Max(0, (uint)GetStat(Stats.Intellect)); // spellpower from intellect + + // Healing bonus from stats + var mHealingDoneOfStatPercent = GetAuraEffectsByType(AuraType.ModSpellHealingOfStatPercent); + foreach (var i in mHealingDoneOfStatPercent) + { + // stat used dependent from misc value (stat index) + Stats usedStat = (Stats)(i.GetSpellEffectInfo().MiscValue); + advertisedBenefit += (uint)MathFunctions.CalculatePct(GetStat(usedStat), i.GetAmount()); + } + + // ... and attack power + var mHealingDonebyAP = GetAuraEffectsByType(AuraType.ModSpellHealingOfAttackPower); + foreach (var i in mHealingDonebyAP) + if (Convert.ToBoolean(i.GetMiscValue() & (int)schoolMask)) + advertisedBenefit += (uint)MathFunctions.CalculatePct(GetTotalAttackPowerValue(WeaponAttackType.BaseAttack), i.GetAmount()); + } + return advertisedBenefit; + } + + int SpellBaseHealingBonusTaken(SpellSchoolMask schoolMask) + { + int advertisedBenefit = 0; + + var mDamageTaken = GetAuraEffectsByType(AuraType.ModHealing); + foreach (var i in mDamageTaken) + if ((i.GetMiscValue() & (int)schoolMask) != 0) + advertisedBenefit += i.GetAmount(); + + return advertisedBenefit; + } + + public int SpellCriticalHealingBonus(SpellInfo spellProto, int damage, Unit victim) + { + // Calculate critical bonus + int crit_bonus = damage; + + damage += crit_bonus; + + damage = (int)(damage * GetTotalAuraMultiplier(AuraType.ModCriticalHealingAmount)); + + return damage; + } + + public uint SpellHealingBonusDone(Unit victim, SpellInfo spellProto, uint healamount, DamageEffectType damagetype, SpellEffectInfo effect, uint stack = 1) + { + // For totems get healing bonus from owner (statue isn't totem in fact) + if (IsTypeId(TypeId.Unit) && IsTotem()) + { + Unit owner1 = GetOwner(); + if (owner1) + return owner1.SpellHealingBonusDone(victim, spellProto, healamount, damagetype, effect, stack); + } + + // No bonus healing for potion spells + if (spellProto.SpellFamilyName == SpellFamilyNames.Potion) + return healamount; + + int DoneTotal = 0; + + // done scripted mod (take it from owner) + Unit owner = GetOwner() ?? this; + var mOverrideClassScript = owner.GetAuraEffectsByType(AuraType.OverrideClassScripts); + foreach (var eff in mOverrideClassScript) + { + if (!eff.IsAffectingSpell(spellProto)) + continue; + + switch (eff.GetMiscValue()) + { + case 3736: // Hateful Totem of the Third Wind / Increased Lesser Healing Wave / LK Arena (4/5/6) Totem of the Third Wind / Savage Totem of the Third Wind + DoneTotal += eff.GetAmount(); + break; + default: + break; + } + } + + // Done fixed damage bonus auras + uint DoneAdvertisedBenefit = SpellBaseHealingBonusDone(spellProto.GetSchoolMask()); + + // Check for table values + float coeff = effect.BonusCoefficient; + float factorMod = 1.0f; + if (effect.BonusCoefficientFromAP > 0.0f) + { + DoneTotal += (int)(effect.BonusCoefficientFromAP * stack * GetTotalAttackPowerValue( + (spellProto.IsRangedWeaponSpell() && spellProto.DmgClass != SpellDmgClass.Melee) ? WeaponAttackType.RangedAttack : WeaponAttackType.BaseAttack)); + } + else if (coeff <= 0.0f) + { + // No bonus healing for SPELL_DAMAGE_CLASS_NONE class spells by default + if (spellProto.DmgClass == SpellDmgClass.None) + return healamount; + } + + // Default calculation + if (DoneAdvertisedBenefit != 0) + { + factorMod *= CalculateLevelPenalty(spellProto) * stack; + Player modOwner = GetSpellModOwner(); + if (modOwner) + { + coeff *= 100.0f; + modOwner.ApplySpellMod(spellProto.Id, SpellModOp.BonusMultiplier, ref coeff); + coeff /= 100.0f; + } + + DoneTotal += (int)(DoneAdvertisedBenefit * coeff * factorMod); + } + + foreach (SpellEffectInfo eff in spellProto.GetEffectsForDifficulty(GetMap().GetDifficultyID())) + { + if (eff == null) + continue; + + switch (eff.ApplyAuraName) + { + // Bonus healing does not apply to these spells + case AuraType.PeriodicLeech: + case AuraType.PeriodicHealthFunnel: + DoneTotal = 0; + break; + } + if (eff.Effect == SpellEffectName.HealthLeech) + DoneTotal = 0; + } + + // use float as more appropriate for negative values and percent applying + float heal = (healamount + DoneTotal) * (damagetype == DamageEffectType.DOT ? 1.0f : SpellHealingPctDone(victim, spellProto)); + // apply spellmod to Done amount + Player _modOwner = GetSpellModOwner(); + if (_modOwner) + _modOwner.ApplySpellMod(spellProto.Id, damagetype == DamageEffectType.DOT ? SpellModOp.Dot : SpellModOp.Damage, ref heal); + + return (uint)Math.Max(heal, 0.0f); + } + + public float SpellHealingPctDone(Unit victim, SpellInfo spellProto) + { + // For totems pct done mods are calculated when its calculation is run on the player in SpellHealingBonusDone. + if (IsTypeId(TypeId.Unit) && IsTotem()) + return 1.0f; + + // No bonus healing for potion spells + if (spellProto.SpellFamilyName == SpellFamilyNames.Potion) + return 1.0f; + + float DoneTotalMod = 1.0f; + + // Healing done percent + var mHealingDonePct = GetAuraEffectsByType(AuraType.ModHealingDonePercent); + foreach (var eff in mHealingDonePct) + MathFunctions.AddPct(ref DoneTotalMod, eff.GetAmount()); + + return DoneTotalMod; + } + + public uint SpellHealingBonusTaken(Unit caster, SpellInfo spellProto, uint healamount, DamageEffectType damagetype, SpellEffectInfo effect, uint stack = 1) + { + float TakenTotalMod = 1.0f; + + // Healing taken percent + float minval = GetMaxNegativeAuraModifier(AuraType.ModHealingPct); + if (minval != 0) + MathFunctions.AddPct(ref TakenTotalMod, minval); + + float maxval = GetMaxPositiveAuraModifier(AuraType.ModHealingPct); + if (maxval != 0) + MathFunctions.AddPct(ref TakenTotalMod, maxval); + + // Tenacity increase healing % taken + AuraEffect Tenacity = GetAuraEffect(58549, 0); + if (Tenacity != null) + MathFunctions.AddPct(ref TakenTotalMod, Tenacity.GetAmount()); + + // Healing Done + int TakenTotal = 0; + + // Taken fixed damage bonus auras + int TakenAdvertisedBenefit = SpellBaseHealingBonusTaken(spellProto.GetSchoolMask()); + + // Nourish cast + if (spellProto.SpellFamilyName == SpellFamilyNames.Druid && spellProto.SpellFamilyFlags[1].HasAnyFlag(0x2000000u)) + { + // Rejuvenation, Regrowth, Lifebloom, or Wild Growth + if (GetAuraEffect(AuraType.PeriodicHeal, SpellFamilyNames.Druid, new FlagArray128(0x50, 0x4000010, 0)) != null) + // increase healing by 20% + TakenTotalMod *= 1.2f; + } + + // Check for table values + float coeff = effect.BonusCoefficient; + float factorMod = 1.0f; + if (coeff <= 0.0f) + { + // No bonus healing for SPELL_DAMAGE_CLASS_NONE class spells by default + if (spellProto.DmgClass == SpellDmgClass.None) + { + healamount = (uint)Math.Max((healamount * TakenTotalMod), 0.0f); + return healamount; + } + } + + // Default calculation + if (TakenAdvertisedBenefit != 0) + { + factorMod *= CalculateLevelPenalty(spellProto) * stack; + Player modOwner = GetSpellModOwner(); + if (modOwner) + { + coeff *= 100.0f; + modOwner.ApplySpellMod(spellProto.Id, SpellModOp.BonusMultiplier, ref coeff); + coeff /= 100.0f; + } + + TakenTotal += (int)(TakenAdvertisedBenefit * coeff * factorMod); + } + + var mHealingGet = GetAuraEffectsByType(AuraType.ModHealingReceived); + foreach (var eff in mHealingGet) + if (caster.GetGUID() == eff.GetCasterGUID() && eff.IsAffectingSpell(spellProto)) + MathFunctions.AddPct(ref TakenTotalMod, eff.GetAmount()); + + foreach (SpellEffectInfo eff in spellProto.GetEffectsForDifficulty(GetMap().GetDifficultyID())) + { + if (eff == null) + continue; + + switch (eff.ApplyAuraName) + { + // Bonus healing does not apply to these spells + case AuraType.PeriodicLeech: + case AuraType.PeriodicHealthFunnel: + TakenTotal = 0; + break; + } + if (eff.Effect == SpellEffectName.HealthLeech) + TakenTotal = 0; + } + + float heal = (healamount + TakenTotal) * TakenTotalMod; + + return (uint)Math.Max(heal, 0.0f); + } + + public bool IsSpellCrit(Unit victim, SpellInfo spellProto, SpellSchoolMask schoolMask, WeaponAttackType attackType = WeaponAttackType.BaseAttack) + { + return RandomHelper.randChance(GetUnitSpellCriticalChance(victim, spellProto, schoolMask, attackType)); + } + + public float GetUnitSpellCriticalChance(Unit victim, SpellInfo spellProto, SpellSchoolMask schoolMask, WeaponAttackType attackType = WeaponAttackType.BaseAttack) + { + //! Mobs can't crit with spells. Player Totems can + //! Fire Elemental (from totem) can too - but this part is a hack and needs more research + if (GetGUID().IsCreatureOrVehicle() && !(IsTotem() && GetOwnerGUID().IsPlayer()) && GetEntry() != 15438) + return 0.0f; + + // not critting spell + if (spellProto.HasAttribute(SpellAttr2.CantCrit)) + return 0.0f; + + float crit_chance = 0.0f; + switch (spellProto.DmgClass) + { + case SpellDmgClass.None: + // We need more spells to find a general way (if there is any) + switch (spellProto.Id) + { + case 379: // Earth Shield + case 33778: // Lifebloom Final Bloom + case 64844: // Divine Hymn + case 71607: // Item - Bauble of True Blood 10m + case 71646: // Item - Bauble of True Blood 25m + break; + default: + return 0.0f; + } + goto case SpellDmgClass.Magic; + case SpellDmgClass.Magic: + { + if (schoolMask.HasAnyFlag(SpellSchoolMask.Normal)) + crit_chance = 0.0f; + // For other schools + else if (IsTypeId(TypeId.Player)) + crit_chance = GetFloatValue(PlayerFields.CritPercentage); + else + crit_chance = m_baseSpellCritChance; + + // taken + if (victim) + { + if (!spellProto.IsPositive()) + { + // Modify critical chance by victim SPELL_AURA_MOD_ATTACKER_SPELL_AND_WEAPON_CRIT_CHANCE + crit_chance += victim.GetTotalAuraModifier(AuraType.ModAttackerSpellAndWeaponCritChance); + } + // scripted (increase crit chance ... against ... target by x% + var mOverrideClassScript = GetAuraEffectsByType(AuraType.OverrideClassScripts); + foreach (var eff in mOverrideClassScript) + { + if (!eff.IsAffectingSpell(spellProto)) + continue; + + switch (eff.GetMiscValue()) + { + case 911: // Shatter + if (victim.HasAuraState(AuraStateType.Frozen, spellProto, this)) + { + crit_chance *= 1.5f; + AuraEffect _eff = eff.GetBase().GetEffect(1); + if (_eff != null) + crit_chance += _eff.GetAmount(); + } + break; + default: + break; + } + } + // Custom crit by class + switch (spellProto.SpellFamilyName) + { + case SpellFamilyNames.Rogue: + // Shiv-applied poisons can't crit + if (FindCurrentSpellBySpellId(5938) != null) + crit_chance = 0.0f; + break; + case SpellFamilyNames.Paladin: + // Flash of light + if (spellProto.SpellFamilyFlags[0].HasAnyFlag(0x40000000u)) + { + // Sacred Shield + AuraEffect aura = victim.GetAuraEffect(58597, 1, GetGUID()); + if (aura != null) + crit_chance += aura.GetAmount(); + break; + } + // Exorcism + else if (spellProto.GetCategory() == 19) + { + if (victim.GetCreatureTypeMask().HasAnyFlag((uint)CreatureType.MaskDemonOrUnDead)) + return 100.0f; + break; + } + break; + case SpellFamilyNames.Shaman: + // Lava Burst + if (spellProto.SpellFamilyFlags[1].HasAnyFlag(0x00001000u)) + { + if (victim.GetAuraEffect(AuraType.PeriodicDamage, SpellFamilyNames.Shaman, new FlagArray128(0x10000000, 0, 0), GetGUID()) != null) + if (victim.GetTotalAuraModifier(AuraType.ModAttackerSpellAndWeaponCritChance) > -100) + return 100.0f; + break; + } + break; + } + } + break; + } + case SpellDmgClass.Melee: + case SpellDmgClass.Ranged: + { + if (victim) + crit_chance += GetUnitCriticalChance(attackType, victim); + break; + } + default: + return 0.0f; + } + // percent done + // only players use intelligence for critical chance computations + Player modOwner = GetSpellModOwner(); + if (modOwner != null) + modOwner.ApplySpellMod(spellProto.Id, SpellModOp.CriticalChance, ref crit_chance); + + var critChanceForCaster = victim.GetAuraEffectsByType(AuraType.ModCritChanceForCaster); + foreach (AuraEffect aurEff in critChanceForCaster) + if (aurEff.GetCasterGUID() == GetGUID() && aurEff.IsAffectingSpell(spellProto)) + crit_chance += aurEff.GetAmount(); + + return crit_chance > 0.0f ? crit_chance : 0.0f; + } + + // Calculate spell hit result can be: + // Every spell can: Evade/Immune/Reflect/Sucesful hit + // For melee based spells: + // Miss + // Dodge + // Parry + // For spells + // Resist + public SpellMissInfo SpellHitResult(Unit victim, SpellInfo spell, bool CanReflect) + { + // Check for immune + if (victim.IsImmunedToSpell(spell)) + return SpellMissInfo.Immune; + + // All positive spells can`t miss + // @todo client not show miss log for this spells - so need find info for this in dbc and use it! + if (spell.IsPositive() + && (!IsHostileTo(victim))) // prevent from affecting enemy by "positive" spell + return SpellMissInfo.None; + // Check for immune + if (victim.IsImmunedToDamage(spell)) + return SpellMissInfo.Immune; + + if (this == victim) + return SpellMissInfo.None; + + // Return evade for units in evade mode + if (victim.IsTypeId(TypeId.Unit) && victim.ToCreature().IsEvadingAttacks()) + return SpellMissInfo.Evade; + + // Try victim reflect spell + if (CanReflect) + { + int reflectchance = victim.GetTotalAuraModifier(AuraType.ReflectSpells); + var mReflectSpellsSchool = victim.GetAuraEffectsByType(AuraType.ReflectSpellsSchool); + foreach (var eff in mReflectSpellsSchool) + if (Convert.ToBoolean(eff.GetMiscValue() & (int)spell.GetSchoolMask())) + reflectchance += eff.GetAmount(); + if (reflectchance > 0 && RandomHelper.randChance(reflectchance)) + { + // Start triggers for remove charges if need (trigger only for victim, and mark as active spell) + ProcDamageAndSpell(victim, ProcFlags.None, ProcFlags.TakenSpellMagicDmgClassNeg, ProcFlagsExLegacy.Reflect, 1, WeaponAttackType.BaseAttack, spell); + return SpellMissInfo.Reflect; + } + } + + switch (spell.DmgClass) + { + case SpellDmgClass.Ranged: + case SpellDmgClass.Melee: + return MeleeSpellHitResult(victim, spell); + case SpellDmgClass.None: + return SpellMissInfo.None; + case SpellDmgClass.Magic: + return MagicSpellHitResult(victim, spell); + } + return SpellMissInfo.None; + } + + // Melee based spells hit result calculations + SpellMissInfo MeleeSpellHitResult(Unit victim, SpellInfo spellInfo) + { + // Spells with SPELL_ATTR3_IGNORE_HIT_RESULT will additionally fully ignore + // resist and deflect chances + if (spellInfo.HasAttribute(SpellAttr3.IgnoreHitResult)) + return SpellMissInfo.None; + + WeaponAttackType attType = WeaponAttackType.BaseAttack; + + // Check damage class instead of attack type to correctly handle judgements + // - they are meele, but can't be dodged/parried/deflected because of ranged dmg class + if (spellInfo.DmgClass == SpellDmgClass.Ranged) + attType = WeaponAttackType.RangedAttack; + + int roll = RandomHelper.IRand(0, 9999); + + int missChance = (int)(MeleeSpellMissChance(victim, attType, spellInfo.Id) * 100.0f); + // Roll miss + int tmp = missChance; + if (roll < tmp) + return SpellMissInfo.Miss; + + // Chance resist mechanic + int resist_chance = victim.GetMechanicResistChance(spellInfo) * 100; + tmp += resist_chance; + if (roll < tmp) + return SpellMissInfo.Resist; + + // Same spells cannot be parried/dodged + if (spellInfo.HasAttribute(SpellAttr0.ImpossibleDodgeParryBlock)) + return SpellMissInfo.None; + + bool canDodge = true; + bool canParry = true; + bool canBlock = spellInfo.HasAttribute(SpellAttr3.BlockableSpell); + + // if victim is casting or cc'd it can't avoid attacks + if (victim.IsNonMeleeSpellCast(false) || victim.HasUnitState(UnitState.Controlled)) + { + canDodge = false; + canParry = false; + canBlock = false; + } + + // Ranged attacks can only miss, resist and deflect + if (attType == WeaponAttackType.RangedAttack) + { + canParry = false; + canDodge = false; + + // only if in front + if (victim.HasInArc(MathFunctions.PI, this) || victim.HasAuraType(AuraType.IgnoreHitDirection)) + { + int deflect_chance = victim.GetTotalAuraModifier(AuraType.DeflectSpells) * 100; + tmp += deflect_chance; + if (roll < tmp) + return SpellMissInfo.Deflect; + } + return SpellMissInfo.None; + } + + // Check for attack from behind + if (!victim.HasInArc(MathFunctions.PI, this)) + { + if (!victim.HasAuraType(AuraType.IgnoreHitDirection)) + { + // Can`t dodge from behind in PvP (but its possible in PvE) + if (victim.IsTypeId(TypeId.Player)) + canDodge = false; + // Can`t parry or block + canParry = false; + canBlock = false; + } + else // Only deterrence as of 3.3.5 + { + if (spellInfo.HasAttribute(SpellCustomAttributes.ReqCasterBehindTarget)) + canParry = false; + } + } + + // Ignore combat result aura + var ignore = GetAuraEffectsByType(AuraType.IgnoreCombatResult); + foreach (var aurEff in ignore) + { + if (!aurEff.IsAffectingSpell(spellInfo)) + continue; + + switch ((MeleeHitOutcome)aurEff.GetMiscValue()) + { + case MeleeHitOutcome.Dodge: + canDodge = false; + break; + case MeleeHitOutcome.Block: + canBlock = false; + break; + case MeleeHitOutcome.Parry: + canParry = false; + break; + default: + Log.outDebug(LogFilter.Unit, "Spell {0} SPELL_AURA_IGNORE_COMBAT_RESULT has unhandled state {1}", aurEff.GetId(), aurEff.GetMiscValue()); + break; + } + } + + if (canDodge) + { + // Roll dodge + int dodgeChance = (int)(GetUnitDodgeChance(attType, victim) * 100.0f); + if (dodgeChance < 0) + dodgeChance = 0; + + if (roll < (tmp += dodgeChance)) + return SpellMissInfo.Dodge; + } + + if (canParry) + { + // Roll parry + int parryChance = (int)(GetUnitParryChance(attType, victim) * 100.0f); + if (parryChance < 0) + parryChance = 0; + + tmp += parryChance; + if (roll < tmp) + return SpellMissInfo.Parry; + } + + if (canBlock) + { + int blockChance = (int)(GetUnitBlockChance(attType, victim) * 100.0f); + if (blockChance < 0) + blockChance = 0; + tmp += blockChance; + + if (roll < tmp) + return SpellMissInfo.Block; + } + + return SpellMissInfo.None; + } + + // @todo need use unit spell resistances in calculations + SpellMissInfo MagicSpellHitResult(Unit victim, SpellInfo spell) + { + // Can`t miss on dead target (on skinning for example) + if (!victim.IsAlive() && !victim.IsTypeId(TypeId.Player)) + return SpellMissInfo.None; + + SpellSchoolMask schoolMask = spell.GetSchoolMask(); + // PvP - PvE spell misschances per leveldif > 2 + int lchance = victim.IsTypeId(TypeId.Player) ? 7 : 11; + int thisLevel = (int)GetLevelForTarget(victim); + if (IsTypeId(TypeId.Unit) && ToCreature().IsTrigger()) + thisLevel = (int)Math.Max(thisLevel, spell.SpellLevel); + int leveldif = (int)(victim.GetLevelForTarget(this)) - thisLevel; + int levelBasedHitDiff = leveldif; + + // Base hit chance from attacker and victim levels + int modHitChance = 100; + if (levelBasedHitDiff >= 0) + { + if (!victim.IsTypeId(TypeId.Player)) + { + modHitChance = 94 - 3 * Math.Min(levelBasedHitDiff, 3); + levelBasedHitDiff -= 3; + } + else + { + modHitChance = 96 - Math.Min(levelBasedHitDiff, 2); + levelBasedHitDiff -= 2; + } + if (levelBasedHitDiff > 0) + modHitChance -= lchance * Math.Min(levelBasedHitDiff, 7); + } + else + modHitChance = 97 - levelBasedHitDiff; + + // Spellmod from SPELLMOD_RESIST_MISS_CHANCE + Player modOwner = GetSpellModOwner(); + if (modOwner != null) + modOwner.ApplySpellMod(spell.Id, SpellModOp.ResistMissChance, ref modHitChance); + + // Spells with SPELL_ATTR3_IGNORE_HIT_RESULT will ignore target's avoidance effects + if (!spell.HasAttribute(SpellAttr3.IgnoreHitResult)) + { + // Chance hit from victim SPELL_AURA_MOD_ATTACKER_SPELL_HIT_CHANCE auras + modHitChance += victim.GetTotalAuraModifierByMiscMask(AuraType.ModAttackerSpellHitChance, (int)schoolMask); + } + + int HitChance = modHitChance * 100; + // Increase hit chance from attacker SPELL_AURA_MOD_SPELL_HIT_CHANCE and attacker ratings + HitChance += (int)(modHitChance * 100.0f); + + if (HitChance < 100) + HitChance = 100; + else if (HitChance > 10000) + HitChance = 10000; + + int tmp = 10000 - HitChance; + + int rand = RandomHelper.IRand(0, 10000); + + if (rand < tmp) + return SpellMissInfo.Miss; + + // Spells with SPELL_ATTR3_IGNORE_HIT_RESULT will additionally fully ignore + // resist and deflect chances + if (spell.HasAttribute(SpellAttr3.IgnoreHitResult)) + return SpellMissInfo.None; + + // Chance resist mechanic (select max value from every mechanic spell effect) + int resist_chance = victim.GetMechanicResistChance(spell) * 100; + tmp += resist_chance; + + // Roll chance + if (rand < tmp) + return SpellMissInfo.Resist; + + // cast by caster in front of victim + if (!victim.HasUnitState(UnitState.Controlled) && (victim.HasInArc(MathFunctions.PI, this) || victim.HasAuraType(AuraType.IgnoreHitDirection))) + { + int deflect_chance = victim.GetTotalAuraModifier(AuraType.DeflectSpells) * 100; + tmp += deflect_chance; + if (rand < tmp) + return SpellMissInfo.Deflect; + } + + return SpellMissInfo.None; + } + + public void CastSpell(SpellCastTargets targets, SpellInfo spellInfo, Dictionary values, TriggerCastFlags triggerFlags = TriggerCastFlags.None, Item castItem = null, AuraEffect triggeredByAura = null, ObjectGuid originalCaster = default(ObjectGuid)) + { + if (spellInfo == null) + { + Log.outError(LogFilter.Spells, "CastSpell: unknown spell by caster: {0}", GetGUID().ToString()); + return; + } + + Spell spell = new Spell(this, spellInfo, triggerFlags, originalCaster); + + if (values != null) + foreach (var pair in values) + spell.SetSpellValue(pair.Key, pair.Value); + + spell.m_CastItem = castItem; + spell.prepare(targets, triggeredByAura); + } + public void CastSpell(Unit victim, uint spellId, bool triggered, Item castItem = null, AuraEffect triggeredByAura = null, ObjectGuid originalCaster = default(ObjectGuid)) + { + CastSpell(victim, spellId, triggered ? TriggerCastFlags.FullMask : TriggerCastFlags.None, castItem, triggeredByAura, originalCaster); + } + public void CastSpell(Unit victim, uint spellId, TriggerCastFlags triggerFlags = TriggerCastFlags.None, Item castItem = null, AuraEffect triggeredByAura = null, ObjectGuid originalCaster = default(ObjectGuid)) + { + SpellInfo spellInfo = Global.SpellMgr.GetSpellInfo(spellId); + if (spellInfo == null) + { + Log.outError(LogFilter.Spells, "CastSpell: unknown spell id {0} by caster: {1}", spellId, GetGUID().ToString()); + return; + } + + CastSpell(victim, spellInfo, triggerFlags, castItem, triggeredByAura, originalCaster); + } + public void CastSpell(Unit victim, SpellInfo spellInfo, bool triggered, Item castItem = null, AuraEffect triggeredByAura = null, ObjectGuid originalCaster = default(ObjectGuid)) + { + CastSpell(victim, spellInfo, triggered ? TriggerCastFlags.FullMask : TriggerCastFlags.None, castItem, triggeredByAura, originalCaster); + } + public void CastSpell(Unit victim, SpellInfo spellInfo, TriggerCastFlags triggerFlags = TriggerCastFlags.None, Item castItem = null, AuraEffect triggeredByAura = null, ObjectGuid originalCaster = default(ObjectGuid)) + { + SpellCastTargets targets = new SpellCastTargets(); + targets.SetUnitTarget(victim); + CastSpell(targets, spellInfo, null, triggerFlags, castItem, triggeredByAura, originalCaster); + } + public void CastSpell(float x, float y, float z, uint spellId, bool triggered, Item castItem = null, AuraEffect triggeredByAura = null, ObjectGuid originalCaster = default(ObjectGuid)) + { + SpellInfo spellInfo = Global.SpellMgr.GetSpellInfo(spellId); + if (spellInfo == null) + { + Log.outError(LogFilter.Unit, "CastSpell: unknown spell id {0} by caster: {1}", spellId, GetGUID().ToString()); + return; + } + SpellCastTargets targets = new SpellCastTargets(); + targets.SetDst(x, y, z, GetOrientation()); + + CastSpell(targets, spellInfo, null, triggered ? TriggerCastFlags.FullMask : TriggerCastFlags.None, castItem, triggeredByAura, originalCaster); + } + public void CastSpell(GameObject go, uint spellId, bool triggered, Item castItem = null, AuraEffect triggeredByAura = null, ObjectGuid originalCaster = default(ObjectGuid)) + { + SpellInfo spellInfo = Global.SpellMgr.GetSpellInfo(spellId); + if (spellInfo == null) + { + Log.outError(LogFilter.Unit, "CastSpell: unknown spell id {0} by caster: {1}", spellId, GetGUID().ToString()); + return; + } + SpellCastTargets targets = new SpellCastTargets(); + targets.SetGOTarget(go); + + CastSpell(targets, spellInfo, null, triggered ? TriggerCastFlags.FullMask : TriggerCastFlags.None, castItem, triggeredByAura, originalCaster); + } + + public void CastCustomSpell(Unit target, uint spellId, int bp0, int bp1, int bp2, bool triggered, Item castItem = null, AuraEffect triggeredByAura = null, ObjectGuid originalCaster = default(ObjectGuid)) + { + Dictionary values = new Dictionary(); + if (bp0 != 0) + values.Add(SpellValueMod.BasePoint0, bp0); + if (bp1 != 0) + values.Add(SpellValueMod.BasePoint1, bp1); + if (bp2 != 0) + values.Add(SpellValueMod.BasePoint2, bp2); + CastCustomSpell(spellId, values, target, triggered ? TriggerCastFlags.FullMask : TriggerCastFlags.None, castItem, triggeredByAura, originalCaster); + } + public void CastCustomSpell(uint spellId, SpellValueMod mod, int value, Unit target, bool triggered, Item castItem = null, AuraEffect triggeredByAura = null, ObjectGuid originalCaster = default(ObjectGuid)) + { + Dictionary values = new Dictionary(); + values.Add(mod, value); + CastCustomSpell(spellId, values, target, triggered ? TriggerCastFlags.FullMask : TriggerCastFlags.None, castItem, triggeredByAura, originalCaster); + } + public void CastCustomSpell(uint spellId, SpellValueMod mod, int value, Unit target = null, TriggerCastFlags triggerFlags = TriggerCastFlags.None, Item castItem = null, AuraEffect triggeredByAura = null, ObjectGuid originalCaster = default(ObjectGuid)) + { + Dictionary values = new Dictionary(); + values.Add(mod, value); + CastCustomSpell(spellId, values, target, triggerFlags, castItem, triggeredByAura, originalCaster); + } + public void CastCustomSpell(uint spellId, Dictionary values, Unit victim = null, TriggerCastFlags triggerFlags = TriggerCastFlags.None, Item castItem = null, AuraEffect triggeredByAura = null, ObjectGuid originalCaster = default(ObjectGuid)) + { + SpellInfo spellInfo = Global.SpellMgr.GetSpellInfo(spellId); + if (spellInfo == null) + { + Log.outError(LogFilter.Unit, "CastSpell: unknown spell id {0} by caster: {1}", spellId, GetGUID().ToString()); + return; + } + SpellCastTargets targets = new SpellCastTargets(); + targets.SetUnitTarget(victim); + + CastSpell(targets, spellInfo, values, triggerFlags, castItem, triggeredByAura, originalCaster); + } + + public void FinishSpell(CurrentSpellTypes spellType, bool ok = true) + { + Spell spell = GetCurrentSpell(spellType); + if (spell == null) + return; + + if (spellType == CurrentSpellTypes.Channeled) + spell.SendChannelUpdate(0); + + spell.finish(ok); + } + + public float CalculateLevelPenalty(SpellInfo spellProto) + { + if (spellProto.SpellLevel <= 0 || spellProto.SpellLevel >= spellProto.MaxLevel) + return 1.0f; + + float LvlPenalty = 0.0f; + + if (spellProto.SpellLevel < 20) + LvlPenalty = (20.0f - spellProto.SpellLevel) * 3.75f; + float LvlFactor = (spellProto.SpellLevel + 6.0f) / getLevel(); + if (LvlFactor > 1.0f) + LvlFactor = 1.0f; + + return MathFunctions.AddPct(ref LvlFactor, -LvlPenalty); + } + + uint GetCastingTimeForBonus(SpellInfo spellProto, DamageEffectType damagetype, uint CastingTime) + { + // Not apply this to creature casted spells with casttime == 0 + if (CastingTime == 0 && IsTypeId(TypeId.Unit) && !IsPet()) + return 3500; + + if (CastingTime > 7000) CastingTime = 7000; + if (CastingTime < 1500) CastingTime = 1500; + + if (damagetype == DamageEffectType.DOT && !spellProto.IsChanneled()) + CastingTime = 3500; + + int overTime = 0; + byte effects = 0; + bool DirectDamage = false; + bool AreaEffect = false; + + foreach (SpellEffectInfo effect in spellProto.GetEffectsForDifficulty(GetMap().GetDifficultyID())) + { + if (effect == null) + continue; + + switch (effect.Effect) + { + case SpellEffectName.SchoolDamage: + case SpellEffectName.PowerDrain: + case SpellEffectName.HealthLeech: + case SpellEffectName.EnvironmentalDamage: + case SpellEffectName.PowerBurn: + case SpellEffectName.Heal: + DirectDamage = true; + break; + case SpellEffectName.ApplyAura: + switch (effect.ApplyAuraName) + { + case AuraType.PeriodicDamage: + case AuraType.PeriodicHeal: + case AuraType.PeriodicLeech: + if (spellProto.GetDuration() != 0) + overTime = spellProto.GetDuration(); + break; + default: + // -5% per additional effect + ++effects; + break; + } + break; + default: + break; + } + + if (effect.IsTargetingArea()) + AreaEffect = true; + } + + // Combined Spells with Both Over Time and Direct Damage + if (overTime > 0 && CastingTime > 0 && DirectDamage) + { + // mainly for DoTs which are 3500 here otherwise + int OriginalCastTime = spellProto.CalcCastTime(); + if (OriginalCastTime > 7000) OriginalCastTime = 7000; + if (OriginalCastTime < 1500) OriginalCastTime = 1500; + // Portion to Over Time + float PtOT = (overTime / 15000.0f) / ((overTime / 15000.0f) + (OriginalCastTime / 3500.0f)); + + if (damagetype == DamageEffectType.DOT) + CastingTime = (uint)(CastingTime * PtOT); + else if (PtOT < 1.0f) + CastingTime = (uint)(CastingTime * (1 - PtOT)); + else + CastingTime = 0; + } + + // Area Effect Spells receive only half of bonus + if (AreaEffect) + CastingTime /= 2; + + // 50% for damage and healing spells for leech spells from damage bonus and 0% from healing + foreach (SpellEffectInfo effect in spellProto.GetEffectsForDifficulty(GetMap().GetDifficultyID())) + { + if (effect != null && (effect.Effect == SpellEffectName.HealthLeech || + (effect.Effect == SpellEffectName.ApplyAura && effect.ApplyAuraName == AuraType.PeriodicLeech))) + { + CastingTime /= 2; + break; + } + } + + // -5% of total per any additional effect + for (byte i = 0; i < effects; ++i) + CastingTime *= (uint)0.95f; + + return CastingTime; + } + + public virtual SpellInfo GetCastSpellInfo(SpellInfo spellInfo) + { + var swaps = GetAuraEffectsByType(AuraType.OverrideActionbarSpells); + var swaps2 = GetAuraEffectsByType(AuraType.OverrideActionbarSpellsTriggered); + if (!swaps2.Empty()) + swaps.AddRange(swaps2); + + foreach (AuraEffect auraEffect in swaps) + { + if (auraEffect.GetMiscValue() == spellInfo.Id || auraEffect.IsAffectingSpell(spellInfo)) + { + SpellInfo newInfo = Global.SpellMgr.GetSpellInfo((uint)auraEffect.GetAmount()); + if (newInfo != null) + return newInfo; + } + } + + return spellInfo; + } + + public uint GetCastSpellXSpellVisualId(SpellInfo spellInfo) + { + var visualOverrides = GetAuraEffectsByType(AuraType.OverrideSpellVisual); + foreach (AuraEffect effect in visualOverrides) + { + if (effect.GetMiscValue() == spellInfo.Id) + { + SpellInfo visualSpell = Global.SpellMgr.GetSpellInfo((uint)effect.GetMiscValueB()); + if (visualSpell != null) + { + spellInfo = visualSpell; + break; + } + } + } + + return spellInfo.GetSpellXSpellVisualId(this); + } + + public SpellHistory GetSpellHistory() { return _spellHistory; } + + public static ProcFlagsExLegacy createProcExtendMask(SpellNonMeleeDamage damageInfo, SpellMissInfo missCondition) + { + ProcFlagsExLegacy procEx = ProcFlagsExLegacy.None; + // Check victim state + if (missCondition != SpellMissInfo.None) + switch (missCondition) + { + case SpellMissInfo.Miss: + procEx |= ProcFlagsExLegacy.Miss; + break; + case SpellMissInfo.Resist: + procEx |= ProcFlagsExLegacy.Resist; + break; + case SpellMissInfo.Dodge: + procEx |= ProcFlagsExLegacy.Dodge; + break; + case SpellMissInfo.Parry: + procEx |= ProcFlagsExLegacy.Parry; + break; + case SpellMissInfo.Block: + procEx |= ProcFlagsExLegacy.Block; + break; + case SpellMissInfo.Evade: + procEx |= ProcFlagsExLegacy.Evade; + break; + case SpellMissInfo.Immune: + procEx |= ProcFlagsExLegacy.Immune; + break; + case SpellMissInfo.Deflect: + procEx |= ProcFlagsExLegacy.Deflect; + break; + case SpellMissInfo.Absorb: + procEx |= ProcFlagsExLegacy.Absorb; + break; + case SpellMissInfo.Reflect: + procEx |= ProcFlagsExLegacy.Reflect; + break; + default: + break; + } + else + { + // On block + if (damageInfo.blocked != 0) + procEx |= ProcFlagsExLegacy.Block; + // On absorb + if (damageInfo.absorb != 0) + procEx |= ProcFlagsExLegacy.Absorb; + // On crit + if (damageInfo.HitInfo.HasAnyFlag(SpellHitType.Crit)) + procEx |= ProcFlagsExLegacy.CriticalHit; + else + procEx |= ProcFlagsExLegacy.NormalHit; + } + return procEx; + } + + public void SetAuraStack(uint spellId, Unit target, uint stack) + { + Aura aura = target.GetAura(spellId, GetGUID()); + if (aura == null) + aura = AddAura(spellId, target); + if (aura != null && stack != 0) + aura.SetStackAmount((byte)stack); + } + + public Spell FindCurrentSpellBySpellId(uint spell_id) + { + foreach (var spell in m_currentSpells.Values) + { + if (spell == null) + continue; + if (spell.m_spellInfo.Id == spell_id) + return spell; + } + return null; + } + + public int GetCurrentSpellCastTime(uint spell_id) + { + Spell spell = FindCurrentSpellBySpellId(spell_id); + if (spell != null) + return spell.GetCastTime(); + return 0; + } + + public bool CanMoveDuringChannel() + { + Spell spell = m_currentSpells.LookupByKey(CurrentSpellTypes.Channeled); + if (spell) + if (spell.getState() != SpellState.Finished) + return spell.GetSpellInfo().HasAttribute(SpellAttr5.CanChannelWhenMoving) && spell.IsChannelActive(); + + return false; + } + + bool HasBreakableByDamageAuraType(AuraType type, uint excludeAura) + { + var auras = GetAuraEffectsByType(type); + foreach (var eff in auras) + if ((excludeAura == 0 || excludeAura != eff.GetSpellInfo().Id) && //Avoid self interrupt of channeled Crowd Control spells like Seduction + eff.GetSpellInfo().AuraInterruptFlags.HasAnyFlag(SpellAuraInterruptFlags.TakeDamage)) + return true; + return false; + } + + public bool HasBreakableByDamageCrowdControlAura(Unit excludeCasterChannel = null) + { + uint excludeAura = 0; + Spell currentChanneledSpell = excludeCasterChannel?.GetCurrentSpell(CurrentSpellTypes.Channeled); + if (currentChanneledSpell != null) + excludeAura = currentChanneledSpell.GetSpellInfo().Id; //Avoid self interrupt of channeled Crowd Control spells like Seduction + + return (HasBreakableByDamageAuraType(AuraType.ModConfuse, excludeAura) + || HasBreakableByDamageAuraType(AuraType.ModFear, excludeAura) + || HasBreakableByDamageAuraType(AuraType.ModStun, excludeAura) + || HasBreakableByDamageAuraType(AuraType.ModRoot, excludeAura) + || HasBreakableByDamageAuraType(AuraType.ModRoot2, excludeAura) + || HasBreakableByDamageAuraType(AuraType.Transform, excludeAura)); + } + + public uint GetDiseasesByCaster(ObjectGuid casterGUID, bool remove = false) + { + AuraType[] diseaseAuraTypes = + { + AuraType.PeriodicDamage, // Frost Fever and Blood Plague + AuraType.Linked, // Crypt Fever and Ebon Plague + AuraType.None + }; + + uint diseases = 0; + foreach (var aura in diseaseAuraTypes) + { + if (aura == AuraType.None) + break; + + for (var i = 0; i < m_modAuras[aura].Count;) + { + var eff = m_modAuras[aura][i]; + // Get auras with disease dispel type by caster + if (eff.GetSpellInfo().Dispel == DispelType.Disease + && eff.GetCasterGUID() == casterGUID) + { + ++diseases; + + if (remove) + { + RemoveAura(eff.GetId(), eff.GetCasterGUID()); + i = 0; + continue; + } + } + i++; + } + } + return diseases; + } + + uint GetDoTsByCaster(ObjectGuid casterGUID) + { + AuraType[] diseaseAuraTypes = + { + AuraType.PeriodicDamage, + AuraType.PeriodicDamagePercent, + AuraType.None + }; + + uint dots = 0; + foreach (var aura in diseaseAuraTypes) + { + if (aura == AuraType.None) + break; + + var auras = GetAuraEffectsByType(aura); + foreach (var eff in auras) + { + // Get auras by caster + if (eff.GetCasterGUID() == casterGUID) + ++dots; + } + } + return dots; + } + + public void SendEnergizeSpellLog(Unit victim, uint spellId, int amount, int overEnergize, PowerType powerType) + { + SpellEnergizeLog data = new SpellEnergizeLog(); + data.CasterGUID = GetGUID(); + data.TargetGUID = victim.GetGUID(); + data.SpellID = spellId; + data.Type = powerType; + data.Amount = amount; + data.OverEnergize = overEnergize; + data.LogData.Initialize(victim); + + SendCombatLogMessage(data); + } + + public void EnergizeBySpell(Unit victim, uint spellId, int damage, PowerType powerType) + { + int gain = victim.ModifyPower(powerType, damage); + int overEnergize = damage - gain; + + SpellInfo spellInfo = Global.SpellMgr.GetSpellInfo(spellId); + victim.getHostileRefManager().threatAssist(this, damage * 0.5f, spellInfo); + + SendEnergizeSpellLog(victim, spellId, damage, overEnergize, powerType); + } + + public void ApplySpellImmune(uint spellId, SpellImmunity op, object type, bool apply) + { + if (apply) + { + m_spellImmune[op].RemoveAll(p => p.spellType == Convert.ToUInt32(type)); + + SpellImmune Immune = new SpellImmune(); + Immune.spellId = spellId; + Immune.spellType = Convert.ToUInt32(type); + m_spellImmune.Add(op, Immune); + } + else + { + foreach (var spell in m_spellImmune[op].ToList()) + { + if (spell.spellId == spellId && spell.spellType == Convert.ToUInt32(type)) + { + m_spellImmune.Remove(op, spell); + break; + } + } + } + } + public virtual bool IsImmunedToSpell(SpellInfo spellInfo) + { + if (spellInfo == null) + return false; + + // Single spell immunity. + var idList = m_spellImmune.LookupByKey(SpellImmunity.Id); + foreach (var immune in idList) + if (immune.spellType == spellInfo.Id) + return true; + + if (spellInfo.HasAttribute(SpellAttr0.UnaffectedByInvulnerability)) + return false; + + if (spellInfo.Dispel != 0) + { + var dispelList = m_spellImmune.LookupByKey(SpellImmunity.Dispel); + foreach (var immune in dispelList) + if (immune.spellType == (int)spellInfo.Dispel) + return true; + } + + // Spells that don't have effectMechanics. + if (spellInfo.Mechanic != 0) + { + var mechanicList = m_spellImmune.LookupByKey(SpellImmunity.Mechanic); + foreach (var immune in mechanicList) + if (immune.spellType == (int)spellInfo.Mechanic) + return true; + } + + bool immuneToAllEffects = true; + foreach (SpellEffectInfo effect in spellInfo.GetEffectsForDifficulty(GetMap().GetDifficultyID())) + { + // State/effect immunities applied by aura expect full spell immunity + // Ignore effects with mechanic, they are supposed to be checked separately + if (effect == null || !effect.IsEffect()) + continue; + + if (!IsImmunedToSpellEffect(spellInfo, effect.EffectIndex)) + { + immuneToAllEffects = false; + break; + } + } + + if (immuneToAllEffects) //Return immune only if the target is immune to all spell effects. + return true; + + if (spellInfo.Id != 42292 && spellInfo.Id != 59752) + { + var schoolList = m_spellImmune.LookupByKey(SpellImmunity.School); + foreach (var immune in schoolList) + { + SpellInfo immuneSpellInfo = Global.SpellMgr.GetSpellInfo(immune.spellId); + if (Convert.ToBoolean(immune.spellType & (uint)spellInfo.GetSchoolMask()) + && !(immuneSpellInfo != null && immuneSpellInfo.IsPositive() && spellInfo.IsPositive()) + && !spellInfo.CanPierceImmuneAura(immuneSpellInfo)) + return true; + } + } + + return false; + } + public uint GetSchoolImmunityMask() + { + uint mask = 0; + var mechanicList = m_spellImmune.LookupByKey(SpellImmunity.School); + foreach (var spell in mechanicList) + mask |= spell.spellType; + + return mask; + } + public uint GetMechanicImmunityMask() + { + uint mask = 0; + var mechanicList = m_spellImmune.LookupByKey(SpellImmunity.Mechanic); + foreach (var spell in mechanicList) + mask |= (uint)(1 << (int)spell.spellType); + + return mask; + } + public virtual bool IsImmunedToSpellEffect(SpellInfo spellInfo, uint index) + { + if (spellInfo == null) + return false; + + SpellEffectInfo effect = spellInfo.GetEffect(GetMap().GetDifficultyID(), index); + if (effect == null || !effect.IsEffect()) + return false; + + // If m_immuneToEffect type contain this effect type, IMMUNE effect. + uint eff = (uint)effect.Effect; + var effectList = m_spellImmune.LookupByKey(SpellImmunity.Effect); + foreach (var immune in effectList) + if (immune.spellType == eff) + return true; + + uint mechanic = (uint)effect.Mechanic; + if (mechanic != 0) + { + var mechanicList = m_spellImmune.LookupByKey(SpellImmunity.Mechanic); + foreach (var immune in mechanicList) + if (immune.spellType == mechanic) + return true; + } + + uint aura = (uint)effect.ApplyAuraName; + if (aura != 0) + { + var list = m_spellImmune.LookupByKey(SpellImmunity.State); + foreach (var immune in list) + if (immune.spellType == aura) + if (!spellInfo.HasAttribute(SpellAttr3.IgnoreHitResult)) + return true; + + // Check for immune to application of harmful magical effects + var immuneAuraApply = GetAuraEffectsByType(AuraType.ModImmuneAuraApplySchool); + foreach (var immune in immuneAuraApply) + if (spellInfo.Dispel == DispelType.Magic && // Magic debuff + Convert.ToBoolean(immune.GetMiscValue() & (uint)spellInfo.GetSchoolMask()) && // Check school + !spellInfo.IsPositiveEffect(index)) // Harmful + return true; + } + + return false; + } + public bool IsImmunedToDamage(SpellSchoolMask shoolMask) + { + // If m_immuneToSchool type contain this school type, IMMUNE damage. + var schoolList = m_spellImmune.LookupByKey(SpellImmunity.School); + foreach (var immune in schoolList) + if (Convert.ToBoolean(immune.spellType & (uint)shoolMask)) + return true; + + // If m_immuneToDamage type contain magic, IMMUNE damage. + var damageList = m_spellImmune.LookupByKey(SpellImmunity.Damage); + foreach (var immune in damageList) + if (Convert.ToBoolean(immune.spellType & (uint)shoolMask)) + return true; + + return false; + } + public bool IsImmunedToDamage(SpellInfo spellInfo) + { + if (spellInfo.HasAttribute(SpellAttr0.UnaffectedByInvulnerability)) + return false; + + uint shoolMask = (uint)spellInfo.GetSchoolMask(); + if (spellInfo.Id != 42292 && spellInfo.Id != 59752) + { + // If m_immuneToSchool type contain this school type, IMMUNE damage. + var schoolList = m_spellImmune.LookupByKey(SpellImmunity.School); + foreach (var immune in schoolList) + if (Convert.ToBoolean(immune.spellType & shoolMask) && !spellInfo.CanPierceImmuneAura(Global.SpellMgr.GetSpellInfo(immune.spellId))) + return true; + } + + // If m_immuneToDamage type contain magic, IMMUNE damage. + var damageList = m_spellImmune.LookupByKey(SpellImmunity.Damage); + foreach (var immune in damageList) + if (Convert.ToBoolean(immune.spellType & shoolMask)) + return true; + + return false; + } + + public void ProcDamageAndSpell(Unit victim, ProcFlags procAttacker, ProcFlags procVictim, ProcFlagsExLegacy procExtra, uint amount, WeaponAttackType attType = WeaponAttackType.BaseAttack, SpellInfo procSpell = null, SpellInfo procAura = null) + { + // Not much to do if no flags are set. + if (procAttacker != 0) + ProcDamageAndSpellFor(false, victim, procAttacker, procExtra, attType, procSpell, amount, procAura); + // Now go on with a victim's events'n'auras + // Not much to do if no flags are set or there is no victim + if (victim != null && victim.IsAlive() && procVictim != 0) + victim.ProcDamageAndSpellFor(true, this, procVictim, procExtra, attType, procSpell, amount, procAura); + } + void ProcDamageAndSpellFor(bool isVictim, Unit target, ProcFlags procFlag, ProcFlagsExLegacy procExtra, WeaponAttackType attType, SpellInfo procSpell, uint damage, SpellInfo procAura = null) + { + // Player is loaded now - do not allow passive spell casts to proc + if (IsTypeId(TypeId.Player) && ToPlayer().GetSession().PlayerLoading()) + return; + // For melee/ranged based attack need update skills and set some Aura states if victim present + if (Convert.ToBoolean(procFlag & ProcFlags.MeleeBasedTriggerMask) && target != null) + { + // If exist crit/parry/dodge/block need update aura state (for victim and attacker) + if (Convert.ToBoolean(procExtra & (ProcFlagsExLegacy.CriticalHit | ProcFlagsExLegacy.Parry | ProcFlagsExLegacy.Dodge | ProcFlagsExLegacy.Block))) + { + // for victim + if (isVictim) + { + // if victim and dodge attack + if (Convert.ToBoolean(procExtra & ProcFlagsExLegacy.Dodge)) + { + // Update AURA_STATE on dodge + if (GetClass() != Class.Rogue) // skip Rogue Riposte + { + ModifyAuraState(AuraStateType.Defense, true); + StartReactiveTimer(ReactiveType.Defense); + } + } + // if victim and parry attack + if (Convert.ToBoolean(procExtra & ProcFlagsExLegacy.Parry)) + { + // For Hunters only Counterattack + if (GetClass() == Class.Hunter) + { + ModifyAuraState(AuraStateType.HunterParry, true); + StartReactiveTimer(ReactiveType.HunterParry); + } + else + { + ModifyAuraState(AuraStateType.Defense, true); + StartReactiveTimer(ReactiveType.Defense); + } + } + // if and victim block attack + if (Convert.ToBoolean(procExtra & ProcFlagsExLegacy.Block)) + { + ModifyAuraState(AuraStateType.Defense, true); + StartReactiveTimer(ReactiveType.Defense); + } + } + else // For attacker + { + // Overpower on victim dodge + if (Convert.ToBoolean(procExtra & ProcFlagsExLegacy.Dodge) && IsTypeId(TypeId.Player) && GetClass() == Class.Warrior) + { + ToPlayer().AddComboPoints(1); + StartReactiveTimer(ReactiveType.OverPower); + } + } + } + } + + Unit actor = isVictim ? target : this; + Unit actionTarget = !isVictim ? target : this; + + DamageInfo damageInfo = new DamageInfo(actor, actionTarget, damage, procSpell, (procSpell != null ? procSpell.SchoolMask : SpellSchoolMask.Normal), DamageEffectType.Direct, attType); + HealInfo healInfo = new HealInfo(actor, actionTarget, damage, procSpell, (procSpell != null ? procSpell.SchoolMask : SpellSchoolMask.Normal)); + ProcEventInfo eventInfo = new ProcEventInfo(actor, actionTarget, target, (uint)procFlag, 0, 0, (uint)procExtra, null, damageInfo, healInfo); + + var now = DateTime.Now; + List procTriggered = new List(); + // Fill procTriggered list + foreach (var pair in GetAppliedAuras()) + { + // Do not allow auras to proc from effect triggered by itself + if (procAura != null && procAura.Id == pair.Key || pair.Value == null) + continue; + + if (pair.Value.GetBase().IsProcOnCooldown(now)) + continue; + + ProcTriggeredData triggerData = new ProcTriggeredData(pair.Value.GetBase()); + // Defensive procs are active on absorbs (so absorption effects are not a hindrance) + bool active = damage != 0 || (Convert.ToBoolean(procExtra & ProcFlagsExLegacy.Block) && isVictim); + if (isVictim) + procExtra &= ~ProcFlagsExLegacy.InternalReqFamily; + + SpellInfo spellProto = pair.Value.GetBase().GetSpellInfo(); + + // only auras that has triggered spell should proc from fully absorbed damage + if (procExtra.HasAnyFlag(ProcFlagsExLegacy.Absorb) && isVictim && damage != 0) + { + foreach (SpellEffectInfo effect in pair.Value.GetBase().GetSpellEffectInfos()) + { + if (effect != null && effect.TriggerSpell != 0) + { + active = true; + break; + } + } + } + + if (!IsTriggeredAtSpellProcEvent(target, triggerData.aura, procSpell, procFlag, procExtra, attType, isVictim, active, triggerData.spellProcEvent)) + continue; + + // do checks using conditions table + if (!Global.ConditionMgr.IsObjectMeetingNotGroupedConditions(ConditionSourceType.SpellProc, spellProto.Id, eventInfo.GetActor(), eventInfo.GetActionTarget())) + continue; + + // AuraScript Hook + if (!triggerData.aura.CallScriptCheckProcHandlers(pair.Value, eventInfo)) + continue; + + bool procSuccess = RollProcResult(target, triggerData.aura, attType, isVictim, triggerData.spellProcEvent); + triggerData.aura.SetLastProcAttemptTime(now); + if (!procSuccess) + continue; + + bool triggeredCanProcAura = true; + // Additional checks for triggered spells (ignore trap casts) + if (procExtra.HasAnyFlag(ProcFlagsExLegacy.InternalTriggered) && !procFlag.HasAnyFlag(ProcFlags.DoneTrapActivation)) + { + if (!spellProto.HasAttribute(SpellAttr3.CanProcWithTriggered)) + triggeredCanProcAura = false; + } + + foreach (AuraEffect aurEff in pair.Value.GetBase().GetAuraEffects()) + { + if (aurEff != null) + { + // Skip this auras + if (isNonTriggerAura(aurEff.GetAuraType())) + continue; + // If not trigger by default and spellProcEvent == null - skip + if (!isTriggerAura(aurEff.GetAuraType()) && triggerData.spellProcEvent == null) + continue; + // Some spells must always trigger + if (triggeredCanProcAura || isAlwaysTriggeredAura(aurEff.GetAuraType())) + triggerData.effMask |= (uint)(1 << aurEff.GetEffIndex()); + } + } + if (triggerData.effMask != 0) + procTriggered.Add(triggerData); + } + + // Nothing found + if (procTriggered.Empty()) + return; + + // Note: must SetCantProc(false) before return + if (procExtra.HasAnyFlag(ProcFlagsExLegacy.InternalTriggered | ProcFlagsExLegacy.InternalCantProc)) + SetCantProc(true); + + // Handle effects proceed this time + foreach (var proc in procTriggered) + { + // look for aura in auras list, it may be removed while proc event processing + if (proc.aura.IsRemoved()) + continue; + + bool useCharges = proc.aura.IsUsingCharges(); + // no more charges to use, prevent proc + if (useCharges && proc.aura.GetCharges() == 0) + continue; + + bool takeCharges = false; + SpellInfo spellInfo = proc.aura.GetSpellInfo(); + uint Id = proc.aura.GetId(); + + AuraApplication aurApp = proc.aura.GetApplicationOfTarget(GetGUID()); + + bool prepare = proc.aura.CallScriptPrepareProcHandlers(aurApp, eventInfo); + + TimeSpan cooldown = TimeSpan.Zero; + if (prepare) + { + cooldown = TimeSpan.FromMilliseconds(spellInfo.ProcCooldown); + if (proc.spellProcEvent != null && proc.spellProcEvent.cooldown != 0) + cooldown = TimeSpan.FromSeconds(proc.spellProcEvent.cooldown); + } + + proc.aura.SetLastProcSuccessTime(now); + + // Note: must SetCantProc(false) before return + if (spellInfo.HasAttribute(SpellAttr3.DisableProc)) + SetCantProc(true); + + bool handled = proc.aura.CallScriptProcHandlers(aurApp, eventInfo); + + // "handled" is needed as long as proc can be handled in multiple places + if (!handled) + { + Log.outDebug(LogFilter.Spells, "ProcDamageAndSpell: casting spell {0} (triggered with value by {1} aura of spell {2})", spellInfo.Id, (isVictim ? "a victim's" : "an attacker's"), Id); + takeCharges = true; + } + + if (!handled) + { + for (byte effIndex = 0; effIndex < SpellConst.MaxEffects; ++effIndex) + { + if (!Convert.ToBoolean(proc.effMask & (1 << effIndex))) + continue; + + AuraEffect triggeredByAura = proc.aura.GetEffect(effIndex); + Contract.Assert(triggeredByAura != null); + + bool prevented = proc.aura.CallScriptEffectProcHandlers(triggeredByAura, aurApp, eventInfo); + if (prevented) + { + takeCharges = true; + continue; + } + + switch (triggeredByAura.GetAuraType()) + { + case AuraType.ProcTriggerSpell: + { + Log.outDebug(LogFilter.Spells, "ProcDamageAndSpell: casting spell {0} (triggered by {1} aura of spell {2})", spellInfo.Id, (isVictim ? "a victim's" : "an attacker's"), triggeredByAura.GetId()); + // Don`t drop charge or add cooldown for not started trigger + if (HandleProcTriggerSpell(target, damage, triggeredByAura, procSpell, procFlag, procExtra)) + takeCharges = true; + break; + } + case AuraType.ProcTriggerDamage: + { + // target has to be valid + if (eventInfo.GetProcTarget() == null) + break; + + triggeredByAura.HandleProcTriggerDamageAuraProc(aurApp, eventInfo); // this function is part of the new proc system + takeCharges = true; + break; + } + case AuraType.ManaShield: + case AuraType.Dummy: + { + Log.outDebug(LogFilter.Spells, "ProcDamageAndSpell: casting spell id {0} (triggered by {1} dummy aura of spell {2})", spellInfo.Id, (isVictim ? "a victim's" : "an attacker's"), triggeredByAura.GetId()); + if (HandleDummyAuraProc(target, damage, triggeredByAura, procSpell, procFlag, procExtra)) + takeCharges = true; + break; + } + case AuraType.ProcOnPowerAmount2: + case AuraType.ProcOnPowerAmount: + { + triggeredByAura.HandleProcTriggerSpellOnPowerAmountAuraProc(aurApp, eventInfo); + takeCharges = true; + break; + } + case AuraType.ObsModPower: + case AuraType.ModSpellCritChance: + case AuraType.ModDamagePercentTaken: + case AuraType.ModMeleeHaste: + case AuraType.ModMeleeHaste3: + Log.outDebug(LogFilter.Spells, "ProcDamageAndSpell: casting spell id {0} (triggered by {1} aura of spell {2})", spellInfo.Id, isVictim ? "a victim's" : "an attacker's", triggeredByAura.GetId()); + takeCharges = true; + break; + case AuraType.OverrideClassScripts: + { + Log.outDebug(LogFilter.Spells, "ProcDamageAndSpell: casting spell id {0} (triggered by {1} aura of spell {2})", spellInfo.Id, (isVictim ? "a victim's" : "an attacker's"), triggeredByAura.GetId()); + if (HandleOverrideClassScriptAuraProc(target, damage, triggeredByAura, procSpell)) + takeCharges = true; + break; + } + case AuraType.RaidProcFromChargeWithValue: + { + Log.outDebug(LogFilter.Spells, "ProcDamageAndSpell: casting mending (triggered by {0} dummy aura of spell {1})", + (isVictim ? "a victim's" : "an attacker's"), triggeredByAura.GetId()); + + HandleAuraRaidProcFromChargeWithValue(triggeredByAura); + takeCharges = true; + break; + } + case AuraType.RaidProcFromCharge: + { + Log.outDebug(LogFilter.Spells, "ProcDamageAndSpell: casting mending (triggered by {0} dummy aura of spell {1})", + (isVictim ? "a victim's" : "an attacker's"), triggeredByAura.GetId()); + + HandleAuraRaidProcFromCharge(triggeredByAura); + takeCharges = true; + break; + } + case AuraType.ProcTriggerSpellWithValue: + { + Log.outDebug(LogFilter.Spells, "ProcDamageAndSpell: casting spell {0} (triggered with value by {1} aura of spell {2})", spellInfo.Id, (isVictim ? "a victim's" : "an attacker's"), triggeredByAura.GetId()); + + if (HandleProcTriggerSpell(target, damage, triggeredByAura, procSpell, procFlag, procExtra)) + takeCharges = true; + break; + } + case AuraType.ModCastingSpeedNotStack: + // Skip melee hits or instant cast spells + if (procSpell != null && procSpell.CalcCastTime() != 0) + takeCharges = true; + break; + case AuraType.ReflectSpellsSchool: + // Skip Melee hits and spells ws wrong school + if (procSpell != null && Convert.ToBoolean(triggeredByAura.GetMiscValue() & (int)procSpell.SchoolMask)) // School check + takeCharges = true; + break; + case AuraType.SpellMagnet: + // Skip Melee hits and targets with magnet aura + if (procSpell != null && (triggeredByAura.GetBase().GetUnitOwner().ToUnit() == ToUnit())) // Magnet + takeCharges = true; + break; + case AuraType.ModPowerCostSchoolPct: + case AuraType.ModPowerCostSchool: + // Skip melee hits and spells ws wrong school or zero cost + if (procSpell != null && Convert.ToBoolean(triggeredByAura.GetMiscValue() & (int)procSpell.SchoolMask)) // School check + { + var costs = procSpell.CalcPowerCost(this, procSpell.GetSchoolMask()); + var m = costs.Find(cost => cost.Amount > 0); + if (m != null) + takeCharges = true; + } + break; + case AuraType.MechanicImmunity: + // Compare mechanic + if (procSpell != null && procSpell.Mechanic == (Mechanics)triggeredByAura.GetMiscValue()) + takeCharges = true; + break; + case AuraType.ModMechanicResistance: + // Compare mechanic + if (procSpell != null && procSpell.Mechanic == (Mechanics)triggeredByAura.GetMiscValue()) + takeCharges = true; + break; + case AuraType.ModSpellDamageFromCaster: + // Compare casters + if (triggeredByAura.GetCasterGUID() == target.GetGUID()) + takeCharges = true; + break; + // CC Auras which use their amount amount to drop + // Are there any more auras which need this? + case AuraType.ModConfuse: + case AuraType.ModFear: + case AuraType.ModStun: + case AuraType.ModRoot: + case AuraType.Transform: + case AuraType.ModRoot2: + { + // chargeable mods are breaking on hit + if (useCharges) + takeCharges = true; + else + { + // Spell own direct damage at apply wont break the CC + if (procSpell != null && (procSpell.Id == triggeredByAura.GetId())) + { + Aura aura = triggeredByAura.GetBase(); + // called from spellcast, should not have ticked yet + if (aura.GetDuration() == aura.GetMaxDuration()) + break; + } + int damageLeft = triggeredByAura.GetAmount(); + // No damage left + if (damageLeft < damage) + proc.aura.Remove(); + else + triggeredByAura.SetAmount((int)(damageLeft - damage)); + } + break; + } + default: + // nothing do, just charges counter + takeCharges = true; + break; + } + proc.aura.CallScriptAfterEffectProcHandlers(triggeredByAura, aurApp, eventInfo); + } + } + + if (prepare && takeCharges && cooldown != TimeSpan.Zero) + proc.aura.AddProcCooldown(now + cooldown); + + // Remove charge (aura can be removed by triggers) + if (prepare && useCharges && takeCharges) + { + // Set charge drop delay (only for missiles) + if (procExtra.HasAnyFlag(ProcFlagsExLegacy.Reflect) && target && procSpell != null && procSpell.Speed > 0.0f) + { + // Set up missile speed based delay (from Spell.cpp: Spell.AddUnitTarget().L2237) + uint delay = (uint)Math.Floor(Math.Max(target.GetDistance(this), 5.0f) / procSpell.Speed * 1000.0f); + // Schedule charge drop + proc.aura.DropChargeDelayed(delay); + } + else + proc.aura.DropCharge(); + } + + proc.aura.CallScriptAfterProcHandlers(aurApp, eventInfo); + + if (spellInfo.HasAttribute(SpellAttr3.DisableProc)) + SetCantProc(false); + } + + // Cleanup proc requirements + if (procExtra.HasAnyFlag(ProcFlagsExLegacy.InternalTriggered | ProcFlagsExLegacy.InternalCantProc)) + SetCantProc(false); + } + + void SetCantProc(bool apply) + { + if (apply) + ++m_procDeep; + else + { + Contract.Assert(m_procDeep != 0); + --m_procDeep; + } + } + + public void CastStop(uint except_spellid = 0) + { + for (var i = CurrentSpellTypes.Generic; i < CurrentSpellTypes.Max; i++) + if (GetCurrentSpell(i) != null && GetCurrentSpell(i).m_spellInfo.Id != except_spellid) + InterruptSpell(i, false); + } + public void ModSpellCastTime(SpellInfo spellInfo, ref int castTime, Spell spell = null) + { + if (spellInfo == null || castTime < 0) + return; + + // called from caster + Player modOwner = GetSpellModOwner(); + if (modOwner != null) + modOwner.ApplySpellMod(spellInfo.Id, SpellModOp.CastingTime, ref castTime, spell); + + if (!(spellInfo.HasAttribute(SpellAttr0.Ability | SpellAttr0.Tradespell) || spellInfo.HasAttribute(SpellAttr3.NoDoneBonus)) + && (IsTypeId(TypeId.Player) && spellInfo.SpellFamilyName != 0) || IsTypeId(TypeId.Unit)) + castTime = (int)(castTime * GetFloatValue(UnitFields.ModCastSpeed)); + else if (spellInfo.HasAttribute(SpellAttr0.ReqAmmo) && !spellInfo.HasAttribute(SpellAttr2.AutorepeatFlag)) + castTime = (int)(castTime * m_modAttackSpeedPct[(int)WeaponAttackType.RangedAttack]); + else if (Global.SpellMgr.IsPartOfSkillLine(SkillType.Cooking, spellInfo.Id) && HasAura(67556)) // cooking with Chef Hat. + castTime = 500; + } + public void ModSpellDurationTime(SpellInfo spellInfo, ref int duration, Spell spell = null) + { + if (spellInfo == null || duration < 0) + return; + + if (spellInfo.IsChanneled() && !spellInfo.HasAttribute(SpellAttr5.HasteAffectDuration)) + return; + + // called from caster + Player modOwner = GetSpellModOwner(); + if (modOwner) + modOwner.ApplySpellMod(spellInfo.Id, SpellModOp.CastingTime, ref duration, spell); + + if (!(spellInfo.HasAttribute(SpellAttr0.Ability) || spellInfo.HasAttribute(SpellAttr0.Tradespell) || spellInfo.HasAttribute(SpellAttr3.NoDoneBonus)) && + (IsTypeId(TypeId.Player) && spellInfo.SpellFamilyName != 0) || IsTypeId(TypeId.Unit)) + duration = (int)(duration * GetFloatValue(UnitFields.ModCastSpeed)); + else if (spellInfo.HasAttribute(SpellAttr0.ReqAmmo) && !spellInfo.HasAttribute(SpellAttr2.AutorepeatFlag)) + duration = (int)(duration * m_modAttackSpeedPct[(int)WeaponAttackType.RangedAttack]); + } + public float ApplyEffectModifiers(SpellInfo spellProto, uint effect_index, float value) + { + Player modOwner = GetSpellModOwner(); + if (modOwner != null) + { + modOwner.ApplySpellMod(spellProto.Id, SpellModOp.AllEffects, ref value); + switch (effect_index) + { + case 0: + modOwner.ApplySpellMod(spellProto.Id, SpellModOp.Effect1, ref value); + break; + case 1: + modOwner.ApplySpellMod(spellProto.Id, SpellModOp.Effect2, ref value); + break; + case 2: + modOwner.ApplySpellMod(spellProto.Id, SpellModOp.Effect3, ref value); + break; + case 3: + modOwner.ApplySpellMod(spellProto.Id, SpellModOp.Effect4, ref value); + break; + case 4: + modOwner.ApplySpellMod(spellProto.Id, SpellModOp.Effect5, ref value); + break; + } + } + return value; + } + + public ushort GetMaxSkillValueForLevel(Unit target = null) + { + return (ushort)(target != null ? GetLevelForTarget(target) : getLevel() * 5); + } + public Player GetSpellModOwner() + { + if (IsTypeId(TypeId.Player)) + return ToPlayer(); + if (IsPet() || IsTotem()) + { + Unit owner = GetOwner(); + if (owner != null && owner.IsTypeId(TypeId.Player)) + return owner.ToPlayer(); + } + return null; + } + + public Spell GetCurrentSpell(CurrentSpellTypes spellType) + { + return m_currentSpells.LookupByKey(spellType); + } + public void SetCurrentCastSpell(Spell pSpell) + { + Contract.Assert(pSpell != null); // NULL may be never passed here, use InterruptSpell or InterruptNonMeleeSpells + + CurrentSpellTypes CSpellType = pSpell.GetCurrentContainer(); + + if (pSpell == GetCurrentSpell(CSpellType)) // avoid breaking self + return; + + // break same type spell if it is not delayed + InterruptSpell(CSpellType, false); + + // special breakage effects: + switch (CSpellType) + { + case CurrentSpellTypes.Generic: + { + // generic spells always break channeled not delayed spells + InterruptSpell(CurrentSpellTypes.Channeled, false); + + // autorepeat breaking + if (GetCurrentSpell(CurrentSpellTypes.AutoRepeat) != null) + { + // break autorepeat if not Auto Shot + if (m_currentSpells[CurrentSpellTypes.AutoRepeat].m_spellInfo.Id != 75) + InterruptSpell(CurrentSpellTypes.AutoRepeat); + m_AutoRepeatFirstCast = true; + } + if (pSpell.m_spellInfo.CalcCastTime(getLevel()) > 0) + AddUnitState(UnitState.Casting); + + break; + } + case CurrentSpellTypes.Channeled: + { + // channel spells always break generic non-delayed and any channeled spells + InterruptSpell(CurrentSpellTypes.Generic, false); + InterruptSpell(CurrentSpellTypes.Channeled); + + // it also does break autorepeat if not Auto Shot + if (GetCurrentSpell(CurrentSpellTypes.AutoRepeat) != null && + m_currentSpells[CurrentSpellTypes.AutoRepeat].m_spellInfo.Id != 75) + InterruptSpell(CurrentSpellTypes.AutoRepeat); + AddUnitState(UnitState.Casting); + + break; + } + case CurrentSpellTypes.AutoRepeat: + { + // only Auto Shoot does not break anything + if (pSpell.m_spellInfo.Id != 75) + { + // generic autorepeats break generic non-delayed and channeled non-delayed spells + InterruptSpell(CurrentSpellTypes.Generic, false); + InterruptSpell(CurrentSpellTypes.Channeled, false); + } + // special action: set first cast flag + m_AutoRepeatFirstCast = true; + + break; + } + default: + break; // other spell types don't break anything now + } + + // current spell (if it is still here) may be safely deleted now + if (GetCurrentSpell(CSpellType) != null) + m_currentSpells[CSpellType].SetReferencedFromCurrent(false); + + // set new current spell + m_currentSpells[CSpellType] = pSpell; + pSpell.SetReferencedFromCurrent(true); + + pSpell.m_selfContainer = m_currentSpells[pSpell.GetCurrentContainer()]; + } + + public bool IsNonMeleeSpellCast(bool withDelayed, bool skipChanneled = false, bool skipAutorepeat = false, bool isAutoshoot = false, bool skipInstant = true) + { + // We don't do loop here to explicitly show that melee spell is excluded. + // Maybe later some special spells will be excluded too. + + // generic spells are cast when they are not finished and not delayed + var currentSpell = GetCurrentSpell(CurrentSpellTypes.Generic); + if (currentSpell && + (currentSpell.getState() != SpellState.Finished) && + (withDelayed || currentSpell.getState() != SpellState.Delayed)) + { + if (!skipInstant || currentSpell.GetCastTime() != 0) + { + if (!isAutoshoot || !currentSpell.m_spellInfo.HasAttribute(SpellAttr2.NotResetAutoActions)) + return true; + } + } + currentSpell = GetCurrentSpell(CurrentSpellTypes.Channeled); + // channeled spells may be delayed, but they are still considered cast + if (!skipChanneled && currentSpell && + (currentSpell.getState() != SpellState.Finished)) + { + if (!isAutoshoot || !currentSpell.m_spellInfo.HasAttribute(SpellAttr2.NotResetAutoActions)) + return true; + } + currentSpell = GetCurrentSpell(CurrentSpellTypes.AutoRepeat); + // autorepeat spells may be finished or delayed, but they are still considered cast + if (!skipAutorepeat && currentSpell) + return true; + + return false; + } + + public uint SpellCriticalDamageBonus(SpellInfo spellProto, uint damage, Unit victim = null) + { + // Calculate critical bonus + int crit_bonus = (int)damage; + float crit_mod = 0.0f; + + switch (spellProto.DmgClass) + { + case SpellDmgClass.Melee: // for melee based spells is 100% + case SpellDmgClass.Ranged: + // @todo write here full calculation for melee/ranged spells + crit_bonus += (int)damage; + break; + default: + crit_bonus += (int)damage / 2; // for spells is 50% + break; + } + + crit_mod += (GetTotalAuraMultiplierByMiscMask(AuraType.ModCritDamageBonus, (uint)spellProto.GetSchoolMask()) - 1.0f) * 100; + + if (crit_bonus != 0) + MathFunctions.AddPct(ref crit_bonus, (int)crit_mod); + + crit_bonus -= (int)damage; + + if (damage > crit_bonus) + { + // adds additional damage to critBonus (from talents) + Player modOwner = GetSpellModOwner(); + if (modOwner != null) + modOwner.ApplySpellMod(spellProto.Id, SpellModOp.CritDamageBonus, ref crit_bonus); + } + + crit_bonus += (int)damage; + + return (uint)crit_bonus; + } + + bool isSpellBlocked(Unit victim, SpellInfo spellProto, WeaponAttackType attackType = WeaponAttackType.BaseAttack) + { + // These spells can't be blocked + if (spellProto != null && spellProto.HasAttribute(SpellAttr0.ImpossibleDodgeParryBlock)) + return false; + + // Can't block when casting/controlled + if (victim.IsNonMeleeSpellCast(false) || victim.HasUnitState(UnitState.Controlled)) + return false; + + if (victim.HasAuraType(AuraType.IgnoreHitDirection) || victim.HasInArc(MathFunctions.PI, this)) + { + // Check creatures flags_extra for disable block + if (victim.IsTypeId(TypeId.Unit) && + victim.ToCreature().GetCreatureTemplate().FlagsExtra.HasAnyFlag(CreatureFlagsExtra.NoBlock)) + return false; + + float blockChance = GetUnitBlockChance(attackType, victim); + if (RandomHelper.randChance(blockChance)) + return true; + } + return false; + } + + public void _DeleteRemovedAuras() + { + while (!m_removedAuras.Empty()) + { + m_removedAuras.Remove(m_removedAuras.First()); + } + } + + bool IsTriggeredAtSpellProcEvent(Unit victim, Aura aura, SpellInfo procSpell, ProcFlags procFlag, ProcFlagsExLegacy procExtra, WeaponAttackType attType, bool isVictim, bool active, SpellProcEventEntry spellProcEvent) + { + SpellInfo spellInfo = aura.GetSpellInfo(); + + // let the aura be handled by new proc system if it has new entry + if (Global.SpellMgr.GetSpellProcEntry(spellInfo.Id) != null) + return false; + + // Get proc Event Entry + spellProcEvent = Global.SpellMgr.GetSpellProcEvent(spellInfo.Id); + + // Get EventProcFlag + ProcFlags EventProcFlag; + if (spellProcEvent != null && spellProcEvent.procFlags != 0) // if exist get custom spellProcEvent.procFlags + EventProcFlag = (ProcFlags)spellProcEvent.procFlags; + else + EventProcFlag = spellInfo.ProcFlags; // else get from spell proto + // Continue if no trigger exist + if (EventProcFlag == 0) + return false; + + // Check spellProcEvent data requirements + if (!Global.SpellMgr.IsSpellProcEventCanTriggeredBy(spellInfo, spellProcEvent, EventProcFlag, procSpell, procFlag, procExtra, active)) + return false; + // In most cases req get honor or XP from kill + if (EventProcFlag.HasAnyFlag(ProcFlags.Kill) && IsTypeId(TypeId.Player)) + { + bool allow = false; + + if (victim != null) + allow = ToPlayer().isHonorOrXPTarget(victim); + + // Shadow Word: Death - can trigger from every kill + if (aura.GetId() == 32409) + allow = true; + if (!allow) + return false; + } + // Aura added by spell can`t trigger from self (prevent drop charges/do triggers) + // But except periodic and kill triggers (can triggered from self) + if (procSpell != null && procSpell.Id == spellInfo.Id + && !spellInfo.ProcFlags.HasAnyFlag(ProcFlags.TakenPeriodic | ProcFlags.Kill)) + return false; + + // Check if current equipment allows aura to proc + if (!isVictim && IsTypeId(TypeId.Player)) + { + Player player = ToPlayer(); + if (spellInfo.EquippedItemClass == ItemClass.Weapon) + { + Item item = null; + if (attType == WeaponAttackType.BaseAttack || attType == WeaponAttackType.RangedAttack) + item = player.GetUseableItemByPos(InventorySlots.Bag0, EquipmentSlot.MainHand); + else if (attType == WeaponAttackType.OffAttack) + item = player.GetUseableItemByPos(InventorySlots.Bag0, EquipmentSlot.OffHand); + + if (player.IsInFeralForm()) + return false; + + if (item == null || item.IsBroken() || item.GetTemplate().GetClass() != ItemClass.Weapon || !Convert.ToBoolean((1 << (int)item.GetTemplate().GetSubClass()) & spellInfo.EquippedItemSubClassMask)) + return false; + } + else if (spellInfo.EquippedItemClass == ItemClass.Armor) + { + // Check if player is wearing shield + Item item = player.GetUseableItemByPos(InventorySlots.Bag0, EquipmentSlot.OffHand); + if (item == null || item.IsBroken() || item.GetTemplate().GetClass() != ItemClass.Armor || !Convert.ToBoolean((1 << (int)item.GetTemplate().GetSubClass()) & spellInfo.EquippedItemSubClassMask)) + return false; + } + } + + return true; + } + + bool RollProcResult(Unit victim, Aura aura, WeaponAttackType attType, bool isVictim, SpellProcEventEntry spellProcEvent) + { + SpellInfo spellInfo = aura.GetSpellInfo(); + // Get chance from spell + float chance = spellInfo.ProcChance; + // If in spellProcEvent exist custom chance, chance = spellProcEvent.customChance; + if (spellProcEvent != null && spellProcEvent.customChance != 0.0f) + chance = spellProcEvent.customChance; + // If PPM exist calculate chance from PPM + if (spellProcEvent != null && spellProcEvent.ppmRate != 0) + { + if (!isVictim) + { + uint WeaponSpeed = GetBaseAttackTime(attType); + chance = GetPPMProcChance(WeaponSpeed, spellProcEvent.ppmRate, spellInfo); + } + else + { + uint WeaponSpeed = victim.GetBaseAttackTime(attType); + chance = victim.GetPPMProcChance(WeaponSpeed, spellProcEvent.ppmRate, spellInfo); + } + } + + if (spellInfo.ProcBasePPM > 0.0f) + chance = aura.CalcPPMProcChance(isVictim ? victim : this); + + // Apply chance modifer aura + Player modOwner = GetSpellModOwner(); + if (modOwner != null) + modOwner.ApplySpellMod(spellInfo.Id, SpellModOp.ChanceOfSuccess, ref chance); + + return RandomHelper.randChance(chance); + } + + uint getTransForm() { return m_transform; } + + public bool HasStealthAura() { return HasAuraType(AuraType.ModStealth); } + public bool HasInvisibilityAura() { return HasAuraType(AuraType.ModInvisibility); } + public bool isFeared() { return HasAuraType(AuraType.ModFear); } + public bool isFrozen() { return HasAuraState(AuraStateType.Frozen); } + public bool IsPolymorphed() + { + uint transformId = getTransForm(); + if (transformId == 0) + return false; + + SpellInfo spellInfo = Global.SpellMgr.GetSpellInfo(transformId); + if (spellInfo == null) + return false; + + return spellInfo.GetSpellSpecific() == SpellSpecificType.MagePolymorph; + } + + public int HealBySpell(Unit victim, SpellInfo spellInfo, uint addHealth, bool critical = false) + { + uint absorb = 0; + // calculate heal absorb and reduce healing + CalcHealAbsorb(victim, spellInfo, ref addHealth, ref absorb); + + int gain = DealHeal(victim, addHealth); + SendHealSpellLog(victim, spellInfo.Id, addHealth, (uint)(addHealth - gain), absorb, critical); + return gain; + } + public int DealHeal(Unit victim, uint addhealth) + { + int gain = 0; + + if (victim.IsAIEnabled) + victim.GetAI().HealReceived(this, addhealth); + + if (IsAIEnabled) + GetAI().HealDone(victim, addhealth); + + if (addhealth != 0) + gain = (int)victim.ModifyHealth(addhealth); + + // Hook for OnHeal Event + uint tempGain = (uint)gain; + Global.ScriptMgr.OnHeal(this, victim, ref tempGain); + gain = (int)tempGain; + + Unit unit = this; + + if (IsTypeId(TypeId.Unit) && IsTotem()) + unit = GetOwner(); + + Player player = unit.ToPlayer(); + if (player != null) + { + Battleground bg = player.GetBattleground(); + if (bg) + bg.UpdatePlayerScore(player, ScoreType.HealingDone, (uint)gain); + + // use the actual gain, as the overheal shall not be counted, skip gain 0 (it ignored anyway in to criteria) + if (gain != 0) + player.UpdateCriteria(CriteriaTypes.HealingDone, (uint)gain, 0, 0, victim); + + player.UpdateCriteria(CriteriaTypes.HighestHealCasted, addhealth); + } + + if ((player = victim.ToPlayer()) != null) + { + player.UpdateCriteria(CriteriaTypes.TotalHealingReceived, (uint)gain); + player.UpdateCriteria(CriteriaTypes.HighestHealingReceived, addhealth); + } + + return gain; + } + + void SendHealSpellLog(Unit victim, uint spellID, uint health, uint overHeal, uint absorbed, bool crit) + { + SpellHealLog spellHealLog = new SpellHealLog(); + + spellHealLog.TargetGUID = victim.GetGUID(); + spellHealLog.CasterGUID = GetGUID(); + + spellHealLog.SpellID = spellID; + spellHealLog.Health = health; + spellHealLog.OverHeal = overHeal; + spellHealLog.Absorbed = absorbed; + + spellHealLog.Crit = crit; + + /// @todo: 6.x Has to be implemented + /* + spellHealLog.WriteBit("Multistrike"); + + var hasCritRollMade = spellHealLog.WriteBit("HasCritRollMade"); + var hasCritRollNeeded = spellHealLog.WriteBit("HasCritRollNeeded"); + var hasLogData = spellHealLog.WriteBit("HasLogData"); + + if (hasCritRollMade) + packet.ReadSingle("CritRollMade"); + + if (hasCritRollNeeded) + packet.ReadSingle("CritRollNeeded"); + + if (hasLogData) + SpellParsers.ReadSpellCastLogData(packet); + */ + spellHealLog.LogData.Initialize(victim); + SendCombatLogMessage(spellHealLog); + } + + bool HandleDummyAuraProc(Unit victim, uint damage, AuraEffect triggeredByAura, SpellInfo procSpell, ProcFlags procFlag, ProcFlagsExLegacy procEx) + { + SpellInfo dummySpell = triggeredByAura.GetSpellInfo(); + int triggerAmount = triggeredByAura.GetAmount(); + + Item castItem = !triggeredByAura.GetBase().GetCastItemGUID().IsEmpty() && IsTypeId(TypeId.Player) + ? ToPlayer().GetItemByGuid(triggeredByAura.GetBase().GetCastItemGUID()) : null; + + Unit target = victim; + uint triggered_spell_id = triggeredByAura.GetSpellEffectInfo().TriggerSpell; + + // processed charge only counting case + if (triggered_spell_id == 0) + return true; + + SpellInfo triggerEntry = Global.SpellMgr.GetSpellInfo(triggered_spell_id); + if (triggerEntry == null) + { + Log.outError(LogFilter.Unit, "Unit.HandleDummyAuraProc: Spell {0} has non-existing triggered spell {1}", dummySpell.Id, triggered_spell_id); + return false; + } + + CastSpell(target, triggered_spell_id, true, castItem, triggeredByAura); + + return true; + } + + bool HandleProcTriggerSpell(Unit victim, uint damage, AuraEffect triggeredByAura, SpellInfo procSpell, ProcFlags procFlags, ProcFlagsExLegacy procEx) + { + // Get triggered aura spell info + SpellInfo auraSpellInfo = triggeredByAura.GetSpellInfo(); + + // Basepoints of trigger aura + int triggerAmount = triggeredByAura.GetAmount(); + + // Set trigger spell id, target, custom basepoints + uint trigger_spell_id = triggeredByAura.GetSpellEffectInfo().TriggerSpell; + + Unit target = null; + int basepoints0 = 0; + + if (triggeredByAura.GetAuraType() == AuraType.ProcTriggerSpellWithValue) + basepoints0 = triggerAmount; + + Item castItem = !triggeredByAura.GetBase().GetCastItemGUID().IsEmpty() && IsTypeId(TypeId.Player) ? ToPlayer().GetItemByGuid(triggeredByAura.GetBase().GetCastItemGUID()) : null; + + // All ok. Check current trigger spell + SpellInfo triggerEntry = Global.SpellMgr.GetSpellInfo(trigger_spell_id); + if (triggerEntry == null) + { + // Don't cast unknown spell + Log.outError(LogFilter.Unit, "Unit.HandleProcTriggerSpell: Spell {0} has 0 in EffectTriggered[{1}]. Unhandled custom case?", auraSpellInfo.Id, triggeredByAura.GetEffIndex()); + return false; + } + + // not allow proc extra attack spell at extra attack + if (m_extraAttacks != 0 && triggerEntry.HasEffect(SpellEffectName.AddExtraAttacks)) + return false; + + // Custom basepoints/target for exist spell + // dummy basepoints or other customs + switch (trigger_spell_id) + { + // Maelstrom Weapon + case 53817: + { + // Item - Shaman T10 Enhancement 4P Bonus + AuraEffect aurEff = GetAuraEffect(70832, 0); + if (aurEff != null) + { + Aura maelstrom = GetAura(53817); + if (maelstrom != null) + if ((maelstrom.GetStackAmount() == maelstrom.GetSpellInfo().StackAmount) && RandomHelper.randChance(aurEff.GetAmount())) + CastSpell(this, 70831, true, castItem, triggeredByAura); + } + break; + } + } + + // extra attack should hit same target + if (triggerEntry.HasEffect(SpellEffectName.AddExtraAttacks)) + target = victim; + + // try detect target manually if not set + if (target == null) + target = !procFlags.HasAnyFlag(ProcFlags.DoneSpellMagicDmgClassPos | ProcFlags.DoneSpellNoneDmgClassPos) && triggerEntry.IsPositive() ? this : victim; + + if (basepoints0 != 0) + CastCustomSpell(target, trigger_spell_id, basepoints0, 0, 0, true, castItem, triggeredByAura); + else + CastSpell(target, trigger_spell_id, true, castItem, triggeredByAura); + + return true; + } + bool HandleOverrideClassScriptAuraProc(Unit victim, uint damage, AuraEffect triggeredByAura, SpellInfo procSpell) + { + int scriptId = triggeredByAura.GetMiscValue(); + + if (victim == null || !victim.IsAlive()) + return false; + + Item castItem = !triggeredByAura.GetBase().GetCastItemGUID().IsEmpty() && IsTypeId(TypeId.Player) ? ToPlayer().GetItemByGuid(triggeredByAura.GetBase().GetCastItemGUID()) : null; + + uint triggered_spell_id = 0; + + switch (scriptId) + { + case 4537: // Dreamwalker Raiment 6 pieces bonus + triggered_spell_id = 28750; // Blessing of the Claw + break; + default: + break; + } + + // not processed + if (triggered_spell_id == 0) + return false; + + // standard non-dummy case + SpellInfo triggerEntry = Global.SpellMgr.GetSpellInfo(triggered_spell_id); + + if (triggerEntry == null) + { + Log.outError(LogFilter.Unit, "Unit.HandleOverrideClassScriptAuraProc: Spell {0} triggering for class script id {1}", triggered_spell_id, scriptId); + return false; + } + + CastSpell(victim, triggered_spell_id, true, castItem, triggeredByAura); //Do not allow auras to proc from ef + + return true; + } + bool HandleAuraRaidProcFromChargeWithValue(AuraEffect triggeredByAura) + { + // aura can be deleted at casts + SpellInfo spellProto = triggeredByAura.GetSpellInfo(); + uint triggered_spell_id = 0; + int heal = triggeredByAura.GetAmount(); + ObjectGuid caster_guid = triggeredByAura.GetCasterGUID(); + + // Currently only Prayer of Mending + if (triggered_spell_id == 0) + { + Log.outDebug(LogFilter.Spells, "Unit.HandleAuraRaidProcFromChargeWithValue, received not handled spell: {0}", spellProto.Id); + return false; + } + + // jumps + int jumps = triggeredByAura.GetBase().GetCharges() - 1; + + // current aura expire + triggeredByAura.GetBase().SetCharges(1); // will removed at next charges decrease + + // next target selection + if (jumps > 0) + { + Unit caster = triggeredByAura.GetCaster(); + if (caster != null) + { + SpellEffectInfo effect = triggeredByAura.GetSpellEffectInfo(); + float radius = effect.CalcRadius(caster); + Unit target = GetNextRandomRaidMemberOrPet(radius); + if (target != null) + { + CastCustomSpell(target, spellProto.Id, heal, 0, 0, true, null, triggeredByAura, caster_guid); + Aura aura = target.GetAura(spellProto.Id, caster.GetGUID()); + if (aura != null) + aura.SetCharges(jumps); + } + } + } + + // heal + CastSpell(this, triggered_spell_id, true, null, triggeredByAura, caster_guid); + return true; + } + bool HandleAuraRaidProcFromCharge(AuraEffect triggeredByAura) + { + // aura can be deleted at casts + SpellInfo spellProto = triggeredByAura.GetSpellInfo(); + + uint damageSpellId; + switch (spellProto.Id) + { + case 57949: // shiver + damageSpellId = 57952; + break; + case 59978: // shiver + damageSpellId = 59979; + break; + case 43593: // Cold Stare + damageSpellId = 43594; + break; + default: + Log.outError(LogFilter.Unit, "Unit.HandleAuraRaidProcFromCharge, received unhandled spell: {0}", spellProto.Id); + return false; + } + + ObjectGuid caster_guid = triggeredByAura.GetCasterGUID(); + + // jumps + int jumps = triggeredByAura.GetBase().GetCharges() - 1; + + // current aura expire + triggeredByAura.GetBase().SetCharges(1); // will removed at next charges decrease + + // next target selection + if (jumps > 0) + { + Unit caster = triggeredByAura.GetCaster(); + if (caster != null) + { + SpellEffectInfo effect = triggeredByAura.GetSpellEffectInfo(); + float radius = effect.CalcRadius(caster); + Unit target = GetNextRandomRaidMemberOrPet(radius); + if (target != null) + { + CastSpell(target, spellProto, true, null, triggeredByAura, caster_guid); + Aura aura = target.GetAura(spellProto.Id, caster.GetGUID()); + if (aura != null) + aura.SetCharges(jumps); + } + } + } + + CastSpell(this, damageSpellId, true, null, triggeredByAura, caster_guid); + + return true; + } + + public int GetMaxPositiveAuraModifier(AuraType auratype) + { + int modifier = 0; + + var mTotalAuraList = GetAuraEffectsByType(auratype); + foreach (var eff in mTotalAuraList) + { + if (eff.GetAmount() > modifier) + modifier = eff.GetAmount(); + } + + return modifier; + } + + public int GetMaxNegativeAuraModifier(AuraType auratype) + { + int modifier = 0; + + var mTotalAuraList = GetAuraEffectsByType(auratype); + foreach (var eff in mTotalAuraList) + if (eff.GetAmount() < modifier) + modifier = eff.GetAmount(); + + return modifier; + } + + public void ApplyCastTimePercentMod(float val, bool apply) + { + if (val > 0) + { + ApplyPercentModFloatValue(UnitFields.ModCastSpeed, val, !apply); + ApplyPercentModFloatValue(UnitFields.ModCastHaste, val, !apply); + ApplyPercentModFloatValue(UnitFields.ModHasteRegen, val, !apply); + } + else + { + ApplyPercentModFloatValue(UnitFields.ModCastSpeed, -val, apply); + ApplyPercentModFloatValue(UnitFields.ModCastHaste, -val, apply); + ApplyPercentModFloatValue(UnitFields.ModHasteRegen, -val, apply); + } + } + + public void RemoveAllGroupBuffsFromCaster(ObjectGuid casterGUID) + { + foreach (var iter in m_ownedAuras.KeyValueList) + { + Aura aura = iter.Value; + if (aura.GetCasterGUID() == casterGUID && aura.GetSpellInfo().IsGroupBuff()) + RemoveOwnedAura(iter); + } + } + + public void DelayOwnedAuras(uint spellId, ObjectGuid caster, int delaytime) + { + var range = m_ownedAuras.LookupByKey(spellId); + foreach (var aura in range) + { + if (caster.IsEmpty() || aura.GetCasterGUID() == caster) + { + if (aura.GetDuration() < delaytime) + aura.SetDuration(0); + else + aura.SetDuration(aura.GetDuration() - delaytime); + + // update for out of range group members (on 1 slot use) + aura.SetNeedClientUpdateForTargets(); + Log.outDebug(LogFilter.Spells, "Aura {0} partially interrupted on {1}, new duration: {2} ms", aura.GetId(), GetGUID().ToString(), aura.GetDuration()); + } + } + } + + public void CalculateSpellDamageTaken(SpellNonMeleeDamage damageInfo, int damage, SpellInfo spellInfo, WeaponAttackType attackType = WeaponAttackType.BaseAttack, bool crit = false) + { + if (damage < 0) + return; + + Unit victim = damageInfo.target; + if (victim == null || !victim.IsAlive()) + return; + + SpellSchoolMask damageSchoolMask = damageInfo.schoolMask; + + if (IsDamageReducedByArmor(damageSchoolMask, spellInfo)) + damage = (int)CalcArmorReducedDamage(damageInfo.attacker, victim, (uint)damage, spellInfo, attackType); + + bool blocked = false; + // Per-school calc + switch (spellInfo.DmgClass) + { + // Melee and Ranged Spells + case SpellDmgClass.Ranged: + case SpellDmgClass.Melee: + { + // Physical Damage + if (damageSchoolMask.HasAnyFlag(SpellSchoolMask.Normal)) + { + // Get blocked status + blocked = isSpellBlocked(victim, spellInfo, attackType); + } + + if (crit) + { + damageInfo.HitInfo |= SpellHitType.Crit; + + // Calculate crit bonus + uint crit_bonus = (uint)damage; + // Apply crit_damage bonus for melee spells + Player modOwner = GetSpellModOwner(); + if (modOwner != null) + modOwner.ApplySpellMod(spellInfo.Id, SpellModOp.CritDamageBonus, ref crit_bonus); + damage += (int)crit_bonus; + + // Apply SPELL_AURA_MOD_ATTACKER_RANGED_CRIT_DAMAGE or SPELL_AURA_MOD_ATTACKER_MELEE_CRIT_DAMAGE + float critPctDamageMod = 0.0f; + if (attackType == WeaponAttackType.RangedAttack) + critPctDamageMod += victim.GetTotalAuraModifier(AuraType.ModAttackerRangedCritDamage); + else + critPctDamageMod += victim.GetTotalAuraModifier(AuraType.ModAttackerMeleeCritDamage); + + // Increase crit damage from SPELL_AURA_MOD_CRIT_DAMAGE_BONUS + critPctDamageMod += (GetTotalAuraMultiplierByMiscMask(AuraType.ModCritDamageBonus, (uint)spellInfo.GetSchoolMask()) - 1.0f) * 100; + + if (critPctDamageMod != 0) + MathFunctions.AddPct(ref damage, (int)critPctDamageMod); + } + + // Spell weapon based damage CAN BE crit & blocked at same time + if (blocked) + { + // double blocked amount if block is critical + uint value = victim.GetBlockPercent(); + if (victim.isBlockCritical()) + value *= 2; // double blocked percent + damageInfo.blocked = (uint)MathFunctions.CalculatePct(damage, value); + damage -= (int)damageInfo.blocked; + } + uint dmg = (uint)damage; + ApplyResilience(victim, ref dmg); + damage = (int)dmg; + break; + } + // Magical Attacks + case SpellDmgClass.None: + case SpellDmgClass.Magic: + { + // If crit add critical bonus + if (crit) + { + damageInfo.HitInfo |= SpellHitType.Crit; + damage = (int)SpellCriticalDamageBonus(spellInfo, (uint)damage, victim); + } + uint dmg = (uint)damage; + ApplyResilience(victim, ref dmg); + damage = (int)dmg; + break; + } + default: + break; + } + + // Script Hook For CalculateSpellDamageTaken -- Allow scripts to change the Damage post class mitigation calculations + Global.ScriptMgr.ModifySpellDamageTaken(damageInfo.target, damageInfo.attacker, ref damage); + + // Calculate absorb resist + if (damage > 0) + { + CalcAbsorbResist(victim, damageSchoolMask, DamageEffectType.SpellDirect, (uint)damage, ref damageInfo.absorb, ref damageInfo.resist, spellInfo); + damage -= (int)(damageInfo.absorb + damageInfo.resist); + } + else + damage = 0; + + damageInfo.damage = (uint)damage; + } + public void DealSpellDamage(SpellNonMeleeDamage damageInfo, bool durabilityLoss) + { + if (damageInfo == null) + return; + + Unit victim = damageInfo.target; + if (victim == null) + return; + + if (!victim.IsAlive() || victim.HasUnitState(UnitState.InFlight) || (victim.IsTypeId(TypeId.Unit) && victim.ToCreature().IsEvadingAttacks())) + return; + + SpellInfo spellProto = Global.SpellMgr.GetSpellInfo(damageInfo.SpellId); + if (spellProto == null) + { + Log.outDebug(LogFilter.Unit, "Unit.DealSpellDamage has wrong damageInfo.SpellID: {0}", damageInfo.SpellId); + return; + } + + // Call default DealDamage + CleanDamage cleanDamage = new CleanDamage(damageInfo.cleanDamage, damageInfo.absorb, WeaponAttackType.BaseAttack, MeleeHitOutcome.Normal); + DealDamage(victim, damageInfo.damage, cleanDamage, DamageEffectType.SpellDirect, damageInfo.schoolMask, spellProto, durabilityLoss); + } + + public void SendSpellNonMeleeDamageLog(SpellNonMeleeDamage log) + { + SpellNonMeleeDamageLog packet = new SpellNonMeleeDamageLog(); + packet.Me = log.target.GetGUID(); + packet.CasterGUID = log.attacker.GetGUID(); + packet.CastID = log.castId; + packet.SpellID = (int)log.SpellId; + packet.Damage = (int)log.damage; + if (log.damage > log.preHitHealth) + packet.Overkill = (int)(log.damage - log.preHitHealth); + else + packet.Overkill = 0; + + packet.SchoolMask = (byte)log.schoolMask; + packet.ShieldBlock = (int)log.blocked; + packet.Resisted = (int)log.resist; + packet.Absorbed = (int)log.absorb; + packet.Periodic = log.periodicLog; + packet.Flags = (int)log.HitInfo; + SendCombatLogMessage(packet); + } + + public void SendPeriodicAuraLog(SpellPeriodicAuraLogInfo info) + { + AuraEffect aura = info.auraEff; + + SpellPeriodicAuraLog data = new SpellPeriodicAuraLog(); + data.TargetGUID = GetGUID(); + data.CasterGUID = aura.GetCasterGUID(); + data.SpellID = aura.GetId(); + data.LogData.Initialize(this); + + SpellPeriodicAuraLog.SpellLogEffect spellLogEffect = new SpellPeriodicAuraLog.SpellLogEffect(); + spellLogEffect.Effect = (uint)aura.GetAuraType(); + spellLogEffect.Amount = info.damage; + spellLogEffect.OverHealOrKill = (uint)info.overDamage; + spellLogEffect.SchoolMaskOrPower = (uint)aura.GetSpellInfo().GetSchoolMask(); + spellLogEffect.AbsorbedOrAmplitude = info.absorb; + spellLogEffect.Resisted = info.resist; + spellLogEffect.Crit = info.critical; + /// @todo: implement debug info + + data.Effects.Add(spellLogEffect); + + SendCombatLogMessage(data); + } + public void SendSpellMiss(Unit target, uint spellID, SpellMissInfo missInfo) + { + SpellMissLog spellMissLog = new SpellMissLog(); + spellMissLog.SpellID = spellID; + spellMissLog.Caster = GetGUID(); + spellMissLog.Entries.Add(new SpellLogMissEntry(target.GetGUID(), (byte)missInfo)); + SendMessageToSet(spellMissLog, true); + } + + void SendSpellDamageResist(Unit target, uint spellId) + { + ProcResist procResist = new ProcResist(); + procResist.Caster = GetGUID(); + procResist.SpellID = spellId; + procResist.Target = target.GetGUID(); + SendMessageToSet(procResist, true); + } + + public void SendSpellDamageImmune(Unit target, uint spellId, bool isPeriodic) + { + SpellOrDamageImmune spellOrDamageImmune = new SpellOrDamageImmune(); + spellOrDamageImmune.CasterGUID = GetGUID(); + spellOrDamageImmune.VictimGUID = target.GetGUID(); + spellOrDamageImmune.SpellID = spellId; + spellOrDamageImmune.IsPeriodic = isPeriodic; + SendMessageToSet(spellOrDamageImmune, true); + } + + public void SendSpellInstakillLog(uint spellId, Unit caster, Unit target = null) + { + SpellInstakillLog spellInstakillLog = new SpellInstakillLog(); + spellInstakillLog.Caster = caster.GetGUID(); + spellInstakillLog.Target = target ? target.GetGUID() : caster.GetGUID(); + spellInstakillLog.SpellID = spellId; + SendMessageToSet(spellInstakillLog, false); + } + + public void RemoveAurasOnEvade() + { + if (IsCharmedOwnedByPlayerOrPlayer()) // if it is a player owned creature it should not remove the aura + return; + + // don't remove vehicle auras, passengers aren't supposed to drop off the vehicle + // don't remove clone caster on evade (to be verified) + RemoveAllAurasExceptType(AuraType.ControlVehicle, AuraType.CloneCaster); + } + + public void RemoveAllAurasOnDeath() + { + // used just after dieing to remove all visible auras + // and disable the mods for the passive ones + foreach (var app in GetAppliedAuras()) + { + if (app.Value == null) + continue; + + Aura aura = app.Value.GetBase(); + if (!aura.IsPassive() && !aura.IsDeathPersistent()) + _UnapplyAura(app, AuraRemoveMode.ByDeath); + } + + foreach (var pair in GetOwnedAuras()) + { + Aura aura = pair.Value; + if (pair.Value == null) + continue; + + if (!aura.IsPassive() && !aura.IsDeathPersistent()) + RemoveOwnedAura(pair, AuraRemoveMode.ByDeath); + } + } + public void RemoveMovementImpairingAuras() + { + RemoveAurasWithMechanic((1 << (int)Mechanics.Snare) | (1 << (int)Mechanics.Root)); + } + public void RemoveAllAurasRequiringDeadTarget() + { + foreach (var app in GetAppliedAuras()) + { + Aura aura = app.Value.GetBase(); + if (!aura.IsPassive() && aura.GetSpellInfo().IsRequiringDeadTarget()) + _UnapplyAura(app, AuraRemoveMode.Default); + } + + foreach (var aura in GetOwnedAuras()) + { + if (!aura.Value.IsPassive() && aura.Value.GetSpellInfo().IsRequiringDeadTarget()) + RemoveOwnedAura(aura, AuraRemoveMode.Default); + } + } + + public AuraEffect IsScriptOverriden(SpellInfo spell, int script) + { + var auras = GetAuraEffectsByType(AuraType.OverrideClassScripts); + foreach (var eff in auras) + { + if (eff.GetMiscValue() == script) + if (eff.IsAffectingSpell(spell)) + return eff; + } + return null; + } + + public void ApplySpellDispelImmunity(SpellInfo spellProto, DispelType type, bool apply) + { + ApplySpellImmune(spellProto.Id, SpellImmunity.Dispel, type, apply); + + if (apply && spellProto.HasAttribute(SpellAttr1.DispelAurasOnImmunity)) + { + // Create dispel mask by dispel type + uint dispelMask = SpellInfo.GetDispelMask(type); + // Dispel all existing auras vs current dispel type + var auras = GetAppliedAuras(); + foreach (var pair in auras) + { + SpellInfo spell = pair.Value.GetBase().GetSpellInfo(); + if ((spell.GetDispelMask() & dispelMask) != 0) + { + // Dispel aura + RemoveAura(pair); + } + } + } + } + + public bool isNonTriggerAura(AuraType type) + { + switch (type) + { + case AuraType.ModPowerRegen: + case AuraType.ReducePushback: + return true; + } + return false; + } + public bool isTriggerAura(AuraType type) + { + switch (type) + { + case AuraType.ProcOnPowerAmount: + case AuraType.ProcOnPowerAmount2: + case AuraType.Dummy: + case AuraType.ModConfuse: + case AuraType.ModThreat: + case AuraType.ModStun: + case AuraType.ModDamageDone: + case AuraType.ModDamageTaken: + case AuraType.ModResistance: + case AuraType.ModStealth: + case AuraType.ModFear: + case AuraType.ModRoot: + case AuraType.Transform: + case AuraType.ReflectSpells: + case AuraType.DamageImmunity: + case AuraType.ProcTriggerSpell: + case AuraType.ProcTriggerDamage: + case AuraType.ModCastingSpeedNotStack: + case AuraType.SchoolAbsorb: + case AuraType.ModPowerCostSchoolPct: + case AuraType.ModPowerCostSchool: + case AuraType.ReflectSpellsSchool: + case AuraType.MechanicImmunity: + case AuraType.ModDamagePercentTaken: + case AuraType.SpellMagnet: + case AuraType.ModAttackPower: + case AuraType.ModPowerRegenPercent: + case AuraType.AddCasterHitTrigger: + case AuraType.OverrideClassScripts: + case AuraType.ModMechanicResistance: + case AuraType.MeleeAttackPowerAttackerBonus: + case AuraType.ModMeleeHaste: + case AuraType.ModMeleeHaste3: + case AuraType.ModAttackerMeleeHitChance: + case AuraType.RaidProcFromCharge: + case AuraType.RaidProcFromChargeWithValue: + case AuraType.ProcTriggerSpellWithValue: + case AuraType.ModSpellDamageFromCaster: + case AuraType.AbilityIgnoreAurastate: + case AuraType.ModRoot2: + return true; + } + return false; + } + public bool isAlwaysTriggeredAura(AuraType type) + { + switch (type) + { + case AuraType.OverrideClassScripts: + case AuraType.ModFear: + case AuraType.ModRoot: + case AuraType.ModStun: + case AuraType.Transform: + case AuraType.SpellMagnet: + case AuraType.SchoolAbsorb: + case AuraType.ModStealth: + case AuraType.ModRoot2: + return true; + } + return false; + } + + public DiminishingLevels GetDiminishing(DiminishingGroup group) + { + foreach (var dim in m_Diminishing) + { + if (dim.DRGroup != group) + continue; + + if (dim.hitCount == 0) + return DiminishingLevels.Level1; + + if (dim.hitTime == 0) + return DiminishingLevels.Level1; + + // If last spell was casted more than 18 seconds ago - reset the count. + if (dim.stack == 0 && Time.GetMSTimeDiff(dim.hitTime, Time.GetMSTime()) > 18 * Time.InMilliseconds) + { + dim.hitCount = DiminishingLevels.Level1; + return DiminishingLevels.Level1; + } + // or else increase the count. + else + return dim.hitCount; + } + return DiminishingLevels.Level1; + } + + public void IncrDiminishing(DiminishingGroup group) + { + // Checking for existing in the table + foreach (var dim in m_Diminishing) + { + if (dim.DRGroup != group) + continue; + if (dim.hitCount < Global.SpellMgr.GetDiminishingReturnsMaxLevel(group)) + dim.hitCount += 1; + return; + } + m_Diminishing.Add(new DiminishingReturn(group, Time.GetMSTime(), DiminishingLevels.Level2)); + } + + public float ApplyDiminishingToDuration(DiminishingGroup group, int duration, Unit caster, DiminishingLevels Level, int limitduration) + { + if (duration == -1 || group == DiminishingGroup.None) + return 1.0f; + + // test pet/charm masters instead pets/charmeds + Unit targetOwner = GetCharmerOrOwner(); + Unit casterOwner = caster.GetCharmerOrOwner(); + + if (limitduration > 0 && duration > limitduration) + { + Unit target = targetOwner ?? this; + Unit source = casterOwner ?? caster; + + if ((target.IsTypeId(TypeId.Player) || target.ToCreature().GetCreatureTemplate().FlagsExtra.HasAnyFlag(CreatureFlagsExtra.AllDiminish)) + && source.IsTypeId(TypeId.Player)) + duration = limitduration; + } + + float mod = 1.0f; + + switch (group) + { + case DiminishingGroup.Taunt: + if (IsTypeId(TypeId.Unit) && ToCreature().GetCreatureTemplate().FlagsExtra.HasAnyFlag(CreatureFlagsExtra.TauntDiminish)) + { + DiminishingLevels diminish = Level; + switch (diminish) + { + case DiminishingLevels.Level1: + break; + case DiminishingLevels.Level2: + mod = 0.65f; + break; + case DiminishingLevels.Level3: + mod = 0.4225f; + break; + case DiminishingLevels.Level4: + mod = 0.274625f; + break; + case DiminishingLevels.TauntImmune: + mod = 0.0f; + break; + default: + break; + } + } + break; + case DiminishingGroup.AOEKnockback: + if ((Global.SpellMgr.GetDiminishingReturnsGroupType(group) == DiminishingReturnsType.Player && (((targetOwner ? targetOwner : this).ToPlayer()) + || IsTypeId(TypeId.Unit) && ToCreature().GetCreatureTemplate().FlagsExtra.HasAnyFlag(CreatureFlagsExtra.AllDiminish))) + || Global.SpellMgr.GetDiminishingReturnsGroupType(group) == DiminishingReturnsType.All) + { + DiminishingLevels diminish = Level; + switch (diminish) + { + case DiminishingLevels.Level1: + break; + case DiminishingLevels.Level2: + mod = 0.5f; + break; + default: + break; + } + } + break; + default: + if ((Global.SpellMgr.GetDiminishingReturnsGroupType(group) == DiminishingReturnsType.Player && (((targetOwner ? targetOwner : this).ToPlayer()) + || IsTypeId(TypeId.Unit) && ToCreature().GetCreatureTemplate().FlagsExtra.HasAnyFlag(CreatureFlagsExtra.AllDiminish))) + || Global.SpellMgr.GetDiminishingReturnsGroupType(group) == DiminishingReturnsType.All) + { + DiminishingLevels diminish = Level; + switch (diminish) + { + case DiminishingLevels.Level1: + break; + case DiminishingLevels.Level2: + mod = 0.5f; + break; + case DiminishingLevels.Level3: + mod = 0.25f; + break; + case DiminishingLevels.Immune: + mod = 0.0f; + break; + default: break; + } + } + break; + } + + duration = (int)(duration * mod); + return mod; + } + + public void ApplyDiminishingAura(DiminishingGroup group, bool apply) + { + // Checking for existing in the table + foreach (var dim in m_Diminishing) + { + if (dim.DRGroup != group) + continue; + + if (apply) + dim.stack += 1; + else if (dim.stack != 0) + { + dim.stack -= 1; + // Remember time after last aura from group removed + if (dim.stack == 0) + dim.hitTime = Time.GetMSTime(); + } + break; + } + } + + public uint GetRemainingPeriodicAmount(ObjectGuid caster, uint spellId, AuraType auraType, int effectIndex = 0) + { + uint amount = 0; + var periodicAuras = GetAuraEffectsByType(auraType); + foreach (var eff in periodicAuras) + { + if (eff.GetCasterGUID() != caster || eff.GetId() != spellId || eff.GetEffIndex() != effectIndex || eff.GetTotalTicks() == 0) + continue; + amount += (uint)((eff.GetAmount() * Math.Max(eff.GetTotalTicks() - eff.GetTickNumber(), 0)) / eff.GetTotalTicks()); + break; + } + + return amount; + } + + // Interrupts + public void InterruptNonMeleeSpells(bool withDelayed, uint spell_id = 0, bool withInstant = true) + { + // generic spells are interrupted if they are not finished or delayed + if (GetCurrentSpell(CurrentSpellTypes.Generic) != null && (spell_id == 0 || m_currentSpells[CurrentSpellTypes.Generic].m_spellInfo.Id == spell_id)) + InterruptSpell(CurrentSpellTypes.Generic, withDelayed, withInstant); + + // autorepeat spells are interrupted if they are not finished or delayed + if (GetCurrentSpell(CurrentSpellTypes.AutoRepeat) != null && (spell_id == 0 || m_currentSpells[CurrentSpellTypes.AutoRepeat].m_spellInfo.Id == spell_id)) + InterruptSpell(CurrentSpellTypes.AutoRepeat, withDelayed, withInstant); + + // channeled spells are interrupted if they are not finished, even if they are delayed + if (GetCurrentSpell(CurrentSpellTypes.Channeled) != null && (spell_id == 0 || m_currentSpells[CurrentSpellTypes.Channeled].m_spellInfo.Id == spell_id)) + InterruptSpell(CurrentSpellTypes.Channeled, true, true); + } + public void InterruptSpell(CurrentSpellTypes spellType, bool withDelayed = true, bool withInstant = true) + { + Contract.Assert(spellType < CurrentSpellTypes.Max); + + Log.outDebug(LogFilter.Unit, "Interrupt spell for unit {0}", GetEntry()); + Spell spell = m_currentSpells.LookupByKey(spellType); + if (spell != null + && (withDelayed || spell.getState() != SpellState.Delayed) + && (withInstant || spell.GetCastTime() > 0)) + { + // for example, do not let self-stun aura interrupt itself + if (!spell.IsInterruptable()) + return; + + // send autorepeat cancel message for autorepeat spells + if (spellType == CurrentSpellTypes.AutoRepeat) + if (IsTypeId(TypeId.Player)) + ToPlayer().SendAutoRepeatCancel(this); + + if (spell.getState() != SpellState.Finished) + spell.cancel(); + + m_currentSpells[spellType] = null; + spell.SetReferencedFromCurrent(false); + } + } + public void UpdateInterruptMask() + { + m_interruptMask = 0; + foreach (var app in m_interruptableAuras) + m_interruptMask |= (uint)app.GetBase().GetSpellInfo().AuraInterruptFlags; + + Spell spell = GetCurrentSpell(CurrentSpellTypes.Channeled); + if (spell != null) + if (spell.getState() == SpellState.Casting) + m_interruptMask |= (uint)spell.m_spellInfo.ChannelInterruptFlags; + } + + // Auras + public List GetSingleCastAuras() { return m_scAuras; } + public List> GetOwnedAuras() + { + return m_ownedAuras.KeyValueList; + } + public List> GetAppliedAuras() + { + return m_appliedAuras.KeyValueList; + } + + public Aura AddAura(uint spellId, Unit target) + { + if (target == null) + return null; + + SpellInfo spellInfo = Global.SpellMgr.GetSpellInfo(spellId); + if (spellInfo == null) + return null; + + if (!target.IsAlive() && !spellInfo.IsPassive() && !spellInfo.HasAttribute(SpellAttr2.CanTargetDead)) + return null; + + return AddAura(spellInfo, SpellConst.MaxEffectMask, target); + } + + public Aura AddAura(SpellInfo spellInfo, uint effMask, Unit target) + { + if (spellInfo == null) + return null; + + if (target.IsImmunedToSpell(spellInfo)) + return null; + + for (byte i = 0; i < SpellConst.MaxEffects; ++i) + { + if (!Convert.ToBoolean(effMask & (1 << i))) + continue; + if (target.IsImmunedToSpellEffect(spellInfo, i)) + effMask &= ~(uint)(1 << i); + } + + ObjectGuid castId = ObjectGuid.Create(HighGuid.Cast, SpellCastSource.Normal, GetMapId(), spellInfo.Id, GetMap().GenerateLowGuid(HighGuid.Cast)); + Aura aura = Aura.TryRefreshStackOrCreate(spellInfo, castId, effMask, target, this); + if (aura != null) + { + aura.ApplyForTargets(); + return aura; + } + return null; + } + + public bool HandleSpellClick(Unit clicker, sbyte seatId = 0) + { + bool result = false; + + uint spellClickEntry = GetVehicleKit() != null ? GetVehicleKit().GetCreatureEntry() : GetEntry(); + var clickPair = Global.ObjectMgr.GetSpellClickInfoMapBounds(spellClickEntry); + foreach (var clickInfo in clickPair) + { + //! First check simple relations from clicker to clickee + if (!clickInfo.IsFitToRequirements(clicker, this)) + continue; + + //! Check database conditions + if (!Global.ConditionMgr.IsObjectMeetingSpellClickConditions(spellClickEntry, clickInfo.spellId, clicker, this)) + continue; + + Unit caster = Convert.ToBoolean(clickInfo.castFlags & (byte)SpellClickCastFlags.CasterClicker) ? clicker : this; + Unit target = Convert.ToBoolean(clickInfo.castFlags & (byte)SpellClickCastFlags.TargetClicker) ? clicker : this; + ObjectGuid origCasterGUID = Convert.ToBoolean(clickInfo.castFlags & (byte)SpellClickCastFlags.OrigCasterOwner) ? GetOwnerGUID() : clicker.GetGUID(); + + SpellInfo spellEntry = Global.SpellMgr.GetSpellInfo(clickInfo.spellId); + // if (!spellEntry) should be checked at npc_spellclick load + + if (seatId > -1) + { + byte i = 0; + bool valid = false; + foreach (SpellEffectInfo effect in spellEntry.GetEffectsForDifficulty(GetMap().GetDifficultyID())) + { + if (effect == null) + continue; + + if (effect.ApplyAuraName == AuraType.ControlVehicle) + { + valid = true; + break; + } + ++i; + } + + if (!valid) + { + Log.outError(LogFilter.Sql, "Spell {0} specified in npc_spellclick_spells is not a valid vehicle enter aura!", clickInfo.spellId); + continue; + } + + if (IsInMap(caster)) + caster.CastCustomSpell(clickInfo.spellId, SpellValueMod.BasePoint0 + i, seatId + 1, target, GetVehicleKit() != null ? TriggerCastFlags.IgnoreCasterMountedOrOnVehicle : TriggerCastFlags.None, null, null, origCasterGUID); + else // This can happen during Player._LoadAuras + { + int[] bp0 = new int[SpellConst.MaxEffects]; + foreach (SpellEffectInfo effect in spellEntry.GetEffectsForDifficulty(GetMap().GetDifficultyID())) + { + if (effect != null) + bp0[effect.EffectIndex] = effect.BasePoints; + } + + bp0[i] = seatId; + Aura.TryRefreshStackOrCreate(spellEntry, ObjectGuid.Create(HighGuid.Cast, SpellCastSource.Normal, GetMapId(), spellEntry.Id, GetMap().GenerateLowGuid(HighGuid.Cast)), SpellConst.MaxEffectMask, this, clicker, bp0, null, origCasterGUID); + } + } + else + { + if (IsInMap(caster)) + caster.CastSpell(target, spellEntry, GetVehicleKit() != null ? TriggerCastFlags.IgnoreCasterMountedOrOnVehicle : TriggerCastFlags.None, null, null, origCasterGUID); + else + Aura.TryRefreshStackOrCreate(spellEntry, ObjectGuid.Create(HighGuid.Cast, SpellCastSource.Normal, GetMapId(), spellEntry.Id, GetMap().GenerateLowGuid(HighGuid.Cast)), SpellConst.MaxEffectMask, this, clicker, null, null, origCasterGUID); + } + + result = true; + } + + Creature creature = ToCreature(); + if (creature && creature.IsAIEnabled) + creature.GetAI().OnSpellClick(clicker, ref result); + + return result; + } + + public float GetTotalAuraMultiplierByMiscMask(AuraType auratype, uint miscMask) + { + Dictionary SameEffectSpellGroup = new Dictionary(); + float multiplier = 1.0f; + + var mTotalAuraList = GetAuraEffectsByType(auratype); + foreach (var eff in mTotalAuraList) + { + if (eff.GetMiscValue().HasAnyFlag((int)miscMask)) + { + // Check if the Aura Effect has a the Same Effect Stack Rule and if so, use the highest amount of that SpellGroup + // If the Aura Effect does not have this Stack Rule, it returns false so we can add to the multiplier as usual + if (!Global.SpellMgr.AddSameEffectStackRuleSpellGroups(eff.GetSpellInfo(), eff.GetAmount(), out SameEffectSpellGroup)) + MathFunctions.AddPct(ref multiplier, eff.GetAmount()); + } + } + // Add the highest of the Same Effect Stack Rule SpellGroups to the multiplier + foreach (var group in SameEffectSpellGroup.Values) + MathFunctions.AddPct(ref multiplier, group); + + return multiplier; + } + + public bool HasAura(uint spellId, ObjectGuid casterGUID = default(ObjectGuid), ObjectGuid itemCasterGUID = default(ObjectGuid), uint reqEffMask = 0) + { + if (GetAuraApplication(spellId, casterGUID, itemCasterGUID, reqEffMask) != null) + return true; + return false; + } + public bool HasAuraEffect(uint spellId, uint effIndex, ObjectGuid casterGUID = default(ObjectGuid)) + { + var range = m_appliedAuras.LookupByKey(spellId); + if (!range.Empty()) + { + foreach (var aura in range) + if (aura.HasEffect(effIndex) && (casterGUID.IsEmpty() || aura.GetBase().GetCasterGUID() == casterGUID)) + return true; + } + + return false; + } + public bool HasAuraWithMechanic(uint mechanicMask) + { + foreach (var pair in GetAppliedAuras()) + { + SpellInfo spellInfo = pair.Value.GetBase().GetSpellInfo(); + if (spellInfo.Mechanic != 0 && Convert.ToBoolean(mechanicMask & (1 << (int)spellInfo.Mechanic))) + return true; + + foreach (SpellEffectInfo effect in pair.Value.GetBase().GetSpellEffectInfos()) + if (effect != null && effect.Effect != 0 && effect.Mechanic != 0) + if (Convert.ToBoolean(mechanicMask & (1 << (int)effect.Mechanic))) + return true; + } + + return false; + } + + int GetMaxPositiveAuraModifierByMiscValue(AuraType auratype, int miscValue) + { + int modifier = 0; + + var mTotalAuraList = GetAuraEffectsByType(auratype); + foreach (var eff in mTotalAuraList) + { + if (eff.GetMiscValue() == miscValue && eff.GetAmount() > modifier) + modifier = eff.GetAmount(); + } + + return modifier; + } + + int GetMaxNegativeAuraModifierByMiscValue(AuraType auratype, int miscValue) + { + int modifier = 0; + + var mTotalAuraList = GetAuraEffectsByType(auratype); + foreach (var eff in mTotalAuraList) + { + if (eff.GetMiscValue() == miscValue && eff.GetAmount() < modifier) + modifier = eff.GetAmount(); + } + + return modifier; + } + + // target dependent range checks + public float GetSpellMaxRangeForTarget(Unit target, SpellInfo spellInfo) + { + if (spellInfo.RangeEntry == null) + return 0; + if (spellInfo.RangeEntry.MaxRangeFriend == spellInfo.RangeEntry.MaxRangeHostile) + return spellInfo.GetMaxRange(); + if (!target) + return spellInfo.GetMaxRange(true); + return spellInfo.GetMaxRange(!IsHostileTo(target)); + } + + public float GetSpellMinRangeForTarget(Unit target, SpellInfo spellInfo) + { + if (spellInfo.RangeEntry == null) + return 0; + if (spellInfo.RangeEntry.MinRangeFriend == spellInfo.RangeEntry.MinRangeHostile) + return spellInfo.GetMinRange(); + if (!target) + return spellInfo.GetMinRange(true); + return spellInfo.GetMinRange(!IsHostileTo(target)); + } + + public bool HasAuraType(AuraType auraType) + { + return !m_modAuras.LookupByKey(auraType).Empty(); + } + public bool HasAuraTypeWithCaster(AuraType auratype, ObjectGuid caster) + { + var mTotalAuraList = GetAuraEffectsByType(auratype); + foreach (var aura in mTotalAuraList) + if (caster == aura.GetCasterGUID()) + return true; + return false; + } + public bool HasAuraTypeWithMiscvalue(AuraType auratype, int miscvalue) + { + var mTotalAuraList = GetAuraEffectsByType(auratype); + foreach (var aura in mTotalAuraList) + if (miscvalue == aura.GetMiscValue()) + return true; + return false; + } + public bool HasAuraTypeWithAffectMask(AuraType auratype, SpellInfo affectedSpell) + { + var mTotalAuraList = GetAuraEffectsByType(auratype); + foreach (var aura in mTotalAuraList) + if (aura.IsAffectingSpell(affectedSpell)) + return true; + return false; + } + public bool HasAuraTypeWithValue(AuraType auratype, int value) + { + var mTotalAuraList = GetAuraEffectsByType(auratype); + foreach (var aura in mTotalAuraList) + if (value == aura.GetAmount()) + return true; + return false; + } + + public bool HasNegativeAuraWithInterruptFlag(uint flag, ObjectGuid guid = default(ObjectGuid)) + { + if (!Convert.ToBoolean(m_interruptMask & flag)) + return false; + foreach (var aura in m_interruptableAuras) + { + if (!aura.IsPositive() && Convert.ToBoolean((uint)aura.GetBase().GetSpellInfo().AuraInterruptFlags & flag) + && (guid.IsEmpty() || aura.GetBase().GetCasterGUID() == guid)) + return true; + } + return false; + } + bool HasNegativeAuraWithAttribute(SpellAttr0 flag, ObjectGuid guid = default(ObjectGuid)) + { + foreach (var list in GetAppliedAuras()) + { + Aura aura = list.Value.GetBase(); + if (!list.Value.IsPositive() && aura.GetSpellInfo().HasAttribute(flag) && (guid.IsEmpty() || aura.GetCasterGUID() == guid)) + return true; + } + return false; + } + + public uint GetAuraCount(uint spellId) + { + uint count = 0; + var range = m_appliedAuras.LookupByKey(spellId); + foreach (var aura in range) + { + if (aura.GetBase().GetStackAmount() == 0) + ++count; + else + count += aura.GetBase().GetStackAmount(); + } + + return count; + } + public Aura GetAuraOfRankedSpell(uint spellId, ObjectGuid casterGUID = default(ObjectGuid), ObjectGuid itemCasterGUID = default(ObjectGuid), uint reqEffMask = 0) + { + var aurApp = GetAuraApplicationOfRankedSpell(spellId, casterGUID, itemCasterGUID, reqEffMask); + return aurApp?.GetBase(); + } + AuraApplication GetAuraApplicationOfRankedSpell(uint spellId, ObjectGuid casterGUID = default(ObjectGuid), ObjectGuid itemCasterGUID = default(ObjectGuid), uint reqEffMask = 0, AuraApplication except = null) + { + uint rankSpell = Global.SpellMgr.GetFirstSpellInChain(spellId); + while (rankSpell != 0) + { + AuraApplication aurApp = GetAuraApplication(rankSpell, casterGUID, itemCasterGUID, reqEffMask, except); + if (aurApp != null) + return aurApp; + rankSpell = Global.SpellMgr.GetNextSpellInChain(rankSpell); + } + return null; + } + + public List GetDispellableAuraList(Unit caster, uint dispelMask) + { + List dispelList = new List(); + + var auras = GetOwnedAuras(); + foreach (var pair in auras) + { + Aura aura = pair.Value; + AuraApplication aurApp = aura.GetApplicationOfTarget(GetGUID()); + if (aurApp == null) + continue; + + // don't try to remove passive auras + if (aura.IsPassive()) + continue; + + if (Convert.ToBoolean(aura.GetSpellInfo().GetDispelMask() & dispelMask)) + { + // do not remove positive auras if friendly target + // negative auras if non-friendly target + if (aurApp.IsPositive() == IsFriendlyTo(caster)) + continue; + + // The charges / stack amounts don't count towards the total number of auras that can be dispelled. + // Ie: A dispel on a target with 5 stacks of Winters Chill and a Polymorph has 1 / (1 + 1) -> 50% chance to dispell + // Polymorph instead of 1 / (5 + 1) -> 16%. + bool dispelCharges = aura.GetSpellInfo().HasAttribute(SpellAttr7.DispelCharges); + byte charges = dispelCharges ? aura.GetCharges() : aura.GetStackAmount(); + if (charges > 0) + dispelList.Add(new DispelCharges(aura, charges)); + } + } + + return dispelList; + } + + public void RemoveAurasWithInterruptFlags(SpellAuraInterruptFlags flag, uint except = 0) + { + if (!Convert.ToBoolean(m_interruptMask & (uint)flag)) + return; + + // interrupt auras + for (var i = 0; i < m_interruptableAuras.Count; i++) + { + Aura aura = m_interruptableAuras[i].GetBase(); + + if (Convert.ToBoolean(aura.GetSpellInfo().AuraInterruptFlags & flag) && (except == 0 || aura.GetId() != except) + && !(Convert.ToBoolean(flag & SpellAuraInterruptFlags.Move) && HasAuraTypeWithAffectMask(AuraType.CastWhileWalking, aura.GetSpellInfo()))) + { + uint removedAuras = m_removedAurasCount; + RemoveAura(aura); + if (m_removedAurasCount > removedAuras + 1) + i = 0; + } + } + + // interrupt channeled spell + Spell spell = GetCurrentSpell(CurrentSpellTypes.Channeled); + if (spell != null) + if (spell.getState() == SpellState.Casting + && Convert.ToBoolean((uint)spell.m_spellInfo.ChannelInterruptFlags & (uint)flag) + && spell.m_spellInfo.Id != except + && !(Convert.ToBoolean(flag & SpellAuraInterruptFlags.Move) && HasAuraTypeWithAffectMask(AuraType.CastWhileWalking, spell.GetSpellInfo()))) + InterruptNonMeleeSpells(false); + + UpdateInterruptMask(); + } + public void RemoveAurasWithMechanic(uint mechanic_mask, AuraRemoveMode removemode = AuraRemoveMode.Default, uint except = 0) + { + foreach (var app in GetAppliedAuras()) + { + if (app.Value == null) + continue; + Aura aura = app.Value.GetBase(); + if (except == 0 || aura.GetId() != except) + { + if (Convert.ToBoolean(aura.GetSpellInfo().GetAllEffectsMechanicMask() & mechanic_mask)) + { + RemoveAura(app, removemode); + continue; + } + } + } + } + public void RemoveAurasDueToSpellBySteal(uint spellId, ObjectGuid casterGUID, Unit stealer) + { + var range = m_ownedAuras.LookupByKey(spellId); + foreach (var aura in range) + { + if (aura.GetCasterGUID() == casterGUID) + { + int[] damage = new int[SpellConst.MaxEffects]; + int[] baseDamage = new int[SpellConst.MaxEffects]; + uint effMask = 0; + uint recalculateMask = 0; + Unit caster = aura.GetCaster(); + for (byte i = 0; i < SpellConst.MaxEffects; ++i) + { + if (aura.GetEffect(i) != null) + { + baseDamage[i] = aura.GetEffect(i).GetBaseAmount(); + damage[i] = aura.GetEffect(i).GetAmount(); + effMask |= 1u << i; + if (aura.GetEffect(i).CanBeRecalculated()) + recalculateMask |= 1u << i; + } + else + { + baseDamage[i] = 0; + damage[i] = 0; + } + } + + bool stealCharge = aura.GetSpellInfo().HasAttribute(SpellAttr7.DispelCharges); + // Cast duration to unsigned to prevent permanent aura's such as Righteous Fury being permanently added to caster + uint dur = (uint)Math.Min(2u * Time.Minute * Time.InMilliseconds, aura.GetDuration()); + + Aura oldAura = stealer.GetAura(aura.GetId(), aura.GetCasterGUID()); + if (oldAura != null) + { + if (stealCharge) + oldAura.ModCharges(1); + else + oldAura.ModStackAmount(1); + oldAura.SetDuration((int)dur); + } + else + { + // single target state must be removed before aura creation to preserve existing single target aura + if (aura.IsSingleTarget()) + aura.UnregisterSingleTarget(); + + Aura newAura = Aura.TryRefreshStackOrCreate(aura.GetSpellInfo(), aura.GetCastGUID(), effMask, stealer, null, baseDamage, null, aura.GetCasterGUID()); + if (newAura != null) + { + // created aura must not be single target aura,, so stealer won't loose it on recast + if (newAura.IsSingleTarget()) + { + newAura.UnregisterSingleTarget(); + // bring back single target aura status to the old aura + aura.SetIsSingleTarget(true); + caster.GetSingleCastAuras().Add(aura); + } + // FIXME: using aura.GetMaxDuration() maybe not blizzlike but it fixes stealing of spells like Innervate + newAura.SetLoadedState(aura.GetMaxDuration(), (int)dur, stealCharge ? 1 : aura.GetCharges(), 1, recalculateMask, damage); + newAura.ApplyForTargets(); + } + } + + if (stealCharge) + aura.ModCharges(-1, AuraRemoveMode.EnemySpell); + else + aura.ModStackAmount(-1, AuraRemoveMode.EnemySpell); + + return; + } + } + } + public void RemoveAurasDueToItemSpell(uint spellId, ObjectGuid castItemGuid) + { + foreach (var app in m_appliedAuras.LookupByKey(spellId)) + { + if (app.GetBase().GetCastItemGUID() == castItemGuid) + { + RemoveAura(app); + } + } + } + public void RemoveAurasByType(AuraType auraType, ObjectGuid casterGUID = default(ObjectGuid), Aura except = null, bool negative = true, bool positive = true) + { + var list = m_modAuras[auraType]; + for (var i = 0; i < list.Count; i++) + { + Aura aura = list[i].GetBase(); + AuraApplication aurApp = aura.GetApplicationOfTarget(GetGUID()); + + if (aura != except && (casterGUID.IsEmpty() || aura.GetCasterGUID() == casterGUID) + && ((negative && !aurApp.IsPositive()) || (positive && aurApp.IsPositive()))) + { + uint removedAuras = m_removedAurasCount; + RemoveAura(aurApp); + if (m_removedAurasCount > removedAuras + 1) + i = 0; + } + } + } + public void RemoveNotOwnSingleTargetAuras(uint newPhase = 0, bool phaseid = false) + { + // Iterate m_ownedAuras - aura is marked as single target in Unit::AddAura (and pushed to m_ownedAuras). + // m_appliedAuras will NOT contain the aura before first Unit::Update after adding it to m_ownedAuras. + // Quickly removing such an aura will lead to it not being unregistered from caster's single cast auras container + // leading to assertion failures if the aura was cast on a player that can + // (and is changing map at the point where this function is called). + // Such situation occurs when player is logging in inside an instance and fails the entry check for any reason. + // The aura that was loaded from db (indirectly, via linked casts) gets removed before it has a chance + // to register in m_appliedAuras + var list = GetOwnedAuras().ToList(); + for (var i = 0; i < list.Count; i++) + { + Aura aura = list[i].Value; + + if (aura.GetCasterGUID() != GetGUID() && aura.IsSingleTarget()) + { + if (newPhase == 0 && !phaseid) + RemoveOwnedAura(list[i]); + else + { + Unit caster = aura.GetCaster(); + if (!caster || (newPhase != 0 && !caster.IsInPhase(newPhase)) || (newPhase == 0 && !caster.IsInPhase(this))) + RemoveOwnedAura(list[i]); + } + } + } + + // single target auras at other targets + for (var i = 0; i < m_scAuras.Count; i++) + { + var aura = m_scAuras[i]; + if (aura.GetUnitOwner() != this && !aura.GetUnitOwner().IsInPhase(newPhase)) + aura.Remove(); + } + } + // All aura base removes should go threw this function! + public void RemoveOwnedAura(KeyValuePair pair, AuraRemoveMode removeMode = AuraRemoveMode.Default) + { + Aura aura = pair.Value; + Contract.Assert(!aura.IsRemoved()); + + m_ownedAuras.Remove(pair); + m_removedAuras.Add(aura); + + // Unregister single target aura + if (aura.IsSingleTarget()) + aura.UnregisterSingleTarget(); + + aura._Remove(removeMode); + } + public void RemoveOwnedAura(uint spellId, ObjectGuid casterGUID = default(ObjectGuid), uint reqEffMask = 0, AuraRemoveMode removeMode = AuraRemoveMode.Default) + { + var list = m_ownedAuras.Where(p => p.Key == spellId); + foreach (var pair in list.ToList()) + { + if (((pair.Value.GetEffectMask() & reqEffMask) == reqEffMask) && (casterGUID.IsEmpty() || pair.Value.GetCasterGUID() == casterGUID)) + RemoveOwnedAura(pair, removeMode); + } + } + public void RemoveOwnedAura(Aura aura, AuraRemoveMode removeMode = AuraRemoveMode.Default) + { + if (aura.IsRemoved()) + return; + + Contract.Assert(aura.GetOwner() == this); + + if (removeMode == AuraRemoveMode.None) + { + Log.outError(LogFilter.Spells, "Unit.RemoveOwnedAura() called with unallowed removeMode AURA_REMOVE_NONE, spellId {0}", aura.GetId()); + return; + } + + uint spellId = aura.GetId(); + var range = m_ownedAuras.Where(p => p.Key == spellId); + + foreach (var pair in range) + { + if (pair.Value == aura) + { + RemoveOwnedAura(pair, removeMode); + return; + } + } + + Contract.Assert(false); + } + + public void RemoveAurasDueToSpell(uint spellId, ObjectGuid casterGUID = default(ObjectGuid), uint reqEffMask = 0, AuraRemoveMode removeMode = AuraRemoveMode.Default) + { + var list = m_appliedAuras.LookupByKey(spellId); + if (list.Empty()) + return; + + foreach (var spell in list.ToList()) + { + Aura aura = spell.GetBase(); + if (((aura.GetEffectMask() & reqEffMask) == reqEffMask) + && (casterGUID.IsEmpty() || aura.GetCasterGUID() == casterGUID)) + { + RemoveAura(spell, removeMode); + } + } + } + public void RemoveAurasDueToSpellByDispel(uint spellId, uint dispellerSpellId, ObjectGuid casterGUID, Unit dispeller, byte chargesRemoved = 1) + { + var range = m_ownedAuras.LookupByKey(spellId); + foreach (var aura in range) + { + if (aura.GetCasterGUID() == casterGUID) + { + DispelInfo dispelInfo = new DispelInfo(dispeller, dispellerSpellId, chargesRemoved); + + // Call OnDispel hook on AuraScript + aura.CallScriptDispel(dispelInfo); + + if (aura.GetSpellInfo().HasAttribute(SpellAttr7.DispelCharges)) + aura.ModCharges(-dispelInfo.GetRemovedCharges(), AuraRemoveMode.EnemySpell); + else + aura.ModStackAmount(-dispelInfo.GetRemovedCharges(), AuraRemoveMode.EnemySpell); + + // Call AfterDispel hook on AuraScript + aura.CallScriptAfterDispel(dispelInfo); + + return; + } + } + } + public void RemoveAuraFromStack(uint spellId, ObjectGuid casterGUID = default(ObjectGuid), AuraRemoveMode removeMode = AuraRemoveMode.Default, ushort num = 1) + { + var range = m_ownedAuras.LookupByKey(spellId); + foreach (var aura in range) + { + if ((aura.GetAuraType() == AuraObjectType.Unit) && (casterGUID.IsEmpty() || aura.GetCasterGUID() == casterGUID)) + { + aura.ModStackAmount(-num, removeMode); + return; + } + } + } + public void RemoveAura(KeyValuePair appMap, AuraRemoveMode mode = AuraRemoveMode.Default) + { + var aurApp = appMap.Value; + // Do not remove aura which is already being removed + if (aurApp.HasRemoveMode()) + return; + Aura aura = aurApp.GetBase(); + _UnapplyAura(appMap, mode); + // Remove aura - for Area and Target auras + if (aura.GetOwner() == this) + aura.Remove(mode); + } + public void RemoveAura(uint spellId, ObjectGuid caster = default(ObjectGuid), uint reqEffMask = 0, AuraRemoveMode removeMode = AuraRemoveMode.Default) + { + var range = m_appliedAuras.LookupByKey(spellId); + foreach (var iter in range) + { + Aura aura = iter.GetBase(); + if (((aura.GetEffectMask() & reqEffMask) == reqEffMask) && (caster.IsEmpty() || aura.GetCasterGUID() == caster)) + { + RemoveAura(iter, removeMode); + return; + } + } + } + public void RemoveAura(AuraApplication aurApp, AuraRemoveMode mode = AuraRemoveMode.Default) + { + // we've special situation here, RemoveAura called while during aura removal + // this kind of call is needed only when aura effect removal handler + // or event triggered by it expects to remove + // not yet removed effects of an aura + if (aurApp.HasRemoveMode()) + { + // remove remaining effects of an aura + for (byte effectIndex = 0; effectIndex < SpellConst.MaxEffects; ++effectIndex) + { + if (aurApp.HasEffect(effectIndex)) + aurApp._HandleEffect(effectIndex, false); + } + return; + } + // no need to remove + if (aurApp.GetBase().GetApplicationOfTarget(GetGUID()) != aurApp || aurApp.GetBase().IsRemoved()) + return; + + uint spellId = aurApp.GetBase().GetId(); + var range = m_appliedAuras.Where(p => p.Key == spellId); + + foreach (var pair in range) + { + if (aurApp == pair.Value) + { + RemoveAura(pair, mode); + return; + } + } + } + public void RemoveAura(Aura aura, AuraRemoveMode mode = AuraRemoveMode.Default) + { + if (aura.IsRemoved()) + return; + AuraApplication aurApp = aura.GetApplicationOfTarget(GetGUID()); + if (aurApp != null) + RemoveAura(aurApp, mode); + } + public void RemoveAurasWithAttribute(SpellAttr0 flags) + { + foreach (var app in GetAppliedAuras()) + { + SpellInfo spell = app.Value.GetBase().GetSpellInfo(); + if (spell.HasAttribute(flags)) + RemoveAura(app); + } + } + public void RemoveAurasWithFamily(SpellFamilyNames family, FlagArray128 familyFlag, ObjectGuid casterGUID) + { + foreach (var pair in GetAppliedAuras()) + { + Aura aura = pair.Value.GetBase(); + if (casterGUID.IsEmpty() || aura.GetCasterGUID() == casterGUID) + { + SpellInfo spell = aura.GetSpellInfo(); + if (spell.SpellFamilyName == family && spell.SpellFamilyFlags & familyFlag) + { + RemoveAura(pair); + continue; + } + } + } + } + + public void RemoveAppliedAuras(Func check) + { + foreach (var pair in m_appliedAuras) + { + if (check(pair.Value)) + RemoveAura(pair); + } + } + + public void RemoveOwnedAuras(Func check) + { + foreach (var pair in m_ownedAuras) + { + if (check(pair.Value)) + RemoveOwnedAura(pair); + } + } + + void RemoveAppliedAuras(uint spellId, Func check) + { + foreach (var app in m_appliedAuras.LookupByKey(spellId)) + { + if (check(app)) + RemoveAura(app); + } + } + + void RemoveOwnedAuras(uint spellId, Func check) + { + foreach (var aura in m_ownedAuras.LookupByKey(spellId)) + { + if (check(aura)) + RemoveOwnedAura(aura); + } + } + + public void RemoveAurasByType(AuraType auraType, Func check) + { + var list = m_modAuras[auraType]; + for (var i = 0; i < list.Count; ++i) + { + Aura aura = m_modAuras[auraType][i].GetBase(); + AuraApplication aurApp = aura.GetApplicationOfTarget(GetGUID()); + Contract.Assert(aurApp != null); + + if (check(aurApp)) + { + uint removedAuras = m_removedAurasCount; + RemoveAura(aurApp); + if (m_removedAurasCount > removedAuras + 1) + i = 0; + } + } + } + + void RemoveAreaAurasDueToLeaveWorld() + { + // make sure that all area auras not applied on self are removed + foreach (var pair in GetOwnedAuras()) + { + var appMap = pair.Value.GetApplicationMap(); + foreach (var aurApp in appMap.Values.ToList()) + { + Unit target = aurApp.GetTarget(); + if (target == this) + continue; + target.RemoveAura(aurApp); + // things linked on aura remove may apply new area aura - so start from the beginning + } + } + + // remove area auras owned by others + foreach (var pair in GetAppliedAuras()) + { + if (pair.Value.GetBase().GetOwner() != this) + RemoveAura(pair); + } + } + public void RemoveAllAuras() + { + // this may be a dead loop if some events on aura remove will continiously apply aura on remove + // we want to have all auras removed, so use your brain when linking events + while (!m_appliedAuras.Empty()) + _UnapplyAura(m_appliedAuras.FirstOrDefault(), AuraRemoveMode.Default); + + while (!m_ownedAuras.Empty()) + RemoveOwnedAura(m_ownedAuras.FirstOrDefault()); + } + public void RemoveArenaAuras() + { + // in join, remove positive buffs, on end, remove negative + // used to remove positive visible auras in arenas + RemoveAppliedAuras(aurApp => + { + Aura aura = aurApp.GetBase(); + return !aura.GetSpellInfo().HasAttribute(SpellAttr4.Unk21) // don't remove stances, shadowform, pally/hunter auras + && !aura.IsPassive() // don't remove passive auras + && (aurApp.IsPositive() || !aura.GetSpellInfo().HasAttribute(SpellAttr3.DeathPersistent)); // not negative death persistent auras + }); + } + public void RemoveAllAurasExceptType(AuraType type) + { + foreach (var pair in GetAppliedAuras()) + { + if (pair.Value == null) + continue; + Aura aura = pair.Value.GetBase(); + if (!aura.GetSpellInfo().HasAura(GetMap().GetDifficultyID(), type)) + _UnapplyAura(pair, AuraRemoveMode.Default); + } + + foreach (var pair in GetOwnedAuras()) + { + if (pair.Value == null) + continue; + Aura aura = pair.Value; + if (!aura.GetSpellInfo().HasAura(GetMap().GetDifficultyID(), type)) + RemoveOwnedAura(pair, AuraRemoveMode.Default); + } + } + public void RemoveAllAurasExceptType(AuraType type1, AuraType type2) + { + foreach (var pair in GetAppliedAuras()) + { + Aura aura = pair.Value.GetBase(); + if (!aura.GetSpellInfo().HasAura(GetMap().GetDifficultyID(), type1) || !aura.GetSpellInfo().HasAura(GetMap().GetDifficultyID(), type2)) + _UnapplyAura(pair, AuraRemoveMode.Default); + } + + foreach (var pair in GetOwnedAuras()) + { + Aura aura = pair.Value; + if (!aura.GetSpellInfo().HasAura(GetMap().GetDifficultyID(), type1) || !aura.GetSpellInfo().HasAura(GetMap().GetDifficultyID(), type2)) + RemoveOwnedAura(pair, AuraRemoveMode.Default); + } + } + + public void ModifyAuraState(AuraStateType flag, bool apply) + { + if (apply) + { + if (!HasFlag(UnitFields.AuraState, (1u << ((int)flag - 1)))) + { + SetFlag(UnitFields.AuraState, (1u << (int)flag - 1)); + if (IsTypeId(TypeId.Player)) + { + var sp_list = ToPlayer().GetSpellMap(); + foreach (var spell in sp_list) + { + if (spell.Value.State == PlayerSpellState.Removed || spell.Value.Disabled) + continue; + + SpellInfo spellInfo = Global.SpellMgr.GetSpellInfo(spell.Key); + if (spellInfo == null || !spellInfo.IsPassive()) + continue; + + if (spellInfo.CasterAuraState == flag) + CastSpell(this, spell.Key, true, null); + } + } + else if (IsPet()) + { + Pet pet = ToPet(); + foreach (var spell in pet.m_spells) + { + if (spell.Value.state == PetSpellState.Removed) + continue; + SpellInfo spellInfo = Global.SpellMgr.GetSpellInfo(spell.Key); + if (spellInfo == null || !spellInfo.IsPassive()) + continue; + if (spellInfo.CasterAuraState == flag) + CastSpell(this, spell.Key, true, null); + } + } + } + } + else + { + if (HasFlag(UnitFields.AuraState, (1u << (int)flag - 1))) + { + RemoveFlag(UnitFields.AuraState, (1u << (int)flag - 1)); + + foreach (var app in GetAppliedAuras()) + { + if (app.Value == null) + continue; + + SpellInfo spellProto = app.Value.GetBase().GetSpellInfo(); + if (app.Value.GetBase().GetCasterGUID() == GetGUID() && spellProto.CasterAuraState == flag && (spellProto.IsPassive() || flag != AuraStateType.Enrage)) + RemoveAura(app); + } + } + } + } + public bool HasAuraState(AuraStateType flag, SpellInfo spellProto = null, Unit Caster = null) + { + if (Caster != null) + { + if (spellProto != null) + { + var stateAuras = Caster.GetAuraEffectsByType(AuraType.AbilityIgnoreAurastate); + foreach (var aura in stateAuras) + if (aura.IsAffectingSpell(spellProto)) + return true; + } + // Check per caster aura state + // If aura with aurastate by caster not found return false + if (Convert.ToBoolean((1 << (int)flag) & (int)AuraStateType.PerCasterAuraStateMask)) + { + var range = m_auraStateAuras.LookupByKey(flag); + foreach (var auraApp in range) + if (auraApp.GetBase().GetCasterGUID() == Caster.GetGUID()) + return true; + return false; + } + } + + return HasFlag(UnitFields.AuraState, (uint)(1 << (int)flag - 1)); + } + + SpellSchools GetSpellSchoolByAuraGroup(UnitMods unitMod) + { + SpellSchools school = SpellSchools.Normal; + + switch (unitMod) + { + case UnitMods.ResistanceHoly: + school = SpellSchools.Holy; + break; + case UnitMods.ResistanceFire: + school = SpellSchools.Fire; + break; + case UnitMods.ResistanceNature: + school = SpellSchools.Nature; + break; + case UnitMods.ResistanceFrost: + school = SpellSchools.Frost; + break; + case UnitMods.ResistanceShadow: + school = SpellSchools.Shadow; + break; + case UnitMods.ResistanceArcane: + school = SpellSchools.Arcane; + break; + } + + return school; + } + + public void _ApplyAllAuraStatMods() + { + foreach (var i in GetAppliedAuras()) + i.Value.GetBase().HandleAllEffects(i.Value, AuraEffectHandleModes.Stat, true); + } + public void _RemoveAllAuraStatMods() + { + foreach (var i in GetAppliedAuras()) + i.Value.GetBase().HandleAllEffects(i.Value, AuraEffectHandleModes.Stat, false); + } + + // removes aura application from lists and unapplies effects + public void _UnapplyAura(KeyValuePair pair, AuraRemoveMode removeMode) + { + AuraApplication aurApp = pair.Value; + Contract.Assert(aurApp != null); + Contract.Assert(!aurApp.HasRemoveMode()); + Contract.Assert(aurApp.GetTarget() == this); + + aurApp.SetRemoveMode(removeMode); + Aura aura = aurApp.GetBase(); + Log.outDebug(LogFilter.Spells, "Aura {0} now is remove mode {1}", aura.GetId(), removeMode); + + // dead loop is killing the server probably + Contract.Assert(m_removedAurasCount < 0xFFFFFFFF); + + ++m_removedAurasCount; + + Unit caster = aura.GetCaster(); + + m_appliedAuras.Remove(pair); + + if (aura.GetSpellInfo().AuraInterruptFlags != 0) + { + m_interruptableAuras.Remove(aurApp); + UpdateInterruptMask(); + } + + bool auraStateFound = false; + AuraStateType auraState = aura.GetSpellInfo().GetAuraState(GetMap().GetDifficultyID()); + if (auraState != 0) + { + bool canBreak = false; + // Get mask of all aurastates from remaining auras + var list = m_auraStateAuras.LookupByKey(auraState); + for (var i = 0; i < list.Count && !(auraStateFound && canBreak);) + { + if (list[i] == aurApp) + { + m_auraStateAuras.Remove(auraState, list[i]); + list = m_auraStateAuras.LookupByKey(auraState); + i = 0; + canBreak = true; + continue; + } + auraStateFound = true; + ++i; + } + } + + aurApp._Remove(); + aura._UnapplyForTarget(this, caster, aurApp); + + // remove effects of the spell - needs to be done after removing aura from lists + for (byte c = 0; c < SpellConst.MaxEffects; ++c) + { + if (aurApp.HasEffect(c)) + aurApp._HandleEffect(c, false); + } + + // all effect mustn't be applied + Contract.Assert(aurApp.GetEffectMask() == 0); + + // Remove totem at next update if totem loses its aura + if (aurApp.GetRemoveMode() == AuraRemoveMode.Expire && IsTypeId(TypeId.Unit) && IsTotem()) + { + if (ToTotem().GetSpell() == aura.GetId() && ToTotem().GetTotemType() == TotemType.Passive) + ToTotem().setDeathState(DeathState.JustDied); + } + + // Remove aurastates only if were not found + if (!auraStateFound) + ModifyAuraState(auraState, false); + + aura.HandleAuraSpecificMods(aurApp, caster, false, false); + } + + public void _UnapplyAura(AuraApplication aurApp, AuraRemoveMode removeMode) + { + // aura can be removed from unit only if it's applied on it, shouldn't happen + Contract.Assert(aurApp.GetBase().GetApplicationOfTarget(GetGUID()) == aurApp); + + uint spellId = aurApp.GetBase().GetId(); + var range = m_appliedAuras.LookupByKey(spellId); + + foreach (var app in range) + { + if (app == aurApp) + { + _UnapplyAura(new KeyValuePair(spellId, app), removeMode); + return; + } + } + Contract.Assert(false); + } + + public AuraEffect GetAuraEffect(uint spellId, uint effIndex, ObjectGuid casterGUID = default(ObjectGuid)) + { + var range = m_appliedAuras.LookupByKey(spellId); + if (!range.Empty()) + { + foreach (var aura in range) + { + if (aura.HasEffect(effIndex) + && (casterGUID.IsEmpty() || aura.GetBase().GetCasterGUID() == casterGUID)) + { + return aura.GetBase().GetEffect(effIndex); + } + } + } + return null; + } + public AuraEffect GetAuraEffectOfRankedSpell(uint spellId, uint effIndex, ObjectGuid casterGUID = default(ObjectGuid)) + { + uint rankSpell = Global.SpellMgr.GetFirstSpellInChain(spellId); + while (rankSpell != 0) + { + AuraEffect aurEff = GetAuraEffect(rankSpell, effIndex, casterGUID); + if (aurEff != null) + return aurEff; + rankSpell = Global.SpellMgr.GetNextSpellInChain(rankSpell); + } + return null; + } + + // spell mustn't have familyflags + public AuraEffect GetAuraEffect(AuraType type, SpellFamilyNames family, FlagArray128 familyFlag, ObjectGuid casterGUID = default(ObjectGuid)) + { + var auras = GetAuraEffectsByType(type); + foreach (var aura in auras) + { + SpellInfo spell = aura.GetSpellInfo(); + if (spell.SpellFamilyName == family && spell.SpellFamilyFlags & familyFlag) + { + if (!casterGUID.IsEmpty() && aura.GetCasterGUID() != casterGUID) + continue; + return aura; + } + } + return null; + + } + + public AuraApplication GetAuraApplication(uint spellId, ObjectGuid casterGUID = default(ObjectGuid), ObjectGuid itemCasterGUID = default(ObjectGuid), uint reqEffMask = 0, AuraApplication except = null) + { + var range = m_appliedAuras.LookupByKey(spellId); + if (!range.Empty()) + { + foreach (var app in range) + { + Aura aura = app.GetBase(); + if (((aura.GetEffectMask() & reqEffMask) == reqEffMask) && (casterGUID.IsEmpty() || aura.GetCasterGUID() == casterGUID) + && (itemCasterGUID.IsEmpty() || aura.GetCastItemGUID() == itemCasterGUID) && (except == null || except != app)) + { + return app; + } + } + } + return null; + } + public Aura GetAura(uint spellId, ObjectGuid casterGUID = default(ObjectGuid), ObjectGuid itemCasterGUID = default(ObjectGuid), uint reqEffMask = 0) + { + AuraApplication aurApp = GetAuraApplication(spellId, casterGUID, itemCasterGUID, reqEffMask); + return aurApp?.GetBase(); + } + + uint BuildAuraStateUpdateForTarget(Unit target) + { + uint auraStates = GetUInt32Value(UnitFields.AuraState) & ~(uint)AuraStateType.PerCasterAuraStateMask; + foreach (var state in m_auraStateAuras) + if (Convert.ToBoolean((1 << (int)state.Key - 1) & (uint)AuraStateType.PerCasterAuraStateMask)) + if (state.Value.GetBase().GetCasterGUID() == target.GetGUID()) + auraStates |= (uint)(1 << (int)state.Key - 1); + + return auraStates; + } + + public bool CanProc() { return m_procDeep == 0; } + + public void _ApplyAuraEffect(Aura aura, uint effIndex) + { + Contract.Assert(aura != null); + Contract.Assert(aura.HasEffect(effIndex)); + AuraApplication aurApp = aura.GetApplicationOfTarget(GetGUID()); + Contract.Assert(aurApp != null); + if (aurApp.GetEffectMask() == 0) + _ApplyAura(aurApp, (uint)(1 << (int)effIndex)); + else + aurApp._HandleEffect(effIndex, true); + } + // handles effects of aura application + // should be done after registering aura in lists + public void _ApplyAura(AuraApplication aurApp, uint effMask) + { + Aura aura = aurApp.GetBase(); + + _RemoveNoStackAurasDueToAura(aura); + + if (aurApp.HasRemoveMode()) + return; + + // Update target aura state flag + AuraStateType aState = aura.GetSpellInfo().GetAuraState(GetMap().GetDifficultyID()); + if (aState != 0) + ModifyAuraState(aState, true); + + if (aurApp.HasRemoveMode()) + return; + + // Sitdown on apply aura req seated + if (aura.GetSpellInfo().AuraInterruptFlags.HasAnyFlag(SpellAuraInterruptFlags.NotSeated) && !IsSitState()) + SetStandState(UnitStandStateType.Sit); + + Unit caster = aura.GetCaster(); + + if (aurApp.HasRemoveMode()) + return; + + aura.HandleAuraSpecificMods(aurApp, caster, true, false); + aura.HandleAuraSpecificPeriodics(aurApp, caster); + + // apply effects of the aura + for (byte i = 0; i < SpellConst.MaxEffects; i++) + { + if (Convert.ToBoolean(effMask & 1 << i) && !(aurApp.HasRemoveMode())) + aurApp._HandleEffect(i, true); + } + } + public void _AddAura(UnitAura aura, Unit caster) + { + Contract.Assert(!m_cleanupDone); + m_ownedAuras.Add(aura.GetId(), aura); + + _RemoveNoStackAurasDueToAura(aura); + + if (aura.IsRemoved()) + return; + + aura.SetIsSingleTarget(caster != null && aura.GetSpellInfo().IsSingleTarget() || aura.HasEffectType(AuraType.ControlVehicle)); + if (aura.IsSingleTarget()) + { + + // @HACK: Player is not in world during loading auras. + //Single target auras are not saved or loaded from database + //but may be created as a result of aura links (player mounts with passengers) + Contract.Assert((IsInWorld && !IsDuringRemoveFromWorld()) || (aura.GetCasterGUID() == GetGUID()) || (IsLoading() && aura.HasEffectType(AuraType.ControlVehicle))); + + // register single target aura + caster.m_scAuras.Add(aura); + // remove other single target auras + var scAuras = caster.GetSingleCastAuras(); + for (var i = 0; i < scAuras.Count; ++i) + { + var aur = scAuras[i]; + if (aur != aura && aur.IsSingleTargetWith(aura)) + aur.Remove(); + + } + } + } + public Aura _TryStackingOrRefreshingExistingAura(SpellInfo newAura, uint effMask, Unit caster, int[] baseAmount = null, Item castItem = null, ObjectGuid casterGUID = default(ObjectGuid), int castItemLevel = -1) + { + Contract.Assert(!casterGUID.IsEmpty() || caster); + + // Check if these can stack anyway + if (casterGUID.IsEmpty() && !newAura.IsStackableOnOneSlotWithDifferentCasters()) + casterGUID = caster.GetGUID(); + + // passive and Incanter's Absorption and auras with different type can stack with themselves any number of times + if (!newAura.IsMultiSlotAura()) + { + // check if cast item changed + ObjectGuid castItemGUID = ObjectGuid.Empty; + if (castItem != null) + { + castItemGUID = castItem.GetGUID(); + castItemLevel = (int)castItem.GetItemLevel(castItem.GetOwner()); + } + + // find current aura from spell and change it's stackamount, or refresh it's duration + Aura foundAura = GetOwnedAura(newAura.Id, casterGUID, (newAura.HasAttribute(SpellCustomAttributes.EnchantProc) ? castItemGUID : ObjectGuid.Empty), 0); + if (foundAura != null) + { + // effect masks do not match + // extremely rare case + // let's just recreate aura + if (effMask != foundAura.GetEffectMask()) + return null; + + // update basepoints with new values - effect amount will be recalculated in ModStackAmount + foreach (SpellEffectInfo effect in foundAura.GetSpellEffectInfos()) + { + if (effect == null) + continue; + + AuraEffect eff = foundAura.GetEffect(effect.EffectIndex); + if (eff == null) + continue; + + int bp; + if (baseAmount != null) + bp = baseAmount[effect.EffectIndex]; + else + bp = effect.BasePoints; + + int oldBP = eff.m_baseAmount; + oldBP = bp; + } + + // correct cast item guid if needed + if (castItemGUID != foundAura.GetCastItemGUID()) + { + castItemGUID = foundAura.GetCasterGUID(); + castItemLevel = foundAura.GetCastItemLevel(); + } + + // try to increase stack amount + foundAura.ModStackAmount(1); + return foundAura; + } + } + + return null; + } + + void _RemoveNoStackAurasDueToAura(Aura aura) + { + SpellInfo spellProto = aura.GetSpellInfo(); + + // passive spell special case (only non stackable with ranks) + if (spellProto.IsPassiveStackableWithRanks()) + return; + + if (!IsHighestExclusiveAura(aura)) + { + if (!aura.GetSpellInfo().IsAffectingArea(GetMap().GetDifficultyID())) + { + Unit caster = aura.GetCaster(); + if (caster && caster.IsTypeId(TypeId.Player)) + Spell.SendCastResult(caster.ToPlayer(), aura.GetSpellInfo(), aura.GetSpellXSpellVisualId(), aura.GetCastGUID(), SpellCastResult.AuraBounced); + } + + aura.Remove(); + return; + } + + bool remove = false; + for (var i = 0; i < m_appliedAuras.KeyValueList.Count; i++) + { + var app = m_appliedAuras.KeyValueList[i]; + if (remove) + { + remove = false; + i = 0; + } + + if (aura.CanStackWith(app.Value.GetBase())) + continue; + + RemoveAura(app, AuraRemoveMode.Default); + if (i == m_appliedAuras.KeyValueList.Count - 1) + break; + remove = true; + } + } + public int GetHighestExclusiveSameEffectSpellGroupValue(AuraEffect aurEff, AuraType auraType, bool checkMiscValue = false, int miscValue = 0) + { + int val = 0; + var spellGroupList = Global.SpellMgr.GetSpellSpellGroupMapBounds(aurEff.GetSpellInfo().GetFirstRankSpell().Id); + foreach (var spellGroup in spellGroupList) + { + if (Global.SpellMgr.GetSpellGroupStackRule(spellGroup) == SpellGroupStackRule.ExclusiveSameEffect) + { + var auraEffList = GetAuraEffectsByType(auraType); + foreach (var auraEffect in auraEffList) + { + if (aurEff != auraEffect && (!checkMiscValue || auraEffect.GetMiscValue() == miscValue) && + Global.SpellMgr.IsSpellMemberOfSpellGroup(auraEffect.GetSpellInfo().Id, spellGroup)) + { + // absolute value only + if (Math.Abs(val) < Math.Abs(auraEffect.GetAmount())) + val = auraEffect.GetAmount(); + } + } + } + } + return val; + } + + void UpdateLastDamagedTime(SpellInfo spellProto) + { + if (!IsTypeId(TypeId.Unit) || IsPet()) + return; + + if (spellProto != null && spellProto.HasAura(Difficulty.None, AuraType.DamageShield)) + return; + + SetLastDamagedTime(Time.UnixTime); + } + + public bool IsHighestExclusiveAura(Aura aura, bool removeOtherAuraApplications = false) + { + foreach (AuraEffect aurEff in aura.GetAuraEffects()) + { + if (aurEff == null) + continue; + + AuraType auraType = aurEff.GetSpellEffectInfo().ApplyAuraName; + var auras = GetAuraEffectsByType(auraType); + for (var i = 0; i < auras.Count;) + { + AuraEffect existingAurEff = auras[i]; + ++i; + + if (Global.SpellMgr.CheckSpellGroupStackRules(aura.GetSpellInfo(), existingAurEff.GetSpellInfo()) == SpellGroupStackRule.ExclusiveHighest) + { + int diff = Math.Abs(aurEff.GetAmount()) - Math.Abs(existingAurEff.GetAmount()); + if (diff == 0) + diff = (int)(aura.GetEffectMask() - existingAurEff.GetBase().GetEffectMask()); + + if (diff > 0) + { + Aura auraBase = existingAurEff.GetBase(); + // no removing of area auras from the original owner, as that completely cancels them + if (removeOtherAuraApplications && (!auraBase.IsArea() || auraBase.GetOwner() != this)) + { + AuraApplication aurApp = existingAurEff.GetBase().GetApplicationOfTarget(GetGUID()); + if (aurApp != null) + { + bool hasMoreThanOneEffect = auraBase.HasMoreThanOneEffectForType(auraType); + uint removedAuras = m_removedAurasCount; + RemoveAura(aurApp); + if (hasMoreThanOneEffect || m_removedAurasCount > removedAuras + 1) + i = 0; + } + } + } + else if (diff < 0) + return false; + } + } + + } + + return true; + } + + public Aura GetOwnedAura(uint spellId, ObjectGuid casterGUID = default(ObjectGuid), ObjectGuid itemCasterGUID = default(ObjectGuid), uint reqEffMask = 0, Aura except = null) + { + var range = m_ownedAuras.LookupByKey(spellId); + foreach (var aura in range) + { + if (((aura.GetEffectMask() & reqEffMask) == reqEffMask) && (casterGUID.IsEmpty() || aura.GetCasterGUID() == casterGUID) + && (itemCasterGUID.IsEmpty() || aura.GetCastItemGUID() == itemCasterGUID) && (except == null || except != aura)) + { + return aura; + } + } + return null; + } + + public List GetAuraEffectsByType(AuraType type) + { + return m_modAuras.LookupByKey(type); + } + public int GetMaxPositiveAuraModifierByMiscMask(AuraType auratype, uint miscMask, AuraEffect except = null) + { + int modifier = 0; + + var mTotalAuraList = GetAuraEffectsByType(auratype); + foreach (var eff in mTotalAuraList) + { + if (except != eff && Convert.ToBoolean(eff.GetMiscValue() & miscMask) && eff.GetAmount() > modifier) + modifier = eff.GetAmount(); + } + + return modifier; + } + public int GetMaxNegativeAuraModifierByMiscMask(AuraType auratype, uint miscMask) + { + int modifier = 0; + + var mTotalAuraList = GetAuraEffectsByType(auratype); + foreach (var eff in mTotalAuraList) + { + if (Convert.ToBoolean(eff.GetMiscValue() & miscMask) && eff.GetAmount() < modifier) + modifier = eff.GetAmount(); + } + + return modifier; + } + public int GetTotalAuraModifier(AuraType auratype) + { + Dictionary SameEffectSpellGroup = new Dictionary(); + int modifier = 0; + + var mTotalAuraList = GetAuraEffectsByType(auratype); + foreach (var aura in mTotalAuraList) + if (!Global.SpellMgr.AddSameEffectStackRuleSpellGroups(aura.GetSpellInfo(), aura.GetAmount(), out SameEffectSpellGroup)) + modifier += aura.GetAmount(); + + foreach (var pair in SameEffectSpellGroup) + modifier += pair.Value; + + return modifier; + } + public float GetTotalAuraMultiplier(AuraType auratype) + { + float multiplier = 1.0f; + + var mTotalAuraList = GetAuraEffectsByType(auratype); + foreach (var aura in mTotalAuraList) + MathFunctions.AddPct(ref multiplier, aura.GetAmount()); + + return multiplier; + } + + public void _RegisterAuraEffect(AuraEffect aurEff, bool apply) + { + if (apply) + m_modAuras.Add(aurEff.GetAuraType(), aurEff); + else + m_modAuras.Remove(aurEff.GetAuraType(), aurEff); + } + public float GetTotalAuraModValue(UnitMods unitMod) + { + if (unitMod >= UnitMods.End) + { + Log.outError(LogFilter.Unit, "attempt to access non-existing UnitMods in GetTotalAuraModValue()!"); + return 0.0f; + } + + if (m_auraModifiersGroup[(int)unitMod][(int)UnitModifierType.TotalPCT] <= 0.0f) + return 0.0f; + + float value = MathFunctions.CalculatePct(m_auraModifiersGroup[(int)unitMod][(int)UnitModifierType.BaseValue], Math.Max(m_auraModifiersGroup[(int)unitMod][(int)UnitModifierType.BasePCTExcludeCreate], -100.0f)); + value *= m_auraModifiersGroup[(int)unitMod][(int)UnitModifierType.BasePCT]; + value += m_auraModifiersGroup[(int)unitMod][(int)UnitModifierType.TotalValue]; + value *= m_auraModifiersGroup[(int)unitMod][(int)UnitModifierType.TotalPCT]; + + return value; + } + + public void SetVisibleAura(AuraApplication aurApp) + { + m_visibleAuras.Add(aurApp); + m_visibleAurasToUpdate.Add(aurApp); + UpdateAuraForGroup(); + } + + public void RemoveVisibleAura(AuraApplication aurApp) + { + m_visibleAuras.Remove(aurApp); + m_visibleAurasToUpdate.Remove(aurApp); + UpdateAuraForGroup(); + } + + void UpdateAuraForGroup() + { + Player player = ToPlayer(); + if (player != null) + { + if (player.GetGroup() != null) + player.SetGroupUpdateFlag(GroupUpdateFlags.Auras); + } + else if (IsPet()) + { + Pet pet = ToPet(); + if (pet.isControlled()) + pet.SetGroupUpdateFlag(GroupUpdatePetFlags.Auras); + } + } + + public SortedSet GetVisibleAuras() { return m_visibleAuras; } + public bool HasVisibleAura(AuraApplication aurApp) { return m_visibleAuras.Contains(aurApp); } + public void SetVisibleAuraUpdate(AuraApplication aurApp) { m_visibleAurasToUpdate.Add(aurApp); } + } +} diff --git a/Game/Entities/Unit/Unit.cs b/Game/Entities/Unit/Unit.cs new file mode 100644 index 000000000..148926d19 --- /dev/null +++ b/Game/Entities/Unit/Unit.cs @@ -0,0 +1,2942 @@ +/* + * Copyright (C) 2012-2017 CypherCore + * + * 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, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +using Framework.Constants; +using Framework.Dynamic; +using Framework.GameMath; +using Framework.IO; +using Game.AI; +using Game.BattleGrounds; +using Game.Chat; +using Game.Combat; +using Game.DataStorage; +using Game.Maps; +using Game.Movement; +using Game.Network.Packets; +using Game.Spells; +using System; +using System.Collections.Generic; +using System.Diagnostics.Contracts; +using System.Linq; + +namespace Game.Entities +{ + public partial class Unit : WorldObject + { + public Unit(bool isWorldObject) : base(isWorldObject) + { + moveSpline = new MoveSpline(); + i_motionMaster = new MotionMaster(this); + threatManager = new ThreatManager(this); + m_unitTypeMask = UnitTypeMask.None; + m_HostileRefManager = new HostileRefManager(this); + _spellHistory = new SpellHistory(this); + m_FollowingRefManager = new RefManager(); + + objectTypeId = TypeId.Unit; + objectTypeMask |= TypeMask.Unit; + m_updateFlag = UpdateFlag.Living; + + m_modAttackSpeedPct = new float[] { 1.0f, 1.0f, 1.0f }; + m_deathState = DeathState.Alive; + + for (byte i = 0; i < (int)UnitMods.End; ++i) + m_auraModifiersGroup[i] = new float[] { 0.0f, 100.0f, 1.0f, 0.0f, 1.0f }; + + m_auraModifiersGroup[(int)UnitMods.DamageOffHand][(int)UnitModifierType.TotalPCT] = 0.5f; + + foreach (AuraType auraType in Enum.GetValues(typeof(AuraType))) + m_modAuras[auraType] = new List(); + + for (byte i = 0; i < (int)WeaponAttackType.Max; ++i) + m_weaponDamage[i] = new float[] { 1.0f, 2.0f }; + + if (IsTypeId(TypeId.Player)) + { + m_modMeleeHitChance = 7.5f; + m_modRangedHitChance = 7.5f; + m_modSpellHitChance = 15.0f; + } + m_baseSpellCritChance = 5; + + for (byte i = 0; i < (int)SpellSchools.Max; ++i) + m_threatModifier[i] = 1.0f; + + for (byte i = 0; i < (int)UnitMoveType.Max; ++i) + m_speed_rate[i] = 1.0f; + + _redirectThreatInfo = new RedirectThreatInfo(); + m_serverSideVisibility.SetValue(ServerSideVisibilityType.Ghost, GhostVisibilityType.Alive); + + movesplineTimer = new TimeTrackerSmall(); + } + + public override void Dispose() + { + // set current spells as deletable + for (CurrentSpellTypes i = 0; i < CurrentSpellTypes.Max; ++i) + { + if (m_currentSpells.ContainsKey(i)) + { + if (m_currentSpells[i] != null) + { + m_currentSpells[i].SetReferencedFromCurrent(false); + m_currentSpells[i] = null; + } + } + } + + m_Events.KillAllEvents(true); + + _DeleteRemovedAuras(); + + //i_motionMaster = null; + m_charmInfo = null; + moveSpline = null; + _spellHistory = null; + + /*ASSERT(!m_duringRemoveFromWorld); + ASSERT(!m_attacking); + ASSERT(m_attackers.empty()); + ASSERT(m_sharedVision.empty()); + ASSERT(m_Controlled.empty()); + ASSERT(m_appliedAuras.empty()); + ASSERT(m_ownedAuras.empty()); + ASSERT(m_removedAuras.empty()); + ASSERT(m_gameObj.empty()); + ASSERT(m_dynObj.empty());*/ + + base.Dispose(); + } + + public override void Update(uint diff) + { + // WARNING! Order of execution here is important, do not change. + // Spells must be processed with event system BEFORE they go to _UpdateSpells. + m_Events.Update(diff); + + if (!IsInWorld) + return; + + _UpdateSpells(diff); + + // If this is set during update SetCantProc(false) call is missing somewhere in the code + // Having this would prevent spells from being proced, so let's crash + Contract.Assert(m_procDeep == 0); + + if (CanHaveThreatList() && GetThreatManager().isNeedUpdateToClient(diff)) + SendThreatListUpdate(); + + // update combat timer only for players and pets (only pets with PetAI) + if (IsInCombat() && (IsTypeId(TypeId.Player) || (IsPet() && IsControlledByPlayer()))) + { + // Check UNIT_STATE_MELEE_ATTACKING or UNIT_STATE_CHASE (without UNIT_STATE_FOLLOW in this case) so pets can reach far away + // targets without stopping half way there and running off. + // These flags are reset after target dies or another command is given. + if (m_HostileRefManager.isEmpty()) + { + // m_CombatTimer set at aura start and it will be freeze until aura removing + if (m_CombatTimer <= diff) + ClearInCombat(); + else + m_CombatTimer -= diff; + } + } + + uint att; + // not implemented before 3.0.2 + if ((att = getAttackTimer(WeaponAttackType.BaseAttack)) != 0) + setAttackTimer(WeaponAttackType.BaseAttack, (diff >= att ? 0 : att - diff)); + if ((att = getAttackTimer(WeaponAttackType.RangedAttack)) != 0) + setAttackTimer(WeaponAttackType.RangedAttack, (diff >= att ? 0 : att - diff)); + if ((att = getAttackTimer(WeaponAttackType.OffAttack)) != 0) + setAttackTimer(WeaponAttackType.OffAttack, (diff >= att ? 0 : att - diff)); + + // update abilities available only for fraction of time + UpdateReactives(diff); + + if (IsAlive()) + { + ModifyAuraState(AuraStateType.HealthLess20Percent, HealthBelowPct(20)); + ModifyAuraState(AuraStateType.HealthLess35Percent, HealthBelowPct(35)); + ModifyAuraState(AuraStateType.HealthAbove75Percent, HealthAbovePct(75)); + } + + UpdateSplineMovement(diff); + GetMotionMaster().UpdateMotion(diff); + } + void _UpdateSpells(uint diff) + { + if (GetCurrentSpell(CurrentSpellTypes.AutoRepeat) != null) + _UpdateAutoRepeatSpell(); + + for (CurrentSpellTypes i = 0; i < CurrentSpellTypes.Max; ++i) + { + if (GetCurrentSpell(i) != null && m_currentSpells[i].getState() == SpellState.Finished) + { + m_currentSpells[i].SetReferencedFromCurrent(false); + m_currentSpells[i] = null; + } + } + + foreach (var app in GetOwnedAuras()) + { + Aura i_aura = app.Value; + if (i_aura == null) + continue; + i_aura.UpdateOwner(diff, this); + } + + // remove expired auras - do that after updates(used in scripts?) + foreach (var aura in GetOwnedAuras()) + { + if (aura.Value != null && aura.Value.IsExpired()) + RemoveOwnedAura(aura, AuraRemoveMode.Expire); + } + + foreach (var aura in m_visibleAurasToUpdate) + aura.ClientUpdate(); + + m_visibleAurasToUpdate.Clear(); + + _DeleteRemovedAuras(); + + if (!m_gameObj.Empty()) + { + foreach (var go in m_gameObj.ToList()) + { + if (!go.isSpawned()) + { + go.SetOwnerGUID(ObjectGuid.Empty); + go.SetRespawnTime(0); + go.Delete(); + m_gameObj.Remove(go); + } + } + } + + _spellHistory.Update(); + } + + public void HandleEmoteCommand(Emote anim_id) + { + EmoteMessage packet = new EmoteMessage(); + packet.Guid = GetGUID(); + packet.EmoteID = (int)anim_id; + SendMessageToSet(packet, true); + } + public void SendDurabilityLoss(Player receiver, uint percent) + { + DurabilityDamageDeath packet = new DurabilityDamageDeath(); + packet.Percent = percent; + receiver.SendPacket(packet); + } + + public bool IsInDisallowedMountForm() + { + SpellInfo transformSpellInfo = Global.SpellMgr.GetSpellInfo(getTransForm()); + if (transformSpellInfo != null) + if (transformSpellInfo.HasAttribute(SpellAttr0.CastableWhileMounted)) + return false; + + ShapeShiftForm form = GetShapeshiftForm(); + if (form != 0) + { + SpellShapeshiftFormRecord shapeshift = CliDB.SpellShapeshiftFormStorage.LookupByKey(form); + if (shapeshift == null) + return true; + + if (!shapeshift.Flags.HasAnyFlag(SpellShapeshiftFormFlags.IsNotAShapeshift)) + return true; + } + if (GetDisplayId() == GetNativeDisplayId()) + return false; + + CreatureDisplayInfoRecord display = CliDB.CreatureDisplayInfoStorage.LookupByKey(GetDisplayId()); + if (display == null) + return true; + + CreatureDisplayInfoExtraRecord displayExtra = CliDB.CreatureDisplayInfoExtraStorage.LookupByKey(display.ExtendedDisplayInfoID); + if (displayExtra == null) + return true; + + CreatureModelDataRecord model = CliDB.CreatureModelDataStorage.LookupByKey(display.ModelID); + ChrRacesRecord race = CliDB.ChrRacesStorage.LookupByKey(displayExtra.DisplayRaceID); + + if (model != null && !Convert.ToBoolean(model.Flags & 0x80)) + if (race != null && !Convert.ToBoolean(race.Flags & 0x4)) + return true; + + return false; + } + + public void SendClearTarget() + { + BreakTarget breakTarget = new BreakTarget(); + breakTarget.UnitGUID = GetGUID(); + SendMessageToSet(breakTarget, false); + } + public virtual bool IsLoading() { return false; } + public bool IsDuringRemoveFromWorld() { return m_duringRemoveFromWorld; } + + //SharedVision + public bool HasSharedVision() { return !m_sharedVision.Empty(); } + public List GetSharedVisionList() { return m_sharedVision; } + + public void AddPlayerToVision(Player player) + { + if (m_sharedVision.Empty()) + { + setActive(true); + SetWorldObject(true); + } + m_sharedVision.Add(player); + } + + // only called in Player.SetSeer + public void RemovePlayerFromVision(Player player) + { + m_sharedVision.Remove(player); + if (m_sharedVision.Empty()) + { + setActive(false); + SetWorldObject(false); + } + } + + public virtual void Talk(string text, ChatMsg msgType, Language language, float textRange, WorldObject target) + { + var builder = new CustomChatTextBuilder(this, msgType, text, language, target); + var localizer = new LocalizedPacketDo(builder); + var worker = new PlayerDistWorker(this, textRange, localizer); + Cell.VisitWorldObjects(this, worker, textRange); + } + + public virtual void Say(string text, Language language, WorldObject target = null) + { + Talk(text, ChatMsg.MonsterSay, language, WorldConfig.GetFloatValue(WorldCfg.ListenRangeSay), target); + } + + public virtual void Yell(string text, Language language, WorldObject target = null) + { + Talk(text, ChatMsg.MonsterYell, language, WorldConfig.GetFloatValue(WorldCfg.ListenRangeYell), target); + } + + public virtual void TextEmote(string text, WorldObject target = null, bool isBossEmote = false) + { + Talk(text, isBossEmote ? ChatMsg.RaidBossEmote : ChatMsg.MonsterEmote, Language.Universal, WorldConfig.GetFloatValue(WorldCfg.ListenRangeTextemote), target); + } + + public virtual void Whisper(string text, Language language, Player target, bool isBossWhisper = false) + { + if (!target) + return; + + LocaleConstant locale = target.GetSession().GetSessionDbLocaleIndex(); + ChatPkt data = new ChatPkt(); + data.Initialize(isBossWhisper ? ChatMsg.RaidBossWhisper : ChatMsg.MonsterWhisper, Language.Universal, this, target, text, 0, "", locale); + target.SendPacket(data); + } + + public void Talk(uint textId, ChatMsg msgType, float textRange, WorldObject target) + { + if (!CliDB.BroadcastTextStorage.ContainsKey(textId)) + { + Log.outError(LogFilter.Unit, "Unit.Talk: `broadcast_text` was not {0} found", textId); + return; + } + + var builder = new BroadcastTextBuilder(this, msgType, textId, target); + var localizer = new LocalizedPacketDo(builder); + var worker = new PlayerDistWorker(this, textRange, localizer); + Cell.VisitWorldObjects(this, worker, textRange); + } + + public virtual void Say(uint textId, WorldObject target = null) + { + Talk(textId, ChatMsg.MonsterSay, WorldConfig.GetFloatValue(WorldCfg.ListenRangeSay), target); + } + + public virtual void Yell(uint textId, WorldObject target = null) + { + Talk(textId, ChatMsg.MonsterYell, WorldConfig.GetFloatValue(WorldCfg.ListenRangeYell), target); + } + + public virtual void TextEmote(uint textId, WorldObject target = null, bool isBossEmote = false) + { + Talk(textId, isBossEmote ? ChatMsg.RaidBossEmote : ChatMsg.MonsterEmote, WorldConfig.GetFloatValue(WorldCfg.ListenRangeTextemote), target); + } + + public virtual void Whisper(uint textId, Player target, bool isBossWhisper = false) + { + if (!target) + return; + + BroadcastTextRecord bct = CliDB.BroadcastTextStorage.LookupByKey(textId); + if (bct == null) + { + Log.outError(LogFilter.Unit, "Unit.Whisper: `broadcast_text` was not {0} found", textId); + return; + } + + LocaleConstant locale = target.GetSession().GetSessionDbLocaleIndex(); + ChatPkt data = new ChatPkt(); + data.Initialize(isBossWhisper ? ChatMsg.RaidBossWhisper : ChatMsg.MonsterWhisper, Language.Universal, this, target, Global.DB2Mgr.GetBroadcastTextValue(bct, locale, GetGender()), 0, "", locale); + target.SendPacket(data); + } + + public override void UpdateObjectVisibility(bool forced = true) + { + if (!forced) + AddToNotify(NotifyFlags.VisibilityChanged); + else + { + base.UpdateObjectVisibility(true); + // call MoveInLineOfSight for nearby creatures + AIRelocationNotifier notifier = new AIRelocationNotifier(this); + Cell.VisitAllObjects(this, notifier, GetVisibilityRange()); + } + } + + public override void AddToWorld() + { + if (!IsInWorld) + { + base.AddToWorld(); + } + RebuildTerrainSwaps(); + } + + public override void RemoveFromWorld() + { + // cleanup + + if (IsInWorld) + { + m_duringRemoveFromWorld = true; + if (IsVehicle()) + RemoveVehicleKit(true); + + RemoveCharmAuras(); + RemoveAurasByType(AuraType.BindSight); + RemoveNotOwnSingleTargetAuras(); + + RemoveAllGameObjects(); + RemoveAllDynObjects(); + RemoveAllAreaTriggers(); + + ExitVehicle(); // Remove applied auras with SPELL_AURA_CONTROL_VEHICLE + UnsummonAllTotems(); + RemoveAllControlled(); + + RemoveAreaAurasDueToLeaveWorld(); + + if (!GetCharmerGUID().IsEmpty()) + { + Log.outFatal(LogFilter.Unit, "Unit {0} has charmer guid when removed from world", GetEntry()); + } + Unit owner = GetOwner(); + if (owner != null) + { + if (owner.m_Controlled.Contains(this)) + { + Log.outFatal(LogFilter.Unit, "Unit {0} is in controlled list of {1} when removed from world", GetEntry(), owner.GetEntry()); + } + } + + base.RemoveFromWorld(); + m_duringRemoveFromWorld = false; + } + } + + public void CleanupBeforeRemoveFromMap(bool finalCleanup) + { + // This needs to be before RemoveFromWorld to make GetCaster() return a valid for aura removal + InterruptNonMeleeSpells(true); + + if (IsInWorld) + RemoveFromWorld(); + + // A unit may be in removelist and not in world, but it is still in grid + // and may have some references during delete + RemoveAllAuras(); + RemoveAllGameObjects(); + + if (finalCleanup) + m_cleanupDone = true; + + m_Events.KillAllEvents(false); // non-delatable (currently casted spells) will not deleted now but it will deleted at call in Map.RemoveAllObjectsInRemoveList + CombatStop(); + DeleteThreatList(); + getHostileRefManager().setOnlineOfflineState(false); + GetMotionMaster().Clear(false); // remove different non-standard movement generators. + } + public override void CleanupsBeforeDelete(bool finalCleanup = true) + { + CleanupBeforeRemoveFromMap(finalCleanup); + + base.CleanupsBeforeDelete(finalCleanup); + } + + public void setTransForm(uint spellid) { m_transform = spellid; } + public uint GetTransForm() { return m_transform; } + + public Vehicle GetVehicleKit() { return m_vehicleKit; } + public Vehicle GetVehicle() { return m_vehicle; } + public void SetVehicle(Vehicle vehicle) { m_vehicle = vehicle; } + public Unit GetVehicleBase() + { + return m_vehicle != null ? m_vehicle.GetBase() : null; + } + public Creature GetVehicleCreatureBase() + { + Unit veh = GetVehicleBase(); + if (veh != null) + { + Creature c = veh.ToCreature(); + if (c != null) + return c; + } + return null; + } + public ITransport GetDirectTransport() + { + Vehicle veh = GetVehicle(); + if (veh != null) + return veh; + return GetTransport(); + } + + public void _RegisterDynObject(DynamicObject dynObj) + { + m_dynObj.Add(dynObj); + if (IsTypeId(TypeId.Unit) && IsAIEnabled) + ToCreature().GetAI().JustRegisteredDynObject(dynObj); + } + + public void _UnregisterDynObject(DynamicObject dynObj) + { + m_dynObj.Remove(dynObj); + if (IsTypeId(TypeId.Unit) && IsAIEnabled) + ToCreature().GetAI().JustUnregisteredDynObject(dynObj); + } + + public DynamicObject GetDynObject(uint spellId) + { + return GetDynObjects(spellId).FirstOrDefault(); + } + + List GetDynObjects(uint spellId) + { + List dynamicobjects = new List(); + foreach (var obj in m_dynObj) + if (obj.GetSpellId() == spellId) + dynamicobjects.Add(obj); + + return dynamicobjects; + } + + public void RemoveDynObject(uint spellId) + { + foreach (var dynObj in m_dynObj.ToList()) + { + if (dynObj.GetSpellId() == spellId) + dynObj.Remove(); + } + } + + public void RemoveAllDynObjects() + { + while (!m_dynObj.Empty()) + m_dynObj.First().Remove(); + } + + public GameObject GetGameObject(uint spellId) + { + return GetGameObjects(spellId).FirstOrDefault(); + } + + List GetGameObjects(uint spellId) + { + List gameobjects = new List(); + foreach (var obj in m_gameObj) + if (obj.GetSpellId() == spellId) + gameobjects.Add(obj); + + return gameobjects; + } + + public void AddGameObject(GameObject gameObj) + { + if (gameObj == null || !gameObj.GetOwnerGUID().IsEmpty()) + return; + + m_gameObj.Add(gameObj); + gameObj.SetOwnerGUID(GetGUID()); + + if (gameObj.GetSpellId() != 0) + { + SpellInfo createBySpell = Global.SpellMgr.GetSpellInfo(gameObj.GetSpellId()); + // Need disable spell use for owner + if (createBySpell != null && createBySpell.HasAttribute(SpellAttr0.DisabledWhileActive)) + // note: item based cooldowns and cooldown spell mods with charges ignored (unknown existing cases) + GetSpellHistory().StartCooldown(createBySpell, 0, null, true); + } + + if (IsTypeId(TypeId.Unit) && ToCreature().IsAIEnabled) + ToCreature().GetAI().JustSummonedGameobject(gameObj); + } + + public void RemoveGameObject(GameObject gameObj, bool del) + { + if (gameObj == null || gameObj.GetOwnerGUID() != GetGUID()) + return; + + gameObj.SetOwnerGUID(ObjectGuid.Empty); + + for (byte i = 0; i < SharedConst.MaxGameObjectSlot; ++i) + { + if (m_ObjectSlot[i] == gameObj.GetGUID()) + { + m_ObjectSlot[i].Clear(); + break; + } + } + + // GO created by some spell + uint spellid = gameObj.GetSpellId(); + if (spellid != 0) + { + RemoveAurasDueToSpell(spellid); + + SpellInfo createBySpell = Global.SpellMgr.GetSpellInfo(spellid); + // Need activate spell use for owner + if (createBySpell != null && createBySpell.IsCooldownStartedOnEvent()) + // note: item based cooldowns and cooldown spell mods with charges ignored (unknown existing cases) + GetSpellHistory().SendCooldownEvent(createBySpell); + } + + m_gameObj.Remove(gameObj); + + if (IsTypeId(TypeId.Unit) && ToCreature().IsAIEnabled) + ToCreature().GetAI().SummonedGameobjectDespawn(gameObj); + + if (del) + { + gameObj.SetRespawnTime(0); + gameObj.Delete(); + } + } + + public void RemoveGameObject(uint spellid, bool del) + { + if (m_gameObj.Empty()) + return; + + foreach (var obj in m_gameObj) + { + if (spellid == 0 || obj.GetSpellId() == spellid) + { + obj.SetOwnerGUID(ObjectGuid.Empty); + if (del) + { + obj.SetRespawnTime(0); + obj.Delete(); + } + } + } + } + + void RemoveAllGameObjects() + { + // remove references to unit + while (!m_gameObj.Empty()) + { + var obj = m_gameObj.First(); + obj.SetOwnerGUID(ObjectGuid.Empty); + obj.SetRespawnTime(0); + obj.Delete(); + m_gameObj.Remove(obj); + } + } + + public void _RegisterAreaTrigger(AreaTrigger areaTrigger) + { + m_areaTrigger.Add(areaTrigger); + if (IsTypeId(TypeId.Unit) && IsAIEnabled) + ToCreature().GetAI().JustRegisteredAreaTrigger(areaTrigger); + } + + public void _UnregisterAreaTrigger(AreaTrigger areaTrigger) + { + m_areaTrigger.Remove(areaTrigger); + if (IsTypeId(TypeId.Unit) && IsAIEnabled) + ToCreature().GetAI().JustUnregisteredAreaTrigger(areaTrigger); + } + + AreaTrigger GetAreaTrigger(uint spellId) + { + List areaTriggers = GetAreaTriggers(spellId); + return areaTriggers.Empty() ? null : areaTriggers[0]; + } + + public List GetAreaTriggers(uint spellId) + { + return m_areaTrigger.Where(trigger => trigger.GetSpellId() == spellId).ToList(); + } + + public void RemoveAreaTrigger(uint spellId) + { + if (m_areaTrigger.Empty()) + return; + + foreach (var areaTrigger in m_areaTrigger.ToList()) + { + if (areaTrigger.GetSpellId() == spellId) + areaTrigger.Remove(); + } + } + + public void RemoveAreaTrigger(AuraEffect aurEff) + { + if (m_areaTrigger.Empty()) + return; + foreach (AreaTrigger areaTrigger in m_areaTrigger) + { + if (areaTrigger.GetAuraEffect() == aurEff) + { + areaTrigger.Remove(); + break; // There can only be one AreaTrigger per AuraEffect + } + } + } + + public void RemoveAllAreaTriggers() + { + while (!m_areaTrigger.Empty()) + m_areaTrigger[0].Remove(); + } + + public bool IsVendor() { return HasFlag64(UnitFields.NpcFlags, NPCFlags.Vendor); } + public bool IsTrainer() { return HasFlag64(UnitFields.NpcFlags, NPCFlags.Trainer); } + public bool IsQuestGiver() { return HasFlag64(UnitFields.NpcFlags, NPCFlags.QuestGiver); } + public bool IsGossip() { return HasFlag64(UnitFields.NpcFlags, NPCFlags.Gossip); } + public bool IsTaxi() { return HasFlag64(UnitFields.NpcFlags, NPCFlags.FlightMaster); } + public bool IsGuildMaster() { return HasFlag64(UnitFields.NpcFlags, NPCFlags.Petitioner); } + public bool IsBattleMaster() { return HasFlag64(UnitFields.NpcFlags, NPCFlags.BattleMaster); } + public bool IsBanker() { return HasFlag64(UnitFields.NpcFlags, NPCFlags.Banker); } + public bool IsInnkeeper() { return HasFlag64(UnitFields.NpcFlags, NPCFlags.Innkeeper); } + public bool IsSpiritHealer() { return HasFlag64(UnitFields.NpcFlags, NPCFlags.SpiritHealer); } + public bool IsSpiritGuide() { return HasFlag64(UnitFields.NpcFlags, NPCFlags.SpiritGuide); } + public bool IsTabardDesigner() { return HasFlag64(UnitFields.NpcFlags, NPCFlags.TabardDesigner); } + public bool IsAuctioner() { return HasFlag64(UnitFields.NpcFlags, NPCFlags.Auctioneer); } + public bool IsArmorer() { return HasFlag64(UnitFields.NpcFlags, NPCFlags.Repair); } + public bool IsServiceProvider() + { + return HasFlag64(UnitFields.NpcFlags, + NPCFlags.Vendor | NPCFlags.Trainer | NPCFlags.FlightMaster | + NPCFlags.Petitioner | NPCFlags.BattleMaster | NPCFlags.Banker | + NPCFlags.Innkeeper | NPCFlags.SpiritHealer | + NPCFlags.SpiritGuide | NPCFlags.TabardDesigner | NPCFlags.Auctioneer); + } + public bool IsSpiritService() { return HasFlag64(UnitFields.NpcFlags, NPCFlags.SpiritHealer | NPCFlags.SpiritGuide); } + public bool IsCritter() { return GetCreatureType() == CreatureType.Critter; } + public bool IsInFlight() { return HasUnitState(UnitState.InFlight); } + + public Guardian GetGuardianPet() + { + ObjectGuid pet_guid = GetPetGUID(); + if (!pet_guid.IsEmpty()) + { + Creature pet = ObjectAccessor.GetCreatureOrPetOrVehicle(this, pet_guid); + if (pet != null) + if (pet.HasUnitTypeMask(UnitTypeMask.Guardian)) + return (Guardian)pet; + + Log.outFatal(LogFilter.Unit, "Unit:GetGuardianPet: Guardian {0} not exist.", pet_guid); + SetPetGUID(ObjectGuid.Empty); + } + + return null; + } + + public Unit SelectNearbyTarget(Unit exclude = null, float dist = SharedConst.NominalMeleeRange) + { + List targets = new List(); + var u_check = new AnyUnfriendlyUnitInObjectRangeCheck(this, this, dist); + var searcher = new UnitListSearcher(this, targets, u_check); + Cell.VisitAllObjects(this, searcher, dist); + + // remove current target + if (GetVictim()) + targets.Remove(GetVictim()); + + if (exclude) + targets.Remove(exclude); + + // remove not LoS targets + foreach (var unit in targets) + { + if (!IsWithinLOSInMap(unit) || unit.IsTotem() || unit.IsSpiritService() || unit.IsCritter()) + targets.Remove(unit); + } + + // no appropriate targets + if (targets.Empty()) + return null; + + // select random + return targets.PickRandom(); + } + + public void EnterVehicle(Unit Base, sbyte seatId = -1) + { + CastCustomSpell(SharedConst.VehicleSpellRideHardcoded, SpellValueMod.BasePoint0, seatId + 1, Base, TriggerCastFlags.IgnoreCasterMountedOrOnVehicle); + } + + public void _EnterVehicle(Vehicle vehicle, sbyte seatId, AuraApplication aurApp) + { + // Must be called only from aura handler + Contract.Assert(aurApp != null); + + if (!IsAlive() || GetVehicleKit() == vehicle || vehicle.GetBase().IsOnVehicle(this)) + return; + + if (m_vehicle != null) + { + if (m_vehicle != vehicle) + { + Log.outDebug(LogFilter.Vehicle, "EnterVehicle: {0} exit {1} and enter {2}.", GetEntry(), m_vehicle.GetBase().GetEntry(), vehicle.GetBase().GetEntry()); + ExitVehicle(); + } + else if (seatId >= 0 && seatId == GetTransSeat()) + return; + } + + if (aurApp.HasRemoveMode()) + return; + + Player player = ToPlayer(); + if (player != null) + { + if (vehicle.GetBase().IsTypeId(TypeId.Player) && player.IsInCombat()) + { + vehicle.GetBase().RemoveAura(aurApp); + return; + } + } + + Contract.Assert(!m_vehicle); + vehicle.AddPassenger(this, seatId); + } + + public void ChangeSeat(sbyte seatId, bool next = true) + { + if (m_vehicle == null) + return; + + // Don't change if current and new seat are identical + if (seatId == GetTransSeat()) + return; + + var seat = (seatId < 0 ? m_vehicle.GetNextEmptySeat(GetTransSeat(), next) : m_vehicle.Seats.LookupByKey(seatId)); + // The second part of the check will only return true if seatId >= 0. @Vehicle.GetNextEmptySeat makes sure of that. + if (seat == null || !seat.IsEmpty()) + return; + + AuraEffect rideVehicleEffect = null; + var vehicleAuras = m_vehicle.GetBase().GetAuraEffectsByType(AuraType.ControlVehicle); + foreach (var eff in vehicleAuras) + { + if (eff.GetCasterGUID() != GetGUID()) + continue; + + // Make sure there is only one ride vehicle aura on target cast by the unit changing seat + Contract.Assert(rideVehicleEffect == null); + rideVehicleEffect = eff; + } + + // Unit riding a vehicle must always have control vehicle aura on target + Contract.Assert(rideVehicleEffect != null); + + rideVehicleEffect.ChangeAmount((seatId < 0 ? GetTransSeat() : seatId) + 1); + } + + public void ExitVehicle(Position exitPosition = null) + { + //! This function can be called at upper level code to initialize an exit from the passenger's side. + if (m_vehicle == null) + return; + + GetVehicleBase().RemoveAurasByType(AuraType.ControlVehicle, GetGUID()); + //! The following call would not even be executed successfully as the + //! SPELL_AURA_CONTROL_VEHICLE unapply handler already calls _ExitVehicle without + //! specifying an exitposition. The subsequent call below would return on if (!m_vehicle). + + //! To do: + //! We need to allow SPELL_AURA_CONTROL_VEHICLE unapply handlers in spellscripts + //! to specify exit coordinates and either store those per passenger, or we need to + //! init spline movement based on those coordinates in unapply handlers, and + //! relocate exiting passengers based on Unit.moveSpline data. Either way, + //! Coming Soon(TM) + } + + public void _ExitVehicle(Position exitPosition = null) + { + // It's possible m_vehicle is NULL, when this function is called indirectly from @VehicleJoinEvent.Abort. + // In that case it was not possible to add the passenger to the vehicle. The vehicle aura has already been removed + // from the target in the aforementioned function and we don't need to do anything else at this point. + if (m_vehicle == null) + return; + + // This should be done before dismiss, because there may be some aura removal + Vehicle vehicle = m_vehicle.RemovePassenger(this); + + Player player = ToPlayer(); + + // If the player is on mounted duel and exits the mount, he should immediatly lose the duel + if (player && player.duel != null && player.duel.isMounted) + player.DuelComplete(DuelCompleteType.Fled); + + SetControlled(false, UnitState.Root); // SMSG_MOVE_FORCE_UNROOT, ~MOVEMENTFLAG_ROOT + + Position pos; + if (exitPosition == null) // Exit position not specified + pos = vehicle.GetBase().GetPosition(); // This should use passenger's current position, leaving it as it is now + // because we calculate positions incorrect (sometimes under map) + else + pos = exitPosition; + + AddUnitState(UnitState.Move); + + if (player != null) + player.SetFallInformation(0, GetPositionZ()); + + float height = pos.GetPositionZ(); + + MoveSplineInit init = new MoveSplineInit(this); + + // Creatures without inhabit type air should begin falling after exiting the vehicle + if (IsTypeId(TypeId.Unit) && !ToCreature().CanFly() && height > GetMap().GetWaterOrGroundLevel(GetPhases(), pos.GetPositionX(), pos.GetPositionY(), pos.GetPositionZ(), ref height) + 0.1f) + init.SetFall(); + + init.MoveTo(pos.GetPositionX(), pos.GetPositionY(), height, false); + init.SetFacing(GetOrientation()); + init.SetTransportExit(); + init.Launch(); + + if (player != null) + player.ResummonPetTemporaryUnSummonedIfAny(); + + if (vehicle.GetBase().HasUnitTypeMask(UnitTypeMask.Minion) && vehicle.GetBase().IsTypeId(TypeId.Unit)) + if (((Minion)vehicle.GetBase()).GetOwner() == this) + vehicle.GetBase().ToCreature().DespawnOrUnsummon(); + + if (HasUnitTypeMask(UnitTypeMask.Accessory)) + { + // Vehicle just died, we die too + if (vehicle.GetBase().getDeathState() == DeathState.JustDied) + setDeathState(DeathState.JustDied); + // If for other reason we as minion are exiting the vehicle (ejected, master dismounted) - unsummon + else + ToTempSummon().UnSummon(2000); // Approximation + } + } + + void SendCancelOrphanSpellVisual(uint id) + { + CancelOrphanSpellVisual cancelOrphanSpellVisual = new CancelOrphanSpellVisual(); + cancelOrphanSpellVisual.SpellVisualID = id; + SendMessageToSet(cancelOrphanSpellVisual, true); + } + + void SendPlayOrphanSpellVisual(ObjectGuid target, uint spellVisualId, float travelSpeed, bool speedAsTime = false, bool withSourceOrientation = false) + { + PlayOrphanSpellVisual playOrphanSpellVisual = new PlayOrphanSpellVisual(); + playOrphanSpellVisual.SourceLocation = GetPosition(); + if (withSourceOrientation) + playOrphanSpellVisual.SourceRotation = new Vector3(0.0f, 0.0f, GetOrientation()); + playOrphanSpellVisual.Target = target; // exclusive with TargetLocation + playOrphanSpellVisual.SpellVisualID = spellVisualId; + playOrphanSpellVisual.TravelSpeed = travelSpeed; + playOrphanSpellVisual.SpeedAsTime = speedAsTime; + playOrphanSpellVisual.UnkZero = 0.0f; + SendMessageToSet(playOrphanSpellVisual, true); + } + + void SendPlayOrphanSpellVisual(Vector3 targetLocation, uint spellVisualId, float travelSpeed, bool speedAsTime = false, bool withSourceOrientation = false) + { + PlayOrphanSpellVisual playOrphanSpellVisual = new PlayOrphanSpellVisual(); + playOrphanSpellVisual.SourceLocation = GetPosition(); + if (withSourceOrientation) + playOrphanSpellVisual.SourceRotation = new Vector3(0.0f, 0.0f, GetOrientation()); + playOrphanSpellVisual.TargetLocation = targetLocation; // exclusive with Target + playOrphanSpellVisual.SpellVisualID = spellVisualId; + playOrphanSpellVisual.TravelSpeed = travelSpeed; + playOrphanSpellVisual.SpeedAsTime = speedAsTime; + playOrphanSpellVisual.UnkZero = 0.0f; + SendMessageToSet(playOrphanSpellVisual, true); + } + + void SendCancelSpellVisual(uint id) + { + CancelSpellVisual cancelSpellVisual = new CancelSpellVisual(); + cancelSpellVisual.Source = GetGUID(); + cancelSpellVisual.SpellVisualID = id; + SendMessageToSet(cancelSpellVisual, true); + } + + public void SendPlaySpellVisual(ObjectGuid targetGuid, uint spellVisualId, uint missReason, uint reflectStatus, float travelSpeed, bool speedAsTime = false) + { + PlaySpellVisual playSpellVisual = new PlaySpellVisual(); + playSpellVisual.Source = GetGUID(); + playSpellVisual.Target = targetGuid; // exclusive with TargetPosition + playSpellVisual.SpellVisualID = spellVisualId; + playSpellVisual.TravelSpeed = travelSpeed; + playSpellVisual.MissReason = (ushort)missReason; + playSpellVisual.ReflectStatus = (ushort)reflectStatus; + playSpellVisual.SpeedAsTime = speedAsTime; + SendMessageToSet(playSpellVisual, true); + } + + public void SendPlaySpellVisual(Vector3 targetPosition, float o, uint spellVisualId, uint missReason, uint reflectStatus, float travelSpeed, bool speedAsTime = false) + { + PlaySpellVisual playSpellVisual = new PlaySpellVisual(); + playSpellVisual.Source = GetGUID(); + playSpellVisual.TargetPosition = targetPosition; // exclusive with Target + playSpellVisual.Orientation = o; + playSpellVisual.SpellVisualID = spellVisualId; + playSpellVisual.TravelSpeed = travelSpeed; + playSpellVisual.MissReason = (ushort)missReason; + playSpellVisual.ReflectStatus = (ushort)reflectStatus; + playSpellVisual.SpeedAsTime = speedAsTime; + SendMessageToSet(playSpellVisual, true); + } + + void SendCancelSpellVisualKit(uint id) + { + CancelSpellVisualKit cancelSpellVisualKit = new CancelSpellVisualKit(); + cancelSpellVisualKit.Source = GetGUID(); + cancelSpellVisualKit.SpellVisualKitID = id; + SendMessageToSet(cancelSpellVisualKit, true); + } + + public void SendPlaySpellVisualKit(uint id, uint type, uint duration) + { + PlaySpellVisualKit playSpellVisualKit = new PlaySpellVisualKit(); + playSpellVisualKit.Unit = GetGUID(); + playSpellVisualKit.KitRecID = id; + playSpellVisualKit.KitType = type; + playSpellVisualKit.Duration = duration; + SendMessageToSet(playSpellVisualKit, true); + } + + public void UnsummonAllTotems() + { + for (byte i = 0; i < SharedConst.MaxSummonSlot; ++i) + { + if (m_SummonSlot[i].IsEmpty()) + continue; + + Creature OldTotem = GetMap().GetCreature(m_SummonSlot[i]); + if (OldTotem != null) + if (OldTotem.IsSummon()) + OldTotem.ToTempSummon().UnSummon(); + } + } + + public bool IsOnVehicle(Unit vehicle) + { + return m_vehicle != null && m_vehicle == vehicle.GetVehicleKit(); + } + + public UnitAI GetAI() { return i_AI; } + void SetAI(UnitAI newAI) { i_AI = newAI; } + + public bool isPossessing() + { + Unit u = GetCharm(); + if (u != null) + return u.isPossessed(); + else + return false; + } + public Unit GetCharm() + { + ObjectGuid charm_guid = GetCharmGUID(); + if (!charm_guid.IsEmpty()) + { + Unit pet = Global.ObjAccessor.GetUnit(this, charm_guid); + if (pet != null) + return pet; + + Log.outError(LogFilter.Unit, "Unit.GetCharm: Charmed creature {0} not exist.", charm_guid); + SetGuidValue(UnitFields.Charm, ObjectGuid.Empty); + } + + return null; + } + public bool IsCharmed() { return !GetCharmerGUID().IsEmpty(); } + public bool isPossessed() { return HasUnitState(UnitState.Possessed); } + + public HostileRefManager getHostileRefManager() { return m_HostileRefManager; } + + public override bool SetInPhase(uint id, bool update, bool apply) + { + bool res = base.SetInPhase(id, update, apply); + + if (!IsInWorld) + return res; + + RemoveNotOwnSingleTargetAuras(0, true); + + if (IsTypeId(TypeId.Unit) || (!ToPlayer().IsGameMaster() && !ToPlayer().GetSession().PlayerLogout())) + { + HostileRefManager refManager = getHostileRefManager(); + HostileReference refe = refManager.getFirst(); + + while (refe != null) + { + Unit unit = refe.GetSource().GetOwner(); + if (unit != null) + { + Creature creature = unit.ToCreature(); + if (creature != null) + refManager.setOnlineOfflineState(creature, creature.IsInPhase(this)); + } + + refe = refe.next(); + } + + // modify threat lists for new phasemask + if (!IsTypeId(TypeId.Player)) + { + List threatList = GetThreatManager().getThreatList(); + List offlineThreatList = GetThreatManager().getOfflineThreatList(); + + // merge expects sorted lists + threatList.Sort(); + offlineThreatList.Sort(); + threatList.AddRange(offlineThreatList); + + foreach (var host in threatList) + { + Unit unit = host.getTarget(); + if (unit != null) + unit.getHostileRefManager().setOnlineOfflineState(ToCreature(), unit.IsInPhase(this)); + } + } + } + + + foreach (var unit in m_Controlled) + if (unit.IsTypeId(TypeId.Unit)) + unit.SetInPhase(id, true, apply); + + for (byte i = 0; i < SharedConst.MaxSummonSlot; ++i) + { + if (!m_SummonSlot[i].IsEmpty()) + { + Creature summon = GetMap().GetCreature(m_SummonSlot[i]); + if (summon != null) + summon.SetInPhase(id, true, apply); + } + } + + RemoveNotOwnSingleTargetAuras(0, true); + + return res; + } + public uint GetModelForForm(ShapeShiftForm form) + { + if (IsTypeId(TypeId.Player)) + { + Aura artifactAura = GetAura(PlayerConst.ArtifactsAllWeaponsGeneralWeaponEquippedPassive); + if (artifactAura != null) + { + Item artifact = ToPlayer().GetItemByGuid(artifactAura.GetCastItemGUID()); + if (artifact) + { + ArtifactAppearanceRecord artifactAppearance = CliDB.ArtifactAppearanceStorage.LookupByKey(artifact.GetModifier(ItemModifier.ArtifactAppearanceId)); + if (artifactAppearance != null) + if ((ShapeShiftForm)artifactAppearance.ModifiesShapeshiftFormDisplay == form) + return artifactAppearance.ShapeshiftDisplayID; + } + } + + byte hairColor = GetByteValue(PlayerFields.Bytes, PlayerFieldOffsets.BytesOffsetHairColorId); + byte skinColor = GetByteValue(PlayerFields.Bytes, PlayerFieldOffsets.BytesOffsetSkinId); + + switch (form) + { + case ShapeShiftForm.CatForm: + { + if (GetRace() == Race.NightElf) + { + if (HasAura(210333)) // Glyph of the Feral Chameleon + hairColor = (byte)RandomHelper.URand(0, 10); + + switch (hairColor) + { + case 7: // Violet + case 8: + return 29405; + case 3: // Light Blue + return 29406; + case 0: // Green + case 1: // Light Green + case 2: // Dark Green + return 29407; + case 4: // White + return 29408; + default: // original - Dark Blue + return 892; + } + } + else if (GetRace() == Race.Troll) + { + if (HasAura(210333)) // Glyph of the Feral Chameleon + hairColor = (byte)RandomHelper.URand(0, 12); + + switch (hairColor) + { + case 0: // Red + case 1: + return 33668; + case 2: // Yellow + case 3: + return 33667; + case 4: // Blue + case 5: + case 6: + return 33666; + case 7: // Purple + case 10: + return 33665; + default: // original - white + return 33669; + } + } + else if (GetRace() == Race.Worgen) + { + if (HasAura(210333)) // Glyph of the Feral Chameleon + hairColor = (byte)RandomHelper.URand(0, 9); + + // Male + if (GetGender() == Gender.Male) + { + switch (skinColor) + { + case 1: // Brown + return 33662; + case 2: // Black + case 7: + return 33661; + case 4: // yellow + return 33664; + case 3: // White + case 5: + return 33663; + default: // original - Gray + return 33660; + } + } + // Female + else + { + switch (skinColor) + { + case 5: // Brown + case 6: + return 33662; + case 7: // Black + case 8: + return 33661; + case 3: // yellow + case 4: + return 33664; + case 2: // White + return 33663; + default: // original - Gray + return 33660; + } + } + } + else if (GetRace() == Race.Tauren) + { + if (HasAura(210333)) // Glyph of the Feral Chameleon + hairColor = (byte)RandomHelper.URand(0, 20); + + if (GetGender() == Gender.Male) + { + switch (skinColor) + { + case 12: // White + case 13: + case 14: + case 18: // Completly White + return 29409; + case 9: // Light Brown + case 10: + case 11: + return 29410; + case 6: // Brown + case 7: + case 8: + return 29411; + case 0: // Dark + case 1: + case 2: + case 3: // Dark Grey + case 4: + case 5: + return 29412; + default: // original - Grey + return 8571; + } + } + // Female + else + { + switch (skinColor) + { + case 10: // White + return 29409; + case 6: // Light Brown + case 7: + return 29410; + case 4: // Brown + case 5: + return 29411; + case 0: // Dark + case 1: + case 2: + case 3: + return 29412; + default: // original - Grey + return 8571; + } + } + } + else if (Player.TeamForRace(GetRace()) == Team.Alliance) + return 892; + else + return 8571; + } + case ShapeShiftForm.BearForm: + { + if (GetRace() == Race.NightElf) + { + if (HasAura(210333)) // Glyph of the Feral Chameleon + hairColor = (byte)RandomHelper.URand(0, 8); + + switch (hairColor) + { + case 0: // Green + case 1: // Light Green + case 2: // Dark Green + return 29413; // 29415? + case 6: // Dark Blue + return 29414; + case 4: // White + return 29416; + case 3: // Light Blue + return 29417; + default: // original - Violet + return 29415; + } + } + else if (GetRace() == Race.Troll) + { + if (HasAura(210333)) // Glyph of the Feral Chameleon + hairColor = (byte)RandomHelper.URand(0, 14); + + switch (hairColor) + { + case 0: // Red + case 1: + return 33657; + case 2: // Yellow + case 3: + return 33659; + case 7: // Purple + case 10: + return 33656; + case 8: // White + case 9: + case 11: + case 12: + return 33658; + default: // original - Blue + return 33655; + } + } + else if (GetRace() == Race.Worgen) + { + if (HasAura(210333)) // Glyph of the Feral Chameleon + hairColor = (byte)RandomHelper.URand(0, 8); + + // Male + if (GetGender() == Gender.Male) + { + switch (skinColor) + { + case 1: // Brown + return 33652; + case 2: // Black + case 7: + return 33651; + case 4: // Yellow + return 33653; + case 3: // White + case 5: + return 33654; + default: // original - Gray + return 33650; + } + } + // Female + else + { + switch (skinColor) + { + case 5: // Brown + case 6: + return 33652; + case 7: // Black + case 8: + return 33651; + case 3: // yellow + case 4: + return 33654; + case 2: // White + return 33653; + default: // original - Gray + return 33650; + } + } + } + else if (GetRace() == Race.Tauren) + { + if (HasAura(210333)) // Glyph of the Feral Chameleon + hairColor = (byte)RandomHelper.URand(0, 20); + + if (GetGender() == Gender.Male) + { + switch (skinColor) + { + case 0: // Dark (Black) + case 1: + case 2: + return 29418; + case 3: // White + case 4: + case 5: + case 12: + case 13: + case 14: + return 29419; + case 9: // Light Brown/Grey + case 10: + case 11: + case 15: + case 16: + case 17: + return 29420; + case 18: // Completly White + return 29421; + default: // original - Brown + return 2289; + } + } + // Female + else + { + switch (skinColor) + { + case 0: // Dark (Black) + case 1: + return 29418; + case 2: // White + case 3: + return 29419; + case 6: // Light Brown/Grey + case 7: + case 8: + case 9: + return 29420; + case 10: // Completly White + return 29421; + default: // original - Brown + return 2289; + } + } + } + else if (Player.TeamForRace(GetRace()) == Team.Alliance) + return 29415; + else + return 2289; + } + case ShapeShiftForm.FlightForm: + if (Player.TeamForRace(GetRace()) == Team.Alliance) + return 20857; + return 20872; + case ShapeShiftForm.FlightFormEpic: + if (HasAura(219062)) // Glyph of the Sentinel + { + switch (GetRace()) + { + case Race.NightElf: // Blue + return 64328; + case Race.Tauren: // Brown + return 64329; + case Race.Worgen: // Purple + return 64330; + case Race.Troll: // White + return 64331; + default: + break; + } + } + if (Player.TeamForRace(GetRace()) == Team.Alliance) + return (GetRace() == Race.Worgen ? 37729 : 21243u); + if (GetRace() == Race.Troll) + return 37730; + return 21244; + case ShapeShiftForm.MoonkinForm: + switch (GetRace()) + { + case Race.NightElf: + return 15374; + case Race.Tauren: + return 15375; + case Race.Worgen: + return 37173; + case Race.Troll: + return 37174; + default: + break; + } + break; + case ShapeShiftForm.AquaticForm: + if (HasAura(114333)) // Glyph of the Orca + return 4591; + return 2428; + case ShapeShiftForm.TravelForm: + { + if (HasAura(131113)) // Glyph of the Cheetah + return 1043; + + if (HasAura(224122)) // Glyph of the Doe + return 70450; + + switch (GetRace()) + { + case Race.NightElf: + case Race.Worgen: + return 40816; + case Race.Troll: + case Race.Tauren: + return 45339; + default: + break; + } + break; + } + case ShapeShiftForm.GhostWolf: + if (HasAura(58135)) // Glyph of Spectral Wolf + return 60247; + break; + } + } + + uint modelid = 0; + SpellShapeshiftFormRecord formEntry = CliDB.SpellShapeshiftFormStorage.LookupByKey((uint)form); + if (formEntry != null && formEntry.CreatureDisplayID[0] != 0) + { + // Take the alliance modelid as default + if (!IsTypeId(TypeId.Player)) + return formEntry.CreatureDisplayID[0]; + else + { + if (Player.TeamForRace(GetRace()) == Team.Alliance) + modelid = formEntry.CreatureDisplayID[0]; + else + modelid = formEntry.CreatureDisplayID[1]; + + // If the player is horde but there are no values for the horde modelid - take the alliance modelid + if (modelid == 0 && Player.TeamForRace(GetRace()) == Team.Horde) + modelid = formEntry.CreatureDisplayID[0]; + } + } + + return modelid; + } + + public virtual bool CanUseAttackType(WeaponAttackType attacktype) + { + switch (attacktype) + { + case WeaponAttackType.BaseAttack: + return !HasFlag(UnitFields.Flags, UnitFlags.Disarmed); + case WeaponAttackType.OffAttack: + return !HasFlag(UnitFields.Flags2, UnitFlags2.DisarmOffhand); + case WeaponAttackType.RangedAttack: + return !HasFlag(UnitFields.Flags2, UnitFlags2.DisarmRanged); + default: + return true; + } + } + + public Totem ToTotem() { return IsTotem() ? (this as Totem) : null; } + public TempSummon ToTempSummon() { return IsSummon() ? (this as TempSummon) : null; } + public virtual void setDeathState(DeathState s) + { + // Death state needs to be updated before RemoveAllAurasOnDeath() is called, to prevent entering combat + m_deathState = s; + + if (s != DeathState.Alive && s != DeathState.JustRespawned) + { + CombatStop(); + DeleteThreatList(); + getHostileRefManager().deleteReferences(); + + if (IsNonMeleeSpellCast(false)) + InterruptNonMeleeSpells(false); + + ExitVehicle(); // Exit vehicle before calling RemoveAllControlled + // vehicles use special type of charm that is not removed by the next function + // triggering an assert + UnsummonAllTotems(); + RemoveAllControlled(); + RemoveAllAurasOnDeath(); + } + + if (s == DeathState.JustDied) + { + ModifyAuraState(AuraStateType.HealthLess20Percent, false); + ModifyAuraState(AuraStateType.HealthLess35Percent, false); + // remove aurastates allowing special moves + ClearAllReactives(); + m_Diminishing.Clear(); + if (IsInWorld) + { + // Only clear MotionMaster for entities that exists in world + // Avoids crashes in the following conditions : + // * Using 'call pet' on dead pets + // * Using 'call stabled pet' + // * Logging in with dead pets + GetMotionMaster().Clear(false); + GetMotionMaster().MoveIdle(); + } + StopMoving(); + DisableSpline(); + // without this when removing IncreaseMaxHealth aura player may stuck with 1 hp + // do not why since in IncreaseMaxHealth currenthealth is checked + SetHealth(0); + SetPower(getPowerType(), 0); + SetUInt32Value(UnitFields.NpcEmotestate, 0); + + // players in instance don't have ZoneScript, but they have InstanceScript + ZoneScript zoneScript = GetZoneScript() != null ? GetZoneScript() : GetInstanceScript(); + if (zoneScript != null) + zoneScript.OnUnitDeath(this); + } + else if (s == DeathState.JustRespawned) + RemoveFlag(UnitFields.Flags, UnitFlags.Skinnable); // clear skinnable for creature and player (at Battleground) + } + + public bool IsVisible() + { + return (m_serverSideVisibility.GetValue(ServerSideVisibilityType.GM) > (uint)AccountTypes.Player) ? false : true; + } + + public void SetVisible(bool val) + { + if (!val) + m_serverSideVisibility.SetValue(ServerSideVisibilityType.GM, AccountTypes.GameMaster); + else + m_serverSideVisibility.SetValue(ServerSideVisibilityType.GM, AccountTypes.Player); + + UpdateObjectVisibility(); + } + public bool IsMounted() { return HasFlag(UnitFields.Flags, UnitFlags.Mount); } + public uint GetMountID() { return GetUInt32Value(UnitFields.MountDisplayId); } + + public void SetShapeshiftForm(ShapeShiftForm form) + { + SetByteValue(UnitFields.Bytes2, UnitBytes2Offsets.ShapeshiftForm, (byte)form); + } + + public void SetModifierValue(UnitMods unitMod, UnitModifierType modifierType, float value) + { + m_auraModifiersGroup[(int)unitMod][(int)modifierType] = value; + } + public float GetTotalAuraMultiplierByMiscValue(AuraType auratype, int miscValue) + { + Dictionary SameEffectSpellGroup = new Dictionary(); + float multiplier = 1.0f; + + var mTotalAuraList = GetAuraEffectsByType(auratype); + foreach (var i in mTotalAuraList) + { + if (i.GetMiscValue() == miscValue) + if (!Global.SpellMgr.AddSameEffectStackRuleSpellGroups(i.GetSpellInfo(), i.GetAmount(), out SameEffectSpellGroup)) + MathFunctions.AddPct(ref multiplier, i.GetAmount()); + } + + foreach (var pair in SameEffectSpellGroup) + MathFunctions.AddPct(ref multiplier, pair.Value); + + return multiplier; + } + public int GetTotalAuraModifierByMiscValue(AuraType auratype, int miscValue) + { + Dictionary SameEffectSpellGroup = new Dictionary(); + int modifier = 0; + + var mTotalAuraList = GetAuraEffectsByType(auratype); + foreach (var i in mTotalAuraList) + { + if (i.GetMiscValue() == miscValue) + if (!Global.SpellMgr.AddSameEffectStackRuleSpellGroups(i.GetSpellInfo(), i.GetAmount(), out SameEffectSpellGroup)) + modifier += i.GetAmount(); + } + + foreach (var pair in SameEffectSpellGroup) + modifier += pair.Value; + + return modifier; + } + public int GetTotalAuraModifierByMiscMask(AuraType auratype, int miscMask) + { + Dictionary SameEffectSpellGroup = new Dictionary(); + int modifier = 0; + + var mTotalAuraList = GetAuraEffectsByType(auratype); + + foreach (var i in mTotalAuraList) + if (Convert.ToBoolean(i.GetMiscValue() & miscMask)) + if (!Global.SpellMgr.AddSameEffectStackRuleSpellGroups(i.GetSpellInfo(), i.GetAmount(), out SameEffectSpellGroup)) + modifier += i.GetAmount(); + + foreach (var pair in SameEffectSpellGroup) + modifier += pair.Value; + + return modifier; + } + public int CalcSpellDuration(SpellInfo spellProto) + { + sbyte comboPoints = (sbyte)(m_playerMovingMe != null ? m_playerMovingMe.GetComboPoints() : 0); + + int minduration = spellProto.GetDuration(); + int maxduration = spellProto.GetMaxDuration(); + + int duration; + + if (comboPoints != 0 && minduration != -1 && minduration != maxduration) + duration = minduration + (maxduration - minduration) * comboPoints / 5; + else + duration = minduration; + + return duration; + } + + public int ModSpellDuration(SpellInfo spellProto, Unit target, int duration, bool positive, uint effectMask) + { + // don't mod permanent auras duration + if (duration < 0) + return duration; + + // some auras are not affected by duration modifiers + if (spellProto.HasAttribute(SpellAttr7.IgnoreDurationMods)) + return duration; + + // cut duration only of negative effects + if (!positive) + { + uint mechanic = spellProto.GetSpellMechanicMaskByEffectMask(effectMask); + + int durationMod; + int durationMod_always = 0; + int durationMod_not_stack = 0; + + for (byte i = 1; i <= (int)Mechanics.Enraged; ++i) + { + if (!Convert.ToBoolean(mechanic & 1 << i)) + continue; + // Find total mod value (negative bonus) + int new_durationMod_always = target.GetTotalAuraModifierByMiscValue(AuraType.MechanicDurationMod, i); + // Find max mod (negative bonus) + int new_durationMod_not_stack = target.GetMaxNegativeAuraModifierByMiscValue(AuraType.MechanicDurationModNotStack, i); + // Check if mods applied before were weaker + if (new_durationMod_always < durationMod_always) + durationMod_always = new_durationMod_always; + if (new_durationMod_not_stack < durationMod_not_stack) + durationMod_not_stack = new_durationMod_not_stack; + } + + // Select strongest negative mod + if (durationMod_always > durationMod_not_stack) + durationMod = durationMod_not_stack; + else + durationMod = durationMod_always; + + if (durationMod != 0) + MathFunctions.AddPct(ref duration, durationMod); + + // there are only negative mods currently + durationMod_always = target.GetTotalAuraModifierByMiscValue(AuraType.ModAuraDurationByDispel, (int)spellProto.Dispel); + durationMod_not_stack = target.GetMaxNegativeAuraModifierByMiscValue(AuraType.ModAuraDurationByDispelNotStack, (int)spellProto.Dispel); + + durationMod = 0; + if (durationMod_always > durationMod_not_stack) + durationMod += durationMod_not_stack; + else + durationMod += durationMod_always; + + if (durationMod != 0) + MathFunctions.AddPct(ref duration, durationMod); + } + else + { + // else positive mods here, there are no currently + // when there will be, change GetTotalAuraModifierByMiscValue to GetTotalPositiveAuraModifierByMiscValue + + // Mixology - duration boost + if (target.IsTypeId(TypeId.Player)) + { + if (spellProto.SpellFamilyName == SpellFamilyNames.Potion && ( + Global.SpellMgr.IsSpellMemberOfSpellGroup(spellProto.Id, SpellGroup.ElixirBattle) || + Global.SpellMgr.IsSpellMemberOfSpellGroup(spellProto.Id, SpellGroup.ElixirGuardian))) + { + SpellEffectInfo effect = spellProto.GetEffect(Difficulty.None, 0); + if (target.HasAura(53042) && effect != null && target.HasSpell(effect.TriggerSpell)) + duration *= 2; + } + } + } + + // Glyphs which increase duration of selfcasted buffs + if (target == this) + { + switch (spellProto.SpellFamilyName) + { + case SpellFamilyNames.Druid: + if (spellProto.SpellFamilyFlags[0].HasAnyFlag(0x100u)) + { + // Glyph of Thorns + AuraEffect aurEff = GetAuraEffect(57862, 0); + if (aurEff != null) + duration += aurEff.GetAmount() * Time.Minute * Time.InMilliseconds; + } + break; + } + } + return Math.Max(duration, 0); + } + + // creates aura application instance and registers it in lists + // aura application effects are handled separately to prevent aura list corruption + public AuraApplication _CreateAuraApplication(Aura aura, uint effMask) + { + // can't apply aura on unit which is going to be deleted - to not create a memory leak + Contract.Assert(!m_cleanupDone); + // aura musn't be removed + Contract.Assert(!aura.IsRemoved()); + + // aura mustn't be already applied on target + Contract.Assert(!aura.IsAppliedOnTarget(GetGUID()), "Unit._CreateAuraApplication: aura musn't be applied on target"); + + SpellInfo aurSpellInfo = aura.GetSpellInfo(); + uint aurId = aurSpellInfo.Id; + + // ghost spell check, allow apply any auras at player loading in ghost mode (will be cleanup after load) + if (!IsAlive() && !aurSpellInfo.IsDeathPersistent() && + (!IsTypeId(TypeId.Player) || !ToPlayer().GetSession().PlayerLoading())) + return null; + + Unit caster = aura.GetCaster(); + + AuraApplication aurApp = new AuraApplication(this, caster, aura, effMask); + m_appliedAuras.Add(aurId, aurApp); + + if (aurSpellInfo.AuraInterruptFlags != 0) + { + m_interruptableAuras.Add(aurApp); + AddInterruptMask((uint)aurSpellInfo.AuraInterruptFlags); + } + + AuraStateType aState = aura.GetSpellInfo().GetAuraState(GetMap().GetDifficultyID()); + if (aState != 0) + m_auraStateAuras.Add(aState, aurApp); + + aura._ApplyForTarget(this, caster, aurApp); + return aurApp; + } + public void AddInterruptMask(uint mask) { m_interruptMask |= mask; } + + void _UpdateAutoRepeatSpell() + { + // check "realtime" interrupts + // don't cancel spells which are affected by a SPELL_AURA_CAST_WHILE_WALKING effect + if (((IsTypeId(TypeId.Player) && ToPlayer().isMoving()) || IsNonMeleeSpellCast(false, false, true, GetCurrentSpell(CurrentSpellTypes.AutoRepeat).m_spellInfo.Id == 75)) && + !HasAuraTypeWithAffectMask(AuraType.CastWhileWalking, m_currentSpells[CurrentSpellTypes.AutoRepeat].m_spellInfo)) + { + // cancel wand shoot + if (m_currentSpells[CurrentSpellTypes.AutoRepeat].m_spellInfo.Id != 75) + InterruptSpell(CurrentSpellTypes.AutoRepeat); + m_AutoRepeatFirstCast = true; + return; + } + + // apply delay (Auto Shot (spellID 75) not affected) + if (m_AutoRepeatFirstCast && getAttackTimer(WeaponAttackType.RangedAttack) < 500 && m_currentSpells[CurrentSpellTypes.AutoRepeat].m_spellInfo.Id != 75) + setAttackTimer(WeaponAttackType.RangedAttack, 500); + m_AutoRepeatFirstCast = false; + + // castroutine + if (isAttackReady(WeaponAttackType.RangedAttack)) + { + // Check if able to cast + if (m_currentSpells[CurrentSpellTypes.AutoRepeat].CheckCast(true) != SpellCastResult.SpellCastOk) + { + InterruptSpell(CurrentSpellTypes.AutoRepeat); + return; + } + + // we want to shoot + Spell spell = new Spell(this, m_currentSpells[CurrentSpellTypes.AutoRepeat].m_spellInfo, TriggerCastFlags.FullMask); + spell.prepare(m_currentSpells[CurrentSpellTypes.AutoRepeat].m_targets); + + // all went good, reset attack + resetAttackTimer(WeaponAttackType.RangedAttack); + } + } + + public void UpdateDisplayPower() + { + PowerType displayPower = PowerType.Mana; + switch (GetShapeshiftForm()) + { + case ShapeShiftForm.Ghoul: + case ShapeShiftForm.CatForm: + displayPower = PowerType.Energy; + break; + case ShapeShiftForm.BearForm: + displayPower = PowerType.Rage; + break; + case ShapeShiftForm.TravelForm: + case ShapeShiftForm.GhostWolf: + displayPower = PowerType.Mana; + break; + default: + { + var powerTypeAuras = GetAuraEffectsByType(AuraType.ModPowerDisplay); + if (!powerTypeAuras.Empty()) + { + AuraEffect powerTypeAura = powerTypeAuras.First(); + displayPower = (PowerType)powerTypeAura.GetMiscValue(); + } + else + { + ChrClassesRecord cEntry = CliDB.ChrClassesStorage.LookupByKey(GetClass()); + if (cEntry != null && cEntry.PowerType < PowerType.Max) + displayPower = cEntry.PowerType; + } + break; + } + } + + setPowerType(displayPower); + } + + public FactionTemplateRecord GetFactionTemplateEntry() + { + FactionTemplateRecord entry = CliDB.FactionTemplateStorage.LookupByKey(getFaction()); + if (entry == null) + { + ObjectGuid guid = ObjectGuid.Empty; // prevent repeating spam same faction problem + + if (GetGUID() != guid) + { + Player player = ToPlayer(); + Creature creature = ToCreature(); + if (player != null) + Log.outError(LogFilter.Unit, "Player {0} has invalid faction (faction template id) #{1}", player.GetName(), getFaction()); + else if (creature != null) + Log.outError(LogFilter.Unit, "Creature (template id: {0}) has invalid faction (faction template id) #{1}", creature.GetCreatureTemplate().Entry, getFaction()); + else + Log.outError(LogFilter.Unit, "Unit (name={0}, type={1}) has invalid faction (faction template id) #{2}", GetName(), GetTypeId(), getFaction()); + + guid = GetGUID(); + } + } + return entry; + } + + public bool IsInFeralForm() + { + ShapeShiftForm form = GetShapeshiftForm(); + return form == ShapeShiftForm.CatForm || form == ShapeShiftForm.BearForm || form == ShapeShiftForm.DireBearForm || form == ShapeShiftForm.GhostWolf; + } + public bool IsControlledByPlayer() { return m_ControlledByPlayer; } + + public bool IsCharmedOwnedByPlayerOrPlayer() { return GetCharmerOrOwnerOrOwnGUID().IsPlayer(); } + + public void addFollower(FollowerReference pRef) + { + m_FollowingRefManager.insertFirst(pRef); + } + public void removeFollower(FollowerReference pRef) { } //nothing to do yet + + public uint GetCreatureTypeMask() + { + uint creatureType = (uint)GetCreatureType(); + return (uint)(creatureType >= 1 ? (1 << (int)(creatureType - 1)) : 0); + } + + public Pet ToPet() + { + return IsPet() ? (this as Pet) : null; + } + public MotionMaster GetMotionMaster() { return i_motionMaster; } + + public void PlayOneShotAnimKitId(ushort animKitId) + { + if (!CliDB.AnimKitStorage.ContainsKey(animKitId)) + { + Log.outError(LogFilter.Unit, "Unit.PlayOneShotAnimKitId using invalid AnimKit ID: {0}", animKitId); + return; + } + + PlayOneShotAnimKit packet = new PlayOneShotAnimKit(); + packet.Unit = GetGUID(); + packet.AnimKitID = animKitId; + SendMessageToSet(packet, true); + } + + public void SetAIAnimKitId(ushort animKitId) + { + if (_aiAnimKitId == animKitId) + return; + + if (animKitId != 0 && !CliDB.AnimKitStorage.ContainsKey(animKitId)) + return; + + _aiAnimKitId = animKitId; + + SetAIAnimKit data = new SetAIAnimKit(); + data.Unit = GetGUID(); + data.AnimKitID = animKitId; + SendMessageToSet(data, true); + } + + public override ushort GetAIAnimKitId() { return _aiAnimKitId; } + + public void SetMovementAnimKitId(ushort animKitId) + { + if (_movementAnimKitId == animKitId) + return; + + if (animKitId != 0 && !CliDB.AnimKitStorage.ContainsKey(animKitId)) + return; + + _movementAnimKitId = animKitId; + + SetMovementAnimKit data = new SetMovementAnimKit(); + data.Unit = GetGUID(); + data.AnimKitID = animKitId; + SendMessageToSet(data, true); + } + + public override ushort GetMovementAnimKitId() { return _movementAnimKitId; } + + public void SetMeleeAnimKitId(ushort animKitId) + { + if (_meleeAnimKitId == animKitId) + return; + + if (animKitId != 0 && !CliDB.AnimKitStorage.ContainsKey(animKitId)) + return; + + _meleeAnimKitId = animKitId; + + SetMeleeAnimKit data = new SetMeleeAnimKit(); + data.Unit = GetGUID(); + data.AnimKitID = animKitId; + SendMessageToSet(data, true); + } + + public override ushort GetMeleeAnimKitId() { return _meleeAnimKitId; } + + public uint GetVirtualItemId(int slot) + { + if (slot >= SharedConst.MaxEquipmentItems) + return 0; + + return GetUInt32Value(UnitFields.VirtualItemSlotId + slot * 2); + } + + public ushort GetVirtualItemAppearanceMod(uint slot) + { + if (slot >= SharedConst.MaxEquipmentItems) + return 0; + + return GetUInt16Value(UnitFields.VirtualItemSlotId + (int)slot * 2 + 1, 0); + } + + public void SetVirtualItem(int slot, uint itemId, ushort appearanceModId = 0, ushort itemVisual = 0) + { + if (slot >= SharedConst.MaxEquipmentItems) + return; + + SetUInt32Value(UnitFields.VirtualItemSlotId + slot * 2, itemId); + SetUInt16Value(UnitFields.VirtualItemSlotId + slot * 2 + 1, 0, appearanceModId); + SetUInt16Value(UnitFields.VirtualItemSlotId + slot * 2 + 1, 1, itemVisual); + } + + //Unit + public void SetLevel(uint lvl) + { + SetUInt32Value(UnitFields.Level, lvl); + + if (IsTypeId(TypeId.Player)) + { + Player player = ToPlayer(); + if (player.GetGroup()) + player.SetGroupUpdateFlag(GroupUpdateFlags.Level); + Global.WorldMgr.UpdateCharacterInfoLevel(ToPlayer().GetGUID(), (byte)lvl); + } + } + public uint getLevel() + { + return GetUInt32Value(UnitFields.Level); + } + public override uint GetLevelForTarget(WorldObject target) + { + return getLevel(); + } + + public Race GetRace() + { + return (Race)GetByteValue(UnitFields.Bytes0, 0); + } + public uint getRaceMask() + { + return (uint)(1 << ((int)GetRace() - 1)); + } + public Class GetClass() + { + return (Class)GetByteValue(UnitFields.Bytes0, 1); + } + public uint getClassMask() + { + return (uint)(1 << ((int)GetClass() - 1)); + } + public Gender GetGender() + { + return (Gender)GetByteValue(UnitFields.Bytes0, 3); + } + + public void SetNativeDisplayId(uint modelId) + { + SetUInt32Value(UnitFields.NativeDisplayId, modelId); + } + public virtual void SetDisplayId(uint modelId) + { + SetUInt32Value(UnitFields.DisplayId, modelId); + // Set Gender by modelId + CreatureModelInfo minfo = Global.ObjectMgr.GetCreatureModelInfo(modelId); + if (minfo != null) + SetByteValue(UnitFields.Bytes0, 3, (byte)minfo.gender); + } + public uint GetNativeDisplayId() + { + return GetUInt32Value(UnitFields.NativeDisplayId); + } + + public virtual Unit GetOwner() + { + ObjectGuid ownerid = GetOwnerGUID(); + if (!ownerid.IsEmpty()) + return Global.ObjAccessor.GetUnit(this, ownerid); + + return null; + } + public virtual float GetFollowAngle() { return MathFunctions.PiOver2; } + + public ObjectGuid GetOwnerGUID() { return GetGuidValue(UnitFields.SummonedBy); } + public ObjectGuid GetCreatorGUID() { return GetGuidValue(UnitFields.CreatedBy); } + public ObjectGuid GetMinionGUID() { return GetGuidValue(UnitFields.Summon); } + public ObjectGuid GetCharmerGUID() { return GetGuidValue(UnitFields.CharmedBy); } + public ObjectGuid GetCharmGUID() { return GetGuidValue(UnitFields.Charm); } + public ObjectGuid GetPetGUID() { return m_SummonSlot[0]; } + public ObjectGuid GetCritterGUID() { return GetGuidValue(UnitFields.Critter); } + public ObjectGuid GetCharmerOrOwnerGUID() + { + return !GetCharmerGUID().IsEmpty() ? GetCharmerGUID() : GetOwnerGUID(); + } + ObjectGuid GetCharmerOrOwnerOrOwnGUID() + { + ObjectGuid guid = GetCharmerOrOwnerGUID(); + if (!guid.IsEmpty()) + return guid; + + return GetGUID(); + } + public Unit GetCharmer() + { + ObjectGuid charmerid = GetCharmerGUID(); + if (!charmerid.IsEmpty()) + return Global.ObjAccessor.GetUnit(this, charmerid); + return null; + } + Unit GetCharmerOrOwnerOrSelf() + { + Unit u = GetCharmerOrOwner(); + if (u != null) + return u; + + return this; + } + public Player GetCharmerOrOwnerPlayerOrPlayerItself() + { + ObjectGuid guid = GetCharmerOrOwnerGUID(); + if (guid.IsPlayer()) + return Global.ObjAccessor.FindPlayer(guid); + + return IsTypeId(TypeId.Player) ? ToPlayer() : null; + } + public Unit GetCharmerOrOwner() + { + return !GetCharmerGUID().IsEmpty() ? GetCharmer() : GetOwner(); + } + + public List GetChannelObjects() { return GetDynamicStructuredValues(UnitDynamicFields.ChannelObjects); } + public void AddChannelObject(ObjectGuid guid) { AddDynamicStructuredValue(UnitDynamicFields.ChannelObjects, guid); } + + public void SetOwnerGUID(ObjectGuid owner) + { + if (GetOwnerGUID() == owner) + return; + + SetGuidValue(UnitFields.SummonedBy, owner); + if (owner.IsEmpty()) + return; + + // Update owner dependent fields + Player player = Global.ObjAccessor.GetPlayer(this, owner); + if (player == null || !player.HaveAtClient(this)) // if player cannot see this unit yet, he will receive needed data with create object + return; + + SetFieldNotifyFlag(UpdateFieldFlags.Owner); + + UpdateData udata = new UpdateData(GetMapId()); + UpdateObject packet; + BuildValuesUpdateBlockForPlayer(udata, player); + udata.BuildPacket(out packet); + player.SendPacket(packet); + + RemoveFieldNotifyFlag(UpdateFieldFlags.Owner); + } + public void SetCreatorGUID(ObjectGuid creator) { SetGuidValue(UnitFields.CreatedBy, creator); } + void SetMinionGUID(ObjectGuid guid) { SetGuidValue(UnitFields.Summon, guid); } + void SetCharmerGUID(ObjectGuid owner) { SetGuidValue(UnitFields.CharmedBy, owner); } + public void SetPetGUID(ObjectGuid guid) { m_SummonSlot[0] = guid; } + void SetCritterGUID(ObjectGuid guid) { SetGuidValue(UnitFields.Critter, guid); } + + public uint GetCombatTimer() { return m_CombatTimer; } + public bool IsPvP() { return HasByteFlag(UnitFields.Bytes2, UnitBytes2Offsets.PvpFlag, UnitBytes2Flags.PvP); } + public bool IsFFAPvP() { return HasByteFlag(UnitFields.Bytes2, UnitBytes2Offsets.PvpFlag, UnitBytes2Flags.FFAPvp); } + + public ShapeShiftForm GetShapeshiftForm() + { + return (ShapeShiftForm)GetByteValue(UnitFields.Bytes2, UnitBytes2Offsets.ShapeshiftForm); + } + public CreatureType GetCreatureType() + { + if (IsTypeId(TypeId.Player)) + { + ShapeShiftForm form = GetShapeshiftForm(); + var ssEntry = CliDB.SpellShapeshiftFormStorage.LookupByKey((uint)form); + if (ssEntry != null && ssEntry.CreatureType > 0) + return (CreatureType)ssEntry.CreatureType; + else + return CreatureType.Humanoid; + } + else + return ToCreature().GetCreatureTemplate().CreatureType; + } + Player GetAffectingPlayer() + { + if (GetCharmerOrOwnerGUID().IsEmpty()) + return IsTypeId(TypeId.Player) ? ToPlayer() : null; + + Unit owner = GetCharmerOrOwner(); + if (owner != null) + return owner.GetCharmerOrOwnerPlayerOrPlayerItself(); + return null; + } + + public void DeMorph() + { + SetDisplayId(GetNativeDisplayId()); + } + + public uint GetModelForTotem(PlayerTotemType totemType) + { + switch (GetRace()) + { + case Race.Orc: + { + switch (totemType) + { + case PlayerTotemType.Fire: // fire + return 30758; + case PlayerTotemType.Earth: // earth + return 30757; + case PlayerTotemType.Water: // water + return 30759; + case PlayerTotemType.Air: // air + return 30756; + } + break; + } + case Race.Dwarf: + { + switch (totemType) + { + case PlayerTotemType.Fire: // fire + return 30754; + case PlayerTotemType.Earth: // earth + return 30753; + case PlayerTotemType.Water: // water + return 30755; + case PlayerTotemType.Air: // air + return 30736; + } + break; + } + case Race.Troll: + { + switch (totemType) + { + case PlayerTotemType.Fire: // fire + return 30762; + case PlayerTotemType.Earth: // earth + return 30761; + case PlayerTotemType.Water: // water + return 30763; + case PlayerTotemType.Air: // air + return 30760; + } + break; + } + case Race.Tauren: + { + switch (totemType) + { + case PlayerTotemType.Fire: // fire + return 4589; + case PlayerTotemType.Earth: // earth + return 4588; + case PlayerTotemType.Water: // water + return 4587; + case PlayerTotemType.Air: // air + return 4590; + } + break; + } + case Race.Draenei: + { + switch (totemType) + { + case PlayerTotemType.Fire: // fire + return 19074; + case PlayerTotemType.Earth: // earth + return 19073; + case PlayerTotemType.Water: // water + return 19075; + case PlayerTotemType.Air: // air + return 19071; + } + break; + } + case Race.Goblin: + { + switch (totemType) + { + case PlayerTotemType.Fire: // fire + return 30783; + case PlayerTotemType.Earth: // earth + return 30782; + case PlayerTotemType.Water: // water + return 30784; + case PlayerTotemType.Air: // air + return 30781; + } + break; + } + } + return 0; + } + + public bool IsStopped() { return !(HasUnitState(UnitState.Moving)); } + + public bool HasUnitTypeMask(UnitTypeMask mask) { return Convert.ToBoolean(mask & m_unitTypeMask); } + public void AddUnitTypeMask(UnitTypeMask mask) { m_unitTypeMask |= mask; } + + public bool IsAlive() { return m_deathState == DeathState.Alive; } + public bool IsDying() { return m_deathState == DeathState.JustDied; } + public bool IsDead() { return (m_deathState == DeathState.Dead || m_deathState == DeathState.Corpse); } + public bool IsSummon() { return m_unitTypeMask.HasAnyFlag(UnitTypeMask.Summon); } + public bool IsGuardian() { return m_unitTypeMask.HasAnyFlag(UnitTypeMask.Guardian); } + public bool IsPet() { return m_unitTypeMask.HasAnyFlag(UnitTypeMask.Pet); } + public bool IsHunterPet() { return m_unitTypeMask.HasAnyFlag(UnitTypeMask.HunterPet); } + public bool IsTotem() { return m_unitTypeMask.HasAnyFlag(UnitTypeMask.Totem); } + public bool IsVehicle() { return m_unitTypeMask.HasAnyFlag(UnitTypeMask.Vehicle); } + + public void AddUnitState(UnitState f) + { + m_state |= f; + } + public bool HasUnitState(UnitState f) + { + return Convert.ToBoolean(m_state & f); + } + public void ClearUnitState(UnitState f) + { + m_state &= ~f; + } + public void SetStandFlags(object flags) + { + SetByteFlag(UnitFields.Bytes1, UnitBytes1Offsets.VisFlag, flags); + } + public void RemoveStandFlags(object flags) + { + RemoveByteFlag(UnitFields.Bytes1, UnitBytes1Offsets.VisFlag, flags); + } + + public override bool IsAlwaysVisibleFor(WorldObject seer) + { + if (base.IsAlwaysVisibleFor(seer)) + return true; + + // Always seen by owner + ObjectGuid guid = GetCharmerOrOwnerGUID(); + if (!guid.IsEmpty()) + if (seer.GetGUID() == guid) + return true; + + Player seerPlayer = seer.ToPlayer(); + if (seerPlayer != null) + { + Unit owner = GetOwner(); + if (owner != null) + { + Player ownerPlayer = owner.ToPlayer(); + if (ownerPlayer) + if (ownerPlayer.IsGroupVisibleFor(seerPlayer)) + return true; + } + } + + return false; + } + + //Faction + public bool IsNeutralToAll() + { + var my_faction = GetFactionTemplateEntry(); + if (my_faction == null || my_faction.Faction == 0) + return true; + + var raw_faction = CliDB.FactionStorage.LookupByKey(my_faction.Faction); + if (raw_faction != null && raw_faction.ReputationIndex >= 0) + return false; + + return my_faction.IsNeutralToAll(); + } + public bool IsHostileTo(Unit unit) + { + return GetReactionTo(unit) <= ReputationRank.Hostile; + } + public bool IsFriendlyTo(Unit unit) + { + return GetReactionTo(unit) >= ReputationRank.Friendly; + } + public ReputationRank GetReactionTo(Unit target) + { + // always friendly to self + if (this == target) + return ReputationRank.Friendly; + + // always friendly to charmer or owner + if (GetCharmerOrOwnerOrSelf() == target.GetCharmerOrOwnerOrSelf()) + return ReputationRank.Friendly; + + if (HasFlag(UnitFields.Flags, UnitFlags.PvpAttackable)) + { + if (target.HasFlag(UnitFields.Flags, UnitFlags.PvpAttackable)) + { + Player selfPlayerOwner = GetAffectingPlayer(); + Player targetPlayerOwner = target.GetAffectingPlayer(); + + if (selfPlayerOwner != null && targetPlayerOwner != null) + { + // always friendly to other unit controlled by player, or to the player himself + if (selfPlayerOwner == targetPlayerOwner) + return ReputationRank.Friendly; + + // duel - always hostile to opponent + if (selfPlayerOwner.duel != null && selfPlayerOwner.duel.opponent == targetPlayerOwner && selfPlayerOwner.duel.startTime != 0) + return ReputationRank.Hostile; + + // same group - checks dependant only on our faction - skip FFA_PVP for example + if (selfPlayerOwner.IsInRaidWith(targetPlayerOwner)) + return ReputationRank.Friendly; // return true to allow config option AllowTwoSide.Interaction.Group to work + } + + // check FFA_PVP + if (Convert.ToBoolean(GetByteValue(UnitFields.Bytes2, UnitBytes2Offsets.PvpFlag) & (byte)UnitBytes2Flags.FFAPvp) + && Convert.ToBoolean(target.GetByteValue(UnitFields.Bytes2, UnitBytes2Offsets.PvpFlag) & (byte)UnitBytes2Flags.FFAPvp)) + return ReputationRank.Hostile; + + if (selfPlayerOwner != null) + { + var targetFactionTemplateEntry = target.GetFactionTemplateEntry(); + if (targetFactionTemplateEntry != null) + { + if (!selfPlayerOwner.HasFlag(UnitFields.Flags2, UnitFlags2.IgnoreReputation)) + { + var targetFactionEntry = CliDB.FactionStorage.LookupByKey(targetFactionTemplateEntry.Faction); + if (targetFactionEntry != null) + { + if (targetFactionEntry.CanHaveReputation()) + { + // check contested flags + if (Convert.ToBoolean(targetFactionTemplateEntry.Flags & (uint)FactionTemplateFlags.ContestedGuard) + && selfPlayerOwner.HasFlag(PlayerFields.Flags, PlayerFlags.ContestedPVP)) + return ReputationRank.Hostile; + + // if faction has reputation, hostile state depends only from AtWar state + if (selfPlayerOwner.GetReputationMgr().IsAtWar(targetFactionEntry)) + return ReputationRank.Hostile; + return ReputationRank.Friendly; + } + } + } + } + } + } + } + // do checks dependant only on our faction + return GetFactionReactionTo(GetFactionTemplateEntry(), target); + } + ReputationRank GetFactionReactionTo(FactionTemplateRecord factionTemplateEntry, Unit target) + { + // always neutral when no template entry found + if (factionTemplateEntry == null) + return ReputationRank.Neutral; + + var targetFactionTemplateEntry = target.GetFactionTemplateEntry(); + if (targetFactionTemplateEntry == null) + return ReputationRank.Neutral; + + Player targetPlayerOwner = target.GetAffectingPlayer(); + if (targetPlayerOwner != null) + { + // check contested flags + if (Convert.ToBoolean(factionTemplateEntry.Flags & (uint)FactionTemplateFlags.ContestedGuard) + && targetPlayerOwner.HasFlag(PlayerFields.Flags, PlayerFlags.ContestedPVP)) + return ReputationRank.Hostile; + ReputationRank repRank = targetPlayerOwner.GetReputationMgr().GetForcedRankIfAny(factionTemplateEntry); + if (repRank != ReputationRank.None) + return repRank; + if (!target.HasFlag(UnitFields.Flags2, UnitFlags2.IgnoreReputation)) + { + var factionEntry = CliDB.FactionStorage.LookupByKey(factionTemplateEntry.Faction); + if (factionEntry != null) + { + if (factionEntry.CanHaveReputation()) + { + // CvP case - check reputation, don't allow state higher than neutral when at war + repRank = targetPlayerOwner.GetReputationMgr().GetRank(factionEntry); + if (targetPlayerOwner.GetReputationMgr().IsAtWar(factionEntry)) + repRank = (ReputationRank)Math.Min((int)ReputationRank.Neutral, (int)repRank); + return repRank; + } + } + } + } + + // common faction based check + if (factionTemplateEntry.IsHostileTo(targetFactionTemplateEntry)) + return ReputationRank.Hostile; + if (factionTemplateEntry.IsFriendlyTo(targetFactionTemplateEntry)) + return ReputationRank.Friendly; + if (targetFactionTemplateEntry.IsFriendlyTo(factionTemplateEntry)) + return ReputationRank.Friendly; + if (Convert.ToBoolean(factionTemplateEntry.Flags & (uint)FactionTemplateFlags.HostileByDefault)) + return ReputationRank.Hostile; + // neutral by default + return ReputationRank.Neutral; + } + + public uint getFaction() + { + return GetUInt32Value(UnitFields.FactionTemplate); + } + public void SetFaction(uint faction) + { + SetUInt32Value(UnitFields.FactionTemplate, faction); + } + public void RestoreFaction() + { + if (IsTypeId(TypeId.Player)) + ToPlayer().setFactionForRace(GetRace()); + else + { + if (HasUnitTypeMask(UnitTypeMask.Minion)) + { + Unit owner = GetOwner(); + if (owner) + { + SetFaction(owner.getFaction()); + return; + } + } + CreatureTemplate cinfo = ToCreature().GetCreatureTemplate(); + if (cinfo != null) // normal creature + SetFaction(cinfo.Faction); + } + } + + public bool IsInPartyWith(Unit unit) + { + if (this == unit) + return true; + + Unit u1 = GetCharmerOrOwnerOrSelf(); + Unit u2 = unit.GetCharmerOrOwnerOrSelf(); + if (u1 == u2) + return true; + + if (u1.IsTypeId(TypeId.Player) && u2.IsTypeId(TypeId.Player)) + return u1.ToPlayer().IsInSameGroupWith(u2.ToPlayer()); + else if ((u2.IsTypeId(TypeId.Player) && u1.IsTypeId(TypeId.Unit) && u1.ToCreature().GetCreatureTemplate().TypeFlags.HasAnyFlag(CreatureTypeFlags.TreatAsRaidUnit)) || + (u1.IsTypeId(TypeId.Player) && u2.IsTypeId(TypeId.Unit) && u2.ToCreature().GetCreatureTemplate().TypeFlags.HasAnyFlag(CreatureTypeFlags.TreatAsRaidUnit))) + return true; + else + return false; + } + + public bool IsInRaidWith(Unit unit) + { + if (this == unit) + return true; + + Unit u1 = GetCharmerOrOwnerOrSelf(); + Unit u2 = unit.GetCharmerOrOwnerOrSelf(); + if (u1 == u2) + return true; + + if (u1.IsTypeId(TypeId.Player) && u2.IsTypeId(TypeId.Player)) + return u1.ToPlayer().IsInSameRaidWith(u2.ToPlayer()); + else if ((u2.IsTypeId(TypeId.Player) && u1.IsTypeId(TypeId.Unit) && u1.ToCreature().GetCreatureTemplate().TypeFlags.HasAnyFlag(CreatureTypeFlags.TreatAsRaidUnit)) || + (u1.IsTypeId(TypeId.Player) && u2.IsTypeId(TypeId.Unit) && u2.ToCreature().GetCreatureTemplate().TypeFlags.HasAnyFlag(CreatureTypeFlags.TreatAsRaidUnit))) + return true; + else + return false; + } + + public SheathState GetSheath() { return (SheathState)GetByteValue(UnitFields.Bytes2, UnitBytes2Offsets.SheathState); } + public void SetSheath(SheathState sheathed) { SetByteValue(UnitFields.Bytes2, UnitBytes2Offsets.SheathState, (byte)sheathed); } + + public UnitStandStateType GetStandState() { return (UnitStandStateType)GetByteValue(UnitFields.Bytes1, UnitBytes1Offsets.StandState); } + + public bool IsSitState() + { + UnitStandStateType s = GetStandState(); + return + s == UnitStandStateType.SitChair || s == UnitStandStateType.SitLowChair || + s == UnitStandStateType.SitMediumChair || s == UnitStandStateType.SitHighChair || + s == UnitStandStateType.Sit; + } + + public bool IsStandState() + { + UnitStandStateType s = GetStandState(); + return !IsSitState() && s != UnitStandStateType.Sleep && s != UnitStandStateType.Kneel; + } + + public void SetStandState(UnitStandStateType state, uint animKitId = 0) + { + SetByteValue(UnitFields.Bytes1, UnitBytes1Offsets.StandState, (byte)state); + + if (IsStandState()) + RemoveAurasWithInterruptFlags(SpellAuraInterruptFlags.NotSeated); + + if (IsTypeId(TypeId.Player)) + { + StandStateUpdate packet = new StandStateUpdate(state, animKitId); + ToPlayer().SendPacket(packet); + } + } + + public uint GetDisplayId() { return GetUInt32Value(UnitFields.DisplayId); } + + public void RestoreDisplayId() + { + AuraEffect handledAura = null; + // try to receive model from transform auras + var transforms = GetAuraEffectsByType(AuraType.Transform); + if (!transforms.Empty()) + { + // iterate over already applied transform auras - from newest to oldest + foreach (var eff in transforms) + { + AuraApplication aurApp = eff.GetBase().GetApplicationOfTarget(GetGUID()); + if (aurApp != null) + { + if (handledAura == null) + handledAura = eff; + // prefer negative auras + if (!aurApp.IsPositive()) + { + handledAura = eff; + break; + } + } + } + } + uint modelId; + // transform aura was found + if (handledAura != null) + handledAura.HandleEffect(this, AuraEffectHandleModes.SendForClient, true); + // we've found shapeshift + else if ((modelId = GetModelForForm(GetShapeshiftForm())) != 0) + SetDisplayId(modelId); + // no auras found - set modelid to default + else + SetDisplayId(GetNativeDisplayId()); + } + + public bool IsDamageReducedByArmor(SpellSchoolMask schoolMask, SpellInfo spellInfo = null, sbyte effIndex = -1) + { + // only physical spells damage gets reduced by armor + if ((schoolMask & SpellSchoolMask.Normal) == 0) + return false; + if (spellInfo != null) + { + // there are spells with no specific attribute but they have "ignores armor" in tooltip + if (spellInfo.HasAttribute(SpellCustomAttributes.IgnoreArmor)) + return false; + + if (effIndex != -1) + { + // bleeding effects are not reduced by armor + SpellEffectInfo effect = spellInfo.GetEffect(GetMap().GetDifficultyID(), (uint)effIndex); + if (effect != null) + { + if (effect.ApplyAuraName == AuraType.PeriodicDamage || effect.Effect == SpellEffectName.SchoolDamage) + if (spellInfo.GetEffectMechanicMask((byte)effIndex).HasAnyFlag((1u << (int)Mechanics.Bleed))) + return false; + } + } + } + return true; + } + + public override void BuildValuesUpdate(UpdateType updatetype, ByteBuffer data, Player target) + { + if (target == null) + return; + + ByteBuffer fieldBuffer = new ByteBuffer(); + + uint valCount = valuesCount; + + uint[] flags = UpdateFieldFlags.UnitUpdateFieldFlags; + uint visibleFlag = UpdateFieldFlags.Public; + + if (target == this) + visibleFlag |= UpdateFieldFlags.Private; + else if (IsTypeId(TypeId.Player)) + valCount = (int)PlayerFields.EndNotSelf; + + UpdateMask updateMask = new UpdateMask(valCount); + + Player plr = GetCharmerOrOwnerPlayerOrPlayerItself(); + if (GetOwnerGUID() == target.GetGUID()) + visibleFlag |= UpdateFieldFlags.Owner; + + if (HasFlag(ObjectFields.DynamicFlags, UnitDynFlags.SpecialInfo)) + if (HasAuraTypeWithCaster(AuraType.Empathy, target.GetGUID())) + visibleFlag |= UpdateFieldFlags.SpecialInfo; + + if (plr != null && plr.IsInSameRaidWith(target)) + visibleFlag |= UpdateFieldFlags.PartyMember; + + Creature creature = ToCreature(); + for (var index = 0; index < valCount; ++index) + { + if (Convert.ToBoolean(_fieldNotifyFlags & flags[index]) || + Convert.ToBoolean((flags[index] & visibleFlag) & UpdateFieldFlags.SpecialInfo) || + ((updatetype == UpdateType.Values ? _changesMask.Get(index) : HasUpdateValue(index)) && flags[index].HasAnyFlag(visibleFlag)) || + (index == (int)UnitFields.AuraState && HasFlag(UnitFields.AuraState, AuraStateType.PerCasterAuraStateMask))) + { + updateMask.SetBit(index); + + if (index == (int)UnitFields.NpcFlags) + { + uint appendValue = GetUInt32Value(UnitFields.NpcFlags); + + if (creature != null) + if (!target.CanSeeSpellClickOn(creature)) + appendValue &= ~(uint)NPCFlags.SpellClick; + + fieldBuffer.WriteUInt32(appendValue); + } + else if (index == (int)UnitFields.AuraState) + { + // Check per caster aura states to not enable using a spell in client if specified aura is not by target + fieldBuffer.WriteUInt32(BuildAuraStateUpdateForTarget(target)); + } + // FIXME: Some values at server stored in float format but must be sent to client in public uint format + else if ((index >= (int)UnitFields.NegStat && index < (int)UnitFields.NegStat + (int)Stats.Max) || + (index >= (int)UnitFields.ResistanceBuffModsPositive && index < ((int)UnitFields.ResistanceBuffModsPositive + (int)SpellSchools.Max)) || + (index >= (int)UnitFields.ResistanceBuffModsNegative && index < ((int)UnitFields.ResistanceBuffModsNegative + (int)SpellSchools.Max)) || + (index >= (int)UnitFields.PosStat && index < (int)UnitFields.PosStat + (int)Stats.Max)) + { + fieldBuffer.WriteUInt32((uint)GetFloatValue(index)); + } + // Gamemasters should be always able to select units - remove not selectable flag + else if (index == (int)UnitFields.Flags) + { + UnitFlags appendValue = (UnitFlags)GetUInt32Value(index); + if (target.IsGameMaster()) + appendValue &= ~UnitFlags.NotSelectable; + + fieldBuffer.WriteUInt32((uint)appendValue); + } + // use modelid_a if not gm, _h if gm for CREATURE_FLAG_EXTRA_TRIGGER creatures + else if (index == (int)UnitFields.DisplayId) + { + uint displayId = GetUInt32Value(index); + if (creature != null) + { + CreatureTemplate cinfo = creature.GetCreatureTemplate(); + + // this also applies for transform auras + SpellInfo transform = Global.SpellMgr.GetSpellInfo(getTransForm()); + if (transform != null) + { + foreach (SpellEffectInfo effect in transform.GetEffectsForDifficulty(GetMap().GetDifficultyID())) + { + if (effect != null && effect.IsAura(AuraType.Transform)) + { + CreatureTemplate transformInfo = Global.ObjectMgr.GetCreatureTemplate((uint)effect.MiscValue); + if (transformInfo != null) + { + cinfo = transformInfo; + break; + } + } + } + } + + if (cinfo.FlagsExtra.HasAnyFlag(CreatureFlagsExtra.Trigger)) + if (target.IsGameMaster()) + displayId = cinfo.GetFirstVisibleModel(); + } + + fieldBuffer.WriteUInt32(displayId); + } + // hide lootable animation for unallowed players + else if (index == (int)ObjectFields.DynamicFlags) + { + UnitDynFlags dynamicFlags = (UnitDynFlags)GetUInt32Value(index) & ~UnitDynFlags.Tapped; + + if (creature != null) + { + if (creature.hasLootRecipient() && !creature.isTappedBy(target)) + dynamicFlags |= UnitDynFlags.Tapped; + + if (!target.isAllowedToLoot(creature)) + dynamicFlags &= ~UnitDynFlags.Lootable; + } + + // unit UNIT_DYNFLAG_TRACK_UNIT should only be sent to caster of SPELL_AURA_MOD_STALKED auras + if (dynamicFlags.HasAnyFlag(UnitDynFlags.TrackUnit)) + if (!HasAuraTypeWithCaster(AuraType.ModStalked, target.GetGUID())) + dynamicFlags &= ~UnitDynFlags.TrackUnit; + + fieldBuffer.WriteUInt32((uint)dynamicFlags); + } + // FG: pretend that OTHER players in own group are friendly ("blue") + else if (index == (int)UnitFields.Bytes2 || index == (int)UnitFields.FactionTemplate) + { + if (IsControlledByPlayer() && target != this && WorldConfig.GetBoolValue(WorldCfg.AllowTwoSideInteractionGroup) && IsInRaidWith(target)) + { + FactionTemplateRecord ft1 = GetFactionTemplateEntry(); + FactionTemplateRecord ft2 = target.GetFactionTemplateEntry(); + if (ft1 != null && ft2 != null && !ft1.IsFriendlyTo(ft2)) + { + if (index == (int)UnitFields.Bytes2) + // Allow targetting opposite faction in party when enabled in config + fieldBuffer.WriteUInt32(GetUInt32Value(index) & ((int)UnitBytes2Flags.Sanctuary << 8)); // this flag is at public byte offset 1 !! + else + // pretend that all other HOSTILE players have own faction, to allow follow, heal, rezz (trade wont work) + fieldBuffer.WriteUInt32(target.getFaction()); + } + else + fieldBuffer.WriteUInt32(GetUInt32Value(index)); + } + else + fieldBuffer.WriteUInt32(GetUInt32Value(index)); + } + else + { + // send in current format (float as float, public uint as uint32) + WriteValue(fieldBuffer, index); + } + } + } + + updateMask.AppendToPacket(data); + data.WriteBytes(fieldBuffer); + } + + public override void DestroyForPlayer(Player target) + { + Battleground bg = target.GetBattleground(); + if (bg != null) + { + if (bg.isArena()) + { + DestroyArenaUnit destroyArenaUnit = new DestroyArenaUnit(); + destroyArenaUnit.Guid = GetGUID(); + target.SendPacket(destroyArenaUnit); + } + } + + base.DestroyForPlayer(target); + } + } +} diff --git a/Game/Entities/Vehicle.cs b/Game/Entities/Vehicle.cs new file mode 100644 index 000000000..ee0df27eb --- /dev/null +++ b/Game/Entities/Vehicle.cs @@ -0,0 +1,696 @@ +/* + * Copyright (C) 2012-2017 CypherCore + * + * 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, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +using Framework.Constants; +using Framework.Dynamic; +using Game.BattleGrounds; +using Game.DataStorage; +using Game.Movement; +using System; +using System.Collections.Generic; +using System.Diagnostics.Contracts; +using System.Linq; + +namespace Game.Entities +{ + public class Vehicle : ITransport, IDisposable + { + public Vehicle(Unit unit, VehicleRecord vehInfo, uint creatureEntry) + { + _me = unit; + _vehicleInfo = vehInfo; + _creatureEntry = creatureEntry; + _status = Status.None; + _lastShootPos = new Position(); + + for (uint i = 0; i < SharedConst.MaxVehicleSeats; ++i) + { + uint seatId = _vehicleInfo.SeatID[i]; + if (seatId != 0) + { + VehicleSeatRecord veSeat = CliDB.VehicleSeatStorage.LookupByKey(seatId); + if (veSeat != null) + { + Seats.Add((sbyte)i, new VehicleSeat(veSeat)); + if (veSeat.CanEnterOrExit()) + ++UsableSeatNum; + } + } + } + + // Set or remove correct flags based on available seats. Will overwrite db data (if wrong). + if (UsableSeatNum != 0) + _me.SetFlag64(UnitFields.NpcFlags, (_me.IsTypeId(TypeId.Player) ? NPCFlags.PlayerVehicle : NPCFlags.SpellClick)); + else + _me.RemoveFlag64(UnitFields.NpcFlags, (_me.IsTypeId(TypeId.Player) ? NPCFlags.PlayerVehicle : NPCFlags.SpellClick)); + + InitMovementInfoForBase(); + } + + public virtual void Dispose() + { + /// @Uninstall must be called before this. + Contract.Assert(_status == Status.UnInstalling); + foreach (var pair in Seats) + Contract.Assert(pair.Value.IsEmpty()); + } + + public void Install() + { + if (_me.IsTypeId(TypeId.Unit)) + { + PowerDisplayRecord powerDisplay = CliDB.PowerDisplayStorage.LookupByKey(_vehicleInfo.PowerDisplayID[0]); + if (powerDisplay != null) + _me.setPowerType((PowerType)powerDisplay.PowerType); + else if (_me.GetClass() == Class.Rogue) + _me.setPowerType(PowerType.Energy); + } + + _status = Status.Installed; + if (GetBase().IsTypeId(TypeId.Unit)) + Global.ScriptMgr.OnInstall(this); + } + + public void InstallAllAccessories(bool evading) + { + if (GetBase().IsTypeId(TypeId.Player) || !evading) + RemoveAllPassengers(); // We might have aura's saved in the DB with now invalid casters - remove + + List accessories = Global.ObjectMgr.GetVehicleAccessoryList(this); + if (accessories == null) + return; + + foreach (var acc in accessories) + if (!evading || acc.IsMinion) // only install minions on evade mode + InstallAccessory(acc.AccessoryEntry, acc.SeatId, acc.IsMinion, acc.SummonedType, acc.SummonTime); + } + + public void Uninstall() + { + // @Prevent recursive uninstall call. (Bad script in OnUninstall/OnRemovePassenger/PassengerBoarded hook.) + if (_status == Status.UnInstalling && !GetBase().HasUnitTypeMask(UnitTypeMask.Minion)) + { + Log.outError(LogFilter.Vehicle, "Vehicle GuidLow: {0}, Entry: {1} attempts to uninstall, but already has STATUS_UNINSTALLING! " + + "Check Uninstall/PassengerBoarded script hooks for errors.", _me.GetGUID().ToString(), _me.GetEntry()); + return; + } + + _status = Status.UnInstalling; + Log.outDebug(LogFilter.Vehicle, "Vehicle.Uninstall Entry: {0}, GuidLow: {1}", _creatureEntry, _me.GetGUID().ToString()); + RemoveAllPassengers(); + + if (GetBase().IsTypeId(TypeId.Unit)) + Global.ScriptMgr.OnUninstall(this); + } + + public void Reset(bool evading = false) + { + if (!GetBase().IsTypeId(TypeId.Unit)) + return; + + Log.outDebug(LogFilter.Vehicle, "Vehicle.Reset (Entry: {0}, GuidLow: {1}, DBGuid: {2})", GetCreatureEntry(), _me.GetGUID().ToString(), _me.ToCreature().GetSpawnId()); + + ApplyAllImmunities(); + InstallAllAccessories(evading); + + Global.ScriptMgr.OnReset(this); + } + + void ApplyAllImmunities() + { + // This couldn't be done in DB, because some spells have MECHANIC_NONE + + // Vehicles should be immune on Knockback ... + _me.ApplySpellImmune(0, SpellImmunity.Effect, SpellEffectName.KnockBack, true); + _me.ApplySpellImmune(0, SpellImmunity.Effect, SpellEffectName.KnockBackDest, true); + + // Mechanical units & vehicles ( which are not Bosses, they have own immunities in DB ) should be also immune on healing ( exceptions in switch below ) + if (_me.IsTypeId(TypeId.Unit) && _me.ToCreature().GetCreatureTemplate().CreatureType == CreatureType.Mechanical && !_me.ToCreature().isWorldBoss()) + { + // Heal & dispel ... + _me.ApplySpellImmune(0, SpellImmunity.Effect, SpellEffectName.Heal, true); + _me.ApplySpellImmune(0, SpellImmunity.Effect, SpellEffectName.HealPct, true); + _me.ApplySpellImmune(0, SpellImmunity.Effect, SpellEffectName.Dispel, true); + _me.ApplySpellImmune(0, SpellImmunity.State, AuraType.PeriodicHeal, true); + + // ... Shield & Immunity grant spells ... + _me.ApplySpellImmune(0, SpellImmunity.State, AuraType.SchoolImmunity, true); + _me.ApplySpellImmune(0, SpellImmunity.State, AuraType.ModUnattackable, true); + _me.ApplySpellImmune(0, SpellImmunity.State, AuraType.SchoolAbsorb, true); + _me.ApplySpellImmune(0, SpellImmunity.Mechanic, Mechanics.Shield, true); + _me.ApplySpellImmune(0, SpellImmunity.Mechanic, Mechanics.Immune_Shield, true); + + // ... Resistance, Split damage, Change stats ... + _me.ApplySpellImmune(0, SpellImmunity.State, AuraType.DamageShield, true); + _me.ApplySpellImmune(0, SpellImmunity.State, AuraType.SplitDamagePct, true); + _me.ApplySpellImmune(0, SpellImmunity.State, AuraType.ModResistance, true); + _me.ApplySpellImmune(0, SpellImmunity.State, AuraType.ModStat, true); + _me.ApplySpellImmune(0, SpellImmunity.State, AuraType.ModDamagePercentTaken, true); + } + + // Different immunities for vehicles goes below + switch (GetVehicleInfo().Id) + { + // code below prevents a bug with movable cannons + case 160: // Strand of the Ancients + case 244: // Wintergrasp + case 452: // Isle of Conquest + case 510: // Isle of Conquest + case 543: // Isle of Conquest + _me.SetControlled(true, UnitState.Root); + // why we need to apply this? we can simple add immunities to slow mechanic in DB + _me.ApplySpellImmune(0, SpellImmunity.State, AuraType.ModDecreaseSpeed, true); + break; + default: + break; + } + } + + public void RemoveAllPassengers() + { + Log.outDebug(LogFilter.Vehicle, "Vehicle.RemoveAllPassengers. Entry: {0}, GuidLow: {1}", _creatureEntry, _me.GetGUID().ToString()); + + // Setting to_Abort to true will cause @VehicleJoinEvent.Abort to be executed on next @Unit.UpdateEvents call + // This will properly "reset" the pending join process for the passenger. + { + // Update vehicle in every pending join event - Abort may be called after vehicle is deleted + Vehicle eventVehicle = _status != Status.UnInstalling ? this : null; + + while (!_pendingJoinEvents.Empty()) + { + VehicleJoinEvent e = _pendingJoinEvents.First(); + e.ScheduleAbort(); + e.Target = eventVehicle; + _pendingJoinEvents.Remove(_pendingJoinEvents.First()); + } + } + + // Passengers always cast an aura with SPELL_AURA_CONTROL_VEHICLE on the vehicle + // We just remove the aura and the unapply handler will make the target leave the vehicle. + // We don't need to iterate over Seats + _me.RemoveAurasByType(AuraType.ControlVehicle); + } + + public bool HasEmptySeat(sbyte seatId) + { + var seat = Seats.LookupByKey(seatId); + if (seat == null) + return false; + return seat.IsEmpty(); + } + + public Unit GetPassenger(sbyte seatId) + { + var seat = Seats.LookupByKey(seatId); + if (seat == null) + return null; + + return Global.ObjAccessor.GetUnit(GetBase(), seat.Passenger.Guid); + } + + public VehicleSeat GetNextEmptySeat(sbyte seatId, bool next) + { + var seat = Seats.LookupByKey(seatId); + if (seat == null) + return seat; + + foreach (var sea in Seats) + { + if (!seat.IsEmpty() || (!seat.SeatInfo.CanEnterOrExit() && !seat.SeatInfo.IsUsableByOverride())) + continue; + + seat = sea.Value; + } + + return seat; + } + + void InstallAccessory(uint entry, sbyte seatId, bool minion, byte type, uint summonTime) + { + // @Prevent adding accessories when vehicle is uninstalling. (Bad script in OnUninstall/OnRemovePassenger/PassengerBoarded hook.) + + if (_status == Status.UnInstalling) + { + Log.outError(LogFilter.Vehicle, "Vehicle ({0}, Entry: {1}) attempts to install accessory (Entry: {2}) on seat {3} with STATUS_UNINSTALLING! " + + "Check Uninstall/PassengerBoarded script hooks for errors.", _me.GetGUID().ToString(), GetCreatureEntry(), entry, seatId); + return; + } + + Log.outDebug(LogFilter.Vehicle, "Vehicle ({0}, Entry {1}): installing accessory (Entry: {2}) on seat: {3}", _me.GetGUID().ToString(), GetCreatureEntry(), entry, seatId); + + TempSummon accessory = _me.SummonCreature(entry, _me, (TempSummonType)type, summonTime); + Contract.Assert(accessory); + + if (minion) + accessory.AddUnitTypeMask(UnitTypeMask.Accessory); + + _me.HandleSpellClick(accessory, seatId); + + // If for some reason adding accessory to vehicle fails it will unsummon in + // @VehicleJoinEvent.Abort + } + + public bool AddPassenger(Unit unit, sbyte seatId) + { + // @Prevent adding passengers when vehicle is uninstalling. (Bad script in OnUninstall/OnRemovePassenger/PassengerBoarded hook.) + if (_status == Status.UnInstalling) + { + Log.outError(LogFilter.Vehicle, "Passenger GuidLow: {0}, Entry: {1}, attempting to board vehicle GuidLow: {2}, Entry: {3} during uninstall! SeatId: {4}", + unit.GetGUID().ToString(), unit.GetEntry(), _me.GetGUID().ToString(), _me.GetEntry(), seatId); + return false; + } + + Log.outDebug(LogFilter.Vehicle, "Unit {0} scheduling enter vehicle (entry: {1}, vehicleId: {2}, guid: {3} (dbguid: {4}) on seat {5}", + unit.GetName(), _me.GetEntry(), _vehicleInfo.Id, _me.GetGUID().ToString(), + (_me.IsTypeId(TypeId.Unit) ? _me.ToCreature().GetSpawnId() : 0), seatId); + + // The seat selection code may kick other passengers off the vehicle. + // While the validity of the following may be arguable, it is possible that when such a passenger + // exits the vehicle will dismiss. That's why the actual adding the passenger to the vehicle is scheduled + // asynchronously, so it can be cancelled easily in case the vehicle is uninstalled meanwhile. + VehicleJoinEvent e = new VehicleJoinEvent(this, unit); + unit.m_Events.AddEvent(e, unit.m_Events.CalculateTime(0)); + + KeyValuePair seat = new KeyValuePair(); + if (seatId < 0) // no specific seat requirement + { + foreach (var _seat in Seats) + { + seat = _seat; + if (seat.Value.IsEmpty() && (_seat.Value.SeatInfo.CanEnterOrExit() || _seat.Value.SeatInfo.IsUsableByOverride())) + break; + } + + if (seat.Value == null) // no available seat + { + e.ScheduleAbort(); + return false; + } + + e.Seat = seat; + _pendingJoinEvents.Add(e); + } + else + { + seat = new KeyValuePair(seatId, Seats.LookupByKey(seatId)); + if (seat.Value == null) + { + e.ScheduleAbort(); + return false; + } + + e.Seat = seat; + _pendingJoinEvents.Add(e); + if (!seat.Value.IsEmpty()) + { + Unit passenger = Global.ObjAccessor.GetUnit(GetBase(), seat.Value.Passenger.Guid); + Contract.Assert(passenger != null); + passenger.ExitVehicle(); + } + + Contract.Assert(seat.Value.IsEmpty()); + } + + return true; + } + + public Vehicle RemovePassenger(Unit unit) + { + if (unit.GetVehicle() != this) + return null; + + var seat = GetSeatKeyValuePairForPassenger(unit); + Contract.Assert(seat.Value != null); + + Log.outDebug( LogFilter.Vehicle, "Unit {0} exit vehicle entry {1} id {2} dbguid {3} seat {4}", + unit.GetName(), _me.GetEntry(), _vehicleInfo.Id, _me.GetGUID().ToString(), seat.Key); + + if (seat.Value.SeatInfo.CanEnterOrExit() && ++UsableSeatNum != 0) + _me.SetFlag64(UnitFields.NpcFlags, (_me.IsTypeId(TypeId.Player) ? NPCFlags.PlayerVehicle : NPCFlags.SpellClick)); + + // Remove UNIT_FLAG_NOT_SELECTABLE if passenger did not have it before entering vehicle + if (seat.Value.SeatInfo.Flags[0].HasAnyFlag((uint)VehicleSeatFlags.PassengerNotSelectable) && !seat.Value.Passenger.IsUnselectable) + unit.RemoveFlag(UnitFields.Flags, UnitFlags.NotSelectable); + + seat.Value.Passenger.Reset(); + + if (_me.IsTypeId(TypeId.Unit) && unit.IsTypeId(TypeId.Player) && seat.Value.SeatInfo.Flags[0].HasAnyFlag((uint)VehicleSeatFlags.CanControl)) + _me.RemoveCharmedBy(unit); + + if (_me.IsInWorld) + unit.m_movementInfo.ResetTransport(); + + // only for flyable vehicles + if (unit.IsFlying()) + _me.CastSpell(unit, SharedConst.VehicleSpellParachute, true); + + if (_me.IsTypeId(TypeId.Unit) && _me.ToCreature().IsAIEnabled) + _me.ToCreature().GetAI().PassengerBoarded(unit, seat.Key, false); + + if (GetBase().IsTypeId(TypeId.Unit)) + Global.ScriptMgr.OnRemovePassenger(this, unit); + + unit.SetVehicle(null); + return this; + } + + public void RelocatePassengers() + { + Contract.Assert(_me.GetMap() != null); + + List> seatRelocation = new List>(); + + // not sure that absolute position calculation is correct, it must depend on vehicle pitch angle + foreach (var pair in Seats) + { + Unit passenger = Global.ObjAccessor.GetUnit(GetBase(), pair.Value.Passenger.Guid); + if (passenger != null) + { + Contract.Assert(passenger.IsInWorld); + + float px, py, pz, po; + passenger.m_movementInfo.transport.pos.GetPosition(out px, out py, out pz, out po); + CalculatePassengerPosition(ref px, ref py, ref pz, ref po); + + seatRelocation.Add(Tuple.Create(passenger, new Position(px, py, pz, po))); + } + } + + foreach (var pair in seatRelocation) + pair.Item1.UpdatePosition(pair.Item2); + } + + public bool IsVehicleInUse() + { + foreach (var pair in Seats) + if (!pair.Value.IsEmpty()) + return true; + + return false; + } + + void InitMovementInfoForBase() + { + VehicleFlags vehicleFlags = (VehicleFlags)GetVehicleInfo().Flags; + + if (vehicleFlags.HasAnyFlag(VehicleFlags.NoStrafe)) + _me.AddUnitMovementFlag2(MovementFlag2.NoStrafe); + if (vehicleFlags.HasAnyFlag(VehicleFlags.NoJumping)) + _me.AddUnitMovementFlag2(MovementFlag2.NoJumping); + if (vehicleFlags.HasAnyFlag(VehicleFlags.Fullspeedturning)) + _me.AddUnitMovementFlag2(MovementFlag2.FullSpeedTurning); + if (vehicleFlags.HasAnyFlag(VehicleFlags.AllowPitching)) + _me.AddUnitMovementFlag2(MovementFlag2.AlwaysAllowPitching); + if (vehicleFlags.HasAnyFlag(VehicleFlags.Fullspeedpitching)) + _me.AddUnitMovementFlag2(MovementFlag2.FullSpeedPitching); + } + + public VehicleSeatRecord GetSeatForPassenger(Unit passenger) + { + foreach (var pair in Seats) + if (pair.Value.Passenger.Guid == passenger.GetGUID()) + return pair.Value.SeatInfo; + + return null; + } + + KeyValuePair GetSeatKeyValuePairForPassenger(Unit passenger) + { + foreach (var pair in Seats) + if (pair.Value.Passenger.Guid == passenger.GetGUID()) + return pair; + + return Seats.Last(); + } + + public byte GetAvailableSeatCount() + { + byte ret = 0; + foreach (var pair in Seats) + if (pair.Value.IsEmpty() && (pair.Value.SeatInfo.CanEnterOrExit() || pair.Value.SeatInfo.IsUsableByOverride())) + ++ret; + + return ret; + } + + public void CalculatePassengerPosition(ref float x, ref float y, ref float z, ref float o) + { + TransportPosHelper.CalculatePassengerPosition(ref x, ref y, ref z, ref o, + GetBase().GetPositionX(), GetBase().GetPositionY(), + GetBase().GetPositionZ(), GetBase().GetOrientation()); + } + + public void CalculatePassengerOffset(ref float x, ref float y, ref float z, ref float o) + { + TransportPosHelper.CalculatePassengerOffset(ref x, ref y, ref z, ref o, + GetBase().GetPositionX(), GetBase().GetPositionY(), + GetBase().GetPositionZ(), GetBase().GetOrientation()); + } + + public void RemovePendingEvent(VehicleJoinEvent e) + { + foreach (var Event in _pendingJoinEvents) + { + if (Event == e) + { + _pendingJoinEvents.Remove(Event); + break; + } + } + } + + public void RemovePendingEventsForSeat(sbyte seatId) + { + foreach (var Event in _pendingJoinEvents.ToList()) + { + if (Event.Seat.Key == seatId) + { + Event.ScheduleAbort(); + _pendingJoinEvents.Remove(Event); + } + } + } + + public void RemovePendingEventsForPassenger(Unit passenger) + { + foreach (var Event in _pendingJoinEvents.ToList()) + { + if (Event.Passenger == passenger) + { + Event.ScheduleAbort(); + _pendingJoinEvents.Remove(Event); + } + } + } + + public Unit GetBase() { return _me; } + public VehicleRecord GetVehicleInfo() { return _vehicleInfo; } + public uint GetCreatureEntry() { return _creatureEntry; } + + public void SetLastShootPos(Position pos) { _lastShootPos.Relocate(pos); } + Position GetLastShootPos() { return _lastShootPos; } + + Unit _me; + VehicleRecord _vehicleInfo; //< DBC data for vehicle + List vehiclePlayers = new List(); + + uint _creatureEntry; //< Can be different than the entry of _me in case of players + Status _status; //< Internal variable for sanity checks + Position _lastShootPos; + + List _pendingJoinEvents = new List(); + public Dictionary Seats = new Dictionary(); + public uint UsableSeatNum; //< Number of seats that match VehicleSeatEntry.UsableByPlayer, used for proper display flags + + public static implicit operator bool(Vehicle vehicle) + { + return vehicle != null; + } + + public enum Status + { + None, + Installed, + UnInstalling, + } + } + + public class VehicleJoinEvent : BasicEvent + { + public VehicleJoinEvent(Vehicle v, Unit u) + { + Target = v; + Passenger = u; + Seat = Target.Seats.Last(); + } + + public override bool Execute(ulong e_time, uint p_time) + { + Contract.Assert(Passenger.IsInWorld); + Contract.Assert(Target != null && Target.GetBase().IsInWorld); + Contract.Assert(Target.GetBase().HasAuraTypeWithCaster(AuraType.ControlVehicle, Passenger.GetGUID())); + + Target.RemovePendingEventsForSeat(Seat.Key); + Target.RemovePendingEventsForPassenger(Passenger); + + Passenger.SetVehicle(Target); + Seat.Value.Passenger.Guid = Passenger.GetGUID(); + Seat.Value.Passenger.IsUnselectable = Passenger.HasFlag(UnitFields.Flags, UnitFlags.NotSelectable); + if (Seat.Value.SeatInfo.CanEnterOrExit()) + { + Contract.Assert(Target.UsableSeatNum != 0); + --Target.UsableSeatNum; + if (Target.UsableSeatNum == 0) + { + if (Target.GetBase().IsTypeId(TypeId.Player)) + Target.GetBase().RemoveFlag64(UnitFields.NpcFlags, NPCFlags.PlayerVehicle); + else + Target.GetBase().RemoveFlag64(UnitFields.NpcFlags, NPCFlags.SpellClick); + } + } + + Passenger.InterruptNonMeleeSpells(false); + Passenger.RemoveAurasByType(AuraType.Mounted); + + VehicleSeatRecord veSeat = Seat.Value.SeatInfo; + + Player player = Passenger.ToPlayer(); + if (player != null) + { + // drop flag + Battleground bg = player.GetBattleground(); + if (bg) + bg.EventPlayerDroppedFlag(player); + + player.StopCastingCharm(); + player.StopCastingBindSight(); + player.SendOnCancelExpectedVehicleRideAura(); + if (!veSeat.Flags[1].HasAnyFlag((uint)VehicleSeatFlagsB.KeepPet)) + player.UnsummonPetTemporaryIfAny(); + } + + if (Seat.Value.SeatInfo.Flags[0].HasAnyFlag((uint)VehicleSeatFlags.PassengerNotSelectable)) + Passenger.SetFlag(UnitFields.Flags, UnitFlags.NotSelectable); + + Passenger.m_movementInfo.transport.pos.Relocate(veSeat.AttachmentOffset.X, veSeat.AttachmentOffset.Y, veSeat.AttachmentOffset.Z); + Passenger.m_movementInfo.transport.time = 0; + Passenger.m_movementInfo.transport.seat = Seat.Key; + Passenger.m_movementInfo.transport.guid = Target.GetBase().GetGUID(); + + if (Target.GetBase().IsTypeId(TypeId.Unit) && Passenger.IsTypeId(TypeId.Player) && + Seat.Value.SeatInfo.Flags[0].HasAnyFlag((uint)VehicleSeatFlags.CanControl)) + Contract.Assert(Target.GetBase().SetCharmedBy(Passenger, CharmType.Vehicle)); // SMSG_CLIENT_CONTROL + + Passenger.SendClearTarget(); // SMSG_BREAK_TARGET + Passenger.SetControlled(true, UnitState.Root); // SMSG_FORCE_ROOT - In some cases we send SMSG_SPLINE_MOVE_ROOT here (for creatures) + // also adds MOVEMENTFLAG_ROOT + + MoveSplineInit init = new MoveSplineInit(Passenger); + init.DisableTransportPathTransformations(); + init.MoveTo(veSeat.AttachmentOffset.X, veSeat.AttachmentOffset.Y, veSeat.AttachmentOffset.Z, false, true); + init.SetFacing(0.0f); + init.SetTransportEnter(); + init.Launch(); + + Creature creature = Target.GetBase().ToCreature(); + if (creature != null) + { + if (creature.IsAIEnabled) + creature.GetAI().PassengerBoarded(Passenger, Seat.Key, true); + + Global.ScriptMgr.OnAddPassenger(Target, Passenger, Seat.Key); + + // Actually quite a redundant hook. Could just use OnAddPassenger and check for unit typemask inside script. + if (Passenger.HasUnitTypeMask(UnitTypeMask.Accessory)) + Global.ScriptMgr.OnInstallAccessory(Target, Passenger.ToCreature()); + } + + return true; + } + + public override void Abort(ulong e_time) + { + // Check if the Vehicle was already uninstalled, in which case all auras were removed already + if (Target != null) + { + Log.outDebug(LogFilter.Vehicle, "Passenger GuidLow: {0}, Entry: {1}, board on vehicle GuidLow: {2}, Entry: {3} SeatId: {4} cancelled", + Passenger.GetGUID().ToString(), Passenger.GetEntry(), Target.GetBase().GetGUID().ToString(), Target.GetBase().GetEntry(), Seat.Key); + + /// Remove the pending event when Abort was called on the event directly + Target.RemovePendingEvent(this); + + // @SPELL_AURA_CONTROL_VEHICLE auras can be applied even when the passenger is not (yet) on the vehicle. + // When this code is triggered it means that something went wrong in @Vehicle.AddPassenger, and we should remove + // the aura manually. + Target.GetBase().RemoveAurasByType(AuraType.ControlVehicle, Passenger.GetGUID()); + } + else + Log.outDebug(LogFilter.Vehicle, "Passenger GuidLow: {0}, Entry: {1}, board on uninstalled vehicle SeatId: {2} cancelled", + Passenger.GetGUID().ToString(), Passenger.GetEntry(), Seat.Key); + + if (Passenger.IsInWorld && Passenger.HasUnitTypeMask(UnitTypeMask.Accessory)) + Passenger.ToCreature().DespawnOrUnsummon(); + } + + public Vehicle Target; + public Unit Passenger; + public KeyValuePair Seat; + } + + public struct PassengerInfo + { + public ObjectGuid Guid; + public bool IsUnselectable; + + public void Reset() + { + Guid = ObjectGuid.Empty; + IsUnselectable = false; + } + } + + public class VehicleSeat + { + public VehicleSeat(VehicleSeatRecord seatInfo) + { + SeatInfo = seatInfo; + Passenger.Reset(); + } + + public bool IsEmpty() { return Passenger.Guid.IsEmpty(); } + + public VehicleSeatRecord SeatInfo; + public PassengerInfo Passenger; + } + + public struct VehicleAccessory + { + public VehicleAccessory(uint entry, sbyte seatId, bool isMinion, byte summonType, uint summonTime) + { + AccessoryEntry = entry; + IsMinion = isMinion; + SummonTime = summonTime; + SeatId = seatId; + SummonedType = summonType; + } + public uint AccessoryEntry; + public bool IsMinion; + public uint SummonTime; + public sbyte SeatId; + public byte SummonedType; + } +} diff --git a/Game/Events/GameEventManager.cs b/Game/Events/GameEventManager.cs new file mode 100644 index 000000000..826f26596 --- /dev/null +++ b/Game/Events/GameEventManager.cs @@ -0,0 +1,1722 @@ +/* + * Copyright (C) 2012-2017 CypherCore + * + * 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, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +using Framework.Constants; +using Framework.Database; +using Game.DataStorage; +using Game.Entities; +using Game.Maps; +using Game.Network.Packets; +using System; +using System.Collections.Generic; + +namespace Game +{ + public class GameEventManager : Singleton + { + GameEventManager() { } + + bool CheckOneGameEvent(ushort entry) + { + switch (mGameEvent[entry].state) + { + default: + case GameEventState.Normal: + { + long currenttime = Time.UnixTime; + // Get the event information + return mGameEvent[entry].start < currenttime + && currenttime < mGameEvent[entry].end + && (currenttime - mGameEvent[entry].start) % (mGameEvent[entry].occurence * Time.Minute) < mGameEvent[entry].length * Time.Minute; + } + // if the state is conditions or nextphase, then the event should be active + case GameEventState.WorldConditions: + case GameEventState.WorldNextPhase: + return true; + // finished world events are inactive + case GameEventState.WorldFinished: + case GameEventState.Internal: + return false; + // if inactive world event, check the prerequisite events + case GameEventState.WorldInactive: + { + long currenttime = Time.UnixTime; + foreach (var gameEventId in mGameEvent[entry].prerequisite_events) + { + if ((mGameEvent[gameEventId].state != GameEventState.WorldNextPhase && mGameEvent[gameEventId].state != GameEventState.WorldFinished) || // if prereq not in nextphase or finished state, then can't start this one + mGameEvent[gameEventId].nextstart > currenttime) // if not in nextphase state for long enough, can't start this one + return false; + } + // all prerequisite events are met + // but if there are no prerequisites, this can be only activated through gm command + return !(mGameEvent[entry].prerequisite_events.Empty()); + } + } + } + + public uint NextCheck(ushort entry) + { + long currenttime = Time.UnixTime; + + // for NEXTPHASE state world events, return the delay to start the next event, so the followup event will be checked correctly + if ((mGameEvent[entry].state == GameEventState.WorldNextPhase || mGameEvent[entry].state == GameEventState.WorldFinished) && mGameEvent[entry].nextstart >= currenttime) + return (uint)(mGameEvent[entry].nextstart - currenttime); + + // for CONDITIONS state world events, return the length of the wait period, so if the conditions are met, this check will be called again to set the timer as NEXTPHASE event + if (mGameEvent[entry].state == GameEventState.WorldConditions) + { + if (mGameEvent[entry].length != 0) + return mGameEvent[entry].length * 60; + else + return Time.Day; + } + + // outdated event: we return max + if (currenttime > mGameEvent[entry].end) + return Time.Day; + + // never started event, we return delay before start + if (mGameEvent[entry].start > currenttime) + return (uint)(mGameEvent[entry].start - currenttime); + + uint delay; + // in event, we return the end of it + if ((((currenttime - mGameEvent[entry].start) % (mGameEvent[entry].occurence * 60)) < (mGameEvent[entry].length * 60))) + // we return the delay before it ends + delay = (uint)((mGameEvent[entry].length * Time.Minute) - ((currenttime - mGameEvent[entry].start) % (mGameEvent[entry].occurence * Time.Minute))); + else // not in window, we return the delay before next start + delay = (uint)((mGameEvent[entry].occurence * Time.Minute) - ((currenttime - mGameEvent[entry].start) % (mGameEvent[entry].occurence * Time.Minute))); + // In case the end is before next check + if (mGameEvent[entry].end < currenttime + delay) + return (uint)(mGameEvent[entry].end - currenttime); + else + return delay; + } + + void StartInternalEvent(ushort event_id) + { + if (event_id < 1 || event_id >= mGameEvent.Length) + return; + + if (!mGameEvent[event_id].isValid()) + return; + + if (m_ActiveEvents.Contains(event_id)) + return; + + StartEvent(event_id); + } + + public bool StartEvent(ushort event_id, bool overwrite = false) + { + GameEventData data = mGameEvent[event_id]; + if (data.state == GameEventState.Normal || data.state == GameEventState.Internal) + { + AddActiveEvent(event_id); + ApplyNewEvent(event_id); + if (overwrite) + { + mGameEvent[event_id].start = Time.UnixTime; + if (data.end <= data.start) + data.end = data.start + data.length; + } + + // When event is started, set its worldstate to current time + Global.WorldMgr.setWorldState(event_id, Time.UnixTime); + return false; + } + else + { + if (data.state == GameEventState.WorldInactive) + // set to conditions phase + data.state = GameEventState.WorldConditions; + + // add to active events + AddActiveEvent(event_id); + // add spawns + ApplyNewEvent(event_id); + + // check if can go to next state + bool conditions_met = CheckOneGameEventConditions(event_id); + // save to db + SaveWorldEventStateToDB(event_id); + // force game event update to set the update timer if conditions were met from a command + // this update is needed to possibly start events dependent on the started one + // or to scedule another update where the next event will be started + if (overwrite && conditions_met) + Global.WorldMgr.ForceGameEventUpdate(); + + return conditions_met; + } + } + + public void StopEvent(ushort event_id, bool overwrite = false) + { + GameEventData data = mGameEvent[event_id]; + bool serverwide_evt = data.state != GameEventState.Normal && data.state != GameEventState.Internal; + + RemoveActiveEvent(event_id); + UnApplyEvent(event_id); + + // When event is stopped, clean up its worldstate + Global.WorldMgr.setWorldState(event_id, 0); + + if (overwrite && !serverwide_evt) + { + data.start = Time.UnixTime - data.length * Time.Minute; + if (data.end <= data.start) + data.end = data.start + data.length; + } + else if (serverwide_evt) + { + // if finished world event, then only gm command can stop it + if (overwrite || data.state != GameEventState.WorldFinished) + { + // reset conditions + data.nextstart = 0; + data.state = GameEventState.WorldInactive; + foreach (var pair in data.conditions) + pair.Value.done = 0; + + SQLTransaction trans = new SQLTransaction(); + PreparedStatement stmt = DB.Characters.GetPreparedStatement(CharStatements.DEL_ALL_GAME_EVENT_CONDITION_SAVE); + stmt.AddValue(0, event_id); + trans.Append(stmt); + + stmt = DB.Characters.GetPreparedStatement(CharStatements.DEL_GAME_EVENT_SAVE); + stmt.AddValue(0, event_id); + trans.Append(stmt); + + DB.Characters.CommitTransaction(trans); + } + } + } + + public void LoadFromDB() + { + { + uint oldMSTime = Time.GetMSTime(); + // 0 1 2 3 4 5 6 7 8 + SQLResult result = DB.World.Query("SELECT eventEntry, UNIX_TIMESTAMP(start_time), UNIX_TIMESTAMP(end_time), occurence, length, holiday, description, world_event, announce FROM game_event"); + if (result.IsEmpty()) + { + mGameEvent.Initialize(); + Log.outInfo(LogFilter.ServerLoading, "Loaded 0 game events. DB table `game_event` is empty."); + return; + } + + uint count = 0; + do + { + byte event_id = result.Read(0); + if (event_id == 0) + { + Log.outError(LogFilter.Sql, "`game_event` game event entry 0 is reserved and can't be used."); + continue; + } + + GameEventData pGameEvent = mGameEvent[event_id]; + ulong starttime = result.Read(1); + pGameEvent.start = (long)starttime; + ulong endtime = result.Read(2); + pGameEvent.end = (long)endtime; + pGameEvent.occurence = result.Read(3); + pGameEvent.length = result.Read(4); + pGameEvent.holiday_id = (HolidayIds)result.Read(5); + + pGameEvent.state = (GameEventState)result.Read(7); + pGameEvent.nextstart = 0; + pGameEvent.announce = result.Read(8); + + if (pGameEvent.length == 0 && pGameEvent.state == GameEventState.Normal) // length>0 is validity check + { + Log.outError(LogFilter.Sql, "`game_event` game event id ({0}) isn't a world event and has length = 0, thus it can't be used.", event_id); + continue; + } + + if (pGameEvent.holiday_id != HolidayIds.None) + { + if (!CliDB.HolidaysStorage.ContainsKey((uint)pGameEvent.holiday_id)) + { + Log.outError(LogFilter.Sql, "`game_event` game event id ({0}) have not existed holiday id {1}.", event_id, pGameEvent.holiday_id); + pGameEvent.holiday_id = HolidayIds.None; + } + } + + pGameEvent.description = result.Read(6); + + //mGameEvent[event_id] = pGameEvent; + + ++count; + } + while (result.NextRow()); + + Log.outInfo(LogFilter.ServerLoading, "Loaded {0} game events in {1} ms", count, Time.GetMSTimeDiffToNow(oldMSTime)); + + } + + Log.outInfo(LogFilter.ServerLoading, "Loading Game Event Saves Data..."); + { + uint oldMSTime = Time.GetMSTime(); + + // 0 1 2 + SQLResult result = DB.Characters.Query("SELECT eventEntry, state, next_start FROM game_event_save"); + + if (result.IsEmpty()) + Log.outInfo(LogFilter.ServerLoading, "Loaded 0 game event saves in game events. DB table `game_event_save` is empty."); + else + { + uint count = 0; + do + { + byte event_id = result.Read(0); + + if (event_id >= mGameEvent.Length) + { + Log.outError(LogFilter.Sql, "`game_event_save` game event entry ({0}) not exist in `game_event`", event_id); + continue; + } + + if (mGameEvent[event_id].state != GameEventState.Normal && mGameEvent[event_id].state != GameEventState.Internal) + { + mGameEvent[event_id].state = (GameEventState)result.Read(1); + mGameEvent[event_id].nextstart = result.Read(2); + } + else + { + Log.outError(LogFilter.Sql, "game_event_save includes event save for non-worldevent id {0}", event_id); + continue; + } + + ++count; + } + while (result.NextRow()); + + Log.outInfo(LogFilter.ServerLoading, "Loaded {0} game event saves in game events in {1} ms", count, Time.GetMSTimeDiffToNow(oldMSTime)); + + } + } + + Log.outInfo(LogFilter.ServerLoading, "Loading Game Event Prerequisite Data..."); + { + uint oldMSTime = Time.GetMSTime(); + + // 0 1 + SQLResult result = DB.World.Query("SELECT eventEntry, prerequisite_event FROM game_event_prerequisite"); + if (result.IsEmpty()) + Log.outInfo(LogFilter.ServerLoading, "Loaded 0 game event prerequisites in game events. DB table `game_event_prerequisite` is empty."); + else + { + uint count = 0; + do + { + ushort event_id = result.Read(0); + + if (event_id >= mGameEvent.Length) + { + Log.outError(LogFilter.Sql, "`game_event_prerequisite` game event id ({0}) is out of range compared to max event id in `game_event`", event_id); + continue; + } + + if (mGameEvent[event_id].state != GameEventState.Normal && mGameEvent[event_id].state != GameEventState.Internal) + { + ushort prerequisite_event = result.Read(1); + if (prerequisite_event >= mGameEvent.Length) + { + Log.outError(LogFilter.Sql, "`game_event_prerequisite` game event prerequisite id ({0}) not exist in `game_event`", prerequisite_event); + continue; + } + mGameEvent[event_id].prerequisite_events.Add(prerequisite_event); + } + else + { + Log.outError(LogFilter.Sql, "game_event_prerequisiste includes event entry for non-worldevent id {0}", event_id); + continue; + } + + ++count; + } + while (result.NextRow()); + + Log.outInfo(LogFilter.ServerLoading, "Loaded {0} game event prerequisites in game events in {1} ms", count, Time.GetMSTimeDiffToNow(oldMSTime)); + + } + } + + Log.outInfo(LogFilter.ServerLoading, "Loading Game Event Creature Data..."); + { + uint oldMSTime = Time.GetMSTime(); + + // 0 1 + SQLResult result = DB.World.Query("SELECT guid, eventEntry FROM game_event_creature"); + + if (result.IsEmpty()) + Log.outInfo(LogFilter.ServerLoading, "Loaded 0 creatures in game events. DB table `game_event_creature` is empty"); + else + { + uint count = 0; + do + { + ulong guid = result.Read(0); + short event_id = result.Read(1); + int internal_event_id = mGameEvent.Length + event_id - 1; + + CreatureData data = Global.ObjectMgr.GetCreatureData(guid); + if (data == null) + { + Log.outError(LogFilter.Sql, "`game_event_creature` contains creature (GUID: {0}) not found in `creature` table.", guid); + continue; + } + + if (internal_event_id < 0 || internal_event_id >= mGameEventCreatureGuids.Length) + { + Log.outError(LogFilter.Sql, "`game_event_creature` game event id ({0}) not exist in `game_event`", event_id); + continue; + } + mGameEventCreatureGuids[internal_event_id].Add(guid); + + ++count; + } + while (result.NextRow()); + + Log.outInfo(LogFilter.ServerLoading, "Loaded {0} creatures in game events in {1} ms", count, Time.GetMSTimeDiffToNow(oldMSTime)); + + } + } + + Log.outInfo(LogFilter.ServerLoading, "Loading Game Event GO Data..."); + { + uint oldMSTime = Time.GetMSTime(); + + // 0 1 + SQLResult result = DB.World.Query("SELECT guid, eventEntry FROM game_event_gameobject"); + + if (result.IsEmpty()) + Log.outInfo(LogFilter.ServerLoading, "Loaded 0 gameobjects in game events. DB table `game_event_gameobject` is empty."); + else + { + uint count = 0; + do + { + ulong guid = result.Read(0); + short event_id = result.Read(1); + int internal_event_id = mGameEvent.Length + event_id - 1; + + + GameObjectData data = Global.ObjectMgr.GetGOData(guid); + if (data == null) + { + Log.outError(LogFilter.Sql, "`game_event_gameobject` contains gameobject (GUID: {0}) not found in `gameobject` table.", guid); + continue; + } + + if (internal_event_id < 0 || internal_event_id >= mGameEventGameobjectGuids.Length) + { + Log.outError(LogFilter.Sql, "`game_event_gameobject` game event id ({0}) not exist in `game_event`", event_id); + continue; + } + mGameEventGameobjectGuids[internal_event_id].Add(guid); + + ++count; + } + while (result.NextRow()); + + Log.outInfo(LogFilter.ServerLoading, "Loaded {0} gameobjects in game events in {1} ms", count, Time.GetMSTimeDiffToNow(oldMSTime)); + } + } + + Log.outInfo(LogFilter.ServerLoading, "Loading Game Event Model/Equipment Change Data..."); + { + uint oldMSTime = Time.GetMSTime(); + + // 0 1 2 3 4 + SQLResult result = DB.World.Query("SELECT creature.guid, creature.id, game_event_model_equip.eventEntry, game_event_model_equip.modelid, game_event_model_equip.equipment_id " + + "FROM creature JOIN game_event_model_equip ON creature.guid=game_event_model_equip.guid"); + + if (result.IsEmpty()) + Log.outInfo(LogFilter.ServerLoading, "Loaded 0 model/equipment changes in game events. DB table `game_event_model_equip` is empty."); + else + { + uint count = 0; + do + { + ulong guid = result.Read(0); + uint entry = result.Read(1); + ushort event_id = result.Read(2); + + if (event_id >= mGameEventModelEquip.Length) + { + Log.outError(LogFilter.Sql, "`game_event_model_equip` game event id ({0}) is out of range compared to max event id in `game_event`", event_id); + continue; + } + + ModelEquip newModelEquipSet = new ModelEquip(); + newModelEquipSet.modelid = result.Read(3); + newModelEquipSet.equipment_id = result.Read(4); + newModelEquipSet.equipement_id_prev = 0; + newModelEquipSet.modelid_prev = 0; + + if (newModelEquipSet.equipment_id > 0) + { + sbyte equipId = (sbyte)newModelEquipSet.equipment_id; + if (Global.ObjectMgr.GetEquipmentInfo(entry, equipId) == null) + { + Log.outError(LogFilter.Sql, "Table `game_event_model_equip` have creature (Guid: {0}, entry: {1}) with equipment_id {2} not found in table `creature_equip_template`, set to no equipment.", + guid, entry, newModelEquipSet.equipment_id); + continue; + } + } + + mGameEventModelEquip[event_id].Add(Tuple.Create(guid, newModelEquipSet)); + + ++count; + } + while (result.NextRow()); + + Log.outInfo(LogFilter.ServerLoading, "Loaded {0} model/equipment changes in game events in {1} ms", count, Time.GetMSTimeDiffToNow(oldMSTime)); + } + } + + Log.outInfo(LogFilter.ServerLoading, "Loading Game Event Quest Data..."); + { + uint oldMSTime = Time.GetMSTime(); + + // 0 1 2 + SQLResult result = DB.World.Query("SELECT id, quest, eventEntry FROM game_event_creature_quest"); + + if (result.IsEmpty()) + Log.outInfo(LogFilter.ServerLoading, "Loaded 0 quests additions in game events. DB table `game_event_creature_quest` is empty."); + else + { + uint count = 0; + do + { + uint id = result.Read(0); + uint quest = result.Read(1); + ushort event_id = result.Read(2); + + if (event_id >= mGameEventCreatureQuests.Length) + { + Log.outError(LogFilter.Sql, "`game_event_creature_quest` game event id ({0}) not exist in `game_event`", event_id); + continue; + } + + mGameEventCreatureQuests[event_id].Add(Tuple.Create(id, quest)); + + ++count; + } + while (result.NextRow()); + + Log.outInfo(LogFilter.ServerLoading, "Loaded {0} quests additions in game events in {1} ms", count, Time.GetMSTimeDiffToNow(oldMSTime)); + } + } + + Log.outInfo(LogFilter.ServerLoading, "Loading Game Event GO Quest Data..."); + { + uint oldMSTime = Time.GetMSTime(); + + // 0 1 2 + SQLResult result = DB.World.Query("SELECT id, quest, eventEntry FROM game_event_gameobject_quest"); + + if (result.IsEmpty()) + Log.outInfo(LogFilter.ServerLoading, "Loaded 0 go quests additions in game events. DB table `game_event_gameobject_quest` is empty."); + else + { + uint count = 0; + do + { + uint id = result.Read(0); + uint quest = result.Read(1); + ushort event_id = result.Read(2); + + if (event_id >= mGameEventGameObjectQuests.Length) + { + Log.outError(LogFilter.Sql, "`game_event_gameobject_quest` game event id ({0}) not exist in `game_event`", event_id); + continue; + } + + mGameEventGameObjectQuests[event_id].Add(Tuple.Create(id, quest)); + + ++count; + } + while (result.NextRow()); + + Log.outInfo(LogFilter.ServerLoading, "Loaded {0} quests additions in game events in {1} ms", count, Time.GetMSTimeDiffToNow(oldMSTime)); + } + } + + Log.outInfo(LogFilter.ServerLoading, "Loading Game Event Quest Condition Data..."); + { + uint oldMSTime = Time.GetMSTime(); + + // 0 1 2 3 + SQLResult result = DB.World.Query("SELECT quest, eventEntry, condition_id, num FROM game_event_quest_condition"); + + if (result.IsEmpty()) + Log.outInfo(LogFilter.ServerLoading, "Loaded 0 quest event conditions in game events. DB table `game_event_quest_condition` is empty."); + else + { + uint count = 0; + do + { + uint quest = result.Read(0); + ushort event_id = result.Read(1); + uint condition = result.Read(2); + float num = result.Read(3); + + if (event_id >= mGameEvent.Length) + { + Log.outError(LogFilter.Sql, "`game_event_quest_condition` game event id ({0}) is out of range compared to max event id in `game_event`", event_id); + continue; + } + + if (!mQuestToEventConditions.ContainsKey(quest)) + mQuestToEventConditions[quest] = new GameEventQuestToEventConditionNum(); + + mQuestToEventConditions[quest].event_id = event_id; + mQuestToEventConditions[quest].condition = condition; + mQuestToEventConditions[quest].num = num; + + ++count; + } + while (result.NextRow()); + + Log.outInfo(LogFilter.ServerLoading, "Loaded {0} quest event conditions in game events in {1} ms", count, Time.GetMSTimeDiffToNow(oldMSTime)); + } + } + + Log.outInfo(LogFilter.ServerLoading, "Loading Game Event Condition Data..."); + { + uint oldMSTime = Time.GetMSTime(); + + // 0 1 2 3 4 + SQLResult result = DB.World.Query("SELECT eventEntry, condition_id, req_num, max_world_state_field, done_world_state_field FROM game_event_condition"); + + if (result.IsEmpty()) + Log.outInfo(LogFilter.ServerLoading, "Loaded 0 conditions in game events. DB table `game_event_condition` is empty."); + else + { + uint count = 0; + do + { + ushort event_id = result.Read(0); + uint condition = result.Read(1); + + if (event_id >= mGameEvent.Length) + { + Log.outError(LogFilter.Sql, "`game_event_condition` game event id ({0}) is out of range compared to max event id in `game_event`", event_id); + continue; + } + + mGameEvent[event_id].conditions[condition].reqNum = result.Read(2); + mGameEvent[event_id].conditions[condition].done = 0; + mGameEvent[event_id].conditions[condition].max_world_state = result.Read(3); + mGameEvent[event_id].conditions[condition].done_world_state = result.Read(4); + + ++count; + } + while (result.NextRow()); + + Log.outInfo(LogFilter.ServerLoading, "Loaded {0} conditions in game events in {1} ms", count, Time.GetMSTimeDiffToNow(oldMSTime)); + } + } + + Log.outInfo(LogFilter.ServerLoading, "Loading Game Event Condition Save Data..."); + { + uint oldMSTime = Time.GetMSTime(); + + // 0 1 2 + SQLResult result = DB.Characters.Query("SELECT eventEntry, condition_id, done FROM game_event_condition_save"); + + if (result.IsEmpty()) + Log.outInfo(LogFilter.ServerLoading, "Loaded 0 condition saves in game events. DB table `game_event_condition_save` is empty."); + else + { + uint count = 0; + do + { + ushort event_id = result.Read(0); + uint condition = result.Read(1); + + if (event_id >= mGameEvent.Length) + { + Log.outError(LogFilter.Sql, "`game_event_condition_save` game event id ({0}) is out of range compared to max event id in `game_event`", event_id); + continue; + } + + if (mGameEvent[event_id].conditions.ContainsKey(condition)) + { + mGameEvent[event_id].conditions[condition].done = result.Read(2); + } + else + { + Log.outError(LogFilter.Sql, "game_event_condition_save contains not present condition evt id {0} cond id {1}", event_id, condition); + continue; + } + + ++count; + } + while (result.NextRow()); + + Log.outInfo(LogFilter.ServerLoading, "Loaded {0} condition saves in game events in {1} ms", count, Time.GetMSTimeDiffToNow(oldMSTime)); + } + } + + Log.outInfo(LogFilter.ServerLoading, "Loading Game Event NPCflag Data..."); + { + uint oldMSTime = Time.GetMSTime(); + + // 0 1 2 + SQLResult result = DB.World.Query("SELECT guid, eventEntry, npcflag FROM game_event_npcflag"); + + if (result.IsEmpty()) + Log.outInfo(LogFilter.ServerLoading, "Loaded 0 npcflags in game events. DB table `game_event_npcflag` is empty."); + else + { + uint count = 0; + do + { + ulong guid = result.Read(0); + ushort event_id = result.Read(1); + ulong npcflag = result.Read(2); + + if (event_id >= mGameEvent.Length) + { + Log.outError(LogFilter.Sql, "`game_event_npcflag` game event id ({0}) is out of range compared to max event id in `game_event`", event_id); + continue; + } + + mGameEventNPCFlags[event_id].Add(Tuple.Create(guid, npcflag)); + + ++count; + } + while (result.NextRow()); + + Log.outInfo(LogFilter.ServerLoading, "Loaded {0} npcflags in game events in {1} ms", count, Time.GetMSTimeDiffToNow(oldMSTime)); + } + } + + Log.outInfo(LogFilter.ServerLoading, "Loading Game Event Seasonal Quest Relations..."); + { + uint oldMSTime = Time.GetMSTime(); + + // 0 1 + SQLResult result = DB.World.Query("SELECT questId, eventEntry FROM game_event_seasonal_questrelation"); + + if (result.IsEmpty()) + Log.outInfo(LogFilter.ServerLoading, "Loaded 0 seasonal quests additions in game events. DB table `game_event_seasonal_questrelation` is empty."); + else + { + uint count = 0; + do + { + uint questId = result.Read(0); + ushort eventEntry = result.Read(1); /// @todo Change to byte + + if (Global.ObjectMgr.GetQuestTemplate(questId) == null) + { + Log.outError(LogFilter.Sql, "`game_event_seasonal_questrelation` quest id ({0}) does not exist in `quest_template`", questId); + continue; + } + + if (eventEntry >= mGameEvent.Length) + { + Log.outError(LogFilter.Sql, "`game_event_seasonal_questrelation` event id ({0}) not exist in `game_event`", eventEntry); + continue; + } + + _questToEventLinks[questId] = eventEntry; + ++count; + } + while (result.NextRow()); + + Log.outInfo(LogFilter.ServerLoading, "Loaded {0} quests additions in game events in {1} ms", count, Time.GetMSTimeDiffToNow(oldMSTime)); + } + } + + Log.outInfo(LogFilter.ServerLoading, "Loading Game Event Vendor Additions Data..."); + { + uint oldMSTime = Time.GetMSTime(); + + // 0 1 2 3 4 5 6 + SQLResult result = DB.World.Query("SELECT eventEntry, guid, item, maxcount, incrtime, ExtendedCost, type FROM game_event_npc_vendor ORDER BY guid, slot ASC"); + + if (result.IsEmpty()) + Log.outInfo(LogFilter.ServerLoading, "Loaded 0 vendor additions in game events. DB table `game_event_npc_vendor` is empty."); + else + { + uint count = 0; + do + { + byte event_id = result.Read(0); + + if (event_id >= mGameEventVendors.Length) + { + Log.outError(LogFilter.Sql, "`game_event_npc_vendor` game event id ({0}) not exist in `game_event`", event_id); + continue; + } + + NPCVendorEntry newEntry = new NPCVendorEntry(); + ulong guid = result.Read(1); + newEntry.item = result.Read(2); + newEntry.maxcount = result.Read(3); + newEntry.incrtime = result.Read(4); + newEntry.ExtendedCost = result.Read(5); + newEntry.Type = result.Read(6); + // get the event npc flag for checking if the npc will be vendor during the event or not + ulong event_npc_flag = 0; + var flist = mGameEventNPCFlags[event_id]; + foreach (var pair in flist) + { + if (pair.Item1 == guid) + { + event_npc_flag = pair.Item2; + break; + } + } + // get creature entry + newEntry.entry = 0; + CreatureData data = Global.ObjectMgr.GetCreatureData(guid); + if (data != null) + newEntry.entry = data.id; + + // check validity with event's npcflag + if (!Global.ObjectMgr.IsVendorItemValid(newEntry.entry, newEntry.item, newEntry.maxcount, newEntry.incrtime, newEntry.ExtendedCost, (ItemVendorType)newEntry.Type, null, null, event_npc_flag)) + continue; + + mGameEventVendors[event_id].Add(newEntry); + + ++count; + } + while (result.NextRow()); + + Log.outInfo(LogFilter.ServerLoading, "Loaded {0} vendor additions in game events in {1} ms", count, Time.GetMSTimeDiffToNow(oldMSTime)); + } + } + + Log.outInfo(LogFilter.ServerLoading, "Loading Game Event BattlegroundData..."); + { + uint oldMSTime = Time.GetMSTime(); + + // 0 1 + SQLResult result = DB.World.Query("SELECT eventEntry, bgflag FROM game_event_Battleground_holiday"); + + if (result.IsEmpty()) + Log.outInfo(LogFilter.ServerLoading, "Loaded 0 Battlegroundholidays in game events. DB table `game_event_Battleground_holiday` is empty."); + else + { + uint count = 0; + do + { + ushort event_id = result.Read(0); + + if (event_id >= mGameEvent.Length) + { + Log.outError(LogFilter.Sql, "`game_event_Battleground_holiday` game event id ({0}) not exist in `game_event`", event_id); + continue; + } + + mGameEventBattlegroundHolidays[event_id] = result.Read(1); + + ++count; + } + while (result.NextRow()); + + Log.outInfo(LogFilter.ServerLoading, "Loaded {0} Battlegroundholidays in game events in {1} ms", count, Time.GetMSTimeDiffToNow(oldMSTime)); + } + } + + Log.outInfo(LogFilter.ServerLoading, "Loading Game Event Pool Data..."); + { + uint oldMSTime = Time.GetMSTime(); + + // 0 1 + SQLResult result = DB.World.Query("SELECT pool_template.entry, game_event_pool.eventEntry FROM pool_template" + + " JOIN game_event_pool ON pool_template.entry = game_event_pool.pool_entry"); + + if (result.IsEmpty()) + Log.outInfo(LogFilter.ServerLoading, "Loaded 0 pools for game events. DB table `game_event_pool` is empty."); + else + { + uint count = 0; + do + { + uint entry = result.Read(0); + short event_id = result.Read(1); + int internal_event_id = mGameEvent.Length + event_id - 1; + + if (internal_event_id < 0 || internal_event_id >= mGameEventPoolIds.Length) + { + Log.outError(LogFilter.Sql, "`game_event_pool` game event id ({0}) not exist in `game_event`", event_id); + continue; + } + + if (!Global.PoolMgr.CheckPool(entry)) + { + Log.outError(LogFilter.Sql, "Pool Id ({0}) has all creatures or gameobjects with explicit chance sum <>100 and no equal chance defined. The pool system cannot pick one to spawn.", entry); + continue; + } + + + mGameEventPoolIds[internal_event_id].Add(entry); + + ++count; + } + while (result.NextRow()); + + Log.outInfo(LogFilter.ServerLoading, "Loaded {0} pools for game events in {1} ms", count, Time.GetMSTimeDiffToNow(oldMSTime)); + } + } + } + + public ulong GetNPCFlag(Creature cr) + { + ulong mask = 0; + ulong guid = cr.GetSpawnId(); + + foreach (var id in m_ActiveEvents) + { + foreach (var pair in mGameEventNPCFlags[id]) + if (pair.Item1 == guid) + mask |= pair.Item2; + } + + return mask; + } + + public void Initialize() + { + SQLResult result = DB.World.Query("SELECT MAX(eventEntry) FROM game_event"); + if (!result.IsEmpty()) + { + + int maxEventId = result.Read(0); + + // Id starts with 1 and array with 0, thus increment + maxEventId++; + + mGameEvent = new GameEventData[maxEventId]; + mGameEventCreatureGuids = new List[maxEventId * 2 - 1]; + mGameEventGameobjectGuids = new List[maxEventId * 2 - 1]; + mGameEventPoolIds = new List[maxEventId * 2 - 1]; + for (var i = 0; i < maxEventId * 2 - 1; ++i) + { + mGameEventCreatureGuids[i] = new List(); + mGameEventGameobjectGuids[i] = new List(); + mGameEventPoolIds[i] = new List(); + } + + mGameEventCreatureQuests = new List>[maxEventId]; + mGameEventGameObjectQuests = new List>[maxEventId]; + mGameEventVendors = new List[maxEventId]; + mGameEventBattlegroundHolidays = new uint[maxEventId]; + mGameEventNPCFlags = new List>[maxEventId]; + mGameEventModelEquip = new List>[maxEventId]; + for (var i = 0; i < maxEventId; ++i) + { + mGameEvent[i] = new GameEventData(); + mGameEventCreatureQuests[i] = new List>(); + mGameEventGameObjectQuests[i] = new List>(); + mGameEventVendors[i] = new List(); + mGameEventNPCFlags[i] = new List>(); + mGameEventModelEquip[i] = new List>(); + } + } + } + + public uint StartSystem() // return the next event delay in ms + { + m_ActiveEvents.Clear(); + uint delay = Update(); + isSystemInit = true; + return delay; + } + + public void StartArenaSeason() + { + int season = WorldConfig.GetIntValue(WorldCfg.ArenaSeasonId); + SQLResult result = DB.World.Query("SELECT eventEntry FROM game_event_arena_seasons WHERE season = '{0}'", season); + + if (result.IsEmpty()) + { + Log.outError(LogFilter.Gameevent, "ArenaSeason ({0}) must be an existant Arena Season", season); + return; + } + + ushort eventId = result.Read(0); + + if (eventId >= mGameEvent.Length) + { + Log.outError(LogFilter.Gameevent, "EventEntry {0} for ArenaSeason ({1}) does not exists", eventId, season); + return; + } + + StartEvent(eventId, true); + Log.outInfo(LogFilter.Gameevent, "Arena Season {0} started...", season); + + } + + public uint Update() // return the next event delay in ms + { + long currenttime = Time.UnixTime; + uint nextEventDelay = Time.Day; // 1 day + uint calcDelay; + List activate = new List(); + List deactivate = new List(); + for (ushort id = 1; id < mGameEvent.Length; ++id) + { + // must do the activating first, and after that the deactivating + // so first queue it + if (CheckOneGameEvent(id)) + { + // if the world event is in NEXTPHASE state, and the time has passed to finish this event, then do so + if (mGameEvent[id].state == GameEventState.WorldNextPhase && mGameEvent[id].nextstart <= currenttime) + { + // set this event to finished, null the nextstart time + mGameEvent[id].state = GameEventState.WorldFinished; + mGameEvent[id].nextstart = 0; + // save the state of this gameevent + SaveWorldEventStateToDB(id); + // queue for deactivation + if (IsActiveEvent(id)) + deactivate.Add(id); + // go to next event, this no longer needs an event update timer + continue; + } + else if (mGameEvent[id].state == GameEventState.WorldConditions && CheckOneGameEventConditions(id)) + // changed, save to DB the gameevent state, will be updated in next update cycle + SaveWorldEventStateToDB(id); + + Log.outDebug(LogFilter.Misc, "GameEvent {0} is active", id); + // queue for activation + if (!IsActiveEvent(id)) + activate.Add(id); + } + else + { + // If event is inactive, periodically clean up its worldstate + Global.WorldMgr.setWorldState(id, 0); + Log.outDebug(LogFilter.Misc, "GameEvent {0} is not active", id); + if (IsActiveEvent(id)) + deactivate.Add(id); + else + { + if (!isSystemInit) + { + short event_nid = (short)(-1 * id); + // spawn all negative ones for this event + GameEventSpawn(event_nid); + } + } + } + calcDelay = NextCheck(id); + if (calcDelay < nextEventDelay) + nextEventDelay = calcDelay; + } + // now activate the queue + // a now activated event can contain a spawn of a to-be-deactivated one + // following the activate - deactivate order, deactivating the first event later will leave the spawn in (wont disappear then reappear clientside) + foreach (var eventId in activate) + { + // start the event + // returns true the started event completed + // in that case, initiate next update in 1 second + if (StartEvent(eventId)) + nextEventDelay = 0; + } + + foreach (var eventId in deactivate) + StopEvent(eventId); + + Log.outInfo(LogFilter.Gameevent, "Next game event check in {0} seconds.", nextEventDelay + 1); + return (nextEventDelay + 1) * Time.InMilliseconds; // Add 1 second to be sure event has started/stopped at next call + } + + void UnApplyEvent(ushort event_id) + { + Log.outInfo(LogFilter.Gameevent, "GameEvent {0} \"{1}\" removed.", event_id, mGameEvent[event_id].description); + //! Run SAI scripts with SMART_EVENT_GAME_EVENT_END + RunSmartAIScripts(event_id, false); + // un-spawn positive event tagged objects + GameEventUnspawn((short)event_id); + // spawn negative event tagget objects + short event_nid = (short)(-1 * event_id); + GameEventSpawn(event_nid); + // restore equipment or model + ChangeEquipOrModel((short)event_id, false); + // Remove quests that are events only to non event npc + UpdateEventQuests(event_id, false); + UpdateWorldStates(event_id, false); + // update npcflags in this event + UpdateEventNPCFlags(event_id); + // remove vendor items + UpdateEventNPCVendor(event_id, false); + // update bg holiday + UpdateBattlegroundSettings(); + } + + void ApplyNewEvent(ushort event_id) + { + byte announce = mGameEvent[event_id].announce; + if (announce == 1)// || (announce == 2 && WorldConfigEventAnnounce)) + Global.WorldMgr.SendWorldText(CypherStrings.Eventmessage, mGameEvent[event_id].description); + + Log.outInfo(LogFilter.Gameevent, "GameEvent {0} \"{1}\" started.", event_id, mGameEvent[event_id].description); + + //! Run SAI scripts with SMART_EVENT_GAME_EVENT_END + RunSmartAIScripts(event_id, true); + + // spawn positive event tagget objects + GameEventSpawn((short)event_id); + // un-spawn negative event tagged objects + short event_nid = (short)(-1 * event_id); + GameEventUnspawn(event_nid); + // Change equipement or model + ChangeEquipOrModel((short)event_id, true); + // Add quests that are events only to non event npc + UpdateEventQuests(event_id, true); + UpdateWorldStates(event_id, true); + // update npcflags in this event + UpdateEventNPCFlags(event_id); + // add vendor items + UpdateEventNPCVendor(event_id, true); + // update bg holiday + UpdateBattlegroundSettings(); + // If event's worldstate is 0, it means the event hasn't been started yet. In that case, reset seasonal quests. + // When event ends (if it expires or if it's stopped via commands) worldstate will be set to 0 again, ready for another seasonal quest reset. + if (Global.WorldMgr.getWorldState(event_id) == 0) + Global.WorldMgr.ResetEventSeasonalQuests(event_id); + } + + void UpdateEventNPCFlags(ushort event_id) + { + MultiMap creaturesByMap = new MultiMap(); + + // go through the creatures whose npcflags are changed in the event + foreach (var pair in mGameEventNPCFlags[event_id]) + { + // get the creature data from the low guid to get the entry, to be able to find out the whole guid + CreatureData data = Global.ObjectMgr.GetCreatureData(pair.Item1); + if (data != null) + creaturesByMap.Add(data.mapid, pair.Item1); + } + + foreach (var key in creaturesByMap.Keys) + { + Global.MapMgr.DoForAllMapsWithMapId(key, (Map map) => + { + foreach (var spawnId in creaturesByMap[key]) + { + var creatureBounds = map.GetCreatureBySpawnIdStore().LookupByKey(spawnId); + foreach (var creature in creatureBounds) + { + ulong npcflag = GetNPCFlag(creature); + CreatureTemplate creatureTemplate = creature.GetCreatureTemplate(); + if (creatureTemplate != null) + npcflag |= (ulong)creatureTemplate.Npcflag; + + creature.SetUInt64Value(UnitFields.NpcFlags, npcflag); + // reset gossip options, since the flag change might have added / removed some + //cr->ResetGossipOptions(); + } + } + }); + } + } + + void UpdateBattlegroundSettings() + { + uint mask = 0; + foreach (var eventId in m_ActiveEvents) + mask |= mGameEventBattlegroundHolidays[eventId]; + + Global.BattlegroundMgr.SetHolidayWeekends(mask); + } + + void UpdateEventNPCVendor(ushort eventId, bool activate) + { + foreach (var npcEventVendor in mGameEventVendors[eventId]) + { + if (activate) + Global.ObjectMgr.AddVendorItem(npcEventVendor.entry, npcEventVendor.item, npcEventVendor.maxcount, npcEventVendor.incrtime, npcEventVendor.ExtendedCost, (ItemVendorType)npcEventVendor.Type, false); + else + Global.ObjectMgr.RemoveVendorItem(npcEventVendor.entry, npcEventVendor.item, (ItemVendorType)npcEventVendor.Type, false); + } + } + + void GameEventSpawn(short event_id) + { + int internal_event_id = mGameEvent.Length + event_id - 1; + + if (internal_event_id < 0 || internal_event_id >= mGameEventCreatureGuids.Length) + { + Log.outError(LogFilter.Gameevent, "GameEventMgr.GameEventSpawn attempt access to out of range mGameEventCreatureGuids element {0} (size: {1})", + internal_event_id, mGameEventCreatureGuids.Length); + return; + } + + foreach (var guid in mGameEventCreatureGuids[internal_event_id]) + { + // Add to correct cell + CreatureData data = Global.ObjectMgr.GetCreatureData(guid); + if (data != null) + { + Global.ObjectMgr.AddCreatureToGrid(guid, data); + + // Spawn if necessary (loaded grids only) + Map map = Global.MapMgr.CreateBaseMap(data.mapid); + // We use spawn coords to spawn + if (!map.Instanceable() && map.IsGridLoaded(data.posX, data.posY)) + { + Creature creature = new Creature(); + Log.outDebug(LogFilter.Misc, "Spawning creature {0}", guid.ToString()); + creature.LoadCreatureFromDB(guid, map); + } + } + } + + if (internal_event_id < 0 || internal_event_id >= mGameEventGameobjectGuids.Length) + { + Log.outError(LogFilter.Gameevent, "GameEventMgr.GameEventSpawn attempt access to out of range mGameEventGameobjectGuids element {0} (size: {1})", + internal_event_id, mGameEventGameobjectGuids.Length); + return; + } + + foreach (var guid in mGameEventGameobjectGuids[internal_event_id]) + { + // Add to correct cell + GameObjectData data = Global.ObjectMgr.GetGOData(guid); + if (data != null) + { + Global.ObjectMgr.AddGameObjectToGrid(guid, data); + // Spawn if necessary (loaded grids only) + // this base map checked as non-instanced and then only existed + Map map = Global.MapMgr.CreateBaseMap(data.mapid); + // We use current coords to unspawn, not spawn coords since creature can have changed grid + if (!map.Instanceable() && map.IsGridLoaded(data.posX, data.posY)) + { + GameObject pGameobject = new GameObject(); + Log.outDebug(LogFilter.Misc, "Spawning gameobject {0}", guid.ToString()); + /// @todo find out when it is add to map + if (pGameobject.LoadGameObjectFromDB(guid, map, false)) + { + if (pGameobject.isSpawnedByDefault()) + map.AddToMap(pGameobject); + } + } + } + } + + if (internal_event_id < 0 || internal_event_id >= mGameEventPoolIds.Length) + { + Log.outError(LogFilter.Gameevent, "GameEventMgr.GameEventSpawn attempt access to out of range mGameEventPoolIds element {0} (size: {1})", + internal_event_id, mGameEventPoolIds.Length); + return; + } + + foreach (var id in mGameEventPoolIds[internal_event_id]) + Global.PoolMgr.SpawnPool(id); + } + + void GameEventUnspawn(short event_id) + { + int internal_event_id = mGameEvent.Length + event_id - 1; + + if (internal_event_id < 0 || internal_event_id >= mGameEventCreatureGuids.Length) + { + Log.outError(LogFilter.Gameevent, "GameEventMgr.GameEventUnspawn attempt access to out of range mGameEventCreatureGuids element {0} (size: {1})", + internal_event_id, mGameEventCreatureGuids.Length); + return; + } + + foreach (var guid in mGameEventCreatureGuids[internal_event_id]) + { + // check if it's needed by another event, if so, don't remove + if (event_id > 0 && hasCreatureActiveEventExcept(guid, (ushort)event_id)) + continue; + + // Remove the creature from grid + CreatureData data = Global.ObjectMgr.GetCreatureData(guid); + if (data != null) + { + Global.ObjectMgr.RemoveCreatureFromGrid(guid, data); + + Global.MapMgr.DoForAllMapsWithMapId(data.mapid, map => + { + var creatureBounds = map.GetCreatureBySpawnIdStore().LookupByKey(guid); + foreach (var creature in creatureBounds) + creature.AddObjectToRemoveList(); + }); + } + } + + if (internal_event_id < 0 || internal_event_id >= mGameEventGameobjectGuids.Length) + { + Log.outError(LogFilter.Gameevent, "GameEventMgr.GameEventUnspawn attempt access to out of range mGameEventGameobjectGuids element {0} (size: {1})", + internal_event_id, mGameEventGameobjectGuids.Length); + return; + } + + foreach (var guid in mGameEventGameobjectGuids[internal_event_id]) + { + // check if it's needed by another event, if so, don't remove + if (event_id > 0 && hasGameObjectActiveEventExcept(guid, (ushort)event_id)) + continue; + // Remove the gameobject from grid + GameObjectData data = Global.ObjectMgr.GetGOData(guid); + if (data != null) + { + Global.ObjectMgr.RemoveGameObjectFromGrid(guid, data); + + Global.MapMgr.DoForAllMapsWithMapId(data.mapid, map => + { + var gameobjectBounds = map.GetGameObjectBySpawnIdStore().LookupByKey(guid); + foreach (var go in gameobjectBounds) + go.AddObjectToRemoveList(); + + }); + } + } + + if (internal_event_id < 0 || internal_event_id >= mGameEventPoolIds.Length) + { + Log.outError(LogFilter.Gameevent, "GameEventMgr.GameEventUnspawn attempt access to out of range mGameEventPoolIds element {0} (size: {1})", internal_event_id, mGameEventPoolIds.Length); + return; + } + + foreach (var poolId in mGameEventPoolIds[internal_event_id]) + Global.PoolMgr.DespawnPool(poolId); + } + + void ChangeEquipOrModel(short event_id, bool activate) + { + foreach (var tuple in mGameEventModelEquip[event_id]) + { + // Remove the creature from grid + CreatureData data = Global.ObjectMgr.GetCreatureData(tuple.Item1); + if (data == null) + continue; + + // Update if spawned + Global.MapMgr.DoForAllMapsWithMapId(data.mapid, map => + { + var creatureBounds = map.GetCreatureBySpawnIdStore().LookupByKey(tuple.Item1); + foreach (var creature in creatureBounds) + { + if (activate) + { + tuple.Item2.equipement_id_prev = creature.GetCurrentEquipmentId(); + tuple.Item2.modelid_prev = creature.GetDisplayId(); + creature.LoadEquipment(tuple.Item2.equipment_id, true); + if (tuple.Item2.modelid > 0 && tuple.Item2.modelid_prev != tuple.Item2.modelid && + Global.ObjectMgr.GetCreatureModelInfo(tuple.Item2.modelid) != null) + { + creature.SetDisplayId(tuple.Item2.modelid); + creature.SetNativeDisplayId(tuple.Item2.modelid); + } + } + else + { + creature.LoadEquipment(tuple.Item2.equipement_id_prev, true); + if (tuple.Item2.modelid_prev > 0 && tuple.Item2.modelid_prev != tuple.Item2.modelid && + Global.ObjectMgr.GetCreatureModelInfo(tuple.Item2.modelid_prev) != null) + { + creature.SetDisplayId(tuple.Item2.modelid_prev); + creature.SetNativeDisplayId(tuple.Item2.modelid_prev); + } + } + } + }); + + // now last step: put in data + CreatureData data2 = Global.ObjectMgr.NewOrExistCreatureData(tuple.Item1); + if (activate) + { + tuple.Item2.modelid_prev = data2.displayid; + tuple.Item2.equipement_id_prev = (byte)data2.equipmentId; + data2.displayid = tuple.Item2.modelid; + data2.equipmentId = tuple.Item2.equipment_id; + } + else + { + data2.displayid = tuple.Item2.modelid_prev; + data2.equipmentId = tuple.Item2.equipement_id_prev; + } + } + } + + bool hasCreatureQuestActiveEventExcept(uint questId, ushort eventId) + { + foreach (var activeEventId in m_ActiveEvents) + { + if (activeEventId != eventId) + foreach (var pair in mGameEventCreatureQuests[activeEventId]) + if (pair.Item2 == questId) + return true; + } + return false; + } + + bool hasGameObjectQuestActiveEventExcept(uint questId, ushort eventId) + { + foreach (var activeEventId in m_ActiveEvents) + { + if (activeEventId != eventId) + foreach (var pair in mGameEventGameObjectQuests[activeEventId]) + if (pair.Item2 == questId) + return true; + } + return false; + } + bool hasCreatureActiveEventExcept(ulong creatureId, ushort eventId) + { + foreach (var activeEventId in m_ActiveEvents) + { + if (activeEventId != eventId) + { + int internal_event_id = mGameEvent.Length + activeEventId - 1; + foreach (var id in mGameEventCreatureGuids[internal_event_id]) + if (id == creatureId) + return true; + } + } + return false; + } + bool hasGameObjectActiveEventExcept(ulong goId, ushort eventId) + { + foreach (var activeEventId in m_ActiveEvents) + { + if (activeEventId != eventId) + { + int internal_event_id = mGameEvent.Length + activeEventId - 1; + foreach (var id in mGameEventGameobjectGuids[internal_event_id]) + if (id == goId) + return true; + } + } + return false; + } + + void UpdateEventQuests(ushort eventId, bool activate) + { + foreach (var pair in mGameEventCreatureQuests[eventId]) + { + var CreatureQuestMap = Global.ObjectMgr.GetCreatureQuestRelationMap(); + if (activate) // Add the pair(id, quest) to the multimap + CreatureQuestMap.Add(pair.Item1, pair.Item2); + else + { + if (!hasCreatureQuestActiveEventExcept(pair.Item2, eventId)) + { + // Remove the pair(id, quest) from the multimap + CreatureQuestMap.Remove(pair.Item1, pair.Item2); + } + } + } + foreach (var pair in mGameEventGameObjectQuests[eventId]) + { + var GameObjectQuestMap = Global.ObjectMgr.GetGOQuestRelationMap(); + if (activate) // Add the pair(id, quest) to the multimap + GameObjectQuestMap.Add(pair.Item1, pair.Item2); + else + { + if (!hasGameObjectQuestActiveEventExcept(pair.Item2, eventId)) + { + // Remove the pair(id, quest) from the multimap + GameObjectQuestMap.Remove(pair.Item1, pair.Item2); + } + } + } + } + + void UpdateWorldStates(ushort event_id, bool Activate) + { + GameEventData Event = mGameEvent[event_id]; + if (Event.holiday_id != HolidayIds.None) + { + BattlegroundTypeId bgTypeId = Global.BattlegroundMgr.WeekendHolidayIdToBGType(Event.holiday_id); + if (bgTypeId != BattlegroundTypeId.None) + { + BattlemasterListRecord bl = CliDB.BattlemasterListStorage.LookupByKey(bgTypeId); + if (bl != null && bl.HolidayWorldState != 0) + { + UpdateWorldState worldstate = new UpdateWorldState(); + worldstate.VariableID = bl.HolidayWorldState; + worldstate.Value = Activate ? 1 : 0; + //worldstate.Hidden = false; + Global.WorldMgr.SendGlobalMessage(worldstate); + } + } + } + } + + public void HandleQuestComplete(uint quest_id) + { + // translate the quest to event and condition + var questToEvent = mQuestToEventConditions.LookupByKey(quest_id); + // quest is registered + if (questToEvent != null) + { + ushort event_id = questToEvent.event_id; + uint condition = questToEvent.condition; + float num = questToEvent.num; + + // the event is not active, so return, don't increase condition finishes + if (!IsActiveEvent(event_id)) + return; + // not in correct phase, return + if (mGameEvent[event_id].state != GameEventState.WorldConditions) + return; + var eventFinishCond = mGameEvent[event_id].conditions.LookupByKey(condition); + // condition is registered + if (eventFinishCond != null) + { + // increase the done count, only if less then the req + if (eventFinishCond.done < eventFinishCond.reqNum) + { + eventFinishCond.done += num; + // check max limit + if (eventFinishCond.done > eventFinishCond.reqNum) + eventFinishCond.done = eventFinishCond.reqNum; + // save the change to db + SQLTransaction trans = new SQLTransaction(); + + PreparedStatement stmt = DB.Characters.GetPreparedStatement(CharStatements.DEL_GAME_EVENT_CONDITION_SAVE); + stmt.AddValue(0, event_id); + stmt.AddValue(1, condition); + trans.Append(stmt); + + stmt = DB.Characters.GetPreparedStatement(CharStatements.INS_GAME_EVENT_CONDITION_SAVE); + stmt.AddValue(0, event_id); + stmt.AddValue(1, condition); + stmt.AddValue(2, eventFinishCond.done); + trans.Append(stmt); + DB.Characters.CommitTransaction(trans); + // check if all conditions are met, if so, update the event state + if (CheckOneGameEventConditions(event_id)) + { + // changed, save to DB the gameevent state + SaveWorldEventStateToDB(event_id); + // force update events to set timer + Global.WorldMgr.ForceGameEventUpdate(); + } + } + } + } + } + + bool CheckOneGameEventConditions(ushort event_id) + { + foreach (var pair in mGameEvent[event_id].conditions) + if (pair.Value.done < pair.Value.reqNum) + // return false if a condition doesn't match + return false; + // set the phase + mGameEvent[event_id].state = GameEventState.WorldNextPhase; + // set the followup events' start time + if (mGameEvent[event_id].nextstart == 0) + { + long currenttime = Time.UnixTime; + mGameEvent[event_id].nextstart = currenttime + mGameEvent[event_id].length * 60; + } + return true; + } + + void SaveWorldEventStateToDB(ushort event_id) + { + SQLTransaction trans = new SQLTransaction(); + + PreparedStatement stmt = DB.Characters.GetPreparedStatement(CharStatements.DEL_GAME_EVENT_SAVE); + stmt.AddValue(0, event_id); + trans.Append(stmt); + + stmt = DB.Characters.GetPreparedStatement(CharStatements.INS_GAME_EVENT_SAVE); + stmt.AddValue(0, event_id); + stmt.AddValue(1, mGameEvent[event_id].state); + stmt.AddValue(2, mGameEvent[event_id].nextstart != 0 ? mGameEvent[event_id].nextstart : 0); + trans.Append(stmt); + DB.Characters.CommitTransaction(trans); + } + + void SendWorldStateUpdate(Player player, ushort event_id) + { + foreach (var pair in mGameEvent[event_id].conditions) + { + if (pair.Value.done_world_state != 0) + player.SendUpdateWorldState(pair.Value.done_world_state, (uint)(pair.Value.done)); + if (pair.Value.max_world_state != 0) + player.SendUpdateWorldState(pair.Value.max_world_state, (uint)(pair.Value.reqNum)); + } + } + + void RunSmartAIScripts(ushort event_id, bool activate) + { + //! Iterate over every supported source type (creature and gameobject) + //! Not entirely sure how this will affect units in non-loaded grids. + Global.MapMgr.DoForAllMaps(map => + { + GameEventAIHookWorker worker = new GameEventAIHookWorker(event_id, activate); + var visitor = new Visitor(worker, GridMapTypeMask.None); + visitor.Visit(map.GetObjectsStore()); + }); + } + + public ushort GetEventIdForQuest(Quest quest) + { + if (quest == null) + return 0; + + return _questToEventLinks.LookupByKey(quest.Id); + } + + public bool IsHolidayActive(HolidayIds id) + { + if (id == HolidayIds.None) + return false; + + var events = GetEventMap(); + var activeEvents = GetActiveEventList(); + + foreach (var eventId in activeEvents) + if (events[eventId].holiday_id == id) + return true; + + return false; + } + + public bool IsEventActive(ushort event_id) + { + var ae = GetActiveEventList(); + return ae.Contains(event_id); + } + + + public List GetActiveEventList() { return m_ActiveEvents; } + public GameEventData[] GetEventMap() { return mGameEvent; } + public bool IsActiveEvent(ushort event_id) { return m_ActiveEvents.Contains(event_id); } + + void AddActiveEvent(ushort event_id) { m_ActiveEvents.Add(event_id); } + void RemoveActiveEvent(ushort event_id) { m_ActiveEvents.Remove(event_id); } + + + List>[] mGameEventCreatureQuests; + List>[] mGameEventGameObjectQuests; + List[] mGameEventVendors; + List>[] mGameEventModelEquip; + List[] mGameEventPoolIds; + GameEventData[] mGameEvent; + uint[] mGameEventBattlegroundHolidays; + Dictionary mQuestToEventConditions = new Dictionary(); + List>[] mGameEventNPCFlags; + List m_ActiveEvents = new List(); + Dictionary _questToEventLinks = new Dictionary(); + bool isSystemInit; + + public List[] mGameEventCreatureGuids; + public List[] mGameEventGameobjectGuids; + } + + public class GameEventFinishCondition + { + public float reqNum; // required number // use float, since some events use percent + public float done; // done number + public uint max_world_state; // max resource count world state update id + public uint done_world_state; // done resource count world state update id + } + + public class GameEventQuestToEventConditionNum + { + public ushort event_id; + public uint condition; + public float num; + } + + public class GameEventData + { + public GameEventData() + { + start = 1; + } + + public long start; // occurs after this time + public long end; // occurs before this time + public long nextstart; // after this time the follow-up events count this phase completed + public uint occurence; // time between end and start + public uint length; // length of the event (Time.Minutes) after finishing all conditions + public HolidayIds holiday_id; + public GameEventState state; // state of the game event, these are saved into the game_event table on change! + public Dictionary conditions = new Dictionary(); // conditions to finish + public List prerequisite_events = new List(); // events that must be completed before starting this event + public string description; + public byte announce; // if 0 dont announce, if 1 announce, if 2 take config value + + public bool isValid() { return length > 0 || state > GameEventState.Normal; } + } + + public class ModelEquip + { + public uint modelid; + public uint modelid_prev; + public byte equipment_id; + public byte equipement_id_prev; + } + + public struct NPCVendorEntry + { + public uint entry; // creature entry + public uint item; // item id + public int maxcount; // 0 for infinite + public uint incrtime; // time for restore items amount if maxcount != 0 + public uint ExtendedCost; + public byte Type; // 1 item, 2 currency + } + + class GameEventAIHookWorker : Notifier + { + public GameEventAIHookWorker(ushort eventId, bool activate) + { + _eventId = eventId; + _activate = activate; + } + + public override void Visit(ICollection objs) + { + foreach (var creature in objs) + if (creature.IsInWorld && creature.IsAIEnabled) + creature.GetAI().sOnGameEvent(_activate, _eventId); + } + public override void Visit(ICollection objs) + { + foreach (var gameobject in objs) + if (gameobject.IsInWorld) + gameobject.GetAI().OnGameEvent(_activate, _eventId); + } + + ushort _eventId; + bool _activate; + } + + public enum GameEventState + { + Normal = 0, // standard game events + WorldInactive = 1, // not yet started + WorldConditions = 2, // condition matching phase + WorldNextPhase = 3, // conditions are met, now 'length' timer to start next event + WorldFinished = 4, // next events are started, unapply this one + Internal = 5 // never handled in update + } +} diff --git a/Game/Game.csproj b/Game/Game.csproj new file mode 100644 index 000000000..b3f80c668 --- /dev/null +++ b/Game/Game.csproj @@ -0,0 +1,519 @@ + + + + + Debug + AnyCPU + {83EF8D5C-AFA1-4546-BCDD-6422D55B1515} + Library + Properties + Game + Game + v4.6.2 + 512 + + + + true + ..\Build\Debug\ + DEBUG;TRACE + full + x86 + prompt + true + false + false + + + ..\Build\Release\ + TRACE + true + pdbonly + x86 + prompt + true + + + true + ..\Build\x64\Debug\ + DEBUG;TRACE + full + x64 + prompt + true + false + MinimumRecommendedRules.ruleset + + + bin\x64\Release\ + TRACE + true + pdbonly + x64 + prompt + + + + ..\Libs\Google.Protobuf.dll + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + {82d442a9-18c0-4c59-8ec6-0dfe3e34d334} + Framework + + + + + + + + + + \ No newline at end of file diff --git a/Game/Garrisons/GarrisonManager.cs b/Game/Garrisons/GarrisonManager.cs new file mode 100644 index 000000000..1cee58371 --- /dev/null +++ b/Game/Garrisons/GarrisonManager.cs @@ -0,0 +1,502 @@ +/* + * Copyright (C) 2012-2017 CypherCore + * + * 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, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +using Framework.Constants; +using Framework.Database; +using Game.DataStorage; +using Game.Entities; +using System; +using System.Collections.Generic; +using System.Diagnostics.Contracts; +using System.Linq; + +namespace Game.Garrisons +{ + public class GarrisonManager : Singleton + { + GarrisonManager() { } + + public void Initialize() + { + foreach (GarrSiteLevelPlotInstRecord siteLevelPlotInst in CliDB.GarrSiteLevelPlotInstStorage.Values) + _garrisonPlotInstBySiteLevel.Add(siteLevelPlotInst.GarrSiteLevelID, siteLevelPlotInst); + + foreach (GameObjectsRecord gameObject in CliDB.GameObjectsStorage.Values) + { + if (gameObject.Type == GameObjectTypes.GarrisonPlot) + { + if (!_garrisonPlots.ContainsKey(gameObject.MapID)) + _garrisonPlots[gameObject.MapID] = new Dictionary(); + + _garrisonPlots[gameObject.MapID][(uint)gameObject.Data[0]] = gameObject; + } + } + + foreach (GarrPlotBuildingRecord plotBuilding in CliDB.GarrPlotBuildingStorage.Values) + _garrisonBuildingsByPlot.Add(plotBuilding.GarrPlotID, plotBuilding.GarrBuildingID); + + foreach (GarrBuildingPlotInstRecord buildingPlotInst in CliDB.GarrBuildingPlotInstStorage.Values) + _garrisonBuildingPlotInstances[MathFunctions.MakePair64(buildingPlotInst.GarrBuildingID, buildingPlotInst.GarrSiteLevelPlotInstID)] = buildingPlotInst.Id; + + foreach (GarrBuildingRecord building in CliDB.GarrBuildingStorage.Values) + _garrisonBuildingsByType.Add(building.Type, building.Id); + + for (var i = 0; i < 2; ++i) + _garrisonFollowerAbilities[i] = new Dictionary(); + + foreach (GarrFollowerXAbilityRecord followerAbility in CliDB.GarrFollowerXAbilityStorage.Values) + { + GarrAbilityRecord ability = CliDB.GarrAbilityStorage.LookupByKey(followerAbility.GarrAbilityID); + if (ability != null) + { + if (ability.FollowerTypeID != (uint)GarrisonFollowerType.Garrison) + continue; + + if (!ability.Flags.HasAnyFlag(GarrisonAbilityFlags.CannotRoll) && ability.Flags.HasAnyFlag(GarrisonAbilityFlags.Trait)) + _garrisonFollowerRandomTraits.Add(ability); + + if (followerAbility.FactionIndex < 2) + { + var dic = _garrisonFollowerAbilities[followerAbility.FactionIndex]; + + if (!dic.ContainsKey(followerAbility.GarrFollowerID)) + dic[followerAbility.GarrFollowerID] = new GarrAbilities(); + + if (ability.Flags.HasAnyFlag(GarrisonAbilityFlags.Trait)) + dic[followerAbility.GarrFollowerID].Traits.Add(ability); + else + dic[followerAbility.GarrFollowerID].Counters.Add(ability); + } + } + } + + InitializeDbIdSequences(); + LoadPlotFinalizeGOInfo(); + LoadFollowerClassSpecAbilities(); + } + + public GarrSiteLevelRecord GetGarrSiteLevelEntry(uint garrSiteId, uint level) + { + foreach (GarrSiteLevelRecord siteLevel in CliDB.GarrSiteLevelStorage.Values) + if (siteLevel.SiteID == garrSiteId && siteLevel.Level == level) + return siteLevel; + + return null; + } + + public List GetGarrPlotInstForSiteLevel(uint garrSiteLevelId) + { + return _garrisonPlotInstBySiteLevel.LookupByKey(garrSiteLevelId); + } + + public GameObjectsRecord GetPlotGameObject(uint mapId, uint garrPlotInstanceId) + { + var pair = _garrisonPlots.LookupByKey(mapId); + if (pair != null) + { + var gameobjectsRecord = pair.LookupByKey(garrPlotInstanceId); + if (gameobjectsRecord != null) + return gameobjectsRecord; + } + + return null; + } + + public bool IsPlotMatchingBuilding(uint garrPlotId, uint garrBuildingId) + { + var plotList = _garrisonBuildingsByPlot.LookupByKey(garrPlotId); + if (!plotList.Empty()) + return plotList.Contains(garrBuildingId); + + return false; + } + + public uint GetGarrBuildingPlotInst(uint garrBuildingId, uint garrSiteLevelPlotInstId) + { + return _garrisonBuildingPlotInstances.LookupByKey(MathFunctions.MakePair64(garrBuildingId, garrSiteLevelPlotInstId)); + } + + public uint GetPreviousLevelBuilding(uint buildingType, uint currentLevel) + { + var list = _garrisonBuildingsByType.LookupByKey(buildingType); + if (!list.Empty()) + { + foreach (uint buildingId in list) + if (CliDB.GarrBuildingStorage.LookupByKey(buildingId).Level == currentLevel - 1) + return buildingId; + } + + return 0; + } + + public FinalizeGarrisonPlotGOInfo GetPlotFinalizeGOInfo(uint garrPlotInstanceID) + { + return _finalizePlotGOInfo.LookupByKey(garrPlotInstanceID); + } + + public ulong GenerateFollowerDbId() + { + if (_followerDbIdGenerator >= ulong.MaxValue) + { + Log.outFatal(LogFilter.Server, "Garrison follower db id overflow! Can't continue, shutting down server. "); + Global.WorldMgr.StopNow(); + } + + return _followerDbIdGenerator++; + } + + // Counters, Traits + uint[,] AbilitiesForQuality = + { + { 0, 0 }, + { 1, 0 }, + { 1, 1 }, // Uncommon + { 1, 2 }, // Rare + { 2, 3 }, // Epic + { 2, 3 } // Legendary + }; + + //todo check this method, might be slow..... + public List RollFollowerAbilities(uint garrFollowerId, GarrFollowerRecord follower, uint quality, uint faction, bool initial) + { + Contract.Assert(faction< 2); + + bool hasForcedExclusiveTrait = false; + List result = new List(); + uint[] slots = { AbilitiesForQuality[quality, 0], AbilitiesForQuality[quality, 1] }; + + GarrAbilities garrAbilities = null; + var abilities = _garrisonFollowerAbilities[faction].LookupByKey(garrFollowerId); + if (abilities != null) + garrAbilities = abilities; + + List abilityList = new List(); + List forcedAbilities = new List(); + List traitList = new List(); + List forcedTraits = new List(); + if (garrAbilities != null) + { + foreach (GarrAbilityRecord ability in garrAbilities.Counters) + { + if (ability.Flags.HasAnyFlag(GarrisonAbilityFlags.HordeOnly) && faction != GarrisonFactionIndex.Horde) + continue; + else if (ability.Flags.HasAnyFlag(GarrisonAbilityFlags.AllianceOnly) && faction != GarrisonFactionIndex.Alliance) + continue; + + if (ability.Flags.HasAnyFlag(GarrisonAbilityFlags.CannotRemove)) + forcedAbilities.Add(ability); + else + abilityList.Add(ability); + } + + foreach (GarrAbilityRecord ability in garrAbilities.Traits) + { + if (ability.Flags.HasAnyFlag(GarrisonAbilityFlags.HordeOnly) && faction != GarrisonFactionIndex.Horde) + continue; + else if (ability.Flags.HasAnyFlag(GarrisonAbilityFlags.AllianceOnly) && faction != GarrisonFactionIndex.Alliance) + continue; + + if (ability.Flags.HasAnyFlag(GarrisonAbilityFlags.CannotRemove)) + forcedTraits.Add(ability); + else + traitList.Add(ability); + } + } + + abilityList.RandomResize((uint)Math.Max(0, slots[0] - forcedAbilities.Count)); + traitList.RandomResize((uint)Math.Max(0, slots[1] - forcedTraits.Count)); + + // Add abilities specified in GarrFollowerXAbility.db2 before generic classspec ones on follower creation + if (initial) + { + forcedAbilities.AddRange(abilityList); + forcedTraits.AddRange(traitList); + } + + forcedAbilities.Sort(); + abilityList.Sort(); + forcedTraits.Sort(); + traitList.Sort(); + + // check if we have a trait from exclusive category + foreach (GarrAbilityRecord ability in forcedTraits) + { + if (ability.Flags.HasAnyFlag(GarrisonAbilityFlags.Exclusive)) + { + hasForcedExclusiveTrait = true; + break; + } + } + + if (slots[0] > forcedAbilities.Count + abilityList.Count) + { + List classSpecAbilities = GetClassSpecAbilities(follower, faction); + List classSpecAbilitiesTemp = classSpecAbilities.Except(forcedAbilities).ToList(); + + abilityList = classSpecAbilitiesTemp.Union(abilityList).ToList(); + abilityList.RandomResize((uint)Math.Max(0, slots[0] - forcedAbilities.Count)); + } + + if (slots[1] > forcedTraits.Count + traitList.Count) + { + List genericTraitsTemp = new List(); + foreach (GarrAbilityRecord ability in _garrisonFollowerRandomTraits) + { + if (ability.Flags.HasAnyFlag(GarrisonAbilityFlags.HordeOnly) && faction != GarrisonFactionIndex.Horde) + continue; + else if (ability.Flags.HasAnyFlag(GarrisonAbilityFlags.AllianceOnly) && faction != GarrisonFactionIndex.Alliance) + continue; + + // forced exclusive trait exists, skip other ones entirely + if (hasForcedExclusiveTrait && ability.Flags.HasAnyFlag(GarrisonAbilityFlags.Exclusive)) + continue; + + genericTraitsTemp.Add(ability); + } + + List genericTraits = genericTraitsTemp.Except(forcedTraits).ToList(); + genericTraits.AddRange(genericTraits); + genericTraits.Sort((GarrAbilityRecord a1, GarrAbilityRecord a2) => + { + int e1 = (int)(a1.Flags & GarrisonAbilityFlags.Exclusive); + int e2 = (int)(a2.Flags & GarrisonAbilityFlags.Exclusive); + if (e1 != e2) + return e1.CompareTo(e2); + + return a1.Id.CompareTo(a2.Id); + }); + genericTraits = genericTraits.Distinct().ToList(); + + int firstExclusive = 0; + int total = genericTraits.Count; + for (var i = 0; i < total; ++i, ++firstExclusive) + if (genericTraits[i].Flags.HasAnyFlag(GarrisonAbilityFlags.Exclusive)) + break; + + while (traitList.Count < Math.Max(0, slots[1] - forcedTraits.Count) && total != 0) + { + var garrAbility = genericTraits[RandomHelper.IRand(0, total-- - 1)]; + if (garrAbility.Flags.HasAnyFlag(GarrisonAbilityFlags.Exclusive)) + total = firstExclusive; // selected exclusive trait - no other can be selected now + else + --firstExclusive; + + traitList.Add(garrAbility); + genericTraits.Remove(garrAbility); + } + } + + result.AddRange(forcedAbilities); + result.AddRange(abilityList); + result.AddRange(forcedTraits); + result.AddRange(traitList); + + return result; + } + + List GetClassSpecAbilities(GarrFollowerRecord follower, uint faction) + { + List abilities = new List(); + uint classSpecId; + switch (faction) + { + case GarrisonFactionIndex.Horde: + classSpecId = follower.HordeGarrClassSpecID; + break; + case GarrisonFactionIndex.Alliance: + classSpecId = follower.AllianceGarrClassSpecID; + break; + default: + return abilities; + } + + if (!CliDB.GarrClassSpecStorage.ContainsKey(classSpecId)) + return abilities; + + var garrAbility = _garrisonFollowerClassSpecAbilities.LookupByKey(classSpecId); + if (!garrAbility.Empty()) + abilities = garrAbility; + + return abilities; + } + + void InitializeDbIdSequences() + { + SQLResult result = DB.Characters.Query("SELECT MAX(dbId) FROM character_garrison_followers"); + if (!result.IsEmpty()) + _followerDbIdGenerator = result.Read(0) + 1; + } + + void LoadPlotFinalizeGOInfo() + { + // 0 1 2 3 4 5 6 + SQLResult result = DB.World.Query("SELECT garrPlotInstanceId, hordeGameObjectId, hordeX, hordeY, hordeZ, hordeO, hordeAnimKitId, " + + // 7 8 9 10 11 12 + "allianceGameObjectId, allianceX, allianceY, allianceZ, allianceO, allianceAnimKitId FROM garrison_plot_finalize_info"); + + if (result.IsEmpty()) + { + Log.outInfo(LogFilter.ServerLoading, "Loaded 0 garrison follower class spec abilities. DB table `garrison_plot_finalize_info` is empty."); + return; + } + + uint msTime = Time.GetMSTime(); + do + { + uint garrPlotInstanceId = result.Read(0); + uint hordeGameObjectId = result.Read(1); + uint allianceGameObjectId = result.Read(7); + ushort hordeAnimKitId = result.Read(6); + ushort allianceAnimKitId = result.Read(12); + + if (!CliDB.GarrPlotInstanceStorage.ContainsKey(garrPlotInstanceId)) + { + Log.outError(LogFilter.Sql, "Non-existing GarrPlotInstance.db2 entry {0} was referenced in `garrison_plot_finalize_info`.", garrPlotInstanceId); + continue; + } + + GameObjectTemplate goTemplate = Global.ObjectMgr.GetGameObjectTemplate(hordeGameObjectId); + if (goTemplate == null) + { + Log.outError(LogFilter.Sql, "Non-existing gameobject_template entry {0} was referenced in `garrison_plot_finalize_info`.`hordeGameObjectId` for garrPlotInstanceId {1}.", + hordeGameObjectId, garrPlotInstanceId); + continue; + } + + if (goTemplate.type != GameObjectTypes.Goober) + { + Log.outError(LogFilter.Sql, "Invalid gameobject type {0} (entry {1}) was referenced in `garrison_plot_finalize_info`.`hordeGameObjectId` for garrPlotInstanceId {2}.", + goTemplate.type, hordeGameObjectId, garrPlotInstanceId); + continue; + } + + goTemplate = Global.ObjectMgr.GetGameObjectTemplate(allianceGameObjectId); + if (goTemplate == null) + { + Log.outError(LogFilter.Sql, "Non-existing gameobject_template entry {0} was referenced in `garrison_plot_finalize_info`.`allianceGameObjectId` for garrPlotInstanceId {1}.", + allianceGameObjectId, garrPlotInstanceId); + continue; + } + + if (goTemplate.type != GameObjectTypes.Goober) + { + Log.outError(LogFilter.Sql, "Invalid gameobject type {0} (entry {1}) was referenced in `garrison_plot_finalize_info`.`allianceGameObjectId` for garrPlotInstanceId {2}.", + goTemplate.type, allianceGameObjectId, garrPlotInstanceId); + continue; + } + + if (hordeAnimKitId != 0 && !CliDB.AnimKitStorage.ContainsKey(hordeAnimKitId)) + { + Log.outError(LogFilter.Sql, "Non-existing AnimKit.dbc entry {0} was referenced in `garrison_plot_finalize_info`.`hordeAnimKitId` for garrPlotInstanceId {1}.", + hordeAnimKitId, garrPlotInstanceId); + continue; + } + + if (allianceAnimKitId != 0 && !CliDB.AnimKitStorage.ContainsKey(allianceAnimKitId)) + { + Log.outError(LogFilter.Sql, "Non-existing AnimKit.dbc entry {0} was referenced in `garrison_plot_finalize_info`.`allianceAnimKitId` for garrPlotInstanceId {1}.", + allianceAnimKitId, garrPlotInstanceId); + continue; + } + + FinalizeGarrisonPlotGOInfo info = new FinalizeGarrisonPlotGOInfo(); + info.factionInfo[GarrisonFactionIndex.Horde].GameObjectId = hordeGameObjectId; + info.factionInfo[GarrisonFactionIndex.Horde].Pos.Relocate(result.Read(2), result.Read(3), result.Read(4), result.Read(5)); + info.factionInfo[GarrisonFactionIndex.Horde].AnimKitId = hordeAnimKitId; + + info.factionInfo[GarrisonFactionIndex.Alliance].GameObjectId = allianceGameObjectId; + info.factionInfo[GarrisonFactionIndex.Alliance].Pos.Relocate(result.Read(8), result.Read(9), result.Read(10), result.Read(11)); + info.factionInfo[GarrisonFactionIndex.Alliance].AnimKitId = allianceAnimKitId; + + _finalizePlotGOInfo[garrPlotInstanceId] = info; + + } while (result.NextRow()); + + Log.outInfo(LogFilter.ServerLoading, "Loaded {0} garrison plot finalize entries in {1}.", _finalizePlotGOInfo.Count, Time.GetMSTimeDiffToNow(msTime)); + } + + void LoadFollowerClassSpecAbilities() + { + SQLResult result = DB.World.Query("SELECT classSpecId, abilityId FROM garrison_follower_class_spec_abilities"); + if (result.IsEmpty()) + { + Log.outInfo(LogFilter.ServerLoading, "Loaded 0 garrison follower class spec abilities. DB table `garrison_follower_class_spec_abilities` is empty."); + return; + } + + uint msTime = Time.GetMSTime(); + uint count = 0; + do + { + uint classSpecId = result.Read(0); + uint abilityId = result.Read(1); + + if (!CliDB.GarrClassSpecStorage.ContainsKey(classSpecId)) + { + Log.outError(LogFilter.Sql, "Non-existing GarrClassSpec.db2 entry {0} was referenced in `garrison_follower_class_spec_abilities` by row ({1}, {2}).", classSpecId, classSpecId, abilityId); + continue; + } + + GarrAbilityRecord ability = CliDB.GarrAbilityStorage.LookupByKey(abilityId); + if (ability == null) + { + Log.outError(LogFilter.Sql, "Non-existing GarrAbility.db2 entry {0} was referenced in `garrison_follower_class_spec_abilities` by row ({1}, {2}).", abilityId, classSpecId, abilityId); + continue; + } + + _garrisonFollowerClassSpecAbilities.Add(classSpecId, ability); + ++count; + + } while (result.NextRow()); + + //foreach (var key in _garrisonFollowerClassSpecAbilities.Keys) + //_garrisonFollowerClassSpecAbilities[key].Sort(); + + Log.outInfo(LogFilter.ServerLoading, "Loaded {0} garrison follower class spec abilities in {1}.", count, Time.GetMSTimeDiffToNow(msTime)); + } + + MultiMap _garrisonPlotInstBySiteLevel = new MultiMap(); + Dictionary> _garrisonPlots = new Dictionary>(); + MultiMap _garrisonBuildingsByPlot = new MultiMap(); + Dictionary _garrisonBuildingPlotInstances = new Dictionary(); + MultiMap _garrisonBuildingsByType = new MultiMap(); + Dictionary _finalizePlotGOInfo = new Dictionary(); + Dictionary[] _garrisonFollowerAbilities = new Dictionary[2]; + MultiMap _garrisonFollowerClassSpecAbilities = new MultiMap(); + List _garrisonFollowerRandomTraits = new List(); + + ulong _followerDbIdGenerator = 1; + } + + class GarrAbilities + { + public List Counters = new List(); + public List Traits = new List(); + } + + public class FinalizeGarrisonPlotGOInfo + { + public FactionInfo[] factionInfo = new FactionInfo[2]; + + public struct FactionInfo + { + public uint GameObjectId; + public Position Pos; + public ushort AnimKitId; + } + } +} diff --git a/Game/Garrisons/GarrisonMap.cs b/Game/Garrisons/GarrisonMap.cs new file mode 100644 index 000000000..009195f64 --- /dev/null +++ b/Game/Garrisons/GarrisonMap.cs @@ -0,0 +1,142 @@ +/* + * Copyright (C) 2012-2017 CypherCore + * + * 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, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +using Framework.Constants; +using Game.Entities; +using Game.Maps; +using System.Collections.Generic; +using System.Linq; + +namespace Game.Garrisons +{ + class GarrisonMap : Map + { + public GarrisonMap(uint id, long expiry, uint instanceId, Map parent, ObjectGuid owner) + : base(id, expiry, instanceId, Difficulty.Normal, parent) + { + _owner = owner; + InitVisibilityDistance(); + } + + public override void LoadGridObjects(Grid grid, Cell cell) + { + LoadGridObjects(grid, cell); + + GarrisonGridLoader loader = new GarrisonGridLoader(grid, this, cell); + loader.LoadN(); + } + + public Garrison GetGarrison() + { + if (_loadingPlayer) + return _loadingPlayer.GetGarrison(); + + Player owner = Global.ObjAccessor.FindConnectedPlayer(_owner); + if (owner) + return owner.GetGarrison(); + + return null; + } + + public override void InitVisibilityDistance() + { + //init visibility distance for instances + m_VisibleDistance = Global.WorldMgr.GetMaxVisibleDistanceInBGArenas(); + m_VisibilityNotifyPeriod = Global.WorldMgr.GetVisibilityNotifyPeriodInBGArenas(); + } + + public override bool AddPlayerToMap(Player player, bool initPlayer = true) + { + if (player.GetGUID() == _owner) + _loadingPlayer = player; + + bool result = base.AddPlayerToMap(player, initPlayer); + + if (player.GetGUID() == _owner) + _loadingPlayer = null; + + return result; + } + + ObjectGuid _owner; + Player _loadingPlayer; // @workaround Player is not registered in ObjectAccessor during login + } + + class GarrisonGridLoader : Notifier + { + public GarrisonGridLoader(Grid grid, GarrisonMap map, Cell cell) + { + i_cell = cell; + i_grid = grid; + i_map = map; + i_garrison = map.GetGarrison(); + } + + public void LoadN() + { + if (i_garrison != null) + { + i_cell.data.cell_y = 0; + for (uint x = 0; x < MapConst.MaxCells; ++x) + { + i_cell.data.cell_x = x; + for (uint y = 0; y < MapConst.MaxCells; ++y) + { + i_cell.data.cell_y = y; + + //Load creatures and game objects + var visitor = new Visitor(this, GridMapTypeMask.AllGrid); + i_grid.VisitGrid(x, y, visitor); + } + } + } + + Log.outDebug(LogFilter.Maps, "{0} GameObjects and {1} Creatures loaded for grid {2} on map {3}", i_gameObjects, i_creatures, i_grid.GetGridId(), i_map.GetId()); + } + + public override void Visit(ICollection objs) + { + List plots = i_garrison.GetPlots().ToList(); + if (!plots.Empty()) + { + CellCoord cellCoord = i_cell.GetCellCoord(); + foreach (Garrison.Plot plot in plots) + { + Position spawn = plot.PacketInfo.PlotPos; + if (cellCoord != GridDefines.ComputeCellCoord(spawn.GetPositionX(), spawn.GetPositionY())) + continue; + + GameObject go = plot.CreateGameObject(i_map, i_garrison.GetFaction()); + if (!go) + continue; + + var cell = new Cell(cellCoord); + i_map.AddToGrid(go, cell); + go.AddToWorld(); + ++i_gameObjects; + } + } + } + + Cell i_cell; + Grid i_grid; + GarrisonMap i_map; + Garrison i_garrison; + uint i_gameObjects; + uint i_creatures; + } +} diff --git a/Game/Garrisons/Garrisons.cs b/Game/Garrisons/Garrisons.cs new file mode 100644 index 000000000..50c2c12c9 --- /dev/null +++ b/Game/Garrisons/Garrisons.cs @@ -0,0 +1,864 @@ +/* + * Copyright (C) 2012-2017 CypherCore + * + * 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, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +using Framework.Constants; +using Framework.Database; +using Framework.Dynamic; +using Framework.GameMath; +using Game.DataStorage; +using Game.Entities; +using Game.Maps; +using Game.Network.Packets; +using System; +using System.Collections.Generic; +using System.Diagnostics.Contracts; + +namespace Game.Garrisons +{ + public class Garrison + { + public Garrison(Player owner) + { + _owner = owner; + _followerActivationsRemainingToday = 1; + } + + public bool LoadFromDB(SQLResult garrison, SQLResult blueprints, SQLResult buildings, SQLResult followers, SQLResult abilities) + { + if (garrison.IsEmpty()) + return false; + + _siteLevel = CliDB.GarrSiteLevelStorage.LookupByKey(garrison.Read(0)); + _followerActivationsRemainingToday = garrison.Read(1); + if (_siteLevel == null) + return false; + + InitializePlots(); + + if (!blueprints.IsEmpty()) + { + do + { + GarrBuildingRecord building = CliDB.GarrBuildingStorage.LookupByKey(blueprints.Read(0)); + if (building != null) + _knownBuildings.Add(building.Id); + + } while (blueprints.NextRow()); + } + + if (!buildings.IsEmpty()) + { + do + { + uint plotInstanceId = buildings.Read(0); + uint buildingId = buildings.Read(1); + ulong timeBuilt = buildings.Read(2); + bool active = buildings.Read(3); + + + Plot plot = GetPlot(plotInstanceId); + if (plot == null) + continue; + + if (!CliDB.GarrBuildingStorage.ContainsKey(buildingId)) + continue; + + plot.BuildingInfo.PacketInfo.HasValue = true; + plot.BuildingInfo.PacketInfo.Value.GarrPlotInstanceID = plotInstanceId; + plot.BuildingInfo.PacketInfo.Value.GarrBuildingID = buildingId; + plot.BuildingInfo.PacketInfo.Value.TimeBuilt = (long)timeBuilt; + plot.BuildingInfo.PacketInfo.Value.Active = active; + + } while (buildings.NextRow()); + } + + if (!followers.IsEmpty()) + { + do + { + ulong dbId = followers.Read(0); + uint followerId = followers.Read(1); + if (!CliDB.GarrFollowerStorage.ContainsKey(followerId)) + continue; + + _followerIds.Add(followerId); + + var follower = new Follower(); + follower.PacketInfo.DbID = dbId; + follower.PacketInfo.GarrFollowerID = followerId; + follower.PacketInfo.Quality = followers.Read(2); + follower.PacketInfo.FollowerLevel = followers.Read(3); + follower.PacketInfo.ItemLevelWeapon = followers.Read(4); + follower.PacketInfo.ItemLevelArmor = followers.Read(5); + follower.PacketInfo.Xp = followers.Read(6); + follower.PacketInfo.CurrentBuildingID = followers.Read(7); + follower.PacketInfo.CurrentMissionID = followers.Read(8); + follower.PacketInfo.FollowerStatus = followers.Read(9); + if (!CliDB.GarrBuildingStorage.ContainsKey(follower.PacketInfo.CurrentBuildingID)) + follower.PacketInfo.CurrentBuildingID = 0; + + //if (!sGarrMissionStore.LookupEntry(follower.PacketInfo.CurrentMissionID)) + // follower.PacketInfo.CurrentMissionID = 0; + _followers[followerId] = follower; + + } while (followers.NextRow()); + + if (!abilities.IsEmpty()) + { + do + { + ulong dbId = abilities.Read(0); + GarrAbilityRecord ability = CliDB.GarrAbilityStorage.LookupByKey(abilities.Read(1)); + + if (ability == null) + continue; + + var garrisonFollower = _followers.LookupByKey(dbId); + if (garrisonFollower == null) + continue; + + garrisonFollower.PacketInfo.AbilityID.Add(ability); + } while (abilities.NextRow()); + } + } + + return true; + } + + public void SaveToDB(SQLTransaction trans) + { + DeleteFromDB(_owner.GetGUID().GetCounter(), trans); + + PreparedStatement stmt = DB.Characters.GetPreparedStatement(CharStatements.INS_CHARACTER_GARRISON); + stmt.AddValue(0, _owner.GetGUID().GetCounter()); + stmt.AddValue(1, _siteLevel.Id); + stmt.AddValue(2, _followerActivationsRemainingToday); + trans.Append(stmt); + + foreach (uint building in _knownBuildings) + { + stmt = DB.Characters.GetPreparedStatement(CharStatements.INS_CHARACTER_GARRISON_BLUEPRINTS); + stmt.AddValue(0, _owner.GetGUID().GetCounter()); + stmt.AddValue(1, building); + trans.Append(stmt); + } + + foreach (var plot in _plots.Values) + { + if (plot.BuildingInfo.PacketInfo.HasValue) + { + stmt = DB.Characters.GetPreparedStatement(CharStatements.INS_CHARACTER_GARRISON_BUILDINGS); + stmt.AddValue(0, _owner.GetGUID().GetCounter()); + stmt.AddValue(1, plot.BuildingInfo.PacketInfo.Value.GarrPlotInstanceID); + stmt.AddValue(2, plot.BuildingInfo.PacketInfo.Value.GarrBuildingID); + stmt.AddValue(3, plot.BuildingInfo.PacketInfo.Value.TimeBuilt); + stmt.AddValue(4, plot.BuildingInfo.PacketInfo.Value.Active); + trans.Append(stmt); + } + } + + foreach (var follower in _followers.Values) + { + byte index = 0; + stmt = DB.Characters.GetPreparedStatement(CharStatements.INS_CHARACTER_GARRISON_FOLLOWERS); + stmt.AddValue(index++, follower.PacketInfo.DbID); + stmt.AddValue(index++, _owner.GetGUID().GetCounter()); + stmt.AddValue(index++, follower.PacketInfo.GarrFollowerID); + stmt.AddValue(index++, follower.PacketInfo.Quality); + stmt.AddValue(index++, follower.PacketInfo.FollowerLevel); + stmt.AddValue(index++, follower.PacketInfo.ItemLevelWeapon); + stmt.AddValue(index++, follower.PacketInfo.ItemLevelArmor); + stmt.AddValue(index++, follower.PacketInfo.Xp); + stmt.AddValue(index++, follower.PacketInfo.CurrentBuildingID); + stmt.AddValue(index++, follower.PacketInfo.CurrentMissionID); + stmt.AddValue(index++, follower.PacketInfo.FollowerStatus); + trans.Append(stmt); + + byte slot = 0; + foreach (GarrAbilityRecord ability in follower.PacketInfo.AbilityID) + { + stmt = DB.Characters.GetPreparedStatement(CharStatements.INS_CHARACTER_GARRISON_FOLLOWER_ABILITIES); + stmt.AddValue(0, follower.PacketInfo.DbID); + stmt.AddValue(1, ability.Id); + stmt.AddValue(2, slot++); + trans.Append(stmt); + } + } + } + + public static void DeleteFromDB(ulong ownerGuid, SQLTransaction trans) + { + PreparedStatement stmt = DB.Characters.GetPreparedStatement(CharStatements.DEL_CHARACTER_GARRISON); + stmt.AddValue(0, ownerGuid); + trans.Append(stmt); + + stmt = DB.Characters.GetPreparedStatement(CharStatements.DEL_CHARACTER_GARRISON_BLUEPRINTS); + stmt.AddValue(0, ownerGuid); + trans.Append(stmt); + + stmt = DB.Characters.GetPreparedStatement(CharStatements.DEL_CHARACTER_GARRISON_BUILDINGS); + stmt.AddValue(0, ownerGuid); + trans.Append(stmt); + + stmt = DB.Characters.GetPreparedStatement(CharStatements.DEL_CHARACTER_GARRISON_FOLLOWERS); + stmt.AddValue(0, ownerGuid); + trans.Append(stmt); + } + + public bool Create(uint garrSiteId) + { + GarrSiteLevelRecord siteLevel = Global.GarrisonMgr.GetGarrSiteLevelEntry(garrSiteId, 1); + if (siteLevel == null) + return false; + + _siteLevel = siteLevel; + + InitializePlots(); + + GarrisonCreateResult garrisonCreateResult = new GarrisonCreateResult(); + garrisonCreateResult.GarrSiteLevelID = _siteLevel.Id; + _owner.SendPacket(garrisonCreateResult); + _owner.SendUpdatePhasing(); + SendRemoteInfo(); + return true; + } + + public void Delete() + { + SQLTransaction trans = new SQLTransaction(); + DeleteFromDB(_owner.GetGUID().GetCounter(), trans); + DB.Characters.CommitTransaction(trans); + + GarrisonDeleteResult garrisonDelete = new GarrisonDeleteResult(); + garrisonDelete.Result = GarrisonError.Success; + garrisonDelete.GarrSiteID = _siteLevel.SiteID; + _owner.SendPacket(garrisonDelete); + } + + void InitializePlots() + { + var plots = Global.GarrisonMgr.GetGarrPlotInstForSiteLevel(_siteLevel.Id); + + for (var i = 0; i < plots.Count; ++i) + { + uint garrPlotInstanceId = plots[i].GarrPlotInstanceID; + GarrPlotInstanceRecord plotInstance = CliDB.GarrPlotInstanceStorage.LookupByKey(garrPlotInstanceId); + GameObjectsRecord gameObject = Global.GarrisonMgr.GetPlotGameObject(_siteLevel.MapID, garrPlotInstanceId); + if (plotInstance == null || gameObject == null) + continue; + + GarrPlotRecord plot = CliDB.GarrPlotStorage.LookupByKey(plotInstance.GarrPlotID); + if (plot == null) + continue; + + Plot plotInfo = _plots[garrPlotInstanceId]; + plotInfo.PacketInfo.GarrPlotInstanceID = garrPlotInstanceId; + plotInfo.PacketInfo.PlotPos.Relocate(gameObject.Position.X, gameObject.Position.Y, gameObject.Position.Z, 2 * (float)Math.Acos(gameObject.RotationW)); + plotInfo.PacketInfo.PlotType = plot.PlotType; + plotInfo.EmptyGameObjectId = gameObject.Id; + plotInfo.GarrSiteLevelPlotInstId = plots[i].Id; + } + } + + void Upgrade() + { + } + + void Enter() + { + WorldLocation loc = new WorldLocation(_siteLevel.MapID); + loc.Relocate(_owner); + _owner.TeleportTo(loc, TeleportToOptions.Seamless); + } + + void Leave() + { + MapRecord map = CliDB.MapStorage.LookupByKey(_siteLevel.MapID); + if (map != null) + { + WorldLocation loc = new WorldLocation((uint)map.ParentMapID); + loc.Relocate(_owner); + _owner.TeleportTo(loc, TeleportToOptions.Seamless); + } + } + + public uint GetFaction() + { + return _owner.GetTeam() == Team.Horde ? GarrisonFactionIndex.Horde : GarrisonFactionIndex.Alliance; + } + + public ICollection GetPlots() + { + return _plots.Values; + } + + Plot GetPlot(uint garrPlotInstanceId) + { + return _plots.LookupByKey(garrPlotInstanceId); + } + + public void LearnBlueprint(uint garrBuildingId) + { + GarrisonLearnBlueprintResult learnBlueprintResult = new GarrisonLearnBlueprintResult(); + learnBlueprintResult.GarrTypeID = GarrisonType.Garrison; + learnBlueprintResult.BuildingID = garrBuildingId; + learnBlueprintResult.Result = GarrisonError.Success; + + if (!CliDB.GarrBuildingStorage.ContainsKey(garrBuildingId)) + learnBlueprintResult.Result = GarrisonError.InvalidBuildingId; + else if (_knownBuildings.Contains(garrBuildingId)) + learnBlueprintResult.Result = GarrisonError.BlueprintExists; + else + _knownBuildings.Add(garrBuildingId); + + _owner.SendPacket(learnBlueprintResult); + } + + void UnlearnBlueprint(uint garrBuildingId) + { + GarrisonUnlearnBlueprintResult unlearnBlueprintResult = new GarrisonUnlearnBlueprintResult(); + unlearnBlueprintResult.GarrTypeID = GarrisonType.Garrison; + unlearnBlueprintResult.BuildingID = garrBuildingId; + unlearnBlueprintResult.Result = GarrisonError.Success; + + if (!CliDB.GarrBuildingStorage.ContainsKey(garrBuildingId)) + unlearnBlueprintResult.Result = GarrisonError.InvalidBuildingId; + else if (!_knownBuildings.Contains(garrBuildingId)) + unlearnBlueprintResult.Result = GarrisonError.RequiresBlueprint; + else + _knownBuildings.Remove(garrBuildingId); + + _owner.SendPacket(unlearnBlueprintResult); + } + + public void PlaceBuilding(uint garrPlotInstanceId, uint garrBuildingId) + { + GarrisonPlaceBuildingResult placeBuildingResult = new GarrisonPlaceBuildingResult(); + placeBuildingResult.GarrTypeID = GarrisonType.Garrison; + placeBuildingResult.Result = CheckBuildingPlacement(garrPlotInstanceId, garrBuildingId); + if (placeBuildingResult.Result == GarrisonError.Success) + { + placeBuildingResult.BuildingInfo.GarrPlotInstanceID = garrPlotInstanceId; + placeBuildingResult.BuildingInfo.GarrBuildingID = garrBuildingId; + placeBuildingResult.BuildingInfo.TimeBuilt = Time.UnixTime; + + Plot plot = GetPlot(garrPlotInstanceId); + uint oldBuildingId = 0; + Map map = FindMap(); + GarrBuildingRecord building = CliDB.GarrBuildingStorage.LookupByKey(garrBuildingId); + if (map) + plot.DeleteGameObject(map); + + if (plot.BuildingInfo.PacketInfo.HasValue) + { + oldBuildingId = plot.BuildingInfo.PacketInfo.Value.GarrBuildingID; + if (CliDB.GarrBuildingStorage.LookupByKey(oldBuildingId).Type != building.Type) + plot.ClearBuildingInfo(_owner); + } + + plot.SetBuildingInfo(placeBuildingResult.BuildingInfo, _owner); + if (map) + { + GameObject go = plot.CreateGameObject(map, GetFaction()); + if (go) + map.AddToMap(go); + } + + _owner.ModifyCurrency((CurrencyTypes)building.CostCurrencyID, -building.CostCurrencyAmount, false, true); + _owner.ModifyMoney(-building.CostMoney * MoneyConstants.Gold, false); + + if (oldBuildingId != 0) + { + GarrisonBuildingRemoved buildingRemoved = new GarrisonBuildingRemoved(); + buildingRemoved.GarrTypeID = GarrisonType.Garrison; + buildingRemoved.Result = GarrisonError.Success; + buildingRemoved.GarrPlotInstanceID = garrPlotInstanceId; + buildingRemoved.GarrBuildingID = oldBuildingId; + _owner.SendPacket(buildingRemoved); + } + + _owner.UpdateCriteria(CriteriaTypes.PlaceGarrisonBuilding, garrBuildingId); + } + + _owner.SendPacket(placeBuildingResult); + } + + public void CancelBuildingConstruction(uint garrPlotInstanceId) + { + GarrisonBuildingRemoved buildingRemoved = new GarrisonBuildingRemoved(); + buildingRemoved.GarrTypeID = GarrisonType.Garrison; + buildingRemoved.Result = CheckBuildingRemoval(garrPlotInstanceId); + if (buildingRemoved.Result == GarrisonError.Success) + { + Plot plot = GetPlot(garrPlotInstanceId); + + buildingRemoved.GarrPlotInstanceID = garrPlotInstanceId; + buildingRemoved.GarrBuildingID = plot.BuildingInfo.PacketInfo.Value.GarrBuildingID; + + Map map = FindMap(); + if (map) + plot.DeleteGameObject(map); + + plot.ClearBuildingInfo(_owner); + _owner.SendPacket(buildingRemoved); + + GarrBuildingRecord constructing = CliDB.GarrBuildingStorage.LookupByKey(buildingRemoved.GarrBuildingID); + // Refund construction/upgrade cost + _owner.ModifyCurrency((CurrencyTypes)constructing.CostCurrencyID, constructing.CostCurrencyAmount, false, true); + _owner.ModifyMoney(constructing.CostMoney * MoneyConstants.Gold, false); + + if (constructing.Level > 1) + { + // Restore previous level building + uint restored = Global.GarrisonMgr.GetPreviousLevelBuilding(constructing.Type, constructing.Level); + Contract.Assert(restored != 0); + + GarrisonPlaceBuildingResult placeBuildingResult = new GarrisonPlaceBuildingResult(); + placeBuildingResult.GarrTypeID = GarrisonType.Garrison; + placeBuildingResult.Result = GarrisonError.Success; + placeBuildingResult.BuildingInfo.GarrPlotInstanceID = garrPlotInstanceId; + placeBuildingResult.BuildingInfo.GarrBuildingID = restored; + placeBuildingResult.BuildingInfo.TimeBuilt = Time.UnixTime; + placeBuildingResult.BuildingInfo.Active = true; + + plot.SetBuildingInfo(placeBuildingResult.BuildingInfo, _owner); + _owner.SendPacket(placeBuildingResult); + } + + if (map) + { + GameObject go = plot.CreateGameObject(map, GetFaction()); + if (go) + map.AddToMap(go); + } + } + else + _owner.SendPacket(buildingRemoved); + } + + public void ActivateBuilding(uint garrPlotInstanceId) + { + Plot plot = GetPlot(garrPlotInstanceId); + if (plot != null) + { + if (plot.BuildingInfo.CanActivate() && plot.BuildingInfo.PacketInfo.HasValue && !plot.BuildingInfo.PacketInfo.Value.Active) + { + plot.BuildingInfo.PacketInfo.Value.Active = true; + Map map = FindMap(); + if (map) + { + plot.DeleteGameObject(map); + GameObject go = plot.CreateGameObject(map, GetFaction()); + if (go) + map.AddToMap(go); + } + + GarrisonBuildingActivated buildingActivated = new GarrisonBuildingActivated(); + buildingActivated.GarrPlotInstanceID = garrPlotInstanceId; + _owner.SendPacket(buildingActivated); + } + } + } + + public void AddFollower(uint garrFollowerId) + { + GarrisonAddFollowerResult addFollowerResult = new GarrisonAddFollowerResult(); + addFollowerResult.GarrTypeID = GarrisonType.Garrison; + GarrFollowerRecord followerEntry = CliDB.GarrFollowerStorage.LookupByKey(garrFollowerId); + if (_followerIds.Contains(garrFollowerId) || followerEntry == null) + { + addFollowerResult.Result = GarrisonError.FollowerExists; + _owner.SendPacket(addFollowerResult); + return; + } + + _followerIds.Add(garrFollowerId); + ulong dbId = Global.GarrisonMgr.GenerateFollowerDbId(); + + Follower follower = new Follower(); + follower.PacketInfo.DbID = dbId; + follower.PacketInfo.GarrFollowerID = garrFollowerId; + follower.PacketInfo.Quality = followerEntry.Quality; // TODO: handle magic upgrades + follower.PacketInfo.FollowerLevel = followerEntry.Level; + follower.PacketInfo.ItemLevelWeapon = followerEntry.ItemLevelWeapon; + follower.PacketInfo.ItemLevelArmor = followerEntry.ItemLevelArmor; + follower.PacketInfo.Xp = 0; + follower.PacketInfo.CurrentBuildingID = 0; + follower.PacketInfo.CurrentMissionID = 0; + follower.PacketInfo.AbilityID = Global.GarrisonMgr.RollFollowerAbilities(garrFollowerId, followerEntry, follower.PacketInfo.Quality, GetFaction(), true); + follower.PacketInfo.FollowerStatus = 0; + + _followers[dbId] = follower; + addFollowerResult.Follower = follower.PacketInfo; + _owner.SendPacket(addFollowerResult); + + _owner.UpdateCriteria(CriteriaTypes.RecruitGarrisonFollower, follower.PacketInfo.DbID); + } + + public Follower GetFollower(ulong dbId) + { + return _followers.LookupByKey(dbId); + } + + public void SendInfo() + { + GetGarrisonInfoResult garrisonInfo = new GetGarrisonInfoResult(); + garrisonInfo.FactionIndex = GetFaction(); + + GarrisonInfo garrison = new GarrisonInfo(); + garrison.GarrTypeID = GarrisonType.Garrison; + garrison.GarrSiteID = _siteLevel.SiteID; + garrison.GarrSiteLevelID = _siteLevel.Id; + garrison.NumFollowerActivationsRemaining = _followerActivationsRemainingToday; + foreach (var plot in _plots.Values) + { + garrison.Plots.Add(plot.PacketInfo); + if (plot.BuildingInfo.PacketInfo.HasValue) + garrison.Buildings.Add(plot.BuildingInfo.PacketInfo.Value); + } + + foreach (var follower in _followers.Values) + garrison.Followers.Add(follower.PacketInfo); + + garrisonInfo.Garrisons.Add(garrison); + + _owner.SendPacket(garrisonInfo); + } + + public void SendRemoteInfo() + { + MapRecord garrisonMap = CliDB.MapStorage.LookupByKey(_siteLevel.MapID); + if (garrisonMap == null || _owner.GetMapId() != garrisonMap.ParentMapID) + return; + + GarrisonRemoteInfo remoteInfo = new GarrisonRemoteInfo(); + + GarrisonRemoteSiteInfo remoteSiteInfo = new GarrisonRemoteSiteInfo(); + remoteSiteInfo.GarrSiteLevelID = _siteLevel.Id; + foreach (var p in _plots) + if (p.Value.BuildingInfo.PacketInfo.HasValue) + remoteSiteInfo.Buildings.Add(new GarrisonRemoteBuildingInfo(p.Key, p.Value.BuildingInfo.PacketInfo.Value.GarrBuildingID)); + + remoteInfo.Sites.Add(remoteSiteInfo); + _owner.SendPacket(remoteInfo); + } + + public void SendBlueprintAndSpecializationData() + { + GarrisonRequestBlueprintAndSpecializationDataResult data = new GarrisonRequestBlueprintAndSpecializationDataResult(); + data.GarrTypeID = GarrisonType.Garrison; + data.BlueprintsKnown = _knownBuildings; + _owner.SendPacket(data); + } + + public void SendBuildingLandmarks(Player receiver) + { + GarrisonBuildingLandmarks buildingLandmarks = new GarrisonBuildingLandmarks(); + + foreach (var plot in _plots.Values) + { + if (plot.BuildingInfo.PacketInfo.HasValue) + { + uint garrBuildingPlotInstId = Global.GarrisonMgr.GetGarrBuildingPlotInst(plot.BuildingInfo.PacketInfo.Value.GarrBuildingID, plot.GarrSiteLevelPlotInstId); + if (garrBuildingPlotInstId != 0) + buildingLandmarks.Landmarks.Add(new GarrisonBuildingLandmark(garrBuildingPlotInstId, plot.PacketInfo.PlotPos)); + } + } + + receiver.SendPacket(buildingLandmarks); + } + + Map FindMap() + { + return Global.MapMgr.FindMap(_siteLevel.MapID, (uint)_owner.GetGUID().GetCounter()); + } + + GarrisonError CheckBuildingPlacement(uint garrPlotInstanceId, uint garrBuildingId) + { + GarrPlotInstanceRecord plotInstance = CliDB.GarrPlotInstanceStorage.LookupByKey(garrPlotInstanceId); + Plot plot = GetPlot(garrPlotInstanceId); + if (plotInstance == null || plot == null) + return GarrisonError.InvalidPlotInstanceId; + + GarrBuildingRecord building = CliDB.GarrBuildingStorage.LookupByKey(garrBuildingId); + if (building == null) + return GarrisonError.InvalidBuildingId; + + if (!Global.GarrisonMgr.IsPlotMatchingBuilding(plotInstance.GarrPlotID, garrBuildingId)) + return GarrisonError.InvalidPlotBuilding; + + // Cannot place buldings of higher level than garrison level + if (building.Level > _siteLevel.Level) + return GarrisonError.InvalidBuildingId; + + if (building.Flags.HasAnyFlag(GarrisonBuildingFlags.NeedsPlan)) + { + if (!_knownBuildings.Contains(garrBuildingId)) + return GarrisonError.RequiresBlueprint; + } + else // Building is built as a quest reward + return GarrisonError.InvalidBuildingId; + + // Check all plots to find if we already have this building + GarrBuildingRecord existingBuilding; + foreach (var p in _plots) + { + if (p.Value.BuildingInfo.PacketInfo.HasValue) + { + existingBuilding = CliDB.GarrBuildingStorage.LookupByKey(p.Value.BuildingInfo.PacketInfo.Value.GarrBuildingID); + if (existingBuilding.Type == building.Type) + if (p.Key != garrPlotInstanceId || existingBuilding.Level + 1 != building.Level) // check if its an upgrade in same plot + return GarrisonError.BuildingExists; + } + } + + if (!_owner.HasCurrency(building.CostCurrencyID, (uint)building.CostCurrencyAmount)) + return GarrisonError.NotEnoughCurrency; + + if (!_owner.HasEnoughMoney(building.CostMoney * MoneyConstants.Gold)) + return GarrisonError.NotEnoughGold; + + // New building cannot replace another building currently under construction + if (plot.BuildingInfo.PacketInfo.HasValue) + if (!plot.BuildingInfo.PacketInfo.Value.Active) + return GarrisonError.NoBuilding; + + return GarrisonError.Success; + } + + GarrisonError CheckBuildingRemoval(uint garrPlotInstanceId) + { + Plot plot = GetPlot(garrPlotInstanceId); + if (plot == null) + return GarrisonError.InvalidPlotInstanceId; + + if (!plot.BuildingInfo.PacketInfo.HasValue) + return GarrisonError.NoBuilding; + + if (plot.BuildingInfo.CanActivate()) + return GarrisonError.BuildingExists; + + return GarrisonError.Success; + } + + public void ResetFollowerActivationLimit() { _followerActivationsRemainingToday = 1; } + + Player _owner; + GarrSiteLevelRecord _siteLevel; + uint _followerActivationsRemainingToday; + + Dictionary _plots = new Dictionary(); + List _knownBuildings = new List(); + Dictionary _followers = new Dictionary(); + List _followerIds = new List(); + + public class Building + { + public bool CanActivate() + { + if (PacketInfo.HasValue) + { + GarrBuildingRecord building = CliDB.GarrBuildingStorage.LookupByKey(PacketInfo.Value.GarrBuildingID); + if (PacketInfo.Value.TimeBuilt + building.BuildDuration <= Time.UnixTime) + return true; + } + + return false; + } + + public ObjectGuid Guid; + public List Spawns = new List(); + public Optional PacketInfo; + } + + public class Plot + { + public GameObject CreateGameObject(Map map, uint faction) + { + uint entry = EmptyGameObjectId; + if (BuildingInfo.PacketInfo.HasValue) + { + GarrPlotInstanceRecord plotInstance = CliDB.GarrPlotInstanceStorage.LookupByKey(PacketInfo.GarrPlotInstanceID); + GarrPlotRecord plot = CliDB.GarrPlotStorage.LookupByKey(plotInstance.GarrPlotID); + GarrBuildingRecord building = CliDB.GarrBuildingStorage.LookupByKey(BuildingInfo.PacketInfo.Value.GarrBuildingID); + + entry = faction == GarrisonFactionIndex.Horde ? plot.HordeConstructionGameObjectID : plot.AllianceConstructionGameObjectID; + if (BuildingInfo.PacketInfo.Value.Active || entry == 0) + entry = faction == GarrisonFactionIndex.Horde ? building.HordeGameObjectID : building.AllianceGameObjectID; + } + + if (Global.ObjectMgr.GetGameObjectTemplate(entry) == null) + { + Log.outError(LogFilter.Garrison, "Garrison attempted to spawn gameobject whose template doesn't exist ({0})", entry); + return null; + } + + Position pos = PacketInfo.PlotPos; + GameObject go = new GameObject(); + if (!go.Create(entry, map, 0, pos, Quaternion.WAxis, 255, GameObjectState.Active)) + return null; + + if (BuildingInfo.CanActivate() && BuildingInfo.PacketInfo.HasValue && !BuildingInfo.PacketInfo.Value.Active) + { + FinalizeGarrisonPlotGOInfo finalizeInfo = Global.GarrisonMgr.GetPlotFinalizeGOInfo(PacketInfo.GarrPlotInstanceID); + if (finalizeInfo != null) + { + Position pos2 = finalizeInfo.factionInfo[faction].Pos; + GameObject finalizer = new GameObject(); + if (finalizer.Create(finalizeInfo.factionInfo[faction].GameObjectId, map, 0, pos2, Quaternion.WAxis, 255, GameObjectState.Ready)) + { + // set some spell id to make the object delete itself after use + finalizer.SetSpellId(finalizer.GetGoInfo().Goober.spell); + finalizer.SetRespawnTime(0); + + ushort animKit = finalizeInfo.factionInfo[faction].AnimKitId; + if (animKit != 0) + finalizer.SetAnimKitId(animKit, false); + + map.AddToMap(finalizer); + } + } + } + + if (go.GetGoType() == GameObjectTypes.GarrisonBuilding && go.GetGoInfo().garrisonBuilding.SpawnMap != 0) + { + foreach (var cellGuids in Global.ObjectMgr.GetMapObjectGuids((uint)go.GetGoInfo().garrisonBuilding.SpawnMap, (byte)map.GetSpawnMode())) + { + foreach (var spawnId in cellGuids.Value.creatures) + { + Creature spawn = BuildingSpawnHelper(go, spawnId, map); + if (spawn) + BuildingInfo.Spawns.Add(spawn.GetGUID()); + } + + foreach (var spawnId in cellGuids.Value.gameobjects) + { + GameObject spawn = BuildingSpawnHelper(go, spawnId, map); + if (spawn) + BuildingInfo.Spawns.Add(spawn.GetGUID()); + } + } + } + + BuildingInfo.Guid = go.GetGUID(); + return go; + } + + public void DeleteGameObject(Map map) + { + if (BuildingInfo.Guid.IsEmpty()) + return; + + foreach (var guid in BuildingInfo.Spawns) + { + WorldObject obj = null; + switch (guid.GetHigh()) + { + case HighGuid.Creature: + obj = map.GetCreature(guid); + break; + case HighGuid.GameObject: + obj = map.GetGameObject(guid); + break; + default: + continue; + } + + if (obj) + obj.AddObjectToRemoveList(); + } + + BuildingInfo.Spawns.Clear(); + + GameObject oldBuilding = map.GetGameObject(BuildingInfo.Guid); + if (oldBuilding) + oldBuilding.Delete(); + + BuildingInfo.Guid.Clear(); + } + + public void ClearBuildingInfo(Player owner) + { + GarrisonPlotPlaced plotPlaced = new GarrisonPlotPlaced(); + plotPlaced.GarrTypeID = GarrisonType.Garrison; + plotPlaced.PlotInfo = PacketInfo; + owner.SendPacket(plotPlaced); + + BuildingInfo.PacketInfo.Clear(); + } + + public void SetBuildingInfo(GarrisonBuildingInfo buildingInfo, Player owner) + { + if (!BuildingInfo.PacketInfo.HasValue) + { + GarrisonPlotRemoved plotRemoved = new GarrisonPlotRemoved(); + plotRemoved.GarrPlotInstanceID = PacketInfo.GarrPlotInstanceID; + owner.SendPacket(plotRemoved); + } + + BuildingInfo.PacketInfo.Set(buildingInfo); + } + + T BuildingSpawnHelper(GameObject building, ulong spawnId, Map map) where T : WorldObject, new() + { + T spawn = new T(); + if (!spawn.LoadFromDB(spawnId, map)) + return null; + + float x = spawn.GetPositionX(); + float y = spawn.GetPositionY(); + float z = spawn.GetPositionZ(); + float o = spawn.GetOrientation(); + TransportPosHelper.CalculatePassengerPosition(ref x, ref y, ref z, ref o, building.GetPositionX(), building.GetPositionY(), building.GetPositionZ(), building.GetOrientation()); + + spawn.Relocate(x, y, z, o); + switch (spawn.GetTypeId()) + { + case TypeId.Unit: + spawn.ToCreature().SetHomePosition(x, y, z, o); + break; + case TypeId.GameObject: + spawn.ToGameObject().RelocateStationaryPosition(x, y, z, o); + break; + } + + if (!spawn.IsPositionValid()) + return null; + + if (!map.AddToMap(spawn)) + return null; + + return spawn; + } + + public GarrisonPlotInfo PacketInfo; + public uint EmptyGameObjectId; + public uint GarrSiteLevelPlotInstId; + public Building BuildingInfo; + } + + public class Follower + { + public uint GetItemLevel() + { + return (PacketInfo.ItemLevelWeapon + PacketInfo.ItemLevelArmor) / 2; + } + + public GarrisonFollower PacketInfo = new GarrisonFollower(); + } + } +} diff --git a/Game/Globals/Global.cs b/Game/Globals/Global.cs new file mode 100644 index 000000000..9e199185b --- /dev/null +++ b/Game/Globals/Global.cs @@ -0,0 +1,109 @@ +/* + * Copyright (C) 2012-2017 CypherCore + * + * 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, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +using Game; +using Game.Achievements; +using Game.AI; +using Game.Arenas; +using Game.BattleFields; +using Game.BattleGrounds; +using Game.BlackMarket; +using Game.Collision; +using Game.DataStorage; +using Game.DungeonFinding; +using Game.Entities; +using Game.Garrisons; +using Game.Groups; +using Game.Guilds; +using Game.Maps; +using Game.PvP; +using Game.Scenarios; +using Game.Scripting; +using Game.SupportSystem; + +public static class Global +{ + //Main + public static ObjectAccessor ObjAccessor { get { return ObjectAccessor.Instance; } } + public static ObjectManager ObjectMgr { get { return ObjectManager.Instance; } } + public static WorldManager WorldMgr { get { return WorldManager.Instance; } } + public static RealmManager RealmMgr { get { return RealmManager.Instance; } } + + //Guild + public static GuildManager GuildMgr { get { return GuildManager.Instance; } } + public static GuildFinderManager GuildFinderMgr { get { return GuildFinderManager.Instance; } } + + //Social + public static CalendarManager CalendarMgr { get { return CalendarManager.Instance; } } + public static SocialManager SocialMgr { get { return SocialManager.Instance; } } + + //Scripts + public static ScriptManager ScriptMgr { get { return ScriptManager.Instance; } } + public static SmartAIManager SmartAIMgr { get { return SmartAIManager.Instance; } } + + //Groups + public static GroupManager GroupMgr { get { return GroupManager.Instance; } } + public static LFGManager LFGMgr { get { return LFGManager.Instance; } } + public static ArenaTeamManager ArenaTeamMgr { get { return ArenaTeamManager.Instance; } } + + //Maps System + public static MapManager MapMgr { get { return MapManager.Instance; } } + public static MMapManager MMapMgr { get { return MMapManager.Instance; } } + public static VMapManager VMapMgr { get { return VMapManager.Instance; } } + public static WaypointManager WaypointMgr { get { return WaypointManager.Instance; } } + public static TransportManager TransportMgr { get { return TransportManager.Instance; } } + public static InstanceSaveManager InstanceSaveMgr { get { return InstanceSaveManager.Instance; } } + public static TaxiPathGraph TaxiPathGraph { get { return TaxiPathGraph.Instance; } } + public static ScenarioManager ScenarioMgr { get { return ScenarioManager.Instance; } } + + //PVP + public static BattlegroundManager BattlegroundMgr { get { return BattlegroundManager.Instance; } } + public static OutdoorPvPManager OutdoorPvPMgr { get { return OutdoorPvPManager.Instance; } } + public static BattleFieldManager BattleFieldMgr { get { return BattleFieldManager.Instance; } } + + //Account + public static AccountManager AccountMgr { get { return AccountManager.Instance; } } + public static BNetAccountManager BNetAccountMgr { get { return BNetAccountManager.Instance; } } + + //Garrison + public static GarrisonManager GarrisonMgr { get { return GarrisonManager.Instance; } } + + //Achievement + public static AchievementGlobalMgr AchievementMgr { get { return AchievementGlobalMgr.Instance; } } + public static CriteriaManager CriteriaMgr { get { return CriteriaManager.Instance; } } + + //DataStorage + public static AreaTriggerDataStorage AreaTriggerDataStorage { get { return AreaTriggerDataStorage.Instance; } } + public static CharacterTemplateDataStorage CharacterTemplateDataStorage { get { return CharacterTemplateDataStorage.Instance; } } + public static ConversationDataStorage ConversationDataStorage { get { return ConversationDataStorage.Instance; } } + + //Misc + public static ConditionManager ConditionMgr { get { return ConditionManager.Instance; } } + public static DB2Manager DB2Mgr { get { return DB2Manager.Instance; } } + public static DisableManager DisableMgr { get { return DisableManager.Instance; } } + public static PoolManager PoolMgr { get { return PoolManager.Instance; } } + public static WeatherManager WeatherMgr { get { return WeatherManager.Instance; } } + + public static GameEventManager GameEventMgr { get { return GameEventManager.Instance; } } + public static CreatureTextManager CreatureTextMgr { get { return CreatureTextManager.Instance; } } + public static AuctionManager AuctionMgr { get { return AuctionManager.Instance; } } + + public static SpellManager SpellMgr { get { return SpellManager.Instance; } } + public static SupportManager SupportMgr { get { return SupportManager.Instance; } } + public static WargenCheckManager WardenCheckMgr { get { return WargenCheckManager.Instance; } } + public static BlackMarketManager BlackMarketMgr { get { return BlackMarketManager.Instance; } } +} diff --git a/Game/Globals/ObjectAccessor.cs b/Game/Globals/ObjectAccessor.cs new file mode 100644 index 000000000..ab68c3469 --- /dev/null +++ b/Game/Globals/ObjectAccessor.cs @@ -0,0 +1,273 @@ +/* + * Copyright (C) 2012-2017 CypherCore + * + * 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, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +using Framework.Constants; +using Game; +using Game.Entities; +using Game.Maps; +using System; +using System.Collections.Concurrent; +using System.Collections.Generic; + +public class ObjectAccessor : Singleton +{ + ObjectAccessor() { } + + public WorldObject GetWorldObject(WorldObject p, ObjectGuid guid) + { + switch (guid.GetHigh()) + { + case HighGuid.Player: + return GetPlayer(p, guid); + case HighGuid.Transport: + case HighGuid.GameObject: + return GetGameObject(p, guid); + case HighGuid.Vehicle: + case HighGuid.Creature: + return GetCreature(p, guid); + case HighGuid.Pet: + return GetPet(p, guid); + case HighGuid.DynamicObject: + return GetDynamicObject(p, guid); + case HighGuid.AreaTrigger: + return GetAreaTrigger(p, guid); + case HighGuid.Corpse: + return GetCorpse(p, guid); + case HighGuid.Conversation: + return GetConversation(p, guid); + default: + return null; + } + } + + public WorldObject GetObjectByTypeMask(WorldObject p, ObjectGuid guid, TypeMask typemask) + { + switch (guid.GetHigh()) + { + case HighGuid.Item: + if (typemask.HasAnyFlag(TypeMask.Item) && p.IsTypeId(TypeId.Player)) + return ((Player)p).GetItemByGuid(guid); + break; + case HighGuid.Player: + if (typemask.HasAnyFlag(TypeMask.Player)) + return GetPlayer(p, guid); + break; + case HighGuid.Transport: + case HighGuid.GameObject: + if (typemask.HasAnyFlag(TypeMask.GameObject)) + return GetGameObject(p, guid); + break; + case HighGuid.Creature: + case HighGuid.Vehicle: + if (typemask.HasAnyFlag(TypeMask.Unit)) + return GetCreature(p, guid); + break; + case HighGuid.Pet: + if (typemask.HasAnyFlag(TypeMask.Unit)) + return GetPet(p, guid); + break; + case HighGuid.DynamicObject: + if (typemask.HasAnyFlag(TypeMask.DynamicObject)) + return GetDynamicObject(p, guid); + break; + case HighGuid.AreaTrigger: + if (typemask.HasAnyFlag(TypeMask.AreaTrigger)) + return GetAreaTrigger(p, guid); + break; + case HighGuid.Conversation: + if (typemask.HasAnyFlag(TypeMask.Conversation)) + return GetConversation(p, guid); + break; + case HighGuid.Corpse: + break; + } + + return null; + } + + public static Corpse GetCorpse(WorldObject u, ObjectGuid guid) + { + return u.GetMap().GetCorpse(guid); + } + + public static GameObject GetGameObject(WorldObject u, ObjectGuid guid) + { + return u.GetMap().GetGameObject(guid); + } + + static Transport GetTransportOnMap(WorldObject u, ObjectGuid guid) + { + return u.GetMap().GetTransport(guid); + } + + Transport GetTransport(ObjectGuid guid) + { + return _transports.LookupByKey(guid); + } + + static DynamicObject GetDynamicObject(WorldObject u, ObjectGuid guid) + { + return u.GetMap().GetDynamicObject(guid); + } + + static AreaTrigger GetAreaTrigger(WorldObject u, ObjectGuid guid) + { + return u.GetMap().GetAreaTrigger(guid); + } + + static Conversation GetConversation(WorldObject u, ObjectGuid guid) + { + return u.GetMap().GetConversation(guid); + } + + public Unit GetUnit(WorldObject u, ObjectGuid guid) + { + if (guid.IsPlayer()) + return GetPlayer(u, guid); + + if (guid.IsPet()) + return GetPet(u, guid); + + return GetCreature(u, guid); + } + + public static Creature GetCreature(WorldObject u, ObjectGuid guid) + { + return u.GetMap().GetCreature(guid); + } + + public static Pet GetPet(WorldObject u, ObjectGuid guid) + { + return u.GetMap().GetPet(guid); + } + + public Player GetPlayer(Map m, ObjectGuid guid) + { + Player player = _players.LookupByKey(guid); + if (player) + if (player.IsInWorld && player.GetMap() == m) + return player; + + return null; + } + + public Player GetPlayer(WorldObject u, ObjectGuid guid) + { + return GetPlayer(u.GetMap(), guid); + } + + public static Creature GetCreatureOrPetOrVehicle(WorldObject u, ObjectGuid guid) + { + if (guid.IsPet()) + return GetPet(u, guid); + + if (guid.IsCreatureOrVehicle()) + return GetCreature(u, guid); + + return null; + } + + // these functions return objects if found in whole world + // ACCESS LIKE THAT IS NOT THREAD SAFE + public Player FindPlayer(ObjectGuid guid) + { + Player player = FindConnectedPlayer(guid); + return player && player.IsInWorld ? player : null; + } + public Player FindPlayerByName(string name) + { + Player player = PlayerNameMapHolder.Find(name); + if (!player || !player.IsInWorld) + return null; + + return player; + } + + // this returns Player even if he is not in world, for example teleporting + public Player FindConnectedPlayer(ObjectGuid guid) + { + return _players.LookupByKey(guid); + } + public Player FindConnectedPlayerByName(string name) + { + return PlayerNameMapHolder.Find(name); + } + + public Transport FindTransport(ObjectGuid guid) + { + return _transports.LookupByKey(guid); + } + + public void SaveAllPlayers() + { + foreach (var pl in GetPlayers()) + pl.SaveToDB(); + } + + public ICollection GetPlayers() + { + return _players.Values; + } + + public void AddObject(Player obj) + { + PlayerNameMapHolder.Insert(obj); + _players.TryAdd(obj.GetGUID(), obj); + } + public void AddObject(Transport obj) + { + _transports.TryAdd(obj.GetGUID(), obj); + } + + public void RemoveObject(Player obj) + { + Player player; + PlayerNameMapHolder.Remove(obj); + _players.TryRemove(obj.GetGUID(), out player); + } + public void RemoveObject(Transport obj) + { + Transport transport; + _transports.TryRemove(obj.GetGUID(), out transport); + } + + ConcurrentDictionary _players = new ConcurrentDictionary(); + ConcurrentDictionary _transports = new ConcurrentDictionary(); +} + +class PlayerNameMapHolder +{ + public static void Insert(Player p) + { + _playerNameMap[p.GetName()] = p; + } + + public static void Remove(Player p) + { + _playerNameMap.Remove(p.GetName()); + } + + public static Player Find(string name) + { + if (!ObjectManager.NormalizePlayerName(ref name)) + return null; + + return _playerNameMap.LookupByKey(name); + } + + static Dictionary _playerNameMap = new Dictionary(); +} diff --git a/Game/Globals/ObjectManager.cs b/Game/Globals/ObjectManager.cs new file mode 100644 index 000000000..499b6be7a --- /dev/null +++ b/Game/Globals/ObjectManager.cs @@ -0,0 +1,9891 @@ +/* + * Copyright (C) 2012-2017 CypherCore + * + * 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, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +using Framework.Collections; +using Framework.Constants; +using Framework.Database; +using Framework.GameMath; +using Game.Conditions; +using Game.DataStorage; +using Game.Entities; +using Game.Mails; +using Game.Maps; +using Game.Misc; +using Game.Scripting; +using Game.Spells; +using System; +using System.Collections.Generic; +using System.Diagnostics.Contracts; +using System.Linq; +using System.Runtime.InteropServices; + +namespace Game +{ + public sealed class ObjectManager : Singleton + { + ObjectManager() + { + for (var i = 0; i < SharedConst.MaxCreatureDifficulties; ++i) + { + _difficultyEntries[i] = new List(); + _hasDifficultyEntries[i] = new List(); + } + + lang_description = new LanguageDesc[] + { + new LanguageDesc(Language.Addon, 0, 0 ), + new LanguageDesc(Language.Universal, 0, 0 ), + new LanguageDesc(Language.Orcish, 669, SkillType.LangOrcish ), + new LanguageDesc(Language.Darnassian, 671, SkillType.LangDarnassian ), + new LanguageDesc(Language.Taurahe, 670, SkillType.LangTaurahe ), + new LanguageDesc(Language.Dwarvish, 672, SkillType.LangDwarven ), + new LanguageDesc(Language.Common, 668, SkillType.LangCommon ), + new LanguageDesc(Language.Demonic, 815, SkillType.LangDemonTongue ), + new LanguageDesc(Language.Titan, 816, SkillType.LangTitan ), + new LanguageDesc(Language.Thalassian, 813, SkillType.LangThalassian ), + new LanguageDesc(Language.Draconic, 814, SkillType.LangDraconic ), + new LanguageDesc(Language.Kalimag, 817, SkillType.LangOldTongue ), + new LanguageDesc(Language.Gnomish, 7340, SkillType.LangGnomish ), + new LanguageDesc(Language.Troll, 7341, SkillType.LangTroll ), + new LanguageDesc(Language.Gutterspeak, 17737, SkillType.LangForsaken ), + new LanguageDesc(Language.Draenei, 29932, SkillType.LangDraenei ), + new LanguageDesc(Language.Zombie, 0, 0 ), + new LanguageDesc(Language.GnomishBinary, 0, 0 ), + new LanguageDesc(Language.GoblinBinary, 0, 0 ), + new LanguageDesc(Language.Worgen, 69270, SkillType.LangGilnean ), + new LanguageDesc(Language.Goblin, 69269, SkillType.LangGoblin ), + new LanguageDesc(Language.PandarenNeutral, 108127, SkillType.LangPandarenNeutral ), + new LanguageDesc(Language.PandarenAlliance, 108130, SkillType.LangPandarenAlliance ), + new LanguageDesc(Language.PandarenHorde, 108131, SkillType.LangPandarenHorde ), + }; + } + + //Static Methods + public static bool NormalizePlayerName(ref string name) + { + if (name.IsEmpty()) + return false; + + //CultureInfo cultureInfo = Thread.CurrentThread.CurrentCulture; + //TextInfo textInfo = cultureInfo.TextInfo; + + //str = textInfo.ToTitleCase(str); + + name = name.ToLower(); + + var charArray = name.ToCharArray(); + charArray[0] = char.ToUpper(charArray[0]); + + name = new string(charArray); + return true; + } + public static ExtendedPlayerName ExtractExtendedPlayerName(string name) + { + int pos = name.IndexOf('-'); + if (pos != -1) + return new ExtendedPlayerName(name.Substring(0, pos), name.Substring(pos + 1)); + else + return new ExtendedPlayerName(name, ""); + } + public static LanguageDesc GetLanguageDescByID(Language lang) + { + for (byte i = 0; i < lang_description.Length; ++i) + { + if (lang_description[i].lang_id == lang) + return lang_description[i]; + } + + return null; + } + static LanguageType GetRealmLanguageType(bool create) + { + switch ((RealmZones)WorldConfig.GetIntValue(WorldCfg.RealmZone)) + { + case RealmZones.Unknown: // any language + case RealmZones.Development: + case RealmZones.TestServer: + case RealmZones.QaServer: + return LanguageType.Any; + case RealmZones.UnitedStates: // extended-Latin + case RealmZones.Oceanic: + case RealmZones.LatinAmerica: + case RealmZones.English: + case RealmZones.German: + case RealmZones.French: + case RealmZones.Spanish: + return LanguageType.ExtendenLatin; + case RealmZones.Korea: // East-Asian + case RealmZones.Taiwan: + case RealmZones.China: + return LanguageType.EastAsia; + case RealmZones.Russian: // Cyrillic + return LanguageType.Cyrillic; + default: + return create ? LanguageType.BasicLatin : LanguageType.Any; // basic-Latin at create, any at login + } + } + + public static uint ChooseDisplayId(CreatureTemplate cinfo, CreatureData data = null) + { + // Load creature model (display id) + if (data != null && data.displayid != 0) + return data.displayid; + + if (!cinfo.FlagsExtra.HasAnyFlag(CreatureFlagsExtra.Trigger)) + return cinfo.GetRandomValidModelId(); + + // Triggers by default receive the invisible model + return cinfo.GetFirstInvisibleModel(); + } + + public static void ChooseCreatureFlags(CreatureTemplate cInfo, out ulong npcFlag, out uint unitFlags, out uint unitFlags2, out uint unitFlags3, out uint dynamicFlags, CreatureData data = null) + { + npcFlag = (ulong)cInfo.Npcflag; + unitFlags = (uint)cInfo.UnitFlags; + unitFlags2 = cInfo.UnitFlags2; + unitFlags3 = cInfo.UnitFlags3; + dynamicFlags = cInfo.DynamicFlags; + + if (data != null) + { + if (data.npcflag != 0) + npcFlag = data.npcflag; + + if (data.unit_flags != 0) + unitFlags = data.unit_flags; + + if (data.unit_flags2 != 0) + unitFlags2 = data.unit_flags2; + + if (data.unit_flags3 != 0) + unitFlags3 = data.unit_flags3; + + if (data.dynamicflags != 0) + dynamicFlags = data.dynamicflags; + } + } + public static ObjectGuid GetPlayerGUIDByName(string name) + { + PreparedStatement stmt = DB.Characters.GetPreparedStatement(CharStatements.SEL_GUID_BY_NAME); + stmt.AddValue(0, name); + SQLResult result = DB.Characters.Query(stmt); + + if (!result.IsEmpty()) + return ObjectGuid.Create(HighGuid.Player, result.Read(0)); + + return ObjectGuid.Empty; + } + public static bool GetPlayerNameByGUID(ObjectGuid guid, out string name) + { + name = ""; + + Player player = Global.ObjAccessor.FindConnectedPlayer(guid); + if (player) + { + name = player.GetName(); + return true; + } + + CharacterInfo characterInfo = Global.WorldMgr.GetCharacterInfo(guid); + if (characterInfo == null) + return false; + + name = characterInfo.Name; + return true; + } + public static bool GetPlayerNameAndClassByGUID(ObjectGuid guid, out string name, out byte _class) + { + name = ""; + _class = 0; + + Player player = Global.ObjAccessor.FindConnectedPlayer(guid); + if (player) + { + name = player.GetName(); + _class = (byte)player.GetClass(); + return true; + } + + CharacterInfo characterInfo = Global.WorldMgr.GetCharacterInfo(guid); + if (characterInfo != null) + { + name = characterInfo.Name; + _class = (byte)characterInfo.ClassID; + return true; + } + + return false; + } + public static Team GetPlayerTeamByGUID(ObjectGuid guid) + { + CharacterInfo characterInfo = Global.WorldMgr.GetCharacterInfo(guid); + if (characterInfo != null) + return Player.TeamForRace(characterInfo.RaceID); + + return 0; + } + public static uint GetPlayerAccountIdByGUID(ObjectGuid guid) + { + CharacterInfo characterInfo = Global.WorldMgr.GetCharacterInfo(guid); + if (characterInfo != null) + return characterInfo.AccountId; + + return 0; + } + + public static ResponseCodes CheckPlayerName(string name, LocaleConstant locale, bool create = false) + { + if (name.Length > 12) + return ResponseCodes.CharNameTooLong; + + uint minName = WorldConfig.GetUIntValue(WorldCfg.MinPlayerName); + if (name.Length < minName) + return ResponseCodes.CharNameTooShort; + + uint strictMask = WorldConfig.GetUIntValue(WorldCfg.StrictPlayerNames); + if (!IsValidString(name, strictMask, false, create)) + return ResponseCodes.CharNameMixedLanguages; + + name = name.ToLower(); + for (int i = 2; i < name.Length; ++i) + if (name[i] == name[i - 1] && name[i] == name[i - 2]) + return ResponseCodes.CharNameThreeConsecutive; + + return Global.DB2Mgr.ValidateName(name, locale); + } + public static PetNameInvalidReason CheckPetName(string name) + { + if (name.Length > 12) + return PetNameInvalidReason.TooLong; + + uint minName = WorldConfig.GetUIntValue(WorldCfg.MinPetName); + if (name.Length < minName) + return PetNameInvalidReason.TooShort; + + uint strictMask = WorldConfig.GetUIntValue(WorldCfg.StrictPetNames); + if (!IsValidString(name, strictMask, false)) + return PetNameInvalidReason.MixedLanguages; + + return PetNameInvalidReason.Success; + } + public static bool IsValidCharterName(string name) + { + if (name.Length > 24) + return false; + + uint minName = WorldConfig.GetUIntValue(WorldCfg.MinCharterName); + if (name.Length < minName) + return false; + + uint strictMask = WorldConfig.GetUIntValue(WorldCfg.StrictCharterNames); + + return IsValidString(name, strictMask, true); + } + public static void AddLocaleString(string value, LocaleConstant locale, StringArray data) + { + if (!string.IsNullOrEmpty(value)) + data[(int)locale] = value; + } + public static void GetLocaleString(StringArray data, LocaleConstant locale, ref string value) + { + if (data.Length > (int)locale && !string.IsNullOrEmpty(data[(int)locale])) + value = data[(int)locale]; + } + + static bool IsValidString(string str, uint strictMask, bool numericOrSpace, bool create = false) + { + if (strictMask == 0) // any language, ignore realm + { + if (IsCultureString(LanguageType.BasicLatin, str, numericOrSpace)) + return true; + if (IsCultureString(LanguageType.ExtendenLatin, str, numericOrSpace)) + return true; + if (IsCultureString(LanguageType.Cyrillic, str, numericOrSpace)) + return true; + if (IsCultureString(LanguageType.EastAsia, str, numericOrSpace)) + return true; + return false; + } + + if (Convert.ToBoolean(strictMask & 0x2)) // realm zone specific + { + LanguageType lt = GetRealmLanguageType(create); + if (lt.HasAnyFlag(LanguageType.ExtendenLatin)) + { + if (IsCultureString(LanguageType.BasicLatin, str, numericOrSpace)) + return true; + if (IsCultureString(LanguageType.ExtendenLatin, str, numericOrSpace)) + return true; + } + if (lt.HasAnyFlag(LanguageType.Cyrillic)) + if (IsCultureString(LanguageType.Cyrillic, str, numericOrSpace)) + return true; + if (lt.HasAnyFlag(LanguageType.EastAsia)) + if (IsCultureString(LanguageType.EastAsia, str, numericOrSpace)) + return true; + } + + if (Convert.ToBoolean(strictMask & 0x1)) // basic Latin + { + if (IsCultureString(LanguageType.BasicLatin, str, numericOrSpace)) + return true; + } + + return false; + } + static bool IsCultureString(LanguageType culture, string str, bool numericOrSpace) + { + foreach (var wchar in str) + { + if (numericOrSpace && (char.IsNumber(wchar) || char.IsWhiteSpace(wchar))) + return true; + + switch (culture) + { + case LanguageType.BasicLatin: + if (wchar >= 'a' && wchar <= 'z') // LATIN SMALL LETTER A - LATIN SMALL LETTER Z + return true; + if (wchar >= 'A' && wchar <= 'Z') // LATIN CAPITAL LETTER A - LATIN CAPITAL LETTER Z + return true; + return false; + case LanguageType.ExtendenLatin: + if (wchar >= 0x00C0 && wchar <= 0x00D6) // LATIN CAPITAL LETTER A WITH GRAVE - LATIN CAPITAL LETTER O WITH DIAERESIS + return true; + if (wchar >= 0x00D8 && wchar <= 0x00DE) // LATIN CAPITAL LETTER O WITH STROKE - LATIN CAPITAL LETTER THORN + return true; + if (wchar == 0x00DF) // LATIN SMALL LETTER SHARP S + return true; + if (wchar >= 0x00E0 && wchar <= 0x00F6) // LATIN SMALL LETTER A WITH GRAVE - LATIN SMALL LETTER O WITH DIAERESIS + return true; + if (wchar >= 0x00F8 && wchar <= 0x00FE) // LATIN SMALL LETTER O WITH STROKE - LATIN SMALL LETTER THORN + return true; + if (wchar >= 0x0100 && wchar <= 0x012F) // LATIN CAPITAL LETTER A WITH MACRON - LATIN SMALL LETTER I WITH OGONEK + return true; + if (wchar == 0x1E9E) // LATIN CAPITAL LETTER SHARP S + return true; + return false; + case LanguageType.Cyrillic: + if (wchar >= 0x0410 && wchar <= 0x044F) // CYRILLIC CAPITAL LETTER A - CYRILLIC SMALL LETTER YA + return true; + if (wchar == 0x0401 || wchar == 0x0451) // CYRILLIC CAPITAL LETTER IO, CYRILLIC SMALL LETTER IO + return true; + return false; + case LanguageType.EastAsia: + if (wchar >= 0x1100 && wchar <= 0x11F9) // Hangul Jamo + return true; + if (wchar >= 0x3041 && wchar <= 0x30FF) // Hiragana + Katakana + return true; + if (wchar >= 0x3131 && wchar <= 0x318E) // Hangul Compatibility Jamo + return true; + if (wchar >= 0x31F0 && wchar <= 0x31FF) // Katakana Phonetic Ext. + return true; + if (wchar >= 0x3400 && wchar <= 0x4DB5) // CJK Ideographs Ext. A + return true; + if (wchar >= 0x4E00 && wchar <= 0x9FC3) // Unified CJK Ideographs + return true; + if (wchar >= 0xAC00 && wchar <= 0xD7A3) // Hangul Syllables + return true; + if (wchar >= 0xFF01 && wchar <= 0xFFEE) // Halfwidth forms + return true; + return false; + } + } + + return false; + } + + //General + public void LoadCypherStrings() + { + var time = Time.GetMSTime(); + CypherStringStorage.Clear(); + + SQLResult result = DB.World.Query("SELECT entry, content_default, content_loc1, content_loc2, content_loc3, content_loc4, content_loc5, content_loc6, content_loc7, content_loc8 FROM trinity_string"); + if (result.IsEmpty()) + { + Log.outError(LogFilter.ServerLoading, "Loaded 0 CypherStrings. DB table `trinity_string` is empty."); + Global.WorldMgr.StopNow(); + return; + } + uint count = 0; + do + { + uint entry = result.Read(0); + + CypherStringStorage[entry] = new StringArray((int)SharedConst.DefaultLocale + 1); + count++; + + for (var i = SharedConst.DefaultLocale; i >= 0; --i) + AddLocaleString(result.Read((int)i + 1).ConvertFormatSyntax(), i, CypherStringStorage[entry]); + } + while (result.NextRow()); + + Log.outInfo(LogFilter.ServerLoading, "Loaded {0} CypherStrings in {1} ms", count, Time.GetMSTimeDiffToNow(time)); + } + + public void LoadRaceAndClassExpansionRequirements() + { + uint oldMSTime = Time.GetMSTime(); + _raceExpansionRequirementStorage.Clear(); + + // 0 1 + SQLResult result = DB.World.Query("SELECT raceID, expansion FROM `race_expansion_requirement`"); + if (!result.IsEmpty()) + { + uint count = 0; + do + { + byte raceID = result.Read(0); + byte expansion = result.Read(1); + + ChrRacesRecord raceEntry = CliDB.ChrRacesStorage.LookupByKey(raceID); + if (raceEntry == null) + { + Log.outError(LogFilter.Sql, "Race {0} defined in `race_expansion_requirement` does not exists, skipped.", raceID); + continue; + } + + if (expansion >= (int)Expansion.Max) + { + Log.outError(LogFilter.Sql, "Race {0} defined in `race_expansion_requirement` has incorrect expansion {1}, skipped.", raceID, expansion); + continue; + } + + _raceExpansionRequirementStorage[raceID] = expansion; + + ++count; + } + while (result.NextRow()); + Log.outInfo(LogFilter.ServerLoading, "Loaded {0} race expansion requirements in {1} ms.", count, Time.GetMSTimeDiffToNow(oldMSTime)); + } + else + Log.outInfo(LogFilter.ServerLoading, "Loaded 0 race expansion requirements. DB table `race_expansion_requirement` is empty."); + + oldMSTime = Time.GetMSTime(); + _classExpansionRequirementStorage.Clear(); + + // 0 1 + result = DB.World.Query("SELECT classID, expansion FROM `class_expansion_requirement`"); + if (!result.IsEmpty()) + { + uint count = 0; + do + { + byte classID = result.Read(0); + byte expansion = result.Read(1); + + ChrClassesRecord classEntry = CliDB.ChrClassesStorage.LookupByKey(classID); + if (classEntry == null) + { + Log.outError(LogFilter.Sql, "Class {0} defined in `class_expansion_requirement` does not exists, skipped.", classID); + continue; + } + + if (expansion >= (int)Expansion.Max) + { + Log.outError(LogFilter.Sql, "Class {0} defined in `class_expansion_requirement` has incorrect expansion {1}, skipped.", classID, expansion); + continue; + } + + _classExpansionRequirementStorage[classID] = expansion; + + ++count; + } + while (result.NextRow()); + Log.outInfo(LogFilter.ServerLoading, "Loaded {0} class expansion requirements in {1} ms.", count, Time.GetMSTimeDiffToNow(oldMSTime)); + } + else + Log.outInfo(LogFilter.ServerLoading, "Loaded 0 class expansion requirements. DB table `class_expansion_requirement` is empty."); + } + public void LoadRealmNames() + { + uint oldMSTime = Time.GetMSTime(); + _realmNameStorage.Clear(); + + // 0 1 + SQLResult result = DB.Login.Query("SELECT id, name FROM `realmlist`"); + if (result.IsEmpty()) + { + Log.outInfo(LogFilter.ServerLoading, "Loaded 0 realm names. DB table `realmlist` is empty."); + return; + } + + uint count = 0; + do + { + uint realm = result.Read(0); + string realmName = result.Read(1); + + _realmNameStorage[realm] = realmName; + + ++count; + } + while (result.NextRow()); + Log.outInfo(LogFilter.ServerLoading, "Loaded {0} realm names in {1} ms.", count, Time.GetMSTimeDiffToNow(oldMSTime)); + } + + public string GetCypherString(uint entry, LocaleConstant locale = LocaleConstant.enUS) + { + if (!CypherStringStorage.ContainsKey(entry)) + { + Log.outError(LogFilter.Sql, "Cypher string entry {0} not found in DB.", entry); + return ""; + } + + var cs = CypherStringStorage[entry]; + if (cs.Length > (int)locale && !string.IsNullOrEmpty(cs[(int)locale])) + return cs[(int)locale]; + + return cs[(int)SharedConst.DefaultLocale]; + } + public string GetCypherString(CypherStrings cmd, LocaleConstant locale = LocaleConstant.enUS) + { + return GetCypherString((uint)cmd, locale); + } + + public string GetRealmName(uint realm) + { + return _realmNameStorage.LookupByKey(realm); + } + public bool GetRealmName(uint realmId, ref string name, ref string normalizedName) + { + var realmName = _realmNameStorage.LookupByKey(realmId); + if (realmName != null) + { + name = realmName; + normalizedName = realmName.Normalize(); + return true; + } + return false; + } + public Dictionary GetRaceExpansionRequirements() { return _raceExpansionRequirementStorage; } + public Expansion GetRaceExpansionRequirement(Race race) + { + if (_raceExpansionRequirementStorage.ContainsKey((byte)race)) + return (Expansion)_raceExpansionRequirementStorage[(byte)race]; + return Expansion.Classic; + } + public Dictionary GetClassExpansionRequirements() { return _classExpansionRequirementStorage; } + public Expansion GetClassExpansionRequirement(Class class_) + { + var exp = _classExpansionRequirementStorage.LookupByKey(class_); + if (_classExpansionRequirementStorage.ContainsKey((byte)class_)) + return (Expansion)_classExpansionRequirementStorage[(byte)class_]; + return Expansion.Classic; + } + + //Gossip + public void LoadGossipMenu() + { + uint oldMSTime = Time.GetMSTime(); + + gossipMenusStorage.Clear(); + + SQLResult result = DB.World.Query("SELECT entry, text_id FROM gossip_menu"); + + if (result.IsEmpty()) + { + Log.outError(LogFilter.ServerLoading, "Loaded 0 gossip_menu entries. DB table `gossip_menu` is empty!"); + return; + } + + uint count = 0; + + do + { + GossipMenus gMenu = new GossipMenus(); + + gMenu.entry = result.Read(0); + gMenu.text_id = result.Read(1); + + if (GetNpcText(gMenu.text_id) == null) + { + Log.outError(LogFilter.Sql, "Table gossip_menu entry {0} are using non-existing textid {1}", gMenu.entry, gMenu.text_id); + continue; + } + + gossipMenusStorage.Add(gMenu.entry, gMenu); + + ++count; + } + while (result.NextRow()); + + Log.outInfo(LogFilter.ServerLoading, "Loaded {0} gossip_menu entries in {1} ms", count, Time.GetMSTimeDiffToNow(oldMSTime)); + } + public void LoadGossipMenuItems() + { + uint oldMSTime = Time.GetMSTime(); + + gossipMenuItemsStorage.Clear(); + + SQLResult result = DB.World.Query( + // 0 1 2 3 4 5 6 7 8 9 10 11 12 + "SELECT menu_id, id, option_icon, option_text, OptionBroadcastTextID, option_id, npc_option_npcflag, action_menu_id, action_poi_id, box_coded, box_money, box_text, BoxBroadcastTextID " + + "FROM gossip_menu_option ORDER BY menu_id, id"); + + if (result.IsEmpty()) + { + Log.outError(LogFilter.ServerLoading, "Loaded 0 gossip_menu_option entries. DB table `gossip_menu_option` is empty!"); + return; + } + + uint count = 0; + do + { + GossipMenuItems gMenuItem = new GossipMenuItems(); + + gMenuItem.MenuId = result.Read(0); + gMenuItem.OptionIndex = result.Read(1); + gMenuItem.OptionIcon = (GossipOptionIcon)result.Read(2); + gMenuItem.OptionText = result.Read(3); + gMenuItem.OptionBroadcastTextId = result.Read(4); + gMenuItem.OptionType = (GossipOption)result.Read(5); + gMenuItem.OptionNpcflag = (NPCFlags)result.Read(6); + gMenuItem.ActionMenuId = result.Read(7); + gMenuItem.ActionPoiId = result.Read(8); + gMenuItem.BoxCoded = result.Read(9); + gMenuItem.BoxMoney = result.Read(10); + gMenuItem.BoxText = result.Read(11); + gMenuItem.BoxBroadcastTextId = result.Read(12); + + if (gMenuItem.OptionIcon >= GossipOptionIcon.Max) + { + Log.outError(LogFilter.Sql, "Table gossip_menu_option for menu {0}, id {1} has unknown icon id {2}. Replacing with GOSSIPICONCHAT", gMenuItem.MenuId, gMenuItem.OptionIndex, gMenuItem.OptionIcon); + gMenuItem.OptionIcon = GossipOptionIcon.Chat; + } + + if (gMenuItem.OptionBroadcastTextId != 0) + { + if (!CliDB.BroadcastTextStorage.ContainsKey(gMenuItem.OptionBroadcastTextId)) + { + Log.outError(LogFilter.Sql, "Table `gossip_menu_option` for menu {0}, id {1} has non-existing or incompatible OptionBroadcastTextId {2}, ignoring.", gMenuItem.MenuId, gMenuItem.OptionIndex, gMenuItem.OptionBroadcastTextId); + gMenuItem.OptionBroadcastTextId = 0; + } + } + + if (gMenuItem.OptionType >= GossipOption.Max) + Log.outError(LogFilter.Sql, "Table gossip_menu_option for menu {0}, id {1} has unknown option id {2}. Option will not be used", gMenuItem.MenuId, gMenuItem.OptionIndex, gMenuItem.OptionType); + + if (gMenuItem.ActionPoiId != 0 && GetPointOfInterest(gMenuItem.ActionPoiId) == null) + { + Log.outError(LogFilter.Sql, "Table gossip_menu_option for menu {0}, id {1} use non-existing actionpoiid {2}, ignoring", gMenuItem.MenuId, gMenuItem.OptionIndex, gMenuItem.ActionPoiId); + gMenuItem.ActionPoiId = 0; + } + + if (gMenuItem.BoxBroadcastTextId != 0) + { + if (!CliDB.BroadcastTextStorage.ContainsKey(gMenuItem.BoxBroadcastTextId)) + { + Log.outError(LogFilter.Sql, "Table `gossip_menu_option` for menu {0}, id {1} has non-existing or incompatible BoxBroadcastTextId {2}, ignoring.", gMenuItem.MenuId, gMenuItem.OptionIndex, gMenuItem.BoxBroadcastTextId); + gMenuItem.BoxBroadcastTextId = 0; + } + } + + gossipMenuItemsStorage.Add(gMenuItem.MenuId, gMenuItem); + ++count; + + } while (result.NextRow()); + + Log.outInfo(LogFilter.ServerLoading, "Loaded {0} gossip_menu_option entries in {1} ms", count, Time.GetMSTimeDiffToNow(oldMSTime)); + } + public void LoadPointsOfInterest() + { + uint oldMSTime = Time.GetMSTime(); + + pointsOfInterestStorage.Clear(); // need for reload case + + // 0 1 2 3 4 5 6 + SQLResult result = DB.World.Query("SELECT ID, PositionX, PositionY, Icon, Flags, Importance, Name FROM points_of_interest"); + + if (result.IsEmpty()) + { + Log.outError(LogFilter.ServerLoading, "Loaded 0 Points of Interest definitions. DB table `points_of_interest` is empty."); + return; + } + + uint count = 0; + do + { + uint id = result.Read(0); + + PointOfInterest POI = new PointOfInterest(); + POI.ID = id; + POI.Pos = new Vector2(result.Read(1), result.Read(2)); + POI.Icon = result.Read(3); + POI.Flags = result.Read(4); + POI.Importance = result.Read(5); + POI.Name = result.Read(6); + + if (!GridDefines.IsValidMapCoord(POI.Pos.X, POI.Pos.Y)) + { + Log.outError(LogFilter.Sql, "Table `points_of_interest` (ID: {0}) have invalid coordinates (PositionX: {1} PositionY: {2}), ignored.", id, POI.Pos.X, POI.Pos.Y); + continue; + } + + pointsOfInterestStorage[id] = POI; + + ++count; + } while (result.NextRow()); + + Log.outInfo(LogFilter.ServerLoading, "Loaded {0} Points of Interest definitions in {1} ms", count, Time.GetMSTimeDiffToNow(oldMSTime)); + } + + public List GetGossipMenusMapBounds(uint uiMenuId) + { + return gossipMenusStorage.LookupByKey(uiMenuId); + } + public List GetGossipMenuItemsMapBounds(uint uiMenuId) + { + return gossipMenuItemsStorage.LookupByKey(uiMenuId); + } + public PointOfInterest GetPointOfInterest(uint id) + { + return pointsOfInterestStorage.LookupByKey(id); + } + + public void LoadGraveyardZones() + { + uint oldMSTime = Time.GetMSTime(); + + GraveYardStorage.Clear(); // need for reload case + + // 0 1 2 + SQLResult result = DB.World.Query("SELECT ID, GhostZone, faction FROM graveyard_zone"); + + if (result.IsEmpty()) + { + Log.outInfo(LogFilter.ServerLoading, "Loaded 0 graveyard-zone links. DB table `graveyard_zone` is empty."); + return; + } + + uint count = 0; + + do + { + ++count; + uint safeLocId = result.Read(0); + uint zoneId = result.Read(1); + Team team = (Team)result.Read(2); + + WorldSafeLocsRecord entry = CliDB.WorldSafeLocsStorage.LookupByKey(safeLocId); + if (entry == null) + { + Log.outError(LogFilter.Sql, "Table `graveyard_zone` has a record for not existing graveyard (WorldSafeLocs.dbc id) {0}, skipped.", safeLocId); + continue; + } + + AreaTableRecord areaEntry = CliDB.AreaTableStorage.LookupByKey(zoneId); + if (areaEntry == null) + { + Log.outError(LogFilter.Sql, "Table `graveyard_zone` has a record for not existing zone id ({0}), skipped.", zoneId); + continue; + } + + if (team != 0 && team != Team.Horde && team != Team.Alliance) + { + Log.outError(LogFilter.Sql, "Table `graveyard_zone` has a record for non player faction ({0}), skipped.", team); + continue; + } + + if (!AddGraveYardLink(safeLocId, zoneId, team, false)) + Log.outError(LogFilter.Sql, "Table `graveyard_zone` has a duplicate record for Graveyard (ID: {0}) and Zone (ID: {1}), skipped.", safeLocId, zoneId); + } while (result.NextRow()); + + Log.outInfo(LogFilter.ServerLoading, "Loaded {0} graveyard-zone links in {1} ms", count, Time.GetMSTimeDiffToNow(oldMSTime)); + } + + WorldSafeLocsRecord GetDefaultGraveYard(Team team) + { + if (team == Team.Horde) + return CliDB.WorldSafeLocsStorage.LookupByKey(10); + else if (team == Team.Alliance) + return CliDB.WorldSafeLocsStorage.LookupByKey(4); + else return null; + } + public WorldSafeLocsRecord GetClosestGraveYard(float x, float y, float z, uint MapId, Team team) + { + // search for zone associated closest graveyard + uint zoneId = Global.MapMgr.GetZoneId(MapId, x, y, z); + if (zoneId == 0) + { + if (z > -500) + { + Log.outError(LogFilter.Server, "ZoneId not found for map {0} coords ({1}, {2}, {3})", MapId, x, y, z); + return GetDefaultGraveYard(team); + } + } + + // Simulate std. algorithm: + // found some graveyard associated to (ghost_zone, ghost_map) + // + // if mapId == graveyard.mapId (ghost in plain zone or city or Battleground) and search graveyard at same map + // then check faction + // if mapId != graveyard.mapId (ghost in instance) and search any graveyard associated + // then check faction + var range = GraveYardStorage.LookupByKey(zoneId); + MapRecord mapEntry = CliDB.MapStorage.LookupByKey(MapId); + + // not need to check validity of map object; MapId _MUST_ be valid here + if (range.Empty() && !mapEntry.IsBattlegroundOrArena()) + { + if (zoneId != 0) // zone == 0 can't be fixed, used by bliz for bugged zones + Log.outError(LogFilter.Sql, "Table `game_graveyard_zone` incomplete: Zone {0} Team {1} does not have a linked graveyard.", zoneId, team); + return GetDefaultGraveYard(team); + } + + // at corpse map + bool foundNear = false; + float distNear = 10000; + WorldSafeLocsRecord entryNear = null; + + // at entrance map for corpse map + bool foundEntr = false; + float distEntr = 10000; + WorldSafeLocsRecord entryEntr = null; + + // some where other + WorldSafeLocsRecord entryFar = null; + + foreach (var data in range) + { + WorldSafeLocsRecord entry = CliDB.WorldSafeLocsStorage.LookupByKey(data.safeLocId); + if (entry == null) + { + Log.outError(LogFilter.Sql, "Table `game_graveyard_zone` has record for not existing graveyard (WorldSafeLocs.dbc id) {0}, skipped.", data.safeLocId); + continue; + } + + // skip enemy faction graveyard + // team == 0 case can be at call from .neargrave + if (data.team != 0 && team != 0 && data.team != (uint)team) + continue; + + // find now nearest graveyard at other map + if (MapId != entry.MapID) + { + // if find graveyard at different map from where entrance placed (or no entrance data), use any first + if (mapEntry == null + || mapEntry.CorpseMapID < 0 + || mapEntry.CorpseMapID != entry.MapID + || (mapEntry.CorpsePos.X == 0 && mapEntry.CorpsePos.Y == 0)) + { + // not have any corrdinates for check distance anyway + entryFar = entry; + continue; + } + + // at entrance map calculate distance (2D); + float dist2 = (entry.Loc.X - mapEntry.CorpsePos.X) * (entry.Loc.X - mapEntry.CorpsePos.X) + + (entry.Loc.Y - mapEntry.CorpsePos.Y) * (entry.Loc.Y - mapEntry.CorpsePos.Y); + if (foundEntr) + { + if (dist2 < distEntr) + { + distEntr = dist2; + entryEntr = entry; + } + } + else + { + foundEntr = true; + distEntr = dist2; + entryEntr = entry; + } + } + // find now nearest graveyard at same map + else + { + float dist2 = (entry.Loc.X - x) * (entry.Loc.X - x) + (entry.Loc.Y - y) * (entry.Loc.Y - y) + (entry.Loc.Z - z) * (entry.Loc.Z - z); + if (foundNear) + { + if (dist2 < distNear) + { + distNear = dist2; + entryNear = entry; + } + } + else + { + foundNear = true; + distNear = dist2; + entryNear = entry; + } + } + } + + if (entryNear != null) + return entryNear; + + if (entryEntr != null) + return entryEntr; + + return entryFar; + } + public GraveYardData FindGraveYardData(uint id, uint zoneId) + { + var range = GraveYardStorage.LookupByKey(zoneId); + foreach (var data in range) + { + if (data.safeLocId == id) + return data; + } + return null; + } + + public bool AddGraveYardLink(uint id, uint zoneId, Team team, bool persist = true) + { + if (FindGraveYardData(id, zoneId) != null) + return false; + + // add link to loaded data + GraveYardData data = new GraveYardData(); + data.safeLocId = id; + data.team = (uint)team; + + GraveYardStorage.Add(zoneId, data); + + // add link to DB + if (persist) + { + PreparedStatement stmt = DB.World.GetPreparedStatement(WorldStatements.INS_GRAVEYARD_ZONE); + + stmt.AddValue(0, id); + stmt.AddValue(1, zoneId); + stmt.AddValue(2, team); + + DB.World.Execute(stmt); + } + + return true; + } + public void RemoveGraveYardLink(uint id, uint zoneId, Team team, bool persist = false) + { + var range = GraveYardStorage.LookupByKey(zoneId); + if (range.Empty()) + { + Log.outError(LogFilter.Sql, "Table `game_graveyard_zone` incomplete: Zone {0} Team {1} does not have a linked graveyard.", zoneId, team); + return; + } + + bool found = false; + + + foreach (var data in range) + { + // skip not matching safezone id + if (data.safeLocId != id) + continue; + + // skip enemy faction graveyard at same map (normal area, city, or Battleground) + // team == 0 case can be at call from .neargrave + if (data.team != 0 && team != 0 && data.team != (uint)team) + continue; + + found = true; + break; + } + + // no match, return + if (!found) + return; + + // remove from links + GraveYardStorage.Remove(zoneId); + + // remove link from DB + if (persist) + { + PreparedStatement stmt = DB.World.GetPreparedStatement(WorldStatements.DEL_GRAVEYARD_ZONE); + + stmt.AddValue(0, id); + stmt.AddValue(1, zoneId); + stmt.AddValue(2, team); + + DB.World.Execute(stmt); + } + } + + //Scripts + public void LoadScriptNames() + { + uint oldMSTime = Time.GetMSTime(); + + scriptNamesStorage.Add(""); + SQLResult result = DB.World.Query( + "SELECT DISTINCT(ScriptName) FROM Battleground_template WHERE ScriptName <> '' " + + "UNION SELECT DISTINCT(ScriptName) FROM creature WHERE ScriptName <> '' " + + "UNION SELECT DISTINCT(ScriptName) FROM creature_template WHERE ScriptName <> '' " + + "UNION SELECT DISTINCT(ScriptName) FROM criteria_data WHERE ScriptName <> '' AND type = 11 " + + "UNION SELECT DISTINCT(ScriptName) FROM gameobject WHERE ScriptName <> '' " + + "UNION SELECT DISTINCT(ScriptName) FROM gameobject_template WHERE ScriptName <> '' " + + "UNION SELECT DISTINCT(ScriptName) FROM item_script_names WHERE ScriptName <> '' " + + "UNION SELECT DISTINCT(ScriptName) FROM areatrigger_scripts WHERE ScriptName <> '' " + + "UNION SELECT DISTINCT(ScriptName) FROM areatrigger_template WHERE ScriptName <> '' " + + "UNION SELECT DISTINCT(ScriptName) FROM spell_script_names WHERE ScriptName <> '' " + + "UNION SELECT DISTINCT(ScriptName) FROM transports WHERE ScriptName <> '' " + + "UNION SELECT DISTINCT(ScriptName) FROM game_weather WHERE ScriptName <> '' " + + "UNION SELECT DISTINCT(ScriptName) FROM conditions WHERE ScriptName <> '' " + + "UNION SELECT DISTINCT(ScriptName) FROM outdoorpvp_template WHERE ScriptName <> '' " + + "UNION SELECT DISTINCT(ScriptName) FROM scene_template WHERE ScriptName <> '' " + + "UNION SELECT DISTINCT(script) FROM instance_template WHERE script <> ''"); + + if (result.IsEmpty()) + { + Log.outError(LogFilter.ServerLoading, "Loaded empty set of Script Names!"); + return; + } + + uint count = 1; + do + { + scriptNamesStorage.Add(result.Read(0)); + ++count; + } + while (result.NextRow()); + + scriptNamesStorage.Sort(); + Log.outInfo(LogFilter.ServerLoading, "Loaded {0} Script Names in {1} ms", count, Time.GetMSTimeDiffToNow(oldMSTime)); + } + public void LoadAreaTriggerScripts() + { + uint oldMSTime = Time.GetMSTime(); + + areaTriggerScriptStorage.Clear(); // need for reload case + SQLResult result = DB.World.Query("SELECT entry, ScriptName FROM areatrigger_scripts"); + + if (result.IsEmpty()) + { + Log.outInfo(LogFilter.ServerLoading, "Loaded 0 areatrigger scripts. DB table `areatrigger_scripts` is empty."); + return; + } + uint count = 0; + do + { + uint id = result.Read(0); + string scriptName = result.Read(1); + + AreaTriggerRecord atEntry = CliDB.AreaTriggerStorage.LookupByKey(id); + if (atEntry == null) + { + Log.outError(LogFilter.Sql, "Area trigger (Id:{0}) does not exist in `AreaTrigger.dbc`.", id); + continue; + } + ++count; + areaTriggerScriptStorage[id] = GetScriptId(scriptName); + } while (result.NextRow()); + + Log.outInfo(LogFilter.ServerLoading, "Loaded {0} areatrigger scripts in {1} ms", count, Time.GetMSTimeDiffToNow(oldMSTime)); + } + void LoadScripts(ScriptsType type) + { + uint oldMSTime = Time.GetMSTime(); + + var scripts = GetScriptsMapByType(type); + if (scripts == null) + return; + + string tableName = GetScriptsTableNameByType(type); + if (string.IsNullOrEmpty(tableName)) + return; + + if (Global.MapMgr.IsScriptScheduled()) // function cannot be called when scripts are in use. + return; + + Log.outInfo(LogFilter.ServerLoading, "Loading {0}...", tableName); + + scripts.Clear(); // need for reload support + + bool isSpellScriptTable = (type == ScriptsType.Spell); + // 0 1 2 3 4 5 6 7 8 9 + SQLResult result = DB.World.Query("SELECT id, delay, command, datalong, datalong2, dataint, x, y, z, o{0} FROM {1}", isSpellScriptTable ? ", effIndex" : "", tableName); + if (result.IsEmpty()) + { + Log.outError(LogFilter.ServerLoading, "Loaded 0 script definitions. DB table `{0}` is empty!", tableName); + return; + } + + uint count = 0; + do + { + ScriptInfo tmp = new ScriptInfo(); + tmp.type = type; + tmp.id = result.Read(0); + if (isSpellScriptTable) + tmp.id |= result.Read(10) << 24; + tmp.delay = result.Read(1); + tmp.command = (ScriptCommands)result.Read(2); + unsafe + { + tmp.Raw.nData[0] = result.Read(3); + tmp.Raw.nData[1] = result.Read(4); + tmp.Raw.nData[2] = (uint)result.Read(5); + tmp.Raw.fData[0] = result.Read(6); + tmp.Raw.fData[1] = result.Read(7); + tmp.Raw.fData[2] = result.Read(8); + tmp.Raw.fData[3] = result.Read(9); + } + + // generic command args check + switch (tmp.command) + { + case ScriptCommands.Talk: + { + if (tmp.Talk.ChatType > ChatMsg.RaidBossWhisper) + { + Log.outError(LogFilter.Sql, "Table `{0}` has invalid talk type (datalong = {1}) in SCRIPT_COMMAND_TALK for script id {2}", + tableName, tmp.Talk.ChatType, tmp.id); + continue; + } + if (!CliDB.BroadcastTextStorage.ContainsKey((uint)tmp.Talk.TextID)) + { + Log.outError(LogFilter.Sql, "Table `{0}` has invalid talk text id (dataint = {1}) in SCRIPT_COMMAND_TALK for script id {2}", + tableName, tmp.Talk.TextID, tmp.id); + continue; + } + break; + } + + case ScriptCommands.Emote: + { + if (!CliDB.EmotesStorage.ContainsKey(tmp.Emote.EmoteID)) + { + Log.outError(LogFilter.Sql, "Table `{0}` has invalid emote id (datalong = {1}) in SCRIPT_COMMAND_EMOTE for script id {2}", + tableName, tmp.Emote.EmoteID, tmp.id); + continue; + } + break; + } + + case ScriptCommands.TeleportTo: + { + if (!CliDB.MapStorage.ContainsKey(tmp.TeleportTo.MapID)) + { + Log.outError(LogFilter.Sql, "Table `{0}` has invalid map (Id: {1}) in SCRIPT_COMMAND_TELEPORT_TO for script id {2}", + tableName, tmp.TeleportTo.MapID, tmp.id); + continue; + } + + if (!GridDefines.IsValidMapCoord(tmp.TeleportTo.DestX, tmp.TeleportTo.DestY, tmp.TeleportTo.DestZ, tmp.TeleportTo.Orientation)) + { + Log.outError(LogFilter.Sql, "Table `{0}` has invalid coordinates (X: {1} Y: {2} Z: {3} O: {4}) in SCRIPT_COMMAND_TELEPORT_TO for script id {5}", + tableName, tmp.TeleportTo.DestX, tmp.TeleportTo.DestY, tmp.TeleportTo.DestZ, tmp.TeleportTo.Orientation, tmp.id); + continue; + } + break; + } + + case ScriptCommands.QuestExplored: + { + Quest quest = GetQuestTemplate(tmp.QuestExplored.QuestID); + if (quest == null) + { + Log.outError(LogFilter.Sql, "Table `{0}` has invalid quest (ID: {1}) in SCRIPT_COMMAND_QUEST_EXPLORED in `datalong` for script id {2}", + tableName, tmp.QuestExplored.QuestID, tmp.id); + continue; + } + + if (!quest.HasSpecialFlag(QuestSpecialFlags.ExplorationOrEvent)) + { + Log.outError(LogFilter.Sql, "Table `{0}` has quest (ID: {1}) in SCRIPT_COMMAND_QUEST_EXPLORED in `datalong` for script id {2}, but quest not have flag QUEST_SPECIAL_FLAGS_EXPLORATION_OR_EVENT in quest flags. Script command or quest flags wrong. Quest modified to require objective.", + tableName, tmp.QuestExplored.QuestID, tmp.id); + + // this will prevent quest completing without objective + quest.SetSpecialFlag(QuestSpecialFlags.ExplorationOrEvent); + + // continue; - quest objective requirement set and command can be allowed + } + + if (tmp.QuestExplored.Distance > SharedConst.DefaultVisibilityDistance) + { + Log.outError(LogFilter.Sql, "Table `{0}` has too large distance ({1}) for exploring objective complete in `datalong2` in SCRIPT_COMMAND_QUEST_EXPLORED in `datalong` for script id {2}", + tableName, tmp.QuestExplored.Distance, tmp.id); + continue; + } + + if (tmp.QuestExplored.Distance != 0 && tmp.QuestExplored.Distance > SharedConst.DefaultVisibilityDistance) + { + Log.outError(LogFilter.Sql, "Table `{0}` has too large distance ({1}) for exploring objective complete in `datalong2` in SCRIPT_COMMAND_QUEST_EXPLORED in `datalong` for script id {2}, max distance is {3} or 0 for disable distance check", + tableName, tmp.QuestExplored.Distance, tmp.id, SharedConst.DefaultVisibilityDistance); + continue; + } + + if (tmp.QuestExplored.Distance != 0 && tmp.QuestExplored.Distance < SharedConst.InteractionDistance) + { + Log.outError(LogFilter.Sql, "Table `{0}` has too small distance ({1}) for exploring objective complete in `datalong2` in SCRIPT_COMMAND_QUEST_EXPLORED in `datalong` for script id {2}, min distance is {3} or 0 for disable distance check", + tableName, tmp.QuestExplored.Distance, tmp.id, SharedConst.InteractionDistance); + continue; + } + + break; + } + + case ScriptCommands.KillCredit: + { + if (GetCreatureTemplate(tmp.KillCredit.CreatureEntry) == null) + { + Log.outError(LogFilter.Sql, "Table `{0}` has invalid creature (Entry: {1}) in SCRIPT_COMMAND_KILL_CREDIT for script id {2}", + tableName, tmp.KillCredit.CreatureEntry, tmp.id); + continue; + } + break; + } + + case ScriptCommands.RespawnGameobject: + { + GameObjectData data = GetGOData(tmp.RespawnGameObject.GOGuid); + if (data == null) + { + Log.outError(LogFilter.Sql, "Table `{0}` has invalid gameobject (GUID: {1}) in SCRIPT_COMMAND_RESPAWN_GAMEOBJECT for script id {2}", + tableName, tmp.RespawnGameObject.GOGuid, tmp.id); + continue; + } + + GameObjectTemplate info = GetGameObjectTemplate(data.id); + if (info == null) + { + Log.outError(LogFilter.Sql, "Table `{0}` has gameobject with invalid entry (GUID: {1} Entry: {2}) in SCRIPT_COMMAND_RESPAWN_GAMEOBJECT for script id {3}", + tableName, tmp.RespawnGameObject.GOGuid, data.id, tmp.id); + continue; + } + + if (info.type == GameObjectTypes.FishingNode || info.type == GameObjectTypes.FishingHole || info.type == GameObjectTypes.Door || + info.type == GameObjectTypes.Button || info.type == GameObjectTypes.Trap) + { + Log.outError(LogFilter.Sql, "Table `{0}` have gameobject type ({1}) unsupported by command SCRIPT_COMMAND_RESPAWN_GAMEOBJECT for script id {2}", + tableName, info.entry, tmp.id); + continue; + } + break; + } + + case ScriptCommands.TempSummonCreature: + { + if (!GridDefines.IsValidMapCoord(tmp.TempSummonCreature.PosX, tmp.TempSummonCreature.PosY, tmp.TempSummonCreature.PosZ, tmp.TempSummonCreature.Orientation)) + { + Log.outError(LogFilter.Sql, "Table `{0}` has invalid coordinates (X: {1} Y: {2} Z: {3} O: {4}) in SCRIPT_COMMAND_TEMP_SUMMON_CREATURE for script id {5}", + tableName, tmp.TempSummonCreature.PosX, tmp.TempSummonCreature.PosY, tmp.TempSummonCreature.PosZ, tmp.TempSummonCreature.Orientation, tmp.id); + continue; + } + + if (GetCreatureTemplate(tmp.TempSummonCreature.CreatureEntry) == null) + { + Log.outError(LogFilter.Sql, "Table `{0}` has invalid creature (Entry: {1}) in SCRIPT_COMMAND_TEMP_SUMMON_CREATURE for script id {2}", + tableName, tmp.TempSummonCreature.CreatureEntry, tmp.id); + continue; + } + break; + } + + case ScriptCommands.OpenDoor: + case ScriptCommands.CloseDoor: + { + GameObjectData data = GetGOData(tmp.ToggleDoor.GOGuid); + if (data == null) + { + Log.outError(LogFilter.Sql, "Table `{0}` has invalid gameobject (GUID: {1}) in {2} for script id {3}", + tableName, tmp.ToggleDoor.GOGuid, tmp.command, tmp.id); + continue; + } + + GameObjectTemplate info = GetGameObjectTemplate(data.id); + if (info == null) + { + Log.outError(LogFilter.Sql, "Table `{0}` has gameobject with invalid entry (GUID: {1} Entry: {2}) in {3} for script id {4}", + tableName, tmp.ToggleDoor.GOGuid, data.id, tmp.command, tmp.id); + continue; + } + + if (info.type != GameObjectTypes.Door) + { + Log.outError(LogFilter.Sql, "Table `{0}` has gameobject type ({1}) non supported by command {2} for script id {3}", + tableName, info.entry, tmp.command, tmp.id); + continue; + } + + break; + } + + case ScriptCommands.RemoveAura: + { + if (Global.SpellMgr.GetSpellInfo(tmp.RemoveAura.SpellID) == null) + { + Log.outError(LogFilter.Sql, "Table `{0}` using non-existent spell (id: {1}) in SCRIPT_COMMAND_REMOVE_AURA for script id {2}", + tableName, tmp.RemoveAura.SpellID, tmp.id); + continue; + } + if (Convert.ToBoolean((int)tmp.RemoveAura.Flags & ~0x1)) // 1 bits (0, 1) + { + Log.outError(LogFilter.Sql, "Table `{0}` using unknown flags in datalong2 ({1}) in SCRIPT_COMMAND_REMOVE_AURA for script id {2}", + tableName, tmp.RemoveAura.Flags, tmp.id); + continue; + } + break; + } + + case ScriptCommands.CastSpell: + { + if (Global.SpellMgr.GetSpellInfo(tmp.CastSpell.SpellID) == null) + { + Log.outError(LogFilter.Sql, "Table `{0}` using non-existent spell (id: {1}) in SCRIPT_COMMAND_CAST_SPELL for script id {2}", + tableName, tmp.CastSpell.SpellID, tmp.id); + continue; + } + if ((int)tmp.CastSpell.Flags > 4) // targeting type + { + Log.outError(LogFilter.Sql, "Table `{0}` using unknown target in datalong2 ({1}) in SCRIPT_COMMAND_CAST_SPELL for script id {2}", + tableName, tmp.CastSpell.Flags, tmp.id); + continue; + } + if ((int)tmp.CastSpell.Flags != 4 && Convert.ToBoolean(tmp.CastSpell.CreatureEntry & ~0x1)) // 1 bit (0, 1) + { + Log.outError(LogFilter.Sql, "Table `{0}` using unknown flags in dataint ({1}) in SCRIPT_COMMAND_CAST_SPELL for script id {2}", + tableName, tmp.CastSpell.CreatureEntry, tmp.id); + continue; + } + else if ((int)tmp.CastSpell.Flags == 4 && GetCreatureTemplate((uint)tmp.CastSpell.CreatureEntry) == null) + { + Log.outError(LogFilter.Sql, "Table `{0}` using invalid creature entry in dataint ({1}) in SCRIPT_COMMAND_CAST_SPELL for script id {2}", + tableName, tmp.CastSpell.CreatureEntry, tmp.id); + continue; + } + break; + } + + case ScriptCommands.CreateItem: + { + if (GetItemTemplate(tmp.CreateItem.ItemEntry) == null) + { + Log.outError(LogFilter.Sql, "Table `{0}` has nonexistent item (entry: {1}) in SCRIPT_COMMAND_CREATE_ITEM for script id {2}", + tableName, tmp.CreateItem.ItemEntry, tmp.id); + continue; + } + if (tmp.CreateItem.Amount == 0) + { + Log.outError(LogFilter.Sql, "Table `{0}` SCRIPT_COMMAND_CREATE_ITEM but amount is {1} for script id {2}", + tableName, tmp.CreateItem.Amount, tmp.id); + continue; + } + break; + } + case ScriptCommands.PlayAnimkit: + { + if (!CliDB.AnimKitStorage.ContainsKey(tmp.PlayAnimKit.AnimKitID)) + { + Log.outError(LogFilter.Sql, "Table `{0}` has invalid AnimKid id (datalong = {1}) in SCRIPT_COMMAND_PLAY_ANIMKIT for script id {2}", + tableName, tmp.PlayAnimKit.AnimKitID, tmp.id); + continue; + } + break; + } + default: + break; + } + + if (!scripts.ContainsKey(tmp.id)) + scripts[tmp.id] = new MultiMap(); + + scripts[tmp.id].Add(tmp.delay, tmp); + + ++count; + } + while (result.NextRow()); + + Log.outInfo(LogFilter.ServerLoading, "Loaded {0} script definitions in {1} ms", count, Time.GetMSTimeDiffToNow(oldMSTime)); + } + public void LoadSpellScripts() + { + LoadScripts(ScriptsType.Spell); + + // check ids + foreach (var script in sSpellScripts) + { + uint spellId = script.Key & 0x00FFFFFF; + SpellInfo spellInfo = Global.SpellMgr.GetSpellInfo(spellId); + if (spellInfo == null) + { + Log.outError(LogFilter.Sql, "Table `spell_scripts` has not existing spell (Id: {0}) as script id", spellId); + continue; + } + + byte i = (byte)((script.Key >> 24) & 0x000000FF); + //check for correct spellEffect + SpellEffectInfo effect = spellInfo.GetEffect(i); + if (effect != null && (effect.Effect == 0 || (effect.Effect != SpellEffectName.ScriptEffect && effect.Effect != SpellEffectName.Dummy))) + Log.outError(LogFilter.Sql, "Table `spell_scripts` - spell {0} effect {1} is not SPELL_EFFECT_SCRIPT_EFFECT or SPELL_EFFECT_DUMMY", spellId, i); + } + } + public void LoadEventScripts() + { + LoadScripts(ScriptsType.Event); + + List evt_scripts = new List(); + // Load all possible script entries from gameobjects + foreach (var go in gameObjectTemplateStorage) + { + uint eventId = go.Value.GetEventScriptId(); + if (eventId != 0) + evt_scripts.Add(eventId); + } + + // Load all possible script entries from spells + foreach (var spell in Global.SpellMgr.GetSpellInfoStorage().Values) + { + foreach (SpellEffectInfo effect in spell.GetEffectsForDifficulty(Difficulty.None)) + { + if (effect == null) + continue; + + if (effect.Effect == SpellEffectName.SendEvent) + if (effect.MiscValue != 0) + evt_scripts.Add((uint)effect.MiscValue); + } + } + + foreach (var path_idx in CliDB.TaxiPathNodesByPath) + { + for (uint node_idx = 0; node_idx < path_idx.Value.Length; ++node_idx) + { + TaxiPathNodeRecord node = path_idx.Value[node_idx]; + + if (node.ArrivalEventID != 0) + evt_scripts.Add(node.ArrivalEventID); + + if (node.DepartureEventID != 0) + evt_scripts.Add(node.DepartureEventID); + } + } + + // Then check if all scripts are in above list of possible script entries + foreach (var script in sEventScripts) + { + var id = evt_scripts.Find(p => p == script.Key); + if (id == 0) + Log.outError(LogFilter.Sql, "Table `event_scripts` has script (Id: {0}) not referring to any gameobject_template type 10 data2 field, type 3 data6 field, type 13 data 2 field or any spell effect {1}", + script.Key, SpellEffectName.SendEvent); + } + } + + //Load WP Scripts + public void LoadWaypointScripts() + { + LoadScripts(ScriptsType.Waypoint); + + List actionSet = new List(); + + foreach (var script in sWaypointScripts) + actionSet.Add(script.Key); + + PreparedStatement stmt = DB.World.GetPreparedStatement(WorldStatements.SEL_WAYPOINT_DATA_ACTION); + SQLResult result = DB.World.Query(stmt); + + if (!result.IsEmpty()) + { + do + { + uint action = result.Read(0); + + actionSet.Remove(action); + } + while (result.NextRow()); + } + + foreach (var id in actionSet) + Log.outError(LogFilter.Sql, "There is no waypoint which links to the waypoint script {0}", id); + } + public void LoadSpellScriptNames() + { + uint oldMSTime = Time.GetMSTime(); + + spellScriptsStorage.Clear(); // need for reload case + + SQLResult result = DB.World.Query("SELECT spell_id, ScriptName FROM spell_script_names"); + + if (result.IsEmpty()) + { + Log.outInfo(LogFilter.ServerLoading, "Loaded 0 spell script names. DB table `spell_script_names` is empty!"); + return; + } + + uint count = 0; + do + { + int spellId = result.Read(0); + string scriptName = result.Read(1); + + bool allRanks = false; + if (spellId < 0) + { + allRanks = true; + spellId = -spellId; + } + + SpellInfo spellInfo = Global.SpellMgr.GetSpellInfo((uint)spellId); + if (spellInfo == null) + { + Log.outError(LogFilter.Sql, "Scriptname: `{0}` spell (Id: {1}) does not exist.", scriptName, spellId); + continue; + } + + if (allRanks) + { + if (!spellInfo.IsRanked()) + Log.outError(LogFilter.Sql, "Scriptname: `{0}` spell (Id: {1}) has no ranks of spell.", scriptName, spellId); + + if (spellInfo.GetFirstRankSpell().Id != spellId) + { + Log.outError(LogFilter.Sql, "Scriptname: `{0}` spell (Id: {1}) is not first rank of spell.", scriptName, spellId); + continue; + } + while (spellInfo != null) + { + spellScriptsStorage.Add(spellInfo.Id, GetScriptId(scriptName)); + spellInfo = spellInfo.GetNextRankSpell(); + } + } + else + { + if (spellInfo.IsRanked()) + Log.outError(LogFilter.Sql, "Scriptname: `{0}` spell (Id: {1}) is ranked spell. Perhaps not all ranks are assigned to this script.", scriptName, spellId); + + spellScriptsStorage.Add(spellInfo.Id, GetScriptId(scriptName)); + } + + ++count; + } + while (result.NextRow()); + + Log.outInfo(LogFilter.ServerLoading, "Loaded {0} spell script names in {1} ms", count, Time.GetMSTimeDiffToNow(oldMSTime)); + } + public void ValidateSpellScripts() + { + uint oldMSTime = Time.GetMSTime(); + + if (spellScriptsStorage.Empty()) + { + Log.outInfo(LogFilter.ServerLoading, "Validated 0 scripts."); + return; + } + + uint count = 0; + + foreach (var script in spellScriptsStorage.ToList()) + { + SpellInfo spellEntry = Global.SpellMgr.GetSpellInfo(script.Key); + Dictionary SpellScriptLoaders = Global.ScriptMgr.CreateSpellScriptLoaders(script.Key); + + foreach (var pair in SpellScriptLoaders) + { + SpellScript spellScript = pair.Key.GetSpellScript(); + AuraScript auraScript = pair.Key.GetAuraScript(); + bool valid = true; + + if (spellScript == null && auraScript == null) + { + Log.outError(LogFilter.Scripts, "Functions GetSpellScript() and GetAuraScript() of script `{0}` do not return objects - script skipped", GetScriptName(pair.Value)); + valid = false; + } + + if (spellScript != null) + { + spellScript._Init(pair.Key.GetName(), spellEntry.Id); + spellScript._Register(); + if (!spellScript._Validate(spellEntry)) + valid = false; + } + + if (auraScript != null) + { + auraScript._Init(pair.Key.GetName(), spellEntry.Id); + auraScript._Register(); + if (!auraScript._Validate(spellEntry)) + valid = false; + } + + if (!valid) + spellScriptsStorage.Remove(pair.Value); + } + ++count; + } + + Log.outInfo(LogFilter.ServerLoading, "Validated {0} scripts in {1} ms", count, Time.GetMSTimeDiffToNow(oldMSTime)); + } + + public List GetSpellScriptsBounds(uint spellId) + { + return spellScriptsStorage.LookupByKey(spellId); + } + public string GetScriptName(uint id) + { + return id < scriptNamesStorage.Count ? scriptNamesStorage[(int)id] : ""; + } + public uint GetScriptId(string name) + { + // use binary search to find the script name in the sorted vector + // assume "" is the first element + if (string.IsNullOrEmpty(name)) + return 0; + + return (uint)scriptNamesStorage.IndexOf(name); + } + public uint GetAreaTriggerScriptId(uint triggerid) + { + return areaTriggerScriptStorage.LookupByKey(triggerid); + } + public Dictionary> GetScriptsMapByType(ScriptsType type) + { + switch (type) + { + case ScriptsType.Spell: + return sSpellScripts; + case ScriptsType.Event: + return sEventScripts; + case ScriptsType.Waypoint: + return sWaypointScripts; + default: + return null; + } + } + public string GetScriptsTableNameByType(ScriptsType type) + { + switch (type) + { + case ScriptsType.Spell: + return "spell_scripts"; + case ScriptsType.Event: + return "event_scripts"; + case ScriptsType.Waypoint: + return "waypoint_scripts"; + default: + return ""; + } + } + + //Creatures + public void LoadCreatureTemplates() + { + var time = Time.GetMSTime(); + + // 0 1 2 3 4 5 6 7 8 + SQLResult result = DB.World.Query("SELECT entry, difficulty_entry_1, difficulty_entry_2, difficulty_entry_3, KillCredit1, KillCredit2, modelid1, modelid2, modelid3, " + + //9 10 11 12 13 14 15 16 17 18 19 + "modelid4, name, femaleName, subname, IconName, gossip_menu_id, minlevel, maxlevel, HealthScalingExpansion, RequiredExpansion, VignetteID, " + + //20 21 22 23 24 25 26 27 28 29 30 + "faction, npcflag, speed_walk, speed_run, scale, rank, dmgschool, BaseAttackTime, RangeAttackTime, BaseVariance, RangeVariance, " + + //31 32 33 34 35 36 37 38 39 40 + "unit_class, unit_flags, unit_flags2, unit_flags3, dynamicflags, family, trainer_type, trainer_class, trainer_race, type, " + + //41 42 43 44 45 46 47 48 49 50 51 + "type_flags, type_flags2, lootid, pickpocketloot, skinloot, resistance1, resistance2, resistance3, resistance4, resistance5, resistance6, " + + //52 53 54 55 56 57 58 59 60 61 62 63 64 + "spell1, spell2, spell3, spell4, spell5, spell6, spell7, spell8, VehicleId, mingold, maxgold, AIName, MovementType, " + + //65 66 67 68 69 70 71 72 73 + "InhabitType, HoverHeight, HealthModifier, HealthModifierExtra, ManaModifier, ManaModifierExtra, ArmorModifier, DamageModifier, ExperienceModifier, " + + //74 75 76 77 78 79 + "RacialLeader, movementId, RegenHealth, mechanic_immune_mask, flags_extra, ScriptName FROM creature_template"); + + if (result.IsEmpty()) + { + Log.outError(LogFilter.ServerLoading, "Loaded 0 creatures. DB table `creature_template` is empty."); + return; + } + + uint count = 0; + do + { + LoadCreatureTemplate(result.GetFields()); + ++count; + } while (result.NextRow()); + + // Checking needs to be done after loading because of the difficulty self referencing + foreach (var template in creatureTemplateStorage.Values) + CheckCreatureTemplate(template); + + Log.outInfo(LogFilter.ServerLoading, "Loaded {0} creature definitions in {1} ms", count, Time.GetMSTimeDiffToNow(time)); + } + public void LoadCreatureTemplate(SQLFields fields) + { + uint entry = fields.Read(0); + + CreatureTemplate creature = new CreatureTemplate(); + creature.Entry = entry; + + for (var i = 0; i < SharedConst.MaxCreatureDifficulties; ++i) + creature.DifficultyEntry[i] = fields.Read(1 + i); + + for (var i = 0; i < 2; ++i) + creature.KillCredit[i] = fields.Read(4 + i); + + creature.ModelId1 = fields.Read(6); + creature.ModelId2 = fields.Read(7); + creature.ModelId3 = fields.Read(8); + creature.ModelId4 = fields.Read(9); + creature.Name = fields.Read(10); + creature.FemaleName = fields.Read(11); + creature.SubName = fields.Read(12); + creature.IconName = fields.Read(13); + creature.GossipMenuId = fields.Read(14); + creature.Minlevel = fields.Read(15); + creature.Maxlevel = fields.Read(16); + creature.HealthScalingExpansion = fields.Read(17); + creature.RequiredExpansion = fields.Read(18); + creature.VignetteID = fields.Read(19); + creature.Faction = fields.Read(20); + creature.Npcflag = (NPCFlags)fields.Read(21); + creature.SpeedWalk = fields.Read(22); + creature.SpeedRun = fields.Read(23); + creature.Scale = fields.Read(24); + creature.Rank = (CreatureEliteType)fields.Read(25); + creature.DmgSchool = fields.Read(26); + creature.BaseAttackTime = fields.Read(27); + creature.RangeAttackTime = fields.Read(28); + creature.BaseVariance = fields.Read(29); + creature.RangeVariance = fields.Read(30); + creature.UnitClass = fields.Read(31); + creature.UnitFlags = (UnitFlags)fields.Read(32); + creature.UnitFlags2 = fields.Read(33); + creature.UnitFlags3 = fields.Read(34); + creature.DynamicFlags = fields.Read(35); + creature.Family = (CreatureFamily)fields.Read(36); + creature.TrainerType = (TrainerType)fields.Read(37); + creature.TrainerClass = (Class)fields.Read(38); + creature.TrainerRace = (Race)fields.Read(39); + creature.CreatureType = (CreatureType)fields.Read(40); + creature.TypeFlags = (CreatureTypeFlags)fields.Read(41); + creature.TypeFlags2 = fields.Read(42); + creature.LootId = fields.Read(43); + creature.PickPocketId = fields.Read(44); + creature.SkinLootId = fields.Read(45); + + for (var i = (int)SpellSchools.Holy; i < (int)SpellSchools.Max; ++i) + creature.Resistance[i] = fields.Read(46 + i - 1); + + for (var i = 0; i < SharedConst.MaxCreatureSpells; ++i) + creature.Spells[i] = fields.Read(52 + i); + + creature.VehicleId = fields.Read(60); + creature.MinGold = fields.Read(61); + creature.MaxGold = fields.Read(62); + creature.AIName = fields.Read(63); + creature.MovementType = fields.Read(64); + creature.InhabitType = (InhabitType)fields.Read(65); + creature.HoverHeight = fields.Read(66); + creature.ModHealth = fields.Read(67); + creature.ModHealthExtra = fields.Read(68); + creature.ModMana = fields.Read(69); + creature.ModManaExtra = fields.Read(70); + creature.ModArmor = fields.Read(71); + creature.ModDamage = fields.Read(72); + creature.ModExperience = fields.Read(73); + creature.RacialLeader = fields.Read(74); + creature.MovementId = fields.Read(75); + creature.RegenHealth = fields.Read(76); + creature.MechanicImmuneMask = fields.Read(77); + creature.FlagsExtra = (CreatureFlagsExtra)fields.Read(78); + creature.ScriptID = GetScriptId(fields.Read(79)); + + creatureTemplateStorage.Add(entry, creature); + } + public void LoadCreatureTemplateAddons() + { + var time = Time.GetMSTime(); + // 0 1 2 3 4 5 6 7 8 9 + SQLResult result = DB.World.Query("SELECT entry, path_id, mount, bytes1, bytes2, emote, aiAnimKit, movementAnimKit, meleeAnimKit, auras FROM creature_template_addon"); + + if (result.IsEmpty()) + { + Log.outError(LogFilter.ServerLoading, "Loaded 0 creature template addon definitions. DB table `creature_template_addon` is empty."); + return; + } + + uint count = 0; + do + { + uint entry = result.Read(0); + if (GetCreatureTemplate(entry) == null) + { + Log.outError(LogFilter.Sql, "Creature template (Entry: {0}) does not exist but has a record in `creature_template_addon`", entry); + continue; + } + + CreatureAddon creatureAddon = new CreatureAddon(); + creatureAddon.path_id = result.Read(1); + creatureAddon.mount = result.Read(2); + creatureAddon.bytes1 = result.Read(3); + creatureAddon.bytes2 = result.Read(4); + creatureAddon.emote = result.Read(5); + creatureAddon.aiAnimKit = result.Read(6); + creatureAddon.movementAnimKit = result.Read(7); + creatureAddon.meleeAnimKit = result.Read(8); + + var tokens = new StringArray(result.Read(9), ' '); + + creatureAddon.auras = new uint[tokens.Length]; + byte i = 0; + for (var c = 0; c < tokens.Length; ++c) + { + uint spellId = uint.Parse(tokens[c].Replace(",", "")); + SpellInfo AdditionalSpellInfo = Global.SpellMgr.GetSpellInfo(spellId); + if (AdditionalSpellInfo == null) + { + Log.outError(LogFilter.Sql, "Creature (Entry: {0}) has wrong spell {1} defined in `auras` field in `creature_template_addon`.", entry, spellId); + continue; + } + + if (AdditionalSpellInfo.HasAura(Difficulty.None, AuraType.ControlVehicle)) + Log.outError(LogFilter.Sql, "Creature (Entry: {0}) has SPELL_AURA_CONTROL_VEHICLE aura {1} defined in `auras` field in `creature_template_addon`.", entry, spellId); + + if (creatureAddon.auras.Contains(spellId)) + { + Log.outError(LogFilter.Sql, "Creature (Entry: {0}) has duplicate aura (spell {1}) in `auras` field in `creature_template_addon`.", entry, spellId); + continue; + } + + creatureAddon.auras[i++] = spellId; + } + + if (creatureAddon.mount != 0) + { + if (CliDB.CreatureDisplayInfoStorage.LookupByKey(creatureAddon.mount) == null) + { + Log.outError(LogFilter.Sql, "Creature (Entry: {0}) has invalid displayInfoId ({1}) for mount defined in `creature_template_addon`", entry, creatureAddon.mount); + creatureAddon.mount = 0; + } + } + + if (!CliDB.EmotesStorage.ContainsKey(creatureAddon.emote)) + { + Log.outError(LogFilter.Sql, "Creature (Entry: {0}) has invalid emote ({1}) defined in `creatureaddon`.", entry, creatureAddon.emote); + creatureAddon.emote = 0; + } + + if (creatureAddon.aiAnimKit != 0 && !CliDB.AnimKitStorage.ContainsKey(creatureAddon.aiAnimKit)) + { + Log.outError(LogFilter.Sql, "Creature (Entry: {0}) has invalid aiAnimKit ({1}) defined in `creature_template_addon`.", entry, creatureAddon.aiAnimKit); + creatureAddon.aiAnimKit = 0; + } + + if (creatureAddon.movementAnimKit != 0 && !CliDB.AnimKitStorage.ContainsKey(creatureAddon.movementAnimKit)) + { + Log.outError(LogFilter.Sql, "Creature (Entry: {0}) has invalid movementAnimKit ({1}) defined in `creature_template_addon`.", entry, creatureAddon.movementAnimKit); + creatureAddon.movementAnimKit = 0; + } + + if (creatureAddon.meleeAnimKit != 0 && !CliDB.AnimKitStorage.ContainsKey(creatureAddon.meleeAnimKit)) + { + Log.outError(LogFilter.Sql, "Creature (Entry: {0}) has invalid meleeAnimKit ({1}) defined in `creature_template_addon`.", entry, creatureAddon.meleeAnimKit); + creatureAddon.meleeAnimKit = 0; + } + + creatureTemplateAddonStorage.Add(entry, creatureAddon); + count++; + } + while (result.NextRow()); + Log.outInfo(LogFilter.ServerLoading, "Loaded {0} creature template addons in {1} ms", count, Time.GetMSTimeDiffToNow(time)); + } + public void LoadCreatureAddons() + { + var time = Time.GetMSTime(); + // 0 1 2 3 4 5 6 7 8 9 + SQLResult result = DB.World.Query("SELECT guid, path_id, mount, bytes1, bytes2, emote, aiAnimKit, movementAnimKit, meleeAnimKit, auras FROM creature_addon"); + + if (result.IsEmpty()) + { + Log.outError(LogFilter.ServerLoading, "Loaded 0 creature addon definitions. DB table `creature_addon` is empty."); + return; + } + + uint count = 0; + do + { + ulong guid = result.Read(0); + CreatureData creData = GetCreatureData(guid); + if (creData == null) + { + Log.outError(LogFilter.Sql, "Creature (GUID: {0}) does not exist but has a record in `creatureaddon`", guid); + continue; + } + + CreatureAddon creatureAddon = new CreatureAddon(); + + creatureAddon.path_id = result.Read(1); + if (creData.movementType == (byte)MovementGeneratorType.Waypoint && creatureAddon.path_id == 0) + { + creData.movementType = (byte)MovementGeneratorType.Idle; + Log.outError(LogFilter.Sql, "Creature (GUID {0}) has movement type set to WAYPOINTMOTIONTYPE but no path assigned", guid); + } + + creatureAddon.mount = result.Read(2); + creatureAddon.bytes1 = result.Read(3); + creatureAddon.bytes2 = result.Read(4); + creatureAddon.emote = result.Read(5); + creatureAddon.aiAnimKit = result.Read(6); + creatureAddon.movementAnimKit = result.Read(7); + creatureAddon.meleeAnimKit = result.Read(8); + + var tokens = new StringArray(result.Read(9), ' '); + byte i = 0; + creatureAddon.auras = new uint[tokens.Length]; + for (var c = 0; c < tokens.Length; ++c) + { + uint spellId = uint.Parse(tokens[c]); + SpellInfo AdditionalSpellInfo = Global.SpellMgr.GetSpellInfo(spellId); + if (AdditionalSpellInfo == null) + { + Log.outError(LogFilter.Sql, "Creature (GUID: {0}) has wrong spell {1} defined in `auras` field in `creatureaddon`.", guid, spellId); + continue; + } + + if (AdditionalSpellInfo.HasAura(Difficulty.None, AuraType.ControlVehicle)) + Log.outError(LogFilter.Sql, "Creature (GUID: {0}) has SPELL_AURA_CONTROL_VEHICLE aura {1} defined in `auras` field in `creature_addon`.", guid, spellId); + + if (creatureAddon.auras.Contains(spellId)) + { + Log.outError(LogFilter.Sql, "Creature (GUID: {0}) has duplicate aura (spell {1}) in `auras` field in `creature_addon`.", guid, spellId); + continue; + } + + creatureAddon.auras[i++] = spellId; + } + + if (creatureAddon.mount != 0) + { + if (!CliDB.CreatureDisplayInfoStorage.ContainsKey(creatureAddon.mount)) + { + Log.outError(LogFilter.Sql, "Creature (GUID: {0}) has invalid displayInfoId ({1}) for mount defined in `creatureaddon`", guid, creatureAddon.mount); + creatureAddon.mount = 0; + } + } + + if (!CliDB.EmotesStorage.ContainsKey(creatureAddon.emote)) + { + Log.outError(LogFilter.Sql, "Creature (GUID: {0}) has invalid emote ({1}) defined in `creatureaddon`.", guid, creatureAddon.emote); + creatureAddon.emote = 0; + } + + + if (creatureAddon.aiAnimKit != 0 && !CliDB.AnimKitStorage.ContainsKey(creatureAddon.aiAnimKit)) + { + Log.outError(LogFilter.Sql, "Creature (Guid: {0}) has invalid aiAnimKit ({1}) defined in `creature_addon`.", guid, creatureAddon.aiAnimKit); + creatureAddon.aiAnimKit = 0; + } + + if (creatureAddon.movementAnimKit != 0 && !CliDB.AnimKitStorage.ContainsKey(creatureAddon.movementAnimKit)) + { + Log.outError(LogFilter.Sql, "Creature (Guid: {0}) has invalid movementAnimKit ({1}) defined in `creature_addon`.", guid, creatureAddon.movementAnimKit); + creatureAddon.movementAnimKit = 0; + } + + if (creatureAddon.meleeAnimKit != 0 && !CliDB.AnimKitStorage.ContainsKey(creatureAddon.meleeAnimKit)) + { + Log.outError(LogFilter.Sql, "Creature (Guid: {0}) has invalid meleeAnimKit ({1}) defined in `creature_addon`.", guid, creatureAddon.meleeAnimKit); + creatureAddon.meleeAnimKit = 0; + } + + creatureAddonStorage.Add(guid, creatureAddon); + count++; + } while (result.NextRow()); + + Log.outInfo(LogFilter.ServerLoading, "Loaded {0} creature addons in {1} ms", count, Time.GetMSTimeDiffToNow(time)); + } + public void LoadCreatureQuestItems() + { + uint oldMSTime = Time.GetMSTime(); + + // 0 1 2 + SQLResult result = DB.World.Query("SELECT CreatureEntry, ItemId, Idx FROM creature_questitem ORDER BY Idx ASC"); + if (result.IsEmpty()) + { + Log.outInfo(LogFilter.ServerLoading, "Loaded 0 creature quest items. DB table `creature_questitem` is empty."); + return; + } + + uint count = 0; + do + { + uint entry = result.Read(0); + uint item = result.Read(1); + uint idx = result.Read(2); + + if (!creatureTemplateStorage.ContainsKey(entry)) + { + Log.outError(LogFilter.Sql, "Table `creature_questitem` has data for nonexistent creature (entry: {0}, idx: {1}), skipped", entry, idx); + continue; + } + + if (!CliDB.ItemStorage.ContainsKey(item)) + { + Log.outError(LogFilter.Sql, "Table `creature_questitem` has nonexistent item (ID: {0}) in creature (entry: {1}, idx: {2}), skipped", item, entry, idx); + continue; + }; + + _creatureQuestItemStorage.Add(entry, item); + + ++count; + } + while (result.NextRow()); + + Log.outInfo(LogFilter.ServerLoading, "Loaded {0} creature quest items in {1} ms", count, Time.GetMSTimeDiffToNow(oldMSTime)); + } + public void LoadEquipmentTemplates() + { + var time = Time.GetMSTime(); + // 0 1 2 3 4 + SQLResult result = DB.World.Query("SELECT CreatureID, ID, ItemID1, AppearanceModID1, ItemVisual1, " + + //5 6 7 8 9 10 + "ItemID2, AppearanceModID2, ItemVisual2, ItemID3, AppearanceModID3, ItemVisual3 " + + "FROM creature_equip_template"); + + if (result.IsEmpty()) + { + Log.outError(LogFilter.ServerLoading, "Loaded 0 creature equipment templates. DB table `creature_equip_template` is empty!"); + return; + } + + uint count = 0; + do + { + uint entry = result.Read(0); + + if (GetCreatureTemplate(entry) == null) + { + Log.outError(LogFilter.Sql, "Creature template (CreatureID: {0}) does not exist but has a record in `creature_equip_template`", entry); + continue; + } + + uint id = result.Read(1); + + EquipmentInfo equipmentInfo = new EquipmentInfo(); + + for (var i = 0; i < SharedConst.MaxEquipmentItems; ++i) + { + equipmentInfo.Items[i].ItemId = result.Read(2 + i * 3); + equipmentInfo.Items[i].AppearanceModId = result.Read(3 + i * 3); + equipmentInfo.Items[i].ItemVisual = result.Read(4 + i * 3); + + if (equipmentInfo.Items[i].ItemId == 0) + continue; + + var dbcItem = CliDB.ItemStorage.LookupByKey(equipmentInfo.Items[i].ItemId); + if (dbcItem == null) + { + Log.outError(LogFilter.Sql, "Unknown item (ID: {0}) in creature_equip_template.ItemID{1} for CreatureID = {2}, forced to 0.", + equipmentInfo.Items[i].ItemId, i + 1, entry); + equipmentInfo.Items[i].ItemId = 0; + continue; + } + + if (Global.DB2Mgr.GetItemModifiedAppearance(equipmentInfo.Items[i].ItemId, equipmentInfo.Items[i].AppearanceModId) == null) + { + Log.outError(LogFilter.Sql, "Unknown item appearance for (ID: {0}, AppearanceModID: {1}) pair in creature_equip_template.ItemID{2} creature_equip_template.AppearanceModID{3} " + + "for CreatureID: {4} and ID: {5}, forced to default.", + equipmentInfo.Items[i].ItemId, equipmentInfo.Items[i].AppearanceModId, i + 1, i + 1, entry, id); + ItemModifiedAppearanceRecord defaultAppearance = Global.DB2Mgr.GetDefaultItemModifiedAppearance(equipmentInfo.Items[i].ItemId); + if (defaultAppearance != null) + equipmentInfo.Items[i].AppearanceModId = defaultAppearance.AppearanceModID; + else + equipmentInfo.Items[i].AppearanceModId = 0; + continue; + } + + if (dbcItem.inventoryType != InventoryType.Weapon && + dbcItem.inventoryType != InventoryType.Shield && + dbcItem.inventoryType != InventoryType.Ranged && + dbcItem.inventoryType != InventoryType.Weapon2Hand && + dbcItem.inventoryType != InventoryType.WeaponMainhand && + dbcItem.inventoryType != InventoryType.WeaponOffhand && + dbcItem.inventoryType != InventoryType.Holdable && + dbcItem.inventoryType != InventoryType.Thrown && + dbcItem.inventoryType != InventoryType.RangedRight) + { + Log.outError(LogFilter.Sql, "Item (ID {0}) in creature_equip_template.ItemID{1} for CreatureID = {2} is not equipable in a hand, forced to 0.", + equipmentInfo.Items[i].ItemId, i + 1, entry); + equipmentInfo.Items[i].ItemId = 0; + } + } + + equipmentInfoStorage.Add(entry, Tuple.Create(id, equipmentInfo)); + ++count; + + } while (result.NextRow()); + + Log.outInfo(LogFilter.ServerLoading, "Loaded {0} equipment templates in {1} ms", count, Time.GetMSTimeDiffToNow(time)); + } + public void LoadCreatureClassLevelStats() + { + var time = Time.GetMSTime(); + + creatureBaseStatsStorage.Clear(); + // 0 1 2 3 4 5 6 7 8 9 10 11 + SQLResult result = DB.World.Query("SELECT level, class, basemana, basearmor, attackpower, rangedattackpower, damage_base, damage_exp1, damage_exp2, damage_exp3, damage_exp4, damage_exp5 FROM creature_classlevelstats"); + + if (result.IsEmpty()) + { + Log.outError(LogFilter.ServerLoading, "Loaded 0 creature base stats. DB table `creature_classlevelstats` is empty."); + return; + } + + uint count = 0; + do + { + byte Level = result.Read(0); + byte _class = result.Read(1); + + if (_class == 0 || ((1 << (_class - 1)) & (int)Class.ClassMaskAllCreatures) == 0) + Log.outError(LogFilter.Sql, "Creature base stats for level {0} has invalid class {1}", Level, _class); + + CreatureBaseStats stats = new CreatureBaseStats(); + + for (var i = 0; i < (int)Expansion.Max; ++i) + { + stats.BaseHealth[i] = (uint)CliDB.GetGameTableColumnForClass(CliDB.NpcTotalHpGameTable[i].GetRow(Level), (Class)_class); + stats.BaseDamage[i] = CliDB.GetGameTableColumnForClass(CliDB.NpcDamageByClassGameTable[i].GetRow(Level), (Class)_class); + if (stats.BaseDamage[i] < 0.0f) + { + Log.outError(LogFilter.Sql, "Creature base stats for class {0}, level {1} has invalid negative base damage[{2}] - set to 0.0", _class, Level, i); + stats.BaseDamage[i] = 0.0f; + } + } + + stats.BaseMana = result.Read(2); + stats.BaseArmor = result.Read(3); + + stats.AttackPower = result.Read(4); + stats.RangedAttackPower = result.Read(5); + + creatureBaseStatsStorage.Add(MathFunctions.MakePair16(Level, _class), stats); + + ++count; + } while (result.NextRow()); + + foreach (var creatureTemplate in creatureTemplateStorage.Values) + { + for (short lvl = creatureTemplate.Minlevel; lvl <= creatureTemplate.Maxlevel; ++lvl) + { + if (creatureBaseStatsStorage.LookupByKey(MathFunctions.MakePair16((uint)lvl, creatureTemplate.UnitClass)) == null) + Log.outError(LogFilter.Sql, "Missing base stats for creature class {0} level {1}", creatureTemplate.UnitClass, lvl); + } + } + + Log.outInfo(LogFilter.ServerLoading, "Loaded {0} creature base stats in {1} ms", count, Time.GetMSTimeDiffToNow(time)); + } + public void LoadCreatureModelInfo() + { + var time = Time.GetMSTime(); + SQLResult result = DB.World.Query("SELECT DisplayID, BoundingRadius, CombatReach, DisplayID_Other_Gender FROM creature_model_info"); + + if (result.IsEmpty()) + { + Log.outError(LogFilter.ServerLoading, "Loaded 0 creature model definitions. DB table `creaturemodelinfo` is empty."); + return; + } + + // List of model FileDataIDs that the client treats as invisible stalker + uint[] trigggerCreatureModelFileID = { 124640, 124641, 124642, 343863, 439302 }; + + uint count = 0; + do + { + uint displayId = result.Read(0); + + var creatureDisplay = CliDB.CreatureDisplayInfoStorage.LookupByKey(displayId); + if (creatureDisplay == null) + { + Log.outError(LogFilter.Sql, "Table `creature_model_info` has a non-existent DisplayID (ID: {0}). Skipped.", displayId); + continue; + } + + CreatureModelInfo modelInfo = new CreatureModelInfo(); + modelInfo.BoundingRadius = result.Read(1); + modelInfo.CombatReach = result.Read(2); + modelInfo.DisplayIdOtherGender = result.Read(3); + modelInfo.gender = (sbyte)creatureDisplay.Gender; + + // Checks + if (modelInfo.gender == (sbyte)Gender.Unknown) + modelInfo.gender = (sbyte)Gender.Male; + + if (modelInfo.DisplayIdOtherGender != 0 && !CliDB.CreatureDisplayInfoStorage.ContainsKey(modelInfo.DisplayIdOtherGender)) + { + Log.outError(LogFilter.Sql, "Table `creature_model_info` has a non-existent DisplayID_Other_Gender (ID: {0}) being used by DisplayID (ID: {1}).", modelInfo.DisplayIdOtherGender, displayId); + modelInfo.DisplayIdOtherGender = 0; + } + + if (modelInfo.CombatReach < 0.1f) + modelInfo.CombatReach = SharedConst.DefaultCombatReach; + + CreatureModelDataRecord modelData = CliDB.CreatureModelDataStorage.LookupByKey(creatureDisplay.ModelID); + if (modelData != null) + { + for (uint i = 0; i < 5; ++i) + { + if (modelData.FileDataID == trigggerCreatureModelFileID[i]) + { + modelInfo.IsTrigger = true; + break; + } + } + } + + creatureModelStorage.Add(displayId, modelInfo); + count++; + } + while (result.NextRow()); + + Log.outInfo(LogFilter.ServerLoading, "Loaded {0} creature model based info in {1} ms", count, Time.GetMSTimeDiffToNow(time)); + } + public void CheckCreatureTemplate(CreatureTemplate cInfo) + { + if (cInfo == null) + return; + + bool ok = true; // bool to allow continue outside this loop + for (uint diff = 0; diff < SharedConst.MaxCreatureDifficulties && ok; ++diff) + { + if (cInfo.DifficultyEntry[diff] == 0) + continue; + ok = false; // will be set to true at the end of this loop again + + CreatureTemplate difficultyInfo = GetCreatureTemplate(cInfo.DifficultyEntry[diff]); + if (difficultyInfo == null) + { + Log.outError(LogFilter.Sql, "Creature (Entry: {0}) has `difficulty_entry_{1}`={2} but creature entry {3} does not exist.", + cInfo.Entry, diff + 1, cInfo.DifficultyEntry[diff], cInfo.DifficultyEntry[diff]); + continue; + } + + bool ok2 = true; + for (uint diff2 = 0; diff2 < SharedConst.MaxCreatureDifficulties && ok2; ++diff2) + { + ok2 = false; + if (_difficultyEntries[diff2].Contains(cInfo.Entry)) + { + Log.outError(LogFilter.Sql, "Creature (Entry: {0}) is listed as `difficulty_entry_{1}` of another creature, but itself lists {2} in `difficulty_entry_{3}`.", + cInfo.Entry, diff2 + 1, cInfo.DifficultyEntry[diff], diff + 1); + continue; + } + + if (_difficultyEntries[diff2].Contains(cInfo.DifficultyEntry[diff])) + { + Log.outError(LogFilter.Sql, "Creature (Entry: {0}) already listed as `difficulty_entry_{1}` for another entry.", cInfo.DifficultyEntry[diff], diff2 + 1); + continue; + } + + if (_hasDifficultyEntries[diff2].Contains(cInfo.DifficultyEntry[diff])) + { + Log.outError(LogFilter.Sql, "Creature (Entry: {0}) has `difficulty_entry_{1}`={2} but creature entry {3} has itself a value in `difficulty_entry_{4}`.", + cInfo.Entry, diff + 1, cInfo.DifficultyEntry[diff], cInfo.DifficultyEntry[diff], diff2 + 1); + continue; + } + ok2 = true; + } + if (!ok2) + continue; + + if (cInfo.HealthScalingExpansion > difficultyInfo.HealthScalingExpansion) + { + Log.outError(LogFilter.Sql, "Creature (Id: {0}, Expansion {1}) has different `HealthScalingExpansion` in difficulty {2} mode (Id: {3}, Expansion: {4}).", + cInfo.Entry, cInfo.HealthScalingExpansion, diff + 1, cInfo.DifficultyEntry[diff], difficultyInfo.HealthScalingExpansion); + } + + if (cInfo.Minlevel > difficultyInfo.Minlevel) + { + Log.outError(LogFilter.Sql, "Creature (Entry: {0}, minlevel: {1}) has lower `minlevel` in difficulty {2} mode (Entry: {3}, minlevel: {4}).", + cInfo.Entry, cInfo.Minlevel, diff + 1, cInfo.DifficultyEntry[diff], difficultyInfo.Minlevel); + } + + if (cInfo.Maxlevel > difficultyInfo.Maxlevel) + { + Log.outError(LogFilter.Sql, "Creature (Entry: {0}, maxlevel: {1}) has lower `maxlevel` in difficulty {2} mode (Entry: {3}, maxlevel: {4}).", + cInfo.Entry, cInfo.Maxlevel, diff + 1, cInfo.DifficultyEntry[diff], difficultyInfo.Maxlevel); + } + + if (cInfo.Faction != difficultyInfo.Faction) + { + Log.outError(LogFilter.Sql, "Creature (Entry: {0}, faction: {1}) has different `faction` in difficulty {2} mode (Entry: {3}, faction: {4}).", + cInfo.Entry, cInfo.Faction, diff + 1, cInfo.DifficultyEntry[diff], difficultyInfo.Faction); + } + + if (cInfo.UnitClass != difficultyInfo.UnitClass) + { + Log.outError(LogFilter.Sql, "Creature (Entry: {0}, class: {1}) has different `unit_class` in difficulty {2} mode (Entry: {3}, class: {4}).", + cInfo.Entry, cInfo.UnitClass, diff + 1, cInfo.DifficultyEntry[diff], difficultyInfo.UnitClass); + continue; + } + + if (cInfo.Npcflag != difficultyInfo.Npcflag) + { + Log.outError(LogFilter.Sql, "Creature (Entry: {0}) has different `npcflag` in difficulty {1} mode (Entry: {2}).", cInfo.Entry, diff + 1, cInfo.DifficultyEntry[diff]); + Log.outError(LogFilter.Sql, "Possible FIX: UPDATE `creature_template` SET `npcflag`=`npcflag`^{0} WHERE `entry`={1};", cInfo.Npcflag ^ difficultyInfo.Npcflag, cInfo.DifficultyEntry[diff]); + continue; + } + + if (cInfo.DmgSchool != difficultyInfo.DmgSchool) + { + Log.outError(LogFilter.Sql, "Creature (Entry: {0}, `dmgschool`: {1}) has different `dmgschool` in difficulty {2} mode (Entry: {3}, `dmgschool`: {4}).", + cInfo.Entry, cInfo.DmgSchool, diff + 1, cInfo.DifficultyEntry[diff], difficultyInfo.DmgSchool); + Log.outError(LogFilter.Sql, "Possible FIX: UPDATE `creature_template` SET `dmgschool`={0} WHERE `entry`={1};", cInfo.DmgSchool, cInfo.DifficultyEntry[diff]); + } + + if (cInfo.UnitFlags2 != difficultyInfo.UnitFlags2) + { + Log.outError(LogFilter.Sql, "Creature (Entry: {0}, `unit_flags2`: {1}) has different `unit_flags2` in difficulty {2} mode (Entry: {3}, `unit_flags2`: {4}).", + cInfo.Entry, cInfo.UnitFlags2, diff + 1, cInfo.DifficultyEntry[diff], difficultyInfo.UnitFlags2); + Log.outError(LogFilter.Sql, "Possible FIX: UPDATE `creature_template` SET `unit_flags2`=`unit_flags2`^{0} WHERE `entry`={1};", cInfo.UnitFlags2 ^ difficultyInfo.UnitFlags2, cInfo.DifficultyEntry[diff]); + } + + if (cInfo.Family != difficultyInfo.Family) + { + Log.outError(LogFilter.Sql, "Creature (Entry: {0}, family: {1}) has different `family` in difficulty {2} mode (Entry: {3}, family: {4}).", + cInfo.Entry, cInfo.Family, diff + 1, cInfo.DifficultyEntry[diff], difficultyInfo.Family); + } + + if (cInfo.TrainerClass != difficultyInfo.TrainerClass) + { + Log.outError(LogFilter.Sql, "Creature (Entry: {0}) has different `trainer_class` in difficulty {1} mode (Entry: {2}).", cInfo.Entry, diff + 1, cInfo.DifficultyEntry[diff]); + continue; + } + + if (cInfo.TrainerRace != difficultyInfo.TrainerRace) + { + Log.outError(LogFilter.Sql, "Creature (Entry: {0}) has different `trainer_race` in difficulty {1} mode (Entry: {2}).", cInfo.Entry, diff + 1, cInfo.DifficultyEntry[diff]); + continue; + } + + if (cInfo.TrainerType != difficultyInfo.TrainerType) + { + Log.outError(LogFilter.Sql, "Creature (Entry: {0}) has different `trainer_type` in difficulty {1} mode (Entry: {2}).", cInfo.Entry, diff + 1, cInfo.DifficultyEntry[diff]); + continue; + } + + if (cInfo.CreatureType != difficultyInfo.CreatureType) + { + Log.outError(LogFilter.Sql, "Creature (Entry: {0}, type: {1}) has different `type` in difficulty {2} mode (Entry: {3}, type: {4}).", + cInfo.Entry, cInfo.CreatureType, diff + 1, cInfo.DifficultyEntry[diff], difficultyInfo.CreatureType); + } + + if (cInfo.VehicleId == 0 && difficultyInfo.VehicleId != 0) + { + Log.outError(LogFilter.Sql, "Non-vehicle Creature (Entry: {0}, VehicleId: {1}) has `VehicleId` set in difficulty {2} mode (Entry: {3}, VehicleId: {4}).", + cInfo.Entry, cInfo.VehicleId, diff + 1, cInfo.DifficultyEntry[diff], difficultyInfo.VehicleId); + } + + if (cInfo.RegenHealth != difficultyInfo.RegenHealth) + { + Log.outError(LogFilter.Sql, "Creature (Entry: {0}, RegenHealth: {1}) has different `RegenHealth` in difficulty {2} mode (Entry: {3}, RegenHealth: {4}).", + cInfo.Entry, cInfo.RegenHealth, diff + 1, cInfo.DifficultyEntry[diff], difficultyInfo.RegenHealth); + Log.outError(LogFilter.Sql, "Possible FIX: UPDATE `creature_template` SET `RegenHealth`={0} WHERE `entry`={1};", cInfo.RegenHealth, cInfo.DifficultyEntry[diff]); + } + + uint differenceMask = cInfo.MechanicImmuneMask & (~difficultyInfo.MechanicImmuneMask); + if (differenceMask != 0) + { + Log.outError(LogFilter.Sql, "Creature (Entry: {0}, mechanic_immune_mask: {1}) has weaker immunities in difficulty {2} mode (Entry: {3}, mechanic_immune_mask: {4}).", + cInfo.Entry, cInfo.MechanicImmuneMask, diff + 1, cInfo.DifficultyEntry[diff], difficultyInfo.MechanicImmuneMask); + Log.outError(LogFilter.Sql, "Possible FIX: UPDATE `creature_template` SET `mechanic_immune_mask`=`mechanic_immune_mask`|{0} WHERE `entry`={1};", differenceMask, cInfo.DifficultyEntry[diff]); + } + + differenceMask = (uint)((cInfo.FlagsExtra ^ difficultyInfo.FlagsExtra) & (~CreatureFlagsExtra.InstanceBind)); + if (differenceMask != 0) + { + Log.outError(LogFilter.Sql, "Creature (Entry: {0}, flags_extra: {1}) has different `flags_extra` in difficulty {2} mode (Entry: {3}, flags_extra: {4}).", + cInfo.Entry, cInfo.FlagsExtra, diff + 1, cInfo.DifficultyEntry[diff], difficultyInfo.FlagsExtra); + Log.outError(LogFilter.Sql, "Possible FIX: UPDATE `creature_template` SET `flags_extra`=`flags_extra`^{0} WHERE `entry`={1};", differenceMask, cInfo.DifficultyEntry[diff]); + } + + if (difficultyInfo.AIName.IsEmpty()) + { + Log.outError(LogFilter.Sql, "Creature (Entry: {0}) lists difficulty {1} mode entry {2} with `AIName` filled in. `AIName` of difficulty 0 mode creature is always used instead.", + cInfo.Entry, diff + 1, cInfo.DifficultyEntry[diff]); + continue; + } + + if (difficultyInfo.ScriptID != 0) + { + Log.outError(LogFilter.Sql, "Creature (Entry: {0}) lists difficulty {1} mode entry {2} with `ScriptName` filled in. `ScriptName` of difficulty 0 mode creature is always used instead.", + cInfo.Entry, diff + 1, cInfo.DifficultyEntry[diff]); + continue; + } + + _hasDifficultyEntries[diff].Add(cInfo.Entry); + _difficultyEntries[diff].Add(cInfo.DifficultyEntry[diff]); + ok = true; + } + + if (!CliDB.FactionTemplateStorage.ContainsKey(cInfo.Faction)) + Log.outError(LogFilter.Sql, "Creature (Entry: {0}) has non-existing faction template ({1}).", cInfo.Entry, cInfo.Faction); + + // used later for scale + CreatureDisplayInfoRecord displayScaleEntry = null; + + if (cInfo.ModelId1 != 0) + { + CreatureDisplayInfoRecord displayEntry = CliDB.CreatureDisplayInfoStorage.LookupByKey(cInfo.ModelId1); + if (displayEntry == null) + { + Log.outError(LogFilter.Sql, "Creature (Entry: {0}) lists non-existing Modelid1 id ({1}), this can crash the client.", cInfo.Entry, cInfo.ModelId1); + //cInfo.ModelId1 = 0; + } + else if (displayScaleEntry == null) + displayScaleEntry = displayEntry; + + CreatureModelInfo modelInfo = GetCreatureModelInfo(cInfo.ModelId1); + if (modelInfo == null) + Log.outError(LogFilter.Sql, "No model data exist for `Modelid1` = {0} listed by creature (Entry: {1}).", cInfo.ModelId1, cInfo.Entry); + } + + if (cInfo.ModelId2 != 0) + { + CreatureDisplayInfoRecord displayEntry = CliDB.CreatureDisplayInfoStorage.LookupByKey(cInfo.ModelId2); + if (displayEntry == null) + { + Log.outError(LogFilter.Sql, "Creature (Entry: {0}) lists non-existing Modelid2 id ({1}), this can crash the client.", cInfo.Entry, cInfo.ModelId2); + cInfo.ModelId2 = 0; + } + else if (displayScaleEntry == null) + displayScaleEntry = displayEntry; + + CreatureModelInfo modelInfo = GetCreatureModelInfo(cInfo.ModelId2); + if (modelInfo == null) + Log.outError(LogFilter.Sql, "No model data exist for `Modelid2` = {0} listed by creature (Entry: {1}).", cInfo.ModelId2, cInfo.Entry); + } + + if (cInfo.ModelId3 != 0) + { + CreatureDisplayInfoRecord displayEntry = CliDB.CreatureDisplayInfoStorage.LookupByKey(cInfo.ModelId3); + if (displayEntry == null) + { + Log.outError(LogFilter.Sql, "Creature (Entry: {0}) lists non-existing Modelid3 id ({1}), this can crash the client.", cInfo.Entry, cInfo.ModelId3); + cInfo.ModelId3 = 0; + } + else if (displayScaleEntry == null) + displayScaleEntry = displayEntry; + + CreatureModelInfo modelInfo = GetCreatureModelInfo(cInfo.ModelId3); + if (modelInfo == null) + Log.outError(LogFilter.Sql, "No model data exist for `Modelid3` = {0} listed by creature (Entry: {1}).", cInfo.ModelId3, cInfo.Entry); + } + + if (cInfo.ModelId4 != 0) + { + CreatureDisplayInfoRecord displayEntry = CliDB.CreatureDisplayInfoStorage.LookupByKey(cInfo.ModelId4); + if (displayEntry == null) + { + Log.outError(LogFilter.Sql, "Creature (Entry: {0}) lists non-existing Modelid4 id ({1}), this can crash the client.", cInfo.Entry, cInfo.ModelId4); + cInfo.ModelId4 = 0; + } + else if (displayScaleEntry == null) + displayScaleEntry = displayEntry; + + CreatureModelInfo modelInfo = GetCreatureModelInfo(cInfo.ModelId4); + if (modelInfo == null) + Log.outError(LogFilter.Sql, "No model data exist for `Modelid4` = {0} listed by creature (Entry: {1}).", cInfo.ModelId4, cInfo.Entry); + } + + if (displayScaleEntry == null) + Log.outError(LogFilter.Sql, "Creature (Entry: {0}) does not have any existing display id in Modelid1/Modelid2/Modelid3/Modelid4.", cInfo.Entry); + + for (int k = 0; k < SharedConst.MaxCreatureKillCredit; ++k) + { + if (cInfo.KillCredit[k] != 0) + { + if (GetCreatureTemplate(cInfo.KillCredit[k]) == null) + { + Log.outError(LogFilter.Sql, "Creature (Entry: {0}) lists non-existing creature entry {1} in `KillCredit{2}`.", cInfo.Entry, cInfo.KillCredit[k], k + 1); + cInfo.KillCredit[k] = 0; + } + } + } + + if (cInfo.UnitClass == 0 || ((1 << ((int)cInfo.UnitClass - 1)) & (int)Class.ClassMaskAllCreatures) == 0) + { + Log.outError(LogFilter.Sql, "Creature (Entry: {0}) has invalid unit_class ({1}) in creature_template. Set to 1 (UNIT_CLASS_WARRIOR).", cInfo.Entry, cInfo.UnitClass); + cInfo.UnitClass = (uint)Class.Warrior; + } + + if (cInfo.DmgSchool >= (uint)SpellSchools.Max) + { + Log.outError(LogFilter.Sql, "Creature (Entry: {0}) has invalid spell school value ({1}) in `dmgschool`.", cInfo.Entry, cInfo.DmgSchool); + cInfo.DmgSchool = (uint)SpellSchools.Normal; + } + + if (cInfo.BaseAttackTime == 0) + cInfo.BaseAttackTime = SharedConst.BaseAttackTime; + + if (cInfo.RangeAttackTime == 0) + cInfo.RangeAttackTime = SharedConst.BaseAttackTime; + + if (cInfo.Npcflag.HasAnyFlag(NPCFlags.Trainer) && (uint)cInfo.TrainerType >= 4) + Log.outError(LogFilter.Sql, "Creature (Entry: {0}) has wrong trainer type {1}.", cInfo.Entry, cInfo.TrainerType); + + if (cInfo.SpeedWalk == 0.0f) + { + Log.outError(LogFilter.Sql, "Creature (Entry: {0}) has wrong value ({1}) in speed_walk, set to 1.", cInfo.Entry, cInfo.SpeedWalk); + cInfo.SpeedWalk = 1.0f; + } + + if (cInfo.SpeedRun == 0.0f) + { + Log.outError(LogFilter.Sql, "Creature (Entry: {0}) has wrong value ({1}) in speed_run, set to 1.14286.", cInfo.Entry, cInfo.SpeedRun); + cInfo.SpeedRun = 1.14286f; + } + + if (cInfo.CreatureType != 0 && !CliDB.CreatureTypeStorage.ContainsKey((uint)cInfo.CreatureType)) + { + Log.outError(LogFilter.Sql, "Creature (Entry: {0}) has invalid creature type ({1}) in `type`.", cInfo.Entry, cInfo.CreatureType); + cInfo.CreatureType = CreatureType.Humanoid; + } + + // must exist or used hidden but used in data horse case + if (cInfo.Family != 0 && !CliDB.CreatureFamilyStorage.ContainsKey(cInfo.Family) && cInfo.Family != CreatureFamily.HorseCustom) + { + Log.outError(LogFilter.Sql, "Creature (Entry: {0}) has invalid creature family ({1}) in `family`.", cInfo.Entry, cInfo.Family); + cInfo.Family = CreatureFamily.None; + } + + if (cInfo.InhabitType <= 0 || cInfo.InhabitType > InhabitType.Anywhere) + { + Log.outError(LogFilter.Sql, "Creature (Entry: {0}) has wrong value ({1}) in `InhabitType`, creature will not correctly walk/swim/fly.", cInfo.Entry, cInfo.InhabitType); + cInfo.InhabitType = InhabitType.Anywhere; + } + + if (cInfo.HoverHeight < 0.0f) + { + Log.outError(LogFilter.Sql, "Creature (Entry: {0}) has wrong value ({1}) in `HoverHeight`", cInfo.Entry, cInfo.HoverHeight); + cInfo.HoverHeight = 1.0f; + } + + if (cInfo.VehicleId != 0) + { + if (!CliDB.VehicleStorage.ContainsKey(cInfo.VehicleId)) + { + Log.outError(LogFilter.Sql, "Creature (Entry: {0}) has a non-existing VehicleId ({1}). This *WILL* cause the client to freeze!", cInfo.Entry, cInfo.VehicleId); + cInfo.VehicleId = 0; + } + } + + for (byte j = 0; j < SharedConst.MaxCreatureSpells; ++j) + { + if (cInfo.Spells[j] != 0 && !Global.SpellMgr.HasSpellInfo(cInfo.Spells[j])) + { + Log.outError(LogFilter.Sql, "Creature (Entry: {0}) has non-existing Spell{1} ({2}), set to 0.", cInfo.Entry, j + 1, cInfo.Spells[j]); + cInfo.Spells[j] = 0; + } + } + + if (cInfo.MovementType >= (uint)MovementGeneratorType.MaxDB) + { + Log.outError(LogFilter.Sql, "Creature (Entry: {0}) has wrong movement generator type ({1}), ignored and set to IDLE.", cInfo.Entry, cInfo.MovementType); + cInfo.MovementType = (uint)MovementGeneratorType.Idle; + } + + /// if not set custom creature scale then load scale from CreatureDisplayInfo.dbc + if (cInfo.Scale <= 0.0f) + { + if (displayScaleEntry != null) + cInfo.Scale = displayScaleEntry.CreatureModelScale; + else + cInfo.Scale = 1.0f; + } + + if (cInfo.HealthScalingExpansion < (int)Expansion.LevelCurrent || cInfo.HealthScalingExpansion > ((int)Expansion.Max - 1)) + { + Log.outError(LogFilter.Sql, "Table `creature_template` lists creature (Id: {0}) with invalid `HealthScalingExpansion` {1}. Ignored and set to 0.", cInfo.Entry, cInfo.HealthScalingExpansion); + cInfo.HealthScalingExpansion = 0; + } + + if (cInfo.RequiredExpansion > (int)(Expansion.Max - 1)) + { + Log.outError(LogFilter.Sql, "Table `creature_template` lists creature (Entry: {0}) with `RequiredExpansion` {1}. Ignored and set to 0.", cInfo.Entry, cInfo.RequiredExpansion); + cInfo.RequiredExpansion = 0; + } + + uint badFlags = (uint)(cInfo.FlagsExtra & ~CreatureFlagsExtra.DBAllowed); + if (badFlags != 0) + { + Log.outError(LogFilter.Sql, "Table `creature_template` lists creature (Entry: {0}) with disallowed `flags_extra` {1}, removing incorrect flag.", cInfo.Entry, badFlags); + cInfo.FlagsExtra &= CreatureFlagsExtra.DBAllowed; + } + + // -1, as expansion, is used in CreatureDifficulty.db2 for + // auto-updating the levels of creatures having their expansion + // set to that value to the current expansion's max leveling level + if (cInfo.HealthScalingExpansion == (int)Expansion.LevelCurrent) + { + cInfo.Minlevel = (short)(WorldConfig.GetIntValue(WorldCfg.MaxPlayerLevel) + cInfo.Minlevel); + cInfo.Maxlevel = (short)(WorldConfig.GetIntValue(WorldCfg.MaxPlayerLevel) + cInfo.Maxlevel); + cInfo.HealthScalingExpansion = (int)Expansion.WarlordsOfDraenor; + } + + if (cInfo.Minlevel < 1 || cInfo.Minlevel > SharedConst.StrongMaxLevel) + { + Log.outError(LogFilter.Sql, "Creature (ID: {0}): MinLevel {1} is not within [1, 255], value has been set to 1.", cInfo.Entry, cInfo.Minlevel); + cInfo.Minlevel = 1; + } + + if (cInfo.Maxlevel < 1 || cInfo.Maxlevel > SharedConst.StrongMaxLevel) + { + Log.outError(LogFilter.Sql, "Creature (ID: {0}): MaxLevel {1} is not within [1, 255], value has been set to 1.", cInfo.Entry, cInfo.Maxlevel); + cInfo.Maxlevel = 1; + } + + cInfo.ModDamage *= Creature._GetDamageMod(cInfo.Rank); + } + public void LoadLinkedRespawn() + { + uint oldMSTime = Time.GetMSTime(); + + linkedRespawnStorage.Clear(); + // 0 1 2 + SQLResult result = DB.World.Query("SELECT guid, linkedGuid, linkType FROM linked_respawn ORDER BY guid ASC"); + + if (result.IsEmpty()) + { + Log.outError(LogFilter.ServerLoading, "Loaded 0 linked respawns. DB table `linked_respawn` is empty."); + return; + } + + do + { + ulong guidLow = result.Read(0); + ulong linkedGuidLow = result.Read(1); + byte linkType = result.Read(2); + + ObjectGuid guid = ObjectGuid.Empty; + ObjectGuid linkedGuid = ObjectGuid.Empty; + bool error = false; + switch ((CreatureLinkedRespawnType)linkType) + { + case CreatureLinkedRespawnType.CreatureToCreature: + { + CreatureData slave = GetCreatureData(guidLow); + if (slave == null) + { + Log.outError(LogFilter.Sql, "Couldn't get creature data for GUIDLow {0}", guidLow); + error = true; + break; + } + + CreatureData master = GetCreatureData(linkedGuidLow); + if (master == null) + { + Log.outError(LogFilter.Sql, "Couldn't get creature data for GUIDLow {0}", linkedGuidLow); + error = true; + break; + } + + MapRecord map = CliDB.MapStorage.LookupByKey(master.mapid); + if (map == null || !map.Instanceable() || (master.mapid != slave.mapid)) + { + Log.outError(LogFilter.Sql, "Creature '{0}' linking to '{1}' on an unpermitted map.", guidLow, linkedGuidLow); + error = true; + break; + } + + if (!Convert.ToBoolean(master.spawnMask & slave.spawnMask)) // they must have a possibility to meet (normal/heroic difficulty) + { + Log.outError(LogFilter.Sql, "LinkedRespawn: Creature '{0}' linking to '{1}' with not corresponding spawnMask", guidLow, linkedGuidLow); + error = true; + break; + } + + guid = ObjectGuid.Create(HighGuid.Creature, slave.mapid, slave.id, guidLow); + linkedGuid = ObjectGuid.Create(HighGuid.Creature, master.mapid, master.id, linkedGuidLow); + break; + } + case CreatureLinkedRespawnType.CreatureToGO: + { + CreatureData slave = GetCreatureData(guidLow); + if (slave == null) + { + Log.outError(LogFilter.Sql, "Couldn't get creature data for GUIDLow {0}", guidLow); + error = true; + break; + } + + GameObjectData master = GetGOData(linkedGuidLow); + if (master == null) + { + Log.outError(LogFilter.Sql, "Couldn't get gameobject data for GUIDLow {0}", linkedGuidLow); + error = true; + break; + } + + MapRecord map = CliDB.MapStorage.LookupByKey(master.mapid); + if (map == null || !map.Instanceable() || (master.mapid != slave.mapid)) + { + Log.outError(LogFilter.Sql, "Creature '{0}' linking to '{1}' on an unpermitted map.", guidLow, linkedGuidLow); + error = true; + break; + } + + if (!Convert.ToBoolean(master.spawnMask & slave.spawnMask)) // they must have a possibility to meet (normal/heroic difficulty) + { + Log.outError(LogFilter.Sql, "LinkedRespawn: Creature '{0}' linking to '{1}' with not corresponding spawnMask", guidLow, linkedGuidLow); + error = true; + break; + } + + guid = ObjectGuid.Create(HighGuid.Creature, slave.mapid, slave.id, guidLow); + linkedGuid = ObjectGuid.Create(HighGuid.GameObject, master.mapid, master.id, linkedGuidLow); + break; + } + case CreatureLinkedRespawnType.GOToGO: + { + GameObjectData slave = GetGOData(guidLow); + if (slave == null) + { + Log.outError(LogFilter.Sql, "Couldn't get gameobject data for GUIDLow {0}", guidLow); + error = true; + break; + } + + GameObjectData master = GetGOData(linkedGuidLow); + if (master == null) + { + Log.outError(LogFilter.Sql, "Couldn't get gameobject data for GUIDLow {0}", linkedGuidLow); + error = true; + break; + } + + MapRecord map = CliDB.MapStorage.LookupByKey(master.mapid); + if (map == null || !map.Instanceable() || (master.mapid != slave.mapid)) + { + Log.outError(LogFilter.Sql, "Creature '{0}' linking to '{1}' on an unpermitted map.", guidLow, linkedGuidLow); + error = true; + break; + } + + if (!Convert.ToBoolean(master.spawnMask & slave.spawnMask)) // they must have a possibility to meet (normal/heroic difficulty) + { + Log.outError(LogFilter.Sql, "LinkedRespawn: Creature '{0}' linking to '{1}' with not corresponding spawnMask", guidLow, linkedGuidLow); + error = true; + break; + } + + guid = ObjectGuid.Create(HighGuid.GameObject, slave.mapid, slave.id, guidLow); + linkedGuid = ObjectGuid.Create(HighGuid.GameObject, master.mapid, master.id, linkedGuidLow); + break; + } + case CreatureLinkedRespawnType.GOToCreature: + { + GameObjectData slave = GetGOData(guidLow); + if (slave == null) + { + Log.outError(LogFilter.Sql, "Couldn't get gameobject data for GUIDLow {0}", guidLow); + error = true; + break; + } + + CreatureData master = GetCreatureData(linkedGuidLow); + if (master == null) + { + Log.outError(LogFilter.Sql, "Couldn't get creature data for GUIDLow {0}", linkedGuidLow); + error = true; + break; + } + + MapRecord map = CliDB.MapStorage.LookupByKey(master.mapid); + if (map == null || !map.Instanceable() || (master.mapid != slave.mapid)) + { + Log.outError(LogFilter.Sql, "Creature '{0}' linking to '{1}' on an unpermitted map.", guidLow, linkedGuidLow); + error = true; + break; + } + + if (!Convert.ToBoolean(master.spawnMask & slave.spawnMask)) // they must have a possibility to meet (normal/heroic difficulty) + { + Log.outError(LogFilter.Sql, "LinkedRespawn: Creature '{0}' linking to '{1}' with not corresponding spawnMask", guidLow, linkedGuidLow); + error = true; + break; + } + + guid = ObjectGuid.Create(HighGuid.GameObject, slave.mapid, slave.id, guidLow); + linkedGuid = ObjectGuid.Create(HighGuid.Creature, master.mapid, master.id, linkedGuidLow); + break; + } + + } + + if (!error) + linkedRespawnStorage[guid] = linkedGuid; + } while (result.NextRow()); + + Log.outInfo(LogFilter.ServerLoading, "Loaded {0} linked respawns in {1} ms", linkedRespawnStorage.Count, Time.GetMSTimeDiffToNow(oldMSTime)); + } + public void LoadNPCText() + { + uint oldMSTime = Time.GetMSTime(); + + _npcTextStorage.Clear(); + + SQLResult result = DB.World.Query("SELECT ID, Probability0, Probability1, Probability2, Probability3, Probability4, Probability5, Probability6, Probability7, " + + "BroadcastTextID0, BroadcastTextID1, BroadcastTextID2, BroadcastTextID3, BroadcastTextID4, BroadcastTextID5, BroadcastTextID6, BroadcastTextID7 FROM npc_text"); + + if (result.IsEmpty()) + { + Log.outInfo(LogFilter.ServerLoading, "Loaded 0 npc texts, table is empty!"); + return; + } + + do + { + uint textID = result.Read(0); + if (textID == 0) + { + Log.outError(LogFilter.Sql, "Table `npc_text` has record wit reserved id 0, ignore."); + continue; + } + + NpcText npcText = new NpcText(); + for (int i = 0; i < SharedConst.MaxNpcTextOptions; i++) + { + npcText.Data[i].Probability = result.Read(1 + i); + npcText.Data[i].BroadcastTextID = result.Read(9 + i); + } + + for (int i = 0; i < SharedConst.MaxNpcTextOptions; i++) + { + if (npcText.Data[i].BroadcastTextID != 0) + { + if (!CliDB.BroadcastTextStorage.ContainsKey(npcText.Data[i].BroadcastTextID)) + { + Log.outError(LogFilter.Sql, "NPCText (Id: {0}) has a non-existing or incompatible BroadcastText (ID: {1}, Index: {2})", textID, npcText.Data[i].BroadcastTextID, i); + npcText.Data[i].BroadcastTextID = 0; + } + } + } + + for (byte i = 0; i < SharedConst.MaxNpcTextOptions; i++) + { + if (npcText.Data[i].Probability > 0 && npcText.Data[i].BroadcastTextID == 0) + { + Log.outError(LogFilter.Sql, "NPCText (ID: {0}) has a probability (Index: {1}) set, but no BroadcastTextID to go with it", textID, i); + npcText.Data[i].Probability = 0; + } + } + _npcTextStorage[textID] = npcText; + } while (result.NextRow()); + + Log.outInfo(LogFilter.ServerLoading, "Loaded {0} npc texts in {1} ms", _npcTextStorage.Count, Time.GetMSTimeDiffToNow(oldMSTime)); + } + + public void LoadTrainerSpell() + { + var time = Time.GetMSTime(); + SQLResult result = DB.World.Query("SELECT b.ID, a.SpellID, a.MoneyCost, a.ReqSkillLine, a.ReqSkillRank, a.Reqlevel, a.Index FROM npc_trainer AS a " + + "INNER JOIN npc_trainer AS b ON a.ID = -(b.SpellID) UNION SELECT * FROM npc_trainer WHERE SpellID > 0"); + + if (result.IsEmpty()) + { + Log.outError(LogFilter.ServerLoading, "Loaded 0 Trainers. DB table `npc_trainer` is empty!"); + return; + } + + uint count = 0; + do + { + uint ID = result.Read(0); + uint SpellID = result.Read(1); + uint MoneyCost = result.Read(2); + uint ReqSkillLine = result.Read(3); + uint ReqSkillRank = result.Read(4); + uint Reqlevel = result.Read(5); + uint Index = result.Read(6); + + AddSpellToTrainer(ID, SpellID, MoneyCost, ReqSkillLine, ReqSkillRank, Reqlevel, Index); + count++; + } while (result.NextRow()); + + Log.outInfo(LogFilter.ServerLoading, "Loaded {0} Trainers in {1} ms", count, Time.GetMSTimeDiffToNow(time)); + } + void AddSpellToTrainer(uint ID, uint SpellID, uint MoneyCost, uint ReqSkillLine, uint ReqSkillRank, uint Reqlevel, uint Index) + { + if (ID >= 200000) + return; + + CreatureTemplate cInfo = GetCreatureTemplate(ID); + if (cInfo == null) + { + Log.outError(LogFilter.Sql, "Table `npc_trainer` contains entries for a non-existing creature template (ID: {0}), ignoring", ID); + return; + } + + if (!cInfo.Npcflag.HasAnyFlag(NPCFlags.Trainer)) + { + Log.outError(LogFilter.Sql, "Table `npc_trainer` contains entries for a creature template (ID: {0}) without trainer flag, ignoring", ID); + return; + } + + SpellInfo spellinfo = Global.SpellMgr.GetSpellInfo(SpellID); + if (spellinfo == null) + { + Log.outError(LogFilter.Sql, "Table `npc_trainer` contains an ID ({0}) for a non-existing spell (Spell: {1}), ignoring", ID, SpellID); + return; + } + + if (!Global.SpellMgr.IsSpellValid(spellinfo)) + { + Log.outError(LogFilter.Sql, "Table `npc_trainer` contains an ID ({0}) for a broken spell (Spell: {1}), ignoring", ID, SpellID); + return; + } + + if (cacheTrainerSpellStorage.LookupByKey(ID) == null) + cacheTrainerSpellStorage.Add(ID, new TrainerSpellData()); + + TrainerSpellData data = cacheTrainerSpellStorage[ID]; + TrainerSpell trainerSpell = new TrainerSpell(); + trainerSpell.SpellID = SpellID; + trainerSpell.MoneyCost = MoneyCost; + trainerSpell.ReqSkillLine = ReqSkillLine; + trainerSpell.ReqSkillRank = ReqSkillRank; + trainerSpell.ReqLevel = Reqlevel; + trainerSpell.Index = Index; + + if (trainerSpell.ReqLevel == 0) + trainerSpell.ReqLevel = spellinfo.SpellLevel; + + // calculate learned spell for profession case when stored cast-spell + trainerSpell.ReqAbility[0] = SpellID; + + foreach (SpellEffectInfo effect in spellinfo.GetEffectsForDifficulty(Difficulty.None)) + { + if (effect == null || effect.Effect != SpellEffectName.LearnSpell) + continue; + + if (trainerSpell.ReqAbility[0] == SpellID) + trainerSpell.ReqAbility[0] = 0; + // player must be able to cast spell on himself + if (effect.TargetA.GetTarget() != 0 && effect.TargetA.GetTarget() != Targets.UnitAlly + && effect.TargetA.GetTarget() != Targets.UnitAny && effect.TargetA.GetTarget() != Targets.UnitCaster) + { + Log.outError(LogFilter.Sql, "Table `npc_trainer` has spell {0} for trainer entry {1} with learn effect which has incorrect target type, ignoring learn effect!", SpellID, ID); + continue; + } + + trainerSpell.ReqAbility[effect.EffectIndex] = effect.TriggerSpell; + + if (trainerSpell.ReqAbility[effect.EffectIndex] != 0) + { + SpellInfo learnedSpellInfo = Global.SpellMgr.GetSpellInfo(trainerSpell.ReqAbility[effect.EffectIndex]); + if (learnedSpellInfo != null && learnedSpellInfo.IsProfession()) + data.trainerType = 2; + } + } + data.spellList[SpellID] = trainerSpell; + return; + } + public void LoadVendors() + { + var time = Time.GetMSTime(); + // For reload case + cacheVendorItemStorage.Clear(); + + List skipvendors = new List(); + + SQLResult result = DB.World.Query("SELECT entry, item, maxcount, incrtime, ExtendedCost, type FROM npc_vendor ORDER BY entry, slot ASC"); + if (result.IsEmpty()) + { + Log.outError(LogFilter.ServerLoading, "Loaded 0 Vendors. DB table `npc_vendor` is empty!"); + return; + } + + uint count = 0; + + do + { + uint entry = result.Read(0); + int itemid = result.Read(1); + + // if item is a negative, its a reference + if (itemid < 0) + count += LoadReferenceVendor((int)entry, -itemid, 0, skipvendors); + else + { + int maxcount = result.Read(2); + uint incrtime = result.Read(3); + uint ExtendedCost = result.Read(4); + ItemVendorType type = (ItemVendorType)result.Read(5); + + if (!IsVendorItemValid(entry, (uint)itemid, maxcount, incrtime, ExtendedCost, type, null, skipvendors)) + continue; + + if (cacheVendorItemStorage.LookupByKey(entry) == null) + cacheVendorItemStorage.Add(entry, new VendorItemData()); + + cacheVendorItemStorage[entry].AddItem((uint)itemid, maxcount, incrtime, ExtendedCost, type); + ++count; + } + } while (result.NextRow()); + + Log.outInfo(LogFilter.ServerLoading, "Loaded {0} Vendors in {1} ms", count, Time.GetMSTimeDiffToNow(time)); + } + uint LoadReferenceVendor(int vendor, int item, byte type, List skip_vendors) + { + // find all items from the reference vendor + PreparedStatement stmt = DB.World.GetPreparedStatement(WorldStatements.SEL_NPC_VENDOR_REF); + stmt.AddValue(0, item); + stmt.AddValue(1, type); + SQLResult result = DB.World.Query(stmt); + + if (result.IsEmpty()) + return 0; + + uint count = 0; + do + { + int item_id = result.Read(0); + + // if item is a negative, its a reference + if (item_id < 0) + count += LoadReferenceVendor(vendor, -item_id, type, skip_vendors); + else + { + int maxcount = result.Read(1); + uint incrtime = result.Read(2); + uint ExtendedCost = result.Read(3); + ItemVendorType _type = (ItemVendorType)result.Read(4); + + if (!IsVendorItemValid((uint)vendor, (uint)item_id, maxcount, incrtime, ExtendedCost, _type, null, skip_vendors)) + continue; + + VendorItemData vList = cacheVendorItemStorage[(uint)vendor]; + + vList.AddItem((uint)item_id, maxcount, incrtime, ExtendedCost, _type); + ++count; + } + } while (result.NextRow()); + + return count; + } + public void LoadCreatures() + { + var time = Time.GetMSTime(); + + // 0 1 2 3 4 5 6 7 8 9 10 + SQLResult result = DB.World.Query("SELECT creature.guid, id, map, modelid, equipment_id, position_x, position_y, position_z, orientation, spawntimesecs, spawndist, " + + // 11 12 13 14 15 16 17 18 19 20 21 + "currentwaypoint, curhealth, curmana, MovementType, spawnMask, eventEntry, pool_entry, creature.npcflag, creature.unit_flags, creature.unit_flags2, creature.unit_flags3, " + + // 22 23 24 25 + "creature.dynamicflags, creature.phaseid, creature.phasegroup, creature.ScriptName " + + "FROM creature LEFT OUTER JOIN game_event_creature ON creature.guid = game_event_creature.guid LEFT OUTER JOIN pool_creature ON creature.guid = pool_creature.guid"); + + if (result.IsEmpty()) + { + Log.outError(LogFilter.ServerLoading, "Loaded 0 creatures. DB table `creature` is empty."); + return; + } + + Dictionary spawnMasks = new Dictionary(); + foreach (var mapDifficultyPair in Global.DB2Mgr.GetMapDifficulties()) + { + foreach (var difficultyPair in mapDifficultyPair.Value) + { + if (!spawnMasks.ContainsKey(mapDifficultyPair.Key)) + spawnMasks[mapDifficultyPair.Key] = 0; + + spawnMasks[mapDifficultyPair.Key] |= (uint)(1 << (int)difficultyPair.Key); + } + } + + uint count = 0; + do + { + ulong guid = result.Read(0); + uint entry = result.Read(1); + + CreatureTemplate cInfo = GetCreatureTemplate(entry); + if (cInfo == null) + { + Log.outError(LogFilter.Sql, "Table `creature` has creature (GUID: {0}) with non existing creature entry {1}, skipped.", guid, entry); + continue; + } + + CreatureData data = new CreatureData(); + data.id = entry; + data.mapid = result.Read(2); + data.displayid = result.Read(3); + data.equipmentId = result.Read(4); + data.posX = result.Read(5); + data.posY = result.Read(6); + data.posZ = result.Read(7); + data.orientation = result.Read(8); + data.spawntimesecs = result.Read(9); + data.spawndist = result.Read(10); + data.currentwaypoint = result.Read(11); + data.curhealth = result.Read(12); + data.curmana = result.Read(13); + data.movementType = result.Read(14); + data.spawnMask = result.Read(15); + short gameEvent = result.Read(16); + uint PoolId = result.Read(17); + data.npcflag = result.Read(18); + data.unit_flags = result.Read(19); + data.unit_flags2 = result.Read(20); + data.unit_flags3 = result.Read(21); + data.dynamicflags = result.Read(22); + data.phaseid = result.Read(23); + data.phaseGroup = result.Read(24); + data.ScriptId = GetScriptId(result.Read(25)); + if (data.ScriptId == 0) + data.ScriptId = cInfo.ScriptID; + + var mapEntry = CliDB.MapStorage.LookupByKey(data.mapid); + if (mapEntry == null) + { + Log.outError(LogFilter.Sql, "Table `creature` have creature (GUID: {0}) that spawned at not existed map (Id: {1}), skipped.", guid, data.mapid); + continue; + } + + //if (!_transportMaps.Contains(data.mapid) && (spawnMasks.ContainsKey(data.mapid) && Convert.ToBoolean(data.spawnMask & ~spawnMasks[data.mapid]))) + //Log.outError(LogFilter.Sql, "Table `creature` have creature (GUID: {0}) that have wrong spawn mask {1} including not supported difficulty modes for map (Id: {2}) spawnMasks[data.mapid]: {3}.", guid, data.spawnMask, data.mapid, spawnMasks[data.mapid]); + + bool ok = true; + for (uint diff = 0; diff < SharedConst.MaxCreatureDifficulties && ok; ++diff) + { + if (_difficultyEntries[diff].Contains(data.id)) + { + Log.outError(LogFilter.Sql, "Table `creature` have creature (GUID: {0}) that listed as difficulty {1} template (entry: {2}) in `creaturetemplate`, skipped.", guid, diff + 1, data.id); + ok = false; + } + } + if (!ok) + continue; + + // -1 random, 0 no equipment, + if (data.equipmentId != 0) + { + if (GetEquipmentInfo(data.id, data.equipmentId) == null) + { + Log.outError(LogFilter.Sql, "Table `creature` have creature (Entry: {0}) with equipmentid {1} not found in table `creatureequiptemplate`, set to no equipment.", data.id, data.equipmentId); + data.equipmentId = 0; + } + } + + if (cInfo.FlagsExtra.HasAnyFlag(CreatureFlagsExtra.InstanceBind)) + { + if (!mapEntry.IsDungeon()) + Log.outError(LogFilter.Sql, "Table `creature` have creature (GUID: {0} Entry: {1}) with `creature_template`.`flagsextra` including CREATUREFLAGEXTRAINSTANCEBIND " + + "but creature are not in instance.", guid, data.id); + } + + if (data.spawndist < 0.0f) + { + Log.outError(LogFilter.Sql, "Table `creature` have creature (GUID: {0} Entry: {1}) with `spawndist`< 0, set to 0.", guid, data.id); + data.spawndist = 0.0f; + } + else if (data.movementType == (byte)MovementGeneratorType.Random) + { + if (MathFunctions.fuzzyEq(data.spawndist, 0.0f)) + { + Log.outError(LogFilter.Sql, "Table `creature` have creature (GUID: {0} Entry: {1}) with `MovementType`=1 (random movement) but with `spawndist`=0, replace by idle movement type (0).", guid, data.id); + data.movementType = (byte)MovementGeneratorType.Idle; + } + } + else if (data.movementType == (byte)MovementGeneratorType.Idle) + { + if (data.spawndist != 0.0f) + { + Log.outError(LogFilter.Sql, "Table `creature` have creature (GUID: {0} Entry: {1}) with `MovementType`=0 (idle) have `spawndist`<>0, set to 0.", guid, data.id); + data.spawndist = 0.0f; + } + } + + if (Math.Abs(data.orientation) > 2 * MathFunctions.PI) + { + Log.outError(LogFilter.Sql, "Table `creature` has creature (GUID: {0} Entry: {1}) with abs(`orientation`) > 2*PI (orientation is expressed in radians), normalized.", guid, data.id); + data.orientation = Position.NormalizeOrientation(data.orientation); + } + + data.phaseMask = 1; + if (data.phaseGroup != 0 && data.phaseid != 0) + { + Log.outError(LogFilter.Sql, "Table `creature` have creature (GUID: {0} Entry: {1}) with both `phaseid` and `phasegroup` set, `phasegroup` set to 0", guid, data.id); + data.phaseGroup = 0; + } + + if (data.phaseid != 0) + { + if (!CliDB.PhaseStorage.ContainsKey(data.phaseid)) + { + Log.outError(LogFilter.Sql, "Table `creature` have creature (GUID: {0} Entry: {1}) with `phaseid` {2} does not exist, set to 0", guid, data.id, data.phaseid); + data.phaseid = 0; + } + } + + if (data.phaseGroup != 0) + { + if (Global.DB2Mgr.GetPhasesForGroup(data.phaseGroup).Empty()) + { + Log.outError(LogFilter.Sql, "Table `creature` have creature (GUID: {0} Entry: {1}) with `phasegroup` {2} does not exist, set to 0", guid, data.id, data.phaseGroup); + data.phaseGroup = 0; + } + } + + if (WorldConfig.GetBoolValue(WorldCfg.CalculateCreatureZoneAreaData)) + { + uint zoneId = 0; + uint areaId = 0; + Global.MapMgr.GetZoneAndAreaId(out zoneId, out areaId, data.mapid, data.posX, data.posY, data.posZ); + + PreparedStatement stmt = DB.World.GetPreparedStatement(WorldStatements.UPD_CREATURE_ZONE_AREA_DATA); + stmt.AddValue(0, zoneId); + stmt.AddValue(1, areaId); + stmt.AddValue(2, guid); + + DB.World.Execute(stmt); + } + + // Add to grid if not managed by the game event or pool system + if (gameEvent == 0 && PoolId == 0) + AddCreatureToGrid(guid, data); + + creatureDataStorage[guid] = data; + count++; + } while (result.NextRow()); + + Log.outInfo(LogFilter.ServerLoading, "Loaded {0} creatures in {1} ms", count, Time.GetMSTimeDiffToNow(time)); + } + public void AddCreatureToGrid(ulong guid, CreatureData data) + { + uint mask = data.spawnMask; + for (byte i = 0; mask != 0; i++, mask >>= 1) + { + if (Convert.ToBoolean(mask & 1)) + { + CellCoord cellCoord = GridDefines.ComputeCellCoord(data.posX, data.posY); + var cellguids = CreateCellObjectGuids(data.mapid, i, cellCoord); + cellguids.creatures.Add(guid); + } + } + } + public void RemoveCreatureFromGrid(ulong guid, CreatureData data) + { + uint mask = data.spawnMask; + for (byte i = 0; mask != 0; i++, mask >>= 1) + { + if (Convert.ToBoolean(mask & 1)) + { + CellCoord cellCoord = GridDefines.ComputeCellCoord(data.posX, data.posY); + CellObjectGuids cellguids = GetCellObjectGuids(data.mapid, i, cellCoord.GetId()); + if (cellguids == null) + return; + + cellguids.creatures.Remove(guid); + } + } + } + public ulong AddCreatureData(uint entry, uint team, uint mapId, float x, float y, float z, float o, uint spawntimedelay) + { + CreatureTemplate cInfo = GetCreatureTemplate(entry); + if (cInfo == null) + return 0; + + uint level = cInfo.Minlevel == cInfo.Maxlevel ? (uint)cInfo.Minlevel : RandomHelper.URand(cInfo.Minlevel, cInfo.Maxlevel); // Only used for extracting creature base stats + CreatureBaseStats stats = GetCreatureBaseStats(level, cInfo.UnitClass); + Map map = Global.MapMgr.CreateBaseMap(mapId); + if (!map) + return 0; + + ulong guid = GenerateCreatureSpawnId(); + CreatureData data = NewOrExistCreatureData(guid); + data.id = entry; + data.mapid = (ushort)mapId; + data.displayid = 0; + data.equipmentId = 0; + data.posX = x; + data.posY = y; + data.posZ = z; + data.orientation = o; + data.spawntimesecs = spawntimedelay; + data.spawndist = 0; + data.currentwaypoint = 0; + data.curhealth = stats.GenerateHealth(cInfo); + data.curmana = stats.GenerateMana(cInfo); + data.movementType = (byte)cInfo.MovementType; + data.spawnMask = 1; + data.phaseMask = PhaseMasks.Normal; + data.dbData = false; + data.npcflag = (uint)cInfo.Npcflag; + data.unit_flags = (uint)cInfo.UnitFlags; + data.dynamicflags = cInfo.DynamicFlags; + + AddCreatureToGrid(guid, data); + + // We use spawn coords to spawn + if (!map.Instanceable() && !map.IsRemovalGrid(x, y)) + { + Creature creature = new Creature(); + if (!creature.LoadCreatureFromDB(guid, map)) + { + Log.outError(LogFilter.Server, "AddCreature: Cannot add creature entry {0} to map", entry); + return 0; + } + } + + return guid; + } + public List GetCreatureQuestItemList(uint id) + { + return _creatureQuestItemStorage.LookupByKey(id); + } + public CreatureAddon GetCreatureAddon(ulong lowguid) + { + return creatureAddonStorage.LookupByKey(lowguid); + } + public CreatureTemplate GetCreatureTemplate(uint entry) + { + return creatureTemplateStorage.LookupByKey(entry); + } + public CreatureAddon GetCreatureTemplateAddon(uint entry) + { + return creatureTemplateAddonStorage.LookupByKey(entry); + } + + public Dictionary GetCreatureTemplates() + { + return creatureTemplateStorage; + } + public CreatureData GetCreatureData(ulong guid) + { + return creatureDataStorage.LookupByKey(guid); + } + public ObjectGuid GetLinkedRespawnGuid(ObjectGuid guid) + { + var retGuid = linkedRespawnStorage.LookupByKey(guid); + if (retGuid.IsEmpty()) + return ObjectGuid.Empty; + return retGuid; + } + public bool SetCreatureLinkedRespawn(ulong guidLow, ulong linkedGuidLow) + { + if (guidLow == 0) + return false; + + CreatureData master = GetCreatureData(guidLow); + ObjectGuid guid = ObjectGuid.Create(HighGuid.Creature, master.mapid, master.id, guidLow); + PreparedStatement stmt; + + if (linkedGuidLow == 0) // we're removing the linking + { + linkedRespawnStorage.Remove(guid); + stmt = DB.World.GetPreparedStatement(WorldStatements.DEL_CRELINKED_RESPAWN); + stmt.AddValue(0, guidLow); + DB.World.Execute(stmt); + return true; + } + + CreatureData slave = GetCreatureData(linkedGuidLow); + if (slave == null) + { + Log.outError(LogFilter.Sql, "Creature '{0}' linking to non-existent creature '{1}'.", guidLow, linkedGuidLow); + return false; + } + + MapRecord map = CliDB.MapStorage.LookupByKey(master.mapid); + if (map == null || !map.Instanceable() || (master.mapid != slave.mapid)) + { + Log.outError(LogFilter.Sql, "Creature '{0}' linking to '{1}' on an unpermitted map.", guidLow, linkedGuidLow); + return false; + } + + if (!Convert.ToBoolean(master.spawnMask & slave.spawnMask)) // they must have a possibility to meet (normal/heroic difficulty) + { + Log.outError(LogFilter.Sql, "LinkedRespawn: Creature '{0}' linking to '{1}' with not corresponding spawnMask", guidLow, linkedGuidLow); + return false; + } + + ObjectGuid linkedGuid = ObjectGuid.Create(HighGuid.Creature, slave.mapid, slave.id, linkedGuidLow); + + linkedRespawnStorage[guid] = linkedGuid; + stmt = DB.World.GetPreparedStatement(WorldStatements.REP_CREATURE_LINKED_RESPAWN); + stmt.AddValue(0, guidLow); + stmt.AddValue(1, linkedGuidLow); + DB.World.Execute(stmt); + return true; + } + public CreatureData NewOrExistCreatureData(ulong guid) + { + if (!creatureDataStorage.ContainsKey(guid)) + creatureDataStorage[guid] = new CreatureData(); + return creatureDataStorage[guid]; + } + public void DeleteCreatureData(ulong guid) + { + CreatureData data = GetCreatureData(guid); + if (data != null) + RemoveCreatureFromGrid(guid, data); + + creatureDataStorage.Remove(guid); + } + public CreatureBaseStats GetCreatureBaseStats(uint level, uint unitClass) + { + var stats = creatureBaseStatsStorage.LookupByKey(MathFunctions.MakePair16(level, unitClass)); + if (stats != null) + return stats; + + return new DefaultCreatureBaseStats(); + } + public CreatureModelInfo GetCreatureModelRandomGender(ref uint displayID) + { + CreatureModelInfo modelInfo = GetCreatureModelInfo(displayID); + if (modelInfo == null) + return null; + + // If a model for another gender exists, 50% chance to use it + if (modelInfo.DisplayIdOtherGender != 0 && RandomHelper.NextDouble() == 0) + { + CreatureModelInfo minfotmp = GetCreatureModelInfo(modelInfo.DisplayIdOtherGender); + if (minfotmp == null) + Log.outError(LogFilter.Sql, "Model (Entry: {0}) has modelidothergender {1} not found in table `creaturemodelinfo`. ", displayID, modelInfo.DisplayIdOtherGender); + else + { + // DisplayID changed + displayID = modelInfo.DisplayIdOtherGender; + return minfotmp; + } + } + + return modelInfo; + } + public CreatureModelInfo GetCreatureModelInfo(uint modelId) + { + return creatureModelStorage.LookupByKey(modelId); + } + public TrainerSpellData GetNpcTrainerSpells(uint entry) + { + return cacheTrainerSpellStorage.LookupByKey(entry); + } + public NpcText GetNpcText(uint textId) + { + return _npcTextStorage.LookupByKey(textId); + } + + //GameObjects + public void LoadGameObjectTemplate() + { + var time = Time.GetMSTime(); + + foreach (GameObjectsRecord db2go in CliDB.GameObjectsStorage.Values) + { + GameObjectTemplate go = new GameObjectTemplate(); + go.entry = db2go.Id; + go.type = db2go.Type; + go.displayId = db2go.DisplayID; + go.name = db2go.Name[Global.WorldMgr.GetDefaultDbcLocale()]; + go.size = db2go.Size; + + unsafe + { + fixed (int* b = go.Raw.data) + { + for (byte x = 0; x < db2go.Data.Length; ++x) + b[x] = db2go.Data[x]; + } + } + + go.RequiredLevel = 0; + go.ScriptId = 0; + + gameObjectTemplateStorage[db2go.Id] = go; + } + + // 0 1 2 3 4 5 6 7 + SQLResult result = DB.World.Query("SELECT entry, type, displayId, name, IconName, castBarCaption, unk1, size, " + + //8 9 10 11 12 13 14 15 16 17 18 19 20 + "Data0, Data1, Data2, Data3, Data4, Data5, Data6, Data7, Data8, Data9, Data10, Data11, Data12, " + + //21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 + "Data13, Data14, Data15, Data16, Data17, Data18, Data19, Data20, Data21, Data22, Data23, Data24, Data25, Data26, Data27, Data28, " + + //37 38 39 40 41 42 43 + "Data29, Data30, Data31, Data32, RequiredLevel, AIName, ScriptName FROM gameobject_template"); + + if (result.IsEmpty()) + { + Log.outError(LogFilter.ServerLoading, "Loaded 0 gameobject definitions. DB table `gameobject_template` is empty."); + } + else + { + uint count = 0; + do + { + uint entry = result.Read(0); + + GameObjectTemplate got = new GameObjectTemplate(); + + got.entry = entry; + got.type = (GameObjectTypes)result.Read(1); + got.displayId = result.Read(2); + got.name = result.Read(3); + got.IconName = result.Read(4); + got.castBarCaption = result.Read(5); + got.unk1 = result.Read(6); + got.size = result.Read(7); + + unsafe + { + fixed (int* b = got.Raw.data) + { + for (byte x = 0; x < SharedConst.MaxGOData; ++x) + b[x] = result.Read(8 + x); + } + } + + got.RequiredLevel = result.Read(41); + got.AIName = result.Read(42); + got.ScriptId = Global.ObjectMgr.GetScriptId(result.Read(43)); + + switch (got.type) + { + case GameObjectTypes.Door: //0 + if (got.Door.open != 0) + CheckGOLockId(got, got.Door.open, 1); + CheckGONoDamageImmuneId(got, got.Door.noDamageImmune, 3); + break; + case GameObjectTypes.Button: //1 + if (got.Button.open != 0) + CheckGOLockId(got, got.Button.open, 1); + CheckGONoDamageImmuneId(got, got.Button.noDamageImmune, 4); + break; + case GameObjectTypes.QuestGiver: //2 + if (got.QuestGiver.open != 0) + CheckGOLockId(got, got.QuestGiver.open, 0); + CheckGONoDamageImmuneId(got, got.QuestGiver.noDamageImmune, 5); + break; + case GameObjectTypes.Chest: //3 + if (got.Chest.open != 0) + CheckGOLockId(got, got.Chest.open, 0); + + CheckGOConsumable(got, got.Chest.consumable, 3); + + if (got.Chest.linkedTrap != 0) // linked trap + CheckGOLinkedTrapId(got, got.Chest.linkedTrap, 7); + break; + case GameObjectTypes.Trap: //6 + if (got.Trap.open != 0) + CheckGOLockId(got, got.Trap.open, 0); + break; + case GameObjectTypes.Chair: //7 + CheckAndFixGOChairHeightId(got, ref got.Chair.chairheight, 1); + break; + case GameObjectTypes.SpellFocus: //8 + if (got.SpellFocus.spellFocusType != 0) + { + if (!CliDB.SpellFocusObjectStorage.ContainsKey(got.SpellFocus.spellFocusType)) + Log.outError(LogFilter.Sql, "GameObject (Entry: {0} GoType: {1}) have data0={2} but SpellFocus (Id: {3}) not exist.", + entry, got.type, got.SpellFocus.spellFocusType, got.SpellFocus.spellFocusType); + } + + if (got.SpellFocus.linkedTrap != 0) // linked trap + CheckGOLinkedTrapId(got, got.SpellFocus.linkedTrap, 2); + break; + case GameObjectTypes.Goober: //10 + if (got.Goober.open != 0) + CheckGOLockId(got, got.Goober.open, 0); + + CheckGOConsumable(got, got.Goober.consumable, 3); + + if (got.Goober.pageID != 0) // pageId + { + if (GetPageText(got.Goober.pageID) == null) + Log.outError(LogFilter.Sql, "GameObject (Entry: {0} GoType: {1}) have data7={2} but PageText (Entry {3}) not exist.", entry, got.type, got.Goober.pageID, got.Goober.pageID); + } + CheckGONoDamageImmuneId(got, got.Goober.noDamageImmune, 11); + if (got.Goober.linkedTrap != 0) // linked trap + CheckGOLinkedTrapId(got, got.Goober.linkedTrap, 12); + break; + case GameObjectTypes.AreaDamage: //12 + if (got.AreaDamage.open != 0) + CheckGOLockId(got, got.AreaDamage.open, 0); + break; + case GameObjectTypes.Camera: //13 + if (got.Camera.open != 0) + CheckGOLockId(got, got.Camera.open, 0); + break; + case GameObjectTypes.MapObjTransport: //15 + { + if (got.MoTransport.taxiPathID != 0) + { + if (got.MoTransport.taxiPathID >= CliDB.TaxiPathNodesByPath.Count || CliDB.TaxiPathNodesByPath[got.MoTransport.taxiPathID].Empty()) + Log.outError(LogFilter.Sql, "GameObject (Entry: {0} GoType: {1}) have data0={2} but TaxiPath (Id: {3}) not exist.", + entry, got.type, got.MoTransport.taxiPathID, got.MoTransport.taxiPathID); + } + int transportMap = got.MoTransport.SpawnMap; + if (transportMap != 0) + _transportMaps.Add((ushort)transportMap); + break; + } + case GameObjectTypes.SpellCaster: //22 + // always must have spell + CheckGOSpellId(got, got.SpellCaster.spell, 0); + break; + case GameObjectTypes.FlagStand: //24 + if (got.FlagStand.open != 0) + CheckGOLockId(got, got.FlagStand.open, 0); + CheckGONoDamageImmuneId(got, got.FlagStand.noDamageImmune, 5); + break; + case GameObjectTypes.FishingHole: //25 + if (got.FishingHole.open != 0) + CheckGOLockId(got, got.FishingHole.open, 4); + break; + case GameObjectTypes.FlagDrop: //26 + if (got.FlagDrop.open != 0) + CheckGOLockId(got, got.FlagDrop.open, 0); + CheckGONoDamageImmuneId(got, got.FlagDrop.noDamageImmune, 3); + break; + case GameObjectTypes.BarberChair: //32 + CheckAndFixGOChairHeightId(got, ref got.BarberChair.chairheight, 0); + if (got.BarberChair.SitAnimKit != 0 && !CliDB.AnimKitStorage.ContainsKey(got.BarberChair.SitAnimKit)) + { + Log.outError(LogFilter.Sql, "GameObject (Entry: {0} GoType: {1}) have data2 = {2} but AnimKit.dbc (Id: {3}) not exist, set to 0.", + entry, got.type, got.BarberChair.SitAnimKit, got.BarberChair.SitAnimKit); + got.BarberChair.SitAnimKit = 0; + } + break; + case GameObjectTypes.GarrisonBuilding: + { + int transportMap = got.garrisonBuilding.SpawnMap; + if (transportMap != 0) + _transportMaps.Add((ushort)transportMap); + } + break; + case GameObjectTypes.GatheringNode: + if (got.GatheringNode.open != 0) + CheckGOLockId(got, got.GatheringNode.open, 0); + if (got.GatheringNode.linkedTrap != 0) + CheckGOLinkedTrapId(got, got.GatheringNode.linkedTrap, 20); + break; + } + + gameObjectTemplateStorage[entry] = got; + ++count; + } + while (result.NextRow()); + + Log.outInfo(LogFilter.ServerLoading, "Loaded {0} game object templates in {1} ms", count, Time.GetMSTimeDiffToNow(time)); + } + } + public void LoadGameObjectTemplateAddons() + { + uint oldMSTime = Time.GetMSTime(); + + // 0 1 2 3 4 + SQLResult result = DB.World.Query("SELECT entry, faction, flags, mingold, maxgold FROM gameobject_template_addon"); + + if (result.IsEmpty()) + { + Log.outInfo(LogFilter.ServerLoading, "Loaded 0 gameobject template addon definitions. DB table `gameobject_template_addon` is empty."); + return; + } + + uint count = 0; + do + { + uint entry = result.Read(0); + + GameObjectTemplate got = Global.ObjectMgr.GetGameObjectTemplate(entry); + if (got == null) + { + Log.outError(LogFilter.Sql, "GameObject template (Entry: {0}) does not exist but has a record in `gameobject_template_addon`", entry); + continue; + } + + GameObjectTemplateAddon gameObjectAddon = new GameObjectTemplateAddon(); + gameObjectAddon.faction = result.Read(1); + gameObjectAddon.flags = result.Read(2); + gameObjectAddon.mingold = result.Read(3); + gameObjectAddon.maxgold = result.Read(4); + + // checks + if (gameObjectAddon.faction != 0 && !CliDB.FactionTemplateStorage.ContainsKey(gameObjectAddon.faction)) + Log.outError(LogFilter.Sql, "GameObject (Entry: {0}) has invalid faction ({1}) defined in `gameobject_template_addon`.", entry, gameObjectAddon.faction); + + if (gameObjectAddon.maxgold > 0) + { + switch (got.type) + { + case GameObjectTypes.Chest: + case GameObjectTypes.FishingHole: + break; + default: + Log.outError(LogFilter.Sql, "GameObject (Entry {0} GoType: {1}) cannot be looted but has maxgold set in `gameobject_template_addon`.", entry, got.type); + break; + } + } + + _gameObjectTemplateAddonStore[entry] = gameObjectAddon; + + ++count; + } + while (result.NextRow()); + + Log.outInfo(LogFilter.ServerLoading, "Loaded {0} game object template addons in {1} ms", count, Time.GetMSTimeDiffToNow(oldMSTime)); + } + public void LoadGameobjects() + { + var time = Time.GetMSTime(); + // 0 1 2 3 4 5 6 + SQLResult result = DB.World.Query("SELECT gameobject.guid, id, map, position_x, position_y, position_z, orientation, " + + // 7 8 9 10 11 12 13 14 15 16 + "rotation0, rotation1, rotation2, rotation3, spawntimesecs, animprogress, state, spawnMask, eventEntry, pool_entry, " + + // 17 18 19 + "phaseid, phasegroup, ScriptName " + + "FROM gameobject LEFT OUTER JOIN game_event_gameobject ON gameobject.guid = game_event_gameobject.guid " + + "LEFT OUTER JOIN pool_gameobject ON gameobject.guid = pool_gameobject.guid"); + + if (result.IsEmpty()) + { + Log.outError(LogFilter.ServerLoading, "Loaded 0 gameobjects. DB table `gameobject` is empty."); + + return; + } + uint count = 0; + + // build single time for check spawnmask + Dictionary spawnMasks = new Dictionary(); + foreach (var mapDifficultyPair in Global.DB2Mgr.GetMapDifficulties()) + { + foreach (var difficultyPair in mapDifficultyPair.Value) + { + if (!spawnMasks.ContainsKey(mapDifficultyPair.Key)) + spawnMasks[mapDifficultyPair.Key] = 0; + spawnMasks[mapDifficultyPair.Key] |= (uint)(1 << (int)difficultyPair.Key); + } + } + + do + { + ulong guid = result.Read(0); + uint entry = result.Read(1); + + GameObjectTemplate gInfo = GetGameObjectTemplate(entry); + if (gInfo == null) + { + Log.outError(LogFilter.Sql, "Table `gameobject` has gameobject (GUID: {0}) with non existing gameobject entry {1}, skipped.", guid, entry); + continue; + } + + if (gInfo.displayId == 0) + { + switch (gInfo.type) + { + case GameObjectTypes.Trap: + case GameObjectTypes.SpellFocus: + break; + default: + Log.outError(LogFilter.Sql, "Gameobject (GUID: {0} Entry {1} GoType: {2}) doesn't have a displayId ({3}), not loaded.", guid, entry, gInfo.type, gInfo.displayId); + break; + } + } + + if (gInfo.displayId != 0 && !CliDB.GameObjectDisplayInfoStorage.ContainsKey(gInfo.displayId)) + { + Log.outError(LogFilter.Sql, "Gameobject (GUID: {0} Entry {1} GoType: {2}) has an invalid displayId ({3}), not loaded.", guid, entry, gInfo.type, gInfo.displayId); + continue; + } + + GameObjectData data = new GameObjectData(); + data.id = entry; + data.mapid = result.Read(2); + data.posX = result.Read(3); + data.posY = result.Read(4); + data.posZ = result.Read(5); + data.orientation = result.Read(6); + data.rotation.X = result.Read(7); + data.rotation.Y = result.Read(8); + data.rotation.Z = result.Read(9); + data.rotation.W = result.Read(10); + data.spawntimesecs = result.Read(11); + + var mapEntry = CliDB.MapStorage.LookupByKey(data.mapid); + if (mapEntry == null) + { + Log.outError(LogFilter.Sql, "Table `gameobject` has gameobject (GUID: {0} Entry: {1}) spawned on a non-existed map (Id: {2}), skip", guid, data.id, data.mapid); + continue; + } + + if (data.spawntimesecs == 0 && gInfo.IsDespawnAtAction()) + { + Log.outError(LogFilter.Sql, "Table `gameobject` has gameobject (GUID: {0} Entry: {1}) with `spawntimesecs` (0) value, but the gameobejct is marked as despawnable at action.", guid, data.id); + } + + data.animprogress = result.Read(12); + data.artKit = 0; + + uint gostate = result.Read(13); + if (gostate >= (uint)GameObjectState.Max) + { + if (gInfo.type != GameObjectTypes.Transport || gostate > (int)GameObjectState.TransportActive + SharedConst.MaxTransportStopFrames) + { + Log.outError(LogFilter.Sql, "Table `gameobject` has gameobject (GUID: {0} Entry: {1}) with invalid `state` ({2}) value, skip", guid, data.id, gostate); + continue; + } + } + data.go_state = (GameObjectState)gostate; + + data.spawnMask = result.Read(14); + + //if (!_transportMaps.Contains(data.mapid) && (spawnMasks.ContainsKey(data.mapid) && Convert.ToBoolean(data.spawnMask & ~spawnMasks[data.mapid]))) + //Log.outError(LogFilter.Sql, "Table `gameobject` has gameobject (GUID: {0} Entry: {1}) that has wrong spawn mask {2} including not supported difficulty modes for map (Id: {3}), skip", + //guid, data.id, data.spawnMask, data.mapid); + + short gameEvent = result.Read(15); + uint PoolId = result.Read(16); + data.phaseid = result.Read(17); + data.phaseGroup = result.Read(18); + + if (data.phaseGroup != 0 && data.phaseid != 0) + { + Log.outError(LogFilter.Sql, "Table `gameobject` have gameobject (GUID: {0} Entry: {1}) with both `phaseid` and `phasegroup` set, `phasegroup` set to 0", guid, data.id); + data.phaseGroup = 0; + } + + if (data.phaseid != 0) + { + if (!CliDB.PhaseStorage.ContainsKey(data.phaseid)) + { + Log.outError(LogFilter.Sql, "Table `gameobject` have gameobject (GUID: {0} Entry: {1}) with `phaseid` {2} does not exist, set to 0", guid, data.id, data.phaseid); + data.phaseid = 0; + } + } + + if (data.phaseGroup != 0) + { + if (Global.DB2Mgr.GetPhasesForGroup(data.phaseGroup).Empty()) + { + Log.outError(LogFilter.Sql, "Table `gameobject` have gameobject (GUID: {0} Entry: {1}) with `phaseGroup` {2} does not exist, set to 0", guid, data.id, data.phaseGroup); + data.phaseGroup = 0; + } + } + + data.ScriptId = GetScriptId(result.Read(19)); + if (data.ScriptId == 0) + data.ScriptId = gInfo.ScriptId; + + if (Math.Abs(data.orientation) > 2 * MathFunctions.PI) + { + Log.outError(LogFilter.Sql, "Table `gameobject` has gameobject (GUID: {0} Entry: {1}) with abs(`orientation`) > 2*PI (orientation is expressed in radians), normalized.", guid, data.id); + data.orientation = Position.NormalizeOrientation(data.orientation); + } + + if (data.rotation.X < -1.0f || data.rotation.X > 1.0f) + { + Log.outError(LogFilter.Sql, "Table `gameobject` has gameobject (GUID: {0} Entry: {1}) with invalid rotationX ({1}) value, skip", guid, data.id, data.rotation.X); + continue; + } + + if (data.rotation.Y < -1.0f || data.rotation.Y > 1.0f) + { + Log.outError(LogFilter.Sql, "Table `gameobject` has gameobject (GUID: {0} Entry: {1}) with invalid rotationY ({2}) value, skip", guid, data.id, data.rotation.Y); + continue; + } + + if (data.rotation.Z < -1.0f || data.rotation.Z > 1.0f) + { + Log.outError(LogFilter.Sql, "Table `gameobject` has gameobject (GUID: {0} Entry: {1}) with invalid rotationZ ({3}) value, skip", guid, data.id, data.rotation.Z); + continue; + } + + if (data.rotation.W < -1.0f || data.rotation.W > 1.0f) + { + Log.outError(LogFilter.Sql, "Table `gameobject` has gameobject (GUID: {0} Entry: {1}) with invalid rotationW ({4}) value, skip", guid, data.id, data.rotation.W); + continue; + } + + if (!GridDefines.IsValidMapCoord(data.mapid, data.posX, data.posY, data.posZ, data.orientation)) + { + Log.outError(LogFilter.Sql, "Table `gameobject` has gameobject (GUID: {0} Entry: {1}) with invalid coordinates, skip", guid, data.id); + continue; + } + + data.phaseMask = 1; + + if (WorldConfig.GetBoolValue(WorldCfg.CalculateGameobjectZoneAreaData)) + { + uint zoneId = 0; + uint areaId = 0; + Global.MapMgr.GetZoneAndAreaId(out zoneId, out areaId, data.mapid, data.posX, data.posY, data.posZ); + + PreparedStatement stmt = DB.World.GetPreparedStatement(WorldStatements.UPD_GAMEOBJECT_ZONE_AREA_DATA); + stmt.AddValue(0, zoneId); + stmt.AddValue(1, areaId); + stmt.AddValue(2, guid); + DB.World.Execute(stmt); + } + + if (gameEvent == 0 && PoolId == 0) // if not this is to be managed by GameEvent System or Pool system + AddGameObjectToGrid(guid, data); + + gameObjectDataStorage[guid] = data; + ++count; + } while (result.NextRow()); + + Log.outInfo(LogFilter.ServerLoading, "Loaded {0} gameobjects in {1} ms", count, Time.GetMSTimeDiffToNow(time)); + } + public void LoadGameObjectAddons() + { + uint oldMSTime = Time.GetMSTime(); + + _gameObjectAddonStorage.Clear(); + + // 0 1 2 3 4 5 6 + SQLResult result = DB.World.Query("SELECT guid, parent_rotation0, parent_rotation1, parent_rotation2, parent_rotation3, invisibilityType, invisibilityValue FROM gameobject_addon"); + + if (result.IsEmpty()) + { + Log.outInfo(LogFilter.ServerLoading, "Loaded 0 gameobject addon definitions. DB table `gameobject_addon` is empty."); + return; + } + + uint count = 0; + do + { + ulong guid = result.Read(0); + + GameObjectData goData = GetGOData(guid); + if (goData == null) + { + Log.outError(LogFilter.Sql, "GameObject (GUID: {0}) does not exist but has a record in `gameobject_addon`", guid); + continue; + } + + GameObjectAddon gameObjectAddon = new GameObjectAddon(); + gameObjectAddon.ParentRotation = new Quaternion(result.Read(1), result.Read(2), result.Read(3), result.Read(4)); + gameObjectAddon.invisibilityType = (InvisibilityType)result.Read(5); + gameObjectAddon.invisibilityValue = result.Read(6); + + if (gameObjectAddon.invisibilityType >= InvisibilityType.Max) + { + Log.outError(LogFilter.Sql, "GameObject (GUID: {0}) has invalid InvisibilityType in `gameobject_addon`, disabled invisibility", guid); + gameObjectAddon.invisibilityType = InvisibilityType.General; + gameObjectAddon.invisibilityValue = 0; + } + + if (gameObjectAddon.invisibilityType != 0 && gameObjectAddon.invisibilityValue == 0) + { + Log.outError(LogFilter.Sql, "GameObject (GUID: {0}) has InvisibilityType set but has no InvisibilityValue in `gameobject_addon`, set to 1", guid); + gameObjectAddon.invisibilityValue = 1; + } + + if (!gameObjectAddon.ParentRotation.isUnit()) + { + Log.outError(LogFilter.Sql, "GameObject (GUID: {0}) has invalid parent rotation in `gameobject_addon`, set to default", guid); + gameObjectAddon.ParentRotation = Quaternion.WAxis; + } + + _gameObjectAddonStorage[guid] = gameObjectAddon; + ++count; + } + while (result.NextRow()); + + Log.outInfo(LogFilter.ServerLoading, "Loaded {0} gameobject addons in {1} ms", count, Time.GetMSTimeDiffToNow(oldMSTime)); + } + public void LoadGameObjectQuestItems() + { + uint oldMSTime = Time.GetMSTime(); + + // 0 1 + SQLResult result = DB.World.Query("SELECT GameObjectEntry, ItemId, Idx FROM gameobject_questitem ORDER BY Idx ASC"); + if (result.IsEmpty()) + { + Log.outInfo(LogFilter.ServerLoading, "Loaded 0 gameobject quest items. DB table `gameobject_questitem` is empty."); + return; + } + + uint count = 0; + do + { + uint entry = result.Read(0); + uint item = result.Read(1); + uint idx = result.Read(2); + + if (!gameObjectTemplateStorage.ContainsKey(entry)) + { + Log.outError(LogFilter.Sql, "Table `gameobject_questitem` has data for nonexistent gameobject (entry: {0}, idx: {1}), skipped", entry, idx); + continue; + } + + if (!CliDB.ItemStorage.ContainsKey(item)) + { + Log.outError(LogFilter.Sql, "Table `gameobject_questitem` has nonexistent item (ID: {0}) in gameobject (entry: {1}, idx: {2}), skipped", item, entry, idx); + continue; + } + + _gameObjectQuestItemStorage.Add(entry, item); + + ++count; + } + while (result.NextRow()); + + Log.outInfo(LogFilter.ServerLoading, "Loaded {0} gameobject quest items in {1} ms", count, Time.GetMSTimeDiffToNow(oldMSTime)); + } + public void LoadGameObjectForQuests() + { + uint oldMSTime = Time.GetMSTime(); + + _gameObjectForQuestStorage.Clear(); // need for reload case + + if (GetGameObjectTemplates().Empty()) + { + Log.outInfo(LogFilter.ServerLoading, "Loaded 0 GameObjects for quests"); + return; + } + + uint count = 0; + + // collect GO entries for GO that must activated + foreach (var go in GetGameObjectTemplates()) + { + switch (go.Value.type) + { + case GameObjectTypes.QuestGiver: + _gameObjectForQuestStorage.Add(go.Value.entry); + ++count; + break; + case GameObjectTypes.Chest: + { + // scan GO chest with loot including quest items + uint loot_id = (go.Value.GetLootId()); + + // find quest loot for GO + if (go.Value.Chest.questID != 0 || Loots.LootStorage.Gameobject.HaveQuestLootFor(loot_id)) + { + _gameObjectForQuestStorage.Add(go.Value.entry); + ++count; + } + break; + } + case GameObjectTypes.Generic: + { + if (go.Value.Generic.questID > 0) //quests objects + { + _gameObjectForQuestStorage.Add(go.Value.entry); + ++count; + } + break; + } + case GameObjectTypes.Goober: + { + if (go.Value.Goober.questID > 0) //quests objects + { + _gameObjectForQuestStorage.Add(go.Value.entry); + ++count; + } + break; + } + default: + break; + } + } + + Log.outInfo(LogFilter.ServerLoading, "Loaded {0} GameObjects for quests in {1} ms", count, Time.GetMSTimeDiffToNow(oldMSTime)); + } + public void AddGameObjectToGrid(ulong guid, GameObjectData data) + { + uint mask = data.spawnMask; + for (byte i = 0; mask != 0; i++, mask >>= 1) + { + if (Convert.ToBoolean(mask & 1)) + { + CellCoord cellCoord = GridDefines.ComputeCellCoord(data.posX, data.posY); + var cellguids = CreateCellObjectGuids(data.mapid, i, cellCoord); + cellguids.gameobjects.Add(guid); + } + } + } + public void RemoveGameObjectFromGrid(ulong guid, GameObjectData data) + { + uint mask = data.spawnMask; + for (byte i = 0; mask != 0; i++, mask >>= 1) + { + if (Convert.ToBoolean(mask & 1)) + { + CellCoord cellCoord = GridDefines.ComputeCellCoord(data.posX, data.posY); + CellObjectGuids cellguids = GetCellObjectGuids(data.mapid, i, cellCoord.GetId()); + if (cellguids == null) + return; + + cellguids.gameobjects.Remove(guid); + } + } + } + public ulong AddGOData(uint entry, uint mapId, float x, float y, float z, float o, uint spawntimedelay, float rotation0, float rotation1, float rotation2, float rotation3) + { + GameObjectTemplate goinfo = GetGameObjectTemplate(entry); + if (goinfo == null) + return 0; + + Map map = Global.MapMgr.CreateBaseMap(mapId); + if (map == null) + return 0; + + ulong guid = GenerateGameObjectSpawnId(); + GameObjectData data = new GameObjectData(); + data.id = entry; + data.mapid = (ushort)mapId; + data.posX = x; + data.posY = y; + data.posZ = z; + data.orientation = o; + data.rotation.X = rotation0; + data.rotation.Y = rotation1; + data.rotation.Z = rotation2; + data.rotation.W = rotation3; + data.spawntimesecs = (int)spawntimedelay; + data.animprogress = 100; + data.spawnMask = 1; + data.go_state = GameObjectState.Ready; + data.phaseMask = (ushort)PhaseMasks.Normal; + data.artKit = (byte)(goinfo.type == GameObjectTypes.ControlZone ? 21 : 0); + data.dbData = false; + + NewGOData(guid, data); + AddGameObjectToGrid(guid, data); + + // Spawn if necessary (loaded grids only) + // We use spawn coords to spawn + if (!map.Instanceable() && map.IsGridLoaded(x, y)) + { + GameObject go = new GameObject(); + if (!go.LoadGameObjectFromDB(guid, map)) + { + Log.outError(LogFilter.Server, "AddGOData: cannot add gameobject entry {0} to map", entry); + return 0; + } + } + + Log.outDebug(LogFilter.Maps, "AddGOData: dbguid:{0} entry:{1} map:{2} x:{3} y:{4} z:{5} o:{6}", guid, entry, mapId, x, y, z, o); + + return guid; + } + + public GameObjectAddon GetGameObjectAddon(ulong lowguid) + { + return _gameObjectAddonStorage.LookupByKey(lowguid); + } + public List GetGameObjectQuestItemList(uint id) + { + return _gameObjectQuestItemStorage.LookupByKey(id); + } + MultiMap GetGameObjectQuestItemMap() { return _gameObjectQuestItemStorage; } + public GameObjectData GetGOData(ulong guid) + { + return gameObjectDataStorage.LookupByKey(guid); + } + public void DeleteGOData(ulong guid) + { + GameObjectData data = GetGOData(guid); + if (data != null) + RemoveGameObjectFromGrid(guid, data); + + gameObjectDataStorage.Remove(guid); + } + public void NewGOData(ulong guid, GameObjectData data) + { + gameObjectDataStorage.Add(guid, data); + } + public GameObjectTemplate GetGameObjectTemplate(uint entry) + { + return gameObjectTemplateStorage.LookupByKey(entry); + } + public GameObjectTemplateAddon GetGameObjectTemplateAddon(uint entry) + { + return _gameObjectTemplateAddonStore.LookupByKey(entry); + } + public Dictionary GetGameObjectTemplates() + { + return gameObjectTemplateStorage; + } + public bool IsGameObjectForQuests(uint entry) + { + return _gameObjectForQuestStorage.Contains(entry); + } + void CheckGOLockId(GameObjectTemplate goInfo, uint dataN, uint N) + { + if (CliDB.LockStorage.ContainsKey(dataN)) + return; + + Log.outError(LogFilter.Sql, "Gameobject (Entry: {0} GoType: {1}) have data{2}={3} but lock (Id: {4}) not found.", goInfo.entry, goInfo.type, N, goInfo.Door.open, goInfo.Door.open); + } + void CheckGOLinkedTrapId(GameObjectTemplate goInfo, uint dataN, uint N) + { + GameObjectTemplate trapInfo = Global.ObjectMgr.GetGameObjectTemplate(dataN); + if (trapInfo != null) + { + if (trapInfo.type != GameObjectTypes.Trap) + Log.outError(LogFilter.Sql, "Gameobject (Entry: {0} GoType: {1}) have data{2}={3} but GO (Entry {4}) have not GAMEOBJECT_TYPE_TRAP type.", goInfo.entry, goInfo.type, N, dataN, dataN); + } + } + void CheckGOSpellId(GameObjectTemplate goInfo, uint dataN, uint N) + { + if (Global.SpellMgr.HasSpellInfo(dataN)) + return; + + Log.outError(LogFilter.Sql, "Gameobject (Entry: {0} GoType: {1}) have data{2}={3} but Spell (Entry {4}) not exist.", goInfo.entry, goInfo.type, N, dataN, dataN); + } + void CheckAndFixGOChairHeightId(GameObjectTemplate goInfo, ref uint dataN, uint N) + { + if (dataN <= (UnitStandStateType.SitHighChair - UnitStandStateType.SitLowChair)) + return; + + Log.outError(LogFilter.Sql, "Gameobject (Entry: {0} GoType: {1}) have data{2}={3} but correct chair height in range 0..{4}.", goInfo.entry, goInfo.type, N, dataN, UnitStandStateType.SitHighChair - UnitStandStateType.SitLowChair); + + // prevent client and server unexpected work + dataN = 0; + } + void CheckGONoDamageImmuneId(GameObjectTemplate goTemplate, uint dataN, uint N) + { + // 0/1 correct values + if (dataN <= 1) + return; + + Log.outError(LogFilter.Sql, "Gameobject (Entry: {0} GoType: {1}) have data{2}={3} but expected boolean (0/1) noDamageImmune field value.", goTemplate.entry, goTemplate.type, N, dataN); + } + void CheckGOConsumable(GameObjectTemplate goInfo, uint dataN, uint N) + { + // 0/1 correct values + if (dataN <= 1) + return; + + Log.outError(LogFilter.Sql, "Gameobject (Entry: {0} GoType: {1}) have data{2}={3} but expected boolean (0/1) consumable field value.", + goInfo.entry, goInfo.type, N, dataN); + } + + //Items + public void LoadItemTemplates() + { + var oldMSTime = Time.GetMSTime(); + uint sparseCount = 0; + + foreach (var sparse in CliDB.ItemSparseStorage.Values) + { + ItemRecord db2Data = CliDB.ItemStorage.LookupByKey(sparse.Id); + if (db2Data == null) + continue; + + var itemTemplate = new ItemTemplate(db2Data, sparse); + + itemTemplate.MaxDurability = FillMaxDurability(db2Data.Class, db2Data.SubClass, sparse.inventoryType, (ItemQuality)sparse.Quality, sparse.ItemLevel); + FillDisenchantFields(out itemTemplate.DisenchantID, out itemTemplate.RequiredDisenchantSkill, itemTemplate); + + var itemSpecOverrides = Global.DB2Mgr.GetItemSpecOverrides(sparse.Id); + if (itemSpecOverrides != null) + { + foreach (ItemSpecOverrideRecord itemSpecOverride in itemSpecOverrides) + { + ChrSpecializationRecord specialization = CliDB.ChrSpecializationStorage.LookupByKey(itemSpecOverride.SpecID); + if (specialization != null) + { + itemTemplate.ItemSpecClassMask |= 1u << (specialization.ClassID - 1); + itemTemplate.Specializations[0].Set(ItemTemplate.CalculateItemSpecBit(specialization), true); + + itemTemplate.Specializations[1] = itemTemplate.Specializations[1].Or(itemTemplate.Specializations[0]); + itemTemplate.Specializations[2] = itemTemplate.Specializations[2].Or(itemTemplate.Specializations[0]); + } + } + } + else + { + ItemSpecStats itemSpecStats = new ItemSpecStats(db2Data, sparse); + + foreach (ItemSpecRecord itemSpec in CliDB.ItemSpecStorage.Values) + { + if (itemSpecStats.ItemType != itemSpec.ItemType) + continue; + + bool hasPrimary = itemSpec.PrimaryStat == ItemSpecStat.None; + bool hasSecondary = itemSpec.SecondaryStat == ItemSpecStat.None; + for (uint i = 0; i < itemSpecStats.ItemSpecStatCount; ++i) + { + if (itemSpecStats.ItemSpecStatTypes[i] == itemSpec.PrimaryStat) + hasPrimary = true; + if (itemSpecStats.ItemSpecStatTypes[i] == itemSpec.SecondaryStat) + hasSecondary = true; + } + + if (!hasPrimary || !hasSecondary) + continue; + + ChrSpecializationRecord specialization = CliDB.ChrSpecializationStorage.LookupByKey(itemSpec.SpecID); + if (specialization != null) + { + if (Convert.ToBoolean((1 << (specialization.ClassID - 1)) & sparse.AllowableClass)) + { + itemTemplate.ItemSpecClassMask |= 1u << (specialization.ClassID - 1); + int specBit = ItemTemplate.CalculateItemSpecBit(specialization); + itemTemplate.Specializations[0].Set(specBit, true); + if (itemSpec.MaxLevel > 40) + itemTemplate.Specializations[1].Set(specBit, true); + if (itemSpec.MaxLevel >= 110) + itemTemplate.Specializations[2].Set(specBit, true); + } + } + } + } + + ++sparseCount; + ItemTemplateStorage.Add(sparse.Id, itemTemplate); + } + + foreach (var effectEntry in CliDB.ItemEffectStorage.Values) + { + var itemTemplate = ItemTemplateStorage.LookupByKey(effectEntry.ItemID); + if (itemTemplate == null) + continue; + + itemTemplate.Effects.Add(effectEntry); + } + + // Check if item templates for DBC referenced character start outfit are present + List notFoundOutfit = new List(); + foreach (var entry in CliDB.CharStartOutfitStorage.Values) + { + for (int j = 0; j < ItemConst.MaxOutfitItems; ++j) + { + if (entry.ItemID[j] <= 0) + continue; + + uint item_id = (uint)entry.ItemID[j]; + + if (GetItemTemplate(item_id) == null) + notFoundOutfit.Add(item_id); + } + } + + foreach (var id in notFoundOutfit) + Log.outError(LogFilter.Sql, "Item (Entry: {0}) does not exist in `item_template` but is referenced in `CharStartOutfit.dbc`", id); + + Log.outInfo(LogFilter.ServerLoading, "Loaded {0} item templates in {1} ms", sparseCount, Time.GetMSTimeDiffToNow(oldMSTime)); + } + + static float[] qualityMultipliers = new float[] + { + 0.92f, 0.92f, 0.92f, 1.11f, 1.32f, 1.61f, 0.0f, 0.0f + }; + + static float[] armorMultipliers = new float[] + { + 0.00f, // INVTYPE_NON_EQUIP + 0.60f, // INVTYPE_HEAD + 0.00f, // INVTYPE_NECK + 0.60f, // INVTYPE_SHOULDERS + 0.00f, // INVTYPE_BODY + 1.00f, // INVTYPE_CHEST + 0.33f, // INVTYPE_WAIST + 0.72f, // INVTYPE_LEGS + 0.48f, // INVTYPE_FEET + 0.33f, // INVTYPE_WRISTS + 0.33f, // INVTYPE_HANDS + 0.00f, // INVTYPE_FINGER + 0.00f, // INVTYPE_TRINKET + 0.00f, // INVTYPE_WEAPON + 0.72f, // INVTYPE_SHIELD + 0.00f, // INVTYPE_RANGED + 0.00f, // INVTYPE_CLOAK + 0.00f, // INVTYPE_2HWEAPON + 0.00f, // INVTYPE_BAG + 0.00f, // INVTYPE_TABARD + 1.00f, // INVTYPE_ROBE + 0.00f, // INVTYPE_WEAPONMAINHAND + 0.00f, // INVTYPE_WEAPONOFFHAND + 0.00f, // INVTYPE_HOLDABLE + 0.00f, // INVTYPE_AMMO + 0.00f, // INVTYPE_THROWN + 0.00f, // INVTYPE_RANGEDRIGHT + 0.00f, // INVTYPE_QUIVER + 0.00f, // INVTYPE_RELIC + }; + + static float[] weaponMultipliers = new float[] + { + 0.91f, // ITEM_SUBCLASS_WEAPON_AXE + 1.00f, // ITEM_SUBCLASS_WEAPON_AXE2 + 1.00f, // ITEM_SUBCLASS_WEAPON_BOW + 1.00f, // ITEM_SUBCLASS_WEAPON_GUN + 0.91f, // ITEM_SUBCLASS_WEAPON_MACE + 1.00f, // ITEM_SUBCLASS_WEAPON_MACE2 + 1.00f, // ITEM_SUBCLASS_WEAPON_POLEARM + 0.91f, // ITEM_SUBCLASS_WEAPON_SWORD + 1.00f, // ITEM_SUBCLASS_WEAPON_SWORD2 + 1.00f, // ITEM_SUBCLASS_WEAPON_WARGLAIVES + 1.00f, // ITEM_SUBCLASS_WEAPON_STAFF + 0.00f, // ITEM_SUBCLASS_WEAPON_EXOTIC + 0.00f, // ITEM_SUBCLASS_WEAPON_EXOTIC2 + 0.66f, // ITEM_SUBCLASS_WEAPON_FIST_WEAPON + 0.00f, // ITEM_SUBCLASS_WEAPON_MISCELLANEOUS + 0.66f, // ITEM_SUBCLASS_WEAPON_DAGGER + 0.00f, // ITEM_SUBCLASS_WEAPON_THROWN + 0.00f, // ITEM_SUBCLASS_WEAPON_SPEAR + 1.00f, // ITEM_SUBCLASS_WEAPON_CROSSBOW + 0.66f, // ITEM_SUBCLASS_WEAPON_WAND + 0.66f, // ITEM_SUBCLASS_WEAPON_FISHING_POLE + }; + + uint FillMaxDurability(ItemClass itemClass, uint itemSubClass, InventoryType inventoryType, ItemQuality quality, uint itemLevel) + { + if (itemClass != ItemClass.Armor && itemClass != ItemClass.Weapon) + return 0; + + float levelPenalty = 1.0f; + if (itemLevel <= 28) + levelPenalty = 0.966f - (28u - itemLevel) / 54.0f; + + if (itemClass == ItemClass.Armor) + { + if (inventoryType > InventoryType.Robe) + return 0; + + return 5 * (uint)(Math.Round(25.0f * qualityMultipliers[(int)quality] * armorMultipliers[(int)inventoryType] * levelPenalty)); + } + + return 5 * (uint)(Math.Round(18.0f * qualityMultipliers[(int)quality] * weaponMultipliers[itemSubClass] * levelPenalty)); + } + void FillDisenchantFields(out uint disenchantID, out uint requiredDisenchantSkill, ItemTemplate itemTemplate) + { + disenchantID = 0; + requiredDisenchantSkill = 0xFFFFFFFF; + if (itemTemplate.GetFlags().HasAnyFlag(ItemFlags.Conjured | ItemFlags.NoDisenchant) || + itemTemplate.GetBonding() == ItemBondingType.Quest || itemTemplate.GetArea() != 0 || itemTemplate.GetMap() != 0 || + itemTemplate.GetMaxStackSize() > 1 || + itemTemplate.GetQuality() < ItemQuality.Uncommon || itemTemplate.GetQuality() > ItemQuality.Epic || + !(itemTemplate.GetClass() == ItemClass.Armor || itemTemplate.GetClass() == ItemClass.Weapon) || + !(Item.GetSpecialPrice(itemTemplate) != 0 || Global.DB2Mgr.HasItemCurrencyCost(itemTemplate.GetId()))) + return; + + foreach (var disenchant in CliDB.ItemDisenchantLootStorage.Values) + { + if (disenchant.ItemClass == (uint)itemTemplate.GetClass() && disenchant.ItemQuality == (uint)itemTemplate.GetQuality() && + disenchant.MinItemLevel <= itemTemplate.GetBaseItemLevel() && disenchant.MaxItemLevel >= itemTemplate.GetBaseItemLevel()) + { + if (disenchant.Id == 60 || disenchant.Id == 61) // epic item disenchant ilvl range 66-99 (classic) + { + if (itemTemplate.GetBaseRequiredLevel() > 60 || itemTemplate.GetRequiredSkillRank() > 300) + continue; // skip to epic item disenchant ilvl range 90-199 (TBC) + } + else if (disenchant.Id == 66 || disenchant.Id == 67) // epic item disenchant ilvl range 90-199 (TBC) + { + if (itemTemplate.GetBaseRequiredLevel() <= 60 || (itemTemplate.GetRequiredSkill() != 0 && itemTemplate.GetRequiredSkillRank() <= 300)) + continue; + } + + disenchantID = disenchant.Id; + requiredDisenchantSkill = disenchant.RequiredDisenchantSkill; + return; + } + } + } + public void LoadItemTemplateAddon() + { + var time = Time.GetMSTime(); + + uint count = 0; + SQLResult result = DB.World.Query("SELECT Id, FlagsCu, FoodType, MinMoneyLoot, MaxMoneyLoot, SpellPPMChance FROM item_template_addon"); + if (!result.IsEmpty()) + { + do + { + uint itemId = result.Read(0); + ItemTemplate itemTemplate = GetItemTemplate(itemId); + if (itemTemplate == null) + { + Log.outError(LogFilter.Sql, "Item {0} specified in `itemtemplateaddon` does not exist, skipped.", itemId); + continue; + } + + uint minMoneyLoot = result.Read(3); + uint maxMoneyLoot = result.Read(4); + if (minMoneyLoot > maxMoneyLoot) + { + Log.outError(LogFilter.Sql, "Minimum money loot specified in `itemtemplateaddon` for item {0} was greater than maximum amount, swapping.", itemId); + uint temp = minMoneyLoot; + minMoneyLoot = maxMoneyLoot; + maxMoneyLoot = temp; + } + itemTemplate.FlagsCu = (ItemFlagsCustom)result.Read(1); + itemTemplate.FoodType = result.Read(2); + itemTemplate.MinMoneyLoot = minMoneyLoot; + itemTemplate.MaxMoneyLoot = maxMoneyLoot; + itemTemplate.SpellPPMRate = result.Read(5); + ++count; + } while (result.NextRow()); + } + + Log.outInfo(LogFilter.ServerLoading, "Loaded {0} item addon templates in {1} ms", count, Time.GetMSTimeDiffToNow(time)); + } + public void LoadItemScriptNames() + { + uint oldMSTime = Time.GetMSTime(); + uint count = 0; + + SQLResult result = DB.World.Query("SELECT Id, ScriptName FROM item_script_names"); + if (!result.IsEmpty()) + { + do + { + uint itemId = result.Read(0); + if (GetItemTemplate(itemId) == null) + { + Log.outError(LogFilter.Sql, "Item {0} specified in `item_script_names` does not exist, skipped.", itemId); + continue; + } + + ItemTemplateStorage[itemId].ScriptId = GetScriptId(result.Read(1)); + ++count; + } while (result.NextRow()); + } + + Log.outInfo(LogFilter.ServerLoading, "Loaded {0} item script names in {1} ms", count, Time.GetMSTimeDiffToNow(oldMSTime)); + } + + public ItemTemplate GetItemTemplate(uint ItemId) + { + return ItemTemplateStorage.LookupByKey(ItemId); + } + public Dictionary GetItemTemplates() + { + return ItemTemplateStorage; + } + public void AddVendorItem(uint entry, uint item, int maxcount, uint incrtime, uint extendedCost, ItemVendorType type, bool persist = true) + { + VendorItemData vList = cacheVendorItemStorage[entry]; + vList.AddItem(item, maxcount, incrtime, extendedCost, type); + + if (persist) + { + PreparedStatement stmt = DB.World.GetPreparedStatement(WorldStatements.INS_NPC_VENDOR); + + stmt.AddValue(0, entry); + stmt.AddValue(1, item); + stmt.AddValue(2, maxcount); + stmt.AddValue(3, incrtime); + stmt.AddValue(4, extendedCost); + stmt.AddValue(5, (byte)type); + + DB.World.Execute(stmt); + } + } + public bool RemoveVendorItem(uint entry, uint item, ItemVendorType type, bool persist = true) + { + var iter = cacheVendorItemStorage.LookupByKey(entry); + if (iter == null) + return false; + + if (!iter.RemoveItem(item, type)) + return false; + + if (persist) + { + PreparedStatement stmt = DB.World.GetPreparedStatement(WorldStatements.DEL_NPC_VENDOR); + + stmt.AddValue(0, entry); + stmt.AddValue(1, item); + stmt.AddValue(2, (byte)type); + + DB.World.Execute(stmt); + } + + return true; + } + public bool IsVendorItemValid(uint vendorentry, uint id, int maxcount, uint incrtime, uint ExtendedCost, ItemVendorType type, Player player = null, List skipvendors = null, ulong ORnpcflag = 0) + { + CreatureTemplate cInfo = GetCreatureTemplate(vendorentry); + if (cInfo == null) + { + if (player != null) + player.SendSysMessage(CypherStrings.CommandVendorselection); + else + Log.outError(LogFilter.Sql, "Table `(gameevent)npcvendor` have data for not existed creature template (Entry: {0}), ignore", vendorentry); + return false; + } + + if (!Convert.ToBoolean(((ulong)cInfo.Npcflag | ORnpcflag) & (ulong)NPCFlags.Vendor)) + { + if (skipvendors == null || skipvendors.Count() == 0) + { + if (player != null) + player.SendSysMessage(CypherStrings.CommandVendorselection); + else + Log.outError(LogFilter.Sql, "Table `(gameevent)npcvendor` have data for not creature template (Entry: {0}) without vendor flag, ignore", vendorentry); + + if (skipvendors != null) + skipvendors.Add(vendorentry); + } + return false; + } + + if ((type == ItemVendorType.Item && GetItemTemplate(id) == null) || + (type == ItemVendorType.Currency && CliDB.CurrencyTypesStorage.LookupByKey(id) == null)) + { + if (player != null) + player.SendSysMessage(CypherStrings.ItemNotFound, id, type); + else + Log.outError(LogFilter.Sql, "Table `(gameevent)npcvendor` for Vendor (Entry: {0}) have in item list non-existed item ({1}, type {2}), ignore", vendorentry, id, type); + return false; + } + + if (ExtendedCost != 0 && !CliDB.ItemExtendedCostStorage.ContainsKey(ExtendedCost)) + { + if (player != null) + player.SendSysMessage(CypherStrings.ExtendedCostNotExist, ExtendedCost); + else + Log.outError(LogFilter.Sql, "Table `(gameevent)npcvendor` have Item (Entry: {0}) with wrong ExtendedCost ({1}) for vendor ({2}), ignore", id, ExtendedCost, vendorentry); + return false; + } + + if (type == ItemVendorType.Item) // not applicable to currencies + { + if (maxcount > 0 && incrtime == 0) + { + if (player != null) + player.SendSysMessage("MaxCount != 0 ({0}) but IncrTime == 0", maxcount); + else + Log.outError(LogFilter.Sql, "Table `(gameevent)npcvendor` has `maxcount` ({0}) for item {1} of vendor (Entry: {2}) but `incrtime`=0, ignore", maxcount, id, vendorentry); + return false; + } + else if (maxcount == 0 && incrtime > 0) + { + if (player != null) + player.SendSysMessage("MaxCount == 0 but IncrTime<>= 0"); + else + Log.outError(LogFilter.Sql, "Table `(gameevent)npcvendor` has `maxcount`=0 for item {0} of vendor (Entry: {0}) but `incrtime`<>0, ignore", id, vendorentry); + return false; + } + } + + VendorItemData vItems = GetNpcVendorItemList(vendorentry); + if (vItems == null) + return true; // later checks for non-empty lists + + if (vItems.FindItemCostPair(id, ExtendedCost, type) != null) + { + if (player != null) + player.SendSysMessage(CypherStrings.ItemAlreadyInList, id, ExtendedCost, type); + else + Log.outError(LogFilter.Sql, "Table `npcvendor` has duplicate items {0} (with extended cost {1}, type {2}) for vendor (Entry: {3}), ignoring", id, ExtendedCost, type, vendorentry); + return false; + } + + return true; + } + public VendorItemData GetNpcVendorItemList(uint entry) + { + return cacheVendorItemStorage.LookupByKey(entry); + } + public EquipmentInfo GetEquipmentInfo(uint entry, int id) + { + var equip = equipmentInfoStorage.LookupByKey(entry); + if (equip.Empty()) + return null; + + if (id == -1) + return equip[RandomHelper.IRand(0, equip.Count - 1)].Item2; + else + return equip.Find(p => p.Item1 == id).Item2; + } + + //Maps + public void LoadInstanceTemplate() + { + var time = Time.GetMSTime(); + // 0 1 2 4 + SQLResult result = DB.World.Query("SELECT map, parent, script, allowMount FROM instance_template"); + + if (result.IsEmpty()) + { + Log.outInfo(LogFilter.ServerLoading, "Loaded 0 instance templates. DB table `instance_template` is empty!"); + return; + } + + uint count = 0; + do + { + uint mapID = result.Read(0); + + if (!Global.MapMgr.IsValidMAP(mapID, true)) + { + Log.outError(LogFilter.Sql, "ObjectMgr.LoadInstanceTemplate: bad mapid {0} for template!", mapID); + continue; + } + + var instanceTemplate = new InstanceTemplate(); + instanceTemplate.AllowMount = result.Read(3); + instanceTemplate.Parent = result.Read(1); + instanceTemplate.ScriptId = Global.ObjectMgr.GetScriptId(result.Read(2)); + + instanceTemplateStorage.Add(mapID, instanceTemplate); + + ++count; + } while (result.NextRow()); + + Log.outInfo(LogFilter.ServerLoading, "Loaded {0} instance templates in {1} ms", count, Time.GetMSTimeDiffToNow(time)); + } + public void LoadGameTele() + { + uint oldMSTime = Time.GetMSTime(); + + gameTeleStorage.Clear(); + + // 0 1 2 3 4 5 6 + SQLResult result = DB.World.Query("SELECT id, position_x, position_y, position_z, orientation, map, name FROM game_tele"); + + if (result.IsEmpty()) + { + Log.outError(LogFilter.ServerLoading, "Loaded 0 GameTeleports. DB table `game_tele` is empty!"); + return; + } + + uint count = 0; + + do + { + uint id = result.Read(0); + + GameTele gt = new GameTele(); + + gt.posX = result.Read(1); + gt.posY = result.Read(2); + gt.posZ = result.Read(3); + gt.orientation = result.Read(4); + gt.mapId = result.Read(5); + gt.name = result.Read(6); + + if (!GridDefines.IsValidMapCoord(gt.mapId, gt.posX, gt.posY, gt.posZ, gt.orientation)) + { + Log.outError(LogFilter.Sql, "Wrong position for id {0} (name: {1}) in `game_tele` table, ignoring.", id, gt.name); + continue; + } + gameTeleStorage.Add(id, gt); + ++count; + } while (result.NextRow()); + + Log.outInfo(LogFilter.ServerLoading, "Loaded {0} GameTeleports in {1} ms", count, Time.GetMSTimeDiffToNow(oldMSTime)); + } + public void LoadAreaTriggerTeleports() + { + uint oldMSTime = Time.GetMSTime(); + + _areaTriggerStorage.Clear(); // need for reload case + + // 0 1 + SQLResult result = DB.World.Query("SELECT ID, PortLocID FROM areatrigger_teleport"); + if (result.IsEmpty()) + { + Log.outInfo(LogFilter.ServerLoading, "Loaded 0 area trigger teleport definitions. DB table `areatrigger_teleport` is empty."); + return; + } + + uint count = 0; + do + { + ++count; + + uint Trigger_ID = result.Read(0); + uint PortLocID = result.Read(1); + + WorldSafeLocsRecord portLoc = CliDB.WorldSafeLocsStorage.LookupByKey(PortLocID); + if (portLoc == null) + { + Log.outError(LogFilter.Sql, "Area Trigger (ID: {0}) has a non-existing Port Loc (ID: {1}) in WorldSafeLocs.dbc, skipped", Trigger_ID, PortLocID); + continue; + } + + AreaTriggerStruct at = new AreaTriggerStruct(); + at.target_mapId = portLoc.MapID; + at.target_X = portLoc.Loc.X; + at.target_Y = portLoc.Loc.Y; + at.target_Z = portLoc.Loc.Z; + at.target_Orientation = (portLoc.Facing * MathFunctions.PI) / 180; // Orientation is initially in degrees + at.PortLocId = portLoc.Id; + + AreaTriggerRecord atEntry = CliDB.AreaTriggerStorage.LookupByKey(Trigger_ID); + if (atEntry == null) + { + Log.outError(LogFilter.Sql, "Area trigger (ID: {0}) does not exist in `AreaTrigger.dbc`.", Trigger_ID); + continue; + } + + _areaTriggerStorage[Trigger_ID] = at; + + } while (result.NextRow()); + + Log.outInfo(LogFilter.ServerLoading, "Loaded {0} area trigger teleport definitions in {1} ms", count, Time.GetMSTimeDiffToNow(oldMSTime)); + } + public void LoadAccessRequirements() + { + uint oldMSTime = Time.GetMSTime(); + + _accessRequirementStorage.Clear(); + + // 0 1 2 3 4 5 6 7 8 9 + SQLResult result = DB.World.Query("SELECT mapid, difficulty, level_min, level_max, item, item2, quest_done_A, quest_done_H, completed_achievement, quest_failed_text FROM access_requirement"); + if (result.IsEmpty()) + { + Log.outInfo(LogFilter.ServerLoading, "Loaded 0 access requirement definitions. DB table `access_requirement` is empty."); + return; + } + + uint count = 0; + do + { + uint mapid = result.Read(0); + if (!CliDB.MapStorage.ContainsKey(mapid)) + { + Log.outError(LogFilter.Sql, "Map {0} referenced in `access_requirement` does not exist, skipped.", mapid); + continue; + } + + uint difficulty = result.Read(1); + if (Global.DB2Mgr.GetMapDifficultyData(mapid, (Difficulty)difficulty) == null) + { + Log.outError(LogFilter.Sql, "Map {0} referenced in `access_requirement` does not have difficulty {1}, skipped", mapid, difficulty); + continue; + } + + ulong requirementId = MathFunctions.MakePair64(mapid, difficulty); + + AccessRequirement ar = new AccessRequirement(); + ar.levelMin = result.Read(2); + ar.levelMax = result.Read(3); + ar.item = result.Read(4); + ar.item2 = result.Read(5); + ar.quest_A = result.Read(6); + ar.quest_H = result.Read(7); + ar.achievement = result.Read(8); + ar.questFailedText = result.Read(9); + + if (ar.item != 0) + { + ItemTemplate pProto = GetItemTemplate(ar.item); + if (pProto == null) + { + Log.outError(LogFilter.Sql, "Key item {0} does not exist for map {1} difficulty {2}, removing key requirement.", ar.item, mapid, difficulty); + ar.item = 0; + } + } + + if (ar.item2 != 0) + { + ItemTemplate pProto = GetItemTemplate(ar.item2); + if (pProto == null) + { + Log.outError(LogFilter.Sql, "Second item {0} does not exist for map {1} difficulty {2}, removing key requirement.", ar.item2, mapid, difficulty); + ar.item2 = 0; + } + } + + if (ar.quest_A != 0) + { + if (GetQuestTemplate(ar.quest_A) == null) + { + Log.outError(LogFilter.Sql, "Required Alliance Quest {0} not exist for map {1} difficulty {2}, remove quest done requirement.", ar.quest_A, mapid, difficulty); + ar.quest_A = 0; + } + } + + if (ar.quest_H != 0) + { + if (GetQuestTemplate(ar.quest_H) == null) + { + Log.outError(LogFilter.Sql, "Required Horde Quest {0} not exist for map {1} difficulty {2}, remove quest done requirement.", ar.quest_H, mapid, difficulty); + ar.quest_H = 0; + } + } + + if (ar.achievement != 0) + { + if (!CliDB.AchievementStorage.ContainsKey(ar.achievement)) + { + Log.outError(LogFilter.Sql, "Required Achievement {0} not exist for map {1} difficulty {2}, remove quest done requirement.", ar.achievement, mapid, difficulty); + ar.achievement = 0; + } + } + + _accessRequirementStorage[requirementId] = ar; + ++count; + } while (result.NextRow()); + + Log.outInfo(LogFilter.ServerLoading, "Loaded {0} access requirement definitions in {1} ms", count, Time.GetMSTimeDiffToNow(oldMSTime)); + } + public void LoadInstanceEncounters() + { + uint oldMSTime = Time.GetMSTime(); + + // 0 1 2 3 + SQLResult result = DB.World.Query("SELECT entry, creditType, creditEntry, lastEncounterDungeon FROM instance_encounters"); + if (result.IsEmpty()) + { + Log.outError(LogFilter.ServerLoading, "Loaded 0 instance encounters, table is empty!"); + return; + } + + uint count = 0; + Dictionary> dungeonLastBosses = new Dictionary>(); + do + { + uint entry = result.Read(0); + EncounterCreditType creditType = (EncounterCreditType)result.Read(1); + uint creditEntry = result.Read(2); + uint lastEncounterDungeon = result.Read(3); + DungeonEncounterRecord dungeonEncounter = CliDB.DungeonEncounterStorage.LookupByKey(entry); + if (dungeonEncounter == null) + { + Log.outError(LogFilter.Sql, "Table `instance_encounters` has an invalid encounter id {0}, skipped!", entry); + continue; + } + + if (lastEncounterDungeon != 0 && Global.LFGMgr.GetLFGDungeonEntry(lastEncounterDungeon) == 0) + { + Log.outError(LogFilter.Sql, "Table `instance_encounters` has an encounter {0} ({1}) marked as final for invalid dungeon id {2}, skipped!", + entry, dungeonEncounter.Name[Global.WorldMgr.GetDefaultDbcLocale()], lastEncounterDungeon); + continue; + } + + var pair = dungeonLastBosses.LookupByKey(lastEncounterDungeon); + if (lastEncounterDungeon != 0) + { + if (pair != null) + { + Log.outError(LogFilter.Sql, "Table `instance_encounters` specified encounter {0} ({1}) as last encounter but {2} ({3}) is already marked as one, skipped!", + entry, dungeonEncounter.Name[Global.WorldMgr.GetDefaultDbcLocale()], pair.Item1, pair.Item2.Name[Global.WorldMgr.GetDefaultDbcLocale()]); + continue; + } + + dungeonLastBosses[lastEncounterDungeon] = Tuple.Create(entry, dungeonEncounter); + } + + switch (creditType) + { + case EncounterCreditType.KillCreature: + { + CreatureTemplate creatureInfo = GetCreatureTemplate(creditEntry); + if (creatureInfo == null) + { + Log.outError(LogFilter.Sql, "Table `instance_encounters` has an invalid creature (entry {0}) linked to the encounter {1} ({2}), skipped!", + creditEntry, entry, dungeonEncounter.Name[Global.WorldMgr.GetDefaultDbcLocale()]); + continue; + } + creatureInfo.FlagsExtra |= CreatureFlagsExtra.DungeonBoss; + break; + } + case EncounterCreditType.CastSpell: + if (!Global.SpellMgr.HasSpellInfo(creditEntry)) + { + Log.outError(LogFilter.Sql, "Table `instance_encounters` has an invalid spell (entry {0}) linked to the encounter {1} ({2}), skipped!", + creditEntry, entry, dungeonEncounter.Name[Global.WorldMgr.GetDefaultDbcLocale()]); + continue; + } + break; + default: + Log.outError(LogFilter.Sql, "Table `instance_encounters` has an invalid credit type ({0}) for encounter {1} ({2}), skipped!", + creditType, entry, dungeonEncounter.Name[Global.WorldMgr.GetDefaultDbcLocale()]); + continue; + } + + if (dungeonEncounter.DifficultyID == 0) + { + for (uint i = 0; i < (int)Difficulty.Max; ++i) + { + if (Global.DB2Mgr.GetMapDifficultyData(dungeonEncounter.MapID, (Difficulty)i) != null) + _dungeonEncounterStorage.Add(MathFunctions.MakePair64(dungeonEncounter.MapID, i), new DungeonEncounter(dungeonEncounter, creditType, creditEntry, lastEncounterDungeon)); + } + } + else + _dungeonEncounterStorage.Add(MathFunctions.MakePair64(dungeonEncounter.MapID, (uint)dungeonEncounter.DifficultyID), new DungeonEncounter(dungeonEncounter, creditType, creditEntry, lastEncounterDungeon)); + + ++count; + } while (result.NextRow()); + + Log.outInfo(LogFilter.ServerLoading, "Loaded {0} instance encounters in {1} ms", count, Time.GetMSTimeDiffToNow(oldMSTime)); + } + + public InstanceTemplate GetInstanceTemplate(uint mapID) + { + return instanceTemplateStorage.LookupByKey(mapID); + } + public GameTele GetGameTele(uint id) + { + return gameTeleStorage.LookupByKey(id); + } + public GameTele GetGameTele(string name) + { + return gameTeleStorage.Values.FirstOrDefault(p => p.name.ToLower() == name.ToLower()); + } + public List GetDungeonEncounterList(uint mapId, Difficulty difficulty) + { + return _dungeonEncounterStorage.LookupByKey(MathFunctions.MakePair64(mapId, (uint)difficulty)); + } + public bool IsTransportMap(uint mapId) { return _transportMaps.Contains((ushort)mapId); } + + //Player + public void LoadPlayerInfo() + { + for (uint race = 0; race < (int)Race.Max; ++race) + _playerInfo[race] = new PlayerInfo[(int)Class.Max]; + + var time = Time.GetMSTime(); + // Load playercreate + { + // 0 1 2 3 4 5 6 7 + SQLResult result = DB.World.Query("SELECT race, class, map, zone, position_x, position_y, position_z, orientation FROM playercreateinfo"); + + if (result.IsEmpty()) + { + Log.outError(LogFilter.ServerLoading, "Loaded 0 player create definitions. DB table `playercreateinfo` is empty."); + return; + } + + uint count = 0; + do + { + uint currentrace = result.Read(0); + uint currentclass = result.Read(1); + uint mapId = result.Read(2); + uint zoneId = result.Read(3); + float positionX = result.Read(4); + float positionY = result.Read(5); + float positionZ = result.Read(6); + float orientation = result.Read(7); + + if (currentrace >= (int)Race.Max) + { + Log.outError(LogFilter.Sql, "Wrong race {0} in `playercreateinfo` table, ignoring.", currentrace); + continue; + } + + var rEntry = CliDB.ChrRacesStorage.LookupByKey(currentrace); + if (rEntry == null) + { + Log.outError(LogFilter.Sql, "Wrong race {0} in `playercreateinfo` table, ignoring.", currentrace); + continue; + } + + if (currentclass >= (int)Class.Max) + { + Log.outError(LogFilter.Sql, "Wrong class {0} in `playercreateinfo` table, ignoring.", currentclass); + continue; + } + + if (CliDB.ChrClassesStorage.LookupByKey(currentclass) == null) + { + Log.outError(LogFilter.Sql, "Wrong class {0} in `playercreateinfo` table, ignoring.", currentclass); + continue; + } + + PlayerInfo pInfo = new PlayerInfo(); + pInfo.MapId = mapId; + pInfo.ZoneId = zoneId; + pInfo.PositionX = positionX; + pInfo.PositionY = positionY; + pInfo.PositionZ = positionZ; + pInfo.Orientation = orientation; + + pInfo.DisplayId_m = rEntry.MaleDisplayID; + pInfo.DisplayId_f = rEntry.FemaleDisplayID; + + _playerInfo[currentrace][currentclass] = pInfo; + + ++count; + } while (result.NextRow()); + + Log.outInfo(LogFilter.ServerLoading, "Loaded {0} player create definitions in {1} ms", count, Time.GetMSTimeDiffToNow(time)); + } + time = Time.GetMSTime(); + // Load playercreate items + Log.outInfo(LogFilter.ServerLoading, "Loading Player Create Items Data..."); + { + // 0 1 2 3 + SQLResult result = DB.World.Query("SELECT race, class, itemid, amount FROM playercreateinfo_item"); + + if (result.IsEmpty()) + Log.outError(LogFilter.ServerLoading, "Loaded 0 custom player create items. DB table `playercreateinfo_item` is empty."); + else + { + uint count = 0; + do + { + uint currentrace = result.Read(0); + if (currentrace >= (int)Race.Max) + { + Log.outError(LogFilter.Sql, "Wrong race {0} in `playercreateinfo_item` table, ignoring.", currentrace); + continue; + } + + uint currentclass = result.Read(1); + if (currentclass >= (int)Class.Max) + { + Log.outError(LogFilter.Sql, "Wrong class {0} in `playercreateinfo_item` table, ignoring.", currentclass); + continue; + } + + uint itemid = result.Read(2); + if (GetItemTemplate(itemid).GetId() == 0) + { + Log.outError(LogFilter.Sql, "Item id {0} (race {1} class {2}) in `playercreateinfo_item` table but not listed in `itemtemplate`, ignoring.", itemid, currentrace, currentclass); + continue; + } + + int amount = result.Read(3); + + if (amount == 0) + { + Log.outError(LogFilter.Sql, "Item id {0} (class {1} race {2}) have amount == 0 in `playercreateinfo_item` table, ignoring.", itemid, currentrace, currentclass); + continue; + } + + if (currentrace == 0 || currentclass == 0) + { + uint minrace = currentrace != 0 ? currentrace : 1; + uint maxrace = currentrace != 0 ? currentrace + 1 : (int)Race.Max; + uint minclass = currentclass != 0 ? currentclass : 1; + uint maxclass = currentclass != 0 ? currentclass + 1 : (int)Class.Max; + for (var r = minrace; r < maxrace; ++r) + for (var c = minclass; c < maxclass; ++c) + PlayerCreateInfoAddItemHelper(r, c, itemid, amount); + + } + else + PlayerCreateInfoAddItemHelper(currentrace, currentclass, itemid, amount); + + ++count; + } while (result.NextRow()); + + Log.outInfo(LogFilter.ServerLoading, "Loaded {0} custom player create items in {1} ms", count, Time.GetMSTimeDiffToNow(time)); + } + } + + // Load playercreate skills + Log.outInfo(LogFilter.ServerLoading, "Loading Player Create Skill Data..."); + { + uint oldMSTime = Time.GetMSTime(); + + foreach (SkillRaceClassInfoRecord rcInfo in CliDB.SkillRaceClassInfoStorage.Values) + { + if (rcInfo.Availability == 1) + { + for (int raceIndex = (int)Race.Human; raceIndex < (int)Race.Max; ++raceIndex) + { + if (rcInfo.RaceMask == -1 || Convert.ToBoolean((1 << (raceIndex - 1)) & rcInfo.RaceMask)) + { + for (int classIndex = (int)Class.Warrior; classIndex < (int)Class.Max; ++classIndex) + { + if (rcInfo.ClassMask == -1 || Convert.ToBoolean((1 << (classIndex - 1)) & rcInfo.ClassMask)) + { + PlayerInfo info = _playerInfo[raceIndex][classIndex]; + if (info != null) + info.skills.Add(rcInfo); + } + } + } + } + } + } + Log.outInfo(LogFilter.ServerLoading, "Loaded player create skills in {0} ms", Time.GetMSTimeDiffToNow(oldMSTime)); + } + + // Load playercreate custom spells + Log.outInfo(LogFilter.ServerLoading, "Loading Player Create Custom Spell Data..."); + { + uint oldMSTime = Time.GetMSTime(); + + SQLResult result = DB.World.Query("SELECT racemask, classmask, Spell FROM playercreateinfo_spell_custom"); + if (result.IsEmpty()) + { + Log.outError(LogFilter.ServerLoading, "Loaded 0 player create custom spells. DB table `playercreateinfo_spell_custom` is empty."); + } + else + { + uint count = 0; + do + { + uint raceMask = result.Read(0); + uint classMask = result.Read(1); + uint spellId = result.Read(2); + + if (raceMask != 0 && !Convert.ToBoolean(raceMask & (int)Race.RaceMaskAllPlayable)) + { + Log.outError(LogFilter.Sql, "Wrong race mask {0} in `playercreateinfo_spell_custom` table, ignoring.", raceMask); + continue; + } + + if (classMask != 0 && !Convert.ToBoolean(classMask & (int)Class.ClassMaskAllPlayable)) + { + Log.outError(LogFilter.Sql, "Wrong class mask {0} in `playercreateinfo_spell_custom` table, ignoring.", classMask); + continue; + } + + for (int raceIndex = (int)Race.Human; raceIndex < (int)Race.Max; ++raceIndex) + { + if (raceMask == 0 || Convert.ToBoolean((1 << (raceIndex - 1)) & raceMask)) + { + for (int classIndex = (int)Class.Warrior; classIndex < (int)Class.Max; ++classIndex) + { + if (classMask == 0 || Convert.ToBoolean((1 << (classIndex - 1)) & classMask)) + { + PlayerInfo info = _playerInfo[raceIndex][classIndex]; + if (info != null) + { + info.customSpells.Add(spellId); + ++count; + } + // We need something better here, the check is not accounting for spells used by multiple races/classes but not all of them. + // Either split the masks per class, or per race, which kind of kills the point yet. + } + } + } + } + } + while (result.NextRow()); + + Log.outInfo(LogFilter.ServerLoading, "Loaded {0} custom player create spells in {1} ms", count, Time.GetMSTimeDiffToNow(oldMSTime)); + } + } + + // Load playercreate cast spell + Log.outInfo(LogFilter.ServerLoading, "Loading Player Create Cast Spell Data..."); + { + uint oldMSTime = Time.GetMSTime(); + + SQLResult result = DB.World.Query("SELECT raceMask, classMask, spell FROM playercreateinfo_cast_spell"); + + if (result.IsEmpty()) + Log.outError(LogFilter.ServerLoading, "Loaded 0 player create cast spells. DB table `playercreateinfo_cast_spell` is empty."); + else + { + uint count = 0; + + do + { + uint raceMask = result.Read(0); + uint classMask = result.Read(1); + uint spellId = result.Read(2); + + if (raceMask != 0 && !raceMask.HasAnyFlag((uint)Race.RaceMaskAllPlayable)) + { + Log.outError(LogFilter.Sql, "Wrong race mask {0} in `playercreateinfo_cast_spell` table, ignoring.", raceMask); + continue; + } + + if (classMask != 0 && !classMask.HasAnyFlag((uint)Class.ClassMaskAllPlayable)) + { + Log.outError(LogFilter.Sql, "Wrong class mask {0} in `playercreateinfo_cast_spell` table, ignoring.", classMask); + continue; + } + + for (int raceIndex = (int)Race.Human; raceIndex < (int)Race.Max; ++raceIndex) + { + if (raceMask == 0 || Convert.ToBoolean((1 << (raceIndex - 1)) & raceMask)) + { + for (int classIndex = (int)Class.Warrior; classIndex < (int)Class.Max; ++classIndex) + { + if (classMask == 0 || Convert.ToBoolean((1 << (classIndex - 1)) & classMask)) + { + PlayerInfo info = _playerInfo[raceIndex][classIndex]; + if (info != null) + { + info.castSpells.Add(spellId); + ++count; + } + } + } + } + } + } while (result.NextRow()); + + Log.outInfo(LogFilter.ServerLoading, "Loaded {0} player create cast spells in {1} ms", count, Time.GetMSTimeDiffToNow(oldMSTime)); + } + } + + // Load playercreate actions + time = Time.GetMSTime(); + Log.outInfo(LogFilter.ServerLoading, "Loading Player Create Action Data..."); + { + // 0 1 2 3 4 + SQLResult result = DB.World.Query("SELECT race, class, button, action, type FROM playercreateinfo_action"); + + if (result.IsEmpty()) + Log.outError(LogFilter.ServerLoading, "Loaded 0 player create actions. DB table `playercreateinfo_action` is empty."); + else + { + uint count = 0; + do + { + uint currentrace = result.Read(0); + if (currentrace >= (int)Race.Max) + { + Log.outError(LogFilter.Sql, "Wrong race {0} in `playercreateinfo_action` table, ignoring.", currentrace); + continue; + } + + uint currentclass = result.Read(1); + if (currentclass >= (int)Class.Max) + { + Log.outError(LogFilter.Sql, "Wrong class {0} in `playercreateinfo_action` table, ignoring.", currentclass); + continue; + } + PlayerInfo info = _playerInfo[currentrace][currentclass]; + if (info != null) + info.action.Add(new PlayerCreateInfoAction(result.Read(2), result.Read(3), result.Read(4))); + + ++count; + } while (result.NextRow()); + + Log.outInfo(LogFilter.ServerLoading, "Loaded {0} player create actions in {1} ms", count, Time.GetMSTimeDiffToNow(time)); + } + } + time = Time.GetMSTime(); + // Loading levels data (class/race dependent) + Log.outInfo(LogFilter.ServerLoading, "Loading Player Create Level Stats Data..."); + { + // 0 1 2 3 4 5 6 + SQLResult result = DB.World.Query("SELECT race, class, level, str, agi, sta, inte FROM player_levelstats"); + + if (result.IsEmpty()) + { + Log.outError(LogFilter.ServerLoading, "Loaded 0 level stats definitions. DB table `player_levelstats` is empty."); + Global.WorldMgr.StopNow(); + return; + } + + uint count = 0; + do + { + uint currentrace = result.Read(0); + if (currentrace >= (int)Race.Max) + { + Log.outError(LogFilter.Sql, "Wrong race {0} in `player_levelstats` table, ignoring.", currentrace); + continue; + } + + uint currentclass = result.Read(1); + if (currentclass >= (int)Class.Max) + { + Log.outError(LogFilter.Sql, "Wrong class {0} in `player_levelstats` table, ignoring.", currentclass); + continue; + } + + uint currentlevel = result.Read(2); + if (currentlevel > WorldConfig.GetIntValue(WorldCfg.MaxPlayerLevel)) + { + if (currentlevel > 255) // hardcoded level maximum + Log.outError(LogFilter.Sql, "Wrong (> {0}) level {1} in `player_levelstats` table, ignoring.", 255, currentlevel); + else + { + Log.outError(LogFilter.Sql, "Unused (> MaxPlayerLevel in worldserver.conf) level {0} in `player_levelstats` table, ignoring.", currentlevel); + ++count; // make result loading percent "expected" correct in case disabled detail mode for example. + } + continue; + } + + var pInfo = _playerInfo[currentrace][currentclass]; + if (pInfo == null) + continue; + + var levelinfo = new PlayerLevelInfo(); + + for (var i = 0; i < (int)Stats.Max; i++) + levelinfo.stats[i] = result.Read(i + 3); + + pInfo.levelInfo[currentlevel - 1] = levelinfo; + ++count; + } while (result.NextRow()); + + // Fill gaps and check integrity + for (uint race = 0; race < (int)Race.Max; ++race) + { + // skip non existed races + if (!CliDB.ChrRacesStorage.ContainsKey(race) || _playerInfo[race][0] == null) + continue; + + for (uint _class = 0; _class < (int)Class.Max; ++_class) + { + // skip non existed classes + if (CliDB.ChrClassesStorage.LookupByKey(_class) == null || _playerInfo[race][_class] == null) + continue; + + PlayerInfo pInfo = _playerInfo[race][_class]; + if (pInfo == null) + continue; + + // skip non loaded combinations + if (pInfo.DisplayId_m == 0 || pInfo.DisplayId_f == 0) + continue; + + // skip expansion races if not playing with expansion + if (WorldConfig.GetIntValue(WorldCfg.Expansion) < (int)Expansion.BurningCrusade && (race == (int)Race.BloodElf || race == (int)Race.Draenei)) + continue; + + // skip expansion classes if not playing with expansion + if (WorldConfig.GetIntValue(WorldCfg.Expansion) < (int)Expansion.WrathOfTheLichKing && _class == (int)Class.Deathknight) + continue; + + if (WorldConfig.GetIntValue(WorldCfg.Expansion) < (int)Expansion.MistsOfPandaria && (race == (int)Race.PandarenNeutral || race == (int)Race.PandarenHorde || race == (int)Race.PandarenAlliance)) + continue; + + if (WorldConfig.GetIntValue(WorldCfg.Expansion) < (int)Expansion.Legion && _class == (int)Class.DemonHunter) + continue; + + // fatal error if no level 1 data + if (pInfo.levelInfo == null || pInfo.levelInfo[0] == null) + { + Log.outError(LogFilter.Sql, "Race {0} Class {1} Level 1 does not have stats data!", race, _class); + Global.WorldMgr.StopNow(); + return; + } + + // fill level gaps + for (var level = 1; level < WorldConfig.GetIntValue(WorldCfg.MaxPlayerLevel); ++level) + { + if (pInfo.levelInfo[level] == null) + { + Log.outError(LogFilter.Sql, "Race {0} Class {1} Level {2} does not have stats data. Using stats data of level {3}.", race, _class, level + 1, level); + pInfo.levelInfo[level] = pInfo.levelInfo[level - 1]; + } + } + } + } + + Log.outInfo(LogFilter.ServerLoading, "Loaded {0} level stats definitions in {1} ms", count, Time.GetMSTimeDiffToNow(time)); + } + time = Time.GetMSTime(); + // Loading xp per level data + Log.outInfo(LogFilter.ServerLoading, "Loading Player Create XP Data..."); + { + _playerXPperLevel = new uint[CliDB.XpGameTable.GetTableRowCount() + 1]; + + // 0 1 + SQLResult result = DB.World.Query("SELECT Level, Experience FROM player_xp_for_level"); + + // load the DBC's levels at first... + for (uint level = 1; level < CliDB.XpGameTable.GetTableRowCount(); ++level) + _playerXPperLevel[level] = (uint)CliDB.XpGameTable.GetRow(level).Total; + + uint count = 0; + // ...overwrite if needed (custom values) + if (!result.IsEmpty()) + { + do + { + uint currentlevel = result.Read(0); + uint currentxp = result.Read(1); + + if (currentlevel >= WorldConfig.GetIntValue(WorldCfg.MaxPlayerLevel)) + { + if (currentlevel > SharedConst.StrongMaxLevel) // hardcoded level maximum + Log.outError(LogFilter.Sql, "Wrong (> {0}) level {1} in `player_xp_for_level` table, ignoring.", 255, currentlevel); + else + { + Log.outError(LogFilter.Sql, "Unused (> MaxPlayerLevel in worldserver.conf) level {0} in `player_xp_for_levels` table, ignoring.", currentlevel); + ++count; // make result loading percent "expected" correct in case disabled detail mode for example. + } + continue; + } + //PlayerXPperLevel + _playerXPperLevel[currentlevel] = currentxp; + ++count; + } while (result.NextRow()); + + // fill level gaps + for (var level = 1; level < WorldConfig.GetIntValue(WorldCfg.MaxPlayerLevel); ++level) + { + if (_playerXPperLevel[level] == 0) + { + Log.outError(LogFilter.Sql, "Level {0} does not have XP for level data. Using data of level [{1}] + 12000.", level + 1, level); + _playerXPperLevel[level] = _playerXPperLevel[level - 1] + 12000; + } + } + } + + Log.outInfo(LogFilter.ServerLoading, "Loaded {0} xp for level definition(s) from database in {1} ms", count, Time.GetMSTimeDiffToNow(time)); + } + } + void PlayerCreateInfoAddItemHelper(uint race, uint _class, uint itemId, int count) + { + if (_playerInfo[race][_class] == null) + return; + + if (count > 0) + _playerInfo[race][_class].item.Add(new PlayerCreateInfoItem(itemId, (uint)count)); + else + { + if (count < -1) + Log.outError(LogFilter.Sql, "Invalid count {0} specified on item {1} be removed from original player create info (use -1)!", count, itemId); + + for (byte gender = 0; gender < (int)Gender.None; ++gender) + { + CharStartOutfitRecord entry = Global.DB2Mgr.GetCharStartOutfitEntry(race, _class, gender); + if (entry != null) + { + bool found = false; + for (var x = 0; x < ItemConst.MaxOutfitItems; ++x) + { + if (entry.ItemID[x] > 0 && entry.ItemID[x] == itemId) + { + found = true; + entry.ItemID[x] = 0; + break; + } + } + + if (!found) + Log.outError(LogFilter.Sql, "Item {0} specified to be removed from original create info not found in dbc!", itemId); + } + } + } + } + + public PlayerInfo GetPlayerInfo(Race raceId, Class classId) + { + if (raceId >= Race.Max) + return null; + + if (classId >= Class.Max) + return null; + + var info = _playerInfo[(int)raceId][(int)classId]; + if (info == null) + return null; + + return info; + } + public void GetPlayerClassLevelInfo(Class _class, uint level, out uint baseMana) + { + baseMana = 0; + if (level < 1 || _class >= Class.Max) + return; + + if (level > WorldConfig.GetIntValue(WorldCfg.MaxPlayerLevel)) + level = (byte)WorldConfig.GetIntValue(WorldCfg.MaxPlayerLevel); + + GtBaseMPRecord mp = CliDB.BaseMPGameTable.GetRow(level); + if (mp == null) + { + Log.outError(LogFilter.Sql, "Tried to get non-existant Class-Level combination data for base mp. Class {0} Level {1}", _class, level); + return; + } + + baseMana = (uint)CliDB.GetGameTableColumnForClass(mp, _class); + } + public PlayerLevelInfo GetPlayerLevelInfo(Race race, Class _class, uint level) + { + if (level < 1 || race >= Race.Max || _class >= Class.Max) + return null; + + PlayerInfo pInfo = _playerInfo[(int)race][(int)_class]; + if (pInfo.DisplayId_m == 0 || pInfo.DisplayId_f == 0) + return null; + + if (level <= WorldConfig.GetIntValue(WorldCfg.MaxPlayerLevel)) + return pInfo.levelInfo[level - 1]; + //else + //return BuildPlayerLevelInfo(race, _class, level, info); + return null; + } + /* + void BuildPlayerLevelInfo(byte race, Class _class, byte level, PlayerLevelInfo info) + { + // base data (last known level) + info = _playerInfo[race][_class].levelInfo[WorldConfig.GetIntValue(WorldCfg.MaxPlayerLevel) - 1]; + + // if conversion from uint32 to uint8 causes unexpected behaviour, change lvl to uint32 + for (int lvl = WorldConfig.GetIntValue(WorldCfg.MaxPlayerLevel) - 1; lvl < level; ++lvl) + { + switch (_class) + { + case Class.Warrior: + info.stats[STAT_STRENGTH] += (lvl > 23 ? 2 : (lvl > 1 ? 1 : 0)); + info.stats[STAT_STAMINA] += (lvl > 23 ? 2 : (lvl > 1 ? 1 : 0)); + info.stats[STAT_AGILITY] += (lvl > 36 ? 1 : (lvl > 6 && (lvl % 2) ? 1 : 0)); + info.stats[STAT_INTELLECT] += (lvl > 9 && !(lvl % 2) ? 1 : 0); + break; + case CLASS_PALADIN: + info.stats[STAT_STRENGTH] += (lvl > 3 ? 1 : 0); + info.stats[STAT_STAMINA] += (lvl > 33 ? 2 : (lvl > 1 ? 1 : 0)); + info.stats[STAT_AGILITY] += (lvl > 38 ? 1 : (lvl > 7 && !(lvl % 2) ? 1 : 0)); + info.stats[STAT_INTELLECT] += (lvl > 6 && (lvl % 2) ? 1 : 0); + break; + case CLASS_HUNTER: + info.stats[STAT_STRENGTH] += (lvl > 4 ? 1 : 0); + info.stats[STAT_STAMINA] += (lvl > 4 ? 1 : 0); + info.stats[STAT_AGILITY] += (lvl > 33 ? 2 : (lvl > 1 ? 1 : 0)); + info.stats[STAT_INTELLECT] += (lvl > 8 && (lvl % 2) ? 1 : 0); + break; + case CLASS_ROGUE: + info.stats[STAT_STRENGTH] += (lvl > 5 ? 1 : 0); + info.stats[STAT_STAMINA] += (lvl > 4 ? 1 : 0); + info.stats[STAT_AGILITY] += (lvl > 16 ? 2 : (lvl > 1 ? 1 : 0)); + info.stats[STAT_INTELLECT] += (lvl > 8 && !(lvl % 2) ? 1 : 0); + break; + case CLASS_PRIEST: + info.stats[STAT_STRENGTH] += (lvl > 9 && !(lvl % 2) ? 1 : 0); + info.stats[STAT_STAMINA] += (lvl > 5 ? 1 : 0); + info.stats[STAT_AGILITY] += (lvl > 38 ? 1 : (lvl > 8 && (lvl % 2) ? 1 : 0)); + info.stats[STAT_INTELLECT] += (lvl > 22 ? 2 : (lvl > 1 ? 1 : 0)); + break; + case CLASS_SHAMAN: + info.stats[STAT_STRENGTH] += (lvl > 34 ? 1 : (lvl > 6 && (lvl % 2) ? 1 : 0)); + info.stats[STAT_STAMINA] += (lvl > 4 ? 1 : 0); + info.stats[STAT_AGILITY] += (lvl > 7 && !(lvl % 2) ? 1 : 0); + info.stats[STAT_INTELLECT] += (lvl > 5 ? 1 : 0); + break; + case CLASS_MAGE: + info.stats[STAT_STRENGTH] += (lvl > 9 && !(lvl % 2) ? 1 : 0); + info.stats[STAT_STAMINA] += (lvl > 5 ? 1 : 0); + info.stats[STAT_AGILITY] += (lvl > 9 && !(lvl % 2) ? 1 : 0); + info.stats[STAT_INTELLECT] += (lvl > 24 ? 2 : (lvl > 1 ? 1 : 0)); + break; + case CLASS_WARLOCK: + info.stats[STAT_STRENGTH] += (lvl > 9 && !(lvl % 2) ? 1 : 0); + info.stats[STAT_STAMINA] += (lvl > 38 ? 2 : (lvl > 3 ? 1 : 0)); + info.stats[STAT_AGILITY] += (lvl > 9 && !(lvl % 2) ? 1 : 0); + info.stats[STAT_INTELLECT] += (lvl > 33 ? 2 : (lvl > 2 ? 1 : 0)); + break; + case CLASS_DRUID: + info.stats[STAT_STRENGTH] += (lvl > 38 ? 2 : (lvl > 6 && (lvl % 2) ? 1 : 0)); + info.stats[STAT_STAMINA] += (lvl > 32 ? 2 : (lvl > 4 ? 1 : 0)); + info.stats[STAT_AGILITY] += (lvl > 38 ? 2 : (lvl > 8 && (lvl % 2) ? 1 : 0)); + info.stats[STAT_INTELLECT] += (lvl > 38 ? 3 : (lvl > 4 ? 1 : 0)); + } + } + } + */ + + //Pets + public void LoadPetLevelInfo() + { + uint oldMSTime = Time.GetMSTime(); + + // 0 1 2 3 4 5 6 7 8 9 + SQLResult result = DB.World.Query("SELECT creature_entry, level, hp, mana, str, agi, sta, inte, spi, armor FROM pet_levelstats"); + + if (result.IsEmpty()) + { + Log.outError(LogFilter.ServerLoading, "Loaded 0 level pet stats definitions. DB table `pet_levelstats` is empty."); + return; + } + + uint count = 0; + do + { + uint creatureid = result.Read(0); + if (GetCreatureTemplate(creatureid) == null) + { + Log.outError(LogFilter.Sql, "Wrong creature id {0} in `pet_levelstats` table, ignoring.", creatureid); + continue; + } + + uint currentlevel = result.Read(1); + if (currentlevel > WorldConfig.GetIntValue(WorldCfg.MaxPlayerLevel)) + { + if (currentlevel > SharedConst.StrongMaxLevel) // hardcoded level maximum + Log.outError(LogFilter.Sql, "Wrong (> {0}) level {1} in `pet_levelstats` table, ignoring.", SharedConst.StrongMaxLevel, currentlevel); + else + { + Log.outInfo(LogFilter.Server, "Unused (> MaxPlayerLevel in worldserver.conf) level {0} in `pet_levelstats` table, ignoring.", currentlevel); + ++count; // make result loading percent "expected" correct in case disabled detail mode for example. + } + continue; + } + else if (currentlevel < 1) + { + Log.outError(LogFilter.Sql, "Wrong (<1) level {0} in `pet_levelstats` table, ignoring.", currentlevel); + continue; + } + + var pInfoMapEntry = petInfoStore.LookupByKey(creatureid); + + if (pInfoMapEntry == null) + pInfoMapEntry = new PetLevelInfo[WorldConfig.GetIntValue(WorldCfg.MaxPlayerLevel)]; + + PetLevelInfo pLevelInfo = new PetLevelInfo(); + pLevelInfo.health = result.Read(2); + pLevelInfo.mana = result.Read(3); + pLevelInfo.armor = result.Read(9); + + for (int i = 0; i < (int)Stats.Max; i++) + { + pLevelInfo.stats[i] = result.Read(i + 4); + } + + pInfoMapEntry[currentlevel - 1] = pLevelInfo; + + ++count; + } + while (result.NextRow()); + + // Fill gaps and check integrity + foreach (var map in petInfoStore) + { + var pInfo = map.Value; + + // fatal error if no level 1 data + if (pInfo == null || pInfo[0].health == 0) + { + Log.outError(LogFilter.Sql, "Creature {0} does not have pet stats data for Level 1!", map.Key); + Global.WorldMgr.StopNow(); + } + + // fill level gaps + for (byte level = 1; level < WorldConfig.GetIntValue(WorldCfg.MaxPlayerLevel); ++level) + { + if (pInfo[level].health == 0) + { + Log.outError(LogFilter.Sql, "Creature {0} has no data for Level {1} pet stats data, using data of Level {2}.", map.Key, level + 1, level); + pInfo[level] = pInfo[level - 1]; + } + } + } + + Log.outInfo(LogFilter.ServerLoading, "Loaded {0} level pet stats definitions in {1} ms", count, Time.GetMSTimeDiffToNow(oldMSTime)); + } + public void LoadPetNames() + { + uint oldMSTime = Time.GetMSTime(); + // 0 1 2 + SQLResult result = DB.World.Query("SELECT word, entry, half FROM pet_name_generation"); + + if (result.IsEmpty()) + { + Log.outInfo(LogFilter.ServerLoading, "Loaded 0 pet name parts. DB table `pet_name_generation` is empty!"); + return; + } + + uint count = 0; + + do + { + string word = result.Read(0); + uint entry = result.Read(1); + bool half = result.Read(2); + if (half) + _petHalfName1.Add(entry, word); + else + _petHalfName0.Add(entry, word); + ++count; + } + while (result.NextRow()); + + Log.outInfo(LogFilter.ServerLoading, "Loaded {0} pet name parts in {1} ms", count, Time.GetMSTimeDiffToNow(oldMSTime)); + } + public void LoadPetNumber() + { + uint oldMSTime = Time.GetMSTime(); + + SQLResult result = DB.Characters.Query("SELECT MAX(id) FROM character_pet"); + if (!result.IsEmpty()) + _hiPetNumber = result.Read(0) + 1; + + Log.outInfo(LogFilter.ServerLoading, "Loaded the max pet number: {0} in {1} ms", _hiPetNumber - 1, Time.GetMSTimeDiffToNow(oldMSTime)); + } + + public PetLevelInfo GetPetLevelInfo(uint creatureid, uint level) + { + if (level > WorldConfig.GetIntValue(WorldCfg.MaxPlayerLevel)) + level = WorldConfig.GetUIntValue(WorldCfg.MaxPlayerLevel); + + var petinfo = petInfoStore.LookupByKey(creatureid); + + if (petinfo == null) + return null; + + return petinfo[level - 1]; // data for level 1 stored in [0] array element, ... + } + public string GeneratePetName(uint entry) + { + var list0 = _petHalfName0[entry]; + var list1 = _petHalfName1[entry]; + + if (list0.Empty() || list1.Empty()) + { + CreatureTemplate cinfo = GetCreatureTemplate(entry); + if (cinfo == null) + return ""; + + string petname = Global.DB2Mgr.GetCreatureFamilyPetName(cinfo.Family, Global.WorldMgr.GetDefaultDbcLocale()); + if (!string.IsNullOrEmpty(petname)) + return petname; + else + return cinfo.Name; + } + + return list0[RandomHelper.IRand(0, list0.Count - 1)] + list1[RandomHelper.IRand(0, list1.Count - 1)]; + } + public uint GeneratePetNumber() + { + if (_hiPetNumber >= 0xFFFFFFFE) + { + Log.outError(LogFilter.Misc, "_hiPetNumber Id overflow!! Can't continue, shutting down server. "); + Global.WorldMgr.StopNow(ShutdownExitCode.Error); + } + return _hiPetNumber++; + } + + //Faction Change + public void LoadFactionChangeAchievements() + { + uint oldMSTime = Time.GetMSTime(); + + SQLResult result = DB.World.Query("SELECT alliance_id, horde_id FROM player_factionchange_achievement"); + if (result.IsEmpty()) + { + Log.outError(LogFilter.ServerLoading, "Loaded 0 faction change achievement pairs. DB table `player_factionchange_achievement` is empty."); + return; + } + + uint count = 0; + do + { + uint alliance = result.Read(0); + uint horde = result.Read(1); + + if (!CliDB.AchievementStorage.ContainsKey(alliance)) + Log.outError(LogFilter.Sql, "Achievement {0} (alliance_id) referenced in `player_factionchange_achievement` does not exist, pair skipped!", alliance); + else if (!CliDB.AchievementStorage.ContainsKey(horde)) + Log.outError(LogFilter.Sql, "Achievement {0} (horde_id) referenced in `player_factionchange_achievement` does not exist, pair skipped!", horde); + else + FactionChangeAchievements[alliance] = horde; + + ++count; + } + while (result.NextRow()); + + Log.outInfo(LogFilter.ServerLoading, "Loaded {0} faction change achievement pairs in {1} ms", count, Time.GetMSTimeDiffToNow(oldMSTime)); + } + public void LoadFactionChangeItems() + { + uint oldMSTime = Time.GetMSTime(); + + SQLResult result = DB.World.Query("SELECT alliance_id, horde_id FROM player_factionchange_items"); + + if (result.IsEmpty()) + { + Log.outInfo(LogFilter.ServerLoading, "Loaded 0 faction change item pairs. DB table `player_factionchange_items` is empty."); + return; + } + + uint count = 0; + do + { + uint alliance = result.Read(0); + uint horde = result.Read(1); + + if (GetItemTemplate(alliance) == null) + Log.outError(LogFilter.Sql, "Item {0} (alliance_id) referenced in `player_factionchange_items` does not exist, pair skipped!", alliance); + else if (GetItemTemplate(horde) == null) + Log.outError(LogFilter.Sql, "Item {0} (horde_id) referenced in `player_factionchange_items` does not exist, pair skipped!", horde); + else + FactionChangeItems[alliance] = horde; + + ++count; + } + while (result.NextRow()); + + Log.outInfo(LogFilter.ServerLoading, "Loaded {0} faction change item pairs in {1} ms", count, Time.GetMSTimeDiffToNow(oldMSTime)); + } + public void LoadFactionChangeQuests() + { + uint oldMSTime = Time.GetMSTime(); + + SQLResult result = DB.World.Query("SELECT alliance_id, horde_id FROM player_factionchange_quests"); + if (result.IsEmpty()) + { + Log.outError(LogFilter.ServerLoading, "Loaded 0 faction change quest pairs. DB table `player_factionchange_quests` is empty."); + return; + } + + uint count = 0; + do + { + uint alliance = result.Read(0); + uint horde = result.Read(1); + + if (Global.ObjectMgr.GetQuestTemplate(alliance) == null) + Log.outError(LogFilter.Sql, "Quest {0} (alliance_id) referenced in `player_factionchange_quests` does not exist, pair skipped!", alliance); + else if (Global.ObjectMgr.GetQuestTemplate(horde) == null) + Log.outError(LogFilter.Sql, "Quest {0} (horde_id) referenced in `player_factionchange_quests` does not exist, pair skipped!", horde); + else + FactionChangeQuests[alliance] = horde; + + ++count; + } + while (result.NextRow()); + + Log.outInfo(LogFilter.ServerLoading, "Loaded {0} faction change quest pairs in {1} ms", count, Time.GetMSTimeDiffToNow(oldMSTime)); + } + public void LoadFactionChangeReputations() + { + uint oldMSTime = Time.GetMSTime(); + + SQLResult result = DB.World.Query("SELECT alliance_id, horde_id FROM player_factionchange_reputations"); + if (result.IsEmpty()) + { + Log.outInfo(LogFilter.ServerLoading, "Loaded 0 faction change reputation pairs. DB table `player_factionchange_reputations` is empty."); + return; + } + + uint count = 0; + do + { + uint alliance = result.Read(0); + uint horde = result.Read(1); + + if (!CliDB.FactionStorage.ContainsKey(alliance)) + Log.outError(LogFilter.Sql, "Reputation {0} (alliance_id) referenced in `player_factionchange_reputations` does not exist, pair skipped!", alliance); + else if (!CliDB.FactionStorage.ContainsKey(horde)) + Log.outError(LogFilter.Sql, "Reputation {0} (horde_id) referenced in `player_factionchange_reputations` does not exist, pair skipped!", horde); + else + FactionChangeReputation[alliance] = horde; + + ++count; + } + while (result.NextRow()); + + Log.outInfo(LogFilter.ServerLoading, "Loaded {0} faction change reputation pairs in {1} ms", count, Time.GetMSTimeDiffToNow(oldMSTime)); + } + public void LoadFactionChangeSpells() + { + uint oldMSTime = Time.GetMSTime(); + + SQLResult result = DB.World.Query("SELECT alliance_id, horde_id FROM player_factionchange_spells"); + if (result.IsEmpty()) + { + Log.outError(LogFilter.ServerLoading, "Loaded 0 faction change spell pairs. DB table `player_factionchange_spells` is empty."); + return; + } + + uint count = 0; + do + { + uint alliance = result.Read(0); + uint horde = result.Read(1); + + if (!Global.SpellMgr.HasSpellInfo(alliance)) + Log.outError(LogFilter.Sql, "Spell {0} (alliance_id) referenced in `player_factionchange_spells` does not exist, pair skipped!", alliance); + else if (!Global.SpellMgr.HasSpellInfo(horde)) + Log.outError(LogFilter.Sql, "Spell {0} (horde_id) referenced in `player_factionchange_spells` does not exist, pair skipped!", horde); + else + FactionChangeSpells[alliance] = horde; + + ++count; + } + while (result.NextRow()); + + Log.outInfo(LogFilter.ServerLoading, "Loaded {0} faction change spell pairs in {1} ms", count, Time.GetMSTimeDiffToNow(oldMSTime)); + } + public void LoadFactionChangeTitles() + { + uint oldMSTime = Time.GetMSTime(); + + SQLResult result = DB.World.Query("SELECT alliance_id, horde_id FROM player_factionchange_titles"); + if (result.IsEmpty()) + { + Log.outInfo(LogFilter.ServerLoading, "Loaded 0 faction change title pairs. DB table `player_factionchange_title` is empty."); + return; + } + + uint count = 0; + do + { + uint alliance = result.Read(0); + uint horde = result.Read(1); + + if (!CliDB.CharTitlesStorage.ContainsKey(alliance)) + Log.outError(LogFilter.Sql, "Title {0} (alliance_id) referenced in `player_factionchange_title` does not exist, pair skipped!", alliance); + else if (!CliDB.CharTitlesStorage.ContainsKey(horde)) + Log.outError(LogFilter.Sql, "Title {0} (horde_id) referenced in `player_factionchange_title` does not exist, pair skipped!", horde); + else + FactionChangeTitles[alliance] = horde; + + ++count; + } + while (result.NextRow()); + + Log.outInfo(LogFilter.ServerLoading, "Loaded {0} faction change title pairs in {1} ms", count, Time.GetMSTimeDiffToNow(oldMSTime)); + } + + //Quests + public void LoadQuests() + { + uint oldMSTime = Time.GetMSTime(); + + // For reload case + _questTemplates.Clear(); + _questObjectives.Clear(); + _exclusiveQuestGroups.Clear(); + + SQLResult result = DB.World.Query("SELECT " + + //0 1 2 3 4 5 6 7 8 9 10 + "ID, QuestType, QuestLevel, QuestPackageID, MinLevel, QuestSortID, QuestInfoID, SuggestedGroupNum, RewardNextQuest, RewardXPDifficulty, RewardXPMultiplier, " + + //11 12 13 14 15 16 17 18 19 20 21 + "RewardMoney, RewardMoneyDifficulty, RewardMoneyMultiplier, RewardBonusMoney, RewardDisplaySpell1, RewardDisplaySpell2, RewardDisplaySpell3, RewardSpell, RewardHonor, RewardKillHonor, StartItem, " + + //22 23 24 25 26 + "RewardArtifactXPDifficulty, RewardArtifactXPMultiplier, RewardArtifactCategoryID, Flags, FlagsEx, " + + //27 28 29 30 31 32 33 34 + "RewardItem1, RewardAmount1, ItemDrop1, ItemDropQuantity1, RewardItem2, RewardAmount2, ItemDrop2, ItemDropQuantity2, " + + //35 36 37 38 39 40 41 42 + "RewardItem3, RewardAmount3, ItemDrop3, ItemDropQuantity3, RewardItem4, RewardAmount4, ItemDrop4, ItemDropQuantity4, " + + //43 44 45 46 47 48 + "RewardChoiceItemID1, RewardChoiceItemQuantity1, RewardChoiceItemDisplayID1, RewardChoiceItemID2, RewardChoiceItemQuantity2, RewardChoiceItemDisplayID2, " + + //49 50 51 52 53 54 + "RewardChoiceItemID3, RewardChoiceItemQuantity3, RewardChoiceItemDisplayID3, RewardChoiceItemID4, RewardChoiceItemQuantity4, RewardChoiceItemDisplayID4, " + + //55 56 57 58 59 60 + "RewardChoiceItemID5, RewardChoiceItemQuantity5, RewardChoiceItemDisplayID5, RewardChoiceItemID6, RewardChoiceItemQuantity6, RewardChoiceItemDisplayID6, " + + //61 62 63 64 65 66 67 68 69 70 + "POIContinent, POIx, POIy, POIPriority, RewardTitle, RewardArenaPoints, RewardSkillLineID, RewardNumSkillUps, PortraitGiver, PortraitTurnIn, " + + //71 72 73 74 75 76 77 78 + "RewardFactionID1, RewardFactionValue1, RewardFactionOverride1, RewardFactionCapIn1, RewardFactionID2, RewardFactionValue2, RewardFactionOverride2, RewardFactionCapIn2, " + + //79 80 81 82 83 84 85 86 + "RewardFactionID3, RewardFactionValue3, RewardFactionOverride3, RewardFactionCapIn3, RewardFactionID4, RewardFactionValue4, RewardFactionOverride4, RewardFactionCapIn4, " + + //87 88 89 90 91 + "RewardFactionID5, RewardFactionValue5, RewardFactionOverride5, RewardFactionCapIn5, RewardFactionFlags, " + + //92 93 94 95 96 97 98 99 + "RewardCurrencyID1, RewardCurrencyQty1, RewardCurrencyID2, RewardCurrencyQty2, RewardCurrencyID3, RewardCurrencyQty3, RewardCurrencyID4, RewardCurrencyQty4, " + + //100 101 102 103 104 105 106 + "AcceptedSoundKitID, CompleteSoundKitID, AreaGroupID, TimeAllowed, AllowableRaces, QuestRewardID, Expansion, " + + //107 108 109 110 111 112 113 114 115 + "LogTitle, LogDescription, QuestDescription, AreaDescription, PortraitGiverText, PortraitGiverName, PortraitTurnInText, PortraitTurnInName, QuestCompletionLog" + + " FROM quest_template"); + + if (result.IsEmpty()) + { + Log.outError(LogFilter.ServerLoading, "Loaded 0 quests definitions. DB table `quest_template` is empty."); + return; + } + + // create multimap previous quest for each existed quest + // some quests can have many previous maps set by NextQuestId in previous quest + // for example set of race quests can lead to single not race specific quest + do + { + Quest newQuest = new Quest(result.GetFields()); + _questTemplates[newQuest.Id] = newQuest; + } + while (result.NextRow()); + + // Load `quest_details` + // 0 1 2 3 4 5 6 7 8 + result = DB.World.Query("SELECT ID, Emote1, Emote2, Emote3, Emote4, EmoteDelay1, EmoteDelay2, EmoteDelay3, EmoteDelay4 FROM quest_details"); + + if (result.IsEmpty()) + { + Log.outError(LogFilter.ServerLoading, "Loaded 0 quest details. DB table `quest_details` is empty."); + } + else + { + do + { + uint questId = result.Read(0); + + var quest = _questTemplates.LookupByKey(questId); + if (quest != null) + quest.LoadQuestDetails(result.GetFields()); + else + Log.outError(LogFilter.Sql, "Table `quest_details` has data for quest {0} but such quest does not exist", questId); + } while (result.NextRow()); + } + + // Load `quest_request_items` + // 0 1 2 3 4 5 + result = DB.World.Query("SELECT ID, EmoteOnComplete, EmoteOnIncomplete, EmoteOnCompleteDelay, EmoteOnIncompleteDelay, CompletionText FROM quest_request_items"); + + if (result.IsEmpty()) + { + Log.outError(LogFilter.ServerLoading, "Loaded 0 quest request items. DB table `quest_request_items` is empty."); + } + else + { + do + { + uint questId = result.Read(0); + + var quest = _questTemplates.LookupByKey(questId); + if (quest != null) + quest.LoadQuestRequestItems(result.GetFields()); + else + Log.outError(LogFilter.Sql, "Table `quest_request_items` has data for quest {0} but such quest does not exist", questId); + } while (result.NextRow()); + } + + // Load `quest_offer_reward` + // 0 1 2 3 4 5 6 7 8 9 + result = DB.World.Query("SELECT ID, Emote1, Emote2, Emote3, Emote4, EmoteDelay1, EmoteDelay2, EmoteDelay3, EmoteDelay4, RewardText FROM quest_offer_reward"); + + if (result.IsEmpty()) + { + Log.outError(LogFilter.ServerLoading, "Loaded 0 quest reward emotes. DB table `quest_offer_reward` is empty."); + } + else + { + do + { + uint questId = result.Read(0); + + var quest = _questTemplates.LookupByKey(questId); + if (quest != null) + quest.LoadQuestOfferReward(result.GetFields()); + else + Log.outError(LogFilter.Sql, "Table `quest_offer_reward` has data for quest {0} but such quest does not exist", questId); + } while (result.NextRow()); + } + + // Load `quest_template_addon` + // 0 1 2 3 4 5 6 7 8 + result = DB.World.Query("SELECT ID, MaxLevel, AllowableClasses, SourceSpellID, PrevQuestID, NextQuestID, ExclusiveGroup, RewardMailTemplateID, RewardMailDelay, " + + //9 10 11 12 13 14 15 16 17 + "RequiredSkillID, RequiredSkillPoints, RequiredMinRepFaction, RequiredMaxRepFaction, RequiredMinRepValue, RequiredMaxRepValue, ProvidedItemCount, RewardMailSenderEntry, SpecialFlags FROM quest_template_addon LEFT JOIN quest_mail_sender ON Id=QuestId"); + + if (result.IsEmpty()) + { + Log.outError(LogFilter.ServerLoading, "Loaded 0 quest template addons. DB table `quest_template_addon` is empty."); + } + else + { + do + { + uint questId = result.Read(0); + + var quest = _questTemplates.LookupByKey(questId); + if (quest != null) + quest.LoadQuestTemplateAddon(result.GetFields()); + else + Log.outError(LogFilter.Sql, "Table `quest_template_addon` has data for quest {0} but such quest does not exist", questId); + } while (result.NextRow()); + } + + // Load `quest_objectives` + // 0 1 2 3 4 5 6 7 8 9 + result = DB.World.Query("SELECT ID, QuestID, Type, StorageIndex, ObjectID, Amount, Flags, Flags, ProgressBarWeight, Description FROM quest_objectives ORDER BY StorageIndex ASC"); + + if (result.IsEmpty()) + { + Log.outError(LogFilter.ServerLoading, "Loaded 0 quest objectives. DB table `quest_objectives` is empty."); + } + else + { + do + { + uint questId = result.Read(1); + + var quest = _questTemplates.LookupByKey(questId); + if (quest != null) + quest.LoadQuestObjective(result.GetFields()); + else + Log.outError(LogFilter.Sql, "Table `quest_objectives` has objective for quest {0} but such quest does not exist", questId); + } while (result.NextRow()); + } + + // Load `quest_visual_effect` join table with quest_objectives because visual effects are based on objective ID (core stores objectives by their index in quest) + // 0 1 2 3 4 + result = DB.World.Query("SELECT v.ID, o.ID, o.QuestID, v.Index, v.VisualEffect FROM quest_visual_effect AS v LEFT JOIN quest_objectives AS o ON v.ID = o.ID ORDER BY v.Index DESC"); + + if (result.IsEmpty()) + { + Log.outError(LogFilter.ServerLoading, "Loaded 0 quest visual effects. DB table `quest_visual_effect` is empty."); + } + else + { + do + { + uint vID = result.Read(0); + uint oID = result.Read(1); + + if (vID == 0) + { + Log.outError(LogFilter.Sql, "Table `quest_visual_effect` has visual effect for null objective id"); + continue; + } + + // objID will be null if match for table join is not found + if (vID != oID) + { + Log.outError(LogFilter.Sql, "Table `quest_visual_effect` has visual effect for objective {0} but such objective does not exist.", vID); + continue; + } + + uint questId = result.Read(2); + + // Do not throw error here because error for non existing quest is thrown while loading quest objectives. we do not need duplication + var quest = _questTemplates.LookupByKey(questId); + if (quest != null) + quest.LoadQuestObjectiveVisualEffect(result.GetFields()); + } while (result.NextRow()); + } + + Dictionary usedMailTemplates = new Dictionary(); + + // Post processing + foreach (var qinfo in _questTemplates.Values) + { + // skip post-loading checks for disabled quests + if (Global.DisableMgr.IsDisabledFor(DisableType.Quest, qinfo.Id, null)) + continue; + + // additional quest integrity checks (GO, creaturetemplate and itemtemplate must be loaded already) + + if (qinfo.Type >= QuestType.Max) + Log.outError(LogFilter.Sql, "Quest {0} has `Method` = {1}, expected values are 0, 1 or 2.", qinfo.Id, qinfo.Type); + + if (Convert.ToBoolean(qinfo.SpecialFlags & ~QuestSpecialFlags.DbAllowed)) + { + Log.outError(LogFilter.Sql, "Quest {0} has `SpecialFlags` = {1} > max allowed value. Correct `SpecialFlags` to value <= {2}", + qinfo.Id, qinfo.SpecialFlags, QuestSpecialFlags.DbAllowed); + qinfo.SpecialFlags &= QuestSpecialFlags.DbAllowed; + } + + if (qinfo.Flags.HasAnyFlag(QuestFlags.Daily) && qinfo.Flags.HasAnyFlag(QuestFlags.Weekly)) + { + Log.outError(LogFilter.Sql, "Weekly Quest {0} is marked as daily quest in `Flags`, removed daily flag.", qinfo.Id); + qinfo.Flags &= ~QuestFlags.Daily; + } + + if (qinfo.Flags.HasAnyFlag(QuestFlags.Daily)) + { + if (!qinfo.SpecialFlags.HasAnyFlag(QuestSpecialFlags.Repeatable)) + { + Log.outError(LogFilter.Sql, "Daily Quest {0} not marked as repeatable in `SpecialFlags`, added.", qinfo.Id); + qinfo.SpecialFlags |= QuestSpecialFlags.Repeatable; + } + } + + if (qinfo.Flags.HasAnyFlag(QuestFlags.Weekly)) + { + if (!qinfo.SpecialFlags.HasAnyFlag(QuestSpecialFlags.Repeatable)) + { + Log.outError(LogFilter.Sql, "Weekly Quest {0} not marked as repeatable in `SpecialFlags`, added.", qinfo.Id); + qinfo.SpecialFlags |= QuestSpecialFlags.Repeatable; + } + } + + if (qinfo.SpecialFlags.HasAnyFlag(QuestSpecialFlags.Monthly)) + { + if (!qinfo.SpecialFlags.HasAnyFlag(QuestSpecialFlags.Repeatable)) + { + Log.outError(LogFilter.Sql, "Monthly quest {0} not marked as repeatable in `SpecialFlags`, added.", qinfo.Id); + qinfo.SpecialFlags |= QuestSpecialFlags.Repeatable; + } + } + + if (Convert.ToBoolean(qinfo.Flags & QuestFlags.Tracking)) + { + // at auto-reward can be rewarded only RewardChoiceItemId[0] + for (int j = 1; j < qinfo.RewardChoiceItemId.Length; ++j) + { + var id = qinfo.RewardChoiceItemId[j]; + if (id != 0) + { + Log.outError(LogFilter.Sql, "Quest {0} has `RewardChoiceItemId{1}` = {2} but item from `RewardChoiceItemId{3}` can't be rewarded with quest flag QUESTFLAGSTRACKING.", + qinfo.Id, j + 1, id, j + 1); + // no changes, quest ignore this data + } + } + } + + if (qinfo.MinLevel == -1 || qinfo.MinLevel > SharedConst.DefaultMaxLevel) + { + Log.outError(LogFilter.Sql, "Quest {0} should be disabled because `MinLevel` = {1}", qinfo.Id, qinfo.MinLevel); + // no changes needed, sending -1 in SMSGQUESTQUERYRESPONSE is valid + } + + // client quest log visual (area case) + if (qinfo.QuestSortID > 0) + { + if (!CliDB.AreaTableStorage.ContainsKey(qinfo.QuestSortID)) + { + Log.outError(LogFilter.Sql, "Quest {0} has `ZoneOrSort` = {1} (zone case) but zone with this id does not exist.", + qinfo.Id, qinfo.QuestSortID); + // no changes, quest not dependent from this value but can have problems at client + } + } + // client quest log visual (sort case) + if (qinfo.QuestSortID < 0) + { + var qSort = CliDB.QuestSortStorage.LookupByKey((uint)-qinfo.QuestSortID); + if (qSort == null) + { + Log.outError(LogFilter.Sql, "Quest {0} has `ZoneOrSort` = {1} (sort case) but quest sort with this id does not exist.", + qinfo.Id, qinfo.QuestSortID); + // no changes, quest not dependent from this value but can have problems at client (note some may be 0, we must allow this so no check) + } + //check for proper RequiredSkillId value (skill case) + var skillid = SharedConst.SkillByQuestSort(-qinfo.QuestSortID); + if (skillid != SkillType.None) + { + if (qinfo.RequiredSkillId != (uint)skillid) + { + Log.outError(LogFilter.Sql, "Quest {0} has `ZoneOrSort` = {1} but `RequiredSkillId` does not have a corresponding value ({2}).", + qinfo.Id, qinfo.QuestSortID, skillid); + //override, and force proper value here? + } + } + } + + // AllowableClasses, can be 0/CLASSMASK_ALL_PLAYABLE to allow any class + if (qinfo.AllowableClasses != 0) + { + if (!Convert.ToBoolean(qinfo.AllowableClasses & (uint)Class.ClassMaskAllPlayable)) + { + Log.outError(LogFilter.Sql, "Quest {0} does not contain any playable classes in `RequiredClasses` ({1}), value set to 0 (all classes).", qinfo.Id, qinfo.AllowableClasses); + qinfo.AllowableClasses = 0; + } + } + // AllowableRaces, can be -1/RACEMASK_ALL_PLAYABLE to allow any race + if (qinfo.AllowableRaces != -1) + { + if (!Convert.ToBoolean(qinfo.AllowableRaces & (uint)Race.RaceMaskAllPlayable)) + { + Log.outError(LogFilter.Sql, "Quest {0} does not contain any playable races in `RequiredRaces` ({1}), value set to 0 (all races).", qinfo.Id, qinfo.AllowableRaces); + qinfo.AllowableRaces = -1; + } + } + // RequiredSkillId, can be 0 + if (qinfo.RequiredSkillId != 0) + { + if (!CliDB.SkillLineStorage.ContainsKey(qinfo.RequiredSkillId)) + { + Log.outError(LogFilter.Sql, "Quest {0} has `RequiredSkillId` = {1} but this skill does not exist", + qinfo.Id, qinfo.RequiredSkillId); + } + } + + if (qinfo.RequiredSkillPoints != 0) + { + if (qinfo.RequiredSkillPoints > Global.WorldMgr.GetConfigMaxSkillValue()) + { + Log.outError(LogFilter.Sql, "Quest {0} has `RequiredSkillPoints` = {1} but max possible skill is {2}, quest can't be done.", + qinfo.Id, qinfo.RequiredSkillPoints, Global.WorldMgr.GetConfigMaxSkillValue()); + // no changes, quest can't be done for this requirement + } + } + // else Skill quests can have 0 skill level, this is ok + + if (qinfo.RequiredMinRepFaction != 0 && !CliDB.FactionStorage.ContainsKey(qinfo.RequiredMinRepFaction)) + { + Log.outError(LogFilter.Sql, "Quest {0} has `RequiredMinRepFaction` = {1} but faction template {2} does not exist, quest can't be done.", + qinfo.Id, qinfo.RequiredMinRepFaction, qinfo.RequiredMinRepFaction); + // no changes, quest can't be done for this requirement + } + + if (qinfo.RequiredMaxRepFaction != 0 && !CliDB.FactionStorage.ContainsKey(qinfo.RequiredMaxRepFaction)) + { + Log.outError(LogFilter.Sql, "Quest {0} has `RequiredMaxRepFaction` = {1} but faction template {2} does not exist, quest can't be done.", + qinfo.Id, qinfo.RequiredMaxRepFaction, qinfo.RequiredMaxRepFaction); + // no changes, quest can't be done for this requirement + } + + if (qinfo.RequiredMinRepValue != 0 && qinfo.RequiredMinRepValue > SharedConst.ReputationCap) + { + Log.outError(LogFilter.Sql, "Quest {0} has `RequiredMinRepValue` = {1} but max reputation is {2}, quest can't be done.", + qinfo.Id, qinfo.RequiredMinRepValue, SharedConst.ReputationCap); + // no changes, quest can't be done for this requirement + } + + if (qinfo.RequiredMinRepValue != 0 && qinfo.RequiredMaxRepValue != 0 && qinfo.RequiredMaxRepValue <= qinfo.RequiredMinRepValue) + { + Log.outError(LogFilter.Sql, "Quest {0} has `RequiredMaxRepValue` = {1} and `RequiredMinRepValue` = {2}, quest can't be done.", + qinfo.Id, qinfo.RequiredMaxRepValue, qinfo.RequiredMinRepValue); + // no changes, quest can't be done for this requirement + } + + if (qinfo.RequiredMinRepFaction == 0 && qinfo.RequiredMinRepValue != 0) + { + Log.outError(LogFilter.Sql, "Quest {0} has `RequiredMinRepValue` = {1} but `RequiredMinRepFaction` is 0, value has no effect", + qinfo.Id, qinfo.RequiredMinRepValue); + // warning + } + + if (qinfo.RequiredMaxRepFaction == 0 && qinfo.RequiredMaxRepValue != 0) + { + Log.outError(LogFilter.Sql, "Quest {0} has `RequiredMaxRepValue` = {1} but `RequiredMaxRepFaction` is 0, value has no effect", + qinfo.Id, qinfo.RequiredMaxRepValue); + // warning + } + + if (qinfo.RewardTitleId != 0 && !CliDB.CharTitlesStorage.ContainsKey(qinfo.RewardTitleId)) + { + Log.outError(LogFilter.Sql, "Quest {0} has `RewardTitleId` = {1} but CharTitle Id {1} does not exist, quest can't be rewarded with title.", + qinfo.Id, qinfo.RewardTitleId); + qinfo.RewardTitleId = 0; + // quest can't reward this title + } + + if (qinfo.SourceItemId != 0) + { + if (Global.ObjectMgr.GetItemTemplate(qinfo.SourceItemId) == null) + { + Log.outError(LogFilter.Sql, "Quest {0} has `SourceItemId` = {1} but item with entry {2} does not exist, quest can't be done.", + qinfo.Id, qinfo.SourceItemId, qinfo.SourceItemId); + qinfo.SourceItemId = 0; // quest can't be done for this requirement + } + else if (qinfo.SourceItemIdCount == 0) + { + Log.outError(LogFilter.Sql, "Quest {0} has `StartItem` = {1} but `ProvidedItemCount` = 0, set to 1 but need fix in DB.", + qinfo.Id, qinfo.SourceItemId); + qinfo.SourceItemIdCount = 1; // update to 1 for allow quest work for backward compatibility with DB + } + } + else if (qinfo.SourceItemIdCount > 0) + { + Log.outError(LogFilter.Sql, "Quest {0} has `SourceItemId` = 0 but `SourceItemIdCount` = {1}, useless value.", + qinfo.Id, qinfo.SourceItemIdCount); + qinfo.SourceItemIdCount = 0; // no quest work changes in fact + } + + if (qinfo.SourceSpellID != 0) + { + SpellInfo spellInfo = Global.SpellMgr.GetSpellInfo(qinfo.SourceSpellID); + if (spellInfo == null) + { + Log.outError(LogFilter.Sql, "Quest {0} has `SourceSpellid` = {1} but spell {1} doesn't exist, quest can't be done.", + qinfo.Id, qinfo.SourceSpellID); + qinfo.SourceSpellID = 0; // quest can't be done for this requirement + } + else if (!Global.SpellMgr.IsSpellValid(spellInfo)) + { + Log.outError(LogFilter.Sql, "Quest {0} has `SourceSpellid` = {1} but spell {1} is broken, quest can't be done.", + qinfo.Id, qinfo.SourceSpellID); + qinfo.SourceSpellID = 0; // quest can't be done for this requirement + } + } + + foreach (QuestObjective obj in qinfo.Objectives) + { + // Store objective for lookup by id + _questObjectives[obj.ID] = obj; + + // Check storage index for objectives which store data + if (obj.StorageIndex < 0) + { + switch (obj.Type) + { + case QuestObjectiveType.Monster: + case QuestObjectiveType.Item: + case QuestObjectiveType.GameObject: + case QuestObjectiveType.TalkTo: + case QuestObjectiveType.PlayerKills: + case QuestObjectiveType.AreaTrigger: + case QuestObjectiveType.WinPetBattleAgainstNpc: + case QuestObjectiveType.ObtainCurrency: + Log.outError(LogFilter.Sql, "Quest {0} objective {1} has invalid StorageIndex = {2} for objective type {3}", qinfo.Id, obj.ID, obj.StorageIndex, obj.Type); + break; + default: + break; + } + } + + switch (obj.Type) + { + case QuestObjectiveType.Item: + qinfo.SetSpecialFlag(QuestSpecialFlags.Deliver); + if (GetItemTemplate((uint)obj.ObjectID) == null) + Log.outError(LogFilter.Sql, "Quest {0} objective {1} has non existing item entry {2}, quest can't be done.", qinfo.Id, obj.ID, obj.ObjectID); + break; + case QuestObjectiveType.Monster: + qinfo.SetSpecialFlag(QuestSpecialFlags.Kill | QuestSpecialFlags.Cast); + if (GetCreatureTemplate((uint)obj.ObjectID) == null) + Log.outError(LogFilter.Sql, "Quest {0} objective {1} has non existing creature entry {2}, quest can't be done.", qinfo.Id, obj.ID, obj.ObjectID); + break; + case QuestObjectiveType.GameObject: + qinfo.SetSpecialFlag(QuestSpecialFlags.Kill | QuestSpecialFlags.Cast); + if (GetGameObjectTemplate((uint)obj.ObjectID) == null) + Log.outError(LogFilter.Sql, "Quest {0} objective {1} has non existing gameobject entry {2}, quest can't be done.", qinfo.Id, obj.ID, obj.ObjectID); + break; + case QuestObjectiveType.TalkTo: + // Need checks (is it creature only?) + qinfo.SetSpecialFlag(QuestSpecialFlags.Cast | QuestSpecialFlags.Speakto); + break; + case QuestObjectiveType.MinReputation: + case QuestObjectiveType.MaxReputation: + if (!CliDB.FactionStorage.ContainsKey((uint)obj.ObjectID)) + Log.outError(LogFilter.Sql, "Quest {0} objective {1} has non existing faction id {2}", qinfo.Id, obj.ID, obj.ObjectID); + break; + case QuestObjectiveType.PlayerKills: + qinfo.SetSpecialFlag(QuestSpecialFlags.Kill); + if (obj.Amount <= 0) + Log.outError(LogFilter.Sql, "Quest {0} objective {1} has invalid player kills count {2}", qinfo.Id, obj.ID, obj.Amount); + break; + case QuestObjectiveType.Currency: + case QuestObjectiveType.HaveCurrency: + case QuestObjectiveType.ObtainCurrency: + if (!CliDB.CurrencyTypesStorage.ContainsKey((uint)obj.ObjectID)) + Log.outError(LogFilter.Sql, "Quest {0} objective {1} has non existing currency {2}", qinfo.Id, obj.ID, obj.ObjectID); + if (obj.Amount <= 0) + Log.outError(LogFilter.Sql, "Quest {0} objective {1} has invalid currency amount {2}", qinfo.Id, obj.ID, obj.Amount); + break; + case QuestObjectiveType.LearnSpell: + if (!Global.SpellMgr.HasSpellInfo((uint)obj.ObjectID)) + Log.outError(LogFilter.Sql, "Quest {0} objective {1} has non existing spell id {2}", qinfo.Id, obj.ID, obj.ObjectID); + break; + case QuestObjectiveType.WinPetBattleAgainstNpc: + if (obj.ObjectID != 0 && Global.ObjectMgr.GetCreatureTemplate((uint)obj.ObjectID) == null) + Log.outError(LogFilter.Sql, "Quest {0} objective {1} has non existing creature entry {2}, quest can't be done.", qinfo.Id, obj.ID, obj.ObjectID); + break; + case QuestObjectiveType.DefeatBattlePet: + if (!CliDB.BattlePetSpeciesStorage.ContainsKey((uint)obj.ObjectID)) + Log.outError(LogFilter.Sql, "Quest {0} objective {1} has non existing battlepet species id {2}", qinfo.Id, obj.ID, obj.ObjectID); + break; + case QuestObjectiveType.CriteriaTree: + if (!CliDB.CriteriaTreeStorage.ContainsKey((uint)obj.ObjectID)) + Log.outError(LogFilter.Sql, "Quest {0} objective {1} has non existing criteria tree id {2}", qinfo.Id, obj.ID, obj.ObjectID); + break; + case QuestObjectiveType.AreaTrigger: + if (!CliDB.AreaTriggerStorage.ContainsKey((uint)obj.ObjectID) && obj.ObjectID != -1) + Log.outError(LogFilter.Sql, "Quest {0} objective {1} has non existing areatrigger id {2}", qinfo.Id, obj.ID, obj.ObjectID); + break; + case QuestObjectiveType.Money: + case QuestObjectiveType.WinPvpPetBattles: + break; + default: + Log.outError(LogFilter.Sql, "Quest {0} objective {1} has unhandled type {2}", qinfo.Id, obj.ID, obj.Type); + break; + } + } + + for (var j = 0; j < SharedConst.QuestItemDropCount; j++) + { + var id = qinfo.ItemDrop[j]; + if (id != 0) + { + if (GetItemTemplate(id) == null) + { + Log.outError(LogFilter.Sql, "Quest {0} has `RequiredSourceItemId{1}` = {2} but item with entry {2} does not exist, quest can't be done.", + qinfo.Id, j + 1, id); + // no changes, quest can't be done for this requirement + } + } + else + { + if (qinfo.ItemDropQuantity[j] > 0) + { + Log.outError(LogFilter.Sql, "Quest {0} has `RequiredSourceItemId{1}` = 0 but `RequiredSourceItemCount{1}` = {2}.", + qinfo.Id, j + 1, qinfo.ItemDropQuantity[j]); + // no changes, quest ignore this data + } + } + } + + for (var j = 0; j < SharedConst.QuestRewardChoicesCount; ++j) + { + var id = qinfo.RewardChoiceItemId[j]; + if (id != 0) + { + if (Global.ObjectMgr.GetItemTemplate(id) == null) + { + Log.outError(LogFilter.Sql, "Quest {0} has `RewardChoiceItemId{1}` = {2} but item with entry {2} does not exist, quest will not reward this item.", + qinfo.Id, j + 1, id); + qinfo.RewardChoiceItemId[j] = 0; // no changes, quest will not reward this + } + + if (qinfo.RewardChoiceItemCount[j] == 0) + { + Log.outError(LogFilter.Sql, "Quest {0} has `RewardChoiceItemId{1}` = {2} but `RewardChoiceItemCount{1}` = 0, quest can't be done.", + qinfo.Id, j + 1, id); + // no changes, quest can't be done + } + } + else if (qinfo.RewardChoiceItemCount[j] > 0) + { + Log.outError(LogFilter.Sql, "Quest {0} has `RewardChoiceItemId{1}` = 0 but `RewardChoiceItemCount{1}` = {3}.", + qinfo.Id, j + 1, qinfo.RewardChoiceItemCount[j]); + // no changes, quest ignore this data + } + } + + for (var j = 0; j < SharedConst.QuestRewardItemCount; ++j) + { + var id = qinfo.RewardItemId[j]; + if (id != 0) + { + if (Global.ObjectMgr.GetItemTemplate(id) == null) + { + Log.outError(LogFilter.Sql, "Quest {0} has `RewardItemId{1}` = {2} but item with entry {3} does not exist, quest will not reward this item.", + qinfo.Id, j + 1, id, id); + qinfo.RewardItemId[j] = 0; // no changes, quest will not reward this item + } + + if (qinfo.RewardItemCount[j] == 0) + { + Log.outError(LogFilter.Sql, "Quest {0} has `RewardItemId{1}` = {2} but `RewardItemIdCount{3}` = 0, quest will not reward this item.", + qinfo.Id, j + 1, id, j + 1); + // no changes + } + } + else if (qinfo.RewardItemCount[j] > 0) + { + Log.outError(LogFilter.Sql, "Quest {0} has `RewardItemId{1}` = 0 but `RewardItemIdCount{2}` = {3}.", + qinfo.Id, j + 1, j + 1, qinfo.RewardItemCount[j]); + // no changes, quest ignore this data + } + } + + for (var j = 0; j < SharedConst.QuestRewardReputationsCount; ++j) + { + if (qinfo.RewardFactionId[j] != 0) + { + if (Math.Abs(qinfo.RewardFactionValue[j]) > 9) + { + Log.outError(LogFilter.Sql, "Quest {0} has RewardFactionValueId{1} = {2}. That is outside the range of valid values (-9 to 9).", qinfo.Id, j + 1, qinfo.RewardFactionValue[j]); + } + if (!CliDB.FactionStorage.ContainsKey(qinfo.RewardFactionId[j])) + { + Log.outError(LogFilter.Sql, "Quest {0} has `RewardFactionId{1}` = {2} but raw faction (faction.dbc) {3} does not exist, quest will not reward reputation for this faction.", + qinfo.Id, j + 1, qinfo.RewardFactionId[j], qinfo.RewardFactionId[j]); + qinfo.RewardFactionId[j] = 0; // quest will not reward this + } + } + + else if (qinfo.RewardFactionOverride[j] != 0) + { + Log.outError(LogFilter.Sql, "Quest {0} has `RewardFactionId{1}` = 0 but `RewardFactionValueIdOverride{2}` = {3}.", + qinfo.Id, j + 1, j + 1, qinfo.RewardFactionOverride[j]); + // no changes, quest ignore this data + } + } + + for (uint i = 0; i < SharedConst.QuestRewardDisplaySpellCount; ++i) + { + if (qinfo.RewardDisplaySpell[i] != 0) + { + SpellInfo spellInfo = Global.SpellMgr.GetSpellInfo(qinfo.RewardSpell); + if (spellInfo == null) + { + Log.outError(LogFilter.Sql, "Quest {0} has `RewardSpell` = {1} but spell {2} does not exist, spell removed as display reward.", + qinfo.Id, qinfo.RewardSpell, qinfo.RewardSpell); + qinfo.RewardDisplaySpell[i] = 0; // no spell reward will display for this quest + } + else if (!Global.SpellMgr.IsSpellValid(spellInfo)) + { + Log.outError(LogFilter.Sql, "Quest {0} has `RewardSpell` = {1} but spell {2} is broken, quest will not have a spell reward.", + qinfo.Id, qinfo.RewardSpell, qinfo.RewardSpell); + qinfo.RewardDisplaySpell[i] = 0; // no spell reward will display for this quest + } + } + } + + if (qinfo.RewardSpell > 0) + { + SpellInfo spellInfo = Global.SpellMgr.GetSpellInfo(qinfo.RewardSpell); + if (spellInfo == null) + { + Log.outError(LogFilter.Sql, "Quest {0} has `RewardSpellCast` = {1} but spell {2} does not exist, quest will not have a spell reward.", + qinfo.Id, qinfo.RewardSpell, qinfo.RewardSpell); + qinfo.RewardSpell = 0; // no spell will be casted on player + } + + else if (!Global.SpellMgr.IsSpellValid(spellInfo)) + { + Log.outError(LogFilter.Sql, "Quest {0} has `RewardSpellCast` = {1} but spell {2} is broken, quest will not have a spell reward.", + qinfo.Id, qinfo.RewardSpell, qinfo.RewardSpell); + qinfo.RewardSpell = 0; // no spell will be casted on player + } + } + + if (qinfo.RewardMailTemplateId != 0) + { + if (!CliDB.MailTemplateStorage.ContainsKey(qinfo.RewardMailTemplateId)) + { + Log.outError(LogFilter.Sql, "Quest {0} has `RewardMailTemplateId` = {1} but mail template {2} does not exist, quest will not have a mail reward.", + qinfo.Id, qinfo.RewardMailTemplateId, qinfo.RewardMailTemplateId); + qinfo.RewardMailTemplateId = 0; // no mail will send to player + qinfo.RewardMailDelay = 0; // no mail will send to player + qinfo.RewardMailSenderEntry = 0; + } + else if (usedMailTemplates.ContainsKey(qinfo.RewardMailTemplateId)) + { + var usedId = usedMailTemplates.LookupByKey(qinfo.RewardMailTemplateId); + Log.outError(LogFilter.Sql, "Quest {0} has `RewardMailTemplateId` = {1} but mail template {2} already used for quest {3}, quest will not have a mail reward.", + qinfo.Id, qinfo.RewardMailTemplateId, qinfo.RewardMailTemplateId, usedId); + qinfo.RewardMailTemplateId = 0; // no mail will send to player + qinfo.RewardMailDelay = 0; // no mail will send to player + qinfo.RewardMailSenderEntry = 0; + } + else + usedMailTemplates[qinfo.RewardMailTemplateId] = qinfo.Id; + } + + if (qinfo.NextQuestInChain != 0) + { + var qNext = _questTemplates.LookupByKey(qinfo.NextQuestInChain); + if (qNext == null) + { + Log.outError(LogFilter.Sql, "Quest {0} has `NextQuestIdChain` = {1} but quest {2} does not exist, quest chain will not work.", + qinfo.Id, qinfo.NextQuestInChain, qinfo.NextQuestInChain); + qinfo.NextQuestInChain = 0; + } + else + qNext.prevChainQuests.Add(qinfo.Id); + } + + for (var j = 0; j < SharedConst.QuestRewardCurrencyCount; ++j) + { + if (qinfo.RewardCurrencyId[j] != 0) + { + if (qinfo.RewardCurrencyCount[j] == 0) + { + Log.outError(LogFilter.Sql, "Quest {0} has `RewardCurrencyId{1}` = {2} but `RewardCurrencyCount{3}` = 0, quest can't be done.", + qinfo.Id, j + 1, qinfo.RewardCurrencyId[j], j + 1); + // no changes, quest can't be done for this requirement + } + + if (!CliDB.CurrencyTypesStorage.ContainsKey(qinfo.RewardCurrencyId[j])) + { + Log.outError(LogFilter.Sql, "Quest {0} has `RewardCurrencyId{1}` = {2} but currency with entry {3} does not exist, quest can't be done.", + qinfo.Id, j + 1, qinfo.RewardCurrencyId[j], qinfo.RewardCurrencyId[j]); + qinfo.RewardCurrencyCount[j] = 0; // prevent incorrect work of quest + } + } + else if (qinfo.RewardCurrencyCount[j] > 0) + { + Log.outError(LogFilter.Sql, "Quest {0} has `RewardCurrencyId{1}` = 0 but `RewardCurrencyCount{2}` = {3}, quest can't be done.", + qinfo.Id, j + 1, j + 1, qinfo.RewardCurrencyCount[j]); + qinfo.RewardCurrencyCount[j] = 0; // prevent incorrect work of quest + } + } + + if (qinfo.SoundAccept != 0) + { + if (!CliDB.SoundKitStorage.ContainsKey(qinfo.SoundAccept)) + { + Log.outError(LogFilter.Sql, "Quest {0} has `SoundAccept` = {1} but sound {2} does not exist, set to 0.", + qinfo.Id, qinfo.SoundAccept, qinfo.SoundAccept); + qinfo.SoundAccept = 0; // no sound will be played + } + } + + if (qinfo.SoundTurnIn != 0) + { + if (!CliDB.SoundKitStorage.ContainsKey(qinfo.SoundTurnIn)) + { + Log.outError(LogFilter.Sql, "Quest {0} has `SoundTurnIn` = {1} but sound {2} does not exist, set to 0.", + qinfo.Id, qinfo.SoundTurnIn, qinfo.SoundTurnIn); + qinfo.SoundTurnIn = 0; // no sound will be played + } + } + + if (qinfo.RewardSkillId > 0) + { + if (!CliDB.SkillLineStorage.ContainsKey(qinfo.RewardSkillId)) + { + Log.outError(LogFilter.Sql, "Quest {0} has `RewardSkillId` = {1} but this skill does not exist", + qinfo.Id, qinfo.RewardSkillId); + } + if (qinfo.RewardSkillPoints == 0) + { + Log.outError(LogFilter.Sql, "Quest {0} has `RewardSkillId` = {1} but `RewardSkillPoints` is 0", + qinfo.Id, qinfo.RewardSkillId); + } + } + + if (qinfo.RewardSkillPoints != 0) + { + if (qinfo.RewardSkillPoints > Global.WorldMgr.GetConfigMaxSkillValue()) + { + Log.outError(LogFilter.Sql, "Quest {0} has `RewardSkillPoints` = {1} but max possible skill is {2}, quest can't be done.", + qinfo.Id, qinfo.RewardSkillPoints, Global.WorldMgr.GetConfigMaxSkillValue()); + // no changes, quest can't be done for this requirement + } + if (qinfo.RewardSkillId == 0) + { + Log.outError(LogFilter.Sql, "Quest {0} has `RewardSkillPoints` = {1} but `RewardSkillId` is 0", + qinfo.Id, qinfo.RewardSkillPoints); + } + } + + // fill additional data stores + if (qinfo.PrevQuestId != 0) + { + if (!_questTemplates.ContainsKey((uint)Math.Abs(qinfo.PrevQuestId))) + Log.outError(LogFilter.Sql, "Quest {0} has PrevQuestId {1}, but no such quest", qinfo.Id, qinfo.PrevQuestId); + else + qinfo.prevQuests.Add(qinfo.PrevQuestId); + } + + if (qinfo.NextQuestId != 0) + { + var nextquest = _questTemplates.LookupByKey((uint)Math.Abs(qinfo.NextQuestId)); + if (nextquest == null) + Log.outError(LogFilter.Sql, "Quest {0} has NextQuestId {1}, but no such quest", qinfo.Id, qinfo.NextQuestId); + else + { + int signedQuestId = qinfo.NextQuestId < 0 ? -(int)qinfo.Id : (int)qinfo.Id; + nextquest.prevQuests.Add(signedQuestId); + } + } + + if (qinfo.ExclusiveGroup != 0) + _exclusiveQuestGroups.Add(qinfo.ExclusiveGroup, qinfo.Id); + if (qinfo.LimitTime != 0) + qinfo.SetSpecialFlag(QuestSpecialFlags.Timed); + } + + // check QUEST_SPECIAL_FLAGS_EXPLORATION_OR_EVENT for spell with SPELL_EFFECT_QUEST_COMPLETE + foreach (var spellInfo in Global.SpellMgr.GetSpellInfoStorage().Values) + { + foreach (SpellEffectInfo effect in spellInfo.GetEffectsForDifficulty(Difficulty.None)) + { + if (effect == null || effect.Effect != SpellEffectName.QuestComplete) + continue; + + uint questId = (uint)effect.MiscValue; + Quest quest = GetQuestTemplate(questId); + + // some quest referenced in spells not exist (outdated spells) + if (quest == null) + continue; + + if (!quest.HasSpecialFlag(QuestSpecialFlags.ExplorationOrEvent)) + { + Log.outError(LogFilter.Sql, "Spell (id: {0}) have SPELL_EFFECT_QUEST_COMPLETE for quest {1}, but quest not have flag QUEST_SPECIAL_FLAGS_EXPLORATION_OR_EVENT. " + + "Quest flags must be fixed, quest modified to enable objective.", spellInfo.Id, questId); + + // this will prevent quest completing without objective + quest.SetSpecialFlag(QuestSpecialFlags.ExplorationOrEvent); + } + } + } + + Log.outInfo(LogFilter.ServerLoading, "Loaded {0} quests definitions in {1} ms", _questTemplates.Count, Time.GetMSTimeDiffToNow(oldMSTime)); + } + public void LoadQuestStartersAndEnders() + { + Log.outInfo(LogFilter.ServerLoading, "Loading GO Start Quest Data..."); + LoadGameobjectQuestStarters(); + Log.outInfo(LogFilter.ServerLoading, "Loading GO End Quest Data..."); + LoadGameobjectQuestEnders(); + Log.outInfo(LogFilter.ServerLoading, "Loading Creature Start Quest Data..."); + LoadCreatureQuestStarters(); + Log.outInfo(LogFilter.ServerLoading, "Loading Creature End Quest Data..."); + LoadCreatureQuestEnders(); + } + public void LoadGameobjectQuestStarters() + { + LoadQuestRelationsHelper(_goQuestRelations, null, "gameobject_queststarter", true, true); + + foreach (var pair in _goQuestRelations) + { + GameObjectTemplate goInfo = GetGameObjectTemplate(pair.Key); + if (goInfo == null) + Log.outError(LogFilter.Sql, "Table `gameobject_queststarter` have data for not existed gameobject entry ({0}) and existed quest {1}", pair.Key, pair.Value); + else if (goInfo.type != GameObjectTypes.QuestGiver) + Log.outError(LogFilter.Sql, "Table `gameobject_queststarter` have data gameobject entry ({0}) for quest {1}, but GO is not GAMEOBJECT_TYPE_QUESTGIVER", pair.Key, pair.Value); + } + } + public void LoadGameobjectQuestEnders() + { + LoadQuestRelationsHelper(_goQuestInvolvedRelations, _goQuestInvolvedRelationsReverse, "gameobject_questender", false, true); + + foreach (var pair in _goQuestInvolvedRelations) + { + GameObjectTemplate goInfo = GetGameObjectTemplate(pair.Key); + if (goInfo == null) + Log.outError(LogFilter.Sql, "Table `gameobject_questender` have data for not existed gameobject entry ({0}) and existed quest {1}", pair.Key, pair.Value); + else if (goInfo.type != GameObjectTypes.QuestGiver) + Log.outError(LogFilter.Sql, "Table `gameobject_questender` have data gameobject entry ({0}) for quest {1}, but GO is not GAMEOBJECT_TYPE_QUESTGIVER", pair.Key, pair.Value); + } + } + public void LoadCreatureQuestStarters() + { + LoadQuestRelationsHelper(_creatureQuestRelations, null, "creature_queststarter", true, false); + + foreach (var pair in _creatureQuestRelations) + { + CreatureTemplate cInfo = GetCreatureTemplate(pair.Key); + if (cInfo == null) + Log.outError(LogFilter.Sql, "Table `creature_queststarter` have data for not existed creature entry ({0}) and existed quest {1}", pair.Key, pair.Value); + else if (!Convert.ToBoolean(cInfo.Npcflag & NPCFlags.QuestGiver)) + Log.outError(LogFilter.Sql, "Table `creature_queststarter` has creature entry ({0}) for quest {1}, but npcflag does not include UNIT_NPC_FLAG_QUESTGIVER", pair.Key, pair.Value); + } + } + public void LoadCreatureQuestEnders() + { + LoadQuestRelationsHelper(_creatureQuestInvolvedRelations, _creatureQuestInvolvedRelationsReverse, "creature_questender", false, false); + + foreach (var pair in _creatureQuestInvolvedRelations) + { + CreatureTemplate cInfo = GetCreatureTemplate(pair.Key); + if (cInfo == null) + Log.outError(LogFilter.Sql, "Table `creature_questender` have data for not existed creature entry ({0}) and existed quest {1}", pair.Key, pair.Value); + else if (!Convert.ToBoolean(cInfo.Npcflag & NPCFlags.QuestGiver)) + Log.outError(LogFilter.Sql, "Table `creature_questender` has creature entry ({0}) for quest {1}, but npcflag does not include UNIT_NPC_FLAG_QUESTGIVER", pair.Key, pair.Value); + } + } + void LoadQuestRelationsHelper(MultiMap map, MultiMap reverseMap, string table, bool starter, bool go) + { + uint oldMSTime = Time.GetMSTime(); + + map.Clear(); // need for reload case + + uint count = 0; + + SQLResult result = DB.World.Query("SELECT id, quest, pool_entry FROM {0} qr LEFT JOIN pool_quest pq ON qr.quest = pq.entry", table); + + if (result.IsEmpty()) + { + Log.outError(LogFilter.ServerLoading, "Loaded 0 quest relations from `{0}`, table is empty.", table); + return; + } + + var poolRelationMap = go ? Global.PoolMgr.mQuestGORelation : Global.PoolMgr.mQuestCreatureRelation; + if (starter) + poolRelationMap.Clear(); + + do + { + uint id = result.Read(0); + uint quest = result.Read(1); + uint poolId = result.Read(2); + + if (!_questTemplates.ContainsKey(quest)) + { + Log.outError(LogFilter.Sql, "Table `{0}`: Quest {1} listed for entry {2} does not exist.", table, quest, id); + continue; + } + + if (poolId == 0 || !starter) + { + map.Add(id, quest); + if (reverseMap != null) + reverseMap.Add(quest, id); + } + else if (starter) + poolRelationMap.Add(quest, id); + + ++count; + } while (result.NextRow()); + + Log.outInfo(LogFilter.ServerLoading, "Loaded {0} quest relations from {1} in {2} ms", count, table, Time.GetMSTimeDiffToNow(oldMSTime)); + } + public void LoadQuestPOI() + { + uint oldMSTime = Time.GetMSTime(); + + _questPOIStorage.Clear(); // need for reload case + + uint count = 0; + + // 0 1 2 3 4 5 6 7 8 9 10 11 12 13 + SQLResult result = DB.World.Query("SELECT QuestID, BlobIndex, Idx1, ObjectiveIndex, QuestObjectiveID, QuestObjectID, MapID, WorldMapAreaId, Floor, Priority, Flags, WorldEffectID, PlayerConditionID, WoDUnk1 FROM quest_poi order by QuestID, Idx1"); + if (result.IsEmpty()) + { + Log.outError(LogFilter.ServerLoading, "Loaded 0 quest POI definitions. DB table `quest_poi` is empty."); + return; + } + + // 0 1 2 3 + SQLResult points = DB.World.Query("SELECT QuestID, Idx1, X, Y FROM quest_poi_points ORDER BY QuestID DESC, Idx1, Idx2"); + Dictionary> POIs = new Dictionary>(); + + if (!points.IsEmpty()) + { + do + { + uint questId = points.Read(0); + int Idx1 = points.Read(1); + int x = points.Read(2); + int y = points.Read(3); + + if (!POIs.ContainsKey(questId)) + POIs[questId] = new MultiMap(); + + QuestPOIPoint point = new QuestPOIPoint(x, y); + POIs[questId].Add(Idx1, point); + } while (points.NextRow()); + } + + do + { + uint QuestID = Convert.ToUInt32(result.Read(0)); + int BlobIndex = result.Read(1); + int Idx1 = result.Read(2); + int ObjectiveIndex = result.Read(3); + int QuestObjectiveID = result.Read(4); + int QuestObjectID = result.Read(5); + int MapID = result.Read(6); + int WorldMapAreaId = result.Read(7); + int Floor = result.Read(8); + int Priority = result.Read(9); + int Flags = result.Read(10); + int WorldEffectID = result.Read(11); + int PlayerConditionID = result.Read(12); + int WoDUnk1 = result.Read(13); + + if (Global.ObjectMgr.GetQuestTemplate(QuestID) == null) + Log.outError(LogFilter.Sql, "`quest_poi` quest id ({0}) Idx1 ({1}) does not exist in `quest_template`", QuestID, Idx1); + + QuestPOI POI = new QuestPOI(BlobIndex, ObjectiveIndex, QuestObjectiveID, QuestObjectID, MapID, WorldMapAreaId, Floor, Priority, Flags, WorldEffectID, PlayerConditionID, WoDUnk1); + if (!POIs.ContainsKey(QuestID) || !POIs[QuestID].ContainsKey(Idx1)) + { + Log.outError(LogFilter.Sql, "Table quest_poi references unknown quest points for quest {0} POI id {1}", QuestID, BlobIndex); + continue; + } + POI.points = POIs[QuestID][Idx1]; + _questPOIStorage.Add(QuestID, POI); + + ++count; + } while (result.NextRow()); + + Log.outInfo(LogFilter.ServerLoading, "Loaded {0} quest POI definitions in {1} ms", count, Time.GetMSTimeDiffToNow(oldMSTime)); + } + public void LoadQuestAreaTriggers() + { + uint oldMSTime = Time.GetMSTime(); + + _questAreaTriggerStorage.Clear(); // need for reload case + + SQLResult result = DB.World.Query("SELECT id, quest FROM areatrigger_involvedrelation"); + + if (result.IsEmpty()) + { + Log.outInfo(LogFilter.ServerLoading, "Loaded 0 quest trigger points. DB table `areatrigger_involvedrelation` is empty."); + return; + } + + uint count = 0; + do + { + ++count; + + uint trigger_ID = result.Read(0); + uint quest_ID = result.Read(1); + + AreaTriggerRecord atEntry = CliDB.AreaTriggerStorage.LookupByKey(trigger_ID); + if (atEntry == null) + { + Log.outError(LogFilter.Sql, "Area trigger (ID:{0}) does not exist in `AreaTrigger.dbc`.", trigger_ID); + continue; + } + + Quest quest = GetQuestTemplate(quest_ID); + + if (quest == null) + { + Log.outError(LogFilter.Sql, "Table `areatrigger_involvedrelation` has record (id: {0}) for not existing quest {1}", trigger_ID, quest_ID); + continue; + } + + if (!quest.HasSpecialFlag(QuestSpecialFlags.ExplorationOrEvent)) + { + Log.outError(LogFilter.Sql, "Table `areatrigger_involvedrelation` has record (id: {0}) for not quest {1}, but quest not have flag QUEST_SPECIAL_FLAGS_EXPLORATION_OR_EVENT. Trigger or quest flags must be fixed, quest modified to require objective.", trigger_ID, quest_ID); + + // this will prevent quest completing without objective + quest.SetSpecialFlag(QuestSpecialFlags.ExplorationOrEvent); + + // continue; - quest modified to required objective and trigger can be allowed. + } + + _questAreaTriggerStorage.Add(trigger_ID, quest_ID); + + } while (result.NextRow()); + + foreach (var pair in _questObjectives) + { + QuestObjective objective = pair.Value; + if (objective.Type == QuestObjectiveType.AreaTrigger) + _questAreaTriggerStorage.Add((uint)objective.ObjectID, objective.QuestID); + } + + Log.outInfo(LogFilter.ServerLoading, "Loaded {0} quest trigger points in {1} ms", count, Time.GetMSTimeDiffToNow(oldMSTime)); + } + public void LoadQuestGreetings() + { + uint oldMSTime = Time.GetMSTime(); + + _questGreetingStorage.Clear(); // need for reload case + + // 0 1 2 3 + SQLResult result = DB.World.Query("SELECT ID, type, GreetEmoteType, GreetEmoteDelay, Greeting FROM quest_greeting"); + if (result.IsEmpty()) + { + Log.outInfo(LogFilter.ServerLoading, "Loaded 0 npc texts, table is empty!"); + return; + } + + do + { + uint id = result.Read(0); + byte type = result.Read(1); + // overwrite + switch (type) + { + case 0: // Creature + type = (byte)TypeId.Unit; + if (Global.ObjectMgr.GetCreatureTemplate(id) == null) + { + Log.outError(LogFilter.Sql, "Table `quest_greeting`: creature template entry {0} does not exist.", id); + continue; + } + break; + case 1: // GameObject + type = (byte)TypeId.GameObject; + if (Global.ObjectMgr.GetGameObjectTemplate(id) == null) + { + Log.outError(LogFilter.Sql, "Table `quest_greeting`: gameobject template entry {0} does not exist.", id); + continue; + } + break; + default: + continue; + } + + ushort greetEmoteType = result.Read(2); + uint greetEmoteDelay = result.Read(3); + string greeting = result.Read(4); + + if (!_questGreetingStorage.ContainsKey(type)) + _questGreetingStorage[type] = new Dictionary(); + + _questGreetingStorage[type][id] = new QuestGreeting(greetEmoteType, greetEmoteDelay, greeting); + } + while (result.NextRow()); + + Log.outInfo(LogFilter.ServerLoading, "Loaded {0} quest_greeting in {1} ms", _questGreetingStorage.Count, Time.GetMSTimeDiffToNow(oldMSTime)); + } + + public Quest GetQuestTemplate(uint questId) + { + return _questTemplates.LookupByKey(questId); + } + public Dictionary GetQuestTemplates() + { + return _questTemplates; + } + public Dictionary GetQuestStorage() + { + return _questTemplates; + } + public MultiMap GetGOQuestRelationMap() + { + return _goQuestRelations; + } + public List GetGOQuestRelationBounds(uint go_entry) + { + return _goQuestRelations.LookupByKey(go_entry); + } + public List GetGOQuestInvolvedRelationBounds(uint go_entry) + { + return _goQuestInvolvedRelations.LookupByKey(go_entry); + } + public List GetGOQuestInvolvedRelationReverseBounds(uint questId) + { + return _goQuestInvolvedRelationsReverse.LookupByKey(questId); + } + public MultiMap GetCreatureQuestRelationMap() + { + return _creatureQuestRelations; + } + public List GetCreatureQuestRelationBounds(uint creature_entry) + { + return _creatureQuestRelations.LookupByKey(creature_entry); + } + public List GetCreatureQuestInvolvedRelationBounds(uint creature_entry) + { + return _creatureQuestInvolvedRelations.LookupByKey(creature_entry); + } + public List GetCreatureQuestInvolvedRelationReverseBounds(uint questId) + { + return _creatureQuestInvolvedRelationsReverse.LookupByKey(questId); + } + public List GetQuestPOIList(uint questId) + { + return _questPOIStorage.LookupByKey(questId); + } + public QuestObjective GetQuestObjective(uint questObjectiveId) + { + return _questObjectives.LookupByKey(questObjectiveId); + } + public List GetQuestsForAreaTrigger(uint triggerId) + { + return _questAreaTriggerStorage.LookupByKey(triggerId); + } + public QuestGreeting GetQuestGreeting(ObjectGuid guid) + { + var dic = _questGreetingStorage.LookupByKey(guid.GetTypeId()); + if (dic.Empty()) + return null; + + var greeting = dic.LookupByKey(guid.GetEntry()); + if (greeting == null) + return null; + + return greeting; + } + + //Spells /Skills / Phases + public void LoadTerrainSwapDefaults() + { + _terrainMapDefaultStore.Clear(); + + uint oldMSTime = Time.GetMSTime(); + + SQLResult result = DB.World.Query("SELECT MapId, TerrainSwapMap FROM `terrain_swap_defaults`"); + if (result.IsEmpty()) + { + Log.outInfo(LogFilter.ServerLoading, "Loaded 0 terrain swap defaults. DB table `terrain_swap_defaults` is empty."); + return; + } + + uint count = 0; + do + { + uint mapId = result.Read(0); + if (!CliDB.MapStorage.ContainsKey(mapId)) + { + Log.outError(LogFilter.Sql, "Map {0} defined in `terrain_swap_defaults` does not exist, skipped.", mapId); + continue; + } + + uint terrainSwap = result.Read(1); + if (!CliDB.MapStorage.ContainsKey(terrainSwap)) + { + Log.outError(LogFilter.Sql, "TerrainSwapMap {0} defined in `terrain_swap_defaults` does not exist, skipped.", terrainSwap); + continue; + } + + _terrainMapDefaultStore.Add(mapId, terrainSwap); + + ++count; + } while (result.NextRow()); + + Log.outInfo(LogFilter.ServerLoading, "Loaded {0} terrain swap defaults in {1} ms.", count, Time.GetMSTimeDiffToNow(oldMSTime)); + } + public void LoadTerrainPhaseInfo() + { + _terrainPhaseInfoStore.Clear(); + + uint oldMSTime = Time.GetMSTime(); + + SQLResult result = DB.World.Query("SELECT Id, TerrainSwapMap FROM `terrain_phase_info`"); + if (result.IsEmpty()) + { + Log.outInfo(LogFilter.ServerLoading, "Loaded 0 terrain phase infos. DB table `terrain_phase_info` is empty."); + return; + } + + uint count = 0; + do + { + uint phaseId = result.Read(0); + if (!CliDB.PhaseStorage.ContainsKey(phaseId)) + { + Log.outError(LogFilter.Sql, "Phase {0} defined in `terrain_phase_info` does not exist, skipped.", phaseId); + continue; + } + + uint terrainSwap = result.Read(1); + + _terrainPhaseInfoStore.Add(phaseId, terrainSwap); + + ++count; + } + while (result.NextRow()); + Log.outInfo(LogFilter.ServerLoading, "Loaded {0} phase infos in {1} ms.", count, Time.GetMSTimeDiffToNow(oldMSTime)); + } + public void LoadTerrainWorldMaps() + { + _terrainWorldMapStore.Clear(); + + uint oldMSTime = Time.GetMSTime(); + + // 0 1 + SQLResult result = DB.World.Query("SELECT TerrainSwapMap, WorldMapArea FROM `terrain_worldmap`"); + + if (result.IsEmpty()) + { + Log.outInfo(LogFilter.ServerLoading, "Loaded 0 terrain world maps. DB table `terrain_worldmap` is empty."); + return; + } + + uint count = 0; + do + { + uint mapId = result.Read(0); + + if (!CliDB.MapStorage.ContainsKey(mapId)) + { + Log.outError(LogFilter.Sql, "TerrainSwapMap {0} defined in `terrain_worldmap` does not exist, skipped.", mapId); + continue; + } + + uint worldMapArea = result.Read(1); + + _terrainWorldMapStore.Add(mapId, worldMapArea); + + ++count; + } while (result.NextRow()); + + Log.outInfo(LogFilter.ServerLoading, "Loaded {0} terrain world maps in {1} ms.", count, Time.GetMSTimeDiffToNow(oldMSTime)); + } + public void LoadAreaPhases() + { + _phases.Clear(); + + uint oldMSTime = Time.GetMSTime(); + + // 0 1 + SQLResult result = DB.World.Query("SELECT AreaId, PhaseId FROM `phase_area`"); + if (result.IsEmpty()) + { + Log.outInfo(LogFilter.ServerLoading, "Loaded 0 phase areas. DB table `phase_area` is empty."); + return; + } + + uint count = 0; + do + { + PhaseInfoStruct phase = new PhaseInfoStruct(); + uint area = result.Read(0); + phase.Id = result.Read(1); + + _phases.Add(area, phase); + + ++count; + } while (result.NextRow()); + + Log.outInfo(LogFilter.ServerLoading, "Loaded {0} phase areas in {1} ms.", count, Time.GetMSTimeDiffToNow(oldMSTime)); + } + public void LoadNPCSpellClickSpells() + { + uint oldMSTime = Time.GetMSTime(); + + _spellClickInfoStorage.Clear(); + // 0 1 2 3 + SQLResult result = DB.World.Query("SELECT npc_entry, spell_id, cast_flags, user_type FROM npc_spellclick_spells"); + + if (result.IsEmpty()) + { + Log.outError(LogFilter.ServerLoading, "Loaded 0 spellclick spells. DB table `npc_spellclick_spells` is empty."); + return; + } + + uint count = 0; + + do + { + uint npc_entry = result.Read(0); + CreatureTemplate cInfo = GetCreatureTemplate(npc_entry); + if (cInfo == null) + { + Log.outError(LogFilter.Sql, "Table npc_spellclick_spells references unknown creature_template {0}. Skipping entry.", npc_entry); + continue; + } + + uint spellid = result.Read(1); + SpellInfo spellinfo = Global.SpellMgr.GetSpellInfo(spellid); + if (spellinfo == null) + { + Log.outError(LogFilter.Sql, "Table npc_spellclick_spells creature: {0} references unknown spellid {1}. Skipping entry.", npc_entry, spellid); + continue; + } + + SpellClickUserTypes userType = (SpellClickUserTypes)result.Read(3); + if (userType >= SpellClickUserTypes.Max) + Log.outError(LogFilter.Sql, "Table npc_spellclick_spells creature: {0} references unknown user type {1}. Skipping entry.", npc_entry, userType); + + byte castFlags = result.Read(2); + SpellClickInfo info = new SpellClickInfo(); + info.spellId = spellid; + info.castFlags = castFlags; + info.userType = userType; + _spellClickInfoStorage.Add(npc_entry, info); + + ++count; + } + while (result.NextRow()); + + // all spellclick data loaded, now we check if there are creatures with NPC_FLAG_SPELLCLICK but with no data + // NOTE: It *CAN* be the other way around: no spellclick flag but with spellclick data, in case of creature-only vehicle accessories + var ctc = GetCreatureTemplates(); + foreach (var creature in ctc.Values) + { + if (creature.Npcflag.HasAnyFlag(NPCFlags.SpellClick) && !_spellClickInfoStorage.ContainsKey(creature.Entry)) + { + Log.outError(LogFilter.Sql, "npc_spellclick_spells: Creature template {0} has UNIT_NPC_FLAG_SPELLCLICK but no data in spellclick table! Removing flag", creature.Entry); + creature.Npcflag &= ~NPCFlags.SpellClick; + } + } + + Log.outInfo(LogFilter.ServerLoading, "Loaded {0} spellclick definitions in {1} ms", count, Time.GetMSTimeDiffToNow(oldMSTime)); + } + public void LoadFishingBaseSkillLevel() + { + uint oldMSTime = Time.GetMSTime(); + + _fishingBaseForAreaStorage.Clear(); // for reload case + + SQLResult result = DB.World.Query("SELECT entry, skill FROM skill_fishing_base_level"); + + if (result.IsEmpty()) + { + Log.outError(LogFilter.ServerLoading, "Loaded 0 areas for fishing base skill level. DB table `skill_fishing_base_level` is empty."); + return; + } + + uint count = 0; + do + { + uint entry = result.Read(0); + int skill = result.Read(1); + + AreaTableRecord fArea = CliDB.AreaTableStorage.LookupByKey(entry); + if (fArea == null) + { + Log.outError(LogFilter.Sql, "AreaId {0} defined in `skill_fishing_base_level` does not exist", entry); + continue; + } + + _fishingBaseForAreaStorage[entry] = skill; + ++count; + } + while (result.NextRow()); + + Log.outInfo(LogFilter.ServerLoading, "Loaded {0} areas for fishing base skill level in {1} ms", count, Time.GetMSTimeDiffToNow(oldMSTime)); + } + public void LoadSkillTiers() + { + uint oldMSTime = Time.GetMSTime(); + + _skillTiers.Clear(); + + SQLResult result = DB.World.Query("SELECT ID, Value1, Value2, Value3, Value4, Value5, Value6, Value7, Value8, Value9, Value10, " + + " Value11, Value12, Value13, Value14, Value15, Value16 FROM skill_tiers"); + + if (result.IsEmpty()) + { + Log.outError(LogFilter.ServerLoading, "Loaded 0 skill max values. DB table `skill_tiers` is empty."); + return; + } + + do + { + uint id = result.Read(0); + SkillTiersEntry tier = new SkillTiersEntry(); + for (int i = 0; i < SkillConst.MaxSkillStep; ++i) + tier.Value[i] = result.Read(1 + i); + + _skillTiers[id] = tier; + + } while (result.NextRow()); + + Log.outInfo(LogFilter.ServerLoading, "Loaded {0} skill max values in {1} ms", _skillTiers.Count, Time.GetMSTimeDiffToNow(oldMSTime)); + } + + public List GetPhaseTerrainSwaps(uint phaseid) { return _terrainPhaseInfoStore[phaseid]; } + public List GetDefaultTerrainSwaps(uint mapid) { return _terrainMapDefaultStore[mapid]; } + public List GetTerrainWorldMaps(uint terrainId) { return _terrainWorldMapStore[terrainId]; } + public MultiMap GetDefaultTerrainSwapStore() { return _terrainMapDefaultStore; } + public List GetPhasesForArea(uint area) { return _phases[area]; } + public MultiMap GetAreaAndZonePhases() { return _phases; } + public List GetPhasesForAreaOrZoneForLoading(uint areaOrZone) + { + return _phases.LookupByKey(areaOrZone); + } + public List GetSpellClickInfoMapBounds(uint creature_id) + { + return _spellClickInfoStorage.LookupByKey(creature_id); + } + public int GetFishingBaseSkillLevel(uint entry) + { + return _fishingBaseForAreaStorage.LookupByKey(entry); + } + public SkillTiersEntry GetSkillTier(uint skillTierId) + { + return _skillTiers.LookupByKey(skillTierId); + } + + //Locales + public void LoadCreatureLocales() + { + uint oldMSTime = Time.GetMSTime(); + + _creatureLocaleStorage.Clear(); // need for reload case + + // 0 1 2 3 4 5 + SQLResult result = DB.World.Query("SELECT entry, locale, Name, NameAlt, Title, TitleAlt FROM creature_template_locale"); + + if (result.IsEmpty()) + return; + + do + { + uint id = result.Read(0); + string localeName = result.Read(1); + string name = result.Read(2); + string nameAlt = result.Read(3); + string title = result.Read(4); + string titleAlt = result.Read(5); + + if (!_creatureLocaleStorage.ContainsKey(id)) + _creatureLocaleStorage[id] = new CreatureLocale(); + + LocaleConstant locale = localeName.ToEnum(); + if (locale == LocaleConstant.enUS) + continue; + + CreatureLocale data = _creatureLocaleStorage[id]; + AddLocaleString(name, locale, data.Name); + AddLocaleString(nameAlt, locale, data.NameAlt); + AddLocaleString(title, locale, data.Title); + AddLocaleString(titleAlt, locale, data.TitleAlt); + + } while (result.NextRow()); + } + public void LoadGameObjectLocales() + { + uint oldMSTime = Time.GetMSTime(); + + _gameObjectLocaleStorage.Clear(); // need for reload case + + // 0 1 2 3 4 + SQLResult result = DB.World.Query("SELECT entry, locale, name, castBarCaption, unk1 FROM gameobject_template_locale"); + if (result.IsEmpty()) + return; + + do + { + uint id = result.Read(0); + string localeName = result.Read(1); + + string name = result.Read(2); + string castBarCaption = result.Read(3); + string unk1 = result.Read(4); + + if (!_gameObjectLocaleStorage.ContainsKey(id)) + _gameObjectLocaleStorage[id] = new GameObjectLocale(); + + GameObjectLocale data = _gameObjectLocaleStorage[id]; + LocaleConstant locale = localeName.ToEnum(); + if (locale == LocaleConstant.enUS) + continue; + + AddLocaleString(name, locale, data.Name); + AddLocaleString(castBarCaption, locale, data.CastBarCaption); + AddLocaleString(unk1, locale, data.Unk1); + + } while (result.NextRow()); + + Log.outInfo(LogFilter.ServerLoading, "Loaded {0} gameobject_template_locale locale strings in {1} ms", _gameObjectLocaleStorage.Count, Time.GetMSTimeDiffToNow(oldMSTime)); + } + public void LoadQuestTemplateLocale() + { + uint oldMSTime = Time.GetMSTime(); + + _questObjectivesLocaleStorage.Clear(); // need for reload case + // 0 1 2 3 4 5 6 7 8 9 10 + SQLResult result = DB.World.Query("SELECT Id, locale, LogTitle, LogDescription, QuestDescription, AreaDescription, PortraitGiverText, PortraitGiverName, PortraitTurnInText, PortraitTurnInName, QuestCompletionLog" + + " FROM quest_template_locale"); + if (result.IsEmpty()) + return; + + do + { + uint id = result.Read(0); + string localeName = result.Read(1); + + string logTitle = result.Read(2); + string logDescription = result.Read(3); + string questDescription = result.Read(4); + string areaDescription = result.Read(5); + string portraitGiverText = result.Read(6); + string portraitGiverName = result.Read(7); + string portraitTurnInText = result.Read(8); + string portraitTurnInName = result.Read(9); + string questCompletionLog = result.Read(10); + + if (!_questTemplateLocaleStorage.ContainsKey(id)) + _questTemplateLocaleStorage[id] = new QuestTemplateLocale(); + + QuestTemplateLocale data = _questTemplateLocaleStorage[id]; + LocaleConstant locale = localeName.ToEnum(); + if (locale == LocaleConstant.enUS) + continue; + + AddLocaleString(logTitle, locale, data.LogTitle); + AddLocaleString(logDescription, locale, data.LogDescription); + AddLocaleString(questDescription, locale, data.QuestDescription); + AddLocaleString(areaDescription, locale, data.AreaDescription); + AddLocaleString(portraitGiverText, locale, data.PortraitGiverText); + AddLocaleString(portraitGiverName, locale, data.PortraitGiverName); + AddLocaleString(portraitTurnInText, locale, data.PortraitTurnInText); + AddLocaleString(portraitTurnInName, locale, data.PortraitTurnInName); + AddLocaleString(questCompletionLog, locale, data.QuestCompletionLog); + } while (result.NextRow()); + + Log.outInfo(LogFilter.ServerLoading, "Loaded {0} Quest Tempalate locale strings in {1} ms", _questTemplateLocaleStorage.Count, Time.GetMSTimeDiffToNow(oldMSTime)); + } + public void LoadQuestObjectivesLocale() + { + uint oldMSTime = Time.GetMSTime(); + + _questObjectivesLocaleStorage.Clear(); // need for reload case + // 0 1 2 + SQLResult result = DB.World.Query("SELECT Id, locale, Description FROM quest_objectives_locale"); + if (result.IsEmpty()) + return; + + do + { + uint id = result.Read(0); + string localeName = result.Read(1); + + string Description = result.Read(2); + + if (!_questObjectivesLocaleStorage.ContainsKey(id)) + _questObjectivesLocaleStorage[id] = new QuestObjectivesLocale(); + + QuestObjectivesLocale data = _questObjectivesLocaleStorage[id]; + LocaleConstant locale = localeName.ToEnum(); + if (locale == LocaleConstant.enUS) + continue; + + AddLocaleString(Description, locale, data.Description); + } + while (result.NextRow()); + + Log.outInfo(LogFilter.ServerLoading, "Loaded {0} Quest Objectives locale strings in {1} ms", _questObjectivesLocaleStorage.Count, Time.GetMSTimeDiffToNow(oldMSTime)); + } + public void LoadQuestOfferRewardLocale() + { + uint oldMSTime = Time.GetMSTime(); + + _questOfferRewardLocaleStorage.Clear(); // need for reload case + // 0 1 2 + SQLResult result = DB.World.Query("SELECT Id, locale, RewardText FROM quest_offer_reward_locale"); + if (result.IsEmpty()) + return; + + do + { + uint id = result.Read(0); + string localeName = result.Read(1); + + string RewardText = result.Read(2); + + if (!_questOfferRewardLocaleStorage.ContainsKey(id)) + _questOfferRewardLocaleStorage[id] = new QuestOfferRewardLocale(); + + QuestOfferRewardLocale data = _questOfferRewardLocaleStorage[id]; + LocaleConstant locale = localeName.ToEnum(); + if (locale == LocaleConstant.enUS) + continue; + + AddLocaleString(RewardText, locale, data.RewardText); + } while (result.NextRow()); + + Log.outInfo(LogFilter.ServerLoading, "Loaded {0} Quest Offer Reward locale strings in {1} ms", _questOfferRewardLocaleStorage.Count, Time.GetMSTimeDiffToNow(oldMSTime)); + } + public void LoadQuestRequestItemsLocale() + { + uint oldMSTime = Time.GetMSTime(); + + _questRequestItemsLocaleStorage.Clear(); // need for reload case + // 0 1 2 + SQLResult result = DB.World.Query("SELECT Id, locale, CompletionText FROM quest_request_items_locale"); + if (result.IsEmpty()) + return; + + do + { + uint id = result.Read(0); + string localeName = result.Read(1); + string completionText = result.Read(2); + + if (!_questRequestItemsLocaleStorage.ContainsKey(id)) + _questRequestItemsLocaleStorage[id] = new QuestRequestItemsLocale(); + + QuestRequestItemsLocale data = _questRequestItemsLocaleStorage[id]; + LocaleConstant locale = localeName.ToEnum(); + if (locale == LocaleConstant.enUS) + continue; + + AddLocaleString(completionText, locale, data.CompletionText); + } while (result.NextRow()); + + Log.outInfo(LogFilter.ServerLoading, "Loaded {0} Quest Request Items locale strings in {1} ms", _questRequestItemsLocaleStorage.Count, Time.GetMSTimeDiffToNow(oldMSTime)); + } + public void LoadGossipMenuItemsLocales() + { + uint oldMSTime = Time.GetMSTime(); + + _gossipMenuItemsLocaleStorage.Clear(); // need for reload case + + SQLResult result = DB.World.Query("SELECT menu_id, id, option_text_loc1, box_text_loc1, option_text_loc2, box_text_loc2, " + + "option_text_loc3, box_text_loc3, option_text_loc4, box_text_loc4, option_text_loc5, box_text_loc5, option_text_loc6, box_text_loc6, " + + "option_text_loc7, box_text_loc7, option_text_loc8, box_text_loc8 FROM locales_gossip_menu_option"); + + if (result.IsEmpty()) + return; + + do + { + uint menuId = result.Read(0); + uint id = result.Read(1); + + GossipMenuItemsLocale data = new GossipMenuItemsLocale(); + for (byte i = 1; i < (int)LocaleConstant.OldTotal; ++i) + { + LocaleConstant locale = (LocaleConstant)i; + AddLocaleString(result.Read(2 + 2 * (i - 1)), locale, data.OptionText); + AddLocaleString(result.Read(2 + 2 * (i - 1) + 1), locale, data.BoxText); + } + + _gossipMenuItemsLocaleStorage[MathFunctions.MakePair32(menuId, id)] = data; + } + while (result.NextRow()); + + Log.outInfo(LogFilter.ServerLoading, "Loaded {0} gossip_menu_option locale strings in {1} ms", _gossipMenuItemsLocaleStorage.Count, Time.GetMSTimeDiffToNow(oldMSTime)); + } + public void LoadPageTextLocales() + { + uint oldMSTime = Time.GetMSTime(); + + _pageTextLocaleStorage.Clear(); // needed for reload case + + // 0 1 2 + SQLResult result = DB.World.Query("SELECT ID, locale, Text FROM page_text_locale"); + if (result.IsEmpty()) + return; + + do + { + uint id = result.Read(0); + string localeName = result.Read(1); + string text = result.Read(2); + + if (!_pageTextLocaleStorage.ContainsKey(id)) + _pageTextLocaleStorage[id] = new PageTextLocale(); + + PageTextLocale data = _pageTextLocaleStorage[id]; + LocaleConstant locale = localeName.ToEnum(); + if (locale == LocaleConstant.enUS) + continue; + + AddLocaleString(text, locale, data.Text); + } while (result.NextRow()); + + Log.outInfo(LogFilter.ServerLoading, "Loaded {0} PageText locale strings in {1} ms", _pageTextLocaleStorage.Count, Time.GetMSTimeDiffToNow(oldMSTime)); + } + public void LoadPointOfInterestLocales() + { + uint oldMSTime = Time.GetMSTime(); + + _pointOfInterestLocaleStorage.Clear(); // need for reload case + + // 0 1 2 + SQLResult result = DB.World.Query("SELECT ID, locale, Name FROM points_of_interest_locale"); + if (result.IsEmpty()) + return; + + do + { + uint id = result.Read(0); + string localeName = result.Read(1); + string name = result.Read(2); + + if (!_pointOfInterestLocaleStorage.ContainsKey(id)) + _pointOfInterestLocaleStorage[id] = new PointOfInterestLocale(); + + PointOfInterestLocale data = _pointOfInterestLocaleStorage[id]; + + LocaleConstant locale = localeName.ToEnum(); + if (locale == LocaleConstant.enUS) + continue; + + AddLocaleString(name, locale, data.Name); + } + while (result.NextRow()); + } + + public CreatureLocale GetCreatureLocale(uint entry) + { + return _creatureLocaleStorage.LookupByKey(entry); + } + public GameObjectLocale GetGameObjectLocale(uint entry) + { + return _gameObjectLocaleStorage.LookupByKey(entry); + } + public QuestTemplateLocale GetQuestLocale(uint entry) + { + return _questTemplateLocaleStorage.LookupByKey(entry); + } + public QuestOfferRewardLocale GetQuestOfferRewardLocale(uint entry) + { + return _questOfferRewardLocaleStorage.LookupByKey(entry); + } + public QuestRequestItemsLocale GetQuestRequestItemsLocale(uint entry) + { + return _questRequestItemsLocaleStorage.LookupByKey(entry); + } + public QuestObjectivesLocale GetQuestObjectivesLocale(uint entry) + { + return _questObjectivesLocaleStorage.LookupByKey(entry); + } + public GossipMenuItemsLocale GetGossipMenuItemsLocale(uint menuId, uint menuItemId) + { + return _gossipMenuItemsLocaleStorage.LookupByKey(MathFunctions.MakePair32(menuId, menuItemId)); + } + public PageTextLocale GetPageTextLocale(uint entry) + { + return _pageTextLocaleStorage.LookupByKey(entry); + } + public PointOfInterestLocale GetPointOfInterestLocale(uint id) + { + return _pointOfInterestLocaleStorage.LookupByKey(id); + } + + //General + public void LoadReputationRewardRate() + { + uint oldMSTime = Time.GetMSTime(); + + _repRewardRateStorage.Clear(); // for reload case + + // 0 1 2 3 4 5 6 7 + SQLResult result = DB.World.Query("SELECT faction, quest_rate, quest_daily_rate, quest_weekly_rate, quest_monthly_rate, quest_repeatable_rate, creature_rate, spell_rate FROM reputation_reward_rate"); + if (result.IsEmpty()) + { + Log.outError(LogFilter.ServerLoading, "Loaded `reputation_reward_rate`, table is empty!"); + return; + } + uint count = 0; + do + { + uint factionId = result.Read(0); + + RepRewardRate repRate = new RepRewardRate(); + + repRate.questRate = result.Read(1); + repRate.questDailyRate = result.Read(2); + repRate.questWeeklyRate = result.Read(3); + repRate.questMonthlyRate = result.Read(4); + repRate.questRepeatableRate = result.Read(5); + repRate.creatureRate = result.Read(6); + repRate.spellRate = result.Read(7); + + FactionRecord factionEntry = CliDB.FactionStorage.LookupByKey(factionId); + if (factionEntry == null) + { + Log.outError(LogFilter.Sql, "Faction (faction.dbc) {0} does not exist but is used in `reputation_reward_rate`", factionId); + continue; + } + + if (repRate.questRate < 0.0f) + { + Log.outError(LogFilter.Sql, "Table reputation_reward_rate has quest_rate with invalid rate {0}, skipping data for faction {1}", repRate.questRate, factionId); + continue; + } + + if (repRate.questDailyRate < 0.0f) + { + Log.outError(LogFilter.Sql, "Table reputation_reward_rate has quest_daily_rate with invalid rate {0}, skipping data for faction {1}", repRate.questDailyRate, factionId); + continue; + } + + if (repRate.questWeeklyRate < 0.0f) + { + Log.outError(LogFilter.Sql, "Table reputation_reward_rate has quest_weekly_rate with invalid rate {0}, skipping data for faction {1}", repRate.questWeeklyRate, factionId); + continue; + } + + if (repRate.questMonthlyRate < 0.0f) + { + Log.outError(LogFilter.Sql, "Table reputation_reward_rate has quest_monthly_rate with invalid rate {0}, skipping data for faction {1}", repRate.questMonthlyRate, factionId); + continue; + } + + if (repRate.questRepeatableRate < 0.0f) + { + Log.outError(LogFilter.Sql, "Table reputation_reward_rate has quest_repeatable_rate with invalid rate {0}, skipping data for faction {1}", repRate.questRepeatableRate, factionId); + continue; + } + + if (repRate.creatureRate < 0.0f) + { + Log.outError(LogFilter.Sql, "Table reputation_reward_rate has creature_rate with invalid rate {0}, skipping data for faction {1}", repRate.creatureRate, factionId); + continue; + } + + if (repRate.spellRate < 0.0f) + { + Log.outError(LogFilter.Sql, "Table reputation_reward_rate has spell_rate with invalid rate {0}, skipping data for faction {1}", repRate.spellRate, factionId); + continue; + } + + _repRewardRateStorage[factionId] = repRate; + + ++count; + } + while (result.NextRow()); + + Log.outInfo(LogFilter.ServerLoading, "Loaded {0} reputation_reward_rate in {1} ms", count, Time.GetMSTimeDiffToNow(oldMSTime)); + } + public void LoadReputationOnKill() + { + uint oldMSTime = Time.GetMSTime(); + + // For reload case + _repOnKillStorage.Clear(); + + // 0 1 2 + SQLResult result = DB.World.Query("SELECT creature_id, RewOnKillRepFaction1, RewOnKillRepFaction2, " + + // 3 4 5 6 7 8 9 + "IsTeamAward1, MaxStanding1, RewOnKillRepValue1, IsTeamAward2, MaxStanding2, RewOnKillRepValue2, TeamDependent " + + "FROM creature_onkill_reputation"); + + if (result.IsEmpty()) + { + Log.outError(LogFilter.ServerLoading, "oaded 0 creature award reputation definitions. DB table `creature_onkill_reputation` is empty."); + return; + } + uint count = 0; + do + { + uint creature_id = result.Read(0); + + ReputationOnKillEntry repOnKill = new ReputationOnKillEntry(); + repOnKill.RepFaction1 = result.Read(1); + repOnKill.RepFaction2 = result.Read(2); + repOnKill.IsTeamAward1 = result.Read(3); + repOnKill.ReputationMaxCap1 = result.Read(4); + repOnKill.RepValue1 = result.Read(5); + repOnKill.IsTeamAward2 = result.Read(6); + repOnKill.ReputationMaxCap2 = result.Read(7); + repOnKill.RepValue2 = result.Read(8); + repOnKill.TeamDependent = result.Read(9); + + if (GetCreatureTemplate(creature_id) == null) + { + Log.outError(LogFilter.Sql, "Table `creature_onkill_reputation` have data for not existed creature entry ({0}), skipped", creature_id); + continue; + } + + if (repOnKill.RepFaction1 != 0) + { + FactionRecord factionEntry1 = CliDB.FactionStorage.LookupByKey(repOnKill.RepFaction1); + if (factionEntry1 == null) + { + Log.outError(LogFilter.Sql, "Faction (faction.dbc) {0} does not exist but is used in `creature_onkill_reputation`", repOnKill.RepFaction1); + continue; + } + } + + if (repOnKill.RepFaction2 != 0) + { + FactionRecord factionEntry2 = CliDB.FactionStorage.LookupByKey(repOnKill.RepFaction2); + if (factionEntry2 == null) + { + Log.outError(LogFilter.Sql, "Faction (faction.dbc) {0} does not exist but is used in `creature_onkill_reputation`", repOnKill.RepFaction2); + continue; + } + } + + _repOnKillStorage[creature_id] = repOnKill; + + ++count; + } while (result.NextRow()); + + Log.outInfo(LogFilter.ServerLoading, "Loaded {0} creature award reputation definitions in {1} ms", count, Time.GetMSTimeDiffToNow(oldMSTime)); + } + public void LoadReputationSpilloverTemplate() + { + var oldMSTime = Time.GetMSTime(); + + _repSpilloverTemplateStorage.Clear(); // for reload case + + // 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 + SQLResult result = DB.World.Query("SELECT faction, faction1, rate_1, rank_1, faction2, rate_2, rank_2, faction3, rate_3, rank_3, faction4, rate_4, rank_4, faction5, rate_5, rank_5 FROM " + + "reputation_spillover_template"); + if (result.IsEmpty()) + { + Log.outError(LogFilter.ServerLoading, "Loaded `reputation_spillover_template`, table is empty."); + return; + } + + uint count = 0; + do + { + uint factionId = result.Read(0); + + RepSpilloverTemplate repTemplate = new RepSpilloverTemplate(); + repTemplate.faction[0] = result.Read(1); + repTemplate.faction_rate[0] = result.Read(2); + repTemplate.faction_rank[0] = result.Read(3); + repTemplate.faction[1] = result.Read(4); + repTemplate.faction_rate[1] = result.Read(5); + repTemplate.faction_rank[1] = result.Read(6); + repTemplate.faction[2] = result.Read(7); + repTemplate.faction_rate[2] = result.Read(8); + repTemplate.faction_rank[2] = result.Read(9); + repTemplate.faction[3] = result.Read(10); + repTemplate.faction_rate[3] = result.Read(11); + repTemplate.faction_rank[3] = result.Read(12); + repTemplate.faction[4] = result.Read(13); + repTemplate.faction_rate[4] = result.Read(14); + repTemplate.faction_rank[4] = result.Read(15); + + var factionEntry = CliDB.FactionStorage.LookupByKey(factionId); + if (factionEntry == null) + { + Log.outError(LogFilter.Sql, "Faction (faction.dbc) {0} does not exist but is used in `reputation_spillover_template`", factionId); + continue; + } + + if (factionEntry.ParentFactionID == 0) + { + Log.outError(LogFilter.Sql, "Faction (faction.dbc) {0} in `reputation_spillover_template` does not belong to any team, skipping", factionId); + continue; + } + + bool invalidSpilloverFaction = false; + for (var i = 0; i < 5; ++i) + { + if (repTemplate.faction[i] != 0) + { + var factionSpillover = CliDB.FactionStorage.LookupByKey(repTemplate.faction[i]); + if (factionSpillover.Id == 0) + { + Log.outError(LogFilter.Sql, "Spillover faction (faction.dbc) {0} does not exist but is used in `reputation_spillover_template` for faction {1}, skipping", repTemplate.faction[i], factionId); + invalidSpilloverFaction = true; + break; + } + + if (!factionSpillover.CanHaveReputation()) + { + Log.outError(LogFilter.Sql, "Spillover faction (faction.dbc) {0} for faction {1} in `reputation_spillover_template` can not be listed for client, and then useless, skipping", + repTemplate.faction[i], factionId); + invalidSpilloverFaction = true; + break; + } + + if (repTemplate.faction_rank[i] >= (uint)ReputationRank.Max) + { + Log.outError(LogFilter.Sql, "Rank {0} used in `reputation_spillover_template` for spillover faction {1} is not valid, skipping", repTemplate.faction_rank[i], repTemplate.faction[i]); + invalidSpilloverFaction = true; + break; + } + } + } + + if (invalidSpilloverFaction) + continue; + + _repSpilloverTemplateStorage[factionId] = repTemplate; + ++count; + } while (result.NextRow()); + + Log.outInfo(LogFilter.ServerLoading, "Loaded {0} reputation_spillover_template in {1} ms", count, Time.GetMSTimeDiffToNow(oldMSTime)); + } + public void LoadTavernAreaTriggers() + { + uint oldMSTime = Time.GetMSTime(); + + _tavernAreaTriggerStorage.Clear(); // need for reload case + + SQLResult result = DB.World.Query("SELECT id FROM areatrigger_tavern"); + + if (result.IsEmpty()) + { + Log.outInfo(LogFilter.ServerLoading, "Loaded 0 tavern triggers. DB table `areatrigger_tavern` is empty."); + return; + } + + uint count = 0; + do + { + ++count; + + uint Trigger_ID = result.Read(0); + + AreaTriggerRecord atEntry = CliDB.AreaTriggerStorage.LookupByKey(Trigger_ID); + if (atEntry == null) + { + Log.outError(LogFilter.Sql, "Area trigger (ID:{0}) does not exist in `AreaTrigger.dbc`.", Trigger_ID); + continue; + } + + _tavernAreaTriggerStorage.Add(Trigger_ID); + } while (result.NextRow()); + + Log.outInfo(LogFilter.ServerLoading, "Loaded {0} tavern triggers in {1} ms", count, Time.GetMSTimeDiffToNow(oldMSTime)); + } + public void LoadMailLevelRewards() + { + uint oldMSTime = Time.GetMSTime(); + + _mailLevelRewardStorage.Clear(); // for reload case + + // 0 1 2 3 + SQLResult result = DB.World.Query("SELECT level, raceMask, mailTemplateId, senderEntry FROM mail_level_reward"); + + if (result.IsEmpty()) + { + Log.outError(LogFilter.ServerLoading, "Loaded 0 level dependent mail rewards. DB table `mail_level_reward` is empty."); + return; + } + + uint count = 0; + do + { + byte level = result.Read(0); + uint raceMask = result.Read(1); + uint mailTemplateId = result.Read(2); + uint senderEntry = result.Read(3); + + if (level > WorldConfig.GetIntValue(WorldCfg.MaxPlayerLevel)) + { + Log.outError(LogFilter.Sql, "Table `mail_level_reward` have data for level {0} that more supported by client ({1}), ignoring.", level, WorldConfig.GetIntValue(WorldCfg.MaxPlayerLevel)); + continue; + } + + if (!Convert.ToBoolean(raceMask & (uint)Race.RaceMaskAllPlayable)) + { + Log.outError(LogFilter.Sql, "Table `mail_level_reward` have raceMask ({0}) for level {1} that not include any player races, ignoring.", raceMask, level); + continue; + } + + if (!CliDB.MailTemplateStorage.ContainsKey(mailTemplateId)) + { + Log.outError(LogFilter.Sql, "Table `mail_level_reward` have invalid mailTemplateId ({0}) for level {1} that invalid not include any player races, ignoring.", mailTemplateId, level); + continue; + } + + if (GetCreatureTemplate(senderEntry) == null) + { + Log.outError(LogFilter.Sql, "Table `mail_level_reward` have not existed sender creature entry ({0}) for level {1} that invalid not include any player races, ignoring.", senderEntry, level); + continue; + } + + _mailLevelRewardStorage.Add(level, new MailLevelReward(raceMask, mailTemplateId, senderEntry)); + + ++count; + } + while (result.NextRow()); + + Log.outInfo(LogFilter.ServerLoading, "Loaded {0} level dependent mail rewards in {1} ms", count, Time.GetMSTimeDiffToNow(oldMSTime)); + } + public void LoadExplorationBaseXP() + { + uint oldMSTime = Time.GetMSTime(); + + SQLResult result = DB.World.Query("SELECT level, basexp FROM exploration_basexp"); + + if (result.IsEmpty()) + { + Log.outError(LogFilter.ServerLoading, "Loaded 0 BaseXP definitions. DB table `exploration_basexp` is empty."); + return; + } + + uint count = 0; + do + { + byte level = result.Read(0); + uint basexp = result.Read(1); + _baseXPTable[level] = basexp; + ++count; + } + while (result.NextRow()); + + Log.outInfo(LogFilter.ServerLoading, "Loaded {0} BaseXP definitions in {1} ms", count, Time.GetMSTimeDiffToNow(oldMSTime)); + } + public void LoadTempSummons() + { + uint oldMSTime = Time.GetMSTime(); + + _tempSummonDataStorage.Clear(); // needed for reload case + + // 0 1 2 3 4 5 6 7 8 9 + SQLResult result = DB.World.Query("SELECT summonerId, summonerType, groupId, entry, position_x, position_y, position_z, orientation, summonType, summonTime FROM creature_summon_groups"); + + if (result.IsEmpty()) + { + Log.outInfo(LogFilter.ServerLoading, "Loaded 0 temp summons. DB table `creature_summon_groups` is empty."); + return; + } + + uint count = 0; + do + { + uint summonerId = result.Read(0); + SummonerType summonerType = (SummonerType)result.Read(1); + byte group = result.Read(2); + + switch (summonerType) + { + case SummonerType.Creature: + if (GetCreatureTemplate(summonerId) == null) + { + Log.outError(LogFilter.Sql, "Table `creature_summon_groups` has summoner with non existing entry {0} for creature summoner type, skipped.", summonerId); + continue; + } + break; + case SummonerType.GameObject: + if (GetGameObjectTemplate(summonerId) == null) + { + Log.outError(LogFilter.Sql, "Table `creature_summon_groups` has summoner with non existing entry {0} for gameobject summoner type, skipped.", summonerId); + continue; + } + break; + case SummonerType.Map: + if (!CliDB.MapStorage.ContainsKey(summonerId)) + { + Log.outError(LogFilter.Sql, "Table `creature_summon_groups` has summoner with non existing entry {0} for map summoner type, skipped.", summonerId); + continue; + } + break; + default: + Log.outError(LogFilter.Sql, "Table `creature_summon_groups` has unhandled summoner type {0} for summoner {1}, skipped.", summonerType, summonerId); + continue; + } + + TempSummonData data = new TempSummonData(); + data.entry = result.Read(3); + + if (GetCreatureTemplate(data.entry) == null) + { + Log.outError(LogFilter.Sql, "Table `creature_summon_groups` has creature in group [Summoner ID: {0}, Summoner Type: {1}, Group ID: {2}] with non existing creature entry {3}, skipped.", + summonerId, summonerType, group, data.entry); + continue; + } + + float posX = result.Read(4); + float posY = result.Read(5); + float posZ = result.Read(6); + float orientation = result.Read(7); + + data.pos = new Position(posX, posY, posZ, orientation); + + data.type = (TempSummonType)result.Read(8); + + if (data.type > TempSummonType.ManualDespawn) + { + Log.outError(LogFilter.Sql, "Table `creature_summon_groups` has unhandled temp summon type {0} in group [Summoner ID: {1}, Summoner Type: {2}, Group ID: {3}] for creature entry {4}, skipped.", + data.type, summonerId, summonerType, group, data.entry); + continue; + } + + data.time = result.Read(9); + + Tuple key = Tuple.Create(summonerId, summonerType, group); + _tempSummonDataStorage.Add(key, data); + + ++count; + + } while (result.NextRow()); + + Log.outInfo(LogFilter.ServerLoading, "Loaded {0} temp summons in {1} ms", count, Time.GetMSTimeDiffToNow(oldMSTime)); + } + public void LoadPageTexts() + { + uint oldMSTime = Time.GetMSTime(); + + // 0 1 2 3 4 + SQLResult result = DB.World.Query("SELECT ID, text, NextPageID, PlayerConditionID, Flags FROM page_text"); + + if (result.IsEmpty()) + { + Log.outInfo(LogFilter.ServerLoading, "Loaded 0 page texts. DB table `page_text` is empty!"); + return; + } + + uint count = 0; + do + { + uint id = result.Read(0); + + PageText pageText = new PageText(); + pageText.Text = result.Read(1); + pageText.NextPageID = result.Read(2); + pageText.PlayerConditionID = result.Read(3); + pageText.Flags = result.Read(4); + + _pageTextStorage[id] = pageText; + ++count; + } + while (result.NextRow()); + + foreach (var pair in _pageTextStorage) + { + if (pair.Value.NextPageID != 0) + { + if (!_pageTextStorage.ContainsKey(pair.Value.NextPageID)) + Log.outError(LogFilter.Sql, "Page text (ID: {0}) has non-existing `NextPageID` ({1})", pair.Key, pair.Value.NextPageID); + + } + } + Log.outInfo(LogFilter.ServerLoading, "Loaded {0} page texts in {1} ms", count, Time.GetMSTimeDiffToNow(oldMSTime)); + } + public void LoadReservedPlayersNames() + { + uint oldMSTime = Time.GetMSTime(); + + _reservedNamesStorage.Clear(); // need for reload case + + SQLResult result = DB.Characters.Query("SELECT name FROM reserved_name"); + + if (result.IsEmpty()) + { + Log.outInfo(LogFilter.ServerLoading, "Loaded 0 reserved player names. DB table `reserved_name` is empty!"); + return; + } + + uint count = 0; + do + { + string name = result.Read(0); + + _reservedNamesStorage.Add(name.ToLower()); + ++count; + } + while (result.NextRow()); + + Log.outInfo(LogFilter.ServerLoading, "Loaded {0} reserved player names in {1} ms", count, Time.GetMSTimeDiffToNow(oldMSTime)); + } + //not very fast function but it is called only once a day, or on starting-up + public void ReturnOrDeleteOldMails(bool serverUp) + { + uint oldMSTime = Time.GetMSTime(); + + long curTime = Time.UnixTime; + DateTime lt = Time.UnixTimeToDateTime(curTime).ToLocalTime(); + long basetime = curTime; + Log.outInfo(LogFilter.Server, "Returning mails current time: hour: {0}, minute: {1}, second: {2} ", lt.Hour, lt.Minute, lt.Second); + + PreparedStatement stmt; + // Delete all old mails without item and without body immediately, if starting server + if (!serverUp) + { + stmt = DB.Characters.GetPreparedStatement(CharStatements.DEL_EMPTY_EXPIRED_MAIL); + stmt.AddValue(0, basetime); + DB.Characters.Execute(stmt); + } + stmt = DB.Characters.GetPreparedStatement(CharStatements.SEL_EXPIRED_MAIL); + stmt.AddValue(0, basetime); + SQLResult result = DB.Characters.Query(stmt); + if (result.IsEmpty()) + { + Log.outInfo(LogFilter.ServerLoading, "No expired mails found."); + return; // any mails need to be returned or deleted + } + + MultiMap itemsCache = new MultiMap(); + stmt = DB.Characters.GetPreparedStatement(CharStatements.SEL_EXPIRED_MAIL_ITEMS); + stmt.AddValue(0, (uint)basetime); + SQLResult items = DB.Characters.Query(stmt); + if (!items.IsEmpty()) + { + MailItemInfo item = new MailItemInfo(); + do + { + item.item_guid = result.Read(0); + item.item_template = result.Read(1); + uint mailId = result.Read(2); + itemsCache.Add(mailId, item); + } while (items.NextRow()); + } + + uint deletedCount = 0; + uint returnedCount = 0; + do + { + Mail m = new Mail(); + m.messageID = result.Read(0); + m.messageType = (MailMessageType)result.Read(1); + m.sender = result.Read(2); + m.receiver = result.Read(3); + bool has_items = result.Read(4); + m.expire_time = result.Read(5); + m.deliver_time = 0; + m.COD = result.Read(6); + m.checkMask = (MailCheckMask)result.Read(7); + m.mailTemplateId = result.Read(8); + + Player player = null; + if (serverUp) + player = Global.ObjAccessor.FindPlayer(ObjectGuid.Create(HighGuid.Player, m.receiver)); + + if (player && player.m_mailsLoaded) + { // this code will run very improbably (the time is between 4 and 5 am, in game is online a player, who has old mail + // his in mailbox and he has already listed his mails) + continue; + } + + // Delete or return mail + if (has_items) + { + // read items from cache + List temp = itemsCache[m.messageID]; + Extensions.Swap(ref m.items, ref temp); + + // if it is mail from non-player, or if it's already return mail, it shouldn't be returned, but deleted + if (m.messageType != MailMessageType.Normal || (m.checkMask.HasAnyFlag(MailCheckMask.CodPayment | MailCheckMask.Returned))) + { + // mail open and then not returned + foreach (var itemInfo in m.items) + { + stmt = DB.Characters.GetPreparedStatement(CharStatements.DEL_ITEM_INSTANCE); + stmt.AddValue(0, itemInfo.item_guid); + DB.Characters.Execute(stmt); + } + } + else + { + // Mail will be returned + stmt = DB.Characters.GetPreparedStatement(CharStatements.UPD_MAIL_RETURNED); + stmt.AddValue(0, m.receiver); + stmt.AddValue(1, m.sender); + stmt.AddValue(2, basetime + 30 * Time.Day); + stmt.AddValue(3, basetime); + stmt.AddValue(4, MailCheckMask.Returned); + stmt.AddValue(5, m.messageID); + DB.Characters.Execute(stmt); + foreach (var itemInfo in m.items) + { + // Update receiver in mail items for its proper delivery, and in instance_item for avoid lost item at sender delete + stmt = DB.Characters.GetPreparedStatement(CharStatements.UPD_MAIL_ITEM_RECEIVER); + stmt.AddValue(0, m.sender); + stmt.AddValue(1, itemInfo.item_guid); + DB.Characters.Execute(stmt); + + stmt = DB.Characters.GetPreparedStatement(CharStatements.UPD_ITEM_OWNER); + stmt.AddValue(0, m.sender); + stmt.AddValue(1, itemInfo.item_guid); + DB.Characters.Execute(stmt); + } + ++returnedCount; + continue; + } + } + + stmt = DB.Characters.GetPreparedStatement(CharStatements.DEL_MAIL_BY_ID); + stmt.AddValue(0, m.messageID); + DB.Characters.Execute(stmt); + ++deletedCount; + } + while (result.NextRow()); + + Log.outInfo(LogFilter.ServerLoading, "Processed {0} expired mails: {1} deleted and {2} returned in {3} ms", deletedCount + returnedCount, deletedCount, returnedCount, Time.GetMSTimeDiffToNow(oldMSTime)); + } + public void LoadSceneTemplates() + { + uint oldMSTime = Time.GetMSTime(); + _sceneTemplateStorage.Clear(); + + SQLResult result = DB.World.Query("SELECT SceneId, Flags, ScriptPackageID, ScriptName FROM scene_template"); + if (result.IsEmpty()) + { + Log.outInfo(LogFilter.ServerLoading, "Loaded 0 scene templates. DB table `scene_template` is empty."); + return; + } + + uint count = 0; + do + { + uint sceneId = result.Read(0); + SceneTemplate sceneTemplate = new SceneTemplate(); + sceneTemplate.SceneId = sceneId; + sceneTemplate.PlaybackFlags = (SceneFlags)result.Read(1); + sceneTemplate.ScenePackageId = result.Read(2); + sceneTemplate.ScriptId = GetScriptId(result.Read(3)); + + _sceneTemplateStorage[sceneId] = sceneTemplate; + + } while (result.NextRow()); + + Log.outInfo(LogFilter.ServerLoading, "Loaded {0} scene templates in {1} ms.", count, Time.GetMSTimeDiffToNow(oldMSTime)); + } + + public MailLevelReward GetMailLevelReward(uint level, uint raceMask) + { + var mailList = _mailLevelRewardStorage.LookupByKey((byte)level); + if (mailList.Empty()) + return null; + + foreach (var mailReward in mailList) + if (Convert.ToBoolean(mailReward.raceMask & raceMask)) + return mailReward; + + return null; + } + public RepRewardRate GetRepRewardRate(uint factionId) + { + return _repRewardRateStorage.LookupByKey(factionId); + } + public RepSpilloverTemplate GetRepSpillover(uint factionId) + { + return _repSpilloverTemplateStorage.LookupByKey(factionId); + } + public ReputationOnKillEntry GetReputationOnKilEntry(uint id) + { + return _repOnKillStorage.LookupByKey(id); + } + public void SetHighestGuids() + { + SQLResult result = DB.Characters.Query("SELECT MAX(guid) FROM characters"); + if (!result.IsEmpty()) + GetGuidSequenceGenerator(HighGuid.Player).Set(result.Read(0) + 1); + + result = DB.Characters.Query("SELECT MAX(guid) FROM item_instance"); + if (!result.IsEmpty()) + GetGuidSequenceGenerator(HighGuid.Item).Set(result.Read(0) + 1); + + // Cleanup other tables from not existed guids ( >= hiItemGuid) + DB.Characters.Execute("DELETE FROM character_inventory WHERE item >= {0}", GetGuidSequenceGenerator(HighGuid.Item).GetNextAfterMaxUsed()); // One-time query + DB.Characters.Execute("DELETE FROM mail_items WHERE item_guid >= {0}", GetGuidSequenceGenerator(HighGuid.Item).GetNextAfterMaxUsed()); // One-time query + DB.Characters.Execute("DELETE FROM auctionhouse WHERE itemguid >= {0}", GetGuidSequenceGenerator(HighGuid.Item).GetNextAfterMaxUsed()); // One-time query + DB.Characters.Execute("DELETE FROM guild_bank_item WHERE item_guid >= {0}", GetGuidSequenceGenerator(HighGuid.Item).GetNextAfterMaxUsed()); // One-time query + + result = DB.World.Query("SELECT MAX(guid) FROM transports"); + if (!result.IsEmpty()) + GetGuidSequenceGenerator(HighGuid.Transport).Set(result.Read(0) + 1); + + result = DB.Characters.Query("SELECT MAX(id) FROM auctionhouse"); + if (!result.IsEmpty()) + _auctionId = result.Read(0) + 1; + + result = DB.Characters.Query("SELECT MAX(id) FROM mail"); + if (!result.IsEmpty()) + _mailId = result.Read(0) + 1; + + result = DB.Characters.Query("SELECT MAX(arenateamid) FROM arena_team"); + if (!result.IsEmpty()) + Global.ArenaTeamMgr.SetNextArenaTeamId(result.Read(0) + 1); + + result = DB.Characters.Query("SELECT MAX(setguid) FROM character_equipmentsets"); + if (!result.IsEmpty()) + _equipmentSetGuid = result.Read(0) + 1; + + result = DB.Characters.Query("SELECT MAX(guildId) FROM guild"); + if (result.GetRowCount() == 1) + Global.GuildMgr.SetNextGuildId(result.Read(0) + 1); + + result = DB.Characters.Query("SELECT MAX(itemId) from character_void_storage"); + if (!result.IsEmpty()) + _voidItemId = result.Read(0) + 1; + + result = DB.World.Query("SELECT MAX(guid) FROM creature"); + if (!result.IsEmpty()) + _creatureSpawnId = result.Read(0) + 1; + + result = DB.World.Query("SELECT MAX(guid) FROM gameobject"); + if (!result.IsEmpty()) + _gameObjectSpawnId = result.Read(0) + 1; + } + public uint GenerateAuctionID() + { + if (_auctionId >= 0xFFFFFFFE) + { + Log.outError(LogFilter.Server, "Auctions ids overflow!! Can't continue, shutting down server. "); + Global.WorldMgr.StopNow(); + } + return _auctionId++; + } + public ulong GenerateEquipmentSetGuid() + { + if (_equipmentSetGuid >= 0xFFFFFFFFFFFFFFFE) + { + Log.outError(LogFilter.Server, "EquipmentSet guid overflow!! Can't continue, shutting down server. "); + Global.WorldMgr.StopNow(); + } + return _equipmentSetGuid++; + } + public uint GenerateMailID() + { + if (_mailId >= 0xFFFFFFFE) + { + Log.outError(LogFilter.Server, "Mail ids overflow!! Can't continue, shutting down server. "); + Global.WorldMgr.StopNow(); + } + return _mailId++; + } + public ulong GenerateVoidStorageItemId() + { + if (_voidItemId >= 0xFFFFFFFFFFFFFFFE) + { + Log.outError(LogFilter.Misc, "_voidItemId overflow!! Can't continue, shutting down server. "); + Global.WorldMgr.StopNow(ShutdownExitCode.Error); + } + return _voidItemId++; + } + public ulong GenerateCreatureSpawnId() + { + if (_creatureSpawnId >= 0xFFFFFFFFFFFFFFFE) + { + Log.outFatal(LogFilter.Server, "Creature spawn id overflow!! Can't continue, shutting down server. "); + Global.WorldMgr.StopNow(); + } + return _creatureSpawnId++; + } + public ulong GenerateGameObjectSpawnId() + { + if (_gameObjectSpawnId >= 0xFFFFFFFFFFFFFFFE) + { + Log.outFatal(LogFilter.Server, "Gameobject spawn id overflow!! Can't continue, shutting down server. "); + Global.WorldMgr.StopNow(); + } + return _gameObjectSpawnId++; + } + public ObjectGuidGenerator GetGenerator(HighGuid high) + { + Contract.Assert(ObjectGuid.IsGlobal(high) || ObjectGuid.IsRealmSpecific(high), "Only global guid can be generated in ObjectMgr context"); + + return GetGuidSequenceGenerator(high); + } + ObjectGuidGenerator GetGuidSequenceGenerator(HighGuid high) + { + if (!_guidGenerators.ContainsKey(high)) + _guidGenerators[high] = new ObjectGuidGenerator(high); + + return _guidGenerators[high]; + } + + public uint GetBaseXP(uint level) + { + return _baseXPTable.ContainsKey(level) ? _baseXPTable[level] : 0; + } + public uint GetXPForLevel(uint level) + { + if (level < _playerXPperLevel.Length) + return _playerXPperLevel[level]; + return 0; + } + public uint GetMaxLevelForExpansion(Expansion expansion) + { + switch (expansion) + { + case Expansion.Classic: + return 60; + case Expansion.BurningCrusade: + return 70; + case Expansion.WrathOfTheLichKing: + return 80; + case Expansion.Cataclysm: + return 85; + case Expansion.MistsOfPandaria: + return 90; + case Expansion.WarlordsOfDraenor: + return 100; + case Expansion.Legion: + return 110; + default: + break; + } + return 0; + } + public CellObjectGuids CreateCellObjectGuids(uint mapid, byte spawnMask, CellCoord cellCoord) + { + return CreateCellObjectGuids(mapid, spawnMask, cellCoord.GetId()); + } + CellObjectGuids CreateCellObjectGuids(uint mapid, byte spawnMask, uint cellid) + { + uint newid = MathFunctions.MakePair32(mapid, spawnMask); + + if (!mapObjectGuidsStore.ContainsKey(newid)) + mapObjectGuidsStore.Add(newid, new Dictionary()); + + if (!mapObjectGuidsStore[newid].ContainsKey(cellid)) + mapObjectGuidsStore[newid].Add(cellid, new CellObjectGuids()); + + return mapObjectGuidsStore[newid][cellid]; + } + public CellObjectGuids GetCellObjectGuids(uint mapid, byte spawnMask, uint cellid) + { + uint newid = MathFunctions.MakePair32(mapid, spawnMask); + + if (mapObjectGuidsStore.ContainsKey(newid) && mapObjectGuidsStore[newid].ContainsKey(cellid)) + return mapObjectGuidsStore[newid][cellid]; + + return null; + } + public Dictionary GetMapObjectGuids(uint mapid, byte spawnMode) + { + var pair = MathFunctions.MakePair32(mapid, spawnMode); + return mapObjectGuidsStore.LookupByKey(pair); + } + public PageText GetPageText(uint pageEntry) + { + return _pageTextStorage.LookupByKey(pageEntry); + } + + public uint GetNearestTaxiNode(float x, float y, float z, uint mapid, Team team) + { + bool found = false; + float dist = 10000; + uint id = 0; + + TaxiNodeFlags requireFlag = (team == Team.Alliance) ? TaxiNodeFlags.Alliance : TaxiNodeFlags.Horde; + foreach (var node in CliDB.TaxiNodesStorage.Values) + { + var i = node.Id; + if (node.MapID != mapid || !node.Flags.HasAnyFlag(requireFlag)) + continue; + + byte field = (byte)((i - 1) / 8); + byte submask = (byte)(1 << (int)((i - 1) % 8)); + + // skip not taxi network nodes + if ((CliDB.TaxiNodesMask[field] & submask) == 0) + continue; + + float dist2 = (node.Pos.X - x) * (node.Pos.X - x) + (node.Pos.Y - y) * (node.Pos.Y - y) + (node.Pos.Z - z) * (node.Pos.Z - z); + if (found) + { + if (dist2 < dist) + { + dist = dist2; + id = i; + } + } + else + { + found = true; + dist = dist2; + id = i; + } + } + + return id; + } + public void GetTaxiPath(uint source, uint destination, out uint path, out uint cost) + { + var pathSet = CliDB.TaxiPathSetBySource.LookupByKey(source); + if (pathSet == null) + { + path = 0; + cost = 0; + return; + } + + var dest_i = pathSet.LookupByKey(destination); + if (dest_i == null) + { + path = 0; + cost = 0; + return; + } + + cost = dest_i.price; + path = dest_i.ID; + } + public uint GetTaxiMountDisplayId(uint id, Team team, bool allowed_alt_team = false) + { + uint mount_id = 0; + + // select mount creature id + TaxiNodesRecord node = CliDB.TaxiNodesStorage.LookupByKey(id); + if (node != null) + { + uint mount_entry = 0; + if (team == Team.Alliance) + mount_entry = node.MountCreatureID[1]; + else + mount_entry = node.MountCreatureID[0]; + + // Fix for Alliance not being able to use Acherus taxi + // only one mount type for both sides + if (mount_entry == 0 && allowed_alt_team) + { + // Simply reverse the selection. At least one team in theory should have a valid mount ID to choose. + mount_entry = team == Team.Alliance ? node.MountCreatureID[0] : node.MountCreatureID[1]; + } + + CreatureTemplate mount_info = GetCreatureTemplate(mount_entry); + if (mount_info != null) + { + mount_id = mount_info.GetRandomValidModelId(); + if (mount_id == 0) + { + Log.outError(LogFilter.Sql, "No displayid found for the taxi mount with the entry {0}! Can't load it!", mount_entry); + return 0; + } + } + } + + // minfo is not actually used but the mount_id was updated + GetCreatureModelRandomGender(ref mount_id); + + return mount_id; + } + + public AreaTriggerStruct GetAreaTrigger(uint trigger) + { + return _areaTriggerStorage.LookupByKey(trigger); + } + public AccessRequirement GetAccessRequirement(uint mapid, Difficulty difficulty) + { + return _accessRequirementStorage.LookupByKey(MathFunctions.MakePair64(mapid, (uint)difficulty)); + } + public bool IsTavernAreaTrigger(uint Trigger_ID) + { + return _tavernAreaTriggerStorage.Contains(Trigger_ID); + } + public AreaTriggerStruct GetGoBackTrigger(uint Map) + { + bool useParentDbValue = false; + uint parentId = 0; + MapRecord mapEntry = CliDB.MapStorage.LookupByKey(Map); + if (mapEntry == null || mapEntry.CorpseMapID < 0) + return null; + + if (mapEntry.IsDungeon()) + { + InstanceTemplate iTemplate = GetInstanceTemplate(Map); + + if (iTemplate == null) + return null; + + parentId = iTemplate.Parent; + useParentDbValue = true; + } + + uint entrance_map = (uint)mapEntry.CorpseMapID; + foreach (var pair in _areaTriggerStorage) + { + if ((!useParentDbValue && pair.Value.target_mapId == entrance_map) || (useParentDbValue && pair.Value.target_mapId == parentId)) + { + AreaTriggerRecord atEntry = CliDB.AreaTriggerStorage.LookupByKey(pair.Key); + if (atEntry != null && atEntry.MapID == Map) + return pair.Value; + } + } + return null; + } + public AreaTriggerStruct GetMapEntranceTrigger(uint Map) + { + foreach (var pair in _areaTriggerStorage) + { + if (pair.Value.target_mapId == Map) + { + AreaTriggerRecord atEntry = CliDB.AreaTriggerStorage.LookupByKey(pair.Key); + if (atEntry != null) + return pair.Value; + } + } + return null; + } + + public SceneTemplate GetSceneTemplate(uint sceneId) + { + return _sceneTemplateStorage.LookupByKey(sceneId); + } + + public List GetSummonGroup(uint summonerId, SummonerType summonerType, byte group) + { + Tuple key = Tuple.Create(summonerId, summonerType, group); + return _tempSummonDataStorage.LookupByKey(key); + } + + public bool IsReservedName(string name) + { + return _reservedNamesStorage.Contains(name.ToLower()); + } + + //Vehicles + public void LoadVehicleTemplateAccessories() + { + uint oldMSTime = Time.GetMSTime(); + + _vehicleTemplateAccessoryStore.Clear(); // needed for reload case + + uint count = 0; + + // 0 1 2 3 4 5 + SQLResult result = DB.World.Query("SELECT `entry`, `accessory_entry`, `seat_id`, `minion`, `summontype`, `summontimer` FROM `vehicle_template_accessory`"); + if (result.IsEmpty()) + { + Log.outError(LogFilter.ServerLoading, "Loaded 0 vehicle template accessories. DB table `vehicle_template_accessory` is empty."); + return; + } + + do + { + uint entry = result.Read(0); + uint accessory = result.Read(1); + sbyte seatId = result.Read(2); + bool isMinion = result.Read(3); + byte summonType = result.Read(4); + uint summonTimer = result.Read(5); + + if (GetCreatureTemplate(entry) == null) + { + Log.outError(LogFilter.Sql, "Table `vehicle_template_accessory`: creature template entry {0} does not exist.", entry); + continue; + } + + if (GetCreatureTemplate(accessory) == null) + { + Log.outError(LogFilter.Sql, "Table `vehicle_template_accessory`: Accessory {0} does not exist.", accessory); + continue; + } + + if (!_spellClickInfoStorage.ContainsKey(entry)) + { + Log.outError(LogFilter.Sql, "Table `vehicle_template_accessory`: creature template entry {0} has no data in npc_spellclick_spells", entry); + continue; + } + + _vehicleTemplateAccessoryStore.Add(entry, new VehicleAccessory(accessory, seatId, isMinion, summonType, summonTimer)); + + ++count; + } + while (result.NextRow()); + + Log.outInfo(LogFilter.ServerLoading, "Loaded {0} Vehicle Template Accessories in {1} ms", count, Time.GetMSTimeDiffToNow(oldMSTime)); + } + public void LoadVehicleAccessories() + { + uint oldMSTime = Time.GetMSTime(); + + _vehicleAccessoryStore.Clear(); // needed for reload case + + uint count = 0; + + // 0 1 2 3 4 5 + SQLResult result = DB.World.Query("SELECT `guid`, `accessory_entry`, `seat_id`, `minion`, `summontype`, `summontimer` FROM `vehicle_accessory`"); + + if (result.IsEmpty()) + { + Log.outInfo(LogFilter.ServerLoading, "Loaded 0 Vehicle Accessories in {0} ms", Time.GetMSTimeDiffToNow(oldMSTime)); + return; + } + + do + { + uint uiGUID = result.Read(0); + uint uiAccessory = result.Read(1); + sbyte uiSeat = result.Read(2); + bool bMinion = result.Read(3); + byte uiSummonType = result.Read(4); + uint uiSummonTimer = result.Read(5); + + if (GetCreatureTemplate(uiAccessory) == null) + { + Log.outError(LogFilter.Sql, "Table `vehicle_accessory`: Accessory {0} does not exist.", uiAccessory); + continue; + } + + _vehicleAccessoryStore.Add(uiGUID, new VehicleAccessory(uiAccessory, uiSeat, bMinion, uiSummonType, uiSummonTimer)); + + ++count; + } + while (result.NextRow()); + + Log.outInfo(LogFilter.ServerLoading, "Loaded {0} Vehicle Accessories in {1} ms", count, Time.GetMSTimeDiffToNow(oldMSTime)); + } + + public List GetVehicleAccessoryList(Vehicle veh) + { + Creature cre = veh.GetBase().ToCreature(); + if (cre != null) + { + // Give preference to GUID-based accessories + var list = _vehicleAccessoryStore.LookupByKey(cre.GetSpawnId()); + if (!list.Empty()) + return list; + } + + // Otherwise return entry-based + return _vehicleTemplateAccessoryStore.LookupByKey(veh.GetCreatureEntry()); + } + + #region Fields + public static LanguageDesc[] lang_description; + //General + Dictionary CypherStringStorage = new Dictionary(); + Dictionary _repRewardRateStorage = new Dictionary(); + Dictionary _repOnKillStorage = new Dictionary(); + Dictionary _repSpilloverTemplateStorage = new Dictionary(); + MultiMap _mailLevelRewardStorage = new MultiMap(); + MultiMap, TempSummonData> _tempSummonDataStorage = new MultiMap, TempSummonData>(); + Dictionary _pageTextStorage = new Dictionary(); + List _reservedNamesStorage = new List(); + Dictionary _sceneTemplateStorage = new Dictionary(); + + Dictionary _raceExpansionRequirementStorage = new Dictionary(); + Dictionary _classExpansionRequirementStorage = new Dictionary(); + Dictionary _realmNameStorage = new Dictionary(); + + //Quest + Dictionary _questTemplates = new Dictionary(); + MultiMap _goQuestRelations = new MultiMap(); + MultiMap _goQuestInvolvedRelations = new MultiMap(); + MultiMap _goQuestInvolvedRelationsReverse = new MultiMap(); + MultiMap _creatureQuestRelations = new MultiMap(); + MultiMap _creatureQuestInvolvedRelations = new MultiMap(); + MultiMap _creatureQuestInvolvedRelationsReverse = new MultiMap(); + public MultiMap _exclusiveQuestGroups = new MultiMap(); + MultiMap _questPOIStorage = new MultiMap(); + MultiMap _questAreaTriggerStorage = new MultiMap(); + Dictionary _questObjectives = new Dictionary(); + Dictionary> _questGreetingStorage = new Dictionary>(); + + //Scripts + List scriptNamesStorage = new List(); + MultiMap spellScriptsStorage = new MultiMap(); + public Dictionary> sSpellScripts = new Dictionary>(); + public Dictionary> sEventScripts = new Dictionary>(); + public Dictionary> sWaypointScripts = new Dictionary>(); + Dictionary areaTriggerScriptStorage = new Dictionary(); + + //Maps + public Dictionary gameTeleStorage = new Dictionary(); + Dictionary> mapObjectGuidsStore = new Dictionary>(); + Dictionary instanceTemplateStorage = new Dictionary(); + public MultiMap GraveYardStorage = new MultiMap(); + List _transportMaps = new List(); + + //Spells /Skills / Phases + MultiMap _terrainPhaseInfoStore = new MultiMap(); + MultiMap _terrainMapDefaultStore = new MultiMap(); + MultiMap _terrainWorldMapStore = new MultiMap(); + MultiMap _phases = new MultiMap(); + MultiMap _spellClickInfoStorage = new MultiMap(); + Dictionary _fishingBaseForAreaStorage = new Dictionary(); + Dictionary _skillTiers = new Dictionary(); + + //Gossip + MultiMap gossipMenuItemsStorage = new MultiMap(); + MultiMap gossipMenusStorage = new MultiMap(); + Dictionary pointsOfInterestStorage = new Dictionary(); + + //Creature + Dictionary creatureTemplateStorage = new Dictionary(); + Dictionary creatureModelStorage = new Dictionary(); + Dictionary creatureDataStorage = new Dictionary(); + Dictionary creatureAddonStorage = new Dictionary(); + MultiMap _creatureQuestItemStorage = new MultiMap(); + Dictionary creatureTemplateAddonStorage = new Dictionary(); + MultiMap> equipmentInfoStorage = new MultiMap>(); + Dictionary linkedRespawnStorage = new Dictionary(); + Dictionary creatureBaseStatsStorage = new Dictionary(); + Dictionary cacheVendorItemStorage = new Dictionary(); + Dictionary cacheTrainerSpellStorage = new Dictionary(); + List[] _difficultyEntries = new List[SharedConst.MaxCreatureDifficulties]; // already loaded difficulty 1 value in creatures, used in CheckCreatureTemplate + List[] _hasDifficultyEntries = new List[SharedConst.MaxCreatureDifficulties]; // already loaded creatures with difficulty 1 values, used in CheckCreatureTemplate + Dictionary _npcTextStorage = new Dictionary(); + + //GameObject + Dictionary gameObjectTemplateStorage = new Dictionary(); + Dictionary gameObjectDataStorage = new Dictionary(); + Dictionary _gameObjectTemplateAddonStore = new Dictionary(); + Dictionary _gameObjectAddonStorage = new Dictionary(); + MultiMap _gameObjectQuestItemStorage = new MultiMap(); + List _gameObjectForQuestStorage = new List(); + + //Item + Dictionary ItemTemplateStorage = new Dictionary(); + + //Player + PlayerInfo[][] _playerInfo = new PlayerInfo[(int)Race.Max][]; + + //Faction Change + public Dictionary FactionChangeAchievements = new Dictionary(); + public Dictionary FactionChangeItems = new Dictionary(); + public Dictionary FactionChangeQuests = new Dictionary(); + public Dictionary FactionChangeReputation = new Dictionary(); + public Dictionary FactionChangeSpells = new Dictionary(); + public Dictionary FactionChangeTitles = new Dictionary(); + + //Pets + Dictionary petInfoStore = new Dictionary(); + MultiMap _petHalfName0 = new MultiMap(); + MultiMap _petHalfName1 = new MultiMap(); + + //Vehicles + MultiMap _vehicleTemplateAccessoryStore = new MultiMap(); + MultiMap _vehicleAccessoryStore = new MultiMap(); + + //Locales + Dictionary _creatureLocaleStorage = new Dictionary(); + Dictionary _gameObjectLocaleStorage = new Dictionary(); + Dictionary _questTemplateLocaleStorage = new Dictionary(); + Dictionary _questObjectivesLocaleStorage = new Dictionary(); + Dictionary _questOfferRewardLocaleStorage = new Dictionary(); + Dictionary _questRequestItemsLocaleStorage = new Dictionary(); + Dictionary _gossipMenuItemsLocaleStorage = new Dictionary(); + Dictionary _pageTextLocaleStorage = new Dictionary(); + Dictionary _pointOfInterestLocaleStorage = new Dictionary(); + + List _tavernAreaTriggerStorage = new List(); + Dictionary _areaTriggerStorage = new Dictionary(); + Dictionary _accessRequirementStorage = new Dictionary(); + MultiMap _dungeonEncounterStorage = new MultiMap(); + + Dictionary _guidGenerators = new Dictionary(); + // first free id for selected id type + uint _auctionId; + ulong _equipmentSetGuid; + uint _mailId; + uint _hiPetNumber; + ulong _voidItemId; + ulong _creatureSpawnId; + ulong _gameObjectSpawnId; + uint[] _playerXPperLevel; + Dictionary _baseXPTable = new Dictionary(); + #endregion + } + + [StructLayout(LayoutKind.Explicit)] + public struct ScriptInfo + { + [FieldOffset(0)] + public ScriptsType type; + + [FieldOffset(4)] + public uint id; + + [FieldOffset(8)] + public uint delay; + + [FieldOffset(12)] + public ScriptCommands command; + + [FieldOffset(16)] + public raw Raw; + + [FieldOffset(16)] + public talk Talk; + + [FieldOffset(16)] + public emote Emote; + + [FieldOffset(16)] + public fieldset FieldSet; + + [FieldOffset(16)] + public moveto MoveTo; + + [FieldOffset(16)] + public flagtoggle FlagToggle; + + [FieldOffset(16)] + public teleportto TeleportTo; + + [FieldOffset(16)] + public questexplored QuestExplored; + + [FieldOffset(16)] + public killcredit KillCredit; + + [FieldOffset(16)] + public respawngameobject RespawnGameObject; + + [FieldOffset(16)] + public tempsummoncreature TempSummonCreature; + + [FieldOffset(16)] + public toggledoor ToggleDoor; + + [FieldOffset(16)] + public removeaura RemoveAura; + + [FieldOffset(16)] + public castspell CastSpell; + + [FieldOffset(16)] + public playsound PlaySound; + + [FieldOffset(16)] + public createitem CreateItem; + + [FieldOffset(16)] + public despawnself DespawnSelf; + + [FieldOffset(16)] + public loadpath LoadPath; + + [FieldOffset(16)] + public callscript CallScript; + + [FieldOffset(16)] + public kill Kill; + + [FieldOffset(16)] + public orientation Orientation; + + [FieldOffset(16)] + public equip Equip; + + [FieldOffset(16)] + public model Model; + + [FieldOffset(16)] + public playmovie PlayMovie; + + [FieldOffset(16)] + public movement Movement; + + [FieldOffset(16)] + public playanimkit PlayAnimKit; + + public string GetDebugInfo() + { + return string.Format("{0} ('{1}' script id: {2})", command, Global.ObjectMgr.GetScriptsTableNameByType(type), id); + } + + #region Structs + public unsafe struct raw + { + public fixed uint nData[3]; + public fixed float fData[4]; + } + + public struct talk // TALK (0) + { + public ChatMsg ChatType; // datalong + public eScriptFlags Flags; // datalong2 + public int TextID; // dataint + } + + public struct emote // EMOTE (1) + { + public uint EmoteID; // datalong + public eScriptFlags Flags; // datalong2 + } + + public struct fieldset // FIELDSET (2) + { + public uint FieldID; // datalong + public uint FieldValue; // datalong2 + } + + public struct moveto // MOVETO (3) + { + public uint Unused1; // datalong + public uint TravelTime; // datalong2 + public int Unused2; // dataint + + public float DestX; + public float DestY; + public float DestZ; + } + + public struct flagtoggle // FLAGSET (4) + // FLAGREMOVE (5) + { + public uint FieldID; // datalong + public uint FieldValue; // datalong2 + } + + public struct teleportto // TELEPORTTO (6) + { + public uint MapID; // datalong + public eScriptFlags Flags; // datalong2 + public int Unused1; // dataint + + public float DestX; + public float DestY; + public float DestZ; + public float Orientation; + } + + public struct questexplored // QUESTEXPLORED (7) + { + public uint QuestID; // datalong + public uint Distance; // datalong2 + } + + public struct killcredit // KILLCREDIT (8) + { + public uint CreatureEntry; // datalong + public eScriptFlags Flags; // datalong2 + } + + public struct respawngameobject // RESPAWNGAMEOBJECT (9) + { + public uint GOGuid; // datalong + public uint DespawnDelay; // datalong2 + } + + public struct tempsummoncreature // TEMPSUMMONCREATURE (10) + { + public uint CreatureEntry; // datalong + public uint DespawnDelay; // datalong2 + public int Unused1; // dataint + + public float PosX; + public float PosY; + public float PosZ; + public float Orientation; + } + + public struct toggledoor // CLOSEDOOR (12) + // OPENDOOR (11) + { + public uint GOGuid; // datalong + public uint ResetDelay; // datalong2 + } + + // ACTIVATEOBJECT (13) + + public struct removeaura // REMOVEAURA (14) + { + public uint SpellID; // datalong + public eScriptFlags Flags; // datalong2 + } + + public struct castspell // CASTSPELL (15) + { + public uint SpellID; // datalong + public eScriptFlags Flags; // datalong2 + public int CreatureEntry; // dataint + + public float SearchRadius; + } + + public struct playsound // PLAYSOUND (16) + { + public uint SoundID; // datalong + public eScriptFlags Flags; // datalong2 + } + + public struct createitem // CREATEITEM (17) + { + public uint ItemEntry; // datalong + public uint Amount; // datalong2 + } + + public struct despawnself // DESPAWNSELF (18) + { + public uint DespawnDelay; // datalong + } + + public struct loadpath // LOADPATH (20) + { + public uint PathID; // datalong + public uint IsRepeatable; // datalong2 + } + + public struct callscript // CALLSCRIPTTOUNIT (21) + { + public uint CreatureEntry; // datalong + public uint ScriptID; // datalong2 + public uint ScriptType; // dataint + } + + public struct kill // KILL (22) + { + public uint Unused1; // datalong + public uint Unused2; // datalong2 + public int RemoveCorpse; // dataint + } + + public struct orientation // ORIENTATION (30) + { + public eScriptFlags Flags; // datalong + public uint Unused1; // datalong2 + public int Unused2; // dataint + + public float Unused3; + public float Unused4; + public float Unused5; + public float _Orientation; + } + + public struct equip // EQUIP (31) + { + public uint EquipmentID; // datalong + } + + public struct model // MODEL (32) + { + public uint ModelID; // datalong + } + + // CLOSEGOSSIP (33) + + public struct playmovie // PLAYMOVIE (34) + { + public uint MovieID; // datalong + } + + public struct movement // SCRIPT_COMMAND_MOVEMENT (35) + { + public uint MovementType; // datalong + public uint MovementDistance; // datalong2 + public int Path; // dataint + } + + public struct playanimkit // SCRIPT_COMMAND_PLAY_ANIMKIT (36) + { + public uint AnimKitID; // datalong + } + #endregion + } + + public class CellObjectGuids + { + public SortedSet creatures = new SortedSet(); + public SortedSet gameobjects = new SortedSet(); + } + + public class GameTele + { + public float posX; + public float posY; + public float posZ; + public float orientation; + public uint mapId; + public string name; + public string nameLow; + } + + public class PetLevelInfo + { + public PetLevelInfo() + { + health = 0; + mana = 0; + } + + public uint[] stats = new uint[(int)Stats.Max]; + public uint health; + public uint mana; + public uint armor; + } + + public class SpellClickInfo + { + public uint spellId; + public byte castFlags; + public SpellClickUserTypes userType; + + // helpers + public bool IsFitToRequirements(Unit clicker, Unit clickee) + { + Player playerClicker = clicker.ToPlayer(); + if (playerClicker == null) + return true; + + Unit summoner = null; + // Check summoners for party + if (clickee.IsSummon()) + summoner = clickee.ToTempSummon().GetSummoner(); + if (summoner == null) + summoner = clickee; + + // This only applies to players + switch (userType) + { + case SpellClickUserTypes.Friend: + if (!playerClicker.IsFriendlyTo(summoner)) + return false; + break; + case SpellClickUserTypes.Raid: + if (!playerClicker.IsInRaidWith(summoner)) + return false; + break; + case SpellClickUserTypes.Party: + if (!playerClicker.IsInPartyWith(summoner)) + return false; + break; + default: + break; + } + + return true; + } + } + + public class GraveYardData + { + public uint safeLocId; + public uint team; + } + + public class QuestPOI + { + public QuestPOI(int _BlobIndex, int _ObjectiveIndex, int _QuestObjectiveID, int _QuestObjectID, int _MapID, int _WorldMapAreaID, int _Foor, int _Priority, int _Flags, + int _WorldEffectID, int _PlayerConditionID, int _UnkWoD1) + { + BlobIndex = _BlobIndex; + ObjectiveIndex = _ObjectiveIndex; + QuestObjectiveID = _QuestObjectiveID; + QuestObjectID = _QuestObjectID; + MapID = _MapID; + WorldMapAreaID = _WorldMapAreaID; + Floor = _Foor; + Priority = _Priority; + Flags = _Flags; + WorldEffectID = _WorldEffectID; + PlayerConditionID = _PlayerConditionID; + UnkWoD1 = _UnkWoD1; + } + + public int BlobIndex; + public int ObjectiveIndex; + public int QuestObjectiveID; + public int QuestObjectID; + public int MapID; + public int WorldMapAreaID; + public int Floor; + public int Priority; + public int Flags; + public int WorldEffectID; + public int PlayerConditionID; + public int UnkWoD1; + public List points = new List(); + } + + public class QuestPOIPoint + { + public QuestPOIPoint(int _x, int _y) + { + X = _x; + Y = _y; + } + + public int X; + public int Y; + } + + public class QuestGreeting + { + public QuestGreeting() + { + greeting = ""; + } + public QuestGreeting(ushort _greetEmoteType, uint _greetEmoteDelay, string _greeting) + { + greetEmoteType = _greetEmoteType; + greetEmoteDelay = _greetEmoteDelay; + greeting = _greeting; + } + + public ushort greetEmoteType; + public uint greetEmoteDelay; + public string greeting; + } + + public class AreaTriggerStruct + { + public uint target_mapId; + public float target_X; + public float target_Y; + public float target_Z; + public float target_Orientation; + public uint PortLocId; + } + + public class DungeonEncounter + { + public DungeonEncounter(DungeonEncounterRecord _dbcEntry, EncounterCreditType _creditType, uint _creditEntry, uint _lastEncounterDungeon) + { + dbcEntry = _dbcEntry; + creditType = _creditType; + creditEntry = _creditEntry; + lastEncounterDungeon = _lastEncounterDungeon; + } + + public DungeonEncounterRecord dbcEntry; + public EncounterCreditType creditType; + public uint creditEntry; + public uint lastEncounterDungeon; + } + + public class MailLevelReward + { + public MailLevelReward(uint _raceMask = 0, uint _mailTemplateId = 0, uint _senderEntry = 0) + { + raceMask = _raceMask; + mailTemplateId = _mailTemplateId; + senderEntry = _senderEntry; + } + + public uint raceMask; + public uint mailTemplateId; + public uint senderEntry; + } + + public class PageText + { + public string Text; + public uint NextPageID; + public int PlayerConditionID; + public byte Flags; + } + + public struct ExtendedPlayerName + { + public ExtendedPlayerName(string name, string realmName) + { + Name = name; + Realm = realmName; + } + + public string Name; + public string Realm; + } + + public class LanguageDesc + { + public LanguageDesc(Language langid, uint spellid, object skillid) + { + lang_id = langid; + spell_id = spellid; + skill_id = Convert.ToUInt32(skillid); + } + + public Language lang_id; + public uint spell_id; + public uint skill_id; + } + + class ItemSpecStats + { + public ItemSpecStats(ItemRecord item, ItemSparseRecord sparse) + { + if (item.Class == ItemClass.Weapon) + { + ItemType = 5; + switch ((ItemSubClassWeapon)item.SubClass) + { + case ItemSubClassWeapon.Axe: + AddStat(ItemSpecStat.OneHandedAxe); + break; + case ItemSubClassWeapon.Axe2: + AddStat(ItemSpecStat.TwoHandedAxe); + break; + case ItemSubClassWeapon.Bow: + AddStat(ItemSpecStat.Bow); + break; + case ItemSubClassWeapon.Gun: + AddStat(ItemSpecStat.Gun); + break; + case ItemSubClassWeapon.Mace: + AddStat(ItemSpecStat.OneHandedMace); + break; + case ItemSubClassWeapon.Mace2: + AddStat(ItemSpecStat.TwoHandedMace); + break; + case ItemSubClassWeapon.Polearm: + AddStat(ItemSpecStat.Polearm); + break; + case ItemSubClassWeapon.Sword: + AddStat(ItemSpecStat.OneHandedSword); + break; + case ItemSubClassWeapon.Sword2: + AddStat(ItemSpecStat.TwoHandedSword); + break; + case ItemSubClassWeapon.Warglaives: + AddStat(ItemSpecStat.Warglaives); + break; + case ItemSubClassWeapon.Staff: + AddStat(ItemSpecStat.Staff); + break; + case ItemSubClassWeapon.Fist: + AddStat(ItemSpecStat.FistWeapon); + break; + case ItemSubClassWeapon.Dagger: + AddStat(ItemSpecStat.Dagger); + break; + case ItemSubClassWeapon.Thrown: + AddStat(ItemSpecStat.Thrown); + break; + case ItemSubClassWeapon.Crossbow: + AddStat(ItemSpecStat.Crossbow); + break; + case ItemSubClassWeapon.Wand: + AddStat(ItemSpecStat.Wand); + break; + default: + break; + } + } + else if (item.Class == ItemClass.Armor) + { + switch ((ItemSubClassArmor)item.SubClass) + { + case ItemSubClassArmor.Cloth: + if (sparse.inventoryType != InventoryType.Cloak) + { + ItemType = 1; + break; + } + + ItemType = 0; + AddStat(ItemSpecStat.Cloak); + break; + case ItemSubClassArmor.Leather: + ItemType = 2; + break; + case ItemSubClassArmor.Mail: + ItemType = 3; + break; + case ItemSubClassArmor.Plate: + ItemType = 4; + break; + default: + if (item.SubClass == (int)ItemSubClassArmor.Shield) + { + ItemType = 6; + AddStat(ItemSpecStat.Shield); + } + else if (item.SubClass > (int)ItemSubClassArmor.Shield && item.SubClass <= (int)ItemSubClassArmor.Relic) + { + ItemType = 6; + AddStat(ItemSpecStat.Relic); + } + else + ItemType = 0; + break; + } + } + else if (item.Class == ItemClass.Gem) + { + ItemType = 7; + GemPropertiesRecord gem = CliDB.GemPropertiesStorage.LookupByKey(sparse.GemProperties); + if (gem != null) + { + if (gem.Type.HasAnyFlag(SocketColor.RelicIron)) + AddStat(ItemSpecStat.RelicIron); + if (gem.Type.HasAnyFlag(SocketColor.RelicBlood)) + AddStat(ItemSpecStat.RelicBlood); + if (gem.Type.HasAnyFlag(SocketColor.RelicShadow)) + AddStat(ItemSpecStat.RelicShadow); + if (gem.Type.HasAnyFlag(SocketColor.RelicFel)) + AddStat(ItemSpecStat.RelicFel); + if (gem.Type.HasAnyFlag(SocketColor.RelicArcane)) + AddStat(ItemSpecStat.RelicArcane); + if (gem.Type.HasAnyFlag(SocketColor.RelicFrost)) + AddStat(ItemSpecStat.RelicFrost); + if (gem.Type.HasAnyFlag(SocketColor.RelicFire)) + AddStat(ItemSpecStat.RelicFire); + if (gem.Type.HasAnyFlag(SocketColor.RelicWater)) + AddStat(ItemSpecStat.RelicWater); + if (gem.Type.HasAnyFlag(SocketColor.RelicLife)) + AddStat(ItemSpecStat.RelicLife); + if (gem.Type.HasAnyFlag(SocketColor.RelicWind)) + AddStat(ItemSpecStat.RelicWind); + if (gem.Type.HasAnyFlag(SocketColor.RelicHoly)) + AddStat(ItemSpecStat.RelicHoly); + } + } + else + ItemType = 0; + + for (uint i = 0; i < ItemConst.MaxStats; ++i) + if (sparse.ItemStatType[i] != -1) + AddModStat(sparse.ItemStatType[i]); + } + + void AddStat(ItemSpecStat statType) + { + if (ItemSpecStatCount >= ItemConst.MaxStats) + return; + + for (uint i = 0; i < ItemConst.MaxStats; ++i) + if (ItemSpecStatTypes[i] == statType) + return; + + ItemSpecStatTypes[ItemSpecStatCount++] = statType; + } + + void AddModStat(int itemStatType) + { + switch ((ItemModType)itemStatType) + { + case ItemModType.Agility: + AddStat(ItemSpecStat.Agility); + break; + case ItemModType.Strength: + AddStat(ItemSpecStat.Strength); + break; + case ItemModType.Intellect: + AddStat(ItemSpecStat.Intellect); + break; + case ItemModType.DodgeRating: + AddStat(ItemSpecStat.Dodge); + break; + case ItemModType.ParryRating: + AddStat(ItemSpecStat.Parry); + break; + case ItemModType.CritMeleeRating: + case ItemModType.CritRangedRating: + case ItemModType.CritSpellRating: + case ItemModType.CritRating: + AddStat(ItemSpecStat.Crit); + break; + case ItemModType.HasteRating: + AddStat(ItemSpecStat.Haste); + break; + case ItemModType.HitRating: + AddStat(ItemSpecStat.Hit); + break; + case ItemModType.ExtraArmor: + AddStat(ItemSpecStat.BonusArmor); + break; + case ItemModType.AgiStrInt: + AddStat(ItemSpecStat.Agility); + AddStat(ItemSpecStat.Strength); + AddStat(ItemSpecStat.Intellect); + break; + case ItemModType.AgiStr: + AddStat(ItemSpecStat.Agility); + AddStat(ItemSpecStat.Strength); + break; + case ItemModType.AgiInt: + AddStat(ItemSpecStat.Agility); + AddStat(ItemSpecStat.Intellect); + break; + case ItemModType.StrInt: + AddStat(ItemSpecStat.Strength); + AddStat(ItemSpecStat.Intellect); + break; + } + } + + public uint ItemType; + public ItemSpecStat[] ItemSpecStatTypes = new ItemSpecStat[ItemConst.MaxStats]; + public uint ItemSpecStatCount; + } + + public class SkillTiersEntry + { + public uint Id; + public uint[] Value = new uint[SkillConst.MaxSkillStep]; + } + + public class PhaseInfoStruct + { + public uint Id; + public List Conditions = new List(); + } + + public class SceneTemplate + { + public uint SceneId; + public SceneFlags PlaybackFlags; + public uint ScenePackageId; + public uint ScriptId; + } + + public class DefaultCreatureBaseStats : CreatureBaseStats + { + public DefaultCreatureBaseStats() + { + BaseArmor = 1; + for (byte j = 0; j < 4; ++j) + { + BaseHealth[j] = 1; + BaseDamage[j] = 0.0f; + } + BaseMana = 0; + AttackPower = 0; + RangedAttackPower = 0; + } + } + + public class GossipMenuItemsLocale + { + public StringArray OptionText = new StringArray((int)LocaleConstant.Total); + public StringArray BoxText = new StringArray((int)LocaleConstant.Total); + } +} diff --git a/Game/Groups/Group.cs b/Game/Groups/Group.cs new file mode 100644 index 000000000..fb7916463 --- /dev/null +++ b/Game/Groups/Group.cs @@ -0,0 +1,2665 @@ +/* + * Copyright (C) 2012-2017 CypherCore + * + * 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, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +using Framework.Constants; +using Framework.Database; +using Game.BattleFields; +using Game.BattleGrounds; +using Game.DataStorage; +using Game.Entities; +using Game.Loots; +using Game.Maps; +using Game.Network; +using Game.Network.Packets; +using System; +using System.Collections.Generic; +using System.Diagnostics.Contracts; +using System.Linq; + +namespace Game.Groups +{ + public class Group + { + public Group() + { + m_leaderName = ""; + m_groupFlags = GroupFlags.None; + m_dungeonDifficulty = Difficulty.Normal; + m_raidDifficulty = Difficulty.NormalRaid; + m_legacyRaidDifficulty = Difficulty.Raid10N; + m_lootMethod = LootMethod.FreeForAll; + m_lootThreshold = ItemQuality.Uncommon; + + for (byte i = 0; i < (int)Difficulty.Max; ++i) + m_boundInstances[i] = new Dictionary(); + } + + public bool Create(Player leader) + { + ObjectGuid leaderGuid = leader.GetGUID(); + + m_guid = ObjectGuid.Create(HighGuid.Party, Global.GroupMgr.GenerateGroupId()); + m_leaderGuid = leaderGuid; + m_leaderName = leader.GetName(); + + if (isBGGroup() || isBFGroup()) + { + m_groupFlags = GroupFlags.MaskBgRaid; + m_groupCategory = GroupCategory.Instance; + } + + if (m_groupFlags.HasAnyFlag(GroupFlags.Raid)) + _initRaidSubGroupsCounter(); + + if (!isLFGGroup()) + m_lootMethod = LootMethod.GroupLoot; + + m_lootThreshold = ItemQuality.Uncommon; + m_looterGuid = leaderGuid; + + m_dungeonDifficulty = Difficulty.Normal; + m_raidDifficulty = Difficulty.NormalRaid; + m_legacyRaidDifficulty = Difficulty.Raid10N; + + if (!isBGGroup() && !isBFGroup()) + { + m_dungeonDifficulty = leader.GetDungeonDifficultyID(); + m_raidDifficulty = leader.GetRaidDifficultyID(); + m_legacyRaidDifficulty = leader.GetLegacyRaidDifficultyID(); + + m_dbStoreId = Global.GroupMgr.GenerateNewGroupDbStoreId(); + + Global.GroupMgr.RegisterGroupDbStoreId(m_dbStoreId, this); + + // Store group in database + PreparedStatement stmt = DB.Characters.GetPreparedStatement(CharStatements.INS_GROUP); + + byte index = 0; + + stmt.AddValue(index++, m_dbStoreId); + stmt.AddValue(index++, m_leaderGuid.GetCounter()); + stmt.AddValue(index++, m_lootMethod); + stmt.AddValue(index++, m_looterGuid.GetCounter()); + stmt.AddValue(index++, m_lootThreshold); + stmt.AddValue(index++, m_targetIcons[0]); + stmt.AddValue(index++, m_targetIcons[1]); + stmt.AddValue(index++, m_targetIcons[2]); + stmt.AddValue(index++, m_targetIcons[3]); + stmt.AddValue(index++, m_targetIcons[4]); + stmt.AddValue(index++, m_targetIcons[5]); + stmt.AddValue(index++, m_targetIcons[6]); + stmt.AddValue(index++, m_targetIcons[7]); + stmt.AddValue(index++, m_groupFlags); + stmt.AddValue(index++, m_dungeonDifficulty); + stmt.AddValue(index++, m_raidDifficulty); + stmt.AddValue(index++, m_legacyRaidDifficulty); + stmt.AddValue(index++, m_masterLooterGuid.GetCounter()); + + DB.Characters.Execute(stmt); + + ConvertLeaderInstancesToGroup(leader, this, false); + + Contract.Assert(AddMember(leader)); // If the leader can't be added to a new group because it appears full, something is clearly wrong. + } + else if (!AddMember(leader)) + return false; + + return true; + } + + public void LoadGroupFromDB(SQLFields field) + { + m_dbStoreId = field.Read(17); + m_guid = ObjectGuid.Create(HighGuid.Party, Global.GroupMgr.GenerateGroupId()); + m_leaderGuid = ObjectGuid.Create(HighGuid.Player, field.Read(0)); + + // group leader not exist + if (!ObjectManager.GetPlayerNameByGUID(m_leaderGuid, out m_leaderName)) + return; + + m_lootMethod = (LootMethod)field.Read(1); + m_looterGuid = ObjectGuid.Create(HighGuid.Player, field.Read(2)); + m_lootThreshold = (ItemQuality)field.Read(3); + + for (byte i = 0; i < MapConst.TargetIconsCount; ++i) + m_targetIcons[i].SetRawValue(field.Read(4 + i).ToByteArray()); + + m_groupFlags = (GroupFlags)field.Read(12); + if (m_groupFlags.HasAnyFlag(GroupFlags.Raid)) + _initRaidSubGroupsCounter(); + + m_dungeonDifficulty = Player.CheckLoadedDungeonDifficultyID((Difficulty)field.Read(13)); + m_raidDifficulty = Player.CheckLoadedRaidDifficultyID((Difficulty)field.Read(14)); + m_legacyRaidDifficulty = Player.CheckLoadedLegacyRaidDifficultyID((Difficulty)field.Read(15)); + + m_masterLooterGuid = ObjectGuid.Create(HighGuid.Player, field.Read(16)); + + if (m_groupFlags.HasAnyFlag(GroupFlags.Lfg)) + Global.LFGMgr._LoadFromDB(field, GetGUID()); + } + + public void LoadMemberFromDB(ulong guidLow, byte memberFlags, byte subgroup, LfgRoles roles) + { + MemberSlot member = new MemberSlot(); + member.guid = ObjectGuid.Create(HighGuid.Player, guidLow); + + // skip non-existed member + if (!ObjectManager.GetPlayerNameAndClassByGUID(member.guid, out member.name, out member._class)) + { + PreparedStatement stmt = DB.Characters.GetPreparedStatement(CharStatements.DEL_GROUP_MEMBER); + stmt.AddValue(0, guidLow); + DB.Characters.Execute(stmt); + return; + } + + member.group = subgroup; + member.flags = (GroupMemberFlags)memberFlags; + member.roles = roles; + member.readyChecked = false; + + m_memberSlots.Add(member); + + SubGroupCounterIncrease(subgroup); + + Global.LFGMgr.SetupGroupMember(member.guid, GetGUID()); + } + + public void ConvertToLFG() + { + m_groupFlags = (m_groupFlags | GroupFlags.Lfg | GroupFlags.LfgRestricted); + m_groupCategory = GroupCategory.Instance; + m_lootMethod = LootMethod.GroupLoot; + if (!isBGGroup() && !isBFGroup()) + { + PreparedStatement stmt = DB.Characters.GetPreparedStatement(CharStatements.UPD_GROUP_TYPE); + + stmt.AddValue(0, (byte)m_groupFlags); + stmt.AddValue(1, m_dbStoreId); + + DB.Characters.Execute(stmt); + } + + SendUpdate(); + } + + public void ConvertToRaid() + { + m_groupFlags = (m_groupFlags | GroupFlags.Raid); + + _initRaidSubGroupsCounter(); + + if (!isBGGroup() && !isBFGroup()) + { + PreparedStatement stmt = DB.Characters.GetPreparedStatement(CharStatements.UPD_GROUP_TYPE); + + stmt.AddValue(0, (byte)m_groupFlags); + stmt.AddValue(1, m_dbStoreId); + + DB.Characters.Execute(stmt); + } + + SendUpdate(); + + // update quest related GO states (quest activity dependent from raid membership) + foreach (var member in m_memberSlots) + { + Player player = Global.ObjAccessor.FindPlayer(member.guid); + if (player != null) + player.UpdateForQuestWorldObjects(); + } + } + + public void ConvertToGroup() + { + if (m_memberSlots.Count > 5) + return; // What message error should we send? + + m_groupFlags = GroupFlags.None; + + m_subGroupsCounts = null; + + if (!isBGGroup() && !isBFGroup()) + { + PreparedStatement stmt = DB.Characters.GetPreparedStatement(CharStatements.UPD_GROUP_TYPE); + + stmt.AddValue(0, (byte)m_groupFlags); + stmt.AddValue(1, m_dbStoreId); + + DB.Characters.Execute(stmt); + } + + SendUpdate(); + + // update quest related GO states (quest activity dependent from raid membership) + foreach (var member in m_memberSlots) + { + Player player = Global.ObjAccessor.FindPlayer(member.guid); + if (player != null) + player.UpdateForQuestWorldObjects(); + } + } + + public bool AddInvite(Player player) + { + if (player == null || player.GetGroupInvite()) + return false; + Group group = player.GetGroup(); + if (group && (group.isBGGroup() || group.isBFGroup())) + group = player.GetOriginalGroup(); + if (group) + return false; + + RemoveInvite(player); + + m_invitees.Add(player); + + player.SetGroupInvite(this); + + Global.ScriptMgr.OnGroupInviteMember(this, player.GetGUID()); + + return true; + } + + public bool AddLeaderInvite(Player player) + { + if (!AddInvite(player)) + return false; + + m_leaderGuid = player.GetGUID(); + m_leaderName = player.GetName(); + return true; + } + + public void RemoveInvite(Player player) + { + if (player != null) + { + m_invitees.Remove(player); + player.SetGroupInvite(null); + } + } + + public void RemoveAllInvites() + { + foreach (var pl in m_invitees) + if (pl != null) + pl.SetGroupInvite(null); + + m_invitees.Clear(); + } + + public Player GetInvited(ObjectGuid guid) + { + foreach (var pl in m_invitees) + { + if (pl != null && pl.GetGUID() == guid) + return pl; + } + return null; + } + + public Player GetInvited(string name) + { + foreach (var pl in m_invitees) + { + if (pl != null && pl.GetName() == name) + return pl; + } + return null; + } + + public bool AddMember(Player player) + { + // Get first not-full group + byte subGroup = 0; + if (m_subGroupsCounts != null) + { + bool groupFound = false; + for (; subGroup < MapConst.MaxRaidSubGroups; ++subGroup) + { + if (m_subGroupsCounts[subGroup] < MapConst.MaxGroupSize) + { + groupFound = true; + break; + } + } + // We are raid group and no one slot is free + if (!groupFound) + return false; + } + + MemberSlot member = new MemberSlot(); + member.guid = player.GetGUID(); + member.name = player.GetName(); + member._class = (byte)player.GetClass(); + member.group = subGroup; + member.flags = 0; + member.roles = 0; + member.readyChecked = false; + m_memberSlots.Add(member); + + SubGroupCounterIncrease(subGroup); + + player.SetGroupInvite(null); + if (player.GetGroup() != null) + { + if (isBGGroup() || isBFGroup()) // if player is in group and he is being added to BG raid group, then call SetBattlegroundRaid() + player.SetBattlegroundOrBattlefieldRaid(this, subGroup); + else //if player is in bg raid and we are adding him to normal group, then call SetOriginalGroup() + player.SetOriginalGroup(this, subGroup); + } + else //if player is not in group, then call set group + player.SetGroup(this, subGroup); + + player.SetPartyType(m_groupCategory, GroupType.Normal); + player.ResetGroupUpdateSequenceIfNeeded(this); + + // if the same group invites the player back, cancel the homebind timer + player.m_InstanceValid = player.CheckInstanceValidity(false); + + if (!isRaidGroup()) // reset targetIcons for non-raid-groups + { + for (byte i = 0; i < MapConst.TargetIconsCount; ++i) + m_targetIcons[i].Clear(); + } + + // insert into the table if we're not a Battlegroundgroup + if (!isBGGroup() && !isBFGroup()) + { + PreparedStatement stmt = DB.Characters.GetPreparedStatement(CharStatements.INS_GROUP_MEMBER); + + stmt.AddValue(0, m_dbStoreId); + stmt.AddValue(1, member.guid.GetCounter()); + stmt.AddValue(2, member.flags); + stmt.AddValue(3, member.group); + stmt.AddValue(4, member.roles); + + DB.Characters.Execute(stmt); + + } + + SendUpdate(); + Global.ScriptMgr.OnGroupAddMember(this, player.GetGUID()); + + if (!IsLeader(player.GetGUID()) && !isBGGroup() && !isBFGroup()) + { + // reset the new member's instances, unless he is currently in one of them + // including raid/heroic instances that they are not permanently bound to! + player.ResetInstances(InstanceResetMethod.GroupJoin, false, false); + player.ResetInstances(InstanceResetMethod.GroupJoin, true, false); + player.ResetInstances(InstanceResetMethod.GroupJoin, true, true); + + if (player.GetDungeonDifficultyID() != GetDungeonDifficultyID()) + { + player.SetDungeonDifficultyID(GetDungeonDifficultyID()); + player.SendDungeonDifficulty(); + } + if (player.GetRaidDifficultyID() != GetRaidDifficultyID()) + { + player.SetRaidDifficultyID(GetRaidDifficultyID()); + player.SendRaidDifficulty(false); + } + if (player.GetLegacyRaidDifficultyID() != GetLegacyRaidDifficultyID()) + { + player.SetLegacyRaidDifficultyID(GetLegacyRaidDifficultyID()); + player.SendRaidDifficulty(true); + } + } + player.SetGroupUpdateFlag(GroupUpdateFlags.Full); + Pet pet = player.GetPet(); + if (pet) + pet.SetGroupUpdateFlag(GroupUpdatePetFlags.Full); + + UpdatePlayerOutOfRange(player); + + // quest related GO state dependent from raid membership + if (isRaidGroup()) + player.UpdateForQuestWorldObjects(); + + { + // Broadcast new player group member fields to rest of the group + player.SetFieldNotifyFlag(UpdateFieldFlags.PartyMember); + + UpdateData groupData = new UpdateData(player.GetMapId()); + UpdateObject groupDataPacket; + + // Broadcast group members' fields to player + for (GroupReference refe = GetFirstMember(); refe != null; refe = refe.next()) + { + if (refe.GetSource() == player) + continue; + + Player memberPlayer = refe.GetSource(); + if (memberPlayer != null) + { + if (player.HaveAtClient(memberPlayer)) + { + memberPlayer.SetFieldNotifyFlag(UpdateFieldFlags.PartyMember); + memberPlayer.BuildValuesUpdateBlockForPlayer(groupData, player); + memberPlayer.RemoveFieldNotifyFlag(UpdateFieldFlags.PartyMember); + } + + if (memberPlayer.HaveAtClient(player)) + { + UpdateData newData = new UpdateData(player.GetMapId()); + UpdateObject newDataPacket; + player.BuildValuesUpdateBlockForPlayer(newData, memberPlayer); + if (newData.HasData()) + { + newData.BuildPacket(out newDataPacket); + memberPlayer.SendPacket(newDataPacket); + } + } + } + } + + if (groupData.HasData()) + { + groupData.BuildPacket(out groupDataPacket); + player.SendPacket(groupDataPacket); + } + + player.RemoveFieldNotifyFlag(UpdateFieldFlags.PartyMember); + } + + if (m_maxEnchantingLevel < player.GetSkillValue(SkillType.Enchanting)) + m_maxEnchantingLevel = player.GetSkillValue(SkillType.Enchanting); + + return true; + } + + public bool RemoveMember(ObjectGuid guid, RemoveMethod method = RemoveMethod.Default, ObjectGuid kicker = default(ObjectGuid), string reason = null) + { + BroadcastGroupUpdate(); + + Global.ScriptMgr.OnGroupRemoveMember(this, guid, method, kicker, reason); + + Player player = Global.ObjAccessor.FindConnectedPlayer(guid); + if (player) + { + for (GroupReference refe = GetFirstMember(); refe != null; refe = refe.next()) + { + Player groupMember = refe.GetSource(); + if (groupMember) + { + if (groupMember.GetGUID() == guid) + continue; + + groupMember.RemoveAllGroupBuffsFromCaster(guid); + player.RemoveAllGroupBuffsFromCaster(groupMember.GetGUID()); + } + } + } + + // LFG group vote kick handled in scripts + if (isLFGGroup() && method == RemoveMethod.Kick) + return m_memberSlots.Count != 0; + + // remove member and change leader (if need) only if strong more 2 members _before_ member remove (BG/BF allow 1 member group) + if (GetMembersCount() > ((isBGGroup() || isLFGGroup() || isBFGroup()) ? 1 : 2)) + { + if (player) + { + // Battlegroundgroup handling + if (isBGGroup() || isBFGroup()) + player.RemoveFromBattlegroundOrBattlefieldRaid(); + else + // Regular group + { + if (player.GetOriginalGroup() == this) + player.SetOriginalGroup(null); + else + player.SetGroup(null); + + // quest related GO state dependent from raid membership + player.UpdateForQuestWorldObjects(); + + + } + player.SetPartyType(m_groupCategory, GroupType.None); + + if (method == RemoveMethod.Kick || method == RemoveMethod.KickLFG) + player.SendPacket(new GroupUninvite()); + + _homebindIfInstance(player); + } + + // Remove player from group in DB + if (!isBGGroup() && !isBFGroup()) + { + PreparedStatement stmt = DB.Characters.GetPreparedStatement(CharStatements.DEL_GROUP_MEMBER); + stmt.AddValue(0, guid.GetCounter()); + DB.Characters.Execute(stmt); + DelinkMember(guid); + } + + // Reevaluate group enchanter if the leaving player had enchanting skill or the player is offline + if (!player || player.GetSkillValue(SkillType.Enchanting) != 0) + ResetMaxEnchantingLevel(); + + // Remove player from loot rolls + foreach (var roll in RollId) + { + if (!roll.playerVote.ContainsKey(guid)) + continue; + + var vote = roll.playerVote[guid]; + if (vote == RollType.Greed || vote == RollType.Disenchant) + --roll.totalGreed; + else if (vote == RollType.Need) + --roll.totalNeed; + else if (vote == RollType.Pass) + --roll.totalPass; + + if (vote != RollType.NotValid) + --roll.totalPlayersRolling; + + roll.playerVote.Remove(guid); + + if (roll.totalPass + roll.totalNeed + roll.totalGreed >= roll.totalPlayersRolling) + CountTheRoll(roll); + } + + // Update subgroups + var slot = _getMemberSlot(guid); + if (slot != null) + { + SubGroupCounterDecrease(slot.group); + m_memberSlots.Remove(slot); + } + + // Pick new leader if necessary + if (m_leaderGuid == guid) + { + foreach (var member in m_memberSlots) + { + if (Global.ObjAccessor.FindPlayer(member.guid) != null) + { + ChangeLeader(member.guid); + break; + } + } + } + + SendUpdate(); + + if (isLFGGroup() && GetMembersCount() == 1) + { + Player leader = Global.ObjAccessor.FindPlayer(GetLeaderGUID()); + uint mapId = Global.LFGMgr.GetDungeonMapId(GetGUID()); + if (mapId == 0 || leader == null || (leader.IsAlive() && leader.GetMapId() != mapId)) + { + Disband(); + return false; + } + } + + if (m_memberMgr.getSize() < ((isLFGGroup() || isBGGroup()) ? 1 : 2)) + Disband(); + else if (player) + { + // send update to removed player too so party frames are destroyed clientside + SendUpdateDestroyGroupToPlayer(player); + } + + return true; + } + // If group size before player removal <= 2 then disband it + else + { + Disband(); + return false; + } + } + + public void ChangeLeader(ObjectGuid newLeaderGuid, sbyte partyIndex = 0) + { + var slot = _getMemberSlot(newLeaderGuid); + if (slot == null) + return; + + Player newLeader = Global.ObjAccessor.FindPlayer(slot.guid); + + // Don't allow switching leader to offline players + if (newLeader == null) + return; + + Global.ScriptMgr.OnGroupChangeLeader(this, newLeaderGuid, m_leaderGuid); + + if (!isBGGroup() && !isBFGroup()) + { + PreparedStatement stmt; + SQLTransaction trans = new SQLTransaction(); + + // Remove the groups permanent instance bindings + for (byte i = 0; i < (int)Difficulty.Max; ++i) + { + foreach (var pair in m_boundInstances[i]) + { + // Do not unbind saves of instances that already had map created (a newLeader entered) + // forcing a new instance with another leader requires group disbanding (confirmed on retail) + if (pair.Value.perm && Global.MapMgr.FindMap(pair.Key, pair.Value.save.GetInstanceId()) == null) + { + stmt = DB.Characters.GetPreparedStatement(CharStatements.DEL_GROUP_INSTANCE_PERM_BINDING); + stmt.AddValue(0, m_dbStoreId); + stmt.AddValue(1, pair.Value.save.GetInstanceId()); + trans.Append(stmt); + + pair.Value.save.RemoveGroup(this); + m_boundInstances[i].Remove(pair.Key); + } + } + } + + // Copy the permanent binds from the new leader to the group + ConvertLeaderInstancesToGroup(newLeader, this, true); + + // Update the group leader + stmt = DB.Characters.GetPreparedStatement(CharStatements.UPD_GROUP_LEADER); + + stmt.AddValue(0, newLeader.GetGUID().GetCounter()); + stmt.AddValue(1, m_dbStoreId); + + trans.Append(stmt); + + DB.Characters.CommitTransaction(trans); + } + + m_leaderGuid = newLeader.GetGUID(); + m_leaderName = newLeader.GetName(); + ToggleGroupMemberFlag(slot, GroupMemberFlags.Assistant, false); + + GroupNewLeader groupNewLeader = new GroupNewLeader(); + groupNewLeader.Name = m_leaderName; + groupNewLeader.PartyIndex = partyIndex; + BroadcastPacket(groupNewLeader, true); + } + + public static void ConvertLeaderInstancesToGroup(Player player, Group group, bool switchLeader) + { + // copy all binds to the group, when changing leader it's assumed the character + // will not have any solo binds + for (byte i = 0; i < (int)Difficulty.Max; ++i) + { + foreach (var pair in player.m_boundInstances[i]) + { + if (!switchLeader || group.GetBoundInstance(pair.Value.save.GetDifficultyID(), pair.Key) == null) + if (pair.Value.extendState != 0) // not expired + group.BindToInstance(pair.Value.save, pair.Value.perm, false); + + // permanent binds are not removed + if (switchLeader && !pair.Value.perm) + { + player.UnbindInstance(pair, (Difficulty)i, false); + } + } + } + + // if group leader is in a non-raid dungeon map and nobody is actually bound to this map then the group can "take over" the instance + // (example: two-player group disbanded by disconnect where the player reconnects within 60 seconds and the group is reformed) + Map playerMap = player.GetMap(); + if (playerMap) + { + if (!switchLeader && playerMap.IsNonRaidDungeon()) + { + InstanceSave save = Global.InstanceSaveMgr.GetInstanceSave(playerMap.GetInstanceId()); + if (save != null) + { + if (save.GetGroupCount() == 0 && save.GetPlayerCount() == 0) + { + Log.outDebug(LogFilter.Maps, "Group.ConvertLeaderInstancesToGroup: Group for player {0} is taking over unbound instance map {1} with Id {2}", player.GetName(), playerMap.GetId(), playerMap.GetInstanceId()); + // if nobody is saved to this, then the save wasn't permanent + group.BindToInstance(save, false, false); + } + } + } + } + } + + public void Disband(bool hideDestroy = false) + { + Global.ScriptMgr.OnGroupDisband(this); + + Player player; + foreach (var member in m_memberSlots) + { + player = Global.ObjAccessor.FindPlayer(member.guid); + if (player == null) + continue; + + //we cannot call _removeMember because it would invalidate member iterator + //if we are removing player from Battlegroundraid + if (isBGGroup() || isBFGroup()) + player.RemoveFromBattlegroundOrBattlefieldRaid(); + else + { + //we can remove player who is in Battlegroundfrom his original group + if (player.GetOriginalGroup() == this) + player.SetOriginalGroup(null); + else + player.SetGroup(null); + } + + player.SetPartyType(m_groupCategory, GroupType.None); + + // quest related GO state dependent from raid membership + if (isRaidGroup()) + player.UpdateForQuestWorldObjects(); + + if (!hideDestroy) + player.SendPacket(new GroupDestroyed()); + + SendUpdateDestroyGroupToPlayer(player); + + _homebindIfInstance(player); + } + RollId.Clear(); + m_memberSlots.Clear(); + + RemoveAllInvites(); + + if (!isBGGroup() && !isBFGroup()) + { + SQLTransaction trans = new SQLTransaction(); + + PreparedStatement stmt = DB.Characters.GetPreparedStatement(CharStatements.DEL_GROUP); + stmt.AddValue(0, m_dbStoreId); + trans.Append(stmt); + + stmt = DB.Characters.GetPreparedStatement(CharStatements.DEL_GROUP_MEMBER_ALL); + stmt.AddValue(0, m_dbStoreId); + trans.Append(stmt); + + DB.Characters.CommitTransaction(trans); + + ResetInstances(InstanceResetMethod.GroupDisband, false, false, null); + ResetInstances(InstanceResetMethod.GroupDisband, true, false, null); + ResetInstances(InstanceResetMethod.GroupDisband, true, true, null); + + stmt = DB.Characters.GetPreparedStatement(CharStatements.DEL_LFG_DATA); + stmt.AddValue(0, m_dbStoreId); + DB.Characters.Execute(stmt); + + Global.GroupMgr.FreeGroupDbStoreId(this); + } + + Global.GroupMgr.RemoveGroup(this); + } + + void SendLootStartRollToPlayer(uint countDown, uint mapId, Player p, bool canNeed, Roll r) + { + StartLootRoll startLootRoll = new StartLootRoll(); + startLootRoll.LootObj = r.getTarget().GetGUID(); + startLootRoll.MapID = (int)mapId; + startLootRoll.RollTime = countDown; + startLootRoll.ValidRolls = r.rollTypeMask; + if (!canNeed) + startLootRoll.ValidRolls &= ~RollMask.Need; + startLootRoll.Method = GetLootMethod(); + r.FillPacket(startLootRoll.Item); + + p.SendPacket(startLootRoll); + } + + void SendLootRoll(ObjectGuid playerGuid, int rollNumber, RollType rollType, Roll roll) + { + LootRollBroadcast lootRoll = new LootRollBroadcast(); + lootRoll.LootObj = roll.getTarget().GetGUID(); + lootRoll.Player = playerGuid; + lootRoll.Roll = rollNumber; + lootRoll.RollType = rollType; + roll.FillPacket(lootRoll.Item); + lootRoll.Write(); + + foreach (var pair in roll.playerVote) + { + Player p = Global.ObjAccessor.FindConnectedPlayer(pair.Key); + if (!p || !p.GetSession()) + continue; + + if (pair.Value != RollType.NotValid) + p.SendPacket(lootRoll, false); + } + } + + void SendLootRollWon(ObjectGuid winnerGuid, int rollNumber, RollType rollType, Roll roll) + { + LootRollWon lootRollWon = new LootRollWon(); + lootRollWon.LootObj = roll.getTarget().GetGUID(); + lootRollWon.Winner = winnerGuid; + lootRollWon.Roll = rollNumber; + lootRollWon.RollType = rollType; + roll.FillPacket(lootRollWon.Item); + lootRollWon.MainSpec = true; // offspec rolls not implemented + lootRollWon.Write(); + + foreach (var pair in roll.playerVote) + { + Player p = Global.ObjAccessor.FindConnectedPlayer(pair.Key); + if (!p || !p.GetSession()) + continue; + + if (pair.Value != RollType.NotValid) + p.SendPacket(lootRollWon, false); + } + } + + void SendLootAllPassed(Roll roll) + { + LootAllPassed lootAllPassed = new LootAllPassed(); + lootAllPassed.LootObj = roll.getTarget().GetGUID(); + roll.FillPacket(lootAllPassed.Item); + lootAllPassed.Write(); + + foreach (var pair in roll.playerVote) + { + Player player = Global.ObjAccessor.FindConnectedPlayer(pair.Key); + if (!player || !player.GetSession()) + continue; + + if (pair.Value != RollType.NotValid) + player.SendPacket(lootAllPassed, false); + } + } + + void SendLootRollsComplete(Roll roll) + { + LootRollsComplete lootRollsComplete = new LootRollsComplete(); + lootRollsComplete.LootObj = roll.getTarget().GetGUID(); + lootRollsComplete.LootListID = (byte)(roll.itemSlot + 1); + lootRollsComplete.Write(); + + foreach (var pair in roll.playerVote) + { + Player player = Global.ObjAccessor.FindConnectedPlayer(pair.Key); + if (!player || !player.GetSession()) + continue; + + if (pair.Value != RollType.NotValid) + player.SendPacket(lootRollsComplete, false); + } + } + + // notify group members which player is the allowed looter for the given creature + public void SendLooter(Creature creature, Player groupLooter) + { + Contract.Assert(creature); + + LootList lootList = new LootList(); + lootList.Owner = creature.GetGUID(); + lootList.LootObj = creature.loot.GetGUID(); + + if (GetLootMethod() == LootMethod.MasterLoot && creature.loot.hasOverThresholdItem()) + lootList.Master.Set(GetMasterLooterGuid()); + + if (groupLooter) + lootList.RoundRobinWinner.Set(groupLooter.GetGUID()); + + BroadcastPacket(lootList, false); + } + + public void GroupLoot(Loot loot, WorldObject lootedObject) + { + byte itemSlot = 0; + foreach (var i in loot.items) + { + if (i.freeforall) + continue; + + ItemTemplate item = Global.ObjectMgr.GetItemTemplate(i.itemid); + if (item == null) + continue; + + //roll for over-threshold item if it's one-player loot + if (item.GetQuality() >= m_lootThreshold) + { + Roll r = new Roll(i); + + for (GroupReference refe = GetFirstMember(); refe != null; refe = refe.next()) + { + Player playerToRoll = refe.GetSource(); + if (!playerToRoll || playerToRoll.GetSession() == null) + continue; + + bool allowedForPlayer = i.AllowedForPlayer(playerToRoll); + if (allowedForPlayer && playerToRoll.IsAtGroupRewardDistance(lootedObject)) + { + r.totalPlayersRolling++; + if (playerToRoll.GetPassOnGroupLoot()) + { + r.playerVote[playerToRoll.GetGUID()] = RollType.Pass; + r.totalPass++; + // can't broadcast the pass now. need to wait until all rolling players are known. + } + else + r.playerVote[playerToRoll.GetGUID()] = RollType.NotEmitedYet; + } + } + + if (r.totalPlayersRolling > 0) + { + r.setLoot(loot); + r.itemSlot = itemSlot; + if (item.DisenchantID != 0 && m_maxEnchantingLevel >= item.RequiredDisenchantSkill) + r.rollTypeMask |= RollMask.Disenchant; + + if (item.GetFlags2().HasAnyFlag(ItemFlags2.CanOnlyRollGreed)) + r.rollTypeMask &= ~RollMask.Need; + + loot.items[itemSlot].is_blocked = true; + + //Broadcast Pass and Send Rollstart + foreach (var pair in r.playerVote) + { + Player p = Global.ObjAccessor.FindPlayer(pair.Key); + if (!p || p.GetSession() == null) + continue; + + if (pair.Value == RollType.Pass) + SendLootRoll(p.GetGUID(), -1, RollType.Pass, r); + else + SendLootStartRollToPlayer(60000, lootedObject.GetMapId(), p, p.CanRollForItemInLFG(item, lootedObject) == InventoryResult.Ok, r); + } + + RollId.Add(r); + Creature creature = lootedObject.ToCreature(); + GameObject go = lootedObject.ToGameObject(); + if (creature) + { + creature.m_groupLootTimer = 60000; + creature.lootingGroupLowGUID = GetGUID(); + } + else if (go) + { + go.m_groupLootTimer = 60000; + go.lootingGroupLowGUID = GetGUID(); + } + } + } + else + i.is_underthreshold = true; + } + + foreach (var i in loot.quest_items) + { + if (!i.follow_loot_rules) + continue; + + ItemTemplate item = Global.ObjectMgr.GetItemTemplate(i.itemid); + Roll r = new Roll(i); + + for (var refe = GetFirstMember(); refe != null; refe = refe.next()) + { + Player playerToRoll = refe.GetSource(); + if (!playerToRoll || playerToRoll.GetSession() == null) + continue; + + bool allowedForPlayer = i.AllowedForPlayer(playerToRoll); + if (allowedForPlayer && playerToRoll.IsAtGroupRewardDistance(lootedObject)) + { + r.totalPlayersRolling++; + r.playerVote[playerToRoll.GetGUID()] = RollType.NotEmitedYet; + } + } + + if (r.totalPlayersRolling > 0) + { + r.setLoot(loot); + r.itemSlot = itemSlot; + + loot.quest_items[itemSlot - loot.items.Count].is_blocked = true; + + //Broadcast Pass and Send Rollstart + foreach (var pair in r.playerVote) + { + Player p = Global.ObjAccessor.FindPlayer(pair.Key); + if (!p || p.GetSession() == null) + continue; + + if (pair.Value == RollType.Pass) + SendLootRoll(p.GetGUID(), -1, RollType.Pass, r); + else + SendLootStartRollToPlayer(60000, lootedObject.GetMapId(), p, p.CanRollForItemInLFG(item, lootedObject) == InventoryResult.Ok, r); + } + + RollId.Add(r); + + Creature creature = lootedObject.ToCreature(); + GameObject go = lootedObject.ToGameObject(); + if (creature) + { + creature.m_groupLootTimer = 60000; + creature.lootingGroupLowGUID = GetGUID(); + } + else if (go) + { + go.m_groupLootTimer = 60000; + go.lootingGroupLowGUID = GetGUID(); + } + } + } + } + + public void MasterLoot(Loot loot, WorldObject pLootedObject) + { + MasterLootCandidateList data = new MasterLootCandidateList(); + data.LootObj = loot.GetGUID(); + + for (GroupReference refe = GetFirstMember(); refe != null; refe = refe.next()) + { + Player looter = refe.GetSource(); + if (!looter.IsInWorld) + continue; + + if (looter.IsAtGroupRewardDistance(pLootedObject)) + data.Players.Add(looter.GetGUID()); + } + + for (GroupReference refe = GetFirstMember(); refe != null; refe = refe.next()) + { + Player looter = refe.GetSource(); + if (looter.IsAtGroupRewardDistance(pLootedObject)) + looter.SendPacket(data); + } + } + + public void CountRollVote(ObjectGuid playerGuid, ObjectGuid lootObjectGuid, byte lootListId, RollType choice) + { + var roll = GetRoll(lootObjectGuid, lootListId); + if (roll == null) + return; + + var rollType = roll.playerVote.LookupByKey(playerGuid); + // this condition means that player joins to the party after roll begins + if (rollType == 0) + return; + + if (roll.getLoot() != null) + if (roll.getLoot().items.Empty()) + return; + + switch (choice) + { + case RollType.Pass: // Player choose pass + SendLootRoll(playerGuid, -1, RollType.Pass, roll); + ++roll.totalPass; + rollType = RollType.Pass; + break; + case RollType.Need: // player choose Need + SendLootRoll(playerGuid, 0, RollType.Need, roll); + ++roll.totalNeed; + rollType = RollType.Need; + break; + case RollType.Greed: // player choose Greed + SendLootRoll(playerGuid, -7, RollType.Greed, roll); + ++roll.totalGreed; + rollType = RollType.Greed; + break; + case RollType.Disenchant: // player choose Disenchant + SendLootRoll(playerGuid, -8, RollType.Disenchant, roll); + ++roll.totalGreed; + rollType = RollType.Disenchant; + break; + } + + if (roll.totalPass + roll.totalNeed + roll.totalGreed >= roll.totalPlayersRolling) + CountTheRoll(roll); + } + + public void EndRoll(Loot pLoot) + { + foreach (var roll in RollId) + { + if (roll.getLoot() == pLoot) + { + CountTheRoll(roll); //i don't have to edit player votes, who didn't vote ... he will pass + } + } + } + + void CountTheRoll(Roll roll) + { + if (!roll.isValid()) // is loot already deleted ? + { + RollId.Remove(roll); + return; + } + + //end of the roll + if (roll.totalNeed > 0) + { + if (!roll.playerVote.Empty()) + { + byte maxresul = 0; + ObjectGuid maxguid = roll.playerVote.First().Key; + Player player; + + foreach (var pair in roll.playerVote) + { + if (pair.Value != RollType.Need) + continue; + + byte randomN = (byte)RandomHelper.IRand(1, 100); + SendLootRoll(pair.Key, randomN, RollType.Need, roll); + if (maxresul < randomN) + { + maxguid = pair.Key; + maxresul = randomN; + } + } + SendLootRollWon(maxguid, maxresul, RollType.Need, roll); + player = Global.ObjAccessor.FindPlayer(maxguid); + + if (player && player.GetSession() != null) + { + player.UpdateCriteria(CriteriaTypes.RollNeedOnLoot, roll.itemid, maxresul); + + List dest = new List(); + LootItem item = (roll.itemSlot >= roll.getLoot().items.Count ? roll.getLoot().quest_items[roll.itemSlot - roll.getLoot().items.Count] : roll.getLoot().items[roll.itemSlot]); + InventoryResult msg = player.CanStoreNewItem(ItemConst.NullBag, ItemConst.NullSlot, dest, roll.itemid, item.count); + if (msg == InventoryResult.Ok) + { + item.is_looted = true; + roll.getLoot().NotifyItemRemoved(roll.itemSlot); + roll.getLoot().unlootedCount--; + player.StoreNewItem(dest, roll.itemid, true, item.randomPropertyId, item.GetAllowedLooters(), item.context, item.BonusListIDs); + } + else + { + item.is_blocked = false; + player.SendEquipError(msg, null, null, roll.itemid); + } + } + } + } + else if (roll.totalGreed > 0) + { + if (!roll.playerVote.Empty()) + { + byte maxresul = 0; + ObjectGuid maxguid = roll.playerVote.First().Key; + Player player; + RollType rollVote = RollType.NotValid; + + foreach (var pair in roll.playerVote) + { + if (pair.Value != RollType.Greed && pair.Value != RollType.Disenchant) + continue; + + byte randomN = (byte)RandomHelper.IRand(1, 100); + SendLootRoll(pair.Key, randomN, pair.Value, roll); + if (maxresul < randomN) + { + maxguid = pair.Key; + maxresul = randomN; + rollVote = pair.Value; + } + } + SendLootRollWon(maxguid, maxresul, rollVote, roll); + player = Global.ObjAccessor.FindPlayer(maxguid); + + if (player && player.GetSession() != null) + { + player.UpdateCriteria(CriteriaTypes.RollGreedOnLoot, roll.itemid, maxresul); + + LootItem item = roll.itemSlot >= roll.getLoot().items.Count ? roll.getLoot().quest_items[roll.itemSlot - roll.getLoot().items.Count] : roll.getLoot().items[roll.itemSlot]; + + if (rollVote == RollType.Greed) + { + List dest = new List(); + InventoryResult msg = player.CanStoreNewItem(ItemConst.NullBag, ItemConst.NullSlot, dest, roll.itemid, item.count); + if (msg == InventoryResult.Ok) + { + item.is_looted = true; + roll.getLoot().NotifyItemRemoved(roll.itemSlot); + roll.getLoot().unlootedCount--; + player.StoreNewItem(dest, roll.itemid, true, item.randomPropertyId, item.GetAllowedLooters(), item.context, item.BonusListIDs); + } + else + { + item.is_blocked = false; + player.SendEquipError(msg, null, null, roll.itemid); + } + } + else if (rollVote == RollType.Disenchant) + { + item.is_looted = true; + roll.getLoot().NotifyItemRemoved(roll.itemSlot); + roll.getLoot().unlootedCount--; + ItemTemplate pProto = Global.ObjectMgr.GetItemTemplate(roll.itemid); + player.UpdateCriteria(CriteriaTypes.CastSpell, 13262); // Disenchant + + List dest = new List(); + InventoryResult msg = player.CanStoreNewItem(ItemConst.NullBag, ItemConst.NullSlot, dest, roll.itemid, item.count); + if (msg == InventoryResult.Ok) + player.AutoStoreLoot(pProto.DisenchantID, LootStorage.Disenchant, true); + else // If the player's inventory is full, send the disenchant result in a mail. + { + Loot loot = new Loot(); + loot.FillLoot(pProto.DisenchantID, LootStorage.Disenchant, player, true); + + uint max_slot = loot.GetMaxSlotInLootFor(player); + for (uint i = 0; i < max_slot; ++i) + { + LootItem lootItem = loot.LootItemInSlot(i, player); + player.SendEquipError(msg, null, null, lootItem.itemid); + player.SendItemRetrievalMail(lootItem.itemid, lootItem.count); + } + } + } + } + } + } + else + { + SendLootAllPassed(roll); + + // remove is_blocked so that the item is lootable by all players + LootItem item = roll.itemSlot >= roll.getLoot().items.Count ? roll.getLoot().quest_items[roll.itemSlot - roll.getLoot().items.Count] : roll.getLoot().items[roll.itemSlot]; + if (item != null) + item.is_blocked = false; + } + + SendLootRollsComplete(roll); + + RollId.Remove(roll); + } + + public void SetTargetIcon(byte symbol, ObjectGuid target, ObjectGuid changedBy, sbyte partyIndex) + { + if (symbol >= MapConst.TargetIconsCount) + return; + + // clean other icons + if (!target.IsEmpty()) + for (byte i = 0; i < MapConst.TargetIconsCount; ++i) + if (m_targetIcons[i] == target) + SetTargetIcon(i, ObjectGuid.Empty, changedBy, partyIndex); + + m_targetIcons[symbol] = target; + + SendRaidTargetUpdateSingle updateSingle = new SendRaidTargetUpdateSingle(); + updateSingle.PartyIndex = partyIndex; + updateSingle.Target = target; + updateSingle.ChangedBy = changedBy; + updateSingle.Symbol = (sbyte)symbol; + BroadcastPacket(updateSingle, true); + } + + public void SendTargetIconList(WorldSession session, sbyte partyIndex) + { + if (session == null) + return; + + SendRaidTargetUpdateAll updateAll = new SendRaidTargetUpdateAll(); + updateAll.PartyIndex = partyIndex; + for (byte i = 0; i < MapConst.TargetIconsCount; i++) + updateAll.TargetIcons.Add(i, m_targetIcons[i]); + + session.SendPacket(updateAll); + } + + public void SendUpdate() + { + foreach (var member in m_memberSlots) + SendUpdateToPlayer(member.guid, member); + } + + public void SendUpdateToPlayer(ObjectGuid playerGUID, MemberSlot memberSlot = null) + { + Player player = Global.ObjAccessor.FindPlayer(playerGUID); + + if (player == null || player.GetSession() == null || player.GetGroup() != this) + return; + + // if MemberSlot wasn't provided + if (memberSlot == null) + { + var slot = _getMemberSlot(playerGUID); + if (slot == null) // if there is no MemberSlot for such a player + return; + + memberSlot = slot; + } + PartyUpdate partyUpdate = new PartyUpdate(); + + partyUpdate.PartyFlags = m_groupFlags; + partyUpdate.PartyIndex = (byte)m_groupCategory; + partyUpdate.PartyType = IsCreated() ? GroupType.Normal : GroupType.None; + + partyUpdate.PartyGUID = m_guid; + partyUpdate.LeaderGUID = m_leaderGuid; + + partyUpdate.SequenceNum = player.NextGroupUpdateSequenceNumber(m_groupCategory); + + partyUpdate.MyIndex = -1; + byte index = 0; + for (var i = 0; i < m_memberSlots.Count; ++i, ++index) + { + var member = m_memberSlots[i]; + if (memberSlot.guid == member.guid) + partyUpdate.MyIndex = index; + + Player memberPlayer = Global.ObjAccessor.FindConnectedPlayer(member.guid); + + PartyPlayerInfo playerInfos = new PartyPlayerInfo(); + + playerInfos.GUID = member.guid; + playerInfos.Name = member.name; + playerInfos.Class = member._class; + + playerInfos.Status = GroupMemberOnlineStatus.Offline; + if (memberPlayer && memberPlayer.GetSession() && !memberPlayer.GetSession().PlayerLogout()) + playerInfos.Status = GroupMemberOnlineStatus.Online | (isBGGroup() || isBFGroup() ? GroupMemberOnlineStatus.PVP : 0); + + playerInfos.Subgroup = member.group; // groupid + playerInfos.Flags = (byte)member.flags; // See enum GroupMemberFlags + playerInfos.RolesAssigned = (byte)member.roles; // Lfg Roles + + partyUpdate.PlayerList.Add(playerInfos); + } + + if (GetMembersCount() > 1) + { + // LootSettings + partyUpdate.LootSettings.HasValue = true; + + partyUpdate.LootSettings.Value.Method = (byte)m_lootMethod; + partyUpdate.LootSettings.Value.Threshold = (byte)m_lootThreshold; + partyUpdate.LootSettings.Value.LootMaster = m_lootMethod == LootMethod.MasterLoot ? m_masterLooterGuid : ObjectGuid.Empty; + + // Difficulty Settings + partyUpdate.DifficultySettings.HasValue = true; + + partyUpdate.DifficultySettings.Value.DungeonDifficultyID = (uint)m_dungeonDifficulty; + partyUpdate.DifficultySettings.Value.RaidDifficultyID = (uint)m_raidDifficulty; + partyUpdate.DifficultySettings.Value.LegacyRaidDifficultyID = (uint)m_legacyRaidDifficulty; + } + + // LfgInfos + if (isLFGGroup()) + { + partyUpdate.LfgInfos.HasValue = true; + + partyUpdate.LfgInfos.Value.Slot = Global.LFGMgr.GetDungeon(m_guid); + partyUpdate.LfgInfos.Value.BootCount = 0; + partyUpdate.LfgInfos.Value.Aborted = false; + + partyUpdate.LfgInfos.Value.MyFlags = (byte)(Global.LFGMgr.GetState(m_guid) == LfgState.FinishedDungeon ? 2 : 0); + + uint randomSlot = 0; + var selectedDungeons = Global.LFGMgr.GetSelectedDungeons(player.GetGUID()); + if (selectedDungeons.Count == 1) + { + LfgDungeonsRecord dungeon = CliDB.LfgDungeonsStorage.LookupByKey(selectedDungeons.First()); + if (dungeon != null) + if (dungeon.Type == LfgType.RandomDungeon) + randomSlot= dungeon.Id; + } + + partyUpdate.LfgInfos.Value.MyRandomSlot = randomSlot; + + partyUpdate.LfgInfos.Value.MyPartialClear = 0; + partyUpdate.LfgInfos.Value.MyGearDiff = 0.0f; + partyUpdate.LfgInfos.Value.MyFirstReward = false; + + partyUpdate.LfgInfos.Value.MyStrangerCount = 0; + partyUpdate.LfgInfos.Value.MyKickVoteCount = 0; + } + + player.SendPacket(partyUpdate); + } + + void SendUpdateDestroyGroupToPlayer(Player player) + { + PartyUpdate partyUpdate = new PartyUpdate(); + partyUpdate.PartyFlags = GroupFlags.Destroyed; + partyUpdate.PartyIndex = (byte)m_groupCategory; + partyUpdate.PartyType = GroupType.None; + partyUpdate.PartyGUID = m_guid; + partyUpdate.MyIndex = -1; + partyUpdate.SequenceNum = player.NextGroupUpdateSequenceNumber(m_groupCategory); + player.SendPacket(partyUpdate); + } + + public void UpdatePlayerOutOfRange(Player player) + { + if (!player || !player.IsInWorld) + return; + + PartyMemberState packet = new PartyMemberState(); + packet.Initialize(player); + packet.Write(); + + for (GroupReference refe = GetFirstMember(); refe != null; refe = refe.next()) + { + Player member = refe.GetSource(); + if (member && member != player && (!member.IsInMap(player) || !member.IsWithinDist(player, member.GetSightRange(), false))) + member.SendPacket(packet, false); + } + } + + public void BroadcastAddonMessagePacket(ServerPacket packet, string prefix, bool ignorePlayersInBGRaid, int group = -1, ObjectGuid ignore = default(ObjectGuid)) + { + for (GroupReference refe = GetFirstMember(); refe != null; refe = refe.next()) + { + Player player = refe.GetSource(); + if (player == null || (!ignore.IsEmpty() && player.GetGUID() == ignore) || (ignorePlayersInBGRaid && player.GetGroup() != this)) + continue; + + if ((group == -1 || refe.getSubGroup() == group)) + if (player.GetSession().IsAddonRegistered(prefix)) + player.SendPacket(packet); + } + } + + public void BroadcastPacket(ServerPacket packet, bool ignorePlayersInBGRaid, int group = -1, ObjectGuid ignore = default(ObjectGuid)) + { + packet.Write(); + for (GroupReference refe = GetFirstMember(); refe != null; refe = refe.next()) + { + Player player = refe.GetSource(); + if (!player || (!ignore.IsEmpty() && player.GetGUID() == ignore) || (ignorePlayersInBGRaid && player.GetGroup() != this)) + continue; + + if (player.GetSession() != null && (group == -1 || refe.getSubGroup() == group)) + player.SendPacket(packet, false); + } + } + + bool _setMembersGroup(ObjectGuid guid, byte group) + { + var slot = _getMemberSlot(guid); + if (slot == null) + return false; + + slot.group = group; + + SubGroupCounterIncrease(group); + + if (!isBGGroup() && !isBFGroup()) + { + PreparedStatement stmt = DB.Characters.GetPreparedStatement(CharStatements.UPD_GROUP_MEMBER_SUBGROUP); + + stmt.AddValue(0, group); + stmt.AddValue(1, guid.GetCounter()); + + DB.Characters.Execute(stmt); + } + + return true; + } + + public bool SameSubGroup(Player member1, Player member2) + { + if (!member1 || !member2) + return false; + + if (member1.GetGroup() != this || member2.GetGroup() != this) + return false; + else + return member1.GetSubGroup() == member2.GetSubGroup(); + } + + public void ChangeMembersGroup(ObjectGuid guid, byte group) + { + // Only raid groups have sub groups + if (!isRaidGroup()) + return; + + // Check if player is really in the raid + var slot = _getMemberSlot(guid); + if (slot == null) + return; + + byte prevSubGroup = slot.group; + // Abort if the player is already in the target sub group + if (prevSubGroup == group) + return; + + // Update the player slot with the new sub group setting + slot.group = group; + + // Increase the counter of the new sub group.. + SubGroupCounterIncrease(group); + + // ..and decrease the counter of the previous one + SubGroupCounterDecrease(prevSubGroup); + + // Preserve new sub group in database for non-raid groups + if (!isBGGroup() && !isBFGroup()) + { + PreparedStatement stmt = DB.Characters.GetPreparedStatement(CharStatements.UPD_GROUP_MEMBER_SUBGROUP); + + stmt.AddValue(0, group); + stmt.AddValue(1, guid.GetCounter()); + + DB.Characters.Execute(stmt); + } + + // In case the moved player is online, update the player object with the new sub group references + Player player = Global.ObjAccessor.FindPlayer(guid); + if (player) + { + if (player.GetGroup() == this) + player.GetGroupRef().setSubGroup(group); + else + { + // If player is in BG raid, it is possible that he is also in normal raid - and that normal raid is stored in m_originalGroup reference + player.GetOriginalGroupRef().setSubGroup(group); + } + } + + // Broadcast the changes to the group + SendUpdate(); + } + + public void SwapMembersGroups(ObjectGuid firstGuid, ObjectGuid secondGuid) + { + if (!isRaidGroup()) + return; + + MemberSlot[] slots = new MemberSlot[2]; + slots[0] = _getMemberSlot(firstGuid); + slots[1] = _getMemberSlot(secondGuid); + if (slots[0] == null || slots[1] == null) + return; + + if (slots[0].group == slots[1].group) + return; + + byte tmp = slots[0].group; + slots[0].group = slots[1].group; + slots[1].group = tmp; + + SQLTransaction trans = new SQLTransaction(); + for (byte i = 0; i < 2; i++) + { + // Preserve new sub group in database for non-raid groups + if (!isBGGroup() && !isBFGroup()) + { + PreparedStatement stmt = DB.Characters.GetPreparedStatement(CharStatements.UPD_GROUP_MEMBER_SUBGROUP); + stmt.AddValue(0, slots[i].group); + stmt.AddValue(1, slots[i].guid.GetCounter()); + + trans.Append(stmt); + } + + Player player = Global.ObjAccessor.FindConnectedPlayer(slots[i].guid); + if (player) + { + if (player.GetGroup() == this) + player.GetGroupRef().setSubGroup(slots[i].group); + else + player.GetOriginalGroupRef().setSubGroup(slots[i].group); + } + } + DB.Characters.CommitTransaction(trans); + + SendUpdate(); + } + + public void UpdateLooterGuid(WorldObject pLootedObject, bool ifneed = false) + { + switch (GetLootMethod()) + { + case LootMethod.MasterLoot: + case LootMethod.FreeForAll: + return; + default: + // round robin style looting applies for all low + // quality items in each loot method except free for all and master loot + break; + } + + ObjectGuid oldLooterGUID = GetLooterGuid(); + var memberSlot = _getMemberSlot(oldLooterGUID); + if (memberSlot != null) + { + if (ifneed) + { + // not update if only update if need and ok + Player looter = Global.ObjAccessor.FindPlayer(memberSlot.guid); + if (looter && looter.IsAtGroupRewardDistance(pLootedObject)) + return; + } + } + + // search next after current + Player pNewLooter = null; + foreach (var member in m_memberSlots) + { + if (member == memberSlot) + continue; + + Player player = Global.ObjAccessor.FindPlayer(member.guid); + if (player) + if (player.IsAtGroupRewardDistance(pLootedObject)) + { + pNewLooter = player; + break; + } + } + + if (!pNewLooter) + { + // search from start + foreach (var member in m_memberSlots) + { + Player player = Global.ObjAccessor.FindPlayer(member.guid); + if (player) + if (player.IsAtGroupRewardDistance(pLootedObject)) + { + pNewLooter = player; + break; + } + } + } + + if (pNewLooter) + { + if (oldLooterGUID != pNewLooter.GetGUID()) + { + SetLooterGuid(pNewLooter.GetGUID()); + SendUpdate(); + } + } + else + { + SetLooterGuid(ObjectGuid.Empty); + SendUpdate(); + } + } + + public GroupJoinBattlegroundResult CanJoinBattlegroundQueue(Battleground bgOrTemplate, BattlegroundQueueTypeId bgQueueTypeId, uint MinPlayerCount, uint MaxPlayerCount, bool isRated, uint arenaSlot, out ObjectGuid errorGuid) + { + errorGuid = new ObjectGuid(); + // check if this group is LFG group + if (isLFGGroup()) + return GroupJoinBattlegroundResult.LfgCantUseBattleground; + + BattlemasterListRecord bgEntry = CliDB.BattlemasterListStorage.LookupByKey(bgOrTemplate.GetTypeID()); + if (bgEntry == null) + return GroupJoinBattlegroundResult.JoinFailed; // shouldn't happen + + // check for min / max count + uint memberscount = GetMembersCount(); + + if (memberscount > bgEntry.MaxGroupSize) // no MinPlayerCount for Battlegrounds + return GroupJoinBattlegroundResult.None; // ERR_GROUP_JOIN_Battleground_TOO_MANY handled on client side + + // get a player as reference, to compare other players' stats to (arena team id, queue id based on level, etc.) + Player reference = GetFirstMember().GetSource(); + // no reference found, can't join this way + if (!reference) + return GroupJoinBattlegroundResult.JoinFailed; + + PvpDifficultyRecord bracketEntry = Global.DB2Mgr.GetBattlegroundBracketByLevel(bgOrTemplate.GetMapId(), reference.getLevel()); + if (bracketEntry == null) + return GroupJoinBattlegroundResult.JoinFailed; + + uint arenaTeamId = reference.GetArenaTeamId((byte)arenaSlot); + Team team = reference.GetTeam(); + + BattlegroundQueueTypeId bgQueueTypeIdRandom = Global.BattlegroundMgr.BGQueueTypeId(BattlegroundTypeId.RB, 0); + + // check every member of the group to be able to join + memberscount = 0; + for (GroupReference refe = GetFirstMember(); refe != null; refe = refe.next(), ++memberscount) + { + Player member = refe.GetSource(); + // offline member? don't let join + if (!member) + return GroupJoinBattlegroundResult.JoinFailed; + // don't allow cross-faction join as group + if (member.GetTeam() != team) + { + errorGuid = member.GetGUID(); + return GroupJoinBattlegroundResult.JoinTimedOut; + } + // not in the same Battleground level braket, don't let join + PvpDifficultyRecord memberBracketEntry = Global.DB2Mgr.GetBattlegroundBracketByLevel(bracketEntry.MapID, member.getLevel()); + if (memberBracketEntry != bracketEntry) + return GroupJoinBattlegroundResult.JoinRangeIndex; + // don't let join rated matches if the arena team id doesn't match + if (isRated && member.GetArenaTeamId((byte)arenaSlot) != arenaTeamId) + return GroupJoinBattlegroundResult.JoinFailed; + // don't let join if someone from the group is already in that bg queue + if (member.InBattlegroundQueueForBattlegroundQueueType(bgQueueTypeId)) + return GroupJoinBattlegroundResult.JoinFailed; // not blizz-like + // don't let join if someone from the group is in bg queue random + if (member.InBattlegroundQueueForBattlegroundQueueType(bgQueueTypeIdRandom)) + return GroupJoinBattlegroundResult.InRandomBg; + // don't let join to bg queue random if someone from the group is already in bg queue + if (bgOrTemplate.GetTypeID() == BattlegroundTypeId.RB && member.InBattlegroundQueue()) + return GroupJoinBattlegroundResult.InNonRandomBg; + // check for deserter debuff in case not arena queue + if (bgOrTemplate.GetTypeID() != BattlegroundTypeId.AA && !member.CanJoinToBattleground(bgOrTemplate)) + return GroupJoinBattlegroundResult.Deserters; + // check if member can join any more Battleground queues + if (!member.HasFreeBattlegroundQueueId()) + return GroupJoinBattlegroundResult.TooManyQueues; // not blizz-like + // check if someone in party is using dungeon system + if (member.isUsingLfg()) + return GroupJoinBattlegroundResult.LfgCantUseBattleground; + // check Freeze debuff + if (member.HasAura(9454)) + return GroupJoinBattlegroundResult.JoinFailed; + } + + // only check for MinPlayerCount since MinPlayerCount == MaxPlayerCount for arenas... + if (bgOrTemplate.isArena() && memberscount != MinPlayerCount) + return GroupJoinBattlegroundResult.ArenaTeamPartySize; + + return GroupJoinBattlegroundResult.None; + } + + public void SetDungeonDifficultyID(Difficulty difficulty) + { + m_dungeonDifficulty = difficulty; + if (!isBGGroup() && !isBFGroup()) + { + PreparedStatement stmt = DB.Characters.GetPreparedStatement(CharStatements.UPD_GROUP_DIFFICULTY); + + stmt.AddValue(0, (byte)m_dungeonDifficulty); + stmt.AddValue(1, m_dbStoreId); + + DB.Characters.Execute(stmt); + } + + for (GroupReference refe = GetFirstMember(); refe != null; refe = refe.next()) + { + Player player = refe.GetSource(); + if (player.GetSession() == null) + continue; + + player.SetDungeonDifficultyID(difficulty); + player.SendDungeonDifficulty(); + } + } + + public void SetRaidDifficultyID(Difficulty difficulty) + { + m_raidDifficulty = difficulty; + if (!isBGGroup() && !isBFGroup()) + { + PreparedStatement stmt = DB.Characters.GetPreparedStatement(CharStatements.UPD_GROUP_RAID_DIFFICULTY); + + stmt.AddValue(0, (byte)m_raidDifficulty); + stmt.AddValue(1, m_dbStoreId); + + DB.Characters.Execute(stmt); + } + + for (GroupReference refe = GetFirstMember(); refe != null; refe = refe.next()) + { + Player player = refe.GetSource(); + if (player.GetSession() == null) + continue; + + player.SetRaidDifficultyID(difficulty); + player.SendRaidDifficulty(false); + } + } + + public void SetLegacyRaidDifficultyID(Difficulty difficulty) + { + m_legacyRaidDifficulty = difficulty; + if (!isBGGroup() && !isBFGroup()) + { + PreparedStatement stmt = DB.Characters.GetPreparedStatement(CharStatements.UPD_GROUP_LEGACY_RAID_DIFFICULTY); + + stmt.AddValue(0, m_legacyRaidDifficulty); + stmt.AddValue(1, m_dbStoreId); + + DB.Characters.Execute(stmt); + } + + for (GroupReference refe = GetFirstMember(); refe != null; refe = refe.next()) + { + Player player = refe.GetSource(); + if (player.GetSession() == null) + continue; + + player.SetLegacyRaidDifficultyID(difficulty); + player.SendRaidDifficulty(true); + } + } + + public Difficulty GetDifficultyID(MapRecord mapEntry) + { + if (!mapEntry.IsRaid()) + return m_dungeonDifficulty; + + MapDifficultyRecord defaultDifficulty = Global.DB2Mgr.GetDefaultMapDifficulty(mapEntry.Id); + if (defaultDifficulty == null) + return m_legacyRaidDifficulty; + + DifficultyRecord difficulty = CliDB.DifficultyStorage.LookupByKey(defaultDifficulty.DifficultyID); + if (difficulty == null || difficulty.Flags.HasAnyFlag(DifficultyFlags.Legacy)) + return m_legacyRaidDifficulty; + + return m_raidDifficulty; + } + + public Difficulty GetDungeonDifficultyID() { return m_dungeonDifficulty; } + public Difficulty GetRaidDifficultyID() { return m_raidDifficulty; } + public Difficulty GetLegacyRaidDifficultyID() { return m_legacyRaidDifficulty; } + + public void ResetInstances(InstanceResetMethod method, bool isRaid, bool isLegacy, Player SendMsgTo) + { + if (isBGGroup() || isBFGroup()) + return; + + // method can be INSTANCE_RESET_ALL, INSTANCE_RESET_CHANGE_DIFFICULTY, INSTANCE_RESET_GROUP_DISBAND + + // we assume that when the difficulty changes, all instances that can be reset will be + Difficulty diff = GetDungeonDifficultyID(); + if (isRaid) + { + if (!isLegacy) + diff = GetRaidDifficultyID(); + else + diff = GetLegacyRaidDifficultyID(); + } + + foreach (var pair in m_boundInstances[(int)diff].ToList()) + { + InstanceSave instanceSave = pair.Value.save; + MapRecord entry = CliDB.MapStorage.LookupByKey(pair.Key); + if (entry == null || entry.IsRaid() != isRaid || (!instanceSave.CanReset() && method != InstanceResetMethod.GroupDisband)) + continue; + + if (method == InstanceResetMethod.All) + { + // the "reset all instances" method can only reset normal maps + if (entry.InstanceType == MapTypes.Raid || diff == Difficulty.Heroic) + continue; + } + + bool isEmpty = true; + // if the map is loaded, reset it + Map map = Global.MapMgr.FindMap(instanceSave.GetMapId(), instanceSave.GetInstanceId()); + if (map && map.IsDungeon() && !(method == InstanceResetMethod.GroupDisband && !instanceSave.CanReset())) + { + if (instanceSave.CanReset()) + isEmpty = ((InstanceMap)map).Reset(method); + else + isEmpty = !map.HavePlayers(); + } + + if (SendMsgTo) + { + if (!isEmpty) + SendMsgTo.SendResetInstanceFailed(ResetFailedReason.Failed, instanceSave.GetMapId()); + else if (WorldConfig.GetBoolValue(WorldCfg.InstancesResetAnnounce)) + { + Group group = SendMsgTo.GetGroup(); + if (group) + { + for (GroupReference refe = group.GetFirstMember(); refe != null; refe = refe.next()) + { + Player player = refe.GetSource(); + if (player) + player.SendResetInstanceSuccess(instanceSave.GetMapId()); + } + } + else + SendMsgTo.SendResetInstanceSuccess(instanceSave.GetMapId()); + } + else + SendMsgTo.SendResetInstanceSuccess(instanceSave.GetMapId()); + } + + if (isEmpty || method == InstanceResetMethod.GroupDisband || method == InstanceResetMethod.ChangeDifficulty) + { + // do not reset the instance, just unbind if others are permanently bound to it + if (instanceSave.CanReset()) + instanceSave.DeleteFromDB(); + else + { + PreparedStatement stmt = DB.Characters.GetPreparedStatement(CharStatements.DEL_GROUP_INSTANCE_BY_INSTANCE); + stmt.AddValue(0, instanceSave.GetInstanceId()); + + DB.Characters.Execute(stmt); + } + + + // i don't know for sure if hash_map iterators + m_boundInstances[(int)diff].Remove(pair.Key); + // this unloads the instance save unless online players are bound to it + // (eg. permanent binds or GM solo binds) + instanceSave.RemoveGroup(this); + } + } + } + + public InstanceBind GetBoundInstance(Player player) + { + uint mapid = player.GetMapId(); + MapRecord mapEntry = CliDB.MapStorage.LookupByKey(mapid); + return GetBoundInstance(mapEntry); + } + + public InstanceBind GetBoundInstance(Map aMap) + { + return GetBoundInstance(aMap.GetEntry()); + } + + public InstanceBind GetBoundInstance(MapRecord mapEntry) + { + if (mapEntry == null || !mapEntry.IsDungeon()) + return null; + + Difficulty difficulty = GetDifficultyID(mapEntry); + return GetBoundInstance(difficulty, mapEntry.Id); + } + + public InstanceBind GetBoundInstance(Difficulty difficulty, uint mapId) + { + // some instances only have one difficulty + Global.DB2Mgr.GetDownscaledMapDifficultyData(mapId, ref difficulty); + + return m_boundInstances[(int)difficulty].LookupByKey(mapId); + } + + public InstanceBind BindToInstance(InstanceSave save, bool permanent, bool load = false) + { + if (save == null || isBGGroup() || isBFGroup()) + return null; + + if (!m_boundInstances[(int)save.GetDifficultyID()].ContainsKey(save.GetMapId())) + m_boundInstances[(int)save.GetDifficultyID()][save.GetMapId()] = new InstanceBind(); + + InstanceBind bind = m_boundInstances[(int)save.GetDifficultyID()][save.GetMapId()]; + if (!load && (bind.save == null || permanent != bind.perm || save != bind.save)) + { + PreparedStatement stmt = DB.Characters.GetPreparedStatement(CharStatements.REP_GROUP_INSTANCE); + + stmt.AddValue(0, m_dbStoreId); + stmt.AddValue(1, save.GetInstanceId()); + stmt.AddValue(2, permanent); + + DB.Characters.Execute(stmt); + } + + if (bind.save != save) + { + if (bind.save != null) + bind.save.RemoveGroup(this); + save.AddGroup(this); + } + + bind.save = save; + bind.perm = permanent; + if (!load) + Log.outDebug(LogFilter.Maps, "Group.BindToInstance: Group ({0}, storage id: {1}) is now bound to map {2}, instance {3}, difficulty {4}", + GetGUID().ToString(), m_dbStoreId, save.GetMapId(), save.GetInstanceId(), save.GetDifficultyID()); + + m_boundInstances[(int)save.GetDifficultyID()][save.GetMapId()] = bind; + + return bind; + } + + public void UnbindInstance(uint mapid, Difficulty difficulty, bool unload = false) + { + var instanceBind = m_boundInstances[(int)difficulty].LookupByKey(mapid); + if (instanceBind != null) + { + if (!unload) + { + PreparedStatement stmt = DB.Characters.GetPreparedStatement(CharStatements.DEL_GROUP_INSTANCE_BY_GUID); + + stmt.AddValue(0, m_dbStoreId); + stmt.AddValue(1, instanceBind.save.GetInstanceId()); + + DB.Characters.Execute(stmt); + } + + instanceBind.save.RemoveGroup(this); // save can become invalid + m_boundInstances[(int)difficulty].Remove(mapid); + } + } + + void _homebindIfInstance(Player player) + { + if (player && !player.IsGameMaster() && CliDB.MapStorage.LookupByKey(player.GetMapId()).IsDungeon()) + player.m_InstanceValid = false; + } + + public void BroadcastGroupUpdate() + { + // FG: HACK: force flags update on group leave - for values update hack + // -- not very efficient but safe + foreach (var member in m_memberSlots) + { + Player pp = Global.ObjAccessor.FindPlayer(member.guid); + if (pp && pp.IsInWorld) + { + pp.ForceValuesUpdateAtIndex(UnitFields.Bytes2); + pp.ForceValuesUpdateAtIndex(UnitFields.FactionTemplate); + Log.outDebug(LogFilter.Server, "-- Forced group value update for '{0}'", pp.GetName()); + } + } + } + + public void ResetMaxEnchantingLevel() + { + m_maxEnchantingLevel = 0; + Player member = null; + foreach (var memberSlot in m_memberSlots) + { + member = Global.ObjAccessor.FindPlayer(memberSlot.guid); + if (member && m_maxEnchantingLevel < member.GetSkillValue(SkillType.Enchanting)) + m_maxEnchantingLevel = member.GetSkillValue(SkillType.Enchanting); + } + } + + public void SetLootMethod(LootMethod method) + { + m_lootMethod = method; + } + + public void SetLooterGuid(ObjectGuid guid) + { + m_looterGuid = guid; + } + + public void SetMasterLooterGuid(ObjectGuid guid) + { + m_masterLooterGuid = guid; + } + + public void SetLootThreshold(ItemQuality threshold) + { + m_lootThreshold = threshold; + } + + public void SetLfgRoles(ObjectGuid guid, LfgRoles roles) + { + var slot = _getMemberSlot(guid); + if (slot == null) + return; + + slot.roles = roles; + SendUpdate(); + } + + public LfgRoles GetLfgRoles(ObjectGuid guid) + { + MemberSlot slot = _getMemberSlot(guid); + if (slot == null) + return 0; + + return slot.roles; + } + + public void Update(uint diff) + { + UpdateReadyCheck(diff); + } + + void UpdateReadyCheck(uint diff) + { + if (!m_readyCheckStarted) + return; + + m_readyCheckTimer -= (int)diff; + if (m_readyCheckTimer <= 0) + EndReadyCheck(); + } + + public void StartReadyCheck(ObjectGuid starterGuid, sbyte partyIndex, uint duration = MapConst.ReadycheckDuration) + { + if (m_readyCheckStarted) + return; + + MemberSlot slot = _getMemberSlot(starterGuid); + if (slot == null) + return; + + m_readyCheckStarted = true; + m_readyCheckTimer = (int)duration; + + SetOfflineMembersReadyChecked(); + + SetMemberReadyChecked(slot); + + ReadyCheckStarted readyCheckStarted = new ReadyCheckStarted(); + readyCheckStarted.PartyGUID = m_guid; + readyCheckStarted.PartyIndex = partyIndex; + readyCheckStarted.InitiatorGUID = starterGuid; + readyCheckStarted.Duration = duration; + BroadcastPacket(readyCheckStarted, false); + } + + void EndReadyCheck() + { + if (!m_readyCheckStarted) + return; + + m_readyCheckStarted = false; + m_readyCheckTimer = 0; + + ResetMemberReadyChecked(); + + ReadyCheckCompleted readyCheckCompleted = new ReadyCheckCompleted(); + readyCheckCompleted.PartyIndex = 0; + readyCheckCompleted.PartyGUID = m_guid; + BroadcastPacket(readyCheckCompleted, false); + } + + bool IsReadyCheckCompleted() + { + foreach (var member in m_memberSlots) + if (!member.readyChecked) + return false; + return true; + } + + public void SetMemberReadyCheck(ObjectGuid guid, bool ready) + { + if (!m_readyCheckStarted) + return; + + MemberSlot slot = _getMemberSlot(guid); + if (slot != null) + SetMemberReadyCheck(slot, ready); + } + + void SetMemberReadyCheck(MemberSlot slot, bool ready) + { + ReadyCheckResponse response = new ReadyCheckResponse(); + response.PartyGUID = m_guid; + response.Player = slot.guid; + response.IsReady = ready; + BroadcastPacket(response, false); + + SetMemberReadyChecked(slot); + } + + void SetOfflineMembersReadyChecked() + { + foreach (MemberSlot member in m_memberSlots) + { + Player player = Global.ObjAccessor.FindConnectedPlayer(member.guid); + if (!player || !player.GetSession()) + SetMemberReadyCheck(member, false); + } + } + + void SetMemberReadyChecked(MemberSlot slot) + { + slot.readyChecked = true; + if (IsReadyCheckCompleted()) + EndReadyCheck(); + } + + void ResetMemberReadyChecked() + { + foreach (MemberSlot member in m_memberSlots) + member.readyChecked = false; + } + + public void AddRaidMarker(byte markerId, uint mapId, float positionX, float positionY, float positionZ, ObjectGuid transportGuid = default(ObjectGuid)) + { + if (markerId >= MapConst.RaidMarkersCount || m_markers[markerId] != null) + return; + + m_activeMarkers |= (1u << markerId); + m_markers[markerId] = new RaidMarker(mapId, positionX, positionY, positionZ, transportGuid); + SendRaidMarkersChanged(); + } + + public void DeleteRaidMarker(byte markerId) + { + if (markerId > MapConst.RaidMarkersCount) + return; + + for (byte i = 0; i < MapConst.RaidMarkersCount; i++) + { + if (m_markers[i] != null && (markerId == i || markerId == MapConst.RaidMarkersCount)) + { + m_markers[i] = null; + m_activeMarkers &= ~(1u << i); + } + } + + SendRaidMarkersChanged(); + } + + public void SendRaidMarkersChanged(WorldSession session = null, sbyte partyIndex = 0) + { + RaidMarkersChanged packet = new RaidMarkersChanged(); + + packet.PartyIndex = partyIndex; + packet.ActiveMarkers = m_activeMarkers; + + for (byte i = 0; i < MapConst.RaidMarkersCount; i++) + { + if (m_markers[i] != null) + packet.RaidMarkers.Add(m_markers[i]); + } + + if (session) + session.SendPacket(packet); + else + BroadcastPacket(packet, false); + } + + public bool IsFull() + { + return isRaidGroup() ? (m_memberSlots.Count >= MapConst.MaxRaidSize) : (m_memberSlots.Count >= MapConst.MaxGroupSize); + } + + public bool isLFGGroup() + { + return m_groupFlags.HasAnyFlag(GroupFlags.Lfg); + } + public bool isRaidGroup() + { + return m_groupFlags.HasAnyFlag(GroupFlags.Raid); + } + + public bool isBGGroup() + { + return m_bgGroup != null; + } + + public bool isBFGroup() + { + return m_bfGroup != null; + } + + public bool IsCreated() + { + return GetMembersCount() > 0; + } + + public bool isRollLootActive() { return !RollId.Empty(); } + + public ObjectGuid GetLeaderGUID() + { + return m_leaderGuid; + } + + public ObjectGuid GetGUID() + { + return m_guid; + } + + public ulong GetLowGUID() + { + return m_guid.GetCounter(); + } + + string GetLeaderName() + { + return m_leaderName; + } + + public LootMethod GetLootMethod() + { + return m_lootMethod; + } + + public ObjectGuid GetLooterGuid() + { + if (GetLootMethod() == LootMethod.FreeForAll) + return ObjectGuid.Empty; + + return m_looterGuid; + } + + public ObjectGuid GetMasterLooterGuid() + { + return m_masterLooterGuid; + } + + public ItemQuality GetLootThreshold() + { + return m_lootThreshold; + } + + public bool IsMember(ObjectGuid guid) + { + return _getMemberSlot(guid) != null; + } + + public bool IsLeader(ObjectGuid guid) + { + return GetLeaderGUID() == guid; + } + + public ObjectGuid GetMemberGUID(string name) + { + foreach (var member in m_memberSlots) + if (member.name == name) + return member.guid; + return ObjectGuid.Empty; + } + + public bool IsAssistant(ObjectGuid guid) + { + var mslot = _getMemberSlot(guid); + if (mslot == null) + return false; + return mslot.flags.HasAnyFlag(GroupMemberFlags.Assistant); + } + + public bool SameSubGroup(ObjectGuid guid1, ObjectGuid guid2) + { + var mslot2 = _getMemberSlot(guid2); + if (mslot2 == null) + return false; + return SameSubGroup(guid1, mslot2); + } + + public bool SameSubGroup(ObjectGuid guid1, MemberSlot slot2) + { + var mslot1 = _getMemberSlot(guid1); + if (mslot1 == null || slot2 == null) + return false; + return (mslot1.group == slot2.group); + } + + public bool HasFreeSlotSubGroup(byte subgroup) + { + return (m_subGroupsCounts != null && m_subGroupsCounts[subgroup] < MapConst.MaxGroupSize); + } + + public byte GetMemberGroup(ObjectGuid guid) + { + var mslot = _getMemberSlot(guid); + if (mslot == null) + return (byte)(MapConst.MaxRaidSubGroups + 1); + return mslot.group; + } + + public void SetBattlegroundGroup(Battleground bg) + { + m_bgGroup = bg; + } + + public void SetBattlefieldGroup(BattleField bg) + { + m_bfGroup = bg; + } + + public void SetGroupMemberFlag(ObjectGuid guid, bool apply, GroupMemberFlags flag) + { + // Assistants, main assistants and main tanks are only available in raid groups + if (!isRaidGroup()) + return; + + // Check if player is really in the raid + var slot = _getMemberSlot(guid); + if (slot == null) + return; + + // Do flag specific actions, e.g ensure uniqueness + switch (flag) + { + case GroupMemberFlags.MainAssist: + RemoveUniqueGroupMemberFlag(GroupMemberFlags.MainAssist); // Remove main assist flag from current if any. + break; + case GroupMemberFlags.MainTank: + RemoveUniqueGroupMemberFlag(GroupMemberFlags.MainTank); // Remove main tank flag from current if any. + break; + case GroupMemberFlags.Assistant: + break; + default: + return; // This should never happen + } + + // Switch the actual flag + ToggleGroupMemberFlag(slot, flag, apply); + + // Preserve the new setting in the db + PreparedStatement stmt = DB.Characters.GetPreparedStatement(CharStatements.UPD_GROUP_MEMBER_FLAG); + + stmt.AddValue(0, slot.flags); + stmt.AddValue(1, guid.GetCounter()); + + DB.Characters.Execute(stmt); + + // Broadcast the changes to the group + SendUpdate(); + } + + Roll GetRoll(ObjectGuid lootObjectGuid, byte lootListId) + { + foreach (var roll in RollId) + if (roll.getTarget().GetGUID() == lootObjectGuid && roll.itemSlot == lootListId && roll.isValid()) + return roll; + return null; + } + + public void LinkMember(GroupReference pRef) + { + m_memberMgr.insertFirst(pRef); + } + + void DelinkMember(ObjectGuid guid) + { + GroupReference refe = m_memberMgr.getFirst(); + while (refe != null) + { + GroupReference nextRef = refe.next(); + if (refe.GetSource().GetGUID() == guid) + { + refe.unlink(); + break; + } + refe = nextRef; + } + } + + public Dictionary GetBoundInstances(Difficulty difficulty) + { + return m_boundInstances[(int)difficulty]; + } + + void _initRaidSubGroupsCounter() + { + // Sub group counters initialization + if (m_subGroupsCounts == null) + m_subGroupsCounts = new byte[MapConst.MaxRaidSubGroups]; + + foreach (var memberSlot in m_memberSlots) + ++m_subGroupsCounts[memberSlot.group]; + } + + MemberSlot _getMemberSlot(ObjectGuid guid) + { + foreach (var member in m_memberSlots) + if (member.guid == guid) + return member; + return null; + } + + void SubGroupCounterIncrease(byte subgroup) + { + if (m_subGroupsCounts != null) + ++m_subGroupsCounts[subgroup]; + } + + void SubGroupCounterDecrease(byte subgroup) + { + if (m_subGroupsCounts != null) + --m_subGroupsCounts[subgroup]; + } + + public void RemoveUniqueGroupMemberFlag(GroupMemberFlags flag) + { + foreach (var member in m_memberSlots) + if (member.flags.HasAnyFlag(flag)) + member.flags &= ~flag; + } + + void ToggleGroupMemberFlag(MemberSlot slot, GroupMemberFlags flag, bool apply) + { + if (apply) + slot.flags |= flag; + else + slot.flags &= ~flag; + } + + public void SetEveryoneIsAssistant(bool apply) + { + if (apply) + m_groupFlags |= GroupFlags.EveryoneAssistant; + else + m_groupFlags &= ~GroupFlags.EveryoneAssistant; + + foreach (MemberSlot member in m_memberSlots) + ToggleGroupMemberFlag(member, GroupMemberFlags.Assistant, apply); + + SendUpdate(); + } + + public GroupCategory GetGroupCategory() { return m_groupCategory; } + + public uint GetDbStoreId() { return m_dbStoreId; } + public List GetMemberSlots() { return m_memberSlots; } + public GroupReference GetFirstMember() { return (GroupReference)m_memberMgr.getFirst(); } + public uint GetMembersCount() { return (uint)m_memberSlots.Count; } + public GroupFlags GetGroupFlags() { return m_groupFlags; } + + bool IsReadyCheckStarted() { return m_readyCheckStarted; } + + public void BroadcastWorker(Action worker) + { + for (GroupReference refe = GetFirstMember(); refe != null; refe = refe.next()) + worker(refe.GetSource()); + } + + List m_memberSlots = new List(); + GroupRefManager m_memberMgr = new GroupRefManager(); + List m_invitees = new List(); + ObjectGuid m_leaderGuid; + string m_leaderName; + GroupFlags m_groupFlags; + GroupCategory m_groupCategory; + Difficulty m_dungeonDifficulty; + Difficulty m_raidDifficulty; + Difficulty m_legacyRaidDifficulty; + Battleground m_bgGroup; + BattleField m_bfGroup; + ObjectGuid[] m_targetIcons = new ObjectGuid[MapConst.TargetIconsCount]; + LootMethod m_lootMethod; + ItemQuality m_lootThreshold; + ObjectGuid m_looterGuid; + ObjectGuid m_masterLooterGuid; + List RollId = new List(); + Dictionary[] m_boundInstances = new Dictionary[(int)Difficulty.Max]; + byte[] m_subGroupsCounts; + ObjectGuid m_guid; + uint m_maxEnchantingLevel; + uint m_dbStoreId; + + // Ready Check + bool m_readyCheckStarted; + int m_readyCheckTimer; + + // Raid markers + RaidMarker[] m_markers = new RaidMarker[MapConst.RaidMarkersCount]; + uint m_activeMarkers; + + public static implicit operator bool (Group group) + { + return group != null; + } + } + + public class Roll : LootValidatorRef + { + public Roll(LootItem li) + { + itemid = li.itemid; + itemRandomPropId = li.randomPropertyId; + itemRandomSuffix = li.randomSuffix; + itemCount = li.count; + rollTypeMask = RollMask.AllNoDisenchant; + } + + public void setLoot(Loot pLoot) + { + link(pLoot, this); + } + + public Loot getLoot() + { + return getTarget(); + } + + public override void targetObjectBuildLink() + { + // called from link() + getTarget().addLootValidatorRef(this); + } + + public void FillPacket(LootItemData lootItem) + { + lootItem.UIType = (totalPlayersRolling > totalNeed + totalGreed + totalPass) ? LootSlotType.RollOngoing : LootSlotType.AllowLoot; + lootItem.Quantity = itemCount; + lootItem.LootListID = (byte)(itemSlot + 1); + + LootItem lootItemInSlot = this.getTarget().GetItemInSlot(itemSlot); + if (lootItemInSlot != null) + { + lootItem.CanTradeToTapList = lootItemInSlot.allowedGUIDs.Count > 1; + lootItem.Loot = new ItemInstance(lootItemInSlot); + } + } + + public uint itemid; + public ItemRandomEnchantmentId itemRandomPropId; + public uint itemRandomSuffix; + public byte itemCount; + public Dictionary playerVote = new Dictionary(); + public byte totalPlayersRolling; + public byte totalNeed; + public byte totalGreed; + public byte totalPass; + public byte itemSlot; + public RollMask rollTypeMask; + } + + public class MemberSlot + { + public ObjectGuid guid; + public string name; + public byte _class; + public byte group; + public GroupMemberFlags flags; + public LfgRoles roles; + public bool readyChecked; + } + + public class RaidMarker + { + public RaidMarker(uint mapId, float positionX, float positionY, float positionZ, ObjectGuid transportGuid = default(ObjectGuid)) + { + Location = new WorldLocation(mapId, positionX, positionY, positionZ); + TransportGUID = transportGuid; + } + + public WorldLocation Location; + public ObjectGuid TransportGUID; + } +} diff --git a/Game/Groups/GroupManager.cs b/Game/Groups/GroupManager.cs new file mode 100644 index 000000000..8a0a9c7aa --- /dev/null +++ b/Game/Groups/GroupManager.cs @@ -0,0 +1,239 @@ +/* + * Copyright (C) 2012-2017 CypherCore + * + * 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, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +using Framework.Constants; +using Framework.Database; +using Game.DataStorage; +using Game.Entities; +using Game.Maps; +using System.Collections.Generic; + +namespace Game.Groups +{ + public class GroupManager : Singleton + { + GroupManager() + { + NextGroupDbStoreId = 1; + NextGroupId = 1; + } + + public uint GenerateNewGroupDbStoreId() + { + uint newStorageId = NextGroupDbStoreId; + + for (uint i = ++NextGroupDbStoreId; i < 0xFFFFFFFF; ++i) + { + if ((i < GroupDbStore.Count && GroupDbStore[i] == null) || i >= GroupDbStore.Count) + { + NextGroupDbStoreId = i; + break; + } + } + + if (newStorageId == NextGroupDbStoreId) + { + Log.outError(LogFilter.Server, "Group storage ID overflow!! Can't continue, shutting down server. "); + Global.WorldMgr.StopNow(); + } + + return newStorageId; + } + + public void RegisterGroupDbStoreId(uint storageId, Group group) + { + GroupDbStore[storageId] = group; + } + + public void FreeGroupDbStoreId(Group group) + { + uint storageId = group.GetDbStoreId(); + + if (storageId < NextGroupDbStoreId) + NextGroupDbStoreId = storageId; + + GroupDbStore[storageId - 1] = null; + } + + public Group GetGroupByDbStoreId(uint storageId) + { + if (storageId < GroupDbStore.Count) + return GroupDbStore[storageId]; + + return null; + } + + public ulong GenerateGroupId() + { + if (NextGroupId >= 0xFFFFFFFE) + { + Log.outError(LogFilter.Server, "Group guid overflow!! Can't continue, shutting down server. "); + Global.WorldMgr.StopNow(); + } + return NextGroupId++; + } + + public Group GetGroupByGUID(ObjectGuid groupId) + { + return GroupStore.LookupByKey(groupId.GetCounter()); + } + + public void AddGroup(Group group) + { + GroupStore[group.GetGUID().GetCounter()] = group; + } + + public void RemoveGroup(Group group) + { + GroupStore.Remove(group.GetGUID().GetCounter()); + } + + public void LoadGroups() + { + { + uint oldMSTime = Time.GetMSTime(); + + // Delete all groups whose leader does not exist + DB.Characters.Execute("DELETE FROM groups WHERE leaderGuid NOT IN (SELECT guid FROM characters)"); + // Delete all groups with less than 2 members + DB.Characters.Execute("DELETE FROM groups WHERE guid NOT IN (SELECT guid FROM group_member GROUP BY guid HAVING COUNT(guid) > 1)"); + + // 0 1 2 3 4 5 6 7 8 9 + SQLResult result = DB.Characters.Query("SELECT g.leaderGuid, g.lootMethod, g.looterGuid, g.lootThreshold, g.icon1, g.icon2, g.icon3, g.icon4, g.icon5, g.icon6" + + // 10 11 12 13 14 15 16 17 18 19 + ", g.icon7, g.icon8, g.groupType, g.difficulty, g.raiddifficulty, g.legacyRaidDifficulty, g.masterLooterGuid, g.guid, lfg.dungeon, lfg.state FROM groups g LEFT JOIN lfg_data lfg ON lfg.guid = g.guid ORDER BY g.guid ASC"); + if (result.IsEmpty()) + { + Log.outInfo(LogFilter.ServerLoading, "Loaded 0 group definitions. DB table `groups` is empty!"); + return; + } + + uint count = 0; + do + { + Group group = new Group(); + group.LoadGroupFromDB(result.GetFields()); + AddGroup(group); + + // Get the ID used for storing the group in the database and register it in the pool. + uint storageId = group.GetDbStoreId(); + + RegisterGroupDbStoreId(storageId, group); + + // Increase the next available storage ID + if (storageId == NextGroupDbStoreId) + NextGroupDbStoreId++; + + ++count; + } + while (result.NextRow()); + + Log.outInfo(LogFilter.ServerLoading, "Loaded {0} group definitions in {1} ms", count, Time.GetMSTimeDiffToNow(oldMSTime)); + } + + Log.outInfo(LogFilter.ServerLoading, "Loading Group members..."); + { + uint oldMSTime = Time.GetMSTime(); + + // Delete all rows from group_member or group_instance with no group + DB.Characters.Execute("DELETE FROM group_member WHERE guid NOT IN (SELECT guid FROM groups)"); + DB.Characters.Execute("DELETE FROM group_instance WHERE guid NOT IN (SELECT guid FROM groups)"); + // Delete all members that does not exist + DB.Characters.Execute("DELETE FROM group_member WHERE memberGuid NOT IN (SELECT guid FROM characters)"); + + // 0 1 2 3 4 + SQLResult result = DB.Characters.Query("SELECT guid, memberGuid, memberFlags, subgroup, roles FROM group_member ORDER BY guid"); + if (result.IsEmpty()) + { + Log.outInfo(LogFilter.ServerLoading, "Loaded 0 group members. DB table `group_member` is empty!"); + return; + } + + uint count = 0; + + do + { + Group group = GetGroupByDbStoreId(result.Read(0)); + + if (group) + group.LoadMemberFromDB(result.Read(1), result.Read(2), result.Read(3), (LfgRoles)result.Read(4)); + else + Log.outError(LogFilter.Server, "GroupMgr:LoadGroups: Consistency failed, can't find group (storage id: {0})", result.Read(0)); + + ++count; + } + while (result.NextRow()); + + Log.outInfo(LogFilter.ServerLoading, "Loaded {0} group members in {1} ms", count, Time.GetMSTimeDiffToNow(oldMSTime)); + } + + Log.outInfo(LogFilter.ServerLoading, "Loading Group instance saves..."); + { + uint oldMSTime = Time.GetMSTime(); + // 0 1 2 3 4 5 6 7 + SQLResult result = DB.Characters.Query("SELECT gi.guid, i.map, gi.instance, gi.permanent, i.difficulty, i.resettime, i.entranceId, COUNT(g.guid) " + + "FROM group_instance gi INNER JOIN instance i ON gi.instance = i.id " + + "LEFT JOIN character_instance ci LEFT JOIN groups g ON g.leaderGuid = ci.guid ON ci.instance = gi.instance AND ci.permanent = 1 GROUP BY gi.instance ORDER BY gi.guid"); + if (result.IsEmpty()) + { + Log.outInfo(LogFilter.ServerLoading, "Loaded 0 group-instance saves. DB table `group_instance` is empty!"); + return; + } + + uint count = 0; + do + { + Group group = GetGroupByDbStoreId(result.Read(0)); + // group will never be NULL (we have run consistency sql's before loading) + + MapRecord mapEntry = CliDB.MapStorage.LookupByKey(result.Read(1)); + if (mapEntry == null || !mapEntry.IsDungeon()) + { + Log.outError(LogFilter.Sql, "Incorrect entry in group_instance table : no dungeon map {0}", result.Read(1)); + continue; + } + + uint diff = result.Read(4); + DifficultyRecord difficultyEntry = CliDB.DifficultyStorage.LookupByKey(diff); + if (difficultyEntry == null || difficultyEntry.InstanceType != mapEntry.InstanceType) + continue; + + InstanceSave save = Global.InstanceSaveMgr.AddInstanceSave(mapEntry.Id, result.Read(2), (Difficulty)diff, result.Read(5), result.Read(6), result.Read(7) != 0, true); + group.BindToInstance(save, result.Read(3), true); + ++count; + } + while (result.NextRow()); + + Log.outInfo(LogFilter.ServerLoading, "Loaded {0} group-instance saves in {1} ms", count, Time.GetMSTimeDiffToNow(oldMSTime)); + } + } + + public void Update(uint diff) + { + foreach (var group in GroupStore.Values) + { + if (group) + group.Update(diff); + } + } + + Dictionary GroupStore = new Dictionary(); + Dictionary GroupDbStore = new Dictionary(); + ulong NextGroupId; + uint NextGroupDbStoreId; + } +} diff --git a/Game/Groups/GroupReference.cs b/Game/Groups/GroupReference.cs new file mode 100644 index 000000000..e010310a8 --- /dev/null +++ b/Game/Groups/GroupReference.cs @@ -0,0 +1,50 @@ +/* + * Copyright (C) 2012-2017 CypherCore + * + * 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, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +using Framework.Dynamic; +using Game.Entities; + +namespace Game.Groups +{ + public class GroupReference : Reference + { + public GroupReference() + { + iSubGroup = 0; + } + + ~GroupReference() { unlink(); } + + public override void targetObjectBuildLink() + { + getTarget().LinkMember(this); + } + + public new GroupReference next() { return (GroupReference)base.next(); } + + public byte getSubGroup() { return iSubGroup; } + + public void setSubGroup(byte pSubGroup) { iSubGroup = pSubGroup; } + + byte iSubGroup; + } + + public class GroupRefManager : RefManager + { + public new GroupReference getFirst() { return (GroupReference)base.getFirst(); } + } +} diff --git a/Game/Guilds/Guild.cs b/Game/Guilds/Guild.cs new file mode 100644 index 000000000..a9718dc3a --- /dev/null +++ b/Game/Guilds/Guild.cs @@ -0,0 +1,3781 @@ +/* + * Copyright (C) 2012-2017 CypherCore + * + * 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, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +using Framework.Constants; +using Framework.Database; +using Game.Achievements; +using Game.DataStorage; +using Game.Entities; +using Game.Groups; +using Game.Network; +using Game.Network.Packets; +using System; +using System.Collections.Generic; +using System.Diagnostics.Contracts; +using System.Linq; + +namespace Game.Guilds +{ + public class Guild + { + public Guild() + { + m_achievementSys = new GuildAchievementMgr(this); + } + + public bool Create(Player pLeader, string name) + { + // Check if guild with such name already exists + if (Global.GuildMgr.GetGuildByName(name) != null) + return false; + + WorldSession pLeaderSession = pLeader.GetSession(); + if (pLeaderSession == null) + return false; + + m_id = Global.GuildMgr.GenerateGuildId(); + m_leaderGuid = pLeader.GetGUID(); + m_name = name; + m_info = ""; + m_motd = "No message set."; + m_bankMoney = 0; + m_createdDate = Time.UnixTime; + _CreateLogHolders(); + + Log.outDebug(LogFilter.Guild, "GUILD: creating guild [{0}] for leader {1} ({2})", + name, pLeader.GetName(), m_leaderGuid); + + SQLTransaction trans = new SQLTransaction(); + + PreparedStatement stmt = DB.Characters.GetPreparedStatement(CharStatements.DEL_GUILD_MEMBERS); + stmt.AddValue(0, m_id); + trans.Append(stmt); + + byte index = 0; + stmt = DB.Characters.GetPreparedStatement(CharStatements.INS_GUILD); + stmt.AddValue(index, m_id); + stmt.AddValue(++index, name); + stmt.AddValue(++index, m_leaderGuid.GetCounter()); + stmt.AddValue(++index, m_info); + stmt.AddValue(++index, m_motd); + stmt.AddValue(++index, m_createdDate); + stmt.AddValue(++index, m_emblemInfo.GetStyle()); + stmt.AddValue(++index, m_emblemInfo.GetColor()); + stmt.AddValue(++index, m_emblemInfo.GetBorderStyle()); + stmt.AddValue(++index, m_emblemInfo.GetBorderColor()); + stmt.AddValue(++index, m_emblemInfo.GetBackgroundColor()); + stmt.AddValue(++index, m_bankMoney); + trans.Append(stmt); + + DB.Characters.CommitTransaction(trans); + _CreateDefaultGuildRanks(pLeaderSession.GetSessionDbLocaleIndex()); // Create default ranks + bool ret = AddMember(m_leaderGuid, GuildDefaultRanks.Master); // Add guildmaster + + if (ret) + { + Member leader = GetMember(m_leaderGuid); + if (leader != null) + SendEventNewLeader(leader, null); + + Global.ScriptMgr.OnGuildCreate(this, pLeader, name); + } + + return ret; + } + + public void Disband() + { + Global.ScriptMgr.OnGuildDisband(this); + + BroadcastPacket(new GuildEventDisbanded()); + + while (!m_members.Empty()) + { + var member = m_members.First(); + DeleteMember(member.Value.GetGUID(), true); + } + + SQLTransaction trans = new SQLTransaction(); + + PreparedStatement stmt = DB.Characters.GetPreparedStatement(CharStatements.DEL_GUILD); + stmt.AddValue(0, m_id); + trans.Append(stmt); + + stmt = DB.Characters.GetPreparedStatement(CharStatements.DEL_GUILD_RANKS); + stmt.AddValue(0, m_id); + trans.Append(stmt); + + stmt = DB.Characters.GetPreparedStatement(CharStatements.DEL_GUILD_BANK_TABS); + stmt.AddValue(0, m_id); + trans.Append(stmt); + + // Free bank tab used memory and delete items stored in them + _DeleteBankItems(trans, true); + + stmt = DB.Characters.GetPreparedStatement(CharStatements.DEL_GUILD_BANK_ITEMS); + stmt.AddValue(0, m_id); + trans.Append(stmt); + + stmt = DB.Characters.GetPreparedStatement(CharStatements.DEL_GUILD_BANK_RIGHTS); + stmt.AddValue(0, m_id); + trans.Append(stmt); + + stmt = DB.Characters.GetPreparedStatement(CharStatements.DEL_GUILD_BANK_EVENTLOGS); + stmt.AddValue(0, m_id); + trans.Append(stmt); + + stmt = DB.Characters.GetPreparedStatement(CharStatements.DEL_GUILD_EVENTLOGS); + stmt.AddValue(0, m_id); + trans.Append(stmt); + + DB.Characters.CommitTransaction(trans); + + Global.GuildFinderMgr.DeleteGuild(GetGUID()); + + Global.GuildMgr.RemoveGuild(m_id); + } + + public void SaveToDB() + { + SQLTransaction trans = new SQLTransaction(); + + m_achievementSys.SaveToDB(trans); + + DB.Characters.CommitTransaction(trans); + } + + public void UpdateMemberData(Player player, GuildMemberData dataid, uint value) + { + Member member = GetMember(player.GetGUID()); + if (member != null) + { + switch (dataid) + { + case GuildMemberData.ZoneId: + member.SetZoneId(value); + break; + case GuildMemberData.AchievementPoints: + member.SetAchievementPoints(value); + break; + case GuildMemberData.Level: + member.SetLevel(value); + break; + default: + Log.outError(LogFilter.Guild, "Guild.UpdateMemberData: Called with incorrect DATAID {0} (value {1})", dataid, value); + return; + } + } + } + + void OnPlayerStatusChange(Player player, GuildMemberFlags flag, bool state) + { + Member member = GetMember(player.GetGUID()); + if (member != null) + { + if (state) + member.AddFlag(flag); + else + member.RemoveFlag(flag); + } + } + + public bool SetName(string name) + { + if (m_name == name || string.IsNullOrEmpty(name) || name.Length > 24 || Global.ObjectMgr.IsReservedName(name) || !ObjectManager.IsValidCharterName(name)) + return false; + + m_name = name; + PreparedStatement stmt = DB.Characters.GetPreparedStatement(CharStatements.UPD_GUILD_NAME); + stmt.AddValue(0, m_name); + stmt.AddValue(1, GetId()); + DB.Characters.Execute(stmt); + + GuildNameChanged guildNameChanged = new GuildNameChanged(); + guildNameChanged.GuildGUID = GetGUID(); + guildNameChanged.GuildName = m_name; + BroadcastPacket(guildNameChanged); + + return true; + } + + public void HandleRoster(WorldSession session = null) + { + GuildRoster roster = new GuildRoster(); + roster.NumAccounts = (int)m_accountsNumber; + roster.CreateDate = (uint)m_createdDate; + roster.GuildFlags = 0; + + + foreach (var member in m_members.Values) + { + GuildRosterMemberData memberData = new GuildRosterMemberData(); + + memberData.Guid = member.GetGUID(); + memberData.RankID = member.GetRankId(); + memberData.AreaID = (int)member.GetZoneId(); + memberData.PersonalAchievementPoints = (int)member.GetAchievementPoints(); + memberData.GuildReputation = (int)member.GetTotalReputation(); + memberData.LastSave = (member.IsOnline() ? 0.0f : ((ulong)Time.UnixTime - member.GetLogoutTime()) / Time.Day); + + //GuildRosterProfessionData + + memberData.VirtualRealmAddress = Global.WorldMgr.GetVirtualRealmAddress(); + memberData.Status = (byte)member.GetFlags(); + memberData.Level = member.GetLevel(); + memberData.ClassID = (byte)member.GetClass(); + memberData.Gender = (byte)member.GetGender(); + + memberData.Authenticated = false; + memberData.SorEligible = false; + + memberData.Name = member.GetName(); + memberData.Note = member.GetPublicNote(); + memberData.OfficerNote = member.GetOfficerNote(); + + roster.MemberData.Add(memberData); + } + + roster.WelcomeText = m_motd; + roster.InfoText = m_info; + + if (session != null) + session.SendPacket(roster); + } + + public void SendQueryResponse(WorldSession session) + { + QueryGuildInfoResponse response = new QueryGuildInfoResponse(); + response.GuildGUID = GetGUID(); + response.HasGuildInfo = true; + + response.Info.GuildGuid = GetGUID(); + response.Info.VirtualRealmAddress = Global.WorldMgr.GetVirtualRealmAddress(); + + response.Info.EmblemStyle = m_emblemInfo.GetStyle(); + response.Info.EmblemColor = m_emblemInfo.GetColor(); + response.Info.BorderStyle = m_emblemInfo.GetBorderStyle(); + response.Info.BorderColor = m_emblemInfo.GetBorderColor(); + response.Info.BackgroundColor = m_emblemInfo.GetBackgroundColor(); + + for (byte i = 0; i < _GetRanksSize(); ++i) + response.Info.Ranks.Add(new QueryGuildInfoResponse.GuildInfo.RankInfo(m_ranks[i].GetId(), i, m_ranks[i].GetName())); + + response.Info.GuildName = m_name; + + session.SendPacket(response); + } + + public void SendGuildRankInfo(WorldSession session) + { + GuildRanks ranks = new GuildRanks(); + + for (byte i = 0; i < _GetRanksSize(); i++) + { + RankInfo rankInfo = GetRankInfo(i); + if (rankInfo == null) + continue; + + GuildRankData rankData = new GuildRankData(); + + rankData.RankID = rankInfo.GetId(); + rankData.RankOrder = i; + rankData.Flags = (uint)rankInfo.GetRights(); + rankData.WithdrawGoldLimit = (uint)rankInfo.GetBankMoneyPerDay(); + rankData.RankName = rankInfo.GetName(); + + for (byte j = 0; j < GuildConst.MaxBankTabs; ++j) + { + rankData.TabFlags[j] = (uint)rankInfo.GetBankTabRights(j); + rankData.TabWithdrawItemLimit[j] = (uint)rankInfo.GetBankTabSlotsPerDay(j); + } + + ranks.Ranks.Add(rankData); + } + + session.SendPacket(ranks); + } + + public void HandleSetAchievementTracking(WorldSession session, List achievementIds) + { + Player player = session.GetPlayer(); + + Member member = GetMember(player.GetGUID()); + if (member != null) + { + List criteriaIds = new List(); + foreach (var achievementId in achievementIds) + { + var achievement = CliDB.AchievementStorage.LookupByKey(achievementId); + if (achievement != null) + { + CriteriaTree tree = Global.CriteriaMgr.GetCriteriaTree(achievement.CriteriaTree); + if (tree != null) + { + CriteriaManager.WalkCriteriaTree(tree, node => + { + if (node.Criteria != null) + criteriaIds.Add(node.Criteria.ID); + }); + } + } + } + + member.SetTrackedCriteriaIds(criteriaIds); + GetAchievementMgr().SendAllTrackedCriterias(player, member.GetTrackedCriteriaIds()); + } + } + + public void HandleGetAchievementMembers(WorldSession session, uint achievementId) + { + GetAchievementMgr().SendAchievementMembers(session.GetPlayer(), achievementId); + } + + public void HandleSetMOTD(WorldSession session, string motd) + { + if (m_motd == motd) + return; + + // Player must have rights to set MOTD + if (!_HasRankRight(session.GetPlayer(), GuildRankRights.SetMotd)) + SendCommandResult(session, GuildCommandType.EditMOTD, GuildCommandError.Permissions); + else + { + m_motd = motd; + + Global.ScriptMgr.OnGuildMOTDChanged(this, motd); + + PreparedStatement stmt = DB.Characters.GetPreparedStatement(CharStatements.UPD_GUILD_MOTD); + stmt.AddValue(0, motd); + stmt.AddValue(1, m_id); + DB.Characters.Execute(stmt); + + SendEventMOTD(session, true); + } + } + + public void HandleSetInfo(WorldSession session, string info) + { + if (m_info == info) + return; + + // Player must have rights to set guild's info + if (_HasRankRight(session.GetPlayer(), GuildRankRights.ModifyGuildInfo)) + { + m_info = info; + + Global.ScriptMgr.OnGuildInfoChanged(this, info); + + PreparedStatement stmt = DB.Characters.GetPreparedStatement(CharStatements.UPD_GUILD_INFO); + stmt.AddValue(0, info); + stmt.AddValue(1, m_id); + DB.Characters.Execute(stmt); + } + } + + public void HandleSetEmblem(WorldSession session, EmblemInfo emblemInfo) + { + Player player = session.GetPlayer(); + if (!_IsLeader(player)) + SendSaveEmblemResult(session, GuildEmblemError.NotGuildMaster); // "Only guild leaders can create emblems." + else if (!player.HasEnoughMoney(10 * MoneyConstants.Gold)) + SendSaveEmblemResult(session, GuildEmblemError.NotEnoughMoney); // "You can't afford to do that." + else + { + player.ModifyMoney(-(long)10 * MoneyConstants.Gold); + + m_emblemInfo = emblemInfo; + m_emblemInfo.SaveToDB(m_id); + + SendSaveEmblemResult(session, GuildEmblemError.Success); // "Guild Emblem saved." + + SendQueryResponse(session); + } + } + + public void HandleSetNewGuildMaster(WorldSession session, string name) + { + Player player = session.GetPlayer(); + // Only the guild master can throne a new guild master + if (!_IsLeader(player)) + SendCommandResult(session, GuildCommandType.ChangeLeader, GuildCommandError.Permissions); + // Old GM must be a guild member + Member oldGuildMaster = GetMember(player.GetGUID()); + if (oldGuildMaster != null) + { + // Same for the new one + Member newGuildMaster = GetMember(name); + if (newGuildMaster != null) + { + _SetLeaderGUID(newGuildMaster); + oldGuildMaster.ChangeRank(GuildDefaultRanks.Initiate); + + SendEventNewLeader(newGuildMaster, oldGuildMaster); + } + } + } + + public void HandleSetBankTabInfo(WorldSession session, byte tabId, string name, string icon) + { + BankTab tab = GetBankTab(tabId); + if (tab == null) + { + Log.outError(LogFilter.Guild, "Guild.HandleSetBankTabInfo: Player {0} trying to change bank tab info from unexisting tab {1}.", + session.GetPlayer().GetName(), tabId); + return; + } + + tab.SetInfo(name, icon); + + GuildEventTabModified packet = new GuildEventTabModified(); + packet.Tab = tabId; + packet.Name = name; + packet.Icon = icon; + BroadcastPacket(packet); + } + + public void HandleSetMemberNote(WorldSession session, string note, ObjectGuid guid, bool isPublic) + { + // Player must have rights to set public/officer note + if (!_HasRankRight(session.GetPlayer(), isPublic ? GuildRankRights.EditPublicNote : GuildRankRights.EOffNote)) + SendCommandResult(session, GuildCommandType.EditPublicNote, GuildCommandError.Permissions); + Member member = GetMember(guid); + if (member != null) + { + if (isPublic) + member.SetPublicNote(note); + else + member.SetOfficerNote(note); + + HandleRoster(session); // FIXME - We should send SMSG_GUILD_MEMBER_UPDATE_NOTE + + GuildMemberUpdateNote updateNote = new GuildMemberUpdateNote(); + updateNote.Member = guid; + updateNote.IsPublic = isPublic; + updateNote.Note = note; + session.SendPacket(updateNote); // @todo - Verify receiver of this packet... + } + } + + public void HandleSetRankInfo(WorldSession session, uint rankId, string name, GuildRankRights rights, uint moneyPerDay, List rightsAndSlots) + { + // Only leader can modify ranks + if (!_IsLeader(session.GetPlayer())) + SendCommandResult(session, GuildCommandType.ChangeRank, GuildCommandError.Permissions); + + RankInfo rankInfo = GetRankInfo(rankId); + if (rankInfo != null) + { + Log.outDebug(LogFilter.Guild, "Changed RankName to '{0}', rights to 0x{1}", name, rights); + + rankInfo.SetName(name); + rankInfo.SetRights(rights); + _SetRankBankMoneyPerDay(rankId, moneyPerDay); + + foreach (var rightsAndSlot in rightsAndSlots) + _SetRankBankTabRightsAndSlots(rankId, rightsAndSlot); + + GuildEventRankChanged packet = new GuildEventRankChanged(); + packet.RankID = rankId; + BroadcastPacket(packet); + } + } + + public void HandleBuyBankTab(WorldSession session, byte tabId) + { + Player player = session.GetPlayer(); + if (player == null) + return; + + Member member = GetMember(player.GetGUID()); + if (member == null) + return; + + if (_GetPurchasedTabsSize() >= GuildConst.MaxBankTabs) + return; + + if (tabId != _GetPurchasedTabsSize()) + return; + + // Do not get money for bank tabs that the GM bought, we had to buy them already. + // This is just a speedup check, GetGuildBankTabPrice will return 0. + if (tabId < GuildConst.MaxBankTabs - 2) // 7th tab is actually the 6th + { + uint tabCost = _GetGuildBankTabPrice(tabId) * MoneyConstants.Gold; + if (tabCost == 0) + return; + + if (!player.HasEnoughMoney(tabCost)) // Should not happen, this is checked by client + return; + + player.ModifyMoney(-tabCost); + } + + _CreateNewBankTab(); + + BroadcastPacket(new GuildEventTabAdded()); + + SendPermissions(session); //Hack to force client to update permissions + } + + public void HandleInviteMember(WorldSession session, string name) + { + Player pInvitee = Global.ObjAccessor.FindPlayerByName(name); + if (pInvitee == null) + { + SendCommandResult(session, GuildCommandType.InvitePlayer, GuildCommandError.PlayerNotFound_S, name); + return; + } + + Player player = session.GetPlayer(); + // Do not show invitations from ignored players + if (pInvitee.GetSocial().HasIgnore(player.GetGUID())) + return; + + if (!WorldConfig.GetBoolValue(WorldCfg.AllowTwoSideInteractionGuild) && pInvitee.GetTeam() != player.GetTeam()) + { + SendCommandResult(session, GuildCommandType.InvitePlayer, GuildCommandError.NotAllied, name); + return; + } + + // Invited player cannot be in another guild + if (pInvitee.GetGuildId() != 0) + { + SendCommandResult(session, GuildCommandType.InvitePlayer, GuildCommandError.AlreadyInGuild_S, name); + return; + } + + // Invited player cannot be invited + if (pInvitee.GetGuildIdInvited() != 0) + { + SendCommandResult(session, GuildCommandType.InvitePlayer, GuildCommandError.AlreadyInvitedToGuild_S, name); + return; + } + // Inviting player must have rights to invite + if (!_HasRankRight(player, GuildRankRights.Invite)) + { + SendCommandResult(session, GuildCommandType.InvitePlayer, GuildCommandError.Permissions); + return; + } + + SendCommandResult(session, GuildCommandType.InvitePlayer, GuildCommandError.Success, name); + + Log.outDebug(LogFilter.Guild, "Player {0} invited {1} to join his Guild", player.GetName(), name); + + pInvitee.SetGuildIdInvited(m_id); + _LogEvent(GuildEventLogTypes.InvitePlayer, player.GetGUID().GetCounter(), pInvitee.GetGUID().GetCounter()); + + GuildInvite invite = new GuildInvite(); + + invite.InviterVirtualRealmAddress = Global.WorldMgr.GetVirtualRealmAddress(); + invite.GuildVirtualRealmAddress = Global.WorldMgr.GetVirtualRealmAddress(); + invite.GuildGUID = GetGUID(); + + invite.EmblemStyle = m_emblemInfo.GetStyle(); + invite.EmblemColor = m_emblemInfo.GetColor(); + invite.BorderStyle = m_emblemInfo.GetBorderStyle(); + invite.BorderColor = m_emblemInfo.GetBorderColor(); + invite.Background = m_emblemInfo.GetBackgroundColor(); + invite.AchievementPoints = (int)GetAchievementMgr().GetAchievementPoints(); + + invite.InviterName = player.GetName(); + invite.GuildName = GetName(); + + Guild oldGuild = pInvitee.GetGuild(); + if (oldGuild) + { + invite.OldGuildGUID = oldGuild.GetGUID(); + invite.OldGuildName = oldGuild.GetName(); + invite.OldGuildVirtualRealmAddress = Global.WorldMgr.GetVirtualRealmAddress(); + } + + pInvitee.SendPacket(invite); + } + + public void HandleAcceptMember(WorldSession session) + { + Player player = session.GetPlayer(); + if (!WorldConfig.GetBoolValue(WorldCfg.AllowTwoSideInteractionGuild) && + player.GetTeam() != ObjectManager.GetPlayerTeamByGUID(GetLeaderGUID())) + return; + + AddMember(player.GetGUID()); + } + + public void HandleLeaveMember(WorldSession session) + { + Player player = session.GetPlayer(); + + // If leader is leaving + if (_IsLeader(player)) + { + if (m_members.Count > 1) + // Leader cannot leave if he is not the last member + SendCommandResult(session, GuildCommandType.LeaveGuild, GuildCommandError.LeaderLeave); + else + { + // Guild is disbanded if leader leaves. + Disband(); + } + } + else + { + DeleteMember(player.GetGUID(), false, false); + + _LogEvent(GuildEventLogTypes.LeaveGuild, player.GetGUID().GetCounter()); + SendEventPlayerLeft(player); + + SendCommandResult(session, GuildCommandType.LeaveGuild, GuildCommandError.Success, m_name); + } + + Global.CalendarMgr.RemovePlayerGuildEventsAndSignups(player.GetGUID(), GetId()); + } + + public void HandleRemoveMember(WorldSession session, ObjectGuid guid) + { + Player player = session.GetPlayer(); + + // Player must have rights to remove members + if (!_HasRankRight(player, GuildRankRights.Remove)) + SendCommandResult(session, GuildCommandType.RemovePlayer, GuildCommandError.Permissions); + + Member member = GetMember(guid); + if (member != null) + { + string name = member.GetName(); + + // Guild masters cannot be removed + if (member.IsRank(GuildDefaultRanks.Master)) + SendCommandResult(session, GuildCommandType.RemovePlayer, GuildCommandError.LeaderLeave); + // Do not allow to remove player with the same rank or higher + else + { + Member memberMe = GetMember(player.GetGUID()); + if (memberMe == null || member.IsRankNotLower(memberMe.GetRankId())) + SendCommandResult(session, GuildCommandType.RemovePlayer, GuildCommandError.RankTooHigh_S, name); + else + { + DeleteMember(guid, false, true); + _LogEvent(GuildEventLogTypes.UninvitePlayer, player.GetGUID().GetCounter(), guid.GetCounter()); + + Player pMember = Global.ObjAccessor.FindConnectedPlayer(guid); + SendEventPlayerLeft(pMember, player, true); + + SendCommandResult(session, GuildCommandType.RemovePlayer, GuildCommandError.Success, name); + } + } + } + } + + public void HandleUpdateMemberRank(WorldSession session, ObjectGuid guid, bool demote) + { + Player player = session.GetPlayer(); + GuildCommandType type = demote ? GuildCommandType.DemotePlayer : GuildCommandType.PromotePlayer; + // Player must have rights to promote + Member member; + if (!_HasRankRight(player, demote ? GuildRankRights.Demote : GuildRankRights.Promote)) + SendCommandResult(session, type, GuildCommandError.LeaderLeave); + // Promoted player must be a member of guild + else if ((member = GetMember(guid)) != null) + { + string name = member.GetName(); + // Player cannot promote himself + if (member.IsSamePlayer(player.GetGUID())) + { + SendCommandResult(session, type, GuildCommandError.NameInvalid); + return; + } + + Member memberMe = GetMember(player.GetGUID()); + byte rankId = memberMe.GetRankId(); + if (demote) + { + // Player can demote only lower rank members + if (member.IsRankNotLower(rankId)) + { + SendCommandResult(session, type, GuildCommandError.RankTooHigh_S, name); + return; + } + // Lowest rank cannot be demoted + if (member.GetRankId() >= _GetLowestRankId()) + { + SendCommandResult(session, type, GuildCommandError.RankTooLow_S, name); + return; + } + } + else + { + // Allow to promote only to lower rank than member's rank + // member.GetRankId() + 1 is the highest rank that current player can promote to + if (member.IsRankNotLower((uint)(rankId + 1))) + { + SendCommandResult(session, type, GuildCommandError.RankTooHigh_S, name); + return; + } + } + + uint newRankId = (uint)(member.GetRankId() + (demote ? 1 : -1)); + member.ChangeRank(newRankId); + _LogEvent(demote ? GuildEventLogTypes.DemotePlayer : GuildEventLogTypes.PromotePlayer, player.GetGUID().GetCounter(), member.GetGUID().GetCounter(), (byte)newRankId); + //_BroadcastEvent(demote ? GuildEvents.Demotion : GuildEvents.Promotion, ObjectGuid.Empty, player.GetName(), name, _GetRankName((byte)newRankId)); + } + } + + public void HandleSetMemberRank(WorldSession session, ObjectGuid targetGuid, ObjectGuid setterGuid, uint rank) + { + Player player = session.GetPlayer(); + Member member = GetMember(targetGuid); + GuildRankRights rights = GuildRankRights.Promote; + GuildCommandType type = GuildCommandType.PromotePlayer; + + if (rank > member.GetRankId()) + { + rights = GuildRankRights.Demote; + type = GuildCommandType.DemotePlayer; + } + + // Promoted player must be a member of guild + if (!_HasRankRight(player, rights)) + { + SendCommandResult(session, type, GuildCommandError.Permissions); + return; + } + + // Player cannot promote himself + if (member.IsSamePlayer(player.GetGUID())) + { + SendCommandResult(session, type, GuildCommandError.NameInvalid); + return; + } + + SendGuildRanksUpdate(setterGuid, targetGuid, rank); + } + + public void HandleAddNewRank(WorldSession session, string name) + { + byte size = _GetRanksSize(); + if (size >= GuildConst.MaxRanks) + return; + + // Only leader can add new rank + if (_IsLeader(session.GetPlayer())) + if (_CreateRank(name, GuildRankRights.GChatListen | GuildRankRights.GChatSpeak)) + BroadcastPacket(new GuildEventRanksUpdated()); + } + + public void HandleRemoveRank(WorldSession session, uint rankId) + { + // Cannot remove rank if total count is minimum allowed by the client or is not leader + if (_GetRanksSize() <= GuildConst.MinRanks || rankId >= _GetRanksSize() || !_IsLeader(session.GetPlayer())) + return; + + // Delete bank rights for rank + PreparedStatement stmt = DB.Characters.GetPreparedStatement(CharStatements.DEL_GUILD_BANK_RIGHTS_FOR_RANK); + stmt.AddValue(0, m_id); + stmt.AddValue(1, rankId); + DB.Characters.Execute(stmt); + // Delete rank + stmt = DB.Characters.GetPreparedStatement(CharStatements.DEL_GUILD_RANK); + stmt.AddValue(0, m_id); + stmt.AddValue(1, rankId); + DB.Characters.Execute(stmt); + + m_ranks.RemoveAt((int)rankId); + + BroadcastPacket(new GuildEventRanksUpdated()); + } + + public void HandleMemberDepositMoney(WorldSession session, ulong amount, bool cashFlow = false) + { + Player player = session.GetPlayer(); + + // Call script after validation and before money transfer. + Global.ScriptMgr.OnGuildMemberDepositMoney(this, player, amount); + + SQLTransaction trans = new SQLTransaction(); + _ModifyBankMoney(trans, amount, true); + if (!cashFlow) + { + player.ModifyMoney(-(long)amount); + player.SaveGoldToDB(trans); + } + + _LogBankEvent(trans, cashFlow ? GuildBankEventLogTypes.CashFlowDeposit : GuildBankEventLogTypes.DepositMoney, 0, player.GetGUID().GetCounter(), (uint)amount); + DB.Characters.CommitTransaction(trans); + + SendEventBankMoneyChanged(); + + if (player.GetSession().HasPermission(RBACPermissions.LogGmTrade)) + { + Log.outCommand(player.GetSession().GetAccountId(), "GM {0} (Account: {1}) deposit money (Amount: {2}) to guild bank (Guild ID {3})", + player.GetName(), player.GetSession().GetAccountId(), amount, m_id); + } + } + + public bool HandleMemberWithdrawMoney(WorldSession session, ulong amount, bool repair = false) + { + // clamp amount to MAX_MONEY_AMOUNT, Players can't hold more than that anyway + amount = Math.Min(amount, PlayerConst.MaxMoneyAmount); + + if (m_bankMoney < amount) // Not enough money in bank + return false; + + Player player = session.GetPlayer(); + + Member member = GetMember(player.GetGUID()); + if (member == null) + return false; + + if ((ulong)_GetMemberRemainingMoney(member) < amount) // Check if we have enough slot/money today + return false; + + // Call script after validation and before money transfer. + Global.ScriptMgr.OnGuildMemberWitdrawMoney(this, player, amount, repair); + + SQLTransaction trans = new SQLTransaction(); + // Add money to player (if required) + if (!repair) + { + if (!player.ModifyMoney((long)amount)) + return false; + + player.SaveGoldToDB(trans); + } + + // Update remaining money amount + member.UpdateBankWithdrawValue(trans, GuildConst.MaxBankTabs, (uint)amount); + // Remove money from bank + _ModifyBankMoney(trans, amount, false); + + // Log guild bank event + _LogBankEvent(trans, repair ? GuildBankEventLogTypes.RepairMoney : GuildBankEventLogTypes.WithdrawMoney, 0, player.GetGUID().GetCounter(), (uint)amount); + DB.Characters.CommitTransaction(trans); + + SendEventBankMoneyChanged(); + return true; + } + + public void HandleMemberLogout(WorldSession session) + { + Player player = session.GetPlayer(); + Member member = GetMember(player.GetGUID()); + if (member != null) + { + member.SetStats(player); + member.UpdateLogoutTime(); + member.ResetFlags(); + } + + SendEventPresenceChanged(session, false, true); + SaveToDB(); + } + + public void HandleDelete(WorldSession session) + { + // Only leader can disband guild + if (_IsLeader(session.GetPlayer())) + { + Disband(); + Log.outDebug(LogFilter.Guild, "Guild Successfully Disbanded"); + } + } + + public void HandleGuildPartyRequest(WorldSession session) + { + Player player = session.GetPlayer(); + Group group = player.GetGroup(); + + // Make sure player is a member of the guild and that he is in a group. + if (!IsMember(player.GetGUID()) || !group) + return; + + GuildPartyState partyStateResponse = new GuildPartyState(); + partyStateResponse.InGuildParty = (player.GetMap().GetOwnerGuildId(player.GetTeam()) == GetId()); + partyStateResponse.NumMembers = 0; + partyStateResponse.NumRequired = 0; + partyStateResponse.GuildXPEarnedMult = 0.0f; + session.SendPacket(partyStateResponse); + } + + public void HandleGuildRequestChallengeUpdate(WorldSession session) + { + GuildChallengeUpdate updatePacket = new GuildChallengeUpdate(); + + for (int i = 0; i < GuildConst.ChallengesTypes; ++i) + updatePacket.CurrentCount[i] = 0; /// @todo current count + + for (int i = 0; i < GuildConst.ChallengesTypes; ++i) + updatePacket.MaxCount[i] = GuildConst.ChallengesMaxCount[i]; + + for (int i = 0; i < GuildConst.ChallengesTypes; ++i) + updatePacket.MaxLevelGold[i] = GuildConst.ChallengeMaxLevelGoldReward[i]; + + for (int i = 0; i < GuildConst.ChallengesTypes; ++i) + updatePacket.Gold[i] = GuildConst.ChallengeGoldReward[i]; + + session.SendPacket(updatePacket); + } + + public void SendEventLog(WorldSession session) + { + var logs = m_eventLog.GetGuildLog(); + + if (logs == null) + return; + + GuildEventLogQueryResults packet = new GuildEventLogQueryResults(); + foreach (var logEntry in logs) + { + EventLogEntry eventLog = (EventLogEntry)logEntry; + eventLog.WritePacket(packet); + } + + session.SendPacket(packet); + } + + public void SendNewsUpdate(WorldSession session) + { + var logs = m_eventLog.GetGuildLog(); + + if (logs == null) + return; + + GuildNewsPkt packet = new GuildNewsPkt(); + foreach (var logEntry in logs) + { + NewsLogEntry eventLog = (NewsLogEntry)logEntry; + eventLog.WritePacket(packet); + } + + session.SendPacket(packet); + } + + public void SendBankLog(WorldSession session, byte tabId) + { + // GuildConst.MaxBankTabs send by client for money log + if (tabId < _GetPurchasedTabsSize() || tabId == GuildConst.MaxBankTabs) + { + var logs = m_bankEventLog[tabId].GetGuildLog(); + + if (logs == null) + return; + + GuildBankLogQueryResults packet = new GuildBankLogQueryResults(); + packet.Tab = tabId; + + //if (tabId == GUILD_BANK_MAX_TABS && hasCashFlow) + // packet.WeeklyBonusMoney.Set(uint64(weeklyBonusMoney)); + + foreach (var logEntry in logs) + { + BankEventLogEntry bankEventLog = (BankEventLogEntry)logEntry; + bankEventLog.WritePacket(packet); + } + + session.SendPacket(packet); + } + } + + public void SendBankTabText(WorldSession session, byte tabId) + { + BankTab tab = GetBankTab(tabId); + if (tab != null) + tab.SendText(this, session); + } + + public void SendPermissions(WorldSession session) + { + Member member = GetMember(session.GetPlayer().GetGUID()); + if (member == null) + return; + + byte rankId = member.GetRankId(); + + GuildPermissionsQueryResults queryResult = new GuildPermissionsQueryResults(); + queryResult.RankID = rankId; + queryResult.WithdrawGoldLimit = _GetMemberRemainingMoney(member); + queryResult.Flags = (int)_GetRankRights(rankId); + queryResult.NumTabs = _GetPurchasedTabsSize(); + + for (byte tabId = 0; tabId < GuildConst.MaxBankTabs; ++tabId) + { + GuildPermissionsQueryResults.GuildRankTabPermissions tabPerm; + tabPerm.Flags = (int)_GetRankBankTabRights(rankId, tabId); + tabPerm.WithdrawItemLimit = _GetMemberRemainingSlots(member, tabId); + queryResult.Tab.Add(tabPerm); + } + + session.SendPacket(queryResult); + } + + public void SendMoneyInfo(WorldSession session) + { + Member member = GetMember(session.GetPlayer().GetGUID()); + if (member == null) + return; + + int amount = _GetMemberRemainingMoney(member); + + GuildBankRemainingWithdrawMoney packet = new GuildBankRemainingWithdrawMoney(); + packet.RemainingWithdrawMoney = amount; + session.SendPacket(packet); + } + + public void SendLoginInfo(WorldSession session) + { + Player player = session.GetPlayer(); + Member member = GetMember(player.GetGUID()); + if (member == null) + return; + + SendEventMOTD(session); + SendGuildRankInfo(session); + SendEventPresenceChanged(session, true, true); // Broadcast + + // Send to self separately, player is not in world yet and is not found by _BroadcastEvent + SendEventPresenceChanged(session, true); + + if (member.GetGUID() == GetLeaderGUID()) + { + GuildFlaggedForRename renameFlag = new GuildFlaggedForRename(); + renameFlag.FlagSet = false; + player.SendPacket(renameFlag); + } + + foreach (var entry in CliDB.GuildPerkSpellsStorage.Values) + player.LearnSpell(entry.SpellID, true); + + m_achievementSys.SendAllData(player); + + // tells the client to request bank withdrawal limit + player.SendPacket(new GuildMemberDailyReset()); + + member.SetStats(player); + member.AddFlag(GuildMemberFlags.Online); + } + + void SendEventBankMoneyChanged() + { + GuildEventBankMoneyChanged eventPacket = new GuildEventBankMoneyChanged(); + eventPacket.Money = GetBankMoney(); + BroadcastPacket(eventPacket); + } + + void SendEventMOTD(WorldSession session, bool broadcast = false) + { + GuildEventMotd eventPacket = new GuildEventMotd(); + eventPacket.MotdText = GetMOTD(); + + if (broadcast) + BroadcastPacket(eventPacket); + else + { + session.SendPacket(eventPacket); + Log.outDebug(LogFilter.Guild, "SMSG_GUILD_EVENT_MOTD [{0}] ", session.GetPlayerInfo()); + } + } + + void SendEventNewLeader(Member newLeader, Member oldLeader, bool isSelfPromoted = false) + { + GuildEventNewLeader eventPacket = new GuildEventNewLeader(); + eventPacket.SelfPromoted = isSelfPromoted; + if (newLeader != null) + { + eventPacket.NewLeaderGUID = newLeader.GetGUID(); + eventPacket.NewLeaderName = newLeader.GetName(); + eventPacket.NewLeaderVirtualRealmAddress = Global.WorldMgr.GetVirtualRealmAddress(); + } + + if (oldLeader != null) + { + eventPacket.OldLeaderGUID = oldLeader.GetGUID(); + eventPacket.OldLeaderName = oldLeader.GetName(); + eventPacket.OldLeaderVirtualRealmAddress = Global.WorldMgr.GetVirtualRealmAddress(); + } + + BroadcastPacket(eventPacket); + } + + void SendEventPlayerLeft(Player leaver, Player remover = null, bool isRemoved = false) + { + GuildEventPlayerLeft eventPacket = new GuildEventPlayerLeft(); + eventPacket.Removed = isRemoved; + eventPacket.LeaverGUID = leaver.GetGUID(); + eventPacket.LeaverName = leaver.GetName(); + eventPacket.LeaverVirtualRealmAddress = Global.WorldMgr.GetVirtualRealmAddress(); + + if (isRemoved && remover) + { + eventPacket.RemoverGUID = remover.GetGUID(); + eventPacket.RemoverName = remover.GetName(); + eventPacket.RemoverVirtualRealmAddress = Global.WorldMgr.GetVirtualRealmAddress(); + } + + BroadcastPacket(eventPacket); + } + + void SendEventPresenceChanged(WorldSession session, bool loggedOn, bool broadcast = false) + { + Player player = session.GetPlayer(); + + GuildEventPresenceChange eventPacket = new GuildEventPresenceChange(); + eventPacket.Guid = player.GetGUID(); + eventPacket.Name = player.GetName(); + eventPacket.VirtualRealmAddress = Global.WorldMgr.GetVirtualRealmAddress(); + eventPacket.LoggedOn = loggedOn; + eventPacket.Mobile = false; + + if (broadcast) + BroadcastPacket(eventPacket); + else + session.SendPacket(eventPacket); + } + + public bool LoadFromDB(SQLFields fields) + { + m_id = fields.Read(0); + m_name = fields.Read(1); + m_leaderGuid = ObjectGuid.Create(HighGuid.Player, fields.Read(2)); + + if (!m_emblemInfo.LoadFromDB(fields)) + { + Log.outError(LogFilter.Guild, "Guild {0} has invalid emblem colors (Background: {1}, Border: {2}, Emblem: {3}), skipped.", + m_id, m_emblemInfo.GetBackgroundColor(), m_emblemInfo.GetBorderColor(), m_emblemInfo.GetColor()); + return false; + } + + m_info = fields.Read(8); + m_motd = fields.Read(9); + m_createdDate = fields.Read(10); + m_bankMoney = fields.Read(11); + + byte purchasedTabs = (byte)fields.Read(12); + if (purchasedTabs > GuildConst.MaxBankTabs) + purchasedTabs = GuildConst.MaxBankTabs; + + for (byte i = 0; i < purchasedTabs; ++i) + m_bankTabs.Add(new BankTab(m_id, i)); + + _CreateLogHolders(); + return true; + } + + public void LoadRankFromDB(SQLFields field) + { + RankInfo rankInfo = new RankInfo(m_id); + + rankInfo.LoadFromDB(field); + + m_ranks.Add(rankInfo); + } + + public bool LoadMemberFromDB(SQLFields field) + { + ulong lowguid = field.Read(1); + Member member = new Member(m_id, ObjectGuid.Create(HighGuid.Player, lowguid), field.Read(2)); + if (!member.LoadFromDB(field)) + { + _DeleteMemberFromDB(lowguid); + return false; + } + m_members[member.GetGUID()] = member; + return true; + } + + public void LoadBankRightFromDB(SQLFields field) + { + // tabId rights slots + GuildBankRightsAndSlots rightsAndSlots = new GuildBankRightsAndSlots(field.Read(1), field.Read(3), field.Read(4)); + // rankId + _SetRankBankTabRightsAndSlots(field.Read(2), rightsAndSlots, false); + } + + public bool LoadEventLogFromDB(SQLFields field) + { + if (m_eventLog.CanInsert()) + { + m_eventLog.LoadEvent(new EventLogEntry( + m_id, // guild id + field.Read(1), // guid + field.Read(6), // timestamp + (GuildEventLogTypes)field.Read(2), // event type + field.Read(3), // player guid 1 + field.Read(4), // player guid 2 + field.Read(5))); // rank + return true; + } + return false; + } + + public bool LoadBankEventLogFromDB(SQLFields field) + { + byte dbTabId = field.Read(1); + bool isMoneyTab = (dbTabId == GuildConst.BankMoneyLogsTab); + if (dbTabId < _GetPurchasedTabsSize() || isMoneyTab) + { + byte tabId = isMoneyTab ? (byte)GuildConst.MaxBankTabs : dbTabId; + LogHolder pLog = m_bankEventLog[tabId]; + if (pLog.CanInsert()) + { + uint guid = field.Read(2); + GuildBankEventLogTypes eventType = (GuildBankEventLogTypes)field.Read(3); + if (BankEventLogEntry.IsMoneyEvent(eventType)) + { + if (!isMoneyTab) + { + Log.outError(LogFilter.Guild, "GuildBankEventLog ERROR: MoneyEvent(LogGuid: {0}, Guild: {1}) does not belong to money tab ({2}), ignoring...", guid, m_id, dbTabId); + return false; + } + } + else if (isMoneyTab) + { + Log.outError(LogFilter.Guild, "GuildBankEventLog ERROR: non-money event (LogGuid: {0}, Guild: {1}) belongs to money tab, ignoring...", guid, m_id); + return false; + } + pLog.LoadEvent(new BankEventLogEntry( + m_id, // guild id + guid, // guid + field.Read(8), // timestamp + dbTabId, // tab id + eventType, // event type + field.Read(4), // player guid + field.Read(5), // item or money + field.Read(6), // itam stack count + field.Read(7))); // dest tab id + } + } + return true; + } + + public void LoadGuildNewsLogFromDB(SQLFields field) + { + if (!m_newsLog.CanInsert()) + return; + + var news = new NewsLogEntry( + m_id, // guild id + field.Read(1), // guid + field.Read(6), // timestamp //64 bits? + (GuildNews)field.Read(2), // type + ObjectGuid.Create(HighGuid.Player, field.Read(3)), // player guid + field.Read(4), // Flags + field.Read(5)); // value) + + m_newsLog.LoadEvent(news); + + } + + public void LoadBankTabFromDB(SQLFields field) + { + byte tabId = field.Read(1); + if (tabId >= _GetPurchasedTabsSize()) + Log.outError(LogFilter.Guild, "Invalid tab (tabId: {0}) in guild bank, skipped.", tabId); + else + m_bankTabs[tabId].LoadFromDB(field); + } + + public bool LoadBankItemFromDB(SQLFields field) + { + byte tabId = field.Read(46); + if (tabId >= _GetPurchasedTabsSize()) + { + Log.outError(LogFilter.Guild, "Invalid tab for item (GUID: {0}, id: {1}) in guild bank, skipped.", + field.Read(0), field.Read(1)); + return false; + } + return m_bankTabs[tabId].LoadItemFromDB(field); + } + + public bool Validate() + { + // Validate ranks data + // GUILD RANKS represent a sequence starting from 0 = GUILD_MASTER (ALL PRIVILEGES) to max 9 (lowest privileges). + // The lower rank id is considered higher rank - so promotion does rank-- and demotion does rank++ + // Between ranks in sequence cannot be gaps - so 0, 1, 2, 4 is impossible + // Min ranks count is 2 and max is 10. + bool broken_ranks = false; + byte ranks = _GetRanksSize(); + if (ranks < GuildConst.MinRanks || ranks > GuildConst.MaxRanks) + { + Log.outError(LogFilter.Guild, "Guild {0} has invalid number of ranks, creating new...", m_id); + broken_ranks = true; + } + else + { + for (byte rankId = 0; rankId < ranks; ++rankId) + { + RankInfo rankInfo = GetRankInfo(rankId); + if (rankInfo.GetId() != rankId) + { + Log.outError(LogFilter.Guild, "Guild {0} has broken rank id {1}, creating default set of ranks...", m_id, rankId); + broken_ranks = true; + } + else + { + SQLTransaction trans = new SQLTransaction(); + rankInfo.CreateMissingTabsIfNeeded(_GetPurchasedTabsSize(), trans, true); + DB.Characters.CommitTransaction(trans); + } + } + } + + if (broken_ranks) + { + m_ranks.Clear(); + _CreateDefaultGuildRanks(); + } + + // Validate members' data + foreach (var member in m_members.Values) + if (member.GetRankId() > _GetRanksSize()) + member.ChangeRank(_GetLowestRankId()); + + // Repair the structure of the guild. + // If the guildmaster doesn't exist or isn't member of the guild + // attempt to promote another member. + Member pLeader = GetMember(m_leaderGuid); + if (pLeader == null) + { + DeleteMember(m_leaderGuid); + // If no more members left, disband guild + if (m_members.Empty()) + { + Disband(); + return false; + } + } + else if (!pLeader.IsRank(GuildDefaultRanks.Master)) + _SetLeaderGUID(pLeader); + + _UpdateAccountsNumber(); + return true; + } + + public void BroadcastToGuild(WorldSession session, bool officerOnly, string msg, Language language) + { + if (session != null && session.GetPlayer() != null && _HasRankRight(session.GetPlayer(), officerOnly ? GuildRankRights.OffChatSpeak : GuildRankRights.GChatSpeak)) + { + ChatPkt data = new ChatPkt(); + data.Initialize(officerOnly ? ChatMsg.Officer : ChatMsg.Guild, language, session.GetPlayer(), null, msg); + foreach (var member in m_members.Values) + { + Player player = member.FindPlayer(); + if (player != null) + if (player.GetSession() != null && _HasRankRight(player, officerOnly ? GuildRankRights.OffChatListen : GuildRankRights.GChatListen) && + !player.GetSocial().HasIgnore(session.GetPlayer().GetGUID())) + player.SendPacket(data); + } + } + } + + public void BroadcastAddonToGuild(WorldSession session, bool officerOnly, string msg, string prefix) + { + if (session != null && session.GetPlayer() != null && _HasRankRight(session.GetPlayer(), officerOnly ? GuildRankRights.OffChatSpeak : GuildRankRights.GChatSpeak)) + { + ChatPkt data = new ChatPkt(); + data.Initialize(officerOnly ? ChatMsg.Officer : ChatMsg.Guild, Language.Addon, session.GetPlayer(), null, msg, 0, "", LocaleConstant.enUS, prefix); + foreach (var member in m_members.Values) + { + Player player = member.FindPlayer(); + if (player) + { + if (player.GetSession() != null && _HasRankRight(player, officerOnly ? GuildRankRights.OffChatListen : GuildRankRights.GChatListen) && + !player.GetSocial().HasIgnore(session.GetPlayer().GetGUID()) && player.GetSession().IsAddonRegistered(prefix)) + player.SendPacket(data); + } + } + } + } + + public void BroadcastPacketToRank(ServerPacket packet, byte rankId) + { + packet.Write(); + foreach (var member in m_members.Values) + { + if (member.IsRank(rankId)) + { + Player player = member.FindPlayer(); + if (player != null) + player.SendPacket(packet, false); + } + } + } + + public void BroadcastPacket(ServerPacket packet) + { + packet.Write(); + foreach (var member in m_members.Values) + { + Player player = member.FindPlayer(); + if (player != null) + player.SendPacket(packet, false); + } + } + + public void BroadcastPacketIfTrackingAchievement(ServerPacket packet, uint criteriaId) + { + packet.Write(); + foreach (var member in m_members.Values) + { + if (member.IsTrackingCriteriaId(criteriaId)) + { + Player player = member.FindPlayer(); + if (player) + player.SendPacket(packet, false); + } + } + } + + public void MassInviteToEvent(WorldSession session, uint minLevel, uint maxLevel, uint minRank) + { + CalendarEventInitialInvites packet = new CalendarEventInitialInvites(); + + foreach (var member in m_members.Values) + { + // not sure if needed, maybe client checks it as well + if (packet.Invites.Count >= SharedConst.CalendarMaxInvites) + { + Player player = session.GetPlayer(); + if (player != null) + Global.CalendarMgr.SendCalendarCommandResult(player.GetGUID(), CalendarError.InvitesExceeded); + return; + } + + uint level = Player.GetLevelFromDB(member.GetGUID()); + + if (member.GetGUID() != session.GetPlayer().GetGUID() && level >= minLevel && level <= maxLevel && member.IsRankNotLower(minRank)) + packet.Invites.Add(new CalendarEventInitialInviteInfo(member.GetGUID(), (byte)level)); + } + + session.SendPacket(packet); + } + + public bool AddMember(ObjectGuid guid, byte rankId = GuildConst.RankNone) + { + Player player = Global.ObjAccessor.FindPlayer(guid); + // Player cannot be in guild + if (player != null) + { + if (player.GetGuildId() != 0) + return false; + } + else if (Player.GetGuildIdFromDB(guid) != 0) + return false; + + // Remove all player signs from another petitions + // This will be prevent attempt to join many guilds and corrupt guild data integrity + Player.RemovePetitionsAndSigns(guid); + + ulong lowguid = guid.GetCounter(); + + // If rank was not passed, assign lowest possible rank + if (rankId == GuildConst.RankNone) + rankId = _GetLowestRankId(); + + Member member = new Member(m_id, guid, rankId); + string name = ""; + if (player != null) + { + m_members[guid] = member; + player.SetInGuild(m_id); + player.SetGuildIdInvited(0); + player.SetRank(rankId); + player.SetGuildLevel(GetLevel()); + SendLoginInfo(player.GetSession()); + name = player.GetName(); + } + else + { + member.ResetFlags(); + + bool ok = false; + // Player must exist + PreparedStatement stmt = DB.Characters.GetPreparedStatement(CharStatements.SEL_CHAR_DATA_FOR_GUILD); + stmt.AddValue(0, lowguid); + SQLResult result = DB.Characters.Query(stmt); + if (!result.IsEmpty()) + { + name = result.Read(0); + member.SetStats( + name, + result.Read(1), + (Class)result.Read(2), + (Gender)result.Read(3), + result.Read(4), + result.Read(5), + 0); + + ok = member.CheckStats(); + } + + if (!ok) + return false; + + m_members[guid] = member; + } + + SQLTransaction trans = new SQLTransaction(); + member.SaveToDB(trans); + DB.Characters.CommitTransaction(trans); + + _UpdateAccountsNumber(); + _LogEvent(GuildEventLogTypes.JoinGuild, lowguid); + + GuildEventPlayerJoined joinNotificationPacket = new GuildEventPlayerJoined(); + joinNotificationPacket.Guid = guid; + joinNotificationPacket.Name = name; + joinNotificationPacket.VirtualRealmAddress = Global.WorldMgr.GetVirtualRealmAddress(); + BroadcastPacket(joinNotificationPacket); + + Global.GuildFinderMgr.RemoveAllMembershipRequestsFromPlayer(guid); + + // Call scripts if member was succesfully added (and stored to database) + Global.ScriptMgr.OnGuildAddMember(this, player, rankId); + + return true; + } + + public void DeleteMember(ObjectGuid guid, bool isDisbanding = false, bool isKicked = false, bool canDeleteGuild = false) + { + Player player = Global.ObjAccessor.FindPlayer(guid); + + // Guild master can be deleted when loading guild and guid doesn't exist in characters table + // or when he is removed from guild by gm command + if (m_leaderGuid == guid && !isDisbanding) + { + Member oldLeader = null; + Member newLeader = null; + foreach (var member in m_members) + { + if (member.Key == guid) + oldLeader = member.Value; + else if (newLeader == null || newLeader.GetRankId() > member.Value.GetRankId()) + newLeader = member.Value; + } + + if (newLeader == null) + { + Disband(); + return; + } + + _SetLeaderGUID(newLeader); + + // If player not online data in data field will be loaded from guild tabs no need to update it !! + Player newLeaderPlayer = newLeader.FindPlayer(); + if (newLeaderPlayer) + newLeaderPlayer.SetRank(GuildDefaultRanks.Master); + + // If leader does not exist (at guild loading with deleted leader) do not send broadcasts + if (oldLeader != null) + { + SendEventNewLeader(newLeader, oldLeader, true); + SendEventPlayerLeft(player); + } + } + // Call script on remove before member is actually removed from guild (and database) + Global.ScriptMgr.OnGuildRemoveMember(this, player, isDisbanding, isKicked); + + m_members.Remove(guid); + + // If player not online data in data field will be loaded from guild tabs no need to update it !! + if (player != null) + { + player.SetInGuild(0); + player.SetRank(0); + player.SetGuildLevel(0); + + foreach (var entry in CliDB.GuildPerkSpellsStorage.Values) + player.RemoveSpell(entry.SpellID, false, false); + } + + _DeleteMemberFromDB(guid.GetCounter()); + if (!isDisbanding) + _UpdateAccountsNumber(); + } + + public bool ChangeMemberRank(ObjectGuid guid, byte newRank) + { + if (newRank <= _GetLowestRankId()) // Validate rank (allow only existing ranks) + { + Member member = GetMember(guid); + if (member != null) + { + member.ChangeRank(newRank); + return true; + } + } + return false; + } + + public bool IsMember(ObjectGuid guid) + { + return m_members.ContainsKey(guid); + } + + public void SwapItems(Player player, byte tabId, byte slotId, byte destTabId, byte destSlotId, uint splitedAmount) + { + if (tabId >= _GetPurchasedTabsSize() || slotId >= GuildConst.MaxBankSlots || + destTabId >= _GetPurchasedTabsSize() || destSlotId >= GuildConst.MaxBankSlots) + return; + + if (tabId == destTabId && slotId == destSlotId) + return; + + BankMoveItemData from = new BankMoveItemData(this, player, tabId, slotId); + BankMoveItemData to = new BankMoveItemData(this, player, destTabId, destSlotId); + _MoveItems(from, to, splitedAmount); + } + + public void SwapItemsWithInventory(Player player, bool toChar, byte tabId, byte slotId, byte playerBag, byte playerSlotId, uint splitedAmount) + { + if ((slotId >= GuildConst.MaxBankSlots && slotId != ItemConst.NullSlot) || tabId >= _GetPurchasedTabsSize()) + return; + + BankMoveItemData bankData = new BankMoveItemData(this, player, tabId, slotId); + PlayerMoveItemData charData = new PlayerMoveItemData(this, player, playerBag, playerSlotId); + if (toChar) + _MoveItems(bankData, charData, splitedAmount); + else + _MoveItems(charData, bankData, splitedAmount); + } + + public void SetBankTabText(byte tabId, string text) + { + BankTab pTab = GetBankTab(tabId); + if (pTab != null) + { + pTab.SetText(text); + pTab.SendText(this); + + GuildEventTabTextChanged eventPacket = new GuildEventTabTextChanged(); + eventPacket.Tab = tabId; + BroadcastPacket(eventPacket); + } + } + + // Private methods + void _CreateLogHolders() + { + m_eventLog = new LogHolder(100); + m_newsLog = new LogHolder(250); + for (byte tabId = 0; tabId <= GuildConst.MaxBankTabs; ++tabId) + m_bankEventLog[tabId] = new LogHolder(25); + } + + void _CreateNewBankTab() + { + byte tabId = _GetPurchasedTabsSize(); // Next free id + m_bankTabs.Add(new BankTab(m_id, tabId)); + + SQLTransaction trans = new SQLTransaction(); + + PreparedStatement stmt = DB.Characters.GetPreparedStatement(CharStatements.DEL_GUILD_BANK_TAB); + stmt.AddValue(0, m_id); + stmt.AddValue(1, tabId); + trans.Append(stmt); + + stmt = DB.Characters.GetPreparedStatement(CharStatements.INS_GUILD_BANK_TAB); + stmt.AddValue(0, m_id); + stmt.AddValue(1, tabId); + trans.Append(stmt); + + ++tabId; + foreach (var rank in m_ranks) + rank.CreateMissingTabsIfNeeded(tabId, trans, false); + + DB.Characters.CommitTransaction(trans); + } + + void _CreateDefaultGuildRanks(LocaleConstant loc = LocaleConstant.enUS) + { + PreparedStatement stmt = DB.Characters.GetPreparedStatement(CharStatements.DEL_GUILD_RANKS); + stmt.AddValue(0, m_id); + DB.Characters.Execute(stmt); + + stmt = DB.Characters.GetPreparedStatement(CharStatements.DEL_GUILD_BANK_RIGHTS); + stmt.AddValue(0, m_id); + DB.Characters.Execute(stmt); + + _CreateRank(Global.ObjectMgr.GetCypherString( CypherStrings.GuildMaster, loc), GuildRankRights.All); + _CreateRank(Global.ObjectMgr.GetCypherString(CypherStrings.GuildOfficer, loc), GuildRankRights.All); + _CreateRank(Global.ObjectMgr.GetCypherString(CypherStrings.GuildVeteran, loc), GuildRankRights.GChatListen | GuildRankRights.GChatSpeak); + _CreateRank(Global.ObjectMgr.GetCypherString(CypherStrings.GuildMember, loc), GuildRankRights.GChatListen | GuildRankRights.GChatSpeak); + _CreateRank(Global.ObjectMgr.GetCypherString(CypherStrings.GuildInitiate, loc), GuildRankRights.GChatListen | GuildRankRights.GChatSpeak); + } + + bool _CreateRank(string name, GuildRankRights rights) + { + byte newRankId = _GetRanksSize(); + if (newRankId >= GuildConst.MaxRanks) + return false; + + // Ranks represent sequence 0, 1, 2, ... where 0 means guildmaster + RankInfo info = new RankInfo(m_id, newRankId, name, rights, 0); + m_ranks.Add(info); + + SQLTransaction trans = new SQLTransaction(); + info.CreateMissingTabsIfNeeded(_GetPurchasedTabsSize(), trans); + info.SaveToDB(trans); + DB.Characters.CommitTransaction(trans); + + return true; + } + + void _UpdateAccountsNumber() + { + // We use a set to be sure each element will be unique + List accountsIdSet = new List(); + foreach (var member in m_members.Values) + accountsIdSet.Add(member.GetAccountId()); + + m_accountsNumber = (uint)accountsIdSet.Count; + } + + bool _IsLeader(Player player) + { + if (player.GetGUID() == m_leaderGuid) + return true; + + Member member = GetMember(player.GetGUID()); + if (member != null) + return member.IsRank(GuildDefaultRanks.Master); + return false; + } + + void _DeleteBankItems(SQLTransaction trans, bool removeItemsFromDB) + { + for (byte tabId = 0; tabId < _GetPurchasedTabsSize(); ++tabId) + { + m_bankTabs[tabId].Delete(trans, removeItemsFromDB); + m_bankTabs[tabId] = null; + } + m_bankTabs.Clear(); + } + + bool _ModifyBankMoney(SQLTransaction trans, ulong amount, bool add) + { + if (add) + m_bankMoney += amount; + else + { + // Check if there is enough money in bank. + if (m_bankMoney < amount) + return false; + m_bankMoney -= amount; + } + + PreparedStatement stmt = DB.Characters.GetPreparedStatement(CharStatements.UPD_GUILD_BANK_MONEY); + stmt.AddValue(0, m_bankMoney); + stmt.AddValue(1, m_id); + trans.Append(stmt); + return true; + } + + void _SetLeaderGUID(Member pLeader) + { + if (pLeader == null) + return; + + m_leaderGuid = pLeader.GetGUID(); + pLeader.ChangeRank(GuildDefaultRanks.Master); + + PreparedStatement stmt = DB.Characters.GetPreparedStatement(CharStatements.UPD_GUILD_LEADER); + stmt.AddValue(0, m_leaderGuid.GetCounter()); + stmt.AddValue(1, m_id); + DB.Characters.Execute(stmt); + } + + void _SetRankBankMoneyPerDay(uint rankId, uint moneyPerDay) + { + RankInfo rankInfo = GetRankInfo(rankId); + if (rankInfo != null) + rankInfo.SetBankMoneyPerDay(moneyPerDay); + } + + void _SetRankBankTabRightsAndSlots(uint rankId, GuildBankRightsAndSlots rightsAndSlots, bool saveToDB = true) + { + if (rightsAndSlots.GetTabId() >= _GetPurchasedTabsSize()) + return; + + RankInfo rankInfo = GetRankInfo(rankId); + if (rankInfo != null) + rankInfo.SetBankTabSlotsAndRights(rightsAndSlots, saveToDB); + } + + string _GetRankName(byte rankId) + { + RankInfo rankInfo = GetRankInfo(rankId); + if (rankInfo != null) + return rankInfo.GetName(); + return ""; + } + + GuildRankRights _GetRankRights(byte rankId) + { + RankInfo rankInfo = GetRankInfo(rankId); + if (rankInfo != null) + return rankInfo.GetRights(); + return 0; + } + + int _GetRankBankMoneyPerDay(byte rankId) + { + RankInfo rankInfo = GetRankInfo(rankId); + if (rankInfo != null) + return rankInfo.GetBankMoneyPerDay(); + return 0; + } + + int _GetRankBankTabSlotsPerDay(byte rankId, byte tabId) + { + if (tabId < _GetPurchasedTabsSize()) + { + RankInfo rankInfo = GetRankInfo(rankId); + if (rankInfo != null) + return rankInfo.GetBankTabSlotsPerDay(tabId); + } + return 0; + } + + GuildBankRights _GetRankBankTabRights(byte rankId, byte tabId) + { + RankInfo rankInfo = GetRankInfo(rankId); + if (rankInfo != null) + return rankInfo.GetBankTabRights(tabId); + return 0; + } + + int _GetMemberRemainingSlots(Member member, byte tabId) + { + if (member != null) + { + byte rankId = member.GetRankId(); + if (rankId == GuildDefaultRanks.Master) + return GuildConst.WithdrawSlotUnlimited; + if ((_GetRankBankTabRights(rankId, tabId) & GuildBankRights.ViewTab) != 0) + { + int remaining = _GetRankBankTabSlotsPerDay(rankId, tabId) - member.GetBankWithdrawValue(tabId); + if (remaining > 0) + return remaining; + } + } + return 0; + } + + int _GetMemberRemainingMoney(Member member) + { + if (member != null) + { + byte rankId = member.GetRankId(); + if (rankId == GuildDefaultRanks.Master) + return GuildConst.WithdrawMoneyUnlimited; + + if ((_GetRankRights(rankId) & (GuildRankRights.WithdrawRepair | GuildRankRights.WithdrawGold)) != 0) + { + int remaining = _GetRankBankMoneyPerDay(rankId) - member.GetBankWithdrawValue(GuildConst.MaxBankTabs); + if (remaining > 0) + return remaining; + } + } + return 0; + } + + void _UpdateMemberWithdrawSlots(SQLTransaction trans, ObjectGuid guid, byte tabId) + { + Member member = GetMember(guid); + if (member != null) + { + byte rankId = member.GetRankId(); + if (rankId != GuildDefaultRanks.Master + && member.GetBankWithdrawValue(tabId) < _GetRankBankTabSlotsPerDay(rankId, tabId)) + member.UpdateBankWithdrawValue(trans, tabId, 1); + } + } + + bool _MemberHasTabRights(ObjectGuid guid, byte tabId, GuildBankRights rights) + { + Member member = GetMember(guid); + if (member != null) + { + // Leader always has full rights + if (member.IsRank(GuildDefaultRanks.Master) || m_leaderGuid == guid) + return true; + return (_GetRankBankTabRights(member.GetRankId(), tabId) & rights) == rights; + } + return false; + } + + void _LogEvent(GuildEventLogTypes eventType, ulong playerGuid1, ulong playerGuid2 = 0, byte newRank = 0) + { + SQLTransaction trans = new SQLTransaction(); + m_eventLog.AddEvent(trans, new EventLogEntry(m_id, m_eventLog.GetNextGUID(), eventType, playerGuid1, playerGuid2, newRank)); + DB.Characters.CommitTransaction(trans); + + Global.ScriptMgr.OnGuildEvent(this, (byte)eventType, playerGuid1, playerGuid2, newRank); + } + + void _LogBankEvent(SQLTransaction trans, GuildBankEventLogTypes eventType, byte tabId, ulong lowguid, uint itemOrMoney, ushort itemStackCount = 0, byte destTabId = 0) + { + if (tabId > GuildConst.MaxBankTabs) + return; + + // not logging moves within the same tab + if (eventType == GuildBankEventLogTypes.MoveItem && tabId == destTabId) + return; + + byte dbTabId = tabId; + if (BankEventLogEntry.IsMoneyEvent(eventType)) + { + tabId = GuildConst.MaxBankTabs; + dbTabId = GuildConst.BankMoneyLogsTab; + } + LogHolder pLog = m_bankEventLog[tabId]; + pLog.AddEvent(trans, new BankEventLogEntry(m_id, pLog.GetNextGUID(), eventType, dbTabId, lowguid, itemOrMoney, itemStackCount, destTabId)); + + Global.ScriptMgr.OnGuildBankEvent(this, (byte)eventType, tabId, lowguid, itemOrMoney, itemStackCount, destTabId); + } + + Item _GetItem(byte tabId, byte slotId) + { + BankTab tab = GetBankTab(tabId); + if (tab != null) + return tab.GetItem(slotId); + return null; + } + + void _RemoveItem(SQLTransaction trans, byte tabId, byte slotId) + { + BankTab pTab = GetBankTab(tabId); + if (pTab != null) + pTab.SetItem(trans, slotId, null); + } + + void _MoveItems(MoveItemData pSrc, MoveItemData pDest, uint splitedAmount) + { + // 1. Initialize source item + if (!pSrc.InitItem()) + return; // No source item + + // 2. Check source item + if (!pSrc.CheckItem(ref splitedAmount)) + return; // Source item or splited amount is invalid + + // 3. Check destination rights + if (!pDest.HasStoreRights(pSrc)) + return; // Player has no rights to store item in destination + + // 4. Check source withdraw rights + if (!pSrc.HasWithdrawRights(pDest)) + return; // Player has no rights to withdraw items from source + + // 5. Check split + if (splitedAmount != 0) + { + // 5.1. Clone source item + if (!pSrc.CloneItem(splitedAmount)) + return; // Item could not be cloned + + // 5.2. Move splited item to destination + _DoItemsMove(pSrc, pDest, true, splitedAmount); + } + else // 6. No split + { + // 6.1. Try to merge items in destination (pDest.GetItem() == NULL) + if (!_DoItemsMove(pSrc, pDest, false)) // Item could not be merged + { + // 6.2. Try to swap items + // 6.2.1. Initialize destination item + if (!pDest.InitItem()) + return; + + // 6.2.2. Check rights to store item in source (opposite direction) + if (!pSrc.HasStoreRights(pDest)) + return; // Player has no rights to store item in source (opposite direction) + + if (!pDest.HasWithdrawRights(pSrc)) + return; // Player has no rights to withdraw item from destination (opposite direction) + + // 6.2.3. Swap items (pDest.GetItem() != NULL) + _DoItemsMove(pSrc, pDest, true); + } + } + // 7. Send changes + _SendBankContentUpdate(pSrc, pDest); + } + + bool _DoItemsMove(MoveItemData pSrc, MoveItemData pDest, bool sendError, uint splitedAmount = 0) + { + Item pDestItem = pDest.GetItem(); + bool swap = (pDestItem != null); + + Item pSrcItem = pSrc.GetItem(splitedAmount != 0); + // 1. Can store source item in destination + if (!pDest.CanStore(pSrcItem, swap, sendError)) + return false; + + // 2. Can store destination item in source + if (swap) + if (!pSrc.CanStore(pDestItem, true, true)) + return false; + + // GM LOG (@todo move to scripts) + pDest.LogAction(pSrc); + if (swap) + pSrc.LogAction(pDest); + + SQLTransaction trans = new SQLTransaction(); + // 3. Log bank events + pDest.LogBankEvent(trans, pSrc, pSrcItem.GetCount()); + if (swap) + pSrc.LogBankEvent(trans, pDest, pDestItem.GetCount()); + + // 4. Remove item from source + pSrc.RemoveItem(trans, pDest, splitedAmount); + + // 5. Remove item from destination + if (swap) + pDest.RemoveItem(trans, pSrc); + + // 6. Store item in destination + pDest.StoreItem(trans, pSrcItem); + + // 7. Store item in source + if (swap) + pSrc.StoreItem(trans, pDestItem); + + DB.Characters.CommitTransaction(trans); + return true; + } + + void _SendBankContentUpdate(MoveItemData pSrc, MoveItemData pDest) + { + Contract.Assert(pSrc.IsBank() || pDest.IsBank()); + + byte tabId = 0; + List slots = new List(); + if (pSrc.IsBank()) // B . + { + tabId = pSrc.GetContainer(); + slots.Insert(0, pSrc.GetSlotId()); + if (pDest.IsBank()) // B . B + { + // Same tab - add destination slots to collection + if (pDest.GetContainer() == pSrc.GetContainer()) + pDest.CopySlots(slots); + else // Different tabs - send second message + { + List destSlots = new List(); + pDest.CopySlots(destSlots); + _SendBankContentUpdate(pDest.GetContainer(), destSlots); + } + } + } + else if (pDest.IsBank()) // C . B + { + tabId = pDest.GetContainer(); + pDest.CopySlots(slots); + } + + _SendBankContentUpdate(tabId, slots); + } + + void _SendBankContentUpdate(byte tabId, List slots) + { + BankTab tab = GetBankTab(tabId); + if (tab != null) + { + GuildBankQueryResults packet = new GuildBankQueryResults(); + packet.FullUpdate = true; // @todo + packet.Tab = tabId; + packet.Money = m_bankMoney; + + foreach (var slot in slots) + { + Item tabItem = tab.GetItem(slot); + + GuildBankItemInfo itemInfo = new GuildBankItemInfo(); + + itemInfo.Slot = slot; + itemInfo.Item.ItemID = tabItem ? tabItem.GetEntry() : 0; + itemInfo.Count = (int)(tabItem ? tabItem.GetCount() : 0); + itemInfo.Charges = tabItem ? Math.Abs(tabItem.GetSpellCharges()) : 0; + itemInfo.OnUseEnchantmentID = 0/*int32(tabItem->GetItemSuffixFactor())*/; + itemInfo.Flags = 0; + itemInfo.Locked = false; + + if (tabItem != null) + { + byte i = 0; + foreach (ItemDynamicFieldGems gemData in tabItem.GetGems()) + { + if (gemData.ItemId != 0) + { + ItemGemData gem = new ItemGemData(); + gem.Slot = i; + gem.Item = new ItemInstance(gemData); + itemInfo.SocketEnchant.Add(gem); + } + ++i; + } + } + + packet.ItemInfo.Add(itemInfo); + } + + foreach (var member in m_members.Values) + { + if (_MemberHasTabRights(member.GetGUID(), tabId, GuildBankRights.ViewTab)) + { + Player player = member.FindPlayer(); + if (player != null) + { + packet.WithdrawalsRemaining = _GetMemberRemainingSlots(member, tabId); + player.SendPacket(packet); + } + } + } + } + } + + public void SendBankList(WorldSession session, byte tabId, bool fullUpdate) + { + Member member = GetMember(session.GetPlayer().GetGUID()); + if (member == null) // Shouldn't happen, just in case + return; + + GuildBankQueryResults packet = new GuildBankQueryResults(); + + packet.Money = m_bankMoney; + packet.WithdrawalsRemaining = _GetMemberRemainingSlots(member, tabId); + packet.Tab = tabId; + packet.FullUpdate = fullUpdate; + + // TabInfo + if (fullUpdate) + { + for (byte i = 0; i < _GetPurchasedTabsSize(); ++i) + { + GuildBankTabInfo tabInfo; + tabInfo.TabIndex = i; + tabInfo.Name = m_bankTabs[i].GetName(); + tabInfo.Icon = m_bankTabs[i].GetIcon(); + packet.TabInfo.Add(tabInfo); + } + + } + + if (fullUpdate && _MemberHasTabRights(session.GetPlayer().GetGUID(), tabId, GuildBankRights.ViewTab)) + { + BankTab tab = GetBankTab(tabId); + if (tab != null) + { + for (byte slotId = 0; slotId < GuildConst.MaxBankSlots; ++slotId) + { + Item tabItem = tab.GetItem(slotId); + if (tabItem) + { + GuildBankItemInfo itemInfo = new GuildBankItemInfo(); + + itemInfo.Slot = slotId; + itemInfo.Item.ItemID = tabItem.GetEntry(); + itemInfo.Count = (int)tabItem.GetCount(); + itemInfo.Charges = Math.Abs(tabItem.GetSpellCharges()); + itemInfo.EnchantmentID = tabItem.GetItemRandomPropertyId(); // verify that... + itemInfo.OnUseEnchantmentID = 0/*int32(tabItem->GetItemSuffixFactor())*/; + itemInfo.Flags = 0; + + byte i = 0; + foreach (ItemDynamicFieldGems gemData in tabItem.GetGems()) + { + if (gemData.ItemId != 0) + { + ItemGemData gem = new ItemGemData(); + gem.Slot = i; + gem.Item = new ItemInstance(gemData); + itemInfo.SocketEnchant.Add(gem); + } + ++i; + } + + itemInfo.Locked = false; + + packet.ItemInfo.Add(itemInfo); + } + } + } + } + + session.SendPacket(packet); + } + + void SendGuildRanksUpdate(ObjectGuid setterGuid, ObjectGuid targetGuid, uint rank) + { + Member member = GetMember(targetGuid); + Contract.Assert(member != null); + + GuildSendRankChange rankChange = new GuildSendRankChange(); + rankChange.Officer = setterGuid; + rankChange.Other = targetGuid; + rankChange.RankID = rank; + rankChange.Promote = (rank < member.GetRankId()); + BroadcastPacket(rankChange); + + member.ChangeRank(rank); + + Log.outDebug(LogFilter.Network, "SMSG_GUILD_RANKS_UPDATE [Broadcast] Target: {0}, Issuer: {1}, RankId: {2}", targetGuid.ToString(), setterGuid.ToString(), rank); + } + + public void ResetTimes(bool weekly) + { + foreach (var member in m_members.Values) + { + member.ResetValues(weekly); + Player player = member.FindPlayer(); + if (player != null) + { + // tells the client to request bank withdrawal limit + player.SendPacket(new GuildMemberDailyReset()); + } + } + } + + public void AddGuildNews(GuildNews type, ObjectGuid guid, uint flags, uint value) + { + NewsLogEntry news = new NewsLogEntry(m_id, m_newsLog.GetNextGUID(), type, guid, flags, value); + + SQLTransaction trans = new SQLTransaction(); + m_newsLog.AddEvent(trans, news); + DB.Characters.CommitTransaction(trans); + + GuildNewsPkt newsPacket = new GuildNewsPkt(); + news.WritePacket(newsPacket); + BroadcastPacket(newsPacket); + } + + bool HasAchieved(uint achievementId) + { + return m_achievementSys.HasAchieved(achievementId); + } + + public void UpdateCriteria(CriteriaTypes type, ulong miscValue1, ulong miscValue2, ulong miscValue3, Unit unit, Player player) + { + m_achievementSys.UpdateCriteria(type, miscValue1, miscValue2, miscValue3, unit, player); + } + + public void HandleNewsSetSticky(WorldSession session, uint newsId, bool sticky) + { + var news = (NewsLogEntry)m_newsLog.GetGuildLog().Find(p => p.GetGUID() == newsId); + + if (news == null) + { + Log.outDebug(LogFilter.Guild, "HandleNewsSetSticky: [{0}] requested unknown newsId {1} - Sticky: {2}", + session.GetPlayerInfo(), newsId, sticky); + return; + } + + news.SetSticky(sticky); + + Log.outDebug(LogFilter.Guild, "HandleNewsSetSticky: [{0}] chenged newsId {1} sticky to {2}", session.GetPlayerInfo(), newsId, sticky); + + GuildNewsPkt newsPacket = new GuildNewsPkt(); + news.WritePacket(newsPacket); + session.SendPacket(newsPacket); + } + + public ulong GetId() { return m_id; } + public ObjectGuid GetGUID() { return ObjectGuid.Create(HighGuid.Guild, m_id); } + public ObjectGuid GetLeaderGUID() { return m_leaderGuid; } + public string GetName() { return m_name; } + public string GetMOTD() { return m_motd; } + public string GetInfo() { return m_info; } + public long GetCreatedDate() { return m_createdDate; } + public ulong GetBankMoney() { return m_bankMoney; } + + public void BroadcastWorker(IDoWork _do, Player except = null) + { + foreach (var member in m_members.Values) + { + Player player = member.FindPlayer(); + if (player != null) + if (player != except) + _do.Invoke(player); + } + } + + public int GetMembersCount() { return m_members.Count; } + + public GuildAchievementMgr GetAchievementMgr() { return m_achievementSys; } + + // Pre-6.x guild leveling + public byte GetLevel() { return GuildConst.OldMaxLevel; } + + public EmblemInfo GetEmblemInfo() { return m_emblemInfo; } + + byte _GetRanksSize() { return (byte)m_ranks.Count; } + + RankInfo GetRankInfo(uint rankId) { return rankId < _GetRanksSize() ? m_ranks[(int)rankId] : null; } + + bool _HasRankRight(Player player, GuildRankRights right) + { + if (player != null) + { + Member member = GetMember(player.GetGUID()); + if (member != null) + return (_GetRankRights(member.GetRankId()) & right) != GuildRankRights.None; + return false; + } + return false; + } + + byte _GetLowestRankId() { return (byte)(m_ranks.Count - 1); } + + byte _GetPurchasedTabsSize() { return (byte)m_bankTabs.Count; } + + BankTab GetBankTab(byte tabId) { return tabId < m_bankTabs.Count ? m_bankTabs[tabId] : null; } + + public Member GetMember(ObjectGuid guid) + { + return m_members.LookupByKey(guid); + } + + public Member GetMember(string name) + { + foreach (var member in m_members.Values) + if (member.GetName() == name) + return member; + + return null; + } + + void _DeleteMemberFromDB(ulong lowguid) + { + PreparedStatement stmt = DB.Characters.GetPreparedStatement(CharStatements.DEL_GUILD_MEMBER); + stmt.AddValue(0, lowguid); + DB.Characters.Execute(stmt); + } + + uint _GetGuildBankTabPrice(byte tabId) + { + switch (tabId) + { + case 0: return 100; + case 1: return 250; + case 2: return 500; + case 3: return 1000; + case 4: return 2500; + case 5: return 5000; + default: return 0; + } + } + + public static void SendCommandResult(WorldSession session, GuildCommandType type, GuildCommandError errCode, string param = "") + { + GuildCommandResult resultPacket = new GuildCommandResult(); + resultPacket.Command = type; + resultPacket.Result = errCode; + resultPacket.Name = param; + session.SendPacket(resultPacket); + } + + public static void SendSaveEmblemResult(WorldSession session, GuildEmblemError errCode) + { + PlayerSaveGuildEmblem saveResponse = new PlayerSaveGuildEmblem(); + saveResponse.Error = errCode; + session.SendPacket(saveResponse); + } + + #region Fields + ulong m_id; + string m_name; + ObjectGuid m_leaderGuid; + string m_motd; + string m_info; + long m_createdDate; + + EmblemInfo m_emblemInfo = new EmblemInfo(); + uint m_accountsNumber; + ulong m_bankMoney; + + List m_ranks = new List(); + Dictionary m_members = new Dictionary(); + List m_bankTabs = new List(); + + // These are actually ordered lists. The first element is the oldest entry. + LogHolder m_eventLog; + LogHolder[] m_bankEventLog = new LogHolder[GuildConst.MaxBankTabs + 1]; + LogHolder m_newsLog; + GuildAchievementMgr m_achievementSys; + #endregion + + public static implicit operator bool(Guild guild) + { + return guild != null; + } + + #region Classes + public class Member + { + public Member(ulong guildId, ObjectGuid guid, byte rankId) + { + m_guildId = guildId; + m_guid = guid; + m_zoneId = 0; + m_level = 0; + m_class = 0; + m_flags = GuildMemberFlags.None; + m_logoutTime = (ulong)Time.UnixTime; + m_accountId = 0; + m_rankId = rankId; + m_achievementPoints = 0; + m_totalActivity = 0; + m_weekActivity = 0; + m_totalReputation = 0; + m_weekReputation = 0; + } + + public void SetStats(Player player) + { + m_name = player.GetName(); + m_level = (byte)player.getLevel(); + m_class = player.GetClass(); + _gender = (Gender)player.GetByteValue(PlayerFields.Bytes3, PlayerFieldOffsets.Bytes3OffsetGender); + m_zoneId = player.GetZoneId(); + m_accountId = player.GetSession().GetAccountId(); + m_achievementPoints = player.GetAchievementPoints(); + } + + public void SetStats(string name, byte level, Class _class, Gender gender, uint zoneId, uint accountId, uint reputation) + { + m_name = name; + m_level = level; + m_class = _class; + _gender = gender; + m_zoneId = zoneId; + m_accountId = accountId; + m_totalReputation = reputation; + } + + public void SetPublicNote(string publicNote) + { + if (m_publicNote == publicNote) + return; + + m_publicNote = publicNote; + + PreparedStatement stmt = DB.Characters.GetPreparedStatement(CharStatements.UPD_GUILD_MEMBER_PNOTE); + stmt.AddValue(0, publicNote); + stmt.AddValue(1, m_guid.GetCounter()); + DB.Characters.Execute(stmt); + } + + public void SetOfficerNote(string officerNote) + { + if (m_officerNote == officerNote) + return; + + m_officerNote = officerNote; + + PreparedStatement stmt = DB.Characters.GetPreparedStatement(CharStatements.UPD_GUILD_MEMBER_OFFNOTE); + stmt.AddValue(0, officerNote); + stmt.AddValue(1, m_guid.GetCounter()); + DB.Characters.Execute(stmt); + } + + public void ChangeRank(uint newRank) + { + m_rankId = (byte)newRank; + + // Update rank information in player's field, if he is online. + Player player = FindPlayer(); + if (player != null) + player.SetRank((byte)newRank); + + PreparedStatement stmt = DB.Characters.GetPreparedStatement(CharStatements.UPD_GUILD_MEMBER_RANK); + stmt.AddValue(0, newRank); + stmt.AddValue(1, m_guid.GetCounter()); + DB.Characters.Execute(stmt); + } + + public void SaveToDB(SQLTransaction trans) + { + PreparedStatement stmt = DB.Characters.GetPreparedStatement(CharStatements.INS_GUILD_MEMBER); + stmt.AddValue(0, m_guildId); + stmt.AddValue(1, m_guid.GetCounter()); + stmt.AddValue(2, m_rankId); + stmt.AddValue(3, m_publicNote); + stmt.AddValue(4, m_officerNote); + DB.Characters.ExecuteOrAppend(trans, stmt); + } + + public bool LoadFromDB(SQLFields field) + { + m_publicNote = field.Read(3); + m_officerNote = field.Read(4); + + for (byte i = 0; i <= GuildConst.MaxBankTabs; ++i) + m_bankWithdraw[i] = field.Read(5 + i); + + SetStats(field.Read(14), + field.Read(15), // characters.level + (Class)field.Read(16), // characters.class + (Gender)field.Read(17), // characters.gender + field.Read(18), // characters.zone + field.Read(19), // characters.account + 0); + m_logoutTime = field.Read(20); // characters.logout_time + m_totalActivity = 0; + m_weekActivity = 0; + m_weekReputation = 0; + + if (!CheckStats()) + return false; + + if (m_zoneId == 0) + { + Log.outError(LogFilter.Guild, "Player ({0}) has broken zone-data", m_guid.ToString()); + m_zoneId = Player.GetZoneIdFromDB(m_guid); + } + ResetFlags(); + return true; + } + + public bool CheckStats() + { + if (m_level < 1) + { + Log.outError(LogFilter.Guild, "Player ({0}) has a broken data in field `characters`.`level`, deleting him from guild!", m_guid.ToString()); + return false; + } + + if (m_class < Class.Warrior || m_class >= Class.Max) + { + Log.outError(LogFilter.Guild, "Player ({0}) has a broken data in field `characters`.`class`, deleting him from guild!", m_guid.ToString()); + return false; + } + return true; + } + + public void UpdateBankWithdrawValue(SQLTransaction trans, byte tabId, uint amount) + { + m_bankWithdraw[tabId] += (int)amount; + + PreparedStatement stmt = DB.Characters.GetPreparedStatement(CharStatements.INS_GUILD_MEMBER_WITHDRAW); + stmt.AddValue(0, m_guid.GetCounter()); + for (byte i = 0; i <= GuildConst.MaxBankTabs; ) + { + int withdraw = m_bankWithdraw[i++]; + stmt.AddValue(i, withdraw); + } + + DB.Characters.ExecuteOrAppend(trans, stmt); + } + + public void ResetValues(bool weekly = false) + { + for (byte tabId = 0; tabId <= GuildConst.MaxBankTabs; ++tabId) + m_bankWithdraw[tabId] = 0; + + if (weekly) + { + m_weekActivity = 0; + m_weekReputation = 0; + } + } + + public int GetBankWithdrawValue(byte tabId) + { + // Guild master has unlimited amount. + if (IsRank(GuildDefaultRanks.Master)) + return tabId == GuildConst.MaxBankTabs ? GuildConst.WithdrawMoneyUnlimited : GuildConst.WithdrawSlotUnlimited; + + return m_bankWithdraw[tabId]; + } + + public void SetZoneId(uint id) { m_zoneId = id; } + + public void SetAchievementPoints(uint val) { m_achievementPoints = val; } + + public void SetLevel(uint var) { m_level = (byte)var; } + + public void AddFlag(GuildMemberFlags var) { m_flags |= var; } + + public void RemoveFlag(GuildMemberFlags var) { m_flags &= ~var; } + + public void ResetFlags() { m_flags = GuildMemberFlags.None; } + + public ObjectGuid GetGUID() { return m_guid; } + public string GetName() { return m_name; } + public uint GetAccountId() { return m_accountId; } + public byte GetRankId() { return m_rankId; } + public ulong GetLogoutTime() { return m_logoutTime; } + public string GetPublicNote() { return m_publicNote; } + public string GetOfficerNote() { return m_officerNote; } + public Class GetClass() { return m_class; } + public Gender GetGender() { return _gender; } + public byte GetLevel() { return m_level; } + public GuildMemberFlags GetFlags() { return m_flags; } + public uint GetZoneId() { return m_zoneId; } + public uint GetAchievementPoints() { return m_achievementPoints; } + public ulong GetTotalActivity() { return m_totalActivity; } + public ulong GetWeekActivity() { return m_weekActivity; } + public uint GetTotalReputation() { return m_totalReputation; } + public uint GetWeekReputation() { return m_weekReputation; } + + public List GetTrackedCriteriaIds() { return m_trackedCriteriaIds; } + public void SetTrackedCriteriaIds(List criteriaIds) { m_trackedCriteriaIds = criteriaIds; } + public bool IsTrackingCriteriaId(uint criteriaId) { return m_trackedCriteriaIds.Contains(criteriaId); } + public bool IsOnline() { return m_flags.HasAnyFlag(GuildMemberFlags.Online); } + + public void UpdateLogoutTime() { m_logoutTime = (ulong)Time.UnixTime; } + public bool IsRank(byte rankId) { return m_rankId == rankId; } + public bool IsRankNotLower(uint rankId) { return m_rankId <= rankId; } + public bool IsSamePlayer(ObjectGuid guid) { return m_guid == guid; } + + public Player FindPlayer() { return Global.ObjAccessor.FindPlayer(m_guid); } + + #region Fields + ulong m_guildId; + ObjectGuid m_guid; + string m_name; + uint m_zoneId; + byte m_level; + Class m_class; + Gender _gender; + GuildMemberFlags m_flags; + ulong m_logoutTime; + uint m_accountId; + byte m_rankId; + string m_publicNote = ""; + string m_officerNote = ""; + + List m_trackedCriteriaIds = new List(); + + int[] m_bankWithdraw = new int[GuildConst.MaxBankTabs + 1]; + uint m_achievementPoints; + ulong m_totalActivity; + ulong m_weekActivity; + uint m_totalReputation; + uint m_weekReputation; + #endregion + } + + public class LogEntry + { + public LogEntry(ulong guildId, uint guid) + { + m_guildId = guildId; + m_guid = guid; + m_timestamp = Time.UnixTime; + } + + public LogEntry(ulong guildId, uint guid, long timestamp) + { + m_guildId = guildId; + m_guid = guid; + m_timestamp = timestamp; + } + + public uint GetGUID() { return m_guid; } + public long GetTimestamp() { return m_timestamp; } + + public virtual void SaveToDB(SQLTransaction trans) { } + + public ulong m_guildId; + public uint m_guid; + public long m_timestamp; + } + + public class EventLogEntry : LogEntry + { + public EventLogEntry(ulong guildId, uint guid, GuildEventLogTypes eventType, ulong playerGuid1, ulong playerGuid2, byte newRank) + : base(guildId, guid) + { + m_eventType = eventType; + m_playerGuid1 = playerGuid1; + m_playerGuid2 = playerGuid2; + m_newRank = newRank; + } + + public EventLogEntry(ulong guildId, uint guid, long timestamp, GuildEventLogTypes eventType, ulong playerGuid1, ulong playerGuid2, byte newRank) + : base(guildId, guid, timestamp) + { + m_eventType = eventType; + m_playerGuid1 = playerGuid1; + m_playerGuid2 = playerGuid2; + m_newRank = newRank; + } + + public override void SaveToDB(SQLTransaction trans) + { + PreparedStatement stmt = DB.Characters.GetPreparedStatement(CharStatements.DEL_GUILD_EVENTLOG); + stmt.AddValue(0, m_guildId); + stmt.AddValue(1, m_guid); + DB.Characters.ExecuteOrAppend(trans, stmt); + + byte index = 0; + stmt = DB.Characters.GetPreparedStatement(CharStatements.INS_GUILD_EVENTLOG); + stmt.AddValue(index, m_guildId); + stmt.AddValue(++index, m_guid); + stmt.AddValue(++index, m_eventType); + stmt.AddValue(++index, m_playerGuid1); + stmt.AddValue(++index, m_playerGuid2); + stmt.AddValue(++index, m_newRank); + stmt.AddValue(++index, m_timestamp); + DB.Characters.ExecuteOrAppend(trans, stmt); + } + + public void WritePacket(GuildEventLogQueryResults packet) + { + ObjectGuid playerGUID = ObjectGuid.Create(HighGuid.Player, m_playerGuid1); + ObjectGuid otherGUID = ObjectGuid.Create(HighGuid.Player, m_playerGuid2); + + GuildEventEntry eventEntry; + eventEntry.PlayerGUID = playerGUID; + eventEntry.OtherGUID = otherGUID; + eventEntry.TransactionType = (byte)m_eventType; + eventEntry.TransactionDate = (uint)(Time.UnixTime - m_timestamp); + eventEntry.RankID = m_newRank; + packet.Entry.Add(eventEntry); + } + + GuildEventLogTypes m_eventType; + ulong m_playerGuid1; + ulong m_playerGuid2; + byte m_newRank; + } + + public class BankEventLogEntry : LogEntry + { + public BankEventLogEntry(ulong guildId, uint guid, GuildBankEventLogTypes eventType, byte tabId, ulong playerGuid, ulong itemOrMoney, ushort itemStackCount, byte destTabId) + : base(guildId, guid) + { + m_eventType = eventType; + m_bankTabId = tabId; + m_playerGuid = playerGuid; + m_itemOrMoney = itemOrMoney; + m_itemStackCount = itemStackCount; + m_destTabId = destTabId; + } + + public BankEventLogEntry(ulong guildId, uint guid, long timestamp, byte tabId, GuildBankEventLogTypes eventType, ulong playerGuid, ulong itemOrMoney, ushort itemStackCount, byte destTabId) + : base(guildId, guid, timestamp) + { + m_eventType = eventType; + m_bankTabId = tabId; + m_playerGuid = playerGuid; + m_itemOrMoney = itemOrMoney; + m_itemStackCount = itemStackCount; + m_destTabId = destTabId; + } + + public static bool IsMoneyEvent(GuildBankEventLogTypes eventType) + { + return + eventType == GuildBankEventLogTypes.DepositMoney || + eventType == GuildBankEventLogTypes.WithdrawMoney || + eventType == GuildBankEventLogTypes.RepairMoney || + eventType == GuildBankEventLogTypes.CashFlowDeposit; + } + + bool IsMoneyEvent() + { + return IsMoneyEvent(m_eventType); + } + + public override void SaveToDB(SQLTransaction trans) + { + byte index = 0; + + PreparedStatement stmt = DB.Characters.GetPreparedStatement(CharStatements.DEL_GUILD_BANK_EVENTLOG); + stmt.AddValue(index, m_guildId); + stmt.AddValue(++index, m_guid); + stmt.AddValue(++index, m_bankTabId); + DB.Characters.ExecuteOrAppend(trans, stmt); + + index = 0; + stmt = DB.Characters.GetPreparedStatement(CharStatements.INS_GUILD_BANK_EVENTLOG); + stmt.AddValue(index, m_guildId); + stmt.AddValue(++index, m_guid); + stmt.AddValue(++index, m_bankTabId); + stmt.AddValue(++index, m_eventType); + stmt.AddValue(++index, m_playerGuid); + stmt.AddValue(++index, m_itemOrMoney); + stmt.AddValue(++index, m_itemStackCount); + stmt.AddValue(++index, m_destTabId); + stmt.AddValue(++index, m_timestamp); + DB.Characters.ExecuteOrAppend(trans, stmt); + } + + public void WritePacket(GuildBankLogQueryResults packet) + { + ObjectGuid logGuid = ObjectGuid.Create(HighGuid.Player, m_playerGuid); + + bool hasItem = m_eventType == GuildBankEventLogTypes.DepositItem || m_eventType == GuildBankEventLogTypes.WithdrawItem || + m_eventType == GuildBankEventLogTypes.MoveItem || m_eventType == GuildBankEventLogTypes.MoveItem2; + + bool itemMoved = (m_eventType == GuildBankEventLogTypes.MoveItem || m_eventType == GuildBankEventLogTypes.MoveItem2); + + bool hasStack = (hasItem && m_itemStackCount > 1) || itemMoved; + + GuildBankLogEntry bankLogEntry = new GuildBankLogEntry(); + bankLogEntry.PlayerGUID = logGuid; + bankLogEntry.TimeOffset = (uint)(Time.UnixTime - m_timestamp); + bankLogEntry.EntryType = (sbyte)m_eventType; + + if (hasStack) + bankLogEntry.Count.Set(m_itemStackCount); + + if (IsMoneyEvent()) + bankLogEntry.Money.Set(m_itemOrMoney); + + if (hasItem) + bankLogEntry.ItemID.Set((int)m_itemOrMoney); + + if (itemMoved) + bankLogEntry.OtherTab.Set((sbyte)m_destTabId); + + packet.Entry.Add(bankLogEntry); + } + + GuildBankEventLogTypes m_eventType; + byte m_bankTabId; + ulong m_playerGuid; + ulong m_itemOrMoney; + ushort m_itemStackCount; + byte m_destTabId; + } + + public class NewsLogEntry : LogEntry + { + public NewsLogEntry(ulong guildId, uint guid, GuildNews type, ObjectGuid playerGuid, uint flags, uint value) + : base(guildId, guid) + { + m_type = type; + m_playerGuid = playerGuid; + m_flags = (int)flags; + m_value = value; + } + + public NewsLogEntry(ulong guildId, uint guid, long timestamp, GuildNews type, ObjectGuid playerGuid, uint flags, uint value) + : base(guildId, guid, timestamp) + { + m_type = type; + m_playerGuid = playerGuid; + m_flags = (int)flags; + m_value = value; + } + + public GuildNews GetNewsType() { return m_type; } + + public ObjectGuid GetPlayerGuid() { return m_playerGuid; } + + public uint GetValue() { return m_value; } + + public int GetFlags() { return m_flags; } + + public void SetSticky(bool sticky) + { + if (sticky) + m_flags |= 1; + else + m_flags &= ~1; + } + + public override void SaveToDB(SQLTransaction trans) + { + byte index = 0; + PreparedStatement stmt = DB.Characters.GetPreparedStatement(CharStatements.INS_GUILD_NEWS); + stmt.AddValue(index, m_guildId); + stmt.AddValue(++index, GetGUID()); + stmt.AddValue(++index, GetNewsType()); + stmt.AddValue(++index, GetPlayerGuid().GetCounter()); + stmt.AddValue(++index, GetFlags()); + stmt.AddValue(++index, GetValue()); + stmt.AddValue(++index, GetTimestamp()); + DB.Characters.ExecuteOrAppend(trans, stmt); + } + + public void WritePacket(GuildNewsPkt newsPacket) + { + GuildNewsEvent newsEvent = new GuildNewsEvent(); + newsEvent.Id = (int)GetGUID(); + newsEvent.MemberGuid = GetPlayerGuid(); + newsEvent.CompletedDate = (uint)GetTimestamp(); + newsEvent.Flags = GetFlags(); + newsEvent.Type = (int)GetNewsType(); + + //for (public byte i = 0; i < 2; i++) + // newsEvent.Data[i] = + + //newsEvent.MemberList.push_back(MemberGuid); + + if (GetNewsType() == GuildNews.ItemLooted || GetNewsType() == GuildNews.ItemCrafted || GetNewsType() == GuildNews.ItemPurchased) + { + ItemInstance itemInstance = new ItemInstance(); + itemInstance.ItemID = GetValue(); + newsEvent.Item.Set(itemInstance); + } + + newsPacket.NewsEvents.Add(newsEvent); + } + + GuildNews m_type; + ObjectGuid m_playerGuid; + int m_flags; + uint m_value; + } + + public class LogHolder + { + public LogHolder(uint maxRecords) + { + m_maxRecords = maxRecords; + m_nextGUID = GuildConst.EventLogGuidUndefined; + } + + public byte GetSize() { return (byte)m_log.Count; } + + public bool CanInsert() { return m_log.Count < m_maxRecords; } + + public void LoadEvent(LogEntry entry) + { + if (m_nextGUID == GuildConst.EventLogGuidUndefined) + m_nextGUID = entry.GetGUID(); + m_log.Insert(0, entry); + } + + public void AddEvent(SQLTransaction trans, LogEntry entry) + { + // Check max records limit + if (m_log.Count >= m_maxRecords) + m_log.RemoveAt(0); + + // Add event to list + m_log.Add(entry); + // Save to DB + entry.SaveToDB(trans); + } + + public uint GetNextGUID() + { + if (m_nextGUID == GuildConst.EventLogGuidUndefined) + m_nextGUID = 0; + else + m_nextGUID = (m_nextGUID + 1) % m_maxRecords; + return m_nextGUID; + } + + public List GetGuildLog() { return m_log; } + + + List m_log = new List(); + uint m_maxRecords; + uint m_nextGUID; + } + + public class RankInfo + { + public RankInfo(ulong guildId = 0) + { + m_guildId = guildId; + m_rankId = GuildConst.RankNone; + m_rights = GuildRankRights.None; + m_bankMoneyPerDay = 0; + + for (var i = 0; i < GuildConst.MaxBankTabs; ++i) + m_bankTabRightsAndSlots[i] = new GuildBankRightsAndSlots(); + } + + public RankInfo(ulong guildId, byte rankId, string name, GuildRankRights rights, uint money) + { + m_guildId = guildId; + m_rankId = rankId; + m_name = name; + m_rights = rights; + m_bankMoneyPerDay = money; + + for (var i = 0; i < GuildConst.MaxBankTabs; ++i) + m_bankTabRightsAndSlots[i] = new GuildBankRightsAndSlots(); + } + + public void LoadFromDB(SQLFields field) + { + m_rankId = field.Read(1); + m_name = field.Read(2); + m_rights = (GuildRankRights)field.Read(3); + m_bankMoneyPerDay = field.Read(4); + if (m_rankId == GuildDefaultRanks.Master) // Prevent loss of leader rights + m_rights |= GuildRankRights.All; + } + + public void SaveToDB(SQLTransaction trans) + { + PreparedStatement stmt = DB.Characters.GetPreparedStatement(CharStatements.INS_GUILD_RANK); + stmt.AddValue(0, m_guildId); + stmt.AddValue(1, m_rankId); + stmt.AddValue(2, m_name); + stmt.AddValue(3, m_rights); + DB.Characters.ExecuteOrAppend(trans, stmt); + } + + public void CreateMissingTabsIfNeeded(byte tabs, SQLTransaction trans, bool logOnCreate = false) + { + for (byte i = 0; i < tabs; ++i) + { + GuildBankRightsAndSlots rightsAndSlots = m_bankTabRightsAndSlots[i]; + if (rightsAndSlots.GetTabId() == i) + continue; + + rightsAndSlots.SetTabId(i); + if (m_rankId == GuildDefaultRanks.Master) + rightsAndSlots.SetGuildMasterValues(); + + if (logOnCreate) + Log.outError(LogFilter.Guild, "Guild {0} has broken Tab {1} for rank {2}. Created default tab.", m_guildId, i, m_rankId); + + PreparedStatement stmt = DB.Characters.GetPreparedStatement(CharStatements.INS_GUILD_BANK_RIGHT); + stmt.AddValue(0, m_guildId); + stmt.AddValue(1, i); + stmt.AddValue(2, m_rankId); + stmt.AddValue(3, rightsAndSlots.GetRights()); + stmt.AddValue(4, rightsAndSlots.GetSlots()); + trans.Append(stmt); + } + } + + public void SetName(string name) + { + if (m_name == name) + return; + + m_name = name; + + PreparedStatement stmt = DB.Characters.GetPreparedStatement(CharStatements.UPD_GUILD_RANK_NAME); + stmt.AddValue(0, m_name); + stmt.AddValue(1, m_rankId); + stmt.AddValue(2, m_guildId); + DB.Characters.Execute(stmt); + } + + public void SetRights(GuildRankRights rights) + { + if (m_rankId == GuildDefaultRanks.Master) // Prevent loss of leader rights + rights = GuildRankRights.All; + + if (m_rights == rights) + return; + + m_rights = rights; + + PreparedStatement stmt = DB.Characters.GetPreparedStatement(CharStatements.UPD_GUILD_RANK_RIGHTS); + stmt.AddValue(0, m_rights); + stmt.AddValue(1, m_rankId); + stmt.AddValue(2, m_guildId); + DB.Characters.Execute(stmt); + } + + public void SetBankMoneyPerDay(uint money) + { + if (m_rankId == GuildDefaultRanks.Master) // Prevent loss of leader rights + money = Convert.ToUInt32(GuildConst.WithdrawMoneyUnlimited); + + if (m_bankMoneyPerDay == money) + return; + + m_bankMoneyPerDay = money; + + PreparedStatement stmt = DB.Characters.GetPreparedStatement(CharStatements.UPD_GUILD_RANK_BANK_MONEY); + stmt.AddValue(0, money); + stmt.AddValue(1, m_rankId); + stmt.AddValue(2, m_guildId); + DB.Characters.Execute(stmt); + } + + public void SetBankTabSlotsAndRights(GuildBankRightsAndSlots rightsAndSlots, bool saveToDB) + { + if (m_rankId == GuildDefaultRanks.Master) // Prevent loss of leader rights + rightsAndSlots.SetGuildMasterValues(); + + GuildBankRightsAndSlots guildBR = m_bankTabRightsAndSlots[rightsAndSlots.GetTabId()]; + guildBR = rightsAndSlots; + + if (saveToDB) + { + PreparedStatement stmt = DB.Characters.GetPreparedStatement(CharStatements.INS_GUILD_BANK_RIGHT); + stmt.AddValue(0, m_guildId); + stmt.AddValue(1, guildBR.GetTabId()); + stmt.AddValue(2, m_rankId); + stmt.AddValue(3, guildBR.GetRights()); + stmt.AddValue(4, guildBR.GetSlots()); + DB.Characters.Execute(stmt); + } + } + + public byte GetId() { return m_rankId; } + + public string GetName() { return m_name; } + + public GuildRankRights GetRights() { return m_rights; } + + public int GetBankMoneyPerDay() { return (int)m_bankMoneyPerDay; } + + public GuildBankRights GetBankTabRights(byte tabId) + { + return tabId < GuildConst.MaxBankTabs ? m_bankTabRightsAndSlots[tabId].GetRights() : 0; + } + + public int GetBankTabSlotsPerDay(byte tabId) + { + return tabId < GuildConst.MaxBankTabs ? m_bankTabRightsAndSlots[tabId].GetSlots() : 0; + } + + + ulong m_guildId; + byte m_rankId; + string m_name; + GuildRankRights m_rights; + uint m_bankMoneyPerDay; + GuildBankRightsAndSlots[] m_bankTabRightsAndSlots = new GuildBankRightsAndSlots[GuildConst.MaxBankTabs]; + } + + public class BankTab + { + public BankTab(ulong guildId, byte tabId) + { + m_guildId = guildId; + m_tabId = tabId; + } + + public void LoadFromDB(SQLFields field) + { + m_name = field.Read(2); + m_icon = field.Read(3); + m_text = field.Read(4); + } + + public bool LoadItemFromDB(SQLFields field) + { + byte slotId = field.Read(47); + uint itemGuid = field.Read(0); + uint itemEntry = field.Read(1); + if (slotId >= GuildConst.MaxBankSlots) + { + Log.outError(LogFilter.Guild, "Invalid slot for item (GUID: {0}, id: {1}) in guild bank, skipped.", itemGuid, itemEntry); + return false; + } + + ItemTemplate proto = Global.ObjectMgr.GetItemTemplate(itemEntry); + if (proto == null) + { + Log.outError(LogFilter.Guild, "Unknown item (GUID: {0}, id: {1}) in guild bank, skipped.", itemGuid, itemEntry); + return false; + } + + Item pItem = Bag.NewItemOrBag(proto); + if (!pItem.LoadFromDB(itemGuid, ObjectGuid.Empty, field, itemEntry)) + { + Log.outError(LogFilter.Guild, "Item (GUID {0}, id: {1}) not found in item_instance, deleting from guild bank!", itemGuid, itemEntry); + + PreparedStatement stmt = DB.Characters.GetPreparedStatement(CharStatements.DEL_NONEXISTENT_GUILD_BANK_ITEM); + stmt.AddValue(0, m_guildId); + stmt.AddValue(1, m_tabId); + stmt.AddValue(2, slotId); + DB.Characters.Execute(stmt); + return false; + } + + pItem.AddToWorld(); + m_items[slotId] = pItem; + return true; + } + + public void Delete(SQLTransaction trans, bool removeItemsFromDB = false) + { + for (byte slotId = 0; slotId < GuildConst.MaxBankSlots; ++slotId) + { + Item pItem = m_items[slotId]; + if (pItem != null) + { + pItem.RemoveFromWorld(); + if (removeItemsFromDB) + pItem.DeleteFromDB(trans); + pItem = null; + } + } + } + + public void SetInfo(string name, string icon) + { + if (m_name == name && m_icon == icon) + return; + + m_name = name; + m_icon = icon; + + PreparedStatement stmt = DB.Characters.GetPreparedStatement(CharStatements.UPD_GUILD_BANK_TAB_INFO); + stmt.AddValue(0, m_name); + stmt.AddValue(1, m_icon); + stmt.AddValue(2, m_guildId); + stmt.AddValue(3, m_tabId); + DB.Characters.Execute(stmt); + } + + public void SetText(string text) + { + if (m_text == text) + return; + + m_text = text; + + PreparedStatement stmt = DB.Characters.GetPreparedStatement(CharStatements.UPD_GUILD_BANK_TAB_TEXT); + stmt.AddValue(0, m_text); + stmt.AddValue(1, m_guildId); + stmt.AddValue(2, m_tabId); + DB.Characters.Execute(stmt); + } + + public void SendText(Guild guild, WorldSession session = null) + { + GuildBankTextQueryResult textQuery = new GuildBankTextQueryResult(); + textQuery.Tab = m_tabId; + textQuery.Text = m_text; + + if (session != null) + { + Log.outDebug(LogFilter.Guild, "SMSG_GUILD_BANK_QUERY_TEXT_RESULT [{0}]: Tabid: {1}, Text: {2}", session.GetPlayerInfo(), m_tabId, m_text); + session.SendPacket(textQuery); + } + else + { + Log.outDebug(LogFilter.Guild, "SMSG_GUILD_BANK_QUERY_TEXT_RESULT [Broadcast]: Tabid: {0}, Text: {1}", m_tabId, m_text); + guild.BroadcastPacket(textQuery); + } + } + + public string GetName() { return m_name; } + + public string GetIcon() { return m_icon; } + + public string GetText() { return m_text; } + + public Item GetItem(byte slotId) { return slotId < GuildConst.MaxBankSlots ? m_items[slotId] : null; } + + public bool SetItem(SQLTransaction trans, byte slotId, Item item) + { + if (slotId >= GuildConst.MaxBankSlots) + return false; + + m_items[slotId] = item; + + PreparedStatement stmt = DB.Characters.GetPreparedStatement(CharStatements.DEL_GUILD_BANK_ITEM); + stmt.AddValue(0, m_guildId); + stmt.AddValue(1, m_tabId); + stmt.AddValue(2, slotId); + DB.Characters.ExecuteOrAppend(trans, stmt); + + if (item != null) + { + stmt = DB.Characters.GetPreparedStatement(CharStatements.INS_GUILD_BANK_ITEM); + stmt.AddValue(0, m_guildId); + stmt.AddValue(1, m_tabId); + stmt.AddValue(2, slotId); + stmt.AddValue(3, item.GetGUID().GetCounter()); + DB.Characters.ExecuteOrAppend(trans, stmt); + + item.SetGuidValue(ItemFields.Contained, ObjectGuid.Empty); + item.SetGuidValue(ItemFields.Owner, ObjectGuid.Empty); + item.FSetState(ItemUpdateState.New); + item.SaveToDB(trans); // Not in inventory and can be saved standalone + } + return true; + } + + + ulong m_guildId; + byte m_tabId; + Item[] m_items = new Item[GuildConst.MaxBankSlots]; + string m_name; + string m_icon; + string m_text; + } + + public class GuildBankRightsAndSlots + { + public GuildBankRightsAndSlots(byte _tabId = 0xFF, sbyte _rights = 0, int _slots = 0) + { + tabId = _tabId; + rights = (GuildBankRights)_rights; + slots = _slots; + } + + public void SetGuildMasterValues() + { + rights = GuildBankRights.Full; + slots = GuildConst.WithdrawSlotUnlimited; + } + + public void SetTabId(byte _tabId) { tabId = _tabId; } + public void SetSlots(int _slots) { slots = _slots; } + public void SetRights(GuildBankRights _rights) { rights = _rights; } + + public byte GetTabId() { return tabId; } + public int GetSlots() { return slots; } + public GuildBankRights GetRights() { return rights; } + + byte tabId; + GuildBankRights rights; + int slots; + } + + public class EmblemInfo + { + public EmblemInfo() + { + m_style = 0; + m_color = 0; + m_borderStyle = 0; + m_borderColor = 0; + m_backgroundColor = 0; + } + + public void ReadPacket(SaveGuildEmblem packet) + { + m_style = packet.EStyle; + m_color = packet.EColor; + m_borderStyle = packet.BStyle; + m_borderColor = packet.BColor; + m_backgroundColor = packet.Bg; + } + + public bool ValidateEmblemColors() + { + return CliDB.GuildColorBackgroundStorage.ContainsKey(m_backgroundColor) && + CliDB.GuildColorBorderStorage.ContainsKey(m_borderColor) && + CliDB.GuildColorEmblemStorage.ContainsKey(m_color); + } + + public bool LoadFromDB(SQLFields field) + { + m_style = field.Read(3); + m_color = field.Read(4); + m_borderStyle = field.Read(5); + m_borderColor = field.Read(6); + m_backgroundColor = field.Read(7); + + return ValidateEmblemColors(); + } + + public void SaveToDB(ulong guildId) + { + PreparedStatement stmt = DB.Characters.GetPreparedStatement(CharStatements.UPD_GUILD_EMBLEM_INFO); + stmt.AddValue(0, m_style); + stmt.AddValue(1, m_color); + stmt.AddValue(2, m_borderStyle); + stmt.AddValue(3, m_borderColor); + stmt.AddValue(4, m_backgroundColor); + stmt.AddValue(5, guildId); + DB.Characters.Execute(stmt); + } + + public uint GetStyle() { return m_style; } + + public uint GetColor() { return m_color; } + + public uint GetBorderStyle() { return m_borderStyle; } + + public uint GetBorderColor() { return m_borderColor; } + + public uint GetBackgroundColor() { return m_backgroundColor; } + + uint m_style; + uint m_color; + uint m_borderStyle; + uint m_borderColor; + uint m_backgroundColor; + } + + public abstract class MoveItemData + { + public MoveItemData(Guild guild, Player player, byte container, byte slotId) + { + m_pGuild = guild; + m_pPlayer = player; + m_container = container; + m_slotId = slotId; + m_pItem = null; + m_pClonedItem = null; + } + + public virtual bool CheckItem(ref uint splitedAmount) + { + Contract.Assert(m_pItem != null); + if (splitedAmount > m_pItem.GetCount()) + return false; + if (splitedAmount == m_pItem.GetCount()) + splitedAmount = 0; + return true; + } + + public bool CanStore(Item pItem, bool swap, bool sendError) + { + m_vec.Clear(); + InventoryResult msg = CanStore(pItem, swap); + if (sendError && msg != InventoryResult.Ok) + m_pPlayer.SendEquipError(msg, pItem); + return (msg == InventoryResult.Ok); + } + + public bool CloneItem(uint count) + { + Contract.Assert(m_pItem != null); + m_pClonedItem = m_pItem.CloneItem(count); + if (m_pClonedItem == null) + { + m_pPlayer.SendEquipError(InventoryResult.ItemNotFound, m_pItem); + return false; + } + return true; + } + + public virtual void LogAction(MoveItemData pFrom) + { + Contract.Assert(pFrom.GetItem() != null); + + Global.ScriptMgr.OnGuildItemMove(m_pGuild, m_pPlayer, pFrom.GetItem(), + pFrom.IsBank(), pFrom.GetContainer(), pFrom.GetSlotId(), + IsBank(), GetContainer(), GetSlotId()); + } + + public void CopySlots(List ids) + { + foreach (var item in m_vec) + ids.Add((byte)item.pos); + } + + public abstract bool IsBank(); + // Initializes item. Returns true, if item exists, false otherwise. + public abstract bool InitItem(); + // Checks splited amount against item. Splited amount cannot be more that number of items in stack. + // Defines if player has rights to save item in container + public virtual bool HasStoreRights(MoveItemData pOther) { return true; } + // Defines if player has rights to withdraw item from container + public virtual bool HasWithdrawRights(MoveItemData pOther) { return true; } + // Remove item from container (if splited update items fields) + public abstract void RemoveItem(SQLTransaction trans, MoveItemData pOther, uint splitedAmount = 0); + // Saves item to container + public abstract Item StoreItem(SQLTransaction trans, Item pItem); + // Log bank event + public abstract void LogBankEvent(SQLTransaction trans, MoveItemData pFrom, uint count); + + public abstract InventoryResult CanStore(Item pItem, bool swap); + + public Item GetItem(bool isCloned = false) { return isCloned ? m_pClonedItem : m_pItem; } + public byte GetContainer() { return m_container; } + public byte GetSlotId() { return m_slotId; } + + public Guild m_pGuild; + public Player m_pPlayer; + public byte m_container; + public byte m_slotId; + public Item m_pItem; + public Item m_pClonedItem; + public List m_vec = new List(); + } + + public class PlayerMoveItemData : MoveItemData + { + public PlayerMoveItemData(Guild guild, Player player, byte container, byte slotId) + : base(guild, player, container, slotId) { } + + public override bool IsBank() { return false; } + + public override bool InitItem() + { + m_pItem = m_pPlayer.GetItemByPos(m_container, m_slotId); + if (m_pItem != null) + { + // Anti-WPE protection. Do not move non-empty bags to bank. + if (m_pItem.IsNotEmptyBag()) + { + m_pPlayer.SendEquipError(InventoryResult.DestroyNonemptyBag, m_pItem); + m_pItem = null; + } + // Bound items cannot be put into bank. + else if (!m_pItem.CanBeTraded()) + { + m_pPlayer.SendEquipError(InventoryResult.CantSwap, m_pItem); + m_pItem = null; + } + } + return (m_pItem != null); + } + + public override void RemoveItem(SQLTransaction trans, MoveItemData pOther, uint splitedAmount = 0) + { + if (splitedAmount != 0) + { + m_pItem.SetCount(m_pItem.GetCount() - splitedAmount); + m_pItem.SetState(ItemUpdateState.Changed, m_pPlayer); + m_pPlayer.SaveInventoryAndGoldToDB(trans); + } + else + { + m_pPlayer.MoveItemFromInventory(m_container, m_slotId, true); + m_pItem.DeleteFromInventoryDB(trans); + m_pItem = null; + } + } + + public override Item StoreItem(SQLTransaction trans, Item pItem) + { + Contract.Assert(pItem != null); + m_pPlayer.MoveItemToInventory(m_vec, pItem, true); + m_pPlayer.SaveInventoryAndGoldToDB(trans); + return pItem; + } + + public override void LogBankEvent(SQLTransaction trans, MoveItemData pFrom, uint count) + { + Contract.Assert(pFrom != null); + // Bank . Char + m_pGuild._LogBankEvent(trans, GuildBankEventLogTypes.WithdrawItem, pFrom.GetContainer(), m_pPlayer.GetGUID().GetCounter(), + pFrom.GetItem().GetEntry(), (ushort)count); + } + + public override InventoryResult CanStore(Item pItem, bool swap) + { + return m_pPlayer.CanStoreItem(m_container, m_slotId, m_vec, pItem, swap); + } + } + + public class BankMoveItemData : MoveItemData + { + public BankMoveItemData(Guild guild, Player player, byte container, byte slotId) + : base(guild, player, container, slotId) { } + + public override bool IsBank() { return true; } + + public override bool InitItem() + { + m_pItem = m_pGuild._GetItem(m_container, m_slotId); + return (m_pItem != null); + } + public override bool HasStoreRights(MoveItemData pOther) + { + Contract.Assert(pOther != null); + // Do not check rights if item is being swapped within the same bank tab + if (pOther.IsBank() && pOther.GetContainer() == m_container) + return true; + return m_pGuild._MemberHasTabRights(m_pPlayer.GetGUID(), m_container, GuildBankRights.DepositItem); + } + + public override bool HasWithdrawRights(MoveItemData pOther) + { + Contract.Assert(pOther != null); + // Do not check rights if item is being swapped within the same bank tab + if (pOther.IsBank() && pOther.GetContainer() == m_container) + return true; + + int slots = 0; + Member member = m_pGuild.GetMember(m_pPlayer.GetGUID()); + if (member != null) + slots = m_pGuild._GetMemberRemainingSlots(member, m_container); + + return slots != 0; + } + + public override void RemoveItem(SQLTransaction trans, MoveItemData pOther, uint splitedAmount = 0) + { + Contract.Assert(m_pItem != null); + if (splitedAmount != 0) + { + m_pItem.SetCount(m_pItem.GetCount() - splitedAmount); + m_pItem.FSetState(ItemUpdateState.Changed); + m_pItem.SaveToDB(trans); + } + else + { + m_pGuild._RemoveItem(trans, m_container, m_slotId); + m_pItem = null; + } + // Decrease amount of player's remaining items (if item is moved to different tab or to player) + if (!pOther.IsBank() || pOther.GetContainer() != m_container) + m_pGuild._UpdateMemberWithdrawSlots(trans, m_pPlayer.GetGUID(), m_container); + } + + public override Item StoreItem(SQLTransaction trans, Item pItem) + { + if (pItem == null) + return null; + + BankTab pTab = m_pGuild.GetBankTab(m_container); + if (pTab == null) + return null; + + Item pLastItem = pItem; + foreach (var pos in m_vec) + { + Log.outDebug(LogFilter.Guild, "GUILD STORAGE: StoreItem tab = {0}, slot = {1}, item = {2}, count = {3}", + m_container, m_slotId, pItem.GetEntry(), pItem.GetCount()); + pLastItem = _StoreItem(trans, pTab, pItem, pos, pos.Equals(m_vec.Last())); + } + return pLastItem; + } + + public override void LogBankEvent(SQLTransaction trans, MoveItemData pFrom, uint count) + { + Contract.Assert(pFrom.GetItem() != null); + if (pFrom.IsBank()) + // Bank . Bank + m_pGuild._LogBankEvent(trans, GuildBankEventLogTypes.MoveItem, pFrom.GetContainer(), m_pPlayer.GetGUID().GetCounter(), + pFrom.GetItem().GetEntry(), (ushort)count, m_container); + else + // Char . Bank + m_pGuild._LogBankEvent(trans, GuildBankEventLogTypes.DepositItem, m_container, m_pPlayer.GetGUID().GetCounter(), + pFrom.GetItem().GetEntry(), (ushort)count); + } + public override void LogAction(MoveItemData pFrom) + { + base.LogAction(pFrom); + if (!pFrom.IsBank() && m_pPlayer.GetSession().HasPermission(RBACPermissions.LogGmTrade)) /// @todo Move this to scripts + { + Log.outCommand(m_pPlayer.GetSession().GetAccountId(), "GM {0} ({1}) (Account: {2}) deposit item: {3} (Entry: {4} Count: {5}) to guild bank named: {6} (Guild ID: {7})", + m_pPlayer.GetName(), m_pPlayer.GetGUID().ToString(), m_pPlayer.GetSession().GetAccountId(), pFrom.GetItem().GetTemplate().GetName(), + pFrom.GetItem().GetEntry(), pFrom.GetItem().GetCount(), m_pGuild.GetName(), m_pGuild.GetId()); + } + } + + Item _StoreItem(SQLTransaction trans, BankTab pTab, Item pItem, ItemPosCount pos, bool clone) + { + byte slotId = (byte)pos.pos; + uint count = pos.count; + Item pItemDest = pTab.GetItem(slotId); + if (pItemDest != null) + { + pItemDest.SetCount(pItemDest.GetCount() + count); + pItemDest.FSetState(ItemUpdateState.Changed); + pItemDest.SaveToDB(trans); + if (!clone) + { + pItem.RemoveFromWorld(); + pItem.DeleteFromDB(trans); + } + return pItemDest; + } + + if (clone) + pItem = pItem.CloneItem(count); + else + pItem.SetCount(count); + + if (pItem != null && pTab.SetItem(trans, slotId, pItem)) + return pItem; + + return null; + } + bool _ReserveSpace(byte slotId, Item pItem, Item pItemDest, ref uint count) + { + uint requiredSpace = pItem.GetMaxStackCount(); + if (pItemDest != null) + { + // Make sure source and destination items match and destination item has space for more stacks. + if (pItemDest.GetEntry() != pItem.GetEntry() || pItemDest.GetCount() >= pItem.GetMaxStackCount()) + return false; + requiredSpace -= pItemDest.GetCount(); + } + // Let's not be greedy, reserve only required space + requiredSpace = Math.Min(requiredSpace, count); + + // Reserve space + ItemPosCount pos = new ItemPosCount(slotId, requiredSpace); + if (!pos.isContainedIn(m_vec)) + { + m_vec.Add(pos); + count -= requiredSpace; + } + return true; + } + + void CanStoreItemInTab(Item pItem, byte skipSlotId, bool merge, ref uint count) + { + for (byte slotId = 0; (slotId < GuildConst.MaxBankSlots) && (count > 0); ++slotId) + { + // Skip slot already processed in CanStore (when destination slot was specified) + if (slotId == skipSlotId) + continue; + + Item pItemDest = m_pGuild._GetItem(m_container, slotId); + if (pItemDest == pItem) + pItemDest = null; + + // If merge skip empty, if not merge skip non-empty + if ((pItemDest != null) != merge) + continue; + + _ReserveSpace(slotId, pItem, pItemDest, ref count); + } + } + + public override InventoryResult CanStore(Item pItem, bool swap) + { + Log.outDebug(LogFilter.Guild, "GUILD STORAGE: CanStore() tab = {0}, slot = {1}, item = {2}, count = {3}", + m_container, m_slotId, pItem.GetEntry(), pItem.GetCount()); + + uint count = pItem.GetCount(); + // Soulbound items cannot be moved + if (pItem.IsSoulBound()) + return InventoryResult.DropBoundItem; + + // Make sure destination bank tab exists + if (m_container >= m_pGuild._GetPurchasedTabsSize()) + return InventoryResult.WrongBagType; + + // Slot explicitely specified. Check it. + if (m_slotId != ItemConst.NullSlot) + { + Item pItemDest = m_pGuild._GetItem(m_container, m_slotId); + // Ignore swapped item (this slot will be empty after move) + if ((pItemDest == pItem) || swap) + pItemDest = null; + + if (!_ReserveSpace(m_slotId, pItem, pItemDest, ref count)) + return InventoryResult.CantStack; + + if (count == 0) + return InventoryResult.Ok; + } + + // Slot was not specified or it has not enough space for all the items in stack + // Search for stacks to merge with + if (pItem.GetMaxStackCount() > 1) + { + CanStoreItemInTab(pItem, m_slotId, true, ref count); + if (count == 0) + return InventoryResult.Ok; + } + + // Search free slot for item + CanStoreItemInTab(pItem, m_slotId, false, ref count); + if (count == 0) + return InventoryResult.Ok; + + return InventoryResult.BankFull; + } + } + #endregion + } +} diff --git a/Game/Guilds/GuildFinderManager.cs b/Game/Guilds/GuildFinderManager.cs new file mode 100644 index 000000000..93bffadf6 --- /dev/null +++ b/Game/Guilds/GuildFinderManager.cs @@ -0,0 +1,512 @@ +/* + * Copyright (C) 2012-2017 CypherCore + * + * 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, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +using Framework.Constants; +using Framework.Database; +using Game.DataStorage; +using Game.Entities; +using Game.Network.Packets; +using System; +using System.Collections.Generic; + +namespace Game.Guilds +{ + public class GuildFinderManager : Singleton + { + GuildFinderManager() { } + + public void LoadFromDB() + { + LoadGuildSettings(); + LoadMembershipRequests(); + } + + void LoadGuildSettings() + { + Log.outInfo(LogFilter.ServerLoading, "Loading guild finder guild-related settings..."); + // 0 1 2 3 4 5 6 7 + SQLResult result = DB.Characters.Query("SELECT gfgs.guildId, gfgs.availability, gfgs.classRoles, gfgs.interests, gfgs.level, gfgs.listed, gfgs.comment, c.race " + + "FROM guild_finder_guild_settings gfgs LEFT JOIN guild_member gm ON gm.guildid=gfgs.guildId LEFT JOIN characters c ON c.guid = gm.guid LIMIT 1"); + + if (result.IsEmpty()) + { + Log.outInfo(LogFilter.ServerLoading, "Loaded 0 guild finder guild-related settings. Table `guild_finder_guild_settings` is empty."); + return; + } + + uint count = 0; + uint oldMSTime = Time.GetMSTime(); + do + { + ObjectGuid guildId = ObjectGuid.Create(HighGuid.Guild, result.Read(0)); + byte availability = result.Read(1); + byte classRoles = result.Read(2); + byte interests = result.Read(3); + byte level = result.Read(4); + bool listed = (result.Read(5) != 0); + string comment = result.Read(6); + + uint guildTeam = TeamId.Alliance; + ChrRacesRecord raceEntry = CliDB.ChrRacesStorage.LookupByKey(result.Read(7)); + if (raceEntry != null) + if (raceEntry.TeamID == 1) + guildTeam = TeamId.Horde; + + LFGuildSettings settings = new LFGuildSettings(listed, guildTeam, guildId, classRoles, availability, interests, level, comment); + _guildSettings[guildId] = settings; + + ++count; + } while (result.NextRow()); + + Log.outInfo(LogFilter.ServerLoading, "Loaded {0} guild finder guild-related settings in {1} ms.", count, Time.GetMSTimeDiffToNow(oldMSTime)); + } + + void LoadMembershipRequests() + { + Log.outInfo(LogFilter.ServerLoading, "Loading guild finder membership requests..."); + // 0 1 2 3 4 5 6 + SQLResult result = DB.Characters.Query("SELECT guildId, playerGuid, availability, classRole, interests, comment, submitTime FROM guild_finder_applicant"); + + if (result.IsEmpty()) + { + Log.outInfo(LogFilter.ServerLoading, "Loaded 0 guild finder membership requests. Table `guild_finder_applicant` is empty."); + return; + } + + uint count = 0; + uint oldMSTime = Time.GetMSTime(); + do + { + ObjectGuid guildId = ObjectGuid.Create(HighGuid.Guild, result.Read(0)); + ObjectGuid playerId = ObjectGuid.Create(HighGuid.Player, result.Read(1)); + byte availability = result.Read(2); + byte classRoles = result.Read(3); + byte interests = result.Read(4); + string comment = result.Read(5); + uint submitTime = result.Read(6); + + MembershipRequest request = new MembershipRequest(playerId, guildId, availability, classRoles, interests, comment, submitTime); + + if (!_membershipRequestsByGuild.ContainsKey(guildId)) + _membershipRequestsByGuild[guildId] = new Dictionary(); + + if (!_membershipRequestsByPlayer.ContainsKey(playerId)) + _membershipRequestsByPlayer[playerId] = new Dictionary(); + + _membershipRequestsByGuild[guildId][playerId] = request; + _membershipRequestsByPlayer[playerId][guildId] = request; + + ++count; + } while (result.NextRow()); + + Log.outInfo(LogFilter.ServerLoading, "Loaded {0} guild finder membership requests in {1} ms.", count, Time.GetMSTimeDiffToNow(oldMSTime)); + } + + public void AddMembershipRequest(ObjectGuid guildGuid, MembershipRequest request) + { + _membershipRequestsByGuild[guildGuid][request.GetPlayerGUID()] = request; + _membershipRequestsByPlayer[request.GetPlayerGUID()][guildGuid] = request; + + SQLTransaction trans = new SQLTransaction(); + PreparedStatement stmt = DB.Characters.GetPreparedStatement(CharStatements.REP_GUILD_FINDER_APPLICANT); + stmt.AddValue(0, request.GetGuildGuid()); + stmt.AddValue(1, request.GetPlayerGUID()); + stmt.AddValue(2, request.GetAvailability()); + stmt.AddValue(3, request.GetClassRoles()); + stmt.AddValue(4, request.GetInterests()); + stmt.AddValue(5, request.GetComment()); + stmt.AddValue(6, request.GetSubmitTime()); + trans.Append(stmt); + DB.Characters.CommitTransaction(trans); + + // Notify the applicant his submittion has been added + Player player = Global.ObjAccessor.FindPlayer(request.GetPlayerGUID()); + if (player) + SendMembershipRequestListUpdate(player); + + // Notify the guild master and officers the list changed + Guild guild = Global.GuildMgr.GetGuildById(guildGuid.GetCounter()); + if (guild) + SendApplicantListUpdate(guild); + } + + public void RemoveAllMembershipRequestsFromPlayer(ObjectGuid playerId) + { + var playerDic = _membershipRequestsByPlayer.LookupByKey(playerId); + if (playerDic == null) + return; + + SQLTransaction trans = new SQLTransaction(); + foreach (var guid in playerDic.Keys) + { + PreparedStatement stmt = DB.Characters.GetPreparedStatement(CharStatements.DEL_GUILD_FINDER_APPLICANT); + stmt.AddValue(0, guid.GetCounter()); + stmt.AddValue(1, playerId.GetCounter()); + trans.Append(stmt); + + + // Notify the guild master and officers the list changed + Guild guild = Global.GuildMgr.GetGuildByGuid(guid); + if (guild) + SendApplicantListUpdate(guild); + + if (!_membershipRequestsByGuild.ContainsKey(guid)) + continue; + + var guildDic = _membershipRequestsByGuild[guid]; + guildDic.Remove(playerId); + if (guildDic.Empty()) + _membershipRequestsByGuild.Remove(guid); + } + + DB.Characters.CommitTransaction(trans); + _membershipRequestsByPlayer.Remove(playerId); + } + + public void RemoveMembershipRequest(ObjectGuid playerId, ObjectGuid guildId) + { + + if (_membershipRequestsByGuild.ContainsKey(guildId)) + { + var guildDic = _membershipRequestsByGuild[guildId]; + guildDic.Remove(playerId); + if (guildDic.Empty()) + _membershipRequestsByGuild.Remove(guildId); + } + + var playerDic = _membershipRequestsByPlayer.LookupByKey(playerId); + if (playerDic != null) + { + playerDic.Remove(guildId); + if (playerDic.Empty()) + _membershipRequestsByPlayer.Remove(playerId); + } + + SQLTransaction trans = new SQLTransaction(); + PreparedStatement stmt = DB.Characters.GetPreparedStatement(CharStatements.DEL_GUILD_FINDER_APPLICANT); + stmt.AddValue(0, guildId.GetCounter()); + stmt.AddValue(1, playerId.GetCounter()); + trans.Append(stmt); + + DB.Characters.CommitTransaction(trans); + + // Notify the applicant his submittion has been removed + Player player = Global.ObjAccessor.FindPlayer(playerId); + if (player) + SendMembershipRequestListUpdate(player); + + // Notify the guild master and officers the list changed + Guild guild = Global.GuildMgr.GetGuildByGuid(guildId); + if (guild) + SendApplicantListUpdate(guild); + } + + public List GetAllMembershipRequestsForPlayer(ObjectGuid playerGuid) + { + List resultSet = new List(); + var playerDic = _membershipRequestsByPlayer.LookupByKey(playerGuid); + if (playerDic == null) + return resultSet; + + foreach (var guildRequestPair in playerDic) + resultSet.Add(guildRequestPair.Value); + + return resultSet; + } + + public byte CountRequestsFromPlayer(ObjectGuid playerId) + { + return (byte)(_membershipRequestsByPlayer.ContainsKey(playerId) ? _membershipRequestsByPlayer[playerId].Count : 0); + } + + public List GetGuildsMatchingSetting(LFGuildPlayer settings, uint faction) + { + List resultSet = new List(); + foreach (var guildSettings in _guildSettings.Values) + { + if (!guildSettings.IsListed()) + continue; + + if (guildSettings.GetTeam() != faction) + continue; + + if (!Convert.ToBoolean(guildSettings.GetAvailability() & settings.GetAvailability())) + continue; + + if (!Convert.ToBoolean(guildSettings.GetClassRoles() & settings.GetClassRoles())) + continue; + + if (!Convert.ToBoolean(guildSettings.GetInterests() & settings.GetInterests())) + continue; + + if (!Convert.ToBoolean(guildSettings.GetLevel() & settings.GetLevel())) + continue; + + resultSet.Add(guildSettings); + } + + return resultSet; + } + + public bool HasRequest(ObjectGuid playerId, ObjectGuid guildId) + { + var guildDic = _membershipRequestsByGuild.LookupByKey(guildId); + if (guildDic == null) + return false; + + return guildDic.ContainsKey(playerId); + } + + public void SetGuildSettings(ObjectGuid guildGuid, LFGuildSettings settings) + { + _guildSettings[guildGuid] = settings; + + SQLTransaction trans = new SQLTransaction(); + + PreparedStatement stmt = DB.Characters.GetPreparedStatement(CharStatements.REP_GUILD_FINDER_GUILD_SETTINGS); + stmt.AddValue(0, settings.GetGUID()); + stmt.AddValue(1, settings.GetAvailability()); + stmt.AddValue(2, settings.GetClassRoles()); + stmt.AddValue(3, settings.GetInterests()); + stmt.AddValue(4, settings.GetLevel()); + stmt.AddValue(5, settings.IsListed()); + stmt.AddValue(6, settings.GetComment()); + trans.Append(stmt); + + DB.Characters.CommitTransaction(trans); + } + + public void DeleteGuild(ObjectGuid guildId) + { + SQLTransaction trans = new SQLTransaction(); + PreparedStatement stmt; + var guildDic = _membershipRequestsByGuild.LookupByKey(guildId); + if (guildDic != null) + { + foreach (var guid in guildDic.Keys) + { + stmt = DB.Characters.GetPreparedStatement(CharStatements.DEL_GUILD_FINDER_APPLICANT); + stmt.AddValue(0, guildId.GetCounter()); + stmt.AddValue(1, guid.GetCounter()); + trans.Append(stmt); + + if (_membershipRequestsByPlayer.ContainsKey(guid)) + { + var playerDic = _membershipRequestsByPlayer.LookupByKey(guid); + playerDic.Remove(guildId); + if (playerDic.Empty()) + _membershipRequestsByPlayer.Remove(guid); + } + + // Notify the applicant his submition has been removed + Player player = Global.ObjAccessor.FindPlayer(guid); + if (player) + SendMembershipRequestListUpdate(player); + } + } + + stmt = DB.Characters.GetPreparedStatement(CharStatements.DEL_GUILD_FINDER_GUILD_SETTINGS); + stmt.AddValue(0, guildId.GetCounter()); + trans.Append(stmt); + + DB.Characters.CommitTransaction(trans); + + _membershipRequestsByGuild.Remove(guildId); + _guildSettings.Remove(guildId); + + // Notify the guild master the list changed (even if he's not a GM any more, not sure if needed) + Guild guild = Global.GuildMgr.GetGuildById(guildId.GetCounter()); + if (guild) + SendApplicantListUpdate(guild); + } + + void SendApplicantListUpdate(Guild guild) + { + LFGuildApplicantListChanged applicantListChanged = new LFGuildApplicantListChanged(); + + guild.BroadcastPacketToRank(applicantListChanged, GuildDefaultRanks.Officer); + + Player player = Global.ObjAccessor.FindPlayer(guild.GetLeaderGUID()); + if (player) + player.SendPacket(applicantListChanged, false); + } + + void SendMembershipRequestListUpdate(Player player) + { + player.SendPacket(new LFGuildApplicationsListChanged()); + } + + public Dictionary GetAllMembershipRequestsForGuild(ObjectGuid guildGuid) { return _membershipRequestsByGuild.LookupByKey(guildGuid); } + + public LFGuildSettings GetGuildSettings(ObjectGuid guildGuid) { return _guildSettings.LookupByKey(guildGuid); } + + Dictionary _guildSettings = new Dictionary(); + Dictionary> _membershipRequestsByGuild = new Dictionary>(); + Dictionary> _membershipRequestsByPlayer = new Dictionary>(); + } + + public class MembershipRequest + { + public MembershipRequest(MembershipRequest settings) + { + _comment = settings.GetComment(); + + _availability = settings.GetAvailability(); + _classRoles = settings.GetClassRoles(); + _interests = settings.GetInterests(); + _guildId = settings.GetGuildGuid(); + _playerGUID = settings.GetPlayerGUID(); + _time = settings.GetSubmitTime(); + } + + public MembershipRequest(ObjectGuid playerGUID, ObjectGuid guildId, uint availability, uint classRoles, uint interests, string comment, long submitTime) + { + _comment = comment; + _guildId = guildId; + _playerGUID = playerGUID; + _availability = (byte)availability; + _classRoles = (byte)classRoles; + _interests = (byte)interests; + _time = submitTime; + } + + public MembershipRequest() + { + _time = Time.UnixTime; + } + + public ObjectGuid GetGuildGuid() { return _guildId; } + public ObjectGuid GetPlayerGUID() { return _playerGUID; } + public byte GetAvailability() { return _availability; } + public byte GetClassRoles() { return _classRoles; } + public byte GetInterests() { return _interests; } + public long GetSubmitTime() { return _time; } + public long GetExpiryTime() { return _time + 30 * 24 * 3600; } // Adding 30 days + public string GetComment() { return _comment; } + + string _comment = ""; + + ObjectGuid _guildId; + ObjectGuid _playerGUID; + + byte _availability; + byte _classRoles; + byte _interests; + + long _time; + } + + public class LFGuildPlayer + { + public LFGuildPlayer() + { + _guid = ObjectGuid.Empty; + _roles = 0; + _availability = 0; + _interests = 0; + _level = 0; + } + + public LFGuildPlayer(ObjectGuid guid, uint role, uint availability, uint interests, uint level) + { + _guid = guid; + _roles = (byte)role; + _availability = (byte)availability; + _interests = (byte)interests; + _level = (byte)level; + } + + public LFGuildPlayer(ObjectGuid guid, uint role, uint availability, uint interests, uint level, string comment) + { + _comment = comment; + + _guid = guid; + _roles = (byte)role; + _availability = (byte)availability; + _interests = (byte)interests; + _level = (byte)level; + } + + public LFGuildPlayer(LFGuildPlayer settings) + { + _comment = settings.GetComment(); + _guid = settings.GetGUID(); + _roles = settings.GetClassRoles(); + _availability = settings.GetAvailability(); + _interests = settings.GetInterests(); + _level = settings.GetLevel(); + } + + public ObjectGuid GetGUID() { return _guid; } + public byte GetClassRoles() { return _roles; } + public byte GetAvailability() { return _availability; } + public byte GetInterests() { return _interests; } + public byte GetLevel() { return _level; } + public string GetComment() { return _comment; } + + string _comment = ""; + ObjectGuid _guid; + byte _roles; + byte _availability; + byte _interests; + byte _level; + } + + public class LFGuildSettings : LFGuildPlayer + { + public LFGuildSettings() + { + _listed = false; + _team = TeamId.Alliance; + } + + public LFGuildSettings(bool listed, uint team) + { + _listed = listed; + _team = team; + } + + public LFGuildSettings(bool listed, uint team, ObjectGuid guid, uint role, uint availability, uint interests, uint level) + : base(guid, role, availability, interests, level) + { + _listed = listed; + _team = team; + } + + public LFGuildSettings(bool listed, uint team, ObjectGuid guid, uint role, uint availability, uint interests, uint level, string comment) + : base(guid, role, availability, interests, level, comment) + { + _listed = listed; + _team = team; + } + + public LFGuildSettings(LFGuildSettings settings) + : base(settings) + { + _listed = settings.IsListed(); + _team = settings.GetTeam(); + } + + public bool IsListed() { return _listed; } + void SetListed(bool state) { _listed = state; } + + public uint GetTeam() { return _team; } + + bool _listed; + uint _team; + } +} diff --git a/Game/Guilds/GuildManager.cs b/Game/Guilds/GuildManager.cs new file mode 100644 index 000000000..b47b8bf59 --- /dev/null +++ b/Game/Guilds/GuildManager.cs @@ -0,0 +1,531 @@ +/* + * Copyright (C) 2012-2017 CypherCore + * + * 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, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +using Framework.Constants; +using Framework.Database; +using Game.DataStorage; +using Game.Entities; +using Game.Guilds; +using System.Collections.Generic; +using System.Linq; + +namespace Game +{ + public sealed class GuildManager : Singleton + { + GuildManager() { } + + public void AddGuild(Guild guild) + { + GuildStore[guild.GetId()] = guild; + } + + public void RemoveGuild(ulong guildId) + { + GuildStore.Remove(guildId); + } + + public void SaveGuilds() + { + foreach (var guild in GuildStore.Values) + guild.SaveToDB(); + } + + public uint GenerateGuildId() + { + if (NextGuildId >= 0xFFFFFFFE) + { + Log.outError(LogFilter.Guild, "Guild ids overflow!! Can't continue, shutting down server. "); + Global.WorldMgr.StopNow(); + } + return NextGuildId++; + } + + public Guild GetGuildById(ulong guildId) + { + return GuildStore.LookupByKey(guildId); + } + + public Guild GetGuildByGuid(ObjectGuid guid) + { + // Full guids are only used when receiving/sending data to client + // everywhere else guild id is used + if (guid.IsGuild()) + { + ulong guildId = guid.GetCounter(); + if (guildId != 0) + return GetGuildById(guildId); + } + + return null; + } + + public Guild GetGuildByName(string guildName) + { + foreach (var guild in GuildStore.Values) + { + if (guildName == guild.GetName()) + return guild; + } + return null; + } + + public string GetGuildNameById(uint guildId) + { + Guild guild = GetGuildById(guildId); + if (guild != null) + return guild.GetName(); + + return ""; + } + + public Guild GetGuildByLeader(ObjectGuid guid) + { + foreach (var guild in GuildStore.Values) + if (guild.GetLeaderGUID() == guid) + return guild; + + return null; + } + + public void LoadGuilds() + { + Log.outInfo(LogFilter.ServerLoading, "Loading Guilds Definitions..."); + { + uint oldMSTime = Time.GetMSTime(); + + // 0 1 2 3 4 5 6 + SQLResult result = DB.Characters.Query("SELECT g.guildid, g.name, g.leaderguid, g.EmblemStyle, g.EmblemColor, g.BorderStyle, g.BorderColor, " + + // 7 8 9 10 11 12 + "g.BackgroundColor, g.info, g.motd, g.createdate, g.BankMoney, COUNT(gbt.guildid) " + + "FROM guild g LEFT JOIN guild_bank_tab gbt ON g.guildid = gbt.guildid GROUP BY g.guildid ORDER BY g.guildid ASC"); + + if (result.IsEmpty()) + { + Log.outError(LogFilter.Guild, "Loaded 0 guild definitions. DB table `guild` is empty."); + return; + } + + uint count = 0; + do + { + Guild guild = new Guild(); + + if (!guild.LoadFromDB(result.GetFields())) + continue; + + AddGuild(guild); + count++; + } while (result.NextRow()); + Log.outInfo(LogFilter.ServerLoading, "Loaded {0} guild definitions in {1} ms", count, Time.GetMSTimeDiffToNow(oldMSTime)); + } + + Log.outInfo(LogFilter.ServerLoading, "Loading guild ranks..."); + { + uint oldMSTime = Time.GetMSTime(); + + // Delete orphaned guild rank entries before loading the valid ones + DB.Characters.Execute("DELETE gr FROM guild_rank gr LEFT JOIN guild g ON gr.guildId = g.guildId WHERE g.guildId IS NULL"); + + // 0 1 2 3 4 + SQLResult result = DB.Characters.Query("SELECT guildid, rid, rname, rights, BankMoneyPerDay FROM guild_rank ORDER BY guildid ASC, rid ASC"); + + if (result.IsEmpty()) + { + Log.outInfo(LogFilter.ServerLoading, "Loaded 0 guild ranks. DB table `guild_rank` is empty."); + } + else + { + + uint count = 0; + do + { + uint guildId = result.Read(0); + Guild guild = GetGuildById(guildId); + if (guild) + guild.LoadRankFromDB(result.GetFields()); + + ++count; + } + while (result.NextRow()); + + Log.outInfo(LogFilter.ServerLoading, "Loaded {0} guild ranks in {1} ms", count, Time.GetMSTimeDiffToNow(oldMSTime)); + } + } + + // 3. Load all guild members + Log.outInfo(LogFilter.ServerLoading, "Loading guild members..."); + { + uint oldMSTime = Time.GetMSTime(); + + // Delete orphaned guild member entries before loading the valid ones + DB.Characters.Execute("DELETE gm FROM guild_member gm LEFT JOIN guild g ON gm.guildId = g.guildId WHERE g.guildId IS NULL"); + DB.Characters.Execute("DELETE gm FROM guild_member_withdraw gm LEFT JOIN guild_member g ON gm.guid = g.guid WHERE g.guid IS NULL"); + + // 0 1 2 3 4 5 6 7 8 9 10 + SQLResult result = DB.Characters.Query("SELECT gm.guildid, gm.guid, rank, pnote, offnote, w.tab0, w.tab1, w.tab2, w.tab3, w.tab4, w.tab5, " + + // 11 12 13 14 15 16 17 18 19 20 + "w.tab6, w.tab7, w.money, c.name, c.level, c.class, c.gender, c.zone, c.account, c.logout_time " + + "FROM guild_member gm LEFT JOIN guild_member_withdraw w ON gm.guid = w.guid " + + "LEFT JOIN characters c ON c.guid = gm.guid ORDER BY gm.guildid ASC"); + + if (result.IsEmpty()) + Log.outInfo(LogFilter.ServerLoading, "Loaded 0 guild members. DB table `guild_member` is empty."); + else + { + uint count = 0; + + do + { + uint guildId = result.Read(0); + Guild guild = GetGuildById(guildId); + if (guild) + guild.LoadMemberFromDB(result.GetFields()); + + ++count; + } + while (result.NextRow()); + + Log.outInfo(LogFilter.ServerLoading, "Loaded {0} guild members in {1} ms", count, Time.GetMSTimeDiffToNow(oldMSTime)); + } + } + + // 4. Load all guild bank tab rights + Log.outInfo(LogFilter.ServerLoading, "Loading bank tab rights..."); + { + uint oldMSTime = Time.GetMSTime(); + + // Delete orphaned guild bank right entries before loading the valid ones + DB.Characters.Execute("DELETE gbr FROM guild_bank_right gbr LEFT JOIN guild g ON gbr.guildId = g.guildId WHERE g.guildId IS NULL"); + + // 0 1 2 3 4 + SQLResult result = DB.Characters.Query("SELECT guildid, TabId, rid, gbright, SlotPerDay FROM guild_bank_right ORDER BY guildid ASC, TabId ASC"); + + if (result.IsEmpty()) + { + Log.outInfo(LogFilter.ServerLoading, "Loaded 0 guild bank tab rights. DB table `guild_bank_right` is empty."); + } + else + { + uint count = 0; + do + { + uint guildId = result.Read(0); + Guild guild = GetGuildById(guildId); + if (guild) + guild.LoadBankRightFromDB(result.GetFields()); + + ++count; + } + while (result.NextRow()); + + Log.outInfo(LogFilter.ServerLoading, "Loaded {0} bank tab rights in {1} ms", count, Time.GetMSTimeDiffToNow(oldMSTime)); + } + } + + // 5. Load all event logs + Log.outInfo(LogFilter.ServerLoading, "Loading guild event logs..."); + { + uint oldMSTime = Time.GetMSTime(); + + DB.Characters.Execute("DELETE FROM guild_eventlog WHERE LogGuid > {0}", GuildConst.EventLogMaxRecords); + + // 0 1 2 3 4 5 6 + SQLResult result = DB.Characters.Query("SELECT guildid, LogGuid, EventType, PlayerGuid1, PlayerGuid2, NewRank, TimeStamp FROM guild_eventlog ORDER BY TimeStamp DESC, LogGuid DESC"); + + if (result.IsEmpty()) + { + Log.outInfo(LogFilter.ServerLoading, "Loaded 0 guild event logs. DB table `guild_eventlog` is empty."); + } + else + { + uint count = 0; + do + { + uint guildId = result.Read(0); + Guild guild = GetGuildById(guildId); + if (guild) + guild.LoadEventLogFromDB(result.GetFields()); + + ++count; + } + while (result.NextRow()); + + Log.outInfo(LogFilter.ServerLoading, "Loaded {0} guild event logs in {1} ms", count, Time.GetMSTimeDiffToNow(oldMSTime)); + } + } + + // 6. Load all bank event logs + Log.outInfo(LogFilter.ServerLoading, "Loading guild bank event logs..."); + { + uint oldMSTime = Time.GetMSTime(); + + // Remove log entries that exceed the number of allowed entries per guild + DB.Characters.Execute("DELETE FROM guild_bank_eventlog WHERE LogGuid > {0}", GuildConst.BankLogMaxRecords); + + // 0 1 2 3 4 5 6 7 8 + SQLResult result = DB.Characters.Query("SELECT guildid, TabId, LogGuid, EventType, PlayerGuid, ItemOrMoney, ItemStackCount, DestTabId, TimeStamp FROM guild_bank_eventlog ORDER BY TimeStamp DESC, LogGuid DESC"); + + if (result.IsEmpty()) + { + Log.outInfo(LogFilter.ServerLoading, "Loaded 0 guild bank event logs. DB table `guild_bank_eventlog` is empty."); + } + else + { + uint count = 0; + do + { + uint guildId = result.Read(0); + Guild guild = GetGuildById(guildId); + if (guild) + guild.LoadBankEventLogFromDB(result.GetFields()); + + ++count; + } + while (result.NextRow()); + + Log.outInfo(LogFilter.ServerLoading, "Loaded {0} guild bank event logs in {1} ms", count, Time.GetMSTimeDiffToNow(oldMSTime)); + } + } + + // 7. Load all news event logs + Log.outInfo(LogFilter.ServerLoading, "Loading Guild News..."); + { + uint oldMSTime = Time.GetMSTime(); + + DB.Characters.Execute("DELETE FROM guild_newslog WHERE LogGuid > {0}", GuildConst.NewsLogMaxRecords); + + // 0 1 2 3 4 5 6 + SQLResult result = DB.Characters.Query("SELECT guildid, LogGuid, EventType, PlayerGuid, Flags, Value, Timestamp FROM guild_newslog ORDER BY TimeStamp DESC, LogGuid DESC"); + + if (result.IsEmpty()) + Log.outInfo(LogFilter.ServerLoading, "Loaded 0 guild event logs. DB table `guild_newslog` is empty."); + else + { + uint count = 0; + do + { + uint guildId = result.Read(0); + Guild guild = GetGuildById(guildId); + if (guild) + guild.LoadGuildNewsLogFromDB(result.GetFields()); + + ++count; + } + while (result.NextRow()); + + Log.outInfo(LogFilter.ServerLoading, "Loaded {0} guild new logs in {1} ms", count, Time.GetMSTimeDiffToNow(oldMSTime)); + } + } + + // 8. Load all guild bank tabs + Log.outInfo(LogFilter.ServerLoading, "Loading guild bank tabs..."); + { + uint oldMSTime = Time.GetMSTime(); + + // Delete orphaned guild bank tab entries before loading the valid ones + DB.Characters.Execute("DELETE gbt FROM guild_bank_tab gbt LEFT JOIN guild g ON gbt.guildId = g.guildId WHERE g.guildId IS NULL"); + + // 0 1 2 3 4 + SQLResult result = DB.Characters.Query("SELECT guildid, TabId, TabName, TabIcon, TabText FROM guild_bank_tab ORDER BY guildid ASC, TabId ASC"); + + if (result.IsEmpty()) + { + Log.outInfo(LogFilter.ServerLoading, "Loaded 0 guild bank tabs. DB table `guild_bank_tab` is empty."); + } + else + { + uint count = 0; + do + { + uint guildId = result.Read(0); + Guild guild = GetGuildById(guildId); + if (guild) + guild.LoadBankTabFromDB(result.GetFields()); + + ++count; + } + while (result.NextRow()); + + Log.outInfo(LogFilter.ServerLoading, "Loaded {0} guild bank tabs in {1} ms", count, Time.GetMSTimeDiffToNow(oldMSTime)); + } + } + + // 9. Fill all guild bank tabs + Log.outInfo(LogFilter.ServerLoading, "Filling bank tabs with items..."); + { + uint oldMSTime = Time.GetMSTime(); + + // Delete orphan guild bank items + DB.Characters.Execute("DELETE gbi FROM guild_bank_item gbi LEFT JOIN guild g ON gbi.guildId = g.guildId WHERE g.guildId IS NULL"); + + SQLResult result = DB.Characters.Query(DB.Characters.GetPreparedStatement(CharStatements.SEL_GUILD_BANK_ITEMS)); + if (result.IsEmpty()) + { + Log.outInfo(LogFilter.ServerLoading, "Loaded 0 guild bank tab items. DB table `guild_bank_item` or `item_instance` is empty."); + } + else + { + uint count = 0; + do + { + ulong guildId = result.Read(45); + Guild guild = GetGuildById(guildId); + if (guild) + guild.LoadBankItemFromDB(result.GetFields()); + + ++count; + } + while (result.NextRow()); + + Log.outInfo(LogFilter.ServerLoading, "Loaded {0} guild bank tab items in {1} ms", count, Time.GetMSTimeDiffToNow(oldMSTime)); + } + } + + // 10. Load guild achievements + Log.outInfo(LogFilter.ServerLoading, "Loading guild achievements..."); + { + uint oldMSTime = Time.GetMSTime(); + + long achievementCount = 0; + long criteriaCount = 0; + + SQLResult achievementResult; + SQLResult criteriaResult; + foreach (var pair in GuildStore) + { + PreparedStatement stmt = DB.Characters.GetPreparedStatement(CharStatements.SEL_GUILD_ACHIEVEMENT); + stmt.AddValue(0, pair.Key); + achievementResult = DB.Characters.Query(stmt); + + stmt = DB.Characters.GetPreparedStatement(CharStatements.SEL_GUILD_ACHIEVEMENT_CRITERIA); + stmt.AddValue(0, pair.Key); + criteriaResult = DB.Characters.Query(stmt); + + if (!achievementResult.IsEmpty()) + achievementCount += achievementResult.GetRowCount(); + if (!criteriaResult.IsEmpty()) + criteriaCount += criteriaResult.GetRowCount(); + + pair.Value.GetAchievementMgr().LoadFromDB(achievementResult, criteriaResult); + } + + Log.outInfo(LogFilter.ServerLoading, "Loaded {0} guild achievements and {1} criterias in {2} ms", achievementCount, criteriaCount, Time.GetMSTimeDiffToNow(oldMSTime)); + } + + // 11. Validate loaded guild data + Log.outInfo(LogFilter.Server, "Validating data of loaded guilds..."); + { + uint oldMSTime = Time.GetMSTime(); + + foreach (var guild in GuildStore.ToList()) + { + if (!guild.Value.Validate()) + GuildStore.Remove(guild.Key); + } + + Log.outInfo(LogFilter.ServerLoading, "Validated data of loaded guilds in {0} ms", Time.GetMSTimeDiffToNow(oldMSTime)); + } + } + + public void LoadGuildRewards() + { + uint oldMSTime = Time.GetMSTime(); + + // 0 1 2 3 + SQLResult result = DB.World.Query("SELECT ItemID, MinGuildRep, RaceMask, Cost FROM guild_rewards"); + + if (result.IsEmpty()) + { + Log.outError(LogFilter.ServerLoading, "Loaded 0 guild reward definitions. DB table `guild_rewards` is empty."); + return; + } + + uint count = 0; + do + { + GuildReward reward = new GuildReward(); + + reward.ItemID = result.Read(0); + reward.MinGuildRep = result.Read(1); + reward.RaceMask = result.Read(2); + reward.Cost = result.Read(3); + + if (Global.ObjectMgr.GetItemTemplate(reward.ItemID) == null) + { + Log.outError(LogFilter.ServerLoading, "Guild rewards constains not existing item entry {0}", reward.ItemID); + continue; + } + + if (reward.MinGuildRep >= (int)ReputationRank.Max) + { + Log.outError(LogFilter.ServerLoading, "Guild rewards contains wrong reputation standing {0}, max is {1}", reward.MinGuildRep, (int)ReputationRank.Max - 1); + continue; + } + + PreparedStatement stmt = DB.World.GetPreparedStatement(WorldStatements.SEL_GUILD_REWARDS_REQ_ACHIEVEMENTS); + stmt.AddValue(0, reward.ItemID); + SQLResult reqAchievementResult = DB.World.Query(stmt); + if (!reqAchievementResult.IsEmpty()) + { + do + { + uint requiredAchievementId = reqAchievementResult.Read(0); + if (!CliDB.AchievementStorage.ContainsKey(requiredAchievementId)) + { + Log.outError(LogFilter.ServerLoading, "Guild rewards constains not existing achievement entry {0}", requiredAchievementId); + continue; + } + + reward.AchievementsRequired.Add(requiredAchievementId); + } while (reqAchievementResult.NextRow()); + } + + guildRewards.Add(reward); + ++count; + } while (result.NextRow()); + + Log.outInfo(LogFilter.ServerLoading, "Loaded {0} guild reward definitions in {1} ms", count, Time.GetMSTimeDiffToNow(oldMSTime)); + } + + public void ResetTimes(bool week) + { + DB.Characters.Execute(DB.Characters.GetPreparedStatement(CharStatements.DEL_GUILD_MEMBER_WITHDRAW)); + + foreach (var guild in GuildStore.Values) + guild.ResetTimes(week); + } + + public void SetNextGuildId(uint Id) { NextGuildId = Id; } + + public List GetGuildRewards() { return guildRewards; } + + + uint NextGuildId; + Dictionary GuildStore = new Dictionary(); + List guildRewards = new List(); + } + + public class GuildReward + { + public uint ItemID; + public byte MinGuildRep; + public int RaceMask; + public ulong Cost; + public List AchievementsRequired = new List(); + } +} diff --git a/Game/Handlers/ArenaHandler.cs b/Game/Handlers/ArenaHandler.cs new file mode 100644 index 000000000..ebed62623 --- /dev/null +++ b/Game/Handlers/ArenaHandler.cs @@ -0,0 +1,33 @@ +/* + * Copyright (C) 2012-2017 CypherCore + * + * 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, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +using Framework.Constants; +using Game.Network.Packets; + +namespace Game +{ + public partial class WorldSession + { + void SendNotInArenaTeamPacket(ArenaTypes type) + { + ArenaError arenaError = new ArenaError(); + arenaError.ErrorType = ArenaErrorType.NoTeam; + arenaError.TeamSize = (byte)type; // team type (2=2v2, 3=3v3, 5=5v5), can be used for custom types... + SendPacket(arenaError); + } + } +} diff --git a/Game/Handlers/ArtifactHandler.cs b/Game/Handlers/ArtifactHandler.cs new file mode 100644 index 000000000..31025ebc2 --- /dev/null +++ b/Game/Handlers/ArtifactHandler.cs @@ -0,0 +1,236 @@ +/* + * Copyright (C) 2012-2017 CypherCore + * + * 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, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +using Framework.Constants; +using Game.DataStorage; +using Game.Entities; +using Game.Network; +using Game.Network.Packets; +using System; +using System.Collections.Generic; + +namespace Game +{ + public partial class WorldSession + { + [WorldPacketHandler(ClientOpcodes.ArtifactAddPower)] + void HandleArtifactAddPower(ArtifactAddPower artifactAddPower) + { + if (!_player.GetGameObjectIfCanInteractWith(artifactAddPower.ForgeGUID, GameObjectTypes.ArtifactForge)) + return; + + Item artifact = _player.GetItemByGuid(artifactAddPower.ArtifactGUID); + if (!artifact) + return; + + ulong xpCost = 0; + GtArtifactLevelXPRecord cost = CliDB.ArtifactLevelXPGameTable.GetRow(artifact.GetTotalPurchasedArtifactPowers() + 1); + if (cost != null) + xpCost = (ulong)(artifact.GetModifier(ItemModifier.ArtifactTier) == 1 ? cost.XP2 : cost.XP); + + if (xpCost > artifact.GetUInt64Value(ItemFields.ArtifactXp)) + return; + + if (artifactAddPower.PowerChoices.Empty()) + return; + + ItemDynamicFieldArtifactPowers artifactPower = artifact.GetArtifactPower(artifactAddPower.PowerChoices[0].ArtifactPowerID); + if (artifactPower == null) + return; + + ArtifactPowerRecord artifactPowerEntry = CliDB.ArtifactPowerStorage.LookupByKey(artifactPower.ArtifactPowerId); + if (artifactPowerEntry == null) + return; + + if (artifactAddPower.PowerChoices[0].Rank != artifactPower.PurchasedRank + 1 || + artifactAddPower.PowerChoices[0].Rank > artifactPowerEntry.MaxRank) + return; + + var artifactPowerLinks = Global.DB2Mgr.GetArtifactPowerLinks(artifactPower.ArtifactPowerId); + if (artifactPowerLinks != null) + { + bool hasAnyLink = false; + foreach (uint artifactPowerLinkId in artifactPowerLinks) + { + ArtifactPowerRecord artifactPowerLink = CliDB.ArtifactPowerStorage.LookupByKey(artifactPowerLinkId); + if (artifactPowerLink == null) + continue; + + ItemDynamicFieldArtifactPowers artifactPowerLinkLearned = artifact.GetArtifactPower(artifactPowerLinkId); + if (artifactPowerLinkLearned == null) + continue; + + if (artifactPowerLinkLearned.PurchasedRank >= artifactPowerLink.MaxRank) + { + hasAnyLink = true; + break; + } + } + + if (!hasAnyLink) + return; + } + + ArtifactPowerRankRecord artifactPowerRank = Global.DB2Mgr.GetArtifactPowerRank(artifactPower.ArtifactPowerId, (byte)(artifactPower.CurrentRankWithBonus + 1 - 1)); // need data for next rank, but -1 because of how db2 data is structured + if (artifactPowerRank == null) + return; + + ItemDynamicFieldArtifactPowers newPower = artifactPower; + ++newPower.PurchasedRank; + ++newPower.CurrentRankWithBonus; + artifact.SetArtifactPower(newPower); + + if (artifact.IsEquipped()) + { + _player.ApplyArtifactPowerRank(artifact, artifactPowerRank, true); + + foreach (ItemDynamicFieldArtifactPowers power in artifact.GetArtifactPowers()) + { + ArtifactPowerRecord scaledArtifactPowerEntry = CliDB.ArtifactPowerStorage.LookupByKey(power.ArtifactPowerId); + if (!scaledArtifactPowerEntry.Flags.HasAnyFlag(ArtifactPowerFlag.ScalesWithNumPowers)) + continue; + + ArtifactPowerRankRecord scaledArtifactPowerRank = Global.DB2Mgr.GetArtifactPowerRank(scaledArtifactPowerEntry.Id, 0); + if (scaledArtifactPowerRank == null) + continue; + + ItemDynamicFieldArtifactPowers newScaledPower = power; + ++newScaledPower.CurrentRankWithBonus; + artifact.SetArtifactPower(newScaledPower); + + _player.ApplyArtifactPowerRank(artifact, scaledArtifactPowerRank, false); + _player.ApplyArtifactPowerRank(artifact, scaledArtifactPowerRank, true); + } + } + + artifact.SetUInt64Value(ItemFields.ArtifactXp, artifact.GetUInt64Value(ItemFields.ArtifactXp) - xpCost); + artifact.SetState(ItemUpdateState.Changed, _player); + } + + [WorldPacketHandler(ClientOpcodes.ArtifactSetAppearance)] + void HandleArtifactSetAppearance(ArtifactSetAppearance artifactSetAppearance) + { + if (!_player.GetGameObjectIfCanInteractWith(artifactSetAppearance.ForgeGUID, GameObjectTypes.ArtifactForge)) + return; + + ArtifactAppearanceRecord artifactAppearance = CliDB.ArtifactAppearanceStorage.LookupByKey(artifactSetAppearance.ArtifactAppearanceID); + if (artifactAppearance == null) + return; + + Item artifact = _player.GetItemByGuid(artifactSetAppearance.ArtifactGUID); + if (!artifact) + return; + + ArtifactAppearanceSetRecord artifactAppearanceSet = CliDB.ArtifactAppearanceSetStorage.LookupByKey(artifactAppearance.ArtifactAppearanceSetID); + if (artifactAppearanceSet == null || artifactAppearanceSet.ArtifactID != artifact.GetTemplate().GetArtifactID()) + return; + + PlayerConditionRecord playerCondition = CliDB.PlayerConditionStorage.LookupByKey(artifactAppearance.PlayerConditionID); + if (playerCondition != null) + if (!ConditionManager.IsPlayerMeetingCondition(_player, playerCondition)) + return; + + artifact.SetAppearanceModId(artifactAppearance.AppearanceModID); + artifact.SetModifier(ItemModifier.ArtifactAppearanceId, artifactAppearance.Id); + artifact.SetState(ItemUpdateState.Changed, _player); + Item childItem = _player.GetChildItemByGuid(artifact.GetChildItem()); + if (childItem) + { + childItem.SetAppearanceModId(artifactAppearance.AppearanceModID); + childItem.SetState(ItemUpdateState.Changed, _player); + } + + if (artifact.IsEquipped()) + { + // change weapon appearance + _player.SetVisibleItemSlot(artifact.GetSlot(), artifact); + if (childItem) + _player.SetVisibleItemSlot(childItem.GetSlot(), childItem); + + // change druid form appearance + if (artifactAppearance.ShapeshiftDisplayID != 0 && artifactAppearance.ModifiesShapeshiftFormDisplay != 0 && _player.GetShapeshiftForm() == (ShapeShiftForm)artifactAppearance.ModifiesShapeshiftFormDisplay) + _player.RestoreDisplayId(); + } + } + + [WorldPacketHandler(ClientOpcodes.ConfirmArtifactRespec)] + void HandleConfirmArtifactRespec(ConfirmArtifactRespec confirmArtifactRespec) + { + if (!_player.GetNPCIfCanInteractWith(confirmArtifactRespec.NpcGUID, NPCFlags.ArtifactPowerRespec)) + return; + + Item artifact = _player.GetItemByGuid(confirmArtifactRespec.ArtifactGUID); + if (!artifact) + return; + + ulong xpCost = 0; + GtArtifactLevelXPRecord cost = CliDB.ArtifactLevelXPGameTable.GetRow(artifact.GetTotalPurchasedArtifactPowers() + 1); + if (cost != null) + xpCost = (ulong)(artifact.GetModifier(ItemModifier.ArtifactTier) == 1 ? cost.XP2 : cost.XP); + + if (xpCost > artifact.GetUInt64Value(ItemFields.ArtifactXp)) + return; + + ulong newAmount = artifact.GetUInt64Value(ItemFields.ArtifactXp) - xpCost; + for (uint i = 0; i <= artifact.GetTotalPurchasedArtifactPowers(); ++i) + { + GtArtifactLevelXPRecord cost1 = CliDB.ArtifactLevelXPGameTable.GetRow(i); + if (cost1 != null) + newAmount += (ulong)(artifact.GetModifier(ItemModifier.ArtifactTier) == 1 ? cost1.XP2 : cost1.XP); + } + + foreach (ItemDynamicFieldArtifactPowers artifactPower in artifact.GetArtifactPowers()) + { + byte oldPurchasedRank = artifactPower.PurchasedRank; + if (oldPurchasedRank == 0) + continue; + + ItemDynamicFieldArtifactPowers newPower = artifactPower; + newPower.PurchasedRank -= oldPurchasedRank; + newPower.CurrentRankWithBonus -= oldPurchasedRank; + artifact.SetArtifactPower(newPower); + + if (artifact.IsEquipped()) + { + ArtifactPowerRankRecord artifactPowerRank = Global.DB2Mgr.GetArtifactPowerRank(artifactPower.ArtifactPowerId, 0); + if (artifactPowerRank != null) + _player.ApplyArtifactPowerRank(artifact, artifactPowerRank, false); + } + } + + foreach (ItemDynamicFieldArtifactPowers power in artifact.GetArtifactPowers()) + { + ArtifactPowerRecord scaledArtifactPowerEntry = CliDB.ArtifactPowerStorage.LookupByKey(power.ArtifactPowerId); + if (!scaledArtifactPowerEntry.Flags.HasAnyFlag(ArtifactPowerFlag.ScalesWithNumPowers)) + continue; + + ArtifactPowerRankRecord scaledArtifactPowerRank = Global.DB2Mgr.GetArtifactPowerRank(scaledArtifactPowerEntry.Id, 0); + if (scaledArtifactPowerRank == null) + continue; + + ItemDynamicFieldArtifactPowers newScaledPower = power; + newScaledPower.CurrentRankWithBonus = 0; + artifact.SetArtifactPower(newScaledPower); + + _player.ApplyArtifactPowerRank(artifact, scaledArtifactPowerRank, false); + } + + artifact.SetUInt64Value(ItemFields.ArtifactXp, newAmount); + artifact.SetState(ItemUpdateState.Changed, _player); + } + } +} diff --git a/Game/Handlers/AuctionHandler.cs b/Game/Handlers/AuctionHandler.cs new file mode 100644 index 000000000..724a98fc7 --- /dev/null +++ b/Game/Handlers/AuctionHandler.cs @@ -0,0 +1,673 @@ +/* + * Copyright (C) 2012-2017 CypherCore + * + * 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, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +using Framework.Constants; +using Framework.Database; +using Framework.Dynamic; +using Game.DataStorage; +using Game.Entities; +using Game.Mails; +using Game.Network; +using Game.Network.Packets; +using System; +using System.Collections.Generic; + +namespace Game +{ + public partial class WorldSession + { + [WorldPacketHandler(ClientOpcodes.AuctionHelloRequest)] + void HandleAuctionHelloOpcode(AuctionHelloRequest packet) + { + Creature unit = GetPlayer().GetNPCIfCanInteractWith(packet.Guid, NPCFlags.Auctioneer); + if (!unit) + { + Log.outDebug(LogFilter.Network, "WORLD: HandleAuctionHelloOpcode - {0} not found or you can't interact with him.", packet.Guid.ToString()); + return; + } + + // remove fake death + if (GetPlayer().HasUnitState(UnitState.Died)) + GetPlayer().RemoveAurasByType(AuraType.FeignDeath); + + SendAuctionHello(packet.Guid, unit); + } + + public void SendAuctionHello(ObjectGuid guid, Creature unit) + { + if (GetPlayer().getLevel() < WorldConfig.GetIntValue(WorldCfg.AuctionLevelReq)) + { + SendNotification(Global.ObjectMgr.GetCypherString(CypherStrings.AuctionReq), WorldConfig.GetIntValue(WorldCfg.AuctionLevelReq)); + return; + } + + AuctionHouseRecord ahEntry = Global.AuctionMgr.GetAuctionHouseEntry(unit.getFaction()); + if (ahEntry == null) + return; + + AuctionHelloResponse packet = new AuctionHelloResponse(); + packet.Guid = guid; + packet.OpenForBusiness = true; // 3.3.3: 1 - AH enabled, 0 - AH disabled + SendPacket(packet); + } + + public void SendAuctionCommandResult(AuctionEntry auction, AuctionAction action, AuctionError errorCode, uint bidError = 0) + { + AuctionCommandResult auctionCommandResult = new AuctionCommandResult(); + auctionCommandResult.InitializeAuction(auction); + auctionCommandResult.Command = action; + auctionCommandResult.ErrorCode = errorCode; + SendPacket(auctionCommandResult); + } + + public void SendAuctionOutBidNotification(AuctionEntry auction, Item item) + { + AuctionOutBidNotification packet = new AuctionOutBidNotification(); + packet.BidAmount = auction.bid; + packet.MinIncrement = auction.GetAuctionOutBid(); + packet.Info.Initialize(auction, item); + SendPacket(packet); + } + + public void SendAuctionClosedNotification(AuctionEntry auction, float mailDelay, bool sold, Item item) + { + AuctionClosedNotification packet = new AuctionClosedNotification(); + packet.Info.Initialize(auction, item); + packet.ProceedsMailDelay = mailDelay; + packet.Sold = sold; + SendPacket(packet); + } + + public void SendAuctionWonNotification(AuctionEntry auction, Item item) + { + AuctionWonNotification packet = new AuctionWonNotification(); + packet.Info.Initialize(auction, item); + SendPacket(packet); + } + + public void SendAuctionOwnerBidNotification(AuctionEntry auction, Item item) + { + AuctionOwnerBidNotification packet = new AuctionOwnerBidNotification(); + packet.Info.Initialize(auction, item); + packet.Bidder = ObjectGuid.Create(HighGuid.Player, auction.bidder); + packet.MinIncrement = auction.GetAuctionOutBid(); + SendPacket(packet); + } + + [WorldPacketHandler(ClientOpcodes.AuctionSellItem)] + void HandleAuctionSellItem(AuctionSellItem packet) + { + foreach (var aitem in packet.Items) + if (aitem.Guid.IsEmpty() || aitem.UseCount == 0 || aitem.UseCount > 1000) + return; + + if (packet.MinBid == 0 || packet.RunTime == 0) + return; + + if (packet.MinBid > PlayerConst.MaxMoneyAmount || packet.BuyoutPrice > PlayerConst.MaxMoneyAmount) + { + Log.outDebug(LogFilter.Network, "WORLD: HandleAuctionSellItem - Player {0} ({1}) attempted to sell item with higher price than max gold amount.", GetPlayer().GetName(), GetPlayer().GetGUID().ToString()); + SendAuctionCommandResult(null, AuctionAction.SellItem, AuctionError.DatabaseError); + return; + } + + Creature creature = GetPlayer().GetNPCIfCanInteractWith(packet.Auctioneer, NPCFlags.Auctioneer); + if (!creature) + { + Log.outDebug(LogFilter.Network, "WORLD: HandleAuctionSellItem - {0} not found or you can't interact with him.", packet.Auctioneer.ToString()); + return; + } + + uint houseId = 0; + AuctionHouseRecord auctionHouseEntry = Global.AuctionMgr.GetAuctionHouseEntry(creature.getFaction(), ref houseId); + if (auctionHouseEntry == null) + { + Log.outDebug(LogFilter.Network, "WORLD: HandleAuctionSellItem - {0} has wrong faction.", packet.Auctioneer.ToString()); + return; + } + + packet.RunTime *= Time.Minute; + switch (packet.RunTime) + { + case 1 * SharedConst.MinAuctionTime: + case 2 * SharedConst.MinAuctionTime: + case 4 * SharedConst.MinAuctionTime: + break; + default: + return; + } + + if (GetPlayer().HasUnitState(UnitState.Died)) + GetPlayer().RemoveAurasByType(AuraType.FeignDeath); + + uint finalCount = 0; + foreach (var packetItem in packet.Items) + { + Item aitem = GetPlayer().GetItemByGuid(packetItem.Guid); + if (!aitem) + { + SendAuctionCommandResult(null, AuctionAction.SellItem, AuctionError.ItemNotFound); + return; + } + + if (Global.AuctionMgr.GetAItem(aitem.GetGUID().GetCounter()) || !aitem.CanBeTraded() || aitem.IsNotEmptyBag() || + aitem.GetTemplate().GetFlags().HasAnyFlag(ItemFlags.Conjured) || aitem.GetUInt32Value(ItemFields.Duration) != 0 || + aitem.GetCount() < packetItem.UseCount) + { + SendAuctionCommandResult(null, AuctionAction.SellItem, AuctionError.DatabaseError); + return; + } + + finalCount += packetItem.UseCount; + } + + if (packet.Items.Empty()) + { + SendAuctionCommandResult(null, AuctionAction.SellItem, AuctionError.DatabaseError); + return; + } + + if (finalCount == 0) + { + SendAuctionCommandResult(null, AuctionAction.SellItem, AuctionError.DatabaseError); + return; + } + + // check if there are 2 identical guids, in this case user is most likely cheating + for (int i = 0; i < packet.Items.Count; ++i) + { + for (int j = i + 1; j < packet.Items.Count; ++j) + { + if (packet.Items[i].Guid == packet.Items[j].Guid) + { + SendAuctionCommandResult(null, AuctionAction.SellItem, AuctionError.DatabaseError); + return; + } + } + } + + foreach (var packetItem in packet.Items) + { + Item aitem = GetPlayer().GetItemByGuid(packetItem.Guid); + + if (aitem.GetMaxStackCount() < finalCount) + { + SendAuctionCommandResult(null, AuctionAction.SellItem, AuctionError.DatabaseError); + return; + } + } + + Item item = GetPlayer().GetItemByGuid(packet.Items[0].Guid); + + uint auctionTime = (uint)(packet.RunTime * WorldConfig.GetFloatValue(WorldCfg.RateAuctionTime)); + AuctionHouseObject auctionHouse = Global.AuctionMgr.GetAuctionsMap(creature.getFaction()); + + uint deposit = Global.AuctionMgr.GetAuctionDeposit(auctionHouseEntry, packet.RunTime, item, finalCount); + if (!GetPlayer().HasEnoughMoney(deposit)) + { + SendAuctionCommandResult(null, AuctionAction.SellItem, AuctionError.NotEnoughtMoney); + return; + } + + AuctionEntry AH = new AuctionEntry(); + SQLTransaction trans; + + if (WorldConfig.GetBoolValue(WorldCfg.AllowTwoSideInteractionAuction)) + AH.auctioneer = 23442; ///@TODO - HARDCODED DB GUID, BAD BAD BAD + else + AH.auctioneer = creature.GetSpawnId(); + + // Required stack size of auction matches to current item stack size, just move item to auctionhouse + if (packet.Items.Count == 1 && item.GetCount() == packet.Items[0].UseCount) + { + if (HasPermission(RBACPermissions.LogGmTrade)) + { + Log.outCommand(GetAccountId(), "GM {0} (Account: {1}) create auction: {2} (Entry: {3} Count: {4})", + GetPlayerName(), GetAccountId(), item.GetTemplate().GetName(), item.GetEntry(), item.GetCount()); + } + + AH.Id = Global.ObjectMgr.GenerateAuctionID(); + AH.itemGUIDLow = item.GetGUID().GetCounter(); + AH.itemEntry = item.GetEntry(); + AH.itemCount = item.GetCount(); + AH.owner = GetPlayer().GetGUID().GetCounter(); + AH.startbid = (uint)packet.MinBid; + AH.bidder = 0; + AH.bid = 0; + AH.buyout = (uint)packet.BuyoutPrice; + AH.expire_time = Time.UnixTime + auctionTime; + AH.deposit = deposit; + AH.etime = packet.RunTime; + AH.auctionHouseEntry = auctionHouseEntry; + + Log.outInfo(LogFilter.Network, "CMSG_AUCTION_SELL_ITEM: {0} {1} is selling item {2} {3} to auctioneer {4} with count {5} with initial bid {6} with buyout {7} and with time {8} (in sec) in auctionhouse {9}", + GetPlayer().GetGUID().ToString(), GetPlayer().GetName(), item.GetGUID().ToString(), item.GetTemplate().GetName(), AH.auctioneer, item.GetCount(), packet.MinBid, packet.BuyoutPrice, auctionTime, AH.GetHouseId()); + Global.AuctionMgr.AddAItem(item); + auctionHouse.AddAuction(AH); + + GetPlayer().MoveItemFromInventory(item.GetBagSlot(), item.GetSlot(), true); + + trans = new SQLTransaction(); + item.DeleteFromInventoryDB(trans); + item.SaveToDB(trans); + AH.SaveToDB(trans); + GetPlayer().SaveInventoryAndGoldToDB(trans); + DB.Characters.CommitTransaction(trans); + + SendAuctionCommandResult(AH, AuctionAction.SellItem, AuctionError.Ok); + + GetPlayer().UpdateCriteria(CriteriaTypes.CreateAuction, 1); + } + else // Required stack size of auction does not match to current item stack size, clone item and set correct stack size + { + Item newItem = item.CloneItem(finalCount, GetPlayer()); + if (!newItem) + { + Log.outError(LogFilter.Network, "CMSG_AuctionAction.SellItem: Could not create clone of item {0}", item.GetEntry()); + SendAuctionCommandResult(null, AuctionAction.SellItem, AuctionError.DatabaseError); + return; + } + + if (HasPermission(RBACPermissions.LogGmTrade)) + { + Log.outCommand(GetAccountId(), "GM {0} (Account: {1}) create auction: {2} (Entry: {3} Count: {4})", + GetPlayerName(), GetAccountId(), newItem.GetTemplate().GetName(), newItem.GetEntry(), newItem.GetCount()); + } + + AH.Id = Global.ObjectMgr.GenerateAuctionID(); + AH.itemGUIDLow = newItem.GetGUID().GetCounter(); + AH.itemEntry = newItem.GetEntry(); + AH.itemCount = newItem.GetCount(); + AH.owner = GetPlayer().GetGUID().GetCounter(); + AH.startbid = (uint)packet.MinBid; + AH.bidder = 0; + AH.bid = 0; + AH.buyout = (uint)packet.BuyoutPrice; + AH.expire_time = Time.UnixTime + auctionTime; + AH.deposit = deposit; + AH.etime = packet.RunTime; + AH.auctionHouseEntry = auctionHouseEntry; + + Log.outInfo(LogFilter.Network, "CMSG_AuctionAction.SellItem: {0} {1} is selling {2} {3} to auctioneer {4} with count {5} with initial bid {6} with buyout {7} and with time {8} (in sec) in auctionhouse {9}", + GetPlayer().GetGUID().ToString(), GetPlayer().GetName(), newItem.GetGUID().ToString(), newItem.GetTemplate().GetName(), AH.auctioneer, newItem.GetCount(), packet.MinBid, packet.BuyoutPrice, auctionTime, AH.GetHouseId()); + Global.AuctionMgr.AddAItem(newItem); + auctionHouse.AddAuction(AH); + + foreach (var packetItem in packet.Items) + { + Item item2 = GetPlayer().GetItemByGuid(packetItem.Guid); + + // Item stack count equals required count, ready to delete item - cloned item will be used for auction + if (item2.GetCount() == packetItem.UseCount) + { + GetPlayer().MoveItemFromInventory(item2.GetBagSlot(), item2.GetSlot(), true); + + trans = new SQLTransaction(); + item2.DeleteFromInventoryDB(trans); + item2.DeleteFromDB(trans); + DB.Characters.CommitTransaction(trans); + } + else // Item stack count is bigger than required count, update item stack count and save to database - cloned item will be used for auction + { + item2.SetCount(item2.GetCount() - packetItem.UseCount); + item2.SetState(ItemUpdateState.Changed, GetPlayer()); + GetPlayer().ItemRemovedQuestCheck(item2.GetEntry(), packetItem.UseCount); + item2.SendUpdateToPlayer(GetPlayer()); + + trans = new SQLTransaction(); + item2.SaveToDB(trans); + DB.Characters.CommitTransaction(trans); + } + } + + trans = new SQLTransaction(); + newItem.SaveToDB(trans); + AH.SaveToDB(trans); + GetPlayer().SaveInventoryAndGoldToDB(trans); + DB.Characters.CommitTransaction(trans); + + SendAuctionCommandResult(AH, AuctionAction.SellItem, AuctionError.Ok); + + GetPlayer().UpdateCriteria(CriteriaTypes.CreateAuction, 1); + } + + GetPlayer().ModifyMoney(-deposit); + } + + [WorldPacketHandler(ClientOpcodes.AuctionPlaceBid)] + void HandleAuctionPlaceBid(AuctionPlaceBid packet) + { + if (packet.AuctionItemID == 0 || packet.BidAmount == 0) + return; // check for cheaters + + Creature creature = GetPlayer().GetNPCIfCanInteractWith(packet.Auctioneer, NPCFlags.Auctioneer); + if (!creature) + { + Log.outDebug(LogFilter.Network, "WORLD: HandleAuctionPlaceBid - {0} not found or you can't interact with him.", packet.Auctioneer.ToString()); + return; + } + + // remove fake death + if (GetPlayer().HasUnitState(UnitState.Died)) + GetPlayer().RemoveAurasByType(AuraType.FeignDeath); + + AuctionHouseObject auctionHouse = Global.AuctionMgr.GetAuctionsMap(creature.getFaction()); + + AuctionEntry auction = auctionHouse.GetAuction(packet.AuctionItemID); + Player player = GetPlayer(); + + if (auction == null || auction.owner == player.GetGUID().GetCounter()) + { + //you cannot bid your own auction: + SendAuctionCommandResult(null, AuctionAction.PlaceBid, AuctionError.BidOwn); + return; + } + + // impossible have online own another character (use this for speedup check in case online owner) + ObjectGuid ownerGuid = ObjectGuid.Create(HighGuid.Player, auction.owner); + Player auction_owner = Global.ObjAccessor.FindPlayer(ownerGuid); + if (!auction_owner && ObjectManager.GetPlayerAccountIdByGUID(ownerGuid) == player.GetSession().GetAccountId()) + { + //you cannot bid your another character auction: + SendAuctionCommandResult(null, AuctionAction.PlaceBid, AuctionError.BidOwn); + return; + } + + // cheating + if (packet.BidAmount <= auction.bid || packet.BidAmount < auction.startbid) + return; + + // price too low for next bid if not buyout + if ((packet.BidAmount < auction.buyout || auction.buyout == 0) && packet.BidAmount < auction.bid + auction.GetAuctionOutBid()) + { + // client already test it but just in case ... + SendAuctionCommandResult(auction, AuctionAction.PlaceBid, AuctionError.HigherBid); + return; + } + + if (!player.HasEnoughMoney(packet.BidAmount)) + { + // client already test it but just in case ... + SendAuctionCommandResult(auction, AuctionAction.PlaceBid, AuctionError.NotEnoughtMoney); + return; + } + + SQLTransaction trans = new SQLTransaction(); + if (packet.BidAmount < auction.buyout || auction.buyout == 0) + { + if (auction.bidder > 0) + { + if (auction.bidder == player.GetGUID().GetCounter()) + player.ModifyMoney(-(long)(packet.BidAmount - auction.bid)); + else + { + // mail to last bidder and return money + Global.AuctionMgr.SendAuctionOutbiddedMail(auction, packet.BidAmount, GetPlayer(), trans); + player.ModifyMoney(-(long)packet.BidAmount); + } + } + else + player.ModifyMoney(-(long)packet.BidAmount); + + auction.bidder = player.GetGUID().GetCounter(); + auction.bid = (uint)packet.BidAmount; + GetPlayer().UpdateCriteria(CriteriaTypes.HighestAuctionBid, packet.BidAmount); + + PreparedStatement stmt = DB.Characters.GetPreparedStatement(CharStatements.UPD_AUCTION_BID); + stmt.AddValue(0, auction.bidder); + stmt.AddValue(1, auction.bid); + stmt.AddValue(2, auction.Id); + trans.Append(stmt); + + SendAuctionCommandResult(auction, AuctionAction.PlaceBid, AuctionError.Ok); + + // Not sure if we must send this now. + Player owner = Global.ObjAccessor.FindConnectedPlayer(ObjectGuid.Create(HighGuid.Player, auction.owner)); + Item item = Global.AuctionMgr.GetAItem(auction.itemGUIDLow); + if (owner && item) + owner.GetSession().SendAuctionOwnerBidNotification(auction, item); + } + else + { + //buyout: + if (player.GetGUID().GetCounter() == auction.bidder) + player.ModifyMoney(-auction.buyout - auction.bid); + else + { + player.ModifyMoney(-auction.buyout); + if (auction.bidder != 0) //buyout for bidded auction .. + Global.AuctionMgr.SendAuctionOutbiddedMail(auction, auction.buyout, GetPlayer(), trans); + } + auction.bidder = player.GetGUID().GetCounter(); + auction.bid = auction.buyout; + GetPlayer().UpdateCriteria(CriteriaTypes.HighestAuctionBid, auction.buyout); + + SendAuctionCommandResult(auction, AuctionAction.PlaceBid, AuctionError.Ok); + + //- Mails must be under transaction control too to prevent data loss + Global.AuctionMgr.SendAuctionSalePendingMail(auction, trans); + Global.AuctionMgr.SendAuctionSuccessfulMail(auction, trans); + Global.AuctionMgr.SendAuctionWonMail(auction, trans); + + auction.DeleteFromDB(trans); + + Global.AuctionMgr.RemoveAItem(auction.itemGUIDLow); + auctionHouse.RemoveAuction(auction); + } + + player.SaveInventoryAndGoldToDB(trans); + DB.Characters.CommitTransaction(trans); + } + + [WorldPacketHandler(ClientOpcodes.AuctionRemoveItem)] + void HandleAuctionRemoveItem(AuctionRemoveItem packet) + { + Creature creature = GetPlayer().GetNPCIfCanInteractWith(packet.Auctioneer, NPCFlags.Auctioneer); + if (!creature) + { + Log.outDebug(LogFilter.Network, "WORLD: HandleAuctionRemoveItem - {0} not found or you can't interact with him.", packet.Auctioneer.ToString()); + return; + } + + // remove fake death + if (GetPlayer().HasUnitState(UnitState.Died)) + GetPlayer().RemoveAurasByType(AuraType.FeignDeath); + + AuctionHouseObject auctionHouse = Global.AuctionMgr.GetAuctionsMap(creature.getFaction()); + + AuctionEntry auction = auctionHouse.GetAuction((uint)packet.AuctionItemID); + Player player = GetPlayer(); + + SQLTransaction trans = new SQLTransaction(); + if (auction != null && auction.owner == player.GetGUID().GetCounter()) + { + Item pItem = Global.AuctionMgr.GetAItem(auction.itemGUIDLow); + if (pItem) + { + if (auction.bidder > 0) // If we have a bidder, we have to send him the money he paid + { + uint auctionCut = auction.GetAuctionCut(); + if (!player.HasEnoughMoney(auctionCut)) //player doesn't have enough money, maybe message needed + return; + Global.AuctionMgr.SendAuctionCancelledToBidderMail(auction, trans); + player.ModifyMoney(-auctionCut); + } + + // item will deleted or added to received mail list + new MailDraft(auction.BuildAuctionMailSubject(MailAuctionAnswers.Canceled), AuctionEntry.BuildAuctionMailBody(0, 0, auction.buyout, auction.deposit, 0)) + .AddItem(pItem) + .SendMailTo(trans, new MailReceiver(player), new MailSender(auction), MailCheckMask.Copied); + } + else + { + Log.outError(LogFilter.Network, "Auction id: {0} got non existing item (item guid : {1})!", auction.Id, auction.itemGUIDLow); + SendAuctionCommandResult(null, AuctionAction.Cancel, AuctionError.DatabaseError); + return; + } + } + else + { + SendAuctionCommandResult(null, AuctionAction.Cancel, AuctionError.DatabaseError); + //this code isn't possible ... maybe there should be assert + Log.outError(LogFilter.Network, "CHEATER: {0} tried to cancel auction (id: {1}) of another player or auction is null", player.GetGUID().ToString(), packet.AuctionItemID); + return; + } + + //inform player, that auction is removed + SendAuctionCommandResult(auction, AuctionAction.Cancel, AuctionError.Ok); + + // Now remove the auction + player.SaveInventoryAndGoldToDB(trans); + auction.DeleteFromDB(trans); + DB.Characters.CommitTransaction(trans); + + Global.AuctionMgr.RemoveAItem(auction.itemGUIDLow); + auctionHouse.RemoveAuction(auction); + } + + [WorldPacketHandler(ClientOpcodes.AuctionListBidderItems)] + void HandleAuctionListBidderItems(AuctionListBidderItems packet) + { + Creature creature = GetPlayer().GetNPCIfCanInteractWith(packet.Auctioneer, NPCFlags.Auctioneer); + if (!creature) + { + Log.outDebug(LogFilter.Network, "WORLD: HandleAuctionListBidderItems - {0} not found or you can't interact with him.", packet.Auctioneer.ToString()); + return; + } + + // remove fake death + if (GetPlayer().HasUnitState(UnitState.Died)) + GetPlayer().RemoveAurasByType(AuraType.FeignDeath); + + AuctionHouseObject auctionHouse = Global.AuctionMgr.GetAuctionsMap(creature.getFaction()); + + AuctionListBidderItemsResult result = new AuctionListBidderItemsResult(); + + Player player = GetPlayer(); + auctionHouse.BuildListBidderItems(result, player, ref result.TotalCount); + result.DesiredDelay = 300; + SendPacket(result); + } + + [WorldPacketHandler(ClientOpcodes.AuctionListOwnerItems)] + void HandleAuctionListOwnerItems(AuctionListOwnerItems packet) + { + Creature creature = GetPlayer().GetNPCIfCanInteractWith(packet.Auctioneer, NPCFlags.Auctioneer); + if (!creature) + { + Log.outDebug(LogFilter.Network, "WORLD: HandleAuctionListOwnerItems - {0} not found or you can't interact with him.", packet.Auctioneer.ToString()); + return; + } + + // remove fake death + if (GetPlayer().HasUnitState(UnitState.Died)) + GetPlayer().RemoveAurasByType(AuraType.FeignDeath); + + AuctionHouseObject auctionHouse = Global.AuctionMgr.GetAuctionsMap(creature.getFaction()); + + AuctionListOwnerItemsResult result = new AuctionListOwnerItemsResult(); + + auctionHouse.BuildListOwnerItems(result, GetPlayer(), ref result.TotalCount); + result.DesiredDelay = 300; + SendPacket(result); + } + + [WorldPacketHandler(ClientOpcodes.AuctionListItems)] + void HandleAuctionListItems(AuctionListItems packet) + { + Creature creature = GetPlayer().GetNPCIfCanInteractWith(packet.Auctioneer, NPCFlags.Auctioneer); + if (!creature) + { + Log.outDebug(LogFilter.Network, "WORLD: HandleAuctionListItems - {0} not found or you can't interact with him.", packet.Auctioneer.ToString()); + return; + } + + // remove fake death + if (GetPlayer().HasUnitState(UnitState.Died)) + GetPlayer().RemoveAurasByType(AuraType.FeignDeath); + + AuctionHouseObject auctionHouse = Global.AuctionMgr.GetAuctionsMap(creature.getFaction()); + + + Optional filters = new Optional(); + + AuctionListItemsResult result = new AuctionListItemsResult(); + if (!packet.ClassFilters.Empty()) + { + filters.HasValue = true; + + foreach (var classFilter in packet.ClassFilters) + { + if (!classFilter.SubClassFilters.Empty()) + { + foreach (var subClassFilter in classFilter.SubClassFilters) + { + filters.Value.Classes[classFilter.ItemClass].SubclassMask |= (AuctionSearchFilters.FilterType)(1 << subClassFilter.ItemSubclass); + filters.Value.Classes[classFilter.ItemClass].InvTypes[subClassFilter.ItemSubclass] = subClassFilter.InvTypeMask; + } + } + else + filters.Value.Classes[classFilter.ItemClass].SubclassMask = AuctionSearchFilters.FilterType.SkipSubclass; + } + } + + auctionHouse.BuildListAuctionItems(result, GetPlayer(), packet.Name.ToLower(), packet.Offset, packet.MinLevel, packet.MaxLevel, packet.OnlyUsable, filters, packet.Quality); + + result.DesiredDelay = WorldConfig.GetUIntValue(WorldCfg.AuctionSearchDelay); + result.OnlyUsable = packet.OnlyUsable; + SendPacket(result); + } + + [WorldPacketHandler(ClientOpcodes.AuctionListPendingSales)] + void HandleAuctionListPendingSales(AuctionListPendingSales packet) + { + AuctionListPendingSalesResult result = new AuctionListPendingSalesResult(); + result.TotalNumRecords = 0; + SendPacket(result); + } + + [WorldPacketHandler(ClientOpcodes.AuctionReplicateItems)] + void HandleReplicateItems(AuctionReplicateItems packet) + { + /* Creature* creature = GetPlayer()->GetNPCIfCanInteractWith(packet.Auctioneer, UNIT_NPC_FLAG_AUCTIONEER); + if (!creature) + { + TC_LOG_DEBUG("network", "WORLD: HandleReplicateItems - {0} not found or you can't interact with him.", packet.Auctioneer.ToString().c_str()); + return; + } + + // remove fake death + if (GetPlayer()->HasUnitState(UNIT_STATE_DIED)) + GetPlayer()->RemoveAurasByType(SPELL_AURA_FEIGN_DEATH); + + AuctionHouseObject* auctionHouse = sAuctionMgr->GetAuctionsMap(creature->getFaction()); + + WorldPackets::AuctionHouse::AuctionReplicateResponse response; + + auctionHouse->BuildReplicate(response, GetPlayer(), packet.ChangeNumberGlobal, packet.ChangeNumberCursor, packet.ChangeNumberTombstone, packet.Count); + */ + //@todo implement this properly + AuctionReplicateResponse response = new AuctionReplicateResponse(); + response.ChangeNumberCursor = packet.ChangeNumberCursor; + response.ChangeNumberGlobal = packet.ChangeNumberGlobal; + response.ChangeNumberTombstone = packet.ChangeNumberTombstone; + response.DesiredDelay = WorldConfig.GetUIntValue(WorldCfg.AuctionSearchDelay) * 5; + response.Result = 0; + SendPacket(response); + } + } +} diff --git a/Game/Handlers/AuthenticationHandler.cs b/Game/Handlers/AuthenticationHandler.cs new file mode 100644 index 000000000..962db2abb --- /dev/null +++ b/Game/Handlers/AuthenticationHandler.cs @@ -0,0 +1,104 @@ +/* + * Copyright (C) 2012-2017 CypherCore + * + * 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, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +using Framework.Constants; +using Game.Network.Packets; + +namespace Game +{ + public partial class WorldSession + { + public void SendAuthResponse(BattlenetRpcErrorCode code, bool queued, uint queuePos = 0) + { + AuthResponse response = new AuthResponse(); + response.Result = code; + + if (code == BattlenetRpcErrorCode.Ok) + { + response.SuccessInfo.HasValue = true; + + response.SuccessInfo.Value = new AuthResponse.AuthSuccessInfo(); + response.SuccessInfo.Value.AccountExpansionLevel = (byte)GetExpansion(); + response.SuccessInfo.Value.ActiveExpansionLevel = (byte)GetExpansion(); + response.SuccessInfo.Value.VirtualRealmAddress = Global.WorldMgr.GetVirtualRealmAddress(); + response.SuccessInfo.Value.Time = (uint)Time.UnixTime; + + var realm = Global.WorldMgr.GetRealm(); + + // Send current home realm. Also there is no need to send it later in realm queries. + response.SuccessInfo.Value.VirtualRealms.Add(new VirtualRealmInfo(realm.Id.GetAddress(), true, false, realm.Name, realm.NormalizedName)); + + if (HasPermission(RBACPermissions.UseCharacterTemplates)) + foreach (var templ in Global.CharacterTemplateDataStorage.GetCharacterTemplates().Values) + response.SuccessInfo.Value.Templates.Add(templ); + + response.SuccessInfo.Value.AvailableClasses = Global.ObjectMgr.GetClassExpansionRequirements(); + response.SuccessInfo.Value.AvailableRaces = Global.ObjectMgr.GetRaceExpansionRequirements(); + } + + if (queued) + { + response.WaitInfo.HasValue = true; + response.WaitInfo.Value.WaitCount = queuePos; + } + + SendPacket(response); + } + + public void SendAuthWaitQue(uint position) + { + if (position != 0) + { + WaitQueueUpdate waitQueueUpdate = new WaitQueueUpdate(); + waitQueueUpdate.WaitInfo.WaitCount = position; + waitQueueUpdate.WaitInfo.WaitTime = 0; + waitQueueUpdate.WaitInfo.HasFCM = false; + SendPacket(waitQueueUpdate); + } + else + SendPacket(new WaitQueueFinish()); + } + + public void SendClientCacheVersion(uint version) + { + ClientCacheVersion cache = new ClientCacheVersion(); + cache.CacheVersion = version; + SendPacket(cache);//enabled it + } + + public void SendSetTimeZoneInformation() + { + /// @todo: replace dummy values + SetTimeZoneInformation packet = new SetTimeZoneInformation(); + packet.ServerTimeTZ = "Europe/Paris"; + packet.GameTimeTZ = "Europe/Paris"; + + SendPacket(packet);//enabled it + } + + public void SendFeatureSystemStatusGlueScreen() + { + FeatureSystemStatusGlueScreen features = new FeatureSystemStatusGlueScreen(); + features.BpayStoreAvailable = false; + features.BpayStoreDisabledByParentalControls = false; + features.CharUndeleteEnabled = WorldConfig.GetBoolValue(WorldCfg.FeatureSystemCharacterUndeleteEnabled); + features.BpayStoreEnabled = WorldConfig.GetBoolValue(WorldCfg.FeatureSystemBpayStoreEnabled); + + SendPacket(features); + } + } +} diff --git a/Game/Handlers/BankHandler.cs b/Game/Handlers/BankHandler.cs new file mode 100644 index 000000000..9c8499a3a --- /dev/null +++ b/Game/Handlers/BankHandler.cs @@ -0,0 +1,151 @@ +/* + * Copyright (C) 2012-2017 CypherCore + * + * 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, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +using Framework.Constants; +using Game.DataStorage; +using Game.Entities; +using Game.Network; +using Game.Network.Packets; +using System.Collections.Generic; + +namespace Game +{ + public partial class WorldSession + { + [WorldPacketHandler(ClientOpcodes.AutobankItem)] + void HandleAutoBankItem(AutoBankItem packet) + { + if (!CanUseBank()) + { + Log.outDebug(LogFilter.Network, "WORLD: HandleAutoBankItemOpcode - {0} not found or you can't interact with him.", m_currentBankerGUID.ToString()); + return; + } + + Item item = GetPlayer().GetItemByPos(packet.Bag, packet.Slot); + if (!item) + return; + + List dest = new List(); + InventoryResult msg = GetPlayer().CanBankItem(ItemConst.NullBag, ItemConst.NullSlot, dest, item, false); + if (msg != InventoryResult.Ok) + { + GetPlayer().SendEquipError(msg, item); + return; + } + + if (dest.Count == 1 && dest[0].pos == item.GetPos()) + { + GetPlayer().SendEquipError(InventoryResult.CantSwap, item); + return; + } + + GetPlayer().RemoveItem(packet.Bag, packet.Slot, true); + GetPlayer().ItemRemovedQuestCheck(item.GetEntry(), item.GetCount()); + GetPlayer().BankItem(dest, item, true); + } + + [WorldPacketHandler(ClientOpcodes.BankerActivate)] + void HandleBankerActivate(Hello packet) + { + Creature unit = GetPlayer().GetNPCIfCanInteractWith(packet.Unit, NPCFlags.Banker); + if (!unit) + { + Log.outError(LogFilter.Network, "HandleBankerActivate: {0} not found or you can not interact with him.", packet.Unit.ToString()); + return; + } + + if (GetPlayer().HasUnitState(UnitState.Died)) + GetPlayer().RemoveAurasByType(AuraType.FeignDeath); + + SendShowBank(packet.Unit); + } + + [WorldPacketHandler(ClientOpcodes.AutostoreBankItem)] + void HandleAutoStoreBankItem(AutoStoreBankItem packet) + { + if (!CanUseBank()) + { + Log.outDebug(LogFilter.Network, "WORLD: HandleAutoBankItemOpcode - {0} not found or you can't interact with him.", m_currentBankerGUID.ToString()); + return; + } + + Item item = GetPlayer().GetItemByPos(packet.Bag, packet.Slot); + if (!item) + return; + + if (Player.IsBankPos(packet.Bag, packet.Slot)) // moving from bank to inventory + { + List dest = new List(); + InventoryResult msg = GetPlayer().CanStoreItem(ItemConst.NullBag, ItemConst.NullSlot, dest, item, false); + if (msg != InventoryResult.Ok) + { + GetPlayer().SendEquipError(msg, item); + return; + } + + GetPlayer().RemoveItem(packet.Bag, packet.Slot, true); + Item storedItem = GetPlayer().StoreItem(dest, item, true); + if (storedItem) + GetPlayer().ItemAddedQuestCheck(storedItem.GetEntry(), storedItem.GetCount()); + } + else // moving from inventory to bank + { + List dest = new List(); + InventoryResult msg = GetPlayer().CanBankItem(ItemConst.NullBag, ItemConst.NullSlot, dest, item, false); + if (msg != InventoryResult.Ok) + { + GetPlayer().SendEquipError(msg, item); + return; + } + + GetPlayer().RemoveItem(packet.Bag, packet.Slot, true); + GetPlayer().BankItem(dest, item, true); + } + } + + [WorldPacketHandler(ClientOpcodes.BuyBankSlot)] + void HandleBuyBankSlot(BuyBankSlot packet) + { + if (!CanUseBank(packet.Guid)) + Log.outDebug(LogFilter.Network, "WORLD: HandleBuyBankSlot - {0} not found or you can't interact with him.", packet.Guid.ToString()); + + uint slot = GetPlayer().GetBankBagSlotCount(); + // next slot + ++slot; + + BankBagSlotPricesRecord slotEntry = CliDB.BankBagSlotPricesStorage.LookupByKey(slot); + if (slotEntry == null) + return; + + uint price = slotEntry.Cost; + if (!GetPlayer().HasEnoughMoney(price)) + return; + + GetPlayer().SetBankBagSlotCount((byte)slot); + GetPlayer().ModifyMoney(-price); + GetPlayer().UpdateCriteria(CriteriaTypes.BuyBankSlot); + } + + public void SendShowBank(ObjectGuid guid) + { + m_currentBankerGUID = guid; + ShowBank packet = new ShowBank(); + packet.Guid = guid; + SendPacket(packet); + } + } +} diff --git a/Game/Handlers/BattleFieldHandler.cs b/Game/Handlers/BattleFieldHandler.cs new file mode 100644 index 000000000..96551277f --- /dev/null +++ b/Game/Handlers/BattleFieldHandler.cs @@ -0,0 +1,156 @@ +/* + * Copyright (C) 2012-2017 CypherCore + * + * 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, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +using Framework.Constants; +using Game.BattleFields; +using Game.Network.Packets; + +namespace Game +{ + public partial class WorldSession + { + /// + /// This send to player windows for invite player to join the war. + /// + /// The queue id of Bf + /// The zone where the battle is (4197 for wg) + /// Time in second that the player have for accept + public void SendBfInvitePlayerToWar(ulong queueId, uint zoneId, uint acceptTime) + { + BFMgrEntryInvite bfMgrEntryInvite = new BFMgrEntryInvite(); + bfMgrEntryInvite.QueueID = queueId; + bfMgrEntryInvite.AreaID = (int)zoneId; + bfMgrEntryInvite.ExpireTime = Time.UnixTime + acceptTime; + SendPacket(bfMgrEntryInvite); + } + + /// + /// This send invitation to player to join the queue. + /// + /// The queue id of Bf + /// Battlefield State + public void SendBfInvitePlayerToQueue(ulong queueId, BattlefieldState battleState) + { + BFMgrQueueInvite bfMgrQueueInvite = new BFMgrQueueInvite(); + bfMgrQueueInvite.QueueID = queueId; + bfMgrQueueInvite.BattleState = battleState; + SendPacket(bfMgrQueueInvite); + } + + /// + /// This send packet for inform player that he join queue. + /// + /// The queue id of Bf + /// The zone where the battle is (4197 for wg) + /// Battlefield status + /// if able to queue + /// on log in send queue status + public void SendBfQueueInviteResponse(ulong queueId, uint zoneId, BattlefieldState battleStatus, bool canQueue = true, bool loggingIn = false) + { + BFMgrQueueRequestResponse bfMgrQueueRequestResponse = new BFMgrQueueRequestResponse(); + bfMgrQueueRequestResponse.QueueID = queueId; + bfMgrQueueRequestResponse.AreaID = (int)zoneId; + bfMgrQueueRequestResponse.Result = (sbyte)(canQueue ? 1 : 0); + bfMgrQueueRequestResponse.BattleState = battleStatus; + bfMgrQueueRequestResponse.LoggingIn = loggingIn; + SendPacket(bfMgrQueueRequestResponse); + } + + /// + /// This is call when player accept to join war. + /// + /// The queue id of Bf + /// Whether player is added to Bf on the spot or teleported from queue + /// Whether player belongs to attacking team or not + public void SendBfEntered(ulong queueId, bool relocated, bool onOffense) + { + BFMgrEntering bfMgrEntering = new BFMgrEntering(); + bfMgrEntering.ClearedAFK = _player.isAFK(); + bfMgrEntering.Relocated = relocated; + bfMgrEntering.OnOffense = onOffense; + bfMgrEntering.QueueID = queueId; + SendPacket(bfMgrEntering); + } + + /// + /// This is call when player leave battlefield zone. + /// + /// The queue id of Bf + /// The queue id of Bf + /// Battlefield status + /// Whether player is added to Bf on the spot or teleported from queue + /// Reason why player left battlefield + public void SendBfLeaveMessage(ulong queueId, BattlefieldState battleState, bool relocated, BFLeaveReason reason = BFLeaveReason.Exited) + { + BFMgrEjected bfMgrEjected = new BFMgrEjected(); + bfMgrEjected.QueueID = queueId; + bfMgrEjected.Reason = reason; + bfMgrEjected.BattleState = battleState; + bfMgrEjected.Relocated = relocated; + SendPacket(bfMgrEjected); + } + + /// + /// Send by client on clicking in accept or refuse of invitation windows for join game. + /// + //[WorldPacketHandler(ClientOpcodes.BfMgrEntryInviteResponse)] + void HandleBfEntryInviteResponse(BFMgrEntryInviteResponse bfMgrEntryInviteResponse) + { + BattleField bf = Global.BattleFieldMgr.GetBattlefieldByQueueId(bfMgrEntryInviteResponse.QueueID); + if (bf == null) + return; + + // If player accept invitation + if (bfMgrEntryInviteResponse.AcceptedInvite) + { + bf.PlayerAcceptInviteToWar(GetPlayer()); + } + else + { + if (GetPlayer().GetZoneId() == bf.GetZoneId()) + bf.KickPlayerFromBattlefield(GetPlayer().GetGUID()); + } + } + + /// + /// Send by client when he click on accept for queue. + /// + //[WorldPacketHandler(ClientOpcodes.BfMgrQueueInviteResponse)] + void HandleBfQueueInviteResponse(BFMgrQueueInviteResponse bfMgrQueueInviteResponse) + { + BattleField bf = Global.BattleFieldMgr.GetBattlefieldByQueueId(bfMgrQueueInviteResponse.QueueID); + if (bf == null) + return; + + if (bfMgrQueueInviteResponse.AcceptedInvite) + bf.PlayerAcceptInviteToQueue(GetPlayer()); + } + + /// + /// Send by client when exited battlefield + /// + //[WorldPacketHandler(ClientOpcodes.BfMgrQueueExitRequest)] + void HandleBfExitRequest(BFMgrQueueExitRequest bfMgrQueueExitRequest) + { + BattleField bf = Global.BattleFieldMgr.GetBattlefieldByQueueId(bfMgrQueueExitRequest.QueueID); + if (bf == null) + return; + + bf.AskToLeaveQueue(GetPlayer()); + } + } +} diff --git a/Game/Handlers/BattleGroundHandler.cs b/Game/Handlers/BattleGroundHandler.cs new file mode 100644 index 000000000..26db67e2e --- /dev/null +++ b/Game/Handlers/BattleGroundHandler.cs @@ -0,0 +1,694 @@ +/* + * Copyright (C) 2012-2017 CypherCore + * + * 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, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +using Framework.Constants; +using Game.Arenas; +using Game.BattleFields; +using Game.BattleGrounds; +using Game.DataStorage; +using Game.Entities; +using Game.Groups; +using Game.Network; +using Game.Network.Packets; +using System; +using System.Collections.Generic; + +namespace Game +{ + public partial class WorldSession + { + [WorldPacketHandler(ClientOpcodes.BattlemasterHello)] + void HandleBattlemasterHello(Hello hello) + { + Creature unit = GetPlayer().GetNPCIfCanInteractWith(hello.Unit, NPCFlags.BattleMaster); + if (!unit) + return; + + // Stop the npc if moving + unit.StopMoving(); + + BattlegroundTypeId bgTypeId = Global.BattlegroundMgr.GetBattleMasterBG(unit.GetEntry()); + + if (!GetPlayer().GetBGAccessByLevel(bgTypeId)) + { + // temp, must be gossip message... + SendNotification(CypherStrings.YourBgLevelReqError); + return; + } + + Global.BattlegroundMgr.SendBattlegroundList(GetPlayer(), hello.Unit, bgTypeId); + } + + [WorldPacketHandler(ClientOpcodes.BattlemasterJoin)] + void HandleBattlemasterJoin(BattlemasterJoin battlemasterJoin) + { + bool isPremade = false; + Group grp = null; + + BattlefieldStatusFailed battlefieldStatusFailed; + + uint bgTypeId_ = (uint)(battlemasterJoin.QueueID & 0xFFFF); + if (!CliDB.BattlemasterListStorage.ContainsKey(bgTypeId_)) + { + Log.outError(LogFilter.Network, "Battleground: invalid bgtype ({0}) received. possible cheater? player guid {1}", bgTypeId_, GetPlayer().GetGUID().ToString()); + return; + } + + if (Global.DisableMgr.IsDisabledFor(DisableType.Battleground, bgTypeId_, null)) + { + GetPlayer().SendSysMessage(CypherStrings.BgDisabled); + return; + } + BattlegroundTypeId bgTypeId = (BattlegroundTypeId)bgTypeId_; + + // can do this, since it's Battleground, not arena + BattlegroundQueueTypeId bgQueueTypeId = Global.BattlegroundMgr.BGQueueTypeId(bgTypeId, 0); + BattlegroundQueueTypeId bgQueueTypeIdRandom = Global.BattlegroundMgr.BGQueueTypeId(BattlegroundTypeId.RB, 0); + + // ignore if player is already in BG + if (GetPlayer().InBattleground()) + return; + + // get bg instance or bg template if instance not found + Battleground bg = Global.BattlegroundMgr.GetBattlegroundTemplate(bgTypeId); + if (!bg) + return; + + // expected bracket entry + PvpDifficultyRecord bracketEntry = Global.DB2Mgr.GetBattlegroundBracketByLevel(bg.GetMapId(), GetPlayer().getLevel()); + if (bracketEntry == null) + return; + + GroupJoinBattlegroundResult err = GroupJoinBattlegroundResult.None; + // check queue conditions + if (!battlemasterJoin.JoinAsGroup) + { + if (GetPlayer().isUsingLfg()) + { + Global.BattlegroundMgr.BuildBattlegroundStatusFailed(out battlefieldStatusFailed, bg, GetPlayer(), 0, 0, GroupJoinBattlegroundResult.LfgCantUseBattleground); + SendPacket(battlefieldStatusFailed); + return; + } + + // check Deserter debuff + if (!GetPlayer().CanJoinToBattleground(bg)) + { + Global.BattlegroundMgr.BuildBattlegroundStatusFailed(out battlefieldStatusFailed, bg, GetPlayer(), 0, 0, GroupJoinBattlegroundResult.Deserters); + SendPacket(battlefieldStatusFailed); + return; + } + + if (GetPlayer().GetBattlegroundQueueIndex(bgQueueTypeIdRandom) < SharedConst.MaxPlayerBGQueues) + { + // player is already in random queue + Global.BattlegroundMgr.BuildBattlegroundStatusFailed(out battlefieldStatusFailed, bg, GetPlayer(), 0, 0, GroupJoinBattlegroundResult.InRandomBg); + SendPacket(battlefieldStatusFailed); + return; + } + + if (GetPlayer().InBattlegroundQueue() && bgTypeId == BattlegroundTypeId.RB) + { + // player is already in queue, can't start random queue + Global.BattlegroundMgr.BuildBattlegroundStatusFailed(out battlefieldStatusFailed, bg, GetPlayer(), 0, 0, GroupJoinBattlegroundResult.InNonRandomBg); + SendPacket(battlefieldStatusFailed); + return; + } + + // check if already in queue + if (GetPlayer().GetBattlegroundQueueIndex(bgQueueTypeId) < SharedConst.MaxPlayerBGQueues) + return; // player is already in this queue + + // check if has free queue slots + if (!GetPlayer().HasFreeBattlegroundQueueId()) + { + Global.BattlegroundMgr.BuildBattlegroundStatusFailed(out battlefieldStatusFailed, bg, GetPlayer(), 0, 0, GroupJoinBattlegroundResult.TooManyQueues); + SendPacket(battlefieldStatusFailed); + return; + } + + // check Freeze debuff + if (_player.HasAura(9454)) + return; + + BattlegroundQueue bgQueue = Global.BattlegroundMgr.GetBattlegroundQueue(bgQueueTypeId); + GroupQueueInfo ginfo = bgQueue.AddGroup(GetPlayer(), null, bgTypeId, bracketEntry, 0, false, isPremade, 0, 0); + + uint avgTime = bgQueue.GetAverageQueueWaitTime(ginfo, bracketEntry.GetBracketId()); + uint queueSlot = GetPlayer().AddBattlegroundQueueId(bgQueueTypeId); + + BattlefieldStatusQueued battlefieldStatusQueued; + Global.BattlegroundMgr.BuildBattlegroundStatusQueued(out battlefieldStatusQueued, bg, GetPlayer(), queueSlot, ginfo.JoinTime, avgTime, ginfo.ArenaType, false); + SendPacket(battlefieldStatusQueued); + + + Log.outDebug(LogFilter.Battleground, "Battleground: player joined queue for bg queue type {0} bg type {1}: GUID {2}, NAME {3}", + bgQueueTypeId, bgTypeId, GetPlayer().GetGUID().ToString(), GetPlayer().GetName()); + } + else + { + grp = GetPlayer().GetGroup(); + + if (!grp) + return; + + if (grp.GetLeaderGUID() != GetPlayer().GetGUID()) + return; + + ObjectGuid errorGuid; + err = grp.CanJoinBattlegroundQueue(bg, bgQueueTypeId, 0, bg.GetMaxPlayersPerTeam(), false, 0, out errorGuid); + isPremade = (grp.GetMembersCount() >= bg.GetMinPlayersPerTeam()); + + BattlegroundQueue bgQueue = Global.BattlegroundMgr.GetBattlegroundQueue(bgQueueTypeId); + GroupQueueInfo ginfo = null; + uint avgTime = 0; + + if (err == 0) + { + Log.outDebug(LogFilter.Battleground, "Battleground: the following players are joining as group:"); + ginfo = bgQueue.AddGroup(GetPlayer(), grp, bgTypeId, bracketEntry, 0, false, isPremade, 0, 0); + avgTime = bgQueue.GetAverageQueueWaitTime(ginfo, bracketEntry.GetBracketId()); + } + + for (GroupReference refe = grp.GetFirstMember(); refe != null; refe = refe.next()) + { + Player member = refe.GetSource(); + if (!member) + continue; // this should never happen + + if (err != 0) + { + BattlefieldStatusFailed battlefieldStatus; + Global.BattlegroundMgr.BuildBattlegroundStatusFailed(out battlefieldStatus, bg, GetPlayer(), 0, 0, err, errorGuid); + member.SendPacket(battlefieldStatus); + continue; + } + + // add to queue + uint queueSlot = member.AddBattlegroundQueueId(bgQueueTypeId); + + BattlefieldStatusQueued battlefieldStatusQueued; + Global.BattlegroundMgr.BuildBattlegroundStatusQueued(out battlefieldStatusQueued, bg, member, queueSlot, ginfo.JoinTime, avgTime, ginfo.ArenaType, true); + member.SendPacket(battlefieldStatusQueued); + Log.outDebug(LogFilter.Battleground, "Battleground: player joined queue for bg queue type {0} bg type {1}: GUID {2}, NAME {3}", + bgQueueTypeId, bgTypeId, member.GetGUID().ToString(), member.GetName()); + } + Log.outDebug(LogFilter.Battleground, "Battleground: group end"); + } + + Global.BattlegroundMgr.ScheduleQueueUpdate(0, 0, bgQueueTypeId, bgTypeId, bracketEntry.GetBracketId()); + } + + [WorldPacketHandler(ClientOpcodes.PvpLogData)] + void HandlePVPLogData(PVPLogDataRequest packet) + { + Battleground bg = GetPlayer().GetBattleground(); + if (!bg) + return; + + // Prevent players from sending BuildPvpLogDataPacket in an arena except for when sent in Battleground.EndBattleground. + if (bg.isArena()) + return; + + PVPLogData pvpLogData; + bg.BuildPvPLogDataPacket(out pvpLogData); + SendPacket(pvpLogData); + } + + [WorldPacketHandler(ClientOpcodes.BattlefieldList)] + void HandleBattlefieldList(BattlefieldListRequest battlefieldList) + { + BattlemasterListRecord bl = CliDB.BattlemasterListStorage.LookupByKey(battlefieldList.ListID); + if (bl == null) + { + Log.outDebug(LogFilter.Battleground, "BattlegroundHandler: invalid bgtype ({0}) with player (Name: {1}, GUID: {2}) received.", battlefieldList.ListID, GetPlayer().GetName(), GetPlayer().GetGUID().ToString()); + return; + } + + Global.BattlegroundMgr.SendBattlegroundList(GetPlayer(), ObjectGuid.Empty, (BattlegroundTypeId)battlefieldList.ListID); + } + + [WorldPacketHandler(ClientOpcodes.BattlefieldPort)] + void HandleBattleFieldPort(BattlefieldPort battlefieldPort) + { + if (!GetPlayer().InBattlegroundQueue()) + { + Log.outDebug(LogFilter.Battleground, "CMSG_BATTLEFIELD_PORT {0} Slot: {1}, Unk: {2}, Time: {3}, AcceptedInvite: {4}. Player not in queue!", + GetPlayerInfo(), battlefieldPort.Ticket.Id, battlefieldPort.Ticket.Type, battlefieldPort.Ticket.Time, battlefieldPort.AcceptedInvite); + return; + } + + BattlegroundQueueTypeId bgQueueTypeId = GetPlayer().GetBattlegroundQueueTypeId(battlefieldPort.Ticket.Id); + if (bgQueueTypeId == BattlegroundQueueTypeId.None) + { + Log.outDebug(LogFilter.Battleground, "CMSG_BATTLEFIELD_PORT {0} Slot: {1}, Unk: {2}, Time: {3}, AcceptedInvite: {4}. Invalid queueSlot!", + GetPlayerInfo(), battlefieldPort.Ticket.Id, battlefieldPort.Ticket.Type, battlefieldPort.Ticket.Time, battlefieldPort.AcceptedInvite); + return; + } + + BattlegroundQueue bgQueue = Global.BattlegroundMgr.GetBattlegroundQueue(bgQueueTypeId); + + //we must use temporary variable, because GroupQueueInfo pointer can be deleted in BattlegroundQueue.RemovePlayer() function + GroupQueueInfo ginfo; + if (!bgQueue.GetPlayerGroupInfoData(GetPlayer().GetGUID(), out ginfo)) + { + Log.outDebug(LogFilter.Battleground, "CMSG_BATTLEFIELD_PORT {0} Slot: {1}, Unk: {2}, Time: {3}, AcceptedInvite: {4}. Player not in queue (No player Group Info)!", + GetPlayerInfo(), battlefieldPort.Ticket.Id, battlefieldPort.Ticket.Type, battlefieldPort.Ticket.Time, battlefieldPort.AcceptedInvite); + return; + } + // if action == 1, then instanceId is required + if (ginfo.IsInvitedToBGInstanceGUID == 0 && battlefieldPort.AcceptedInvite) + { + Log.outDebug(LogFilter.Battleground, "CMSG_BATTLEFIELD_PORT {0} Slot: {1}, Unk: {2}, Time: {3}, AcceptedInvite: {4}. Player is not invited to any bg!", + GetPlayerInfo(), battlefieldPort.Ticket.Id, battlefieldPort.Ticket.Type, battlefieldPort.Ticket.Time, battlefieldPort.AcceptedInvite); + return; + } + + BattlegroundTypeId bgTypeId = Global.BattlegroundMgr.BGTemplateId(bgQueueTypeId); + // BGTemplateId returns Battleground_AA when it is arena queue. + // Do instance id search as there is no AA bg instances. + Battleground bg = Global.BattlegroundMgr.GetBattleground(ginfo.IsInvitedToBGInstanceGUID, bgTypeId == BattlegroundTypeId.AA ? BattlegroundTypeId.None : bgTypeId); + if (!bg) + { + if (battlefieldPort.AcceptedInvite) + { + Log.outDebug(LogFilter.Battleground, "CMSG_BATTLEFIELD_PORT {0} Slot: {1}, Unk: {2}, Time: {3}, AcceptedInvite: {4}. Cant find BG with id {5}!", + GetPlayerInfo(), battlefieldPort.Ticket.Id, battlefieldPort.Ticket.Type, battlefieldPort.Ticket.Time, battlefieldPort.AcceptedInvite, ginfo.IsInvitedToBGInstanceGUID); + return; + } + + bg = Global.BattlegroundMgr.GetBattlegroundTemplate(bgTypeId); + if (!bg) + { + Log.outError(LogFilter.Network, "BattlegroundHandler: bg_template not found for type id {0}.", bgTypeId); + return; + } + } + + // get real bg type + bgTypeId = bg.GetTypeID(); + + // expected bracket entry + PvpDifficultyRecord bracketEntry = Global.DB2Mgr.GetBattlegroundBracketByLevel(bg.GetMapId(), GetPlayer().getLevel()); + if (bracketEntry == null) + return; + + //some checks if player isn't cheating - it is not exactly cheating, but we cannot allow it + if (battlefieldPort.AcceptedInvite && ginfo.ArenaType == 0) + { + //if player is trying to enter Battleground(not arena!) and he has deserter debuff, we must just remove him from queue + if (!GetPlayer().CanJoinToBattleground(bg)) + { + // send bg command result to show nice message + BattlefieldStatusFailed battlefieldStatus; + Global.BattlegroundMgr.BuildBattlegroundStatusFailed(out battlefieldStatus, bg, GetPlayer(), battlefieldPort.Ticket.Id, 0, GroupJoinBattlegroundResult.Deserters); + SendPacket(battlefieldStatus); + battlefieldPort.AcceptedInvite = false; + Log.outDebug(LogFilter.Battleground, "Player {0} ({1}) has a deserter debuff, do not port him to Battleground!", GetPlayer().GetName(), GetPlayer().GetGUID().ToString()); + } + //if player don't match Battlegroundmax level, then do not allow him to enter! (this might happen when player leveled up during his waiting in queue + if (GetPlayer().getLevel() > bg.GetMaxLevel()) + { + Log.outDebug(LogFilter.Network, "Player {0} ({1}) has level ({2}) higher than maxlevel ({3}) of Battleground({4})! Do not port him to Battleground!", + GetPlayer().GetName(), GetPlayer().GetGUID().ToString(), GetPlayer().getLevel(), bg.GetMaxLevel(), bg.GetTypeID()); + battlefieldPort.AcceptedInvite = false; + } + } + + if (battlefieldPort.AcceptedInvite) + { + // check Freeze debuff + if (GetPlayer().HasAura(9454)) + return; + + if (!GetPlayer().IsInvitedForBattlegroundQueueType(bgQueueTypeId)) + return; // cheating? + + if (!GetPlayer().InBattleground()) + GetPlayer().SetBattlegroundEntryPoint(); + + // resurrect the player + if (!GetPlayer().IsAlive()) + { + GetPlayer().ResurrectPlayer(1.0f); + GetPlayer().SpawnCorpseBones(); + } + // stop taxi flight at port + if (GetPlayer().IsInFlight()) + { + GetPlayer().GetMotionMaster().MovementExpired(); + GetPlayer().CleanupAfterTaxiFlight(); + } + + BattlefieldStatusActive battlefieldStatus; + Global.BattlegroundMgr.BuildBattlegroundStatusActive(out battlefieldStatus, bg, GetPlayer(), battlefieldPort.Ticket.Id, GetPlayer().GetBattlegroundQueueJoinTime(bgQueueTypeId), bg.GetArenaType()); + SendPacket(battlefieldStatus); + + // remove BattlegroundQueue status from BGmgr + bgQueue.RemovePlayer(GetPlayer().GetGUID(), false); + // this is still needed here if Battleground"jumping" shouldn't add deserter debuff + // also this is required to prevent stuck at old Battlegroundafter SetBattlegroundId set to new + Battleground currentBg = GetPlayer().GetBattleground(); + if (currentBg) + currentBg.RemovePlayerAtLeave(GetPlayer().GetGUID(), false, true); + + // set the destination instance id + GetPlayer().SetBattlegroundId(bg.GetInstanceID(), bgTypeId); + // set the destination team + GetPlayer().SetBGTeam(ginfo.Team); + + Global.BattlegroundMgr.SendToBattleground(GetPlayer(), ginfo.IsInvitedToBGInstanceGUID, bgTypeId); + Log.outDebug(LogFilter.Battleground, "Battleground: player {0} ({1}) joined battle for bg {2}, bgtype {3}, queue type {4}.", GetPlayer().GetName(), GetPlayer().GetGUID().ToString(), bg.GetInstanceID(), bg.GetTypeID(), bgQueueTypeId); + } + else // leave queue + { + // if player leaves rated arena match before match start, it is counted as he played but he lost + if (ginfo.IsRated && ginfo.IsInvitedToBGInstanceGUID != 0) + { + ArenaTeam at = Global.ArenaTeamMgr.GetArenaTeamById((uint)ginfo.Team); + if (at != null) + { + Log.outDebug(LogFilter.Battleground, "UPDATING memberLost's personal arena rating for {0} by opponents rating: {1}, because he has left queue!", GetPlayer().GetGUID().ToString(), ginfo.OpponentsTeamRating); + at.MemberLost(GetPlayer(), ginfo.OpponentsMatchmakerRating); + at.SaveToDB(); + } + } + BattlefieldStatusNone battlefieldStatus = new BattlefieldStatusNone(); + battlefieldStatus.Ticket = battlefieldPort.Ticket; + SendPacket(battlefieldStatus); + + GetPlayer().RemoveBattlegroundQueueId(bgQueueTypeId); // must be called this way, because if you move this call to queue.removeplayer, it causes bugs + bgQueue.RemovePlayer(GetPlayer().GetGUID(), true); + // player left queue, we should update it - do not update Arena Queue + if (ginfo.ArenaType == 0) + Global.BattlegroundMgr.ScheduleQueueUpdate(ginfo.ArenaMatchmakerRating, ginfo.ArenaType, bgQueueTypeId, bgTypeId, bracketEntry.GetBracketId()); + + Log.outDebug(LogFilter.Battleground, "Battleground: player {0} ({1}) left queue for bgtype {2}, queue type {3}.", GetPlayer().GetName(), GetPlayer().GetGUID().ToString(), bg.GetTypeID(), bgQueueTypeId); + } + } + + [WorldPacketHandler(ClientOpcodes.BattlefieldLeave)] + void HandleBattlefieldLeave(BattlefieldLeave packet) + { + // not allow leave Battlegroundin combat + if (GetPlayer().IsInCombat()) + { + Battleground bg = GetPlayer().GetBattleground(); + if (bg) + if (bg.GetStatus() != BattlegroundStatus.WaitLeave) + return; + } + + GetPlayer().LeaveBattleground(); + } + + [WorldPacketHandler(ClientOpcodes.RequestBattlefieldStatus)] + void HandleRequestBattlefieldStatus(RequestBattlefieldStatus packet) + { + // we must update all queues here + Battleground bg = null; + for (byte i = 0; i < SharedConst.MaxPlayerBGQueues; ++i) + { + BattlegroundQueueTypeId bgQueueTypeId = GetPlayer().GetBattlegroundQueueTypeId(i); + if (bgQueueTypeId == 0) + continue; + + BattlegroundTypeId bgTypeId = Global.BattlegroundMgr.BGTemplateId(bgQueueTypeId); + ArenaTypes arenaType = Global.BattlegroundMgr.BGArenaType(bgQueueTypeId); + if (bgTypeId == GetPlayer().GetBattlegroundTypeId()) + { + bg = GetPlayer().GetBattleground(); + //i cannot check any variable from player class because player class doesn't know if player is in 2v2 / 3v3 or 5v5 arena + //so i must use bg pointer to get that information + if (bg && bg.GetArenaType() == arenaType) + { + BattlefieldStatusActive battlefieldStatus; + Global.BattlegroundMgr.BuildBattlegroundStatusActive(out battlefieldStatus, bg, GetPlayer(), i, GetPlayer().GetBattlegroundQueueJoinTime(bgQueueTypeId), arenaType); + SendPacket(battlefieldStatus); + continue; + } + } + + //we are sending update to player about queue - he can be invited there! + //get GroupQueueInfo for queue status + BattlegroundQueue bgQueue = Global.BattlegroundMgr.GetBattlegroundQueue(bgQueueTypeId); + GroupQueueInfo ginfo; + if (!bgQueue.GetPlayerGroupInfoData(GetPlayer().GetGUID(), out ginfo)) + continue; + + if (ginfo.IsInvitedToBGInstanceGUID != 0) + { + bg = Global.BattlegroundMgr.GetBattleground(ginfo.IsInvitedToBGInstanceGUID, bgTypeId); + if (!bg) + continue; + + BattlefieldStatusNeedConfirmation battlefieldStatus; + Global.BattlegroundMgr.BuildBattlegroundStatusNeedConfirmation(out battlefieldStatus, bg, GetPlayer(), i, GetPlayer().GetBattlegroundQueueJoinTime(bgQueueTypeId), Time.GetMSTimeDiff(Time.GetMSTime(), ginfo.RemoveInviteTime), arenaType); + SendPacket(battlefieldStatus); + } + else + { + bg = Global.BattlegroundMgr.GetBattlegroundTemplate(bgTypeId); + if (!bg) + continue; + + // expected bracket entry + PvpDifficultyRecord bracketEntry = Global.DB2Mgr.GetBattlegroundBracketByLevel(bg.GetMapId(), GetPlayer().getLevel()); + if (bracketEntry == null) + continue; + + uint avgTime = bgQueue.GetAverageQueueWaitTime(ginfo, bracketEntry.GetBracketId()); + BattlefieldStatusQueued battlefieldStatus; + Global.BattlegroundMgr.BuildBattlegroundStatusQueued(out battlefieldStatus, bg, GetPlayer(), i, GetPlayer().GetBattlegroundQueueJoinTime(bgQueueTypeId), avgTime, arenaType, ginfo.Players.Count > 1); + SendPacket(battlefieldStatus); + } + } + } + + [WorldPacketHandler(ClientOpcodes.BattlemasterJoinArena)] + void HandleBattlemasterJoinArena(BattlemasterJoinArena packet) + { + // ignore if we already in BG or BG queue + if (GetPlayer().InBattleground()) + return; + + ArenaTypes arenatype = (ArenaTypes)ArenaTeam.GetTypeBySlot(packet.TeamSizeIndex); + + //check existence + Battleground bg = Global.BattlegroundMgr.GetBattlegroundTemplate(BattlegroundTypeId.AA); + if (!bg) + { + Log.outError(LogFilter.Network, "Battleground: template bg (all arenas) not found"); + return; + } + + if (Global.DisableMgr.IsDisabledFor(DisableType.Battleground, (uint)BattlegroundTypeId.AA, null)) + { + GetPlayer().SendSysMessage(CypherStrings.ArenaDisabled); + return; + } + + BattlegroundTypeId bgTypeId = bg.GetTypeID(); + BattlegroundQueueTypeId bgQueueTypeId = Global.BattlegroundMgr.BGQueueTypeId(bgTypeId, arenatype); + PvpDifficultyRecord bracketEntry = Global.DB2Mgr.GetBattlegroundBracketByLevel(bg.GetMapId(), GetPlayer().getLevel()); + if (bracketEntry == null) + return; + + Group grp = GetPlayer().GetGroup(); + // no group found, error + if (!grp) + return; + if (grp.GetLeaderGUID() != GetPlayer().GetGUID()) + return; + + uint ateamId = GetPlayer().GetArenaTeamId(packet.TeamSizeIndex); + // check real arenateam existence only here (if it was moved to group.CanJoin .. () then we would ahve to get it twice) + ArenaTeam at = Global.ArenaTeamMgr.GetArenaTeamById(ateamId); + if (at == null) + { + GetPlayer().GetSession().SendNotInArenaTeamPacket(arenatype); + return; + } + + // get the team rating for queuing + uint arenaRating = at.GetRating(); + uint matchmakerRating = at.GetAverageMMR(grp); + // the arenateam id must match for everyone in the group + + if (arenaRating <= 0) + arenaRating = 1; + + BattlegroundQueue bgQueue = Global.BattlegroundMgr.GetBattlegroundQueue(bgQueueTypeId); + + uint avgTime = 0; + GroupQueueInfo ginfo = null; + + ObjectGuid errorGuid; + var err = grp.CanJoinBattlegroundQueue(bg, bgQueueTypeId, (uint)arenatype, (uint)arenatype, true, packet.TeamSizeIndex, out errorGuid); + if (err == 0) + { + Log.outDebug(LogFilter.Battleground, "Battleground: arena team id {0}, leader {1} queued with matchmaker rating {2} for type {3}", GetPlayer().GetArenaTeamId(packet.TeamSizeIndex), GetPlayer().GetName(), matchmakerRating, arenatype); + + ginfo = bgQueue.AddGroup(GetPlayer(), grp, bgTypeId, bracketEntry, arenatype, true, false, arenaRating, matchmakerRating, ateamId); + avgTime = bgQueue.GetAverageQueueWaitTime(ginfo, bracketEntry.GetBracketId()); + } + + for (GroupReference refe = grp.GetFirstMember(); refe != null; refe = refe.next()) + { + Player member = refe.GetSource(); + if (!member) + continue; + + if (err != 0) + { + BattlefieldStatusFailed battlefieldStatus; + Global.BattlegroundMgr.BuildBattlegroundStatusFailed(out battlefieldStatus, bg, GetPlayer(), 0, arenatype, err, errorGuid); + member.SendPacket(battlefieldStatus); + continue; + } + + // add to queue + uint queueSlot = member.AddBattlegroundQueueId(bgQueueTypeId); + + BattlefieldStatusQueued battlefieldStatusQueued; + Global.BattlegroundMgr.BuildBattlegroundStatusQueued(out battlefieldStatusQueued, bg, member, queueSlot, ginfo.JoinTime, avgTime, arenatype, true); + member.SendPacket(battlefieldStatusQueued); + + Log.outDebug(LogFilter.Battleground, "Battleground: player joined queue for arena as group bg queue type {0} bg type {1}: GUID {2}, NAME {3}", bgQueueTypeId, bgTypeId, member.GetGUID().ToString(), member.GetName()); + } + + Global.BattlegroundMgr.ScheduleQueueUpdate(matchmakerRating, arenatype, bgQueueTypeId, bgTypeId, bracketEntry.GetBracketId()); + } + + [WorldPacketHandler(ClientOpcodes.ReportPvpPlayerAfk)] + void HandleReportPvPAFK(ReportPvPPlayerAFK reportPvPPlayerAFK) + { + Player reportedPlayer = Global.ObjAccessor.FindPlayer(reportPvPPlayerAFK.Offender); + if (!reportedPlayer) + { + Log.outDebug(LogFilter.Battleground, "WorldSession.HandleReportPvPAFK: player not found"); + return; + } + + Log.outDebug(LogFilter.BattlegroundReportPvpAfk, "WorldSession.HandleReportPvPAFK: {0} [IP: {1}] reported {2}", _player.GetName(), _player.GetSession().GetRemoteAddress(), reportedPlayer.GetGUID().ToString()); + + reportedPlayer.ReportedAfkBy(GetPlayer()); + } + + [WorldPacketHandler(ClientOpcodes.RequestRatedBattlefieldInfo)] + void HandleRequestRatedBattlefieldInfo(RequestRatedBattlefieldInfo packet) + { + /// @Todo: perfome research in this case + /// The unk fields are related to arenas + WorldPacket data = new WorldPacket(ServerOpcodes.RatedBattlefieldInfo); + data.WriteInt32(0); // BgWeeklyWins20vs20 + data.WriteInt32(0); // BgWeeklyPlayed20vs20 + data.WriteInt32(0); // BgWeeklyPlayed15vs15 + data.WriteInt32(0); + data.WriteInt32(0); // BgWeeklyWins10vs10 + data.WriteInt32(0); + data.WriteInt32(0); + data.WriteInt32(0); + data.WriteInt32(0); // BgWeeklyWins15vs15 + data.WriteInt32(0); + data.WriteInt32(0); + data.WriteInt32(0); + data.WriteInt32(0); + data.WriteInt32(0); + data.WriteInt32(0); + data.WriteInt32(0); // BgWeeklyPlayed10vs10 + data.WriteInt32(0); + data.WriteInt32(0); + + //SendPacket(data); + } + + [WorldPacketHandler(ClientOpcodes.GetPvpOptionsEnabled, Processing = PacketProcessing.Inplace)] + void HandleGetPVPOptionsEnabled(GetPVPOptionsEnabled packet) + { + // This packet is completely irrelevant, it triggers PVP_TYPES_ENABLED lua event but that is not handled in interface code as of 6.1.2 + PVPOptionsEnabled pvpOptionsEnabled = new PVPOptionsEnabled(); + pvpOptionsEnabled.PugBattlegrounds = true; + SendPacket(new PVPOptionsEnabled()); + } + + [WorldPacketHandler(ClientOpcodes.RequestPvpRewards)] + void HandleRequestPvpReward(RequestPVPRewards packet) + { + GetPlayer().SendPvpRewards(); + } + + [WorldPacketHandler(ClientOpcodes.AreaSpiritHealerQuery)] + void HandleAreaSpiritHealerQuery(AreaSpiritHealerQuery areaSpiritHealerQuery) + { + Creature unit = ObjectAccessor.GetCreature(GetPlayer(), areaSpiritHealerQuery.HealerGuid); + if (!unit) + return; + + if (!unit.IsSpiritService()) // it's not spirit service + return; + + Battleground bg = GetPlayer().GetBattleground(); + if (bg != null) + Global.BattlegroundMgr.SendAreaSpiritHealerQuery(GetPlayer(), bg, areaSpiritHealerQuery.HealerGuid); + + BattleField bf = Global.BattleFieldMgr.GetBattlefieldToZoneId(GetPlayer().GetZoneId()); + if (bf != null) + bf.SendAreaSpiritHealerQuery(GetPlayer(), areaSpiritHealerQuery.HealerGuid); + } + + [WorldPacketHandler(ClientOpcodes.AreaSpiritHealerQueue)] + void HandleAreaSpiritHealerQueue(AreaSpiritHealerQueue areaSpiritHealerQueue) + { + Creature unit = ObjectAccessor.GetCreature(GetPlayer(), areaSpiritHealerQueue.HealerGuid); + if (!unit) + return; + + if (!unit.IsSpiritService()) // it's not spirit service + return; + + Battleground bg = GetPlayer().GetBattleground(); + if (bg) + bg.AddPlayerToResurrectQueue(areaSpiritHealerQueue.HealerGuid, GetPlayer().GetGUID()); + + BattleField bf = Global.BattleFieldMgr.GetBattlefieldToZoneId(GetPlayer().GetZoneId()); + if (bf != null) + bf.AddPlayerToResurrectQueue(areaSpiritHealerQueue.HealerGuid, GetPlayer().GetGUID()); + } + + [WorldPacketHandler(ClientOpcodes.HearthAndResurrect)] + void HandleHearthAndResurrect(HearthAndResurrect packet) + { + if (GetPlayer().IsInFlight()) + return; + + BattleField bf = Global.BattleFieldMgr.GetBattlefieldToZoneId(GetPlayer().GetZoneId()); + if (bf != null) + { + bf.PlayerAskToLeave(_player); + return; + } + + AreaTableRecord atEntry = CliDB.AreaTableStorage.LookupByKey(GetPlayer().GetAreaId()); + if (atEntry == null || !atEntry.Flags[0].HasAnyFlag(AreaFlags.CanHearthAndResurrect)) + return; + + GetPlayer().BuildPlayerRepop(); + GetPlayer().ResurrectPlayer(1.0f); + GetPlayer().TeleportTo(GetPlayer().GetHomebind()); + } + } +} diff --git a/Game/Handlers/BattlePetHandler.cs b/Game/Handlers/BattlePetHandler.cs new file mode 100644 index 000000000..8a52b4ec4 --- /dev/null +++ b/Game/Handlers/BattlePetHandler.cs @@ -0,0 +1,96 @@ +/* + * Copyright (C) 2012-2017 CypherCore + * + * 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, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +using Framework.Constants; +using Game.BattlePets; +using Game.Network; +using Game.Network.Packets; + +namespace Game +{ + public partial class WorldSession + { + [WorldPacketHandler(ClientOpcodes.BattlePetRequestJournal)] + void HandleBattlePetRequestJournal(BattlePetRequestJournal battlePetRequestJournal) + { + // TODO: Move this to BattlePetMgr::SendJournal() just to have all packets in one file + BattlePetJournal battlePetJournal = new BattlePetJournal(); + battlePetJournal.Trap = GetBattlePetMgr().GetTrapLevel(); + + foreach (var battlePet in GetBattlePetMgr().GetLearnedPets()) + battlePetJournal.Pets.Add(battlePet.PacketInfo); + + battlePetJournal.Slots = GetBattlePetMgr().GetSlots(); + SendPacket(battlePetJournal); + } + + [WorldPacketHandler(ClientOpcodes.BattlePetSetBattleSlot)] + void HandleBattlePetSetBattleSlot(BattlePetSetBattleSlot battlePetSetBattleSlot) + { + BattlePetMgr.BattlePet pet = GetBattlePetMgr().GetPet(battlePetSetBattleSlot.PetGuid); + if (pet != null) + GetBattlePetMgr().GetSlot(battlePetSetBattleSlot.Slot).Pet = pet.PacketInfo; + } + + [WorldPacketHandler(ClientOpcodes.BattlePetModifyName)] + void HandleBattlePetModifyName(BattlePetModifyName battlePetModifyName) + { + BattlePetMgr.BattlePet pet = GetBattlePetMgr().GetPet(battlePetModifyName.PetGuid); + if (pet != null) + { + pet.PacketInfo.Name = battlePetModifyName.Name; + + if (pet.SaveInfo != BattlePetSaveInfo.New) + pet.SaveInfo = BattlePetSaveInfo.Changed; + } + } + + [WorldPacketHandler(ClientOpcodes.BattlePetDeletePet)] + void HandleBattlePetDeletePet(BattlePetDeletePet battlePetDeletePet) + { + GetBattlePetMgr().RemovePet(battlePetDeletePet.PetGuid); + } + + [WorldPacketHandler(ClientOpcodes.BattlePetSetFlags)] + void HandleBattlePetSetFlags(BattlePetSetFlags battlePetSetFlags) + { + var pet = GetBattlePetMgr().GetPet(battlePetSetFlags.PetGuid); + if (pet != null) + { + if (battlePetSetFlags.ControlType == FlagsControlType.Apply) + pet.PacketInfo.Flags |= (ushort)battlePetSetFlags.Flags; + else + pet.PacketInfo.Flags &= (ushort)~battlePetSetFlags.Flags; + + if (pet.SaveInfo != BattlePetSaveInfo.New) + pet.SaveInfo = BattlePetSaveInfo.Changed; + } + } + + [WorldPacketHandler(ClientOpcodes.CageBattlePet)] + void HandleCageBattlePet(CageBattlePet cageBattlePet) + { + GetBattlePetMgr().CageBattlePet(cageBattlePet.PetGuid); + } + + [WorldPacketHandler(ClientOpcodes.BattlePetSummon, Processing = PacketProcessing.Inplace)] + void HandleBattlePetSummon(BattlePetSummon battlePetSummon) + { + GetBattlePetMgr().SummonPet(battlePetSummon.PetGuid); + } + } +} diff --git a/Game/Handlers/BattlenetHandler.cs b/Game/Handlers/BattlenetHandler.cs new file mode 100644 index 000000000..4db37d46c --- /dev/null +++ b/Game/Handlers/BattlenetHandler.cs @@ -0,0 +1,103 @@ +/* + * Copyright (C) 2012-2017 CypherCore + * + * 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, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +using Framework.Constants; +using Game.Network; +using Game.Network.Packets; +using Game.Services; +using Google.Protobuf; +using System; + +namespace Game +{ + public partial class WorldSession + { + [WorldPacketHandler(ClientOpcodes.BattlenetRequest, Status = SessionStatus.Authed)] + void HandleBattlenetRequest(BattlenetRequest request) + { + var handler = ServiceManager.GetHandler((NameHash)request.Method.GetServiceHash(), request.Method.GetMethodId()); + if (handler == null) + { + Log.outError(LogFilter.Server, "No defined handler ServiceHash {0} MethodId {1} Sent by {2}", (NameHash)request.Method.GetServiceHash(), request.Method.GetMethodId(), GetPlayerInfo()); + } + else + { + var status = handler.Invoke(this, new CodedInputStream(request.Data)); + if (status != BattlenetRpcErrorCode.Ok) + SendBattlenetResponse(request.Method.GetServiceHash(), request.Method.GetMethodId(), status); + } + } + + [WorldPacketHandler(ClientOpcodes.BattlenetRequestRealmListTicket, Status = SessionStatus.Authed)] + void HandleBattlenetRequestRealmListTicket(RequestRealmListTicket requestRealmListTicket) + { + SetRealmListSecret(requestRealmListTicket.Secret); + + RealmListTicket realmListTicket = new RealmListTicket(); + realmListTicket.Token = requestRealmListTicket.Token; + realmListTicket.Allow = true; + realmListTicket.Ticket = new Framework.IO.ByteBuffer(); + realmListTicket.Ticket.WriteCString("WorldserverRealmListTicket"); + + SendPacket(realmListTicket); + } + + public void SendBattlenetResponse(uint serviceHash, uint methodId, IMessage response) + { + Response bnetResponse = new Response(); + bnetResponse.BnetStatus = BattlenetRpcErrorCode.Ok; + bnetResponse.Method.Type = MathFunctions.MakePair64(methodId, serviceHash); + bnetResponse.Method.ObjectId = 1; + bnetResponse.Method.Token = _battlenetRequestToken++; + + if (response.CalculateSize() != 0) + bnetResponse.Data.WriteBytes(response.ToByteArray()); + + SendPacket(bnetResponse); + } + + public void SendBattlenetResponse(uint serviceHash, uint methodId, BattlenetRpcErrorCode status) + { + Response bnetResponse = new Response(); + bnetResponse.BnetStatus = status; + bnetResponse.Method.Type = MathFunctions.MakePair64(methodId, serviceHash); + bnetResponse.Method.ObjectId = 1; + bnetResponse.Method.Token = _battlenetRequestToken++; + + SendPacket(bnetResponse); + } + + public void SendBattlenetRequest(uint serviceHash, uint methodId, IMessage request, Action callback) + { + _battlenetResponseCallbacks[_battlenetRequestToken] = callback; + SendBattlenetRequest(serviceHash, methodId, request); + } + + public void SendBattlenetRequest(uint serviceHash, uint methodId, IMessage request) + { + Notification notification = new Notification(); + notification.Method.Type = MathFunctions.MakePair64(methodId, serviceHash); + notification.Method.ObjectId = 1; + notification.Method.Token = _battlenetRequestToken++; + + if (request.CalculateSize() != 0) + notification.Data.WriteBytes(request.ToByteArray()); + + SendPacket(notification); + } + } +} diff --git a/Game/Handlers/BlackMarketHandlers.cs b/Game/Handlers/BlackMarketHandlers.cs new file mode 100644 index 000000000..1070ffd41 --- /dev/null +++ b/Game/Handlers/BlackMarketHandlers.cs @@ -0,0 +1,165 @@ +/* + * Copyright (C) 2012-2017 CypherCore + * + * 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, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +using Framework.Constants; +using Framework.Database; +using Game.BlackMarket; +using Game.Entities; +using Game.Network; +using Game.Network.Packets; + +namespace Game +{ + public partial class WorldSession + { + [WorldPacketHandler(ClientOpcodes.BlackMarketOpen)] + void HandleBlackMarketOpen(BlackMarketOpen blackMarketOpen) + { + Creature unit = GetPlayer().GetNPCIfCanInteractWith(blackMarketOpen.Guid, NPCFlags.BlackMarket | NPCFlags.BlackMarketView); + if (!unit) + { + Log.outDebug(LogFilter.Network, "WORLD: HandleBlackMarketHello - {0} not found or you can't interact with him.", blackMarketOpen.Guid.ToString()); + return; + } + + // remove fake death + if (GetPlayer().HasUnitState(UnitState.Died)) + GetPlayer().RemoveAurasByType(AuraType.FeignDeath); + + SendBlackMarketOpenResult(blackMarketOpen.Guid, unit); + } + + void SendBlackMarketOpenResult(ObjectGuid guid, Creature auctioneer) + { + BlackMarketOpenResult packet = new BlackMarketOpenResult(); + packet.Guid = guid; + packet.Enable = Global.BlackMarketMgr.IsEnabled(); + SendPacket(packet); + } + + [WorldPacketHandler(ClientOpcodes.BlackMarketRequestItems)] + void HandleBlackMarketRequestItems(BlackMarketRequestItems blackMarketRequestItems) + { + if (!Global.BlackMarketMgr.IsEnabled()) + return; + + Creature unit = GetPlayer().GetNPCIfCanInteractWith(blackMarketRequestItems.Guid, NPCFlags.BlackMarket | NPCFlags.BlackMarketView); + if (!unit) + { + Log.outDebug(LogFilter.Network, "WORLD: HandleBlackMarketRequestItems - {0} not found or you can't interact with him.", blackMarketRequestItems.Guid.ToString()); + return; + } + + BlackMarketRequestItemsResult result = new BlackMarketRequestItemsResult(); + Global.BlackMarketMgr.BuildItemsResponse(result, GetPlayer()); + SendPacket(result); + } + + [WorldPacketHandler(ClientOpcodes.BlackMarketBidOnItem)] + void HandleBlackMarketBidOnItem(BlackMarketBidOnItem blackMarketBidOnItem) + { + if (!Global.BlackMarketMgr.IsEnabled()) + return; + + Player player = GetPlayer(); + Creature unit = player.GetNPCIfCanInteractWith(blackMarketBidOnItem.Guid, NPCFlags.BlackMarket); + if (!unit) + { + Log.outDebug(LogFilter.Network, "WORLD: HandleBlackMarketBidOnItem - {0} not found or you can't interact with him.", blackMarketBidOnItem.Guid.ToString()); + return; + } + + BlackMarketEntry entry = Global.BlackMarketMgr.GetAuctionByID(blackMarketBidOnItem.MarketID); + if (entry == null) + { + Log.outDebug(LogFilter.Network, "WORLD: HandleBlackMarketBidOnItem - {0} (name: {1}) tried to bid on a nonexistent auction (MarketId: {2}).", player.GetGUID().ToString(), player.GetName(), blackMarketBidOnItem.MarketID); + SendBlackMarketBidOnItemResult(BlackMarketError.ItemNotFound, blackMarketBidOnItem.MarketID, blackMarketBidOnItem.Item); + return; + } + + if (entry.GetBidder() == player.GetGUID().GetCounter()) + { + Log.outDebug(LogFilter.Network, "WORLD: HandleBlackMarketBidOnItem - {0} (name: {1}) tried to place a bid on an item he already bid on. (MarketId: {2}).", player.GetGUID().ToString(), player.GetName(), blackMarketBidOnItem.MarketID); + SendBlackMarketBidOnItemResult(BlackMarketError.AlreadyBid, blackMarketBidOnItem.MarketID, blackMarketBidOnItem.Item); + return; + } + + if (!entry.ValidateBid(blackMarketBidOnItem.BidAmount)) + { + Log.outDebug(LogFilter.Network, "WORLD: HandleBlackMarketBidOnItem - {0} (name: {1}) tried to place an invalid bid. Amount: {2} (MarketId: {3}).", player.GetGUID().ToString(), player.GetName(), blackMarketBidOnItem.BidAmount, blackMarketBidOnItem.MarketID); + SendBlackMarketBidOnItemResult(BlackMarketError.HigherBid, blackMarketBidOnItem.MarketID, blackMarketBidOnItem.Item); + return; + } + + if (!player.HasEnoughMoney(blackMarketBidOnItem.BidAmount)) + { + Log.outDebug(LogFilter.Network, "WORLD: HandleBlackMarketBidOnItem - {0} (name: {1}) does not have enough money to place bid. (MarketId: {2}).", player.GetGUID().ToString(), player.GetName(), blackMarketBidOnItem.MarketID); + SendBlackMarketBidOnItemResult(BlackMarketError.NotEnoughMoney, blackMarketBidOnItem.MarketID, blackMarketBidOnItem.Item); + return; + } + + if (entry.GetSecondsRemaining() <= 0) + { + Log.outDebug(LogFilter.Network, "WORLD: HandleBlackMarketBidOnItem - {0} (name: {1}) tried to bid on a completed auction. (MarketId: {2}).", player.GetGUID().ToString(), player.GetName(), blackMarketBidOnItem.MarketID); + SendBlackMarketBidOnItemResult(BlackMarketError.DatabaseError, blackMarketBidOnItem.MarketID, blackMarketBidOnItem.Item); + return; + } + + SQLTransaction trans = new SQLTransaction(); + + Global.BlackMarketMgr.SendAuctionOutbidMail(entry, trans); + entry.PlaceBid(blackMarketBidOnItem.BidAmount, player, trans); + + DB.Characters.CommitTransaction(trans); + + SendBlackMarketBidOnItemResult(BlackMarketError.Ok, blackMarketBidOnItem.MarketID, blackMarketBidOnItem.Item); + } + + void SendBlackMarketBidOnItemResult(BlackMarketError result, uint marketId, ItemInstance item) + { + BlackMarketBidOnItemResult packet = new BlackMarketBidOnItemResult(); + + packet.MarketID = marketId; + packet.Item = item; + packet.Result = result; + + SendPacket(packet); + } + + public void SendBlackMarketWonNotification(BlackMarketEntry entry, Item item) + { + BlackMarketWon packet = new BlackMarketWon(); + + packet.MarketID = entry.GetMarketId(); + packet.Item = new ItemInstance(item); + packet.RandomPropertiesID = item.GetItemRandomPropertyId(); + + SendPacket(packet); + } + + public void SendBlackMarketOutbidNotification(BlackMarketTemplate templ) + { + BlackMarketOutbid packet = new BlackMarketOutbid(); + + packet.MarketID = templ.MarketID; + packet.Item = templ.Item; + packet.RandomPropertiesID = templ.Item.RandomPropertiesID; + + SendPacket(packet); + } + } +} diff --git a/Game/Handlers/CalendarHandler.cs b/Game/Handlers/CalendarHandler.cs new file mode 100644 index 000000000..df3364f8a --- /dev/null +++ b/Game/Handlers/CalendarHandler.cs @@ -0,0 +1,524 @@ +/* + * Copyright (C) 2012-2017 CypherCore + * + * 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, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +using Framework.Constants; +using Framework.Database; +using Game.Entities; +using Game.Guilds; +using Game.Maps; +using Game.Network; +using Game.Network.Packets; +using System; + +namespace Game +{ + public partial class WorldSession + { + [WorldPacketHandler(ClientOpcodes.CalendarGet)] + void HandleCalendarGetCalendar(CalendarGetCalendar calendarGetCalendar) + { + ObjectGuid guid = GetPlayer().GetGUID(); + + long currTime = Time.UnixTime; + + CalendarSendCalendar packet = new CalendarSendCalendar(); + packet.ServerTime = currTime; + + var invites = Global.CalendarMgr.GetPlayerInvites(guid); + foreach (var invite in invites) + { + CalendarSendCalendarInviteInfo inviteInfo = new CalendarSendCalendarInviteInfo(); + inviteInfo.EventID = invite.EventId; + inviteInfo.InviteID = invite.InviteId; + inviteInfo.InviterGuid = invite.SenderGuid; + inviteInfo.Status = invite.Status; + inviteInfo.Moderator = invite.Rank; + CalendarEvent calendarEvent = Global.CalendarMgr.GetEvent(invite.EventId); + if (calendarEvent != null) + inviteInfo.InviteType = (byte)(calendarEvent.IsGuildEvent() && calendarEvent.GuildId == _player.GetGuildId() ? 1 : 0); + + packet.Invites.Add(inviteInfo); + } + + var playerEvents = Global.CalendarMgr.GetPlayerEvents(guid); + foreach (var calendarEvent in playerEvents) + { + CalendarSendCalendarEventInfo eventInfo; + eventInfo.EventID = calendarEvent.EventId; + eventInfo.Date = calendarEvent.Date; + Guild guild = Global.GuildMgr.GetGuildById(calendarEvent.GuildId); + eventInfo.EventGuildID = guild ? guild.GetGUID() : ObjectGuid.Empty; + eventInfo.EventName = calendarEvent.Title; + eventInfo.EventType = calendarEvent.EventType; + eventInfo.Flags = calendarEvent.Flags; + eventInfo.OwnerGuid = calendarEvent.OwnerGuid; + eventInfo.TextureID = calendarEvent.TextureId; + + packet.Events.Add(eventInfo); + } + + for (byte i = 0; i < (int)Difficulty.Max; ++i) + { + var boundInstances = GetPlayer().GetBoundInstances((Difficulty)i); + foreach (var pair in boundInstances) + { + if (pair.Value.perm) + { + CalendarSendCalendarRaidLockoutInfo lockoutInfo; + + InstanceSave save = pair.Value.save; + lockoutInfo.MapID = (int)save.GetMapId(); + lockoutInfo.DifficultyID = (uint)save.GetDifficultyID(); + lockoutInfo.ExpireTime = save.GetResetTime() - currTime; + lockoutInfo.InstanceID = save.GetInstanceId(); // instance save id as unique instance copy id + + packet.RaidLockouts.Add(lockoutInfo); + } + } + } + + SendPacket(packet); + } + + [WorldPacketHandler(ClientOpcodes.CalendarGetEvent)] + void HandleCalendarGetEvent(CalendarGetEvent calendarGetEvent) + { + CalendarEvent calendarEvent = Global.CalendarMgr.GetEvent(calendarGetEvent.EventID); + if (calendarEvent != null) + Global.CalendarMgr.SendCalendarEvent(GetPlayer().GetGUID(), calendarEvent, CalendarSendEventType.Get); + else + Global.CalendarMgr.SendCalendarCommandResult(GetPlayer().GetGUID(), CalendarError.EventInvalid); + } + + [WorldPacketHandler(ClientOpcodes.CalendarGuildFilter)] + void HandleCalendarGuildFilter(CalendarGuildFilter calendarGuildFilter) + { + Guild guild = Global.GuildMgr.GetGuildById(GetPlayer().GetGuildId()); + if (guild) + guild.MassInviteToEvent(this, calendarGuildFilter.MinLevel, calendarGuildFilter.MaxLevel, calendarGuildFilter.MaxRankOrder); + } + + [WorldPacketHandler(ClientOpcodes.CalendarAddEvent)] + void HandleCalendarAddEvent(CalendarAddEvent calendarAddEvent) + { + ObjectGuid guid = GetPlayer().GetGUID(); + + // prevent events in the past + // To Do: properly handle timezones and remove the "- time_t(86400L)" hack + if (calendarAddEvent.EventInfo.Time < (Time.UnixTime - 86400L)) + return; + + CalendarEvent calendarEvent = new CalendarEvent(Global.CalendarMgr.GetFreeEventId(), guid, 0, (CalendarEventType)calendarAddEvent.EventInfo.EventType, calendarAddEvent.EventInfo.TextureID, + calendarAddEvent.EventInfo.Time, (CalendarFlags)calendarAddEvent.EventInfo.Flags, calendarAddEvent.EventInfo.Title, calendarAddEvent.EventInfo.Description, 0); + + if (calendarEvent.IsGuildEvent() || calendarEvent.IsGuildAnnouncement()) + { + Player creator = Global.ObjAccessor.FindPlayer(guid); + if (creator) + calendarEvent.GuildId = creator.GetGuildId(); + } + + if (calendarEvent.IsGuildAnnouncement()) + { + CalendarInvite invite = new CalendarInvite(0, calendarEvent.EventId, ObjectGuid.Empty, guid, SharedConst.CalendarDefaultResponseTime, CalendarInviteStatus.NotSignedUp, CalendarModerationRank.Player, ""); + // WARNING: By passing pointer to a local variable, the underlying method(s) must NOT perform any kind + // of storage of the pointer as it will lead to memory corruption + Global.CalendarMgr.AddInvite(calendarEvent, invite); + } + else + { + SQLTransaction trans = null; + if (calendarAddEvent.EventInfo.Invites.Length > 1) + trans = new SQLTransaction(); + + for (int i = 0; i < calendarAddEvent.EventInfo.Invites.Length; ++i) + { + CalendarInvite invite = new CalendarInvite(Global.CalendarMgr.GetFreeInviteId(), calendarEvent.EventId, + calendarAddEvent.EventInfo.Invites[i].Guid, guid, SharedConst.CalendarDefaultResponseTime, (CalendarInviteStatus)calendarAddEvent.EventInfo.Invites[i].Status, + (CalendarModerationRank)calendarAddEvent.EventInfo.Invites[i].Moderator, ""); + Global.CalendarMgr.AddInvite(calendarEvent, invite, trans); + } + + if (calendarAddEvent.EventInfo.Invites.Length > 1) + DB.Characters.CommitTransaction(trans); + } + + Global.CalendarMgr.AddEvent(calendarEvent, CalendarSendEventType.Add); + } + + [WorldPacketHandler(ClientOpcodes.CalendarUpdateEvent)] + void HandleCalendarUpdateEvent(CalendarUpdateEvent calendarUpdateEvent) + { + ObjectGuid guid = GetPlayer().GetGUID(); + long oldEventTime; + + // prevent events in the past + // To Do: properly handle timezones and remove the "- time_t(86400L)" hack + if (calendarUpdateEvent.EventInfo.Time < (Time.UnixTime - 86400L)) + return; + + CalendarEvent calendarEvent = Global.CalendarMgr.GetEvent(calendarUpdateEvent.EventInfo.EventID); + if (calendarEvent != null) + { + oldEventTime = calendarEvent.Date; + + calendarEvent.EventType = (CalendarEventType)calendarUpdateEvent.EventInfo.EventType; + calendarEvent.Flags = (CalendarFlags)calendarUpdateEvent.EventInfo.Flags; + calendarEvent.Date = calendarUpdateEvent.EventInfo.Time; + calendarEvent.TextureId = (int)calendarUpdateEvent.EventInfo.TextureID; + calendarEvent.Title = calendarUpdateEvent.EventInfo.Title; + calendarEvent.Description = calendarUpdateEvent.EventInfo.Description; + + Global.CalendarMgr.UpdateEvent(calendarEvent); + Global.CalendarMgr.SendCalendarEventUpdateAlert(calendarEvent, oldEventTime); + } + else + Global.CalendarMgr.SendCalendarCommandResult(guid, CalendarError.EventInvalid); + } + + [WorldPacketHandler(ClientOpcodes.CalendarRemoveEvent)] + void HandleCalendarRemoveEvent(CalendarRemoveEvent calendarRemoveEvent) + { + ObjectGuid guid = GetPlayer().GetGUID(); + Global.CalendarMgr.RemoveEvent(calendarRemoveEvent.EventID, guid); + } + + [WorldPacketHandler(ClientOpcodes.CalendarCopyEvent)] + void HandleCalendarCopyEvent(CalendarCopyEvent calendarCopyEvent) + { + ObjectGuid guid = GetPlayer().GetGUID(); + + // prevent events in the past + // To Do: properly handle timezones and remove the "- time_t(86400L)" hack + if (calendarCopyEvent.Date < (Time.UnixTime - 86400L)) + return; + + CalendarEvent oldEvent = Global.CalendarMgr.GetEvent(calendarCopyEvent.EventID); + if (oldEvent == null) + { + CalendarEvent newEvent = new CalendarEvent(oldEvent, Global.CalendarMgr.GetFreeEventId()); + newEvent.Date = calendarCopyEvent.Date; + Global.CalendarMgr.AddEvent(newEvent, CalendarSendEventType.Copy); + + var invites = Global.CalendarMgr.GetEventInvites(calendarCopyEvent.EventID); + SQLTransaction trans = null; + if (invites.Count > 1) + trans = new SQLTransaction(); + + foreach (var invite in invites) + Global.CalendarMgr.AddInvite(newEvent, new CalendarInvite(invite, Global.CalendarMgr.GetFreeInviteId(), newEvent.EventId), trans); + + if (invites.Count > 1) + DB.Characters.CommitTransaction(trans); + // should we change owner when somebody makes a copy of event owned by another person? + } + else + Global.CalendarMgr.SendCalendarCommandResult(guid, CalendarError.EventInvalid); + } + + [WorldPacketHandler(ClientOpcodes.CalendarEventInvite)] + void HandleCalendarEventInvite(CalendarEventInvite calendarEventInvite) + { + ObjectGuid playerGuid = GetPlayer().GetGUID(); + + ObjectGuid inviteeGuid = ObjectGuid.Empty; + Team inviteeTeam = 0; + ulong inviteeGuildId = 0; + + Player player = Global.ObjAccessor.FindPlayerByName(calendarEventInvite.Name); + if (player) + { + // Invitee is online + inviteeGuid = player.GetGUID(); + inviteeTeam = player.GetTeam(); + inviteeGuildId = player.GetGuildId(); + } + else + { + // Invitee offline, get data from database + PreparedStatement stmt = DB.Characters.GetPreparedStatement(CharStatements.SEL_GUID_RACE_ACC_BY_NAME); + stmt.AddValue(0, calendarEventInvite.Name); + SQLResult result = DB.Characters.Query(stmt); + if (!result.IsEmpty()) + { + inviteeGuid = ObjectGuid.Create(HighGuid.Player, result.Read(0)); + inviteeTeam = Player.TeamForRace((Race)result.Read(1)); + inviteeGuildId = Player.GetGuildIdFromDB(inviteeGuid); + } + } + + if (inviteeGuid.IsEmpty()) + { + Global.CalendarMgr.SendCalendarCommandResult(playerGuid, CalendarError.PlayerNotFound); + return; + } + + if (GetPlayer().GetTeam() != inviteeTeam && !WorldConfig.GetBoolValue(WorldCfg.AllowTwoSideInteractionCalendar)) + { + Global.CalendarMgr.SendCalendarCommandResult(playerGuid, CalendarError.NotAllied); + return; + } + + SQLResult result1 = DB.Characters.Query("SELECT flags FROM character_social WHERE guid = {0} AND friend = {1}", inviteeGuid, playerGuid); + if (!result1.IsEmpty()) + { + + if (Convert.ToBoolean(result1.Read(0) & (byte)SocialFlag.Ignored)) + { + Global.CalendarMgr.SendCalendarCommandResult(playerGuid, CalendarError.IgnoringYouS, calendarEventInvite.Name); + return; + } + } + + if (!calendarEventInvite.Creating) + { + CalendarEvent calendarEvent = Global.CalendarMgr.GetEvent(calendarEventInvite.EventID); + if (calendarEvent != null) + { + if (calendarEvent.IsGuildEvent() && calendarEvent.GuildId == inviteeGuildId) + { + // we can't invite guild members to guild events + Global.CalendarMgr.SendCalendarCommandResult(playerGuid, CalendarError.NoGuildInvites); + return; + } + + CalendarInvite invite = new CalendarInvite(Global.CalendarMgr.GetFreeInviteId(), calendarEventInvite.EventID, inviteeGuid, playerGuid, SharedConst.CalendarDefaultResponseTime, CalendarInviteStatus.Invited, CalendarModerationRank.Player, ""); + Global.CalendarMgr.AddInvite(calendarEvent, invite); + } + else + Global.CalendarMgr.SendCalendarCommandResult(playerGuid, CalendarError.EventInvalid); + } + else + { + if (calendarEventInvite.IsSignUp && inviteeGuildId == GetPlayer().GetGuildId()) + { + Global.CalendarMgr.SendCalendarCommandResult(playerGuid, CalendarError.NoGuildInvites); + return; + } + + CalendarInvite invite = new CalendarInvite(calendarEventInvite.EventID, 0, inviteeGuid, playerGuid, SharedConst.CalendarDefaultResponseTime, CalendarInviteStatus.Invited, CalendarModerationRank.Player, ""); + Global.CalendarMgr.SendCalendarEventInvite(invite); + } + } + + [WorldPacketHandler(ClientOpcodes.CalendarEventSignUp)] + void HandleCalendarEventSignup(CalendarEventSignUp calendarEventSignUp) + { + ObjectGuid guid = GetPlayer().GetGUID(); + + CalendarEvent calendarEvent = Global.CalendarMgr.GetEvent(calendarEventSignUp.EventID); + if (calendarEvent != null) + { + if (calendarEvent.IsGuildEvent() && calendarEvent.GuildId != GetPlayer().GetGuildId()) + { + Global.CalendarMgr.SendCalendarCommandResult(guid, CalendarError.GuildPlayerNotInGuild); + return; + } + + CalendarInviteStatus status = calendarEventSignUp.Tentative ? CalendarInviteStatus.Tentative : CalendarInviteStatus.SignedUp; + CalendarInvite invite = new CalendarInvite(Global.CalendarMgr.GetFreeInviteId(), calendarEventSignUp.EventID, guid, guid, Time.UnixTime, status, CalendarModerationRank.Player, ""); + Global.CalendarMgr.AddInvite(calendarEvent, invite); + Global.CalendarMgr.SendCalendarClearPendingAction(guid); + } + else + Global.CalendarMgr.SendCalendarCommandResult(guid, CalendarError.EventInvalid); + } + + [WorldPacketHandler(ClientOpcodes.CalendarEventRsvp)] + void HandleCalendarEventRsvp(CalendarEventRSVP calendarEventRSVP) + { + ObjectGuid guid = GetPlayer().GetGUID(); + + CalendarEvent calendarEvent = Global.CalendarMgr.GetEvent(calendarEventRSVP.EventID); + if (calendarEvent != null) + { + // i think we still should be able to remove self from locked events + if (calendarEventRSVP.Status != CalendarInviteStatus.Removed && calendarEvent.IsLocked()) + { + Global.CalendarMgr.SendCalendarCommandResult(guid, CalendarError.EventLocked); + return; + } + + CalendarInvite invite = Global.CalendarMgr.GetInvite(calendarEventRSVP.InviteID); + if (invite != null) + { + invite.Status = calendarEventRSVP.Status; + invite.ResponseTime = Time.UnixTime; + + Global.CalendarMgr.UpdateInvite(invite); + Global.CalendarMgr.SendCalendarEventStatus(calendarEvent, invite); + Global.CalendarMgr.SendCalendarClearPendingAction(guid); + } + else + Global.CalendarMgr.SendCalendarCommandResult(guid, CalendarError.NoInvite); // correct? + } + else + Global.CalendarMgr.SendCalendarCommandResult(guid, CalendarError.EventInvalid); + } + + [WorldPacketHandler(ClientOpcodes.CalendarRemoveInvite)] + void HandleCalendarEventRemoveInvite(CalendarRemoveInvite calendarRemoveInvite) + { + ObjectGuid guid = GetPlayer().GetGUID(); + + CalendarEvent calendarEvent = Global.CalendarMgr.GetEvent(calendarRemoveInvite.EventID); + if (calendarEvent != null) + { + if (calendarEvent.OwnerGuid == calendarRemoveInvite.Guid) + { + Global.CalendarMgr.SendCalendarCommandResult(guid, CalendarError.DeleteCreatorFailed); + return; + } + + Global.CalendarMgr.RemoveInvite(calendarRemoveInvite.InviteID, calendarRemoveInvite.EventID, guid); + } + else + Global.CalendarMgr.SendCalendarCommandResult(guid, CalendarError.NoInvite); + } + + [WorldPacketHandler(ClientOpcodes.CalendarEventStatus)] + void HandleCalendarEventStatus(CalendarEventStatus calendarEventStatus) + { + ObjectGuid guid = GetPlayer().GetGUID(); + + CalendarEvent calendarEvent = Global.CalendarMgr.GetEvent(calendarEventStatus.EventID); + if (calendarEvent != null) + { + CalendarInvite invite = Global.CalendarMgr.GetInvite(calendarEventStatus.InviteID); + if (invite != null) + { + invite.Status = (CalendarInviteStatus)calendarEventStatus.Status; + + Global.CalendarMgr.UpdateInvite(invite); + Global.CalendarMgr.SendCalendarEventStatus(calendarEvent, invite); + Global.CalendarMgr.SendCalendarClearPendingAction(calendarEventStatus.Guid); + } + else + Global.CalendarMgr.SendCalendarCommandResult(guid, CalendarError.NoInvite); // correct? + } + else + Global.CalendarMgr.SendCalendarCommandResult(guid, CalendarError.EventInvalid); + } + + [WorldPacketHandler(ClientOpcodes.CalendarEventModeratorStatus)] + void HandleCalendarEventModeratorStatus(CalendarEventModeratorStatus calendarEventModeratorStatus) + { + ObjectGuid guid = GetPlayer().GetGUID(); + + CalendarEvent calendarEvent = Global.CalendarMgr.GetEvent(calendarEventModeratorStatus.EventID); + if (calendarEvent != null) + { + CalendarInvite invite = Global.CalendarMgr.GetInvite(calendarEventModeratorStatus.InviteID); + if (invite != null) + { + invite.Rank = (CalendarModerationRank)calendarEventModeratorStatus.Status; + Global.CalendarMgr.UpdateInvite(invite); + Global.CalendarMgr.SendCalendarEventModeratorStatusAlert(calendarEvent, invite); + } + else + Global.CalendarMgr.SendCalendarCommandResult(guid, CalendarError.NoInvite); // correct? + } + else + Global.CalendarMgr.SendCalendarCommandResult(guid, CalendarError.EventInvalid); + } + + [WorldPacketHandler(ClientOpcodes.CalendarComplain)] + void HandleCalendarComplain(CalendarComplain calendarComplain) + { + // what to do with complains? + } + + [WorldPacketHandler(ClientOpcodes.CalendarGetNumPending)] + void HandleCalendarGetNumPending(CalendarGetNumPending calendarGetNumPending) + { + ObjectGuid guid = GetPlayer().GetGUID(); + uint pending = Global.CalendarMgr.GetPlayerNumPending(guid); + + SendPacket(new CalendarSendNumPending(pending)); + } + + [WorldPacketHandler(ClientOpcodes.SetSavedInstanceExtend)] + void HandleSetSavedInstanceExtend(SetSavedInstanceExtend setSavedInstanceExtend) + { + Player player = GetPlayer(); + if (player) + { + InstanceBind instanceBind = player.GetBoundInstance((uint)setSavedInstanceExtend.MapID, (Difficulty)setSavedInstanceExtend.DifficultyID, setSavedInstanceExtend.Extend); // include expired instances if we are toggling extend on + if (instanceBind == null || instanceBind.save == null || !instanceBind.perm) + return; + + BindExtensionState newState; + if (!setSavedInstanceExtend.Extend || instanceBind.extendState == BindExtensionState.Expired) + newState = BindExtensionState.Normal; + else + newState = BindExtensionState.Extended; + + player.BindToInstance(instanceBind.save, true, newState, false); + } + /* + InstancePlayerBind* instanceBind = GetPlayer().GetBoundInstance(setSavedInstanceExtend.MapID, Difficulty(setSavedInstanceExtend.DifficultyID); + if (!instanceBind || !instanceBind.save) + return; + + InstanceSave* save = instanceBind.save; + // http://www.wowwiki.com/Instance_Lock_Extension + // SendCalendarRaidLockoutUpdated(save); + */ + } + + public void SendCalendarRaidLockout(InstanceSave save, bool add) + { + long currTime = Time.UnixTime; + + if (add) + { + CalendarRaidLockoutAdded calendarRaidLockoutAdded = new CalendarRaidLockoutAdded(); + calendarRaidLockoutAdded.InstanceID = save.GetInstanceId(); + calendarRaidLockoutAdded.ServerTime = (uint)currTime; + calendarRaidLockoutAdded.MapID = (int)save.GetMapId(); + calendarRaidLockoutAdded.DifficultyID = save.GetDifficultyID(); + calendarRaidLockoutAdded.TimeRemaining = (int)(save.GetResetTime() - currTime); + SendPacket(calendarRaidLockoutAdded); + } + else + { + CalendarRaidLockoutRemoved calendarRaidLockoutRemoved = new CalendarRaidLockoutRemoved(); + calendarRaidLockoutRemoved.InstanceID = save.GetInstanceId(); + calendarRaidLockoutRemoved.MapID = (int)save.GetMapId(); + calendarRaidLockoutRemoved.DifficultyID = save.GetDifficultyID(); + SendPacket(calendarRaidLockoutRemoved); + } + } + + public void SendCalendarRaidLockoutUpdated(InstanceSave save) + { + if (save == null) + return; + + ObjectGuid guid = GetPlayer().GetGUID(); + long currTime = Time.UnixTime; + + CalendarRaidLockoutUpdated packet = new CalendarRaidLockoutUpdated(); + packet.DifficultyID = (uint)save.GetDifficultyID(); + packet.MapID = (int)save.GetMapId(); + packet.NewTimeRemaining = 0; // FIXME + packet.OldTimeRemaining = (int)(save.GetResetTime() - currTime); + + SendPacket(packet); + } + } +} diff --git a/Game/Handlers/ChannelHandler.cs b/Game/Handlers/ChannelHandler.cs new file mode 100644 index 000000000..f46a7ff79 --- /dev/null +++ b/Game/Handlers/ChannelHandler.cs @@ -0,0 +1,200 @@ +/* + * Copyright (C) 2012-2017 CypherCore + * + * 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, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +using Framework.Constants; +using Game.Chat; +using Game.DataStorage; +using Game.Network; +using Game.Network.Packets; +using System; +using System.Collections.Generic; + +namespace Game +{ + public partial class WorldSession + { + [WorldPacketHandler(ClientOpcodes.ChatJoinChannel)] + void HandleJoinChannel(JoinChannel packet) + { + AreaTableRecord zone = CliDB.AreaTableStorage.LookupByKey(GetPlayer().GetZoneId()); + if (packet.ChatChannelId != 0) + { + ChatChannelsRecord channel = CliDB.ChatChannelsStorage.LookupByKey(packet.ChatChannelId); + if (channel == null) + return; + + if (zone == null || !GetPlayer().CanJoinConstantChannelInZone(channel, zone)) + return; + } + + if (string.IsNullOrEmpty(packet.ChannelName)) + return; + + if (packet.ChannelName.IsNumber()) + return; + + ChannelManager cMgr = ChannelManager.ForTeam(GetPlayer().GetTeam()); + if (cMgr != null) + { + Channel channel = cMgr.GetJoinChannel((uint)packet.ChatChannelId, packet.ChannelName, zone); + if (channel != null) + channel.JoinChannel(GetPlayer(), packet.Password); + } + } + + [WorldPacketHandler(ClientOpcodes.ChatLeaveChannel)] + void HandleLeaveChannel(LeaveChannel packet) + { + if (string.IsNullOrEmpty(packet.ChannelName) && packet.ZoneChannelID == 0) + return; + + AreaTableRecord zone = CliDB.AreaTableStorage.LookupByKey(GetPlayer().GetZoneId()); + if (packet.ZoneChannelID != 0) + { + ChatChannelsRecord channel = CliDB.ChatChannelsStorage.LookupByKey(packet.ZoneChannelID); + if (channel == null) + return; + + if (zone == null || !GetPlayer().CanJoinConstantChannelInZone(channel, zone)) + return; + } + + ChannelManager cMgr = ChannelManager.ForTeam(GetPlayer().GetTeam()); + if (cMgr != null) + { + Channel channel = cMgr.GetChannel((uint)packet.ZoneChannelID, packet.ChannelName, GetPlayer(), true, zone); + if (channel != null) + channel.LeaveChannel(GetPlayer(), true); + + if (packet.ZoneChannelID != 0) + cMgr.LeftChannel((uint)packet.ZoneChannelID, zone); + else + cMgr.LeftChannel(packet.ChannelName); + } + } + + [WorldPacketHandler(ClientOpcodes.ChatChannelAnnouncements)] + [WorldPacketHandler(ClientOpcodes.ChatChannelDeclineInvite)] + [WorldPacketHandler(ClientOpcodes.ChatChannelDisplayList)] + [WorldPacketHandler(ClientOpcodes.ChatChannelList)] + [WorldPacketHandler(ClientOpcodes.ChatChannelOwner)] + void HandleChannelCommand(ChannelCommand packet) + { + Channel channel = ChannelManager.GetChannelForPlayerByNamePart(packet.ChannelName, GetPlayer()); + if (channel == null) + return; + + switch (packet.GetOpcode()) + { + case ClientOpcodes.ChatChannelAnnouncements: + channel.Announce(GetPlayer()); + break; + case ClientOpcodes.ChatChannelDeclineInvite: + channel.DeclineInvite(GetPlayer()); + break; + case ClientOpcodes.ChatChannelDisplayList: + case ClientOpcodes.ChatChannelList: + channel.List(GetPlayer()); + break; + case ClientOpcodes.ChatChannelOwner: + channel.SendWhoOwner(GetPlayer()); + break; + } + } + + [WorldPacketHandler(ClientOpcodes.ChatChannelBan)] + [WorldPacketHandler(ClientOpcodes.ChatChannelInvite)] + [WorldPacketHandler(ClientOpcodes.ChatChannelKick)] + [WorldPacketHandler(ClientOpcodes.ChatChannelModerator)] + [WorldPacketHandler(ClientOpcodes.ChatChannelMute)] + [WorldPacketHandler(ClientOpcodes.ChatChannelSetOwner)] + [WorldPacketHandler(ClientOpcodes.ChatChannelSilenceAll)] + [WorldPacketHandler(ClientOpcodes.ChatChannelUnban)] + [WorldPacketHandler(ClientOpcodes.ChatChannelUnmoderator)] + [WorldPacketHandler(ClientOpcodes.ChatChannelUnmute)] + [WorldPacketHandler(ClientOpcodes.ChatChannelUnsilenceAll)] + void HandleChannelPlayerCommand(ChannelPlayerCommand packet) + { + if (packet.Name.Length >= 49) + { + Log.outDebug(LogFilter.ChatSystem, "{0} {1} ChannelName: {2}, Name: {3}, Name too long.", packet.GetOpcode(), GetPlayerInfo(), packet.ChannelName, packet.Name); + return; + } + + if (!ObjectManager.NormalizePlayerName(ref packet.Name)) + return; + + Channel channel = ChannelManager.GetChannelForPlayerByNamePart(packet.ChannelName, GetPlayer()); + if (channel == null) + return; + + switch (packet.GetOpcode()) + { + case ClientOpcodes.ChatChannelBan: + channel.Ban(GetPlayer(), packet.Name); + break; + case ClientOpcodes.ChatChannelInvite: + channel.Invite(GetPlayer(), packet.Name); + break; + case ClientOpcodes.ChatChannelKick: + channel.Kick(GetPlayer(), packet.Name); + break; + case ClientOpcodes.ChatChannelModerator: + channel.SetModerator(GetPlayer(), packet.Name); + break; + case ClientOpcodes.ChatChannelMute: + channel.SetMute(GetPlayer(), packet.Name); + break; + case ClientOpcodes.ChatChannelSetOwner: + channel.SetOwner(GetPlayer(), packet.Name); + break; + case ClientOpcodes.ChatChannelSilenceAll: + channel.SilenceAll(GetPlayer(), packet.Name); + break; + case ClientOpcodes.ChatChannelUnban: + channel.UnBan(GetPlayer(), packet.Name); + break; + case ClientOpcodes.ChatChannelUnmoderator: + channel.UnsetModerator(GetPlayer(), packet.Name); + break; + case ClientOpcodes.ChatChannelUnmute: + channel.UnsetMute(GetPlayer(), packet.Name); + break; + case ClientOpcodes.ChatChannelUnsilenceAll: + channel.UnsilenceAll(GetPlayer(), packet.Name); + break; + } + } + + [WorldPacketHandler(ClientOpcodes.ChatChannelPassword)] + void HandleChannelPassword(ChannelPassword packet) + { + if (packet.Password.Length > 31) + { + Log.outDebug(LogFilter.ChatSystem, "{0} {1} ChannelName: {2}, Password: {3}, Password too long.", + packet.GetOpcode(), GetPlayerInfo(), packet.ChannelName, packet.Password); + return; + } + + Log.outDebug(LogFilter.ChatSystem, "{0} {1} ChannelName: {2}, Password: {3}", packet.GetOpcode(), GetPlayerInfo(), packet.ChannelName, packet.Password); + + Channel channel = ChannelManager.GetChannelForPlayerByNamePart(packet.ChannelName, GetPlayer()); + if (channel != null) + channel.Password(GetPlayer(), packet.Password); + } + } +} diff --git a/Game/Handlers/CharacterHandler.cs b/Game/Handlers/CharacterHandler.cs new file mode 100644 index 000000000..84923057f --- /dev/null +++ b/Game/Handlers/CharacterHandler.cs @@ -0,0 +1,2675 @@ +/* + * Copyright (C) 2012-2017 CypherCore + * + * 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, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +using Framework.Collections; +using Framework.Constants; +using Framework.Database; +using Game.DataStorage; +using Game.Entities; +using Game.Groups; +using Game.Guilds; +using Game.Maps; +using Game.Network; +using Game.Network.Packets; +using System; +using System.Collections.Generic; + +namespace Game +{ + public partial class WorldSession + { + [WorldPacketHandler(ClientOpcodes.EnumCharacters, Status = SessionStatus.Authed)] + void HandleCharEnum(EnumCharacters charEnum) + { + // remove expired bans + PreparedStatement stmt = DB.Characters.GetPreparedStatement(CharStatements.DEL_EXPIRED_BANS); + DB.Characters.Execute(stmt); + + // get all the data necessary for loading all characters (along with their pets) on the account + if (WorldConfig.GetBoolValue(WorldCfg.DeclinedNamesUsed)) + stmt = DB.Characters.GetPreparedStatement(CharStatements.SEL_ENUM_DECLINED_NAME); + else + stmt = DB.Characters.GetPreparedStatement(CharStatements.SEL_ENUM); + + stmt.AddValue(0, PetSaveMode.AsCurrent); + stmt.AddValue(1, GetAccountId()); + + _queryProcessor.AddQuery(DB.Characters.AsyncQuery(stmt).WithCallback(HandleCharEnumCallback)); + } + + void HandleCharEnumCallback(SQLResult result) + { + byte demonHunterCount = 0; // We use this counter to allow multiple demon hunter creations when allowed in config + bool canAlwaysCreateDemonHunter = HasPermission(RBACPermissions.SkipCheckCharacterCreationDemonHunter); + if (WorldConfig.GetIntValue(WorldCfg.CharacterCreatingMinLevelForDemonHunter) == 0) // char level = 0 means this check is disabled, so always true + canAlwaysCreateDemonHunter = true; + + EnumCharactersResult charResult = new EnumCharactersResult(); + charResult.Success = true; + charResult.IsDeletedCharacters = false; + charResult.DisabledClassesMask.Set(WorldConfig.GetUIntValue(WorldCfg.CharacterCreatingDisabledClassmask)); + + _legitCharacters.Clear(); + if (!result.IsEmpty()) + { + do + { + EnumCharactersResult.CharacterInfo charInfo = new EnumCharactersResult.CharacterInfo(result.GetFields()); + + Log.outInfo(LogFilter.Network, "Loading Character {0} from account {1}.", charInfo.Guid.ToString(), GetAccountId()); + + if (!Player.ValidateAppearance((Race)charInfo.RaceId, charInfo.ClassId, (Gender)charInfo.Sex, charInfo.HairStyle, charInfo.HairColor, charInfo.Face, charInfo.FacialHair, charInfo.Skin, charInfo.CustomDisplay)) + { + Log.outError(LogFilter.Player, "Player {0} has wrong Appearance values (Hair/Skin/Color), forcing recustomize", charInfo.Guid.ToString()); + + // Make sure customization always works properly - send all zeroes instead + charInfo.Skin = 0; + charInfo.Face = 0; + charInfo.HairStyle = 0; + charInfo.HairColor = 0; + charInfo.FacialHair = 0; + + if (!(charInfo.CustomizationFlag == CharacterCustomizeFlags.Customize)) + { + PreparedStatement stmt = DB.Characters.GetPreparedStatement(CharStatements.UPD_ADD_AT_LOGIN_FLAG); + stmt.AddValue(0, (ushort)AtLoginFlags.Customize); + stmt.AddValue(1, charInfo.Guid.GetCounter()); + DB.Characters.Execute(stmt); + charInfo.CustomizationFlag = CharacterCustomizeFlags.Customize; + } + } + + // Do not allow locked characters to login + if (!charInfo.Flags.HasAnyFlag(CharacterFlags.CharacterLockedForTransfer | CharacterFlags.LockedByBilling)) + _legitCharacters.Add(charInfo.Guid); + + if (!Global.WorldMgr.HasCharacterInfo(charInfo.Guid)) // This can happen if characters are inserted into the database manually. Core hasn't loaded name data yet. + Global.WorldMgr.AddCharacterInfo(charInfo.Guid, GetAccountId(), charInfo.Name, charInfo.Sex, charInfo.RaceId, (byte)charInfo.ClassId, charInfo.Level, false); + + if (charInfo.ClassId == Class.DemonHunter) + demonHunterCount++; + + if (demonHunterCount >= WorldConfig.GetIntValue(WorldCfg.DemonHuntersPerRealm) && !canAlwaysCreateDemonHunter) + charResult.HasDemonHunterOnRealm = true; + else + charResult.HasDemonHunterOnRealm = false; + + if (charInfo.Level >= WorldConfig.GetIntValue(WorldCfg.CharacterCreatingMinLevelForDemonHunter) || canAlwaysCreateDemonHunter) + charResult.HasLevel70OnRealm = true; + + charResult.Characters.Add(charInfo); + } + while (result.NextRow()); + } + + charResult.IsDemonHunterCreationAllowed = (!charResult.HasDemonHunterOnRealm && charResult.HasLevel70OnRealm) || canAlwaysCreateDemonHunter; + + SendPacket(charResult); + } + + [WorldPacketHandler(ClientOpcodes.EnumCharactersDeletedByClient, Status = SessionStatus.Authed)] + void HandleCharUndeleteEnum(EnumCharacters enumCharacters) + { + /// get all the data necessary for loading all undeleted characters (along with their pets) on the account + PreparedStatement stmt; + if (WorldConfig.GetBoolValue(WorldCfg.DeclinedNamesUsed)) + stmt = DB.Characters.GetPreparedStatement(CharStatements.SEL_UNDELETE_ENUM_DECLINED_NAME); + else + stmt = DB.Characters.GetPreparedStatement(CharStatements.SEL_UNDELETE_ENUM); + + stmt.AddValue(0, (uint)PetSaveMode.AsCurrent); + stmt.AddValue(1, GetAccountId()); + + _queryProcessor.AddQuery(DB.Characters.AsyncQuery(stmt).WithCallback(HandleCharUndeleteEnumCallback)); + } + + void HandleCharUndeleteEnumCallback(SQLResult result) + { + EnumCharactersResult charEnum = new EnumCharactersResult(); + charEnum.Success = true; + charEnum.IsDeletedCharacters = true; + charEnum.DisabledClassesMask.Set(WorldConfig.GetUIntValue(WorldCfg.CharacterCreatingDisabledClassmask)); + + if (!result.IsEmpty()) + { + do + { + EnumCharactersResult.CharacterInfo charInfo = new EnumCharactersResult.CharacterInfo(result.GetFields()); + + Log.outInfo(LogFilter.Network, "Loading undeleted char guid {0} from account {1}.", charInfo.Guid.ToString(), GetAccountId()); + + if (!Global.WorldMgr.HasCharacterInfo(charInfo.Guid)) // This can happen if characters are inserted into the database manually. Core hasn't loaded name data yet. + Global.WorldMgr.AddCharacterInfo(charInfo.Guid, GetAccountId(), charInfo.Name, charInfo.Sex, charInfo.RaceId, (byte)charInfo.ClassId, charInfo.Level, true); + + charEnum.Characters.Add(charInfo); + } + while (result.NextRow()); + } + + SendPacket(charEnum); + } + + [WorldPacketHandler(ClientOpcodes.CreateCharacter, Status = SessionStatus.Authed)] + void HandleCharCreate(CreateCharacter charCreate) + { + if (!HasPermission(RBACPermissions.SkipCheckCharacterCreationTeammask)) + { + int mask = WorldConfig.GetIntValue(WorldCfg.CharacterCreatingDisabled); + if (mask != 0) + { + bool disabled = false; + + uint team = Player.TeamIdForRace(charCreate.CreateInfo.RaceId); + switch (team) + { + case TeamId.Alliance: + disabled = Convert.ToBoolean(mask & (1 << 0)); + break; + case TeamId.Horde: + disabled = Convert.ToBoolean(mask & (1 << 1)); + break; + case TeamId.Neutral: + disabled = Convert.ToBoolean(mask & (1 << 2)); + break; + } + + if (disabled) + { + SendCharCreate(ResponseCodes.CharCreateDisabled); + return; + } + } + } + + ChrClassesRecord classEntry = CliDB.ChrClassesStorage.LookupByKey(charCreate.CreateInfo.ClassId); + if (classEntry == null) + { + Log.outError(LogFilter.Network, "Class ({0}) not found in DBC while creating new char for account (ID: {1}): wrong DBC files or cheater?", charCreate.CreateInfo.ClassId, GetAccountId()); + SendCharCreate(ResponseCodes.CharCreateFailed); + return; + } + + ChrRacesRecord raceEntry = CliDB.ChrRacesStorage.LookupByKey(charCreate.CreateInfo.RaceId); + if (raceEntry == null) + { + Log.outError(LogFilter.Network, "Race ({0}) not found in DBC while creating new char for account (ID: {1}): wrong DBC files or cheater?", charCreate.CreateInfo.RaceId, GetAccountId()); + SendCharCreate(ResponseCodes.CharCreateFailed); + return; + } + + // prevent character creating Expansion race without Expansion account + var raceExpansionRequirement = Global.ObjectMgr.GetRaceExpansionRequirement(charCreate.CreateInfo.RaceId); + if (raceExpansionRequirement > GetExpansion()) + { + Log.outError(LogFilter.Network, "Expansion {0} account:[{1}] tried to Create character with expansion {2} race ({3})", GetExpansion(), GetAccountId(), raceExpansionRequirement, charCreate.CreateInfo.RaceId); + SendCharCreate(ResponseCodes.CharCreateExpansion); + return; + } + + // prevent character creating Expansion class without Expansion account + var classExpansionRequirement = Global.ObjectMgr.GetClassExpansionRequirement(charCreate.CreateInfo.ClassId); + if (classExpansionRequirement > GetExpansion()) + { + Log.outError(LogFilter.Network, "Expansion {0} account:[{1}] tried to Create character with expansion {2} class ({3})", GetExpansion(), GetAccountId(), classExpansionRequirement, charCreate.CreateInfo.ClassId); + SendCharCreate(ResponseCodes.CharCreateExpansionClass); + return; + } + + if (!HasPermission(RBACPermissions.SkipCheckCharacterCreationRacemask)) + { + int raceMaskDisabled = WorldConfig.GetIntValue(WorldCfg.CharacterCreatingDisabledRacemask); + if (Convert.ToBoolean((1 << ((int)charCreate.CreateInfo.RaceId - 1)) & raceMaskDisabled)) + { + SendCharCreate(ResponseCodes.CharCreateDisabled); + return; + } + } + + if (!HasPermission(RBACPermissions.SkipCheckCharacterCreationClassmask)) + { + int classMaskDisabled = WorldConfig.GetIntValue(WorldCfg.CharacterCreatingDisabledClassmask); + if (Convert.ToBoolean((1 << ((int)charCreate.CreateInfo.ClassId - 1)) & classMaskDisabled)) + { + SendCharCreate(ResponseCodes.CharCreateDisabled); + return; + } + } + + // prevent character creating with invalid name + if (!ObjectManager.NormalizePlayerName(ref charCreate.CreateInfo.Name)) + { + Log.outError(LogFilter.Network, "Account:[{0}] but tried to Create character with empty [name] ", GetAccountId()); + SendCharCreate(ResponseCodes.CharNameNoName); + return; + } + + // check name limitations + ResponseCodes res = ObjectManager.CheckPlayerName(charCreate.CreateInfo.Name, GetSessionDbcLocale(), true); + if (res != ResponseCodes.CharNameSuccess) + { + SendCharCreate(res); + return; + } + + if (!HasPermission(RBACPermissions.SkipCheckCharacterCreationReservedname) && Global.ObjectMgr.IsReservedName(charCreate.CreateInfo.Name)) + { + SendCharCreate(ResponseCodes.CharNameReserved); + return; + } + + if (charCreate.CreateInfo.ClassId == Class.Deathknight && !HasPermission(RBACPermissions.SkipCheckCharacterCreationDeathKnight)) + { + // speedup check for death knight class disabled case + if (WorldConfig.GetIntValue(WorldCfg.DeathKnightsPerRealm) == 0) + { + SendCharCreate(ResponseCodes.CharCreateUniqueClassLimit); + return; + } + + // speedup check for death knight class disabled case + if (WorldConfig.GetIntValue(WorldCfg.CharacterCreatingMinLevelForDeathKnight) > WorldConfig.GetIntValue(WorldCfg.MaxPlayerLevel)) + { + SendCharCreate(ResponseCodes.CharCreateLevelRequirement); + return; + } + } + + CharacterCreateInfo createInfo = charCreate.CreateInfo; + PreparedStatement stmt = DB.Characters.GetPreparedStatement(CharStatements.SEL_CHECK_NAME); + stmt.AddValue(0, charCreate.CreateInfo.Name); + + _queryProcessor.AddQuery(DB.Characters.AsyncQuery(stmt).WithChainingCallback((queryCallback, result) => + { + if (!result.IsEmpty()) + { + SendCharCreate(ResponseCodes.CharCreateNameInUse); + return; + } + + stmt = DB.Login.GetPreparedStatement(LoginStatements.SEL_SUM_REALM_CHARACTERS); + stmt.AddValue(0, GetAccountId()); + queryCallback.SetNextQuery(DB.Login.AsyncQuery(stmt)); + + }).WithChainingCallback((queryCallback, result) => + { + ulong acctCharCount = 0; + if (!result.IsEmpty()) + acctCharCount = result.Read(0); + + if (acctCharCount >= WorldConfig.GetUIntValue(WorldCfg.CharactersPerAccount)) + { + SendCharCreate(ResponseCodes.CharCreateAccountLimit); + return; + } + + stmt = DB.Characters.GetPreparedStatement(CharStatements.SEL_SUM_CHARS); + stmt.AddValue(0, GetAccountId()); + queryCallback.SetNextQuery(DB.Characters.AsyncQuery(stmt)); + }).WithChainingCallback((queryCallback, result) => + { + if (!result.IsEmpty()) + { + createInfo.CharCount = (byte)result.Read(0); // SQL's COUNT() returns uint64 but it will always be less than uint8.Max + + if (createInfo.CharCount >= WorldConfig.GetIntValue(WorldCfg.CharactersPerRealm)) + { + SendCharCreate(ResponseCodes.CharCreateServerLimit); + return; + } + } + + bool allowTwoSideAccounts = !Global.WorldMgr.IsPvPRealm() || HasPermission(RBACPermissions.TwoSideCharacterCreation); + int skipCinematics = WorldConfig.GetIntValue(WorldCfg.SkipCinematics); + + Action finalizeCharacterCreation = result1 => + { + bool haveSameRace = false; + int deathKnightReqLevel = WorldConfig.GetIntValue(WorldCfg.CharacterCreatingMinLevelForDeathKnight); + int demonHunterReqLevel = WorldConfig.GetIntValue(WorldCfg.CharacterCreatingMinLevelForDemonHunter); + bool hasDeathKnightReqLevel = (deathKnightReqLevel == 0); + bool hasDemonHunterReqLevel = (demonHunterReqLevel == 0); + bool checkDeathKnightReqs = createInfo.ClassId == Class.Deathknight && !HasPermission(RBACPermissions.SkipCheckCharacterCreationDeathKnight); + bool checkDemonHunterReqs = createInfo.ClassId == Class.DemonHunter && !HasPermission(RBACPermissions.SkipCheckCharacterCreationDemonHunter); + + if (result1 != null && !result1.IsEmpty()) + { + Team team = Player.TeamForRace(createInfo.RaceId); + int freeDeathKnightSlots = WorldConfig.GetIntValue(WorldCfg.DeathKnightsPerRealm); + int freeDemonHunterSlots = WorldConfig.GetIntValue(WorldCfg.DemonHuntersPerRealm); + + byte accRace = result1.Read(1); + + if (checkDeathKnightReqs) + { + byte accClass = result1.Read(2); + if (accClass == (byte)Class.Deathknight) + { + if (freeDeathKnightSlots > 0) + --freeDeathKnightSlots; + + if (freeDeathKnightSlots == 0) + { + SendCharCreate(ResponseCodes.CharCreateUniqueClassLimit); + return; + } + } + + if (!hasDeathKnightReqLevel) + { + byte accLevel = result1.Read(0); + if (accLevel >= deathKnightReqLevel) + hasDeathKnightReqLevel = true; + } + } + + if (checkDemonHunterReqs) + { + byte accClass = result1.Read(2); + if (accClass == (byte)Class.DemonHunter) + { + if (freeDemonHunterSlots > 0) + --freeDemonHunterSlots; + + if (freeDemonHunterSlots == 0) + { + SendCharCreate(ResponseCodes.CharCreateFailed); + return; + } + } + + if (!hasDemonHunterReqLevel) + { + byte accLevel = result1.Read(0); + if (accLevel >= demonHunterReqLevel) + hasDemonHunterReqLevel = true; + } + } + + // need to check team only for first character + /// @todo what to if account already has characters of both races? + if (!allowTwoSideAccounts) + { + Team accTeam = 0; + if (accRace > 0) + accTeam = Player.TeamForRace((Race)accRace); + + if (accTeam != team) + { + SendCharCreate(ResponseCodes.CharCreatePvpTeamsViolation); + return; + } + } + + // search same race for cinematic or same class if need + // @todo check if cinematic already shown? (already logged in?; cinematic field) + while ((skipCinematics == 1 && !haveSameRace) || createInfo.ClassId == Class.Deathknight || createInfo.ClassId == Class.DemonHunter) + { + if (!result1.NextRow()) + break; + + accRace = result1.Read(1); + + if (!haveSameRace) + haveSameRace = createInfo.RaceId == (Race)accRace; + + if (checkDeathKnightReqs) + { + byte acc_class = result1.Read(2); + if (acc_class == (byte)Class.Deathknight) + { + if (freeDeathKnightSlots > 0) + --freeDeathKnightSlots; + + if (freeDeathKnightSlots == 0) + { + SendCharCreate(ResponseCodes.CharCreateUniqueClassLimit); + return; + } + } + + if (!hasDeathKnightReqLevel) + { + byte acc_level = result1.Read(0); + if (acc_level >= deathKnightReqLevel) + hasDeathKnightReqLevel = true; + } + } + + if (checkDemonHunterReqs) + { + byte acc_class = result1.Read(2); + if (acc_class == (byte)Class.DemonHunter) + { + if (freeDemonHunterSlots > 0) + --freeDemonHunterSlots; + + if (freeDemonHunterSlots == 0) + { + SendCharCreate(ResponseCodes.CharCreateFailed); + return; + } + } + + if (!hasDemonHunterReqLevel) + { + byte acc_level = result1.Read(0); + if (acc_level >= demonHunterReqLevel) + hasDemonHunterReqLevel = true; + } + } + } + } + + if (checkDeathKnightReqs && !hasDeathKnightReqLevel) + { + SendCharCreate(ResponseCodes.CharCreateLevelRequirement); + return; + } + + if (checkDemonHunterReqs && !hasDemonHunterReqLevel) + { + SendCharCreate(ResponseCodes.CharCreateLevelRequirement); + return; + } + + Player newChar = new Player(this); + newChar.GetMotionMaster().Initialize(); + if (!newChar.Create(Global.ObjectMgr.GetGenerator(HighGuid.Player).Generate(), createInfo)) + { + // Player not create (race/class/etc problem?) + newChar.CleanupsBeforeDelete(); + + SendCharCreate(ResponseCodes.CharCreateError); + return; + } + + if ((haveSameRace && skipCinematics == 1) || skipCinematics == 2) + newChar.setCinematic(1); // not show intro + + newChar.atLoginFlags = AtLoginFlags.FirstLogin; // First login + + // Player created, save it now + newChar.SaveToDB(true); + createInfo.CharCount += 1; + + SQLTransaction trans = new SQLTransaction(); + + stmt = DB.Login.GetPreparedStatement(LoginStatements.DEL_REALM_CHARACTERS_BY_REALM); + stmt.AddValue(0, GetAccountId()); + stmt.AddValue(1, Global.WorldMgr.GetRealm().Id.Realm); + trans.Append(stmt); + + stmt = DB.Login.GetPreparedStatement(LoginStatements.INS_REALM_CHARACTERS); + stmt.AddValue(0, createInfo.CharCount); + stmt.AddValue(1, GetAccountId()); + stmt.AddValue(2, Global.WorldMgr.GetRealm().Id.Realm); + trans.Append(stmt); + + DB.Login.CommitTransaction(trans); + + // Success + SendCharCreate(ResponseCodes.CharCreateSuccess); + + Log.outInfo(LogFilter.Player, "Account: {0} (IP: {1}) Create Character: {2} {3}", GetAccountId(), GetRemoteAddress(), createInfo.Name, newChar.GetGUID().ToString()); + Global.ScriptMgr.OnPlayerCreate(newChar); + Global.WorldMgr.AddCharacterInfo(newChar.GetGUID(), GetAccountId(), newChar.GetName(), newChar.GetByteValue(PlayerFields.Bytes3, PlayerFieldOffsets.Bytes3OffsetGender), (byte)newChar.GetRace(), (byte)newChar.GetClass(), (byte)newChar.getLevel(), false); + + newChar.CleanupsBeforeDelete(); + }; + + if (!allowTwoSideAccounts || skipCinematics == 1 || createInfo.ClassId == Class.Deathknight || createInfo.ClassId == Class.DemonHunter) + { + finalizeCharacterCreation(new SQLResult()); + return; + } + + stmt = DB.Characters.GetPreparedStatement(CharStatements.SEL_CHAR_CREATE_INFO); + stmt.AddValue(0, GetAccountId()); + stmt.AddValue(1, (skipCinematics == 1 || createInfo.ClassId == Class.Deathknight || createInfo.ClassId == Class.DemonHunter) ? 12 : 1); + queryCallback.WithCallback(finalizeCharacterCreation).SetNextQuery(DB.Characters.AsyncQuery(stmt)); + })); + } + + [WorldPacketHandler(ClientOpcodes.CharDelete, Status = SessionStatus.Authed)] + void HandleCharDelete(CharDelete charDelete) + { + // can't delete loaded character + if (Global.ObjAccessor.FindPlayer(charDelete.Guid)) + return; + + // is guild leader + if (Global.GuildMgr.GetGuildByLeader(charDelete.Guid)) + { + SendCharDelete(ResponseCodes.CharDeleteFailedGuildLeader); + return; + } + + // is arena team captain + if (Global.ArenaTeamMgr.GetArenaTeamByCaptain(charDelete.Guid) != null) + { + SendCharDelete(ResponseCodes.CharDeleteFailedArenaCaptain); + return; + } + + CharacterInfo characterInfo = Global.WorldMgr.GetCharacterInfo(charDelete.Guid); + if (characterInfo == null) + { + //Global.ScriptMgr.OnPlayerFailedDelete(charDelete.Guid, initAccountId); + return; + } + + uint accountId = characterInfo.AccountId; + string name = characterInfo.Name; + byte level = characterInfo.Level; + + // prevent deleting other players' characters using cheating tools + if (accountId != GetAccountId()) + return; + + string IP_str = GetRemoteAddress(); + Log.outInfo(LogFilter.Player, "Account: {0}, IP: {1} deleted character: {2}, {3}, Level: {4}", accountId, IP_str, name, charDelete.Guid.ToString(), level); + Global.ScriptMgr.OnPlayerDelete(charDelete.Guid); + + Global.GuildFinderMgr.RemoveAllMembershipRequestsFromPlayer(charDelete.Guid); + Global.CalendarMgr.RemoveAllPlayerEventsAndInvites(charDelete.Guid); + Player.DeleteFromDB(charDelete.Guid, accountId); + + SendCharDelete(ResponseCodes.CharDeleteSuccess); + } + + [WorldPacketHandler(ClientOpcodes.GenerateRandomCharacterName, Status = SessionStatus.Authed)] + void HandleRandomizeCharName(GenerateRandomCharacterName packet) + { + if (!Player.IsValidRace((Race)packet.Race)) + { + Log.outError(LogFilter.Network, "Invalid race ({0}) sent by accountId: {1}", packet.Race, GetAccountId()); + return; + } + + if (!Player.IsValidGender((Gender)packet.Sex)) + { + Log.outError(LogFilter.Network, "Invalid gender ({0}) sent by accountId: {1}", packet.Sex, GetAccountId()); + return; + } + + GenerateRandomCharacterNameResult result = new GenerateRandomCharacterNameResult(); + result.Success = true; + result.Name = Global.DB2Mgr.GetNameGenEntry(packet.Race, packet.Sex, GetSessionDbcLocale(), Global.WorldMgr.GetDefaultDbcLocale()); + + SendPacket(result); + } + + [WorldPacketHandler(ClientOpcodes.ReorderCharacters, Status = SessionStatus.Authed)] + void HandleReorderCharacters(ReorderCharacters reorderChars) + { + SQLTransaction trans = new SQLTransaction(); + + foreach (var reorderInfo in reorderChars.Entries) + { + PreparedStatement stmt = DB.Characters.GetPreparedStatement(CharStatements.UPD_CHAR_LIST_SLOT); + stmt.AddValue(0, reorderInfo.NewPosition); + stmt.AddValue(1, reorderInfo.PlayerGUID.GetCounter()); + stmt.AddValue(2, GetAccountId()); + trans.Append(stmt); + } + + DB.Characters.CommitTransaction(trans); + } + + [WorldPacketHandler(ClientOpcodes.PlayerLogin, Status = SessionStatus.Authed)] + void HandlePlayerLogin(PlayerLogin playerLogin) + { + if (PlayerLoading() || GetPlayer() != null) + { + Log.outError(LogFilter.Network, "Player tries to login again, AccountId = {0}", GetAccountId()); + return; + } + + m_playerLoading = playerLogin.Guid; + Log.outDebug(LogFilter.Network, "Character {0} logging in", playerLogin.Guid.ToString()); + + if (!_legitCharacters.Contains(playerLogin.Guid)) + { + Log.outError(LogFilter.Network, "Account ({0}) can't login with that character ({1}).", GetAccountId(), playerLogin.Guid.ToString()); + KickPlayer(); + return; + } + + SendConnectToInstance(ConnectToSerial.WorldAttempt1); + } + + public void HandleContinuePlayerLogin() + { + if (!PlayerLoading() || GetPlayer()) + { + KickPlayer(); + return; + } + + LoginQueryHolder holder = new LoginQueryHolder(GetAccountId(), m_playerLoading); + holder.Initialize(); + + SendPacket(new ResumeComms(ConnectionType.Instance)); + + _charLoginCallback = DB.Characters.DelayQueryHolder(holder); + } + + public void HandlePlayerLogin(LoginQueryHolder holder) + { + ObjectGuid playerGuid = holder.GetGuid(); + + Player pCurrChar = new Player(this); + if (!pCurrChar.LoadFromDB(playerGuid, holder)) + { + SetPlayer(null); + KickPlayer(); + m_playerLoading.Clear(); + return; + } + + pCurrChar.SetUInt32Value(PlayerFields.VirtualRealm, Global.WorldMgr.GetVirtualRealmAddress()); + + SendTutorialsData(); + + pCurrChar.GetMotionMaster().Initialize(); + pCurrChar.SendDungeonDifficulty(); + + LoginVerifyWorld loginVerifyWorld = new LoginVerifyWorld(); + loginVerifyWorld.MapID = (int)pCurrChar.GetMapId(); + loginVerifyWorld.Pos = pCurrChar.GetPosition(); + SendPacket(loginVerifyWorld); + + LoadAccountData(holder.GetResult(PlayerLoginQueryLoad.AccountData), AccountDataTypes.PerCharacterCacheMask); + + AccountDataTimes accountDataTimes = new AccountDataTimes(); + accountDataTimes.PlayerGuid = playerGuid; + accountDataTimes.ServerTime = (uint)Global.WorldMgr.GetGameTime(); + for (AccountDataTypes i = 0; i < AccountDataTypes.Max; ++i) + accountDataTimes.AccountTimes[(int)i] = (uint)GetAccountData(i).Time; + + SendPacket(accountDataTimes); + + SendFeatureSystemStatus(); + + MOTD motd = new MOTD(); + motd.Text = Global.WorldMgr.GetMotd(); + SendPacket(motd); + + SendSetTimeZoneInformation(); + + // Send PVPSeason + { + PVPSeason season = new PVPSeason(); + season.PreviousSeason = (WorldConfig.GetUIntValue(WorldCfg.ArenaSeasonId) - (WorldConfig.GetBoolValue(WorldCfg.ArenaSeasonInProgress) ? 1u : 0u)); + + if (WorldConfig.GetBoolValue(WorldCfg.ArenaSeasonInProgress)) + season.CurrentSeason = WorldConfig.GetUIntValue(WorldCfg.ArenaSeasonId); + + SendPacket(season); + } + + SQLResult resultGuild = holder.GetResult(PlayerLoginQueryLoad.Guild); + if (!resultGuild.IsEmpty()) + { + pCurrChar.SetInGuild(resultGuild.Read(0)); + pCurrChar.SetRank(resultGuild.Read(1)); + Guild guild = Global.GuildMgr.GetGuildById(pCurrChar.GetGuildId()); + if (guild) + pCurrChar.SetGuildLevel(guild.GetLevel()); + } + else if (pCurrChar.GetGuildId() != 0) + { + pCurrChar.SetInGuild(0); + pCurrChar.SetRank(0); + pCurrChar.SetGuildLevel(0); + } + + //WorldPacket data = new WorldPacket(ServerOpcodes.LearnedDanceMoves); + //data.WriteUInt64(0); + //SendPacket(data); + + // TODO: Move this to BattlePetMgr::SendJournalLock() just to have all packets in one file + SendPacket(new BattlePetJournalLockAcquired()); + + pCurrChar.SendInitialPacketsBeforeAddToMap(); + + //Show cinematic at the first time that player login + if (pCurrChar.getCinematic() == 0) + { + pCurrChar.setCinematic(1); + ChrClassesRecord cEntry = CliDB.ChrClassesStorage.LookupByKey(pCurrChar.GetClass()); + if (cEntry != null) + { + ChrRacesRecord rEntry = CliDB.ChrRacesStorage.LookupByKey(pCurrChar.GetRace()); + if (pCurrChar.GetClass() == Class.DemonHunter) // @todo: find a more generic solution + pCurrChar.SendMovieStart(469); + else if (cEntry.CinematicSequenceID != 0) + pCurrChar.SendCinematicStart(cEntry.CinematicSequenceID); + else if (rEntry != null) + pCurrChar.SendCinematicStart(rEntry.CinematicSequenceID); + } + } + + if (!pCurrChar.GetMap().AddPlayerToMap(pCurrChar)) + { + var at = Global.ObjectMgr.GetGoBackTrigger(pCurrChar.GetMapId()); + if (at != null) + pCurrChar.TeleportTo(at.target_mapId, at.target_X, at.target_Y, at.target_Z, pCurrChar.Orientation); + else + pCurrChar.TeleportTo(pCurrChar.GetHomebind()); + } + Global.ObjAccessor.AddObject(pCurrChar); + + if (pCurrChar.GetGuildId() != 0) + { + Guild guild = Global.GuildMgr.GetGuildById(pCurrChar.GetGuildId()); + if (guild) + guild.SendLoginInfo(this); + else + { + // remove wrong guild data + Log.outError(LogFilter.Server, "Player {0} ({1}) marked as member of not existing guild (id: {2}), removing guild membership for player.", pCurrChar.GetName(), pCurrChar.GetGUID().ToString(), + pCurrChar.GetGuildId()); + pCurrChar.SetInGuild(0); + } + } + pCurrChar.SendInitialPacketsAfterAddToMap(); + + PreparedStatement stmt = DB.Characters.GetPreparedStatement(CharStatements.UPD_CHAR_ONLINE); + stmt.AddValue(0, pCurrChar.GetGUID().GetCounter()); + DB.Characters.Execute(stmt); + + stmt = DB.Login.GetPreparedStatement(LoginStatements.UPD_ACCOUNT_ONLINE); + stmt.AddValue(0, GetAccountId()); + DB.Login.Execute(stmt); + + pCurrChar.SetInGameTime(Time.GetMSTime()); + + // announce group about member online (must be after add to player list to receive announce to self) + Group group = pCurrChar.GetGroup(); + if (group) + { + group.SendUpdate(); + group.ResetMaxEnchantingLevel(); + } + + // friend status + Global.SocialMgr.SendFriendStatus(pCurrChar, FriendsResult.Online, pCurrChar.GetGUID(), true); + + // Place character in world (and load zone) before some object loading + pCurrChar.LoadCorpse(holder.GetResult(PlayerLoginQueryLoad.CorpseLocation)); + + // setting Ghost+speed if dead + if (pCurrChar.getDeathState() != DeathState.Alive) + { + // not blizz like, we must correctly save and load player instead... + if (pCurrChar.GetRace() == Race.NightElf && !pCurrChar.HasAura(20584)) + pCurrChar.CastSpell(pCurrChar, 20584, true);// auras SPELL_AURA_INCREASE_SPEED(+speed in wisp form), SPELL_AURA_INCREASE_SWIM_SPEED(+swim speed in wisp form), SPELL_AURA_TRANSFORM (to wisp form) + + if (!pCurrChar.HasAura(8326)) + pCurrChar.CastSpell(pCurrChar, 8326, true, null); // auras SPELL_AURA_GHOST, SPELL_AURA_INCREASE_SPEED(why?), SPELL_AURA_INCREASE_SWIM_SPEED(why?) + + pCurrChar.SetWaterWalking(true); + } + + pCurrChar.ContinueTaxiFlight(); + + // reset for all pets before pet loading + if (pCurrChar.HasAtLoginFlag(AtLoginFlags.ResetPetTalents)) + { + // Delete all of the player's pet spells + PreparedStatement stmtSpells = DB.Characters.GetPreparedStatement(CharStatements.DEL_ALL_PET_SPELLS_BY_OWNER); + stmtSpells.AddValue(0, pCurrChar.GetGUID().GetCounter()); + DB.Characters.Execute(stmtSpells); + + // Then reset all of the player's pet specualizations + PreparedStatement stmtSpec = DB.Characters.GetPreparedStatement(CharStatements.UPD_PET_SPECS_BY_OWNER); + stmtSpec.AddValue(0, pCurrChar.GetGUID().GetCounter()); + DB.Characters.Execute(stmtSpec); + } + + // Load pet if any (if player not alive and in taxi flight or another then pet will remember as temporary unsummoned) + pCurrChar.LoadPet(); + + // Set FFA PvP for non GM in non-rest mode + if (Global.WorldMgr.IsFFAPvPRealm() && !pCurrChar.IsGameMaster() && !pCurrChar.HasFlag(PlayerFields.Flags, PlayerFlags.Resting)) + pCurrChar.SetByteFlag(UnitFields.Bytes2, UnitBytes2Offsets.PvpFlag, UnitBytes2Flags.FFAPvp); + + if (pCurrChar.HasFlag(PlayerFields.Flags, PlayerFlags.ContestedPVP)) + pCurrChar.SetContestedPvP(); + + // Apply at_login requests + if (pCurrChar.HasAtLoginFlag(AtLoginFlags.ResetSpells)) + { + pCurrChar.ResetSpells(); + SendNotification(CypherStrings.ResetSpells); + } + + if (pCurrChar.HasAtLoginFlag(AtLoginFlags.ResetTalents)) + { + pCurrChar.ResetTalents(true); + pCurrChar.ResetTalentSpecialization(); + pCurrChar.SendTalentsInfoData(); // original talents send already in to SendInitialPacketsBeforeAddToMap, resend reset state + SendNotification(CypherStrings.ResetTalents); + } + + if (pCurrChar.HasAtLoginFlag(AtLoginFlags.FirstLogin)) + { + pCurrChar.RemoveAtLoginFlag(AtLoginFlags.FirstLogin); + + PlayerInfo info = Global.ObjectMgr.GetPlayerInfo(pCurrChar.GetRace(), pCurrChar.GetClass()); + foreach (var spellId in info.castSpells) + pCurrChar.CastSpell(pCurrChar, spellId, true); + } + + // show time before shutdown if shutdown planned. + if (Global.WorldMgr.IsShuttingDown()) + Global.WorldMgr.ShutdownMsg(true, pCurrChar); + + if (WorldConfig.GetBoolValue(WorldCfg.AllTaxiPaths)) + pCurrChar.SetTaxiCheater(true); + + if (pCurrChar.IsGameMaster()) + SendNotification(CypherStrings.GmOn); + + string IP_str = GetRemoteAddress(); + Log.outDebug(LogFilter.Network, "Account: {0} (IP: {1}) Login Character:[{2}] ({3}) Level: {4}", + GetAccountId(), IP_str, pCurrChar.GetName(), pCurrChar.GetGUID().ToString(), pCurrChar.getLevel()); + + if (!pCurrChar.IsStandState() && !pCurrChar.HasUnitState(UnitState.Stunned)) + pCurrChar.SetStandState(UnitStandStateType.Stand); + + m_playerLoading.Clear(); + + Global.ScriptMgr.OnPlayerLogin(pCurrChar); + } + + public void AbortLogin(LoginFailureReason reason) + { + if (!PlayerLoading() || GetPlayer()) + { + KickPlayer(); + return; + } + + m_playerLoading.Clear(); + SendPacket(new CharacterLoginFailed(reason)); + } + + [WorldPacketHandler(ClientOpcodes.LoadingScreenNotify, Status = SessionStatus.Authed)] + void HandleLoadScreen(LoadingScreenNotify loadingScreenNotify) + { + // TODO: Do something with this packet + } + + public void SendFeatureSystemStatus() + { + FeatureSystemStatus features = new FeatureSystemStatus(); + + /// START OF DUMMY VALUES + features.ComplaintStatus = 2; + features.ScrollOfResurrectionRequestsRemaining = 1; + features.ScrollOfResurrectionMaxRequestsPerDay = 1; + features.TwitterPostThrottleLimit = 60; + features.TwitterPostThrottleCooldown = 20; + features.CfgRealmID = 2; + features.CfgRealmRecID = 0; + features.TokenPollTimeSeconds = 300; + features.TokenRedeemIndex = 0; + features.VoiceEnabled = false; + features.BrowserEnabled = false; // Has to be false, otherwise client will crash if "Customer Support" is opened + + features.EuropaTicketSystemStatus.HasValue = true; + features.EuropaTicketSystemStatus.Value.ThrottleState.MaxTries = 10; + features.EuropaTicketSystemStatus.Value.ThrottleState.PerMilliseconds = 60000; + features.EuropaTicketSystemStatus.Value.ThrottleState.TryCount = 1; + features.EuropaTicketSystemStatus.Value.ThrottleState.LastResetTimeBeforeNow = 111111; + features.ComplaintStatus = 0; + features.TutorialsEnabled = true; + features.NPETutorialsEnabled = true; + /// END OF DUMMY VALUES + + features.EuropaTicketSystemStatus.Value.TicketsEnabled = WorldConfig.GetBoolValue(WorldCfg.SupportTicketsEnabled); + features.EuropaTicketSystemStatus.Value.BugsEnabled = WorldConfig.GetBoolValue(WorldCfg.SupportBugsEnabled); + features.EuropaTicketSystemStatus.Value.ComplaintsEnabled = WorldConfig.GetBoolValue(WorldCfg.SupportComplaintsEnabled); + features.EuropaTicketSystemStatus.Value.SuggestionsEnabled = WorldConfig.GetBoolValue(WorldCfg.SupportSuggestionsEnabled); + + SendPacket(features); + } + + [WorldPacketHandler(ClientOpcodes.SetFactionAtWar)] + void HandleSetFactionAtWar(SetFactionAtWar packet) + { + GetPlayer().GetReputationMgr().SetAtWar(packet.FactionIndex, true); + } + + [WorldPacketHandler(ClientOpcodes.SetFactionNotAtWar)] + void HandleSetFactionNotAtWar(SetFactionNotAtWar packet) + { + GetPlayer().GetReputationMgr().SetAtWar(packet.FactionIndex, false); + } + + [WorldPacketHandler(ClientOpcodes.Tutorial)] + void HandleTutorialFlag(TutorialSetFlag packet) + { + switch (packet.Action) + { + case TutorialAction.Update: + { + byte index = (byte)(packet.TutorialBit >> 5); + if (index >= SharedConst.MaxAccountTutorialValues) + { + Log.outError(LogFilter.Network, "CMSG_TUTORIAL_FLAG received bad TutorialBit {0}.", packet.TutorialBit); + return; + } + uint flag = GetTutorialInt(index); + flag |= (uint)(1 << (int)(packet.TutorialBit & 0x1F)); + SetTutorialInt(index, flag); + break; + } + case TutorialAction.Clear: + for (byte i = 0; i < SharedConst.MaxAccountTutorialValues; ++i) + SetTutorialInt(i, 0xFFFFFFFF); + break; + case TutorialAction.Reset: + for (byte i = 0; i < SharedConst.MaxAccountTutorialValues; ++i) + SetTutorialInt(i, 0x00000000); + break; + default: + Log.outError(LogFilter.Network, "CMSG_TUTORIAL_FLAG received unknown TutorialAction {0}.", packet.Action); + return; + } + } + + [WorldPacketHandler(ClientOpcodes.SetWatchedFaction)] + void HandleSetWatchedFaction(SetWatchedFaction packet) + { + GetPlayer().SetInt32Value(PlayerFields.WatchedFactionIndex, (int)packet.FactionIndex); + } + + [WorldPacketHandler(ClientOpcodes.SetFactionInactive)] + void HandleSetFactionInactive(SetFactionInactive packet) + { + GetPlayer().GetReputationMgr().SetInactive(packet.Index, packet.State); + } + + [WorldPacketHandler(ClientOpcodes.RequestForcedReactions)] + void HandleRequestForcedReactions(RequestForcedReactions requestForcedReactions) + { + GetPlayer().GetReputationMgr().SendForceReactions(); + } + + [WorldPacketHandler(ClientOpcodes.CharacterRenameRequest, Status = SessionStatus.Authed)] + void HandleCharRename(CharacterRenameRequest request) + { + if (!_legitCharacters.Contains(request.RenameInfo.Guid)) + { + Log.outError(LogFilter.Network, "Account {0}, IP: {1} tried to rename character {2}, but it does not belong to their account!", + GetAccountId(), GetRemoteAddress(), request.RenameInfo.Guid.ToString()); + KickPlayer(); + return; + } + + // prevent character rename to invalid name + if (!ObjectManager.NormalizePlayerName(ref request.RenameInfo.NewName)) + { + SendCharRename(ResponseCodes.CharNameNoName, request.RenameInfo); + return; + } + + ResponseCodes res = ObjectManager.CheckPlayerName(request.RenameInfo.NewName, GetSessionDbcLocale(), true); + if (res != ResponseCodes.CharNameSuccess) + { + SendCharRename(res, request.RenameInfo); + return; + } + + if (!HasPermission(RBACPermissions.SkipCheckCharacterCreationReservedname) && Global.ObjectMgr.IsReservedName(request.RenameInfo.NewName)) + { + SendCharRename(ResponseCodes.CharNameReserved, request.RenameInfo); + return; + } + + // Ensure that there is no character with the desired new name + PreparedStatement stmt = DB.Characters.GetPreparedStatement(CharStatements.SEL_FREE_NAME); + stmt.AddValue(0, request.RenameInfo.Guid.GetCounter()); + stmt.AddValue(1, request.RenameInfo.NewName); + + _queryProcessor.AddQuery(DB.Characters.AsyncQuery(stmt).WithCallback(HandleCharRenameCallBack, request.RenameInfo)); + } + + void HandleCharRenameCallBack(CharacterRenameInfo renameInfo, SQLResult result) + { + if (result.IsEmpty()) + { + SendCharRename(ResponseCodes.CharNameFailure, renameInfo); + return; + } + + string oldName = result.Read(0); + // check name limitations + AtLoginFlags atLoginFlags = (AtLoginFlags)result.Read(1); + if (!atLoginFlags.HasAnyFlag(AtLoginFlags.Rename)) + { + SendCharRename(ResponseCodes.CharCreateError, renameInfo); + return; + } + atLoginFlags &= ~AtLoginFlags.Rename; + + SQLTransaction trans = new SQLTransaction(); + ulong lowGuid = renameInfo.Guid.GetCounter(); + + // Update name and at_login flag in the db + PreparedStatement stmt = DB.Characters.GetPreparedStatement(CharStatements.UPD_CHAR_NAME_AT_LOGIN); + stmt.AddValue(0, renameInfo.NewName); + stmt.AddValue(1, atLoginFlags); + stmt.AddValue(2, lowGuid); + trans.Append(stmt); + + stmt = DB.Characters.GetPreparedStatement(CharStatements.DEL_CHAR_DECLINED_NAME); + stmt.AddValue(0, lowGuid); + trans.Append(stmt); + + DB.Characters.CommitTransaction(trans); + + Log.outInfo(LogFilter.Player, "Account: {0} (IP: {1}) Character:[{2}] ({3}) Changed name to: {4}", + GetAccountId(), GetRemoteAddress(), oldName, renameInfo.Guid.ToString(), renameInfo.NewName); + + SendCharRename(ResponseCodes.Success, renameInfo); + + Global.WorldMgr.UpdateCharacterInfo(renameInfo.Guid, renameInfo.NewName); + } + + [WorldPacketHandler(ClientOpcodes.SetPlayerDeclinedNames, Status = SessionStatus.Authed)] + void HandleSetPlayerDeclinedNames(SetPlayerDeclinedNames packet) + { + // not accept declined names for unsupported languages + string name; + if (!ObjectManager.GetPlayerNameByGUID(packet.Player, out name)) + { + SendSetPlayerDeclinedNamesResult(DeclinedNameResult.Error, packet.Player); + return; + } + + if (!char.IsLetter(name[0])) // name already stored as only single alphabet using + { + SendSetPlayerDeclinedNamesResult(DeclinedNameResult.Error, packet.Player); + return; + } + + for (int i = 0; i < SharedConst.MaxDeclinedNameCases; ++i) + { + string declinedName = packet.DeclinedNames.name[i]; + if (!ObjectManager.NormalizePlayerName(ref declinedName)) + { + SendSetPlayerDeclinedNamesResult(DeclinedNameResult.Error, packet.Player); + return; + } + packet.DeclinedNames.name[i] = declinedName; + } + + for (int i = 0; i < SharedConst.MaxDeclinedNameCases; ++i) + { + string declinedName = packet.DeclinedNames.name[i]; + DB.Characters.EscapeString(ref declinedName); + packet.DeclinedNames.name[i] = declinedName; + } + + SQLTransaction trans = new SQLTransaction(); + + PreparedStatement stmt = DB.Characters.GetPreparedStatement(CharStatements.DEL_CHAR_DECLINED_NAME); + stmt.AddValue(0, packet.Player.GetCounter()); + trans.Append(stmt); + + stmt = DB.Characters.GetPreparedStatement(CharStatements.INS_CHAR_DECLINED_NAME); + stmt.AddValue(0, packet.Player.GetCounter()); + + for (byte i = 0; i < SharedConst.MaxDeclinedNameCases; i++) + stmt.AddValue(i + 1, packet.DeclinedNames.name[i]); + + trans.Append(stmt); + + DB.Characters.CommitTransaction(trans); + + SendSetPlayerDeclinedNamesResult(DeclinedNameResult.Success, packet.Player); + } + + [WorldPacketHandler(ClientOpcodes.AlterAppearance)] + void HandleAlterAppearance(AlterApperance packet) + { + BarberShopStyleRecord bs_hair = CliDB.BarberShopStyleStorage.LookupByKey(packet.NewHairStyle); + if (bs_hair == null || bs_hair.Type != 0 || bs_hair.Race != (byte)GetPlayer().GetRace() || bs_hair.Sex != GetPlayer().GetByteValue(PlayerFields.Bytes3, PlayerFieldOffsets.Bytes3OffsetGender)) + return; + + BarberShopStyleRecord bs_facialHair = CliDB.BarberShopStyleStorage.LookupByKey(packet.NewFacialHair); + if (bs_facialHair == null || bs_facialHair.Type != 2 || bs_facialHair.Race != (byte)GetPlayer().GetRace() || bs_facialHair.Sex != GetPlayer().GetByteValue(PlayerFields.Bytes3, PlayerFieldOffsets.Bytes3OffsetGender)) + return; + + BarberShopStyleRecord bs_skinColor = CliDB.BarberShopStyleStorage.LookupByKey(packet.NewSkinColor); + if (bs_skinColor != null && (bs_skinColor.Type != 3 || bs_skinColor.Race != (byte)GetPlayer().GetRace() || bs_skinColor.Sex != GetPlayer().GetByteValue(PlayerFields.Bytes3, PlayerFieldOffsets.Bytes3OffsetGender))) + return; + + BarberShopStyleRecord bs_face = CliDB.BarberShopStyleStorage.LookupByKey(packet.NewFace); + if (bs_face != null && (bs_face.Type != 4 || bs_face.Race != (byte)GetPlayer().GetRace() || bs_face.Sex != GetPlayer().GetByteValue(PlayerFields.Bytes3, PlayerFieldOffsets.Bytes3OffsetGender))) + return; + + Array customDisplayEntries = new Array(PlayerConst.CustomDisplaySize); + Array customDisplay = new Array(PlayerConst.CustomDisplaySize); + for (int i = 0; i < PlayerConst.CustomDisplaySize; ++i) + { + BarberShopStyleRecord bs_customDisplay = CliDB.BarberShopStyleStorage.LookupByKey(packet.NewCustomDisplay[i]); + if (bs_customDisplay != null && (bs_customDisplay.Type != 5 + i || bs_customDisplay.Race != (byte)_player.GetRace() || bs_customDisplay.Sex != _player.GetByteValue(PlayerFields.Bytes3, PlayerFieldOffsets.Bytes3OffsetGender))) + return; + + customDisplayEntries[i] = bs_customDisplay; + customDisplay[i] = (byte)(bs_customDisplay != null ? bs_customDisplay.Data : 0); + } + + if (!Player.ValidateAppearance(GetPlayer().GetRace(), GetPlayer().GetClass(), (Gender)GetPlayer().GetByteValue(PlayerFields.Bytes3, PlayerFieldOffsets.Bytes3OffsetGender), + bs_hair.Data, (byte)packet.NewHairColor, bs_face != null ? bs_face.Data : GetPlayer().GetByteValue(PlayerFields.Bytes, PlayerFieldOffsets.BytesOffsetFaceId), + bs_facialHair.Data, (bs_skinColor != null ? bs_skinColor.Data : GetPlayer().GetByteValue(PlayerFields.Bytes, PlayerFieldOffsets.BytesOffsetSkinId)), customDisplay)) + return; + + GameObject go = GetPlayer().FindNearestGameObjectOfType(GameObjectTypes.BarberChair, 5.0f); + if (!go) + { + SendPacket(new BarberShopResult(BarberShopResult.ResultEnum.NotOnChair)); + return; + } + + if (GetPlayer().GetStandState() != (UnitStandStateType)((int)UnitStandStateType.SitLowChair + go.GetGoInfo().BarberChair.chairheight)) + { + SendPacket(new BarberShopResult(BarberShopResult.ResultEnum.NotOnChair)); + return; + } + + uint cost = GetPlayer().GetBarberShopCost(bs_hair, packet.NewHairColor, bs_facialHair, bs_skinColor, bs_face, customDisplayEntries); + if (!GetPlayer().HasEnoughMoney((ulong)cost)) + { + SendPacket(new BarberShopResult(BarberShopResult.ResultEnum.NoMoney)); + return; + } + + SendPacket(new BarberShopResult(BarberShopResult.ResultEnum.Success)); + + _player.ModifyMoney(-cost); + _player.UpdateCriteria(CriteriaTypes.GoldSpentAtBarber, cost); + + _player.SetByteValue(PlayerFields.Bytes, PlayerFieldOffsets.BytesOffsetHairStyleId, bs_hair.Data); + _player.SetByteValue(PlayerFields.Bytes, PlayerFieldOffsets.BytesOffsetHairColorId, (byte)packet.NewHairColor); + _player.SetByteValue(PlayerFields.Bytes2, PlayerFieldOffsets.Bytes2OffsetFacialStyle, bs_facialHair.Data); + if (bs_skinColor != null) + GetPlayer().SetByteValue(PlayerFields.Bytes, PlayerFieldOffsets.BytesOffsetSkinId, bs_skinColor.Data); + if (bs_face != null) + _player.SetByteValue(PlayerFields.Bytes, PlayerFieldOffsets.BytesOffsetFaceId, bs_face.Data); + + for (int i = 0; i < PlayerConst.CustomDisplaySize; ++i) + _player.SetByteValue(PlayerFields.Bytes2, (byte)(PlayerFieldOffsets.Bytes2OffsetCustomDisplayOption + i), customDisplay[i]); + + _player.UpdateCriteria(CriteriaTypes.VisitBarberShop, 1); + + _player.SetStandState(UnitStandStateType.Stand); + } + + [WorldPacketHandler(ClientOpcodes.CharCustomize, Status = SessionStatus.Authed)] + void HandleCharCustomize(CharCustomize packet) + { + if (!_legitCharacters.Contains(packet.CustomizeInfo.CharGUID)) + { + Log.outError(LogFilter.Network, "Account {0}, IP: {1} tried to customise {2}, but it does not belong to their account!", + GetAccountId(), GetRemoteAddress(), packet.CustomizeInfo.CharGUID.ToString()); + KickPlayer(); + return; + } + + PreparedStatement stmt = DB.Characters.GetPreparedStatement(CharStatements.SEL_CHAR_CUSTOMIZE_INFO); + stmt.AddValue(0, packet.CustomizeInfo.CharGUID.GetCounter()); + + _queryProcessor.AddQuery(DB.Characters.AsyncQuery(stmt).WithCallback(HandleCharCustomizeCallback, packet.CustomizeInfo)); + } + + void HandleCharCustomizeCallback(CharCustomizeInfo customizeInfo, SQLResult result) + { + if (result.IsEmpty()) + { + SendCharCustomize(ResponseCodes.CharCreateError, customizeInfo); + return; + } + + string oldName = result.Read(0); + byte plrRace = result.Read(1); + byte plrClass = result.Read(2); + byte plrGender = result.Read(3); + AtLoginFlags atLoginFlags = (AtLoginFlags)result.Read(4); + + if (!Player.ValidateAppearance((Race)plrRace, (Class)plrClass, (Gender)plrGender, customizeInfo.HairStyleID, customizeInfo.HairColorID, customizeInfo.FaceID, + customizeInfo.FacialHairStyleID, customizeInfo.SkinID, customizeInfo.CustomDisplay)) + { + SendCharCustomize(ResponseCodes.CharCreateError, customizeInfo); + return; + } + + if (!atLoginFlags.HasAnyFlag(AtLoginFlags.Customize)) + { + SendCharCustomize(ResponseCodes.CharCreateError, customizeInfo); + return; + } + + // prevent character rename + if (WorldConfig.GetBoolValue(WorldCfg.PreventRenameCustomization) && (customizeInfo.CharName != oldName)) + { + SendCharCustomize(ResponseCodes.CharNameFailure, customizeInfo); + return; + } + + atLoginFlags &= ~AtLoginFlags.Customize; + + // prevent character rename to invalid name + if (!ObjectManager.NormalizePlayerName(ref customizeInfo.CharName)) + { + SendCharCustomize(ResponseCodes.CharNameNoName, customizeInfo); + return; + } + + ResponseCodes res = ObjectManager.CheckPlayerName(customizeInfo.CharName, GetSessionDbcLocale(), true); + if (res != ResponseCodes.CharNameSuccess) + { + SendCharCustomize(res, customizeInfo); + return; + } + + // check name limitations + if (!HasPermission(RBACPermissions.SkipCheckCharacterCreationReservedname) && Global.ObjectMgr.IsReservedName(customizeInfo.CharName)) + { + SendCharCustomize(ResponseCodes.CharNameReserved, customizeInfo); + return; + } + + // character with this name already exist + /// @todo: make async + ObjectGuid newGuid = ObjectManager.GetPlayerGUIDByName(customizeInfo.CharName); + if (!newGuid.IsEmpty()) + { + if (newGuid != customizeInfo.CharGUID) + { + SendCharCustomize(ResponseCodes.CharCreateNameInUse, customizeInfo); + return; + } + } + + PreparedStatement stmt; + SQLTransaction trans = new SQLTransaction(); + ulong lowGuid = customizeInfo.CharGUID.GetCounter(); + + /// Customize + { + stmt = DB.Characters.GetPreparedStatement(CharStatements.UPD_GENDER_AND_APPEARANCE); + + stmt.AddValue(0, customizeInfo.SexID); + stmt.AddValue(1, customizeInfo.SkinID); + stmt.AddValue(2, customizeInfo.FaceID); + stmt.AddValue(3, customizeInfo.HairStyleID); + stmt.AddValue(4, customizeInfo.HairColorID); + stmt.AddValue(5, customizeInfo.FacialHairStyleID); + stmt.AddValue(6, customizeInfo.CustomDisplay[0]); + stmt.AddValue(7, customizeInfo.CustomDisplay[1]); + stmt.AddValue(8, customizeInfo.CustomDisplay[2]); + stmt.AddValue(9, lowGuid); + + trans.Append(stmt); + } + + /// Name Change and update atLogin flags + { + stmt = DB.Characters.GetPreparedStatement(CharStatements.UPD_CHAR_NAME_AT_LOGIN); + stmt.AddValue(0, customizeInfo.CharName); + stmt.AddValue(1, atLoginFlags); + stmt.AddValue(2, lowGuid); + trans.Append(stmt); + + stmt = DB.Characters.GetPreparedStatement(CharStatements.DEL_CHAR_DECLINED_NAME); + stmt.AddValue(0, lowGuid); + + trans.Append(stmt); + } + + DB.Characters.CommitTransaction(trans); + + Global.WorldMgr.UpdateCharacterInfo(customizeInfo.CharGUID, customizeInfo.CharName, customizeInfo.SexID); + + SendCharCustomize(ResponseCodes.Success, customizeInfo); + + Log.outInfo(LogFilter.Player, "Account: {0} (IP: {1}), Character[{2}] ({3}) Customized to: {4}", + GetAccountId(), GetRemoteAddress(), oldName, customizeInfo.CharGUID.ToString(), customizeInfo.CharName); + } + + [WorldPacketHandler(ClientOpcodes.SaveEquipmentSet)] + void HandleEquipmentSetSave(SaveEquipmentSet saveEquipmentSet) + { + if (saveEquipmentSet.Set.SetID >= ItemConst.MaxEquipmentSetIndex) // client set slots amount + return; + + if (saveEquipmentSet.Set.Type > EquipmentSetInfo.EquipmentSetType.Transmog) + return; + + for (byte i = 0; i < EquipmentSlot.End; ++i) + { + if (!Convert.ToBoolean(saveEquipmentSet.Set.IgnoreMask & (1 << i))) + { + if (saveEquipmentSet.Set.Type == EquipmentSetInfo.EquipmentSetType.Equipment) + { + saveEquipmentSet.Set.Appearances[i] = 0; + + ObjectGuid itemGuid = saveEquipmentSet.Set.Pieces[i]; + + Item item = _player.GetItemByPos(InventorySlots.Bag0, i); + + /// cheating check 1 (item equipped but sent empty guid) + if (!item && !itemGuid.IsEmpty()) + return; + + /// cheating check 2 (sent guid does not match equipped item) + if (item && item.GetGUID() != itemGuid) + return; + } + else + { + saveEquipmentSet.Set.Pieces[i].Clear(); + if (saveEquipmentSet.Set.Appearances[i] != 0) + { + if (!CliDB.ItemModifiedAppearanceStorage.ContainsKey(saveEquipmentSet.Set.Appearances[i])) + return; + + var pairValue = GetCollectionMgr().HasItemAppearance((uint)saveEquipmentSet.Set.Appearances[i]); + if (!pairValue.Item1) + return; + } + } + } + else + { + saveEquipmentSet.Set.Pieces[i].Clear(); + saveEquipmentSet.Set.Appearances[i] = 0; + } + } + saveEquipmentSet.Set.IgnoreMask &= 0x7FFFF; /// clear invalid bits (i > EQUIPMENT_SLOT_END) + if (saveEquipmentSet.Set.Type == EquipmentSetInfo.EquipmentSetType.Equipment) + { + saveEquipmentSet.Set.Enchants[0] = 0; + saveEquipmentSet.Set.Enchants[1] = 0; + } + else + { + var validateIllusion = new Func(enchantId => + { + SpellItemEnchantmentRecord illusion = CliDB.SpellItemEnchantmentStorage.LookupByKey(enchantId); + if (illusion == null) + return false; + + if (illusion.ItemVisual == 0 || !illusion.Flags.HasAnyFlag(EnchantmentSlotMask.Collectable)) + return false; + + PlayerConditionRecord condition = CliDB.PlayerConditionStorage.LookupByKey(illusion.PlayerConditionID); + if (condition != null) + if (!ConditionManager.IsPlayerMeetingCondition(_player, condition)) + return false; + + if (illusion.ScalingClassRestricted > 0 && illusion.ScalingClassRestricted != (byte)_player.GetClass()) + return false; + + return true; + }); + + if (saveEquipmentSet.Set.Enchants[0] != 0 && !validateIllusion((uint)saveEquipmentSet.Set.Enchants[0])) + return; + + if (saveEquipmentSet.Set.Enchants[1] != 0 && !validateIllusion((uint)saveEquipmentSet.Set.Enchants[1])) + return; + } + + GetPlayer().SetEquipmentSet(saveEquipmentSet.Set); + } + + [WorldPacketHandler(ClientOpcodes.DeleteEquipmentSet)] + void HandleDeleteEquipmentSet(DeleteEquipmentSet packet) + { + GetPlayer().DeleteEquipmentSet(packet.ID); + } + + [WorldPacketHandler(ClientOpcodes.UseEquipmentSet)] + void HandleUseEquipmentSet(UseEquipmentSet useEquipmentSet) + { + ObjectGuid ignoredItemGuid = new ObjectGuid(0, 1); + for (byte i = 0; i < EquipmentSlot.End; ++i) + { + Log.outDebug(LogFilter.Player, "{0}: ContainerSlot: {1}, Slot: {2}", useEquipmentSet.Items[i].Item.ToString(), useEquipmentSet.Items[i].ContainerSlot, useEquipmentSet.Items[i].Slot); + + // check if item slot is set to "ignored" (raw value == 1), must not be unequipped then + if (useEquipmentSet.Items[i].Item == ignoredItemGuid) + continue; + + // Only equip weapons in combat + if (GetPlayer().IsInCombat() && i != EquipmentSlot.MainHand && i != EquipmentSlot.OffHand) + continue; + + Item item = GetPlayer().GetItemByGuid(useEquipmentSet.Items[i].Item); + + ushort dstPos = (ushort)(i | (InventorySlots.Bag0 << 8)); + if (!item) + { + Item uItem = GetPlayer().GetItemByPos(InventorySlots.Bag0, i); + if (!uItem) + continue; + + List itemPosCount = new List(); + InventoryResult inventoryResult = GetPlayer().CanStoreItem(ItemConst.NullBag, ItemConst.NullSlot, itemPosCount, uItem, false); + if (inventoryResult == InventoryResult.Ok) + { + GetPlayer().RemoveItem(InventorySlots.Bag0, i, true); + GetPlayer().StoreItem(itemPosCount, uItem, true); + } + else + GetPlayer().SendEquipError(inventoryResult, uItem); + + continue; + } + + if (item.GetPos() == dstPos) + continue; + + GetPlayer().SwapItem(item.GetPos(), dstPos); + } + + UseEquipmentSetResult result = new UseEquipmentSetResult(); + result.GUID = useEquipmentSet.GUID; + result.Reason = 0; // 4 - equipment swap failed - inventory is full + SendPacket(result); + } + + [WorldPacketHandler(ClientOpcodes.CharRaceOrFactionChange, Status = SessionStatus.Authed)] + void HandleCharRaceOrFactionChange(CharRaceOrFactionChange packet) + { + if (!_legitCharacters.Contains(packet.RaceOrFactionChangeInfo.Guid)) + { + Log.outError(LogFilter.Network, "Account {0}, IP: {1} tried to factionchange character {2}, but it does not belong to their account!", + GetAccountId(), GetRemoteAddress(), packet.RaceOrFactionChangeInfo.Guid.ToString()); + KickPlayer(); + return; + } + + PreparedStatement stmt = DB.Characters.GetPreparedStatement(CharStatements.SEL_CHAR_RACE_OR_FACTION_CHANGE_INFOS); + stmt.AddValue(0, packet.RaceOrFactionChangeInfo.Guid.GetCounter()); + + _queryProcessor.AddQuery(DB.Characters.AsyncQuery(stmt).WithCallback(HandleCharRaceOrFactionChangeCallback, packet.RaceOrFactionChangeInfo)); + } + + void HandleCharRaceOrFactionChangeCallback(CharRaceOrFactionChangeInfo factionChangeInfo, SQLResult result) + { + if (result.IsEmpty()) + { + SendCharFactionChange(ResponseCodes.CharCreateError, factionChangeInfo); + return; + } + + // get the players old (at this moment current) race + CharacterInfo characterInfo = Global.WorldMgr.GetCharacterInfo(factionChangeInfo.Guid); + if (characterInfo == null) + { + SendCharFactionChange(ResponseCodes.CharCreateError, factionChangeInfo); + return; + } + + string oldName = characterInfo.Name; + Race oldRace = characterInfo.RaceID; + Class playerClass = characterInfo.ClassID; + byte level = characterInfo.Level; + + if (Global.ObjectMgr.GetPlayerInfo(factionChangeInfo.RaceID, playerClass) == null) + { + SendCharFactionChange(ResponseCodes.CharCreateError, factionChangeInfo); + return; + } + + AtLoginFlags atLoginFlags = (AtLoginFlags)result.Read(0); + string knownTitlesStr = result.Read(1); + + AtLoginFlags usedLoginFlag = (factionChangeInfo.FactionChange ? AtLoginFlags.ChangeFaction : AtLoginFlags.ChangeRace); + if (!atLoginFlags.HasAnyFlag(usedLoginFlag)) + { + SendCharFactionChange(ResponseCodes.CharCreateError, factionChangeInfo); + return; + } + + uint newTeamId = Player.TeamIdForRace(factionChangeInfo.RaceID); + if (newTeamId == TeamId.Neutral) + { + SendCharFactionChange(ResponseCodes.CharCreateRestrictedRaceclass, factionChangeInfo); + return; + } + + if (factionChangeInfo.FactionChange == (Player.TeamIdForRace(oldRace) == newTeamId)) + { + SendCharFactionChange(factionChangeInfo.FactionChange ? ResponseCodes.CharCreateCharacterSwapFaction : ResponseCodes.CharCreateCharacterRaceOnly, factionChangeInfo); + return; + } + + if (!HasPermission(RBACPermissions.SkipCheckCharacterCreationRacemask)) + { + uint raceMaskDisabled = WorldConfig.GetUIntValue(WorldCfg.CharacterCreatingDisabledRacemask); + if (Convert.ToBoolean(1 << ((int)factionChangeInfo.RaceID - 1) & raceMaskDisabled)) + { + SendCharFactionChange(ResponseCodes.CharCreateError, factionChangeInfo); + return; + } + } + + // prevent character rename + if (WorldConfig.GetBoolValue(WorldCfg.PreventRenameCustomization) && (factionChangeInfo.Name != oldName)) + { + SendCharFactionChange(ResponseCodes.CharNameFailure, factionChangeInfo); + return; + } + + // prevent character rename to invalid name + if (!ObjectManager.NormalizePlayerName(ref factionChangeInfo.Name)) + { + SendCharFactionChange(ResponseCodes.CharNameNoName, factionChangeInfo); + return; + } + + ResponseCodes res = ObjectManager.CheckPlayerName(factionChangeInfo.Name, GetSessionDbcLocale(), true); + if (res != ResponseCodes.CharNameSuccess) + { + SendCharFactionChange(res, factionChangeInfo); + return; + } + + // check name limitations + if (!HasPermission(RBACPermissions.SkipCheckCharacterCreationReservedname) && Global.ObjectMgr.IsReservedName(factionChangeInfo.Name)) + { + SendCharFactionChange(ResponseCodes.CharNameReserved, factionChangeInfo); + return; + } + + // character with this name already exist + ObjectGuid newGuid = ObjectManager.GetPlayerGUIDByName(factionChangeInfo.Name); + if (!newGuid.IsEmpty()) + { + if (newGuid != factionChangeInfo.Guid) + { + SendCharFactionChange(ResponseCodes.CharCreateNameInUse, factionChangeInfo); + return; + } + } + + if (Global.ArenaTeamMgr.GetArenaTeamByCaptain(factionChangeInfo.Guid) != null) + { + SendCharFactionChange(ResponseCodes.CharCreateCharacterArenaLeader, factionChangeInfo); + return; + } + + // All checks are fine, deal with race change now + ulong lowGuid = factionChangeInfo.Guid.GetCounter(); + + PreparedStatement stmt; + SQLTransaction trans = new SQLTransaction(); + + // resurrect the character in case he's dead + Player.OfflineResurrect(factionChangeInfo.Guid, trans); + + /// Name Change and update atLogin flags + { + stmt = DB.Characters.GetPreparedStatement(CharStatements.UPD_CHAR_NAME_AT_LOGIN); + stmt.AddValue(0, factionChangeInfo.Name); + stmt.AddValue(1, (ushort)((atLoginFlags | AtLoginFlags.Resurrect) & ~usedLoginFlag)); + stmt.AddValue(2, lowGuid); + + trans.Append(stmt); + + stmt = DB.Characters.GetPreparedStatement(CharStatements.DEL_CHAR_DECLINED_NAME); + stmt.AddValue(0, lowGuid); + + trans.Append(stmt); + } + + // Customize + { + stmt = DB.Characters.GetPreparedStatement(CharStatements.UPD_GENDER_AND_APPEARANCE); + stmt.AddValue(0, factionChangeInfo.SexID); + stmt.AddValue(1, factionChangeInfo.SkinID); + stmt.AddValue(2, factionChangeInfo.FaceID); + stmt.AddValue(3, factionChangeInfo.HairStyleID); + stmt.AddValue(4, factionChangeInfo.HairColorID); + stmt.AddValue(5, factionChangeInfo.FacialHairStyleID); + stmt.AddValue(6, factionChangeInfo.CustomDisplay[0]); + stmt.AddValue(7, factionChangeInfo.CustomDisplay[1]); + stmt.AddValue(8, factionChangeInfo.CustomDisplay[2]); + stmt.AddValue(9, lowGuid); + + trans.Append(stmt); + } + + // Race Change + { + stmt = DB.Characters.GetPreparedStatement(CharStatements.UPD_CHAR_RACE); + stmt.AddValue(0, factionChangeInfo.RaceID); + stmt.AddValue(1, lowGuid); + + trans.Append(stmt); + } + + Global.WorldMgr.UpdateCharacterInfo(factionChangeInfo.Guid, factionChangeInfo.Name, factionChangeInfo.SexID, factionChangeInfo.RaceID); + + if (oldRace != factionChangeInfo.RaceID) + { + // Switch Languages + // delete all languages first + stmt = DB.Characters.GetPreparedStatement(CharStatements.DEL_CHAR_SKILL_LANGUAGES); + stmt.AddValue(0, lowGuid); + trans.Append(stmt); + + // Now add them back + stmt = DB.Characters.GetPreparedStatement(CharStatements.INS_CHAR_SKILL_LANGUAGE); + stmt.AddValue(0, lowGuid); + + // Faction specific languages + if (newTeamId == TeamId.Horde) + stmt.AddValue(1, 109); + else + stmt.AddValue(1, 98); + + trans.Append(stmt); + + // Race specific languages + if (factionChangeInfo.RaceID != Race.Orc && factionChangeInfo.RaceID != Race.Human) + { + stmt = DB.Characters.GetPreparedStatement(CharStatements.INS_CHAR_SKILL_LANGUAGE); + stmt.AddValue(0, lowGuid); + + switch (factionChangeInfo.RaceID) + { + case Race.Dwarf: + stmt.AddValue(1, 111); + break; + case Race.Draenei: + stmt.AddValue(1, 759); + break; + case Race.Gnome: + stmt.AddValue(1, 313); + break; + case Race.NightElf: + stmt.AddValue(1, 113); + break; + case Race.Worgen: + stmt.AddValue(1, 791); + break; + case Race.Undead: + stmt.AddValue(1, 673); + break; + case Race.Tauren: + stmt.AddValue(1, 115); + break; + case Race.Troll: + stmt.AddValue(1, 315); + break; + case Race.BloodElf: + stmt.AddValue(1, 137); + break; + case Race.Goblin: + stmt.AddValue(1, 792); + break; + } + + trans.Append(stmt); + } + + // Team Conversation + if (factionChangeInfo.FactionChange) + { + // Delete all Flypaths + stmt = DB.Characters.GetPreparedStatement(CharStatements.UPD_CHAR_TAXI_PATH); + stmt.AddValue(0, lowGuid); + trans.Append(stmt); + + if (level > 7) + { + // Update Taxi path + // this doesn't seem to be 100% blizzlike... but it can't really be helped. + string taximaskstream = ""; + + + var factionMask = newTeamId == TeamId.Horde ? CliDB.HordeTaxiNodesMask : CliDB.AllianceTaxiNodesMask; + for (byte i = 0; i < PlayerConst.TaxiMaskSize; ++i) + { + // i = (315 - 1) / 8 = 39 + // m = 1 << ((315 - 1) % 8) = 4 + int deathKnightExtraNode = playerClass != Class.Deathknight || i != 39 ? 0 : 4; + taximaskstream += (uint)(factionMask[i] | deathKnightExtraNode) + ' '; + } + + stmt = DB.Characters.GetPreparedStatement(CharStatements.UPD_CHAR_TAXIMASK); + stmt.AddValue(0, taximaskstream); + stmt.AddValue(1, lowGuid); + trans.Append(stmt); + } + + /// @todo: make this part asynch + if (!WorldConfig.GetBoolValue(WorldCfg.AllowTwoSideInteractionGuild)) + { + // Reset guild + stmt = DB.Characters.GetPreparedStatement(CharStatements.SEL_GUILD_MEMBER); + stmt.AddValue(0, lowGuid); + + result = DB.Characters.Query(stmt); + if (!result.IsEmpty()) + { + Guild guild = Global.GuildMgr.GetGuildById(result.Read(0)); + if (guild) + guild.DeleteMember(factionChangeInfo.Guid, false, false, true); + } + + Player.LeaveAllArenaTeams(factionChangeInfo.Guid); + } + + if (!HasPermission(RBACPermissions.TwoSideAddFriend)) + { + // Delete Friend List + stmt = DB.Characters.GetPreparedStatement(CharStatements.DEL_CHAR_SOCIAL_BY_GUID); + stmt.AddValue(0, lowGuid); + trans.Append(stmt); + + stmt = DB.Characters.GetPreparedStatement(CharStatements.DEL_CHAR_SOCIAL_BY_FRIEND); + stmt.AddValue(0, lowGuid); + trans.Append(stmt); + } + + // Reset homebind and position + stmt = DB.Characters.GetPreparedStatement(CharStatements.DEL_PLAYER_HOMEBIND); + stmt.AddValue(0, lowGuid); + trans.Append(stmt); + + stmt = DB.Characters.GetPreparedStatement(CharStatements.INS_PLAYER_HOMEBIND); + stmt.AddValue(0, lowGuid); + + WorldLocation loc; + ushort zoneId = 0; + if (newTeamId == TeamId.Alliance) + { + loc = new WorldLocation(0, -8867.68f, 673.373f, 97.9034f, 0.0f); + zoneId = 1519; + } + else + { + loc = new WorldLocation(1, 1633.33f, -4439.11f, 15.7588f, 0.0f); + zoneId = 1637; + } + + stmt.AddValue(1, loc.GetMapId()); + stmt.AddValue(2, zoneId); + stmt.AddValue(3, loc.GetPositionX()); + stmt.AddValue(4, loc.GetPositionY()); + stmt.AddValue(5, loc.GetPositionZ()); + trans.Append(stmt); + + Player.SavePositionInDB(loc, zoneId, factionChangeInfo.Guid, trans); + + // Achievement conversion + foreach (var it in Global.ObjectMgr.FactionChangeAchievements) + { + uint achiev_alliance = it.Key; + uint achiev_horde = it.Value; + + stmt = DB.Characters.GetPreparedStatement(CharStatements.DEL_CHAR_ACHIEVEMENT_BY_ACHIEVEMENT); + stmt.AddValue(0, (ushort)(newTeamId == TeamId.Alliance ? achiev_alliance : achiev_horde)); + stmt.AddValue(1, lowGuid); + trans.Append(stmt); + + stmt = DB.Characters.GetPreparedStatement(CharStatements.UPD_CHAR_ACHIEVEMENT); + stmt.AddValue(0, (ushort)(newTeamId == TeamId.Alliance ? achiev_alliance : achiev_horde)); + stmt.AddValue(1, (ushort)(newTeamId == TeamId.Alliance ? achiev_horde : achiev_alliance)); + stmt.AddValue(2, lowGuid); + trans.Append(stmt); + } + + // Item conversion + foreach (var it in Global.ObjectMgr.FactionChangeItems) + { + uint item_alliance = it.Key; + uint item_horde = it.Value; + + stmt = DB.Characters.GetPreparedStatement(CharStatements.UPD_CHAR_INVENTORY_FACTION_CHANGE); + stmt.AddValue(0, (newTeamId == TeamId.Alliance ? item_alliance : item_horde)); + stmt.AddValue(1, (newTeamId == TeamId.Alliance ? item_horde : item_alliance)); + stmt.AddValue(2, lowGuid); + trans.Append(stmt); + } + + // Delete all current quests + stmt = DB.Characters.GetPreparedStatement(CharStatements.DEL_CHAR_QUESTSTATUS); + stmt.AddValue(0, lowGuid); + trans.Append(stmt); + + // Quest conversion + foreach (var it in Global.ObjectMgr.FactionChangeQuests) + { + uint quest_alliance = it.Key; + uint quest_horde = it.Value; + + stmt = DB.Characters.GetPreparedStatement(CharStatements.DEL_CHAR_QUESTSTATUS_REWARDED_BY_QUEST); + stmt.AddValue(0, lowGuid); + stmt.AddValue(1, (newTeamId == TeamId.Alliance ? quest_alliance : quest_horde)); + trans.Append(stmt); + + stmt = DB.Characters.GetPreparedStatement(CharStatements.UPD_CHAR_QUESTSTATUS_REWARDED_FACTION_CHANGE); + stmt.AddValue(0, (newTeamId == TeamId.Alliance ? quest_alliance : quest_horde)); + stmt.AddValue(1, (newTeamId == TeamId.Alliance ? quest_horde : quest_alliance)); + stmt.AddValue(2, lowGuid); + trans.Append(stmt); + } + + // Mark all rewarded quests as "active" (will count for completed quests achievements) + stmt = DB.Characters.GetPreparedStatement(CharStatements.UPD_CHAR_QUESTSTATUS_REWARDED_ACTIVE); + stmt.AddValue(0, lowGuid); + trans.Append(stmt); + + // Disable all old-faction specific quests + { + var questTemplates = Global.ObjectMgr.GetQuestTemplates(); + foreach (Quest quest in questTemplates.Values) + { + uint newRaceMask = (uint)(newTeamId == TeamId.Alliance ? Race.RaceMaskAlliance : Race.RaceMaskHorde); + if (quest.AllowableRaces != -1 && !Convert.ToBoolean(quest.AllowableRaces & newRaceMask)) + { + stmt = DB.Characters.GetPreparedStatement(CharStatements.UPD_CHAR_QUESTSTATUS_REWARDED_ACTIVE_BY_QUEST); + stmt.AddValue(0, lowGuid); + stmt.AddValue(1, quest.Id); + trans.Append(stmt); + } + } + } + + // Spell conversion + foreach (var it in Global.ObjectMgr.FactionChangeSpells) + { + uint spell_alliance = it.Key; + uint spell_horde = it.Value; + + stmt = DB.Characters.GetPreparedStatement(CharStatements.DEL_CHAR_SPELL_BY_SPELL); + stmt.AddValue(0, (newTeamId == TeamId.Alliance ? spell_alliance : spell_horde)); + stmt.AddValue(1, lowGuid); + trans.Append(stmt); + + stmt = DB.Characters.GetPreparedStatement(CharStatements.UPD_CHAR_SPELL_FACTION_CHANGE); + stmt.AddValue(0, (newTeamId == TeamId.Alliance ? spell_alliance : spell_horde)); + stmt.AddValue(1, (newTeamId == TeamId.Alliance ? spell_horde : spell_alliance)); + stmt.AddValue(2, lowGuid); + trans.Append(stmt); + } + + // Reputation conversion + foreach (var it in Global.ObjectMgr.FactionChangeReputation) + { + uint reputation_alliance = it.Key; + uint reputation_horde = it.Value; + uint newReputation = (newTeamId == TeamId.Alliance) ? reputation_alliance : reputation_horde; + uint oldReputation = (newTeamId == TeamId.Alliance) ? reputation_horde : reputation_alliance; + + // select old standing set in db + stmt = DB.Characters.GetPreparedStatement(CharStatements.SEL_CHAR_REP_BY_FACTION); + stmt.AddValue(0, oldReputation); + stmt.AddValue(1, lowGuid); + + result = DB.Characters.Query(stmt); + if (!result.IsEmpty()) + { + int oldDBRep = result.Read(0); + FactionRecord factionEntry = CliDB.FactionStorage.LookupByKey(oldReputation); + + // old base reputation + int oldBaseRep = ReputationMgr.GetBaseReputationOf(factionEntry, oldRace, playerClass); + + // new base reputation + int newBaseRep = ReputationMgr.GetBaseReputationOf(CliDB.FactionStorage.LookupByKey(newReputation), factionChangeInfo.RaceID, playerClass); + + // final reputation shouldnt change + int FinalRep = oldDBRep + oldBaseRep; + int newDBRep = FinalRep - newBaseRep; + + stmt = DB.Characters.GetPreparedStatement(CharStatements.DEL_CHAR_REP_BY_FACTION); + stmt.AddValue(0, newReputation); + stmt.AddValue(1, lowGuid); + trans.Append(stmt); + + stmt = DB.Characters.GetPreparedStatement(CharStatements.UPD_CHAR_REP_FACTION_CHANGE); + stmt.AddValue(0, (ushort)newReputation); + stmt.AddValue(1, newDBRep); + stmt.AddValue(2, (ushort)oldReputation); + stmt.AddValue(3, lowGuid); + trans.Append(stmt); + } + } + + // Title conversion + if (!string.IsNullOrEmpty(knownTitlesStr)) + { + uint ktcount = PlayerConst.KnowTitlesSize * 2; + uint[] knownTitles = new uint[ktcount]; + + var tokens = new StringArray(knownTitlesStr, ' '); + if (tokens.Length != ktcount) + { + SendCharFactionChange(ResponseCodes.CharCreateError, factionChangeInfo); + return; + } + + for (int index = 0; index < ktcount; ++index) + knownTitles[index] = uint.Parse(tokens[index]); + + foreach (var it in Global.ObjectMgr.FactionChangeTitles) + { + uint title_alliance = it.Key; + uint title_horde = it.Value; + + CharTitlesRecord atitleInfo = CliDB.CharTitlesStorage.LookupByKey(title_alliance); + CharTitlesRecord htitleInfo = CliDB.CharTitlesStorage.LookupByKey(title_horde); + // new team + if (newTeamId == TeamId.Alliance) + { + uint maskID = htitleInfo.MaskID; + uint index = maskID / 32; + uint old_flag = (uint)(1 << (int)(maskID % 32)); + uint new_flag = (uint)(1 << (int)(atitleInfo.MaskID % 32)); + if (Convert.ToBoolean(knownTitles[index] & old_flag)) + { + knownTitles[index] &= ~old_flag; + // use index of the new title + knownTitles[atitleInfo.MaskID / 32] |= new_flag; + } + } + else + { + uint maskID = atitleInfo.MaskID; + uint index = maskID / 32; + uint old_flag = (uint)(1 << (int)(maskID % 32)); + uint new_flag = (uint)(1 << (int)(htitleInfo.MaskID % 32)); + if (Convert.ToBoolean(knownTitles[index] & old_flag)) + { + knownTitles[index] &= ~old_flag; + // use index of the new title + knownTitles[htitleInfo.MaskID / 32] |= new_flag; + } + } + + string ss = ""; + for (uint index = 0; index < ktcount; ++index) + ss += knownTitles[index] + ' '; + + stmt = DB.Characters.GetPreparedStatement(CharStatements.UPD_CHAR_TITLES_FACTION_CHANGE); + stmt.AddValue(0, ss); + stmt.AddValue(1, lowGuid); + trans.Append(stmt); + + // unset any currently chosen title + stmt = DB.Characters.GetPreparedStatement(CharStatements.RES_CHAR_TITLES_FACTION_CHANGE); + stmt.AddValue(0, lowGuid); + trans.Append(stmt); + } + } + } + } + + DB.Characters.CommitTransaction(trans); + + Log.outDebug(LogFilter.Player, "{0} (IP: {1}) changed race from {2} to {3}", GetPlayerInfo(), GetRemoteAddress(), oldRace, factionChangeInfo.RaceID); + + SendCharFactionChange(ResponseCodes.Success, factionChangeInfo); + } + + [WorldPacketHandler(ClientOpcodes.OpeningCinematic)] + void HandleOpeningCinematic(OpeningCinematic packet) + { + // Only players that has not yet gained any experience can use this + if (GetPlayer().GetUInt32Value(PlayerFields.Xp) != 0) + return; + + ChrClassesRecord classEntry = CliDB.ChrClassesStorage.LookupByKey(GetPlayer().GetClass()); + if (classEntry != null) + { + ChrRacesRecord raceEntry = CliDB.ChrRacesStorage.LookupByKey(GetPlayer().GetRace()); + if (classEntry.CinematicSequenceID != 0) + GetPlayer().SendCinematicStart(classEntry.CinematicSequenceID); + else if (raceEntry != null) + GetPlayer().SendCinematicStart(raceEntry.CinematicSequenceID); + } + } + + [WorldPacketHandler(ClientOpcodes.GetUndeleteCharacterCooldownStatus, Status = SessionStatus.Authed)] + void HandleGetUndeleteCooldownStatus(GetUndeleteCharacterCooldownStatus getCooldown) + { + PreparedStatement stmt = DB.Login.GetPreparedStatement(LoginStatements.SEL_LAST_CHAR_UNDELETE); + stmt.AddValue(0, GetBattlenetAccountId()); + + _queryProcessor.AddQuery(DB.Login.AsyncQuery(stmt).WithCallback(HandleUndeleteCooldownStatusCallback)); + } + + void HandleUndeleteCooldownStatusCallback(SQLResult result) + { + uint cooldown = 0; + uint maxCooldown = WorldConfig.GetUIntValue(WorldCfg.FeatureSystemCharacterUndeleteCooldown); + if (!result.IsEmpty()) + { + uint lastUndelete = result.Read(0); + uint now = (uint)Time.UnixTime; + if (lastUndelete + maxCooldown > now) + cooldown = Math.Max(0, lastUndelete + maxCooldown - now); + } + + SendUndeleteCooldownStatusResponse(cooldown, maxCooldown); + } + + [WorldPacketHandler(ClientOpcodes.UndeleteCharacter, Status = SessionStatus.Authed)] + void HandleCharUndelete(UndeleteCharacter undeleteCharacter) + { + if (!WorldConfig.GetBoolValue(WorldCfg.FeatureSystemCharacterUndeleteEnabled)) + { + SendUndeleteCharacterResponse(CharacterUndeleteResult.Disabled, undeleteCharacter.UndeleteInfo); + return; + } + + PreparedStatement stmt = DB.Login.GetPreparedStatement(LoginStatements.SEL_LAST_CHAR_UNDELETE); + stmt.AddValue(0, GetBattlenetAccountId()); + + CharacterUndeleteInfo undeleteInfo = undeleteCharacter.UndeleteInfo; + _queryProcessor.AddQuery(DB.Login.AsyncQuery(stmt).WithChainingCallback((queryCallback, result) => + { + if (!result.IsEmpty()) + { + uint lastUndelete = result.Read(0); + uint maxCooldown = WorldConfig.GetUIntValue(WorldCfg.FeatureSystemCharacterUndeleteCooldown); + if (lastUndelete != 0 && (lastUndelete + maxCooldown > Time.UnixTime)) + { + SendUndeleteCharacterResponse(CharacterUndeleteResult.Cooldown, undeleteInfo); + return; + } + } + + stmt = DB.Characters.GetPreparedStatement(CharStatements.SEL_CHAR_DEL_INFO_BY_GUID); + stmt.AddValue(0, undeleteInfo.CharacterGuid.GetCounter()); + queryCallback.SetNextQuery(DB.Characters.AsyncQuery(stmt)); + }).WithChainingCallback((queryCallback, result) => + { + if (result.IsEmpty()) + { + SendUndeleteCharacterResponse(CharacterUndeleteResult.CharCreate, undeleteInfo); + return; + } + + undeleteInfo.Name = result.Read(1); + uint account = result.Read(2); + if (account != GetAccountId()) + { + SendUndeleteCharacterResponse(CharacterUndeleteResult.Unknown, undeleteInfo); + return; + } + + stmt = DB.Characters.GetPreparedStatement(CharStatements.SEL_CHECK_NAME); + stmt.AddValue(0, undeleteInfo.Name); + queryCallback.SetNextQuery(DB.Characters.AsyncQuery(stmt)); + }).WithChainingCallback((queryCallback, result) => + { + if (!result.IsEmpty()) + { + SendUndeleteCharacterResponse(CharacterUndeleteResult.NameTakenByThisAccount, undeleteInfo); + return; + } + + /// @todo: add more safety checks + /// * max char count per account + /// * max death knight count + /// * max demon hunter count + /// * team violation + + stmt = DB.Characters.GetPreparedStatement(CharStatements.SEL_SUM_CHARS); + stmt.AddValue(0, GetAccountId()); + queryCallback.SetNextQuery(DB.Characters.AsyncQuery(stmt)); + }).WithCallback(result => + { + if (!result.IsEmpty()) + { + if (result.Read(0) >= WorldConfig.GetUIntValue(WorldCfg.CharactersPerRealm)) // SQL's COUNT() returns uint64 but it will always be less than uint8.Max + { + SendUndeleteCharacterResponse(CharacterUndeleteResult.CharCreate, undeleteInfo); + return; + } + } + + stmt = DB.Characters.GetPreparedStatement(CharStatements.UPD_RESTORE_DELETE_INFO); + stmt.AddValue(0, undeleteInfo.Name); + stmt.AddValue(1, GetAccountId()); + stmt.AddValue(2, undeleteInfo.CharacterGuid.GetCounter()); + DB.Characters.Execute(stmt); + + stmt = DB.Login.GetPreparedStatement(LoginStatements.UPD_LAST_CHAR_UNDELETE); + stmt.AddValue(0, GetBattlenetAccountId()); + DB.Login.Execute(stmt); + + Global.WorldMgr.UpdateCharacterInfoDeleted(undeleteInfo.CharacterGuid, false, undeleteInfo.Name); + + SendUndeleteCharacterResponse(CharacterUndeleteResult.Ok, undeleteInfo); + })); + } + + [WorldPacketHandler(ClientOpcodes.RepopRequest)] + void HandleRepopRequest(RepopRequest packet) + { + if (GetPlayer().IsAlive() || GetPlayer().HasFlag(PlayerFields.Flags, PlayerFlags.Ghost)) + return; + + if (GetPlayer().HasAuraType(AuraType.PreventResurrection)) + return; // silently return, client should display the error by itself + + // the world update order is sessions, players, creatures + // the netcode runs in parallel with all of these + // creatures can kill players + // so if the server is lagging enough the player can + // release spirit after he's killed but before he is updated + if (GetPlayer().getDeathState() == DeathState.JustDied) + { + Log.outDebug(LogFilter.Network, "HandleRepopRequestOpcode: got request after player {0} ({1}) was killed and before he was updated", + GetPlayer().GetName(), GetPlayer().GetGUID().ToString()); + GetPlayer().KillPlayer(); + } + + //this is spirit release confirm? + GetPlayer().RemovePet(null, PetSaveMode.NotInSlot, true); + GetPlayer().BuildPlayerRepop(); + GetPlayer().RepopAtGraveyard(); + } + + [WorldPacketHandler(ClientOpcodes.ClientPortGraveyard)] + void HandlePortGraveyard(PortGraveyard packet) + { + if (GetPlayer().IsAlive() || !GetPlayer().HasFlag(PlayerFields.Flags, PlayerFlags.Ghost)) + return; + GetPlayer().RepopAtGraveyard(); + } + + [WorldPacketHandler(ClientOpcodes.RequestCemeteryList, Processing = PacketProcessing.Inplace)] + void HandleRequestCemeteryList(RequestCemeteryList requestCemeteryList) + { + uint zoneId = GetPlayer().GetZoneId(); + uint team = (uint)GetPlayer().GetTeam(); + + List graveyardIds = new List(); + var range = Global.ObjectMgr.GraveYardStorage.LookupByKey(zoneId); + + for (int i = 0; i < range.Count && graveyardIds.Count < 16; ++i) // client max + { + var gYard = range[i]; + if (gYard.team == 0 || gYard.team == team) + graveyardIds.Add(i); + } + + if (graveyardIds.Empty()) + { + Log.outDebug(LogFilter.Network, "No graveyards found for zone {0} for player {1} (team {2}) in CMSG_REQUEST_CEMETERY_LIST", + zoneId, m_GUIDLow, team); + return; + } + + RequestCemeteryListResponse packet = new RequestCemeteryListResponse(); + packet.IsGossipTriggered = false; + + foreach (uint id in graveyardIds) + packet.CemeteryID.Add(id); + + SendPacket(packet); + } + + [WorldPacketHandler(ClientOpcodes.ReclaimCorpse)] + void HandleReclaimCorpse(ReclaimCorpse packet) + { + if (GetPlayer().IsAlive()) + return; + + // do not allow corpse reclaim in arena + if (GetPlayer().InArena()) + return; + + // body not released yet + if (!GetPlayer().HasFlag(PlayerFields.Flags, PlayerFlags.Ghost)) + return; + + Corpse corpse = GetPlayer().GetCorpse(); + if (!corpse) + return; + + // prevent resurrect before 30-sec delay after body release not finished + if ((corpse.GetGhostTime() + GetPlayer().GetCorpseReclaimDelay(corpse.GetCorpseType() == CorpseType.ResurrectablePVP)) > Time.UnixTime) + return; + + if (!corpse.IsWithinDistInMap(GetPlayer(), 39, true)) + return; + + // resurrect + GetPlayer().ResurrectPlayer(GetPlayer().InBattleground() ? 1.0f : 0.5f); + + // spawn bones + GetPlayer().SpawnCorpseBones(); + } + + [WorldPacketHandler(ClientOpcodes.ResurrectResponse)] + void HandleResurrectResponse(ResurrectResponse packet) + { + if (GetPlayer().IsAlive()) + return; + + if (packet.Response != 0) // Accept = 0 Decline = 1 Timeout = 2 + { + GetPlayer().ClearResurrectRequestData(); // reject + return; + } + + if (!GetPlayer().IsRessurectRequestedBy(packet.Resurrecter)) + return; + + Player ressPlayer = Global.ObjAccessor.GetPlayer(GetPlayer(), packet.Resurrecter); + if (ressPlayer) + { + InstanceScript instance = ressPlayer.GetInstanceScript(); + if (instance != null) + { + if (instance.IsEncounterInProgress()) + { + if (instance.GetCombatResurrectionCharges() == 0) + return; + else + instance.UseCombatResurrection(); + } + } + } + + GetPlayer().ResurrectUsingRequestData(); + } + + [WorldPacketHandler(ClientOpcodes.StandStateChange)] + void HandleStandStateChange(StandStateChange packet) + { + GetPlayer().SetStandState(packet.StandState); + } + + void SendCharCreate(ResponseCodes result) + { + CreateChar response = new CreateChar(); + response.Code = result; + + SendPacket(response); + } + + void SendCharDelete(ResponseCodes result) + { + DeleteChar response = new DeleteChar(); + response.Code = result; + + SendPacket(response); + } + + void SendCharRename(ResponseCodes result, CharacterRenameInfo renameInfo) + { + CharacterRenameResult packet = new CharacterRenameResult(); + packet.Result = result; + packet.Name = renameInfo.NewName; + if (result == ResponseCodes.Success) + packet.Guid.Set(renameInfo.Guid); + + SendPacket(packet); + } + + void SendCharCustomize(ResponseCodes result, CharCustomizeInfo customizeInfo) + { + if (result == ResponseCodes.Success) + { + CharCustomizeResponse response = new CharCustomizeResponse(customizeInfo); + SendPacket(response); + } + else + { + CharCustomizeFailed failed = new CharCustomizeFailed(); + failed.Result = (byte)result; + failed.CharGUID = customizeInfo.CharGUID; + SendPacket(failed); + } + } + + void SendCharFactionChange(ResponseCodes result, CharRaceOrFactionChangeInfo factionChangeInfo) + { + CharFactionChangeResult packet = new CharFactionChangeResult(); + packet.Result = result; + packet.Guid = factionChangeInfo.Guid; + + if (result == ResponseCodes.Success) + { + packet.Display.HasValue = true; + packet.Display.Value.Name = factionChangeInfo.Name; + packet.Display.Value.SexID = (byte)factionChangeInfo.SexID; + packet.Display.Value.SkinID = factionChangeInfo.SkinID; + packet.Display.Value.HairColorID = factionChangeInfo.HairColorID; + packet.Display.Value.HairStyleID = factionChangeInfo.HairStyleID; + packet.Display.Value.FacialHairStyleID = factionChangeInfo.FacialHairStyleID; + packet.Display.Value.FaceID = factionChangeInfo.FaceID; + packet.Display.Value.RaceID = (byte)factionChangeInfo.RaceID; + packet.Display.Value.CustomDisplay = factionChangeInfo.CustomDisplay; + } + + SendPacket(packet); + } + + void SendSetPlayerDeclinedNamesResult(DeclinedNameResult result, ObjectGuid guid) + { + SetPlayerDeclinedNamesResult packet = new SetPlayerDeclinedNamesResult(); + packet.ResultCode = result; + packet.Player = guid; + + SendPacket(packet); + } + + void SendUndeleteCooldownStatusResponse(uint currentCooldown, uint maxCooldown) + { + UndeleteCooldownStatusResponse response = new UndeleteCooldownStatusResponse(); + response.OnCooldown = (currentCooldown > 0); + response.MaxCooldown = maxCooldown; + response.CurrentCooldown = currentCooldown; + + SendPacket(response); + } + + void SendUndeleteCharacterResponse(CharacterUndeleteResult result, CharacterUndeleteInfo undeleteInfo) + { + UndeleteCharacterResponse response = new UndeleteCharacterResponse(); + response.UndeleteInfo = undeleteInfo; + response.Result = result; + + SendPacket(response); + } + } + + public class LoginQueryHolder : SQLQueryHolder + { + public LoginQueryHolder(uint accountId, ObjectGuid guid) + { + m_accountId = accountId; + m_guid = guid; + } + + public void Initialize() + { + ulong lowGuid = m_guid.GetCounter(); + + PreparedStatement stmt = DB.Characters.GetPreparedStatement(CharStatements.SEL_CHARACTER); + stmt.AddValue(0, lowGuid); + SetQuery(PlayerLoginQueryLoad.From, stmt); + + stmt = DB.Characters.GetPreparedStatement(CharStatements.SEL_GROUP_MEMBER); + stmt.AddValue(0, lowGuid); + SetQuery(PlayerLoginQueryLoad.Group, stmt); + + stmt = DB.Characters.GetPreparedStatement(CharStatements.SEL_CHARACTER_INSTANCE); + stmt.AddValue(0, lowGuid); + SetQuery(PlayerLoginQueryLoad.BoundInstances, stmt); + + stmt = DB.Characters.GetPreparedStatement(CharStatements.SEL_CHARACTER_AURAS); + stmt.AddValue(0, lowGuid); + SetQuery(PlayerLoginQueryLoad.Auras, stmt); + + stmt = DB.Characters.GetPreparedStatement(CharStatements.SEL_CHARACTER_AURA_EFFECTS); + stmt.AddValue(0, lowGuid); + SetQuery(PlayerLoginQueryLoad.AuraEffects, stmt); + + stmt = DB.Characters.GetPreparedStatement(CharStatements.SEL_CHARACTER_SPELL); + stmt.AddValue(0, lowGuid); + SetQuery(PlayerLoginQueryLoad.Spells, stmt); + + stmt = DB.Characters.GetPreparedStatement(CharStatements.SEL_CHARACTER_QUESTSTATUS); + stmt.AddValue(0, lowGuid); + SetQuery(PlayerLoginQueryLoad.QuestStatus, stmt); + + stmt = DB.Characters.GetPreparedStatement(CharStatements.SEL_CHARACTER_QUESTSTATUS_OBJECTIVES); + stmt.AddValue(0, lowGuid); + SetQuery(PlayerLoginQueryLoad.QuestStatusObjectives, stmt); + + stmt = DB.Characters.GetPreparedStatement(CharStatements.SEL_CHARACTER_QUESTSTATUS_DAILY); + stmt.AddValue(0, lowGuid); + SetQuery(PlayerLoginQueryLoad.DailyQuestStatus, stmt); + + stmt = DB.Characters.GetPreparedStatement(CharStatements.SEL_CHARACTER_QUESTSTATUS_WEEKLY); + stmt.AddValue(0, lowGuid); + SetQuery(PlayerLoginQueryLoad.WeeklyQuestStatus, stmt); + + stmt = DB.Characters.GetPreparedStatement(CharStatements.SEL_CHARACTER_QUESTSTATUS_MONTHLY); + stmt.AddValue(0, lowGuid); + SetQuery(PlayerLoginQueryLoad.MonthlyQuestStatus, stmt); + + stmt = DB.Characters.GetPreparedStatement(CharStatements.SEL_CHARACTER_QUESTSTATUS_SEASONAL); + stmt.AddValue(0, lowGuid); + SetQuery(PlayerLoginQueryLoad.SeasonalQuestStatus, stmt); + + stmt = DB.Characters.GetPreparedStatement(CharStatements.SEL_CHARACTER_REPUTATION); + stmt.AddValue(0, lowGuid); + SetQuery(PlayerLoginQueryLoad.Reputation, stmt); + + stmt = DB.Characters.GetPreparedStatement(CharStatements.SEL_CHARACTER_INVENTORY); + stmt.AddValue(0, lowGuid); + SetQuery(PlayerLoginQueryLoad.Inventory, stmt); + + stmt = DB.Characters.GetPreparedStatement(CharStatements.SEL_ITEM_INSTANCE_ARTIFACT); + stmt.AddValue(0, lowGuid); + SetQuery(PlayerLoginQueryLoad.Artifacts, stmt); + + stmt = DB.Characters.GetPreparedStatement(CharStatements.SEL_CHAR_VOID_STORAGE); + stmt.AddValue(0, lowGuid); + SetQuery(PlayerLoginQueryLoad.VoidStorage, stmt); + + stmt = DB.Characters.GetPreparedStatement(CharStatements.SEL_CHARACTER_ACTIONS); + stmt.AddValue(0, lowGuid); + SetQuery(PlayerLoginQueryLoad.Actions, stmt); + + stmt = DB.Characters.GetPreparedStatement(CharStatements.SEL_CHARACTER_MAILCOUNT); + stmt.AddValue(0, lowGuid); + stmt.AddValue(1, Time.UnixTime); + SetQuery(PlayerLoginQueryLoad.MailCount, stmt); + + stmt = DB.Characters.GetPreparedStatement(CharStatements.SEL_CHARACTER_MAILDATE); + stmt.AddValue(0, lowGuid); + SetQuery(PlayerLoginQueryLoad.MailDate, stmt); + + stmt = DB.Characters.GetPreparedStatement(CharStatements.SEL_CHARACTER_SOCIALLIST); + stmt.AddValue(0, lowGuid); + SetQuery(PlayerLoginQueryLoad.SocialList, stmt); + + stmt = DB.Characters.GetPreparedStatement(CharStatements.SEL_CHARACTER_HOMEBIND); + stmt.AddValue(0, lowGuid); + SetQuery(PlayerLoginQueryLoad.HomeBind, stmt); + + stmt = DB.Characters.GetPreparedStatement(CharStatements.SEL_CHARACTER_SPELLCOOLDOWNS); + stmt.AddValue(0, lowGuid); + SetQuery(PlayerLoginQueryLoad.SpellCooldowns, stmt); + + stmt = DB.Characters.GetPreparedStatement(CharStatements.SEL_CHARACTER_SPELL_CHARGES); + stmt.AddValue(0, lowGuid); + SetQuery(PlayerLoginQueryLoad.SpellCharges, stmt); + + if (WorldConfig.GetBoolValue(WorldCfg.DeclinedNamesUsed)) + { + stmt = DB.Characters.GetPreparedStatement(CharStatements.SEL_CHARACTER_DECLINEDNAMES); + stmt.AddValue(0, lowGuid); + SetQuery(PlayerLoginQueryLoad.DeclinedNames, stmt); + } + + stmt = DB.Characters.GetPreparedStatement(CharStatements.SEL_GUILD_MEMBER); + stmt.AddValue(0, lowGuid); + SetQuery(PlayerLoginQueryLoad.Guild, stmt); + + stmt = DB.Characters.GetPreparedStatement(CharStatements.SEL_CHARACTER_ARENAINFO); + stmt.AddValue(0, lowGuid); + SetQuery(PlayerLoginQueryLoad.ArenaInfo, stmt); + + stmt = DB.Characters.GetPreparedStatement(CharStatements.SEL_CHARACTER_ACHIEVEMENTS); + stmt.AddValue(0, lowGuid); + SetQuery(PlayerLoginQueryLoad.Achievements, stmt); + + stmt = DB.Characters.GetPreparedStatement(CharStatements.SEL_CHARACTER_CRITERIAPROGRESS); + stmt.AddValue(0, lowGuid); + SetQuery(PlayerLoginQueryLoad.CriteriaProgress, stmt); + + stmt = DB.Characters.GetPreparedStatement(CharStatements.SEL_CHARACTER_EQUIPMENTSETS); + stmt.AddValue(0, lowGuid); + SetQuery(PlayerLoginQueryLoad.EquipmentSets, stmt); + + stmt = DB.Characters.GetPreparedStatement(CharStatements.SEL_CHARACTER_TRANSMOG_OUTFITS); + stmt.AddValue(0, lowGuid); + SetQuery(PlayerLoginQueryLoad.TransmogOutfits, stmt); + + stmt = DB.Characters.GetPreparedStatement(CharStatements.SEL_CHAR_CUF_PROFILES); + stmt.AddValue(0, lowGuid); + SetQuery(PlayerLoginQueryLoad.CufProfiles, stmt); + + stmt = DB.Characters.GetPreparedStatement(CharStatements.SEL_CHARACTER_BGDATA); + stmt.AddValue(0, lowGuid); + SetQuery(PlayerLoginQueryLoad.BgData, stmt); + + stmt = DB.Characters.GetPreparedStatement(CharStatements.SEL_CHARACTER_GLYPHS); + stmt.AddValue(0, lowGuid); + SetQuery(PlayerLoginQueryLoad.Glyphs, stmt); + + stmt = DB.Characters.GetPreparedStatement(CharStatements.SEL_CHARACTER_TALENTS); + stmt.AddValue(0, lowGuid); + SetQuery(PlayerLoginQueryLoad.Talents, stmt); + + stmt = DB.Characters.GetPreparedStatement(CharStatements.SEL_PLAYER_ACCOUNT_DATA); + stmt.AddValue(0, lowGuid); + SetQuery(PlayerLoginQueryLoad.AccountData, stmt); + + stmt = DB.Characters.GetPreparedStatement(CharStatements.SEL_CHARACTER_SKILLS); + stmt.AddValue(0, lowGuid); + SetQuery(PlayerLoginQueryLoad.Skills, stmt); + + stmt = DB.Characters.GetPreparedStatement(CharStatements.SEL_CHARACTER_RANDOMBG); + stmt.AddValue(0, lowGuid); + SetQuery(PlayerLoginQueryLoad.RandomBg, stmt); + + stmt = DB.Characters.GetPreparedStatement(CharStatements.SEL_CHARACTER_BANNED); + stmt.AddValue(0, lowGuid); + SetQuery(PlayerLoginQueryLoad.Banned, stmt); + + stmt = DB.Characters.GetPreparedStatement(CharStatements.SEL_CHARACTER_QUESTSTATUSREW); + stmt.AddValue(0, lowGuid); + SetQuery(PlayerLoginQueryLoad.QuestStatusRew, stmt); + + stmt = DB.Characters.GetPreparedStatement(CharStatements.SEL_ACCOUNT_INSTANCELOCKTIMES); + stmt.AddValue(0, m_accountId); + SetQuery(PlayerLoginQueryLoad.InstanceLockTimes, stmt); + + stmt = DB.Characters.GetPreparedStatement(CharStatements.SEL_PLAYER_CURRENCY); + stmt.AddValue(0, lowGuid); + SetQuery(PlayerLoginQueryLoad.Currency, stmt); + + stmt = DB.Characters.GetPreparedStatement(CharStatements.SEL_CORPSE_LOCATION); + stmt.AddValue(0, lowGuid); + SetQuery(PlayerLoginQueryLoad.CorpseLocation, stmt); + + stmt = DB.Characters.GetPreparedStatement(CharStatements.SEL_CHARACTER_GARRISON); + stmt.AddValue(0, lowGuid); + SetQuery(PlayerLoginQueryLoad.Garrison, stmt); + + stmt = DB.Characters.GetPreparedStatement(CharStatements.SEL_CHARACTER_GARRISON_BLUEPRINTS); + stmt.AddValue(0, lowGuid); + SetQuery(PlayerLoginQueryLoad.GarrisonBlueprints, stmt); + + stmt = DB.Characters.GetPreparedStatement(CharStatements.SEL_CHARACTER_GARRISON_BUILDINGS); + stmt.AddValue(0, lowGuid); + SetQuery(PlayerLoginQueryLoad.GarrisonBuildings, stmt); + + stmt = DB.Characters.GetPreparedStatement(CharStatements.SEL_CHARACTER_GARRISON_FOLLOWERS); + stmt.AddValue(0, lowGuid); + SetQuery(PlayerLoginQueryLoad.GarrisonFollowers, stmt); + + stmt = DB.Characters.GetPreparedStatement(CharStatements.SEL_CHARACTER_GARRISON_FOLLOWER_ABILITIES); + stmt.AddValue(0, lowGuid); + SetQuery(PlayerLoginQueryLoad.GarrisonFollowerAbilities, stmt); + } + + public ObjectGuid GetGuid() { return m_guid; } + + uint GetAccountId() { return m_accountId; } + + uint m_accountId; + ObjectGuid m_guid; + } + + // used at player loading query list preparing, and later result selection + public enum PlayerLoginQueryLoad + { + From, + Group, + BoundInstances, + Auras, + AuraEffects, + Spells, + QuestStatus, + QuestStatusObjectives, + DailyQuestStatus, + Reputation, + Inventory, + Artifacts, + Actions, + MailCount, + MailDate, + SocialList, + HomeBind, + SpellCooldowns, + SpellCharges, + DeclinedNames, + Guild, + ArenaInfo, + Achievements, + CriteriaProgress, + EquipmentSets, + TransmogOutfits, + BgData, + Glyphs, + Talents, + AccountData, + Skills, + WeeklyQuestStatus, + RandomBg, + Banned, + QuestStatusRew, + InstanceLockTimes, + SeasonalQuestStatus, + MonthlyQuestStatus, + VoidStorage, + Currency, + CufProfiles, + CorpseLocation, + Garrison, + GarrisonBlueprints, + GarrisonBuildings, + GarrisonFollowers, + GarrisonFollowerAbilities, + Max + } +} diff --git a/Game/Handlers/ChatHandler.cs b/Game/Handlers/ChatHandler.cs new file mode 100644 index 000000000..259416756 --- /dev/null +++ b/Game/Handlers/ChatHandler.cs @@ -0,0 +1,668 @@ +/* + * Copyright (C) 2012-2017 CypherCore + * + * 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, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +using Framework.Constants; +using Game.Chat; +using Game.DataStorage; +using Game.Entities; +using Game.Groups; +using Game.Guilds; +using Game.Network; +using Game.Network.Packets; +using System.Collections.Generic; +using System.Linq; + +namespace Game +{ + public partial class WorldSession + { + [WorldPacketHandler(ClientOpcodes.ChatMessageGuild)] + [WorldPacketHandler(ClientOpcodes.ChatMessageOfficer)] + [WorldPacketHandler(ClientOpcodes.ChatMessageParty)] + [WorldPacketHandler(ClientOpcodes.ChatMessageRaid)] + [WorldPacketHandler(ClientOpcodes.ChatMessageRaidWarning)] + [WorldPacketHandler(ClientOpcodes.ChatMessageSay)] + [WorldPacketHandler(ClientOpcodes.ChatMessageYell)] + [WorldPacketHandler(ClientOpcodes.ChatMessageInstanceChat)] + void HandleChatMessage(ChatMessage packet) + { + ChatMsg type; + + switch (packet.GetOpcode()) + { + case ClientOpcodes.ChatMessageSay: + type = ChatMsg.Say; + break; + case ClientOpcodes.ChatMessageYell: + type = ChatMsg.Yell; + break; + case ClientOpcodes.ChatMessageGuild: + type = ChatMsg.Guild; + break; + case ClientOpcodes.ChatMessageOfficer: + type = ChatMsg.Officer; + break; + case ClientOpcodes.ChatMessageParty: + type = ChatMsg.Party; + break; + case ClientOpcodes.ChatMessageRaid: + type = ChatMsg.Raid; + break; + case ClientOpcodes.ChatMessageRaidWarning: + type = ChatMsg.RaidWarning; + break; + case ClientOpcodes.ChatMessageInstanceChat: + type = ChatMsg.InstanceChat; + break; + default: + Log.outError(LogFilter.Network, "HandleMessagechatOpcode : Unknown chat opcode ({0})", packet.GetOpcode()); + return; + } + + HandleChat(type, packet.Language, packet.Text); + } + + [WorldPacketHandler(ClientOpcodes.ChatMessageWhisper)] + void HandleChatMessageWhisper(ChatMessageWhisper packet) + { + HandleChat(ChatMsg.Whisper, packet.Language, packet.Text, packet.Target); + } + + [WorldPacketHandler(ClientOpcodes.ChatMessageChannel)] + void HandleChatMessageChannel(ChatMessageChannel packet) + { + HandleChat(ChatMsg.Channel, packet.Language, packet.Text, packet.Target); + } + + [WorldPacketHandler(ClientOpcodes.ChatMessageEmote)] + void HandleChatMessageEmote(ChatMessageEmote packet) + { + HandleChat(ChatMsg.Emote, Language.Universal, packet.Text); + } + + void HandleChat(ChatMsg type, Language lang, string msg, string target = "") + { + Player sender = GetPlayer(); + + if (lang == Language.Universal && type != ChatMsg.Emote) + { + Log.outError(LogFilter.Network, "CMSG_MESSAGECHAT: Possible hacking-attempt: {0} tried to send a message in universal language", GetPlayerInfo()); + SendNotification(CypherStrings.UnknownLanguage); + return; + } + + // prevent talking at unknown language (cheating) + LanguageDesc langDesc = ObjectManager.GetLanguageDescByID(lang); + if (langDesc == null) + { + SendNotification(CypherStrings.UnknownLanguage); + return; + } + + if (langDesc.skill_id != 0 && !sender.HasSkill((SkillType)langDesc.skill_id)) + { + // also check SPELL_AURA_COMPREHEND_LANGUAGE (client offers option to speak in that language) + var langAuras = sender.GetAuraEffectsByType(AuraType.ComprehendLanguage); + bool foundAura = false; + foreach (var eff in langAuras) + { + if (eff.GetMiscValue() == (int)lang) + { + foundAura = true; + break; + } + } + if (!foundAura) + { + SendNotification(CypherStrings.NotLearnedLanguage); + return; + } + } + + // send in universal language if player in .gm on mode (ignore spell effects) + if (sender.IsGameMaster()) + lang = Language.Universal; + else + { + // send in universal language in two side iteration allowed mode + if (HasPermission(RBACPermissions.TwoSideInteractionChat)) + lang = Language.Universal; + else + { + switch (type) + { + case ChatMsg.Party: + case ChatMsg.Raid: + case ChatMsg.RaidWarning: + // allow two side chat at group channel if two side group allowed + if (WorldConfig.GetBoolValue(WorldCfg.AllowTwoSideInteractionGroup)) + lang = Language.Universal; + break; + case ChatMsg.Guild: + case ChatMsg.Officer: + // allow two side chat at guild channel if two side guild allowed + if (WorldConfig.GetBoolValue(WorldCfg.AllowTwoSideInteractionGuild)) + lang = Language.Universal; + break; + } + } + // but overwrite it by SPELL_AURA_MOD_LANGUAGE auras (only single case used) + var ModLangAuras = sender.GetAuraEffectsByType(AuraType.ModLanguage); + if (!ModLangAuras.Empty()) + lang = (Language)ModLangAuras.FirstOrDefault().GetMiscValue(); + } + + if (!sender.CanSpeak()) + { + string timeStr = Time.secsToTimeString((ulong)(m_muteTime - Time.UnixTime)); + SendNotification(CypherStrings.WaitBeforeSpeaking, timeStr); + return; + } + + if (sender.HasAura(1852) && type != ChatMsg.Whisper) + { + SendNotification(Global.ObjectMgr.GetCypherString(CypherStrings.GmSilence), sender.GetName()); + return; + } + + if (string.IsNullOrEmpty(msg)) + return; + + if (new CommandHandler(this).ParseCommand(msg)) + return; + + switch (type) + { + case ChatMsg.Say: + // Prevent cheating + if (!sender.IsAlive()) + return; + + if (sender.getLevel() < WorldConfig.GetIntValue(WorldCfg.ChatSayLevelReq)) + { + SendNotification(Global.ObjectMgr.GetCypherString(CypherStrings.SayReq), WorldConfig.GetIntValue(WorldCfg.ChatSayLevelReq)); + return; + } + + sender.Say(msg, lang); + break; + case ChatMsg.Emote: + // Prevent cheating + if (!sender.IsAlive()) + return; + + if (sender.getLevel() < WorldConfig.GetIntValue(WorldCfg.ChatEmoteLevelReq)) + { + SendNotification(Global.ObjectMgr.GetCypherString(CypherStrings.SayReq), WorldConfig.GetIntValue(WorldCfg.ChatEmoteLevelReq)); + return; + } + + sender.TextEmote(msg); + break; + case ChatMsg.Yell: + // Prevent cheating + if (!sender.IsAlive()) + return; + + if (sender.getLevel() < WorldConfig.GetIntValue(WorldCfg.ChatYellLevelReq)) + { + SendNotification(Global.ObjectMgr.GetCypherString(CypherStrings.SayReq), WorldConfig.GetIntValue(WorldCfg.ChatYellLevelReq)); + return; + } + + sender.Yell(msg, lang); + break; + case ChatMsg.Whisper: + // @todo implement cross realm whispers (someday) + ExtendedPlayerName extName = ObjectManager.ExtractExtendedPlayerName(target); + + if (!ObjectManager.NormalizePlayerName(ref extName.Name)) + { + SendChatPlayerNotfoundNotice(target); + break; + } + + Player receiver = Global.ObjAccessor.FindPlayerByName(extName.Name); + if (!receiver || (lang != Language.Addon && !receiver.isAcceptWhispers() && receiver.GetSession().HasPermission(RBACPermissions.CanFilterWhispers) && !receiver.IsInWhisperWhiteList(sender.GetGUID()))) + { + SendChatPlayerNotfoundNotice(target); + return; + } + if (!sender.IsGameMaster() && sender.getLevel() < WorldConfig.GetIntValue(WorldCfg.ChatWhisperLevelReq) && !receiver.IsInWhisperWhiteList(sender.GetGUID())) + { + SendNotification(Global.ObjectMgr.GetCypherString(CypherStrings.WhisperReq), WorldConfig.GetIntValue(WorldCfg.ChatWhisperLevelReq)); + return; + } + + if (GetPlayer().GetTeam() != receiver.GetTeam() && !HasPermission(RBACPermissions.TwoSideInteractionChat) && !receiver.IsInWhisperWhiteList(sender.GetGUID())) + { + SendChatPlayerNotfoundNotice(target); + return; + } + + if (GetPlayer().HasAura(1852) && !receiver.IsGameMaster()) + { + SendNotification(Global.ObjectMgr.GetCypherString(CypherStrings.GmSilence), GetPlayer().GetName()); + return; + } + + if (receiver.getLevel() < WorldConfig.GetIntValue(WorldCfg.ChatWhisperLevelReq) || + (HasPermission(RBACPermissions.CanFilterWhispers) && !sender.isAcceptWhispers() && !sender.IsInWhisperWhiteList(receiver.GetGUID()))) + sender.AddWhisperWhiteList(receiver.GetGUID()); + + GetPlayer().Whisper(msg, lang, receiver); + break; + case ChatMsg.Party: + { + // if player is in Battleground, he cannot say to Battlegroundmembers by /p + Group group = GetPlayer().GetOriginalGroup(); + if (!group) + { + group = GetPlayer().GetGroup(); + if (!group || group.isBGGroup()) + return; + } + + if (group.IsLeader(GetPlayer().GetGUID())) + type = ChatMsg.PartyLeader; + + Global.ScriptMgr.OnPlayerChat(GetPlayer(), type, lang, msg, group); + + ChatPkt data = new ChatPkt(); + data.Initialize(type, lang, sender, null, msg); + group.BroadcastPacket(data, false, group.GetMemberGroup(GetPlayer().GetGUID())); + } + break; + case ChatMsg.Guild: + if (GetPlayer().GetGuildId() != 0) + { + Guild guild = Global.GuildMgr.GetGuildById(GetPlayer().GetGuildId()); + if (guild) + { + Global.ScriptMgr.OnPlayerChat(GetPlayer(), type, lang, msg, guild); + + guild.BroadcastToGuild(this, false, msg, lang == Language.Addon ? Language.Addon : Language.Universal); + } + } + break; + case ChatMsg.Officer: + if (GetPlayer().GetGuildId() != 0) + { + Guild guild = Global.GuildMgr.GetGuildById(GetPlayer().GetGuildId()); + if (guild) + { + Global.ScriptMgr.OnPlayerChat(GetPlayer(), type, lang, msg, guild); + + guild.BroadcastToGuild(this, true, msg, lang == Language.Addon ? Language.Addon : Language.Universal); + } + } + break; + case ChatMsg.Raid: + { + Group group = GetPlayer().GetGroup(); + if (!group || !group.isRaidGroup() || group.isBGGroup()) + return; + + if (group.IsLeader(GetPlayer().GetGUID())) + type = ChatMsg.RaidLeader; + + Global.ScriptMgr.OnPlayerChat(GetPlayer(), type, lang, msg, group); + + ChatPkt data = new ChatPkt(); + data.Initialize(type, lang, sender, null, msg); + group.BroadcastPacket(data, false); + } + break; + case ChatMsg.RaidWarning: + { + Group group = GetPlayer().GetGroup(); + if (!group || !group.isRaidGroup() || !(group.IsLeader(GetPlayer().GetGUID()) || group.IsAssistant(GetPlayer().GetGUID())) || group.isBGGroup()) + return; + + Global.ScriptMgr.OnPlayerChat(GetPlayer(), type, lang, msg, group); + + ChatPkt data = new ChatPkt(); + //in Battleground, raid warning is sent only to players in Battleground - code is ok + data.Initialize(ChatMsg.RaidWarning, lang, sender, null, msg); + group.BroadcastPacket(data, false); + } + break; + case ChatMsg.Channel: + if (!HasPermission(RBACPermissions.SkipCheckChatChannelReq)) + { + if (GetPlayer().getLevel() < WorldConfig.GetIntValue(WorldCfg.ChatChannelLevelReq)) + { + SendNotification(Global.ObjectMgr.GetCypherString(CypherStrings.ChannelReq), WorldConfig.GetIntValue(WorldCfg.ChatChannelLevelReq)); + return; + } + } + Channel chn = ChannelManager.GetChannelForPlayerByNamePart(target, sender); + if (chn != null) + { + Global.ScriptMgr.OnPlayerChat(GetPlayer(), type, lang, msg, chn); + chn.Say(GetPlayer().GetGUID(), msg, lang); + } + break; + case ChatMsg.InstanceChat: + { + Group group = GetPlayer().GetGroup(); + if (!group) + return; + + if (group.IsLeader(GetPlayer().GetGUID())) + type = ChatMsg.InstanceChatLeader; + + Global.ScriptMgr.OnPlayerChat(GetPlayer(), type, lang, msg, group); + + ChatPkt packet = new ChatPkt(); + packet.Initialize(type, lang, sender, null, msg); + group.BroadcastPacket(packet, false); + break; + } + default: + Log.outError(LogFilter.ChatSystem, "CHAT: unknown message type {0}, lang: {1}", type, lang); + break; + } + } + + [WorldPacketHandler(ClientOpcodes.ChatAddonMessageGuild)] + [WorldPacketHandler(ClientOpcodes.ChatAddonMessageOfficer)] + [WorldPacketHandler(ClientOpcodes.ChatAddonMessageParty)] + [WorldPacketHandler(ClientOpcodes.ChatAddonMessageRaid)] + [WorldPacketHandler(ClientOpcodes.ChatAddonMessageInstanceChat)] + void HandleChatAddonMessage(ChatAddonMessage packet) + { + ChatMsg type; + + switch (packet.GetOpcode()) + { + case ClientOpcodes.ChatAddonMessageGuild: + type = ChatMsg.Guild; + break; + case ClientOpcodes.ChatAddonMessageOfficer: + type = ChatMsg.Officer; + break; + case ClientOpcodes.ChatAddonMessageParty: + type = ChatMsg.Party; + break; + case ClientOpcodes.ChatAddonMessageRaid: + type = ChatMsg.Raid; + break; + case ClientOpcodes.ChatAddonMessageInstanceChat: + type = ChatMsg.InstanceChat; + break; + default: + Log.outError(LogFilter.Network, "HandleChatAddonMessage: Unknown addon chat opcode ({0})", packet.GetOpcode()); + return; + } + + HandleChatAddon(type, packet.Prefix, packet.Text); + } + + [WorldPacketHandler(ClientOpcodes.ChatAddonMessageWhisper)] + void HandleChatAddonMessageWhisper(ChatAddonMessageWhisper packet) + { + HandleChatAddon(ChatMsg.Whisper, packet.Prefix, packet.Text, packet.Target); + } + + [WorldPacketHandler(ClientOpcodes.ChatAddonMessageChannel)] + void HandleChatAddonMessageChannel(ChatAddonMessageChannel chatAddonMessageChannel) + { + HandleChatAddon(ChatMsg.Channel, chatAddonMessageChannel.Prefix, chatAddonMessageChannel.Text, chatAddonMessageChannel.Target); + } + + void HandleChatAddon(ChatMsg type, string prefix, string text, string target = "") + { + Player sender = GetPlayer(); + + if (string.IsNullOrEmpty(prefix) || prefix.Length > 16) + return; + + // Disabled addon channel? + if (!WorldConfig.GetBoolValue(WorldCfg.AddonChannel)) + return; + + switch (type) + { + case ChatMsg.Guild: + case ChatMsg.Officer: + if (sender.GetGuildId() != 0) + { + Guild guild = Global.GuildMgr.GetGuildById(sender.GetGuildId()); + if (guild) + guild.BroadcastAddonToGuild(this, type == ChatMsg.Officer, text, prefix); + } + break; + case ChatMsg.Whisper: + /// @todo implement cross realm whispers (someday) + ExtendedPlayerName extName = ObjectManager.ExtractExtendedPlayerName(target); + + if (!ObjectManager.NormalizePlayerName(ref extName.Name)) + break; + + Player receiver = Global.ObjAccessor.FindPlayerByName(extName.Name); + if (!receiver) + break; + + sender.WhisperAddon(text, prefix, receiver); + break; + // Messages sent to "RAID" while in a party will get delivered to "PARTY" + case ChatMsg.Party: + case ChatMsg.Raid: + case ChatMsg.InstanceChat: + { + Group group = null; + int subGroup = -1; + if (type != ChatMsg.InstanceChat) + group = sender.GetOriginalGroup(); + + if (!group) + { + group = sender.GetGroup(); + if (!group) + break; + + if (type == ChatMsg.Party) + subGroup = sender.GetSubGroup(); + } + + ChatPkt data = new ChatPkt(); + data.Initialize(type, Language.Addon, sender, null, text, 0, "", LocaleConstant.enUS, prefix); + group.BroadcastAddonMessagePacket(data, prefix, true, subGroup, sender.GetGUID()); + break; + } + case ChatMsg.Channel: + Channel chn = ChannelManager.GetChannelForPlayerByNamePart(target, sender); + if (chn != null) + chn.AddonSay(sender.GetGUID(), prefix, text); + break; + + default: + Log.outError(LogFilter.Server, "HandleAddonMessagechat: unknown addon message type {0}", type); + break; + } + } + + [WorldPacketHandler(ClientOpcodes.ChatMessageAfk)] + void HandleChatMessageAFK(ChatMessageAFK packet) + { + Player sender = GetPlayer(); + + if (sender.IsInCombat()) + return; + + if (sender.HasAura(1852)) + { + SendNotification(CypherStrings.GmSilence, sender.GetName()); + return; + } + + if (sender.isAFK()) // Already AFK + { + if (string.IsNullOrEmpty(packet.Text)) + sender.ToggleAFK(); // Remove AFK + else + sender.autoReplyMsg = packet.Text; // Update message + } + else // New AFK mode + { + sender.autoReplyMsg = string.IsNullOrEmpty(packet.Text) ? Global.ObjectMgr.GetCypherString(CypherStrings.PlayerAfkDefault) : packet.Text; + + if (sender.isDND()) + sender.ToggleDND(); + + sender.ToggleAFK(); + } + + Global.ScriptMgr.OnPlayerChat(sender, ChatMsg.Afk, Language.Universal, packet.Text); + } + + [WorldPacketHandler(ClientOpcodes.ChatMessageDnd)] + void HandleChatMessageDND(ChatMessageDND packet) + { + Player sender = GetPlayer(); + + if (sender.IsInCombat()) + return; + + if (sender.HasAura(1852)) + { + SendNotification(CypherStrings.GmSilence, sender.GetName()); + return; + } + + if (sender.isDND()) // Already DND + { + if (string.IsNullOrEmpty(packet.Text)) + sender.ToggleDND(); // Remove DND + else + sender.autoReplyMsg = packet.Text; // Update message + } + else // New DND mode + { + sender.autoReplyMsg = string.IsNullOrEmpty(packet.Text) ? Global.ObjectMgr.GetCypherString(CypherStrings.PlayerDndDefault) : packet.Text; + + if (sender.isAFK()) + sender.ToggleAFK(); + + sender.ToggleDND(); + } + + Global.ScriptMgr.OnPlayerChat(sender, ChatMsg.Dnd, Language.Universal, packet.Text); + } + + [WorldPacketHandler(ClientOpcodes.Emote)] + void HandleEmote(EmoteClient packet) + { + if (!GetPlayer().IsAlive() || GetPlayer().HasUnitState(UnitState.Died)) + return; + + Global.ScriptMgr.OnPlayerClearEmote(GetPlayer()); + if (GetPlayer().GetUInt32Value(UnitFields.NpcEmotestate) != 0) + GetPlayer().SetUInt32Value(UnitFields.NpcEmotestate, 0); + } + + [WorldPacketHandler(ClientOpcodes.SendTextEmote)] + void HandleTextEmote(CTextEmote packet) + { + if (!GetPlayer().IsAlive()) + return; + + if (!GetPlayer().CanSpeak()) + { + string timeStr = Time.secsToTimeString((ulong)(m_muteTime - Time.UnixTime)); + SendNotification(CypherStrings.WaitBeforeSpeaking, timeStr); + return; + } + + Global.ScriptMgr.OnPlayerTextEmote(GetPlayer(), (uint)packet.SoundIndex, (uint)packet.EmoteID, packet.Target); + + EmotesTextRecord em = CliDB.EmotesTextStorage.LookupByKey(packet.EmoteID); + if (em == null) + return; + + uint emote_anim = em.EmoteID; + + switch ((Emote)emote_anim) + { + case Emote.StateSleep: + case Emote.StateSit: + case Emote.StateKneel: + case Emote.OneshotNone: + break; + case Emote.StateDance: + case Emote.StateRead: + GetPlayer().SetUInt32Value(UnitFields.NpcEmotestate, emote_anim); + break; + default: + // Only allow text-emotes for "dead" entities (feign death included) + if (GetPlayer().HasUnitState(UnitState.Died)) + break; + GetPlayer().HandleEmoteCommand((Emote)emote_anim); + break; + } + + STextEmote textEmote = new STextEmote(); + textEmote.SourceGUID = GetPlayer().GetGUID(); + textEmote.SourceAccountGUID = GetAccountGUID(); + textEmote.TargetGUID = packet.Target; + textEmote.EmoteID = packet.EmoteID; + textEmote.SoundIndex = packet.SoundIndex; + GetPlayer().SendMessageToSetInRange(textEmote, WorldConfig.GetFloatValue(WorldCfg.ListenRangeTextemote), true); + + Unit unit = Global.ObjAccessor.GetUnit(GetPlayer(), packet.Target); + + GetPlayer().UpdateCriteria(CriteriaTypes.DoEmote, (uint)packet.SoundIndex, 0, 0, unit); + + // Send scripted event call + if (unit) + { + Creature creature = unit.ToCreature(); + if (creature) + creature.GetAI().ReceiveEmote(GetPlayer(), (TextEmotes)packet.SoundIndex); + } + } + + [WorldPacketHandler(ClientOpcodes.ChatReportIgnored)] + void HandleChatIgnoredOpcode(ChatReportIgnored packet) + { + Player player = Global.ObjAccessor.FindPlayer(packet.IgnoredGUID); + if (!player || player.GetSession() == null) + return; + + ChatPkt data = new ChatPkt(); + data.Initialize(ChatMsg.Ignored, Language.Universal, GetPlayer(), GetPlayer(), GetPlayer().GetName()); + player.SendPacket(data); + } + + void SendChatPlayerNotfoundNotice(string name) + { + SendPacket(new ChatPlayerNotfound(name)); + } + + void SendPlayerAmbiguousNotice(string name) + { + SendPacket(new ChatPlayerAmbiguous(name)); + } + + void SendChatRestricted(ChatRestrictionType restriction) + { + SendPacket(new ChatRestricted(restriction)); + } + + } +} diff --git a/Game/Handlers/CollectionsHandler.cs b/Game/Handlers/CollectionsHandler.cs new file mode 100644 index 000000000..0dcffedac --- /dev/null +++ b/Game/Handlers/CollectionsHandler.cs @@ -0,0 +1,50 @@ +/* + * Copyright (C) 2012-2017 CypherCore + * + * 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, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +using Framework.Constants; +using Game.Network; +using Game.Network.Packets; + +namespace Game +{ + public partial class WorldSession + { + [WorldPacketHandler(ClientOpcodes.CollectionItemSetFavorite)] + void HandleCollectionItemSetFavorite(CollectionItemSetFavorite collectionItemSetFavorite) + { + switch (collectionItemSetFavorite.Type) + { + case CollectionType.Toybox: + GetCollectionMgr().ToySetFavorite(collectionItemSetFavorite.ID, collectionItemSetFavorite.IsFavorite); + break; + case CollectionType.Appearance: + { + var pair = GetCollectionMgr().HasItemAppearance(collectionItemSetFavorite.ID); + if (!pair.Item1 || pair.Item2) + return; + + GetCollectionMgr().SetAppearanceIsFavorite(collectionItemSetFavorite.ID, collectionItemSetFavorite.IsFavorite); + break; + } + case CollectionType.TransmogSet: + break; + default: + break; + } + } + } +} diff --git a/Game/Handlers/CombatHandler.cs b/Game/Handlers/CombatHandler.cs new file mode 100644 index 000000000..c5351f63f --- /dev/null +++ b/Game/Handlers/CombatHandler.cs @@ -0,0 +1,89 @@ +/* + * Copyright (C) 2012-2017 CypherCore + * + * 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, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +using Framework.Constants; +using Game.DataStorage; +using Game.Entities; +using Game.Network; +using Game.Network.Packets; +using System; +using System.Diagnostics.Contracts; + +namespace Game +{ + public partial class WorldSession + { + [WorldPacketHandler(ClientOpcodes.AttackSwing, Processing = PacketProcessing.Inplace)] + void HandleAttackSwing(AttackSwing packet) + { + Unit enemy = Global.ObjAccessor.GetUnit(GetPlayer(), packet.Victim); + if (!enemy) + { + // stop attack state at client + SendAttackStop(null); + return; + } + + if (!GetPlayer().IsValidAttackTarget(enemy)) + { + // stop attack state at client + SendAttackStop(enemy); + return; + } + + //! Client explicitly checks the following before sending CMSG_ATTACKSWING packet, + //! so we'll place the same check here. Note that it might be possible to reuse this snippet + //! in other places as well. + Vehicle vehicle = GetPlayer().GetVehicle(); + if (vehicle) + { + VehicleSeatRecord seat = vehicle.GetSeatForPassenger(GetPlayer()); + Contract.Assert(seat != null); + if (!seat.Flags[0].HasAnyFlag((uint)VehicleSeatFlags.CanAttack)) + { + SendAttackStop(enemy); + return; + } + } + + GetPlayer().Attack(enemy, true); + } + + [WorldPacketHandler(ClientOpcodes.AttackStop, Processing = PacketProcessing.Inplace)] + void HandleAttackStop(AttackStop packet) + { + GetPlayer().AttackStop(); + } + + [WorldPacketHandler(ClientOpcodes.SetSheathed, Processing = PacketProcessing.Inplace)] + void HandleSetSheathed(SetSheathed packet) + { + if (packet.CurrentSheathState >= (int)SheathState.Max) + { + Log.outError(LogFilter.Network, "Unknown sheath state {0} ??", packet.CurrentSheathState); + return; + } + + GetPlayer().SetSheath((SheathState)packet.CurrentSheathState); + } + + void SendAttackStop(Unit enemy) + { + SendPacket(new SAttackStop(GetPlayer(), enemy)); + } + } +} diff --git a/Game/Handlers/DuelHandler.cs b/Game/Handlers/DuelHandler.cs new file mode 100644 index 000000000..3f4735a93 --- /dev/null +++ b/Game/Handlers/DuelHandler.cs @@ -0,0 +1,104 @@ +/* + * Copyright (C) 2012-2017 CypherCore + * + * 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, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +using Framework.Constants; +using Game.Entities; +using Game.Network; +using Game.Network.Packets; + +namespace Game +{ + public partial class WorldSession + { + [WorldPacketHandler(ClientOpcodes.CanDuel)] + void HandleCanDuel(CanDuel packet) + { + Player player = Global.ObjAccessor.FindPlayer(packet.TargetGUID); + + if (!player) + return; + + CanDuelResult response = new CanDuelResult(); + response.TargetGUID = packet.TargetGUID; + response.Result = player.duel == null; + SendPacket(response); + + if (response.Result) + { + if (GetPlayer().IsMounted()) + GetPlayer().CastSpell(player, 62875); + else + GetPlayer().CastSpell(player, 7266); + } + } + + [WorldPacketHandler(ClientOpcodes.DuelResponse)] + void HandleDuelResponse(DuelResponse duelResponse) + { + if (duelResponse.Accepted) + HandleDuelAccepted(); + else + HandleDuelCancelled(); + } + + void HandleDuelAccepted() + { + if (GetPlayer().duel == null) // ignore accept from duel-sender + return; + + Player player = GetPlayer(); + Player plTarget = player.duel.opponent; + + if (player == player.duel.initiator || !plTarget || player == plTarget || player.duel.startTime != 0 || plTarget.duel.startTime != 0) + return; + + Log.outDebug(LogFilter.Network, "Player 1 is: {0} ({1})", player.GetGUID().ToString(), player.GetName()); + Log.outDebug(LogFilter.Network, "Player 2 is: {0} ({1})", plTarget.GetGUID().ToString(), plTarget.GetName()); + + long now = Time.UnixTime; + player.duel.startTimer = now; + plTarget.duel.startTimer = now; + + DuelCountdown packet = new DuelCountdown(3000); + packet.Write(); + + player.SendPacket(packet, false); + plTarget.SendPacket(packet, false); + } + + void HandleDuelCancelled() + { + // no duel requested + if (GetPlayer().duel == null) + return; + + // player surrendered in a duel using /forfeit + if (GetPlayer().duel.startTime != 0) + { + GetPlayer().CombatStopWithPets(true); + if (GetPlayer().duel.opponent) + GetPlayer().duel.opponent.CombatStopWithPets(true); + + GetPlayer().CastSpell(GetPlayer(), 7267, true); // beg + GetPlayer().DuelComplete(DuelCompleteType.Won); + return; + } + + GetPlayer().DuelComplete(DuelCompleteType.Interrupted); + } + } +} diff --git a/Game/Handlers/GarrisonHandler.cs b/Game/Handlers/GarrisonHandler.cs new file mode 100644 index 000000000..fecaa8bf4 --- /dev/null +++ b/Game/Handlers/GarrisonHandler.cs @@ -0,0 +1,73 @@ +/* + * Copyright (C) 2012-2017 CypherCore + * + * 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, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +using Framework.Constants; +using Game.Garrisons; +using Game.Network; +using Game.Network.Packets; + +namespace Game +{ + public partial class WorldSession + { + [WorldPacketHandler(ClientOpcodes.GetGarrisonInfo)] + void HandleGetGarrisonInfo(GetGarrisonInfo getGarrisonInfo) + { + Garrison garrison = _player.GetGarrison(); + if (garrison != null) + garrison.SendInfo(); + } + + [WorldPacketHandler(ClientOpcodes.GarrisonPurchaseBuilding)] + void HandleGarrisonPurchaseBuilding(GarrisonPurchaseBuilding garrisonPurchaseBuilding) + { + if (!_player.GetNPCIfCanInteractWith(garrisonPurchaseBuilding.NpcGUID, NPCFlags.GarrisonArchitect)) + return; + + Garrison garrison = _player.GetGarrison(); + if (garrison != null) + garrison.PlaceBuilding(garrisonPurchaseBuilding.PlotInstanceID, garrisonPurchaseBuilding.BuildingID); + } + + [WorldPacketHandler(ClientOpcodes.GarrisonCancelConstruction)] + void HandleGarrisonCancelConstruction(GarrisonCancelConstruction garrisonCancelConstruction) + { + if (!_player.GetNPCIfCanInteractWith(garrisonCancelConstruction.NpcGUID, NPCFlags.GarrisonArchitect)) + return; + + Garrison garrison = _player.GetGarrison(); + if (garrison != null) + garrison.CancelBuildingConstruction(garrisonCancelConstruction.PlotInstanceID); + } + + [WorldPacketHandler(ClientOpcodes.GarrisonRequestBlueprintAndSpecializationData)] + void HandleGarrisonRequestBlueprintAndSpecializationData(GarrisonRequestBlueprintAndSpecializationData garrisonRequestBlueprintAndSpecializationData) + { + Garrison garrison = _player.GetGarrison(); + if (garrison != null) + garrison.SendBlueprintAndSpecializationData(); + } + + [WorldPacketHandler(ClientOpcodes.GarrisonGetBuildingLandmarks)] + void HandleGarrisonGetBuildingLandmarks(GarrisonGetBuildingLandmarks garrisonGetBuildingLandmarks) + { + Garrison garrison = _player.GetGarrison(); + if (garrison != null) + garrison.SendBuildingLandmarks(_player); + } + } +} diff --git a/Game/Handlers/GroupHandler.cs b/Game/Handlers/GroupHandler.cs new file mode 100644 index 000000000..81855ca9a --- /dev/null +++ b/Game/Handlers/GroupHandler.cs @@ -0,0 +1,655 @@ +/* + * Copyright (C) 2012-2017 CypherCore + * + * 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, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +using Framework.Constants; +using Game.Entities; +using Game.Groups; +using Game.Network; +using Game.Network.Packets; +using System.Diagnostics.Contracts; + +namespace Game +{ + public partial class WorldSession + { + public void SendPartyResult(PartyOperation operation, string member, PartyResult res, uint val = 0) + { + PartyCommandResult packet = new PartyCommandResult(); + + packet.Name = member; + packet.Command = (byte)operation; + packet.Result = (byte)res; + packet.ResultData = val; + packet.ResultGUID = ObjectGuid.Empty; + + SendPacket(packet); + } + + [WorldPacketHandler(ClientOpcodes.PartyInvite)] + void HandlePartyInvite(PartyInviteClient packet) + { + Player player = Global.ObjAccessor.FindPlayerByName(packet.TargetName); + + // no player + if (!player) + { + SendPartyResult(PartyOperation.Invite, packet.TargetName, PartyResult.BadPlayerNameS); + return; + } + + // player trying to invite himself (most likely cheating) + if (player == GetPlayer()) + { + SendPartyResult(PartyOperation.Invite, player.GetName(), PartyResult.BadPlayerNameS); + return; + } + + // restrict invite to GMs + if (!WorldConfig.GetBoolValue(WorldCfg.AllowGmGroup) && !GetPlayer().IsGameMaster() && player.IsGameMaster()) + { + SendPartyResult(PartyOperation.Invite, player.GetName(), PartyResult.BadPlayerNameS); + return; + } + + // can't group with + if (!GetPlayer().IsGameMaster() && !WorldConfig.GetBoolValue(WorldCfg.AllowTwoSideInteractionGroup) && GetPlayer().GetTeam() != player.GetTeam()) + { + SendPartyResult(PartyOperation.Invite, player.GetName(), PartyResult.PlayerWrongFaction); + return; + } + if (GetPlayer().GetInstanceId() != 0 && player.GetInstanceId() != 0 && GetPlayer().GetInstanceId() != player.GetInstanceId() && GetPlayer().GetMapId() == player.GetMapId()) + { + SendPartyResult(PartyOperation.Invite, player.GetName(), PartyResult.TargetNotInInstanceS); + return; + } + // just ignore us + if (player.GetInstanceId() != 0 && player.GetDungeonDifficultyID() != GetPlayer().GetDungeonDifficultyID()) + { + SendPartyResult(PartyOperation.Invite, player.GetName(), PartyResult.IgnoringYouS); + return; + } + + if (player.GetSocial().HasIgnore(GetPlayer().GetGUID())) + { + SendPartyResult(PartyOperation.Invite, player.GetName(), PartyResult.IgnoringYouS); + return; + } + + if (!player.GetSocial().HasFriend(GetPlayer().GetGUID()) && GetPlayer().getLevel() < WorldConfig.GetIntValue(WorldCfg.PartyLevelReq)) + { + SendPartyResult(PartyOperation.Invite, player.GetName(), PartyResult.InviteRestricted); + return; + } + + Group group = GetPlayer().GetGroup(); + if (group && group.isBGGroup()) + group = GetPlayer().GetOriginalGroup(); + + Group group2 = player.GetGroup(); + if (group2 && group2.isBGGroup()) + group2 = player.GetOriginalGroup(); + + PartyInvite partyInvite; + // player already in another group or invited + if (group2 || player.GetGroupInvite()) + { + SendPartyResult(PartyOperation.Invite, player.GetName(), PartyResult.AlreadyInGroupS); + + if (group2) + { + // tell the player that they were invited but it failed as they were already in a group + partyInvite = new PartyInvite(); + partyInvite.Initialize(GetPlayer(), packet.ProposedRoles, false); + player.SendPacket(partyInvite); + } + + return; + } + + if (group) + { + // not have permissions for invite + if (!group.IsLeader(GetPlayer().GetGUID()) && !group.IsAssistant(GetPlayer().GetGUID())) + { + SendPartyResult(PartyOperation.Invite, "", PartyResult.NotLeader); + return; + } + // not have place + if (group.IsFull()) + { + SendPartyResult(PartyOperation.Invite, "", PartyResult.GroupFull); + return; + } + } + + // ok, but group not exist, start a new group + // but don't create and save the group to the DB until + // at least one person joins + if (!group) + { + group = new Group(); + // new group: if can't add then delete + if (!group.AddLeaderInvite(GetPlayer())) + return; + + if (!group.AddInvite(player)) + { + group.RemoveAllInvites(); + return; + } + } + else + { + // already existed group: if can't add then just leave + if (!group.AddInvite(player)) + return; + } + + partyInvite = new PartyInvite(); + partyInvite.Initialize(GetPlayer(), packet.ProposedRoles, true); + player.SendPacket(partyInvite); + + SendPartyResult(PartyOperation.Invite, player.GetName(), PartyResult.Ok); + } + + [WorldPacketHandler(ClientOpcodes.PartyInviteResponse)] + void HandlePartyInviteResponse(PartyInviteResponse packet) + { + Group group = GetPlayer().GetGroupInvite(); + if (!group) + return; + + if (packet.Accept) + { + // Remove player from invitees in any case + group.RemoveInvite(GetPlayer()); + + if (group.GetLeaderGUID() == GetPlayer().GetGUID()) + { + Log.outError(LogFilter.Network, "HandleGroupAcceptOpcode: player {0} ({1}) tried to accept an invite to his own group", GetPlayer().GetName(), GetPlayer().GetGUID().ToString()); + return; + } + + // Group is full + if (group.IsFull()) + { + SendPartyResult(PartyOperation.Invite, "", PartyResult.GroupFull); + return; + } + + Player leader = Global.ObjAccessor.FindPlayer(group.GetLeaderGUID()); + + // Forming a new group, create it + if (!group.IsCreated()) + { + // This can happen if the leader is zoning. To be removed once delayed actions for zoning are implemented + if (!leader) + { + group.RemoveAllInvites(); + return; + } + + // If we're about to create a group there really should be a leader present + Contract.Assert(leader); + group.RemoveInvite(leader); + group.Create(leader); + Global.GroupMgr.AddGroup(group); + } + + // Everything is fine, do it, PLAYER'S GROUP IS SET IN ADDMEMBER!!! + if (!group.AddMember(GetPlayer())) + return; + + group.BroadcastGroupUpdate(); + } + else + { + // Remember leader if online (group will be invalid if group gets disbanded) + Player leader = Global.ObjAccessor.FindPlayer(group.GetLeaderGUID()); + + // uninvite, group can be deleted + GetPlayer().UninviteFromGroup(); + + if (!leader || leader.GetSession() == null) + return; + + // report + GroupDecline decline = new GroupDecline(GetPlayer().GetName()); + leader.SendPacket(decline); + } + } + + [WorldPacketHandler(ClientOpcodes.PartyUninvite)] + void HandlePartyUninvite(PartyUninvite packet) + { + //can't uninvite yourself + if (packet.TargetGUID == GetPlayer().GetGUID()) + { + Log.outError(LogFilter.Network, "HandleGroupUninviteGuidOpcode: leader {0}({1}) tried to uninvite himself from the group.", + GetPlayer().GetName(), GetPlayer().GetGUID().ToString()); + return; + } + + PartyResult res = GetPlayer().CanUninviteFromGroup(packet.TargetGUID); + if (res != PartyResult.Ok) + { + SendPartyResult(PartyOperation.UnInvite, "", res); + return; + } + + Group grp = GetPlayer().GetGroup(); + // grp is checked already above in CanUninviteFromGroup() + Contract.Assert(grp); + + if (grp.IsMember(packet.TargetGUID)) + { + Player.RemoveFromGroup(grp, packet.TargetGUID, RemoveMethod.Kick, GetPlayer().GetGUID(), packet.Reason); + return; + } + Player player = grp.GetInvited(packet.TargetGUID); + if (player) + { + player.UninviteFromGroup(); + return; + } + + SendPartyResult(PartyOperation.UnInvite, "", PartyResult.TargetNotInGroupS); + } + + [WorldPacketHandler(ClientOpcodes.SetPartyLeader, Processing = PacketProcessing.Inplace)] + void HandleSetPartyLeader(SetPartyLeader packet) + { + Player player = Global.ObjAccessor.FindConnectedPlayer(packet.TargetGUID); + Group group = GetPlayer().GetGroup(); + + if (!group || !player) + return; + + if (!group.IsLeader(GetPlayer().GetGUID()) || player.GetGroup() != group) + return; + + // Everything's fine, accepted. + group.ChangeLeader(packet.TargetGUID, packet.PartyIndex); + group.SendUpdate(); + } + + [WorldPacketHandler(ClientOpcodes.SetRole, Processing = PacketProcessing.Inplace)] + void HandleSetRole(SetRole packet) + { + RoleChangedInform roleChangedInform = new RoleChangedInform(); + + Group group = GetPlayer().GetGroup(); + byte oldRole = (byte)(group ? group.GetLfgRoles(packet.TargetGUID) : 0); + if (oldRole == packet.Role) + return; + + roleChangedInform.PartyIndex = packet.PartyIndex; + roleChangedInform.From = GetPlayer().GetGUID(); + roleChangedInform.ChangedUnit = packet.TargetGUID; + roleChangedInform.OldRole = oldRole; + roleChangedInform.NewRole = packet.Role; + + if (group) + { + group.BroadcastPacket(roleChangedInform, false); + group.SetLfgRoles(packet.TargetGUID, (LfgRoles)packet.Role); + } + else + SendPacket(roleChangedInform); + } + + [WorldPacketHandler(ClientOpcodes.LeaveGroup)] + void HandleLeaveGroup(LeaveGroup packet) + { + Group grp = GetPlayer().GetGroup(); + if (!grp) + return; + + if (GetPlayer().InBattleground()) + { + SendPartyResult(PartyOperation.Invite, "", PartyResult.InviteRestricted); + return; + } + + /** error handling **/ + /********************/ + + // everything's fine, do it + SendPartyResult(PartyOperation.Leave, GetPlayer().GetName(), PartyResult.Ok); + + GetPlayer().RemoveFromGroup(RemoveMethod.Leave); + } + + [WorldPacketHandler(ClientOpcodes.SetLootMethod)] + void HandleSetLootMethod(SetLootMethod packet) + { + Group group = GetPlayer().GetGroup(); + if (!group) + return; + + /** error handling **/ + if (!group.IsLeader(GetPlayer().GetGUID())) + return; + + switch (packet.LootMethod) + { + case LootMethod.FreeForAll: + case LootMethod.MasterLoot: + case LootMethod.GroupLoot: + case LootMethod.PersonalLoot: + break; + default: + return; + } + + if (packet.LootThreshold < ItemQuality.Uncommon || packet.LootThreshold > ItemQuality.Artifact) + return; + + if (packet.LootMethod == LootMethod.MasterLoot && !group.IsMember(packet.LootMasterGUID)) + return; + + // everything's fine, do it + group.SetLootMethod(packet.LootMethod); + group.SetMasterLooterGuid(packet.LootMasterGUID); + group.SetLootThreshold(packet.LootThreshold); + group.SendUpdate(); + } + + [WorldPacketHandler(ClientOpcodes.LootRoll)] + void HandleLootRoll(LootRoll packet) + { + Group group = GetPlayer().GetGroup(); + if (!group) + return; + + group.CountRollVote(GetPlayer().GetGUID(), packet.LootObj, (byte)(packet.LootListID - 1), packet.RollType); + + switch (packet.RollType) + { + case RollType.Need: + GetPlayer().UpdateCriteria(CriteriaTypes.RollNeed, 1); + break; + case RollType.Greed: + GetPlayer().UpdateCriteria(CriteriaTypes.RollGreed, 1); + break; + } + } + + [WorldPacketHandler(ClientOpcodes.MinimapPing)] + void HandleMinimapPing(MinimapPingClient packet) + { + if (!GetPlayer().GetGroup()) + return; + + MinimapPing minimapPing = new MinimapPing(); + minimapPing.Sender = GetPlayer().GetGUID(); + minimapPing.PositionX = packet.PositionX; + minimapPing.PositionY = packet.PositionY; + GetPlayer().GetGroup().BroadcastPacket(minimapPing, true, -1, GetPlayer().GetGUID()); + } + + [WorldPacketHandler(ClientOpcodes.RandomRoll)] + void HandleRandomRoll(RandomRollClient packet) + { + if (packet.Min > packet.Max || packet.Max > 10000) // < 32768 for urand call + return; + + GetPlayer().DoRandomRoll(packet.Min, packet.Max); + } + + [WorldPacketHandler(ClientOpcodes.UpdateRaidTarget)] + void HandleUpdateRaidTarget(UpdateRaidTarget packet) + { + Group group = GetPlayer().GetGroup(); + if (!group) + return; + + if (packet.Symbol == -1) // target icon request + group.SendTargetIconList(this, packet.PartyIndex); + else // target icon update + { + if (group.isRaidGroup() && !group.IsLeader(GetPlayer().GetGUID()) && !group.IsAssistant(GetPlayer().GetGUID())) + return; + + if (packet.Target.IsPlayer()) + { + Player target = Global.ObjAccessor.FindConnectedPlayer(packet.Target); + if (!target || target.IsHostileTo(GetPlayer())) + return; + } + + group.SetTargetIcon((byte)packet.Symbol, packet.Target, GetPlayer().GetGUID(), packet.PartyIndex); + } + } + + [WorldPacketHandler(ClientOpcodes.ConvertRaid)] + void HandleConvertRaid(ConvertRaid packet) + { + Group group = GetPlayer().GetGroup(); + if (!group) + return; + + if (GetPlayer().InBattleground()) + return; + + // error handling + if (!group.IsLeader(GetPlayer().GetGUID()) || group.GetMembersCount() < 2) + return; + + // everything's fine, do it (is it 0 (PartyOperation.Invite) correct code) + SendPartyResult(PartyOperation.Invite, "", PartyResult.Ok); + + // New 4.x: it is now possible to convert a raid to a group if member count is 5 or less + if (packet.Raid) + group.ConvertToRaid(); + else + group.ConvertToGroup(); + } + + [WorldPacketHandler(ClientOpcodes.RequestPartyJoinUpdates)] + void HandleRequestPartyJoinUpdates(RequestPartyJoinUpdates packet) + { + Group group = GetPlayer().GetGroup(); + if (!group) + return; + + group.SendTargetIconList(this, packet.PartyIndex); + group.SendRaidMarkersChanged(this, packet.PartyIndex); + } + + [WorldPacketHandler(ClientOpcodes.ChangeSubGroup, Processing = PacketProcessing.ThreadUnsafe)] + void HandleChangeSubGroup(ChangeSubGroup packet) + { + // we will get correct for group here, so we don't have to check if group is BG raid + Group group = GetPlayer().GetGroup(); + if (!group) + return; + + if (packet.NewSubGroup >= MapConst.MaxRaidSubGroups) + return; + + ObjectGuid senderGuid = GetPlayer().GetGUID(); + if (!group.IsLeader(senderGuid) && !group.IsAssistant(senderGuid)) + return; + + if (!group.HasFreeSlotSubGroup(packet.NewSubGroup)) + return; + + group.ChangeMembersGroup(packet.TargetGUID, packet.NewSubGroup); + } + + [WorldPacketHandler(ClientOpcodes.SwapSubGroups, Processing = PacketProcessing.ThreadUnsafe)] + void HandleSwapSubGroups(SwapSubGroups packet) + { + Group group = GetPlayer().GetGroup(); + if (!group) + return; + + ObjectGuid senderGuid = GetPlayer().GetGUID(); + if (!group.IsLeader(senderGuid) && !group.IsAssistant(senderGuid)) + return; + + group.SwapMembersGroups(packet.FirstTarget, packet.SecondTarget); + } + + [WorldPacketHandler(ClientOpcodes.SetAssistantLeader)] + void HandleSetAssistantLeader(SetAssistantLeader packet) + { + Group group = GetPlayer().GetGroup(); + if (!group) + return; + + if (!group.IsLeader(GetPlayer().GetGUID())) + return; + + group.SetGroupMemberFlag(packet.Target, packet.Apply, GroupMemberFlags.Assistant); + } + + [WorldPacketHandler(ClientOpcodes.SetPartyAssignment)] + void HandleSetPartyAssignment(SetPartyAssignment packet) + { + Group group = GetPlayer().GetGroup(); + if (!group) + return; + + ObjectGuid senderGuid = GetPlayer().GetGUID(); + if (!group.IsLeader(senderGuid) && !group.IsAssistant(senderGuid)) + return; + + switch ((GroupMemberAssignment)packet.Assignment) + { + case GroupMemberAssignment.MainAssist: + group.RemoveUniqueGroupMemberFlag(GroupMemberFlags.MainAssist); + group.SetGroupMemberFlag(packet.Target, packet.Set, GroupMemberFlags.MainAssist); + break; + case GroupMemberAssignment.MainTank: + group.RemoveUniqueGroupMemberFlag(GroupMemberFlags.MainTank); // Remove main assist flag from current if any. + group.SetGroupMemberFlag(packet.Target, packet.Set, GroupMemberFlags.MainTank); + break; + } + + group.SendUpdate(); + } + + [WorldPacketHandler(ClientOpcodes.DoReadyCheck)] + void HandleDoReadyCheckOpcode(DoReadyCheck packet) + { + Group group = GetPlayer().GetGroup(); + if (!group) + return; + + /** error handling **/ + if (!group.IsLeader(GetPlayer().GetGUID()) && !group.IsAssistant(GetPlayer().GetGUID())) + return; + + // everything's fine, do it + group.StartReadyCheck(GetPlayer().GetGUID(), packet.PartyIndex); + } + + [WorldPacketHandler(ClientOpcodes.ReadyCheckResponse, Processing = PacketProcessing.Inplace)] + void HandleReadyCheckResponseOpcode(ReadyCheckResponseClient packet) + { + Group group = GetPlayer().GetGroup(); + if (!group) + return; + + // everything's fine, do it + group.SetMemberReadyCheck(GetPlayer().GetGUID(), packet.IsReady); + } + + [WorldPacketHandler(ClientOpcodes.RequestPartyMemberStats)] + void HandleRequestPartyMemberStats(RequestPartyMemberStats packet) + { + PartyMemberState partyMemberStats = new PartyMemberState(); + + Player player = Global.ObjAccessor.FindConnectedPlayer(packet.TargetGUID); + if (!player) + { + partyMemberStats.MemberGuid = packet.TargetGUID; + partyMemberStats.MemberStats.Status = GroupMemberOnlineStatus.Offline; + } + else + partyMemberStats.Initialize(player); + + SendPacket(partyMemberStats); + } + + [WorldPacketHandler(ClientOpcodes.RequestRaidInfo)] + void HandleRequestRaidInfo(RequestRaidInfo packet) + { + // every time the player checks the character screen + GetPlayer().SendRaidInfo(); + } + + [WorldPacketHandler(ClientOpcodes.OptOutOfLoot)] + void HandleOptOutOfLoot(OptOutOfLoot packet) + { + // ignore if player not loaded + if (!GetPlayer()) // needed because STATUS_AUTHED + { + if (packet.PassOnLoot) + Log.outError(LogFilter.Network, "CMSG_OPT_OUT_OF_LOOT value<>0 for not-loaded character!"); + return; + } + + GetPlayer().SetPassOnGroupLoot(packet.PassOnLoot); + } + + [WorldPacketHandler(ClientOpcodes.InitiateRolePoll)] + void HandleInitiateRolePoll(InitiateRolePoll packet) + { + Group group = GetPlayer().GetGroup(); + if (!group) + return; + + ObjectGuid guid = GetPlayer().GetGUID(); + if (!group.IsLeader(guid) && !group.IsAssistant(guid)) + return; + + RolePollInform rolePollInform = new RolePollInform(); + rolePollInform.From = guid; + rolePollInform.PartyIndex = packet.PartyIndex; + group.BroadcastPacket(rolePollInform, true); + } + + [WorldPacketHandler(ClientOpcodes.SetEveryoneIsAssistant)] + void HandleSetEveryoneIsAssistant(SetEveryoneIsAssistant packet) + { + Group group = GetPlayer().GetGroup(); + if (!group) + return; + + if (!group.IsLeader(GetPlayer().GetGUID())) + return; + + group.SetEveryoneIsAssistant(packet.EveryoneIsAssistant); + } + + [WorldPacketHandler(ClientOpcodes.ClearRaidMarker)] + void HandleClearRaidMarker(ClearRaidMarker packet) + { + Group group = GetPlayer().GetGroup(); + if (!group) + return; + + if (group.isRaidGroup() && !group.IsLeader(GetPlayer().GetGUID()) && !group.IsAssistant(GetPlayer().GetGUID())) + return; + + group.DeleteRaidMarker(packet.MarkerId); + } + } +} diff --git a/Game/Handlers/GuildFinderHandler.cs b/Game/Handlers/GuildFinderHandler.cs new file mode 100644 index 000000000..313ae3cb4 --- /dev/null +++ b/Game/Handlers/GuildFinderHandler.cs @@ -0,0 +1,249 @@ +/* + * Copyright (C) 2012-2017 CypherCore + * + * 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, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +using Framework.Constants; +using Game.Entities; +using Game.Guilds; +using Game.Network; +using Game.Network.Packets; +using System; +using System.Collections.Generic; + +namespace Game +{ + public partial class WorldSession + { + [WorldPacketHandler(ClientOpcodes.LfGuildAddRecruit)] + void HandleGuildFinderAddRecruit(LFGuildAddRecruit lfGuildAddRecruit) + { + if (Global.GuildFinderMgr.GetAllMembershipRequestsForPlayer(GetPlayer().GetGUID()).Count >= 10) + return; + + if (!lfGuildAddRecruit.GuildGUID.IsGuild()) + return; + if (!lfGuildAddRecruit.ClassRoles.HasAnyFlag((uint)GuildFinderOptionsRoles.All) || lfGuildAddRecruit.ClassRoles > (uint)GuildFinderOptionsRoles.All) + return; + if (!lfGuildAddRecruit.Availability.HasAnyFlag((uint)GuildFinderOptionsAvailability.Always) || lfGuildAddRecruit.Availability > (uint)GuildFinderOptionsAvailability.Always) + return; + if (!lfGuildAddRecruit.PlayStyle.HasAnyFlag((uint)GuildFinderOptionsInterest.All) || lfGuildAddRecruit.PlayStyle > (uint)GuildFinderOptionsInterest.All) + return; + + MembershipRequest request = new MembershipRequest(GetPlayer().GetGUID(), lfGuildAddRecruit.GuildGUID, lfGuildAddRecruit.Availability, + lfGuildAddRecruit.ClassRoles, lfGuildAddRecruit.PlayStyle, lfGuildAddRecruit.Comment, Time.UnixTime); + Global.GuildFinderMgr.AddMembershipRequest(lfGuildAddRecruit.GuildGUID, request); + } + + [WorldPacketHandler(ClientOpcodes.LfGuildBrowse)] + void HandleGuildFinderBrowse(LFGuildBrowse lfGuildBrowse) + { + if (!lfGuildBrowse.ClassRoles.HasAnyFlag((uint)GuildFinderOptionsRoles.All) || lfGuildBrowse.ClassRoles > (uint)GuildFinderOptionsRoles.All) + return; + if (!lfGuildBrowse.Availability.HasAnyFlag((uint)GuildFinderOptionsAvailability.Always) || lfGuildBrowse.Availability > (uint)GuildFinderOptionsAvailability.Always) + return; + if (!lfGuildBrowse.PlayStyle.HasAnyFlag((uint)GuildFinderOptionsInterest.All) || lfGuildBrowse.PlayStyle > (uint)GuildFinderOptionsInterest.All) + return; + if (lfGuildBrowse.CharacterLevel > WorldConfig.GetIntValue(WorldCfg.MaxPlayerLevel) || lfGuildBrowse.CharacterLevel < 1) + return; + + Player player = GetPlayer(); + + LFGuildPlayer settings = new LFGuildPlayer(player.GetGUID(), lfGuildBrowse.ClassRoles, lfGuildBrowse.Availability, lfGuildBrowse.PlayStyle, (uint)GuildFinderOptionsLevel.Any); + var guildList = Global.GuildFinderMgr.GetGuildsMatchingSetting(settings, (uint)player.GetTeam()); + + LFGuildBrowseResult lfGuildBrowseResult = new LFGuildBrowseResult(); + for (var i = 0; i < guildList.Count; ++i) + { + LFGuildSettings guildSettings = guildList[i]; + LFGuildBrowseData guildData = new LFGuildBrowseData(); + Guild guild = Global.GuildMgr.GetGuildByGuid(guildSettings.GetGUID()); + + guildData.GuildName = guild.GetName(); + guildData.GuildGUID = guild.GetGUID(); + guildData.GuildVirtualRealm = Global.WorldMgr.GetVirtualRealmAddress(); + guildData.GuildMembers = guild.GetMembersCount(); + guildData.GuildAchievementPoints = guild.GetAchievementMgr().GetAchievementPoints(); + guildData.PlayStyle = guildSettings.GetInterests(); + guildData.Availability = guildSettings.GetAvailability(); + guildData.ClassRoles = guildSettings.GetClassRoles(); + guildData.LevelRange = guildSettings.GetLevel(); + guildData.EmblemStyle = guild.GetEmblemInfo().GetStyle(); + guildData.EmblemColor = guild.GetEmblemInfo().GetColor(); + guildData.BorderStyle = guild.GetEmblemInfo().GetBorderStyle(); + guildData.BorderColor = guild.GetEmblemInfo().GetBorderColor(); + guildData.Background = guild.GetEmblemInfo().GetBackgroundColor(); + guildData.Comment = guildSettings.GetComment(); + guildData.Cached = 0; + guildData.MembershipRequested = (sbyte)(Global.GuildFinderMgr.HasRequest(player.GetGUID(), guild.GetGUID()) ? 1 : 0); + + lfGuildBrowseResult.Post.Add(guildData); + } + + player.SendPacket(lfGuildBrowseResult); + } + + [WorldPacketHandler(ClientOpcodes.LfGuildDeclineRecruit)] + void HandleGuildFinderDeclineRecruit(LFGuildDeclineRecruit lfGuildDeclineRecruit) + { + if (!GetPlayer().GetGuild()) + return; + + if (!lfGuildDeclineRecruit.RecruitGUID.IsPlayer()) + return; + + Global.GuildFinderMgr.RemoveMembershipRequest(lfGuildDeclineRecruit.RecruitGUID, GetPlayer().GetGuild().GetGUID()); + } + + [WorldPacketHandler(ClientOpcodes.LfGuildGetApplications)] + void HandleGuildFinderGetApplications(LFGuildGetApplications lfGuildGetApplications) + { + List applicatedGuilds = Global.GuildFinderMgr.GetAllMembershipRequestsForPlayer(GetPlayer().GetGUID()); + LFGuildApplications lfGuildApplications = new LFGuildApplications(); + lfGuildApplications.NumRemaining = 10 - Global.GuildFinderMgr.CountRequestsFromPlayer(GetPlayer().GetGUID()); + + for (var i = 0; i < applicatedGuilds.Count; ++i) + { + MembershipRequest application = applicatedGuilds[i]; + LFGuildApplicationData applicationData = new LFGuildApplicationData(); + + Guild guild = Global.GuildMgr.GetGuildByGuid(application.GetGuildGuid()); + LFGuildSettings guildSettings = Global.GuildFinderMgr.GetGuildSettings(application.GetGuildGuid()); + + applicationData.GuildGUID = application.GetGuildGuid(); + applicationData.GuildVirtualRealm = Global.WorldMgr.GetVirtualRealmAddress(); + applicationData.GuildName = guild.GetName(); + applicationData.ClassRoles = guildSettings.GetClassRoles(); + applicationData.PlayStyle = guildSettings.GetInterests(); + applicationData.Availability = guildSettings.GetAvailability(); + applicationData.SecondsSinceCreated = (uint)(Time.UnixTime - application.GetSubmitTime()); + applicationData.SecondsUntilExpiration = (uint)(application.GetExpiryTime() - Time.UnixTime); + applicationData.Comment = application.GetComment(); + + lfGuildApplications.Application.Add(applicationData); + } + + GetPlayer().SendPacket(lfGuildApplications); + } + + [WorldPacketHandler(ClientOpcodes.LfGuildGetGuildPost)] + void HandleGuildFinderGetGuildPost(LFGuildGetGuildPost lfGuildGetGuildPost) + { + Player player = GetPlayer(); + + Guild guild = player.GetGuild(); + if (!guild) // Player must be in guild + return; + + LFGuildPost lfGuildPost = new LFGuildPost(); + if (guild.GetLeaderGUID() == player.GetGUID()) + { + LFGuildSettings settings = Global.GuildFinderMgr.GetGuildSettings(guild.GetGUID()); + lfGuildPost.Post.HasValue = true; + lfGuildPost.Post.Value.Active = settings.IsListed(); + lfGuildPost.Post.Value.PlayStyle = settings.GetInterests(); + lfGuildPost.Post.Value.Availability = settings.GetAvailability(); + lfGuildPost.Post.Value.ClassRoles = settings.GetClassRoles(); + lfGuildPost.Post.Value.LevelRange = settings.GetLevel(); + lfGuildPost.Post.Value.Comment = settings.GetComment(); + } + + player.SendPacket(lfGuildPost); + } + + // Lists all recruits for a guild - Misses times + [WorldPacketHandler(ClientOpcodes.LfGuildGetRecruits)] + void HandleGuildFinderGetRecruits(LFGuildGetRecruits lfGuildGetRecruits) + { + Player player = GetPlayer(); + Guild guild = player.GetGuild(); + if (!guild) + return; + + long now = Time.UnixTime; + LFGuildRecruits lfGuildRecruits = new LFGuildRecruits(); + lfGuildRecruits.UpdateTime = now; + var recruitsList = Global.GuildFinderMgr.GetAllMembershipRequestsForGuild(guild.GetGUID()); + if (recruitsList != null) + { + foreach (var recruitRequestPair in recruitsList) + { + LFGuildRecruitData recruitData = new LFGuildRecruitData(); + recruitData.RecruitGUID = recruitRequestPair.Key; + recruitData.RecruitVirtualRealm = Global.WorldMgr.GetVirtualRealmAddress(); + recruitData.Comment = recruitRequestPair.Value.GetComment(); + recruitData.ClassRoles = recruitRequestPair.Value.GetClassRoles(); + recruitData.PlayStyle = recruitRequestPair.Value.GetInterests(); + recruitData.Availability = recruitRequestPair.Value.GetAvailability(); + recruitData.SecondsSinceCreated = (uint)(now - recruitRequestPair.Value.GetSubmitTime()); + recruitData.SecondsUntilExpiration = (uint)(recruitRequestPair.Value.GetExpiryTime() - now); + + CharacterInfo charInfo = Global.WorldMgr.GetCharacterInfo(recruitRequestPair.Key); + if (charInfo != null) + { + recruitData.Name = charInfo.Name; + recruitData.CharacterClass = (int)charInfo.ClassID; + recruitData.CharacterGender = (int)charInfo.Sex; + recruitData.CharacterLevel = charInfo.Level; + } + + lfGuildRecruits.Recruits.Add(recruitData); + } + } + + player.SendPacket(lfGuildRecruits); + } + + [WorldPacketHandler(ClientOpcodes.LfGuildRemoveRecruit)] + void HandleGuildFinderRemoveRecruit(LFGuildRemoveRecruit lfGuildRemoveRecruit) + { + if (!lfGuildRemoveRecruit.GuildGUID.IsGuild()) + return; + + Global.GuildFinderMgr.RemoveMembershipRequest(GetPlayer().GetGUID(), lfGuildRemoveRecruit.GuildGUID); + } + + // Sent any time a guild master sets an option in the interface and when listing / unlisting his guild + [WorldPacketHandler(ClientOpcodes.LfGuildSetGuildPost)] + void HandleGuildFinderSetGuildPost(LFGuildSetGuildPost lfGuildSetGuildPost) + { + // Level sent is zero if untouched, force to any (from interface). Idk why + if (lfGuildSetGuildPost.LevelRange == 0) + lfGuildSetGuildPost.LevelRange = (uint)GuildFinderOptionsLevel.Any; + + if (!lfGuildSetGuildPost.ClassRoles.HasAnyFlag((uint)GuildFinderOptionsRoles.All) || lfGuildSetGuildPost.ClassRoles > (uint)GuildFinderOptionsRoles.All) + return; + if (!lfGuildSetGuildPost.Availability.HasAnyFlag((uint)GuildFinderOptionsAvailability.Always) || lfGuildSetGuildPost.Availability > (uint)GuildFinderOptionsAvailability.Always) + return; + if (!lfGuildSetGuildPost.PlayStyle.HasAnyFlag((uint)GuildFinderOptionsInterest.All) || lfGuildSetGuildPost.PlayStyle > (uint)GuildFinderOptionsInterest.All) + return; + if (!lfGuildSetGuildPost.LevelRange.HasAnyFlag((uint)GuildFinderOptionsLevel.All) || lfGuildSetGuildPost.LevelRange > (uint)GuildFinderOptionsLevel.All) + return; + + Player player = GetPlayer(); + + if (player.GetGuildId() == 0) // Player must be in guild + return; + + Guild guild = Global.GuildMgr.GetGuildById(player.GetGuildId()); + if (guild.GetLeaderGUID() != player.GetGUID()) + return; + + LFGuildSettings settings = new LFGuildSettings(lfGuildSetGuildPost.Active, (uint)player.GetTeam(), player.GetGuild().GetGUID(), lfGuildSetGuildPost.ClassRoles, + lfGuildSetGuildPost.Availability, lfGuildSetGuildPost.PlayStyle, lfGuildSetGuildPost.LevelRange, lfGuildSetGuildPost.Comment); + Global.GuildFinderMgr.SetGuildSettings(player.GetGuild().GetGUID(), settings); + } + } +} diff --git a/Game/Handlers/GuildHandler.cs b/Game/Handlers/GuildHandler.cs new file mode 100644 index 000000000..eee44f029 --- /dev/null +++ b/Game/Handlers/GuildHandler.cs @@ -0,0 +1,467 @@ +/* + * Copyright (C) 2012-2017 CypherCore + * + * 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, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +using Framework.Constants; +using Game.Entities; +using Game.Guilds; +using Game.Network; +using Game.Network.Packets; +using System.Collections.Generic; + +namespace Game +{ + public partial class WorldSession + { + [WorldPacketHandler(ClientOpcodes.QueryGuildInfo, Status = SessionStatus.Authed)] + void HandleGuildQuery(QueryGuildInfo query) + { + Guild guild = Global.GuildMgr.GetGuildByGuid(query.GuildGuid); + if (guild) + { + if (guild.IsMember(query.PlayerGuid)) + { + guild.SendQueryResponse(this); + return; + } + } + + QueryGuildInfoResponse response = new QueryGuildInfoResponse(); + response.GuildGUID = query.GuildGuid; + SendPacket(response); + } + + [WorldPacketHandler(ClientOpcodes.GuildInviteByName)] + void HandleGuildInviteByName(GuildInviteByName packet) + { + if (!ObjectManager.NormalizePlayerName(ref packet.Name)) + return; + + Guild guild = GetPlayer().GetGuild(); + if (guild) + guild.HandleInviteMember(this, packet.Name); + } + + [WorldPacketHandler(ClientOpcodes.GuildOfficerRemoveMember)] + void HandleGuildOfficerRemoveMember(GuildOfficerRemoveMember packet) + { + Guild guild = GetPlayer().GetGuild(); + if (guild) + guild.HandleRemoveMember(this, packet.Removee); + } + + [WorldPacketHandler(ClientOpcodes.AcceptGuildInvite)] + void HandleGuildAcceptInvite(AcceptGuildInvite packet) + { + if (GetPlayer().GetGuildId() == 0) + { + Guild guild = Global.GuildMgr.GetGuildById(GetPlayer().GetGuildIdInvited()); + if (guild) + guild.HandleAcceptMember(this); + } + } + + [WorldPacketHandler(ClientOpcodes.GuildDeclineInvitation)] + void HandleGuildDeclineInvitation(GuildDeclineInvitation packet) + { + GetPlayer().SetGuildIdInvited(0); + GetPlayer().SetInGuild(0); + } + + [WorldPacketHandler(ClientOpcodes.GuildGetRoster)] + void HandleGuildGetRoster(GuildGetRoster packet) + { + Guild guild = GetPlayer().GetGuild(); + if (guild) + guild.HandleRoster(this); + else + Guild.SendCommandResult(this, GuildCommandType.GetRoster, GuildCommandError.PlayerNotInGuild); + } + + [WorldPacketHandler(ClientOpcodes.GuildPromoteMember)] + void HandleGuildPromoteMember(GuildPromoteMember packet) + { + Guild guild = GetPlayer().GetGuild(); + if (guild) + guild.HandleUpdateMemberRank(this, packet.Promotee, false); + } + + [WorldPacketHandler(ClientOpcodes.GuildDemoteMember)] + void HandleGuildDemoteMember(GuildDemoteMember packet) + { + Guild guild = GetPlayer().GetGuild(); + if (guild) + guild.HandleUpdateMemberRank(this, packet.Demotee, true); + } + + [WorldPacketHandler(ClientOpcodes.GuildAssignMemberRank)] + void HandleGuildAssignRank(GuildAssignMemberRank packet) + { + ObjectGuid setterGuid = GetPlayer().GetGUID(); + + Guild guild = GetPlayer().GetGuild(); + if (guild) + guild.HandleSetMemberRank(this, packet.Member, setterGuid, (byte)packet.RankOrder); + } + + [WorldPacketHandler(ClientOpcodes.GuildLeave)] + void HandleGuildLeave(GuildLeave packet) + { + Guild guild = GetPlayer().GetGuild(); + if (guild) + guild.HandleLeaveMember(this); + } + + [WorldPacketHandler(ClientOpcodes.GuildDelete)] + void HandleGuildDisband(GuildDelete packet) + { + Guild guild = GetPlayer().GetGuild(); + if (guild) + guild.HandleDelete(this); + } + + [WorldPacketHandler(ClientOpcodes.GuildUpdateMotdText)] + void HandleGuildUpdateMotdText(GuildUpdateMotdText packet) + { + Guild guild = GetPlayer().GetGuild(); + if (guild) + guild.HandleSetMOTD(this, packet.MotdText); + } + + [WorldPacketHandler(ClientOpcodes.GuildSetMemberNote)] + void HandleGuildSetMemberNote(GuildSetMemberNote packet) + { + Guild guild = GetPlayer().GetGuild(); + if (guild) + guild.HandleSetMemberNote(this, packet.Note, packet.NoteeGUID, packet.IsPublic); + } + + [WorldPacketHandler(ClientOpcodes.GuildGetRanks)] + void HandleGuildGetRanks(GuildGetRanks packet) + { + Guild guild = Global.GuildMgr.GetGuildByGuid(packet.GuildGUID); + if (guild) + if (guild.IsMember(GetPlayer().GetGUID())) + guild.SendGuildRankInfo(this); + } + + [WorldPacketHandler(ClientOpcodes.GuildAddRank)] + void HandleGuildAddRank(GuildAddRank packet) + { + Guild guild = GetPlayer().GetGuild(); + if (guild) + guild.HandleAddNewRank(this, packet.Name); + } + + [WorldPacketHandler(ClientOpcodes.GuildDeleteRank)] + void HandleGuildDeleteRank(GuildDeleteRank packet) + { + Guild guild = GetPlayer().GetGuild(); + if (guild) + guild.HandleRemoveRank(this, (byte)packet.RankOrder); + } + + [WorldPacketHandler(ClientOpcodes.GuildUpdateInfoText)] + void HandleGuildUpdateInfoText(GuildUpdateInfoText packet) + { + Guild guild = GetPlayer().GetGuild(); + if (guild) + guild.HandleSetInfo(this, packet.InfoText); + } + + [WorldPacketHandler(ClientOpcodes.SaveGuildEmblem)] + void HandleSaveGuildEmblem(SaveGuildEmblem packet) + { + Guild.EmblemInfo emblemInfo = new Guild.EmblemInfo(); + emblemInfo.ReadPacket(packet); + + if (GetPlayer().GetNPCIfCanInteractWith(packet.Vendor, NPCFlags.TabardDesigner)) + { + // Remove fake death + if (GetPlayer().HasUnitState(UnitState.Died)) + GetPlayer().RemoveAurasByType(AuraType.FeignDeath); + + if (!emblemInfo.ValidateEmblemColors()) + { + Guild.SendSaveEmblemResult(this, GuildEmblemError.InvalidTabardColors); + return; + } + + Guild guild = GetPlayer().GetGuild(); + if (guild) + guild.HandleSetEmblem(this, emblemInfo); + else + Guild.SendSaveEmblemResult(this, GuildEmblemError.NoGuild); // "You are not part of a guild!"; + } + else + Guild.SendSaveEmblemResult(this, GuildEmblemError.InvalidVendor); // "That's not an emblem vendor!" + } + + [WorldPacketHandler(ClientOpcodes.GuildEventLogQuery)] + void HandleGuildEventLogQuery(GuildEventLogQuery packet) + { + Guild guild = GetPlayer().GetGuild(); + if (guild) + guild.SendEventLog(this); + } + + [WorldPacketHandler(ClientOpcodes.GuildBankRemainingWithdrawMoneyQuery)] + void HandleGuildBankMoneyWithdrawn(GuildBankRemainingWithdrawMoneyQuery packet) + { + Guild guild = GetPlayer().GetGuild(); + if (guild) + guild.SendMoneyInfo(this); + } + + [WorldPacketHandler(ClientOpcodes.GuildPermissionsQuery)] + void HandleGuildPermissionsQuery(GuildPermissionsQuery packet) + { + Guild guild = GetPlayer().GetGuild(); + if (guild) + guild.SendPermissions(this); + } + + [WorldPacketHandler(ClientOpcodes.GuildBankActivate)] + void HandleGuildBankActivate(GuildBankActivate packet) + { + GameObject go = GetPlayer().GetGameObjectIfCanInteractWith(packet.Banker, GameObjectTypes.GuildBank); + if (go == null) + return; + + Guild guild = GetPlayer().GetGuild(); + if (guild == null) + { + Guild.SendCommandResult(this, GuildCommandType.ViewTab, GuildCommandError.PlayerNotInGuild); + return; + } + + guild.SendBankList(this, 0, packet.FullUpdate); + } + + [WorldPacketHandler(ClientOpcodes.GuildBankQueryTab)] + void HandleGuildBankQueryTab(GuildBankQueryTab packet) + { + if (GetPlayer().GetGameObjectIfCanInteractWith(packet.Banker, GameObjectTypes.GuildBank)) + { + Guild guild = GetPlayer().GetGuild(); + if (guild) + guild.SendBankList(this, packet.Tab, packet.FullUpdate); + } + } + + [WorldPacketHandler(ClientOpcodes.GuildBankDepositMoney)] + void HandleGuildBankDepositMoney(GuildBankDepositMoney packet) + { + if (GetPlayer().GetGameObjectIfCanInteractWith(packet.Banker, GameObjectTypes.GuildBank)) + { + if (packet.Money != 0 && GetPlayer().HasEnoughMoney(packet.Money)) + { + Guild guild = GetPlayer().GetGuild(); + if (guild) + guild.HandleMemberDepositMoney(this, packet.Money); + } + } + } + + [WorldPacketHandler(ClientOpcodes.GuildBankWithdrawMoney)] + void HandleGuildBankWithdrawMoney(GuildBankWithdrawMoney packet) + { + if (packet.Money != 0 && GetPlayer().GetGameObjectIfCanInteractWith(packet.Banker, GameObjectTypes.GuildBank)) + { + Guild guild = GetPlayer().GetGuild(); + if (guild) + guild.HandleMemberWithdrawMoney(this, packet.Money); + } + } + + [WorldPacketHandler(ClientOpcodes.GuildBankSwapItems)] + void HandleGuildBankSwapItems(GuildBankSwapItems packet) + { + if (!GetPlayer().GetGameObjectIfCanInteractWith(packet.Banker, GameObjectTypes.GuildBank)) + return; + + Guild guild = GetPlayer().GetGuild(); + if (!guild) + return; + + if (packet.BankOnly) + { + guild.SwapItems(GetPlayer(), packet.BankTab1, packet.BankSlot1, packet.BankTab, packet.BankSlot, (uint)packet.StackCount); + } + else + { + // Player <-> Bank + // Allow to work with inventory only + if (!Player.IsInventoryPos(packet.ContainerSlot, packet.ContainerItemSlot) && !packet.AutoStore) + GetPlayer().SendEquipError(InventoryResult.InternalBagError); + else + guild.SwapItemsWithInventory(GetPlayer(), packet.ToSlot != 0, packet.BankTab, packet.BankSlot, packet.ContainerSlot, packet.ContainerItemSlot, (uint)packet.StackCount); + } + } + + [WorldPacketHandler(ClientOpcodes.GuildBankBuyTab)] + void HandleGuildBankBuyTab(GuildBankBuyTab packet) + { + if (packet.Banker.IsEmpty() || GetPlayer().GetGameObjectIfCanInteractWith(packet.Banker, GameObjectTypes.GuildBank)) + { + Guild guild = GetPlayer().GetGuild(); + if (guild) + guild.HandleBuyBankTab(this, packet.BankTab); + } + } + + [WorldPacketHandler(ClientOpcodes.GuildBankUpdateTab)] + void HandleGuildBankUpdateTab(GuildBankUpdateTab packet) + { + if (!string.IsNullOrEmpty(packet.Name) && !string.IsNullOrEmpty(packet.Icon)) + { + if (GetPlayer().GetGameObjectIfCanInteractWith(packet.Banker, GameObjectTypes.GuildBank)) + { + Guild guild = GetPlayer().GetGuild(); + if (guild) + guild.HandleSetBankTabInfo(this, packet.BankTab, packet.Name, packet.Icon); + } + } + } + + [WorldPacketHandler(ClientOpcodes.GuildBankLogQuery)] + void HandleGuildBankLogQuery(GuildBankLogQuery packet) + { + Guild guild = GetPlayer().GetGuild(); + if (guild) + guild.SendBankLog(this, (byte)packet.Tab); + } + + [WorldPacketHandler(ClientOpcodes.GuildBankTextQuery)] + void HandleGuildBankTextQuery(GuildBankTextQuery packet) + { + Guild guild = GetPlayer().GetGuild(); + if (guild) + guild.SendBankTabText(this, (byte)packet.Tab); + } + + [WorldPacketHandler(ClientOpcodes.GuildBankSetTabText)] + void HandleGuildBankSetTabText(GuildBankSetTabText packet) + { + Guild guild = GetPlayer().GetGuild(); + if (guild) + guild.SetBankTabText((byte)packet.Tab, packet.TabText); + } + + [WorldPacketHandler(ClientOpcodes.GuildSetRankPermissions)] + void HandleGuildSetRankPermissions(GuildSetRankPermissions packet) + { + Guild guild = GetPlayer().GetGuild(); + if (!guild) + return; + + List rightsAndSlots = new List(GuildConst.MaxBankTabs); + for (byte tabId = 0; tabId < GuildConst.MaxBankTabs; ++tabId) + rightsAndSlots[tabId] = new Guild.GuildBankRightsAndSlots(tabId, (sbyte)packet.TabFlags[tabId], packet.TabWithdrawItemLimit[tabId]); + + guild.HandleSetRankInfo(this, (byte)packet.RankOrder, packet.RankName, (GuildRankRights)packet.Flags, (uint)packet.WithdrawGoldLimit, rightsAndSlots); + } + + [WorldPacketHandler(ClientOpcodes.RequestGuildPartyState)] + void HandleGuildRequestPartyState(RequestGuildPartyState packet) + { + Guild guild = Global.GuildMgr.GetGuildByGuid(packet.GuildGUID); + if (guild) + guild.HandleGuildPartyRequest(this); + } + + [WorldPacketHandler(ClientOpcodes.GuildChangeNameRequest)] + void HandleGuildChallengeUpdateRequest(GuildChallengeUpdateRequest packet) + { + Guild guild = GetPlayer().GetGuild(); + if (guild) + guild.HandleGuildRequestChallengeUpdate(this); + } + + [WorldPacketHandler(ClientOpcodes.DeclineGuildInvites)] + void HandleDeclineGuildInvites(DeclineGuildInvites packet) + { + GetPlayer().ApplyModFlag(PlayerFields.Flags, PlayerFlags.AutoDeclineGuild, packet.Allow); + } + + [WorldPacketHandler(ClientOpcodes.RequestGuildRewardsList, Processing = PacketProcessing.Inplace)] + void HandleRequestGuildRewardsList(RequestGuildRewardsList packet) + { + if (Global.GuildMgr.GetGuildById(GetPlayer().GetGuildId())) + { + var rewards = Global.GuildMgr.GetGuildRewards(); + + GuildRewardList rewardList = new GuildRewardList(); + rewardList.Version = (uint)Time.UnixTime; + + for (int i = 0; i < rewards.Count; i++) + { + GuildRewardItem rewardItem = new GuildRewardItem(); + rewardItem.ItemID = rewards[i].ItemID; + rewardItem.RaceMask = (uint)rewards[i].RaceMask; + rewardItem.MinGuildLevel = 0; + rewardItem.MinGuildRep = rewards[i].MinGuildRep; + rewardItem.AchievementsRequired = rewards[i].AchievementsRequired; + rewardItem.Cost = rewards[i].Cost; + rewardList.RewardItems.Add(rewardItem); + } + + SendPacket(rewardList); + } + } + + [WorldPacketHandler(ClientOpcodes.GuildQueryNews, Processing = PacketProcessing.Inplace)] + void HandleGuildQueryNews(GuildQueryNews packet) + { + Guild guild = GetPlayer().GetGuild(); + if (guild) + if (guild.GetGUID() == packet.GuildGUID) + guild.SendNewsUpdate(this); + } + + [WorldPacketHandler(ClientOpcodes.GuildNewsUpdateSticky)] + void HandleGuildNewsUpdateSticky(GuildNewsUpdateSticky packet) + { + Guild guild = GetPlayer().GetGuild(); + if (guild) + guild.HandleNewsSetSticky(this, (uint)packet.NewsID, packet.Sticky); + } + + [WorldPacketHandler(ClientOpcodes.GuildSetGuildMaster)] + void HandleGuildSetGuildMaster(GuildSetGuildMaster packet) + { + Guild guild = GetPlayer().GetGuild(); + if (guild) + guild.HandleSetNewGuildMaster(this, packet.NewMasterName); + } + + [WorldPacketHandler(ClientOpcodes.GuildSetAchievementTracking)] + void HandleGuildSetAchievementTracking(GuildSetAchievementTracking packet) + { + Guild guild = GetPlayer().GetGuild(); + if (guild) + guild.HandleSetAchievementTracking(this, packet.AchievementIDs); + } + + [WorldPacketHandler(ClientOpcodes.GuildGetAchievementMembers)] + void HandleGuildGetAchievementMembers(GuildGetAchievementMembers getAchievementMembers) + { + Guild guild = GetPlayer().GetGuild(); + if (guild) + guild.HandleGetAchievementMembers(this, getAchievementMembers.AchievementID); + } + } +} diff --git a/Game/Handlers/HotfixHandler.cs b/Game/Handlers/HotfixHandler.cs new file mode 100644 index 000000000..7c59ead71 --- /dev/null +++ b/Game/Handlers/HotfixHandler.cs @@ -0,0 +1,94 @@ +/* + * Copyright (C) 2012-2017 CypherCore + * + * 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, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +using Framework.Constants; +using Game.DataStorage; +using Game.Network; +using Game.Network.Packets; +using System.Collections.Generic; + +namespace Game +{ + public partial class WorldSession + { + [WorldPacketHandler(ClientOpcodes.DbQueryBulk, Processing = PacketProcessing.Inplace, Status = SessionStatus.Authed)] + void HandleDBQueryBulk(DBQueryBulk dbQuery) + { + IDB2Storage store = Global.DB2Mgr.GetStorage(dbQuery.TableHash); + if (store == null) + { + Log.outError(LogFilter.Network, "CMSG_DB_QUERY_BULK: {0} requested unsupported unknown hotfix type: {1}", GetPlayerInfo(), dbQuery.TableHash); + return; + } + + foreach (DBQueryBulk.DBQueryRecord record in dbQuery.Queries) + { + DBReply dbReply = new DBReply(); + dbReply.TableHash = dbQuery.TableHash; + dbReply.RecordID = record.RecordID; + + if (store.HasRecord(record.RecordID)) + { + dbReply.Allow = true; + dbReply.Timestamp = (uint)Global.WorldMgr.GetGameTime(); + store.WriteRecord(record.RecordID, GetSessionDbcLocale(), dbReply.Data); + } + else + { + Log.outTrace(LogFilter.Network, "CMSG_DB_QUERY_BULK: {0} requested non-existing entry {1} in datastore: {2}", GetPlayerInfo(), record.RecordID, dbQuery.TableHash); + dbReply.Timestamp = (uint)Time.UnixTime; + } + + SendPacket(dbReply); + } + } + + void SendHotfixList(int version) + { + SendPacket(new HotfixList(version, Global.DB2Mgr.GetHotfixData())); + } + + [WorldPacketHandler(ClientOpcodes.HotfixQuery, Status = SessionStatus.Authed)] + void HandleHotfixQuery(HotfixQuery hotfixQuery) + { + Dictionary hotfixes = Global.DB2Mgr.GetHotfixData(); + + HotfixQueryResponse hotfixQueryResponse = new HotfixQueryResponse(); + foreach (ulong hotfixId in hotfixQuery.Hotfixes) + { + int hotfix = hotfixes.LookupByKey(hotfixId); + if (hotfix != 0) + { + var storage = Global.DB2Mgr.GetStorage(MathFunctions.Pair64_HiPart(hotfixId)); + + HotfixQueryResponse.HotfixData hotfixData = new HotfixQueryResponse.HotfixData(); + hotfixData.ID = hotfixId; + hotfixData.RecordID = hotfix; + if (storage.HasRecord((uint)hotfixData.RecordID)) + { + hotfixData.Data.HasValue = true; + storage.WriteRecord((uint)hotfixData.RecordID, GetSessionDbcLocale(), hotfixData.Data.Value); + } + + hotfixQueryResponse.Hotfixes.Add(hotfixData); + } + } + + SendPacket(hotfixQueryResponse); + } + } +} diff --git a/Game/Handlers/InspectHandler.cs b/Game/Handlers/InspectHandler.cs new file mode 100644 index 000000000..f5af84612 --- /dev/null +++ b/Game/Handlers/InspectHandler.cs @@ -0,0 +1,154 @@ +/* + * Copyright (C) 2012-2017 CypherCore + * + * 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, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +using Framework.Constants; +using Game.Entities; +using Game.Guilds; +using Game.Network; +using Game.Network.Packets; + +namespace Game +{ + public partial class WorldSession + { + [WorldPacketHandler(ClientOpcodes.Inspect)] + void HandleInspect(Inspect inspect) + { + Player player = Global.ObjAccessor.FindPlayer(inspect.Target); + if (!player) + { + Log.outDebug(LogFilter.Network, "WorldSession.HandleInspectOpcode: Target {0} not found.", inspect.Target.ToString()); + return; + } + + if (!GetPlayer().IsWithinDistInMap(player, SharedConst.InspectDistance, false)) + return; + + if (GetPlayer().IsValidAttackTarget(player)) + return; + + InspectResult inspectResult = new InspectResult(); + inspectResult.InspecteeGUID = inspect.Target; + + for (byte i = 0; i < EquipmentSlot.End; ++i) + { + Item item = player.GetItemByPos(InventorySlots.Bag0, i); + if (item) + inspectResult.Items.Add(new InspectItemData(item, i)); + } + + inspectResult.ClassID = player.GetClass(); + inspectResult.GenderID = (Gender)player.GetByteValue(PlayerFields.Bytes3, PlayerFieldOffsets.Bytes3OffsetGender); + + if (GetPlayer().CanBeGameMaster() || WorldConfig.GetIntValue(WorldCfg.TalentsInspecting) + (GetPlayer().GetTeamId() == player.GetTeamId() ? 1 : 0) > 1) + { + var talents = player.GetTalentMap(player.GetActiveTalentGroup()); + foreach (var v in talents) + { + if (v.Value != PlayerSpellState.Removed) + inspectResult.Talents.Add((ushort)v.Key); + } + } + + Guild guild = Global.GuildMgr.GetGuildById(player.GetGuildId()); + if (guild) + { + inspectResult.GuildData.HasValue = true; + + InspectGuildData guildData = inspectResult.GuildData.Value; + guildData.GuildGUID = guild.GetGUID(); + guildData.NumGuildMembers = guild.GetMembersCount(); + guildData.AchievementPoints = (int)guild.GetAchievementMgr().GetAchievementPoints(); + } + + inspectResult.InspecteeGUID = inspect.Target; + inspectResult.SpecializationID = (int)player.GetUInt32Value(PlayerFields.CurrentSpecId); + + SendPacket(inspectResult); + } + + [WorldPacketHandler(ClientOpcodes.RequestHonorStats)] + void HandleRequestHonorStatsOpcode(RequestHonorStats request) + { + Player player = Global.ObjAccessor.FindPlayer(request.TargetGUID); + if (!player) + { + Log.outDebug(LogFilter.Network, "WorldSession.HandleRequestHonorStatsOpcode: Target {0} not found.", request.TargetGUID.ToString()); + return; + } + + if (!GetPlayer().IsWithinDistInMap(player, SharedConst.InspectDistance, false)) + return; + + if (GetPlayer().IsValidAttackTarget(player)) + return; + + InspectHonorStats honorStats = new InspectHonorStats(); + honorStats.PlayerGUID = request.TargetGUID; + honorStats.LifetimeHK = player.GetUInt32Value(PlayerFields.LifetimeHonorableKills); + honorStats.YesterdayHK = player.GetUInt16Value(PlayerFields.Kills, 1); + honorStats.TodayHK = player.GetUInt16Value(PlayerFields.Kills, 0); + honorStats.LifetimeMaxRank = 0; /// @todo + + SendPacket(honorStats); + } + + [WorldPacketHandler(ClientOpcodes.InspectPvp)] + void HandleInspectPVP(InspectPVPRequest request) + { + /// @todo: deal with request.InspectRealmAddress + + Player player = Global.ObjAccessor.FindPlayer(request.InspectTarget); + if (!player) + { + Log.outDebug(LogFilter.Network, "WorldSession.HandleInspectPVP: Target {0} not found.", request.InspectTarget.ToString()); + return; + } + + if (!GetPlayer().IsWithinDistInMap(player, SharedConst.InspectDistance, false)) + return; + + if (GetPlayer().IsValidAttackTarget(player)) + return; + + InspectPVPResponse response = new InspectPVPResponse(); + response.ClientGUID = request.InspectTarget; + /// @todo: fill brackets + + SendPacket(response); + } + + [WorldPacketHandler(ClientOpcodes.QueryInspectAchievements)] + void HandleQueryInspectAchievements(QueryInspectAchievements inspect) + { + Player player = Global.ObjAccessor.FindPlayer(inspect.Guid); + if (!player) + { + Log.outDebug(LogFilter.Network, "WorldSession.HandleQueryInspectAchievements: [{0}] inspected unknown Player [{1}]", GetPlayer().GetGUID().ToString(), inspect.Guid.ToString()); + return; + } + + if (!GetPlayer().IsWithinDistInMap(player, SharedConst.InspectDistance, false)) + return; + + if (GetPlayer().IsValidAttackTarget(player)) + return; + + player.SendRespondInspectAchievements(GetPlayer()); + } + } +} diff --git a/Game/Handlers/ItemHandler.cs b/Game/Handlers/ItemHandler.cs new file mode 100644 index 000000000..9df16b83b --- /dev/null +++ b/Game/Handlers/ItemHandler.cs @@ -0,0 +1,1127 @@ +/* + * Copyright (C) 2012-2017 CypherCore + * + * 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, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +using Framework.Constants; +using Framework.Database; +using Game.BattlePets; +using Game.DataStorage; +using Game.Entities; +using Game.Network; +using Game.Network.Packets; +using System; +using System.Collections.Generic; + +namespace Game +{ + public partial class WorldSession + { + [WorldPacketHandler(ClientOpcodes.SplitItem)] + void HandleSplitItem(SplitItem splitItem) + { + if (splitItem.Inv.Items.Count != 0) + { + Log.outError(LogFilter.Network, "WORLD: HandleSplitItemOpcode - Invalid itemCount ({0})", splitItem.Inv.Items.Count); + return; + } + + ushort src = (ushort)((splitItem.FromPackSlot << 8) | splitItem.FromSlot); + ushort dst = (ushort)((splitItem.ToPackSlot << 8) | splitItem.ToSlot); + + if (src == dst) + return; + + if (splitItem.Quantity == 0) + return; //check count - if zero it's fake packet + + if (!_player.IsValidPos(splitItem.FromPackSlot, splitItem.FromSlot, true)) + { + _player.SendEquipError(InventoryResult.ItemNotFound); + return; + } + + if (!_player.IsValidPos(splitItem.ToPackSlot, splitItem.ToSlot, false)) // can be autostore pos + { + _player.SendEquipError(InventoryResult.WrongSlot); + return; + } + + _player.SplitItem(src, dst, (uint)splitItem.Quantity); + } + + [WorldPacketHandler(ClientOpcodes.SwapInvItem)] + void HandleSwapInvenotryItem(SwapInvItem swapInvItem) + { + if (swapInvItem.Inv.Items.Count != 2) + { + Log.outError(LogFilter.Network, "WORLD: HandleSwapInvItemOpcode - Invalid itemCount ({0})", swapInvItem.Inv.Items.Count); + return; + } + + // prevent attempt swap same item to current position generated by client at special checting sequence + if (swapInvItem.Slot1 == swapInvItem.Slot2) + return; + + if (!GetPlayer().IsValidPos(InventorySlots.Bag0, swapInvItem.Slot1, true)) + { + GetPlayer().SendEquipError(InventoryResult.ItemNotFound); + return; + } + + if (!GetPlayer().IsValidPos(InventorySlots.Bag0, swapInvItem.Slot2, true)) + { + GetPlayer().SendEquipError(InventoryResult.WrongSlot); + return; + } + + if (Player.IsBankPos(InventorySlots.Bag0, swapInvItem.Slot1) && !CanUseBank()) + { + Log.outDebug(LogFilter.Network, "WORLD: HandleSwapInvItemOpcode - {0} not found or you can't interact with him.", m_currentBankerGUID.ToString()); + return; + } + + if (Player.IsBankPos(InventorySlots.Bag0, swapInvItem.Slot2) && !CanUseBank()) + { + Log.outDebug(LogFilter.Network, "WORLD: HandleSwapInvItemOpcode - {0} not found or you can't interact with him.", m_currentBankerGUID.ToString()); + return; + } + + ushort src = (ushort)((InventorySlots.Bag0 << 8) | swapInvItem.Slot1); + ushort dst = (ushort)((InventorySlots.Bag0 << 8) | swapInvItem.Slot2); + + GetPlayer().SwapItem(src, dst); + } + + [WorldPacketHandler(ClientOpcodes.AutoEquipItemSlot)] + void HandleAutoEquipItemSlot(AutoEquipItemSlot packet) + { + // cheating attempt, client should never send opcode in that case + if (packet.Inv.Items.Count != 1 || !Player.IsEquipmentPos(InventorySlots.Bag0, packet.ItemDstSlot)) + return; + + Item item = GetPlayer().GetItemByGuid(packet.Item); + ushort dstPos = (ushort)(packet.ItemDstSlot | (InventorySlots.Bag0 << 8)); + ushort srcPos = (ushort)(packet.Inv.Items[0].Slot | (packet.Inv.Items[0].ContainerSlot << 8)); + + if (item == null || item.GetPos() != srcPos || srcPos == dstPos) + return; + + GetPlayer().SwapItem(srcPos, dstPos); + } + + [WorldPacketHandler(ClientOpcodes.SwapItem)] + void HandleSwapItem(SwapItem swapItem) + { + if (swapItem.Inv.Items.Count != 2) + { + Log.outError(LogFilter.Network, "WORLD: HandleSwapItem - Invalid itemCount ({0})", swapItem.Inv.Items.Count); + return; + } + + ushort src = (ushort)((swapItem.ContainerSlotA << 8) | swapItem.SlotA); + ushort dst = (ushort)((swapItem.ContainerSlotB << 8) | swapItem.SlotB); + + var pl = GetPlayer(); + + // prevent attempt swap same item to current position generated by client at special checting sequence + if (src == dst) + return; + + if (!pl.IsValidPos(swapItem.ContainerSlotA, swapItem.SlotA, true)) + { + pl.SendEquipError(InventoryResult.ItemNotFound); + return; + } + + if (!pl.IsValidPos(swapItem.ContainerSlotB, swapItem.SlotB, true)) + { + pl.SendEquipError(InventoryResult.WrongSlot); + return; + } + + + if (Player.IsBankPos(swapItem.ContainerSlotA, swapItem.SlotA) && !CanUseBank()) + { + Log.outDebug(LogFilter.Network, "WORLD: HandleSwapInvItemOpcode - {0} not found or you can't interact with him.", m_currentBankerGUID.ToString()); + return; + } + + if (Player.IsBankPos(swapItem.ContainerSlotB, swapItem.SlotB) && !CanUseBank()) + { + Log.outDebug(LogFilter.Network, "WORLD: HandleSwapInvItemOpcode - {0} not found or you can't interact with him.", m_currentBankerGUID.ToString()); + return; + } + + pl.SwapItem(src, dst); + } + + [WorldPacketHandler(ClientOpcodes.AutoEquipItem)] + void HandleAutoEquipItem(AutoEquipItem autoEquipItem) + { + if (autoEquipItem.Inv.Items.Count != 1) + { + Log.outError(LogFilter.Network, "WORLD: HandleAutoEquipItem - Invalid itemCount ({0})", autoEquipItem.Inv.Items.Count); + return; + } + + var pl = GetPlayer(); + Item srcItem = pl.GetItemByPos(autoEquipItem.PackSlot, autoEquipItem.Slot); + if (srcItem == null) + return; // only at cheat + + ushort dest; + InventoryResult msg = pl.CanEquipItem(ItemConst.NullSlot, out dest, srcItem, !srcItem.IsBag()); + if (msg != InventoryResult.Ok) + { + pl.SendEquipError(msg, srcItem); + return; + } + + ushort src = srcItem.GetPos(); + if (dest == src) // prevent equip in same slot, only at cheat + return; + + Item dstItem = pl.GetItemByPos(dest); + if (dstItem == null) // empty slot, simple case + { + if (!srcItem.GetChildItem().IsEmpty()) + { + InventoryResult childEquipResult = _player.CanEquipChildItem(srcItem); + if (childEquipResult != InventoryResult.Ok) + { + _player.SendEquipError(msg, srcItem); + return; + } + } + + pl.RemoveItem(autoEquipItem.PackSlot, autoEquipItem.Slot, true); + pl.EquipItem(dest, srcItem, true); + if (!srcItem.GetChildItem().IsEmpty()) + _player.EquipChildItem(autoEquipItem.PackSlot, autoEquipItem.Slot, srcItem); + + pl.AutoUnequipOffhandIfNeed(); + } + else // have currently equipped item, not simple case + { + byte dstbag = dstItem.GetBagSlot(); + byte dstslot = dstItem.GetSlot(); + + msg = pl.CanUnequipItem(dest, !srcItem.IsBag()); + if (msg != InventoryResult.Ok) + { + pl.SendEquipError(msg, dstItem); + return; + } + + if (!dstItem.HasFlag(ItemFields.Flags, ItemFieldFlags.Child)) + { + // check dest.src move possibility + List sSrc = new List(); + ushort eSrc = 0; + if (pl.IsInventoryPos(src)) + { + msg = pl.CanStoreItem(autoEquipItem.PackSlot, autoEquipItem.Slot, sSrc, dstItem, true); + if (msg != InventoryResult.Ok) + msg = pl.CanStoreItem(autoEquipItem.PackSlot, ItemConst.NullSlot, sSrc, dstItem, true); + if (msg != InventoryResult.Ok) + msg = pl.CanStoreItem(ItemConst.NullBag, ItemConst.NullSlot, sSrc, dstItem, true); + } + else if (Player.IsBankPos(src)) + { + msg = pl.CanBankItem(autoEquipItem.PackSlot, autoEquipItem.Slot, sSrc, dstItem, true); + if (msg != InventoryResult.Ok) + msg = pl.CanBankItem(autoEquipItem.PackSlot, ItemConst.NullSlot, sSrc, dstItem, true); + if (msg != InventoryResult.Ok) + msg = pl.CanBankItem(ItemConst.NullBag, ItemConst.NullSlot, sSrc, dstItem, true); + } + else if (Player.IsEquipmentPos(src)) + { + msg = pl.CanEquipItem(autoEquipItem.Slot, out eSrc, dstItem, true); + if (msg == InventoryResult.Ok) + msg = pl.CanUnequipItem(eSrc, true); + } + + if (msg == InventoryResult.Ok && Player.IsEquipmentPos(dest) && !srcItem.GetChildItem().IsEmpty()) + msg = _player.CanEquipChildItem(srcItem); + + if (msg != InventoryResult.Ok) + { + pl.SendEquipError(msg, dstItem, srcItem); + return; + } + + // now do moves, remove... + pl.RemoveItem(dstbag, dstslot, false); + pl.RemoveItem(autoEquipItem.PackSlot, autoEquipItem.Slot, false); + + // add to dest + pl.EquipItem(dest, srcItem, true); + + // add to src + if (pl.IsInventoryPos(src)) + pl.StoreItem(sSrc, dstItem, true); + else if (Player.IsBankPos(src)) + pl.BankItem(sSrc, dstItem, true); + else if (Player.IsEquipmentPos(src)) + pl.EquipItem(eSrc, dstItem, true); + + if (Player.IsEquipmentPos(dest) && !srcItem.GetChildItem().IsEmpty()) + _player.EquipChildItem(autoEquipItem.PackSlot, autoEquipItem.Slot, srcItem); + } + else + { + Item parentItem = _player.GetItemByGuid(dstItem.GetGuidValue(ItemFields.Creator)); + if (parentItem) + { + if (Player.IsEquipmentPos(dest)) + { + _player.AutoUnequipChildItem(parentItem); + // dest is now empty + _player.SwapItem(src, dest); + // src is now empty + _player.SwapItem(parentItem.GetPos(), src); + } + } + } + + pl.AutoUnequipOffhandIfNeed(); + } + } + + [WorldPacketHandler(ClientOpcodes.DestroyItem)] + void HandleDestroyItem(DestroyItem destroyItem) + { + ushort pos = (ushort)((destroyItem.ContainerId << 8) | destroyItem.SlotNum); + + // prevent drop unequipable items (in combat, for example) and non-empty bags + if (Player.IsEquipmentPos(pos) || Player.IsBagPos(pos)) + { + InventoryResult msg = _player.CanUnequipItem(pos, false); + if (msg != InventoryResult.Ok) + { + _player.SendEquipError(msg, _player.GetItemByPos(pos)); + return; + } + } + + Item pItem = _player.GetItemByPos(destroyItem.ContainerId, destroyItem.SlotNum); + if (pItem == null) + { + _player.SendEquipError(InventoryResult.ItemNotFound); + return; + } + + if (pItem.GetTemplate().GetFlags().HasAnyFlag(ItemFlags.NoUserDestroy)) + { + _player.SendEquipError(InventoryResult.DropBoundItem); + return; + } + + if (destroyItem.Count != 0) + { + uint i_count = destroyItem.Count; + _player.DestroyItemCount(pItem, ref i_count, true); + } + else + _player.DestroyItem(destroyItem.ContainerId, destroyItem.SlotNum, true); + } + + [WorldPacketHandler(ClientOpcodes.ReadItem)] + void HandleReadItem(ReadItem readItem) + { + Item item = _player.GetItemByPos(readItem.PackSlot, readItem.Slot); + if (item != null && item.GetTemplate().GetPageText() != 0) + { + InventoryResult msg = _player.CanUseItem(item); + if (msg == InventoryResult.Ok) + { + ReadItemResultOK packet = new ReadItemResultOK(); + packet.Item = item.GetGUID(); + SendPacket(packet); + } + else + { + /// @todo: 6.x research new values + /*WorldPackets.Item.ReadItemResultFailed packet; + packet.Item = item->GetGUID(); + packet.Subcode = ??; + packet.Delay = ??; + SendPacket(packet.Write());*/ + + Log.outInfo(LogFilter.Network, "STORAGE: Unable to read item"); + _player.SendEquipError(msg, item); + } + } + else + _player.SendEquipError(InventoryResult.ItemNotFound); + } + + [WorldPacketHandler(ClientOpcodes.SellItem)] + void HandleSellItem(SellItem packet) + { + if (packet.ItemGUID.IsEmpty()) + return; + + var pl = GetPlayer(); + + Creature creature = pl.GetNPCIfCanInteractWith(packet.VendorGUID, NPCFlags.Vendor); + if (creature == null) + { + Log.outDebug(LogFilter.Network, "WORLD: HandleSellItemOpcode - {0} not found or you can not interact with him.", packet.VendorGUID.ToString()); + pl.SendSellError(SellResult.CantFindVendor, null, packet.ItemGUID); + return; + } + + // remove fake death + if (pl.HasUnitState(UnitState.Died)) + pl.RemoveAurasByType(AuraType.FeignDeath); + + Item pItem = pl.GetItemByGuid(packet.ItemGUID); + if (pItem != null) + { + // prevent sell not owner item + if (pl.GetGUID() != pItem.GetOwnerGUID()) + { + pl.SendSellError(SellResult.CantSellItem, creature, packet.ItemGUID); + return; + } + + // prevent sell non empty bag by drag-and-drop at vendor's item list + if (pItem.IsNotEmptyBag()) + { + pl.SendSellError(SellResult.CantSellItem, creature, packet.ItemGUID); + return; + } + + // prevent sell currently looted item + if (pl.GetLootGUID() == pItem.GetGUID()) + { + pl.SendSellError(SellResult.CantSellItem, creature, packet.ItemGUID); + return; + } + + // prevent selling item for sellprice when the item is still refundable + // this probably happens when right clicking a refundable item, the client sends both + // CMSG_SELL_ITEM and CMSG_REFUND_ITEM (unverified) + if (pItem.HasFlag(ItemFields.Flags, ItemFieldFlags.Refundable)) + return; // Therefore, no feedback to client + + // special case at auto sell (sell all) + if (packet.Amount == 0) + packet.Amount = pItem.GetCount(); + else + { + // prevent sell more items that exist in stack (possible only not from client) + if (packet.Amount > pItem.GetCount()) + { + pl.SendSellError(SellResult.CantSellItem, creature, packet.ItemGUID); + return; + } + } + + ItemTemplate pProto = pItem.GetTemplate(); + if (pProto != null) + { + if (pProto.GetSellPrice() > 0) + { + if (packet.Amount < pItem.GetCount()) // need split items + { + Item pNewItem = pItem.CloneItem(packet.Amount, pl); + if (pNewItem == null) + { + Log.outError(LogFilter.Network, "WORLD: HandleSellItemOpcode - could not create clone of item {0}; count = {1}", pItem.GetEntry(), packet.Amount); + pl.SendSellError(SellResult.CantSellItem, creature, packet.ItemGUID); + return; + } + + pItem.SetCount(pItem.GetCount() - packet.Amount); + pl.ItemRemovedQuestCheck(pItem.GetEntry(), packet.Amount); + if (pl.IsInWorld) + pItem.SendUpdateToPlayer(pl); + pItem.SetState(ItemUpdateState.Changed, pl); + + pl.AddItemToBuyBackSlot(pNewItem); + if (pl.IsInWorld) + pNewItem.SendUpdateToPlayer(pl); + } + else + { + pl.ItemRemovedQuestCheck(pItem.GetEntry(), pItem.GetCount()); + pl.RemoveItem(pItem.GetBagSlot(), pItem.GetSlot(), true); + Item.RemoveItemFromUpdateQueueOf(pItem, pl); + pl.AddItemToBuyBackSlot(pItem); + } + + uint money = pProto.GetSellPrice() * packet.Amount; + pl.ModifyMoney(money); + pl.UpdateCriteria(CriteriaTypes.MoneyFromVendors, money); + } + else + pl.SendSellError(SellResult.CantSellItem, creature, packet.ItemGUID); + return; + } + } + pl.SendSellError(SellResult.CantSellItem, creature, packet.ItemGUID); + return; + } + + [WorldPacketHandler(ClientOpcodes.BuyBackItem)] + void HandleBuybackItem(BuyBackItem packet) + { + Creature creature = _player.GetNPCIfCanInteractWith(packet.VendorGUID, NPCFlags.Vendor); + if (creature == null) + { + Log.outDebug(LogFilter.Network, "WORLD: HandleBuybackItem - {0} not found or you can not interact with him.", packet.VendorGUID.ToString()); + _player.SendSellError(SellResult.CantFindVendor, null, ObjectGuid.Empty); + return; + } + + // remove fake death + if (_player.HasUnitState(UnitState.Died)) + _player.RemoveAurasByType(AuraType.FeignDeath); + + Item pItem = _player.GetItemFromBuyBackSlot(packet.Slot); + if (pItem != null) + { + uint price = _player.GetUInt32Value(PlayerFields.BuyBackPrice1 + (int)(packet.Slot - InventorySlots.BuyBackStart)); + if (!_player.HasEnoughMoney(price)) + { + _player.SendBuyError(BuyResult.NotEnoughtMoney, creature, pItem.GetEntry()); + return; + } + + List dest = new List(); + InventoryResult msg = _player.CanStoreItem(ItemConst.NullBag, ItemConst.NullSlot, dest, pItem, false); + if (msg == InventoryResult.Ok) + { + _player.ModifyMoney(-price); + _player.RemoveItemFromBuyBackSlot(packet.Slot, false); + _player.ItemAddedQuestCheck(pItem.GetEntry(), pItem.GetCount()); + _player.UpdateCriteria(CriteriaTypes.ReceiveEpicItem, pItem.GetEntry(), pItem.GetCount()); + _player.StoreItem(dest, pItem, true); + } + else + _player.SendEquipError(msg, pItem); + return; + } + else + _player.SendBuyError(BuyResult.CantFindItem, creature, 0); + } + + [WorldPacketHandler(ClientOpcodes.BuyItem)] + void HandleBuyItem(BuyItem packet) + { + // client expects count starting at 1, and we send vendorslot+1 to client already + if (packet.Muid > 0) + --packet.Muid; + else + return; // cheating + + switch (packet.ItemType) + { + case ItemVendorType.Item: + Item bagItem = GetPlayer().GetItemByGuid(packet.ContainerGUID); + + byte bag = ItemConst.NullBag; + if (bagItem != null && bagItem.IsBag()) + bag = bagItem.GetSlot(); + else if (packet.ContainerGUID == GetPlayer().GetGUID()) // The client sends the player guid when trying to store an item in the default backpack + bag = InventorySlots.Bag0; + + GetPlayer().BuyItemFromVendorSlot(packet.VendorGUID, packet.Muid, packet.Item.ItemID, (byte)packet.Quantity, bag, (byte)packet.Slot); + break; + case ItemVendorType.Currency: + GetPlayer().BuyCurrencyFromVendorSlot(packet.VendorGUID, packet.Muid, packet.Item.ItemID, (byte)packet.Quantity); + break; + default: + Log.outDebug(LogFilter.Network, "WORLD: received wrong itemType {0} in HandleBuyItem", packet.ItemType); + break; + } + } + + [WorldPacketHandler(ClientOpcodes.AutoStoreBagItem)] + void HandleAutoStoreBagItem(AutoStoreBagItem packet) + { + if (!packet.Inv.Items.Empty()) + { + Log.outError(LogFilter.Network, "HandleAutoStoreBagItemOpcode - Invalid itemCount ({0})", packet.Inv.Items.Count); + return; + } + + Item item = GetPlayer().GetItemByPos(packet.ContainerSlotA, packet.SlotA); + if (!item) + return; + + if (!GetPlayer().IsValidPos(packet.ContainerSlotB, ItemConst.NullSlot, false)) // can be autostore pos + { + GetPlayer().SendEquipError(InventoryResult.WrongSlot); + return; + } + + ushort src = item.GetPos(); + InventoryResult msg; + // check unequip potability for equipped items and bank bags + if (Player.IsEquipmentPos(src) || Player.IsBagPos(src)) + { + msg = GetPlayer().CanUnequipItem(src, !Player.IsBagPos(src)); + if (msg != InventoryResult.Ok) + { + GetPlayer().SendEquipError(msg, item); + return; + } + } + + List dest = new List(); + msg = GetPlayer().CanStoreItem(packet.ContainerSlotB, ItemConst.NullSlot, dest, item, false); + if (msg != InventoryResult.Ok) + { + GetPlayer().SendEquipError(msg, item); + return; + } + + // no-op: placed in same slot + if (dest.Count == 1 && dest[0].pos == src) + { + // just remove grey item state + GetPlayer().SendEquipError(InventoryResult.InternalBagError, item); + return; + } + + GetPlayer().RemoveItem(packet.ContainerSlotA, packet.SlotA, true); + GetPlayer().StoreItem(dest, item, true); + } + + public void SendEnchantmentLog(ObjectGuid owner, ObjectGuid caster, ObjectGuid itemGuid, uint itemId, uint enchantId, uint slot) + { + EnchantmentLog packet = new EnchantmentLog(); + packet.Caster = caster; + packet.Owner = owner; + packet.ItemGUID = itemGuid; + packet.ItemID = itemId; + packet.Enchantment = enchantId; + packet.EnchantSlot = slot; + + GetPlayer().SendMessageToSet(packet, true); + } + + public void SendItemEnchantTimeUpdate(ObjectGuid Playerguid, ObjectGuid Itemguid, uint slot, uint Duration) + { + ItemEnchantTimeUpdate data = new ItemEnchantTimeUpdate(); + data.ItemGuid = Itemguid; + data.DurationLeft = Duration; + data.Slot = slot; + data.OwnerGuid = Playerguid; + SendPacket(data); + } + + [WorldPacketHandler(ClientOpcodes.WrapItem)] + void HandleWrapItem(WrapItem packet) + { + if (packet.Inv.Items.Count != 2) + { + Log.outError(LogFilter.Network, "HandleWrapItem - Invalid itemCount ({0})", packet.Inv.Items.Count); + return; + } + + // Gift + byte giftContainerSlot = packet.Inv.Items[0].ContainerSlot; + byte giftSlot = packet.Inv.Items[0].Slot; + // Item + byte itemContainerSlot = packet.Inv.Items[1].ContainerSlot; + byte itemSlot = packet.Inv.Items[1].Slot; + + Item gift = GetPlayer().GetItemByPos(giftContainerSlot, giftSlot); + if (!gift) + { + GetPlayer().SendEquipError(InventoryResult.ItemNotFound, gift); + return; + } + + if (!gift.GetTemplate().GetFlags().HasAnyFlag(ItemFlags.IsWrapper)) // cheating: non-wrapper wrapper + { + GetPlayer().SendEquipError(InventoryResult.ItemNotFound, gift); + return; + } + + Item item = GetPlayer().GetItemByPos(itemContainerSlot, itemSlot); + if (!item) + { + GetPlayer().SendEquipError(InventoryResult.ItemNotFound, item); + return; + } + + if (item == gift) // not possable with pacjket from real client + { + GetPlayer().SendEquipError(InventoryResult.CantWrapWrapped, item); + return; + } + + if (item.IsEquipped()) + { + GetPlayer().SendEquipError(InventoryResult.CantWrapEquipped, item); + return; + } + + if (item.GetUInt64Value(ItemFields.GiftCreator) != 0) // HasFlag(ITEM_FIELD_FLAGS, ITEM_FLAGS_WRAPPED); + { + GetPlayer().SendEquipError(InventoryResult.CantWrapWrapped, item); + return; + } + + if (item.IsBag()) + { + GetPlayer().SendEquipError(InventoryResult.CantWrapBags, item); + return; + } + + if (item.IsSoulBound()) + { + GetPlayer().SendEquipError(InventoryResult.CantWrapBound, item); + return; + } + + if (item.GetMaxStackCount() != 1) + { + GetPlayer().SendEquipError(InventoryResult.CantWrapStackable, item); + return; + } + + // maybe not correct check (it is better than nothing) + if (item.GetTemplate().GetMaxCount() > 0) + { + GetPlayer().SendEquipError(InventoryResult.CantWrapUnique, item); + return; + } + + SQLTransaction trans = new SQLTransaction(); + + PreparedStatement stmt = DB.Characters.GetPreparedStatement(CharStatements.INS_CHAR_GIFT); + stmt.AddValue(0, item.GetOwnerGUID().GetCounter()); + stmt.AddValue(1, item.GetGUID().GetCounter()); + stmt.AddValue(2, item.GetEntry()); + stmt.AddValue(3, item.GetUInt32Value(ItemFields.Flags)); + trans.Append(stmt); + + item.SetEntry(gift.GetEntry()); + + switch (item.GetEntry()) + { + case 5042: + item.SetEntry(5043); + break; + case 5048: + item.SetEntry(5044); + break; + case 17303: + item.SetEntry(17302); + break; + case 17304: + item.SetEntry(17305); + break; + case 17307: + item.SetEntry(17308); + break; + case 21830: + item.SetEntry(21831); + break; + } + + item.SetGuidValue(ItemFields.GiftCreator, GetPlayer().GetGUID()); + item.SetUInt32Value(ItemFields.Flags, (uint)ItemFieldFlags.Wrapped); + item.SetState(ItemUpdateState.Changed, GetPlayer()); + + if (item.GetState() == ItemUpdateState.New) // save new item, to have alway for `character_gifts` record in `item_instance` + { + // after save it will be impossible to remove the item from the queue + Item.RemoveItemFromUpdateQueueOf(item, GetPlayer()); + item.SaveToDB(trans); // item gave inventory record unchanged and can be save standalone + } + DB.Characters.CommitTransaction(trans); + + uint count = 1; + GetPlayer().DestroyItemCount(gift, ref count, true); + } + + [WorldPacketHandler(ClientOpcodes.SocketGems)] + void HandleSocketGems(SocketGems socketGems) + { + if (socketGems.ItemGuid.IsEmpty()) + return; + + //cheat . tried to socket same gem multiple times + if ((!socketGems.GemItem[0].IsEmpty() && (socketGems.GemItem[0] == socketGems.GemItem[1] || socketGems.GemItem[0] == socketGems.GemItem[2])) || + (!socketGems.GemItem[1].IsEmpty() && (socketGems.GemItem[1] == socketGems.GemItem[2]))) + return; + + Item itemTarget = GetPlayer().GetItemByGuid(socketGems.ItemGuid); + if (!itemTarget) //missing item to socket + return; + + ItemTemplate itemProto = itemTarget.GetTemplate(); + if (itemProto == null) + return; + + //this slot is excepted when applying / removing meta gem bonus + byte slot = itemTarget.IsEquipped() ? itemTarget.GetSlot() : ItemConst.NullSlot; + + Item[] gems = new Item[ItemConst.MaxGemSockets]; + ItemDynamicFieldGems[] gemData = new ItemDynamicFieldGems[ItemConst.MaxGemSockets]; + GemPropertiesRecord[] gemProperties = new GemPropertiesRecord[ItemConst.MaxGemSockets]; + ItemDynamicFieldGems[] oldGemData = new ItemDynamicFieldGems[ItemConst.MaxGemSockets]; + + + for (int i = 0; i < ItemConst.MaxGemSockets; ++i) + { + Item gem = _player.GetItemByGuid(socketGems.GemItem[i]); + if (gem) + { + gems[i] = gem; + gemData[i].ItemId = gem.GetEntry(); + gemData[i].Context = (byte)gem.GetUInt32Value(ItemFields.Context); + for (ushort b = 0; b < gem.GetDynamicValues(ItemDynamicFields.BonusListIds).Count && b < 16; ++b) + gemData[i].BonusListIDs[b] = (ushort)gem.GetDynamicValue(ItemDynamicFields.BonusListIds, b); + + gemProperties[i] = CliDB.GemPropertiesStorage.LookupByKey(gem.GetTemplate().GetGemProperties()); + } + + oldGemData[i] = itemTarget.GetGem((ushort)i); + } + + // Find first prismatic socket + uint firstPrismatic = 0; + while (firstPrismatic < ItemConst.MaxGemSockets && itemTarget.GetSocketColor(firstPrismatic) != 0) + ++firstPrismatic; + + for (uint i = 0; i < ItemConst.MaxGemSockets; ++i) //check for hack maybe + { + if (gemProperties[i] == null) + continue; + + // tried to put gem in socket where no socket exists (take care about prismatic sockets) + if (itemTarget.GetSocketColor(i) == 0) + { + // no prismatic socket + if (itemTarget.GetEnchantmentId(EnchantmentSlot.Prismatic) == 0) + return; + + if (i != firstPrismatic) + return; + } + + // Gem must match socket color + if (ItemConst.SocketColorToGemTypeMask[(int)itemTarget.GetSocketColor(i)] != gemProperties[i].Type) + { + // unless its red, blue, yellow or prismatic + if (!ItemConst.SocketColorToGemTypeMask[(int)itemTarget.GetSocketColor(i)].HasAnyFlag(SocketColor.Prismatic) || !gemProperties[i].Type.HasAnyFlag(SocketColor.Prismatic)) + return; + } + } + + // check unique-equipped conditions + for (int i = 0; i < ItemConst.MaxGemSockets; ++i) + { + if (!gems[i]) + continue; + + // continue check for case when attempt add 2 similar unique equipped gems in one item. + ItemTemplate iGemProto = gems[i].GetTemplate(); + + // unique item (for new and already placed bit removed enchantments + if (iGemProto.GetFlags().HasAnyFlag(ItemFlags.UniqueEquippable)) + { + for (int j = 0; j < ItemConst.MaxGemSockets; ++j) + { + if (i == j) // skip self + continue; + + if (gems[j]) + { + if (iGemProto.GetId() == gems[j].GetEntry()) + { + GetPlayer().SendEquipError(InventoryResult.ItemUniqueEquippableSocketed, itemTarget); + return; + } + } + else if (oldGemData[j] != null) + { + if (iGemProto.GetId() == oldGemData[j].ItemId) + { + GetPlayer().SendEquipError(InventoryResult.ItemUniqueEquippableSocketed, itemTarget); + return; + } + } + } + } + + // unique limit type item + int limit_newcount = 0; + if (iGemProto.GetItemLimitCategory() != 0) + { + ItemLimitCategoryRecord limitEntry = CliDB.ItemLimitCategoryStorage.LookupByKey(iGemProto.GetItemLimitCategory()); + if (limitEntry != null) + { + // NOTE: limitEntry.mode is not checked because if item has limit then it is applied in equip case + for (int j = 0; j < ItemConst.MaxGemSockets; ++j) + { + if (gems[j]) + { + // new gem + if (iGemProto.GetItemLimitCategory() == gems[j].GetTemplate().GetItemLimitCategory()) + ++limit_newcount; + } + else if (oldGemData[j] != null) + { + // existing gem + ItemTemplate jProto = Global.ObjectMgr.GetItemTemplate(oldGemData[j].ItemId); + if (jProto != null) + if (iGemProto.GetItemLimitCategory() == jProto.GetItemLimitCategory()) + ++limit_newcount; + } + } + + if (limit_newcount > 0 && limit_newcount > limitEntry.Quantity) + { + GetPlayer().SendEquipError(InventoryResult.ItemUniqueEquippableSocketed, itemTarget); + return; + } + } + } + + // for equipped item check all equipment for duplicate equipped gems + if (itemTarget.IsEquipped()) + { + InventoryResult res = GetPlayer().CanEquipUniqueItem(gems[i], slot, (uint)Math.Max(limit_newcount, 0)); + if (res != 0) + { + GetPlayer().SendEquipError(res, itemTarget); + return; + } + } + } + + bool SocketBonusActivated = itemTarget.GemsFitSockets(); //save state of socketbonus + GetPlayer().ToggleMetaGemsActive(slot, false); //turn off all metagems (except for the target item) + + //if a meta gem is being equipped, all information has to be written to the item before testing if the conditions for the gem are met + + //remove ALL mods - gem can change item level + if (itemTarget.IsEquipped()) + _player._ApplyItemMods(itemTarget, itemTarget.GetSlot(), false); + + for (ushort i = 0; i < ItemConst.MaxGemSockets; ++i) + { + if (gems[i]) + { + uint gemScalingLevel = _player.getLevel(); + uint fixedLevel = gems[i].GetModifier(ItemModifier.ScalingStatDistributionFixedLevel); + if (fixedLevel != 0) + gemScalingLevel = fixedLevel; + + itemTarget.SetGem(i, gemData[i], gemScalingLevel); + + if (gemProperties[i] != null && gemProperties[i].EnchantID != 0) + itemTarget.SetEnchantment(EnchantmentSlot.Sock1 + i, gemProperties[i].EnchantID, 0, 0, GetPlayer().GetGUID()); + + uint gemCount = 1; + GetPlayer().DestroyItemCount(gems[i], ref gemCount, true); + } + } + + if (itemTarget.IsEquipped()) + _player._ApplyItemMods(itemTarget, itemTarget.GetSlot(), true); + + Item childItem = _player.GetChildItemByGuid(itemTarget.GetChildItem()); + if (childItem) + { + if (childItem.IsEquipped()) + _player._ApplyItemMods(childItem, childItem.GetSlot(), false); + childItem.CopyArtifactDataFromParent(itemTarget); + if (childItem.IsEquipped()) + _player._ApplyItemMods(childItem, childItem.GetSlot(), true); + } + + bool SocketBonusToBeActivated = itemTarget.GemsFitSockets();//current socketbonus state + if (SocketBonusActivated ^ SocketBonusToBeActivated) //if there was a change... + { + GetPlayer().ApplyEnchantment(itemTarget, EnchantmentSlot.Bonus, false); + itemTarget.SetEnchantment(EnchantmentSlot.Bonus, SocketBonusToBeActivated ? itemTarget.GetTemplate().GetSocketBonus() : 0, 0, 0, GetPlayer().GetGUID()); + GetPlayer().ApplyEnchantment(itemTarget, EnchantmentSlot.Bonus, true); + //it is not displayed, client has an inbuilt system to determine if the bonus is activated + } + + GetPlayer().ToggleMetaGemsActive(slot, true); //turn on all metagems (except for target item) + + GetPlayer().RemoveTradeableItem(itemTarget); + itemTarget.ClearSoulboundTradeable(GetPlayer()); // clear tradeable flag + + itemTarget.SendUpdateSockets(); + } + + [WorldPacketHandler(ClientOpcodes.CancelTempEnchantment)] + void HandleCancelTempEnchantment(CancelTempEnchantment packet) + { + // apply only to equipped item + if (!Player.IsEquipmentPos(InventorySlots.Bag0, (byte)packet.Slot)) + return; + + Item item = GetPlayer().GetItemByPos(InventorySlots.Bag0, (byte)packet.Slot); + if (!item) + return; + + if (item.GetEnchantmentId(EnchantmentSlot.Temp) == 0) + return; + + GetPlayer().ApplyEnchantment(item, EnchantmentSlot.Temp, false); + item.ClearEnchantment(EnchantmentSlot.Temp); + } + + [WorldPacketHandler(ClientOpcodes.GetItemPurchaseData)] + void HandleGetItemPurchaseData(GetItemPurchaseData packet) + { + Item item = GetPlayer().GetItemByGuid(packet.ItemGUID); + if (!item) + { + Log.outDebug(LogFilter.Network, "HandleGetItemPurchaseData: Item {0} not found!", packet.ItemGUID.ToString()); + return; + } + + GetPlayer().SendRefundInfo(item); + } + + [WorldPacketHandler(ClientOpcodes.ItemPurchaseRefund)] + void HandleItemRefund(ItemPurchaseRefund packet) + { + Item item = GetPlayer().GetItemByGuid(packet.ItemGUID); + if (!item) + { + Log.outDebug(LogFilter.Network, "WorldSession.HandleItemRefund: Item {0} not found!", packet.ItemGUID.ToString()); + return; + } + + // Don't try to refund item currently being disenchanted + if (GetPlayer().GetLootGUID() == packet.ItemGUID) + return; + + GetPlayer().RefundItem(item); + } + + bool CanUseBank(ObjectGuid bankerGUID = default(ObjectGuid)) + { + // bankerGUID parameter is optional, set to 0 by default. + if (bankerGUID.IsEmpty()) + bankerGUID = m_currentBankerGUID; + + bool isUsingBankCommand = (bankerGUID == GetPlayer().GetGUID() && bankerGUID == m_currentBankerGUID); + + if (!isUsingBankCommand) + { + Creature creature = GetPlayer().GetNPCIfCanInteractWith(bankerGUID, NPCFlags.Banker); + if (!creature) + return false; + } + + return true; + } + + [WorldPacketHandler(ClientOpcodes.UseCritterItem)] + void HandleUseCritterItem(UseCritterItem useCritterItem) + { + Item item = GetPlayer().GetItemByGuid(useCritterItem.ItemGuid); + if (!item) + return; + + if (item.GetTemplate().Effects.Count < 2) + return; + + uint spellToLearn = item.GetTemplate().Effects[1].SpellID; + foreach (BattlePetSpeciesRecord entry in CliDB.BattlePetSpeciesStorage.Values) + { + if (entry.SummonSpellID == spellToLearn) + { + GetBattlePetMgr().AddPet(entry.Id, entry.CreatureID, BattlePetMgr.RollPetBreed(entry.Id), BattlePetMgr.GetDefaultPetQuality(entry.Id)); + _player.UpdateCriteria(CriteriaTypes.OwnBattlePetCount); + break; + } + } + + GetPlayer().DestroyItem(item.GetBagSlot(), item.GetSlot(), true); + } + + [WorldPacketHandler(ClientOpcodes.UpgradeItem)] + void HandleUpgradeItem(UpgradeItem upgradeItem) + { + ItemUpgradeResult itemUpgradeResult = new ItemUpgradeResult(); + if (!_player.GetNPCIfCanInteractWith(upgradeItem.ItemMaster, NPCFlags.ItemUpgradeMaster)) + { + Log.outDebug(LogFilter.Network, "WORLD: HandleUpgradeItems - {0} not found or player can't interact with it.", upgradeItem.ItemMaster.ToString()); + itemUpgradeResult.Success = false; + SendPacket(itemUpgradeResult); + return; + } + + Item item = _player.GetItemByGuid(upgradeItem.ItemGUID); + if (!item) + { + Log.outDebug(LogFilter.Network, "WORLD: HandleUpgradeItems: Item {0} not found!", upgradeItem.ItemGUID.ToString()); + itemUpgradeResult.Success = false; + SendPacket(itemUpgradeResult); + return; + } + + ItemUpgradeRecord itemUpgradeEntry = CliDB.ItemUpgradeStorage.LookupByKey(upgradeItem.UpgradeID); + if (itemUpgradeEntry == null) + { + Log.outDebug(LogFilter.Network, "WORLD: HandleUpgradeItems - ItemUpgradeEntry ({0}) not found.", upgradeItem.UpgradeID); + itemUpgradeResult.Success = false; + SendPacket(itemUpgradeResult); + return; + } + + // Check if player has enough currency + if (!_player.HasCurrency(itemUpgradeEntry.CurrencyID, itemUpgradeEntry.CurrencyCost)) + { + Log.outDebug(LogFilter.Network, "WORLD: HandleUpgradeItems - Player has not enougth currency (ID: {0}, Cost: {1}) not found.", itemUpgradeEntry.CurrencyID, itemUpgradeEntry.CurrencyCost); + itemUpgradeResult.Success = false; + SendPacket(itemUpgradeResult); + return; + } + + uint currentUpgradeId = item.GetModifier(ItemModifier.UpgradeId); + if (currentUpgradeId != itemUpgradeEntry.PrevItemUpgradeID) + { + Log.outDebug(LogFilter.Network, "WORLD: HandleUpgradeItems - ItemUpgradeEntry ({0}) is not related to this ItemUpgradePath ({1}).", itemUpgradeEntry.Id, currentUpgradeId); + itemUpgradeResult.Success = false; + SendPacket(itemUpgradeResult); + return; + } + + itemUpgradeResult.Success = true; + SendPacket(itemUpgradeResult); + + if (item.IsEquipped()) + _player._ApplyItemBonuses(item, item.GetSlot(), false); + + item.SetModifier(ItemModifier.UpgradeId, itemUpgradeEntry.Id); + + if (item.IsEquipped()) + _player._ApplyItemBonuses(item, item.GetSlot(), true); + + item.SetState(ItemUpdateState.Changed, _player); + _player.ModifyCurrency((CurrencyTypes)itemUpgradeEntry.CurrencyID, -(int)itemUpgradeEntry.CurrencyCost); + } + + } +} diff --git a/Game/Handlers/LFGHandler.cs b/Game/Handlers/LFGHandler.cs new file mode 100644 index 000000000..7a9ae7aee --- /dev/null +++ b/Game/Handlers/LFGHandler.cs @@ -0,0 +1,577 @@ +/* + * Copyright (C) 2012-2017 CypherCore + * + * 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, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +using Framework.Constants; +using Game.DungeonFinding; +using Game.Entities; +using Game.Groups; +using Game.Network; +using Game.Network.Packets; +using System.Collections.Generic; +using System.Linq; + +namespace Game +{ + public partial class WorldSession + { + void BuildQuestReward(LfgPlayerQuestReward questReward, Quest quest, Player player) + { + byte rewCount = (byte)(quest.GetRewItemsCount() + quest.GetRewCurrencyCount()); + + questReward.RewardMoney = player.GetQuestMoneyReward(quest); + questReward.RewardXP = player.GetQuestXPReward(quest); + if (rewCount != 0) + { + for (byte i = 0; i < SharedConst.QuestRewardCurrencyCount; ++i) + { + var rewardCurrency = new LfgPlayerQuestReward.LfgPlayerQuestRewardCurrency(); + uint currencyId = quest.RewardCurrencyId[i]; + if (currencyId != 0) + { + rewardCurrency.CurrencyID = currencyId; + rewardCurrency.Quantity = quest.RewardCurrencyCount[i]; + questReward.Currency.Add(rewardCurrency); + } + } + + for (byte i = 0; i < SharedConst.QuestRewardItemCount; ++i) + { + var rewardItem = new LfgPlayerQuestReward.LfgPlayerQuestRewardItem(); + uint itemId = quest.RewardItemId[i]; + if (itemId != 0) + { + ItemTemplate item = Global.ObjectMgr.GetItemTemplate(itemId); + rewardItem.ItemID = itemId; + rewardItem.Quantity = quest.RewardItemCount[i]; + questReward.Items.Add(rewardItem); + } + } + } + } + + [WorldPacketHandler(ClientOpcodes.DfJoin)] + void HandleDfJoin(DFJoin dfJoin) + { + if (!Global.LFGMgr.isOptionEnabled(LfgOptions.EnableDungeonFinder | LfgOptions.EnableRaidBrowser) || + (GetPlayer().GetGroup() && GetPlayer().GetGroup().GetLeaderGUID() != GetPlayer().GetGUID() && + (GetPlayer().GetGroup().GetMembersCount() == MapConst.MaxGroupSize || !GetPlayer().GetGroup().isLFGGroup()))) + return; + + if (dfJoin.Slots.Empty()) + { + Log.outDebug(LogFilter.Lfg, "ClientOpcodes.DfJoin {0} no dungeons selected", GetPlayerInfo()); + return; + } + + List newDungeons = new List(); + for (int i = 0; i < dfJoin.Slots.Count; ++i) + { + uint dungeon = dfJoin.Slots[i]; + if (dungeon != 0) + newDungeons.Add(dungeon); + + } + + Global.LFGMgr.JoinLfg(GetPlayer(), dfJoin.Roles, newDungeons, dfJoin.Comment); + } + + [WorldPacketHandler(ClientOpcodes.DfLeave)] + void HandleDfLeave(DFLeave dfLeave) + { + ObjectGuid leaveGuid = dfLeave.Ticket.RequesterGuid; + Group group = GetPlayer().GetGroup(); + ObjectGuid guid = GetPlayer().GetGUID(); + ObjectGuid gguid = group ? group.GetGUID() : guid; + + // Check cheating - only leader can leave the queue + if (!group || group.GetLeaderGUID() == leaveGuid) + Global.LFGMgr.LeaveLfg(gguid); + } + + [WorldPacketHandler(ClientOpcodes.DfProposalResponse)] + void HandleDfProposalResponse(DFProposalResponse dfProposalResponse) + { + Global.LFGMgr.UpdateProposal(dfProposalResponse.ProposalID, dfProposalResponse.Ticket.RequesterGuid, dfProposalResponse.Accepted); + } + + [WorldPacketHandler(ClientOpcodes.DfSetRoles)] + void HandleDfSetRoles(DFSetRoles setRoles) + { + ObjectGuid guid = GetPlayer().GetGUID(); + Group group = GetPlayer().GetGroup(); + if (!group) + { + Log.outDebug(LogFilter.Lfg, "CMSG_LFG_SET_ROLES {0} Not in group", + GetPlayerInfo()); + return; + } + ObjectGuid gguid = group.GetGUID(); + Log.outDebug(LogFilter.Lfg, "CMSG_LFG_SET_ROLES: Group {0}, Player {1}, Roles: {2}", gguid.ToString(), GetPlayerInfo(), setRoles.RolesDesired); + Global.LFGMgr.UpdateRoleCheck(gguid, guid, setRoles.RolesDesired); + } + + [WorldPacketHandler(ClientOpcodes.DfBootPlayerVote)] + void HandleDfBootPlayerVote(DFBootPlayerVote bootPlayerVote) + { + ObjectGuid guid = GetPlayer().GetGUID(); + Log.outDebug(LogFilter.Lfg, "ClientOpcodes.DfBootPlayerVote: {0} agree: {1}", GetPlayerInfo(), bootPlayerVote.Vote); + Global.LFGMgr.UpdateBoot(guid, bootPlayerVote.Vote); + } + + [WorldPacketHandler(ClientOpcodes.DfTeleport)] + void HandleDfTeleport(DFTeleport teleport) + { + Log.outDebug(LogFilter.Lfg, "CMSG_LFG_TELEPORT {0} out: {1}", GetPlayerInfo(), teleport.TeleportOut); + Global.LFGMgr.TeleportPlayer(GetPlayer(), teleport.TeleportOut, true); + } + + //[WorldPacketHandler(ClientOpcodes.DfGetSystemInfo, Processing = PacketProcessing.ThreadSafe)] + void HandleDfGetSystemInfo(DFGetSystemInfo dfGetSystemInfo) + { + Log.outDebug(LogFilter.Lfg, "CMSG_LFG_Lock_INFO_REQUEST {0} for {1}", GetPlayerInfo(), (dfGetSystemInfo.Player ? "player" : "party")); + + if (dfGetSystemInfo.Player) + SendLfgPlayerLockInfo(); + else + SendLfgPartyLockInfo(); + } + + [WorldPacketHandler(ClientOpcodes.DfGetJoinStatus, Processing = PacketProcessing.ThreadSafe)] + void HandleDfGetJoinStatus(DFGetJoinStatus packet) + { + if (!GetPlayer().isUsingLfg()) + return; + + ObjectGuid guid = GetPlayer().GetGUID(); + LfgUpdateData updateData = Global.LFGMgr.GetLfgStatus(guid); + + if (GetPlayer().GetGroup()) + { + SendLfgUpdateStatus(updateData, true); + updateData.dungeons.Clear(); + SendLfgUpdateStatus(updateData, false); + } + else + { + SendLfgUpdateStatus(updateData, false); + updateData.dungeons.Clear(); + SendLfgUpdateStatus(updateData, true); + } + } + + public void SendLfgPlayerLockInfo() + { + ObjectGuid guid = GetPlayer().GetGUID(); + + // Get Random dungeons that can be done at a certain level and expansion + uint level = GetPlayer().getLevel(); + List randomDungeons = Global.LFGMgr.GetRandomAndSeasonalDungeons(level, (uint)GetExpansion()); + + // Get player Locked Dungeons + Dictionary Lock = Global.LFGMgr.GetLockedDungeons(guid); + uint rsize = (uint)randomDungeons.Count; + uint lsize = (uint)Lock.Count; + + LfgPlayerInfo lfgPlayerInfo = new LfgPlayerInfo(); + lfgPlayerInfo.BlackList = new LFGBlackList(Lock); + + foreach (var slot in randomDungeons) + { + var dungeonInfo = new LfgPlayerDungeonInfo(); + dungeonInfo.Slot = slot; + dungeonInfo.FirstReward = true; + + LfgReward reward = Global.LFGMgr.GetRandomDungeonReward(slot, level); + Quest quest = null; + bool done = false; + if (reward != null) + { + quest = Global.ObjectMgr.GetQuestTemplate(reward.firstQuest); + if (quest != null) + { + done = !GetPlayer().CanRewardQuest(quest, false); + if (done) + { + quest = Global.ObjectMgr.GetQuestTemplate(reward.otherQuest); + dungeonInfo.FirstReward = false; + } + } + } + + if (quest != null) + { + dungeonInfo.ShortageEligible = true; + + dungeonInfo.CompletionQuantity = 1; + dungeonInfo.CompletionLimit = 1; + dungeonInfo.SpecificLimit = 1; + dungeonInfo.OverallLimit = 1; + dungeonInfo.Quantity = 1; + + BuildQuestReward(dungeonInfo.Rewards, quest, GetPlayer()); + } + lfgPlayerInfo.Dungeons.Add(dungeonInfo); + } + + SendPacket(lfgPlayerInfo); + } + + public void SendLfgPartyLockInfo() + { + ObjectGuid guid = GetPlayer().GetGUID(); + Group group = GetPlayer().GetGroup(); + if (!group) + return; + + // Get the Locked dungeons of the other party members + Dictionary> LockMap = new Dictionary>(); + for (GroupReference refe = group.GetFirstMember(); refe != null; refe = refe.next()) + { + Player plrg = refe.GetSource(); + if (!plrg) + continue; + + ObjectGuid pguid = plrg.GetGUID(); + if (pguid == guid) + continue; + + LockMap[pguid] = Global.LFGMgr.GetLockedDungeons(pguid); + } + + Log.outDebug(LogFilter.Lfg, "SMSG_LFG_PARTY_INFO {0}", GetPlayerInfo()); + + LfgPartyInfo partyInfo = new LfgPartyInfo(); + foreach (var it in LockMap) + { + var blackList = new LFGBlackList(it.Value); + blackList.PlayerGuid.Set(it.Key); + + partyInfo.Players.Add(blackList); + } + + SendPacket(partyInfo); + } + + public void SendLfgUpdateStatus(LfgUpdateData updateData, bool party) + { + ObjectGuid guid = GetPlayer().GetGUID(); + + LFGUpdateStatus updateStatus = new LFGUpdateStatus(); + updateStatus.Ticket.RequesterGuid = guid; + updateStatus.Ticket.Id = Global.LFGMgr.GetQueueId(guid); + updateStatus.Ticket.Type = (byte)updateData.state; + updateStatus.Ticket.Time = (uint)Global.LFGMgr.GetQueueJoinTime(guid); + + updateStatus.SubType = (byte)(updateData.dungeons.Count > 0 ? Global.LFGMgr.GetDungeonType(updateData.dungeons[0]) : 0); + updateStatus.Reason = (byte)updateData.updateType; + + updateStatus.Slots = updateData.dungeons; + updateStatus.RequestedRoles = (uint)Global.LFGMgr.GetRoles(guid); + updateStatus.IsParty = party; + + switch (updateData.updateType) + { + case LfgUpdateType.JoinQueueInitial: // Joined queue outside the dungeon + updateStatus.Joined = updateStatus.LfgJoined = true; + break; + case LfgUpdateType.JoinQueue: + case LfgUpdateType.AddedToQueue: // Rolecheck Success + updateStatus.Joined = updateStatus.LfgJoined = true; + updateStatus.Queued = true; + break; + case LfgUpdateType.ProposalBegin: + updateStatus.Joined = updateStatus.LfgJoined = true; + break; + case LfgUpdateType.UpdateStatus: + updateStatus.Joined = updateStatus.LfgJoined = updateData.state != LfgState.Rolecheck && updateData.state != LfgState.None; + updateStatus.Queued = updateData.state == LfgState.Queued; + break; + default: + break; + } + + SendPacket(updateStatus); + } + + public void SendLfgRoleChosen(ObjectGuid guid, LfgRoles roles) + { + RoleChosen roleChosen = new RoleChosen(); + roleChosen.Player = guid; + roleChosen.RoleMask = roles; + roleChosen.Accepted = roles > 0; + SendPacket(roleChosen); + } + + public void SendLfgRoleCheckUpdate(LfgRoleCheck roleCheck) + { + List dungeons = new List(); + if (roleCheck.rDungeonId != 0) + dungeons.Add(roleCheck.rDungeonId); + else + dungeons = roleCheck.dungeons; + + Log.outDebug(LogFilter.Lfg, "SMSG_LFG_ROLE_CHECK_UPDATE {0}", GetPlayerInfo()); + + LFGRoleCheckUpdate roleCheckUpdate = new LFGRoleCheckUpdate(); + roleCheckUpdate.PartyIndex = (byte)roleCheck.rDungeonId; //Wrong + roleCheckUpdate.RoleCheckStatus = (byte)roleCheck.state; + roleCheckUpdate.IsBeginning = roleCheck.state == LfgRoleCheckState.Initialiting; + + if (!dungeons.Empty()) + foreach (var it in dungeons) + roleCheckUpdate.JoinSlots.Add(Global.LFGMgr.GetLFGDungeonEntry(it)); // Dungeon + + if (!roleCheck.roles.Empty()) + { + // Leader info MUST be sent 1st :S + ObjectGuid guid = roleCheck.leader; + LfgRoles roles = roleCheck.roles.LookupByKey(guid); + Player player = Global.ObjAccessor.FindPlayer(guid); + + var roleCheckUpdateMember = new LFGRoleCheckUpdate.LFGRoleCheckUpdateMember(); + + roleCheckUpdateMember.Guid = guid; + roleCheckUpdateMember.RolesDesired = (uint)roles; + + roleCheckUpdateMember.Level = (byte)(player ? player.getLevel() : 0); + roleCheckUpdateMember.RoleCheckComplete = roles > 0; + + roleCheckUpdate.Members.Add(roleCheckUpdateMember); + + foreach (var it in roleCheck.roles) + { + if (it.Key == roleCheck.leader) + continue; + + roleCheckUpdateMember = new LFGRoleCheckUpdate.LFGRoleCheckUpdateMember(); + + guid = it.Key; + roles = it.Value; + player = Global.ObjAccessor.FindPlayer(guid); + roleCheckUpdateMember.Guid = guid; + roleCheckUpdateMember.RolesDesired = (uint)roles; + roleCheckUpdateMember.Level = (byte)(player ? player.getLevel() : 0); + roleCheckUpdateMember.RoleCheckComplete = roles > 0; + + roleCheckUpdate.Members.Add(roleCheckUpdateMember); + } + } + + SendPacket(roleCheckUpdate); + } + + public void SendLfgJoinResult(LfgJoinResultData joinData) + { + ObjectGuid guid = GetPlayer().GetGUID(); + + LFGJoinResult joinResult = new LFGJoinResult(); + joinResult.Ticket = new RideTicket(guid, Global.LFGMgr.GetQueueId(guid), (int)Global.LFGMgr.GetState(guid), (uint)Time.UnixTime); + + joinResult.Result = (byte)joinData.result; + joinResult.ResultDetail= (byte)joinData.state; + foreach (var it in joinData.lockmap) + { + var blackList = new LFGBlackList(it.Value); + blackList.PlayerGuid.Set(it.Key); + joinResult.BlackList.Add(blackList); + } + + SendPacket(joinResult); + } + + public void SendLfgQueueStatus(LfgQueueStatusData queueData) + { + Log.outDebug(LogFilter.Lfg, "SMSG_LFG_QUEUE_STATUS {0} state: {1} dungeon: {2}, waitTime: {3}, " + + "avgWaitTime: {4}, waitTimeTanks: {5}, waitTimeHealer: {6}, waitTimeDps: {7}, queuedTime: {8}, tanks: {9}, healers: {10}, dps: {11}", + GetPlayerInfo(), Global.LFGMgr.GetState(GetPlayer().GetGUID()), queueData.dungeonId, queueData.waitTime, queueData.waitTimeAvg, + queueData.waitTimeTank, queueData.waitTimeHealer, queueData.waitTimeDps, queueData.queuedTime, queueData.tanks, queueData.healers, queueData.dps); + + LFGQueueStatus queueStatus = new LFGQueueStatus(); + queueStatus.Ticket = new RideTicket(GetPlayer().GetGUID(), queueData.dungeonId, 3, (uint)queueData.joinTime); + queueStatus.Slot = queueData.queueId; + queueStatus.AvgWaitTime = (uint)queueData.waitTimeAvg; + queueStatus.QueuedTime = queueData.queuedTime; + + + queueStatus.LastNeeded[0] = queueData.tanks; + queueStatus.AvgWaitTimeByRole[0] = (uint)queueData.waitTimeTank; + queueStatus.LastNeeded[1] = queueData.healers; + queueStatus.AvgWaitTimeByRole[1] = (uint)queueData.waitTimeHealer; + queueStatus.LastNeeded[2] = queueData.dps; + queueStatus.AvgWaitTimeByRole[2] = (uint)queueData.waitTimeDps; + queueStatus.AvgWaitTimeMe = (uint)queueData.waitTime; + + SendPacket(queueStatus); + } + + public void SendLfgPlayerReward(LfgPlayerRewardData rewardData) + { + if (rewardData.rdungeonEntry == 0 || rewardData.sdungeonEntry == 0 || rewardData.quest == null) + return; + + Log.outDebug(LogFilter.Lfg, "SMSG_LFG_PLAYER_REWARD {0} rdungeonEntry: {1}, sdungeonEntry: {2}, done: {3}", + GetPlayerInfo(), rewardData.rdungeonEntry, rewardData.sdungeonEntry, rewardData.done); + + byte itemNum = (byte)(rewardData.quest.GetRewItemsCount() + rewardData.quest.GetRewCurrencyCount()); + + LFGPlayerReward playerReward = new LFGPlayerReward(); + playerReward.ActualSlot = rewardData.rdungeonEntry; // Random Dungeon Finished + playerReward.QueuedSlot = rewardData.sdungeonEntry; // Dungeon Finished + + playerReward.RewardMoney = GetPlayer().GetQuestMoneyReward(rewardData.quest); + playerReward.AddedXP = GetPlayer().GetQuestXPReward(rewardData.quest); + + for (byte i = 0; i < SharedConst.QuestRewardCurrencyCount; ++i) + { + var playerRewards = new LFGPlayerReward.LFGPlayerRewards(); + uint currencyId = rewardData.quest.RewardCurrencyId[i]; + if (currencyId != 0) + { + playerRewards.RewardItem = currencyId; + playerRewards.RewardItemQuantity = rewardData.quest.RewardCurrencyCount[i]; + playerRewards.IsCurrency = true; + playerReward.Rewards.Add(playerRewards); + } + } + + for (byte i = 0; i < SharedConst.QuestRewardItemCount; ++i) + { + var playerRewards = new LFGPlayerReward.LFGPlayerRewards(); + uint itemId = rewardData.quest.RewardItemId[i]; + if (itemId != 0) + { + ItemTemplate item = Global.ObjectMgr.GetItemTemplate(itemId); + playerRewards.RewardItem = itemId; + playerRewards.RewardItemQuantity = rewardData.quest.RewardItemCount[i]; + playerRewards.IsCurrency = false; + playerReward.Rewards.Add(playerRewards); + } + } + + SendPacket(playerReward); + } + + public void SendLfgBootProposalUpdate(LfgPlayerBoot boot) + { + ObjectGuid guid = GetPlayer().GetGUID(); + LfgAnswer playerVote = boot.votes.LookupByKey(guid); + byte votesNum = 0; + byte agreeNum = 0; + uint secsleft = (uint)((boot.cancelTime - Time.UnixTime) / 1000); + foreach (var it in boot.votes) + { + if (it.Value != LfgAnswer.Pending) + { + ++votesNum; + if (it.Value == LfgAnswer.Agree) + ++agreeNum; + } + } + Log.outDebug(LogFilter.Lfg, "SMSG_LFG_BOOT_PROPOSAL_UPDATE {0} inProgress: {1} - didVote: {2} - agree: {3} - victim: {4} votes: {5} - agrees: {6} - left: {7} - needed: {8} - reason {9}", + GetPlayerInfo(), boot.inProgress, playerVote != LfgAnswer.Pending, playerVote == LfgAnswer.Agree, boot.victim.ToString(), votesNum, agreeNum, secsleft, SharedConst.LFGKickVotesNeeded, boot.reason); + + LfgBootPlayer lfgBootPlayer = new LfgBootPlayer(); + lfgBootPlayer.Info.VoteInProgress = boot.inProgress; // Vote in progress + lfgBootPlayer.Info.VotePassed = agreeNum >= SharedConst.LFGKickVotesNeeded; // Did succeed + lfgBootPlayer.Info.MyVoteCompleted = playerVote != LfgAnswer.Pending; // Did Vote + lfgBootPlayer.Info.MyVote = playerVote == LfgAnswer.Agree; // Agree + lfgBootPlayer.Info.Target = boot.victim; // Victim GUID + lfgBootPlayer.Info.TotalVotes = votesNum; // Total Votes + lfgBootPlayer.Info.BootVotes = agreeNum; // Agree Count + lfgBootPlayer.Info.TimeLeft = secsleft; // Time Left + lfgBootPlayer.Info.VotesNeeded = SharedConst.LFGKickVotesNeeded; // Needed Votes + lfgBootPlayer.Info.Reason = boot.reason; // Kick reason + SendPacket(lfgBootPlayer); + } + + public void SendLfgProposalUpdate(LfgProposal proposal) + { + ObjectGuid playerGuid = GetPlayer().GetGUID(); + ObjectGuid guildGuid = proposal.players.LookupByKey(playerGuid).group; + bool silent = !proposal.isNew && guildGuid == proposal.group; + uint dungeonEntry = proposal.dungeonId; + + Log.outDebug(LogFilter.Lfg, "SMSG_LFG_PROPOSAL_UPDATE {0} state: {1}", GetPlayerInfo(), proposal.state); + + // show random dungeon if player selected random dungeon and it's not lfg group + if (!silent) + { + List playerDungeons = Global.LFGMgr.GetSelectedDungeons(playerGuid); + if (!playerDungeons.Contains(proposal.dungeonId)) + dungeonEntry = playerDungeons.First(); + } + + dungeonEntry = Global.LFGMgr.GetLFGDungeonEntry(dungeonEntry); + + LFGProposalUpdate proposalUpdate = new LFGProposalUpdate(); + proposalUpdate.Ticket = new RideTicket(playerGuid, Global.LFGMgr.GetQueueId(playerGuid), 3, (uint)Global.LFGMgr.GetQueueJoinTime(playerGuid)); + + proposalUpdate.InstanceID = dungeonEntry; + proposalUpdate.ProposalID = proposal.id; + proposalUpdate.Slot = proposal.dungeonId; + proposalUpdate.State = (byte)proposal.state; + proposalUpdate.CompletedMask = proposal.encounters; + + proposalUpdate.ValidCompletedMask = false; + proposalUpdate.ProposalSilent = silent; + + foreach (var pair in proposal.players) + { + var updatePlayer = new LFGProposalUpdate.LFGProposalUpdatePlayer(); + updatePlayer.Roles = (uint)pair.Value.role; + updatePlayer.Me = (pair.Key == playerGuid); + updatePlayer.SameParty = (pair.Value.group == guildGuid); + updatePlayer.MyParty = (pair.Value.group == proposal.group); + updatePlayer.Responded = (pair.Value.accept != LfgAnswer.Pending); + updatePlayer.Accepted = (pair.Value.accept == LfgAnswer.Agree); + + proposalUpdate.Players.Add(updatePlayer); + } + + SendPacket(proposalUpdate); + } + + public void SendLfgLfrList(bool update) + { + /* WorldPacket data = new WorldPacket(ServerOpcodes.LfgUpdateSearch); + data.WriteUInt8(update); // In Lfg Queue? + SendPacket(data); + */ + } + + public void SendLfgDisabled() + { + SendPacket(new LfgDisabled()); + } + + public void SendLfgOfferContinue(uint dungeonEntry) + { + Log.outDebug(LogFilter.Lfg, "SMSG_LFG_OFFER_CONTINUE {0} dungeon entry: {1}", GetPlayerInfo(), dungeonEntry); + LfgOfferContinue lfgOfferContinue = new LfgOfferContinue(); + lfgOfferContinue.Slot = dungeonEntry; + SendPacket(lfgOfferContinue); + } + + public void SendLfgTeleportError(LfgTeleportResult err) + { + Log.outDebug(LogFilter.Lfg, "SMSG_LFG_TELEPORT_DENIED {0} reason: {1}", GetPlayerInfo(), err); + LfgTeleportDenied lfgTeleportDenied = new LfgTeleportDenied(); + lfgTeleportDenied.Reason = err; + SendPacket(lfgTeleportDenied); + } + } +} \ No newline at end of file diff --git a/Game/Handlers/LogoutHandler.cs b/Game/Handlers/LogoutHandler.cs new file mode 100644 index 000000000..bd411443f --- /dev/null +++ b/Game/Handlers/LogoutHandler.cs @@ -0,0 +1,102 @@ +/* + * Copyright (C) 2012-2017 CypherCore + * + * 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, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +using Framework.Constants; +using Game.Entities; +using Game.Network; +using Game.Network.Packets; + +namespace Game +{ + public partial class WorldSession + { + [WorldPacketHandler(ClientOpcodes.LogoutRequest)] + void HandleLogoutRequest(LogoutRequest packet) + { + Player pl = GetPlayer(); + if (!GetPlayer().GetLootGUID().IsEmpty()) + GetPlayer().SendLootReleaseAll(); + + bool instantLogout = (pl.HasFlag(PlayerFields.Flags, PlayerFlags.Resting) && !pl.IsInCombat() || + pl.IsInFlight() || HasPermission(RBACPermissions.InstantLogout)); + + bool canLogoutInCombat = pl.HasFlag(PlayerFields.Flags, PlayerFlags.Resting); + + int reason = 0; + if (pl.IsInCombat() && !canLogoutInCombat) + reason = 1; + else if (pl.IsFalling()) + reason = 3; // is jumping or falling + else if (pl.duel != null || pl.HasAura(9454)) // is dueling or frozen by GM via freeze command + reason = 2; // FIXME - Need the correct value + + LogoutResponse logoutResponse = new LogoutResponse(); + logoutResponse.LogoutResult = reason; + logoutResponse.Instant = instantLogout; + SendPacket(logoutResponse); + + if (reason != 0) + { + SetLogoutStartTime(0); + return; + } + + // instant logout in taverns/cities or on taxi or for admins, gm's, mod's if its enabled in worldserver.conf + if (instantLogout) + { + LogoutPlayer(true); + return; + } + + // not set flags if player can't free move to prevent lost state at logout cancel + if (pl.CanFreeMove()) + { + if (pl.GetStandState() == UnitStandStateType.Stand) + pl.SetStandState(UnitStandStateType.Sit); + pl.SetRooted(true); + pl.SetFlag(UnitFields.Flags, UnitFlags.Stunned); + } + + SetLogoutStartTime(Time.UnixTime); + } + + [WorldPacketHandler(ClientOpcodes.LogoutCancel)] + void HandleLogoutCancel(LogoutCancel packet) + { + // Player have already logged out serverside, too late to cancel + if (!GetPlayer()) + return; + + SetLogoutStartTime(0); + + SendPacket(new LogoutCancelAck()); + + // not remove flags if can't free move - its not set in Logout request code. + if (GetPlayer().CanFreeMove()) + { + //!we can move again + GetPlayer().SetRooted(false); + + //! Stand Up + GetPlayer().SetStandState(UnitStandStateType.Stand); + + //! DISABLE_ROTATE + GetPlayer().RemoveFlag(UnitFields.Flags, UnitFlags.Stunned); + } + } + } +} diff --git a/Game/Handlers/LootHandler.cs b/Game/Handlers/LootHandler.cs new file mode 100644 index 000000000..2a8e0f11f --- /dev/null +++ b/Game/Handlers/LootHandler.cs @@ -0,0 +1,585 @@ +/* + * Copyright (C) 2012-2017 CypherCore + * + * 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, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +using Framework.Constants; +using Game.DataStorage; +using Game.Entities; +using Game.Groups; +using Game.Guilds; +using Game.Loots; +using Game.Maps; +using Game.Network; +using Game.Network.Packets; +using System; +using System.Collections.Generic; + +namespace Game +{ + public partial class WorldSession + { + [WorldPacketHandler(ClientOpcodes.LootItem)] + void HandleAutostoreLootItem(LootItemPkt packet) + { + Player player = GetPlayer(); + AELootResult aeResult = player.GetAELootView().Count > 1 ? new AELootResult() : null; + + /// @todo Implement looting by LootObject guid + foreach (LootRequest req in packet.Loot) + { + Loot loot = null; + ObjectGuid lguid = player.GetLootWorldObjectGUID(req.Object); + + if (lguid.IsGameObject()) + { + GameObject go = player.GetMap().GetGameObject(lguid); + + // not check distance for GO in case owned GO (fishing bobber case, for example) or Fishing hole GO + if (!go || ((go.GetOwnerGUID() != player.GetGUID() && go.GetGoType() != GameObjectTypes.FishingHole) && !go.IsWithinDistInMap(player, SharedConst.InteractionDistance))) + { + player.SendLootRelease(lguid); + continue; + } + + loot = go.loot; + } + else if (lguid.IsItem()) + { + Item pItem = player.GetItemByGuid(lguid); + + if (!pItem) + { + player.SendLootRelease(lguid); + continue; + } + + loot = pItem.loot; + } + else if (lguid.IsCorpse()) + { + Corpse bones = ObjectAccessor.GetCorpse(player, lguid); + if (!bones) + { + player.SendLootRelease(lguid); + continue; + } + + loot = bones.loot; + } + else + { + Creature creature = player.GetMap().GetCreature(lguid); + + bool lootAllowed = creature && creature.IsAlive() == (player.GetClass() == Class.Rogue && creature.loot.loot_type == LootType.Pickpocketing); + if (!lootAllowed || !creature.IsWithinDistInMap(player, AELootCreatureCheck.LootDistance)) + { + player.SendLootError(req.Object, lguid, lootAllowed ? LootError.TooFar : LootError.DidntKill); + continue; + } + + loot = creature.loot; + } + + player.StoreLootItem((byte)(req.LootListID - 1), loot, aeResult); + + // If player is removing the last LootItem, delete the empty container. + if (loot.isLooted() && lguid.IsItem()) + player.GetSession().DoLootRelease(lguid); + } + + if (aeResult != null) + { + foreach (var resultValue in aeResult.GetByOrder()) + { + player.SendNewItem(resultValue.item, resultValue.count, false, false, true); + player.UpdateCriteria(CriteriaTypes.LootItem, resultValue.item.GetEntry(), resultValue.count); + player.UpdateCriteria(CriteriaTypes.LootType, resultValue.item.GetEntry(), resultValue.count, (ulong)resultValue.lootType); + player.UpdateCriteria(CriteriaTypes.LootEpicItem, resultValue.item.GetEntry(), resultValue.count); + } + } + } + + [WorldPacketHandler(ClientOpcodes.LootMoney)] + void HandleLootMoney(LootMoney lootMoney) + { + Player player = GetPlayer(); + + foreach (var lootView in player.GetAELootView()) + { + ObjectGuid guid = lootView.Value; + Loot loot = null; + bool shareMoney = true; + + switch (guid.GetHigh()) + { + case HighGuid.GameObject: + { + GameObject go = player.GetMap().GetGameObject(guid); + + // do not check distance for GO if player is the owner of it (ex. fishing bobber) + if (go && ((go.GetOwnerGUID() == player.GetGUID() || go.IsWithinDistInMap(player, SharedConst.InteractionDistance)))) + loot = go.loot; + + break; + } + case HighGuid.Corpse: // remove insignia ONLY in BG + { + Corpse bones = ObjectAccessor.GetCorpse(player, guid); + + if (bones && bones.IsWithinDistInMap(player, SharedConst.InteractionDistance)) + { + loot = bones.loot; + shareMoney = false; + } + + break; + } + case HighGuid.Item: + { + Item item = player.GetItemByGuid(guid); + if (item) + { + loot = item.loot; + shareMoney = false; + } + break; + } + case HighGuid.Creature: + case HighGuid.Vehicle: + { + Creature creature = player.GetMap().GetCreature(guid); + bool lootAllowed = creature && creature.IsAlive() == (player.GetClass() == Class.Rogue && creature.loot.loot_type == LootType.Pickpocketing); + if (lootAllowed && creature.IsWithinDistInMap(player, AELootCreatureCheck.LootDistance)) + { + loot = creature.loot; + if (creature.IsAlive()) + shareMoney = false; + } + else + player.SendLootError(lootView.Key, guid, lootAllowed ? LootError.TooFar : LootError.DidntKill); + break; + } + default: + continue; // unlootable type + } + + if (loot == null) + continue; + + loot.NotifyMoneyRemoved(); + if (shareMoney && player.GetGroup() != null) //item, pickpocket and players can be looted only single player + { + + Group group = player.GetGroup(); + + List playersNear = new List(); + for (GroupReference refe = group.GetFirstMember(); refe != null; refe = refe.next()) + { + Player member = refe.GetSource(); + if (!member) + continue; + + if (player.IsAtGroupRewardDistance(member)) + playersNear.Add(member); + } + + uint goldPerPlayer = (uint)(loot.gold / playersNear.Count); + + foreach (var pl in playersNear) + { + pl.ModifyMoney(goldPerPlayer); + pl.UpdateCriteria(CriteriaTypes.LootMoney, goldPerPlayer); + + Guild guild = Global.GuildMgr.GetGuildById(pl.GetGuildId()); + if (guild) + { + uint guildGold = MathFunctions.CalculatePct(goldPerPlayer, pl.GetTotalAuraModifier(AuraType.DepositBonusMoneyInGuildBankOnLoot)); + if (guildGold != 0) + guild.HandleMemberDepositMoney(this, guildGold, true); + } + + LootMoneyNotify packet = new LootMoneyNotify(); + packet.Money = goldPerPlayer; + packet.SoleLooter = playersNear.Count <= 1 ? true : false; + pl.SendPacket(packet); + } + } + else + { + player.ModifyMoney(loot.gold); + player.UpdateCriteria(CriteriaTypes.LootMoney, loot.gold); + + Guild guild = Global.GuildMgr.GetGuildById(player.GetGuildId()); + if (guild) + { + uint guildGold = MathFunctions.CalculatePct(loot.gold, player.GetTotalAuraModifier(AuraType.DepositBonusMoneyInGuildBankOnLoot)); + if (guildGold != 0) + guild.HandleMemberDepositMoney(this, guildGold, true); + } + + LootMoneyNotify packet = new LootMoneyNotify(); + packet.Money = loot.gold; + packet.SoleLooter = true; // "You loot..." + SendPacket(packet); + } + + loot.gold = 0; + + // Delete the money loot record from the DB + if (!loot.containerID.IsEmpty()) + loot.DeleteLootMoneyFromContainerItemDB(); + + // Delete container if empty + if (loot.isLooted() && guid.IsItem()) + player.GetSession().DoLootRelease(guid); + } + } + + class AELootCreatureCheck : ICheck + { + public static float LootDistance = 30.0f; + + public AELootCreatureCheck(Player looter, ObjectGuid mainLootTarget) + { + _looter = looter; + _mainLootTarget = mainLootTarget; + } + + public bool Invoke(Creature creature) + { + if (creature.IsAlive()) + return false; + + if (creature.GetGUID() == _mainLootTarget) + return false; + + if (!_looter.IsWithinDist(creature, LootDistance)) + return false; + + return _looter.isAllowedToLoot(creature); + } + + Player _looter; + ObjectGuid _mainLootTarget; + } + + [WorldPacketHandler(ClientOpcodes.LootUnit)] + void HandleLoot(LootUnit packet) + { + // Check possible cheat + if (!GetPlayer().IsAlive() || !packet.Unit.IsCreatureOrVehicle()) + return; + + List corpses = new List(); + AELootCreatureCheck check = new AELootCreatureCheck(_player, packet.Unit); + CreatureListSearcher searcher = new CreatureListSearcher(_player, corpses, check); + Cell.VisitGridObjects(_player, searcher, AELootCreatureCheck.LootDistance); + + if (!corpses.Empty()) + SendPacket(new AELootTargets((uint)corpses.Count + 1)); + + GetPlayer().SendLoot(packet.Unit, LootType.Corpse); + + if (!corpses.Empty()) + { + // main target + SendPacket(new AELootTargetsAck()); + + foreach (Creature creature in corpses) + { + GetPlayer().SendLoot(creature.GetGUID(), LootType.Corpse, true); + SendPacket(new AELootTargetsAck()); + } + } + + // interrupt cast + if (GetPlayer().IsNonMeleeSpellCast(false)) + GetPlayer().InterruptNonMeleeSpells(false); + } + + [WorldPacketHandler(ClientOpcodes.LootRelease)] + void HandleLootRelease(LootRelease packet) + { + // cheaters can modify lguid to prevent correct apply loot release code and re-loot + // use internal stored guid + if (GetPlayer().HasLootWorldObjectGUID(packet.Unit)) + DoLootRelease(packet.Unit); + } + + public void DoLootRelease(ObjectGuid lguid) + { + Player player = GetPlayer(); + Loot loot; + + if (player.GetLootGUID() == lguid) + player.SetLootGUID(ObjectGuid.Empty); + player.SendLootRelease(lguid); + + player.RemoveFlag(UnitFields.Flags, UnitFlags.Looting); + + if (!player.IsInWorld) + return; + + if (lguid.IsGameObject()) + { + GameObject go = player.GetMap().GetGameObject(lguid); + + // not check distance for GO in case owned GO (fishing bobber case, for example) or Fishing hole GO + if (!go || ((go.GetOwnerGUID() != player.GetGUID() && go.GetGoType() != GameObjectTypes.FishingHole) && !go.IsWithinDistInMap(player, SharedConst.InteractionDistance))) + return; + + loot = go.loot; + + if (go.GetGoType() == GameObjectTypes.Door) + { + // locked doors are opened with spelleffect openlock, prevent remove its as looted + go.UseDoorOrButton(); + } + else if (loot.isLooted() || go.GetGoType() == GameObjectTypes.FishingNode) + { + if (go.GetGoType() == GameObjectTypes.FishingHole) + { // The fishing hole used once more + go.AddUse(); // if the max usage is reached, will be despawned in next tick + if (go.GetUseCount() >= go.m_goValue.FishingHole.MaxOpens) + go.SetLootState(LootState.JustDeactivated); + else + go.SetLootState(LootState.Ready); + } + else + go.SetLootState(LootState.JustDeactivated); + + loot.clear(); + } + else + { + // not fully looted object + go.SetLootState(LootState.Activated, player); + + // if the round robin player release, reset it. + if (player.GetGUID() == loot.roundRobinPlayer) + loot.roundRobinPlayer.Clear(); + } + } + else if (lguid.IsCorpse()) // ONLY remove insignia at BG + { + Corpse corpse = ObjectAccessor.GetCorpse(player, lguid); + if (!corpse || !corpse.IsWithinDistInMap(player, SharedConst.InteractionDistance)) + return; + + loot = corpse.loot; + + if (loot.isLooted()) + { + loot.clear(); + corpse.RemoveFlag(CorpseFields.DynamicFlags, 0x0001); + } + } + else if (lguid.IsItem()) + { + Item pItem = player.GetItemByGuid(lguid); + if (!pItem) + return; + + ItemTemplate proto = pItem.GetTemplate(); + + // destroy only 5 items from stack in case prospecting and milling + if (proto.GetFlags().HasAnyFlag(ItemFlags.IsProspectable | ItemFlags.IsMillable)) + { + pItem.m_lootGenerated = false; + pItem.loot.clear(); + + uint count = pItem.GetCount(); + + // >=5 checked in spell code, but will work for cheating cases also with removing from another stacks. + if (count > 5) + count = 5; + + player.DestroyItemCount(pItem, ref count, true); + } + else + { + if (pItem.loot.isLooted() || !proto.GetFlags().HasAnyFlag(ItemFlags.HasLoot)) // Only delete item if no loot or money (unlooted loot is saved to db) + player.DestroyItem(pItem.GetBagSlot(), pItem.GetSlot(), true); + } + return; // item can be looted only single player + } + else + { + Creature creature = player.GetMap().GetCreature(lguid); + + bool lootAllowed = creature && creature.IsAlive() == (player.GetClass() == Class.Rogue && creature.loot.loot_type == LootType.Pickpocketing); + if (!lootAllowed || !creature.IsWithinDistInMap(player, AELootCreatureCheck.LootDistance)) + return; + + loot = creature.loot; + if (loot.isLooted()) + { + creature.RemoveFlag(ObjectFields.DynamicFlags, UnitDynFlags.Lootable); + + // skip pickpocketing loot for speed, skinning timer reduction is no-op in fact + if (!creature.IsAlive()) + creature.AllLootRemovedFromCorpse(); + + loot.clear(); + } + else + { + // if the round robin player release, reset it. + if (player.GetGUID() == loot.roundRobinPlayer) + { + loot.roundRobinPlayer.Clear(); + + Group group = player.GetGroup(); + if (group) + { + if (group.GetLootMethod() != LootMethod.MasterLoot) + group.SendLooter(creature, null); + + + } + // force dynflag update to update looter and lootable info + creature.ForceValuesUpdateAtIndex(ObjectFields.DynamicFlags); + } + } + } + + //Player is not looking at loot list, he doesn't need to see updates on the loot list + loot.RemoveLooter(player.GetGUID()); + player.RemoveAELootedObject(loot.GetGUID()); + } + + void DoLootReleaseAll() + { + Dictionary lootView = _player.GetAELootView(); + foreach (var lootPair in lootView) + DoLootRelease(lootPair.Value); + } + + //[WorldPacketHandler(ClientOpcodes.MasterLootItem)] + void HandleLootMasterGive(WorldPacket packet) + { + ObjectGuid lootguid = packet.ReadPackedGuid(); + byte slotid = packet.ReadUInt8(); + ObjectGuid target_playerguid = packet.ReadPackedGuid(); + + if (GetPlayer().GetGroup() == null || GetPlayer().GetGroup().GetLooterGuid() != GetPlayer().GetGUID() || GetPlayer().GetGroup().GetLootMethod() != LootMethod.MasterLoot) + { + GetPlayer().SendLootError(lootguid, ObjectGuid.Empty, LootError.DidntKill); + return; + } + + Player target = Global.ObjAccessor.FindPlayer(target_playerguid); + if (!target) + { + GetPlayer().SendLootError(lootguid, ObjectGuid.Empty, LootError.PlayerNotFound); + return; + } + + Log.outDebug(LogFilter.Network, "HandleLootMasterGive (CMSG_LOOT_MASTER_GIVE, 0x02A3) Target = [{0}].", target.GetName()); + + if (GetPlayer().GetLootGUID() != lootguid) + { + GetPlayer().SendLootError(lootguid, ObjectGuid.Empty, LootError.DidntKill); + return; + } + + if (!GetPlayer().IsInRaidWith(target) || !GetPlayer().IsInMap(target)) + { + GetPlayer().SendLootError(lootguid, ObjectGuid.Empty, LootError.MasterOther); + Log.outInfo(LogFilter.Loot, "MasterLootItem: Player {0} tried to give an item to ineligible player {1}!", GetPlayer().GetName(), target.GetName()); + return; + } + + Loot loot = null; + + if (GetPlayer().GetLootGUID().IsCreatureOrVehicle()) + { + Creature creature = GetPlayer().GetMap().GetCreature(lootguid); + if (creature == null) + return; + + loot = creature.loot; + } + else if (GetPlayer().GetLootGUID().IsGameObject()) + { + GameObject pGO = GetPlayer().GetMap().GetGameObject(lootguid); + if (pGO == null) + return; + + loot = pGO.loot; + } + + if (loot == null) + return; + + if (slotid >= loot.items.Count + loot.quest_items.Count) + { + Log.outDebug(LogFilter.Loot, "MasterLootItem: Player {0} might be using a hack! (slot {1}, size {2})", + GetPlayer().GetName(), slotid, loot.items.Count); + return; + } + + LootItem item = slotid >= loot.items.Count ? loot.quest_items[slotid - loot.items.Count] : loot.items[slotid]; + + List dest = new List(); ; + InventoryResult msg = target.CanStoreNewItem(ItemConst.NullBag, ItemConst.NullSlot, dest, item.itemid, item.count); + if (item.follow_loot_rules && !item.AllowedForPlayer(target)) + msg = InventoryResult.CantEquipEver; + if (msg != InventoryResult.Ok) + { + if (msg == InventoryResult.ItemMaxCount) + GetPlayer().SendLootError(lootguid, ObjectGuid.Empty, LootError.MasterUniqueItem); + else if (msg == InventoryResult.InvFull) + GetPlayer().SendLootError(lootguid, ObjectGuid.Empty, LootError.MasterInvFull); + else + GetPlayer().SendLootError(lootguid, ObjectGuid.Empty, LootError.MasterOther); + + target.SendEquipError(msg, null, null, item.itemid); + return; + } + + // not move item from loot to target inventory + Item newitem = target.StoreNewItem(dest, item.itemid, true, item.randomPropertyId, item.GetAllowedLooters(), item.context, item.BonusListIDs); + target.SendNewItem(newitem, item.count, false, false, true); + target.UpdateCriteria(CriteriaTypes.LootItem, item.itemid, item.count); + target.UpdateCriteria(CriteriaTypes.LootType, item.itemid, item.count, (ulong)loot.loot_type); + target.UpdateCriteria(CriteriaTypes.LootEpicItem, item.itemid, item.count); + + // mark as looted + item.count = 0; + item.is_looted = true; + + loot.NotifyItemRemoved(slotid); + --loot.unlootedCount; + } + + [WorldPacketHandler(ClientOpcodes.SetLootSpecialization)] + void HandleSetLootSpecialization(SetLootSpecialization packet) + { + if (packet.SpecID != 0) + { + ChrSpecializationRecord chrSpec = CliDB.ChrSpecializationStorage.LookupByKey(packet.SpecID); + if (chrSpec != null) + { + if (chrSpec.ClassID == (uint)GetPlayer().GetClass()) + GetPlayer().SetLootSpecId(packet.SpecID); + } + } + else + GetPlayer().SetLootSpecId(0); + } + } +} diff --git a/Game/Handlers/MailHandler.cs b/Game/Handlers/MailHandler.cs new file mode 100644 index 000000000..ea3ec6000 --- /dev/null +++ b/Game/Handlers/MailHandler.cs @@ -0,0 +1,692 @@ +/* + * Copyright (C) 2012-2017 CypherCore + * + * 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, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +using Framework.Constants; +using Framework.Database; +using Game.DataStorage; +using Game.Entities; +using Game.Guilds; +using Game.Mails; +using Game.Network; +using Game.Network.Packets; +using System; +using System.Collections.Generic; +using System.Linq; + +namespace Game +{ + public partial class WorldSession + { + bool CanOpenMailBox(ObjectGuid guid) + { + if (guid == GetPlayer().GetGUID()) + { + if (!HasPermission(RBACPermissions.CommandMailbox)) + { + Log.outWarn(LogFilter.ChatSystem, "{0} attempt open mailbox in cheating way.", GetPlayer().GetName()); + return false; + } + } + else if (guid.IsGameObject()) + { + if (!GetPlayer().GetGameObjectIfCanInteractWith(guid, GameObjectTypes.Mailbox)) + return false; + } + else if (guid.IsAnyTypeCreature()) + { + if (!GetPlayer().GetNPCIfCanInteractWith(guid, NPCFlags.Mailbox)) + return false; + } + else + return false; + + return true; + } + + [WorldPacketHandler(ClientOpcodes.SendMail)] + void HandleSendMail(SendMail packet) + { + if (packet.Info.Attachments.Count > SharedConst.MaxMailItems) // client limit + { + GetPlayer().SendMailResult(0, MailResponseType.Send, MailResponseResult.TooManyAttachments); + return; + } + + if (!CanOpenMailBox(packet.Info.Mailbox)) + return; + + if (string.IsNullOrEmpty(packet.Info.Target)) + return; + + Player player = GetPlayer(); + if (player.getLevel() < WorldConfig.GetIntValue(WorldCfg.MailLevelReq)) + { + SendNotification(CypherStrings.MailSenderReq, WorldConfig.GetIntValue(WorldCfg.MailLevelReq)); + return; + } + + ObjectGuid receiverGuid = ObjectGuid.Empty; + if (ObjectManager.NormalizePlayerName(ref packet.Info.Target)) + receiverGuid = ObjectManager.GetPlayerGUIDByName(packet.Info.Target); + + if (receiverGuid.IsEmpty()) + { + Log.outInfo(LogFilter.Network, "Player {0} is sending mail to {1} (GUID: not existed!) with subject {2}" + + "and body {3} includes {4} items, {5} copper and {6} COD copper with StationeryID = {7}", + GetPlayerInfo(), packet.Info.Target, packet.Info.Subject, packet.Info.Body, + packet.Info.Attachments.Count, packet.Info.SendMoney, packet.Info.Cod, packet.Info.StationeryID); + player.SendMailResult(0, MailResponseType.Send, MailResponseResult.RecipientNotFound); + return; + } + + if (packet.Info.SendMoney < 0) + { + GetPlayer().SendMailResult(0, MailResponseType.Send, MailResponseResult.InternalError); + Log.outWarn(LogFilter.Server, "Player {0} attempted to send mail to {1} ({2}) with negative money value (SendMoney: {3})", + GetPlayerInfo(), packet.Info.Target, receiverGuid.ToString(), packet.Info.SendMoney); + return; + } + + if (packet.Info.Cod < 0) + { + GetPlayer().SendMailResult(0, MailResponseType.Send, MailResponseResult.InternalError); + Log.outWarn(LogFilter.Server, "Player {0} attempted to send mail to {1} ({2}) with negative COD value (Cod: {3})", + GetPlayerInfo(), packet.Info.Target, receiverGuid.ToString(), packet.Info.Cod); + return; + } + + Log.outInfo(LogFilter.Network, "Player {0} is sending mail to {1} ({2}) with subject {3} and body {4}" + + "includes {5} items, {6} copper and {7} COD copper with StationeryID = {8}", + GetPlayerInfo(), packet.Info.Target, receiverGuid.ToString(), packet.Info.Subject, + packet.Info.Body, packet.Info.Attachments.Count, packet.Info.SendMoney, packet.Info.Cod, packet.Info.StationeryID); + + if (player.GetGUID() == receiverGuid) + { + player.SendMailResult(0, MailResponseType.Send, MailResponseResult.CannotSendToSelf); + return; + } + + uint cost = (uint)(!packet.Info.Attachments.Empty() ? 30 * packet.Info.Attachments.Count : 30); // price hardcoded in client + + long reqmoney = cost + packet.Info.SendMoney; + + // Check for overflow + if (reqmoney < packet.Info.SendMoney) + { + player.SendMailResult(0, MailResponseType.Send, MailResponseResult.NotEnoughMoney); + return; + } + + if (!player.HasEnoughMoney(reqmoney) && !player.IsGameMaster()) + { + player.SendMailResult(0, MailResponseType.Send, MailResponseResult.NotEnoughMoney); + return; + } + + Player receiver = Global.ObjAccessor.FindPlayer(receiverGuid); + + Team receiverTeam = 0; + byte mailsCount = 0; //do not allow to send to one player more than 100 mails + byte receiverLevel = 0; + uint receiverAccountId = 0; + + if (receiver) + { + receiverTeam = receiver.GetTeam(); + mailsCount = (byte)receiver.GetMails().Count; + receiverLevel = (byte)receiver.getLevel(); + receiverAccountId = receiver.GetSession().GetAccountId(); + } + else + { + receiverTeam = ObjectManager.GetPlayerTeamByGUID(receiverGuid); + + PreparedStatement stmt = DB.Characters.GetPreparedStatement(CharStatements.SEL_MAIL_COUNT); + stmt.AddValue(0, receiverGuid.GetCounter()); + + SQLResult result = DB.Characters.Query(stmt); + if (!result.IsEmpty()) + mailsCount = (byte)result.Read(0); + + stmt = DB.Characters.GetPreparedStatement(CharStatements.SEL_CHAR_LEVEL); + stmt.AddValue(0, receiverGuid.GetCounter()); + + result = DB.Characters.Query(stmt); + if (!result.IsEmpty()) + receiverLevel = result.Read(0); + + receiverAccountId = ObjectManager.GetPlayerAccountIdByGUID(receiverGuid); + } + + // do not allow to have more than 100 mails in mailbox.. mails count is in opcode byte!!! - so max can be 255.. + if (mailsCount > 100) + { + player.SendMailResult(0, MailResponseType.Send, MailResponseResult.RecipientCapReached); + return; + } + + // test the receiver's Faction... or all items are account bound + bool accountBound = !packet.Info.Attachments.Empty(); + foreach (var att in packet.Info.Attachments) + { + Item item = player.GetItemByGuid(att.ItemGUID); + if (item) + { + ItemTemplate itemProto = item.GetTemplate(); + if (itemProto == null || !itemProto.GetFlags().HasAnyFlag(ItemFlags.IsBoundToAccount)) + { + accountBound = false; + break; + } + } + } + + if (!accountBound && player.GetTeam() != receiverTeam && !HasPermission(RBACPermissions.TwoSideInteractionMail)) + { + player.SendMailResult(0, MailResponseType.Send, MailResponseResult.NotYourTeam); + return; + } + + if (receiverLevel < WorldConfig.GetIntValue(WorldCfg.MailLevelReq)) + { + SendNotification(CypherStrings.MailReceiverReq, WorldConfig.GetIntValue(WorldCfg.MailLevelReq)); + return; + } + + List items = new List(); + foreach (var att in packet.Info.Attachments) + { + if (att.ItemGUID.IsEmpty()) + { + player.SendMailResult(0, MailResponseType.Send, MailResponseResult.MailAttachmentInvalid); + return; + } + + Item item = player.GetItemByGuid(att.ItemGUID); + + // prevent sending bag with items (cheat: can be placed in bag after adding equipped empty bag to mail) + if (!item) + { + player.SendMailResult(0, MailResponseType.Send, MailResponseResult.MailAttachmentInvalid); + return; + } + + if (!item.CanBeTraded(true)) + { + player.SendMailResult(0, MailResponseType.Send, MailResponseResult.EquipError, InventoryResult.MailBoundItem); + return; + } + + if (item.IsBoundAccountWide() && item.IsSoulBound() && player.GetSession().GetAccountId() != receiverAccountId) + { + player.SendMailResult(0, MailResponseType.Send, MailResponseResult.EquipError, InventoryResult.NotSameAccount); + return; + } + + if (item.GetTemplate().GetFlags().HasAnyFlag(ItemFlags.Conjured) || item.GetUInt32Value(ItemFields.Duration) != 0) + { + player.SendMailResult(0, MailResponseType.Send, MailResponseResult.EquipError, InventoryResult.MailBoundItem); + return; + } + + if (packet.Info.Cod != 0 && item.HasFlag(ItemFields.Flags, ItemFieldFlags.Wrapped)) + { + player.SendMailResult(0, MailResponseType.Send, MailResponseResult.CantSendWrappedCod); + return; + } + + if (item.IsNotEmptyBag()) + { + player.SendMailResult(0, MailResponseType.Send, MailResponseResult.EquipError, InventoryResult.DestroyNonemptyBag); + return; + } + + items.Add(item); + } + + player.SendMailResult(0, MailResponseType.Send, MailResponseResult.Ok); + + player.ModifyMoney(-reqmoney); + player.UpdateCriteria(CriteriaTypes.GoldSpentForMail, cost); + + bool needItemDelay = false; + + MailDraft draft = new MailDraft(packet.Info.Subject, packet.Info.Body); + + SQLTransaction trans = new SQLTransaction(); + + if (!packet.Info.Attachments.Empty() || packet.Info.SendMoney > 0) + { + bool log = HasPermission(RBACPermissions.LogGmTrade); + if (!packet.Info.Attachments.Empty()) + { + foreach (var item in items) + { + if (log) + { + Log.outCommand(GetAccountId(), "GM {0} ({1}) (Account: {2}) mail item: {3} (Entry: {4} Count: {5}) to player: {6} ({7}) (Account: {8})", + GetPlayerName(), GetPlayer().GetGUID().ToString(), GetAccountId(), item.GetTemplate().GetName(), item.GetEntry(), item.GetCount(), + packet.Info.Target, receiverGuid.ToString(), receiverAccountId); + } + + item.SetNotRefundable(GetPlayer()); // makes the item no longer refundable + player.MoveItemFromInventory(item.GetBagSlot(), item.GetSlot(), true); + + item.DeleteFromInventoryDB(trans); // deletes item from character's inventory + item.SetOwnerGUID(receiverGuid); + item.SaveToDB(trans); // recursive and not have transaction guard into self, item not in inventory and can be save standalone + + draft.AddItem(item); + } + + // if item send to character at another account, then apply item delivery delay + needItemDelay = player.GetSession().GetAccountId() != receiverAccountId; + } + + if (log && packet.Info.SendMoney > 0) + { + Log.outCommand(GetAccountId(), "GM {0} ({1}) (Account: {{2}) mail money: {3} to player: {4} ({5}) (Account: {6})", + GetPlayerName(), GetPlayer().GetGUID().ToString(), GetAccountId(), packet.Info.SendMoney, packet.Info.Target, receiverGuid.ToString(), receiverAccountId); + } + } + + // If theres is an item, there is a one hour delivery delay if sent to another account's character. + uint deliver_delay = needItemDelay ? WorldConfig.GetUIntValue(WorldCfg.MailDeliveryDelay) : 0; + + // Mail sent between guild members arrives instantly + Guild guild = Global.GuildMgr.GetGuildById(player.GetGuildId()); + if (guild) + if (guild.IsMember(receiverGuid)) + deliver_delay = 0; + + // don't ask for COD if there are no items + if (packet.Info.Attachments.Empty()) + packet.Info.Cod = 0; + + // will delete item or place to receiver mail list + draft.AddMoney((ulong)packet.Info.SendMoney).AddCOD((uint)packet.Info.Cod).SendMailTo(trans, new MailReceiver(receiver, receiverGuid.GetCounter()), new MailSender(player), string.IsNullOrEmpty(packet.Info.Body) ? MailCheckMask.Copied : MailCheckMask.HasBody, deliver_delay); + + player.SaveInventoryAndGoldToDB(trans); + DB.Characters.CommitTransaction(trans); + } + + //called when mail is read + [WorldPacketHandler(ClientOpcodes.MailMarkAsRead)] + void HandleMailMarkAsRead(MailMarkAsRead packet) + { + if (!CanOpenMailBox(packet.Mailbox)) + return; + + Player player = GetPlayer(); + Mail m = player.GetMail(packet.MailID); + if (m != null && m.state != MailState.Deleted) + { + if (player.unReadMails != 0) + --player.unReadMails; + m.checkMask = m.checkMask | MailCheckMask.Read; + player.m_mailsUpdated = true; + m.state = MailState.Changed; + } + } + + //called when client deletes mail + [WorldPacketHandler(ClientOpcodes.MailDelete)] + void HandleMailDelete(MailDelete packet) + { + Mail m = GetPlayer().GetMail(packet.MailID); + Player player = GetPlayer(); + player.m_mailsUpdated = true; + if (m != null) + { + // delete shouldn't show up for COD mails + if (m.COD != 0) + { + player.SendMailResult(packet.MailID, MailResponseType.Deleted, MailResponseResult.InternalError); + return; + } + + m.state = MailState.Deleted; + } + player.SendMailResult(packet.MailID, MailResponseType.Deleted, MailResponseResult.Ok); + } + + [WorldPacketHandler(ClientOpcodes.MailReturnToSender)] + void HandleMailReturnToSender(MailReturnToSender packet) + { + Player player = GetPlayer(); + Mail m = player.GetMail(packet.MailID); + if (m == null || m.state == MailState.Deleted || m.deliver_time > Time.UnixTime || m.sender != packet.SenderGUID.GetCounter()) + { + player.SendMailResult(packet.MailID, MailResponseType.ReturnedToSender, MailResponseResult.InternalError); + return; + } + //we can return mail now, so firstly delete the old one + SQLTransaction trans = new SQLTransaction(); + + PreparedStatement stmt = DB.Characters.GetPreparedStatement(CharStatements.DEL_MAIL_BY_ID); + stmt.AddValue(0, packet.MailID); + trans.Append(stmt); + + stmt = DB.Characters.GetPreparedStatement(CharStatements.DEL_MAIL_ITEM_BY_ID); + stmt.AddValue(0, packet.MailID); + trans.Append(stmt); + + player.RemoveMail(packet.MailID); + + // only return mail if the player exists (and delete if not existing) + if (m.messageType == MailMessageType.Normal && m.sender != 0) + { + MailDraft draft = new MailDraft(m.subject, m.body); + if (m.mailTemplateId != 0) + draft = new MailDraft(m.mailTemplateId, false); // items already included + + if (m.HasItems()) + { + foreach (var itemInfo in m.items) + { + Item item = player.GetMItem(itemInfo.item_guid); + if (item) + draft.AddItem(item); + player.RemoveMItem(itemInfo.item_guid); + } + } + draft.AddMoney(m.money).SendReturnToSender(GetAccountId(), m.receiver, m.sender, trans); + } + + DB.Characters.CommitTransaction(trans); + + player.SendMailResult(packet.MailID, MailResponseType.ReturnedToSender, MailResponseResult.Ok); + } + + //called when player takes item attached in mail + [WorldPacketHandler(ClientOpcodes.MailTakeItem)] + void HandleMailTakeItem(MailTakeItem packet) + { + uint AttachID = packet.AttachID; + + if (!CanOpenMailBox(packet.Mailbox)) + return; + + Player player = GetPlayer(); + + Mail m = player.GetMail(packet.MailID); + if (m == null || m.state == MailState.Deleted || m.deliver_time > Time.UnixTime) + { + player.SendMailResult(packet.MailID, MailResponseType.ItemTaken, MailResponseResult.InternalError); + return; + } + + // verify that the mail has the item to avoid cheaters taking COD items without paying + if (!m.items.Any(p => p.item_guid == AttachID)) + { + player.SendMailResult(packet.MailID, MailResponseType.ItemTaken, MailResponseResult.InternalError); + return; + } + + // prevent cheating with skip client money check + if (!player.HasEnoughMoney(m.COD)) + { + player.SendMailResult(packet.MailID, MailResponseType.ItemTaken, MailResponseResult.NotEnoughMoney); + return; + } + + Item it = player.GetMItem(packet.AttachID); + + List dest = new List(); + InventoryResult msg = GetPlayer().CanStoreItem(ItemConst.NullBag, ItemConst.NullSlot, dest, it, false); + if (msg == InventoryResult.Ok) + { + SQLTransaction trans = new SQLTransaction(); + m.RemoveItem(packet.AttachID); + m.removedItems.Add(packet.AttachID); + + if (m.COD > 0) //if there is COD, take COD money from player and send them to sender by mail + { + ObjectGuid sender_guid = ObjectGuid.Create(HighGuid.Player, m.sender); + Player receiver = Global.ObjAccessor.FindPlayer(sender_guid); + + uint sender_accId = 0; + + if (HasPermission(RBACPermissions.LogGmTrade)) + { + string sender_name; + if (receiver) + { + sender_accId = receiver.GetSession().GetAccountId(); + sender_name = receiver.GetName(); + } + else + { + // can be calculated early + sender_accId = ObjectManager.GetPlayerAccountIdByGUID(sender_guid); + + if (!ObjectManager.GetPlayerNameByGUID(sender_guid, out sender_name)) + sender_name = Global.ObjectMgr.GetCypherString(CypherStrings.Unknown); + } + Log.outCommand(GetAccountId(), "GM {0} (Account: {1}) receiver mail item: {2} (Entry: {3} Count: {4}) and send COD money: {5} to player: {6} (Account: {7})", + GetPlayerName(), GetAccountId(), it.GetTemplate().GetName(), it.GetEntry(), it.GetCount(), m.COD, sender_name, sender_accId); + } + else if (!receiver) + sender_accId = ObjectManager.GetPlayerAccountIdByGUID(sender_guid); + + // check player existence + if (receiver || sender_accId != 0) + { + new MailDraft(m.subject, "") + .AddMoney(m.COD) + .SendMailTo(trans, new MailReceiver(receiver, m.sender), new MailSender( MailMessageType.Normal, m.receiver), MailCheckMask.CodPayment); + } + + player.ModifyMoney(-(int)m.COD); + } + m.COD = 0; + m.state = MailState.Changed; + player.m_mailsUpdated = true; + player.RemoveMItem(it.GetGUID().GetCounter()); + + uint count = it.GetCount(); // save counts before store and possible merge with deleting + it.SetState(ItemUpdateState.Unchanged); // need to set this state, otherwise item cannot be removed later, if neccessary + player.MoveItemToInventory(dest, it, true); + + player.SaveInventoryAndGoldToDB(trans); + player._SaveMail(trans); + DB.Characters.CommitTransaction(trans); + + player.SendMailResult(packet.MailID, MailResponseType.ItemTaken, MailResponseResult.Ok, 0, packet.AttachID, count); + } + else + player.SendMailResult(packet.MailID, MailResponseType.ItemTaken, MailResponseResult.EquipError, msg); + } + + [WorldPacketHandler(ClientOpcodes.MailTakeMoney)] + void HandleMailTakeMoney(MailTakeMoney packet) + { + if (!CanOpenMailBox(packet.Mailbox)) + return; + + Player player = GetPlayer(); + + Mail m = player.GetMail(packet.MailID); + if ((m == null || m.state == MailState.Deleted || m.deliver_time > Time.UnixTime) || + (packet.Money > 0 && m.money != (ulong)packet.Money)) + { + player.SendMailResult(packet.MailID, MailResponseType.MoneyTaken, MailResponseResult.InternalError); + return; + } + + if (!player.ModifyMoney((long)m.money, false)) + { + player.SendMailResult(packet.MailID, MailResponseType.MoneyTaken, MailResponseResult.EquipError, InventoryResult.TooMuchGold); + return; + } + + m.money = 0; + m.state = MailState.Changed; + player.m_mailsUpdated = true; + + player.SendMailResult(packet.MailID, MailResponseType.MoneyTaken, MailResponseResult.Ok); + + // save money and mail to prevent cheating + SQLTransaction trans = new SQLTransaction(); + player.SaveGoldToDB(trans); + player._SaveMail(trans); + DB.Characters.CommitTransaction(trans); + } + + //called when player lists his received mails + [WorldPacketHandler(ClientOpcodes.MailGetList)] + void HandleGetMailList(MailGetList packet) + { + if (!CanOpenMailBox(packet.Mailbox)) + return; + + Player player = GetPlayer(); + + //load players mails, and mailed items + if (!player.m_mailsLoaded) + player._LoadMail(); + + var mails = player.GetMails(); + + MailListResult response = new MailListResult(); + long curTime = Time.UnixTime; + + foreach (Mail m in mails) + { + // skip deleted or not delivered (deliver delay not expired) mails + if (m.state == MailState.Deleted || curTime < m.deliver_time) + continue; + + // max. 50 mails can be sent + if (response.Mails.Count < 50) + response.Mails.Add(new MailListEntry(m, player)); + } + + SendPacket(response); + + // recalculate m_nextMailDelivereTime and unReadMails + GetPlayer().UpdateNextMailTimeAndUnreads(); + } + + //used when player copies mail body to his inventory + [WorldPacketHandler(ClientOpcodes.MailCreateTextItem)] + void HandleMailCreateTextItem(MailCreateTextItem packet) + { + if (!CanOpenMailBox(packet.Mailbox)) + return; + + Player player = GetPlayer(); + + Mail m = player.GetMail(packet.MailID); + if (m == null || (string.IsNullOrEmpty(m.body) && m.mailTemplateId == 0) || m.state == MailState.Deleted || m.deliver_time > Time.UnixTime) + { + player.SendMailResult(packet.MailID, MailResponseType.MadePermanent, MailResponseResult.InternalError); + return; + } + + Item bodyItem = new Item(); // This is not bag and then can be used new Item. + if (!bodyItem.Create(Global.ObjectMgr.GetGenerator(HighGuid.Item).Generate(), 8383, player)) + return; + + // in mail template case we need create new item text + if (m.mailTemplateId != 0) + { + MailTemplateRecord mailTemplateEntry = CliDB.MailTemplateStorage.LookupByKey(m.mailTemplateId); + if (mailTemplateEntry == null) + { + player.SendMailResult(packet.MailID, MailResponseType.MadePermanent, MailResponseResult.InternalError); + return; + } + + bodyItem.SetText(mailTemplateEntry.Body[GetSessionDbcLocale()]); + } + else + bodyItem.SetText(m.body); + + if (m.messageType == MailMessageType.Normal) + bodyItem.SetGuidValue(ItemFields.Creator, ObjectGuid.Create(HighGuid.Player, m.sender)); + + bodyItem.SetFlag(ItemFields.Flags, ItemFieldFlags.Readable); + + Log.outInfo(LogFilter.Network, "HandleMailCreateTextItem mailid={0}", packet.MailID); + + List dest = new List(); + InventoryResult msg = GetPlayer().CanStoreItem(ItemConst.NullBag, ItemConst.NullSlot, dest, bodyItem, false); + if (msg == InventoryResult.Ok) + { + m.checkMask = m.checkMask | MailCheckMask.Copied; + m.state = MailState.Changed; + player.m_mailsUpdated = true; + + player.StoreItem(dest, bodyItem, true); + player.SendMailResult(packet.MailID, MailResponseType.MadePermanent, MailResponseResult.Ok); + } + else + player.SendMailResult(packet.MailID, MailResponseType.MadePermanent, MailResponseResult.EquipError, msg); + } + + [WorldPacketHandler(ClientOpcodes.QueryNextMailTime)] + void HandleQueryNextMailTime(MailQueryNextMailTime packet) + { + MailQueryNextTimeResult result = new MailQueryNextTimeResult(); + + if (!GetPlayer().m_mailsLoaded) + GetPlayer()._LoadMail(); + + if (GetPlayer().unReadMails > 0) + { + result.NextMailTime = 0.0f; + + long now = Time.UnixTime; + List sentSenders = new List(); + + foreach (Mail mail in GetPlayer().GetMails()) + { + if (mail.checkMask.HasAnyFlag(MailCheckMask.Read)) + continue; + + // and already delivered + if (now < mail.deliver_time) + continue; + + // only send each mail sender once + if (sentSenders.Any(p => p == mail.sender)) + continue; + + result.Next.Add(new MailQueryNextTimeResult.MailNextTimeEntry(mail)); + + sentSenders.Add(mail.sender); + + // do not send more than 2 mails + if (sentSenders.Count > 2) + break; + } + } + else + result.NextMailTime = -Time.Day; + + SendPacket(result); + } + + public void SendShowMailBox(ObjectGuid guid) + { + ShowMailbox packet = new ShowMailbox(); + packet.PostmasterGUID = guid; + SendPacket(packet); + } + } +} diff --git a/Game/Handlers/MiscHandler.cs b/Game/Handlers/MiscHandler.cs new file mode 100644 index 000000000..b1ab977e9 --- /dev/null +++ b/Game/Handlers/MiscHandler.cs @@ -0,0 +1,782 @@ +/* + * Copyright (C) 2012-2017 CypherCore + * + * 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, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +using Framework.Constants; +using Framework.IO; +using Game.BattleGrounds; +using Game.DataStorage; +using Game.Entities; +using Game.Groups; +using Game.Guilds; +using Game.Maps; +using Game.Network; +using Game.Network.Packets; +using Game.PvP; +using System; +using System.Collections.Generic; +using System.Text; + +namespace Game +{ + public partial class WorldSession + { + [WorldPacketHandler(ClientOpcodes.RequestAccountData, Status = SessionStatus.Authed)] + void HandleRequestAccountData(RequestAccountData request) + { + if (request.DataType > AccountDataTypes.Max) + return; + + AccountData adata = GetAccountData(request.DataType); + if (adata.Data == null) + return; + + UpdateAccountData data = new UpdateAccountData(); + data.Player = GetPlayer() ? GetPlayer().GetGUID() : ObjectGuid.Empty; + data.Time = (uint)adata.Time; + data.Size = (uint)adata.Data.Length; + data.DataType = request.DataType; + data.CompressedData = new ByteBuffer(ZLib.Compress(Encoding.UTF8.GetBytes(adata.Data))); + + SendPacket(data); + } + + [WorldPacketHandler(ClientOpcodes.UpdateAccountData, Status = SessionStatus.Authed)] + void HandleUpdateAccountData(UserClientUpdateAccountData packet) + { + if (packet.DataType > AccountDataTypes.Max) + return; + + if (packet.Size == 0) + { + SetAccountData(packet.DataType, 0, ""); + return; + } + + if (packet.Size > 0xFFFF) + { + Log.outError(LogFilter.Network, "UpdateAccountData: Account data packet too big, size {0}", packet.Size); + return; + } + + byte[] data = ZLib.Decompress(packet.CompressedData.GetData(), packet.Size); + SetAccountData(packet.DataType, packet.Time, Encoding.Default.GetString(data)); + } + + [WorldPacketHandler(ClientOpcodes.SetSelection)] + void HandleSetSelection(SetSelection packet) + { + GetPlayer().SetSelection(packet.Selection); + } + + [WorldPacketHandler(ClientOpcodes.ObjectUpdateFailed)] + void HandleObjectUpdateFailed(ObjectUpdateFailed objectUpdateFailed) + { + Log.outError(LogFilter.Network, "Object update failed for {0} for player {1} ({2})", objectUpdateFailed.ObjectGUID.ToString(), GetPlayerName(), GetPlayer().GetGUID().ToString()); + + // If create object failed for current player then client will be stuck on loading screen + if (GetPlayer().GetGUID() == objectUpdateFailed.ObjectGUID) + { + LogoutPlayer(true); + return; + } + + // Pretend we've never seen this object + GetPlayer().m_clientGUIDs.Remove(objectUpdateFailed.ObjectGUID); + } + + [WorldPacketHandler(ClientOpcodes.ObjectUpdateRescued, Processing = PacketProcessing.Inplace)] + void HandleObjectUpdateRescued(ObjectUpdateRescued objectUpdateRescued) + { + Log.outError(LogFilter.Network, "Object update rescued for {0} for player {1} ({2})", objectUpdateRescued.ObjectGUID.ToString(), GetPlayerName(), GetPlayer().GetGUID().ToString()); + + // Client received values update after destroying object + // re-register object in m_clientGUIDs to send DestroyObject on next visibility update + GetPlayer().m_clientGUIDs.Add(objectUpdateRescued.ObjectGUID); + } + + [WorldPacketHandler(ClientOpcodes.SetActionButton)] + void HandleSetActionButton(SetActionButton packet) + { + uint action = packet.GetButtonAction(); + uint type = packet.GetButtonType(); + + if (packet.Action == 0) + GetPlayer().RemoveActionButton(packet.Index); + else + GetPlayer().AddActionButton(packet.Index, action, type); + } + + [WorldPacketHandler(ClientOpcodes.SetActionBarToggles)] + void HandleSetActionBarToggles(SetActionBarToggles packet) + { + if (!GetPlayer()) // ignore until not logged (check needed because STATUS_AUTHED) + { + if (packet.Mask != 0) + Log.outError(LogFilter.Network, "WorldSession.HandleSetActionBarToggles in not logged state with value: {0}, ignored", packet.Mask); + return; + } + + GetPlayer().SetByteValue(PlayerFields.FieldBytes, PlayerFieldOffsets.FieldBytesOffsetActionBarToggles, packet.Mask); + } + + [WorldPacketHandler(ClientOpcodes.CompleteCinematic)] + void HandleCompleteCinematic(CompleteCinematic packet) + { + // If player has sight bound to visual waypoint NPC we should remove it + GetPlayer().GetCinematicMgr().EndCinematic(); + } + + [WorldPacketHandler(ClientOpcodes.NextCinematicCamera)] + void HandleNextCinematicCamera(NextCinematicCamera packet) + { + // Sent by client when cinematic actually begun. So we begin the server side process + GetPlayer().GetCinematicMgr().BeginCinematic(); + } + + [WorldPacketHandler(ClientOpcodes.CompleteMovie)] + void HandleCompleteMovie(CompleteMovie packet) + { + uint movie = _player.GetMovie(); + if (movie == 0) + return; + + _player.SetMovie(0); + Global.ScriptMgr.OnMovieComplete(_player, movie); + } + + [WorldPacketHandler(ClientOpcodes.ViolenceLevel, Processing = PacketProcessing.Inplace, Status = SessionStatus.Authed)] + void HandleViolenceLevel(ViolenceLevel violenceLevel) + { + // do something? + } + + [WorldPacketHandler(ClientOpcodes.AreaTrigger)] + void HandleAreaTrigger(AreaTriggerPkt packet) + { + Player player = GetPlayer(); + if (player.IsInFlight()) + { + Log.outDebug(LogFilter.Network, "HandleAreaTriggerOpcode: Player '{0}' (GUID: {1}) in flight, ignore Area Trigger ID:{2}", + player.GetName(), player.GetGUID().ToString(), packet.AreaTriggerID); + return; + } + + AreaTriggerRecord atEntry = CliDB.AreaTriggerStorage.LookupByKey(packet.AreaTriggerID); + if (atEntry == null) + { + Log.outDebug(LogFilter.Network, "HandleAreaTriggerOpcode: Player '{0}' (GUID: {1}) send unknown (by DBC) Area Trigger ID:{2}", + player.GetName(), player.GetGUID().ToString(), packet.AreaTriggerID); + return; + } + + if (packet.Entered && !player.IsInAreaTriggerRadius(atEntry)) + { + Log.outDebug(LogFilter.Network, "HandleAreaTriggerOpcode: Player '{0}' ({1}) too far, ignore Area Trigger ID: {2}", + player.GetName(), player.GetGUID().ToString(), packet.AreaTriggerID); + return; + } + + if (player.isDebugAreaTriggers) + player.SendSysMessage(packet.Entered ? CypherStrings.DebugAreatriggerEntered : CypherStrings.DebugAreatriggerLeft, packet.AreaTriggerID); + + if (Global.ScriptMgr.OnAreaTrigger(player, atEntry, packet.Entered)) + return; + + if (player.IsAlive()) + { + List quests = Global.ObjectMgr.GetQuestsForAreaTrigger(packet.AreaTriggerID); + if (quests != null) + { + foreach (uint questId in quests) + { + Quest qInfo = Global.ObjectMgr.GetQuestTemplate(questId); + if (qInfo != null && player.GetQuestStatus(questId) == QuestStatus.Incomplete) + { + foreach (QuestObjective obj in qInfo.Objectives) + { + if (obj.Type == QuestObjectiveType.AreaTrigger && !player.IsQuestObjectiveComplete(obj)) + { + player.SetQuestObjectiveData(obj, 1); + player.SendQuestUpdateAddCreditSimple(obj); + break; + } + } + + if (player.CanCompleteQuest(questId)) + player.CompleteQuest(questId); + } + } + } + } + + if (Global.ObjectMgr.IsTavernAreaTrigger(packet.AreaTriggerID)) + { + // set resting flag we are in the inn + player.GetRestMgr().SetRestFlag(RestFlag.Tavern, atEntry.Id); + + if (Global.WorldMgr.IsFFAPvPRealm()) + player.RemoveByteFlag(UnitFields.Bytes2, UnitBytes2Offsets.PvpFlag, UnitBytes2Flags.FFAPvp); + + return; + } + Battleground bg = player.GetBattleground(); + if (bg) + bg.HandleAreaTrigger(player, packet.AreaTriggerID, packet.Entered); + + OutdoorPvP pvp = player.GetOutdoorPvP(); + if (pvp != null) + { + if (pvp.HandleAreaTrigger(player, packet.AreaTriggerID, packet.Entered)) + return; + } + + AreaTriggerStruct at = Global.ObjectMgr.GetAreaTrigger(packet.AreaTriggerID); + if (at == null) + return; + + bool teleported = false; + if (player.GetMapId() != at.target_mapId) + { + EnterState denyReason = Global.MapMgr.PlayerCannotEnter(at.target_mapId, player, false); + if (denyReason != 0) + { + bool reviveAtTrigger = false; // should we revive the player if he is trying to enter the correct instance? + switch (denyReason) + { + case EnterState.CannotEnterNoEntry: + Log.outDebug(LogFilter.Maps, "MAP: Player '{0}' attempted to enter map with id {1} which has no entry", player.GetName(), at.target_mapId); + break; + case EnterState.CannotEnterUninstancedDungeon: + Log.outDebug(LogFilter.Maps, "MAP: Player '{0}' attempted to enter dungeon map {1} but no instance template was found", player.GetName(), at.target_mapId); + break; + case EnterState.CannotEnterDifficultyUnavailable: + { + Log.outDebug(LogFilter.Maps, "MAP: Player '{0}' attempted to enter instance map {1} but the requested difficulty was not found", player.GetName(), at.target_mapId); + MapRecord entry = CliDB.MapStorage.LookupByKey(at.target_mapId); + if (entry != null) + player.SendTransferAborted(entry.Id, TransferAbortReason.Difficulty, (byte)player.GetDifficultyID(entry)); + } + break; + case EnterState.CannotEnterNotInRaid: + Log.outDebug(LogFilter.Maps, "MAP: Player '{0}' must be in a raid group to enter map {1}", player.GetName(), at.target_mapId); + player.SendRaidGroupOnlyMessage(RaidGroupReason.Only, 0); + reviveAtTrigger = true; + break; + case EnterState.CannotEnterCorpseInDifferentInstance: + player.SendPacket(new AreaTriggerNoCorpse()); + Log.outDebug(LogFilter.Maps, "MAP: Player '{0}' does not have a corpse in instance map {1} and cannot enter", player.GetName(), at.target_mapId); + break; + case EnterState.CannotEnterInstanceBindMismatch: + { + MapRecord entry = CliDB.MapStorage.LookupByKey(at.target_mapId); + if (entry != null) + { + string mapName = entry.MapName[player.GetSession().GetSessionDbcLocale()]; + Log.outDebug(LogFilter.Maps, "MAP: Player '{0}' cannot enter instance map '{1}' because their permanent bind is incompatible with their group's", player.GetName(), mapName); + // is there a special opcode for this? + // @todo figure out how to get player localized difficulty string (e.g. "10 player", "Heroic" etc) + player.SendSysMessage(CypherStrings.InstanceBindMismatch, mapName); + } + reviveAtTrigger = true; + } + break; + case EnterState.CannotEnterTooManyInstances: + player.SendTransferAborted(at.target_mapId, TransferAbortReason.TooManyInstances); + Log.outDebug(LogFilter.Maps, "MAP: Player '{0}' cannot enter instance map {1} because he has exceeded the maximum number of instances per hour.", player.GetName(), at.target_mapId); + reviveAtTrigger = true; + break; + case EnterState.CannotEnterMaxPlayers: + player.SendTransferAborted(at.target_mapId, TransferAbortReason.MaxPlayers); + reviveAtTrigger = true; + break; + case EnterState.CannotEnterZoneInCombat: + player.SendTransferAborted(at.target_mapId, TransferAbortReason.ZoneInCombat); + reviveAtTrigger = true; + break; + default: + break; + } + + if (reviveAtTrigger) // check if the player is touching the areatrigger leading to the map his corpse is on + { + if (!player.IsAlive() && player.HasCorpse()) + { + if (player.GetCorpseLocation().GetMapId() == at.target_mapId) + { + player.ResurrectPlayer(0.5f); + player.SpawnCorpseBones(); + } + } + } + + return; + } + + Group group = player.GetGroup(); + if (group) + if (group.isLFGGroup() && player.GetMap().IsDungeon()) + teleported = player.TeleportToBGEntryPoint(); + } + + if (!teleported) + { + WorldSafeLocsRecord entranceLocation = null; + InstanceSave instanceSave = player.GetInstanceSave(at.target_mapId); + if (instanceSave != null) + { + // Check if we can contact the instancescript of the instance for an updated entrance location + Map map = Global.MapMgr.FindMap(at.target_mapId, player.GetInstanceSave(at.target_mapId).GetInstanceId()); + if (map) + { + InstanceMap instanceMap = map.ToInstanceMap(); + if (instanceMap != null) + { + InstanceScript instanceScript = instanceMap.GetInstanceScript(); + if (instanceScript != null) + entranceLocation = CliDB.WorldSafeLocsStorage.LookupByKey(instanceScript.GetEntranceLocation()); + } + } + + // Finally check with the instancesave for an entrance location if we did not get a valid one from the instancescript + if (entranceLocation == null) + entranceLocation = CliDB.WorldSafeLocsStorage.LookupByKey(instanceSave.GetEntranceLocation()); + } + + if (entranceLocation != null) + player.TeleportTo(entranceLocation.MapID, entranceLocation.Loc.X, entranceLocation.Loc.Y, entranceLocation.Loc.Z, (float)(entranceLocation.Facing * Math.PI / 180), TeleportToOptions.NotLeaveTransport); + else + player.TeleportTo(at.target_mapId, at.target_X, at.target_Y, at.target_Z, at.target_Orientation, TeleportToOptions.NotLeaveTransport); + } + } + + [WorldPacketHandler(ClientOpcodes.RequestPlayedTime)] + void HandlePlayedTime(RequestPlayedTime packet) + { + PlayedTime playedTime = new PlayedTime(); + playedTime.TotalTime = GetPlayer().GetTotalPlayedTime(); + playedTime.LevelTime = GetPlayer().GetLevelPlayedTime(); + playedTime.TriggerEvent = packet.TriggerScriptEvent; // 0-1 - will not show in chat frame + SendPacket(playedTime); + } + + [WorldPacketHandler(ClientOpcodes.SaveCufProfiles, Processing = PacketProcessing.Inplace)] + void HandleSaveCUFProfiles(SaveCUFProfiles packet) + { + if (packet.CUFProfiles.Count > PlayerConst.MaxCUFProfiles) + { + Log.outError(LogFilter.Player, "HandleSaveCUFProfiles - {0} tried to save more than {1} CUF profiles. Hacking attempt?", GetPlayerName(), PlayerConst.MaxCUFProfiles); + return; + } + + for (byte i = 0; i < packet.CUFProfiles.Count; ++i) + GetPlayer().SaveCUFProfile(i, packet.CUFProfiles[i]); + + for (byte i = (byte)packet.CUFProfiles.Count; i < PlayerConst.MaxCUFProfiles; ++i) + GetPlayer().SaveCUFProfile(i, null); + } + + public void SendLoadCUFProfiles() + { + Player player = GetPlayer(); + + LoadCUFProfiles loadCUFProfiles = new LoadCUFProfiles(); + + for (byte i = 0; i < PlayerConst.MaxCUFProfiles; ++i) + { + CUFProfile cufProfile = player.GetCUFProfile(i); + if (cufProfile != null) + loadCUFProfiles.CUFProfiles.Add(cufProfile); + } + + SendPacket(loadCUFProfiles); + } + + [WorldPacketHandler(ClientOpcodes.SetAdvancedCombatLogging, Processing = PacketProcessing.Inplace)] + void HandleSetAdvancedCombatLogging(SetAdvancedCombatLogging setAdvancedCombatLogging) + { + GetPlayer().SetAdvancedCombatLogging(setAdvancedCombatLogging.Enable); + } + + [WorldPacketHandler(ClientOpcodes.MountSpecialAnim)] + void HandleMountSpecialAnim(MountSpecial mountSpecial) + { + SpecialMountAnim specialMountAnim = new SpecialMountAnim(); + specialMountAnim.UnitGUID = _player.GetGUID(); + GetPlayer().SendMessageToSet(specialMountAnim, false); + } + + [WorldPacketHandler(ClientOpcodes.MountSetFavorite)] + void HandleMountSetFavorite(MountSetFavorite mountSetFavorite) + { + _collectionMgr.MountSetFavorite(mountSetFavorite.MountSpellID, mountSetFavorite.IsFavorite); + } + + [WorldPacketHandler(ClientOpcodes.PvpPrestigeRankUp)] + void HandlePvpPrestigeRankUp(PvpPrestigeRankUp pvpPrestigeRankUp) + { + if (_player.CanPrestige()) + _player.Prestige(); + } + + [WorldPacketHandler(ClientOpcodes.ChatUnregisterAllAddonPrefixes)] + void HandleUnregisterAllAddonPrefixes(ChatUnregisterAllAddonPrefixes packet) + { + _registeredAddonPrefixes.Clear(); + } + + [WorldPacketHandler(ClientOpcodes.ChatRegisterAddonPrefixes)] + void HandleAddonRegisteredPrefixes(ChatRegisterAddonPrefixes packet) + { + _registeredAddonPrefixes.AddRange(packet.Prefixes); + + if (_registeredAddonPrefixes.Count > 64) // shouldn't happen + { + _filterAddonMessages = false; + return; + } + + _filterAddonMessages = true; + } + + [WorldPacketHandler(ClientOpcodes.TogglePvp)] + void HandleTogglePvP(TogglePvP packet) + { + Player player = GetPlayer(); + + bool inPvP = player.HasFlag(PlayerFields.Flags, PlayerFlags.InPVP); + player.ApplyModFlag(PlayerFields.Flags, PlayerFlags.InPVP, !inPvP); + player.ApplyModFlag(PlayerFields.Flags, PlayerFlags.PVPTimer, inPvP); + + if (player.HasFlag(PlayerFields.Flags, PlayerFlags.InPVP)) + { + if (!player.IsPvP() || player.pvpInfo.EndTimer != 0) + player.UpdatePvP(true, true); + } + else + { + if (!player.pvpInfo.IsHostile && player.IsPvP()) + player.pvpInfo.EndTimer = Time.UnixTime; // start toggle-off + } + } + + [WorldPacketHandler(ClientOpcodes.SetPvp)] + void HandleSetPvP(SetPvP packet) + { + Player player = GetPlayer(); + + player.ApplyModFlag(PlayerFields.Flags, PlayerFlags.InPVP, packet.EnablePVP); + player.ApplyModFlag(PlayerFields.Flags, PlayerFlags.PVPTimer, !packet.EnablePVP); + + if (player.HasFlag(PlayerFields.Flags, PlayerFlags.InPVP)) + { + if (!player.IsPvP() || player.pvpInfo.EndTimer != 0) + player.UpdatePvP(true, true); + } + else + { + if (!player.pvpInfo.IsHostile && player.IsPvP()) + player.pvpInfo.EndTimer = Time.UnixTime; // start set-off + } + } + + [WorldPacketHandler(ClientOpcodes.FarSight)] + void HandleFarSight(FarSight farSight) + { + if (farSight.Enable) + { + Log.outDebug(LogFilter.Network, "Added FarSight {0} to player {1}", GetPlayer().GetUInt64Value(PlayerFields.Farsight), GetPlayer().GetGUID().ToString()); + WorldObject target = GetPlayer().GetViewpoint(); + if (target) + GetPlayer().SetSeer(target); + else + Log.outDebug(LogFilter.Network, "Player {0} (GUID: {1}) requests non-existing seer {2}", GetPlayer().GetName(), GetPlayer().GetGUID().ToString(), GetPlayer().GetUInt64Value(PlayerFields.Farsight)); + } + else + { + Log.outDebug(LogFilter.Network, "Player {0} set vision to self", GetPlayer().GetGUID().ToString()); + GetPlayer().SetSeer(GetPlayer()); + } + + GetPlayer().UpdateVisibilityForPlayer(); + } + + [WorldPacketHandler(ClientOpcodes.SetTitle)] + void HandleSetTitle(SetTitle packet) + { + // -1 at none + if (packet.TitleID > 0 && packet.TitleID < PlayerConst.MaxTitleIndex) + { + if (!GetPlayer().HasTitle((uint)packet.TitleID)) + return; + } + else + packet.TitleID = 0; + + GetPlayer().SetUInt32Value(PlayerFields.ChosenTitle, (uint)packet.TitleID); + } + + [WorldPacketHandler(ClientOpcodes.ResetInstances)] + void HandleResetInstances(ResetInstances packet) + { + Group group = GetPlayer().GetGroup(); + if (group) + { + if (group.IsLeader(GetPlayer().GetGUID())) + group.ResetInstances(InstanceResetMethod.All, false, false, GetPlayer()); + } + else + GetPlayer().ResetInstances(InstanceResetMethod.All, false, false); + } + + [WorldPacketHandler(ClientOpcodes.SetDungeonDifficulty)] + void HandleSetDungeonDifficulty(SetDungeonDifficulty setDungeonDifficulty) + { + DifficultyRecord difficultyEntry = CliDB.DifficultyStorage.LookupByKey(setDungeonDifficulty.DifficultyID); + if (difficultyEntry == null) + { + Log.outDebug(LogFilter.Network, "WorldSession.HandleSetDungeonDifficulty: {0} sent an invalid instance mode {1}!", + GetPlayer().GetGUID().ToString(), setDungeonDifficulty.DifficultyID); + return; + } + + if (difficultyEntry.InstanceType != MapTypes.Instance) + { + Log.outDebug(LogFilter.Network, "WorldSession.HandleSetDungeonDifficulty: {0} sent an non-dungeon instance mode {1}!", + GetPlayer().GetGUID().ToString(), difficultyEntry.Id); + return; + } + + if (!difficultyEntry.Flags.HasAnyFlag(DifficultyFlags.CanSelect)) + { + Log.outDebug(LogFilter.Network, "WorldSession.HandleSetDungeonDifficulty: {0} sent unselectable instance mode {1}!", + GetPlayer().GetGUID().ToString(), difficultyEntry.Id); + return; + } + + Difficulty difficultyID = (Difficulty)difficultyEntry.Id; + if (difficultyID == GetPlayer().GetDungeonDifficultyID()) + return; + + // cannot reset while in an instance + Map map = GetPlayer().GetMap(); + if (map && map.IsDungeon()) + { + Log.outDebug(LogFilter.Network, "WorldSession:HandleSetDungeonDifficulty: player (Name: {0}, {1}) tried to reset the instance while player is inside!", + GetPlayer().GetName(), GetPlayer().GetGUID().ToString()); + return; + } + + Group group = GetPlayer().GetGroup(); + if (group) + { + if (group.IsLeader(GetPlayer().GetGUID())) + { + for (GroupReference refe = group.GetFirstMember(); refe != null; refe = refe.next()) + { + Player groupGuy = refe.GetSource(); + if (!groupGuy) + continue; + + if (!groupGuy.IsInMap(groupGuy)) + return; + + if (groupGuy.GetMap().IsNonRaidDungeon()) + { + Log.outDebug(LogFilter.Network, "WorldSession:HandleSetDungeonDifficulty: {0} tried to reset the instance while group member (Name: {1}, {2}) is inside!", + GetPlayer().GetGUID().ToString(), groupGuy.GetName(), groupGuy.GetGUID().ToString()); + return; + } + } + // the difficulty is set even if the instances can't be reset + //_player->SendDungeonDifficulty(true); + group.ResetInstances(InstanceResetMethod.ChangeDifficulty, false, false, GetPlayer()); + group.SetDungeonDifficultyID(difficultyID); + } + } + else + { + GetPlayer().ResetInstances(InstanceResetMethod.ChangeDifficulty, false, false); + GetPlayer().SetDungeonDifficultyID(difficultyID); + GetPlayer().SendDungeonDifficulty(); + } + } + + [WorldPacketHandler(ClientOpcodes.SetRaidDifficulty)] + void HandleSetRaidDifficulty(SetRaidDifficulty setRaidDifficulty) + { + DifficultyRecord difficultyEntry = CliDB.DifficultyStorage.LookupByKey(setRaidDifficulty.DifficultyID); + if (difficultyEntry == null) + { + Log.outDebug(LogFilter.Network, "WorldSession.HandleSetDungeonDifficulty: {0} sent an invalid instance mode {1}!", + GetPlayer().GetGUID().ToString(), setRaidDifficulty.DifficultyID); + return; + } + + if (difficultyEntry.InstanceType != MapTypes.Raid) + { + Log.outDebug(LogFilter.Network, "WorldSession.HandleSetDungeonDifficulty: {0} sent an non-dungeon instance mode {1}!", + GetPlayer().GetGUID().ToString(), difficultyEntry.Id); + return; + } + + if (!difficultyEntry.Flags.HasAnyFlag(DifficultyFlags.CanSelect)) + { + Log.outDebug(LogFilter.Network, "WorldSession.HandleSetDungeonDifficulty: {0} sent unselectable instance mode {1}!", + GetPlayer().GetGUID().ToString(), difficultyEntry.Id); + return; + } + + if (((int)(difficultyEntry.Flags & DifficultyFlags.Legacy) >> 5) != setRaidDifficulty.Legacy) + { + Log.outDebug(LogFilter.Network, "WorldSession.HandleSetDungeonDifficulty: {0} sent not matching legacy difficulty {1}!", + GetPlayer().GetGUID().ToString(), difficultyEntry.Id); + return; + } + + Difficulty difficultyID = (Difficulty)difficultyEntry.Id; + if (difficultyID == (setRaidDifficulty.Legacy != 0 ? GetPlayer().GetLegacyRaidDifficultyID() : GetPlayer().GetRaidDifficultyID())) + return; + + // cannot reset while in an instance + Map map = GetPlayer().GetMap(); + if (map && map.IsDungeon()) + { + Log.outDebug(LogFilter.Network, "WorldSession:HandleSetRaidDifficulty: player (Name: {0}, {1} tried to reset the instance while inside!", + GetPlayer().GetName(), GetPlayer().GetGUID().ToString()); + return; + } + + Group group = GetPlayer().GetGroup(); + if (group) + { + if (group.IsLeader(GetPlayer().GetGUID())) + { + for (GroupReference refe = group.GetFirstMember(); refe != null; refe = refe.next()) + { + Player groupGuy = refe.GetSource(); + if (!groupGuy) + continue; + + if (!groupGuy.IsInMap(groupGuy)) + return; + + if (groupGuy.GetMap().IsRaid()) + { + Log.outDebug(LogFilter.Network, "WorldSession:HandleSetRaidDifficulty: player {0} tried to reset the instance while inside!", GetPlayer().GetGUID().ToString()); + return; + } + } + // the difficulty is set even if the instances can't be reset + group.ResetInstances(InstanceResetMethod.ChangeDifficulty, true, setRaidDifficulty.Legacy != 0, GetPlayer()); + if (setRaidDifficulty.Legacy != 0) + group.SetLegacyRaidDifficultyID(difficultyID); + else + group.SetRaidDifficultyID(difficultyID); + } + } + else + { + GetPlayer().ResetInstances(InstanceResetMethod.ChangeDifficulty, true, setRaidDifficulty.Legacy != 0); + if (setRaidDifficulty.Legacy != 0) + GetPlayer().SetLegacyRaidDifficultyID(difficultyID); + else + GetPlayer().SetRaidDifficultyID(difficultyID); + + GetPlayer().SendRaidDifficulty(setRaidDifficulty.Legacy != 0); + } + } + + [WorldPacketHandler(ClientOpcodes.SetTaxiBenchmarkMode, Processing = PacketProcessing.Inplace)] + void HandleSetTaxiBenchmark(SetTaxiBenchmarkMode packet) + { + _player.ApplyModFlag(PlayerFields.Flags, PlayerFlags.TaxiBenchmark, packet.Enable); + } + + public void SendSetPhaseShift(List phaseIds, List terrainswaps, List worldMapAreaSwaps) + { + PhaseShift phaseShift = new PhaseShift(); + phaseShift.ClientGUID = GetPlayer().GetGUID(); + phaseShift.PersonalGUID = GetPlayer().GetGUID(); + phaseShift.PhaseShifts = phaseIds; + phaseShift.VisibleMapIDs = terrainswaps; + phaseShift.UiWorldMapAreaIDSwaps = worldMapAreaSwaps; + SendPacket(phaseShift); + } + + [WorldPacketHandler(ClientOpcodes.GuildSetFocusedAchievement)] + void HandleGuildSetFocusedAchievement(GuildSetFocusedAchievement setFocusedAchievement) + { + Guild guild = Global.GuildMgr.GetGuildById(GetPlayer().GetGuildId()); + if (guild) + guild.GetAchievementMgr().SendAchievementInfo(GetPlayer(), setFocusedAchievement.AchievementID); + } + + [WorldPacketHandler(ClientOpcodes.InstanceLockResponse)] + void HandleInstanceLockResponse(InstanceLockResponse packet) + { + if (!GetPlayer().HasPendingBind()) + { + Log.outInfo(LogFilter.Network, "InstanceLockResponse: Player {0} (guid {1}) tried to bind himself/teleport to graveyard without a pending bind!", + GetPlayer().GetName(), GetPlayer().GetGUID().ToString()); + return; + } + + if (packet.AcceptLock) + GetPlayer().BindToInstance(); + else + GetPlayer().RepopAtGraveyard(); + + GetPlayer().SetPendingBind(0, 0); + } + + [WorldPacketHandler(ClientOpcodes.WardenData)] + void HandleWardenDataOpcode(WardenData packet) + { + if (_warden == null || packet.Data.GetSize() == 0) + return; + + var unpacked = new ByteBuffer(_warden.DecryptData(packet.Data.GetData())); + WardenOpcodes opcode = (WardenOpcodes)packet.Data.ReadUInt8(); + + switch (opcode) + { + case WardenOpcodes.CMSG_ModuleMissing: + _warden.SendModuleToClient(); + break; + case WardenOpcodes.Cmsg_ModuleOk: + _warden.RequestHash(); + break; + case WardenOpcodes.Smsg_CheatChecksRequest: + _warden.HandleData(packet.Data); + break; + case WardenOpcodes.Cmsg_MemChecksResult: + Log.outDebug(LogFilter.Warden, "NYI WARDEN_CMSG_MEM_CHECKS_RESULT received!"); + break; + case WardenOpcodes.Cmsg_HashResult: + _warden.HandleHashResult(packet.Data); + _warden.InitializeModule(); + break; + case WardenOpcodes.Cmsg_ModuleFailed: + Log.outDebug(LogFilter.Warden, "NYI WARDEN_CMSG_MODULE_FAILED received!"); + break; + default: + Log.outDebug(LogFilter.Warden, "Got unknown warden opcode {0} of size {1}.", opcode, packet.Data.GetSize() - 1); + break; + } + } + } +} diff --git a/Game/Handlers/MovementHandler.cs b/Game/Handlers/MovementHandler.cs new file mode 100644 index 000000000..7871d434d --- /dev/null +++ b/Game/Handlers/MovementHandler.cs @@ -0,0 +1,651 @@ +/* + * Copyright (C) 2012-2017 CypherCore + * + * 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, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +using Framework.Constants; +using Game.BattleGrounds; +using Game.DataStorage; +using Game.Entities; +using Game.Garrisons; +using Game.Maps; +using Game.Movement; +using Game.Network; +using Game.Network.Packets; +using System; +using System.Collections.Generic; + +namespace Game +{ + public partial class WorldSession + { + [WorldPacketHandler(ClientOpcodes.MoveChangeTransport, Processing = PacketProcessing.ThreadSafe)] + [WorldPacketHandler(ClientOpcodes.MoveFallReset, Processing = PacketProcessing.ThreadSafe)] + [WorldPacketHandler(ClientOpcodes.MoveFallLand, Processing = PacketProcessing.ThreadSafe)] + [WorldPacketHandler(ClientOpcodes.MoveHeartbeat, Processing = PacketProcessing.ThreadSafe)] + [WorldPacketHandler(ClientOpcodes.MoveJump, Processing = PacketProcessing.ThreadSafe)] + [WorldPacketHandler(ClientOpcodes.MoveSetFacing, Processing = PacketProcessing.ThreadSafe)] + [WorldPacketHandler(ClientOpcodes.MoveSetPitch, Processing = PacketProcessing.ThreadSafe)] + [WorldPacketHandler(ClientOpcodes.MoveStartAscend, Processing = PacketProcessing.ThreadSafe)] + [WorldPacketHandler(ClientOpcodes.MoveStartBackward, Processing = PacketProcessing.ThreadSafe)] + [WorldPacketHandler(ClientOpcodes.MoveStartDescend, Processing = PacketProcessing.ThreadSafe)] + [WorldPacketHandler(ClientOpcodes.MoveStartForward, Processing = PacketProcessing.ThreadSafe)] + [WorldPacketHandler(ClientOpcodes.MoveStartPitchDown, Processing = PacketProcessing.ThreadSafe)] + [WorldPacketHandler(ClientOpcodes.MoveStartPitchUp, Processing = PacketProcessing.ThreadSafe)] + [WorldPacketHandler(ClientOpcodes.MoveStartStrafeLeft, Processing = PacketProcessing.ThreadSafe)] + [WorldPacketHandler(ClientOpcodes.MoveStartStrafeRight, Processing = PacketProcessing.ThreadSafe)] + [WorldPacketHandler(ClientOpcodes.MoveStartSwim, Processing = PacketProcessing.ThreadSafe)] + [WorldPacketHandler(ClientOpcodes.MoveStartTurnLeft, Processing = PacketProcessing.ThreadSafe)] + [WorldPacketHandler(ClientOpcodes.MoveStartTurnRight, Processing = PacketProcessing.ThreadSafe)] + [WorldPacketHandler(ClientOpcodes.MoveStop, Processing = PacketProcessing.ThreadSafe)] + [WorldPacketHandler(ClientOpcodes.MoveStopAscend, Processing = PacketProcessing.ThreadSafe)] + [WorldPacketHandler(ClientOpcodes.MoveStopPitch, Processing = PacketProcessing.ThreadSafe)] + [WorldPacketHandler(ClientOpcodes.MoveStopStrafe, Processing = PacketProcessing.ThreadSafe)] + [WorldPacketHandler(ClientOpcodes.MoveStopSwim, Processing = PacketProcessing.ThreadSafe)] + [WorldPacketHandler(ClientOpcodes.MoveStopTurn, Processing = PacketProcessing.ThreadSafe)] + [WorldPacketHandler(ClientOpcodes.MoveDoubleJump, Processing = PacketProcessing.ThreadSafe)] + void HandleMovement(ClientPlayerMovement packet) + { + HandleMovementOpcode(packet.GetOpcode(), packet.movementInfo); + } + + void HandleMovementOpcode(ClientOpcodes opcode, MovementInfo movementInfo) + { + Unit mover = GetPlayer().m_unitMovedByMe; + Player plrMover = mover.ToPlayer(); + + if (plrMover && plrMover.IsBeingTeleported()) + return; + + GetPlayer().ValidateMovementInfo(movementInfo); + + if (movementInfo.Guid != mover.GetGUID()) + { + Log.outError(LogFilter.Network, "HandleMovementOpcodes: guid error"); + return; + } + if (!movementInfo.Pos.IsPositionValid()) + { + Log.outError(LogFilter.Network, "HandleMovementOpcodes: Invalid Position"); + return; + } + + // stop some emotes at player move + if (plrMover && (plrMover.GetUInt32Value(UnitFields.NpcEmotestate) != 0)) + plrMover.SetUInt32Value(UnitFields.NpcEmotestate, (uint)Emote.OneshotNone); + + //handle special cases + if (!movementInfo.transport.guid.IsEmpty()) + { + if (movementInfo.transport.pos.GetPositionX() > 50 || movementInfo.transport.pos.GetPositionY() > 50 || movementInfo.transport.pos.GetPositionZ() > 50) + return; + + if (!GridDefines.IsValidMapCoord(movementInfo.Pos.posX + movementInfo.transport.pos.posX, movementInfo.Pos.posY + movementInfo.transport.pos.posY, + movementInfo.Pos.posZ + movementInfo.transport.pos.posZ, movementInfo.Pos.Orientation + movementInfo.transport.pos.Orientation)) + return; + + if (plrMover) + { + if (!plrMover.GetTransport()) + { + Transport transport = plrMover.GetMap().GetTransport(movementInfo.transport.guid); + if (transport) + transport.AddPassenger(plrMover); + } + else if (plrMover.GetTransport().GetGUID() != movementInfo.transport.guid) + { + plrMover.GetTransport().RemovePassenger(plrMover); + Transport transport = plrMover.GetMap().GetTransport(movementInfo.transport.guid); + if (transport) + transport.AddPassenger(plrMover); + else + movementInfo.ResetTransport(); + } + } + + if (!mover.GetTransport() && !mover.GetVehicle()) + { + GameObject go = mover.GetMap().GetGameObject(movementInfo.transport.guid); + if (!go || go.GetGoType() != GameObjectTypes.Transport) + movementInfo.transport.Reset(); + } + } + else if (plrMover && plrMover.GetTransport()) // if we were on a transport, leave + plrMover.GetTransport().RemovePassenger(plrMover); + + // fall damage generation (ignore in flight case that can be triggered also at lags in moment teleportation to another map). + if (opcode == ClientOpcodes.MoveFallLand && plrMover && !plrMover.IsInFlight()) + plrMover.HandleFall(movementInfo); + + if (plrMover && movementInfo.HasMovementFlag(MovementFlag.Swimming) != plrMover.IsInWater()) + { + // now client not include swimming flag in case jumping under water + plrMover.SetInWater(!plrMover.IsInWater() || plrMover.GetMap().IsUnderWater(movementInfo.Pos.posX, movementInfo.Pos.posY, movementInfo.Pos.posZ)); + } + + uint mstime = Time.GetMSTime(); + + if (m_clientTimeDelay == 0) + m_clientTimeDelay = mstime - movementInfo.Time; + + movementInfo.Time = movementInfo.Time + m_clientTimeDelay; + + movementInfo.Guid = mover.GetGUID(); + mover.m_movementInfo = movementInfo; + + // Some vehicles allow the passenger to turn by himself + Vehicle vehicle = mover.GetVehicle(); + if (vehicle) + { + VehicleSeatRecord seat = vehicle.GetSeatForPassenger(mover); + if (seat != null) + { + if (seat.Flags[0].HasAnyFlag((uint)VehicleSeatFlags.AllowTurning)) + { + if (movementInfo.Pos.GetOrientation() != mover.GetOrientation()) + { + mover.SetOrientation(movementInfo.Pos.GetOrientation()); + mover.RemoveAurasWithInterruptFlags(SpellAuraInterruptFlags.Turning); + } + } + } + return; + } + + mover.UpdatePosition(movementInfo.Pos); + + MoveUpdate moveUpdate = new MoveUpdate(); + moveUpdate.movementInfo = mover.m_movementInfo; + mover.SendMessageToSet(moveUpdate, GetPlayer()); + + if (plrMover) // nothing is charmed, or player charmed + { + if (plrMover.IsSitState() && movementInfo.HasMovementFlag(MovementFlag.MaskMoving | MovementFlag.MaskTurning)) + plrMover.SetStandState(UnitStandStateType.Stand); + + plrMover.UpdateFallInformationIfNeed(movementInfo, opcode); + + if (movementInfo.Pos.posZ < plrMover.GetMap().GetMinHeight(movementInfo.Pos.GetPositionX(), movementInfo.Pos.GetPositionY())) + { + if (!(plrMover.GetBattleground() && plrMover.GetBattleground().HandlePlayerUnderMap(GetPlayer()))) + { + // NOTE: this is actually called many times while falling + // even after the player has been teleported away + /// @todo discard movement packets after the player is rooted + if (plrMover.IsAlive()) + { + plrMover.SetFlag(PlayerFields.Flags, PlayerFlags.IsOutOfBounds); + plrMover.EnvironmentalDamage(EnviromentalDamage.FallToVoid, (uint)GetPlayer().GetMaxHealth()); + // player can be alive if GM/etc + // change the death state to CORPSE to prevent the death timer from + // starting in the next player update + if (!plrMover.IsAlive()) + plrMover.KillPlayer(); + } + } + } + else + plrMover.RemoveFlag(PlayerFields.Flags, PlayerFlags.IsOutOfBounds); + } + } + + [WorldPacketHandler(ClientOpcodes.WorldPortResponse, Status = SessionStatus.Transfer)] + void HandleMoveWorldportAck(WorldPortResponse packet) + { + HandleMoveWorldportAck(); + } + + void HandleMoveWorldportAck() + { + // ignore unexpected far teleports + if (!GetPlayer().IsBeingTeleportedFar()) + return; + + bool seamlessTeleport = GetPlayer().IsBeingTeleportedSeamlessly(); + GetPlayer().SetSemaphoreTeleportFar(false); + + // get the teleport destination + WorldLocation loc = GetPlayer().GetTeleportDest(); + + // possible errors in the coordinate validity check + if (!GridDefines.IsValidMapCoord(loc)) + { + LogoutPlayer(false); + return; + } + + // get the destination map entry, not the current one, this will fix homebind and reset greeting + MapRecord mapEntry = CliDB.MapStorage.LookupByKey(loc.GetMapId()); + InstanceTemplate mInstance = Global.ObjectMgr.GetInstanceTemplate(loc.GetMapId()); + + // reset instance validity, except if going to an instance inside an instance + if (!GetPlayer().m_InstanceValid && mInstance == null) + GetPlayer().m_InstanceValid = true; + + Map oldMap = GetPlayer().GetMap(); + Map newMap = Global.MapMgr.CreateMap(loc.GetMapId(), GetPlayer()); + + if (GetPlayer().IsInWorld) + { + Log.outError(LogFilter.Network, "Player (Name {0}) is still in world when teleported from map {1} to new map {2}", GetPlayer().GetName(), oldMap.GetId(), loc.GetMapId()); + oldMap.RemovePlayerFromMap(GetPlayer(), false); + } + + // relocate the player to the teleport destination + // the CannotEnter checks are done in TeleporTo but conditions may change + // while the player is in transit, for example the map may get full + if (newMap == null || newMap.CannotEnter(GetPlayer()) != 0) + { + Log.outError(LogFilter.Network, "Map {0} could not be created for {1} ({2}), porting player to homebind", loc.GetMapId(), newMap ? newMap.GetMapName() : "Unknown", GetPlayer().GetGUID().ToString()); + GetPlayer().TeleportTo(GetPlayer().GetHomebind()); + return; + } + + float z = loc.GetPositionZ(); + if (GetPlayer().HasUnitMovementFlag(MovementFlag.Hover)) + z += GetPlayer().GetFloatValue(UnitFields.HoverHeight); + + GetPlayer().Relocate(loc.GetPositionX(), loc.GetPositionY(), z, loc.GetOrientation()); + + GetPlayer().ResetMap(); + GetPlayer().SetMap(newMap); + + ResumeToken resumeToken = new ResumeToken(); + resumeToken.SequenceIndex = _player.m_movementCounter; + resumeToken.Reason = seamlessTeleport ? 2 : 1u; + SendPacket(resumeToken); + + if (!seamlessTeleport) + GetPlayer().SendInitialPacketsBeforeAddToMap(); + + if (!GetPlayer().GetMap().AddPlayerToMap(GetPlayer(), !seamlessTeleport)) + { + Log.outError(LogFilter.Network, "WORLD: failed to teleport player {0} ({1}) to map {2} ({3}) because of unknown reason!", + GetPlayer().GetName(), GetPlayer().GetGUID().ToString(), loc.GetMapId(), newMap ? newMap.GetMapName() : "Unknown"); + GetPlayer().ResetMap(); + GetPlayer().SetMap(oldMap); + GetPlayer().TeleportTo(GetPlayer().GetHomebind()); + return; + } + + // Battleground state prepare (in case join to BG), at relogin/tele player not invited + // only add to bg group and object, if the player was invited (else he entered through command) + if (GetPlayer().InBattleground()) + { + Battleground bg; + // cleanup setting if outdated + if (!mapEntry.IsBattlegroundOrArena()) + { + // We're not in BG + GetPlayer().SetBattlegroundId(0, BattlegroundTypeId.None); + // reset destination bg team + GetPlayer().SetBGTeam(0); + } + // join to bg case + else if (bg = GetPlayer().GetBattleground()) + { + if (GetPlayer().IsInvitedForBattlegroundInstance(GetPlayer().GetBattlegroundId())) + bg.AddPlayer(GetPlayer()); + } + } + + if (!seamlessTeleport) + GetPlayer().SendInitialPacketsAfterAddToMap(); + else + { + GetPlayer().UpdateVisibilityForPlayer(); + Garrison garrison = GetPlayer().GetGarrison(); + if (garrison != null) + garrison.SendRemoteInfo(); + } + + // flight fast teleport case + if (GetPlayer().GetMotionMaster().GetCurrentMovementGeneratorType() == MovementGeneratorType.Flight) + { + if (!GetPlayer().InBattleground()) + { + if (!seamlessTeleport) + { + // short preparations to continue flight + FlightPathMovementGenerator flight = (FlightPathMovementGenerator)GetPlayer().GetMotionMaster().top(); + flight.Initialize(GetPlayer()); + } + return; + } + + // Battlegroundstate prepare, stop flight + GetPlayer().GetMotionMaster().MovementExpired(); + GetPlayer().CleanupAfterTaxiFlight(); + } + + // resurrect character at enter into instance where his corpse exist after add to map + if (mapEntry.IsDungeon() && !GetPlayer().IsAlive()) + { + if (GetPlayer().GetCorpseLocation().GetMapId() == mapEntry.Id) + { + GetPlayer().ResurrectPlayer(0.5f, false); + GetPlayer().SpawnCorpseBones(); + } + } + + bool allowMount = !mapEntry.IsDungeon() || mapEntry.IsBattlegroundOrArena(); + if (mInstance != null) + { + // check if this instance has a reset time and send it to player if so + Difficulty diff = GetPlayer().GetDifficultyID(mapEntry); + MapDifficultyRecord mapDiff = Global.DB2Mgr.GetMapDifficultyData(mapEntry.Id, diff); + if (mapDiff != null) + { + if (mapDiff.GetRaidDuration() != 0) + { + long timeReset = Global.InstanceSaveMgr.GetResetTimeFor(mapEntry.Id, diff); + if (timeReset != 0) + { + uint timeleft = (uint)(timeReset - Time.UnixTime); + GetPlayer().SendInstanceResetWarning(mapEntry.Id, diff, timeleft, true); + } + } + } + + // check if instance is valid + if (!GetPlayer().CheckInstanceValidity(false)) + GetPlayer().m_InstanceValid = false; + + // instance mounting is handled in InstanceTemplate + allowMount = mInstance.AllowMount; + } + + // mount allow check + if (!allowMount) + GetPlayer().RemoveAurasByType(AuraType.Mounted); + + // update zone immediately, otherwise leave channel will cause crash in mtmap + uint newzone, newarea; + GetPlayer().GetZoneAndAreaId(out newzone, out newarea); + GetPlayer().UpdateZone(newzone, newarea); + + // honorless target + if (GetPlayer().pvpInfo.IsHostile) + GetPlayer().CastSpell(GetPlayer(), 2479, true); + + // in friendly area + else if (GetPlayer().IsPvP() && !GetPlayer().HasFlag(PlayerFields.Flags, PlayerFlags.InPVP)) + GetPlayer().UpdatePvP(false, false); + + // resummon pet + GetPlayer().ResummonPetTemporaryUnSummonedIfAny(); + + //lets process all delayed operations on successful teleport + GetPlayer().ProcessDelayedOperations(); + } + + [WorldPacketHandler(ClientOpcodes.SuspendTokenResponse, Status = SessionStatus.Transfer)] + void HandleSuspendTokenResponse(SuspendTokenResponse suspendTokenResponse) + { + if (!_player.IsBeingTeleportedFar()) + return; + + WorldLocation loc = GetPlayer().GetTeleportDest(); + + if (CliDB.MapStorage.LookupByKey(loc.GetMapId()).IsDungeon()) + { + UpdateLastInstance updateLastInstance = new UpdateLastInstance(); + updateLastInstance.MapID = loc.GetMapId(); + SendPacket(updateLastInstance); + } + + NewWorld packet = new NewWorld(); + packet.MapID = loc.GetMapId(); + packet.Pos = loc; + packet.Reason = (uint)(!_player.IsBeingTeleportedSeamlessly() ? NewWorldReason.Normal : NewWorldReason.Seamless); + SendPacket(packet); + + if (_player.IsBeingTeleportedSeamlessly()) + HandleMoveWorldportAck(); + } + + [WorldPacketHandler(ClientOpcodes.MoveTeleportAck, Processing = PacketProcessing.ThreadSafe)] + void HandleMoveTeleportAck(MoveTeleportAck packet) + { + Player plMover = GetPlayer().m_unitMovedByMe.ToPlayer(); + + if (!plMover || !plMover.IsBeingTeleportedNear()) + return; + + if (packet.MoverGUID != plMover.GetGUID()) + return; + + plMover.SetSemaphoreTeleportNear(false); + + uint old_zone = plMover.GetZoneId(); + + WorldLocation dest = plMover.GetTeleportDest(); + + plMover.UpdatePosition(dest, true); + + uint newzone, newarea; + plMover.GetZoneAndAreaId(out newzone, out newarea); + plMover.UpdateZone(newzone, newarea); + + // new zone + if (old_zone != newzone) + { + // honorless target + if (plMover.pvpInfo.IsHostile) + plMover.CastSpell(plMover, 2479, true); + + // in friendly area + else if (plMover.IsPvP() && !plMover.HasFlag(PlayerFields.Flags, PlayerFlags.InPVP)) + plMover.UpdatePvP(false, false); + } + + // resummon pet + GetPlayer().ResummonPetTemporaryUnSummonedIfAny(); + + //lets process all delayed operations on successful teleport + GetPlayer().ProcessDelayedOperations(); + } + + [WorldPacketHandler(ClientOpcodes.MoveForceFlightBackSpeedChangeAck, Processing = PacketProcessing.ThreadSafe)] + [WorldPacketHandler(ClientOpcodes.MoveForceFlightSpeedChangeAck, Processing = PacketProcessing.ThreadSafe)] + [WorldPacketHandler(ClientOpcodes.MoveForcePitchRateChangeAck, Processing = PacketProcessing.ThreadSafe)] + [WorldPacketHandler(ClientOpcodes.MoveForceRunBackSpeedChangeAck, Processing = PacketProcessing.ThreadSafe)] + [WorldPacketHandler(ClientOpcodes.MoveForceRunSpeedChangeAck, Processing = PacketProcessing.ThreadSafe)] + [WorldPacketHandler(ClientOpcodes.MoveForceSwimBackSpeedChangeAck, Processing = PacketProcessing.ThreadSafe)] + [WorldPacketHandler(ClientOpcodes.MoveForceSwimSpeedChangeAck, Processing = PacketProcessing.ThreadSafe)] + [WorldPacketHandler(ClientOpcodes.MoveForceTurnRateChangeAck, Processing = PacketProcessing.ThreadSafe)] + [WorldPacketHandler(ClientOpcodes.MoveForceWalkSpeedChangeAck, Processing = PacketProcessing.ThreadSafe)] + void HandleForceSpeedChangeAck(MovementSpeedAck packet) + { + GetPlayer().ValidateMovementInfo(packet.Ack.movementInfo); + + // now can skip not our packet + if (GetPlayer().GetGUID() != packet.Ack.movementInfo.Guid) + return; + + /*----------------*/ + // client ACK send one packet for mounted/run case and need skip all except last from its + // in other cases anti-cheat check can be fail in false case + UnitMoveType move_type; + + ClientOpcodes opcode = packet.GetOpcode(); + switch (opcode) + { + case ClientOpcodes.MoveForceWalkSpeedChangeAck: + move_type = UnitMoveType.Walk; + break; + case ClientOpcodes.MoveForceRunSpeedChangeAck: + move_type = UnitMoveType.Run; + break; + case ClientOpcodes.MoveForceRunBackSpeedChangeAck: + move_type = UnitMoveType.RunBack; + break; + case ClientOpcodes.MoveForceSwimSpeedChangeAck: + move_type = UnitMoveType.Swim; + break; + case ClientOpcodes.MoveForceSwimBackSpeedChangeAck: + move_type = UnitMoveType.SwimBack; + break; + case ClientOpcodes.MoveForceTurnRateChangeAck: + move_type = UnitMoveType.TurnRate; + break; + case ClientOpcodes.MoveForceFlightSpeedChangeAck: + move_type = UnitMoveType.Flight; + break; + case ClientOpcodes.MoveForceFlightBackSpeedChangeAck: + move_type = UnitMoveType.FlightBack; + break; + case ClientOpcodes.MoveForcePitchRateChangeAck: + move_type = UnitMoveType.PitchRate; + break; + default: + Log.outError(LogFilter.Network, "WorldSession.HandleForceSpeedChangeAck: Unknown move type opcode: {0}", opcode); + return; + } + + // skip all forced speed changes except last and unexpected + // in run/mounted case used one ACK and it must be skipped. m_forced_speed_changes[MOVE_RUN] store both. + if (GetPlayer().m_forced_speed_changes[(int)move_type] > 0) + { + --GetPlayer().m_forced_speed_changes[(int)move_type]; + if (GetPlayer().m_forced_speed_changes[(int)move_type] > 0) + return; + } + + if (!GetPlayer().GetTransport() && Math.Abs(GetPlayer().GetSpeed(move_type) - packet.Speed) > 0.01f) + { + if (GetPlayer().GetSpeed(move_type) > packet.Speed) // must be greater - just correct + { + Log.outError(LogFilter.Network, "{0}SpeedChange player {1} is NOT correct (must be {2} instead {3}), force set to correct value", + move_type, GetPlayer().GetName(), GetPlayer().GetSpeed(move_type), packet.Speed); + GetPlayer().SetSpeedRate(move_type, GetPlayer().GetSpeedRate(move_type)); + } + else // must be lesser - cheating + { + Log.outDebug(LogFilter.Server, "Player {0} from account id {1} kicked for incorrect speed (must be {2} instead {3})", + GetPlayer().GetName(), GetPlayer().GetSession().GetAccountId(), GetPlayer().GetSpeed(move_type), packet.Speed); + GetPlayer().GetSession().KickPlayer(); + } + } + } + + [WorldPacketHandler(ClientOpcodes.SetActiveMover)] + void HandleSetActiveMover(SetActiveMover packet) + { + if (GetPlayer().IsInWorld) + { + if (_player.m_unitMovedByMe.GetGUID() != packet.ActiveMover) + Log.outError(LogFilter.Network, "HandleSetActiveMover: incorrect mover guid: mover is {0} and should be {1},", packet.ActiveMover.ToString(), _player.m_unitMovedByMe.GetGUID().ToString()); + } + } + + [WorldPacketHandler(ClientOpcodes.MoveKnockBackAck, Processing = PacketProcessing.ThreadSafe)] + void HandleMoveKnockBackAck(MoveKnockBackAck movementAck) + { + GetPlayer().ValidateMovementInfo(movementAck.Ack.movementInfo); + + if (GetPlayer().m_unitMovedByMe.GetGUID() != movementAck.Ack.movementInfo.Guid) + return; + + GetPlayer().m_movementInfo = movementAck.Ack.movementInfo; + + MoveUpdateKnockBack updateKnockBack = new MoveUpdateKnockBack(); + updateKnockBack.movementInfo = GetPlayer().m_movementInfo; + GetPlayer().SendMessageToSet(updateKnockBack, false); + } + + [WorldPacketHandler(ClientOpcodes.MoveEnableSwimToFlyTransAck, Processing = PacketProcessing.ThreadSafe)] + [WorldPacketHandler(ClientOpcodes.MoveFeatherFallAck, Processing = PacketProcessing.ThreadSafe)] + [WorldPacketHandler(ClientOpcodes.MoveForceRootAck, Processing = PacketProcessing.ThreadSafe)] + [WorldPacketHandler(ClientOpcodes.MoveForceUnrootAck, Processing = PacketProcessing.ThreadSafe)] + [WorldPacketHandler(ClientOpcodes.MoveGravityDisableAck, Processing = PacketProcessing.ThreadSafe)] + [WorldPacketHandler(ClientOpcodes.MoveGravityEnableAck, Processing = PacketProcessing.ThreadSafe)] + [WorldPacketHandler(ClientOpcodes.MoveHoverAck, Processing = PacketProcessing.ThreadSafe)] + [WorldPacketHandler(ClientOpcodes.MoveSetCanFlyAck, Processing = PacketProcessing.ThreadSafe)] + [WorldPacketHandler(ClientOpcodes.MoveSetCanTurnWhileFallingAck, Processing = PacketProcessing.ThreadSafe)] + [WorldPacketHandler(ClientOpcodes.MoveSetIgnoreMovementForcesAck, Processing = PacketProcessing.ThreadSafe)] + [WorldPacketHandler(ClientOpcodes.MoveWaterWalkAck, Processing = PacketProcessing.ThreadSafe)] + [WorldPacketHandler(ClientOpcodes.MoveEnableDoubleJumpAck, Processing = PacketProcessing.ThreadSafe)] + void HandleMovementAckMessage(MovementAckMessage movementAck) + { + GetPlayer().ValidateMovementInfo(movementAck.Ack.movementInfo); + } + + [WorldPacketHandler(ClientOpcodes.SummonResponse)] + void HandleSummonResponseOpcode(SummonResponse packet) + { + if (!GetPlayer().IsAlive() || GetPlayer().IsInCombat()) + return; + + GetPlayer().SummonIfPossible(packet.Accept); + } + + [WorldPacketHandler(ClientOpcodes.MoveSetCollisionHeightAck, Processing = PacketProcessing.ThreadSafe)] + void HandleSetCollisionHeightAck(MoveSetCollisionHeightAck packet) + { + GetPlayer().ValidateMovementInfo(packet.Data.movementInfo); + } + + [WorldPacketHandler(ClientOpcodes.MoveTimeSkipped, Processing = PacketProcessing.Inplace)] + void HandleMoveTimeSkipped(MoveTimeSkipped moveTimeSkipped) + { + } + + [WorldPacketHandler(ClientOpcodes.MoveSplineDone, Processing = PacketProcessing.ThreadSafe)] + void HandleMoveSplineDoneOpcode(MoveSplineDone moveSplineDone) + { + MovementInfo movementInfo = moveSplineDone.movementInfo; + _player.ValidateMovementInfo(movementInfo); + + // in taxi flight packet received in 2 case: + // 1) end taxi path in far (multi-node) flight + // 2) switch from one map to other in case multim-map taxi path + // we need process only (1) + + uint curDest = GetPlayer().m_taxi.GetTaxiDestination(); + if (curDest != 0) + { + TaxiNodesRecord curDestNode = CliDB.TaxiNodesStorage.LookupByKey(curDest); + + // far teleport case + if (curDestNode != null && curDestNode.MapID != GetPlayer().GetMapId()) + { + if (GetPlayer().GetMotionMaster().GetCurrentMovementGeneratorType() == MovementGeneratorType.Flight) + { + // short preparations to continue flight + FlightPathMovementGenerator flight = (FlightPathMovementGenerator)GetPlayer().GetMotionMaster().top(); + + flight.SetCurrentNodeAfterTeleport(); + TaxiPathNodeRecord node = flight.GetPath()[(int)flight.GetCurrentNode()]; + flight.SkipCurrentNode(); + + GetPlayer().TeleportTo(curDestNode.MapID, node.Loc.X, node.Loc.Y, node.Loc.Z, GetPlayer().GetOrientation()); + } + } + + return; + } + + // at this point only 1 node is expected (final destination) + if (GetPlayer().m_taxi.GetPath().Count != 1) + return; + + GetPlayer().CleanupAfterTaxiFlight(); + GetPlayer().SetFallInformation(0, GetPlayer().GetPositionZ()); + if (GetPlayer().pvpInfo.IsHostile) + GetPlayer().CastSpell(GetPlayer(), 2479, true); + } + } +} diff --git a/Game/Handlers/NPCHandler.cs b/Game/Handlers/NPCHandler.cs new file mode 100644 index 000000000..44ac3c00b --- /dev/null +++ b/Game/Handlers/NPCHandler.cs @@ -0,0 +1,675 @@ +/* + * Copyright (C) 2012-2017 CypherCore + * + * 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, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +using Framework.Constants; +using Framework.Database; +using Game.BattleGrounds; +using Game.DataStorage; +using Game.Entities; +using Game.Network; +using Game.Network.Packets; +using System; +using System.Collections.Generic; + +namespace Game +{ + public partial class WorldSession + { + [WorldPacketHandler(ClientOpcodes.TabardVendorActivate)] + void HandleTabardVendorActivate(Hello packet) + { + Creature unit = GetPlayer().GetNPCIfCanInteractWith(packet.Unit, NPCFlags.TabardDesigner); + if (!unit) + { + Log.outDebug(LogFilter.Network, "WORLD: HandleTabardVendorActivateOpcode - {0} not found or you can not interact with him.", packet.Unit.ToString()); + return; + } + + // remove fake death + if (GetPlayer().HasUnitState(UnitState.Died)) + GetPlayer().RemoveAurasByType(AuraType.FeignDeath); + + SendTabardVendorActivate(packet.Unit); + } + + public void SendTabardVendorActivate(ObjectGuid guid) + { + PlayerTabardVendorActivate packet = new PlayerTabardVendorActivate(); + packet.Vendor = guid; + SendPacket(packet); + } + + [WorldPacketHandler(ClientOpcodes.TrainerList)] + void HandleTrainerList(Hello packet) + { + SendTrainerList(packet.Unit); + } + + public void SendTrainerList(ObjectGuid guid, uint index = 0) + { + string str = Global.ObjectMgr.GetCypherString(CypherStrings.NpcTainerHello); + SendTrainerList(guid, str, index); + } + void SendTrainerList(ObjectGuid guid, string title, uint index = 0) + { + Creature unit = GetPlayer().GetNPCIfCanInteractWith(guid, NPCFlags.Trainer); + if (unit == null) + { + Log.outDebug(LogFilter.Network, "WORLD: SendTrainerList - {0} not found or you can not interact with him.", guid.ToString()); + return; + } + + // remove fake death + if (GetPlayer().HasUnitState(UnitState.Died)) + GetPlayer().RemoveAurasByType(AuraType.FeignDeath); + + TrainerSpellData trainer_spells = unit.GetTrainerSpells(); + if (trainer_spells == null) + { + Log.outDebug(LogFilter.Network, "WORLD: SendTrainerList - Training spells not found for {0}", guid.ToString()); + return; + } + + TrainerList packet = new TrainerList(); + packet.TrainerGUID = guid; + packet.TrainerType = (int)trainer_spells.trainerType; + packet.Greeting = title; + + // reputation discount + float fDiscountMod = GetPlayer().GetReputationPriceDiscount(unit); + + foreach (var tSpell in trainer_spells.spellList.Values) + { + if (index != 0 && tSpell.Index != index) + continue; + + bool valid = true; + for (var i = 0; i < SharedConst.MaxTrainerspellAbilityReqs; i++) + { + if (tSpell.ReqAbility[i] == 0) + continue; + + if (!GetPlayer().IsSpellFitByClassAndRace(tSpell.ReqAbility[i])) + { + valid = false; + break; + } + } + + if (!valid) + continue; + + TrainerSpellState state = GetPlayer().GetTrainerSpellState(tSpell); + + TrainerListSpell spell = new TrainerListSpell(); + spell.SpellID = (int)tSpell.SpellID; + spell.MoneyCost = (int)Math.Floor(tSpell.MoneyCost * fDiscountMod); + spell.ReqSkillLine = (int)tSpell.ReqSkillLine; + spell.ReqSkillRank = (int)tSpell.ReqSkillRank; + spell.ReqLevel = (byte)tSpell.ReqLevel; + spell.Usable = (state == TrainerSpellState.GreenDisabled ? TrainerSpellState.Green : state); + + byte maxReq = 0; + for (var i = 0; i < SharedConst.MaxTrainerspellAbilityReqs; ++i) + { + if (tSpell.ReqAbility[i] == 0) + continue; + + uint prevSpellId = Global.SpellMgr.GetPrevSpellInChain(tSpell.ReqAbility[i]); + if (prevSpellId != 0) + { + spell.ReqAbility[maxReq] = (int)prevSpellId; + ++maxReq; + } + + if (maxReq == 2) + break; + + var spellsRequired = Global.SpellMgr.GetSpellsRequiredForSpellBounds(tSpell.ReqAbility[i]); + for (var c = 0; c < spellsRequired.Count && maxReq < SharedConst.MaxTrainerspellAbilityReqs; ++c) + { + spell.ReqAbility[maxReq] = (int)spellsRequired[c]; + ++maxReq; + } + + if (maxReq == 2) + break; + } + + packet.Spells.Add(spell); + } + + SendPacket(packet); + } + + [WorldPacketHandler(ClientOpcodes.TrainerBuySpell)] + void HandleTrainerBuySpell(TrainerBuySpell packet) + { + Creature trainer = _player.GetNPCIfCanInteractWith(packet.TrainerGUID, NPCFlags.Trainer); + if (trainer == null) + { + Log.outDebug(LogFilter.Network, "WORLD: HandleTrainerBuySpell - {0} not found or you can not interact with him.", packet.TrainerGUID.ToString()); + return; + } + + // remove fake death + if (_player.HasUnitState(UnitState.Died)) + _player.RemoveAurasByType(AuraType.FeignDeath); + + // check race for mount trainers + if (trainer.GetCreatureTemplate().TrainerType == TrainerType.Mounts) + { + Race trainerRace = trainer.GetCreatureTemplate().TrainerRace; + if (trainerRace != 0) + if (_player.GetRace() != trainerRace) + return; + } + + // check class for class trainers + if (_player.GetClass() != trainer.GetCreatureTemplate().TrainerClass && trainer.GetCreatureTemplate().TrainerType == TrainerType.Class) + return; + + // check present spell in trainer spell list + var trainerSpells = trainer.GetTrainerSpells(); + if (trainerSpells == null) + { + SendTrainerBuyFailed(packet.TrainerGUID, packet.SpellID, 0); + return; + } + + var trainerSpell = trainerSpells.spellList.LookupByKey(packet.SpellID); + if (trainerSpell == null) + { + SendTrainerBuyFailed(packet.TrainerGUID, packet.SpellID, 0); + return; + } + + // can't be learn, cheat? Or double learn with lags... + if (_player.GetTrainerSpellState(trainerSpell) != TrainerSpellState.Green) + { + SendTrainerBuyFailed(packet.TrainerGUID, packet.SpellID, 0); + return; + } + + // apply reputation discount + uint nSpellCost = (uint)(Math.Floor((double)trainerSpell.MoneyCost) * _player.GetReputationPriceDiscount(trainer)); + + // check money requirement + if (!_player.HasEnoughMoney(nSpellCost)) + { + SendTrainerBuyFailed(packet.TrainerGUID, packet.SpellID, 1); + return; + } + + _player.ModifyMoney(-nSpellCost); + + trainer.SendPlaySpellVisualKit(179, 0, 0); // 53 SpellCastDirected + _player.SendPlaySpellVisualKit(362, 1, 0); // 113 EmoteSalute + + // learn explicitly or cast explicitly + if (trainerSpell.IsCastable()) + _player.CastSpell(GetPlayer(), trainerSpell.SpellID, true); + else + _player.LearnSpell(packet.SpellID, false); + } + + void SendTrainerBuyFailed(ObjectGuid trainerGUID, uint spellID, uint trainerFailedReason) + { + TrainerBuyFailed trainerBuyFailed = new TrainerBuyFailed(); + trainerBuyFailed.TrainerGUID = trainerGUID; + trainerBuyFailed.SpellID = spellID; // should be same as in packet from client + trainerBuyFailed.TrainerFailedReason = trainerFailedReason; // 1 == "Not enough money for trainer service." 0 == "Trainer service %d unavailable." + SendPacket(trainerBuyFailed); + } + + [WorldPacketHandler(ClientOpcodes.TalkToGossip)] + void HandleGossipHello(Hello packet) + { + Creature unit = GetPlayer().GetNPCIfCanInteractWith(packet.Unit, NPCFlags.Gossip); + if (unit == null) + { + Log.outDebug(LogFilter.Network, "WORLD: HandleGossipHello - {0} not found or you can not interact with him.", packet.Unit.ToString()); + return; + } + + // set faction visible if needed + var factionTemplateEntry = CliDB.FactionTemplateStorage.LookupByKey(unit.getFaction()); + if (factionTemplateEntry != null) + GetPlayer().GetReputationMgr().SetVisible(factionTemplateEntry); + + GetPlayer().RemoveAurasWithInterruptFlags(SpellAuraInterruptFlags.Talk); + + if (unit.IsArmorer() || unit.IsCivilian() || unit.IsQuestGiver() || unit.IsServiceProvider() || unit.IsGuard()) + unit.StopMoving(); + + // If spiritguide, no need for gossip menu, just put player into resurrect queue + if (unit.IsSpiritGuide()) + { + Battleground bg = GetPlayer().GetBattleground(); + if (bg) + { + bg.AddPlayerToResurrectQueue(unit.GetGUID(), GetPlayer().GetGUID()); + Global.BattlegroundMgr.SendAreaSpiritHealerQuery(GetPlayer(), bg, unit.GetGUID()); + return; + } + } + + if (!Global.ScriptMgr.OnGossipHello(GetPlayer(), unit)) + { + GetPlayer().PrepareGossipMenu(unit, unit.GetCreatureTemplate().GossipMenuId, true); + GetPlayer().SendPreparedGossip(unit); + } + unit.GetAI().sGossipHello(GetPlayer()); + } + + [WorldPacketHandler(ClientOpcodes.GossipSelectOption)] + void HandleGossipSelectOption(GossipSelectOption packet) + { + if (GetPlayer().PlayerTalkClass.GetGossipMenu().GetItem(packet.GossipIndex) == null) + return; + + // Prevent cheating on C# scripted menus + if (GetPlayer().PlayerTalkClass.GetGossipMenu().GetSenderGUID() != packet.GossipUnit) + return; + + Creature unit = null; + GameObject go = null; + if (packet.GossipUnit.IsCreatureOrVehicle()) + { + unit = GetPlayer().GetNPCIfCanInteractWith(packet.GossipUnit, NPCFlags.Gossip); + if (unit == null) + { + Log.outDebug(LogFilter.Network, "WORLD: HandleGossipSelectOption - {0} not found or you can't interact with him.", packet.GossipUnit.ToString()); + return; + } + } + else if (packet.GossipUnit.IsGameObject()) + { + go = GetPlayer().GetGameObjectIfCanInteractWith(packet.GossipUnit); + if (go == null) + { + Log.outDebug(LogFilter.Network, "WORLD: HandleGossipSelectOption - {0} not found or you can't interact with it.", packet.GossipUnit.ToString()); + return; + } + } + else + { + Log.outDebug(LogFilter.Network, "WORLD: HandleGossipSelectOption - unsupported {0}.", packet.GossipUnit.ToString()); + return; + } + + // remove fake death + if (GetPlayer().HasUnitState(UnitState.Died)) + GetPlayer().RemoveAurasByType(AuraType.FeignDeath); + + if ((unit && unit.GetScriptId() != unit.LastUsedScriptID) || (go != null && go.GetScriptId() != go.LastUsedScriptID)) + { + Log.outDebug(LogFilter.Network, "WORLD: HandleGossipSelectOption - Script reloaded while in use, ignoring and set new scipt id"); + if (unit != null) + unit.LastUsedScriptID = unit.GetScriptId(); + + if (go != null) + go.LastUsedScriptID = go.GetScriptId(); + GetPlayer().PlayerTalkClass.SendCloseGossip(); + return; + } + if (!string.IsNullOrEmpty(packet.PromotionCode)) + { + if (unit) + { + unit.GetAI().sGossipSelectCode(GetPlayer(), packet.GossipID, packet.GossipIndex, packet.PromotionCode); + if (!Global.ScriptMgr.OnGossipSelectCode(GetPlayer(), unit, GetPlayer().PlayerTalkClass.GetGossipOptionSender(packet.GossipIndex), GetPlayer().PlayerTalkClass.GetGossipOptionAction(packet.GossipIndex), packet.PromotionCode)) + GetPlayer().OnGossipSelect(unit, packet.GossipIndex, packet.GossipID); + } + else + { + go.GetAI().GossipSelectCode(GetPlayer(), packet.GossipID, packet.GossipIndex, packet.PromotionCode); + Global.ScriptMgr.OnGossipSelectCode(GetPlayer(), go, GetPlayer().PlayerTalkClass.GetGossipOptionSender(packet.GossipIndex), GetPlayer().PlayerTalkClass.GetGossipOptionAction(packet.GossipIndex), packet.PromotionCode); + } + } + else + { + if (unit != null) + { + unit.GetAI().sGossipSelect(GetPlayer(), packet.GossipID, packet.GossipIndex); + if (!Global.ScriptMgr.OnGossipSelect(GetPlayer(), unit, GetPlayer().PlayerTalkClass.GetGossipOptionSender(packet.GossipIndex), GetPlayer().PlayerTalkClass.GetGossipOptionAction(packet.GossipIndex))) + GetPlayer().OnGossipSelect(unit, packet.GossipIndex, packet.GossipID); + } + else + { + go.GetAI().GossipSelect(GetPlayer(), packet.GossipID, packet.GossipIndex); + if (!Global.ScriptMgr.OnGossipSelect(GetPlayer(), go, GetPlayer().PlayerTalkClass.GetGossipOptionSender(packet.GossipIndex), GetPlayer().PlayerTalkClass.GetGossipOptionAction(packet.GossipIndex))) + GetPlayer().OnGossipSelect(go, packet.GossipIndex, packet.GossipID); + } + } + } + + [WorldPacketHandler(ClientOpcodes.SpiritHealerActivate)] + void HandleSpiritHealerActivate(SpiritHealerActivate packet) + { + Creature unit = GetPlayer().GetNPCIfCanInteractWith(packet.Healer, NPCFlags.SpiritHealer); + if (!unit) + { + Log.outDebug(LogFilter.Network, "WORLD: HandleSpiritHealerActivateOpcode - {0} not found or you can not interact with him.", packet.Healer.ToString()); + return; + } + + // remove fake death + if (GetPlayer().HasUnitState(UnitState.Died)) + GetPlayer().RemoveAurasByType(AuraType.FeignDeath); + + SendSpiritResurrect(); + } + + void SendSpiritResurrect() + { + GetPlayer().ResurrectPlayer(0.5f, true); + + GetPlayer().DurabilityLossAll(0.25f, true); + + // get corpse nearest graveyard + WorldSafeLocsRecord corpseGrave = null; + WorldLocation corpseLocation = GetPlayer().GetCorpseLocation(); + if (GetPlayer().HasCorpse()) + { + corpseGrave = Global.ObjectMgr.GetClosestGraveYard(corpseLocation.GetPositionX(), corpseLocation.GetPositionY(), + corpseLocation.GetPositionZ(), corpseLocation.GetMapId(), GetPlayer().GetTeam()); + } + + // now can spawn bones + GetPlayer().SpawnCorpseBones(); + + // teleport to nearest from corpse graveyard, if different from nearest to player ghost + if (corpseGrave != null) + { + WorldSafeLocsRecord ghostGrave = Global.ObjectMgr.GetClosestGraveYard( + GetPlayer().GetPositionX(), GetPlayer().GetPositionY(), GetPlayer().GetPositionZ(), GetPlayer().GetMapId(), GetPlayer().GetTeam()); + + if (corpseGrave != ghostGrave) + GetPlayer().TeleportTo(corpseGrave.MapID, corpseGrave.Loc.X, corpseGrave.Loc.Y, corpseGrave.Loc.Z, GetPlayer().GetOrientation()); + // or update at original position + else + GetPlayer().UpdateObjectVisibility(); + } + // or update at original position + else + GetPlayer().UpdateObjectVisibility(); + } + + [WorldPacketHandler(ClientOpcodes.BinderActivate)] + void HandleBinderActivate(Hello packet) + { + if (!GetPlayer().IsInWorld || !GetPlayer().IsAlive()) + return; + + Creature unit = GetPlayer().GetNPCIfCanInteractWith(packet.Unit, NPCFlags.Innkeeper); + if (!unit) + { + Log.outDebug(LogFilter.Network, "WORLD: HandleBinderActivate - {0} not found or you can not interact with him.", packet.Unit.ToString()); + return; + } + + // remove fake death + if (GetPlayer().HasUnitState(UnitState.Died)) + GetPlayer().RemoveAurasByType(AuraType.FeignDeath); + + SendBindPoint(unit); + } + + void SendBindPoint(Creature npc) + { + // prevent set homebind to instances in any case + if (GetPlayer().GetMap().Instanceable()) + return; + + uint bindspell = 3286; + + // send spell for homebinding (3286) + npc.CastSpell(GetPlayer(), bindspell, true); + + GetPlayer().PlayerTalkClass.SendCloseGossip(); + } + + [WorldPacketHandler(ClientOpcodes.RequestStabledPets)] + void HandleRequestStabledPets(RequestStabledPets packet) + { + if (!CheckStableMaster(packet.StableMaster)) + return; + + // remove fake death + if (GetPlayer().HasUnitState(UnitState.Died)) + GetPlayer().RemoveAurasByType(AuraType.FeignDeath); + + // remove mounts this fix bug where getting pet from stable while mounted deletes pet. + if (GetPlayer().IsMounted()) + GetPlayer().RemoveAurasByType(AuraType.Mounted); + + SendStablePet(packet.StableMaster); + } + + public void SendStablePet(ObjectGuid guid) + { + PreparedStatement stmt = DB.Characters.GetPreparedStatement(CharStatements.SEL_PET_SLOTS_DETAIL); + stmt.AddValue(0, guid.GetCounter()); + stmt.AddValue(1, PetSaveMode.FirstStableSlot); + stmt.AddValue(2, PetSaveMode.LastStableSlot); + + _queryProcessor.AddQuery(DB.Characters.AsyncQuery(stmt).WithCallback(SendStablePetCallback, guid)); + } + + void SendStablePetCallback(ObjectGuid guid, SQLResult result) + { + if (!GetPlayer()) + return; + + PetStableList packet = new PetStableList(); + packet.StableMaster = guid; + + Pet pet = GetPlayer().GetPet(); + + uint petSlot = 0; + // not let move dead pet in slot + if (pet && pet.IsAlive() && pet.getPetType() == PetType.Hunter) + { + PetStableInfo stableEntry;// = new PetStableInfo(); + stableEntry.PetSlot = petSlot; + stableEntry.PetNumber = pet.GetCharmInfo().GetPetNumber(); + stableEntry.CreatureID = pet.GetEntry(); + stableEntry.DisplayID = pet.GetDisplayId(); + stableEntry.ExperienceLevel = pet.getLevel(); + stableEntry.PetFlags = PetStableinfo.Active; + stableEntry.PetName = pet.GetName(); + ++petSlot; + + packet.Pets.Add(stableEntry); + } + + if (!result.IsEmpty()) + { + do + { + PetStableInfo stableEntry;// = new PetStableInfo(); + + stableEntry.PetSlot = petSlot; + stableEntry.PetNumber = result.Read(1); // petnumber + stableEntry.CreatureID = result.Read(2); // creature entry + stableEntry.DisplayID = result.Read(5); // creature displayid + stableEntry.ExperienceLevel = result.Read(3); // level + stableEntry.PetFlags = PetStableinfo.Inactive; + stableEntry.PetName = result.Read(4); // Name + + ++petSlot; + packet.Pets.Add(stableEntry); + } + while (result.NextRow()); + } + + SendPacket(packet); + } + + void SendPetStableResult(byte res) + { + SendPacket(new PetStableResult(res)); + } + + [WorldPacketHandler(ClientOpcodes.RepairItem)] + void HandleRepairItem(RepairItem packet) + { + Creature unit = GetPlayer().GetNPCIfCanInteractWith(packet.NpcGUID, NPCFlags.Repair); + if (!unit) + { + Log.outDebug(LogFilter.Network, "WORLD: HandleRepairItemOpcode - {0} not found or you can not interact with him.", packet.NpcGUID.ToString()); + return; + } + + // remove fake death + if (GetPlayer().HasUnitState(UnitState.Died)) + GetPlayer().RemoveAurasByType(AuraType.FeignDeath); + + // reputation discount + float discountMod = GetPlayer().GetReputationPriceDiscount(unit); + + if (!packet.ItemGUID.IsEmpty()) + { + Log.outDebug(LogFilter.Network, "ITEM: Repair {0}, at {1}", packet.ItemGUID.ToString(), packet.NpcGUID.ToString()); + + Item item = GetPlayer().GetItemByGuid(packet.ItemGUID); + if (item) + GetPlayer().DurabilityRepair(item.GetPos(), true, discountMod, packet.UseGuildBank); + } + else + { + Log.outDebug(LogFilter.Network, "ITEM: Repair all items at {0}", packet.NpcGUID.ToString()); + GetPlayer().DurabilityRepairAll(true, discountMod, packet.UseGuildBank); + } + } + + [WorldPacketHandler(ClientOpcodes.ListInventory)] + void HandleListInventory(Hello packet) + { + if (!GetPlayer().IsAlive()) + return; + + SendListInventory(packet.Unit); + } + + public void SendListInventory(ObjectGuid vendorGuid) + { + Creature vendor = GetPlayer().GetNPCIfCanInteractWith(vendorGuid, NPCFlags.Vendor); + if (vendor == null) + { + Log.outDebug(LogFilter.Network, "WORLD: SendListInventory - {0} not found or you can not interact with him.", vendorGuid.ToString()); + GetPlayer().SendSellError(SellResult.CantFindVendor, null, ObjectGuid.Empty); + return; + } + + // remove fake death + if (GetPlayer().HasUnitState(UnitState.Died)) + GetPlayer().RemoveAurasByType(AuraType.FeignDeath); + + // Stop the npc if moving + if (vendor.HasUnitState(UnitState.Moving)) + vendor.StopMoving(); + + VendorItemData vendorItems = vendor.GetVendorItems(); + int rawItemCount = vendorItems != null ? vendorItems.GetItemCount() : 0; + + VendorInventory packet = new VendorInventory(); + packet.Vendor = vendor.GetGUID(); + + float discountMod = GetPlayer().GetReputationPriceDiscount(vendor); + byte count = 0; + for (uint slot = 0; slot < rawItemCount; ++slot) + { + VendorItem vendorItem = vendorItems.GetItem(slot); + if (vendorItem == null) + continue; + + VendorItemPkt item = new VendorItemPkt(); + + if (vendorItem.Type == ItemVendorType.Item) + { + ItemTemplate itemTemplate = Global.ObjectMgr.GetItemTemplate(vendorItem.item); + if (itemTemplate == null) + continue; + + int leftInStock = vendorItem.maxcount == 0 ? -1 : (int)vendor.GetVendorItemCurrentCount(vendorItem); + if (!GetPlayer().IsGameMaster()) + { + if (!Convert.ToBoolean(itemTemplate.GetAllowableClass() & GetPlayer().getClassMask()) && itemTemplate.GetBonding() == ItemBondingType.OnAcquire) + continue; + + if ((itemTemplate.GetFlags2().HasAnyFlag(ItemFlags2.FactionHorde) && GetPlayer().GetTeam() == Team.Alliance) || + (itemTemplate.GetFlags2().HasAnyFlag(ItemFlags2.FactionAlliance) && GetPlayer().GetTeam() == Team.Horde)) + continue; + + if (leftInStock == 0) + continue; + } + + if (!Global.ConditionMgr.IsObjectMeetingVendorItemConditions(vendor.GetEntry(), vendorItem.item, _player, vendor)) + { + Log.outDebug(LogFilter.Condition, "SendListInventory: conditions not met for creature entry {0} item {1}", vendor.GetEntry(), vendorItem.item); + continue; + } + + int price = (int)(vendorItem.IsGoldRequired(itemTemplate) ? Math.Floor(itemTemplate.GetBuyPrice() * discountMod) : 0); + + int priceMod = GetPlayer().GetTotalAuraModifier(AuraType.ModVendorItemsPrices); + if (priceMod != 0) + price -= MathFunctions.CalculatePct(price, priceMod); + + item.MuID = (int)slot + 1; + item.Durability = (int)itemTemplate.MaxDurability; + item.ExtendedCostID = (int)vendorItem.ExtendedCost; + item.Type = (int)vendorItem.Type; + item.Quantity = leftInStock; + item.StackCount = (int)itemTemplate.GetBuyCount(); + item.Price = (ulong)price; + + item.Item.ItemID = vendorItem.item; + + packet.Items.Add(item); + } + else if (vendorItem.Type == ItemVendorType.Currency) + { + CurrencyTypesRecord currencyTemplate = CliDB.CurrencyTypesStorage.LookupByKey(vendorItem.item); + if (currencyTemplate == null) + continue; + + if (vendorItem.ExtendedCost == 0) + continue; // there's no price defined for currencies, only extendedcost is used + + item.MuID = (int)slot + 1; // client expects counting to start at 1 + item.ExtendedCostID = (int)vendorItem.ExtendedCost; + item.Item.ItemID = vendorItem.item; + item.Type = (int)vendorItem.Type; + item.StackCount = (int)vendorItem.maxcount; + + packet.Items.Add(item); + } + else + continue; + + if (++count >= SharedConst.MaxVendorItems) + break; + } + + SendPacket(packet); + } + } +} diff --git a/Game/Handlers/PetHandler.cs b/Game/Handlers/PetHandler.cs new file mode 100644 index 000000000..ccc69663c --- /dev/null +++ b/Game/Handlers/PetHandler.cs @@ -0,0 +1,726 @@ +/* + * Copyright (C) 2012-2017 CypherCore + * + * 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, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +using Framework.Constants; +using Framework.Database; +using Game.Entities; +using Game.Network; +using Game.Network.Packets; +using Game.Spells; +using System.Collections.Generic; +using System.Diagnostics.Contracts; + +namespace Game +{ + public partial class WorldSession + { + [WorldPacketHandler(ClientOpcodes.DismissCritter)] + void HandleDismissCritter(DismissCritter packet) + { + Unit pet = ObjectAccessor.GetCreatureOrPetOrVehicle(GetPlayer(), packet.CritterGUID); + if (!pet) + { + Log.outDebug(LogFilter.Network, "Vanitypet {0} does not exist - player '{1}' ({2} / account: {3}) attempted to dismiss it (possibly lagged out)", + packet.CritterGUID.ToString(), GetPlayer().GetName(), GetPlayer().GetGUID().ToString(), GetAccountId()); + return; + } + + if (GetPlayer().GetCritterGUID() == pet.GetGUID()) + { + if (pet.IsTypeId(TypeId.Unit) && pet.ToCreature().IsSummon()) + pet.ToTempSummon().UnSummon(); + } + } + + [WorldPacketHandler(ClientOpcodes.RequestPetInfo)] + void HandleRequestPetInfo(RequestPetInfo packet) + { + } + + [WorldPacketHandler(ClientOpcodes.PetAction)] + void HandlePetAction(PetAction packet) + { + ObjectGuid guid1 = packet.PetGUID; //pet guid + ObjectGuid guid2 = packet.TargetGUID; //tag guid + + uint spellid = UnitActionBarEntry.UNIT_ACTION_BUTTON_ACTION(packet.Action); + ActiveStates flag = (ActiveStates)UnitActionBarEntry.UNIT_ACTION_BUTTON_TYPE(packet.Action); //delete = 0x07 CastSpell = C1 + + // used also for charmed creature + Unit pet = Global.ObjAccessor.GetUnit(GetPlayer(), guid1); + if (!pet) + { + Log.outError(LogFilter.Network, "HandlePetAction: {0} doesn't exist for {1}", guid1.ToString(), GetPlayer().GetGUID().ToString()); + return; + } + + if (pet != GetPlayer().GetFirstControlled()) + { + Log.outError(LogFilter.Network, "HandlePetAction: {0} does not belong to {1}", guid1.ToString(), GetPlayer().GetGUID().ToString()); + return; + } + + if (!pet.IsAlive()) + { + SpellInfo spell = (flag == ActiveStates.Enabled || flag == ActiveStates.Passive) ? Global.SpellMgr.GetSpellInfo(spellid) : null; + if (spell == null) + return; + if (!spell.HasAttribute(SpellAttr0.CastableWhileDead)) + return; + } + + /// @todo allow control charmed player? + if (pet.IsTypeId(TypeId.Player) && !(flag == ActiveStates.Command && spellid == (uint)CommandStates.Attack)) + return; + + if (GetPlayer().m_Controlled.Count == 1) + HandlePetActionHelper(pet, guid1, spellid, flag, guid2, packet.ActionPosition.X, packet.ActionPosition.Y, packet.ActionPosition.Z); + else + { + //If a pet is dismissed, m_Controlled will change + List controlled = new List(); + foreach (var unit in GetPlayer().m_Controlled) + if (unit.GetEntry() == pet.GetEntry() && unit.IsAlive()) + controlled.Add(unit); + + foreach (var unit in controlled) + HandlePetActionHelper(unit, guid1, spellid, flag, guid2, packet.ActionPosition.X, packet.ActionPosition.Y, packet.ActionPosition.Z); + } + } + + [WorldPacketHandler(ClientOpcodes.PetStopAttack)] + void HandlePetStopAttack(PetStopAttack packet) + { + Unit pet = ObjectAccessor.GetCreatureOrPetOrVehicle(GetPlayer(), packet.PetGUID); + if (!pet) + { + Log.outError(LogFilter.Network, "HandlePetStopAttack: {0} does not exist", packet.PetGUID.ToString()); + return; + } + + if (pet != GetPlayer().GetPet() && pet != GetPlayer().GetCharm()) + { + Log.outError(LogFilter.Network, "HandlePetStopAttack: {0} isn't a pet or charmed creature of player {1}", packet.PetGUID.ToString(), GetPlayer().GetName()); + return; + } + + if (!pet.IsAlive()) + return; + + pet.AttackStop(); + } + + void HandlePetActionHelper(Unit pet, ObjectGuid guid1, uint spellid, ActiveStates flag, ObjectGuid guid2, float x, float y, float z) + { + CharmInfo charmInfo = pet.GetCharmInfo(); + if (charmInfo == null) + { + Log.outError(LogFilter.Network, "WorldSession.HandlePetAction(petGuid: {0}, tagGuid: {1}, spellId: {2}, flag: {3}): object (GUID: {4} Entry: {5} TypeId: {6}) is considered pet-like but doesn't have a charminfo!", + guid1, guid2, spellid, flag, pet.GetGUID().ToString(), pet.GetEntry(), pet.GetTypeId()); + return; + } + + switch (flag) + { + case ActiveStates.Command: //0x07 + switch ((CommandStates)spellid) + { + case CommandStates.Stay: //flat=1792 //STAY + pet.StopMoving(); + pet.GetMotionMaster().Clear(false); + pet.GetMotionMaster().MoveIdle(); + charmInfo.SetCommandState(CommandStates.Stay); + + charmInfo.SetIsCommandAttack(false); + charmInfo.SetIsAtStay(true); + charmInfo.SetIsCommandFollow(false); + charmInfo.SetIsFollowing(false); + charmInfo.SetIsReturning(false); + charmInfo.SaveStayPosition(); + break; + case CommandStates.Follow: //spellid=1792 //FOLLOW + pet.AttackStop(); + pet.InterruptNonMeleeSpells(false); + pet.GetMotionMaster().MoveFollow(GetPlayer(), SharedConst.PetFollowDist, pet.GetFollowAngle()); + charmInfo.SetCommandState(CommandStates.Follow); + + charmInfo.SetIsCommandAttack(false); + charmInfo.SetIsAtStay(false); + charmInfo.SetIsReturning(true); + charmInfo.SetIsCommandFollow(true); + charmInfo.SetIsFollowing(false); + break; + case CommandStates.Attack: //spellid=1792 //ATTACK + { + // Can't attack if owner is pacified + if (GetPlayer().HasAuraType(AuraType.ModPacify)) + { + /// @todo Send proper error message to client + return; + } + + // only place where pet can be player + Unit TargetUnit = Global.ObjAccessor.GetUnit(GetPlayer(), guid2); + if (!TargetUnit) + return; + + Unit owner = pet.GetOwner(); + if (owner) + if (!owner.IsValidAttackTarget(TargetUnit)) + return; + + pet.ClearUnitState(UnitState.Follow); + // This is true if pet has no target or has target but targets differs. + if (pet.GetVictim() != TargetUnit || (pet.GetVictim() == TargetUnit && !pet.GetCharmInfo().IsCommandAttack())) + { + if (pet.GetVictim()) + pet.AttackStop(); + + if (!pet.IsTypeId(TypeId.Player) && pet.ToCreature().IsAIEnabled) + { + charmInfo.SetIsCommandAttack(true); + charmInfo.SetIsAtStay(false); + charmInfo.SetIsFollowing(false); + charmInfo.SetIsCommandFollow(false); + charmInfo.SetIsReturning(false); + + pet.ToCreature().GetAI().AttackStart(TargetUnit); + + //10% chance to play special pet attack talk, else growl + if (pet.IsPet() && pet.ToPet().getPetType() == PetType.Summon && pet != TargetUnit && RandomHelper.IRand(0, 100) < 10) + pet.SendPetTalk(PetTalk.Attack); + else + { + // 90% chance for pet and 100% chance for charmed creature + pet.SendPetAIReaction(guid1); + } + } + else // charmed player + { + if (pet.GetVictim() && pet.GetVictim() != TargetUnit) + pet.AttackStop(); + + charmInfo.SetIsCommandAttack(true); + charmInfo.SetIsAtStay(false); + charmInfo.SetIsFollowing(false); + charmInfo.SetIsCommandFollow(false); + charmInfo.SetIsReturning(false); + + pet.Attack(TargetUnit, true); + pet.SendPetAIReaction(guid1); + } + } + break; + } + case CommandStates.Abandon: // abandon (hunter pet) or dismiss (summoned pet) + if (pet.GetCharmerGUID() == GetPlayer().GetGUID()) + GetPlayer().StopCastingCharm(); + else if (pet.GetOwnerGUID() == GetPlayer().GetGUID()) + { + Contract.Assert(pet.IsTypeId(TypeId.Unit)); + if (pet.IsPet()) + { + if (pet.ToPet().getPetType() == PetType.Hunter) + GetPlayer().RemovePet(pet.ToPet(), PetSaveMode.AsDeleted); + else + //dismissing a summoned pet is like killing them (this prevents returning a soulshard...) + pet.setDeathState(DeathState.Corpse); + } + else if (pet.HasUnitTypeMask(UnitTypeMask.Minion)) + { + ((Minion)pet).UnSummon(); + } + } + break; + case CommandStates.MoveTo: + pet.StopMoving(); + pet.GetMotionMaster().Clear(false); + pet.GetMotionMaster().MovePoint(0, x, y, z); + charmInfo.SetCommandState(CommandStates.MoveTo); + + charmInfo.SetIsCommandAttack(false); + charmInfo.SetIsAtStay(true); + charmInfo.SetIsFollowing(false); + charmInfo.SetIsReturning(false); + charmInfo.SaveStayPosition(); + break; + default: + Log.outError(LogFilter.Network, "WORLD: unknown PET flag Action {0} and spellid {1}.", flag, spellid); + break; + } + break; + case ActiveStates.Reaction: // 0x6 + switch ((ReactStates)spellid) + { + case ReactStates.Passive: //passive + pet.AttackStop(); + goto case ReactStates.Defensive; + case ReactStates.Defensive: //recovery + case ReactStates.Aggressive: //activete + if (pet.IsTypeId(TypeId.Unit)) + pet.ToCreature().SetReactState((ReactStates)spellid); + break; + } + break; + case ActiveStates.Disabled: // 0x81 spell (disabled), ignore + case ActiveStates.Passive: // 0x01 + case ActiveStates.Enabled: // 0xC1 spell + { + Unit unit_target = null; + + if (!guid2.IsEmpty()) + unit_target = Global.ObjAccessor.GetUnit(GetPlayer(), guid2); + + // do not cast unknown spells + SpellInfo spellInfo = Global.SpellMgr.GetSpellInfo(spellid); + if (spellInfo == null) + { + Log.outError(LogFilter.Network, "WORLD: unknown PET spell id {0}", spellid); + return; + } + + foreach (SpellEffectInfo effect in spellInfo.GetEffectsForDifficulty(Difficulty.None)) + { + if (effect != null && (effect.TargetA.GetTarget() == Targets.UnitSrcAreaEnemy || effect.TargetA.GetTarget() == Targets.UnitDestAreaEnemy || effect.TargetA.GetTarget() == Targets.DestDynobjEnemy)) + return; + } + + // do not cast not learned spells + if (!pet.HasSpell(spellid) || spellInfo.IsPassive()) + return; + + // Clear the flags as if owner clicked 'attack'. AI will reset them + // after AttackStart, even if spell failed + if (pet.GetCharmInfo() != null) + { + pet.GetCharmInfo().SetIsAtStay(false); + pet.GetCharmInfo().SetIsCommandAttack(true); + pet.GetCharmInfo().SetIsReturning(false); + pet.GetCharmInfo().SetIsFollowing(false); + } + + Spell spell = new Spell(pet, spellInfo, TriggerCastFlags.None); + + SpellCastResult result = spell.CheckPetCast(unit_target); + + //auto turn to target unless possessed + if (result == SpellCastResult.UnitNotInfront && !pet.isPossessed() && !pet.IsVehicle()) + { + Unit unit_target2 = spell.m_targets.GetUnitTarget(); + if (unit_target) + { + pet.SetInFront(unit_target); + Player player = unit_target.ToPlayer(); + if (player) + pet.SendUpdateToPlayer(player); + } + else if (unit_target2) + { + pet.SetInFront(unit_target2); + Player player = unit_target2.ToPlayer(); + if (player) + pet.SendUpdateToPlayer(player); + } + Unit powner = pet.GetCharmerOrOwner(); + if (powner) + { + Player player = powner.ToPlayer(); + if (player) + pet.SendUpdateToPlayer(player); + } + + result = SpellCastResult.SpellCastOk; + } + + if (result == SpellCastResult.SpellCastOk) + { + unit_target = spell.m_targets.GetUnitTarget(); + + //10% chance to play special pet attack talk, else growl + //actually this only seems to happen on special spells, fire shield for imp, torment for voidwalker, but it's stupid to check every spell + if (pet.IsPet() && (pet.ToPet().getPetType() == PetType.Summon) && (pet != unit_target) && (RandomHelper.IRand(0, 100) < 10)) + pet.SendPetTalk(PetTalk.SpecialSpell); + else + { + pet.SendPetAIReaction(guid1); + } + + if (unit_target && !GetPlayer().IsFriendlyTo(unit_target) && !pet.isPossessed() && !pet.IsVehicle()) + { + // This is true if pet has no target or has target but targets differs. + if (pet.GetVictim() != unit_target) + { + if (pet.GetVictim()) + pet.AttackStop(); + pet.GetMotionMaster().Clear(); + if (pet.ToCreature().IsAIEnabled) + pet.ToCreature().GetAI().AttackStart(unit_target); + } + } + + spell.prepare(spell.m_targets); + } + else + { + if (pet.isPossessed() || pet.IsVehicle()) /// @todo: confirm this check + Spell.SendCastResult(GetPlayer(), spellInfo, spell.m_SpellVisual, spell.m_castId, result); + else + spell.SendPetCastResult(result); + + if (!pet.GetSpellHistory().HasCooldown(spellid)) + pet.GetSpellHistory().ResetCooldown(spellid, true); + + spell.finish(false); + spell.Dispose(); + + // reset specific flags in case of spell fail. AI will reset other flags + if (pet.GetCharmInfo() != null) + pet.GetCharmInfo().SetIsCommandAttack(false); + } + break; + } + default: + Log.outError(LogFilter.Network, "WORLD: unknown PET flag Action {0} and spellid {1}.", flag, spellid); + break; + } + } + + [WorldPacketHandler(ClientOpcodes.QueryPetName)] + void HandleQueryPetName(QueryPetName packet) + { + SendQueryPetNameResponse(packet.UnitGUID); + } + + void SendQueryPetNameResponse(ObjectGuid guid) + { + QueryPetNameResponse response = new QueryPetNameResponse(); + response.UnitGUID = guid; + + Creature unit = ObjectAccessor.GetCreatureOrPetOrVehicle(GetPlayer(), guid); + if (unit) + { + response.Allow = true; + response.Timestamp = unit.GetUInt32Value(UnitFields.PetNameTimestamp); + response.Name = unit.GetName(); + + Pet pet = unit.ToPet(); + if (pet) + { + DeclinedName names = pet.GetDeclinedNames(); + if (names != null) + { + response.HasDeclined = true; + response.DeclinedNames = names; + } + } + return; + } + + GetPlayer().SendPacket(response); + } + + bool CheckStableMaster(ObjectGuid guid) + { + // spell case or GM + if (guid == GetPlayer().GetGUID()) + { + if (!GetPlayer().IsGameMaster() && !GetPlayer().HasAuraType(AuraType.OpenStable)) + { + Log.outDebug(LogFilter.Network, "{0} attempt open stable in cheating way.", guid.ToString()); + return false; + } + } + // stable master case + else + { + if (!GetPlayer().GetNPCIfCanInteractWith(guid, NPCFlags.StableMaster)) + { + Log.outDebug(LogFilter.Network, "Stablemaster {0} not found or you can't interact with him.", guid.ToString()); + return false; + } + } + return true; + } + + [WorldPacketHandler(ClientOpcodes.PetSetAction)] + void HandlePetSetAction(PetSetAction packet) + { + ObjectGuid petguid = packet.PetGUID; + Unit pet = Global.ObjAccessor.GetUnit(GetPlayer(), petguid); + if (!pet || pet != GetPlayer().GetFirstControlled()) + { + Log.outError(LogFilter.Network, "HandlePetSetAction: Unknown {0} or pet owner {1}", petguid.ToString(), GetPlayer().GetGUID().ToString()); + return; + } + + CharmInfo charmInfo = pet.GetCharmInfo(); + if (charmInfo == null) + { + Log.outError(LogFilter.Network, "WorldSession.HandlePetSetAction: {0} is considered pet-like but doesn't have a charminfo!", pet.GetGUID().ToString()); + return; + } + + uint position = packet.Index; + uint actionData = packet.Action; + + uint spell_id = UnitActionBarEntry.UNIT_ACTION_BUTTON_ACTION(actionData); + ActiveStates act_state = (ActiveStates)UnitActionBarEntry.UNIT_ACTION_BUTTON_TYPE(actionData); + + Log.outDebug(LogFilter.Network, "Player {0} has changed pet spell action. Position: {1}, Spell: {2}, State: {3}", GetPlayer().GetName(), position, spell_id, act_state); + + + //if it's act for spell (en/disable/cast) and there is a spell given (0 = remove spell) which pet doesn't know, don't add + if (!((act_state == ActiveStates.Enabled || act_state == ActiveStates.Disabled || act_state == ActiveStates.Passive) && spell_id != 0 && !pet.HasSpell(spell_id))) + { + SpellInfo spellInfo = Global.SpellMgr.GetSpellInfo(spell_id); + if (spellInfo != null) + { + //sign for autocast + if (act_state == ActiveStates.Enabled) + { + if (pet.GetTypeId() == TypeId.Unit && pet.IsPet()) + ((Pet)pet).ToggleAutocast(spellInfo, true); + else + { + foreach (var unit in GetPlayer().m_Controlled) + if (unit.GetEntry() == pet.GetEntry()) + unit.GetCharmInfo().ToggleCreatureAutocast(spellInfo, true); + } + } + //sign for no/turn off autocast + else if (act_state == ActiveStates.Disabled) + { + if (pet.GetTypeId() == TypeId.Unit && pet.IsPet()) + pet.ToPet().ToggleAutocast(spellInfo, false); + else + { + foreach (var unit in GetPlayer().m_Controlled) + if (unit.GetEntry() == pet.GetEntry()) + unit.GetCharmInfo().ToggleCreatureAutocast(spellInfo, false); + } + } + } + + charmInfo.SetActionBar((byte)position, spell_id, act_state); + } + } + + [WorldPacketHandler(ClientOpcodes.PetRename)] + void HandlePetRename(PetRename packet) + { + ObjectGuid petguid = packet.RenameData.PetGUID; + bool isdeclined = packet.RenameData.HasDeclinedNames; + string name = packet.RenameData.NewName; + + Pet pet = ObjectAccessor.GetPet(GetPlayer(), petguid); + // check it! + if (!pet || !pet.IsPet() || pet.ToPet().getPetType() != PetType.Hunter || !pet.HasByteFlag(UnitFields.Bytes2, UnitBytes2Offsets.PetFlags, UnitPetFlags.CanBeRenamed) || + pet.GetOwnerGUID() != GetPlayer().GetGUID() || pet.GetCharmInfo() == null) + return; + + PetNameInvalidReason res = ObjectManager.CheckPetName(name); + if (res != PetNameInvalidReason.Success) + { + SendPetNameInvalid(res, name, null); + return; + } + + if (Global.ObjectMgr.IsReservedName(name)) + { + SendPetNameInvalid(PetNameInvalidReason.Reserved, name, null); + return; + } + + pet.SetName(name); + pet.SetGroupUpdateFlag(GroupUpdatePetFlags.Name); + pet.RemoveByteFlag(UnitFields.Bytes2, UnitBytes2Offsets.PetFlags, UnitPetFlags.CanBeRenamed); + + PreparedStatement stmt; + SQLTransaction trans = new SQLTransaction(); + if (isdeclined) + { + stmt = DB.Characters.GetPreparedStatement(CharStatements.DEL_CHAR_PET_DECLINEDNAME); + stmt.AddValue(0, pet.GetCharmInfo().GetPetNumber()); + trans.Append(stmt); + + stmt = DB.Characters.GetPreparedStatement(CharStatements.INS_CHAR_PET_DECLINEDNAME); + stmt.AddValue(0, pet.GetCharmInfo().GetPetNumber()); + stmt.AddValue(1, GetPlayer().GetGUID().ToString()); + + for (byte i = 0; i < SharedConst.MaxDeclinedNameCases; i++) + stmt.AddValue(i + 1, packet.RenameData.DeclinedNames.name[i]); + + trans.Append(stmt); + } + + stmt = DB.Characters.GetPreparedStatement(CharStatements.UPD_CHAR_PET_NAME); + stmt.AddValue(0, name); + stmt.AddValue(1, GetPlayer().GetGUID().ToString()); + stmt.AddValue(2, pet.GetCharmInfo().GetPetNumber()); + trans.Append(stmt); + + DB.Characters.CommitTransaction(trans); + + pet.SetUInt32Value(UnitFields.PetNameTimestamp, (uint)Time.UnixTime); // cast can't be helped + } + + [WorldPacketHandler(ClientOpcodes.PetAbandon)] + void HandlePetAbandon(PetAbandon packet) + { + if (!GetPlayer().IsInWorld) + return; + + Creature pet = ObjectAccessor.GetCreatureOrPetOrVehicle(GetPlayer(), packet.Pet); + if (pet) + { + if (pet.IsPet()) + GetPlayer().RemovePet(pet.ToPet(), PetSaveMode.AsDeleted); + else if (pet.GetGUID() == GetPlayer().GetCharmGUID()) + GetPlayer().StopCastingCharm(); + } + } + + [WorldPacketHandler(ClientOpcodes.PetSpellAutocast)] + void HandlePetSpellAutocast(PetSpellAutocast packet) + { + Creature pet = ObjectAccessor.GetCreatureOrPetOrVehicle(GetPlayer(), packet.PetGUID); + if (!pet) + { + Log.outError(LogFilter.Network, "WorldSession.HandlePetSpellAutocast: {0} not found.", packet.PetGUID.ToString()); + return; + } + + if (pet != GetPlayer().GetGuardianPet() && pet != GetPlayer().GetCharm()) + { + Log.outError(LogFilter.Network, "WorldSession.HandlePetSpellAutocast: {0} isn't pet of player {1} ({2}).", + packet.PetGUID.ToString(), GetPlayer().GetName(), GetPlayer().GetGUID().ToString()); + return; + } + + SpellInfo spellInfo = Global.SpellMgr.GetSpellInfo(packet.SpellID); + if (spellInfo == null) + { + Log.outError(LogFilter.Network, "WorldSession.HandlePetSpellAutocast: Unknown spell id {0} used by {1}.", packet.SpellID, packet.PetGUID.ToString()); + return; + } + + // do not add not learned spells/ passive spells + if (!pet.HasSpell(packet.SpellID) || !spellInfo.IsAutocastable()) + return; + + CharmInfo charmInfo = pet.GetCharmInfo(); + if (charmInfo == null) + { + Log.outError(LogFilter.Network, "WorldSession.HandlePetSpellAutocastOpcod: object {0} is considered pet-like but doesn't have a charminfo!", pet.GetGUID().ToString()); + return; + } + + if (pet.IsPet()) + pet.ToPet().ToggleAutocast(spellInfo, packet.AutocastEnabled); + else + charmInfo.ToggleCreatureAutocast(spellInfo, packet.AutocastEnabled); + + charmInfo.SetSpellAutocast(spellInfo, packet.AutocastEnabled); + } + + [WorldPacketHandler(ClientOpcodes.PetCastSpell)] + void HandlePetCastSpell(PetCastSpell petCastSpell) + { + SpellInfo spellInfo = Global.SpellMgr.GetSpellInfo(petCastSpell.Cast.SpellID); + if (spellInfo == null) + { + Log.outError(LogFilter.Network, "WorldSession.HandlePetCastSpell: unknown spell id {0} tried to cast by {1}", petCastSpell.Cast.SpellID, petCastSpell.PetGUID.ToString()); + return; + } + + Unit caster = Global.ObjAccessor.GetUnit(GetPlayer(), petCastSpell.PetGUID); + if (!caster) + { + Log.outError(LogFilter.Network, "WorldSession.HandlePetCastSpell: Caster {0} not found.", petCastSpell.PetGUID.ToString()); + return; + } + + // This opcode is also sent from charmed and possessed units (players and creatures) + if (caster != GetPlayer().GetGuardianPet() && caster != GetPlayer().GetCharm()) + { + Log.outError(LogFilter.Network, "WorldSession.HandlePetCastSpell: {0} isn't pet of player {1} ({2}).", petCastSpell.PetGUID.ToString(), GetPlayer().GetName(), GetPlayer().GetGUID().ToString()); + return; + } + + // do not cast not learned spells + if (!caster.HasSpell(spellInfo.Id) || spellInfo.IsPassive()) + return; + + SpellCastTargets targets = new SpellCastTargets(caster, petCastSpell.Cast); + caster.ClearUnitState(UnitState.Follow); + + Spell spell = new Spell(caster, spellInfo, TriggerCastFlags.None); + spell.m_fromClient = true; + spell.m_misc.Data0 = petCastSpell.Cast.Misc[0]; + spell.m_misc.Data1 = petCastSpell.Cast.Misc[1]; + spell.m_targets = targets; + + SpellCastResult result = spell.CheckPetCast(null); + + if (result == SpellCastResult.SpellCastOk) + { + Creature creature = caster.ToCreature(); + if (creature) + { + Pet pet = creature.ToPet(); + if (pet) + { + // 10% chance to play special pet attack talk, else growl + // actually this only seems to happen on special spells, fire shield for imp, torment for voidwalker, but it's stupid to check every spell + if (pet.getPetType() == PetType.Summon && (RandomHelper.IRand(0, 100) < 10)) + pet.SendPetTalk(PetTalk.SpecialSpell); + else + pet.SendPetAIReaction(petCastSpell.PetGUID); + } + } + + SpellPrepare spellPrepare = new SpellPrepare(); + spellPrepare.ClientCastID = petCastSpell.Cast.CastID; + spellPrepare.ServerCastID = spell.m_castId; + SendPacket(spellPrepare); + + spell.prepare(targets); + } + else + { + spell.SendPetCastResult(result); + + if (!caster.GetSpellHistory().HasCooldown(spellInfo.Id)) + caster.GetSpellHistory().ResetCooldown(spellInfo.Id, true); + + spell.finish(false); + spell.Dispose(); + } + } + + void SendPetNameInvalid(PetNameInvalidReason error, string name, DeclinedName declinedName) + { + PetNameInvalid petNameInvalid = new PetNameInvalid(); + petNameInvalid.Result = error; + petNameInvalid.RenameData.NewName = name; + for (int i = 0; i < SharedConst.MaxDeclinedNameCases; i++) + petNameInvalid.RenameData.DeclinedNames.name[i] = declinedName.name[i]; + + SendPacket(petNameInvalid); + } + } +} diff --git a/Game/Handlers/PetitionsHandler.cs b/Game/Handlers/PetitionsHandler.cs new file mode 100644 index 000000000..1e9c96048 --- /dev/null +++ b/Game/Handlers/PetitionsHandler.cs @@ -0,0 +1,535 @@ +/* + * Copyright (C) 2012-2017 CypherCore + * + * 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, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +using Framework.Constants; +using Framework.Database; +using Game.Entities; +using Game.Guilds; +using Game.Network; +using Game.Network.Packets; +using System.Collections.Generic; +using System.Text; + +namespace Game +{ + public partial class WorldSession + { + [WorldPacketHandler(ClientOpcodes.PetitionBuy)] + void HandlePetitionBuy(PetitionBuy packet) + { + // prevent cheating + Creature creature = GetPlayer().GetNPCIfCanInteractWith(packet.Unit, NPCFlags.Petitioner); + if (!creature) + { + Log.outDebug(LogFilter.Network, "WORLD: HandlePetitionBuyOpcode - {0} not found or you can't interact with him.", packet.Unit.ToString()); + return; + } + + // remove fake death + if (GetPlayer().HasUnitState(UnitState.Died)) + GetPlayer().RemoveAurasByType(AuraType.FeignDeath); + + uint charterItemID = GuildConst.CharterItemId; + int cost = WorldConfig.GetIntValue(WorldCfg.CharterCostGuild); + + // do not let if already in guild. + if (GetPlayer().GetGuildId() != 0) + return; + + if (Global.GuildMgr.GetGuildByName(packet.Title)) + { + Guild.SendCommandResult(this, GuildCommandType.CreateGuild, GuildCommandError.NameExists_S, packet.Title); + return; + } + + if (Global.ObjectMgr.IsReservedName(packet.Title) || !ObjectManager.IsValidCharterName(packet.Title)) + { + Guild.SendCommandResult(this, GuildCommandType.CreateGuild, GuildCommandError.NameInvalid, packet.Title); + return; + } + + ItemTemplate pProto = Global.ObjectMgr.GetItemTemplate(charterItemID); + if (pProto == null) + { + GetPlayer().SendBuyError(BuyResult.CantFindItem, null, charterItemID); + return; + } + + if (!GetPlayer().HasEnoughMoney(cost)) + { //player hasn't got enough money + GetPlayer().SendBuyError(BuyResult.NotEnoughtMoney, creature, charterItemID); + return; + } + + List dest = new List(); + InventoryResult msg = GetPlayer().CanStoreNewItem(ItemConst.NullBag, ItemConst.NullSlot, dest, charterItemID, pProto.GetBuyCount()); + if (msg != InventoryResult.Ok) + { + GetPlayer().SendEquipError(msg, null, null, charterItemID); + return; + } + + GetPlayer().ModifyMoney(-cost); + Item charter = GetPlayer().StoreNewItem(dest, charterItemID, true); + if (!charter) + return; + + charter.SetUInt32Value(ItemFields.Enchantment, (uint)charter.GetGUID().GetCounter()); + // ITEM_FIELD_ENCHANTMENT_1_1 is guild/arenateam id + // ITEM_FIELD_ENCHANTMENT_1_1+1 is current signatures count (showed on item) + charter.SetState(ItemUpdateState.Changed, GetPlayer()); + GetPlayer().SendNewItem(charter, 1, true, false); + + // a petition is invalid, if both the owner and the type matches + // we checked above, if this player is in an arenateam, so this must be + // datacorruption + PreparedStatement stmt = DB.Characters.GetPreparedStatement(CharStatements.SEL_PETITION_BY_OWNER); + stmt.AddValue(0, GetPlayer().GetGUID().GetCounter()); + SQLResult result = DB.Characters.Query(stmt); + + StringBuilder ssInvalidPetitionGUIDs = new StringBuilder(); + + if (!result.IsEmpty()) + { + do + { + ssInvalidPetitionGUIDs.AppendFormat("'{0}', ", result.Read(0)); + } while (result.NextRow()); + } + + // delete petitions with the same guid as this one + ssInvalidPetitionGUIDs.AppendFormat("'{0}'", charter.GetGUID().GetCounter()); + + Log.outDebug(LogFilter.Network, "Invalid petition GUIDs: {0}", ssInvalidPetitionGUIDs.ToString()); + SQLTransaction trans = new SQLTransaction(); + trans.Append("DELETE FROM petition WHERE petitionguid IN ({0})", ssInvalidPetitionGUIDs.ToString()); + trans.Append("DELETE FROM petition_sign WHERE petitionguid IN ({0})", ssInvalidPetitionGUIDs.ToString()); + + stmt = DB.Characters.GetPreparedStatement(CharStatements.INS_PETITION); + stmt.AddValue(0, GetPlayer().GetGUID().GetCounter()); + stmt.AddValue(1, charter.GetGUID().GetCounter()); + stmt.AddValue(2, packet.Title); + trans.Append(stmt); + + DB.Characters.CommitTransaction(trans); + } + + [WorldPacketHandler(ClientOpcodes.PetitionShowSignatures)] + void HandlePetitionShowSignatures(PetitionShowSignatures packet) + { + Log.outDebug(LogFilter.Network, "Received opcode CMSG_PETITION_SHOW_SIGNATURES"); + + byte signs = 0; + + // if has guild => error, return; + if (GetPlayer().GetGuildId() != 0) + return; + + PreparedStatement stmt = DB.Characters.GetPreparedStatement(CharStatements.SEL_PETITION_SIGNATURE); + stmt.AddValue(0, packet.Item.GetCounter()); + SQLResult result = DB.Characters.Query(stmt); + + // result == NULL also correct in case no sign yet + if (!result.IsEmpty()) + signs = (byte)result.GetRowCount(); + + ServerPetitionShowSignatures signaturesPacket = new ServerPetitionShowSignatures(); + signaturesPacket.Item = packet.Item; + signaturesPacket.Owner = GetPlayer().GetGUID(); + signaturesPacket.OwnerAccountID = ObjectGuid.Create(HighGuid.WowAccount, ObjectManager.GetPlayerAccountIdByGUID(GetPlayer().GetGUID())); + signaturesPacket.PetitionID = (int)packet.Item.GetCounter(); // @todo verify that... + + for (byte i = 1; i <= signs; ++i) + { + ObjectGuid signerGUID = ObjectGuid.Create(HighGuid.Player, result.Read(0)); + + ServerPetitionShowSignatures.PetitionSignature signature = new ServerPetitionShowSignatures.PetitionSignature(); + signature.Signer = signerGUID; + signature.Choice = 0; + signaturesPacket.Signatures.Add(signature); + + result.NextRow(); + } + SendPacket(signaturesPacket); + } + + [WorldPacketHandler(ClientOpcodes.QueryPetition)] + void HandleQueryPetition(QueryPetition packet) + { + SendPetitionQuery(packet.ItemGUID); + } + + public void SendPetitionQuery(ObjectGuid petitionGUID) + { + ObjectGuid ownerGUID = ObjectGuid.Empty; + string title = "NO_NAME_FOR_GUID"; + + QueryPetitionResponse responsePacket = new QueryPetitionResponse(); + responsePacket.PetitionID = (uint)petitionGUID.GetCounter(); // PetitionID (in Trinity always same as GUID_LOPART(petition guid)) + + PreparedStatement stmt = DB.Characters.GetPreparedStatement(CharStatements.SEL_PETITION); + stmt.AddValue(0, petitionGUID.GetCounter()); + SQLResult result = DB.Characters.Query(stmt); + + if (!result.IsEmpty()) + { + ownerGUID = ObjectGuid.Create(HighGuid.Player, result.Read(0)); + title = result.Read(1); + } + else + { + Log.outDebug(LogFilter.Network, "CMSG_PETITION_Select failed for petition ({0})", petitionGUID.ToString()); + return; + } + + int reqSignatures = WorldConfig.GetIntValue(WorldCfg.MinPetitionSigns); + + PetitionInfo petitionInfo = new PetitionInfo(); + petitionInfo.PetitionID = (int)petitionGUID.GetCounter(); + petitionInfo.Petitioner = ownerGUID; + petitionInfo.MinSignatures = reqSignatures; + petitionInfo.MaxSignatures = reqSignatures; + petitionInfo.Title = title; + + responsePacket.Allow = true; + responsePacket.Info = petitionInfo; + + SendPacket(responsePacket); + } + + [WorldPacketHandler(ClientOpcodes.PetitionRenameGuild)] + void HandlePetitionRenameGuild(PetitionRenameGuild packet) + { + Item item = GetPlayer().GetItemByGuid(packet.PetitionGuid); + if (!item) + return; + + if (Global.GuildMgr.GetGuildByName(packet.NewGuildName)) + { + Guild.SendCommandResult(this, GuildCommandType.CreateGuild, GuildCommandError.NameExists_S, packet.NewGuildName); + return; + } + if (Global.ObjectMgr.IsReservedName(packet.NewGuildName) || !ObjectManager.IsValidCharterName(packet.NewGuildName)) + { + Guild.SendCommandResult(this, GuildCommandType.CreateGuild, GuildCommandError.NameInvalid, packet.NewGuildName); + return; + } + + PreparedStatement stmt = DB.Characters.GetPreparedStatement(CharStatements.UPD_PETITION_NAME); + stmt.AddValue(0, packet.NewGuildName); + stmt.AddValue(1, packet.PetitionGuid.GetCounter()); + DB.Characters.Execute(stmt); + + PetitionRenameGuildResponse renameResponse = new PetitionRenameGuildResponse(); + renameResponse.PetitionGuid = packet.PetitionGuid; + renameResponse.NewGuildName = packet.NewGuildName; + SendPacket(renameResponse); + } + + [WorldPacketHandler(ClientOpcodes.SignPetition)] + void HandleSignPetition(SignPetition packet) + { + PreparedStatement stmt = DB.Characters.GetPreparedStatement(CharStatements.SEL_PETITION_SIGNATURES); + stmt.AddValue(0, packet.PetitionGUID.GetCounter()); + stmt.AddValue(1, packet.PetitionGUID.GetCounter()); + SQLResult result = DB.Characters.Query(stmt); + + if (result.IsEmpty()) + { + Log.outError(LogFilter.Network, "Petition {0} is not found for player {1} {2}", packet.PetitionGUID.ToString(), GetPlayer().GetGUID().ToString(), GetPlayer().GetName()); + return; + } + + ObjectGuid ownerGuid = ObjectGuid.Create(HighGuid.Player, result.Read(0)); + ulong signs = result.Read(1); + + if (ownerGuid == GetPlayer().GetGUID()) + return; + + // not let enemies sign guild charter + if (!WorldConfig.GetBoolValue(WorldCfg.AllowTwoSideInteractionGuild) && GetPlayer().GetTeam() != ObjectManager.GetPlayerTeamByGUID(ownerGuid)) + { + Guild.SendCommandResult(this, GuildCommandType.CreateGuild, GuildCommandError.NotAllied); + return; + } + + + if (GetPlayer().GetGuildId() != 0) + { + Guild.SendCommandResult(this, GuildCommandType.InvitePlayer, GuildCommandError.AlreadyInGuild_S, GetPlayer().GetName()); + return; + } + if (GetPlayer().GetGuildIdInvited() != 0) + { + Guild.SendCommandResult(this, GuildCommandType.InvitePlayer, GuildCommandError.AlreadyInvitedToGuild_S, GetPlayer().GetName()); + return; + } + + // Client doesn't allow to sign petition two times by one character, but not check sign by another character from same account + // not allow sign another player from already sign player account + stmt = DB.Characters.GetPreparedStatement(CharStatements.SEL_PETITION_SIG_BY_ACCOUNT); + stmt.AddValue(0, GetAccountId()); + stmt.AddValue(1, packet.PetitionGUID.GetCounter()); + result = DB.Characters.Query(stmt); + + PetitionSignResults signResult = new PetitionSignResults(); + signResult.Player = GetPlayer().GetGUID(); + signResult.Item = packet.PetitionGUID; + + if (!result.IsEmpty()) + { + signResult.Error = PetitionSigns.AlreadySigned; + + // close at signer side + SendPacket(signResult); + return; + } + + stmt = DB.Characters.GetPreparedStatement(CharStatements.INS_PETITION_SIGNATURE); + stmt.AddValue(0, ownerGuid.GetCounter()); + stmt.AddValue(1, packet.PetitionGUID.GetCounter()); + stmt.AddValue(2, GetPlayer().GetGUID().GetCounter()); + stmt.AddValue(3, GetAccountId()); + DB.Characters.Execute(stmt); + + Log.outDebug(LogFilter.Network, "PETITION SIGN: {0} by player: {1} ({2} Account: {3})", packet.PetitionGUID.ToString(), GetPlayer().GetName(), GetPlayer().GetGUID().ToString(), GetAccountId()); + + signResult.Error = PetitionSigns.Ok; + + // close at signer side + SendPacket(signResult); + + // update for owner if online + Player owner = Global.ObjAccessor.FindPlayer(ownerGuid); + if (owner) + owner.SendPacket(signResult); + } + + [WorldPacketHandler(ClientOpcodes.DeclinePetition)] + void HandleDeclinePetition(DeclinePetition packet) + { + PreparedStatement stmt = DB.Characters.GetPreparedStatement(CharStatements.SEL_PETITION_OWNER_BY_GUID); + stmt.AddValue(0, packet.PetitionGUID.GetCounter()); + SQLResult result = DB.Characters.Query(stmt); + + if (result.IsEmpty()) + return; + + ObjectGuid ownerguid = ObjectGuid.Create(HighGuid.Player, result.Read(0)); + + Player owner = Global.ObjAccessor.FindPlayer(ownerguid); + if (owner) // petition owner online + { + // Disabled because packet isn't handled by the client in any way + /* + WorldPacket data = new WorldPacket(ServerOpcodes.PetitionDecline); + data.WritePackedGuid(GetPlayer().GetGUID()); + owner.SendPacket(data); + */ + } + } + + [WorldPacketHandler(ClientOpcodes.OfferPetition)] + void HandleOfferPetition(OfferPetition packet) + { + Player player = Global.ObjAccessor.FindConnectedPlayer(packet.TargetPlayer); + if (!player) + return; + + if (!WorldConfig.GetBoolValue(WorldCfg.AllowTwoSideInteractionGuild) && GetPlayer().GetTeam() != player.GetTeam()) + { + Guild.SendCommandResult(this, GuildCommandType.CreateGuild, GuildCommandError.NotAllied); + return; + } + + if (player.GetGuildId() != 0) + { + Guild.SendCommandResult(this, GuildCommandType.InvitePlayer, GuildCommandError.AlreadyInGuild_S, GetPlayer().GetName()); + return; + } + + if (player.GetGuildIdInvited() != 0) + { + Guild.SendCommandResult(this, GuildCommandType.InvitePlayer, GuildCommandError.AlreadyInvitedToGuild_S, GetPlayer().GetName()); + return; + } + + PreparedStatement stmt = DB.Characters.GetPreparedStatement(CharStatements.SEL_PETITION_SIGNATURE); + stmt.AddValue(0, packet.ItemGUID.GetCounter()); + SQLResult result = DB.Characters.Query(stmt); + + byte signs = 0; + // result == NULL also correct charter without signs + if (!result.IsEmpty()) + signs = (byte)result.GetRowCount(); + + ServerPetitionShowSignatures signaturesPacket = new ServerPetitionShowSignatures(); + signaturesPacket.Item = packet.ItemGUID; + signaturesPacket.Owner = GetPlayer().GetGUID(); + signaturesPacket.OwnerAccountID = ObjectGuid.Create(HighGuid.WowAccount, player.GetSession().GetAccountId()); + signaturesPacket.PetitionID = (int)packet.ItemGUID.GetCounter(); // @todo verify that... + + for (byte i = 1; i <= signs; ++i) + { + ObjectGuid signerGUID = ObjectGuid.Create(HighGuid.Player, result.Read(0)); + + ServerPetitionShowSignatures.PetitionSignature signature = new ServerPetitionShowSignatures.PetitionSignature(); + signature.Signer = signerGUID; + signature.Choice = 0; + signaturesPacket.Signatures.Add(signature); + + result.NextRow(); + } + + player.SendPacket(signaturesPacket); + } + + [WorldPacketHandler(ClientOpcodes.TurnInPetition)] + void HandleTurnInPetition(TurnInPetition packet) + { + // Check if player really has the required petition charter + Item item = GetPlayer().GetItemByGuid(packet.Item); + if (!item) + return; + + // Get petition data from db + ObjectGuid ownerguid; + string name; + + PreparedStatement stmt = DB.Characters.GetPreparedStatement(CharStatements.SEL_PETITION); + stmt.AddValue(0, packet.Item.GetCounter()); + SQLResult result = DB.Characters.Query(stmt); + + if (!result.IsEmpty()) + { + ownerguid = ObjectGuid.Create(HighGuid.Player, result.Read(0)); + name = result.Read(1); + } + else + { + Log.outError(LogFilter.Network, "Player {0} ({1}) tried to turn in petition ({2}) that is not present in the database", GetPlayer().GetName(), GetPlayer().GetGUID().ToString(), packet.Item.ToString()); + return; + } + + // Only the petition owner can turn in the petition + if (GetPlayer().GetGUID() != ownerguid) + return; + + TurnInPetitionResult resultPacket = new TurnInPetitionResult(); + + // Check if player is already in a guild + if (GetPlayer().GetGuildId() != 0) + { + resultPacket.Result = PetitionTurns.AlreadyInGuild; + GetPlayer().SendPacket(resultPacket); + return; + } + + // Check if guild name is already taken + if (Global.GuildMgr.GetGuildByName(name)) + { + Guild.SendCommandResult(this, GuildCommandType.CreateGuild, GuildCommandError.NameExists_S, name); + return; + } + + // Get petition signatures from db + byte signatures; + + stmt = DB.Characters.GetPreparedStatement(CharStatements.SEL_PETITION_SIGNATURE); + stmt.AddValue(0, packet.Item.GetCounter()); + result = DB.Characters.Query(stmt); + + if (!result.IsEmpty()) + signatures = (byte)result.GetRowCount(); + else + signatures = 0; + + uint requiredSignatures = WorldConfig.GetUIntValue(WorldCfg.MinPetitionSigns); + + // Notify player if signatures are missing + if (signatures < requiredSignatures) + { + resultPacket.Result = PetitionTurns.NeedMoreSignatures; + SendPacket(resultPacket); + return; + } + // Proceed with guild/arena team creation + + // Delete charter item + GetPlayer().DestroyItem(item.GetBagSlot(), item.GetSlot(), true); + + // Create guild + Guild guild = new Guild(); + if (!guild.Create(GetPlayer(), name)) + return; + + // Register guild and add guild master + Global.GuildMgr.AddGuild(guild); + + Guild.SendCommandResult(this, GuildCommandType.CreateGuild, GuildCommandError.Success, name); + + // Add members from signatures + for (byte i = 0; i < signatures; ++i) + { + guild.AddMember(ObjectGuid.Create(HighGuid.Player, result.Read(0))); + result.NextRow(); + } + + SQLTransaction trans = new SQLTransaction(); + + stmt = DB.Characters.GetPreparedStatement(CharStatements.DEL_PETITION_BY_GUID); + stmt.AddValue(0, packet.Item.GetCounter()); + trans.Append(stmt); + + stmt = DB.Characters.GetPreparedStatement(CharStatements.DEL_PETITION_SIGNATURE_BY_GUID); + stmt.AddValue(0, packet.Item.GetCounter()); + trans.Append(stmt); + + DB.Characters.CommitTransaction(trans); + + // created + Log.outDebug(LogFilter.Network, "Player {0} ({1}) turning in petition {2}", GetPlayer().GetName(), GetPlayer().GetGUID().ToString(), packet.Item.ToString()); + + resultPacket.Result = PetitionTurns.Ok; + SendPacket(resultPacket); + } + + [WorldPacketHandler(ClientOpcodes.PetitionShowList)] + void HandlePetitionShowList(PetitionShowList packet) + { + SendPetitionShowList(packet.PetitionUnit); + } + + public void SendPetitionShowList(ObjectGuid guid) + { + Creature creature = GetPlayer().GetNPCIfCanInteractWith(guid, NPCFlags.Petitioner); + if (!creature) + { + Log.outDebug(LogFilter.Network, "WORLD: HandlePetitionShowListOpcode - {0} not found or you can't interact with him.", guid.ToString()); + return; + } + + WorldPacket data = new WorldPacket(ServerOpcodes.PetitionShowList); + data.WritePackedGuid(guid); // npc guid + + ServerPetitionShowList packet = new ServerPetitionShowList(); + packet.Unit = guid; + packet.Price = WorldConfig.GetUIntValue(WorldCfg.CharterCostGuild); + SendPacket(packet); + } + } +} diff --git a/Game/Handlers/QueryHandler.cs b/Game/Handlers/QueryHandler.cs new file mode 100644 index 000000000..5ecf046b8 --- /dev/null +++ b/Game/Handlers/QueryHandler.cs @@ -0,0 +1,447 @@ +/* + * Copyright (C) 2012-2017 CypherCore + * + * 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, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +using Framework.Constants; +using Framework.GameMath; +using Game.DataStorage; +using Game.Entities; +using Game.Maps; +using Game.Misc; +using Game.Network; +using Game.Network.Packets; +using System.Collections.Generic; + +namespace Game +{ + public partial class WorldSession + { + [WorldPacketHandler(ClientOpcodes.QueryPlayerName)] + void HandleNameQueryRequest(QueryPlayerName queryPlayerName) + { + SendNameQuery(queryPlayerName.Player); + } + + public void SendNameQuery(ObjectGuid guid) + { + Player player = Global.ObjAccessor.FindPlayer(guid); + + QueryPlayerNameResponse response = new QueryPlayerNameResponse(); + response.Player = guid; + + if (response.Data.Initialize(guid, player)) + response.Result = ResponseCodes.Success; + else + response.Result = ResponseCodes.Failure; // name unknown + + SendPacket(response); + } + + [WorldPacketHandler(ClientOpcodes.QueryTime)] + void HandleQueryTime(QueryTime packet) + { + SendQueryTimeResponse(); + } + + void SendQueryTimeResponse() + { + QueryTimeResponse queryTimeResponse = new QueryTimeResponse(); + queryTimeResponse.CurrentTime = Time.UnixTime; + SendPacket(queryTimeResponse); + } + + [WorldPacketHandler(ClientOpcodes.QueryGameObject, Processing = PacketProcessing.Inplace)] + void HandleGameObjectQuery(QueryGameObject packet) + { + QueryGameObjectResponse response = new QueryGameObjectResponse(); + response.GameObjectID = packet.GameObjectID; + + GameObjectTemplate gameObjectInfo = Global.ObjectMgr.GetGameObjectTemplate(packet.GameObjectID); + if (gameObjectInfo != null) + { + response.Allow = true; + GameObjectStats stats = new GameObjectStats(); + + stats.Type = (uint)gameObjectInfo.type; + stats.DisplayID = gameObjectInfo.displayId; + + stats.Name[0] = gameObjectInfo.name; + stats.IconName = gameObjectInfo.IconName; + stats.CastBarCaption = gameObjectInfo.castBarCaption; + stats.UnkString = gameObjectInfo.unk1; + + LocaleConstant localeConstant = GetSessionDbLocaleIndex(); + if (localeConstant != LocaleConstant.enUS) + { + GameObjectLocale gameObjectLocale = Global.ObjectMgr.GetGameObjectLocale(packet.GameObjectID); + if (gameObjectLocale != null) + { + ObjectManager.GetLocaleString(gameObjectLocale.Name, localeConstant, ref stats.Name[0]); + ObjectManager.GetLocaleString(gameObjectLocale.CastBarCaption, localeConstant, ref stats.CastBarCaption); + ObjectManager.GetLocaleString(gameObjectLocale.Unk1, localeConstant, ref stats.UnkString); + } + } + + var items = Global.ObjectMgr.GetGameObjectQuestItemList(packet.GameObjectID); + foreach (int item in items) + stats.QuestItems.Add(item); + + unsafe + { + fixed (int* ptr = gameObjectInfo.Raw.data) + { + for (int i = 0; i < SharedConst.MaxGOData; i++) + stats.Data[i] = ptr[i]; + } + } + stats.RequiredLevel = (uint)gameObjectInfo.RequiredLevel; + response.Stats = stats; + } + + SendPacket(response); + } + + [WorldPacketHandler(ClientOpcodes.QueryCreature, Processing = PacketProcessing.Inplace)] + void HandleCreatureQuery(QueryCreature packet) + { + QueryCreatureResponse response = new QueryCreatureResponse(); + + response.CreatureID = packet.CreatureID; + + CreatureTemplate creatureInfo = Global.ObjectMgr.GetCreatureTemplate(packet.CreatureID); + if (creatureInfo != null) + { + response.Allow = true; + + CreatureStats stats = new CreatureStats(); + + stats.Leader = creatureInfo.RacialLeader; + + string name = creatureInfo.Name; + string nameAlt = creatureInfo.FemaleName; + + stats.Flags[0] = (uint)creatureInfo.TypeFlags; + stats.Flags[1] = creatureInfo.TypeFlags2; + + stats.CreatureType = (int)creatureInfo.CreatureType; + stats.CreatureFamily = (int)creatureInfo.Family; + stats.Classification = (int)creatureInfo.Rank; + + for (uint i = 0; i < SharedConst.MaxCreatureKillCredit; ++i) + stats.ProxyCreatureID[i] = creatureInfo.KillCredit[i]; + + stats.CreatureDisplayID[0] = creatureInfo.ModelId1; + stats.CreatureDisplayID[1] = creatureInfo.ModelId2; + stats.CreatureDisplayID[2] = creatureInfo.ModelId3; + stats.CreatureDisplayID[3] = creatureInfo.ModelId4; + + stats.HpMulti = creatureInfo.ModHealth; + stats.EnergyMulti = creatureInfo.ModMana; + + stats.CreatureMovementInfoID = creatureInfo.MovementId; + stats.RequiredExpansion = creatureInfo.RequiredExpansion; + stats.HealthScalingExpansion = creatureInfo.HealthScalingExpansion; + stats.VignetteID = creatureInfo.VignetteID; + + stats.Title = creatureInfo.SubName; + //stats.TitleAlt = ; + stats.CursorName = creatureInfo.IconName; + + var items = Global.ObjectMgr.GetCreatureQuestItemList(packet.CreatureID); + foreach (uint item in items) + stats.QuestItems.Add(item); + + LocaleConstant localeConstant = GetSessionDbLocaleIndex(); + if (localeConstant != LocaleConstant.enUS) + { + CreatureLocale creatureLocale = Global.ObjectMgr.GetCreatureLocale(packet.CreatureID); + if (creatureLocale != null) + { + ObjectManager.GetLocaleString(creatureLocale.Name, localeConstant, ref name); + ObjectManager.GetLocaleString(creatureLocale.NameAlt, localeConstant, ref nameAlt); + ObjectManager.GetLocaleString(creatureLocale.Title, localeConstant, ref stats.Title); + ObjectManager.GetLocaleString(creatureLocale.TitleAlt, localeConstant, ref stats.TitleAlt); + } + } + stats.Name[0] = name; + stats.NameAlt[0] = nameAlt; + + response.Stats = stats; + } + + SendPacket(response); + } + + [WorldPacketHandler(ClientOpcodes.QueryNpcText)] + void HandleNpcTextQuery(QueryNPCText packet) + { + NpcText npcText = Global.ObjectMgr.GetNpcText(packet.TextID); + + QueryNPCTextResponse response = new QueryNPCTextResponse(); + response.TextID = packet.TextID; + + if (npcText != null) + { + for (byte i = 0; i < SharedConst.MaxNpcTextOptions; ++i) + { + response.Probabilities[i] = npcText.Data[i].Probability; + response.BroadcastTextID[i] = npcText.Data[i].BroadcastTextID; + if (!response.Allow && npcText.Data[i].BroadcastTextID != 0) + response.Allow = true; + } + } + + if (!response.Allow) + Log.outError(LogFilter.Sql, "HandleNpcTextQuery: no BroadcastTextID found for text {0} in `npc_text table`", packet.TextID); + + SendPacket(response); + } + + [WorldPacketHandler(ClientOpcodes.QueryPageText)] + void HandleQueryPageText(QueryPageText packet) + { + QueryPageTextResponse response = new QueryPageTextResponse(); + response.PageTextID = packet.PageTextID; + + uint pageID = packet.PageTextID; + while (pageID != 0) + { + PageText pageText = Global.ObjectMgr.GetPageText(pageID); + + if (pageText == null) + break; + + QueryPageTextResponse.PageTextInfo page; + page.ID = pageID; + page.NextPageID = pageText.NextPageID; + page.Text = pageText.Text; + page.PlayerConditionID = pageText.PlayerConditionID; + page.Flags = pageText.Flags; + + LocaleConstant locale = GetSessionDbLocaleIndex(); + if (locale != LocaleConstant.enUS) + { + PageTextLocale pageLocale = Global.ObjectMgr.GetPageTextLocale(pageID); + if (pageLocale != null) + ObjectManager.GetLocaleString(pageLocale.Text, locale, ref page.Text); + } + + response.Pages.Add(page); + pageID = pageText.NextPageID; + } + + response.Allow = !response.Pages.Empty(); + SendPacket(response); + } + + [WorldPacketHandler(ClientOpcodes.QueryCorpseLocationFromClient)] + void HandleQueryCorpseLocation(QueryCorpseLocationFromClient queryCorpseLocation) + { + CorpseLocation packet = new CorpseLocation(); + Player player = Global.ObjAccessor.FindConnectedPlayer(queryCorpseLocation.Player); + if (!player || !player.HasCorpse() || !_player.IsInSameRaidWith(player)) + { + packet.Valid = false; // corpse not found + packet.Player = queryCorpseLocation.Player; + SendPacket(packet); + return; + } + + WorldLocation corpseLocation = player.GetCorpseLocation(); + uint corpseMapID = corpseLocation.GetMapId(); + uint mapID = corpseLocation.GetMapId(); + float x = corpseLocation.GetPositionX(); + float y = corpseLocation.GetPositionY(); + float z = corpseLocation.GetPositionZ(); + + // if corpse at different map + if (mapID != player.GetMapId()) + { + // search entrance map for proper show entrance + MapRecord corpseMapEntry = CliDB.MapStorage.LookupByKey(mapID); + if (corpseMapEntry != null) + { + if (corpseMapEntry.IsDungeon() && corpseMapEntry.CorpseMapID >= 0) + { + // if corpse map have entrance + Map entranceMap = Global.MapMgr.CreateBaseMap((uint)corpseMapEntry.CorpseMapID); + if (entranceMap != null) + { + mapID = (uint)corpseMapEntry.CorpseMapID; + x = corpseMapEntry.CorpsePos.X; + y = corpseMapEntry.CorpsePos.Y; + z = entranceMap.GetHeight(player.GetPhases(), x, y, MapConst.MaxHeight); + } + } + } + } + + packet.Valid = true; + packet.Player = queryCorpseLocation.Player; + packet.MapID = (int)corpseMapID; + packet.ActualMapID = (int)mapID; + packet.Position = new Vector3(x, y, z); + packet.Transport = ObjectGuid.Empty; + SendPacket(packet); + } + + [WorldPacketHandler(ClientOpcodes.QueryCorpseTransport)] + void HandleQueryCorpseTransport(QueryCorpseTransport queryCorpseTransport) + { + CorpseTransportQuery response = new CorpseTransportQuery(); + response.Player = queryCorpseTransport.Player; + + Player player = Global.ObjAccessor.FindConnectedPlayer(queryCorpseTransport.Player); + if (player) + { + Corpse corpse = player.GetCorpse(); + if (_player.IsInSameRaidWith(player) && corpse && !corpse.GetTransGUID().IsEmpty() && corpse.GetTransGUID() == queryCorpseTransport.Transport) + { + response.Position = new Vector3(corpse.GetTransOffsetX(), corpse.GetTransOffsetY(), corpse.GetTransOffsetZ()); + response.Facing = corpse.GetTransOffsetO(); + } + } + + SendPacket(response); + } + + [WorldPacketHandler(ClientOpcodes.QueryQuestCompletionNpcs)] + void HandleQueryQuestCompletionNPCs(QueryQuestCompletionNPCs queryQuestCompletionNPCs) + { + QuestCompletionNPCResponse response = new QuestCompletionNPCResponse(); + + foreach (var questID in queryQuestCompletionNPCs.QuestCompletionNPCs) + { + QuestCompletionNPC questCompletionNPC = new QuestCompletionNPC(); + + if (Global.ObjectMgr.GetQuestTemplate(questID) == null) + { + Log.outDebug(LogFilter.Network, "WORLD: Unknown quest {0} in CMSG_QUEST_NPC_QUERY by {1}", questID, GetPlayer().GetGUID()); + continue; + } + + questCompletionNPC.QuestID = questID; + + var creatures = Global.ObjectMgr.GetCreatureQuestInvolvedRelationReverseBounds(questID); + foreach (var id in creatures) + questCompletionNPC.NPCs.Add(id); + + var gos = Global.ObjectMgr.GetGOQuestInvolvedRelationReverseBounds(questID); + foreach (var id in gos) + questCompletionNPC.NPCs.Add(id | 0x80000000); // GO mask + + response.QuestCompletionNPCs.Add(questCompletionNPC); + } + + SendPacket(response); + } + + [WorldPacketHandler(ClientOpcodes.QuestPoiQuery)] + void HandleQuestPOIQuery(QuestPOIQuery packet) + { + if (packet.MissingQuestCount >= SharedConst.MaxQuestLogSize) + return; + + // Read quest ids and add the in a unordered_set so we don't send POIs for the same quest multiple times + HashSet questIds = new HashSet(); + for (int i = 0; i < packet.MissingQuestCount; ++i) + questIds.Add(packet.MissingQuestPOIs[i]); // QuestID + + QuestPOIQueryResponse response = new QuestPOIQueryResponse(); + + foreach (var QuestID in questIds) + { + bool questOk = false; + + ushort questSlot = GetPlayer().FindQuestSlot(QuestID); + + if (questSlot != SharedConst.MaxQuestLogSize) + questOk = GetPlayer().GetQuestSlotQuestId(questSlot) == QuestID; + + if (questOk) + { + var poiData = Global.ObjectMgr.GetQuestPOIList(QuestID); + if (poiData != null) + { + QuestPOIData questPOIData = new QuestPOIData(); + + questPOIData.QuestID = QuestID; + + foreach (var data in poiData) + { + QuestPOIBlobData questPOIBlobData = new QuestPOIBlobData(); + + questPOIBlobData.BlobIndex = data.BlobIndex; + questPOIBlobData.ObjectiveIndex = data.ObjectiveIndex; + questPOIBlobData.QuestObjectiveID = data.QuestObjectiveID; + questPOIBlobData.QuestObjectID = data.QuestObjectID; + questPOIBlobData.MapID = data.MapID; + questPOIBlobData.WorldMapAreaID = data.WorldMapAreaID; + questPOIBlobData.Floor = data.Floor; + questPOIBlobData.Priority = data.Priority; + questPOIBlobData.Flags = data.Flags; + questPOIBlobData.WorldEffectID = data.WorldEffectID; + questPOIBlobData.PlayerConditionID = data.PlayerConditionID; + questPOIBlobData.UnkWoD1 = data.UnkWoD1; + + foreach (var point in data.points) + questPOIBlobData.QuestPOIBlobPointStats.Add(new QuestPOIBlobPoint(point.X, point.Y)); + + questPOIData.QuestPOIBlobDataStats.Add(questPOIBlobData); + } + + response.QuestPOIDataStats.Add(questPOIData); + } + } + } + + SendPacket(response); + } + + [WorldPacketHandler(ClientOpcodes.ItemTextQuery)] + void HandleItemTextQuery(ItemTextQuery packet) + { + QueryItemTextResponse queryItemTextResponse = new QueryItemTextResponse(); + queryItemTextResponse.Id = packet.Id; + + Item item = GetPlayer().GetItemByGuid(packet.Id); + if (item) + { + queryItemTextResponse.Valid = true; + queryItemTextResponse.Text = item.GetText(); + } + + SendPacket(queryItemTextResponse); + } + + [WorldPacketHandler(ClientOpcodes.QueryRealmName)] + void HandleQueryRealmName(QueryRealmName queryRealmName) + { + RealmQueryResponse realmQueryResponse = new RealmQueryResponse(); + realmQueryResponse.VirtualRealmAddress = queryRealmName.VirtualRealmAddress; + + RealmHandle realmHandle = new RealmHandle(queryRealmName.VirtualRealmAddress); + if (Global.ObjectMgr.GetRealmName(realmHandle.Realm, ref realmQueryResponse.NameInfo.RealmNameActual, ref realmQueryResponse.NameInfo.RealmNameNormalized)) + { + realmQueryResponse.LookupState = (byte)ResponseCodes.Success; + realmQueryResponse.NameInfo.IsInternalRealm = false; + realmQueryResponse.NameInfo.IsLocal = queryRealmName.VirtualRealmAddress == Global.WorldMgr.GetRealm().Id.GetAddress(); + } + else + realmQueryResponse.LookupState = (byte)ResponseCodes.Failure; + } + } +} diff --git a/Game/Handlers/QuestHandler.cs b/Game/Handlers/QuestHandler.cs new file mode 100644 index 000000000..1ab1805d8 --- /dev/null +++ b/Game/Handlers/QuestHandler.cs @@ -0,0 +1,647 @@ +/* + * Copyright (C) 2012-2017 CypherCore + * + * 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, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +using Framework.Constants; +using Game.BattleGrounds; +using Game.Entities; +using Game.Groups; +using Game.Network; +using Game.Network.Packets; + +namespace Game +{ + public partial class WorldSession + { + [WorldPacketHandler(ClientOpcodes.QuestGiverStatusQuery, Processing = PacketProcessing.Inplace)] + void HandleQuestgiverStatusQuery(QuestGiverStatusQuery packet) + { + QuestGiverStatus questStatus = QuestGiverStatus.None; + + var questgiver = Global.ObjAccessor.GetObjectByTypeMask(GetPlayer(), packet.QuestGiverGUID, TypeMask.Unit | TypeMask.GameObject); + if (!questgiver) + { + Log.outInfo(LogFilter.Network, "Error in CMSG_QUESTGIVER_STATUS_QUERY, called for non-existing questgiver {0}", packet.QuestGiverGUID.ToString()); + return; + } + + switch (questgiver.GetTypeId()) + { + case TypeId.Unit: + if (!questgiver.ToCreature().IsHostileTo(GetPlayer()))// do not show quest status to enemies + questStatus = GetPlayer().GetQuestDialogStatus(questgiver); + break; + case TypeId.GameObject: + questStatus = GetPlayer().GetQuestDialogStatus(questgiver); + break; + default: + Log.outError(LogFilter.Network, "QuestGiver called for unexpected type {0}", questgiver.GetTypeId()); + break; + } + + //inform client about status of quest + GetPlayer().PlayerTalkClass.SendQuestGiverStatus(questStatus, packet.QuestGiverGUID); + } + + [WorldPacketHandler(ClientOpcodes.QuestGiverHello)] + void HandleQuestgiverHello(QuestGiverHello packet) + { + Creature creature = GetPlayer().GetNPCIfCanInteractWith(packet.QuestGiverGUID, NPCFlags.QuestGiver); + if (creature == null) + { + Log.outDebug(LogFilter.Network, "WORLD: HandleQuestgiverHello - {0} not found or you can't interact with him.", packet.QuestGiverGUID.ToString()); + return; + } + + // remove fake death + if (GetPlayer().HasUnitState(UnitState.Died)) + GetPlayer().RemoveAurasByType(AuraType.FeignDeath); + // Stop the npc if moving + creature.StopMoving(); + + if (Global.ScriptMgr.OnGossipHello(GetPlayer(), creature)) + return; + + GetPlayer().PrepareGossipMenu(creature, creature.GetCreatureTemplate().GossipMenuId, true); + GetPlayer().SendPreparedGossip(creature); + + creature.GetAI().sGossipHello(GetPlayer()); + } + + [WorldPacketHandler(ClientOpcodes.QuestGiverAcceptQuest)] + void HandleQuestgiverAcceptQuest(QuestGiverAcceptQuest packet) + { + WorldObject obj; + if (!packet.QuestGiverGUID.IsPlayer()) + obj = Global.ObjAccessor.GetObjectByTypeMask(_player, packet.QuestGiverGUID, TypeMask.Unit | TypeMask.GameObject | TypeMask.Item); + else + obj = Global.ObjAccessor.FindPlayer(packet.QuestGiverGUID); + + var CLOSE_GOSSIP_CLEAR_DIVIDER = new System.Action(() => + { + GetPlayer().PlayerTalkClass.SendCloseGossip(); + GetPlayer().SetDivider(ObjectGuid.Empty); + }); + + // no or incorrect quest giver + if (obj == null) + { + CLOSE_GOSSIP_CLEAR_DIVIDER(); + return; + } + + Player playerQuestObject = obj.ToPlayer(); + if (playerQuestObject) + { + if ((_player.GetDivider().IsEmpty() && _player.GetDivider() != packet.QuestGiverGUID) || !playerQuestObject.CanShareQuest(packet.QuestID)) + { + CLOSE_GOSSIP_CLEAR_DIVIDER(); + return; + } + if (!_player.IsInSameRaidWith(playerQuestObject)) + { + CLOSE_GOSSIP_CLEAR_DIVIDER(); + return; + } + } + else + { + if (!obj.hasQuest(packet.QuestID)) + { + CLOSE_GOSSIP_CLEAR_DIVIDER(); + return; + } + } + + // some kind of WPE protection + if (!_player.CanInteractWithQuestGiver(obj)) + { + CLOSE_GOSSIP_CLEAR_DIVIDER(); + return; + } + + Quest quest = Global.ObjectMgr.GetQuestTemplate(packet.QuestID); + if (quest != null) + { + // prevent cheating + if (!GetPlayer().CanTakeQuest(quest, true)) + { + CLOSE_GOSSIP_CLEAR_DIVIDER(); + return; + } + + if (!_player.GetDivider().IsEmpty()) + { + Player player = Global.ObjAccessor.FindPlayer(_player.GetDivider()); + if (player != null) + { + player.SendPushToPartyResponse(_player, QuestPushReason.Accepted); + _player.SetDivider(ObjectGuid.Empty); + } + } + + if (_player.CanAddQuest(quest, true)) + { + _player.AddQuestAndCheckCompletion(quest, obj); + + if (quest.HasFlag(QuestFlags.PartyAccept)) + { + var group = _player.GetGroup(); + if (group) + { + for (GroupReference refe = group.GetFirstMember(); refe != null; refe = refe.next()) + { + Player player = refe.GetSource(); + + if (!player || player == _player) // not self + continue; + + if (player.CanTakeQuest(quest, true)) + { + player.SetDivider(_player.GetGUID()); + + //need confirmation that any gossip window will close + player.PlayerTalkClass.SendCloseGossip(); + + _player.SendQuestConfirmAccept(quest, player); + } + } + } + } + + _player.PlayerTalkClass.SendCloseGossip(); + + if (quest.SourceSpellID > 0) + _player.CastSpell(_player, quest.SourceSpellID, true); + + return; + } + } + + CLOSE_GOSSIP_CLEAR_DIVIDER(); + } + + [WorldPacketHandler(ClientOpcodes.QuestGiverQueryQuest)] + void HandleQuestgiverQueryQuest(QuestGiverQueryQuest packet) + { + // Verify that the guid is valid and is a questgiver or involved in the requested quest + var obj = Global.ObjAccessor.GetObjectByTypeMask(GetPlayer(), packet.QuestGiverGUID, (TypeMask.Unit | TypeMask.GameObject | TypeMask.Item)); + if (!obj || (!obj.hasQuest(packet.QuestID) && !obj.hasInvolvedQuest(packet.QuestID))) + { + GetPlayer().PlayerTalkClass.SendCloseGossip(); + return; + } + + Quest quest = Global.ObjectMgr.GetQuestTemplate(packet.QuestID); + if (quest != null) + { + if (!GetPlayer().CanTakeQuest(quest, true)) + return; + + if (quest.IsAutoAccept() && GetPlayer().CanAddQuest(quest, true)) + GetPlayer().AddQuestAndCheckCompletion(quest, obj); + + if (quest.IsAutoComplete()) + GetPlayer().PlayerTalkClass.SendQuestGiverRequestItems(quest, obj.GetGUID(), GetPlayer().CanCompleteQuest(quest.Id), true); + else + GetPlayer().PlayerTalkClass.SendQuestGiverQuestDetails(quest, obj.GetGUID(), true); + } + } + + [WorldPacketHandler(ClientOpcodes.QueryQuestInfo)] + void HandleQuestQuery(QueryQuestInfo packet) + { + Quest quest = Global.ObjectMgr.GetQuestTemplate(packet.QuestID); + if (quest != null) + _player.PlayerTalkClass.SendQuestQueryResponse(quest); + else + { + QueryQuestInfoResponse response = new QueryQuestInfoResponse(); + response.QuestID = packet.QuestID; + SendPacket(response); + } + } + + [WorldPacketHandler(ClientOpcodes.QuestGiverChooseReward)] + void HandleQuestgiverChooseReward(QuestGiverChooseReward packet) + { + Quest quest = Global.ObjectMgr.GetQuestTemplate(packet.QuestID); + if (quest == null) + return; + + // This is Real Item Entry, not slot id as pre 5.x + if (packet.ItemChoiceID != 0) + { + ItemTemplate rewardProto = Global.ObjectMgr.GetItemTemplate(packet.ItemChoiceID); + if (rewardProto == null) + { + Log.outError(LogFilter.Network, "Error in CMSG_QUESTGIVER_CHOOSE_REWARD: player {0} ({1}) tried to get invalid reward item (Item Entry: {2}) for quest {3} (possible packet-hacking detected)", GetPlayer().GetName(), GetPlayer().GetGUID().ToString(), packet.ItemChoiceID, packet.QuestID); + return; + } + + bool itemValid = false; + for (uint i = 0; i < quest.GetRewChoiceItemsCount(); ++i) + { + if (quest.RewardChoiceItemId[i] != 0 && quest.RewardChoiceItemId[i] == packet.ItemChoiceID) + { + itemValid = true; + break; + } + } + + if (!itemValid && quest.PackageID != 0) + { + var questPackageItems = Global.DB2Mgr.GetQuestPackageItems(quest.PackageID); + if (questPackageItems != null) + { + foreach (var questPackageItem in questPackageItems) + { + if (questPackageItem.ItemID != packet.ItemChoiceID) + continue; + + if (_player.CanSelectQuestPackageItem(questPackageItem)) + { + itemValid = true; + break; + } + } + } + + if (!itemValid) + { + var questPackageItems1 = Global.DB2Mgr.GetQuestPackageItemsFallback(quest.PackageID); + if (questPackageItems1 != null) + { + foreach (var questPackageItem in questPackageItems1) + { + if (questPackageItem.ItemID != packet.ItemChoiceID) + continue; + + itemValid = true; + break; + } + } + } + } + + if (!itemValid) + { + Log.outError(LogFilter.Network, "Error in CMSG_QUESTGIVER_CHOOSE_REWARD: player {0} ({1}) tried to get reward item (Item Entry: {2}) wich is not a reward for quest {3} (possible packet-hacking detected)", GetPlayer().GetName(), GetPlayer().GetGUID().ToString(), packet.ItemChoiceID, packet.QuestID); + return; + } + } + + WorldObject obj = GetPlayer(); + if (!quest.HasFlag(QuestFlags.AutoComplete)) + { + obj = Global.ObjAccessor.GetObjectByTypeMask(GetPlayer(), packet.QuestGiverGUID, TypeMask.Unit | TypeMask.GameObject); + if (!obj || !obj.hasInvolvedQuest(packet.QuestID)) + return; + + // some kind of WPE protection + if (!GetPlayer().CanInteractWithQuestGiver(obj)) + return; + } + + if ((!GetPlayer().CanSeeStartQuest(quest) && GetPlayer().GetQuestStatus(packet.QuestID) == QuestStatus.None) || + (GetPlayer().GetQuestStatus(packet.QuestID) != QuestStatus.Complete && !quest.IsAutoComplete())) + { + Log.outError(LogFilter.Network, "Error in QuestStatus.Complete: player {0} ({1}) tried to complete quest {2}, but is not allowed to do so (possible packet-hacking or high latency)", + GetPlayer().GetName(), GetPlayer().GetGUID().ToString(), packet.QuestID); + return; + } + + if (GetPlayer().CanRewardQuest(quest, packet.ItemChoiceID, true)) + { + GetPlayer().RewardQuest(quest, packet.ItemChoiceID, obj); + + switch (obj.GetTypeId()) + { + case TypeId.Unit: + case TypeId.Player: + { + //For AutoSubmition was added plr case there as it almost same exclute AI script cases. + Creature creatureQGiver = obj.ToCreature(); + if (!creatureQGiver || !Global.ScriptMgr.OnQuestReward(GetPlayer(), creatureQGiver, quest, packet.ItemChoiceID)) + { + // Send next quest + Quest nextQuest = GetPlayer().GetNextQuest(packet.QuestGiverGUID, quest); + if (nextQuest != null) + { + // Only send the quest to the player if the conditions are met + if (GetPlayer().CanTakeQuest(nextQuest, false)) + { + if (nextQuest.IsAutoAccept() && GetPlayer().CanAddQuest(nextQuest, true)) + GetPlayer().AddQuestAndCheckCompletion(nextQuest, obj); + + GetPlayer().PlayerTalkClass.SendQuestGiverQuestDetails(nextQuest, packet.QuestGiverGUID, true); + } + } + + if (creatureQGiver) + creatureQGiver.GetAI().sQuestReward(GetPlayer(), quest, packet.ItemChoiceID); + } + break; + } + case TypeId.GameObject: + GameObject questGiver = obj.ToGameObject(); + if (!Global.ScriptMgr.OnQuestReward(GetPlayer(), questGiver, quest, packet.ItemChoiceID)) + { + // Send next quest + Quest nextQuest = GetPlayer().GetNextQuest(packet.QuestGiverGUID, quest); + if (nextQuest != null) + { + // Only send the quest to the player if the conditions are met + if (GetPlayer().CanTakeQuest(nextQuest, false)) + { + if (nextQuest.IsAutoAccept() && GetPlayer().CanAddQuest(nextQuest, true)) + GetPlayer().AddQuestAndCheckCompletion(nextQuest, obj); + + GetPlayer().PlayerTalkClass.SendQuestGiverQuestDetails(nextQuest, packet.QuestGiverGUID, true); + } + } + + questGiver.GetAI().QuestReward(GetPlayer(), quest, packet.ItemChoiceID); + } + break; + default: + break; + } + } + else + GetPlayer().PlayerTalkClass.SendQuestGiverOfferReward(quest, packet.QuestGiverGUID, true); + } + + [WorldPacketHandler(ClientOpcodes.QuestGiverRequestReward)] + void HandleQuestgiverRequestReward(QuestGiverRequestReward packet) + { + WorldObject obj = Global.ObjAccessor.GetObjectByTypeMask(GetPlayer(), packet.QuestGiverGUID, TypeMask.Unit | TypeMask.GameObject); + if (obj == null || !obj.hasInvolvedQuest(packet.QuestID)) + return; + + // some kind of WPE protection + if (!GetPlayer().CanInteractWithQuestGiver(obj)) + return; + + if (GetPlayer().CanCompleteQuest(packet.QuestID)) + GetPlayer().CompleteQuest(packet.QuestID); + + if (GetPlayer().GetQuestStatus(packet.QuestID) != QuestStatus.Complete) + return; + + Quest quest = Global.ObjectMgr.GetQuestTemplate(packet.QuestID); + if (quest != null) + GetPlayer().PlayerTalkClass.SendQuestGiverOfferReward(quest, packet.QuestGiverGUID, true); + } + + [WorldPacketHandler(ClientOpcodes.QuestLogRemoveQuest)] + void HandleQuestLogRemoveQuest(QuestLogRemoveQuest packet) + { + if (packet.Entry < SharedConst.MaxQuestLogSize) + { + uint questId = GetPlayer().GetQuestSlotQuestId(packet.Entry); + if (questId != 0) + { + if (!GetPlayer().TakeQuestSourceItem(questId, true)) + return; // can't un-equip some items, reject quest cancel + + Quest quest = Global.ObjectMgr.GetQuestTemplate(questId); + if (quest != null) + { + if (quest.HasSpecialFlag(QuestSpecialFlags.Timed)) + GetPlayer().RemoveTimedQuest(questId); + + if (quest.HasFlag(QuestFlags.Pvp)) + { + GetPlayer().pvpInfo.IsHostile = GetPlayer().pvpInfo.IsInHostileArea || GetPlayer().HasPvPForcingQuest(); + GetPlayer().UpdatePvPState(); + } + } + + GetPlayer().TakeQuestSourceItem(questId, true); // remove quest src item from player + GetPlayer().AbandonQuest(questId); // remove all quest items player received before abandoning quest. Note, this does not remove normal drop items that happen to be quest requirements. + GetPlayer().RemoveActiveQuest(questId); + GetPlayer().RemoveCriteriaTimer(CriteriaTimedTypes.Quest, questId); + + Log.outInfo(LogFilter.Network, "Player {0} abandoned quest {1}", GetPlayer().GetGUID().ToString(), questId); + + Global.ScriptMgr.OnQuestStatusChange(_player, questId); + } + + GetPlayer().SetQuestSlot(packet.Entry, 0); + + GetPlayer().UpdateCriteria(CriteriaTypes.QuestAbandoned, 1); + } + } + + [WorldPacketHandler(ClientOpcodes.QuestConfirmAccept)] + void HandleQuestConfirmAccept(QuestConfirmAccept packet) + { + Quest quest = Global.ObjectMgr.GetQuestTemplate(packet.QuestID); + if (quest != null) + { + if (!quest.HasFlag(QuestFlags.PartyAccept)) + return; + + Player originalPlayer = Global.ObjAccessor.FindPlayer(GetPlayer().GetDivider()); + if (originalPlayer == null) + return; + + if (!GetPlayer().IsInSameRaidWith(originalPlayer)) + return; + + if (!!originalPlayer.IsActiveQuest(packet.QuestID)) + return; + + if (!GetPlayer().CanTakeQuest(quest, true)) + return; + + if (GetPlayer().CanAddQuest(quest, true)) + { + GetPlayer().AddQuestAndCheckCompletion(quest, null); // NULL, this prevent DB script from duplicate running + + if (quest.SourceSpellID > 0) + _player.CastSpell(_player, quest.SourceSpellID, true); + } + } + + GetPlayer().SetDivider(ObjectGuid.Empty); + } + + [WorldPacketHandler(ClientOpcodes.QuestGiverCompleteQuest)] + void HandleQuestgiverCompleteQuest(QuestGiverCompleteQuest packet) + { + bool autoCompleteMode = packet.FromScript; // 0 - standart complete quest mode with npc, 1 - auto-complete mode + + Quest quest = Global.ObjectMgr.GetQuestTemplate(packet.QuestID); + if (quest == null) + return; + + if (autoCompleteMode && !quest.HasFlag(QuestFlags.AutoComplete)) + return; + + WorldObject obj; + if (autoCompleteMode) + obj = GetPlayer(); + else + obj = Global.ObjAccessor.GetObjectByTypeMask(GetPlayer(), packet.QuestGiverGUID, TypeMask.Unit | TypeMask.GameObject); + + if (!obj) + return; + + if (!autoCompleteMode) + { + if (!obj.hasInvolvedQuest(packet.QuestID)) + return; + + // some kind of WPE protection + if (!GetPlayer().CanInteractWithQuestGiver(obj)) + return; + } + else + { + // Do not allow completing quests on other players. + if (packet.QuestGiverGUID != GetPlayer().GetGUID()) + return; + } + + if (!GetPlayer().CanSeeStartQuest(quest) && GetPlayer().GetQuestStatus(packet.QuestID) == QuestStatus.None) + { + Log.outError(LogFilter.Network, "Possible hacking attempt: Player {0} ({1}) tried to complete quest [entry: {2}] without being in possession of the quest!", + GetPlayer().GetName(), GetPlayer().GetGUID().ToString(), packet.QuestID); + return; + } + Battleground bg = GetPlayer().GetBattleground(); + if (bg) + bg.HandleQuestComplete(packet.QuestID, GetPlayer()); + + if (GetPlayer().GetQuestStatus(packet.QuestID) != QuestStatus.Complete) + { + if (quest.IsRepeatable()) + GetPlayer().PlayerTalkClass.SendQuestGiverRequestItems(quest, packet.QuestGiverGUID, GetPlayer().CanCompleteRepeatableQuest(quest), false); + else + GetPlayer().PlayerTalkClass.SendQuestGiverRequestItems(quest, packet.QuestGiverGUID, GetPlayer().CanRewardQuest(quest, false), false); + } + else + { + if (quest.HasSpecialFlag(QuestSpecialFlags.Deliver)) // some items required + GetPlayer().PlayerTalkClass.SendQuestGiverRequestItems(quest, packet.QuestGiverGUID, GetPlayer().CanRewardQuest(quest, false), false); + else // no items required + GetPlayer().PlayerTalkClass.SendQuestGiverOfferReward(quest, packet.QuestGiverGUID, true); + } + } + + [WorldPacketHandler(ClientOpcodes.PushQuestToParty)] + void HandlePushQuestToParty(PushQuestToParty packet) + { + if (!GetPlayer().CanShareQuest(packet.QuestID)) + return; + + Quest quest = Global.ObjectMgr.GetQuestTemplate(packet.QuestID); + if (quest == null) + return; + + Player sender = GetPlayer(); + + Group group = sender.GetGroup(); + if (!group) + return; + for (GroupReference refe = group.GetFirstMember(); refe != null; refe = refe.next()) + { + Player receiver = refe.GetSource(); + + if (!receiver || receiver == sender) + continue; + + if (!receiver.SatisfyQuestStatus(quest, false)) + { + sender.SendPushToPartyResponse(receiver, QuestPushReason.OnQuest); + continue; + } + + if (receiver.GetQuestStatus(packet.QuestID) == QuestStatus.Complete) + { + sender.SendPushToPartyResponse(receiver, QuestPushReason.AlreadyDone); + continue; + } + + if (!receiver.CanTakeQuest(quest, false)) + { + sender.SendPushToPartyResponse(receiver, QuestPushReason.Invalid); + continue; + } + + if (!receiver.SatisfyQuestLog(false)) + { + sender.SendPushToPartyResponse(receiver, QuestPushReason.LogFull); + continue; + } + + if (!receiver.GetDivider().IsEmpty()) + { + sender.SendPushToPartyResponse(receiver, QuestPushReason.Busy); + continue; + } + + sender.SendPushToPartyResponse(receiver, QuestPushReason.Success); + + if (quest.IsAutoAccept() && receiver.CanAddQuest(quest, true) && receiver.CanTakeQuest(quest, true)) + receiver.AddQuestAndCheckCompletion(quest, sender); + + if ((quest.IsAutoComplete() && quest.IsRepeatable() && !quest.IsDailyOrWeekly()) || quest.HasFlag(QuestFlags.AutoComplete)) + receiver.PlayerTalkClass.SendQuestGiverRequestItems(quest, sender.GetGUID(), receiver.CanCompleteRepeatableQuest(quest), true); + else + { + receiver.SetDivider(sender.GetGUID()); + receiver.PlayerTalkClass.SendQuestGiverQuestDetails(quest, receiver.GetGUID(), true); + } + } + } + + [WorldPacketHandler(ClientOpcodes.QuestPushResult)] + void HandleQuestPushResult(QuestPushResult packet) + { + if (!GetPlayer().GetDivider().IsEmpty()) + { + if (_player.GetDivider() == packet.SenderGUID) + { + Player player = Global.ObjAccessor.FindPlayer(_player.GetDivider()); + if (player) + player.SendPushToPartyResponse(_player, packet.Result); + } + + _player.SetDivider(ObjectGuid.Empty); + } + } + + [WorldPacketHandler(ClientOpcodes.QuestGiverStatusMultipleQuery)] + void HandleQuestgiverStatusMultipleQuery(QuestGiverStatusMultipleQuery packet) + { + _player.SendQuestGiverStatusMultiple(); + } + + [WorldPacketHandler(ClientOpcodes.RequestWorldQuestUpdate)] + void HandleRequestWorldQuestUpdate(RequestWorldQuestUpdate packet) + { + WorldQuestUpdate response = new WorldQuestUpdate(); + + /// @todo: 7.x Has to be implemented + //response.WorldQuestUpdates.push_back(WorldPackets::Quest::WorldQuestUpdateInfo(lastUpdate, questID, timer, variableID, value)); + + SendPacket(response); + } + } +} diff --git a/Game/Handlers/RAFHandler.cs b/Game/Handlers/RAFHandler.cs new file mode 100644 index 000000000..66706f1a0 --- /dev/null +++ b/Game/Handlers/RAFHandler.cs @@ -0,0 +1,86 @@ +/* + * Copyright (C) 2012-2017 CypherCore + * + * 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, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +using Framework.Constants; +using Game.Entities; +using Game.Network; +using Game.Network.Packets; + +namespace Game +{ + public partial class WorldSession + { + [WorldPacketHandler(ClientOpcodes.GrantLevel)] + void HandleGrantLevel(GrantLevel grantLevel) + { + Player target = Global.ObjAccessor.GetPlayer(GetPlayer(), grantLevel.Target); + + // check cheating + byte levels = GetPlayer().GetGrantableLevels(); + ReferAFriendError error = 0; + if (!target) + error = ReferAFriendError.NoTarget; + if (levels == 0) + error = ReferAFriendError.InsufficientGrantableLevels; + else if (GetRecruiterId() != target.GetSession().GetAccountId()) + error = ReferAFriendError.NotReferredBy; + else if (target.GetTeamId() != GetPlayer().GetTeamId()) + error = ReferAFriendError.DifferentFaction; + else if (target.getLevel() >= GetPlayer().getLevel()) + error = ReferAFriendError.TargetTooHigh; + else if (target.getLevel() >= WorldConfig.GetIntValue(WorldCfg.MaxRecruitAFriendBonusPlayerLevel)) + error = ReferAFriendError.GrantLevelMaxI; + else if (target.GetGroup() != GetPlayer().GetGroup()) + error = ReferAFriendError.NotInGroup; + else if (target.getLevel() >= Global.ObjectMgr.GetMaxLevelForExpansion(target.GetSession().GetExpansion())) + error = ReferAFriendError.InsufExpanLvl; + + if (error != 0) + { + ReferAFriendFailure failure = new ReferAFriendFailure(); + failure.Reason = error; + if (error == ReferAFriendError.NotInGroup) + failure.Str = target.GetName(); + + SendPacket(failure); + return; + } + + ProposeLevelGrant proposeLevelGrant = new ProposeLevelGrant(); + proposeLevelGrant.Sender = GetPlayer().GetGUID(); + target.SendPacket(proposeLevelGrant); + } + + [WorldPacketHandler(ClientOpcodes.AcceptLevelGrant)] + void HandleAcceptGrantLevel(AcceptLevelGrant acceptLevelGrant) + { + Player other = Global.ObjAccessor.GetPlayer(GetPlayer(), acceptLevelGrant.Granter); + if (!(other && other.GetSession() != null)) + return; + + if (GetAccountId() != other.GetSession().GetRecruiterId()) + return; + + if (other.GetGrantableLevels() != 0) + other.SetGrantableLevels(other.GetGrantableLevels() - 1); + else + return; + + GetPlayer().GiveLevel(GetPlayer().getLevel() + 1); + } + } +} diff --git a/Game/Handlers/ScenarioHandler.cs b/Game/Handlers/ScenarioHandler.cs new file mode 100644 index 000000000..4dddb5b31 --- /dev/null +++ b/Game/Handlers/ScenarioHandler.cs @@ -0,0 +1,52 @@ +/* + * Copyright (C) 2012-2017 CypherCore + * + * 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, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +using Framework.Constants; +using Game.Network; +using Game.Network.Packets; +using System.Collections.Generic; + +namespace Game +{ + public partial class WorldSession + { + [WorldPacketHandler(ClientOpcodes.QueryScenarioPoi)] + void HandleQueryScenarioPOI(QueryScenarioPOI queryScenarioPOI) + { + ScenarioPOIs response = new ScenarioPOIs(); + + // Read criteria tree ids and add the in a unordered_set so we don't send POIs for the same criteria tree multiple times + List criteriaTreeIds = new List(); + for (int i = 0; i < queryScenarioPOI.MissingScenarioPOIs.Count; ++i) + criteriaTreeIds.Add(queryScenarioPOI.MissingScenarioPOIs[i]); // CriteriaTreeID + + foreach (int criteriaTreeId in criteriaTreeIds) + { + var poiVector = Global.ScenarioMgr.GetScenarioPOIs((uint)criteriaTreeId); + if (poiVector != null) + { + ScenarioPOIData scenarioPOIData = new ScenarioPOIData(); + scenarioPOIData.CriteriaTreeID = criteriaTreeId; + scenarioPOIData.ScenarioPOIs = poiVector; + response.ScenarioPOIDataStats.Add(scenarioPOIData); + } + } + + SendPacket(response); + } + } +} diff --git a/Game/Handlers/SceneHandler.cs b/Game/Handlers/SceneHandler.cs new file mode 100644 index 000000000..a1aaa0d5f --- /dev/null +++ b/Game/Handlers/SceneHandler.cs @@ -0,0 +1,49 @@ +/* + * Copyright (C) 2012-2017 CypherCore + * + * 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, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +using Framework.Constants; +using Game.Network; +using Game.Network.Packets; + +namespace Game +{ + public partial class WorldSession + { + [WorldPacketHandler(ClientOpcodes.SceneTriggerEvent)] + void HandleSceneTriggerEvent(SceneTriggerEvent sceneTriggerEvent) + { + Log.outDebug(LogFilter.Scenes, "HandleSceneTriggerEvent: SceneInstanceID: {0} Event: {1}", sceneTriggerEvent.SceneInstanceID, sceneTriggerEvent._Event); + + GetPlayer().GetSceneMgr().OnSceneTrigger(sceneTriggerEvent.SceneInstanceID, sceneTriggerEvent._Event); + } + + void HandleScenePlaybackComplete(ScenePlaybackComplete scenePlaybackComplete) + { + Log.outDebug(LogFilter.Scenes, "HandleScenePlaybackComplete: SceneInstanceID: {0}", scenePlaybackComplete.SceneInstanceID); + + GetPlayer().GetSceneMgr().OnSceneComplete(scenePlaybackComplete.SceneInstanceID); + } + + void HandleScenePlaybackCanceled(ScenePlaybackCanceled scenePlaybackCanceled) + { + Log.outDebug(LogFilter.Scenes, "HandleScenePlaybackCanceled: SceneInstanceID: {0}", scenePlaybackCanceled.SceneInstanceID); + + GetPlayer().GetSceneMgr().OnSceneCancel(scenePlaybackCanceled.SceneInstanceID); + } + + } +} diff --git a/Game/Handlers/SkillHandler.cs b/Game/Handlers/SkillHandler.cs new file mode 100644 index 000000000..f0517b472 --- /dev/null +++ b/Game/Handlers/SkillHandler.cs @@ -0,0 +1,95 @@ +/* + * Copyright (C) 2012-2017 CypherCore + * + * 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, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +using Framework.Constants; +using Game.DataStorage; +using Game.Entities; +using Game.Network; +using Game.Network.Packets; +using System; + +namespace Game +{ + public partial class WorldSession + { + [WorldPacketHandler(ClientOpcodes.LearnTalents)] + void HandleLearnTalents(LearnTalents packet) + { + LearnTalentsFailed learnTalentsFailed = new LearnTalentsFailed(); + bool anythingLearned = false; + foreach (uint talentId in packet.Talents) + { + TalentLearnResult result = _player.LearnTalent(talentId, ref learnTalentsFailed.SpellID); + if (result != 0) + { + if (learnTalentsFailed.Reason == 0) + learnTalentsFailed.Reason = (uint)result; + + learnTalentsFailed.Talents.Add((ushort)talentId); + } + else + anythingLearned = true; + } + + if (learnTalentsFailed.Reason != 0) + SendPacket(learnTalentsFailed); + + if (anythingLearned) + GetPlayer().SendTalentsInfoData(); + } + + [WorldPacketHandler(ClientOpcodes.ConfirmRespecWipe)] + void HandleConfirmRespecWipe(ConfirmRespecWipe confirmRespecWipe) + { + Creature unit = GetPlayer().GetNPCIfCanInteractWith(confirmRespecWipe.RespecMaster, NPCFlags.Trainer); + if (unit == null) + { + Log.outDebug(LogFilter.Network, "WORLD: HandleTalentWipeConfirm - {0} not found or you can't interact with him.", confirmRespecWipe.RespecMaster.ToString()); + return; + } + + if (confirmRespecWipe.RespecType != SpecResetType.Talents) + { + Log.outDebug(LogFilter.Network, "WORLD: HandleConfirmRespecWipe - reset type {0} is not implemented.", confirmRespecWipe.RespecType); + return; + } + + // remove fake death + if (GetPlayer().HasUnitState(UnitState.Died)) + GetPlayer().RemoveAurasByType(AuraType.FeignDeath); + + if (!GetPlayer().ResetTalents()) + { + GetPlayer().SendRespecWipeConfirm(ObjectGuid.Empty, 0); + return; + } + + GetPlayer().SendTalentsInfoData(); + unit.CastSpell(GetPlayer(), 14867, true); //spell: "Untalent Visual Effect" + } + + [WorldPacketHandler(ClientOpcodes.UnlearnSkill)] + void HandleUnlearnSkill(UnlearnSkill packet) + { + SkillRaceClassInfoRecord rcEntry = Global.DB2Mgr.GetSkillRaceClassInfo(packet.SkillLine, GetPlayer().GetRace(), GetPlayer().GetClass()); + if (rcEntry == null || !rcEntry.Flags.HasAnyFlag(SkillRaceClassInfoFlags.Unlearnable)) + return; + + GetPlayer().SetSkill(packet.SkillLine, 0, 0, 0); + } + } +} diff --git a/Game/Handlers/SocialHandler.cs b/Game/Handlers/SocialHandler.cs new file mode 100644 index 000000000..9ec9f79b0 --- /dev/null +++ b/Game/Handlers/SocialHandler.cs @@ -0,0 +1,355 @@ +/* + * Copyright (C) 2012-2017 CypherCore + * + * 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, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +using Framework.Constants; +using Framework.Database; +using Game.DataStorage; +using Game.Entities; +using Game.Guilds; +using Game.Network; +using Game.Network.Packets; +using System; +using System.Collections.Generic; + +namespace Game +{ + public partial class WorldSession + { + [WorldPacketHandler(ClientOpcodes.Who)] + void HandleWho(WhoRequestPkt whoRequest) + { + WhoRequest request = whoRequest.Request; + + // zones count, client limit = 10 (2.0.10) + // can't be received from real client or broken packet + if (whoRequest.Areas.Count > 10) + return; + + // user entered strings count, client limit=4 (checked on 2.0.10) + // can't be received from real client or broken packet + if (request.Words.Count > 4) + return; + + /// @todo: handle following packet values + /// VirtualRealmNames + /// ShowEnemies + /// ShowArenaPlayers + /// ExactName + /// ServerInfo + + request.Words.ForEach(p => p = p.ToLower()); + + request.Name.ToLower(); + request.Guild.ToLower(); + + // client send in case not set max level value 100 but we support 255 max level, + // update it to show GMs with characters after 100 level + if (whoRequest.Request.MaxLevel >= 100) + whoRequest.Request.MaxLevel = 255; + + var team = GetPlayer().GetTeam(); + + uint gmLevelInWhoList = WorldConfig.GetUIntValue(WorldCfg.GmLevelInWhoList); + + WhoResponsePkt response = new WhoResponsePkt(); + foreach (var target in Global.ObjAccessor.GetPlayers()) + { + // player can see member of other team only if CONFIG_ALLOW_TWO_SIDE_WHO_LIST + if (target.GetTeam() != team && !HasPermission(RBACPermissions.TwoSideWhoList)) + continue; + + // player can see MODERATOR, GAME MASTER, ADMINISTRATOR only if CONFIG_GM_IN_WHO_LIST + if (target.GetSession().GetSecurity() > (AccountTypes)gmLevelInWhoList && !HasPermission(RBACPermissions.WhoSeeAllSecLevels)) + continue; + + // do not process players which are not in world + if (!target.IsInWorld) + continue; + + // check if target is globally visible for player + if (!target.IsVisibleGloballyFor(GetPlayer())) + continue; + + // check if target's level is in level range + uint lvl = target.getLevel(); + if (lvl < request.MinLevel || lvl > request.MaxLevel) + continue; + + // check if class matches classmask + int class_ = (byte)target.GetClass(); + if (!Convert.ToBoolean(request.ClassFilter & (1 << class_))) + continue; + + // check if race matches racemask + int race = (int)target.GetRace(); + if (!Convert.ToBoolean(request.RaceFilter & (1 << race))) + continue; + + if (!whoRequest.Areas.Empty()) + { + if (whoRequest.Areas.Contains((int)target.GetZoneId())) + continue; + } + + string wTargetName = target.GetName().ToLower(); + if (!string.IsNullOrEmpty(request.Name) && !wTargetName.Equals(request.Name)) + continue; + + string wTargetGuildName = ""; + Guild targetGuild = target.GetGuild(); + if (targetGuild) + wTargetGuildName = targetGuild.GetName().ToLower(); + + if (!string.IsNullOrEmpty(request.Guild) && !wTargetGuildName.Equals(request.Guild)) + continue; + + if (!request.Words.Empty()) + { + string aname = ""; + AreaTableRecord areaEntry = CliDB.AreaTableStorage.LookupByKey(target.GetZoneId()); + if (areaEntry != null) + aname = areaEntry.AreaName[GetSessionDbcLocale()].ToLower(); + + bool show = false; + for (int i = 0; i < request.Words.Count; ++i) + { + if (!string.IsNullOrEmpty(request.Words[i])) + { + if (wTargetName.Equals(request.Words[i]) || + wTargetGuildName.Equals(request.Words[i]) || + aname.Equals(request.Words[i])) + { + show = true; + break; + } + } + } + + if (!show) + continue; + } + + WhoEntry whoEntry = new WhoEntry(); + if (!whoEntry.PlayerData.Initialize(target.GetGUID(), target)) + continue; + + if (targetGuild) + { + whoEntry.GuildGUID = targetGuild.GetGUID(); + whoEntry.GuildVirtualRealmAddress = Global.WorldMgr.GetVirtualRealmAddress(); + whoEntry.GuildName = targetGuild.GetName(); + } + + whoEntry.AreaID = (int)target.GetZoneId(); + whoEntry.IsGM = target.IsGameMaster(); + + response.Response.Add(whoEntry); + + // 50 is maximum player count sent to client + if (response.Response.Count >= 50) + break; + } + + SendPacket(response); + } + + [WorldPacketHandler(ClientOpcodes.WhoIs)] + void HandleWhoIs(WhoIsRequest packet) + { + if (!HasPermission(RBACPermissions.OpcodeWhois)) + { + SendNotification(CypherStrings.YouNotHavePermission); + return; + } + + if (!ObjectManager.NormalizePlayerName(ref packet.CharName)) + { + SendNotification(CypherStrings.NeedCharacterName); + return; + } + + Player player = Global.ObjAccessor.FindPlayerByName(packet.CharName); + if (!player) + { + SendNotification(CypherStrings.PlayerNotExistOrOffline, packet.CharName); + return; + } + + PreparedStatement stmt = DB.Login.GetPreparedStatement(LoginStatements.SEL_ACCOUNT_WHOIS); + stmt.AddValue(0, player.GetSession().GetAccountId()); + + SQLResult result = DB.Login.Query(stmt); + if (result.IsEmpty()) + { + SendNotification(CypherStrings.AccountForPlayerNotFound, packet.CharName); + return; + } + + string acc = result.Read(0); + if (string.IsNullOrEmpty(acc)) + acc = "Unknown"; + + string email = result.Read(1); + if (string.IsNullOrEmpty(email)) + email = "Unknown"; + + string lastip = result.Read(2); + if (string.IsNullOrEmpty(lastip)) + lastip = "Unknown"; + + WhoIsResponse response = new WhoIsResponse(); + response.AccountName = packet.CharName + "'s " + "account is " + acc + ", e-mail: " + email + ", last ip: " + lastip; + SendPacket(response); + } + + [WorldPacketHandler(ClientOpcodes.SendContactList)] + void HandleContactList(SendContactList packet) + { + GetPlayer().GetSocial().SendSocialList(GetPlayer(), packet.Flags); + } + + [WorldPacketHandler(ClientOpcodes.AddFriend)] + void HandleAddFriend(AddFriend packet) + { + if (!ObjectManager.NormalizePlayerName(ref packet.Name)) + return; + + PreparedStatement stmt = DB.Characters.GetPreparedStatement(CharStatements.SEL_GUID_RACE_ACC_BY_NAME); + stmt.AddValue(0, packet.Name); + + _queryProcessor.AddQuery(DB.Characters.AsyncQuery(stmt).WithCallback(HandleAddFriendCallBack, packet.Notes)); + } + + void HandleAddFriendCallBack(string friendNote, SQLResult result) + { + if (!GetPlayer()) + return; + + ObjectGuid friendGuid = ObjectGuid.Empty; + FriendsResult friendResult = FriendsResult.NotFound; + + if (!result.IsEmpty()) + { + ulong lowGuid = result.Read(0); + if (lowGuid != 0) + { + friendGuid = ObjectGuid.Create(HighGuid.Player, lowGuid); + Team team = Player.TeamForRace((Race)result.Read(1)); + uint friendAccountId = result.Read(2); + + if (HasPermission(RBACPermissions.AllowGmFriend) || Global.AccountMgr.IsPlayerAccount(Global.AccountMgr.GetSecurity(friendAccountId, (int)Global.WorldMgr.GetRealm().Id.Realm))) + { + if (friendGuid == GetPlayer().GetGUID()) + friendResult = FriendsResult.Self; + else if (GetPlayer().GetTeam() != team && !HasPermission(RBACPermissions.TwoSideAddFriend)) + friendResult = FriendsResult.Enemy; + else if (GetPlayer().GetSocial().HasFriend(friendGuid)) + friendResult = FriendsResult.Already; + else + { + Player playerFriend = Global.ObjAccessor.FindPlayer(friendGuid); + if (playerFriend && playerFriend.IsVisibleGloballyFor(GetPlayer())) + friendResult = FriendsResult.AddedOnline; + else + friendResult = FriendsResult.AddedOffline; + + if (GetPlayer().GetSocial().AddToSocialList(friendGuid, SocialFlag.Friend)) + GetPlayer().GetSocial().SetFriendNote(friendGuid, friendNote); + else + friendResult = FriendsResult.ListFull; + } + } + } + } + + Global.SocialMgr.SendFriendStatus(GetPlayer(), friendResult, friendGuid); + } + + [WorldPacketHandler(ClientOpcodes.DelFriend)] + void HandleDelFriend(DelFriend packet) + { + /// @todo: handle VirtualRealmAddress + GetPlayer().GetSocial().RemoveFromSocialList(packet.Player.Guid, SocialFlag.Friend); + + Global.SocialMgr.SendFriendStatus(GetPlayer(), FriendsResult.Removed, packet.Player.Guid); + } + + [WorldPacketHandler(ClientOpcodes.AddIgnore)] + void HandleAddIgnore(AddIgnore packet) + { + if (!ObjectManager.NormalizePlayerName(ref packet.Name)) + return; + + PreparedStatement stmt = DB.Characters.GetPreparedStatement(CharStatements.SEL_GUID_BY_NAME); + stmt.AddValue(0, packet.Name); + + _queryProcessor.AddQuery(DB.Characters.AsyncQuery(stmt).WithCallback(HandleAddIgnoreCallBack)); + } + + void HandleAddIgnoreCallBack(SQLResult result) + { + if (!GetPlayer()) + return; + + ObjectGuid IgnoreGuid = ObjectGuid.Empty; + FriendsResult ignoreResult = FriendsResult.IgnoreNotFound; + + if (result.IsEmpty()) + { + ulong lowGuid = result.Read(0); + if (lowGuid != 0) + { + IgnoreGuid = ObjectGuid.Create(HighGuid.Player, lowGuid); + + if (IgnoreGuid == GetPlayer().GetGUID()) //not add yourself + ignoreResult = FriendsResult.IgnoreSelf; + else if (GetPlayer().GetSocial().HasIgnore(IgnoreGuid)) + ignoreResult = FriendsResult.IgnoreAlready; + else + { + ignoreResult = FriendsResult.IgnoreAdded; + + // ignore list full + if (!GetPlayer().GetSocial().AddToSocialList(IgnoreGuid, SocialFlag.Ignored)) + ignoreResult = FriendsResult.IgnoreFull; + } + } + } + + Global.SocialMgr.SendFriendStatus(GetPlayer(), ignoreResult, IgnoreGuid); + } + + [WorldPacketHandler(ClientOpcodes.DelIgnore)] + void HandleDelIgnore(DelIgnore packet) + { + /// @todo: handle VirtualRealmAddress + Log.outDebug(LogFilter.Network, "WorldSession.HandleDelIgnoreOpcode: {0}", packet.Player.Guid.ToString()); + + GetPlayer().GetSocial().RemoveFromSocialList(packet.Player.Guid, SocialFlag.Ignored); + + Global.SocialMgr.SendFriendStatus(GetPlayer(), FriendsResult.IgnoreRemoved, packet.Player.Guid); + } + + [WorldPacketHandler(ClientOpcodes.SetContactNotes)] + void HandleSetContactNotes(SetContactNotes packet) + { + /// @todo: handle VirtualRealmAddress + Log.outDebug(LogFilter.Network, "WorldSession.HandleSetContactNotesOpcode: Contact: {0}, Notes: {1}", packet.Player.Guid.ToString(), packet.Notes); + GetPlayer().GetSocial().SetFriendNote(packet.Player.Guid, packet.Notes); + } + } +} diff --git a/Game/Handlers/SpellHandler.cs b/Game/Handlers/SpellHandler.cs new file mode 100644 index 000000000..e1d721685 --- /dev/null +++ b/Game/Handlers/SpellHandler.cs @@ -0,0 +1,616 @@ +/* + * Copyright (C) 2012-2017 CypherCore + * + * 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, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +using Framework.Constants; +using Framework.Database; +using Game.DataStorage; +using Game.Entities; +using Game.Guilds; +using Game.Network; +using Game.Network.Packets; +using Game.Spells; +using System; +using System.Collections.Generic; +using System.Linq; + +namespace Game +{ + public partial class WorldSession + { + [WorldPacketHandler(ClientOpcodes.UseItem)] + void HandleUseItem(UseItem packet) + { + Player user = GetPlayer(); + + // ignore for remote control state + if (user.m_unitMovedByMe != user) + return; + + Item item = user.GetUseableItemByPos(packet.PackSlot, packet.Slot); + if (item == null) + { + user.SendEquipError(InventoryResult.ItemNotFound); + return; + } + + if (item.GetGUID() != packet.CastItem) + { + user.SendEquipError(InventoryResult.ItemNotFound); + return; + } + + ItemTemplate proto = item.GetTemplate(); + if (proto == null) + { + user.SendEquipError(InventoryResult.ItemNotFound, item); + return; + } + + // some item classes can be used only in equipped state + if (proto.GetInventoryType() != InventoryType.NonEquip && !item.IsEquipped()) + { + user.SendEquipError(InventoryResult.ItemNotFound, item); + return; + } + + InventoryResult msg = user.CanUseItem(item); + if (msg != InventoryResult.Ok) + { + user.SendEquipError(msg, item); + return; + } + + // only allow conjured consumable, bandage, poisons (all should have the 2^21 item flag set in DB) + if (proto.GetClass() == ItemClass.Consumable && !proto.GetFlags().HasAnyFlag(ItemFlags.IgnoreDefaultArenaRestrictions) && user.InArena()) + { + user.SendEquipError(InventoryResult.NotDuringArenaMatch, item); + return; + } + + // don't allow items banned in arena + if (proto.GetFlags().HasAnyFlag(ItemFlags.NotUseableInArena) && user.InArena()) + { + user.SendEquipError(InventoryResult.NotDuringArenaMatch, item); + return; + } + + if (user.IsInCombat()) + { + for (int i = 0; i < proto.Effects.Count; ++i) + { + SpellInfo spellInfo = Global.SpellMgr.GetSpellInfo(proto.Effects[i].SpellID); + if (spellInfo != null) + { + if (!spellInfo.CanBeUsedInCombat()) + { + user.SendEquipError(InventoryResult.NotInCombat, item); + return; + } + } + } + } + + // check also BIND_WHEN_PICKED_UP and BIND_QUEST_ITEM for .additem or .additemset case by GM (not binded at adding to inventory) + if (item.GetBonding() == ItemBondingType.OnUse || item.GetBonding() == ItemBondingType.OnAcquire || item.GetBonding() == ItemBondingType.Quest) + { + if (!item.IsSoulBound()) + { + item.SetState(ItemUpdateState.Changed, user); + item.SetBinding(true); + GetCollectionMgr().AddItemAppearance(item); + } + } + + SpellCastTargets targets = new SpellCastTargets(user, packet.Cast); + + // Note: If script stop casting it must send appropriate data to client to prevent stuck item in gray state. + if (!Global.ScriptMgr.OnItemUse(user, item, targets, packet.Cast.CastID)) + { + // no script or script not process request by self + user.CastItemUseSpell(item, targets, packet.Cast.CastID, packet.Cast.Misc); + } + } + + [WorldPacketHandler(ClientOpcodes.OpenItem)] + void HandleOpenItem(OpenItem packet) + { + Player player = GetPlayer(); + + // ignore for remote control state + if (player.m_unitMovedByMe != player) + return; + + Item item = player.GetItemByPos(packet.Slot, packet.PackSlot); + if (!item) + { + player.SendEquipError(InventoryResult.ItemNotFound); + return; + } + + ItemTemplate proto = item.GetTemplate(); + if (proto == null) + { + player.SendEquipError(InventoryResult.ItemNotFound, item); + return; + } + + // Verify that the bag is an actual bag or wrapped item that can be used "normally" + if (!proto.GetFlags().HasAnyFlag(ItemFlags.HasLoot) && !item.HasFlag(ItemFields.Flags, ItemFieldFlags.Wrapped)) + { + player.SendEquipError(InventoryResult.ClientLockedOut, item); + Log.outError(LogFilter.Network, "Possible hacking attempt: Player {0} [guid: {1}] tried to open item [guid: {2}, entry: {3}] which is not openable!", + player.GetName(), player.GetGUID().ToString(), item.GetGUID().ToString(), proto.GetId()); + return; + } + + // locked item + uint lockId = proto.GetLockID(); + if (lockId != 0) + { + LockRecord lockInfo = CliDB.LockStorage.LookupByKey(lockId); + if (lockInfo == null) + { + player.SendEquipError(InventoryResult.ItemLocked, item); + Log.outError(LogFilter.Network, "WORLD:OpenItem: item [guid = {0}] has an unknown lockId: {1}!", item.GetGUID().ToString(), lockId); + return; + } + + // was not unlocked yet + if (item.IsLocked()) + { + player.SendEquipError(InventoryResult.ItemLocked, item); + return; + } + } + + if (item.HasFlag(ItemFields.Flags, ItemFieldFlags.Wrapped))// wrapped? + { + PreparedStatement stmt = DB.Characters.GetPreparedStatement(CharStatements.SEL_CHARACTER_GIFT_BY_ITEM); + stmt.AddValue(0, item.GetGUID().GetCounter()); + SQLResult result = DB.Characters.Query(stmt); + + if (!result.IsEmpty()) + { + uint entry = result.Read(0); + uint flags = result.Read(1); + + item.SetUInt64Value(ItemFields.GiftCreator, 0); + item.SetEntry(entry); + item.SetUInt32Value(ItemFields.Flags, flags); + item.SetState(ItemUpdateState.Changed, player); + } + else + { + Log.outError(LogFilter.Network, "Wrapped item {0} don't have record in character_gifts table and will deleted", item.GetGUID().ToString()); + player.DestroyItem(item.GetBagSlot(), item.GetSlot(), true); + return; + } + + stmt = DB.Characters.GetPreparedStatement(CharStatements.DEL_GIFT); + stmt.AddValue(0, item.GetGUID().GetCounter()); + DB.Characters.Execute(stmt); + } + else + player.SendLoot(item.GetGUID(), LootType.Corpse); + } + + [WorldPacketHandler(ClientOpcodes.GameObjUse)] + void HandleGameObjectUse(GameObjUse packet) + { + GameObject obj = GetPlayer().GetGameObjectIfCanInteractWith(packet.Guid); + if (obj) + { + // ignore for remote control state + if (GetPlayer().m_unitMovedByMe != GetPlayer()) + if (!(GetPlayer().IsOnVehicle(GetPlayer().m_unitMovedByMe) || GetPlayer().IsMounted()) && !obj.GetGoInfo().IsUsableMounted()) + return; + + obj.Use(GetPlayer()); + } + } + + [WorldPacketHandler(ClientOpcodes.GameObjReportUse)] + void HandleGameobjectReportUse(GameObjReportUse packet) + { + // ignore for remote control state + if (GetPlayer().m_unitMovedByMe != GetPlayer()) + return; + + GameObject go = GetPlayer().GetGameObjectIfCanInteractWith(packet.Guid); + if (go) + { + if (go.GetAI().GossipHello(GetPlayer(), false)) + return; + + GetPlayer().UpdateCriteria(CriteriaTypes.UseGameobject, go.GetEntry()); + } + } + + [WorldPacketHandler(ClientOpcodes.CastSpell, Processing = PacketProcessing.ThreadSafe)] + void HandleCastSpell(CastSpell cast) + { + // ignore for remote control state (for player case) + Unit mover = GetPlayer().m_unitMovedByMe; + if (mover != GetPlayer() && mover.IsTypeId(TypeId.Player)) + return; + + SpellInfo spellInfo = Global.SpellMgr.GetSpellInfo(cast.Cast.SpellID); + if (spellInfo == null) + { + Log.outError(LogFilter.Network, "WORLD: unknown spell id {0}", cast.Cast.SpellID); + return; + } + + if (spellInfo.IsPassive()) + return; + + Unit caster = mover; + if (caster.IsTypeId(TypeId.Unit) && !caster.ToCreature().HasSpell(spellInfo.Id)) + { + // If the vehicle creature does not have the spell but it allows the passenger to cast own spells + // change caster to player and let him cast + if (!GetPlayer().IsOnVehicle(caster) || spellInfo.CheckVehicle(GetPlayer()) != SpellCastResult.SpellCastOk) + return; + + caster = GetPlayer(); + } + + // check known spell or raid marker spell (which not requires player to know it) + if (caster.IsTypeId(TypeId.Player) && !caster.ToPlayer().HasActiveSpell(spellInfo.Id) && !spellInfo.HasEffect(SpellEffectName.ChangeRaidMarker) && !spellInfo.HasAttribute(SpellAttr8.RaidMarker)) + return; + + // Check possible spell cast overrides + spellInfo = caster.GetCastSpellInfo(spellInfo); + + // Client is resending autoshot cast opcode when other spell is casted during shoot rotation + // Skip it to prevent "interrupt" message + if (spellInfo.IsAutoRepeatRangedSpell() && GetPlayer().GetCurrentSpell(CurrentSpellTypes.AutoRepeat) != null + && GetPlayer().GetCurrentSpell(CurrentSpellTypes.AutoRepeat).m_spellInfo == spellInfo) + return; + + // can't use our own spells when we're in possession of another unit, + if (GetPlayer().isPossessing()) + return; + + // client provided targets + SpellCastTargets targets = new SpellCastTargets(caster, cast.Cast); + + // auto-selection buff level base at target level (in spellInfo) + if (targets.GetUnitTarget() != null) + { + SpellInfo actualSpellInfo = spellInfo.GetAuraRankForLevel(targets.GetUnitTarget().getLevel()); + + // if rank not found then function return NULL but in explicit cast case original spell can be casted and later failed with appropriate error message + if (actualSpellInfo != null) + spellInfo = actualSpellInfo; + } + + if (cast.Cast.MoveUpdate.HasValue) + HandleMovementOpcode(ClientOpcodes.MoveStop, cast.Cast.MoveUpdate.Value); + + Spell spell = new Spell(caster, spellInfo, TriggerCastFlags.None, ObjectGuid.Empty, false); + + SpellPrepare spellPrepare = new SpellPrepare(); + spellPrepare.ClientCastID = cast.Cast.CastID; + spellPrepare.ServerCastID = spell.m_castId; + SendPacket(spellPrepare); + + spell.m_fromClient = true; + spell.m_misc.Data0 = cast.Cast.Misc[0]; + spell.m_misc.Data1 = cast.Cast.Misc[1]; + spell.prepare(targets); + } + + [WorldPacketHandler(ClientOpcodes.CancelCast, Processing = PacketProcessing.ThreadSafe)] + void HandleCancelCast(CancelCast packet) + { + if (GetPlayer().IsNonMeleeSpellCast(false)) + GetPlayer().InterruptNonMeleeSpells(false, packet.SpellID, false); + } + + [WorldPacketHandler(ClientOpcodes.CancelAura)] + void HandleCancelAura(CancelAura cancelAura) + { + SpellInfo spellInfo = Global.SpellMgr.GetSpellInfo(cancelAura.SpellID); + if (spellInfo == null) + return; + + // not allow remove spells with attr SPELL_ATTR0_CANT_CANCEL + if (spellInfo.HasAttribute(SpellAttr0.CantCancel)) + return; + + // channeled spell case (it currently casted then) + if (spellInfo.IsChanneled()) + { + Spell curSpell = GetPlayer().GetCurrentSpell(CurrentSpellTypes.Channeled); + if (curSpell != null) + if (curSpell.GetSpellInfo().Id == cancelAura.SpellID) + GetPlayer().InterruptSpell(CurrentSpellTypes.Channeled); + return; + } + + // non channeled case: + // don't allow remove non positive spells + // don't allow cancelling passive auras (some of them are visible) + if (!spellInfo.IsPositive() || spellInfo.IsPassive()) + return; + + GetPlayer().RemoveOwnedAura(cancelAura.SpellID, cancelAura.CasterGUID, 0, AuraRemoveMode.Cancel); + } + + [WorldPacketHandler(ClientOpcodes.CancelGrowthAura)] + void HandleCancelGrowthAura(CancelGrowthAura cancelGrowthAura) + { + GetPlayer().RemoveAurasByType(AuraType.ModScale, aurApp => + { + SpellInfo spellInfo = aurApp.GetBase().GetSpellInfo(); + return !spellInfo.HasAttribute(SpellAttr0.CantCancel) && spellInfo.IsPositive() && !spellInfo.IsPassive(); + }); + } + + [WorldPacketHandler(ClientOpcodes.CancelMountAura)] + void HandleCancelMountAura(CancelMountAura packet) + { + GetPlayer().RemoveAurasByType(AuraType.Mounted, aurApp => + { + SpellInfo spellInfo = aurApp.GetBase().GetSpellInfo(); + return !spellInfo.HasAttribute(SpellAttr0.CantCancel) && spellInfo.IsPositive() && !spellInfo.IsPassive(); + }); + } + + [WorldPacketHandler(ClientOpcodes.PetCancelAura)] + void HandlePetCancelAura(PetCancelAura packet) + { + SpellInfo spellInfo = Global.SpellMgr.GetSpellInfo(packet.SpellID); + if (spellInfo == null) + { + Log.outError(LogFilter.Network, "WORLD: unknown PET spell id {0}", packet.SpellID); + return; + } + + Creature pet= ObjectAccessor.GetCreatureOrPetOrVehicle(_player, packet.PetGUID); + if (pet == null) + { + Log.outError(LogFilter.Network, "HandlePetCancelAura: Attempt to cancel an aura for non-existant {0} by player '{1}'", packet.PetGUID.ToString(), GetPlayer().GetName()); + return; + } + + if (pet != GetPlayer().GetGuardianPet() && pet != GetPlayer().GetCharm()) + { + Log.outError(LogFilter.Network, "HandlePetCancelAura: {0} is not a pet of player '{1}'", packet.PetGUID.ToString(), GetPlayer().GetName()); + return; + } + + if (!pet.IsAlive()) + { + pet.SendPetActionFeedback(packet.SpellID, ActionFeedback.PetDead); + return; + } + + pet.RemoveOwnedAura(packet.SpellID, ObjectGuid.Empty, 0, AuraRemoveMode.Cancel); + } + + [WorldPacketHandler(ClientOpcodes.CancelAutoRepeatSpell)] + void HandleCancelAutoRepeatSpell(CancelAutoRepeatSpell packet) + { + //may be better send SMSG_CANCEL_AUTO_REPEAT? + //cancel and prepare for deleting + _player.InterruptSpell(CurrentSpellTypes.AutoRepeat); + } + + [WorldPacketHandler(ClientOpcodes.CancelChannelling)] + void HandleCancelChanneling(CancelChannelling cancelChanneling) + { + // ignore for remote control state (for player case) + Unit mover = _player.m_unitMovedByMe; + if (mover != _player && mover.IsTypeId(TypeId.Player)) + return; + + mover.InterruptSpell(CurrentSpellTypes.Channeled); + } + + [WorldPacketHandler(ClientOpcodes.TotemDestroyed)] + void HandleTotemDestroyed(TotemDestroyed totemDestroyed) + { + // ignore for remote control state + if (GetPlayer().m_unitMovedByMe != GetPlayer()) + return; + + byte slotId = totemDestroyed.Slot; + slotId += (int)SummonSlot.Totem; + + if (slotId >= SharedConst.MaxTotemSlot) + return; + + if (GetPlayer().m_SummonSlot[slotId].IsEmpty()) + return; + + Creature totem = ObjectAccessor.GetCreature(GetPlayer(), _player.m_SummonSlot[slotId]); + if (totem != null && totem.IsTotem())// && totem.GetGUID() == packet.TotemGUID) Unknown why blizz doesnt send the guid when you right click it. + totem.ToTotem().UnSummon(); + } + + [WorldPacketHandler(ClientOpcodes.SelfRes)] + void HandleSelfRes(SelfRes packet) + { + if (_player.HasAuraType(AuraType.PreventResurrection)) + return; // silent return, client should display error by itself and not send this opcode + + if (_player.GetUInt32Value(PlayerFields.SelfResSpell) != 0) + { + SpellInfo spellInfo = Global.SpellMgr.GetSpellInfo(_player.GetUInt32Value(PlayerFields.SelfResSpell)); + if (spellInfo != null) + _player.CastSpell(_player, spellInfo, false, null); + + _player.SetUInt32Value(PlayerFields.SelfResSpell, 0); + } + } + + [WorldPacketHandler(ClientOpcodes.SpellClick)] + void HandleSpellClick(SpellClick packet) + { + // this will get something not in world. crash + Creature unit = ObjectAccessor.GetCreatureOrPetOrVehicle(GetPlayer(), packet.SpellClickUnitGuid); + if (unit == null) + return; + + /// @todo Unit.SetCharmedBy: 28782 is not in world but 0 is trying to charm it! . crash + if (!unit.IsInWorld) + return; + + unit.HandleSpellClick(GetPlayer()); + } + + [WorldPacketHandler(ClientOpcodes.GetMirrorImageData)] + void HandleMirrorImageDataRequest(GetMirrorImageData packet) + { + ObjectGuid guid = packet.UnitGUID; + + // Get unit for which data is needed by client + Unit unit = Global.ObjAccessor.GetUnit(GetPlayer(), guid); + if (!unit) + return; + + if (!unit.HasAuraType(AuraType.CloneCaster)) + return; + + // Get creator of the unit (SPELL_AURA_CLONE_CASTER does not stack) + Unit creator = unit.GetAuraEffectsByType(AuraType.CloneCaster).FirstOrDefault().GetCaster(); + if (!creator) + return; + + Player player = creator.ToPlayer(); + if (player) + { + MirrorImageComponentedData data = new MirrorImageComponentedData(); + data.UnitGUID = guid; + data.DisplayID = (int)creator.GetDisplayId(); + data.RaceID = (byte)creator.GetRace(); + data.Gender = (byte)creator.GetGender(); + data.ClassID = (byte)creator.GetClass(); + + Guild guild = player.GetGuild(); + + data.SkinColor = player.GetByteValue(PlayerFields.Bytes, PlayerFieldOffsets.BytesOffsetSkinId); + data.FaceVariation = player.GetByteValue(PlayerFields.Bytes, PlayerFieldOffsets.BytesOffsetFaceId); + data.HairVariation = player.GetByteValue(PlayerFields.Bytes, PlayerFieldOffsets.BytesOffsetHairStyleId); + data.HairColor = player.GetByteValue(PlayerFields.Bytes, PlayerFieldOffsets.BytesOffsetHairColorId); + data.BeardVariation = player.GetByteValue(PlayerFields.Bytes2, PlayerFieldOffsets.Bytes2OffsetFacialStyle); + for (int i = 0; i < PlayerConst.CustomDisplaySize; ++i) + data.CustomDisplay[i] = player.GetByteValue(PlayerFields.Bytes2, (byte)(PlayerFieldOffsets.Bytes2OffsetCustomDisplayOption + i)); + data.GuildGUID = (guild ? guild.GetGUID() : ObjectGuid.Empty); + + byte[] itemSlots = + { + EquipmentSlot.Head, + EquipmentSlot.Shoulders, + EquipmentSlot.Shirt, + EquipmentSlot.Chest, + EquipmentSlot.Waist , + EquipmentSlot.Legs , + EquipmentSlot.Feet, + EquipmentSlot.Wrist, + EquipmentSlot.Hands, + EquipmentSlot.Tabard, + EquipmentSlot.Cloak + }; + + // Display items in visible slots + foreach (var slot in itemSlots) + { + uint itemDisplayId; + Item item = player.GetItemByPos(InventorySlots.Bag0, slot); + if ((slot == EquipmentSlot.Head && player.HasFlag(PlayerFields.Flags, PlayerFlags.HideHelm)) || + (slot == EquipmentSlot.Cloak && player.HasFlag(PlayerFields.Flags, PlayerFlags.HideCloak))) + itemDisplayId = 0; + else if (item) + itemDisplayId = item.GetDisplayId(player); + else + itemDisplayId = 0; + + data.ItemDisplayID.Add((int)itemDisplayId); + } + + SendPacket(data); + } + else + { + MirrorImageCreatureData data = new MirrorImageCreatureData(); + data.UnitGUID = guid; + data.DisplayID = (int)creator.GetDisplayId(); + SendPacket(data); + } + } + + [WorldPacketHandler(ClientOpcodes.MissileTrajectoryCollision)] + void HandleMissileTrajectoryCollision(MissileTrajectoryCollision packet) + { + Unit caster = Global.ObjAccessor.GetUnit(_player, packet.Target); + if (caster == null) + return; + + Spell spell = caster.FindCurrentSpellBySpellId(packet.SpellID); + if (spell == null || !spell.m_targets.HasDst()) + return; + + Position pos = spell.m_targets.GetDstPos(); + pos.Relocate(packet.CollisionPos); + spell.m_targets.ModDst(pos); + + NotifyMissileTrajectoryCollision data = new NotifyMissileTrajectoryCollision(); + data.Caster = packet.Target; + data.CastID = packet.CastID; + data.CollisionPos = packet.CollisionPos; + caster.SendMessageToSet(data, true); + } + + [WorldPacketHandler(ClientOpcodes.UpdateMissileTrajectory)] + void HandleUpdateMissileTrajectory(UpdateMissileTrajectory packet) + { + Unit caster = Global.ObjAccessor.GetUnit(GetPlayer(), packet.Guid); + Spell spell = caster ? caster.GetCurrentSpell(CurrentSpellTypes.Generic) : null; + if (!spell || spell.m_spellInfo.Id != packet.SpellID || !spell.m_targets.HasDst() || !spell.m_targets.HasSrc()) + return; + + Position pos = spell.m_targets.GetSrcPos(); + pos.Relocate(packet.FirePos); + spell.m_targets.ModSrc(pos); + + pos = spell.m_targets.GetDstPos(); + pos.Relocate(packet.ImpactPos); + spell.m_targets.ModDst(pos); + + spell.m_targets.SetPitch(packet.Pitch); + spell.m_targets.SetSpeed(packet.Speed); + + if (packet.Status.HasValue) + { + GetPlayer().ValidateMovementInfo(packet.Status.Value); + /*public uint opcode; + recvPacket >> opcode; + recvPacket.SetOpcode(CMSG_MOVE_STOP); // always set to CMSG_MOVE_STOP in client SetOpcode + //HandleMovementOpcodes(recvPacket);*/ + } + } + + [WorldPacketHandler(ClientOpcodes.RequestCategoryCooldowns, Processing = PacketProcessing.Inplace)] + void HandleRequestCategoryCooldowns(RequestCategoryCooldowns requestCategoryCooldowns) + { + GetPlayer().SendSpellCategoryCooldowns(); + } + } +} diff --git a/Game/Handlers/TaxiHandler.cs b/Game/Handlers/TaxiHandler.cs new file mode 100644 index 000000000..9ce5c8ad9 --- /dev/null +++ b/Game/Handlers/TaxiHandler.cs @@ -0,0 +1,238 @@ +/* + * Copyright (C) 2012-2017 CypherCore + * + * 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, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +using Framework.Constants; +using Game.DataStorage; +using Game.Entities; +using Game.Movement; +using Game.Network; +using Game.Network.Packets; +using System.Collections.Generic; +using System.Linq; + +namespace Game +{ + public partial class WorldSession + { + [WorldPacketHandler(ClientOpcodes.EnableTaxiNode, Processing = PacketProcessing.ThreadSafe)] + void HandleEnableTaxiNodeOpcode(EnableTaxiNode enableTaxiNode) + { + Creature unit = GetPlayer().GetNPCIfCanInteractWith(enableTaxiNode.Unit, NPCFlags.FlightMaster); + if (unit) + SendLearnNewTaxiNode(unit); + } + + [WorldPacketHandler(ClientOpcodes.TaxiNodeStatusQuery, Processing = PacketProcessing.ThreadSafe)] + void HandleTaxiNodeStatusQuery(TaxiNodeStatusQuery taxiNodeStatusQuery) + { + SendTaxiStatus(taxiNodeStatusQuery.UnitGUID); + } + + public void SendTaxiStatus(ObjectGuid guid) + { + // cheating checks + Creature unit = ObjectAccessor.GetCreature(GetPlayer(), guid); + if (unit == null) + { + Log.outDebug(LogFilter.Network, "WorldSession.SendTaxiStatus - {0} not found.", guid.ToString()); + return; + } + + uint curloc = Global.ObjectMgr.GetNearestTaxiNode(unit.GetPositionX(), unit.GetPositionY(), unit.GetPositionZ(), unit.GetMapId(), GetPlayer().GetTeam()); + + TaxiNodeStatusPkt data = new TaxiNodeStatusPkt(); + data.Unit = guid; + + if (curloc == 0) + data.Status = TaxiNodeStatus.None; + else if (unit.GetReactionTo(GetPlayer()) >= ReputationRank.Neutral) + data.Status = GetPlayer().m_taxi.IsTaximaskNodeKnown(curloc) ? TaxiNodeStatus.Learned : TaxiNodeStatus.Unlearned; + else + data.Status = TaxiNodeStatus.NotEligible; + + SendPacket(data); + } + + [WorldPacketHandler(ClientOpcodes.TaxiQueryAvailableNodes, Processing = PacketProcessing.ThreadSafe)] + void HandleTaxiQueryAvailableNodes(TaxiQueryAvailableNodes taxiQueryAvailableNodes) + { + // cheating checks + Creature unit = GetPlayer().GetNPCIfCanInteractWith(taxiQueryAvailableNodes.Unit, NPCFlags.FlightMaster); + if (unit == null) + { + Log.outDebug(LogFilter.Network, "WORLD: HandleTaxiQueryAvailableNodes - {0} not found or you can't interact with him.", taxiQueryAvailableNodes.Unit.ToString()); + return; + } + + // remove fake death + if (GetPlayer().HasUnitState(UnitState.Died)) + GetPlayer().RemoveAurasByType(AuraType.FeignDeath); + + // unknown taxi node case + if (SendLearnNewTaxiNode(unit)) + return; + + // known taxi node case + SendTaxiMenu(unit); + } + + public void SendTaxiMenu(Creature unit) + { + // find current node + uint curloc = Global.ObjectMgr.GetNearestTaxiNode(unit.GetPositionX(), unit.GetPositionY(), unit.GetPositionZ(), unit.GetMapId(), GetPlayer().GetTeam()); + if (curloc == 0) + return; + + bool lastTaxiCheaterState = GetPlayer().isTaxiCheater(); + if (unit.GetEntry() == 29480) + GetPlayer().SetTaxiCheater(true); // Grimwing in Ebon Hold, special case. NOTE: Not perfect, Zul'Aman should not be included according to WoWhead, and I think taxicheat includes it. + + ShowTaxiNodes data = new ShowTaxiNodes(); + data.WindowInfo.HasValue = true; + data.WindowInfo.Value.UnitGUID = unit.GetGUID(); + data.WindowInfo.Value.CurrentNode = (int)curloc; + + GetPlayer().m_taxi.AppendTaximaskTo(data, lastTaxiCheaterState); + + SendPacket(data); + + GetPlayer().SetTaxiCheater(lastTaxiCheaterState); + } + + public void SendDoFlight(uint mountDisplayId, uint path, uint pathNode = 0) + { + // remove fake death + if (GetPlayer().HasUnitState(UnitState.Died)) + GetPlayer().RemoveAurasByType(AuraType.FeignDeath); + + while (GetPlayer().GetMotionMaster().GetCurrentMovementGeneratorType() == MovementGeneratorType.Flight) + GetPlayer().GetMotionMaster().MovementExpired(false); + + if (mountDisplayId != 0) + GetPlayer().Mount(mountDisplayId); + + GetPlayer().GetMotionMaster().MoveTaxiFlight(path, pathNode); + } + + public bool SendLearnNewTaxiNode(Creature unit) + { + // find current node + uint curloc = Global.ObjectMgr.GetNearestTaxiNode(unit.GetPositionX(), unit.GetPositionY(), unit.GetPositionZ(), unit.GetMapId(), GetPlayer().GetTeam()); + + if (curloc == 0) + return true; + + if (GetPlayer().m_taxi.SetTaximaskNode(curloc)) + { + SendPacket(new NewTaxiPath()); + + TaxiNodeStatusPkt data = new TaxiNodeStatusPkt(); + data.Unit = unit.GetGUID(); + data.Status = TaxiNodeStatus.Learned; + SendPacket(data); + + return true; + } + else + return false; + } + + public void SendDiscoverNewTaxiNode(uint nodeid) + { + if (GetPlayer().m_taxi.SetTaximaskNode(nodeid)) + SendPacket(new NewTaxiPath()); + } + + [WorldPacketHandler(ClientOpcodes.ActivateTaxi, Processing = PacketProcessing.ThreadSafe)] + void HandleActivateTaxi(ActivateTaxi activateTaxi) + { + Creature unit = GetPlayer().GetNPCIfCanInteractWith(activateTaxi.Vendor, NPCFlags.FlightMaster); + if (unit == null) + { + Log.outDebug(LogFilter.Network, "WORLD: HandleActivateTaxiOpcode - {0} not found or you can't interact with it.", activateTaxi.Vendor.ToString()); + return; + } + + uint curloc = Global.ObjectMgr.GetNearestTaxiNode(unit.GetPositionX(), unit.GetPositionY(), unit.GetPositionZ(), unit.GetMapId(), GetPlayer().GetTeam()); + if (curloc == 0) + return; + + TaxiNodesRecord from = CliDB.TaxiNodesStorage.LookupByKey(curloc); + TaxiNodesRecord to = CliDB.TaxiNodesStorage.LookupByKey(activateTaxi.Node); + if (to == null) + return; + + if (!GetPlayer().isTaxiCheater()) + { + if (!GetPlayer().m_taxi.IsTaximaskNodeKnown(curloc) || !GetPlayer().m_taxi.IsTaximaskNodeKnown(activateTaxi.Node)) + { + SendActivateTaxiReply(ActivateTaxiReply.NotVisited); + return; + } + } + + uint preferredMountDisplay = 0; + MountRecord mount = CliDB.MountStorage.LookupByKey(activateTaxi.FlyingMountID); + if (mount != null) + { + if (GetPlayer().HasSpell(mount.SpellId)) + { + var mountDisplays = Global.DB2Mgr.GetMountDisplays(mount.Id); + if (mountDisplays != null) + { + List usableDisplays = mountDisplays.Where(mountDisplay => + { + PlayerConditionRecord playerCondition = CliDB.PlayerConditionStorage.LookupByKey(mountDisplay.PlayerConditionID); + if (playerCondition != null) + return ConditionManager.IsPlayerMeetingCondition(GetPlayer(), playerCondition); + + return true; + }).ToList(); + + if (!usableDisplays.Empty()) + preferredMountDisplay = usableDisplays.PickRandom().DisplayID; + } + } + } + + List nodes = new List(); + Global.TaxiPathGraph.GetCompleteNodeRoute(from, to, GetPlayer(), nodes); + GetPlayer().ActivateTaxiPathTo(nodes, unit, 0, preferredMountDisplay); + } + + public void SendActivateTaxiReply(ActivateTaxiReply reply = ActivateTaxiReply.Ok) + { + ActivateTaxiReplyPkt data = new ActivateTaxiReplyPkt(); + data.Reply = reply; + SendPacket(data); + } + + [WorldPacketHandler(ClientOpcodes.TaxiRequestEarlyLanding, Processing = PacketProcessing.ThreadSafe)] + void HandleTaxiRequestEarlyLanding(TaxiRequestEarlyLanding taxiRequestEarlyLanding) + { + if (GetPlayer().GetMotionMaster().GetCurrentMovementGeneratorType() == MovementGeneratorType.Flight) + { + if (GetPlayer().m_taxi.RequestEarlyLanding()) + { + FlightPathMovementGenerator flight = (FlightPathMovementGenerator)GetPlayer().GetMotionMaster().top(); + flight.LoadPath(GetPlayer(), flight.GetPath()[(int)flight.GetCurrentNode()].NodeIndex); + flight.Reset(GetPlayer()); + } + } + } + } +} diff --git a/Game/Handlers/TicketHandler.cs b/Game/Handlers/TicketHandler.cs new file mode 100644 index 000000000..ab13798c8 --- /dev/null +++ b/Game/Handlers/TicketHandler.cs @@ -0,0 +1,115 @@ +/* + * Copyright (C) 2012-2017 CypherCore + * + * 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, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +using Framework.Constants; +using Framework.Database; +using Game.Network; +using Game.Network.Packets; +using Game.SupportSystem; + +namespace Game +{ + public partial class WorldSession + { + [WorldPacketHandler(ClientOpcodes.GmTicketGetCaseStatus, Processing = PacketProcessing.Inplace)] + void HandleGMTicketGetCaseStatus(GMTicketGetCaseStatus packet) + { + //TODO: Implement GmCase and handle this packet correctly + GMTicketCaseStatus status = new GMTicketCaseStatus(); + SendPacket(status); + } + + [WorldPacketHandler(ClientOpcodes.GmTicketGetSystemStatus, Processing = PacketProcessing.Inplace)] + void HandleGMTicketSystemStatusOpcode(GMTicketGetSystemStatus packet) + { + // Note: This only disables the ticket UI at client side and is not fully reliable + // Note: This disables the whole customer support UI after trying to send a ticket in disabled state (MessageBox: "GM Help Tickets are currently unavaiable."). UI remains disabled until the character relogs. + GMTicketSystemStatusPkt response = new GMTicketSystemStatusPkt(); + response.Status = Global.SupportMgr.GetSupportSystemStatus() ? 1 : 0; + SendPacket(response); + } + + [WorldPacketHandler(ClientOpcodes.SupportTicketSubmitBug)] + void HandleSupportTicketSubmitBug(SupportTicketSubmitBug packet) + { + if (!Global.SupportMgr.GetBugSystemStatus()) + return; + + BugTicket ticket = new BugTicket(GetPlayer()); + ticket.SetPosition(packet.Header.MapID, packet.Header.Position); + ticket.SetFacing(packet.Header.Facing); + ticket.SetNote(packet.Note); + + Global.SupportMgr.AddTicket(ticket); + } + + [WorldPacketHandler(ClientOpcodes.SupportTicketSubmitSuggestion)] + void HandleSupportTicketSubmitSuggestion(SupportTicketSubmitSuggestion packet) + { + if (!Global.SupportMgr.GetSuggestionSystemStatus()) + return; + + SuggestionTicket ticket = new SuggestionTicket(GetPlayer()); + ticket.SetPosition(packet.Header.MapID, packet.Header.Position); + ticket.SetFacing(packet.Header.Facing); + ticket.SetNote(packet.Note); + + Global.SupportMgr.AddTicket(ticket); + } + + [WorldPacketHandler(ClientOpcodes.SupportTicketSubmitComplaint)] + void HandleSupportTicketSubmitComplaint(SupportTicketSubmitComplaint packet) + { + if (!Global.SupportMgr.GetComplaintSystemStatus()) + return; + + ComplaintTicket comp = new ComplaintTicket(GetPlayer()); + comp.SetPosition(packet.Header.MapID, packet.Header.Position); + comp.SetFacing(packet.Header.Facing); + comp.SetChatLog(packet.ChatLog); + comp.SetTargetCharacterGuid(packet.TargetCharacterGUID); + comp.SetComplaintType((GMSupportComplaintType)packet.ComplaintType); + comp.SetNote(packet.Note); + + Global.SupportMgr.AddTicket(comp); + } + + [WorldPacketHandler(ClientOpcodes.BugReport)] + void HandleBugReport(BugReport bugReport) + { + // Note: There is no way to trigger this with standard UI except /script ReportBug("text") + if (!Global.SupportMgr.GetBugSystemStatus()) + return; + + PreparedStatement stmt = DB.Characters.GetPreparedStatement(CharStatements.INS_BUG_REPORT); + stmt.AddValue(0, bugReport.Text); + stmt.AddValue(1, bugReport.DiagInfo); + DB.Characters.Execute(stmt); + } + + [WorldPacketHandler(ClientOpcodes.Complaint)] + void HandleComplaint(Complaint packet) + { // NOTE: all chat messages from this spammer are automatically ignored by the spam reporter until logout in case of chat spam. + // if it's mail spam - ALL mails from this spammer are automatically removed by client + + ComplaintResult result = new ComplaintResult(); + result.ComplaintType = packet.ComplaintType; + result.Result = 0; + SendPacket(result); + } + } +} diff --git a/Game/Handlers/TimeHandler.cs b/Game/Handlers/TimeHandler.cs new file mode 100644 index 000000000..35ed3db90 --- /dev/null +++ b/Game/Handlers/TimeHandler.cs @@ -0,0 +1,54 @@ +/* + * Copyright (C) 2012-2017 CypherCore + * + * 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, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +using Framework.Constants; +using Game.Network; +using Game.Network.Packets; +using System.Linq; + +namespace Game +{ + public partial class WorldSession + { + [WorldPacketHandler(ClientOpcodes.UiTimeRequest, Status = SessionStatus.Authed, Processing = PacketProcessing.Inplace)] + void HandleUITimeRequest(UITimeRequest packet) + { + UITime response = new UITime(); + response.Time = (uint)Time.UnixTime; + SendPacket(response); + } + + [WorldPacketHandler(ClientOpcodes.TimeSyncResponse, Processing = PacketProcessing.Inplace)] + void HandleTimeSyncResponse(TimeSyncResponse packet) + { + Log.outDebug(LogFilter.Network, "CMSG_TIME_SYNC_RESP"); + + if (packet.SequenceIndex != _player.m_timeSyncQueue.FirstOrDefault()) + Log.outError(LogFilter.Network, "Wrong time sync counter from player {0} (cheater?)", _player.GetName()); + + Log.outDebug(LogFilter.Network, "Time sync received: counter {0}, client ticks {1}, time since last sync {2}", packet.SequenceIndex, packet.ClientTime, packet.ClientTime - _player.m_timeSyncClient); + + uint ourTicks = packet.ClientTime + (Time.GetMSTime() - _player.m_timeSyncServer); + + // diff should be small + Log.outDebug(LogFilter.Network, "Our ticks: {0}, diff {1}, latency {2}", ourTicks, ourTicks - packet.ClientTime, GetLatency()); + + _player.m_timeSyncClient = packet.ClientTime; + _player.m_timeSyncQueue.Pop(); + } + } +} diff --git a/Game/Handlers/TokenHandler.cs b/Game/Handlers/TokenHandler.cs new file mode 100644 index 000000000..30a02f3bb --- /dev/null +++ b/Game/Handlers/TokenHandler.cs @@ -0,0 +1,52 @@ +/* + * Copyright (C) 2012-2017 CypherCore + * + * 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, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +using Framework.Constants; +using Game.Network; +using Game.Network.Packets; + +namespace Game +{ + public partial class WorldSession + { + [WorldPacketHandler(ClientOpcodes.UpdateWowTokenAuctionableList)] + void HandleUpdateListedAuctionableTokens(UpdateListedAuctionableTokens updateListedAuctionableTokens) + { + UpdateListedAuctionableTokensResponse response = new UpdateListedAuctionableTokensResponse(); + + /// @todo: fix 6.x implementation + response.UnkInt = updateListedAuctionableTokens.UnkInt; + response.Result = TokenResult.Success; + + SendPacket(response); + } + + [WorldPacketHandler(ClientOpcodes.RequestWowTokenMarketPrice)] + void HandleRequestWowTokenMarketPrice(RequestWowTokenMarketPrice requestWowTokenMarketPrice) + { + WowTokenMarketPriceResponse response = new WowTokenMarketPriceResponse(); + + /// @todo: 6.x fix implementation + response.CurrentMarketPrice = 300000000; + response.UnkInt = requestWowTokenMarketPrice.UnkInt; + response.Result = TokenResult.Success; + //packet.ReadUInt32("UnkInt32"); + + SendPacket(response); + } + } +} diff --git a/Game/Handlers/ToyHandler.cs b/Game/Handlers/ToyHandler.cs new file mode 100644 index 000000000..9b866b43f --- /dev/null +++ b/Game/Handlers/ToyHandler.cs @@ -0,0 +1,96 @@ +/* + * Copyright (C) 2012-2017 CypherCore + * + * 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, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +using Framework.Constants; +using Game.Entities; +using Game.Network; +using Game.Network.Packets; +using Game.Spells; + +namespace Game +{ + public partial class WorldSession + { + [WorldPacketHandler(ClientOpcodes.AddToy)] + void HandleAddToy(AddToy packet) + { + if (packet.Guid.IsEmpty()) + return; + + Item item = _player.GetItemByGuid(packet.Guid); + if (!item) + { + _player.SendEquipError(InventoryResult.ItemNotFound); + return; + } + + if (!Global.DB2Mgr.IsToyItem(item.GetEntry())) + return; + + InventoryResult msg = _player.CanUseItem(item); + if (msg != InventoryResult.Ok) + { + _player.SendEquipError(msg, item); + return; + } + + if (_collectionMgr.AddToy(item.GetEntry(), false)) + _player.DestroyItem(item.GetBagSlot(), item.GetSlot(), true); + } + + [WorldPacketHandler(ClientOpcodes.UseToy)] + void HandleUseToy(UseToy packet) + { + ItemTemplate item = Global.ObjectMgr.GetItemTemplate(packet.ItemID); + if (item == null) + return; + + if (!_collectionMgr.HasToy(packet.ItemID)) + return; + + var effect = item.Effects.Find(eff => { return packet.Cast.SpellID == eff.SpellID; }); + if (effect == null) + return; + + SpellInfo spellInfo = Global.SpellMgr.GetSpellInfo(packet.Cast.SpellID); + if (spellInfo == null) + { + Log.outError(LogFilter.Network, "HandleUseToy: unknown spell id: {0} used by Toy Item entry {1}", packet.Cast.SpellID, packet.ItemID); + return; + } + + if (_player.isPossessing()) + return; + + SpellCastTargets targets = new SpellCastTargets(_player, packet.Cast); + + Spell spell = new Spell(_player, spellInfo, TriggerCastFlags.None, ObjectGuid.Empty, false); + + SpellPrepare spellPrepare = new SpellPrepare(); + spellPrepare.ClientCastID = packet.Cast.CastID; + spellPrepare.ServerCastID = spell.m_castId; + SendPacket(spellPrepare); + + spell.m_fromClient = true; + spell.m_castItemEntry = packet.ItemID; + spell.m_misc.Data0 = packet.Cast.Misc[0]; + spell.m_misc.Data1 = packet.Cast.Misc[1]; + spell.m_castFlagsEx |= SpellCastFlagsEx.UseToySpell; + spell.prepare(targets); + } + } +} diff --git a/Game/Handlers/TradeHandler.cs b/Game/Handlers/TradeHandler.cs new file mode 100644 index 000000000..3b7964332 --- /dev/null +++ b/Game/Handlers/TradeHandler.cs @@ -0,0 +1,765 @@ +/* + * Copyright (C) 2012-2017 CypherCore + * + * 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, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +using Framework.Constants; +using Framework.Database; +using Game.Entities; +using Game.Network; +using Game.Network.Packets; +using Game.Spells; +using System.Collections.Generic; + +namespace Game +{ + public partial class WorldSession + { + public void SendTradeStatus(TradeStatusPkt info) + { + info.Clear(); // reuse packet + Player trader = GetPlayer().GetTrader(); + info.PartnerIsSameBnetAccount = trader && trader.GetSession().GetBattlenetAccountId() == GetBattlenetAccountId(); + SendPacket(info); + } + + [WorldPacketHandler(ClientOpcodes.IgnoreTrade)] + void HandleIgnoreTradeOpcode(IgnoreTrade packet) { } + + [WorldPacketHandler(ClientOpcodes.BusyTrade)] + void HandleBusyTradeOpcode(BusyTrade packet) { } + + public void SendUpdateTrade(bool trader_data = true) + { + TradeData view_trade = trader_data ? GetPlayer().GetTradeData().GetTraderData() : GetPlayer().GetTradeData(); + + TradeUpdated tradeUpdated = new TradeUpdated(); + tradeUpdated.WhichPlayer = (byte)(trader_data ? 1 : 0); + tradeUpdated.ClientStateIndex = view_trade.GetClientStateIndex(); + tradeUpdated.CurrentStateIndex = view_trade.GetServerStateIndex(); + tradeUpdated.Gold = view_trade.GetMoney(); + tradeUpdated.ProposedEnchantment = (int)view_trade.GetSpell(); + + for (byte i = 0; i < (byte)TradeSlots.Count; ++i) + { + Item item = view_trade.GetItem((TradeSlots)i); + if (item) + { + TradeUpdated.TradeItem tradeItem = new TradeUpdated.TradeItem(); + tradeItem.Slot = i; + tradeItem.Item = new ItemInstance(item); + tradeItem.StackCount = (int)item.GetCount(); + tradeItem.GiftCreator = item.GetGuidValue(ItemFields.GiftCreator); + if (!item.HasFlag(ItemFields.Flags, ItemFieldFlags.Wrapped)) + { + tradeItem.Unwrapped.HasValue = true; + TradeUpdated.UnwrappedTradeItem unwrappedItem = tradeItem.Unwrapped.Value; + unwrappedItem.EnchantID = (int)item.GetEnchantmentId(EnchantmentSlot.Perm); + unwrappedItem.OnUseEnchantmentID = (int)item.GetEnchantmentId(EnchantmentSlot.Use); + unwrappedItem.Creator = item.GetGuidValue(ItemFields.Creator); + unwrappedItem.Charges = item.GetSpellCharges(); + unwrappedItem.Lock = item.GetTemplate().GetLockID() != 0 && !item.HasFlag(ItemFields.Flags, ItemFieldFlags.Unlocked); + unwrappedItem.MaxDurability = item.GetUInt32Value(ItemFields.MaxDurability); + unwrappedItem.Durability = item.GetUInt32Value(ItemFields.Durability); + + byte g = 0; + foreach (ItemDynamicFieldGems gemData in item.GetGems()) + { + if (gemData.ItemId != 0) + { + ItemGemData gem = new ItemGemData(); + gem.Slot = g; + gem.Item = new ItemInstance(gemData); + tradeItem.Unwrapped.Value.Gems.Add(gem); + } + ++g; + } + } + tradeUpdated.Items.Add(tradeItem); + } + } + + SendPacket(tradeUpdated); + } + + void moveItems(Item[] myItems, Item[] hisItems) + { + Player trader = GetPlayer().GetTrader(); + if (!trader) + return; + + for (byte i = 0; i < (int)TradeSlots.TradedCount; ++i) + { + List traderDst = new List(); + List playerDst = new List(); + bool traderCanTrade = (myItems[i] == null || trader.CanStoreItem(ItemConst.NullBag, ItemConst.NullSlot, traderDst, myItems[i], false) == InventoryResult.Ok); + bool playerCanTrade = (hisItems[i] == null || GetPlayer().CanStoreItem(ItemConst.NullBag, ItemConst.NullSlot, playerDst, hisItems[i], false) == InventoryResult.Ok); + if (traderCanTrade && playerCanTrade) + { + // Ok, if trade item exists and can be stored + // If we trade in both directions we had to check, if the trade will work before we actually do it + // A roll back is not possible after we stored it + if (myItems[i]) + { + // logging + Log.outDebug(LogFilter.Network, "partner storing: {0}", myItems[i].GetGUID().ToString()); + + if (HasPermission(RBACPermissions.LogGmTrade)) + { + Log.outCommand(_player.GetSession().GetAccountId(), "GM {0} (Account: {1}) trade: {2} (Entry: {3} Count: {4}) to player: {5} (Account: {6})", + GetPlayer().GetName(), GetPlayer().GetSession().GetAccountId(), myItems[i].GetTemplate().GetName(), myItems[i].GetEntry(), myItems[i].GetCount(), + trader.GetName(), trader.GetSession().GetAccountId()); + } + + // adjust time (depends on /played) + if (myItems[i].HasFlag(ItemFields.Flags, ItemFieldFlags.BopTradeable)) + myItems[i].SetUInt32Value(ItemFields.CreatePlayedTime, trader.GetTotalPlayedTime() - (GetPlayer().GetTotalPlayedTime() - myItems[i].GetUInt32Value(ItemFields.CreatePlayedTime))); + // store + trader.MoveItemToInventory(traderDst, myItems[i], true, true); + } + if (hisItems[i]) + { + // logging + Log.outDebug(LogFilter.Network, "player storing: {0}", hisItems[i].GetGUID().ToString()); + + if (HasPermission(RBACPermissions.LogGmTrade)) + { + Log.outCommand(trader.GetSession().GetAccountId(), "GM {0} (Account: {1}) trade: {2} (Entry: {3} Count: {4}) to player: {5} (Account: {6})", + trader.GetName(), trader.GetSession().GetAccountId(), hisItems[i].GetTemplate().GetName(), hisItems[i].GetEntry(), hisItems[i].GetCount(), + GetPlayer().GetName(), GetPlayer().GetSession().GetAccountId()); + } + + + // adjust time (depends on /played) + if (hisItems[i].HasFlag(ItemFields.Flags, ItemFieldFlags.BopTradeable)) + hisItems[i].SetUInt32Value(ItemFields.CreatePlayedTime, GetPlayer().GetTotalPlayedTime() - (trader.GetTotalPlayedTime() - hisItems[i].GetUInt32Value(ItemFields.CreatePlayedTime))); + // store + GetPlayer().MoveItemToInventory(playerDst, hisItems[i], true, true); + } + } + else + { + // in case of fatal error log error message + // return the already removed items to the original owner + if (myItems[i]) + { + if (!traderCanTrade) + Log.outError(LogFilter.Network, "trader can't store item: {0}", myItems[i].GetGUID().ToString()); + if (GetPlayer().CanStoreItem(ItemConst.NullBag, ItemConst.NullSlot, playerDst, myItems[i], false) == InventoryResult.Ok) + GetPlayer().MoveItemToInventory(playerDst, myItems[i], true, true); + else + Log.outError(LogFilter.Network, "player can't take item back: {0}", myItems[i].GetGUID().ToString()); + } + // return the already removed items to the original owner + if (hisItems[i]) + { + if (!playerCanTrade) + Log.outError(LogFilter.Network, "player can't store item: {0}", hisItems[i].GetGUID().ToString()); + if (trader.CanStoreItem(ItemConst.NullBag, ItemConst.NullSlot, traderDst, hisItems[i], false) == InventoryResult.Ok) + trader.MoveItemToInventory(traderDst, hisItems[i], true, true); + else + Log.outError(LogFilter.Network, "trader can't take item back: {0}", hisItems[i].GetGUID().ToString()); + } + } + } + } + + static void setAcceptTradeMode(TradeData myTrade, TradeData hisTrade, Item[] myItems, Item[] hisItems) + { + myTrade.SetInAcceptProcess(true); + hisTrade.SetInAcceptProcess(true); + + // store items in local list and set 'in-trade' flag + for (byte i = 0; i < (int)TradeSlots.Count; ++i) + { + Item item = myTrade.GetItem((TradeSlots)i); + if (item) + { + Log.outDebug(LogFilter.Network, "player trade item {0} bag: {1} slot: {2}", item.GetGUID().ToString(), item.GetBagSlot(), item.GetSlot()); + //Can return null + myItems[i] = item; + myItems[i].SetInTrade(); + } + item = hisTrade.GetItem((TradeSlots)i); + if (item) + { + Log.outDebug(LogFilter.Network, "partner trade item {0} bag: {1} slot: {2}", item.GetGUID().ToString(), item.GetBagSlot(), item.GetSlot()); + hisItems[i] = item; + hisItems[i].SetInTrade(); + } + } + } + + static void clearAcceptTradeMode(TradeData myTrade, TradeData hisTrade) + { + myTrade.SetInAcceptProcess(false); + hisTrade.SetInAcceptProcess(false); + } + + static void clearAcceptTradeMode(Item[] myItems, Item[] hisItems) + { + // clear 'in-trade' flag + for (byte i = 0; i < (int)TradeSlots.Count; ++i) + { + if (myItems[i]) + myItems[i].SetInTrade(false); + if (hisItems[i]) + hisItems[i].SetInTrade(false); + } + } + + [WorldPacketHandler(ClientOpcodes.AcceptTrade)] + void HandleAcceptTrade(AcceptTrade acceptTrade) + { + TradeData my_trade = GetPlayer().GetTradeData(); + if (my_trade == null) + return; + + Player trader = my_trade.GetTrader(); + + TradeData his_trade = trader.GetTradeData(); + if (his_trade == null) + return; + + Item[] myItems = new Item[(int)TradeSlots.Count]; + Item[] hisItems = new Item[(int)TradeSlots.Count]; + + // set before checks for propertly undo at problems (it already set in to client) + my_trade.SetAccepted(true); + + TradeStatusPkt info = new TradeStatusPkt(); + if (his_trade.GetServerStateIndex() != acceptTrade.StateIndex) + { + info.Status = TradeStatus.StateChanged; + SendTradeStatus(info); + my_trade.SetAccepted(false); + return; + } + + if (!GetPlayer().IsWithinDistInMap(trader, 11.11f, false)) + { + info.Status = TradeStatus.TooFarAway; + SendTradeStatus(info); + my_trade.SetAccepted(false); + return; + } + + // not accept case incorrect money amount + if (!GetPlayer().HasEnoughMoney(my_trade.GetMoney())) + { + info.Status = TradeStatus.Failed; + info.BagResult = InventoryResult.NotEnoughMoney; + SendTradeStatus(info); + my_trade.SetAccepted(false, true); + return; + } + + // not accept case incorrect money amount + if (!trader.HasEnoughMoney(his_trade.GetMoney())) + { + info.Status = TradeStatus.Failed; + info.BagResult = InventoryResult.NotEnoughMoney; + trader.GetSession().SendTradeStatus(info); + his_trade.SetAccepted(false, true); + return; + } + + if (GetPlayer().GetMoney() >= PlayerConst.MaxMoneyAmount - his_trade.GetMoney()) + { + info.Status = TradeStatus.Failed; + info.BagResult = InventoryResult.TooMuchGold; + SendTradeStatus(info); + my_trade.SetAccepted(false, true); + return; + } + + if (trader.GetMoney() >= PlayerConst.MaxMoneyAmount - my_trade.GetMoney()) + { + info.Status = TradeStatus.Failed; + info.BagResult = InventoryResult.TooMuchGold; + trader.GetSession().SendTradeStatus(info); + his_trade.SetAccepted(false, true); + return; + } + + // not accept if some items now can't be trade (cheating) + for (byte i = 0; i < (byte)TradeSlots.Count; ++i) + { + Item item = my_trade.GetItem((TradeSlots)i); + if (item) + { + if (!item.CanBeTraded(false, true)) + { + info.Status = TradeStatus.Cancelled; + SendTradeStatus(info); + return; + } + + if (item.IsBindedNotWith(trader)) + { + info.Status = TradeStatus.Failed; + info.BagResult = InventoryResult.TradeBoundItem; + SendTradeStatus(info); + return; + } + } + item = his_trade.GetItem((TradeSlots)i); + if (item) + { + if (!item.CanBeTraded(false, true)) + { + info.Status = TradeStatus.Cancelled; + SendTradeStatus(info); + return; + } + } + } + + if (his_trade.IsAccepted()) + { + setAcceptTradeMode(my_trade, his_trade, myItems, hisItems); + + Spell my_spell = null; + SpellCastTargets my_targets = new SpellCastTargets(); + + Spell his_spell = null; + SpellCastTargets his_targets = new SpellCastTargets(); + + // not accept if spell can't be casted now (cheating) + uint my_spell_id = my_trade.GetSpell(); + if (my_spell_id != 0) + { + SpellInfo spellEntry = Global.SpellMgr.GetSpellInfo(my_spell_id); + Item castItem = my_trade.GetSpellCastItem(); + + if (spellEntry == null || !his_trade.GetItem(TradeSlots.NonTraded) || + (my_trade.HasSpellCastItem() && !castItem)) + { + clearAcceptTradeMode(my_trade, his_trade); + clearAcceptTradeMode(myItems, hisItems); + + my_trade.SetSpell(0); + return; + } + + my_spell = new Spell(GetPlayer(), spellEntry, TriggerCastFlags.FullMask); + my_spell.m_CastItem = castItem; + my_targets.SetTradeItemTarget(GetPlayer()); + my_spell.m_targets = my_targets; + + SpellCastResult res = my_spell.CheckCast(true); + if (res != SpellCastResult.SpellCastOk) + { + my_spell.SendCastResult(res); + + clearAcceptTradeMode(my_trade, his_trade); + clearAcceptTradeMode(myItems, hisItems); + + my_spell.Dispose(); + my_trade.SetSpell(0); + return; + } + } + + // not accept if spell can't be casted now (cheating) + uint his_spell_id = his_trade.GetSpell(); + if (his_spell_id != 0) + { + SpellInfo spellEntry = Global.SpellMgr.GetSpellInfo(his_spell_id); + Item castItem = his_trade.GetSpellCastItem(); + + if (spellEntry == null || !my_trade.GetItem(TradeSlots.NonTraded) || (his_trade.HasSpellCastItem() && !castItem)) + { + his_trade.SetSpell(0); + + clearAcceptTradeMode(my_trade, his_trade); + clearAcceptTradeMode(myItems, hisItems); + return; + } + + his_spell = new Spell(trader, spellEntry, TriggerCastFlags.FullMask); + his_spell.m_CastItem = castItem; + his_targets.SetTradeItemTarget(trader); + his_spell.m_targets = his_targets; + + SpellCastResult res = his_spell.CheckCast(true); + if (res != SpellCastResult.SpellCastOk) + { + his_spell.SendCastResult(res); + + clearAcceptTradeMode(my_trade, his_trade); + clearAcceptTradeMode(myItems, hisItems); + + my_spell.Dispose(); + his_spell.Dispose(); + + his_trade.SetSpell(0); + return; + } + } + + // inform partner client + info.Status = TradeStatus.Accepted; + trader.GetSession().SendTradeStatus(info); + + // test if item will fit in each inventory + TradeStatusPkt myCanCompleteInfo = new TradeStatusPkt(); + TradeStatusPkt hisCanCompleteInfo = new TradeStatusPkt(); + hisCanCompleteInfo.BagResult = trader.CanStoreItems(myItems, (int)TradeSlots.TradedCount, ref hisCanCompleteInfo.ItemID); + myCanCompleteInfo.BagResult = GetPlayer().CanStoreItems(hisItems, (int)TradeSlots.TradedCount, ref myCanCompleteInfo.ItemID); + + clearAcceptTradeMode(myItems, hisItems); + + // in case of missing space report error + if (myCanCompleteInfo.BagResult != InventoryResult.Ok) + { + clearAcceptTradeMode(my_trade, his_trade); + + myCanCompleteInfo.Status = TradeStatus.Failed; + trader.GetSession().SendTradeStatus(myCanCompleteInfo); + myCanCompleteInfo.FailureForYou = true; + SendTradeStatus(myCanCompleteInfo); + my_trade.SetAccepted(false); + his_trade.SetAccepted(false); + return; + } + else if (hisCanCompleteInfo.BagResult != InventoryResult.Ok) + { + clearAcceptTradeMode(my_trade, his_trade); + + hisCanCompleteInfo.Status = TradeStatus.Failed; + SendTradeStatus(hisCanCompleteInfo); + hisCanCompleteInfo.FailureForYou = true; + trader.GetSession().SendTradeStatus(hisCanCompleteInfo); + my_trade.SetAccepted(false); + his_trade.SetAccepted(false); + return; + } + + // execute trade: 1. remove + for (byte i = 0; i < (int)TradeSlots.TradedCount; ++i) + { + if (myItems[i]) + { + myItems[i].SetGuidValue(ItemFields.GiftCreator, GetPlayer().GetGUID()); + GetPlayer().MoveItemFromInventory(myItems[i].GetBagSlot(), myItems[i].GetSlot(), true); + } + if (hisItems[i]) + { + hisItems[i].SetGuidValue(ItemFields.GiftCreator, trader.GetGUID()); + trader.MoveItemFromInventory(hisItems[i].GetBagSlot(), hisItems[i].GetSlot(), true); + } + } + + // execute trade: 2. store + moveItems(myItems, hisItems); + + // logging money + if (HasPermission(RBACPermissions.LogGmTrade)) + { + if (my_trade.GetMoney() > 0) + { + Log.outCommand(GetPlayer().GetSession().GetAccountId(), "GM {0} (Account: {1}) give money (Amount: {2}) to player: {3} (Account: {4})", + GetPlayer().GetName(), GetPlayer().GetSession().GetAccountId(), my_trade.GetMoney(), trader.GetName(), trader.GetSession().GetAccountId()); + } + + if (his_trade.GetMoney() > 0) + { + Log.outCommand(GetPlayer().GetSession().GetAccountId(), "GM {0} (Account: {1}) give money (Amount: {2}) to player: {3} (Account: {4})", + trader.GetName(), trader.GetSession().GetAccountId(), his_trade.GetMoney(), GetPlayer().GetName(), GetPlayer().GetSession().GetAccountId()); + } + } + + + // update money + GetPlayer().ModifyMoney(-(long)my_trade.GetMoney()); + GetPlayer().ModifyMoney((long)his_trade.GetMoney()); + trader.ModifyMoney(-(long)his_trade.GetMoney()); + trader.ModifyMoney((long)my_trade.GetMoney()); + + if (my_spell) + my_spell.prepare(my_targets); + + if (his_spell) + his_spell.prepare(his_targets); + + // cleanup + clearAcceptTradeMode(my_trade, his_trade); + GetPlayer().SetTradeData(null); + trader.SetTradeData(null); + + // desynchronized with the other saves here (SaveInventoryAndGoldToDB() not have own transaction guards) + SQLTransaction trans = new SQLTransaction(); + GetPlayer().SaveInventoryAndGoldToDB(trans); + trader.SaveInventoryAndGoldToDB(trans); + DB.Characters.CommitTransaction(trans); + + info.Status = TradeStatus.Complete; + trader.GetSession().SendTradeStatus(info); + SendTradeStatus(info); + } + else + { + info.Status = TradeStatus.Accepted; + trader.GetSession().SendTradeStatus(info); + } + } + + [WorldPacketHandler(ClientOpcodes.UnacceptTrade)] + void HandleUnacceptTrade(UnacceptTrade packet) + { + TradeData my_trade = GetPlayer().GetTradeData(); + if (my_trade == null) + return; + + my_trade.SetAccepted(false, true); + } + + [WorldPacketHandler(ClientOpcodes.BeginTrade)] + void HandleBeginTrade(BeginTrade packet) + { + TradeData my_trade = GetPlayer().GetTradeData(); + if (my_trade == null) + return; + + TradeStatusPkt info = new TradeStatusPkt(); + my_trade.GetTrader().GetSession().SendTradeStatus(info); + SendTradeStatus(info); + } + + public void SendCancelTrade() + { + if (PlayerRecentlyLoggedOut() || PlayerLogout()) + return; + + TradeStatusPkt info = new TradeStatusPkt(); + info.Status = TradeStatus.Cancelled; + SendTradeStatus(info); + } + + [WorldPacketHandler(ClientOpcodes.CancelTrade, Status = SessionStatus.LoggedinOrRecentlyLogout)] + void HandleCancelTrade(CancelTrade cancelTrade) + { + // sent also after LOGOUT COMPLETE + if (GetPlayer()) // needed because STATUS_LOGGEDIN_OR_RECENTLY_LOGGOUT + GetPlayer().TradeCancel(true); + } + + [WorldPacketHandler(ClientOpcodes.InitiateTrade)] + void HandleInitiateTrade(InitiateTrade initiateTrade) + { + if (GetPlayer().GetTradeData() != null) + return; + + TradeStatusPkt info = new TradeStatusPkt(); + if (!GetPlayer().IsAlive()) + { + info.Status = TradeStatus.Dead; + SendTradeStatus(info); + return; + } + + if (GetPlayer().HasUnitState(UnitState.Stunned)) + { + info.Status = TradeStatus.Stunned; + SendTradeStatus(info); + return; + } + + if (isLogingOut()) + { + info.Status = TradeStatus.LoggingOut; + SendTradeStatus(info); + return; + } + + if (GetPlayer().IsInFlight()) + { + info.Status = TradeStatus.TooFarAway; + SendTradeStatus(info); + return; + } + + if (GetPlayer().getLevel() < WorldConfig.GetIntValue(WorldCfg.TradeLevelReq)) + { + SendNotification(Global.ObjectMgr.GetCypherString(CypherStrings.TradeReq), WorldConfig.GetIntValue(WorldCfg.TradeLevelReq)); + return; + } + + + Player pOther = Global.ObjAccessor.FindPlayer(initiateTrade.Guid); + if (!pOther) + { + info.Status = TradeStatus.NoTarget; + SendTradeStatus(info); + return; + } + + if (pOther == GetPlayer() || pOther.GetTradeData() != null) + { + info.Status = TradeStatus.PlayerBusy; + SendTradeStatus(info); + return; + } + + if (!pOther.IsAlive()) + { + info.Status = TradeStatus.TargetDead; + SendTradeStatus(info); + return; + } + + if (pOther.IsInFlight()) + { + info.Status = TradeStatus.TooFarAway; + SendTradeStatus(info); + return; + } + + if (pOther.HasUnitState(UnitState.Stunned)) + { + info.Status = TradeStatus.TargetStunned; + SendTradeStatus(info); + return; + } + + if (pOther.GetSession().isLogingOut()) + { + info.Status = TradeStatus.TargetLoggingOut; + SendTradeStatus(info); + return; + } + + if (pOther.GetSocial().HasIgnore(GetPlayer().GetGUID())) + { + info.Status = TradeStatus.PlayerIgnored; + SendTradeStatus(info); + return; + } + + if ((pOther.GetTeam() != GetPlayer().GetTeam() || + pOther.HasFlag(PlayerFields.FlagsEx, PlayerFlagsEx.MercenaryMode) || + GetPlayer().HasFlag(PlayerFields.FlagsEx, PlayerFlagsEx.MercenaryMode)) && + (!WorldConfig.GetBoolValue(WorldCfg.AllowTwoSideTrade) && + !HasPermission(RBACPermissions.AllowTwoSideTrade))) + { + info.Status = TradeStatus.WrongFaction; + SendTradeStatus(info); + return; + } + + if (!pOther.IsWithinDistInMap(GetPlayer(), 11.11f, false)) + { + info.Status = TradeStatus.TooFarAway; + SendTradeStatus(info); + return; + } + + if (pOther.getLevel() < WorldConfig.GetIntValue(WorldCfg.TradeLevelReq)) + { + SendNotification(Global.ObjectMgr.GetCypherString(CypherStrings.TradeOtherReq), WorldConfig.GetIntValue(WorldCfg.TradeLevelReq)); + return; + } + + // OK start trade + GetPlayer().SetTradeData(new TradeData(GetPlayer(), pOther)); + pOther.SetTradeData(new TradeData(pOther, GetPlayer())); + + info.Status = TradeStatus.Proposed; + info.Partner = GetPlayer().GetGUID(); + pOther.GetSession().SendTradeStatus(info); + } + + [WorldPacketHandler(ClientOpcodes.SetTradeGold)] + void HandleSetTradeGold(SetTradeGold setTradeGold) + { + TradeData my_trade = GetPlayer().GetTradeData(); + if (my_trade == null) + return; + + my_trade.UpdateClientStateIndex(); + my_trade.SetMoney(setTradeGold.Coinage); + } + + [WorldPacketHandler(ClientOpcodes.SetTradeItem)] + void HandleSetTradeItem(SetTradeItem setTradeItem) + { + TradeData my_trade = GetPlayer().GetTradeData(); + if (my_trade == null) + return; + + TradeStatusPkt info = new TradeStatusPkt(); + // invalid slot number + if (setTradeItem.TradeSlot >= (byte)TradeSlots.Count) + { + info.Status = TradeStatus.Cancelled; + SendTradeStatus(info); + return; + } + + // check cheating, can't fail with correct client operations + Item item = GetPlayer().GetItemByPos(setTradeItem.PackSlot, setTradeItem.ItemSlotInPack); + if (!item || (setTradeItem.TradeSlot != (byte)TradeSlots.NonTraded && !item.CanBeTraded(false, true))) + { + info.Status = TradeStatus.Cancelled; + SendTradeStatus(info); + return; + } + + ObjectGuid iGUID = item.GetGUID(); + + // prevent place single item into many trade slots using cheating and client bugs + if (my_trade.HasItem(iGUID)) + { + // cheating attempt + info.Status = TradeStatus.Cancelled; + SendTradeStatus(info); + return; + } + + my_trade.UpdateClientStateIndex(); + if (setTradeItem.TradeSlot != (byte)TradeSlots.NonTraded && item.IsBindedNotWith(my_trade.GetTrader())) + { + info.Status = TradeStatus.NotOnTaplist; + info.TradeSlot = setTradeItem.TradeSlot; + SendTradeStatus(info); + return; + } + + my_trade.SetItem((TradeSlots)setTradeItem.TradeSlot, item); + } + + [WorldPacketHandler(ClientOpcodes.ClearTradeItem)] + void HandleClearTradeItem(ClearTradeItem clearTradeItem) + { + TradeData my_trade = GetPlayer().GetTradeData(); + if (my_trade == null) + return; + + my_trade.UpdateClientStateIndex(); + + // invalid slot number + if (clearTradeItem.TradeSlot >= (byte)TradeSlots.Count) + return; + + my_trade.SetItem((TradeSlots)clearTradeItem.TradeSlot, null); + } + + [WorldPacketHandler(ClientOpcodes.SetTradeCurrency)] + void HandleSetTradeCurrency(SetTradeCurrency setTradeCurrency) + { + } + } +} diff --git a/Game/Handlers/TransmogrificationHandler.cs b/Game/Handlers/TransmogrificationHandler.cs new file mode 100644 index 000000000..edfe597e4 --- /dev/null +++ b/Game/Handlers/TransmogrificationHandler.cs @@ -0,0 +1,295 @@ +/* + * Copyright (C) 2012-2017 CypherCore + * + * 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, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +using Framework.Constants; +using Game.DataStorage; +using Game.Entities; +using Game.Network; +using Game.Network.Packets; +using System; +using System.Collections.Generic; + +namespace Game +{ + public partial class WorldSession + { + [WorldPacketHandler(ClientOpcodes.TransmogrifyItems)] + void HandleTransmogrifyItems(TransmogrifyItems transmogrifyItems) + { + Player player = GetPlayer(); + + // Validate + if (!player.GetNPCIfCanInteractWith(transmogrifyItems.Npc, NPCFlags.Transmogrifier)) + { + Log.outDebug(LogFilter.Network, "WORLD: HandleTransmogrifyItems - Unit (GUID: {0}) not found or player can't interact with it.", transmogrifyItems.ToString()); + return; + } + + long cost = 0; + Dictionary transmogItems = new Dictionary(); + Dictionary illusionItems = new Dictionary(); + + List resetAppearanceItems = new List(); + List resetIllusionItems = new List(); + List bindAppearances = new List(); + + foreach (TransmogrifyItem transmogItem in transmogrifyItems.Items) + { + // slot of the transmogrified item + if (transmogItem.Slot >= EquipmentSlot.End) + { + Log.outDebug(LogFilter.Network, "WORLD: HandleTransmogrifyItems - Player ({0}, name: {1}) tried to transmogrify wrong slot {2} when transmogrifying items.", player.GetGUID().ToString(), player.GetName(), transmogItem.Slot); + return; + } + + // transmogrified item + Item itemTransmogrified = player.GetItemByPos(InventorySlots.Bag0, (byte)transmogItem.Slot); + if (!itemTransmogrified) + { + Log.outDebug(LogFilter.Network, "WORLD: HandleTransmogrifyItems - Player (GUID: {0}, name: {1}) tried to transmogrify an invalid item in a valid slot (slot: {2}).", player.GetGUID().ToString(), player.GetName(), transmogItem.Slot); + return; + } + + if (transmogItem.ItemModifiedAppearanceID != 0) + { + ItemModifiedAppearanceRecord itemModifiedAppearance = CliDB.ItemModifiedAppearanceStorage.LookupByKey(transmogItem.ItemModifiedAppearanceID); + if (itemModifiedAppearance == null) + { + Log.outDebug(LogFilter.Network, "WORLD: HandleTransmogrifyItems - {0}, Name: {1} tried to transmogrify using invalid appearance ({2}).", player.GetGUID().ToString(), player.GetName(), transmogItem.ItemModifiedAppearanceID); + return; + } + + var pairValue = GetCollectionMgr().HasItemAppearance((uint)transmogItem.ItemModifiedAppearanceID); + if (!pairValue.Item1) + { + Log.outDebug(LogFilter.Network, "WORLD: HandleTransmogrifyItems - {0}, Name: {1} tried to transmogrify using appearance he has not collected ({2}).", player.GetGUID().ToString(), player.GetName(), transmogItem.ItemModifiedAppearanceID); + return; + } + ItemTemplate itemTemplate = Global.ObjectMgr.GetItemTemplate(itemModifiedAppearance.ItemID); + if (player.CanUseItem(itemTemplate) != InventoryResult.Ok) + { + Log.outDebug(LogFilter.Network, "WORLD: HandleTransmogrifyItems - {0}, Name: {1} tried to transmogrify using appearance he can never use ({2}).", player.GetGUID().ToString(), player.GetName(), transmogItem.ItemModifiedAppearanceID); + return; + } + + // validity of the transmogrification items + if (!Item.CanTransmogrifyItemWithItem(itemTransmogrified, itemModifiedAppearance)) + { + Log.outDebug(LogFilter.Network, "WORLD: HandleTransmogrifyItems - {0}, Name: {1} failed CanTransmogrifyItemWithItem ({2} with appearance {3}).", player.GetGUID().ToString(), player.GetName(), itemTransmogrified.GetEntry(), transmogItem.ItemModifiedAppearanceID); + return; + } + + transmogItems[itemTransmogrified] = (uint)transmogItem.ItemModifiedAppearanceID; + if (pairValue.Item2) + bindAppearances.Add((uint)transmogItem.ItemModifiedAppearanceID); + + // add cost + cost += itemTransmogrified.GetSpecialPrice(); + } + else + resetAppearanceItems.Add(itemTransmogrified); + + if (transmogItem.SpellItemEnchantmentID != 0) + { + if (transmogItem.Slot != EquipmentSlot.MainHand && transmogItem.Slot != EquipmentSlot.OffHand) + { + Log.outDebug(LogFilter.Network, "WORLD: HandleTransmogrifyItems - {0}, Name: {1} tried to transmogrify illusion into non-weapon slot ({2}).", player.GetGUID().ToString(), player.GetName(), transmogItem.Slot); + return; + } + + SpellItemEnchantmentRecord illusion = CliDB.SpellItemEnchantmentStorage.LookupByKey(transmogItem.SpellItemEnchantmentID); + if (illusion == null) + { + Log.outDebug(LogFilter.Network, "WORLD: HandleTransmogrifyItems - {0}, Name: {1} tried to transmogrify illusion using invalid enchant ({2}).", player.GetGUID().ToString(), player.GetName(), transmogItem.SpellItemEnchantmentID); + return; + } + + if (illusion.ItemVisual == 0 || !illusion.Flags.HasAnyFlag(EnchantmentSlotMask.Collectable)) + { + Log.outDebug(LogFilter.Network, "WORLD: HandleTransmogrifyItems - {0}, Name: {1} tried to transmogrify illusion using not allowed enchant ({2}).", player.GetGUID().ToString(), player.GetName(), transmogItem.SpellItemEnchantmentID); + return; + } + + PlayerConditionRecord condition = CliDB.PlayerConditionStorage.LookupByKey(illusion.PlayerConditionID); + if (condition != null) + { + if (!ConditionManager.IsPlayerMeetingCondition(player, condition)) + { + Log.outDebug(LogFilter.Network, "WORLD: HandleTransmogrifyItems - {0}, Name: {1} tried to transmogrify illusion using not collected enchant ({2}).", player.GetGUID().ToString(), player.GetName(), transmogItem.SpellItemEnchantmentID); + return; + } + } + + if (illusion.ScalingClassRestricted > 0 && illusion.ScalingClassRestricted != (byte)player.GetClass()) + { + Log.outDebug(LogFilter.Network, "WORLD: HandleTransmogrifyItems - {0}, Name: {1} tried to transmogrify illusion using not allowed class enchant ({2}).", player.GetGUID().ToString(), player.GetName(), transmogItem.SpellItemEnchantmentID); + return; + } + + illusionItems[itemTransmogrified] = (uint)transmogItem.SpellItemEnchantmentID; + cost += illusion.TransmogCost; + } + else + resetIllusionItems.Add(itemTransmogrified); + } + + if (cost != 0) // 0 cost if reverting look + { + if (!player.HasEnoughMoney(cost)) + return; + + player.ModifyMoney(-cost); + } + + // Everything is fine, proceed + foreach (var transmogPair in transmogItems) + { + Item transmogrified = transmogPair.Key; + + if (!transmogrifyItems.CurrentSpecOnly) + { + transmogrified.SetModifier(ItemModifier.TransmogAppearanceAllSpecs, transmogPair.Value); + transmogrified.SetModifier(ItemModifier.TransmogAppearanceSpec1, 0); + transmogrified.SetModifier(ItemModifier.TransmogAppearanceSpec2, 0); + transmogrified.SetModifier(ItemModifier.TransmogAppearanceSpec3, 0); + transmogrified.SetModifier(ItemModifier.TransmogAppearanceSpec4, 0); + } + else + { + if (transmogrified.GetModifier(ItemModifier.TransmogAppearanceSpec1) == 0) + transmogrified.SetModifier(ItemModifier.TransmogAppearanceSpec1, transmogrified.GetModifier(ItemModifier.TransmogAppearanceAllSpecs)); + if (transmogrified.GetModifier(ItemModifier.TransmogAppearanceSpec2) == 0) + transmogrified.SetModifier(ItemModifier.TransmogAppearanceSpec2, transmogrified.GetModifier(ItemModifier.TransmogAppearanceAllSpecs)); + if (transmogrified.GetModifier(ItemModifier.TransmogAppearanceSpec3) == 0) + transmogrified.SetModifier(ItemModifier.TransmogAppearanceSpec3, transmogrified.GetModifier(ItemModifier.TransmogAppearanceAllSpecs)); + if (transmogrified.GetModifier(ItemModifier.TransmogAppearanceSpec4) == 0) + transmogrified.SetModifier(ItemModifier.TransmogAppearanceSpec4, transmogrified.GetModifier(ItemModifier.TransmogAppearanceAllSpecs)); + transmogrified.SetModifier(ItemConst.AppearanceModifierSlotBySpec[player.GetActiveTalentGroup()], transmogPair.Value); + } + + player.SetVisibleItemSlot(transmogrified.GetSlot(), transmogrified); + + transmogrified.SetNotRefundable(player); + transmogrified.ClearSoulboundTradeable(player); + transmogrified.SetState(ItemUpdateState.Changed, player); + } + + foreach (var illusionPair in illusionItems) + { + Item transmogrified = illusionPair.Key; + + if (!transmogrifyItems.CurrentSpecOnly) + { + transmogrified.SetModifier(ItemModifier.EnchantIllusionAllSpecs, illusionPair.Value); + transmogrified.SetModifier(ItemModifier.EnchantIllusionSpec1, 0); + transmogrified.SetModifier(ItemModifier.EnchantIllusionSpec2, 0); + transmogrified.SetModifier(ItemModifier.EnchantIllusionSpec3, 0); + transmogrified.SetModifier(ItemModifier.EnchantIllusionSpec4, 0); + } + else + { + if (transmogrified.GetModifier(ItemModifier.EnchantIllusionSpec1) == 0) + transmogrified.SetModifier(ItemModifier.EnchantIllusionSpec1, transmogrified.GetModifier(ItemModifier.EnchantIllusionAllSpecs)); + if (transmogrified.GetModifier(ItemModifier.EnchantIllusionSpec2) == 0) + transmogrified.SetModifier(ItemModifier.EnchantIllusionSpec2, transmogrified.GetModifier(ItemModifier.EnchantIllusionAllSpecs)); + if (transmogrified.GetModifier(ItemModifier.EnchantIllusionSpec3) == 0) + transmogrified.SetModifier(ItemModifier.EnchantIllusionSpec3, transmogrified.GetModifier(ItemModifier.EnchantIllusionAllSpecs)); + if (transmogrified.GetModifier(ItemModifier.EnchantIllusionSpec4) == 0) + transmogrified.SetModifier(ItemModifier.EnchantIllusionSpec4, transmogrified.GetModifier(ItemModifier.EnchantIllusionAllSpecs)); + transmogrified.SetModifier(ItemConst.IllusionModifierSlotBySpec[player.GetActiveTalentGroup()], illusionPair.Value); + } + + player.SetVisibleItemSlot(transmogrified.GetSlot(), transmogrified); + + transmogrified.SetNotRefundable(player); + transmogrified.ClearSoulboundTradeable(player); + transmogrified.SetState(ItemUpdateState.Changed, player); + } + + foreach (Item item in resetAppearanceItems) + { + if (!transmogrifyItems.CurrentSpecOnly) + { + item.SetModifier(ItemModifier.TransmogAppearanceAllSpecs, 0); + item.SetModifier(ItemModifier.TransmogAppearanceSpec1, 0); + item.SetModifier(ItemModifier.TransmogAppearanceSpec2, 0); + item.SetModifier(ItemModifier.TransmogAppearanceSpec3, 0); + item.SetModifier(ItemModifier.TransmogAppearanceSpec4, 0); + } + else + { + if (item.GetModifier(ItemModifier.TransmogAppearanceSpec1) == 0) + item.SetModifier(ItemModifier.TransmogAppearanceSpec1, item.GetModifier(ItemModifier.TransmogAppearanceAllSpecs)); + if (item.GetModifier(ItemModifier.TransmogAppearanceSpec2) == 0) + item.SetModifier(ItemModifier.TransmogAppearanceSpec2, item.GetModifier(ItemModifier.TransmogAppearanceAllSpecs)); + if (item.GetModifier(ItemModifier.TransmogAppearanceSpec2) == 0) + item.SetModifier(ItemModifier.TransmogAppearanceSpec3, item.GetModifier(ItemModifier.TransmogAppearanceAllSpecs)); + if (item.GetModifier(ItemModifier.TransmogAppearanceSpec4) == 0) + item.SetModifier(ItemModifier.TransmogAppearanceSpec4, item.GetModifier(ItemModifier.TransmogAppearanceAllSpecs)); + item.SetModifier(ItemConst.AppearanceModifierSlotBySpec[player.GetActiveTalentGroup()], 0); + item.SetModifier(ItemModifier.EnchantIllusionAllSpecs, 0); + } + + item.SetState(ItemUpdateState.Changed, player); + player.SetVisibleItemSlot(item.GetSlot(), item); + } + + foreach (Item item in resetIllusionItems) + { + if (!transmogrifyItems.CurrentSpecOnly) + { + item.SetModifier(ItemModifier.EnchantIllusionAllSpecs, 0); + item.SetModifier(ItemModifier.EnchantIllusionSpec1, 0); + item.SetModifier(ItemModifier.EnchantIllusionSpec2, 0); + item.SetModifier(ItemModifier.EnchantIllusionSpec3, 0); + item.SetModifier(ItemModifier.EnchantIllusionSpec4, 0); + } + else + { + if (item.GetModifier(ItemModifier.EnchantIllusionSpec1) == 0) + item.SetModifier(ItemModifier.EnchantIllusionSpec1, item.GetModifier(ItemModifier.EnchantIllusionAllSpecs)); + if (item.GetModifier(ItemModifier.EnchantIllusionSpec2) == 0) + item.SetModifier(ItemModifier.EnchantIllusionSpec2, item.GetModifier(ItemModifier.EnchantIllusionAllSpecs)); + if (item.GetModifier(ItemModifier.EnchantIllusionSpec3) == 0) + item.SetModifier(ItemModifier.EnchantIllusionSpec3, item.GetModifier(ItemModifier.EnchantIllusionAllSpecs)); + if (item.GetModifier(ItemModifier.EnchantIllusionSpec4) == 0) + item.SetModifier(ItemModifier.EnchantIllusionSpec4, item.GetModifier(ItemModifier.EnchantIllusionAllSpecs)); + item.SetModifier(ItemConst.IllusionModifierSlotBySpec[player.GetActiveTalentGroup()], 0); + item.SetModifier(ItemModifier.TransmogAppearanceAllSpecs, 0); + } + + item.SetState(ItemUpdateState.Changed, player); + player.SetVisibleItemSlot(item.GetSlot(), item); + } + + foreach (uint itemModifedAppearanceId in bindAppearances) + { + var itemsProvidingAppearance = GetCollectionMgr().GetItemsProvidingTemporaryAppearance(itemModifedAppearanceId); + foreach (ObjectGuid itemGuid in itemsProvidingAppearance) + { + Item item = player.GetItemByGuid(itemGuid); + if (item) + { + item.SetNotRefundable(player); + item.ClearSoulboundTradeable(player); + GetCollectionMgr().AddItemAppearance(item); + } + } + } + } + } +} diff --git a/Game/Handlers/VehicleHandler.cs b/Game/Handlers/VehicleHandler.cs new file mode 100644 index 000000000..45bdb6dc3 --- /dev/null +++ b/Game/Handlers/VehicleHandler.cs @@ -0,0 +1,216 @@ +/* + * Copyright (C) 2012-2017 CypherCore + * + * 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, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +using Framework.Constants; +using Game.DataStorage; +using Game.Entities; +using Game.Network; +using Game.Network.Packets; +using System.Diagnostics.Contracts; + +namespace Game +{ + public partial class WorldSession + { + [WorldPacketHandler(ClientOpcodes.MoveDismissVehicle)] + void HandleMoveDismissVehicle(MoveDismissVehicle packet) + { + ObjectGuid vehicleGUID = GetPlayer().GetCharmGUID(); + if (vehicleGUID.IsEmpty()) // something wrong here... + return; + + GetPlayer().ValidateMovementInfo(packet.Status); + GetPlayer().m_movementInfo = packet.Status; + + GetPlayer().ExitVehicle(); + } + + [WorldPacketHandler(ClientOpcodes.RequestVehiclePrevSeat)] + void HandleRequestVehiclePrevSeat(RequestVehiclePrevSeat packet) + { + Unit vehicle_base = GetPlayer().GetVehicleBase(); + if (!vehicle_base) + return; + + VehicleSeatRecord seat = GetPlayer().GetVehicle().GetSeatForPassenger(GetPlayer()); + if (!seat.CanSwitchFromSeat()) + { + Log.outError(LogFilter.Network, "HandleRequestVehiclePrevSeat: {0} tried to switch seats but current seatflags {1} don't permit that.", + GetPlayer().GetGUID().ToString(), seat.Flags[0]); + return; + } + + GetPlayer().ChangeSeat(-1, false); + } + + [WorldPacketHandler(ClientOpcodes.RequestVehicleNextSeat)] + void HandleRequestVehicleNextSeat(RequestVehicleNextSeat packet) + { + Unit vehicle_base = GetPlayer().GetVehicleBase(); + if (!vehicle_base) + return; + + VehicleSeatRecord seat = GetPlayer().GetVehicle().GetSeatForPassenger(GetPlayer()); + if (!seat.CanSwitchFromSeat()) + { + Log.outError(LogFilter.Network, "HandleRequestVehicleNextSeat: {0} tried to switch seats but current seatflags {1} don't permit that.", + GetPlayer().GetGUID().ToString(), seat.Flags[0]); + return; + } + + GetPlayer().ChangeSeat(-1, true); + } + + [WorldPacketHandler(ClientOpcodes.MoveChangeVehicleSeats)] + void HandleMoveChangeVehicleSeats(MoveChangeVehicleSeats packet) + { + Unit vehicle_base = GetPlayer().GetVehicleBase(); + if (!vehicle_base) + return; + + VehicleSeatRecord seat = GetPlayer().GetVehicle().GetSeatForPassenger(GetPlayer()); + if (!seat.CanSwitchFromSeat()) + { + Log.outError(LogFilter.Network, "HandleMoveChangeVehicleSeats, {0} tried to switch seats but current seatflags {1} don't permit that.", + GetPlayer().GetGUID().ToString(), seat.Flags[0]); + return; + } + + GetPlayer().ValidateMovementInfo(packet.Status); + + if (vehicle_base.GetGUID() != packet.Status.Guid) + return; + + vehicle_base.m_movementInfo = packet.Status; + + Unit vehUnit; + if (packet.DstVehicle.IsEmpty()) + GetPlayer().ChangeSeat(-1, packet.DstSeatIndex != 255); + else if (vehUnit = Global.ObjAccessor.GetUnit(GetPlayer(), packet.DstVehicle)) + { + Vehicle vehicle = vehUnit.GetVehicleKit(); + if (vehicle) + if (vehicle.HasEmptySeat((sbyte)packet.DstSeatIndex)) + vehUnit.HandleSpellClick(GetPlayer(), (sbyte)packet.DstSeatIndex); + } + } + + [WorldPacketHandler(ClientOpcodes.RequestVehicleSwitchSeat)] + void HandleRequestVehicleSwitchSeat(RequestVehicleSwitchSeat packet) + { + Unit vehicle_base = GetPlayer().GetVehicleBase(); + if (!vehicle_base) + return; + + VehicleSeatRecord seat = GetPlayer().GetVehicle().GetSeatForPassenger(GetPlayer()); + if (!seat.CanSwitchFromSeat()) + { + Log.outError(LogFilter.Network, "HandleRequestVehicleSwitchSeat: {0} tried to switch seats but current seatflags {1} don't permit that.", + GetPlayer().GetGUID().ToString(), seat.Flags[0]); + return; + } + Unit vehUnit; + if (vehicle_base.GetGUID() == packet.Vehicle) + GetPlayer().ChangeSeat((sbyte)packet.SeatIndex); + else if (vehUnit = Global.ObjAccessor.GetUnit(GetPlayer(), packet.Vehicle)) + { + Vehicle vehicle = vehUnit.GetVehicleKit(); + if (vehicle) + if (vehicle.HasEmptySeat((sbyte)packet.SeatIndex)) + vehUnit.HandleSpellClick(GetPlayer(), (sbyte)packet.SeatIndex); + } + } + + [WorldPacketHandler(ClientOpcodes.RideVehicleInteract)] + void HandleRideVehicleInteract(RideVehicleInteract packet) + { + Player player = Global.ObjAccessor.FindPlayer(packet.Vehicle); + if (player) + { + if (!player.GetVehicleKit()) + return; + if (!player.IsInRaidWith(GetPlayer())) + return; + if (!player.IsWithinDistInMap(GetPlayer(), SharedConst.InteractionDistance)) + return; + + GetPlayer().EnterVehicle(player); + } + } + + [WorldPacketHandler(ClientOpcodes.EjectPassenger)] + void HandleEjectPassenger(EjectPassenger packet) + { + Vehicle vehicle = GetPlayer().GetVehicleKit(); + if (!vehicle) + { + Log.outError(LogFilter.Network, "HandleEjectPassenger: {0} is not in a vehicle!", GetPlayer().GetGUID().ToString()); + return; + } + + if (packet.Passenger.IsUnit()) + { + Unit unit = Global.ObjAccessor.GetUnit(GetPlayer(), packet.Passenger); + if (!unit) + { + Log.outError(LogFilter.Network, "{0} tried to eject {1} from vehicle, but the latter was not found in world!", GetPlayer().GetGUID().ToString(), packet.Passenger.ToString()); + return; + } + + if (!unit.IsOnVehicle(vehicle.GetBase())) + { + Log.outError(LogFilter.Network, "{0} tried to eject {1}, but they are not in the same vehicle", GetPlayer().GetGUID().ToString(), packet.Passenger.ToString()); + return; + } + + VehicleSeatRecord seat = vehicle.GetSeatForPassenger(unit); + Contract.Assert(seat != null); + if (seat.IsEjectable()) + unit.ExitVehicle(); + else + Log.outError(LogFilter.Network, "{0} attempted to eject {1} from non-ejectable seat.", GetPlayer().GetGUID().ToString(), packet.Passenger.ToString()); + } + + else + Log.outError(LogFilter.Network, "HandleEjectPassenger: {0} tried to eject invalid {1}", GetPlayer().GetGUID().ToString(), packet.Passenger.ToString()); + } + + [WorldPacketHandler(ClientOpcodes.RequestVehicleExit)] + void HandleRequestVehicleExit(RequestVehicleExit packet) + { + Vehicle vehicle = GetPlayer().GetVehicle(); + if (vehicle) + { + VehicleSeatRecord seat = vehicle.GetSeatForPassenger(GetPlayer()); + if (seat != null) + { + if (seat.CanEnterOrExit()) + GetPlayer().ExitVehicle(); + else + Log.outError(LogFilter.Network, "{0} tried to exit vehicle, but seatflags {1} (ID: {2}) don't permit that.", + GetPlayer().GetGUID().ToString(), seat.Id, seat.Flags[0]); + } + } + } + + [WorldPacketHandler(ClientOpcodes.MoveSetVehicleRecIdAck)] + void HandleMoveSetVehicleRecAck(MoveSetVehicleRecIdAck setVehicleRecIdAck) + { + GetPlayer().ValidateMovementInfo(setVehicleRecIdAck.Data.movementInfo); + } + } +} diff --git a/Game/Handlers/VoidStorageHandler.cs b/Game/Handlers/VoidStorageHandler.cs new file mode 100644 index 000000000..214e975e5 --- /dev/null +++ b/Game/Handlers/VoidStorageHandler.cs @@ -0,0 +1,266 @@ +/* + * Copyright (C) 2012-2017 CypherCore + * + * 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, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +using Framework.Constants; +using Game.Entities; +using Game.Network; +using Game.Network.Packets; +using System.Collections.Generic; + +namespace Game +{ + public partial class WorldSession + { + public void SendVoidStorageTransferResult(VoidTransferError result) + { + SendPacket(new VoidTransferResult(result)); + } + + [WorldPacketHandler(ClientOpcodes.UnlockVoidStorage)] + void HandleVoidStorageUnlock(UnlockVoidStorage unlockVoidStorage) + { + Creature unit = GetPlayer().GetNPCIfCanInteractWith(unlockVoidStorage.Npc, NPCFlags.VaultKeeper); + if (!unit) + { + Log.outDebug(LogFilter.Network, "WORLD: HandleVoidStorageUnlock - {0} not found or player can't interact with it.", unlockVoidStorage.Npc.ToString()); + return; + } + + if (GetPlayer().IsVoidStorageUnlocked()) + { + Log.outDebug(LogFilter.Network, "WORLD: HandleVoidStorageUnlock - Player({0}, name: {1}) tried to unlock void storage a 2nd time.", GetPlayer().GetGUID().ToString(), GetPlayer().GetName()); + return; + } + + GetPlayer().ModifyMoney(-SharedConst.VoidStorageUnlockCost); + GetPlayer().UnlockVoidStorage(); + } + + [WorldPacketHandler(ClientOpcodes.QueryVoidStorage)] + void HandleVoidStorageQuery(QueryVoidStorage queryVoidStorage) + { + Player player = GetPlayer(); + + Creature unit = player.GetNPCIfCanInteractWith(queryVoidStorage.Npc, NPCFlags.Transmogrifier | NPCFlags.VaultKeeper); + if (!unit) + { + Log.outDebug(LogFilter.Network, "WORLD: HandleVoidStorageQuery - {0} not found or player can't interact with it.", queryVoidStorage.Npc.ToString()); + SendPacket(new VoidStorageFailed()); + return; + } + + if (!GetPlayer().IsVoidStorageUnlocked()) + { + Log.outDebug(LogFilter.Network, "WORLD: HandleVoidStorageQuery - {0} name: {1} queried void storage without unlocking it.", player.GetGUID().ToString(), player.GetName()); + SendPacket(new VoidStorageFailed()); + return; + } + + byte count = 0; + for (byte i = 0; i < SharedConst.VoidStorageMaxSlot; ++i) + if (player.GetVoidStorageItem(i) != null) + ++count; + + VoidStorageContents voidStorageContents = new VoidStorageContents(); + for (byte i = 0; i < SharedConst.VoidStorageMaxSlot; ++i) + { + VoidStorageItem item = player.GetVoidStorageItem(i); + if (item == null) + continue; + + VoidItem voidItem = new VoidItem(); + voidItem.Guid = ObjectGuid.Create(HighGuid.Item, item.ItemId); + voidItem.Creator = item.CreatorGuid; + voidItem.Slot = i; + voidItem.Item = new ItemInstance(item); + + voidStorageContents.Items.Add(voidItem); + } + + SendPacket(voidStorageContents); + } + + [WorldPacketHandler(ClientOpcodes.VoidStorageTransfer)] + void HandleVoidStorageTransfer(VoidStorageTransfer voidStorageTransfer) + { + Player player = GetPlayer(); + + Creature unit = player.GetNPCIfCanInteractWith(voidStorageTransfer.Npc, NPCFlags.VaultKeeper); + if (!unit) + { + Log.outDebug(LogFilter.Network, "WORLD: HandleVoidStorageTransfer - {0} not found or player can't interact with it.", voidStorageTransfer.Npc.ToString()); + return; + } + + if (!player.IsVoidStorageUnlocked()) + { + Log.outDebug(LogFilter.Network, "WORLD: HandleVoidStorageTransfer - Player ({0}, name: {1}) queried void storage without unlocking it.", player.GetGUID().ToString(), player.GetName()); + return; + } + + if (voidStorageTransfer.Deposits.Length > player.GetNumOfVoidStorageFreeSlots()) + { + SendVoidStorageTransferResult(VoidTransferError.Full); + return; + } + + uint freeBagSlots = 0; + if (!voidStorageTransfer.Withdrawals.Empty()) + { + // make this a Player function + for (byte i = InventorySlots.BagStart; i < InventorySlots.BagEnd; i++) + { + Bag bag = player.GetBagByPos(i); + if (bag) + freeBagSlots += bag.GetFreeSlots(); + } + for (byte i = InventorySlots.ItemStart; i < InventorySlots.ItemEnd; i++) + { + if (!player.GetItemByPos(InventorySlots.Bag0, i)) + ++freeBagSlots; + } + } + + if (voidStorageTransfer.Withdrawals.Length > freeBagSlots) + { + SendVoidStorageTransferResult(VoidTransferError.InventoryFull); + return; + } + + if (!player.HasEnoughMoney((voidStorageTransfer.Deposits.Length * SharedConst.VoidStorageStoreItemCost))) + { + SendVoidStorageTransferResult(VoidTransferError.NotEnoughMoney); + return; + } + + VoidStorageTransferChanges voidStorageTransferChanges = new VoidStorageTransferChanges(); + + byte depositCount = 0; + for (int i = 0; i < voidStorageTransfer.Deposits.Length; ++i) + { + Item item = player.GetItemByGuid(voidStorageTransfer.Deposits[i]); + if (!item) + { + Log.outDebug(LogFilter.Network, "WORLD: HandleVoidStorageTransfer - {0} {1} wants to deposit an invalid item ({2}).", player.GetGUID().ToString(), player.GetName(), voidStorageTransfer.Deposits[i].ToString()); + continue; + } + + VoidStorageItem itemVS = new VoidStorageItem(Global.ObjectMgr.GenerateVoidStorageItemId(), item.GetEntry(), item.GetGuidValue(ItemFields.Creator), + item.GetItemRandomEnchantmentId(), item.GetItemSuffixFactor(), item.GetModifier(ItemModifier.UpgradeId), + item.GetModifier(ItemModifier.ScalingStatDistributionFixedLevel), item.GetModifier(ItemModifier.ArtifactKnowledgeLevel), + (byte)item.GetUInt32Value(ItemFields.Context), item.GetDynamicValues(ItemDynamicFields.BonusListIds)); + + VoidItem voidItem; + voidItem.Guid = ObjectGuid.Create(HighGuid.Item, itemVS.ItemId); + voidItem.Creator = item.GetGuidValue(ItemFields.Creator); + voidItem.Item = new ItemInstance(itemVS); + voidItem.Slot = _player.AddVoidStorageItem(itemVS); + + voidStorageTransferChanges.AddedItems.Add(voidItem); + + player.DestroyItem(item.GetBagSlot(), item.GetSlot(), true); + ++depositCount; + } + + long cost = depositCount * SharedConst.VoidStorageStoreItemCost; + + player.ModifyMoney(-cost); + + for (int i = 0; i < voidStorageTransfer.Withdrawals.Length; ++i) + { + byte slot; + VoidStorageItem itemVS = player.GetVoidStorageItem(voidStorageTransfer.Withdrawals[i].GetCounter(), out slot); + if (itemVS == null) + { + Log.outDebug(LogFilter.Network, "WORLD: HandleVoidStorageTransfer - {0} {1} tried to withdraw an invalid item ({2})", player.GetGUID().ToString(), player.GetName(), voidStorageTransfer.Withdrawals[i].ToString()); + continue; + } + + List dest = new List(); + InventoryResult msg = player.CanStoreNewItem(ItemConst.NullBag, ItemConst.NullSlot, dest, itemVS.ItemEntry, 1); + if (msg != InventoryResult.Ok) + { + SendVoidStorageTransferResult(VoidTransferError.InventoryFull); + Log.outDebug(LogFilter.Network, "WORLD: HandleVoidStorageTransfer - {0} {1} couldn't withdraw {2} because inventory was full.", player.GetGUID().ToString(), player.GetName(), voidStorageTransfer.Withdrawals[i].ToString()); + return; + } + + Item item = player.StoreNewItem(dest, itemVS.ItemEntry, true, itemVS.ItemRandomPropertyId, null, itemVS.Context, itemVS.BonusListIDs); + item.SetUInt32Value(ItemFields.PropertySeed, itemVS.ItemSuffixFactor); + item.SetGuidValue(ItemFields.Creator, itemVS.CreatorGuid); + item.SetModifier(ItemModifier.UpgradeId, itemVS.ItemUpgradeId); + item.SetBinding(true); + GetCollectionMgr().AddItemAppearance(item); + + voidStorageTransferChanges.RemovedItems.Add(ObjectGuid.Create(HighGuid.Item, itemVS.ItemId)); + + player.DeleteVoidStorageItem(slot); + } + + SendPacket(voidStorageTransferChanges); + SendVoidStorageTransferResult(VoidTransferError.Ok); + } + + [WorldPacketHandler(ClientOpcodes.SwapVoidItem)] + void HandleVoidSwapItem(SwapVoidItem swapVoidItem) + { + Player player = GetPlayer(); + + Creature unit = player.GetNPCIfCanInteractWith(swapVoidItem.Npc, NPCFlags.VaultKeeper); + if (!unit) + { + Log.outDebug(LogFilter.Network, "WORLD: HandleVoidSwapItem - {0} not found or player can't interact with it.", swapVoidItem.Npc.ToString()); + return; + } + + if (!player.IsVoidStorageUnlocked()) + { + Log.outDebug(LogFilter.Network, "WORLD: HandleVoidSwapItem - Player ({0}, name: {1}) queried void storage without unlocking it.", player.GetGUID().ToString(), player.GetName()); + return; + } + + byte oldSlot; + if (player.GetVoidStorageItem(swapVoidItem.VoidItemGuid.GetCounter(), out oldSlot) == null) + { + Log.outDebug(LogFilter.Network, "WORLD: HandleVoidSwapItem - Player (GUID: {0}, name: {1}) requested swapping an invalid item (slot: {2}, itemid: {3}).", player.GetGUID().ToString(), player.GetName(), swapVoidItem.DstSlot, swapVoidItem.VoidItemGuid.ToString()); + return; + } + + bool usedDestSlot = player.GetVoidStorageItem((byte)swapVoidItem.DstSlot) != null; + ObjectGuid itemIdDest = ObjectGuid.Empty; + if (usedDestSlot) + itemIdDest = ObjectGuid.Create(HighGuid.Item, player.GetVoidStorageItem((byte)swapVoidItem.DstSlot).ItemId); + + if (!player.SwapVoidStorageItem(oldSlot, (byte)swapVoidItem.DstSlot)) + { + SendVoidStorageTransferResult(VoidTransferError.InternalError1); + return; + } + + VoidItemSwapResponse voidItemSwapResponse = new VoidItemSwapResponse(); + voidItemSwapResponse.VoidItemA = swapVoidItem.VoidItemGuid; + voidItemSwapResponse.VoidItemSlotA = swapVoidItem.DstSlot; + if (usedDestSlot) + { + voidItemSwapResponse.VoidItemB = itemIdDest; + voidItemSwapResponse.VoidItemSlotB = oldSlot; + } + + SendPacket(voidItemSwapResponse); + } + } +} diff --git a/Game/Loot/Loot.cs b/Game/Loot/Loot.cs new file mode 100644 index 000000000..ea91f16ff --- /dev/null +++ b/Game/Loot/Loot.cs @@ -0,0 +1,918 @@ +/* + * Copyright (C) 2012-2017 CypherCore + * + * 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, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +using Framework.Constants; +using Framework.Database; +using Framework.Dynamic; +using Game.Conditions; +using Game.Entities; +using Game.Groups; +using Game.Network.Packets; +using System; +using System.Collections.Generic; + +namespace Game.Loots +{ + public class LootItem + { + public LootItem(LootStoreItem li) + { + itemid = li.itemid; + conditions = li.conditions; + + ItemTemplate proto = Global.ObjectMgr.GetItemTemplate(itemid); + freeforall = proto != null && proto.GetFlags().HasAnyFlag(ItemFlags.MultiDrop); + follow_loot_rules = proto != null && proto.FlagsCu.HasAnyFlag(ItemFlagsCustom.FollowLootRules); + + needs_quest = li.needs_quest; + + randomSuffix = ItemEnchantment.GenerateEnchSuffixFactor(itemid); + randomPropertyId = ItemEnchantment.GenerateItemRandomPropertyId(itemid); + upgradeId = Global.DB2Mgr.GetRulesetItemUpgrade(itemid); + canSave = true; + } + + public LootItem() + { + canSave = true; + } + + public bool AllowedForPlayer(Player player) + { + // DB conditions check + if (!Global.ConditionMgr.IsObjectMeetToConditions(player, conditions)) + return false; + + ItemTemplate pProto = Global.ObjectMgr.GetItemTemplate(itemid); + if (pProto == null) + return false; + + // not show loot for players without profession or those who already know the recipe + if (pProto.GetFlags().HasAnyFlag(ItemFlags.HideUnusableRecipe) && (!player.HasSkill((SkillType)pProto.GetRequiredSkill()) || player.HasSpell(pProto.Effects[1].SpellID))) + return false; + + // not show loot for not own team + if (pProto.GetFlags2().HasAnyFlag(ItemFlags2.FactionHorde) && player.GetTeam() != Team.Horde) + return false; + + if (pProto.GetFlags2().HasAnyFlag(ItemFlags2.FactionAlliance) && player.GetTeam() != Team.Alliance) + return false; + + // check quest requirements + if (!pProto.FlagsCu.HasAnyFlag(ItemFlagsCustom.IgnoreQuestStatus) + && ((needs_quest || (pProto.GetStartQuest() != 0 && player.GetQuestStatus(pProto.GetStartQuest()) != QuestStatus.None)) && !player.HasQuestForItem(itemid))) + return false; + + // Don't show bind-when-picked-up unique items if player already has the maximum allowed quantity. + if (pProto.GetBonding() == ItemBondingType.OnAcquire && pProto.GetMaxCount() != 0 && player.GetItemCount(itemid, true) >= pProto.GetMaxCount()) + return false; + + return true; + } + + public void AddAllowedLooter(Player player) + { + allowedGUIDs.Add(player.GetGUID()); + } + + public List GetAllowedLooters() { return allowedGUIDs; } + + public uint itemid; + public uint randomSuffix; + public ItemRandomEnchantmentId randomPropertyId; + public uint upgradeId; + public List BonusListIDs = new List(); + public byte context; + public List conditions = new List(); // additional loot condition + public List allowedGUIDs = new List(); + public byte count; + public bool is_looted; + public bool is_blocked; + public bool freeforall; // free for all + public bool is_underthreshold; + public bool is_counted; + public bool needs_quest; // quest drop + public bool follow_loot_rules; + public bool canSave; + } + + public class QuestItem + { + public byte index; // position in quest_items; + public bool is_looted; + + public QuestItem() + { + index = 0; + is_looted = false; + } + + public QuestItem(byte _index, bool _islooted = false) + { + index = _index; + is_looted = _islooted; + } + } + + public class LootValidatorRef : Reference + { + public LootValidatorRef() + { + + } + + public override void targetObjectDestroyLink() + { + + } + + public override void sourceObjectDestroyLink() + { + + } + } + + public class LootValidatorRefManager : RefManager + { + public new LootValidatorRef getFirst() { return (LootValidatorRef)base.getFirst(); } + public new LootValidatorRef getLast() { return (LootValidatorRef)base.getLast(); } + } + + public class Loot + { + public Loot(uint _gold = 0) + { + gold = _gold; + unlootedCount = 0; + loot_type = LootType.None; + maxDuplicates = 1; + containerID = ObjectGuid.Empty; + } + + // Inserts the item into the loot (called by LootTemplate processors) + public void AddItem(LootStoreItem item) + { + ItemTemplate proto = Global.ObjectMgr.GetItemTemplate(item.itemid); + if (proto == null) + return; + + uint count = RandomHelper.URand(item.mincount, item.maxcount); + uint stacks = (uint)(count / proto.GetMaxStackSize() + (Convert.ToBoolean(count % proto.GetMaxStackSize()) ? 1 : 0)); + + List lootItems = item.needs_quest ? quest_items : items; + uint limit = (uint)(item.needs_quest ? SharedConst.MaxNRQuestItems : SharedConst.MaxNRLootItems); + + for (uint i = 0; i < stacks && lootItems.Count < limit; ++i) + { + LootItem generatedLoot = new LootItem(item); + generatedLoot.context = _difficultyBonusTreeMod; + generatedLoot.count = (byte)Math.Min(count, proto.GetMaxStackSize()); + if (_difficultyBonusTreeMod != 0) + { + List bonusListIDs = Global.DB2Mgr.GetItemBonusTree(generatedLoot.itemid, _difficultyBonusTreeMod); + generatedLoot.BonusListIDs.AddRange(generatedLoot.BonusListIDs); + } + lootItems.Add(generatedLoot); + count -= proto.GetMaxStackSize(); + + // non-conditional one-player only items are counted here, + // free for all items are counted in FillFFALoot(), + // non-ffa conditionals are counted in FillNonQuestNonFFAConditionalLoot() + if (!item.needs_quest && item.conditions.Empty() && !proto.GetFlags().HasAnyFlag(ItemFlags.MultiDrop)) + ++unlootedCount; + } + } + + public LootItem GetItemInSlot(uint lootSlot) + { + if (lootSlot < items.Count) + return items[(int)lootSlot]; + + lootSlot -= (uint)items.Count; + if (lootSlot < quest_items.Count) + return quest_items[(int)lootSlot]; + + return null; + } + + // Calls processor of corresponding LootTemplate (which handles everything including references) + public bool FillLoot(uint lootId, LootStore store, Player lootOwner, bool personal, bool noEmptyError = false, LootModes lootMode = LootModes.Default) + { + // Must be provided + if (lootOwner == null) + return false; + + LootTemplate tab = store.GetLootFor(lootId); + + if (tab == null) + { + if (!noEmptyError) + Log.outError(LogFilter.Sql, "Table '{0}' loot id #{1} used but it doesn't have records.", store.GetName(), lootId); + return false; + } + + _difficultyBonusTreeMod = lootOwner.GetMap().GetDifficultyLootBonusTreeMod(); + + tab.Process(this, store.IsRatesAllowed(), (byte)lootMode); // Processing is done there, callback via Loot.AddItem() + + // Setting access rights for group loot case + Group group = lootOwner.GetGroup(); + if (!personal && group != null) + { + roundRobinPlayer = lootOwner.GetGUID(); + + for (GroupReference refe = group.GetFirstMember(); refe != null; refe = refe.next()) + { + Player player = refe.GetSource(); + if (player) // should actually be looted object instead of lootOwner but looter has to be really close so doesnt really matter + FillNotNormalLootFor(player, player.IsAtGroupRewardDistance(lootOwner)); + } + + for (byte i = 0; i < items.Count; ++i) + { + ItemTemplate proto = Global.ObjectMgr.GetItemTemplate(items[i].itemid); + if (proto != null) + if (proto.GetQuality() < group.GetLootThreshold()) + items[i].is_underthreshold = true; + } + } + // ... for personal loot + else + FillNotNormalLootFor(lootOwner, true); + + return true; + } + + void FillNotNormalLootFor(Player player, bool presentAtLooting) + { + ulong plguid = player.GetGUID().GetCounter(); + + var questItemList = PlayerQuestItems.LookupByKey(plguid); + if (questItemList.Empty()) + FillQuestLoot(player); + + questItemList = PlayerFFAItems.LookupByKey(plguid); + if (questItemList.Empty()) + FillFFALoot(player); + + questItemList = PlayerNonQuestNonFFAConditionalItems.LookupByKey(plguid); + if (questItemList.Empty()) + FillNonQuestNonFFAConditionalLoot(player, presentAtLooting); + + // if not auto-processed player will have to come and pick it up manually + if (!presentAtLooting) + return; + + // Process currency items + uint max_slot = GetMaxSlotInLootFor(player); + LootItem item = null; + int itemsSize = items.Count; + for (byte i = 0; i < max_slot; ++i) + { + if (i < items.Count) + item = items[i]; + else + item = quest_items[i - itemsSize]; + + if (!item.is_looted && item.freeforall && item.AllowedForPlayer(player)) + { + ItemTemplate proto = Global.ObjectMgr.GetItemTemplate(item.itemid); + if (proto != null) + if (proto.IsCurrencyToken()) + player.StoreLootItem(i, this); + } + } + } + + List FillFFALoot(Player player) + { + List ql = new List(); + + for (byte i = 0; i < items.Count; ++i) + { + LootItem item = items[i]; + if (!item.is_looted && item.freeforall && item.AllowedForPlayer(player)) + { + ql.Add(new QuestItem(i)); + ++unlootedCount; + } + } + if (ql.Empty()) + return null; + + + PlayerFFAItems[player.GetGUID().GetCounter()] = ql; + return ql; + } + + List FillQuestLoot(Player player) + { + if (items.Count == SharedConst.MaxNRLootItems) + return null; + + List ql = new List(); + + for (byte i = 0; i < quest_items.Count; ++i) + { + LootItem item = quest_items[i]; + + if (!item.is_looted && (item.AllowedForPlayer(player) || (item.follow_loot_rules && player.GetGroup() != null + && ((player.GetGroup().GetLootMethod() == LootMethod.MasterLoot && player.GetGroup().GetLooterGuid() == player.GetGUID()) || player.GetGroup().GetLootMethod() != LootMethod.MasterLoot)))) + { + ql.Add(new QuestItem(i)); + + // quest items get blocked when they first appear in a + // player's quest vector + // + // increase once if one looter only, looter-times if free for all + if (item.freeforall || !item.is_blocked) + ++unlootedCount; + if (!player.GetGroup() || player.GetGroup().GetLootMethod() != LootMethod.GroupLoot) + item.is_blocked = true; + + if (items.Count + ql.Count == SharedConst.MaxNRLootItems) + break; + } + } + if (ql.Empty()) + return null; + + PlayerQuestItems[player.GetGUID().GetCounter()] = ql; + return ql; + } + + List FillNonQuestNonFFAConditionalLoot(Player player, bool presentAtLooting) + { + List ql = new List(); + + for (byte i = 0; i < items.Count; ++i) + { + LootItem item = items[i]; + if (!item.is_looted && !item.freeforall && (item.AllowedForPlayer(player) || (item.follow_loot_rules && player.GetGroup() != null + && ((player.GetGroup().GetLootMethod() == LootMethod.MasterLoot && player.GetGroup().GetLooterGuid() == player.GetGUID()) || player.GetGroup().GetLootMethod() != LootMethod.MasterLoot)))) + { + if (presentAtLooting) + item.AddAllowedLooter(player); + if (!item.conditions.Empty()) + { + ql.Add(new QuestItem(i)); + if (!item.is_counted) + { + ++unlootedCount; + item.is_counted = true; + } + } + } + } + if (ql.Empty()) + return null; + + PlayerNonQuestNonFFAConditionalItems[player.GetGUID().GetCounter()] = ql; + return ql; + } + + public void NotifyItemRemoved(byte lootIndex) + { + // notify all players that are looting this that the item was removed + // convert the index to the slot the player sees + for (int i = 0; i < PlayersLooting.Count; ++i) + { + Player player = Global.ObjAccessor.FindPlayer(PlayersLooting[i]); + if (player != null) + player.SendNotifyLootItemRemoved(GetGUID(), lootIndex); + else + PlayersLooting.RemoveAt(i); + } + } + + public void NotifyMoneyRemoved() + { + // notify all players that are looting this that the money was removed + for (var i = 0; i < PlayersLooting.Count; ++i) + { + Player player = Global.ObjAccessor.FindPlayer(PlayersLooting[i]); + if (player != null) + player.SendNotifyLootMoneyRemoved(GetGUID()); + else + PlayersLooting.RemoveAt(i); + } + } + + public void NotifyQuestItemRemoved(byte questIndex) + { + // when a free for all questitem is looted + // all players will get notified of it being removed + // (other questitems can be looted by each group member) + // bit inefficient but isn't called often + for (var i = 0; i < PlayersLooting.Count; ++i) + { + Player player = Global.ObjAccessor.FindPlayer(PlayersLooting[i]); + if (player) + { + var pql = PlayerQuestItems.LookupByKey(player.GetGUID().GetCounter()); + if (!pql.Empty()) + { + byte j; + for (j = 0; j < pql.Count; ++j) + if (pql[j].index == questIndex) + break; + + if (j < pql.Count) + player.SendNotifyLootItemRemoved(GetGUID(), (byte)(items.Count + j)); + } + } + else + PlayersLooting.RemoveAt(i); + } + } + + public void generateMoneyLoot(uint minAmount, uint maxAmount) + { + if (maxAmount > 0) + { + if (maxAmount <= minAmount) + gold = (uint)(maxAmount * WorldConfig.GetFloatValue(WorldCfg.RateDropMoney)); + else if ((maxAmount - minAmount) < 32700) + gold = (uint)(RandomHelper.URand(minAmount, maxAmount) * WorldConfig.GetFloatValue(WorldCfg.RateDropMoney)); + else + gold = (uint)(RandomHelper.URand(minAmount >> 8, maxAmount >> 8) * WorldConfig.GetFloatValue(WorldCfg.RateDropMoney)) << 8; + } + } + + public void DeleteLootItemFromContainerItemDB(uint itemID) + { + // Deletes a single item associated with an openable item from the DB + PreparedStatement stmt = DB.Characters.GetPreparedStatement(CharStatements.DEL_ITEMCONTAINER_ITEM); + stmt.AddValue(0, containerID.GetCounter()); + stmt.AddValue(1, itemID); + DB.Characters.Execute(stmt); + + // Mark the item looted to prevent resaving + foreach (var lootItem in items) + { + if (lootItem.itemid != itemID) + continue; + + lootItem.canSave = false; + break; + } + } + + public void DeleteLootMoneyFromContainerItemDB() + { + // Deletes money loot associated with an openable item from the DB + PreparedStatement stmt = DB.Characters.GetPreparedStatement(CharStatements.DEL_ITEMCONTAINER_MONEY); + stmt.AddValue(0, containerID.GetCounter()); + DB.Characters.Execute(stmt); + } + + public LootItem LootItemInSlot(uint lootSlot, Player player) + { + QuestItem qitem, ffaitem, conditem; + return LootItemInSlot(lootSlot, player, out qitem, out ffaitem, out conditem); + } + public LootItem LootItemInSlot(uint lootSlot, Player player, out QuestItem qitem, out QuestItem ffaitem, out QuestItem conditem) + { + qitem = null; + ffaitem = null; + conditem = null; + + LootItem item = null; + bool is_looted = true; + if (lootSlot >= items.Count) + { + uint questSlot = (uint)(lootSlot - items.Count); + var questItems = PlayerQuestItems.LookupByKey(player.GetGUID().GetCounter()); + if (!questItems.Empty()) + { + QuestItem qitem2 = questItems.Find(p => p.index == questSlot); + if (qitem2 != null) + { + qitem = qitem2; + item = quest_items[qitem2.index]; + is_looted = qitem2.is_looted; + } + } + } + else + { + item = items[(int)lootSlot]; + is_looted = item.is_looted; + if (item.freeforall) + { + var questItemList = PlayerFFAItems.LookupByKey(player.GetGUID().GetCounter()); + if (!questItemList.Empty()) + { + foreach (var c in questItemList) + { + if (c.index == lootSlot) + { + QuestItem ffaitem2 = c; + ffaitem = ffaitem2; + is_looted = ffaitem2.is_looted; + break; + } + } + } + } + else if (!item.conditions.Empty()) + { + var questItemList = PlayerNonQuestNonFFAConditionalItems.LookupByKey(player.GetGUID().GetCounter()); + if (!questItemList.Empty()) + { + foreach (var iter in questItemList) + { + if (iter.index == lootSlot) + { + QuestItem conditem2 = iter; + conditem = conditem2; + is_looted = conditem2.is_looted; + break; + } + } + } + } + } + + if (is_looted) + return null; + + return item; + } + + public uint GetMaxSlotInLootFor(Player player) + { + var questItemList = PlayerQuestItems.LookupByKey(player.GetGUID().GetCounter()); + return (uint)(items.Count + questItemList.Count); + } + + // return true if there is any item that is lootable for any player (not quest item, FFA or conditional) + public bool hasItemForAll() + { + // Gold is always lootable + if (gold != 0) + return true; + + foreach (LootItem item in items) + if (!item.is_looted && !item.freeforall && item.conditions.Empty()) + return true; + + return false; + } + + // return true if there is any FFA, quest or conditional item for the player. + public bool hasItemFor(Player player) + { + var lootPlayerQuestItems = GetPlayerQuestItems(); + var q_list = lootPlayerQuestItems.LookupByKey(player.GetGUID().GetCounter()); + if (!q_list.Empty()) + { + foreach (var qi in q_list) + { + LootItem item = quest_items[qi.index]; + if (!qi.is_looted && !item.is_looted) + return true; + } + } + + var lootPlayerFFAItems = GetPlayerFFAItems(); + var ffa_list = lootPlayerFFAItems.LookupByKey(player.GetGUID().GetCounter()); + if (!ffa_list.Empty()) + { + foreach (var fi in ffa_list) + { + LootItem item = items[fi.index]; + if (!fi.is_looted && !item.is_looted) + return true; + } + } + + var lootPlayerNonQuestNonFFAConditionalItems = GetPlayerNonQuestNonFFAConditionalItems(); + var conditional_list = lootPlayerNonQuestNonFFAConditionalItems.LookupByKey(player.GetGUID().GetCounter()); + if (!conditional_list.Empty()) + { + foreach (var ci in conditional_list) + { + LootItem item = items[ci.index]; + if (!ci.is_looted && !item.is_looted) + return true; + } + } + + return false; + } + + // return true if there is any item over the group threshold (i.e. not underthreshold). + public bool hasOverThresholdItem() + { + for (byte i = 0; i < items.Count; ++i) + { + if (!items[i].is_looted && !items[i].is_underthreshold && !items[i].freeforall) + return true; + } + + return false; + } + + public void BuildLootResponse(LootResponse packet, Player viewer, PermissionTypes permission) + { + if (permission == PermissionTypes.None) + return; + + packet.Coins = gold; + + switch (permission) + { + case PermissionTypes.Group: + case PermissionTypes.Master: + case PermissionTypes.Restricted: + { + // if you are not the round-robin group looter, you can only see + // blocked rolled items and quest items, and !ffa items + for (byte i = 0; i < items.Count; ++i) + { + if (!items[i].is_looted && !items[i].freeforall && items[i].conditions.Empty() && items[i].AllowedForPlayer(viewer)) + { + LootSlotType slot_type; + + if (items[i].is_blocked) // for ML & restricted is_blocked = !is_underthreshold + { + switch (permission) + { + case PermissionTypes.Group: + slot_type = LootSlotType.RollOngoing; + break; + case PermissionTypes.Master: + { + if (viewer.GetGroup() && viewer.GetGroup().GetMasterLooterGuid() == viewer.GetGUID()) + slot_type = LootSlotType.Master; + else + slot_type = LootSlotType.Locked; + break; + } + case PermissionTypes.Restricted: + slot_type = LootSlotType.Locked; + break; + default: + continue; + } + } + else if (roundRobinPlayer.IsEmpty() || viewer.GetGUID() == roundRobinPlayer || !items[i].is_underthreshold) + { + // no round robin owner or he has released the loot + // or it IS the round robin group owner + // => item is lootable + slot_type = LootSlotType.AllowLoot; + } + else + // item shall not be displayed. + continue; + + LootItemData lootItem = new LootItemData(); + lootItem.LootListID = (byte)(i + 1); + lootItem.UIType = slot_type; + lootItem.Quantity = items[i].count; + lootItem.Loot = new ItemInstance(items[i]); + packet.Items.Add(lootItem); + } + } + break; + } + case PermissionTypes.All: + case PermissionTypes.Owner: + { + for (byte i = 0; i < items.Count; ++i) + { + if (!items[i].is_looted && !items[i].freeforall && items[i].conditions.Empty() && items[i].AllowedForPlayer(viewer)) + { + LootItemData lootItem = new LootItemData(); + lootItem.LootListID = (byte)(i + 1); + lootItem.UIType = (permission == PermissionTypes.Owner ? LootSlotType.Owner : LootSlotType.AllowLoot); + lootItem.Quantity = items[i].count; + lootItem.Loot = new ItemInstance(items[i]); + packet.Items.Add(lootItem); + } + } + break; + } + default: + return; + } + + LootSlotType slotType = permission == PermissionTypes.Owner ? LootSlotType.Owner : LootSlotType.AllowLoot; + var lootPlayerQuestItems = GetPlayerQuestItems(); + var q_list = lootPlayerQuestItems.LookupByKey(viewer.GetGUID().GetCounter()); + if (!q_list.Empty()) + { + foreach (var qi in q_list) + { + LootItem item = quest_items[qi.index]; + if (!qi.is_looted && !item.is_looted) + { + LootItemData lootItem = new LootItemData(); + lootItem.LootListID = (byte)(items.Count + qi.index + 1); + lootItem.Quantity = item.count; + lootItem.Loot = new ItemInstance(item); + + if (item.follow_loot_rules) + { + switch (permission) + { + case PermissionTypes.Master: + lootItem.UIType = LootSlotType.Master; + break; + case PermissionTypes.Restricted: + lootItem.UIType = item.is_blocked ? LootSlotType.Locked : LootSlotType.AllowLoot; + break; + case PermissionTypes.Group: + if (!item.is_blocked) + lootItem.UIType = LootSlotType.AllowLoot; + else + lootItem.UIType = LootSlotType.RollOngoing; + break; + default: + lootItem.UIType = slotType; + break; + } + } + else + lootItem.UIType = slotType; + + packet.Items.Add(lootItem); + } + } + } + + var lootPlayerFFAItems = GetPlayerFFAItems(); + var ffa_list = lootPlayerFFAItems.LookupByKey(viewer.GetGUID().GetCounter()); + if (!ffa_list.Empty()) + { + foreach (var fi in ffa_list) + { + LootItem item = items[fi.index]; + if (!fi.is_looted && !item.is_looted) + { + LootItemData lootItem = new LootItemData(); + lootItem.LootListID = (byte)(items.Count + fi.index + 1); + lootItem.UIType = slotType; + lootItem.Quantity = item.count; + lootItem.Loot = new ItemInstance(item); + packet.Items.Add(lootItem); + } + } + } + + var lootPlayerNonQuestNonFFAConditionalItems = GetPlayerNonQuestNonFFAConditionalItems(); + var conditional_list = lootPlayerNonQuestNonFFAConditionalItems.LookupByKey(viewer.GetGUID().GetCounter()); + if (!conditional_list.Empty()) + { + foreach (var ci in conditional_list) + { + LootItem item = items[ci.index]; + if (!ci.is_looted && !item.is_looted) + { + LootItemData lootItem = new LootItemData(); + lootItem.LootListID = (byte)(items.Count + ci.index + 1); + lootItem.Quantity = item.count; + lootItem.Loot = new ItemInstance(item); + + if (item.follow_loot_rules) + { + switch (permission) + { + case PermissionTypes.Master: + lootItem.UIType = LootSlotType.Master; + break; + case PermissionTypes.Restricted: + lootItem.UIType = item.is_blocked ? LootSlotType.Locked : LootSlotType.AllowLoot; + break; + case PermissionTypes.Group: + if (!item.is_blocked) + lootItem.UIType = LootSlotType.AllowLoot; + else + lootItem.UIType = LootSlotType.RollOngoing; + break; + default: + lootItem.UIType = slotType; + break; + } + } + else + lootItem.UIType = slotType; + + packet.Items.Add(lootItem); + } + } + } + } + + public void addLootValidatorRef(LootValidatorRef pLootValidatorRef) + { + i_LootValidatorRefManager.insertFirst(pLootValidatorRef); + } + + public void clear() + { + PlayerQuestItems.Clear(); + + PlayerFFAItems.Clear(); + + PlayerNonQuestNonFFAConditionalItems.Clear(); + + PlayersLooting.Clear(); + items.Clear(); + quest_items.Clear(); + gold = 0; + unlootedCount = 0; + roundRobinPlayer = ObjectGuid.Empty; + i_LootValidatorRefManager.clearReferences(); + _difficultyBonusTreeMod = 0; + } + + public bool empty() { return items.Empty() && gold == 0; } + public bool isLooted() { return gold == 0 && unlootedCount == 0; } + + public void AddLooter(ObjectGuid guid) { PlayersLooting.Add(guid); } + public void RemoveLooter(ObjectGuid guid) { PlayersLooting.Remove(guid); } + + public ObjectGuid GetGUID() { return _GUID; } + public void SetGUID(ObjectGuid guid) { _GUID = guid; } + + public MultiMap GetPlayerQuestItems() { return PlayerQuestItems; } + public MultiMap GetPlayerFFAItems() { return PlayerFFAItems; } + public MultiMap GetPlayerNonQuestNonFFAConditionalItems() { return PlayerNonQuestNonFFAConditionalItems; } + + public List items = new List(); + public List quest_items = new List(); + public uint gold; + public byte unlootedCount; + public ObjectGuid roundRobinPlayer; // GUID of the player having the Round-Robin ownership for the loot. If 0, round robin owner has released. + public LootType loot_type; // required for achievement system + public byte maxDuplicates; // Max amount of items with the same entry that can drop (default is 1; on 25 man raid mode 3) + + public ObjectGuid containerID; + + List PlayersLooting = new List(); + MultiMap PlayerQuestItems = new MultiMap(); + MultiMap PlayerFFAItems = new MultiMap(); + MultiMap PlayerNonQuestNonFFAConditionalItems = new MultiMap(); + + // All rolls are registered here. They need to know, when the loot is not valid anymore + LootValidatorRefManager i_LootValidatorRefManager = new LootValidatorRefManager(); + + // Loot GUID + ObjectGuid _GUID; + byte _difficultyBonusTreeMod; + } + + public class AELootResult + { + public void Add(Item item, byte count, LootType lootType) + { + var id = _byItem.LookupByKey(item); + if (id != 0) + { + var resultValue = _byOrder[id]; + resultValue.count += count; + } + else + { + _byItem[item] = _byOrder.Count; + ResultValue value; + value.item = item; + value.count = count; + value.lootType = lootType; + _byOrder.Add(value); + } + } + + public List GetByOrder() + { + return _byOrder; + } + + List _byOrder = new List(); + Dictionary _byItem = new Dictionary(); + + public struct ResultValue + { + public Item item; + public byte count; + public LootType lootType; + } + } +} diff --git a/Game/Loot/LootManager.cs b/Game/Loot/LootManager.cs new file mode 100644 index 000000000..c6830615e --- /dev/null +++ b/Game/Loot/LootManager.cs @@ -0,0 +1,1098 @@ +/* + * Copyright (C) 2012-2017 CypherCore + * + * 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, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +using Framework.Constants; +using Framework.Database; +using Game.Conditions; +using Game.DataStorage; +using Game.Entities; +using System; +using System.Collections.Generic; + +namespace Game.Loots +{ + using LootStoreItemList = List; + using LootTemplateMap = Dictionary; + + public class LootManager : LootStorage + { + static void Initialize() + { + Creature = new LootStore("creature_loot_template", "creature entry"); + Disenchant = new LootStore("disenchant_loot_template", "item disenchant id"); + Fishing = new LootStore("fishing_loot_template", "area id"); + Gameobject = new LootStore("gameobject_loot_template", "gameobject entry"); + Items = new LootStore("item_loot_template", "item entry"); + Mail = new LootStore("mail_loot_template", "mail template id", false); + Milling = new LootStore("milling_loot_template", "item entry (herb)"); + Pickpocketing = new LootStore("pickpocketing_loot_template", "creature pickpocket lootid"); + Prospecting = new LootStore("prospecting_loot_template", "item entry (ore)"); + Reference = new LootStore("reference_loot_template", "reference id", false); + Skinning = new LootStore("skinning_loot_template", "creature skinning id"); + Spell = new LootStore("spell_loot_template", "spell id (random item creating)", false); + } + + public static void LoadLootTables() + { + Initialize(); + LoadLootTemplates_Creature(); + LoadLootTemplates_Fishing(); + LoadLootTemplates_Gameobject(); + LoadLootTemplates_Item(); + LoadLootTemplates_Mail(); + LoadLootTemplates_Milling(); + LoadLootTemplates_Pickpocketing(); + LoadLootTemplates_Skinning(); + LoadLootTemplates_Disenchant(); + LoadLootTemplates_Prospecting(); + LoadLootTemplates_Spell(); + + LoadLootTemplates_Reference(); + } + + public static void LoadLootTemplates_Creature() + { + Log.outInfo(LogFilter.ServerLoading, "Loading creature loot templates..."); + + uint oldMSTime = Time.GetMSTime(); + + List lootIdSet, lootIdSetUsed = new List(); + uint count = Creature.LoadAndCollectLootIds(out lootIdSet); + + // Remove real entries and check loot existence + var ctc = Global.ObjectMgr.GetCreatureTemplates(); + foreach (var pair in ctc) + { + uint lootid = pair.Value.LootId; + if (lootid != 0) + { + if (!lootIdSet.Contains(lootid)) + Creature.ReportNonExistingId(lootid, pair.Value.Entry); + else + lootIdSetUsed.Add(lootid); + } + } + + foreach (var id in lootIdSetUsed) + lootIdSet.Remove(id); + + // output error for any still listed (not referenced from appropriate table) ids + Creature.ReportUnusedIds(lootIdSet); + + if (count != 0) + Log.outInfo(LogFilter.ServerLoading, "Loaded {0} creature loot templates in {1} ms", count, Time.GetMSTimeDiffToNow(oldMSTime)); + else + Log.outError(LogFilter.ServerLoading, "Loaded 0 creature loot templates. DB table `creature_loot_template` is empty"); + } + + public static void LoadLootTemplates_Disenchant() + { + Log.outInfo(LogFilter.ServerLoading, "Loading disenchanting loot templates..."); + + uint oldMSTime = Time.GetMSTime(); + + List lootIdSet, lootIdSetUsed = new List(); + uint count = Disenchant.LoadAndCollectLootIds(out lootIdSet); + + foreach (var disenchant in CliDB.ItemDisenchantLootStorage.Values) + { + uint lootid = disenchant.Id; + if (!lootIdSet.Contains(lootid)) + Disenchant.ReportNonExistingId(lootid, disenchant.Id); + else + lootIdSetUsed.Add(lootid); + } + + foreach (var id in lootIdSetUsed) + lootIdSet.Remove(id); + + // output error for any still listed (not referenced from appropriate table) ids + Disenchant.ReportUnusedIds(lootIdSet); + + if (count != 0) + Log.outInfo(LogFilter.ServerLoading, "Loaded {0} disenchanting loot templates in {1} ms", count, Time.GetMSTimeDiffToNow(oldMSTime)); + else + Log.outError(LogFilter.ServerLoading, "Loaded 0 disenchanting loot templates. DB table `disenchant_loot_template` is empty"); + } + + public static void LoadLootTemplates_Fishing() + { + Log.outInfo(LogFilter.ServerLoading, "Loading fishing loot templates..."); + + uint oldMSTime = Time.GetMSTime(); + + List lootIdSet; + uint count = Fishing.LoadAndCollectLootIds(out lootIdSet); + + // remove real entries and check existence loot + foreach (var areaEntry in CliDB.AreaTableStorage.Values) + if (lootIdSet.Contains(areaEntry.Id)) + lootIdSet.Remove(areaEntry.Id); + + // output error for any still listed (not referenced from appropriate table) ids + Fishing.ReportUnusedIds(lootIdSet); + + if (count != 0) + Log.outInfo(LogFilter.ServerLoading, "Loaded {0} fishing loot templates in {1} ms", count, Time.GetMSTimeDiffToNow(oldMSTime)); + else + Log.outError(LogFilter.ServerLoading, "Loaded 0 fishing loot templates. DB table `fishing_loot_template` is empty"); + } + + public static void LoadLootTemplates_Gameobject() + { + Log.outInfo(LogFilter.ServerLoading, "Loading gameobject loot templates..."); + + uint oldMSTime = Time.GetMSTime(); + + List lootIdSet, lootIdSetUsed = new List(); + uint count = Gameobject.LoadAndCollectLootIds(out lootIdSet); + + // remove real entries and check existence loot + var gotc = Global.ObjectMgr.GetGameObjectTemplates(); + foreach (var go in gotc) + { + uint lootid = go.Value.GetLootId(); + if (lootid != 0) + { + if (!lootIdSet.Contains(lootid)) + Gameobject.ReportNonExistingId(lootid, go.Value.entry); + else + lootIdSetUsed.Add(lootid); + } + } + + foreach (var id in lootIdSetUsed) + lootIdSet.Remove(id); + + // output error for any still listed (not referenced from appropriate table) ids + Gameobject.ReportUnusedIds(lootIdSet); + + if (count != 0) + Log.outInfo(LogFilter.ServerLoading, "Loaded {0} gameobject loot templates in {1} ms", count, Time.GetMSTimeDiffToNow(oldMSTime)); + else + Log.outError(LogFilter.ServerLoading, "Loaded 0 gameobject loot templates. DB table `gameobject_loot_template` is empty"); + } + + public static void LoadLootTemplates_Item() + { + Log.outInfo(LogFilter.ServerLoading, "Loading item loot templates..."); + + uint oldMSTime = Time.GetMSTime(); + + List lootIdSet; + uint count = Items.LoadAndCollectLootIds(out lootIdSet); + + // remove real entries and check existence loot + var its = Global.ObjectMgr.GetItemTemplates(); + foreach (var pair in its) + if (lootIdSet.Contains(pair.Value.GetId()) && pair.Value.GetFlags().HasAnyFlag(ItemFlags.HasLoot)) + lootIdSet.Remove(pair.Value.GetId()); + + // output error for any still listed (not referenced from appropriate table) ids + Items.ReportUnusedIds(lootIdSet); + + if (count != 0) + Log.outInfo(LogFilter.ServerLoading, "Loaded {0} item loot templates in {1} ms", count, Time.GetMSTimeDiffToNow(oldMSTime)); + else + Log.outError(LogFilter.ServerLoading, "Loaded 0 item loot templates. DB table `item_loot_template` is empty"); + } + + public static void LoadLootTemplates_Milling() + { + Log.outInfo(LogFilter.ServerLoading, "Loading milling loot templates..."); + + uint oldMSTime = Time.GetMSTime(); + + List lootIdSet; + uint count = Milling.LoadAndCollectLootIds(out lootIdSet); + + // remove real entries and check existence loot + var its = Global.ObjectMgr.GetItemTemplates(); + foreach (var pair in its) + { + if (!pair.Value.GetFlags().HasAnyFlag(ItemFlags.IsMillable)) + continue; + + if (lootIdSet.Contains(pair.Value.GetId())) + lootIdSet.Remove(pair.Value.GetId()); + } + + // output error for any still listed (not referenced from appropriate table) ids + Milling.ReportUnusedIds(lootIdSet); + + if (count != 0) + Log.outInfo(LogFilter.ServerLoading, "Loaded {0} milling loot templates in {1} ms", count, Time.GetMSTimeDiffToNow(oldMSTime)); + else + Log.outError(LogFilter.ServerLoading, "Loaded 0 milling loot templates. DB table `milling_loot_template` is empty"); + } + + public static void LoadLootTemplates_Pickpocketing() + { + Log.outInfo(LogFilter.ServerLoading, "Loading pickpocketing loot templates..."); + + uint oldMSTime = Time.GetMSTime(); + + List lootIdSet; + List lootIdSetUsed = new List(); + uint count = Pickpocketing.LoadAndCollectLootIds(out lootIdSet); + + // Remove real entries and check loot existence + var ctc = Global.ObjectMgr.GetCreatureTemplates(); + foreach (var pair in ctc) + { + uint lootid = pair.Value.PickPocketId; + if (lootid != 0) + { + if (!lootIdSet.Contains(lootid)) + Pickpocketing.ReportNonExistingId(lootid, pair.Value.Entry); + else + lootIdSetUsed.Add(lootid); + } + } + + foreach (var id in lootIdSetUsed) + lootIdSet.Remove(id); + + // output error for any still listed (not referenced from appropriate table) ids + Pickpocketing.ReportUnusedIds(lootIdSet); + + if (count != 0) + Log.outInfo(LogFilter.ServerLoading, "Loaded {0} pickpocketing loot templates in {1} ms", count, Time.GetMSTimeDiffToNow(oldMSTime)); + else + Log.outError(LogFilter.ServerLoading, "Loaded 0 pickpocketing loot templates. DB table `pickpocketing_loot_template` is empty"); + } + + public static void LoadLootTemplates_Prospecting() + { + Log.outInfo(LogFilter.ServerLoading, "Loading prospecting loot templates..."); + + uint oldMSTime = Time.GetMSTime(); + + List lootIdSet; + uint count = Prospecting.LoadAndCollectLootIds(out lootIdSet); + + // remove real entries and check existence loot + var its = Global.ObjectMgr.GetItemTemplates(); + foreach (var pair in its) + { + if (!pair.Value.GetFlags().HasAnyFlag(ItemFlags.IsProspectable)) + continue; + + if (lootIdSet.Contains(pair.Value.GetId())) + lootIdSet.Remove(pair.Value.GetId()); + } + + // output error for any still listed (not referenced from appropriate table) ids + Prospecting.ReportUnusedIds(lootIdSet); + + if (count != 0) + Log.outInfo(LogFilter.ServerLoading, "Loaded {0} prospecting loot templates in {1} ms", count, Time.GetMSTimeDiffToNow(oldMSTime)); + else + Log.outError(LogFilter.ServerLoading, "Loaded 0 prospecting loot templates. DB table `prospecting_loot_template` is empty"); + } + + public static void LoadLootTemplates_Mail() + { + Log.outInfo(LogFilter.ServerLoading, "Loading mail loot templates..."); + + uint oldMSTime = Time.GetMSTime(); + + List lootIdSet; + uint count = Mail.LoadAndCollectLootIds(out lootIdSet); + + // remove real entries and check existence loot + foreach (var mail in CliDB.MailTemplateStorage.Values) + if (lootIdSet.Contains(mail.Id)) + lootIdSet.Remove(mail.Id); + + // output error for any still listed (not referenced from appropriate table) ids + Mail.ReportUnusedIds(lootIdSet); + + if (count != 0) + Log.outInfo(LogFilter.ServerLoading, "Loaded {0} mail loot templates in {1} ms", count, Time.GetMSTimeDiffToNow(oldMSTime)); + else + Log.outError(LogFilter.ServerLoading, "Loaded 0 mail loot templates. DB table `mail_loot_template` is empty"); + } + + public static void LoadLootTemplates_Skinning() + { + Log.outInfo(LogFilter.ServerLoading, "Loading skinning loot templates..."); + + uint oldMSTime = Time.GetMSTime(); + + List lootIdSet; + List lootIdSetUsed = new List(); + uint count = Skinning.LoadAndCollectLootIds(out lootIdSet); + + // remove real entries and check existence loot + var ctc = Global.ObjectMgr.GetCreatureTemplates(); + foreach (var pair in ctc) + { + uint lootid = pair.Value.SkinLootId; + if (lootid != 0) + { + if (!lootIdSet.Contains(lootid)) + Skinning.ReportNonExistingId(lootid, pair.Value.Entry); + else + lootIdSetUsed.Add(lootid); + } + } + + foreach (var id in lootIdSetUsed) + lootIdSet.Remove(id); + + // output error for any still listed (not referenced from appropriate table) ids + Skinning.ReportUnusedIds(lootIdSet); + + if (count != 0) + Log.outInfo(LogFilter.ServerLoading, "Loaded {0} skinning loot templates in {1} ms", count, Time.GetMSTimeDiffToNow(oldMSTime)); + else + Log.outError(LogFilter.ServerLoading, "Loaded 0 skinning loot templates. DB table `skinning_loot_template` is empty"); + } + + public static void LoadLootTemplates_Spell() + { + // TODO: change this to use MiscValue from spell effect as id instead of spell id + Log.outInfo(LogFilter.ServerLoading, "Loading spell loot templates..."); + + uint oldMSTime = Time.GetMSTime(); + + List lootIdSet; + uint count = Spell.LoadAndCollectLootIds(out lootIdSet); + + // remove real entries and check existence loot + foreach (var spellInfo in Global.SpellMgr.GetSpellInfoStorage().Values) + { + // possible cases + if (!spellInfo.IsLootCrafting()) + continue; + + if (!lootIdSet.Contains(spellInfo.Id)) + { + // not report about not trainable spells (optionally supported by DB) + // ignore 61756 (Northrend Inscription Research (FAST QA VERSION) for example + if (!spellInfo.HasAttribute(SpellAttr0.NotShapeshift) || spellInfo.HasAttribute(SpellAttr0.Tradespell)) + { + Spell.ReportNonExistingId(spellInfo.Id, spellInfo.Id); + } + } + else + lootIdSet.Remove(spellInfo.Id); + } + + // output error for any still listed (not referenced from appropriate table) ids + Spell.ReportUnusedIds(lootIdSet); + + if (count != 0) + Log.outInfo(LogFilter.ServerLoading, "Loaded {0} spell loot templates in {1} ms", count, Time.GetMSTimeDiffToNow(oldMSTime)); + else + Log.outError(LogFilter.ServerLoading, "Loaded 0 spell loot templates. DB table `spell_loot_template` is empty"); + } + + public static void LoadLootTemplates_Reference() + { + Log.outInfo(LogFilter.ServerLoading, "Loading reference loot templates..."); + + uint oldMSTime = Time.GetMSTime(); + + List lootIdSet; + Reference.LoadAndCollectLootIds(out lootIdSet); + + // check references and remove used + Creature.CheckLootRefs(lootIdSet); + Fishing.CheckLootRefs(lootIdSet); + Gameobject.CheckLootRefs(lootIdSet); + Items.CheckLootRefs(lootIdSet); + Milling.CheckLootRefs(lootIdSet); + Pickpocketing.CheckLootRefs(lootIdSet); + Skinning.CheckLootRefs(lootIdSet); + Disenchant.CheckLootRefs(lootIdSet); + Prospecting.CheckLootRefs(lootIdSet); + Mail.CheckLootRefs(lootIdSet); + Reference.CheckLootRefs(lootIdSet); + + // output error for any still listed ids (not referenced from any loot table) + Reference.ReportUnusedIds(lootIdSet); + + Log.outInfo(LogFilter.ServerLoading, "Loaded refence loot templates in {0} ms", Time.GetMSTimeDiffToNow(oldMSTime)); + } + } + + public class LootStoreItem + { + public uint itemid; // id of the item + public uint reference; // referenced TemplateleId + public float chance; // chance to drop for both quest and non-quest items, chance to be used for refs + public ushort lootmode; + public bool needs_quest; // quest drop (negative ChanceOrQuestChance in DB) + public byte groupid; + public byte mincount; // mincount for drop items + public byte maxcount; // max drop count for the item mincount or Ref multiplicator + public List conditions; // additional loot condition + + public LootStoreItem(uint _itemid, uint _reference, float _chance, bool _needs_quest, ushort _lootmode, byte _groupid, byte _mincount, byte _maxcount) + { + itemid = _itemid; + reference = _reference; + chance = _chance; + lootmode = _lootmode; + needs_quest = _needs_quest; + groupid = _groupid; + mincount = _mincount; + maxcount = _maxcount; + conditions = new List(); + } + + public bool Roll(bool rate) + { + if (chance >= 100.0f) + return true; + + if (reference > 0) // reference case + return RandomHelper.randChance(chance * (rate ? WorldConfig.GetFloatValue(WorldCfg.RateDropItemReferenced) : 1.0f)); + + ItemTemplate pProto = Global.ObjectMgr.GetItemTemplate(itemid); + + float qualityModifier = pProto != null && rate ? WorldConfig.GetFloatValue(qualityToRate[(int)pProto.GetQuality()]) : 1.0f; + + return RandomHelper.randChance(chance * qualityModifier); + } + public bool IsValid(LootStore store, uint entry) + { + if (mincount == 0) + { + Log.outError(LogFilter.Sql, "Table '{0}' entry {1} item {2}: wrong mincount ({3}) - skipped", store.GetName(), entry, itemid, reference); + return false; + } + + if (reference == 0) // item (quest or non-quest) entry, maybe grouped + { + ItemTemplate proto = Global.ObjectMgr.GetItemTemplate(itemid); + if (proto == null) + { + Log.outError(LogFilter.Sql, "Table '{0}' entry {1} item {2}: item entry not listed in `item_template` - skipped", store.GetName(), entry, itemid); + return false; + } + + if (chance == 0 && groupid == 0) // Zero chance is allowed for grouped entries only + { + Log.outError(LogFilter.Sql, "Table '{0}' entry {1} item {2}: equal-chanced grouped entry, but group not defined - skipped", store.GetName(), entry, itemid); + return false; + } + + if (chance != 0 && chance < 0.000001f) // loot with low chance + { + Log.outError(LogFilter.Sql, "Table '{0}' entry {1} item {2}: low chance ({3}) - skipped", + store.GetName(), entry, itemid, chance); + return false; + } + + if (maxcount < mincount) // wrong max count + { + Log.outError(LogFilter.Sql, "Table '{0}' entry {1} item {2}: max count ({3}) less that min count ({4}) - skipped", store.GetName(), entry, itemid, maxcount, reference); + return false; + } + } + else // mincountOrRef < 0 + { + if (needs_quest) + Log.outError(LogFilter.Sql, "Table '{0}' entry {1} item {2}: quest chance will be treated as non-quest chance", store.GetName(), entry, itemid); + else if (chance == 0) // no chance for the reference + { + Log.outError(LogFilter.Sql, "Table '{0}' entry {1} item {2}: zero chance is specified for a reference, skipped", store.GetName(), entry, itemid); + return false; + } + } + return true; // Referenced template existence is checked at whole store level + } + + public static WorldCfg[] qualityToRate = new WorldCfg[7] + { + WorldCfg.RateDropItemPoor, // ITEM_QUALITY_POOR + WorldCfg.RateDropItemNormal, // ITEM_QUALITY_NORMAL + WorldCfg.RateDropItemUncommon, // ITEM_QUALITY_UNCOMMON + WorldCfg.RateDropItemRare, // ITEM_QUALITY_RARE + WorldCfg.RateDropItemEpic, // ITEM_QUALITY_EPIC + WorldCfg.RateDropItemLegendary, // ITEM_QUALITY_LEGENDARY + WorldCfg.RateDropItemArtifact, // ITEM_QUALITY_ARTIFACT + }; + } + + public class LootStore + { + public LootStore(string name, string entryName, bool ratesAllowed = true) + { + m_name = name; + m_entryName = entryName; + m_ratesAllowed = ratesAllowed; + } + + void Verify() + { + foreach (var i in m_LootTemplates) + i.Value.Verify(this, i.Key); + } + + public uint LoadAndCollectLootIds(out List lootIdSet) + { + uint count = LoadLootTable(); + lootIdSet = new List(); + + foreach (var tab in m_LootTemplates) + lootIdSet.Add(tab.Key); + + return count; + } + public void CheckLootRefs(List ref_set = null) + { + foreach (var pair in m_LootTemplates) + pair.Value.CheckLootRefs(m_LootTemplates, ref_set); + } + public void ReportUnusedIds(List lootIdSet) + { + // all still listed ids isn't referenced + foreach (var id in lootIdSet) + Log.outError( LogFilter.Sql, "Table '{0}' entry {1} isn't {2} and not referenced from loot, and then useless.", GetName(), id, GetEntryName()); + } + public void ReportNonExistingId(uint lootId, uint ownerId) + { + Log.outError( LogFilter.Sql, "Table '{0}' Entry {1} does not exist but it is used by {2} {3}", GetName(), lootId, GetEntryName(), ownerId); + } + + public bool HaveLootFor(uint loot_id) { return m_LootTemplates.LookupByKey(loot_id) != null; } + public bool HaveQuestLootFor(uint loot_id) + { + var lootTemplate = m_LootTemplates.LookupByKey(loot_id); + if (lootTemplate == null) + return false; + + // scan loot for quest items + return lootTemplate.HasQuestDrop(m_LootTemplates); + } + public bool HaveQuestLootForPlayer(uint loot_id, Player player) + { + var tab = m_LootTemplates.LookupByKey(loot_id); + if (tab != null) + if (tab.HasQuestDropForPlayer(m_LootTemplates, player)) + return true; + + return false; + } + + public LootTemplate GetLootFor(uint loot_id) + { + var tab = m_LootTemplates.LookupByKey(loot_id); + + if (tab == null) + return null; + + return tab; + } + public void ResetConditions() + { + foreach (var pair in m_LootTemplates) + { + List empty = new List(); + pair.Value.CopyConditions(empty); + } + } + public LootTemplate GetLootForConditionFill(uint loot_id) + { + var tab = m_LootTemplates.LookupByKey(loot_id); + + if (tab == null) + return null; + + return tab; + } + + public string GetName() { return m_name; } + string GetEntryName() { return m_entryName; } + public bool IsRatesAllowed() { return m_ratesAllowed; } + + uint LoadLootTable() + { + // Clearing store (for reloading case) + Clear(); + + // 0 1 2 3 4 5 6 7 8 + SQLResult result = DB.World.Query("SELECT Entry, Item, Reference, Chance, QuestRequired, LootMode, GroupId, MinCount, MaxCount FROM {0}", GetName()); + if (result.IsEmpty()) + return 0; + + uint count = 0; + do + { + uint entry = result.Read(0); + uint item = result.Read(1); + uint reference = result.Read(2); + float chance = result.Read(3); + bool needsquest = result.Read(4); + ushort lootmode = result.Read(5); + byte groupid = result.Read(6); + byte mincount = result.Read(7); + byte maxcount = result.Read(8); + + if (groupid >= 1 << 7) // it stored in 7 bit field + { + Log.outError(LogFilter.Sql, "Table '{0}' entry {1} item {2}: group ({3}) must be less {4} - skipped", GetName(), entry, item, groupid, 1 << 7); + return 0; + } + + LootStoreItem storeitem = new LootStoreItem(item, reference, chance, needsquest, lootmode, groupid, mincount, maxcount); + + if (!storeitem.IsValid(this, entry)) // Validity checks + continue; + + // Looking for the template of the entry + // often entries are put together + if (m_LootTemplates.Empty() || !m_LootTemplates.ContainsKey(entry)) + m_LootTemplates.Add(entry, new LootTemplate()); + + // Adds current row to the template + m_LootTemplates[entry].AddEntry(storeitem); + ++count; + } + while (result.NextRow()); + + Verify(); // Checks validity of the loot store + + return count; + } + void Clear() + { + m_LootTemplates.Clear(); + } + + LootTemplateMap m_LootTemplates = new LootTemplateMap(); + string m_name; + string m_entryName; + bool m_ratesAllowed; + } + + public class LootTemplate + { + public void AddEntry(LootStoreItem item) + { + if (item.groupid > 0 && item.reference == 0) // Group + { + if (!Groups.ContainsKey(item.groupid - 1)) + Groups[item.groupid - 1] = new LootGroup(); + + Groups[item.groupid - 1].AddEntry(item); // Adds new entry to the group + } + else // Non-grouped entries and references are stored together + Entries.Add(item); + } + + public void Process(Loot loot, bool rate, ushort lootMode, byte groupId = 0) + { + if (groupId != 0) // Group reference uses own processing of the group + { + if (groupId > Groups.Count) + return; // Error message already printed at loading stage + + if (Groups[groupId - 1] == null) + return; + + Groups[groupId - 1].Process(loot, lootMode); + return; + } + + // Rolling non-grouped items + foreach (var item in Entries) + { + if (!Convert.ToBoolean(item.lootmode & lootMode)) // Do not add if mode mismatch + continue; + + if (!item.Roll(rate)) + continue; // Bad luck for the entry + + if (item.reference > 0) // References processing + { + LootTemplate Referenced = LootManager.Reference.GetLootFor(item.reference); + if (Referenced == null) + continue; // Error message already printed at loading stage + + uint maxcount = (uint)(item.maxcount * WorldConfig.GetFloatValue(WorldCfg.RateDropItemReferencedAmount)); + for (uint loop = 0; loop < maxcount; ++loop) // Ref multiplicator + Referenced.Process(loot, rate, lootMode, item.groupid); + } + else // Plain entries (not a reference, not grouped) + loot.AddItem(item); // Chance is already checked, just add + } + + // Now processing groups + foreach (var group in Groups.Values) + { + if (group != null) + group.Process(loot, lootMode); + } + } + public void CopyConditions(List conditions) + { + foreach (var i in Entries) + i.conditions.Clear(); + + foreach (var group in Groups.Values) + group.CopyConditions(conditions); + } + public void CopyConditions(LootItem li) + { + // Copies the conditions list from a template item to a LootItem + foreach (var item in Entries) + { + if (item.itemid != li.itemid) + continue; + + li.conditions = item.conditions; + break; + } + } + + + public bool HasQuestDrop(LootTemplateMap store, byte groupId = 0) + { + if (groupId != 0) // Group reference + { + if (groupId > Groups.Count) + return false; // Error message [should be] already printed at loading stage + + if (Groups[groupId - 1] == null) + return false; + + return Groups[groupId - 1].HasQuestDrop(); + } + + foreach (var item in Entries) + { + if (item.reference > 0) // References + { + var Referenced = store.LookupByKey(item.reference); + if (Referenced == null) + continue; // Error message [should be] already printed at loading stage + if (Referenced.HasQuestDrop(store, item.groupid)) + return true; + } + else if (item.needs_quest) + return true; // quest drop found + } + + // Now processing groups + foreach (var group in Groups.Values) + if (group.HasQuestDrop()) + return true; + + return false; + } + + public bool HasQuestDropForPlayer(LootTemplateMap store, Player player, byte groupId = 0) + { + if (groupId != 0) // Group reference + { + if (groupId > Groups.Count) + return false; // Error message already printed at loading stage + + if (Groups[groupId - 1] == null) + return false; + + return Groups[groupId - 1].HasQuestDropForPlayer(player); + } + + // Checking non-grouped entries + foreach (var item in Entries) + { + if (item.reference > 0) // References processing + { + var Referenced = store.LookupByKey(item.reference); + if (Referenced == null) + continue; // Error message already printed at loading stage + if (Referenced.HasQuestDropForPlayer(store, player, item.groupid)) + return true; + } + else if (player.HasQuestForItem(item.itemid)) + return true; // active quest drop found + } + + // Now checking groups + foreach (var group in Groups.Values) + if (group.HasQuestDropForPlayer(player)) + return true; + + return false; + } + + + public void Verify(LootStore lootstore, uint id) + { + // Checking group chances + foreach (var group in Groups) + group.Value.Verify(lootstore, id, (byte)(group.Key + 1)); + + /// @todo References validity checks + } + public void CheckLootRefs(LootTemplateMap store, List ref_set) + { + foreach (var item in Entries) + { + if (item.reference > 0) + { + if (LootManager.Reference.GetLootFor(item.reference) == null) + LootManager.Reference.ReportNonExistingId(item.reference, item.itemid); + else if (ref_set != null) + ref_set.Remove(item.reference); + } + } + + foreach (var group in Groups.Values) + group.CheckLootRefs(store, ref_set); + } + public bool addConditionItem(Condition cond) + { + if (cond == null || !cond.isLoaded())//should never happen, checked at loading + { + Log.outError(LogFilter.Loot, "LootTemplate.addConditionItem: condition is null"); + return false; + } + + if (!Entries.Empty()) + { + foreach (var i in Entries) + { + if (i.itemid == cond.SourceEntry) + { + i.conditions.Add(cond); + return true; + } + } + } + + if (!Groups.Empty()) + { + foreach (var group in Groups.Values) + { + if (group == null) + continue; + + LootStoreItemList itemList = group.GetExplicitlyChancedItemList(); + if (!itemList.Empty()) + { + foreach (var i in itemList) + { + if (i.itemid == cond.SourceEntry) + { + i.conditions.Add(cond); + return true; + } + } + } + + itemList = group.GetEqualChancedItemList(); + if (!itemList.Empty()) + { + foreach (var i in itemList) + { + if (i.itemid == cond.SourceEntry) + { + i.conditions.Add(cond); + return true; + } + } + } + } + } + return false; + } + public bool isReference(uint id) + { + foreach (var storeItem in Entries) + if (storeItem.itemid == id && storeItem.reference > 0) + return true; + + return false;//not found or not reference + } + + + LootStoreItemList Entries = new LootStoreItemList(); // not grouped only + Dictionary Groups = new Dictionary(); // groups have own (optimised) processing, grouped entries go there + + public class LootGroup // A set of loot definitions for items (refs are not allowed) + { + public void AddEntry(LootStoreItem item) + { + if (item.chance != 0) + ExplicitlyChanced.Add(item); + else + EqualChanced.Add(item); + } + + public bool HasQuestDrop() + { + foreach (var i in ExplicitlyChanced) + if (i.needs_quest) + return true; + + foreach (var i in EqualChanced) + if (i.needs_quest) + return true; + + return false; + } + + public bool HasQuestDropForPlayer(Player player) + { + foreach (var i in ExplicitlyChanced) + if (player.HasQuestForItem(i.itemid)) + return true; + + foreach (var i in EqualChanced) + if (player.HasQuestForItem(i.itemid)) + return true; + + return false; + } + + public void Process(Loot loot, ushort lootMode) + { + LootStoreItem item = Roll(loot, lootMode); + if (item != null) + loot.AddItem(item); + } + float RawTotalChance() + { + float result = 0; + + foreach (var i in ExplicitlyChanced) + if (!i.needs_quest) + result += i.chance; + + return result; + } + float TotalChance() + { + float result = RawTotalChance(); + + if (!EqualChanced.Empty() && result < 100.0f) + return 100.0f; + + return result; + } + + public void Verify(LootStore lootstore, uint id, byte group_id = 0) + { + float chance = RawTotalChance(); + if (chance > 101.0f) /// @todo replace with 100% when DBs will be ready + Log.outError(LogFilter.Sql, "Table '{0}' entry {1} group {2} has total chance > 100% ({3})", lootstore.GetName(), id, group_id, chance); + + if (chance >= 100.0f && !EqualChanced.Empty()) + Log.outError(LogFilter.Sql, "Table '{0}' entry {1} group {2} has items with chance=0% but group total chance >= 100% ({3})", lootstore.GetName(), id, group_id, chance); + + } + public void CheckLootRefs(LootTemplateMap store, List ref_set) + { + foreach (var item in ExplicitlyChanced) + { + if (item.reference > 0) + { + if (LootManager.Reference.GetLootFor(item.reference) == null) + LootManager.Reference.ReportNonExistingId(item.reference, item.itemid); + else if (ref_set != null) + ref_set.Remove(item.reference); + } + } + + foreach (var item in EqualChanced) + { + if (item.reference > 0) + { + if (LootManager.Reference.GetLootFor(item.reference) == null) + LootManager.Reference.ReportNonExistingId(item.reference, item.itemid); + else if (ref_set != null) + ref_set.Remove(item.reference); + } + } + } + public LootStoreItemList GetExplicitlyChancedItemList() { return ExplicitlyChanced; } + public LootStoreItemList GetEqualChancedItemList() { return EqualChanced; } + public void CopyConditions(List conditions) + { + foreach (var i in ExplicitlyChanced) + i.conditions.Clear(); + + foreach (var i in EqualChanced) + i.conditions.Clear(); + } + + LootStoreItemList ExplicitlyChanced = new LootStoreItemList(); // Entries with chances defined in DB + LootStoreItemList EqualChanced = new LootStoreItemList(); // Zero chances - every entry takes the same chance + + LootStoreItem Roll(Loot loot, ushort lootMode) + { + LootStoreItemList possibleLoot = ExplicitlyChanced; + possibleLoot.RemoveAll(new LootGroupInvalidSelector(loot, lootMode).Check); + + if (!possibleLoot.Empty()) // First explicitly chanced entries are checked + { + float roll = (float)RandomHelper.randChance(); + + foreach (var item in possibleLoot) // check each explicitly chanced entry in the template and modify its chance based on quality. + { + if (item.chance >= 100.0f) + return item; + + roll -= item.chance; + if (roll < 0) + return item; + } + } + + possibleLoot = EqualChanced; + possibleLoot.RemoveAll(new LootGroupInvalidSelector(loot, lootMode).Check); + if (!possibleLoot.Empty()) // If nothing selected yet - an item is taken from equal-chanced part + return possibleLoot.PickRandom(); + + return null; // Empty drop from the group + } + } + } + + public struct LootGroupInvalidSelector + { + public LootGroupInvalidSelector(Loot loot, ushort lootMode) + { + _loot = loot; + _lootMode = lootMode; + } + + public bool Check(LootStoreItem item) + { + if (!Convert.ToBoolean(item.lootmode & _lootMode)) + return true; + + byte foundDuplicates = 0; + foreach (var lootItem in _loot.items) + if (lootItem.itemid == item.itemid) + if (++foundDuplicates == _loot.maxDuplicates) + return true; + + return false; + } + + Loot _loot; + ushort _lootMode; + } +} diff --git a/Game/Loot/LootStorage.cs b/Game/Loot/LootStorage.cs new file mode 100644 index 000000000..b7ba9f818 --- /dev/null +++ b/Game/Loot/LootStorage.cs @@ -0,0 +1,35 @@ +/* + * Copyright (C) 2012-2017 CypherCore + * + * 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, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +namespace Game.Loots +{ + public class LootStorage + { + public static LootStore Creature; + public static LootStore Fishing; + public static LootStore Gameobject; + public static LootStore Items; + public static LootStore Mail; + public static LootStore Milling; + public static LootStore Pickpocketing; + public static LootStore Reference; + public static LootStore Skinning; + public static LootStore Disenchant; + public static LootStore Prospecting; + public static LootStore Spell; + } +} diff --git a/Game/Mails/Mail.cs b/Game/Mails/Mail.cs new file mode 100644 index 000000000..2ff3e7521 --- /dev/null +++ b/Game/Mails/Mail.cs @@ -0,0 +1,179 @@ +/* + * Copyright (C) 2012-2017 CypherCore + * + * 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, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +using Framework.Constants; +using Game.BlackMarket; +using Game.Entities; +using System.Collections.Generic; + +namespace Game.Mails +{ + public class Mail + { + public void AddItem(ulong itemGuidLow, uint item_template) + { + MailItemInfo mii = new MailItemInfo(); + mii.item_guid = itemGuidLow; + mii.item_template = item_template; + items.Add(mii); + } + + public bool RemoveItem(uint item_guid) + { + foreach (var item in items) + { + if (item.item_guid == item_guid) + { + items.Remove(item); + return true; + } + } + return false; + } + + public bool HasItems() { return !items.Empty(); } + + public uint messageID; + public MailMessageType messageType; + public MailStationery stationery; + public uint mailTemplateId; + public ulong sender; + public ulong receiver; + public string subject; + public string body; + public List items = new List(); + public List removedItems = new List(); + public long expire_time; + public long deliver_time; + public ulong money; + public ulong COD; + public MailCheckMask checkMask; + public MailState state; + } + + public class MailItemInfo + { + public ulong item_guid; + public uint item_template; + } + + public class MailReceiver + { + public MailReceiver(ulong receiver_lowguid) + { + m_receiver = null; + m_receiver_lowguid = receiver_lowguid; + } + + public MailReceiver(Player receiver) + { + m_receiver = receiver; + m_receiver_lowguid = receiver.GetGUID().GetCounter(); + } + + public MailReceiver(Player receiver, ulong receiver_lowguid) + { + m_receiver = receiver; + m_receiver_lowguid = receiver_lowguid; + + // ASSERT(!receiver || receiver.GetGUID().GetCounter() == receiver_lowguid); + } + + public Player GetPlayer() { return m_receiver; } + public ulong GetPlayerGUIDLow() { return m_receiver_lowguid; } + + Player m_receiver; + ulong m_receiver_lowguid; + } + + public class MailSender + { + public MailSender(MailMessageType messageType, ulong sender_guidlow_or_entry, MailStationery stationery = MailStationery.Default) + { + m_messageType = messageType; + m_senderId = sender_guidlow_or_entry; + m_stationery = stationery; + } + + public MailSender(WorldObject sender, MailStationery stationery = MailStationery.Default) + { + m_stationery = stationery; + switch (sender.GetTypeId()) + { + case TypeId.Unit: + m_messageType = MailMessageType.Creature; + m_senderId = sender.GetEntry(); + break; + case TypeId.GameObject: + m_messageType = MailMessageType.Gameobject; + m_senderId = sender.GetEntry(); + break; + case TypeId.Player: + m_messageType = MailMessageType.Normal; + m_senderId = sender.GetGUID().GetCounter(); + break; + default: + m_messageType = MailMessageType.Normal; + m_senderId = 0; // will show mail from not existed player + Log.outError(LogFilter.Server, "MailSender:MailSender - Mail have unexpected sender typeid ({0})", sender.GetTypeId()); + break; + } + } + + public MailSender(CalendarEvent sender) + { + m_messageType = MailMessageType.Calendar; + m_senderId = (uint)sender.EventId; + m_stationery = MailStationery.Default; + } + public MailSender(AuctionEntry sender) + { + m_messageType = MailMessageType.Auction; + m_senderId = sender.GetHouseId(); + m_stationery = MailStationery.Auction; + } + + public MailSender(BlackMarketEntry sender) + { + m_messageType = MailMessageType.Blackmarket; + m_senderId = sender.GetTemplate().SellerNPC; + m_stationery = MailStationery.Auction; + } + + public MailSender(Player sender) + { + m_messageType = MailMessageType.Normal; + m_stationery = sender.IsGameMaster() ? MailStationery.Gm : MailStationery.Default; + m_senderId = sender.GetGUID().GetCounter(); + } + + public MailSender(uint senderEntry) + { + m_messageType = MailMessageType.Creature; + m_senderId = senderEntry; + m_stationery = MailStationery.Default; + } + + public MailMessageType GetMailMessageType() { return m_messageType; } + public ulong GetSenderId() { return m_senderId; } + public MailStationery GetStationery() { return m_stationery; } + + MailMessageType m_messageType; + ulong m_senderId; // player low guid or other object entry + MailStationery m_stationery; + } +} diff --git a/Game/Mails/MailDraft.cs b/Game/Mails/MailDraft.cs new file mode 100644 index 000000000..3fb9beb96 --- /dev/null +++ b/Game/Mails/MailDraft.cs @@ -0,0 +1,265 @@ +/* + * Copyright (C) 2012-2017 CypherCore + * + * 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, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +using Framework.Constants; +using Framework.Database; +using Game.Entities; +using Game.Loots; +using System.Collections.Generic; + +namespace Game.Mails +{ + public class MailDraft + { + public MailDraft(uint mailTemplateId, bool need_items = true) + { + m_mailTemplateId = mailTemplateId; + m_mailTemplateItemsNeed = need_items; + m_money = 0; + m_COD = 0; + } + + public MailDraft(string subject, string body) + { + m_mailTemplateId = 0; + m_mailTemplateItemsNeed = false; + m_subject = subject; + m_body = body; + m_money = 0; + m_COD = 0; + } + + public MailDraft AddItem(Item item) + { + m_items[item.GetGUID().GetCounter()] = item; + return this; + } + + void prepareItems(Player receiver, SQLTransaction trans) + { + if (m_mailTemplateId == 0 || !m_mailTemplateItemsNeed) + return; + + m_mailTemplateItemsNeed = false; + + Loot mailLoot = new Loot(); + + // can be empty + mailLoot.FillLoot(m_mailTemplateId, LootStorage.Mail, receiver, true, true); + + uint max_slot = mailLoot.GetMaxSlotInLootFor(receiver); + for (uint i = 0; m_items.Count < SharedConst.MaxMailItems && i < max_slot; ++i) + { + LootItem lootitem = mailLoot.LootItemInSlot(i, receiver); + if (lootitem != null) + { + Item item = Item.CreateItem(lootitem.itemid, lootitem.count, receiver); + if (item != null) + { + item.SaveToDB(trans); // save for prevent lost at next mail load, if send fail then item will deleted + AddItem(item); + } + } + } + } + + void deleteIncludedItems(SQLTransaction trans, bool inDB = false) + { + foreach (var item in m_items.Values) + { + if (inDB) + { + PreparedStatement stmt = DB.Characters.GetPreparedStatement(CharStatements.DEL_ITEM_INSTANCE); + stmt.AddValue(0, item.GetGUID().GetCounter()); + trans.Append(stmt); + } + } + + m_items.Clear(); + } + + public void SendReturnToSender(uint senderAcc, ulong senderGuid, ulong receiver_guid, SQLTransaction trans) + { + ObjectGuid receiverGuid = ObjectGuid.Create(HighGuid.Player, receiver_guid); + Player receiver = Global.ObjAccessor.FindPlayer(receiverGuid); + + uint rc_account = 0; + if (receiver == null) + rc_account = ObjectManager.GetPlayerAccountIdByGUID(receiverGuid); + + if (receiver == null && rc_account == 0) // sender not exist + { + deleteIncludedItems(trans, true); + return; + } + + // prepare mail and send in other case + bool needItemDelay = false; + + if (!m_items.Empty()) + { + // if item send to character at another account, then apply item delivery delay + needItemDelay = senderAcc != rc_account; + + // set owner to new receiver (to prevent delete item with sender char deleting) + foreach (var item in m_items.Values) + { + item.SaveToDB(trans); // item not in inventory and can be save standalone + // owner in data will set at mail receive and item extracting + PreparedStatement stmt = DB.Characters.GetPreparedStatement(CharStatements.UPD_ITEM_OWNER); + stmt.AddValue(0, receiver_guid); + stmt.AddValue(1, item.GetGUID().GetCounter()); + trans.Append(stmt); + } + } + + // If theres is an item, there is a one hour delivery delay. + uint deliver_delay = needItemDelay ? WorldConfig.GetUIntValue(WorldCfg.MailDeliveryDelay) : 0; + + // will delete item or place to receiver mail list + SendMailTo(trans, new MailReceiver(receiver, receiver_guid), new MailSender(MailMessageType.Normal, senderGuid), MailCheckMask.Returned, deliver_delay); + } + + public void SendMailTo(SQLTransaction trans, Player receiver, MailSender sender, MailCheckMask checkMask = MailCheckMask.None, uint deliver_delay = 0) + { + SendMailTo(trans, new MailReceiver(receiver), sender, checkMask, deliver_delay); + } + public void SendMailTo(SQLTransaction trans, MailReceiver receiver, MailSender sender, MailCheckMask checkMask = MailCheckMask.None, uint deliver_delay = 0) + { + Player pReceiver = receiver.GetPlayer(); // can be NULL + Player pSender = Global.ObjAccessor.FindPlayer(ObjectGuid.Create(HighGuid.Player, sender.GetSenderId())); + + if (pReceiver != null) + prepareItems(pReceiver, trans); // generate mail template items + + uint mailId = Global.ObjectMgr.GenerateMailID(); + + long deliver_time = Time.UnixTime + deliver_delay; + + //expire time if COD 3 days, if no COD 30 days, if auction sale pending 1 hour + uint expire_delay; + + // auction mail without any items and money + if (sender.GetMailMessageType() == MailMessageType.Auction && m_items.Empty() && m_money == 0) + expire_delay = WorldConfig.GetUIntValue(WorldCfg.MailDeliveryDelay); + // default case: expire time if COD 3 days, if no COD 30 days (or 90 days if sender is a game master) + else + if (m_COD != 0) + expire_delay = 3 * Time.Day; + else + expire_delay = (uint)(pSender != null && pSender.IsGameMaster() ? 90 * Time.Day : 30 * Time.Day); + + long expire_time = deliver_time + expire_delay; + + // Add to DB + byte index = 0; + PreparedStatement stmt = DB.Characters.GetPreparedStatement(CharStatements.INS_MAIL); + stmt.AddValue(index, mailId); + stmt.AddValue(++index, (byte)sender.GetMailMessageType()); + stmt.AddValue(++index, (sbyte)sender.GetStationery()); + stmt.AddValue(++index, GetMailTemplateId()); + stmt.AddValue(++index, sender.GetSenderId()); + stmt.AddValue(++index, receiver.GetPlayerGUIDLow()); + stmt.AddValue(++index, GetSubject()); + stmt.AddValue(++index, GetBody()); + stmt.AddValue(++index, !m_items.Empty()); + stmt.AddValue(++index, expire_time); + stmt.AddValue(++index, deliver_time); + stmt.AddValue(++index, m_money); + stmt.AddValue(++index, m_COD); + stmt.AddValue(++index, (byte)checkMask); + trans.Append(stmt); + + foreach (var item in m_items.Values) + { + stmt = DB.Characters.GetPreparedStatement(CharStatements.INS_MAIL_ITEM); + stmt.AddValue(0, mailId); + stmt.AddValue(1, item.GetGUID().GetCounter()); + stmt.AddValue(2, receiver.GetPlayerGUIDLow()); + trans.Append(stmt); + } + + // For online receiver update in game mail status and data + if (pReceiver != null) + { + pReceiver.AddNewMailDeliverTime(deliver_time); + + if (pReceiver.IsMailsLoaded()) + { + Mail m = new Mail(); + m.messageID = mailId; + m.mailTemplateId = GetMailTemplateId(); + m.subject = GetSubject(); + m.body = GetBody(); + m.money = GetMoney(); + m.COD = GetCOD(); + + foreach (var item in m_items.Values) + m.AddItem(item.GetGUID().GetCounter(), item.GetEntry()); + + m.messageType = sender.GetMailMessageType(); + m.stationery = sender.GetStationery(); + m.sender = sender.GetSenderId(); + m.receiver = receiver.GetPlayerGUIDLow(); + m.expire_time = expire_time; + m.deliver_time = deliver_time; + m.checkMask = checkMask; + m.state = MailState.Unchanged; + + pReceiver.AddMail(m); // to insert new mail to beginning of maillist + + if (!m_items.Empty()) + { + foreach (var item in m_items.Values) + pReceiver.AddMItem(item); + } + } + else if (!m_items.Empty()) + deleteIncludedItems(null); + } + else if (!m_items.Empty()) + deleteIncludedItems(null); + } + + uint GetMailTemplateId() { return m_mailTemplateId; } + string GetSubject() { return m_subject; } + ulong GetMoney() { return m_money; } + ulong GetCOD() { return m_COD; } + string GetBody() { return m_body; } + + public MailDraft AddMoney(ulong money) + { + m_money = money; + return this; + } + public MailDraft AddCOD(uint COD) + { + m_COD = COD; + return this; + } + + uint m_mailTemplateId; + bool m_mailTemplateItemsNeed; + string m_subject; + string m_body; + + Dictionary m_items = new Dictionary(); + + ulong m_money; + ulong m_COD; + } +} diff --git a/Game/Maps/AreaBoundary.cs b/Game/Maps/AreaBoundary.cs new file mode 100644 index 000000000..be9b412ac --- /dev/null +++ b/Game/Maps/AreaBoundary.cs @@ -0,0 +1,271 @@ +/* + * Copyright (C) 2012-2017 CypherCore + * + * 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, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +using Game.Entities; + +namespace Game.Maps +{ + public class AreaBoundary + { + public AreaBoundary(BoundaryType bType, bool isInverted) + { + m_boundaryType = bType; + m_isInvertedBoundary = isInverted; + } + + public BoundaryType GetBoundaryType() { return m_boundaryType; } + + public bool IsWithinBoundary(Position pos) { return (IsWithinBoundaryArea(pos) != m_isInvertedBoundary); } + + public virtual bool IsWithinBoundaryArea(Position pos) { return false; } + + BoundaryType m_boundaryType; + bool m_isInvertedBoundary; + + public enum BoundaryType + { + Rectangle, // Rectangle Aligned With The Coordinate Axis + Circle, + Ellipse, + Triangle, + Parallelogram, + ZRange, + } + + public class DoublePosition : Position + { + public DoublePosition(double x = 0.0, double y = 0.0, double z = 0.0, float o = 0f) : base((float)x, (float)y, (float)z, o) + { + d_positionX = x; + d_positionY = y; + d_positionZ = z; + } + public DoublePosition(float x, float y = 0f, float z = 0f, float o = 0f) : base(x, y, z, o) + { + d_positionX = x; + d_positionY = y; + d_positionZ = z; + } + public DoublePosition(Position pos) : this(pos.GetPositionX(), pos.GetPositionY(), pos.GetPositionZ(), pos.GetOrientation()) { } + + public double GetDoublePositionX() { return d_positionX; } + public double GetDoublePositionY() { return d_positionY; } + public double GetDoublePositionZ() { return d_positionZ; } + + public double GetDoubleExactDist2dSq(DoublePosition pos) + { + double offX = GetDoublePositionX() - pos.GetDoublePositionX(); + double offY = GetDoublePositionY() - pos.GetDoublePositionY(); + return (offX * offX) + (offY * offY); + } + + public Position sync() + { + posX = (float)d_positionX; + posY = (float)d_positionY; + posZ = (float)d_positionZ; + return this; + } + + double d_positionX; + double d_positionY; + double d_positionZ; + } + } + + public class RectangleBoundary : AreaBoundary + { + // X axis is north/south, Y axis is east/west, larger values are northwest + public RectangleBoundary(float southX, float northX, float eastY, float westY, bool isInverted = false) : base(BoundaryType.Rectangle, isInverted) + { + _minX = southX; + _maxX = northX; + _minY = eastY; + _maxY = westY; + } + + public override bool IsWithinBoundaryArea(Position pos) + { + if (pos == null) + return false; + + return !(pos.GetPositionX() < _minX || pos.GetPositionX() > _maxX || pos.GetPositionY() < _minY || pos.GetPositionY() > _maxY); + } + + float _minX; + float _maxX; + float _minY; + float _maxY; + } + + public class CircleBoundary : AreaBoundary + { + public CircleBoundary(Position center, double radius, bool isInverted = false) : this(new DoublePosition(center), radius, isInverted) { } + public CircleBoundary(Position center, Position pointOnCircle, bool isInverted = false) : this(new DoublePosition(center), new DoublePosition(pointOnCircle), isInverted) { } + public CircleBoundary(DoublePosition center, double radius, bool isInverted = false) : base(BoundaryType.Circle, isInverted) + { + _center = center; + _radiusSq = radius * radius; + } + public CircleBoundary(DoublePosition center, DoublePosition pointOnCircle, bool isInverted = false) : base(BoundaryType.Circle, isInverted) + { + _center = center; + _radiusSq = center.GetDoubleExactDist2dSq(pointOnCircle); + } + + public override bool IsWithinBoundaryArea(Position pos) + { + if (pos == null) + return false; + + double offX = _center.GetDoublePositionX() - pos.GetPositionX(); + double offY = _center.GetDoublePositionY() - pos.GetPositionY(); + return offX * offX + offY * offY <= _radiusSq; + } + + DoublePosition _center; + double _radiusSq; + } + + public class EllipseBoundary : AreaBoundary + { + public EllipseBoundary(Position center, double radiusX, double radiusY, bool isInverted = false) : this(new DoublePosition(center), radiusX, radiusY, isInverted) { } + public EllipseBoundary(DoublePosition center, double radiusX, double radiusY, bool isInverted = false) :base(BoundaryType.Ellipse, isInverted) + { + _center = center; + _radiusYSq = radiusY * radiusY; + _scaleXSq = _radiusYSq / (radiusX * radiusX); + } + + public override bool IsWithinBoundaryArea(Position pos) + { + if (pos == null) + return false; + + double offX = _center.GetDoublePositionX() - pos.GetPositionX(), offY = _center.GetDoublePositionY() - pos.GetPositionY(); + return (offX * offX) * _scaleXSq + (offY * offY) <= _radiusYSq; + } + + DoublePosition _center; + double _radiusYSq; + double _scaleXSq; + } + + public class TriangleBoundary : AreaBoundary + { + public TriangleBoundary(Position pointA, Position pointB, Position pointC, bool isInverted = false) + : this(new DoublePosition(pointA), new DoublePosition(pointB), new DoublePosition(pointC), isInverted) { } + public TriangleBoundary(DoublePosition pointA, DoublePosition pointB, DoublePosition pointC, bool isInverted = false) : base(BoundaryType.Triangle, isInverted) + { + _a = pointA; + _b = pointB; + _c = pointC; + _abx = _b.GetDoublePositionX() - _a.GetDoublePositionX(); + _bcx = _c.GetDoublePositionX() - _b.GetDoublePositionX(); + _cax = _a.GetDoublePositionX() - _c.GetDoublePositionX(); + _aby = _b.GetDoublePositionY() - _a.GetDoublePositionY(); + _bcy = _c.GetDoublePositionY() - _b.GetDoublePositionY(); + _cay = _a.GetDoublePositionY() - _c.GetDoublePositionY(); + } + + public override bool IsWithinBoundaryArea(Position pos) + { + if (pos == null) + return false; + + // half-plane signs + bool sign1 = ((-_b.GetDoublePositionX() + pos.GetPositionX()) * _aby - (-_b.GetDoublePositionY() + pos.GetPositionY()) * _abx) < 0; + bool sign2 = ((-_c.GetDoublePositionX() + pos.GetPositionX()) * _bcy - (-_c.GetDoublePositionY() + pos.GetPositionY()) * _bcx) < 0; + bool sign3 = ((-_a.GetDoublePositionX() + pos.GetPositionX()) * _cay - (-_a.GetDoublePositionY() + pos.GetPositionY()) * _cax) < 0; + + // if all signs are the same, the point is inside the triangle + return ((sign1 == sign2) && (sign2 == sign3)); + } + + DoublePosition _a; + DoublePosition _b; + DoublePosition _c; + double _abx; + double _bcx; + double _cax; + double _aby; + double _bcy; + double _cay; + } + + public class ParallelogramBoundary : AreaBoundary + { + // Note: AB must be orthogonal to AD + public ParallelogramBoundary(Position cornerA, Position cornerB, Position cornerD, bool isInverted = false) + : this(new DoublePosition(cornerA), new DoublePosition(cornerB), new DoublePosition(cornerD), isInverted) { } + public ParallelogramBoundary(DoublePosition cornerA, DoublePosition cornerB, DoublePosition cornerD, bool isInverted = false) : base(BoundaryType.Parallelogram, isInverted) + { + _a = cornerA; + _b = cornerB; + _d = cornerD; + _c = new DoublePosition(_d.GetDoublePositionX() + (_b.GetDoublePositionX() - _a.GetDoublePositionX()), _d.GetDoublePositionY() + (_b.GetDoublePositionY() - _a.GetDoublePositionY())); + _abx = _b.GetDoublePositionX() - _a.GetDoublePositionX(); + _dax = _a.GetDoublePositionX() - _d.GetDoublePositionX(); + _aby = _b.GetDoublePositionY() - _a.GetDoublePositionY(); + _day = _a.GetDoublePositionY() - _d.GetDoublePositionY(); + } + + public override bool IsWithinBoundaryArea(Position pos) + { + if (pos == null) + return false; + + // half-plane signs + bool sign1 = ((-_b.GetDoublePositionX() + pos.GetPositionX()) * _aby - (-_b.GetDoublePositionY() + pos.GetPositionY()) * _abx) < 0; + bool sign2 = ((-_a.GetDoublePositionX() + pos.GetPositionX()) * _day - (-_a.GetDoublePositionY() + pos.GetPositionY()) * _dax) < 0; + bool sign3 = ((-_d.GetDoublePositionY() + pos.GetPositionY()) * _abx - (-_d.GetDoublePositionX() + pos.GetPositionX()) * _aby) < 0; // AB = -CD + bool sign4 = ((-_c.GetDoublePositionY() + pos.GetPositionY()) * _dax - (-_c.GetDoublePositionX() + pos.GetPositionX()) * _day) < 0; // DA = -BC + + // if all signs are equal, the point is inside + return ((sign1 == sign2) && (sign2 == sign3) && (sign3 == sign4)); + } + + DoublePosition _a; + DoublePosition _b; + DoublePosition _d; + DoublePosition _c; + double _abx; + double _dax; + double _aby; + double _day; + } + + public class ZRangeBoundary : AreaBoundary + { + public ZRangeBoundary(float minZ, float maxZ, bool isInverted = false) : base(BoundaryType.ZRange, isInverted) + { + _minZ = minZ; + _maxZ = maxZ; + } + + public override bool IsWithinBoundaryArea(Position pos) + { + if (pos == null) + return false; + + return !(pos.GetPositionZ() < _minZ || pos.GetPositionZ() > _maxZ); + } + + float _minZ; + float _maxZ; + } +} diff --git a/Game/Maps/Cell.cs b/Game/Maps/Cell.cs new file mode 100644 index 000000000..992e88200 --- /dev/null +++ b/Game/Maps/Cell.cs @@ -0,0 +1,319 @@ +/* + * Copyright (C) 2012-2017 CypherCore + * + * 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, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +using Framework.Constants; +using Game.Entities; +using System; + +namespace Game.Maps +{ + public class Cell + { + public Cell(ICoord p) + { + data.grid_x = p.x_coord / MapConst.MaxCells; + data.grid_y = p.y_coord / MapConst.MaxCells; + data.cell_x = p.x_coord % MapConst.MaxCells; + data.cell_y = p.y_coord % MapConst.MaxCells; + } + + public Cell(float x, float y) + { + ICoord p = GridDefines.ComputeCellCoord(x, y); + data.grid_x = p.x_coord / MapConst.MaxCells; + data.grid_y = p.y_coord / MapConst.MaxCells; + data.cell_x = p.x_coord % MapConst.MaxCells; + data.cell_y = p.y_coord % MapConst.MaxCells; + } + + public Cell(Cell cell) { data = cell.data; } + + public bool IsCellValid() + { + return data.cell_x < MapConst.MaxCells && data.cell_y < MapConst.MaxCells; + } + + public uint GetId() + { + return data.grid_x * MapConst.MaxGrids + data.grid_y; + } + + public uint GetCellX() { return data.cell_x; } + public uint GetCellY() { return data.cell_y; } + public uint GetGridX() { return data.grid_x; } + public uint GetGridY() { return data.grid_y; } + public bool NoCreate() { return data.nocreate; } + public void SetNoCreate() { data.nocreate = true; } + + public string GetGridCellString() + { + return string.Format("grid[{0}, {1}]cell[{2}, {3}]", GetGridX(), GetGridY(), GetCellX(), GetCellY()); + } + + public struct Data + { + public uint grid_x; + public uint grid_y; + public uint cell_x; + public uint cell_y; + public bool nocreate; + } + public Data data; + + public CellCoord GetCellCoord() + { + return new CellCoord( + data.grid_x * MapConst.MaxCells + data.cell_x, + data.grid_y * MapConst.MaxCells + data.cell_y); + } + + public bool DiffCell(Cell cell) + { + return (data.cell_x != cell.data.cell_x || + data.cell_y != cell.data.cell_y); + } + + public bool DiffGrid(Cell cell) + { + return (data.grid_x != cell.data.grid_x || + data.grid_y != cell.data.grid_y); + } + + public void Visit(CellCoord standing_cell, Visitor visitor, Map map, WorldObject obj, float radius) + { + //we should increase search radius by object's radius, otherwise + //we could have problems with huge creatures, which won't attack nearest players etc + Visit(standing_cell, visitor, map, obj.GetPositionX(), obj.GetPositionY(), radius + obj.GetObjectSize()); + } + + public void Visit(CellCoord standing_cell, Visitor visitor, Map map, float x_off, float y_off, float radius) + { + if (!standing_cell.IsCoordValid()) + return; + + //no jokes here... Actually placing ASSERT() here was good idea, but + //we had some problems with DynamicObjects, which pass radius = 0.0f (DB issue?) + //maybe it is better to just return when radius <= 0.0f? + if (radius <= 0.0f) + { + map.Visit(this, visitor); + return; + } + //lets limit the upper value for search radius + if (radius > MapConst.SizeofGrids) + radius = MapConst.SizeofGrids; + + //lets calculate object coord offsets from cell borders. + CellArea area = CalculateCellArea(x_off, y_off, radius); + //if radius fits inside standing cell + if (area == null) + { + map.Visit(this, visitor); + return; + } + + //visit all cells, found in CalculateCellArea() + //if radius is known to reach cell area more than 4x4 then we should call optimized VisitCircle + //currently this technique works with MAX_NUMBER_OF_CELLS 16 and higher, with lower values + //there are nothing to optimize because SIZE_OF_GRID_CELL is too big... + if ((area.high_bound.x_coord > (area.low_bound.x_coord + 4)) && (area.high_bound.y_coord > (area.low_bound.y_coord + 4))) + { + VisitCircle(visitor, map, area.low_bound, area.high_bound); + return; + } + + //ALWAYS visit standing cell first!!! Since we deal with small radiuses + //it is very essential to call visitor for standing cell firstly... + map.Visit(this, visitor); + + // loop the cell range + for (uint x = area.low_bound.x_coord; x <= area.high_bound.x_coord; ++x) + { + for (uint y = area.low_bound.y_coord; y <= area.high_bound.y_coord; ++y) + { + CellCoord cellCoord = new CellCoord(x, y); + //lets skip standing cell since we already visited it + if (cellCoord != standing_cell) + { + Cell r_zone = new Cell(cellCoord); + r_zone.data.nocreate = data.nocreate; + map.Visit(r_zone, visitor); + } + } + } + } + + void VisitCircle(Visitor visitor, Map map, ICoord begin_cell, ICoord end_cell) + { + //here is an algorithm for 'filling' circum-squared octagon + uint x_shift = (uint)Math.Ceiling((end_cell.x_coord - begin_cell.x_coord) * 0.3f - 0.5f); + //lets calculate x_start/x_end coords for central strip... + uint x_start = begin_cell.x_coord + x_shift; + uint x_end = end_cell.x_coord - x_shift; + + //visit central strip with constant width... + for (uint x = x_start; x <= x_end; ++x) + { + for (uint y = begin_cell.y_coord; y <= end_cell.y_coord; ++y) + { + CellCoord cellCoord = new CellCoord(x, y); + Cell r_zone = new Cell(cellCoord); + r_zone.data.nocreate = data.nocreate; + map.Visit(r_zone, visitor); + } + } + + //if x_shift == 0 then we have too small cell area, which were already + //visited at previous step, so just return from procedure... + if (x_shift == 0) + return; + + uint y_start = end_cell.y_coord; + uint y_end = begin_cell.y_coord; + //now we are visiting borders of an octagon... + for (uint step = 1; step <= (x_start - begin_cell.x_coord); ++step) + { + //each step reduces strip height by 2 cells... + y_end += 1; + y_start -= 1; + for (uint y = y_start; y >= y_end; --y) + { + //we visit cells symmetrically from both sides, heading from center to sides and from up to bottom + //e.g. filling 2 trapezoids after filling central cell strip... + CellCoord cellCoord_left = new CellCoord(x_start - step, y); + Cell r_zone_left = new Cell(cellCoord_left); + r_zone_left.data.nocreate = data.nocreate; + map.Visit(r_zone_left, visitor); + + //right trapezoid cell visit + CellCoord cellCoord_right = new CellCoord(x_end + step, y); + Cell r_zone_right = new Cell(cellCoord_right); + r_zone_right.data.nocreate = data.nocreate; + map.Visit(r_zone_right, visitor); + } + } + } + + public static void VisitGridObjects(WorldObject center_obj, Notifier visitor, float radius, bool dont_load = true) + { + CellCoord p = GridDefines.ComputeCellCoord(center_obj.GetPositionX(), center_obj.GetPositionY()); + Cell cell = new Cell(p); + if (dont_load) + cell.SetNoCreate(); + + Visitor gnotifier = new Visitor(visitor, GridMapTypeMask.AllGrid); + cell.Visit(p, gnotifier, center_obj.GetMap(), center_obj, radius); + } + + public static void VisitWorldObjects(WorldObject center_obj, Notifier visitor, float radius, bool dont_load = true) + { + CellCoord p = GridDefines.ComputeCellCoord(center_obj.GetPositionX(), center_obj.GetPositionY()); + Cell cell = new Cell(p); + if (dont_load) + cell.SetNoCreate(); + + Visitor gnotifier = new Visitor(visitor, GridMapTypeMask.AllWorld); + cell.Visit(p, gnotifier, center_obj.GetMap(), center_obj, radius); + } + + public static void VisitAllObjects(WorldObject center_obj, Notifier visitor, float radius, bool dont_load = true) + { + CellCoord p = GridDefines.ComputeCellCoord(center_obj.GetPositionX(), center_obj.GetPositionY()); + Cell cell = new Cell(p); + if (dont_load) + cell.SetNoCreate(); + + Visitor wnotifier = new Visitor(visitor, GridMapTypeMask.AllWorld); + cell.Visit(p, wnotifier, center_obj.GetMap(), center_obj, radius); + Visitor gnotifier = new Visitor(visitor, GridMapTypeMask.AllGrid); + cell.Visit(p, gnotifier, center_obj.GetMap(), center_obj, radius); + } + + public static void VisitGridObjects(float x, float y, Map map, Notifier visitor, float radius, bool dont_load = true) + { + CellCoord p = GridDefines.ComputeCellCoord(x, y); + Cell cell = new Cell(p); + if (dont_load) + cell.SetNoCreate(); + + Visitor gnotifier = new Visitor(visitor, GridMapTypeMask.AllGrid); + cell.Visit(p, gnotifier, map, x, y, radius); + } + + public static void VisitWorldObjects(float x, float y, Map map, Notifier visitor, float radius, bool dont_load = true) + { + CellCoord p = GridDefines.ComputeCellCoord(x, y); + Cell cell = new Cell(p); + if (dont_load) + cell.SetNoCreate(); + + Visitor gnotifier = new Visitor(visitor, GridMapTypeMask.AllWorld); + cell.Visit(p, gnotifier, map, x, y, radius); + } + + public static void VisitAllObjects(float x, float y, Map map, Notifier visitor, float radius, bool dont_load = true) + { + CellCoord p = GridDefines.ComputeCellCoord(x, y); + Cell cell = new Cell(p); + if (dont_load) + cell.SetNoCreate(); + + Visitor wnotifier = new Visitor(visitor, GridMapTypeMask.AllWorld); + cell.Visit(p, wnotifier, map, x, y, radius); + Visitor gnotifier = new Visitor(visitor, GridMapTypeMask.AllGrid); + cell.Visit(p, gnotifier, map, x, y, radius); + } + + public static CellArea CalculateCellArea(float x, float y, float radius) + { + if (radius <= 0.0f) + { + CellCoord center = (CellCoord)GridDefines.ComputeCellCoord(x, y).normalize(); + return new CellArea(center, center); + } + + CellCoord centerX = (CellCoord)GridDefines.ComputeCellCoord(x - radius, y - radius).normalize(); + CellCoord centerY = (CellCoord)GridDefines.ComputeCellCoord(x + radius, y + radius).normalize(); + + return new CellArea(centerX, centerY); + } + } + + public class CellArea + { + public CellArea() { } + public CellArea(CellCoord low, CellCoord high) + { + low_bound = low; + high_bound = high; + } + + void ResizeBorders(ICoord begin_cell, ICoord end_cell) + { + begin_cell = low_bound; + end_cell = high_bound; + } + + public bool Check() + { + return low_bound == high_bound; + } + + public ICoord low_bound; + public ICoord high_bound; + } +} diff --git a/Game/Maps/Grid.cs b/Game/Maps/Grid.cs new file mode 100644 index 000000000..d8a7515fe --- /dev/null +++ b/Game/Maps/Grid.cs @@ -0,0 +1,477 @@ +/* + * Copyright (C) 2012-2017 CypherCore + * + * 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, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +using Framework.Constants; +using Game.Entities; +using System.Collections.Generic; +using System.Linq; + +namespace Game.Maps +{ + public class GridInfo + { + public GridInfo() + { + i_timer = new TimeTracker(0); + vis_Update = new PeriodicTimer(0, RandomHelper.IRand(0, 1000)); + i_unloadActiveLockCount = 0; + i_unloadExplicitLock = false; + i_unloadReferenceLock = false; + } + + public GridInfo(long expiry, bool unload = true) + { + i_timer = new TimeTracker((int)expiry); + vis_Update = new PeriodicTimer(0, RandomHelper.IRand(0, 1000)); + i_unloadActiveLockCount = 0; + i_unloadExplicitLock = !unload; + i_unloadReferenceLock = false; + } + + public TimeTracker getTimeTracker() + { + return i_timer; + } + + public bool getUnloadLock() + { + return i_unloadActiveLockCount != 0 || i_unloadExplicitLock || i_unloadReferenceLock; + } + + public void setUnloadExplicitLock(bool on) + { + i_unloadExplicitLock = on; + } + + public void setUnloadReferenceLock(bool on) + { + i_unloadReferenceLock = on; + } + + public void incUnloadActiveLock() + { + ++i_unloadActiveLockCount; + } + + public void decUnloadActiveLock() + { + if (i_unloadActiveLockCount != 0) --i_unloadActiveLockCount; + } + + private void setTimer(TimeTracker pTimer) + { + i_timer = pTimer; + } + + public void ResetTimeTracker(long interval) + { + i_timer.Reset(interval); + } + + public void UpdateTimeTracker(long diff) + { + i_timer.Update(diff); + } + + public PeriodicTimer getRelocationTimer() + { + return vis_Update; + } + + TimeTracker i_timer; + PeriodicTimer vis_Update; + + ushort i_unloadActiveLockCount; // lock from active object spawn points (prevent clone loading) + bool i_unloadExplicitLock; // explicit manual lock or config setting + bool i_unloadReferenceLock; // lock from instance map copy + } + + public class Grid + { + public Grid(uint id, uint x, uint y, long expiry, bool unload = true) + { + gridId = id; + gridX = x; + gridY = y; + gridInfo = new GridInfo(expiry, unload); + gridState = GridState.Invalid; + gridObjectDataLoaded = false; + + for (uint xx = 0; xx < MapConst.MaxCells; ++xx) + { + i_cells[xx] = new GridCell[MapConst.MaxCells]; + for (uint yy = 0; yy < MapConst.MaxCells; ++yy) + i_cells[xx][yy] = new GridCell(); + } + } + + public Grid(Cell cell, uint expiry, bool unload = true) : this(cell.GetId(), cell.GetGridX(), cell.GetGridY(), expiry, unload) { } + + public GridCell GetGridCell(uint x, uint y) + { + return i_cells[x][y]; + } + + public uint GetGridId() + { + return gridId; + } + + private void SetGridId(uint id) + { + gridId = id; + } + + public GridState GetGridState() + { + return gridState; + } + + public void SetGridState(GridState s) + { + gridState = s; + } + + public uint getX() + { + return gridX; + } + + public uint getY() + { + return gridY; + } + + public bool isGridObjectDataLoaded() + { + return gridObjectDataLoaded; + } + + public void setGridObjectDataLoaded(bool pLoaded) + { + gridObjectDataLoaded = pLoaded; + } + + public GridInfo getGridInfoRef() + { + return gridInfo; + } + + private TimeTracker getTimeTracker() + { + return gridInfo.getTimeTracker(); + } + + public bool getUnloadLock() + { + return gridInfo.getUnloadLock(); + } + + public void setUnloadExplicitLock(bool on) + { + gridInfo.setUnloadExplicitLock(on); + } + + public void setUnloadReferenceLock(bool on) + { + gridInfo.setUnloadReferenceLock(on); + } + + public void incUnloadActiveLock() + { + gridInfo.incUnloadActiveLock(); + } + + public void decUnloadActiveLock() + { + gridInfo.decUnloadActiveLock(); + } + + public void ResetTimeTracker(long interval) + { + gridInfo.ResetTimeTracker(interval); + } + + public void UpdateTimeTracker(long diff) + { + gridInfo.UpdateTimeTracker(diff); + } + + public void Update(Map m, uint diff) + { + switch (GetGridState()) + { + case GridState.Active: + // Only check grid activity every (grid_expiry/10) ms, because it's really useless to do it every cycle + getGridInfoRef().UpdateTimeTracker(diff); + if (getGridInfoRef().getTimeTracker().Passed()) + { + if (GetWorldObjectCountInNGrid() == 0 && !m.ActiveObjectsNearGrid(this)) + { + ObjectGridStoper worker = new ObjectGridStoper(); + var visitor = new Visitor(worker, GridMapTypeMask.AllGrid); + VisitAllGrids(visitor); + SetGridState(GridState.Idle); + Log.outDebug(LogFilter.Maps, "Grid[{0}, {1}] on map {2} moved to IDLE state", getX(), getY(), + m.GetId()); + } + else + m.ResetGridExpiry(this, 0.1f); + } + break; + case GridState.Idle: + m.ResetGridExpiry(this); + SetGridState(GridState.Removal); + Log.outDebug(LogFilter.Maps, "Grid[{0}, {1}] on map {2} moved to REMOVAL state", getX(), getY(), + m.GetId()); + break; + case GridState.Removal: + if (!getGridInfoRef().getUnloadLock()) + { + getGridInfoRef().UpdateTimeTracker(diff); + if (getGridInfoRef().getTimeTracker().Passed()) + { + if (!m.UnloadGrid(this, false)) + { + Log.outDebug(LogFilter.Maps, + "Grid[{0}, {1}] for map {2} differed unloading due to players or active objects nearby", + getX(), getY(), m.GetId()); + m.ResetGridExpiry(this); + } + } + } + break; + } + } + + public void VisitAllGrids(Visitor visitor) + { + for (uint x = 0; x < MapConst.MaxCells; ++x) + for (uint y = 0; y < MapConst.MaxCells; ++y) + GetGridCell(x, y).Visit(visitor); + } + + public void VisitGrid(uint x, uint y, Visitor visitor) + { + GetGridCell(x, y).Visit(visitor); + } + + public uint GetWorldObjectCountInNGrid() where T : WorldObject + { + uint count = 0; + for (uint x = 0; x < MapConst.MaxCells; ++x) + for (uint y = 0; y < MapConst.MaxCells; ++y) + count += i_cells[x][y].GetWorldObjectCountInGrid(); + return count; + } + + uint gridId; + uint gridX; + uint gridY; + GridInfo gridInfo; + GridState gridState; + bool gridObjectDataLoaded; + GridCell[][] i_cells = new GridCell[MapConst.MaxCells][]; + } + + public class GridCell + { + public GridCell() + { + _objects = new MultiTypeContainer(); + _container = new MultiTypeContainer(); + } + + public void Visit(Visitor visitor) + { + switch (visitor._mask) + { + case GridMapTypeMask.AllGrid: + visitor.Visit(_container.gameObjects.ToList()); + visitor.Visit(_container.creatures.ToList()); + visitor.Visit(_container.dynamicObjects.ToList()); + visitor.Visit(_container.corpses.ToList()); + visitor.Visit(_container.areaTriggers.ToList()); + visitor.Visit(_container.conversations.ToList()); + visitor.Visit(_container.worldObjects.ToList()); + break; + case GridMapTypeMask.AllWorld: + visitor.Visit(_objects.players); + visitor.Visit(_objects.creatures.ToList()); + visitor.Visit(_objects.corpses.ToList()); + visitor.Visit(_objects.dynamicObjects.ToList()); + visitor.Visit(_objects.worldObjects.ToList()); + break; + default: + Log.outError(LogFilter.Server, "{0} called Visit with Unknown Mask {1}.", visitor.ToString(), visitor._mask); + break; + } + } + + public uint GetWorldObjectCountInGrid() where T : WorldObject + { + return (uint)_objects.GetCount(); + } + + public void AddWorldObject(WorldObject obj) + { + _objects.Insert(obj); + } + + public void AddGridObject(WorldObject obj) + { + _container.Insert(obj); + } + + public void RemoveWorldObject(WorldObject obj) + { + _objects.Remove(obj); + } + + public void RemoveGridObject(WorldObject obj) + { + _container.Remove(obj); + } + + public bool HasWorldObject(WorldObject obj) + { + return _objects.Contains(obj); + } + + public bool HasGridObject(WorldObject obj) + { + return _container.Contains(obj); + } + + /// + /// Holds all World objects - Player, Pets, Corpse(resurrectable), DynamicObject(farsight) + /// + MultiTypeContainer _objects; + + /// + /// Holds all Grid objects - GameObjects, Creatures(except pets), DynamicObject, Corpse(Bones), AreaTrigger, Conversation + /// + MultiTypeContainer _container; + } + + public class MultiTypeContainer + { + public void Insert(WorldObject obj) + { + worldObjects.Add(obj); + switch (obj.GetTypeId()) + { + case TypeId.Unit: + creatures.Add((Creature)obj); + break; + case TypeId.Player: + players.Add((Player)obj); + break; + case TypeId.GameObject: + gameObjects.Add((GameObject)obj); + break; + case TypeId.DynamicObject: + dynamicObjects.Add((DynamicObject)obj); + break; + case TypeId.Corpse: + corpses.Add((Corpse)obj); + break; + case TypeId.AreaTrigger: + areaTriggers.Add((AreaTrigger)obj); + break; + case TypeId.Conversation: + conversations.Add((Conversation)obj); + break; + } + } + + public void Remove(WorldObject obj) + { + worldObjects.Remove(obj); + switch (obj.GetTypeId()) + { + case TypeId.Unit: + creatures.Remove((Creature)obj); + break; + case TypeId.Player: + players.Remove((Player)obj); + break; + case TypeId.GameObject: + gameObjects.Remove((GameObject)obj); + break; + case TypeId.DynamicObject: + dynamicObjects.Remove((DynamicObject)obj); + break; + case TypeId.Corpse: + corpses.Remove((Corpse)obj); + break; + case TypeId.AreaTrigger: + areaTriggers.Remove((AreaTrigger)obj); + break; + case TypeId.Conversation: + conversations.Remove((Conversation)obj); + break; + } + } + + public bool Contains(WorldObject obj) + { + return worldObjects.Contains(obj); + } + + public int GetCount() + { + switch (typeof(T).Name) + { + case "Creature": + return creatures.Count; + case "Player": + return players.Count; + case "GameObject": + return gameObjects.Count; + case "DynamicObject": + return dynamicObjects.Count; + case "Corpse": + return corpses.Count; + case "AreaTrigger": + return areaTriggers.Count; + case "Conversation": + return conversations.Count; + } + + return 0; + } + + public List players = new List(); + public List creatures = new List(); + public List corpses = new List(); + public List dynamicObjects = new List(); + public List areaTriggers = new List(); + public List conversations = new List(); + public List gameObjects = new List(); + public List worldObjects = new List(); + } + + public enum GridState + { + Invalid = 0, + Active = 1, + Idle = 2, + Removal = 3, + Max = 4 + } +} \ No newline at end of file diff --git a/Game/Maps/GridDefines.cs b/Game/Maps/GridDefines.cs new file mode 100644 index 000000000..707166cf8 --- /dev/null +++ b/Game/Maps/GridDefines.cs @@ -0,0 +1,286 @@ +/* + * Copyright (C) 2012-2017 CypherCore + * + * 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, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +using Framework.Constants; +using Game.Entities; +using System; + +namespace Game.Maps +{ + public class GridDefines + { + public static bool IsValidMapCoord(float c) + { + return !float.IsInfinity(c) && (Math.Abs(c) <= (MapConst.MapHalfSize - 0.5f)); + } + + public static bool IsValidMapCoord(float x, float y) + { + return (IsValidMapCoord(x) && IsValidMapCoord(y)); + } + + public static bool IsValidMapCoord(float x, float y, float z) + { + return IsValidMapCoord(x, y) && IsValidMapCoord(z); + } + + public static bool IsValidMapCoord(float x, float y, float z, float o) + { + return IsValidMapCoord(x, y, z) && !float.IsInfinity(o); + } + + public static bool IsValidMapCoord(uint mapid, float x, float y) + { + return Global.MapMgr.IsValidMAP(mapid, false) && IsValidMapCoord(x, y); + } + + public static bool IsValidMapCoord(uint mapid, float x, float y, float z) + { + return Global.MapMgr.IsValidMAP(mapid, false) && IsValidMapCoord(x, y, z); + } + + public static bool IsValidMapCoord(uint mapid, float x, float y, float z, float o) + { + return Global.MapMgr.IsValidMAP(mapid, false) && IsValidMapCoord(x, y, z, o); + } + + public static bool IsValidMapCoord(WorldLocation loc) + { + return IsValidMapCoord(loc.GetMapId(), loc.GetPositionX(), loc.GetPositionY(), loc.GetPositionZ(), loc.GetOrientation()); + } + + public static void NormalizeMapCoord(ref float c) + { + if (c > MapConst.MapHalfSize - 0.5f) + c = MapConst.MapHalfSize - 0.5f; + else if (c < -(MapConst.MapHalfSize - 0.5f)) + c = -(MapConst.MapHalfSize - 0.5f); + } + + public static GridCoord ComputeGridCoord(float x, float y) + { + double x_offset = ((double)x - MapConst.CenterGridOffset) / MapConst.SizeofGrids; + double y_offset = ((double)y - MapConst.CenterGridOffset) / MapConst.SizeofGrids; + + uint x_val = (uint)(x_offset + MapConst.CenterGridId + 0.5f); + uint y_val = (uint)(y_offset + MapConst.CenterGridId + 0.5f); + return new GridCoord(x_val, y_val); + } + + public static CellCoord ComputeCellCoord(float x, float y) + { + double x_offset = ((double)x - MapConst.CenterGridCellOffset) / MapConst.SizeofCells; + double y_offset = ((double)y - MapConst.CenterGridCellOffset) / MapConst.SizeofCells; + + uint x_val = (uint)(x_offset + MapConst.CenterGridCellId + 0.5f); + uint y_val = (uint)(y_offset + MapConst.CenterGridCellId + 0.5f); + return new CellCoord(x_val, y_val); + } + } + + public class CellCoord : ICoord + { + const int Limit = MapConst.TotalCellsPerMap; + + public CellCoord(uint x, uint y) + { + x_coord = x; + y_coord = y; + } + public CellCoord(CellCoord obj) + { + x_coord = obj.x_coord; + y_coord = obj.y_coord; + } + + public bool IsCoordValid() + { + return x_coord < Limit && y_coord < Limit; + } + public ICoord normalize() + { + x_coord = Math.Min(x_coord, Limit - 1); + y_coord = Math.Min(y_coord, Limit - 1); + return this; + } + public uint GetId() + { + return y_coord * Limit + x_coord; + } + + public void dec_x(uint val) + { + if (x_coord > val) + x_coord -= val; + else + x_coord = 0; + } + public void inc_x(uint val) + { + if (x_coord + val < Limit) + x_coord += val; + else + x_coord = Limit - 1; + } + + public void dec_y(uint val) + { + if (y_coord > val) + y_coord -= val; + else + y_coord = 0; + } + public void inc_y(uint val) + { + if (y_coord + val < Limit) + y_coord += val; + else + y_coord = Limit - 1; + } + + public static bool operator ==(CellCoord p1, CellCoord p2) + { + return (p1.x_coord == p2.x_coord && p1.y_coord == p2.y_coord); + } + public static bool operator !=(CellCoord p1, CellCoord p2) + { + return !(p1 == p2); + } + public override bool Equals(object obj) + { + return base.Equals(obj); + } + + public override int GetHashCode() + { + return base.GetHashCode(); + } + + public uint x_coord { get; set; } + public uint y_coord { get; set; } + } + + public class GridCoord : ICoord + { + const int Limit = MapConst.MaxGrids; + + public GridCoord(uint x, uint y) + { + x_coord = x; + y_coord = y; + } + public GridCoord(GridCoord obj) + { + x_coord = obj.x_coord; + y_coord = obj.y_coord; + } + + public bool IsCoordValid() + { + return x_coord < Limit && y_coord < Limit; + } + public ICoord normalize() + { + x_coord = Math.Min(x_coord, Limit - 1); + y_coord = Math.Min(y_coord, Limit - 1); + return this; + } + public uint GetId() + { + return y_coord * Limit + x_coord; + } + + public void dec_x(uint val) + { + if (x_coord > val) + x_coord -= val; + else + x_coord = 0; + } + public void inc_x(uint val) + { + if (x_coord + val < Limit) + x_coord += val; + else + x_coord = Limit - 1; + } + + public void dec_y(uint val) + { + if (y_coord > val) + y_coord -= val; + else + y_coord = 0; + } + public void inc_y(uint val) + { + if (y_coord + val < Limit) + y_coord += val; + else + y_coord = Limit - 1; + } + + public static bool operator ==(GridCoord first, GridCoord other) + { + if (ReferenceEquals(first, other)) + return true; + + if ((object)first == null || (object)other == null) + return false; + + return first.Equals(other); + } + + public static bool operator !=(GridCoord first, GridCoord other) + { + return !(first == other); + } + + public override bool Equals(object obj) + { + return obj != null && obj is ObjectGuid && Equals((ObjectGuid)obj); + } + + public bool Equals(GridCoord other) + { + return other.x_coord == x_coord && other.y_coord == y_coord; + } + + public override int GetHashCode() + { + return new { x_coord, y_coord }.GetHashCode(); + } + + + public uint x_coord { get; set; } + public uint y_coord { get; set; } + } + + public interface ICoord + { + bool IsCoordValid(); + ICoord normalize(); + uint GetId(); + void dec_x(uint val); + void inc_x(uint val); + void dec_y(uint val); + void inc_y(uint val); + + uint x_coord { get; set; } + uint y_coord { get; set; } + } +} diff --git a/Game/Maps/GridMap.cs b/Game/Maps/GridMap.cs new file mode 100644 index 000000000..fd360635d --- /dev/null +++ b/Game/Maps/GridMap.cs @@ -0,0 +1,712 @@ +/* + * Copyright (C) 2012-2017 CypherCore + * + * 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, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +using Framework.Constants; +using Framework.GameMath; +using Game.DataStorage; +using System; +using System.Collections.Generic; +using System.IO; +using System.Runtime.InteropServices; + +namespace Game.Maps +{ + public class GridMap + { + public GridMap() + { + // Height level data + _gridHeight = MapConst.InvalidHeight; + _gridGetHeight = getHeightFromFlat; + + // Liquid data + _liquidLevel = MapConst.InvalidHeight; + } + + public bool loadData(string filename) + { + unloadData(); + if (!File.Exists(filename)) + return true; + + using (BinaryReader reader = new BinaryReader(new FileStream(filename, FileMode.Open, FileAccess.Read))) + { + mapFileHeader header = reader.ReadStruct(); + if (new string(header.mapMagic) != MapConst.MapMagic || new string(header.versionMagic) != MapConst.MapVersionMagic) + { + Log.outError(LogFilter.Maps, "Map file '{0}' is from an incompatible map version. Please recreate using the mapextractor.", filename); + return false; + } + if (header.areaMapOffset != 0) + LoadAreaData(reader, header.areaMapOffset); + + if (header.heightMapOffset != 0) + LoadHeightData(reader, header.heightMapOffset); + + if (header.liquidMapOffset != 0) + LoadLiquidData(reader, header.liquidMapOffset); + } + return true; + } + + public void unloadData() + { + _areaMap = null; + m_V9 = null; + m_V8 = null; + _liquidEntry = null; + _liquidFlags = null; + _liquidMap = null; + _gridGetHeight = getHeightFromFlat; + } + + void LoadAreaData(BinaryReader reader, uint offset) + { + reader.BaseStream.Seek(offset, SeekOrigin.Begin); + map_AreaHeader areaHeader = reader.ReadStruct(); + + _gridArea = areaHeader.gridArea; + + if (!areaHeader.flags.HasAnyFlag(AreaHeaderFlags.NoArea)) + { + _areaMap = new ushort[16 * 16]; + for (var i = 0; i < _areaMap.Length; ++i) + _areaMap[i] = reader.ReadUInt16(); + } + } + + void LoadHeightData(BinaryReader reader, uint offset) + { + reader.BaseStream.Seek(offset, SeekOrigin.Begin); + map_HeightHeader mapHeader = reader.ReadStruct(); + + _gridHeight = mapHeader.gridHeight; + _flags = (uint)mapHeader.flags; + + if (!mapHeader.flags.HasAnyFlag(HeightHeaderFlags.NoHeight)) + { + if (mapHeader.flags.HasAnyFlag(HeightHeaderFlags.HeightAsInt16)) + { + m_uint16_V9 = new ushort[129 * 129]; + for (var i = 0; i < m_uint16_V9.Length; ++i) + m_uint16_V9[i] = reader.ReadUInt16(); + + m_uint16_V8 = new ushort[128 * 128]; + for (var i = 0; i < m_uint16_V8.Length; ++i) + m_uint16_V8[i] = reader.ReadUInt16(); + + _gridIntHeightMultiplier = (mapHeader.gridMaxHeight - mapHeader.gridHeight) / 65535; + _gridGetHeight = getHeightFromUint16; + } + else if (mapHeader.flags.HasAnyFlag(HeightHeaderFlags.HeightAsInt8)) + { + m_ubyte_V9 = reader.ReadBytes(129 * 129); + m_ubyte_V8 = reader.ReadBytes(128 * 128); + _gridIntHeightMultiplier = (mapHeader.gridMaxHeight - mapHeader.gridHeight) / 255; + _gridGetHeight = getHeightFromUint8; + } + else + { + m_V9 = new float[129 * 129]; + for (var i = 0; i < m_V9.Length; ++i) + m_V9[i] = reader.ReadSingle(); + + m_V8 = new float[128 * 128]; + for (var i = 0; i < m_V8.Length; ++i) + m_V8[i] = reader.ReadSingle(); + + _gridGetHeight = getHeightFromFloat; + } + } + else + _gridGetHeight = getHeightFromFlat; + + if (mapHeader.flags.HasAnyFlag(HeightHeaderFlags.HeightHasFlightBounds)) + { + _maxHeight = new short[3 * 3]; + for (var i = 0; i < _maxHeight.Length; ++i) + _maxHeight[i] = reader.ReadInt16(); + + _minHeight = new short[3 * 3]; + for (var i = 0; i < _minHeight.Length; ++i) + _minHeight[i] = reader.ReadInt16(); + } + } + + void LoadLiquidData(BinaryReader reader, uint offset) + { + reader.BaseStream.Seek(offset, SeekOrigin.Begin); + map_LiquidHeader liquidHeader = reader.ReadStruct(); + + _liquidType = liquidHeader.liquidType; + _liquidOffX = liquidHeader.offsetX; + _liquidOffY = liquidHeader.offsetY; + _liquidWidth = liquidHeader.width; + _liquidHeight = liquidHeader.height; + _liquidLevel = liquidHeader.liquidLevel; + + if (!liquidHeader.flags.HasAnyFlag(LiquidHeaderFlags.LiquidNoType)) + { + _liquidEntry = new ushort[16 * 16]; + for (var i = 0; i < _liquidEntry.Length; ++i) + _liquidEntry[i] = reader.ReadUInt16(); + + _liquidFlags = reader.ReadBytes(16 * 16); + } + + if (!liquidHeader.flags.HasAnyFlag(LiquidHeaderFlags.LiquidNoHeight)) + { + _liquidMap = new float[_liquidWidth * _liquidHeight]; + for (var i = 0; i < _liquidMap.Length; ++i) + _liquidMap[i] = reader.ReadSingle(); + } + } + + public ushort getArea(float x, float y) + { + if (_areaMap == null) + return _gridArea; + + x = 16 * (32 - x / MapConst.SizeofGrids); + y = 16 * (32 - y / MapConst.SizeofGrids); + int lx = (int)x & 15; + int ly = (int)y & 15; + return _areaMap[lx * 16 + ly]; + } + + float getHeightFromFlat(float x, float y) + { + return _gridHeight; + } + + float getHeightFromFloat(float x, float y) + { + if (m_uint16_V8 == null || m_uint16_V9 == null) + return _gridHeight; + + x = MapConst.MapResolution * (32 - x / MapConst.SizeofGrids); + y = MapConst.MapResolution * (32 - y / MapConst.SizeofGrids); + + int x_int = (int)x; + int y_int = (int)y; + x -= x_int; + y -= y_int; + x_int &= (MapConst.MapResolution - 1); + y_int &= (MapConst.MapResolution - 1); + + float a, b, c; + if (x + y < 1) + { + if (x > y) + { + // 1 triangle (h1, h2, h5 points) + float h1 = m_V9[(x_int) * 129 + y_int]; + float h2 = m_V9[(x_int + 1) * 129 + y_int]; + float h5 = 2 * m_V8[x_int * 128 + y_int]; + a = h2 - h1; + b = h5 - h1 - h2; + c = h1; + } + else + { + // 2 triangle (h1, h3, h5 points) + float h1 = m_V9[x_int * 129 + y_int]; + float h3 = m_V9[x_int * 129 + y_int + 1]; + float h5 = 2 * m_V8[x_int * 128 + y_int]; + a = h5 - h1 - h3; + b = h3 - h1; + c = h1; + } + } + else + { + if (x > y) + { + // 3 triangle (h2, h4, h5 points) + float h2 = m_V9[(x_int + 1) * 129 + y_int]; + float h4 = m_V9[(x_int + 1) * 129 + y_int + 1]; + float h5 = 2 * m_V8[x_int * 128 + y_int]; + a = h2 + h4 - h5; + b = h4 - h2; + c = h5 - h4; + } + else + { + // 4 triangle (h3, h4, h5 points) + float h3 = m_V9[(x_int) * 129 + y_int + 1]; + float h4 = m_V9[(x_int + 1) * 129 + y_int + 1]; + float h5 = 2 * m_V8[x_int * 128 + y_int]; + a = h4 - h3; + b = h3 + h4 - h5; + c = h5 - h4; + } + } + // Calculate height + return a * x + b * y + c; + } + + float getHeightFromUint8(float x, float y) + { + if (m_ubyte_V8 == null || m_ubyte_V9 == null) + return _gridHeight; + + x = MapConst.MapResolution * (32 - x / MapConst.SizeofGrids); + y = MapConst.MapResolution * (32 - y / MapConst.SizeofGrids); + + int x_int = (int)x; + int y_int = (int)y; + x -= x_int; + y -= y_int; + x_int &= (MapConst.MapResolution - 1); + y_int &= (MapConst.MapResolution - 1); + int a, b, c; + + unsafe + { + fixed (byte* V9 = m_ubyte_V9) + { + byte* V9_h1_ptr = &V9[x_int * 128 + x_int + y_int]; + if (x + y < 1) + { + if (x > y) + { + // 1 triangle (h1, h2, h5 points) + int h1 = V9_h1_ptr[0]; + int h2 = V9_h1_ptr[129]; + int h5 = 2 * m_ubyte_V8[x_int * 128 + y_int]; + a = h2 - h1; + b = h5 - h1 - h2; + c = h1; + } + else + { + // 2 triangle (h1, h3, h5 points) + int h1 = V9_h1_ptr[0]; + int h3 = V9_h1_ptr[1]; + int h5 = 2 * m_ubyte_V8[x_int * 128 + y_int]; + a = h5 - h1 - h3; + b = h3 - h1; + c = h1; + } + } + else + { + if (x > y) + { + // 3 triangle (h2, h4, h5 points) + int h2 = V9_h1_ptr[129]; + int h4 = V9_h1_ptr[130]; + int h5 = 2 * m_ubyte_V8[x_int * 128 + y_int]; + a = h2 + h4 - h5; + b = h4 - h2; + c = h5 - h4; + } + else + { + // 4 triangle (h3, h4, h5 points) + int h3 = V9_h1_ptr[1]; + int h4 = V9_h1_ptr[130]; + int h5 = 2 * m_ubyte_V8[x_int * 128 + y_int]; + a = h4 - h3; + b = h3 + h4 - h5; + c = h5 - h4; + } + } + // Calculate height + return ((a * x) + (b * y) + c) * _gridIntHeightMultiplier + _gridHeight; + } + } + } + + float getHeightFromUint16(float x, float y) + { + if (m_uint16_V8 == null || m_uint16_V9 == null) + return _gridHeight; + + x = MapConst.MapResolution * (32 - x / MapConst.SizeofGrids); + y = MapConst.MapResolution * (32 - y / MapConst.SizeofGrids); + + int x_int = (int)x; + int y_int = (int)y; + x -= x_int; + y -= y_int; + x_int &= (MapConst.MapResolution - 1); + y_int &= (MapConst.MapResolution - 1); + int a, b, c; + unsafe + { + fixed (ushort* V9 = m_uint16_V9) + { + ushort* V9_h1_ptr = &V9[x_int * 128 + x_int + y_int]; + if (x + y < 1) + { + if (x > y) + { + // 1 triangle (h1, h2, h5 points) + int h1 = V9_h1_ptr[0]; + int h2 = V9_h1_ptr[129]; + int h5 = 2 * m_uint16_V8[x_int * 128 + y_int]; + a = h2 - h1; + b = h5 - h1 - h2; + c = h1; + } + else + { + // 2 triangle (h1, h3, h5 points) + int h1 = V9_h1_ptr[0]; + int h3 = V9_h1_ptr[1]; + int h5 = 2 * m_uint16_V8[x_int * 128 + y_int]; + a = h5 - h1 - h3; + b = h3 - h1; + c = h1; + } + } + else + { + if (x > y) + { + // 3 triangle (h2, h4, h5 points) + int h2 = V9_h1_ptr[129]; + int h4 = V9_h1_ptr[130]; + int h5 = 2 * m_uint16_V8[x_int * 128 + y_int]; + a = h2 + h4 - h5; + b = h4 - h2; + c = h5 - h4; + } + else + { + // 4 triangle (h3, h4, h5 points) + int h3 = V9_h1_ptr[1]; + int h4 = V9_h1_ptr[130]; + int h5 = 2 * m_uint16_V8[x_int * 128 + y_int]; + a = h4 - h3; + b = h3 + h4 - h5; + c = h5 - h4; + } + } + // Calculate height + return ((a * x) + (b * y) + c) * _gridIntHeightMultiplier + _gridHeight; + } + } + } + + public float getMinHeight(float x, float y) + { + if (_minHeight == null) + return -500.0f; + + uint[] indices = + { + 3, 0, 4, + 0, 1, 4, + 1, 2, 4, + 2, 5, 4, + 5, 8, 4, + 8, 7, 4, + 7, 6, 4, + 6, 3, 4 + }; + + float[] boundGridCoords = + { + 0.0f, 0.0f, + 0.0f, -266.66666f, + 0.0f, -533.33331f, + -266.66666f, 0.0f, + -266.66666f, -266.66666f, + -266.66666f, -533.33331f, + -533.33331f, 0.0f, + -533.33331f, -266.66666f, + -533.33331f, -533.33331f + }; + + Cell cell = new Cell(x, y); + float gx = x - (cell.GetGridX() - MapConst.CenterGridId + 1) *MapConst.SizeofGrids; + float gy = y - (cell.GetGridY() - MapConst.CenterGridId + 1) *MapConst.SizeofGrids; + + uint quarterIndex = 0; + if (cell.GetCellY() < MapConst.MaxCells / 2) + { + if (cell.GetCellX() < MapConst.MaxCells / 2) + { + quarterIndex = 4 + (gy > gx ? 1u : 0u); + } + else + quarterIndex = (2 + ((-MapConst.SizeofGrids - gx) > gy ? 1u : 0)); + } + else if (cell.GetCellX() < MapConst.MaxCells / 2) + { + quarterIndex = 6 + ((-MapConst.SizeofGrids - gx) <= gy ? 1u : 0); + } + else + quarterIndex = gx > gy ? 1u : 0; + + quarterIndex *= 3; + + return new Plane( + new Vector3(boundGridCoords[indices[quarterIndex + 0] * 2 + 0], boundGridCoords[indices[quarterIndex + 0] * 2 + 1], _minHeight[indices[quarterIndex + 0]]), + new Vector3(boundGridCoords[indices[quarterIndex + 1] * 2 + 0], boundGridCoords[indices[quarterIndex + 1] * 2 + 1], _minHeight[indices[quarterIndex + 1]]), + new Vector3(boundGridCoords[indices[quarterIndex + 2] * 2 + 0], boundGridCoords[indices[quarterIndex + 2] * 2 + 1], _minHeight[indices[quarterIndex + 2]]) + ).GetDistanceToPlane(new Vector3(gx, gy, 0.0f)); + } + + public float getLiquidLevel(float x, float y) + { + if (_liquidMap == null) + return _liquidLevel; + + x = MapConst.MapResolution * (32 - x / MapConst.SizeofGrids); + y = MapConst.MapResolution * (32 - y / MapConst.SizeofGrids); + + int cx_int = ((int)x & (MapConst.MapResolution - 1)) - _liquidOffY; + int cy_int = ((int)y & (MapConst.MapResolution - 1)) - _liquidOffX; + + if (cx_int < 0 || cx_int >= _liquidHeight) + return MapConst.InvalidHeight; + if (cy_int < 0 || cy_int >= _liquidWidth) + return MapConst.InvalidHeight; + + return _liquidMap[cx_int * _liquidWidth + cy_int]; + } + + // Why does this return LIQUID data? + public byte getTerrainType(float x, float y) + { + if (_liquidFlags == null) + return 0; + + x = 16 * (32 - x / MapConst.SizeofGrids); + y = 16 * (32 - y / MapConst.SizeofGrids); + int lx = (int)x & 15; + int ly = (int)y & 15; + return _liquidFlags[lx * 16 + ly]; + } + + // Get water state on map + public ZLiquidStatus getLiquidStatus(float x, float y, float z, byte ReqLiquidType, LiquidData data) + { + // Check water type (if no water return) + if (_liquidType == 0 && _liquidFlags == null) + return ZLiquidStatus.NoWater; + + // Get cell + float cx = MapConst.MapResolution * (32 - x / MapConst.SizeofGrids); + float cy = MapConst.MapResolution * (32 - y / MapConst.SizeofGrids); + + int x_int = (int)cx & (MapConst.MapResolution - 1); + int y_int = (int)cy & (MapConst.MapResolution - 1); + + // Check water type in cell + int idx = (x_int >> 3) * 16 + (y_int >> 3); + byte type = _liquidFlags != null ? _liquidFlags[idx] : (byte)_liquidType; + uint entry = 0; + if (_liquidEntry != null) + { + var liquidEntry = CliDB.LiquidTypeStorage.LookupByKey(_liquidEntry[idx]); + if (liquidEntry != null) + { + entry = liquidEntry.Id; + type &= MapConst.MapLiquidTypeDarkWater; + uint liqTypeIdx = liquidEntry.LiquidType; + if (entry < 21) + { + var area = CliDB.AreaTableStorage.LookupByKey(getArea(x, y)); + if (area != null) + { + uint overrideLiquid = area.LiquidTypeID[liquidEntry.LiquidType]; + if (overrideLiquid == 0 && area.ParentAreaID == 0) + { + area = CliDB.AreaTableStorage.LookupByKey(area.ParentAreaID); + if (area != null) + overrideLiquid = area.LiquidTypeID[liquidEntry.LiquidType]; + } + var liq = CliDB.LiquidTypeStorage.LookupByKey(overrideLiquid); + if (liq != null) + { + entry = overrideLiquid; + liqTypeIdx = liq.LiquidType; + } + } + } + type |= (byte)(1 << (int)liqTypeIdx); + } + } + + if (type == 0) + return ZLiquidStatus.NoWater; + + // Check req liquid type mask + if (ReqLiquidType != 0 && !Convert.ToBoolean(ReqLiquidType & type)) + return ZLiquidStatus.NoWater; + + // Check water level: + // Check water height map + int lx_int = x_int - _liquidOffY; + int ly_int = y_int - _liquidOffX; + if (lx_int < 0 || lx_int >= _liquidHeight) + return ZLiquidStatus.NoWater; + if (ly_int < 0 || ly_int >= _liquidWidth) + return ZLiquidStatus.NoWater; + + // Get water level + float liquid_level = _liquidMap != null ? _liquidMap[lx_int * _liquidWidth + ly_int] : _liquidLevel; + // Get ground level (sub 0.2 for fix some errors) + float ground_level = getHeight(x, y); + + // Check water level and ground level + if (liquid_level < ground_level || z < ground_level - 2) + return ZLiquidStatus.NoWater; + + // All ok in water . store data + if (data != null) + { + data.entry = entry; + data.type_flags = type; + data.level = liquid_level; + data.depth_level = ground_level; + } + + // For speed check as int values + float delta = liquid_level - z; + + if (delta > 2.0f) // Under water + return ZLiquidStatus.UnderWater; + if (delta > 0.0f) // In water + return ZLiquidStatus.InWater; + if (delta > -0.1f) // Walk on water + return ZLiquidStatus.WaterWalk; + // Above water + return ZLiquidStatus.AboveWater; + } + + public float getHeight(float x, float y) { return _gridGetHeight(x, y); } + + #region Fields + delegate float GetHeight(float x, float y); + + GetHeight _gridGetHeight; + uint _flags; + + public float[] m_V9; + public ushort[] m_uint16_V9; + public byte[] m_ubyte_V9; + + public float[] m_V8; + public ushort[] m_uint16_V8; + public byte[] m_ubyte_V8; + short[] _maxHeight; + short[] _minHeight; + float _gridHeight; + float _gridIntHeightMultiplier; + + //Area data + public ushort[] _areaMap; + + //Liquid Map + float _liquidLevel; + ushort[] _liquidEntry; + byte[] _liquidFlags; + float[] _liquidMap; + ushort _gridArea; + ushort _liquidType; + byte _liquidOffX; + byte _liquidOffY; + byte _liquidWidth; + byte _liquidHeight; + #endregion + } + + + [StructLayout(LayoutKind.Sequential)] + public struct mapFileHeader + { + [MarshalAs(UnmanagedType.ByValArray, SizeConst = 4)] + public char[] mapMagic; + [MarshalAs(UnmanagedType.ByValArray, SizeConst = 4)] + public char[] versionMagic; + public uint buildMagic; + public uint areaMapOffset; + public uint areaMapSize; + public uint heightMapOffset; + public uint heightMapSize; + public uint liquidMapOffset; + public uint liquidMapSize; + public uint holesOffset; + public uint holesSize; + } + + [StructLayout(LayoutKind.Sequential)] + public struct map_AreaHeader + { + public uint fourcc; + public AreaHeaderFlags flags; + public ushort gridArea; + } + + [StructLayout(LayoutKind.Sequential)] + public struct map_HeightHeader + { + public uint fourcc; + public HeightHeaderFlags flags; + public float gridHeight; + public float gridMaxHeight; + } + + [StructLayout(LayoutKind.Sequential)] + public struct map_LiquidHeader + { + public uint fourcc; + public LiquidHeaderFlags flags; + public ushort liquidType; + public byte offsetX; + public byte offsetY; + public byte width; + public byte height; + public float liquidLevel; + } + + [StructLayout(LayoutKind.Sequential)] + public class LiquidData + { + public uint type_flags; + public uint entry; + public float level; + public float depth_level; + } + + [Flags] + public enum AreaHeaderFlags : ushort + { + NoArea = 0x0001 + } + + [Flags] + public enum HeightHeaderFlags + { + NoHeight = 0x0001, + HeightAsInt16 = 0x0002, + HeightAsInt8 = 0x0004, + HeightHasFlightBounds = 0x0008 + } + + [Flags] + public enum LiquidHeaderFlags : ushort + { + LiquidNoType = 0x0001, + LiquidNoHeight = 0x0002 + } +} diff --git a/Game/Maps/GridNotifiers.cs b/Game/Maps/GridNotifiers.cs new file mode 100644 index 000000000..856d1230d --- /dev/null +++ b/Game/Maps/GridNotifiers.cs @@ -0,0 +1,2258 @@ +/* + * Copyright (C) 2012-2017 CypherCore + * + * 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, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +using Framework.Constants; +using Game.Chat; +using Game.Entities; +using Game.Network; +using Game.Network.Packets; +using Game.Spells; +using System; +using System.Collections.Generic; +using System.Linq; + +namespace Game.Maps +{ + public abstract class Notifier + { + public virtual void Visit(ICollection objs) { } + public virtual void Visit(ICollection objs) { } + public virtual void Visit(ICollection objs) { } + public virtual void Visit(ICollection objs) { } + public virtual void Visit(ICollection objs) { } + public virtual void Visit(ICollection objs) { } + public virtual void Visit(ICollection objs) { } + public virtual void Visit(ICollection objs) { } + + public void CreatureUnitRelocationWorker(Creature c, Unit u) + { + if (!u.IsAlive() || !c.IsAlive() || c == u || u.IsInFlight()) + return; + + if (!c.HasUnitState(UnitState.Sightless)) + { + if (c.IsAIEnabled && c.CanSeeOrDetect(u, false, true)) + c.GetAI().MoveInLineOfSight_Safe(u); + else + { + if (u.IsTypeId(TypeId.Player) && u.HasStealthAura() && c.IsAIEnabled && c.CanSeeOrDetect(u, false, true, true)) + c.GetAI().TriggerAlert(u); + } + } + } + } + + public class Visitor + { + public Visitor(Notifier notifier, GridMapTypeMask mask) + { + _notifier = notifier; + _mask = mask; + } + + public void Visit(ICollection collection) { _notifier.Visit(collection); } + public void Visit(ICollection creatures) { _notifier.Visit(creatures); } + public void Visit(ICollection areatriggers) { _notifier.Visit(areatriggers); } + public void Visit(ICollection conversations) { _notifier.Visit(conversations); } + public void Visit(ICollection gameobjects) { _notifier.Visit(gameobjects); } + public void Visit(ICollection dynamicobjects) { _notifier.Visit(dynamicobjects); } + public void Visit(ICollection corpses) { _notifier.Visit(corpses); } + public void Visit(ICollection players) { _notifier.Visit(players); } + + public void Visit(Dictionary dict) { Visit(dict.Values); } + + Notifier _notifier; + internal GridMapTypeMask _mask; + } + + public class VisibleNotifier : Notifier + { + public VisibleNotifier(Player pl) + { + i_player = pl; + i_data = new UpdateData(pl.GetMapId()); + vis_guids = new List(pl.m_clientGUIDs); + i_visibleNow = new List(); + } + + public override void Visit(ICollection objs) + { + foreach (var obj in objs) + { + vis_guids.Remove(obj.GetGUID()); + i_player.UpdateVisibilityOf(obj, i_data, i_visibleNow); + } + } + + public void SendToSelf() + { + // at this moment i_clientGUIDs have guids that not iterate at grid level checks + // but exist one case when this possible and object not out of range: transports + Transport transport = i_player.GetTransport(); + if (transport) + { + foreach (var obj in transport.GetPassengers()) + { + if (vis_guids.Contains(obj.GetGUID())) + { + vis_guids.Remove(obj.GetGUID()); + + switch (obj.GetTypeId()) + { + case TypeId.GameObject: + i_player.UpdateVisibilityOf(obj.ToGameObject(), i_data, i_visibleNow); + break; + case TypeId.Player: + i_player.UpdateVisibilityOf(obj.ToPlayer(), i_data, i_visibleNow); + if (!obj.isNeedNotify(NotifyFlags.VisibilityChanged)) + obj.ToPlayer().UpdateVisibilityOf(i_player); + break; + case TypeId.Unit: + i_player.UpdateVisibilityOf(obj.ToCreature(), i_data, i_visibleNow); + break; + case TypeId.DynamicObject: + i_player.UpdateVisibilityOf(obj.ToDynamicObject(), i_data, i_visibleNow); + break; + default: + break; + } + } + } + } + + foreach (var guid in vis_guids) + { + i_player.m_clientGUIDs.Remove(guid); + i_data.AddOutOfRangeGUID(guid); + + if (guid.IsPlayer()) + { + Player pl = Global.ObjAccessor.FindPlayer(guid); + if (pl != null && pl.IsInWorld && !pl.isNeedNotify(NotifyFlags.VisibilityChanged)) + pl.UpdateVisibilityOf(i_player); + } + } + + if (!i_data.HasData()) + return; + + UpdateObject packet; + i_data.BuildPacket(out packet); + i_player.SendPacket(packet); + + foreach (var obj in i_visibleNow) + i_player.SendInitialVisiblePackets(obj); + } + + internal Player i_player; + internal UpdateData i_data; + internal List vis_guids; + internal List i_visibleNow; + } + + public class VisibleChangesNotifier : Notifier + { + public VisibleChangesNotifier(WorldObject obj) + { + i_object = obj; + } + + public override void Visit(ICollection objs) + { + foreach (var player in objs) + { + if (player.GetGUID() == i_object.GetGUID()) + return; + + player.UpdateVisibilityOf(i_object); + + if (player.HasSharedVision()) + { + foreach (var visionPlayer in player.GetSharedVisionList()) + { + if (visionPlayer.seerView == player) + visionPlayer.UpdateVisibilityOf(i_object); + } + } + } + } + + public override void Visit(ICollection objs) + { + foreach (var creature in objs) + { + if (creature.HasSharedVision()) + { + foreach (var visionPlayer in creature.GetSharedVisionList()) + if (visionPlayer.seerView == creature) + visionPlayer.UpdateVisibilityOf(i_object); + } + } + } + + public override void Visit(ICollection objs) + { + foreach (var dynamicObj in objs) + { + Unit caster = dynamicObj.GetCaster(); + if (caster) + { + Player pl = caster.ToPlayer(); + if (pl && pl.seerView == dynamicObj) + pl.UpdateVisibilityOf(i_object); + } + } + } + + WorldObject i_object; + } + + public class PlayerRelocationNotifier : VisibleNotifier + { + public PlayerRelocationNotifier(Player player) : base(player) { } + + public override void Visit(ICollection objs) + { + base.Visit(objs); + + foreach (var player in objs) + { + vis_guids.Remove(player.GetGUID()); + + i_player.UpdateVisibilityOf(player, i_data, i_visibleNow); + + if (player.seerView.isNeedNotify(NotifyFlags.VisibilityChanged)) + continue; + + player.UpdateVisibilityOf(i_player); + } + } + + public override void Visit(ICollection objs) + { + base.Visit(objs); + + bool relocated_for_ai = (i_player == i_player.seerView); + + foreach (var creature in objs) + { + vis_guids.Remove(creature.GetGUID()); + + i_player.UpdateVisibilityOf(creature, i_data, i_visibleNow); + + if (relocated_for_ai && !creature.isNeedNotify(NotifyFlags.VisibilityChanged)) + CreatureUnitRelocationWorker(creature, i_player); + } + } + } + + public class CreatureRelocationNotifier : Notifier + { + public CreatureRelocationNotifier(Creature c) + { + i_creature = c; + } + + public override void Visit(ICollection objs) + { + foreach (var player in objs) + { + if (!player.seerView.isNeedNotify(NotifyFlags.VisibilityChanged)) + player.UpdateVisibilityOf(i_creature); + + CreatureUnitRelocationWorker(i_creature, player); + } + } + + public override void Visit(ICollection objs) + { + if (!i_creature.IsAlive()) + return; + + foreach (var creature in objs) + { + CreatureUnitRelocationWorker(i_creature, creature); + + if (!creature.isNeedNotify(NotifyFlags.VisibilityChanged)) + CreatureUnitRelocationWorker(creature, i_creature); + } + } + + Creature i_creature; + } + + public class DelayedUnitRelocation : Notifier + { + public DelayedUnitRelocation(Cell c, CellCoord pair, Map map, float radius) + { + i_map = map; + cell = c; + p = pair; + i_radius = radius; + } + + public override void Visit(ICollection objs) + { + foreach (var player in objs) + { + WorldObject viewPoint = player.seerView; + + if (!viewPoint.isNeedNotify(NotifyFlags.VisibilityChanged)) + continue; + + if (player != viewPoint && !viewPoint.IsPositionValid()) + continue; + + var relocate = new PlayerRelocationNotifier(player); + Cell.VisitAllObjects(viewPoint, relocate, i_radius, false); + + relocate.SendToSelf(); + } + } + + public override void Visit(ICollection objs) + { + foreach (var creature in objs) + { + if (!creature.isNeedNotify(NotifyFlags.VisibilityChanged)) + continue; + + CreatureRelocationNotifier relocate = new CreatureRelocationNotifier(creature); + + var c2world_relocation = new Visitor(relocate, GridMapTypeMask.AllWorld); + var c2grid_relocation = new Visitor(relocate, GridMapTypeMask.AllGrid); + + cell.Visit(p, c2world_relocation, i_map, creature, i_radius); + cell.Visit(p, c2grid_relocation, i_map, creature, i_radius); + } + } + + Map i_map; + Cell cell; + CellCoord p; + float i_radius; + } + + public class AIRelocationNotifier : Notifier + { + public AIRelocationNotifier(Unit unit) + { + i_unit = unit; + isCreature = unit.IsTypeId(TypeId.Unit); + } + + public override void Visit(ICollection objs) + { + foreach (var creature in objs) + { + CreatureUnitRelocationWorker(creature, i_unit); + if (isCreature) + CreatureUnitRelocationWorker(i_unit.ToCreature(), creature); + } + } + + Unit i_unit; + bool isCreature; + } + + public class MessageDistDeliverer : Notifier + { + public MessageDistDeliverer(WorldObject src, ServerPacket msg, float dist, bool own_team_only = false, Player skipped = null) + { + i_source = src; + i_message = msg; + i_distSq = dist * dist; + team = (uint)((own_team_only && src.IsTypeId(TypeId.Player)) ? ((Player)src).GetTeam() : 0); + skipped_receiver = skipped; + } + + public override void Visit(ICollection objs) + { + foreach (var player in objs) + { + if (!player.IsInPhase(i_source)) + continue; + + if (player.GetExactDist2dSq(i_source.GetPosition()) > i_distSq) + continue; + + // Send packet to all who are sharing the player's vision + if (player.HasSharedVision()) + { + foreach (var visionPlayer in player.GetSharedVisionList()) + if (visionPlayer.seerView == player) + SendPacket(visionPlayer); + } + + if (player.seerView == player || player.GetVehicle() != null) + SendPacket(player); + } + } + + public override void Visit(ICollection objs) + { + foreach (var creature in objs) + { + if (creature.GetCreatureTemplate().Entry == 1) + { + + } + if (!creature.IsInPhase(i_source)) + continue; + + if (creature.GetExactDist2dSq(i_source.GetPosition()) > i_distSq) + continue; + + // Send packet to all who are sharing the creature's vision + if (creature.HasSharedVision()) + { + foreach (var visionPlayer in creature.GetSharedVisionList()) + if (visionPlayer.seerView == creature) + SendPacket(visionPlayer); + } + } + } + + public override void Visit(ICollection objs) + { + foreach (var obj in objs) + { + if (!obj.IsInPhase(i_source)) + continue; + + if (obj.GetExactDist2dSq(i_source.GetPosition()) > i_distSq) + continue; + + // Send packet back to the caster if the caster has vision of dynamic object + Unit caster = obj.GetCaster(); + if (caster) + { + Player player = caster.ToPlayer(); + if (player && player.seerView == obj) + SendPacket(player); + } + } + } + + void SendPacket(Player player) + { + // never send packet to self + if (i_source == player || (team != 0 && (uint)player.GetTeam() != team) || skipped_receiver == player) + return; + + if (!player.HaveAtClient(i_source)) + return; + + player.SendPacket(i_message); + } + + WorldObject i_source; + ServerPacket i_message; + float i_distSq; + uint team; + Player skipped_receiver; + } + + public class UpdaterNotifier : Notifier + { + public UpdaterNotifier(uint diff) + { + i_timeDiff = diff; + } + + public override void Visit(ICollection objs) + { + foreach (var obj in objs.ToList()) + { + if (obj.IsTypeId(TypeId.Player) || obj.IsTypeId(TypeId.Corpse)) + continue; + + if (obj.IsInWorld) + obj.Update(i_timeDiff); + } + } + + uint i_timeDiff; + } + + public class PlayerWorker : Notifier + { + PlayerWorker(WorldObject searcher, Action _action) + { + _searcher = searcher; + action = _action; + } + + public override void Visit(ICollection objs) + { + foreach (var player in objs) + if (player.IsInPhase(_searcher)) + action.Invoke(player); + } + + WorldObject _searcher; + Action action; + } + + public class CreatureWorker : Notifier + { + public CreatureWorker(WorldObject searcher, IDoWork _Do) + { + _searcher = searcher; + Do = _Do; + } + + public override void Visit(ICollection objs) + { + foreach (var creature in objs) + { + if (creature.IsInPhase(_searcher)) + Do.Invoke(creature); + } + } + + WorldObject _searcher; + IDoWork Do; + } + + public class GameObjectWorker : Notifier + { + public GameObjectWorker(WorldObject searcher, IDoWork _Do) + { + Do = _Do; + _searcher = searcher; + } + + public override void Visit(ICollection objs) + { + foreach (var obj in objs) + if (obj.IsInPhase(_searcher)) + Do.Invoke(obj); + } + + WorldObject _searcher; + IDoWork Do; + } + + public class WorldObjectWorker : Notifier + { + public WorldObjectWorker(WorldObject searcher, IDoWork _do, GridMapTypeMask mapTypeMask = GridMapTypeMask.All) + { + i_mapTypeMask = mapTypeMask; + _searcher = searcher; + i_do = _do; + } + + public override void Visit(ICollection objs) + { + foreach (var obj in objs) + { + switch (obj.GetTypeId()) + { + case TypeId.GameObject: + if (!i_mapTypeMask.HasAnyFlag(GridMapTypeMask.GameObject)) + return; + break; + case TypeId.Player: + if (!i_mapTypeMask.HasAnyFlag(GridMapTypeMask.Player)) + return; + break; + case TypeId.Unit: + if (!i_mapTypeMask.HasAnyFlag(GridMapTypeMask.Creature)) + return; + break; + case TypeId.Corpse: + if (!i_mapTypeMask.HasAnyFlag(GridMapTypeMask.Corpse)) + return; + break; + case TypeId.DynamicObject: + if (!i_mapTypeMask.HasAnyFlag(GridMapTypeMask.DynamicObject)) + return; + break; + case TypeId.AreaTrigger: + if (!i_mapTypeMask.HasAnyFlag(GridMapTypeMask.AreaTrigger)) + return; + break; + case TypeId.Conversation: + if (!i_mapTypeMask.HasAnyFlag(GridMapTypeMask.Conversation)) + return; + break; + } + + if (obj.IsInPhase(_searcher)) + i_do.Invoke(obj); + } + } + + GridMapTypeMask i_mapTypeMask; + WorldObject _searcher; + IDoWork i_do; + } + + public class ResetNotifier : Notifier + { + public override void Visit(ICollection objs) + { + foreach (var player in objs) + player.ResetAllNotifies(); + } + public override void Visit(ICollection objs) + { + foreach (var creature in objs) + creature.ResetAllNotifies(); + } + } + + public class WorldObjectChangeAccumulator : Notifier + { + public WorldObjectChangeAccumulator(WorldObject obj, Dictionary d) + { + updateData = d; + worldObject = obj; + } + + public override void Visit(ICollection objs) + { + foreach (var player in objs) + { + BuildPacket(player); + + if (!player.GetSharedVisionList().Empty()) + { + foreach (var visionPlayer in player.GetSharedVisionList()) + BuildPacket(visionPlayer); + } + } + } + + public override void Visit(ICollection objs) + { + foreach (var creature in objs) + { + if (!creature.GetSharedVisionList().Empty()) + { + foreach (var visionPlayer in creature.GetSharedVisionList()) + BuildPacket(visionPlayer); + } + } + } + public override void Visit(ICollection objs) + { + foreach (var obj in objs) + { + ObjectGuid guid = obj.GetCasterGUID(); + + if (guid.IsPlayer()) + { + //Caster may be NULL if DynObj is in removelist + Player caster = Global.ObjAccessor.FindPlayer(guid); + if (caster != null) + if (caster.GetGuidValue(PlayerFields.Farsight) == obj.GetGUID()) + BuildPacket(caster); + } + } + } + + void BuildPacket(Player player) + { + // Only send update once to a player + if (!plr_list.Contains(player.GetGUID()) && player.HaveAtClient(worldObject)) + { + worldObject.BuildFieldsUpdate(player, updateData); + plr_list.Add(player.GetGUID()); + } + } + + Dictionary updateData; + WorldObject worldObject; + List plr_list = new List(); + } + + public class PlayerDistWorker : Notifier + { + public PlayerDistWorker(WorldObject searcher, float _dist, IDoWork _Do) + { + i_searcher = searcher; + i_dist = _dist; + Do = _Do; + } + + public override void Visit(ICollection objs) + { + foreach (var player in objs) + { + if (player.IsInPhase(i_searcher) && player.IsWithinDist(i_searcher, i_dist)) + Do.Invoke(player); + } + } + + WorldObject i_searcher; + float i_dist; + IDoWork Do; + } + + public class CallOfHelpCreatureInRangeDo : IDoWork + { + public CallOfHelpCreatureInRangeDo(Unit funit, Unit enemy, float range) + { + i_funit = funit; + i_enemy = enemy; + i_range = range; + } + + public void Invoke(Creature u) + { + if (u == i_funit) + return; + + if (!u.CanAssistTo(i_funit, i_enemy, false)) + return; + + // too far + if (!u.IsWithinDistInMap(i_funit, i_range)) + return; + + // only if see assisted creature's enemy + if (!u.IsWithinLOSInMap(i_enemy)) + return; + + if (u.GetAI() != null) + u.GetAI().AttackStart(i_enemy); + } + + Unit i_funit; + Unit i_enemy; + float i_range; + } + + public class LocalizedPacketDo : IDoWork + { + public LocalizedPacketDo(MessageBuilder builder) + { + Builder = builder; + } + + public void Invoke(Player player) + { + LocaleConstant loc_idx = player.GetSession().GetSessionDbLocaleIndex(); + int cache_idx = (int)loc_idx + 1; + ServerPacket data; + + // create if not cached yet + if (i_data_cache.Length < cache_idx + 1 || i_data_cache[cache_idx] == null) + { + if (i_data_cache.Length < cache_idx + 1) + Array.Resize(ref i_data_cache, cache_idx + 1); + + data = Builder.Invoke(loc_idx); + + i_data_cache[cache_idx] = data; + } + else + data = i_data_cache[cache_idx]; + + player.SendPacket(data); + } + + MessageBuilder Builder; + ServerPacket[] i_data_cache = new ServerPacket[(int)LocaleConstant.Max]; // 0 = default, i => i-1 locale index + } + + public class LocalizedPacketListDo : IDoWork + { + public LocalizedPacketListDo(MessageBuilder builder) + { + i_builder = builder; + } + + public void Invoke(Player p) + { + LocaleConstant loc_idx = p.GetSession().GetSessionDbLocaleIndex(); + int cache_idx = (int)loc_idx + 1; + List data_list = new List(); + + // create if not cached yet + if (i_data_cache.Count < cache_idx + 1 || i_data_cache[cache_idx].Empty()) + { + data_list = i_data_cache[cache_idx]; + + i_builder.Invoke(data_list, loc_idx); + } + else + data_list = i_data_cache[cache_idx]; + + foreach (var packet in data_list) + p.SendPacket(packet); + } + + MessageBuilder i_builder; + MultiMap i_data_cache = new MultiMap(); + // 0 = default, i => i-1 locale index + } + + public class RespawnDo : IDoWork + { + public void Invoke(WorldObject obj) + { + switch (obj.GetTypeId()) + { + case TypeId.Unit: + obj.ToCreature().Respawn(); + break; + case TypeId.GameObject: + obj.ToGameObject().Respawn(); + break; + } + } + } + + //Searchers + public class WorldObjectSearcher : Notifier + { + public WorldObjectSearcher(WorldObject searcher, ICheck check, GridMapTypeMask mapTypeMask = GridMapTypeMask.All) + { + i_mapTypeMask = mapTypeMask; + _searcher = searcher; + i_check = check; + } + + public override void Visit(ICollection objs) + { + // already found + if (i_object) + return; + + foreach (var obj in objs) + { + switch (obj.GetTypeId()) + { + case TypeId.Player: + if (!i_mapTypeMask.HasAnyFlag(GridMapTypeMask.Player)) + continue; + break; + case TypeId.GameObject: + if (!i_mapTypeMask.HasAnyFlag(GridMapTypeMask.GameObject)) + continue; + break; + case TypeId.Corpse: + if (!i_mapTypeMask.HasAnyFlag(GridMapTypeMask.Corpse)) + continue; + break; + case TypeId.Unit: + if (!i_mapTypeMask.HasAnyFlag(GridMapTypeMask.Creature)) + continue; + break; + case TypeId.DynamicObject: + if (!i_mapTypeMask.HasAnyFlag(GridMapTypeMask.DynamicObject)) + continue; + break; + case TypeId.AreaTrigger: + if (!i_mapTypeMask.HasAnyFlag(GridMapTypeMask.AreaTrigger)) + continue; + break; + case TypeId.Conversation: + if (!i_mapTypeMask.HasAnyFlag(GridMapTypeMask.Conversation)) + continue; + break; + } + + if (!obj.IsInPhase(_searcher)) + continue; + + if (i_check.Invoke(obj)) + { + i_object = obj; + return; + } + + } + } + + public WorldObject GetTarget() { return i_object; } + + GridMapTypeMask i_mapTypeMask; + WorldObject i_object; + WorldObject _searcher; + ICheck i_check; + } + public class WorldObjectLastSearcher : Notifier + { + public WorldObjectLastSearcher(WorldObject searcher, ICheck check, GridMapTypeMask mapTypeMask = GridMapTypeMask.All) + { + i_mapTypeMask = mapTypeMask; + _searcher = searcher; + i_check = check; + } + + public override void Visit(ICollection objs) + { + foreach (var obj in objs) + { + switch (obj.GetTypeId()) + { + case TypeId.Player: + if (!i_mapTypeMask.HasAnyFlag(GridMapTypeMask.Player)) + continue; + break; + case TypeId.GameObject: + if (!i_mapTypeMask.HasAnyFlag(GridMapTypeMask.GameObject)) + continue; + break; + case TypeId.Corpse: + if (!i_mapTypeMask.HasAnyFlag(GridMapTypeMask.Corpse)) + continue; + break; + case TypeId.Unit: + if (!i_mapTypeMask.HasAnyFlag(GridMapTypeMask.Creature)) + continue; + break; + case TypeId.DynamicObject: + if (!i_mapTypeMask.HasAnyFlag(GridMapTypeMask.DynamicObject)) + continue; + break; + case TypeId.AreaTrigger: + if (!i_mapTypeMask.HasAnyFlag(GridMapTypeMask.AreaTrigger)) + continue; + break; + case TypeId.Conversation: + if (!i_mapTypeMask.HasAnyFlag(GridMapTypeMask.Conversation)) + continue; + break; + } + + if (!obj.IsInPhase(_searcher)) + continue; + + if (i_check.Invoke(obj)) + i_object = obj; + } + } + + public WorldObject GetTarget() { return i_object; } + + GridMapTypeMask i_mapTypeMask; + WorldObject i_object; + WorldObject _searcher; + ICheck i_check; + } + public class WorldObjectListSearcher : Notifier + { + public WorldObjectListSearcher(WorldObject searcher, List objects, ICheck check, GridMapTypeMask mapTypeMask = GridMapTypeMask.All) + { + i_mapTypeMask = mapTypeMask; + _searcher = searcher; + i_objects = objects; + i_check = check; + } + + public override void Visit(ICollection objs) + { + foreach (var obj in objs) + { + switch (obj.GetTypeId()) + { + case TypeId.Player: + if (!i_mapTypeMask.HasAnyFlag(GridMapTypeMask.Player)) + continue; + break; + case TypeId.GameObject: + if (!i_mapTypeMask.HasAnyFlag(GridMapTypeMask.GameObject)) + continue; + break; + case TypeId.Corpse: + if (!i_mapTypeMask.HasAnyFlag(GridMapTypeMask.Corpse)) + continue; + break; + case TypeId.Unit: + if (!i_mapTypeMask.HasAnyFlag(GridMapTypeMask.Creature)) + continue; + break; + case TypeId.DynamicObject: + if (!i_mapTypeMask.HasAnyFlag(GridMapTypeMask.DynamicObject)) + continue; + break; + case TypeId.AreaTrigger: + if (!i_mapTypeMask.HasAnyFlag(GridMapTypeMask.AreaTrigger)) + continue; + break; + case TypeId.Conversation: + if (!i_mapTypeMask.HasAnyFlag(GridMapTypeMask.Conversation)) + continue; + break; + } + + if (i_check.Invoke(obj)) + i_objects.Add(obj); + } + } + + GridMapTypeMask i_mapTypeMask; + List i_objects; + WorldObject _searcher; + ICheck i_check; + } + + public class GameObjectSearcher : Notifier + { + public GameObjectSearcher(WorldObject searcher, ICheck check) + { + _searcher = searcher; + i_check = check; + } + + public override void Visit(ICollection objs) + { + // already found + if (i_object) + return; + + foreach (var go in objs) + { + if (!go.IsInPhase(_searcher)) + continue; + + if (i_check.Invoke(go)) + { + i_object = go; + return; + } + } + } + + public GameObject GetTarget() { return i_object; } + + WorldObject _searcher; + GameObject i_object; + ICheck i_check; + } + public class GameObjectLastSearcher : Notifier + { + public GameObjectLastSearcher(WorldObject searcher, ICheck check) + { + _searcher = searcher; + i_check = check; + } + + public override void Visit(ICollection objs) + { + foreach (var go in objs) + { + if (!go.IsInPhase(_searcher)) + continue; + + if (i_check.Invoke(go)) + i_object = go; + } + } + + public GameObject GetTarget() { return i_object; } + + WorldObject _searcher; + GameObject i_object; + ICheck i_check; + } + public class GameObjectListSearcher : Notifier + { + public GameObjectListSearcher(WorldObject searcher, List objects, ICheck check) + { + _searcher = searcher; + i_objects = objects; + i_check = check; + } + + public override void Visit(ICollection objs) + { + foreach (var obj in objs) + { + if (obj.IsInPhase(_searcher)) + if (i_check.Invoke(obj)) + i_objects.Add(obj); + } + } + + WorldObject _searcher; + List i_objects; + ICheck i_check; + } + + public class UnitSearcher : Notifier + { + public UnitSearcher(WorldObject searcher, ICheck check) + { + _searcher = searcher; + i_check = check; + } + + public override void Visit(ICollection objs) + { + foreach (var player in objs) + { + if (!player.IsInPhase(_searcher)) + continue; + + if (i_check.Invoke(player)) + { + i_object = player; + return; + } + } + } + + public override void Visit(ICollection objs) + { + foreach (var creature in objs) + { + if (!creature.IsInPhase(_searcher)) + continue; + + if (i_check.Invoke(creature)) + { + i_object = creature; + return; + } + } + } + + public Unit GetTarget() { return i_object; } + + WorldObject _searcher; + Unit i_object; + ICheck i_check; + } + public class UnitLastSearcher : Notifier + { + public UnitLastSearcher(WorldObject searcher, ICheck check) + { + _searcher = searcher; + i_check = check; + } + + public override void Visit(ICollection objs) + { + foreach (var player in objs) + { + if (!player.IsInPhase(_searcher)) + continue; + + if (i_check.Invoke(player)) + i_object = player; + } + } + + public override void Visit(ICollection objs) + { + foreach (var creature in objs) + { + if (!creature.IsInPhase(_searcher)) + continue; + + if (i_check.Invoke(creature)) + i_object = creature; + } + } + + public Unit GetTarget() { return i_object; } + + WorldObject _searcher; + Unit i_object; + ICheck i_check; + } + public class UnitListSearcher : Notifier + { + public UnitListSearcher(WorldObject searcher, List objects, ICheck check) + { + _searcher = searcher; + i_objects = objects; + i_check = check; + } + + public override void Visit(ICollection objs) + { + foreach (var player in objs) + { + if (player.IsInPhase(_searcher)) + if (i_check.Invoke(player)) + i_objects.Add(player); + } + } + public override void Visit(ICollection objs) + { + foreach (var creature in objs) + { + if (creature.IsInPhase(_searcher)) + if (i_check.Invoke(creature)) + i_objects.Add(creature); + } + } + + WorldObject _searcher; + List i_objects; + ICheck i_check; + } + + public class CreatureSearcher : Notifier + { + public CreatureSearcher(WorldObject searcher, ICheck check) + { + _searcher = searcher; + i_check = check; + } + + public override void Visit(ICollection objs) + { + // already found + if (i_object) + return; + + foreach (var creature in objs) + { + if (!creature.IsInPhase(_searcher)) + continue; + + if (i_check.Invoke(creature)) + { + i_object = creature; + return; + } + } + } + + public Creature GetTarget() { return i_object; } + + WorldObject _searcher; + Creature i_object; + ICheck i_check; + } + public class CreatureLastSearcher : Notifier + { + public CreatureLastSearcher(WorldObject searcher, ICheck check) + { + _searcher = searcher; + i_check = check; + } + + public override void Visit(ICollection objs) + { + foreach (var creature in objs) + { + if (!creature.IsInPhase(_searcher)) + continue; + + if (i_check.Invoke(creature)) + i_object = creature; + } + } + + public Creature GetTarget() { return i_object; } + + WorldObject _searcher; + Creature i_object; + ICheck i_check; + } + public class CreatureListSearcher : Notifier + { + public CreatureListSearcher(WorldObject searcher, List objects, ICheck check) + { + _searcher = searcher; + i_objects = objects; + i_check = check; + } + + public override void Visit(ICollection objs) + { + foreach (var creature in objs) + if (creature.IsInPhase(_searcher)) + if (i_check.Invoke(creature)) + i_objects.Add(creature); + } + + WorldObject _searcher; + List i_objects; + ICheck i_check; + } + + public class PlayerSearcher : Notifier + { + public PlayerSearcher(WorldObject searcher, ICheck check) + { + _searcher = searcher; + i_check = check; + } + + public override void Visit(ICollection objs) + { + // already found + if (i_object) + return; + + foreach (var player in objs) + { + if (!player.IsInPhase(_searcher)) + continue; + + if (i_check.Invoke(player)) + { + i_object = player; + return; + } + } + } + + public Player GetTarget() { return i_object; } + + WorldObject _searcher; + Player i_object; + ICheck i_check; + } + public class PlayerLastSearcher : Notifier + { + public PlayerLastSearcher(WorldObject searcher, ICheck check) + { + _searcher = searcher; + i_check = check; + } + + public override void Visit(ICollection objs) + { + foreach (var player in objs) + { + if (!player.IsInPhase(_searcher)) + continue; + + if (i_check.Invoke(player)) + i_object = player; + } + } + + public Player GetTarget() { return i_object; } + + WorldObject _searcher; + Player i_object; + ICheck i_check; + } + public class PlayerListSearcher : Notifier + { + public PlayerListSearcher(WorldObject searcher, List objects, ICheck check) + { + _searcher = searcher; + i_objects = objects; + i_check = check; + } + + public override void Visit(ICollection objs) + { + foreach (var player in objs) + if (player.IsInPhase(_searcher)) + if (i_check.Invoke(player)) + i_objects.Add(player); + } + + WorldObject _searcher; + List i_objects; + ICheck i_check; + } + + //Checks + #region Checks + public class MostHPMissingInRange : ICheck where T : Unit + { + public MostHPMissingInRange(Unit obj, float range, uint hp) + { + i_obj = obj; + i_range = range; + i_hp = hp; + } + + public bool Invoke(T u) + { + if (u.IsAlive() && u.IsInCombat() && !i_obj.IsHostileTo(u) && i_obj.IsWithinDistInMap(u, i_range) && u.GetMaxHealth() - u.GetHealth() > i_hp) + { + i_hp = (uint)(u.GetMaxHealth() - u.GetHealth()); + return true; + } + return false; + } + + Unit i_obj; + float i_range; + ulong i_hp; + } + + public class FriendlyCCedInRange : ICheck + { + public FriendlyCCedInRange(Unit obj, float range) + { + i_obj = obj; + i_range = range; + } + + public bool Invoke(Creature u) + { + if (u.IsAlive() && u.IsInCombat() && !i_obj.IsHostileTo(u) && i_obj.IsWithinDistInMap(u, i_range) && + (u.isFeared() || u.IsCharmed() || u.isFrozen() || u.HasUnitState(UnitState.Stunned) || u.HasUnitState(UnitState.Confused))) + return true; + return false; + } + + Unit i_obj; + float i_range; + } + + public class FriendlyMissingBuffInRange : ICheck + { + public FriendlyMissingBuffInRange(Unit obj, float range, uint spellid) + { + i_obj = obj; + i_range = range; + i_spell = spellid; + } + + public bool Invoke(Creature u) + { + if (u.IsAlive() && u.IsInCombat() && !i_obj.IsHostileTo(u) && i_obj.IsWithinDistInMap(u, i_range) && + !(u.HasAura(i_spell))) + { + return true; + } + return false; + } + + Unit i_obj; + float i_range; + uint i_spell; + } + + public class AnyUnfriendlyUnitInObjectRangeCheck : ICheck + { + public AnyUnfriendlyUnitInObjectRangeCheck(WorldObject obj, Unit funit, float range) + { + i_obj = obj; + i_funit = funit; + i_range = range; + } + + public bool Invoke(Unit u) + { + if (u.IsAlive() && i_obj.IsWithinDistInMap(u, i_range) && !i_funit.IsFriendlyTo(u)) + return true; + else + return false; + } + + WorldObject i_obj; + Unit i_funit; + float i_range; + } + + public class AnyUnfriendlyNoTotemUnitInObjectRangeCheck : ICheck + { + public AnyUnfriendlyNoTotemUnitInObjectRangeCheck(WorldObject obj, Unit funit, float range) + { + i_obj = obj; + i_funit = funit; + i_range = range; + } + + public bool Invoke(Unit u) + { + if (!u.IsAlive()) + return false; + + if (u.GetCreatureType() == CreatureType.NonCombatPet) + return false; + + if (u.IsTypeId(TypeId.Unit) && u.IsTotem()) + return false; + + if (!u.isTargetableForAttack(false)) + return false; + + return i_obj.IsWithinDistInMap(u, i_range) && !i_funit.IsFriendlyTo(u); + } + + WorldObject i_obj; + Unit i_funit; + float i_range; + } + + public class AnyFriendlyUnitInObjectRangeCheck : ICheck + { + public AnyFriendlyUnitInObjectRangeCheck(WorldObject obj, Unit funit, float range, bool playerOnly = false) + { + i_obj = obj; + i_funit = funit; + i_range = range; + i_playerOnly = playerOnly; + } + + public bool Invoke(Unit u) + { + if (u.IsAlive() && i_obj.IsWithinDistInMap(u, i_range) && i_funit.IsFriendlyTo(u) && (!i_playerOnly || u.IsTypeId(TypeId.Player))) + return true; + else + return false; + } + + WorldObject i_obj; + Unit i_funit; + float i_range; + bool i_playerOnly; + } + + public class AnyGroupedUnitInObjectRangeCheck : ICheck + { + public AnyGroupedUnitInObjectRangeCheck(WorldObject obj, Unit funit, float range, bool raid) + { + _source = obj; + _refUnit = funit; + _range = range; + _raid = raid; + } + + public bool Invoke(Unit u) + { + if (_raid) + { + if (!_refUnit.IsInRaidWith(u)) + return false; + } + else if (!_refUnit.IsInPartyWith(u)) + return false; + + return !_refUnit.IsHostileTo(u) && u.IsAlive() && _source.IsWithinDistInMap(u, _range); + } + + WorldObject _source; + Unit _refUnit; + float _range; + bool _raid; + } + + public class AnyUnitInObjectRangeCheck : ICheck + { + public AnyUnitInObjectRangeCheck(WorldObject obj, float range, bool check3D = true) + { + i_obj = obj; + i_range = range; + i_check3D = check3D; + } + + public bool Invoke(Unit u) + { + if (u.IsAlive() && i_obj.IsWithinDistInMap(u, i_range, i_check3D)) + return true; + + return false; + } + + WorldObject i_obj; + float i_range; + bool i_check3D; + } + + // Success at unit in range, range update for next check (this can be use with UnitLastSearcher to find nearest unit) + public class NearestAttackableUnitInObjectRangeCheck : ICheck + { + public NearestAttackableUnitInObjectRangeCheck(WorldObject obj, Unit funit, float range) + { + i_obj = obj; + i_funit = funit; + i_range = range; + } + + public bool Invoke(Unit u) + { + if (u.isTargetableForAttack() && i_obj.IsWithinDistInMap(u, i_range) && + !i_funit.IsFriendlyTo(u) && i_funit.CanSeeOrDetect(u)) + { + i_range = i_obj.GetDistance(u); // use found unit range as new range limit for next check + return true; + } + + return false; + } + + WorldObject i_obj; + Unit i_funit; + float i_range; + } + + public class AnyAoETargetUnitInObjectRangeCheck : ICheck + { + public AnyAoETargetUnitInObjectRangeCheck(WorldObject obj, Unit funit, float range) + { + i_obj = obj; + i_funit = funit; + _spellInfo = null; + i_range = range; + + Unit check = i_funit; + Unit owner = i_funit.GetOwner(); + if (owner) + check = owner; + i_targetForPlayer = (check.IsTypeId(TypeId.Player)); + if (i_obj.IsTypeId(TypeId.DynamicObject)) + _spellInfo = i_obj.ToDynamicObject().GetSpellInfo(); + } + + public bool Invoke(Unit u) + { + // Check contains checks for: live, non-selectable, non-attackable flags, flight check and GM check, ignore totems + if (u.IsTypeId(TypeId.Unit) && u.IsTotem()) + return false; + + if (i_funit._IsValidAttackTarget(u, _spellInfo, i_obj.IsTypeId(TypeId.DynamicObject) ? i_obj : null) && i_obj.IsWithinDistInMap(u, i_range)) + return true; + + return false; + } + + bool i_targetForPlayer; + WorldObject i_obj; + Unit i_funit; + SpellInfo _spellInfo; + float i_range; + } + + public class AnyDeadUnitCheck : ICheck + { + public bool Invoke(Unit u) { return !u.IsAlive(); } + } + + public class NearestHostileUnitCheck : ICheck + { + public NearestHostileUnitCheck(Creature creature, float dist = 0, bool playerOnly = false) + { + me = creature; + i_playerOnly = playerOnly; + + m_range = (dist == 0 ? 9999 : dist); + } + + public bool Invoke(Unit u) + { + if (!me.IsWithinDistInMap(u, m_range)) + return false; + + if (!me.IsValidAttackTarget(u)) + return false; + + if (i_playerOnly && !u.IsTypeId(TypeId.Player)) + return false; + + m_range = me.GetDistance(u); // use found unit range as new range limit for next check + return true; + } + + Creature me; + float m_range; + bool i_playerOnly; + } + + class NearestHostileUnitInAttackDistanceCheck : ICheck + { + public NearestHostileUnitInAttackDistanceCheck(Creature creature, float dist = 0) + { + me = creature; + m_range = (dist == 0 ? 9999 : dist); + m_force = (dist == 0 ? false : true); + } + + public bool Invoke(Unit u) + { + if (!me.IsWithinDistInMap(u, m_range)) + return false; + + if (!me.CanSeeOrDetect(u)) + return false; + + if (m_force) + { + if (!me.IsValidAttackTarget(u)) + return false; + } + else if (!me.CanStartAttack(u, false)) + return false; + + m_range = me.GetDistance(u); // use found unit range as new range limit for next check + return true; + } + + Creature me; + float m_range; + bool m_force; + } + + class NearestHostileUnitInAggroRangeCheck : ICheck + { + public NearestHostileUnitInAggroRangeCheck(Creature creature, bool useLOS = false) + { + _me = creature; + _useLOS = useLOS; + } + + public bool Invoke(Unit u) + { + if (!u.IsHostileTo(_me)) + return false; + + if (!u.IsWithinDistInMap(_me, _me.GetAggroRange(u))) + return false; + + if (!_me.IsValidAttackTarget(u)) + return false; + + if (_useLOS && !u.IsWithinLOSInMap(_me)) + return false; + + return true; + } + + Creature _me; + bool _useLOS; + } + + class AnyAssistCreatureInRangeCheck : ICheck + { + public AnyAssistCreatureInRangeCheck(Unit funit, Unit enemy, float range) + { + i_funit = funit; + i_enemy = enemy; + i_range = range; + + } + + public bool Invoke(Creature u) + { + if (u == i_funit) + return false; + + if (!u.CanAssistTo(i_funit, i_enemy)) + return false; + + // too far + if (!i_funit.IsWithinDistInMap(u, i_range)) + return false; + + // only if see assisted creature + if (!i_funit.IsWithinLOSInMap(u)) + return false; + + return true; + } + + Unit i_funit; + Unit i_enemy; + float i_range; + } + + class NearestAssistCreatureInCreatureRangeCheck : ICheck + { + public NearestAssistCreatureInCreatureRangeCheck(Creature obj, Unit enemy, float range) + { + i_obj = obj; + i_enemy = enemy; + i_range = range; + } + + public bool Invoke(Creature u) + { + if (u == i_obj) + return false; + if (!u.CanAssistTo(i_obj, i_enemy)) + return false; + + if (!i_obj.IsWithinDistInMap(u, i_range)) + return false; + + if (!i_obj.IsWithinLOSInMap(u)) + return false; + + i_range = i_obj.GetDistance(u); // use found unit range as new range limit for next check + return true; + } + + Creature i_obj; + Unit i_enemy; + float i_range; + } + + // Success at unit in range, range update for next check (this can be use with CreatureLastSearcher to find nearest creature) + class NearestCreatureEntryWithLiveStateInObjectRangeCheck : ICheck + { + public NearestCreatureEntryWithLiveStateInObjectRangeCheck(WorldObject obj, uint entry, bool alive, float range) + { + i_obj = obj; + i_entry = entry; + i_alive = alive; + i_range = range; + } + + public bool Invoke(Creature u) + { + if (u.getDeathState() != DeathState.Dead && u.GetEntry() == i_entry && u.IsAlive() == i_alive && i_obj.IsWithinDistInMap(u, i_range)) + { + i_range = i_obj.GetDistance(u); // use found unit range as new range limit for next check + return true; + } + return false; + } + + WorldObject i_obj; + uint i_entry; + bool i_alive; + float i_range; + } + + public class AnyPlayerInObjectRangeCheck : ICheck + { + public AnyPlayerInObjectRangeCheck(WorldObject obj, float range, bool reqAlive = true) + { + _obj = obj; + _range = range; + _reqAlive = reqAlive; + } + + public bool Invoke(Player pl) + { + if (_reqAlive && !pl.IsAlive()) + return false; + + if (!_obj.IsWithinDistInMap(pl, _range)) + return false; + + return true; + } + + WorldObject _obj; + float _range; + bool _reqAlive; + } + + class NearestPlayerInObjectRangeCheck : ICheck + { + public NearestPlayerInObjectRangeCheck(WorldObject obj, float range) + { + i_obj = obj; + i_range = range; + + } + + public bool Invoke(Player pl) + { + if (pl.IsAlive() && i_obj.IsWithinDistInMap(pl, i_range)) + { + i_range = i_obj.GetDistance(pl); + return true; + } + + return false; + } + + WorldObject i_obj; + float i_range; + } + + class AllFriendlyCreaturesInGrid : ICheck + { + public AllFriendlyCreaturesInGrid(Unit obj) + { + unit = obj; + } + + public bool Invoke(Unit u) + { + if (u.IsAlive() && u.IsVisible() && u.IsFriendlyTo(unit)) + return true; + + return false; + } + + Unit unit; + } + + class AllGameObjectsWithEntryInRange : ICheck + { + public AllGameObjectsWithEntryInRange(WorldObject obj, uint entry, float maxRange) + { + m_pObject = obj; + m_uiEntry = entry; + m_fRange = maxRange; + } + + public bool Invoke(GameObject go) + { + if (m_uiEntry == 0 || go.GetEntry() == m_uiEntry && m_pObject.IsWithinDist(go, m_fRange, false)) + return true; + + return false; + } + + WorldObject m_pObject; + uint m_uiEntry; + float m_fRange; + } + + class AllCreaturesOfEntryInRange : ICheck + { + public AllCreaturesOfEntryInRange(WorldObject obj, uint entry, float maxRange) + { + m_pObject = obj; + m_uiEntry = entry; + m_fRange = maxRange; + } + + public bool Invoke(Creature creature) + { + if (m_uiEntry == 0 || creature.GetEntry() == m_uiEntry && m_pObject.IsWithinDist(creature, m_fRange, false)) + return true; + + return false; + } + + WorldObject m_pObject; + uint m_uiEntry; + float m_fRange; + } + + class PlayerAtMinimumRangeAway : ICheck + { + public PlayerAtMinimumRangeAway(Unit _unit, float fMinRange) + { + unit = _unit; + fRange = fMinRange; + } + + public bool Invoke(Player player) + { + //No threat list check, must be done explicit if expected to be in combat with creature + if (!player.IsGameMaster() && player.IsAlive() && !unit.IsWithinDist(player, fRange, false)) + return true; + + return false; + } + + Unit unit; + float fRange; + } + + class GameObjectInRangeCheck : ICheck + { + public GameObjectInRangeCheck(float _x, float _y, float _z, float _range, uint _entry = 0) + { + x = _x; + y = _y; + z = _z; + range = _range; + entry = _entry; + } + + public bool Invoke(GameObject go) + { + if (entry == 0 || (go.GetGoInfo() != null && go.GetGoInfo().entry == entry)) + return go.IsInRange(x, y, z, range); + else return false; + } + + float x, y, z, range; + uint entry; + } + + public class AllWorldObjectsInRange : ICheck + { + public AllWorldObjectsInRange(WorldObject obj, float maxRange) + { + m_pObject = obj; + m_fRange = maxRange; + } + + public bool Invoke(WorldObject go) + { + return m_pObject.IsWithinDist(go, m_fRange, false) && m_pObject.IsInPhase(go); + } + + WorldObject m_pObject; + float m_fRange; + } + + class ObjectTypeIdCheck : ICheck + { + public ObjectTypeIdCheck(TypeId typeId, bool equals) + { + _typeId = typeId; + _equals = equals; + } + + public bool Invoke(WorldObject obj) + { + return (obj.GetTypeId() == _typeId) == _equals; + } + + TypeId _typeId; + bool _equals; + } + + public class ObjectGUIDCheck : ICheck + { + public ObjectGUIDCheck(ObjectGuid GUID) + { + _GUID = GUID; + } + + public bool Invoke(WorldObject obj) + { + return obj.GetGUID() == _GUID; + } + + public static implicit operator Predicate(ObjectGUIDCheck check) + { + return check.Invoke; + } + + ObjectGuid _GUID; + } + + class HeightDifferenceCheck : ICheck + { + public HeightDifferenceCheck(WorldObject go, float diff, bool reverse) + { + _baseObject = go; + _difference = diff; + _reverse = reverse; + + } + + public bool Invoke(WorldObject unit) + { + return (unit.GetPositionZ() - _baseObject.GetPositionZ() > _difference) != _reverse; + } + + WorldObject _baseObject; + float _difference; + bool _reverse; + } + + public class UnitAuraCheck : ICheck where T : WorldObject + { + public UnitAuraCheck(bool present, uint spellId, ObjectGuid casterGUID = default(ObjectGuid)) + { + _present = present; + _spellId = spellId; + _casterGUID = casterGUID; + } + + public bool Invoke(T obj) + { + return obj.ToUnit() && obj.ToUnit().HasAura(_spellId, _casterGUID) == _present; + } + + public static implicit operator Predicate(UnitAuraCheck unit) + { + return unit.Invoke; + } + + bool _present; + uint _spellId; + ObjectGuid _casterGUID; + } + + class GameObjectFocusCheck : ICheck + { + public GameObjectFocusCheck(Unit unit, uint focusId) + { + i_unit = unit; + i_focusId = focusId; + } + + public bool Invoke(GameObject go) + { + if (go.GetGoInfo().type != GameObjectTypes.SpellFocus) + return false; + + if (go.GetGoInfo().SpellFocus.spellFocusType != i_focusId) + return false; + + if (!go.isSpawned()) + return false; + + float dist = go.GetGoInfo().SpellFocus.radius / 2.0f; + + return go.IsWithinDistInMap(i_unit, dist); + } + + Unit i_unit; + uint i_focusId; + } + + // Find the nearest Fishing hole and return true only if source object is in range of hole + class NearestGameObjectFishingHole : ICheck + { + public NearestGameObjectFishingHole(WorldObject obj, float range) + { + i_obj = obj; + i_range = range; + } + + public bool Invoke(GameObject go) + { + if (go.GetGoInfo().type == GameObjectTypes.FishingHole && go.isSpawned() && i_obj.IsWithinDistInMap(go, i_range) && i_obj.IsWithinDistInMap(go, go.GetGoInfo().FishingHole.radius)) + { + i_range = i_obj.GetDistance(go); + return true; + } + return false; + } + + WorldObject i_obj; + float i_range; + } + + class NearestGameObjectCheck : ICheck + { + public NearestGameObjectCheck(WorldObject obj) + { + i_obj = obj; + i_range = 999; + } + + public bool Invoke(GameObject go) + { + if (i_obj.IsWithinDistInMap(go, i_range)) + { + i_range = i_obj.GetDistance(go); // use found GO range as new range limit for next check + return true; + } + return false; + } + + WorldObject i_obj; + float i_range; + } + + // Success at unit in range, range update for next check (this can be use with GameobjectLastSearcher to find nearest GO) + class NearestGameObjectEntryInObjectRangeCheck : ICheck + { + public NearestGameObjectEntryInObjectRangeCheck(WorldObject obj, uint entry, float range) + { + i_obj = obj; + i_entry = entry; + i_range = range; + } + + public bool Invoke(GameObject go) + { + if (go.GetEntry() == i_entry && i_obj.IsWithinDistInMap(go, i_range)) + { + i_range = i_obj.GetDistance(go); // use found GO range as new range limit for next check + return true; + } + return false; + } + + WorldObject i_obj; + uint i_entry; + float i_range; + } + + // Success at unit in range, range update for next check (this can be use with GameobjectLastSearcher to find nearest GO with a certain type) + class NearestGameObjectTypeInObjectRangeCheck : ICheck + { + public NearestGameObjectTypeInObjectRangeCheck(WorldObject obj, GameObjectTypes type, float range) + { + i_obj = obj; + i_type = type; + i_range = range; + } + + public bool Invoke(GameObject go) + { + if (go.GetGoType() == i_type && i_obj.IsWithinDistInMap(go, i_range)) + { + i_range = i_obj.GetDistance(go); // use found GO range as new range limit for next check + return true; + } + return false; + } + + WorldObject i_obj; + GameObjectTypes i_type; + float i_range; + } + + public class AnyDeadUnitObjectInRangeCheck : ICheck where T : WorldObject + { + public AnyDeadUnitObjectInRangeCheck(Unit searchObj, float range) + { + i_searchObj = searchObj; + i_range = range; + } + + public virtual bool Invoke(T obj) + { + Player player = obj.ToPlayer(); + if (player) + return !player.IsAlive() && !player.HasAuraType(AuraType.Ghost) && i_searchObj.IsWithinDistInMap(player, i_range); + + Creature creature = obj.ToCreature(); + if (creature) + return !creature.IsAlive() && i_searchObj.IsWithinDistInMap(creature, i_range); + + Corpse corpse = obj.ToCorpse(); + if (corpse) + return corpse.GetCorpseType() != CorpseType.Bones && i_searchObj.IsWithinDistInMap(corpse, i_range); + + return false; + } + + Unit i_searchObj; + float i_range; + } + + public class AnyDeadUnitSpellTargetInRangeCheck : AnyDeadUnitObjectInRangeCheck where T : WorldObject + { + public AnyDeadUnitSpellTargetInRangeCheck(Unit searchObj, float range, SpellInfo spellInfo, SpellTargetCheckTypes check) + : base(searchObj, range) + { + i_spellInfo = spellInfo; + i_check = new WorldObjectSpellTargetCheck(searchObj, searchObj, spellInfo, check, null); + } + + public override bool Invoke(T obj) + { + return base.Invoke(obj) && i_check.Invoke(obj); + } + + SpellInfo i_spellInfo; + WorldObjectSpellTargetCheck i_check; + } + + public class PlayerOrPetCheck : ICheck + { + public bool Invoke(WorldObject obj) + { + if (obj.IsTypeId(TypeId.Player)) + return false; + + Creature creature = obj.ToCreature(); + if (creature) + return !creature.IsPet(); + + return true; + } + } + #endregion +} \ No newline at end of file diff --git a/Game/Maps/Instances/InstanceSaveManager.cs b/Game/Maps/Instances/InstanceSaveManager.cs new file mode 100644 index 000000000..370d0b8d0 --- /dev/null +++ b/Game/Maps/Instances/InstanceSaveManager.cs @@ -0,0 +1,779 @@ +/* + * Copyright (C) 2012-2017 CypherCore + * + * 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, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +using Framework.Constants; +using Framework.Database; +using Game.DataStorage; +using Game.Entities; +using Game.Groups; +using Game.Scenarios; +using System; +using System.Collections.Generic; +using System.Diagnostics.Contracts; +using System.Linq; + +namespace Game.Maps +{ + public class InstanceSaveManager : Singleton + { + InstanceSaveManager() { } + + public InstanceSave AddInstanceSave(uint mapId, uint instanceId, Difficulty difficulty, long resetTime, uint entranceId, bool canReset, bool load = false) + { + InstanceSave old_save = GetInstanceSave(instanceId); + if (old_save != null) + return old_save; + + MapRecord entry = CliDB.MapStorage.LookupByKey(mapId); + if (entry == null) + { + Log.outError(LogFilter.Server, "InstanceSaveManager.AddInstanceSave: wrong mapid = {0}, instanceid = {1}!", mapId, instanceId); + return null; + } + + if (instanceId == 0) + { + Log.outError(LogFilter.Server, "InstanceSaveManager.AddInstanceSave: mapid = {0}, wrong instanceid = {1}!", mapId, instanceId); + return null; + } + + DifficultyRecord difficultyEntry = CliDB.DifficultyStorage.LookupByKey(difficulty); + if (difficultyEntry == null || difficultyEntry.InstanceType != entry.InstanceType) + { + Log.outError(LogFilter.Server, "InstanceSaveManager.AddInstanceSave: mapid = {0}, instanceid = {1}, wrong dificalty {2}!", mapId, instanceId, difficulty); + return null; + } + + if (entranceId != 0 && !CliDB.WorldSafeLocsStorage.ContainsKey(entranceId)) + { + Log.outWarn(LogFilter.Misc, "InstanceSaveManager.AddInstanceSave: invalid entranceId = {0} defined for instance save with mapid = {1}, instanceid = {2}!", entranceId, mapId, instanceId); + entranceId = 0; + } + + if (resetTime == 0) + { + // initialize reset time + // for normal instances if no creatures are killed the instance will reset in two hours + if (entry.InstanceType == MapTypes.Raid || difficulty > Difficulty.Normal) + resetTime = GetResetTimeFor(mapId, difficulty); + else + { + resetTime = Time.UnixTime + 2 * Time.Hour; + // normally this will be removed soon after in InstanceMap.Add, prevent error + ScheduleReset(true, resetTime, new InstResetEvent(0, mapId, difficulty, instanceId)); + } + } + + Log.outDebug(LogFilter.Maps, "InstanceSaveManager.AddInstanceSave: mapid = {0}, instanceid = {1}", mapId, instanceId); + + InstanceSave save = new InstanceSave(mapId, instanceId, difficulty, entranceId, resetTime, canReset); + if (!load) + save.SaveToDB(); + + m_instanceSaveById[instanceId] = save; + return save; + } + + public InstanceSave GetInstanceSave(uint InstanceId) + { + return m_instanceSaveById.LookupByKey(InstanceId); + } + + public void DeleteInstanceFromDB(uint instanceid) + { + SQLTransaction trans = new SQLTransaction(); + + PreparedStatement stmt = DB.Characters.GetPreparedStatement(CharStatements.DEL_INSTANCE_BY_INSTANCE); + stmt.AddValue(0, instanceid); + trans.Append(stmt); + + stmt = DB.Characters.GetPreparedStatement(CharStatements.DEL_CHAR_INSTANCE_BY_INSTANCE); + stmt.AddValue(0, instanceid); + trans.Append(stmt); + + stmt = DB.Characters.GetPreparedStatement(CharStatements.DEL_GROUP_INSTANCE_BY_INSTANCE); + stmt.AddValue(0, instanceid); + trans.Append(stmt); + + stmt = DB.Characters.GetPreparedStatement(CharStatements.DEL_SCENARIO_INSTANCE_CRITERIA_FOR_INSTANCE); + stmt.AddValue(0, instanceid); + trans.Append(stmt); + + DB.Characters.CommitTransaction(trans); + // Respawn times should be deleted only when the map gets unloaded + } + + public void RemoveInstanceSave(uint InstanceId) + { + var instanceSave = m_instanceSaveById.LookupByKey(InstanceId); + if (instanceSave != null) + { + // save the resettime for normal instances only when they get unloaded + long resettime = instanceSave.GetResetTimeForDB(); + if (resettime != 0) + { + PreparedStatement stmt = DB.Characters.GetPreparedStatement(CharStatements.UPD_INSTANCE_RESETTIME); + + stmt.AddValue(0, resettime); + stmt.AddValue(1, InstanceId); + + DB.Characters.Execute(stmt); + } + + instanceSave.SetToDelete(true); + m_instanceSaveById.Remove(InstanceId); + } + } + + public void LoadInstances() + { + uint oldMSTime = Time.GetMSTime(); + + // Delete expired instances (Instance related spawns are removed in the following cleanup queries) + DB.Characters.Execute("DELETE i FROM instance i LEFT JOIN instance_reset ir ON mapid = map AND i.difficulty = ir.difficulty " + + "WHERE (i.resettime > 0 AND i.resettime < UNIX_TIMESTAMP()) OR (ir.resettime IS NOT NULL AND ir.resettime < UNIX_TIMESTAMP())"); + + // Delete invalid character_instance and group_instance references + DB.Characters.Execute("DELETE ci.* FROM character_instance AS ci LEFT JOIN characters AS c ON ci.guid = c.guid WHERE c.guid IS NULL"); + DB.Characters.Execute("DELETE gi.* FROM group_instance AS gi LEFT JOIN groups AS g ON gi.guid = g.guid WHERE g.guid IS NULL"); + + // Delete invalid instance references + DB.Characters.Execute("DELETE i.* FROM instance AS i LEFT JOIN character_instance AS ci ON i.id = ci.instance LEFT JOIN group_instance AS gi ON i.id = gi.instance WHERE ci.guid IS NULL AND gi.guid IS NULL"); + + // Delete invalid references to instance + DB.Characters.Execute("DELETE FROM creature_respawn WHERE instanceId > 0 AND instanceId NOT IN (SELECT id FROM instance)"); + DB.Characters.Execute("DELETE FROM gameobject_respawn WHERE instanceId > 0 AND instanceId NOT IN (SELECT id FROM instance)"); + DB.Characters.Execute("DELETE tmp.* FROM character_instance AS tmp LEFT JOIN instance ON tmp.instance = instance.id WHERE tmp.instance > 0 AND instance.id IS NULL"); + DB.Characters.Execute("DELETE tmp.* FROM group_instance AS tmp LEFT JOIN instance ON tmp.instance = instance.id WHERE tmp.instance > 0 AND instance.id IS NULL"); + + // Clean invalid references to instance + DB.Characters.Execute("UPDATE corpse SET instanceId = 0 WHERE instanceId > 0 AND instanceId NOT IN (SELECT id FROM instance)"); + DB.Characters.Execute("UPDATE characters AS tmp LEFT JOIN instance ON tmp.instance_id = instance.id SET tmp.instance_id = 0 WHERE tmp.instance_id > 0 AND instance.id IS NULL"); + + // Initialize instance id storage (Needs to be done after the trash has been clean out) + Global.MapMgr.InitInstanceIds(); + + // Load reset times and clean expired instances + LoadResetTimes(); + + Log.outInfo(LogFilter.ServerLoading, "Loaded instances in {0} ms", Time.GetMSTimeDiffToNow(oldMSTime)); + } + + void LoadResetTimes() + { + long now = Time.UnixTime; + long today = (now / Time.Day) * Time.Day; + + // NOTE: Use DirectPExecute for tables that will be queried later + + // get the current reset times for normal instances (these may need to be updated) + // these are only kept in memory for InstanceSaves that are loaded later + // resettime = 0 in the DB for raid/heroic instances so those are skipped + Dictionary> instResetTime = new Dictionary>(); + + // index instance ids by map/difficulty pairs for fast reset warning send + MultiMap mapDiffResetInstances = new MultiMap(); + + SQLResult result = DB.Characters.Query("SELECT id, map, difficulty, resettime FROM instance ORDER BY id ASC"); + if (!result.IsEmpty()) + { + do + { + uint instanceId = result.Read(0); + + // Instances are pulled in ascending order from db and nextInstanceId is initialized with 1, + // so if the instance id is used, increment until we find the first unused one for a potential new instance + if (Global.MapMgr.GetNextInstanceId() == instanceId) + Global.MapMgr.SetNextInstanceId(instanceId + 1); + + // Mark instance id as being used + Global.MapMgr.RegisterInstanceId(instanceId); + long resettime = result.Read(3); + if (resettime != 0) + { + uint mapid = result.Read(1); + uint difficulty = result.Read(2); + + instResetTime[instanceId] = Tuple.Create(MathFunctions.MakePair32(mapid, difficulty), resettime); + mapDiffResetInstances.Add(MathFunctions.MakePair32(mapid, difficulty), instanceId); + } + } + while (result.NextRow()); + + // update reset time for normal instances with the max creature respawn time + X hours + SQLResult result2 = DB.Characters.Query(DB.Characters.GetPreparedStatement(CharStatements.SEL_MAX_CREATURE_RESPAWNS)); + if (!result2.IsEmpty()) + { + do + { + uint instance = result2.Read(1); + long resettime = result2.Read(0) + 2 * Time.Hour; + var pair = instResetTime.LookupByKey(instance); + if (pair != null && pair.Item2 != resettime) + { + DB.Characters.Execute("UPDATE instance SET resettime = '{0}' WHERE id = '{1}'", resettime, instance); + instResetTime[instance] = Tuple.Create(pair.Item1, resettime); + } + } + while (result.NextRow()); + } + + // schedule the reset times + foreach (var pair in instResetTime) + if (pair.Value.Item2 > now) + ScheduleReset(true, pair.Value.Item2, new InstResetEvent(0, MathFunctions.Pair32_LoPart(pair.Value.Item1), (Difficulty)MathFunctions.Pair32_HiPart(pair.Value.Item1), pair.Key)); + } + + // load the global respawn times for raid/heroic instances + uint diff = (uint)(WorldConfig.GetIntValue(WorldCfg.InstanceResetTimeHour) * Time.Hour); + result = DB.Characters.Query("SELECT mapid, difficulty, resettime FROM instance_reset"); + if (!result.IsEmpty()) + { + do + { + uint mapid = result.Read(0); + Difficulty difficulty = (Difficulty)result.Read(1); + ulong oldresettime = result.Read(2); + + MapDifficultyRecord mapDiff = Global.DB2Mgr.GetMapDifficultyData(mapid, difficulty); + if (mapDiff == null) + { + Log.outError(LogFilter.Server, "InstanceSaveManager.LoadResetTimes: invalid mapid({0})/difficulty({1}) pair in instance_reset!", mapid, difficulty); + DB.Characters.Execute("DELETE FROM instance_reset WHERE mapid = '{0}' AND difficulty = '{1}'", mapid, difficulty); + continue; + } + + // update the reset time if the hour in the configs changes + ulong newresettime = (oldresettime / Time.Day) * Time.Day + diff; + if (oldresettime != newresettime) + DB.Characters.Execute("UPDATE instance_reset SET resettime = '{0}' WHERE mapid = '{1}' AND difficulty = '{2}'", newresettime, mapid, difficulty); + + InitializeResetTimeFor(mapid, difficulty, (long)newresettime); + } while (result.NextRow()); + } + + // calculate new global reset times for expired instances and those that have never been reset yet + // add the global reset times to the priority queue + foreach (var mapDifficultyPair in Global.DB2Mgr.GetMapDifficulties()) + { + uint mapid = mapDifficultyPair.Key; + + foreach (var difficultyPair in mapDifficultyPair.Value) + { + Difficulty difficulty = (Difficulty)difficultyPair.Key; + MapDifficultyRecord mapDiff = difficultyPair.Value; + if (mapDiff.GetRaidDuration() == 0) + continue; + + // the reset_delay must be at least one day + uint period = (uint)(((mapDiff.GetRaidDuration() * WorldConfig.GetFloatValue(WorldCfg.RateInstanceResetTime)) / Time.Day) * Time.Day); + if (period < Time.Day) + period = Time.Day; + + long t = GetResetTimeFor(mapid, difficulty); + if (t == 0) + { + // initialize the reset time + t = today + period + diff; + DB.Characters.Execute("INSERT INTO instance_reset VALUES ('{0}', '{1}', '{2}')", mapid, (uint)difficulty, (uint)t); + } + + if (t < now) + { + // assume that expired instances have already been cleaned + // calculate the next reset time + t = (t / Time.Day) * Time.Day; + t += ((today - t) / period + 1) * period + diff; + DB.Characters.Execute("UPDATE instance_reset SET resettime = '{0}' WHERE mapid = '{1}' AND difficulty= '{2}'", t, mapid, (uint)difficulty); + } + + InitializeResetTimeFor(mapid, difficulty, t); + + // schedule the global reset/warning + byte type; + for (type = 1; type < 4; ++type) + if (t - ResetTimeDelay[type - 1] > now) + break; + + ScheduleReset(true, t - ResetTimeDelay[type - 1], new InstResetEvent(type, mapid, difficulty, 0)); + + var range = mapDiffResetInstances.LookupByKey(MathFunctions.MakePair32(mapid, (uint)difficulty)); + foreach (var id in range) + ScheduleReset(true, t - ResetTimeDelay[type - 1], new InstResetEvent(type, mapid, difficulty, id)); + + } + } + } + + public long GetSubsequentResetTime(uint mapid, Difficulty difficulty, long resetTime) + { + MapDifficultyRecord mapDiff = Global.DB2Mgr.GetMapDifficultyData(mapid, difficulty); + if (mapDiff == null || mapDiff.GetRaidDuration() == 0) + { + Log.outError(LogFilter.Misc, "InstanceSaveManager.GetSubsequentResetTime: not valid difficulty or no reset delay for map {0}", mapid); + return 0; + } + + long diff = WorldConfig.GetIntValue(WorldCfg.InstanceResetTimeHour) * Time.Hour; + long period = (uint)(((mapDiff.GetRaidDuration() * WorldConfig.GetFloatValue(WorldCfg.RateInstanceResetTime)) / Time.Day) * Time.Day); + if (period < Time.Day) + period = Time.Day; + + return ((resetTime + Time.Minute) / Time.Day * Time.Day) + period + diff; + } + + public void ScheduleReset(bool add, long time, InstResetEvent Event) + { + if (!add) + { + // find the event in the queue and remove it + var range = m_resetTimeQueue.LookupByKey(time); + foreach (var instResetEvent in range) + { + if (instResetEvent == Event) + { + m_resetTimeQueue.Remove(time, instResetEvent); + return; + } + } + + // in case the reset time changed (should happen very rarely), we search the whole queue + foreach (var pair in m_resetTimeQueue) + { + if (pair.Value == Event) + { + m_resetTimeQueue.Remove(pair); + return; + } + } + + Log.outError(LogFilter.Server, "InstanceSaveManager.ScheduleReset: cannot cancel the reset, the event({0}, {1}, {2}) was not found!", Event.type, Event.mapid, Event.instanceId); + } + else + m_resetTimeQueue.Add(time, Event); + } + + public void ForceGlobalReset(uint mapId, Difficulty difficulty) + { + if (Global.DB2Mgr.GetDownscaledMapDifficultyData(mapId, ref difficulty) == null) + return; + // remove currently scheduled reset times + ScheduleReset(false, 0, new InstResetEvent(1, mapId, difficulty, 0)); + ScheduleReset(false, 0, new InstResetEvent(4, mapId, difficulty, 0)); + // force global reset on the instance + _ResetOrWarnAll(mapId, difficulty, false, Time.UnixTime); + } + + public void Update() + { + long now = Time.UnixTime; + long t; + + while (!m_resetTimeQueue.Empty()) + { + t = m_resetTimeQueue.First().Key; + if (t >= now) + break; + + InstResetEvent Event = m_resetTimeQueue.First().Value; + if (Event.type == 0) + { + // for individual normal instances, max creature respawn + X hours + _ResetInstance(Event.mapid, Event.instanceId); + m_resetTimeQueue.Remove(m_resetTimeQueue.First()); + } + else + { + // global reset/warning for a certain map + long resetTime = GetResetTimeFor(Event.mapid, Event.difficulty); + _ResetOrWarnAll(Event.mapid, Event.difficulty, Event.type != 4, resetTime); + if (Event.type != 4) + { + // schedule the next warning/reset + ++Event.type; + ScheduleReset(true, resetTime - ResetTimeDelay[Event.type - 1], Event); + } + m_resetTimeQueue.Remove(m_resetTimeQueue.First()); + } + } + } + + void _ResetSave(KeyValuePair pair) + { + // unbind all players bound to the instance + // do not allow UnbindInstance to automatically unload the InstanceSaves + lock_instLists = true; + + bool shouldDelete = true; + var pList = pair.Value.m_playerList; + List temp = new List(); // list of expired binds that should be unbound + foreach (var player in pList) + { + InstanceBind bind = player.GetBoundInstance(pair.Value.GetMapId(), pair.Value.GetDifficultyID()); + if (bind != null) + { + Contract.Assert(bind.save == pair.Value); + if (bind.perm && bind.extendState != 0) // permanent and not already expired + { + // actual promotion in DB already happened in caller + bind.extendState = bind.extendState == BindExtensionState.Extended ? BindExtensionState.Normal : BindExtensionState.Expired; + shouldDelete = false; + continue; + } + } + temp.Add(player); + } + + var gList = pair.Value.m_groupList; + while (!gList.Empty()) + { + Group group = gList.First(); + group.UnbindInstance(pair.Value.GetMapId(), pair.Value.GetDifficultyID(), true); + } + + if (shouldDelete) + m_instanceSaveById.Remove(pair.Key); + + lock_instLists = false; + } + + void _ResetInstance(uint mapid, uint instanceId) + { + Log.outDebug(LogFilter.Maps, "InstanceSaveMgr._ResetInstance {0}, {1}", mapid, instanceId); + Map map = Global.MapMgr.CreateBaseMap(mapid); + if (!map.Instanceable()) + return; + + var pair = m_instanceSaveById.Find(instanceId); + if (pair.Value != null) + _ResetSave(pair); + + DeleteInstanceFromDB(instanceId); // even if save not loaded + + Map iMap = ((MapInstanced)map).FindInstanceMap(instanceId); + + if (iMap != null && iMap.IsDungeon()) + ((InstanceMap)iMap).Reset(InstanceResetMethod.RespawnDelay); + + if (iMap != null) + { + iMap.DeleteRespawnTimes(); + iMap.DeleteCorpseData(); + } + else + Map.DeleteRespawnTimesInDB(mapid, instanceId); + + // Free up the instance id and allow it to be reused + Global.MapMgr.FreeInstanceId(instanceId); + } + + void _ResetOrWarnAll(uint mapid, Difficulty difficulty, bool warn, long resetTime) + { + // global reset for all instances of the given map + MapRecord mapEntry = CliDB.MapStorage.LookupByKey(mapid); + if (!mapEntry.Instanceable()) + return; + + Log.outDebug(LogFilter.Misc, "InstanceSaveManager.ResetOrWarnAll: Processing map {0} ({1}) on difficulty {2} (warn? {3})", mapEntry.MapName[Global.WorldMgr.GetDefaultDbcLocale()], mapid, difficulty, warn); + long now = Time.UnixTime; + + if (!warn) + { + // calculate the next reset time + long next_reset = GetSubsequentResetTime(mapid, difficulty, resetTime); + if (next_reset == 0) + return; + + // delete them from the DB, even if not loaded + SQLTransaction trans = new SQLTransaction(); + + PreparedStatement stmt = DB.Characters.GetPreparedStatement(CharStatements.DEL_EXPIRED_CHAR_INSTANCE_BY_MAP_DIFF); + stmt.AddValue(0, mapid); + stmt.AddValue(1, (byte)difficulty); + trans.Append(stmt); + + stmt = DB.Characters.GetPreparedStatement(CharStatements.DEL_GROUP_INSTANCE_BY_MAP_DIFF); + stmt.AddValue(0, mapid); + stmt.AddValue(1, (byte)difficulty); + trans.Append(stmt); + + stmt = DB.Characters.GetPreparedStatement(CharStatements.DEL_EXPIRED_INSTANCE_BY_MAP_DIFF); + stmt.AddValue(0, mapid); + stmt.AddValue(1, (byte)difficulty); + trans.Append(stmt); + + stmt = DB.Characters.GetPreparedStatement(CharStatements.UPD_EXPIRE_CHAR_INSTANCE_BY_MAP_DIFF); + stmt.AddValue(0, mapid); + stmt.AddValue(1, (byte)difficulty); + trans.Append(stmt); + + DB.Characters.CommitTransaction(trans); + + // promote loaded binds to instances of the given map + foreach (var pair in m_instanceSaveById.ToList()) + { + if (pair.Value.GetMapId() == mapid && pair.Value.GetDifficultyID() == difficulty) + _ResetSave(pair); + } + + SetResetTimeFor(mapid, difficulty, next_reset); + ScheduleReset(true, next_reset - 3600, new InstResetEvent(1, mapid, difficulty, 0)); + + // Update it in the DB + stmt = DB.Characters.GetPreparedStatement(CharStatements.UPD_GLOBAL_INSTANCE_RESETTIME); + stmt.AddValue(0, next_reset); + stmt.AddValue(1, mapid); + stmt.AddValue(2, difficulty); + + DB.Characters.Execute(stmt); + } + + // note: this isn't fast but it's meant to be executed very rarely + Map map = Global.MapMgr.CreateBaseMap(mapid); // _not_ include difficulty + var instMaps = ((MapInstanced)map).GetInstancedMaps(); + uint timeLeft; + + foreach (var pair in instMaps) + { + Map map2 = pair.Value; + if (!map2.IsDungeon()) + continue; + + if (warn) + { + if (now >= resetTime) + timeLeft = 0; + else + timeLeft = (uint)(resetTime - now); + + ((InstanceMap)map2).SendResetWarnings(timeLeft); + } + else + ((InstanceMap)map2).Reset(InstanceResetMethod.Global); + } + + /// @todo delete creature/gameobject respawn times even if the maps are not loaded + } + + public uint GetNumBoundPlayersTotal() + { + uint ret = 0; + foreach (var pair in m_instanceSaveById) + ret += pair.Value.GetPlayerCount(); + + return ret; + } + + public uint GetNumBoundGroupsTotal() + { + uint ret = 0; + foreach (var pair in m_instanceSaveById) + ret += pair.Value.GetGroupCount(); + + return ret; + } + + + public long GetResetTimeFor(uint mapid, Difficulty d) + { + return m_resetTimeByMapDifficulty.LookupByKey(MathFunctions.MakePair64(mapid, (uint)d)); + } + + // Use this on startup when initializing reset times + void InitializeResetTimeFor(uint mapid, Difficulty d, long t) + { + m_resetTimeByMapDifficulty[MathFunctions.MakePair64(mapid, (uint)d)] = t; + } + // Use this only when updating existing reset times + void SetResetTimeFor(uint mapid, Difficulty d, long t) + { + var key = MathFunctions.MakePair64(mapid, (uint)d); + Contract.Assert(m_resetTimeByMapDifficulty.ContainsKey(key)); + m_resetTimeByMapDifficulty[key] = t; + } + + public Dictionary GetResetTimeMap() + { + return m_resetTimeByMapDifficulty; + } + + public int GetNumInstanceSaves() { return m_instanceSaveById.Count; } + + public class InstResetEvent + { + public InstResetEvent(byte t = 0, uint _mapid = 0, Difficulty d = Difficulty.Normal, uint _instanceid = 0) + { + type = t; + difficulty = d; + mapid = _mapid; + instanceId = _instanceid; + } + + public byte type; + public Difficulty difficulty; + public uint mapid; + public uint instanceId; + } + + static ushort[] ResetTimeDelay = { 3600, 900, 300, 60 }; + + // used during global instance resets + public bool lock_instLists; + // fast lookup by instance id + Dictionary m_instanceSaveById = new Dictionary(); + // fast lookup for reset times (always use existed functions for access/set) + Dictionary m_resetTimeByMapDifficulty = new Dictionary(); + MultiMap m_resetTimeQueue = new MultiMap(); + } + + public class InstanceSave + { + public InstanceSave(uint MapId, uint InstanceId, Difficulty difficulty, uint entranceId, long resetTime, bool canReset) + { + m_resetTime = resetTime; + m_instanceid = InstanceId; + m_mapid = MapId; + m_difficulty = difficulty; + m_entranceId = entranceId; + m_canReset = canReset; + m_toDelete = false; + } + + public void SaveToDB() + { + // save instance data too + string data = ""; + uint completedEncounters = 0; + + Map map = Global.MapMgr.FindMap(GetMapId(), m_instanceid); + if (map != null) + { + Contract.Assert(map.IsDungeon()); + InstanceScript instanceScript = ((InstanceMap)map).GetInstanceScript(); + if (instanceScript != null) + { + data = instanceScript.GetSaveData(); + completedEncounters = instanceScript.GetCompletedEncounterMask(); + m_entranceId = instanceScript.GetEntranceLocation(); + } + + InstanceScenario scenario = map.ToInstanceMap().GetInstanceScenario(); + if (scenario != null) + scenario.SaveToDB(); + } + + PreparedStatement stmt = DB.Characters.GetPreparedStatement(CharStatements.INS_INSTANCE_SAVE); + stmt.AddValue(0, m_instanceid); + stmt.AddValue(1, GetMapId()); + stmt.AddValue(2, GetResetTimeForDB()); + stmt.AddValue(3, (uint)GetDifficultyID()); + stmt.AddValue(4, completedEncounters); + stmt.AddValue(5, data); + stmt.AddValue(6, m_entranceId); + DB.Characters.Execute(stmt); + } + + public long GetResetTimeForDB() + { + // only save the reset time for normal instances + MapRecord entry = CliDB.MapStorage.LookupByKey(GetMapId()); + if (entry == null || entry.InstanceType == MapTypes.Raid || GetDifficultyID() == Difficulty.Heroic) + return 0; + else + return GetResetTime(); + } + + InstanceTemplate GetTemplate() + { + return Global.ObjectMgr.GetInstanceTemplate(m_mapid); + } + + MapRecord GetMapEntry() + { + return CliDB.MapStorage.LookupByKey(m_mapid); + } + + public void DeleteFromDB() + { + Global.InstanceSaveMgr.DeleteInstanceFromDB(GetInstanceId()); + } + + bool UnloadIfEmpty() + { + if (m_playerList.Empty() && m_groupList.Empty()) + { + if (!Global.InstanceSaveMgr.lock_instLists) + Global.InstanceSaveMgr.RemoveInstanceSave(GetInstanceId()); + + return false; + } + else + return true; + } + + public uint GetPlayerCount() { return (uint)m_playerList.Count; } + public uint GetGroupCount() { return (uint)m_groupList.Count; } + + public uint GetInstanceId() { return m_instanceid; } + public uint GetMapId() { return m_mapid; } + + public long GetResetTime() { return m_resetTime; } + public void SetResetTime(long resetTime) { m_resetTime = resetTime; } + + public uint GetEntranceLocation() { return m_entranceId; } + void SetEntranceLocation(uint entranceId) { m_entranceId = entranceId; } + + public void AddPlayer(Player player) + { + m_playerList.Add(player); + } + public bool RemovePlayer(Player player) + { + m_playerList.Remove(player); + + return UnloadIfEmpty(); + } + + public void AddGroup(Group group) { m_groupList.Add(group); } + public bool RemoveGroup(Group group) + { + m_groupList.Remove(group); + + return UnloadIfEmpty(); + } + + public bool CanReset() { return m_canReset; } + public void SetCanReset(bool canReset) { m_canReset = canReset; } + + public Difficulty GetDifficultyID() { return m_difficulty; } + + public void SetToDelete(bool toDelete) + { + m_toDelete = toDelete; + } + + public List m_playerList = new List(); + public List m_groupList = new List(); + long m_resetTime; + uint m_instanceid; + uint m_mapid; + Difficulty m_difficulty; + uint m_entranceId; + bool m_canReset; + bool m_toDelete; + } +} diff --git a/Game/Maps/Instances/InstanceScript.cs b/Game/Maps/Instances/InstanceScript.cs new file mode 100644 index 000000000..52c6cde4e --- /dev/null +++ b/Game/Maps/Instances/InstanceScript.cs @@ -0,0 +1,946 @@ +/* + * Copyright (C) 2012-2017 CypherCore + * + * 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, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +using Framework.Constants; +using Framework.Database; +using Framework.IO; +using Game.Entities; +using Game.Groups; +using Game.Network.Packets; +using Game.Scenarios; +using System.Collections.Generic; +using System.Diagnostics.Contracts; +using System.Text; + +namespace Game.Maps +{ + public class InstanceScript : ZoneScript + { + public InstanceScript(Map map) + { + instance = map; + } + + public void SaveToDB() + { + InstanceScenario scenario = instance.ToInstanceMap().GetInstanceScenario(); + if (scenario != null) + scenario.SaveToDB(); + + string data = GetSaveData(); + if (string.IsNullOrEmpty(data)) + return; + + PreparedStatement stmt = DB.Characters.GetPreparedStatement(CharStatements.UPD_INSTANCE_DATA); + stmt.AddValue(0, GetCompletedEncounterMask()); + stmt.AddValue(1, data); + stmt.AddValue(2, _entranceId); + stmt.AddValue(3, instance.GetInstanceId()); + DB.Characters.Execute(stmt); + } + + public virtual bool IsEncounterInProgress() + { + foreach (var boss in bosses.Values) + { + if (boss.state == EncounterState.InProgress) + return true; + } + + return false; + } + + public override void OnCreatureCreate(Creature creature) + { + AddObject(creature, true); + AddMinion(creature, true); + } + + public override void OnCreatureRemove(Creature creature) + { + AddObject(creature, false); + AddMinion(creature, false); + } + + public override void OnGameObjectCreate(GameObject go) + { + AddObject(go, true); + AddDoor(go, true); + } + + public override void OnGameObjectRemove(GameObject go) + { + AddObject(go, false); + AddDoor(go, false); + } + + public ObjectGuid GetObjectGuid(uint type) + { + return _objectGuids.LookupByKey(type); + } + + public override ObjectGuid GetGuidData(uint type) + { + return GetObjectGuid(type); + } + + public void SetHeaders(string dataHeaders) + { + foreach (char header in dataHeaders) + if (char.IsLetter(header)) + headers.Add(header); + } + + public void LoadBossBoundaries(BossBoundaryEntry[] data) + { + foreach (BossBoundaryEntry entry in data) + { + if (entry.BossId < bosses.Count) + bosses[entry.BossId].boundary.Add(entry.Boundary); + } + } + + public void LoadMinionData(params MinionData[] data) + { + foreach (var minion in data) + { + if (minion.entry == 0) + continue; + + if (minion.bossId < bosses.Count) + minions.Add(minion.entry, new MinionInfo(bosses[minion.bossId])); + } + + Log.outDebug(LogFilter.Scripts, "InstanceScript.LoadMinionData: {0} minions loaded.", minions.Count); + } + + public void LoadDoorData(params DoorData[] data) + { + foreach (var door in data) + { + if (door.entry == 0) + continue; + + if (door.bossId < bosses.Count) + doors.Add(door.entry, new DoorInfo(bosses[door.bossId], door.type)); + } + + Log.outDebug(LogFilter.Scripts, "InstanceScript.LoadDoorData: {0} doors loaded.", doors.Count); + } + + public void LoadObjectData(ObjectData[] creatureData, ObjectData[] gameObjectData) + { + if (creatureData != null) + LoadObjectData(creatureData, _creatureInfo); + + if (gameObjectData != null) + LoadObjectData(gameObjectData, _gameObjectInfo); + + Log.outDebug(LogFilter.Scripts, "InstanceScript.LoadObjectData: {0} objects loaded.", _creatureInfo.Count + _gameObjectInfo.Count); + } + + void LoadObjectData(ObjectData[] objectData, Dictionary objectInfo) + { + foreach (var data in objectData) + { + Contract.Assert(!objectInfo.ContainsKey(data.entry)); + objectInfo[data.entry] = data.type; + } + } + + void UpdateMinionState(Creature minion, EncounterState state) + { + switch (state) + { + case EncounterState.NotStarted: + if (!minion.IsAlive()) + minion.Respawn(); + else if (minion.IsInCombat()) + minion.GetAI().EnterEvadeMode(); + break; + case EncounterState.InProgress: + if (!minion.IsAlive()) + minion.Respawn(); + else if (minion.GetVictim() == null) + minion.GetAI().DoZoneInCombat(); + break; + default: + break; + } + } + + public virtual void UpdateDoorState(GameObject door) + { + var range = doors.LookupByKey(door.GetEntry()); + if (range.Empty()) + return; + + bool open = true; + foreach (var info in range) + { + if (!open) + break; + + switch (info.type) + { + case DoorType.Room: + open = (info.bossInfo.state != EncounterState.InProgress); + break; + case DoorType.Passage: + open = (info.bossInfo.state == EncounterState.Done); + break; + case DoorType.SpawnHole: + open = (info.bossInfo.state == EncounterState.InProgress); + break; + default: + break; + } + } + + door.SetGoState(open ? GameObjectState.Active : GameObjectState.Ready); + } + + public BossInfo GetBossInfo(uint id) + { + Contract.Assert(id < bosses.Count); + return bosses[id]; + } + + void AddObject(Creature obj, bool add) + { + if (_creatureInfo.ContainsKey(obj.GetEntry())) + AddObject(obj, _creatureInfo[obj.GetEntry()], add); + } + + void AddObject(GameObject obj, bool add) + { + if (_gameObjectInfo.ContainsKey(obj.GetEntry())) + AddObject(obj, _gameObjectInfo[obj.GetEntry()], add); + } + + void AddObject(WorldObject obj, uint type, bool add) + { + if (add) + _objectGuids[type] = obj.GetGUID(); + else + { + var guid = _objectGuids.LookupByKey(type); + if (!guid.IsEmpty() && guid == obj.GetGUID()) + _objectGuids.Remove(type); + } + } + + public virtual void AddDoor(GameObject door, bool add) + { + var range = doors.LookupByKey(door.GetEntry()); + if (range.Empty()) + return; + + foreach (var data in range) + { + if (add) + data.bossInfo.door[(int)data.type].Add(door.GetGUID()); + else + data.bossInfo.door[(int)data.type].Remove(door.GetGUID()); + } + + if (add) + UpdateDoorState(door); + } + + public void AddMinion(Creature minion, bool add) + { + var minionInfo = minions.LookupByKey(minion.GetEntry()); + if (minionInfo == null) + return; + + if (add) + minionInfo.bossInfo.minion.Add(minion.GetGUID()); + else + minionInfo.bossInfo.minion.Remove(minion.GetGUID()); + } + + public Creature GetCreature(uint type) + { + return instance.GetCreature(GetObjectGuid(type)); + } + + public GameObject GetGameObject(uint type) + { + return instance.GetGameObject(GetObjectGuid(type)); + } + + public virtual bool SetBossState(uint id, EncounterState state) + { + if (id < bosses.Count) + { + BossInfo bossInfo = bosses[id]; + if (bossInfo.state == EncounterState.ToBeDecided) // loading + { + bossInfo.state = state; + //Log.outError(LogFilter.General "Inialize boss {0} state as {1}.", id, (uint32)state); + return false; + } + else + { + if (bossInfo.state == state) + return false; + + if (state == EncounterState.Done) + { + foreach (var guid in bossInfo.minion) + { + Creature minion = instance.GetCreature(guid); + if (minion) + if (minion.isWorldBoss() && minion.IsAlive()) + return false; + } + } + + switch (state) + { + case EncounterState.InProgress: + { + uint resInterval = GetCombatResurrectionChargeInterval(); + InitializeCombatResurrections(1, resInterval); + SendEncounterStart(1, 9, resInterval, resInterval); + break; + } + case EncounterState.Fail: + case EncounterState.Done: + ResetCombatResurrections(); + SendEncounterEnd(); + break; + default: + break; + } + + bossInfo.state = state; + SaveToDB(); + } + + for (uint type = 0; type < (int)DoorType.Max; ++type) + { + foreach (var guid in bossInfo.door[type]) + { + GameObject door = instance.GetGameObject(guid); + if (door) + UpdateDoorState(door); + } + } + + foreach (var guid in bossInfo.minion) + { + Creature minion = instance.GetCreature(guid); + if (minion) + UpdateMinionState(minion, state); + } + + return true; + } + return false; + } + + public bool _SkipCheckRequiredBosses(Player player = null) + { + return player && player.GetSession().HasPermission(RBACPermissions.SkipCheckInstanceRequiredBosses); + } + + public virtual void Load(string data) + { + if (string.IsNullOrEmpty(data)) + { + OUT_LOAD_INST_DATA_FAIL(); + return; + } + + OUT_LOAD_INST_DATA(data); + + var loadStream = new StringArguments(data); + + if (ReadSaveDataHeaders(loadStream)) + { + ReadSaveDataBossStates(loadStream); + ReadSaveDataMore(loadStream); + } + else + OUT_LOAD_INST_DATA_FAIL(); + + OUT_LOAD_INST_DATA_COMPLETE(); + } + + bool ReadSaveDataHeaders(StringArguments data) + { + foreach (char header in headers) + { + char buff = data.NextChar(); + + if (header != buff) + return false; + } + + return true; + } + + void ReadSaveDataBossStates(StringArguments data) + { + uint bossId = 0; + foreach (var i in bosses) + { + EncounterState buff = (EncounterState)data.NextUInt32(); + if (buff == EncounterState.InProgress || buff == EncounterState.Fail || buff == EncounterState.Special) + buff = EncounterState.NotStarted; + + if (buff < EncounterState.ToBeDecided) + SetBossState(bossId++, buff); + } + } + + public virtual string GetSaveData() + { + OUT_SAVE_INST_DATA(); + + StringBuilder saveStream = new StringBuilder(); + + WriteSaveDataHeaders(saveStream); + WriteSaveDataBossStates(saveStream); + WriteSaveDataMore(saveStream); + + OUT_SAVE_INST_DATA_COMPLETE(); + + return saveStream.ToString(); + } + + void WriteSaveDataHeaders(StringBuilder data) + { + foreach (char header in headers) + data.AppendFormat("{0} ", header); + } + + void WriteSaveDataBossStates(StringBuilder data) + { + foreach (BossInfo bossInfo in bosses.Values) + data.AppendFormat("{0} ", (uint)bossInfo.state); + } + + public void HandleGameObject(ObjectGuid guid, bool open, GameObject go = null) + { + if (!go) + go = instance.GetGameObject(guid); + if (go) + go.SetGoState(open ? GameObjectState.Active : GameObjectState.Ready); + else + Log.outDebug(LogFilter.Scripts, "InstanceScript: HandleGameObject failed"); + } + + public void DoUseDoorOrButton(ObjectGuid uiGuid, uint withRestoreTime = 0, bool useAlternativeState = false) + { + if (uiGuid.IsEmpty()) + return; + + GameObject go = instance.GetGameObject(uiGuid); + if (go) + { + if (go.GetGoType() == GameObjectTypes.Door || go.GetGoType() == GameObjectTypes.Button) + { + if (go.getLootState() == LootState.Ready) + go.UseDoorOrButton(withRestoreTime, useAlternativeState); + else if (go.getLootState() == LootState.Activated) + go.ResetDoorOrButton(); + } + else + Log.outError(LogFilter.Scripts, "InstanceScript: DoUseDoorOrButton can't use gameobject entry {0}, because type is {1}.", go.GetEntry(), go.GetGoType()); + } + else + Log.outDebug(LogFilter.Scripts, "InstanceScript: DoUseDoorOrButton failed"); + } + + void DoCloseDoorOrButton(ObjectGuid guid) + { + if (guid.IsEmpty()) + return; + + GameObject go = instance.GetGameObject(guid); + if (go) + { + if (go.GetGoType() == GameObjectTypes.Door || go.GetGoType() == GameObjectTypes.Button) + { + if (go.getLootState() == LootState.Activated) + go.ResetDoorOrButton(); + } + else + Log.outError(LogFilter.Scripts, "InstanceScript: DoCloseDoorOrButton can't use gameobject entry {0}, because type is {1}.", go.GetEntry(), go.GetGoType()); + } + else + Log.outDebug(LogFilter.Scripts, "InstanceScript: DoCloseDoorOrButton failed"); + } + + public void DoRespawnGameObject(ObjectGuid guid, uint timeToDespawn) + { + GameObject go = instance.GetGameObject(guid); + if (go) + { + switch (go.GetGoType()) + { + case GameObjectTypes.Door: + case GameObjectTypes.Button: + case GameObjectTypes.Trap: + case GameObjectTypes.FishingNode: + // not expect any of these should ever be handled + Log.outError(LogFilter.Scripts, "InstanceScript: DoRespawnGameObject can't respawn gameobject entry {0}, because type is {1}.", go.GetEntry(), go.GetGoType()); + return; + default: + break; + } + + if (go.isSpawned()) + return; + + go.SetRespawnTime((int)timeToDespawn); + } + else + Log.outDebug(LogFilter.Scripts, "InstanceScript: DoRespawnGameObject failed"); + } + + public void DoUpdateWorldState(uint uiStateId, uint uiStateData) + { + var lPlayers = instance.GetPlayers(); + + if (!lPlayers.Empty()) + { + foreach (var player in lPlayers) + player.SendUpdateWorldState(uiStateId, uiStateData); + } + else + Log.outDebug(LogFilter.Scripts, "DoUpdateWorldState attempt send data but no players in map."); + } + + // Send Notify to all players in instance + void DoSendNotifyToInstance(string format, params object[] args) + { + var players = instance.GetPlayers(); + + if (!players.Empty()) + { + foreach (var player in players) + { + WorldSession session = player.GetSession(); + if (session != null) + session.SendNotification(format, args); + } + } + } + + // Update Achievement Criteria for all players in instance + public void DoUpdateCriteria(CriteriaTypes type, uint miscValue1 = 0, uint miscValue2 = 0, Unit unit = null) + { + var PlayerList = instance.GetPlayers(); + + if (!PlayerList.Empty()) + foreach (var player in PlayerList) + player.UpdateCriteria(type, miscValue1, miscValue2, 0, unit); + } + + // Start timed achievement for all players in instance + public void DoStartCriteriaTimer(CriteriaTimedTypes type, uint entry) + { + var PlayerList = instance.GetPlayers(); + + if (!PlayerList.Empty()) + foreach (var player in PlayerList) + player.StartCriteriaTimer(type, entry); + } + + // Stop timed achievement for all players in instance + public void DoStopCriteriaTimer(CriteriaTimedTypes type, uint entry) + { + var PlayerList = instance.GetPlayers(); + + if (!PlayerList.Empty()) + foreach (var player in PlayerList) + player.RemoveCriteriaTimer(type, entry); + } + + // Remove Auras due to Spell on all players in instance + public void DoRemoveAurasDueToSpellOnPlayers(uint spell) + { + var PlayerList = instance.GetPlayers(); + if (!PlayerList.Empty()) + { + foreach (var player in PlayerList) + { + player.RemoveAurasDueToSpell(spell); + Pet pet = player.GetPet(); + if (pet != null) + pet.RemoveAurasDueToSpell(spell); + } + } + } + + // Cast spell on all players in instance + public void DoCastSpellOnPlayers(uint spell) + { + var PlayerList = instance.GetPlayers(); + + if (!PlayerList.Empty()) + foreach (var player in PlayerList) + player.CastSpell(player, spell, true); + } + + public virtual bool CheckAchievementCriteriaMeet(uint criteria_id, Player source, Unit target = null, uint miscvalue1 = 0) + { + Log.outError(LogFilter.Server, "Achievement system call CheckAchievementCriteriaMeet but instance script for map {0} not have implementation for achievement criteria {1}", + instance.GetId(), criteria_id); + return false; + } + + void SetEntranceLocation(uint worldSafeLocationId) + { + _entranceId = worldSafeLocationId; + if (_temporaryEntranceId != 0) + _temporaryEntranceId = 0; + } + + public void SendEncounterUnit(EncounterFrameType type, Unit unit = null, byte priority = 0) + { + switch (type) + { + case EncounterFrameType.Engage: + if (unit == null) + return; + + InstanceEncounterEngageUnit encounterEngageMessage = new InstanceEncounterEngageUnit(); + encounterEngageMessage.Unit = unit.GetGUID(); + encounterEngageMessage.TargetFramePriority = priority; + instance.SendToPlayers(encounterEngageMessage); + break; + case EncounterFrameType.Disengage: + if (!unit) + return; + + InstanceEncounterDisengageUnit encounterDisengageMessage = new InstanceEncounterDisengageUnit(); + encounterDisengageMessage.Unit = unit.GetGUID(); + instance.SendToPlayers(encounterDisengageMessage); + break; + case EncounterFrameType.UpdatePriority: + if (!unit) + return; + + InstanceEncounterChangePriority encounterChangePriorityMessage = new InstanceEncounterChangePriority(); + encounterChangePriorityMessage.Unit = unit.GetGUID(); + encounterChangePriorityMessage.TargetFramePriority = priority; + instance.SendToPlayers(encounterChangePriorityMessage); + break; + default: + break; + } + } + + void SendEncounterStart(uint inCombatResCount = 0, uint maxInCombatResCount = 0, uint inCombatResChargeRecovery = 0, uint nextCombatResChargeTime = 0) + { + InstanceEncounterStart encounterStartMessage = new InstanceEncounterStart(); + encounterStartMessage.InCombatResCount = inCombatResCount; + encounterStartMessage.MaxInCombatResCount = maxInCombatResCount; + encounterStartMessage.CombatResChargeRecovery = inCombatResChargeRecovery; + encounterStartMessage.NextCombatResChargeTime = nextCombatResChargeTime; + + instance.SendToPlayers(encounterStartMessage); + } + + void SendEncounterEnd() + { + instance.SendToPlayers(new InstanceEncounterEnd()); + } + + void SendBossKillCredit(uint encounterId) + { + BossKillCredit bossKillCreditMessage = new BossKillCredit(); + bossKillCreditMessage.DungeonEncounterID = encounterId; + + instance.SendToPlayers(bossKillCreditMessage); + } + + public void UpdateEncounterStateForKilledCreature(uint creatureId, Unit source) + { + UpdateEncounterState(EncounterCreditType.KillCreature, creatureId, source); + } + + public void UpdateEncounterStateForSpellCast(uint spellId, Unit source) + { + UpdateEncounterState(EncounterCreditType.CastSpell, spellId, source); + } + + void UpdateEncounterState(EncounterCreditType type, uint creditEntry, Unit source) + { + var encounters = Global.ObjectMgr.GetDungeonEncounterList(instance.GetId(), instance.GetDifficultyID()); + if (encounters.Empty()) + return; + + uint dungeonId = 0; + + foreach (var encounter in encounters) + { + if (encounter.creditType == type && encounter.creditEntry == creditEntry) + { + completedEncounters |= (1u << encounter.dbcEntry.Bit); + if (encounter.lastEncounterDungeon != 0) + { + dungeonId = encounter.lastEncounterDungeon; + Log.outDebug(LogFilter.Lfg, "UpdateEncounterState: Instance {0} (instanceId {1}) completed encounter {2}. Credit Dungeon: {3}", + instance.GetMapName(), instance.GetInstanceId(), encounter.dbcEntry.Name[Global.WorldMgr.GetDefaultDbcLocale()], dungeonId); + break; + } + } + } + + if (dungeonId != 0) + { + var players = instance.GetPlayers(); + foreach (var player in players) + { + Group grp = player.GetGroup(); + if (grp != null) + if (grp.isLFGGroup()) + { + Global.LFGMgr.FinishDungeon(grp.GetGUID(), dungeonId); + return; + } + } + } + } + + void UpdatePhasing() + { + var players = instance.GetPlayers(); + foreach (var player in players) + player.SendUpdatePhasing(); + } + + public void UpdateCombatResurrection(uint diff) + { + if (!_combatResurrectionTimerStarted) + return; + + _combatResurrectionTimer -= diff; + if (_combatResurrectionTimer <= 0) + { + AddCombatResurrectionCharge(); + _combatResurrectionTimerStarted = false; + } + } + + void InitializeCombatResurrections(byte charges = 1, uint interval = 0) + { + _combatResurrectionCharges = charges; + if (interval == 0) + return; + + _combatResurrectionTimer = interval; + _combatResurrectionTimerStarted = true; + } + + public void AddCombatResurrectionCharge() + { + ++_combatResurrectionCharges; + _combatResurrectionTimer = GetCombatResurrectionChargeInterval(); + _combatResurrectionTimerStarted = true; + + var gainCombatResurrectionCharge = new InstanceEncounterGainCombatResurrectionCharge(); + gainCombatResurrectionCharge.InCombatResCount = _combatResurrectionCharges; + gainCombatResurrectionCharge.CombatResChargeRecovery = _combatResurrectionTimer; + instance.SendToPlayers(gainCombatResurrectionCharge); + } + + public void UseCombatResurrection() + { + --_combatResurrectionCharges; + + instance.SendToPlayers(new InstanceEncounterInCombatResurrection()); + } + + public void ResetCombatResurrections() + { + _combatResurrectionCharges = 0; + _combatResurrectionTimer = 0; + _combatResurrectionTimerStarted = false; + } + + public uint GetCombatResurrectionChargeInterval() + { + uint interval = 0; + int playerCount = instance.GetPlayers().Count; + if (playerCount != 0) + interval = (uint)(90 * Time.Minute * Time.InMilliseconds / playerCount); + + return interval; + } + + bool InstanceHasScript(WorldObject obj, string scriptName) + { + InstanceMap instance = obj.GetMap().ToInstanceMap(); + if (instance != null) + return instance.GetScriptName() == scriptName; + + return false; + } + + public virtual void Initialize() { } + public virtual void Update(uint diff) { } + public virtual void OnPlayerEnter(Player player) { } + + // Return wether server allow two side groups or not + public bool ServerAllowsTwoSideGroups() { return WorldConfig.GetBoolValue(WorldCfg.AllowTwoSideInteractionGroup); } + + public EncounterState GetBossState(uint id) { return id < bosses.Count ? bosses[id].state : EncounterState.ToBeDecided; } + public List GetBossBoundary(uint id) { return id < bosses.Count ? bosses[id].boundary : null; } + + public virtual bool CheckRequiredBosses(uint bossId, Player player = null) { return true; } + + public void SetCompletedEncountersMask(uint newMask) { completedEncounters = newMask; } + + public uint GetCompletedEncounterMask() { return completedEncounters; } + + // Sets a temporary entrance that does not get saved to db + void SetTemporaryEntranceLocation(uint worldSafeLocationId) { _temporaryEntranceId = worldSafeLocationId; } + + // Get's the current entrance id + public uint GetEntranceLocation() { return _temporaryEntranceId != 0 ? _temporaryEntranceId : _entranceId; } + + public virtual void FillInitialWorldStates(InitWorldStates data) { } + + public int GetEncounterCount() { return bosses.Count; } + + public byte GetCombatResurrectionCharges() { return _combatResurrectionCharges; } + + public void SetBossNumber(uint number) + { + for (uint i = 0; i < number; ++i) + bosses.Add(i, new BossInfo()); + } + + public void OUT_SAVE_INST_DATA() { Log.outDebug(LogFilter.Scripts, "Saving Instance Data for Instance {0} (Map {1}, Instance Id {2})", instance.GetMapName(), instance.GetId(), instance.GetInstanceId()); } + public void OUT_SAVE_INST_DATA_COMPLETE() { Log.outDebug(LogFilter.Scripts, "Saving Instance Data for Instance {0} (Map {1}, Instance Id {2}) completed.", instance.GetMapName(), instance.GetId(), instance.GetInstanceId()); } + public void OUT_LOAD_INST_DATA(string input) { Log.outDebug(LogFilter.Scripts, "Loading Instance Data for Instance {0} (Map {1}, Instance Id {2}). Input is '{3}'", instance.GetMapName(), instance.GetId(), instance.GetInstanceId(), input); } + public void OUT_LOAD_INST_DATA_COMPLETE() { Log.outDebug(LogFilter.Scripts, "Instance Data Load for Instance {0} (Map {1}, Instance Id: {2}) is complete.", instance.GetMapName(), instance.GetId(), instance.GetInstanceId()); } + public void OUT_LOAD_INST_DATA_FAIL() { Log.outDebug(LogFilter.Scripts, "Unable to load Instance Data for Instance {0} (Map {1}, Instance Id: {2}).", instance.GetMapName(), instance.GetId(), instance.GetInstanceId()); } + + public virtual void ReadSaveDataMore(StringArguments data) { } + + public virtual void WriteSaveDataMore(StringBuilder data) { } + + public Map instance; + List headers = new List(); + Dictionary bosses = new Dictionary(); + MultiMap doors = new MultiMap(); + Dictionary minions = new Dictionary(); + Dictionary _creatureInfo = new Dictionary(); + Dictionary _gameObjectInfo = new Dictionary(); + Dictionary _objectGuids = new Dictionary(); + uint completedEncounters; + uint _entranceId; + uint _temporaryEntranceId; + uint _combatResurrectionTimer; + byte _combatResurrectionCharges; // the counter for available battle resurrections + bool _combatResurrectionTimerStarted; + } + + + public class DoorData + { + public DoorData(uint _entry, uint _bossid, DoorType _type) + { + entry = _entry; + bossId = _bossid; + type = _type; + } + + public uint entry; + public uint bossId; + public DoorType type; + } + + public class BossBoundaryEntry + { + public BossBoundaryEntry(uint bossId, AreaBoundary boundary) + { + BossId = bossId; + Boundary = boundary; + } + + public uint BossId; + public AreaBoundary Boundary; + } + + public class MinionData + { + public MinionData(uint _entry, uint _bossid) + { + entry = _entry; + bossId = _bossid; + } + + public uint entry; + public uint bossId; + } + + public struct ObjectData + { + public ObjectData(uint _entry, uint _type) + { + entry = _entry; + type = _type; + } + + public uint entry; + public uint type; + } + + public class BossInfo + { + public BossInfo() + { + state = EncounterState.ToBeDecided; + for (var i = 0; i < (int)DoorType.Max; ++i) + door[i] = new List(); + } + + public EncounterState state; + public List[] door = new List[(int)DoorType.Max]; + public List minion = new List(); + public List boundary = new List(); + } + class DoorInfo + { + public DoorInfo(BossInfo _bossInfo, DoorType _type) + { + bossInfo = _bossInfo; + type = _type; + } + + public BossInfo bossInfo; + public DoorType type; + } + class MinionInfo + { + public MinionInfo(BossInfo _bossInfo) + { + bossInfo = _bossInfo; + } + + public BossInfo bossInfo; + } +} diff --git a/Game/Maps/Instances/MapInstance.cs b/Game/Maps/Instances/MapInstance.cs new file mode 100644 index 000000000..6c16d2253 --- /dev/null +++ b/Game/Maps/Instances/MapInstance.cs @@ -0,0 +1,307 @@ +/* + * Copyright (C) 2012-2017 CypherCore + * + * 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, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +using Framework.Constants; +using Game.BattleGrounds; +using Game.DataStorage; +using Game.Entities; +using Game.Garrisons; +using Game.Groups; +using Game.Scenarios; +using System.Collections.Generic; +using System.Diagnostics.Contracts; +using System.Linq; + +namespace Game.Maps +{ + public class MapInstanced : Map + { + public MapInstanced(uint id, uint expiry) : base(id, expiry, 0, Difficulty.Normal) + { + for (uint x = 0; x < MapConst.MaxGrids; ++x) + GridMapReference[x] = new uint[MapConst.MaxGrids]; + } + + public override void InitVisibilityDistance() + { + if (m_InstancedMaps.Empty()) + return; + //initialize visibility distances for all instance copies + foreach (var i in m_InstancedMaps) + i.Value.InitVisibilityDistance(); + } + + public override void Update(uint diff) + { + base.Update(diff); + + foreach (var i in m_InstancedMaps.ToList()) + { + if (i.Value.CanUnload(diff)) + { + if (!DestroyInstance(i)) + { + //m_unloadTimer + } + } + else + i.Value.Update(diff); + } + } + + public override void DelayedUpdate(uint t_diff) + { + foreach (var i in m_InstancedMaps) + i.Value.DelayedUpdate(t_diff); + + base.DelayedUpdate(t_diff); + } + + public override void UnloadAll() + { + // Unload instanced maps + foreach (var i in m_InstancedMaps) + i.Value.UnloadAll(); + + m_InstancedMaps.Clear(); + + base.UnloadAll(); + } + + public Map CreateInstanceForPlayer(uint mapId, Player player, uint loginInstanceId = 0) + { + if (GetId() != mapId || player == null) + return null; + + Map map = null; + uint newInstanceId = 0; // instanceId of the resulting map + + if (IsBattlegroundOrArena()) + { + // instantiate or find existing bg map for player + // the instance id is set in Battlegroundid + newInstanceId = player.GetBattlegroundId(); + if (newInstanceId == 0) + return null; + + map = Global.MapMgr.FindMap(mapId, newInstanceId); + if (map == null) + { + Battleground bg = player.GetBattleground(); + if (bg) + map = CreateBattleground(newInstanceId, bg); + else + { + player.TeleportToBGEntryPoint(); + return null; + } + } + } + else if(!IsGarrison()) + { + InstanceBind pBind = player.GetBoundInstance(GetId(), player.GetDifficultyID(GetEntry())); + InstanceSave pSave = pBind != null ? pBind.save : null; + + // priority: + // 1. player's permanent bind + // 2. player's current instance id if this is at login + // 3. group's current bind + // 4. player's current bind + if (pBind == null || !pBind.perm) + { + if (loginInstanceId != 0) // if the player has a saved instance id on login, we either use this instance or relocate him out (return null) + { + map = FindInstanceMap(loginInstanceId); + return (map && map.GetId() == GetId()) ? map : null; // is this check necessary? or does MapInstanced only find instances of itself? + } + + InstanceBind groupBind = null; + Group group = player.GetGroup(); + // use the player's difficulty setting (it may not be the same as the group's) + if (group) + { + groupBind = group.GetBoundInstance(this); + if (groupBind != null) + { + // solo saves should be reset when entering a group's instance + player.UnbindInstance(GetId(), player.GetDifficultyID(GetEntry())); + pSave = groupBind.save; + } + } + } + if (pSave != null) + { + // solo/perm/group + newInstanceId = pSave.GetInstanceId(); + map = FindInstanceMap(newInstanceId); + // it is possible that the save exists but the map doesn't + if (map == null) + map = CreateInstance(newInstanceId, pSave, pSave.GetDifficultyID(), player.GetTeamId()); + } + else + { + // if no instanceId via group members or instance saves is found + // the instance will be created for the first time + newInstanceId = Global.MapMgr.GenerateInstanceId(); + + Difficulty diff = player.GetGroup() != null ? player.GetGroup().GetDifficultyID(GetEntry()) : player.GetDifficultyID(GetEntry()); + //Seems it is now possible, but I do not know if it should be allowed + map = FindInstanceMap(newInstanceId); + if (map == null) + map = CreateInstance(newInstanceId, null, diff, player.GetTeamId()); + } + } + else + { + newInstanceId = (uint)player.GetGUID().GetCounter(); + map = FindInstanceMap(newInstanceId); + if (!map) + map = CreateGarrison(newInstanceId, player); + } + + return map; + } + + InstanceMap CreateInstance(uint InstanceId, InstanceSave save, Difficulty difficulty, int teamId) + { + // make sure we have a valid map id + MapRecord entry = CliDB.MapStorage.LookupByKey(GetId()); + if (entry == null) + { + Log.outError(LogFilter.Maps, "CreateInstance: no record for map {0}", GetId()); + Contract.Assert(false); + } + InstanceTemplate iTemplate = Global.ObjectMgr.GetInstanceTemplate(GetId()); + if (iTemplate == null) + { + Log.outError(LogFilter.Maps, "CreateInstance: no instance template for map {0}", GetId()); + Contract.Assert(false); + } + + // some instances only have one difficulty + Global.DB2Mgr.GetDownscaledMapDifficultyData(GetId(), ref difficulty); + + Log.outDebug(LogFilter.Maps, "MapInstanced.CreateInstance: {0} map instance {1} for {2} created with difficulty {3}", save != null ? "" : "new ", InstanceId, GetId(), difficulty); + + InstanceMap map = new InstanceMap(GetId(), GetGridExpiry(), InstanceId, difficulty, this); + Contract.Assert(map.IsDungeon()); + + map.LoadRespawnTimes(); + map.LoadCorpseData(); + + bool load_data = save != null; + map.CreateInstanceData(load_data); + InstanceScenario instanceScenario = Global.ScenarioMgr.CreateInstanceScenario(map, teamId); + if (instanceScenario != null) + map.SetInstanceScenario(instanceScenario); + + if (WorldConfig.GetBoolValue(WorldCfg.InstancemapLoadGrids)) + map.LoadAllCells(); + + m_InstancedMaps[InstanceId] = map; + return map; + } + + BattlegroundMap CreateBattleground(uint InstanceId, Battleground bg) + { + Log.outDebug(LogFilter.Maps, "MapInstanced.CreateBattleground: map bg {0} for {1} created.", InstanceId, GetId()); + + BattlegroundMap map = new BattlegroundMap(GetId(), (uint)GetGridExpiry(), InstanceId, this, Difficulty.None); + Contract.Assert(map.IsBattlegroundOrArena()); + map.SetBG(bg); + bg.SetBgMap(map); + + m_InstancedMaps[InstanceId] = map; + return map; + } + + GarrisonMap CreateGarrison(uint instanceId, Player owner) + { + GarrisonMap map = new GarrisonMap(GetId(), GetGridExpiry(), instanceId, this, owner.GetGUID()); + Contract.Assert(map.IsGarrison()); + + m_InstancedMaps[instanceId] = map; + return map; + } + + bool DestroyInstance(KeyValuePair pair) + { + pair.Value.RemoveAllPlayers(); + if (pair.Value.HavePlayers()) + return false; + + pair.Value.UnloadAll(); + // should only unload VMaps if this is the last instance and grid unloading is enabled + if (m_InstancedMaps.Count <= 1 && WorldConfig.GetBoolValue(WorldCfg.GridUnload)) + { + Global.VMapMgr.unloadMap(pair.Value.GetId()); + Global.MMapMgr.unloadMap(pair.Value.GetId()); + // in that case, unload grids of the base map, too + // so in the next map creation, (EnsureGridCreated actually) VMaps will be reloaded + base.UnloadAll(); + } + + // Free up the instance id and allow it to be reused for bgs and arenas (other instances are handled in the InstanceSaveMgr) + if (pair.Value.IsBattlegroundOrArena()) + Global.MapMgr.FreeInstanceId(pair.Value.GetInstanceId()); + + // erase map + m_InstancedMaps.Remove(pair.Key); + + return true; + } + + public override EnterState CannotEnter(Player player) { return EnterState.CanEnter; } + + public Map FindInstanceMap(uint instanceId) + { + return m_InstancedMaps.LookupByKey(instanceId); + } + + public void AddGridMapReference(GridCoord p) + { + ++GridMapReference[p.x_coord][p.y_coord]; + SetUnloadReferenceLock(new GridCoord(63 - p.x_coord, 63 - p.y_coord), true); + } + + public void RemoveGridMapReference(GridCoord p) + { + --GridMapReference[p.x_coord][p.y_coord]; + if (GridMapReference[p.x_coord][p.y_coord] == 0) + SetUnloadReferenceLock(new GridCoord(63 - p.x_coord, 63 - p.y_coord), false); + } + + public Dictionary GetInstancedMaps() { return m_InstancedMaps; } + + Dictionary m_InstancedMaps = new Dictionary(); + uint[][] GridMapReference = new uint[MapConst.MaxGrids][]; + } + + public class InstanceTemplate + { + public uint Parent; + public uint ScriptId; + public bool AllowMount; + } + + public class InstanceBind + { + public InstanceSave save; + public bool perm; + public BindExtensionState extendState; + } +} diff --git a/Game/Maps/MMapManager.cs b/Game/Maps/MMapManager.cs new file mode 100644 index 000000000..9d62e8cc9 --- /dev/null +++ b/Game/Maps/MMapManager.cs @@ -0,0 +1,581 @@ +/* + * Copyright (C) 2012-2017 CypherCore + * + * 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, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +using Framework.Constants; +using Game.DataStorage; +using System; +using System.Collections.Generic; +using System.Diagnostics.Contracts; +using System.IO; +using System.Linq; +using System.Runtime.InteropServices; +using System.Text; + +namespace Game +{ + public class MMapManager : Singleton + { + MMapManager() { } + + const string MAP_FILE_NAME_FORMAT = "{0}/mmaps/{1:D4}.mmap"; + const string TILE_FILE_NAME_FORMAT = "{0}/mmaps/{1:D4}{2:D2}{3:D2}.mmtile"; + + public void Initialize() + { + foreach (MapRecord mapEntry in CliDB.MapStorage.Values) + { + if (mapEntry.ParentMapID != -1) + phaseMapData.Add((uint)mapEntry.ParentMapID, mapEntry.Id); + } + } + + MMapData GetMMapData(uint mapId) + { + return loadedMMaps.LookupByKey(mapId); + } + + bool loadMapData(uint mapId) + { + // we already have this map loaded? + if (loadedMMaps.ContainsKey(mapId) && loadedMMaps[mapId] != null) + return true; + + // load and init dtNavMesh - read parameters from file + string filename = string.Format(MAP_FILE_NAME_FORMAT, Global.WorldMgr.GetDataPath(), mapId); + if (!File.Exists(filename)) + { + Log.outError(LogFilter.Maps, "Could not open mmap file {0}", filename); + return false; + } + + using (BinaryReader reader = new BinaryReader(new FileStream(filename, FileMode.Open, FileAccess.Read), Encoding.UTF8)) + { + Detour.dtNavMeshParams Params = new Detour.dtNavMeshParams(); + Params.orig[0] = reader.ReadSingle(); + Params.orig[1] = reader.ReadSingle(); + Params.orig[2] = reader.ReadSingle(); + + Params.tileWidth = reader.ReadSingle(); + Params.tileHeight = reader.ReadSingle(); + Params.maxTiles = reader.ReadInt32(); + Params.maxPolys = reader.ReadInt32(); + + Detour.dtNavMesh mesh = new Detour.dtNavMesh(); + if (Detour.dtStatusFailed(mesh.init(Params))) + { + Log.outError(LogFilter.Maps, "MMAP:loadMapData: Failed to initialize dtNavMesh for mmap {0:D4} from file {1}", mapId, filename); + return false; + } + + Log.outInfo(LogFilter.Maps, "MMAP:loadMapData: Loaded {0:D4}.mmap", mapId); + + // store inside our map list + loadedMMaps[mapId] = new MMapData(mesh, mapId); + return true; + } + } + + uint packTileID(int x, int y) + { + return (uint)(x << 16 | y); + } + + public bool loadMap(uint mapId, int x, int y) + { + // make sure the mmap is loaded and ready to load tiles + if (!loadMapData(mapId)) + return false; + + // get this mmap data + MMapData mmap = loadedMMaps[mapId]; + Contract.Assert(mmap.navMesh != null); + + // check if we already have this tile loaded + uint packedGridPos = packTileID(x, y); + if (mmap.loadedTileRefs.ContainsKey(packedGridPos)) + return false; + + // load this tile . mmaps/MMMXXYY.mmtile + string filename = string.Format(TILE_FILE_NAME_FORMAT, Global.WorldMgr.GetDataPath(), mapId, x, y); + if (!File.Exists(filename)) + { + Log.outDebug(LogFilter.Maps, "MMAP:loadMap: Could not open mmtile file '{0}'", filename); + return false; + } + + using (BinaryReader reader = new BinaryReader(new FileStream(filename, FileMode.Open, FileAccess.Read))) + { + MmapTileHeader fileHeader = reader.ReadStruct(); + Array.Reverse(fileHeader.mmapMagic); + if (new string(fileHeader.mmapMagic) != MapConst.mmapMagic) + { + Log.outError(LogFilter.Maps, "MMAP:loadMap: Bad header in mmap {0:D4}{1:D2}{2:D2}.mmtile", mapId, x, y); + return false; + } + if (fileHeader.mmapVersion != MapConst.mmapVersion) + { + Log.outError(LogFilter.Maps, "MMAP:loadMap: {0:D4}{1:D2}{2:D2}.mmtile was built with generator v{3}, expected v{4}", + mapId, x, y, fileHeader.mmapVersion, MapConst.mmapVersion); + return false; + } + + var bytes = reader.ReadBytes((int)fileHeader.size); + + Detour.dtRawTileData data = new Detour.dtRawTileData(); + data.FromBytes(bytes, 0); + + ulong tileRef = 0; + // memory allocated for data is now managed by detour, and will be deallocated when the tile is removed + if (Detour.dtStatusSucceed(mmap.navMesh.addTile(data, 0, 0, ref tileRef))) + { + mmap.loadedTileRefs.Add(packedGridPos, tileRef); + ++loadedTiles; + Log.outInfo(LogFilter.Maps, "MMAP:loadMap: Loaded mmtile {0:D4}[{1:D2}, {2:D2}]", mapId, x, y); + + var phasedMaps = phaseMapData.LookupByKey(mapId); + if (!phasedMaps.Empty()) + { + mmap.AddBaseTile(packedGridPos, data, fileHeader, fileHeader.size); + LoadPhaseTiles(phasedMaps, x, y); + } + + return true; + } + + Log.outError(LogFilter.Maps, "MMAP:loadMap: Could not load {0:D4}{1:D2}{2:D2}.mmtile into navmesh", mapId, x, y); + return false; + } + } + + PhasedTile LoadTile(uint mapId, int x, int y) + { + // load this tile . mmaps/MMMXXYY.mmtile + string filename = string.Format(TILE_FILE_NAME_FORMAT, Global.WorldMgr.GetDataPath(), mapId, x, y); + if (!File.Exists(filename)) + { + // Not all tiles have phased versions, don't flood this msg + return null; + } + PhasedTile pTile = new PhasedTile(); + + using (BinaryReader reader = new BinaryReader(new FileStream(filename, FileMode.Open, FileAccess.Read))) + { + // read header + pTile.fileHeader = reader.ReadStruct(); + Array.Reverse(pTile.fileHeader.mmapMagic); + if (new string(pTile.fileHeader.mmapMagic) != MapConst.mmapMagic) + { + Log.outError(LogFilter.Maps, "MMAP.LoadTile: Bad header in mmap {0:D4}{1:D2}{2:D2}.mmtile", mapId, x, y); + return null; + } + + if (pTile.fileHeader.mmapVersion != MapConst.mmapVersion) + { + Log.outError(LogFilter.Maps, "MMAP:LoadTile: {0:D4}{1:D2}{2:D2}.mmtile was built with generator v{3}, expected v{4}", mapId, x, y, pTile.fileHeader.mmapVersion, MapConst.mmapVersion); + return null; + } + + pTile.data = new Detour.dtRawTileData(); + pTile.data.FromBytes(reader.ReadBytes((int)pTile.fileHeader.size), 0); + + if (pTile.data.ToBytes().Length == 0) + { + Log.outError(LogFilter.Maps, "MMAP.LoadTile: Bad header or data in mmap {0:D4}{1:D2}{2:D2}.mmtile", mapId, x, y); + return null; + } + } + + return pTile; + } + + void LoadPhaseTiles(List phasedMapData, int x, int y) + { + Log.outDebug(LogFilter.Maps, "MMAP.LoadPhaseTiles: Loading phased mmtiles for map {0}, X: {1}, Y: {2}", phasedMapData.FirstOrDefault(), x, y); + + uint packedGridPos = packTileID(x, y); + + foreach (uint phaseMapId in phasedMapData) + { + PhasedTile data = LoadTile(phaseMapId, x, y); + // only a few tiles have terrain swaps, do not write error for them + if (data != null) + { + Log.outDebug(LogFilter.Maps, "MMAP.LoadPhaseTiles: Loaded phased {0:D4}{1:D2}{2:D2}.mmtile for root phase map {3}", phaseMapId, x, y, phasedMapData.FirstOrDefault()); + if (!_phaseTiles.ContainsKey(phaseMapId)) + _phaseTiles[phaseMapId] = new Dictionary(); + + _phaseTiles[phaseMapId][packedGridPos] = data; + } + } + } + + void UnloadPhaseTile(List phasedMapData, int x, int y) + { + Log.outDebug(LogFilter.Maps, "MMAP.UnloadPhaseTile: Unloading phased mmtile for map {0}, X: {1}, Y: {2}", phasedMapData.FirstOrDefault(), x, y); + + uint packedGridPos = packTileID(x, y); + + foreach (uint phaseMapId in phasedMapData.ToList()) + { + var phasedTileDic = _phaseTiles.LookupByKey(phaseMapId); + if (phasedTileDic == null) + continue; + + var phaseTile = phasedTileDic.LookupByKey(packedGridPos); + if (phaseTile != null) + { + Log.outDebug(LogFilter.Maps, "MMAP.UnloadPhaseTile: Unloaded phased {0:D4}{1:D2}{2:D2}.mmtile for root phase map {3}", phaseMapId, x, y, phasedMapData.FirstOrDefault()); + phasedTileDic.Remove(packedGridPos); + } + } + } + + public bool unloadMap(uint mapId, uint x, uint y) + { + // check if we have this map loaded + MMapData mmap = GetMMapData(mapId); + if (mmap == null) + { + // file may not exist, therefore not loaded + Log.outDebug(LogFilter.Maps, "MMAP:unloadMap: Asked to unload not loaded navmesh map. {0:D4}{1:D2}{2:D2}.mmtile", mapId, x, y); + return false; + } + + // check if we have this tile loaded + uint packedGridPos = packTileID((int)x, (int)y); + if (!mmap.loadedTileRefs.ContainsKey(packedGridPos)) + { + // file may not exist, therefore not loaded + Log.outDebug(LogFilter.Maps, "MMAP:unloadMap: Asked to unload not loaded navmesh tile. {0:D4}{1:D2}{2:D2}.mmtile", mapId, x, y); + return false; + } + + ulong tileRef = mmap.loadedTileRefs.LookupByKey(packedGridPos); + + // unload, and mark as non loaded + Detour.dtRawTileData data; + if (Detour.dtStatusFailed(mmap.navMesh.removeTile(tileRef, out data))) + { + // this is technically a memory leak + // if the grid is later reloaded, dtNavMesh.addTile will return error but no extra memory is used + // we cannot recover from this error - assert out + Log.outError(LogFilter.Maps, "MMAP:unloadMap: Could not unload {0:D4}{1:D2}{2:D2}.mmtile from navmesh", mapId, x, y); + Contract.Assert(false); + } + else + { + mmap.loadedTileRefs.Remove(packedGridPos); + --loadedTiles; + Log.outInfo(LogFilter.Maps, "MMAP:unloadMap: Unloaded mmtile {0:D4}[{1:D2}, {2:D2}] from {3:D4}", mapId, x, y, mapId); + + var phasedMaps = phaseMapData.LookupByKey(mapId); + if (!phasedMaps.Empty()) + { + mmap.DeleteBaseTile(packedGridPos); + UnloadPhaseTile(phasedMaps, (int)x, (int)y); + } + return true; + } + + return false; + } + + public bool unloadMap(uint mapId) + { + if (!loadedMMaps.ContainsKey(mapId)) + { + // file may not exist, therefore not loaded + Log.outDebug(LogFilter.Maps, "MMAP:unloadMap: Asked to unload not loaded navmesh map {0:D4}", mapId); + return false; + } + + // unload all tiles from given map + MMapData mmap = loadedMMaps.LookupByKey(mapId); + foreach (var i in mmap.loadedTileRefs) + { + uint x = (i.Key >> 16); + uint y = (i.Key & 0x0000FFFF); + Detour.dtRawTileData data; + if (Detour.dtStatusFailed(mmap.navMesh.removeTile(i.Value, out data))) + Log.outError(LogFilter.Maps, "MMAP:unloadMap: Could not unload {0:D4}{1:D2}{2:D2}.mmtile from navmesh", mapId, x, y); + else + { + var phasedMaps = phaseMapData.LookupByKey(mapId); + if (!phasedMaps.Empty()) + { + mmap.DeleteBaseTile(i.Key); + UnloadPhaseTile(phasedMaps, (int)x, (int)y); + } + --loadedTiles; + Log.outInfo(LogFilter.Maps, "MMAP:unloadMap: Unloaded mmtile {0:D4} [{1:D2}, {2:D2}] from {3:D4}", mapId, x, y, mapId); + } + } + + loadedMMaps.Remove(mapId); + Log.outInfo(LogFilter.Maps, "MMAP:unloadMap: Unloaded {0:D4}.mmap", mapId); + + return true; + } + + public bool unloadMapInstance(uint mapId, uint instanceId) + { + // check if we have this map loaded + MMapData mmap = GetMMapData(mapId); + if (mmap == null) + { + // file may not exist, therefore not loaded + Log.outDebug(LogFilter.Maps, "MMAP:unloadMapInstance: Asked to unload not loaded navmesh map {0}", mapId); + return false; + } + + if (!mmap.navMeshQueries.ContainsKey(instanceId)) + { + Log.outDebug(LogFilter.Maps, "MMAP:unloadMapInstance: Asked to unload not loaded dtNavMeshQuery mapId {0} instanceId {1}", mapId, instanceId); + return false; + } + + mmap.navMeshQueries.Remove(instanceId); + Log.outInfo(LogFilter.Maps, "MMAP:unloadMapInstance: Unloaded mapId {0} instanceId {1}", mapId, instanceId); + + return true; + } + + public Detour.dtNavMesh GetNavMesh(uint mapId, List swaps) + { + MMapData mmap = GetMMapData(mapId); + if (mmap == null) + return null; + + return mmap.GetNavMesh(swaps); + } + + public Detour.dtNavMeshQuery GetNavMeshQuery(uint mapId, uint instanceId, List swaps) + { + MMapData mmap = GetMMapData(mapId); + if (mmap == null) + return null; + + if (!mmap.navMeshQueries.ContainsKey(instanceId)) + { + // allocate mesh query + Detour.dtNavMeshQuery query = new Detour.dtNavMeshQuery(); + if (Detour.dtStatusFailed(query.init(mmap.GetNavMesh(swaps), 1024))) + { + Log.outError(LogFilter.Maps, "MMAP:GetNavMeshQuery: Failed to initialize dtNavMeshQuery for mapId {0} instanceId {1}", mapId, instanceId); + return null; + } + + Log.outInfo(LogFilter.Maps, "MMAP:GetNavMeshQuery: created dtNavMeshQuery for mapId {0} instanceId {1}", mapId, instanceId); + mmap.navMeshQueries.Add(instanceId, query); + } + + return mmap.navMeshQueries[instanceId]; + } + + public uint getLoadedTilesCount() { return loadedTiles; } + public int getLoadedMapsCount() { return loadedMMaps.Count; } + + public Dictionary GetPhaseTileContainer(uint mapId) { return _phaseTiles.LookupByKey(mapId); } + + Dictionary loadedMMaps = new Dictionary(); + MultiMap phaseMapData = new MultiMap(); + Dictionary> _phaseTiles = new Dictionary>(); + uint loadedTiles; + } + + public class MMapData + { + public MMapData(Detour.dtNavMesh mesh, uint mapId) + { + navMesh = mesh; + _mapId = mapId; + } + + void RemoveSwap(PhasedTile ptile, uint swap, uint packedXY) + { + uint x = (packedXY >> 16); + uint y = (packedXY & 0x0000FFFF); + + if (!loadedPhasedTiles[swap].Contains(packedXY)) + { + Log.outDebug(LogFilter.Maps, "MMapData.RemoveSwap: mmtile {0:D4}[{1:D2}, {2:D2}] unload skipped, due to not loaded", swap, x, y); + return; + } + Detour.dtMeshHeader header = ptile.data.header; + + Detour.dtRawTileData data; + // remove old tile + if (Detour.dtStatusFailed(navMesh.removeTile(loadedTileRefs[packedXY], out data))) + Log.outError(LogFilter.Maps, "MMapData.RemoveSwap: Could not unload phased {0:D4}{1:D2}{2:D2}.mmtile from navmesh", swap, x, y); + else + { + Log.outDebug(LogFilter.Maps, "MMapData.RemoveSwap: Unloaded phased {0:D4}{1:D2}{2:D2}.mmtile from navmesh", swap, x, y); + + // restore base tile + ulong loadedRef = 0; + if (Detour.dtStatusSucceed(navMesh.addTile(_baseTiles[packedXY].data, 0, 0, ref loadedRef))) + { + Log.outDebug(LogFilter.Maps, "MMapData.RemoveSwap: Loaded base mmtile {0:D4}[{1:D2}, {2:D2}] into {0:D4}[{1:D2}, {2:D2}]", _mapId, x, y, _mapId, header.x, header.y); + } + else + Log.outError(LogFilter.Maps, "MMapData.RemoveSwap: Could not load base {0:D4}{1:D2}{2:D2}.mmtile to navmesh", _mapId, x, y); + + loadedTileRefs[packedXY] = loadedRef; + } + + loadedPhasedTiles.Remove(swap, packedXY); + + if (loadedPhasedTiles[swap].Empty()) + { + _activeSwaps.Remove(swap); + Log.outDebug(LogFilter.Maps, "MMapData.RemoveSwap: Fully removed swap {0} from map {1}", swap, _mapId); + } + } + + void AddSwap(PhasedTile ptile, uint swap, uint packedXY) + { + uint x = (packedXY >> 16); + uint y = (packedXY & 0x0000FFFF); + + if (!loadedTileRefs.ContainsKey(packedXY)) + { + Log.outDebug(LogFilter.Maps, "MMapData.AddSwap: phased mmtile {0:D4}[{1:D2}, {2:D2}] load skipped, due to not loaded base tile on map {3}", swap, x, y, _mapId); + return; + } + if (loadedPhasedTiles[swap].Contains(packedXY)) + { + Log.outDebug(LogFilter.Maps, "MMapData.AddSwap: WARNING! phased mmtile {0:D4}[{1:D2}, {2:D2}] load skipped, due to already loaded on map {3}", swap, x, y, _mapId); + return; + } + + Detour.dtMeshHeader header = ptile.data.header; + + Detour.dtMeshTile oldTile = navMesh.getTileByRef(loadedTileRefs[packedXY]); + if (oldTile == null) + { + Log.outDebug(LogFilter.Maps, "MMapData.AddSwap: phased mmtile {0:D4}[{1:D2}, {2:D2}] load skipped, due to not loaded base tile ref on map {3}", swap, x, y, _mapId); + return; + } + + // header xy is based on the swap map's tile set, wich doesn't have all the same tiles as root map, so copy the xy from the orignal header + header.x = oldTile.header.x; + header.y = oldTile.header.y; + + Detour.dtRawTileData data; + // remove old tile + if (Detour.dtStatusFailed(navMesh.removeTile(loadedTileRefs[packedXY], out data))) + Log.outError(LogFilter.Maps, "MMapData.AddSwap: Could not unload {0:D4}{1:D2}{2:D2}.mmtile from navmesh", _mapId, x, y); + else + { + Log.outDebug(LogFilter.Maps, "MMapData.AddSwap: Unloaded {0:D4}{1:D2}{2:D2}.mmtile from navmesh", _mapId, x, y); + + _activeSwaps.Add(swap); + loadedPhasedTiles.Add(swap, packedXY); + + // add new swapped tile + ulong loadedRef = 0; + if (Detour.dtStatusSucceed(navMesh.addTile(ptile.data, 0, 0, ref loadedRef))) + Log.outDebug(LogFilter.Maps, "MMapData.AddSwap: Loaded phased mmtile {0:D4}[{1:D2}, {2:D2}] into {0:D4}[{1:D2}, {2:D2}]", swap, x, y, _mapId, header.x, header.y); + else + Log.outError(LogFilter.Maps, "MMapData.AddSwap: Could not load {0:D4}{1:D2}{2:D2}.mmtile to navmesh", swap, x, y); + + loadedTileRefs[packedXY] = loadedRef; + } + } + + public Detour.dtNavMesh GetNavMesh(List swaps) + { + foreach (uint swap in _activeSwaps) + { + if (!swaps.Contains(swap)) // swap not active + { + var ptc = Global.MMapMgr.GetPhaseTileContainer(swap); + foreach (var pair in ptc) + RemoveSwap(pair.Value, swap, pair.Key); // remove swap + } + } + + // for each of the calling unit's terrain swaps + foreach (uint swap in swaps) + { + if (!_activeSwaps.Contains(swap)) // swap not active + { + // for each of the terrain swap's xy tiles + var ptc = Global.MMapMgr.GetPhaseTileContainer(swap); + if (ptc != null) + { + foreach (var pair in ptc) + AddSwap(pair.Value, swap, pair.Key); // add swap + } + } + } + + return navMesh; + } + + public void AddBaseTile(uint packedGridPos, Detour.dtRawTileData data, MmapTileHeader fileHeader, uint dataSize) + { + if (!_baseTiles.ContainsKey(packedGridPos)) + { + PhasedTile phasedTile = new PhasedTile(); + phasedTile.data = data; + phasedTile.fileHeader = fileHeader; + phasedTile.dataSize = (int)dataSize; + _baseTiles[packedGridPos] = phasedTile; + } + } + + public void DeleteBaseTile(uint packedGridPos) + { + var phaseTile = _baseTiles.LookupByKey(packedGridPos); + if (phaseTile != null) + { + _baseTiles.Remove(packedGridPos); + } + } + + public Dictionary navMeshQueries = new Dictionary(); // instanceId to query + + public Detour.dtNavMesh navMesh; + public Dictionary loadedTileRefs = new Dictionary(); + MultiMap loadedPhasedTiles = new MultiMap(); + + uint _mapId; + Dictionary _baseTiles = new Dictionary(); + List _activeSwaps = new List(); + } + + public class PhasedTile + { + public Detour.dtRawTileData data; + public MmapTileHeader fileHeader; + public int dataSize; + } + + [StructLayout(LayoutKind.Sequential)] + public struct MmapTileHeader + { + [MarshalAs(UnmanagedType.ByValArray, SizeConst = 4)] + public char[] mmapMagic; + public uint dtVersion; + public uint mmapVersion; + public uint size; + public byte usesLiquids; + [MarshalAs(UnmanagedType.ByValArray, SizeConst = 3)] + public byte[] padding; + } +} diff --git a/Game/Maps/Map.cs b/Game/Maps/Map.cs new file mode 100644 index 000000000..55f8a038f --- /dev/null +++ b/Game/Maps/Map.cs @@ -0,0 +1,4675 @@ +/* + * Copyright (C) 2012-2017 CypherCore + * + * 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, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +using Framework.Constants; +using Framework.Database; +using Framework.GameMath; +using Game.BattleGrounds; +using Game.Collision; +using Game.Combat; +using Game.DataStorage; +using Game.Entities; +using Game.Groups; +using Game.Network; +using Game.Network.Packets; +using Game.Scenarios; +using System; +using System.Collections; +using System.Collections.Generic; +using System.Diagnostics.Contracts; +using System.IO; +using System.Linq; + +namespace Game.Maps +{ + public class Map + { + public Map(uint id, long expiry, uint instanceId, Difficulty spawnmode, Map parent = null) + { + i_mapRecord = CliDB.MapStorage.LookupByKey(id); + i_spawnMode = spawnmode; + i_InstanceId = instanceId; + m_VisibleDistance = SharedConst.DefaultVisibilityDistance; + m_VisibilityNotifyPeriod = SharedConst.DefaultVisibilityNotifyPeriod; + i_gridExpiry = expiry; + _defaultLight = Global.DB2Mgr.GetDefaultMapLight(id); + + m_parentMap = parent ?? this; + + for (uint x = 0; x < MapConst.MaxGrids; ++x) + { + i_grids[x] = new Grid[MapConst.MaxGrids]; + GridMaps[x] = new GridMap[MapConst.MaxGrids]; + for (uint y = 0; y < MapConst.MaxGrids; ++y) + { + //z code + GridMaps[x][y] = null; + setGrid(null, x, y); + } + } + + //lets initialize visibility distance for map + InitVisibilityDistance(); + + GetGuidSequenceGenerator(HighGuid.Transport).Set(Global.ObjectMgr.GetGenerator(HighGuid.Transport).GetNextAfterMaxUsed()); + + Global.ScriptMgr.OnCreateMap(this); + } + + public static bool ExistMap(uint mapid, uint gx, uint gy) + { + string fileName = string.Format("{0}/maps/{1:D4}_{2:D2}_{3:D2}.map", Global.WorldMgr.GetDataPath(), mapid, gx, gy); + if (!File.Exists(fileName)) + { + Log.outError(LogFilter.Maps, "Map file '{0}': does not exist!", fileName); + return false; + } + + using (var reader = new BinaryReader(new FileStream(fileName, FileMode.Open, FileAccess.Read))) + { + var header = reader.ReadStruct(); + if (new string(header.mapMagic) != MapConst.MapMagic || new string(header.versionMagic) != MapConst.MapVersionMagic) + { + Log.outError(LogFilter.Maps, + "Map file '{0}' is from an incompatible map version ({1}), {2} is expected. Please recreate using the mapextractor.", + fileName, Convert.ToString(header.versionMagic), MapConst.MapVersionMagic); + return false; + } + return true; + } + } + + public static bool ExistVMap(uint mapid, uint gx, uint gy) + { + if (Global.VMapMgr.isMapLoadingEnabled()) + { + bool exists = Global.VMapMgr.existsMap(mapid, gx, gy); + if (!exists) + { + string name = VMapManager.getMapFileName(mapid); + Log.outError(LogFilter.Maps, "VMap file '{0}' is missing or points to wrong version of vmap file. Redo vmaps with latest version of vmap_assembler.exe.", + Global.WorldMgr.GetDataPath() + "vmaps/" + name); + return false; + } + } + return true; + } + + void LoadMMap(uint gx, uint gy) + { + if (Global.MMapMgr.loadMap(GetId(), (int)gx, (int)gy)) + Log.outInfo(LogFilter.Maps, "MMAP loaded name:{0}, id:{1}, x:{2}, y:{3} (mmap rep.: x:{4}, y:{5})", GetMapName(), GetId(), gx, gy, gx, gy); + else + Log.outInfo(LogFilter.Maps, "Could not load MMAP name:{0}, id:{1}, x:{2}, y:{3} (mmap rep.: x:{4}, y:{5})", GetMapName(), GetId(), gx, gy, gx, gy); + } + + void LoadVMap(uint gx, uint gy) + { + // x and y are swapped !! + VMAPLoadResult vmapLoadResult = Global.VMapMgr.loadMap(GetId(), gx, gy); + switch (vmapLoadResult) + { + case VMAPLoadResult.OK: + Log.outInfo(LogFilter.Maps, "VMAP loaded name:{0}, id:{1}, x:{2}, y:{3} (vmap rep.: x:{4}, y:{5})", GetMapName(), GetId(), gx, gy, gx, gy); + break; + case VMAPLoadResult.Error: + Log.outInfo(LogFilter.Maps, "Could not load VMAP name:{0}, id:{1}, x:{2}, y:{3} (vmap rep.: x:{4}, y:{5})", GetMapName(), GetId(), gx, gy, gx, gy); + break; + case VMAPLoadResult.Ignored: + Log.outDebug(LogFilter.Maps, "Ignored VMAP name:{0}, id:{1}, x:{2}, y:{3} (vmap rep.: x:{4}, y:{5})", GetMapName(), GetId(), gx, gy, gx, gy); + break; + } + } + + void LoadMap(uint gx, uint gy, bool reload = false) + { + if (i_InstanceId != 0) + { + if (GridMaps[gx][gy] != null) + return; + + // load grid map for base map + if (m_parentMap.GridMaps[gx][gy] == null) + m_parentMap.EnsureGridCreated(new GridCoord(63 - gx, 63 - gy)); + + ((MapInstanced)m_parentMap).AddGridMapReference(new GridCoord(gx, gy)); + GridMaps[gx][gy] = m_parentMap.GridMaps[gx][gy]; + return; + } + + if (GridMaps[gx][gy] != null && !reload) + return; + + if (GridMaps[gx][gy] != null) + { + Log.outInfo(LogFilter.Maps, "Unloading previously loaded map {0} before reloading.", GetId()); + Global.ScriptMgr.OnUnloadGridMap(this, GridMaps[gx][gy], gx, gy); + + GridMaps[gx][gy] = null; + } + // map file name + string filename = string.Format("{0}/maps/{1:D4}_{2:D2}_{3:D2}.map", Global.WorldMgr.GetDataPath(), GetId(), gx, gy); + Log.outInfo(LogFilter.Maps, "Loading map {0}", filename); + // loading data + GridMaps[gx][gy] = new GridMap(); + if (!GridMaps[gx][gy].loadData(filename)) + Log.outError(LogFilter.Maps, "Error loading map file: {0}", filename); + + Global.ScriptMgr.OnLoadGridMap(this, GridMaps[gx][gy], gx, gy); + } + + void LoadMapAndVMap(uint gx, uint gy) + { + LoadMap(gx, gy); + // Only load the data for the base map + if (i_InstanceId == 0) + { + LoadVMap(gx, gy); + LoadMMap(gx, gy); + } + } + + public void LoadAllCells() + { + for (uint cellX = 0; cellX < MapConst.TotalCellsPerMap; cellX++) + for (uint cellY = 0; cellY < MapConst.TotalCellsPerMap; cellY++) + LoadGrid((cellX + 0.5f - MapConst.CenterGridCellId) * MapConst.SizeofCells, (cellY + 0.5f - MapConst.CenterGridCellId) * MapConst.SizeofCells); + } + + public virtual void InitVisibilityDistance() + { + //init visibility for continents + m_VisibleDistance = Global.WorldMgr.GetMaxVisibleDistanceOnContinents(); + m_VisibilityNotifyPeriod = Global.WorldMgr.GetVisibilityNotifyPeriodOnContinents(); + } + + public void AddToGrid(T obj, Cell cell)where T : WorldObject + { + Grid grid = getGrid(cell.GetGridX(), cell.GetGridY()); + switch (obj.GetTypeId()) + { + case TypeId.Corpse: + if (grid.isGridObjectDataLoaded()) + { + // Corpses are a special object type - they can be added to grid via a call to AddToMap + // or loaded through ObjectGridLoader. + // Both corpses loaded from database and these freshly generated by Player::CreateCoprse are added to _corpsesByCell + // ObjectGridLoader loads all corpses from _corpsesByCell even if they were already added to grid before it was loaded + // so we need to explicitly check it here (Map::AddToGrid is only called from Player::BuildPlayerRepop, not from ObjectGridLoader) + // to avoid failing an assertion in GridObject::AddToGrid + if (obj.IsWorldObject()) + { + obj.SetCurrentCell(cell); + grid.GetGridCell(cell.GetCellX(), cell.GetCellY()).AddWorldObject(obj); + } + else + grid.GetGridCell(cell.GetCellX(), cell.GetCellY()).AddGridObject(obj); + } + return; + case TypeId.GameObject: + case TypeId.AreaTrigger: + case TypeId.DynamicObject: + grid.GetGridCell(cell.GetCellX(), cell.GetCellY()).AddGridObject(obj); + break; + default: + if (obj.IsWorldObject()) + grid.GetGridCell(cell.GetCellX(), cell.GetCellY()).AddWorldObject(obj); + else + grid.GetGridCell(cell.GetCellX(), cell.GetCellY()).AddGridObject(obj); + break; + } + + obj.SetCurrentCell(cell); + } + + public void RemoveFromGrid(WorldObject obj, Cell cell) + { + if (cell == null) + return; + + Grid grid = getGrid(cell.GetGridX(), cell.GetGridY()); + if (grid == null) + return; + + if (obj.IsWorldObject()) + grid.GetGridCell(cell.GetCellX(), cell.GetCellY()).RemoveWorldObject(obj); + else + grid.GetGridCell(cell.GetCellX(), cell.GetCellY()).RemoveGridObject(obj); + + obj.SetCurrentCell(null); + } + + void SwitchGridContainers(WorldObject obj, bool on) + { + if (obj.IsPermanentWorldObject()) + return; + + CellCoord p = GridDefines.ComputeCellCoord(obj.GetPositionX(), obj.GetPositionY()); + if (!p.IsCoordValid()) + { + Log.outError(LogFilter.Maps, "Map.SwitchGridContainers: Object {0} has invalid coordinates X:{1} Y:{2} grid cell [{3}:{4}]", + obj.GetGUID(), obj.GetPositionX(), obj.GetPositionY(), p.x_coord, p.y_coord); + return; + } + + var cell = new Cell(p); + if (!IsGridLoaded(new GridCoord(cell.GetGridX(), cell.GetGridY()))) + return; + + Log.outDebug(LogFilter.Maps, "Switch object {0} from grid[{1}, {2}] {3}", obj.GetGUID(), cell.GetGridX(), cell.GetGridY(), on); + Grid ngrid = getGrid(cell.GetGridX(), cell.GetGridY()); + Contract.Assert(ngrid != null); + + GridCell grid = ngrid.GetGridCell(cell.GetCellX(), cell.GetCellY()); + + RemoveFromGrid(obj, cell); //This step is not really necessary but we want to do ASSERT in remove/add + AddToGrid(obj, cell); + if (on) + { + grid.AddWorldObject(obj); + AddWorldObject(obj); + } + else + { + grid.AddGridObject(obj); + RemoveWorldObject(obj); + } + if (obj.IsTypeId(TypeId.Unit)) + obj.ToCreature().m_isTempWorldObject = on; + } + + void DeleteFromWorld(Player player) + { + Global.ObjAccessor.RemoveObject(player); + RemoveUpdateObject(player); /// @todo I do not know why we need this, it should be removed in ~Object anyway + player.Dispose(); + } + + void DeleteFromWorld(Transport transport) + { + Global.ObjAccessor.RemoveObject(transport); + transport.Dispose(); + } + + void DeleteFromWorld(WorldObject obj) + { + obj.Dispose(); + } + + void EnsureGridCreated(GridCoord p) + { + lock (this) + { + EnsureGridCreated_i(p); + } + } + + void EnsureGridCreated_i(GridCoord p) + { + if (getGrid(p.x_coord, p.y_coord) == null) + { + Log.outDebug(LogFilter.Maps, "Creating grid[{0}, {1}] for map {2} instance {3}", p.x_coord, p.y_coord, + GetId(), i_InstanceId); + + setGrid(new Grid(p.x_coord * MapConst.MaxGrids + p.y_coord, p.x_coord, p.y_coord, i_gridExpiry, WorldConfig.GetBoolValue(WorldCfg.GridUnload)), + p.x_coord, p.y_coord); + + getGrid(p.x_coord, p.y_coord).SetGridState(GridState.Idle); + + //z coord + uint gx = (MapConst.MaxGrids - 1) - p.x_coord; + uint gy = (MapConst.MaxGrids - 1) - p.y_coord; + + if (GridMaps[gx][gy] == null) + LoadMapAndVMap(gx, gy); + } + } + + void EnsureGridLoadedForActiveObject(Cell cell, WorldObject obj) + { + EnsureGridLoaded(cell); + Grid grid = getGrid(cell.GetGridX(), cell.GetGridY()); + + // refresh grid state & timer + if (grid.GetGridState() != GridState.Active) + { + Log.outDebug(LogFilter.Maps, "Active object {0} triggers loading of grid [{1}, {2}] on map {3}", + obj.GetGUID(), cell.GetGridX(), cell.GetGridY(), GetId()); + ResetGridExpiry(grid, 0.1f); + grid.SetGridState(GridState.Active); + } + } + + private bool EnsureGridLoaded(Cell cell) + { + EnsureGridCreated(new GridCoord(cell.GetGridX(), cell.GetGridY())); + Grid grid = getGrid(cell.GetGridX(), cell.GetGridY()); + + if (!isGridObjectDataLoaded(cell.GetGridX(), cell.GetGridY())) + { + Log.outDebug(LogFilter.Maps, "Loading grid[{0}, {1}] for map {2} instance {3}", cell.GetGridX(), + cell.GetGridY(), GetId(), i_InstanceId); + + setGridObjectDataLoaded(true, cell.GetGridX(), cell.GetGridY()); + + LoadGridObjects(grid, cell); + + Balance(); + return true; + } + + return false; + } + + public virtual void LoadGridObjects(Grid grid, Cell cell) + { + ObjectGridLoader loader = new ObjectGridLoader(grid, this, cell); + loader.LoadN(); + } + + public void LoadGrid(float x, float y) + { + EnsureGridLoaded(new Cell(x, y)); + } + + public virtual bool AddPlayerToMap(Player player, bool initPlayer = true) + { + CellCoord cellCoord = GridDefines.ComputeCellCoord(player.GetPositionX(), player.GetPositionY()); + if (!cellCoord.IsCoordValid()) + { + Log.outError(LogFilter.Maps, "Map.AddPlayer (GUID: {0}) has invalid coordinates X:{1} Y:{2}", + player.GetGUID().ToString(), player.GetPositionX(), player.GetPositionY()); + return false; + } + var cell = new Cell(cellCoord); + EnsureGridLoadedForActiveObject(cell, player); + AddToGrid(player, cell); + + Contract.Assert(player.GetMap() == this); + player.SetMap(this); + player.AddToWorld(); + + if (initPlayer) + SendInitSelf(player); + + SendInitTransports(player); + SendZoneDynamicInfo(player); + + if (initPlayer) + player.m_clientGUIDs.Clear(); + + player.UpdateObjectVisibility(false); + player.SendUpdatePhasing(); + + if (player.IsAlive()) + ConvertCorpseToBones(player.GetGUID()); + + m_activePlayers.Add(player); + + Global.ScriptMgr.OnPlayerEnterMap(this, player); + return true; + } + + void InitializeObject(WorldObject obj) + { + if (!obj.IsTypeId(TypeId.Unit) || !obj.IsTypeId(TypeId.GameObject)) + return; + obj._moveState = ObjectCellMoveState.None; + } + + public bool AddToMap(WorldObject obj) + { + //TODO: Needs clean up. An object should not be added to map twice. + if (obj.IsInWorld) + { + obj.UpdateObjectVisibility(true); + return true; + } + + CellCoord cellCoord = GridDefines.ComputeCellCoord(obj.GetPositionX(), obj.GetPositionY()); + if (!cellCoord.IsCoordValid()) + { + Log.outError(LogFilter.Maps, + "Map.Add: Object {0} has invalid coordinates X:{1} Y:{2} grid cell [{3}:{4}]", obj.GetGUID(), + obj.GetPositionX(), obj.GetPositionY(), cellCoord.x_coord, cellCoord.y_coord); + return false; //Should delete object + } + + var cell = new Cell(cellCoord); + if (obj.isActiveObject()) + EnsureGridLoadedForActiveObject(cell, obj); + else + EnsureGridCreated(new GridCoord(cell.GetGridX(), cell.GetGridY())); + AddToGrid(obj, cell); + Log.outDebug(LogFilter.Maps, "Object {0} enters grid[{1}, {2}]", obj.GetGUID().ToString(), cell.GetGridX(), cell.GetGridY()); + + obj.AddToWorld(); + + InitializeObject(obj); + + if (obj.isActiveObject()) + AddToActive(obj); + + obj.RebuildTerrainSwaps(); + + //something, such as vehicle, needs to be update immediately + //also, trigger needs to cast spell, if not update, cannot see visual + obj.UpdateObjectVisibilityOnCreate(); + return true; + } + + public bool AddToMap(Transport obj) + { + //TODO: Needs clean up. An object should not be added to map twice. + if (obj.IsInWorld) + return true; + + CellCoord cellCoord = GridDefines.ComputeCellCoord(obj.GetPositionX(), obj.GetPositionY()); + if (!cellCoord.IsCoordValid()) + { + Log.outError(LogFilter.Maps, + "Map.Add: Object {0} has invalid coordinates X:{1} Y:{2} grid cell [{3}:{4}]", obj.GetGUID(), + obj.GetPositionX(), obj.GetPositionY(), cellCoord.x_coord, cellCoord.y_coord); + return false; //Should delete object + } + + obj.AddToWorld(); + _transports.Add(obj); + + // Broadcast creation to players + foreach (Player player in GetPlayers()) + { + if (player.GetTransport() != obj) + { + var data = new UpdateData(GetId()); + obj.BuildCreateUpdateBlockForPlayer(data, player); + UpdateObject packet; + data.BuildPacket(out packet); + player.SendPacket(packet); + } + } + + return true; + } + + public bool IsGridLoaded(float x, float y) + { + return IsGridLoaded(GridDefines.ComputeGridCoord(x, y)); + } + + public bool IsGridLoaded(GridCoord p) + { + return (getGrid(p.x_coord, p.y_coord) != null && isGridObjectDataLoaded(p.x_coord, p.y_coord)); + } + + void VisitNearbyCellsOf(WorldObject obj, Visitor gridVisitor, Visitor worldVisitor) + { + // Check for valid position + if (!obj.IsPositionValid()) + return; + + // Update mobs/objects in ALL visible cells around object! + CellArea area = Cell.CalculateCellArea(obj.GetPositionX(), obj.GetPositionY(), obj.GetGridActivationRange()); + + for (uint x = area.low_bound.x_coord; x <= area.high_bound.x_coord; ++x) + { + for (uint y = area.low_bound.y_coord; y <= area.high_bound.y_coord; ++y) + { + // marked cells are those that have been visited + // don't visit the same cell twice + uint cell_id = (y * MapConst.TotalCellsPerMap) + x; + if (isCellMarked(cell_id)) + continue; + + markCell(cell_id); + var pair = new CellCoord(x, y); + var cell = new Cell(pair); + cell.SetNoCreate(); + Visit(cell, gridVisitor); + Visit(cell, worldVisitor); + } + } + } + + public virtual void Update(uint diff) + { + _dynamicTree.update(diff); + + // update worldsessions for existing players + foreach (Player player in m_activePlayers.ToList()) + { + if (player.IsInWorld) + { + WorldSession session = player.GetSession(); + var updater = new MapSessionFilter(session); + session.Update(diff, updater); + } + } + // update active cells around players and active objects + resetMarkedCells(); + + var update = new UpdaterNotifier(diff); + + var grid_object_update = new Visitor(update, GridMapTypeMask.AllGrid); + var world_object_update = new Visitor(update, GridMapTypeMask.AllWorld); + + foreach (Player player in m_activePlayers.ToList()) + { + if (!player.IsInWorld) + continue; + + // update players at tick + player.Update(diff); + + VisitNearbyCellsOf(player, grid_object_update, world_object_update); + + // If player is using far sight, visit that object too + WorldObject viewPoint = player.GetViewpoint(); + if (viewPoint) + { + if (viewPoint.IsTypeId(TypeId.Unit)) + VisitNearbyCellsOf(viewPoint.ToCreature(), grid_object_update, world_object_update); + else if (viewPoint.IsTypeId(TypeId.DynamicObject)) + VisitNearbyCellsOf(viewPoint.ToDynamicObject(), grid_object_update, world_object_update); + } + + // Handle updates for creatures in combat with player and are more than 60 yards away + if (player.IsInCombat()) + { + List updateList = new List(); + HostileReference refe = player.getHostileRefManager().getFirst(); + + while (refe != null) + { + Unit unit = refe.GetSource().GetOwner(); + if (unit) + if (unit.ToCreature() && unit.GetMapId() == player.GetMapId() && !unit.IsWithinDistInMap(player, GetVisibilityRange(), false)) + updateList.Add(unit.ToCreature()); + + refe = refe.next(); + } + + // Process deferred update list for player + foreach (Creature c in updateList) + VisitNearbyCellsOf(c, grid_object_update, world_object_update); + } + } + + foreach (WorldObject obj in m_activeNonPlayers.ToList()) + { + if (!obj.IsInWorld) + continue; + + VisitNearbyCellsOf(obj, grid_object_update, world_object_update); + } + + foreach (Transport transport in _transports.ToList()) + { + if (!transport || !transport.IsInWorld) + continue; + + transport.Update(diff); + } + + SendObjectUpdates(); + + // Process necessary scripts + if (!m_scriptSchedule.Empty()) + { + i_scriptLock = true; + ScriptsProcess(); + i_scriptLock = false; + } + + MoveAllCreaturesInMoveList(); + MoveAllGameObjectsInMoveList(); + MoveAllAreaTriggersInMoveList(); + + if (!m_activePlayers.Empty() || !m_activeNonPlayers.Empty()) + ProcessRelocationNotifies(diff); + + Global.ScriptMgr.OnMapUpdate(this, diff); + } + + void ProcessRelocationNotifies(uint diff) + { + for (uint x = 0; x < MapConst.MaxGrids; ++x) + { + for (uint y = 0; y < MapConst.MaxGrids; ++y) + { + Grid grid = getGrid(x, y); + if (grid == null) + continue; + + if (grid.GetGridState() != GridState.Active) + continue; + + grid.getGridInfoRef().getRelocationTimer().TUpdate((int)diff); + if (!grid.getGridInfoRef().getRelocationTimer().TPassed()) + continue; + + uint gx = grid.getX(); + uint gy = grid.getY(); + + var cell_min = new CellCoord(gx * MapConst.MaxCells, gy * MapConst.MaxCells); + var cell_max = new CellCoord(cell_min.x_coord + MapConst.MaxCells, cell_min.y_coord + MapConst.MaxCells); + + for (uint xx = cell_min.x_coord; xx < cell_max.x_coord; ++xx) + { + for (uint yy = cell_min.y_coord; yy < cell_max.y_coord; ++yy) + { + uint cell_id = (yy * MapConst.TotalCellsPerMap) + xx; + if (!isCellMarked(cell_id)) + continue; + + var pair = new CellCoord(xx, yy); + var cell = new Cell(pair); + cell.SetNoCreate(); + + var cell_relocation = new DelayedUnitRelocation(cell, pair, this, SharedConst.MaxVisibilityDistance); + var grid_object_relocation = new Visitor(cell_relocation, GridMapTypeMask.AllGrid); + var world_object_relocation = new Visitor(cell_relocation, GridMapTypeMask.AllWorld); + + Visit(cell, grid_object_relocation); + Visit(cell, world_object_relocation); + } + } + } + } + var reset = new ResetNotifier(); + var grid_notifier = new Visitor(reset, GridMapTypeMask.AllGrid); + var world_notifier = new Visitor(reset, GridMapTypeMask.AllWorld); + + for (uint x = 0; x < MapConst.MaxGrids; ++x) + { + for (uint y = 0; y < MapConst.MaxGrids; ++y) + { + Grid grid = getGrid(x, y); + if (grid == null) + continue; + + if (grid.GetGridState() != GridState.Active) + continue; + + if (!grid.getGridInfoRef().getRelocationTimer().TPassed()) + continue; + + grid.getGridInfoRef().getRelocationTimer().TReset((int)diff, m_VisibilityNotifyPeriod); + + uint gx = grid.getX(); + uint gy = grid.getY(); + + var cell_min = new CellCoord(gx * MapConst.MaxCells, gy * MapConst.MaxCells); + var cell_max = new CellCoord(cell_min.x_coord + MapConst.MaxCells, + cell_min.y_coord + MapConst.MaxCells); + + for (uint xx = cell_min.x_coord; xx < cell_max.x_coord; ++xx) + { + for (uint yy = cell_min.y_coord; yy < cell_max.y_coord; ++yy) + { + uint cell_id = (yy * MapConst.TotalCellsPerMap) + xx; + if (!isCellMarked(cell_id)) + continue; + + var pair = new CellCoord(xx, yy); + var cell = new Cell(pair); + cell.SetNoCreate(); + Visit(cell, grid_notifier); + Visit(cell, world_notifier); + } + } + } + } + } + + public virtual void RemovePlayerFromMap(Player player, bool remove) + { + Global.ScriptMgr.OnPlayerLeaveMap(this, player); + + player.RemoveFromWorld(); + SendRemoveTransports(player); + + player.UpdateObjectVisibility(true); + Cell cell = player.GetCurrentCell(); + RemoveFromGrid(player, cell); + + m_activePlayers.Remove(player); + + if (remove) + DeleteFromWorld(player); + } + + public void RemoveFromMap(WorldObject obj, bool remove) + { + obj.RemoveFromWorld(); + if (obj.isActiveObject()) + RemoveFromActive(obj); + + obj.UpdateObjectVisibility(true); + Cell cell = obj.GetCurrentCell(); + RemoveFromGrid(obj, cell); + + obj.ResetMap(); + + if (remove) + { + // if option set then object already saved at this moment + if (!WorldConfig.GetBoolValue(WorldCfg.SaveRespawnTimeImmediately)) + obj.SaveRespawnTime(); + DeleteFromWorld(obj); + } + } + + public void RemoveFromMap(Transport obj, bool remove) + { + obj.RemoveFromWorld(); + + var players = GetPlayers(); + if (!players.Empty()) + { + UpdateData data = new UpdateData(GetId()); + obj.BuildOutOfRangeUpdateBlock(data); + UpdateObject packet; + data.BuildPacket(out packet); + packet.Write(); + foreach (var pl in players) + if (pl.GetTransport() != obj) + pl.SendPacket(packet, false); + } + + if (!_transports.Contains(obj)) + return; + + _transports.Remove(obj); + + obj.ResetMap(); + if (remove) + { + // if option set then object already saved at this moment + if (!WorldConfig.GetBoolValue(WorldCfg.SaveRespawnTimeImmediately)) + obj.SaveRespawnTime(); + DeleteFromWorld(obj); + } + } + + public void PlayerRelocation(Player player, float x, float y, float z, float orientation) + { + var oldcell = new Cell(player.GetPositionX(), player.GetPositionY()); + var newcell = new Cell(x, y); + + //! If hovering, always increase our server-side Z position + //! Client automatically projects correct position based on Z coord sent in monster move + //! and UNIT_FIELD_HOVERHEIGHT sent in object updates + if (player.HasUnitMovementFlag(MovementFlag.Hover)) + z += player.GetFloatValue(UnitFields.HoverHeight); + + player.Relocate(x, y, z, orientation); + if (player.IsVehicle()) + player.GetVehicleKit().RelocatePassengers(); + + if (oldcell.DiffGrid(newcell) || oldcell.DiffCell(newcell)) + { + Log.outDebug(LogFilter.Maps, + "Player {0} relocation grid[{1}, {2}]cell[{3}, {4}].grid[{5}, {6}]cell[{7}, {8}]", + player.GetName(), oldcell.GetGridX(), oldcell.GetGridY(), oldcell.GetCellX(), oldcell.GetCellY(), + newcell.GetGridX(), newcell.GetGridY(), newcell.GetCellX(), newcell.GetCellY()); + + RemoveFromGrid(player, oldcell); + if (oldcell.DiffGrid(newcell)) + EnsureGridLoadedForActiveObject(newcell, player); + + AddToGrid(player, newcell); + } + + player.UpdateObjectVisibility(false); + } + + public void CreatureRelocation(Creature creature, float x, float y, float z, float ang, bool respawnRelocationOnFail = true) + { + CheckGridIntegrity(creature, false); + + Cell old_cell = creature.GetCurrentCell(); + var new_cell = new Cell(x, y); + + if (!respawnRelocationOnFail && getGrid(new_cell.GetGridX(), new_cell.GetGridY()) == null) + return; + + //! If hovering, always increase our server-side Z position + //! Client automatically projects correct position based on Z coord sent in monster move + //! and UNIT_FIELD_HOVERHEIGHT sent in object updates + if (creature.HasUnitMovementFlag(MovementFlag.Hover)) + z += creature.GetFloatValue(UnitFields.HoverHeight); + + // delay creature move for grid/cell to grid/cell moves + if (old_cell.DiffCell(new_cell) || old_cell.DiffGrid(new_cell)) + { + AddCreatureToMoveList(creature, x, y, z, ang); + // in diffcell/diffgrid case notifiers called at finishing move creature in MoveAllCreaturesInMoveList + } + else + { + creature.Relocate(x, y, z, ang); + if (creature.IsVehicle()) + creature.GetVehicleKit().RelocatePassengers(); + creature.UpdateObjectVisibility(false); + RemoveCreatureFromMoveList(creature); + } + + CheckGridIntegrity(creature, true); + } + + public void GameObjectRelocation(GameObject go, float x, float y, float z, float orientation, bool respawnRelocationOnFail = true) + { + var integrity_check = new Cell(go.GetPositionX(), go.GetPositionY()); + Cell old_cell = go.GetCurrentCell(); + + var new_cell = new Cell(x, y); + if (!respawnRelocationOnFail && getGrid(new_cell.GetGridX(), new_cell.GetGridY()) == null) + return; + + // delay creature move for grid/cell to grid/cell moves + if (old_cell.DiffCell(new_cell) || old_cell.DiffGrid(new_cell)) + { + Log.outDebug(LogFilter.Maps, + "GameObject (GUID: {0} Entry: {1}) added to moving list from grid[{2}, {3}]cell[{4}, {5}] to grid[{6}, {7}]cell[{8}, {9}].", + go.GetGUID().ToString(), go.GetEntry(), old_cell.GetGridX(), old_cell.GetGridY(), old_cell.GetCellX(), + old_cell.GetCellY(), new_cell.GetGridX(), new_cell.GetGridY(), new_cell.GetCellX(), + new_cell.GetCellY()); + AddGameObjectToMoveList(go, x, y, z, orientation); + // in diffcell/diffgrid case notifiers called at finishing move go in Map.MoveAllGameObjectsInMoveList + } + else + { + go.Relocate(x, y, z, orientation); + go.UpdateModelPosition(); + go.UpdateObjectVisibility(false); + RemoveGameObjectFromMoveList(go); + } + + old_cell = go.GetCurrentCell(); + integrity_check = new Cell(go.GetPositionX(), go.GetPositionY()); + } + + public void DynamicObjectRelocation(DynamicObject dynObj, float x, float y, float z, float orientation) + { + Cell integrity_check = new Cell(dynObj.GetPositionX(), dynObj.GetPositionY()); + Cell old_cell = dynObj.GetCurrentCell(); + + Contract.Assert(integrity_check == old_cell); + Cell new_cell = new Cell(x, y); + + if (getGrid(new_cell.GetGridX(), new_cell.GetGridY()) == null) + return; + + // delay creature move for grid/cell to grid/cell moves + if (old_cell.DiffCell(new_cell) || old_cell.DiffGrid(new_cell)) + { + + Log.outDebug(LogFilter.Maps, "DynamicObject (GUID: {0}) added to moving list from grid[{1}, {2}]cell[{3}, {4}] to grid[{5}, {6}]cell[{7}, {8}].", + dynObj.GetGUID().ToString(), old_cell.GetGridX(), old_cell.GetGridY(), old_cell.GetCellX(), old_cell.GetCellY(), new_cell.GetGridX(), new_cell.GetGridY(), new_cell.GetCellX(), new_cell.GetCellY()); + + AddDynamicObjectToMoveList(dynObj, x, y, z, orientation); + // in diffcell/diffgrid case notifiers called at finishing move dynObj in Map.MoveAllGameObjectsInMoveList + } + else + { + dynObj.Relocate(x, y, z, orientation); + dynObj.UpdateObjectVisibility(false); + RemoveDynamicObjectFromMoveList(dynObj); + } + + old_cell = dynObj.GetCurrentCell(); + integrity_check = new Cell(dynObj.GetPositionX(), dynObj.GetPositionY()); + Contract.Assert(integrity_check == old_cell); + } + + public void AreaTriggerRelocation(AreaTrigger at, float x, float y, float z, float orientation) + { + Cell integrity_check = new Cell(at.GetPositionX(), at.GetPositionY()); + Cell old_cell = at.GetCurrentCell(); + + Contract.Assert(integrity_check == old_cell); + Cell new_cell = new Cell(x, y); + + if (getGrid(new_cell.GetGridX(), new_cell.GetGridY()) == null) + return; + + // delay areatrigger move for grid/cell to grid/cell moves + if (old_cell.DiffCell(new_cell) || old_cell.DiffGrid(new_cell)) + { + Log.outDebug(LogFilter.Maps, "AreaTrigger ({0}) added to moving list from {1} to {2}.", at.GetGUID().ToString(), old_cell.GetGridCellString(), new_cell.GetGridCellString()); + + AddAreaTriggerToMoveList(at, x, y, z, orientation); + // in diffcell/diffgrid case notifiers called at finishing move at in Map::MoveAllAreaTriggersInMoveList + } + else + { + at.Relocate(x, y, z, orientation); + at.UpdateShape(); + at.UpdateObjectVisibility(false); + RemoveAreaTriggerFromMoveList(at); + } + + old_cell = at.GetCurrentCell(); + integrity_check = new Cell(at.GetPositionX(), at.GetPositionY()); + Contract.Assert(integrity_check == old_cell); + } + + void AddCreatureToMoveList(Creature c, float x, float y, float z, float ang) + { + if (_creatureToMoveLock) //can this happen? + return; + + if (c._moveState == ObjectCellMoveState.None) + creaturesToMove.Add(c); + c.SetNewCellPosition(x, y, z, ang); + } + + void AddGameObjectToMoveList(GameObject go, float x, float y, float z, float ang) + { + if (_gameObjectsToMoveLock) //can this happen? + return; + + if (go._moveState == ObjectCellMoveState.None) + _gameObjectsToMove.Add(go); + go.SetNewCellPosition(x, y, z, ang); + } + + void RemoveGameObjectFromMoveList(GameObject go) + { + if (_gameObjectsToMoveLock) //can this happen? + return; + + if (go._moveState == ObjectCellMoveState.Active) + go._moveState = ObjectCellMoveState.Inactive; + } + + void RemoveCreatureFromMoveList(Creature c) + { + if (_creatureToMoveLock) //can this happen? + return; + + if (c._moveState == ObjectCellMoveState.Active) + c._moveState = ObjectCellMoveState.Inactive; + } + + void AddDynamicObjectToMoveList(DynamicObject dynObj, float x, float y, float z, float ang) + { + if (_dynamicObjectsToMoveLock) //can this happen? + return; + + if (dynObj._moveState == ObjectCellMoveState.None) + _dynamicObjectsToMove.Add(dynObj); + dynObj.SetNewCellPosition(x, y, z, ang); + } + + void RemoveDynamicObjectFromMoveList(DynamicObject dynObj) + { + if (_dynamicObjectsToMoveLock) //can this happen? + return; + + if (dynObj._moveState == ObjectCellMoveState.Active) + dynObj._moveState = ObjectCellMoveState.Inactive; + } + + void AddAreaTriggerToMoveList(AreaTrigger at, float x, float y, float z, float ang) + { + if (_areaTriggersToMoveLock) //can this happen? + return; + + if (at._moveState == ObjectCellMoveState.None) + _areaTriggersToMove.Add(at); + at.SetNewCellPosition(x, y, z, ang); + } + + void RemoveAreaTriggerFromMoveList(AreaTrigger at) + { + if (_areaTriggersToMoveLock) //can this happen? + return; + + if (at._moveState == ObjectCellMoveState.Active) + at._moveState = ObjectCellMoveState.Inactive; + } + + void MoveAllCreaturesInMoveList() + { + _creatureToMoveLock = true; + foreach (Creature creature in creaturesToMove.ToList()) + { + if (creature.GetMap() != this) //pet is teleported to another map + continue; + + if (creature._moveState != ObjectCellMoveState.Active) + { + creature._moveState = ObjectCellMoveState.None; + continue; + } + + creature._moveState = ObjectCellMoveState.None; + if (!creature.IsInWorld) + continue; + + // do move or do move to respawn or remove creature if previous all fail + if (CreatureCellRelocation(creature, new Cell(creature._newPosition.posX, creature._newPosition.posY))) + { + // update pos + creature.Relocate(creature._newPosition); + if (creature.IsVehicle()) + creature.GetVehicleKit().RelocatePassengers(); + creature.UpdateObjectVisibility(false); + } + else + { + // if creature can't be move in new cell/grid (not loaded) move it to repawn cell/grid + // creature coordinates will be updated and notifiers send + if (!CreatureRespawnRelocation(creature, false)) + { + // ... or unload (if respawn grid also not loaded) + //This may happen when a player just logs in and a pet moves to a nearby unloaded cell + //To avoid this, we can load nearby cells when player log in + //But this check is always needed to ensure safety + /// @todo pets will disappear if this is outside CreatureRespawnRelocation + /// //need to check why pet is frequently relocated to an unloaded cell + if (creature.IsPet()) + ((Pet)creature).Remove(PetSaveMode.NotInSlot, true); + else + AddObjectToRemoveList(creature); + } + } + } + creaturesToMove.Clear(); + _creatureToMoveLock = false; + } + + void MoveAllGameObjectsInMoveList() + { + _gameObjectsToMoveLock = true; + foreach (GameObject go in _gameObjectsToMove) + { + if (go.GetMap() != this) //transport is teleported to another map + continue; + + if (go._moveState != ObjectCellMoveState.Active) + { + go._moveState = ObjectCellMoveState.None; + continue; + } + + go._moveState = ObjectCellMoveState.None; + if (!go.IsInWorld) + continue; + + // do move or do move to respawn or remove creature if previous all fail + if (GameObjectCellRelocation(go, new Cell(go._newPosition.posX, go._newPosition.posY))) + { + // update pos + go.Relocate(go._newPosition); + go.UpdateModelPosition(); + go.UpdateObjectVisibility(false); + } + else + { + // if GameObject can't be move in new cell/grid (not loaded) move it to repawn cell/grid + // GameObject coordinates will be updated and notifiers send + if (!GameObjectRespawnRelocation(go, false)) + { + // ... or unload (if respawn grid also not loaded) + Log.outDebug(LogFilter.Maps, + "GameObject (GUID: {0} Entry: {1}) cannot be move to unloaded respawn grid.", + go.GetGUID().ToString(), go.GetEntry()); + AddObjectToRemoveList(go); + } + } + } + _gameObjectsToMove.Clear(); + _gameObjectsToMoveLock = false; + } + + void MoveAllDynamicObjectsInMoveList() + { + _dynamicObjectsToMoveLock = true; + foreach (var dynObj in _dynamicObjectsToMove) + { + if (dynObj.GetMap() != this) //transport is teleported to another map + continue; + + if (dynObj._moveState != ObjectCellMoveState.Active) + { + dynObj._moveState = ObjectCellMoveState.None; + continue; + } + + dynObj._moveState = ObjectCellMoveState.None; + if (!dynObj.IsInWorld) + continue; + + // do move or do move to respawn or remove creature if previous all fail + if (DynamicObjectCellRelocation(dynObj, new Cell(dynObj._newPosition.posX, dynObj._newPosition.posY))) + { + // update pos + dynObj.Relocate(dynObj._newPosition); + dynObj.UpdateObjectVisibility(false); + } + else + Log.outDebug(LogFilter.Maps, "DynamicObject (GUID: {0}) cannot be moved to unloaded grid.", dynObj.GetGUID().ToString()); + } + + _dynamicObjectsToMove.Clear(); + _dynamicObjectsToMoveLock = false; + } + + void MoveAllAreaTriggersInMoveList() + { + _areaTriggersToMoveLock = true; + foreach (AreaTrigger at in _areaTriggersToMove) + { + if (at.GetMap() != this) //transport is teleported to another map + continue; + + if (at._moveState != ObjectCellMoveState.Active) + { + at._moveState = ObjectCellMoveState.None; + continue; + } + + at._moveState = ObjectCellMoveState.None; + if (!at.IsInWorld) + continue; + + // do move or do move to respawn or remove creature if previous all fail + if (AreaTriggerCellRelocation(at, new Cell(at._newPosition.posX, at._newPosition.posY))) + { + // update pos + at.Relocate(at._newPosition); + at.UpdateObjectVisibility(false); + } + else + { + Log.outDebug(LogFilter.Maps, "AreaTrigger ({0}) cannot be moved to unloaded grid.", at.GetGUID().ToString()); + } + } + + _areaTriggersToMove.Clear(); + _areaTriggersToMoveLock = false; + } + + private bool CreatureCellRelocation(Creature c, Cell new_cell) + { + Cell old_cell = c.GetCurrentCell(); + if (!old_cell.DiffGrid(new_cell)) // in same grid + { + // if in same cell then none do + if (old_cell.DiffCell(new_cell)) + { + RemoveFromGrid(c, old_cell); + AddToGrid(c, new_cell); + } + + return true; + } + + // in diff. grids but active creature + if (c.isActiveObject()) + { + EnsureGridLoadedForActiveObject(new_cell, c); + + Log.outDebug(LogFilter.Maps, + "Active creature (GUID: {0} Entry: {1}) moved from grid[{2}, {3}] to grid[{4}, {5}].", + c.GetGUID().ToString(), c.GetEntry(), old_cell.GetGridX(), + old_cell.GetGridY(), new_cell.GetGridX(), new_cell.GetGridY()); + RemoveFromGrid(c, old_cell); + AddToGrid(c, new_cell); + + return true; + } + + // in diff. loaded grid normal creature + var grid = new GridCoord(new_cell.GetGridX(), new_cell.GetGridY()); + if (IsGridLoaded(grid)) + { + RemoveFromGrid(c, old_cell); + EnsureGridCreated(grid); + AddToGrid(c, new_cell); + return true; + } + + // fail to move: normal creature attempt move to unloaded grid + return false; + } + + private bool GameObjectCellRelocation(GameObject go, Cell new_cell) + { + Cell old_cell = go.GetCurrentCell(); + if (!old_cell.DiffGrid(new_cell)) // in same grid + { + // if in same cell then none do + if (old_cell.DiffCell(new_cell)) + { + RemoveFromGrid(go, old_cell); + AddToGrid(go, new_cell); + } + + return true; + } + + // in diff. grids but active GameObject + if (go.isActiveObject()) + { + EnsureGridLoadedForActiveObject(new_cell, go); + + Log.outDebug(LogFilter.Maps, + "Active GameObject (GUID: {0} Entry: {1}) moved from grid[{2}, {3}] to grid[{4}, {5}].", + go.GetGUID().ToString(), go.GetEntry(), old_cell.GetGridX(), old_cell.GetGridY(), new_cell.GetGridX(), + new_cell.GetGridY()); + + RemoveFromGrid(go, old_cell); + AddToGrid(go, new_cell); + + return true; + } + + // in diff. loaded grid normal GameObject + if (IsGridLoaded(new GridCoord(new_cell.GetGridX(), new_cell.GetGridY()))) + { + Log.outDebug(LogFilter.Maps, + "GameObject (GUID: {0} Entry: {1}) moved from grid[{2}, {3}] to grid[{4}, {5}].", go.GetGUID().ToString(), + go.GetEntry(), old_cell.GetGridX(), old_cell.GetGridY(), new_cell.GetGridX(), new_cell.GetGridY()); + + RemoveFromGrid(go, old_cell); + EnsureGridCreated(new GridCoord(new_cell.GetGridX(), new_cell.GetGridY())); + AddToGrid(go, new_cell); + + return true; + } + + // fail to move: normal GameObject attempt move to unloaded grid + Log.outDebug(LogFilter.Maps, + "GameObject (GUID: {0} Entry: {1}) attempted to move from grid[{2}, {3}] to unloaded grid[{4}, {5}].", + go.GetGUID().ToString(), go.GetEntry(), old_cell.GetGridX(), old_cell.GetGridY(), new_cell.GetGridX(), + new_cell.GetGridY()); + return false; + } + + private bool DynamicObjectCellRelocation(DynamicObject go, Cell new_cell) + { + Cell old_cell = go.GetCurrentCell(); + if (!old_cell.DiffGrid(new_cell)) // in same grid + { + // if in same cell then none do + if (old_cell.DiffCell(new_cell)) + { + Log.outDebug(LogFilter.Maps, "DynamicObject (GUID: {0}) moved in grid[{1}, {2}] from cell[{3}, {4}] to cell[{5}, {6}].", go.GetGUID().ToString(), old_cell.GetGridX(), old_cell.GetGridY(), old_cell.GetCellX(), old_cell.GetCellY(), new_cell.GetCellX(), new_cell.GetCellY()); + + RemoveFromGrid(go, old_cell); + AddToGrid(go, new_cell); + } + else + Log.outDebug(LogFilter.Maps, "DynamicObject (GUID: {0}) moved in same {1}.", go.GetGUID().ToString(), old_cell.GetGridCellString()); + + return true; + } + + // in diff. grids but active GameObject + if (go.isActiveObject()) + { + EnsureGridLoadedForActiveObject(new_cell, go); + + Log.outDebug(LogFilter.Maps, "Active DynamicObject (GUID: {0}) moved from {1} to {2}.", go.GetGUID().ToString(), old_cell.GetGridCellString(), new_cell.GetGridCellString()); + + RemoveFromGrid(go, old_cell); + AddToGrid(go, new_cell); + + return true; + } + + // in diff. loaded grid normal GameObject + if (IsGridLoaded(new GridCoord(new_cell.GetGridX(), new_cell.GetGridY()))) + { + Log.outDebug(LogFilter.Maps, "DynamicObject (GUID: {0}) moved from {1} to {2}.", go.GetGUID().ToString(), old_cell.GetGridCellString(), new_cell.GetGridCellString()); + + RemoveFromGrid(go, old_cell); + EnsureGridCreated(new GridCoord(new_cell.GetGridX(), new_cell.GetGridY())); + AddToGrid(go, new_cell); + + return true; + } + + // fail to move: normal GameObject attempt move to unloaded grid + Log.outDebug(LogFilter.Maps, "DynamicObject (GUID: {0}) attempted to move from {1} to unloaded {2}.", go.GetGUID().ToString(), old_cell.GetGridCellString(), new_cell.GetGridCellString()); + return false; + } + + bool AreaTriggerCellRelocation(AreaTrigger at, Cell new_cell) + { + Cell old_cell = at.GetCurrentCell(); + if (!old_cell.DiffGrid(new_cell)) // in same grid + { + // if in same cell then none do + if (old_cell.DiffCell(new_cell)) + { + Log.outDebug(LogFilter.Maps, "AreaTrigger ({0}) moved in grid[{0}, {1}] from cell[{2}, {3}] to cell[{4}, {5}].", at.GetGUID().ToString(), old_cell.GetGridX(), old_cell.GetGridY(), + old_cell.GetCellX(), old_cell.GetCellY(), new_cell.GetCellX(), new_cell.GetCellY()); + + RemoveFromGrid(at, old_cell); + AddToGrid(at, new_cell); + } + else + { + Log.outDebug(LogFilter.Maps, "AreaTrigger ({0}) moved in same grid[{1}, {2}]cell[{3}, {4}].", at.GetGUID().ToString(), old_cell.GetGridX(), old_cell.GetGridY(), old_cell.GetCellX(), old_cell.GetCellY()); + } + + return true; + } + + // in diff. grids but active AreaTrigger + if (at.isActiveObject()) + { + EnsureGridLoadedForActiveObject(new_cell, at); + + Log.outDebug(LogFilter.Maps, "Active AreaTrigger ({0}) moved from {1} to {2}.", at.GetGUID().ToString(), old_cell.GetGridCellString(), new_cell.GetGridCellString()); + + RemoveFromGrid(at, old_cell); + AddToGrid(at, new_cell); + + return true; + } + + // in diff. loaded grid normal AreaTrigger + if (IsGridLoaded(new GridCoord(new_cell.GetGridX(), new_cell.GetGridY()))) + { + Log.outDebug(LogFilter.Maps, "AreaTrigger ({0}) moved from {1} to {2}.", at.GetGUID().ToString(), old_cell.GetGridCellString(), new_cell.GetGridCellString()); + + RemoveFromGrid(at, old_cell); + EnsureGridCreated(new GridCoord(new_cell.GetGridX(), new_cell.GetGridY())); + AddToGrid(at, new_cell); + + return true; + } + + // fail to move: normal AreaTrigger attempt move to unloaded grid + Log.outDebug(LogFilter.Maps, "AreaTrigger ({0}) attempted to move from {1} to unloaded {2}.", at.GetGUID().ToString(), old_cell.GetGridCellString(), new_cell.GetGridCellString()); + return false; + } + + public bool CreatureRespawnRelocation(Creature c, bool diffGridOnly) + { + float resp_x, resp_y, resp_z, resp_o; + c.GetRespawnPosition(out resp_x, out resp_y, out resp_z, out resp_o); + var resp_cell = new Cell(resp_x, resp_y); + + //creature will be unloaded with grid + if (diffGridOnly && !c.GetCurrentCell().DiffGrid(resp_cell)) + return true; + + c.CombatStop(); + c.GetMotionMaster().Clear(); + + // teleport it to respawn point (like normal respawn if player see) + if (CreatureCellRelocation(c, resp_cell)) + { + c.Relocate(resp_x, resp_y, resp_z, resp_o); + c.GetMotionMaster().Initialize(); // prevent possible problems with default move generators + c.UpdateObjectVisibility(false); + return true; + } + + return false; + } + + public bool GameObjectRespawnRelocation(GameObject go, bool diffGridOnly) + { + float resp_x, resp_y, resp_z, resp_o; + go.GetRespawnPosition(out resp_x, out resp_y, out resp_z, out resp_o); + var resp_cell = new Cell(resp_x, resp_y); + + //GameObject will be unloaded with grid + if (diffGridOnly && !go.GetCurrentCell().DiffGrid(resp_cell)) + return true; + + Log.outDebug(LogFilter.Maps, + "GameObject (GUID: {0} Entry: {1}) moved from grid[{2}, {3}] to respawn grid[{4}, {5}].", + go.GetGUID().ToString(), go.GetEntry(), go.GetCurrentCell().GetGridX(), go.GetCurrentCell().GetGridY(), + resp_cell.GetGridX(), resp_cell.GetGridY()); + + // teleport it to respawn point (like normal respawn if player see) + if (GameObjectCellRelocation(go, resp_cell)) + { + go.Relocate(resp_x, resp_y, resp_z, resp_o); + go.UpdateObjectVisibility(false); + return true; + } + return false; + } + + public bool UnloadGrid(Grid grid, bool unloadAll) + { + uint x = grid.getX(); + uint y = grid.getY(); + + if (!unloadAll) + { + //pets, possessed creatures (must be active), transport passengers + if (grid.GetWorldObjectCountInNGrid() != 0) + return false; + + if (ActiveObjectsNearGrid(grid)) + return false; + } + + Log.outDebug(LogFilter.Maps, "Unloading grid[{0}, {1}] for map {2}", x, y, GetId()); + + if (!unloadAll) + { + // Finish creature moves, remove and delete all creatures with delayed remove before moving to respawn grids + // Must know real mob position before move + MoveAllCreaturesInMoveList(); + MoveAllGameObjectsInMoveList(); + MoveAllAreaTriggersInMoveList(); + + // move creatures to respawn grids if this is diff.grid or to remove list + ObjectGridEvacuator worker = new ObjectGridEvacuator(); + var visitor = new Visitor(worker, GridMapTypeMask.AllGrid); + grid.VisitAllGrids(visitor); + + // Finish creature moves, remove and delete all creatures with delayed remove before unload + MoveAllCreaturesInMoveList(); + MoveAllGameObjectsInMoveList(); + MoveAllAreaTriggersInMoveList(); + } + + { + ObjectGridCleaner worker = new ObjectGridCleaner(); + var visitor = new Visitor(worker, GridMapTypeMask.AllGrid); + grid.VisitAllGrids(visitor); + } + + RemoveAllObjectsInRemoveList(); + + { + ObjectGridUnloader worker = new ObjectGridUnloader(); + var visitor = new Visitor(worker, GridMapTypeMask.AllGrid); + grid.VisitAllGrids(visitor); + } + + Contract.Assert(i_objectsToRemove.Empty()); + setGrid(null, x, y); + + uint gx = (MapConst.MaxGrids - 1) - x; + uint gy = (MapConst.MaxGrids - 1) - y; + + { + if (i_InstanceId == 0) + { + if (GridMaps[gx][gy] != null) + { + GridMaps[gx][gy].unloadData(); + } + Global.VMapMgr.unloadMap(GetId(), gx, gy); + Global.MMapMgr.unloadMap(GetId(), gx, gy); + } + else + ((MapInstanced)m_parentMap).RemoveGridMapReference(new GridCoord(gx, gy)); + + GridMaps[gx][gy] = null; + } + Log.outDebug(LogFilter.Maps, "Unloading grid[{0}, {1}] for map {2} finished", x, y, GetId()); + return true; + } + + public virtual void RemoveAllPlayers() + { + if (HavePlayers()) + { + foreach (Player pl in m_activePlayers) + { + if (!pl.IsBeingTeleportedFar()) + { + // this is happening for bg + Log.outError(LogFilter.Maps, + "Map.UnloadAll: player {0} is still in map {1} during unload, this should not happen!", + pl.GetName(), GetId()); + pl.TeleportTo(pl.GetHomebind()); + } + } + } + } + + public virtual void UnloadAll() + { + // clear all delayed moves, useless anyway do this moves before map unload. + creaturesToMove.Clear(); + _gameObjectsToMove.Clear(); + + for (uint x = 0; x < MapConst.MaxGrids; ++x) + { + for (uint y = 0; y < MapConst.MaxGrids; ++y) + { + var grid = getGrid(x, y); + if (grid == null) + continue; + + UnloadGrid(grid, true); // deletes the grid and removes it from the GridRefManager + } + } + + foreach (Transport transport in _transports) + RemoveFromMap(transport, true); + + _transports.Clear(); + + foreach (var corpse in _corpsesByCell.Values.ToList()) + { + corpse.RemoveFromWorld(); + corpse.ResetMap(); + corpse.Dispose(); + } + + _corpsesByCell.Clear(); + _corpsesByPlayer.Clear(); + _corpseBones.Clear(); + + Global.ScriptMgr.OnDestroyMap(this); + + foreach (WorldObject obj in i_worldObjects.ToList()) + { + Contract.Assert(obj.IsWorldObject()); + obj.RemoveFromWorld(); + obj.ResetMap(); + } + + if (!m_scriptSchedule.Empty()) + Global.MapMgr.DecreaseScheduledScriptCount((uint)m_scriptSchedule.Count); + + Global.MMapMgr.unloadMapInstance(GetId(), i_InstanceId); + } + + private GridMap GetGridMap(float x, float y) + { + // half opt method + var gx = (uint)(32 - x / MapConst.SizeofGrids); //grid x + var gy = (uint)(32 - y / MapConst.SizeofGrids); //grid y + + // ensure GridMap is loaded + EnsureGridCreated(new GridCoord(63 - gx, 63 - gy)); + + return GridMaps[gx][gy]; + } + + public float GetWaterOrGroundLevel(List phases, float x, float y, float z) + { + float ground = 0f; + return GetWaterOrGroundLevel(phases, x, y, z, ref ground); + } + + public float GetWaterOrGroundLevel(List phases, float x, float y, float z, ref float ground, bool swim = false) + { + if (GetGridMap(x, y) != null) + { + // we need ground level (including grid height version) for proper return water level in point + float ground_z = GetHeight(phases, x, y, z, true, 50.0f); + ground = ground_z; + + LiquidData liquid_status; + + ZLiquidStatus res = getLiquidStatus(x, y, ground_z, MapConst.MapAllLiquidTypes, out liquid_status); + return res != ZLiquidStatus.NoWater ? liquid_status.level : ground_z; + } + + return MapConst.VMAPInvalidHeightValue; + } + + public float GetHeight(float x, float y, float z, bool checkVMap = true, float maxSearchDist = MapConst.DefaultHeightSearch) + { + // find raw .map surface under Z coordinates + float mapHeight = MapConst.VMAPInvalidHeightValue; + GridMap gmap = GetGridMap(x, y); + if (gmap != null) + { + float gridHeight = gmap.getHeight(x, y); + // look from a bit higher pos to find the floor, ignore under surface case + if (z + 2.0f > gridHeight) + mapHeight = gridHeight; + } + + float vmapHeight = MapConst.VMAPInvalidHeightValue; + if (checkVMap) + { + if (Global.VMapMgr.isHeightCalcEnabled()) + vmapHeight = Global.VMapMgr.getHeight(GetId(), x, y, z + 2.0f, maxSearchDist); + // look from a bit higher pos to find the floor + } + + // mapHeight set for any above raw ground Z or <= INVALID_HEIGHT + // vmapheight set for any under Z value or <= INVALID_HEIGHT + if (vmapHeight > MapConst.InvalidHeight) + { + if (mapHeight > MapConst.InvalidHeight) + { + // we have mapheight and vmapheight and must select more appropriate + + // vmap height above map height + // or if the distance of the vmap height is less the land height distance + if (vmapHeight > mapHeight || Math.Abs(mapHeight - z) > Math.Abs(vmapHeight - z)) + return vmapHeight; + return mapHeight; // better use .map surface height + } + return vmapHeight; // we have only vmapHeight (if have) + } + + return mapHeight; // explicitly use map data + } + + public float GetMinHeight(float x, float y) + { + GridMap grid = GetGridMap(x, y); + if (grid != null) + return grid.getMinHeight(x, y); + + return -500.0f; + } + + private bool IsOutdoorWMO(uint mogpFlags, int adtId, int rootId, int groupId, WMOAreaTableRecord wmoEntry, AreaTableRecord atEntry) + { + bool outdoor = true; + + if (wmoEntry != null && atEntry != null) + { + if (atEntry.Flags[0].HasAnyFlag(AreaFlags.Outside)) + return true; + if (atEntry.Flags[0].HasAnyFlag(AreaFlags.Inside)) + return false; + } + + outdoor = Convert.ToBoolean(mogpFlags & 0x8); + + if (wmoEntry != null) + { + if (Convert.ToBoolean(wmoEntry.Flags & 4)) + return true; + if ((wmoEntry.Flags & 2) != 0) + outdoor = false; + } + return outdoor; + } + + public bool IsOutdoors(float x, float y, float z) + { + uint mogpFlags; + int adtId, rootId, groupId; + + // no wmo found? . outside by default + if (!GetAreaInfo(x, y, z, out mogpFlags, out adtId, out rootId, out groupId)) + return true; + + AreaTableRecord atEntry = null; + WMOAreaTableRecord wmoEntry = Global.DB2Mgr.GetWMOAreaTable(rootId, adtId, groupId); + if (wmoEntry != null) + { + Log.outDebug(LogFilter.Maps, "Got WMOAreaTableEntry! flag {0}, areaid {1}", wmoEntry.Flags, + wmoEntry.AreaTableID); + atEntry = CliDB.AreaTableStorage.LookupByKey(wmoEntry.AreaTableID); + } + return IsOutdoorWMO(mogpFlags, adtId, rootId, groupId, wmoEntry, atEntry); + } + + private bool GetAreaInfo(float x, float y, float z, out uint flags, out int adtId, out int rootId, out int groupId) + { + flags = 0; + adtId = 0; + rootId = 0; + groupId = 0; + + float vmap_z = z; + if (Global.VMapMgr.getAreaInfo(GetId(), x, y, ref vmap_z, out flags, out adtId, out rootId, out groupId)) + { + // check if there's terrain between player height and object height + GridMap gmap = GetGridMap(x, y); + if (gmap != null) + { + float _mapheight = gmap.getHeight(x, y); + // z + 2.0f condition taken from GetHeight(), not sure if it's such a great choice... + if (z + 2.0f > _mapheight && _mapheight > vmap_z) + return false; + } + return true; + } + + return false; + } + + public uint GetAreaId(float x, float y, float z) + { + bool throwaway; + return GetAreaId(x, y, z, out throwaway); + } + + public uint GetAreaId(float x, float y, float z, out bool isOutdoors) + { + uint mogpFlags; + int adtId, rootId, groupId; + WMOAreaTableRecord wmoEntry = null; + AreaTableRecord atEntry = null; + bool haveAreaInfo = false; + uint areaId = 0; + + if (GetAreaInfo(x, y, z, out mogpFlags, out adtId, out rootId, out groupId)) + { + haveAreaInfo = true; + wmoEntry = Global.DB2Mgr.GetWMOAreaTable(rootId, adtId, groupId); + if (wmoEntry != null) + { + areaId = wmoEntry.AreaTableID; + atEntry = CliDB.AreaTableStorage.LookupByKey(wmoEntry.AreaTableID); + } + } + + if (areaId == 0) + { + GridMap gmap = GetGridMap(x, y); + if (gmap != null) + areaId = gmap.getArea(x, y); + + // this used while not all *.map files generated (instances) + if (areaId == 0) + areaId = i_mapRecord.AreaTableID; + } + + if (haveAreaInfo) + isOutdoors = IsOutdoorWMO(mogpFlags, adtId, rootId, groupId, wmoEntry, atEntry); + else + isOutdoors = true; + + return areaId; + } + + public uint GetZoneId(float x, float y, float z) + { + uint areaId = GetAreaId(x, y, z); + AreaTableRecord area = CliDB.AreaTableStorage.LookupByKey(areaId); + if (area != null) + if (area.ParentAreaID != 0) + return area.ParentAreaID; + + return areaId; + } + + public void GetZoneAndAreaId(out uint zoneid, out uint areaid, float x, float y, float z) + { + areaid = zoneid = GetAreaId(x, y, z); + AreaTableRecord area = CliDB.AreaTableStorage.LookupByKey(areaid); + if (area != null) + if (area.ParentAreaID != 0) + zoneid = area.ParentAreaID; + } + + private byte GetTerrainType(float x, float y) + { + GridMap gmap = GetGridMap(x, y); + if (gmap != null) + return gmap.getTerrainType(x, y); + return 0; + } + + public ZLiquidStatus getLiquidStatus(float x, float y, float z, byte ReqLiquidType) + { + LiquidData throwaway; + return getLiquidStatus(x, y, z, ReqLiquidType, out throwaway); + } + + public ZLiquidStatus getLiquidStatus(float x, float y, float z, byte ReqLiquidType, out LiquidData data) + { + data = new LiquidData(); + var result = ZLiquidStatus.NoWater; + float liquid_level = MapConst.InvalidHeight; + float ground_level = MapConst.InvalidHeight; + uint liquid_type = 0; + + if (Global.VMapMgr.GetLiquidLevel(GetId(), x, y, z, ReqLiquidType, ref liquid_level, ref ground_level, ref liquid_type)) + { + Log.outDebug(LogFilter.Maps, "getLiquidStatus(): vmap liquid level: {0} ground: {1} type: {2}", + liquid_level, ground_level, liquid_type); + // Check water level and ground level + if (liquid_level > ground_level && z > ground_level - 2) + { + // All ok in water . store data + // hardcoded in client like this + if (GetId() == 530 && liquid_type == 2) + liquid_type = 15; + + uint liquidFlagType = 0; + LiquidTypeRecord liq = CliDB.LiquidTypeStorage.LookupByKey(liquid_type); + if (liq != null) + liquidFlagType = liq.LiquidType; + + if (liquid_type != 0 && liquid_type < 21) + { + AreaTableRecord area = CliDB.AreaTableStorage.LookupByKey(GetAreaId(x, y, z)); + if (area != null) + { + uint overrideLiquid = area.LiquidTypeID[liquidFlagType]; + if (overrideLiquid == 0 && area.ParentAreaID != 0) + { + area = CliDB.AreaTableStorage.LookupByKey(area.ParentAreaID); + if (area != null) + overrideLiquid = area.LiquidTypeID[liquidFlagType]; + } + + liq = CliDB.LiquidTypeStorage.LookupByKey(overrideLiquid); + if (liq != null) + { + liquid_type = overrideLiquid; + liquidFlagType = liq.LiquidType; + } + } + } + + data.level = liquid_level; + data.depth_level = ground_level; + + data.entry = liquid_type; + data.type_flags = (uint)(1 << (int)liquidFlagType); + + float delta = liquid_level - z; + + // Get position delta + if (delta > 2.0f) // Under water + return ZLiquidStatus.UnderWater; + if (delta > 0.0f) // In water + return ZLiquidStatus.InWater; + if (delta > -0.1f) // Walk on water + return ZLiquidStatus.WaterWalk; + result = ZLiquidStatus.AboveWater; + } + } + + GridMap gmap = GetGridMap(x, y); + if (gmap != null) + { + var map_data = new LiquidData(); + ZLiquidStatus map_result = gmap.getLiquidStatus(x, y, z, ReqLiquidType, map_data); + // Not override LIQUID_MAP_ABOVE_WATER with LIQUID_MAP_NO_WATER: + if (map_result != ZLiquidStatus.NoWater && (map_data.level > ground_level)) + { + // hardcoded in client like this + if (GetId() == 530 && map_data.entry == 2) + map_data.entry = 15; + + data = map_data; + + return map_result; + } + } + return result; + } + + public float GetWaterLevel(float x, float y) + { + GridMap gmap = GetGridMap(x, y); + if (gmap != null) + return gmap.getLiquidLevel(x, y); + return 0; + } + + public bool isInLineOfSight(float x1, float y1, float z1, float x2, float y2, float z2, List phases) + { + return Global.VMapMgr.isInLineOfSight(GetId(), x1, y1, z1, x2, y2, z2) + && _dynamicTree.isInLineOfSight(new Vector3(x1, y1, z1), new Vector3(x2, y2, z2), phases); + } + + public bool getObjectHitPos(List phases, float x1, float y1, float z1, float x2, float y2, float z2, out float rx, out float ry, out float rz, float modifyDist) + { + var startPos = new Vector3(x1, y1, z1); + var dstPos = new Vector3(x2, y2, z2); + + var resultPos = new Vector3(); + bool result = _dynamicTree.getObjectHitPos(phases, startPos, dstPos, ref resultPos, modifyDist); + + rx = resultPos.X; + ry = resultPos.Y; + rz = resultPos.Z; + return result; + } + + public float GetHeight(List phases, float x, float y, float z, bool vmap = true, float maxSearchDist = MapConst.DefaultHeightSearch) + { + return Math.Max(GetHeight(x, y, z, vmap, maxSearchDist), _dynamicTree.getHeight(x, y, z, maxSearchDist, phases)); + } + + public bool IsInWater(float x, float y, float pZ) + { + return Convert.ToBoolean(getLiquidStatus(x, y, pZ, MapConst.MapAllLiquidTypes) & + (ZLiquidStatus.InWater | ZLiquidStatus.UnderWater)); + } + + public bool IsUnderWater(float x, float y, float z) + { + return + Convert.ToBoolean(getLiquidStatus(x, y, z, MapConst.MapLiquidTypeWater | MapConst.MapLiquidTypeOcean) & + ZLiquidStatus.UnderWater); + } + + private bool CheckGridIntegrity(Creature c, bool moved) + { + Cell cur_cell = c.GetCurrentCell(); + var xy_cell = new Cell(c.GetPositionX(), c.GetPositionY()); + if (xy_cell != cur_cell) + return true; + + return true; + } + + public string GetMapName() + { + return i_mapRecord != null ? i_mapRecord.MapName[Global.WorldMgr.GetDefaultDbcLocale()] : "UNNAMEDMAP"; + } + + public void SendInitSelf(Player pl) + { + var data = new UpdateData(pl.GetMapId()); + + // attach to player data current transport data + Transport transport = pl.GetTransport(); + if (transport != null) + transport.BuildCreateUpdateBlockForPlayer(data, pl); + + pl.BuildCreateUpdateBlockForPlayer(data, pl); + + // build other passengers at transport also (they always visible and marked as visible and will not send at visibility update at add to map + + if (transport != null) + { + foreach (WorldObject obj in transport.GetPassengers()) + { + if (pl != obj && pl.HaveAtClient(obj)) + obj.BuildCreateUpdateBlockForPlayer(data, pl); + } + } + UpdateObject packet; + data.BuildPacket(out packet); + pl.SendPacket(packet); + } + + void SendInitTransports(Player player) + { + var transData = new UpdateData(player.GetMapId()); + + foreach (Transport transport in _transports) + { + if (transport != player.GetTransport() && player.IsInPhase(transport)) + transport.BuildCreateUpdateBlockForPlayer(transData, player); + } + + UpdateObject packet; + transData.BuildPacket(out packet); + player.SendPacket(packet); + } + + void SendRemoveTransports(Player player) + { + var transData = new UpdateData(player.GetMapId()); + foreach (Transport transport in _transports) + if (transport != player.GetTransport()) + transport.BuildOutOfRangeUpdateBlock(transData); + + UpdateObject packet; + transData.BuildPacket(out packet); + player.SendPacket(packet); + } + + public void SendUpdateTransportVisibility(Player player, List previousPhases) + { + // Hack to send out transports + UpdateData transData = new UpdateData(player.GetMapId()); + foreach (var trans in _transports) + { + if (trans == player.GetTransport()) + continue; + + if (player.IsInPhase(trans) && !previousPhases.Intersect(trans.GetPhases()).Any()) + trans.BuildCreateUpdateBlockForPlayer(transData, player); + else if (!player.IsInPhase(trans)) + trans.BuildOutOfRangeUpdateBlock(transData); + } + + UpdateObject packet; + transData.BuildPacket(out packet); + player.SendPacket(packet); + } + + void setGrid(Grid grid, uint x, uint y) + { + if (x >= MapConst.MaxGrids || y >= MapConst.MaxGrids) + { + Log.outError(LogFilter.Maps, "Map.setNGrid Invalid grid coordinates found: {0}, {1}!", x, y); + return; + } + i_grids[x][y] = grid; + } + + void SendObjectUpdates() + { + Dictionary update_players = new Dictionary(); + + while (!_updateObjects.Empty()) + { + WorldObject obj = _updateObjects[0]; + Contract.Assert(obj.IsInWorld); + _updateObjects.RemoveAt(0); + obj.BuildUpdate(update_players); + } + + UpdateObject packet; + foreach (var iter in update_players) + { + iter.Value.BuildPacket(out packet); + iter.Key.SendPacket(packet); + } + } + + public virtual void DelayedUpdate(uint diff) + { + foreach (var transport in _transports.ToList()) + { + if (!transport.IsInWorld) + continue; + + transport.DelayedUpdate(diff); + } + + RemoveAllObjectsInRemoveList(); + + // Don't unload grids if it's Battleground, since we may have manually added GOs, creatures, those doesn't load from DB at grid re-load ! + // This isn't really bother us, since as soon as we have instanced BG-s, the whole map unloads as the BG gets ended + if (!IsBattlegroundOrArena()) + { + for (uint x = 0; x < MapConst.MaxGrids; ++x) + { + for (uint y = 0; y < MapConst.MaxGrids; ++y) + { + Grid grid = getGrid(x, y); + if (grid != null) + grid.Update(this, diff); + } + } + } + } + + public void AddObjectToRemoveList(WorldObject obj) + { + Contract.Assert(obj.GetMapId() == GetId() && obj.GetInstanceId() == GetInstanceId()); + + obj.CleanupsBeforeDelete(false); // remove or simplify at least cross referenced links + + i_objectsToRemove.Add(obj); + } + + public void AddObjectToSwitchList(WorldObject obj, bool on) + { + Contract.Assert(obj.GetMapId() == GetId() && obj.GetInstanceId() == GetInstanceId()); + // i_objectsToSwitch is iterated only in Map::RemoveAllObjectsInRemoveList() and it uses + // the contained objects only if GetTypeId() == TYPEID_UNIT , so we can return in all other cases + if (!obj.IsTypeId(TypeId.Unit) && !obj.IsTypeId(TypeId.GameObject)) + return; + + if (!i_objectsToSwitch.ContainsKey(obj)) + i_objectsToSwitch.Add(obj, on); + else if (i_objectsToSwitch[obj] != on) + i_objectsToSwitch.Remove(obj); + else + Contract.Assert(false); + } + + void RemoveAllObjectsInRemoveList() + { + while (!i_objectsToSwitch.Empty()) + { + KeyValuePair pair = i_objectsToSwitch.First(); + WorldObject obj = pair.Key; + bool on = pair.Value; + i_objectsToSwitch.Remove(pair.Key); + + if (!obj.IsPermanentWorldObject()) + { + switch (obj.GetTypeId()) + { + case TypeId.Unit: + SwitchGridContainers(obj.ToCreature(), on); + break; + case TypeId.GameObject: + SwitchGridContainers(obj.ToGameObject(), on); + break; + default: + break; + } + } + } + + while (!i_objectsToRemove.Empty()) + { + WorldObject obj = i_objectsToRemove.First(); + + switch (obj.GetTypeId()) + { + case TypeId.Corpse: + { + Corpse corpse = ObjectAccessor.GetCorpse(obj, obj.GetGUID()); + if (corpse == null) + Log.outError(LogFilter.Maps, "Tried to delete corpse/bones {0} that is not in map.", obj.GetGUID().ToString()); + else + RemoveFromMap(corpse, true); + break; + } + case TypeId.DynamicObject: + RemoveFromMap(obj, true); + break; + case TypeId.AreaTrigger: + RemoveFromMap(obj, true); + break; + case TypeId.Conversation: + RemoveFromMap(obj, true); + break; + case TypeId.GameObject: + GameObject go = obj.ToGameObject(); + if (go.IsTransport()) + RemoveFromMap(go.ToTransport(), true); + else + RemoveFromMap(go, true); + break; + case TypeId.Unit: + // in case triggered sequence some spell can continue casting after prev CleanupsBeforeDelete call + // make sure that like sources auras/etc removed before destructor start + obj.ToCreature().CleanupsBeforeDelete(); + RemoveFromMap(obj.ToCreature(), true); + break; + default: + Log.outError(LogFilter.Maps, "Non-grid object (TypeId: {0}) is in grid object remove list, ignored.", obj.GetTypeId()); + break; + } + + i_objectsToRemove.Remove(obj); + } + } + + public uint GetPlayersCountExceptGMs() + { + uint count = 0; + foreach (Player pl in m_activePlayers) + if (!pl.IsGameMaster()) + ++count; + return count; + } + + public void SendToPlayers(ServerPacket data) + { + data.Write(); + foreach (Player pl in m_activePlayers) + pl.SendPacket(data, false); + } + + public bool ActiveObjectsNearGrid(Grid grid) + { + var cell_min = new CellCoord(grid.getX() * MapConst.MaxCells, + grid.getY() * MapConst.MaxCells); + var cell_max = new CellCoord(cell_min.x_coord + MapConst.MaxCells, + cell_min.y_coord + MapConst.MaxCells); + + //we must find visible range in cells so we unload only non-visible cells... + float viewDist = GetVisibilityRange(); + uint cell_range = (uint)Math.Ceiling(viewDist / MapConst.SizeofCells) + 1; + + cell_min.dec_x(cell_range); + cell_min.dec_y(cell_range); + cell_max.inc_x(cell_range); + cell_max.inc_y(cell_range); + + foreach (Player pl in m_activePlayers) + { + CellCoord p = GridDefines.ComputeCellCoord(pl.GetPositionX(), pl.GetPositionY()); + if ((cell_min.x_coord <= p.x_coord && p.x_coord <= cell_max.x_coord) && + (cell_min.y_coord <= p.y_coord && p.y_coord <= cell_max.y_coord)) + return true; + } + + foreach (WorldObject obj in m_activeNonPlayers) + { + CellCoord p = GridDefines.ComputeCellCoord(obj.GetPositionX(), obj.GetPositionY()); + if ((cell_min.x_coord <= p.x_coord && p.x_coord <= cell_max.x_coord) && + (cell_min.y_coord <= p.y_coord && p.y_coord <= cell_max.y_coord)) + return true; + } + + return false; + } + + public void AddToActive(WorldObject obj) + { + AddToActiveHelper(obj); + + if (obj.IsTypeId(TypeId.Unit)) + { + Creature c = obj.ToCreature(); + // also not allow unloading spawn grid to prevent creating creature clone at load + if (!c.IsPet() && c.GetSpawnId() != 0) + { + float x, y, z; + c.GetRespawnPosition(out x, out y, out z); + GridCoord p = GridDefines.ComputeGridCoord(x, y); + if (getGrid(p.x_coord, p.y_coord) != null) + getGrid(p.x_coord, p.y_coord).incUnloadActiveLock(); + else + { + GridCoord p2 = GridDefines.ComputeGridCoord(c.GetPositionX(), c.GetPositionY()); + Log.outError(LogFilter.Maps, + "Active creature (GUID: {0} Entry: {1}) added to grid[{2}, {3}] but spawn grid[{4}, {5}] was not loaded.", + c.GetGUID().ToString(), c.GetEntry(), p.x_coord, p.y_coord, p2.x_coord, p2.y_coord); + } + } + } + } + + void AddToActiveHelper(WorldObject obj) + { + m_activeNonPlayers.Add(obj); + } + + public void RemoveFromActive(WorldObject obj) + { + RemoveFromActiveHelper(obj); + + if (obj.IsTypeId(TypeId.Unit)) + { + Creature c = obj.ToCreature(); + // also allow unloading spawn grid + if (!c.IsPet() && c.GetSpawnId() != 0) + { + float x, y, z; + c.GetRespawnPosition(out x, out y, out z); + GridCoord p = GridDefines.ComputeGridCoord(x, y); + if (getGrid(p.x_coord, p.y_coord) != null) + getGrid(p.x_coord, p.y_coord).decUnloadActiveLock(); + else + { + GridCoord p2 = GridDefines.ComputeGridCoord(c.GetPositionX(), c.GetPositionY()); + Log.outDebug(LogFilter.Maps, + "Active creature (GUID: {0} Entry: {1}) removed from grid[{2}, {3}] but spawn grid[{4}, {5}] was not loaded.", + c.GetGUID().ToString(), c.GetEntry(), p.x_coord, p.y_coord, p2.x_coord, p2.y_coord); + } + } + } + } + + void RemoveFromActiveHelper(WorldObject obj) + { + m_activeNonPlayers.Remove(obj); + } + + public void SaveCreatureRespawnTime(ulong dbGuid, long respawnTime) + { + if (respawnTime == 0) + { + // Delete only + RemoveCreatureRespawnTime(dbGuid); + return; + } + + _creatureRespawnTimes[dbGuid] = respawnTime; + + PreparedStatement stmt = DB.Characters.GetPreparedStatement(CharStatements.REP_CREATURE_RESPAWN); + stmt.AddValue(0, dbGuid); + stmt.AddValue(1, respawnTime); + stmt.AddValue(2, GetId()); + stmt.AddValue(3, GetInstanceId()); + DB.Characters.Execute(stmt); + } + + public void RemoveCreatureRespawnTime(ulong dbGuid) + { + _creatureRespawnTimes.Remove(dbGuid); + + PreparedStatement stmt = DB.Characters.GetPreparedStatement(CharStatements.DEL_CREATURE_RESPAWN); + stmt.AddValue(0, dbGuid); + stmt.AddValue(1, GetId()); + stmt.AddValue(2, GetInstanceId()); + DB.Characters.Execute(stmt); + } + + public void SaveGORespawnTime(ulong dbGuid, long respawnTime) + { + if (respawnTime == 0) + { + // Delete only + RemoveGORespawnTime(dbGuid); + return; + } + + _goRespawnTimes[dbGuid] = respawnTime; + + PreparedStatement stmt = DB.Characters.GetPreparedStatement(CharStatements.REP_GO_RESPAWN); + stmt.AddValue(0, dbGuid); + stmt.AddValue(1, respawnTime); + stmt.AddValue(2, GetId()); + stmt.AddValue(3, GetInstanceId()); + DB.Characters.Execute(stmt); + } + + public void RemoveGORespawnTime(ulong dbGuid) + { + _goRespawnTimes.Remove(dbGuid); + + PreparedStatement stmt = DB.Characters.GetPreparedStatement(CharStatements.DEL_GO_RESPAWN); + stmt.AddValue(0, dbGuid); + stmt.AddValue(1, GetId()); + stmt.AddValue(2, GetInstanceId()); + DB.Characters.Execute(stmt); + } + + public void LoadRespawnTimes() + { + PreparedStatement stmt = DB.Characters.GetPreparedStatement(CharStatements.SEL_CREATURE_RESPAWNS); + stmt.AddValue(0, GetId()); + stmt.AddValue(1, GetInstanceId()); + SQLResult result = DB.Characters.Query(stmt); + if (!result.IsEmpty()) + { + do + { + var loguid = result.Read(0); + var respawnTime = result.Read(1); + + _creatureRespawnTimes[loguid] = respawnTime; + } while (result.NextRow()); + } + + stmt = DB.Characters.GetPreparedStatement(CharStatements.SEL_GO_RESPAWNS); + stmt.AddValue(0, GetId()); + stmt.AddValue(1, GetInstanceId()); + result = DB.Characters.Query(stmt); + if (!result.IsEmpty()) + { + do + { + var loguid = result.Read(0); + var respawnTime = result.Read(1); + + _goRespawnTimes[loguid] = respawnTime; + } while (result.NextRow()); + } + } + + public void DeleteRespawnTimes() + { + _creatureRespawnTimes.Clear(); + _goRespawnTimes.Clear(); + + DeleteRespawnTimesInDB(GetId(), GetInstanceId()); + } + + public static void DeleteRespawnTimesInDB(uint mapId, uint instanceId) + { + PreparedStatement stmt = DB.Characters.GetPreparedStatement(CharStatements.DEL_CREATURE_RESPAWN_BY_INSTANCE); + stmt.AddValue(0, mapId); + stmt.AddValue(1, instanceId); + DB.Characters.Execute(stmt); + + stmt = DB.Characters.GetPreparedStatement(CharStatements.DEL_GO_RESPAWN_BY_INSTANCE); + stmt.AddValue(0, mapId); + stmt.AddValue(1, instanceId); + DB.Characters.Execute(stmt); + } + + public long GetLinkedRespawnTime(ObjectGuid guid) + { + ObjectGuid linkedGuid = Global.ObjectMgr.GetLinkedRespawnGuid(guid); + switch (linkedGuid.GetHigh()) + { + case HighGuid.Creature: + return GetCreatureRespawnTime(linkedGuid.GetCounter()); + case HighGuid.GameObject: + return GetGORespawnTime(linkedGuid.GetCounter()); + default: + break; + } + + return 0L; + } + + public void LoadCorpseData() + { + MultiMap phases = new MultiMap(); + + PreparedStatement stmt = DB.Characters.GetPreparedStatement(CharStatements.SEL_CORPSE_PHASES); + stmt.AddValue(0, GetId()); + stmt.AddValue(1, GetInstanceId()); + + // 0 1 + // SELECT OwnerGuid, PhaseId FROM corpse_phases cp LEFT JOIN corpse c ON cp.OwnerGuid = c.guid WHERE c.mapId = ? AND c.instanceId = ? + SQLResult phaseResult = DB.Characters.Query(stmt); + if (!phaseResult.IsEmpty()) + { + do + { + ulong guid = phaseResult.Read(0); + uint phaseId = phaseResult.Read(1); + + phases.Add(guid, phaseId); + + } while (phaseResult.NextRow()); + } + + stmt = DB.Characters.GetPreparedStatement(CharStatements.SEL_CORPSES); + stmt.AddValue(0, GetId()); + stmt.AddValue(1, GetInstanceId()); + + // 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 + // SELECT posX, posY, posZ, orientation, mapId, displayId, itemCache, bytes1, bytes2, flags, dynFlags, time, corpseType, instanceId, guid FROM corpse WHERE mapId = ? AND instanceId = ? + SQLResult result = DB.Characters.Query(stmt); + if (result.IsEmpty()) + return; + + do + { + CorpseType type = (CorpseType)result.Read(12); + ulong guid = result.Read(14); + if (type >= CorpseType.Max || type == CorpseType.Bones) + { + Log.outError(LogFilter.Maps, "Corpse (guid: {0}) have wrong corpse type ({1}), not loading.", guid, type); + continue; + } + + Corpse corpse = new Corpse(type); + if (!corpse.LoadCorpseFromDB(GenerateLowGuid(HighGuid.Corpse), result.GetFields())) + continue; + + foreach (var phaseId in phases[guid]) + corpse.SetInPhase(phaseId, false, true); + + AddCorpse(corpse); + } while (result.NextRow()); + } + + public void DeleteCorpseData() + { + // DELETE cp, c FROM corpse_phases cp INNER JOIN corpse c ON cp.OwnerGuid = c.guid WHERE c.mapId = ? AND c.instanceId = ? + PreparedStatement stmt = DB.Characters.GetPreparedStatement(CharStatements.DEL_CORPSES_FROM_MAP); + stmt.AddValue(0, GetId()); + stmt.AddValue(1, GetInstanceId()); + DB.Characters.Execute(stmt); + } + + public void AddCorpse(Corpse corpse) + { + corpse.SetMap(this); + + _corpsesByCell.Add(corpse.GetCellCoord().GetId(), corpse); + if (corpse.GetCorpseType() != CorpseType.Bones) + _corpsesByPlayer[corpse.GetOwnerGUID()] = corpse; + else + _corpseBones.Add(corpse); + } + + void RemoveCorpse(Corpse corpse) + { + Contract.Assert(corpse); + + corpse.DestroyForNearbyPlayers(); + if (corpse.GetCurrentCell() != null) + RemoveFromMap(corpse, false); + else + { + corpse.RemoveFromWorld(); + corpse.ResetMap(); + } + + _corpsesByCell.Remove(corpse.GetCellCoord().GetId(), corpse); + if (corpse.GetCorpseType() != CorpseType.Bones) + _corpsesByPlayer.Remove(corpse.GetOwnerGUID()); + else + _corpseBones.Remove(corpse); + } + + public Corpse ConvertCorpseToBones(ObjectGuid ownerGuid, bool insignia = false) + { + Corpse corpse = GetCorpseByPlayer(ownerGuid); + if (!corpse) + return null; + + RemoveCorpse(corpse); + + // remove corpse from DB + SQLTransaction trans = new SQLTransaction(); + corpse.DeleteFromDB(trans); + DB.Characters.CommitTransaction(trans); + + Corpse bones = null; + + // create the bones only if the map and the grid is loaded at the corpse's location + // ignore bones creating option in case insignia + if ((insignia || + (IsBattlegroundOrArena() ? WorldConfig.GetBoolValue(WorldCfg.DeathBonesBgOrArena) : WorldConfig.GetBoolValue(WorldCfg.DeathBonesWorld))) && + !IsRemovalGrid(corpse.GetPositionX(), corpse.GetPositionY())) + { + // Create bones, don't change Corpse + bones = new Corpse(); + bones.Create(corpse.GetGUID().GetCounter(), this); + + for (byte i = (int)ObjectFields.Guid + 4; i < (int)CorpseFields.End; ++i) // don't overwrite guid + bones.SetUInt32Value(i, corpse.GetUInt32Value(i)); + + bones.SetCellCoord(corpse.GetCellCoord()); + bones.Relocate(corpse.GetPositionX(), corpse.GetPositionY(), corpse.GetPositionZ(), corpse.GetOrientation()); + + bones.SetUInt32Value(CorpseFields.Flags, corpse.GetUInt32Value(CorpseFields.Flags) | (uint)CorpseFlags.Bones); + + bones.CopyPhaseFrom(corpse); + + AddCorpse(bones); + + // add bones in grid store if grid loaded where corpse placed + AddToMap(bones); + } + + // all references to the corpse should be removed at this point + corpse.Dispose(); + + return bones; + } + + public void RemoveOldCorpses() + { + long now = Time.UnixTime; + + List corpses = new List(); + + foreach (var p in _corpsesByPlayer) + if (p.Value.IsExpired(now)) + corpses.Add(p.Key); + + foreach (ObjectGuid ownerGuid in corpses) + ConvertCorpseToBones(ownerGuid); + + List expiredBones = new List(); + foreach (Corpse bones in _corpseBones) + if (bones.IsExpired(now)) + expiredBones.Add(bones); + + foreach (Corpse bones in expiredBones) + { + RemoveCorpse(bones); + bones.Dispose(); + } + } + + void SendZoneDynamicInfo(Player player) + { + uint zoneId = GetZoneId(player.GetPositionX(), player.GetPositionY(), player.GetPositionZ()); + var zoneInfo = _zoneDynamicInfo.LookupByKey(zoneId); + if (zoneInfo == null) + return; + + uint music = zoneInfo.MusicId; + if (music != 0) + player.SendPacket(new PlayMusic(music)); + + WeatherState weatherId = zoneInfo.WeatherId; + if (weatherId != 0) + { + WeatherPkt weather = new WeatherPkt(weatherId, zoneInfo.WeatherGrade); + player.SendPacket(weather); + } + + uint overrideLightId = zoneInfo.OverrideLightId; + if (overrideLightId != 0) + { + OverrideLight overrideLight = new OverrideLight(); + overrideLight.AreaLightID = _defaultLight; + overrideLight.OverrideLightID = overrideLightId; + overrideLight.TransitionMilliseconds = zoneInfo.LightFadeInTime; + player.SendPacket(overrideLight); + } + } + + public void SetZoneMusic(uint zoneId, uint musicId) + { + if (!_zoneDynamicInfo.ContainsKey(zoneId)) + _zoneDynamicInfo[zoneId] = new ZoneDynamicInfo(); + + _zoneDynamicInfo[zoneId].MusicId = musicId; + + var players = GetPlayers(); + if (!players.Empty()) + { + PlayMusic data = new PlayMusic(musicId); + data.Write(); + + foreach (var player in players) + if (player.GetZoneId() == zoneId) + player.SendPacket(data, false); + } + } + + void SetZoneWeather(uint zoneId, WeatherState weatherId, float weatherGrade) + { + if (!_zoneDynamicInfo.ContainsKey(zoneId)) + _zoneDynamicInfo[zoneId] = new ZoneDynamicInfo(); + + ZoneDynamicInfo info = _zoneDynamicInfo[zoneId]; + info.WeatherId = weatherId; + info.WeatherGrade = weatherGrade; + var players = GetPlayers(); + + if (!players.Empty()) + { + WeatherPkt weather = new WeatherPkt(weatherId, weatherGrade); + weather.Write(); + foreach (var player in players) + if (player.GetZoneId() == zoneId) + player.SendPacket(weather, false); + } + } + + void SetZoneOverrideLight(uint zoneId, uint lightId, uint fadeInTime) + { + if (!_zoneDynamicInfo.ContainsKey(zoneId)) + _zoneDynamicInfo[zoneId] = new ZoneDynamicInfo(); + + ZoneDynamicInfo info = _zoneDynamicInfo[zoneId]; + info.OverrideLightId = lightId; + info.LightFadeInTime = fadeInTime; + var players = GetPlayers(); + + if (!players.Empty()) + { + OverrideLight overrideLight = new OverrideLight(); + overrideLight.AreaLightID = _defaultLight; + overrideLight.OverrideLightID = lightId; + overrideLight.TransitionMilliseconds = fadeInTime; + overrideLight.Write(); + + foreach (var player in players) + if (player.GetZoneId() == zoneId) + player.SendPacket(overrideLight, false); + } + } + + public MapRecord GetEntry() + { + return i_mapRecord; + } + + public bool CanUnload(uint diff) + { + if (m_unloadTimer == 0) + return false; + + if (m_unloadTimer <= diff) + return true; + + m_unloadTimer -= diff; + return false; + } + + public float GetVisibilityRange() + { + return m_VisibleDistance; + } + + public bool IsRemovalGrid(float x, float y) + { + GridCoord p = GridDefines.ComputeGridCoord(x, y); + return getGrid(p.x_coord, p.y_coord) == null || + getGrid(p.x_coord, p.y_coord).GetGridState() == GridState.Removal; + } + + private bool GetUnloadLock(GridCoord p) + { + return getGrid(p.x_coord, p.y_coord).getUnloadLock(); + } + + void SetUnloadLock(GridCoord p, bool on) + { + getGrid(p.x_coord, p.y_coord).setUnloadExplicitLock(on); + } + + public void ResetGridExpiry(Grid grid, float factor = 1) + { + grid.ResetTimeTracker((long)(i_gridExpiry * factor)); + } + + public long GetGridExpiry() + { + return i_gridExpiry; + } + + private Map GetParent() + { + return m_parentMap; + } + + public uint GetInstanceId() + { + return i_InstanceId; + } + + public Difficulty GetSpawnMode() + { + return i_spawnMode; + } + + public virtual EnterState CannotEnter(Player player) { return EnterState.CanEnter; } + + public Difficulty GetDifficultyID() + { + return GetSpawnMode(); + } + + public MapDifficultyRecord GetMapDifficulty() + { + return Global.DB2Mgr.GetMapDifficultyData(GetId(), GetDifficultyID()); + } + + public byte GetDifficultyLootBonusTreeMod() + { + MapDifficultyRecord mapDifficulty = GetMapDifficulty(); + if (mapDifficulty != null && mapDifficulty.ItemBonusTreeModID != 0) + return mapDifficulty.ItemBonusTreeModID; + + DifficultyRecord difficulty = CliDB.DifficultyStorage.LookupByKey(GetDifficultyID()); + if (difficulty != null) + return difficulty.ItemBonusTreeModID; + + return 0; + } + + public uint GetId() + { + return i_mapRecord.Id; + } + + public bool Instanceable() + { + return i_mapRecord != null && i_mapRecord.Instanceable(); + } + + public bool IsDungeon() + { + return i_mapRecord != null && i_mapRecord.IsDungeon(); + } + + public bool IsNonRaidDungeon() + { + return i_mapRecord != null && i_mapRecord.IsNonRaidDungeon(); + } + + public bool IsRaid() + { + return i_mapRecord != null && i_mapRecord.IsRaid(); + } + + public bool IsRaidOrHeroicDungeon() + { + return IsRaid() || IsHeroic(); + } + + public bool IsHeroic() + { + DifficultyRecord difficulty = CliDB.DifficultyStorage.LookupByKey(i_spawnMode); + if (difficulty != null) + return difficulty.Flags.HasAnyFlag(DifficultyFlags.Heroic); + return false; + } + + public bool Is25ManRaid() + { + // since 25man difficulties are 1 and 3, we can check them like that + return IsRaid() && (i_spawnMode == Difficulty.Raid25N || i_spawnMode == Difficulty.Raid25HC); + } + + public bool IsBattleground() + { + return i_mapRecord != null && i_mapRecord.IsBattleground(); + } + + public bool IsBattleArena() + { + return i_mapRecord != null && i_mapRecord.IsBattleArena(); + } + + public bool IsBattlegroundOrArena() + { + return i_mapRecord != null && i_mapRecord.IsBattlegroundOrArena(); + } + + public bool IsGarrison() + { + return i_mapRecord != null && i_mapRecord.IsGarrison(); + } + + private bool GetEntrancePos(out uint mapid, out float x, out float y) + { + mapid = 0; + x = 0; + y = 0; + + if (i_mapRecord == null) + return false; + + return i_mapRecord.GetEntrancePos(out mapid, out x, out y); + } + + void resetMarkedCells() + { + marked_cells.SetAll(false); + } + + private bool isCellMarked(uint pCellId) + { + return marked_cells.Get((int)pCellId); + } + + void markCell(uint pCellId) + { + marked_cells.Set((int)pCellId, true); + } + + public bool HavePlayers() + { + return !m_activePlayers.Empty(); + } + + public void AddWorldObject(WorldObject obj) + { + i_worldObjects.Add(obj); + } + + public void RemoveWorldObject(WorldObject obj) + { + i_worldObjects.Remove(obj); + } + + public List GetPlayers() + { + return m_activePlayers; + } + + public Dictionary GetObjectsStore() { return _objectsStore; } + + public MultiMap GetCreatureBySpawnIdStore() { return _creatureBySpawnIdStore; } + + public MultiMap GetGameObjectBySpawnIdStore() { return _gameobjectBySpawnIdStore; } + + public List GetCorpsesInCell(uint cellId) + { + return _corpsesByCell.LookupByKey(cellId); + } + + public Corpse GetCorpseByPlayer(ObjectGuid ownerGuid) + { + return _corpsesByPlayer.LookupByKey(ownerGuid); + } + + public MapInstanced ToMapInstanced() { return Instanceable() ? (this as MapInstanced) : null; } + public InstanceMap ToInstanceMap() { return IsDungeon() ? (this as InstanceMap) : null; } + public BattlegroundMap ToBattlegroundMap() { return IsBattlegroundOrArena() ? (this as BattlegroundMap) : null; } + + void Balance() + { + _dynamicTree.balance(); + } + + public void RemoveGameObjectModel(GameObjectModel model) + { + _dynamicTree.remove(model); + } + + public void InsertGameObjectModel(GameObjectModel model) + { + _dynamicTree.insert(model); + } + + public bool ContainsGameObjectModel(GameObjectModel model) + { + return _dynamicTree.contains(model); + } + + public virtual uint GetOwnerGuildId(Team team = Team.Other) + { + return 0; + } + + public long GetCreatureRespawnTime(ulong dbGuid) + { + return _creatureRespawnTimes.LookupByKey(dbGuid); + } + + public long GetGORespawnTime(ulong dbGuid) + { + return _goRespawnTimes.LookupByKey(dbGuid); + } + + void SetTimer(uint t) + { + i_gridExpiry = t < MapConst.MinGridDelay ? MapConst.MinGridDelay : t; + } + + private Grid getGrid(uint x, uint y) + { + return i_grids[x][y]; + } + + private bool isGridObjectDataLoaded(uint x, uint y) + { + return getGrid(x, y).isGridObjectDataLoaded(); + } + + void setGridObjectDataLoaded(bool pLoaded, uint x, uint y) + { + getGrid(x, y).setGridObjectDataLoaded(pLoaded); + } + + public void SetUnloadReferenceLock(GridCoord p, bool on) + { + getGrid(p.x_coord, p.y_coord).setUnloadReferenceLock(on); + } + + public AreaTrigger GetAreaTrigger(ObjectGuid guid) + { + return (AreaTrigger)_objectsStore.LookupByKey(guid); + } + + public Conversation GetConversation(ObjectGuid guid) + { + return (Conversation)_objectsStore.LookupByKey(guid); + } + + public Corpse GetCorpse(ObjectGuid guid) + { + return (Corpse)_objectsStore.LookupByKey(guid); + } + + public Creature GetCreature(ObjectGuid guid) + { + return (Creature)_objectsStore.LookupByKey(guid); + } + + public DynamicObject GetDynamicObject(ObjectGuid guid) + { + return (DynamicObject)_objectsStore.LookupByKey(guid); + } + + public GameObject GetGameObject(ObjectGuid guid) + { + return (GameObject)_objectsStore.LookupByKey(guid); + } + + public Pet GetPet(ObjectGuid guid) + { + return (Pet)_objectsStore.LookupByKey(guid); + } + + public Transport GetTransport(ObjectGuid guid) + { + if (!guid.IsMOTransport()) + return null; + + GameObject go = GetGameObject(guid); + return go ? go.ToTransport() : null; + } + + public void Visit(Cell cell, Visitor visitor) + { + uint x = cell.GetGridX(); + uint y = cell.GetGridY(); + uint cell_x = cell.GetCellX(); + uint cell_y = cell.GetCellY(); + + if (!cell.NoCreate() || IsGridLoaded(new GridCoord(x, y))) + { + EnsureGridLoaded(cell); + getGrid(x, y).VisitGrid(cell_x, cell_y, visitor); + } + } + + public TempSummon SummonCreature(uint entry, Position pos, SummonPropertiesRecord properties = null, uint duration = 0, Unit summoner = null, uint spellId = 0, uint vehId = 0) + { + var mask = UnitTypeMask.Summon; + if (properties != null) + { + switch (properties.Category) + { + case SummonCategory.Pet: + mask = UnitTypeMask.Guardian; + break; + case SummonCategory.Puppet: + mask = UnitTypeMask.Puppet; + break; + case SummonCategory.Vehicle: + mask = UnitTypeMask.Minion; + break; + case SummonCategory.Wild: + case SummonCategory.Ally: + case SummonCategory.Unk: + { + switch (properties.Type) + { + case SummonType.Minion: + case SummonType.Guardian: + case SummonType.Guardian2: + mask = UnitTypeMask.Guardian; + break; + case SummonType.Totem: + case SummonType.LightWell: + mask = UnitTypeMask.Totem; + break; + case SummonType.Vehicle: + case SummonType.Vehicle2: + mask = UnitTypeMask.Summon; + break; + case SummonType.Minipet: + mask = UnitTypeMask.Minion; + break; + default: + if (Convert.ToBoolean(properties.Flags & 512)) // Mirror Image, Summon Gargoyle + mask = UnitTypeMask.Guardian; + break; + } + break; + } + default: + return null; + } + } + + TempSummon summon = null; + switch (mask) + { + case UnitTypeMask.Summon: + summon = new TempSummon(properties, summoner, false); + break; + case UnitTypeMask.Guardian: + summon = new Guardian(properties, summoner, false); + break; + case UnitTypeMask.Puppet: + summon = new Puppet(properties, summoner); + break; + case UnitTypeMask.Totem: + summon = new Totem(properties, summoner); + break; + case UnitTypeMask.Minion: + summon = new Minion(properties, summoner, false); + break; + default: + return null; + } + + if (!summon.Create(GenerateLowGuid(HighGuid.Creature), this, 0, entry, pos.posX, pos.posY, pos.posZ, pos.Orientation, null, vehId)) + return null; + + // Set the summon to the summoner's phase + summon.CopyPhaseFrom(summoner); + + summon.SetUInt32Value(UnitFields.CreatedBySpell, spellId); + + summon.SetHomePosition(pos); + + summon.InitStats(duration); + AddToMap(summon.ToCreature()); + summon.InitSummon(); + + // call MoveInLineOfSight for nearby creatures + AIRelocationNotifier notifier = new AIRelocationNotifier(summon); + Cell.VisitAllObjects(summon, notifier, GetVisibilityRange()); + + return summon; + } + + public ulong GenerateLowGuid(HighGuid high) + { + //Contract.Assert(!ObjectGuid.IsMapSpecific(high), "Only map specific guid can be generated in Map context"); + + return GetGuidSequenceGenerator(high).Generate(); + } + + ObjectGuidGenerator GetGuidSequenceGenerator(HighGuid high) + { + if (!_guidGenerators.ContainsKey(high)) + _guidGenerators[high] = new ObjectGuidGenerator(high); + + return _guidGenerators[high]; + } + + public void AddUpdateObject(WorldObject obj) + { + _updateObjects.Add(obj); + } + + public void RemoveUpdateObject(WorldObject obj) + { + _updateObjects.Remove(obj); + } + + public static implicit operator bool (Map map) + { + return map != null; + } + + #region Scripts + + // Put scripts in the execution queue + public void ScriptsStart(ScriptsType scriptsType, uint id, WorldObject source, WorldObject target) + { + var scripts = Global.ObjectMgr.GetScriptsMapByType(scriptsType); + + // Find the script map + MultiMap list = scripts.LookupByKey(id); + if (list == null) + return; + + // prepare static data + ObjectGuid sourceGUID = source != null ? source.GetGUID() : ObjectGuid.Empty; //some script commands doesn't have source + ObjectGuid targetGUID = target != null ? target.GetGUID() : ObjectGuid.Empty; + ObjectGuid ownerGUID = (source != null && source.GetTypeId() == TypeId.Item) ? ((Item)source).GetOwnerGUID() : ObjectGuid.Empty; + + // Schedule script execution for all scripts in the script map + bool immedScript = false; + foreach (var script in list) + { + ScriptAction sa; + sa.sourceGUID = sourceGUID; + sa.targetGUID = targetGUID; + sa.ownerGUID = ownerGUID; + + sa.script = script.Value; + m_scriptSchedule.Add(Global.WorldMgr.GetGameTime() + script.Key, sa); + if (script.Key == 0) + immedScript = true; + + Global.MapMgr.IncreaseScheduledScriptsCount(); + } + // If one of the effects should be immediate, launch the script execution + if (immedScript && !i_scriptLock) + { + i_scriptLock = true; + ScriptsProcess(); + i_scriptLock = false; + } + } + + public void ScriptCommandStart(ScriptInfo script, uint delay, WorldObject source, WorldObject target) + { + // NOTE: script record _must_ exist until command executed + + // prepare static data + ObjectGuid sourceGUID = source != null ? source.GetGUID() : ObjectGuid.Empty; + ObjectGuid targetGUID = target != null ? target.GetGUID() : ObjectGuid.Empty; + ObjectGuid ownerGUID = (source != null && source.GetTypeId() == TypeId.Item) ? ((Item)source).GetOwnerGUID() : ObjectGuid.Empty; + + var sa = new ScriptAction(); + sa.sourceGUID = sourceGUID; + sa.targetGUID = targetGUID; + sa.ownerGUID = ownerGUID; + + sa.script = script; + m_scriptSchedule.Add(Global.WorldMgr.GetGameTime() + delay, sa); + + Global.MapMgr.IncreaseScheduledScriptsCount(); + + // If effects should be immediate, launch the script execution + if (delay == 0 && !i_scriptLock) + { + i_scriptLock = true; + ScriptsProcess(); + i_scriptLock = false; + } + } + + // Helpers for ScriptProcess method. + private Player _GetScriptPlayerSourceOrTarget(WorldObject source, WorldObject target, ScriptInfo scriptInfo) + { + Player player = null; + if (source == null && target == null) + Log.outError(LogFilter.Scripts, "{0} source and target objects are NULL.", scriptInfo.GetDebugInfo()); + else + { + // Check target first, then source. + if (target != null) + player = target.ToPlayer(); + if (player == null && source != null) + player = source.ToPlayer(); + + if (player == null) + Log.outError(LogFilter.Scripts, "{0} neither source nor target object is player (source: TypeId: {1}, Entry: {2}, {3}; target: TypeId: {4}, Entry: {5}, {6}), skipping.", + scriptInfo.GetDebugInfo(), source ? source.GetTypeId() : 0, source ? source.GetEntry() : 0, source ? source.GetGUID().ToString() : "", + target ? target.GetTypeId() : 0, target ? target.GetEntry() : 0, target ? target.GetGUID().ToString() : ""); + } + return player; + } + + private Creature _GetScriptCreatureSourceOrTarget(WorldObject source, WorldObject target, ScriptInfo scriptInfo, bool bReverse = false) + { + Creature creature = null; + if (source == null && target == null) + Log.outError(LogFilter.Scripts, "{0} source and target objects are NULL.", scriptInfo.GetDebugInfo()); + else + { + if (bReverse) + { + // Check target first, then source. + if (target != null) + creature = target.ToCreature(); + if (creature == null && source != null) + creature = source.ToCreature(); + } + else + { + // Check source first, then target. + if (source != null) + creature = source.ToCreature(); + if (creature == null && target != null) + creature = target.ToCreature(); + } + + if (creature == null) + Log.outError(LogFilter.Scripts, "{0} neither source nor target are creatures (source: TypeId: {1}, Entry: {2}, {3}; target: TypeId: {4}, Entry: {5}, {6}), skipping.", + scriptInfo.GetDebugInfo(), source ? source.GetTypeId() : 0, source ? source.GetEntry() : 0, source ? source.GetGUID().ToString() : "", + target ? target.GetTypeId() : 0, target ? target.GetEntry() : 0, target ? target.GetGUID().ToString() : ""); + } + return creature; + } + + private Unit _GetScriptUnit(WorldObject obj, bool isSource, ScriptInfo scriptInfo) + { + Unit unit = null; + if (obj == null) + Log.outError(LogFilter.Scripts, "{0} {1} object is NULL.", scriptInfo.GetDebugInfo(), + isSource ? "source" : "target"); + else if (!obj.isTypeMask(TypeMask.Unit)) + Log.outError(LogFilter.Scripts, + "{0} {1} object is not unit (TypeId: {2}, Entry: {3}, GUID: {4}), skipping.", scriptInfo.GetDebugInfo(), isSource ? "source" : "target", obj.GetTypeId(), obj.GetEntry(), obj.GetGUID().ToString()); + else + { + unit = obj.ToUnit(); + if (unit == null) + Log.outError(LogFilter.Scripts, "{0} {1} object could not be casted to unit.", scriptInfo.GetDebugInfo(), isSource ? "source" : "target"); + } + return unit; + } + + private Player _GetScriptPlayer(WorldObject obj, bool isSource, ScriptInfo scriptInfo) + { + Player player = null; + if (obj == null) + Log.outError(LogFilter.Scripts, "{0} {1} object is NULL.", scriptInfo.GetDebugInfo(), + isSource ? "source" : "target"); + else + { + player = obj.ToPlayer(); + if (player == null) + Log.outError(LogFilter.Scripts, "{0} {1} object is not a player (TypeId: {2}, Entry: {3}, GUID: {4}).", + scriptInfo.GetDebugInfo(), isSource ? "source" : "target", obj.GetTypeId(), obj.GetEntry(), obj.GetGUID().ToString()); + } + return player; + } + + private Creature _GetScriptCreature(WorldObject obj, bool isSource, ScriptInfo scriptInfo) + { + Creature creature = null; + if (obj == null) + Log.outError(LogFilter.Scripts, "{0} {1} object is NULL.", scriptInfo.GetDebugInfo(), isSource ? "source" : "target"); + else + { + creature = obj.ToCreature(); + if (creature == null) + Log.outError(LogFilter.Scripts, + "{0} {1} object is not a creature (TypeId: {2}, Entry: {3}, GUID: {4}).", scriptInfo.GetDebugInfo(), isSource ? "source" : "target", obj.GetTypeId(), obj.GetEntry(), obj.GetGUID().ToString()); + } + return creature; + } + + private WorldObject _GetScriptWorldObject(WorldObject obj, bool isSource, ScriptInfo scriptInfo) + { + WorldObject pWorldObject = null; + if (obj == null) + Log.outError(LogFilter.Scripts, "{0} {1} object is NULL.", scriptInfo.GetDebugInfo(), isSource ? "source" : "target"); + else + { + pWorldObject = obj; + if (pWorldObject == null) + Log.outError(LogFilter.Scripts, + "{0} {1} object is not a world object (TypeId: {2}, Entry: {3}, GUID: {4}).", scriptInfo.GetDebugInfo(), isSource ? "source" : "target", obj.GetTypeId(), obj.GetEntry(), obj.GetGUID().ToString()); + } + return pWorldObject; + } + + void _ScriptProcessDoor(WorldObject source, WorldObject target, ScriptInfo scriptInfo) + { + bool bOpen = false; + ulong guid = scriptInfo.ToggleDoor.GOGuid; + int nTimeToToggle = Math.Max(15, (int)scriptInfo.ToggleDoor.ResetDelay); + switch (scriptInfo.command) + { + case ScriptCommands.OpenDoor: + bOpen = true; + break; + case ScriptCommands.CloseDoor: + break; + default: + Log.outError(LogFilter.Scripts, "{0} unknown command for _ScriptProcessDoor.", scriptInfo.GetDebugInfo()); + return; + } + if (guid == 0) + Log.outError(LogFilter.Scripts, "{0} door guid is not specified.", scriptInfo.GetDebugInfo()); + else if (source == null) + Log.outError(LogFilter.Scripts, "{0} source object is NULL.", scriptInfo.GetDebugInfo()); + else if (!source.isTypeMask(TypeMask.Unit)) + Log.outError(LogFilter.Scripts, + "{0} source object is not unit (TypeId: {1}, Entry: {2}, GUID: {3}), skipping.", scriptInfo.GetDebugInfo(), source.GetTypeId(), source.GetEntry(), source.GetGUID().ToString()); + else + { + if (source == null) + Log.outError(LogFilter.Scripts, + "{0} source object could not be casted to world object (TypeId: {1}, Entry: {2}, GUID: {3}), skipping.", scriptInfo.GetDebugInfo(), source.GetTypeId(), source.GetEntry(), source.GetGUID().ToString()); + else + { + GameObject pDoor = _FindGameObject(source, guid); + if (pDoor == null) + Log.outError(LogFilter.Scripts, "{0} gameobject was not found (guid: {1}).", scriptInfo.GetDebugInfo(), guid); + else if (pDoor.GetGoType() != GameObjectTypes.Door) + Log.outError(LogFilter.Scripts, "{0} gameobject is not a door (GoType: {1}, Entry: {2}, GUID: {3}).", scriptInfo.GetDebugInfo(), pDoor.GetGoType(), pDoor.GetEntry(), pDoor.GetGUID().ToString()); + else if (bOpen == (pDoor.GetGoState() == GameObjectState.Ready)) + { + pDoor.UseDoorOrButton((uint)nTimeToToggle); + + if (target != null && target.isTypeMask(TypeMask.GameObject)) + { + GameObject goTarget = target.ToGameObject(); + if (goTarget != null && goTarget.GetGoType() == GameObjectTypes.Button) + goTarget.UseDoorOrButton((uint)nTimeToToggle); + } + } + } + } + } + + private GameObject _FindGameObject(WorldObject searchObject, ulong guid) + { + var bounds = searchObject.GetMap().GetGameObjectBySpawnIdStore().LookupByKey(guid); + if (bounds.Empty()) + return null; + + return bounds[0]; + } + + // Process queued scripts + void ScriptsProcess() + { + if (m_scriptSchedule.Empty()) + return; + + // Process overdue queued scripts + KeyValuePair iter = m_scriptSchedule.First(); + while (!m_scriptSchedule.Empty() && (iter.Key <= Global.WorldMgr.GetGameTime())) + { + ScriptAction step = iter.Value; + + WorldObject source = null; + if (!step.sourceGUID.IsEmpty()) + { + switch (step.sourceGUID.GetHigh()) + { + case HighGuid.Item: // as well as HIGHGUID_CONTAINER + Player player = Global.ObjAccessor.FindPlayer(step.ownerGUID); + if (player != null) + source = player.GetItemByGuid(step.sourceGUID); + break; + case HighGuid.Creature: + case HighGuid.Vehicle: + source = GetCreature(step.sourceGUID); + break; + case HighGuid.Pet: + source = GetPet(step.sourceGUID); + break; + case HighGuid.Player: + source = Global.ObjAccessor.FindPlayer(step.sourceGUID); + break; + case HighGuid.GameObject: + case HighGuid.Transport: + source = GetGameObject(step.sourceGUID); + break; + case HighGuid.Corpse: + source = GetCorpse(step.sourceGUID); + break; + default: + Log.outError(LogFilter.Scripts, "{0} source with unsupported high guid (GUID: {1}, high guid: {2}).", + step.script.GetDebugInfo(), step.sourceGUID, step.sourceGUID.ToString()); + break; + } + } + + WorldObject target = null; + if (!step.targetGUID.IsEmpty()) + { + switch (step.targetGUID.GetHigh()) + { + case HighGuid.Creature: + case HighGuid.Vehicle: + target = GetCreature(step.targetGUID); + break; + case HighGuid.Pet: + target = GetPet(step.targetGUID); + break; + case HighGuid.Player: + target = Global.ObjAccessor.FindPlayer(step.targetGUID); + break; + case HighGuid.GameObject: + case HighGuid.Transport: + target = GetGameObject(step.targetGUID); + break; + case HighGuid.Corpse: + target = GetCorpse(step.targetGUID); + break; + default: + Log.outError(LogFilter.Scripts, "{0} target with unsupported high guid {1}.", step.script.GetDebugInfo(), step.targetGUID.ToString()); + break; + } + } + + switch (step.script.command) + { + case ScriptCommands.Talk: + { + if (step.script.Talk.ChatType > ChatMsg.Whisper && step.script.Talk.ChatType != ChatMsg.RaidBossWhisper) + { + Log.outError(LogFilter.Scripts, "{0} invalid chat type ({1}) specified, skipping.", + step.script.GetDebugInfo(), step.script.Talk.ChatType); + break; + } + + if (step.script.Talk.Flags.HasAnyFlag(eScriptFlags.TalkUsePlayer)) + source = _GetScriptPlayerSourceOrTarget(source, target, step.script); + else + source = _GetScriptCreatureSourceOrTarget(source, target, step.script); + + if (source) + { + Unit sourceUnit = source.ToUnit(); + if (!sourceUnit) + { + Log.outError(LogFilter.Scripts, "{0} source object ({1}) is not an unit, skipping.", step.script.GetDebugInfo(), source.GetGUID().ToString()); + break; + } + + switch (step.script.Talk.ChatType) + { + case ChatMsg.Say: + sourceUnit.Say((uint)step.script.Talk.TextID, target); + break; + case ChatMsg.Yell: + sourceUnit.Yell((uint)step.script.Talk.TextID, target); + break; + case ChatMsg.Emote: + case ChatMsg.RaidBossEmote: + sourceUnit.TextEmote((uint)step.script.Talk.TextID, target, step.script.Talk.ChatType == ChatMsg.RaidBossEmote); + break; + case ChatMsg.Whisper: + case ChatMsg.RaidBossWhisper: + { + Player receiver = target ? target.ToPlayer() : null; + if (!receiver) + Log.outError(LogFilter.Scripts, "{0} attempt to whisper to non-player unit, skipping.", step.script.GetDebugInfo()); + else + sourceUnit.Whisper((uint)step.script.Talk.TextID, receiver, step.script.Talk.ChatType == ChatMsg.RaidBossWhisper); + break; + } + default: + break; // must be already checked at load + } + } + break; + } + case ScriptCommands.Emote: + { + // Source or target must be Creature. + Creature cSource = _GetScriptCreatureSourceOrTarget(source, target, step.script); + if (cSource) + { + if (step.script.Emote.Flags.HasAnyFlag(eScriptFlags.EmoteUseState)) + cSource.SetUInt32Value(UnitFields.NpcEmotestate, step.script.Emote.EmoteID); + else + cSource.HandleEmoteCommand((Emote)step.script.Emote.EmoteID); + } + break; + } + case ScriptCommands.FieldSet: + { + // Source or target must be Creature. + Creature cSource = _GetScriptCreatureSourceOrTarget(source, target, step.script); + if (cSource) + { + // Validate field number. + if (step.script.FieldSet.FieldID <= (int)ObjectFields.Entry || + step.script.FieldSet.FieldID >= cSource.valuesCount) + Log.outError(LogFilter.Scripts, + "{0} wrong field {1} (max count: {2}) in object (TypeId: {3}, Entry: {4}, GUID: {5}) specified, skipping.", + step.script.GetDebugInfo(), step.script.FieldSet.FieldID, + cSource.valuesCount, cSource.GetTypeId(), cSource.GetEntry(), cSource.GetGUID().ToString()); + else + cSource.SetUInt32Value(step.script.FieldSet.FieldID, step.script.FieldSet.FieldValue); + } + break; + } + case ScriptCommands.MoveTo: + { + // Source or target must be Creature. + Creature cSource = _GetScriptCreatureSourceOrTarget(source, target, step.script); + if (cSource) + { + Unit unit = cSource.ToUnit(); + if (step.script.MoveTo.TravelTime != 0) + { + float speed = + unit.GetDistance(step.script.MoveTo.DestX, step.script.MoveTo.DestY, + step.script.MoveTo.DestZ) / (step.script.MoveTo.TravelTime * 0.001f); + unit.MonsterMoveWithSpeed(step.script.MoveTo.DestX, step.script.MoveTo.DestY, + step.script.MoveTo.DestZ, speed); + } + else + unit.NearTeleportTo(step.script.MoveTo.DestX, step.script.MoveTo.DestY, + step.script.MoveTo.DestZ, unit.GetOrientation()); + } + break; + } + case ScriptCommands.FlagSet: + { + // Source or target must be Creature. + Creature cSource = _GetScriptCreatureSourceOrTarget(source, target, step.script); + if (cSource) + { + // Validate field number. + if (step.script.FlagToggle.FieldID <= (int)ObjectFields.Entry || + step.script.FlagToggle.FieldID >= cSource.valuesCount) + Log.outError(LogFilter.Scripts, + "{0} wrong field {1} (max count: {2}) in object (TypeId: {3}, Entry: {4}, GUID: {5}) specified, skipping.", + step.script.GetDebugInfo(), step.script.FlagToggle.FieldID, + cSource.valuesCount, cSource.GetTypeId(), cSource.GetEntry(), cSource.GetGUID().ToString()); + else + cSource.SetFlag(step.script.FlagToggle.FieldID, step.script.FlagToggle.FieldValue); + } + break; + } + case ScriptCommands.FlagRemove: + { + // Source or target must be Creature. + Creature cSource = _GetScriptCreatureSourceOrTarget(source, target, step.script); + if (cSource) + { + // Validate field number. + if (step.script.FlagToggle.FieldID <= (int)ObjectFields.Entry || + step.script.FlagToggle.FieldID >= cSource.valuesCount) + Log.outError(LogFilter.Scripts, + "{0} wrong field {1} (max count: {2}) in object (TypeId: {3}, Entry: {4}, GUID: {5}) specified, skipping.", + step.script.GetDebugInfo(), step.script.FlagToggle.FieldID, + cSource.valuesCount, cSource.GetTypeId(), cSource.GetEntry(), cSource.GetGUID().ToString()); + else + cSource.RemoveFlag(step.script.FlagToggle.FieldID, step.script.FlagToggle.FieldValue); + } + break; + } + case ScriptCommands.TeleportTo: + { + if (step.script.TeleportTo.Flags.HasAnyFlag(eScriptFlags.TeleportUseCreature)) + { + // Source or target must be Creature. + Creature cSource = _GetScriptCreatureSourceOrTarget(source, target, step.script); + if (cSource) + cSource.NearTeleportTo(step.script.TeleportTo.DestX, step.script.TeleportTo.DestY, + step.script.TeleportTo.DestZ, step.script.TeleportTo.Orientation); + } + else + { + // Source or target must be Player. + Player player = _GetScriptPlayerSourceOrTarget(source, target, step.script); + if (player) + player.TeleportTo(step.script.TeleportTo.MapID, step.script.TeleportTo.DestX, + step.script.TeleportTo.DestY, step.script.TeleportTo.DestZ, step.script.TeleportTo.Orientation); + } + break; + } + case ScriptCommands.QuestExplored: + { + if (!source) + { + Log.outError(LogFilter.Scripts, "{0} source object is NULL.", step.script.GetDebugInfo()); + break; + } + if (!target) + { + Log.outError(LogFilter.Scripts, "{0} target object is NULL.", step.script.GetDebugInfo()); + break; + } + + // when script called for item spell casting then target == (unit or GO) and source is player + WorldObject worldObject; + Player player = target.ToPlayer(); + if (player != null) + { + if (!source.IsTypeId(TypeId.Unit) && !source.IsTypeId(TypeId.GameObject) && !source.IsTypeId(TypeId.Player)) + { + Log.outError(LogFilter.Scripts, "{0} source is not unit, gameobject or player (TypeId: {1}, Entry: {2}, GUID: {3}), skipping.", + step.script.GetDebugInfo(), source.GetTypeId(), source.GetEntry(), source.GetGUID().ToString()); + break; + } + worldObject = source; + } + else + { + player = source.ToPlayer(); + if (player != null) + { + if (!target.IsTypeId(TypeId.Unit) && !target.IsTypeId(TypeId.GameObject) && !target.IsTypeId(TypeId.Player)) + { + Log.outError(LogFilter.Scripts, + "{0} target is not unit, gameobject or player (TypeId: {1}, Entry: {2}, GUID: {3}), skipping.", step.script.GetDebugInfo(), target.GetTypeId(), target.GetEntry(), target.GetGUID().ToString()); + break; + } + worldObject = target; + } + else + { + Log.outError(LogFilter.Scripts, "{0} neither source nor target is player (Entry: {0}, GUID: {1}; target: Entry: {2}, GUID: {3}), skipping.", + step.script.GetDebugInfo(), source.GetEntry(), source.GetGUID().ToString(), target.GetEntry(), target.GetGUID().ToString()); + break; + } + } + + // quest id and flags checked at script loading + if ((!worldObject.IsTypeId(TypeId.Unit) || worldObject.ToUnit().IsAlive()) && + (step.script.QuestExplored.Distance == 0 || + worldObject.IsWithinDistInMap(player, step.script.QuestExplored.Distance))) + player.AreaExploredOrEventHappens(step.script.QuestExplored.QuestID); + else + player.FailQuest(step.script.QuestExplored.QuestID); + + break; + } + + case ScriptCommands.KillCredit: + { + // Source or target must be Player. + Player player = _GetScriptPlayerSourceOrTarget(source, target, step.script); + if (player) + { + if (step.script.KillCredit.Flags.HasAnyFlag(eScriptFlags.KillcreditRewardGroup)) + player.RewardPlayerAndGroupAtEvent(step.script.KillCredit.CreatureEntry, player); + else + player.KilledMonsterCredit(step.script.KillCredit.CreatureEntry, ObjectGuid.Empty); + } + break; + } + case ScriptCommands.RespawnGameobject: + { + if (step.script.RespawnGameObject.GOGuid == 0) + { + Log.outError(LogFilter.Scripts, "{0} gameobject guid (datalong) is not specified.", step.script.GetDebugInfo()); + break; + } + + // Source or target must be WorldObject. + WorldObject pSummoner = _GetScriptWorldObject(source, true, step.script); + if (pSummoner) + { + GameObject pGO = _FindGameObject(pSummoner, step.script.RespawnGameObject.GOGuid); + if (pGO == null) + { + Log.outError(LogFilter.Scripts, "{0} gameobject was not found (guid: {1}).", step.script.GetDebugInfo(), step.script.RespawnGameObject.GOGuid); + break; + } + + if (pGO.GetGoType() == GameObjectTypes.FishingNode || + pGO.GetGoType() == GameObjectTypes.Door || pGO.GetGoType() == GameObjectTypes.Button || + pGO.GetGoType() == GameObjectTypes.Trap) + { + Log.outError(LogFilter.Scripts, + "{0} can not be used with gameobject of type {1} (guid: {2}).", step.script.GetDebugInfo(), pGO.GetGoType(), step.script.RespawnGameObject.GOGuid); + break; + } + + // Check that GO is not spawned + if (!pGO.isSpawned()) + { + int nTimeToDespawn = Math.Max(5, (int)step.script.RespawnGameObject.DespawnDelay); + pGO.SetLootState(LootState.Ready); + pGO.SetRespawnTime(nTimeToDespawn); + + pGO.GetMap().AddToMap(pGO); + } + } + break; + } + case ScriptCommands.TempSummonCreature: + { + // Source must be WorldObject. + WorldObject pSummoner = _GetScriptWorldObject(source, true, step.script); + if (pSummoner) + { + if (step.script.TempSummonCreature.CreatureEntry == 0) + Log.outError(LogFilter.Scripts, "{0} creature entry (datalong) is not specified.", step.script.GetDebugInfo()); + else + { + float x = step.script.TempSummonCreature.PosX; + float y = step.script.TempSummonCreature.PosY; + float z = step.script.TempSummonCreature.PosZ; + float o = step.script.TempSummonCreature.Orientation; + + if (pSummoner.SummonCreature(step.script.TempSummonCreature.CreatureEntry, x, y, z, o, TempSummonType.TimedOrDeadDespawn, step.script.TempSummonCreature.DespawnDelay) == null) + Log.outError(LogFilter.Scripts, "{0} creature was not spawned (entry: {1}).", step.script.GetDebugInfo(), step.script.TempSummonCreature.CreatureEntry); + } + } + break; + } + + case ScriptCommands.OpenDoor: + case ScriptCommands.CloseDoor: + _ScriptProcessDoor(source, target, step.script); + break; + case ScriptCommands.ActivateObject: + { + // Source must be Unit. + Unit unit = _GetScriptUnit(source, true, step.script); + if (unit) + { + // Target must be GameObject. + if (target != null) + { + Log.outError(LogFilter.Scripts, "{0} target object is NULL.", step.script.GetDebugInfo()); + break; + } + + if (!target.IsTypeId(TypeId.GameObject)) + { + Log.outError(LogFilter.Scripts, + "{0} target object is not gameobject (TypeId: {1}, Entry: {2}, GUID: {3}), skipping.", step.script.GetDebugInfo(), target.GetTypeId(), target.GetEntry(), + target.GetGUID().ToString()); + break; + } + GameObject pGO = target.ToGameObject(); + if (pGO) + pGO.Use(unit); + } + break; + } + case ScriptCommands.RemoveAura: + { + // Source (datalong2 != 0) or target (datalong2 == 0) must be Unit. + bool bReverse = step.script.RemoveAura.Flags.HasAnyFlag(eScriptFlags.RemoveauraReverse); + Unit unit = _GetScriptUnit(bReverse ? source : target, bReverse, step.script); + if (unit) + unit.RemoveAurasDueToSpell(step.script.RemoveAura.SpellID); + break; + } + case ScriptCommands.CastSpell: + { + //@todo Allow gameobjects to be targets and casters + if (source == null && target == null) + { + Log.outError(LogFilter.Scripts, "{0} source and target objects are NULL.", step.script.GetDebugInfo()); + break; + } + + Unit uSource = null; + Unit uTarget = null; + // source/target cast spell at target/source (script.datalong2: 0: s.t 1: s.s 2: t.t 3: t.s + switch (step.script.CastSpell.Flags) + { + case eScriptFlags.CastspellSourceToTarget: // source . target + uSource = source?.ToUnit(); + uTarget = target?.ToUnit(); + break; + case eScriptFlags.CastspellSourceToSource: // source . source + uSource =source?.ToUnit(); + uTarget = uSource; + break; + case eScriptFlags.CastspellTargetToTarget: // target . target + uSource = target?.ToUnit(); + uTarget = uSource; + break; + case eScriptFlags.CastspellTargetToSource: // target . source + uSource = target?.ToUnit(); + uTarget = source?.ToUnit(); + break; + case eScriptFlags.CastspellSearchCreature: // source . creature with entry + uSource = source?.ToUnit(); + uTarget = uSource?.FindNearestCreature((uint)Math.Abs(step.script.CastSpell.CreatureEntry), step.script.CastSpell.SearchRadius); + break; + } + + if (uSource == null || !uSource.isTypeMask(TypeMask.Unit)) + { + Log.outError(LogFilter.Scripts, "{0} no source unit found for spell {1}", step.script.GetDebugInfo(), step.script.CastSpell.SpellID); + break; + } + + if (uTarget == null || !uTarget.isTypeMask(TypeMask.Unit)) + { + Log.outError(LogFilter.Scripts, "{0} no target unit found for spell {1}", step.script.GetDebugInfo(), step.script.CastSpell.SpellID); + break; + } + + bool triggered = ((int)step.script.CastSpell.Flags != 4) + ? step.script.CastSpell.CreatureEntry.HasAnyFlag((int)eScriptFlags.CastspellTriggered) + : step.script.CastSpell.CreatureEntry < 0; + uSource.CastSpell(uTarget, step.script.CastSpell.SpellID, triggered); + break; + } + + case ScriptCommands.PlaySound: + // Source must be WorldObject. + WorldObject obj = _GetScriptWorldObject(source, true, step.script); + if (obj) + { + // PlaySound.Flags bitmask: 0/1=anyone/target + Player player2 = null; + if (step.script.PlaySound.Flags.HasAnyFlag(eScriptFlags.PlaysoundTargetPlayer)) + { + // Target must be Player. + player2 = _GetScriptPlayer(target, false, step.script); + if (target == null) + break; + } + + // PlaySound.Flags bitmask: 0/2=without/with distance dependent + if (step.script.PlaySound.Flags.HasAnyFlag(eScriptFlags.PlaysoundDistanceSound)) + obj.PlayDistanceSound(step.script.PlaySound.SoundID, player2); + else + obj.PlayDirectSound(step.script.PlaySound.SoundID, player2); + } + break; + + case ScriptCommands.CreateItem: + // Target or source must be Player. + Player pReceiver = _GetScriptPlayerSourceOrTarget(source, target, step.script); + if (pReceiver) + { + var dest = new List(); + InventoryResult msg = pReceiver.CanStoreNewItem(ItemConst.NullBag, ItemConst.NullSlot, dest, step.script.CreateItem.ItemEntry, step.script.CreateItem.Amount); + if (msg == InventoryResult.Ok) + { + Item item = pReceiver.StoreNewItem(dest, step.script.CreateItem.ItemEntry, true); + if (item != null) + pReceiver.SendNewItem(item, step.script.CreateItem.Amount, false, true); + } + else + pReceiver.SendEquipError(msg, null, null, step.script.CreateItem.ItemEntry); + } + break; + + case ScriptCommands.DespawnSelf: + { + // Target or source must be Creature. + Creature cSource = _GetScriptCreatureSourceOrTarget(source, target, step.script, true); + if (cSource) + cSource.DespawnOrUnsummon(step.script.DespawnSelf.DespawnDelay); + break; + } + case ScriptCommands.LoadPath: + { + // Source must be Unit. + Unit unit = _GetScriptUnit(source, true, step.script); + if (unit) + { + if (Global.WaypointMgr.GetPath(step.script.LoadPath.PathID) == null) + Log.outError(LogFilter.Scripts, "{0} source object has an invalid path ({1}), skipping.", step.script.GetDebugInfo(), step.script.LoadPath.PathID); + else + unit.GetMotionMaster().MovePath(step.script.LoadPath.PathID, step.script.LoadPath.IsRepeatable != 0); + } + break; + } + case ScriptCommands.CallscriptToUnit: + { + if (step.script.CallScript.CreatureEntry == 0) + { + Log.outError(LogFilter.Scripts, "{0} creature entry is not specified, skipping.", step.script.GetDebugInfo()); + break; + } + if (step.script.CallScript.ScriptID == 0) + { + Log.outError(LogFilter.Scripts, "{0} script id is not specified, skipping.", step.script.GetDebugInfo()); + break; + } + + Creature cTarget = null; + var creatureBounds = _creatureBySpawnIdStore.LookupByKey(step.script.CallScript.CreatureEntry); + if (!creatureBounds.Empty()) + { + // Prefer alive (last respawned) creature + var foundCreature = creatureBounds.Find(creature => { return creature.IsAlive(); }); + + cTarget = foundCreature ?? creatureBounds[0]; + } + + if (cTarget == null) + { + Log.outError(LogFilter.Scripts, "{0} target was not found (entry: {1})", step.script.GetDebugInfo(), step.script.CallScript.CreatureEntry); + break; + } + + // Insert script into schedule but do not start it + ScriptsStart((ScriptsType)step.script.CallScript.ScriptType, step.script.CallScript.ScriptID, cTarget, null); + break; + } + + case ScriptCommands.Kill: + { + // Source or target must be Creature. + Creature cSource = _GetScriptCreatureSourceOrTarget(source, target, step.script); + if (cSource) + { + if (cSource.IsDead()) + Log.outError(LogFilter.Scripts, "{0} creature is already dead (Entry: {1}, GUID: {2})", step.script.GetDebugInfo(), cSource.GetEntry(), cSource.GetGUID().ToString()); + else + { + cSource.setDeathState(DeathState.JustDied); + if (step.script.Kill.RemoveCorpse == 1) + cSource.RemoveCorpse(); + } + } + break; + } + case ScriptCommands.Orientation: + { + // Source must be Unit. + Unit sourceUnit = _GetScriptUnit(source, true, step.script); + if (sourceUnit) + { + if (step.script.Orientation.Flags.HasAnyFlag(eScriptFlags.OrientationFaceTarget)) + { + // Target must be Unit. + Unit targetUnit = _GetScriptUnit(target, false, step.script); + if (targetUnit == null) + break; + + sourceUnit.SetFacingToObject(targetUnit); + } + else + sourceUnit.SetFacingTo(step.script.Orientation._Orientation); + } + break; + } + case ScriptCommands.Equip: + { + // Source must be Creature. + Creature cSource = _GetScriptCreature(source, target, step.script); + if (cSource) + cSource.LoadEquipment((int)step.script.Equip.EquipmentID); + break; + } + case ScriptCommands.Model: + { + // Source must be Creature. + Creature cSource = _GetScriptCreature(source, target, step.script); + if (cSource) + cSource.SetDisplayId(step.script.Model.ModelID); + break; + } + case ScriptCommands.CloseGossip: + { + // Source must be Player. + Player player = _GetScriptPlayer(source, true, step.script); + if (player != null) + player.PlayerTalkClass.SendCloseGossip(); + break; + } + case ScriptCommands.Playmovie: + { + // Source must be Player. + Player player = _GetScriptPlayer(source, true, step.script); + if (player) + player.SendMovieStart(step.script.PlayMovie.MovieID); + break; + } + case ScriptCommands.Movement: + { + // Source must be Creature. + Creature cSource = _GetScriptCreature(source, true, step.script); + if (cSource) + { + if (!cSource.IsAlive()) + return; + + cSource.GetMotionMaster().MovementExpired(); + cSource.GetMotionMaster().MoveIdle(); + + switch ((MovementGeneratorType)step.script.Movement.MovementType) + { + case MovementGeneratorType.Random: + cSource.GetMotionMaster().MoveRandom(step.script.Movement.MovementDistance); + break; + case MovementGeneratorType.Waypoint: + cSource.GetMotionMaster().MovePath((uint)step.script.Movement.Path, false); + break; + } + } + break; + } + case ScriptCommands.PlayAnimkit: + { + // Source must be Creature. + Creature cSource = _GetScriptCreature(source, true, step.script); + if (cSource) + cSource.PlayOneShotAnimKitId((ushort)step.script.PlayAnimKit.AnimKitID); + break; + } + default: + Log.outError(LogFilter.Scripts, "Unknown script command {0}.", step.script.GetDebugInfo()); + break; + } + + m_scriptSchedule.Remove(iter); + iter = m_scriptSchedule.FirstOrDefault(); + Global.MapMgr.DecreaseScheduledScriptCount(); + } + } + + #endregion + + #region Fields + bool _creatureToMoveLock; + List creaturesToMove = new List(); + + bool _gameObjectsToMoveLock; + List _gameObjectsToMove = new List(); + + bool _dynamicObjectsToMoveLock; + List _dynamicObjectsToMove = new List(); + + bool _areaTriggersToMoveLock; + List _areaTriggersToMove = new List(); + + GridMap[][] GridMaps = new GridMap[MapConst.MaxGrids][]; + Dictionary _creatureRespawnTimes = new Dictionary(); + DynamicMapTree _dynamicTree = new DynamicMapTree(); + + Dictionary _goRespawnTimes = new Dictionary(); + List _transports = new List(); + Grid[][] i_grids = new Grid[MapConst.MaxGrids][]; + MapRecord i_mapRecord; + List i_objectsToRemove = new List(); + Dictionary i_objectsToSwitch = new Dictionary(); + Difficulty i_spawnMode; + List i_worldObjects = new List(); + protected List m_activeNonPlayers = new List(); + protected List m_activePlayers = new List(); + Map m_parentMap; + SortedMultiMap m_scriptSchedule = new SortedMultiMap(); + + BitArray marked_cells = new BitArray(MapConst.TotalCellsPerMap * MapConst.TotalCellsPerMap); + public Dictionary CreatureGroupHolder = new Dictionary(); + internal uint i_InstanceId; + long i_gridExpiry; + List i_objects = new List(); + bool i_scriptLock; + + public int m_VisibilityNotifyPeriod; + public float m_VisibleDistance; + internal uint m_unloadTimer; + + Dictionary _zoneDynamicInfo = new Dictionary(); + uint _defaultLight; + Dictionary _guidGenerators = new Dictionary(); + Dictionary _objectsStore = new Dictionary(); + MultiMap _creatureBySpawnIdStore = new MultiMap(); + MultiMap _gameobjectBySpawnIdStore = new MultiMap(); + MultiMap _corpsesByCell = new MultiMap(); + Dictionary _corpsesByPlayer = new Dictionary(); + List _corpseBones = new List(); + + List _updateObjects = new List(); + #endregion + } + + public class InstanceMap : Map + { + public InstanceMap(uint id, long expiry, uint InstanceId, Difficulty spawnMode, Map _parent) + : base(id, expiry, InstanceId, spawnMode, _parent) + { + //lets initialize visibility distance for dungeons + InitVisibilityDistance(); + + // the timer is started by default, and stopped when the first player joins + // this make sure it gets unloaded if for some reason no player joins + m_unloadTimer = (uint)Math.Max(WorldConfig.GetIntValue(WorldCfg.InstanceUnloadDelay), 1); + } + + public override void InitVisibilityDistance() + { + //init visibility distance for instances + m_VisibleDistance = Global.WorldMgr.GetMaxVisibleDistanceInInstances(); + m_VisibilityNotifyPeriod = Global.WorldMgr.GetVisibilityNotifyPeriodInInstances(); + } + + public override EnterState CannotEnter(Player player) + { + if (player.GetMap() == this) + { + Log.outError(LogFilter.Maps, "InstanceMap:CannotEnter - player {0} ({1}) already in map {2}, {3}, {4}!", player.GetName(), player.GetGUID().ToString(), GetId(), GetInstanceId(), GetSpawnMode()); + Contract.Assert(false); + return EnterState.CannotEnterAlreadyInMap; + } + + // allow GM's to enter + if (player.IsGameMaster()) + return base.CannotEnter(player); + + // cannot enter if the instance is full (player cap), GMs don't count + uint maxPlayers = GetMaxPlayers(); + if (GetPlayersCountExceptGMs() >= maxPlayers) + { + Log.outInfo(LogFilter.Maps, "MAP: Instance '{0}' of map '{1}' cannot have more than '{2}' players. Player '{3}' rejected", GetInstanceId(), GetMapName(), maxPlayers, player.GetName()); + return EnterState.CannotEnterMaxPlayers; + } + + // cannot enter while an encounter is in progress (unless this is a relog, in which case it is permitted) + if (!player.IsLoading() && IsRaid() && GetInstanceScript() != null && GetInstanceScript().IsEncounterInProgress()) + return EnterState.CannotEnterZoneInCombat; + + // cannot enter if player is permanent saved to a different instance id + InstanceBind playerBind = player.GetBoundInstance(GetId(), GetDifficultyID()); + if (playerBind != null) + if (playerBind.perm && playerBind.save != null) + if (playerBind.save.GetInstanceId() != GetInstanceId()) + return EnterState.CannotEnterInstanceBindMismatch; + + return base.CannotEnter(player); + } + + public override bool AddPlayerToMap(Player player, bool initPlayer = true) + { + /// @todo Not sure about checking player level: already done in HandleAreaTriggerOpcode + // GMs still can teleport player in instance. + // Is it needed? + { + // Dungeon only code + if (IsDungeon()) + { + Group group = player.GetGroup(); + + // increase current instances (hourly limit) + if (!group || !group.isLFGGroup()) + player.AddInstanceEnterTime(GetInstanceId(), Time.UnixTime); + + // get or create an instance save for the map + InstanceSave mapSave = Global.InstanceSaveMgr.GetInstanceSave(GetInstanceId()); + if (mapSave == null) + { + Log.outInfo(LogFilter.Maps, "InstanceMap.Add: creating instance save for map {0} spawnmode {1} with instance id {2}", GetId(), GetSpawnMode(), GetInstanceId()); + mapSave = Global.InstanceSaveMgr.AddInstanceSave(GetId(), GetInstanceId(), GetSpawnMode(), 0, 0, true); + } + + Contract.Assert(mapSave != null); + + // check for existing instance binds + InstanceBind playerBind = player.GetBoundInstance(GetId(), GetSpawnMode()); + if (playerBind != null && playerBind.perm) + { + // cannot enter other instances if bound permanently + if (playerBind.save != mapSave) + { + Log.outError(LogFilter.Maps, "InstanceMap.Add: player {0}({1}) is permanently bound to instance {2} {3}, {4}, {5}, {6}, {7}, {8} but he is being put into instance {9} {10}, {11}, {12}, {13}, {14}, {15}", + player.GetName(), player.GetGUID().ToString(), GetMapName(), playerBind.save.GetMapId(), + playerBind.save.GetInstanceId(), playerBind.save.GetDifficultyID(), + playerBind.save.GetPlayerCount(), playerBind.save.GetGroupCount(), + playerBind.save.CanReset(), GetMapName(), mapSave.GetMapId(), mapSave.GetInstanceId(), + mapSave.GetDifficultyID(), mapSave.GetPlayerCount(), mapSave.GetGroupCount(), + mapSave.CanReset()); + return false; + } + } + else + { + if (group) + { + // solo saves should have been reset when the map was loaded + InstanceBind groupBind = group.GetBoundInstance(this); + if (playerBind != null && playerBind.save != mapSave) + { + Log.outError(LogFilter.Maps, + "InstanceMapAdd: player {0}({1}) is being put into instance {2} {3}, {4}, {5}, {6}, {7}, {8} but he is in group {9} and is bound to instance {10}, {11}, {12}, {13}, {14}, {15}!", + player.GetName(), player.GetGUID().ToString(), GetMapName(), mapSave.GetMapId(), mapSave.GetInstanceId(), + mapSave.GetDifficultyID(), mapSave.GetPlayerCount(), mapSave.GetGroupCount(), + mapSave.CanReset(), group.GetLeaderGUID().ToString(), + playerBind.save.GetMapId(), playerBind.save.GetInstanceId(), + playerBind.save.GetDifficultyID(), playerBind.save.GetPlayerCount(), + playerBind.save.GetGroupCount(), playerBind.save.CanReset()); + if (groupBind != null) + Log.outError(LogFilter.Maps, + "InstanceMap.Add: the group is bound to the instance {0} {1}, {2}, {3}, {4}, {5}, {6}", + GetMapName(), groupBind.save.GetMapId(), groupBind.save.GetInstanceId(), + groupBind.save.GetDifficultyID(), groupBind.save.GetPlayerCount(), + groupBind.save.GetGroupCount(), groupBind.save.CanReset()); + Contract.Assert(false); + return false; + } + // bind to the group or keep using the group save + if (groupBind == null) + group.BindToInstance(mapSave, false); + else + { + // cannot jump to a different instance without resetting it + if (groupBind.save != mapSave) + { + Log.outError(LogFilter.Maps, + "InstanceMap.Add: player {0}({1}) is being put into instance {2}, {3}, {4} but he is in group {5} which is bound to instance {6}, {7}, {8}!", + player.GetName(), player.GetGUID().ToString(), mapSave.GetMapId(), mapSave.GetInstanceId(), + mapSave.GetDifficultyID(), group.GetLeaderGUID().ToString(), + groupBind.save.GetMapId(), groupBind.save.GetInstanceId(), + groupBind.save.GetDifficultyID()); + Log.outError(LogFilter.Maps, "MapSave players: {0}, group count: {1}", + mapSave.GetPlayerCount(), mapSave.GetGroupCount()); + if (groupBind.save != null) + Log.outError(LogFilter.Maps, "GroupBind save players: {0}, group count: {1}", + groupBind.save.GetPlayerCount(), groupBind.save.GetGroupCount()); + else + Log.outError(LogFilter.Maps, "GroupBind save NULL"); + return false; + } + // if the group/leader is permanently bound to the instance + // players also become permanently bound when they enter + if (groupBind.perm) + { + PendingRaidLock pendingRaidLock = new PendingRaidLock(); + pendingRaidLock.TimeUntilLock = 60000; + pendingRaidLock.CompletedMask = i_data != null ? i_data.GetCompletedEncounterMask() : 0; + pendingRaidLock.Extending = false; + pendingRaidLock.WarningOnly = false; // events it throws: 1 : INSTANCE_LOCK_WARNING 0 : INSTANCE_LOCK_STOP / INSTANCE_LOCK_START + player.SendPacket(pendingRaidLock); + player.SetPendingBind(mapSave.GetInstanceId(), 60000); + } + } + } + else + { + // set up a solo bind or continue using it + if (playerBind == null) + player.BindToInstance(mapSave, false); + else + // cannot jump to a different instance without resetting it + Contract.Assert(playerBind.save == mapSave); + } + } + } + + // for normal instances cancel the reset schedule when the + // first player enters (no players yet) + SetResetSchedule(false); + + Log.outInfo(LogFilter.Maps, "MAP: Player '{0}' entered instance '{1}' of map '{2}'", player.GetName(), + GetInstanceId(), GetMapName()); + // initialize unload state + m_unloadTimer = 0; + m_resetAfterUnload = false; + m_unloadWhenEmpty = false; + } + + // this will acquire the same mutex so it cannot be in the previous block + base.AddPlayerToMap(player, initPlayer); + + if (i_data != null) + i_data.OnPlayerEnter(player); + + if (i_scenario != null) + i_scenario.OnPlayerEnter(player); + + return true; + } + + public override void Update(uint diff) + { + base.Update(diff); + + if (i_data != null) + { + i_data.Update(diff); + i_data.UpdateCombatResurrection(diff); + } + + if (i_scenario != null) + i_scenario.Update(diff); + } + + public override void RemovePlayerFromMap(Player player, bool remove) + { + Log.outInfo(LogFilter.Maps, + "MAP: Removing player '{0}' from instance '{1}' of map '{2}' before relocating to another map", + player.GetName(), GetInstanceId(), GetMapName()); + //if last player set unload timer + if (m_unloadTimer == 0 && GetPlayers().Count == 1) + m_unloadTimer = m_unloadWhenEmpty ? 1 : (uint)Math.Max(WorldConfig.GetIntValue(WorldCfg.InstanceUnloadDelay), 1); + + if (i_scenario != null) + i_scenario.OnPlayerExit(player); + + base.RemovePlayerFromMap(player, remove); + // for normal instances schedule the reset after all players have left + SetResetSchedule(true); + } + + public void CreateInstanceData(bool load) + { + if (i_data != null) + return; + + InstanceTemplate mInstance = Global.ObjectMgr.GetInstanceTemplate(GetId()); + if (mInstance != null) + { + i_script_id = mInstance.ScriptId; + i_data = Global.ScriptMgr.CreateInstanceData(this); + } + + if (i_data == null) + return; + + i_data.Initialize(); + + if (load) + { + /// @todo make a global storage for this + PreparedStatement stmt = DB.Characters.GetPreparedStatement(CharStatements.SEL_INSTANCE); + stmt.AddValue(0, GetId()); + stmt.AddValue(1, i_InstanceId); + SQLResult result = DB.Characters.Query(stmt); + + if (!result.IsEmpty()) + { + var data = result.Read(0); + i_data.SetCompletedEncountersMask(result.Read(1)); + if (data != "") + { + Log.outDebug(LogFilter.Maps, "Loading instance data for `{0}` with id {1}", + Global.ObjectMgr.GetScriptName(i_script_id), i_InstanceId); + i_data.Load(data); + } + } + } + } + + public bool Reset(InstanceResetMethod method) + { + // note: since the map may not be loaded when the instance needs to be reset + // the instance must be deleted from the DB by InstanceSaveManager + + if (HavePlayers()) + { + if (method == InstanceResetMethod.All || method == InstanceResetMethod.ChangeDifficulty) + { + // notify the players to leave the instance so it can be reset + foreach (Player player in GetPlayers()) + player.SendResetFailedNotify(GetId()); + } + else + { + bool doUnload = true; + if (method == InstanceResetMethod.Global) + { + // set the homebind timer for players inside (1 minute) + foreach (Player player in GetPlayers()) + { + InstanceBind bind = player.GetBoundInstance(GetId(), GetDifficultyID()); + if (bind != null && bind.extendState != 0 && bind.save.GetInstanceId() == GetInstanceId()) + doUnload = false; + else + player.m_InstanceValid = false; + } + + if (doUnload && HasPermBoundPlayers()) // check if any unloaded players have a nonexpired save to this + doUnload = false; + } + + if (doUnload) + { + // the unload timer is not started + // instead the map will unload immediately after the players have left + m_unloadWhenEmpty = true; + m_resetAfterUnload = true; + } + } + } + else + { + // unloaded at next update + m_unloadTimer = 1; + m_resetAfterUnload = !(method == InstanceResetMethod.Global && HasPermBoundPlayers()); + } + + return GetPlayers().Empty(); + } + + public string GetScriptName() + { + return Global.ObjectMgr.GetScriptName(i_script_id); + } + + public void PermBindAllPlayers() + { + if (!IsDungeon()) + return; + + InstanceSave save = Global.InstanceSaveMgr.GetInstanceSave(GetInstanceId()); + if (save == null) + { + Log.outError(LogFilter.Maps, "Cannot bind players to instance map (Name: {0}, Entry: {1}, Difficulty: {2}, ID: {3}) because no instance save is available!", GetMapName(), GetId(), GetDifficultyID(), GetInstanceId()); + return; + } + + // perm bind all players that are currently inside the instance + foreach (Player player in GetPlayers()) + { + // never instance bind GMs with GM mode enabled + if (player.IsGameMaster()) + continue; + + InstanceBind bind = player.GetBoundInstance(save.GetMapId(), save.GetDifficultyID()); + if (bind != null && bind.perm) + { + if (bind.save != null && bind.save.GetInstanceId() != save.GetInstanceId()) + { + Log.outError(LogFilter.Maps, "Player (GUID: {0}, Name: {1}) is in instance map (Name: {2}, Entry: {3}, Difficulty: {4}, ID: {5}) that is being bound, but already has a save for the map on ID {6}!", + player.GetGUID().GetCounter(), player.GetName(), GetMapName(), save.GetMapId(), save.GetDifficultyID(), save.GetInstanceId(), bind.save.GetInstanceId()); + } + else if (bind.save == null) + { + Log.outError(LogFilter.Maps, "Player (GUID: {0}, Name: {1}) is in instance map (Name: {2}, Entry: {3}, Difficulty: {4}, ID: {5}) that is being bound, but already has a bind (without associated save) for the map!", + player.GetGUID().GetCounter(), player.GetName(), GetMapName(), save.GetMapId(), save.GetDifficultyID(), save.GetInstanceId()); + } + } + else + { + player.BindToInstance(save, true); + InstanceSaveCreated data = new InstanceSaveCreated(); + data.Gm = player.IsGameMaster(); + player.SendPacket(data); + + player.GetSession().SendCalendarRaidLockout(save, true); + + // if group leader is in instance, group also gets bound + Group group = player.GetGroup(); + if (group) + if (group.GetLeaderGUID() == player.GetGUID()) + group.BindToInstance(save, true); + } + } + } + + public override void UnloadAll() + { + Contract.Assert(!HavePlayers()); + + if (m_resetAfterUnload) + { + DeleteRespawnTimes(); + DeleteCorpseData(); + } + + base.UnloadAll(); + } + + public void SendResetWarnings(uint timeLeft) + { + foreach (Player player in GetPlayers()) + player.SendInstanceResetWarning(GetId(), player.GetDifficultyID(GetEntry()), timeLeft, true); + } + + void SetResetSchedule(bool on) + { + // only for normal instances + // the reset time is only scheduled when there are no payers inside + // it is assumed that the reset time will rarely (if ever) change while the reset is scheduled + if (IsDungeon() && !HavePlayers() && !IsRaidOrHeroicDungeon()) + { + InstanceSave save = Global.InstanceSaveMgr.GetInstanceSave(GetInstanceId()); + if (save != null) + Global.InstanceSaveMgr.ScheduleReset(on, save.GetResetTime(), + new InstanceSaveManager.InstResetEvent(0, GetId(), GetSpawnMode(), GetInstanceId())); + else + Log.outError(LogFilter.Maps, + "InstanceMap.SetResetSchedule: cannot turn schedule {0}, there is no save information for instance (map [id: {1}, name: {2}], instance id: {3}, difficulty: {4})", + on ? "on" : "off", GetId(), GetMapName(), GetInstanceId(), GetSpawnMode()); + } + } + + bool HasPermBoundPlayers() + { + PreparedStatement stmt = DB.Characters.GetPreparedStatement(CharStatements.SEL_PERM_BIND_BY_INSTANCE); + stmt.AddValue(0, GetInstanceId()); + return !DB.Characters.Query(stmt).IsEmpty(); + } + + public uint GetMaxPlayers() + { + MapDifficultyRecord mapDiff = GetMapDifficulty(); + if (mapDiff != null && mapDiff.MaxPlayers != 0) + return mapDiff.MaxPlayers; + + return GetEntry().MaxPlayers; + } + + private uint GetMaxResetDelay() + { + MapDifficultyRecord mapDiff = GetMapDifficulty(); + return mapDiff != null ? mapDiff.GetRaidDuration() : 0; + } + + public uint GetScriptId() + { + return i_script_id; + } + + public InstanceScript GetInstanceScript() + { + return i_data; + } + + public InstanceScenario GetInstanceScenario() { return i_scenario; } + public void SetInstanceScenario(InstanceScenario scenario) { i_scenario = scenario; } + + InstanceScript i_data; + uint i_script_id; + InstanceScenario i_scenario; + bool m_resetAfterUnload; + bool m_unloadWhenEmpty; + } + + public class BattlegroundMap : Map + { + public BattlegroundMap(uint id, uint expiry, uint InstanceId, Map _parent, Difficulty spawnMode) + : base(id, expiry, InstanceId, spawnMode, _parent) + { + InitVisibilityDistance(); + } + + public override void InitVisibilityDistance() + { + m_VisibleDistance = Global.WorldMgr.GetMaxVisibleDistanceInBGArenas(); + m_VisibilityNotifyPeriod = Global.WorldMgr.GetVisibilityNotifyPeriodInBGArenas(); + } + + public override EnterState CannotEnter(Player player) + { + if (player.GetMap() == this) + { + Log.outError(LogFilter.Maps, "BGMap:CannotEnter - player {0} is already in map!", player.GetGUID().ToString()); + Contract.Assert(false); + return EnterState.CannotEnterAlreadyInMap; + } + + if (player.GetBattlegroundId() != GetInstanceId()) + return EnterState.CannotEnterInstanceBindMismatch; + + return base.CannotEnter(player); + } + + public override bool AddPlayerToMap(Player player, bool initPlayer = true) + { + player.m_InstanceValid = true; + return base.AddPlayerToMap(player, initPlayer); + } + + public override void RemovePlayerFromMap(Player player, bool remove) + { + Log.outInfo(LogFilter.Maps, + "MAP: Removing player '{0}' from bg '{1}' of map '{2}' before relocating to another map", player.GetName(), + GetInstanceId(), GetMapName()); + base.RemovePlayerFromMap(player, remove); + } + + public void SetUnload() + { + m_unloadTimer = 1; + } + + public override void RemoveAllPlayers() + { + if (HavePlayers()) + foreach (Player player in m_activePlayers) + if (!player.IsBeingTeleportedFar()) + player.TeleportTo(player.GetBattlegroundEntryPoint()); + } + + public Battleground GetBG() { return m_bg; } + public void SetBG(Battleground bg) { m_bg = bg; } + + Battleground m_bg; + } + + public struct ScriptAction + { + public ObjectGuid ownerGUID; + + // owner of source if source is item + public ScriptInfo script; + + public ObjectGuid sourceGUID; + public ObjectGuid targetGUID; + } + + public class ZoneDynamicInfo + { + public uint MusicId; + public WeatherState WeatherId = WeatherState.Fine; + public float WeatherGrade; + public uint OverrideLightId; + public uint LightFadeInTime; + } +} \ No newline at end of file diff --git a/Game/Maps/MapManager.cs b/Game/Maps/MapManager.cs new file mode 100644 index 000000000..f0aaf31b2 --- /dev/null +++ b/Game/Maps/MapManager.cs @@ -0,0 +1,428 @@ +/* + * Copyright (C) 2012-2017 CypherCore + * + * 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, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +using Framework.Constants; +using Game.DataStorage; +using Game.Groups; +using Game.Maps; +using System; +using System.Collections.Generic; +using System.Diagnostics.Contracts; +using System.Linq; + +namespace Game.Entities +{ + public class MapManager : Singleton + { + MapManager() + { + i_gridCleanUpDelay = WorldConfig.GetUIntValue(WorldCfg.IntervalGridclean); + i_timer.SetInterval(WorldConfig.GetIntValue(WorldCfg.IntervalMapupdate)); + } + + public void Initialize() + { + //todo needs alot of support for threadsafe. + //int num_threads= WorldConfig.GetIntValue(WorldCfg.Numthreads); + // Start mtmaps if needed. + //if (num_threads > 0) + //m_updater = new MapUpdater(WorldConfig.GetIntValue(WorldCfg.Numthreads)); + } + + public void InitializeVisibilityDistanceInfo() + { + foreach (var pair in i_maps) + pair.Value.InitVisibilityDistance(); + } + + public Map CreateBaseMap(uint id) + { + Map map = FindBaseMap(id); + + if (map == null) + { + lock (this) + { + + var entry = CliDB.MapStorage.LookupByKey(id); + Contract.Assert(entry != null); + + if (entry.Instanceable()) + map = new MapInstanced(id, i_gridCleanUpDelay); + else + { + map = new Map(id, i_gridCleanUpDelay, 0, Difficulty.None); + map.LoadRespawnTimes(); + map.LoadCorpseData(); + } + + i_maps[id] = map; + } + } + Contract.Assert(map != null); + return map; + } + + public Map FindBaseNonInstanceMap(uint mapId) + { + Map map = FindBaseMap(mapId); + if (map != null && map.Instanceable()) + return null; + return map; + } + + public Map CreateMap(uint id, Player player, uint loginInstanceId = 0) + { + Map m = CreateBaseMap(id); + + if (m != null && m.Instanceable()) + m = ((MapInstanced)m).CreateInstanceForPlayer(id, player, loginInstanceId); + + return m; + } + + public Map FindMap(uint mapid, uint instanceId) + { + Map map = FindBaseMap(mapid); + if (map == null) + return null; + + if (!map.Instanceable()) + return instanceId == 0 ? map : null; + + return ((MapInstanced)map).FindInstanceMap(instanceId); + } + + public EnterState PlayerCannotEnter(uint mapid, Player player, bool loginCheck = false) + { + MapRecord entry = CliDB.MapStorage.LookupByKey(mapid); + if (entry == null) + return EnterState.CannotEnterNoEntry; + + if (!entry.IsDungeon()) + return EnterState.CanEnter; + + InstanceTemplate instance = Global.ObjectMgr.GetInstanceTemplate(mapid); + if (instance == null) + return EnterState.CannotEnterUninstancedDungeon; + + Difficulty targetDifficulty, requestedDifficulty; + targetDifficulty = requestedDifficulty = player.GetDifficultyID(entry); + // Get the highest available difficulty if current setting is higher than the instance allows + MapDifficultyRecord mapDiff = Global.DB2Mgr.GetDownscaledMapDifficultyData(entry.Id, ref targetDifficulty); + if (mapDiff == null) + return EnterState.CannotEnterDifficultyUnavailable; + + //Bypass checks for GMs + if (player.IsGameMaster()) + return EnterState.CanEnter; + + string mapName = entry.MapName[Global.WorldMgr.GetDefaultDbcLocale()]; + + Group group = player.GetGroup(); + if (entry.IsRaid()) // can only enter in a raid group + if ((!group || !group.isRaidGroup()) && WorldConfig.GetBoolValue(WorldCfg.InstanceIgnoreRaid)) + return EnterState.CannotEnterNotInRaid; + + if (!player.IsAlive()) + { + if (player.HasCorpse()) + { + // let enter in ghost mode in instance that connected to inner instance with corpse + uint corpseMap = player.GetCorpseLocation().GetMapId(); + do + { + if (corpseMap == mapid) + break; + + InstanceTemplate corpseInstance = Global.ObjectMgr.GetInstanceTemplate(corpseMap); + corpseMap = corpseInstance != null ? corpseInstance.Parent : 0; + } while (corpseMap != 0); + + if (corpseMap == 0) + return EnterState.CannotEnterCorpseInDifferentInstance; + + Log.outDebug(LogFilter.Maps, "MAP: Player '{0}' has corpse in instance '{1}' and can enter.", player.GetName(), mapName); + } + else + Log.outDebug(LogFilter.Maps, "Map.CanPlayerEnter - player '{0}' is dead but does not have a corpse!", player.GetName()); + } + + //Get instance where player's group is bound & its map + if (!loginCheck && group) + { + InstanceBind boundInstance = group.GetBoundInstance(entry); + if (boundInstance != null && boundInstance.save != null) + { + Map boundMap = FindMap(mapid, boundInstance.save.GetInstanceId()); + if (boundMap != null) + { + EnterState denyReason = boundMap.CannotEnter(player); + if (denyReason != 0) + return denyReason; + } + } + } + // players are only allowed to enter 10 instances per hour + if (entry.IsDungeon() && (player.GetGroup() == null || (player.GetGroup() != null && !player.GetGroup().isLFGGroup()))) + { + uint instaceIdToCheck = 0; + InstanceSave save = player.GetInstanceSave(mapid); + if (save != null) + instaceIdToCheck = save.GetInstanceId(); + + // instanceId can never be 0 - will not be found + if (!player.CheckInstanceCount(instaceIdToCheck) && !player.IsDead()) + return EnterState.CannotEnterTooManyInstances; + } + + //Other requirements + if (player.Satisfy(Global.ObjectMgr.GetAccessRequirement(mapid, targetDifficulty), mapid, true)) + return EnterState.CanEnter; + else + return EnterState.CannotEnterUnspecifiedReason; + } + + public void Update(uint diff) + { + i_timer.Update(diff); + if (!i_timer.Passed()) + return; + + var time = (uint)i_timer.GetCurrent(); + foreach (var map in i_maps.Values.ToList()) + { + //if (!m_updater) + //m_updater.Enqueue(map, (uint)i_timer.GetCurrent()); + //else + map.Update(time); + } + + //List tasks = new List(); + //foreach (var map in i_maps.Values.ToList()) + { + //tasks.Add(System.Threading.Tasks.Task.Factory.StartNew(() => map.Update(time))); + } + //System.Threading.Tasks.Task.WaitAll(tasks.ToArray()); + //if (!m_updater) + // m_updater.Wait(); + + foreach (var map in i_maps.ToList()) + map.Value.DelayedUpdate(time); + + i_timer.SetCurrent(0); + } + + public bool ExistMapAndVMap(uint mapid, float x, float y) + { + GridCoord p = GridDefines.ComputeGridCoord(x, y); + + uint gx = 63 - p.x_coord; + uint gy = 63 - p.y_coord; + + return Map.ExistMap(mapid, gx, gy) && Map.ExistVMap(mapid, gx, gy); + } + + public bool IsValidMAP(uint mapid, bool startUp) + { + MapRecord mEntry = CliDB.MapStorage.LookupByKey(mapid); + + if (startUp) + return mEntry != null ? true : false; + else + return mEntry != null && (!mEntry.IsDungeon() || Global.ObjectMgr.GetInstanceTemplate(mapid) != null); + + // TODO: add check for Battlegroundtemplate + } + + public void UnloadAll() + { + foreach (var pair in i_maps.ToList()) + { + pair.Value.UnloadAll(); + i_maps.Remove(pair.Value.GetId()); + } + } + + public uint GetNumInstances() + { + uint ret = 0; + foreach (var pair in i_maps.ToList()) + { + Map map = pair.Value; + if (!map.Instanceable()) + continue; + var maps = ((MapInstanced)map).GetInstancedMaps(); + foreach (var imap in maps) + if (imap.Value.IsDungeon()) + ret++; + } + return ret; + } + + public uint GetNumPlayersInInstances() + { + uint ret = 0; + foreach (var pair in i_maps) + { + Map map = pair.Value; + if (!map.Instanceable()) + continue; + var maps = ((MapInstanced)map).GetInstancedMaps(); + foreach (var imap in maps) + if (imap.Value.IsDungeon()) + ret += (uint)imap.Value.GetPlayers().Count; + } + return ret; + } + + public void InitInstanceIds() + { + _nextInstanceId = 1; + } + + public void RegisterInstanceId(uint instanceId) + { + // Allocation and sizing was done in InitInstanceIds() + _instanceIds[instanceId] = true; + } + + public uint GenerateInstanceId() + { + uint newInstanceId = _nextInstanceId; + + // Find the lowest available id starting from the current NextInstanceId (which should be the lowest according to the logic in FreeInstanceId() + for (uint i = ++_nextInstanceId; i < 0xFFFFFFFF; ++i) + { + if ((i < _instanceIds.Count && !_instanceIds[i]) || i >= _instanceIds.Count) + { + _nextInstanceId = i; + break; + } + } + + if (newInstanceId == _nextInstanceId) + { + Log.outError(LogFilter.Maps, "Instance ID overflow!! Can't continue, shutting down server. "); + Global.WorldMgr.StopNow(); + } + + _instanceIds[newInstanceId] = true; + + return newInstanceId; + } + + public void FreeInstanceId(uint instanceId) + { + // If freed instance id is lower than the next id available for new instances, use the freed one instead + if (instanceId < _nextInstanceId) + SetNextInstanceId(instanceId); + + _instanceIds[instanceId] = false; + } + + public void SetGridCleanUpDelay(uint t) + { + if (t < MapConst.MinGridDelay) + i_gridCleanUpDelay = MapConst.MinGridDelay; + else + i_gridCleanUpDelay = t; + } + + public void SetMapUpdateInterval(int t) + { + if (t < MapConst.MinMapUpdateDelay) + t = MapConst.MinMapUpdateDelay; + + i_timer.SetInterval(t); + i_timer.Reset(); + } + + public uint GetNextInstanceId() { return _nextInstanceId; } + + public void SetNextInstanceId(uint nextInstanceId) { _nextInstanceId = nextInstanceId; } + + Map FindBaseMap(uint mapId) + { + return i_maps.LookupByKey(mapId); + } + + uint GetAreaId(uint mapid, float x, float y, float z) + { + Map m = CreateBaseMap(mapid); + return m.GetAreaId(x, y, z); + } + + public uint GetZoneId(uint mapid, float x, float y, float z) + { + Map m = CreateBaseMap(mapid); + return m.GetZoneId(x, y, z); + } + + public void GetZoneAndAreaId(out uint zoneid, out uint areaid, uint mapid, float x, float y, float z) + { + Map m = CreateBaseMap(mapid); + m.GetZoneAndAreaId(out zoneid, out areaid, x, y, z); + } + + public void DoForAllMaps(Action worker) + { + foreach (var map in i_maps.Values) + { + MapInstanced mapInstanced = map.ToMapInstanced(); + if (mapInstanced) + { + var instances = mapInstanced.GetInstancedMaps(); + foreach (var instance in instances.Values) + worker(instance); + } + else + worker(map); + } + } + + public void DoForAllMapsWithMapId(uint mapId, Action worker) + { + var map = i_maps.LookupByKey(mapId); + if (map != null) + { + MapInstanced mapInstanced = map.ToMapInstanced(); + if (mapInstanced) + { + var instances = mapInstanced.GetInstancedMaps(); + foreach (var instance in instances) + worker(instance.Value); + } + else + worker(map); + } + } + + public void IncreaseScheduledScriptsCount() { ++_scheduledScripts; } + public void DecreaseScheduledScriptCount() { --_scheduledScripts; } + public void DecreaseScheduledScriptCount(uint count) { _scheduledScripts -= count; } + public bool IsScriptScheduled() { return _scheduledScripts > 0; } + + Dictionary i_maps = new Dictionary(); + IntervalTimer i_timer = new IntervalTimer(); + uint i_gridCleanUpDelay; + Dictionary _instanceIds = new Dictionary(); + uint _nextInstanceId; + //MapUpdater m_updater; + uint _scheduledScripts; + } +} diff --git a/Game/Maps/MapUpdater.cs b/Game/Maps/MapUpdater.cs new file mode 100644 index 000000000..b42f68a4d --- /dev/null +++ b/Game/Maps/MapUpdater.cs @@ -0,0 +1,106 @@ +/* + * Copyright (C) 2012-2017 CypherCore + * + * 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, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +using System; +using System.Collections.Generic; +using System.Threading; + +namespace Game.Maps +{ + public class MapUpdater : IDisposable + { + public MapUpdater(int numThreads) + { + _queue = new Queue(); + + autoResetEvent = new AutoResetEvent[numThreads]; + for (var i = 0; i < numThreads; ++i) + { + autoResetEvent[i] = new AutoResetEvent(false); + ThreadPool.QueueUserWorkItem(new WaitCallback(OnEnqueue), autoResetEvent[i]); + } + } + + public void Enqueue(Map map, uint diff) + { + lock (_syncLock) + { + _queue.Enqueue(new MapUpdateRequest(map, diff)); + Monitor.PulseAll(_syncLock); + } + } + + protected void OnEnqueue(object state) + { + while (true) + { + lock (_syncLock) + { + if (_queue.Count == 0) + { + ((AutoResetEvent)state).Set(); + Monitor.Wait(_syncLock); + } + + if (_queue.Count > 0) + { + _queue.Dequeue().Call(); + } + } + } + } + + public void Wait() + { + WaitHandle.WaitAll(autoResetEvent); + } + + public void Dispose() + { + lock (_syncLock) + { + Monitor.PulseAll(_syncLock); + } + } + + private Queue _queue; + private object _syncLock = new object(); + private AutoResetEvent[] autoResetEvent; + } + + public class MapUpdateRequest + { + Map m_map; + uint m_diff; + + public MapUpdateRequest(Map m, uint d) + { + m_map = m; + m_diff = d; + } + + public void Call() + { + m_map.Update(m_diff); + } + + public uint GetId() + { + return m_map.GetId(); + } + } +} \ No newline at end of file diff --git a/Game/Maps/ObjectGridLoader.cs b/Game/Maps/ObjectGridLoader.cs new file mode 100644 index 000000000..8fd025af5 --- /dev/null +++ b/Game/Maps/ObjectGridLoader.cs @@ -0,0 +1,241 @@ +/* + * Copyright (C) 2012-2017 CypherCore + * + * 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, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +using Framework.Constants; +using Game.Entities; +using System.Collections.Generic; + +namespace Game.Maps +{ + class ObjectGridLoader : Notifier + { + public ObjectGridLoader(Grid grid, Map map, Cell cell) + { + i_cell = new Cell(cell); + + i_grid = grid; + i_map = map; + } + + public void LoadN() + { + i_creatures = 0; + i_gameObjects = 0; + i_corpses = 0; + i_cell.data.cell_y = 0; + for (uint x = 0; x < MapConst.MaxCells; ++x) + { + i_cell.data.cell_x = x; + for (uint y = 0; y < MapConst.MaxCells; ++y) + { + i_cell.data.cell_y = y; + + var visitor = new Visitor(this, GridMapTypeMask.AllGrid); + i_grid.VisitGrid(x, y, visitor); + + ObjectWorldLoader worker = new ObjectWorldLoader(this); + visitor = new Visitor(worker, GridMapTypeMask.AllWorld); + i_grid.VisitGrid(x, y, visitor); + } + } + Log.outDebug(LogFilter.Maps, "{0} GameObjects, {1} Creatures, and {2} Corpses/Bones loaded for grid {3} on map {4}", i_gameObjects, i_creatures, i_corpses, i_grid.GetGridId(), i_map.GetId()); + } + + public override void Visit(ICollection objs) + { + CellCoord cellCoord = i_cell.GetCellCoord(); + CellObjectGuids cellguids = Global.ObjectMgr.GetCellObjectGuids(i_map.GetId(), (byte)i_map.GetSpawnMode(), cellCoord.GetId()); + if (cellguids == null) + return; + + LoadHelper(cellguids.gameobjects, cellCoord, ref i_gameObjects, i_map); + } + + public override void Visit(ICollection objs) + { + CellCoord cellCoord = i_cell.GetCellCoord(); + CellObjectGuids cellguids = Global.ObjectMgr.GetCellObjectGuids(i_map.GetId(), (byte)i_map.GetSpawnMode(), cellCoord.GetId()); + if (cellguids == null) + return; + + LoadHelper(cellguids.creatures, cellCoord, ref i_creatures, i_map); + } + + void LoadHelper(SortedSet guid_set, CellCoord cell, ref uint count, Map map) where T : WorldObject, new() + { + foreach (var i_guid in guid_set) + { + T obj = new T(); + ulong guid = i_guid; + if (!obj.LoadFromDB(guid, map)) + continue; + + AddObjectHelper(cell, ref count, map, obj); + } + } + + void AddObjectHelper(CellCoord cellCoord, ref uint count, Map map, T obj) where T : WorldObject + { + var cell = new Cell(cellCoord); + map.AddToGrid(obj, cell); + obj.AddToWorld(); + ++count; + } + + void AddObjectHelper(CellCoord cellCoord, ref uint count, Map map, Creature obj) + { + map.AddToGrid(obj, new Cell(cellCoord)); + obj.AddToWorld(); + if (obj.isActiveObject()) + map.AddToActive(obj); + + ++count; + } + + public Cell i_cell; + public Grid i_grid; + public Map i_map; + uint i_gameObjects; + uint i_creatures; + public uint i_corpses; + } + + class ObjectWorldLoader : Notifier + { + public ObjectWorldLoader(ObjectGridLoader gloader) + { + i_cell = gloader.i_cell; + i_map = gloader.i_map; + i_grid = gloader.i_grid; + i_corpses = gloader.i_corpses; + } + + public override void Visit(ICollection objs) + { + CellCoord cellCoord = i_cell.GetCellCoord(); + var corpses = i_map.GetCorpsesInCell(cellCoord.GetId()); + if (corpses != null) + { + foreach (Corpse corpse in corpses) + { + corpse.AddToWorld(); + var cell = i_grid.GetGridCell(i_cell.GetCellX(), i_cell.GetCellY()); + if (corpse.IsWorldObject()) + { + i_map.AddToGrid(corpse, new Cell(cellCoord)); + cell.AddWorldObject(corpse); + } + else + cell.AddGridObject(corpse); + + ++i_corpses; + } + } + } + + Cell i_cell; + Map i_map; + Grid i_grid; + + public uint i_corpses; + } + + //Stop the creatures before unloading the NGrid + class ObjectGridStoper : Notifier + { + public override void Visit(ICollection objs) + { + // stop any fights at grid de-activation and remove dynobjects/areatriggers created at cast by creatures + foreach (var creature in objs) + { + creature.RemoveAllDynObjects(); + creature.RemoveAllAreaTriggers(); + + if (creature.IsInCombat()) + { + creature.CombatStop(); + creature.DeleteThreatList(); + if (creature.IsAIEnabled) + creature.GetAI().EnterEvadeMode(); + } + } + } + } + + //Move the foreign creatures back to respawn positions before unloading the NGrid + class ObjectGridEvacuator : Notifier + { + public override void Visit(ICollection objs) + { + foreach (var creature in objs) + { + // creature in unloading grid can have respawn point in another grid + // if it will be unloaded then it will not respawn in original grid until unload/load original grid + // move to respawn point to prevent this case. For player view in respawn grid this will be normal respawn. + creature.GetMap().CreatureRespawnRelocation(creature, true); + } + } + public override void Visit(ICollection objs) + { + foreach (var go in objs) + { + // gameobject in unloading grid can have respawn point in another grid + // if it will be unloaded then it will not respawn in original grid until unload/load original grid + // move to respawn point to prevent this case. For player view in respawn grid this will be normal respawn. + go.GetMap().GameObjectRespawnRelocation(go, true); + } + } + } + + //Clean up and remove from world + class ObjectGridCleaner : Notifier + { + public override void Visit(ICollection objs) + { + foreach (var obj in objs) + { + if (obj.IsTypeId(TypeId.Player)) + continue; + + obj.CleanupsBeforeDelete(); + } + } + } + + //Delete objects before deleting NGrid + class ObjectGridUnloader : Notifier + { + public override void Visit(ICollection objs) + { + foreach (var obj in objs) + { + if (obj.IsTypeId(TypeId.Player) || obj.IsTypeId(TypeId.Corpse)) + continue; + + // if option set then object already saved at this moment + if (!WorldConfig.GetBoolValue(WorldCfg.SaveRespawnTimeImmediately)) + obj.SaveRespawnTime(); + //Some creatures may summon other temp summons in CleanupsBeforeDelete() + //So we need this even after cleaner (maybe we can remove cleaner) + //Example: Flame Leviathan Turret 33139 is summoned when a creature is deleted + //TODO: Check if that script has the correct logic. Do we really need to summons something before deleting? + obj.CleanupsBeforeDelete(); + obj.Dispose(); + } + } + } +} diff --git a/Game/Maps/TransportManager.cs b/Game/Maps/TransportManager.cs new file mode 100644 index 000000000..e7c3ebcc5 --- /dev/null +++ b/Game/Maps/TransportManager.cs @@ -0,0 +1,603 @@ +/* + * Copyright (C) 2012-2017 CypherCore + * + * 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, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +using Framework.Constants; +using Framework.Database; +using Framework.GameMath; +using Game.DataStorage; +using Game.Entities; +using Game.Movement; +using System; +using System.Collections.Generic; +using System.Diagnostics.Contracts; +using System.Linq; + +namespace Game.Maps +{ + public class TransportManager : Singleton + { + TransportManager() { } + + void Unload() + { + _transportTemplates.Clear(); + } + + public void LoadTransportTemplates() + { + uint oldMSTime = Time.GetMSTime(); + + SQLResult result = DB.World.Query("SELECT entry FROM gameobject_template WHERE type = 15 ORDER BY entry ASC"); + + if (result.IsEmpty()) + { + Log.outInfo(LogFilter.ServerLoading, "Loaded 0 transports templates. DB table `gameobject_template` has no transports!"); + return; + } + + uint count = 0; + + do + { + uint entry = result.Read(0); + GameObjectTemplate goInfo = Global.ObjectMgr.GetGameObjectTemplate(entry); + if (goInfo == null) + { + Log.outError(LogFilter.Sql, "Transport {0} has no associated GameObjectTemplate from `gameobject_template` , skipped.", entry); + continue; + } + + if (goInfo.MoTransport.taxiPathID >= CliDB.TaxiPathNodesByPath.Keys.Max()) + { + Log.outError(LogFilter.Sql, "Transport {0} (name: {1}) has an invalid path specified in `gameobject_template`.`data0` ({2}) field, skipped.", entry, goInfo.name, goInfo.MoTransport.taxiPathID); + continue; + } + + if (goInfo.MoTransport.taxiPathID == 0) + continue; + + // paths are generated per template, saves us from generating it again in case of instanced transports + TransportTemplate transport = new TransportTemplate(); + transport.entry = entry; + GeneratePath(goInfo, transport); + + _transportTemplates[entry] = transport; + + // transports in instance are only on one map + if (transport.inInstance) + _instanceTransports.Add(transport.mapsUsed.First(), entry); + + ++count; + } while (result.NextRow()); + + + Log.outInfo(LogFilter.ServerLoading, "Loaded {0} transports in {1} ms", count, Time.GetMSTimeDiffToNow(oldMSTime)); + } + + public void LoadTransportAnimationAndRotation() + { + foreach (TransportAnimationRecord anim in CliDB.TransportAnimationStorage.Values) + AddPathNodeToTransport(anim.TransportID, anim.TimeIndex, anim); + + foreach (TransportRotationRecord rot in CliDB.TransportRotationStorage.Values) + AddPathRotationToTransport(rot.TransportID, rot.TimeIndex, rot); + } + + void GeneratePath(GameObjectTemplate goInfo, TransportTemplate transport) + { + uint pathId = goInfo.MoTransport.taxiPathID; + var path = CliDB.TaxiPathNodesByPath[pathId]; + List keyFrames = transport.keyFrames; + List splinePath = new List(); + List allPoints = new List(); + bool mapChange = false; + + for (uint i = 0; i < path.Length; ++i) + allPoints.Add(new Vector3(path[i].Loc.X, path[i].Loc.Y, path[i].Loc.Z)); + + // Add extra points to allow derivative calculations for all path nodes + allPoints.Insert(0, allPoints.First().lerp(allPoints[1], -0.2f)); + allPoints.Add(allPoints.Last().lerp(allPoints[allPoints.Count - 2], -0.2f)); + allPoints.Add(allPoints.Last().lerp(allPoints[allPoints.Count - 2], -1.0f)); + + SplineRawInitializer initer = new SplineRawInitializer(allPoints); + Spline orientationSpline = new Spline(); + orientationSpline.init_spline_custom(initer); + orientationSpline.initLengths(); + + for (uint i = 0; i < path.Length; ++i) + { + if (!mapChange) + { + var node_i = path[i]; + if (i != path.Length - 1 && (node_i.Flags.HasAnyFlag(TaxiPathNodeFlags.Teleport) || node_i.MapID != path[i + 1].MapID)) + { + keyFrames.Last().Teleport = true; + mapChange = true; + } + else + { + KeyFrame k = new KeyFrame(node_i); + Vector3 h; + orientationSpline.Evaluate_Derivative((int)(i + 1), 0.0f, out h); + k.InitialOrientation = Position.NormalizeOrientation((float)Math.Atan2(h.Y, h.X) + MathFunctions.PI); + + keyFrames.Add(k); + splinePath.Add(new Vector3(node_i.Loc.X, node_i.Loc.Y, node_i.Loc.Z)); + if (!transport.mapsUsed.Contains(k.Node.MapID)) + transport.mapsUsed.Add(k.Node.MapID); + } + } + else + mapChange = false; + } + + if (splinePath.Count >= 2) + { + // Remove special catmull-rom spline points + if (!keyFrames.First().IsStopFrame() && keyFrames.First().Node.ArrivalEventID == 0 && keyFrames.First().Node.DepartureEventID == 0) + { + splinePath.RemoveAt(0); + keyFrames.RemoveAt(0); + } + if (!keyFrames.Last().IsStopFrame() && keyFrames.Last().Node.ArrivalEventID == 0 && keyFrames.Last().Node.DepartureEventID == 0) + { + splinePath.RemoveAt(splinePath.Count - 1); + keyFrames.RemoveAt(keyFrames.Count - 1); + } + } + + Contract.Assert(!keyFrames.Empty()); + + if (transport.mapsUsed.Count > 1) + { + foreach (var mapId in transport.mapsUsed) + Contract.Assert(!CliDB.MapStorage.LookupByKey(mapId).Instanceable()); + + transport.inInstance = false; + } + else + transport.inInstance = CliDB.MapStorage.LookupByKey(transport.mapsUsed.First()).Instanceable(); + + // last to first is always "teleport", even for closed paths + keyFrames.Last().Teleport = true; + + float speed = goInfo.MoTransport.moveSpeed; + float accel = goInfo.MoTransport.accelRate; + float accel_dist = 0.5f * speed * speed / accel; + + transport.accelTime = speed / accel; + transport.accelDist = accel_dist; + + int firstStop = -1; + int lastStop = -1; + + // first cell is arrived at by teleportation :S + keyFrames[0].DistFromPrev = 0; + keyFrames[0].Index = 1; + if (keyFrames[0].IsStopFrame()) + { + firstStop = 0; + lastStop = 0; + } + + // find the rest of the distances between key points + // Every path segment has its own spline + int start = 0; + for (int i = 1; i < keyFrames.Count; ++i) + { + if (keyFrames[i - 1].Teleport || i + 1 == keyFrames.Count) + { + int extra = !keyFrames[i - 1].Teleport ? 1 : 0; + Spline spline = new Spline(); + spline.Init_Spline(splinePath.Skip(start).ToArray(), i - start + extra, Spline.EvaluationMode.Catmullrom); + spline.initLengths(); + for (int j = start; j < i + extra; ++j) + { + keyFrames[j].Index = (uint)(j - start + 1); + keyFrames[j].DistFromPrev = spline.length(j - start, j + 1 - start); + if (j > 0) + keyFrames[j - 1].NextDistFromPrev = keyFrames[j].DistFromPrev; + keyFrames[j].Spline = spline; + } + + if (keyFrames[i - 1].Teleport) + { + keyFrames[i].Index = (uint)(i - start + 1); + keyFrames[i].DistFromPrev = 0.0f; + keyFrames[i - 1].NextDistFromPrev = 0.0f; + keyFrames[i].Spline = spline; + } + + start = i; + } + if (keyFrames[i].IsStopFrame()) + { + // remember first stop frame + if (firstStop == -1) + firstStop = i; + lastStop = i; + } + } + + keyFrames.Last().NextDistFromPrev = keyFrames.First().DistFromPrev; + + if (firstStop == -1 || lastStop == -1) + firstStop = lastStop = 0; + + // at stopping keyframes, we define distSinceStop == 0, + // and distUntilStop is to the next stopping keyframe. + // this is required to properly handle cases of two stopping frames in a row (yes they do exist) + float tmpDist = 0.0f; + for (int i = 0; i < keyFrames.Count; ++i) + { + int j = (i + lastStop) % keyFrames.Count; + if (keyFrames[j].IsStopFrame() || j == lastStop) + tmpDist = 0.0f; + else + tmpDist += keyFrames[j].DistFromPrev; + keyFrames[j].DistSinceStop = tmpDist; + } + + tmpDist = 0.0f; + for (int i = (keyFrames.Count - 1); i >= 0; i--) + { + int j = (i + firstStop) % keyFrames.Count; + tmpDist += keyFrames[(j + 1) % keyFrames.Count].DistFromPrev; + keyFrames[j].DistUntilStop = tmpDist; + if (keyFrames[j].IsStopFrame() || j == firstStop) + tmpDist = 0.0f; + } + + for (int i = 0; i < keyFrames.Count; ++i) + { + float total_dist = keyFrames[i].DistSinceStop + keyFrames[i].DistUntilStop; + if (total_dist < 2 * accel_dist) // won't reach full speed + { + if (keyFrames[i].DistSinceStop < keyFrames[i].DistUntilStop) // is still accelerating + { + // calculate accel+brake time for this short segment + float segment_time = 2.0f * (float)Math.Sqrt((keyFrames[i].DistUntilStop + keyFrames[i].DistSinceStop) / accel); + // substract acceleration time + keyFrames[i].TimeTo = segment_time - (float)Math.Sqrt(2 * keyFrames[i].DistSinceStop / accel); + } + else // slowing down + keyFrames[i].TimeTo = (float)Math.Sqrt(2 * keyFrames[i].DistUntilStop / accel); + } + else if (keyFrames[i].DistSinceStop < accel_dist) // still accelerating (but will reach full speed) + { + // calculate accel + cruise + brake time for this long segment + float segment_time = (keyFrames[i].DistUntilStop + keyFrames[i].DistSinceStop) / speed + (speed / accel); + // substract acceleration time + keyFrames[i].TimeTo = segment_time - (float)Math.Sqrt(2 * keyFrames[i].DistSinceStop / accel); + } + else if (keyFrames[i].DistUntilStop < accel_dist) // already slowing down (but reached full speed) + keyFrames[i].TimeTo = (float)Math.Sqrt(2 * keyFrames[i].DistUntilStop / accel); + else // at full speed + keyFrames[i].TimeTo = (keyFrames[i].DistUntilStop / speed) + (0.5f * speed / accel); + } + + // calculate tFrom times from tTo times + float segmentTime = 0.0f; + for (int i = 0; i < keyFrames.Count; ++i) + { + int j = (i + lastStop) % keyFrames.Count; + if (keyFrames[j].IsStopFrame() || j == lastStop) + segmentTime = keyFrames[j].TimeTo; + keyFrames[j].TimeFrom = segmentTime - keyFrames[j].TimeTo; + } + + // calculate path times + keyFrames[0].ArriveTime = 0; + float curPathTime = 0.0f; + if (keyFrames[0].IsStopFrame()) + { + curPathTime = keyFrames[0].Node.Delay; + keyFrames[0].DepartureTime = (uint)(curPathTime * Time.InMilliseconds); + } + + for (int i = 1; i < keyFrames.Count; ++i) + { + curPathTime += keyFrames[i - 1].TimeTo; + if (keyFrames[i].IsStopFrame()) + { + keyFrames[i].ArriveTime = (uint)(curPathTime * Time.InMilliseconds); + keyFrames[i - 1].NextArriveTime = keyFrames[i].ArriveTime; + curPathTime += keyFrames[i].Node.Delay; + keyFrames[i].DepartureTime = (uint)(curPathTime * Time.InMilliseconds); + } + else + { + curPathTime -= keyFrames[i].TimeTo; + keyFrames[i].ArriveTime = (uint)(curPathTime * Time.InMilliseconds); + keyFrames[i - 1].NextArriveTime = keyFrames[i].ArriveTime; + keyFrames[i].DepartureTime = keyFrames[i].ArriveTime; + } + } + keyFrames.Last().NextArriveTime = keyFrames.Last().DepartureTime; + + transport.pathTime = keyFrames.Last().DepartureTime; + if (transport.pathTime == 0) + { + + } + } + + public void AddPathNodeToTransport(uint transportEntry, uint timeSeg, TransportAnimationRecord node) + { + TransportAnimation animNode = new TransportAnimation(); + if (animNode.TotalTime < timeSeg) + animNode.TotalTime = timeSeg; + + animNode.Path[timeSeg] = node; + + _transportAnimations[transportEntry] = animNode; + } + + public Transport CreateTransport(uint entry, ulong guid = 0, Map map = null, uint phaseid = 0, uint phasegroup = 0) + { + // instance case, execute GetGameObjectEntry hook + if (map != null) + { + // SetZoneScript() is called after adding to map, so fetch the script using map + if (map.IsDungeon()) + { + InstanceScript instance = ((InstanceMap)map).GetInstanceScript(); + if (instance != null) + entry = instance.GetGameObjectEntry(0, entry); + } + + if (entry == 0) + return null; + } + + TransportTemplate tInfo = GetTransportTemplate(entry); + if (tInfo == null) + { + Log.outError(LogFilter.Sql, "Transport {0} will not be loaded, `transport_template` missing", entry); + return null; + } + + // create transport... + Transport trans = new Transport(); + + // ...at first waypoint + TaxiPathNodeRecord startNode = tInfo.keyFrames.First().Node; + uint mapId = startNode.MapID; + float x = startNode.Loc.X; + float y = startNode.Loc.Y; + float z = startNode.Loc.Z; + float o = tInfo.keyFrames.First().InitialOrientation; + + // initialize the gameobject base + ulong guidLow = guid != 0 ? guid : map.GenerateLowGuid(HighGuid.Transport); + if (!trans.Create(guidLow, entry, mapId, x, y, z, o, 255)) + return null; + + if (phaseid != 0) + trans.SetInPhase(phaseid, false, true); + + if (phasegroup != 0) + foreach (var ph in Global.DB2Mgr.GetPhasesForGroup(phasegroup)) + trans.SetInPhase(ph, false, true); + + MapRecord mapEntry = CliDB.MapStorage.LookupByKey(mapId); + if (mapEntry != null) + { + if (mapEntry.Instanceable() != tInfo.inInstance) + { + Log.outError(LogFilter.Transport, "Transport {0} (name: {1}) attempted creation in instance map (id: {2}) but it is not an instanced transport!", entry, trans.GetName(), mapId); + //return null; + } + } + + // use preset map for instances (need to know which instance) + trans.SetMap(map != null ? map : Global.MapMgr.CreateMap(mapId, null)); + if (map != null && map.IsDungeon()) + trans.m_zoneScript = map.ToInstanceMap().GetInstanceScript(); + + // Passengers will be loaded once a player is near + + Global.ObjAccessor.AddObject(trans); + trans.GetMap().AddToMap(trans); + return trans; + } + + public void SpawnContinentTransports() + { + if (_transportTemplates.Empty()) + return; + + uint oldMSTime = Time.GetMSTime(); + + SQLResult result = DB.World.Query("SELECT guid, entry, phaseid, phasegroup FROM transports"); + + uint count = 0; + if (!result.IsEmpty()) + { + do + { + ulong guid = result.Read(0); + uint entry = result.Read(1); + uint phaseid = result.Read(2); + uint phasegroup = result.Read(3); + + TransportTemplate tInfo = GetTransportTemplate(entry); + if (tInfo != null) + if (!tInfo.inInstance) + if (CreateTransport(entry, guid, null, phaseid, phasegroup)) + ++count; + + } while (result.NextRow()); + } + + Log.outInfo(LogFilter.ServerLoading, "Spawned {0} continent transports in {1} ms", count, Time.GetMSTimeDiffToNow(oldMSTime)); + } + + public void CreateInstanceTransports(Map map) + { + var mapTransports = _instanceTransports.LookupByKey(map.GetId()); + + // no transports here + if (mapTransports.Empty()) + return; + + // create transports + foreach (var entry in mapTransports) + CreateTransport(entry, 0, map); + } + + public TransportTemplate GetTransportTemplate(uint entry) + { + return _transportTemplates.LookupByKey(entry); + } + + public TransportAnimation GetTransportAnimInfo(uint entry) + { + return _transportAnimations.LookupByKey(entry); + } + + public void AddPathRotationToTransport(uint transportEntry, uint timeSeg, TransportRotationRecord node) + { + if (!_transportAnimations.ContainsKey(transportEntry)) + _transportAnimations[transportEntry] = new TransportAnimation(); + + _transportAnimations[transportEntry].Rotations[timeSeg] = node; + } + + Dictionary _transportTemplates = new Dictionary(); + MultiMap _instanceTransports = new MultiMap(); + Dictionary _transportAnimations = new Dictionary(); + } + + public class SplineRawInitializer + { + public SplineRawInitializer(List points) + { + _points = points; + } + + public void Initialize(ref Spline.EvaluationMode mode, ref bool cyclic, ref Vector3[] points, ref int lo, ref int hi) + { + mode = Spline.EvaluationMode.Catmullrom; + cyclic = false; + points = new Vector3[_points.Count]; + + for(var i = 0; i < _points.Count; ++i) + points[i] = _points[i]; + + lo = 1; + hi = points.Length - 2; + } + + List _points; + } + + public class KeyFrame + { + public KeyFrame(TaxiPathNodeRecord _node) + { + Node = _node; + DistSinceStop = -1.0f; + DistUntilStop = -1.0f; + DistFromPrev = -1.0f; + TimeFrom = 0.0f; + TimeTo = 0.0f; + Teleport = false; + ArriveTime = 0; + DepartureTime = 0; + Spline = null; + NextDistFromPrev = 0.0f; + NextArriveTime = 0; + } + + public uint Index; + public TaxiPathNodeRecord Node; + public float InitialOrientation; + public float DistSinceStop; + public float DistUntilStop; + public float DistFromPrev; + public float TimeFrom; + public float TimeTo; + public bool Teleport; + public uint ArriveTime; + public uint DepartureTime; + public Spline Spline; + + // Data needed for next frame + public float NextDistFromPrev; + public uint NextArriveTime; + + public bool IsTeleportFrame() { return Teleport; } + public bool IsStopFrame() { return Node.Flags.HasAnyFlag(TaxiPathNodeFlags.Stop); } + } + + public class TransportTemplate + { + public TransportTemplate() + { + pathTime = 0; + accelTime = 0.0f; + accelDist = 0.0f; + } + + public List mapsUsed = new List(); + public bool inInstance; + public uint pathTime; + public List keyFrames = new List(); + public float accelTime; + public float accelDist; + public uint entry; + } + + public class TransportAnimation + { + TransportAnimationRecord GetAnimNode(uint time) + { + if (Path.Empty()) + return null; + + foreach (var pair in Path) + if (time >= pair.Key) + return pair.Value; + + return Path.First().Value; + } + + Quaternion GetAnimRotation(uint time) + { + if (Rotations.Empty()) + return new Quaternion(0.0f, 0.0f, 0.0f, 1.0f); + + TransportRotationRecord rot = Rotations.First().Value; + foreach (var pair in Rotations) + { + if (time >= pair.Key) + { + rot = pair.Value; + break; + } + } + + return new Quaternion(rot.X, rot.Y, rot.Z, rot.W); + } + + public Dictionary Path = new Dictionary(); + public Dictionary Rotations = new Dictionary(); + public uint TotalTime; + } +} diff --git a/Game/Maps/ZoneScript.cs b/Game/Maps/ZoneScript.cs new file mode 100644 index 000000000..8d75250da --- /dev/null +++ b/Game/Maps/ZoneScript.cs @@ -0,0 +1,51 @@ +/* + * Copyright (C) 2012-2017 CypherCore + * + * 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, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +using Framework.Dynamic; +using Game.Entities; + +namespace Game.Maps +{ + public class ZoneScript + { + public virtual uint GetCreatureEntry(ulong guidlow, CreatureData data) { return data.id; } + public virtual uint GetGameObjectEntry(ulong spawnId, uint entry) { return entry; } + + public virtual void OnCreatureCreate(Creature creature) { } + public virtual void OnCreatureRemove(Creature creature) { } + + public virtual void OnGameObjectCreate(GameObject go) { } + public virtual void OnGameObjectRemove(GameObject go) { } + + public virtual void OnUnitDeath(Unit unit) { } + + //All-purpose data storage 64 bit + public virtual ObjectGuid GetGuidData(uint DataId) { return ObjectGuid.Empty; } + public virtual void SetGuidData(uint DataId, ObjectGuid Value) { } + + public virtual ulong GetData64(uint dataId) { return 0; } + public virtual void SetData64(uint dataId, ulong value) { } + + //All-purpose data storage 32 bit + public virtual uint GetData(uint dataId) { return 0; } + public virtual void SetData(uint dataId, uint value) { } + + public virtual void ProcessEvent(WorldObject obj, uint eventId) { } + + protected EventMap _events = new EventMap(); + } +} diff --git a/Game/Miscellaneous/Formulas.cs b/Game/Miscellaneous/Formulas.cs new file mode 100644 index 000000000..74dd17f9b --- /dev/null +++ b/Game/Miscellaneous/Formulas.cs @@ -0,0 +1,253 @@ +/* + * Copyright (C) 2012-2017 CypherCore + * + * 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, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +using Framework.Constants; +using Game.DataStorage; +using Game.Entities; +using System; + +namespace Game +{ + public class Formulas + { + public static float hk_honor_at_level_f(uint level, float multiplier = 1.0f) + { + float honor = multiplier * level * 1.55f; + Global.ScriptMgr.OnHonorCalculation(honor, level, multiplier); + return honor; + } + + public static uint hk_honor_at_level(uint level, float multiplier = 1.0f) + { + return (uint)Math.Ceiling(hk_honor_at_level_f(level, multiplier)); + } + + public static uint GetGrayLevel(uint pl_level) + { + uint level; + + if (pl_level < 7) + level = 0; + else if (pl_level < 35) + { + byte count = 0; + for (int i = 15; i <= pl_level; ++i) + if (i % 5 == 0) ++count; + + level = (uint)((pl_level - 7) - (count - 1)); + } + else + level = pl_level - 10; + + Global.ScriptMgr.OnGrayLevelCalculation(level, pl_level); + return level; + } + + public static XPColorChar GetColorCode(uint pl_level, uint mob_level) + { + XPColorChar color; + + if (mob_level >= pl_level + 5) + color = XPColorChar.Red; + else if (mob_level >= pl_level + 3) + color = XPColorChar.Orange; + else if (mob_level >= pl_level - 2) + color = XPColorChar.Yellow; + else if (mob_level > GetGrayLevel(pl_level)) + color = XPColorChar.Green; + else + color = XPColorChar.Gray; + + Global.ScriptMgr.OnColorCodeCalculation(color, pl_level, mob_level); + return color; + } + + public static uint GetZeroDifference(uint pl_level) + { + uint diff; + + if (pl_level < 4) + diff = 5; + else if (pl_level < 10) + diff = 6; + else if (pl_level < 12) + diff = 7; + else if (pl_level < 16) + diff = 8; + else if (pl_level < 20) + diff = 9; + else if (pl_level < 30) + diff = 11; + else if (pl_level < 40) + diff = 12; + else if (pl_level < 45) + diff = 13; + else if (pl_level < 50) + diff = 14; + else if (pl_level < 55) + diff = 15; + else if (pl_level < 60) + diff = 16; + else + diff = 17; + + Global.ScriptMgr.OnZeroDifferenceCalculation(diff, pl_level); + return diff; + } + + public static uint BaseGain(uint pl_level, uint mob_level) + { + uint baseGain; + + GtXpRecord xpPlayer = CliDB.XpGameTable.GetRow(pl_level); + GtXpRecord xpMob = CliDB.XpGameTable.GetRow(mob_level); + + if (mob_level >= pl_level) + { + uint nLevelDiff = mob_level - pl_level; + if (nLevelDiff > 4) + nLevelDiff = 4; + + baseGain = (uint)Math.Round(xpPlayer.PerKill * (1 + 0.05f * nLevelDiff)); + } + else + { + uint gray_level = GetGrayLevel(pl_level); + if (mob_level > gray_level) + { + uint ZD = GetZeroDifference(pl_level); + baseGain = (uint)Math.Round(xpMob.PerKill * ((1 - ((pl_level - mob_level) / ZD)) * (xpMob.Divisor / xpPlayer.Divisor))); + } + else + baseGain = 0; + } + + Global.ScriptMgr.OnBaseGainCalculation(baseGain, pl_level, mob_level); + return baseGain; + } + + public static uint XPGain(Player player, Unit u, bool isBattleGround = false) + { + Creature creature = u.ToCreature(); + uint gain = 0; + + if (!creature || creature.CanGiveExperience()) + { + float xpMod = 1.0f; + + gain = BaseGain(player.getLevel(), u.getLevel()); + + if (gain != 0 && creature) + { + // Players get only 10% xp for killing creatures of lower expansion levels than himself + if ((creature.GetCreatureTemplate().HealthScalingExpansion < (int)GetExpansionForLevel(player.getLevel()))) + gain = (uint)Math.Round(gain / 10.0f); + + if (creature.isElite()) + { + // Elites in instances have a 2.75x XP bonus instead of the regular 2x world bonus. + if (u.GetMap().IsDungeon()) + xpMod *= 2.75f; + else + xpMod *= 2.0f; + } + + xpMod *= creature.GetCreatureTemplate().ModExperience; + } + xpMod *= isBattleGround ? WorldConfig.GetFloatValue(WorldCfg.RateXpBgKill) : WorldConfig.GetFloatValue(WorldCfg.RateXpKill); + if (creature && creature.m_PlayerDamageReq != 0) // if players dealt less than 50% of the damage and were credited anyway (due to CREATURE_FLAG_EXTRA_NO_PLAYER_DAMAGE_REQ), scale XP gained appropriately (linear scaling) + xpMod *= 1.0f - 2.0f * creature.m_PlayerDamageReq / creature.GetMaxHealth(); + + gain = (uint)(gain * xpMod); + } + + Global.ScriptMgr.OnGainCalculation(gain, player, u); + return gain; + } + + public static float XPInGroupRate(uint count, bool isRaid) + { + float rate; + + if (isRaid) + { + // FIXME: Must apply decrease modifiers depending on raid size. + // set to < 1 to, so client will display raid related strings + rate = 0.99f; + } + else + { + switch (count) + { + case 0: + case 1: + case 2: + rate = 1.0f; + break; + case 3: + rate = 1.166f; + break; + case 4: + rate = 1.3f; + break; + case 5: + default: + rate = 1.4f; + break; + } + } + + Global.ScriptMgr.OnGroupRateCalculation(rate, count, isRaid); + return rate; + } + + static Expansion GetExpansionForLevel(uint level) + { + if (level < 60) + return Expansion.Classic; + else if (level < 70) + return Expansion.BurningCrusade; + else if (level < 80) + return Expansion.WrathOfTheLichKing; + else if (level < 85) + return Expansion.Cataclysm; + else if (level < 90) + return Expansion.MistsOfPandaria; + else if (level < 100) + return Expansion.WarlordsOfDraenor; + else + return Expansion.Legion; + } + + public static uint ConquestRatingCalculator(uint rate) + { + if (rate <= 1500) + return 1350; // Default conquest points + else if (rate > 3000) + rate = 3000; + + // http://www.arenajunkies.com/topic/179536-conquest-point-cap-vs-personal-rating-chart/page__st__60#entry3085246 + return (uint)(1.4326 * ((1511.26 / (1 + 1639.28 / Math.Exp(0.00412 * rate))) + 850.15)); + } + + public static uint BgConquestRatingCalculator(uint rate) + { + // WowWiki: Battlegroundratings receive a bonus of 22.2% to the cap they generate + return (uint)((ConquestRatingCalculator(rate) * 1.222f) + 0.5f); + } + } +} diff --git a/Game/Movement/Generators/ConfusedGenerator.cs b/Game/Movement/Generators/ConfusedGenerator.cs new file mode 100644 index 000000000..de9eb8a10 --- /dev/null +++ b/Game/Movement/Generators/ConfusedGenerator.cs @@ -0,0 +1,125 @@ +/* + * Copyright (C) 2012-2017 CypherCore + * + * 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, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +using Framework.Constants; +using Game.Entities; +using System; + +namespace Game.Movement +{ + public class ConfusedGenerator : MovementGeneratorMedium where T : Unit + { + public ConfusedGenerator() + { + i_nextMoveTime = new TimeTracker(); + } + + public override void DoInitialize(T owner) + { + owner.AddUnitState(UnitState.Confused); + owner.SetFlag(UnitFields.Flags, UnitFlags.Confused); + owner.GetPosition(out i_x, out i_y, out i_z); + + if (!owner.IsAlive() || owner.IsStopped()) + return; + + owner.StopMoving(); + owner.AddUnitState(UnitState.ConfusedMove); + } + + public override void DoFinalize(T owner) + { + if (owner.IsTypeId(TypeId.Player)) + { + owner.RemoveFlag(UnitFields.Flags, UnitFlags.Confused); + owner.ClearUnitState(UnitState.Confused | UnitState.ConfusedMove); + owner.StopMoving(); + } + else if (owner.IsTypeId(TypeId.Unit)) + { + owner.RemoveFlag(UnitFields.Flags, UnitFlags.Confused); + owner.ClearUnitState(UnitState.Confused | UnitState.ConfusedMove); + if (owner.GetVictim()) + owner.SetTarget(owner.GetVictim().GetGUID()); + } + } + + public override void DoReset(T owner) + { + i_nextMoveTime.Reset(0); + + if (!owner.IsAlive() || owner.IsStopped()) + return; + + owner.StopMoving(); + owner.AddUnitState(UnitState.Confused | UnitState.ConfusedMove); + } + public override bool DoUpdate(T owner, uint time_diff) + { + if (owner.HasUnitState(UnitState.Root | UnitState.Stunned | UnitState.Distracted)) + return true; + + if (i_nextMoveTime.Passed()) + { + // currently moving, update location + owner.AddUnitState(UnitState.ConfusedMove); + + if (owner.moveSpline.Finalized()) + i_nextMoveTime.Reset(RandomHelper.IRand(800, 1500)); + } + else + { + // waiting for next move + i_nextMoveTime.Update(time_diff); + if (i_nextMoveTime.Passed()) + { + // start moving + owner.AddUnitState(UnitState.ConfusedMove); + + float dest = (float)(4.0f * RandomHelper.NextDouble() - 2.0f); + + Position pos = new Position(i_x, i_y, i_z); + owner.MovePositionToFirstCollision(ref pos, dest, 0.0f); + + PathGenerator path = new PathGenerator(owner); + path.SetPathLengthLimit(30.0f); + bool result = path.CalculatePath(pos.GetPositionX(), pos.GetPositionY(), pos.GetPositionZ()); + if (!result || path.GetPathType().HasAnyFlag(PathType.NoPath)) + { + i_nextMoveTime.Reset(100); + return true; + } + + MoveSplineInit init = new MoveSplineInit(owner); + init.MovebyPath(path.GetPath()); + init.SetWalk(true); + init.Launch(); + } + } + + return true; + } + + public override MovementGeneratorType GetMovementGeneratorType() + { + return MovementGeneratorType.Confused; + } + + TimeTracker i_nextMoveTime; + float i_x, i_y, i_z; + } +} diff --git a/Game/Movement/Generators/FleeingGenerator.cs b/Game/Movement/Generators/FleeingGenerator.cs new file mode 100644 index 000000000..b8a7463bf --- /dev/null +++ b/Game/Movement/Generators/FleeingGenerator.cs @@ -0,0 +1,223 @@ +/* + * Copyright (C) 2012-2017 CypherCore + * + * 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, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +using Framework.Constants; +using Game.Entities; +using System; + +namespace Game.Movement +{ + public class FleeingGenerator : MovementGeneratorMedium where T : Unit + { + public const float MIN_QUIET_DISTANCE = 28.0f; + public const float MAX_QUIET_DISTANCE = 43.0f; + + public FleeingGenerator(ObjectGuid fright) + { + i_frightGUID = fright; + i_nextCheckTime = new TimeTracker(); + } + + void _setTargetLocation(T owner) + { + if (owner == null) + return; + + if (owner.HasUnitState(UnitState.Root | UnitState.Stunned)) + return; + + if (owner.HasUnitState(UnitState.Casting) && !owner.CanMoveDuringChannel()) + { + owner.CastStop(); + return; + } + + owner.AddUnitState(UnitState.FleeingMove); + + float x, y, z; + _getPoint(owner, out x, out y, out z); + + Position mypos = owner.GetPosition(); + bool isInLOS = Global.VMapMgr.isInLineOfSight(owner.GetMapId(), mypos.posX, mypos.posY, mypos.posZ + 2.0f, x, y, z + 2.0f); + + if (!isInLOS) + { + i_nextCheckTime.Reset(200); + return; + } + + PathGenerator path = new PathGenerator(owner); + path.SetPathLengthLimit(30.0f); + bool result = path.CalculatePath(x, y, z); + if (!result || path.GetPathType().HasAnyFlag(PathType.NoPath)) + { + i_nextCheckTime.Reset(100); + return; + } + + MoveSplineInit init = new MoveSplineInit(owner); + init.MovebyPath(path.GetPath()); + init.SetWalk(false); + int traveltime = init.Launch(); + i_nextCheckTime.Reset(traveltime + RandomHelper.URand(800, 1500)); + } + + void _getPoint(T owner, out float x, out float y, out float z) + { + float dist_from_caster, angle_to_caster; + Unit fright = Global.ObjAccessor.GetUnit(owner, i_frightGUID); + if (fright != null) + { + dist_from_caster = fright.GetDistance(owner); + if (dist_from_caster > 0.2f) + angle_to_caster = fright.GetAngle(owner); + else + angle_to_caster = RandomHelper.FRand(0, 2 * MathFunctions.PI); + } + else + { + dist_from_caster = 0.0f; + angle_to_caster = RandomHelper.FRand(0, 2 * MathFunctions.PI); + } + + float dist, angle; + if (dist_from_caster < MIN_QUIET_DISTANCE) + { + dist = RandomHelper.FRand(0.4f, 1.3f) * (MIN_QUIET_DISTANCE - dist_from_caster); + angle = angle_to_caster + RandomHelper.FRand(-MathFunctions.PI / 8, MathFunctions.PI / 8); + } + else if (dist_from_caster > MAX_QUIET_DISTANCE) + { + dist = RandomHelper.FRand(0.4f, 1.0f) * (MAX_QUIET_DISTANCE - MIN_QUIET_DISTANCE); + angle = -angle_to_caster + RandomHelper.FRand(-MathFunctions.PI / 4, MathFunctions.PI / 4); + } + else // we are inside quiet range + { + dist = RandomHelper.FRand(0.6f, 1.2f) * (MAX_QUIET_DISTANCE - MIN_QUIET_DISTANCE); + angle = RandomHelper.FRand(0, 2 * MathFunctions.PI); + } + + Position pos = owner.GetFirstCollisionPosition(dist, angle); + x = pos.posX; + y = pos.posY; + z = pos.posZ; + } + + public override void DoInitialize(T owner) + { + if (owner == null) + return; + + owner.SetFlag(UnitFields.Flags, UnitFlags.Fleeing); + owner.AddUnitState(UnitState.Fleeing | UnitState.FleeingMove); + _setTargetLocation(owner); + } + + public override void DoFinalize(T owner) + { + if (owner.IsTypeId(TypeId.Player)) + { + owner.RemoveFlag(UnitFields.Flags, UnitFlags.Fleeing); + owner.ClearUnitState(UnitState.Fleeing | UnitState.Fleeing); + owner.StopMoving(); + } + else + { + owner.RemoveFlag(UnitFields.Flags, UnitFlags.Fleeing); + owner.ClearUnitState(UnitState.Fleeing | UnitState.FleeingMove); + if (owner.GetVictim() != null) + owner.SetTarget(owner.GetVictim().GetGUID()); + } + } + + public override void DoReset(T owner) + { + DoInitialize(owner); + } + + public override bool DoUpdate(T owner, uint time_diff) + { + if (owner == null || !owner.IsAlive()) + return false; + + if (owner.HasUnitState(UnitState.Root | UnitState.Stunned)) + { + owner.ClearUnitState(UnitState.FleeingMove); + return true; + } + + i_nextCheckTime.Update(time_diff); + if (i_nextCheckTime.Passed() && owner.moveSpline.Finalized()) + _setTargetLocation(owner); + + return true; + } + + public override MovementGeneratorType GetMovementGeneratorType() + { + return MovementGeneratorType.Fleeing; + } + + ObjectGuid i_frightGUID; + TimeTracker i_nextCheckTime; + } + + public class TimedFleeingGenerator : FleeingGenerator + { + public TimedFleeingGenerator(ObjectGuid fright, uint time) : base(fright) + { + i_totalFleeTime = new TimeTracker(time); + } + + public override void Finalize(Unit owner) + { + owner.RemoveFlag(UnitFields.Flags, UnitFlags.Fleeing); + owner.ClearUnitState(UnitState.Fleeing | UnitState.FleeingMove); + Unit victim = owner.GetVictim(); + if (victim != null) + { + if (owner.IsAlive()) + { + owner.AttackStop(); + owner.ToCreature().GetAI().AttackStart(victim); + } + } + } + + public override bool Update(Unit owner, uint time_diff) + { + if (!owner.IsAlive()) + return false; + + if (owner.HasUnitState(UnitState.Root | UnitState.Stunned)) + { + owner.ClearUnitState(UnitState.FleeingMove); + return true; + } + + i_totalFleeTime.Update(time_diff); + if (i_totalFleeTime.Passed()) + return false; + + // This calls grant-parent Update method hiden by FleeingMovementGenerator.Update(Creature &, uint32) version + // This is done instead of casting Unit& to Creature& and call parent method, then we can use Unit directly + return base.Update(owner, time_diff); + } + + TimeTracker i_totalFleeTime; + } +} diff --git a/Game/Movement/Generators/HomeMovement.cs b/Game/Movement/Generators/HomeMovement.cs new file mode 100644 index 000000000..a716a095a --- /dev/null +++ b/Game/Movement/Generators/HomeMovement.cs @@ -0,0 +1,85 @@ +/* + * Copyright (C) 2012-2017 CypherCore + * + * 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, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +using Framework.Constants; +using Game.Entities; +using Game.Movement; + +namespace Game.AI +{ + public class HomeMovementGenerator : MovementGeneratorMedium where T : Creature + { + public override void DoInitialize(T owner) + { + SetTargetLocation(owner); + } + + public override void DoFinalize(T owner) + { + if (arrived) + { + owner.ClearUnitState(UnitState.Evade); + owner.SetWalk(true); + owner.LoadCreaturesAddon(); + owner.SetSpawnHealth(); + owner.GetAI().JustReachedHome(); + } + } + + public override void DoReset(T owner) { } + + public override bool DoUpdate(T owner, uint time_diff) + { + arrived = skipToHome || owner.moveSpline.Finalized(); + return !arrived; + } + + void SetTargetLocation(T owner) + { + if (owner.HasUnitState(UnitState.Root | UnitState.Stunned | UnitState.Distracted)) + { // if we are ROOT/STUNNED/DISTRACTED even after aura clear, finalize on next update - otherwise we would get stuck in evade + skipToHome = true; + return; + } + + MoveSplineInit init = new MoveSplineInit(owner); + float x, y, z, o; + // at apply we can select more nice return points base at current movegen + if (owner.GetMotionMaster().empty() || !owner.GetMotionMaster().top().GetResetPosition(owner, out x, out y, out z)) + { + owner.GetHomePosition(out x, out y, out z, out o); + init.SetFacing(o); + } + init.MoveTo(x, y, z); + init.SetWalk(false); + init.Launch(); + + skipToHome = false; + arrived = false; + + owner.ClearUnitState(UnitState.AllState & ~(UnitState.Evade | UnitState.IgnorePathfinding)); + } + + public override MovementGeneratorType GetMovementGeneratorType() + { + return MovementGeneratorType.Home; + } + + bool arrived; + bool skipToHome; + } +} diff --git a/Game/Movement/Generators/IdleMovement.cs b/Game/Movement/Generators/IdleMovement.cs new file mode 100644 index 000000000..b632c7afc --- /dev/null +++ b/Game/Movement/Generators/IdleMovement.cs @@ -0,0 +1,159 @@ +/* + * Copyright (C) 2012-2017 CypherCore + * + * 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, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +using Framework.Constants; +using Game.Entities; + +namespace Game.Movement +{ + public class IdleMovementGenerator : IMovementGenerator + { + public override MovementGeneratorType GetMovementGeneratorType() + { + return MovementGeneratorType.Idle; + } + + public bool isActive { get; set; } + + public override void Reset(Unit owner) + { + if (!owner.isStopped()) + owner.StopMoving(); + } + public override void Initialize(Unit owner) + { + Reset(owner); + } + public override void Finalize(Unit owner) + { + } + public override bool Update(Unit owner, uint time_diff) + { + return true; + } + } + + public class RotateMovementGenerator : IMovementGenerator + { + public RotateMovementGenerator(uint time, RotateDirection direction) + { + m_duration = time; + m_maxDuration = time; + m_direction = direction; + } + + public override void Finalize(Unit owner) + { + owner.ClearUnitState(UnitState.Rotating); + if (owner.IsTypeId(TypeId.Unit)) + owner.ToCreature().GetAI().MovementInform(MovementGeneratorType.Rotate, 0); + } + + public override void Initialize(Unit owner) + { + if (!owner.IsStopped()) + owner.StopMoving(); + + if (owner.GetVictim()) + owner.SetInFront(owner.GetVictim()); + + owner.AddUnitState(UnitState.Rotating); + owner.AttackStop(); + } + + public override bool Update(Unit owner, uint time_diff) + { + float angle = owner.GetOrientation(); + angle += time_diff * MathFunctions.TwoPi / m_maxDuration * (m_direction == RotateDirection.Left ? 1.0f : -1.0f); + angle = MathFunctions.wrap(angle, 0.0f, MathFunctions.TwoPi); + + owner.SetOrientation(angle); // UpdateSplinePosition does not set orientation with UNIT_STATE_ROTATING + owner.SetFacingTo(angle); // Send spline movement to clients + + if (m_duration > time_diff) + m_duration -= time_diff; + else + return false; + + return true; + } + + public override void Reset(Unit owner) { } + + public override MovementGeneratorType GetMovementGeneratorType() { return MovementGeneratorType.Rotate; } + + uint m_duration, m_maxDuration; + RotateDirection m_direction; + } + + public class DistractMovementGenerator : IMovementGenerator + { + public DistractMovementGenerator(uint timer) + { + m_timer = timer; + } + + public override void Finalize(Unit owner) + { + owner.ClearUnitState(UnitState.Distracted); + + // If this is a creature, then return orientation to original position (for idle movement creatures) + if (owner.IsTypeId(TypeId.Unit)) + { + float angle = owner.ToCreature().GetHomePosition().GetOrientation(); + owner.SetFacingTo(angle); + } + } + + public override void Initialize(Unit owner) + { + // Distracted creatures stand up if not standing + if (!owner.IsStandState()) + owner.SetStandState(UnitStandStateType.Stand); + + owner.AddUnitState(UnitState.Distracted); + } + + public override bool Update(Unit owner, uint time_diff) + { + if (time_diff > m_timer) + return false; + + m_timer -= time_diff; + return true; + } + + public override void Reset(Unit owner) { } + + public override MovementGeneratorType GetMovementGeneratorType() { return MovementGeneratorType.Distract; } + + uint m_timer; + } + + public class AssistanceDistractMovementGenerator : DistractMovementGenerator + { + public AssistanceDistractMovementGenerator(uint timer) : base(timer) { } + + public override MovementGeneratorType GetMovementGeneratorType() { return MovementGeneratorType.AssistanceDistract; } + + public override void Finalize(Unit owner) + { + owner.ClearUnitState(UnitState.Distracted); + owner.ToCreature().SetReactState(ReactStates.Aggressive); + } + } +} diff --git a/Game/Movement/Generators/MovementGenerators.cs b/Game/Movement/Generators/MovementGenerators.cs new file mode 100644 index 000000000..e74ffcc80 --- /dev/null +++ b/Game/Movement/Generators/MovementGenerators.cs @@ -0,0 +1,91 @@ +/* + * Copyright (C) 2012-2017 CypherCore + * + * 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, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +using Framework.Constants; +using Framework.Dynamic; +using Game.Entities; + +namespace Game.Movement +{ + public abstract class IMovementGenerator + { + public abstract void Finalize(Unit owner); + + public abstract void Initialize(Unit owner); + + public abstract void Reset(Unit owner); + + public abstract bool Update(Unit owner, uint time_diff); + + public abstract MovementGeneratorType GetMovementGeneratorType(); + + public virtual void unitSpeedChanged() { } + + // used by Evade code for select point to evade with expected restart default movement + public virtual bool GetResetPosition(Unit u, out float x, out float y, out float z) + { + x = y = z = 0.0f; + return false; + } + } + + public abstract class MovementGeneratorMedium : IMovementGenerator where T : Unit + { + public override void Initialize(Unit owner) + { + DoInitialize((T)owner); + isActive = true; + } + public override void Finalize(Unit owner) + { + DoFinalize((T)owner); + } + public override void Reset(Unit owner) + { + DoReset((T)owner); + } + public override bool Update(Unit owner, uint time_diff) + { + return DoUpdate((T)owner, time_diff); + } + + public bool isActive { get; set; } + + public abstract void DoInitialize(T owner); + public abstract void DoFinalize(T owner); + public abstract void DoReset(T owner); + public abstract bool DoUpdate(T owner, uint time_diff); + } + + public class FollowerReference : Reference + { + public override void targetObjectBuildLink() + { + getTarget().addFollower(this); + } + + public override void targetObjectDestroyLink() + { + getTarget().removeFollower(this); + } + + public override void sourceObjectDestroyLink() + { + GetSource().stopFollowing(); + } + } +} diff --git a/Game/Movement/Generators/PathGenerator.cs b/Game/Movement/Generators/PathGenerator.cs new file mode 100644 index 000000000..64d055a4a --- /dev/null +++ b/Game/Movement/Generators/PathGenerator.cs @@ -0,0 +1,1008 @@ +/* + * Copyright (C) 2012-2017 CypherCore + * + * 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, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +using Framework.Constants; +using Framework.GameMath; +using Game.Entities; +using Game.Maps; +using System; +using System.Linq; + +namespace Game.Movement +{ + public class PathGenerator + { + public PathGenerator(Unit owner) + { + _polyLength = 0; + pathType = PathType.Blank; + _useStraightPath = false; + _forceDestination = false; + _pointPathLimit = 74; + _endPosition = Vector3.Zero; + _sourceUnit = owner; + _navMesh = null; + _navMeshQuery = null; + Log.outDebug(LogFilter.Maps, "PathGenerator:PathGenerator for {0}", _sourceUnit.GetGUID().ToString()); + + uint mapId = _sourceUnit.GetMapId(); + if (Global.DisableMgr.IsPathfindingEnabled(mapId)) + { + _navMesh = Global.MMapMgr.GetNavMesh(mapId, _sourceUnit.GetTerrainSwaps()); + _navMeshQuery = Global.MMapMgr.GetNavMeshQuery(mapId, _sourceUnit.GetInstanceId(), _sourceUnit.GetTerrainSwaps()); + } + CreateFilter(); + } + + public bool CalculatePath(float destX, float destY, float destZ, bool forceDest = false, bool straightLine = false) + { + float x, y, z; + _sourceUnit.GetPosition(out x, out y, out z); + + if (!GridDefines.IsValidMapCoord(destX, destY, destZ) || !GridDefines.IsValidMapCoord(x, y, z)) + return false; + + Vector3 dest = new Vector3(destX, destY, destZ); + SetEndPosition(dest); + + Vector3 start = new Vector3(x, y, z); + SetStartPosition(start); + + _forceDestination = forceDest; + _straightLine = straightLine; + + Log.outDebug(LogFilter.Maps, "PathGenerator.CalculatePath() for {0} \n", _sourceUnit.GetGUID().ToString()); + + // make sure navMesh works - we can run on map w/o mmap + // check if the start and end point have a .mmtile loaded (can we pass via not loaded tile on the way?) + if (_navMesh == null || _navMeshQuery == null || _sourceUnit.HasUnitState(UnitState.IgnorePathfinding) + || !HaveTile(start) || !HaveTile(dest)) + { + BuildShortcut(); + pathType = PathType.Normal | PathType.NotUsingPath; + return true; + } + + UpdateFilter(); + BuildPolyPath(start, dest); + return true; + } + + ulong GetPathPolyByPosition(ulong[] polyPath, uint polyPathSize, float[] point, ref float distance) + { + if (polyPath == null || polyPathSize == 0) + return 0; + + ulong nearestPoly = 0; + float minDist2d = float.MaxValue; + float minDist3d = 0.0f; + + for (uint i = 0; i < polyPathSize; ++i) + { + float[] closestPoint = new float[3]; + bool posOverPoly = false; + if (Detour.dtStatusFailed(_navMeshQuery.closestPointOnPoly(polyPath[i], point, closestPoint, ref posOverPoly))) + continue; + + float d = Detour.dtVdist2DSqr(point, closestPoint); + if (d < minDist2d) + { + minDist2d = d; + nearestPoly = polyPath[i]; + minDist3d = Detour.dtVdistSqr(point, closestPoint); + } + + if (minDist2d < 1.0f) // shortcut out - close enough for us + break; + } + + distance = (float)Math.Sqrt(minDist3d); + + return (minDist2d < 3.0f) ? nearestPoly : 0u; + } + + ulong GetPolyByLocation(float[] point, ref float distance) + { + // first we check the current path + // if the current path doesn't contain the current poly, + // we need to use the expensive navMesh.findNearestPoly + ulong polyRef = GetPathPolyByPosition(_pathPolyRefs, _polyLength, point, ref distance); + if (polyRef != 0) + return polyRef; + + // we don't have it in our old path + // try to get it by findNearestPoly() + // first try with low search box + float[] extents = { 3.0f, 5.0f, 3.0f }; // bounds of poly search area + float[] closestPoint = { 0.0f, 0.0f, 0.0f }; + if (Detour.dtStatusSucceed(_navMeshQuery.findNearestPoly(point, extents, _filter, ref polyRef, ref closestPoint)) && polyRef != 0) + { + distance = Detour.dtVdist(closestPoint, point); + return polyRef; + } + + // still nothing .. + // try with bigger search box + // Note that the extent should not overlap more than 128 polygons in the navmesh (see dtNavMeshQuery.findNearestPoly) + extents[1] = 50.0f; + if (Detour.dtStatusSucceed(_navMeshQuery.findNearestPoly(point, extents, _filter, ref polyRef, ref closestPoint)) && polyRef != 0) + { + distance = Detour.dtVdist(closestPoint, point); + return polyRef; + } + + return 0; + } + + void BuildPolyPath(Vector3 startPos, Vector3 endPos) + { + // *** getting start/end poly logic *** + + float distToStartPoly = 0; + float distToEndPoly = 0; + float[] startPoint = { startPos.Y, startPos.Z, startPos.X }; + float[] endPoint = { endPos.Y, endPos.Z, endPos.X }; + + ulong startPoly = GetPolyByLocation(startPoint, ref distToStartPoly); + ulong endPoly = GetPolyByLocation(endPoint, ref distToEndPoly); + + // we have a hole in our mesh + // make shortcut path and mark it as NOPATH ( with flying and swimming exception ) + // its up to caller how he will use this info + if (startPoly == 0 || endPoly == 0) + { + Log.outDebug(LogFilter.Maps, "++ BuildPolyPath . (startPoly == 0 || endPoly == 0)\n"); + BuildShortcut(); + bool path = _sourceUnit.IsTypeId(TypeId.Unit) && _sourceUnit.ToCreature().CanFly(); + + bool waterPath = _sourceUnit.IsTypeId(TypeId.Unit) && _sourceUnit.ToCreature().CanSwim(); + if (waterPath) + { + // Check both start and end points, if they're both in water, then we can *safely* let the creature move + for (uint i = 0; i < _pathPoints.Length; ++i) + { + ZLiquidStatus status = _sourceUnit.GetMap().getLiquidStatus(_pathPoints[i].X, _pathPoints[i].Y, _pathPoints[i].Z, MapConst.MapAllLiquidTypes); + // One of the points is not in the water, cancel movement. + if (status == ZLiquidStatus.NoWater) + { + waterPath = false; + break; + } + } + } + + pathType = (path || waterPath) ? (PathType.Normal | PathType.NotUsingPath) : PathType.NoPath; + return; + } + + // we may need a better number here + bool farFromPoly = (distToStartPoly > 7.0f || distToEndPoly > 7.0f); + if (farFromPoly) + { + Log.outDebug(LogFilter.Maps, "++ BuildPolyPath . farFromPoly distToStartPoly={0:F3} distToEndPoly={1:F3}\n", distToStartPoly, distToEndPoly); + + bool buildShotrcut = false; + if (_sourceUnit.IsTypeId(TypeId.Unit)) + { + Creature owner = _sourceUnit.ToCreature(); + + Vector3 p = (distToStartPoly > 7.0f) ? startPos : endPos; + if (_sourceUnit.GetMap().IsUnderWater(p.X, p.Y, p.Z)) + { + Log.outDebug(LogFilter.Maps, "++ BuildPolyPath . underWater case\n"); + if (owner.CanSwim()) + buildShotrcut = true; + } + else + { + Log.outDebug(LogFilter.Maps, "++ BuildPolyPath . flying case\n"); + if (owner.CanFly()) + buildShotrcut = true; + } + } + + if (buildShotrcut) + { + BuildShortcut(); + pathType = (PathType.Normal | PathType.NotUsingPath); + return; + } + else + { + float[] closestPoint = new float[3]; + // we may want to use closestPointOnPolyBoundary instead + bool posOverPoly = false; + if (Detour.dtStatusSucceed(_navMeshQuery.closestPointOnPoly(endPoly, endPoint, closestPoint, ref posOverPoly))) + { + Detour.dtVcopy(endPoint, closestPoint); + SetActualEndPosition(new Vector3(endPoint[2], endPoint[0], endPoint[1])); + } + + pathType = PathType.Incomplete; + } + } + + // *** poly path generating logic *** + + // start and end are on same polygon + // just need to move in straight line + if (startPoly == endPoly) + { + Log.outDebug(LogFilter.Maps, "++ BuildPolyPath . (startPoly == endPoly)\n"); + + BuildShortcut(); + + _pathPolyRefs[0] = startPoly; + _polyLength = 1; + + pathType = farFromPoly ? PathType.Incomplete : PathType.Normal; + Log.outDebug(LogFilter.Maps, "BuildPolyPath . path type {0}\n", pathType); + return; + } + + // look for startPoly/endPoly in current path + /// @todo we can merge it with getPathPolyByPosition() loop + bool startPolyFound = false; + bool endPolyFound = false; + uint pathStartIndex = 0; + uint pathEndIndex = 0; + + if (_polyLength != 0) + { + for (; pathStartIndex < _polyLength; ++pathStartIndex) + { + // here to carch few bugs + if (_pathPolyRefs[pathStartIndex] == 0) + { + Log.outError(LogFilter.Maps, "Invalid poly ref in BuildPolyPath. _polyLength: {0}, pathStartIndex: {1}," + + " startPos: {2}, endPos: {3}, mapid: {4}", _polyLength, pathStartIndex, startPos, endPos, _sourceUnit.GetMapId()); + break; + } + + if (_pathPolyRefs[pathStartIndex] == startPoly) + { + startPolyFound = true; + break; + } + } + + for (pathEndIndex = _polyLength - 1; pathEndIndex > pathStartIndex; --pathEndIndex) + if (_pathPolyRefs[pathEndIndex] == endPoly) + { + endPolyFound = true; + break; + } + } + + if (startPolyFound && endPolyFound) + { + Log.outDebug(LogFilter.Maps, "BuildPolyPath : (startPolyFound && endPolyFound)\n"); + + // we moved along the path and the target did not move out of our old poly-path + // our path is a simple subpath case, we have all the data we need + // just "cut" it out + + _polyLength = pathEndIndex - pathStartIndex + 1; + Array.Copy(_pathPolyRefs, pathStartIndex, _pathPolyRefs, 0, _polyLength); + } + else if (startPolyFound && !endPolyFound) + { + Log.outDebug(LogFilter.Maps, "BuildPolyPath : (startPolyFound && !endPolyFound)\n"); + + // we are moving on the old path but target moved out + // so we have atleast part of poly-path ready + + _polyLength -= pathStartIndex; + + // try to adjust the suffix of the path instead of recalculating entire length + // at given interval the target cannot get too far from its last location + // thus we have less poly to cover + // sub-path of optimal path is optimal + + // take ~80% of the original length + /// @todo play with the values here + uint prefixPolyLength = (uint)(_polyLength * 0.8f + 0.5f); + Array.Copy(_pathPolyRefs, pathStartIndex, _pathPolyRefs, 0, prefixPolyLength); + + ulong suffixStartPoly = _pathPolyRefs[prefixPolyLength - 1]; + + // we need any point on our suffix start poly to generate poly-path, so we need last poly in prefix data + float[] suffixEndPoint = new float[3]; + bool posOverPoly = false; + if (Detour.dtStatusFailed(_navMeshQuery.closestPointOnPoly(suffixStartPoly, endPoint, suffixEndPoint, ref posOverPoly))) + { + // we can hit offmesh connection as last poly - closestPointOnPoly() don't like that + // try to recover by using prev polyref + --prefixPolyLength; + suffixStartPoly = _pathPolyRefs[prefixPolyLength - 1]; + if (Detour.dtStatusFailed(_navMeshQuery.closestPointOnPoly(suffixStartPoly, endPoint, suffixEndPoint, ref posOverPoly))) + { + // suffixStartPoly is still invalid, error state + BuildShortcut(); + pathType = PathType.NoPath; + return; + } + } + + // generate suffix + uint suffixPolyLength = 0; + + uint dtResult; + if (_straightLine) + { + float hit = 0; + float[] hitNormal = new float[3]; + + dtResult = _navMeshQuery.raycast( + suffixStartPoly, + suffixEndPoint, + endPoint, + _filter, + ref hit, + hitNormal, + _pathPolyRefs, + ref suffixPolyLength, + 74 - (int)prefixPolyLength); + + // raycast() sets hit to FLT_MAX if there is a ray between start and end + if (hit != float.MaxValue) + { + // the ray hit something, return no path instead of the incomplete one + pathType = PathType.NoPath; + return; + } + } + else + { + dtResult = _navMeshQuery.findPath( + suffixStartPoly, // start polygon + endPoly, // end polygon + suffixEndPoint, // start position + endPoint, // end position + _filter, // polygon search filter + _pathPolyRefs, + ref suffixPolyLength, + 74 - (int)prefixPolyLength); + } + + if (suffixPolyLength == 0 || Detour.dtStatusFailed(dtResult)) + { + // this is probably an error state, but we'll leave it + // and hopefully recover on the next Update + // we still need to copy our preffix + Log.outError(LogFilter.Maps, "{0}'s Path Build failed: 0 length path", _sourceUnit.GetGUID().ToString()); + } + + Log.outDebug(LogFilter.Maps, "m_polyLength={0} prefixPolyLength={1} suffixPolyLength={2} \n", _polyLength, prefixPolyLength, suffixPolyLength); + + // new path = prefix + suffix - overlap + _polyLength = prefixPolyLength + suffixPolyLength - 1; + } + else + { + Log.outDebug(LogFilter.Maps, "++ BuildPolyPath . (!startPolyFound && !endPolyFound)\n"); + + // either we have no path at all . first run + // or something went really wrong . we aren't moving along the path to the target + // just generate new path + + // free and invalidate old path data + Clear(); + + uint dtResult; + if (_straightLine) + { + float hit = 0; + float[] hitNormal = new float[3]; + + dtResult = _navMeshQuery.raycast( + startPoly, + startPoint, + endPoint, + _filter, + ref hit, + hitNormal, + _pathPolyRefs, + ref _polyLength, + 74); + + // raycast() sets hit to FLT_MAX if there is a ray between start and end + if (hit != float.MaxValue) + { + // the ray hit something, return no path instead of the incomplete one + pathType = PathType.NoPath; + return; + } + } + else + { + dtResult = _navMeshQuery.findPath( + startPoly, // start polygon + endPoly, // end polygon + startPoint, // start position + endPoint, // end position + _filter, // polygon search filter + _pathPolyRefs, // [out] path + ref _polyLength, + 74); // max number of polygons in output path + } + + if (_polyLength == 0 || Detour.dtStatusFailed(dtResult)) + { + // only happens if we passed bad data to findPath(), or navmesh is messed up + Log.outError(LogFilter.Maps, "{0}'s Path Build failed: 0 length path", _sourceUnit.GetGUID().ToString()); + BuildShortcut(); + pathType = PathType.NoPath; + return; + } + } + + // by now we know what type of path we can get + if (_pathPolyRefs[_polyLength - 1] == endPoly && !pathType.HasAnyFlag(PathType.Incomplete)) + pathType = PathType.Normal; + else + pathType = PathType.Incomplete; + + // generate the point-path out of our up-to-date poly-path + BuildPointPath(startPoint, endPoint); + } + + void BuildPointPath(float[] startPoint, float[] endPoint) + { + float[] pathPoints = new float[74 * 3]; + int pointCount = 0; + uint dtResult = Detour.DT_FAILURE; + + if (_straightLine) + { + dtResult = Detour.DT_SUCCESS; + pointCount = 1; + Array.Copy(startPoint, pathPoints, 3); // first point + + // path has to be split into polygons with dist SMOOTH_PATH_STEP_SIZE between them + Vector3 startVec = new Vector3(startPoint[0], startPoint[1], startPoint[2]); + Vector3 endVec = new Vector3(endPoint[0], endPoint[1], endPoint[2]); + Vector3 diffVec = (endVec - startVec); + Vector3 prevVec = startVec; + float len = diffVec.GetLength(); + diffVec *= 4.0f / len; + while (len > 4.0f) + { + len -= 4.0f; + prevVec += diffVec; + pathPoints[3 * pointCount + 0] = prevVec.X; + pathPoints[3 * pointCount + 1] = prevVec.Y; + pathPoints[3 * pointCount + 2] = prevVec.Z; + ++pointCount; + } + + Array.Copy(endPoint, 0, pathPoints, 3 * pointCount, 3); // last point + ++pointCount; + } + else if (_useStraightPath) + { + dtResult = _navMeshQuery.findStraightPath( + startPoint, // start position + endPoint, // end position + _pathPolyRefs, + (int)_polyLength, + pathPoints, // [out] path corner points + null, // [out] flags + null, // [out] shortened path + ref pointCount, + (int)_pointPathLimit, + 0); // maximum number of points/polygons to use + } + else + { + dtResult = FindSmoothPath( + startPoint, // start position + endPoint, // end position + _pathPolyRefs, // current path + _polyLength, // length of current path + out pathPoints, // [out] path corner points + out pointCount, + _pointPathLimit); // maximum number of points + } + + if (pointCount < 2 || Detour.dtStatusFailed(dtResult)) + { + // only happens if pass bad data to findStraightPath or navmesh is broken + // single point paths can be generated here + /// @todo check the exact cases + Log.outDebug(LogFilter.Maps, "++ PathGenerator.BuildPointPath FAILED! path sized {0} returned\n", pointCount); + BuildShortcut(); + pathType = PathType.NoPath; + return; + } + else if (pointCount == _pointPathLimit) + { + Log.outDebug(LogFilter.Maps, "++ PathGenerator.BuildPointPath FAILED! path sized {0} returned, lower than limit set to {1}\n", pointCount, _pointPathLimit); + BuildShortcut(); + pathType = PathType.Short; + return; + } + + _pathPoints = new Vector3[pointCount]; + for (uint i = 0; i < pointCount; ++i) + _pathPoints[i] = new Vector3(pathPoints[i * 3 + 2], pathPoints[i * 3], pathPoints[i * 3 + 1]); + + NormalizePath(); + + // first point is always our current location - we need the next one + SetActualEndPosition(_pathPoints[pointCount - 1]); + + // force the given destination, if needed + if (_forceDestination && (!pathType.HasAnyFlag(PathType.Normal) || !InRange(GetEndPosition(), GetActualEndPosition(), 1.0f, 1.0f))) + { + // we may want to keep partial subpath + if (Dist3DSqr(GetActualEndPosition(), GetEndPosition()) < 0.3f * Dist3DSqr(GetStartPosition(), GetEndPosition())) + { + SetActualEndPosition(GetEndPosition()); + _pathPoints[_pathPoints.Length - 1] = GetEndPosition(); + } + else + { + SetActualEndPosition(GetEndPosition()); + BuildShortcut(); + } + + pathType = (PathType.Normal | PathType.NotUsingPath); + } + Log.outDebug(LogFilter.Maps, "PathGenerator.BuildPointPath path type {0} size {1} poly-size {2}\n", pathType, pointCount, _polyLength); + } + + uint FixupCorridor(ulong[] path, uint npath, uint maxPath, ulong[] visited, int nvisited) + { + int furthestPath = -1; + int furthestVisited = -1; + + // Find furthest common polygon. + for (int i = (int)npath - 1; i >= 0; --i) + { + bool found = false; + for (int j = (int)nvisited - 1; j >= 0; --j) + { + if (path[i] == visited[j]) + { + furthestPath = i; + furthestVisited = j; + found = true; + } + } + if (found) + break; + } + + // If no intersection found just return current path. + if (furthestPath == -1 || furthestVisited == -1) + return npath; + + // Concatenate paths. + + // Adjust beginning of the buffer to include the visited. + uint req = (uint)(nvisited - furthestVisited); + uint orig = (uint)((furthestPath + 1) < npath ? furthestPath + 1 : (int)npath); + uint size = npath > orig ? npath - orig : 0; + if (req + size > maxPath) + size = maxPath - req; + + if (size != 0) + Array.Copy(path, (int)orig, path, (int)req, (int)size); + + // Store visited + for (uint i = 0; i < req; ++i) + path[i] = visited[(nvisited - 1) - i]; + + return req + size; + } + + bool GetSteerTarget(float[] startPos, float[] endPos, float minTargetDist, ulong[] path, uint pathSize, out float[] steerPos, out Detour.dtStraightPathFlags steerPosFlag, out ulong steerPosRef) + { + steerPosRef = 0; + steerPos = new float[3]; + steerPosFlag = 0; + + // Find steer target. + float[] steerPath = new float[3 * 3]; + byte[] steerPathFlags = new byte[3]; + ulong[] steerPathPolys = new ulong[3]; + int nsteerPath = 0; + uint dtResult = _navMeshQuery.findStraightPath(startPos, endPos, path, (int)pathSize, steerPath, steerPathFlags, steerPathPolys, ref nsteerPath, 3, 0); + if (nsteerPath == 0 || Detour.dtStatusFailed(dtResult)) + return false; + + // Find vertex far enough to steer to. + uint ns = 0; + while (ns < nsteerPath) + { + // Stop at Off-Mesh link or when point is further than slop away. + if ((steerPathFlags[ns].HasAnyFlag((byte)Detour.dtStraightPathFlags.DT_STRAIGHTPATH_OFFMESH_CONNECTION) || + !InRangeYZX(steerPath.Skip((int)ns * 3).ToArray(), startPos, minTargetDist, 1000.0f))) + break; + ns++; + } + // Failed to find good point to steer to. + if (ns >= nsteerPath) + return false; + + Detour.dtVcopy(steerPos, 0, steerPath, (int)ns * 3); + steerPos[1] = startPos[1]; // keep Z value + steerPosFlag = (Detour.dtStraightPathFlags)steerPathFlags[ns]; + steerPosRef = steerPathPolys[ns]; + + return true; + } + + uint FindSmoothPath(float[] startPos, float[] endPos, ulong[] polyPath, uint polyPathSize, out float[] smoothPath, out int smoothPathSize, uint maxSmoothPathSize) + { + smoothPathSize = 0; + int nsmoothPath = 0; + smoothPath = new float[74 * 3]; + + ulong[] polys = new ulong[74]; + Array.Copy(polyPath, polys, polyPathSize); + uint npolys = polyPathSize; + + float[] iterPos = new float[3]; + float[] targetPos = new float[3]; + if (Detour.dtStatusFailed(_navMeshQuery.closestPointOnPolyBoundary(polys[0], startPos, iterPos))) + return Detour.DT_FAILURE; + + if (Detour.dtStatusFailed(_navMeshQuery.closestPointOnPolyBoundary(polys[npolys - 1], endPos, targetPos))) + return Detour.DT_FAILURE; + + Detour.dtVcopy(smoothPath, nsmoothPath * 3, iterPos, 0); + nsmoothPath++; + + // Move towards target a small advancement at a time until target reached or + // when ran out of memory to store the path. + while (npolys != 0 && nsmoothPath < maxSmoothPathSize) + { + // Find location to steer towards. + float[] steerPos; + Detour.dtStraightPathFlags steerPosFlag; + ulong steerPosRef = 0; + + if (!GetSteerTarget(iterPos, targetPos, 0.3f, polys, npolys, out steerPos, out steerPosFlag, out steerPosRef)) + break; + + bool endOfPath = steerPosFlag.HasAnyFlag(Detour.dtStraightPathFlags.DT_STRAIGHTPATH_END); + bool offMeshConnection = steerPosFlag.HasAnyFlag(Detour.dtStraightPathFlags.DT_STRAIGHTPATH_OFFMESH_CONNECTION); + + // Find movement delta. + float[] delta = new float[3]; + Detour.dtVsub(delta, steerPos, iterPos); + float len = (float)Math.Sqrt(Detour.dtVdot(delta, delta)); + // If the steer target is end of path or off-mesh link, do not move past the location. + if ((endOfPath || offMeshConnection) && len < 4.0f) + len = 1.0f; + else + len = 4.0f / len; + + float[] moveTgt = new float[3]; + Detour.dtVmad(moveTgt, iterPos, delta, len); + + // Move + float[] result = new float[3]; + int MAX_VISIT_POLY = 16; + ulong[] visited = new ulong[MAX_VISIT_POLY]; + + int nvisited = 0; + _navMeshQuery.moveAlongSurface(polys[0], iterPos, moveTgt, _filter, result, visited, ref nvisited, MAX_VISIT_POLY); + npolys = FixupCorridor(polys, npolys, 74, visited, nvisited); + + _navMeshQuery.getPolyHeight(polys[0], result, ref result[1]); + result[1] += 0.5f; + Detour.dtVcopy(iterPos, result); + + // Handle end of path and off-mesh links when close enough. + if (endOfPath && InRangeYZX(iterPos, steerPos, 0.3f, 1.0f)) + { + // Reached end of path. + Detour.dtVcopy(iterPos, targetPos); + if (nsmoothPath < maxSmoothPathSize) + { + Detour.dtVcopy(smoothPath, nsmoothPath * 3, iterPos, 0); + nsmoothPath++; + } + break; + } + else if (offMeshConnection && InRangeYZX(iterPos, steerPos, 0.3f, 1.0f)) + { + // Advance the path up to and over the off-mesh connection. + ulong prevRef = 0; + ulong polyRef = polys[0]; + uint npos = 0; + while (npos < npolys && polyRef != steerPosRef) + { + prevRef = polyRef; + polyRef = polys[npos]; + npos++; + } + + for (uint i = npos; i < npolys; ++i) + polys[i - npos] = polys[i]; + + npolys -= npos; + + // Handle the connection. + float[] connectionStartPos = new float[3]; + float[] connectionEndPos = new float[3]; + if (Detour.dtStatusSucceed(_navMesh.getOffMeshConnectionPolyEndPoints(prevRef, polyRef, connectionStartPos, connectionEndPos))) + { + if (nsmoothPath < maxSmoothPathSize) + { + Detour.dtVcopy(smoothPath, nsmoothPath * 3, connectionStartPos, 0); + nsmoothPath++; + } + // Move position at the other side of the off-mesh link. + Detour.dtVcopy(iterPos, connectionEndPos); + _navMeshQuery.getPolyHeight(polys[0], iterPos, ref iterPos[1]); + iterPos[1] += 0.5f; + } + } + + // Store results. + if (nsmoothPath < maxSmoothPathSize) + { + Detour.dtVcopy(smoothPath, nsmoothPath * 3, iterPos, 0); + nsmoothPath++; + } + } + + smoothPathSize = nsmoothPath; + + // this is most likely a loop + return nsmoothPath < 74 ? Detour.DT_SUCCESS : Detour.DT_FAILURE; + } + + void NormalizePath() + { + for (uint i = 0; i < _pathPoints.Length; ++i) + _sourceUnit.UpdateAllowedPositionZ(_pathPoints[i].X, _pathPoints[i].Y, ref _pathPoints[i].Z); + } + + void BuildShortcut() + { + Log.outDebug(LogFilter.Maps, "BuildShortcut : making shortcut\n"); + + Clear(); + + // make two point path, our curr pos is the start, and dest is the end + _pathPoints = new Vector3[2]; + + // set start and a default next position + _pathPoints[0] = GetStartPosition(); + _pathPoints[1] = GetActualEndPosition(); + + NormalizePath(); + + pathType = PathType.Shortcut; + } + + void CreateFilter() + { + NavTerrain includeFlags = 0; + NavTerrain excludeFlags = 0; + + if (_sourceUnit.IsTypeId(TypeId.Unit)) + { + Creature creature = _sourceUnit.ToCreature(); + if (creature.CanWalk()) + includeFlags |= NavTerrain.Ground; + + if (creature.CanSwim()) + includeFlags |= (NavTerrain.Water | NavTerrain.Magma | NavTerrain.Slime); + } + else + includeFlags = (NavTerrain.Ground | NavTerrain.Water | NavTerrain.Magma | NavTerrain.Slime); + + _filter.setIncludeFlags((ushort)includeFlags); + _filter.setExcludeFlags((ushort)excludeFlags); + + UpdateFilter(); + } + + void UpdateFilter() + { + // allow creatures to cheat and use different movement types if they are moved + // forcefully into terrain they can't normally move in + if (_sourceUnit.IsInWater() || _sourceUnit.IsUnderWater()) + { + NavTerrain includedFlags = (NavTerrain)_filter.getIncludeFlags(); + includedFlags |= GetNavTerrain(_sourceUnit.GetPositionX(), _sourceUnit.GetPositionY(), _sourceUnit.GetPositionZ()); + + _filter.setIncludeFlags((ushort)includedFlags); + } + } + + NavTerrain GetNavTerrain(float x, float y, float z) + { + LiquidData data; + ZLiquidStatus liquidStatus = _sourceUnit.GetMap().getLiquidStatus(x, y, z, MapConst.MapAllLiquidTypes, out data); + if (liquidStatus == ZLiquidStatus.NoWater) + return NavTerrain.Ground; + + switch (data.type_flags) + { + case MapConst.MapLiquidTypeWater: + case MapConst.MapLiquidTypeOcean: + return NavTerrain.Water; + case MapConst.MapLiquidTypeMagma: + return NavTerrain.Magma; + case MapConst.MapLiquidTypeSlime: + return NavTerrain.Slime; + default: + return NavTerrain.Ground; + } + } + + bool InRange(Vector3 p1, Vector3 p2, float r, float h) + { + Vector3 d = p1 - p2; + return (d.X * d.X + d.Y * d.Y) < r * r && Math.Abs(d.Z) < h; + } + + float Dist3DSqr(Vector3 p1, Vector3 p2) + { + return (p1 - p2).GetLengthSquared(); + } + + public void ReducePathLenghtByDist(float dist) + { + if (GetPathType() == PathType.Blank) + { + Log.outError(LogFilter.Maps, "PathGenerator.ReducePathLenghtByDist called before path was built"); + return; + } + + if (_pathPoints.Length < 2) // path building failure + return; + + int i = _pathPoints.Length; + Vector3 nextVec = _pathPoints[--i]; + while (i > 0) + { + Vector3 currVec = _pathPoints[--i]; + Vector3 diffVec = (nextVec - currVec); + float len = diffVec.GetLength(); + if (len > dist) + { + float step = dist / len; + // same as nextVec + _pathPoints[i + 1] -= diffVec * step; + _sourceUnit.UpdateAllowedPositionZ(_pathPoints[i + 1].X, _pathPoints[i + 1].Y, ref _pathPoints[i + 1].Z); + Array.Resize(ref _pathPoints, i + 2); + break; + } + else if (i == 0) // at second point + { + _pathPoints[1] = _pathPoints[0]; + Array.Resize(ref _pathPoints, 2); + break; + } + + dist -= len; + nextVec = currVec; // we're going backwards + } + } + + public bool IsInvalidDestinationZ(Unit target) + { + return (target.GetPositionZ() - GetActualEndPosition().Z) > 5.0f; + } + + void Clear() + { + _polyLength = 0; + _pathPoints = null; + } + + bool HaveTile(Vector3 p) + { + int tx = -1, ty = -1; + float[] point = { p.Y, p.Z, p.X }; + + _navMesh.calcTileLoc(point, ref tx, ref ty); + + /// Workaround + /// For some reason, often the tx and ty variables wont get a valid value + /// Use this check to prevent getting negative tile coords and crashing on getTileAt + if (tx < 0 || ty < 0) + return false; + + return (_navMesh.getTileAt(tx, ty, 0) != null); + } + + bool InRangeYZX(float[] v1, float[] v2, float r, float h) + { + float dx = v2[0] - v1[0]; + float dy = v2[1] - v1[1]; // elevation + float dz = v2[2] - v1[2]; + return (dx * dx + dz * dz) < r * r && Math.Abs(dy) < h; + } + + public Vector3 GetStartPosition() { return _startPosition; } + public Vector3 GetEndPosition() { return _endPosition; } + public Vector3 GetActualEndPosition() { return _actualEndPosition; } + + public Vector3[] GetPath() + { + return _pathPoints; + } + + public PathType GetPathType() { return pathType; } + + void SetStartPosition(Vector3 point) { _startPosition = point; } + void SetEndPosition(Vector3 point) { _actualEndPosition = point; _endPosition = point; } + void SetActualEndPosition(Vector3 point) { _actualEndPosition = point; } + + public void SetUseStraightPath(bool useStraightPath) { _useStraightPath = useStraightPath; } + + public void SetPathLengthLimit(float distance) { _pointPathLimit = Math.Min((uint)(distance / 4.0f), 74); } + + ulong[] _pathPolyRefs = new ulong[74]; + + uint _polyLength; + uint _pointPathLimit; + bool _straightLine; // use raycast if true for a straight line path + Unit _sourceUnit; + bool _forceDestination; + bool _useStraightPath; + Vector3[] _pathPoints; + + Vector3 _actualEndPosition; + Vector3 _startPosition; + Vector3 _endPosition; + PathType pathType; + + Detour.dtQueryFilter _filter = new Detour.dtQueryFilter(); + Detour.dtNavMeshQuery _navMeshQuery; + Detour.dtNavMesh _navMesh; + } + + public enum PathType + { + Blank = 0x00, // path not built yet + Normal = 0x01, // normal path + Shortcut = 0x02, // travel through obstacles, terrain, air, etc (old behavior) + Incomplete = 0x04, // we have partial path to follow - getting closer to target + NoPath = 0x08, // no valid path at all or error in generating one + NotUsingPath = 0x10, // used when we are either flying/swiming or on map w/o mmaps + Short = 0x20, // path is longer or equal to its limited path length + } + public enum NavTerrain + { + Empty = 0x00, + Ground = 0x01, + Magma = 0x02, + Slime = 0x04, + Water = 0x08, + Unused1 = 0x10, + Unused2 = 0x20, + Unused3 = 0x40, + Unused4 = 0x80 + // we only have 8 bits + } + + public enum PolyFlag + { + Walk = 1, + Swim = 2 + } +} diff --git a/Game/Movement/Generators/PointMovement.cs b/Game/Movement/Generators/PointMovement.cs new file mode 100644 index 000000000..a377ed31a --- /dev/null +++ b/Game/Movement/Generators/PointMovement.cs @@ -0,0 +1,207 @@ +/* + * Copyright (C) 2012-2017 CypherCore + * + * 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, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +using Framework.Constants; +using Game.Entities; + +namespace Game.Movement +{ + public class PointMovementGenerator : MovementGeneratorMedium where T : Unit + { + public PointMovementGenerator(ulong _id, float _x, float _y, float _z, bool _generatePath, float _speed = 0.0f, Unit faceTarget = null, SpellEffectExtraData spellEffectExtraData = null) + { + id = _id; + i_x = _x; + i_y = _y; + i_z = _z; + speed = _speed; + i_faceTarget = faceTarget; + i_spellEffectExtra = spellEffectExtraData; + m_generatePath = _generatePath; + i_recalculateSpeed = false; + } + + public override void DoInitialize(T owner) + { + if (!owner.IsStopped()) + owner.StopMoving(); + + owner.AddUnitState(UnitState.Roaming | UnitState.RoamingMove); + + if (id == EventId.ChargePrepath) + return; + + MoveSplineInit init = new MoveSplineInit(owner); + init.MoveTo(i_x, i_y, i_z, m_generatePath); + if (speed > 0.0f) + init.SetVelocity(speed); + + if (i_faceTarget) + init.SetFacing(i_faceTarget); + + if (i_spellEffectExtra != null) + init.SetSpellEffectExtraData(i_spellEffectExtra); + + init.Launch(); + + // Call for creature group update + Creature creature = owner.ToCreature(); + if (creature != null) + if (creature.GetFormation() != null && creature.GetFormation().getLeader() == creature) + creature.GetFormation().LeaderMoveTo(i_x, i_y, i_z); + } + + public override bool DoUpdate(T owner, uint time_diff) + { + if (owner == null) + return false; + + if (owner.HasUnitState(UnitState.Root | UnitState.Stunned)) + { + owner.ClearUnitState(UnitState.RoamingMove); + return true; + } + + owner.AddUnitState(UnitState.RoamingMove); + + if (id != EventId.ChargePrepath && i_recalculateSpeed && !owner.moveSpline.Finalized()) + { + i_recalculateSpeed = false; + MoveSplineInit init = new MoveSplineInit(owner); + init.MoveTo(i_x, i_y, i_z, m_generatePath); + if (speed > 0.0f) // Default value for point motion type is 0.0, if 0.0 spline will use GetSpeed on unit + init.SetVelocity(speed); + init.Launch(); + + // Call for creature group update + Creature creature = owner.ToCreature(); + if (creature != null) + if (creature.GetFormation() != null && creature.GetFormation().getLeader() == creature) + creature.GetFormation().LeaderMoveTo(i_x, i_y, i_z); + } + + return !owner.moveSpline.Finalized(); + } + + public override void DoFinalize(T owner) + { + if (owner.HasUnitState(UnitState.Charging)) + owner.ClearUnitState(UnitState.Roaming | UnitState.RoamingMove); + + if (owner.moveSpline.Finalized()) + MovementInform(owner); + } + + public override void DoReset(T owner) + { + if (!owner.IsStopped()) + owner.StopMoving(); + + owner.AddUnitState(UnitState.Roaming | UnitState.RoamingMove); + } + + public void MovementInform(T unit) + { + if (!unit.IsTypeId(TypeId.Unit)) + return; + + if (unit.ToCreature().GetAI() != null) + unit.ToCreature().GetAI().MovementInform(MovementGeneratorType.Point, (uint)id); + } + + public override void unitSpeedChanged() + { + i_recalculateSpeed = true; + } + + public override MovementGeneratorType GetMovementGeneratorType() + { + return MovementGeneratorType.Point; + } + + ulong id; + float i_x, i_y, i_z; + float speed; + Unit i_faceTarget; + SpellEffectExtraData i_spellEffectExtra; + bool m_generatePath; + bool i_recalculateSpeed; + } + + public class AssistanceMovementGenerator : PointMovementGenerator + { + public AssistanceMovementGenerator(float _x, float _y, float _z) : base(0, _x, _y, _z, true) { } + + public override MovementGeneratorType GetMovementGeneratorType() { return MovementGeneratorType.Assistance; } + + public override void Finalize(Unit owner) + { + owner.ToCreature().SetNoCallAssistance(false); + owner.ToCreature().CallAssistance(); + if (owner.IsAlive()) + owner.GetMotionMaster().MoveSeekAssistanceDistract(WorldConfig.GetUIntValue(WorldCfg.CreatureFamilyAssistanceDelay)); + } + } + + // Does almost nothing - just doesn't allows previous movegen interrupt current effect. + public class EffectMovementGenerator : IMovementGenerator + { + public EffectMovementGenerator(uint Id, uint arrivalSpellId = 0, ObjectGuid arrivalSpellTargetGuid = default(ObjectGuid)) + { + _Id = Id; + _arrivalSpellId = arrivalSpellId; + _arrivalSpellTargetGuid = arrivalSpellTargetGuid; + } + + public override void Finalize(Unit unit) + { + if (_arrivalSpellId != 0) + unit.CastSpell(Global.ObjAccessor.GetUnit(unit, _arrivalSpellTargetGuid), _arrivalSpellId, true); + + if (!unit.IsTypeId(TypeId.Unit)) + return; + + // Need restore previous movement since we have no proper states system + if (unit.IsAlive() && !unit.HasUnitState(UnitState.Confused | UnitState.Fleeing)) + { + Unit victim = unit.GetVictim(); + if (victim != null) + unit.GetMotionMaster().MoveChase(victim); + else + unit.GetMotionMaster().Initialize(); + } + + if (unit.ToCreature().GetAI() != null) + unit.ToCreature().GetAI().MovementInform(MovementGeneratorType.Effect, _Id); + } + + public override bool Update(Unit owner, uint time_diff) + { + return !owner.moveSpline.Finalized(); + } + + public override void Initialize(Unit owner) { } + + public override void Reset(Unit owner) { } + + public override MovementGeneratorType GetMovementGeneratorType() { return MovementGeneratorType.Effect; } + + uint _Id; + uint _arrivalSpellId; + ObjectGuid _arrivalSpellTargetGuid; + } +} diff --git a/Game/Movement/Generators/RandomMovement.cs b/Game/Movement/Generators/RandomMovement.cs new file mode 100644 index 000000000..82144c0a4 --- /dev/null +++ b/Game/Movement/Generators/RandomMovement.cs @@ -0,0 +1,179 @@ +/* + * Copyright (C) 2012-2017 CypherCore + * + * 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, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +using Framework.Constants; +using Game.Entities; +using Game.Maps; +using System; + +namespace Game.Movement +{ + public class RandomMovementGenerator : MovementGeneratorMedium where T : Creature + { + public RandomMovementGenerator(float spawn_dist = 0.0f) + { + i_nextMoveTime = new TimeTrackerSmall(); + wander_distance = spawn_dist; + } + + TimeTrackerSmall i_nextMoveTime; + float wander_distance; + + public override MovementGeneratorType GetMovementGeneratorType() + { + return MovementGeneratorType.Random; + } + + public override void DoInitialize(T creature) + { + if (!creature.IsAlive()) + return; + + if (wander_distance == 0) + wander_distance = creature.GetRespawnRadius(); + + if (wander_distance == 0)//Temp fix + wander_distance = 50.0f; + + creature.AddUnitState(UnitState.Roaming | UnitState.RoamingMove); + _setRandomLocation(creature); + + } + public override void DoFinalize(T creature) + { + creature.ClearUnitState(UnitState.Roaming | UnitState.RoamingMove); + creature.SetWalk(false); + } + public override void DoReset(T creature) + { + DoInitialize(creature); + } + + public override bool DoUpdate(T creature, uint diff) + { + if (!creature || !creature.IsAlive()) + return false; + + if (creature.HasUnitState(UnitState.Root | UnitState.Stunned | UnitState.Distracted)) + { + i_nextMoveTime.Reset(0); // Expire the timer + creature.ClearUnitState(UnitState.RoamingMove); + return true; + } + + if (creature.moveSpline.Finalized()) + { + i_nextMoveTime.Update((int)diff); + if (i_nextMoveTime.Passed()) + _setRandomLocation(creature); + } + return true; + } + + public void _setRandomLocation(T creature) + { + if (creature.HasUnitState(UnitState.Casting) && !creature.CanMoveDuringChannel()) + { + creature.CastStop(); + return; + } + + float respX, respY, respZ, respO, destX, destY, destZ, travelDistZ; + creature.GetHomePosition(out respX, out respY, out respZ, out respO); + Map map = creature.GetMap(); + + bool is_air_ok = creature.CanFly(); + + float angle = (float)(RandomHelper.NextDouble() * MathFunctions.TwoPi); + float range = (float)(RandomHelper.NextDouble() * wander_distance); + float distanceX = (float)(range * Math.Cos(angle)); + float distanceY = (float)(range * Math.Sin(angle)); + + destX = respX + distanceX; + destY = respY + distanceY; + + // prevent invalid coordinates generation + GridDefines.NormalizeMapCoord(ref destX); + GridDefines.NormalizeMapCoord(ref destY); + + travelDistZ = range; // sin^2+cos^2=1, so travelDistZ=range^2; no need for sqrt below + + if (is_air_ok) // 3D system above ground and above water (flying mode) + { + // Limit height change + float distanceZ = (float)(RandomHelper.NextDouble() * travelDistZ / 2.0f); + destZ = respZ + distanceZ; + float levelZ = map.GetWaterOrGroundLevel(creature.GetPhases(), destX, destY, destZ - 2.5f); + + // Problem here, we must fly above the ground and water, not under. Let's try on next tick + if (levelZ >= destZ) + return; + } + else // 2D only + { + // 10.0 is the max that vmap high can check (MAX_CAN_FALL_DISTANCE) + travelDistZ = travelDistZ >= 10.0f ? 10.0f : travelDistZ; + + // The fastest way to get an accurate result 90% of the time. + // Better result can be obtained like 99% accuracy with a ray light, but the cost is too high and the code is too long. + destZ = map.GetHeight(creature.GetPhases(), destX, destY, respZ + travelDistZ - 2.0f, false); + + if (Math.Abs(destZ - respZ) > travelDistZ) // Map check + { + // Vmap Horizontal or above + destZ = map.GetHeight(creature.GetPhases(), destX, destY, respZ - 2.0f, true); + + if (Math.Abs(destZ - respZ) > travelDistZ) + { + // Vmap Higher + destZ = map.GetHeight(creature.GetPhases(), destX, destY, respZ + travelDistZ - 2.0f, true); + + // let's forget this bad coords where a z cannot be find and retry at next tick + if (Math.Abs(destZ - respZ) > travelDistZ) + return; + } + } + } + + if (is_air_ok) + i_nextMoveTime.Reset(0); + else + { + if (RandomHelper.randChance(50)) + i_nextMoveTime.Reset(RandomHelper.IRand(5000, 10000)); + else + i_nextMoveTime.Reset(RandomHelper.IRand(50, 400)); + } + + creature.AddUnitState(UnitState.RoamingMove); + if (destZ < 25f) + { + + } + MoveSplineInit init = new MoveSplineInit(creature); + init.MoveTo(destX, destY, destZ); + init.SetWalk(true); + init.Launch(); + + //Call for creature group update + if (creature.GetFormation() != null && creature.GetFormation().getLeader() == creature) + creature.GetFormation().LeaderMoveTo(destX, destY, destZ); + } + + public virtual bool GetResetPosition(Creature creature, float x, float y, float z) { return false; } + } +} diff --git a/Game/Movement/Generators/SplineChainMovementGenerator.cs b/Game/Movement/Generators/SplineChainMovementGenerator.cs new file mode 100644 index 000000000..43ce695fb --- /dev/null +++ b/Game/Movement/Generators/SplineChainMovementGenerator.cs @@ -0,0 +1,186 @@ +/* + * Copyright (C) 2012-2017 CypherCore + * + * 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, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +using Framework.Constants; +using Framework.GameMath; +using Game.Entities; +using System; +using System.Collections.Generic; +using System.Diagnostics.Contracts; +using System.Linq; + +namespace Game.Movement +{ + public class SplineChainMovementGenerator : IMovementGenerator + { + public SplineChainMovementGenerator(uint id, List chain, bool walk = false) + { + _id = id; + _chain = chain; + _chainSize = (byte)chain.Count; + _walk = walk; + } + public SplineChainMovementGenerator(SplineChainResumeInfo info) + { + _id = info.PointID; + _chain = info.Chain; + _chainSize = (byte)info.Chain.Count; + _walk = info.IsWalkMode; + finished = info.SplineIndex >= info.Chain.Count; + _nextIndex = info.SplineIndex; + _nextFirstWP = info.PointIndex; + _msToNext = info.TimeToNext; + } + + uint SendPathSpline(Unit me, List wp) + { + int numWp = wp.Count; + Contract.Assert(numWp > 1, "Every path must have source & destination"); + MoveSplineInit init = new MoveSplineInit(me); + if (numWp > 2) + init.MovebyPath(wp.ToArray()); + else + init.MoveTo(wp[1], false, true); + init.SetWalk(_walk); + return (uint)init.Launch(); + } + + void SendSplineFor(Unit me, int index, uint toNext) + { + Contract.Assert(index < _chainSize); + Log.outDebug(LogFilter.Movement, "{0}: Sending spline for {1}.", me.GetGUID().ToString(), index); + + SplineChainLink thisLink = _chain[index]; + uint actualDuration = SendPathSpline(me, thisLink.Points); + if (actualDuration != thisLink.ExpectedDuration) + { + Log.outDebug(LogFilter.Movement, "{0}: Sent spline for {1}, duration is {2} ms. Expected was {3} ms (delta {4} ms). Adjusting.", me.GetGUID().ToString(), index, actualDuration, thisLink.ExpectedDuration, actualDuration - thisLink.ExpectedDuration); + toNext = (uint)(actualDuration / thisLink.ExpectedDuration * toNext); + } + else + { + Log.outDebug(LogFilter.Movement, "{0}: Sent spline for {1}, duration is {2} ms.", me.GetGUID().ToString(), index, actualDuration); + } + } + + public override void Initialize(Unit me) + { + if (_chainSize != 0) + { + if (_nextFirstWP != 0) // this is a resumed movegen that has to start with a partial spline + { + if (finished) + return; + SplineChainLink thisLink = _chain[_nextIndex]; + if (_nextFirstWP >= thisLink.Points.Count) + { + Log.outError(LogFilter.Movement, "{0}: Attempted to resume spline chain from invalid resume state ({1}, {2}).", me.GetGUID().ToString(), _nextIndex, _nextFirstWP); + _nextFirstWP = (byte)(thisLink.Points.Count - 1); + } + List partial = new List(); + partial.AddRange(thisLink.Points.Skip(_nextFirstWP - 1).ToArray()); + SendPathSpline(me, partial); + Log.outDebug(LogFilter.Movement, "{0}: Resumed spline chain generator from resume state.", me.GetGUID().ToString()); + ++_nextIndex; + if (_msToNext == 0) + _msToNext = 1; + _nextFirstWP = 0; + } + else + { + _msToNext = Math.Max(_chain[_nextIndex].TimeToNext, 1u); + SendSplineFor(me, _nextIndex, _msToNext); + ++_nextIndex; + if (_nextIndex >= _chainSize) + _msToNext = 0; + } + } + else + { + Log.outError(LogFilter.Movement, "SplineChainMovementGenerator.Initialize - empty spline chain passed for {0}.", me.GetGUID().ToString()); + } + } + + public override void Finalize(Unit me) + { + if (!finished) + return; + + Creature cMe = me.ToCreature(); + if (cMe && cMe.IsAIEnabled) + cMe.GetAI().MovementInform(MovementGeneratorType.SplineChain, _id); + } + + public override bool Update(Unit me, uint diff) + { + if (finished) + return false; + + // _msToNext being zero here means we're on the final spline + if (_msToNext == 0) + { + finished = me.moveSpline.Finalized(); + return !finished; + } + + if (_msToNext <= diff) + { + // Send next spline + Log.outDebug(LogFilter.Movement, "{0}: Should send spline {1} ({2} ms late).", me.GetGUID().ToString(), _nextIndex, diff - _msToNext); + _msToNext = Math.Max(_chain[_nextIndex].TimeToNext, 1u); + SendSplineFor(me, _nextIndex, _msToNext); + ++_nextIndex; + if (_nextIndex >= _chainSize) + { + // We have reached the final spline, once it finalizes we should also finalize the movegen (start checking on next update) + _msToNext = 0; + return true; + } + } + else + _msToNext -= diff; + return true; + } + + SplineChainResumeInfo GetResumeInfo(Unit me) + { + if (_nextIndex == 0) + return new SplineChainResumeInfo(_id, _chain, _walk, 0, 0, _msToNext); + if (me.moveSpline.Finalized()) + { + if (_nextIndex < _chainSize) + return new SplineChainResumeInfo(_id, _chain, _walk, _nextIndex, 0, 1u); + else + return new SplineChainResumeInfo(); + } + return new SplineChainResumeInfo(_id, _chain, _walk, (byte)(_nextIndex - 1), (byte)(me.moveSpline._currentSplineIdx()), _msToNext); + } + + public override void Reset(Unit owner) { } + + public override MovementGeneratorType GetMovementGeneratorType() { return MovementGeneratorType.SplineChain; } + + uint _id; + List _chain = new List(); + byte _chainSize; + bool _walk; + bool finished; + byte _nextIndex; + byte _nextFirstWP; // only used for resuming + uint _msToNext; + } +} diff --git a/Game/Movement/Generators/TargetMovement.cs b/Game/Movement/Generators/TargetMovement.cs new file mode 100644 index 000000000..8d7db90c8 --- /dev/null +++ b/Game/Movement/Generators/TargetMovement.cs @@ -0,0 +1,401 @@ +/* + * Copyright (C) 2012-2017 CypherCore + * + * 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, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +using Framework.Constants; +using Framework.GameMath; +using Game.Entities; +using System; + +namespace Game.Movement +{ + public interface TargetedMovementGeneratorBase + { + FollowerReference reftarget { get; set; } + void stopFollowing(); + } + + public abstract class TargetedMovementGeneratorMedium : MovementGeneratorMedium, TargetedMovementGeneratorBase where T : Unit + { + public FollowerReference reftarget { get; set; } + public Unit target + { + get { return reftarget.getTarget(); } + } + + public void stopFollowing() { } + + public TargetedMovementGeneratorMedium(Unit _target, float _offset = 0, float _angle = 0) + { + reftarget = new FollowerReference(); + reftarget.link(_target, this); + recheckDistance = new TimeTrackerSmall(); + offset = _offset; + angle = _angle; + recalculateTravel = false; + targetReached = false; + } + + public override bool DoUpdate(T owner, uint time_diff) + { + if (!reftarget.isValid() || !target.IsInWorld) + return false; + + if (owner == null || !owner.IsAlive()) + return false; + + if (owner.HasUnitState(UnitState.NotMove)) + { + _clearUnitStateMove(owner); + return true; + } + + // prevent movement while casting spells with cast time or channel time + if (owner.HasUnitState(UnitState.Casting) && !owner.CanMoveDuringChannel()) + { + if (!owner.isStopped()) + owner.StopMoving(); + return true; + } + + // prevent crash after creature killed pet + if (_lostTarget(owner)) + { + _clearUnitStateMove(owner); + return true; + } + + bool targetMoved = false; + recheckDistance.Update((int)time_diff); + if (recheckDistance.Passed()) + { + recheckDistance.Reset(100); + //More distance let have better performance, less distance let have more sensitive reaction at target move. + float allowed_dist = 0.0f;// owner.GetCombatReach() + WorldConfig.GetFloatValue(WorldCfg.RateTargetPosRecalculationRange); + + if (owner.IsPet() && (owner.GetCharmerOrOwnerGUID() == target.GetGUID())) + allowed_dist = 1.0f; // pet following owner + else + allowed_dist = owner.GetCombatReach() + WorldConfig.GetFloatValue(WorldCfg.RateTargetPosRecalculationRange); + + Vector3 dest = owner.moveSpline.FinalDestination(); + if (owner.moveSpline.onTransport) + { + float o = 0; + ITransport transport = owner.GetDirectTransport(); + if (transport != null) + transport.CalculatePassengerPosition(ref dest.X, ref dest.Y, ref dest.Z, ref o); + } + + // First check distance + if (owner.IsTypeId(TypeId.Unit) && (owner.ToCreature().CanFly() || owner.ToCreature().CanSwim())) + targetMoved = !target.IsWithinDist3d(dest.X, dest.Y, dest.Z, allowed_dist); + else + targetMoved = !target.IsWithinDist2d(dest.X, dest.Y, allowed_dist); + + + // then, if the target is in range, check also Line of Sight. + if (!targetMoved) + targetMoved = !target.IsWithinLOSInMap(owner); + } + + if (recalculateTravel || targetMoved) + _setTargetLocation(owner, targetMoved); + + if (owner.moveSpline.Finalized()) + { + MovementInform(owner); + if (angle == 0.0f && !owner.HasInArc(0.01f, target)) + owner.SetInFront(target); + + if (!targetReached) + { + targetReached = true; + _reachTarget(owner); + } + } + + return true; + } + + public override void unitSpeedChanged() + { + recalculateTravel = true; + } + + public void _setTargetLocation(T owner, bool updateDestination) + { + if (!reftarget.isValid() || !target.IsInWorld) + return; + + if (owner.HasUnitState(UnitState.NotMove)) + return; + + if (owner.HasUnitState(UnitState.Casting) && !owner.CanMoveDuringChannel()) + return; + + if (owner.IsTypeId(TypeId.Unit) && !target.isInAccessiblePlaceFor(owner.ToCreature())) + { + owner.ToCreature().SetCannotReachTarget(true); + return; + } + + if (owner.IsTypeId(TypeId.Unit) && owner.ToCreature().IsFocusing(null, true)) + return; + + float x, y, z; + if (updateDestination || i_path == null) + { + if (offset == 0) + { + if (target.IsWithinDistInMap(owner, SharedConst.ContactDistance)) + return; + + // to nearest contact position + target.GetContactPoint(owner, out x, out y, out z); + } + else + { + float dist = 0; + float size = 0; + + // Pets need special handling. + // We need to subtract GetObjectSize() because it gets added back further down the chain + // and that makes pets too far away. Subtracting it allows pets to properly + // be (GetCombatReach() + i_offset) away. + // Only applies when i_target is pet's owner otherwise pets and mobs end up + // doing a "dance" while fighting + if (owner.IsPet() && target.IsTypeId(TypeId.Player)) + { + dist = 1.0f;// target.GetCombatReach(); + size = 1.0f;// target.GetCombatReach() - target.GetObjectSize(); + } + else + { + dist = offset + 1.0f; + size = owner.GetObjectSize(); + } + + if (target.IsWithinDistInMap(owner, dist)) + return; + + // to at i_offset distance from target and i_angle from target facing + target.GetClosePoint(out x, out y, out z, size, offset, angle); + } + } + else + { + // the destination has not changed, we just need to refresh the path (usually speed change) + var end = i_path.GetEndPosition(); + x = end.X; + y = end.Y; + z = end.Z; + } + + if (i_path == null) + i_path = new PathGenerator(owner); + + // allow pets to use shortcut if no path found when following their master + bool forceDest = (owner.IsTypeId(TypeId.Unit) && owner.IsPet() + && owner.HasUnitState(UnitState.Follow)); + + bool result = i_path.CalculatePath(x, y, z, forceDest); + if (!result && Convert.ToBoolean(i_path.GetPathType() & PathType.NoPath)) + { + // Can't reach target + recalculateTravel = true; + if (owner.IsTypeId(TypeId.Unit)) + owner.ToCreature().SetCannotReachTarget(true); + return; + } + + _addUnitStateMove(owner); + targetReached = false; + recalculateTravel = false; + owner.AddUnitState(UnitState.Chase); + if (owner.IsTypeId(TypeId.Unit)) + owner.ToCreature().SetCannotReachTarget(false); + + MoveSplineInit init = new MoveSplineInit(owner); + init.MovebyPath(i_path.GetPath()); + init.SetWalk(EnableWalking()); + // Using the same condition for facing target as the one that is used for SetInFront on movement end + // - applies to ChaseMovementGenerator mostly + if (angle == 0.0f) + init.SetFacing(target); + + init.Launch(); + } + + public void UpdateFinalDistance(float fDistance) + { + if (typeof(T) == typeof(Player)) + return; + offset = fDistance; + recalculateTravel = true; + } + + bool IsReachable() { return (i_path != null) ? Convert.ToBoolean(i_path.GetPathType() & PathType.Normal) : true; } + + public abstract void MovementInform(T unit); + public abstract bool _lostTarget(T u); + public abstract void _clearUnitStateMove(T u); + public abstract void _addUnitStateMove(T u); + public abstract void _reachTarget(T owner); + public abstract bool EnableWalking(); + public abstract void _updateSpeed(T u); + + #region Fields + PathGenerator i_path; + TimeTrackerSmall recheckDistance; + float offset; + float angle; + public bool recalculateTravel; + bool targetReached; + #endregion + } + + public class ChaseMovementGenerator : TargetedMovementGeneratorMedium> where T : Unit + { + public ChaseMovementGenerator(Unit target) + : base(target) + { + } + public ChaseMovementGenerator(Unit target, float offset, float angle) + : base(target, offset, angle) + { + } + + public override MovementGeneratorType GetMovementGeneratorType() { return MovementGeneratorType.Chase; } + + public override void DoInitialize(T owner) + { + if (owner.IsTypeId(TypeId.Unit)) + owner.SetWalk(false); + + owner.AddUnitState(UnitState.Chase | UnitState.ChaseMove); + _setTargetLocation(owner, true); + } + + public override void DoFinalize(T owner) + { + owner.ClearUnitState(UnitState.Chase | UnitState.ChaseMove); + } + + public override void DoReset(T owner) + { + DoInitialize(owner); + } + + public override bool _lostTarget(T u) + { + return u.GetVictim() != target; + } + public override void _clearUnitStateMove(T u) + { + u.ClearUnitState(UnitState.ChaseMove); + } + public override void _addUnitStateMove(T u) + { + u.AddUnitState(UnitState.ChaseMove); + } + + public override bool EnableWalking() { return false; } + public override void _updateSpeed(T u) { } + public override void _reachTarget(T owner) + { + _clearUnitStateMove(owner); + if (owner.IsWithinMeleeRange(target)) + owner.Attack(target, true); + if (owner.IsTypeId(TypeId.Unit)) + owner.ToCreature().SetCannotReachTarget(false); + } + public override void MovementInform(T unit) + { + if (unit.IsTypeId(TypeId.Unit)) + { + // Pass back the GUIDLow of the target. If it is pet's owner then PetAI will handle + if (unit.ToCreature().GetAI() != null) + unit.ToCreature().GetAI().MovementInform(MovementGeneratorType.Chase, (uint)target.GetGUID().GetCounter()); + } + } + } + + public class FollowMovementGenerator : TargetedMovementGeneratorMedium> where T : Unit + { + public FollowMovementGenerator(Unit target) : base(target) { } + public FollowMovementGenerator(Unit target, float offset, float angle) : base(target, offset, angle) { } + + public override MovementGeneratorType GetMovementGeneratorType() + { + return MovementGeneratorType.Follow; + } + public override void _clearUnitStateMove(T u) + { + u.ClearUnitState(UnitState.FollowMove); + } + public override void _addUnitStateMove(T u) + { + u.AddUnitState(UnitState.FollowMove); + } + public override void DoReset(T owner) + { + DoInitialize(owner); + } + public override bool _lostTarget(T u) { return false; } + public override void _reachTarget(T u) { } + + public override void DoInitialize(T owner) + { + owner.AddUnitState(UnitState.Follow | UnitState.FollowMove); + _updateSpeed(owner); + _setTargetLocation(owner, true); + } + public override void DoFinalize(T owner) + { + owner.ClearUnitState(UnitState.Follow | UnitState.FollowMove); + _updateSpeed(owner); + } + public override void MovementInform(T unit) + { + if (unit.IsTypeId(TypeId.Player)) + return; + + // Pass back the GUIDLow of the target. If it is pet's owner then PetAI will handle + if (unit.ToCreature().GetAI() != null) + unit.ToCreature().GetAI().MovementInform(MovementGeneratorType.Follow, (uint)target.GetGUID().GetCounter()); + } + public override bool EnableWalking() + { + if (typeof(T) == typeof(Player)) + return false; + else + return reftarget.isValid() && target.IsWalking(); + } + public override void _updateSpeed(T owner) + { + if (owner.IsTypeId(TypeId.Player)) + return; + + if (!owner.IsPet() || !owner.IsInWorld || !reftarget.isValid() && target.GetGUID() != owner.GetOwnerGUID()) + return; + + owner.UpdateSpeed(UnitMoveType.Run); + owner.UpdateSpeed(UnitMoveType.Walk); + owner.UpdateSpeed(UnitMoveType.Swim); + } + } +} diff --git a/Game/Movement/Generators/WaypointMovement.cs b/Game/Movement/Generators/WaypointMovement.cs new file mode 100644 index 000000000..c8ffa0d47 --- /dev/null +++ b/Game/Movement/Generators/WaypointMovement.cs @@ -0,0 +1,503 @@ +/* + * Copyright (C) 2012-2017 CypherCore + * + * 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, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +using Framework.Constants; +using Framework.GameMath; +using Game.DataStorage; +using Game.Entities; +using Game.Maps; +using System; +using System.Collections.Generic; +using System.Linq; + +namespace Game.Movement +{ + public class WaypointMovementGenerator : MovementGeneratorMedium where T : Creature + { + const int FLIGHT_TRAVEL_UPDATE = 100; + const int TIMEDIFF_NEXT_WP = 250; + public WaypointMovementGenerator(uint pathid = 0, bool _repeating = true) + { + nextMoveTime = new TimeTrackerSmall(0); + isArrivalDone = false; + pathId = pathid; + repeating = _repeating; + } + public override void DoReset(T owner) + { + owner.AddUnitState(UnitState.Roaming | UnitState.RoamingMove); + StartMoveNow(owner); + } + + public override void DoFinalize(T owner) + { + owner.ClearUnitState(UnitState.Roaming | UnitState.RoamingMove); + owner.SetWalk(false); + } + public override void DoInitialize(T owner) + { + LoadPath(owner); + owner.AddUnitState(UnitState.Roaming | UnitState.RoamingMove); + } + public override bool DoUpdate(T owner, uint time_diff) + { + // Waypoint movement can be switched on/off + // This is quite handy for escort quests and other stuff + if (owner.HasUnitState(UnitState.NotMove)) + { + owner.ClearUnitState(UnitState.RoamingMove); + return true; + } + // prevent a crash at empty waypoint path. + if (path == null || path.Empty()) + return false; + + if (Stopped()) + { + if (CanMove((int)time_diff)) + return StartMove(owner); + } + else + { + if (owner.IsStopped()) + Stop(WorldConfig.GetIntValue(WorldCfg.CreatureStopForPlayer)); + else if (owner.moveSpline.Finalized()) + { + OnArrived(owner); + return StartMove(owner); + } + } + return true; + } + void MovementInform(Creature creature) + { + if (creature.IsAIEnabled) + creature.GetAI().MovementInform(MovementGeneratorType.Waypoint, currentNode); + } + + void Stop(int time) + { + nextMoveTime.Reset(time); + } + bool Stopped() + { + return !nextMoveTime.Passed(); + } + bool CanMove(int diff) + { + nextMoveTime.Update(diff); + return nextMoveTime.Passed(); + } + void StartMoveNow(Creature creature) + { + nextMoveTime.Reset(0); + StartMove(creature); + } + bool StartMove(Creature creature) + { + if (path == null || path.Empty()) + return false; + + if (Stopped()) + return true; + + bool transportPath = creature.GetTransport() != null; + + if (isArrivalDone) + { + if ((currentNode == path.Count - 1) && !repeating) // If that's our last waypoint + { + float x = path[(int)currentNode].x; + float y = path[(int)currentNode].y; + float z = path[(int)currentNode].z; + float o = path[(int)currentNode].orientation; + + if (!transportPath) + creature.SetHomePosition(x, y, z, o); + else + { + Transport trans = creature.GetTransport(); + if (trans) + { + o -= trans.GetOrientation(); + creature.SetTransportHomePosition(x, y, z, o); + trans.CalculatePassengerPosition(ref x, ref y, ref z, ref o); + creature.SetHomePosition(x, y, z, o); + } + else + transportPath = false; + // else if (vehicle) - this should never happen, vehicle offsets are const + } + + creature.GetMotionMaster().Initialize(); + return false; + } + + currentNode = (uint)((currentNode + 1) % path.Count); + } + + WaypointData node = path.LookupByIndex((int)currentNode); + + isArrivalDone = false; + + creature.AddUnitState(UnitState.RoamingMove); + + MoveSplineInit init = new MoveSplineInit(creature); + Position formationDest = new Position(node.x, node.y, node.z); + + //! If creature is on transport, we assume waypoints set in DB are already transport offsets + if (transportPath) + { + init.DisableTransportPathTransformations(); + ITransport trans = creature.GetDirectTransport(); + if (trans != null) + trans.CalculatePassengerPosition(ref formationDest.posX, ref formationDest.posY, ref formationDest.posZ, ref formationDest.Orientation); + } + + init.MoveTo(formationDest.posX, formationDest.posY, formationDest.posZ); + + //! Accepts angles such as 0.00001 and -0.00001, 0 must be ignored, default value in waypoint table + if (node.orientation != 0 && node.delay != 0) + init.SetFacing(formationDest.Orientation); + + switch (node.movetype) + { + case WaypointMoveType.Land: + init.SetAnimation(AnimType.ToGround); + break; + case WaypointMoveType.Takeoff: + init.SetAnimation(AnimType.ToFly); + break; + case WaypointMoveType.Run: + init.SetWalk(false); + break; + case WaypointMoveType.Walk: + init.SetWalk(true); + break; + } + + init.Launch(); + + //Call for creature group update + if (creature.GetFormation() != null && creature.GetFormation().getLeader() == creature) + { + creature.SetWalk(node.movetype != WaypointMoveType.Run); + creature.GetFormation().LeaderMoveTo(formationDest.posX, formationDest.posY, formationDest.posZ); + } + + return true; + } + void LoadPath(Creature creature) + { + if (pathId == 0) + pathId = creature.GetWaypointPath(); + + path = Global.WaypointMgr.GetPath(pathId); + + if (path == null) + { + // No movement found for entry + Log.outError(LogFilter.ScriptsAi, "WaypointMovementGenerator.LoadPath: creature {0} (Entry: {1} GUID: {2}) doesn't have waypoint path id: {3}", creature.GetName(), creature.GetEntry(), creature.GetGUID().ToString(), pathId); + return; + } + + StartMoveNow(creature); + } + void OnArrived(Creature creature) + { + if (path == null || path.Empty()) + return; + + if (isArrivalDone) + return; + + creature.ClearUnitState(UnitState.RoamingMove); + isArrivalDone = true; + + var wpData = path.LookupByIndex((int)currentNode); + if (wpData.event_id != 0 && RandomHelper.IRand(0, 99) < wpData.event_chance) + { + Log.outDebug(LogFilter.Unit, "Creature movement start script {0} at point {1} for {2}.", wpData.event_id, currentNode, creature.GetGUID()); + creature.GetMap().ScriptsStart(ScriptsType.Waypoint, wpData.event_id, creature, null); + } + + // Inform script + MovementInform(creature); + creature.UpdateWaypointID(currentNode); + + if (wpData.delay != 0) + { + creature.ClearUnitState(UnitState.RoamingMove); + Stop((int)wpData.delay); + } + } + + public override MovementGeneratorType GetMovementGeneratorType() + { + return MovementGeneratorType.Waypoint; + } + + TimeTrackerSmall nextMoveTime; + bool isArrivalDone; + uint pathId; + bool repeating; + List path; + uint currentNode; + } + + public class FlightPathMovementGenerator : MovementGeneratorMedium + { + public FlightPathMovementGenerator() { } + + public void LoadPath(Player player, uint startNode = 0) + { + i_path.Clear(); + i_currentNode = (int)startNode; + _pointsForPathSwitch.Clear(); + var taxi = player.m_taxi.GetPath(); + for (int src = 0, dst = 1; dst < taxi.Count; src = dst++) + { + uint path, cost; + Global.ObjectMgr.GetTaxiPath(taxi[src], taxi[dst], out path, out cost); + if (path > CliDB.TaxiPathNodesByPath.Keys.Max()) + return; + + var nodes = CliDB.TaxiPathNodesByPath[path]; + if (!nodes.Empty()) + { + TaxiPathNodeRecord start = nodes[0]; + TaxiPathNodeRecord end = nodes[nodes.Length - 1]; + bool passedPreviousSegmentProximityCheck = false; + for (uint i = 0; i < nodes.Length; ++i) + { + if (passedPreviousSegmentProximityCheck || src == 0 || i_path.Empty() || IsNodeIncludedInShortenedPath(i_path.Last(), nodes[i])) + { + if ((src == 0 || (IsNodeIncludedInShortenedPath(start, nodes[i]) && i >= 2)) && + (dst == taxi.Count - 1 || (IsNodeIncludedInShortenedPath(end, nodes[i]) && i < nodes.Length - 1))) + { + passedPreviousSegmentProximityCheck = true; + i_path.Add(nodes[i]); + } + } + else + { + i_path.RemoveAt(i_path.Count - 1); + _pointsForPathSwitch[_pointsForPathSwitch.Count - 1].PathIndex -= 1; + } + } + } + + _pointsForPathSwitch.Add(new TaxiNodeChangeInfo((uint)(i_path.Count - 1), (int)cost)); + } + } + + public override void DoInitialize(Player owner) + { + Reset(owner); + InitEndGridInfo(); + } + + public override void DoFinalize(Player owner) + { + // remove flag to prevent send object build movement packets for flight state and crash (movement generator already not at top of stack) + owner.ClearUnitState(UnitState.InFlight); + + owner.Dismount(); + owner.RemoveFlag(UnitFields.Flags, UnitFlags.RemoveClientControl | UnitFlags.TaxiFlight); + + if (owner.m_taxi.empty()) + { + owner.getHostileRefManager().setOnlineOfflineState(true); + // update z position to ground and orientation for landing point + // this prevent cheating with landing point at lags + // when client side flight end early in comparison server side + owner.StopMoving(); + } + + owner.RemoveFlag(PlayerFields.Flags, PlayerFlags.TaxiBenchmark); + } + + public override void DoReset(Player owner) + { + owner.getHostileRefManager().setOnlineOfflineState(false); + owner.AddUnitState(UnitState.InFlight); + owner.SetFlag(UnitFields.Flags, UnitFlags.RemoveClientControl | UnitFlags.TaxiFlight); + + MoveSplineInit init = new MoveSplineInit(owner); + uint end = GetPathAtMapEnd(); + init.args.path = new Vector3[end]; + for (int i = i_currentNode; i != end; ++i) + { + Vector3 vertice = new Vector3(i_path[i].Loc.X, i_path[i].Loc.Y, i_path[i].Loc.Z); + init.args.path[i] = vertice; + } + init.SetFirstPointId(i_currentNode); + init.SetFly(); + init.SetSmooth(); + init.SetUncompressed(); + init.SetWalk(true); + init.SetVelocity(30.0f); + init.Launch(); + } + + public override bool DoUpdate(Player player, uint time_diff) + { + uint pointId = (uint)player.moveSpline.currentPathIdx(); + if (pointId > i_currentNode) + { + bool departureEvent = true; + do + { + DoEventIfAny(player, i_path[i_currentNode], departureEvent); + while (!_pointsForPathSwitch.Empty() && _pointsForPathSwitch[0].PathIndex <= i_currentNode) + { + _pointsForPathSwitch.RemoveAt(0); + player.m_taxi.NextTaxiDestination(); + if (!_pointsForPathSwitch.Empty()) + { + player.UpdateCriteria(CriteriaTypes.GoldSpentForTravelling, (uint)_pointsForPathSwitch[0].Cost); + player.ModifyMoney(-_pointsForPathSwitch[0].Cost); + } + } + + if (pointId == i_currentNode) + break; + + if (i_currentNode == _preloadTargetNode) + PreloadEndGrid(); + i_currentNode += (departureEvent ? 1 : 0); + departureEvent = !departureEvent; + } + while (true); + } + + return i_currentNode < (i_path.Count - 1); + } + + public void SetCurrentNodeAfterTeleport() + { + if (i_path.Empty() || i_currentNode >= i_path.Count) + return; + + uint map0 = i_path[i_currentNode].MapID; + for (int i = i_currentNode + 1; i < i_path.Count; ++i) + { + if (i_path[i].MapID != map0) + { + i_currentNode = i; + return; + } + } + + } + + void DoEventIfAny(Player player, TaxiPathNodeRecord node, bool departure) + { + uint eventid = departure ? node.DepartureEventID : node.ArrivalEventID; + if (eventid != 0) + { + Log.outDebug(LogFilter.Scripts, "Taxi {0} event {1} of node {2} of path {3} for player {4}", departure ? "departure" : "arrival", eventid, node.NodeIndex, node.PathID, player.GetName()); + player.GetMap().ScriptsStart(ScriptsType.Event, eventid, player, player); + } + } + + bool GetResetPos(Player player, out float x, out float y, out float z) + { + TaxiPathNodeRecord node = i_path[i_currentNode]; + x = node.Loc.X; + y = node.Loc.Y; + z = node.Loc.Z; + return true; + } + + void InitEndGridInfo() + { + int nodeCount = i_path.Count; //! Number of nodes in path. + _endMapId = i_path[nodeCount - 1].MapID; //! MapId of last node + _preloadTargetNode = (uint)nodeCount - 3; + _endGridX = i_path[nodeCount - 1].Loc.X; + _endGridY = i_path[nodeCount - 1].Loc.Y; + } + + void PreloadEndGrid() + { + // used to preload the final grid where the flightmaster is + Map endMap = Global.MapMgr.FindBaseNonInstanceMap(_endMapId); + + // Load the grid + if (endMap != null) + { + Log.outInfo(LogFilter.Server, "Preloading grid ({0}, {1}) for map {2} at node index {3}/{4}", _endGridX, _endGridY, _endMapId, _preloadTargetNode, i_path.Count - 1); + endMap.LoadGrid(_endGridX, _endGridY); + } + else + Log.outInfo(LogFilter.Server, "Unable to determine map to preload flightmaster grid"); + } + + uint GetPathAtMapEnd() + { + if (i_currentNode >= i_path.Count) + return (uint)i_path.Count; + + uint curMapId = i_path[i_currentNode].MapID; + for (int i = i_currentNode; i < i_path.Count; ++i) + { + if (i_path[i].MapID != curMapId) + return (uint)i; + } + + return (uint)i_path.Count; + } + + bool IsNodeIncludedInShortenedPath(TaxiPathNodeRecord p1, TaxiPathNodeRecord p2) + { + return p1.MapID != p2.MapID || Math.Pow(p1.Loc.X - p2.Loc.X, 2) + Math.Pow(p1.Loc.Y - p2.Loc.Y, 2) > (40.0f * 40.0f); + } + + public override MovementGeneratorType GetMovementGeneratorType() { return MovementGeneratorType.Flight; } + + public List GetPath() { return i_path; } + + bool HasArrived() { return (i_currentNode >= i_path.Count); } + + public void SkipCurrentNode() { ++i_currentNode; } + + public uint GetCurrentNode() { return (uint)i_currentNode; } + + + float _endGridX; //! X coord of last node location + float _endGridY; //! Y coord of last node location + uint _endMapId; //! map Id of last node location + uint _preloadTargetNode; //! node index where preloading starts + + int i_currentNode; + List i_path = new List(); + List _pointsForPathSwitch = new List(); //! node indexes and costs where TaxiPath changes + + class TaxiNodeChangeInfo + { + public TaxiNodeChangeInfo(uint pathIndex, int cost) + { + PathIndex = pathIndex; + Cost = cost; + } + + public uint PathIndex; + public int Cost; + } + } +} diff --git a/Game/Movement/MotionMaster.cs b/Game/Movement/MotionMaster.cs new file mode 100644 index 000000000..7afa80653 --- /dev/null +++ b/Game/Movement/MotionMaster.cs @@ -0,0 +1,764 @@ +/* + * Copyright (C) 2012-2017 CypherCore + * + * 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, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +using Framework.Constants; +using Framework.GameMath; +using Game.AI; +using Game.DataStorage; +using Game.Entities; +using System; +using System.Collections.Generic; +using System.Diagnostics.Contracts; + +namespace Game.Movement +{ + public class MotionMaster + { + public const double gravity = 19.29110527038574; + public const float SPEED_CHARGE = 42.0f; + IdleMovementGenerator staticIdleMovement = new IdleMovementGenerator(); + + public MotionMaster(Unit me) + { + _owner = me; + _expList = null; + _top = -1; + _cleanFlag = MMCleanFlag.None; + + for (byte i = 0; i < (int)MovementSlot.Max; ++i) + { + Impl[i] = null; + _needInit[i] = true; + } + } + + bool isStatic(IMovementGenerator mv) + { + return (mv == staticIdleMovement); + } + + public void Initialize() + { + while (!empty()) + { + IMovementGenerator curr = top(); + pop(); + if (curr != null) + DirectDelete(curr); + } + + InitDefault(); + } + + public void InitDefault() + { + if (_owner.IsTypeId(TypeId.Unit)) + { + IMovementGenerator movement = AISelector.SelectMovementAI(_owner.ToCreature()); + StartMovement(movement ?? staticIdleMovement, MovementSlot.Idle); + } + else + StartMovement(staticIdleMovement, MovementSlot.Idle); + } + + public virtual void UpdateMotion(uint diff) + { + if (!_owner) + return; + + if (_owner.HasUnitState(UnitState.Root | UnitState.Stunned)) + return; + + Contract.Assert(!empty()); + + _cleanFlag |= MMCleanFlag.Update; + if (!top().Update(_owner, diff)) + { + _cleanFlag &= ~MMCleanFlag.Update; + MovementExpired(); + } + else + _cleanFlag &= ~MMCleanFlag.Update; + + if (_expList != null) + { + for (int i = 0; i < _expList.Count; ++i) + { + IMovementGenerator mg = _expList[i]; + DirectDelete(mg); + } + + _expList = null; + + if (empty()) + Initialize(); + else if (needInitTop()) + InitTop(); + else if (Convert.ToBoolean(_cleanFlag & MMCleanFlag.Reset)) + top().Reset(_owner); + + _cleanFlag &= ~MMCleanFlag.Reset; + } + + _owner.UpdateUnderwaterState(_owner.GetMap(), _owner.GetPositionX(), _owner.GetPositionY(), _owner.GetPositionZ()); + } + + void DirectClean(bool reset) + { + while (size() > 1) + { + IMovementGenerator curr = top(); + pop(); + if (curr != null) + DirectDelete(curr); + } + + if (empty()) + return; + + if (needInitTop()) + InitTop(); + else if (reset) + top().Reset(_owner); + } + + void DelayedClean() + { + while (size() > 1) + { + IMovementGenerator curr = top(); + pop(); + if (curr != null) + DelayedDelete(curr); + } + } + + void DirectExpire(bool reset) + { + if (size() > 1) + { + IMovementGenerator curr = top(); + pop(); + DirectDelete(curr); + } + + while (!empty() && top() == null)//not sure this will work + --_top; + + if (empty()) + Initialize(); + else if (needInitTop()) + InitTop(); + else if (reset) + top().Reset(_owner); + } + + void DelayedExpire() + { + if (size() > 1) + { + IMovementGenerator curr = top(); + pop(); + DelayedDelete(curr); + } + + while (!empty() && top() == null) + --_top; + } + + public void MoveIdle() + { + if (empty() || !isStatic(top())) + StartMovement(staticIdleMovement, MovementSlot.Idle); + } + + public void MoveRandom(float spawndist = 0.0f) + { + if (_owner.IsTypeId(TypeId.Unit)) + StartMovement(new RandomMovementGenerator(spawndist), MovementSlot.Idle); + } + + public void MoveTargetedHome() + { + Clear(false); + + if (_owner.IsTypeId(TypeId.Unit) && _owner.ToCreature().GetCharmerOrOwnerGUID().IsEmpty()) + { + StartMovement(new HomeMovementGenerator(), MovementSlot.Active); + } + else if (_owner.IsTypeId(TypeId.Unit) && !_owner.ToCreature().GetCharmerOrOwnerGUID().IsEmpty()) + { + Unit target = _owner.ToCreature().GetCharmerOrOwner(); + if (target) + StartMovement(new FollowMovementGenerator(target, SharedConst.PetFollowDist, SharedConst.PetFollowAngle), MovementSlot.Active); + } + } + + public void MoveConfused() + { + if (_owner.IsTypeId(TypeId.Player)) + StartMovement(new ConfusedGenerator(), MovementSlot.Controlled); + else + StartMovement(new ConfusedGenerator(), MovementSlot.Controlled); + } + + public void MoveChase(Unit target, float dist = 0.0f, float angle = 0.0f) + { + if (!target || target == _owner) + return; + + if (_owner.IsTypeId(TypeId.Player)) + StartMovement(new ChaseMovementGenerator(target, dist, angle), MovementSlot.Active); + else + StartMovement(new ChaseMovementGenerator(target, dist, angle), MovementSlot.Active); + } + + public void MoveFollow(Unit target, float dist = 0.0f, float angle = 0.0f, MovementSlot slot = MovementSlot.Idle) + { + if (!target || target == _owner) + return; + + if (_owner.IsTypeId(TypeId.Player)) + StartMovement(new FollowMovementGenerator(target, dist, angle), slot); + else + StartMovement(new FollowMovementGenerator(target, dist, angle), slot); + } + + public void MovePoint(ulong id, float x, float y, float z, bool generatePath = true) + { + if (_owner.IsTypeId(TypeId.Player)) + StartMovement(new PointMovementGenerator(id, x, y, z, generatePath), MovementSlot.Active); + else + StartMovement(new PointMovementGenerator(id, x, y, z, generatePath), MovementSlot.Active); + } + + public void MovePoint(uint id, Position pos, bool generatePath = true) + { + MovePoint(id, pos.posX, pos.posY, pos.posZ, generatePath); + } + + public void MoveLand(uint id, Position pos) + { + float x, y, z; + pos.GetPosition(out x, out y, out z); + + MoveSplineInit init = new MoveSplineInit(_owner); + init.MoveTo(x, y, z); + init.SetAnimation(AnimType.ToGround); + init.Launch(); + StartMovement(new EffectMovementGenerator(id), MovementSlot.Active); + } + + void MoveTakeoff(uint id, Position pos) + { + float x, y, z; + pos.GetPosition(out x, out y, out z); + + MoveSplineInit init = new MoveSplineInit(_owner); + init.MoveTo(x, y, z); + init.SetAnimation(AnimType.ToFly); + init.Launch(); + StartMovement(new EffectMovementGenerator(id), MovementSlot.Active); + } + + public void MoveKnockbackFrom(float srcX, float srcY, float speedXY, float speedZ, SpellEffectExtraData spellEffectExtraData = null) + { + //this function may make players fall below map + if (_owner.IsTypeId(TypeId.Player)) + return; + + float x, y, z; + float moveTimeHalf = (float)(speedZ / gravity); + float dist = 2 * moveTimeHalf * speedXY; + float max_height = -MoveSpline.computeFallElevation(moveTimeHalf, false, -speedZ); + + _owner.GetNearPoint(_owner, out x, out y, out z, _owner.GetObjectSize(), dist, _owner.GetAngle(srcX, srcY) + MathFunctions.PI); + + MoveSplineInit init = new MoveSplineInit(_owner); + init.MoveTo(x, y, z); + init.SetParabolic(max_height, 0); + init.SetOrientationFixed(true); + init.SetVelocity(speedXY); + if (spellEffectExtraData != null) + init.SetSpellEffectExtraData(spellEffectExtraData); + + init.Launch(); + StartMovement(new EffectMovementGenerator(0), MovementSlot.Controlled); + } + + public void MoveJumpTo(float angle, float speedXY, float speedZ) + { + //this function may make players fall below map + if (_owner.IsTypeId(TypeId.Player)) + return; + + float x, y, z; + + float moveTimeHalf = (float)(speedZ / gravity); + float dist = 2 * moveTimeHalf * speedXY; + _owner.GetClosePoint(out x, out y, out z, _owner.GetObjectSize(), dist, angle); + MoveJump(x, y, z, 0.0f, speedXY, speedZ); + } + + public void MoveJump(Position pos, float speedXY, float speedZ, uint id = EventId.Jump, bool hasOrientation = false, JumpArrivalCastArgs arrivalCast = null, SpellEffectExtraData spellEffectExtraData = null) + { + MoveJump(pos.GetPositionX(), pos.GetPositionY(), pos.GetPositionZ(), pos.GetOrientation(), speedXY, speedZ, id, hasOrientation, arrivalCast, spellEffectExtraData); + } + + public void MoveJump(float x, float y, float z, float o, float speedXY, float speedZ, uint id = EventId.Jump, bool hasOrientation = false, + JumpArrivalCastArgs arrivalCast = null, SpellEffectExtraData spellEffectExtraData = null) + { + Log.outDebug(LogFilter.Server, "Unit ({0}) jump to point (X: {1} Y: {2} Z: {3})", _owner.GetGUID().ToString(), x, y, z); + + float moveTimeHalf = (float)(speedZ / gravity); + float max_height = -MoveSpline.computeFallElevation(moveTimeHalf, false, -speedZ); + + MoveSplineInit init = new MoveSplineInit(_owner); + init.MoveTo(x, y, z, false); + init.SetParabolic(max_height, 0); + init.SetVelocity(speedXY); + if (hasOrientation) + init.SetFacing(o); + if (spellEffectExtraData != null) + init.SetSpellEffectExtraData(spellEffectExtraData); + init.Launch(); + + uint arrivalSpellId = 0; + ObjectGuid arrivalSpellTargetGuid = ObjectGuid.Empty; + if (arrivalCast != null) + { + arrivalSpellId = arrivalCast.SpellId; + arrivalSpellTargetGuid = arrivalCast.Target; + } + + StartMovement(new EffectMovementGenerator(id, arrivalSpellId, arrivalSpellTargetGuid), MovementSlot.Controlled); + } + + void MoveCirclePath(float x, float y, float z, float radius, bool clockwise, byte stepCount) + { + float step = 2 * MathFunctions.PI / stepCount * (clockwise ? -1.0f : 1.0f); + Position pos = new Position(x, y, z, 0.0f); + float angle = pos.GetAngle(_owner.GetPositionX(), _owner.GetPositionY()); + + MoveSplineInit init = new MoveSplineInit(_owner); + init.args.path = new Vector3[stepCount]; + for (byte i = 0; i < stepCount; angle += step, ++i) + { + Vector3 point = new Vector3(); + point.X = (float)(x + radius * Math.Cos(angle)); + point.Y = (float)(y + radius * Math.Sin(angle)); + + if (_owner.IsFlying()) + point.Z = z; + else + point.Z = _owner.GetMap().GetHeight(_owner.GetPhases(), point.X, point.Y, z); + + init.args.path[i] = point; + } + + if (_owner.IsFlying()) + { + init.SetFly(); + init.SetCyclic(); + init.SetAnimation(AnimType.ToFly); + } + else + { + init.SetWalk(true); + init.SetCyclic(); + } + + init.Launch(); + } + + void MoveSmoothPath(uint pointId, Vector3[] pathPoints, int pathSize, bool walk = false, bool fly = false) + { + MoveSplineInit init = new MoveSplineInit(_owner); + if (fly) + { + init.SetFly(); + init.SetUncompressed(); + } + + init.MovebyPath(pathPoints); + init.SetSmooth(); + init.SetWalk(walk); + init.Launch(); + + // This code is not correct + // EffectMovementGenerator does not affect UNIT_STATE_ROAMING | UNIT_STATE_ROAMING_MOVE + // need to call PointMovementGenerator with various pointIds + StartMovement(new EffectMovementGenerator(pointId), MovementSlot.Active); + //Position pos(pathPoints[pathSize - 1].x, pathPoints[pathSize - 1].y, pathPoints[pathSize - 1].z); + //MovePoint(EVENT_CHARGE_PREPATH, pos, false); + } + + void MoveAlongSplineChain(uint pointId, uint dbChainId, bool walk) + { + Creature owner = _owner.ToCreature(); + if (!owner) + { + Log.outError(LogFilter.Misc, "MotionMaster.MoveAlongSplineChain: non-creature {0} tried to walk along DB spline chain. Ignoring.", _owner.GetGUID().ToString()); + return; + } + List chain = Global.ScriptMgr.GetSplineChain(owner, (byte)dbChainId); + if (chain.Empty()) + { + Log.outError(LogFilter.Misc, "MotionMaster.MoveAlongSplineChain: creature with entry {0} tried to walk along non-existing spline chain with DB id {1}.", owner.GetEntry(), dbChainId); + return; + } + MoveAlongSplineChain(pointId, chain, walk); + } + + void MoveAlongSplineChain(uint pointId, List chain, bool walk) + { + StartMovement(new SplineChainMovementGenerator(pointId, chain, walk), MovementSlot.Active); + } + + void ResumeSplineChain(SplineChainResumeInfo info) + { + if (info.Empty()) + { + Log.outError(LogFilter.Movement, "MotionMaster.ResumeSplineChain: unit with entry {0} tried to resume a spline chain from empty info.", _owner.GetEntry()); + return; + } + + StartMovement(new SplineChainMovementGenerator(info), MovementSlot.Active); + } + + public void MoveFall(uint id = 0) + { + // use larger distance for vmap height search than in most other cases + float tz = _owner.GetMap().GetHeight(_owner.GetPhases(), _owner.GetPositionX(), _owner.GetPositionY(), _owner.GetPositionZ(), true, MapConst.MaxFallDistance); + if (tz <= MapConst.InvalidHeight) + return; + + // Abort too if the ground is very near + if (Math.Abs(_owner.GetPositionZ() - tz) < 0.1f) + return; + + _owner.SetFall(true); + + // don't run spline movement for players + if (_owner.IsTypeId(TypeId.Player)) + return; + + MoveSplineInit init = new MoveSplineInit(_owner); + init.MoveTo(_owner.GetPositionX(), _owner.GetPositionY(), tz, false); + init.SetFall(); + init.Launch(); + StartMovement(new EffectMovementGenerator(id), MovementSlot.Controlled); + } + + public void MoveCharge(float x, float y, float z, float speed = SPEED_CHARGE, uint id = EventId.Charge, bool generatePath = false, Unit target = null, SpellEffectExtraData spellEffectExtraData = null) + { + if (Impl[(int)MovementSlot.Controlled] != null && Impl[(int)MovementSlot.Controlled].GetMovementGeneratorType() != MovementGeneratorType.Distract) + return; + + if (_owner.IsTypeId(TypeId.Player)) + StartMovement(new PointMovementGenerator(id, x, y, z, generatePath, speed, target, spellEffectExtraData), MovementSlot.Controlled); + else + StartMovement(new PointMovementGenerator(id, x, y, z, generatePath, speed, target, spellEffectExtraData), MovementSlot.Controlled); + } + + public void MoveCharge(PathGenerator path, float speed = SPEED_CHARGE, Unit target = null, SpellEffectExtraData spellEffectExtraData = null) + { + Vector3 dest = path.GetActualEndPosition(); + + MoveCharge(dest.X, dest.Y, dest.Z, SPEED_CHARGE, EventId.ChargePrepath); + + // Charge movement is not started when using EVENT_CHARGE_PREPATH + MoveSplineInit init = new MoveSplineInit(_owner); + init.MovebyPath(path.GetPath()); + init.SetVelocity(speed); + if (target != null) + init.SetFacing(target); + if (spellEffectExtraData != null) + init.SetSpellEffectExtraData(spellEffectExtraData); + init.Launch(); + } + + public void MoveSeekAssistance(float x, float y, float z) + { + if (_owner.IsTypeId(TypeId.Player)) + Log.outError(LogFilter.Server, "Player {0} attempt to seek assistance", _owner.GetGUID().ToString()); + else + { + _owner.AttackStop(); + _owner.CastStop(); + _owner.ToCreature().SetReactState(ReactStates.Passive); + StartMovement(new AssistanceMovementGenerator(x, y, z), MovementSlot.Active); + } + } + + public void MoveSeekAssistanceDistract(uint time) + { + if (_owner.IsTypeId(TypeId.Player)) + Log.outError(LogFilter.Server, "Player {0} attempt to call distract after assistance", _owner.GetGUID().ToString()); + else + StartMovement(new AssistanceDistractMovementGenerator(time), MovementSlot.Active); + } + + public void MoveFleeing(Unit enemy, uint time) + { + if (!enemy) + return; + + if (_owner.IsTypeId(TypeId.Player)) + StartMovement(new FleeingGenerator(enemy.GetGUID()), MovementSlot.Controlled); + else + { + if (time != 0) + StartMovement(new TimedFleeingGenerator(enemy.GetGUID(), time), MovementSlot.Controlled); + else + StartMovement(new FleeingGenerator(enemy.GetGUID()), MovementSlot.Controlled); + } + } + + public void MoveTaxiFlight(uint path, uint pathnode) + { + if (_owner.IsTypeId(TypeId.Player)) + { + if (path < CliDB.TaxiPathNodesByPath.Count) + { + Log.outDebug(LogFilter.Server, "{0} taxi to (Path {1} node {2})", _owner.GetName(), path, pathnode); + FlightPathMovementGenerator mgen = new FlightPathMovementGenerator(); + mgen.LoadPath(_owner.ToPlayer()); + StartMovement(mgen, MovementSlot.Controlled); + } + else + { + Log.outDebug(LogFilter.Server, "{0} attempt taxi to (not existed Path {1} node {2})", + _owner.GetName(), path, pathnode); + } + } + else + { + Log.outDebug(LogFilter.Server, "Creature (Entry: {0} GUID: {1}) attempt taxi to (Path {2} node {3})", + _owner.GetEntry(), _owner.GetGUID().ToString(), path, pathnode); + } + } + + public void MoveDistract(uint timer) + { + if (Impl[(int)MovementSlot.Controlled] != null) + return; + + StartMovement(new DistractMovementGenerator(timer), MovementSlot.Controlled); + } + + void StartMovement(IMovementGenerator m, MovementSlot slot) + { + int _slot = (int)slot; + IMovementGenerator curr = Impl[_slot]; + if (curr != null) + { + Impl[_slot] = null; // in case a new one is generated in this slot during directdelete + if (_top == _slot && Convert.ToBoolean(_cleanFlag & MMCleanFlag.Update)) + DelayedDelete(curr); + else + DirectDelete(curr); + } + else if (_top < _slot) + { + _top = _slot; + } + + Impl[_slot] = m; + if (_top > _slot) + _needInit[_slot] = true; + else + { + _needInit[_slot] = false; + m.Initialize(_owner); + } + } + + public void MovePath(uint path_id, bool repeatable) + { + if (path_id == 0) + return; + + StartMovement(new WaypointMovementGenerator(path_id, repeatable), MovementSlot.Idle); + } + + void MoveRotate(uint time, RotateDirection direction) + { + if (time == 0) + return; + + StartMovement(new RotateMovementGenerator(time, direction), MovementSlot.Active); + } + + public void propagateSpeedChange() + { + for (int i = 0; i <= _top; ++i) + { + if (Impl[i] != null) + Impl[i].unitSpeedChanged(); + } + } + + public MovementGeneratorType GetCurrentMovementGeneratorType() + { + if (empty()) + return MovementGeneratorType.Idle; + + return top().GetMovementGeneratorType(); + } + + public MovementGeneratorType GetMotionSlotType(MovementSlot slot) + { + if (Impl[(int)slot] == null) + return MovementGeneratorType.Null; + else + return Impl[(int)slot].GetMovementGeneratorType(); + } + + void InitTop() + { + top().Initialize(_owner); + _needInit[_top] = false; + } + + void DirectDelete(IMovementGenerator curr) + { + if (isStatic(curr)) + return; + curr.Finalize(_owner); + } + + void DelayedDelete(IMovementGenerator curr) + { + if (isStatic(curr)) + return; + if (_expList == null) + _expList = new List(); + _expList.Add(curr); + } + + public bool GetDestination(out float x, out float y, out float z) + { + x = 0f; + y = 0f; + z = 0f; + if (_owner.moveSpline.Finalized()) + return false; + + Vector3 dest = _owner.moveSpline.FinalDestination(); + x = dest.X; + y = dest.Y; + z = dest.Z; + return true; + } + + void pop() + { + if (empty()) + return; + + Impl[_top] = null; + while (!empty() && top() == null) + --_top; + } + + void push(IMovementGenerator _Val) + { + ++_top; + Impl[_top] = _Val; + } + + bool needInitTop() + { + if (empty()) + return false; + + return _needInit[_top]; + } + + public bool empty() { return (_top < 0); } + + int size() { return _top + 1; } + + public IMovementGenerator top() + { + Contract.Assert(!empty()); + return Impl[_top]; + } + + public IMovementGenerator GetMotionSlot(int slot) + { + Contract.Assert(slot >= 0); + return Impl[slot]; + } + + public void Clear(bool reset = true) + { + if (Convert.ToBoolean(_cleanFlag & MMCleanFlag.Update)) + { + if (reset) + _cleanFlag |= MMCleanFlag.Reset; + else + _cleanFlag &= ~MMCleanFlag.Reset; + DelayedClean(); + } + else + DirectClean(reset); + } + + public void MovementExpired(bool reset = true) + { + if (Convert.ToBoolean(_cleanFlag & MMCleanFlag.Update)) + { + if (reset) + _cleanFlag |= MMCleanFlag.Reset; + else + _cleanFlag &= ~MMCleanFlag.Reset; + DelayedExpire(); + } + else + DirectExpire(reset); + } + + public static uint SplineId + { + get { return splineId++; } + } + + static uint splineId = 0; + + public Unit _owner { get; private set; } + protected IMovementGenerator[] Impl = new IMovementGenerator[(int)MovementSlot.Max]; + MMCleanFlag _cleanFlag; + bool[] _needInit = new bool[(int)MovementSlot.Max]; + int _top; + List _expList = new List(); + } + + public class JumpArrivalCastArgs + { + public uint SpellId; + public ObjectGuid Target; + } + + enum MMCleanFlag + { + None = 0, + Update = 1, // Clear or Expire called from update + Reset = 2 // Flag if need top().Reset() + } +} diff --git a/Game/Movement/MoveSpline.cs b/Game/Movement/MoveSpline.cs new file mode 100644 index 000000000..9339b787f --- /dev/null +++ b/Game/Movement/MoveSpline.cs @@ -0,0 +1,421 @@ +/* + * Copyright (C) 2012-2017 CypherCore + * + * 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, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +using Framework.Constants; +using Framework.Dynamic; +using Framework.GameMath; +using System; +using System.Collections.Generic; + +namespace Game.Movement +{ + public class MoveSpline + { + public MoveSpline() + { + m_Id = 0; + time_passed = 0; + vertical_acceleration = 0.0f; + initialOrientation = 0.0f; + effect_start_time = 0; + point_Idx = 0; + point_Idx_offset = 0; + onTransport = false; + splineIsFacingOnly = false; + splineflags.Flags = SplineFlag.Done; + } + + public void Initialize(MoveSplineInitArgs args) + { + splineflags = args.flags; + facing = args.facing; + m_Id = args.splineId; + point_Idx_offset = args.path_Idx_offset; + initialOrientation = args.initialOrientation; + + time_passed = 0; + vertical_acceleration = 0.0f; + effect_start_time = 0; + spell_effect_extra = args.spellEffectExtra; + splineIsFacingOnly = args.path.Length == 2 && args.facing.type != MonsterMoveType.Normal && ((args.path[1] - args.path[0]).GetLength() < 0.1f); + + // Check if its a stop spline + if (args.flags.hasFlag(SplineFlag.Done)) + { + spline.clear(); + return; + } + + init_spline(args); + + // init parabolic / animation + // spline initialized, duration known and i able to compute parabolic acceleration + if (args.flags.hasFlag(SplineFlag.Parabolic | SplineFlag.Animation | SplineFlag.FadeObject)) + { + effect_start_time = (int)(Duration() * args.time_perc); + if (args.flags.hasFlag(SplineFlag.Parabolic) && effect_start_time < Duration()) + { + float f_duration = (float)TimeSpan.FromMilliseconds(Duration() - effect_start_time).TotalSeconds; + vertical_acceleration = args.parabolic_amplitude * 8.0f / (f_duration * f_duration); + } + } + } + + void init_spline(MoveSplineInitArgs args) + { + Spline.EvaluationMode[] modes = new Spline.EvaluationMode[2] { Spline.EvaluationMode.Linear, Spline.EvaluationMode.Catmullrom }; + if (args.flags.hasFlag(SplineFlag.Cyclic)) + { + spline.init_cyclic_spline(args.path, args.path.Length, modes[Convert.ToInt32(args.flags.isSmooth())], 0); + } + else + { + spline.Init_Spline(args.path, args.path.Length, modes[Convert.ToInt32(args.flags.isSmooth())]); + } + + // init spline timestamps + if (splineflags.hasFlag(SplineFlag.Falling)) + { + FallInitializer init = new FallInitializer(spline.getPoint(spline.first()).Z); + spline.initLengths(init); + } + else + { + CommonInitializer init = new CommonInitializer(args.velocity); + spline.initLengths(init); + } + + // TODO: what to do in such cases? problem is in input data (all points are at same coords) + if (spline.length() < 1) + { + Log.outError(LogFilter.Unit, "MoveSpline.init_spline: zero length spline, wrong input data?"); + spline.set_length(spline.last(), spline.isCyclic() ? 1000 : 1); + } + point_Idx = spline.first(); + } + + public int currentPathIdx() + { + int point = point_Idx_offset + point_Idx - spline.first() + (Finalized() ? 1 : 0); + if (isCyclic()) + point = point % (spline.last() - spline.first()); + return point; + } + + public Vector3[] getPath() { return spline.getPoints(); } + public int timePassed() { return time_passed; } + + public int Duration() { return spline.length(); } + public int _currentSplineIdx() { return point_Idx; } + public uint GetId() { return m_Id; } + public bool Finalized() { return splineflags.hasFlag(SplineFlag.Done); } + void _Finalize() + { + splineflags.SetUnsetFlag(SplineFlag.Done); + point_Idx = spline.last() - 1; + time_passed = Duration(); + } + public Vector4 computePosition(int time_point, int point_index) + { + float u = 1.0f; + int seg_time = spline.length(point_index, point_index + 1); + if (seg_time > 0) + u = (time_point - spline.length(point_index)) / (float)seg_time; + + Vector3 c; + float orientation = initialOrientation; + spline.Evaluate_Percent(point_index, u, out c); + + if (splineflags.hasFlag(SplineFlag.Parabolic)) + computeParabolicElevation(time_point, ref c.Z); + else if (splineflags.hasFlag(SplineFlag.Falling)) + computeFallElevation(time_point, ref c.Z); + + if (splineflags.hasFlag(SplineFlag.Done) && facing.type != MonsterMoveType.Normal) + { + if (facing.type == MonsterMoveType.FacingAngle) + orientation = facing.angle; + else if (facing.type == MonsterMoveType.FacingSpot) + orientation = (float)Math.Atan2(facing.f.Y - c.Y, facing.f.X - c.X); + //nothing to do for MoveSplineFlag.Final_Target flag + } + else + { + if (!splineflags.hasFlag(SplineFlag.OrientationFixed | SplineFlag.Falling | SplineFlag.Unknown0)) + { + Vector3 hermite; + spline.Evaluate_Derivative(point_Idx, u, out hermite); + orientation = (float)Math.Atan2(hermite.Y, hermite.X); + } + + if (splineflags.hasFlag(SplineFlag.Backward)) + orientation = orientation - (float)Math.PI; + } + + return new Vector4(c.X, c.Y, c.Z, orientation); + } + public Vector4 ComputePosition() + { + return computePosition(time_passed, point_Idx); + } + public Vector4 ComputePosition(int time_offset) + { + int time_point = time_passed + time_offset; + if (time_point >= Duration()) + return computePosition(Duration(), spline.last() - 1); + if (time_point <= 0) + return computePosition(0, spline.first()); + + // find point_index where spline.length(point_index) < time_point < spline.length(point_index + 1) + int point_index = point_Idx; + while (time_point >= spline.length(point_index + 1)) + ++point_index; + + while (time_point < spline.length(point_index)) + --point_index; + + return computePosition(time_point, point_index); + } + public void computeParabolicElevation(int time_point, ref float el) + { + if (time_point > effect_start_time) + { + float t_passedf = MSToSec((uint)(time_point - effect_start_time)); + float t_durationf = MSToSec((uint)(Duration() - effect_start_time)); //client use not modified duration here + if (spell_effect_extra.HasValue && spell_effect_extra.Value.ParabolicCurveId != 0) + t_passedf *= Global.DB2Mgr.GetCurveValueAt(spell_effect_extra.Value.ParabolicCurveId, time_point / Duration()); + + el += (t_durationf - t_passedf) * 0.5f * vertical_acceleration * t_passedf; + } + } + public void computeFallElevation(int time_point, ref float el) + { + float z_now = spline.getPoint(spline.first()).Z - computeFallElevation(MSToSec((uint)time_point), false); + float final_z = FinalDestination().Z; + el = Math.Max(z_now, final_z); + } + public static float computeFallElevation(float t_passed, bool isSafeFall, float start_velocity = 0.0f) + { + float termVel; + float result; + + if (isSafeFall) + termVel = SharedConst.terminalSafefallVelocity; + else + termVel = SharedConst.terminalVelocity; + + if (start_velocity > termVel) + start_velocity = termVel; + + float terminal_time = (float)((isSafeFall ? SharedConst.terminal_safeFall_fallTime : SharedConst.terminal_fallTime) - start_velocity / SharedConst.gravity); // the time that needed to reach terminalVelocity + + if (t_passed > terminal_time) + { + result = termVel * (t_passed - terminal_time) + + start_velocity * terminal_time + + (float)SharedConst.gravity * terminal_time * terminal_time * 0.5f; + } + else + result = t_passed * (float)(start_velocity + t_passed * SharedConst.gravity * 0.5f); + + return result; + } + + float MSToSec(uint ms) + { + return ms / 1000.0f; + } + + public void Interrupt() { splineflags.SetUnsetFlag(SplineFlag.Done); } + public void updateState(int difftime) + { + do + { + _updateState(ref difftime); + } while (difftime > 0); + } + UpdateResult _updateState(ref int ms_time_diff) + { + if (Finalized()) + { + ms_time_diff = 0; + return UpdateResult.Arrived; + } + + UpdateResult result = UpdateResult.None; + int minimal_diff = Math.Min(ms_time_diff, segment_time_elapsed()); + time_passed += minimal_diff; + ms_time_diff -= minimal_diff; + + if (time_passed >= next_timestamp()) + { + ++point_Idx; + if (point_Idx < spline.last()) + { + result = UpdateResult.NextSegment; + } + else + { + if (spline.isCyclic()) + { + point_Idx = spline.first(); + time_passed = time_passed % Duration(); + result = UpdateResult.NextCycle; + } + else + { + _Finalize(); + ms_time_diff = 0; + result = UpdateResult.Arrived; + } + } + } + + return result; + } + int next_timestamp() { return spline.length(point_Idx + 1); } + int segment_time_elapsed() { return next_timestamp() - time_passed; } + public bool isCyclic() { return splineflags.hasFlag(SplineFlag.Cyclic); } + public bool isFalling() { return splineflags.hasFlag(SplineFlag.Falling); } + public bool Initialized() { return !spline.empty(); } + public Vector3 FinalDestination() { return Initialized() ? spline.getPoint(spline.last()) : new Vector3(); } + + #region Fields + public MoveSplineInitArgs InitArgs; + public Spline spline = new Spline(); + public FacingInfo facing; + public MoveSplineFlag splineflags = new MoveSplineFlag(); + public bool onTransport; + public bool splineIsFacingOnly; + public uint m_Id; + public int time_passed; + public float vertical_acceleration; + public float initialOrientation; + public int effect_start_time; + public int point_Idx; + public int point_Idx_offset; + public Optional spell_effect_extra; + #endregion + + public class CommonInitializer : Initializer + { + public CommonInitializer(float _velocity) + { + velocityInv = 1000f / _velocity; + time = 1; + } + public float velocityInv; + public int time; + public int SetGetTime(Spline s, int i) + { + time += (int)(s.SegLength(i) * velocityInv); + return time; + } + } + public class FallInitializer : Initializer + { + public FallInitializer(float startelevation) + { + startElevation = startelevation; + } + float startElevation; + public int SetGetTime(Spline s, int i) + { + return (int)(computeFallTime(startElevation - s.getPoint(i + 1).Z, false) * 1000.0f); + } + + float computeFallTime(float path_length, bool isSafeFall) + { + if (path_length < 0.0f) + return 0.0f; + + float time; + if (isSafeFall) + { + if (path_length >= SharedConst.terminal_safeFall_length) + time = (path_length - SharedConst.terminal_safeFall_length) / SharedConst.terminalSafefallVelocity + SharedConst.terminal_safeFall_fallTime; + else + time = (float)Math.Sqrt(2.0f * path_length / SharedConst.gravity); + } + else + { + if (path_length >= SharedConst.terminal_length) + time = (path_length - SharedConst.terminal_length) / SharedConst.terminalVelocity + SharedConst.terminal_fallTime; + else + time = (float)Math.Sqrt(2.0f * path_length / SharedConst.gravity); + } + + return time; + } + } + public enum UpdateResult + { + None = 0x01, + Arrived = 0x02, + NextCycle = 0x04, + NextSegment = 0x08 + } + } + public interface Initializer + { + int SetGetTime(Spline s, int i); + } + + public class SplineChainLink + { + public SplineChainLink(Vector3[] points, uint expectedDuration, uint msToNext) + { + Points.AddRange(points); + ExpectedDuration = expectedDuration; + TimeToNext = msToNext; + } + + public SplineChainLink(uint expectedDuration, uint msToNext) + { + ExpectedDuration = expectedDuration; + TimeToNext = msToNext; + } + + public List Points = new List(); + public uint ExpectedDuration; + public uint TimeToNext; + } + + public class SplineChainResumeInfo + { + public SplineChainResumeInfo() { } + public SplineChainResumeInfo(uint id, List chain, bool walk, byte splineIndex, byte wpIndex, uint msToNext) + { + PointID = id; + Chain = chain; + IsWalkMode = walk; + SplineIndex = splineIndex; + PointIndex = wpIndex; + TimeToNext = msToNext; + } + + public bool Empty() { return Chain.Empty(); } + public void Clear() { Chain.Clear(); } + + public uint PointID; + public List Chain = new List(); + public bool IsWalkMode; + public byte SplineIndex; + public byte PointIndex; + public uint TimeToNext; + } +} diff --git a/Game/Movement/MoveSplineFlags.cs b/Game/Movement/MoveSplineFlags.cs new file mode 100644 index 000000000..749886dde --- /dev/null +++ b/Game/Movement/MoveSplineFlags.cs @@ -0,0 +1,55 @@ +/* + * Copyright (C) 2012-2017 CypherCore + * + * 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, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +using Framework.Constants; +using System; + +namespace Game.Movement +{ + public class MoveSplineFlag + { + public MoveSplineFlag() { } + public MoveSplineFlag(SplineFlag f) { Flags = f; } + public MoveSplineFlag(MoveSplineFlag f) { Flags = f.Flags; } + + public bool isSmooth() { return Flags.HasAnyFlag(SplineFlag.Catmullrom); } + public bool isLinear() { return !isSmooth(); } + + public byte getAnimationId() { return animId; } + public bool hasAllFlags(SplineFlag f) { return (Flags & f) == f; } + public bool hasFlag(SplineFlag f) { return (Flags & f) != 0; } + + public void SetUnsetFlag(SplineFlag f, bool Set = true) + { + if (Set) + Flags |= f; + else + Flags &= ~f; + } + + public void EnableAnimation(uint anim) { Flags = (Flags & ~(SplineFlag.MaskAnimations | SplineFlag.Falling | SplineFlag.Parabolic | SplineFlag.FallingSlow | SplineFlag.FadeObject)) | SplineFlag.Animation | ((SplineFlag)anim & SplineFlag.MaskAnimations); } + public void EnableParabolic() { Flags = (Flags & ~(SplineFlag.MaskAnimations | SplineFlag.Falling | SplineFlag.Animation | SplineFlag.FallingSlow | SplineFlag.FadeObject)) | SplineFlag.Parabolic; } + public void EnableFlying() { Flags = (Flags & ~SplineFlag.Falling) | SplineFlag.Flying; } + public void EnableFalling() { Flags = (Flags & ~(SplineFlag.MaskAnimations | SplineFlag.Parabolic | SplineFlag.Animation | SplineFlag.Flying)) | SplineFlag.Falling; } + public void EnableCatmullRom() { Flags = (Flags & ~SplineFlag.SmoothGroundPath) | SplineFlag.Catmullrom; } + public void EnableTransportEnter() { Flags = (Flags & ~SplineFlag.TransportExit) | SplineFlag.TransportEnter; } + public void EnableTransportExit() { Flags = (Flags & ~SplineFlag.TransportEnter) | SplineFlag.TransportExit; } + + public SplineFlag Flags; + public byte animId; + } +} diff --git a/Game/Movement/MoveSplineInit.cs b/Game/Movement/MoveSplineInit.cs new file mode 100644 index 000000000..8f40afeb1 --- /dev/null +++ b/Game/Movement/MoveSplineInit.cs @@ -0,0 +1,341 @@ +/* + * Copyright (C) 2012-2017 CypherCore + * + * 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, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +using Framework.Constants; +using Framework.GameMath; +using Game.Entities; +using Game.Network.Packets; +using System; + +namespace Game.Movement +{ + public class MoveSplineInit + { + public MoveSplineInit(Unit m) + { + unit = m; + args.splineId = MotionMaster.SplineId; + + // Elevators also use MOVEMENTFLAG_ONTRANSPORT but we do not keep track of their position changes + args.TransformForTransport = !unit.GetTransGUID().IsEmpty(); + // mix existing state into new + args.flags.SetUnsetFlag(SplineFlag.CanSwim, unit.CanSwim()); + args.walk = unit.HasUnitMovementFlag(MovementFlag.Walking); + args.flags.SetUnsetFlag(SplineFlag.Flying, unit.HasUnitMovementFlag(MovementFlag.CanFly | MovementFlag.DisableGravity)); + args.flags.SetUnsetFlag(SplineFlag.SmoothGroundPath, true); // enabled by default, CatmullRom mode or client config "pathSmoothing" will disable this + args.flags.SetUnsetFlag(SplineFlag.Steering, unit.HasFlag64(UnitFields.NpcFlags, NPCFlags.Steering)); + } + + UnitMoveType SelectSpeedType(MovementFlag moveFlags) + { + if (Convert.ToBoolean(moveFlags & (MovementFlag.Flying | MovementFlag.CanFly | MovementFlag.DisableGravity))) + { + if (Convert.ToBoolean(moveFlags & MovementFlag.Backward)) + return UnitMoveType.FlightBack; + else + return UnitMoveType.Flight; + } + else if (Convert.ToBoolean(moveFlags & MovementFlag.Swimming)) + { + if (Convert.ToBoolean(moveFlags & MovementFlag.Backward)) + return UnitMoveType.SwimBack; + else + return UnitMoveType.Swim; + } + else if (Convert.ToBoolean(moveFlags & MovementFlag.Walking)) + { + return UnitMoveType.Walk; + } + else if (Convert.ToBoolean(moveFlags & MovementFlag.Backward)) + return UnitMoveType.RunBack; + + // Flying creatures use MOVEMENTFLAG_CAN_FLY or MOVEMENTFLAG_DISABLE_GRAVITY + // Run speed is their default flight speed. + return UnitMoveType.Run; + } + + public int Launch() + { + MoveSpline move_spline = unit.moveSpline; + + bool transport = !unit.GetTransGUID().IsEmpty(); + Vector4 real_position = new Vector4(); + // there is a big chance that current position is unknown if current state is not finalized, need compute it + // this also allows calculate spline position and update map position in much greater intervals + // Don't compute for transport movement if the unit is in a motion between two transports + if (!move_spline.Finalized() && move_spline.onTransport == transport) + real_position = move_spline.ComputePosition(); + else + { + Position pos; + if (!transport) + pos = unit; + else + pos = unit.m_movementInfo.transport.pos; + + real_position.X = pos.GetPositionX(); + real_position.Y = pos.GetPositionY(); + real_position.Z = pos.GetPositionZ(); + real_position.W = unit.GetOrientation(); + } + + // should i do the things that user should do? - no. + if (args.path.Length == 0) + return 0; + + // correct first vertex + args.path[0] = new Vector3(real_position.X, real_position.Y, real_position.Z); + args.initialOrientation = real_position.W; + move_spline.onTransport = !unit.GetTransGUID().IsEmpty(); + + MovementFlag moveFlags = unit.m_movementInfo.GetMovementFlags(); + if (!args.flags.hasFlag(SplineFlag.Backward)) + moveFlags = (moveFlags & ~MovementFlag.Backward) | MovementFlag.Forward; + else + moveFlags = (moveFlags & ~MovementFlag.Forward) | MovementFlag.Backward; + + if (Convert.ToBoolean(moveFlags & MovementFlag.Root)) + moveFlags &= ~MovementFlag.MaskMoving; + + if (!args.HasVelocity) + { + // If spline is initialized with SetWalk method it only means we need to select + // walk move speed for it but not add walk flag to unit + var moveFlagsForSpeed = moveFlags; + if (args.walk) + moveFlagsForSpeed |= MovementFlag.Walking; + else + moveFlagsForSpeed &= ~MovementFlag.Walking; + + args.velocity = unit.GetSpeed(SelectSpeedType(moveFlagsForSpeed)); + } + + if (!args.Validate(unit)) + return 0; + + unit.m_movementInfo.SetMovementFlags(moveFlags); + move_spline.Initialize(args); + + MonsterMove packet = new MonsterMove(); + packet.MoverGUID = unit.GetGUID(); + packet.Pos = new Vector3(real_position.X, real_position.Y, real_position.Z); + packet.InitializeSplineData(move_spline); + if (transport) + { + packet.SplineData.Move.TransportGUID = unit.GetTransGUID(); + packet.SplineData.Move.VehicleSeat = unit.GetTransSeat(); + } + unit.SendMessageToSet(packet, true); + + return move_spline.Duration(); + } + + public void Stop() + { + MoveSpline move_spline = unit.moveSpline; + + // No need to stop if we are not moving + if (move_spline.Finalized()) + return; + + bool transport = !unit.GetTransGUID().IsEmpty(); + Vector4 loc = new Vector4(); + if (move_spline.onTransport == transport) + loc = move_spline.ComputePosition(); + else + { + Position pos; + if (!transport) + pos = unit; + else + pos = unit.m_movementInfo.transport.pos; + + loc.X = pos.GetPositionX(); + loc.Y = pos.GetPositionY(); + loc.Z = pos.GetPositionZ(); + loc.W = unit.GetOrientation(); + } + + args.flags.Flags = SplineFlag.Done; + unit.m_movementInfo.RemoveMovementFlag(MovementFlag.Forward); + move_spline.onTransport = transport; + move_spline.Initialize(args); + + MonsterMove packet = new MonsterMove(); + packet.MoverGUID = unit.GetGUID(); + packet.Pos = new Vector3(loc.X, loc.Y, loc.Z); + packet.SplineData.StopDistanceTolerance = 2; + packet.SplineData.ID = move_spline.GetId(); + + if (transport) + { + packet.SplineData.Move.TransportGUID = unit.GetTransGUID(); + packet.SplineData.Move.VehicleSeat = unit.GetTransSeat(); + } + + unit.SendMessageToSet(packet, true); + } + + public void SetFacing(Unit target) + { + args.facing.angle = unit.GetAngle(target); + args.facing.target = target.GetGUID(); + args.facing.type = MonsterMoveType.FacingTarget; + } + + public void SetFacing(float angle) + { + if (args.TransformForTransport) + { + Unit vehicle = unit.GetVehicleBase(); + Transport transport = unit.GetTransport(); + if (vehicle != null) + angle -= vehicle.Orientation; + else if (transport != null) + angle -= transport.Orientation; + } + + args.facing.angle = MathFunctions.wrap(angle, 0.0f, MathFunctions.TwoPi); + args.facing.type = MonsterMoveType.FacingAngle; + } + + public void MoveTo(Vector3 dest, bool generatePath = true, bool forceDestination = false) + { + if (generatePath) + { + PathGenerator path = new PathGenerator(unit); + bool result = path.CalculatePath(dest.X, dest.Y, dest.Z, forceDestination); + if (result && !Convert.ToBoolean(path.GetPathType() & PathType.NoPath)) + { + MovebyPath(path.GetPath()); + return; + } + } + + args.path_Idx_offset = 0; + args.path = new Vector3[2]; + TransportPathTransform transform = new TransportPathTransform(unit, args.TransformForTransport); + args.path[1] = transform.Calc(dest); + } + + public void SetFall() + { + args.flags.EnableFalling(); + args.flags.SetUnsetFlag(SplineFlag.FallingSlow, unit.HasUnitMovementFlag(MovementFlag.FallingSlow)); + } + + public void SetFirstPointId(int pointId) { args.path_Idx_offset = pointId; } + + public void SetFly() { args.flags.EnableFlying(); } + + public void SetWalk(bool enable) { args.walk = enable; } + + public void SetSmooth() { args.flags.EnableCatmullRom(); } + + public void SetUncompressed() { args.flags.SetUnsetFlag(SplineFlag.UncompressedPath); } + + public void SetCyclic() { args.flags.SetUnsetFlag(SplineFlag.Cyclic); } + + public void SetVelocity(float vel) { args.velocity = vel; args.HasVelocity = true; } + + void SetBackward() { args.flags.SetUnsetFlag(SplineFlag.Backward); } + + public void SetTransportEnter() { args.flags.EnableTransportEnter(); } + + public void SetTransportExit() { args.flags.EnableTransportExit(); } + + public void SetOrientationFixed(bool enable) { args.flags.SetUnsetFlag(SplineFlag.OrientationFixed, enable); } + + public void MovebyPath(Vector3[] controls, int path_offset = 0) + { + args.path_Idx_offset = path_offset; + args.path = new Vector3[controls.Length]; + TransportPathTransform transform = new TransportPathTransform(unit, args.TransformForTransport); + for (var i = 0; i < controls.Length; i++) + args.path[i] = transform.Calc(controls[i]); + + } + + public void MoveTo(float x, float y, float z, bool generatePath = true, bool forceDest = false) + { + MoveTo(new Vector3(x, y, z), generatePath, forceDest); + } + + public void SetParabolic(float amplitude, float time_shift) + { + args.time_perc = time_shift; + args.parabolic_amplitude = amplitude; + args.flags.EnableParabolic(); + } + + public void SetAnimation(AnimType anim) + { + args.time_perc = 0.0f; + args.flags.EnableAnimation((byte)anim); + } + + public void SetFacing(Vector3 spot) + { + TransportPathTransform transform = new TransportPathTransform(unit, args.TransformForTransport); + Vector3 finalSpot = transform.Calc(spot); + args.facing.f = new Vector3(finalSpot.X, finalSpot.Y, finalSpot.Z); + args.facing.type = MonsterMoveType.FacingSpot; + } + + public void DisableTransportPathTransformations() { args.TransformForTransport = false; } + + public void SetSpellEffectExtraData(SpellEffectExtraData spellEffectExtraData) + { + args.spellEffectExtra.Set(spellEffectExtraData); + } + + public Vector3[] Path() { return args.path; } + + public MoveSplineInitArgs args = new MoveSplineInitArgs(); + Unit unit; + } + + // Transforms coordinates from global to transport offsets + public class TransportPathTransform + { + public TransportPathTransform(Unit owner, bool transformForTransport) + { + _owner = owner; + _transformForTransport = transformForTransport; + } + public Vector3 Calc(Vector3 input) + { + float x = input.X; + float y = input.Y; + float z = input.Z; + if (_transformForTransport) + { + ITransport transport = _owner.GetDirectTransport(); + if (transport != null) + { + float unused = 0.0f; // need reference + + transport.CalculatePassengerOffset(ref x, ref y, ref z, ref unused); + } + } + return new Vector3(x, y, z); + } + + Unit _owner; + bool _transformForTransport; + } +} diff --git a/Game/Movement/MoveSplineInitArgs.cs b/Game/Movement/MoveSplineInitArgs.cs new file mode 100644 index 000000000..fe79fe975 --- /dev/null +++ b/Game/Movement/MoveSplineInitArgs.cs @@ -0,0 +1,95 @@ +/* + * Copyright (C) 2012-2017 CypherCore + * + * 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, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +using Framework.Dynamic; +using Framework.GameMath; +using Game.Entities; +using System; + +namespace Game.Movement +{ + public class MoveSplineInitArgs + { + public MoveSplineInitArgs(int path_capacity = 16) + { + path_Idx_offset = 0; + velocity = 0.0f; + parabolic_amplitude = 0.0f; + time_perc = 0.0f; + splineId = 0; + initialOrientation = 0.0f; + HasVelocity = false; + TransformForTransport = true; + path = new Vector3[path_capacity]; + } + + public Vector3[] path; + public FacingInfo facing = new FacingInfo(); + public MoveSplineFlag flags = new MoveSplineFlag(); + public int path_Idx_offset; + public float velocity; + public float parabolic_amplitude; + public float time_perc; + public uint splineId; + public float initialOrientation; + public Optional spellEffectExtra; + public bool walk; + public bool HasVelocity; + public bool TransformForTransport; + + // Returns true to show that the arguments were configured correctly and MoveSpline initialization will succeed. + public bool Validate(Unit unit) + { + Func CHECK = new Func(exp => + { + if (!(exp)) + { + Log.outError(LogFilter.Misc, "MoveSplineInitArgs::Validate: expression '{0}' failed for {1} Entry: {2}", exp.ToString(), unit.GetGUID().ToString(), unit.GetEntry()); + return false; + } + return true; + }); + + if (!CHECK(path.Length > 1)) + return false; + if (!CHECK(velocity > 0.01f)) + return false; + if (!CHECK(time_perc >= 0.0f && time_perc <= 1.0f)) + return false; + if (!CHECK(_checkPathLengths())) + return false; + return true; + } + + bool _checkPathLengths() + { + if (path.Length > 2 || facing.type == Framework.Constants.MonsterMoveType.Normal) + for (uint i = 0; i < path.Length - 1; ++i) + if ((path[i + 1] - path[i]).GetLength() < 0.1f) + return false; + return true; + } + } + + public class SpellEffectExtraData + { + public ObjectGuid Target; + public uint SpellVisualId; + public uint ProgressCurveId; + public uint ParabolicCurveId; + } +} diff --git a/Game/Movement/Spline.cs b/Game/Movement/Spline.cs new file mode 100644 index 000000000..6c45fc11d --- /dev/null +++ b/Game/Movement/Spline.cs @@ -0,0 +1,378 @@ +/* + * Copyright (C) 2012-2017 CypherCore + * + * 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, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +using Framework.Constants; +using Framework.GameMath; +using Game.Entities; +using Game.Maps; +using System; +using System.Linq; + +namespace Game.Movement +{ + public class Spline + { + public int getPointCount() { return points.Length; } + public Vector3 getPoint(int i) { return points[i]; } + public Vector3[] getPoints() { return points; } + + public void clear() + { + Array.Clear(points, 0, points.Length); + } + public int first() { return index_lo; } + public int last() { return index_hi; } + + public bool isCyclic() { return cyclic;} + + #region Evaluate + public void Evaluate_Percent(int Idx, float u, out Vector3 c) + { + switch (m_mode) + { + case EvaluationMode.Linear: + EvaluateLinear(Idx, u, out c); + break; + case EvaluationMode.Catmullrom: + EvaluateCatmullRom(Idx, u, out c); + break; + case EvaluationMode.Bezier3_Unused: + EvaluateBezier3(Idx, u, out c); + break; + default: + c = new Vector3(); + break; + } + } + void EvaluateLinear(int index, float u, out Vector3 result) + { + result = points[index] + (points[index + 1] - points[index]) * u; + } + void EvaluateCatmullRom(int index, float t, out Vector3 result) + { + C_Evaluate(points.Skip(index - 1).ToArray(), t, s_catmullRomCoeffs, out result); + } + void EvaluateBezier3(int index, float t, out Vector3 result) + { + index *= (int)3u; + C_Evaluate(points.Skip(index).ToArray(), t, s_Bezier3Coeffs, out result); + } + #endregion + + #region Init + public void init_spline_custom(SplineRawInitializer initializer) + { + initializer.Initialize(ref m_mode, ref cyclic, ref points, ref index_lo, ref index_hi); + } + public void init_cyclic_spline(Vector3[] controls, int count, EvaluationMode m, int cyclic_point) + { + m_mode = m; + cyclic = true; + + Init_Spline(controls, count, m); + } + public void Init_Spline(Vector3[] controls, int count, EvaluationMode m) + { + m_mode = m; + cyclic = false; + + switch (m_mode) + { + case EvaluationMode.Linear: + case EvaluationMode.Catmullrom: + InitCatmullRom(controls, count, cyclic, 0); + break; + case EvaluationMode.Bezier3_Unused: + InitBezier3(controls, count, cyclic, 0); + break; + default: + break; + } + } + void InitLinear(Vector3[] controls, int count, bool cyclic, int cyclic_point) + { + int real_size = count + 1; + + Array.Resize(ref points, real_size); + Array.Copy(controls, points, count); + + // first and last two indexes are space for special 'virtual points' + // these points are required for proper C_Evaluate and C_Evaluate_Derivative methtod work + if (cyclic) + points[count] = controls[cyclic_point]; + else + points[count] = controls[count - 1]; + + index_lo = 0; + index_hi = cyclic ? count : (count - 1); + } + void InitCatmullRom(Vector3[] controls, int count, bool cyclic, int cyclic_point) + { + int real_size = count + (cyclic ? (1 + 2) : (1 + 1)); + + points = new Vector3[real_size]; + + int lo_index = 1; + int high_index = lo_index + count - 1; + + Array.Copy(controls, 0, points, lo_index, count); + + // first and last two indexes are space for special 'virtual points' + // these points are required for proper C_Evaluate and C_Evaluate_Derivative methtod work + if (cyclic) + { + if (cyclic_point == 0) + points[0] = controls[count - 1]; + else + points[0] = controls[0].lerp(controls[1], -1); + + points[high_index + 1] = controls[cyclic_point]; + points[high_index + 2] = controls[cyclic_point + 1]; + } + else + { + points[0] = controls[0].lerp(controls[1], -1); + points[high_index + 1] = controls[count - 1]; + } + + index_lo = lo_index; + index_hi = high_index + (cyclic ? 1 : 0); + } + void InitBezier3(Vector3[] controls, int count, bool cyclic, int cyclic_point) + { + int c = (int)(count / 3u * 3u); + int t = (int)(c / 3u); + + Array.Resize(ref points, c); + Array.Copy(controls, points, c); + + index_lo = 0; + index_hi = t - 1; + } + #endregion + + #region EvaluateDerivative + public void Evaluate_Derivative(int Idx, float u, out Vector3 hermite) + { + switch (m_mode) + { + case EvaluationMode.Linear: + EvaluateDerivativeLinear(Idx, u, out hermite); + break; + case EvaluationMode.Catmullrom: + EvaluateDerivativeCatmullRom(Idx, u, out hermite); + break; + case EvaluationMode.Bezier3_Unused: + EvaluateDerivativeBezier3(Idx, u, out hermite); + break; + default: + hermite = new Vector3(); + break; + } + } + void EvaluateDerivativeLinear(int index, float t, out Vector3 result) + { + result = points[index + 1] - points[index]; + } + void EvaluateDerivativeCatmullRom(int index, float t, out Vector3 result) + { + C_Evaluate_Derivative(points.Skip(index - 1).ToArray(), t, s_catmullRomCoeffs, out result); + } + void EvaluateDerivativeBezier3(int index, float t, out Vector3 result) + { + index *= (int)3u; + C_Evaluate_Derivative(points.Skip(index).ToArray(), t, s_Bezier3Coeffs, out result); + } + #endregion + + #region SegLength + public float SegLength(int i) + { + switch (m_mode) + { + case EvaluationMode.Linear: + return SegLengthLinear(i); + case EvaluationMode.Catmullrom: + return SegLengthCatmullRom(i); + case EvaluationMode.Bezier3_Unused: + return SegLengthBezier3(i); + default: + return 0; + } + } + float SegLengthLinear(int index) + { + return (points[index] - points[index + 1]).GetLength(); + } + float SegLengthCatmullRom(int index) + { + Vector3 curPos, nextPos; + var p = points.Skip(index - 1).ToArray(); + curPos = nextPos = p[1]; + + int i = 1; + double length = 0; + while (i <= 3) + { + C_Evaluate(p, i / (float)3, s_catmullRomCoeffs, out nextPos); + length += (nextPos - curPos).GetLength(); + curPos = nextPos; + ++i; + } + return (float)length; + } + float SegLengthBezier3(int index) + { + index *= (int)3u; + + Vector3 curPos, nextPos; + var p = points.Skip(index).ToArray(); + + C_Evaluate(p, 0.0f, s_Bezier3Coeffs, out nextPos); + curPos = nextPos; + + int i = 1; + double length = 0; + while (i <= 3) + { + C_Evaluate(p, i / (float)3, s_Bezier3Coeffs, out nextPos); + length += (nextPos - curPos).GetLength(); + curPos = nextPos; + ++i; + } + return (float)length; + } + #endregion + + public void computeIndex(float t, ref int index, ref float u) + { + //ASSERT(t >= 0.f && t <= 1.f); + int length_ = (int)(t * length()); + index = computeIndexInBounds(length_); + //ASSERT(index < index_hi); + u = (length_ - length(index)) / (float)length(index, index + 1); + } + + int computeIndexInBounds(int length_) + { + // Temporary disabled: causes infinite loop with t = 1.f + /* + index_type hi = index_hi; + index_type lo = index_lo; + + index_type i = lo + (float)(hi - lo) * t; + + while ((lengths[i] > length) || (lengths[i + 1] <= length)) + { + if (lengths[i] > length) + hi = i - 1; // too big + else if (lengths[i + 1] <= length) + lo = i + 1; // too small + + i = (hi + lo) / 2; + }*/ + + int i = index_lo; + int N = index_hi; + while (i + 1 < N && lengths[i + 1] < length_) + ++i; + + return i; + } + private static readonly Matrix4 s_catmullRomCoeffs = new Matrix4(-0.5f, 1.5f, -1.5f, 0.5f, 1.0f, -2.5f, 2.0f, -0.5f, -0.5f, 0.0f, 0.5f, 0.0f, 0.0f, 1.0f, 0.0f, 0.0f); + + private static readonly Matrix4 s_Bezier3Coeffs = new Matrix4(-1.0f, 3.0f, -3.0f, 1.0f, 3.0f, -6.0f, 3.0f, 0.0f, -3.0f, 3.0f, 0.0f, 0.0f, 1.0f, 0.0f, 0.0f, 0.0f); + + void C_Evaluate(Vector3[] vertice, float t, Matrix4 matr, out Vector3 result) + { + Vector4 tvec = new Vector4(t * t * t, t * t, t, 1.0f); + Vector4 weights = (tvec * matr); + + result = vertice[0] * weights[0] + vertice[1] * weights[1] + + vertice[2] * weights[2] + vertice[3] * weights[3]; + } + void C_Evaluate_Derivative(Vector3[] vertice, float t, Matrix4 matr, out Vector3 result) + { + Vector4 tvec = new Vector4(3.0f * t * t, 2.0f * t, 1.0f, 0.0f); + Vector4 weights = (tvec * matr); + + result = vertice[0] * weights[0] + vertice[1] * weights[1] + + vertice[2] * weights[2] + vertice[3] * weights[3]; + } + + public int length() { return lengths[index_hi];} + + public int length(int first, int last) { return lengths[last] - lengths[first]; } + + public int length(int Idx) { return lengths[Idx]; } + + public void set_length(int i, int length) { lengths[i] = length; } + + public void initLengths(Initializer cacher) + { + int i = index_lo; + Array.Resize(ref lengths, index_hi+1); + int prev_length = 0, new_length = 0; + while (i < index_hi) + { + new_length = cacher.SetGetTime(this, i); + if (new_length < 0) + new_length = int.MaxValue; + lengths[++i] = new_length; + + prev_length = new_length; + } + } + + public void initLengths() + { + int i = index_lo; + int length = 0; + Array.Resize(ref lengths, index_hi + 1); + while (i < index_hi) + { + length += (int)SegLength(i); + lengths[++i] = length; + } + } + + public bool empty() { return index_lo == index_hi;} + + int[] lengths = new int[0]; + Vector3[] points = new Vector3[0]; + public EvaluationMode m_mode; + bool cyclic; + int index_lo; + int index_hi; + public enum EvaluationMode + { + Linear, + Catmullrom, + Bezier3_Unused, + UninitializedMode, + ModesEnd + } + } + + public class FacingInfo + { + public Vector3 f; + public ObjectGuid target; + public float angle; + public MonsterMoveType type; + } +} diff --git a/Game/Movement/WaypointManager.cs b/Game/Movement/WaypointManager.cs new file mode 100644 index 000000000..a5dd81085 --- /dev/null +++ b/Game/Movement/WaypointManager.cs @@ -0,0 +1,157 @@ +/* + * Copyright (C) 2012-2017 CypherCore + * + * 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, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +using Framework.Database; +using Game.Maps; +using System.Collections.Generic; + +namespace Game +{ + public sealed class WaypointManager : Singleton + { + WaypointManager() + { + _waypointStore = new MultiMap(); + } + + public void Load() + { + var oldMSTime = Time.GetMSTime(); + + // 0 1 2 3 4 5 6 7 8 9 + SQLResult result = DB.World.Query("SELECT id, point, position_x, position_y, position_z, orientation, move_type, delay, action, action_chance FROM waypoint_data ORDER BY id, point"); + + if (result.IsEmpty()) + { + Log.outError(LogFilter.ServerLoading, "Loaded 0 waypoints. DB table `waypoint_data` is empty!"); + return; + } + + uint count = 0; + + do + { + WaypointData wp = new WaypointData(); + + uint pathId = result.Read(0); + + float x = result.Read(2); + float y = result.Read(3); + float z = result.Read(4); + float o = result.Read(5); + + GridDefines.NormalizeMapCoord(ref x); + GridDefines.NormalizeMapCoord(ref y); + + wp.id = result.Read(1); + wp.x = x; + wp.y = y; + wp.z = z; + wp.orientation = o; + wp.movetype = (WaypointMoveType)result.Read(6); + if (wp.movetype >= WaypointMoveType.Max) + { + Log.outError(LogFilter.Sql, "Waypoint {0} in waypoint_data has invalid move_type, ignoring", wp.id); + continue; + } + + wp.delay = result.Read(7); + wp.event_id = result.Read(8); + wp.event_chance = result.Read(9); + + _waypointStore.Add(pathId, wp); + ++count; + } while (result.NextRow()); + + Log.outInfo(LogFilter.ServerLoading, "Loaded {0} waypoints in {1} ms", count, Time.GetMSTimeDiffToNow(oldMSTime)); + } + + public void ReloadPath(uint id) + { + if (_waypointStore.ContainsKey(id)) + _waypointStore.Remove(id); + + PreparedStatement stmt = DB.World.GetPreparedStatement(WorldStatements.SEL_WAYPOINT_DATA_BY_ID); + stmt.AddValue(0, id); + SQLResult result = DB.World.Query(stmt); + + if (result.IsEmpty()) + return; + + do + { + WaypointData wp = new WaypointData(); + + float x = result.Read(1); + float y = result.Read(2); + float z = result.Read(3); + float o = result.Read(4); + + GridDefines.NormalizeMapCoord(ref x); + GridDefines.NormalizeMapCoord(ref y); + + wp.id = result.Read(0); + wp.x = x; + wp.y = y; + wp.z = z; + wp.orientation = o; + wp.movetype = (WaypointMoveType)result.Read(5); + + if (wp.movetype >= WaypointMoveType.Max) + { + Log.outError(LogFilter.Sql, "Waypoint {0} in waypoint_data has invalid move_type, ignoring", wp.id); + continue; + } + + wp.delay = result.Read(6); + wp.event_id = result.Read(7); + wp.event_chance = result.Read(8); + + _waypointStore.Add(id, wp); + + } + while (result.NextRow()); + } + + public List GetPath(uint id) + { + return _waypointStore.LookupByKey(id); + } + + MultiMap _waypointStore; + } + + public struct WaypointData + { + public uint id; + public float x, y, z, orientation; + public uint delay; + public uint event_id; + public WaypointMoveType movetype; + public byte event_chance; + } + + public enum WaypointMoveType + { + Walk, + Run, + Land, + Takeoff, + + Max + } +} diff --git a/Game/Network/Packet.cs b/Game/Network/Packet.cs new file mode 100644 index 000000000..8f81fd93e --- /dev/null +++ b/Game/Network/Packet.cs @@ -0,0 +1,223 @@ +/* + * Copyright (C) 2012-2017 CypherCore + * + * 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, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +using Framework.Constants; +using Framework.IO; +using Game.Entities; +using System; + +namespace Game.Network +{ + public abstract class ClientPacket : IDisposable + { + public ClientPacket(WorldPacket worldPacket) + { + _worldPacket = worldPacket; + } + + public void Dispose() + { + _worldPacket.Dispose(); + } + + public abstract void Read(); + + public ClientOpcodes GetOpcode() { return (ClientOpcodes)_worldPacket.GetOpcode(); } + + public void LogPacket(WorldSession session) + { + Log.outDebug(LogFilter.Network, "Received ClientOpcode: {0} From: {1}", GetOpcode(), session != null ? session.GetPlayerInfo() : "Unknown IP"); + } + + protected WorldPacket _worldPacket; + } + + public abstract class ServerPacket : IDisposable + { + public ServerPacket(ServerOpcodes opcode) + { + connectionType = ConnectionType.Realm; + _worldPacket = new WorldPacket(opcode); + } + + public ServerPacket(ServerOpcodes opcode, ConnectionType type = ConnectionType.Realm) + { + connectionType = type; + _worldPacket = new WorldPacket(opcode); + } + + public void Dispose() + { + _worldPacket.Dispose(); + } + + public void Clear() { _worldPacket.Clear(); } + public ServerOpcodes GetOpcode() { return (ServerOpcodes)_worldPacket.GetOpcode(); } + public uint GetSize() { return _worldPacket.GetSize(); } + public byte[] GetData() { return _worldPacket.GetData(); } + + public void LogPacket(WorldSession session) + { + Log.outDebug(LogFilter.Network, "Sent ServerOpcode: {0} To: {1}", GetOpcode(), session != null ? session.GetPlayerInfo() : ""); + } + + public abstract void Write(); + + public ConnectionType GetConnection() { return connectionType; } + + ConnectionType connectionType; + + protected WorldPacket _worldPacket; + } + + public class WorldPacket : ByteBuffer + { + public WorldPacket(ServerOpcodes opcode = ServerOpcodes.None) + { + Opcode = (uint)opcode; + } + + public WorldPacket(byte[] data, uint opcode) : base(data) + { + Opcode = opcode; + } + + public ObjectGuid ReadPackedGuid() + { + var loLength = ReadUInt8(); + var hiLength = ReadUInt8(); + var low = ReadPackedUInt64(loLength); + return new ObjectGuid(ReadPackedUInt64(hiLength), low); + } + + private ulong ReadPackedUInt64(byte length) + { + if (length == 0) + return 0; + + var guid = 0ul; + + for (var i = 0; i < 8; i++) + if ((1 << i & length) != 0) + guid |= (ulong)ReadUInt8() << (i * 8); + + return guid; + } + + public Position ReadPosition() + { + return new Position(ReadFloat(), ReadFloat(), ReadFloat()); + } + + public void WritePackedGuid(ObjectGuid guid) + { + if (guid.IsEmpty()) + { + WriteUInt8(0); + WriteUInt8(0); + return; + } + + byte lowMask, highMask; + byte[] lowPacked, highPacked; + + var loSize = PackUInt64(guid.GetLowValue(), out lowMask, out lowPacked); + var hiSize = PackUInt64(guid.GetHighValue(), out highMask, out highPacked); + + WriteUInt8(lowMask); + WriteUInt8(highMask); + WriteBytes(lowPacked, loSize); + WriteBytes(highPacked, hiSize); + } + + public void WritePackedUInt64(ulong guid) + { + byte mask; + byte[] packed; + var packedSize = PackUInt64(guid, out mask, out packed); + + WriteUInt8(mask); + WriteBytes(packed, packedSize); + } + + uint PackUInt64(ulong value, out byte mask, out byte[] result) + { + uint resultSize = 0; + mask = 0; + result = new byte[8]; + + for (byte i = 0; value != 0; ++i) + { + if ((value & 0xFF) != 0) + { + mask |= (byte)(1 << i); + result[resultSize++] = (byte)(value & 0xFF); + } + + value >>= 8; + } + + return resultSize; + } + + public void WriteBytes(WorldPacket data) + { + FlushBits(); + WriteBytes(data.GetData()); + } + + public void WriteXYZ(Position pos) + { + if (pos == null) + return; + + float x, y, z; + pos.GetPosition(out x, out y, out z); + WriteFloat(x); + WriteFloat(y); + WriteFloat(z); + } + public void WriteXYZO(Position pos) + { + float x, y, z, o; + pos.GetPosition(out x, out y, out z, out o); + WriteFloat(x); + WriteFloat(y); + WriteFloat(z); + WriteFloat(o); + } + + public uint GetOpcode() { return Opcode; } + + uint Opcode; + } + + public class ServerPacketHeader + { + public ServerPacketHeader(uint size, ServerOpcodes opcode) + { + Size = size + 2; + Opcode = opcode; + + data = BitConverter.GetBytes(Size).Combine(BitConverter.GetBytes((ushort)opcode)); + } + + public ServerOpcodes Opcode { get; set; } + public uint Size { get; set; } + public byte[] data { get; set; } + } +} diff --git a/Game/Network/PacketLog.cs b/Game/Network/PacketLog.cs new file mode 100644 index 000000000..2f9c5ac40 --- /dev/null +++ b/Game/Network/PacketLog.cs @@ -0,0 +1,114 @@ +/* + * Copyright (C) 2012-2017 CypherCore + * + * 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, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +using Framework.Configuration; +using Framework.Constants; +using System; +using System.IO; +using System.Net; +using System.Text; + +public class PacketLog +{ + static object syncObj = new object(); + static string FullPath; + + static PacketLog() + { + string logsDir = ConfigMgr.GetDefaultValue("LogsDir", ""); + string logname = ConfigMgr.GetDefaultValue("PacketLogFile", ""); + if (!string.IsNullOrEmpty(logname)) + { + FullPath = logsDir + @"\" + logname; + using (var writer = new BinaryWriter(File.Open(FullPath, FileMode.Create))) + { + writer.Write(Encoding.ASCII.GetBytes("PKT")); + writer.Write((ushort)769); + writer.Write(Encoding.ASCII.GetBytes("T")); + writer.Write(Global.WorldMgr.GetRealm().Build); + writer.Write(Encoding.ASCII.GetBytes("enUS")); + writer.Write(new byte[40]);//SessionKey + writer.Write((uint)Time.UnixTime); + writer.Write(Time.GetMSTime()); + writer.Write(0); + } + } + } + + public static void Write(byte[] data, ClientOpcodes opcode, IPAddress address, uint port, ConnectionType connectionType) + { + if (!CanLog()) + return; + + lock (syncObj) + { + using (var writer = new BinaryWriter(File.Open(FullPath, FileMode.Append), Encoding.ASCII)) + { + writer.Write("CMSG".ToCharArray()); + writer.Write((uint)connectionType); + writer.Write(Time.GetMSTime()); + + writer.Write(20); + byte[] SocketIPBytes = new byte[16]; + if (address.AddressFamily == System.Net.Sockets.AddressFamily.InterNetwork) + Buffer.BlockCopy(address.GetAddressBytes(), 0, SocketIPBytes, 0, 4); + else + Buffer.BlockCopy(address.GetAddressBytes(), 0, SocketIPBytes, 0, 16); + + writer.Write(data.Length + 4); + writer.Write(SocketIPBytes); + writer.Write(port); + writer.Write((uint)opcode); + writer.Write(data); + } + } + } + + public static void Write(byte[] data, ServerOpcodes opcode, IPAddress address, uint port, ConnectionType connectionType) + { + if (!CanLog()) + return; + + lock (syncObj) + { + using (var writer = new BinaryWriter(File.Open(FullPath, FileMode.Append), Encoding.ASCII)) + { + writer.Write("SMSG".ToCharArray()); + writer.Write((uint)connectionType); + writer.Write(Time.GetMSTime()); + + writer.Write(20); + byte[] SocketIPBytes = new byte[16]; + if (address.AddressFamily == System.Net.Sockets.AddressFamily.InterNetwork) + Buffer.BlockCopy(address.GetAddressBytes(), 0, SocketIPBytes, 0, 4); + else + Buffer.BlockCopy(address.GetAddressBytes(), 0, SocketIPBytes, 0, 16); + + writer.Write(data.Length + 4); + writer.Write(SocketIPBytes); + writer.Write(port); + writer.Write((uint)opcode); + writer.Write(data); + } + } + } + + public static bool CanLog() + { + return !string.IsNullOrEmpty(FullPath); + } +} diff --git a/Game/Network/PacketManager.cs b/Game/Network/PacketManager.cs new file mode 100644 index 000000000..2699f9c91 --- /dev/null +++ b/Game/Network/PacketManager.cs @@ -0,0 +1,239 @@ +/* + * Copyright (C) 2012-2017 CypherCore + * + * 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, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +using Framework.Constants; +using Game.Entities; +using System; +using System.Collections.Concurrent; +using System.Collections.Generic; +using System.Reflection; + +namespace Game.Network +{ + public static class PacketManager + { + public static void Initialize() + { + Assembly currentAsm = Assembly.GetExecutingAssembly(); + foreach (var type in currentAsm.GetTypes()) + { + foreach (var methodInfo in type.GetMethods(BindingFlags.Instance | BindingFlags.NonPublic)) + { + foreach (var msgAttr in methodInfo.GetCustomAttributes()) + { + if (msgAttr == null) + continue; + + if (msgAttr.Opcode == ClientOpcodes.Unknown) + { + Log.outError(LogFilter.Network, "Opcode {0} does not have a value", msgAttr.Opcode); + continue; + } + + if (_clientPacketTable.ContainsKey(msgAttr.Opcode)) + { + Log.outError(LogFilter.Network, "Tried to override OpcodeHandler of {0} with {1} (Opcode {2})", _clientPacketTable[msgAttr.Opcode].ToString(), methodInfo.Name, msgAttr.Opcode); + continue; + } + + var parameters = methodInfo.GetParameters(); + if (parameters.Length == 0) + { + Log.outError(LogFilter.Network, "Method: {0} Has no paramters", methodInfo.Name); + continue; + } + + if (parameters[0].ParameterType.BaseType != typeof(ClientPacket)) + { + Log.outError(LogFilter.Network, "Method: {0} has wrong BaseType", methodInfo.Name); + continue; + } + + _clientPacketTable[msgAttr.Opcode] = new PacketHandler(methodInfo, msgAttr.Status, msgAttr.Processing, parameters[0].ParameterType); + } + } + } + } + + public static bool TryPeek(this ConcurrentQueue queue, out WorldPacket result, PacketFilter filter) + { + result = null; + + if (queue.IsEmpty) + return false; + + if (!queue.TryPeek(out result)) + return false; + + if (!filter.Process(result)) + return false; + + return true; + } + + public static PacketHandler GetHandler(ClientOpcodes opcode) + { + return _clientPacketTable.LookupByKey(opcode); + } + + public static bool ContainsHandler(ClientOpcodes opcode) + { + return _clientPacketTable.ContainsKey(opcode); + } + + static ConcurrentDictionary _clientPacketTable = new ConcurrentDictionary(); + + public static bool IsInstanceOnlyOpcode(ServerOpcodes opcode) + { + switch (opcode) + { + case ServerOpcodes.QuestGiverStatus: // ClientQuest + case ServerOpcodes.DuelRequested: // Client + case ServerOpcodes.DuelInBounds: // Client + case ServerOpcodes.QueryTimeResponse: // Client + case ServerOpcodes.DuelWinner: // Client + case ServerOpcodes.DuelComplete: // Client + case ServerOpcodes.DuelOutOfBounds: // Client + case ServerOpcodes.AttackStop: // Client + case ServerOpcodes.AttackStart: // Client + case ServerOpcodes.MountResult: // Client + return true; + default: + return false; + } + } + } + + public class PacketHandler + { + public PacketHandler(MethodInfo info, SessionStatus status, PacketProcessing processingplace, Type type) + { + methodCaller = (Action)GetType().GetMethod("CreateDelegate", BindingFlags.Static | BindingFlags.NonPublic).MakeGenericMethod(type).Invoke(null, new object[] { info }); + sessionStatus = status; + processingPlace = processingplace; + packetType = type; + } + + public void Invoke(WorldSession session, WorldPacket packet) + { + if (packetType == null) + return; + + using (var clientPacket = (ClientPacket)Activator.CreateInstance(packetType, packet)) + { + clientPacket.Read(); + clientPacket.LogPacket(session); + methodCaller(session, clientPacket); + } + } + + static Action CreateDelegate(MethodInfo method) where P1 : ClientPacket + { + // create first delegate. It is not fine because its + // signature contains unknown types T and P1 + Action d = (Action)method.CreateDelegate(typeof(Action)); + // create another delegate having necessary signature. + // It encapsulates first delegate with a closure + return delegate (WorldSession target, ClientPacket p) { d(target, (P1)p); }; + } + + Action methodCaller; + Type packetType; + public PacketProcessing processingPlace { get; private set; } + public SessionStatus sessionStatus { get; private set; } + } + + public abstract class PacketFilter + { + public PacketFilter(WorldSession pSession) + { + m_pSession = pSession; + } + + public abstract bool Process(WorldPacket packet); + + public virtual bool ProcessUnsafe() { return false; } + + protected WorldSession m_pSession; + } + + public class MapSessionFilter : PacketFilter + { + public MapSessionFilter(WorldSession pSession) : base(pSession) { } + + public override bool Process(WorldPacket packet) + { + PacketHandler opHandle = PacketManager.GetHandler((ClientOpcodes)packet.GetOpcode()); + //check if packet handler is supposed to be safe + if (opHandle.processingPlace == PacketProcessing.Inplace) + return true; + + //we do not process thread-unsafe packets + if (opHandle.processingPlace == PacketProcessing.ThreadUnsafe) + return false; + + Player player = m_pSession.GetPlayer(); + if (!player) + return false; + + //in Map.Update() we do not process packets where player is not in world! + return player.IsInWorld; + } + } + + public class WorldSessionFilter : PacketFilter + { + public WorldSessionFilter(WorldSession pSession) : base(pSession) { } + + public override bool Process(WorldPacket packet) + { + PacketHandler opHandle = PacketManager.GetHandler((ClientOpcodes)packet.GetOpcode()); + //check if packet handler is supposed to be safe + if (opHandle.processingPlace == PacketProcessing.Inplace) + return true; + + //thread-unsafe packets should be processed in World.UpdateSessions() + if (opHandle.processingPlace == PacketProcessing.ThreadUnsafe) + return true; + + //no player attached? . our client! ^^ + Player player = m_pSession.GetPlayer(); + if (!player) + return true; + + //lets process all packets for non-in-the-world player + return !player.IsInWorld; + } + + public override bool ProcessUnsafe() { return true; } + } + + [AttributeUsage(AttributeTargets.Method, AllowMultiple = true)] + public sealed class WorldPacketHandlerAttribute : Attribute + { + public WorldPacketHandlerAttribute(ClientOpcodes opcode) + { + Opcode = opcode; + Status = SessionStatus.Loggedin; + Processing = PacketProcessing.ThreadUnsafe; + } + + public ClientOpcodes Opcode { get; private set; } + public SessionStatus Status { get; set; } + public PacketProcessing Processing { get; set; } + } +} diff --git a/Game/Network/PacketUtilities.cs b/Game/Network/PacketUtilities.cs new file mode 100644 index 000000000..980579345 --- /dev/null +++ b/Game/Network/PacketUtilities.cs @@ -0,0 +1,99 @@ +/* + * Copyright (C) 2012-2017 CypherCore + * + * 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, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +using System; +using System.Diagnostics.Contracts; + +namespace Game.Network +{ + public class CompactArray + { + public void Insert(int index, int value) + { + Contract.Assert(index < 0x20); + + _mask |= 1u << index; + if (_contents.Length <= index) + Array.Resize(ref _contents, index + 1); + _contents[index] = value; + } + + void Clear() + { + _mask = 0; + _contents = new int[1]; + } + + public void Read(WorldPacket data) + { + uint mask = data.ReadUInt32(); + + for (int index = 0; mask != 0; mask >>= 1, ++index) + { + if ((mask & 1) != 0) + { + int value = data.ReadInt32(); + Insert(index, value); + } + } + } + + public void Write(WorldPacket data) + { + uint mask = GetMask(); + data.WriteUInt32(mask); + for (int i = 0; i < GetSize(); ++i) + { + if (Convert.ToBoolean(mask & (1 << i))) + data.WriteInt32(this[i]); + } + } + + public override int GetHashCode() + { + return _mask.GetHashCode() ^ _contents.GetHashCode(); + } + + public override bool Equals(object obj) + { + if (obj is CompactArray) + return (CompactArray)obj == this; + + return false; + } + + public static bool operator ==(CompactArray left, CompactArray right) + { + if (left._mask != right._mask) + return false; + + return left._contents == right._contents; + } + + public static bool operator !=(CompactArray left, CompactArray right) + { + return !(left == right); + } + + uint GetMask() { return _mask; } + public int this[int index] { get { return _contents[index]; } } + int GetSize() { return _contents.Length; } + + uint _mask; + int[] _contents = new int[1]; + } +} diff --git a/Game/Network/Packets/AchievementPackets.cs b/Game/Network/Packets/AchievementPackets.cs new file mode 100644 index 000000000..31087cc7e --- /dev/null +++ b/Game/Network/Packets/AchievementPackets.cs @@ -0,0 +1,345 @@ +/* + * Copyright (C) 2012-2017 CypherCore + * + * 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, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +using Framework.Constants; +using Game.Entities; +using System.Collections.Generic; +using System.Linq; + +namespace Game.Network.Packets +{ + public class AllAchievementData : ServerPacket + { + public AllAchievementData() : base(ServerOpcodes.AllAchievementData, ConnectionType.Instance) { } + + public override void Write() + { + Data.Write(_worldPacket); + } + + public AllAchievements Data = new AllAchievements(); + } + + public class RespondInspectAchievements : ServerPacket + { + public RespondInspectAchievements() : base(ServerOpcodes.RespondInspectAchievements, ConnectionType.Instance) { } + + public override void Write() + { + _worldPacket.WritePackedGuid(Player); + Data.Write(_worldPacket); + } + + public ObjectGuid Player; + public AllAchievements Data = new AllAchievements(); + } + + public class CriteriaUpdate : ServerPacket + { + public CriteriaUpdate() : base(ServerOpcodes.CriteriaUpdate, ConnectionType.Instance) { } + + public override void Write() + { + _worldPacket.WriteUInt32(CriteriaID); + _worldPacket.WriteUInt64(Quantity); + _worldPacket.WritePackedGuid(PlayerGUID); + _worldPacket.WriteUInt32(Flags); + _worldPacket.WritePackedTime(CurrentTime); + _worldPacket.WriteUInt32(ElapsedTime); + _worldPacket.WriteUInt32(CreationTime); + } + + public uint CriteriaID; + public ulong Quantity; + public ObjectGuid PlayerGUID; + public uint Flags; + public long CurrentTime; + public uint ElapsedTime; + public uint CreationTime; + } + + public class CriteriaDeleted : ServerPacket + { + public CriteriaDeleted() : base(ServerOpcodes.CriteriaDeleted, ConnectionType.Instance) { } + + public override void Write() + { + _worldPacket.WriteUInt32(CriteriaID); + } + + public uint CriteriaID; + } + + public class AchievementDeleted : ServerPacket + { + public AchievementDeleted() : base(ServerOpcodes.AchievementDeleted, ConnectionType.Instance) { } + + public override void Write() + { + _worldPacket.WriteUInt32(AchievementID); + _worldPacket.WriteUInt32(Immunities); + } + + public uint AchievementID; + public uint Immunities; // this is just garbage, not used by client + } + + public class AchievementEarned : ServerPacket + { + public AchievementEarned() : base(ServerOpcodes.AchievementEarned, ConnectionType.Instance) { } + + public override void Write() + { + _worldPacket.WritePackedGuid(Sender); + _worldPacket.WritePackedGuid(Earner); + _worldPacket.WriteUInt32(AchievementID); + _worldPacket.WritePackedTime(Time); + _worldPacket.WriteUInt32(EarnerNativeRealm); + _worldPacket.WriteUInt32(EarnerVirtualRealm); + _worldPacket.WriteBit(Initial); + _worldPacket.FlushBits(); + } + + public ObjectGuid Earner; + public uint EarnerNativeRealm; + public uint EarnerVirtualRealm; + public uint AchievementID; + public long Time; + public bool Initial; + public ObjectGuid Sender; + } + + public class ServerFirstAchievement : ServerPacket + { + public ServerFirstAchievement() : base(ServerOpcodes.ServerFirstAchievement) { } + + public override void Write() + { + _worldPacket.WriteBits(Name.Length, 7); + _worldPacket.WriteBit(GuildAchievement); + _worldPacket.WritePackedGuid(PlayerGUID); + _worldPacket.WriteUInt32(AchievementID); + _worldPacket.WriteString(Name); + } + + public ObjectGuid PlayerGUID; + public string Name = ""; + public uint AchievementID; + public bool GuildAchievement; + } + + public class GuildCriteriaUpdate : ServerPacket + { + public GuildCriteriaUpdate() : base(ServerOpcodes.GuildCriteriaUpdate) { } + + public override void Write() + { + _worldPacket.WriteUInt32(Progress.Count); + + foreach (GuildCriteriaProgress progress in Progress) + { + _worldPacket.WriteInt32(progress.CriteriaID); + _worldPacket.WriteUInt32(progress.DateCreated); + _worldPacket.WriteUInt32(progress.DateStarted); + _worldPacket.WritePackedTime(progress.DateUpdated); + _worldPacket.WriteUInt64(progress.Quantity); + _worldPacket.WritePackedGuid(progress.PlayerGUID); + _worldPacket.WriteInt32(progress.Flags); + } + } + + public List Progress = new List(); + } + + public class GuildCriteriaDeleted : ServerPacket + { + public GuildCriteriaDeleted() : base(ServerOpcodes.GuildCriteriaDeleted) { } + + public override void Write() + { + _worldPacket.WritePackedGuid(GuildGUID); + _worldPacket.WriteUInt32(CriteriaID); + } + + public ObjectGuid GuildGUID; + public uint CriteriaID; + } + + public class GuildSetFocusedAchievement : ClientPacket + { + public GuildSetFocusedAchievement(WorldPacket packet) : base(packet) { } + + public override void Read() + { + AchievementID = _worldPacket.ReadUInt32(); + } + + public uint AchievementID; + } + + public class GuildAchievementDeleted : ServerPacket + { + public GuildAchievementDeleted() : base(ServerOpcodes.GuildAchievementDeleted) { } + + public override void Write() + { + _worldPacket.WritePackedGuid(GuildGUID); + _worldPacket.WriteUInt32(AchievementID); + _worldPacket.WritePackedTime(TimeDeleted); + } + + public ObjectGuid GuildGUID; + public uint AchievementID; + public long TimeDeleted; + } + + public class GuildAchievementEarned : ServerPacket + { + public GuildAchievementEarned() : base(ServerOpcodes.GuildAchievementEarned) { } + + public override void Write() + { + _worldPacket.WritePackedGuid(GuildGUID); + _worldPacket.WriteUInt32(AchievementID); + _worldPacket.WritePackedTime(TimeEarned); + } + + public uint AchievementID; + public ObjectGuid GuildGUID; + public long TimeEarned; + } + + public class AllGuildAchievements : ServerPacket + { + public AllGuildAchievements() : base(ServerOpcodes.AllGuildAchievements) { } + + public override void Write() + { + _worldPacket.WriteUInt32(Earned.Count()); + + foreach (EarnedAchievement earned in Earned) + earned.Write(_worldPacket); + } + + public List Earned = new List(); + } + + class GuildGetAchievementMembers : ClientPacket + { + public GuildGetAchievementMembers(WorldPacket packet) : base(packet) { } + + public override void Read() + { + PlayerGUID = _worldPacket.ReadPackedGuid(); + GuildGUID = _worldPacket.ReadPackedGuid(); + AchievementID = _worldPacket.ReadUInt32(); + } + + public ObjectGuid PlayerGUID; + public ObjectGuid GuildGUID; + public uint AchievementID; + } + + class GuildAchievementMembers : ServerPacket + { + public GuildAchievementMembers() : base(ServerOpcodes.GuildAchievementMembers) { } + + public override void Write() + { + _worldPacket.WritePackedGuid(GuildGUID); + _worldPacket.WriteUInt32(AchievementID); + _worldPacket.WriteUInt32(Member.Count); + foreach (ObjectGuid guid in Member) + _worldPacket.WritePackedGuid(guid); + } + + public ObjectGuid GuildGUID; + public uint AchievementID; + public List Member = new List(); + } + + //Structs + public struct EarnedAchievement + { + public void Write(WorldPacket data) + { + data.WriteUInt32(Id); + data.WritePackedTime(Date); + data.WritePackedGuid(Owner); + data.WriteUInt32(VirtualRealmAddress); + data.WriteUInt32(NativeRealmAddress); + } + + public uint Id; + public long Date; + public ObjectGuid Owner; + public uint VirtualRealmAddress; + public uint NativeRealmAddress; + } + + public struct CriteriaProgressPkt + { + public void Write(WorldPacket data) + { + data.WriteUInt32(Id); + data.WriteUInt64(Quantity); + data.WritePackedGuid(Player); + data.WritePackedTime(Date); + data.WriteUInt32(TimeFromStart); + data.WriteUInt32(TimeFromCreate); + data.WriteBits(Flags, 4); + data.FlushBits(); + } + + public uint Id; + public ulong Quantity; + public ObjectGuid Player; + public uint Flags; + public long Date; + public uint TimeFromStart; + public uint TimeFromCreate; + } + + public struct GuildCriteriaProgress + { + public uint CriteriaID; + public uint DateCreated; + public uint DateStarted; + public long DateUpdated; + public ulong Quantity; + public ObjectGuid PlayerGUID; + public int Flags; + } + + public class AllAchievements + { + public void Write(WorldPacket data) + { + data.WriteUInt32(Earned.Count); + data.WriteUInt32(Progress.Count); + + foreach (EarnedAchievement earned in Earned) + earned.Write(data); + + foreach (CriteriaProgressPkt progress in Progress) + progress.Write(data); + } + + public List Earned = new List(); + public List Progress = new List(); + } +} diff --git a/Game/Network/Packets/AreaTriggerPackets.cs b/Game/Network/Packets/AreaTriggerPackets.cs new file mode 100644 index 000000000..7aa595de4 --- /dev/null +++ b/Game/Network/Packets/AreaTriggerPackets.cs @@ -0,0 +1,159 @@ +/* + * Copyright (C) 2012-2017 CypherCore + * + * 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, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +using Framework.Constants; +using Framework.Dynamic; +using Framework.GameMath; +using Game.Entities; +using System.Collections.Generic; + + +namespace Game.Network.Packets +{ + class AreaTriggerPkt : ClientPacket + { + public AreaTriggerPkt(WorldPacket packet) : base(packet) { } + + public override void Read() + { + AreaTriggerID = _worldPacket.ReadUInt32(); + Entered = _worldPacket.HasBit(); + FromClient = _worldPacket.HasBit(); + } + + public uint AreaTriggerID; + public bool Entered; + public bool FromClient; + } + + class AreaTriggerDenied : ServerPacket + { + public AreaTriggerDenied() : base(ServerOpcodes.AreaTriggerDenied) { } + + public override void Write() + { + _worldPacket.WriteInt32(AreaTriggerID); + _worldPacket.WriteBit(Entered); + _worldPacket.FlushBits(); + } + + public int AreaTriggerID; + public bool Entered; + } + + class AreaTriggerNoCorpse : ServerPacket + { + public AreaTriggerNoCorpse() : base(ServerOpcodes.AreaTriggerNoCorpse) { } + + public override void Write() { } + } + + class AreaTriggerRePath : ServerPacket + { + public AreaTriggerRePath() : base(ServerOpcodes.AreaTriggerRePath) { } + + public override void Write() + { + _worldPacket.WritePackedGuid( TriggerGUID); + AreaTriggerSpline.Write(_worldPacket); + } + + public AreaTriggerSplineInfo AreaTriggerSpline = new AreaTriggerSplineInfo(); + public ObjectGuid TriggerGUID; + } + + class AreaTriggerReShape : ServerPacket + { + public AreaTriggerReShape() : base(ServerOpcodes.AreaTriggerReShape) { } + + public override void Write() + { + _worldPacket .WritePackedGuid( TriggerGUID); + + _worldPacket.WriteBit(AreaTriggerSpline.HasValue); + _worldPacket.WriteBit(AreaTriggerUnkType.HasValue); + _worldPacket.FlushBits(); + + if (AreaTriggerSpline.HasValue) + AreaTriggerSpline.Value.Write(_worldPacket); + + if (AreaTriggerUnkType.HasValue) + AreaTriggerUnkType.Value.Write(_worldPacket); + } + + public Optional AreaTriggerSpline; + public Optional AreaTriggerUnkType; + public ObjectGuid TriggerGUID; + } + + //Structs + class AreaTriggerSplineInfo + { + public void Write(WorldPacket data) + { + data.WriteUInt32(TimeToTarget); + data.WriteUInt32(ElapsedTimeForMovement); + + data.WriteBits(Points.Count, 16); + data.FlushBits(); + + foreach (Vector3 point in Points) + data.WriteVector3(point); + } + + public uint TimeToTarget; + public uint ElapsedTimeForMovement; + public List Points = new List(); + } + + struct AreaTriggerUnkTypeInfo + { + public void Write(WorldPacket data) + { + data.WriteBit(AreaTriggerUnkGUID.HasValue); + data.WriteBit(Center.HasValue); + data.WriteBit(UnkBit1); + data.WriteBit(UnkBit2); + + data.WriteUInt32(UnkUInt1); + data.WriteInt32(UnkInt1); + data.WriteUInt32(UnkUInt2); + data.WriteFloat(Radius); + data.WriteFloat(BlendFromRadius); + data.WriteFloat(InitialAngel); + data.WriteFloat(ZOffset); + + if (AreaTriggerUnkGUID.HasValue) + data.WritePackedGuid(AreaTriggerUnkGUID.Value); + + if (Center.HasValue) + data.WriteVector3(Center.Value); + } + + public Optional AreaTriggerUnkGUID; + public Optional Center; + public bool UnkBit1; + public bool UnkBit2; + public uint UnkUInt1; + public int UnkInt1; + public uint UnkUInt2; + public float Radius; + public float BlendFromRadius; + public float InitialAngel; + public float ZOffset; + } +} diff --git a/Game/Network/Packets/ArtifactPackets.cs b/Game/Network/Packets/ArtifactPackets.cs new file mode 100644 index 000000000..8261f32a7 --- /dev/null +++ b/Game/Network/Packets/ArtifactPackets.cs @@ -0,0 +1,125 @@ +/* + * Copyright (C) 2012-2017 CypherCore + * + * 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, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +using Framework.Constants; +using Game.Entities; +using System.Collections.Generic; + +namespace Game.Network.Packets +{ + class ArtifactAddPower : ClientPacket + { + public ArtifactAddPower(WorldPacket packet) : base(packet) { } + + public override void Read() + { + ArtifactGUID = _worldPacket.ReadPackedGuid(); + ForgeGUID = _worldPacket.ReadPackedGuid(); + + var powerCount = _worldPacket.ReadUInt32(); + for (var i = 0; i < powerCount; ++i) + { + ArtifactPowerChoice artifactPowerChoice; + artifactPowerChoice.ArtifactPowerID = _worldPacket.ReadUInt32(); + artifactPowerChoice.Rank = _worldPacket.ReadUInt8(); + PowerChoices.Add(artifactPowerChoice); + } + } + + public ObjectGuid ArtifactGUID; + public ObjectGuid ForgeGUID; + public Array PowerChoices = new Array(1); + + public struct ArtifactPowerChoice + { + public uint ArtifactPowerID; + public byte Rank; + } + } + + class ArtifactSetAppearance : ClientPacket + { + public ArtifactSetAppearance(WorldPacket packet) : base(packet) { } + + public override void Read() + { + ArtifactGUID = _worldPacket.ReadPackedGuid(); + ForgeGUID = _worldPacket.ReadPackedGuid(); + ArtifactAppearanceID = _worldPacket.ReadInt32(); + } + + public ObjectGuid ArtifactGUID; + public ObjectGuid ForgeGUID; + public int ArtifactAppearanceID; + } + + class ConfirmArtifactRespec : ClientPacket + { + public ConfirmArtifactRespec(WorldPacket packet) : base(packet) { } + + public override void Read() + { + ArtifactGUID = _worldPacket.ReadPackedGuid(); + NpcGUID = _worldPacket.ReadPackedGuid(); + } + + public ObjectGuid ArtifactGUID; + public ObjectGuid NpcGUID; + } + + class ArtifactForgeOpened : ServerPacket + { + public ArtifactForgeOpened() : base(ServerOpcodes.ArtifactForgeOpened) { } + + public override void Write() + { + _worldPacket.WritePackedGuid(ArtifactGUID); + _worldPacket.WritePackedGuid(ForgeGUID); + } + + public ObjectGuid ArtifactGUID; + public ObjectGuid ForgeGUID; + } + + class ArtifactRespecConfirm : ServerPacket + { + public ArtifactRespecConfirm() : base(ServerOpcodes.ArtifactRespecConfirm) { } + + public override void Write() + { + _worldPacket.WritePackedGuid(ArtifactGUID); + _worldPacket.WritePackedGuid(NpcGUID); + } + + public ObjectGuid ArtifactGUID; + public ObjectGuid NpcGUID; + } + + class ArtifactXpGain : ServerPacket + { + public ArtifactXpGain() : base(ServerOpcodes.ArtifactXpGain) { } + + public override void Write() + { + _worldPacket.WritePackedGuid(ArtifactGUID); + _worldPacket.WriteUInt64(Amount); + } + + public ObjectGuid ArtifactGUID; + public ulong Amount; + } +} diff --git a/Game/Network/Packets/AuctionHousePackets.cs b/Game/Network/Packets/AuctionHousePackets.cs new file mode 100644 index 000000000..0cb227544 --- /dev/null +++ b/Game/Network/Packets/AuctionHousePackets.cs @@ -0,0 +1,562 @@ +/* + * Copyright (C) 2012-2017 CypherCore + * + * 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, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +using Framework.Constants; +using Game.Entities; +using System.Collections.Generic; + +namespace Game.Network.Packets +{ + class AuctionHelloRequest : ClientPacket + { + public AuctionHelloRequest(WorldPacket packet) : base(packet) { } + + public override void Read() + { + Guid = _worldPacket.ReadPackedGuid(); + } + + public ObjectGuid Guid; + } + + class AuctionHelloResponse : ServerPacket + { + public AuctionHelloResponse() : base(ServerOpcodes.AuctionHelloResponse) { } + + public override void Write() + { + _worldPacket.WritePackedGuid(Guid); + _worldPacket.WriteBit(OpenForBusiness); + _worldPacket.FlushBits(); + } + + public ObjectGuid Guid; + public bool OpenForBusiness = true; + } + + class AuctionCommandResult : ServerPacket + { + public AuctionCommandResult() : base(ServerOpcodes.AuctionCommandResult) { } + + public void InitializeAuction(AuctionEntry auction) + { + if (auction != null) + { + AuctionItemID = auction.Id; + Money = auction.bid == auction.buyout ? 0 : auction.bid; + MinIncrement = auction.bid == auction.buyout ? 0 : auction.GetAuctionOutBid(); + Guid = ObjectGuid.Create(HighGuid.Player, auction.bidder); + } + } + + public override void Write() + { + _worldPacket.WriteUInt32(AuctionItemID); + _worldPacket.WriteInt32(Command); + _worldPacket.WriteInt32(ErrorCode); + _worldPacket.WriteInt32(BagResult); + _worldPacket.WritePackedGuid(Guid); + _worldPacket.WriteUInt64(MinIncrement); + _worldPacket.WriteUInt64(Money); + } + + public uint AuctionItemID; // the id of the auction that triggered this notification + public AuctionAction Command; + public AuctionError ErrorCode; + public ulong Money; // the amount of money that the player bid in copper + public AuctionError BagResult; + public ObjectGuid Guid; // the GUID of the bidder for this auction. + public ulong MinIncrement; // the sum of outbid is (1% of current bid) * 5, if the bid is too small, then this value is 1 copper. + } + + class AuctionSellItem : ClientPacket + { + public AuctionSellItem(WorldPacket packet) : base(packet) { } + + public override void Read() + { + Auctioneer = _worldPacket.ReadPackedGuid(); + MinBid = _worldPacket.ReadUInt64(); + BuyoutPrice = _worldPacket.ReadUInt64(); + RunTime = _worldPacket.ReadUInt32(); + + byte ItemsCount = _worldPacket.ReadBits(5); + _worldPacket.ResetBitPos(); + + for (byte i = 0; i < ItemsCount; i++) + { + AuctionItemForSale item; + item.Guid = _worldPacket.ReadPackedGuid(); + item.UseCount = _worldPacket.ReadUInt32(); + Items.Add(item); + } + } + + public ulong BuyoutPrice; + public ObjectGuid Auctioneer; + public ulong MinBid; + public uint RunTime; + public Array Items = new Array(32); + + public struct AuctionItemForSale + { + public ObjectGuid Guid; + public uint UseCount; + } + } + + class AuctionPlaceBid : ClientPacket + { + public AuctionPlaceBid(WorldPacket packet) : base(packet) { } + + public override void Read() + { + Auctioneer = _worldPacket.ReadPackedGuid(); + AuctionItemID = _worldPacket.ReadUInt32(); + BidAmount = _worldPacket.ReadUInt64(); + } + + public ObjectGuid Auctioneer; + public ulong BidAmount; + public uint AuctionItemID; + } + + class AuctionListBidderItems : ClientPacket + { + public AuctionListBidderItems(WorldPacket packet) : base(packet) { } + + public override void Read() + { + Auctioneer = _worldPacket.ReadPackedGuid(); + Offset = _worldPacket.ReadUInt32(); + byte auctionItemIDsCount = _worldPacket.ReadBits(7); + _worldPacket.ResetBitPos(); + + for (byte i = 0; i < auctionItemIDsCount; i++) + { + uint AuctionItemID = _worldPacket.ReadUInt32(); + AuctionItemIDs.Add(AuctionItemID); + } + } + + public uint Offset; + public List AuctionItemIDs = new List(); + public ObjectGuid Auctioneer; + } + + class AuctionRemoveItem : ClientPacket + { + public AuctionRemoveItem(WorldPacket packet) : base(packet) { } + + public override void Read() + { + Auctioneer = _worldPacket.ReadPackedGuid(); + AuctionItemID = _worldPacket.ReadInt32(); + } + + public ObjectGuid Auctioneer; + public int AuctionItemID; + } + + class AuctionReplicateItems : ClientPacket + { + public AuctionReplicateItems(WorldPacket packet) : base(packet) { } + + public override void Read() + { + Auctioneer = _worldPacket.ReadPackedGuid(); + Count = _worldPacket.ReadUInt32(); + ChangeNumberGlobal = _worldPacket.ReadUInt32(); + ChangeNumberCursor = _worldPacket.ReadUInt32(); + ChangeNumberTombstone = _worldPacket.ReadUInt32(); + } + + public ObjectGuid Auctioneer; + public uint Count; + public uint ChangeNumberGlobal; + public uint ChangeNumberCursor; + public uint ChangeNumberTombstone; + } + + class AuctionListPendingSales : ClientPacket + { + public AuctionListPendingSales(WorldPacket packet) : base(packet) { } + + public override void Read() { } + } + + public class AuctionListItemsResult : ServerPacket + { + public AuctionListItemsResult() : base(ServerOpcodes.AuctionListItemsResult) { } + + public override void Write() + { + _worldPacket.WriteInt32(Items.Count); + _worldPacket.WriteInt32(TotalCount); + _worldPacket.WriteInt32(DesiredDelay); + _worldPacket.WriteBit(OnlyUsable); + _worldPacket.FlushBits(); + + foreach (var item in Items) + item.Write(_worldPacket); + } + + public uint DesiredDelay; + public List Items = new List(); + public bool OnlyUsable = true; + public uint TotalCount; + } + + public class AuctionListOwnerItemsResult : ServerPacket + { + public AuctionListOwnerItemsResult() : base(ServerOpcodes.AuctionListOwnerItemsResult) { } + + public override void Write() + { + _worldPacket.WriteInt32(Items.Count); + _worldPacket.WriteUInt32(TotalCount); + _worldPacket.WriteUInt32(DesiredDelay); + + foreach (var item in Items) + item.Write(_worldPacket); + } + + public uint DesiredDelay; + public uint TotalCount; + public List Items = new List(); + } + + public class AuctionListBidderItemsResult : ServerPacket + { + public AuctionListBidderItemsResult() : base(ServerOpcodes.AuctionListBidderItemsResult) { } + + public override void Write() + { + _worldPacket.WriteInt32(Items.Count); + _worldPacket.WriteUInt32(TotalCount); + _worldPacket.WriteUInt32(DesiredDelay); + + foreach (var item in Items) + item.Write(_worldPacket); + } + + public uint DesiredDelay; + public uint TotalCount; + public List Items = new List(); + } + + class AuctionListOwnerItems : ClientPacket + { + public AuctionListOwnerItems(WorldPacket packet) : base(packet) { } + + public override void Read() + { + Auctioneer = _worldPacket.ReadPackedGuid(); + Offset = _worldPacket.ReadUInt32(); + } + + public ObjectGuid Auctioneer; + public uint Offset; + } + + class AuctionListItems : ClientPacket + { + public AuctionListItems(WorldPacket packet) : base(packet) { } + + public override void Read() + { + Offset = _worldPacket.ReadUInt32(); + Auctioneer = _worldPacket.ReadPackedGuid(); + MinLevel = _worldPacket.ReadUInt8(); + MaxLevel = _worldPacket.ReadUInt8(); + Quality = _worldPacket.ReadUInt32(); + SortCount = _worldPacket.ReadUInt8(); + KnownPets = new Array(_worldPacket.ReadInt32()); + for (int i = 0; i < KnownPets.Capacity; ++i) + KnownPets[i] = _worldPacket.ReadUInt8(); + + Name = _worldPacket.ReadString(_worldPacket.ReadBits(8)); + ClassFilters = new Array(_worldPacket.ReadBits(3)); + OnlyUsable = _worldPacket.HasBit(); + ExactMatch = _worldPacket.HasBit(); + + for (int i = 0; i < ClassFilters.Capacity; ++i) + { + var classFilter = new ClassFilter(); + classFilter.ItemClass = _worldPacket.ReadInt32(); + classFilter.SubClassFilters = new Array(_worldPacket.ReadBits(5)); + for (int x = 0; x < classFilter.SubClassFilters.Capacity; ++x) + { + ClassFilter.SubClassFilter subClassFilter; + subClassFilter.ItemSubclass = _worldPacket.ReadInt32(); + subClassFilter.InvTypeMask = _worldPacket.ReadUInt32(); + classFilter.SubClassFilters.Add(subClassFilter); + } + ClassFilters.Add(classFilter); + } + + _worldPacket.Skip(4); // DataSize = (SortCount * 2) + for (int i = 0; i < SortCount; i++) + { + AuctionListItems.Sort sort; + sort.Type = _worldPacket.ReadUInt8(); + sort.Direction = _worldPacket.ReadUInt8(); + DataSort.Add(sort); + } + } + + public uint Offset; + public ObjectGuid Auctioneer; + public byte MinLevel = 1; + public byte MaxLevel = 100; + public uint Quality; + public byte SortCount; + public Array KnownPets; + public sbyte MaxPetLevel; + public string Name = ""; + public Array ClassFilters = new Array(7); + public bool ExactMatch = true; + public bool OnlyUsable; + public List DataSort = new List(); + + public struct Sort + { + public byte Type; + public byte Direction; + } + + public class ClassFilter + { + public struct SubClassFilter + { + public int ItemSubclass; + public uint InvTypeMask; + } + + public int ItemClass; + public Array SubClassFilters = new Array(31); + } + } + + class AuctionListPendingSalesResult : ServerPacket + { + public AuctionListPendingSalesResult() : base(ServerOpcodes.AuctionListPendingSalesResult) { } + + public override void Write() + { + _worldPacket.WriteUInt32(Mails.Count); + _worldPacket.WriteInt32(TotalNumRecords); + + foreach (var mail in Mails) + mail.Write(_worldPacket); + } + + public List Mails = new List(); + public int TotalNumRecords; + } + + class AuctionClosedNotification : ServerPacket + { + public AuctionClosedNotification() : base(ServerOpcodes.AuctionClosedNotification) { } + + public override void Write() + { + Info.Write(_worldPacket); + _worldPacket.WriteFloat(ProceedsMailDelay); + _worldPacket.WriteBit(Sold); + _worldPacket.FlushBits(); + } + + public AuctionOwnerNotification Info; + public float ProceedsMailDelay; + public bool Sold = true; + } + + class AuctionOwnerBidNotification : ServerPacket + { + public AuctionOwnerBidNotification() : base(ServerOpcodes.AuctionOwnerBidNotification) { } + + public override void Write() + { + Info.Write(_worldPacket); + _worldPacket.WriteUInt64(MinIncrement); + _worldPacket.WritePackedGuid(Bidder); + } + + public AuctionOwnerNotification Info; + public ObjectGuid Bidder; + public ulong MinIncrement; + } + + class AuctionWonNotification : ServerPacket + { + public AuctionWonNotification() : base(ServerOpcodes.AuctionWonNotification) { } + + public override void Write() + { + Info.Write(_worldPacket); + } + + public AuctionBidderNotification Info; + } + + class AuctionOutBidNotification : ServerPacket + { + public AuctionOutBidNotification() : base(ServerOpcodes.AuctionOutbidNotification) { } + + public override void Write() + { + Info.Write(_worldPacket); + _worldPacket.WriteUInt64(BidAmount); + _worldPacket.WriteUInt64(MinIncrement); + } + + public AuctionBidderNotification Info; + public ulong BidAmount; + public ulong MinIncrement; + } + + class AuctionReplicateResponse : ServerPacket + { + public AuctionReplicateResponse() : base(ServerOpcodes.AuctionReplicateResponse) { } + + public override void Write() + { + _worldPacket.WriteUInt32(Result); + _worldPacket.WriteUInt32(DesiredDelay); + _worldPacket.WriteUInt32(ChangeNumberGlobal); + _worldPacket.WriteUInt32(ChangeNumberCursor); + _worldPacket.WriteUInt32(ChangeNumberTombstone); + _worldPacket.WriteUInt32(Items.Count); + + foreach (var item in Items) + item.Write(_worldPacket); + } + + public uint ChangeNumberCursor; + public uint ChangeNumberGlobal; + public uint DesiredDelay; + public uint ChangeNumberTombstone; + public uint Result; + public List Items = new List(); + } + + public class AuctionItem + { + public void Write(WorldPacket data) + { + Item.Write(data); + data.WriteInt32(Count); + data.WriteInt32(Charges); + data.WriteInt32(Flags); + data.WriteInt32(AuctionItemID); + data.WritePackedGuid(Owner); + data.WriteUInt64(MinBid); + data.WriteUInt64(MinIncrement); + data.WriteUInt64(BuyoutPrice); + data.WriteInt32(DurationLeft); + data.WriteUInt8(DeleteReason); + data.WriteBits(Enchantments.Count, 4); + data.WriteBits(Gems.Count, 2); + data.WriteBit(CensorServerSideInfo); + data.WriteBit(CensorBidInfo); + data.FlushBits(); + + foreach (ItemGemData gem in Gems) + gem.Write(data); + + foreach (ItemEnchantData enchant in Enchantments) + enchant.Write(data); + + if (!CensorServerSideInfo) + { + data.WritePackedGuid(ItemGuid); + data.WritePackedGuid(OwnerAccountID); + data.WriteInt32(EndTime); + } + + if (!CensorBidInfo) + { + data.WritePackedGuid(Bidder); + data.WriteUInt64(BidAmount); + } + } + + public ItemInstance Item; + public int Count; + public int Charges; + public List Enchantments = new List(); + public int Flags; + public int AuctionItemID; + public ObjectGuid Owner; + public ulong MinBid; + public ulong MinIncrement; + public ulong BuyoutPrice; + public int DurationLeft; + public byte DeleteReason; + public bool CensorServerSideInfo; + public bool CensorBidInfo; + public ObjectGuid ItemGuid; + public ObjectGuid OwnerAccountID; + public uint EndTime; + public ObjectGuid Bidder; + public ulong BidAmount; + public List Gems = new List(); + } + + struct AuctionOwnerNotification + { + public void Initialize(AuctionEntry auction, Item item) + { + AuctionItemID = (int)auction.Id; + Item = new ItemInstance(item); + BidAmount = auction.bid; + } + + public void Write(WorldPacket data) + { + data.WriteInt32(AuctionItemID); + data.WriteUInt64(BidAmount); + Item.Write(data); + } + + public int AuctionItemID; + public ulong BidAmount; + public ItemInstance Item; + } + + struct AuctionBidderNotification + { + public void Initialize(AuctionEntry auction, Item item) + { + AuctionItemID = (int)auction.Id; + Item = new ItemInstance(item); + Bidder = ObjectGuid.Create(HighGuid.Player, auction.bidder); + } + + public void Write(WorldPacket data) + { + data.WriteInt32(AuctionItemID); + data.WritePackedGuid(Bidder); + Item.Write(data); + } + + public int AuctionItemID; + public ObjectGuid Bidder; + public ItemInstance Item; + } +} diff --git a/Game/Network/Packets/AuthenticationPackets.cs b/Game/Network/Packets/AuthenticationPackets.cs new file mode 100644 index 000000000..307cd865f --- /dev/null +++ b/Game/Network/Packets/AuthenticationPackets.cs @@ -0,0 +1,444 @@ +/* + * Copyright (C) 2012-2017 CypherCore + * + * 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, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +using Framework.Constants; +using Framework.Cryptography; +using Framework.Dynamic; +using Framework.IO; +using Game.DataStorage; +using System; +using System.Collections.Generic; +using System.Net; + +namespace Game.Network.Packets +{ + class Ping : ClientPacket + { + public Ping(WorldPacket packet) : base(packet) { } + + public override void Read() + { + Serial = _worldPacket.ReadUInt32(); + Latency = _worldPacket.ReadUInt32(); + } + + public uint Serial; + public uint Latency; + } + + class Pong : ServerPacket + { + public Pong(uint serial) : base(ServerOpcodes.Pong) + { + Serial = serial; + } + + public override void Write() + { + _worldPacket.WriteUInt32(Serial); + } + + uint Serial; + } + + class AuthChallenge : ServerPacket + { + public AuthChallenge() : base(ServerOpcodes.AuthChallenge) { } + + public override void Write() + { + _worldPacket.WriteBytes(DosChallenge); + _worldPacket.WriteBytes(Challenge); + _worldPacket.WriteUInt8(DosZeroBits); + } + + public byte[] Challenge = new byte[16]; + public byte[] DosChallenge = new byte[32]; // Encryption seeds + public byte DosZeroBits; + } + + class AuthSession : ClientPacket + { + public AuthSession(WorldPacket packet) : base(packet) { } + + public override void Read() + { + uint realmJoinTicketSize; + + DosResponse = _worldPacket.ReadUInt64(); + Build = _worldPacket.ReadUInt16(); + BuildType = _worldPacket.ReadInt8(); + RegionID = _worldPacket.ReadUInt32(); + BattlegroupID = _worldPacket.ReadUInt32(); + RealmID = _worldPacket.ReadUInt32(); + + for (var i = 0; i < LocalChallenge.GetLimit(); ++i) + LocalChallenge.Add(_worldPacket.ReadUInt8()); + + Digest = _worldPacket.ReadBytes(24); + + UseIPv6 = _worldPacket.HasBit(); + realmJoinTicketSize = _worldPacket.ReadUInt32(); + if (realmJoinTicketSize != 0) + RealmJoinTicket = _worldPacket.ReadString(realmJoinTicketSize); + } + + public ushort Build; + public sbyte BuildType; + public uint RegionID; + public uint BattlegroupID; + public uint RealmID; + public Array LocalChallenge = new Array(16); + public byte[] Digest = new byte[24]; + public ulong DosResponse; + public string RealmJoinTicket; + public bool UseIPv6; + } + + class AuthResponse : ServerPacket + { + public AuthResponse() : base(ServerOpcodes.AuthResponse) + { + WaitInfo = new Optional(); + SuccessInfo = new Optional(); + } + + public override void Write() + { + _worldPacket.WriteUInt32(Result); + _worldPacket.WriteBit(SuccessInfo.HasValue); + _worldPacket.WriteBit(WaitInfo.HasValue); + _worldPacket.FlushBits(); + + if (SuccessInfo.HasValue) + { + _worldPacket.WriteUInt32(SuccessInfo.Value.VirtualRealmAddress); + _worldPacket.WriteUInt32(SuccessInfo.Value.VirtualRealms.Count); + _worldPacket.WriteUInt32(SuccessInfo.Value.TimeRested); + _worldPacket.WriteUInt8(SuccessInfo.Value.ActiveExpansionLevel); + _worldPacket.WriteUInt8(SuccessInfo.Value.AccountExpansionLevel); + _worldPacket.WriteUInt32(SuccessInfo.Value.TimeSecondsUntilPCKick); + _worldPacket.WriteUInt32(SuccessInfo.Value.AvailableRaces.Count); + _worldPacket.WriteUInt32(SuccessInfo.Value.AvailableClasses.Count); + _worldPacket.WriteUInt32(SuccessInfo.Value.Templates.Count); + _worldPacket.WriteUInt32(SuccessInfo.Value.CurrencyID); + _worldPacket.WriteUInt32(SuccessInfo.Value.Time); + + foreach (var race in SuccessInfo.Value.AvailableRaces) + { + _worldPacket.WriteUInt8(race.Key); /// the current race + _worldPacket.WriteUInt8(race.Value); /// the required Expansion + } + + foreach (var klass in SuccessInfo.Value.AvailableClasses) + { + _worldPacket.WriteUInt8(klass.Key); /// the current class + _worldPacket.WriteUInt8(klass.Value); /// the required Expansion + } + + _worldPacket.WriteBit(SuccessInfo.Value.IsExpansionTrial); + _worldPacket.WriteBit(SuccessInfo.Value.ForceCharacterTemplate); + _worldPacket.WriteBit(SuccessInfo.Value.NumPlayersHorde.HasValue); + _worldPacket.WriteBit(SuccessInfo.Value.NumPlayersAlliance.HasValue); + _worldPacket.FlushBits(); + + { + _worldPacket.WriteUInt32(SuccessInfo.Value.Billing.BillingPlan); + _worldPacket.WriteUInt32(SuccessInfo.Value.Billing.TimeRemain); + // 3x same bit is not a mistake - preserves legacy client behavior of BillingPlanFlags::SESSION_IGR + _worldPacket.WriteBit(SuccessInfo.Value.Billing.InGameRoom); // inGameRoom check in function checking which lua event to fire when remaining time is near end - BILLING_NAG_DIALOG vs IGR_BILLING_NAG_DIALOG + _worldPacket.WriteBit(SuccessInfo.Value.Billing.InGameRoom); // inGameRoom lua return from Script_GetBillingPlan + _worldPacket.WriteBit(SuccessInfo.Value.Billing.InGameRoom); // not used anywhere in the client + _worldPacket.FlushBits(); + } + + if (SuccessInfo.Value.NumPlayersHorde.HasValue) + _worldPacket.WriteUInt16(SuccessInfo.Value.NumPlayersHorde.Value); + + if (SuccessInfo.Value.NumPlayersAlliance.HasValue) + _worldPacket.WriteUInt16(SuccessInfo.Value.NumPlayersAlliance.Value); + + foreach (VirtualRealmInfo virtualRealm in SuccessInfo.Value.VirtualRealms) + virtualRealm.Write(_worldPacket); + + foreach (var templat in SuccessInfo.Value.Templates) + { + _worldPacket.WriteUInt32(templat.TemplateSetId); + _worldPacket.WriteUInt32(templat.Classes.Count); + foreach (var templateClass in templat.Classes) + { + _worldPacket.WriteUInt8(templateClass.ClassID); + _worldPacket.WriteUInt8(templateClass.FactionGroup); + } + + _worldPacket.WriteBits(templat.Name.Length, 7); + _worldPacket.WriteBits(templat.Description.Length, 10); + _worldPacket.FlushBits(); + + _worldPacket.WriteString(templat.Name); + _worldPacket.WriteString(templat.Description); + } + } + + if (WaitInfo.HasValue) + WaitInfo.Value.Write(_worldPacket); + } + + public Optional SuccessInfo; // contains the packet data in case that it has account information (It is never set when WaitInfo is set), otherwise its contents are undefined. + public Optional WaitInfo; // contains the queue wait information in case the account is in the login queue. + public BattlenetRpcErrorCode Result; // the result of the authentication process, possible values are @ref BattlenetRpcErrorCode + + public class AuthSuccessInfo + { + public byte AccountExpansionLevel; // the current expansion of this account, the possible values are in @ref Expansions + public byte ActiveExpansionLevel; // the current server expansion, the possible values are in @ref Expansions + public uint TimeRested; // affects the return value of the GetBillingTimeRested() client API call, it is the number of seconds you have left until the experience points and loot you receive from creatures and quests is reduced. It is only used in the Asia region in retail, it's not implemented in TC and will probably never be. + + public uint VirtualRealmAddress; // a special identifier made from the Index, BattleGroup and Region. @todo implement + public uint TimeSecondsUntilPCKick; // @todo research + public uint CurrencyID; // this is probably used for the ingame shop. @todo implement + public uint Time; + + public BillingInfo Billing; + + public List VirtualRealms = new List(); // list of realms connected to this one (inclusive) @todo implement + public List Templates = new List(); // list of pre-made character templates. @todo implement + + public Dictionary AvailableClasses; // the minimum AccountExpansion required to select the classes + public Dictionary AvailableRaces; // the minimum AccountExpansion required to select the races + + public bool IsExpansionTrial; + public bool ForceCharacterTemplate; // forces the client to always use a character template when creating a new character. @see Templates. @todo implement + public Optional NumPlayersHorde; // number of horde players in this realm. @todo implement + public Optional NumPlayersAlliance; // number of alliance players in this realm. @todo implement + + public struct BillingInfo + { + public uint BillingPlan; + public uint TimeRemain; + public bool InGameRoom; + } + } + } + + class WaitQueueUpdate : ServerPacket + { + public WaitQueueUpdate() : base(ServerOpcodes.WaitQueueUpdate) { } + + public override void Write() + { + WaitInfo.Write(_worldPacket); + } + + public AuthWaitInfo WaitInfo; + } + + class WaitQueueFinish : ServerPacket + { + public WaitQueueFinish() : base(ServerOpcodes.WaitQueueFinish) { } + + public override void Write() { } + } + + class ConnectTo : ServerPacket + { + public ConnectTo() : base(ServerOpcodes.ConnectTo) + { + Payload = new ConnectPayload(); + Payload.PanamaKey = "F41DCB2D728CF3337A4FF338FA89DB01BBBE9C3B65E9DA96268687353E48B94C".ToByteArray(); + Payload.Adler32 = 0xA0A66C10; + + Crypt = new RsaCrypt(); + Crypt.InitializeEncryption(RsaStore.P, RsaStore.Q, RsaStore.DP, RsaStore.DQ, RsaStore.InverseQ); + } + + public override void Write() + { + byte[] port = BitConverter.GetBytes((ushort)Payload.Where.Port); + byte[] address = new byte[16]; + byte addressType = 3; + if (Payload.Where.AddressFamily == System.Net.Sockets.AddressFamily.InterNetwork) + { + Buffer.BlockCopy(Payload.Where.Address.GetAddressBytes(), 0, address, 0, 4); + addressType = 1; + } + else + { + Buffer.BlockCopy(Payload.Where.Address.GetAddressBytes(), 0, address, 0, 16); + addressType = 2; + } + + HmacHash hmacHash = new HmacHash(RsaStore.WherePacketHmac); + hmacHash.Process(address, 16); + hmacHash.Process(BitConverter.GetBytes(addressType), 1); + hmacHash.Process(port, 2); + hmacHash.Process(Haiku); + hmacHash.Process(Payload.PanamaKey, 32); + hmacHash.Process(PiDigits, 108); + hmacHash.Finish(BitConverter.GetBytes(Payload.XorMagic), 1); + + ByteBuffer payload = new ByteBuffer(); + payload.WriteUInt32(Payload.Adler32); + payload.WriteUInt8(addressType); + payload.WriteBytes(address, 16); + payload.WriteUInt16(Payload.Where.Port); + payload.WriteString(Haiku); + payload.WriteBytes(Payload.PanamaKey, 32); + payload.WriteBytes(PiDigits, 108); + payload.WriteUInt8(Payload.XorMagic); + payload.WriteBytes(hmacHash.Digest); + + _worldPacket.WriteUInt64(Key); + _worldPacket.WriteUInt32(Serial); + _worldPacket.WriteBytes(Crypt.Encrypt(payload.GetData()), 256); + _worldPacket.WriteUInt8(Con); + } + + public ulong Key; + public ConnectToSerial Serial; + public ConnectPayload Payload; + public byte Con; + + public string Haiku = "An island of peace\nCorruption is brought ashore\nPandarens will rise\n\0\0\0"; + public byte[] PiDigits = { 0x31, 0x41, 0x59, 0x26, 0x53, 0x58, 0x97, 0x93, 0x23, 0x84, 0x62, 0x64, 0x33, 0x83, 0x27, 0x95, 0x02, 0x88, 0x41, 0x97, + 0x16, 0x93, 0x99, 0x37, 0x51, 0x05, 0x82, 0x09, 0x74, 0x94, 0x45, 0x92, 0x30, 0x78, 0x16, 0x40, 0x62, 0x86, 0x20, 0x89, + 0x98, 0x62, 0x80, 0x34, 0x82, 0x53, 0x42, 0x11, 0x70, 0x67, 0x98, 0x21, 0x48, 0x08, 0x65, 0x13, 0x28, 0x23, 0x06, 0x64, + 0x70, 0x93, 0x84, 0x46, 0x09, 0x55, 0x05, 0x82, 0x23, 0x17, 0x25, 0x35, 0x94, 0x08, 0x12, 0x84, 0x81, 0x11, 0x74, 0x50, + 0x28, 0x41, 0x02, 0x70, 0x19, 0x38, 0x52, 0x11, 0x05, 0x55, 0x96, 0x44, 0x62, 0x29, 0x48, 0x95, 0x49, 0x30, 0x38, 0x19, + 0x64, 0x42, 0x88, 0x10, 0x97, 0x56, 0x65, 0x93, 0x34, 0x46, 0x12, 0x84, 0x75, 0x64, 0x82, 0x33, 0x78, 0x67, 0x83, 0x16, + 0x52, 0x71, 0x20, 0x19, 0x09, 0x14, 0x56, 0x48, 0x56, 0x69 }; + + public class ConnectPayload + { + public IPEndPoint Where; + public uint Adler32; + public byte XorMagic = 0x2A; + public byte[] PanamaKey = new byte[32]; + } + + RsaCrypt Crypt; + } + + class AuthContinuedSession : ClientPacket + { + public AuthContinuedSession(WorldPacket packet) : base(packet) { } + + public override void Read() + { + DosResponse = _worldPacket.ReadUInt64(); + Key = _worldPacket.ReadUInt64(); + LocalChallenge = _worldPacket.ReadBytes(16); + Digest = _worldPacket.ReadBytes(24); + } + + public ulong DosResponse; + public ulong Key; + public byte[] LocalChallenge = new byte[16]; + public byte[] Digest = new byte[24]; + } + + class ResumeComms : ServerPacket + { + public ResumeComms(ConnectionType connection) : base(ServerOpcodes.ResumeComms, connection) { } + + public override void Write() { } + } + + class ConnectToFailed : ClientPacket + { + public ConnectToFailed(WorldPacket packet) : base(packet) { } + + public override void Read() + { + Serial = (ConnectToSerial)_worldPacket.ReadUInt32(); + Con = _worldPacket.ReadUInt8(); + } + + public ConnectToSerial Serial; + byte Con; + } + + class EnableEncryption : ServerPacket + { + public EnableEncryption() : base(ServerOpcodes.EnableEncryption) { } + + public override void Write() { } + } + + //Structs + public struct AuthWaitInfo + { + public void Write(WorldPacket data) + { + data.WriteUInt32(WaitCount); + data.WriteUInt32(WaitTime); + data.WriteBit(HasFCM); + data.FlushBits(); + } + + public uint WaitCount; // position of the account in the login queue + public uint WaitTime; // Wait time in login queue in minutes, if sent queued and this value is 0 client displays "unknown time" + public bool HasFCM; // true if the account has a forced character migration pending. @todo implement + } + + struct VirtualRealmNameInfo + { + public VirtualRealmNameInfo(bool isHomeRealm, bool isInternalRealm, string realmNameActual, string realmNameNormalized) + { + IsLocal = isHomeRealm; + IsInternalRealm = isInternalRealm; + RealmNameActual = realmNameActual; + RealmNameNormalized = realmNameNormalized; + } + + public void Write(WorldPacket data) + { + data.WriteBit(IsLocal); + data.WriteBit(IsInternalRealm); + data.WriteBits(RealmNameActual.Length, 8); + data.WriteBits(RealmNameNormalized.Length, 8); + data.FlushBits(); + + data.WriteString(RealmNameActual); + data.WriteString(RealmNameNormalized); + } + + public bool IsLocal; // true if the realm is the same as the account's home realm + public bool IsInternalRealm; // @todo research + public string RealmNameActual; // the name of the realm + public string RealmNameNormalized; // the name of the realm without spaces + } + + struct VirtualRealmInfo + { + public VirtualRealmInfo(uint realmAddress, bool isHomeRealm, bool isInternalRealm, string realmNameActual, string realmNameNormalized) + { + + RealmAddress = realmAddress; + RealmNameInfo = new VirtualRealmNameInfo(isHomeRealm, isInternalRealm, realmNameActual, realmNameNormalized); + } + + public void Write(WorldPacket data) + { + data.WriteUInt32(RealmAddress); + RealmNameInfo.Write(data); + } + + public uint RealmAddress; // the virtual address of this realm, constructed as RealmHandle::Region << 24 | RealmHandle::Battlegroup << 16 | RealmHandle::Index + public VirtualRealmNameInfo RealmNameInfo; + } +} \ No newline at end of file diff --git a/Game/Network/Packets/BankPackets.cs b/Game/Network/Packets/BankPackets.cs new file mode 100644 index 000000000..858496f52 --- /dev/null +++ b/Game/Network/Packets/BankPackets.cs @@ -0,0 +1,65 @@ +/* + * Copyright (C) 2012-2017 CypherCore + * + * 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, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +using Game.Entities; + +namespace Game.Network.Packets +{ + public class AutoBankItem : ClientPacket + { + public InvUpdate Inv; + public byte Bag; + public byte Slot; + + public AutoBankItem(WorldPacket packet) : base(packet) { } + + public override void Read() + { + Inv = new InvUpdate(_worldPacket); + Bag = _worldPacket.ReadUInt8(); + Slot = _worldPacket.ReadUInt8(); + } + } + + public class AutoStoreBankItem : ClientPacket + { + public InvUpdate Inv; + public byte Bag; + public byte Slot; + + public AutoStoreBankItem(WorldPacket packet) : base(packet) { } + + public override void Read() + { + Inv = new InvUpdate(_worldPacket); + Bag = _worldPacket.ReadUInt8(); + Slot = _worldPacket.ReadUInt8(); + } + } + + public class BuyBankSlot : ClientPacket + { + public BuyBankSlot(WorldPacket packet) : base(packet) { } + + public override void Read() + { + Guid = _worldPacket.ReadPackedGuid(); + } + + public ObjectGuid Guid; + } +} diff --git a/Game/Network/Packets/BattleGroundPackets.cs b/Game/Network/Packets/BattleGroundPackets.cs new file mode 100644 index 000000000..8234718bf --- /dev/null +++ b/Game/Network/Packets/BattleGroundPackets.cs @@ -0,0 +1,601 @@ +/* + * Copyright (C) 2012-2017 CypherCore + * + * 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, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +using Framework.Constants; +using Framework.Dynamic; +using Framework.GameMath; +using Game.Entities; +using System; +using System.Collections.Generic; + +namespace Game.Network.Packets +{ + public class PVPSeason : ServerPacket + { + public PVPSeason() : base(ServerOpcodes.PvpSeason) { } + + public override void Write() + { + _worldPacket.WriteUInt32(CurrentSeason); + _worldPacket.WriteUInt32(PreviousSeason); + } + + public uint PreviousSeason = 0; + public uint CurrentSeason = 0; + } + + public class AreaSpiritHealerQuery : ClientPacket + { + public AreaSpiritHealerQuery(WorldPacket packet) : base(packet) { } + + public override void Read() + { + HealerGuid = _worldPacket.ReadPackedGuid(); + } + + public ObjectGuid HealerGuid; + } + + public class AreaSpiritHealerQueue : ClientPacket + { + public AreaSpiritHealerQueue(WorldPacket packet) : base(packet) { } + + public override void Read() + { + HealerGuid = _worldPacket.ReadPackedGuid(); + } + + public ObjectGuid HealerGuid; + } + + public class HearthAndResurrect : ClientPacket + { + public HearthAndResurrect(WorldPacket packet) : base(packet) { } + + public override void Read() { } + } + + class PVPLogDataRequest : ClientPacket + { + public PVPLogDataRequest(WorldPacket packet) : base(packet) { } + + public override void Read() { } + } + + public class PVPLogData : ServerPacket + { + public PVPLogData() : base(ServerOpcodes.PvpLogData, ConnectionType.Instance) { } + + public override void Write() + { + _worldPacket.WriteBit(Ratings.HasValue); + _worldPacket.WriteBit(Winner.HasValue); + _worldPacket.WriteUInt32(Players.Count); + foreach (var id in PlayerCount) + _worldPacket.WriteInt8(id); + + if (Ratings.HasValue) + Ratings.Value.Write(_worldPacket); + + if (Winner.HasValue) + _worldPacket.WriteUInt8(Winner.Value); + + foreach (PlayerData player in Players) + player.Write(_worldPacket); + } + + public Optional Winner; + public List Players = new List(); + public Optional Ratings; + public sbyte[] PlayerCount = new sbyte[2]; + + public class RatingData + { + public void Write(WorldPacket data) + { + foreach (var id in Prematch) + data.WriteInt32(id); + + foreach (var id in Postmatch) + data.WriteInt32(id); + + foreach (var id in PrematchMMR) + data.WriteInt32(id); + } + + public int[] Prematch = new int[2]; + public int[] Postmatch = new int[2]; + public int[] PrematchMMR = new int[2]; + } + + public struct HonorData + { + public void Write(WorldPacket data) + { + data.WriteUInt32(HonorKills); + data.WriteUInt32(Deaths); + data.WriteUInt32(ContributionPoints); + } + + public uint HonorKills; + public uint Deaths; + public uint ContributionPoints; + } + + public class PlayerData + { + public void Write(WorldPacket data) + { + data.WritePackedGuid(PlayerGUID); + data.WriteUInt32(Kills); + data.WriteUInt32(DamageDone); + data.WriteUInt32(HealingDone); + data.WriteUInt32(Stats.Count); + data.WriteInt32(PrimaryTalentTree); + data.WriteInt32(PrimaryTalentTreeNameIndex); + data.WriteInt32(PlayerRace); + if (!Stats.Empty()) + Stats.ForEach(id => data.WriteInt32(id)); + + data.WriteBit(Faction); + data.WriteBit(IsInWorld); + data.WriteBit(Honor.HasValue); + data.WriteBit(PreMatchRating.HasValue); + data.WriteBit(RatingChange.HasValue); + data.WriteBit(PreMatchMMR.HasValue); + data.WriteBit(MmrChange.HasValue); + data.FlushBits(); + + if (Honor.HasValue) + Honor.Value.Write(data); + + if (PreMatchRating.HasValue) + data.WriteUInt32(PreMatchRating.Value); + + if (RatingChange.HasValue) + data.WriteInt32(RatingChange.Value); + + if (PreMatchMMR.HasValue) + data.WriteUInt32(PreMatchMMR.Value); + + if (MmrChange.HasValue) + data.WriteInt32(MmrChange.Value); + } + + public ObjectGuid PlayerGUID; + public uint Kills; + public byte Faction; + public bool IsInWorld; + public Optional Honor; + public uint DamageDone; + public uint HealingDone; + public Optional PreMatchRating; + public Optional RatingChange; + public Optional PreMatchMMR; + public Optional MmrChange; + public List Stats = new List(); + public int PrimaryTalentTree; + public int PrimaryTalentTreeNameIndex; // controls which name field from ChrSpecialization.dbc will be sent to lua + public Race PlayerRace; + public uint Prestige; + } + } + + public class BattlefieldStatusNone : ServerPacket + { + public BattlefieldStatusNone() : base(ServerOpcodes.BattlefieldStatusNone) { } + + public override void Write() + { + Ticket.Write(_worldPacket); + } + + public RideTicket Ticket; + } + + public class BattlefieldStatusNeedConfirmation : ServerPacket + { + public BattlefieldStatusNeedConfirmation() : base(ServerOpcodes.BattlefieldStatusNeedConfirmation) { } + + public override void Write() + { + Hdr.Write(_worldPacket); + _worldPacket.WriteUInt32(Mapid); + _worldPacket.WriteUInt32(Timeout); + _worldPacket.WriteUInt8(Role); + } + + public uint Timeout; + public uint Mapid; + public BattlefieldStatusHeader Hdr; + public byte Role; + } + + public class BattlefieldStatusActive : ServerPacket + { + public BattlefieldStatusActive() : base(ServerOpcodes.BattlefieldStatusActive) { } + + public override void Write() + { + Hdr.Write(_worldPacket); + _worldPacket.WriteUInt32(Mapid); + _worldPacket.WriteUInt32(ShutdownTimer); + _worldPacket.WriteUInt32(StartTimer); + _worldPacket.WriteBit(ArenaFaction); + _worldPacket.WriteBit(LeftEarly); + _worldPacket.FlushBits(); + } + + public BattlefieldStatusHeader Hdr; + public uint ShutdownTimer; + public byte ArenaFaction; + public bool LeftEarly; + public uint StartTimer; + public uint Mapid; + } + + public class BattlefieldStatusQueued : ServerPacket + { + public BattlefieldStatusQueued() : base(ServerOpcodes.BattlefieldStatusQueued) { } + + public override void Write() + { + Hdr.Write(_worldPacket); + _worldPacket.WriteUInt32(AverageWaitTime); + _worldPacket.WriteUInt32(WaitTime); + _worldPacket.WriteBit(AsGroup); + _worldPacket.WriteBit(EligibleForMatchmaking); + _worldPacket.WriteBit(SuspendedQueue); + _worldPacket.FlushBits(); + } + + public uint AverageWaitTime; + public BattlefieldStatusHeader Hdr; + public bool AsGroup; + public bool SuspendedQueue; + public bool EligibleForMatchmaking; + public uint WaitTime; + } + + public class BattlefieldStatusFailed : ServerPacket + { + public BattlefieldStatusFailed() : base(ServerOpcodes.BattlefieldStatusFailed) { } + + public override void Write() + { + Ticket.Write(_worldPacket); + _worldPacket.WriteUInt64(QueueID); + _worldPacket.WriteUInt32(Reason); + _worldPacket.WritePackedGuid(ClientID); + } + + public ulong QueueID; + public ObjectGuid ClientID; + public int Reason; + public RideTicket Ticket; + } + + class BattlemasterJoin : ClientPacket + { + public BattlemasterJoin(WorldPacket packet) : base(packet) { } + + public override void Read() + { + QueueID = _worldPacket.ReadUInt64(); + Roles = _worldPacket.ReadUInt8(); + BlacklistMap[0] = _worldPacket.ReadInt32(); + BlacklistMap[1] = _worldPacket.ReadInt32(); + JoinAsGroup = _worldPacket.HasBit(); + } + + public bool JoinAsGroup = false; + public byte Roles = 0; + public ulong QueueID = 0; + public int[] BlacklistMap = new int[2]; + } + + class BattlemasterJoinArena : ClientPacket + { + public BattlemasterJoinArena(WorldPacket packet) : base(packet) { } + + public override void Read() + { + TeamSizeIndex = _worldPacket.ReadUInt8(); + Roles = _worldPacket.ReadUInt8(); + } + + public byte TeamSizeIndex; + public byte Roles; + } + + class BattlefieldLeave : ClientPacket + { + public BattlefieldLeave(WorldPacket packet) : base(packet) { } + + public override void Read() { } + } + + class BattlefieldPort : ClientPacket + { + public BattlefieldPort(WorldPacket packet) : base(packet) { } + + public override void Read() + { + Ticket.Read(_worldPacket); + AcceptedInvite = _worldPacket.HasBit(); + } + + public RideTicket Ticket; + public bool AcceptedInvite; + } + + class BattlefieldListRequest : ClientPacket + { + public BattlefieldListRequest(WorldPacket packet) : base(packet) { } + + public override void Read() + { + ListID = _worldPacket.ReadInt32(); + } + + public int ListID; + } + + class BattlefieldList : ServerPacket + { + public BattlefieldList() : base(ServerOpcodes.BattlefieldList) { } + + public override void Write() + { + _worldPacket.WritePackedGuid(BattlemasterGuid); + _worldPacket.WriteInt32(BattlemasterListID); + _worldPacket.WriteUInt8(MinLevel); + _worldPacket.WriteUInt8(MaxLevel); + _worldPacket.WriteUInt32(Battlefields.Count); + if (!Battlefields.Empty()) + Battlefields.ForEach(field => _worldPacket.WriteInt32(field)); + + _worldPacket.WriteBit(PvpAnywhere); + _worldPacket.WriteBit(HasRandomWinToday); + _worldPacket.FlushBits(); + } + + public ObjectGuid BattlemasterGuid; + public int BattlemasterListID; + public byte MinLevel; + public byte MaxLevel; + public List Battlefields = new List(); // Players cannot join a specific Battleground instance anymore - this is always empty + public bool PvpAnywhere; + public bool HasRandomWinToday; + } + + class GetPVPOptionsEnabled : ClientPacket + { + public GetPVPOptionsEnabled(WorldPacket packet) : base(packet) { } + + public override void Read() { } + } + + class PVPOptionsEnabled : ServerPacket + { + public PVPOptionsEnabled() : base(ServerOpcodes.PvpOptionsEnabled) { } + + public override void Write() + { + _worldPacket.WriteBit(RatedBattlegrounds); + _worldPacket.WriteBit(PugBattlegrounds); + _worldPacket.WriteBit(WargameBattlegrounds); + _worldPacket.WriteBit(WargameArenas); + _worldPacket.WriteBit(RatedArenas); + _worldPacket.WriteBit(ArenaSkirmish); + _worldPacket.FlushBits(); + } + + public bool WargameArenas; + public bool RatedArenas; + public bool WargameBattlegrounds; + public bool ArenaSkirmish; + public bool PugBattlegrounds; + public bool RatedBattlegrounds; + } + + class RequestBattlefieldStatus : ClientPacket + { + public RequestBattlefieldStatus(WorldPacket packet) : base(packet) { } + + public override void Read() { } + } + + class ReportPvPPlayerAFK : ClientPacket + { + public ReportPvPPlayerAFK(WorldPacket packet) : base(packet) { } + + public override void Read() + { + Offender = _worldPacket.ReadPackedGuid(); + } + + public ObjectGuid Offender; + } + + class ReportPvPPlayerAFKResult : ServerPacket + { + public ReportPvPPlayerAFKResult() : base(ServerOpcodes.ReportPvpPlayerAfkResult, ConnectionType.Instance) { } + + public override void Write() + { + _worldPacket.WritePackedGuid(Offender); + _worldPacket.WriteUInt8(Result); + _worldPacket.WriteUInt8(NumBlackMarksOnOffender); + _worldPacket.WriteUInt8(NumPlayersIHaveReported); + } + + public ObjectGuid Offender; + public byte NumPlayersIHaveReported = 0; + public byte NumBlackMarksOnOffender = 0; + public ResultCode Result = ResultCode.GenericFailure; + + public enum ResultCode + { + Success = 0, + GenericFailure = 1, // there are more error codes but they are impossible to receive without modifying the client + AFKSystemEnabled = 5, + AFKSystemDisabled = 6 + } + } + + class BattlegroundPlayerPositions : ServerPacket + { + public BattlegroundPlayerPositions() : base(ServerOpcodes.BattlegroundPlayerPositions, ConnectionType.Instance) { } + + public override void Write() + { + _worldPacket .WriteUInt32(FlagCarriers.Count); + FlagCarriers.ForEach(pos => pos.Write(_worldPacket)); + } + + public List FlagCarriers = new List(); + } + + class BattlegroundPlayerJoined : ServerPacket + { + public BattlegroundPlayerJoined() : base(ServerOpcodes.BattlegroundPlayerJoined, ConnectionType.Instance) { } + + public override void Write() + { + _worldPacket.WritePackedGuid(Guid); + } + + public ObjectGuid Guid; + } + + class BattlegroundPlayerLeft : ServerPacket + { + public BattlegroundPlayerLeft() : base(ServerOpcodes.BattlegroundPlayerLeft, ConnectionType.Instance) { } + + public override void Write() + { + _worldPacket.WritePackedGuid(Guid); + } + + public ObjectGuid Guid; + } + + class DestroyArenaUnit : ServerPacket + { + public DestroyArenaUnit() : base(ServerOpcodes.DestroyArenaUnit) { } + + public override void Write() + { + _worldPacket.WritePackedGuid(Guid); + } + + public ObjectGuid Guid; + } + + class RequestPVPRewards : ClientPacket + { + public RequestPVPRewards(WorldPacket packet) : base(packet) { } + + public override void Read() { } + } + + public class RequestPVPRewardsResponse : ServerPacket + { + public RequestPVPRewardsResponse() : base(ServerOpcodes.RequestPvpRewardsResponse) { } + + public override void Write() + { + throw new NotImplementedException(); + } + + public uint RatedRewardPointsThisWeek; + public uint ArenaRewardPointsThisWeek; + public uint RatedMaxRewardPointsThisWeek; + public uint ArenaRewardPoints; + public uint RandomRewardPointsThisWeek; + public uint ArenaMaxRewardPointsThisWeek; + public uint RatedRewardPoints; + public uint MaxRewardPointsThisWeek; + public uint RewardPointsThisWeek; + public uint RandomMaxRewardPointsThisWeek; + } + + class RequestRatedBattlefieldInfo : ClientPacket + { + public RequestRatedBattlefieldInfo(WorldPacket packet) : base(packet) { } + + public override void Read() { } + } + + public class ArenaError : ServerPacket + { + public ArenaError() : base(ServerOpcodes.ArenaError) { } + + public override void Write() + { + _worldPacket.WriteUInt32(ErrorType); + if (ErrorType == ArenaErrorType.NoTeam) + _worldPacket.WriteUInt8(TeamSize); + } + + public ArenaErrorType ErrorType; + public byte TeamSize; + } + + //Structs + public struct BattlefieldStatusHeader + { + public void Write(WorldPacket data) + { + Ticket.Write(data); + data.WriteUInt64(QueueID); + data.WriteUInt8(RangeMin); + data.WriteUInt8(RangeMax); + data.WriteUInt8(TeamSize); + data.WriteUInt32(InstanceID); + data.WriteBit(RegisteredMatch); + data.WriteBit(TournamentRules); + data.FlushBits(); + } + + public RideTicket Ticket; + public ulong QueueID; + public byte RangeMin; + public byte RangeMax; + public byte TeamSize; + public uint InstanceID; + public bool RegisteredMatch; + public bool TournamentRules; + } + + public struct BattlegroundPlayerPosition + { + public void Write(WorldPacket data) + { + data.WritePackedGuid(Guid); + data.WriteVector2(Pos); + data.WriteInt8(IconID); + data.WriteInt8(ArenaSlot); + } + + public ObjectGuid Guid; + public Vector2 Pos; + public sbyte IconID; + public sbyte ArenaSlot; + } +} diff --git a/Game/Network/Packets/BattlePetPackets.cs b/Game/Network/Packets/BattlePetPackets.cs new file mode 100644 index 000000000..065c71e2f --- /dev/null +++ b/Game/Network/Packets/BattlePetPackets.cs @@ -0,0 +1,301 @@ +/* + * Copyright (C) 2012-2017 CypherCore + * + * 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, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +using Framework.Constants; +using Framework.Dynamic; +using Game.Entities; +using System; +using System.Collections.Generic; + +namespace Game.Network.Packets +{ + class BattlePetJournal : ServerPacket + { + public BattlePetJournal() : base(ServerOpcodes.BattlePetJournal) { } + + public override void Write() + { + _worldPacket.WriteUInt16(Trap); + _worldPacket.WriteInt32(Slots.Count); + _worldPacket.WriteInt32(Pets.Count); + _worldPacket.WriteInt32(MaxPets); + _worldPacket.WriteBit(HasJournalLock); + _worldPacket.FlushBits(); + + foreach (var slot in Slots) + slot.Write(_worldPacket); + + foreach (var pet in Pets) + pet.Write(_worldPacket); + } + + public ushort Trap; + bool HasJournalLock = true; + public List Slots = new List(); + public List Pets = new List(); + int MaxPets = 1000; + } + + class BattlePetJournalLockAcquired : ServerPacket + { + public BattlePetJournalLockAcquired() : base(ServerOpcodes.BattlePetJournalLockAcquired) { } + + public override void Write() { } + } + + class BattlePetRequestJournal : ClientPacket + { + public BattlePetRequestJournal(WorldPacket packet) : base(packet) { } + + public override void Read() { } + } + + class BattlePetUpdates : ServerPacket + { + public BattlePetUpdates() : base(ServerOpcodes.BattlePetUpdates) { } + + public override void Write() + { + _worldPacket.WriteInt32(Pets.Count); + _worldPacket.WriteBit(PetAdded); + _worldPacket.FlushBits(); + + foreach (var pet in Pets) + pet.Write(_worldPacket); + } + + public List Pets = new List(); + public bool PetAdded; + } + + class PetBattleSlotUpdates : ServerPacket + { + public PetBattleSlotUpdates() : base(ServerOpcodes.PetBattleSlotUpdates) { } + + public override void Write() + { + _worldPacket.WriteInt32(Slots.Count); + _worldPacket.WriteBit(NewSlot); + _worldPacket.WriteBit(AutoSlotted); + _worldPacket.FlushBits(); + + foreach (var slot in Slots) + slot.Write(_worldPacket); + } + + public List Slots = new List(); + public bool AutoSlotted; + public bool NewSlot; + } + + class BattlePetSetBattleSlot : ClientPacket + { + public BattlePetSetBattleSlot(WorldPacket packet) : base(packet) { } + + public override void Read() + { + PetGuid = _worldPacket.ReadPackedGuid(); + Slot = _worldPacket.ReadUInt8(); + } + + public ObjectGuid PetGuid; + public byte Slot; + } + + class BattlePetModifyName : ClientPacket + { + public BattlePetModifyName(WorldPacket packet) : base(packet) { } + + public override void Read() + { + PetGuid = _worldPacket.ReadPackedGuid(); + uint nameLength = _worldPacket.ReadBits(7); + + if (_worldPacket.HasBit()) + { + Declined = new DeclinedName(); + + byte[] declinedNameLengths = new byte[SharedConst.MaxDeclinedNameCases]; + + for (byte i = 0; i < SharedConst.MaxDeclinedNameCases; ++i) + declinedNameLengths[i] = _worldPacket.ReadBits(7); + + for (byte i = 0; i < SharedConst.MaxDeclinedNameCases; ++i) + Declined.name[i] = _worldPacket.ReadString(declinedNameLengths[i]); + } + + Name = _worldPacket.ReadString(nameLength); + } + + public ObjectGuid PetGuid; + public string Name; + public DeclinedName Declined; + } + + class BattlePetDeletePet : ClientPacket + { + public BattlePetDeletePet(WorldPacket packet) : base(packet) { } + + public override void Read() + { + PetGuid = _worldPacket.ReadPackedGuid(); + } + + public ObjectGuid PetGuid; + } + + class BattlePetSetFlags : ClientPacket + { + public BattlePetSetFlags(WorldPacket packet) : base(packet) { } + + public override void Read() + { + PetGuid = _worldPacket.ReadPackedGuid(); + Flags = _worldPacket.ReadUInt32(); + ControlType = (FlagsControlType)_worldPacket.ReadBits(2); + } + + public ObjectGuid PetGuid; + public uint Flags; + public FlagsControlType ControlType; + } + + class CageBattlePet : ClientPacket + { + public CageBattlePet(WorldPacket packet) : base(packet) { } + + public override void Read() + { + PetGuid = _worldPacket.ReadPackedGuid(); + } + + public ObjectGuid PetGuid; + } + + class BattlePetDeleted : ServerPacket + { + public BattlePetDeleted() : base(ServerOpcodes.BattlePetDeleted) { } + + public override void Write() + { + _worldPacket.WritePackedGuid(PetGuid); + } + + public ObjectGuid PetGuid; + } + + class BattlePetErrorPacket : ServerPacket + { + public BattlePetErrorPacket() : base(ServerOpcodes.BattlePetError) { } + + public override void Write() + { + _worldPacket.WriteBits(Result, 3); + _worldPacket.WriteUInt32(CreatureID); + } + + public BattlePetError Result; + public uint CreatureID; + } + + class BattlePetSummon : ClientPacket + { + public BattlePetSummon(WorldPacket packet) : base(packet) { } + + public override void Read() + { + PetGuid = _worldPacket.ReadPackedGuid(); + } + + public ObjectGuid PetGuid; + } + + + //Structs + public struct BattlePetStruct + { + public void Write(WorldPacket data) + { + data .WritePackedGuid( Guid); + data.WriteUInt32(Species); + data.WriteUInt32(CreatureID); + data.WriteUInt32(CollarID); + data.WriteUInt16(Breed); + data.WriteUInt16(Level); + data.WriteUInt16(Exp); + data.WriteUInt16(Flags); + data.WriteUInt32(Power); + data.WriteUInt32(Health); + data.WriteUInt32(MaxHealth); + data .WriteUInt32( Speed); + data .WriteUInt8( Quality); + data.WriteBits(Name.Length, 7); + data.WriteBit(OwnerInfo.HasValue); // HasOwnerInfo + data.WriteBit(Name.IsEmpty()); // NoRename + data.FlushBits(); + + data.WriteString(Name); + + if (OwnerInfo.HasValue) + { + data.WritePackedGuid(OwnerInfo.Value.Guid); + data.WriteUInt32(OwnerInfo.Value.PlayerVirtualRealm); // Virtual + data.WriteUInt32(OwnerInfo.Value.PlayerNativeRealm); // Native + } + } + + public struct BattlePetOwnerInfo + { + public ObjectGuid Guid; + public uint PlayerVirtualRealm; + public uint PlayerNativeRealm; + } + + public ObjectGuid Guid; + public uint Species; + public uint CreatureID; + public uint CollarID; // what's this? + public ushort Breed; + public ushort Level; + public ushort Exp; + public ushort Flags; + public uint Power; + public uint Health; + public uint MaxHealth; + public uint Speed; + public byte Quality; + public Optional OwnerInfo; + public string Name; + } + + public class BattlePetSlot + { + public void Write(WorldPacket data) + { + data .WritePackedGuid(Pet.Guid.IsEmpty() ? ObjectGuid.Create(HighGuid.BattlePet, 0) : Pet.Guid); + data .WriteUInt32( CollarID); + data .WriteUInt8( Index); + data.WriteBit(Locked); + data.FlushBits(); + } + + public BattlePetStruct Pet; + public uint CollarID; // what's this? + public byte Index; + public bool Locked = true; + } +} diff --git a/Game/Network/Packets/BattlefieldPackets.cs b/Game/Network/Packets/BattlefieldPackets.cs new file mode 100644 index 000000000..3e7e09c67 --- /dev/null +++ b/Game/Network/Packets/BattlefieldPackets.cs @@ -0,0 +1,179 @@ +/* + * Copyright (C) 2012-2017 CypherCore + * + * 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, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +using Framework.Constants; +using Game.Entities; + +namespace Game.Network.Packets +{ + class BFMgrEntryInvite : ServerPacket + { + public BFMgrEntryInvite() : base(ServerOpcodes.None) { } + + public override void Write() + { + _worldPacket.WriteUInt64(QueueID); + _worldPacket.WriteUInt32(AreaID); + _worldPacket.WriteUInt32(ExpireTime); + } + + public ulong QueueID; + public int AreaID; + public long ExpireTime; + } + + class BFMgrEntryInviteResponse : ClientPacket + { + public BFMgrEntryInviteResponse(WorldPacket packet) : base(packet) { } + + public override void Read() + { + QueueID = _worldPacket.ReadUInt64(); + AcceptedInvite = _worldPacket.HasBit(); + } + + public ulong QueueID; + public bool AcceptedInvite; + } + + class BFMgrQueueInvite : ServerPacket + { + public BFMgrQueueInvite() : base(ServerOpcodes.None) { } + + public override void Write() + { + _worldPacket.WriteUInt64(QueueID); + _worldPacket.WriteInt8(BattleState); + _worldPacket.WriteUInt32(Timeout); + _worldPacket.WriteInt32(MinLevel); + _worldPacket.WriteInt32(MaxLevel); + _worldPacket.WriteInt32(MapID); + _worldPacket.WriteUInt32(InstanceID); + _worldPacket.WriteBit(Index); + _worldPacket.FlushBits(); + } + + public ulong QueueID; + public BattlefieldState BattleState; + public uint Timeout = uint.MaxValue; // unused in client + public int MinLevel; // unused in client + public int MaxLevel; // unused in client + public int MapID; // unused in client + public uint InstanceID; // unused in client + public sbyte Index; // unused in client + } + + class BFMgrQueueInviteResponse : ClientPacket + { + public BFMgrQueueInviteResponse(WorldPacket packet) : base(packet) { } + + public override void Read() + { + QueueID = _worldPacket.ReadUInt64(); + AcceptedInvite = _worldPacket.HasBit(); + } + + public ulong QueueID; + public bool AcceptedInvite; + } + + class BFMgrQueueRequestResponse : ServerPacket + { + public BFMgrQueueRequestResponse() : base(ServerOpcodes.None) { } + + public override void Write() + { + _worldPacket.WriteUInt64(QueueID); + _worldPacket.WriteInt32(AreaID); + _worldPacket.WriteInt8(Result); + _worldPacket.WritePackedGuid(FailedPlayerGUID); + _worldPacket.WriteInt8(BattleState); + _worldPacket.WriteBit(LoggingIn); + _worldPacket.FlushBits(); + } + + public ulong QueueID; + public int AreaID; + public sbyte Result; + public ObjectGuid FailedPlayerGUID; + public BattlefieldState BattleState; + public bool LoggingIn; + } + + class BFMgrQueueExitRequest : ClientPacket + { + public BFMgrQueueExitRequest(WorldPacket packet) : base(packet) { } + + public override void Read() + { + QueueID = _worldPacket.ReadUInt64(); + } + + public ulong QueueID; + } + + class BFMgrEntering : ServerPacket + { + public BFMgrEntering() : base(ServerOpcodes.None, ConnectionType.Instance) { } + + public override void Write() + { + _worldPacket.WriteBit(ClearedAFK); + _worldPacket.WriteBit(Relocated); + _worldPacket.WriteBit(OnOffense); + _worldPacket.WriteUInt64(QueueID); + } + + public bool ClearedAFK; + public bool Relocated; + public bool OnOffense; + public ulong QueueID; + } + + class BFMgrEjected : ServerPacket + { + public BFMgrEjected() : base(ServerOpcodes.None, ConnectionType.Instance) { } + + public override void Write() + { + _worldPacket.WriteUInt64(QueueID); + _worldPacket.WriteInt8(Reason); + _worldPacket.WriteInt8(BattleState); + _worldPacket.WriteBit(Relocated); + _worldPacket.FlushBits(); + } + + public ulong QueueID; + public BFLeaveReason Reason; + public BattlefieldState BattleState; + public bool Relocated; + } + + public class AreaSpiritHealerTime : ServerPacket + { + public AreaSpiritHealerTime() : base(ServerOpcodes.AreaSpiritHealerTime) { } + + public override void Write() + { + _worldPacket.WritePackedGuid(HealerGuid); + _worldPacket.WriteUInt32(TimeLeft); + } + + public ObjectGuid HealerGuid; + public uint TimeLeft; + } +} diff --git a/Game/Network/Packets/BattlenetPackets.cs b/Game/Network/Packets/BattlenetPackets.cs new file mode 100644 index 000000000..9be82cae4 --- /dev/null +++ b/Game/Network/Packets/BattlenetPackets.cs @@ -0,0 +1,139 @@ +/* + * Copyright (C) 2012-2017 CypherCore + * + * 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, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +using Framework.Constants; +using Framework.IO; +using System.Collections.Generic; + +namespace Game.Network.Packets +{ + class Notification : ServerPacket + { + public Notification() : base(ServerOpcodes.BattlenetNotification) { } + + public override void Write() + { + Method.Write(_worldPacket); + _worldPacket.WriteUInt32(Data.GetSize()); + _worldPacket.WriteBytes(Data); + } + + public MethodCall Method; + public ByteBuffer Data = new ByteBuffer(); + } + + class Response : ServerPacket + { + public Response() : base(ServerOpcodes.BattlenetResponse) { } + + public override void Write() + { + _worldPacket.WriteUInt32(BnetStatus); + Method.Write(_worldPacket); + _worldPacket.WriteUInt32(Data.GetSize()); + _worldPacket.WriteBytes(Data); + } + + public BattlenetRpcErrorCode BnetStatus = BattlenetRpcErrorCode.Ok; + public MethodCall Method; + public ByteBuffer Data = new ByteBuffer(); + } + + class SetSessionState : ServerPacket + { + public SetSessionState() : base(ServerOpcodes.BattlenetSetSessionState) { } + + public override void Write() + { + _worldPacket.WriteBits(State, 2); + _worldPacket.FlushBits(); + } + + public byte State; + } + + class RealmListTicket : ServerPacket + { + public RealmListTicket() : base(ServerOpcodes.BattlenetRealmListTicket) { } + + public override void Write() + { + _worldPacket.WriteUInt32(Token); + _worldPacket.WriteBit(Allow); + _worldPacket.WriteUInt32(Ticket.GetSize()); + _worldPacket.WriteBytes(Ticket); + } + + public uint Token; + public bool Allow = true; + public ByteBuffer Ticket; + } + + class BattlenetRequest : ClientPacket + { + public BattlenetRequest(WorldPacket packet) : base(packet) { } + + public override void Read() + { + Method.Read(_worldPacket); + uint protoSize = _worldPacket.ReadUInt32(); + + Data = _worldPacket.ReadBytes(protoSize); + } + + public MethodCall Method; + public byte[] Data; + } + + class RequestRealmListTicket : ClientPacket + { + public RequestRealmListTicket(WorldPacket packet) : base(packet) { } + + public override void Read() + { + Token = _worldPacket.ReadUInt32(); + Secret.AddRange(_worldPacket.ReadBytes((uint)Secret.Capacity)); + } + + public uint Token; + public Array Secret = new Array(32); + } + + struct MethodCall + { + public uint GetServiceHash() { return (uint)(Type >> 32); } + public uint GetMethodId() { return (uint)(Type & 0xFFFFFFFF); } + + public void Read(ByteBuffer data) + { + Type = data.ReadUInt64(); + ObjectId = data.ReadUInt64(); + Token = data.ReadUInt32(); + } + + public void Write(ByteBuffer data) + { + data.WriteUInt64(Type); + data.WriteUInt64(ObjectId); + data.WriteUInt32(Token); + } + + public ulong Type; + public ulong ObjectId; + public uint Token; + } +} diff --git a/Game/Network/Packets/BlackMarketPackets.cs b/Game/Network/Packets/BlackMarketPackets.cs new file mode 100644 index 000000000..dfb869c87 --- /dev/null +++ b/Game/Network/Packets/BlackMarketPackets.cs @@ -0,0 +1,190 @@ +/* + * Copyright (C) 2012-2017 CypherCore + * + * 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, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +using Framework.Constants; +using Game.Entities; +using System.Collections.Generic; + +namespace Game.Network.Packets +{ + class BlackMarketOpen : ClientPacket + { + public BlackMarketOpen(WorldPacket packet) : base(packet) { } + + public override void Read() + { + Guid = _worldPacket.ReadPackedGuid(); + } + + public ObjectGuid Guid; + } + + class BlackMarketOpenResult : ServerPacket + { + public BlackMarketOpenResult() : base(ServerOpcodes.BlackMarketOpenResult) { } + + public override void Write() + { + _worldPacket .WritePackedGuid(Guid); + _worldPacket.WriteBit(Enable); + _worldPacket.FlushBits(); + } + + public ObjectGuid Guid; + public bool Enable = true; + } + + class BlackMarketRequestItems : ClientPacket + { + public BlackMarketRequestItems(WorldPacket packet) : base(packet) { } + + public override void Read() + { + Guid = _worldPacket.ReadPackedGuid(); + LastUpdateID = _worldPacket.ReadUInt32(); + } + + public ObjectGuid Guid; + public uint LastUpdateID; + } + + public class BlackMarketRequestItemsResult : ServerPacket + { + public BlackMarketRequestItemsResult() : base(ServerOpcodes.BlackMarketRequestItemsResult) { } + + public override void Write() + { + _worldPacket.WriteInt32(LastUpdateID); + _worldPacket.WriteUInt32(Items.Count); + + foreach (BlackMarketItem item in Items) + item.Write(_worldPacket); + } + + public int LastUpdateID; + public List Items = new List(); + } + + class BlackMarketBidOnItem : ClientPacket + { + public BlackMarketBidOnItem(WorldPacket packet) : base(packet) { } + + public override void Read() + { + Guid = _worldPacket.ReadPackedGuid(); + MarketID = _worldPacket.ReadUInt32(); + BidAmount = _worldPacket.ReadUInt64(); + Item.Read(_worldPacket); + } + + public ObjectGuid Guid; + public uint MarketID; + public ItemInstance Item = new ItemInstance(); + public ulong BidAmount; + } + + class BlackMarketBidOnItemResult : ServerPacket + { + public BlackMarketBidOnItemResult() : base(ServerOpcodes.BlackMarketBidOnItemResult) { } + + public override void Write() + { + _worldPacket.WriteUInt32(MarketID); + _worldPacket.WriteUInt32(Result); + Item.Write(_worldPacket); + } + + public uint MarketID; + public ItemInstance Item; + public BlackMarketError Result; + } + + class BlackMarketOutbid : ServerPacket + { + public BlackMarketOutbid() : base(ServerOpcodes.BlackMarketOutbid) { } + + public override void Write() + { + _worldPacket.WriteUInt32(MarketID); + _worldPacket.WriteUInt32(RandomPropertiesID); + Item.Write(_worldPacket); + } + + public uint MarketID; + public ItemInstance Item; + public uint RandomPropertiesID; + } + + class BlackMarketWon : ServerPacket + { + public BlackMarketWon() : base(ServerOpcodes.BlackMarketWon) { } + + public override void Write() + { + _worldPacket.WriteUInt32(MarketID); + _worldPacket.WriteInt32(RandomPropertiesID); + Item.Write(_worldPacket); + } + + public uint MarketID; + public ItemInstance Item; + public int RandomPropertiesID; + } + + public struct BlackMarketItem + { + public void Read(WorldPacket data) + { + MarketID = data.ReadUInt32(); + SellerNPC = data.ReadUInt32(); + Item.Read(data); + Quantity = data.ReadUInt32(); + MinBid = data.ReadUInt64(); + MinIncrement = data.ReadUInt64(); + CurrentBid = data.ReadUInt64(); + SecondsRemaining = data.ReadUInt32(); + NumBids = data.ReadUInt32(); + HighBid = data.HasBit(); + } + + public void Write(WorldPacket data) + { + data.WriteInt32(MarketID); + data.WriteInt32(SellerNPC); + data.WriteInt32(Quantity); + data.WriteUInt64(MinBid); + data.WriteUInt64(MinIncrement); + data.WriteUInt64(CurrentBid); + data.WriteInt32(SecondsRemaining); + data.WriteInt32(NumBids); + Item.Write(data); + data.WriteBit(HighBid); + data.FlushBits(); + } + + public uint MarketID; + public uint SellerNPC; + public ItemInstance Item; + public uint Quantity; + public ulong MinBid; + public ulong MinIncrement; + public ulong CurrentBid; + public uint SecondsRemaining; + public uint NumBids; + public bool HighBid; + } +} diff --git a/Game/Network/Packets/CalendarPackets.cs b/Game/Network/Packets/CalendarPackets.cs new file mode 100644 index 000000000..f1df5521b --- /dev/null +++ b/Game/Network/Packets/CalendarPackets.cs @@ -0,0 +1,896 @@ +/* + * Copyright (C) 2012-2017 CypherCore + * + * 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, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +using Framework.Constants; +using Game.Entities; +using System.Collections.Generic; + +namespace Game.Network.Packets +{ + class CalendarGetCalendar : ClientPacket + { + public CalendarGetCalendar(WorldPacket packet) : base(packet) { } + + public override void Read() { } + } + + class CalendarGetEvent : ClientPacket + { + public CalendarGetEvent(WorldPacket packet) : base(packet) { } + + public override void Read() + { + EventID = _worldPacket.ReadUInt64(); + } + + public ulong EventID; + } + + class CalendarGuildFilter : ClientPacket + { + public CalendarGuildFilter(WorldPacket packet) : base(packet) { } + + public override void Read() + { + MinLevel = _worldPacket.ReadUInt8(); + MaxLevel = _worldPacket.ReadUInt8(); + MaxRankOrder = _worldPacket.ReadUInt8(); + } + + public byte MinLevel = 1; + public byte MaxLevel = 100; + public byte MaxRankOrder; + } + + class CalendarAddEvent : ClientPacket + { + public CalendarAddEvent(WorldPacket packet) : base(packet) { } + + public override void Read() + { + EventInfo.Read(_worldPacket); + MaxSize = _worldPacket.ReadUInt32(); + } + + public uint MaxSize = 100; + public CalendarAddEventInfo EventInfo = new CalendarAddEventInfo(); + } + + class CalendarUpdateEvent : ClientPacket + { + public CalendarUpdateEvent(WorldPacket packet) : base(packet) { } + + public override void Read() + { + EventInfo.EventID = _worldPacket.ReadUInt64(); + EventInfo.ModeratorID = _worldPacket.ReadUInt64(); + EventInfo.EventType = _worldPacket.ReadUInt8(); + EventInfo.TextureID = _worldPacket.ReadUInt32(); + EventInfo.Time = _worldPacket.ReadPackedTime(); + EventInfo.Flags = _worldPacket.ReadUInt32(); + + byte titleLen = _worldPacket.ReadBits(8); + ushort descLen = _worldPacket.ReadBits(11); + + EventInfo.Title = _worldPacket.ReadString(titleLen); + EventInfo.Description = _worldPacket.ReadString(descLen); + MaxSize = _worldPacket.ReadUInt32(); + } + + public uint MaxSize; + public CalendarUpdateEventInfo EventInfo; + } + + class CalendarRemoveEvent : ClientPacket + { + public CalendarRemoveEvent(WorldPacket packet) : base(packet) { } + + public override void Read() + { + EventID = _worldPacket.ReadUInt64(); + ModeratorID = _worldPacket.ReadUInt64(); + Flags = _worldPacket.ReadUInt32(); + } + + public ulong ModeratorID; + public ulong EventID; + public uint Flags; + } + + class CalendarCopyEvent : ClientPacket + { + public CalendarCopyEvent(WorldPacket packet) : base(packet) { } + + public override void Read() + { + EventID = _worldPacket.ReadUInt64(); + ModeratorID = _worldPacket.ReadUInt64(); + Date = _worldPacket.ReadPackedTime(); + } + + public ulong ModeratorID; + public ulong EventID; + public long Date; + } + + class SCalendarEventInvite : ServerPacket + { + public SCalendarEventInvite() : base(ServerOpcodes.CalendarEventInvite) { } + + public override void Write() + { + _worldPacket.WritePackedGuid(InviteGuid); + _worldPacket.WriteUInt64(EventID); + _worldPacket.WriteUInt64(InviteID); + _worldPacket.WriteUInt8(Level); + _worldPacket.WriteUInt8(Status); + _worldPacket.WriteUInt8(Type); + _worldPacket.WritePackedTime(ResponseTime); + + _worldPacket.WriteBit(ClearPending); + _worldPacket.FlushBits(); + } + + public ulong InviteID; + public long ResponseTime; + public byte Level = 100; + public ObjectGuid InviteGuid; + public ulong EventID; + public byte Type; + public bool ClearPending; + public CalendarInviteStatus Status; + } + + class CalendarSendCalendar : ServerPacket + { + public CalendarSendCalendar() : base(ServerOpcodes.CalendarSendCalendar) { } + + public override void Write() + { + _worldPacket.WritePackedTime(ServerTime); + _worldPacket.WriteUInt32(Invites.Count); + _worldPacket.WriteUInt32(Events.Count); + _worldPacket.WriteUInt32(RaidLockouts.Count); + + foreach (var invite in Invites) + invite.Write(_worldPacket); + + foreach (var lockout in RaidLockouts) + lockout.Write(_worldPacket); + + foreach (var Event in Events) + Event.Write(_worldPacket); + } + + public long ServerTime; + public List Invites = new List(); + public List RaidLockouts = new List(); + public List Events = new List(); + } + + class CalendarSendEvent : ServerPacket + { + public CalendarSendEvent() : base(ServerOpcodes.CalendarSendEvent) { } + + public override void Write() + { + _worldPacket.WriteUInt8(EventType); + _worldPacket.WritePackedGuid(OwnerGuid); + _worldPacket.WriteUInt64(EventID); + _worldPacket.WriteUInt8(GetEventType); + _worldPacket.WriteInt32(TextureID); + _worldPacket.WriteUInt32(Flags); + _worldPacket.WritePackedTime(Date); + _worldPacket.WriteUInt32(LockDate); + _worldPacket.WritePackedGuid(EventGuildID); + _worldPacket.WriteUInt32(Invites.Count); + + _worldPacket.WriteBits(EventName.Length, 8); + _worldPacket.WriteBits(Description.Length, 11); + _worldPacket.FlushBits(); + + foreach (var invite in Invites) + invite.Write(_worldPacket); + + _worldPacket.WriteString(EventName); + _worldPacket.WriteString(Description); + } + + public ObjectGuid OwnerGuid; + public ObjectGuid EventGuildID; + public ulong EventID; + public long Date; + public long LockDate; + public CalendarFlags Flags; + public int TextureID; + public CalendarEventType GetEventType; + public CalendarSendEventType EventType; + public string Description; + public string EventName; + public List Invites = new List(); + } + + class CalendarEventInviteAlert : ServerPacket + { + public CalendarEventInviteAlert() : base(ServerOpcodes.CalendarEventInviteAlert) { } + + public override void Write() + { + _worldPacket.WriteUInt64(EventID); + _worldPacket.WritePackedTime(Date); + _worldPacket.WriteUInt32(Flags); + _worldPacket.WriteUInt8(EventType); + _worldPacket.WriteInt32(TextureID); + _worldPacket.WritePackedGuid(EventGuildID); + _worldPacket.WriteUInt64(InviteID); + _worldPacket.WriteUInt8(Status); + _worldPacket.WriteUInt8(ModeratorStatus); + + // Todo: check order + _worldPacket.WritePackedGuid(InvitedByGuid); + _worldPacket.WritePackedGuid(OwnerGuid); + + _worldPacket.WriteBits(EventName.Length, 8); + _worldPacket.FlushBits(); + _worldPacket.WriteString(EventName); + } + + public ObjectGuid OwnerGuid; + public ObjectGuid EventGuildID; + public ObjectGuid InvitedByGuid; + public ulong InviteID; + public ulong EventID; + public CalendarFlags Flags; + public long Date; + public int TextureID; + public CalendarInviteStatus Status; + public CalendarEventType EventType; + public CalendarModerationRank ModeratorStatus; + public string EventName; + } + + class CalendarEventInvite : ClientPacket + { + public CalendarEventInvite(WorldPacket packet) : base(packet) { } + + public override void Read() + { + EventID = _worldPacket.ReadUInt64(); + ModeratorID = _worldPacket.ReadUInt64(); + + ushort nameLen = _worldPacket.ReadBits(9); + Creating = _worldPacket.HasBit(); + IsSignUp = _worldPacket.HasBit(); + + Name = _worldPacket.ReadString(nameLen); + } + + public ulong ModeratorID; + public bool IsSignUp; + public bool Creating = true; + public ulong EventID; + public string Name; + } + + class CalendarEventRSVP : ClientPacket + { + public CalendarEventRSVP(WorldPacket packet) : base(packet) { } + + public override void Read() + { + EventID = _worldPacket.ReadUInt64(); + InviteID = _worldPacket.ReadUInt64(); + Status = (CalendarInviteStatus)_worldPacket.ReadUInt8(); + } + + public ulong InviteID; + public ulong EventID; + public CalendarInviteStatus Status; + } + + class CalendarEventInviteStatus : ServerPacket + { + public CalendarEventInviteStatus() : base(ServerOpcodes.CalendarEventInviteStatus) { } + + public override void Write() + { + _worldPacket.WritePackedGuid(InviteGuid); + _worldPacket.WriteUInt64(EventID); + _worldPacket.WritePackedTime(Date); + _worldPacket.WriteUInt32(Flags); + _worldPacket.WriteUInt8(Status); + _worldPacket.WritePackedTime(ResponseTime); + + _worldPacket.WriteBit(ClearPending); + _worldPacket.FlushBits(); + } + + public CalendarFlags Flags; + public ulong EventID; + public CalendarInviteStatus Status; + public bool ClearPending; + public long ResponseTime; + public long Date; + public ObjectGuid InviteGuid; + } + + class CalendarEventInviteRemoved : ServerPacket + { + public CalendarEventInviteRemoved() : base(ServerOpcodes.CalendarEventInviteRemoved) { } + + public override void Write() + { + _worldPacket.WritePackedGuid(InviteGuid); + _worldPacket.WriteUInt64(EventID); + _worldPacket.WriteUInt32(Flags); + + _worldPacket.WriteBit(ClearPending); + _worldPacket.FlushBits(); + } + + public ObjectGuid InviteGuid; + public ulong EventID; + public uint Flags; + public bool ClearPending; + } + + class CalendarEventInviteModeratorStatus : ServerPacket + { + public CalendarEventInviteModeratorStatus() : base(ServerOpcodes.CalendarEventInviteModeratorStatus) { } + + public override void Write() + { + _worldPacket.WritePackedGuid(InviteGuid); + _worldPacket.WriteUInt64(EventID); + _worldPacket.WriteUInt8(Status); + + _worldPacket.WriteBit(ClearPending); + _worldPacket.FlushBits(); + } + + public ObjectGuid InviteGuid; + public ulong EventID; + public CalendarInviteStatus Status; + public bool ClearPending; + } + + class CalendarEventInviteRemovedAlert : ServerPacket + { + public CalendarEventInviteRemovedAlert() : base(ServerOpcodes.CalendarEventInviteRemovedAlert) { } + + public override void Write() + { + _worldPacket.WriteUInt64(EventID); + _worldPacket.WritePackedTime(Date); + _worldPacket.WriteUInt32(Flags); + _worldPacket.WriteUInt8(Status); + } + + public ulong EventID; + public long Date; + public CalendarFlags Flags; + public CalendarInviteStatus Status; + } + + class CalendarClearPendingAction : ServerPacket + { + public CalendarClearPendingAction() : base(ServerOpcodes.CalendarClearPendingAction) { } + + public override void Write() { } + } + + class CalendarEventUpdatedAlert : ServerPacket + { + public CalendarEventUpdatedAlert() : base(ServerOpcodes.CalendarEventUpdatedAlert) { } + + public override void Write() + { + _worldPacket.WriteUInt64(EventID); + + _worldPacket.WritePackedTime(OriginalDate); + _worldPacket.WritePackedTime(Date); + _worldPacket.WriteUInt32(LockDate); + _worldPacket.WriteUInt32(Flags); + _worldPacket.WriteUInt32(TextureID); + _worldPacket.WriteUInt8(EventType); + + _worldPacket.WriteBits(EventName.Length, 8); + _worldPacket.WriteBits(Description.Length, 11); + _worldPacket.WriteBit(ClearPending); + _worldPacket.FlushBits(); + + _worldPacket.WriteString(EventName); + _worldPacket.WriteString(Description); + } + + public ulong EventID; + public long Date; + public CalendarFlags Flags; + public long LockDate; + public long OriginalDate; + public int TextureID; + public CalendarEventType EventType; + public bool ClearPending; + public string Description; + public string EventName; + } + + class CalendarEventRemovedAlert : ServerPacket + { + public CalendarEventRemovedAlert() : base(ServerOpcodes.CalendarEventRemovedAlert) { } + + public override void Write() + { + _worldPacket.WriteUInt64(EventID); + _worldPacket.WritePackedTime(Date); + + _worldPacket.WriteBit(ClearPending); + _worldPacket.FlushBits(); + } + + public ulong EventID; + public long Date; + public bool ClearPending; + } + + class CalendarSendNumPending : ServerPacket + { + public CalendarSendNumPending(uint numPending) : base(ServerOpcodes.CalendarSendNumPending) + { + NumPending = numPending; + } + + public override void Write() + { + _worldPacket.WriteUInt32(NumPending); + } + + public uint NumPending; + } + + class CalendarGetNumPending : ClientPacket + { + public CalendarGetNumPending(WorldPacket packet) : base(packet) { } + + public override void Read() { } + } + + class CalendarEventSignUp : ClientPacket + { + public CalendarEventSignUp(WorldPacket packet) : base(packet) { } + + public override void Read() + { + EventID = _worldPacket.ReadUInt64(); + Tentative = _worldPacket.HasBit(); + } + + public bool Tentative; + public ulong EventID; + } + + class CalendarRemoveInvite : ClientPacket + { + public CalendarRemoveInvite(WorldPacket packet) : base(packet) { } + + public override void Read() + { + Guid = _worldPacket.ReadPackedGuid(); + InviteID = _worldPacket.ReadUInt64(); + ModeratorID = _worldPacket.ReadUInt64(); + EventID = _worldPacket.ReadUInt64(); + } + + public ObjectGuid Guid; + public ulong EventID; + public ulong ModeratorID; + public ulong InviteID; + } + + class CalendarEventStatus : ClientPacket + { + public CalendarEventStatus(WorldPacket packet) : base(packet) { } + + public override void Read() + { + Guid = _worldPacket.ReadPackedGuid(); + EventID = _worldPacket.ReadUInt64(); + InviteID = _worldPacket.ReadUInt64(); + ModeratorID = _worldPacket.ReadUInt64(); + Status = _worldPacket.ReadUInt8(); + } + + public ObjectGuid Guid; + public ulong EventID; + public ulong ModeratorID; + public ulong InviteID; + public byte Status; + } + + class SetSavedInstanceExtend : ClientPacket + { + public SetSavedInstanceExtend(WorldPacket packet) : base(packet) { } + + public override void Read() + { + MapID = _worldPacket.ReadInt32(); + DifficultyID = _worldPacket.ReadUInt32(); + Extend = _worldPacket.HasBit(); + } + + public int MapID; + public bool Extend; + public uint DifficultyID; + } + + class CalendarEventModeratorStatus : ClientPacket + { + public CalendarEventModeratorStatus(WorldPacket packet) : base(packet) { } + + public override void Read() + { + Guid = _worldPacket.ReadPackedGuid(); + EventID = _worldPacket.ReadUInt64(); + InviteID = _worldPacket.ReadUInt64(); + ModeratorID = _worldPacket.ReadUInt64(); + Status = _worldPacket.ReadUInt8(); + } + + public ObjectGuid Guid; + public ulong EventID; + public ulong InviteID; + public ulong ModeratorID; + public byte Status; + } + + class CalendarCommandResult : ServerPacket + { + public CalendarCommandResult() : base(ServerOpcodes.CalendarCommandResult) { } + public CalendarCommandResult(byte command, CalendarError result, string name) : base(ServerOpcodes.CalendarCommandResult) + { + Command = command; + Result = result; + Name = name; + } + + public override void Write() + { + _worldPacket.WriteUInt8(Command); + _worldPacket.WriteUInt8(Result); + + _worldPacket.WriteBits(Name.Length, 9); + _worldPacket.FlushBits(); + _worldPacket.WriteString(Name); + } + + public byte Command; + public CalendarError Result; + public string Name; + } + + class CalendarRaidLockoutAdded : ServerPacket + { + public CalendarRaidLockoutAdded() : base(ServerOpcodes.CalendarRaidLockoutAdded) { } + + public override void Write() + { + _worldPacket.WriteUInt64(InstanceID); + _worldPacket.WriteUInt32(ServerTime); + _worldPacket.WriteInt32(MapID); + _worldPacket.WriteUInt32(DifficultyID); + _worldPacket.WriteInt32(TimeRemaining); + } + + public ulong InstanceID; + public Difficulty DifficultyID; + public int TimeRemaining; + public uint ServerTime; + public int MapID; + } + + class CalendarRaidLockoutRemoved : ServerPacket + { + public CalendarRaidLockoutRemoved() : base(ServerOpcodes.CalendarRaidLockoutRemoved) { } + + public override void Write() + { + _worldPacket.WriteUInt64(InstanceID); + _worldPacket.WriteInt32(MapID); + _worldPacket.WriteUInt32(DifficultyID); + } + + public ulong InstanceID; + public int MapID; + public Difficulty DifficultyID; + } + + class CalendarRaidLockoutUpdated : ServerPacket + { + public CalendarRaidLockoutUpdated() : base(ServerOpcodes.CalendarRaidLockoutUpdated) { } + + public override void Write() + { + _worldPacket.WriteUInt32(ServerTime); + _worldPacket.WriteInt32(MapID); + _worldPacket.WriteUInt32(DifficultyID); + _worldPacket.WriteInt32(NewTimeRemaining); + _worldPacket.WriteInt32(OldTimeRemaining); + } + + public int MapID; + public int OldTimeRemaining; + public long ServerTime; + public uint DifficultyID; + public int NewTimeRemaining; + } + + class CalendarEventInitialInvites : ServerPacket + { + public CalendarEventInitialInvites() : base(ServerOpcodes.CalendarEventInitialInvites) { } + + public override void Write() + { + _worldPacket.WriteUInt32(Invites.Count); + foreach (var invite in Invites) + { + _worldPacket.WritePackedGuid(invite.InviteGuid); + _worldPacket.WriteUInt8(invite.Level); + } + } + + public List Invites = new List(); + } + + class CalendarEventInviteStatusAlert : ServerPacket + { + public CalendarEventInviteStatusAlert() : base(ServerOpcodes.CalendarEventInviteStatusAlert) { } + + public override void Write() + { + _worldPacket.WriteUInt64(EventID); + _worldPacket.WritePackedTime(Date); + _worldPacket.WriteUInt32(Flags); + _worldPacket.WriteUInt8(Status); + } + + public ulong EventID; + public uint Flags; + public long Date; + public byte Status; + } + + class CalendarEventInviteNotesAlert : ServerPacket + { + public CalendarEventInviteNotesAlert(ulong eventID, string notes) : base(ServerOpcodes.CalendarEventInviteNotesAlert) + { + EventID = eventID; + Notes = notes; + } + + public override void Write() + { + _worldPacket.WriteUInt64(EventID); + + _worldPacket.WriteBits(Notes.Length, 8); + _worldPacket.FlushBits(); + _worldPacket.WriteString(Notes); + } + + public ulong EventID; + public string Notes; + } + + class CalendarEventInviteNotes : ServerPacket + { + public CalendarEventInviteNotes() : base(ServerOpcodes.CalendarEventInviteNotes) { } + + public override void Write() + { + _worldPacket.WritePackedGuid(InviteGuid); + _worldPacket.WriteUInt64(EventID); + + _worldPacket.WriteBits(Notes.Length, 8); + _worldPacket.WriteBit(ClearPending); + _worldPacket.FlushBits(); + _worldPacket.WriteString(Notes); + } + + public ObjectGuid InviteGuid; + public ulong EventID; + public string Notes = ""; + public bool ClearPending; + } + + class CalendarComplain : ClientPacket + { + public CalendarComplain(WorldPacket packet) : base(packet) { } + + public override void Read() + { + InvitedByGUID = _worldPacket.ReadPackedGuid(); + EventID = _worldPacket.ReadUInt64(); + InviteID = _worldPacket.ReadUInt64(); + } + + ObjectGuid InvitedByGUID; + ulong InviteID; + ulong EventID; + } + + //Structs + struct CalendarAddEventInviteInfo + { + public void Read(WorldPacket data) + { + Guid = data.ReadPackedGuid(); + Status = data.ReadUInt8(); + Moderator = data.ReadUInt8(); + } + + public ObjectGuid Guid; + public byte Status; + public byte Moderator; + } + + class CalendarAddEventInfo + { + public void Read(WorldPacket data) + { + byte titleLength = data.ReadBits(8); + ushort descriptionLength = data.ReadBits(11); + + EventType = data.ReadUInt8(); + TextureID = data.ReadInt32(); + Time = data.ReadPackedTime(); + Flags = data.ReadUInt32(); + var InviteCount = data.ReadUInt32(); + + Title = data.ReadString(titleLength); + Description = data.ReadString(descriptionLength); + + for (var i = 0; i < InviteCount; ++i) + { + CalendarAddEventInviteInfo invite = new CalendarAddEventInviteInfo(); + invite.Read(data); + Invites[i] = invite; + } + } + + public string Title; + public string Description; + public byte EventType; + public int TextureID; + public long Time; + public uint Flags; + public CalendarAddEventInviteInfo[] Invites = new CalendarAddEventInviteInfo[(int)SharedConst.CalendarMaxInvites]; + } + + struct CalendarUpdateEventInfo + { + public ulong EventID; + public ulong ModeratorID; + public string Title; + public string Description; + public byte EventType; + public uint TextureID; + public long Time; + public uint Flags; + } + + struct CalendarSendCalendarInviteInfo + { + public void Write(WorldPacket data) + { + data.WriteUInt64(EventID); + data.WriteUInt64(InviteID); + data.WriteUInt8(Status); + data.WriteUInt8(Moderator); + data.WriteUInt8(InviteType); + data.WritePackedGuid(InviterGuid); + } + + public ulong EventID; + public ulong InviteID; + public ObjectGuid InviterGuid; + public CalendarInviteStatus Status; + public CalendarModerationRank Moderator; + public byte InviteType; + } + struct CalendarSendCalendarRaidLockoutInfo + { + public void Write(WorldPacket data) + { + data.WriteUInt64(InstanceID); + data.WriteInt32(MapID); + data.WriteUInt32(DifficultyID); + data.WriteUInt32(ExpireTime); + } + + public ulong InstanceID; + public int MapID; + public uint DifficultyID; + public long ExpireTime; + } + + struct CalendarSendCalendarEventInfo + { + public void Write(WorldPacket data) + { + data.WriteUInt64(EventID); + data.WriteUInt8(EventType); + data.WritePackedTime(Date); + data.WriteUInt32(Flags); + data.WriteInt32(TextureID); + data.WritePackedGuid(EventGuildID); + data.WritePackedGuid(OwnerGuid); + + data.WriteBits(EventName.Length, 8); + data.FlushBits(); + data.WriteString(EventName); + } + + public ulong EventID; + public string EventName; + public CalendarEventType EventType; + public long Date; + public CalendarFlags Flags; + public int TextureID; + public ObjectGuid EventGuildID; + public ObjectGuid OwnerGuid; + } + + class CalendarEventInviteInfo + { + public void Write(WorldPacket data) + { + data.WritePackedGuid(Guid); + data.WriteUInt64(InviteID); + + data.WriteUInt8(Level); + data.WriteUInt8(Status); + data.WriteUInt8(Moderator); + data.WriteUInt8(InviteType); + + data.WritePackedTime(ResponseTime); + + data.WriteBits(Notes.Length, 8); + data.FlushBits(); + data.WriteString(Notes); + } + + public ObjectGuid Guid; + public ulong InviteID; + public long ResponseTime; + public byte Level = 1; + public CalendarInviteStatus Status; + public CalendarModerationRank Moderator; + public byte InviteType; + public string Notes; + } + + class CalendarEventInitialInviteInfo + { + public CalendarEventInitialInviteInfo(ObjectGuid inviteGuid, byte level) + { + InviteGuid = inviteGuid; + Level = level; + } + + public ObjectGuid InviteGuid; + public byte Level = 100; + } +} diff --git a/Game/Network/Packets/ChannelPackets.cs b/Game/Network/Packets/ChannelPackets.cs new file mode 100644 index 000000000..e14b54458 --- /dev/null +++ b/Game/Network/Packets/ChannelPackets.cs @@ -0,0 +1,327 @@ +/* + * Copyright (C) 2012-2017 CypherCore + * + * 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, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +using Framework.Constants; +using Game.Entities; +using System.Collections.Generic; + +namespace Game.Network.Packets +{ + public class ChannelListResponse : ServerPacket + { + public ChannelListResponse() : base(ServerOpcodes.ChannelList) + { + Members = new List(); + } + + public override void Write() + { + _worldPacket.WriteBit(Display); + _worldPacket.WriteBits(Channel.Length, 7); + _worldPacket.WriteUInt32(ChannelFlags); + _worldPacket.WriteUInt32(Members.Count); + _worldPacket.WriteString(Channel); + + foreach (ChannelPlayer player in Members) + { + _worldPacket.WritePackedGuid(player.Guid); + _worldPacket.WriteUInt32(player.VirtualRealmAddress); + _worldPacket.WriteUInt8(player.Flags); + } + } + + public List Members; + public string Channel; // Channel Name + public ChannelFlags ChannelFlags; + public bool Display; + + public struct ChannelPlayer + { + public ChannelPlayer(ObjectGuid guid, uint realm, ChannelMemberFlags flags) + { + Guid = guid; + VirtualRealmAddress = realm; + Flags = flags; + } + + public ObjectGuid Guid; // Player Guid + public uint VirtualRealmAddress; + public ChannelMemberFlags Flags; + } + } + + public class ChannelNotify : ServerPacket + { + public ChannelNotify() : base(ServerOpcodes.ChannelNotify) { } + + public override void Write() + { + _worldPacket.WriteBits(Type, 6); + _worldPacket.WriteBits(Channel.Length, 7); + _worldPacket.WriteBits(Sender.Length, 6); + + _worldPacket.WritePackedGuid(SenderGuid); + _worldPacket.WritePackedGuid(SenderAccountID); + _worldPacket.WriteUInt32(SenderVirtualRealm); + _worldPacket.WritePackedGuid(TargetGuid); + _worldPacket.WriteUInt32(TargetVirtualRealm); + _worldPacket.WriteInt32(ChatChannelID); + + if (Type == ChatNotify.ModeChangeNotice) + { + _worldPacket.WriteUInt8(OldFlags); + _worldPacket.WriteUInt8(NewFlags); + } + + _worldPacket.WriteString(Channel); + _worldPacket.WriteString(Sender); + } + + public string Sender = ""; + public ObjectGuid SenderGuid; + public ObjectGuid SenderAccountID; + public ChatNotify Type; + public ChannelMemberFlags OldFlags; + public ChannelMemberFlags NewFlags; + public string Channel; + public uint SenderVirtualRealm; + public ObjectGuid TargetGuid; + public uint TargetVirtualRealm; + public int ChatChannelID; + } + + public class ChannelNotifyJoined : ServerPacket + { + public ChannelNotifyJoined() : base(ServerOpcodes.ChannelNotifyJoined) { } + + public override void Write() + { + _worldPacket.WriteBits(Channel.Length, 7); + _worldPacket.WriteBits(ChannelWelcomeMsg.Length, 10); + _worldPacket.WriteUInt32(ChannelFlags); + _worldPacket.WriteInt32(ChatChannelID); + _worldPacket.WriteUInt64(InstanceID); + _worldPacket.WriteString(Channel); + _worldPacket.WriteString(ChannelWelcomeMsg); + } + + public string ChannelWelcomeMsg = ""; + public int ChatChannelID; + public int InstanceID; + public ChannelFlags ChannelFlags; + public string Channel = ""; + } + + public class ChannelNotifyLeft : ServerPacket + { + public ChannelNotifyLeft() : base(ServerOpcodes.ChannelNotifyLeft) { } + + public override void Write() + { + _worldPacket.WriteBits(Channel.Length, 7); + _worldPacket.WriteBit(Suspended); + _worldPacket.WriteUInt32(ChatChannelID); + _worldPacket.WriteString(Channel); + } + + public string Channel; + public uint ChatChannelID; + public bool Suspended; + } + + class UserlistAdd : ServerPacket + { + public UserlistAdd() : base(ServerOpcodes.UserlistAdd) { } + + public override void Write() + { + _worldPacket.WritePackedGuid(AddedUserGUID); + _worldPacket.WriteUInt8(UserFlags); + _worldPacket.WriteUInt32(ChannelFlags); + _worldPacket.WriteUInt32(ChannelID); + + _worldPacket.WriteBits(ChannelName.Length, 7); + _worldPacket.FlushBits(); + _worldPacket.WriteString(ChannelName); + } + + public ObjectGuid AddedUserGUID; + public ChannelFlags ChannelFlags; + public ChannelMemberFlags UserFlags; + public uint ChannelID; + public string ChannelName; + } + + class UserlistRemove : ServerPacket + { + public UserlistRemove() : base(ServerOpcodes.UserlistRemove) { } + + public override void Write() + { + _worldPacket.WritePackedGuid(RemovedUserGUID); + _worldPacket.WriteUInt32(ChannelFlags); + _worldPacket.WriteUInt32(ChannelID); + + _worldPacket.WriteBits(ChannelName.Length, 7); + _worldPacket.FlushBits(); + _worldPacket.WriteString(ChannelName); + } + + public ObjectGuid RemovedUserGUID; + public ChannelFlags ChannelFlags; + public uint ChannelID; + public string ChannelName; + } + + class UserlistUpdate : ServerPacket + { + public UserlistUpdate() : base(ServerOpcodes.UserlistUpdate) { } + + public override void Write() + { + _worldPacket.WritePackedGuid(UpdatedUserGUID); + _worldPacket.WriteUInt8(UserFlags); + _worldPacket.WriteUInt32(ChannelFlags); + _worldPacket.WriteUInt32(ChannelID); + + _worldPacket.WriteBits(ChannelName.Length, 7); + _worldPacket.FlushBits(); + _worldPacket.WriteString(ChannelName); + } + + public ObjectGuid UpdatedUserGUID; + public ChannelFlags ChannelFlags; + public ChannelMemberFlags UserFlags; + public uint ChannelID; + public string ChannelName; + } + + class ChannelCommand : ClientPacket + { + public ChannelCommand(WorldPacket packet) : base(packet) + { + switch (GetOpcode()) + { + case ClientOpcodes.ChatChannelAnnouncements: + case ClientOpcodes.ChatChannelDeclineInvite: + case ClientOpcodes.ChatChannelDisplayList: + case ClientOpcodes.ChatChannelList: + case ClientOpcodes.ChatChannelModerate: + case ClientOpcodes.ChatChannelOwner: + break; + default: + //ABORT(); + break; + } + } + + public override void Read() + { + ChannelName = _worldPacket.ReadString(_worldPacket.ReadBits(7)); + } + + public string ChannelName; + } + + class ChannelPlayerCommand : ClientPacket + { + public ChannelPlayerCommand(WorldPacket packet) : base(packet) + { + switch (GetOpcode()) + { + case ClientOpcodes.ChatChannelBan: + case ClientOpcodes.ChatChannelInvite: + case ClientOpcodes.ChatChannelKick: + case ClientOpcodes.ChatChannelModerator: + case ClientOpcodes.ChatChannelMute: + case ClientOpcodes.ChatChannelSetOwner: + case ClientOpcodes.ChatChannelSilenceAll: + case ClientOpcodes.ChatChannelUnban: + case ClientOpcodes.ChatChannelUnmoderator: + case ClientOpcodes.ChatChannelUnmute: + case ClientOpcodes.ChatChannelUnsilenceAll: + break; + default: + //ABORT(); + break; + } + } + + public override void Read() + { + uint channelNameLength = _worldPacket.ReadBits(7); + uint nameLength = _worldPacket.ReadBits(9); + ChannelName = _worldPacket.ReadString(channelNameLength); + Name = _worldPacket.ReadString(nameLength); + } + + public string ChannelName; + public string Name; + } + + class ChannelPassword : ClientPacket + { + public ChannelPassword(WorldPacket packet) : base(packet) { } + + public override void Read() + { + uint channelNameLength = _worldPacket.ReadBits(7); + uint passwordLength = _worldPacket.ReadBits(7); + ChannelName = _worldPacket.ReadString(channelNameLength); + Password = _worldPacket.ReadString(passwordLength); + } + + public string ChannelName; + public string Password; + } + + public class JoinChannel : ClientPacket + { + public JoinChannel(WorldPacket packet) : base(packet) { } + + public override void Read() + { + ChatChannelId = _worldPacket.ReadInt32(); + CreateVoiceSession = _worldPacket.HasBit(); + Internal = _worldPacket.HasBit(); + uint channelLength = _worldPacket.ReadBits(7); + uint passwordLength = _worldPacket.ReadBits(7); + ChannelName = _worldPacket.ReadString(channelLength); + Password = _worldPacket.ReadString(passwordLength); + } + + public string Password; + public string ChannelName; + public bool CreateVoiceSession; + public int ChatChannelId; + public bool Internal; + } + + public class LeaveChannel : ClientPacket + { + public LeaveChannel(WorldPacket packet) : base(packet) { } + + public override void Read() + { + ZoneChannelID = _worldPacket.ReadInt32(); + ChannelName = _worldPacket.ReadString(_worldPacket.ReadBits(7)); + } + + public int ZoneChannelID; + public string ChannelName; + } +} diff --git a/Game/Network/Packets/CharacterPackets.cs b/Game/Network/Packets/CharacterPackets.cs new file mode 100644 index 000000000..dbe6f0a15 --- /dev/null +++ b/Game/Network/Packets/CharacterPackets.cs @@ -0,0 +1,1073 @@ +/* + * Copyright (C) 2012-2017 CypherCore + * + * 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, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +using Framework.Constants; +using Framework.Database; +using Framework.Dynamic; +using Framework.GameMath; +using Framework.IO; +using Game.Entities; +using System; +using System.Collections.Generic; +using System.Diagnostics.Contracts; + +namespace Game.Network.Packets +{ + public class EnumCharacters : ClientPacket + { + public EnumCharacters(WorldPacket packet) : base(packet) { } + + public override void Read() { } + } + + public class EnumCharactersResult : ServerPacket + { + public EnumCharactersResult() : base(ServerOpcodes.EnumCharactersResult) { } + + public override void Write() + { + _worldPacket.WriteBit(Success); + _worldPacket.WriteBit(IsDeletedCharacters); + _worldPacket.WriteBit(IsDemonHunterCreationAllowed); + _worldPacket.WriteBit(HasDemonHunterOnRealm); + _worldPacket.WriteBit(HasLevel70OnRealm); + _worldPacket.WriteBit(Unknown7x); + _worldPacket.WriteBit(DisabledClassesMask.HasValue); + _worldPacket.WriteUInt32(Characters.Count); + _worldPacket.WriteUInt32(FactionChangeRestrictions.Count); + + if (DisabledClassesMask.HasValue) + _worldPacket.WriteUInt32(DisabledClassesMask.Value); + + foreach (RestrictedFactionChangeRuleInfo rule in FactionChangeRestrictions) + { + _worldPacket.WriteUInt32(rule.Mask); + _worldPacket.WriteUInt8(rule.Race); + } + + foreach (CharacterInfo charInfo in Characters) + { + _worldPacket.WritePackedGuid(charInfo.Guid); + _worldPacket.WriteUInt8(charInfo.ListPosition); + _worldPacket.WriteUInt8(charInfo.RaceId); + _worldPacket.WriteUInt8(charInfo.ClassId); + _worldPacket.WriteUInt8(charInfo.Sex); + _worldPacket.WriteUInt8(charInfo.Skin); + _worldPacket.WriteUInt8(charInfo.Face); + _worldPacket.WriteUInt8(charInfo.HairStyle); + _worldPacket.WriteUInt8(charInfo.HairColor); + _worldPacket.WriteUInt8(charInfo.FacialHair); + charInfo.CustomDisplay.ForEach(id => _worldPacket.WriteUInt8(id)); + _worldPacket.WriteUInt8(charInfo.Level); + _worldPacket.WriteUInt32(charInfo.ZoneId); + _worldPacket.WriteUInt32(charInfo.MapId); + _worldPacket.WriteVector3(charInfo.PreLoadPosition); + _worldPacket.WritePackedGuid(charInfo.GuildGuid); + _worldPacket.WriteUInt32(charInfo.Flags); + _worldPacket.WriteUInt32(charInfo.CustomizationFlag); + _worldPacket.WriteUInt32(charInfo.Flags3); + _worldPacket.WriteUInt32(charInfo.Pet.CreatureDisplayId); + _worldPacket.WriteUInt32(charInfo.Pet.Level); + _worldPacket.WriteUInt32(charInfo.Pet.CreatureFamily); + + _worldPacket.WriteUInt32(charInfo.ProfessionIds[0]); + _worldPacket.WriteUInt32(charInfo.ProfessionIds[1]); + + foreach (var visualItem in charInfo.VisualItems) + { + _worldPacket.WriteUInt32(visualItem.DisplayId); + _worldPacket.WriteUInt32(visualItem.DisplayEnchantId); + _worldPacket.WriteUInt8(visualItem.InventoryType); + } + + _worldPacket.WriteUInt32(charInfo.LastPlayedTime); + _worldPacket.WriteUInt16(charInfo.SpecID); + _worldPacket.WriteUInt32(charInfo.Unknown703); + _worldPacket.WriteUInt32(charInfo.Flags4); + _worldPacket.WriteBits(charInfo.Name.Length, 6); + _worldPacket.WriteBit(charInfo.FirstLogin); + _worldPacket.WriteBit(charInfo.BoostInProgress); + _worldPacket.WriteBits(charInfo.unkWod61x, 5); + _worldPacket.WriteString(charInfo.Name); + } + } + + public bool Success; + public bool IsDeletedCharacters; // used for character undelete list + public bool IsDemonHunterCreationAllowed = false; ///< used for demon hunter early access + public bool HasDemonHunterOnRealm = false; + public bool HasLevel70OnRealm = false; + public bool Unknown7x = false; + + public Optional DisabledClassesMask = new Optional(); + + public List Characters = new List(); // all characters on the list + public List FactionChangeRestrictions = new List(); // @todo: research + + public class CharacterInfo + { + public CharacterInfo(SQLFields fields) + { + // 0 1 2 3 4 5 6 7 + // "SELECT characters.guid, characters.name, characters.race, characters.class, characters.gender, characters.skin, characters.face, characters.hairStyle, " + // 8 9 10 11 12 13 + // "characters.hairColor, characters.facialStyle, characters.customDisplay1, characters.customDisplay2, characters.customDisplay3, characters.level, " + // 14 15 16 17 18 + // "characters.zone, characters.map, characters.position_x, characters.position_y, characters.position_z, " + // 19 20 21 22 23 24 25 + // "guild_member.guildid, characters.playerFlags, characters.at_login, character_pet.entry, character_pet.modelid, character_pet.level, characters.equipmentCache, " + // 26 27 28 29 30 + // "character_banned.guid, characters.slot, characters.logout_time, characters.activeTalentGroup, character_declinedname.genitive" + + Guid = ObjectGuid.Create(HighGuid.Player, fields.Read(0)); + Name = fields.Read(1); + RaceId = fields.Read(2); + ClassId = (Class)fields.Read(3); + Sex = fields.Read(4); + Skin = fields.Read(5); + Face = fields.Read(6); + HairStyle = fields.Read(7); + HairColor = fields.Read(8); + FacialHair = fields.Read(9); + CustomDisplay[0] = fields.Read(10); + CustomDisplay[1] = fields.Read(11); + CustomDisplay[2] = fields.Read(12); + Level = fields.Read(13); + ZoneId = fields.Read(14); + MapId = fields.Read(15); + PreLoadPosition = new Vector3(fields.Read(16), fields.Read(17), fields.Read(18)); + + ulong guildId = fields.Read(19); + if (guildId != 0) + GuildGuid = ObjectGuid.Create(HighGuid.Guild, guildId); + + PlayerFlags playerFlags = (PlayerFlags)fields.Read(20); + AtLoginFlags atLoginFlags = (AtLoginFlags)fields.Read(21); + + if (atLoginFlags.HasAnyFlag(AtLoginFlags.Resurrect)) + playerFlags &= ~PlayerFlags.Ghost; + + if (playerFlags.HasAnyFlag(PlayerFlags.HideHelm)) + Flags |= CharacterFlags.HideHelm; + + if (playerFlags.HasAnyFlag(PlayerFlags.HideCloak)) + Flags |= CharacterFlags.HideCloak; + + if (playerFlags.HasAnyFlag(PlayerFlags.Ghost)) + Flags |= CharacterFlags.Ghost; + + if (atLoginFlags.HasAnyFlag(AtLoginFlags.Rename)) + Flags |= CharacterFlags.Rename; + + if (fields.Read(23) != 0) + Flags |= CharacterFlags.LockedByBilling; + + if (WorldConfig.GetBoolValue(WorldCfg.DeclinedNamesUsed) && !string.IsNullOrEmpty(fields.Read(26))) + Flags |= CharacterFlags.Declined; + + if (atLoginFlags.HasAnyFlag(AtLoginFlags.Customize)) + CustomizationFlag = CharacterCustomizeFlags.Customize; + else if (atLoginFlags.HasAnyFlag(AtLoginFlags.ChangeFaction)) + CustomizationFlag = CharacterCustomizeFlags.Faction; + else if (atLoginFlags.HasAnyFlag(AtLoginFlags.ChangeRace)) + CustomizationFlag = CharacterCustomizeFlags.Race; + + Flags3 = 0; + Flags4 = 0; + FirstLogin = atLoginFlags.HasAnyFlag(AtLoginFlags.FirstLogin); + + // show pet at selection character in character list only for non-ghost character + if (!playerFlags.HasAnyFlag(PlayerFlags.Ghost) && (ClassId == Class.Warlock || ClassId == Class.Hunter || ClassId == Class.Deathknight)) + { + CreatureTemplate creatureInfo = Global.ObjectMgr.GetCreatureTemplate(fields.Read(22)); + if (creatureInfo != null) + { + Pet.CreatureDisplayId = fields.Read(23); + Pet.Level = fields.Read(24); + Pet.CreatureFamily = (uint)creatureInfo.Family; + } + } + + BoostInProgress = false; + ProfessionIds[0] = 0; + ProfessionIds[1] = 0; + + StringArguments equipment = new StringArguments(fields.Read(25)); + ListPosition = fields.Read(27); + LastPlayedTime = fields.Read(28); + + for (byte slot = 0; slot < InventorySlots.BagEnd; ++slot) + { + VisualItems[slot].InventoryType = (byte)equipment.NextUInt32(); + VisualItems[slot].DisplayId = equipment.NextUInt32(); + VisualItems[slot].DisplayEnchantId = equipment.NextUInt32(); + } + } + + public ObjectGuid Guid; + public string Name; + public byte ListPosition; // Order of the characters in list + public byte RaceId; + public Class ClassId; + public byte Sex; + public byte Skin; + public byte Face; + public byte HairStyle; + public byte HairColor; + public byte FacialHair; + public Array CustomDisplay = new Array(PlayerConst.CustomDisplaySize); + public byte Level; + public uint ZoneId; + public uint MapId; + public Vector3 PreLoadPosition; + public ObjectGuid GuildGuid; + public CharacterFlags Flags; // Character flag @see enum CharacterFlags + public CharacterCustomizeFlags CustomizationFlag; // Character customization flags @see enum CharacterCustomizeFlags + public uint Flags3; // Character flags 3 @todo research + public uint Flags4; + public bool FirstLogin; + public byte unkWod61x; + public uint LastPlayedTime; + public ushort SpecID; + public uint Unknown703; + public PetInfo Pet = new PetInfo(); + public bool BoostInProgress; // @todo + public uint[] ProfessionIds = new uint[2]; // @todo + public VisualItemInfo[] VisualItems = new VisualItemInfo[InventorySlots.BagEnd]; + + public struct VisualItemInfo + { + public uint DisplayId; + public uint DisplayEnchantId; + public byte InventoryType; + } + public struct PetInfo + { + public uint CreatureDisplayId; // PetCreatureDisplayID + public uint Level; // PetExperienceLevel + public uint CreatureFamily; // PetCreatureFamilyID + } + } + + public struct RestrictedFactionChangeRuleInfo + { + public RestrictedFactionChangeRuleInfo(uint mask, byte race) + { + Mask = mask; + Race = race; + } + + public uint Mask; + public byte Race; + } + } + + public class CreateCharacter : ClientPacket + { + public CreateCharacter(WorldPacket packet) : base(packet) { } + + public override void Read() + { + CreateInfo = new CharacterCreateInfo(); + uint nameLength = _worldPacket.ReadBits(6); + CreateInfo.TemplateSet.HasValue = _worldPacket.HasBit(); + CreateInfo.RaceId = (Race)_worldPacket.ReadUInt8(); + CreateInfo.ClassId = (Class)_worldPacket.ReadUInt8(); + CreateInfo.Sex = (Gender)_worldPacket.ReadUInt8(); + CreateInfo.Skin = _worldPacket.ReadUInt8(); + CreateInfo.Face = _worldPacket.ReadUInt8(); + CreateInfo.HairStyle = _worldPacket.ReadUInt8(); + CreateInfo.HairColor = _worldPacket.ReadUInt8(); + CreateInfo.FacialHairStyle = _worldPacket.ReadUInt8(); + CreateInfo.OutfitId = _worldPacket.ReadUInt8(); + + for (var i = 0; i < CreateInfo.CustomDisplay.GetLimit(); ++i) + CreateInfo.CustomDisplay.Add(_worldPacket.ReadUInt8()); + + CreateInfo.Name = _worldPacket.ReadString(nameLength); + if (CreateInfo.TemplateSet.HasValue) + CreateInfo.TemplateSet.Value = _worldPacket.ReadUInt32(); + } + + public CharacterCreateInfo CreateInfo; + } + + public class CreateChar : ServerPacket + { + public CreateChar() : base(ServerOpcodes.CreateChar) { } + + public override void Write() + { + _worldPacket.WriteUInt8(Code); + } + + public ResponseCodes Code; + } + + public class CharDelete : ClientPacket + { + public CharDelete(WorldPacket packet) : base(packet) { } + + public override void Read() + { + Guid = _worldPacket.ReadPackedGuid(); + } + + public ObjectGuid Guid; // Guid of the character to delete + } + + public class DeleteChar : ServerPacket + { + public DeleteChar() : base(ServerOpcodes.DeleteChar) { } + + public override void Write() + { + _worldPacket.WriteUInt8(Code); + } + + public ResponseCodes Code; + } + + public class CharacterRenameRequest : ClientPacket + { + public CharacterRenameRequest(WorldPacket packet) : base(packet) { } + + public override void Read() + { + RenameInfo = new CharacterRenameInfo(); + RenameInfo.Guid = _worldPacket.ReadPackedGuid(); + RenameInfo.NewName = _worldPacket.ReadString(_worldPacket.ReadBits(6)); + } + + public CharacterRenameInfo RenameInfo; + } + + public class CharacterRenameResult : ServerPacket + { + public CharacterRenameResult() : base(ServerOpcodes.CharacterRenameResult) + { + Guid = new Optional(); + } + + public override void Write() + { + _worldPacket.WriteUInt8(Result); + _worldPacket.WriteBit(Guid.HasValue); + _worldPacket.WriteBits(Name.Length, 6); + _worldPacket.FlushBits(); + + if (Guid.HasValue) + _worldPacket.WritePackedGuid(Guid.Value); + + _worldPacket.WriteString(Name); + } + + public string Name; + public ResponseCodes Result = 0; + public Optional Guid; + } + + public class CharCustomize : ClientPacket + { + public CharCustomize(WorldPacket packet) : base(packet) { } + + public override void Read() + { + CustomizeInfo = new CharCustomizeInfo(); + CustomizeInfo.CharGUID = _worldPacket.ReadPackedGuid(); + CustomizeInfo.SexID = (Gender)_worldPacket.ReadUInt8(); + CustomizeInfo.SkinID = _worldPacket.ReadUInt8(); + CustomizeInfo.HairColorID = _worldPacket.ReadUInt8(); + CustomizeInfo.HairStyleID = _worldPacket.ReadUInt8(); + CustomizeInfo.FacialHairStyleID = _worldPacket.ReadUInt8(); + CustomizeInfo.FaceID = _worldPacket.ReadUInt8(); + + for (var i = 0; i < CustomizeInfo.CustomDisplay.GetLimit(); ++i) + CustomizeInfo.CustomDisplay.Add(_worldPacket.ReadUInt8()); + + CustomizeInfo.CharName = _worldPacket.ReadString(_worldPacket.ReadBits(6)); + } + + public CharCustomizeInfo CustomizeInfo; + } + + // @todo: CharCustomizeResult + + public class CharRaceOrFactionChange : ClientPacket + { + public CharRaceOrFactionChange(WorldPacket packet) : base(packet) { } + + public override void Read() + { + RaceOrFactionChangeInfo = new CharRaceOrFactionChangeInfo(); + + RaceOrFactionChangeInfo.FactionChange = _worldPacket.HasBit(); + + uint nameLength = _worldPacket.ReadBits(6); + + RaceOrFactionChangeInfo.Guid = _worldPacket.ReadPackedGuid(); + RaceOrFactionChangeInfo.SexID = (Gender)_worldPacket.ReadUInt8(); + RaceOrFactionChangeInfo.RaceID = (Race)_worldPacket.ReadUInt8(); + + RaceOrFactionChangeInfo.SkinID = _worldPacket.ReadUInt8(); + RaceOrFactionChangeInfo.HairColorID = _worldPacket.ReadUInt8(); + RaceOrFactionChangeInfo.HairStyleID = _worldPacket.ReadUInt8(); + RaceOrFactionChangeInfo.FacialHairStyleID = _worldPacket.ReadUInt8(); + RaceOrFactionChangeInfo.FaceID = _worldPacket.ReadUInt8(); + + for (var i = 0; i < RaceOrFactionChangeInfo.CustomDisplay.GetLimit(); ++i) + RaceOrFactionChangeInfo.CustomDisplay.Add(_worldPacket.ReadUInt8()); + + RaceOrFactionChangeInfo.Name = _worldPacket.ReadString(nameLength); + } + + public CharRaceOrFactionChangeInfo RaceOrFactionChangeInfo; + } + + public class CharFactionChangeResult : ServerPacket + { + public CharFactionChangeResult() : base(ServerOpcodes.CharFactionChangeResult) + { + Display = new Optional(); + } + + public override void Write() + { + _worldPacket.WriteUInt8(Result); + _worldPacket.WritePackedGuid(Guid); + _worldPacket.WriteBit(Display.HasValue); + _worldPacket.FlushBits(); + + if (Display.HasValue) + { + _worldPacket.WriteBits(Display.Value.Name.Length, 6); + _worldPacket.WriteUInt8(Display.Value.SexID); + _worldPacket.WriteUInt8(Display.Value.SkinID); + _worldPacket.WriteUInt8(Display.Value.HairColorID); + _worldPacket.WriteUInt8(Display.Value.HairStyleID); + _worldPacket.WriteUInt8(Display.Value.FacialHairStyleID); + _worldPacket.WriteUInt8(Display.Value.FaceID); + _worldPacket.WriteUInt8(Display.Value.RaceID); + Display.Value.CustomDisplay.ForEach(id => _worldPacket.WriteUInt8(id)); + _worldPacket.WriteString(Display.Value.Name); + } + } + + public ResponseCodes Result = 0; + public ObjectGuid Guid; + public Optional Display; + + public class CharFactionChangeDisplayInfo + { + public string Name; + public byte SexID; + public byte SkinID; + public byte HairColorID; + public byte HairStyleID; + public byte FacialHairStyleID; + public byte FaceID; + public byte RaceID; + public Array CustomDisplay = new Array(PlayerConst.CustomDisplaySize); + } + } + + public class GenerateRandomCharacterName : ClientPacket + { + public GenerateRandomCharacterName(WorldPacket packet) : base(packet) { } + + public override void Read() + { + Race = _worldPacket.ReadUInt8(); + Sex = _worldPacket.ReadUInt8(); + } + + public byte Sex; + public byte Race; + } + + public class GenerateRandomCharacterNameResult : ServerPacket + { + public GenerateRandomCharacterNameResult() : base(ServerOpcodes.GenerateRandomCharacterNameResult) { } + + public override void Write() + { + _worldPacket.WriteBit(Success); + _worldPacket.WriteBits(Name.Length, 6); + + _worldPacket.WriteString(Name); + } + + public string Name; + public bool Success; + } + + public class ReorderCharacters : ClientPacket + { + public ReorderCharacters(WorldPacket packet) : base(packet) { } + + public override void Read() + { + uint count = _worldPacket.ReadBits(9); + for (var i = 0; i < count && i < WorldConfig.GetIntValue(WorldCfg.CharactersPerRealm); ++i) + { + ReorderInfo reorderInfo; + reorderInfo.PlayerGUID = _worldPacket.ReadPackedGuid(); + reorderInfo.NewPosition = _worldPacket.ReadUInt8(); + Entries[i] = reorderInfo; + } + } + + public ReorderInfo[] Entries = new ReorderInfo[WorldConfig.GetIntValue(WorldCfg.CharactersPerRealm)]; + + public struct ReorderInfo + { + public ObjectGuid PlayerGUID; + public byte NewPosition; + } + } + + public class UndeleteCharacter : ClientPacket + { + public UndeleteCharacter(WorldPacket packet) : base(packet) { } + + public override void Read() + { + UndeleteInfo = new CharacterUndeleteInfo(); + _worldPacket.WriteUInt32(UndeleteInfo.ClientToken); + _worldPacket.WritePackedGuid(UndeleteInfo.CharacterGuid); + } + + public CharacterUndeleteInfo UndeleteInfo; + } + + public class UndeleteCharacterResponse : ServerPacket + { + public UndeleteCharacterResponse() : base(ServerOpcodes.UndeleteCharacterResponse) { } + + public override void Write() + { + Contract.Assert(UndeleteInfo != null); + _worldPacket.WriteInt32(UndeleteInfo.ClientToken); + _worldPacket.WriteUInt32(Result); + _worldPacket.WritePackedGuid(UndeleteInfo.CharacterGuid); + } + + public CharacterUndeleteInfo UndeleteInfo; + public CharacterUndeleteResult Result; + } + + public class GetUndeleteCharacterCooldownStatus : ClientPacket + { + public GetUndeleteCharacterCooldownStatus(WorldPacket packet) : base(packet) { } + + public override void Read() { } + } + + public class UndeleteCooldownStatusResponse : ServerPacket + { + public UndeleteCooldownStatusResponse() : base(ServerOpcodes.UndeleteCooldownStatusResponse) { } + + public override void Write() + { + _worldPacket.WriteBit(OnCooldown); + _worldPacket.WriteUInt32(MaxCooldown); + _worldPacket.WriteUInt32(CurrentCooldown); + } + + public bool OnCooldown; // + public uint MaxCooldown; // Max. cooldown until next free character restoration. Displayed in undelete confirm message. (in sec) + public uint CurrentCooldown; // Current cooldown until next free character restoration. (in sec) + } + + public class PlayerLogin : ClientPacket + { + public PlayerLogin(WorldPacket packet) : base(packet) { } + + public override void Read() + { + Guid = _worldPacket.ReadPackedGuid(); + FarClip = _worldPacket.ReadFloat(); + } + + public ObjectGuid Guid; // Guid of the player that is logging in + float FarClip = 0.0f; // Visibility distance (for terrain) + } + + public class LoginVerifyWorld : ServerPacket + { + public LoginVerifyWorld() : base(ServerOpcodes.LoginVerifyWorld, ConnectionType.Instance) { } + + public override void Write() + { + _worldPacket.WriteInt32(MapID); + _worldPacket.WriteFloat(Pos.GetPositionX()); + _worldPacket.WriteFloat(Pos.GetPositionY()); + _worldPacket.WriteFloat(Pos.GetPositionZ()); + _worldPacket.WriteFloat(Pos.GetOrientation()); + _worldPacket.WriteUInt32(Reason); + } + + public int MapID = -1; + public Position Pos; + public uint Reason = 0; + } + + public class CharacterLoginFailed : ServerPacket + { + public CharacterLoginFailed(LoginFailureReason code) : base(ServerOpcodes.CharacterLoginFailed) + { + Code = code; + } + + public override void Write() + { + _worldPacket.WriteUInt8(Code); + } + + LoginFailureReason Code; + } + + public class LogoutRequest : ClientPacket + { + public LogoutRequest(WorldPacket packet) : base(packet) { } + + public override void Read() + { + IdleLogout = _worldPacket.HasBit(); + } + + bool IdleLogout; + } + + public class LogoutResponse : ServerPacket + { + public LogoutResponse() : base(ServerOpcodes.LogoutResponse, ConnectionType.Instance) { } + + public override void Write() + { + _worldPacket.WriteInt32(LogoutResult); + _worldPacket.WriteBit(Instant); + _worldPacket.FlushBits(); + } + + public int LogoutResult; + public bool Instant = false; + } + + public class LogoutComplete : ServerPacket + { + public LogoutComplete() : base(ServerOpcodes.LogoutComplete) { } + + public override void Write() { } + } + + public class LogoutCancel : ClientPacket + { + public LogoutCancel(WorldPacket packet) : base(packet) { } + + public override void Read() { } + } + + public class LogoutCancelAck : ServerPacket + { + public LogoutCancelAck() : base(ServerOpcodes.LogoutCancelAck, ConnectionType.Instance) { } + + public override void Write() { } + } + + public class LoadingScreenNotify : ClientPacket + { + public LoadingScreenNotify(WorldPacket packet) : base(packet) { } + + public override void Read() + { + MapID = _worldPacket.ReadInt32(); + Showing = _worldPacket.HasBit(); + } + + int MapID = -1; + bool Showing; + } + + public class InitialSetup : ServerPacket + { + public InitialSetup() : base(ServerOpcodes.InitialSetup, ConnectionType.Instance) { } + + public override void Write() + { + _worldPacket.WriteUInt8(ServerExpansionLevel); + _worldPacket.WriteUInt8(ServerExpansionTier); + } + + public byte ServerExpansionTier; + public byte ServerExpansionLevel; + } + + public class SetActionBarToggles : ClientPacket + { + public SetActionBarToggles(WorldPacket packet) : base(packet) { } + + public override void Read() + { + Mask = _worldPacket.ReadUInt8(); + } + + public byte Mask; + } + + public class RequestPlayedTime : ClientPacket + { + public RequestPlayedTime(WorldPacket packet) : base(packet) { } + + public override void Read() + { + TriggerScriptEvent = _worldPacket.HasBit(); + } + + public bool TriggerScriptEvent; + } + + public class PlayedTime : ServerPacket + { + public PlayedTime() : base(ServerOpcodes.PlayedTime, ConnectionType.Instance) { } + + public override void Write() + { + _worldPacket.WriteUInt32(TotalTime); + _worldPacket.WriteUInt32(LevelTime); + _worldPacket.WriteBit(TriggerEvent); + _worldPacket.FlushBits(); + } + + public uint TotalTime; + public uint LevelTime; + public bool TriggerEvent; + } + + public class SetTitle : ClientPacket + { + public int TitleID; + + public SetTitle(WorldPacket packet) : base(packet) { } + + public override void Read() + { + TitleID = _worldPacket.ReadInt32(); + } + } + + public class AlterApperance : ClientPacket + { + public AlterApperance(WorldPacket packet) : base(packet) { } + + public override void Read() + { + NewHairStyle = _worldPacket.ReadUInt32(); + NewHairColor = _worldPacket.ReadUInt32(); + NewFacialHair = _worldPacket.ReadUInt32(); + NewSkinColor = _worldPacket.ReadUInt32(); + NewFace = _worldPacket.ReadUInt32(); + + for (var i = 0; i < NewCustomDisplay.GetLimit(); ++i) + NewCustomDisplay.Add(_worldPacket.ReadUInt32()); + } + + public uint NewHairStyle; + public uint NewHairColor; + public uint NewFacialHair; + public uint NewSkinColor; + public uint NewFace; + public Array NewCustomDisplay = new Array(PlayerConst.CustomDisplaySize); + } + + public class BarberShopResult : ServerPacket + { + public BarberShopResult(ResultEnum result) : base(ServerOpcodes.BarberShopResult) + { + Result = result; + } + + public override void Write() + { + _worldPacket.WriteInt32(Result); + } + + public ResultEnum Result; + + public enum ResultEnum + { + Success = 0, + NoMoney = 1, + NotOnChair = 2, + NoMoney2 = 3 + } + } + + class LogXPGain : ServerPacket + { + public LogXPGain() : base(ServerOpcodes.LogXpGain) { } + + public override void Write() + { + _worldPacket.WritePackedGuid(Victim); + _worldPacket.WriteInt32(Original); + _worldPacket.WriteUInt8(Reason); + _worldPacket.WriteInt32(Amount); + _worldPacket.WriteFloat(GroupBonus); + _worldPacket.WriteBit(ReferAFriend); + _worldPacket.FlushBits(); + } + + public ObjectGuid Victim; + public int Original; + public PlayerLogXPReason Reason; + public int Amount; + public float GroupBonus; + public bool ReferAFriend; + } + + class TitleEarned : ServerPacket + { + public TitleEarned(ServerOpcodes opcode) : base(opcode) { } + + public override void Write() + { + _worldPacket.WriteUInt32(Index); + } + + public uint Index; + } + + class SetFactionAtWar : ClientPacket + { + public SetFactionAtWar(WorldPacket packet) : base(packet) { } + + public override void Read() + { + FactionIndex = _worldPacket.ReadUInt8(); + } + + public byte FactionIndex; + } + + class SetFactionNotAtWar : ClientPacket + { + public SetFactionNotAtWar(WorldPacket packet) : base(packet) { } + + public override void Read() + { + FactionIndex = _worldPacket.ReadUInt8(); + } + + public byte FactionIndex; + } + + class SetFactionInactive : ClientPacket + { + public SetFactionInactive(WorldPacket packet) : base(packet) { } + + public override void Read() + { + Index = _worldPacket.ReadUInt32(); + State = _worldPacket.HasBit(); + } + + public uint Index; + public bool State; + } + + class SetWatchedFaction : ClientPacket + { + public SetWatchedFaction(WorldPacket packet) : base(packet) { } + + public override void Read() + { + FactionIndex = _worldPacket.ReadUInt32(); + } + + public uint FactionIndex; + } + + class SetFactionVisible : ServerPacket + { + public SetFactionVisible(bool visible) : base(visible ? ServerOpcodes.SetFactionVisible : ServerOpcodes.SetFactionNotVisible, ConnectionType.Instance) { } + + public override void Write() + { + _worldPacket.WriteUInt32(FactionIndex); + } + + public uint FactionIndex; + } + + class CharCustomizeResponse : ServerPacket + { + public CharCustomizeResponse(CharCustomizeInfo customizeInfo) : base(ServerOpcodes.CharCustomize) + { + CharGUID = customizeInfo.CharGUID; + SexID = (byte)customizeInfo.SexID; + SkinID = customizeInfo.SkinID; + HairColorID = customizeInfo.HairColorID; + HairStyleID = customizeInfo.HairStyleID; + FacialHairStyleID = customizeInfo.FacialHairStyleID; + FaceID = customizeInfo.FaceID; + CharName = customizeInfo.CharName; + CustomDisplay = customizeInfo.CustomDisplay; + } + + public override void Write() + { + _worldPacket.WritePackedGuid(CharGUID); + _worldPacket.WriteUInt8(SexID); + _worldPacket.WriteUInt8(SkinID); + _worldPacket.WriteUInt8(HairColorID); + _worldPacket.WriteUInt8(HairStyleID); + _worldPacket.WriteUInt8(FacialHairStyleID); + _worldPacket.WriteUInt8(FaceID); + CustomDisplay.ForEach(id => _worldPacket.WriteUInt8(id)); + _worldPacket.WriteBits(CharName.Length, 6); + _worldPacket.FlushBits(); + _worldPacket.WriteString(CharName); + } + + ObjectGuid CharGUID; + string CharName = ""; + byte SexID; + byte SkinID; + byte HairColorID; + byte HairStyleID; + byte FacialHairStyleID; + byte FaceID; + public Array CustomDisplay = new Array(PlayerConst.CustomDisplaySize); + } + + class CharCustomizeFailed : ServerPacket + { + public CharCustomizeFailed() : base(ServerOpcodes.CharCustomizeFailed) { } + + public override void Write() + { + _worldPacket.WriteUInt8(Result); + _worldPacket.WritePackedGuid(CharGUID); + } + + public byte Result; + public ObjectGuid CharGUID; + } + + class SetPlayerDeclinedNames : ClientPacket + { + public SetPlayerDeclinedNames(WorldPacket packet) : base(packet) + { + DeclinedNames = new DeclinedName(); + } + + public override void Read() + { + Player = _worldPacket.ReadPackedGuid(); ; + + byte[] stringLengths = new byte[SharedConst.MaxDeclinedNameCases]; + + for (byte i = 0; i < SharedConst.MaxDeclinedNameCases; ++i) + stringLengths[i] = _worldPacket.ReadBits(7); + + for (byte i = 0; i < SharedConst.MaxDeclinedNameCases; ++i) + DeclinedNames.name[i] = _worldPacket.ReadString(stringLengths[i]); + } + + public ObjectGuid Player; + public DeclinedName DeclinedNames; + } + + class SetPlayerDeclinedNamesResult : ServerPacket + { + public SetPlayerDeclinedNamesResult() : base(ServerOpcodes.SetPlayerDeclinedNamesResult) { } + + public override void Write() + { + _worldPacket.WriteInt32(ResultCode); + _worldPacket.WritePackedGuid(Player); + } + + public ObjectGuid Player; + public DeclinedNameResult ResultCode; + } + + //Structs + public class CharacterCreateInfo + { + // User specified variables + public Race RaceId = Race.None; + public Class ClassId = Class.None; + public Gender Sex = Gender.None; + public byte Skin = 0; + public byte Face = 0; + public byte HairStyle = 0; + public byte HairColor = 0; + public byte FacialHairStyle = 0; + public Array CustomDisplay = new Array(PlayerConst.CustomDisplaySize); + public byte OutfitId = 0; + public Optional TemplateSet = new Optional(); + public string Name; + + // Server side data + public byte CharCount = 0; + } + + public class CharacterRenameInfo + { + public string NewName; + public ObjectGuid Guid; + } + + public class CharCustomizeInfo + { + public byte HairStyleID = 0; + public byte FaceID = 0; + public ObjectGuid CharGUID; + public Gender SexID = Gender.None; + public string CharName; + public byte HairColorID = 0; + public byte FacialHairStyleID = 0; + public byte SkinID = 0; + public Array CustomDisplay = new Array(PlayerConst.CustomDisplaySize); + } + + public class CharRaceOrFactionChangeInfo + { + public byte HairColorID; + public Race RaceID = Race.None; + public Gender SexID = Gender.None; + public byte SkinID; + public byte FacialHairStyleID; + public ObjectGuid Guid; + public bool FactionChange = false; + public string Name; + public byte FaceID; + public byte HairStyleID; + public Array CustomDisplay = new Array(PlayerConst.CustomDisplaySize); + } + + public class CharacterUndeleteInfo + { // User specified variables + public ObjectGuid CharacterGuid; // Guid of the character to restore + public int ClientToken = 0; // @todo: research + + // Server side data + public string Name; + } +} diff --git a/Game/Network/Packets/ChatPackets.cs b/Game/Network/Packets/ChatPackets.cs new file mode 100644 index 000000000..ac4d12549 --- /dev/null +++ b/Game/Network/Packets/ChatPackets.cs @@ -0,0 +1,471 @@ +/* + * Copyright (C) 2012-2017 CypherCore + * + * 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, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +using Framework.Constants; +using Game.Entities; +using Game.Groups; + +namespace Game.Network.Packets +{ + public class ChatMessage : ClientPacket + { + public ChatMessage(WorldPacket packet) : base(packet) { } + + public override void Read() + { + Language = (Language)_worldPacket.ReadInt32(); + uint len = _worldPacket.ReadBits(9); + Text = _worldPacket.ReadString(len); + } + + public string Text; + public Language Language = Language.Universal; + } + + public class ChatMessageWhisper : ClientPacket + { + public ChatMessageWhisper(WorldPacket packet) : base(packet) { } + + public override void Read() + { + Language = (Language)_worldPacket.ReadInt32(); + uint targetLen = _worldPacket.ReadBits(9); + uint textLen = _worldPacket.ReadBits(9); + Target = _worldPacket.ReadString(targetLen); + Text = _worldPacket.ReadString(textLen); + } + + public Language Language = Language.Universal; + public string Text; + public string Target; + } + + public class ChatMessageChannel : ClientPacket + { + public ChatMessageChannel(WorldPacket packet) : base(packet) { } + + public override void Read() + { + Language = (Language)_worldPacket.ReadInt32(); + uint targetLen = _worldPacket.ReadBits(9); + uint textLen = _worldPacket.ReadBits(9); + Target = _worldPacket.ReadString(targetLen); + Text = _worldPacket.ReadString(textLen); + } + + public Language Language = Language.Universal; + public string Text; + public string Target; + } + + public class ChatAddonMessage : ClientPacket + { + public ChatAddonMessage(WorldPacket packet) : base(packet) { } + + public override void Read() + { + uint prefixLen = _worldPacket.ReadBits(5); + uint textLen = _worldPacket.ReadBits(9); + Prefix = _worldPacket.ReadString(prefixLen); + Text = _worldPacket.ReadString(textLen); + } + + public string Prefix; + public string Text; + } + + public class ChatAddonMessageWhisper : ClientPacket + { + public ChatAddonMessageWhisper(WorldPacket packet) : base(packet) { } + + public override void Read() + { + uint targetLen = _worldPacket.ReadBits(9); + uint prefixLen = _worldPacket.ReadBits(5); + uint textLen = _worldPacket.ReadBits(9); + Target = _worldPacket.ReadString(targetLen); + Prefix = _worldPacket.ReadString(prefixLen); + Text = _worldPacket.ReadString(textLen); + } + + public string Prefix; + public string Target; + public string Text; + } + + class ChatAddonMessageChannel : ClientPacket + { + public ChatAddonMessageChannel(WorldPacket packet) : base(packet) { } + + public override void Read() + { + uint targetLen = _worldPacket.ReadBits(9); + uint prefixLen = _worldPacket.ReadBits(5); + uint textLen = _worldPacket.ReadBits(9); + Target = _worldPacket.ReadString(targetLen); + Prefix = _worldPacket.ReadString(prefixLen); + Text = _worldPacket.ReadString(textLen); + } + + public string Text; + public string Target; + public string Prefix; + } + + public class ChatMessageDND : ClientPacket + { + public ChatMessageDND(WorldPacket packet) : base(packet) { } + + public override void Read() + { + uint len = _worldPacket.ReadBits(9); + Text = _worldPacket.ReadString(len); + } + + public string Text; + } + + public class ChatMessageAFK : ClientPacket + { + public ChatMessageAFK(WorldPacket packet) : base(packet) { } + + public override void Read() + { + uint len = _worldPacket.ReadBits(9); + Text = _worldPacket.ReadString(len); + } + + public string Text; + } + + public class ChatMessageEmote : ClientPacket + { + public ChatMessageEmote(WorldPacket packet) : base(packet) { } + + public override void Read() + { + uint len = _worldPacket.ReadBits(9); + Text = _worldPacket.ReadString(len); + } + + public string Text; + } + + public class ChatPkt : ServerPacket + { + public ChatPkt() : base(ServerOpcodes.Chat) { } + + public void Initialize(ChatMsg chatType, Language language, WorldObject sender, WorldObject receiver, string message, uint achievementId = 0, string channelName = "", LocaleConstant locale = LocaleConstant.enUS, string addonPrefix = "") + { + // Clear everything because same packet can be used multiple times + Clear(); + + SenderGUID.Clear(); + SenderAccountGUID.Clear(); + SenderGuildGUID.Clear(); + PartyGUID.Clear(); + TargetGUID.Clear(); + SenderName = ""; + TargetName = ""; + _ChatFlags = ChatFlags.None; + + SlashCmd = chatType; + _Language = language; + + if (sender) + SetSender(sender, locale); + + if (receiver) + SetReceiver(receiver, locale); + + SenderVirtualAddress = Global.WorldMgr.GetVirtualRealmAddress(); + TargetVirtualAddress = Global.WorldMgr.GetVirtualRealmAddress(); + AchievementID = achievementId; + Channel = channelName; + Prefix = addonPrefix; + ChatText = message; + } + + void SetSender(WorldObject sender, LocaleConstant locale) + { + SenderGUID = sender.GetGUID(); + + Creature creatureSender = sender.ToCreature(); + if (creatureSender) + SenderName = creatureSender.GetName(locale); + + Player playerSender = sender.ToPlayer(); + if (playerSender) + { + SenderAccountGUID = playerSender.GetSession().GetAccountGUID(); + _ChatFlags = playerSender.GetChatFlags(); + + SenderGuildGUID = ObjectGuid.Create(HighGuid.Guild, playerSender.GetGuildId()); + + Group group = playerSender.GetGroup(); + if (group) + PartyGUID = group.GetGUID(); + } + } + + public void SetReceiver(WorldObject receiver, LocaleConstant locale) + { + TargetGUID = receiver.GetGUID(); + + Creature creatureReceiver = receiver.ToCreature(); + if (creatureReceiver) + TargetName = creatureReceiver.GetName(locale); + } + + public override void Write() + { + _worldPacket.WriteUInt8(SlashCmd); + _worldPacket.WriteUInt8(_Language); + _worldPacket.WritePackedGuid(SenderGUID); + _worldPacket.WritePackedGuid(SenderGuildGUID); + _worldPacket.WritePackedGuid(SenderAccountGUID); + _worldPacket.WritePackedGuid(TargetGUID); + _worldPacket.WriteUInt32(TargetVirtualAddress); + _worldPacket.WriteUInt32(SenderVirtualAddress); + _worldPacket.WritePackedGuid(PartyGUID); + _worldPacket.WriteUInt32(AchievementID); + _worldPacket.WriteFloat(DisplayTime); + _worldPacket.WriteBits(SenderName.Length, 11); + _worldPacket.WriteBits(TargetName.Length, 11); + _worldPacket.WriteBits(Prefix.Length, 5); + _worldPacket.WriteBits(Channel.Length, 7); + _worldPacket.WriteBits(ChatText.Length, 12); + _worldPacket.WriteBits((uint)_ChatFlags, 11); + _worldPacket.WriteBit(HideChatLog); + _worldPacket.WriteBit(FakeSenderName); + + _worldPacket.WriteString(SenderName); + _worldPacket.WriteString(TargetName); + _worldPacket.WriteString(Prefix); + _worldPacket.WriteString(Channel); + _worldPacket.WriteString(ChatText); + } + + public ChatMsg SlashCmd = 0; + public Language _Language = Language.Universal; + public ObjectGuid SenderGUID; + public ObjectGuid SenderGuildGUID; + public ObjectGuid SenderAccountGUID; + public ObjectGuid TargetGUID; + public ObjectGuid PartyGUID; + public uint SenderVirtualAddress; + public uint TargetVirtualAddress; + public string SenderName = ""; + public string TargetName = ""; + public string Prefix = ""; + public string Channel = ""; + public string ChatText = ""; + public uint AchievementID = 0; + public ChatFlags _ChatFlags = 0; + public float DisplayTime = 0.0f; + public bool HideChatLog = false; + public bool FakeSenderName = false; + } + + public class EmoteMessage : ServerPacket + { + public EmoteMessage() : base(ServerOpcodes.Emote, ConnectionType.Instance) { } + + public override void Write() + { + _worldPacket.WritePackedGuid(Guid); + _worldPacket.WriteUInt32(EmoteID); + } + + public ObjectGuid Guid; + public int EmoteID; + } + + public class CTextEmote : ClientPacket + { + public CTextEmote(WorldPacket packet) : base(packet) { } + + public override void Read() + { + Target = _worldPacket.ReadPackedGuid(); + EmoteID = _worldPacket.ReadInt32(); + SoundIndex = _worldPacket.ReadInt32(); + } + + public ObjectGuid Target; + public int EmoteID; + public int SoundIndex; + } + + public class STextEmote : ServerPacket + { + public STextEmote() : base(ServerOpcodes.TextEmote, ConnectionType.Instance) { } + + public override void Write() + { + _worldPacket.WritePackedGuid(SourceGUID); + _worldPacket.WritePackedGuid(SourceAccountGUID); + _worldPacket.WriteUInt32(EmoteID); + _worldPacket.WriteInt32(SoundIndex); + _worldPacket.WritePackedGuid(TargetGUID); + } + + public ObjectGuid SourceGUID; + public ObjectGuid SourceAccountGUID; + public ObjectGuid TargetGUID; + public int SoundIndex = -1; + public int EmoteID; + } + + public class PrintNotification : ServerPacket + { + public PrintNotification(string notifyText) : base(ServerOpcodes.PrintNotification) + { + NotifyText = notifyText; + } + + public override void Write() + { + _worldPacket.WriteBits(NotifyText.Length, 12); + _worldPacket.WriteString(NotifyText); + } + + public string NotifyText; + } + + public class EmoteClient : ClientPacket + { + public EmoteClient(WorldPacket packet) : base(packet) { } + + public override void Read() { } + } + + class ChatPlayerNotfound : ServerPacket + { + public ChatPlayerNotfound(string name) : base(ServerOpcodes.ChatPlayerNotfound) + { + Name = name; + } + + public override void Write() + { + _worldPacket.WriteBits(Name.Length, 9); + _worldPacket.WriteString(Name); + } + + string Name; + } + + class ChatPlayerAmbiguous : ServerPacket + { + public ChatPlayerAmbiguous(string name) : base(ServerOpcodes.ChatPlayerAmbiguous) + { + Name = name; + } + + public override void Write() + { + _worldPacket.WriteBits(Name.Length, 9); + _worldPacket.WriteString(Name); + } + + string Name; + } + + class ChatRestricted : ServerPacket + { + public ChatRestricted(ChatRestrictionType reason) : base(ServerOpcodes.ChatRestricted) + { + Reason = reason; + } + + public override void Write() + { + _worldPacket.WriteUInt8(Reason); + } + + ChatRestrictionType Reason; + } + + class ChatServerMessage : ServerPacket + { + public ChatServerMessage() : base(ServerOpcodes.ChatServerMessage) { } + + public override void Write() + { + _worldPacket.WriteUInt32(MessageID); + + _worldPacket.WriteBits(StringParam.Length, 11); + _worldPacket.WriteString(StringParam); + } + + public int MessageID; + public string StringParam = ""; + } + + class ChatRegisterAddonPrefixes : ClientPacket + { + public ChatRegisterAddonPrefixes(WorldPacket packet) : base(packet) { } + + public override void Read() + { + int count = _worldPacket.ReadInt32(); + + for (int i = 0; i < count && i < 64; ++i) + Prefixes[i] = _worldPacket.ReadString(_worldPacket.ReadBits(5)); + } + + public string[] Prefixes = new string[64]; + } + + class ChatUnregisterAllAddonPrefixes : ClientPacket + { + public ChatUnregisterAllAddonPrefixes(WorldPacket packet) : base(packet) { } + + public override void Read() { } + } + + class DefenseMessage : ServerPacket + { + public DefenseMessage() : base(ServerOpcodes.DefenseMessage) { } + + public override void Write() + { + _worldPacket .WriteUInt32(ZoneID); + _worldPacket.WriteBits(MessageText.Length, 12); + _worldPacket.FlushBits(); + _worldPacket.WriteString(MessageText); + } + + public uint ZoneID; + public string MessageText = ""; + } + + class ChatReportIgnored : ClientPacket + { + public ChatReportIgnored(WorldPacket packet) : base(packet) { } + + public override void Read() + { + IgnoredGUID = _worldPacket.ReadPackedGuid(); + Reason = _worldPacket.ReadUInt8(); + } + + public ObjectGuid IgnoredGUID; + public byte Reason; + } +} diff --git a/Game/Network/Packets/ClientConfigPackets.cs b/Game/Network/Packets/ClientConfigPackets.cs new file mode 100644 index 000000000..bdeae569b --- /dev/null +++ b/Game/Network/Packets/ClientConfigPackets.cs @@ -0,0 +1,126 @@ +/* + * Copyright (C) 2012-2017 CypherCore + * + * 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, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +using Framework.Constants; +using Framework.IO; +using Game.Entities; + +namespace Game.Network.Packets +{ + public class AccountDataTimes : ServerPacket + { + public AccountDataTimes() : base(ServerOpcodes.AccountDataTimes) { } + + public override void Write() + { + _worldPacket.WritePackedGuid(PlayerGuid); + _worldPacket.WriteUInt32(ServerTime); + for (int i = 0; i < (int)AccountDataTypes.Max; ++i) + _worldPacket.WriteUInt32(AccountTimes[i]); + } + + public ObjectGuid PlayerGuid; + public uint ServerTime = 0; + public uint[] AccountTimes = new uint[(int)AccountDataTypes.Max]; + } + + public class ClientCacheVersion : ServerPacket + { + public ClientCacheVersion() : base(ServerOpcodes.CacheVersion) { } + + public override void Write() + { + _worldPacket.WriteUInt32(CacheVersion); + } + + public uint CacheVersion = 0; + } + + public class RequestAccountData : ClientPacket + { + public RequestAccountData(WorldPacket packet) : base(packet) { } + + public override void Read() + { + PlayerGuid = _worldPacket.ReadPackedGuid(); + DataType = (AccountDataTypes)_worldPacket.ReadBits(3); + } + + public ObjectGuid PlayerGuid; + public AccountDataTypes DataType = 0; + } + + public class UpdateAccountData : ServerPacket + { + public UpdateAccountData() : base(ServerOpcodes.UpdateAccountData) { } + + public override void Write() + { + _worldPacket.WritePackedGuid(Player); + _worldPacket.WriteUInt32(Time); + _worldPacket.WriteUInt32(Size); + _worldPacket.WriteBits(DataType, 3); + + var bytes = CompressedData.GetData(); + _worldPacket.WriteUInt32(bytes.Length); + _worldPacket.WriteBytes(bytes); + } + + public ObjectGuid Player; + public uint Time = 0; // UnixTime + public uint Size = 0; // decompressed size + public AccountDataTypes DataType = 0; + public ByteBuffer CompressedData; + } + + public class UserClientUpdateAccountData : ClientPacket + { + public UserClientUpdateAccountData(WorldPacket packet) : base(packet) { } + + public override void Read() + { + PlayerGuid = _worldPacket.ReadPackedGuid(); + Time = _worldPacket.ReadUInt32(); + Size = _worldPacket.ReadUInt32(); + DataType = (AccountDataTypes)_worldPacket.ReadBits(3); + + uint compressedSize = _worldPacket.ReadUInt32(); + if (compressedSize != 0) + { + CompressedData = new ByteBuffer(_worldPacket.ReadBytes(compressedSize)); + } + } + + public ObjectGuid PlayerGuid; + public uint Time = 0; // UnixTime + public uint Size = 0; // decompressed size + public AccountDataTypes DataType = 0; + public ByteBuffer CompressedData; + } + + class SetAdvancedCombatLogging : ClientPacket + { + public SetAdvancedCombatLogging(WorldPacket packet) : base(packet) { } + + public override void Read() + { + Enable = _worldPacket.HasBit(); + } + + public bool Enable; + } +} diff --git a/Game/Network/Packets/CollectionPackets.cs b/Game/Network/Packets/CollectionPackets.cs new file mode 100644 index 000000000..22fb3c557 --- /dev/null +++ b/Game/Network/Packets/CollectionPackets.cs @@ -0,0 +1,43 @@ +/* + * Copyright (C) 2012-2017 CypherCore + * + * 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, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +namespace Game.Network.Packets +{ + public enum CollectionType + { + None = -1, + Toybox = 1, + Appearance = 3, + TransmogSet = 4 + } + + class CollectionItemSetFavorite : ClientPacket + { + public CollectionItemSetFavorite(WorldPacket packet) : base(packet) { } + + public override void Read() + { + Type = (CollectionType)_worldPacket.ReadUInt32(); + ID = _worldPacket.ReadUInt32(); + IsFavorite = _worldPacket.HasBit(); + } + + public CollectionType Type; + public uint ID; + public bool IsFavorite; + } +} diff --git a/Game/Network/Packets/CombatLogPackets.cs b/Game/Network/Packets/CombatLogPackets.cs new file mode 100644 index 000000000..d1f66c534 --- /dev/null +++ b/Game/Network/Packets/CombatLogPackets.cs @@ -0,0 +1,704 @@ +/* + * Copyright (C) 2012-2017 CypherCore + * + * 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, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +using Framework.Constants; +using Framework.Dynamic; +using Game.Entities; +using System; +using System.Collections.Generic; + +namespace Game.Network.Packets +{ + class CombatLogServerPacket : ServerPacket + { + public CombatLogServerPacket(ServerOpcodes opcode, ConnectionType connection = ConnectionType.Realm) : base(opcode, connection) + { + LogData = new SpellCastLogData(); + } + + public override void Write() { } + + public void DisableAdvancedCombatLogging() + { + LogData = null; + } + + public void WriteLogDataBit() + { + _worldPacket.WriteBit(LogData != null); + } + + public void FlushBits() + { + _worldPacket.FlushBits(); + } + + public void WriteLogData() + { + if (LogData != null) + LogData.Write(_worldPacket); + } + + internal SpellCastLogData LogData; + } + + class SpellNonMeleeDamageLog : CombatLogServerPacket + { + public SpellNonMeleeDamageLog() : base(ServerOpcodes.SpellNonMeleeDamageLog, ConnectionType.Instance) { } + + public override void Write() + { + _worldPacket.WritePackedGuid(Me); + _worldPacket.WritePackedGuid(CasterGUID); + _worldPacket.WritePackedGuid(CastID); + _worldPacket.WriteInt32(SpellID); + _worldPacket.WriteInt32(SpellXSpellVisualID); + _worldPacket.WriteInt32(Damage); + _worldPacket.WriteInt32(Overkill); + _worldPacket.WriteUInt8(SchoolMask); + _worldPacket.WriteInt32(ShieldBlock); + _worldPacket.WriteInt32(Resisted); + _worldPacket.WriteInt32(Absorbed); + + _worldPacket.WriteBit(Periodic); + _worldPacket.WriteBits(Flags, 7); + _worldPacket.WriteBit(false); // Debug info + WriteLogDataBit(); + _worldPacket.WriteBit(SandboxScaling.HasValue); + FlushBits(); + WriteLogData(); + if (SandboxScaling.HasValue) + SandboxScaling.Value.Write(_worldPacket); + } + + public ObjectGuid Me; + public ObjectGuid CasterGUID; + public ObjectGuid CastID; + public int SpellID; + public int SpellXSpellVisualID; + public int Damage; + public int Overkill; + public byte SchoolMask; + public int ShieldBlock; + public int Resisted; + public bool Periodic; + public int Absorbed; + public int Flags; + // Optional DebugInfo; + Optional SandboxScaling = new Optional(); + } + + class EnvironmentalDamageLog : CombatLogServerPacket + { + public EnvironmentalDamageLog() : base(ServerOpcodes.EnvironmentalDamageLog) { } + + public override void Write() + { + _worldPacket.WritePackedGuid(Victim); + _worldPacket.WriteUInt8(Type); + _worldPacket.WriteInt32(Amount); + _worldPacket.WriteInt32(Resisted); + _worldPacket.WriteInt32(Absorbed); + + WriteLogDataBit(); + FlushBits(); + WriteLogData(); + } + + public ObjectGuid Victim; + public EnviromentalDamage Type; + public int Amount; + public int Resisted; + public int Absorbed; + } + + class SpellExecuteLog : CombatLogServerPacket + { + public SpellExecuteLog() : base(ServerOpcodes.SpellExecuteLog, ConnectionType.Instance) { } + + public override void Write() + { + _worldPacket.WritePackedGuid(Caster); + _worldPacket.WriteUInt32(SpellID); + _worldPacket.WriteUInt32(Effects.Count); + + foreach (SpellLogEffect effect in Effects) + { + _worldPacket.WriteUInt32(effect.Effect); + + _worldPacket.WriteUInt32(effect.PowerDrainTargets.Count); + _worldPacket.WriteUInt32(effect.ExtraAttacksTargets.Count); + _worldPacket.WriteUInt32(effect.DurabilityDamageTargets.Count); + _worldPacket.WriteUInt32(effect.GenericVictimTargets.Count); + _worldPacket.WriteUInt32(effect.TradeSkillTargets.Count); + _worldPacket.WriteUInt32(effect.FeedPetTargets.Count); + + foreach (SpellLogEffectPowerDrainParams powerDrainTarget in effect.PowerDrainTargets) + { + _worldPacket.WritePackedGuid(powerDrainTarget.Victim); + _worldPacket.WriteUInt32(powerDrainTarget.Points); + _worldPacket.WriteUInt32(powerDrainTarget.PowerType); + _worldPacket.WriteFloat(powerDrainTarget.Amplitude); + } + + foreach (SpellLogEffectExtraAttacksParams extraAttacksTarget in effect.ExtraAttacksTargets) + { + _worldPacket.WritePackedGuid(extraAttacksTarget.Victim); + _worldPacket.WriteUInt32(extraAttacksTarget.NumAttacks); + } + + foreach (SpellLogEffectDurabilityDamageParams durabilityDamageTarget in effect.DurabilityDamageTargets) + { + _worldPacket.WritePackedGuid(durabilityDamageTarget.Victim); + _worldPacket.WriteInt32(durabilityDamageTarget.ItemID); + _worldPacket.WriteInt32(durabilityDamageTarget.Amount); + } + + foreach (SpellLogEffectGenericVictimParams genericVictimTarget in effect.GenericVictimTargets) + _worldPacket.WritePackedGuid(genericVictimTarget.Victim); + + foreach (SpellLogEffectTradeSkillItemParams tradeSkillTarget in effect.TradeSkillTargets) + _worldPacket.WriteInt32(tradeSkillTarget.ItemID); + + + foreach (SpellLogEffectFeedPetParams feedPetTarget in effect.FeedPetTargets) + _worldPacket.WriteInt32(feedPetTarget.ItemID); + } + + WriteLogDataBit(); + FlushBits(); + WriteLogData(); + } + + public ObjectGuid Caster; + public uint SpellID; + public List Effects = new List(); + + public class SpellLogEffect + { + public int Effect; + + public List PowerDrainTargets = new List(); + public List ExtraAttacksTargets = new List(); + public List DurabilityDamageTargets = new List(); + public List GenericVictimTargets = new List(); + public List TradeSkillTargets = new List(); + public List FeedPetTargets = new List(); + } + } + + class SpellHealLog : CombatLogServerPacket + { + public SpellHealLog() : base(ServerOpcodes.SpellHealLog, ConnectionType.Instance) { } + + public override void Write() + { + _worldPacket.WritePackedGuid(TargetGUID); + _worldPacket.WritePackedGuid(CasterGUID); + + _worldPacket.WriteUInt32(SpellID); + _worldPacket.WriteUInt32(Health); + _worldPacket.WriteUInt32(OverHeal); + _worldPacket.WriteUInt32(Absorbed); + + _worldPacket.WriteBit(Crit); + + _worldPacket.WriteBit(CritRollMade.HasValue); + _worldPacket.WriteBit(CritRollNeeded.HasValue); + WriteLogDataBit(); + _worldPacket.WriteBit(SandboxScaling.HasValue); + FlushBits(); + + WriteLogData(); + + if (CritRollMade.HasValue) + _worldPacket.WriteFloat(CritRollMade.Value); + + if (CritRollNeeded.HasValue) + _worldPacket.WriteFloat(CritRollNeeded.Value); + + if (SandboxScaling.HasValue) + SandboxScaling.Value.Write(_worldPacket); + } + + public ObjectGuid CasterGUID; + public ObjectGuid TargetGUID; + public uint SpellID; + public uint Health; + public uint OverHeal; + public uint Absorbed; + public bool Crit; + public Optional CritRollMade; + public Optional CritRollNeeded; + Optional SandboxScaling = new Optional(); + } + + class SpellPeriodicAuraLog : CombatLogServerPacket + { + public SpellPeriodicAuraLog() : base(ServerOpcodes.SpellPeriodicAuraLog, ConnectionType.Instance) { } + + public override void Write() + { + _worldPacket.WritePackedGuid(TargetGUID); + _worldPacket.WritePackedGuid(CasterGUID); + _worldPacket.WriteUInt32(SpellID); + _worldPacket.WriteUInt32(Effects.Count); + WriteLogDataBit(); + FlushBits(); + + Effects.ForEach(p => p.Write(_worldPacket)); + + WriteLogData(); + } + + public ObjectGuid TargetGUID; + public ObjectGuid CasterGUID; + public uint SpellID; + public List Effects = new List(); + + public struct PeriodicalAuraLogEffectDebugInfo + { + public float CritRollMade; + public float CritRollNeeded; + } + + public class SpellLogEffect + { + public void Write(WorldPacket data) + { + data.WriteUInt32(Effect); + data.WriteUInt32(Amount); + data.WriteUInt32(OverHealOrKill); + data.WriteUInt32(SchoolMaskOrPower); + data.WriteUInt32(AbsorbedOrAmplitude); + data.WriteUInt32(Resisted); + + data.WriteBit(Crit); + data.WriteBit(DebugInfo.HasValue); + data.WriteBit(SandboxScaling.HasValue); + data.FlushBits(); + + if (SandboxScaling.HasValue) + SandboxScaling.Value.Write(data); + + if (DebugInfo.HasValue) + { + data.WriteFloat(DebugInfo.Value.CritRollMade); + data.WriteFloat(DebugInfo.Value.CritRollNeeded); + } + } + + public uint Effect; + public uint Amount; + public uint OverHealOrKill; + public uint SchoolMaskOrPower; + public uint AbsorbedOrAmplitude; + public uint Resisted; + public bool Crit; + public Optional DebugInfo; + Optional SandboxScaling = new Optional(); + } + } + + class SpellInterruptLog : ServerPacket + { + public SpellInterruptLog() : base(ServerOpcodes.SpellInterruptLog, ConnectionType.Instance) { } + + public override void Write() + { + _worldPacket.WritePackedGuid(Caster); + _worldPacket.WritePackedGuid(Victim); + _worldPacket.WriteUInt32(InterruptedSpellID); + _worldPacket.WriteUInt32(SpellID); + } + + public ObjectGuid Caster; + public ObjectGuid Victim; + public uint InterruptedSpellID; + public uint SpellID; + } + + class SpellDispellLog : ServerPacket + { + public SpellDispellLog() : base(ServerOpcodes.SpellDispellLog, ConnectionType.Instance) { } + + public override void Write() + { + _worldPacket.WriteBit(IsSteal); + _worldPacket.WriteBit(IsBreak); + _worldPacket.WritePackedGuid(TargetGUID); + _worldPacket.WritePackedGuid(CasterGUID); + _worldPacket.WriteUInt32(DispelledBySpellID); + + _worldPacket.WriteInt32(DispellData.Count); + foreach (var data in DispellData) + { + _worldPacket.WriteUInt32(data.SpellID); + _worldPacket.WriteBit(data.Harmful); + _worldPacket.WriteBit(data.Rolled.HasValue); + _worldPacket.WriteBit(data.Needed.HasValue); + if (data.Rolled.HasValue) + _worldPacket.WriteUInt32(data.Rolled.Value); + if (data.Needed.HasValue) + _worldPacket.WriteUInt32(data.Needed.Value); + + _worldPacket.FlushBits(); + } + } + + public List DispellData = new List(); + public ObjectGuid CasterGUID; + public ObjectGuid TargetGUID; + public uint DispelledBySpellID; + public bool IsBreak; + public bool IsSteal; + } + + + class SpellEnergizeLog : CombatLogServerPacket + { + public SpellEnergizeLog() : base(ServerOpcodes.SpellEnergizeLog, ConnectionType.Instance) { } + + public override void Write() + { + _worldPacket.WritePackedGuid(CasterGUID); + _worldPacket.WritePackedGuid(TargetGUID); + + _worldPacket.WriteUInt32(SpellID); + _worldPacket.WriteUInt32(Type); + _worldPacket.WriteInt32(Amount); + _worldPacket.WriteInt32(OverEnergize); + + WriteLogDataBit(); + FlushBits(); + WriteLogData(); + } + + public ObjectGuid TargetGUID; + public ObjectGuid CasterGUID; + public uint SpellID; + public PowerType Type; + public int Amount; + public int OverEnergize; + } + + class SpellInstakillLog : ServerPacket + { + public SpellInstakillLog() : base(ServerOpcodes.SpellInstakillLog, ConnectionType.Instance) { } + + public override void Write() + { + _worldPacket.WritePackedGuid(Target); + _worldPacket.WritePackedGuid(Caster); + _worldPacket.WriteUInt32(SpellID); + } + + public ObjectGuid Target; + public ObjectGuid Caster; + public uint SpellID; + } + + class SpellMissLog : ServerPacket + { + public SpellMissLog() : base(ServerOpcodes.SpellMissLog, ConnectionType.Instance) { } + + public override void Write() + { + _worldPacket.WriteUInt32(SpellID); + _worldPacket.WritePackedGuid(Caster); + _worldPacket.WriteUInt32(Entries); + + foreach (SpellLogMissEntry missEntry in Entries) + missEntry.Write(_worldPacket); + } + + public uint SpellID; + public ObjectGuid Caster; + public List Entries = new List(); + } + + class ProcResist : ServerPacket + { + public ProcResist() : base(ServerOpcodes.ProcResist) { } + + public override void Write() + { + _worldPacket.WritePackedGuid(Caster); + _worldPacket.WritePackedGuid(Target); + _worldPacket.WriteUInt32(SpellID); + _worldPacket.WriteBit(Rolled.HasValue); + _worldPacket.WriteBit(Needed.HasValue); + _worldPacket.FlushBits(); + + if (Rolled.HasValue) + _worldPacket.WriteFloat(Rolled.Value); + + if (Needed.HasValue) + _worldPacket.WriteFloat(Needed.Value); + } + + public ObjectGuid Caster; + public ObjectGuid Target; + public uint SpellID; + public Optional Rolled; + public Optional Needed; + } + + class SpellOrDamageImmune : ServerPacket + { + public SpellOrDamageImmune() : base(ServerOpcodes.SpellOrDamageImmune, ConnectionType.Instance) { } + + public override void Write() + { + _worldPacket.WritePackedGuid(CasterGUID); + _worldPacket.WritePackedGuid(VictimGUID); + _worldPacket.WriteUInt32(SpellID); + _worldPacket.WriteBit(IsPeriodic); + _worldPacket.FlushBits(); + } + + public ObjectGuid CasterGUID; + public ObjectGuid VictimGUID; + public uint SpellID; + public bool IsPeriodic; + } + + class SpellDamageShield : CombatLogServerPacket + { + public SpellDamageShield() : base(ServerOpcodes.SpellDamageShield, ConnectionType.Instance) { } + + public override void Write() + { + _worldPacket.WritePackedGuid(Attacker); + _worldPacket.WritePackedGuid(Defender); + _worldPacket.WriteUInt32(SpellID); + _worldPacket.WriteUInt32(TotalDamage); + _worldPacket.WriteUInt32(OverKill); + _worldPacket.WriteUInt32(SchoolMask); + _worldPacket.WriteUInt32(LogAbsorbed); + + WriteLogDataBit(); + FlushBits(); + WriteLogData(); + } + + public ObjectGuid Attacker; + public ObjectGuid Defender; + public uint SpellID; + public uint TotalDamage; + public uint OverKill; + public uint SchoolMask; + public uint LogAbsorbed; + } + + class AttackerStateUpdate : CombatLogServerPacket + { + public AttackerStateUpdate() : base(ServerOpcodes.AttackerStateUpdate, ConnectionType.Instance) + { + SubDmg = new Optional(); + } + + public override void Write() + { + WorldPacket attackRoundInfo = new WorldPacket(); + attackRoundInfo.WriteUInt32(hitInfo); + attackRoundInfo.WritePackedGuid(AttackerGUID); + attackRoundInfo.WritePackedGuid(VictimGUID); + attackRoundInfo.WriteInt32(Damage); + attackRoundInfo.WriteInt32(OverDamage); + attackRoundInfo.WriteUInt8(SubDmg.HasValue); + + if (SubDmg.HasValue) + { + attackRoundInfo.WriteInt32(SubDmg.Value.SchoolMask); + attackRoundInfo.WriteFloat(SubDmg.Value.FDamage); + attackRoundInfo.WriteInt32(SubDmg.Value.Damage); + if (hitInfo.HasAnyFlag(HitInfo.FullAbsorb | HitInfo.PartialAbsorb)) + attackRoundInfo.WriteInt32(SubDmg.Value.Absorbed); + if (hitInfo.HasAnyFlag(HitInfo.FullResist | HitInfo.PartialResist)) + attackRoundInfo.WriteInt32(SubDmg.Value.Resisted); + } + + attackRoundInfo.WriteUInt8(VictimState); + attackRoundInfo.WriteInt32(AttackerState); + attackRoundInfo.WriteInt32(MeleeSpellID); + + if (hitInfo.HasAnyFlag(HitInfo.Block)) + attackRoundInfo.WriteInt32(BlockAmount); + + if (hitInfo.HasAnyFlag(HitInfo.RageGain)) + attackRoundInfo.WriteInt32(RageGained); + + if (hitInfo.HasAnyFlag(HitInfo.Unk1)) + { + attackRoundInfo.WriteUInt32(UnkState.State1); + attackRoundInfo.WriteFloat(UnkState.State2); + attackRoundInfo.WriteFloat(UnkState.State3); + attackRoundInfo.WriteFloat(UnkState.State4); + attackRoundInfo.WriteFloat(UnkState.State5); + attackRoundInfo.WriteFloat(UnkState.State6); + attackRoundInfo.WriteFloat(UnkState.State7); + attackRoundInfo.WriteFloat(UnkState.State8); + attackRoundInfo.WriteFloat(UnkState.State9); + attackRoundInfo.WriteFloat(UnkState.State10); + attackRoundInfo.WriteFloat(UnkState.State11); + attackRoundInfo.WriteUInt32(UnkState.State12); + } + + if (hitInfo.HasAnyFlag(HitInfo.Block | HitInfo.Unk12)) + attackRoundInfo.WriteFloat(Unk); + + attackRoundInfo.WriteUInt8(SandboxScaling.Type); + attackRoundInfo.WriteUInt8(SandboxScaling.TargetLevel); + attackRoundInfo.WriteUInt8(SandboxScaling.Expansion); + attackRoundInfo.WriteUInt8(SandboxScaling.Class); + attackRoundInfo.WriteUInt8(SandboxScaling.TargetMinScalingLevel); + attackRoundInfo.WriteUInt8(SandboxScaling.TargetMaxScalingLevel); + attackRoundInfo.WriteInt16(SandboxScaling.PlayerLevelDelta); + attackRoundInfo.WriteInt8(SandboxScaling.TargetScalingLevelDelta); + attackRoundInfo.WriteUInt16(SandboxScaling.PlayerItemLevel); + + WriteLogDataBit(); + FlushBits(); + WriteLogData(); + + _worldPacket.WriteInt32(attackRoundInfo.GetSize()); + _worldPacket.WriteBytes(attackRoundInfo); + } + + public HitInfo hitInfo; // Flags + public ObjectGuid AttackerGUID; + public ObjectGuid VictimGUID; + public int Damage; + public int OverDamage = -1; // (damage - health) or -1 if unit is still alive + public Optional SubDmg; + public byte VictimState; + public uint AttackerState; + public uint MeleeSpellID; + public int BlockAmount; + public int RageGained; + public UnkAttackerState UnkState; + public float Unk; + SandboxScalingData SandboxScaling = new SandboxScalingData(); + } + + //Structs + struct SpellLogEffectPowerDrainParams + { + public ObjectGuid Victim; + public uint Points; + public uint PowerType; + public float Amplitude; + } + + struct SpellLogEffectExtraAttacksParams + { + public ObjectGuid Victim; + public uint NumAttacks; + } + + struct SpellLogEffectDurabilityDamageParams + { + public ObjectGuid Victim; + public int ItemID; + public int Amount; + } + + struct SpellLogEffectGenericVictimParams + { + public ObjectGuid Victim; + } + + struct SpellLogEffectTradeSkillItemParams + { + public int ItemID; + } + + struct SpellLogEffectFeedPetParams + { + public int ItemID; + } + + struct SpellLogMissDebug + { + public void Write(WorldPacket data) + { + data.WriteFloat(HitRoll); + data.WriteFloat(HitRollNeeded); + } + + public float HitRoll; + public float HitRollNeeded; + } + + public struct SpellLogMissEntry + { + public SpellLogMissEntry(ObjectGuid victim, byte missReason) + { + Victim = victim; + MissReason = missReason; + Debug = new Optional(); + } + + public void Write(WorldPacket data) + { + data.WritePackedGuid(Victim); + data.WriteUInt8(MissReason); + if (data.WriteBit(Debug.HasValue)) + Debug.Value.Write(data); + + data.FlushBits(); + } + + public ObjectGuid Victim; + public byte MissReason; + Optional Debug; + } + + struct SpellDispellData + { + public uint SpellID; + public bool Harmful; + public Optional Rolled; + public Optional Needed; + } + + public struct SubDamage + { + public int SchoolMask; + public float FDamage; // Float damage (Most of the time equals to Damage) + public int Damage; + public int Absorbed; + public int Resisted; + } + + public struct UnkAttackerState + { + public uint State1; + public float State2; + public float State3; + public float State4; + public float State5; + public float State6; + public float State7; + public float State8; + public float State9; + public float State10; + public float State11; + public uint State12; + } +} diff --git a/Game/Network/Packets/CombatPackets.cs b/Game/Network/Packets/CombatPackets.cs new file mode 100644 index 000000000..e06ed1826 --- /dev/null +++ b/Game/Network/Packets/CombatPackets.cs @@ -0,0 +1,295 @@ +/* + * Copyright (C) 2012-2017 CypherCore + * + * 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, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +using Framework.Constants; +using Game.Entities; +using System.Collections.Generic; + +namespace Game.Network.Packets +{ + public class AttackSwing : ClientPacket + { + public AttackSwing(WorldPacket packet) : base(packet) { } + + public override void Read() + { + Victim = _worldPacket.ReadPackedGuid(); + } + + public ObjectGuid Victim; + } + + public class AttackSwingError : ServerPacket + { + public AttackSwingError(AttackSwingErr reason = AttackSwingErr.CantAttack) : base(ServerOpcodes.AttackSwingError) + { + Reason = reason; + } + + public override void Write() + { + _worldPacket.WriteBits((uint)Reason, 2); + _worldPacket.FlushBits(); + } + + AttackSwingErr Reason; + } + + public class AttackStop : ClientPacket + { + public AttackStop(WorldPacket packet) : base(packet) { } + + public override void Read() { } + } + + public class AttackStart : ServerPacket + { + public AttackStart() : base(ServerOpcodes.AttackStart, ConnectionType.Instance) { } + + public override void Write() + { + _worldPacket.WritePackedGuid(Attacker); + _worldPacket.WritePackedGuid(Victim); + } + + public ObjectGuid Attacker; + public ObjectGuid Victim; + } + + public class SAttackStop : ServerPacket + { + public SAttackStop(Unit attacker, Unit victim) : base(ServerOpcodes.AttackStop, ConnectionType.Instance) + { + Attacker = attacker.GetGUID(); + if (victim) + { + Victim = victim.GetGUID(); + NowDead = victim.IsDead(); + } + } + + public override void Write() + { + _worldPacket.WritePackedGuid(Attacker); + _worldPacket.WritePackedGuid(Victim); + _worldPacket.WriteBit(NowDead); + _worldPacket.FlushBits(); + } + + public ObjectGuid Attacker; + public ObjectGuid Victim; + public bool NowDead = false; + } + + public class ThreatUpdate : ServerPacket + { + public ThreatUpdate() : base(ServerOpcodes.ThreatUpdate, ConnectionType.Instance) { } + + public override void Write() + { + _worldPacket.WritePackedGuid(UnitGUID); + _worldPacket.WriteInt32(ThreatList.Count); + foreach (ThreatInfo threatInfo in ThreatList) + { + _worldPacket.WritePackedGuid(threatInfo.UnitGUID); + _worldPacket.WriteInt64(threatInfo.Threat); + } + } + + public ObjectGuid UnitGUID; + public List ThreatList = new List(); + } + + public class HighestThreatUpdate : ServerPacket + { + public HighestThreatUpdate() : base(ServerOpcodes.HighestThreatUpdate, ConnectionType.Instance) { } + + public override void Write() + { + _worldPacket.WritePackedGuid(UnitGUID); + _worldPacket.WritePackedGuid(HighestThreatGUID); + _worldPacket.WriteInt32(ThreatList.Count); + foreach (ThreatInfo threatInfo in ThreatList) + { + _worldPacket.WritePackedGuid(threatInfo.UnitGUID); + _worldPacket.WriteInt64(threatInfo.Threat); + } + } + + public ObjectGuid UnitGUID; + public List ThreatList = new List(); + public ObjectGuid HighestThreatGUID; + } + + public class ThreatRemove : ServerPacket + { + public ThreatRemove() : base(ServerOpcodes.ThreatRemove, ConnectionType.Instance) { } + + public override void Write() + { + _worldPacket.WritePackedGuid(UnitGUID); + _worldPacket.WritePackedGuid(AboutGUID); + } + + public ObjectGuid AboutGUID; // Unit to remove threat from (e.g. player, pet, guardian) + public ObjectGuid UnitGUID; // Unit being attacked (e.g. creature, boss) + } + + public class AIReaction : ServerPacket + { + public AIReaction() : base(ServerOpcodes.AiReaction, ConnectionType.Instance) { } + + public override void Write() + { + _worldPacket.WritePackedGuid(UnitGUID); + _worldPacket.WriteUInt32(Reaction); + } + + public ObjectGuid UnitGUID; + public AiReaction Reaction; + } + + public class CancelCombat : ServerPacket + { + public CancelCombat() : base(ServerOpcodes.CancelCombat) { } + + public override void Write() { } + } + + public class PowerUpdate : ServerPacket + { + public PowerUpdate() : base(ServerOpcodes.PowerUpdate) + { + Powers = new List(); + } + + public override void Write() + { + _worldPacket.WritePackedGuid(Guid); + _worldPacket.WriteUInt32(Powers.Count); + foreach (var power in Powers) + { + _worldPacket.WriteInt32(power.Power); + _worldPacket.WriteUInt8(power.PowerType); + } + } + + public ObjectGuid Guid; + public List Powers; + } + + public class SetSheathed : ClientPacket + { + public SetSheathed(WorldPacket packet) : base(packet) { } + + public override void Read() + { + CurrentSheathState = _worldPacket.ReadInt32(); + Animate = _worldPacket.HasBit(); + } + + public int CurrentSheathState; + public bool Animate = true; + } + + public class CancelAutoRepeat : ServerPacket + { + public CancelAutoRepeat() : base(ServerOpcodes.CancelAutoRepeat) { } + + public override void Write() + { + _worldPacket.WritePackedGuid(Guid); + } + + public ObjectGuid Guid; + } + + public class HealthUpdate : ServerPacket + { + public HealthUpdate() : base(ServerOpcodes.HealthUpdate) { } + + public override void Write() + { + _worldPacket.WritePackedGuid(Guid); + _worldPacket.WriteInt64(Health); + } + + public ObjectGuid Guid; + public long Health; + } + + public class ThreatClear : ServerPacket + { + public ThreatClear() : base(ServerOpcodes.ThreatClear) { } + + public override void Write() + { + _worldPacket.WritePackedGuid(UnitGUID); + } + + public ObjectGuid UnitGUID; + } + + class PvPCredit : ServerPacket + { + public PvPCredit() : base(ServerOpcodes.PvpCredit) { } + + public override void Write() + { + _worldPacket.WriteInt32(OriginalHonor); + _worldPacket.WriteInt32(Honor); + _worldPacket.WritePackedGuid(Target); + _worldPacket.WriteUInt32(Rank); + } + + public int OriginalHonor; + public int Honor; + public ObjectGuid Target; + public uint Rank; + } + + class BreakTarget : ServerPacket + { + public BreakTarget() : base(ServerOpcodes.BreakTarget) { } + + public override void Write() + { + _worldPacket.WritePackedGuid(UnitGUID); + } + + public ObjectGuid UnitGUID; + } + + //Structs + public struct ThreatInfo + { + public ObjectGuid UnitGUID; + public long Threat; + } + + public struct PowerUpdatePower + { + public PowerUpdatePower(int power, byte powerType) + { + Power = power; + PowerType = powerType; + } + + public int Power; + public byte PowerType; + } +} diff --git a/Game/Network/Packets/DuelPackets.cs b/Game/Network/Packets/DuelPackets.cs new file mode 100644 index 000000000..1418b6253 --- /dev/null +++ b/Game/Network/Packets/DuelPackets.cs @@ -0,0 +1,143 @@ +/* + * Copyright (C) 2012-2017 CypherCore + * + * 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, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +using Framework.Constants; +using Game.Entities; + +namespace Game.Network.Packets +{ + public class CanDuel : ClientPacket + { + public CanDuel(WorldPacket packet) : base(packet) { } + + public override void Read() + { + TargetGUID = _worldPacket.ReadPackedGuid(); + } + + public ObjectGuid TargetGUID; + } + + public class CanDuelResult : ServerPacket + { + public CanDuelResult() : base(ServerOpcodes.CanDuelResult) { } + + public override void Write() + { + _worldPacket.WritePackedGuid(TargetGUID); + _worldPacket.WriteBit(Result); + _worldPacket.FlushBits(); + } + + public ObjectGuid TargetGUID; + public bool Result; + } + + public class DuelComplete : ServerPacket + { + public DuelComplete() : base(ServerOpcodes.DuelComplete, ConnectionType.Instance) { } + + public override void Write() + { + _worldPacket.WriteBit(Started); + _worldPacket.FlushBits(); + } + + public bool Started; + } + + public class DuelCountdown : ServerPacket + { + public DuelCountdown(uint countdown) : base(ServerOpcodes.DuelCountdown) + { + Countdown = countdown; + } + + public override void Write() + { + _worldPacket.WriteUInt32(Countdown); + } + + uint Countdown; + } + + public class DuelInBounds : ServerPacket + { + public DuelInBounds() : base(ServerOpcodes.DuelInBounds, ConnectionType.Instance) { } + + public override void Write() { } + } + + public class DuelOutOfBounds : ServerPacket + { + public DuelOutOfBounds() : base(ServerOpcodes.DuelOutOfBounds, ConnectionType.Instance) { } + + public override void Write() { } + } + + public class DuelRequested : ServerPacket + { + public DuelRequested() : base(ServerOpcodes.DuelRequested, ConnectionType.Instance) { } + + public override void Write() + { + _worldPacket.WritePackedGuid(ArbiterGUID); + _worldPacket.WritePackedGuid(RequestedByGUID); + _worldPacket.WritePackedGuid(RequestedByWowAccount); + } + + public ObjectGuid ArbiterGUID; + public ObjectGuid RequestedByGUID; + public ObjectGuid RequestedByWowAccount; + } + + public class DuelResponse : ClientPacket + { + public DuelResponse(WorldPacket packet) : base(packet) { } + + public override void Read() + { + ArbiterGUID = _worldPacket.ReadPackedGuid(); + Accepted = _worldPacket.HasBit(); + } + + public ObjectGuid ArbiterGUID; + public bool Accepted; + } + + public class DuelWinner : ServerPacket + { + public DuelWinner() : base(ServerOpcodes.DuelWinner, ConnectionType.Instance) { } + + public override void Write() + { + _worldPacket.WriteBits(BeatenName.Length, 6); + _worldPacket.WriteBits(WinnerName.Length, 6); + _worldPacket.WriteBit(Fled); + _worldPacket.WriteUInt32(BeatenVirtualRealmAddress); + _worldPacket.WriteUInt32(WinnerVirtualRealmAddress); + _worldPacket.WriteString(BeatenName); + _worldPacket.WriteString(WinnerName); + } + + public string BeatenName; + public string WinnerName; + public uint BeatenVirtualRealmAddress; + public uint WinnerVirtualRealmAddress; + public bool Fled; + } +} diff --git a/Game/Network/Packets/EquipmentPackets.cs b/Game/Network/Packets/EquipmentPackets.cs new file mode 100644 index 000000000..35cbbfd31 --- /dev/null +++ b/Game/Network/Packets/EquipmentPackets.cs @@ -0,0 +1,172 @@ +/* + * Copyright (C) 2012-2017 CypherCore + * + * 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, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +using Framework.Constants; +using Game.Entities; +using System.Collections.Generic; + +namespace Game.Network.Packets +{ + public class EquipmentSetID : ServerPacket + { + public EquipmentSetID() : base(ServerOpcodes.EquipmentSetId, ConnectionType.Instance) { } + + public override void Write() + { + _worldPacket.WriteUInt64(GUID); + _worldPacket.WriteInt32(Type); + _worldPacket.WriteUInt32(SetID); + } + + public ulong GUID; // Set Identifier + public int Type; + public uint SetID; // Index + } + + public class LoadEquipmentSet : ServerPacket + { + public LoadEquipmentSet() : base(ServerOpcodes.LoadEquipmentSet, ConnectionType.Instance) { } + + public override void Write() + { + _worldPacket.WriteUInt32(SetData.Count); + + foreach (var equipSet in SetData) + { + _worldPacket.WriteInt32(equipSet.Type); + _worldPacket.WriteUInt64(equipSet.Guid); + _worldPacket.WriteUInt32(equipSet.SetID); + _worldPacket.WriteUInt32(equipSet.IgnoreMask); + + for (int i = 0; i < EquipmentSlot.End; ++i) + { + _worldPacket.WritePackedGuid(equipSet.Pieces[i]); + _worldPacket.WriteInt32(equipSet.Appearances[i]); + } + + foreach (var id in equipSet.Enchants) + _worldPacket.WriteInt32(id); + + _worldPacket.WriteBit(equipSet.AssignedSpecIndex != -1); + _worldPacket.WriteBits(equipSet.SetName.Length, 8); + _worldPacket.WriteBits(equipSet.SetIcon.Length, 9); + + if (equipSet.AssignedSpecIndex != -1) + _worldPacket.WriteInt32(equipSet.AssignedSpecIndex); + + _worldPacket.WriteString(equipSet.SetName); + _worldPacket.WriteString(equipSet.SetIcon); + } + } + + public List SetData = new List(); + } + + public class SaveEquipmentSet : ClientPacket + { + public SaveEquipmentSet(WorldPacket packet) : base(packet) + { + Set = new EquipmentSetInfo.EquipmentSetData(); + } + + public override void Read() + { + Set.Type = (EquipmentSetInfo.EquipmentSetType)_worldPacket.ReadInt32(); + Set.Guid = _worldPacket.ReadUInt64(); + Set.SetID = _worldPacket.ReadUInt32(); + Set.IgnoreMask = _worldPacket.ReadUInt32(); + + for (byte i = 0; i < EquipmentSlot.End; ++i) + { + Set.Pieces.Add(_worldPacket.ReadPackedGuid()); + Set.Appearances.Add(_worldPacket.ReadInt32()); + } + + Set.Enchants.Add(_worldPacket.ReadInt32()); + Set.Enchants.Add(_worldPacket.ReadInt32()); + + bool hasSpecIndex = _worldPacket.HasBit(); + + uint setNameLength = _worldPacket.ReadBits(8); + uint setIconLength = _worldPacket.ReadBits(9); + + if (hasSpecIndex) + Set.AssignedSpecIndex = _worldPacket.ReadInt32(); + + Set.SetName = _worldPacket.ReadString(setNameLength); + Set.SetIcon = _worldPacket.ReadString(setIconLength); + } + + public EquipmentSetInfo.EquipmentSetData Set; + } + + class DeleteEquipmentSet : ClientPacket + { + public DeleteEquipmentSet(WorldPacket packet) : base(packet) { } + + public override void Read() + { + ID = _worldPacket.ReadUInt64(); + } + + public ulong ID; + } + + class UseEquipmentSet : ClientPacket + { + public UseEquipmentSet(WorldPacket packet) : base(packet) { } + + public override void Read() + { + Inv = new InvUpdate(_worldPacket); + + for (byte i = 0; i < EquipmentSlot.End; ++i) + { + Items[i].Item = _worldPacket.ReadPackedGuid(); + Items[i].ContainerSlot = _worldPacket.ReadUInt8(); + Items[i].Slot = _worldPacket.ReadUInt8(); + } + + GUID = _worldPacket.ReadUInt64(); + } + + public InvUpdate Inv; + public EquipmentSetItem[] Items = new EquipmentSetItem[EquipmentSlot.End]; + public ulong GUID; //Set Identifier + + public struct EquipmentSetItem + { + public ObjectGuid Item; + public byte ContainerSlot; + public byte Slot; + } + } + + class UseEquipmentSetResult : ServerPacket + { + public UseEquipmentSetResult() : base(ServerOpcodes.UseEquipmentSetResult) { } + + public override void Write() + { + _worldPacket.WriteUInt64(GUID); + _worldPacket.WriteUInt8(Reason); + } + + public ulong GUID; //Set Identifier + public byte Reason; + } +} diff --git a/Game/Network/Packets/GameObjectPackets.cs b/Game/Network/Packets/GameObjectPackets.cs new file mode 100644 index 000000000..070e8dd94 --- /dev/null +++ b/Game/Network/Packets/GameObjectPackets.cs @@ -0,0 +1,138 @@ +/* + * Copyright (C) 2012-2017 CypherCore + * + * 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, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +using Framework.Constants; +using Game.Entities; + +namespace Game.Network.Packets +{ + public class GameObjUse : ClientPacket + { + public GameObjUse(WorldPacket packet) : base(packet) { } + + public override void Read() + { + Guid = _worldPacket.ReadPackedGuid(); + } + + public ObjectGuid Guid; + } + + public class GameObjReportUse : ClientPacket + { + public GameObjReportUse(WorldPacket packet) : base(packet) { } + + public override void Read() + { + Guid = _worldPacket.ReadPackedGuid(); + } + + public ObjectGuid Guid; + } + + class GameObjectDespawn : ServerPacket + { + public GameObjectDespawn() : base(ServerOpcodes.GameObjectDespawn) { } + + public override void Write() + { + _worldPacket.WritePackedGuid(ObjectGUID); + } + + public ObjectGuid ObjectGUID; + } + + class PageTextPkt : ServerPacket + { + public PageTextPkt() : base(ServerOpcodes.PageText) { } + + public override void Write() + { + _worldPacket.WritePackedGuid(GameObjectGUID); + } + + public ObjectGuid GameObjectGUID; + } + + class GameObjectActivateAnimKit : ServerPacket + { + public GameObjectActivateAnimKit() : base(ServerOpcodes.GameObjectActivateAnimKit, ConnectionType.Instance) { } + + public override void Write() + { + _worldPacket.WritePackedGuid(ObjectGUID); + _worldPacket.WriteUInt32(AnimKitID); + _worldPacket.WriteBit(Maintain); + _worldPacket.FlushBits(); + } + + public ObjectGuid ObjectGUID; + public int AnimKitID; + public bool Maintain; + } + + class DestructibleBuildingDamage : ServerPacket + { + public DestructibleBuildingDamage() : base(ServerOpcodes.DestructibleBuildingDamage, ConnectionType.Instance) { } + + public override void Write() + { + _worldPacket.WritePackedGuid(Target); + _worldPacket.WritePackedGuid(Owner); + _worldPacket.WritePackedGuid(Caster); + _worldPacket.WriteInt32(Damage); + _worldPacket.WriteUInt32(SpellID); + } + + public ObjectGuid Target; + public ObjectGuid Caster; + public ObjectGuid Owner; + public int Damage; + public uint SpellID; + } + + class FishNotHooked : ServerPacket + { + public FishNotHooked() : base(ServerOpcodes.FishNotHooked) { } + + public override void Write() { } + } + + class FishEscaped : ServerPacket + { + public FishEscaped() : base(ServerOpcodes.FishEscaped) { } + + public override void Write() { } + } + + class GameObjectCustomAnim : ServerPacket + { + public GameObjectCustomAnim() : base(ServerOpcodes.GameObjectCustomAnim, ConnectionType.Instance) { } + + public override void Write() + { + _worldPacket.WritePackedGuid(ObjectGUID); + _worldPacket.WriteUInt32(CustomAnim); + _worldPacket.WriteBit(PlayAsDespawn); + _worldPacket.FlushBits(); + } + + public ObjectGuid ObjectGUID; + public uint CustomAnim; + public bool PlayAsDespawn; + } +} diff --git a/Game/Network/Packets/GarrisonPackets.cs b/Game/Network/Packets/GarrisonPackets.cs new file mode 100644 index 000000000..3a46ff88c --- /dev/null +++ b/Game/Network/Packets/GarrisonPackets.cs @@ -0,0 +1,610 @@ +/* + * Copyright (C) 2012-2017 CypherCore + * + * 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, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +using Framework.Constants; +using Game.DataStorage; +using Game.Entities; +using System.Collections.Generic; + +namespace Game.Network.Packets +{ + class GarrisonCreateResult : ServerPacket + { + public GarrisonCreateResult() : base(ServerOpcodes.GarrisonCreateResult, ConnectionType.Instance) { } + + public override void Write() + { + _worldPacket.WriteUInt32(Result); + _worldPacket.WriteUInt32(GarrSiteLevelID); + } + + public uint GarrSiteLevelID; + public uint Result; + } + + class GarrisonDeleteResult : ServerPacket + { + public GarrisonDeleteResult() : base(ServerOpcodes.GarrisonDeleteResult, ConnectionType.Instance) { } + + public override void Write() + { + _worldPacket.WriteUInt32(Result); + _worldPacket.WriteUInt32(GarrSiteID); + } + + public GarrisonError Result; + public uint GarrSiteID; + } + + class GetGarrisonInfo : ClientPacket + { + public GetGarrisonInfo(WorldPacket packet) : base(packet) { } + + public override void Read() { } + } + + class GetGarrisonInfoResult : ServerPacket + { + public GetGarrisonInfoResult() : base(ServerOpcodes.GetGarrisonInfoResult, ConnectionType.Instance) { } + + public override void Write() + { + _worldPacket.WriteInt32(FactionIndex); + _worldPacket.WriteUInt32(Garrisons.Count); + _worldPacket.WriteUInt32(FollowerSoftCaps.Count); + + foreach (FollowerSoftCapInfo followerSoftCapInfo in FollowerSoftCaps) + followerSoftCapInfo.Write(_worldPacket); + + foreach (GarrisonInfo garrison in Garrisons) + garrison.Write(_worldPacket); + } + + public uint FactionIndex; + public List Garrisons = new List(); + public List FollowerSoftCaps = new List(); + } + + class GarrisonRemoteInfo : ServerPacket + { + public GarrisonRemoteInfo() : base(ServerOpcodes.GarrisonRemoteInfo, ConnectionType.Instance) { } + + public override void Write() + { + _worldPacket.WriteUInt32(Sites.Count); + foreach (GarrisonRemoteSiteInfo site in Sites) + site.Write(_worldPacket); + } + + public List Sites = new List(); + } + + class GarrisonPurchaseBuilding : ClientPacket + { + public GarrisonPurchaseBuilding(WorldPacket packet) : base(packet) { } + + public override void Read() + { + NpcGUID = _worldPacket.ReadPackedGuid(); + PlotInstanceID = _worldPacket.ReadUInt32(); + BuildingID = _worldPacket.ReadUInt32(); + } + + public ObjectGuid NpcGUID; + public uint BuildingID; + public uint PlotInstanceID; + } + + class GarrisonPlaceBuildingResult : ServerPacket + { + public GarrisonPlaceBuildingResult() : base(ServerOpcodes.GarrisonPlaceBuildingResult, ConnectionType.Instance) { } + + public override void Write() + { + _worldPacket.WriteInt32(GarrTypeID); + _worldPacket.WriteUInt32(Result); + BuildingInfo.Write(_worldPacket); + _worldPacket.WriteBit(PlayActivationCinematic); + _worldPacket.FlushBits(); + } + + public GarrisonType GarrTypeID; + public GarrisonError Result; + public GarrisonBuildingInfo BuildingInfo = new GarrisonBuildingInfo(); + public bool PlayActivationCinematic; + } + + class GarrisonCancelConstruction : ClientPacket + { + public GarrisonCancelConstruction(WorldPacket packet) : base(packet) { } + + public override void Read() + { + NpcGUID = _worldPacket.ReadPackedGuid(); + PlotInstanceID = _worldPacket.ReadUInt32(); + } + + public ObjectGuid NpcGUID; + public uint PlotInstanceID; + } + + class GarrisonBuildingRemoved : ServerPacket + { + public GarrisonBuildingRemoved() : base(ServerOpcodes.GarrisonBuildingRemoved, ConnectionType.Instance) { } + + public override void Write() + { + _worldPacket.WriteInt32(GarrTypeID); + _worldPacket.WriteUInt32(Result); + _worldPacket.WriteUInt32(GarrPlotInstanceID); + _worldPacket.WriteUInt32(GarrBuildingID); + } + + public GarrisonType GarrTypeID; + public GarrisonError Result; + public uint GarrPlotInstanceID; + public uint GarrBuildingID; + } + + class GarrisonLearnBlueprintResult : ServerPacket + { + public GarrisonLearnBlueprintResult() : base(ServerOpcodes.GarrisonLearnBlueprintResult, ConnectionType.Instance) { } + + public override void Write() + { + _worldPacket.WriteInt32(GarrTypeID); + _worldPacket.WriteUInt32(Result); + _worldPacket.WriteUInt32(BuildingID); + } + + public GarrisonType GarrTypeID; + public uint BuildingID; + public GarrisonError Result; + } + + class GarrisonUnlearnBlueprintResult : ServerPacket + { + public GarrisonUnlearnBlueprintResult() : base(ServerOpcodes.GarrisonUnlearnBlueprintResult, ConnectionType.Instance) { } + + public override void Write() + { + _worldPacket.WriteInt32(GarrTypeID); + _worldPacket.WriteUInt32(Result); + _worldPacket.WriteUInt32(BuildingID); + } + + public GarrisonType GarrTypeID; + public uint BuildingID; + public GarrisonError Result; + } + + class GarrisonRequestBlueprintAndSpecializationData : ClientPacket + { + public GarrisonRequestBlueprintAndSpecializationData(WorldPacket packet) : base(packet) { } + + public override void Read() { } + } + + class GarrisonRequestBlueprintAndSpecializationDataResult : ServerPacket + { + public GarrisonRequestBlueprintAndSpecializationDataResult() : base(ServerOpcodes.GarrisonRequestBlueprintAndSpecializationDataResult, ConnectionType.Instance) { } + + public override void Write() + { + _worldPacket.WriteInt32(GarrTypeID); + _worldPacket.WriteUInt32(BlueprintsKnown != null ? BlueprintsKnown.Count : 0); + _worldPacket.WriteUInt32(SpecializationsKnown != null ? SpecializationsKnown.Count : 0); + if (BlueprintsKnown != null) + foreach (uint blueprint in BlueprintsKnown) + _worldPacket.WriteUInt32(blueprint); + + if (SpecializationsKnown != null) + foreach (uint specialization in SpecializationsKnown) + _worldPacket.WriteUInt32(specialization); + } + + public GarrisonType GarrTypeID; + public List SpecializationsKnown = null; + public List BlueprintsKnown = null; + } + + class GarrisonGetBuildingLandmarks : ClientPacket + { + public GarrisonGetBuildingLandmarks(WorldPacket packet) : base(packet) { } + + public override void Read() { } + } + + class GarrisonBuildingLandmarks : ServerPacket + { + public GarrisonBuildingLandmarks() : base(ServerOpcodes.GarrisonBuildingLandmarks, ConnectionType.Instance) { } + + public override void Write() + { + _worldPacket.WriteUInt32(Landmarks.Count); + foreach (GarrisonBuildingLandmark landmark in Landmarks) + landmark.Write(_worldPacket); + } + + public List Landmarks = new List(); + } + + class GarrisonPlotPlaced : ServerPacket + { + public GarrisonPlotPlaced() : base(ServerOpcodes.GarrisonPlotPlaced, ConnectionType.Instance) { } + + public override void Write() + { + _worldPacket.WriteInt32(GarrTypeID); + PlotInfo.Write(_worldPacket); + } + + public GarrisonType GarrTypeID; + public GarrisonPlotInfo PlotInfo; + } + + class GarrisonPlotRemoved : ServerPacket + { + public GarrisonPlotRemoved() : base(ServerOpcodes.GarrisonPlotRemoved, ConnectionType.Instance) { } + + public override void Write() + { + _worldPacket.WriteUInt32(GarrPlotInstanceID); + } + + public uint GarrPlotInstanceID; + } + + class GarrisonAddFollowerResult : ServerPacket + { + public GarrisonAddFollowerResult() : base(ServerOpcodes.GarrisonAddFollowerResult, ConnectionType.Instance) { } + + public override void Write() + { + _worldPacket.WriteInt32(GarrTypeID); + _worldPacket .WriteUInt32(Result); + Follower.Write(_worldPacket); + } + + public GarrisonType GarrTypeID; + public GarrisonFollower Follower; + public GarrisonError Result; + } + + class GarrisonRemoveFollowerResult : ServerPacket + { + public GarrisonRemoveFollowerResult() : base(ServerOpcodes.GarrisonRemoveFollowerResult) { } + + public override void Write() + { + _worldPacket.WriteUInt64(FollowerDBID); + _worldPacket.WriteInt32(GarrTypeID); + _worldPacket.WriteUInt32(Result); + _worldPacket.WriteUInt32(Destroyed); + } + + public ulong FollowerDBID; + public int GarrTypeID; + public uint Result; + public uint Destroyed; + } + + class GarrisonBuildingActivated : ServerPacket + { + public GarrisonBuildingActivated() : base(ServerOpcodes.GarrisonBuildingActivated, ConnectionType.Instance) { } + + public override void Write() + { + _worldPacket.WriteUInt32(GarrPlotInstanceID); + } + + public uint GarrPlotInstanceID; + } + + //Structs + public struct GarrisonPlotInfo + { + public void Write(WorldPacket data) + { + data.WriteUInt32(GarrPlotInstanceID); + data.WriteXYZO(PlotPos); + data.WriteUInt32(PlotType); + } + + public uint GarrPlotInstanceID; + public Position PlotPos; + public uint PlotType; + } + + public class GarrisonBuildingInfo + { + public void Write(WorldPacket data) + { + data.WriteUInt32(GarrPlotInstanceID); + data.WriteUInt32(GarrBuildingID); + data.WriteUInt32(TimeBuilt); + data.WriteUInt32(CurrentGarSpecID); + data.WriteUInt32(TimeSpecCooldown); + data.WriteBit(Active); + data.FlushBits(); + } + + public uint GarrPlotInstanceID; + public uint GarrBuildingID; + public long TimeBuilt; + public uint CurrentGarSpecID; + public long TimeSpecCooldown = 2288912640; // 06/07/1906 18:35:44 - another in the series of magic blizz dates + public bool Active; + } + + public class GarrisonFollower + { + public void Write(WorldPacket data) + { + data.WriteUInt64(DbID); + data.WriteUInt32(GarrFollowerID); + data.WriteUInt32(Quality); + data.WriteUInt32(FollowerLevel); + data.WriteUInt32(ItemLevelWeapon); + data.WriteUInt32(ItemLevelArmor); + data.WriteUInt32(Xp); + data.WriteUInt32(Durability); + data.WriteUInt32(CurrentBuildingID); + data.WriteUInt32(CurrentMissionID); + data.WriteUInt32(AbilityID.Count); + data.WriteUInt32(ZoneSupportSpellID); + data.WriteUInt32(FollowerStatus); + + AbilityID.ForEach(ability => data.WriteUInt32(ability.Id)); + + data.WriteBits(CustomName.Length, 7); + data.FlushBits(); + data.WriteString(CustomName); + } + + public ulong DbID; + public uint GarrFollowerID; + public uint Quality; + public uint FollowerLevel; + public uint ItemLevelWeapon; + public uint ItemLevelArmor; + public uint Xp; + public uint Durability; + public uint CurrentBuildingID; + public uint CurrentMissionID; + public List AbilityID = new List(); + public uint ZoneSupportSpellID; + public uint FollowerStatus; + public string CustomName = ""; + } + + class GarrisonMission + { + public void Write(WorldPacket data) + { + data.WriteUInt64(DbID); + data.WriteUInt32(MissionRecID); + data.WriteUInt32(OfferTime); + data.WriteUInt32(OfferDuration); + data.WriteUInt32(StartTime); + data.WriteUInt32(TravelDuration); + data.WriteUInt32(MissionDuration); + data.WriteUInt32(MissionState); + data.WriteUInt32(Unknown1); + data.WriteUInt32(Unknown2); + } + + public ulong DbID; + public uint MissionRecID; + public long OfferTime; + public uint OfferDuration; + public long StartTime = 2288912640; + public uint TravelDuration; + public uint MissionDuration; + public uint MissionState; + public uint Unknown1 = 0; + public uint Unknown2 = 0; + } + + struct GarrisonMissionReward + { + public void Write(WorldPacket data) + { + data.WriteInt32(ItemID); + data.WriteUInt32(Quantity); + data.WriteInt32(CurrencyID); + data.WriteUInt32(CurrencyQuantity); + data.WriteUInt32(FollowerXP); + data.WriteUInt32(BonusAbilityID); + data.WriteInt32(Unknown); + } + + public int ItemID; + public uint Quantity; + public int CurrencyID; + public uint CurrencyQuantity; + public uint FollowerXP; + public uint BonusAbilityID; + public int Unknown; + } + + struct GarrisonMissionAreaBonus + { + public void Write(WorldPacket data) + { + data.WriteUInt32(GarrMssnBonusAbilityID); + data.WriteUInt32(StartTime); + } + + public uint GarrMssnBonusAbilityID; + public long StartTime; + } + + struct GarrisonTalent + { + public void Write(WorldPacket data) + { + data.WriteInt32(GarrTalentID); + data.WriteInt32(ResearchStartTime); + data.WriteInt32(Flags); + } + + public int GarrTalentID; + public long ResearchStartTime; + public int Flags; + } + + class GarrisonInfo + { + public void Write(WorldPacket data) + { + data.WriteInt32(GarrTypeID); + data.WriteInt32(GarrSiteID); + data.WriteInt32(GarrSiteLevelID); + data.WriteUInt32(Buildings.Count); + data.WriteUInt32(Plots.Count); + data.WriteUInt32(Followers.Count); + data.WriteUInt32(Missions.Count); + data.WriteUInt32(MissionRewards.Count); + data.WriteUInt32(MissionOvermaxRewards.Count); + data.WriteUInt32(MissionAreaBonuses.Count); + data.WriteUInt32(Talents.Count); + data.WriteUInt32(CanStartMission.Count); + data.WriteUInt32(ArchivedMissions.Count); + data.WriteInt32(NumFollowerActivationsRemaining); + data.WriteUInt32(NumMissionsStartedToday); + + foreach (GarrisonPlotInfo plot in Plots) + plot.Write(data); + + foreach (GarrisonMission mission in Missions) + mission.Write(data); + + foreach (List missionReward in MissionRewards) + { + data.WriteUInt32(missionReward.Count); + foreach (GarrisonMissionReward missionRewardItem in missionReward) + missionRewardItem.Write(data); + } + + foreach (List missionReward in MissionOvermaxRewards) + { + data.WriteUInt32(missionReward.Count); + foreach (GarrisonMissionReward missionRewardItem in missionReward) + missionRewardItem.Write(data); + } + + foreach (GarrisonMissionAreaBonus areaBonus in MissionAreaBonuses) + areaBonus.Write(data); + + foreach (GarrisonTalent talent in Talents) + talent.Write(data); + + if (!ArchivedMissions.Empty()) + ArchivedMissions.ForEach(id => data.WriteInt32(id)); + + foreach (GarrisonBuildingInfo building in Buildings) + building.Write(data); + + foreach (bool canStartMission in CanStartMission) + data.WriteBit(canStartMission); + + data.FlushBits(); + + foreach (GarrisonFollower follower in Followers) + follower.Write(data); + } + + public GarrisonType GarrTypeID; + public uint GarrSiteID; + public uint GarrSiteLevelID; + public uint NumFollowerActivationsRemaining; + public uint NumMissionsStartedToday; // might mean something else, but sending 0 here enables follower abilities "Increase success chance of the first mission of the day by %." + public List Plots = new List(); + public List Buildings = new List(); + public List Followers = new List(); + public List Missions = new List(); + public List> MissionRewards = new List>(); + public List> MissionOvermaxRewards = new List>(); + public List MissionAreaBonuses = new List(); + public List Talents = new List(); + public List CanStartMission = new List(); + public List ArchivedMissions = new List(); + } + + struct FollowerSoftCapInfo + { + public void Write(WorldPacket data) + { + data.WriteInt32(GarrFollowerTypeID); + data.WriteUInt32(Count); + } + + public int GarrFollowerTypeID; + public uint Count; + } + + struct GarrisonRemoteBuildingInfo + { + public GarrisonRemoteBuildingInfo(uint plotInstanceId, uint buildingId) + { + GarrPlotInstanceID = plotInstanceId; + GarrBuildingID = buildingId; + } + + public void Write(WorldPacket data) + { + data.WriteUInt32(GarrPlotInstanceID); + data.WriteUInt32(GarrBuildingID); + } + + public uint GarrPlotInstanceID; + public uint GarrBuildingID; + } + + class GarrisonRemoteSiteInfo + { + public void Write(WorldPacket data) + { + data.WriteUInt32(GarrSiteLevelID); + data.WriteUInt32(Buildings.Count); + foreach (GarrisonRemoteBuildingInfo building in Buildings) + building.Write(data); + } + + public uint GarrSiteLevelID; + public List Buildings = new List(); + } + + struct GarrisonBuildingLandmark + { + public GarrisonBuildingLandmark(uint buildingPlotInstId, Position pos) + { + GarrBuildingPlotInstID = buildingPlotInstId; + Pos = pos; + } + + public void Write(WorldPacket data) + { + data.WriteUInt32(GarrBuildingPlotInstID); + data.WriteXYZ(Pos); + } + + public uint GarrBuildingPlotInstID; + public Position Pos; + } +} \ No newline at end of file diff --git a/Game/Network/Packets/GuildFinderPackets.cs b/Game/Network/Packets/GuildFinderPackets.cs new file mode 100644 index 000000000..787d956bb --- /dev/null +++ b/Game/Network/Packets/GuildFinderPackets.cs @@ -0,0 +1,342 @@ +/* + * Copyright (C) 2012-2017 CypherCore + * + * 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, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +using Framework.Constants; +using Framework.Dynamic; +using Game.Entities; +using System.Collections.Generic; + +namespace Game.Network.Packets +{ + class LFGuildAddRecruit : ClientPacket + { + public LFGuildAddRecruit(WorldPacket packet) : base(packet) { } + + public override void Read() + { + GuildGUID = _worldPacket.ReadPackedGuid(); + PlayStyle = _worldPacket.ReadUInt32(); + Availability = _worldPacket.ReadUInt32(); + ClassRoles = _worldPacket.ReadUInt32(); + Comment = _worldPacket.ReadString(_worldPacket.ReadBits(10)); + } + + public ObjectGuid GuildGUID; + public uint Availability; + public uint ClassRoles; + public uint PlayStyle; + public string Comment; + } + + class LFGuildApplicationsListChanged : ServerPacket + { + public LFGuildApplicationsListChanged() : base(ServerOpcodes.LfGuildApplicationsListChanged) { } + + public override void Write() { } + } + + class LFGuildApplicantListChanged : ServerPacket + { + public LFGuildApplicantListChanged() : base(ServerOpcodes.LfGuildApplicantListChanged) { } + + public override void Write() { } + } + + class LFGuildBrowse : ClientPacket + { + public LFGuildBrowse(WorldPacket packet) : base(packet) { } + + public override void Read() + { + PlayStyle = _worldPacket.ReadUInt32(); + Availability = _worldPacket.ReadUInt32(); + ClassRoles = _worldPacket.ReadUInt32(); + CharacterLevel = _worldPacket.ReadUInt32(); + } + + public uint CharacterLevel; + public uint Availability; + public uint ClassRoles; + public uint PlayStyle; + } + + class LFGuildBrowseResult : ServerPacket + { + public LFGuildBrowseResult() : base(ServerOpcodes.LfGuildBrowse) { } + + public override void Write() + { + _worldPacket.WriteUInt32(Post.Count); + foreach (LFGuildBrowseData guildData in Post) + guildData.Write(_worldPacket); + } + + public List Post = new List(); + } + + class LFGuildDeclineRecruit : ClientPacket + { + public LFGuildDeclineRecruit(WorldPacket packet) : base(packet) { } + + public override void Read() + { + RecruitGUID = _worldPacket.ReadPackedGuid(); + } + + public ObjectGuid RecruitGUID; + } + + class LFGuildGetApplications : ClientPacket + { + public LFGuildGetApplications(WorldPacket packet) : base(packet) { } + + public override void Read() { } + } + + class LFGuildApplications : ServerPacket + { + public LFGuildApplications() : base(ServerOpcodes.LfGuildApplications) { } + + public override void Write() + { + _worldPacket.WriteInt32(NumRemaining); + _worldPacket.WriteUInt32(Application.Count); + foreach (LFGuildApplicationData application in Application) + application.Write(_worldPacket); + } + + public List Application = new List(); + public int NumRemaining; + } + + class LFGuildGetGuildPost : ClientPacket + { + public LFGuildGetGuildPost(WorldPacket packet) : base(packet) { } + + public override void Read() { } + } + + class LFGuildPost : ServerPacket + { + public LFGuildPost() : base(ServerOpcodes.LfGuildPost) { } + + public override void Write() + { + _worldPacket.WriteBit(Post.HasValue); + _worldPacket.FlushBits(); + if (Post.HasValue) + Post.Value.Write(_worldPacket); + } + + public Optional Post; + } + + class LFGuildGetRecruits : ClientPacket + { + public LFGuildGetRecruits(WorldPacket packet) : base(packet) { } + + public override void Read() + { + LastUpdate = _worldPacket.ReadUInt32(); + } + + public uint LastUpdate; + } + + class LFGuildRecruits : ServerPacket + { + public LFGuildRecruits() : base(ServerOpcodes.LfGuildRecruits) { } + + public override void Write() + { + _worldPacket.WriteUInt32(Recruits.Count); + _worldPacket.WriteUInt32(UpdateTime); + foreach (LFGuildRecruitData recruit in Recruits) + recruit.Write(_worldPacket); + } + + public List Recruits = new List(); + public long UpdateTime; + } + + class LFGuildRemoveRecruit : ClientPacket + { + public LFGuildRemoveRecruit(WorldPacket packet) : base(packet) { } + + public override void Read() + { + GuildGUID = _worldPacket.ReadPackedGuid(); + } + + public ObjectGuid GuildGUID; + } + + class LFGuildSetGuildPost : ClientPacket + { + public LFGuildSetGuildPost(WorldPacket packet) : base(packet) { } + + public override void Read() + { + PlayStyle = _worldPacket.ReadUInt32(); + Availability = _worldPacket.ReadUInt32(); + ClassRoles = _worldPacket.ReadUInt32(); + LevelRange = _worldPacket.ReadUInt32(); + Active = _worldPacket.HasBit(); + Comment = _worldPacket.ReadString(_worldPacket.ReadBits(10)); + } + + public uint Availability; + public uint PlayStyle; + public uint ClassRoles; + public uint LevelRange; + public bool Active; + public string Comment; + } + + //Structs + + class LFGuildBrowseData + { + public void Write(WorldPacket data) + { + data.WriteBits(GuildName.Length, 7); + data.WriteBits(Comment.Length, 10); + data.WritePackedGuid(GuildGUID); + data.WriteUInt32(GuildVirtualRealm); + data.WriteInt32(GuildMembers); + data.WriteUInt32(GuildAchievementPoints); + data.WriteInt32(PlayStyle); + data.WriteInt32(Availability); + data.WriteInt32(ClassRoles); + data.WriteInt32(LevelRange); + data.WriteUInt32(EmblemStyle); + data.WriteUInt32(EmblemColor); + data.WriteUInt32(BorderStyle); + data.WriteUInt32(BorderColor); + data.WriteUInt32(Background); + data.WriteInt8(Cached); + data.WriteInt8(MembershipRequested); + data.WriteString(GuildName); + data.WriteString(Comment); + } + + public string GuildName = ""; + public ObjectGuid GuildGUID; + public uint GuildVirtualRealm; + public int GuildMembers; + public uint GuildAchievementPoints; + public int PlayStyle; + public int Availability; + public int ClassRoles; + public int LevelRange; + public uint EmblemStyle; + public uint EmblemColor; + public uint BorderStyle; + public uint BorderColor; + public uint Background; + public string Comment = ""; + public sbyte Cached; + public sbyte MembershipRequested; + } + + class LFGuildApplicationData + { + public void Write(WorldPacket data) + { + data.WritePackedGuid(GuildGUID); + data.WriteUInt32(GuildVirtualRealm); + data.WriteInt32(ClassRoles); + data.WriteInt32(PlayStyle); + data.WriteInt32(Availability); + data.WriteUInt32(SecondsSinceCreated); + data.WriteUInt32(SecondsUntilExpiration); + data.WriteBits(GuildName.Length, 7); + data.WriteBits(Comment.Length, 10); + data.FlushBits(); + data.WriteString(GuildName); + data.WriteString(Comment); + } + + public ObjectGuid GuildGUID; + public uint GuildVirtualRealm; + public string GuildName = ""; + public int ClassRoles; + public int PlayStyle; + public int Availability; + public uint SecondsSinceCreated; + public uint SecondsUntilExpiration; + public string Comment = ""; + } + + class GuildPostData + { + public void Write(WorldPacket data) + { + data.WriteBit(Active); + data.WriteBits(Comment.Length, 10); + data.WriteInt32(PlayStyle); + data.WriteInt32(Availability); + data.WriteInt32(ClassRoles); + data.WriteInt32(LevelRange); + data.WriteUInt32(SecondsRemaining); + data.WriteString(Comment); + } + + public bool Active; + public int PlayStyle; + public int Availability; + public int ClassRoles; + public int LevelRange; + public long SecondsRemaining; + public string Comment = ""; + } + + class LFGuildRecruitData + { + public void Write(WorldPacket data) + { + data.WritePackedGuid(RecruitGUID); + data.WriteUInt32(RecruitVirtualRealm); + data.WriteInt32(CharacterClass); + data.WriteInt32(CharacterGender); + data.WriteInt32(CharacterLevel); + data.WriteInt32(ClassRoles); + data.WriteInt32(PlayStyle); + data.WriteInt32(Availability); + data.WriteUInt32(SecondsSinceCreated); + data.WriteUInt32(SecondsUntilExpiration); + data.WriteBits(Name.Length, 6); + data.WriteBits(Comment.Length, 10); + data.FlushBits(); + data.WriteString(Name); + data.WriteString(Comment); + } + + public ObjectGuid RecruitGUID; + public string Name = ""; + public uint RecruitVirtualRealm; + public string Comment = ""; + public int CharacterClass; + public int CharacterGender; + public int CharacterLevel; + public int ClassRoles; + public int PlayStyle; + public int Availability; + public uint SecondsSinceCreated; + public uint SecondsUntilExpiration; + } +} diff --git a/Game/Network/Packets/GuildPackets.cs b/Game/Network/Packets/GuildPackets.cs new file mode 100644 index 000000000..94e716d0d --- /dev/null +++ b/Game/Network/Packets/GuildPackets.cs @@ -0,0 +1,1550 @@ +/* + * Copyright (C) 2012-2017 CypherCore + * + * 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, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +using Framework.Constants; +using Framework.Dynamic; +using Game.Entities; +using System.Collections.Generic; + +namespace Game.Network.Packets +{ + public class QueryGuildInfo : ClientPacket + { + public QueryGuildInfo(WorldPacket packet) : base(packet) { } + + public override void Read() + { + GuildGuid = _worldPacket.ReadPackedGuid(); + PlayerGuid = _worldPacket.ReadPackedGuid(); + } + + public ObjectGuid GuildGuid; + public ObjectGuid PlayerGuid; + } + + public class QueryGuildInfoResponse : ServerPacket + { + public QueryGuildInfoResponse() : base(ServerOpcodes.QueryGuildInfoResponse) { } + + public override void Write() + { + _worldPacket.WritePackedGuid(GuildGUID); + _worldPacket.WriteBit(HasGuildInfo); + _worldPacket.FlushBits(); + + if (HasGuildInfo) + { + _worldPacket.WritePackedGuid(Info.GuildGuid); + _worldPacket.WriteUInt32(Info.VirtualRealmAddress); + _worldPacket.WriteUInt32(Info.Ranks.Count); + _worldPacket.WriteUInt32(Info.EmblemStyle); + _worldPacket.WriteUInt32(Info.EmblemColor); + _worldPacket.WriteUInt32(Info.BorderStyle); + _worldPacket.WriteUInt32(Info.BorderColor); + _worldPacket.WriteUInt32(Info.BackgroundColor); + _worldPacket.WriteBits(Info.GuildName.Length, 7); + _worldPacket.FlushBits(); + + foreach (var rank in Info.Ranks) + { + _worldPacket.WriteUInt32(rank.RankID); + _worldPacket.WriteUInt32(rank.RankOrder); + + _worldPacket.WriteBits(rank.RankName.Length, 7); + _worldPacket.WriteString(rank.RankName); + } + + _worldPacket.WriteString(Info.GuildName); + } + + } + + public ObjectGuid GuildGUID; + public GuildInfo Info; + public bool HasGuildInfo; + + public class GuildInfo + { + public ObjectGuid GuildGuid; + + public uint VirtualRealmAddress; // a special identifier made from the Index, BattleGroup and Region. + + public uint EmblemStyle; + public uint EmblemColor; + public uint BorderStyle; + public uint BorderColor; + public uint BackgroundColor; + public List Ranks = new List(); + public string GuildName = ""; + + public struct RankInfo + { + public RankInfo(uint id, uint order, string name) + { + RankID = id; + RankOrder = order; + RankName = name; + } + + public uint RankID; + public uint RankOrder; + public string RankName; + } + } + } + + public class GuildGetRoster : ClientPacket + { + public GuildGetRoster(WorldPacket packet) : base(packet) { } + + public override void Read() { } + } + + public class GuildRoster : ServerPacket + { + public GuildRoster() : base(ServerOpcodes.GuildRoster) + { + MemberData = new List(); + } + + public override void Write() + { + _worldPacket.WriteInt32(NumAccounts); + _worldPacket.WritePackedTime(CreateDate); + _worldPacket.WriteInt32(GuildFlags); + _worldPacket.WriteUInt32(MemberData.Count); + _worldPacket.WriteBits(WelcomeText.Length, 10); + _worldPacket.WriteBits(InfoText.Length, 10); + _worldPacket.FlushBits(); + + MemberData.ForEach(p => p.Write(_worldPacket)); + + _worldPacket.WriteString(WelcomeText); + _worldPacket.WriteString(InfoText); + } + + public List MemberData; + public string WelcomeText; + public string InfoText; + public uint CreateDate; + public int NumAccounts; + public int GuildFlags; + } + + public class GuildRosterUpdate : ServerPacket + { + public GuildRosterUpdate() : base(ServerOpcodes.GuildRosterUpdate) + { + MemberData = new List(); + } + + public override void Write() + { + _worldPacket.WriteUInt32(MemberData.Count); + + MemberData.ForEach(p => p.Write(_worldPacket)); + } + + public List MemberData; + } + + public class GuildUpdateMotdText : ClientPacket + { + public GuildUpdateMotdText(WorldPacket packet) : base(packet) { } + + public override void Read() + { + uint textLen = _worldPacket.ReadBits(10); + MotdText = _worldPacket.ReadString(textLen); + } + + public string MotdText; + } + + public class GuildCommandResult : ServerPacket + { + public GuildCommandResult() : base(ServerOpcodes.GuildCommandResult) { } + + public override void Write() + { + _worldPacket.WriteInt32(Result); + _worldPacket.WriteInt32(Command); + + _worldPacket.WriteBits(Name.Length, 8); + _worldPacket.WriteString(Name); + } + + public string Name; + public GuildCommandError Result; + public GuildCommandType Command; + } + + public class AcceptGuildInvite : ClientPacket + { + public AcceptGuildInvite(WorldPacket packet) : base(packet) { } + + public override void Read() { } + } + + public class GuildDeclineInvitation : ClientPacket + { + public GuildDeclineInvitation(WorldPacket packet) : base(packet) { } + + public override void Read() { } + } + + public class DeclineGuildInvites : ClientPacket + { + public DeclineGuildInvites(WorldPacket packet) : base(packet) { } + + public override void Read() + { + Allow = _worldPacket.HasBit(); + } + + public bool Allow; + } + + public class GuildInviteByName : ClientPacket + { + public GuildInviteByName(WorldPacket packet) : base(packet) { } + + public override void Read() + { + uint nameLen = _worldPacket.ReadBits(9); + Name = _worldPacket.ReadString(nameLen); + } + + public string Name; + } + + public class GuildInvite : ServerPacket + { + public GuildInvite() : base(ServerOpcodes.GuildInvite) { } + + public override void Write() + { + _worldPacket.WriteBits(InviterName.Length, 6); + _worldPacket.WriteBits(GuildName.Length, 7); + _worldPacket.WriteBits(OldGuildName.Length, 7); + + _worldPacket.WriteUInt32(InviterVirtualRealmAddress); + _worldPacket.WriteUInt32(GuildVirtualRealmAddress); + _worldPacket.WritePackedGuid(GuildGUID); + _worldPacket.WriteUInt32(OldGuildVirtualRealmAddress); + _worldPacket.WritePackedGuid(OldGuildGUID); + _worldPacket.WriteUInt32(EmblemStyle); + _worldPacket.WriteUInt32(EmblemColor); + _worldPacket.WriteUInt32(BorderStyle); + _worldPacket.WriteUInt32(BorderColor); + _worldPacket.WriteUInt32(Background); + _worldPacket.WriteInt32(AchievementPoints); + + _worldPacket.WriteString(InviterName); + _worldPacket.WriteString(GuildName); + _worldPacket.WriteString(OldGuildName); + } + + public ObjectGuid GuildGUID; + public ObjectGuid OldGuildGUID; + public int AchievementPoints; + public uint EmblemColor; + public uint EmblemStyle; + public uint BorderStyle; + public uint BorderColor; + public uint Background; + public uint GuildVirtualRealmAddress; + public uint OldGuildVirtualRealmAddress; + public uint InviterVirtualRealmAddress; + public string InviterName; + public string GuildName; + public string OldGuildName; + } + + public class GuildEventPresenceChange : ServerPacket + { + public GuildEventPresenceChange() : base(ServerOpcodes.GuildEventPresenceChange) { } + + public override void Write() + { + _worldPacket.WritePackedGuid(Guid); + _worldPacket.WriteUInt32(VirtualRealmAddress); + + _worldPacket.WriteBits(Name.Length, 6); + _worldPacket.WriteBit(LoggedOn); + _worldPacket.WriteBit(Mobile); + + _worldPacket.WriteString(Name); + } + + public ObjectGuid Guid; + public uint VirtualRealmAddress; + public string Name; + public bool Mobile; + public bool LoggedOn; + } + + public class GuildEventMotd : ServerPacket + { + public GuildEventMotd() : base(ServerOpcodes.GuildEventMotd) { } + + public override void Write() + { + _worldPacket.WriteBits(MotdText.Length, 10); + _worldPacket.WriteString(MotdText); + } + + public string MotdText; + } + + public class GuildEventPlayerJoined : ServerPacket + { + public GuildEventPlayerJoined() : base(ServerOpcodes.GuildEventPlayerJoined) { } + + public override void Write() + { + _worldPacket.WritePackedGuid(Guid); + _worldPacket.WriteUInt32(VirtualRealmAddress); + + _worldPacket.WriteBits(Name.Length, 6); + _worldPacket.WriteString(Name); + } + + public ObjectGuid Guid; + public string Name; + public uint VirtualRealmAddress; + } + + public class GuildEventRankChanged : ServerPacket + { + public GuildEventRankChanged() : base(ServerOpcodes.GuildEventRankChanged) { } + + public override void Write() + { + _worldPacket.WriteUInt32(RankID); + } + + public uint RankID; + } + + public class GuildEventRanksUpdated : ServerPacket + { + public GuildEventRanksUpdated() : base(ServerOpcodes.GuildEventRanksUpdated) { } + + public override void Write() { } + } + + public class GuildEventBankMoneyChanged : ServerPacket + { + public GuildEventBankMoneyChanged() : base(ServerOpcodes.GuildEventBankMoneyChanged) { } + + public override void Write() + { + _worldPacket.WriteUInt64(Money); + } + + public ulong Money; + } + + public class GuildEventDisbanded : ServerPacket + { + public GuildEventDisbanded() : base(ServerOpcodes.GuildEventDisbanded) { } + + public override void Write() { } + } + + public class GuildEventLogQuery : ClientPacket + { + public GuildEventLogQuery(WorldPacket packet) : base(packet) { } + + public override void Read() { } + } + + public class GuildEventLogQueryResults : ServerPacket + { + public GuildEventLogQueryResults() : base(ServerOpcodes.GuildEventLogQueryResults) + { + Entry = new List(); + } + + public override void Write() + { + _worldPacket.WriteUInt32(Entry.Count); + + foreach (GuildEventEntry entry in Entry) + { + _worldPacket.WritePackedGuid(entry.PlayerGUID); + _worldPacket.WritePackedGuid(entry.OtherGUID); + _worldPacket.WriteUInt8(entry.TransactionType); + _worldPacket.WriteUInt8(entry.RankID); + _worldPacket.WriteUInt32(entry.TransactionDate); + } + } + + public List Entry; + } + + public class GuildEventPlayerLeft : ServerPacket + { + public GuildEventPlayerLeft() : base(ServerOpcodes.GuildEventPlayerLeft) { } + + public override void Write() + { + _worldPacket.WriteBit(Removed); + _worldPacket.WriteBits(LeaverName.Length, 6); + _worldPacket.FlushBits(); + + if (Removed) + { + _worldPacket.WriteBits(RemoverName.Length, 6); + _worldPacket.WritePackedGuid(RemoverGUID); + _worldPacket.WriteUInt32(RemoverVirtualRealmAddress); + _worldPacket.WriteString(RemoverName); + } + + _worldPacket.WritePackedGuid(LeaverGUID); + _worldPacket.WriteUInt32(LeaverVirtualRealmAddress); + _worldPacket.WriteString(LeaverName); + } + + public ObjectGuid LeaverGUID; + public string LeaverName; + public uint LeaverVirtualRealmAddress; + public ObjectGuid RemoverGUID; + public string RemoverName; + public uint RemoverVirtualRealmAddress; + public bool Removed; + } + + public class GuildEventNewLeader : ServerPacket + { + public GuildEventNewLeader() : base(ServerOpcodes.GuildEventNewLeader) { } + + public override void Write() + { + _worldPacket.WriteBit(SelfPromoted); + _worldPacket.WriteBits(NewLeaderName.Length, 6); + _worldPacket.WriteBits(OldLeaderName.Length, 6); + + _worldPacket.WritePackedGuid(OldLeaderGUID); + _worldPacket.WriteUInt32(OldLeaderVirtualRealmAddress); + _worldPacket.WritePackedGuid(NewLeaderGUID); + _worldPacket.WriteUInt32(NewLeaderVirtualRealmAddress); + + _worldPacket.WriteString(NewLeaderName); + _worldPacket.WriteString(OldLeaderName); + } + + public ObjectGuid NewLeaderGUID; + public string NewLeaderName; + public uint NewLeaderVirtualRealmAddress; + public ObjectGuid OldLeaderGUID; + public string OldLeaderName; + public uint OldLeaderVirtualRealmAddress; + public bool SelfPromoted; + } + + public class GuildEventTabAdded : ServerPacket + { + public GuildEventTabAdded() : base(ServerOpcodes.GuildEventTabAdded) { } + + public override void Write() { } + } + + public class GuildEventTabModified : ServerPacket + { + public GuildEventTabModified() : base(ServerOpcodes.GuildEventTabModified) { } + + public override void Write() + { + _worldPacket.WriteInt32(Tab); + + _worldPacket.WriteBits(Name.Length, 7); + _worldPacket.WriteBits(Icon.Length, 9); + _worldPacket.FlushBits(); + + _worldPacket.WriteString(Name); + _worldPacket.WriteString(Icon); + } + + public string Icon; + public string Name; + public int Tab; + } + + public class GuildEventTabTextChanged : ServerPacket + { + public GuildEventTabTextChanged() : base(ServerOpcodes.GuildEventTabTextChanged) { } + + public override void Write() + { + _worldPacket.WriteInt32(Tab); + } + + public int Tab; + } + + public class GuildEventBankContentsChanged : ServerPacket + { + public GuildEventBankContentsChanged() : base(ServerOpcodes.GuildEventBankContentsChanged) { } + + public override void Write() { } + } + + public class GuildPermissionsQuery : ClientPacket + { + public GuildPermissionsQuery(WorldPacket packet) : base(packet) { } + + public override void Read() { } + } + + public class GuildPermissionsQueryResults : ServerPacket + { + public GuildPermissionsQueryResults() : base(ServerOpcodes.GuildPermissionsQueryResults) + { + Tab = new List(); + } + + public override void Write() + { + _worldPacket.WriteUInt32(RankID); + _worldPacket.WriteInt32(WithdrawGoldLimit); + _worldPacket.WriteInt32(Flags); + _worldPacket.WriteInt32(NumTabs); + _worldPacket.WriteUInt32(Tab.Count); + + foreach (GuildRankTabPermissions tab in Tab) + { + _worldPacket.WriteInt32(tab.Flags); + _worldPacket.WriteInt32(tab.WithdrawItemLimit); + } + } + + public int NumTabs; + public int WithdrawGoldLimit; + public int Flags; + public uint RankID; + public List Tab; + + public struct GuildRankTabPermissions + { + public int Flags; + public int WithdrawItemLimit; + } + } + + public class GuildSetRankPermissions : ClientPacket + { + public GuildSetRankPermissions(WorldPacket packet) : base(packet) { } + + public override void Read() + { + RankID = _worldPacket.ReadInt32(); + RankOrder = _worldPacket.ReadInt32(); + Flags = _worldPacket.ReadUInt32(); + OldFlags = _worldPacket.ReadUInt32(); + WithdrawGoldLimit = _worldPacket.ReadInt32(); + + for (byte i = 0; i < GuildConst.MaxBankTabs; i++) + { + TabFlags[i] = _worldPacket.ReadInt32(); + TabWithdrawItemLimit[i] = _worldPacket.ReadInt32(); + } + + _worldPacket.ResetBitPos(); + uint rankNameLen = _worldPacket.ReadBits(7); + + RankName = _worldPacket.ReadString(rankNameLen); + } + + public int RankID; + public int RankOrder; + public int WithdrawGoldLimit; + public uint Flags; + public uint OldFlags; + public int[] TabFlags = new int[GuildConst.MaxBankTabs]; + public int[] TabWithdrawItemLimit = new int[GuildConst.MaxBankTabs]; + public string RankName; + } + + public class GuildAddRank : ClientPacket + { + public GuildAddRank(WorldPacket packet) : base(packet) { } + + public override void Read() + { + _worldPacket.WriteBits(Name.Length, 7); + _worldPacket.FlushBits(); + + RankOrder = _worldPacket.ReadInt32(); + _worldPacket.WriteString(Name); + } + + public string Name; + public int RankOrder; + } + + public class GuildAssignMemberRank : ClientPacket + { + public GuildAssignMemberRank(WorldPacket packet) : base(packet) { } + + public override void Read() + { + Member = _worldPacket.ReadPackedGuid(); + RankOrder = _worldPacket.ReadInt32(); + } + + public ObjectGuid Member; + public int RankOrder; + } + + public class GuildDeleteRank : ClientPacket + { + public GuildDeleteRank(WorldPacket packet) : base(packet) { } + + public override void Read() + { + RankOrder = _worldPacket.ReadInt32(); + } + + public int RankOrder; + } + + public class GuildGetRanks : ClientPacket + { + public GuildGetRanks(WorldPacket packet) : base(packet) { } + + public override void Read() + { + GuildGUID = _worldPacket.ReadPackedGuid(); + } + + public ObjectGuid GuildGUID; + } + + public class GuildRanks : ServerPacket + { + public GuildRanks() : base(ServerOpcodes.GuildRanks) + { + Ranks = new List(); + } + + public override void Write() + { + _worldPacket.WriteUInt32(Ranks.Count); + + Ranks.ForEach(p => p.Write(_worldPacket)); + } + + public List Ranks; + } + + public class GuildSendRankChange : ServerPacket + { + public GuildSendRankChange() : base(ServerOpcodes.GuildSendRankChange) { } + + public override void Write() + { + _worldPacket.WritePackedGuid(Officer); + _worldPacket.WritePackedGuid(Other); + _worldPacket.WriteUInt32(RankID); + + _worldPacket.WriteBit(Promote); + _worldPacket.FlushBits(); + } + + public ObjectGuid Other; + public ObjectGuid Officer; + public bool Promote; + public uint RankID; + } + + public class GuildShiftRank : ClientPacket + { + public GuildShiftRank(WorldPacket packet) : base(packet) { } + + public override void Read() + { + RankOrder = _worldPacket.ReadInt32(); + ShiftUp = _worldPacket.HasBit(); + } + + public bool ShiftUp; + public int RankOrder; + } + + public class GuildUpdateInfoText : ClientPacket + { + public GuildUpdateInfoText(WorldPacket packet) : base(packet) { } + + public override void Read() + { + uint textLen = _worldPacket.ReadBits(11); + InfoText = _worldPacket.ReadString(textLen); + } + + public string InfoText; + } + + public class GuildSetMemberNote : ClientPacket + { + public GuildSetMemberNote(WorldPacket packet) : base(packet) { } + + public override void Read() + { + NoteeGUID = _worldPacket.ReadPackedGuid(); + + uint noteLen = _worldPacket.ReadBits(8); + IsPublic = _worldPacket.HasBit(); + + Note = _worldPacket.ReadString(noteLen); + } + + public ObjectGuid NoteeGUID; + public bool IsPublic; ///< 0 == Officer, 1 == Public + public string Note; + } + + public class GuildMemberUpdateNote : ServerPacket + { + public GuildMemberUpdateNote() : base(ServerOpcodes.GuildMemberUpdateNote) { } + + public override void Write() + { + _worldPacket.WritePackedGuid(Member); + + _worldPacket.WriteBits(Note.Length, 8); + _worldPacket.WriteBit(IsPublic); + _worldPacket.FlushBits(); + + _worldPacket.WriteString(Note); + } + + public ObjectGuid Member; + public bool IsPublic; ///< 0 == Officer, 1 == Public + public string Note; + } + + public class GuildMemberDailyReset : ServerPacket + { + public GuildMemberDailyReset() : base(ServerOpcodes.GuildMemberDailyReset) { } + + public override void Write() { } + } + + public class GuildDelete : ClientPacket + { + public GuildDelete(WorldPacket packet) : base(packet) { } + + public override void Read() { } + } + + public class GuildDemoteMember : ClientPacket + { + public GuildDemoteMember(WorldPacket packet) : base(packet) { } + + public override void Read() + { + Demotee = _worldPacket.ReadPackedGuid(); + } + + public ObjectGuid Demotee; + } + + public class GuildPromoteMember : ClientPacket + { + public GuildPromoteMember(WorldPacket packet) : base(packet) { } + + public override void Read() + { + Promotee = _worldPacket.ReadPackedGuid(); + } + + public ObjectGuid Promotee; + } + + public class GuildOfficerRemoveMember : ClientPacket + { + public GuildOfficerRemoveMember(WorldPacket packet) : base(packet) { } + + public override void Read() + { + Removee = _worldPacket.ReadPackedGuid(); + } + + public ObjectGuid Removee; + } + + public class GuildLeave : ClientPacket + { + public GuildLeave(WorldPacket packet) : base(packet) { } + + public override void Read() { } + } + + public class GuildChangeNameRequest : ClientPacket + { + public GuildChangeNameRequest(WorldPacket packet) : base(packet) { } + + public override void Read() + { + uint nameLen = _worldPacket.ReadBits(7); + NewName = _worldPacket.ReadString(nameLen); + } + + public string NewName; + } + + public class GuildFlaggedForRename : ServerPacket + { + public GuildFlaggedForRename() : base(ServerOpcodes.GuildFlaggedForRename) { } + + public override void Write() + { + _worldPacket.WriteBit(FlagSet); + } + + public bool FlagSet; + } + + public class RequestGuildPartyState : ClientPacket + { + public RequestGuildPartyState(WorldPacket packet) : base(packet) { } + + public override void Read() + { + GuildGUID = _worldPacket.ReadPackedGuid(); + } + + public ObjectGuid GuildGUID; + } + + public class GuildPartyState : ServerPacket + { + public GuildPartyState() : base(ServerOpcodes.GuildPartyState, ConnectionType.Instance) { } + + public override void Write() + { + _worldPacket.WriteBit(InGuildParty); + _worldPacket.FlushBits(); + + _worldPacket.WriteInt32(NumMembers); + _worldPacket.WriteInt32(NumRequired); + _worldPacket.WriteFloat(GuildXPEarnedMult); + } + + public float GuildXPEarnedMult = 0.0f; + public int NumMembers; + public int NumRequired; + public bool InGuildParty; + } + + public class RequestGuildRewardsList : ClientPacket + { + public RequestGuildRewardsList(WorldPacket packet) : base(packet) { } + + public override void Read() + { + CurrentVersion = _worldPacket.ReadUInt32(); + } + + public uint CurrentVersion; + } + + public class GuildRewardList : ServerPacket + { + public GuildRewardList() : base(ServerOpcodes.GuildRewardList) + { + RewardItems = new List(); + } + + public override void Write() + { + _worldPacket .WriteUInt32( Version); + _worldPacket.WriteUInt32(RewardItems.Count); + + RewardItems.ForEach(p => p.Write(_worldPacket)); + } + + public List RewardItems; + public uint Version; + } + + public class GuildBankActivate : ClientPacket + { + public GuildBankActivate(WorldPacket packet) : base(packet) { } + + public override void Read() + { + Banker = _worldPacket.ReadPackedGuid(); + FullUpdate = _worldPacket.HasBit(); + } + + public ObjectGuid Banker; + public bool FullUpdate; + } + + public class GuildBankBuyTab : ClientPacket + { + public GuildBankBuyTab(WorldPacket packet) : base(packet) { } + + public override void Read() + { + Banker = _worldPacket.ReadPackedGuid(); + BankTab = _worldPacket.ReadUInt8(); + } + + public ObjectGuid Banker; + public byte BankTab; + } + + public class GuildBankUpdateTab : ClientPacket + { + public GuildBankUpdateTab(WorldPacket packet) : base(packet) { } + + public override void Read() + { + Banker = _worldPacket.ReadPackedGuid(); + BankTab = _worldPacket.ReadUInt8(); + + _worldPacket.ResetBitPos(); + uint nameLen = _worldPacket.ReadBits(7); + uint iconLen = _worldPacket.ReadBits(9); + + Name = _worldPacket.ReadString(nameLen); + Icon = _worldPacket.ReadString(iconLen); + } + + public ObjectGuid Banker; + public byte BankTab; + public string Name; + public string Icon; + } + + public class GuildBankDepositMoney : ClientPacket + { + public GuildBankDepositMoney(WorldPacket packet) : base(packet) { } + + public override void Read() + { + Banker = _worldPacket.ReadPackedGuid(); + Money = _worldPacket.ReadUInt64(); + } + + public ObjectGuid Banker; + public ulong Money; + } + + public class GuildBankQueryTab : ClientPacket + { + public GuildBankQueryTab(WorldPacket packet) : base(packet) { } + + public override void Read() + { + Banker = _worldPacket.ReadPackedGuid(); + Tab = _worldPacket.ReadUInt8(); + + FullUpdate = _worldPacket.HasBit(); + } + + public ObjectGuid Banker; + public byte Tab; + public bool FullUpdate; + } + + public class GuildBankRemainingWithdrawMoneyQuery : ClientPacket + { + public GuildBankRemainingWithdrawMoneyQuery(WorldPacket packet) : base(packet) { } + + public override void Read() { } + } + + public class GuildBankRemainingWithdrawMoney : ServerPacket + { + public GuildBankRemainingWithdrawMoney() : base(ServerOpcodes.GuildBankRemainingWithdrawMoney) { } + + public override void Write() + { + _worldPacket .WriteInt64( RemainingWithdrawMoney); + } + + public long RemainingWithdrawMoney; + } + + public class GuildBankWithdrawMoney : ClientPacket + { + public GuildBankWithdrawMoney(WorldPacket packet) : base(packet) { } + + public override void Read() + { + Banker = _worldPacket.ReadPackedGuid(); + Money = _worldPacket.ReadUInt64(); + } + + public ObjectGuid Banker; + public ulong Money; + } + + public class GuildBankQueryResults : ServerPacket + { + public GuildBankQueryResults() : base(ServerOpcodes.GuildBankQueryResults) + { + ItemInfo = new List(); + TabInfo = new List(); + } + + public override void Write() + { + _worldPacket.WriteUInt64(Money); + _worldPacket.WriteInt32(Tab); + _worldPacket.WriteInt32(WithdrawalsRemaining); + _worldPacket.WriteUInt32(TabInfo.Count); + _worldPacket.WriteUInt32(ItemInfo.Count); + _worldPacket.WriteBit(FullUpdate); + _worldPacket.FlushBits(); + + foreach (GuildBankTabInfo tab in TabInfo) + { + _worldPacket.WriteUInt32(tab.TabIndex); + _worldPacket.WriteBits(tab.Name.Length, 7); + _worldPacket.WriteBits(tab.Icon.Length, 9);; + + _worldPacket.WriteString(tab.Name); + _worldPacket.WriteString(tab.Icon); + } + + foreach (GuildBankItemInfo item in ItemInfo) + { + _worldPacket.WriteInt32(item.Slot); + _worldPacket.WriteInt32(item.Count); + _worldPacket.WriteInt32(item.EnchantmentID); + _worldPacket.WriteInt32(item.Charges); + _worldPacket.WriteInt32(item.OnUseEnchantmentID); + _worldPacket.WriteInt32(item.Flags); + + item.Item.Write(_worldPacket); + + _worldPacket.WriteBits(item.SocketEnchant.Count, 2); + _worldPacket.WriteBit(item.Locked); + _worldPacket.FlushBits(); + + foreach (ItemGemData socketEnchant in item.SocketEnchant) + socketEnchant.Write(_worldPacket); + } + } + + public List ItemInfo; + public List TabInfo; + public int WithdrawalsRemaining; + public int Tab; + public ulong Money; + public bool FullUpdate; + } + + public class GuildBankSwapItems : ClientPacket + { + public GuildBankSwapItems(WorldPacket packet) : base(packet) { } + + public override void Read() + { + Banker = _worldPacket.ReadPackedGuid(); + BankTab = _worldPacket.ReadUInt8(); + BankSlot = _worldPacket.ReadUInt8(); + ItemID = _worldPacket.ReadUInt32(); + BankTab1 = _worldPacket.ReadUInt8(); + BankSlot1 = _worldPacket.ReadUInt8(); + ItemID1 = _worldPacket.ReadUInt32(); + BankItemCount = _worldPacket.ReadInt32(); + ContainerSlot = _worldPacket.ReadUInt8(); + ContainerItemSlot = _worldPacket.ReadUInt8(); + ToSlot = _worldPacket.ReadUInt8(); + StackCount = _worldPacket.ReadInt32(); + + _worldPacket.ResetBitPos(); + BankOnly = _worldPacket.HasBit(); + AutoStore = _worldPacket.HasBit(); + } + + public ObjectGuid Banker; + public int StackCount; + public int BankItemCount; + public uint ItemID; + public uint ItemID1; + public byte ToSlot; + public byte BankSlot; + public byte BankSlot1; + public byte BankTab; + public byte BankTab1; + public byte ContainerSlot; + public byte ContainerItemSlot; + public bool AutoStore; + public bool BankOnly; + } + + public class GuildBankLogQuery : ClientPacket + { + public GuildBankLogQuery(WorldPacket packet) : base(packet) { } + + public override void Read() + { + Tab = _worldPacket.ReadInt32(); + } + + public int Tab; + } + + public class GuildBankLogQueryResults : ServerPacket + { + public GuildBankLogQueryResults() : base(ServerOpcodes.GuildBankLogQueryResults) + { + Entry = new List(); + } + + public override void Write() + { + _worldPacket.WriteInt32(Tab); + _worldPacket.WriteUInt32(Entry.Count); + _worldPacket.WriteBit(WeeklyBonusMoney.HasValue); + _worldPacket.FlushBits(); + + foreach (GuildBankLogEntry logEntry in Entry) + { + _worldPacket.WritePackedGuid(logEntry.PlayerGUID); + _worldPacket.WriteUInt32(logEntry.TimeOffset); + _worldPacket.WriteInt8(logEntry.EntryType); + + _worldPacket.WriteBit(logEntry.Money.HasValue); + _worldPacket.WriteBit(logEntry.ItemID.HasValue); + _worldPacket.WriteBit(logEntry.Count.HasValue); + _worldPacket.WriteBit(logEntry.OtherTab.HasValue); + _worldPacket.FlushBits(); + + if (logEntry.Money.HasValue) + _worldPacket.WriteUInt64(logEntry.Money.Value); + + if (logEntry.ItemID.HasValue) + _worldPacket.WriteInt32(logEntry.ItemID.Value); + + if (logEntry.Count.HasValue) + _worldPacket.WriteInt32(logEntry.Count.Value); + + if (logEntry.OtherTab.HasValue) + _worldPacket.WriteInt8(logEntry.OtherTab.Value); + } + + if (WeeklyBonusMoney.HasValue) + _worldPacket.WriteUInt64(WeeklyBonusMoney.Value); + } + + public int Tab; + public List Entry; + public Optional WeeklyBonusMoney; + } + + public class GuildBankTextQuery : ClientPacket + { + public GuildBankTextQuery(WorldPacket packet) : base(packet) { } + + public override void Read() + { + Tab = _worldPacket.ReadInt32(); + } + + public int Tab; + } + + public class GuildBankTextQueryResult : ServerPacket + { + public GuildBankTextQueryResult() : base(ServerOpcodes.GuildBankTextQueryResult) { } + + public override void Write() + { + _worldPacket.WriteInt32(Tab); + + _worldPacket.WriteBits(Text.Length, 14); + _worldPacket.WriteString(Text); + } + + public int Tab; + public string Text; + } + + public class GuildBankSetTabText : ClientPacket + { + public GuildBankSetTabText(WorldPacket packet) : base(packet) { } + + public override void Read() + { + Tab = _worldPacket.ReadInt32(); + TabText = _worldPacket.ReadString(_worldPacket.ReadBits(14)); + } + + public int Tab; + public string TabText; + } + + public class GuildQueryNews : ClientPacket + { + public GuildQueryNews(WorldPacket packet) : base(packet) { } + + public override void Read() + { + GuildGUID = _worldPacket.ReadPackedGuid(); + } + + public ObjectGuid GuildGUID; + } + + public class GuildNewsPkt : ServerPacket + { + public GuildNewsPkt() : base(ServerOpcodes.GuildNews) + { + NewsEvents = new List(); + } + + public override void Write() + { + _worldPacket.WriteUInt32(NewsEvents.Count); + foreach (var newsEvent in NewsEvents) + newsEvent.Write(_worldPacket); + } + + public List NewsEvents; + } + + public class GuildNewsUpdateSticky : ClientPacket + { + public GuildNewsUpdateSticky(WorldPacket packet) : base(packet) { } + + public override void Read() + { + GuildGUID = _worldPacket.ReadPackedGuid(); + NewsID = _worldPacket.ReadInt32(); + + Sticky = _worldPacket.HasBit(); + } + + public int NewsID; + public ObjectGuid GuildGUID; + public bool Sticky; + } + + public class GuildSetGuildMaster : ClientPacket + { + public GuildSetGuildMaster(WorldPacket packet) : base(packet) { } + + public override void Read() + { + uint nameLen = _worldPacket.ReadBits(9); + NewMasterName = _worldPacket.ReadString(nameLen); + } + + public string NewMasterName; + } + + public class GuildChallengeUpdateRequest : ClientPacket + { + public GuildChallengeUpdateRequest(WorldPacket packet) : base(packet) { } + + public override void Read() { } + } + + public class GuildChallengeUpdate : ServerPacket + { + public GuildChallengeUpdate() : base(ServerOpcodes.GuildChallengeUpdate) { } + + public override void Write() + { + for (int i = 0; i < GuildConst.ChallengesTypes; ++i) + _worldPacket.WriteInt32(CurrentCount[i]); + + for (int i = 0; i < GuildConst.ChallengesTypes; ++i) + _worldPacket.WriteInt32(MaxCount[i]); + + for (int i = 0; i < GuildConst.ChallengesTypes; ++i) + _worldPacket.WriteInt32(MaxLevelGold[i]); + + for (int i = 0; i < GuildConst.ChallengesTypes; ++i) + _worldPacket.WriteInt32(Gold[i]); + } + + public int[] CurrentCount = new int[GuildConst.ChallengesTypes]; + public int[] MaxCount = new int[GuildConst.ChallengesTypes]; + public int[] Gold = new int[GuildConst.ChallengesTypes]; + public int[] MaxLevelGold = new int[GuildConst.ChallengesTypes]; + } + + public class SaveGuildEmblem : ClientPacket + { + public SaveGuildEmblem(WorldPacket packet) : base(packet) { } + + public override void Read() + { + Vendor = _worldPacket.ReadPackedGuid(); + EStyle = _worldPacket.ReadUInt32(); + EColor = _worldPacket.ReadUInt32(); + BStyle = _worldPacket.ReadUInt32(); + BColor = _worldPacket.ReadUInt32(); + Bg = _worldPacket.ReadUInt32(); + } + + public ObjectGuid Vendor; + public uint BStyle; + public uint EStyle; + public uint BColor; + public uint EColor; + public uint Bg; + } + + public class PlayerSaveGuildEmblem : ServerPacket + { + public PlayerSaveGuildEmblem() : base(ServerOpcodes.PlayerSaveGuildEmblem) { } + + public override void Write() + { + _worldPacket.WriteInt32(Error); + } + + public GuildEmblemError Error; + } + + class GuildSetAchievementTracking : ClientPacket + { + public GuildSetAchievementTracking(WorldPacket packet) : base(packet) { } + + public override void Read() + { + uint count = _worldPacket.ReadUInt32(); + + for (uint i = 0; i < count; ++i) + AchievementIDs.Add(_worldPacket.ReadUInt32()); + } + + public List AchievementIDs = new List(); + } + + class GuildNameChanged : ServerPacket + { + public GuildNameChanged() : base(ServerOpcodes.GuildNameChanged) { } + + public override void Write() + { + _worldPacket.WritePackedGuid(GuildGUID); + _worldPacket.WriteBits(GuildName.Length, 7); + _worldPacket.FlushBits(); + _worldPacket.WriteString(GuildName); + } + + public ObjectGuid GuildGUID; + public string GuildName; + } + + //Structs + public struct GuildRosterProfessionData + { + public void Write(WorldPacket data) + { + data.WriteInt32(DbID); + data.WriteInt32(Rank); + data.WriteInt32(Step); + } + + public int DbID; + public int Rank; + public int Step; + } + + public class GuildRosterMemberData + { + public void Write(WorldPacket data) + { + data.WritePackedGuid(Guid); + data.WriteInt32(RankID); + data.WriteInt32(AreaID); + data.WriteInt32(PersonalAchievementPoints); + data.WriteInt32(GuildReputation); + data.WriteFloat(LastSave); + + for (byte i = 0; i < 2; i++) + Profession[i].Write(data); + + data.WriteUInt32(VirtualRealmAddress); + data.WriteUInt8(Status); + data.WriteUInt8(Level); + data.WriteUInt8(ClassID); + data.WriteUInt8(Gender); + + data.WriteBits(Name.Length, 6); + data.WriteBits(Note.Length, 8); + data.WriteBits(OfficerNote.Length, 8); + data.WriteBit(Authenticated); + data.WriteBit(SorEligible); + + data.WriteString(Name); + data.WriteString(Note); + data.WriteString(OfficerNote); + } + + public ObjectGuid Guid; + public long WeeklyXP; + public long TotalXP; + public int RankID; + public int AreaID; + public int PersonalAchievementPoints; + public int GuildReputation; + public int GuildRepToCap; + public float LastSave; + public string Name; + public uint VirtualRealmAddress; + public string Note; + public string OfficerNote; + public byte Status; + public byte Level; + public byte ClassID; + public byte Gender; + public bool Authenticated; + public bool SorEligible; + public GuildRosterProfessionData[] Profession = new GuildRosterProfessionData[2]; + } + + public struct GuildEventEntry + { + public ObjectGuid PlayerGUID; + public ObjectGuid OtherGUID; + public byte TransactionType; + public byte RankID; + public uint TransactionDate; + } + + public class GuildRankData + { + public void Write(WorldPacket data) + { + data.WriteUInt32(RankID); + data.WriteUInt32(RankOrder); + data.WriteUInt32(Flags); + data.WriteUInt32(WithdrawGoldLimit); + + for (byte i = 0; i < GuildConst.MaxBankTabs; i++) + { + data.WriteUInt32(TabFlags[i]); + data.WriteUInt32(TabWithdrawItemLimit[i]); + } + + data.WriteBits(RankName.Length, 7); + data.WriteString(RankName); + } + + public uint RankID; + public uint RankOrder; + public uint Flags; + public uint WithdrawGoldLimit; + public string RankName; + public uint[] TabFlags = new uint[GuildConst.MaxBankTabs]; + public uint[] TabWithdrawItemLimit = new uint[GuildConst.MaxBankTabs]; + } + + public class GuildRewardItem + { + public void Write(WorldPacket data) + { + data.WriteUInt32(ItemID); + data.WriteUInt32(Unk4); + data.WriteUInt32(AchievementsRequired.Count); + data.WriteUInt32(RaceMask); + data.WriteUInt32(MinGuildLevel); + data.WriteUInt32(MinGuildRep); + data.WriteUInt32(Cost); + + AchievementsRequired.ForEach(p => data.WriteUInt32(p)); + } + + public uint ItemID; + public uint Unk4; + public List AchievementsRequired = new List(); + public uint RaceMask; + public int MinGuildLevel; + public int MinGuildRep; + public ulong Cost; + } + + public class GuildBankItemInfo + { + public ItemInstance Item; + public int Slot; + public int Count; + public int EnchantmentID; + public int Charges; + public int OnUseEnchantmentID; + public int Flags; + public bool Locked; + public List SocketEnchant = new List(); + } + + public struct GuildBankTabInfo + { + public int TabIndex; + public string Name; + public string Icon; + } + + public struct GuildBankLogEntry + { + public ObjectGuid PlayerGUID; + public uint TimeOffset; + public sbyte EntryType; + public Optional Money; + public Optional ItemID; + public Optional Count; + public Optional OtherTab; + } + + public class GuildNewsEvent + { + public void Write(WorldPacket data) + { + data.WriteInt32(Id); + data.WritePackedTime(CompletedDate); + data.WriteInt32(Type); + data.WriteInt32(Flags); + + for (byte i = 0; i < 2; i++) + data.WriteInt32(Data[i]); + + data.WritePackedGuid(MemberGuid); + data.WriteUInt32(MemberList.Count); + + foreach (ObjectGuid memberGuid in MemberList) + data.WritePackedGuid(memberGuid); + + data.WriteBit(Item.HasValue); + data.FlushBits(); + + if (Item.HasValue) + Item.Value.Write(data); + } + + public int Id; + public uint CompletedDate; + public int Type; + public int Flags; + public int[] Data = new int[2]; + public ObjectGuid MemberGuid; + public List MemberList = new List(); + public Optional Item; + } +} diff --git a/Game/Network/Packets/HotfixPackets.cs b/Game/Network/Packets/HotfixPackets.cs new file mode 100644 index 000000000..4a5cef3c5 --- /dev/null +++ b/Game/Network/Packets/HotfixPackets.cs @@ -0,0 +1,150 @@ +/* + * Copyright (C) 2012-2017 CypherCore + * + * 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, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +using Framework.Constants; +using Framework.Dynamic; +using Framework.IO; +using Game.DataStorage; +using Game.Entities; +using System.Collections.Generic; + +namespace Game.Network.Packets +{ + class DBQueryBulk : ClientPacket + { + public DBQueryBulk(WorldPacket packet) : base(packet) { } + + public override void Read() + { + TableHash = _worldPacket.ReadUInt32(); + + uint count = _worldPacket.ReadBits(13); + for (uint i = 0; i < count; ++i) + { + Queries.Add(new DBQueryRecord(_worldPacket.ReadPackedGuid(), _worldPacket.ReadUInt32())); + } + } + + public uint TableHash; + public List Queries = new List(); + + public struct DBQueryRecord + { + public DBQueryRecord(ObjectGuid guid, uint recordId) + { + GUID = guid; + RecordID = recordId; + } + + public ObjectGuid GUID; + public uint RecordID; + } + } + + public class DBReply : ServerPacket + { + public DBReply() : base(ServerOpcodes.DbReply) { } + + public override void Write() + { + _worldPacket.WriteUInt32(TableHash); + _worldPacket.WriteUInt32(RecordID); + _worldPacket.WriteUInt32(Timestamp); + _worldPacket.WriteBit(Allow); + _worldPacket.WriteUInt32(Data.GetSize()); + _worldPacket.WriteBytes(Data.GetData()); + } + + public uint TableHash; + public uint Timestamp; + public uint RecordID; + public bool Allow; + + public ByteBuffer Data = new ByteBuffer(); + } + + class HotfixList : ServerPacket + { + public HotfixList(int hotfixCacheVersion, Dictionary hotfixes) : base(ServerOpcodes.HotfixList) + { + HotfixCacheVersion = hotfixCacheVersion; + Hotfixes = hotfixes; + } + + public override void Write() + { + _worldPacket.WriteInt32(HotfixCacheVersion); + + _worldPacket.WriteUInt32(Hotfixes.Count); + foreach (var hotfixEntry in Hotfixes) + _worldPacket.WriteInt64(hotfixEntry.Key); + } + + public int HotfixCacheVersion; + public Dictionary Hotfixes = new Dictionary(); + } + + class HotfixQuery : ClientPacket + { + public HotfixQuery(WorldPacket packet) : base(packet) { } + + public override void Read() + { + uint hotfixCount = _worldPacket.ReadUInt32(); + //if (hotfixCount > Global.DB2Mgr.GetHotfixData().Count) + //throw PacketArrayMaxCapacityException(hotfixCount, sDB2Manager.GetHotfixData().size()); + + for (var i = 0; i < hotfixCount; ++i) + Hotfixes.Add(_worldPacket.ReadUInt64()); + } + + public List Hotfixes = new List(); + } + + class HotfixQueryResponse : ServerPacket + { + public HotfixQueryResponse() : base(ServerOpcodes.HotfixQueryResponse) { } + + public override void Write() + { + _worldPacket.WriteUInt32(Hotfixes.Count); + foreach (HotfixData hotfix in Hotfixes) + hotfix.Write(_worldPacket); + } + + public List Hotfixes = new List(); + + public class HotfixData + { + public void Write(WorldPacket data) + { + data.WriteInt64(ID); + data.WriteInt32(RecordID); + data.WriteBit(Data.HasValue); + if (Data.HasValue) + { + data.WriteUInt32(Data.Value.GetSize()); + data.WriteBytes(Data.Value); + } + } + + public ulong ID; + public int RecordID; + public Optional Data; + } + } +} diff --git a/Game/Network/Packets/InspectPackets.cs b/Game/Network/Packets/InspectPackets.cs new file mode 100644 index 000000000..279929a84 --- /dev/null +++ b/Game/Network/Packets/InspectPackets.cs @@ -0,0 +1,275 @@ +/* + * Copyright (C) 2012-2017 CypherCore + * + * 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, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +using Framework.Constants; +using Framework.Dynamic; +using Game.Entities; +using System.Collections.Generic; + +namespace Game.Network.Packets +{ + public class Inspect : ClientPacket + { + public Inspect(WorldPacket packet) : base(packet) { } + + public override void Read() + { + Target = _worldPacket.ReadPackedGuid(); + } + + public ObjectGuid Target; + } + + public class InspectResult : ServerPacket + { + public InspectResult() : base(ServerOpcodes.InspectResult) { } + + public override void Write() + { + _worldPacket.WritePackedGuid(InspecteeGUID); + _worldPacket.WriteUInt32(Items.Count); + _worldPacket.WriteUInt32(Glyphs.Count); + _worldPacket.WriteUInt32(Talents.Count); + _worldPacket.WriteUInt32(PvpTalents.Count); + _worldPacket.WriteInt32(ClassID); + _worldPacket.WriteInt32(SpecializationID); + _worldPacket.WriteInt32(GenderID); + + for (int i = 0; i < Glyphs.Count; ++i) + _worldPacket.WriteUInt16(Glyphs[i]); + + for (int i = 0; i < Talents.Count; ++i) + _worldPacket.WriteUInt16(Talents[i]); + + for (int i = 0; i < PvpTalents.Count; ++i) + _worldPacket.WriteUInt16(PvpTalents[i]); + + _worldPacket.WriteBit(GuildData.HasValue); + _worldPacket.FlushBits(); + + Items.ForEach(p => p.Write(_worldPacket)); + + if (GuildData.HasValue) + GuildData.Value.Write(_worldPacket); + } + + public ObjectGuid InspecteeGUID; + public List Items = new List(); + public List Glyphs = new List(); + public List Talents = new List(); + public List PvpTalents = new List(); + public Class ClassID = Class.None; + public Gender GenderID = Gender.None; + public Optional GuildData = new Optional(); + public int SpecializationID; + } + + public class RequestHonorStats : ClientPacket + { + public RequestHonorStats(WorldPacket packet) : base(packet) { } + + public override void Read() + { + TargetGUID = _worldPacket.ReadPackedGuid(); + } + + public ObjectGuid TargetGUID; + } + + public class InspectHonorStats : ServerPacket + { + public InspectHonorStats() : base(ServerOpcodes.InspectHonorStats) { } + + public override void Write() + { + _worldPacket.WritePackedGuid(PlayerGUID); + _worldPacket.WriteUInt8(LifetimeMaxRank); + _worldPacket.WriteUInt16(YesterdayHK); /// @todo: confirm order + _worldPacket.WriteUInt16(TodayHK); /// @todo: confirm order + _worldPacket.WriteUInt32(LifetimeHK); + } + + public ObjectGuid PlayerGUID; + public uint LifetimeHK; + public ushort YesterdayHK; + public ushort TodayHK; + public byte LifetimeMaxRank; + } + + public class InspectPVPRequest : ClientPacket + { + public InspectPVPRequest(WorldPacket packet) : base(packet) { } + + public override void Read() + { + InspectTarget = _worldPacket.ReadPackedGuid(); + InspectRealmAddress = _worldPacket.ReadUInt32(); + } + + public ObjectGuid InspectTarget; + public uint InspectRealmAddress; + } + + public class InspectPVPResponse : ServerPacket + { + public InspectPVPResponse() : base(ServerOpcodes.InspectPvp) { } + + public override void Write() + { + _worldPacket.WritePackedGuid(ClientGUID); + + _worldPacket.WriteBits(Bracket.Count, 3); + _worldPacket.FlushBits(); + + Bracket.ForEach(p => p.Write(_worldPacket)); + } + + public List Bracket; + public ObjectGuid ClientGUID; + } + + public class QueryInspectAchievements : ClientPacket + { + public QueryInspectAchievements(WorldPacket packet) : base(packet) { } + + public override void Read() + { + Guid = _worldPacket.ReadPackedGuid(); + } + + public ObjectGuid Guid; + } + + /// RespondInspectAchievements in AchievementPackets + + + //Structs + public struct InspectEnchantData + { + public InspectEnchantData(uint id, byte index) + { + Id = id; + Index = index; + } + + public void Write(WorldPacket data) + { + data.WriteUInt32(Id); + data.WriteUInt8(Index); + } + + public uint Id; + public byte Index; + } + + public class InspectItemData + { + public InspectItemData(Item item, byte index) + { + CreatorGUID = item.GetGuidValue(ItemFields.Creator); + + Item = new ItemInstance(item); + Index = index; + Usable = true; /// @todo + + for (EnchantmentSlot enchant = 0; enchant < EnchantmentSlot.Max; ++enchant) + { + uint enchId = item.GetEnchantmentId(enchant); + if (enchId != 0) + Enchants.Add(new InspectEnchantData(enchId, (byte)enchant)); + } + + byte i = 0; + foreach (ItemDynamicFieldGems gemData in item.GetGems()) + { + if (gemData.ItemId != 0) + { + ItemGemData gem = new ItemGemData(); + gem.Slot = i; + gem.Item = new ItemInstance(gemData); + Gems.Add(gem); + } + ++i; + } + } + + public void Write(WorldPacket data) + { + data.WritePackedGuid(CreatorGUID); + data.WriteUInt8(Index); + Item.Write(data); + data.WriteBit(Usable); + data.WriteBits(Enchants.Count, 4); + data.WriteBits(Gems.Count, 2); + data.FlushBits(); + + foreach (var gem in Gems) + gem.Write(data); + + for (int i = 0; i < Enchants.Count; ++i) + Enchants[i].Write(data); + } + + public ObjectGuid CreatorGUID; + public ItemInstance Item; + public byte Index; + public bool Usable; + public List Enchants = new List(); + public List Gems = new List(); + } + + public struct InspectGuildData + { + public void Write(WorldPacket data) + { + data.WritePackedGuid(GuildGUID); + data.WriteInt32(NumGuildMembers); + data.WriteInt32(AchievementPoints); + } + + public ObjectGuid GuildGUID; + public int NumGuildMembers; + public int AchievementPoints; + } + + public struct PVPBracketData + { + public void Write(WorldPacket data) + { + data.WriteInt32(Rating); + data.WriteInt32(Rank); + data.WriteInt32(WeeklyPlayed); + data.WriteInt32(WeeklyWon); + data.WriteInt32(SeasonPlayed); + data.WriteInt32(SeasonWon); + data.WriteInt32(WeeklyBestRating); + data.WriteInt32(Unk710); + data.WriteUInt8(Bracket); + } + + public int Rating; + public int Rank; + public int WeeklyPlayed; + public int WeeklyWon; + public int SeasonPlayed; + public int SeasonWon; + public int WeeklyBestRating; + public int Unk710; + public byte Bracket; + } +} + diff --git a/Game/Network/Packets/InstancePackets.cs b/Game/Network/Packets/InstancePackets.cs new file mode 100644 index 000000000..3cba2746b --- /dev/null +++ b/Game/Network/Packets/InstancePackets.cs @@ -0,0 +1,294 @@ +/* + * Copyright (C) 2012-2017 CypherCore + * + * 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, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +using Framework.Constants; +using Game.Entities; +using System.Collections.Generic; + +namespace Game.Network.Packets +{ + class UpdateLastInstance : ServerPacket + { + public UpdateLastInstance() : base(ServerOpcodes.UpdateLastInstance) { } + + public override void Write() + { + _worldPacket.WriteUInt32(MapID); + } + + public uint MapID; + } + + class InstanceInfoPkt : ServerPacket + { + public InstanceInfoPkt() : base(ServerOpcodes.InstanceInfo) { } + + public override void Write() + { + _worldPacket.WriteInt32(LockList.Count); + + foreach (InstanceLockInfos lockInfos in LockList) + lockInfos.Write(_worldPacket); + } + + public List LockList = new List(); + } + + class ResetInstances : ClientPacket + { + public ResetInstances(WorldPacket packet) : base(packet) { } + + public override void Read() { } + } + + class InstanceReset : ServerPacket + { + public InstanceReset() : base(ServerOpcodes.InstanceReset) { } + + public override void Write() + { + _worldPacket.WriteUInt32(MapID); + } + + public uint MapID; + } + + class InstanceResetFailed : ServerPacket + { + public InstanceResetFailed() : base(ServerOpcodes.InstanceResetFailed) { } + + public override void Write() + { + _worldPacket.WriteUInt32(MapID); + _worldPacket.WriteBits(ResetFailedReason, 2); + _worldPacket.FlushBits(); + } + + public uint MapID; + public ResetFailedReason ResetFailedReason; + } + + class ResetFailedNotify : ServerPacket + { + public ResetFailedNotify() : base(ServerOpcodes.ResetFailedNotify) { } + + public override void Write() { } + } + + class InstanceSaveCreated : ServerPacket + { + public InstanceSaveCreated() : base(ServerOpcodes.InstanceSaveCreated) { } + + public override void Write() + { + _worldPacket.WriteBit(Gm); + _worldPacket.FlushBits(); + } + + public bool Gm; + } + + class InstanceLockResponse : ClientPacket + { + public InstanceLockResponse(WorldPacket packet) : base(packet) { } + + public override void Read() + { + AcceptLock = _worldPacket.HasBit(); + } + + public bool AcceptLock; + } + + class RaidGroupOnly : ServerPacket + { + public RaidGroupOnly() : base(ServerOpcodes.RaidGroupOnly) { } + + public override void Write() + { + _worldPacket.WriteInt32(Delay); + _worldPacket.WriteUInt32(Reason); + } + + public int Delay; + public RaidGroupReason Reason; + } + + class PendingRaidLock : ServerPacket + { + public PendingRaidLock() : base(ServerOpcodes.PendingRaidLock) { } + + public override void Write() + { + _worldPacket.WriteInt32(TimeUntilLock); + _worldPacket.WriteUInt32(CompletedMask); + _worldPacket.WriteBit(Extending); + _worldPacket.WriteBit(WarningOnly); + _worldPacket.FlushBits(); + } + + public int TimeUntilLock; + public uint CompletedMask; + public bool Extending; + public bool WarningOnly; + } + + class RaidInstanceMessage : ServerPacket + { + public RaidInstanceMessage() : base(ServerOpcodes.RaidInstanceMessage) { } + + public override void Write() + { + _worldPacket.WriteUInt8(Type); + _worldPacket.WriteUInt32(MapID); + _worldPacket.WriteUInt32(DifficultyID); + _worldPacket.WriteBit(Locked); + _worldPacket.WriteBit(Extended); + _worldPacket.FlushBits(); + } + + public InstanceResetWarningType Type; + public uint MapID; + public Difficulty DifficultyID; + public bool Locked; + public bool Extended; + } + + class InstanceEncounterEngageUnit : ServerPacket + { + public InstanceEncounterEngageUnit() : base(ServerOpcodes.InstanceEncounterEngageUnit, ConnectionType.Instance) { } + + public override void Write() + { + _worldPacket.WritePackedGuid(Unit); + _worldPacket.WriteUInt8(TargetFramePriority); + } + + public ObjectGuid Unit; + public byte TargetFramePriority; // used to set the initial position of the frame if multiple frames are sent + } + + class InstanceEncounterDisengageUnit : ServerPacket + { + public InstanceEncounterDisengageUnit() : base(ServerOpcodes.InstanceEncounterDisengageUnit, ConnectionType.Instance) { } + + public override void Write() + { + _worldPacket.WritePackedGuid(Unit); + } + + public ObjectGuid Unit; + } + + class InstanceEncounterChangePriority : ServerPacket + { + public InstanceEncounterChangePriority() : base(ServerOpcodes.InstanceEncounterChangePriority, ConnectionType.Instance) { } + + public override void Write() + { + _worldPacket.WritePackedGuid(Unit); + _worldPacket.WriteUInt8(TargetFramePriority); + } + + public ObjectGuid Unit; + public byte TargetFramePriority; // used to update the position of the unit's current frame + } + + class InstanceEncounterStart : ServerPacket + { + public InstanceEncounterStart() : base(ServerOpcodes.InstanceEncounterStart, ConnectionType.Instance) { } + + public override void Write() + { + _worldPacket.WriteUInt32(InCombatResCount); + _worldPacket.WriteUInt32(MaxInCombatResCount); + _worldPacket.WriteUInt32(CombatResChargeRecovery); + _worldPacket.WriteUInt32(NextCombatResChargeTime); + } + + public uint InCombatResCount; // amount of usable battle ressurections + public uint MaxInCombatResCount; + public uint CombatResChargeRecovery; + public uint NextCombatResChargeTime; + } + + class InstanceEncounterEnd : ServerPacket + { + public InstanceEncounterEnd() : base(ServerOpcodes.InstanceEncounterEnd, ConnectionType.Instance) { } + + public override void Write() { } + } + + class InstanceEncounterInCombatResurrection : ServerPacket + { + public InstanceEncounterInCombatResurrection() : base(ServerOpcodes.InstanceEncounterInCombatResurrection, ConnectionType.Instance) { } + + public override void Write() { } + } + + class InstanceEncounterGainCombatResurrectionCharge : ServerPacket + { + public InstanceEncounterGainCombatResurrectionCharge() : base(ServerOpcodes.InstanceEncounterGainCombatResurrectionCharge, ConnectionType.Instance) { } + + public override void Write() + { + _worldPacket.WriteInt32(InCombatResCount); + _worldPacket.WriteUInt32(CombatResChargeRecovery); + } + + public int InCombatResCount; + public uint CombatResChargeRecovery; + } + + class BossKillCredit : ServerPacket + { + public BossKillCredit() : base(ServerOpcodes.BossKillCredit, ConnectionType.Instance) { } + + public override void Write() + { + _worldPacket.WriteUInt32(DungeonEncounterID); + } + + public uint DungeonEncounterID; + } + + //Structs + public struct InstanceLockInfos + { + public void Write(WorldPacket data) + { + data.WriteUInt32(MapID); + data.WriteUInt32(DifficultyID); + data.WriteUInt64(InstanceID); + data.WriteInt32(TimeRemaining); + data.WriteUInt32(CompletedMask); + + data.WriteBit(Locked); + data.WriteBit(Extended); + data.FlushBits(); + } + + public ulong InstanceID; + public uint MapID; + public uint DifficultyID; + public int TimeRemaining; + public uint CompletedMask; + + public bool Locked; + public bool Extended; + } +} diff --git a/Game/Network/Packets/ItemPackets.cs b/Game/Network/Packets/ItemPackets.cs new file mode 100644 index 000000000..07feaad66 --- /dev/null +++ b/Game/Network/Packets/ItemPackets.cs @@ -0,0 +1,999 @@ +/* + * Copyright (C) 2012-2017 CypherCore + * + * 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, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +using Framework.Constants; +using Framework.Dynamic; +using Game.Entities; +using System.Collections.Generic; +using System.Linq; + +namespace Game.Network.Packets +{ + public class BuyBackItem : ClientPacket + { + public BuyBackItem(WorldPacket packet) : base(packet) { } + + public override void Read() + { + VendorGUID = _worldPacket.ReadPackedGuid(); + Slot = _worldPacket.ReadUInt32(); + } + + public ObjectGuid VendorGUID; + public uint Slot; + } + + public class BuyItem : ClientPacket + { + public BuyItem(WorldPacket packet) : base(packet) + { + Item = new ItemInstance(); + } + + public override void Read() + { + VendorGUID = _worldPacket.ReadPackedGuid(); + ContainerGUID = _worldPacket.ReadPackedGuid(); + Quantity = _worldPacket.ReadInt32(); + Muid = _worldPacket.ReadUInt32(); + Slot = _worldPacket.ReadUInt32(); + Item.Read(_worldPacket); + ItemType = (ItemVendorType)_worldPacket.ReadBits(2); + } + + public ObjectGuid VendorGUID; + public ItemInstance Item; + public uint Muid; + public uint Slot; + public ItemVendorType ItemType; + public int Quantity; + public ObjectGuid ContainerGUID; + } + + public class BuySucceeded : ServerPacket + { + public BuySucceeded() : base(ServerOpcodes.BuySucceeded) { } + + public override void Write() + { + _worldPacket.WritePackedGuid(VendorGUID); + _worldPacket.WriteUInt32(Muid); + _worldPacket.WriteUInt32(NewQuantity); + _worldPacket.WriteUInt32(QuantityBought); + } + + public ObjectGuid VendorGUID; + public uint Muid; + public uint QuantityBought; + public uint NewQuantity; + } + + public class BuyFailed : ServerPacket + { + public BuyFailed() : base(ServerOpcodes.BuyFailed) { } + + public override void Write() + { + _worldPacket.WritePackedGuid(VendorGUID); + _worldPacket.WriteUInt32(Muid); + _worldPacket.WriteUInt8(Reason); + } + + public ObjectGuid VendorGUID; + public uint Muid; + public BuyResult Reason = BuyResult.CantFindItem; + } + + public class GetItemPurchaseData : ClientPacket + { + public GetItemPurchaseData(WorldPacket packet) : base(packet) { } + + public override void Read() + { + ItemGUID = _worldPacket.ReadPackedGuid(); + } + + public ObjectGuid ItemGUID; + } + + class SetItemPurchaseData : ServerPacket + { + public SetItemPurchaseData() : base(ServerOpcodes.SetItemPurchaseData, ConnectionType.Instance) { } + + public override void Write() + { + _worldPacket.WritePackedGuid(ItemGUID); + Contents.Write(_worldPacket); + _worldPacket.WriteUInt32(Flags); + _worldPacket.WriteUInt32(PurchaseTime); + } + + public uint PurchaseTime; + public uint Flags; + public ItemPurchaseContents Contents = new ItemPurchaseContents(); + public ObjectGuid ItemGUID; + } + + class ItemPurchaseRefund : ClientPacket + { + public ItemPurchaseRefund(WorldPacket packet) : base(packet) { } + + public override void Read() + { + ItemGUID = _worldPacket.ReadPackedGuid(); + } + + public ObjectGuid ItemGUID; + } + + class ItemPurchaseRefundResult : ServerPacket + { + public ItemPurchaseRefundResult() : base(ServerOpcodes.ItemPurchaseRefundResult, ConnectionType.Instance) { } + + public override void Write() + { + _worldPacket.WritePackedGuid(ItemGUID); + _worldPacket.WriteUInt8(Result); + _worldPacket.WriteBit(Contents.HasValue); + _worldPacket.FlushBits(); + if (Contents.HasValue) + Contents.Value.Write(_worldPacket); + } + + public byte Result; + public ObjectGuid ItemGUID; + public Optional Contents; + } + + class ItemExpirePurchaseRefund : ServerPacket + { + public ItemExpirePurchaseRefund() : base(ServerOpcodes.ItemExpirePurchaseRefund, ConnectionType.Instance) { } + + public override void Write() + { + _worldPacket.WritePackedGuid(ItemGUID); + } + + public ObjectGuid ItemGUID; + } + + public class RepairItem : ClientPacket + { + public RepairItem(WorldPacket packet) : base(packet) { } + + public override void Read() + { + NpcGUID = _worldPacket.ReadPackedGuid(); + ItemGUID = _worldPacket.ReadPackedGuid(); + UseGuildBank = _worldPacket.HasBit(); + } + + public ObjectGuid NpcGUID; + public ObjectGuid ItemGUID; + public bool UseGuildBank; + } + + public class SellItem : ClientPacket + { + public SellItem(WorldPacket packet) : base(packet) { } + + public override void Read() + { + VendorGUID = _worldPacket.ReadPackedGuid(); + ItemGUID = _worldPacket.ReadPackedGuid(); + Amount = _worldPacket.ReadUInt32(); + } + + public ObjectGuid VendorGUID; + public ObjectGuid ItemGUID; + public uint Amount; + } + + public class ItemTimeUpdate : ServerPacket + { + public ItemTimeUpdate() : base(ServerOpcodes.ItemTimeUpdate) { } + + public override void Write() + { + _worldPacket.WritePackedGuid(ItemGuid); + _worldPacket.WriteUInt32(DurationLeft); + } + + public ObjectGuid ItemGuid; + public uint DurationLeft; + } + + public class SetProficiency : ServerPacket + { + public SetProficiency() : base(ServerOpcodes.SetProficiency, ConnectionType.Instance) { } + + public override void Write() + { + _worldPacket.WriteUInt32(ProficiencyMask); + _worldPacket.WriteUInt8(ProficiencyClass); + } + + public uint ProficiencyMask; + public byte ProficiencyClass; + } + + public class InventoryChangeFailure : ServerPacket + { + public InventoryChangeFailure() : base(ServerOpcodes.InventoryChangeFailure) { } + + public override void Write() + { + _worldPacket.WriteInt8(BagResult); + _worldPacket.WritePackedGuid(Item[0]); + _worldPacket.WritePackedGuid(Item[1]); + _worldPacket.WriteUInt8(ContainerBSlot); // bag type subclass, used with EQUIP_ERR_EVENT_AUTOEQUIP_BIND_CONFIRM and EQUIP_ERR_WRONG_BAG_TYPE_2 + + switch (BagResult) + { + case InventoryResult.CantEquipLevelI: + case InventoryResult.PurchaseLevelTooLow: + _worldPacket.WriteUInt32(Level); + break; + case InventoryResult.EventAutoequipBindConfirm: + _worldPacket.WritePackedGuid(SrcContainer); + _worldPacket.WriteInt32(SrcSlot); + _worldPacket.WritePackedGuid(DstContainer); + break; + case InventoryResult.ItemMaxLimitCategoryCountExceededIs: + case InventoryResult.ItemMaxLimitCategorySocketedExceededIs: + case InventoryResult.ItemMaxLimitCategoryEquippedExceededIs: + _worldPacket.WriteInt32(LimitCategory); + break; + } + } + + public InventoryResult BagResult; + public byte ContainerBSlot; + public ObjectGuid SrcContainer; + public ObjectGuid DstContainer; + public int SrcSlot; + public int LimitCategory; + public int Level; + public ObjectGuid[] Item = new ObjectGuid[2]; + } + + public class SplitItem : ClientPacket + { + public SplitItem(WorldPacket packet) : base(packet) { } + + public override void Read() + { + Inv = new InvUpdate(_worldPacket); + FromPackSlot = _worldPacket.ReadUInt8(); + FromSlot = _worldPacket.ReadUInt8(); + ToPackSlot = _worldPacket.ReadUInt8(); + ToSlot = _worldPacket.ReadUInt8(); + Quantity = _worldPacket.ReadInt32(); + } + + public byte ToSlot; + public byte ToPackSlot; + public byte FromPackSlot; + public int Quantity; + public InvUpdate Inv; + public byte FromSlot; + } + + public class SwapInvItem : ClientPacket + { + public SwapInvItem(WorldPacket packet) : base(packet) { } + + public override void Read() + { + Inv = new InvUpdate(_worldPacket); + Slot2 = _worldPacket.ReadUInt8(); + Slot1 = _worldPacket.ReadUInt8(); + } + + public InvUpdate Inv; + public byte Slot1; /// Source Slot + public byte Slot2; /// Destination Slot + } + + public class SwapItem : ClientPacket + { + public SwapItem(WorldPacket packet) : base(packet) { } + + public override void Read() + { + Inv = new InvUpdate(_worldPacket); + ContainerSlotB = _worldPacket.ReadUInt8(); + ContainerSlotA = _worldPacket.ReadUInt8(); + SlotB = _worldPacket.ReadUInt8(); + SlotA = _worldPacket.ReadUInt8(); + } + + public InvUpdate Inv; + public byte SlotA; + public byte ContainerSlotB; + public byte SlotB; + public byte ContainerSlotA; + } + + public class AutoEquipItem : ClientPacket + { + public AutoEquipItem(WorldPacket packet) : base(packet) { } + + public override void Read() + { + Inv = new InvUpdate(_worldPacket); + PackSlot = _worldPacket.ReadUInt8(); + Slot = _worldPacket.ReadUInt8(); + } + + public byte Slot; + public InvUpdate Inv; + public byte PackSlot; + } + + class AutoEquipItemSlot : ClientPacket + { + public AutoEquipItemSlot(WorldPacket packet) : base(packet) { } + + public override void Read() + { + Inv = new InvUpdate(_worldPacket); + Item = _worldPacket.ReadPackedGuid(); + ItemDstSlot = _worldPacket.ReadUInt8(); + } + + public ObjectGuid Item; + public byte ItemDstSlot; + public InvUpdate Inv; + } + + public class AutoStoreBagItem : ClientPacket + { + public AutoStoreBagItem(WorldPacket packet) : base(packet) { } + + public override void Read() + { + Inv = new InvUpdate(_worldPacket); + ContainerSlotB = _worldPacket.ReadUInt8(); + ContainerSlotA = _worldPacket.ReadUInt8(); + SlotA = _worldPacket.ReadUInt8(); + } + + public byte ContainerSlotB; + public InvUpdate Inv; + public byte ContainerSlotA; + public byte SlotA; + } + + public class DestroyItem : ClientPacket + { + public DestroyItem(WorldPacket packet) : base(packet) { } + + public override void Read() + { + Count = _worldPacket.ReadUInt32(); + ContainerId = _worldPacket.ReadUInt8(); + SlotNum = _worldPacket.ReadUInt8(); + } + + public uint Count; + public byte SlotNum; + public byte ContainerId; + } + + public class SellResponse : ServerPacket + { + public SellResponse() : base(ServerOpcodes.SellResponse) { } + + public override void Write() + { + _worldPacket.WritePackedGuid(VendorGUID); + _worldPacket.WritePackedGuid(ItemGUID); + _worldPacket.WriteUInt8(Reason); + } + + public ObjectGuid VendorGUID; + public ObjectGuid ItemGUID; + public SellResult Reason = SellResult.Unk; + } + + class ItemPushResult : ServerPacket + { + public ItemPushResult() : base(ServerOpcodes.ItemPushResult) { } + + public override void Write() + { + _worldPacket.WritePackedGuid(PlayerGUID); + _worldPacket.WriteUInt8(Slot); + _worldPacket.WriteInt32(SlotInBag); + _worldPacket.WriteInt32(QuestLogItemID); + _worldPacket.WriteUInt32(Quantity); + _worldPacket.WriteUInt32(QuantityInInventory); + _worldPacket.WriteUInt32(DungeonEncounterID); + _worldPacket.WriteInt32(BattlePetBreedID); + _worldPacket.WriteUInt32(BattlePetBreedQuality); + _worldPacket.WriteInt32(BattlePetSpeciesID); + _worldPacket.WriteInt32(BattlePetLevel); + _worldPacket.WritePackedGuid(ItemGUID); + _worldPacket.WriteBit(Pushed); + _worldPacket.WriteBit(Created); + _worldPacket.WriteBits((uint)DisplayText, 3); + _worldPacket.WriteBit(IsBonusRoll); + _worldPacket.WriteBit(IsEncounterLoot); + _worldPacket.FlushBits(); + + Item.Write(_worldPacket); + } + + public ObjectGuid PlayerGUID; + public byte Slot; + public int SlotInBag; + public ItemInstance Item; + public int QuestLogItemID;// Item ID used for updating quest progress + // only set if different than real ID (similar to CreatureTemplate.KillCredit) + public uint Quantity; + public uint QuantityInInventory; + public int DungeonEncounterID; + public int BattlePetBreedID; + public uint BattlePetBreedQuality; + public int BattlePetSpeciesID; + public int BattlePetLevel; + public ObjectGuid ItemGUID; + public bool Pushed; + public DisplayType DisplayText; + public bool Created; + public bool IsBonusRoll; + public bool IsEncounterLoot; + + + public enum DisplayType + { + Hidden = 0, + Normal = 1, + EncounterLoot = 2 + } + } + + class ReadItem : ClientPacket + { + public ReadItem(WorldPacket packet) : base(packet) { } + + public override void Read() + { + PackSlot = _worldPacket.ReadUInt8(); + Slot = _worldPacket.ReadUInt8(); + } + + public byte PackSlot; + public byte Slot; + } + + class ReadItemResultFailed : ServerPacket + { + public ReadItemResultFailed() : base(ServerOpcodes.ReadItemResultFailed) { } + + public override void Write() + { + _worldPacket.WritePackedGuid(Item); + _worldPacket.WriteUInt32(Delay); + _worldPacket.WriteBits(Subcode, 3); + _worldPacket.FlushBits(); + } + + public ObjectGuid Item; + public byte Subcode; + public uint Delay; + } + + class ReadItemResultOK : ServerPacket + { + public ReadItemResultOK() : base(ServerOpcodes.ReadItemResultOk) { } + + public override void Write() + { + _worldPacket.WritePackedGuid(Item); + } + + public ObjectGuid Item; + } + + class WrapItem : ClientPacket + { + public WrapItem(WorldPacket packet) : base(packet) { } + + public override void Read() + { + Inv = new InvUpdate(_worldPacket); + } + + public InvUpdate Inv; + } + + class EnchantmentLog : ServerPacket + { + public EnchantmentLog() : base(ServerOpcodes.EnchantmentLog) { } + + public override void Write() + { + _worldPacket.WritePackedGuid(Caster); + _worldPacket.WritePackedGuid(Owner); + _worldPacket.WritePackedGuid(ItemGUID); + _worldPacket.WriteUInt32(ItemID); + _worldPacket.WriteUInt32(Enchantment); + _worldPacket.WriteUInt32(EnchantSlot); + } + + public ObjectGuid Caster; + public ObjectGuid Owner; + public ObjectGuid ItemGUID; + public uint ItemID; + public uint EnchantSlot; + public uint Enchantment; + } + + class CancelTempEnchantment : ClientPacket + { + public CancelTempEnchantment(WorldPacket packet) : base(packet) { } + + public override void Read() + { + Slot = _worldPacket.ReadInt32(); + } + + public int Slot; + } + + class ItemCooldown : ServerPacket + { + public ItemCooldown() : base(ServerOpcodes.ItemCooldown) { } + + public override void Write() + { + _worldPacket.WritePackedGuid(ItemGuid); + _worldPacket.WriteUInt32(SpellID); + _worldPacket.WriteUInt32(Cooldown); + } + + public ObjectGuid ItemGuid; + public uint SpellID; + public uint Cooldown; + } + + class ItemEnchantTimeUpdate : ServerPacket + { + public ItemEnchantTimeUpdate() : base(ServerOpcodes.ItemEnchantTimeUpdate, ConnectionType.Instance) { } + + public override void Write() + { + _worldPacket.WritePackedGuid(ItemGuid); + _worldPacket.WriteUInt32(DurationLeft); + _worldPacket.WriteUInt32(Slot); + _worldPacket.WritePackedGuid(OwnerGuid); + } + + public ObjectGuid OwnerGuid; + public ObjectGuid ItemGuid; + public uint DurationLeft; + public uint Slot; + } + + class UseCritterItem : ClientPacket + { + public UseCritterItem(WorldPacket packet) : base(packet) { } + + public override void Read() + { + ItemGuid = _worldPacket.ReadPackedGuid(); + } + + public ObjectGuid ItemGuid; + } + + class UpgradeItem : ClientPacket + { + public UpgradeItem(WorldPacket packet) : base(packet) { } + + public override void Read() + { + ItemMaster = _worldPacket.ReadPackedGuid(); + ItemGUID = _worldPacket.ReadPackedGuid(); + UpgradeID = _worldPacket.ReadInt32(); + ContainerSlot = _worldPacket.ReadInt32(); + Slot = _worldPacket.ReadInt32(); + } + + public ObjectGuid ItemMaster; + public ObjectGuid ItemGUID; + public int ContainerSlot; + public int UpgradeID; + public int Slot; + } + + class ItemUpgradeResult : ServerPacket + { + public ItemUpgradeResult() : base(ServerOpcodes.ItemUpgradeResult) { } + + public override void Write() + { + _worldPacket.WriteBit(Success); + _worldPacket.FlushBits(); + } + + public bool Success; + } + + class SocketGems : ClientPacket + { + public SocketGems(WorldPacket packet) : base(packet) { } + + public override void Read() + { + ItemGuid = _worldPacket.ReadPackedGuid(); + for (int i = 0; i < ItemConst.MaxGemSockets; ++i) + GemItem[i] = _worldPacket.ReadPackedGuid(); + } + + public ObjectGuid ItemGuid; + public ObjectGuid[] GemItem = new ObjectGuid[ItemConst.MaxGemSockets]; + } + + class SocketGemsResult : ServerPacket + { + public SocketGemsResult() : base(ServerOpcodes.SocketGems, ConnectionType.Instance) { } + + public override void Write() + { + _worldPacket.WritePackedGuid(Item); + } + + public ObjectGuid Item; + } + + //Structs + public class ItemBonusInstanceData + { + public void Write(WorldPacket data) + { + data.WriteUInt8(Context); + data.WriteUInt32(BonusListIDs.Count); + foreach (uint bonusID in BonusListIDs) + data.WriteUInt32(bonusID); + } + + public void Read(WorldPacket data) + { + Context = data.ReadUInt8(); + uint bonusListIdSize = data.ReadUInt32(); ; + + BonusListIDs = new List(); + for (uint i = 0u; i < bonusListIdSize; ++i) + { + uint bonusId = data.ReadUInt32(); + BonusListIDs.Add(bonusId); + } + } + + public override int GetHashCode() + { + return Context.GetHashCode() ^ BonusListIDs.GetHashCode(); + } + + public override bool Equals(object obj) + { + if (obj is ItemBonusInstanceData) + return (ItemBonusInstanceData)obj == this; + + return false; + } + + public static bool operator ==(ItemBonusInstanceData left, ItemBonusInstanceData right) + { + if (left.Context != right.Context) + return false; + + if (left.BonusListIDs.Count != right.BonusListIDs.Count) + return false; + + return left.BonusListIDs.SequenceEqual(right.BonusListIDs); + } + + public static bool operator !=(ItemBonusInstanceData left, ItemBonusInstanceData right) + { + return !(left == right); + } + + public byte Context; + public List BonusListIDs = new List(); + } + + public class ItemInstance + { + public ItemInstance() { } + + public ItemInstance(Item item) + { + ItemID = item.GetEntry(); + RandomPropertiesSeed = item.GetItemSuffixFactor(); + RandomPropertiesID = (uint)item.GetItemRandomPropertyId(); + var bonusListIds = item.GetDynamicValues(ItemDynamicFields.BonusListIds); + if (bonusListIds.Count != 0) + { + ItemBonus.HasValue = true; + ItemBonus.Value.BonusListIDs.AddRange(bonusListIds); + ItemBonus.Value.Context = (byte)item.GetUInt32Value(ItemFields.Context); + } + + uint mask = item.GetUInt32Value(ItemFields.ModifiersMask); + if (mask != 0) + { + Modifications.HasValue = true; + + for (int i = 0; mask != 0; mask >>= 1, ++i) + { + if ((mask & 1) != 0) + Modifications.Value.Insert(i, (int)item.GetModifier((ItemModifier)i)); + } + } + } + + public ItemInstance(Loots.LootItem lootItem) + { + ItemID = lootItem.itemid; + RandomPropertiesSeed = lootItem.randomSuffix; + if (lootItem.randomPropertyId.Type != ItemRandomEnchantmentType.BonusList) + RandomPropertiesID = lootItem.randomPropertyId.Id; + + if (!lootItem.BonusListIDs.Empty()) + { + ItemBonus.HasValue = true; + ItemBonus.Value.BonusListIDs = lootItem.BonusListIDs; + ItemBonus.Value.Context = lootItem.context; + } + + if (lootItem.upgradeId != 0) + { + Modifications.HasValue = true; + Modifications.Value.Insert((int)ItemModifier.UpgradeId, (int)lootItem.upgradeId); + } + } + + public ItemInstance(VoidStorageItem voidItem) + { + ItemID = voidItem.ItemEntry; + RandomPropertiesSeed = voidItem.ItemSuffixFactor; + if (voidItem.ItemRandomPropertyId.Type != ItemRandomEnchantmentType.BonusList) + RandomPropertiesID = voidItem.ItemRandomPropertyId.Id; + + if (voidItem.ItemUpgradeId != 0 || voidItem.FixedScalingLevel != 0 || voidItem.ArtifactKnowledgeLevel != 0) + { + Modifications.HasValue = true; + if (voidItem.ItemUpgradeId != 0) + Modifications.Value.Insert((int)ItemModifier.UpgradeId, (int)voidItem.ItemUpgradeId); + if (voidItem.FixedScalingLevel != 0) + Modifications.Value.Insert((int)ItemModifier.ScalingStatDistributionFixedLevel, (int)voidItem.FixedScalingLevel); + if (voidItem.ArtifactKnowledgeLevel != 0) + Modifications.Value.Insert((int)ItemModifier.ArtifactKnowledgeLevel, (int)voidItem.ArtifactKnowledgeLevel); + } + + if (!voidItem.BonusListIDs.Empty()) + { + ItemBonus.HasValue = true; + ItemBonus.Value.Context = voidItem.Context; + ItemBonus.Value.BonusListIDs = voidItem.BonusListIDs; + } + } + + public ItemInstance(ItemDynamicFieldGems gem) + { + ItemID = gem.ItemId; + + ItemBonusInstanceData bonus = new ItemBonusInstanceData(); + bonus.Context = gem.Context; + foreach (ushort bonusListId in gem.BonusListIDs) + if (bonusListId != 0) + bonus.BonusListIDs.Add(bonusListId); + + if (bonus.Context != 0 || !bonus.BonusListIDs.Empty()) + ItemBonus.Set(bonus); + } + + public void Write(WorldPacket data) + { + data.WriteUInt32(ItemID); + data.WriteUInt32(RandomPropertiesSeed); + data.WriteUInt32(RandomPropertiesID); + + data.WriteBit(ItemBonus.HasValue); + data.WriteBit(Modifications.HasValue); + data.FlushBits(); + + if (ItemBonus.HasValue) + ItemBonus.Value.Write(data); + + if (Modifications.HasValue) + Modifications.Value.Write(data); + } + + public void Read(WorldPacket data) + { + ItemID = data.ReadUInt32(); + RandomPropertiesSeed = data.ReadUInt32(); + RandomPropertiesID = data.ReadUInt32(); + + ItemBonus.HasValue = data.HasBit(); + Modifications.HasValue = data.HasBit(); + data.ResetBitPos(); + + if (ItemBonus.HasValue) + ItemBonus.Value.Read(data); + + if (Modifications.HasValue) + Modifications.Value.Read(data); + } + + public override int GetHashCode() + { + return ItemID.GetHashCode() ^ RandomPropertiesSeed.GetHashCode() ^ + RandomPropertiesID.GetHashCode() ^ ItemBonus.GetHashCode() ^ Modifications.GetHashCode(); + } + + public override bool Equals(object obj) + { + if (obj is ItemInstance) + return (ItemInstance)obj == this; + + return false; + } + + public static bool operator ==(ItemInstance left, ItemInstance right) + { + if (left.ItemID != right.ItemID || left.RandomPropertiesID != right.RandomPropertiesID || left.RandomPropertiesSeed != right.RandomPropertiesSeed) + return false; + + if (left.ItemBonus.HasValue != right.ItemBonus.HasValue || left.Modifications.HasValue != right.Modifications.HasValue) + return false; + + if (left.Modifications.HasValue && left.Modifications.Value != right.Modifications.Value) + return false; + + if (left.ItemBonus.HasValue && left.ItemBonus.Value != right.ItemBonus.Value) + return false; + + return true; + } + + public static bool operator !=(ItemInstance left, ItemInstance right) + { + return !(left == right); + } + + public uint ItemID; + public uint RandomPropertiesSeed; + public uint RandomPropertiesID; + public Optional ItemBonus; + public Optional Modifications; + } + + public class ItemEnchantData + { + public ItemEnchantData(int id, uint expiration, int charges, byte slot) + { + ID = id; + Expiration = expiration; + Charges = charges; + Slot = slot; + } + + public void Write(WorldPacket data) + { + data.WriteInt32(ID); + data.WriteUInt32(Expiration); + data.WriteInt32(Charges); + data.WriteUInt8(Slot); + } + + public int ID; + public uint Expiration; + public int Charges; + public byte Slot; + } + + public class ItemGemData + { + public void Write(WorldPacket data) + { + data.WriteUInt8(Slot); + Item.Write(data); + } + + public void Read(WorldPacket data) + { + Slot = data.ReadUInt8(); + Item.Read(data); + } + + public byte Slot; + public ItemInstance Item = new ItemInstance(); + } + + public struct InvUpdate + { + public InvUpdate(WorldPacket data) + { + Items = new List(); + int size = data.ReadBits(2); + for (int i = 0; i < size; ++i) + { + var item = new InvItem + { + ContainerSlot = data.ReadUInt8(), + Slot = data.ReadUInt8() + }; + Items.Add(item); + } + } + + public List Items; + + public struct InvItem + { + public byte ContainerSlot; + public byte Slot; + } + } + + struct ItemPurchaseRefundItem + { + public void Write(WorldPacket data) + { + data.WriteUInt32(ItemID); + data.WriteUInt32(ItemCount); + } + + public uint ItemID; + public uint ItemCount; + } + + struct ItemPurchaseRefundCurrency + { + public void Write(WorldPacket data) + { + data.WriteUInt32(CurrencyID); + data.WriteUInt32(CurrencyCount); + } + + public uint CurrencyID; + public uint CurrencyCount; + } + + class ItemPurchaseContents + { + public void Write(WorldPacket data) + { + data.WriteUInt64(Money); + for (int i = 0; i < 5; ++i) + Items[i].Write(data); + + for (int i = 0; i < 5; ++i) + Currencies[i].Write(data); + } + + public ulong Money; + public ItemPurchaseRefundItem[] Items = new ItemPurchaseRefundItem[5]; + public ItemPurchaseRefundCurrency[] Currencies = new ItemPurchaseRefundCurrency[5]; + } +} diff --git a/Game/Network/Packets/LFGPackets.cs b/Game/Network/Packets/LFGPackets.cs new file mode 100644 index 000000000..c986203ef --- /dev/null +++ b/Game/Network/Packets/LFGPackets.cs @@ -0,0 +1,724 @@ +/* + * Copyright (C) 2012-2017 CypherCore + * + * 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, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +using Framework.Constants; +using Framework.Dynamic; +using Game.Entities; +using System.Collections.Generic; + +namespace Game.Network.Packets +{ + class LfgDisabled : ServerPacket + { + public LfgDisabled() : base(ServerOpcodes.LfgDisabled, ConnectionType.Instance) { } + + public override void Write() { } + } + + class LfgOfferContinue : ServerPacket + { + public LfgOfferContinue() : base(ServerOpcodes.LfgOfferContinue, ConnectionType.Instance) { } + + public override void Write() + { + _worldPacket.WriteUInt32(Slot); + } + + public uint Slot; + } + + class LfgTeleportDenied : ServerPacket + { + public LfgTeleportDenied() : base(ServerOpcodes.LfgTeleportDenied, ConnectionType.Instance) { } + + public override void Write() + { + _worldPacket.WriteUInt32(Reason); + } + + public LfgTeleportResult Reason; + } + + class LfgBootPlayer : ServerPacket + { + public LfgBootPlayer() : base(ServerOpcodes.LfgBootPlayer, ConnectionType.Instance) { } + + public override void Write() + { + _worldPacket.WriteBit(Info.VoteInProgress); + _worldPacket.WriteBit(Info.VotePassed); + _worldPacket.WriteBit(Info.MyVoteCompleted); + _worldPacket.WriteBit(Info.MyVote); + _worldPacket.WriteBits(Info.Reason.Length, 8); + _worldPacket.FlushBits(); + + _worldPacket.WritePackedGuid(Info.Target); + _worldPacket.WriteUInt32(Info.TotalVotes); + _worldPacket.WriteUInt32(Info.BootVotes); + _worldPacket.WriteUInt32(Info.TimeLeft); + _worldPacket.WriteUInt32(Info.VotesNeeded); + _worldPacket.WriteString(Info.Reason); + } + + public LfgBootInfo Info = new LfgBootInfo(); + } + + class RoleChosen : ServerPacket + { + public RoleChosen() : base(ServerOpcodes.RoleChosen) { } + + public override void Write() + { + _worldPacket.WritePackedGuid(Player); + _worldPacket.WriteUInt32(RoleMask); + _worldPacket.WriteBit(Accepted); + _worldPacket.FlushBits(); + } + + public bool Accepted; + public LfgRoles RoleMask; + public ObjectGuid Player; + } + + class DFGetSystemInfo : ClientPacket + { + public DFGetSystemInfo(WorldPacket packet) : base(packet) { } + + public override void Read() + { + Player = _worldPacket.HasBit(); + PartyIndex = _worldPacket.ReadUInt8(); + } + + public bool Player; + public byte PartyIndex; + } + + class DFGetJoinStatus : ClientPacket + { + public DFGetJoinStatus(WorldPacket packet) : base(packet) { } + + public override void Read() { } + } + + class DFJoin : ClientPacket + { + public DFJoin(WorldPacket packet) : base(packet) { } + + public override void Read() + { + QueueAsGroup = _worldPacket.HasBit(); + var commentLength = _worldPacket.ReadBits(8); + + PartyIndex = _worldPacket.ReadUInt8(); + Roles = (LfgRoles)_worldPacket.ReadUInt32(); + var slotsCount = _worldPacket.ReadInt32(); + + for (var i = 0; i < 3; ++i) // Needs + Needs[i] = _worldPacket.ReadUInt32(); + + Comment = _worldPacket.ReadString(commentLength); + + for (var i = 0; i < slotsCount; ++i) // Slots + Slots.Add(_worldPacket.ReadUInt32()); + } + + public bool QueueAsGroup; + public LfgRoles Roles; + public byte PartyIndex; + public string Comment = ""; + public List Slots = new List(); + public uint[] Needs = new uint[3]; + } + + class DFLeave : ClientPacket + { + public DFLeave(WorldPacket packet) : base(packet) { } + + public override void Read() + { + Ticket.Read(_worldPacket); + } + + public RideTicket Ticket; + } + + class LFGJoinResult : ServerPacket + { + public LFGJoinResult() : base(ServerOpcodes.LfgJoinResult) { } + + public override void Write() + { + Ticket.Write(_worldPacket); + + _worldPacket.WriteUInt8(Result); + _worldPacket.WriteUInt8(ResultDetail); + + _worldPacket.WriteUInt32(BlackList.Count); + foreach (var blackList in BlackList) + { + _worldPacket.WritePackedGuid(blackList.PlayerGuid.Value); + + _worldPacket.WriteUInt32(blackList.BlackListSlots.Count); + foreach (var blackListSlot in blackList.BlackListSlots) + { + _worldPacket.WriteUInt32(blackListSlot.Slot); + _worldPacket.WriteUInt32(blackListSlot.Reason); + _worldPacket.WriteInt32(blackListSlot.SubReason1); + _worldPacket.WriteInt32(blackListSlot.SubReason2); + } + } + } + + public byte Result; + public List BlackList = new List(); + public byte ResultDetail; + public RideTicket Ticket; + } + + class LFGUpdateStatus : ServerPacket + { + public LFGUpdateStatus() : base(ServerOpcodes.LfgUpdateStatus) { } + + public override void Write() + { + Ticket.Write(_worldPacket); + + _worldPacket.WriteUInt8(SubType); + _worldPacket.WriteUInt8(Reason); + + for (int i = 0; i < 3; i++) + _worldPacket.WriteUInt8(Needs[i]); + + _worldPacket.WriteUInt32(Slots.Count); + _worldPacket.WriteUInt32(RequestedRoles); + _worldPacket.WriteUInt32(SuspendedPlayers.Count); + + foreach (var slot in Slots) + _worldPacket.WriteUInt32(slot); + + foreach (var player in SuspendedPlayers) + _worldPacket.WritePackedGuid(player); + + _worldPacket.WriteBit(IsParty); + _worldPacket.WriteBit(NotifyUI); + _worldPacket.WriteBit(Joined); + _worldPacket.WriteBit(LfgJoined); + _worldPacket.WriteBit(Queued); + + _worldPacket.WriteBits(Comment.Length, 8); + _worldPacket.WriteString(Comment); + } + + public RideTicket Ticket; + public byte SubType; + public byte Reason; + public byte[] Needs = new byte[3]; + public uint RequestedRoles; + + public List Slots = new List(); + public List SuspendedPlayers = new List(); + + public bool IsParty; + public bool NotifyUI; + public bool Joined; + public bool LfgJoined; + public bool Queued; + public string Comment = ""; + } + + class DFProposalResponse : ClientPacket + { + public DFProposalResponse(WorldPacket packet) : base(packet) { } + + public override void Read() + { + Ticket.Read(_worldPacket); + InstanceID = _worldPacket.ReadUInt64(); + ProposalID = _worldPacket.ReadUInt32(); + Accepted = _worldPacket.HasBit(); + } + + public RideTicket Ticket; + public ulong InstanceID; + public uint ProposalID; + public bool Accepted; + } + + class LFGProposalUpdate : ServerPacket + { + public LFGProposalUpdate() : base(ServerOpcodes.LfgProposalUpdate) { } + + public override void Write() + { + Ticket.Write(_worldPacket); + + _worldPacket.WriteUInt64(InstanceID); + _worldPacket.WriteUInt32(ProposalID); + _worldPacket.WriteUInt32(Slot); + _worldPacket.WriteUInt8(State); + _worldPacket.WriteUInt32(CompletedMask); + + _worldPacket.WriteUInt32(Players.Count); + foreach (var player in Players) + { + _worldPacket.WriteUInt32(player.Roles); + + _worldPacket.WriteBit(player.Me); + _worldPacket.WriteBit(player.SameParty); + _worldPacket.WriteBit(player.MyParty); + _worldPacket.WriteBit(player.Responded); + _worldPacket.WriteBit(player.Accepted); + _worldPacket.FlushBits(); + } + + _worldPacket.WriteBit(ValidCompletedMask); + _worldPacket.WriteBit(ProposalSilent); + _worldPacket.FlushBits(); + } + + public RideTicket Ticket; + public ulong InstanceID; + public uint ProposalID; + public uint Slot; + public byte State; + public uint CompletedMask; + public List Players = new List(); + public bool ValidCompletedMask; + public bool ProposalSilent; + + public struct LFGProposalUpdatePlayer + { + public uint Roles; + public bool Me; + public bool SameParty; + public bool MyParty; + public bool Responded; + public bool Accepted; + } + } + + class LFGQueueStatus : ServerPacket + { + public LFGQueueStatus() : base(ServerOpcodes.LfgQueueStatus) { } + + public override void Write() + { + Ticket.Write(_worldPacket); + + _worldPacket.WriteUInt32(Slot); + _worldPacket.WriteUInt32(AvgWaitTime); + _worldPacket.WriteUInt32(QueuedTime); + + for (int i = 0; i < 3; i++) + { + _worldPacket.WriteUInt32(AvgWaitTimeByRole[i]); + _worldPacket.WriteUInt8(LastNeeded[i]); + } + + _worldPacket.WriteUInt32(AvgWaitTimeMe); + } + + public RideTicket Ticket; + public uint Slot; + public uint AvgWaitTime; + public uint QueuedTime; + public byte[] LastNeeded = new byte[3]; + public uint[] AvgWaitTimeByRole = new uint[3]; + public uint AvgWaitTimeMe; + } + + class LfgPlayerInfo : ServerPacket + { + public LfgPlayerInfo() : base(ServerOpcodes.LfgPlayerInfo) { } + + public override void Write() + { + _worldPacket.WriteUInt32(Dungeons.Count); + + _worldPacket.WriteBit(BlackList.PlayerGuid.HasValue); + _worldPacket.WriteUInt32(BlackList.BlackListSlots.Count); + + if (BlackList.PlayerGuid.HasValue) + _worldPacket.WritePackedGuid(BlackList.PlayerGuid.Value); + + foreach (var blackListSlot in BlackList.BlackListSlots) + { + _worldPacket.WriteUInt32(blackListSlot.Slot); + _worldPacket.WriteUInt32(blackListSlot.Reason); + _worldPacket.WriteInt32(blackListSlot.SubReason1); + _worldPacket.WriteInt32(blackListSlot.SubReason2); + } + + foreach (var dungeonInfo in Dungeons) + { + dungeonInfo.Write(_worldPacket); + } + } + + public LFGBlackList BlackList; + public List Dungeons = new List(); + } + + class LfgPartyInfo : ServerPacket + { + public LfgPartyInfo() : base(ServerOpcodes.LfgPartyInfo) { } + + public override void Write() + { + _worldPacket.WriteUInt32(Players.Count); + foreach (var blackList in Players) + { + _worldPacket.WriteBit(blackList.PlayerGuid.HasValue); + _worldPacket.WriteUInt32(blackList.BlackListSlots.Count); + + if (blackList.PlayerGuid.HasValue) + _worldPacket.WritePackedGuid(blackList.PlayerGuid.Value); + + foreach (var blackListSlot in blackList.BlackListSlots) + { + _worldPacket.WriteUInt32(blackListSlot.Slot); + _worldPacket.WriteUInt32(blackListSlot.Reason); + _worldPacket.WriteInt32(blackListSlot.SubReason1); + _worldPacket.WriteInt32(blackListSlot.SubReason2); + } + } + } + + public List Players = new List(); + } + + class LFGPlayerReward : ServerPacket + { + public LFGPlayerReward() : base(ServerOpcodes.LfgPlayerReward) { } + + public override void Write() + { + _worldPacket.WriteUInt32(ActualSlot); // unconfirmed order + _worldPacket.WriteUInt32(QueuedSlot); // unconfirmed order + _worldPacket.WriteUInt32(RewardMoney); + _worldPacket.WriteUInt32(AddedXP); + + _worldPacket.WriteUInt32(Rewards.Count); + foreach (var reward in Rewards) + { + _worldPacket.WriteUInt32(reward.RewardItem); + _worldPacket.WriteUInt32(reward.RewardItemQuantity); + _worldPacket.WriteUInt32(reward.BonusCurrency); + _worldPacket.WriteBit(reward.IsCurrency); + _worldPacket.FlushBits(); + } + } + + public uint ActualSlot; + public uint QueuedSlot; + public uint RewardMoney; + public uint AddedXP; + public List Rewards = new List(); + + public struct LFGPlayerRewards + { + public uint RewardItem; + public uint RewardItemQuantity; + public int BonusCurrency; + public bool IsCurrency; + } + } + + class LFGRoleCheckUpdate : ServerPacket + { + public LFGRoleCheckUpdate() : base(ServerOpcodes.LfgRoleCheckUpdate) { } + + public override void Write() + { + _worldPacket.WriteUInt8(PartyIndex); + _worldPacket.WriteUInt8(RoleCheckStatus); + _worldPacket.WriteUInt32(JoinSlots.Count); + _worldPacket.WriteUInt64(BgQueueID); + _worldPacket.WriteUInt32(0);// ActivityID);//Not in jam + _worldPacket.WriteUInt32(Members.Count); + + foreach (var slot in JoinSlots) + _worldPacket.WriteUInt32(slot); + + foreach (var member in Members) + { + _worldPacket.WritePackedGuid(member.Guid); + _worldPacket.WriteUInt32(member.RolesDesired); + _worldPacket.WriteUInt8(member.Level); + _worldPacket.WriteBit(member.RoleCheckComplete); + } + + _worldPacket.WriteBit(IsBeginning); + _worldPacket.WriteBit(true);// ShowRoleCheck);//Not in jam + } + + public byte PartyIndex; + public byte RoleCheckStatus; + public ulong BgQueueID; + public List JoinSlots = new List(); + public List Members = new List(); + public bool IsBeginning; + + public class LFGRoleCheckUpdateMember + { + public ObjectGuid Guid; + public bool RoleCheckComplete; + public uint RolesDesired; + public byte Level; + } + } + + class DFSetComment : ClientPacket + { + public DFSetComment(WorldPacket packet) : base(packet) { } + + public override void Read() + { + Ticket.Read(_worldPacket); + Comment = _worldPacket.ReadString(_worldPacket.ReadBits(9)); + } + + public RideTicket Ticket; + public string Comment; + } + + class DFSetRoles : ClientPacket + { + public DFSetRoles(WorldPacket packet) : base(packet) { } + + public override void Read() + { + RolesDesired = (LfgRoles)_worldPacket.ReadUInt32(); + PartyIndex = _worldPacket.ReadUInt8(); + } + + public LfgRoles RolesDesired; + public byte PartyIndex; + } + + class DFBootPlayerVote : ClientPacket + { + public DFBootPlayerVote(WorldPacket packet) : base(packet) { } + + public override void Read() + { + Vote = _worldPacket.HasBit(); + } + + public bool Vote; + } + + class DFTeleport : ClientPacket + { + public DFTeleport(WorldPacket packet) : base(packet) { } + + public override void Read() + { + TeleportOut = _worldPacket.HasBit(); + } + + public bool TeleportOut; + } + + //Structs + public class LfgBootInfo + { + public bool VoteInProgress; + public bool VotePassed; + public bool MyVoteCompleted; + public bool MyVote; + public ObjectGuid Target; + public uint TotalVotes; + public uint BootVotes; + public uint TimeLeft; + public uint VotesNeeded; + public string Reason; + } + + public struct RideTicket + { + public RideTicket(ObjectGuid requesterGuid, uint id, int type, uint time) + { + RequesterGuid = requesterGuid; + Id = id; + Type = type; + Time = time; + } + + public void Read(WorldPacket data) + { + RequesterGuid = data.ReadPackedGuid(); + Id = data.ReadUInt32(); + Type = data.ReadInt32(); + Time = data.ReadUInt32(); + } + + public void Write(WorldPacket data) + { + data.WritePackedGuid(RequesterGuid); + data.WriteUInt32(Id); + data.WriteInt32(Type); + data.WriteUInt32(Time); + } + + public ObjectGuid RequesterGuid; + public uint Id; + public int Type; + public uint Time; + } + + public class LFGBlackList + { + public LFGBlackList(Dictionary lockInfoData) + { + foreach (var lockInfo in lockInfoData) + { + var blackListSlot = new LFGBlackList.LFGBlackListSlot(); + blackListSlot.Slot = lockInfo.Key; // Dungeon entry (id + type) + blackListSlot.Reason = (uint)lockInfo.Value.lockStatus; + blackListSlot.SubReason1 = lockInfo.Value.requiredItemLevel; + blackListSlot.SubReason2 = (int)lockInfo.Value.currentItemLevel; + + BlackListSlots.Add(blackListSlot); + } + } + + public Optional PlayerGuid; + public List BlackListSlots = new List(); + + public class LFGBlackListSlot + { + public uint Slot { get; set; } + public uint Reason { get; set; } + public int SubReason1 { get; set; } + public int SubReason2 { get; set; } + } + } + + public class LfgPlayerDungeonInfo + { + public void Write(WorldPacket data) + { + data.WriteInt32(Slot); + data.WriteInt32(CompletionQuantity); + data.WriteInt32(CompletionLimit); + data.WriteInt32(CompletionCurrencyID); + data.WriteInt32(SpecificQuantity); + data.WriteInt32(SpecificLimit); + data.WriteInt32(OverallQuantity); + data.WriteInt32(OverallLimit); + data.WriteInt32(PurseWeeklyQuantity); + data.WriteInt32(PurseWeeklyLimit); + data.WriteInt32(PurseQuantity); + data.WriteInt32(PurseLimit); + data.WriteInt32(Quantity); + data.WriteInt32(CompletedMask); + + data.WriteInt32(ShortageReward.Count); + + Rewards.Write(data); + + // ShortageReward + foreach (var shortage in ShortageReward) + shortage.Write(data); + + data.WriteBit(FirstReward); + data.WriteBit(ShortageEligible); + data.FlushBits(); + } + + public uint Slot; + public bool FirstReward; + public int CompletionQuantity; + public int CompletionLimit; + public int CompletionCurrencyID; + public int SpecificQuantity; + public int SpecificLimit; + public int OverallQuantity; + public int OverallLimit; + public int PurseWeeklyQuantity; + public int PurseWeeklyLimit; + public int PurseQuantity; + public int PurseLimit; + public int Quantity; + public uint CompletedMask; + public bool ShortageEligible; + public List ShortageReward = new List(); + public LfgPlayerQuestReward Rewards = new LfgPlayerQuestReward(); + } + + public class LfgPlayerQuestReward + { + public void Write(WorldPacket data) + { + data.WriteInt32(Mask); + data.WriteUInt32(RewardMoney); + data.WriteUInt32(RewardXP); + + data.WriteInt32(Items.Count); + data.WriteInt32(Currency.Count); + data.WriteInt32(BonusCurrency.Count); + + // Item + foreach (var item in Items) + { + data.WriteUInt32(item.ItemID); + data.WriteUInt32(item.Quantity); + } + + // Currency + foreach (var currency in Currency) + { + data.WriteUInt32(currency.CurrencyID); + data.WriteUInt32(currency.Quantity); + } + + // BonusCurrency + foreach (var currency in BonusCurrency) + { + data.WriteUInt32(currency.CurrencyID); + data.WriteUInt32(currency.Quantity); + } + + var bit30 = data.WriteBit(false); + if (bit30) + data.WriteUInt32(0); + + data.FlushBits(); + } + + public uint Mask; + public uint RewardMoney; + public uint RewardXP; + public List Items = new List(); + public List Currency = new List(); + public List BonusCurrency = new List(); + + public struct LfgPlayerQuestRewardItem + { + public uint ItemID; + public uint Quantity; + } + + public struct LfgPlayerQuestRewardCurrency + { + public uint CurrencyID; + public uint Quantity; + } + } +} diff --git a/Game/Network/Packets/LootPackets.cs b/Game/Network/Packets/LootPackets.cs new file mode 100644 index 000000000..dc7f2ecd0 --- /dev/null +++ b/Game/Network/Packets/LootPackets.cs @@ -0,0 +1,412 @@ +/* + * Copyright (C) 2012-2017 CypherCore + * + * 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, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +using Framework.Constants; +using Framework.Dynamic; +using Game.Entities; +using System.Collections.Generic; + +namespace Game.Network.Packets +{ + class LootUnit : ClientPacket + { + public LootUnit(WorldPacket packet) : base(packet) { } + + public override void Read() + { + Unit = _worldPacket.ReadPackedGuid(); + } + + public ObjectGuid Unit; + } + + public class LootResponse : ServerPacket + { + public LootResponse() : base(ServerOpcodes.LootResponse, ConnectionType.Instance) { } + + public override void Write() + { + _worldPacket.WritePackedGuid(Owner); + _worldPacket.WritePackedGuid(LootObj); + _worldPacket.WriteUInt8(FailureReason); + _worldPacket.WriteUInt8(AcquireReason); + _worldPacket.WriteUInt8(LootMethod); + _worldPacket.WriteUInt8(Threshold); + _worldPacket.WriteUInt32(Coins); + _worldPacket.WriteUInt32(Items.Count); + _worldPacket.WriteUInt32(Currencies.Count); + _worldPacket.WriteBit(Acquired); + _worldPacket.WriteBit(AELooting); + _worldPacket.FlushBits(); + + foreach (LootItemData item in Items) + item.Write(_worldPacket); + + foreach (LootCurrency currency in Currencies) + { + _worldPacket.WriteUInt32(currency.CurrencyID); + _worldPacket.WriteUInt32(currency.Quantity); + _worldPacket.WriteUInt8(currency.LootListID); + _worldPacket.WriteBits(currency.UIType, 3); + _worldPacket.FlushBits(); + } + } + + public ObjectGuid LootObj; + public ObjectGuid Owner; + public byte Threshold = 2; // Most common value, 2 = Uncommon + public LootMethod LootMethod; + public byte AcquireReason; + public LootError FailureReason = LootError.NoLoot; // Most common value + public uint Coins; + public List Items = new List(); + public List Currencies = new List(); + public bool Acquired; + public bool AELooting; + } + + class LootItemPkt : ClientPacket + { + public LootItemPkt(WorldPacket packet) : base(packet) { } + + public override void Read() + { + uint Count = _worldPacket.ReadUInt32(); + + for (uint i = 0; i < Count; ++i) + { + var loot = new LootRequest() + { + Object = _worldPacket.ReadPackedGuid(), + LootListID = _worldPacket.ReadUInt8() + }; + + Loot.Add(loot); + } + } + + public List Loot = new List(); + } + + class LootRemoved : ServerPacket + { + public LootRemoved() : base(ServerOpcodes.LootRemoved, ConnectionType.Instance) { } + + public override void Write() + { + _worldPacket.WritePackedGuid(Owner); + _worldPacket.WritePackedGuid(LootObj); + _worldPacket.WriteUInt8(LootListID); + } + + public ObjectGuid LootObj; + public ObjectGuid Owner; + public byte LootListID; + } + + class LootRelease : ClientPacket + { + public LootRelease(WorldPacket packet) : base(packet) { } + + public override void Read() + { + Unit = _worldPacket.ReadPackedGuid(); + } + + public ObjectGuid Unit; + } + + class LootMoney : ClientPacket + { + public LootMoney(WorldPacket packet) : base(packet) { } + + public override void Read() { } + } + + class LootMoneyNotify : ServerPacket + { + public LootMoneyNotify() : base(ServerOpcodes.LootMoneyNotify) { } + + public override void Write() + { + _worldPacket.WriteUInt32(Money); + _worldPacket.WriteBit(SoleLooter); + _worldPacket.FlushBits(); + } + + public uint Money; + public bool SoleLooter; + } + + class CoinRemoved : ServerPacket + { + public CoinRemoved() : base(ServerOpcodes.CoinRemoved) { } + + public override void Write() + { + _worldPacket.WritePackedGuid(LootObj); + } + + public ObjectGuid LootObj; + } + + class LootRoll : ClientPacket + { + public LootRoll(WorldPacket packet) : base(packet) { } + + public override void Read() + { + LootObj = _worldPacket.ReadPackedGuid(); + LootListID = _worldPacket.ReadUInt8(); + RollType = (RollType)_worldPacket.ReadUInt8(); + } + + public ObjectGuid LootObj; + public byte LootListID; + public RollType RollType; + } + + class LootReleaseResponse : ServerPacket + { + public LootReleaseResponse() : base(ServerOpcodes.LootRelease) { } + + public override void Write() + { + _worldPacket.WritePackedGuid(LootObj); + _worldPacket.WritePackedGuid(Owner); + } + + public ObjectGuid LootObj; + public ObjectGuid Owner; + } + + class LootReleaseAll : ServerPacket + { + public LootReleaseAll() : base(ServerOpcodes.LootReleaseAll, ConnectionType.Instance) { } + + public override void Write() { } + } + + class LootList : ServerPacket + { + public LootList() : base(ServerOpcodes.LootList) { } + + public override void Write() + { + _worldPacket.WritePackedGuid(Owner); + _worldPacket.WritePackedGuid(LootObj); + + _worldPacket.WriteBit(Master.HasValue); + _worldPacket.WriteBit(RoundRobinWinner.HasValue); + _worldPacket.FlushBits(); + + if (Master.HasValue) + _worldPacket.WritePackedGuid(Master.Value); + + if (RoundRobinWinner.HasValue) + _worldPacket.WritePackedGuid(RoundRobinWinner.Value); + } + + public ObjectGuid Owner; + public ObjectGuid LootObj; + public Optional Master; + public Optional RoundRobinWinner; + } + + class SetLootSpecialization : ClientPacket + { + public SetLootSpecialization(WorldPacket packet) : base(packet) { } + + public override void Read() + { + SpecID = _worldPacket.ReadUInt32(); + } + + public uint SpecID; + } + + class StartLootRoll : ServerPacket + { + public StartLootRoll() : base(ServerOpcodes.StartLootRoll) { } + + public override void Write() + { + _worldPacket.WritePackedGuid(LootObj); + _worldPacket.WriteInt32(MapID); + _worldPacket.WriteUInt32(RollTime); + _worldPacket.WriteUInt8(ValidRolls); + _worldPacket.WriteUInt8(Method); + Item.Write(_worldPacket); + } + + public ObjectGuid LootObj; + public int MapID; + public uint RollTime; + public LootMethod Method; + public RollMask ValidRolls; + public LootItemData Item = new LootItemData(); + } + + class LootRollBroadcast : ServerPacket + { + public LootRollBroadcast() : base(ServerOpcodes.LootRoll) { } + + public override void Write() + { + _worldPacket.WritePackedGuid(LootObj); + _worldPacket.WritePackedGuid(Player); + _worldPacket.WriteInt32(Roll); + _worldPacket.WriteUInt8(RollType); + Item.Write(_worldPacket); + _worldPacket.WriteBit(Autopassed); + _worldPacket.FlushBits(); + } + + public ObjectGuid LootObj; + public ObjectGuid Player; + public int Roll; // Roll value can be negative, it means that it is an "offspec" roll but only during roll selection broadcast (not when sending the result) + public RollType RollType; + public LootItemData Item = new LootItemData(); + public bool Autopassed; // Triggers message |HlootHistory:%d|h[Loot]|h: You automatically passed on: %s because you cannot loot that item. + } + + class LootRollWon : ServerPacket + { + public LootRollWon() : base(ServerOpcodes.LootRollWon) { } + + public override void Write() + { + _worldPacket.WritePackedGuid(LootObj); + _worldPacket.WritePackedGuid(Winner); + _worldPacket.WriteInt32(Roll); + _worldPacket.WriteUInt8(RollType); + Item.Write(_worldPacket); + _worldPacket.WriteBit(MainSpec); + _worldPacket.FlushBits(); + } + + public ObjectGuid LootObj; + public ObjectGuid Winner; + public int Roll; + public RollType RollType; + public LootItemData Item = new LootItemData(); + public bool MainSpec; + } + + class LootAllPassed : ServerPacket + { + public LootAllPassed() : base(ServerOpcodes.LootAllPassed) { } + + public override void Write() + { + _worldPacket.WritePackedGuid(LootObj); + Item.Write(_worldPacket); + } + + public ObjectGuid LootObj; + public LootItemData Item = new LootItemData(); + } + + class LootRollsComplete : ServerPacket + { + public LootRollsComplete() : base(ServerOpcodes.LootRollsComplete) { } + + public override void Write() + { + _worldPacket.WritePackedGuid(LootObj); + _worldPacket.WriteUInt8(LootListID); + } + + public ObjectGuid LootObj; + public byte LootListID; + } + + class AELootTargets : ServerPacket + { + public AELootTargets(uint count) : base(ServerOpcodes.AeLootTargets, ConnectionType.Instance) + { + Count = count; + } + + public override void Write() + { + _worldPacket.WriteUInt32(Count); + } + + uint Count; + } + + class AELootTargetsAck : ServerPacket + { + public AELootTargetsAck() : base(ServerOpcodes.AeLootTargetAck, ConnectionType.Instance) { } + + public override void Write() { } + } + + class MasterLootCandidateList : ServerPacket + { + public MasterLootCandidateList() : base(ServerOpcodes.MasterLootCandidateList) { } + + public override void Write() + { + _worldPacket.WritePackedGuid(LootObj); + _worldPacket.WriteUInt32(Players.Count); + Players.ForEach(guid => _worldPacket.WritePackedGuid(guid)); + } + + public List Players = new List(); + public ObjectGuid LootObj; + } + + //Structs + public class LootItemData + { + public void Write(WorldPacket data) + { + data.WriteBits(Type, 2); + data.WriteBits(UIType, 3); + data.WriteBit(CanTradeToTapList); + data.FlushBits(); + Loot.Write(data); // WorldPackets::Item::ItemInstance + data.WriteUInt32(Quantity); + data.WriteUInt8(LootItemType); + data.WriteUInt8(LootListID); + } + + public byte Type; + public LootSlotType UIType; + public uint Quantity; + public byte LootItemType; + public byte LootListID; + public bool CanTradeToTapList; + public ItemInstance Loot; + } + + public struct LootCurrency + { + public uint CurrencyID; + public uint Quantity; + public byte LootListID; + public byte UIType; + } + + public struct LootRequest + { + public ObjectGuid Object; + public byte LootListID; + } +} diff --git a/Game/Network/Packets/MailPackets.cs b/Game/Network/Packets/MailPackets.cs new file mode 100644 index 000000000..2ba9777ae --- /dev/null +++ b/Game/Network/Packets/MailPackets.cs @@ -0,0 +1,459 @@ +/* + * Copyright (C) 2012-2017 CypherCore + * + * 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, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +using Framework.Constants; +using Framework.Dynamic; +using Game.Entities; +using Game.Mails; +using System.Collections.Generic; + +namespace Game.Network.Packets +{ + public class MailGetList : ClientPacket + { + public MailGetList(WorldPacket packet) : base(packet) { } + + public override void Read() + { + Mailbox = _worldPacket.ReadPackedGuid(); + } + + public ObjectGuid Mailbox; + } + + public class MailListResult : ServerPacket + { + public MailListResult() : base(ServerOpcodes.MailListResult) { } + + public override void Write() + { + _worldPacket.WriteUInt32(Mails.Count); + _worldPacket.WriteInt32(TotalNumRecords); + + Mails.ForEach(p => p.Write(_worldPacket)); + } + + public int TotalNumRecords; + public List Mails = new List(); + } + + public class MailCreateTextItem : ClientPacket + { + public MailCreateTextItem(WorldPacket packet) : base(packet) { } + + public override void Read() + { + Mailbox = _worldPacket.ReadPackedGuid(); + MailID = _worldPacket.ReadUInt32(); + } + + public ObjectGuid Mailbox; + public uint MailID; + } + + public class SendMail : ClientPacket + { + public SendMail(WorldPacket packet) : base(packet) + { + Info = new StructSendMail(); + } + + public override void Read() + { + Info.Mailbox = _worldPacket.ReadPackedGuid(); + Info.StationeryID = _worldPacket.ReadInt32(); + Info.SendMoney = _worldPacket.ReadInt64(); + Info.Cod = _worldPacket.ReadInt64(); + + uint targetLength = _worldPacket.ReadBits(9); + uint subjectLength = _worldPacket.ReadBits(9); + uint bodyLength = _worldPacket.ReadBits(11); + + uint count = _worldPacket.ReadBits(5); + + Info.Target = _worldPacket.ReadString(targetLength); + Info.Subject = _worldPacket.ReadString(subjectLength); + Info.Body = _worldPacket.ReadString(bodyLength); + + for (var i = 0; i < count; ++i) + { + var att = new StructSendMail.MailAttachment() + { + AttachPosition = _worldPacket.ReadUInt8(), + ItemGUID = _worldPacket.ReadPackedGuid() + }; + + Info.Attachments.Add(att); + } + } + + public StructSendMail Info; + + public class StructSendMail + { + public ObjectGuid Mailbox; + public int StationeryID; + public long SendMoney; + public long Cod; + public string Target; + public string Subject; + public string Body; + public List Attachments = new List(); + + public struct MailAttachment + { + public byte AttachPosition; + public ObjectGuid ItemGUID; + } + } + } + + public class MailCommandResult : ServerPacket + { + public MailCommandResult() : base(ServerOpcodes.MailCommandResult) { } + + public override void Write() + { + _worldPacket.WriteUInt32(MailID); + _worldPacket.WriteUInt32(Command); + _worldPacket.WriteUInt32(ErrorCode); + _worldPacket.WriteUInt32(BagResult); + _worldPacket.WriteUInt32(AttachID); + _worldPacket.WriteUInt32(QtyInInventory); + } + + public uint MailID; + public uint Command; + public uint ErrorCode; + public uint BagResult; + public uint AttachID; + public uint QtyInInventory; + } + + public class MailReturnToSender : ClientPacket + { + public MailReturnToSender(WorldPacket packet) : base(packet) { } + + public override void Read() + { + MailID = _worldPacket.ReadUInt32(); + SenderGUID = _worldPacket.ReadPackedGuid(); + } + + public uint MailID; + public ObjectGuid SenderGUID; + } + + public class MailMarkAsRead : ClientPacket + { + public MailMarkAsRead(WorldPacket packet) : base(packet) { } + + public override void Read() + { + Mailbox = _worldPacket.ReadPackedGuid(); + MailID = _worldPacket.ReadUInt32(); + BiReceipt = _worldPacket.HasBit(); + } + + public ObjectGuid Mailbox; + public uint MailID; + public bool BiReceipt; + } + + public class MailDelete : ClientPacket + { + public MailDelete(WorldPacket packet) : base(packet) { } + + public override void Read() + { + MailID = _worldPacket.ReadUInt32(); + DeleteReason = _worldPacket.ReadInt32(); + } + + public uint MailID; + public int DeleteReason; + } + + public class MailTakeItem : ClientPacket + { + public MailTakeItem(WorldPacket packet) : base(packet) { } + + public override void Read() + { + Mailbox = _worldPacket.ReadPackedGuid(); + MailID = _worldPacket.ReadUInt32(); + AttachID = _worldPacket.ReadUInt32(); + } + + public ObjectGuid Mailbox; + public uint MailID; + public uint AttachID; + } + + public class MailTakeMoney : ClientPacket + { + public MailTakeMoney(WorldPacket packet) : base(packet) { } + + public override void Read() + { + Mailbox = _worldPacket.ReadPackedGuid(); + MailID = _worldPacket.ReadUInt32(); + Money = _worldPacket.ReadInt64(); + } + + public ObjectGuid Mailbox; + public uint MailID; + public long Money; + } + + public class MailQueryNextMailTime : ClientPacket + { + public MailQueryNextMailTime(WorldPacket packet) : base(packet) { } + + public override void Read() { } + } + + public class MailQueryNextTimeResult : ServerPacket + { + public MailQueryNextTimeResult() : base(ServerOpcodes.MailQueryNextTimeResult) + { + Next = new List(); + } + + public override void Write() + { + _worldPacket.WriteFloat(NextMailTime); + _worldPacket.WriteInt32(Next.Count); + + foreach (var entry in Next) + { + _worldPacket.WritePackedGuid(entry.SenderGuid); + _worldPacket.WriteFloat(entry.TimeLeft); + _worldPacket.WriteInt32(entry.AltSenderID); + _worldPacket.WriteInt8(entry.AltSenderType); + _worldPacket.WriteInt32(entry.StationeryID); + } + } + + public float NextMailTime; + public List Next; + + public class MailNextTimeEntry + { + public MailNextTimeEntry(Mail mail) + { + switch (mail.messageType) + { + case MailMessageType.Normal: + SenderGuid = ObjectGuid.Create(HighGuid.Player, mail.sender); + break; + case MailMessageType.Auction: + case MailMessageType.Creature: + case MailMessageType.Gameobject: + case MailMessageType.Calendar: + AltSenderID = (int)mail.sender; + break; + } + + TimeLeft = mail.deliver_time - Time.UnixTime; + AltSenderType = (sbyte)mail.messageType; + StationeryID = (int)mail.stationery; + } + + public ObjectGuid SenderGuid; + public float TimeLeft; + public int AltSenderID; + public sbyte AltSenderType; + public int StationeryID; + } + } + + public class NotifyRecievedMail : ServerPacket + { + public NotifyRecievedMail() : base(ServerOpcodes.NotifyReceivedMail) { } + + public override void Write() + { + _worldPacket.WriteFloat(Delay); + } + + public float Delay = 0.0f; + } + + class ShowMailbox : ServerPacket + { + public ShowMailbox() : base(ServerOpcodes.ShowMailbox) { } + + public override void Write() + { + _worldPacket.WritePackedGuid(PostmasterGUID); + } + + public ObjectGuid PostmasterGUID; + } + + //Structs + public class MailAttachedItem + { + public MailAttachedItem(Item item, byte pos) + { + Position = pos; + AttachID = (int)item.GetGUID().GetCounter(); + Item = new ItemInstance(item); + Count = item.GetCount(); + Charges = item.GetSpellCharges(); + MaxDurability = item.GetUInt32Value(ItemFields.MaxDurability); + Durability = item.GetUInt32Value(ItemFields.Durability); + Unlocked = !item.IsLocked(); + + for (EnchantmentSlot slot = 0; slot < EnchantmentSlot.MaxInspected; slot++) + { + if (item.GetEnchantmentId(slot) == 0) + continue; + + Enchants.Add(new ItemEnchantData((int)item.GetEnchantmentId(slot), item.GetEnchantmentDuration(slot), (int)item.GetEnchantmentCharges(slot), (byte)slot)); + } + + byte i = 0; + foreach (ItemDynamicFieldGems gemData in item.GetGems()) + { + if (gemData.ItemId != 0) + { + ItemGemData gem = new ItemGemData(); + gem.Slot = i; + gem.Item = new ItemInstance(gemData); + Gems.Add(gem); + } + ++i; + } + } + + public void Write(WorldPacket data) + { + data.WriteUInt8(Position); + data.WriteUInt32(AttachID); + data.WriteInt32(Count); + data.WriteInt32(Charges); + data.WriteInt32(MaxDurability); + data.WriteInt32(Durability); + Item.Write(data); + data.WriteBits(Enchants.Count, 4); + data.WriteBits(Gems.Count, 2); + data.WriteBit(Unlocked); + data.FlushBits(); + + foreach (ItemGemData gem in Gems) + gem.Write(data); + + foreach (ItemEnchantData en in Enchants) + en.Write(data); + } + + public byte Position; + public int AttachID; + public ItemInstance Item; + public uint Count; + public int Charges; + public uint MaxDurability; + public uint Durability; + public bool Unlocked; + List Enchants = new List(); + List Gems= new List(); + } + + public class MailListEntry + { + public MailListEntry(Mail mail, Player player) + { + MailID = (int)mail.messageID; + SenderType = (byte)mail.messageType; + + switch (mail.messageType) + { + case MailMessageType.Normal: + SenderCharacter.Set(ObjectGuid.Create(HighGuid.Player, mail.sender)); + break; + case MailMessageType.Creature: + case MailMessageType.Gameobject: + case MailMessageType.Auction: + case MailMessageType.Calendar: + AltSenderID.Set((uint)mail.sender); + break; + } + + Cod = mail.COD; + StationeryID = (int)mail.stationery; + SentMoney = mail.money; + Flags = (int)mail.checkMask; + DaysLeft = (mail.expire_time - Time.UnixTime) / Time.Day; + MailTemplateID = (int)mail.mailTemplateId; + Subject = mail.subject; + Body = mail.body; + + for (byte i = 0; i < mail.items.Count; i++) + { + Item item = player.GetMItem(mail.items[i].item_guid); + if (item) + Attachments.Add(new MailAttachedItem(item, i)); + } + } + + public void Write(WorldPacket data) + { + data.WriteInt32(MailID); + data.WriteUInt8(SenderType); + data.WriteUInt64(Cod); + data.WriteInt32(StationeryID); + data.WriteUInt64(SentMoney); + data.WriteInt32(Flags); + data.WriteFloat(DaysLeft); + data.WriteInt32(MailTemplateID); + data.WriteUInt32(Attachments.Count); + + data.WriteBit(SenderCharacter.HasValue); + data.WriteBit(AltSenderID.HasValue); + data.WriteBits(Subject.Length, 8); + data.WriteBits(Body.Length, 13); + data.FlushBits(); + + Attachments.ForEach(p => p.Write(data)); + + if (SenderCharacter.HasValue) + data.WritePackedGuid(SenderCharacter.Value); + + if (AltSenderID.HasValue) + data.WriteInt32(AltSenderID.Value); + + data.WriteString(Subject); + data.WriteString(Body); + } + + public int MailID; + public byte SenderType; + public Optional SenderCharacter; + public Optional AltSenderID; + public ulong Cod; + public int StationeryID; + public ulong SentMoney; + public int Flags; + public float DaysLeft; + public int MailTemplateID; + public string Subject; + public string Body; + public List Attachments = new List(); + } +} diff --git a/Game/Network/Packets/MiscPackets.cs b/Game/Network/Packets/MiscPackets.cs new file mode 100644 index 000000000..f1407ea32 --- /dev/null +++ b/Game/Network/Packets/MiscPackets.cs @@ -0,0 +1,1290 @@ +/* + * Copyright (C) 2012-2017 CypherCore + * + * 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, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +using Framework.Constants; +using Framework.Dynamic; +using Framework.GameMath; +using Game.Entities; +using System.Collections.Generic; +using System; + +namespace Game.Network.Packets +{ + public class BindPointUpdate : ServerPacket + { + public BindPointUpdate() : base(ServerOpcodes.BindPointUpdate, ConnectionType.Instance) { } + + public override void Write() + { + _worldPacket.WriteVector3(BindPosition); + _worldPacket.WriteUInt32(BindMapID); + _worldPacket.WriteUInt32(BindAreaID); + } + + public uint BindMapID = 0xFFFFFFFF; + public Vector3 BindPosition; + public uint BindAreaID; + } + + public class PlayerBound : ServerPacket + { + public PlayerBound(ObjectGuid binderId, uint areaId) : base(ServerOpcodes.PlayerBound) + { + BinderID = binderId; + AreaID = areaId; + } + + public override void Write() + { + _worldPacket.WritePackedGuid(BinderID); + _worldPacket.WriteUInt32(AreaID); + } + + ObjectGuid BinderID; + uint AreaID; + } + + public class BinderConfirm : ServerPacket + { + public BinderConfirm(ObjectGuid unit) : base(ServerOpcodes.BinderConfirm) + { + Unit = unit; + } + + public override void Write() + { + _worldPacket.WritePackedGuid(Unit); + } + + ObjectGuid Unit; + } + + public class InvalidatePlayer : ServerPacket + { + public InvalidatePlayer() : base(ServerOpcodes.InvalidatePlayer) { } + + public override void Write() + { + _worldPacket.WritePackedGuid(Guid); + } + + public ObjectGuid Guid; + } + + public class LoginSetTimeSpeed : ServerPacket + { + public LoginSetTimeSpeed() : base(ServerOpcodes.LoginSetTimeSpeed, ConnectionType.Instance) { } + + public override void Write() + { + _worldPacket.WritePackedTime(ServerTime); + _worldPacket.WritePackedTime(GameTime); + _worldPacket.WriteFloat(NewSpeed); + _worldPacket.WriteUInt32(ServerTimeHolidayOffset); + _worldPacket.WriteUInt32(GameTimeHolidayOffset); + } + + public float NewSpeed; + public int ServerTimeHolidayOffset; + public uint GameTime; + public uint ServerTime; + public int GameTimeHolidayOffset; + } + + public class SetCurrency : ServerPacket + { + public SetCurrency() : base(ServerOpcodes.SetCurrency, ConnectionType.Instance) { } + + public override void Write() + { + _worldPacket.WriteUInt32(Type); + _worldPacket.WriteInt32(Quantity); + _worldPacket.WriteUInt32(Flags); + _worldPacket.WriteBit(WeeklyQuantity.HasValue); + _worldPacket.WriteBit(TrackedQuantity.HasValue); + _worldPacket.WriteBit(MaxQuantity.HasValue); + _worldPacket.WriteBit(SuppressChatLog); + _worldPacket.FlushBits(); + + if (WeeklyQuantity.HasValue) + _worldPacket.WriteInt32(WeeklyQuantity.Value); + + if (TrackedQuantity.HasValue) + _worldPacket.WriteInt32(TrackedQuantity.Value); + + if (MaxQuantity.HasValue) + _worldPacket.WriteInt32(MaxQuantity.Value); + } + + public uint Type; + public int Quantity; + public uint Flags; + public Optional WeeklyQuantity; + public Optional TrackedQuantity; + public Optional MaxQuantity; + public bool SuppressChatLog; + } + + public class SetMaxWeeklyQuantity : ServerPacket + { + public SetMaxWeeklyQuantity() : base(ServerOpcodes.SetMaxWeeklyQuantity, ConnectionType.Instance) { } + + public override void Write() + { + _worldPacket.WriteUInt32("Type"); + _worldPacket.WriteUInt32("MaxWeeklyQuantity"); + } + + public uint MaxWeeklyQuantity; + public uint Type; + } + + public class ResetWeeklyCurrency : ServerPacket + { + public ResetWeeklyCurrency() : base(ServerOpcodes.ResetWeeklyCurrency, ConnectionType.Instance) { } + + public override void Write() { } + } + + public class SetSelection : ClientPacket + { + public SetSelection(WorldPacket packet) : base(packet) { } + + public override void Read() + { + Selection = _worldPacket.ReadPackedGuid(); + } + + public ObjectGuid Selection; // Target + } + + public class SetupCurrency : ServerPacket + { + public SetupCurrency() : base(ServerOpcodes.SetupCurrency, ConnectionType.Instance) { } + + public override void Write() + { + _worldPacket.WriteUInt32(Data.Count); + + foreach (Record data in Data) + { + _worldPacket.WriteUInt32(data.Type); + _worldPacket.WriteUInt32(data.Quantity); + + _worldPacket.WriteBit(data.WeeklyQuantity.HasValue); + _worldPacket.WriteBit(data.MaxWeeklyQuantity.HasValue); + _worldPacket.WriteBit(data.TrackedQuantity.HasValue); + _worldPacket.WriteBit(data.MaxQuantity.HasValue); + _worldPacket.WriteBits(data.Flags, 5); + _worldPacket.FlushBits(); + + if (data.WeeklyQuantity.HasValue) + _worldPacket.WriteUInt32(data.WeeklyQuantity.Value); + if (data.MaxWeeklyQuantity.HasValue) + _worldPacket.WriteUInt32(data.MaxWeeklyQuantity.Value); + if (data.TrackedQuantity.HasValue) + _worldPacket.WriteUInt32(data.TrackedQuantity.Value); + if (data.MaxQuantity.HasValue) + _worldPacket.WriteUInt32(data.MaxQuantity.Value); + } + } + + public List Data = new List(); + + public struct Record + { + public uint Type; + public uint Quantity; + public Optional WeeklyQuantity; // Currency count obtained this Week. + public Optional MaxWeeklyQuantity; // Weekly Currency cap. + public Optional TrackedQuantity; + public Optional MaxQuantity; + public byte Flags; // 0 = none, + } + } + + public class ViolenceLevel : ClientPacket + { + public ViolenceLevel(WorldPacket packet) : base(packet) { } + + public override void Read() + { + violenceLevel = _worldPacket.ReadInt8(); + } + + sbyte violenceLevel = -1; // 0 - no combat effects, 1 - display some combat effects, 2 - blood, 3 - bloody, 4 - bloodier, 5 - bloodiest + } + + public class TimeSyncRequest : ServerPacket + { + public TimeSyncRequest() : base(ServerOpcodes.TimeSyncRequest, ConnectionType.Instance) { } + + public override void Write() + { + _worldPacket.WriteUInt32(SequenceIndex); + } + + public uint SequenceIndex; + } + + public class TimeSyncResponse : ClientPacket + { + public TimeSyncResponse(WorldPacket packet) : base(packet) { } + + public override void Read() + { + SequenceIndex = _worldPacket.ReadUInt32(); + ClientTime = _worldPacket.ReadUInt32(); + } + + public uint ClientTime; // Client ticks in ms + public uint SequenceIndex; // Same index as in request + } + + public class TriggerCinematic : ServerPacket + { + public TriggerCinematic() : base(ServerOpcodes.TriggerCinematic) { } + + public override void Write() + { + _worldPacket.WriteUInt32(CinematicID); + } + + public uint CinematicID; + } + + public class TriggerMovie : ServerPacket + { + public TriggerMovie() : base(ServerOpcodes.TriggerMovie) { } + + public override void Write() + { + _worldPacket.WriteUInt32(MovieID); + } + + public uint MovieID; + } + + public class UITimeRequest : ClientPacket + { + public UITimeRequest(WorldPacket packet) : base(packet) { } + + public override void Read() { } + } + + public class UITime : ServerPacket + { + public UITime() : base(ServerOpcodes.UiTime) { } + + public override void Write() + { + _worldPacket.WriteUInt32(Time); + } + + public uint Time; + } + + public class TutorialFlags : ServerPacket + { + public TutorialFlags() : base(ServerOpcodes.TutorialFlags) { } + + public override void Write() + { + for (byte i = 0; i < (int)Tutorials.Max; ++i) + _worldPacket.WriteUInt32(TutorialData[i]); + } + + public uint[] TutorialData = new uint[(int)Tutorials.Max]; + } + + public class TutorialSetFlag : ClientPacket + { + public TutorialSetFlag(WorldPacket packet) : base(packet) { } + + public override void Read() + { + Action = (TutorialAction)_worldPacket.ReadBits(2); + if (Action == TutorialAction.Update) + TutorialBit = _worldPacket.ReadUInt32(); + } + + public TutorialAction Action; + public uint TutorialBit; + } + + public class WorldServerInfo : ServerPacket + { + public WorldServerInfo() : base(ServerOpcodes.WorldServerInfo, ConnectionType.Instance) + { + InstanceGroupSize = new Optional(); + + RestrictedAccountMaxLevel = new Optional(); + RestrictedAccountMaxMoney = new Optional(); + } + + public override void Write() + { + _worldPacket.WriteUInt32(DifficultyID); + _worldPacket.WriteUInt8(IsTournamentRealm); + _worldPacket.WriteBit(XRealmPvpAlert); + _worldPacket.WriteBit(RestrictedAccountMaxLevel.HasValue); + _worldPacket.WriteBit(RestrictedAccountMaxMoney.HasValue); + _worldPacket.WriteBit(InstanceGroupSize.HasValue); + _worldPacket.FlushBits(); + + if (RestrictedAccountMaxLevel.HasValue) + _worldPacket.WriteUInt32(RestrictedAccountMaxLevel.Value); + + if (RestrictedAccountMaxMoney.HasValue) + _worldPacket.WriteUInt32(RestrictedAccountMaxMoney.Value); + + if (InstanceGroupSize.HasValue) + _worldPacket.WriteUInt32(InstanceGroupSize.Value); + } + + public uint DifficultyID; + public byte IsTournamentRealm; + public bool XRealmPvpAlert; + public Optional RestrictedAccountMaxLevel; + public Optional RestrictedAccountMaxMoney; + public Optional InstanceGroupSize; + } + + public class SetDungeonDifficulty : ClientPacket + { + public SetDungeonDifficulty(WorldPacket packet) : base(packet) { } + + public override void Read() + { + DifficultyID = _worldPacket.ReadInt32(); + } + + public int DifficultyID; + } + + public class SetRaidDifficulty : ClientPacket + { + public SetRaidDifficulty(WorldPacket packet) : base(packet) { } + + public override void Read() + { + DifficultyID = _worldPacket.ReadInt32(); + Legacy = _worldPacket.ReadUInt8(); + } + + public int DifficultyID; + public byte Legacy; + } + + public class DungeonDifficultySet : ServerPacket + { + public DungeonDifficultySet() : base(ServerOpcodes.SetDungeonDifficulty) { } + + public override void Write() + { + _worldPacket.WriteInt32(DifficultyID); + } + + public int DifficultyID; + } + + public class RaidDifficultySet : ServerPacket + { + public RaidDifficultySet() : base(ServerOpcodes.RaidDifficultySet) { } + + public override void Write() + { + _worldPacket.WriteInt32(DifficultyID); + _worldPacket.WriteUInt8(Legacy); + } + + public int DifficultyID; + public bool Legacy; + } + + public class CorpseReclaimDelay : ServerPacket + { + public CorpseReclaimDelay() : base(ServerOpcodes.CorpseReclaimDelay, ConnectionType.Instance) { } + + public override void Write() + { + _worldPacket.WriteUInt32(Remaining); + } + + public uint Remaining; + } + + public class DeathReleaseLoc : ServerPacket + { + public DeathReleaseLoc() : base(ServerOpcodes.DeathReleaseLoc) { } + + public override void Write() + { + _worldPacket.WriteInt32(MapID); + _worldPacket.WriteVector3(Loc); + } + + public int MapID; + public Vector3 Loc; + } + + public class PortGraveyard : ClientPacket + { + public PortGraveyard(WorldPacket packet) : base(packet) { } + + public override void Read() { } + } + + public class PreRessurect : ServerPacket + { + public PreRessurect() : base(ServerOpcodes.PreRessurect) { } + + public override void Write() + { + _worldPacket.WritePackedGuid(PlayerGUID); + } + + public ObjectGuid PlayerGUID; + } + + public class ReclaimCorpse : ClientPacket + { + public ReclaimCorpse(WorldPacket packet) : base(packet) { } + + public override void Read() + { + CorpseGUID = _worldPacket.ReadPackedGuid(); + } + + public ObjectGuid CorpseGUID; + } + + public class RepopRequest : ClientPacket + { + public RepopRequest(WorldPacket packet) : base(packet) { } + + public override void Read() + { + CheckInstance = _worldPacket.HasBit(); + } + + public bool CheckInstance; + } + + public class RequestCemeteryList : ClientPacket + { + public RequestCemeteryList(WorldPacket packet) : base(packet) { } + + public override void Read() { } + } + + public class RequestCemeteryListResponse : ServerPacket + { + public RequestCemeteryListResponse() : base(ServerOpcodes.RequestCemeteryListResponse, ConnectionType.Instance) { } + + public override void Write() + { + _worldPacket.WriteBit(IsGossipTriggered); + _worldPacket.FlushBits(); + + _worldPacket.WriteUInt32(CemeteryID.Count); + foreach (uint cemetery in CemeteryID) + _worldPacket.WriteUInt32(cemetery); + } + + public bool IsGossipTriggered; + public List CemeteryID = new List(); + } + + public class ResurrectResponse : ClientPacket + { + public ResurrectResponse(WorldPacket packet) : base(packet) { } + + public override void Read() + { + Resurrecter = _worldPacket.ReadPackedGuid(); + Response = _worldPacket.ReadUInt32(); + } + + public ObjectGuid Resurrecter; + public uint Response; + } + + public class WeatherPkt : ServerPacket + { + public WeatherPkt(WeatherState weatherID = 0, float intensity = 0.0f, bool abrupt = false) : base(ServerOpcodes.Weather, ConnectionType.Instance) + { + WeatherID = weatherID; + Intensity = intensity; + Abrupt = abrupt; + } + + public override void Write() + { + _worldPacket.WriteUInt32(WeatherID); + _worldPacket.WriteFloat(Intensity); + _worldPacket.WriteBit(Abrupt); + + _worldPacket.FlushBits(); + } + + bool Abrupt; + float Intensity; + WeatherState WeatherID; + } + + public class StandStateChange : ClientPacket + { + public StandStateChange(WorldPacket packet) : base(packet) { } + + public override void Read() + { + StandState = (UnitStandStateType)_worldPacket.ReadUInt32(); + } + + public UnitStandStateType StandState; + } + + public class StandStateUpdate : ServerPacket + { + public StandStateUpdate(UnitStandStateType state, uint animKitId) : base(ServerOpcodes.StandStateUpdate) + { + State = state; + AnimKitID = animKitId; + } + + public override void Write() + { + _worldPacket.WriteUInt32(AnimKitID); + _worldPacket.WriteUInt8(State); + } + + uint AnimKitID; + UnitStandStateType State; + } + + public class StartMirrorTimer : ServerPacket + { + public StartMirrorTimer(MirrorTimerType timer, int value, int maxValue, int scale, int spellID, bool paused) : base(ServerOpcodes.StartMirrorTimer) + { + Timer = timer; + Value = value; + MaxValue = maxValue; + Scale = scale; + SpellID = spellID; + Paused = paused; + } + + public override void Write() + { + _worldPacket.WriteInt32(Timer); + _worldPacket.WriteInt32(Value); + _worldPacket.WriteInt32(MaxValue); + _worldPacket.WriteInt32(Scale); + _worldPacket.WriteInt32(SpellID); + _worldPacket.WriteBit(Paused); + _worldPacket.FlushBits(); + } + + public int Scale; + public int MaxValue; + public MirrorTimerType Timer; + public int SpellID; + public int Value; + public bool Paused; + } + + public class PauseMirrorTimer : ServerPacket + { + public PauseMirrorTimer(MirrorTimerType timer, bool paused) : base(ServerOpcodes.PauseMirrorTimer) + { + Timer = timer; + Paused = paused; + } + + public override void Write() + { + _worldPacket.WriteInt32(Timer); + _worldPacket.WriteBit(Paused); + _worldPacket.FlushBits(); + } + + public bool Paused = true; + public MirrorTimerType Timer; + } + + public class StopMirrorTimer : ServerPacket + { + public StopMirrorTimer(MirrorTimerType timer) : base(ServerOpcodes.StopMirrorTimer) + { + Timer = timer; + } + + public override void Write() + { + _worldPacket.WriteInt32(Timer); + } + + public MirrorTimerType Timer; + } + + public class ExplorationExperience : ServerPacket + { + public ExplorationExperience(uint experience, uint areaID) : base(ServerOpcodes.ExplorationExperience) + { + Experience = experience; + AreaID = areaID; + } + + public override void Write() + { + _worldPacket.WriteUInt32(AreaID); + _worldPacket.WriteUInt32(Experience); + } + + public uint Experience; + public uint AreaID; + } + + public class LevelUpInfo : ServerPacket + { + public LevelUpInfo() : base(ServerOpcodes.LevelUpInfo) { } + + public override void Write() + { + _worldPacket.WriteUInt32(Level); + _worldPacket.WriteUInt32(HealthDelta); + + foreach (int power in PowerDelta) + _worldPacket.WriteInt32(power); + + foreach (int stat in StatDelta) + _worldPacket.WriteInt32(stat); + + _worldPacket.WriteInt32(Cp); + } + + public uint Level = 0; + public uint HealthDelta = 0; + public uint[] PowerDelta = new uint[6]; + public uint[] StatDelta = new uint[(int)Stats.Max]; + public int Cp = 0; + } + + public class PlayMusic : ServerPacket + { + public PlayMusic(uint soundKitID) : base(ServerOpcodes.PlayMusic) + { + SoundKitID = soundKitID; + } + + public override void Write() + { + _worldPacket.WriteUInt32(SoundKitID); + } + + uint SoundKitID; + } + + public class RandomRollClient : ClientPacket + { + public RandomRollClient(WorldPacket packet) : base(packet) { } + + public override void Read() + { + Min = _worldPacket.ReadUInt32(); + Max = _worldPacket.ReadUInt32(); + PartyIndex = _worldPacket.ReadUInt8(); + } + + public uint Min; + public uint Max; + public byte PartyIndex; + } + + public class RandomRoll : ServerPacket + { + public ObjectGuid Roller; + public ObjectGuid RollerWowAccount; + public int Min; + public int Max; + public int Result; + + public RandomRoll() : base(ServerOpcodes.RandomRoll) { } + + public override void Write() + { + _worldPacket.WritePackedGuid(Roller); + _worldPacket.WritePackedGuid(RollerWowAccount); + _worldPacket.WriteInt32(Min); + _worldPacket.WriteInt32(Max); + _worldPacket.WriteInt32(Result); + } + } + + public class EnableBarberShop : ServerPacket + { + public EnableBarberShop() : base(ServerOpcodes.EnableBarberShop) { } + + public override void Write() { } + } + + public class PhaseShift : ServerPacket + { + public PhaseShift() : base(ServerOpcodes.PhaseShiftChange, ConnectionType.Instance) { } + + public override void Write() + { + _worldPacket.WritePackedGuid(ClientGUID); + _worldPacket.WriteUInt32(!PhaseShifts.Empty() ? 0 : 8); // PhaseShiftFlags + _worldPacket.WriteUInt32(PhaseShifts.Count); + _worldPacket.WritePackedGuid(PersonalGUID); + foreach (uint phase in PhaseShifts) + { + _worldPacket.WriteUInt16(1); // PhaseFlags + _worldPacket.WriteUInt16(phase); // PhaseID + } + + _worldPacket.WriteUInt32(VisibleMapIDs.Count * 2); + foreach (uint map in VisibleMapIDs) + _worldPacket.WriteUInt16(map); // Active terrain swap map id + + _worldPacket.WriteUInt32(PreloadMapIDs.Count * 2); + foreach (uint map in PreloadMapIDs) + _worldPacket.WriteUInt16(map); // Inactive terrain swap map id + + _worldPacket.WriteUInt32(UiWorldMapAreaIDSwaps.Count * 2); + foreach (uint map in UiWorldMapAreaIDSwaps) + _worldPacket.WriteUInt16(map); // UI map id, WorldMapArea.dbc, controls map display + } + + public ObjectGuid ClientGUID; + public ObjectGuid PersonalGUID; + public List PhaseShifts = new List(); + public List PreloadMapIDs = new List(); + public List UiWorldMapAreaIDSwaps = new List(); + public List VisibleMapIDs = new List(); + } + + public class ZoneUnderAttack : ServerPacket + { + public ZoneUnderAttack() : base(ServerOpcodes.ZoneUnderAttack, ConnectionType.Instance) { } + + public override void Write() + { + _worldPacket.WriteInt32(AreaID); + } + + public int AreaID; + } + + class DurabilityDamageDeath : ServerPacket + { + public DurabilityDamageDeath() : base(ServerOpcodes.DurabilityDamageDeath) { } + + public override void Write() + { + _worldPacket.WriteUInt32(Percent); + } + + public uint Percent; + } + + class ObjectUpdateFailed : ClientPacket + { + public ObjectUpdateFailed(WorldPacket packet) : base(packet) { } + + public override void Read() + { + ObjectGUID = _worldPacket.ReadPackedGuid(); + } + + public ObjectGuid ObjectGUID; + } + + class ObjectUpdateRescued : ClientPacket + { + public ObjectUpdateRescued(WorldPacket packet) : base(packet) { } + + public override void Read() + { + ObjectGUID = _worldPacket.ReadPackedGuid(); + } + + public ObjectGuid ObjectGUID; + } + + class PlayObjectSound : ServerPacket + { + public PlayObjectSound() : base(ServerOpcodes.PlayObjectSound) { } + + public override void Write() + { + _worldPacket.WriteUInt32(SoundKitID); + _worldPacket.WritePackedGuid(SourceObjectGUID); + _worldPacket.WritePackedGuid(TargetObjectGUID); + _worldPacket.WriteVector3(Position); + } + + ObjectGuid TargetObjectGUID; + ObjectGuid SourceObjectGUID; + uint SoundKitID; + Vector3 Position; + } + + class PlaySound : ServerPacket + { + public PlaySound(ObjectGuid sourceObjectGuid, uint soundKitID) : base(ServerOpcodes.PlaySound) + { + SourceObjectGuid = sourceObjectGuid; + SoundKitID = soundKitID; + } + + public override void Write() + { + _worldPacket.WriteUInt32(SoundKitID); + _worldPacket.WritePackedGuid(SourceObjectGuid); + } + + ObjectGuid SourceObjectGuid; + uint SoundKitID; + } + + class PlaySpeakerBoxSound : ServerPacket + { + public PlaySpeakerBoxSound(ObjectGuid sourceObjectGuid, uint soundKitID) : base(ServerOpcodes.PlaySpeakerbotSound) + { + SourceObjectGUID = sourceObjectGuid; + SoundKitID = soundKitID; + } + + public override void Write() + { + _worldPacket.WritePackedGuid(SourceObjectGUID); + _worldPacket.WriteUInt32(SoundKitID); + } + + public ObjectGuid SourceObjectGUID; + public uint SoundKitID; + } + + class OpeningCinematic : ClientPacket + { + public OpeningCinematic(WorldPacket packet) : base(packet) { } + + public override void Read() { } + } + + class CompleteCinematic : ClientPacket + { + public CompleteCinematic(WorldPacket packet) : base(packet) { } + + public override void Read() { } + } + + class NextCinematicCamera : ClientPacket + { + public NextCinematicCamera(WorldPacket packet) : base(packet) { } + + public override void Read() { } + } + + class CompleteMovie : ClientPacket + { + public CompleteMovie(WorldPacket packet) : base(packet) { } + + public override void Read() { } + } + + class FarSight : ClientPacket + { + public FarSight(WorldPacket packet) : base(packet) { } + + public override void Read() + { + Enable = _worldPacket.HasBit(); + } + + public bool Enable; + } + + class Dismount : ServerPacket + { + public Dismount() : base(ServerOpcodes.Dismount) { } + + public override void Write() + { + _worldPacket.WritePackedGuid(Guid); + } + + public ObjectGuid Guid; + } + + class SaveCUFProfiles : ClientPacket + { + public SaveCUFProfiles(WorldPacket packet) : base(packet) { } + + public override void Read() + { + uint count = _worldPacket.ReadUInt32(); + for (byte i = 0; i < count && i < PlayerConst.MaxCUFProfiles; i++) + { + CUFProfile cufProfile = new CUFProfile(); + + byte strLen = _worldPacket.ReadBits(7); + + // Bool Options + for (byte option = 0; option < (int)CUFBoolOptions.BoolOptionsCount; option++) + cufProfile.BoolOptions.Set(option, _worldPacket.HasBit()); + + // Other Options + cufProfile.FrameHeight = _worldPacket.ReadUInt16(); + cufProfile.FrameWidth = _worldPacket.ReadUInt16(); + + cufProfile.SortBy = _worldPacket.ReadUInt8(); + cufProfile.HealthText = _worldPacket.ReadUInt8(); + + cufProfile.TopPoint = _worldPacket.ReadUInt8(); + cufProfile.BottomPoint = _worldPacket.ReadUInt8(); + cufProfile.LeftPoint = _worldPacket.ReadUInt8(); + + cufProfile.TopOffset = _worldPacket.ReadUInt16(); + cufProfile.BottomOffset = _worldPacket.ReadUInt16(); + cufProfile.LeftOffset = _worldPacket.ReadUInt16(); + + cufProfile.ProfileName = _worldPacket.ReadString(strLen); + + CUFProfiles.Add(cufProfile); + } + } + + public List CUFProfiles = new List(); + } + + class LoadCUFProfiles : ServerPacket + { + public LoadCUFProfiles() : base(ServerOpcodes.LoadCufProfiles, ConnectionType.Instance) { } + + public override void Write() + { + _worldPacket.WriteUInt32(CUFProfiles.Count); + + foreach (CUFProfile cufProfile in CUFProfiles) + { + _worldPacket.WriteBits(cufProfile.ProfileName.Length, 7); + + // Bool Options + for (byte option = 0; option < (int)CUFBoolOptions.BoolOptionsCount; option++) + _worldPacket.WriteBit(cufProfile.BoolOptions[option]); + + // Other Options + _worldPacket.WriteUInt16(cufProfile.FrameHeight); + _worldPacket.WriteUInt16(cufProfile.FrameWidth); + + _worldPacket.WriteUInt8(cufProfile.SortBy); + _worldPacket.WriteUInt8(cufProfile.HealthText); + + _worldPacket.WriteUInt8(cufProfile.TopPoint); + _worldPacket.WriteUInt8(cufProfile.BottomPoint); + _worldPacket.WriteUInt8(cufProfile.LeftPoint); + + _worldPacket.WriteUInt16(cufProfile.TopOffset); + _worldPacket.WriteUInt16(cufProfile.BottomOffset); + _worldPacket.WriteUInt16(cufProfile.LeftOffset); + + _worldPacket.WriteString(cufProfile.ProfileName); + } + } + + public List CUFProfiles = new List(); + } + + class PlayOneShotAnimKit : ServerPacket + { + public PlayOneShotAnimKit() : base(ServerOpcodes.PlayOneShotAnimKit) { } + + public override void Write() + { + _worldPacket.WritePackedGuid(Unit); + _worldPacket.WriteUInt16(AnimKitID); + } + + public ObjectGuid Unit; + public ushort AnimKitID; + } + + class SetAIAnimKit : ServerPacket + { + public SetAIAnimKit() : base(ServerOpcodes.SetAiAnimKit, ConnectionType.Instance) { } + + public override void Write() + { + _worldPacket.WritePackedGuid(Unit); + _worldPacket.WriteUInt16(AnimKitID); + } + + public ObjectGuid Unit; + public ushort AnimKitID; + } + + class SetMeleeAnimKit : ServerPacket + { + public SetMeleeAnimKit() : base(ServerOpcodes.SetMeleeAnimKit, ConnectionType.Instance) { } + + public override void Write() + { + _worldPacket.WritePackedGuid(Unit); + _worldPacket.WriteUInt16(AnimKitID); + } + + public ObjectGuid Unit; + public ushort AnimKitID; + } + + class SetMovementAnimKit : ServerPacket + { + public SetMovementAnimKit() : base(ServerOpcodes.SetMovementAnimKit, ConnectionType.Instance) { } + + public override void Write() + { + _worldPacket.WritePackedGuid(Unit); + _worldPacket.WriteUInt16(AnimKitID); + } + + public ObjectGuid Unit; + public ushort AnimKitID; + } + + class SetPlayHoverAnim : ServerPacket + { + public SetPlayHoverAnim() : base(ServerOpcodes.SetPlayHoverAnim, ConnectionType.Instance) { } + + public override void Write() + { + _worldPacket.WritePackedGuid(UnitGUID); + _worldPacket.WriteBit(PlayHoverAnim); + _worldPacket.FlushBits(); + } + + public ObjectGuid UnitGUID; + public bool PlayHoverAnim; + } + + class TogglePvP : ClientPacket + { + public TogglePvP(WorldPacket packet) : base(packet) { } + + public override void Read() { } + } + + class SetPvP : ClientPacket + { + public SetPvP(WorldPacket packet) : base(packet) { } + + public override void Read() + { + EnablePVP = _worldPacket.HasBit(); + } + + public bool EnablePVP; + } + + class AccountHeirloomUpdate : ServerPacket + { + public AccountHeirloomUpdate() : base(ServerOpcodes.AccountHeirloomUpdate, ConnectionType.Instance) { } + + public override void Write() + { + _worldPacket.WriteBit(IsFullUpdate); + _worldPacket.FlushBits(); + + _worldPacket.WriteInt32(Unk); + + // both lists have to have the same size + _worldPacket.WriteInt32(Heirlooms.Count); + _worldPacket.WriteInt32(Heirlooms.Count); + + foreach (var item in Heirlooms) + _worldPacket.WriteUInt32(item.Key); + + foreach (var flags in Heirlooms) + _worldPacket.WriteUInt32(flags.Value.flags); + } + + public bool IsFullUpdate; + public Dictionary Heirlooms = new Dictionary(); + int Unk; + } + + class MountSpecial : ClientPacket + { + public MountSpecial(WorldPacket packet) : base(packet) { } + + public override void Read() { } + } + + class SpecialMountAnim : ServerPacket + { + public SpecialMountAnim() : base(ServerOpcodes.SpecialMountAnim, ConnectionType.Instance) { } + + public override void Write() + { + _worldPacket.WritePackedGuid(UnitGUID); + } + + public ObjectGuid UnitGUID; + } + + class CrossedInebriationThreshold : ServerPacket + { + public CrossedInebriationThreshold() : base(ServerOpcodes.CrossedInebriationThreshold) { } + + public override void Write() + { + _worldPacket.WritePackedGuid(Guid); + _worldPacket.WriteUInt32(Threshold); + _worldPacket.WriteUInt32(ItemID); + } + + public ObjectGuid Guid; + public uint ItemID; + public uint Threshold; + } + + class SetTaxiBenchmarkMode : ClientPacket + { + public SetTaxiBenchmarkMode(WorldPacket packet) : base(packet) { } + + public override void Read() + { + Enable = _worldPacket.HasBit(); + } + + public bool Enable; + } + + class OverrideLight : ServerPacket + { + public OverrideLight() : base(ServerOpcodes.OverrideLight) { } + + public override void Write() + { + _worldPacket.WriteUInt32(AreaLightID); + _worldPacket.WriteUInt32(OverrideLightID); + _worldPacket.WriteUInt32(TransitionMilliseconds); + } + + public uint AreaLightID; + public uint TransitionMilliseconds; + public uint OverrideLightID; + } + + public class StartTimer : ServerPacket + { + public StartTimer() : base(ServerOpcodes.StartTimer) { } + + public override void Write() + { + _worldPacket.WriteUInt32(Type); + _worldPacket.WriteUInt32(TimeRemaining); + _worldPacket.WriteUInt32(TotalTime); + } + + public uint TimeRemaining; + public uint TotalTime; + public TimerType Type; + } + + class DisplayGameError : ServerPacket + { + public DisplayGameError(GameError error) : base(ServerOpcodes.DisplayGameError) + { + Error = error; + } + + public DisplayGameError(GameError error, int arg) : this(error) + { + Arg.Set(arg); + } + public DisplayGameError(GameError error, int arg1, int arg2) : this(error) + { + Arg.Set(arg1); + Arg2.Set(arg2); + } + + public override void Write() + { + _worldPacket.WriteUInt32(Error); + _worldPacket.WriteBit(Arg.HasValue); + _worldPacket.WriteBit(Arg2.HasValue); + _worldPacket.FlushBits(); + + if (Arg.HasValue) + _worldPacket.WriteInt32(Arg.Value); + + if (Arg2.HasValue) + _worldPacket.WriteInt32(Arg2.Value); + } + + GameError Error; + Optional Arg; + Optional Arg2; + } + + class AccountMountUpdate : ServerPacket + { + public AccountMountUpdate() : base(ServerOpcodes.AccountMountUpdate, ConnectionType.Instance) { } + + public override void Write() + { + _worldPacket.WriteBit(IsFullUpdate); + _worldPacket.WriteUInt32(Mounts.Count); + + foreach (var spell in Mounts) + { + _worldPacket.WriteUInt32(spell.Key); + _worldPacket.WriteBits(spell.Value, 2); + } + + _worldPacket.FlushBits(); + } + + public bool IsFullUpdate = false; + public Dictionary Mounts = new Dictionary(); + } + + class MountSetFavorite : ClientPacket + { + public MountSetFavorite(WorldPacket packet) : base(packet) { } + + public override void Read() + { + MountSpellID = _worldPacket.ReadUInt32(); + IsFavorite = _worldPacket.HasBit(); + } + + public uint MountSpellID; + public bool IsFavorite; + } + + class PvpPrestigeRankUp : ClientPacket + { + public PvpPrestigeRankUp(WorldPacket packet) : base(packet) { } + + public override void Read() { } + } +} diff --git a/Game/Network/Packets/MovementPackets.cs b/Game/Network/Packets/MovementPackets.cs new file mode 100644 index 000000000..b0f30ea84 --- /dev/null +++ b/Game/Network/Packets/MovementPackets.cs @@ -0,0 +1,1254 @@ +/* + * Copyright (C) 2012-2017 CypherCore + * + * 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, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +using Framework.Constants; +using Framework.Dynamic; +using Framework.GameMath; +using Game.Entities; +using Game.Movement; +using System.Collections.Generic; +using System.Linq; + +namespace Game.Network.Packets +{ + public static class MovementExtensions + { + public static void ReadTransportInfo(WorldPacket data, ref MovementInfo.TransportInfo transportInfo) + { + transportInfo.guid = data.ReadPackedGuid(); // Transport Guid + transportInfo.pos.posX = data.ReadFloat(); + transportInfo.pos.posY = data.ReadFloat(); + transportInfo.pos.posZ = data.ReadFloat(); + transportInfo.pos.Orientation = data.ReadFloat(); + transportInfo.seat = data.ReadInt8(); // VehicleSeatIndex + transportInfo.time = data.ReadUInt32(); // MoveTime + + bool hasPrevTime = data.HasBit(); + bool hasVehicleId = data.HasBit(); + + if (hasPrevTime) + transportInfo.prevTime = data.ReadUInt32(); // PrevMoveTime + + if (hasVehicleId) + transportInfo.vehicleId = data.ReadUInt32(); // VehicleRecID + } + + public static void WriteTransportInfo(WorldPacket data, MovementInfo.TransportInfo transportInfo) + { + bool hasPrevTime = transportInfo.prevTime != 0; + bool hasVehicleId = transportInfo.vehicleId != 0; + + data.WritePackedGuid(transportInfo.guid); // Transport Guid + data.WriteFloat(transportInfo.pos.GetPositionX()); + data.WriteFloat(transportInfo.pos.GetPositionY()); + data.WriteFloat(transportInfo.pos.GetPositionZ()); + data.WriteFloat(transportInfo.pos.GetOrientation()); + data.WriteInt8(transportInfo.seat); // VehicleSeatIndex + data.WriteUInt32(transportInfo.time); // MoveTime + + data.WriteBit(hasPrevTime); + data.WriteBit(hasVehicleId); + data.FlushBits(); + + if (hasPrevTime) + data.WriteUInt32(transportInfo.prevTime); // PrevMoveTime + + if (hasVehicleId) + data.WriteUInt32(transportInfo.vehicleId); // VehicleRecID + } + + public static MovementInfo ReadMovementInfo(WorldPacket data) + { + var movementInfo = new MovementInfo(); + movementInfo.Guid = data.ReadPackedGuid(); + movementInfo.Time = data.ReadUInt32(); + movementInfo.Pos.posX = data.ReadFloat(); + movementInfo.Pos.posY = data.ReadFloat(); + movementInfo.Pos.posZ = data.ReadFloat(); + movementInfo.Pos.Orientation = data.ReadFloat(); + movementInfo.Pitch = data.ReadFloat(); + movementInfo.SplineElevation = data.ReadFloat(); + + uint removeMovementForcesCount = data.ReadUInt32(); + + uint moveIndex = data.ReadUInt32(); + + for (uint i = 0; i < removeMovementForcesCount; ++i) + { + ObjectGuid guid = data.ReadPackedGuid(); + } + + // ResetBitReader + + movementInfo.SetMovementFlags((MovementFlag)data.ReadBits(30)); + movementInfo.SetMovementFlags2((MovementFlag2)data.ReadBits(18)); + + bool hasTransport = data.HasBit(); + bool hasFall = data.HasBit(); + bool hasSpline = data.HasBit(); // todo 6.x read this infos + + data.ReadBit(); // HeightChangeFailed + data.ReadBit(); // RemoteTimeValid + + if (hasTransport) + ReadTransportInfo(data, ref movementInfo.transport); + + if (hasFall) + { + movementInfo.jump.fallTime = data.ReadUInt32(); + movementInfo.jump.zspeed = data.ReadFloat(); + + // ResetBitReader + + bool hasFallDirection = data.HasBit(); + if (hasFallDirection) + { + movementInfo.jump.sinAngle = data.ReadFloat(); + movementInfo.jump.cosAngle = data.ReadFloat(); + movementInfo.jump.xyspeed = data.ReadFloat(); + } + } + + return movementInfo; + } + + public static void WriteMovementInfo(WorldPacket data, MovementInfo movementInfo) + { + bool hasTransportData = !movementInfo.transport.guid.IsEmpty(); + bool hasFallDirection = movementInfo.HasMovementFlag(MovementFlag.Falling | MovementFlag.FallingFar); + bool hasFallData = hasFallDirection || movementInfo.jump.fallTime != 0; + bool hasSpline = false; // todo 6.x send this infos + + data.WritePackedGuid(movementInfo.Guid); + data.WriteUInt32(movementInfo.Time); + data.WriteFloat(movementInfo.Pos.GetPositionX()); + data.WriteFloat(movementInfo.Pos.GetPositionY()); + data.WriteFloat(movementInfo.Pos.GetPositionZ()); + data.WriteFloat(movementInfo.Pos.GetOrientation()); + data.WriteFloat(movementInfo.Pitch); + data.WriteFloat(movementInfo.SplineElevation); + + uint removeMovementForcesCount = 0; + data.WriteUInt32(removeMovementForcesCount); + + uint int168 = 0; + data.WriteUInt32(int168); + + /*for (public uint i = 0; i < removeMovementForcesCount; ++i) + { + _worldPacket << ObjectGuid; + }*/ + + data.WriteBits((uint)movementInfo.GetMovementFlags(), 30); + data.WriteBits((uint)movementInfo.GetMovementFlags2(), 18); + + data.WriteBit(hasTransportData); + data.WriteBit(hasFallData); + data.WriteBit(hasSpline); + data.WriteBit(0); // HeightChangeFailed + data.WriteBit(0); // RemoteTimeValid + data.FlushBits(); + + if (hasTransportData) + WriteTransportInfo(data, movementInfo.transport); + + if (hasFallData) + { + data.WriteUInt32(movementInfo.jump.fallTime); + data.WriteFloat(movementInfo.jump.zspeed); + + data.WriteBit(hasFallDirection); + data.FlushBits(); + if (hasFallDirection) + { + data.WriteFloat(movementInfo.jump.cosAngle); + data.WriteFloat(movementInfo.jump.sinAngle); + data.WriteFloat(movementInfo.jump.xyspeed); + } + } + } + + public static void WriteCreateObjectSplineDataBlock(MoveSpline moveSpline, WorldPacket data) + { + data.WriteUInt32(moveSpline.GetId()); // ID + + if (!moveSpline.isCyclic()) // Destination + data.WriteVector3(moveSpline.FinalDestination()); + else + data.WriteVector3(Vector3.Zero); + + bool hasSplineMove = data.WriteBit(!moveSpline.Finalized() && !moveSpline.splineIsFacingOnly); + data.FlushBits(); + + if (hasSplineMove) + { + data.WriteUInt32((uint)moveSpline.splineflags.Flags); // SplineFlags + data.WriteInt32(moveSpline.timePassed()); // Elapsed + data.WriteUInt32(moveSpline.Duration()); // Duration + data.WriteFloat(1.0f); // DurationModifier + data.WriteFloat(1.0f); // NextDurationModifier + data.WriteBits((byte)moveSpline.facing.type, 2); // Face + bool HasJumpGravity = data.WriteBit(moveSpline.splineflags.hasFlag(SplineFlag.Parabolic | SplineFlag.Animation)); // HasJumpGravity + bool HasSpecialTime = data.WriteBit(moveSpline.splineflags.hasFlag(SplineFlag.Parabolic) && moveSpline.effect_start_time < moveSpline.Duration()); // HasSpecialTime + data.WriteBits(moveSpline.getPath().Length, 16); + data.WriteBits((byte)moveSpline.spline.m_mode, 2); // Mode + data.WriteBit(0); // HasSplineFilter + data.WriteBit(moveSpline.spell_effect_extra.HasValue); // HasSpellEffectExtraData + + //if (HasSplineFilterKey) + //{ + // data << uint32(FilterKeysCount); + // for (var i = 0; i < FilterKeysCount; ++i) + // { + // data << float(In); + // data << float(Out); + // } + + // data.WriteBits(FilterFlags, 2); + // data.FlushBits(); + //} + + switch (moveSpline.facing.type) + { + case MonsterMoveType.FacingSpot: + data.WriteVector3(moveSpline.facing.f); // FaceSpot + break; + case MonsterMoveType.FacingTarget: + data.WritePackedGuid(moveSpline.facing.target); // FaceGUID + break; + case MonsterMoveType.FacingAngle: + data.WriteFloat(moveSpline.facing.angle); // FaceDirection + break; + } + + if (HasJumpGravity) + data.WriteFloat(moveSpline.vertical_acceleration); // JumpGravity + + if (HasSpecialTime) + data.WriteUInt32(moveSpline.effect_start_time); // SpecialTime + + foreach (var vec in moveSpline.getPath()) + data.WriteVector3(vec); + + if (moveSpline.spell_effect_extra.HasValue) + { + data.WritePackedGuid(moveSpline.spell_effect_extra.Value.Target); + data.WriteUInt32(moveSpline.spell_effect_extra.Value.SpellVisualId); + data.WriteUInt32(moveSpline.spell_effect_extra.Value.ProgressCurveId); + data.WriteUInt32(moveSpline.spell_effect_extra.Value.ParabolicCurveId); + } + } + } + + public static void WriteCreateObjectAreaTriggerSpline(Spline spline, WorldPacket data) + { + data.WriteBits(spline.getPoints().Length, 16); + foreach (var point in spline.getPoints()) + data.WriteVector3(point); + } + } + + public class ClientPlayerMovement : ClientPacket + { + public ClientPlayerMovement(WorldPacket packet) : base(packet) { } + + public override void Read() + { + movementInfo = MovementExtensions.ReadMovementInfo(_worldPacket); + } + + public MovementInfo movementInfo; + } + + public class MoveUpdate : ServerPacket + { + public MoveUpdate() : base(ServerOpcodes.MoveUpdate, ConnectionType.Instance) { } + + public override void Write() + { + MovementExtensions.WriteMovementInfo(_worldPacket, movementInfo); + } + + public MovementInfo movementInfo; + } + + public class MonsterMove : ServerPacket + { + public MonsterMove() : base(ServerOpcodes.OnMonsterMove, ConnectionType.Instance) + { + SplineData = new MovementMonsterSpline(); + } + + public void InitializeSplineData(MoveSpline moveSpline) + { + SplineData.ID = moveSpline.GetId(); + MovementSpline movementSpline = SplineData.Move; + + MoveSplineFlag splineFlags = moveSpline.splineflags; + splineFlags.SetUnsetFlag(SplineFlag.Cyclic, moveSpline.isCyclic()); + movementSpline.Flags = (uint)(splineFlags.Flags & ~SplineFlag.MaskNoMonsterMove); + movementSpline.Face = moveSpline.facing.type; + movementSpline.FaceDirection = moveSpline.facing.angle; + movementSpline.FaceGUID = moveSpline.facing.target; + movementSpline.FaceSpot = moveSpline.facing.f; + + if (splineFlags.hasFlag(SplineFlag.Animation)) + { + movementSpline.AnimTier = splineFlags.getAnimationId(); + movementSpline.TierTransStartTime = (uint)moveSpline.effect_start_time; + } + + movementSpline.MoveTime = (uint)moveSpline.Duration(); + + if (splineFlags.hasFlag(SplineFlag.Parabolic)) + { + movementSpline.JumpGravity = moveSpline.vertical_acceleration; + movementSpline.SpecialTime = (uint)moveSpline.effect_start_time; + } + + if (splineFlags.hasFlag(SplineFlag.FadeObject)) + movementSpline.SpecialTime = (uint)moveSpline.effect_start_time; + + if (moveSpline.spell_effect_extra.HasValue) + { + movementSpline.SpellEffectExtraData.HasValue = true; + movementSpline.SpellEffectExtraData.Value.TargetGuid = moveSpline.spell_effect_extra.Value.Target; + movementSpline.SpellEffectExtraData.Value.SpellVisualID = moveSpline.spell_effect_extra.Value.SpellVisualId; + movementSpline.SpellEffectExtraData.Value.ProgressCurveID = moveSpline.spell_effect_extra.Value.ProgressCurveId; + movementSpline.SpellEffectExtraData.Value.ParabolicCurveID = moveSpline.spell_effect_extra.Value.ParabolicCurveId; + } + + Spline spline = moveSpline.spline; + Vector3[] array = spline.getPoints(); + + if (splineFlags.hasFlag(SplineFlag.UncompressedPath)) + { + if (!splineFlags.hasFlag(SplineFlag.Cyclic)) + { + int count = spline.getPointCount() - 3; + for (uint i = 0; i < count; ++i) + movementSpline.Points.Add(array[i + 2]); + } + else + { + int count = spline.getPointCount() - 3; + movementSpline.Points.Add(array[1]); + for (uint i = 0; i < count; ++i) + movementSpline.Points.Add(array[i + 1]); + } + } + else + { + int lastIdx = spline.getPointCount() - 3; + Vector3[] realPath = spline.getPoints().Skip(1).ToArray(); + + movementSpline.Points.Add(realPath[lastIdx]); + + if (lastIdx > 1) + { + Vector3 middle = (realPath[0] + realPath[lastIdx]) / 2.0f; + + // first and last points already appended + for (uint i = 1; i < lastIdx; ++i) + movementSpline.PackedDeltas.Add(middle - realPath[i]); + } + } + } + + public override void Write() + { + _worldPacket.WritePackedGuid(MoverGUID); + _worldPacket.WriteVector3(Pos); + SplineData.Write(_worldPacket); + } + + public MovementMonsterSpline SplineData; + public ObjectGuid MoverGUID; + public Vector3 Pos; + } + + public class MoveSplineSetSpeed : ServerPacket + { + public MoveSplineSetSpeed(ServerOpcodes opcode) : base(opcode, ConnectionType.Instance) { } + + public override void Write() + { + _worldPacket.WritePackedGuid(MoverGUID); + _worldPacket.WriteFloat(Speed); + } + + public ObjectGuid MoverGUID; + public float Speed = 1.0f; + } + + public class MoveSetSpeed : ServerPacket + { + public MoveSetSpeed(ServerOpcodes opcode) : base(opcode, ConnectionType.Instance) { } + + public override void Write() + { + _worldPacket.WritePackedGuid(MoverGUID); + _worldPacket.WriteUInt32(SequenceIndex); + _worldPacket.WriteFloat(Speed); + } + + public ObjectGuid MoverGUID; + public uint SequenceIndex = 0; // Unit movement packet index, incremented each time + public float Speed = 1.0f; + } + + public class MoveUpdateSpeed : ServerPacket + { + public MoveUpdateSpeed(ServerOpcodes opcode) : base(opcode, ConnectionType.Instance) { } + + public override void Write() + { + MovementExtensions.WriteMovementInfo(_worldPacket, movementInfo); + _worldPacket.WriteFloat(Speed); + } + + public MovementInfo movementInfo; + public float Speed = 1.0f; + } + + public class MoveSplineSetFlag : ServerPacket + { + public MoveSplineSetFlag(ServerOpcodes opcode) : base(opcode, ConnectionType.Instance) { } + + public override void Write() + { + _worldPacket.WritePackedGuid(MoverGUID); + } + + public ObjectGuid MoverGUID; + } + + public class MoveSetFlag : ServerPacket + { + public MoveSetFlag(ServerOpcodes opcode) : base(opcode, ConnectionType.Instance) { } + + public override void Write() + { + _worldPacket.WritePackedGuid(MoverGUID); + _worldPacket.WriteUInt32(SequenceIndex); + } + + public ObjectGuid MoverGUID; + public uint SequenceIndex = 0; // Unit movement packet index, incremented each time + } + + public class TransferPending : ServerPacket + { + public TransferPending() : base(ServerOpcodes.TransferPending) + { + Ship = new Optional(); + TransferSpellID = new Optional(); + } + + public override void Write() + { + _worldPacket.WriteInt32(MapID); + _worldPacket.WriteBit(Ship.HasValue); + _worldPacket.WriteBit(TransferSpellID.HasValue); + if (Ship.HasValue) + { + _worldPacket.WriteUInt32(Ship.Value.ID); + _worldPacket.WriteInt32(Ship.Value.OriginMapID); + } + + if (TransferSpellID.HasValue) + _worldPacket.WriteInt32(TransferSpellID.Value); + + _worldPacket.FlushBits(); + } + + public int MapID = -1; + public Optional Ship; + public Optional TransferSpellID; + + public struct ShipTransferPending + { + public uint ID; // gameobject_template.entry of the transport the player is teleporting on + public int OriginMapID; // Map id the player is currently on (before teleport) + } + } + + public class TransferAborted : ServerPacket + { + public TransferAborted() : base(ServerOpcodes.TransferAborted) { } + + public override void Write() + { + _worldPacket.WriteUInt32(MapID); + _worldPacket.WriteUInt8(Arg); + _worldPacket.WriteInt32(MapDifficultyXConditionID); + _worldPacket.WriteBits(TransfertAbort, 5); + _worldPacket.FlushBits(); + } + + public uint MapID; + public byte Arg; + public int MapDifficultyXConditionID; + public TransferAbortReason TransfertAbort; + } + + public class NewWorld : ServerPacket + { + public NewWorld() : base(ServerOpcodes.NewWorld) { } + + public override void Write() + { + _worldPacket.WriteUInt32(MapID); + _worldPacket.WriteXYZO(Pos); + _worldPacket.WriteUInt32(Reason); + _worldPacket.WriteXYZ(MovementOffset); + } + + public uint MapID; + public uint Reason; + public Position Pos; + public Position MovementOffset; // Adjusts all pending movement events by this offset + } + + public class WorldPortResponse : ClientPacket + { + public WorldPortResponse(WorldPacket packet) : base(packet) { } + + public override void Read() { } + } + + public class MoveTeleport : ServerPacket + { + public MoveTeleport() : base(ServerOpcodes.MoveTeleport, ConnectionType.Instance) { } + + public override void Write() + { + _worldPacket.WritePackedGuid(MoverGUID); + _worldPacket.WriteUInt32(SequenceIndex); + _worldPacket.WriteXYZ(Pos); + _worldPacket.WriteFloat(Facing); + _worldPacket.WriteUInt8(PreloadWorld); + + _worldPacket.WriteBit(Vehicle.HasValue); + _worldPacket.WriteBit(TransportGUID.HasValue); + _worldPacket.FlushBits(); + + if (Vehicle.HasValue) + { + _worldPacket.WriteUInt8(Vehicle.Value.VehicleSeatIndex); + _worldPacket.WriteBit(Vehicle.Value.VehicleExitVoluntary); + _worldPacket.WriteBit(Vehicle.Value.VehicleExitTeleport); + _worldPacket.FlushBits(); + } + + if (TransportGUID.HasValue) + _worldPacket.WritePackedGuid(TransportGUID.Value); + } + + public Position Pos; + public Optional Vehicle; + public uint SequenceIndex; + public ObjectGuid MoverGUID; + public Optional TransportGUID; + public float Facing; + public byte PreloadWorld; + } + + public class MoveUpdateTeleport : ServerPacket + { + public MoveUpdateTeleport() : base(ServerOpcodes.MoveUpdateTeleport) { } + + public override void Write() + { + MovementExtensions.WriteMovementInfo(_worldPacket, movementInfo); + + _worldPacket.WriteInt32(MovementForces.Count); + _worldPacket.WriteBit(WalkSpeed.HasValue); + _worldPacket.WriteBit(RunSpeed.HasValue); + _worldPacket.WriteBit(RunBackSpeed.HasValue); + _worldPacket.WriteBit(SwimSpeed.HasValue); + _worldPacket.WriteBit(SwimBackSpeed.HasValue); + _worldPacket.WriteBit(FlightSpeed.HasValue); + _worldPacket.WriteBit(FlightBackSpeed.HasValue); + _worldPacket.WriteBit(TurnRate.HasValue); + _worldPacket.WriteBit(PitchRate.HasValue); + _worldPacket.FlushBits(); + + foreach (MovementForce force in MovementForces) + force.Write(_worldPacket); + + if (WalkSpeed.HasValue) + _worldPacket.WriteFloat(WalkSpeed.Value); + + if (RunSpeed.HasValue) + _worldPacket.WriteFloat(RunSpeed.Value); + + if (RunBackSpeed.HasValue) + _worldPacket.WriteFloat(RunBackSpeed.Value); + + if (SwimSpeed.HasValue) + _worldPacket.WriteFloat(SwimSpeed.Value); + + if (SwimBackSpeed.HasValue) + _worldPacket.WriteFloat(SwimBackSpeed.Value); + + if (FlightSpeed.HasValue) + _worldPacket.WriteFloat(FlightSpeed.Value); + + if (FlightBackSpeed.HasValue) + _worldPacket.WriteFloat(FlightBackSpeed.Value); + + if (TurnRate.HasValue) + _worldPacket.WriteFloat(TurnRate.Value); + + if (PitchRate.HasValue) + _worldPacket.WriteFloat(PitchRate.Value); + } + + public MovementInfo movementInfo; + public List MovementForces = new List(); + public Optional SwimBackSpeed; + public Optional FlightSpeed; + public Optional SwimSpeed; + public Optional WalkSpeed; + public Optional TurnRate; + public Optional RunSpeed; + public Optional FlightBackSpeed; + public Optional RunBackSpeed; + public Optional PitchRate; + } + + class MoveUpdateApplyMovementForce : ServerPacket + { + public MoveUpdateApplyMovementForce() : base(ServerOpcodes.MoveUpdateApplyMovementForce) { } + + public override void Write() + { + MovementExtensions.WriteMovementInfo(_worldPacket, movementInfo); + Force.Write(_worldPacket); + } + + MovementInfo movementInfo = new MovementInfo(); + MovementForce Force = new MovementForce(); + } + + class MoveUpdateRemoveMovementForce : ServerPacket + { + public MoveUpdateRemoveMovementForce() : base(ServerOpcodes.MoveUpdateRemoveMovementForce) { } + + public override void Write() + { + MovementExtensions.WriteMovementInfo(_worldPacket, movementInfo); + _worldPacket.WritePackedGuid(TriggerGUID); + } + + MovementInfo movementInfo = new MovementInfo(); + ObjectGuid TriggerGUID; + } + + class MoveTeleportAck : ClientPacket + { + public MoveTeleportAck(WorldPacket packet) : base(packet) { } + + public override void Read() + { + MoverGUID = _worldPacket.ReadPackedGuid(); + AckIndex = _worldPacket.ReadInt32(); + MoveTime = _worldPacket.ReadInt32(); + } + + public ObjectGuid MoverGUID; + int AckIndex = 0; + int MoveTime = 0; + } + + public class MovementAckMessage : ClientPacket + { + public MovementAckMessage(WorldPacket packet) : base(packet) { } + + public override void Read() + { + Ack.Read(_worldPacket); + } + + public MovementAck Ack; + } + + public class MovementSpeedAck : ClientPacket + { + public MovementSpeedAck(WorldPacket packet) : base(packet) { } + + public override void Read() + { + Ack.Read(_worldPacket); + Speed = _worldPacket.ReadFloat(); + } + + public MovementAck Ack; + public float Speed; + } + + public class SetActiveMover : ClientPacket + { + public ObjectGuid ActiveMover; + + public SetActiveMover(WorldPacket packet) : base(packet) { } + + public override void Read() + { + ActiveMover = _worldPacket.ReadPackedGuid(); + } + } + + public class MoveSetActiveMover : ServerPacket + { + public ObjectGuid MoverGUID; + + public MoveSetActiveMover() : base(ServerOpcodes.MoveSetActiveMover) { } + + public override void Write() + { + _worldPacket.WritePackedGuid(MoverGUID); + } + } + + class MoveKnockBack : ServerPacket + { + public MoveKnockBack() : base(ServerOpcodes.MoveKnockBack, ConnectionType.Instance) { } + + public override void Write() + { + _worldPacket.WritePackedGuid(MoverGUID); + _worldPacket.WriteUInt32(SequenceIndex); + _worldPacket.WriteVector2(Direction); + Speeds.Write(_worldPacket); + } + + public ObjectGuid MoverGUID; + public Vector2 Direction; + public MoveKnockBackSpeeds Speeds; + public uint SequenceIndex; + } + + public class MoveUpdateKnockBack : ServerPacket + { + public MoveUpdateKnockBack() : base(ServerOpcodes.MoveUpdateKnockBack) { } + + public override void Write() + { + MovementExtensions.WriteMovementInfo(_worldPacket, movementInfo); + } + + public MovementInfo movementInfo; + } + + class MoveKnockBackAck : ClientPacket + { + public MoveKnockBackAck(WorldPacket packet) : base(packet) { } + + public override void Read() + { + Ack.Read(_worldPacket); + if (_worldPacket.HasBit()) + { + Speeds.HasValue = true; + Speeds.Value.Read(_worldPacket); + } + } + + public MovementAck Ack; + public Optional Speeds; + } + + class MoveSetCollisionHeight : ServerPacket + { + public MoveSetCollisionHeight() : base(ServerOpcodes.MoveSetCollisionHeight) { } + + public override void Write() + { + _worldPacket.WritePackedGuid(MoverGUID); + _worldPacket.WriteUInt32(SequenceIndex); + _worldPacket.WriteFloat(Height); + _worldPacket.WriteFloat(Scale); + _worldPacket.WriteUInt32(MountDisplayID); + _worldPacket.WriteInt32(ScaleDuration); + _worldPacket.WriteBits((uint)Reason, 2); + _worldPacket.FlushBits(); + } + + public float Scale = 1.0f; + public ObjectGuid MoverGUID; + public uint MountDisplayID; + public UpdateCollisionHeightReason Reason = UpdateCollisionHeightReason.Mount; + public uint SequenceIndex; + public int ScaleDuration; + public float Height = 1.0f; + } + + public class MoveUpdateCollisionHeight : ServerPacket + { + public MoveUpdateCollisionHeight() : base(ServerOpcodes.MoveUpdateCollisionHeight) { } + + public override void Write() + { + MovementExtensions.WriteMovementInfo(_worldPacket, movementInfo); + _worldPacket.WriteFloat(Height); + _worldPacket.WriteFloat(Scale); + } + + public MovementInfo movementInfo; + public float Scale = 1.0f; + public float Height = 1.0f; + } + + public class MoveSetCollisionHeightAck : ClientPacket + { + public MoveSetCollisionHeightAck(WorldPacket packet) : base(packet) { } + + public override void Read() + { + Data.Read(_worldPacket); + Height = _worldPacket.ReadFloat(); + MountDisplayID = _worldPacket.ReadUInt32(); + Reason = (UpdateCollisionHeightReason)_worldPacket.ReadBits(2); + } + + public MovementAck Data; + public UpdateCollisionHeightReason Reason = UpdateCollisionHeightReason.Mount; + public uint MountDisplayID; + public float Height = 1.0f; + } + + class MoveTimeSkipped : ClientPacket + { + public MoveTimeSkipped(WorldPacket packet) : base(packet) { } + + public override void Read() + { + MoverGUID = _worldPacket.ReadPackedGuid(); + TimeSkipped = _worldPacket.ReadUInt32(); + } + + public ObjectGuid MoverGUID; + public uint TimeSkipped; + } + + class SummonResponse : ClientPacket + { + public SummonResponse(WorldPacket packet) : base(packet) { } + + public override void Read() + { + SummonerGUID = _worldPacket.ReadPackedGuid(); + Accept = _worldPacket.HasBit(); + } + + public bool Accept; + public ObjectGuid SummonerGUID; + } + + public class ControlUpdate : ServerPacket + { + public ControlUpdate() : base(ServerOpcodes.ControlUpdate) { } + + public override void Write() + { + _worldPacket.WritePackedGuid(Guid); + _worldPacket.WriteBit(On); + _worldPacket.FlushBits(); + } + + public bool On; + public ObjectGuid Guid; + } + + class MoveSplineDone : ClientPacket + { + public MoveSplineDone(WorldPacket packet) : base(packet) { } + + public override void Read() + { + movementInfo = MovementExtensions.ReadMovementInfo(_worldPacket); + SplineID = _worldPacket.ReadInt32(); + } + + public MovementInfo movementInfo; + public int SplineID; + } + + class SummonRequest : ServerPacket + { + public SummonRequest() : base(ServerOpcodes.SummonRequest, ConnectionType.Instance) { } + + public override void Write() + { + _worldPacket.WritePackedGuid(SummonerGUID); + _worldPacket.WriteUInt32(SummonerVirtualRealmAddress); + _worldPacket.WriteInt32(AreaID); + _worldPacket.WriteUInt8(Reason); + _worldPacket.WriteBit(SkipStartingArea); + _worldPacket.FlushBits(); + } + + public ObjectGuid SummonerGUID; + public uint SummonerVirtualRealmAddress; + public int AreaID; + public SummonReason Reason; + public bool SkipStartingArea; + + public enum SummonReason + { + Spell = 0, + Scenario = 1 + } + } + + class SuspendToken : ServerPacket + { + public SuspendToken() : base(ServerOpcodes.SuspendToken, ConnectionType.Instance) { } + + public override void Write() + { + _worldPacket.WriteUInt32(SequenceIndex); + _worldPacket.WriteBits(Reason, 2); + _worldPacket.FlushBits(); + } + + public uint SequenceIndex = 1; + public uint Reason = 1; + } + + class SuspendTokenResponse : ClientPacket + { + public SuspendTokenResponse(WorldPacket packet) : base(packet) { } + + public override void Read() + { + SequenceIndex = _worldPacket.ReadUInt32(); + } + + public uint SequenceIndex; + } + + class ResumeToken : ServerPacket + { + public ResumeToken() : base(ServerOpcodes.ResumeToken, ConnectionType.Instance) { } + + public override void Write() + { + _worldPacket.WriteUInt32(SequenceIndex); + _worldPacket.WriteBits(Reason, 2); + _worldPacket.FlushBits(); + } + + public uint SequenceIndex = 1; + public uint Reason = 1; + } + + class MoveSetCompoundState : ServerPacket + { + public MoveSetCompoundState() : base(ServerOpcodes.MoveSetCompoundState, ConnectionType.Instance) { } + + public override void Write() + { + _worldPacket.WritePackedGuid(MoverGUID); + _worldPacket.WriteUInt32(StateChanges.Count); + foreach (MoveStateChange stateChange in StateChanges) + stateChange.Write(_worldPacket); + } + + public ObjectGuid MoverGUID; + public List StateChanges = new List(); + + public struct CollisionHeightInfo + { + public float Height; + public float Scale; + public UpdateCollisionHeightReason Reason; + } + + public struct KnockBackInfo + { + public float HorzSpeed; + public Vector2 Direction; + public float InitVertSpeed; + } + + public class MoveStateChange + { + public MoveStateChange(ServerOpcodes messageId, uint sequenceIndex) + { + MessageID = messageId; + SequenceIndex = sequenceIndex; + } + + public void Write(WorldPacket data) + { + data.WriteUInt16(MessageID); + data.WriteUInt32(SequenceIndex); + data.WriteBit(Speed.HasValue); + data.WriteBit(KnockBack.HasValue); + data.WriteBit(VehicleRecID.HasValue); + data.WriteBit(CollisionHeight.HasValue); + data.WriteBit(MovementForce_.HasValue); + data.WriteBit(Unknown.HasValue); + data.FlushBits(); + + if (CollisionHeight.HasValue) + { + data.WriteFloat(CollisionHeight.Value.Height); + data.WriteFloat(CollisionHeight.Value.Scale); + data.WriteBits(CollisionHeight.Value.Reason, 2); + data.FlushBits(); + } + + if (Speed.HasValue) + data.WriteFloat(Speed.Value); + + if (KnockBack.HasValue) + { + data.WriteFloat(KnockBack.Value.HorzSpeed); + data.WriteVector2(KnockBack.Value.Direction); + data.WriteFloat(KnockBack.Value.InitVertSpeed); + } + + if (VehicleRecID.HasValue) + data.WriteInt32(VehicleRecID.Value); + + if (Unknown.HasValue) + data.WritePackedGuid(Unknown.Value); + + if (MovementForce_.HasValue) + MovementForce_.Value.Write(data); + } + + public ServerOpcodes MessageID; + public uint SequenceIndex; + public Optional Speed; + public Optional KnockBack; + public Optional VehicleRecID; + public Optional CollisionHeight; + public Optional MovementForce_; + public Optional Unknown; + } + } + + //Structs + public struct MonsterSplineFilterKey + { + public void Write(WorldPacket data) + { + data.WriteInt16(Idx); + data.WriteUInt16(Speed); + } + + public short Idx; + public ushort Speed; + } + + public class MonsterSplineFilter + { + public void Write(WorldPacket data) + { + data.WriteUInt32(FilterKeys.Count); + data.WriteFloat(BaseSpeed); + data.WriteInt16(StartOffset); + data.WriteFloat(DistToPrevFilterKey); + data.WriteInt16(AddedToStart); + + FilterKeys.ForEach(p => p.Write(data)); + + data.WriteBits(FilterFlags, 2); + data.FlushBits(); + } + + public List FilterKeys = new List(); + public byte FilterFlags; + public float BaseSpeed; + public short StartOffset; + public float DistToPrevFilterKey; + public short AddedToStart; + } + + public struct MonsterSplineSpellEffectExtraData + { + public void Write(WorldPacket data) + { + data.WritePackedGuid(TargetGuid); + data.WriteUInt32(SpellVisualID); + data.WriteUInt32(ProgressCurveID); + data.WriteUInt32(ParabolicCurveID); + } + + public ObjectGuid TargetGuid; + public uint SpellVisualID; + public uint ProgressCurveID; + public uint ParabolicCurveID; + } + + public class MovementSpline + { + public void Write(WorldPacket data) + { + data.WriteUInt32(Flags); + data.WriteUInt8(AnimTier); + data.WriteUInt32(TierTransStartTime); + data.WriteInt32(Elapsed); + data.WriteUInt32(MoveTime); + data.WriteFloat(JumpGravity); + data.WriteUInt32(SpecialTime); + data.WriteUInt8(Mode); + data.WriteUInt8(VehicleExitVoluntary); + data.WritePackedGuid(TransportGUID); + data.WriteInt8(VehicleSeat); + data.WriteBits((byte)Face, 2); + data.WriteBits(Points.Count, 16); + data.WriteBits(PackedDeltas.Count, 16); + data.WriteBit(SplineFilter.HasValue); + data.WriteBit(SpellEffectExtraData.HasValue); + data.FlushBits(); + + if (SplineFilter.HasValue) + SplineFilter.Value.Write(data); + + switch (Face) + { + case MonsterMoveType.FacingSpot: + data.WriteVector3(FaceSpot); + break; + case MonsterMoveType.FacingTarget: + data.WriteFloat(FaceDirection); + data.WritePackedGuid(FaceGUID); + break; + case MonsterMoveType.FacingAngle: + data.WriteFloat(FaceDirection); + break; + } + + foreach (Vector3 pos in Points) + data.WriteVector3(pos); + + foreach (Vector3 pos in PackedDeltas) + data.WritePackXYZ(pos); + + if (SpellEffectExtraData.HasValue) + SpellEffectExtraData.Value.Write(data); + } + + public uint Flags; // Spline flags + public MonsterMoveType Face; // Movement direction (see MonsterMoveType enum) + public byte AnimTier; + public uint TierTransStartTime; + public int Elapsed; + public uint MoveTime; + public float JumpGravity; + public uint SpecialTime; + public List Points = new List(); // Spline path + public byte Mode; // Spline mode - actually always 0 in this packet - Catmullrom mode appears only in SMSG_UPDATE_OBJECT. In this packet it is determined by flags + public byte VehicleExitVoluntary; + public ObjectGuid TransportGUID; + public sbyte VehicleSeat = -1; + public List PackedDeltas = new List(); + public Optional SplineFilter; + public Optional SpellEffectExtraData; + public float FaceDirection; + public ObjectGuid FaceGUID; + public Vector3 FaceSpot; + } + + public class MovementMonsterSpline + { + public MovementMonsterSpline() + { + Move = new MovementSpline(); + } + + public void Write(WorldPacket data) + { + data.WriteUInt32(ID); + data.WriteVector3(Destination); + data.WriteBit(CrzTeleport); + data.WriteBits(StopDistanceTolerance, 3); + + Move.Write(data); + } + + public uint ID; + public Vector3 Destination; + public bool CrzTeleport; + public byte StopDistanceTolerance; // Determines how far from spline destination the mover is allowed to stop in place 0, 0, 3.0, 2.76, numeric_limits::max, 1.1, float(INT_MAX); default before this field existed was distance 3.0 (index 2) + public MovementSpline Move; + } + + public struct VehicleTeleport + { + public byte VehicleSeatIndex; + public bool VehicleExitVoluntary; + public bool VehicleExitTeleport; + } + + public struct MovementForce + { + public void Write(WorldPacket data) + { + data.WritePackedGuid(ID); + data.WriteVector3(Origin); + data.WriteVector3(Direction); + data.WriteUInt32(TransportID); + data.WriteFloat(Magnitude); + data.WriteBits(Type, 2); + data.FlushBits(); + } + + public ObjectGuid ID; + public Vector3 Origin; + public Vector3 Direction; + public uint TransportID; + public float Magnitude; + public byte Type; + } + + public struct MovementAck + { + public void Read(WorldPacket data) + { + movementInfo = MovementExtensions.ReadMovementInfo(data); + AckIndex = data.ReadInt32(); + } + + public MovementInfo movementInfo; + public int AckIndex; + } + + public struct MoveKnockBackSpeeds + { + public void Write(WorldPacket data) + { + data.WriteFloat(HorzSpeed); + data.WriteFloat(VertSpeed); + } + + public void Read(WorldPacket data) + { + HorzSpeed = data.ReadFloat(); + VertSpeed = data.ReadFloat(); + } + + public float HorzSpeed; + public float VertSpeed; + } +} diff --git a/Game/Network/Packets/NPCPackets.cs b/Game/Network/Packets/NPCPackets.cs new file mode 100644 index 000000000..757da47f2 --- /dev/null +++ b/Game/Network/Packets/NPCPackets.cs @@ -0,0 +1,355 @@ +/* + * Copyright (C) 2012-2017 CypherCore + * + * 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, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +using Framework.Constants; +using Framework.GameMath; +using Game.Entities; +using System.Collections.Generic; + +namespace Game.Network.Packets +{ + // CMSG_BANKER_ACTIVATE + // CMSG_BINDER_ACTIVATE + // CMSG_BINDER_CONFIRM + // CMSG_GOSSIP_HELLO + // CMSG_LIST_INVENTORY + // CMSG_TRAINER_LIST + // CMSG_BATTLEMASTER_HELLO + public class Hello : ClientPacket + { + public Hello(WorldPacket packet) : base(packet) { } + + public override void Read() + { + Unit = _worldPacket.ReadPackedGuid(); + } + + public ObjectGuid Unit; + } + + public class GossipMessagePkt : ServerPacket + { + public GossipMessagePkt() : base(ServerOpcodes.GossipMessage) { } + + public override void Write() + { + _worldPacket.WritePackedGuid(GossipGUID); + _worldPacket.WriteInt32(GossipID); + _worldPacket.WriteInt32(FriendshipFactionID); + _worldPacket.WriteInt32(TextID); + + _worldPacket.WriteInt32(GossipOptions.Count); + _worldPacket.WriteInt32(GossipText.Count); + + foreach (ClientGossipOptions options in GossipOptions) + { + _worldPacket.WriteInt32(options.ClientOption); + _worldPacket.WriteInt8(options.OptionNPC); + _worldPacket.WriteInt8(options.OptionFlags); + _worldPacket.WriteInt32(options.OptionCost); + + _worldPacket.WriteBits(options.Text.Length, 12); + _worldPacket.WriteBits(options.Confirm.Length, 12); + _worldPacket.FlushBits(); + + _worldPacket.WriteString(options.Text); + _worldPacket.WriteString(options.Confirm); + } + + foreach (ClientGossipText text in GossipText) + { + _worldPacket.WriteInt32(text.QuestID); + _worldPacket.WriteInt32(text.QuestType); + _worldPacket.WriteInt32(text.QuestLevel); + _worldPacket.WriteInt32(text.QuestFlags); + _worldPacket.WriteInt32(text.QuestFlagsEx); + + _worldPacket.WriteBit(text.Repeatable); + _worldPacket.WriteBits(text.QuestTitle.Length, 9); + _worldPacket.FlushBits(); + + _worldPacket.WriteString(text.QuestTitle); + } + } + + public List GossipOptions = new List(); + public int FriendshipFactionID = 0; + public ObjectGuid GossipGUID; + public List GossipText = new List(); + public int TextID = 0; + public int GossipID = 0; + } + + public class GossipSelectOption : ClientPacket + { + public GossipSelectOption(WorldPacket packet) : base(packet) { } + + public override void Read() + { + GossipUnit = _worldPacket.ReadPackedGuid(); + GossipID = _worldPacket.ReadUInt32(); + GossipIndex = _worldPacket.ReadUInt32(); + + uint length = _worldPacket.ReadBits(8); + PromotionCode = _worldPacket.ReadString(length); + } + + public ObjectGuid GossipUnit; + public uint GossipIndex; + public uint GossipID; + public string PromotionCode; + } + + public class GossipComplete : ServerPacket + { + public GossipComplete() : base(ServerOpcodes.GossipComplete) { } + + public override void Write() { } + } + + public class VendorInventory : ServerPacket + { + public VendorInventory() : base(ServerOpcodes.VendorInventory, ConnectionType.Instance) { } + + public override void Write() + { + _worldPacket.WritePackedGuid(Vendor); + _worldPacket.WriteUInt8(Reason); + _worldPacket.WriteInt32(Items.Count); + + foreach (VendorItemPkt item in Items) + item.Write(_worldPacket); + } + + public byte Reason = 0; + public List Items = new List(); + public ObjectGuid Vendor; + } + + public class TrainerList : ServerPacket + { + public TrainerList() : base(ServerOpcodes.TrainerList, ConnectionType.Instance) { } + + public override void Write() + { + _worldPacket.WritePackedGuid(TrainerGUID); + _worldPacket.WriteInt32(TrainerType); + _worldPacket.WriteInt32(TrainerID); + + _worldPacket.WriteInt32(Spells.Count); + foreach (TrainerListSpell spell in Spells) + { + _worldPacket.WriteInt32(spell.SpellID); + _worldPacket.WriteInt32(spell.MoneyCost); + _worldPacket.WriteInt32(spell.ReqSkillLine); + _worldPacket.WriteInt32(spell.ReqSkillRank); + + for (uint i = 0; i < SharedConst.MaxTrainerspellAbilityReqs; ++i) + _worldPacket.WriteInt32(spell.ReqAbility[i]); + + _worldPacket.WriteInt8(spell.Usable); + _worldPacket.WriteInt8(spell.ReqLevel); + } + + _worldPacket.WriteBits(Greeting.Length, 11); + _worldPacket.FlushBits(); + _worldPacket.WriteString(Greeting); + } + + public string Greeting; + public int TrainerType = 0; + public ObjectGuid TrainerGUID; + public int TrainerID = 1; + public List Spells = new List(); + } + + public class ShowBank : ServerPacket + { + public ShowBank() : base(ServerOpcodes.ShowBank, ConnectionType.Instance) { } + + public override void Write() + { + _worldPacket.WritePackedGuid(Guid); + } + + public ObjectGuid Guid; + } + + public class PlayerTabardVendorActivate : ServerPacket + { + public PlayerTabardVendorActivate() : base(ServerOpcodes.PlayerTabardVendorActivate) { } + + public override void Write() + { + _worldPacket.WritePackedGuid(Vendor); + } + + public ObjectGuid Vendor; + } + + class GossipPOI : ServerPacket + { + public GossipPOI() : base(ServerOpcodes.GossipPoi) { } + + public override void Write() + { + _worldPacket.WriteBits(Flags, 14); + _worldPacket.WriteBits(Name.Length, 6); + _worldPacket.WriteFloat(Pos.X); + _worldPacket.WriteFloat(Pos.Y); + _worldPacket.WriteUInt32(Icon); + _worldPacket.WriteUInt32(Importance); + _worldPacket.WriteString(Name); + } + + public uint Flags; + public Vector2 Pos; + public uint Icon; + public uint Importance; + public string Name; + } + + class SpiritHealerActivate : ClientPacket + { + public SpiritHealerActivate(WorldPacket packet) : base(packet) { } + + public override void Read() + { + Healer = _worldPacket.ReadPackedGuid(); + } + + public ObjectGuid Healer; + } + + public class SpiritHealerConfirm : ServerPacket + { + public SpiritHealerConfirm() : base(ServerOpcodes.SpiritHealerConfirm) { } + + public override void Write() + { + _worldPacket.WritePackedGuid(Unit); + } + + public ObjectGuid Unit; + } + + class TrainerBuySpell : ClientPacket + { + public TrainerBuySpell(WorldPacket packet) : base(packet) { } + + public override void Read() + { + TrainerGUID = _worldPacket.ReadPackedGuid(); + TrainerID = _worldPacket.ReadUInt32(); + SpellID= _worldPacket.ReadUInt32(); + } + + public ObjectGuid TrainerGUID; + public uint TrainerID; + public uint SpellID; + } + + class TrainerBuyFailed : ServerPacket + { + public TrainerBuyFailed() : base(ServerOpcodes.TrainerBuyFailed) { } + + public override void Write() + { + _worldPacket.WritePackedGuid(TrainerGUID); + _worldPacket.WriteUInt32(SpellID); + _worldPacket.WriteUInt32(TrainerFailedReason); + } + + public ObjectGuid TrainerGUID; + public uint SpellID; + public uint TrainerFailedReason; + } + + class RequestStabledPets : ClientPacket + { + public RequestStabledPets(WorldPacket packet) : base(packet) { } + + public override void Read() + { + StableMaster = _worldPacket.ReadPackedGuid(); + } + + public ObjectGuid StableMaster; + } + + //Structs + public struct ClientGossipOptions + { + public int ClientOption; + public byte OptionNPC; + public byte OptionFlags; + public int OptionCost; + public string Text; + public string Confirm; + } + + public class ClientGossipText + { + public int QuestID; + public int QuestType; + public int QuestLevel; + public bool Repeatable; + public string QuestTitle; + public int QuestFlags; + public int QuestFlagsEx; + } + + public class VendorItemPkt + { + public void Write(WorldPacket data) + { + data.WriteUInt32(MuID); + data.WriteInt32(Type); + data.WriteInt32(Quantity); + data.WriteUInt64(Price); + data.WriteInt32(Durability); + data.WriteInt32(StackCount); + data.WriteInt32(ExtendedCostID); + data.WriteInt32(PlayerConditionFailed); + Item.Write(data); + data.WriteBit(DoNotFilterOnVendor); + data.FlushBits(); + } + + public int MuID; + public int Type; + public ItemInstance Item = new ItemInstance(); + public int Quantity = -1; + public ulong Price; + public int Durability; + public int StackCount; + public int ExtendedCostID; + public int PlayerConditionFailed; + public bool DoNotFilterOnVendor; + } + + public class TrainerListSpell + { + public int SpellID; + public int MoneyCost; + public int ReqSkillLine; + public int ReqSkillRank; + public int[] ReqAbility = new int[SharedConst.MaxTrainerspellAbilityReqs]; + public TrainerSpellState Usable; + public byte ReqLevel; + } +} diff --git a/Game/Network/Packets/PartyPackets.cs b/Game/Network/Packets/PartyPackets.cs new file mode 100644 index 000000000..d5514e44d --- /dev/null +++ b/Game/Network/Packets/PartyPackets.cs @@ -0,0 +1,1115 @@ +/* + * Copyright (C) 2012-2017 CypherCore + * + * 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, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +using Framework.Constants; +using Framework.Dynamic; +using Game.Entities; +using Game.Groups; +using Game.Spells; +using System; +using System.Collections.Generic; + +namespace Game.Network.Packets +{ + class PartyCommandResult : ServerPacket + { + public PartyCommandResult() : base(ServerOpcodes.PartyCommandResult) { } + + public override void Write() + { + _worldPacket.WriteBits(Name.Length, 9); + _worldPacket.WriteBits(Command, 4); + _worldPacket.WriteBits(Result, 6); + + _worldPacket.WriteUInt32(ResultData); + _worldPacket.WritePackedGuid(ResultGUID); + _worldPacket.WriteString(Name); + } + + public string Name; + public byte Command; + public byte Result; + public uint ResultData; + public ObjectGuid ResultGUID; + } + + class PartyInviteClient : ClientPacket + { + public PartyInviteClient(WorldPacket packet) : base(packet) { } + + public override void Read() + { + PartyIndex = _worldPacket.ReadInt8(); + ProposedRoles = _worldPacket.ReadInt32(); + TargetGUID = _worldPacket.ReadPackedGuid(); + + uint targetNameLen = _worldPacket.ReadBits(9); + uint targetRealmLen = _worldPacket.ReadBits(9); + + TargetName = _worldPacket.ReadString(targetNameLen); + TargetRealm = _worldPacket.ReadString(targetRealmLen); + } + + public sbyte PartyIndex; + public int ProposedRoles; + public string TargetName; + public string TargetRealm; + public ObjectGuid TargetGUID; + } + + class PartyInvite : ServerPacket + { + public PartyInvite() : base(ServerOpcodes.PartyInvite) { } + + public void Initialize(Player inviter, int proposedRoles, bool canAccept) + { + CanAccept = canAccept; + + InviterName = inviter.GetName(); + InviterGUID = inviter.GetGUID(); + InviterBNetAccountId = inviter.GetSession().GetAccountGUID(); + + ProposedRoles = proposedRoles; + + InviterVirtualRealmAddress = Global.WorldMgr.GetVirtualRealmAddress(); + InviterRealmNameActual = Global.WorldMgr.GetRealm().Name; + InviterRealmNameNormalized = Global.WorldMgr.GetRealm().NormalizedName; + } + + public override void Write() + { + _worldPacket.WriteBit(CanAccept); + _worldPacket.WriteBit(MightCRZYou); + _worldPacket.WriteBit(IsXRealm); + _worldPacket.WriteBit(MustBeBNetFriend); + _worldPacket.WriteBit(AllowMultipleRoles); + _worldPacket.WriteBits(InviterName.Length, 6); + + _worldPacket.WriteUInt32(InviterVirtualRealmAddress); + _worldPacket.WriteBit(IsLocal); + _worldPacket.WriteBit(Unk2); + _worldPacket.WriteBits(InviterRealmNameActual.Length, 8); + _worldPacket.WriteBits(InviterRealmNameNormalized.Length, 8); + _worldPacket.WriteString(InviterRealmNameActual); + _worldPacket.WriteString(InviterRealmNameNormalized); + + _worldPacket.WritePackedGuid(InviterGUID); + _worldPacket.WritePackedGuid(InviterBNetAccountId); + _worldPacket.WriteUInt16(Unk1); + _worldPacket.WriteInt32(ProposedRoles); + _worldPacket.WriteInt32(LfgSlots.Count); + _worldPacket.WriteInt32(LfgCompletedMask); + + _worldPacket.WriteString(InviterName); + + foreach (int LfgSlot in LfgSlots) + _worldPacket.WriteInt32(LfgSlot); + } + + public bool MightCRZYou; + public bool MustBeBNetFriend; + public bool AllowMultipleRoles; + public bool Unk2; + public ushort Unk1; + + public bool CanAccept; + + // Inviter + public ObjectGuid InviterGUID; + public ObjectGuid InviterBNetAccountId; + public string InviterName; + + // Realm + public bool IsXRealm; + public bool IsLocal = true; + public uint InviterVirtualRealmAddress; + public string InviterRealmNameActual; + public string InviterRealmNameNormalized; + + // Lfg + public int ProposedRoles; + public int LfgCompletedMask; + public List LfgSlots = new List(); + } + + class PartyInviteResponse : ClientPacket + { + public PartyInviteResponse(WorldPacket packet) : base(packet) { } + + public override void Read() + { + PartyIndex = _worldPacket.ReadInt8(); + + Accept = _worldPacket.HasBit(); + + bool hasRolesDesired = _worldPacket.HasBit(); + if (hasRolesDesired) + RolesDesired.Set(_worldPacket.ReadInt32()); + } + + public sbyte PartyIndex; + public bool Accept; + public Optional RolesDesired; + } + + class PartyUninvite : ClientPacket + { + public PartyUninvite(WorldPacket packet) : base(packet) { } + + public override void Read() + { + PartyIndex = _worldPacket.ReadInt8(); + TargetGUID = _worldPacket.ReadPackedGuid(); + + byte reasonLen = _worldPacket.ReadBits(8); + Reason = _worldPacket.ReadString(reasonLen); + } + + public sbyte PartyIndex; + public ObjectGuid TargetGUID; + public string Reason; + } + + class GroupDecline : ServerPacket + { + public GroupDecline(string name) : base(ServerOpcodes.GroupDecline) + { + Name = name; + } + + public override void Write() + { + _worldPacket.WriteBits(Name.Length, 9); + _worldPacket.FlushBits(); + _worldPacket.WriteString(Name); + } + + public string Name; + } + + class RequestPartyMemberStats : ClientPacket + { + public RequestPartyMemberStats(WorldPacket packet) : base(packet) { } + + public override void Read() + { + PartyIndex = _worldPacket.ReadInt8(); + TargetGUID = _worldPacket.ReadPackedGuid(); + } + + public sbyte PartyIndex; + public ObjectGuid TargetGUID; + } + + class PartyMemberState : ServerPacket + { + public PartyMemberState() : base(ServerOpcodes.PartyMemberState) { } + + public override void Write() + { + _worldPacket.WriteBit(ForEnemy); + _worldPacket.FlushBits(); + + MemberStats.Write(_worldPacket); + _worldPacket.WritePackedGuid(MemberGuid); + } + + public void Initialize(Player player) + { + ForEnemy = false; + + MemberGuid = player.GetGUID(); + + // Status + MemberStats.Status = GroupMemberOnlineStatus.Online; + + if (player.IsPvP()) + MemberStats.Status |= GroupMemberOnlineStatus.PVP; + + if (!player.IsAlive()) + { + if (player.HasFlag(PlayerFields.Flags, PlayerFlags.Ghost)) + MemberStats.Status |= GroupMemberOnlineStatus.Ghost; + else + MemberStats.Status |= GroupMemberOnlineStatus.Dead; + } + + if (player.IsFFAPvP()) + MemberStats.Status |= GroupMemberOnlineStatus.PVPFFA; + + if (player.isAFK()) + MemberStats.Status |= GroupMemberOnlineStatus.AFK; + + if (player.isDND()) + MemberStats.Status |= GroupMemberOnlineStatus.DND; + + if (player.GetVehicle()) + MemberStats.Status |= GroupMemberOnlineStatus.Vehicle; + + // Level + MemberStats.Level = (ushort)player.getLevel(); + + // Health + MemberStats.CurrentHealth = (int)player.GetHealth(); + MemberStats.MaxHealth = (int)player.GetMaxHealth(); + + // Power + MemberStats.PowerType = (byte)player.getPowerType(); + MemberStats.PowerDisplayID = 0; + MemberStats.CurrentPower = (ushort)player.GetPower(player.getPowerType()); + MemberStats.MaxPower = (ushort)player.GetMaxPower(player.getPowerType()); + + // Position + MemberStats.ZoneID = (ushort)player.GetZoneId(); + MemberStats.PositionX = (short)player.GetPositionX(); + MemberStats.PositionY = (short)(player.GetPositionY()); + MemberStats.PositionZ = (short)(player.GetPositionZ()); + + MemberStats.SpecID = (ushort)player.GetUInt32Value(PlayerFields.CurrentSpecId); + MemberStats.PartyType[0] = (sbyte)(player.GetByteValue(PlayerFields.Bytes3, PlayerFieldOffsets.Bytes3OffsetPartyType) & 0xF); + MemberStats.PartyType[1] = (sbyte)(player.GetByteValue(PlayerFields.Bytes3, PlayerFieldOffsets.Bytes3OffsetPartyType) >> 4); + MemberStats.WmoGroupID = 0; + MemberStats.WmoDoodadPlacementID = 0; + + // Vehicle + if (player.GetVehicle() && player.GetVehicle().GetVehicleInfo() != null) + MemberStats.VehicleSeat = player.GetVehicle().GetVehicleInfo().SeatID[player.m_movementInfo.transport.seat]; + + // Auras + foreach (AuraApplication aurApp in player.GetVisibleAuras()) + { + PartyMemberAuraStates aura = new PartyMemberAuraStates(); + aura.SpellID = (int)aurApp.GetBase().GetId(); + aura.ActiveFlags = aurApp.GetEffectMask(); + aura.Flags = (byte)aurApp.GetFlags(); + + if (aurApp.GetFlags().HasAnyFlag(AuraFlags.Scalable)) + { + foreach (AuraEffect aurEff in aurApp.GetBase().GetAuraEffects()) + { + if (aurEff == null) + continue; + + if (aurApp.HasEffect(aurEff.GetEffIndex())) + aura.Points.Add((float)aurEff.GetAmount()); + } + } + + MemberStats.Auras.Add(aura); + } + + // Phases + var phases = player.GetPhases(); + MemberStats.Phases.PhaseShiftFlags = 0x08 | (!phases.Empty() ? 0x10 : 0); + MemberStats.Phases.PersonalGUID = ObjectGuid.Empty; + foreach (uint phaseId in phases) + { + PartyMemberPhase phase; + phase.Id = (ushort)phaseId; + phase.Flags = 1; + MemberStats.Phases.List.Add(phase); + } + + // Pet + if (player.GetPet()) + { + Pet pet = player.GetPet(); + + MemberStats.PetStats.HasValue = true; + + MemberStats.PetStats.Value.GUID = pet.GetGUID(); + MemberStats.PetStats.Value.Name = pet.GetName(); + MemberStats.PetStats.Value.ModelId = (short)pet.GetDisplayId(); + + MemberStats.PetStats.Value.CurrentHealth = (int)pet.GetHealth(); + MemberStats.PetStats.Value.MaxHealth = (int)pet.GetMaxHealth(); + + foreach (AuraApplication aurApp in pet.GetVisibleAuras()) + { + PartyMemberAuraStates aura = new PartyMemberAuraStates(); + + aura.SpellID = (int)aurApp.GetBase().GetId(); + aura.ActiveFlags = aurApp.GetEffectMask(); + aura.Flags = (byte)aurApp.GetFlags(); + + if (aurApp.GetFlags().HasAnyFlag(AuraFlags.Scalable)) + { + foreach (AuraEffect aurEff in aurApp.GetBase().GetAuraEffects()) + { + if (aurEff == null) + continue; + + if (aurApp.HasEffect(aurEff.GetEffIndex())) + aura.Points.Add((float)aurEff.GetAmount()); + } + } + + MemberStats.PetStats.Value.Auras.Add(aura); + } + + } + } + + public bool ForEnemy; + public ObjectGuid MemberGuid; + public PartyMemberStats MemberStats = new PartyMemberStats(); + } + + class SetPartyLeader : ClientPacket + { + public SetPartyLeader(WorldPacket packet) : base(packet) { } + + public override void Read() + { + PartyIndex = _worldPacket.ReadInt8(); + TargetGUID = _worldPacket.ReadPackedGuid(); + } + + public sbyte PartyIndex; + public ObjectGuid TargetGUID; + } + + class SetRole : ClientPacket + { + public SetRole(WorldPacket packet) : base(packet) { } + + public override void Read() + { + PartyIndex = _worldPacket.ReadInt8(); + TargetGUID = _worldPacket.ReadPackedGuid(); + Role = _worldPacket.ReadInt32(); + } + + public sbyte PartyIndex; + public ObjectGuid TargetGUID; + public int Role; + } + + class RoleChangedInform : ServerPacket + { + public RoleChangedInform() : base(ServerOpcodes.RoleChangedInform) { } + + public override void Write() + { + _worldPacket.WriteInt8(PartyIndex); + _worldPacket.WritePackedGuid(From); + _worldPacket.WritePackedGuid(ChangedUnit); + _worldPacket.WriteInt32(OldRole); + _worldPacket.WriteInt32(NewRole); + } + + public sbyte PartyIndex; + public ObjectGuid From; + public ObjectGuid ChangedUnit; + public int OldRole; + public int NewRole; + } + + class LeaveGroup : ClientPacket + { + public LeaveGroup(WorldPacket packet) : base(packet) { } + + public override void Read() + { + PartyIndex = _worldPacket.ReadInt8(); + } + + public sbyte PartyIndex; + } + + class GroupUninvite : ServerPacket + { + public GroupUninvite() : base(ServerOpcodes.GroupUninvite) { } + + public override void Write() { } + } + + class GroupDestroyed : ServerPacket + { + public GroupDestroyed() : base(ServerOpcodes.GroupDestroyed) { } + + public override void Write() { } + } + + class SetLootMethod : ClientPacket + { + public SetLootMethod(WorldPacket packet) : base(packet) { } + + public override void Read() + { + PartyIndex = _worldPacket.ReadInt8(); + LootMethod = (LootMethod)_worldPacket.ReadUInt8(); + LootMasterGUID = _worldPacket.ReadPackedGuid(); + LootThreshold = (ItemQuality)_worldPacket.ReadUInt32(); + } + + public sbyte PartyIndex; + public ObjectGuid LootMasterGUID; + public LootMethod LootMethod; + public ItemQuality LootThreshold; + } + + class MinimapPingClient : ClientPacket + { + public MinimapPingClient(WorldPacket packet) : base(packet) { } + + public override void Read() + { + PositionX = _worldPacket.ReadFloat(); + PositionY = _worldPacket.ReadFloat(); + PartyIndex = _worldPacket.ReadInt8(); + } + + public sbyte PartyIndex; + public float PositionX; + public float PositionY; + } + + class MinimapPing : ServerPacket + { + public MinimapPing() : base(ServerOpcodes.MinimapPing) { } + + public override void Write() + { + _worldPacket.WritePackedGuid(Sender); + _worldPacket.WriteFloat(PositionX); + _worldPacket.WriteFloat(PositionY); + } + + public ObjectGuid Sender; + public float PositionX; + public float PositionY; + } + + class UpdateRaidTarget : ClientPacket + { + public UpdateRaidTarget(WorldPacket packet) : base(packet) { } + + public override void Read() + { + PartyIndex = _worldPacket.ReadInt8(); + Target = _worldPacket.ReadPackedGuid(); + Symbol = _worldPacket.ReadInt8(); + } + + public sbyte PartyIndex; + public ObjectGuid Target; + public sbyte Symbol; + } + + class SendRaidTargetUpdateSingle : ServerPacket + { + public SendRaidTargetUpdateSingle() : base(ServerOpcodes.SendRaidTargetUpdateSingle) { } + + public override void Write() + { + _worldPacket.WriteInt8(PartyIndex); + _worldPacket.WriteInt8(Symbol); + _worldPacket.WritePackedGuid(Target); + _worldPacket.WritePackedGuid(ChangedBy); + } + + public sbyte PartyIndex; + public ObjectGuid Target; + public ObjectGuid ChangedBy; + public sbyte Symbol; + } + + class SendRaidTargetUpdateAll : ServerPacket + { + public SendRaidTargetUpdateAll() : base(ServerOpcodes.SendRaidTargetUpdateAll) { } + + public override void Write() + { + _worldPacket.WriteInt8(PartyIndex); + + _worldPacket.WriteInt32(TargetIcons.Count); + + foreach (var pair in TargetIcons) + { + _worldPacket.WritePackedGuid(pair.Value); + _worldPacket.WriteUInt8(pair.Key); + } + } + + public sbyte PartyIndex; + public Dictionary TargetIcons = new Dictionary(); + } + + class ConvertRaid : ClientPacket + { + public ConvertRaid(WorldPacket packet) : base(packet) { } + + public override void Read() + { + Raid = _worldPacket.HasBit(); + } + + public bool Raid; + } + + class RequestPartyJoinUpdates : ClientPacket + { + public RequestPartyJoinUpdates(WorldPacket packet) : base(packet) { } + + public override void Read() + { + PartyIndex = _worldPacket.ReadInt8(); + } + + public sbyte PartyIndex; + } + + class SetAssistantLeader : ClientPacket + { + public SetAssistantLeader(WorldPacket packet) : base(packet) { } + + public override void Read() + { + PartyIndex = _worldPacket.ReadInt8(); + Target = _worldPacket.ReadPackedGuid(); + Apply = _worldPacket.HasBit(); + } + + public ObjectGuid Target; + public sbyte PartyIndex; + public bool Apply; + } + + class SetPartyAssignment : ClientPacket + { + public SetPartyAssignment(WorldPacket packet) : base(packet) { } + + public override void Read() + { + PartyIndex = _worldPacket.ReadUInt8(); + Assignment = _worldPacket.ReadUInt8(); + Target = _worldPacket.ReadPackedGuid(); + Set = _worldPacket.HasBit(); + } + + public byte Assignment; + public byte PartyIndex; + public ObjectGuid Target; + public bool Set; + } + + class DoReadyCheck : ClientPacket + { + public DoReadyCheck(WorldPacket packet) : base(packet) { } + + public override void Read() + { + PartyIndex = _worldPacket.ReadInt8(); + } + + public sbyte PartyIndex; + } + + class ReadyCheckStarted : ServerPacket + { + public ReadyCheckStarted() : base(ServerOpcodes.ReadyCheckStarted) { } + + public override void Write() + { + _worldPacket.WriteInt8(PartyIndex); + _worldPacket.WritePackedGuid(PartyGUID); + _worldPacket.WritePackedGuid(InitiatorGUID); + _worldPacket.WriteUInt32(Duration); + } + + public sbyte PartyIndex; + public ObjectGuid PartyGUID; + public ObjectGuid InitiatorGUID; + public uint Duration; + } + + class ReadyCheckResponseClient : ClientPacket + { + public ReadyCheckResponseClient(WorldPacket packet) : base(packet) { } + + public override void Read() + { + PartyIndex = _worldPacket.ReadInt8(); + IsReady = _worldPacket.HasBit(); + } + + public sbyte PartyIndex; + public bool IsReady; + } + + class ReadyCheckResponse : ServerPacket + { + public ReadyCheckResponse() : base(ServerOpcodes.ReadyCheckResponse) { } + + public override void Write() + { + _worldPacket.WritePackedGuid(PartyGUID); + _worldPacket.WritePackedGuid(Player); + + _worldPacket.WriteBit(IsReady); + _worldPacket.FlushBits(); + } + + public ObjectGuid PartyGUID; + public ObjectGuid Player; + public bool IsReady; + } + + class ReadyCheckCompleted : ServerPacket + { + public ReadyCheckCompleted() : base(ServerOpcodes.ReadyCheckCompleted) { } + + public override void Write() + { + _worldPacket.WriteInt8(PartyIndex); + _worldPacket.WritePackedGuid(PartyGUID); + } + + public sbyte PartyIndex; + public ObjectGuid PartyGUID; + } + + class RequestRaidInfo : ClientPacket + { + public RequestRaidInfo(WorldPacket packet) : base(packet) { } + + public override void Read() { } + } + + class OptOutOfLoot : ClientPacket + { + public OptOutOfLoot(WorldPacket packet) : base(packet) { } + + public override void Read() + { + PassOnLoot = _worldPacket.HasBit(); + } + + public bool PassOnLoot; + } + + class InitiateRolePoll : ClientPacket + { + public InitiateRolePoll(WorldPacket packet) : base(packet) { } + + public override void Read() + { + PartyIndex = _worldPacket.ReadInt8(); + } + + public sbyte PartyIndex; + } + + class RolePollInform : ServerPacket + { + public RolePollInform() : base(ServerOpcodes.RolePollInform) { } + + public override void Write() + { + _worldPacket.WriteInt8(PartyIndex); + _worldPacket.WritePackedGuid(From); + } + + public sbyte PartyIndex; + public ObjectGuid From; + } + + class GroupNewLeader : ServerPacket + { + public GroupNewLeader() : base(ServerOpcodes.GroupNewLeader) { } + + public override void Write() + { + _worldPacket.WriteInt8(PartyIndex); + _worldPacket.WriteBits(Name.Length, 6); + _worldPacket.WriteString(Name); + } + + public sbyte PartyIndex; + public string Name; + } + + class PartyUpdate : ServerPacket + { + public PartyUpdate() : base(ServerOpcodes.PartyUpdate) { } + + public override void Write() + { + _worldPacket.WriteUInt16(PartyFlags); + _worldPacket.WriteUInt8(PartyIndex); + _worldPacket.WriteUInt8(PartyType); + _worldPacket.WriteInt32(MyIndex); + _worldPacket.WritePackedGuid(PartyGUID); + _worldPacket.WriteInt32(SequenceNum); + _worldPacket.WritePackedGuid(LeaderGUID); + _worldPacket.WriteUInt32(PlayerList.Count); + _worldPacket.WriteBit(LfgInfos.HasValue); + _worldPacket.WriteBit(LootSettings.HasValue); + _worldPacket.WriteBit(DifficultySettings.HasValue); + _worldPacket.FlushBits(); + + foreach (var playerInfo in PlayerList) + playerInfo.Write(_worldPacket); + + if (LootSettings.HasValue) + LootSettings.Value.Write(_worldPacket); + + if (DifficultySettings.HasValue) + DifficultySettings.Value.Write(_worldPacket); + + if (LfgInfos.HasValue) + LfgInfos.Value.Write(_worldPacket); + } + + public GroupFlags PartyFlags; + public byte PartyIndex; + public GroupType PartyType; + + public ObjectGuid PartyGUID; + public ObjectGuid LeaderGUID; + + public int MyIndex; + public int SequenceNum; + + public List PlayerList = new List(); + + public Optional LfgInfos; + public Optional LootSettings; + public Optional DifficultySettings; + } + + class SetEveryoneIsAssistant : ClientPacket + { + public SetEveryoneIsAssistant(WorldPacket packet) : base(packet) { } + + public override void Read() + { + PartyIndex = _worldPacket.ReadInt8(); + EveryoneIsAssistant = _worldPacket.HasBit(); + } + + public sbyte PartyIndex; + public bool EveryoneIsAssistant; + } + + class ChangeSubGroup : ClientPacket + { + public ChangeSubGroup(WorldPacket packet) : base(packet) { } + + public override void Read() + { + TargetGUID = _worldPacket.ReadPackedGuid(); + PartyIndex = _worldPacket.ReadInt8(); + NewSubGroup = _worldPacket.ReadUInt8(); + } + + public ObjectGuid TargetGUID; + public sbyte PartyIndex; + public byte NewSubGroup; + } + + class SwapSubGroups : ClientPacket + { + public SwapSubGroups(WorldPacket packet) : base(packet) { } + + public override void Read() + { + PartyIndex = _worldPacket.ReadInt8(); + FirstTarget = _worldPacket.ReadPackedGuid(); + SecondTarget = _worldPacket.ReadPackedGuid(); + } + + public ObjectGuid FirstTarget; + public ObjectGuid SecondTarget; + public sbyte PartyIndex; + } + + class ClearRaidMarker : ClientPacket + { + public ClearRaidMarker(WorldPacket packet) : base(packet) { } + + public override void Read() + { + MarkerId = _worldPacket.ReadUInt8(); + } + + public byte MarkerId; + } + + class RaidMarkersChanged : ServerPacket + { + public RaidMarkersChanged() : base(ServerOpcodes.RaidMarkersChanged) { } + + public override void Write() + { + _worldPacket.WriteInt8(PartyIndex); + _worldPacket.WriteUInt32(ActiveMarkers); + + _worldPacket.WriteBits(RaidMarkers.Count, 4); + _worldPacket.FlushBits(); + + foreach (RaidMarker raidMarker in RaidMarkers) + { + _worldPacket.WritePackedGuid(raidMarker.TransportGUID); + _worldPacket.WriteUInt32(raidMarker.Location.GetMapId()); + _worldPacket.WriteXYZ(raidMarker.Location); + } + } + + public sbyte PartyIndex; + public uint ActiveMarkers; + + public List RaidMarkers = new List(); + } + + class PartyKillLog : ServerPacket + { + public PartyKillLog() : base(ServerOpcodes.PartyKillLog) { } + + public override void Write() + { + _worldPacket.WritePackedGuid(Player); + _worldPacket.WritePackedGuid(Victim); + } + + public ObjectGuid Player; + public ObjectGuid Victim; + } + + //Structs + struct PartyMemberPhase + { + public void Write(WorldPacket data) + { + data.WriteUInt16(Flags); + data.WriteUInt16(Id); + } + + public ushort Flags; + public ushort Id; + } + + class PartyMemberPhaseStates + { + public void Write(WorldPacket data) + { + data.WriteUInt32(PhaseShiftFlags); + data.WriteUInt32(List.Count); + data.WritePackedGuid(PersonalGUID); + + foreach (PartyMemberPhase phase in List) + phase.Write(data); + } + + public int PhaseShiftFlags; + public ObjectGuid PersonalGUID; + public List List = new List(); + } + + class PartyMemberAuraStates + { + public void Write(WorldPacket data) + { + data.WriteInt32(SpellID); + data.WriteUInt8(Flags); + data.WriteUInt32(ActiveFlags); + data.WriteInt32(Points.Count); + foreach (float points in Points) + data.WriteFloat(points); + } + + public int SpellID; + public byte Flags; + public uint ActiveFlags; + public List Points = new List(); + } + + class PartyMemberPetStats + { + public void Write(WorldPacket data) + { + data.WritePackedGuid(GUID); + data.WriteInt32(ModelId); + data.WriteInt32(CurrentHealth); + data.WriteInt32(MaxHealth); + data.WriteUInt32(Auras.Count); + Auras.ForEach(p => p.Write(data)); + + data.WriteBits(Name.Length, 8); + data.FlushBits(); + data.WriteString(Name); + } + + public ObjectGuid GUID; + public string Name; + public short ModelId; + + public int CurrentHealth; + public int MaxHealth; + + public List Auras = new List(); + } + + class PartyMemberStats + { + public void Write(WorldPacket data) + { + for (byte i = 0; i < 2; i++) + data.WriteInt8(PartyType[i]); + + data.WriteInt16(Status); + data.WriteUInt8(PowerType); + data.WriteInt16(PowerDisplayID); + data.WriteInt32(CurrentHealth); + data.WriteInt32(MaxHealth); + data.WriteUInt16(CurrentPower); + data.WriteUInt16(MaxPower); + data.WriteUInt16(Level); + data.WriteUInt16(SpecID); + data.WriteUInt16(ZoneID); + data.WriteUInt16(WmoGroupID); + data.WriteUInt32(WmoDoodadPlacementID); + data.WriteInt16(PositionX); + data.WriteInt16(PositionY); + data.WriteInt16(PositionZ); + data.WriteInt32(VehicleSeat); + data.WriteInt32(Auras.Count); + + Phases.Write(data); + + foreach (PartyMemberAuraStates aura in Auras) + aura.Write(data); + + data.WriteBit(PetStats.HasValue); + data.FlushBits(); + + if (PetStats.HasValue) + PetStats.Value.Write(data); + } + + public ushort Level; + public GroupMemberOnlineStatus Status; + + public int CurrentHealth; + public int MaxHealth; + + public byte PowerType; + public ushort CurrentPower; + public ushort MaxPower; + + public ushort ZoneID; + public short PositionX; + public short PositionY; + public short PositionZ; + + public int VehicleSeat; + + public PartyMemberPhaseStates Phases = new PartyMemberPhaseStates(); + public List Auras = new List(); + public Optional PetStats; + + public ushort PowerDisplayID; + public ushort SpecID; + public ushort WmoGroupID; + public uint WmoDoodadPlacementID; + public sbyte[] PartyType = new sbyte[2]; + } + + struct PartyPlayerInfo + { + public void Write(WorldPacket data) + { + data.WriteBits(Name.Length, 6); + data.WriteBit(FromSocialQueue); + data.WritePackedGuid(GUID); + data.WriteUInt8(Status); + data.WriteUInt8(Subgroup); + data.WriteUInt8(Flags); + data.WriteUInt8(RolesAssigned); + data.WriteUInt8(Class); + data.WriteString(Name); + } + + public ObjectGuid GUID; + public string Name; + public byte Class; + + public GroupMemberOnlineStatus Status; + public byte Subgroup; + public byte Flags; + public byte RolesAssigned; + public bool FromSocialQueue; + } + + struct PartyLFGInfo + { + public void Write(WorldPacket data) + { + data.WriteUInt8(MyFlags); + data.WriteUInt32(Slot); + data.WriteUInt32(MyRandomSlot); + data.WriteUInt8(MyPartialClear); + data.WriteFloat(MyGearDiff); + data.WriteUInt8(MyStrangerCount); + data.WriteUInt8(MyKickVoteCount); + data.WriteUInt8(BootCount); + data.WriteBit(Aborted); + data.WriteBit(MyFirstReward); + data.FlushBits(); + } + + public byte MyFlags; + public uint Slot; + public byte BootCount; + public uint MyRandomSlot; + public bool Aborted; + public byte MyPartialClear; + public float MyGearDiff; + public byte MyStrangerCount; + public byte MyKickVoteCount; + public bool MyFirstReward; + } + + struct PartyLootSettings + { + public void Write(WorldPacket data) + { + data.WriteUInt8(Method); + data.WritePackedGuid(LootMaster); + data.WriteUInt8(Threshold); + } + + public byte Method; + public ObjectGuid LootMaster; + public byte Threshold; + } + + struct PartyDifficultySettings + { + public void Write(WorldPacket data) + { + data.WriteUInt32(DungeonDifficultyID); + data.WriteUInt32(RaidDifficultyID); + data.WriteUInt32(LegacyRaidDifficultyID); + } + + public uint DungeonDifficultyID; + public uint RaidDifficultyID; + public uint LegacyRaidDifficultyID; + } +} \ No newline at end of file diff --git a/Game/Network/Packets/PetPackets.cs b/Game/Network/Packets/PetPackets.cs new file mode 100644 index 000000000..6f6968d6e --- /dev/null +++ b/Game/Network/Packets/PetPackets.cs @@ -0,0 +1,401 @@ +/* + * Copyright (C) 2012-2017 CypherCore + * + * 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, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +using Framework.Constants; +using Framework.GameMath; +using Game.Entities; +using System.Collections.Generic; + +namespace Game.Network.Packets +{ + class DismissCritter : ClientPacket + { + public DismissCritter(WorldPacket packet) : base(packet) { } + + public override void Read() + { + CritterGUID = _worldPacket.ReadPackedGuid(); + } + + public ObjectGuid CritterGUID; + } + + class RequestPetInfo : ClientPacket + { + public RequestPetInfo(WorldPacket packet) : base(packet) { } + + public override void Read() { } + } + + class PetAbandon : ClientPacket + { + public PetAbandon(WorldPacket packet) : base(packet) { } + + public override void Read() + { + Pet = _worldPacket.ReadPackedGuid(); + } + + public ObjectGuid Pet; + } + + class PetStopAttack : ClientPacket + { + public PetStopAttack(WorldPacket packet) : base(packet) { } + + public override void Read() + { + PetGUID = _worldPacket.ReadPackedGuid(); + } + + public ObjectGuid PetGUID; + } + + class PetSpellAutocast : ClientPacket + { + public PetSpellAutocast(WorldPacket packet) : base(packet) { } + + public override void Read() + { + PetGUID = _worldPacket.ReadPackedGuid(); + SpellID = _worldPacket.ReadUInt32(); + AutocastEnabled = _worldPacket.HasBit(); + } + + public ObjectGuid PetGUID; + public uint SpellID; + public bool AutocastEnabled; + } + + public class PetSpells : ServerPacket + { + public PetSpells() : base(ServerOpcodes.PetSpellsMessage, ConnectionType.Instance) { } + + public override void Write() + { + _worldPacket.WritePackedGuid(PetGUID); + _worldPacket.WriteUInt16(CreatureFamily); + _worldPacket.WriteUInt16(Specialization); + _worldPacket.WriteUInt32(TimeLimit); + _worldPacket.WriteUInt16(((int)CommandState << 8) + (Flag << 16)); + _worldPacket.WriteUInt8(ReactState); + + foreach (uint actionButton in ActionButtons) + _worldPacket.WriteUInt32(actionButton); + + _worldPacket.WriteUInt32(Actions.Count); + _worldPacket.WriteUInt32(Cooldowns.Count); + _worldPacket.WriteUInt32(SpellHistory.Count); + + foreach (uint action in Actions) + _worldPacket.WriteUInt32(action); + + foreach (PetSpellCooldown cooldown in Cooldowns) + { + _worldPacket.WriteUInt32(cooldown.SpellID); + _worldPacket.WriteUInt32(cooldown.Duration); + _worldPacket.WriteUInt32(cooldown.CategoryDuration); + _worldPacket.WriteFloat(cooldown.ModRate); + _worldPacket.WriteUInt16(cooldown.Category); + } + + foreach (PetSpellHistory history in SpellHistory) + { + _worldPacket.WriteInt32(history.CategoryID); + _worldPacket.WriteInt32(history.RecoveryTime); + _worldPacket.WriteFloat(history.ChargeModRate); + _worldPacket.WriteInt8(history.ConsumedCharges); + } + } + + public ObjectGuid PetGUID; + public ushort CreatureFamily; + public ushort Specialization; + public uint TimeLimit; + public ReactStates ReactState; + public CommandStates CommandState; + public byte Flag; + + public uint[] ActionButtons = new uint[10]; + + public List Actions = new List(); + public List Cooldowns = new List(); + public List SpellHistory = new List(); + } + + class PetStableList : ServerPacket + { + public PetStableList() : base(ServerOpcodes.PetStableList, ConnectionType.Instance) { } + + public override void Write() + { + _worldPacket.WritePackedGuid(StableMaster); + + _worldPacket.WriteUInt32(Pets.Count); + foreach (PetStableInfo pet in Pets) + { + _worldPacket.WriteUInt32(pet.PetSlot); + _worldPacket.WriteUInt32(pet.PetNumber); + _worldPacket.WriteUInt32(pet.CreatureID); + _worldPacket.WriteUInt32(pet.DisplayID); + _worldPacket.WriteUInt32(pet.ExperienceLevel); + _worldPacket.WriteUInt32(pet.PetFlags); + + _worldPacket.WriteUInt8(pet.PetName.Length); + _worldPacket.WriteString(pet.PetName); + } + } + + public ObjectGuid StableMaster; + public List Pets = new List(); + } + + class PetLearnedSpells : ServerPacket + { + public PetLearnedSpells() : base(ServerOpcodes.PetLearnedSpells, ConnectionType.Instance) { } + + public override void Write() + { + _worldPacket .WriteUInt32(Spells.Count); + foreach (uint spell in Spells) + _worldPacket.WriteUInt32(spell); + } + + public List Spells = new List(); + } + + class PetUnlearnedSpells : ServerPacket + { + public PetUnlearnedSpells() : base(ServerOpcodes.PetUnlearnedSpells, ConnectionType.Instance) { } + + public override void Write() + { + _worldPacket.WriteUInt32(Spells); + foreach (uint spell in Spells) + _worldPacket.WriteUInt32(spell); + } + + public List Spells = new List(); + } + + class PetNameInvalid : ServerPacket + { + public PetNameInvalid() : base(ServerOpcodes.PetNameInvalid) { } + + public override void Write() + { + _worldPacket.WritePackedGuid(RenameData.PetGUID); + _worldPacket.WriteInt32(RenameData.PetNumber); + + _worldPacket.WriteUInt8(RenameData.NewName.Length); + + _worldPacket.WriteBit(RenameData.HasDeclinedNames); + _worldPacket.FlushBits(); + + if (RenameData.HasDeclinedNames) + { + for (int i = 0; i < SharedConst.MaxDeclinedNameCases; i++) + { + _worldPacket.WriteBits(RenameData.DeclinedNames.name[i].Length, 7); + _worldPacket.FlushBits(); + } + + for (int i = 0; i < SharedConst.MaxDeclinedNameCases; i++) + _worldPacket.WriteString(RenameData.DeclinedNames.name[i]); + } + + _worldPacket.WriteString(RenameData.NewName); + } + + public PetRenameData RenameData; + public PetNameInvalidReason Result; + } + + class PetRename : ClientPacket + { + public PetRename(WorldPacket packet) : base(packet) { } + + public override void Read() + { + RenameData.PetGUID = _worldPacket.ReadPackedGuid(); + RenameData.PetNumber = _worldPacket.ReadInt32(); + + uint nameLen = _worldPacket.ReadUInt8(); + + RenameData.HasDeclinedNames = _worldPacket.HasBit(); + if (RenameData.HasDeclinedNames) + { + RenameData.DeclinedNames = new DeclinedName(); + uint[] count = new uint[SharedConst.MaxDeclinedNameCases]; + for (int i = 0; i < SharedConst.MaxDeclinedNameCases; i++) + count[i] = _worldPacket.ReadBits(7); + + for (int i = 0; i < SharedConst.MaxDeclinedNameCases; i++) + RenameData.DeclinedNames.name[i] = _worldPacket.ReadString(count[i]); + } + + RenameData.NewName = _worldPacket.ReadString(nameLen); + } + + public PetRenameData RenameData; + } + + class PetAction : ClientPacket + { + public PetAction(WorldPacket packet) : base(packet) { } + + public override void Read() + { + PetGUID = _worldPacket.ReadPackedGuid(); + + Action = _worldPacket.ReadUInt32(); + TargetGUID = _worldPacket.ReadPackedGuid(); + + ActionPosition = _worldPacket.ReadVector3(); + } + + public ObjectGuid PetGUID; + public uint Action; + public ObjectGuid TargetGUID; + public Vector3 ActionPosition; + } + + class PetSetAction : ClientPacket + { + public PetSetAction(WorldPacket packet) : base(packet) { } + + public override void Read() + { + PetGUID = _worldPacket.ReadPackedGuid(); + + Index = _worldPacket.ReadUInt32(); + Action = _worldPacket.ReadUInt32(); + } + + public ObjectGuid PetGUID; + public uint Index; + public uint Action; + } + + class PetActionSound : ServerPacket + { + public PetActionSound() : base(ServerOpcodes.PetStableResult) { } + + public override void Write() + { + _worldPacket.WritePackedGuid(UnitGUID); + _worldPacket.WriteUInt32(Action); + } + + public ObjectGuid UnitGUID; + public PetTalk Action; + } + + class PetActionFeedback : ServerPacket + { + public PetActionFeedback() : base(ServerOpcodes.PetStableResult) { } + + public override void Write() + { + _worldPacket.WriteUInt32(SpellID); + _worldPacket.WriteUInt8(Response); + } + + public uint SpellID; + public ActionFeedback Response; + } + + class PetCancelAura : ClientPacket + { + public PetCancelAura(WorldPacket packet) : base(packet) { } + + public override void Read() + { + PetGUID = _worldPacket.ReadPackedGuid(); + SpellID = _worldPacket.ReadUInt32(); + } + + public ObjectGuid PetGUID; + public uint SpellID; + } + + + class PetStableResult : ServerPacket + { + public PetStableResult(byte result) : base(ServerOpcodes.PetStableResult) + { + Result = result; + } + + public override void Write() + { + _worldPacket.WriteUInt8(Result); + } + + public byte Result; + } + + class SetPetSpecialization : ServerPacket + { + public SetPetSpecialization() : base(ServerOpcodes.SetPetSpecialization) { } + + public override void Write() + { + _worldPacket.WriteUInt16(SpecID); + } + + public ushort SpecID; + } + + //Structs + public class PetSpellCooldown + { + public uint SpellID; + public uint Duration; + public uint CategoryDuration; + public float ModRate = 1.0f; + public ushort Category; + } + + public class PetSpellHistory + { + public uint CategoryID; + public uint RecoveryTime; + public float ChargeModRate = 1.0f; + public sbyte ConsumedCharges; + } + + struct PetStableInfo + { + public uint PetSlot; + public uint PetNumber; + public uint CreatureID; + public uint DisplayID; + public uint ExperienceLevel; + public PetStableinfo PetFlags; + public string PetName; + } + + struct PetRenameData + { + public ObjectGuid PetGUID; + public int PetNumber; + public string NewName; + public bool HasDeclinedNames; + public DeclinedName DeclinedNames; + } +} diff --git a/Game/Network/Packets/PetitionPackets.cs b/Game/Network/Packets/PetitionPackets.cs new file mode 100644 index 000000000..db24bd85f --- /dev/null +++ b/Game/Network/Packets/PetitionPackets.cs @@ -0,0 +1,345 @@ +/* + * Copyright (C) 2012-2017 CypherCore + * + * 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, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +using Framework.Collections; +using Framework.Constants; +using Game.Entities; +using System.Collections.Generic; + +namespace Game.Network.Packets +{ + public class QueryPetition : ClientPacket + { + public QueryPetition(WorldPacket packet) : base(packet) { } + + public override void Read() + { + PetitionID = _worldPacket.ReadUInt32(); + ItemGUID = _worldPacket.ReadPackedGuid(); + } + + public ObjectGuid ItemGUID; + public uint PetitionID = 0; + } + + public class QueryPetitionResponse : ServerPacket + { + public QueryPetitionResponse() : base(ServerOpcodes.QueryPetitionResponse) { } + + public override void Write() + { + _worldPacket.WriteUInt32(PetitionID); + _worldPacket.WriteBit(Allow); + _worldPacket.FlushBits(); + + if (Allow) + Info.Write(_worldPacket); + } + + public uint PetitionID = 0; + public bool Allow = false; + public PetitionInfo Info; + } + + public class PetitionShowList : ClientPacket + { + public PetitionShowList(WorldPacket packet) : base(packet) { } + + public override void Read() + { + PetitionUnit = _worldPacket.ReadPackedGuid(); + } + + public ObjectGuid PetitionUnit; + } + + public class ServerPetitionShowList : ServerPacket + { + public ServerPetitionShowList() : base(ServerOpcodes.PetitionShowList) { } + + public override void Write() + { + _worldPacket.WritePackedGuid(Unit); + _worldPacket.WriteUInt32(Price); + } + + public ObjectGuid Unit; + public uint Price = 0; + } + + public class PetitionBuy : ClientPacket + { + public PetitionBuy(WorldPacket packet) : base(packet) { } + + public override void Read() + { + uint titleLen = _worldPacket.ReadBits(7); + + Unit = _worldPacket.ReadPackedGuid(); + Title = _worldPacket.ReadString(titleLen); + } + + public ObjectGuid Unit; + public string Title; + } + + public class PetitionShowSignatures : ClientPacket + { + public PetitionShowSignatures(WorldPacket packet) : base(packet) { } + + public override void Read() + { + Item = _worldPacket.ReadPackedGuid(); + } + + public ObjectGuid Item; + } + + public class ServerPetitionShowSignatures : ServerPacket + { + public ServerPetitionShowSignatures() : base(ServerOpcodes.PetitionShowSignatures) + { + Signatures = new List(); + } + + public override void Write() + { + _worldPacket.WritePackedGuid(Item); + _worldPacket.WritePackedGuid(Owner); + _worldPacket.WritePackedGuid(OwnerAccountID); + _worldPacket.WriteInt32(PetitionID); + + _worldPacket.WriteUInt32(Signatures.Count); + foreach (PetitionSignature signature in Signatures) + { + _worldPacket.WritePackedGuid(signature.Signer); + _worldPacket.WriteInt32(signature.Choice); + } + } + + public ObjectGuid Item; + public ObjectGuid Owner; + public ObjectGuid OwnerAccountID; + public int PetitionID = 0; + public List Signatures; + + public struct PetitionSignature + { + public ObjectGuid Signer; + public int Choice; + } + } + + public class SignPetition : ClientPacket + { + public SignPetition(WorldPacket packet) : base(packet) { } + + public override void Read() + { + PetitionGUID = _worldPacket.ReadPackedGuid(); + Choice = _worldPacket.ReadUInt8(); + } + + public ObjectGuid PetitionGUID; + public byte Choice = 0; + } + + public class PetitionSignResults : ServerPacket + { + public PetitionSignResults() : base(ServerOpcodes.PetitionSignResults) { } + + public override void Write() + { + _worldPacket.WritePackedGuid(Item); + _worldPacket.WritePackedGuid(Player); + + _worldPacket.WriteBits(Error, 4); + _worldPacket.FlushBits(); + } + + public ObjectGuid Item; + public ObjectGuid Player; + public PetitionSigns Error = 0; + } + + public class PetitionAlreadySigned : ServerPacket + { + public PetitionAlreadySigned() : base(ServerOpcodes.PetitionAlreadySigned) { } + + public override void Write() + { + _worldPacket.WritePackedGuid(SignerGUID); + } + + public ObjectGuid SignerGUID; + } + + public class DeclinePetition : ClientPacket + { + public DeclinePetition(WorldPacket packet) : base(packet) { } + + public override void Read() + { + PetitionGUID = _worldPacket.ReadPackedGuid(); + } + + public ObjectGuid PetitionGUID; + } + + public class TurnInPetition : ClientPacket + { + public TurnInPetition(WorldPacket packet) : base(packet) { } + + public override void Read() + { + Item = _worldPacket.ReadPackedGuid(); + } + + public ObjectGuid Item; + } + + public class TurnInPetitionResult : ServerPacket + { + public TurnInPetitionResult() : base(ServerOpcodes.TurnInPetitionResult) { } + + public override void Write() + { + _worldPacket.WriteBits(Result, 4); + _worldPacket.FlushBits(); + } + + public PetitionTurns Result = 0; // PetitionError + } + + public class OfferPetition : ClientPacket + { + public OfferPetition(WorldPacket packet) : base(packet) { } + + public override void Read() + { + ItemGUID = _worldPacket.ReadPackedGuid(); + TargetPlayer = _worldPacket.ReadPackedGuid(); + } + + public ObjectGuid TargetPlayer; + public ObjectGuid ItemGUID; + } + + public class OfferPetitionError : ServerPacket + { + public OfferPetitionError() : base(ServerOpcodes.OfferPetitionError) { } + + public override void Write() + { + _worldPacket.WritePackedGuid(PlayerGUID); + } + + public ObjectGuid PlayerGUID; + } + + public class PetitionRenameGuild : ClientPacket + { + public PetitionRenameGuild(WorldPacket packet) : base(packet) { } + + public override void Read() + { + PetitionGuid = _worldPacket.ReadPackedGuid(); + + _worldPacket.ResetBitPos(); + uint nameLen = _worldPacket.ReadBits(7); + + NewGuildName = _worldPacket.ReadString(nameLen); + } + + public ObjectGuid PetitionGuid; + public string NewGuildName; + } + + public class PetitionRenameGuildResponse : ServerPacket + { + public PetitionRenameGuildResponse() : base(ServerOpcodes.PetitionRenameGuildResponse) { } + + public override void Write() + { + _worldPacket.WritePackedGuid(PetitionGuid); + + _worldPacket.WriteBits(NewGuildName.Length, 7); + _worldPacket.FlushBits(); + + _worldPacket.WriteString(NewGuildName); + } + + public ObjectGuid PetitionGuid; + public string NewGuildName; + } + + public class PetitionInfo + { + public void Write(WorldPacket data) + { + data.WriteInt32(PetitionID); + data.WritePackedGuid(Petitioner); + + data.WriteInt32(MinSignatures); + data.WriteInt32(MaxSignatures); + data.WriteInt32(DeadLine); + data.WriteInt32(IssueDate); + data.WriteInt32(AllowedGuildID); + data.WriteInt32(AllowedClasses); + data.WriteInt32(AllowedRaces); + data.WriteInt16(AllowedGender); + data.WriteInt32(AllowedMinLevel); + data.WriteInt32(AllowedMaxLevel); + data.WriteInt32(NumChoices); + data.WriteInt32(StaticType); + data.WriteUInt32(Muid); + + data.WriteBits(Title.Length, 7); + data.WriteBits(BodyText.Length, 12); + + for (byte i = 0; i < 10; i++) + data.WriteBits(Choicetext[i].Length, 6); + + data.FlushBits(); + + for (byte i = 0; i < 10; i++) + data.WriteString(Choicetext[i]); + + data.WriteString(Title); + data.WriteString(BodyText); + } + + public int PetitionID; + public ObjectGuid Petitioner; + public string Title; + public string BodyText; + public int MinSignatures; + public int MaxSignatures; + public int DeadLine; + public int IssueDate; + public int AllowedGuildID; + public int AllowedClasses; + public int AllowedRaces; + public short AllowedGender; + public int AllowedMinLevel; + public int AllowedMaxLevel; + public int NumChoices; + public int StaticType; + public uint Muid = 0; + public StringArray Choicetext = new StringArray(10); + } +} diff --git a/Game/Network/Packets/QueryPackets.cs b/Game/Network/Packets/QueryPackets.cs new file mode 100644 index 000000000..b42eacf4f --- /dev/null +++ b/Game/Network/Packets/QueryPackets.cs @@ -0,0 +1,773 @@ +/* + * Copyright (C) 2012-2017 CypherCore + * + * 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, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +using Framework.Collections; +using Framework.Constants; +using Framework.Dynamic; +using Framework.GameMath; +using Framework.IO; +using Game.Entities; +using System.Collections.Generic; +using System.Diagnostics.Contracts; + +namespace Game.Network.Packets +{ + public class QueryPlayerName : ClientPacket + { + public QueryPlayerName(WorldPacket packet) : base(packet) { } + + public override void Read() + { + Player = _worldPacket.ReadPackedGuid(); + } + + public ObjectGuid Player; + } + + public class QueryPlayerNameResponse : ServerPacket + { + public QueryPlayerNameResponse() : base(ServerOpcodes.QueryPlayerNameResponse) + { + Data = new PlayerGuidLookupData(); + } + + public override void Write() + { + _worldPacket.WriteInt8(Result); + _worldPacket.WritePackedGuid(Player); + + if (Result == ResponseCodes.Success) + Data.Write(_worldPacket); + } + + public ObjectGuid Player; + public ResponseCodes Result; // 0 - full packet, != 0 - only guid + public PlayerGuidLookupData Data; + } + + public class QueryCreature : ClientPacket + { + public QueryCreature(WorldPacket packet) : base(packet) { } + + public override void Read() + { + CreatureID = _worldPacket.ReadUInt32(); + } + + public uint CreatureID; + } + + public class QueryCreatureResponse : ServerPacket + { + public QueryCreatureResponse() : base(ServerOpcodes.QueryCreatureResponse, ConnectionType.Instance) { } + + public override void Write() + { + _worldPacket.WriteUInt32(CreatureID); + _worldPacket.WriteBit(Allow); + _worldPacket.FlushBits(); + + if (Allow) + { + _worldPacket.WriteBits(Stats.Title.Length + 1, 11); + _worldPacket.WriteBits(Stats.TitleAlt.Length + 1, 11); + _worldPacket.WriteBits(Stats.CursorName.Length + 1, 6); + _worldPacket.WriteBit(Stats.Leader); + + for (var i = 0; i < SharedConst.MaxCreatureNames; ++i) + { + _worldPacket.WriteBits(Stats.Name[i].Length + 1, 11); + _worldPacket.WriteBits(Stats.NameAlt[i].Length + 1, 11); + } + + for (var i = 0; i < SharedConst.MaxCreatureNames; ++i) + { + if (!string.IsNullOrEmpty(Stats.Name[i])) + _worldPacket.WriteCString(Stats.Name[i]); + if (!string.IsNullOrEmpty(Stats.NameAlt[i])) + _worldPacket.WriteCString(Stats.NameAlt[i]); + } + + for (var i = 0; i < 2; ++i) + _worldPacket.WriteUInt32(Stats.Flags[i]); + + _worldPacket.WriteInt32(Stats.CreatureType); + _worldPacket.WriteInt32(Stats.CreatureFamily); + _worldPacket.WriteInt32(Stats.Classification); + + for (var i = 0; i < SharedConst.MaxCreatureKillCredit; ++i) + _worldPacket.WriteUInt32(Stats.ProxyCreatureID[i]); + + for (var i = 0; i < SharedConst.MaxCreatureModelIds; ++i) + _worldPacket.WriteUInt32(Stats.CreatureDisplayID[i]); + + _worldPacket.WriteFloat(Stats.HpMulti); + _worldPacket.WriteFloat(Stats.EnergyMulti); + + _worldPacket.WriteUInt32(Stats.QuestItems.Count); + _worldPacket.WriteUInt32(Stats.CreatureMovementInfoID); + _worldPacket.WriteInt32(Stats.HealthScalingExpansion); + _worldPacket.WriteUInt32(Stats.RequiredExpansion); + _worldPacket.WriteInt32(Stats.VignetteID); + + if (!string.IsNullOrEmpty(Stats.Title)) + _worldPacket.WriteCString(Stats.Title); + + if (!string.IsNullOrEmpty(Stats.TitleAlt)) + _worldPacket.WriteCString(Stats.TitleAlt); + + if (!string.IsNullOrEmpty(Stats.CursorName)) + _worldPacket.WriteCString(Stats.CursorName); + + foreach (var questItem in Stats.QuestItems) + _worldPacket.WriteInt32(questItem); + } + } + + public bool Allow; + public CreatureStats Stats; + public uint CreatureID; + } + + public class QueryPageText : ClientPacket + { + public QueryPageText(WorldPacket packet) : base(packet) { } + + public override void Read() + { + PageTextID = _worldPacket.ReadUInt32(); + ItemGUID = _worldPacket.ReadPackedGuid(); + } + + public ObjectGuid ItemGUID; + public uint PageTextID; + } + + public class QueryPageTextResponse : ServerPacket + { + public QueryPageTextResponse() : base(ServerOpcodes.QueryPageTextResponse) { } + + public override void Write() + { + _worldPacket.WriteUInt32(PageTextID); + _worldPacket.WriteBit(Allow); + _worldPacket.FlushBits(); + + if (Allow) + { + _worldPacket.WriteUInt32(Pages.Count); + foreach (PageTextInfo pageText in Pages) + pageText.Write(_worldPacket); + } + } + + public uint PageTextID; + public bool Allow; + public List Pages = new List(); + + public struct PageTextInfo + { + public void Write(WorldPacket data) + { + data.WriteUInt32(ID); + data.WriteUInt32(NextPageID); + data.WriteInt32(PlayerConditionID); + data.WriteUInt8(Flags); + data.WriteBits(Text.Length, 12); + data.FlushBits(); + + data.WriteString(Text); + } + + public uint ID; + public uint NextPageID; + public int PlayerConditionID; + public byte Flags; + public string Text; + } + } + + public class QueryNPCText : ClientPacket + { + public QueryNPCText(WorldPacket packet) : base(packet) { } + + public override void Read() + { + TextID = _worldPacket.ReadUInt32(); + Guid = _worldPacket.ReadPackedGuid(); + } + + public ObjectGuid Guid; + public uint TextID; + } + + public class QueryNPCTextResponse : ServerPacket + { + public QueryNPCTextResponse() : base(ServerOpcodes.QueryNpcTextResponse, ConnectionType.Instance) { } + + public override void Write() + { + _worldPacket.WriteUInt32(TextID); + _worldPacket.WriteBit(Allow); + + _worldPacket.WriteInt32(Allow ? SharedConst.MaxNpcTextOptions * (4 + 4) : 0); + if (Allow) + { + for (uint i = 0; i < SharedConst.MaxNpcTextOptions; ++i) + _worldPacket.WriteFloat(Probabilities[i]); + + for (uint i = 0; i < SharedConst.MaxNpcTextOptions; ++i) + _worldPacket.WriteUInt32(BroadcastTextID[i]); + } + } + + public uint TextID; + public bool Allow; + public float[] Probabilities = new float[SharedConst.MaxNpcTextOptions]; + public uint[] BroadcastTextID = new uint[SharedConst.MaxNpcTextOptions]; + } + + public class QueryGameObject : ClientPacket + { + public QueryGameObject(WorldPacket packet) : base(packet) { } + + public override void Read() + { + GameObjectID = _worldPacket.ReadUInt32(); + Guid = _worldPacket.ReadPackedGuid(); + } + + public uint GameObjectID; + public ObjectGuid Guid; + } + + public class QueryGameObjectResponse : ServerPacket + { + public QueryGameObjectResponse() : base(ServerOpcodes.QueryGameObjectResponse, ConnectionType.Instance) { } + + public override void Write() + { + _worldPacket.WriteUInt32(GameObjectID); + _worldPacket.WriteBit(Allow); + _worldPacket.FlushBits(); + + ByteBuffer statsData = new ByteBuffer(); + if (Allow) + { + statsData.WriteUInt32(Stats.Type); + statsData.WriteUInt32(Stats.DisplayID); + for (int i = 0; i < 4; i++) + statsData.WriteCString(Stats.Name[i]); + + statsData.WriteCString(Stats.IconName); + statsData.WriteCString(Stats.CastBarCaption); + statsData.WriteCString(Stats.UnkString); + + for (uint i = 0; i < SharedConst.MaxGOData; i++) + statsData.WriteInt32(Stats.Data[i]); + + statsData.WriteFloat(Stats.Size); + statsData.WriteUInt8(Stats.QuestItems.Count); + foreach (int questItem in Stats.QuestItems) + statsData.WriteInt32(questItem); + + statsData.WriteUInt32(Stats.RequiredLevel); + + } + + _worldPacket.WriteUInt32(statsData.GetSize()); + if (statsData.GetSize() != 0) + _worldPacket.WriteBytes(statsData); + } + + public uint GameObjectID; + public bool Allow; + public GameObjectStats Stats; + } + + public class QueryCorpseLocationFromClient : ClientPacket + { + public QueryCorpseLocationFromClient(WorldPacket packet) : base(packet) { } + + public override void Read() + { + Player = _worldPacket.ReadPackedGuid(); + } + + public ObjectGuid Player; + } + + public class CorpseLocation : ServerPacket + { + public CorpseLocation() : base(ServerOpcodes.CorpseLocation) { } + + public override void Write() + { + _worldPacket.WriteBit(Valid); + _worldPacket.FlushBits(); + + _worldPacket.WritePackedGuid(Player); + _worldPacket.WriteInt32(ActualMapID); + _worldPacket.WriteVector3(Position); + _worldPacket.WriteInt32(MapID); + _worldPacket.WritePackedGuid(Transport); + } + + public ObjectGuid Player; + public ObjectGuid Transport; + public Vector3 Position; + public int ActualMapID; + public int MapID; + public bool Valid; + } + + public class QueryCorpseTransport : ClientPacket + { + public QueryCorpseTransport(WorldPacket packet) : base(packet) { } + + public override void Read() + { + Player = _worldPacket.ReadPackedGuid(); + Transport = _worldPacket.ReadPackedGuid(); + } + + public ObjectGuid Player; + public ObjectGuid Transport; + } + + public class CorpseTransportQuery : ServerPacket + { + public CorpseTransportQuery() : base(ServerOpcodes.CorpseTransportQuery) { } + + public override void Write() + { + _worldPacket.WritePackedGuid(Player); + _worldPacket.WriteVector3(Position); + _worldPacket.WriteFloat(Facing); + } + + public ObjectGuid Player; + public Vector3 Position; + public float Facing; + } + + public class QueryTime : ClientPacket + { + public QueryTime(WorldPacket packet) : base(packet) { } + + public override void Read() { } + } + + public class QueryTimeResponse : ServerPacket + { + public QueryTimeResponse() : base(ServerOpcodes.QueryTimeResponse, ConnectionType.Instance) { } + + public override void Write() + { + _worldPacket.WriteInt32(CurrentTime); + } + + public long CurrentTime; + } + + public class QuestPOIQuery : ClientPacket + { + public QuestPOIQuery(WorldPacket packet) : base(packet) { } + + public override void Read() + { + MissingQuestCount = _worldPacket.ReadInt32(); + + for (byte i = 0; i < 50; ++i) + MissingQuestPOIs[i] = _worldPacket.ReadUInt32(); + } + + public int MissingQuestCount; + public uint[] MissingQuestPOIs = new uint[50]; + } + + public class QuestPOIQueryResponse : ServerPacket + { + public QuestPOIQueryResponse() : base(ServerOpcodes.QuestPoiQueryResponse) { } + + public override void Write() + { + _worldPacket.WriteInt32(QuestPOIDataStats.Count); + _worldPacket.WriteInt32(QuestPOIDataStats.Count); + + foreach (QuestPOIData questPOIData in QuestPOIDataStats) + { + _worldPacket.WriteInt32(questPOIData.QuestID); + + _worldPacket.WriteInt32(questPOIData.QuestPOIBlobDataStats.Count); + + foreach (QuestPOIBlobData questPOIBlobData in questPOIData.QuestPOIBlobDataStats) + { + _worldPacket.WriteInt32(questPOIBlobData.BlobIndex); + _worldPacket.WriteInt32(questPOIBlobData.ObjectiveIndex); + _worldPacket.WriteInt32(questPOIBlobData.QuestObjectiveID); + _worldPacket.WriteInt32(questPOIBlobData.QuestObjectID); + _worldPacket.WriteInt32(questPOIBlobData.MapID); + _worldPacket.WriteInt32(questPOIBlobData.WorldMapAreaID); + _worldPacket.WriteInt32(questPOIBlobData.Floor); + _worldPacket.WriteInt32(questPOIBlobData.Priority); + _worldPacket.WriteInt32(questPOIBlobData.Flags); + _worldPacket.WriteInt32(questPOIBlobData.WorldEffectID); + _worldPacket.WriteInt32(questPOIBlobData.PlayerConditionID); + _worldPacket.WriteInt32(questPOIBlobData.UnkWoD1); + _worldPacket.WriteInt32(questPOIBlobData.QuestPOIBlobPointStats.Count); + + foreach (QuestPOIBlobPoint questPOIBlobPoint in questPOIBlobData.QuestPOIBlobPointStats) + { + _worldPacket.WriteInt32(questPOIBlobPoint.X); + _worldPacket.WriteInt32(questPOIBlobPoint.Y); + } + } + } + } + + public List QuestPOIDataStats = new List(); + } + + class QueryQuestCompletionNPCs : ClientPacket + { + public QueryQuestCompletionNPCs(WorldPacket packet) : base(packet) { } + + public override void Read() + { + uint questCount = _worldPacket.ReadUInt32(); + QuestCompletionNPCs = new uint[questCount]; + + for (uint i = 0; i < questCount; ++i) + QuestCompletionNPCs[i] = _worldPacket.ReadUInt32(); + } + + public uint[] QuestCompletionNPCs; + } + + class QuestCompletionNPCResponse : ServerPacket + { + public QuestCompletionNPCResponse() : base(ServerOpcodes.QuestCompletionNpcResponse, ConnectionType.Instance) { } + + public override void Write() + { + _worldPacket.WriteUInt32(QuestCompletionNPCs.Count); + foreach (var quest in QuestCompletionNPCs) + { + _worldPacket.WriteUInt32(quest.QuestID); + + _worldPacket.WriteUInt32(quest.NPCs.Count); + foreach (var npc in quest.NPCs) + _worldPacket.WriteUInt32(npc); + } + } + + public List QuestCompletionNPCs = new List(); + } + + class QueryPetName : ClientPacket + { + public QueryPetName(WorldPacket packet) : base(packet) { } + + public override void Read() + { + UnitGUID = _worldPacket.ReadPackedGuid(); + } + + public ObjectGuid UnitGUID; + } + + class QueryPetNameResponse : ServerPacket + { + public QueryPetNameResponse() : base(ServerOpcodes.QueryPetNameResponse, ConnectionType.Instance) { } + + public override void Write() + { + _worldPacket.WritePackedGuid(UnitGUID); + _worldPacket.WriteBit(Allow); + _worldPacket.FlushBits(); + + if (Allow) + { + _worldPacket.WriteBits(Name.Length, 8); + _worldPacket.WriteBit(HasDeclined); + + for (byte i = 0; i < SharedConst.MaxDeclinedNameCases; ++i) + _worldPacket.WriteBits(DeclinedNames.name[i].Length, 7); + + for (byte i = 0; i < SharedConst.MaxDeclinedNameCases; ++i) + _worldPacket.WriteString(DeclinedNames.name[i]); + + _worldPacket.WriteUInt32(Timestamp); + _worldPacket.WriteString(Name); + } + } + + public ObjectGuid UnitGUID; + public bool Allow; + + public bool HasDeclined; + public DeclinedName DeclinedNames; + public uint Timestamp; + public string Name = ""; + } + + class ItemTextQuery : ClientPacket + { + public ItemTextQuery(WorldPacket packet) : base(packet) { } + + public override void Read() + { + Id = _worldPacket.ReadPackedGuid(); + } + + public ObjectGuid Id; + } + + class QueryItemTextResponse : ServerPacket + { + public QueryItemTextResponse() : base(ServerOpcodes.QueryItemTextResponse) { } + + public override void Write() + { + _worldPacket.WriteBit(Valid); + _worldPacket.WriteBits(Text.Length, 13); + _worldPacket.FlushBits(); + + _worldPacket.WriteString(Text); + _worldPacket.WritePackedGuid(Id); + } + + public ObjectGuid Id; + public bool Valid; + public string Text; + } + + class QueryRealmName : ClientPacket + { + public QueryRealmName(WorldPacket packet) : base(packet) { } + + public override void Read() + { + VirtualRealmAddress = _worldPacket.ReadUInt32(); + } + + public uint VirtualRealmAddress; + } + + class RealmQueryResponse : ServerPacket + { + public RealmQueryResponse() : base(ServerOpcodes.RealmQueryResponse) { } + + public override void Write() + { + _worldPacket.WriteUInt32(VirtualRealmAddress); + _worldPacket.WriteUInt8(LookupState); + if (LookupState == 0) + NameInfo.Write(_worldPacket); + } + + public uint VirtualRealmAddress; + public byte LookupState; + public VirtualRealmNameInfo NameInfo; + } + + //Structs + public class PlayerGuidLookupHint + { + public void Write(WorldPacket data) + { + data.WriteBit(VirtualRealmAddress.HasValue); + data.WriteBit(NativeRealmAddress.HasValue); + data.FlushBits(); + + if (VirtualRealmAddress.HasValue) + data.WriteUInt32(VirtualRealmAddress.Value); + + if (NativeRealmAddress.HasValue) + data.WriteUInt32(NativeRealmAddress.Value); + } + + public Optional VirtualRealmAddress = new Optional(); // current realm (?) (identifier made from the Index, BattleGroup and Region) + public Optional NativeRealmAddress = new Optional(); // original realm (?) (identifier made from the Index, BattleGroup and Region) + } + + public class PlayerGuidLookupData + { + public bool Initialize(ObjectGuid guid, Player player = null) + { + CharacterInfo characterInfo = Global.WorldMgr.GetCharacterInfo(guid); + if (characterInfo == null) + return false; + + if (player) + { + Contract.Assert(player.GetGUID() == guid); + + AccountID = player.GetSession().GetAccountGUID(); + BnetAccountID = player.GetSession().GetBattlenetAccountGUID(); + Name = player.GetName(); + RaceID = player.GetRace(); + Sex = (Gender)player.GetByteValue(PlayerFields.Bytes3, PlayerFieldOffsets.Bytes3OffsetGender); + ClassID = player.GetClass(); + Level = (byte)player.getLevel(); + + DeclinedName names = player.GetDeclinedNames(); + if (names != null) + DeclinedNames = names; + } + else + { + uint accountId = ObjectManager.GetPlayerAccountIdByGUID(guid); + uint bnetAccountId = Global.BNetAccountMgr.GetIdByGameAccount(accountId); + + AccountID = ObjectGuid.Create(HighGuid.WowAccount, accountId); + BnetAccountID = ObjectGuid.Create(HighGuid.BNetAccount, bnetAccountId); + Name = characterInfo.Name; + RaceID = characterInfo.RaceID; + Sex = characterInfo.Sex; + ClassID = characterInfo.ClassID; + Level = characterInfo.Level; + } + + IsDeleted = characterInfo.IsDeleted; + GuidActual = guid; + VirtualRealmAddress = Global.WorldMgr.GetVirtualRealmAddress(); + + return true; + } + + public void Write(WorldPacket data) + { + data.WriteBit(IsDeleted); + data.WriteBits(Name.Length, 6); + + for (byte i = 0; i < SharedConst.MaxDeclinedNameCases; ++i) + data.WriteBits(DeclinedNames.name[i].Length, 7); + + data.FlushBits(); + for (byte i = 0; i < SharedConst.MaxDeclinedNameCases; ++i) + data.WriteString(DeclinedNames.name[i]); + + data.WritePackedGuid(AccountID); + data.WritePackedGuid(BnetAccountID); + data.WritePackedGuid(GuidActual); + data.WriteUInt32(VirtualRealmAddress); + data.WriteUInt8(RaceID); + data.WriteUInt8(Sex); + data.WriteUInt8(ClassID); + data.WriteUInt8(Level); + data.WriteString(Name); + } + + public bool IsDeleted; + public ObjectGuid AccountID; + public ObjectGuid BnetAccountID; + public ObjectGuid GuidActual; + public string Name = ""; + public uint VirtualRealmAddress; + public Race RaceID = Race.None; + public Gender Sex = Gender.None; + public Class ClassID = Class.None; + public byte Level; + public DeclinedName DeclinedNames = new DeclinedName(); + } + + public class CreatureStats + { + public string Title = ""; + public string TitleAlt = ""; + public string CursorName = ""; + public int CreatureType; + public int CreatureFamily; + public int Classification; + public float HpMulti; + public float EnergyMulti; + public bool Leader; + public List QuestItems = new List(); + public uint CreatureMovementInfoID; + public int HealthScalingExpansion; + public uint RequiredExpansion; + public uint VignetteID; + public uint[] Flags = new uint[2]; + public uint[] ProxyCreatureID = new uint[SharedConst.MaxCreatureKillCredit]; + public uint[] CreatureDisplayID = new uint[SharedConst.MaxCreatureModelIds]; + public StringArray Name = new StringArray(SharedConst.MaxCreatureNames); + public StringArray NameAlt = new StringArray(SharedConst.MaxCreatureNames); + } + + public struct DBQueryRecord + { + public ObjectGuid GUID; + public uint RecordID; + } + + public class GameObjectStats + { + public string[] Name = new string[4]; + public string IconName; + public string CastBarCaption; + public string UnkString; + public uint Type; + public uint DisplayID; + public int[] Data = new int[33]; + public float Size; + public List QuestItems = new List(); + public uint RequiredLevel; + } + + public struct QuestPOIBlobPoint + { + public QuestPOIBlobPoint(int x, int y) + { + X = x; + Y = y; + } + + public int X; + public int Y; + } + + public class QuestPOIBlobData + { + public int BlobIndex; + public int ObjectiveIndex; + public int QuestObjectiveID; + public int QuestObjectID; + public int MapID; + public int WorldMapAreaID; + public int Floor; + public int Priority; + public int Flags; + public int WorldEffectID; + public int PlayerConditionID; + public int UnkWoD1; + public List QuestPOIBlobPointStats = new List(); + } + + public class QuestPOIData + { + public uint QuestID; + public List QuestPOIBlobDataStats = new List(); + } + + class QuestCompletionNPC + { + public uint QuestID; + public List NPCs = new List(); + } +} diff --git a/Game/Network/Packets/QuestPackets.cs b/Game/Network/Packets/QuestPackets.cs new file mode 100644 index 000000000..bf873cfeb --- /dev/null +++ b/Game/Network/Packets/QuestPackets.cs @@ -0,0 +1,1129 @@ +/* + * Copyright (C) 2012-2017 CypherCore + * + * 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, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +using Framework.Constants; +using Game.Entities; +using System.Collections.Generic; +using System; + +namespace Game.Network.Packets +{ + public class QuestGiverStatusQuery : ClientPacket + { + public QuestGiverStatusQuery(WorldPacket packet) : base(packet) { } + + public override void Read() + { + QuestGiverGUID = _worldPacket.ReadPackedGuid(); + } + + public ObjectGuid QuestGiverGUID; + } + + public class QuestGiverStatusMultipleQuery : ClientPacket + { + public QuestGiverStatusMultipleQuery(WorldPacket packet) : base(packet) { } + + public override void Read() { } + } + + public class QuestGiverStatusPkt : ServerPacket + { + public QuestGiverStatusPkt() : base(ServerOpcodes.QuestGiverStatus, ConnectionType.Instance) + { + QuestGiver = new QuestGiverInfo(); + } + + public override void Write() + { + _worldPacket.WritePackedGuid(QuestGiver.Guid); + _worldPacket.WriteUInt32(QuestGiver.Status); + } + + public QuestGiverInfo QuestGiver; + } + + public class QuestGiverStatusMultiple : ServerPacket + { + public QuestGiverStatusMultiple() : base(ServerOpcodes.QuestGiverStatusMultiple, ConnectionType.Instance) { } + + public override void Write() + { + _worldPacket.WriteInt32(QuestGiver.Count); + foreach (QuestGiverInfo questGiver in QuestGiver) + { + _worldPacket.WritePackedGuid(questGiver.Guid); + _worldPacket.WriteUInt32(questGiver.Status); + } + } + + public List QuestGiver = new List(); + } + + public class QuestGiverHello : ClientPacket + { + public QuestGiverHello(WorldPacket packet) : base(packet) { } + + public override void Read() + { + QuestGiverGUID = _worldPacket.ReadPackedGuid(); + } + + public ObjectGuid QuestGiverGUID; + } + + public class QueryQuestInfo : ClientPacket + { + public QueryQuestInfo(WorldPacket packet) : base(packet) { } + + public override void Read() + { + QuestID = _worldPacket.ReadUInt32(); + QuestGiver = _worldPacket.ReadPackedGuid(); + } + + public ObjectGuid QuestGiver; + public uint QuestID; + } + + public class QueryQuestInfoResponse : ServerPacket + { + public QueryQuestInfoResponse() : base(ServerOpcodes.QueryQuestInfoResponse, ConnectionType.Instance) { } + + public override void Write() + { + _worldPacket.WriteUInt32(QuestID); + + _worldPacket.WriteBit(Allow); + _worldPacket.FlushBits(); + + if (Allow) + { + _worldPacket.WriteInt32(Info.QuestID); + _worldPacket.WriteInt32(Info.QuestType); + _worldPacket.WriteInt32(Info.QuestLevel); + _worldPacket.WriteInt32(Info.QuestPackageID); + _worldPacket.WriteInt32(Info.QuestMinLevel); + _worldPacket.WriteInt32(Info.QuestSortID); + _worldPacket.WriteInt32(Info.QuestInfoID); + _worldPacket.WriteInt32(Info.SuggestedGroupNum); + _worldPacket.WriteInt32(Info.RewardNextQuest); + _worldPacket.WriteInt32(Info.RewardXPDifficulty); + _worldPacket.WriteFloat(Info.RewardXPMultiplier); + _worldPacket.WriteInt32(Info.RewardMoney); + _worldPacket.WriteInt32(Info.RewardMoneyDifficulty); + _worldPacket.WriteFloat(Info.RewardMoneyMultiplier); + _worldPacket.WriteInt32(Info.RewardBonusMoney); + + foreach (uint id in Info.RewardDisplaySpell) + _worldPacket.WriteInt32(id); + + _worldPacket.WriteInt32(Info.RewardSpell); + _worldPacket.WriteInt32(Info.RewardHonor); + _worldPacket.WriteFloat(Info.RewardKillHonor); + _worldPacket .WriteInt32(Info.RewardArtifactXPDifficulty); + _worldPacket .WriteFloat(Info.RewardArtifactXPMultiplier); + _worldPacket .WriteInt32(Info.RewardArtifactCategoryID); + _worldPacket.WriteInt32(Info.StartItem); + _worldPacket.WriteUInt32(Info.Flags); + _worldPacket.WriteUInt32(Info.FlagsEx); + + for (uint i = 0; i < SharedConst.QuestRewardItemCount; ++i) + { + _worldPacket.WriteInt32(Info.RewardItems[i]); + _worldPacket.WriteInt32(Info.RewardAmount[i]); + _worldPacket.WriteInt32(Info.ItemDrop[i]); + _worldPacket.WriteInt32(Info.ItemDropQuantity[i]); + } + + for (uint i = 0; i < SharedConst.QuestRewardChoicesCount; ++i) + { + _worldPacket.WriteInt32(Info.UnfilteredChoiceItems[i].ItemID); + _worldPacket.WriteInt32(Info.UnfilteredChoiceItems[i].Quantity); + _worldPacket.WriteInt32(Info.UnfilteredChoiceItems[i].DisplayID); + } + + _worldPacket.WriteInt32(Info.POIContinent); + _worldPacket.WriteFloat(Info.POIx); + _worldPacket.WriteFloat(Info.POIy); + _worldPacket.WriteInt32(Info.POIPriority); + + _worldPacket.WriteInt32(Info.RewardTitle); + _worldPacket.WriteInt32(Info.RewardArenaPoints); + _worldPacket.WriteInt32(Info.RewardSkillLineID); + _worldPacket.WriteInt32(Info.RewardNumSkillUps); + + _worldPacket.WriteInt32(Info.PortraitGiver); + _worldPacket.WriteInt32(Info.PortraitTurnIn); + + for (uint i = 0; i < SharedConst.QuestRewardReputationsCount; ++i) + { + _worldPacket.WriteInt32(Info.RewardFactionID[i]); + _worldPacket.WriteInt32(Info.RewardFactionValue[i]); + _worldPacket.WriteInt32(Info.RewardFactionOverride[i]); + _worldPacket.WriteInt32(Info.RewardFactionCapIn[i]); + } + + _worldPacket.WriteInt32(Info.RewardFactionFlags); + + for (uint i = 0; i < SharedConst.QuestRewardCurrencyCount; ++i) + { + _worldPacket.WriteInt32(Info.RewardCurrencyID[i]); + _worldPacket.WriteInt32(Info.RewardCurrencyQty[i]); + } + + _worldPacket.WriteInt32(Info.AcceptedSoundKitID); + _worldPacket.WriteInt32(Info.CompleteSoundKitID); + + _worldPacket.WriteInt32(Info.AreaGroupID); + _worldPacket.WriteInt32(Info.TimeAllowed); + + _worldPacket.WriteUInt32(Info.Objectives.Count); + _worldPacket.WriteInt32(Info.AllowableRaces); + _worldPacket.WriteInt32(Info.QuestRewardID); + _worldPacket.WriteInt32(Info.Expansion); + + _worldPacket.WriteBits(Info.LogTitle.Length, 9); + _worldPacket.WriteBits(Info.LogDescription.Length, 12); + _worldPacket.WriteBits(Info.QuestDescription.Length, 12); + _worldPacket.WriteBits(Info.AreaDescription.Length, 9); + _worldPacket.WriteBits(Info.PortraitGiverText.Length, 10); + _worldPacket.WriteBits(Info.PortraitGiverName.Length, 8); + _worldPacket.WriteBits(Info.PortraitTurnInText.Length, 10); + _worldPacket.WriteBits(Info.PortraitTurnInName.Length, 8); + _worldPacket.WriteBits(Info.QuestCompletionLog.Length, 11); + _worldPacket.FlushBits(); + + foreach (QuestObjective questObjective in Info.Objectives) + { + _worldPacket.WriteUInt32(questObjective.ID); + _worldPacket.WriteUInt8(questObjective.Type); + _worldPacket.WriteInt8(questObjective.StorageIndex); + _worldPacket.WriteInt32(questObjective.ObjectID); + _worldPacket.WriteInt32(questObjective.Amount); + _worldPacket.WriteUInt32(questObjective.Flags); + _worldPacket.WriteUInt32(questObjective.Flags2); + _worldPacket.WriteFloat(questObjective.ProgressBarWeight); + + _worldPacket.WriteInt32(questObjective.VisualEffects.Length); + foreach (var visualEffect in questObjective.VisualEffects) + _worldPacket.WriteInt32(visualEffect); + + _worldPacket.WriteBits(questObjective.Description.Length, 8); + _worldPacket.FlushBits(); + + _worldPacket.WriteString(questObjective.Description); + } + + _worldPacket.WriteString(Info.LogTitle); + _worldPacket.WriteString(Info.LogDescription); + _worldPacket.WriteString(Info.QuestDescription); + _worldPacket.WriteString(Info.AreaDescription); + _worldPacket.WriteString(Info.PortraitGiverText); + _worldPacket.WriteString(Info.PortraitGiverName); + _worldPacket.WriteString(Info.PortraitTurnInText); + _worldPacket.WriteString(Info.PortraitTurnInName); + _worldPacket.WriteString(Info.QuestCompletionLog); + } + } + + public bool Allow; + public QuestInfo Info = new QuestInfo(); + public uint QuestID; + } + + public class QuestUpdateAddCredit : ServerPacket + { + public QuestUpdateAddCredit() : base(ServerOpcodes.QuestUpdateAddCredit, ConnectionType.Instance) { } + + public override void Write() + { + _worldPacket.WritePackedGuid(VictimGUID); + _worldPacket.WriteUInt32(QuestID); + _worldPacket.WriteInt32(ObjectID); + _worldPacket.WriteUInt16(Count); + _worldPacket.WriteUInt16(Required); + _worldPacket.WriteUInt8(ObjectiveType); + } + + public ObjectGuid VictimGUID; + public int ObjectID; + public uint QuestID; + public ushort Count; + public ushort Required; + public byte ObjectiveType; + } + + class QuestUpdateAddCreditSimple : ServerPacket + { + public QuestUpdateAddCreditSimple() : base(ServerOpcodes.QuestUpdateAddCreditSimple, ConnectionType.Instance) { } + + public override void Write() + { + _worldPacket.WriteUInt32(QuestID); + _worldPacket.WriteInt32(ObjectID); + _worldPacket.WriteUInt8(ObjectiveType); + } + + public uint QuestID; + public int ObjectID; + public QuestObjectiveType ObjectiveType; + } + + class QuestUpdateAddPvPCredit : ServerPacket + { + public QuestUpdateAddPvPCredit() : base(ServerOpcodes.QuestUpdateAddPvpCredit, ConnectionType.Instance) { } + + public override void Write() + { + _worldPacket.WriteUInt32(QuestID); + _worldPacket.WriteUInt16(Count); + } + + public uint QuestID; + public ushort Count; + } + + public class QuestGiverOfferRewardMessage : ServerPacket + { + public QuestGiverOfferRewardMessage() : base(ServerOpcodes.QuestGiverOfferRewardMessage) { } + + public override void Write() + { + QuestData.Write(_worldPacket); + _worldPacket.WriteInt32(QuestPackageID); + _worldPacket.WriteInt32(PortraitGiver); + _worldPacket.WriteInt32(PortraitTurnIn); + + _worldPacket.WriteBits(QuestTitle.Length, 9); + _worldPacket.WriteBits(RewardText.Length, 12); + _worldPacket.WriteBits(PortraitGiverText.Length, 10); + _worldPacket.WriteBits(PortraitGiverName.Length, 8); + _worldPacket.WriteBits(PortraitTurnInText.Length, 10); + _worldPacket.WriteBits(PortraitTurnInName.Length, 8); + + _worldPacket.WriteString(QuestTitle); + _worldPacket.WriteString(RewardText); + _worldPacket.WriteString(PortraitGiverText); + _worldPacket.WriteString(PortraitGiverName); + _worldPacket.WriteString(PortraitTurnInText); + _worldPacket.WriteString(PortraitTurnInName); + } + + public uint PortraitTurnIn; + public uint PortraitGiver; + public string QuestTitle = ""; + public string RewardText = ""; + public string PortraitGiverText = ""; + public string PortraitGiverName = ""; + public string PortraitTurnInText = ""; + public string PortraitTurnInName = ""; + public QuestGiverOfferReward QuestData; + public uint QuestPackageID; + } + + public class QuestGiverChooseReward : ClientPacket + { + public QuestGiverChooseReward(WorldPacket packet) : base(packet) { } + + public override void Read() + { + QuestGiverGUID = _worldPacket.ReadPackedGuid(); + QuestID = _worldPacket.ReadUInt32(); + ItemChoiceID = _worldPacket.ReadUInt32(); + } + + public ObjectGuid QuestGiverGUID; + public uint QuestID; + public uint ItemChoiceID; + } + + public class QuestGiverQuestComplete : ServerPacket + { + public QuestGiverQuestComplete() : base(ServerOpcodes.QuestGiverQuestComplete) { } + + public override void Write() + { + _worldPacket.WriteUInt32(QuestID); + _worldPacket.WriteUInt32(XPReward); + _worldPacket.WriteInt64(MoneyReward); + _worldPacket.WriteUInt32(SkillLineIDReward); + _worldPacket.WriteUInt32(NumSkillUpsReward); + + _worldPacket.WriteBit(UseQuestReward); + _worldPacket.WriteBit(LaunchGossip); + _worldPacket.WriteBit(LaunchQuest); + _worldPacket.WriteBit(HideChatMessage); + + ItemReward.Write(_worldPacket); + } + + public uint QuestID; + public uint XPReward; + public long MoneyReward; + public uint SkillLineIDReward; + public uint NumSkillUpsReward; + public bool UseQuestReward; + public bool LaunchGossip; + public bool LaunchQuest; + public bool HideChatMessage; + public ItemInstance ItemReward = new ItemInstance(); + } + + public class QuestGiverCompleteQuest : ClientPacket + { + public QuestGiverCompleteQuest(WorldPacket packet) : base(packet) { } + + public override void Read() + { + QuestGiverGUID = _worldPacket.ReadPackedGuid(); + QuestID = _worldPacket.ReadUInt32(); + FromScript = _worldPacket.HasBit(); + } + + public ObjectGuid QuestGiverGUID; // NPC / GameObject guid for normal quest completion. Player guid for self-completed quests + public uint QuestID; + public bool FromScript; // 0 - standart complete quest mode with npc, 1 - auto-complete mode + } + + public class QuestGiverQuestDetails : ServerPacket + { + public QuestGiverQuestDetails() : base(ServerOpcodes.QuestGiverQuestDetails) { } + + public override void Write() + { + _worldPacket.WritePackedGuid(QuestGiverGUID); + _worldPacket.WritePackedGuid(InformUnit); + _worldPacket.WriteInt32(QuestID); + _worldPacket.WriteInt32(QuestPackageID); + _worldPacket.WriteInt32(PortraitGiver); + _worldPacket.WriteInt32(PortraitTurnIn); + _worldPacket.WriteUInt32(QuestFlags[0]); // Flags + _worldPacket.WriteUInt32(QuestFlags[1]); // FlagsEx + _worldPacket.WriteInt32(SuggestedPartyMembers); + _worldPacket.WriteInt32(LearnSpells.Count); + _worldPacket.WriteUInt32(DescEmotes.Count); + _worldPacket.WriteUInt32(Objectives.Count); + _worldPacket.WriteInt32(QuestStartItemID); + + foreach (int spell in LearnSpells) + _worldPacket.WriteInt32(spell); + + foreach (QuestDescEmote emote in DescEmotes) + { + _worldPacket.WriteInt32(emote.Type); + _worldPacket.WriteUInt32(emote.Delay); + } + + foreach (QuestObjectiveSimple obj in Objectives) + { + _worldPacket.WriteInt32(obj.ID); + _worldPacket.WriteInt32(obj.ObjectID); + _worldPacket.WriteInt32(obj.Amount); + _worldPacket.WriteInt8(obj.Type); + } + + _worldPacket.WriteBits(QuestTitle.Length, 9); + _worldPacket.WriteBits(DescriptionText.Length, 12); + _worldPacket.WriteBits(LogDescription.Length, 12); + _worldPacket.WriteBits(PortraitGiverText.Length, 10); + _worldPacket.WriteBits(PortraitGiverName.Length, 8); + _worldPacket.WriteBits(PortraitTurnInText.Length, 10); + _worldPacket.WriteBits(PortraitTurnInName.Length, 8); + _worldPacket.WriteBit(AutoLaunched); + _worldPacket.WriteBit(StartCheat); + _worldPacket.WriteBit(DisplayPopup); + _worldPacket.FlushBits(); + + Rewards.Write(_worldPacket); + + _worldPacket.WriteString(QuestTitle); + _worldPacket.WriteString(DescriptionText); + _worldPacket.WriteString(LogDescription); + _worldPacket.WriteString(PortraitGiverText); + _worldPacket.WriteString(PortraitGiverName); + _worldPacket.WriteString(PortraitTurnInText); + _worldPacket.WriteString(PortraitTurnInName); + } + + public ObjectGuid QuestGiverGUID; + public ObjectGuid InformUnit; + public uint QuestID; + public int QuestPackageID; + public uint[] QuestFlags = new uint[2]; + public uint SuggestedPartyMembers; + public QuestRewards Rewards = new QuestRewards(); + public List Objectives = new List(); + public List DescEmotes = new List(); + public List LearnSpells = new List(); + public uint PortraitTurnIn; + public uint PortraitGiver; + public int QuestStartItemID; + public string PortraitGiverText = ""; + public string PortraitGiverName = ""; + public string PortraitTurnInText = ""; + public string PortraitTurnInName = ""; + public string QuestTitle = ""; + public string LogDescription = ""; + public string DescriptionText = ""; + public bool DisplayPopup; + public bool StartCheat; + public bool AutoLaunched; + } + + public class QuestGiverRequestItems : ServerPacket + { + public QuestGiverRequestItems() : base(ServerOpcodes.QuestGiverRequestItems) { } + + public override void Write() + { + _worldPacket.WritePackedGuid(QuestGiverGUID); + _worldPacket.WriteInt32(QuestGiverCreatureID); + _worldPacket.WriteInt32(QuestID); + _worldPacket.WriteInt32(CompEmoteDelay); + _worldPacket.WriteInt32(CompEmoteType); + _worldPacket.WriteUInt32(QuestFlags[0]); + _worldPacket.WriteUInt32(QuestFlags[1]); + _worldPacket.WriteInt32(SuggestPartyMembers); + _worldPacket.WriteInt32(MoneyToGet); + _worldPacket.WriteInt32(Collect.Count); + _worldPacket.WriteInt32(Currency.Count); + _worldPacket.WriteInt32(StatusFlags); + + foreach (QuestObjectiveCollect obj in Collect) + { + _worldPacket.WriteInt32(obj.ObjectID); + _worldPacket.WriteInt32(obj.Amount); + _worldPacket.WriteUInt32(obj.Flags); + } + foreach (QuestCurrency cur in Currency) + { + _worldPacket.WriteInt32(cur.CurrencyID); + _worldPacket.WriteInt32(cur.Amount); + } + + _worldPacket.WriteBit(AutoLaunched); + _worldPacket.FlushBits(); + + _worldPacket.WriteBits(QuestTitle.Length, 9); + _worldPacket.WriteBits(CompletionText.Length, 12); + + _worldPacket.WriteString(QuestTitle); + _worldPacket.WriteString(CompletionText); + } + + public ObjectGuid QuestGiverGUID; + public uint QuestGiverCreatureID; + public uint QuestID; + public uint CompEmoteDelay; + public uint CompEmoteType; + public bool AutoLaunched; + public uint SuggestPartyMembers; + public int MoneyToGet; + public List Collect = new List(); + public List Currency = new List(); + public int StatusFlags; + public uint[] QuestFlags = new uint[2]; + public string QuestTitle = ""; + public string CompletionText = ""; + } + + public class QuestGiverRequestReward : ClientPacket + { + public QuestGiverRequestReward(WorldPacket packet) : base(packet) { } + + public override void Read() + { + QuestGiverGUID = _worldPacket.ReadPackedGuid(); + QuestID = _worldPacket.ReadUInt32(); + } + + public ObjectGuid QuestGiverGUID; + public uint QuestID; + } + + public class QuestGiverQueryQuest : ClientPacket + { + public QuestGiverQueryQuest(WorldPacket packet) : base(packet) { } + + public override void Read() + { + QuestGiverGUID = _worldPacket.ReadPackedGuid(); + QuestID = _worldPacket.ReadUInt32(); + RespondToGiver = _worldPacket.HasBit(); + } + + public ObjectGuid QuestGiverGUID; + public uint QuestID; + public bool RespondToGiver; + } + + public class QuestGiverAcceptQuest : ClientPacket + { + public ObjectGuid QuestGiverGUID; + public uint QuestID; + public bool StartCheat; + + public QuestGiverAcceptQuest(WorldPacket packet) : base(packet) { } + + public override void Read() + { + QuestGiverGUID = _worldPacket.ReadPackedGuid(); + QuestID = _worldPacket.ReadUInt32(); + StartCheat = _worldPacket.HasBit(); + } + } + + public class QuestLogRemoveQuest : ClientPacket + { + public QuestLogRemoveQuest(WorldPacket packet) : base(packet) { } + + public override void Read() + { + Entry = _worldPacket.ReadUInt8(); + } + + public byte Entry; + } + + public class QuestGiverQuestList : ServerPacket + { + public QuestGiverQuestList() : base(ServerOpcodes.QuestGiverQuestListMessage) { } + + public override void Write() + { + _worldPacket.WritePackedGuid(QuestGiverGUID); + _worldPacket.WriteUInt32(GreetEmoteDelay); + _worldPacket.WriteUInt32(GreetEmoteType); + _worldPacket.WriteUInt32(GossipTexts.Count); + _worldPacket.WriteBits(Greeting.Length, 11); + _worldPacket.FlushBits(); + + foreach (GossipTextData gossip in GossipTexts) + { + _worldPacket.WriteUInt32(gossip.QuestID); + _worldPacket.WriteUInt32(gossip.QuestType); + _worldPacket.WriteUInt32(gossip.QuestLevel); + _worldPacket.WriteUInt32(gossip.QuestFlags); + _worldPacket.WriteUInt32(gossip.QuestFlagsEx); + + _worldPacket.WriteBit(gossip.Repeatable); + _worldPacket.WriteBits(gossip.QuestTitle.Length, 9); + _worldPacket.FlushBits(); + + _worldPacket.WriteString(gossip.QuestTitle); + } + + _worldPacket.WriteString(Greeting); + } + + public ObjectGuid QuestGiverGUID; + public uint GreetEmoteDelay; + public uint GreetEmoteType; + public List GossipTexts = new List(); + public string Greeting = ""; + } + + class QuestUpdateComplete : ServerPacket + { + public QuestUpdateComplete() : base(ServerOpcodes.QuestUpdateComplete) { } + + public override void Write() + { + _worldPacket.WriteUInt32(QuestID); + } + + public uint QuestID; + } + + class QuestConfirmAcceptResponse : ServerPacket + { + public QuestConfirmAcceptResponse() : base(ServerOpcodes.QuestConfirmAccept) { } + + public override void Write() + { + _worldPacket.WriteUInt32(QuestID); + _worldPacket.WritePackedGuid(InitiatedBy); + + _worldPacket.WriteBits(QuestTitle.Length, 10); + _worldPacket.WriteString(QuestTitle); + } + + public ObjectGuid InitiatedBy; + public uint QuestID; + public string QuestTitle; + } + + class QuestConfirmAccept : ClientPacket + { + public QuestConfirmAccept(WorldPacket packet) : base(packet) { } + + public override void Read() + { + QuestID = _worldPacket.ReadUInt32(); + } + + public uint QuestID; + } + + class QuestPushResultResponse : ServerPacket + { + public QuestPushResultResponse() : base(ServerOpcodes.QuestPushResult) { } + + public override void Write() + { + _worldPacket.WritePackedGuid(SenderGUID); + _worldPacket.WriteUInt8(Result); + } + + public ObjectGuid SenderGUID; + public QuestPushReason Result; + } + + class QuestPushResult : ClientPacket + { + public QuestPushResult(WorldPacket packet) : base(packet) { } + + public override void Read() + { + SenderGUID = _worldPacket.ReadPackedGuid(); + QuestID = _worldPacket.ReadUInt32(); + Result = (QuestPushReason)_worldPacket.ReadUInt8(); + } + + public ObjectGuid SenderGUID; + public uint QuestID; + public QuestPushReason Result; + } + + class QuestGiverInvalidQuest : ServerPacket + { + public QuestGiverInvalidQuest() : base(ServerOpcodes.QuestGiverInvalidQuest) { } + + public override void Write() + { + _worldPacket.WriteUInt32(Reason); + _worldPacket.WriteInt32(ContributionRewardID); + + _worldPacket.WriteBit(SendErrorMessage); + _worldPacket.WriteBits(ReasonText.Length, 9); + _worldPacket.FlushBits(); + + _worldPacket.WriteString(ReasonText); + } + + public QuestFailedReasons Reason; + public int ContributionRewardID; + public bool SendErrorMessage; + public string ReasonText = ""; + } + + class QuestUpdateFailedTimer : ServerPacket + { + public QuestUpdateFailedTimer() : base(ServerOpcodes.QuestUpdateFailedTimer) { } + + public override void Write() + { + _worldPacket.WriteUInt32(QuestID); + } + + public uint QuestID; + } + + class QuestGiverQuestFailed : ServerPacket + { + public QuestGiverQuestFailed() : base(ServerOpcodes.QuestGiverQuestFailed) { } + + public override void Write() + { + _worldPacket.WriteUInt32(QuestID); + _worldPacket.WriteUInt32(Reason); + } + + public uint QuestID; + public InventoryResult Reason; + } + + class PushQuestToParty : ClientPacket + { + public PushQuestToParty(WorldPacket packet) : base(packet) { } + + public override void Read() + { + QuestID = _worldPacket.ReadUInt32(); + } + + public uint QuestID; + } + + class DailyQuestsReset : ServerPacket + { + public DailyQuestsReset() : base(ServerOpcodes.DailyQuestsReset) { } + + public override void Write() + { + _worldPacket.WriteInt32(Count); + } + + public int Count; + } + + class QuestLogFull : ServerPacket + { + public QuestLogFull() : base(ServerOpcodes.QuestLogFull) { } + + public override void Write() { } + } + + class RequestWorldQuestUpdate : ClientPacket + { + public RequestWorldQuestUpdate(WorldPacket packet) : base(packet) { } + + public override void Read() { } + } + + class WorldQuestUpdate : ServerPacket + { + public WorldQuestUpdate() : base(ServerOpcodes.WorldQuestUpdate, ConnectionType.Instance) { } + + public override void Write() + { + _worldPacket.WriteUInt32(WorldQuestUpdates.Count); + + foreach (WorldQuestUpdateInfo worldQuestUpdate in WorldQuestUpdates) + { + _worldPacket.WriteInt32(worldQuestUpdate.LastUpdate); + _worldPacket.WriteUInt32(worldQuestUpdate.QuestID); + _worldPacket.WriteUInt32(worldQuestUpdate.Timer); + _worldPacket.WriteInt32(worldQuestUpdate.VariableID); + _worldPacket.WriteInt32(worldQuestUpdate.Value); + } + } + + List WorldQuestUpdates = new List(); + } + + //Structs + public class QuestGiverInfo + { + public QuestGiverInfo() { } + public QuestGiverInfo(ObjectGuid guid, QuestGiverStatus status) + { + Guid = guid; + Status = status; + } + + public ObjectGuid Guid; + public QuestGiverStatus Status = QuestGiverStatus.None; + } + + public struct QuestInfoChoiceItem + { + public uint ItemID; + public uint Quantity; + public uint DisplayID; + } + + public class QuestInfo + { + public QuestInfo() + { + LogTitle = ""; + LogDescription = ""; + QuestDescription = ""; + AreaDescription = ""; + PortraitGiverText = ""; + PortraitGiverName = ""; + PortraitTurnInText = ""; + PortraitTurnInName = ""; + QuestCompletionLog = ""; + } + + public uint QuestID; + public int QuestType; // Accepted values: 0, 1 or 2. 0 == IsAutoComplete() (skip objectives/details) + public int QuestLevel; // may be -1, static data, in other cases must be used dynamic level: Player.GetQuestLevel (0 is not known, but assuming this is no longer valid for quest intended for client) + public uint QuestPackageID; + public int QuestMinLevel; + public int QuestSortID; // zone or sort to display in quest log + public uint QuestInfoID; + public uint SuggestedGroupNum; + public uint RewardNextQuest; // client will request this quest from NPC, if not 0 + public uint RewardXPDifficulty; // used for calculating rewarded experience + public float RewardXPMultiplier = 1.0f; + public int RewardMoney; // reward money (below max lvl) + public uint RewardMoneyDifficulty; + public float RewardMoneyMultiplier = 1.0f; + public uint RewardBonusMoney; + public uint[] RewardDisplaySpell = new uint[SharedConst.QuestRewardDisplaySpellCount]; // reward spell, this spell will be displayed (icon) + public uint RewardSpell; + public uint RewardHonor; + public float RewardKillHonor; + public int RewardArtifactXPDifficulty; + public float RewardArtifactXPMultiplier; + public int RewardArtifactCategoryID; + public uint StartItem; + public uint Flags; + public uint FlagsEx; + public uint POIContinent; + public float POIx; + public float POIy; + public uint POIPriority; + public int AllowableRaces = -1; + public string LogTitle; + public string LogDescription; + public string QuestDescription; + public string AreaDescription; + public uint RewardTitle; // new 2.4.0, player gets this title (id from CharTitles) + public int RewardArenaPoints; + public uint RewardSkillLineID; // reward skill id + public uint RewardNumSkillUps; // reward skill points + public uint PortraitGiver; // quest giver entry ? + public uint PortraitTurnIn; // quest turn in entry ? + public string PortraitGiverText; + public string PortraitGiverName; + public string PortraitTurnInText; + public string PortraitTurnInName; + public string QuestCompletionLog; + public uint RewardFactionFlags; // rep mask (unsure on what it does) + public uint AcceptedSoundKitID; + public uint CompleteSoundKitID; + public uint AreaGroupID; + public uint TimeAllowed; + public int QuestRewardID; + public int Expansion; + public List Objectives = new List(); + public uint[] RewardItems = new uint[SharedConst.QuestRewardItemCount]; + public uint[] RewardAmount = new uint[SharedConst.QuestRewardItemCount]; + public int[] ItemDrop = new int[SharedConst.QuestItemDropCount]; + public int[] ItemDropQuantity = new int[SharedConst.QuestItemDropCount]; + public QuestInfoChoiceItem[] UnfilteredChoiceItems = new QuestInfoChoiceItem[SharedConst.QuestRewardChoicesCount]; + public uint[] RewardFactionID = new uint[SharedConst.QuestRewardReputationsCount]; + public int[] RewardFactionValue = new int[SharedConst.QuestRewardReputationsCount]; + public int[] RewardFactionOverride = new int[SharedConst.QuestRewardReputationsCount]; + public int[] RewardFactionCapIn = new int[SharedConst.QuestRewardReputationsCount]; + public uint[] RewardCurrencyID = new uint[SharedConst.QuestRewardCurrencyCount]; + public uint[] RewardCurrencyQty = new uint[SharedConst.QuestRewardCurrencyCount]; + } + + public struct QuestChoiceItem + { + public uint ItemID; + public uint Quantity; + } + + public class QuestRewards + { + public void Write(WorldPacket data) + { + data.WriteUInt32(ChoiceItemCount); + + for (int i = 0; i < SharedConst.QuestRewardChoicesCount; ++i) + { + data.WriteUInt32(ChoiceItems[i].ItemID); + data.WriteUInt32(ChoiceItems[i].Quantity); + } + + data.WriteUInt32(ItemCount); + + for (int i = 0; i < SharedConst.QuestRewardItemCount; ++i) + { + data.WriteUInt32(ItemID[i]); + data.WriteUInt32(ItemQty[i]); + } + + data.WriteUInt32(Money); + data.WriteUInt32(XP); + data.WriteUInt64(ArtifactXP); + data.WriteUInt32(ArtifactCategoryID); + data.WriteUInt32(Honor); + data.WriteUInt32(Title); + data.WriteUInt32(FactionFlags); + + for (int i = 0; i < SharedConst.QuestRewardReputationsCount; ++i) + { + data.WriteUInt32(FactionID[i]); + data.WriteInt32(FactionValue[i]); + data.WriteInt32(FactionOverride[i]); + data.WriteInt32(FactionCapIn[i]); + } + + foreach (var id in SpellCompletionDisplayID) + data.WriteInt32(id); + + data.WriteUInt32(SpellCompletionID); + + for (int i = 0; i < SharedConst.QuestRewardCurrencyCount; ++i) + { + data.WriteUInt32(CurrencyID[i]); + data.WriteUInt32(CurrencyQty[i]); + } + + data.WriteUInt32(SkillLineID); + data.WriteUInt32(NumSkillUps); + data.WriteInt32(RewardID); + + data.WriteBit(IsBoostSpell); + data.FlushBits(); + } + + public uint ChoiceItemCount; + public uint ItemCount; + public uint Money; + public uint XP; + public uint ArtifactXP; + public uint ArtifactCategoryID; + public uint Honor; + public uint Title; + public uint FactionFlags; + public int[] SpellCompletionDisplayID = new int[SharedConst.QuestRewardDisplaySpellCount]; + public uint SpellCompletionID; + public uint SkillLineID; + public uint NumSkillUps; + public uint RewardID; + public QuestChoiceItem[] ChoiceItems = new QuestChoiceItem[SharedConst.QuestRewardChoicesCount]; + public uint[] ItemID = new uint[SharedConst.QuestRewardItemCount]; + public uint[] ItemQty = new uint[SharedConst.QuestRewardItemCount]; + public uint[] FactionID = new uint[SharedConst.QuestRewardReputationsCount]; + public int[] FactionValue = new int[SharedConst.QuestRewardReputationsCount]; + public int[] FactionOverride = new int[SharedConst.QuestRewardReputationsCount]; + public int[] FactionCapIn = new int[SharedConst.QuestRewardReputationsCount]; + public uint[] CurrencyID = new uint[SharedConst.QuestRewardCurrencyCount]; + public uint[] CurrencyQty = new uint[SharedConst.QuestRewardCurrencyCount]; + public bool IsBoostSpell; + } + + public struct QuestDescEmote + { + public QuestDescEmote(uint type = 0, uint delay = 0) + { + Type = type; + Delay = delay; + } + + public uint Type; + public uint Delay; + } + + public class QuestGiverOfferReward + { + public void Write(WorldPacket data) + { + data.WritePackedGuid(QuestGiverGUID); + data.WriteUInt32(QuestGiverCreatureID); + data.WriteUInt32(QuestID); + data.WriteUInt32(QuestFlags[0]); // Flags + data.WriteUInt32(QuestFlags[1]); // FlagsEx + data.WriteUInt32(SuggestedPartyMembers); + + data.WriteInt32(Emotes.Count); + foreach (QuestDescEmote emote in Emotes) + { + data.WriteInt32(emote.Type); + data.WriteInt32(emote.Delay); + } + + data.WriteBit(AutoLaunched); + data.FlushBits(); + + Rewards.Write(data); + } + + public ObjectGuid QuestGiverGUID; + public uint QuestGiverCreatureID = 0; + public uint QuestID = 0; + public bool AutoLaunched = false; + public uint SuggestedPartyMembers = 0; + public QuestRewards Rewards = new QuestRewards(); + public List Emotes = new List(); + public uint[] QuestFlags = new uint[2]; // Flags and FlagsEx + } + + public struct QuestObjectiveSimple + { + public uint ID; + public int ObjectID; + public int Amount; + public byte Type; + } + + public struct QuestObjectiveCollect + { + public QuestObjectiveCollect(uint objectID = 0, int amount = 0, uint flags = 0) + { + ObjectID = objectID; + Amount = amount; + Flags = flags; + } + + public uint ObjectID; + public int Amount; + public uint Flags; + } + + public struct QuestCurrency + { + public QuestCurrency(uint currencyID = 0, int amount = 0) + { + CurrencyID = currencyID; + Amount = amount; + } + + public uint CurrencyID; + public int Amount; + } + + public struct GossipTextData + { + public GossipTextData(uint questID, uint questType, uint questLevel, uint questFlags, uint questFlagsEx, bool repeatable, string questTitle) + { + QuestID = questID; + QuestType = questType; + QuestLevel = questLevel; + QuestFlags = questFlags; + QuestFlagsEx = questFlagsEx; + Repeatable = repeatable; + QuestTitle = questTitle; + } + + public uint QuestID; + public uint QuestType; + public uint QuestLevel; + public uint QuestFlags; + public uint QuestFlagsEx; + public bool Repeatable; + public string QuestTitle; + } + + struct WorldQuestUpdateInfo + { + public WorldQuestUpdateInfo(int lastUpdate, uint questID, uint timer, int variableID, int value) + { + LastUpdate = lastUpdate; + QuestID = questID; + Timer = timer; + VariableID = variableID; + Value = value; + } + + public int LastUpdate; + public uint QuestID; + public uint Timer; + // WorldState + public int VariableID; + public int Value; + } +} diff --git a/Game/Network/Packets/ReferAFriendPackets.cs b/Game/Network/Packets/ReferAFriendPackets.cs new file mode 100644 index 000000000..5a37fa46a --- /dev/null +++ b/Game/Network/Packets/ReferAFriendPackets.cs @@ -0,0 +1,75 @@ +/* + * Copyright (C) 2012-2017 CypherCore + * + * 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, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +using Framework.Constants; +using Game.Entities; + +namespace Game.Network.Packets +{ + public class AcceptLevelGrant : ClientPacket + { + public AcceptLevelGrant(WorldPacket packet) : base(packet) { } + + public override void Read() + { + Granter = _worldPacket.ReadPackedGuid(); + } + + public ObjectGuid Granter; + } + + public class GrantLevel : ClientPacket + { + public GrantLevel(WorldPacket packet) : base(packet) { } + + public override void Read() + { + Target = _worldPacket.ReadPackedGuid(); + } + + public ObjectGuid Target; + } + + public class ProposeLevelGrant : ServerPacket + { + public ProposeLevelGrant() : base(ServerOpcodes.ProposeLevelGrant) { } + + public override void Write() + { + _worldPacket.WritePackedGuid(Sender); + } + + public ObjectGuid Sender; + } + + public class ReferAFriendFailure : ServerPacket + { + public ReferAFriendFailure() : base(ServerOpcodes.ReferAFriendFailure) { } + + public override void Write() + { + _worldPacket .WriteInt32(Reason); + // Client uses this string only if Reason == ERR_REFER_A_FRIEND_NOT_IN_GROUP || Reason == ERR_REFER_A_FRIEND_SUMMON_OFFLINE_S + // but always reads it from packet + _worldPacket.WriteBits(Str.Length, 6); + _worldPacket.WriteString(Str); + } + + public string Str; + public ReferAFriendError Reason; + } +} diff --git a/Game/Network/Packets/ReputationPackets.cs b/Game/Network/Packets/ReputationPackets.cs new file mode 100644 index 000000000..14a9a3d36 --- /dev/null +++ b/Game/Network/Packets/ReputationPackets.cs @@ -0,0 +1,123 @@ +/* + * Copyright (C) 2012-2017 CypherCore + * + * 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, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +using Framework.Constants; +using System.Collections.Generic; + +namespace Game.Network.Packets +{ + public class InitializeFactions : ServerPacket + { + const ushort FactionCount = 300; + + public InitializeFactions() : base(ServerOpcodes.InitializeFactions, ConnectionType.Instance) { } + + public override void Write() + { + for (ushort i = 0; i < FactionCount; ++i) + { + _worldPacket.WriteUInt8(FactionFlags[i]); + _worldPacket.WriteInt32(FactionStandings[i]); + } + + for (ushort i = 0; i < FactionCount; ++i) + _worldPacket.WriteBit(FactionHasBonus[i]); + + _worldPacket.FlushBits(); + } + + public int[] FactionStandings = new int[FactionCount]; + public bool[] FactionHasBonus = new bool[FactionCount]; ///< @todo: implement faction bonus + public FactionFlags[] FactionFlags = new FactionFlags[FactionCount]; + } + + class RequestForcedReactions : ClientPacket + { + public RequestForcedReactions(WorldPacket packet) : base(packet) { } + + public override void Read() { } + } + + class SetForcedReactions : ServerPacket + { + public SetForcedReactions() : base(ServerOpcodes.SetForcedReactions, ConnectionType.Instance) { } + + public override void Write() + { + _worldPacket.WriteUInt32(Reactions.Count); + foreach (ForcedReaction reaction in Reactions) + reaction.Write(_worldPacket); + + _worldPacket.FlushBits(); + } + + public List Reactions = new List(); + } + + class SetFactionStanding : ServerPacket + { + public SetFactionStanding() : base(ServerOpcodes.SetFactionStanding, ConnectionType.Instance) { } + + public override void Write() + { + _worldPacket.WriteFloat(ReferAFriendBonus); + _worldPacket.WriteFloat(BonusFromAchievementSystem); + + _worldPacket.WriteUInt32(Faction.Count); + foreach (FactionStandingData factionStanding in Faction) + factionStanding.Write(_worldPacket); + + _worldPacket.WriteBit(ShowVisual); + _worldPacket.FlushBits(); + } + + public float ReferAFriendBonus; + public float BonusFromAchievementSystem; + public List Faction = new List(); + public bool ShowVisual; + } + + struct ForcedReaction + { + public void Write(WorldPacket data) + { + data.WriteInt32(Faction); + data.WriteInt32(Reaction); + } + + public int Faction; + public int Reaction; + } + + struct FactionStandingData + { + public FactionStandingData(int index, int standing) + { + Index = index; + Standing = standing; + } + + public void Write(WorldPacket data) + { + data.WriteInt32(Index); + data.WriteInt32(Standing); + } + + int Index; + int Standing; + } +} diff --git a/Game/Network/Packets/ScenarioPackets.cs b/Game/Network/Packets/ScenarioPackets.cs new file mode 100644 index 000000000..ab8dee618 --- /dev/null +++ b/Game/Network/Packets/ScenarioPackets.cs @@ -0,0 +1,196 @@ +/* + * Copyright (C) 2012-2017 CypherCore + * + * 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, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +using Framework.Constants; +using Game.Scenarios; +using System.Collections.Generic; + +namespace Game.Network.Packets +{ + class ScenarioState : ServerPacket + { + public ScenarioState() : base(ServerOpcodes.ScenarioState, ConnectionType.Instance) { } + + public override void Write() + { + _worldPacket.WriteInt32(ScenarioID); + _worldPacket.WriteInt32(CurrentStep); + _worldPacket.WriteUInt32(DifficultyID); + _worldPacket.WriteUInt32(WaveCurrent); + _worldPacket.WriteUInt32(WaveMax); + _worldPacket.WriteUInt32(TimerDuration); + _worldPacket.WriteUInt32(CriteriaProgress.Count); + _worldPacket.WriteUInt32(BonusObjectives.Count); + _worldPacket.WriteUInt32(PickedSteps.Count); + _worldPacket.WriteUInt32(Spells.Count); + + for (int i = 0; i < PickedSteps.Count; ++i) + _worldPacket.WriteUInt32(PickedSteps[i]); + + _worldPacket.WriteBit(ScenarioComplete); + _worldPacket.FlushBits(); + + foreach (CriteriaProgressPkt progress in CriteriaProgress) + progress.Write(_worldPacket); + + foreach (BonusObjectiveData bonusObjective in BonusObjectives) + bonusObjective.Write(_worldPacket); + + foreach (ScenarioSpellUpdate spell in Spells) + spell.Write(_worldPacket); + } + + public int ScenarioID; + public int CurrentStep = -1; + public uint DifficultyID; + public uint WaveCurrent; + public uint WaveMax; + public uint TimerDuration; + public List CriteriaProgress = new List(); + public List BonusObjectives = new List(); + public List PickedSteps = new List(); + public List Spells = new List(); + public bool ScenarioComplete = false; + } + + class ScenarioProgressUpdate : ServerPacket + { + public ScenarioProgressUpdate() : base(ServerOpcodes.ScenarioProgressUpdate, ConnectionType.Instance) { } + + public override void Write() + { + CriteriaProgress.Write(_worldPacket); + } + + public CriteriaProgressPkt CriteriaProgress; + } + + class ScenarioCompleted : ServerPacket + { + public ScenarioCompleted(uint scenarioId) : base(ServerOpcodes.ScenarioCompleted, ConnectionType.Instance) + { + ScenarioID = scenarioId; + } + + public override void Write() + { + _worldPacket.WriteUInt32(ScenarioID); + } + + public uint ScenarioID; + } + + class ScenarioBoot : ServerPacket + { + public ScenarioBoot() : base(ServerOpcodes.ScenarioBoot, ConnectionType.Instance) { } + + public override void Write() + { + _worldPacket.WriteInt32(ScenarioID); + _worldPacket.WriteInt32(Unk1); + _worldPacket.WriteBits(Unk2, 2); + _worldPacket.FlushBits(); + } + + public int ScenarioID; + public int Unk1; + public byte Unk2; + } + + class QueryScenarioPOI : ClientPacket + { + public QueryScenarioPOI(WorldPacket packet) : base(packet) { } + + public override void Read() + { + var count = _worldPacket.ReadUInt32(); + for (var i = 0; i < count; ++i) + MissingScenarioPOIs.Add(_worldPacket.ReadInt32()); + } + + public Array MissingScenarioPOIs = new Array(35); + } + + class ScenarioPOIs : ServerPacket + { + public ScenarioPOIs() : base(ServerOpcodes.ScenarioPois) { } + + public override void Write() + { + _worldPacket.WriteUInt32(ScenarioPOIDataStats.Count); + + foreach (ScenarioPOIData scenarioPOIData in ScenarioPOIDataStats) + { + _worldPacket.WriteInt32(scenarioPOIData.CriteriaTreeID); + _worldPacket.WriteUInt32(scenarioPOIData.ScenarioPOIs.Count); + + foreach (ScenarioPOI scenarioPOI in scenarioPOIData.ScenarioPOIs) + { + _worldPacket.WriteInt32(scenarioPOI.BlobIndex); + _worldPacket.WriteInt32(scenarioPOI.MapID); + _worldPacket.WriteInt32(scenarioPOI.WorldMapAreaID); + _worldPacket.WriteInt32(scenarioPOI.Floor); + _worldPacket.WriteInt32(scenarioPOI.Priority); + _worldPacket.WriteInt32(scenarioPOI.Flags); + _worldPacket.WriteInt32(scenarioPOI.WorldEffectID); + _worldPacket.WriteInt32(scenarioPOI.PlayerConditionID); + _worldPacket.WriteUInt32(scenarioPOI.Points.Count); + + foreach (var scenarioPOIBlobPoint in scenarioPOI.Points) + { + _worldPacket.WriteInt32(scenarioPOIBlobPoint.X); + _worldPacket.WriteInt32(scenarioPOIBlobPoint.Y); + } + } + } + } + + public List ScenarioPOIDataStats = new List(); + } + + struct BonusObjectiveData + { + public void Write(WorldPacket data) + { + data.WriteInt32(BonusObjectiveID); + data.WriteBit(ObjectiveComplete); + data.FlushBits(); + } + + public int BonusObjectiveID; + public bool ObjectiveComplete; + } + + class ScenarioSpellUpdate + { + public void Write(WorldPacket data) + { + data.WriteUInt32(SpellID); + data.WriteBit(Usable); + data.FlushBits(); + } + + public uint SpellID; + public bool Usable = true; + } + + struct ScenarioPOIData + { + public int CriteriaTreeID; + public List ScenarioPOIs; + } +} diff --git a/Game/Network/Packets/ScenePackets.cs b/Game/Network/Packets/ScenePackets.cs new file mode 100644 index 000000000..5c5bc963f --- /dev/null +++ b/Game/Network/Packets/ScenePackets.cs @@ -0,0 +1,95 @@ +/* + * Copyright (C) 2012-2017 CypherCore + * + * 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, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +using Framework.Constants; +using Game.Entities; + +namespace Game.Network.Packets +{ + class PlayScene : ServerPacket + { + public PlayScene() : base(ServerOpcodes.PlayScene) { } + + public override void Write() + { + _worldPacket.WriteUInt32(SceneID); + _worldPacket.WriteUInt32(PlaybackFlags); + _worldPacket.WriteUInt32(SceneInstanceID); + _worldPacket.WriteUInt32(SceneScriptPackageID); + _worldPacket.WritePackedGuid(TransportGUID); + _worldPacket.WriteXYZO(Location); + } + + public uint SceneID; + public uint PlaybackFlags; + public uint SceneInstanceID; + public uint SceneScriptPackageID; + public ObjectGuid TransportGUID; + public Position Location; + } + + class CancelScene : ServerPacket + { + public CancelScene() : base(ServerOpcodes.CancelScene) { } + + public override void Write() + { + _worldPacket.WriteUInt32(SceneInstanceID); + } + + public uint SceneInstanceID; + } + + class SceneTriggerEvent : ClientPacket + { + public SceneTriggerEvent(WorldPacket packet) : base(packet) { } + + public override void Read() + { + uint len = _worldPacket.ReadBits(6); + SceneInstanceID = _worldPacket.ReadUInt32(); + _Event = _worldPacket.ReadString(len); + } + + public uint SceneInstanceID; + public string _Event; + } + + class ScenePlaybackComplete : ClientPacket + { + public ScenePlaybackComplete(WorldPacket packet) : base(packet) { } + + public override void Read() + { + SceneInstanceID = _worldPacket.ReadUInt32(); + } + + public uint SceneInstanceID; + } + + class ScenePlaybackCanceled : ClientPacket + { + public ScenePlaybackCanceled(WorldPacket packet) : base(packet) { } + + public override void Read() + { + SceneInstanceID = _worldPacket.ReadUInt32(); + } + + public uint SceneInstanceID; + } +} diff --git a/Game/Network/Packets/SocialPackets.cs b/Game/Network/Packets/SocialPackets.cs new file mode 100644 index 000000000..a921024e7 --- /dev/null +++ b/Game/Network/Packets/SocialPackets.cs @@ -0,0 +1,221 @@ +/* + * Copyright (C) 2012-2017 CypherCore + * + * 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, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +using Framework.Constants; +using Game.Entities; +using System.Collections.Generic; + +namespace Game.Network.Packets +{ + public class SendContactList : ClientPacket + { + public SendContactList(WorldPacket packet) : base(packet) { } + + public override void Read() + { + Flags = (SocialFlag)_worldPacket.ReadUInt32(); + } + + public SocialFlag Flags; + } + + public class ContactList : ServerPacket + { + public ContactList() : base(ServerOpcodes.ContactList) + { + Contacts = new List(); + } + + public override void Write() + { + _worldPacket.WriteUInt32(Flags); + _worldPacket.WriteBits(Contacts.Count, 8); + _worldPacket.FlushBits(); + + Contacts.ForEach(p => p.Write(_worldPacket)); + } + + public List Contacts; + public SocialFlag Flags; + } + + public class FriendStatusPkt : ServerPacket + { + public FriendStatusPkt() : base(ServerOpcodes.FriendStatus) { } + + public void Initialize(ObjectGuid guid, FriendsResult result, FriendInfo friendInfo) + { + VirtualRealmAddress = Global.WorldMgr.GetVirtualRealmAddress(); + Notes = friendInfo.Note; + ClassID = friendInfo.Class; + Status = friendInfo.Status; + Guid = guid; + WowAccountGuid = friendInfo.WowAccountGuid; + Level = friendInfo.Level; + AreaID = friendInfo.Area; + FriendResult = result; + } + + public override void Write() + { + _worldPacket.WriteUInt8(FriendResult); + _worldPacket.WritePackedGuid(Guid); + _worldPacket.WritePackedGuid(WowAccountGuid); + _worldPacket.WriteUInt32(VirtualRealmAddress); + _worldPacket.WriteUInt8(Status); + _worldPacket.WriteUInt32(AreaID); + _worldPacket.WriteUInt32(Level); + _worldPacket.WriteUInt32(ClassID); + _worldPacket.WriteBits(Notes.Length, 10); + _worldPacket.FlushBits(); + _worldPacket.WriteString(Notes); + } + + public uint VirtualRealmAddress; + public string Notes; + public Class ClassID = Class.None; + public FriendStatus Status; + public ObjectGuid Guid; + public ObjectGuid WowAccountGuid; + public uint Level; + public uint AreaID; + public FriendsResult FriendResult; + } + + public class AddFriend : ClientPacket + { + public AddFriend(WorldPacket packet) : base(packet) { } + + public override void Read() + { + uint nameLength = _worldPacket.ReadBits(9); + uint noteslength = _worldPacket.ReadBits(10); + Name = _worldPacket.ReadString(nameLength); + Notes = _worldPacket.ReadString(noteslength); + } + + public string Notes; + public string Name; + } + + public class DelFriend : ClientPacket + { + public DelFriend(WorldPacket packet) : base(packet) { } + + public override void Read() + { + Player.Read(_worldPacket); + } + + public QualifiedGUID Player; + } + + public class SetContactNotes : ClientPacket + { + public SetContactNotes(WorldPacket packet) : base(packet) { } + + public override void Read() + { + Player.Read(_worldPacket); + Notes = _worldPacket.ReadString(_worldPacket.ReadBits(10)); + } + + public QualifiedGUID Player; + public string Notes; + } + + public class AddIgnore : ClientPacket + { + public AddIgnore(WorldPacket packet) : base(packet) { } + + public override void Read() + { + Name = _worldPacket.ReadString(_worldPacket.ReadBits(9)); + } + + public string Name; + } + + public class DelIgnore : ClientPacket + { + public DelIgnore(WorldPacket packet) : base(packet) { } + + public override void Read() + { + Player.Read(_worldPacket); + } + + public QualifiedGUID Player; + } + + //Structs + public class ContactInfo + { + public ContactInfo(ObjectGuid guid, FriendInfo friendInfo) + { + Guid = guid; + WowAccountGuid = friendInfo.WowAccountGuid; + VirtualRealmAddr = Global.WorldMgr.GetVirtualRealmAddress(); + NativeRealmAddr = Global.WorldMgr.GetVirtualRealmAddress(); + TypeFlags = friendInfo.Flags; + Notes = friendInfo.Note; + Status = friendInfo.Status; + AreaID = friendInfo.Area; + Level = friendInfo.Level; + ClassID = friendInfo.Class; + } + + public void Write(WorldPacket data) + { + data.WritePackedGuid(Guid); + data.WritePackedGuid(WowAccountGuid); + data.WriteUInt32(VirtualRealmAddr); + data.WriteUInt32(NativeRealmAddr); + data.WriteUInt32(TypeFlags); + data.WriteUInt8(Status); + data.WriteUInt32(AreaID); + data.WriteUInt32(Level); + data.WriteUInt32(ClassID); + data.WriteBits(Notes.Length, 10); + data.FlushBits(); + data.WriteString(Notes); + } + + ObjectGuid Guid; + ObjectGuid WowAccountGuid; + uint VirtualRealmAddr; + uint NativeRealmAddr; + SocialFlag TypeFlags; + string Notes; + FriendStatus Status; + uint AreaID; + uint Level; + Class ClassID = Class.None; + } + + public struct QualifiedGUID + { + public void Read(WorldPacket data) + { + VirtualRealmAddress = data.ReadUInt32(); + Guid = data.ReadPackedGuid(); + } + + public ObjectGuid Guid; + public uint VirtualRealmAddress; + } +} diff --git a/Game/Network/Packets/SpellPackets.cs b/Game/Network/Packets/SpellPackets.cs new file mode 100644 index 000000000..4dfe2b04f --- /dev/null +++ b/Game/Network/Packets/SpellPackets.cs @@ -0,0 +1,1682 @@ +/* + * Copyright (C) 2012-2017 CypherCore + * + * 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, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +using Framework.Constants; +using Framework.Dynamic; +using Framework.GameMath; +using Game.Entities; +using Game.Spells; +using System; +using System.Collections.Generic; + +namespace Game.Network.Packets +{ + class CancelAura : ClientPacket + { + public CancelAura(WorldPacket packet) : base(packet) { } + + public override void Read() + { + SpellID = _worldPacket.ReadUInt32(); + CasterGUID = _worldPacket.ReadPackedGuid(); + } + + public ObjectGuid CasterGUID; + public uint SpellID; + } + + class CancelAutoRepeatSpell : ClientPacket + { + public CancelAutoRepeatSpell(WorldPacket packet) : base(packet) { } + + public override void Read() { } + } + + class CancelChannelling : ClientPacket + { + public CancelChannelling(WorldPacket packet) : base(packet) { } + + public override void Read() + { + ChannelSpell = _worldPacket.ReadInt32(); + } + + int ChannelSpell; + } + + class CancelGrowthAura : ClientPacket + { + public CancelGrowthAura(WorldPacket packet) : base(packet) { } + + public override void Read() { } + } + + class CancelMountAura : ClientPacket + { + public CancelMountAura(WorldPacket packet) : base(packet) { } + + public override void Read() { } + } + + public class RequestCategoryCooldowns : ClientPacket + { + public RequestCategoryCooldowns(WorldPacket packet) : base(packet) { } + + public override void Read() { } + } + + public class SpellCategoryCooldown : ServerPacket + { + public List CategoryCooldowns = new List(); + + public SpellCategoryCooldown() : base(ServerOpcodes.CategoryCooldown, ConnectionType.Instance) { } + + public override void Write() + { + _worldPacket.WriteUInt32(CategoryCooldowns.Count); + + foreach (CategoryCooldownInfo cooldown in CategoryCooldowns) + { + _worldPacket.WriteUInt32(cooldown.Category); + _worldPacket.WriteInt32(cooldown.ModCooldown); + } + } + + public class CategoryCooldownInfo + { + public CategoryCooldownInfo(uint category, int cooldown) + { + Category = category; + ModCooldown = cooldown; + } + + public uint Category = 0; // SpellCategory Id + public int ModCooldown = 0; // Reduced Cooldown in ms + } + } + + public class SendKnownSpells : ServerPacket + { + public SendKnownSpells() : base(ServerOpcodes.SendKnownSpells, ConnectionType.Instance) { } + + public override void Write() + { + _worldPacket.WriteBit(InitialLogin); + _worldPacket.WriteUInt32(KnownSpells.Count); + _worldPacket.WriteUInt32(FavoriteSpells.Count); + + foreach (var spellId in KnownSpells) + _worldPacket.WriteUInt32(spellId); + + foreach (var spellId in FavoriteSpells) + _worldPacket.WriteUInt32(spellId); + } + + public bool InitialLogin; + public List KnownSpells = new List(); + public List FavoriteSpells = new List(); // tradeskill recipes + } + + public class UpdateActionButtons : ServerPacket + { + public ulong[] ActionButtons = new ulong[PlayerConst.MaxActionButtons]; + public byte Reason; + + public UpdateActionButtons() : base(ServerOpcodes.UpdateActionButtons, ConnectionType.Instance) { } + + public override void Write() + { + for (var i = 0; i < PlayerConst.MaxActionButtons; ++i) + _worldPacket.WriteUInt64(ActionButtons[i]); + + _worldPacket.WriteUInt8(Reason); + } + } + + public class SetActionButton : ClientPacket + { + public SetActionButton(WorldPacket packet) : base(packet) { } + + public override void Read() + { + Action = _worldPacket.ReadUInt64(); + Index = _worldPacket.ReadUInt8(); + } + + public uint GetButtonAction() { return (uint)(Action & 0x00000000FFFFFFFF); } + public uint GetButtonType() { return (uint)((Action & 0xFFFFFFFF00000000) >> 56); } + + public ulong Action; // two packed public uint (action and type) + public byte Index; + } + + public class SendUnlearnSpells : ServerPacket + { + List Spells = new List(); + + public SendUnlearnSpells() : base(ServerOpcodes.SendUnlearnSpells, ConnectionType.Instance) { } + + public override void Write() + { + _worldPacket.WriteUInt32(Spells.Count); + foreach (var spell in Spells) + _worldPacket.WriteUInt32(spell); + } + } + + public class AuraUpdate : ServerPacket + { + public AuraUpdate() : base(ServerOpcodes.AuraUpdate, ConnectionType.Instance) { } + + public override void Write() + { + _worldPacket.WriteBit(UpdateAll); + _worldPacket.WriteBits(Auras.Count, 9); + foreach (AuraInfo aura in Auras) + aura.Write(_worldPacket); + + _worldPacket.WritePackedGuid(UnitGUID); + } + + public bool UpdateAll; + public ObjectGuid UnitGUID; + public List Auras = new List(); + } + + public class CastSpell : ClientPacket + { + public SpellCastRequest Cast; + + public CastSpell(WorldPacket packet) : base(packet) + { + Cast = new SpellCastRequest(); + } + + public override void Read() + { + Cast.Read(_worldPacket); + } + } + + public class PetCastSpell : ClientPacket + { + public ObjectGuid PetGUID; + public SpellCastRequest Cast; + + public PetCastSpell(WorldPacket packet) : base(packet) + { + Cast = new SpellCastRequest(); + } + + public override void Read() + { + PetGUID = _worldPacket.ReadPackedGuid(); + Cast.Read(_worldPacket); + } + } + + public class UseItem : ClientPacket + { + public byte PackSlot; + public byte Slot; + public ObjectGuid CastItem; + public SpellCastRequest Cast; + + public UseItem(WorldPacket packet) : base(packet) + { + Cast = new SpellCastRequest(); + } + + public override void Read() + { + PackSlot = _worldPacket.ReadUInt8(); + Slot = _worldPacket.ReadUInt8(); + CastItem = _worldPacket.ReadPackedGuid(); + Cast.Read(_worldPacket); + } + } + + class SpellPrepare : ServerPacket + { + public SpellPrepare() : base(ServerOpcodes.SpellPrepare) { } + + public override void Write() + { + _worldPacket.WritePackedGuid(ClientCastID); + _worldPacket.WritePackedGuid(ServerCastID); + } + + public ObjectGuid ClientCastID; + public ObjectGuid ServerCastID; + } + + class SpellGo : CombatLogServerPacket + { + public SpellGo() : base(ServerOpcodes.SpellGo, ConnectionType.Instance) { } + + public override void Write() + { + Cast.Write(_worldPacket); + + WriteLogDataBit(); + FlushBits(); + + WriteLogData(); + } + + public SpellCastData Cast = new SpellCastData(); + } + + public class SpellStart : ServerPacket + { + public SpellCastData Cast; + + public SpellStart() : base(ServerOpcodes.SpellStart, ConnectionType.Instance) + { + Cast = new SpellCastData(); + } + + public override void Write() + { + Cast.Write(_worldPacket); + } + } + + public class SupercededSpells : ServerPacket + { + public SupercededSpells() : base(ServerOpcodes.SupercededSpells, ConnectionType.Instance) { } + + public override void Write() + { + _worldPacket.WriteUInt32(SpellID.Count); + _worldPacket.WriteUInt32(Superceded.Count); + _worldPacket.WriteUInt32(FavoriteSpellID.Count); + + foreach (var spellId in SpellID) + _worldPacket.WriteUInt32(spellId); + + foreach (var spellId in Superceded) + _worldPacket.WriteUInt32(spellId); + + foreach (var spellId in FavoriteSpellID) + _worldPacket.WriteInt32(spellId); + } + + public List SpellID = new List(); + public List Superceded = new List(); + public List FavoriteSpellID = new List(); + } + + public class LearnedSpells : ServerPacket + { + public LearnedSpells() : base(ServerOpcodes.LearnedSpells, ConnectionType.Instance) { } + + public override void Write() + { + _worldPacket.WriteUInt32(SpellID.Count); + _worldPacket.WriteUInt32(FavoriteSpellID.Count); + + foreach (int spell in SpellID) + _worldPacket.WriteInt32(spell); + + foreach (int spell in FavoriteSpellID) + _worldPacket.WriteInt32(spell); + + _worldPacket.WriteBit(SuppressMessaging); + _worldPacket.FlushBits(); + } + + public List SpellID = new List(); + public List FavoriteSpellID = new List(); + public bool SuppressMessaging; + } + + public class SpellFailure : ServerPacket + { + public SpellFailure() : base(ServerOpcodes.SpellFailure, ConnectionType.Instance) { } + + public override void Write() + { + _worldPacket.WritePackedGuid(CasterUnit); + _worldPacket.WritePackedGuid(CastID); + _worldPacket.WriteInt32(SpellID); + _worldPacket.WriteUInt32(SpellXSpellVisualID); + _worldPacket.WriteUInt16(Reason); + } + + public ObjectGuid CasterUnit; + public uint SpellID; + public uint SpellXSpellVisualID; + public ushort Reason; + public ObjectGuid CastID; + } + + public class SpellFailedOther : ServerPacket + { + public SpellFailedOther() : base(ServerOpcodes.SpellFailedOther, ConnectionType.Instance) { } + + public override void Write() + { + _worldPacket.WritePackedGuid(CasterUnit); + _worldPacket.WritePackedGuid(CastID); + _worldPacket.WriteUInt32(SpellID); + _worldPacket.WriteUInt32(SpellXSpellVisualID); + _worldPacket.WriteUInt16(Reason); + } + + public ObjectGuid CasterUnit; + public uint SpellID; + public uint SpellXSpellVisualID; + public ushort Reason; + public ObjectGuid CastID; + } + + class CastFailedBase : ServerPacket + { + public CastFailedBase(ServerOpcodes serverOpcodes, ConnectionType connectionType) : base(serverOpcodes, connectionType) { } + + public override void Write() { throw new NotImplementedException(); } + + public ObjectGuid CastID; + public int SpellID; + public SpellCastResult Reason; + public int FailedArg1 = -1; + public int FailedArg2 = -1; + } + + class CastFailed : CastFailedBase + { + public CastFailed() : base(ServerOpcodes.CastFailed, ConnectionType.Instance) { } + + public override void Write() + { + _worldPacket.WritePackedGuid(CastID); + _worldPacket.WriteInt32(SpellID); + _worldPacket.WriteInt32(SpellXSpellVisualID); + _worldPacket.WriteInt32(Reason); + _worldPacket.WriteInt32(FailedArg1); + _worldPacket.WriteInt32(FailedArg2); + } + + public int SpellXSpellVisualID; + } + + class PetCastFailed : CastFailedBase + { + public PetCastFailed() : base(ServerOpcodes.PetCastFailed, ConnectionType.Instance) { } + + public override void Write() + { + _worldPacket.WritePackedGuid(CastID); + _worldPacket.WriteInt32(SpellID); + _worldPacket.WriteInt32(Reason); + _worldPacket.WriteInt32(FailedArg1); + _worldPacket.WriteInt32(FailedArg2); + } + } + + public class SetSpellModifier : ServerPacket + { + public SetSpellModifier(ServerOpcodes opcode) : base(opcode, ConnectionType.Instance) { } + + public override void Write() + { + _worldPacket.WriteUInt32(Modifiers.Count); + foreach (SpellModifierInfo spellMod in Modifiers) + spellMod.Write(_worldPacket); + } + + public List Modifiers = new List(); + } + + public class UnlearnedSpells : ServerPacket + { + public UnlearnedSpells() : base(ServerOpcodes.UnlearnedSpells, ConnectionType.Instance) { } + + public override void Write() + { + _worldPacket.WriteUInt32(SpellID.Count); + foreach (uint spellId in SpellID) + _worldPacket.WriteUInt32(spellId); + + _worldPacket.WriteBit(SuppressMessaging); + _worldPacket.FlushBits(); + } + + public List SpellID = new List(); + public bool SuppressMessaging; + } + + public class CooldownEvent : ServerPacket + { + public CooldownEvent(bool isPet, uint spellId) : base(ServerOpcodes.CooldownEvent, ConnectionType.Instance) + { + IsPet = isPet; + SpellID = spellId; + } + + public override void Write() + { + _worldPacket.WriteInt32(SpellID); + _worldPacket.WriteBit(IsPet); + _worldPacket.FlushBits(); + } + + public bool IsPet; + public uint SpellID; + } + + public class ClearCooldowns : ServerPacket + { + public ClearCooldowns() : base(ServerOpcodes.ClearCooldowns, ConnectionType.Instance) { } + + public override void Write() + { + _worldPacket.WriteUInt32(SpellID.Count); + if (!SpellID.Empty()) + SpellID.ForEach(p => _worldPacket.WriteUInt32(p)); + + _worldPacket.WriteBit(IsPet); + _worldPacket.FlushBits(); + } + + public List SpellID = new List(); + public bool IsPet; + } + + public class ClearCooldown : ServerPacket + { + public ClearCooldown() : base(ServerOpcodes.ClearCooldown, ConnectionType.Instance) { } + + public override void Write() + { + _worldPacket.WriteUInt32(SpellID); + _worldPacket.WriteBit(ClearOnHold); + _worldPacket.WriteBit(IsPet); + _worldPacket.FlushBits(); + } + + public bool IsPet; + public uint SpellID; + public bool ClearOnHold; + } + + public class ModifyCooldown : ServerPacket + { + public ModifyCooldown() : base(ServerOpcodes.ModifyCooldown, ConnectionType.Instance) { } + + public override void Write() + { + _worldPacket.WriteUInt32(SpellID); + _worldPacket.WriteInt32(DeltaTime); + _worldPacket.WriteBit(IsPet); + _worldPacket.FlushBits(); + } + + public bool IsPet; + public int DeltaTime; + public uint SpellID; + } + + public class SpellCooldownPkt : ServerPacket + { + public SpellCooldownPkt() : base(ServerOpcodes.SpellCooldown, ConnectionType.Instance) { } + + public override void Write() + { + _worldPacket.WritePackedGuid(Caster); + _worldPacket.WriteUInt8(Flags); + _worldPacket.WriteUInt32(SpellCooldowns.Count); + SpellCooldowns.ForEach(p => p.Write(_worldPacket)); + } + + public List SpellCooldowns = new List(); + public ObjectGuid Caster; + public SpellCooldownFlags Flags; + } + + public class SendSpellHistory : ServerPacket + { + public SendSpellHistory() : base(ServerOpcodes.SendSpellHistory, ConnectionType.Instance) { } + + public override void Write() + { + _worldPacket.WriteUInt32(Entries.Count); + Entries.ForEach(p => p.Write(_worldPacket)); + } + + public List Entries = new List(); + } + + public class ClearAllSpellCharges : ServerPacket + { + public ClearAllSpellCharges() : base(ServerOpcodes.ClearAllSpellCharges, ConnectionType.Instance) { } + + public override void Write() + { + _worldPacket.WriteBit(IsPet); + _worldPacket.FlushBits(); + } + + public bool IsPet; + } + + public class ClearSpellCharges : ServerPacket + { + public ClearSpellCharges() : base(ServerOpcodes.ClearSpellCharges, ConnectionType.Instance) { } + + public override void Write() + { + _worldPacket.WriteUInt32(Category); + _worldPacket.WriteBit(IsPet); + _worldPacket.FlushBits(); + } + + public bool IsPet; + public uint Category; + } + + public class SetSpellCharges : ServerPacket + { + public SetSpellCharges() : base(ServerOpcodes.SetSpellCharges) { } + + public override void Write() + { + _worldPacket.WriteUInt32(Category); + _worldPacket.WriteUInt32(NextRecoveryTime); + _worldPacket.WriteUInt8(ConsumedCharges); + _worldPacket.WriteFloat(ChargeModRate); + _worldPacket.WriteBit(IsPet); + _worldPacket.FlushBits(); + } + + public bool IsPet; + public uint Category; + public uint NextRecoveryTime; + public byte ConsumedCharges; + public float ChargeModRate = 1.0f; + } + + public class SendSpellCharges : ServerPacket + { + public SendSpellCharges() : base(ServerOpcodes.SendSpellCharges, ConnectionType.Instance) { } + + public override void Write() + { + _worldPacket.WriteUInt32(Entries.Count); + Entries.ForEach(p => p.Write(_worldPacket)); + } + + public List Entries = new List(); + } + + public class ClearTarget : ServerPacket + { + public ClearTarget() : base(ServerOpcodes.ClearTarget) { } + + public override void Write() + { + _worldPacket.WritePackedGuid(Guid); + } + + public ObjectGuid Guid; + } + + public class CancelOrphanSpellVisual : ServerPacket + { + public CancelOrphanSpellVisual() : base(ServerOpcodes.CancelOrphanSpellVisual) { } + + public override void Write() + { + _worldPacket.WriteUInt32(SpellVisualID); + } + + public uint SpellVisualID; + } + + public class CancelSpellVisual : ServerPacket + { + public CancelSpellVisual() : base(ServerOpcodes.CancelSpellVisual) { } + + public override void Write() + { + _worldPacket.WritePackedGuid(Source); + _worldPacket.WriteUInt32(SpellVisualID); + } + + public ObjectGuid Source; + public uint SpellVisualID; + } + + class CancelSpellVisualKit : ServerPacket + { + public CancelSpellVisualKit() : base(ServerOpcodes.CancelSpellVisualKit) { } + + public override void Write() + { + _worldPacket.WritePackedGuid(Source); + _worldPacket.WriteUInt32(SpellVisualKitID); + } + + public ObjectGuid Source; + public uint SpellVisualKitID; + } + + class PlayOrphanSpellVisual : ServerPacket + { + public PlayOrphanSpellVisual() : base(ServerOpcodes.PlayOrphanSpellVisual) { } + + public override void Write() + { + _worldPacket.WriteXYZ(SourceLocation); + _worldPacket.WriteVector3(SourceRotation); + _worldPacket.WriteVector3(TargetLocation); + _worldPacket.WritePackedGuid(Target); + _worldPacket.WriteUInt32(SpellVisualID); + _worldPacket.WriteFloat(TravelSpeed); + _worldPacket.WriteFloat(UnkZero); + _worldPacket.WriteBit(SpeedAsTime); + _worldPacket.FlushBits(); + } + + public ObjectGuid Target; // Exclusive with TargetLocation + public Position SourceLocation; + public uint SpellVisualID; + public bool SpeedAsTime; + public float TravelSpeed; + public float UnkZero; // Always zero + public Vector3 SourceRotation; // Vector of rotations, Orientation is z + public Vector3 TargetLocation; // Exclusive with Target + } + + class PlaySpellVisual : ServerPacket + { + public PlaySpellVisual() : base(ServerOpcodes.PlaySpellVisual) { } + + public override void Write() + { + _worldPacket.WritePackedGuid(Source); + _worldPacket.WritePackedGuid(Target); + _worldPacket.WriteVector3(TargetPosition); + _worldPacket.WriteUInt32(SpellVisualID); + _worldPacket.WriteFloat(TravelSpeed); + _worldPacket.WriteUInt16(MissReason); + _worldPacket.WriteUInt16(ReflectStatus); + _worldPacket.WriteFloat(Orientation); + _worldPacket.WriteBit(SpeedAsTime); + _worldPacket.FlushBits(); + } + + public ObjectGuid Source; + public ObjectGuid Target; // Exclusive with TargetPosition + public ushort MissReason; + public uint SpellVisualID; + public bool SpeedAsTime; + public ushort ReflectStatus; + public float TravelSpeed; + public Vector3 TargetPosition; // Exclusive with Target + public float Orientation; + } + + class PlaySpellVisualKit : ServerPacket + { + public PlaySpellVisualKit() : base(ServerOpcodes.PlaySpellVisualKit) { } + + public override void Write() + { + _worldPacket.WritePackedGuid(Unit); + _worldPacket.WriteUInt32(KitRecID); + _worldPacket.WriteUInt32(KitType); + _worldPacket.WriteUInt32(Duration); + } + + public ObjectGuid Unit; + public uint KitRecID; + public uint KitType; + public uint Duration; + } + + public class CancelCast : ClientPacket + { + public CancelCast(WorldPacket packet) : base(packet) { } + + public override void Read() + { + CastID = _worldPacket.ReadPackedGuid(); + SpellID = _worldPacket.ReadUInt32(); + } + + public uint SpellID; + public ObjectGuid CastID; + } + + public class OpenItem : ClientPacket + { + public OpenItem(WorldPacket packet) : base(packet) { } + + public override void Read() + { + Slot = _worldPacket.ReadUInt8(); + PackSlot = _worldPacket.ReadUInt8(); + } + + public byte Slot; + public byte PackSlot; + } + + public class SpellChannelStart : ServerPacket + { + public SpellChannelStart() : base(ServerOpcodes.SpellChannelStart, ConnectionType.Instance) { } + + public override void Write() + { + _worldPacket.WritePackedGuid(CasterGUID); + _worldPacket.WriteInt32(SpellID); + _worldPacket.WriteInt32(SpellXSpellVisualID); + _worldPacket.WriteUInt32(ChannelDuration); + _worldPacket.WriteBit(InterruptImmunities.HasValue); + _worldPacket.WriteBit(HealPrediction.HasValue); + _worldPacket.FlushBits(); + + if (InterruptImmunities.HasValue) + InterruptImmunities.Value.Write(_worldPacket); + + if (HealPrediction.HasValue) + HealPrediction.Value.Write(_worldPacket); + } + + public int SpellID; + public int SpellXSpellVisualID; + public Optional InterruptImmunities; + public ObjectGuid CasterGUID; + public Optional HealPrediction; + public uint ChannelDuration; + } + + public class SpellChannelUpdate : ServerPacket + { + public SpellChannelUpdate() : base(ServerOpcodes.SpellChannelUpdate, ConnectionType.Instance) { } + + public override void Write() + { + _worldPacket.WritePackedGuid(CasterGUID); + _worldPacket.WriteInt32(TimeRemaining); + } + + public ObjectGuid CasterGUID; + public int TimeRemaining; + } + + class ResurrectRequest : ServerPacket + { + public ResurrectRequest() : base(ServerOpcodes.ResurrectRequest) { } + + public override void Write() + { + _worldPacket.WritePackedGuid(ResurrectOffererGUID); + _worldPacket.WriteUInt32(ResurrectOffererVirtualRealmAddress); + _worldPacket.WriteUInt32(PetNumber); + _worldPacket.WriteUInt32(SpellID); + _worldPacket.WriteBits(Name.Length, 11); + _worldPacket.WriteBit(UseTimer); + _worldPacket.WriteBit(Sickness); + _worldPacket.FlushBits(); + + _worldPacket.WriteString(Name); + } + + public ObjectGuid ResurrectOffererGUID; + public uint ResurrectOffererVirtualRealmAddress; + public uint PetNumber; + public uint SpellID; + public bool UseTimer; + public bool Sickness; + public string Name; + } + + class UnlearnSkill : ClientPacket + { + public UnlearnSkill(WorldPacket packet) : base(packet) { } + + public override void Read() + { + SkillLine = _worldPacket.ReadUInt32(); + } + + public uint SkillLine; + } + + class SelfRes : ClientPacket + { + public SelfRes(WorldPacket packet) : base(packet) { } + + public override void Read() { } + } + + class GetMirrorImageData : ClientPacket + { + public GetMirrorImageData(WorldPacket packet) : base(packet) { } + + public override void Read() + { + UnitGUID = _worldPacket.ReadPackedGuid(); + DisplayID = _worldPacket.ReadUInt32(); + } + + public ObjectGuid UnitGUID; + public uint DisplayID; + } + + class MirrorImageComponentedData : ServerPacket + { + public MirrorImageComponentedData() : base(ServerOpcodes.MirrorImageComponentedData) { } + + public override void Write() + { + _worldPacket.WritePackedGuid(UnitGUID); + _worldPacket.WriteUInt32(DisplayID); + _worldPacket.WriteUInt8(RaceID); + _worldPacket.WriteUInt8(Gender); + _worldPacket.WriteUInt8(ClassID); + _worldPacket.WriteUInt8(SkinColor); + _worldPacket.WriteUInt8(FaceVariation); + _worldPacket.WriteUInt8(HairVariation); + _worldPacket.WriteUInt8(HairColor); + _worldPacket.WriteUInt8(BeardVariation); + + CustomDisplay.ForEach(id => _worldPacket.WriteUInt8(id)); + + _worldPacket.WritePackedGuid(GuildGUID); + _worldPacket.WriteUInt32(ItemDisplayID.Count); + + foreach (var itemDisplayId in ItemDisplayID) + _worldPacket.WriteUInt32(itemDisplayId); + } + + public ObjectGuid UnitGUID; + public int DisplayID; + public byte RaceID; + public byte Gender; + public byte ClassID; + public byte SkinColor; + public byte FaceVariation; + public byte HairVariation; + public byte HairColor; + public byte BeardVariation; + public Array CustomDisplay = new Array(PlayerConst.CustomDisplaySize); + public ObjectGuid GuildGUID; + + public List ItemDisplayID = new List(); + } + + class MirrorImageCreatureData : ServerPacket + { + public MirrorImageCreatureData() : base(ServerOpcodes.MirrorImageCreatureData) { } + + public override void Write() + { + _worldPacket.WritePackedGuid(UnitGUID); + _worldPacket.WriteInt32(DisplayID); + } + + public ObjectGuid UnitGUID; + public int DisplayID; + } + + class SpellClick : ClientPacket + { + public SpellClick(WorldPacket packet) : base(packet) { } + + public override void Read() + { + SpellClickUnitGuid = _worldPacket.ReadPackedGuid(); + TryAutoDismount = _worldPacket.HasBit(); + } + + public ObjectGuid SpellClickUnitGuid; + public bool TryAutoDismount; + } + + class ResyncRunes : ServerPacket + { + public ResyncRunes() : base(ServerOpcodes.ResyncRunes) { } + + public override void Write() + { + Runes.Write(_worldPacket); + } + + public RuneData Runes = new RuneData(); + } + + class MissileTrajectoryCollision : ClientPacket + { + public MissileTrajectoryCollision(WorldPacket packet) : base(packet) { } + + public override void Read() + { + Target = _worldPacket.ReadPackedGuid(); + SpellID = _worldPacket.ReadUInt32(); + CastID = _worldPacket.ReadPackedGuid(); + CollisionPos = _worldPacket.ReadVector3(); + } + + public ObjectGuid Target; + public uint SpellID; + public ObjectGuid CastID; + public Vector3 CollisionPos; + } + + class NotifyMissileTrajectoryCollision : ServerPacket + { + public NotifyMissileTrajectoryCollision() : base(ServerOpcodes.NotifyMissileTrajectoryCollision) { } + + public override void Write() + { + _worldPacket.WritePackedGuid(Caster); + _worldPacket.WritePackedGuid(CastID); + _worldPacket.WriteVector3(CollisionPos); + } + + public ObjectGuid Caster; + public ObjectGuid CastID; + public Vector3 CollisionPos; + } + + class UpdateMissileTrajectory : ClientPacket + { + public UpdateMissileTrajectory(WorldPacket packet) : base(packet) { } + + public override void Read() + { + Guid = _worldPacket.ReadPackedGuid(); + MoveMsgID = _worldPacket.ReadUInt16(); + SpellID = _worldPacket.ReadUInt32(); + Pitch = _worldPacket.ReadFloat(); + Speed = _worldPacket.ReadFloat(); + FirePos = _worldPacket.ReadVector3(); + ImpactPos = _worldPacket.ReadVector3(); + bool hasStatus = _worldPacket.HasBit(); + + _worldPacket.ResetBitPos(); + if (hasStatus) + Status.Set(MovementExtensions.ReadMovementInfo(_worldPacket)); + } + + public ObjectGuid Guid; + public ushort MoveMsgID; + public uint SpellID; + public float Pitch; + public float Speed; + public Vector3 FirePos; + public Vector3 ImpactPos; + public Optional Status; + } + + public class SpellDelayed : ServerPacket + { + public SpellDelayed() : base(ServerOpcodes.SpellDelayed, ConnectionType.Instance) { } + + public override void Write() + { + _worldPacket.WritePackedGuid(Caster); + _worldPacket.WriteInt32(ActualDelay); + } + + public ObjectGuid Caster; + public int ActualDelay; + } + + class DispelFailed : ServerPacket + { + public DispelFailed() : base(ServerOpcodes.DispelFailed) { } + + public override void Write() + { + _worldPacket.WritePackedGuid(CasterGUID); + _worldPacket.WritePackedGuid(VictimGUID); + _worldPacket.WriteUInt32(SpellID); + _worldPacket.WriteInt32(FailedSpells.Count); + + FailedSpells.ForEach(FailedSpellID => _worldPacket.WriteUInt32(FailedSpellID)); + } + + public ObjectGuid CasterGUID; + public ObjectGuid VictimGUID; + public uint SpellID; + public List FailedSpells = new List(); + } + + class CustomLoadScreen : ServerPacket + { + public CustomLoadScreen(uint teleportSpellId, uint loadingScreenId) : base(ServerOpcodes.CustomLoadScreen) + { + TeleportSpellID = teleportSpellId; + LoadingScreenID = loadingScreenId; + } + + public override void Write() + { + _worldPacket.WriteUInt32(TeleportSpellID); + _worldPacket.WriteUInt32(LoadingScreenID); + } + + uint TeleportSpellID; + uint LoadingScreenID; + } + + //Structs + public struct SpellLogPowerData + { + public SpellLogPowerData(int powerType, int amount, int cost) + { + PowerType = powerType; + Amount = amount; + Cost = cost; + } + + public int PowerType; + public int Amount; + public int Cost; + } + + public class SpellCastLogData + { + public void Initialize(Unit unit) + { + Health = (long)unit.GetHealth(); + AttackPower = (int)unit.GetTotalAttackPowerValue(unit.GetClass() == Class.Hunter ? WeaponAttackType.RangedAttack : WeaponAttackType.BaseAttack); + SpellPower = unit.SpellBaseDamageBonusDone(SpellSchoolMask.Spell); + PowerData.Add(new SpellLogPowerData((int)unit.getPowerType(), unit.GetPower(unit.getPowerType()), 0)); + } + + public void Initialize(Spell spell) + { + Health = (long)spell.GetCaster().GetHealth(); + AttackPower = (int)spell.GetCaster().GetTotalAttackPowerValue(spell.GetCaster().GetClass() == Class.Hunter ? WeaponAttackType.RangedAttack : WeaponAttackType.BaseAttack); + SpellPower = spell.GetCaster().SpellBaseDamageBonusDone(SpellSchoolMask.Spell); + PowerType primaryPowerType = spell.GetCaster().getPowerType(); + bool primaryPowerAdded = false; + foreach (SpellPowerCost cost in spell.GetPowerCost()) + { + PowerData.Add(new SpellLogPowerData((int)cost.Power, spell.GetCaster().GetPower(cost.Power), cost.Amount)); + if (cost.Power == primaryPowerType) + primaryPowerAdded = true; + } + + if (!primaryPowerAdded) + PowerData.Insert(0, new SpellLogPowerData((int)primaryPowerType, spell.GetCaster().GetPower(primaryPowerType), 0)); + } + + public void Write(WorldPacket data) + { + data.WriteInt64(Health); + data.WriteInt32(AttackPower); + data.WriteInt32(SpellPower); + data.WriteBits(PowerData.Count, 9); + data.FlushBits(); + + foreach (SpellLogPowerData powerData in PowerData) + { + data.WriteInt32(powerData.PowerType); + data.WriteInt32(powerData.Amount); + data.WriteInt32(powerData.Cost); + } + } + + public long Health; + public int AttackPower; + public int SpellPower; + List PowerData = new List(); + } + + class SandboxScalingData + { + public void Write(WorldPacket data) + { + data.WriteBits(Type, 3); + data.WriteInt16(PlayerLevelDelta); + data.WriteUInt16(PlayerItemLevel); + data.WriteUInt8(TargetLevel); + data.WriteUInt8(Expansion); + data.WriteUInt8(Class); + data.WriteUInt8(TargetMinScalingLevel); + data.WriteUInt8(TargetMaxScalingLevel); + data.WriteInt8(TargetScalingLevelDelta); + } + + public uint Type; + public short PlayerLevelDelta; + public ushort PlayerItemLevel; + public byte TargetLevel; + public byte Expansion; + public byte Class = 1; + public byte TargetMinScalingLevel = 1; + public byte TargetMaxScalingLevel = 1; + public sbyte TargetScalingLevelDelta = 1; + } + + public class AuraDataInfo + { + public void Write(WorldPacket data) + { + data.WritePackedGuid(CastID); + data.WriteInt32(SpellID); + data.WriteInt32(SpellXSpellVisualID); + data.WriteUInt8(Flags); + data.WriteUInt32(ActiveFlags); + data.WriteUInt16(CastLevel); + data.WriteUInt8(Applications); + data.WriteBit(CastUnit.HasValue); + data.WriteBit(Duration.HasValue); + data.WriteBit(Remaining.HasValue); + data.WriteBit(TimeMod.HasValue); + data.WriteBits(Points.Length, 6); + data.WriteBits(EstimatedPoints.Count, 6); + data.WriteBit(SandboxScaling.HasValue); + + if (SandboxScaling.HasValue) + SandboxScaling.Value.Write(data); + + if (CastUnit.HasValue) + data.WritePackedGuid(CastUnit.Value); + + if (Duration.HasValue) + data.WriteUInt32(Duration.Value); + + if (Remaining.HasValue) + data.WriteUInt32(Remaining.Value); + + if (TimeMod.HasValue) + data.WriteFloat(TimeMod.Value); + + foreach (var point in Points) + data.WriteFloat(point); + + foreach (var point in EstimatedPoints) + data.WriteFloat(point); + } + + public ObjectGuid CastID; + public int SpellID; + public int SpellXSpellVisualID; + public AuraFlags Flags; + public uint ActiveFlags; + public ushort CastLevel = 1; + public byte Applications = 1; + Optional SandboxScaling; + public Optional CastUnit; + public Optional Duration; + public Optional Remaining; + Optional TimeMod; + public float[] Points = new float[0]; + public List EstimatedPoints = new List(); + } + + public struct AuraInfo + { + public void Write(WorldPacket data) + { + data .WriteUInt8(Slot); + data.WriteBit(AuraData.HasValue); + data.FlushBits(); + + if (AuraData.HasValue) + AuraData.Value.Write(data); + } + + public byte Slot; + public Optional AuraData; + } + + public struct TargetLocation + { + public ObjectGuid Transport; + public Position Location; + + public void Read(WorldPacket data) + { + Transport = data.ReadPackedGuid(); + Location = new Position(); + Location.posX = data.ReadFloat(); + Location.posY = data.ReadFloat(); + Location.posZ = data.ReadFloat(); + } + + public void Write(WorldPacket data) + { + data.WritePackedGuid(Transport); + data.WriteFloat(Location.posX); + data.WriteFloat(Location.posY); + data.WriteFloat(Location.posZ); + } + } + + public class SpellTargetData + { + public void Read(WorldPacket data) + { + Flags = (SpellCastTargetFlags)data.ReadBits(25); + SrcLocation.HasValue = data.HasBit(); + DstLocation.HasValue = data.HasBit(); + Orientation.HasValue = data.HasBit(); + MapID.HasValue = data.HasBit(); + uint nameLength = data.ReadBits(7); + + Unit = data.ReadPackedGuid(); + Item = data.ReadPackedGuid(); + + if (SrcLocation.HasValue) + SrcLocation.Value.Read(data); + + if (DstLocation.HasValue) + DstLocation.Value.Read(data); + + if (Orientation.HasValue) + Orientation.Value = data.ReadFloat(); + + if (MapID.HasValue) + MapID.Value = data.ReadInt32(); + + Name = data.ReadString(nameLength); + } + + public void Write(WorldPacket data) + { + data.WriteBits((uint)Flags, 25); + data.WriteBit(SrcLocation.HasValue); + data.WriteBit(DstLocation.HasValue); + data.WriteBit(Orientation.HasValue); + data.WriteBit(MapID.HasValue); + data.WriteBits(Name.Length, 7); + data.FlushBits(); + + data.WritePackedGuid(Unit); + data.WritePackedGuid(Item); + + if (SrcLocation.HasValue) + SrcLocation.Value.Write(data); + + if (DstLocation.HasValue) + DstLocation.Value.Write(data); + + if (Orientation.HasValue) + data.WriteFloat(Orientation.Value); + + if (MapID.HasValue) + data.WriteInt32(MapID.Value); + + data.WriteString(Name); + } + + public SpellCastTargetFlags Flags; + public ObjectGuid Unit; + public ObjectGuid Item; + public Optional SrcLocation; + public Optional DstLocation; + public Optional Orientation; + public Optional MapID; + public string Name = ""; + } + + public struct MissileTrajectoryRequest + { + public float Pitch; + public float Speed; + + public void Read(WorldPacket data) + { + Pitch = data.ReadFloat(); + Speed = data.ReadFloat(); + } + } + + public struct SpellWeight + { + public uint Type; + public int ID; + public uint Quantity; + } + + public class SpellCastRequest + { + public void Read(WorldPacket data) + { + CastID = data.ReadPackedGuid(); + Misc[0] = data.ReadUInt32(); + Misc[1] = data.ReadUInt32(); + SpellID = data.ReadUInt32(); + SpellXSpellVisualID = data.ReadUInt32(); + MissileTrajectory.Read(data); + Charmer = data.ReadPackedGuid(); + SendCastFlags = data.ReadBits(5); + MoveUpdate.HasValue = data.HasBit(); + var weightCount = data.ReadBits(2); + Target.Read(data); + + if (MoveUpdate.HasValue) + MoveUpdate.Value = MovementExtensions.ReadMovementInfo(data); + + for (var i = 0; i < weightCount; ++i) + { + data.ResetBitPos(); + SpellWeight weight; + weight.Type = data.ReadBits(2); + weight.ID = data.ReadInt32(); + weight.Quantity = data.ReadUInt32(); + Weight.Add(weight); + } + } + + public ObjectGuid CastID; + public uint SpellID; + public uint SpellXSpellVisualID; + public uint SendCastFlags; + public SpellTargetData Target = new SpellTargetData(); + public MissileTrajectoryRequest MissileTrajectory; + public Optional MoveUpdate; + public List Weight = new List(); + public ObjectGuid Charmer; + public uint[] Misc = new uint[2]; + } + + public struct SpellMissStatus + { + public void Write(WorldPacket data) + { + data.WriteBits(Reason, 4); + if (Reason == (byte)SpellMissInfo.Reflect) + data.WriteBits(ReflectStatus, 4); + + data.FlushBits(); + } + + public byte Reason; + public byte ReflectStatus; + } + + public struct SpellPowerData + { + public int Cost; + public PowerType Type; + + public void Write(WorldPacket data) + { + data.WriteInt32(Cost); + data.WriteInt8(Type); + } + } + + public class RuneData + { + public void Write(WorldPacket data) + { + data.WriteUInt8(Start); + data.WriteUInt8(Count); + data.WriteUInt32(Cooldowns.Count); + + foreach (byte cd in Cooldowns) + data.WriteUInt8(cd); + } + + public byte Start; + public byte Count; + public List Cooldowns = new List(); + } + + public struct MissileTrajectoryResult + { + public uint TravelTime; + public float Pitch; + + public void Write(WorldPacket data) + { + data.WriteUInt32(TravelTime); + data.WriteFloat(Pitch); + } + } + + public struct SpellAmmo + { + public int DisplayID; + public sbyte InventoryType; + + public void Write(WorldPacket data) + { + data.WriteInt32(DisplayID); + data.WriteInt8(InventoryType); + } + } + + public struct CreatureImmunities + { + public uint School; + public uint Value; + + public void Write(WorldPacket data) + { + data.WriteUInt32(School); + data.WriteUInt32(Value); + } + } + + public struct SpellHealPrediction + { + public ObjectGuid BeaconGUID; + public uint Points; + public byte Type; + + public void Write(WorldPacket data) + { + data.WriteUInt32(Points); + data.WriteUInt8(Type); + data.WritePackedGuid(BeaconGUID); + } + } + + public class SpellCastData + { + public void Write(WorldPacket data) + { + data.WritePackedGuid(CasterGUID); + data.WritePackedGuid(CasterUnit); + data.WritePackedGuid(CastID); + data.WritePackedGuid(OriginalCastID); + data.WriteInt32(SpellID); + data.WriteUInt32(SpellXSpellVisualID); + data.WriteUInt32(CastFlags); + data.WriteUInt32(CastTime); + + MissileTrajectory.Write(data); + + data.WriteInt32(Ammo.DisplayID); + data.WriteUInt8(DestLocSpellCastIndex); + + Immunities.Write(data); + Predict.Write(data); + + data.WriteBits(CastFlagsEx, 22); + data.WriteBits(HitTargets.Count, 16); + data.WriteBits(MissTargets.Count, 16); + data.WriteBits(MissStatus.Count, 16); + data.WriteBits(RemainingPower.Count, 9); + data.WriteBit(RemainingRunes.HasValue); + data.WriteBits(TargetPoints.Count, 16); + data.FlushBits(); + + foreach (SpellMissStatus status in MissStatus) + status.Write(data); + + Target.Write(data); + + foreach (ObjectGuid target in HitTargets) + data.WritePackedGuid(target); + + foreach (ObjectGuid target in MissTargets) + data.WritePackedGuid(target); + + foreach (SpellPowerData power in RemainingPower) + power.Write(data); + + if (RemainingRunes.HasValue) + RemainingRunes.Value.Write(data); + + foreach (TargetLocation targetLoc in TargetPoints) + targetLoc.Write(data); + } + + public ObjectGuid CasterGUID; + public ObjectGuid CasterUnit; + public ObjectGuid CastID; + public ObjectGuid OriginalCastID; + public int SpellID; + public uint SpellXSpellVisualID; + public SpellCastFlags CastFlags; + public SpellCastFlagsEx CastFlagsEx; + public uint CastTime; + public List HitTargets = new List(); + public List MissTargets = new List(); + public List MissStatus = new List(); + public SpellTargetData Target = new SpellTargetData(); + public List RemainingPower = new List(); + public Optional RemainingRunes; + public MissileTrajectoryResult MissileTrajectory; + public SpellAmmo Ammo; + public byte DestLocSpellCastIndex; + public List TargetPoints = new List(); + public CreatureImmunities Immunities; + public SpellHealPrediction Predict; + } + + public struct SpellModifierData + { + public float ModifierValue; + public byte ClassIndex; + + public void Write(WorldPacket data) + { + data.WriteFloat(ModifierValue); + data.WriteUInt8(ClassIndex); + } + } + + public class SpellModifierInfo + { + public byte ModIndex; + public List ModifierData = new List(); + + public void Write(WorldPacket data) + { + data.WriteUInt8(ModIndex); + data.WriteUInt32(ModifierData.Count); + foreach (SpellModifierData modData in ModifierData) + modData.Write(data); + } + } + + public class SpellCooldownStruct + { + public SpellCooldownStruct(uint spellId, uint forcedCooldown) + { + SrecID = spellId; + ForcedCooldown = forcedCooldown; + } + + public void Write(WorldPacket data) + { + data.WriteUInt32(SrecID); + data.WriteUInt32(ForcedCooldown); + data.WriteFloat(ModRate); + } + + public uint SrecID; + public uint ForcedCooldown; + public float ModRate = 1.0f; + } + + public class SpellHistoryEntry + { + public void Write(WorldPacket data) + { + data.WriteUInt32(SpellID); + data.WriteUInt32(ItemID); + data.WriteUInt32(Category); + data.WriteInt32(RecoveryTime); + data.WriteInt32(CategoryRecoveryTime); + data.WriteFloat(ModRate); + data.WriteBit(unused622_1.HasValue); + data.WriteBit(unused622_2.HasValue); + data.WriteBit(OnHold); + data.FlushBits(); + + if (unused622_1.HasValue) + data .WriteUInt32(unused622_1.Value); + if (unused622_2.HasValue) + data.WriteUInt32(unused622_2.Value); + } + + public uint SpellID; + public uint ItemID; + public uint Category; + public int RecoveryTime; + public int CategoryRecoveryTime; + public float ModRate = 1.0f; + public bool OnHold; + Optional unused622_1; // This field is not used for anything in the client in 6.2.2.20444 + Optional unused622_2; // This field is not used for anything in the client in 6.2.2.20444 + } + + public class SpellChargeEntry + { + public void Write(WorldPacket data) + { + data.WriteUInt32(Category); + data.WriteUInt32(NextRecoveryTime); + data.WriteFloat(ChargeModRate); + data.WriteUInt8(ConsumedCharges); + } + + public uint Category; + public uint NextRecoveryTime; + public float ChargeModRate = 1.0f; + public byte ConsumedCharges; + } + + public struct SpellChannelStartInterruptImmunities + { + public void Write(WorldPacket data) + { + data.WriteInt32(SchoolImmunities); + data.WriteInt32(Immunities); + } + + public int SchoolImmunities; + public int Immunities; + } + + public struct SpellTargetedHealPrediction + { + public void Write(WorldPacket data) + { + data.WritePackedGuid(TargetGUID); + Predict.Write(data); + } + + public ObjectGuid TargetGUID; + public SpellHealPrediction Predict; + } +} diff --git a/Game/Network/Packets/SystemPackets.cs b/Game/Network/Packets/SystemPackets.cs new file mode 100644 index 000000000..782782d16 --- /dev/null +++ b/Game/Network/Packets/SystemPackets.cs @@ -0,0 +1,293 @@ +/* + * Copyright (C) 2012-2017 CypherCore + * + * 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, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +using Framework.Constants; +using Framework.Dynamic; +using System.Collections.Generic; + +namespace Game.Network.Packets +{ + public class FeatureSystemStatus : ServerPacket + { + public FeatureSystemStatus() : base(ServerOpcodes.FeatureSystemStatus) + { + SessionAlert = new Optional(); + EuropaTicketSystemStatus = new Optional(); + } + + public override void Write() + { + _worldPacket.WriteUInt8(ComplaintStatus); + + _worldPacket.WriteUInt32(ScrollOfResurrectionRequestsRemaining); + _worldPacket.WriteUInt32(ScrollOfResurrectionMaxRequestsPerDay); + + _worldPacket.WriteUInt32(CfgRealmID); + _worldPacket.WriteInt32(CfgRealmRecID); + + _worldPacket.WriteUInt32(TwitterPostThrottleLimit); + _worldPacket.WriteUInt32(TwitterPostThrottleCooldown); + + _worldPacket.WriteUInt32(TokenPollTimeSeconds); + _worldPacket.WriteUInt32(TokenRedeemIndex); + _worldPacket.WriteInt64(TokenBalanceAmount); + + _worldPacket.WriteBit(VoiceEnabled); + _worldPacket.WriteBit(EuropaTicketSystemStatus.HasValue); + _worldPacket.WriteBit(ScrollOfResurrectionEnabled); + _worldPacket.WriteBit(BpayStoreEnabled); + _worldPacket.WriteBit(BpayStoreAvailable); + _worldPacket.WriteBit(BpayStoreDisabledByParentalControls); + _worldPacket.WriteBit(ItemRestorationButtonEnabled); + _worldPacket.WriteBit(BrowserEnabled); + _worldPacket.WriteBit(SessionAlert.HasValue); + _worldPacket.WriteBit(RecruitAFriendSendingEnabled); + _worldPacket.WriteBit(CharUndeleteEnabled); + _worldPacket.WriteBit(RestrictedAccount); + _worldPacket.WriteBit(TutorialsEnabled); + _worldPacket.WriteBit(NPETutorialsEnabled); + _worldPacket.WriteBit(TwitterEnabled); + _worldPacket.WriteBit(CommerceSystemEnabled); + _worldPacket.WriteBit(Unk67); + _worldPacket.WriteBit(WillKickFromWorld); + _worldPacket.WriteBit(KioskModeEnabled); + _worldPacket.WriteBit(CompetitiveModeEnabled); + _worldPacket.WriteBit(RaceClassExpansionLevels.HasValue); + _worldPacket.FlushBits(); + + { + _worldPacket.WriteBit(QuickJoinConfig.ToastsDisabled); + _worldPacket.WriteFloat(QuickJoinConfig.ToastDuration); + _worldPacket.WriteFloat(QuickJoinConfig.DelayDuration); + _worldPacket.WriteFloat(QuickJoinConfig.QueueMultiplier); + _worldPacket.WriteFloat(QuickJoinConfig.PlayerMultiplier); + _worldPacket.WriteFloat(QuickJoinConfig.PlayerFriendValue); + _worldPacket.WriteFloat(QuickJoinConfig.PlayerGuildValue); + _worldPacket.WriteFloat(QuickJoinConfig.ThrottleInitialThreshold); + _worldPacket.WriteFloat(QuickJoinConfig.ThrottleDecayTime); + _worldPacket.WriteFloat(QuickJoinConfig.ThrottlePrioritySpike); + _worldPacket.WriteFloat(QuickJoinConfig.ThrottleMinThreshold); + _worldPacket.WriteFloat(QuickJoinConfig.ThrottlePvPPriorityNormal); + _worldPacket.WriteFloat(QuickJoinConfig.ThrottlePvPPriorityLow); + _worldPacket.WriteFloat(QuickJoinConfig.ThrottlePvPHonorThreshold); + _worldPacket.WriteFloat(QuickJoinConfig.ThrottleLfgListPriorityDefault); + _worldPacket.WriteFloat(QuickJoinConfig.ThrottleLfgListPriorityAbove); + _worldPacket.WriteFloat(QuickJoinConfig.ThrottleLfgListPriorityBelow); + _worldPacket.WriteFloat(QuickJoinConfig.ThrottleLfgListIlvlScalingAbove); + _worldPacket.WriteFloat(QuickJoinConfig.ThrottleLfgListIlvlScalingBelow); + _worldPacket.WriteFloat(QuickJoinConfig.ThrottleRfPriorityAbove); + _worldPacket.WriteFloat(QuickJoinConfig.ThrottleRfIlvlScalingAbove); + _worldPacket.WriteFloat(QuickJoinConfig.ThrottleDfMaxItemLevel); + _worldPacket.WriteFloat(QuickJoinConfig.ThrottleDfBestPriority); + } + + if (SessionAlert.HasValue) + { + _worldPacket.WriteInt32(SessionAlert.Value.Delay); + _worldPacket.WriteInt32(SessionAlert.Value.Period); + _worldPacket.WriteInt32(SessionAlert.Value.DisplayTime); + } + + if (RaceClassExpansionLevels.HasValue) + { + _worldPacket.WriteUInt32(RaceClassExpansionLevels.Value.Count); + foreach (var level in RaceClassExpansionLevels.Value) + _worldPacket.WriteUInt8(level); + } + + if (EuropaTicketSystemStatus.HasValue) + { + _worldPacket.WriteBit(EuropaTicketSystemStatus.Value.TicketsEnabled); + _worldPacket.WriteBit(EuropaTicketSystemStatus.Value.BugsEnabled); + _worldPacket.WriteBit(EuropaTicketSystemStatus.Value.ComplaintsEnabled); + _worldPacket.WriteBit(EuropaTicketSystemStatus.Value.SuggestionsEnabled); + + _worldPacket.WriteUInt32(EuropaTicketSystemStatus.Value.ThrottleState.MaxTries); + _worldPacket.WriteUInt32(EuropaTicketSystemStatus.Value.ThrottleState.PerMilliseconds); + _worldPacket.WriteUInt32(EuropaTicketSystemStatus.Value.ThrottleState.TryCount); + _worldPacket.WriteUInt32(EuropaTicketSystemStatus.Value.ThrottleState.LastResetTimeBeforeNow); + } + } + + public bool VoiceEnabled; + public bool BrowserEnabled; + public bool BpayStoreAvailable; + public bool RecruitAFriendSendingEnabled; + public bool BpayStoreEnabled; + public Optional SessionAlert; + public uint ScrollOfResurrectionMaxRequestsPerDay; + public bool ScrollOfResurrectionEnabled; + public Optional EuropaTicketSystemStatus; + public uint ScrollOfResurrectionRequestsRemaining; + public uint CfgRealmID; + public byte ComplaintStatus; + public int CfgRealmRecID; + public uint TwitterPostThrottleLimit; + public uint TwitterPostThrottleCooldown; + public uint TokenPollTimeSeconds; + public uint TokenRedeemIndex; + public long TokenBalanceAmount; + public bool ItemRestorationButtonEnabled; + public bool CharUndeleteEnabled; // Implemented + public bool BpayStoreDisabledByParentalControls; + public bool TwitterEnabled; + public bool CommerceSystemEnabled; + public bool Unk67; + public bool WillKickFromWorld; + + public bool RestrictedAccount; + public bool TutorialsEnabled; + public bool NPETutorialsEnabled; + public bool KioskModeEnabled; + public bool CompetitiveModeEnabled; + public bool TokenBalanceEnabled; + + public Optional> RaceClassExpansionLevels; + public SocialQueueConfig QuickJoinConfig; + + public struct SavedThrottleObjectState + { + public uint MaxTries; + public uint PerMilliseconds; + public uint TryCount; + public uint LastResetTimeBeforeNow; + } + + public struct EuropaTicketConfig + { + public bool TicketsEnabled; + public bool BugsEnabled; + public bool ComplaintsEnabled; + public bool SuggestionsEnabled; + + public SavedThrottleObjectState ThrottleState; + } + + public struct SessionAlertConfig + { + public int Delay; + public int Period; + public int DisplayTime; + } + + public struct SocialQueueConfig + { + public bool ToastsDisabled; + public float ToastDuration; + public float DelayDuration; + public float QueueMultiplier; + public float PlayerMultiplier; + public float PlayerFriendValue; + public float PlayerGuildValue; + public float ThrottleInitialThreshold; + public float ThrottleDecayTime; + public float ThrottlePrioritySpike; + public float ThrottleMinThreshold; + public float ThrottlePvPPriorityNormal; + public float ThrottlePvPPriorityLow; + public float ThrottlePvPHonorThreshold; + public float ThrottleLfgListPriorityDefault; + public float ThrottleLfgListPriorityAbove; + public float ThrottleLfgListPriorityBelow; + public float ThrottleLfgListIlvlScalingAbove; + public float ThrottleLfgListIlvlScalingBelow; + public float ThrottleRfPriorityAbove; + public float ThrottleRfIlvlScalingAbove; + public float ThrottleDfMaxItemLevel; + public float ThrottleDfBestPriority; + } + } + + public class FeatureSystemStatusGlueScreen : ServerPacket + { + public FeatureSystemStatusGlueScreen() : base(ServerOpcodes.FeatureSystemStatusGlueScreen) { } + + public override void Write() + { + _worldPacket.WriteBit(BpayStoreEnabled); + _worldPacket.WriteBit(BpayStoreAvailable); + _worldPacket.WriteBit(BpayStoreDisabledByParentalControls); + _worldPacket.WriteBit(CharUndeleteEnabled); + _worldPacket.WriteBit(CommerceSystemEnabled); + _worldPacket.WriteBit(Unk14); + _worldPacket.WriteBit(WillKickFromWorld); + _worldPacket.WriteBit(IsExpansionPreorderInStore); + _worldPacket.WriteBit(KioskModeEnabled); + _worldPacket.WriteBit(CompetitiveModeEnabled); + _worldPacket.WriteBit(false); // not accessed in handler + _worldPacket.WriteBit(TrialBoostEnabled); + _worldPacket.WriteBit(TokenBalanceEnabled); + _worldPacket.FlushBits(); + + _worldPacket.WriteInt32(TokenPollTimeSeconds); + _worldPacket.WriteInt32(TokenRedeemIndex); + _worldPacket.WriteInt64(TokenBalanceAmount); + } + + public bool BpayStoreAvailable; // NYI + public bool BpayStoreDisabledByParentalControls; // NYI + public bool CharUndeleteEnabled; + public bool BpayStoreEnabled; // NYI + public bool CommerceSystemEnabled; // NYI + public bool Unk14; // NYI + public bool WillKickFromWorld; // NYI + public bool IsExpansionPreorderInStore; // NYI + public bool KioskModeEnabled; // NYI + public bool CompetitiveModeEnabled; // NYI + public bool TrialBoostEnabled; // NYI + public bool TokenBalanceEnabled; // NYI + public int TokenPollTimeSeconds; // NYI + public int TokenRedeemIndex; // NYI + public long TokenBalanceAmount; // NYI + } + + public class MOTD : ServerPacket + { + public MOTD() : base(ServerOpcodes.Motd) { } + + public override void Write() + { + _worldPacket.WriteBits(Text.Count, 4); + _worldPacket.FlushBits(); + + foreach (var line in Text) + { + _worldPacket.WriteBits(line.Length, 7); + _worldPacket.FlushBits(); + _worldPacket.WriteString(line); + } + } + + public List Text; + } + + public class SetTimeZoneInformation : ServerPacket + { + public SetTimeZoneInformation() : base(ServerOpcodes.SetTimeZoneInformation) { } + + public override void Write() + { + _worldPacket.WriteBits(ServerTimeTZ.Length, 7); + _worldPacket.WriteBits(GameTimeTZ.Length, 7); + _worldPacket.WriteString(ServerTimeTZ); + _worldPacket.WriteString(GameTimeTZ); + } + + public string ServerTimeTZ; + public string GameTimeTZ; + } +} diff --git a/Game/Network/Packets/TalentPackets.cs b/Game/Network/Packets/TalentPackets.cs new file mode 100644 index 000000000..6e1a7483e --- /dev/null +++ b/Game/Network/Packets/TalentPackets.cs @@ -0,0 +1,165 @@ +/* + * Copyright (C) 2012-2017 CypherCore + * + * 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, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +using Framework.Constants; +using Game.Entities; +using System.Collections.Generic; + +namespace Game.Network.Packets +{ + class UpdateTalentData : ServerPacket + { + public UpdateTalentData() : base(ServerOpcodes.UpdateTalentData, ConnectionType.Instance) { } + + public override void Write() + { + _worldPacket.WriteUInt8(Info.ActiveGroup); + _worldPacket.WriteUInt32(Info.PrimarySpecialization); + _worldPacket.WriteUInt32(Info.TalentGroups.Count); + + foreach (var talentGroupInfo in Info.TalentGroups) + { + _worldPacket.WriteUInt32(talentGroupInfo.SpecID); + _worldPacket.WriteUInt32(talentGroupInfo.TalentIDs.Count); + _worldPacket.WriteUInt32(talentGroupInfo.PvPTalentIDs.Count); + + foreach (var talentID in talentGroupInfo.TalentIDs) + _worldPacket.WriteUInt16(talentID); + + foreach (ushort talentID in talentGroupInfo.PvPTalentIDs) + _worldPacket.WriteUInt16(talentID); + } + } + + public TalentInfoUpdate Info = new TalentInfoUpdate(); + + public class TalentGroupInfo + { + public uint SpecID; + public List TalentIDs = new List(); + public List PvPTalentIDs = new List(); + } + + public class TalentInfoUpdate + { + public byte ActiveGroup; + public uint PrimarySpecialization; + public List TalentGroups = new List(); + } + } + + class LearnTalents : ClientPacket + { + public LearnTalents(WorldPacket packet) : base(packet) { } + + public override void Read() + { + uint count = _worldPacket.ReadBits(6); + + for (uint i = 0; i < count; ++i) + Talents.Add(_worldPacket.ReadUInt16()); + } + + public Array Talents = new Array(PlayerConst.MaxTalentTiers); + } + + class RespecWipeConfirm : ServerPacket + { + public RespecWipeConfirm() : base(ServerOpcodes.RespecWipeConfirm) { } + + public override void Write() + { + _worldPacket.WriteInt8(RespecType); + _worldPacket.WriteUInt32(Cost); + _worldPacket.WritePackedGuid(RespecMaster); + } + + public ObjectGuid RespecMaster; + public uint Cost; + public SpecResetType RespecType; + } + + class ConfirmRespecWipe : ClientPacket + { + public ConfirmRespecWipe(WorldPacket packet) : base(packet) { } + + public override void Read() + { + RespecMaster = _worldPacket.ReadPackedGuid(); + RespecType = (SpecResetType)_worldPacket.ReadUInt8(); + } + + public ObjectGuid RespecMaster; + public SpecResetType RespecType; + } + + class LearnTalentsFailed : ServerPacket + { + public LearnTalentsFailed() : base(ServerOpcodes.LearnTalentsFailed) { } + + public override void Write() + { + _worldPacket.WriteBits(Reason, 4); + _worldPacket.WriteInt32(SpellID); + _worldPacket.WriteUInt32(Talents.Count); + + foreach (var talent in Talents) + _worldPacket.WriteUInt16(talent); + } + + public uint Reason; + public int SpellID; + public List Talents = new List(); + } + + class ActiveGlyphs : ServerPacket + { + public ActiveGlyphs() : base(ServerOpcodes.ActiveGlyphs) { } + + public override void Write() + { + _worldPacket.WriteUInt32(Glyphs.Count); + foreach (GlyphBinding glyph in Glyphs) + glyph.Write(_worldPacket); + + _worldPacket.WriteBit(IsFullUpdate); + _worldPacket.FlushBits(); + } + + public List Glyphs = new List(); + public bool IsFullUpdate; + } + + //Structs + struct GlyphBinding + { + public GlyphBinding(uint spellId, ushort glyphId) + { + SpellID = spellId; + GlyphID = glyphId; + } + + public void Write(WorldPacket data) + { + data.WriteUInt32(SpellID); + data.WriteUInt16(GlyphID); + } + + uint SpellID; + ushort GlyphID; + } +} diff --git a/Game/Network/Packets/TaxiPackets.cs b/Game/Network/Packets/TaxiPackets.cs new file mode 100644 index 000000000..4241f13e8 --- /dev/null +++ b/Game/Network/Packets/TaxiPackets.cs @@ -0,0 +1,150 @@ +/* + * Copyright (C) 2012-2017 CypherCore + * + * 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, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +using Framework.Constants; +using Framework.Dynamic; +using Game.Entities; + +namespace Game.Network.Packets +{ + class TaxiNodeStatusQuery : ClientPacket + { + public TaxiNodeStatusQuery(WorldPacket packet) : base(packet) { } + + public override void Read() + { + UnitGUID = _worldPacket.ReadPackedGuid(); + } + + public ObjectGuid UnitGUID; + } + + class TaxiNodeStatusPkt : ServerPacket + { + public TaxiNodeStatusPkt() : base(ServerOpcodes.TaxiNodeStatus) { } + + public override void Write() + { + _worldPacket .WritePackedGuid( Unit); + _worldPacket.WriteBits(Status, 2); + _worldPacket.FlushBits(); + } + + public TaxiNodeStatus Status; // replace with TaxiStatus enum + public ObjectGuid Unit; + } + + public class ShowTaxiNodes : ServerPacket + { + public ShowTaxiNodes() : base(ServerOpcodes.ShowTaxiNodes) { } + + public override void Write() + { + _worldPacket.WriteBit(WindowInfo.HasValue); + _worldPacket.FlushBits(); + + _worldPacket.WriteInt32(Nodes.Length); + + if (WindowInfo.HasValue) + { + _worldPacket.WritePackedGuid(WindowInfo.Value.UnitGUID); + _worldPacket.WriteUInt32(WindowInfo.Value.CurrentNode); + } + + foreach (var node in Nodes) + _worldPacket.WriteUInt8(node); + } + + public Optional WindowInfo; + public byte[] Nodes = null; + } + + class EnableTaxiNode : ClientPacket + { + public EnableTaxiNode(WorldPacket packet) : base(packet) { } + + public override void Read() + { + Unit = _worldPacket.ReadPackedGuid(); + } + + public ObjectGuid Unit; + } + + class TaxiQueryAvailableNodes : ClientPacket + { + public TaxiQueryAvailableNodes(WorldPacket packet) : base(packet) { } + + public override void Read() + { + Unit = _worldPacket.ReadPackedGuid(); + } + + public ObjectGuid Unit; + } + + class ActivateTaxi : ClientPacket + { + public ActivateTaxi(WorldPacket packet) : base(packet) { } + + public override void Read() + { + Vendor = _worldPacket.ReadPackedGuid(); + Node = _worldPacket.ReadUInt32(); + GroundMountID = _worldPacket.ReadUInt32(); + FlyingMountID = _worldPacket.ReadUInt32(); + } + + public ObjectGuid Vendor; + public uint Node; + public uint GroundMountID = 0; + public uint FlyingMountID = 0; + } + + class NewTaxiPath : ServerPacket + { + public NewTaxiPath() : base(ServerOpcodes.NewTaxiPath) { } + + public override void Write() { } + } + + class ActivateTaxiReplyPkt : ServerPacket + { + public ActivateTaxiReplyPkt() : base(ServerOpcodes.ActivateTaxiReply) { } + + public override void Write() + { + _worldPacket.WriteBits(Reply, 4); + _worldPacket.FlushBits(); + } + + public ActivateTaxiReply Reply; + } + + class TaxiRequestEarlyLanding : ClientPacket + { + public TaxiRequestEarlyLanding(WorldPacket packet) : base(packet) { } + + public override void Read() { } + } + + public struct ShowTaxiNodesWindowInfo + { + public ObjectGuid UnitGUID; + public int CurrentNode; + } +} diff --git a/Game/Network/Packets/TicketPackets.cs b/Game/Network/Packets/TicketPackets.cs new file mode 100644 index 000000000..23e3e8b08 --- /dev/null +++ b/Game/Network/Packets/TicketPackets.cs @@ -0,0 +1,456 @@ +/* + * Copyright (C) 2012-2017 CypherCore + * + * 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, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +using Framework.Constants; +using Framework.Dynamic; +using Framework.GameMath; +using Game.Entities; +using System.Collections.Generic; + +namespace Game.Network.Packets +{ + public class GMTicketGetSystemStatus : ClientPacket + { + public GMTicketGetSystemStatus(WorldPacket packet) : base(packet) { } + + public override void Read() { } + } + + public class GMTicketSystemStatusPkt : ServerPacket + { + public GMTicketSystemStatusPkt() : base(ServerOpcodes.GmTicketSystemStatus) { } + + public override void Write() + { + _worldPacket.WriteInt32(Status); + } + + public int Status; + } + + public class GMTicketGetCaseStatus : ClientPacket + { + public GMTicketGetCaseStatus(WorldPacket packet) : base(packet) { } + + public override void Read() { } + } + + public class GMTicketCaseStatus : ServerPacket + { + public GMTicketCaseStatus() : base(ServerOpcodes.GmTicketCaseStatus) { } + + public override void Write() + { + _worldPacket.WriteInt32(Cases.Count); + + foreach (var c in Cases) + { + _worldPacket.WriteInt32(c.CaseID); + _worldPacket.WriteInt32(c.CaseOpened); + _worldPacket.WriteInt32(c.CaseStatus); + _worldPacket.WriteInt16(c.CfgRealmID); + _worldPacket.WriteInt64(c.CharacterID); + _worldPacket.WriteInt32(c.WaitTimeOverrideMinutes); + + _worldPacket.WriteBits(c.Url.Length, 11); + _worldPacket.WriteBits(c.WaitTimeOverrideMessage.Length, 10); + + _worldPacket.WriteString(c.Url); + _worldPacket.WriteString(c.WaitTimeOverrideMessage); + } + } + + public List Cases = new List(); + + public struct GMTicketCase + { + public int CaseID; + public int CaseOpened; + public int CaseStatus; + public short CfgRealmID; + public long CharacterID; + public int WaitTimeOverrideMinutes; + public string Url; + public string WaitTimeOverrideMessage; + } + } + + public class GMTicketAcknowledgeSurvey : ClientPacket + { + public GMTicketAcknowledgeSurvey(WorldPacket packet) : base(packet) { } + + public override void Read() + { + CaseID = _worldPacket.ReadInt32(); + } + + int CaseID; + } + + public class SupportTicketSubmitBug : ClientPacket + { + public SupportTicketHeader Header; + public string Note; + + public SupportTicketSubmitBug(WorldPacket packet) : base(packet) { } + + public override void Read() + { + Header.Read(_worldPacket); + Note = _worldPacket.ReadString(_worldPacket.ReadBits(10)); + } + } + + public class SupportTicketSubmitSuggestion : ClientPacket + { + public SupportTicketHeader Header; + public string Note; + + public SupportTicketSubmitSuggestion(WorldPacket packet) : base(packet) { } + + public override void Read() + { + Header.Read(_worldPacket); + Note = _worldPacket.ReadString(_worldPacket.ReadBits(10)); + } + } + + public class SupportTicketSubmitComplaint : ClientPacket + { + public SupportTicketSubmitComplaint(WorldPacket packet) : base(packet) { } + + public override void Read() + { + Header.Read(_worldPacket); + TargetCharacterGUID = _worldPacket.ReadPackedGuid(); + ChatLog.Read(_worldPacket); + ComplaintType = _worldPacket.ReadBits(5); + + uint noteLength = _worldPacket.ReadBits(10); + bool hasMailInfo = _worldPacket.HasBit(); + bool hasCalendarInfo = _worldPacket.HasBit(); + bool hasPetInfo = _worldPacket.HasBit(); + bool hasGuildInfo = _worldPacket.HasBit(); + bool hasLFGListSearchResult = _worldPacket.HasBit(); + bool hasLFGListApplicant = _worldPacket.HasBit(); + + _worldPacket.ResetBitPos(); + + if (hasMailInfo) + { + MailInfo.HasValue = true; + MailInfo.Value.Read(_worldPacket); + } + + Note = _worldPacket.ReadString(noteLength); + + if (hasCalendarInfo) + { + CalenderInfo.HasValue = true; + CalenderInfo.Value.Read(_worldPacket); + } + + if (hasPetInfo) + { + PetInfo.HasValue = true; + PetInfo.Value.Read(_worldPacket); + } + + if (hasGuildInfo) + { + GuildInfo.HasValue = true; + GuildInfo.Value.Read(_worldPacket); + } + + if (hasLFGListSearchResult) + { + LFGListSearchResult.HasValue = true; + LFGListSearchResult.Value.Read(_worldPacket); + } + + if (hasLFGListApplicant) + { + LFGListApplicant.HasValue = true; + LFGListApplicant.Value.Read(_worldPacket); + } + } + + public SupportTicketHeader Header; + public SupportTicketChatLog ChatLog; + public ObjectGuid TargetCharacterGUID; + public byte ComplaintType; + public string Note; + public Optional MailInfo; + public Optional CalenderInfo; + public Optional PetInfo; + public Optional GuildInfo; + public Optional LFGListSearchResult; + public Optional LFGListApplicant; + + public struct SupportTicketChatLine + { + public uint Timestamp; + public string Text; + + public SupportTicketChatLine(WorldPacket data) + { + Timestamp = data.ReadUInt32(); + Text = data.ReadString(data.ReadBits(12)); + } + + public SupportTicketChatLine(uint timestamp, string text) + { + Timestamp = timestamp; + Text = text; + } + + public void Read(WorldPacket data) + { + Timestamp = data.ReadUInt32(); + Text = data.ReadString(data.ReadBits(12)); + } + } + + public class SupportTicketChatLog + { + public void Read(WorldPacket data) + { + uint linesCount = data.ReadUInt32(); + ReportLineIndex.HasValue = data.HasBit(); + data.ResetBitPos(); + + for (uint i = 0; i < linesCount; i++) + Lines.Add(new SupportTicketChatLine(data)); + + if (ReportLineIndex.HasValue) + ReportLineIndex.Value = data.ReadUInt32(); + } + + public List Lines = new List(); + public Optional ReportLineIndex; + } + + public struct SupportTicketMailInfo + { + public void Read(WorldPacket data) + { + MailID = data.ReadInt32(); + uint bodyLength = data.ReadBits(13); + uint subjectLength = data.ReadBits(9); + + MailBody = data.ReadString(bodyLength); + MailSubject = data.ReadString(subjectLength); + } + + int MailID; + string MailSubject; + string MailBody; + } + + public struct SupportTicketCalendarEventInfo + { + public void Read(WorldPacket data) + { + EventID = data.ReadUInt64(); + InviteID = data.ReadUInt64(); + + EventTitle = data.ReadString(data.ReadBits(8)); + } + + ulong EventID; + ulong InviteID; + string EventTitle; + } + + public struct SupportTicketPetInfo + { + public void Read(WorldPacket data) + { + PetID = data.ReadPackedGuid(); + + PetName = data.ReadString(data.ReadBits(8)); + } + + ObjectGuid PetID; + string PetName; + } + + public struct SupportTicketGuildInfo + { + public void Read(WorldPacket data) + { + byte nameLength = data.ReadBits(8); + GuildID = data.ReadPackedGuid(); + + GuildName = data.ReadString(nameLength); + } + + ObjectGuid GuildID; + string GuildName; + } + + public struct SupportTicketLFGListSearchResult + { + public void Read(WorldPacket data) + { + RideTicket.Read(data); + GroupFinderActivityID = data.ReadUInt32(); + LastTitleAuthorGuid = data.ReadPackedGuid(); + LastDescriptionAuthorGuid = data.ReadPackedGuid(); + LastVoiceChatAuthorGuid = data.ReadPackedGuid(); + + byte titleLength = data.ReadBits(8); + byte descriptionLength = data.ReadBits(11); + byte voiceChatLength = data.ReadBits(8); + + Title = data.ReadString(titleLength); + Description = data.ReadString(descriptionLength); + VoiceChat = data.ReadString(voiceChatLength); + } + + RideTicket RideTicket; + uint GroupFinderActivityID; + ObjectGuid LastTitleAuthorGuid; + ObjectGuid LastDescriptionAuthorGuid; + ObjectGuid LastVoiceChatAuthorGuid; + string Title; + string Description; + string VoiceChat; + } + + public struct SupportTicketLFGListApplicant + { + public void Read(WorldPacket data) + { + RideTicket.Read(data); + + Comment = data.ReadString(data.ReadBits(9)); + } + + RideTicket RideTicket; + string Comment; + } + } + + class Complaint : ClientPacket + { + public Complaint(WorldPacket packet) : base(packet) { } + + public override void Read() + { + ComplaintType = (SupportSpamType)_worldPacket.ReadUInt8(); + Offender.Read(_worldPacket); + + switch (ComplaintType) + { + case SupportSpamType.Mail: + MailID = _worldPacket.ReadUInt32(); + break; + case SupportSpamType.Chat: + Chat.Read(_worldPacket); + break; + case SupportSpamType.Calendar: + EventGuid = _worldPacket.ReadUInt64(); + InviteGuid = _worldPacket.ReadUInt64(); + break; + } + } + + public SupportSpamType ComplaintType; + ComplaintOffender Offender; + uint MailID; + ComplaintChat Chat; + + ulong EventGuid; + ulong InviteGuid; + + struct ComplaintOffender + { + public void Read(WorldPacket data) + { + PlayerGuid = data.ReadPackedGuid(); + RealmAddress = data.ReadUInt32(); + TimeSinceOffence = data.ReadUInt32(); + } + + public ObjectGuid PlayerGuid; + public uint RealmAddress; + public uint TimeSinceOffence; + } + + struct ComplaintChat + { + public void Read(WorldPacket data) + { + Command = data.ReadUInt32(); + ChannelID = data.ReadUInt32(); + MessageLog = data.ReadString(data.ReadBits(12)); + } + + public uint Command; + public uint ChannelID; + public string MessageLog; + } + } + + public class ComplaintResult : ServerPacket + { + public ComplaintResult() : base(ServerOpcodes.ComplaintResult) { } + + public override void Write() + { + _worldPacket.WriteUInt32(ComplaintType); + _worldPacket.WriteUInt8(Result); + } + + public SupportSpamType ComplaintType; + public byte Result; + } + + class BugReport : ClientPacket + { + public BugReport(WorldPacket packet) : base(packet) { } + + public override void Read() + { + Type = _worldPacket.ReadBit(); + uint diagLen = _worldPacket.ReadBits(12); + uint textLen = _worldPacket.ReadBits(10); + DiagInfo = _worldPacket.ReadString(diagLen); + Text = _worldPacket.ReadString(textLen); + } + + public uint Type; + public string Text; + public string DiagInfo; + } + + //Structs + public struct SupportTicketHeader + { + public void Read(WorldPacket packet) + { + MapID = packet.ReadUInt32(); + Position = packet.ReadVector3(); + Facing = packet.ReadFloat(); + } + + public uint MapID; + public Vector3 Position; + public float Facing; + } +} diff --git a/Game/Network/Packets/TokenPackets.cs b/Game/Network/Packets/TokenPackets.cs new file mode 100644 index 000000000..82928ef3b --- /dev/null +++ b/Game/Network/Packets/TokenPackets.cs @@ -0,0 +1,98 @@ +/* + * Copyright (C) 2012-2017 CypherCore + * + * 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, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +using Framework.Constants; +using System.Collections.Generic; + +namespace Game.Network.Packets +{ + class UpdateListedAuctionableTokens : ClientPacket + { + public UpdateListedAuctionableTokens(WorldPacket packet) : base(packet) { } + + public override void Read() + { + UnkInt = _worldPacket.ReadUInt32(); + } + + public uint UnkInt; + } + + class UpdateListedAuctionableTokensResponse : ServerPacket + { + public UpdateListedAuctionableTokensResponse() : base(ServerOpcodes.WowTokenUpdateAuctionableListResponse, ConnectionType.Instance) { } + + public override void Write() + { + _worldPacket .WriteUInt32( UnkInt); + _worldPacket .WriteUInt32( Result); + _worldPacket .WriteUInt32(AuctionableTokenAuctionableList.Count); + + foreach (AuctionableTokenAuctionable auctionableTokenAuctionable in AuctionableTokenAuctionableList) + { + _worldPacket.WriteUInt64(auctionableTokenAuctionable.UnkInt1); + _worldPacket.WriteUInt32(auctionableTokenAuctionable.UnkInt2); + _worldPacket.WriteUInt32(auctionableTokenAuctionable.Owner); + _worldPacket.WriteUInt64(auctionableTokenAuctionable.BuyoutPrice); + _worldPacket.WriteUInt32(auctionableTokenAuctionable.EndTime); + } + } + + public uint UnkInt; // send CMSG_UPDATE_WOW_TOKEN_AUCTIONABLE_LIST + public TokenResult Result; + List AuctionableTokenAuctionableList =new List(); + + struct AuctionableTokenAuctionable + { + public ulong UnkInt1; + public uint UnkInt2; + public uint Owner; + public ulong BuyoutPrice; + public uint EndTime; + } + } + + class RequestWowTokenMarketPrice : ClientPacket + { + public RequestWowTokenMarketPrice(WorldPacket packet) : base(packet) { } + + public override void Read() + { + UnkInt = _worldPacket.ReadUInt32(); + } + + public uint UnkInt; + } + + class WowTokenMarketPriceResponse : ServerPacket + { + public WowTokenMarketPriceResponse() : base(ServerOpcodes.WowTokenMarketPriceResponse) { } + + public override void Write() + { + _worldPacket.WriteUInt64(CurrentMarketPrice); + _worldPacket.WriteUInt32(UnkInt); + _worldPacket.WriteUInt32(Result); + _worldPacket.WriteUInt32(UnkInt2); + } + + public ulong CurrentMarketPrice; + public uint UnkInt; // send CMSG_REQUEST_WOW_TOKEN_MARKET_PRICE + public TokenResult Result; + public uint UnkInt2 = 0; + } +} diff --git a/Game/Network/Packets/TotemPackets.cs b/Game/Network/Packets/TotemPackets.cs new file mode 100644 index 000000000..bd1f509d7 --- /dev/null +++ b/Game/Network/Packets/TotemPackets.cs @@ -0,0 +1,75 @@ +/* + * Copyright (C) 2012-2017 CypherCore + * + * 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, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +using Framework.Constants; +using Game.Entities; + +namespace Game.Network.Packets +{ + class TotemDestroyed : ClientPacket + { + public TotemDestroyed(WorldPacket packet) : base(packet) { } + + public override void Read() + { + Slot = _worldPacket.ReadUInt8(); + TotemGUID = _worldPacket.ReadPackedGuid(); + } + + public ObjectGuid TotemGUID; + public byte Slot; + } + + class TotemCreated : ServerPacket + { + public TotemCreated() : base(ServerOpcodes.TotemCreated) { } + + public override void Write() + { + _worldPacket.WriteUInt8(Slot); + _worldPacket.WritePackedGuid(Totem); + _worldPacket.WriteUInt32(Duration); + _worldPacket.WriteUInt32(SpellID); + _worldPacket.WriteFloat(TimeMod); + _worldPacket.WriteBit(CannotDismiss); + _worldPacket.FlushBits(); + } + + public ObjectGuid Totem; + public uint SpellID; + public uint Duration; + public byte Slot; + public float TimeMod = 1.0f; + public bool CannotDismiss; + } + + class TotemMoved : ServerPacket + { + public TotemMoved() : base(ServerOpcodes.TotemMoved) { } + + public override void Write() + { + _worldPacket.WriteUInt8(Slot); + _worldPacket.WriteUInt8(NewSlot); + _worldPacket.WritePackedGuid(Totem); + } + + public ObjectGuid Totem; + public byte Slot; + public byte NewSlot; + } +} diff --git a/Game/Network/Packets/ToyPackets.cs b/Game/Network/Packets/ToyPackets.cs new file mode 100644 index 000000000..fe4904d5d --- /dev/null +++ b/Game/Network/Packets/ToyPackets.cs @@ -0,0 +1,75 @@ +/* + * Copyright (C) 2012-2017 CypherCore + * + * 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, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +using Framework.Constants; +using Game.Entities; +using System.Collections.Generic; + +namespace Game.Network.Packets +{ + class AddToy : ClientPacket + { + public AddToy(WorldPacket packet) : base(packet) { } + + public override void Read() + { + Guid = _worldPacket.ReadPackedGuid(); + } + + public ObjectGuid Guid; + } + + class UseToy : ClientPacket + { + public UseToy(WorldPacket packet) : base(packet) { } + + public override void Read() + { + ItemID = _worldPacket.ReadUInt32(); + Cast.Read(_worldPacket); + } + + public SpellCastRequest Cast = new SpellCastRequest(); + public uint ItemID; + } + + class AccountToysUpdate : ServerPacket + { + public AccountToysUpdate() : base(ServerOpcodes.AccountToysUpdate, ConnectionType.Instance) { } + + public override void Write() + { + _worldPacket.WriteBit(IsFullUpdate); + _worldPacket.FlushBits(); + + // both lists have to have the same size + _worldPacket.WriteUInt32(Toys.Count); + _worldPacket.WriteUInt32(Toys.Count); + + foreach (var item in Toys) + _worldPacket.WriteUInt32(item.Key); + + foreach (var favourite in Toys) + _worldPacket.WriteBit(favourite.Value); + + _worldPacket.FlushBits(); + } + + public bool IsFullUpdate = false; + public Dictionary Toys = new Dictionary(); + } +} diff --git a/Game/Network/Packets/TradePackets.cs b/Game/Network/Packets/TradePackets.cs new file mode 100644 index 000000000..8b1c20216 --- /dev/null +++ b/Game/Network/Packets/TradePackets.cs @@ -0,0 +1,268 @@ +/* + * Copyright (C) 2012-2017 CypherCore + * + * 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, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +using Framework.Constants; +using Framework.Dynamic; +using Game.Entities; +using System.Collections.Generic; + +namespace Game.Network.Packets +{ + public class AcceptTrade : ClientPacket + { + public AcceptTrade(WorldPacket packet) : base(packet) { } + + public override void Read() + { + StateIndex = _worldPacket.ReadUInt32(); + } + + public uint StateIndex; + } + + public class BeginTrade : ClientPacket + { + public BeginTrade(WorldPacket packet) : base(packet) { } + + public override void Read() { } + } + + public class BusyTrade : ClientPacket + { + public BusyTrade(WorldPacket packet) : base(packet) { } + + public override void Read() { } + } + + public class CancelTrade : ClientPacket + { + public CancelTrade(WorldPacket packet) : base(packet) { } + + public override void Read() { } + } + + public class ClearTradeItem : ClientPacket + { + public ClearTradeItem(WorldPacket packet) : base(packet) { } + + public override void Read() + { + TradeSlot = _worldPacket.ReadUInt8(); + } + + public byte TradeSlot; + } + + public class IgnoreTrade : ClientPacket + { + public IgnoreTrade(WorldPacket packet) : base(packet) { } + + public override void Read() { } + } + + public class InitiateTrade : ClientPacket + { + public InitiateTrade(WorldPacket packet) : base(packet) { } + + public override void Read() + { + Guid = _worldPacket.ReadPackedGuid(); + } + + public ObjectGuid Guid; + } + + public class SetTradeCurrency : ClientPacket + { + public SetTradeCurrency(WorldPacket packet) : base(packet) { } + + public override void Read() + { + Type = _worldPacket.ReadUInt32(); + Quantity = _worldPacket.ReadUInt32(); + } + + public uint Type; + public uint Quantity; + } + + public class SetTradeGold : ClientPacket + { + public SetTradeGold(WorldPacket packet) : base(packet) { } + + public override void Read() + { + Coinage = _worldPacket.ReadUInt64(); + } + + public ulong Coinage; + } + + public class SetTradeItem : ClientPacket + { + public SetTradeItem(WorldPacket packet) : base(packet) { } + + public override void Read() + { + TradeSlot = _worldPacket.ReadUInt8(); + PackSlot = _worldPacket.ReadUInt8(); + ItemSlotInPack = _worldPacket.ReadUInt8(); + } + + public byte TradeSlot; + public byte PackSlot; + public byte ItemSlotInPack; + } + + public class UnacceptTrade : ClientPacket + { + public UnacceptTrade(WorldPacket packet) : base(packet) { } + + public override void Read() { } + } + + public class TradeStatusPkt : ServerPacket + { + public TradeStatusPkt() : base(ServerOpcodes.TradeStatus, ConnectionType.Instance) { } + + public override void Write() + { + _worldPacket.WriteBit(PartnerIsSameBnetAccount); + _worldPacket.WriteBits(Status, 5); + switch (Status) + { + case TradeStatus.Failed: + _worldPacket.WriteBit(FailureForYou); + _worldPacket.WriteInt32(BagResult); + _worldPacket.WriteInt32(ItemID); + break; + case TradeStatus.Initiated: + _worldPacket.WriteUInt32(ID); + break; + case TradeStatus.Proposed: + _worldPacket.WritePackedGuid(Partner); + _worldPacket.WritePackedGuid(PartnerAccount); + break; + case TradeStatus.WrongRealm: + case TradeStatus.NotOnTaplist: + _worldPacket.WriteInt8(TradeSlot); + break; + case TradeStatus.NotEnoughCurrency: + case TradeStatus.CurrencyNotTradable: + _worldPacket.WriteInt32(CurrencyType); + _worldPacket.WriteInt32(CurrencyQuantity); + break; + default: + _worldPacket.FlushBits(); + break; + } + } + + public TradeStatus Status = TradeStatus.Initiated; + public byte TradeSlot; + public ObjectGuid PartnerAccount; + public ObjectGuid Partner; + public int CurrencyType; + public int CurrencyQuantity; + public bool FailureForYou; + public InventoryResult BagResult; + public uint ItemID; + public uint ID; + public bool PartnerIsSameBnetAccount; + } + + public class TradeUpdated : ServerPacket + { + public TradeUpdated() : base(ServerOpcodes.TradeUpdated, ConnectionType.Instance) { } + + public override void Write() + { + _worldPacket.WriteUInt8(WhichPlayer); + _worldPacket.WriteUInt32(ID); + _worldPacket.WriteUInt32(ClientStateIndex); + _worldPacket.WriteUInt32(CurrentStateIndex); + _worldPacket.WriteUInt64(Gold); + _worldPacket.WriteInt32(CurrencyType); + _worldPacket.WriteInt32(CurrencyQuantity); + _worldPacket.WriteInt32(ProposedEnchantment); + _worldPacket.WriteUInt32(Items.Count); + + Items.ForEach(item => item.Write(_worldPacket)); + } + + public class UnwrappedTradeItem + { + public void Write(WorldPacket data) + { + data.WriteInt32(EnchantID); + data.WriteInt32(OnUseEnchantmentID); + data.WritePackedGuid(Creator); + data.WriteInt32(Charges); + data.WriteUInt32(MaxDurability); + data.WriteUInt32(Durability); + data.WriteBits(Gems.Count, 2); + data.WriteBit(Lock); + data.FlushBits(); + + foreach (var gem in Gems) + gem.Write(data); + } + + public ItemInstance Item; + public int EnchantID; + public int OnUseEnchantmentID; + public ObjectGuid Creator; + public int Charges; + public bool Lock; + public uint MaxDurability; + public uint Durability; + public List Gems = new List(); + } + + public class TradeItem + { + public void Write(WorldPacket data) + { + data.WriteUInt8(Slot); + data.WriteUInt32(StackCount); + data.WritePackedGuid(GiftCreator); + Item.Write(data); + data.WriteBit(Unwrapped.HasValue); + data.FlushBits(); + + if (Unwrapped.HasValue) + Unwrapped.Value.Write(data); + } + + public byte Slot; + public ItemInstance Item = new ItemInstance(); + public int StackCount; + public ObjectGuid GiftCreator; + public Optional Unwrapped; + } + + public ulong Gold; + public uint CurrentStateIndex; + public byte WhichPlayer; + public uint ClientStateIndex; + public List Items = new List(); + public int CurrencyType; + public uint ID; + public int ProposedEnchantment; + public int CurrencyQuantity; + } +} \ No newline at end of file diff --git a/Game/Network/Packets/TransmogrificationPackets.cs b/Game/Network/Packets/TransmogrificationPackets.cs new file mode 100644 index 000000000..a1950a7d9 --- /dev/null +++ b/Game/Network/Packets/TransmogrificationPackets.cs @@ -0,0 +1,80 @@ +/* + * Copyright (C) 2012-2017 CypherCore + * + * 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, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +using Framework.Constants; +using Game.Entities; +using System.Collections.Generic; + +namespace Game.Network.Packets +{ + class TransmogrifyItems : ClientPacket + { + public TransmogrifyItems(WorldPacket packet) : base(packet) { } + + public override void Read() + { + var itemsCount = _worldPacket.ReadUInt32(); + Npc = _worldPacket.ReadPackedGuid(); + + for (var i = 0; i < itemsCount; ++i) + { + TransmogrifyItem item = new TransmogrifyItem(); + item.Read(_worldPacket); + Items.Add(item); + } + + CurrentSpecOnly = _worldPacket.HasBit(); + } + + public ObjectGuid Npc; + public Array Items = new Array(13); + public bool CurrentSpecOnly; + } + + class TransmogCollectionUpdate : ServerPacket + { + public TransmogCollectionUpdate() : base(ServerOpcodes.TransmogCollectionUpdate) { } + + public override void Write() + { + _worldPacket.WriteBit(IsFullUpdate); + _worldPacket.WriteBit(IsSetFavorite); + _worldPacket.WriteUInt32(FavoriteAppearances.Count); + + foreach (uint itemModifiedAppearanceId in FavoriteAppearances) + _worldPacket.WriteUInt32(itemModifiedAppearanceId); + } + + public bool IsFullUpdate; + public bool IsSetFavorite; + public List FavoriteAppearances = new List(); + } + + struct TransmogrifyItem + { + public void Read(WorldPacket data) + { + ItemModifiedAppearanceID = data.ReadInt32(); + Slot = data.ReadUInt32(); + SpellItemEnchantmentID = data.ReadInt32(); + } + + public int ItemModifiedAppearanceID; + public uint Slot; + public int SpellItemEnchantmentID; + } +} diff --git a/Game/Network/Packets/UpdatePackets.cs b/Game/Network/Packets/UpdatePackets.cs new file mode 100644 index 000000000..89c499c4b --- /dev/null +++ b/Game/Network/Packets/UpdatePackets.cs @@ -0,0 +1,37 @@ +/* + * Copyright (C) 2012-2017 CypherCore + * + * 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, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +using Framework.Constants; + +namespace Game.Network.Packets +{ + public class UpdateObject : ServerPacket + { + public UpdateObject() : base(ServerOpcodes.UpdateObject, ConnectionType.Instance) { } + + public override void Write() + { + _worldPacket.WriteUInt32(NumObjUpdates); + _worldPacket.WriteUInt16(MapID); + _worldPacket.WriteBytes(Data); + } + + public uint NumObjUpdates; + public ushort MapID; + public byte[] Data; + } +} diff --git a/Game/Network/Packets/VehiclePackets.cs b/Game/Network/Packets/VehiclePackets.cs new file mode 100644 index 000000000..8d7d1dd73 --- /dev/null +++ b/Game/Network/Packets/VehiclePackets.cs @@ -0,0 +1,160 @@ +/* + * Copyright (C) 2012-2017 CypherCore + * + * 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, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +using Framework.Constants; +using Game.Entities; + +namespace Game.Network.Packets +{ + public class MoveSetVehicleRecID : ServerPacket + { + public MoveSetVehicleRecID() : base(ServerOpcodes.MoveSetVehicleRecId) { } + + public override void Write() + { + _worldPacket.WritePackedGuid(MoverGUID); + _worldPacket.WriteUInt32(SequenceIndex); + _worldPacket.WriteUInt32(VehicleRecID); + } + + public ObjectGuid MoverGUID; + public uint SequenceIndex; + public uint VehicleRecID; + } + + public class MoveSetVehicleRecIdAck : ClientPacket + { + public MoveSetVehicleRecIdAck(WorldPacket packet) : base(packet) { } + + public override void Read() + { + Data.Read(_worldPacket); + VehicleRecID = _worldPacket.ReadInt32(); ; + } + + public MovementAck Data; + public int VehicleRecID; + } + + public class SetVehicleRecID : ServerPacket + { + public SetVehicleRecID() : base(ServerOpcodes.SetVehicleRecId, ConnectionType.Instance) { } + + public override void Write() + { + _worldPacket.WritePackedGuid(VehicleGUID); + _worldPacket.WriteUInt32(VehicleRecID); + } + + public ObjectGuid VehicleGUID; + public uint VehicleRecID; + } + + public class OnCancelExpectedRideVehicleAura : ServerPacket + { + public OnCancelExpectedRideVehicleAura() : base(ServerOpcodes.OnCancelExpectedRideVehicleAura, ConnectionType.Instance) { } + + public override void Write() { } + } + + public class MoveDismissVehicle : ClientPacket + { + public MoveDismissVehicle(WorldPacket packet) : base(packet) { } + + public override void Read() + { + Status = MovementExtensions.ReadMovementInfo(_worldPacket); + } + + public MovementInfo Status; + } + + public class RequestVehiclePrevSeat : ClientPacket + { + public RequestVehiclePrevSeat(WorldPacket packet) : base(packet) { } + + public override void Read() { } + } + + public class RequestVehicleNextSeat : ClientPacket + { + public RequestVehicleNextSeat(WorldPacket packet) : base(packet) { } + + public override void Read() { } + } + + public class MoveChangeVehicleSeats : ClientPacket + { + public MoveChangeVehicleSeats(WorldPacket packet) : base(packet) { } + + public override void Read() + { + Status = MovementExtensions.ReadMovementInfo(_worldPacket); + DstVehicle = _worldPacket.ReadPackedGuid(); + DstSeatIndex = _worldPacket.ReadUInt8(); + } + + public ObjectGuid DstVehicle; + public MovementInfo Status; + public byte DstSeatIndex = 255; + } + + public class RequestVehicleSwitchSeat : ClientPacket + { + public RequestVehicleSwitchSeat(WorldPacket packet) : base(packet) { } + + public override void Read() + { + Vehicle = _worldPacket.ReadPackedGuid(); + SeatIndex = _worldPacket.ReadUInt8(); + } + + public ObjectGuid Vehicle; + public byte SeatIndex = 255; + } + + public class RideVehicleInteract : ClientPacket + { + public RideVehicleInteract(WorldPacket packet) : base(packet) { } + + public override void Read() + { + Vehicle = _worldPacket.ReadPackedGuid(); + } + + public ObjectGuid Vehicle; + } + + public class EjectPassenger : ClientPacket + { + public EjectPassenger(WorldPacket packet) : base(packet) { } + + public override void Read() + { + Passenger = _worldPacket.ReadPackedGuid(); + } + + public ObjectGuid Passenger; + } + + public class RequestVehicleExit : ClientPacket + { + public RequestVehicleExit(WorldPacket packet) : base(packet) { } + + public override void Read() { } + } +} diff --git a/Game/Network/Packets/VoidStoragePackets.cs b/Game/Network/Packets/VoidStoragePackets.cs new file mode 100644 index 000000000..1353e03c8 --- /dev/null +++ b/Game/Network/Packets/VoidStoragePackets.cs @@ -0,0 +1,183 @@ +/* + * Copyright (C) 2012-2017 CypherCore + * + * 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, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +using Framework.Constants; +using Game.Entities; +using System.Collections.Generic; + +namespace Game.Network.Packets +{ + class VoidTransferResult : ServerPacket + { + public VoidTransferResult(VoidTransferError result) : base(ServerOpcodes.VoidTransferResult, ConnectionType.Instance) + { + Result = result; + } + + public override void Write() + { + _worldPacket.WriteInt32(Result); + } + + public VoidTransferError Result; + } + + class UnlockVoidStorage : ClientPacket + { + public UnlockVoidStorage(WorldPacket packet) : base(packet) { } + + public override void Read() + { + Npc = _worldPacket.ReadPackedGuid(); + } + + public ObjectGuid Npc; + } + + class QueryVoidStorage : ClientPacket + { + public QueryVoidStorage(WorldPacket packet) : base(packet) { } + + public override void Read() + { + Npc = _worldPacket.ReadPackedGuid(); + } + + public ObjectGuid Npc; + } + + class VoidStorageFailed : ServerPacket + { + public VoidStorageFailed() : base(ServerOpcodes.VoidStorageFailed, ConnectionType.Instance) { } + + public override void Write() + { + _worldPacket.WriteUInt8(Reason); + } + + public byte Reason = 0; + } + + class VoidStorageContents : ServerPacket + { + public VoidStorageContents() : base(ServerOpcodes.VoidStorageContents, ConnectionType.Instance) { } + + public override void Write() + { + _worldPacket.WriteBits(Items.Count, 8); + _worldPacket.FlushBits(); + + foreach (VoidItem voidItem in Items) + voidItem.Write(_worldPacket); + } + + public List Items = new List(); + } + + class VoidStorageTransfer : ClientPacket + { + public VoidStorageTransfer(WorldPacket packet) : base(packet) { } + + public override void Read() + { + Npc = _worldPacket.ReadPackedGuid(); + var DepositCount = _worldPacket.ReadInt32(); + var WithdrawalCount = _worldPacket.ReadInt32(); + + for (uint i = 0; i < DepositCount; ++i) + Deposits[i] = _worldPacket.ReadPackedGuid(); + + for (uint i = 0; i < WithdrawalCount; ++i) + Withdrawals[i] = _worldPacket.ReadPackedGuid(); + } + + public ObjectGuid[] Withdrawals = new ObjectGuid[(int)SharedConst.VoidStorageMaxWithdraw]; + public ObjectGuid[] Deposits = new ObjectGuid[(int)SharedConst.VoidStorageMaxDeposit]; + public ObjectGuid Npc; + } + + class VoidStorageTransferChanges : ServerPacket + { + public VoidStorageTransferChanges() : base(ServerOpcodes.VoidStorageTransferChanges, ConnectionType.Instance) { } + + public override void Write() + { + _worldPacket.WriteBits(AddedItems.Count, 4); + _worldPacket.WriteBits(RemovedItems.Count, 4); + _worldPacket.FlushBits(); + + foreach (VoidItem addedItem in AddedItems) + addedItem.Write(_worldPacket); + + foreach (ObjectGuid removedItem in RemovedItems) + _worldPacket.WritePackedGuid(removedItem); + } + + public List RemovedItems = new List(); + public List AddedItems = new List(); + } + + class SwapVoidItem : ClientPacket + { + public SwapVoidItem(WorldPacket packet) : base(packet) { } + + public override void Read() + { + Npc = _worldPacket.ReadPackedGuid(); + VoidItemGuid = _worldPacket.ReadPackedGuid(); + DstSlot = _worldPacket.ReadUInt32(); + } + + public ObjectGuid Npc; + public ObjectGuid VoidItemGuid; + public uint DstSlot; + } + + class VoidItemSwapResponse : ServerPacket + { + public VoidItemSwapResponse() : base(ServerOpcodes.VoidItemSwapResponse, ConnectionType.Instance) { } + + public override void Write() + { + _worldPacket.WritePackedGuid(VoidItemA); + _worldPacket.WriteUInt32(VoidItemSlotA); + _worldPacket.WritePackedGuid(VoidItemB); + _worldPacket.WriteUInt32(VoidItemSlotB); + } + + public ObjectGuid VoidItemA; + public ObjectGuid VoidItemB; + public uint VoidItemSlotA; + public uint VoidItemSlotB; + } + + struct VoidItem + { + public void Write(WorldPacket data) + { + data .WritePackedGuid(Guid); + data .WritePackedGuid(Creator); + data .WriteUInt32(Slot); + Item.Write(data); + } + + public ObjectGuid Guid; + public ObjectGuid Creator; + public uint Slot; + public ItemInstance Item; + } +} diff --git a/Game/Network/Packets/WardenPackets.cs b/Game/Network/Packets/WardenPackets.cs new file mode 100644 index 000000000..4dbda4799 --- /dev/null +++ b/Game/Network/Packets/WardenPackets.cs @@ -0,0 +1,179 @@ +/* + * Copyright (C) 2012-2017 CypherCore + * + * 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, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +using Framework.Constants; +using Framework.IO; + +namespace Game.Network.Packets +{ + public enum WardenOpcodes + { + // Client->Server + CMSG_ModuleMissing = 0, + Cmsg_ModuleOk = 1, + Cmsg_CheatChecksResult = 2, + Cmsg_MemChecksResult = 3, // Only Sent If Mem_Check Bytes Doesn'T Match + Cmsg_HashResult = 4, + Cmsg_ModuleFailed = 5, // This Is Sent When Client Failed To Load Uploaded Module Due To Cache Fail + + // Server->Client + Smsg_ModuleUse = 0, + Smsg_ModuleCache = 1, + Smsg_CheatChecksRequest = 2, + Smsg_ModuleInitialize = 3, + Smsg_MemChecksRequest = 4, // Byte Len; While (!Eof) { Byte Unk(1); Byte Index(++); String Module(Can Be 0); Int Offset; Byte Len; Byte[] Bytes_To_Compare[Len]; } + Smsg_HashRequest = 5 + } + + class WardenData : ClientPacket + { + public WardenData(WorldPacket packet) : base(packet) { } + + public override void Read() + { + uint size = _worldPacket.ReadUInt32(); + + if (size != 0) + Data = new ByteBuffer(_worldPacket.ReadBytes(size)); + } + + public ByteBuffer Data; + } + + class WardenDataServer : ServerPacket + { + public WardenDataServer() : base(ServerOpcodes.WardenData) { } + + public override void Write() + { + _worldPacket.WriteBytes(Data); + } + + public ByteBuffer Data; + } + + class WardenModuleTransfer : ByteBuffer + { + public byte[] Write() + { + WriteUInt8(Command); + WriteUInt16(DataSize); + WriteBytes(Data, 500); + + return GetData(); + } + + public WardenOpcodes Command; + public ushort DataSize; + public byte[] Data = new byte[500]; + } + + class WardenModuleUse : ByteBuffer + { + public byte[] Write() + { + WriteUInt8(Command); + WriteBytes(ModuleId, 16); + WriteBytes(ModuleKey, 16); + WriteUInt32(Size); + + return GetData(); + } + + public WardenOpcodes Command; + public byte[] ModuleId = new byte[16]; + public byte[] ModuleKey = new byte[16]; + public uint Size; + } + + class WardenHashRequest : ByteBuffer + { + public byte[] Write() + { + WriteUInt8(Command); + WriteBytes(Seed); + + return GetData(); + } + + public WardenOpcodes Command; + public byte[] Seed = new byte[16]; + } + + class WardenInitModuleRequest : ByteBuffer + { + public byte[] Write() + { + WriteUInt8(Command1); + WriteUInt16(Size1); + WriteUInt32(CheckSumm1); + WriteUInt8(Unk1); + WriteUInt8(Unk2); + WriteUInt8(Type); + WriteUInt8(String_library1); + foreach (var function in Function1) + WriteUInt32(function); + + WriteUInt8(Command2); + WriteUInt16(Size2); + WriteUInt32(CheckSumm2); + WriteUInt8(Unk3); + WriteUInt8(Unk4); + WriteUInt8(String_library2); + WriteUInt32(Function2); + WriteUInt8(Function2_set); + + WriteUInt8(Command3); + WriteUInt16(Size3); + WriteUInt32(CheckSumm3); + WriteUInt8(Unk5); + WriteUInt8(Unk6); + WriteUInt8(String_library3); + WriteUInt32(Function3); + WriteUInt8(Function3_set); + + return GetData(); + } + + public WardenOpcodes Command1; + public ushort Size1; + public uint CheckSumm1; + public byte Unk1; + public byte Unk2; + public byte Type; + public byte String_library1; + public uint[] Function1 = new uint[4]; + + public WardenOpcodes Command2; + public ushort Size2; + public uint CheckSumm2; + public byte Unk3; + public byte Unk4; + public byte String_library2; + public uint Function2; + public byte Function2_set; + + public WardenOpcodes Command3; + public ushort Size3; + public uint CheckSumm3; + public byte Unk5; + public byte Unk6; + public byte String_library3; + public uint Function3; + public byte Function3_set; + } +} diff --git a/Game/Network/Packets/WhoPackets.cs b/Game/Network/Packets/WhoPackets.cs new file mode 100644 index 000000000..5d328d708 --- /dev/null +++ b/Game/Network/Packets/WhoPackets.cs @@ -0,0 +1,172 @@ +/* + * Copyright (C) 2012-2017 CypherCore + * + * 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, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +using Framework.Constants; +using Framework.Dynamic; +using Game.Entities; +using System.Collections.Generic; + +namespace Game.Network.Packets +{ + public class WhoIsRequest : ClientPacket + { + public WhoIsRequest(WorldPacket packet) : base(packet) { } + + public override void Read() + { + CharName = _worldPacket.ReadString(_worldPacket.ReadBits(6)); + } + + public string CharName; + } + + public class WhoIsResponse : ServerPacket + { + public WhoIsResponse() : base(ServerOpcodes.WhoIs) { } + + public override void Write() + { + _worldPacket.WriteBits(AccountName.Length, 11); + _worldPacket.WriteString(AccountName); + } + + public string AccountName; + } + + public class WhoRequestPkt : ClientPacket + { + public WhoRequestPkt(WorldPacket packet) : base(packet) { } + + public override void Read() + { + uint areasCount = _worldPacket.ReadBits(4); + + Request.Read(_worldPacket); + + for (int i = 0; i < areasCount; ++i) + Areas.Add(_worldPacket.ReadInt32()); + } + + public WhoRequest Request = new WhoRequest(); + public List Areas= new List(); + } + + public class WhoResponsePkt : ServerPacket + { + public WhoResponsePkt() : base(ServerOpcodes.Who) { } + + public override void Write() + { + _worldPacket.WriteBits(Response.Count, 6); + _worldPacket.FlushBits(); + + Response.ForEach(p => p.Write(_worldPacket)); + } + + public List Response = new List(); + } + + public struct WhoRequestServerInfo + { + public void Read(WorldPacket data) + { + FactionGroup = data.ReadInt32(); + Locale = data.ReadInt32(); + RequesterVirtualRealmAddress = data.ReadUInt32(); + } + + public int FactionGroup; + public int Locale; + public uint RequesterVirtualRealmAddress; + } + + public class WhoRequest + { + public void Read(WorldPacket data) + { + MinLevel = data.ReadInt32(); + MaxLevel = data.ReadInt32(); + RaceFilter = data.ReadInt32(); + ClassFilter = data.ReadInt32(); + + uint nameLength = data.ReadBits(6); + uint virtualRealmNameLength = data.ReadBits(9); + uint guildNameLength = data.ReadBits(7); + uint guildVirtualRealmNameLength = data.ReadBits(9); + uint wordsCount = data.ReadBits(3); + + ShowEnemies = data.HasBit(); + ShowArenaPlayers = data.HasBit(); + ExactName = data.HasBit(); + ServerInfo.HasValue = data.HasBit(); + data.ResetBitPos(); + + for (int i = 0; i < wordsCount; ++i) + { + Words.Add(data.ReadString(data.ReadBits(7))); + data.ResetBitPos(); + } + + Name = data.ReadString(nameLength); + VirtualRealmName = data.ReadString(virtualRealmNameLength); + Guild = data.ReadString(guildNameLength); + GuildVirtualRealmName = data.ReadString(guildVirtualRealmNameLength); + + if (ServerInfo.HasValue) + ServerInfo.Value.Read(data); + } + + public int MinLevel; + public int MaxLevel; + public string Name; + public string VirtualRealmName; + public string Guild; + public string GuildVirtualRealmName; + public int RaceFilter = -1; + public int ClassFilter = -1; + public List Words = new List(); + public bool ShowEnemies; + public bool ShowArenaPlayers; + public bool ExactName; + public Optional ServerInfo; + } + + public class WhoEntry + { + public void Write(WorldPacket data) + { + PlayerData.Write(data); + + data.WritePackedGuid(GuildGUID); + data.WriteUInt32(GuildVirtualRealmAddress); + data.WriteInt32(AreaID); + + data.WriteBits(GuildName.Length, 7); + data.WriteBit(IsGM); + data.WriteString(GuildName); + + data.FlushBits(); + } + + public PlayerGuidLookupData PlayerData = new PlayerGuidLookupData(); + public ObjectGuid GuildGUID; + public uint GuildVirtualRealmAddress; + public string GuildName = ""; + public int AreaID; + public bool IsGM; + } +} diff --git a/Game/Network/Packets/WorldStatePackets.cs b/Game/Network/Packets/WorldStatePackets.cs new file mode 100644 index 000000000..626172397 --- /dev/null +++ b/Game/Network/Packets/WorldStatePackets.cs @@ -0,0 +1,86 @@ +/* + * Copyright (C) 2012-2017 CypherCore + * + * 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, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +using Framework.Constants; +using System.Collections.Generic; + +namespace Game.Network.Packets +{ + public class InitWorldStates : ServerPacket + { + public InitWorldStates() : base(ServerOpcodes.InitWorldStates, ConnectionType.Instance) { } + + public override void Write() + { + _worldPacket.WriteUInt32(MapID); + _worldPacket.WriteUInt32(AreaID); + _worldPacket.WriteUInt32(SubareaID); + + _worldPacket.WriteUInt32(Worldstates.Count); + foreach (WorldStateInfo wsi in Worldstates) + { + _worldPacket.WriteUInt32(wsi.VariableID); + _worldPacket.WriteInt32(wsi.Value); + } + } + + public void AddState(uint variableID, int value) + { + Worldstates.Add(new WorldStateInfo(variableID, value)); + } + + public void AddState(uint variableID, bool value) + { + Worldstates.Add(new WorldStateInfo(variableID, value ? 1 : 0)); + } + + public uint AreaID; + public uint SubareaID; + public uint MapID; + + List Worldstates = new List(); + + struct WorldStateInfo + { + public WorldStateInfo(uint variableID, int value) + { + VariableID = variableID; + Value = value; + } + + public uint VariableID; + public int Value; + } + } + + public class UpdateWorldState : ServerPacket + { + public UpdateWorldState() : base(ServerOpcodes.UpdateWorldState, ConnectionType.Instance) { } + + public override void Write() + { + _worldPacket.WriteUInt32(VariableID); + _worldPacket.WriteInt32(Value); + _worldPacket.WriteBit(Hidden); + _worldPacket.FlushBits(); + } + + public int Value; + public bool Hidden; // @todo: research + public uint VariableID; + } +} diff --git a/Game/Network/WorldSocket.cs b/Game/Network/WorldSocket.cs new file mode 100644 index 000000000..345fb5076 --- /dev/null +++ b/Game/Network/WorldSocket.cs @@ -0,0 +1,760 @@ +/* + * Copyright (C) 2012-2017 CypherCore + * + * 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, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +using Framework.Constants; +using Framework.Cryptography; +using Framework.Database; +using Framework.IO; +using Framework.Networking; +using Game.Network.Packets; +using System; +using System.IO; +using System.Net.Sockets; +using System.Runtime.InteropServices; + +namespace Game.Network +{ + public class WorldSocket : SocketBase + { + string ClientConnectionInitialize = "WORLD OF WARCRAFT CONNECTION - CLIENT TO SERVER"; + string ServerConnectionInitialize = "WORLD OF WARCRAFT CONNECTION - SERVER TO CLIENT"; + + byte[] AuthCheckSeed = { 0xC5, 0xC6, 0x98, 0x95, 0x76, 0x3F, 0x1D, 0xCD, 0xB6, 0xA1, 0x37, 0x28, 0xB3, 0x12, 0xFF, 0x8A }; + byte[] SessionKeySeed = { 0x58, 0xCB, 0xCF, 0x40, 0xFE, 0x2E, 0xCE, 0xA6, 0x5A, 0x90, 0xB8, 0x01, 0x68, 0x6C, 0x28, 0x0B }; + byte[] ContinuedSessionSeed = { 0x16, 0xAD, 0x0C, 0xD4, 0x46, 0xF9, 0x4F, 0xB2, 0xEF, 0x7D, 0xEA, 0x2A, 0x17, 0x66, 0x4D, 0x2F }; + + public WorldSocket(Socket socket) : base(socket) + { + _connectType = ConnectionType.Realm; + _serverChallenge = new byte[0].GenerateRandomKey(16); + worldCrypt = new WorldCrypt(); + } + + public override void Start() + { + string ip_address = GetRemoteIpAddress().ToString(); + + PreparedStatement stmt = DB.Login.GetPreparedStatement(LoginStatements.SEL_IP_INFO); + stmt.AddValue(0, ip_address); + stmt.AddValue(1, BitConverter.ToUInt32(GetRemoteIpAddress().GetAddressBytes(), 0)); + + _queryProcessor.AddQuery(DB.Login.AsyncQuery(stmt).WithCallback(CheckIpCallback)); + } + + void CheckIpCallback(SQLResult result) + { + if (!result.IsEmpty()) + { + bool banned = false; + do + { + if (result.Read(0) != 0) + banned = true; + + _ipCountry = result.Read(1); + + } while (result.NextRow()); + + if (banned) + { + Log.outError(LogFilter.Network, "WorldSocket.Connect: Sent Auth Response (IP {0} banned).", GetRemoteIpAddress().ToString()); + CloseSocket(); + return; + } + } + + ByteBuffer packet = new ByteBuffer(); + packet.WriteString(ServerConnectionInitialize); + packet.WriteString("\n"); + AsyncWrite(packet.GetData()); + + AsyncReadWithCallback(InitializeHandler); + } + + public void InitializeHandler(object sender, SocketAsyncEventArgs args) + { + args.Completed -= InitializeHandler; + if (args.SocketError != SocketError.Success) + { + CloseSocket(); + return; + } + + if (args.BytesTransferred > 0) + { + ByteBuffer connBuffer = new ByteBuffer(GetReceiveBuffer()); + + string initializer = connBuffer.ReadString((uint)ClientConnectionInitialize.Length); + if (initializer != ClientConnectionInitialize) + { + CloseSocket(); + return; + } + + // Initialize the zlib stream + _compressionStream = new ZLib.z_stream(); + + // Initialize the deflate algo... + var z_res1 = ZLib.deflateInit2(_compressionStream, 1, 8, -15, 8, 0); + if (z_res1 != 0) + { + CloseSocket(); + Log.outError(LogFilter.Network, "Can't initialize packet compression (zlib: deflateInit2_) Error code: {0}", z_res1); + return; + } + + HandleSendAuthSession(); + AsyncRead(); + return; + } + + AsyncReadWithCallback(InitializeHandler); + } + + public override void ReadHandler(int transferredBytes) + { + if (!IsOpen()) + return; + + while (transferredBytes > 5) + { + if (worldCrypt.IsInitialized) + worldCrypt.Decrypt(GetReceiveBuffer(), 4); + + int size; + uint opcode; + if (!ReadHeader(out opcode, out size)) + { + CloseSocket(); + return; + } + + var data = new byte[size]; + Buffer.BlockCopy(GetReceiveBuffer(), 6, data, 0, size); + if (!ProcessPacket(new WorldPacket(data, opcode))) + { + CloseSocket(); + return; + } + + transferredBytes -= size + 6; + Buffer.BlockCopy(GetReceiveBuffer(), size + 6, GetReceiveBuffer(), 0, transferredBytes); + } + + AsyncRead(); + } + + bool ReadHeader(out uint opcode, out int size) + { + size = BitConverter.ToInt32(GetReceiveBuffer(), 0) - 2; + opcode = BitConverter.ToUInt16(GetReceiveBuffer(), 4); + + if (size >= 0x10000 || (opcode >= (int)ClientOpcodes.Max + 1)) + { + Log.outError(LogFilter.Network, "WorldSocket.ReadHeader(): client {0} sent malformed packet (size: {1}, cmd: {2})", GetRemoteIpAddress().ToString(), size, opcode); + return false; + } + + return true; + } + + bool ProcessPacket(WorldPacket packet) + { + ClientOpcodes opcode = (ClientOpcodes)packet.GetOpcode(); + PacketLog.Write(packet.GetData(), opcode, GetRemoteIpAddress(), GetRemotePort(), _connectType); + + try + { + switch (opcode) + { + case ClientOpcodes.Ping: + Ping ping = new Ping(packet); + ping.Read(); + return HandlePing(ping); + case ClientOpcodes.AuthSession: + if (_worldSession != null) + { + Log.outError(LogFilter.Network, "WorldSocket.ProcessPacket: received duplicate CMSG_AUTH_SESSION from {0}", _worldSession.GetPlayerInfo()); + return false; + } + + AuthSession authSession = new AuthSession(packet); + authSession.Read(); + HandleAuthSession(authSession); + break; + case ClientOpcodes.AuthContinuedSession: + if (_worldSession != null) + { + Log.outError(LogFilter.Network, "WorldSocket.ProcessPacket: received duplicate CMSG_AUTH_CONTINUED_SESSION from {0}", _worldSession.GetPlayerInfo()); + return false; + } + + AuthContinuedSession authContinuedSession = new AuthContinuedSession(packet); + authContinuedSession.Read(); + HandleAuthContinuedSession(authContinuedSession); + break; + case ClientOpcodes.LogDisconnect: + break; + case ClientOpcodes.EnableNagle: + Log.outDebug(LogFilter.Network, "Client {0} requested enabling nagle algorithm", GetRemoteIpAddress().ToString()); + SetNoDelay(false); + break; + case ClientOpcodes.ConnectToFailed: + ConnectToFailed connectToFailed = new ConnectToFailed(packet); + connectToFailed.Read(); + HandleConnectToFailed(connectToFailed); + break; + case ClientOpcodes.EnableEncryptionAck: + HandleEnableEncryptionAck(); + break; + default: + if (_worldSession == null) + { + Log.outError(LogFilter.Network, "ProcessIncoming: Client not authed opcode = {0}", opcode); + return false; + } + + if (!PacketManager.ContainsHandler(opcode)) + { + Log.outError(LogFilter.Network, "No defined handler for opcode {0} sent by {1}", opcode, _worldSession.GetPlayerInfo()); + break; + } + + // Our Idle timer will reset on any non PING opcodes. + // Catches people idling on the login screen and any lingering ingame connections. + _worldSession.ResetTimeOutTime(); + _worldSession.QueuePacket(packet); + break; + } + } + catch (IOException) + { + Log.outError(LogFilter.Network, "WorldSocket.ProcessPacket(): client {0} sent malformed {1}", GetRemoteIpAddress().ToString(), opcode); + return false; + } + + return true; + } + + public void SendPacket(ServerPacket packet, bool writePacket = true) + { + if (!IsOpen()) + return; + + packet.LogPacket(_worldSession); + + if (writePacket) + packet.Write(); + + uint packetSize = packet.GetSize(); + ServerOpcodes opcode = packet.GetOpcode(); + + var data = packet.GetData(); + packet.Dispose(); + PacketLog.Write(data, opcode, GetRemoteIpAddress(), GetRemotePort(), _connectType); + + if (packetSize > 0x400 && worldCrypt.IsInitialized) + { + ByteBuffer buffer = new ByteBuffer(); + buffer.WriteUInt32(packetSize + 2); + buffer.WriteUInt32(ZLib.adler32(ZLib.adler32(0x9827D8F1, BitConverter.GetBytes((ushort)opcode), 2), data, packetSize)); + + uint compressedSize = CompressPacket(data, opcode); + buffer.WriteUInt32(ZLib.adler32(0x9827D8F1, data, compressedSize)); + buffer.WriteBytes(data, compressedSize); + + packetSize = (ushort)(compressedSize + 12); + opcode = ServerOpcodes.CompressedPacket; + + data = buffer.GetData(); + } + + ServerPacketHeader header = new ServerPacketHeader(packetSize, opcode); + if (worldCrypt.IsInitialized) + worldCrypt.Encrypt(header.data, 4); + + AsyncWrite(header.data.Combine(data)); + } + + public void SetWorldSession(WorldSession session) + { + _worldSession = session; + } + + public uint CompressPacket(byte[] data, ServerOpcodes opcode) + { + byte[] uncompressedData = BitConverter.GetBytes((ushort)opcode).Combine(data); + + uint bufferSize = ZLib.deflateBound(_compressionStream, (uint)uncompressedData.Length); + byte[] outPrt = new byte[bufferSize]; + + _compressionStream.next_out = 0; + _compressionStream.avail_out = bufferSize; + _compressionStream.out_buf = outPrt; + + _compressionStream.next_in = 0; + _compressionStream.avail_in = (uint)uncompressedData.Length; + _compressionStream.in_buf = uncompressedData; + + int z_res = ZLib.deflate(_compressionStream, 2); + if (z_res != 0) + { + Log.outError(LogFilter.Network, "Can't compress packet data (zlib: deflate) Error code: {0} msg: {1}", z_res, _compressionStream.msg); + return 0; + } + + uint compressedSize = bufferSize - _compressionStream.avail_out; + Buffer.BlockCopy(outPrt, 0, data, 0, (int)compressedSize); + return compressedSize; + } + + public override void OnClose() + { + _worldSession = null; + } + + public override bool Update() + { + if (!base.Update()) + return false; + + _queryProcessor.ProcessReadyQueries(); + + return true; + } + + void HandleSendAuthSession() + { + _encryptSeed = new byte[16].GenerateRandomKey(16); + _decryptSeed = new byte[16].GenerateRandomKey(16); + + AuthChallenge challenge = new AuthChallenge(); + challenge.Challenge = _serverChallenge; + challenge.DosChallenge = _encryptSeed.Combine(_decryptSeed); + challenge.DosZeroBits = 1; + + SendPacket(challenge); + } + + void HandleAuthSession(AuthSession authSession) + { + // Get the account information from the realmd database + PreparedStatement stmt = DB.Login.GetPreparedStatement(LoginStatements.SEL_ACCOUNT_INFO_BY_NAME); + stmt.AddValue(0, Global.WorldMgr.GetRealm().Id.Realm); + stmt.AddValue(1, authSession.RealmJoinTicket); + + _queryProcessor.AddQuery(DB.Login.AsyncQuery(stmt).WithCallback(HandleAuthSessionCallback, authSession)); + } + + void HandleAuthSessionCallback(AuthSession authSession, SQLResult result) + { + // Stop if the account is not found + if (result.IsEmpty()) + { + Log.outError(LogFilter.Network, "HandleAuthSession: Sent Auth Response (unknown account)."); + CloseSocket(); + return; + } + + AccountInfo account = new AccountInfo(result.GetFields()); + + // For hook purposes, we get Remoteaddress at this point. + string address = GetRemoteIpAddress().ToString(); + + HmacSha256 hmac = new HmacSha256(account.game.SessionKey); + hmac.Process(authSession.LocalChallenge, authSession.LocalChallenge.Count); + hmac.Process(_serverChallenge, 16); + hmac.Finish(AuthCheckSeed, 16); + + // Check that Key and account name are the same on client and server + if (!hmac.Digest.Compare(authSession.Digest)) + { + Log.outError(LogFilter.Network, "WorldSocket.HandleAuthSession: Authentication failed for account: {0} ('{1}') address: {2}", account.game.Id, authSession.RealmJoinTicket, address); + CloseSocket(); + return; + } + + HmacSha256 sessionKeyHmac = new HmacSha256(account.game.SessionKey); + sessionKeyHmac.Process(_serverChallenge, 16); + sessionKeyHmac.Process(authSession.LocalChallenge, authSession.LocalChallenge.Count); + sessionKeyHmac.Finish(SessionKeySeed, 16); + + _sessionKey = new byte[40]; + var sessionKeyGenerator = new SessionKeyGenerator(sessionKeyHmac.Digest, 32); + sessionKeyGenerator.Generate(_sessionKey, 40); + + // As we don't know if attempted login process by ip works, we update last_attempt_ip right away + PreparedStatement stmt = DB.Login.GetPreparedStatement(LoginStatements.UPD_LAST_ATTEMPT_IP); + stmt.AddValue(0, address); + stmt.AddValue(1, authSession.RealmJoinTicket); + + DB.Login.Execute(stmt); + // This also allows to check for possible "hack" attempts on account + + stmt = DB.Login.GetPreparedStatement(LoginStatements.UPD_ACCOUNT_INFO_CONTINUED_SESSION); + stmt.AddValue(0, _sessionKey.ToHexString()); + stmt.AddValue(1, account.game.Id); + DB.Login.Execute(stmt); + + // First reject the connection if packet contains invalid data or realm state doesn't allow logging in + if (Global.WorldMgr.IsClosed()) + { + SendAuthResponseError(BattlenetRpcErrorCode.Denied); + Log.outError(LogFilter.Network, "WorldSocket.HandleAuthSession: World closed, denying client ({0}).", GetRemoteIpAddress()); + CloseSocket(); + return; + } + + if (authSession.RealmID != Global.WorldMgr.GetRealm().Id.Realm) + { + SendAuthResponseError(BattlenetRpcErrorCode.Denied); + Log.outError(LogFilter.Network, "WorldSocket.HandleAuthSession: Client {0} requested connecting with realm id {1} but this realm has id {2} set in config.", + GetRemoteIpAddress().ToString(), authSession.RealmID, Global.WorldMgr.GetRealm().Id.Realm); + CloseSocket(); + return; + } + + // Must be done before WorldSession is created + bool wardenActive = WorldConfig.GetBoolValue(WorldCfg.WardenEnabled); + if (wardenActive && account.game.OS != "Win" && account.game.OS != "Wn64" && account.game.OS != "Mc64") + { + SendAuthResponseError(BattlenetRpcErrorCode.Denied); + Log.outError(LogFilter.Network, "WorldSocket.HandleAuthSession: Client {0} attempted to log in using invalid client OS ({1}).", address, account.game.OS); + CloseSocket(); + return; + } + + //Re-check ip locking (same check as in auth). + if (account.battleNet.IsLockedToIP) // if ip is locked + { + if (account.battleNet.LastIP != address) + { + SendAuthResponseError(BattlenetRpcErrorCode.Denied); + Log.outDebug(LogFilter.Network, "HandleAuthSession: Sent Auth Response (Account IP differs)."); + CloseSocket(); + return; + } + } + else if (!account.battleNet.LockCountry.IsEmpty() && account.battleNet.LockCountry != "00" && !_ipCountry.IsEmpty()) + { + if (account.battleNet.LockCountry != _ipCountry) + { + SendAuthResponseError(BattlenetRpcErrorCode.Denied); + Log.outDebug(LogFilter.Network, "WorldSocket.HandleAuthSession: Sent Auth Response (Account country differs. Original country: {0}, new country: {1}).", account.battleNet.LockCountry, _ipCountry); + CloseSocket(); + return; + } + } + + long mutetime = account.game.MuteTime; + //! Negative mutetime indicates amount of seconds to be muted effective on next login - which is now. + if (mutetime < 0) + { + mutetime = Time.UnixTime + mutetime; + + stmt = DB.Login.GetPreparedStatement(LoginStatements.UPD_MUTE_TIME_LOGIN); + stmt.AddValue(0, mutetime); + stmt.AddValue(1, account.game.Id); + DB.Login.Execute(stmt); + } + + if (account.IsBanned()) // if account banned + { + SendAuthResponseError(BattlenetRpcErrorCode.Denied); + Log.outError(LogFilter.Network, "WorldSocket:HandleAuthSession: Sent Auth Response (Account banned)."); + CloseSocket(); + return; + } + + // Check locked state for server + AccountTypes allowedAccountType = Global.WorldMgr.GetPlayerSecurityLimit(); + Log.outDebug(LogFilter.Network, "Allowed Level: {0} Player Level {1}", allowedAccountType, account.game.Security); + if (allowedAccountType > AccountTypes.Player && account.game.Security < allowedAccountType) + { + SendAuthResponseError(BattlenetRpcErrorCode.Denied); + Log.outInfo(LogFilter.Network, "WorldSocket:HandleAuthSession: User tries to login but his security level is not enough"); + CloseSocket(); + return; + } + + Log.outDebug(LogFilter.Network, "WorldSocket:HandleAuthSession: Client '{0}' authenticated successfully from {1}.", authSession.RealmJoinTicket, address); + + // Update the last_ip in the database + stmt = DB.Login.GetPreparedStatement(LoginStatements.UPD_LAST_IP); + stmt.AddValue(0, address); + stmt.AddValue(1, authSession.RealmJoinTicket); + DB.Login.Execute(stmt); + + _worldSession = new WorldSession(account.game.Id, authSession.RealmJoinTicket, account.battleNet.Id, this, account.game.Security, (Expansion)account.game.Expansion, + mutetime, account.game.OS, account.battleNet.Locale, account.game.Recruiter, account.game.IsRectuiter); + + // Initialize Warden system only if it is enabled by config + //if (wardenActive) + //_worldSession.InitWarden(_sessionKey); + + _queryProcessor.AddQuery(_worldSession.LoadPermissionsAsync().WithCallback(LoadSessionPermissionsCallback)); + } + + void LoadSessionPermissionsCallback(SQLResult result) + { + // RBAC must be loaded before adding session to check for skip queue permission + _worldSession.GetRBACData().LoadFromDBCallback(result); + + SendPacket(new EnableEncryption()); + } + + void HandleAuthContinuedSession(AuthContinuedSession authSession) + { + ConnectToKey key = new ConnectToKey(); + _key = key.Raw = authSession.Key; + + _connectType = key.connectionType; + if (_connectType != ConnectionType.Instance) + { + SendAuthResponseError(BattlenetRpcErrorCode.Denied); + CloseSocket(); + return; + } + + uint accountId = key.AccountId; + PreparedStatement stmt = DB.Login.GetPreparedStatement(LoginStatements.SEL_ACCOUNT_INFO_CONTINUED_SESSION); + stmt.AddValue(0, accountId); + + _queryProcessor.AddQuery(DB.Login.AsyncQuery(stmt).WithCallback(HandleAuthContinuedSessionCallback, authSession)); + } + + void HandleAuthContinuedSessionCallback(AuthContinuedSession authSession, SQLResult result) + { + if (result.IsEmpty()) + { + SendAuthResponseError(BattlenetRpcErrorCode.Denied); + CloseSocket(); + return; + } + + ConnectToKey key = new ConnectToKey(); + _key = key.Raw = authSession.Key; + + uint accountId = key.AccountId; + string login = result.Read(0); + _sessionKey = result.Read(1).ToByteArray(); + + HmacSha256 hmac = new HmacSha256(_sessionKey); + hmac.Process(BitConverter.GetBytes(authSession.Key), 8); + hmac.Process(authSession.LocalChallenge, authSession.LocalChallenge.Length); + hmac.Process(_serverChallenge, 16); + hmac.Finish(ContinuedSessionSeed, 16); + + if (!hmac.Digest.Compare(authSession.Digest)) + { + Log.outError(LogFilter.Network, "WorldSocket.HandleAuthContinuedSession: Authentication failed for account: {0} ('{1}') address: {2}", accountId, login, GetRemoteIpAddress()); + CloseSocket(); + return; + } + + SendPacket(new EnableEncryption()); + } + + void HandleConnectToFailed(ConnectToFailed connectToFailed) + { + if (_worldSession != null) + { + if (_worldSession.PlayerLoading()) + { + switch (connectToFailed.Serial) + { + case ConnectToSerial.WorldAttempt1: + _worldSession.SendConnectToInstance(ConnectToSerial.WorldAttempt2); + break; + case ConnectToSerial.WorldAttempt2: + _worldSession.SendConnectToInstance(ConnectToSerial.WorldAttempt3); + break; + case ConnectToSerial.WorldAttempt3: + _worldSession.SendConnectToInstance(ConnectToSerial.WorldAttempt4); + break; + case ConnectToSerial.WorldAttempt4: + _worldSession.SendConnectToInstance(ConnectToSerial.WorldAttempt5); + break; + case ConnectToSerial.WorldAttempt5: + { + Log.outError(LogFilter.Network, "{0} failed to connect 5 times to world socket, aborting login", _worldSession.GetPlayerInfo()); + _worldSession.AbortLogin(LoginFailureReason.NoWorld); + break; + } + default: + return; + } + } + //else + //{ + // transfer_aborted when/if we get map node redirection + // SendPacket(*WorldPackets.Auth.ResumeComms().Write()); + //} + } + + } + + void HandleEnableEncryptionAck() + { + if (_connectType == ConnectionType.Realm) + { + worldCrypt.Initialize(_sessionKey); + Global.WorldMgr.AddSession(_worldSession); + } + else + { + worldCrypt.Initialize(_sessionKey, _encryptSeed, _decryptSeed); + Global.WorldMgr.AddInstanceSocket(this, _key); + } + } + + public void SendAuthResponseError(BattlenetRpcErrorCode code) + { + AuthResponse response = new AuthResponse(); + response.SuccessInfo.HasValue = false; + response.WaitInfo.HasValue = false; + response.Result = code; + SendPacket(response); + } + + bool HandlePing(Ping ping) + { + if (_LastPingTime == 0) + _LastPingTime = Time.UnixTime; // for 1st ping + else + { + long now = Time.UnixTime; + long diff = now - _LastPingTime; + _LastPingTime = now; + + if (diff < 27) + { + ++_OverSpeedPings; + + uint maxAllowed = WorldConfig.GetUIntValue(WorldCfg.MaxOverspeedPings); + if (maxAllowed != 0 && _OverSpeedPings > maxAllowed) + { + if (_worldSession != null && !_worldSession.HasPermission(RBACPermissions.SkipCheckOverspeedPing)) + { + Log.outError(LogFilter.Network, "WorldSocket:HandlePing: {0} kicked for over-speed pings (address: {1})", _worldSession.GetPlayerInfo(), GetRemoteIpAddress()); + //return ReadDataHandlerResult.Error; + } + } + } + else + _OverSpeedPings = 0; + } + + if (_worldSession != null) + { + _worldSession.SetLatency(ping.Latency); + _worldSession.ResetClientTimeDelay(); + } + else + { + Log.outError(LogFilter.Network, "WorldSocket:HandlePing: peer sent CMSG_PING, but is not authenticated or got recently kicked, address = {0}", GetRemoteIpAddress()); + return false; + } + + SendPacket(new Pong(ping.Serial)); + return true; + } + + ConnectionType _connectType; + ulong _key; + + byte[] _serverChallenge; + WorldCrypt worldCrypt; + byte[] _encryptSeed; + byte[] _decryptSeed; + byte[] _sessionKey; + + long _LastPingTime; + uint _OverSpeedPings; + + WorldSession _worldSession; + + ZLib.z_stream _compressionStream; + + QueryCallbackProcessor _queryProcessor = new QueryCallbackProcessor(); + string _ipCountry; + } + + class AccountInfo + { + public AccountInfo(SQLFields fields) + { + // 0 1 2 3 4 5 6 7 8 9 10 11 + // SELECT a.id, a.sessionkey, ba.last_ip, ba.locked, ba.lock_country, a.expansion, a.mutetime, ba.locale, a.recruiter, a.os, ba.id, aa.gmLevel, + // 12 13 14 + // bab.unbandate > UNIX_TIMESTAMP() OR bab.unbandate = bab.bandate, ab.unbandate > UNIX_TIMESTAMP() OR ab.unbandate = ab.bandate, r.id + // FROM account a LEFT JOIN battlenet_accounts ba ON a.battlenet_account = ba.id LEFT JOIN account_access aa ON a.id = aa.id AND aa.RealmID IN (-1, ?) + // LEFT JOIN battlenet_account_bans bab ON ba.id = bab.id LEFT JOIN account_banned ab ON a.id = ab.id LEFT JOIN account r ON a.id = r.recruiter + // WHERE a.username = ? ORDER BY aa.RealmID DESC LIMIT 1 + game.Id = fields.Read(0); + game.SessionKey = fields.Read(1).ToByteArray(); + battleNet.LastIP = fields.Read(2); + battleNet.IsLockedToIP = fields.Read(3); + battleNet.LockCountry = fields.Read(4); + game.Expansion = fields.Read(5); + game.MuteTime = fields.Read(6); + battleNet.Locale = (LocaleConstant)fields.Read(7); + game.Recruiter = fields.Read(8); + game.OS = fields.Read(9); + battleNet.Id = fields.Read(10); + game.Security = (AccountTypes)fields.Read(11); + battleNet.IsBanned = fields.Read(12) != 0; + game.IsBanned = fields.Read(13) != 0; + game.IsRectuiter = fields.Read(14) != 0; + + uint world_expansion = WorldConfig.GetUIntValue(WorldCfg.Expansion); + if (game.Expansion > world_expansion) + game.Expansion = (byte)world_expansion; + + if (battleNet.Locale >= LocaleConstant.Total) + battleNet.Locale = LocaleConstant.enUS; + } + + public bool IsBanned() { return battleNet.IsBanned || game.IsBanned; } + + public BattleNet battleNet; + public Game game; + + public struct BattleNet + { + public uint Id; + public bool IsLockedToIP; + public string LastIP; + public string LockCountry; + public LocaleConstant Locale; + public bool IsBanned; + } + + public struct Game + { + public uint Id; + public byte[] SessionKey; + public byte Expansion; + public long MuteTime; + public string OS; + public uint Recruiter; + public bool IsRectuiter; + public AccountTypes Security; + public bool IsBanned; + } + } +} diff --git a/Game/Network/WorldSocketManager.cs b/Game/Network/WorldSocketManager.cs new file mode 100644 index 000000000..e577664fa --- /dev/null +++ b/Game/Network/WorldSocketManager.cs @@ -0,0 +1,88 @@ +/* + * Copyright (C) 2012-2017 CypherCore + * + * 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, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +using Framework.Configuration; +using Framework.Constants; +using Framework.Networking; +using System.Net.Sockets; + +namespace Game.Network +{ + public class WorldSocketManager : SocketManager + { + public override bool StartNetwork(string bindIp, int port, int threadCount) + { + _tcpNoDelay = ConfigMgr.GetDefaultValue("Network.TcpNodelay", true); + + Log.outDebug(LogFilter.Misc, "Max allowed socket connections {0}", ushort.MaxValue); + + // -1 means use default + _socketSendBufferSize = ConfigMgr.GetDefaultValue("Network.OutKBuff", -1); + + m_SockOutUBuff = ConfigMgr.GetDefaultValue("Network.OutUBuff", 65536); + + if (m_SockOutUBuff <= 0) + { + Log.outError(LogFilter.Network, "Network.OutUBuff is wrong in your config file"); + return false; + } + + if (!base.StartNetwork(bindIp, port, threadCount)) + return false; + + _instanceAcceptor = new AsyncAcceptor(bindIp, WorldConfig.GetIntValue(WorldCfg.PortInstance)); + _instanceAcceptor.AsyncAcceptSocket(OnSocketOpen); + + return true; + } + + public override void StopNetwork() + { + _instanceAcceptor.Close(); + + base.StopNetwork(); + + _instanceAcceptor = null; + } + + public override void OnSocketOpen(Socket sock) + { + // set some options here + try + { + if (_socketSendBufferSize >= 0) + sock.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.SendBuffer, _socketSendBufferSize); + + // Set TCP_NODELAY. + if (_tcpNoDelay) + sock.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.NoDelay, true); + } + catch (SocketException ex) + { + Log.outException(ex); + return; + } + + base.OnSocketOpen(sock); + } + + AsyncAcceptor _instanceAcceptor; + int _socketSendBufferSize; + int m_SockOutUBuff; + bool _tcpNoDelay; + } +} diff --git a/Game/OutdoorPVP/OutdoorPvP.cs b/Game/OutdoorPVP/OutdoorPvP.cs new file mode 100644 index 000000000..e1904ab3f --- /dev/null +++ b/Game/OutdoorPVP/OutdoorPvP.cs @@ -0,0 +1,802 @@ +/* + * Copyright (C) 2012-2017 CypherCore + * + * 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, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +using Framework.Constants; +using Framework.Database; +using Game.Chat; +using Game.DataStorage; +using Game.Entities; +using Game.Groups; +using Game.Maps; +using Game.Misc; +using Game.Network; +using Game.Network.Packets; +using System; +using System.Collections.Generic; +using System.Diagnostics.Contracts; +using System.Linq; + +namespace Game.PvP +{ + // base class for specific outdoor pvp handlers + public class OutdoorPvP : ZoneScript + { + public OutdoorPvP() + { + m_TypeId = 0; + m_sendUpdate = true; + m_players[0] = new List(); + m_players[1] = new List(); + } + + public virtual void HandlePlayerEnterZone(Player player, uint zone) + { + m_players[player.GetTeamId()].Add(player.GetGUID()); + } + + public virtual void HandlePlayerLeaveZone(Player player, uint zone) + { + // inform the objectives of the leaving + foreach (var pair in m_capturePoints) + pair.Value.HandlePlayerLeave(player); + // remove the world state information from the player (we can't keep everyone up to date, so leave out those who are not in the concerning zones) + if (!player.GetSession().PlayerLogout()) + SendRemoveWorldStates(player); + m_players[player.GetTeamId()].Remove(player.GetGUID()); + Log.outDebug(LogFilter.Outdoorpvp, "Player {0} left an outdoorpvp zone", player.GetName()); + } + + public virtual void HandlePlayerResurrects(Player player, uint zone) { } + + public virtual bool Update(uint diff) + { + bool objective_changed = false; + foreach (var pair in m_capturePoints) + { + if (pair.Value.Update(diff)) + objective_changed = true; + } + return objective_changed; + } + + public void SendUpdateWorldState(uint field, uint value) + { + if (m_sendUpdate) + { + for (int i = 0; i < 2; ++i) + { + foreach (var guid in m_players[i]) + { + Player player = Global.ObjAccessor.FindPlayer(guid); + if (player) + player.SendUpdateWorldState(field, value); + } + } + } + } + + public virtual void HandleKill(Player killer, Unit killed) + { + Group group = killer.GetGroup(); + if (group) + { + for (GroupReference refe = group.GetFirstMember(); refe != null; refe = refe.next()) + { + Player groupGuy = refe.GetSource(); + + if (!groupGuy) + continue; + + // skip if too far away + if (!groupGuy.IsAtGroupRewardDistance(killed)) + continue; + + // creature kills must be notified, even if not inside objective / not outdoor pvp active + // player kills only count if active and inside objective + if ((groupGuy.IsOutdoorPvPActive() && IsInsideObjective(groupGuy)) || killed.IsTypeId(TypeId.Unit)) + HandleKillImpl(groupGuy, killed); + } + } + else + { + // creature kills must be notified, even if not inside objective / not outdoor pvp active + if ((killer.IsOutdoorPvPActive() && IsInsideObjective(killer)) || killed.IsTypeId(TypeId.Unit)) + HandleKillImpl(killer, killed); + } + } + + bool IsInsideObjective(Player player) + { + foreach (var pair in m_capturePoints) + if (pair.Value.IsInsideObjective(player)) + return true; + + return false; + } + + public virtual bool HandleCustomSpell(Player player, uint spellId, GameObject go) + { + foreach (var pair in m_capturePoints) + if (pair.Value.HandleCustomSpell(player, spellId, go)) + return true; + + return false; + } + + public virtual bool HandleOpenGo(Player player, GameObject go) + { + foreach (var pair in m_capturePoints) + if (pair.Value.HandleOpenGo(player, go) >= 0) + return true; + + return false; + } + + public virtual bool HandleGossipOption(Player player, Creature creature, uint id) + { + foreach (var pair in m_capturePoints) + if (pair.Value.HandleGossipOption(player, creature, id)) + return true; + + return false; + } + + public virtual bool CanTalkTo(Player player, Creature c, GossipMenuItems gso) + { + foreach (var pair in m_capturePoints) + if (pair.Value.CanTalkTo(player, c, gso)) + return true; + + return false; + } + + public virtual bool HandleDropFlag(Player player, uint id) + { + foreach (var pair in m_capturePoints) + if (pair.Value.HandleDropFlag(player, id)) + return true; + + return false; + } + + public virtual bool HandleAreaTrigger(Player player, uint trigger, bool entered) + { + return false; + } + + void BroadcastPacket(ServerPacket packet) + { + // This is faster than sWorld.SendZoneMessage + packet.Write(); + for (int team = 0; team < 2; ++team) + { + foreach (var guid in m_players[team]) + { + Player player = Global.ObjAccessor.FindPlayer(guid); + if (player) + player.SendPacket(packet, false); + } + } + } + + public void RegisterZone(uint zoneId) + { + Global.OutdoorPvPMgr.AddZone(zoneId, this); + } + + public bool HasPlayer(Player player) + { + return m_players[player.GetTeamId()].Contains(player.GetGUID()); + } + + public void TeamCastSpell(uint teamIndex, int spellId) + { + foreach (var guid in m_players[teamIndex]) + { + Player player = Global.ObjAccessor.FindPlayer(guid); + if (player) + { + if (spellId > 0) + player.CastSpell(player, (uint)spellId, true); + else + player.RemoveAura((uint)-spellId); // by stack? + } + } + } + + public void TeamApplyBuff(uint teamIndex, uint spellId, uint spellId2) + { + TeamCastSpell(teamIndex, (int)spellId); + TeamCastSpell((uint)(teamIndex == TeamId.Alliance ? TeamId.Horde : TeamId.Alliance), spellId2 != 0 ? -(int)spellId2 : -(int)spellId); + } + + public override void OnGameObjectCreate(GameObject go) + { + if (go.GetGoType() != GameObjectTypes.ControlZone) + return; + + OPvPCapturePoint cp = GetCapturePoint(go.GetSpawnId()); + if (cp != null) + cp.m_capturePoint = go; + } + + public override void OnGameObjectRemove(GameObject go) + { + if (go.GetGoType() != GameObjectTypes.ControlZone) + return; + + OPvPCapturePoint cp = GetCapturePoint(go.GetSpawnId()); + if (cp != null) + cp.m_capturePoint = null; + } + + public void SendDefenseMessage(uint zoneId, uint id) + { + DefenseMessageBuilder builder = new DefenseMessageBuilder(zoneId, id); + var localizer = new LocalizedPacketDo(builder); + BroadcastWorker(localizer, zoneId); + } + + void BroadcastWorker(IDoWork _worker, uint zoneId) + { + for (uint i = 0; i < SharedConst.BGTeamsCount; ++i) + { + foreach (var guid in m_players[i]) + { + Player player = Global.ObjAccessor.FindPlayer(guid); + if (player) + if (player.GetZoneId() == zoneId) + _worker.Invoke(player); + } + } + } + + public virtual void FillInitialWorldStates(InitWorldStates data) { } + + // setup stuff + public virtual bool SetupOutdoorPvP() { return true; } + + public virtual void HandleKillImpl(Player killer, Unit killed) { } + + // awards rewards for player kill + public virtual void AwardKillBonus(Player player) { } + + public OutdoorPvPTypes GetTypeId() { return m_TypeId; } + + public virtual void SendRemoveWorldStates(Player player) { } + + public void AddCapturePoint(OPvPCapturePoint cp) + { + if (m_capturePoints.ContainsKey(cp.m_capturePointSpawnId)) + Log.outError(LogFilter.Outdoorpvp, "OutdoorPvP.AddCapturePoint: CapturePoint {0} already exists!", cp.m_capturePointSpawnId); + + m_capturePoints[cp.m_capturePointSpawnId] = cp; + } + + OPvPCapturePoint GetCapturePoint(ulong lowguid) + { + return m_capturePoints.LookupByKey(lowguid); + } + + public Map GetMap() { return m_map; } + + // Hack to store map because this code is just shit + public void SetMapFromZone(uint zone) + { + AreaTableRecord areaTable = CliDB.AreaTableStorage.LookupByKey(zone); + Contract.Assert(areaTable != null); + Map map = Global.MapMgr.CreateBaseMap(areaTable.MapId); + Contract.Assert(!map.Instanceable()); + m_map = map; + } + + // the map of the objectives belonging to this outdoorpvp + public Dictionary m_capturePoints = new Dictionary(); + List[] m_players = new List[2]; + public OutdoorPvPTypes m_TypeId; + bool m_sendUpdate; + + Map m_map; + } + + public class OPvPCapturePoint + { + public OPvPCapturePoint(OutdoorPvP pvp) + { + m_team = TeamId.Neutral; + m_OldState = ObjectiveStates.Neutral; + m_State = ObjectiveStates.Neutral; + m_PvP = pvp; + + m_activePlayers[0] = new List(); + m_activePlayers[1] = new List(); + } + + public virtual bool HandlePlayerEnter(Player player) + { + if (m_capturePoint) + { + player.SendUpdateWorldState(m_capturePoint.GetGoInfo().ControlZone.worldState1, 1); + player.SendUpdateWorldState(m_capturePoint.GetGoInfo().ControlZone.worldstate2, (uint)Math.Ceiling((m_value + m_maxValue) / (2 * m_maxValue) * 100.0f)); + player.SendUpdateWorldState(m_capturePoint.GetGoInfo().ControlZone.worldstate3, m_neutralValuePct); + } + m_activePlayers[player.GetTeamId()].Add(player.GetGUID()); + return true; + } + + public virtual void HandlePlayerLeave(Player player) + { + if (m_capturePoint) + player.SendUpdateWorldState(m_capturePoint.GetGoInfo().ControlZone.worldState1, 0); + m_activePlayers[player.GetTeamId()].Remove(player.GetGUID()); + } + + public virtual void SendChangePhase() + { + if (!m_capturePoint) + return; + + // send this too, sometimes the slider disappears, dunno why :( + SendUpdateWorldState(m_capturePoint.GetGoInfo().ControlZone.worldState1, 1); + // send these updates to only the ones in this objective + SendUpdateWorldState(m_capturePoint.GetGoInfo().ControlZone.worldstate2, (uint)Math.Ceiling((m_value + m_maxValue) / (2 * m_maxValue) * 100.0f)); + // send this too, sometimes it resets :S + SendUpdateWorldState(m_capturePoint.GetGoInfo().ControlZone.worldstate3, m_neutralValuePct); + } + + void AddGO(uint type, ulong guid) + { + GameObjectData data = Global.ObjectMgr.GetGOData(guid); + if (data == null) + return; + + m_Objects[type] = guid; + m_ObjectTypes[guid] = type; + } + + void AddCre(uint type, ulong guid) + { + CreatureData data = Global.ObjectMgr.GetCreatureData(guid); + if (data == null) + return; + + m_Creatures[type] = guid; + m_CreatureTypes[guid] = type; + } + + public bool AddObject(uint type, uint entry, uint map, float x, float y, float z, float o, float rotation0, float rotation1, float rotation2, float rotation3) + { + ulong guid = Global.ObjectMgr.AddGOData(entry, map, x, y, z, o, 0, rotation0, rotation1, rotation2, rotation3); + if (guid != 0) + { + AddGO(type, guid); + return true; + } + + return false; + } + + public bool AddCreature(uint type, uint entry, uint team, uint map, float x, float y, float z, float o, uint spawntimedelay) + { + ulong guid = Global.ObjectMgr.AddCreatureData(entry, team, map, x, y, z, o, spawntimedelay); + if (guid != 0) + { + AddCre(type, guid); + return true; + } + + return false; + } + + public bool SetCapturePointData(uint entry, uint map, float x, float y, float z, float o, float rotation0, float rotation1, float rotation2, float rotation3) + { + Log.outDebug(LogFilter.Outdoorpvp, "Creating capture point {0}", entry); + + // check info existence + GameObjectTemplate goinfo = Global.ObjectMgr.GetGameObjectTemplate(entry); + if (goinfo == null || goinfo.type != GameObjectTypes.ControlZone) + { + Log.outError(LogFilter.Outdoorpvp, "OutdoorPvP: GO {0} is not capture point!", entry); + return false; + } + + m_capturePointSpawnId = Global.ObjectMgr.AddGOData(entry, map, x, y, z, o, 0, rotation0, rotation1, rotation2, rotation3); + if (m_capturePointSpawnId == 0) + return false; + + // get the needed values from goinfo + m_maxValue = goinfo.ControlZone.maxTime; + m_maxSpeed = m_maxValue / (goinfo.ControlZone.minTime != 0 ? goinfo.ControlZone.minTime : 60); + m_neutralValuePct = goinfo.ControlZone.neutralPercent; + m_minValue = MathFunctions.CalculatePct(m_maxValue, m_neutralValuePct); + + return true; + } + + public bool DelCreature(uint type) + { + if (!m_Creatures.ContainsKey(type)) + { + Log.outDebug(LogFilter.Outdoorpvp, "opvp creature type {0} was already deleted", type); + return false; + } + ulong spawnId = m_Creatures[type]; + + var bounds = m_PvP.GetMap().GetCreatureBySpawnIdStore().LookupByKey(spawnId); + foreach (var creature in bounds) + { + // Don't save respawn time + creature.SetRespawnTime(0); + creature.RemoveCorpse(); + creature.AddObjectToRemoveList(); + } + + Log.outDebug(LogFilter.Outdoorpvp, "deleting opvp creature type {0}", type); + + // delete respawn time for this creature + PreparedStatement stmt = DB.Characters.GetPreparedStatement(CharStatements.DEL_CREATURE_RESPAWN); + stmt.AddValue(0, spawnId); + stmt.AddValue(1, m_PvP.GetMap().GetId()); + stmt.AddValue(2, 0); // instance id, always 0 for world maps + DB.Characters.Execute(stmt); + + Global.ObjectMgr.DeleteCreatureData(spawnId); + m_CreatureTypes.Remove(spawnId); + m_Creatures.Remove(type); + return true; + } + + public bool DelObject(uint type) + { + if (!m_Objects.ContainsKey(type)) + return false; + + ulong spawnId = m_Objects[type]; + var bounds = m_PvP.GetMap().GetGameObjectBySpawnIdStore().LookupByKey(spawnId); + foreach (var gameobject in bounds) + { + // Don't save respawn time + gameobject.SetRespawnTime(0); + gameobject.Delete(); + } + + Global.ObjectMgr.DeleteGOData(spawnId); + m_ObjectTypes.Remove(spawnId); + m_Objects.Remove(type); + return true; + } + + bool DelCapturePoint() + { + Global.ObjectMgr.DeleteGOData(m_capturePointSpawnId); + m_capturePointSpawnId = 0; + + if (m_capturePoint) + { + m_capturePoint.SetRespawnTime(0); // not save respawn time + m_capturePoint.Delete(); + } + + return true; + } + + public virtual void DeleteSpawns() + { + foreach (var type in m_Objects.Keys) + DelObject(type); + + foreach (var type in m_Creatures.Keys) + DelCreature(type); + + DelCapturePoint(); + } + + public virtual bool Update(uint diff) + { + if (!m_capturePoint) + return false; + + float radius = m_capturePoint.GetGoInfo().ControlZone.radius; + + for (int team = 0; team < 2; ++team) + { + foreach (var playerGuid in m_activePlayers[team].ToList()) + { + Player player = Global.ObjAccessor.FindPlayer(playerGuid); + if (player) + if (!m_capturePoint.IsWithinDistInMap(player, radius) || !player.IsOutdoorPvPActive()) + HandlePlayerLeave(player); + } + } + + List players = new List(); + var checker = new AnyPlayerInObjectRangeCheck(m_capturePoint, radius); + var searcher = new PlayerListSearcher(m_capturePoint, players, checker); + Cell.VisitWorldObjects(m_capturePoint, searcher, radius); + + foreach (var player in players) + { + if (player.IsOutdoorPvPActive()) + { + m_activePlayers[player.GetTeamId()].Add(player.GetGUID()); + HandlePlayerEnter(player); + } + } + + // get the difference of numbers + float fact_diff = (m_activePlayers[0].Count - m_activePlayers[1].Count) * diff / 1000; + if (fact_diff == 0.0f) + return false; + + Team Challenger = 0; + float maxDiff = m_maxSpeed * diff; + + if (fact_diff < 0) + { + // horde is in majority, but it's already horde-controlled . no change + if (m_State == ObjectiveStates.Horde && m_value <= -m_maxValue) + return false; + + if (fact_diff < -maxDiff) + fact_diff = -maxDiff; + + Challenger = Team.Horde; + } + else + { + // ally is in majority, but it's already ally-controlled . no change + if (m_State == ObjectiveStates.Alliance && m_value >= m_maxValue) + return false; + + if (fact_diff > maxDiff) + fact_diff = maxDiff; + + Challenger = Team.Alliance; + } + + float oldValue = m_value; + uint oldTeam = m_team; + + m_OldState = m_State; + + m_value += fact_diff; + + if (m_value < -m_minValue) // red + { + if (m_value < -m_maxValue) + m_value = -m_maxValue; + m_State = ObjectiveStates.Horde; + m_team = TeamId.Horde; + } + else if (m_value > m_minValue) // blue + { + if (m_value > m_maxValue) + m_value = m_maxValue; + m_State = ObjectiveStates.Alliance; + m_team = TeamId.Alliance; + } + else if (oldValue * m_value <= 0) // grey, go through mid point + { + // if challenger is ally, then n.a challenge + if (Challenger == Team.Alliance) + m_State = ObjectiveStates.NeutralAllianceChallenge; + // if challenger is horde, then n.h challenge + else if (Challenger == Team.Horde) + m_State = ObjectiveStates.NeutralHordeChallenge; + m_team = TeamId.Neutral; + } + else // grey, did not go through mid point + { + // old phase and current are on the same side, so one team challenges the other + if (Challenger == Team.Alliance && (m_OldState == ObjectiveStates.Horde || m_OldState == ObjectiveStates.NeutralHordeChallenge)) + m_State = ObjectiveStates.HordeAllianceChallenge; + else if (Challenger == Team.Horde && (m_OldState == ObjectiveStates.Alliance || m_OldState == ObjectiveStates.NeutralAllianceChallenge)) + m_State = ObjectiveStates.AllianceHordeChallenge; + m_team = TeamId.Neutral; + } + + if (m_value != oldValue) + SendChangePhase(); + + if (m_OldState != m_State) + { + if (oldTeam != m_team) + ChangeTeam(oldTeam); + ChangeState(); + return true; + } + + return false; + } + + public void SendUpdateWorldState(uint field, uint value) + { + for (int team = 0; team < 2; ++team) + { + // send to all players present in the area + foreach (var guid in m_activePlayers[team]) + { + Player player = Global.ObjAccessor.FindPlayer(guid); + if (player) + player.SendUpdateWorldState(field, value); + } + } + } + + public void SendObjectiveComplete(uint id, ObjectGuid guid) + { + uint team; + switch (m_State) + { + case ObjectiveStates.Alliance: + team = 0; + break; + case ObjectiveStates.Horde: + team = 1; + break; + default: + return; + } + + // send to all players present in the area + foreach (var _guid in m_activePlayers[team]) + { + Player player = Global.ObjAccessor.FindPlayer(_guid); + if (player) + player.KilledMonsterCredit(id, guid); + } + } + + public bool IsInsideObjective(Player player) + { + var plSet = m_activePlayers[player.GetTeamId()]; + return plSet.Contains(player.GetGUID()); + } + + public virtual bool HandleCustomSpell(Player player, uint spellId, GameObject go) + { + if (!player.IsOutdoorPvPActive()) + return false; + return false; + } + + public virtual bool HandleGossipOption(Player player, Creature creature, uint id) + { + return false; + } + + public virtual bool CanTalkTo(Player player, Creature c, GossipMenuItems gso) + { + return false; + } + + public virtual bool HandleDropFlag(Player player, uint id) + { + return false; + } + + public virtual int HandleOpenGo(Player player, GameObject go) + { + var value = m_ObjectTypes.LookupByKey(go.GetSpawnId()); + if (value != 0) + return (int)value; + + return -1; + } + + public virtual void FillInitialWorldStates(InitWorldStates data) { } + + public virtual void ChangeState() { } + + public virtual void ChangeTeam(uint oldTeam) { } + + public ulong m_capturePointSpawnId; + public GameObject m_capturePoint; + // active players in the area of the objective, 0 - alliance, 1 - horde + public List[] m_activePlayers = new List[2]; + // total shift needed to capture the objective + public float m_maxValue; + float m_minValue; + // maximum speed of capture + float m_maxSpeed; + // the status of the objective + public float m_value; + uint m_team; + // objective states + public ObjectiveStates m_OldState { get; set; } + public ObjectiveStates m_State { get; set; } + // neutral value on capture bar + public uint m_neutralValuePct; + // pointer to the OutdoorPvP this objective belongs to + public OutdoorPvP m_PvP { get; set; } + + public Dictionary m_Objects = new Dictionary(); + public Dictionary m_Creatures = new Dictionary(); + Dictionary m_ObjectTypes = new Dictionary(); + Dictionary m_CreatureTypes = new Dictionary(); + } + + class DefenseMessageBuilder : MessageBuilder + { + public DefenseMessageBuilder(uint zoneId, uint id) + { + _zoneId = zoneId; + _id = id; + } + + public override ServerPacket Invoke(LocaleConstant locale = LocaleConstant.enUS) + { + string text = Global.OutdoorPvPMgr.GetDefenseMessage(_zoneId, _id, locale); + + DefenseMessage defenseMessage = new DefenseMessage(); + defenseMessage.ZoneID = _zoneId; + defenseMessage.MessageText = text; + return defenseMessage; + } + + uint _zoneId; // ZoneId + uint _id; // BroadcastTextId + } + + public class go_type + { + public go_type(uint _entry, uint _map, float _x, float _y, float _z, float _o, float _rot0, float _rot1, float _rot2, float _rot3) + { + entry = _entry; + map = _map; + x = _x; + y = _y; + z = _z; + o = _o; + rot0 = _rot0; + rot1 = _rot1; + rot2 = _rot2; + rot3 = _rot3; + } + + public uint entry; + public uint map; + public float x; + public float y; + public float z; + public float o; + public float rot0; + public float rot1; + public float rot2; + public float rot3; + } + + class creature_type + { + public creature_type(uint _entry, uint _map, float _x, float _y, float _z, float _o) + { + entry = _entry; + map = _map; + x = _x; + y = _y; + z = _z; + o = _o; + } + + public uint entry; + public uint map; + public float x; + public float y; + public float z; + public float o; + } +} diff --git a/Game/OutdoorPVP/OutdoorPvPManager.cs b/Game/OutdoorPVP/OutdoorPvPManager.cs new file mode 100644 index 000000000..74d38b7bc --- /dev/null +++ b/Game/OutdoorPVP/OutdoorPvPManager.cs @@ -0,0 +1,251 @@ +/* + * Copyright (C) 2012-2017 CypherCore + * + * 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, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +using Framework.Constants; +using Framework.Database; +using Game.DataStorage; +using Game.Entities; +using Game.Maps; +using Game.Misc; +using System.Collections.Generic; + +namespace Game.PvP +{ + public class OutdoorPvPManager : Singleton + { + OutdoorPvPManager() { } + + public void InitOutdoorPvP() + { + uint oldMSTime = Time.GetMSTime(); + + // 0 1 + SQLResult result = DB.World.Query("SELECT TypeId, ScriptName FROM outdoorpvp_template"); + + if (result.IsEmpty()) + { + Log.outError(LogFilter.ServerLoading, ">> Loaded 0 outdoor PvP definitions. DB table `outdoorpvp_template` is empty."); + return; + } + + uint count = 0; + uint typeId = 0; + + do + { + typeId = result.Read(0); + + if (Global.DisableMgr.IsDisabledFor(DisableType.OutdoorPVP, typeId, null)) + continue; + + if (typeId >= (int)OutdoorPvPTypes.Max) + { + Log.outError(LogFilter.Sql, "Invalid OutdoorPvPTypes value {0} in outdoorpvp_template; skipped.", typeId); + continue; + } + + OutdoorPvPData data = new OutdoorPvPData(); + OutdoorPvPTypes realTypeId = (OutdoorPvPTypes)typeId; + data.TypeId = realTypeId; + data.ScriptId = Global.ObjectMgr.GetScriptId(result.Read(1)); + m_OutdoorPvPDatas[realTypeId] = data; + + ++count; + } + while (result.NextRow()); + + OutdoorPvP pvp; + for (byte i = 1; i < (int)OutdoorPvPTypes.Max; ++i) + { + var outdoor = m_OutdoorPvPDatas.LookupByKey((OutdoorPvPTypes)i); + if (outdoor == null) + { + Log.outError(LogFilter.Sql, "Could not initialize OutdoorPvP object for type ID {0}; no entry in database.", i); + continue; + } + + pvp = Global.ScriptMgr.CreateOutdoorPvP(outdoor); + if (pvp == null) + { + Log.outError(LogFilter.Outdoorpvp, "Could not initialize OutdoorPvP object for type ID {0}; got NULL pointer from script.", i); + continue; + } + + if (!pvp.SetupOutdoorPvP()) + { + Log.outError(LogFilter.Outdoorpvp, "Could not initialize OutdoorPvP object for type ID {0}; SetupOutdoorPvP failed.", i); + continue; + } + + m_OutdoorPvPSet.Add(pvp); + } + + Log.outInfo(LogFilter.ServerLoading, "Loaded {0} outdoor PvP definitions in {1} ms", count, Time.GetMSTimeDiffToNow(oldMSTime)); + } + + public void AddZone(uint zoneid, OutdoorPvP handle) + { + m_OutdoorPvPMap[zoneid] = handle; + } + + public void HandlePlayerEnterZone(Player player, uint zoneid) + { + var outdoor = GetOutdoorPvPToZoneId(zoneid); + if (outdoor == null) + return; + + if (outdoor.HasPlayer(player)) + return; + + outdoor.HandlePlayerEnterZone(player, zoneid); + Log.outDebug(LogFilter.Outdoorpvp, "Player {0} entered outdoorpvp id {1}", player.GetGUID().ToString(), outdoor.GetTypeId()); + } + + public void HandlePlayerLeaveZone(Player player, uint zoneid) + { + var outdoor = GetOutdoorPvPToZoneId(zoneid); + if (outdoor == null) + return; + + // teleport: remove once in removefromworld, once in updatezone + if (!outdoor.HasPlayer(player)) + return; + + outdoor.HandlePlayerLeaveZone(player, zoneid); + Log.outDebug(LogFilter.Outdoorpvp, "Player {0} left outdoorpvp id {1}", player.GetGUID().ToString(), outdoor.GetTypeId()); + } + + public OutdoorPvP GetOutdoorPvPToZoneId(uint zoneid) + { + var outdoor = m_OutdoorPvPMap.LookupByKey(zoneid); + if (outdoor == null) + { + // no handle for this zone, return + return null; + } + return outdoor; + } + + public void Update(uint diff) + { + m_UpdateTimer += diff; + if (m_UpdateTimer > 1000) + { + foreach (var outdoor in m_OutdoorPvPSet) + outdoor.Update(m_UpdateTimer); + m_UpdateTimer = 0; + } + } + + public bool HandleCustomSpell(Player player, uint spellId, GameObject go) + { + foreach (var outdoor in m_OutdoorPvPSet) + { + if (outdoor.HandleCustomSpell(player, spellId, go)) + return true; + } + return false; + } + + public ZoneScript GetZoneScript(uint zoneId) + { + var outdoor = GetOutdoorPvPToZoneId(zoneId); + if (outdoor == null) + return null; + + return outdoor; + } + + public bool HandleOpenGo(Player player, GameObject go) + { + foreach (var outdoor in m_OutdoorPvPSet) + { + if (outdoor.HandleOpenGo(player, go)) + return true; + } + return false; + } + + public void HandleGossipOption(Player player, Creature creature, uint gossipid) + { + foreach (var outdoor in m_OutdoorPvPSet) + { + if (outdoor.HandleGossipOption(player, creature, gossipid)) + return; + } + } + + public bool CanTalkTo(Player player, Creature creature, GossipMenuItems gso) + { + foreach (var outdoor in m_OutdoorPvPSet) + { + if (outdoor.CanTalkTo(player, creature, gso)) + return true; + } + return false; + } + + public void HandleDropFlag(Player player, uint spellId) + { + foreach (var outdoor in m_OutdoorPvPSet) + { + if (outdoor.HandleDropFlag(player, spellId)) + return; + } + } + + public void HandlePlayerResurrects(Player player, uint zoneid) + { + var outdoor = GetOutdoorPvPToZoneId(zoneid); + if (outdoor == null) + return; + + if (outdoor.HasPlayer(player)) + outdoor.HandlePlayerResurrects(player, zoneid); + } + + public string GetDefenseMessage(uint zoneId, uint id, LocaleConstant locale) + { + BroadcastTextRecord bct = CliDB.BroadcastTextStorage.LookupByKey(id); + if (bct != null) + return Global.DB2Mgr.GetBroadcastTextValue(bct, locale); + + Log.outError(LogFilter.Outdoorpvp, "Can not find DefenseMessage (Zone: {0}, Id: {1}). BroadcastText (Id: {2}) does not exist.", zoneId, id, id); + return ""; + } + + // contains all initiated outdoor pvp events + // used when initing / cleaning up + List m_OutdoorPvPSet = new List(); + + // maps the zone ids to an outdoor pvp event + // used in player event handling + Dictionary m_OutdoorPvPMap = new Dictionary(); + + // Holds the outdoor PvP templates + Dictionary m_OutdoorPvPDatas = new Dictionary(); + + // update interval + uint m_UpdateTimer; + } + + public class OutdoorPvPData + { + public OutdoorPvPTypes TypeId; + public uint ScriptId; + } +} diff --git a/Game/OutdoorPVP/Zones/HellfirePeninsulaPvP.cs b/Game/OutdoorPVP/Zones/HellfirePeninsulaPvP.cs new file mode 100644 index 000000000..829b3a716 --- /dev/null +++ b/Game/OutdoorPVP/Zones/HellfirePeninsulaPvP.cs @@ -0,0 +1,400 @@ +/* + * Copyright (C) 2012-2017 CypherCore + * + * 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, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +using Framework.Constants; +using Game.Entities; +using Game.Maps; +using Game.Network.Packets; +using Game.Scripting; + +namespace Game.PvP +{ + struct HPConst + { + public static uint[] LangCapture_A = { DefenseMessages.BrokenHillTakenAlliance, DefenseMessages.OverlookTakenAlliance, DefenseMessages.StadiumTakenAlliance }; + + public static uint[] LangCapture_H = { DefenseMessages.BrokenHillTakenHorde, DefenseMessages.OverlookTakenHorde, DefenseMessages.StadiumTakenHorde }; + + public static uint[] Map_N = { 0x9b5, 0x9b2, 0x9a8 }; + + public static uint[] Map_A = { 0x9b3, 0x9b0, 0x9a7 }; + + public static uint[] Map_H = { 0x9b4, 0x9b1, 0x9a6 }; + + public static uint[] TowerArtKit_A = { 65, 62, 67 }; + + public static uint[] TowerArtKit_H = { 64, 61, 68 }; + + public static uint[] TowerArtKit_N = { 66, 63, 69 }; + + // HP, citadel, ramparts, blood furnace, shattered halls, mag's lair + public static uint[] BuffZones = { 3483, 3563, 3562, 3713, 3714, 3836 }; + + public static uint[] CreditMarker = { 19032, 19028, 19029 }; + + public static uint[] CapturePointEventEnter = { 11404, 11396, 11388 }; + + public static uint[] CapturePointEventLeave = { 11403, 11395, 11387 }; + + public static go_type[] CapturePoints = + { + new go_type(182175, 530, -471.462f, 3451.09f, 34.6432f, 0.174533f, 0.0f, 0.0f, 0.087156f, 0.996195f), // 0 - Broken Hill + new go_type(182174, 530, -184.889f, 3476.93f, 38.205f, -0.017453f, 0.0f, 0.0f, 0.008727f, -0.999962f), // 1 - Overlook + new go_type(182173, 530, -290.016f, 3702.42f, 56.6729f, 0.034907f, 0.0f, 0.0f, 0.017452f, 0.999848f) // 2 - Stadium + }; + + public static go_type[] TowerFlags = + { + new go_type(183514, 530, -467.078f, 3528.17f, 64.7121f, 3.14159f, 0.0f, 0.0f, 1.0f, 0.0f), // 0 broken hill + new go_type(182525, 530, -187.887f, 3459.38f, 60.0403f, -3.12414f, 0.0f, 0.0f, 0.999962f, -0.008727f), // 1 overlook + new go_type(183515, 530, -289.610f, 3696.83f, 75.9447f, 3.12414f, 0.0f, 0.0f, 0.999962f, 0.008727f) // 2 stadium + }; + } + + struct DefenseMessages + { + public const uint OverlookTakenAlliance = 14841; // '|cffffff00The Overlook has been taken by the Alliance!|r' + public const uint OverlookTakenHorde = 14842; // '|cffffff00The Overlook has been taken by the Horde!|r' + public const uint StadiumTakenAlliance = 14843; // '|cffffff00The Stadium has been taken by the Alliance!|r' + public const uint StadiumTakenHorde = 14844; // '|cffffff00The Stadium has been taken by the Horde!|r' + public const uint BrokenHillTakenAlliance = 14845; // '|cffffff00Broken Hill has been taken by the Alliance!|r' + public const uint BrokenHillTakenHorde = 14846; // '|cffffff00Broken Hill has been taken by the Horde!|r' + } + + struct OutdoorPvPHPSpells + { + public const uint AlliancePlayerKillReward = 32155; + public const uint HordePlayerKillReward = 32158; + public const uint AllianceBuff = 32071; + public const uint HordeBuff = 32049; + } + + enum OutdoorPvPHPTowerType + { + BrokenHill = 0, + Overlook = 1, + Stadium = 2, + Num = 3 + } + + struct OutdoorPvPHPWorldStates + { + public const uint Display_A = 0x9ba; + public const uint Display_H = 0x9b9; + + public const uint Count_H = 0x9ae; + public const uint Count_A = 0x9ac; + } + + class HellfirePeninsulaPvP : OutdoorPvP + { + public HellfirePeninsulaPvP() + { + m_TypeId = OutdoorPvPTypes.HellfirePeninsula; + m_AllianceTowersControlled = 0; + m_HordeTowersControlled = 0; + } + + public override bool SetupOutdoorPvP() + { + m_AllianceTowersControlled = 0; + m_HordeTowersControlled = 0; + SetMapFromZone(HPConst.BuffZones[0]); + + // add the zones affected by the pvp buff + for (int i = 0; i < HPConst.BuffZones.Length; ++i) + RegisterZone(HPConst.BuffZones[i]); + + AddCapturePoint(new HellfirePeninsulaCapturePoint(this, OutdoorPvPHPTowerType.BrokenHill)); + + AddCapturePoint(new HellfirePeninsulaCapturePoint(this, OutdoorPvPHPTowerType.Overlook)); + + AddCapturePoint(new HellfirePeninsulaCapturePoint(this, OutdoorPvPHPTowerType.Stadium)); + + return true; + } + + public override void HandlePlayerEnterZone(Player player, uint zone) + { + // add buffs + if (player.GetTeam() == Team.Alliance) + { + if (m_AllianceTowersControlled >= 3) + player.CastSpell(player, OutdoorPvPHPSpells.AllianceBuff, true); + } + else + { + if (m_HordeTowersControlled >= 3) + player.CastSpell(player, OutdoorPvPHPSpells.HordeBuff, true); + } + base.HandlePlayerEnterZone(player, zone); + } + + public override void HandlePlayerLeaveZone(Player player, uint zone) + { + // remove buffs + if (player.GetTeam() == Team.Alliance) + player.RemoveAurasDueToSpell(OutdoorPvPHPSpells.AllianceBuff); + else + player.RemoveAurasDueToSpell(OutdoorPvPHPSpells.HordeBuff); + + base.HandlePlayerLeaveZone(player, zone); + } + + public override bool Update(uint diff) + { + bool changed = base.Update(diff); + if (changed) + { + if (m_AllianceTowersControlled == 3) + TeamApplyBuff(TeamId.Alliance, OutdoorPvPHPSpells.AllianceBuff, OutdoorPvPHPSpells.HordeBuff); + else if (m_HordeTowersControlled == 3) + TeamApplyBuff(TeamId.Horde, OutdoorPvPHPSpells.HordeBuff, OutdoorPvPHPSpells.AllianceBuff); + else + { + TeamCastSpell(TeamId.Alliance, -(int)OutdoorPvPHPSpells.AllianceBuff); + TeamCastSpell(TeamId.Horde, -(int)OutdoorPvPHPSpells.HordeBuff); + } + SendUpdateWorldState(OutdoorPvPHPWorldStates.Count_A, m_AllianceTowersControlled); + SendUpdateWorldState(OutdoorPvPHPWorldStates.Count_H, m_HordeTowersControlled); + } + return changed; + } + + public override void SendRemoveWorldStates(Player player) + { + player.SendUpdateWorldState(OutdoorPvPHPWorldStates.Display_A, 0); + player.SendUpdateWorldState(OutdoorPvPHPWorldStates.Display_H, 0); + player.SendUpdateWorldState(OutdoorPvPHPWorldStates.Count_H, 0); + player.SendUpdateWorldState(OutdoorPvPHPWorldStates.Count_A, 0); + + for (int i = 0; i < (int)OutdoorPvPHPTowerType.Num; ++i) + { + player.SendUpdateWorldState(HPConst.Map_N[i], 0); + player.SendUpdateWorldState(HPConst.Map_A[i], 0); + player.SendUpdateWorldState(HPConst.Map_H[i], 0); + } + } + + public override void FillInitialWorldStates(InitWorldStates packet) + { + packet.AddState(OutdoorPvPHPWorldStates.Display_A, 1); + packet.AddState(OutdoorPvPHPWorldStates.Display_H, 1); + packet.AddState(OutdoorPvPHPWorldStates.Count_A, (int)m_AllianceTowersControlled); + packet.AddState(OutdoorPvPHPWorldStates.Count_H, (int)m_HordeTowersControlled); + + foreach (var capture in m_capturePoints.Values) + capture.FillInitialWorldStates(packet); + } + + public override void HandleKillImpl(Player killer, Unit killed) + { + if (!killed.IsTypeId(TypeId.Player)) + return; + + if (killer.GetTeam() == Team.Alliance && killed.ToPlayer().GetTeam() != Team.Alliance) + killer.CastSpell(killer, OutdoorPvPHPSpells.AlliancePlayerKillReward, true); + else if (killer.GetTeam() == Team.Horde && killed.ToPlayer().GetTeam() != Team.Horde) + killer.CastSpell(killer, OutdoorPvPHPSpells.HordePlayerKillReward, true); + } + + public uint GetAllianceTowersControlled() + { + return m_AllianceTowersControlled; + } + + public void SetAllianceTowersControlled(uint count) + { + m_AllianceTowersControlled = count; + } + + public uint GetHordeTowersControlled() + { + return m_HordeTowersControlled; + } + + public void SetHordeTowersControlled(uint count) + { + m_HordeTowersControlled = count; + } + + // how many towers are controlled + uint m_AllianceTowersControlled; + uint m_HordeTowersControlled; + } + + class HellfirePeninsulaCapturePoint : OPvPCapturePoint + { + public HellfirePeninsulaCapturePoint(OutdoorPvP pvp, OutdoorPvPHPTowerType type) : base(pvp) + { + m_TowerType = (uint)type; + + var capturepoint = HPConst.CapturePoints[m_TowerType]; + var towerflag = HPConst.TowerFlags[m_TowerType]; + + SetCapturePointData(capturepoint.entry, capturepoint.map, capturepoint.x, capturepoint.y, capturepoint.z, capturepoint.o, capturepoint.rot0, + capturepoint.rot1, capturepoint.rot2, capturepoint.rot3); + + AddObject((uint)type, towerflag.entry, towerflag.map, towerflag.x, towerflag.y, towerflag.z, towerflag.o, towerflag.rot0, towerflag.rot1, towerflag.rot2, towerflag.rot3); + } + + public override void ChangeState() + { + uint field = 0; + switch (m_OldState) + { + case ObjectiveStates.Neutral: + field = HPConst.Map_N[m_TowerType]; + break; + case ObjectiveStates.Alliance: + field = HPConst.Map_A[m_TowerType]; + uint alliance_towers = ((HellfirePeninsulaPvP)m_PvP).GetAllianceTowersControlled(); + if (alliance_towers != 0) + ((HellfirePeninsulaPvP)m_PvP).SetAllianceTowersControlled(--alliance_towers); + break; + case ObjectiveStates.Horde: + field = HPConst.Map_H[m_TowerType]; + uint horde_towers = ((HellfirePeninsulaPvP)m_PvP).GetHordeTowersControlled(); + if (horde_towers != 0) + ((HellfirePeninsulaPvP)m_PvP).SetHordeTowersControlled(--horde_towers); + break; + case ObjectiveStates.NeutralAllianceChallenge: + field = HPConst.Map_N[m_TowerType]; + break; + case ObjectiveStates.NeutralHordeChallenge: + field = HPConst.Map_N[m_TowerType]; + break; + case ObjectiveStates.AllianceHordeChallenge: + field = HPConst.Map_A[m_TowerType]; + break; + case ObjectiveStates.HordeAllianceChallenge: + field = HPConst.Map_H[m_TowerType]; + break; + } + + // send world state update + if (field != 0) + { + m_PvP.SendUpdateWorldState(field, 0); + field = 0; + } + uint artkit = 21; + uint artkit2 = HPConst.TowerArtKit_N[m_TowerType]; + switch (m_State) + { + case ObjectiveStates.Neutral: + field = HPConst.Map_N[m_TowerType]; + break; + case ObjectiveStates.Alliance: + { + field = HPConst.Map_A[m_TowerType]; + artkit = 2; + artkit2 = HPConst.TowerArtKit_A[m_TowerType]; + uint alliance_towers = ((HellfirePeninsulaPvP)m_PvP).GetAllianceTowersControlled(); + if (alliance_towers < 3) + ((HellfirePeninsulaPvP)m_PvP).SetAllianceTowersControlled(++alliance_towers); + m_PvP.SendDefenseMessage(HPConst.BuffZones[0], HPConst.LangCapture_A[m_TowerType]); + break; + } + case ObjectiveStates.Horde: + { + field = HPConst.Map_H[m_TowerType]; + artkit = 1; + artkit2 = HPConst.TowerArtKit_H[m_TowerType]; + uint horde_towers = ((HellfirePeninsulaPvP)m_PvP).GetHordeTowersControlled(); + if (horde_towers < 3) + ((HellfirePeninsulaPvP)m_PvP).SetHordeTowersControlled(++horde_towers); + m_PvP.SendDefenseMessage(HPConst.BuffZones[0], HPConst.LangCapture_H[m_TowerType]); + break; + } + case ObjectiveStates.NeutralAllianceChallenge: + field = HPConst.Map_N[m_TowerType]; + break; + case ObjectiveStates.NeutralHordeChallenge: + field = HPConst.Map_N[m_TowerType]; + break; + case ObjectiveStates.AllianceHordeChallenge: + field = HPConst.Map_A[m_TowerType]; + artkit = 2; + artkit2 = HPConst.TowerArtKit_A[m_TowerType]; + break; + case ObjectiveStates.HordeAllianceChallenge: + field = HPConst.Map_H[m_TowerType]; + artkit = 1; + artkit2 = HPConst.TowerArtKit_H[m_TowerType]; + break; + } + + Map map = Global.MapMgr.FindMap(530, 0); + var bounds = map.GetGameObjectBySpawnIdStore().LookupByKey(m_capturePointSpawnId); + foreach (var go in bounds) + go.SetGoArtKit((byte)artkit); + + bounds = map.GetGameObjectBySpawnIdStore().LookupByKey(m_Objects[m_TowerType]); + foreach (var go in bounds) + go.SetGoArtKit((byte)artkit2); + + // send world state update + if (field != 0) + m_PvP.SendUpdateWorldState(field, 1); + + // complete quest objective + if (m_State == ObjectiveStates.Alliance || m_State == ObjectiveStates.Horde) + SendObjectiveComplete(HPConst.CreditMarker[m_TowerType], ObjectGuid.Empty); + } + + public override void FillInitialWorldStates(InitWorldStates packet) + { + switch (m_State) + { + case ObjectiveStates.Alliance: + case ObjectiveStates.AllianceHordeChallenge: + packet.AddState(HPConst.Map_N[m_TowerType], 0); + packet.AddState(HPConst.Map_A[m_TowerType], 1); + packet.AddState(HPConst.Map_H[m_TowerType], 0); + break; + case ObjectiveStates.Horde: + case ObjectiveStates.HordeAllianceChallenge: + packet.AddState(HPConst.Map_N[m_TowerType], 0); + packet.AddState(HPConst.Map_A[m_TowerType], 0); + packet.AddState(HPConst.Map_H[m_TowerType], 1); + break; + case ObjectiveStates.Neutral: + case ObjectiveStates.NeutralAllianceChallenge: + case ObjectiveStates.NeutralHordeChallenge: + default: + packet.AddState(HPConst.Map_N[m_TowerType], 1); + packet.AddState(HPConst.Map_A[m_TowerType], 0); + packet.AddState(HPConst.Map_H[m_TowerType], 0); + break; + } + } + + uint m_TowerType; + } + + [Script] + class HellfirePeninsulaPvPScript : OutdoorPvPScript + { + public HellfirePeninsulaPvPScript() : base("outdoorpvp_hp") { } + + public override OutdoorPvP GetOutdoorPvP() + { + return new HellfirePeninsulaPvP(); + } + } +} diff --git a/Game/OutdoorPVP/Zones/NagrandPvP.cs b/Game/OutdoorPVP/Zones/NagrandPvP.cs new file mode 100644 index 000000000..1ab334c24 --- /dev/null +++ b/Game/OutdoorPVP/Zones/NagrandPvP.cs @@ -0,0 +1,755 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using Game.Network; +using Game.Entities; +using Framework.Constants; +using Game.Scripting; +using Game.Network.Packets; + +namespace Game.PvP.Nagrand +{ + struct Misc + { + // kill credit for pks + public const uint CreditMarker = 24867; + + public const uint MaxGuards = 15; + + public const uint BuffZone = 3518; + + public const uint HalaaGraveyard = 993; + + public const uint HalaaGraveyardZone = 3518; // need to add zone id, not area id + + public const uint RespawnTime = 3600000; // one hour to capture after defeating all guards + + public const uint GuardCheckTime = 500; // every half second + + public const uint FlightNodesNum = 4; + + public static uint[] FlightPathStartNodes = { 103, 105, 107, 109 }; + public static uint[] FlightPathEndNodes = { 104, 106, 108, 110 }; + + // spawned when the alliance is attacking, horde is in control + public static go_type[] HordeControlGOs = + { + new go_type(182267, 530, -1815.8f, 8036.51f, -26.2354f, -2.89725f, 0.0f, 0.0f, 0.992546f, -0.121869f), //ALLY_ROOST_SOUTH + new go_type(182280, 530, -1507.95f, 8132.1f, -19.5585f, -1.3439f, 0.0f, 0.0f, 0.622515f, -0.782608f), //ALLY_ROOST_WEST + new go_type(182281, 530, -1384.52f, 7779.33f, -11.1663f, -0.575959f, 0.0f, 0.0f, 0.284015f, -0.95882f), //ALLY_ROOST_NORTH + new go_type(182282, 530, -1650.11f, 7732.56f, -15.4505f, -2.80998f, 0.0f, 0.0f, 0.986286f, -0.165048f), //ALLY_ROOST_EAST + + new go_type(182222, 530, -1825.4022f, 8039.2602f, -26.08f, -2.89725f, 0.0f, 0.0f, 0.992546f, -0.121869f), //HORDE_BOMB_WAGON_SOUTH + new go_type(182272, 530, -1515.37f, 8136.91f, -20.42f, -1.3439f, 0.0f, 0.0f, 0.622515f, -0.782608f), //HORDE_BOMB_WAGON_WEST + new go_type(182273, 530, -1377.95f, 7773.44f, -10.31f, -0.575959f, 0.0f, 0.0f, 0.284015f, -0.95882f), //HORDE_BOMB_WAGON_NORTH + new go_type(182274, 530, -1659.87f, 7733.15f, -15.75f, -2.80998f, 0.0f, 0.0f, 0.986286f, -0.165048f), //HORDE_BOMB_WAGON_EAST + + new go_type(182266, 530, -1815.8f, 8036.51f, -26.2354f, -2.89725f, 0.0f, 0.0f, 0.992546f, -0.121869f), //DESTROYED_ALLY_ROOST_SOUTH + new go_type(182275, 530, -1507.95f, 8132.1f, -19.5585f, -1.3439f, 0.0f, 0.0f, 0.622515f, -0.782608f), //DESTROYED_ALLY_ROOST_WEST + new go_type(182276, 530, -1384.52f, 7779.33f, -11.1663f, -0.575959f, 0.0f, 0.0f, 0.284015f, -0.95882f), //DESTROYED_ALLY_ROOST_NORTH + new go_type(182277, 530, -1650.11f, 7732.56f, -15.4505f, -2.80998f, 0.0f, 0.0f, 0.986286f, -0.165048f) //DESTROYED_ALLY_ROOST_EAST + }; + + // spawned when the horde is attacking, alliance is in control + public static go_type[] AllianceControlGOs = + { + new go_type(182301, 530, -1815.8f, 8036.51f, -26.2354f, -2.89725f, 0.0f, 0.0f, 0.992546f, -0.121869f), //HORDE_ROOST_SOUTH + new go_type(182302, 530, -1507.95f, 8132.1f, -19.5585f, -1.3439f, 0.0f, 0.0f, 0.622515f, -0.782608f), //HORDE_ROOST_WEST + new go_type(182303, 530, -1384.52f, 7779.33f, -11.1663f, -0.575959f, 0.0f, 0.0f, 0.284015f, -0.95882f), //HORDE_ROOST_NORTH + new go_type(182304, 530, -1650.11f, 7732.56f, -15.4505f, -2.80998f, 0.0f, 0.0f, 0.986286f, -0.165048f), //HORDE_ROOST_EAST + + new go_type(182305, 530, -1825.4022f, 8039.2602f, -26.08f, -2.89725f, 0.0f, 0.0f, 0.992546f, -0.121869f), //ALLY_BOMB_WAGON_SOUTH + new go_type(182306, 530, -1515.37f, 8136.91f, -20.42f, -1.3439f, 0.0f, 0.0f, 0.622515f, -0.782608f), //ALLY_BOMB_WAGON_WEST + new go_type(182307, 530, -1377.95f, 7773.44f, -10.31f, -0.575959f, 0.0f, 0.0f, 0.284015f, -0.95882f), //ALLY_BOMB_WAGON_NORTH + new go_type(182308, 530, -1659.87f, 7733.15f, -15.75f, -2.80998f, 0.0f, 0.0f, 0.986286f, -0.165048f), //ALLY_BOMB_WAGON_EAST + + new go_type(182297, 530, -1815.8f, 8036.51f, -26.2354f, -2.89725f, 0.0f, 0.0f, 0.992546f, -0.121869f), //DESTROYED_HORDE_ROOST_SOUTH + new go_type(182298, 530, -1507.95f, 8132.1f, -19.5585f, -1.3439f, 0.0f, 0.0f, 0.622515f, -0.782608f), //DESTROYED_HORDE_ROOST_WEST + new go_type(182299, 530, -1384.52f, 7779.33f, -11.1663f, -0.575959f, 0.0f, 0.0f, 0.284015f, -0.95882f), //DESTROYED_HORDE_ROOST_NORTH + new go_type(182300, 530, -1650.11f, 7732.56f, -15.4505f, -2.80998f, 0.0f, 0.0f, 0.986286f, -0.165048f) //DESTROYED_HORDE_ROOST_EAST + }; + + public static creature_type[] HordeControlNPCs = + { + new creature_type(18816, 530, -1523.92f, 7951.76f, -17.6942f, 3.51172f), + new creature_type(18821, 530, -1527.75f, 7952.46f, -17.6948f, 3.99317f), + new creature_type(21474, 530, -1520.14f, 7927.11f, -20.2527f, 3.39389f), + new creature_type(21484, 530, -1524.84f, 7930.34f, -20.182f, 3.6405f), + new creature_type(21483, 530, -1570.01f, 7993.8f, -22.4505f, 5.02655f), + new creature_type(18192, 530, -1654.06f, 8000.46f, -26.59f, 3.37f), + new creature_type(18192, 530, -1487.18f, 7899.1f, -19.53f, 0.954f), + new creature_type(18192, 530, -1480.88f, 7908.79f, -19.19f, 4.485f), + new creature_type(18192, 530, -1540.56f, 7995.44f, -20.45f, 0.947f), + new creature_type(18192, 530, -1546.95f, 8000.85f, -20.72f, 6.035f), + new creature_type(18192, 530, -1595.31f, 7860.53f, -21.51f, 3.747f), + new creature_type(18192, 530, -1642.31f, 7995.59f, -25.8f, 3.317f), + new creature_type(18192, 530, -1545.46f, 7995.35f, -20.63f, 1.094f), + new creature_type(18192, 530, -1487.58f, 7907.99f, -19.27f, 5.567f), + new creature_type(18192, 530, -1651.54f, 7988.56f, -26.5289f, 2.98451f), + new creature_type(18192, 530, -1602.46f, 7866.43f, -22.1177f, 4.74729f), + new creature_type(18192, 530, -1591.22f, 7875.29f, -22.3536f, 4.34587f), + new creature_type(18192, 530, -1550.6f, 7944.45f, -21.63f, 3.559f), + new creature_type(18192, 530, -1545.57f, 7935.83f, -21.13f, 3.448f), + new creature_type(18192, 530, -1550.86f, 7937.56f, -21.7f, 3.801f) + }; + + public static creature_type[] AllianceControlNPCs = + { + new creature_type(18817, 530, -1591.18f, 8020.39f, -22.2042f, 4.59022f), + new creature_type(18822, 530, -1588.0f, 8019.0f, -22.2042f, 4.06662f), + new creature_type(21485, 530, -1521.93f, 7927.37f, -20.2299f, 3.24631f), + new creature_type(21487, 530, -1540.33f, 7971.95f, -20.7186f, 3.07178f), + new creature_type(21488, 530, -1570.01f, 7993.8f, -22.4505f, 5.02655f), + new creature_type(18256, 530, -1654.06f, 8000.46f, -26.59f, 3.37f), + new creature_type(18256, 530, -1487.18f, 7899.1f, -19.53f, 0.954f), + new creature_type(18256, 530, -1480.88f, 7908.79f, -19.19f, 4.485f), + new creature_type(18256, 530, -1540.56f, 7995.44f, -20.45f, 0.947f), + new creature_type(18256, 530, -1546.95f, 8000.85f, -20.72f, 6.035f), + new creature_type(18256, 530, -1595.31f, 7860.53f, -21.51f, 3.747f), + new creature_type(18256, 530, -1642.31f, 7995.59f, -25.8f, 3.317f), + new creature_type(18256, 530, -1545.46f, 7995.35f, -20.63f, 1.094f), + new creature_type(18256, 530, -1487.58f, 7907.99f, -19.27f, 5.567f), + new creature_type(18256, 530, -1651.54f, 7988.56f, -26.5289f, 2.98451f), + new creature_type(18256, 530, -1602.46f, 7866.43f, -22.1177f, 4.74729f), + new creature_type(18256, 530, -1591.22f, 7875.29f, -22.3536f, 4.34587f), + new creature_type(18256, 530, -1603.75f, 8000.36f, -24.18f, 4.516f), + new creature_type(18256, 530, -1585.73f, 7994.68f, -23.29f, 4.439f), + new creature_type(18256, 530, -1595.5f, 7991.27f, -23.53f, 4.738f) + }; + } + + class OutdoorPvPNA : OutdoorPvP + { + public OutdoorPvPNA() + { + m_TypeId = OutdoorPvPTypes.Nagrand; + m_obj = null; + } + + public override void HandleKillImpl(Player player, Unit killed) + { + if (killed.GetTypeId() == TypeId.Player && player.GetTeam() != killed.ToPlayer().GetTeam()) + { + player.KilledMonsterCredit(NA_CREDIT_MARKER); // 0 guid, btw it isn't even used in killedmonster function :S + if (player.GetTeam() == Team.Alliance) + player.CastSpell(player, NA_KILL_TOKEN_ALLIANCE, true); + else + player.CastSpell(player, NA_KILL_TOKEN_HORDE, true); + } + } + + public override bool SetupOutdoorPvP() + { + // m_TypeId = OUTDOOR_PVP_NA; _MUST_ be set in ctor, because of spawns cleanup + // add the zones affected by the pvp buff + SetMapFromZone(NA_BUFF_ZONE); + RegisterZone(NA_BUFF_ZONE); + + // halaa + m_obj = new OPvPCapturePointNA(this); + + AddCapturePoint(m_obj); + return true; + } + + public override void HandlePlayerEnterZone(Player player, uint zone) + { + // add buffs + if (player.GetTeam() == m_obj.GetControllingFaction()) + player.CastSpell(player, NA_CAPTURE_BUFF, true); + base.HandlePlayerEnterZone(player, zone); + } + + public override void HandlePlayerLeaveZone(Player player, uint zone) + { + // remove buffs + player.RemoveAurasDueToSpell(NA_CAPTURE_BUFF); + base.HandlePlayerLeaveZone(player, zone); + } + + public override void FillInitialWorldStates(InitWorldStates packet) + { + m_obj.FillInitialWorldStates(packet); + } + + public override void SendRemoveWorldStates(Player player) + { + player.SendUpdateWorldState(NA_UI_HORDE_GUARDS_SHOW, 0); + player.SendUpdateWorldState(NA_UI_ALLIANCE_GUARDS_SHOW, 0); + player.SendUpdateWorldState(NA_UI_GUARDS_MAX, 0); + player.SendUpdateWorldState(NA_UI_GUARDS_LEFT, 0); + player.SendUpdateWorldState(NA_MAP_WYVERN_NORTH_NEU_H, 0); + player.SendUpdateWorldState(NA_MAP_WYVERN_NORTH_NEU_A, 0); + player.SendUpdateWorldState(NA_MAP_WYVERN_NORTH_H, 0); + player.SendUpdateWorldState(NA_MAP_WYVERN_NORTH_A, 0); + player.SendUpdateWorldState(NA_MAP_WYVERN_SOUTH_NEU_H, 0); + player.SendUpdateWorldState(NA_MAP_WYVERN_SOUTH_NEU_A, 0); + player.SendUpdateWorldState(NA_MAP_WYVERN_SOUTH_H, 0); + player.SendUpdateWorldState(NA_MAP_WYVERN_SOUTH_A, 0); + player.SendUpdateWorldState(NA_MAP_WYVERN_WEST_NEU_H, 0); + player.SendUpdateWorldState(NA_MAP_WYVERN_WEST_NEU_A, 0); + player.SendUpdateWorldState(NA_MAP_WYVERN_WEST_H, 0); + player.SendUpdateWorldState(NA_MAP_WYVERN_WEST_A, 0); + player.SendUpdateWorldState(NA_MAP_WYVERN_EAST_NEU_H, 0); + player.SendUpdateWorldState(NA_MAP_WYVERN_EAST_NEU_A, 0); + player.SendUpdateWorldState(NA_MAP_WYVERN_EAST_H, 0); + player.SendUpdateWorldState(NA_MAP_WYVERN_EAST_A, 0); + player.SendUpdateWorldState(NA_MAP_HALAA_NEUTRAL, 0); + player.SendUpdateWorldState(NA_MAP_HALAA_NEU_A, 0); + player.SendUpdateWorldState(NA_MAP_HALAA_NEU_H, 0); + player.SendUpdateWorldState(NA_MAP_HALAA_HORDE, 0); + player.SendUpdateWorldState(NA_MAP_HALAA_ALLIANCE, 0); + } + + public override bool Update(uint diff) + { + return m_obj.Update(diff); + } + + OPvPCapturePointNA m_obj; + } + + class OPvPCapturePointNA : OPvPCapturePoint + { + public OPvPCapturePointNA(OutdoorPvP pvp) : base(pvp) + { + m_capturable = true; + m_HalaaState = HALAA_N; + m_RespawnTimer = Misc.RespawnTime; + m_GuardCheckTimer = Misc.GuardCheckTime; + SetCapturePointData(182210, 530, -1572.57f, 7945.3f, -22.475f, 2.05949f, 0.0f, 0.0f, 0.857167f, 0.515038f); + } + + uint GetAliveGuardsCount() + { + uint cnt = 0; + foreach (var pair in m_Creatures) + { + switch (pair.Key) + { + case NA_NPC_GUARD_01: + case NA_NPC_GUARD_02: + case NA_NPC_GUARD_03: + case NA_NPC_GUARD_04: + case NA_NPC_GUARD_05: + case NA_NPC_GUARD_06: + case NA_NPC_GUARD_07: + case NA_NPC_GUARD_08: + case NA_NPC_GUARD_09: + case NA_NPC_GUARD_10: + case NA_NPC_GUARD_11: + case NA_NPC_GUARD_12: + case NA_NPC_GUARD_13: + case NA_NPC_GUARD_14: + case NA_NPC_GUARD_15: + { + var bounds = m_PvP.GetMap().GetCreatureBySpawnIdStore().LookupByKey(pair.Value); + foreach (var creature in bounds) + if (creature.IsAlive()) + ++cnt; + break; + } + default: + break; + } + } + return cnt; + } + + public Team GetControllingFaction() + { + return m_ControllingFaction; + } + + void SpawnNPCsForTeam(Team team) + { + creature_type[] creatures = null; + if (team == Team.Alliance) + creatures = Misc.AllianceControlNPCs; + else if (team == Team.Horde) + creatures = Misc.HordeControlNPCs; + else + return; + for (int i = 0; i < NA_CONTROL_NPC_NUM; ++i) + AddCreature(i, creatures[i].entry, creatures[i].map, creatures[i].x, creatures[i].y, creatures[i].z, creatures[i].o, OutdoorPvP.GetTeamIdByTeam(team), 1000000); + } + + void DeSpawnNPCs() + { + for (uint i = 0; i < NA_CONTROL_NPC_NUM; ++i) + DelCreature(i); + } + + void SpawnGOsForTeam(Team team) + { + go_type[] gos = null; + if (team == Team.Alliance) + gos = Misc.AllianceControlGOs; + else if (team == Team.Horde) + gos = Misc.HordeControlGOs; + else + return; + for (uint i = 0; i < NA_CONTROL_GO_NUM; ++i) + { + if (i == NA_ROOST_S || + i == NA_ROOST_W || + i == NA_ROOST_N || + i == NA_ROOST_E || + i == NA_BOMB_WAGON_S || + i == NA_BOMB_WAGON_W || + i == NA_BOMB_WAGON_N || + i == NA_BOMB_WAGON_E) + continue; // roosts and bomb wagons are spawned when someone uses the matching destroyed roost + AddObject(i, gos[i].entry, gos[i].map, gos[i].x, gos[i].y, gos[i].z, gos[i].o, gos[i].rot0, gos[i].rot1, gos[i].rot2, gos[i].rot3); + } + } + + void DeSpawnGOs() + { + for (uint i = 0; i < NA_CONTROL_GO_NUM; ++i) + { + DelObject(i); + } + } + + void FactionTakeOver(Team team) + { + if (m_ControllingFaction != 0) + Global.ObjectMgr.RemoveGraveYardLink(NA_HALAA_GRAVEYARD, NA_HALAA_GRAVEYARD_ZONE, m_ControllingFaction, false); + + m_ControllingFaction = team; + if (m_ControllingFaction != 0) + Global.ObjectMgr.AddGraveYardLink(NA_HALAA_GRAVEYARD, NA_HALAA_GRAVEYARD_ZONE, m_ControllingFaction, false); + DeSpawnGOs(); + DeSpawnNPCs(); + SpawnGOsForTeam(team); + SpawnNPCsForTeam(team); + m_GuardsAlive = Misc.MaxGuards; + m_capturable = false; + this.UpdateHalaaWorldState(); + if (team == Team.Alliance) + { + m_WyvernStateSouth = WYVERN_NEU_HORDE; + m_WyvernStateNorth = WYVERN_NEU_HORDE; + m_WyvernStateEast = WYVERN_NEU_HORDE; + m_WyvernStateWest = WYVERN_NEU_HORDE; + m_PvP.TeamApplyBuff(TeamId.Alliance, NA_CAPTURE_BUFF); + m_PvP.SendUpdateWorldState(NA_UI_HORDE_GUARDS_SHOW, 0); + m_PvP.SendUpdateWorldState(NA_UI_ALLIANCE_GUARDS_SHOW, 1); + m_PvP.SendUpdateWorldState(NA_UI_GUARDS_LEFT, m_GuardsAlive); + m_PvP.SendDefenseMessage(NA_HALAA_GRAVEYARD_ZONE, TEXT_HALAA_TAKEN_ALLIANCE); + } + else + { + m_WyvernStateSouth = WYVERN_NEU_ALLIANCE; + m_WyvernStateNorth = WYVERN_NEU_ALLIANCE; + m_WyvernStateEast = WYVERN_NEU_ALLIANCE; + m_WyvernStateWest = WYVERN_NEU_ALLIANCE; + m_PvP.TeamApplyBuff(TeamId.Horde, NA_CAPTURE_BUFF); + m_PvP.SendUpdateWorldState(NA_UI_HORDE_GUARDS_SHOW, 1); + m_PvP.SendUpdateWorldState(NA_UI_ALLIANCE_GUARDS_SHOW, 0); + m_PvP.SendUpdateWorldState(NA_UI_GUARDS_LEFT, m_GuardsAlive); + m_PvP.SendDefenseMessage(NA_HALAA_GRAVEYARD_ZONE, TEXT_HALAA_TAKEN_HORDE); + } + UpdateWyvernRoostWorldState(NA_ROOST_S); + UpdateWyvernRoostWorldState(NA_ROOST_N); + UpdateWyvernRoostWorldState(NA_ROOST_W); + UpdateWyvernRoostWorldState(NA_ROOST_E); + } + + public override void FillInitialWorldStates(InitWorldStates packet) + { + if (m_ControllingFaction == Team.Alliance) + { + packet.AddState(NA_UI_HORDE_GUARDS_SHOW, 0); + packet.AddState(NA_UI_ALLIANCE_GUARDS_SHOW, 1); + } + else if (m_ControllingFaction == Team.Horde) + { + packet.AddState(NA_UI_HORDE_GUARDS_SHOW, 1); + packet.AddState(NA_UI_ALLIANCE_GUARDS_SHOW, 1); + } + else + { + packet.AddState(NA_UI_HORDE_GUARDS_SHOW, 0); + packet.AddState(NA_UI_ALLIANCE_GUARDS_SHOW, 0); + } + + packet.AddState(NA_UI_GUARDS_MAX, Misc.MaxGuards); + packet.AddState(NA_UI_GUARDS_LEFT, m_GuardsAlive); + + packet.AddState(NA_MAP_WYVERN_NORTH_NEU_A, (m_WyvernStateNorth & WYVERN_NEU_HORDE) != 0); + packet.AddState(NA_MAP_WYVERN_NORTH_NEU_A, (m_WyvernStateNorth & WYVERN_NEU_ALLIANCE) != 0); + packet.AddState(NA_MAP_WYVERN_NORTH_H, (m_WyvernStateNorth & WYVERN_HORDE) != 0); + packet.AddState(NA_MAP_WYVERN_NORTH_A, (m_WyvernStateNorth & WYVERN_ALLIANCE) != 0); + + packet.AddState(NA_MAP_WYVERN_SOUTH_NEU_H, (m_WyvernStateSouth & WYVERN_NEU_HORDE) != 0); + packet.AddState(NA_MAP_WYVERN_SOUTH_NEU_A, (m_WyvernStateSouth & WYVERN_NEU_ALLIANCE) != 0); + packet.AddState(NA_MAP_WYVERN_SOUTH_H, (m_WyvernStateSouth & WYVERN_HORDE) != 0); + packet.AddState(NA_MAP_WYVERN_SOUTH_A, (m_WyvernStateSouth & WYVERN_ALLIANCE) != 0); + + packet.AddState(NA_MAP_WYVERN_WEST_NEU_H, (m_WyvernStateWest & WYVERN_NEU_HORDE) != 0); + packet.AddState(NA_MAP_WYVERN_WEST_NEU_A, (m_WyvernStateWest & WYVERN_NEU_ALLIANCE) != 0); + packet.AddState(NA_MAP_WYVERN_WEST_H, (m_WyvernStateWest & WYVERN_HORDE) != 0); + packet.AddState(NA_MAP_WYVERN_WEST_A, (m_WyvernStateWest & WYVERN_ALLIANCE) != 0); + + packet.AddState(NA_MAP_WYVERN_EAST_NEU_H, (m_WyvernStateEast & WYVERN_NEU_HORDE) != 0); + packet.AddState(NA_MAP_WYVERN_EAST_NEU_A, (m_WyvernStateEast & WYVERN_NEU_ALLIANCE) != 0); + packet.AddState(NA_MAP_WYVERN_EAST_H, (m_WyvernStateEast & WYVERN_HORDE) != 0); + packet.AddState(NA_MAP_WYVERN_EAST_A, (m_WyvernStateEast & WYVERN_ALLIANCE) != 0); + + packet.AddState(NA_MAP_HALAA_NEUTRAL, (m_HalaaState & HALAA_N) != 0); + packet.AddState(NA_MAP_HALAA_NEU_A, (m_HalaaState & HALAA_N_A) != 0); + packet.AddState(NA_MAP_HALAA_NEU_H, (m_HalaaState & HALAA_N_H) != 0); + packet.AddState(NA_MAP_HALAA_HORDE, (m_HalaaState & HALAA_H) != 0); + packet.AddState(NA_MAP_HALAA_ALLIANCE, (m_HalaaState & HALAA_A) != 0); + } + + public override bool HandleCustomSpell(Player player, uint spellId, GameObject go) + { + List nodes = new List(); + + bool retval = false; + switch (spellId) + { + case NA_SPELL_FLY_NORTH: + nodes[0] = Misc.FlightPathStartNodes[NA_ROOST_N]; + nodes[1] = Misc.FlightPathEndNodes[NA_ROOST_N]; + player.ActivateTaxiPathTo(nodes); + player.SetFlag(PlayerFields.Flags, PlayerFlags.InPVP); + player.UpdatePvP(true, true); + retval = true; + break; + case NA_SPELL_FLY_SOUTH: + nodes[0] = Misc.FlightPathStartNodes[NA_ROOST_S]; + nodes[1] = Misc.FlightPathEndNodes[NA_ROOST_S]; + player.ActivateTaxiPathTo(nodes); + player.SetFlag(PlayerFields.Flags, PlayerFlags.InPVP); + player.UpdatePvP(true, true); + retval = true; + break; + case NA_SPELL_FLY_WEST: + nodes[0] = Misc.FlightPathStartNodes[NA_ROOST_W]; + nodes[1] = Misc.FlightPathEndNodes[NA_ROOST_W]; + player.ActivateTaxiPathTo(nodes); + player.SetFlag(PlayerFields.Flags, PlayerFlags.InPVP); + player.UpdatePvP(true, true); + retval = true; + break; + case NA_SPELL_FLY_EAST: + nodes[0] = Misc.FlightPathStartNodes[NA_ROOST_E]; + nodes[1] = Misc.FlightPathEndNodes[NA_ROOST_E]; + player.ActivateTaxiPathTo(nodes); + player.SetFlag(PlayerFields.Flags, PlayerFlags.InPVP); + player.UpdatePvP(true, true); + retval = true; + break; + default: + break; + } + + if (retval) + { + //Adding items + uint noSpaceForCount = 0; + + // check space and find places + List dest = new List(); + + uint count = 10; + uint itemid = 24538; + // bomb id count + InventoryResult msg = player.CanStoreNewItem(ItemConst.NullBag, ItemConst.NullSlot, dest, itemid, count, out noSpaceForCount); + if (msg != InventoryResult.Ok) // convert to possible store amount + count -= noSpaceForCount; + + if (count == 0 || dest.Empty()) // can't add any + { + return true; + } + + Item item = player.StoreNewItem(dest, itemid, true); + if (count > 0 && item) + { + player.SendNewItem(item, count, true, false); + } + + return true; + } + return false; + } + + public override int HandleOpenGo(Player player, GameObject go) + { + int retval = base.HandleOpenGo(player, go); + if (retval >= 0) + { + go_type[] gos = null; + if (m_ControllingFaction == Team.Alliance) + gos = Misc.AllianceControlGOs; + else if (m_ControllingFaction == Team.Horde) + gos = Misc.HordeControlGOs; + else + return -1; + + int del = -1; + int del2 = -1; + int add = -1; + int add2 = -1; + + switch (retval) + { + case NA_DESTROYED_ROOST_S: + del = NA_DESTROYED_ROOST_S; + add = NA_ROOST_S; + add2 = NA_BOMB_WAGON_S; + if (m_ControllingFaction == Team.Horde) + m_WyvernStateSouth = WYVERN_ALLIANCE; + else + m_WyvernStateSouth = WYVERN_HORDE; + UpdateWyvernRoostWorldState(NA_ROOST_S); + break; + case NA_DESTROYED_ROOST_N: + del = NA_DESTROYED_ROOST_N; + add = NA_ROOST_N; + add2 = NA_BOMB_WAGON_N; + if (m_ControllingFaction == Team.Horde) + m_WyvernStateNorth = WYVERN_ALLIANCE; + else + m_WyvernStateNorth = WYVERN_HORDE; + UpdateWyvernRoostWorldState(NA_ROOST_N); + break; + case NA_DESTROYED_ROOST_W: + del = NA_DESTROYED_ROOST_W; + add = NA_ROOST_W; + add2 = NA_BOMB_WAGON_W; + if (m_ControllingFaction == Team.Horde) + m_WyvernStateWest = WYVERN_ALLIANCE; + else + m_WyvernStateWest = WYVERN_HORDE; + UpdateWyvernRoostWorldState(NA_ROOST_W); + break; + case NA_DESTROYED_ROOST_E: + del = NA_DESTROYED_ROOST_E; + add = NA_ROOST_E; + add2 = NA_BOMB_WAGON_E; + if (m_ControllingFaction == Team.Horde) + m_WyvernStateEast = WYVERN_ALLIANCE; + else + m_WyvernStateEast = WYVERN_HORDE; + UpdateWyvernRoostWorldState(NA_ROOST_E); + break; + case NA_BOMB_WAGON_S: + del = NA_BOMB_WAGON_S; + del2 = NA_ROOST_S; + add = NA_DESTROYED_ROOST_S; + if (m_ControllingFaction == Team.Horde) + m_WyvernStateSouth = WYVERN_NEU_ALLIANCE; + else + m_WyvernStateSouth = WYVERN_NEU_HORDE; + UpdateWyvernRoostWorldState(NA_ROOST_S); + break; + case NA_BOMB_WAGON_N: + del = NA_BOMB_WAGON_N; + del2 = NA_ROOST_N; + add = NA_DESTROYED_ROOST_N; + if (m_ControllingFaction == Team.Horde) + m_WyvernStateNorth = WYVERN_NEU_ALLIANCE; + else + m_WyvernStateNorth = WYVERN_NEU_HORDE; + UpdateWyvernRoostWorldState(NA_ROOST_N); + break; + case NA_BOMB_WAGON_W: + del = NA_BOMB_WAGON_W; + del2 = NA_ROOST_W; + add = NA_DESTROYED_ROOST_W; + if (m_ControllingFaction == Team.Horde) + m_WyvernStateWest = WYVERN_NEU_ALLIANCE; + else + m_WyvernStateWest = WYVERN_NEU_HORDE; + UpdateWyvernRoostWorldState(NA_ROOST_W); + break; + case NA_BOMB_WAGON_E: + del = NA_BOMB_WAGON_E; + del2 = NA_ROOST_E; + add = NA_DESTROYED_ROOST_E; + if (m_ControllingFaction == Team.Horde) + m_WyvernStateEast = WYVERN_NEU_ALLIANCE; + else + m_WyvernStateEast = WYVERN_NEU_HORDE; + UpdateWyvernRoostWorldState(NA_ROOST_E); + break; + default: + return -1; + break; + } + + if (del > -1) + DelObject((uint)del); + + if (del2 > -1) + DelObject((uint)del2); + + if (add > -1) + AddObject((uint)add, gos[add].entry, gos[add].map, gos[add].x, gos[add].y, gos[add].z, gos[add].o, gos[add].rot0, gos[add].rot1, gos[add].rot2, gos[add].rot3); + + if (add2 > -1) + AddObject((uint)add2, gos[add2].entry, gos[add2].map, gos[add2].x, gos[add2].y, gos[add2].z, gos[add2].o, gos[add2].rot0, gos[add2].rot1, gos[add2].rot2, gos[add2].rot3); + + return retval; + } + return -1; + } + + public override bool Update(uint diff) + { + // let the controlling faction advance in phase + bool capturable = false; + if (m_ControllingFaction == Team.Alliance && m_activePlayers[0].Count > m_activePlayers[1].Count) + capturable = true; + else if (m_ControllingFaction == Team.Horde && m_activePlayers[0].Count < m_activePlayers[1].Count) + capturable = true; + + if (m_GuardCheckTimer < diff) + { + m_GuardCheckTimer = Misc.GuardCheckTime; + uint cnt = GetAliveGuardsCount(); + if (cnt != m_GuardsAlive) + { + m_GuardsAlive = cnt; + if (m_GuardsAlive == 0) + m_capturable = true; + // update the guard count for the players in zone + m_PvP.SendUpdateWorldState(NA_UI_GUARDS_LEFT, m_GuardsAlive); + } + } + else m_GuardCheckTimer -= diff; + + if (m_capturable || capturable) + { + if (m_RespawnTimer < diff) + { + // if the guards have been killed, then the challenger has one hour to take over halaa. + // in case they fail to do it, the guards are respawned, and they have to start again. + if (m_ControllingFaction != 0) + FactionTakeOver(m_ControllingFaction); + m_RespawnTimer = NA_RESPAWN_TIME; + } + else m_RespawnTimer -= diff; + + return base.Update(diff); + } + return false; + } + + public override void ChangeState() + { + uint artkit = 21; + switch (m_State) + { + case OBJECTIVESTATE_NEUTRAL: + m_HalaaState = HALAA_N; + break; + case OBJECTIVESTATE_ALLIANCE: + m_HalaaState = HALAA_A; + FactionTakeOver(Team.Alliance); + artkit = 2; + break; + case OBJECTIVESTATE_HORDE: + m_HalaaState = HALAA_H; + FactionTakeOver(Team.Horde); + artkit = 1; + break; + case OBJECTIVESTATE_NEUTRAL_ALLIANCE_CHALLENGE: + m_HalaaState = HALAA_N_A; + break; + case OBJECTIVESTATE_NEUTRAL_HORDE_CHALLENGE: + m_HalaaState = HALAA_N_H; + break; + case OBJECTIVESTATE_ALLIANCE_HORDE_CHALLENGE: + m_HalaaState = HALAA_N_A; + artkit = 2; + break; + case OBJECTIVESTATE_HORDE_ALLIANCE_CHALLENGE: + m_HalaaState = HALAA_N_H; + artkit = 1; + break; + } + + var bounds = Global.MapMgr.FindMap(530, 0).GetGameObjectBySpawnIdStore().LookupByKey(m_capturePointSpawnId); + foreach (var itr in bounds) + itr.SetGoArtKit((byte)artkit); + + UpdateHalaaWorldState(); + } + + void UpdateHalaaWorldState() + { + m_PvP.SendUpdateWorldState(NA_MAP_HALAA_NEUTRAL, (m_HalaaState & HALAA_N) != 0); + m_PvP.SendUpdateWorldState(NA_MAP_HALAA_NEU_A, (m_HalaaState & HALAA_N_A) != 0); + m_PvP.SendUpdateWorldState(NA_MAP_HALAA_NEU_H, (m_HalaaState & HALAA_N_H) != 0); + m_PvP.SendUpdateWorldState(NA_MAP_HALAA_HORDE, (m_HalaaState & HALAA_H) != 0); + m_PvP.SendUpdateWorldState(NA_MAP_HALAA_ALLIANCE, (m_HalaaState & HALAA_A) != 0); + } + + void UpdateWyvernRoostWorldState(uint roost) + { + switch (roost) + { + case NA_ROOST_S: + m_PvP.SendUpdateWorldState(NA_MAP_WYVERN_SOUTH_NEU_H, (m_WyvernStateSouth & WYVERN_NEU_HORDE) != 0); + m_PvP.SendUpdateWorldState(NA_MAP_WYVERN_SOUTH_NEU_A, (m_WyvernStateSouth & WYVERN_NEU_ALLIANCE) != 0); + m_PvP.SendUpdateWorldState(NA_MAP_WYVERN_SOUTH_H, (m_WyvernStateSouth & WYVERN_HORDE) != 0); + m_PvP.SendUpdateWorldState(NA_MAP_WYVERN_SOUTH_A, (m_WyvernStateSouth & WYVERN_ALLIANCE) != 0); + break; + case NA_ROOST_N: + m_PvP.SendUpdateWorldState(NA_MAP_WYVERN_NORTH_NEU_H, (m_WyvernStateNorth & WYVERN_NEU_HORDE) != 0); + m_PvP.SendUpdateWorldState(NA_MAP_WYVERN_NORTH_NEU_A, (m_WyvernStateNorth & WYVERN_NEU_ALLIANCE) != 0); + m_PvP.SendUpdateWorldState(NA_MAP_WYVERN_NORTH_H, (m_WyvernStateNorth & WYVERN_HORDE) != 0); + m_PvP.SendUpdateWorldState(NA_MAP_WYVERN_NORTH_A, (m_WyvernStateNorth & WYVERN_ALLIANCE) != 0); + break; + case NA_ROOST_W: + m_PvP.SendUpdateWorldState(NA_MAP_WYVERN_WEST_NEU_H, (m_WyvernStateWest & WYVERN_NEU_HORDE) != 0); + m_PvP.SendUpdateWorldState(NA_MAP_WYVERN_WEST_NEU_A, (m_WyvernStateWest & WYVERN_NEU_ALLIANCE) != 0); + m_PvP.SendUpdateWorldState(NA_MAP_WYVERN_WEST_H, (m_WyvernStateWest & WYVERN_HORDE) != 0); + m_PvP.SendUpdateWorldState(NA_MAP_WYVERN_WEST_A, (m_WyvernStateWest & WYVERN_ALLIANCE) != 0); + break; + case NA_ROOST_E: + m_PvP.SendUpdateWorldState(NA_MAP_WYVERN_EAST_NEU_H, (m_WyvernStateEast & WYVERN_NEU_HORDE) != 0); + m_PvP.SendUpdateWorldState(NA_MAP_WYVERN_EAST_NEU_A, (m_WyvernStateEast & WYVERN_NEU_ALLIANCE) != 0); + m_PvP.SendUpdateWorldState(NA_MAP_WYVERN_EAST_H, (m_WyvernStateEast & WYVERN_HORDE) != 0); + m_PvP.SendUpdateWorldState(NA_MAP_WYVERN_EAST_A, (m_WyvernStateEast & WYVERN_ALLIANCE) != 0); + break; + } + } + + bool m_capturable; + + uint m_GuardsAlive; + + Team m_ControllingFaction; + + uint m_WyvernStateNorth; + uint m_WyvernStateSouth; + uint m_WyvernStateEast; + uint m_WyvernStateWest; + + uint m_HalaaState; + + uint m_RespawnTimer; + + uint m_GuardCheckTimer; + } + + class OutdoorPvP_nagrand : OutdoorPvPScript + { + public OutdoorPvP_nagrand() : base("outdoorpvp_na") { } + + public override OutdoorPvP GetOutdoorPvP() + { + return new OutdoorPvPNA(); + } + } +} diff --git a/Game/Pools/PoolManager.cs b/Game/Pools/PoolManager.cs new file mode 100644 index 000000000..933d2839a --- /dev/null +++ b/Game/Pools/PoolManager.cs @@ -0,0 +1,1041 @@ +/* + * Copyright (C) 2012-2017 CypherCore + * + * 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, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +using Framework.Constants; +using Framework.Database; +using Game.Entities; +using Game.Maps; +using System; +using System.Collections.Generic; +using System.Linq; + +namespace Game +{ + public class PoolManager : Singleton + { + PoolManager() { } + + public void Initialize() + { + mQuestSearchMap.Clear(); + mGameobjectSearchMap.Clear(); + mCreatureSearchMap.Clear(); + } + + public void LoadFromDB() + { + // Pool templates + { + uint oldMSTime = Time.GetMSTime(); + + SQLResult result = DB.World.Query("SELECT entry, max_limit FROM pool_template"); + if (result.IsEmpty()) + { + mPoolTemplate.Clear(); + Log.outInfo(LogFilter.ServerLoading, "Loaded 0 object pools. DB table `pool_template` is empty."); + return; + } + + uint count = 0; + do + { + uint pool_id = result.Read(0); + + PoolTemplateData pPoolTemplate = new PoolTemplateData(); + pPoolTemplate.MaxLimit = result.Read(1); + mPoolTemplate[pool_id] = pPoolTemplate; + ++count; + } + while (result.NextRow()); + + Log.outInfo(LogFilter.ServerLoading, "Loaded {0} objects pools in {1} ms", count, Time.GetMSTimeDiffToNow(oldMSTime)); + } + + // Creatures + + Log.outInfo(LogFilter.ServerLoading, "Loading Creatures Pooling Data..."); + { + uint oldMSTime = Time.GetMSTime(); + + // 1 2 3 + SQLResult result = DB.World.Query("SELECT guid, pool_entry, chance FROM pool_creature"); + + if (result.IsEmpty()) + { + Log.outInfo(LogFilter.ServerLoading, "Loaded 0 creatures in pools. DB table `pool_creature` is empty."); + } + else + { + uint count = 0; + do + { + ulong guid = result.Read(0); + uint pool_id = result.Read(1); + float chance = result.Read(2); + + CreatureData data = Global.ObjectMgr.GetCreatureData(guid); + if (data == null) + { + Log.outError(LogFilter.Sql, "`pool_creature` has a non existing creature spawn (GUID: {0}) defined for pool id ({1}), skipped.", guid, pool_id); + continue; + } + if (!mPoolTemplate.ContainsKey(pool_id)) + { + Log.outError(LogFilter.Sql, "`pool_creature` pool id ({0}) is out of range compared to max pool id in `pool_template`, skipped.", pool_id); + continue; + } + if (chance < 0 || chance > 100) + { + Log.outError(LogFilter.Sql, "`pool_creature` has an invalid chance ({0}) for creature guid ({1}) in pool id ({2}), skipped.", chance, guid, pool_id); + continue; + } + PoolTemplateData pPoolTemplate = mPoolTemplate[pool_id]; + PoolObject plObject = new PoolObject(guid, chance); + + if (!mPoolCreatureGroups.ContainsKey(pool_id)) + mPoolCreatureGroups[pool_id] = new PoolGroup(); + + PoolGroup cregroup = mPoolCreatureGroups[pool_id]; + cregroup.SetPoolId(pool_id); + cregroup.AddEntry(plObject, pPoolTemplate.MaxLimit); + + mCreatureSearchMap.Add(guid, pool_id); + ++count; + } + while (result.NextRow()); + + Log.outInfo(LogFilter.ServerLoading, "Loaded {0} creatures in pools in {1} ms", count, Time.GetMSTimeDiffToNow(oldMSTime)); + } + } + + // Gameobjects + + Log.outInfo(LogFilter.ServerLoading, "Loading Gameobject Pooling Data..."); + { + uint oldMSTime = Time.GetMSTime(); + + // 1 2 3 + SQLResult result = DB.World.Query("SELECT guid, pool_entry, chance FROM pool_gameobject"); + + if (result.IsEmpty()) + { + Log.outInfo(LogFilter.ServerLoading, "Loaded 0 gameobjects in pools. DB table `pool_gameobject` is empty."); + } + else + { + uint count = 0; + do + { + ulong guid = result.Read(0); + uint pool_id = result.Read(1); + float chance = result.Read(2); + + GameObjectData data = Global.ObjectMgr.GetGOData(guid); + if (data == null) + { + Log.outError(LogFilter.Sql, "`pool_gameobject` has a non existing gameobject spawn (GUID: {0}) defined for pool id ({1}), skipped.", guid, pool_id); + continue; + } + + GameObjectTemplate goinfo = Global.ObjectMgr.GetGameObjectTemplate(data.id); + if (goinfo.type != GameObjectTypes.Chest && + goinfo.type != GameObjectTypes.FishingHole && + goinfo.type != GameObjectTypes.GatheringNode && + goinfo.type != GameObjectTypes.Goober) + { + Log.outError(LogFilter.Sql, "`pool_gameobject` has a not lootable gameobject spawn (GUID: {0}, type: {1}) defined for pool id ({2}), skipped.", guid, goinfo.type, pool_id); + continue; + } + + if (!mPoolTemplate.ContainsKey(pool_id)) + { + Log.outError(LogFilter.Sql, "`pool_gameobject` pool id ({0}) is out of range compared to max pool id in `pool_template`, skipped.", pool_id); + continue; + } + + if (chance < 0 || chance > 100) + { + Log.outError(LogFilter.Sql, "`pool_gameobject` has an invalid chance ({0}) for gameobject guid ({1}) in pool id ({2}), skipped.", chance, guid, pool_id); + continue; + } + + PoolTemplateData pPoolTemplate = mPoolTemplate[pool_id]; + PoolObject plObject = new PoolObject(guid, chance); + + if (!mPoolGameobjectGroups.ContainsKey(pool_id)) + mPoolGameobjectGroups[pool_id] = new PoolGroup(); + + PoolGroup gogroup = mPoolGameobjectGroups[pool_id]; + gogroup.SetPoolId(pool_id); + gogroup.AddEntry(plObject, pPoolTemplate.MaxLimit); + + mGameobjectSearchMap.Add(guid, pool_id); + ++count; + } + while (result.NextRow()); + + Log.outInfo(LogFilter.ServerLoading, "Loaded {0} gameobject in pools in {1} ms", count, Time.GetMSTimeDiffToNow(oldMSTime)); + } + } + + // Pool of pools + + Log.outInfo(LogFilter.ServerLoading, "Loading Mother Pooling Data..."); + { + uint oldMSTime = Time.GetMSTime(); + + // 1 2 3 + SQLResult result = DB.World.Query("SELECT pool_id, mother_pool, chance FROM pool_pool"); + + if (result.IsEmpty()) + { + Log.outInfo(LogFilter.ServerLoading, "Loaded 0 pools in pools"); + } + else + { + uint count = 0; + do + { + uint child_pool_id = result.Read(0); + uint mother_pool_id = result.Read(1); + float chance = result.Read(2); + + if (!mPoolTemplate.ContainsKey(mother_pool_id)) + { + Log.outError(LogFilter.Sql, "`pool_pool` mother_pool id ({0}) is out of range compared to max pool id in `pool_template`, skipped.", mother_pool_id); + continue; + } + if (!mPoolTemplate.ContainsKey(child_pool_id)) + { + Log.outError(LogFilter.Sql, "`pool_pool` included pool_id ({0}) is out of range compared to max pool id in `pool_template`, skipped.", child_pool_id); + continue; + } + if (mother_pool_id == child_pool_id) + { + Log.outError(LogFilter.Sql, "`pool_pool` pool_id ({0}) includes itself, dead-lock detected, skipped.", child_pool_id); + continue; + } + if (chance < 0 || chance > 100) + { + Log.outError(LogFilter.Sql, "`pool_pool` has an invalid chance ({0}) for pool id ({1}) in mother pool id ({2}), skipped.", chance, child_pool_id, mother_pool_id); + continue; + } + PoolTemplateData pPoolTemplateMother = mPoolTemplate[mother_pool_id]; + PoolObject plObject = new PoolObject(child_pool_id, chance); + + if (!mPoolPoolGroups.ContainsKey(mother_pool_id)) + mPoolPoolGroups[mother_pool_id] = new PoolGroup(); + + PoolGroup plgroup = mPoolPoolGroups[mother_pool_id]; + plgroup.SetPoolId(mother_pool_id); + plgroup.AddEntry(plObject, pPoolTemplateMother.MaxLimit); + + mPoolSearchMap.Add(child_pool_id, mother_pool_id); + ++count; + } + while (result.NextRow()); + + Log.outInfo(LogFilter.ServerLoading, "Loaded {0} pools in mother pools in {1} ms", count, Time.GetMSTimeDiffToNow(oldMSTime)); + } + } + + Log.outInfo(LogFilter.ServerLoading, "Loading Quest Pooling Data..."); + { + uint oldMSTime = Time.GetMSTime(); + + PreparedStatement stmt = DB.World.GetPreparedStatement(WorldStatements.SEL_QUEST_POOLS); + SQLResult result = DB.World.Query(stmt); + + if (result.IsEmpty()) + { + Log.outInfo(LogFilter.ServerLoading, "Loaded 0 quests in pools"); + } + else + { + List creBounds; + List goBounds; + + Dictionary poolTypeMap = new Dictionary(); + uint count = 0; + do + { + uint entry = result.Read(0); + uint pool_id = result.Read(1); + + if (!poolTypeMap.ContainsKey(pool_id)) + poolTypeMap[pool_id] = 0; + + Quest quest = Global.ObjectMgr.GetQuestTemplate(entry); + if (quest == null) + { + Log.outError(LogFilter.Sql, "`pool_quest` has a non existing quest template (Entry: {0}) defined for pool id ({1}), skipped.", entry, pool_id); + continue; + } + + if (!mPoolTemplate.ContainsKey(pool_id)) + { + Log.outError(LogFilter.Sql, "`pool_quest` pool id ({0}) is out of range compared to max pool id in `pool_template`, skipped.", pool_id); + continue; + } + + if (!quest.IsDailyOrWeekly()) + { + Log.outError(LogFilter.Sql, "`pool_quest` has an quest ({0}) which is not daily or weekly in pool id ({1}), use ExclusiveGroup instead, skipped.", entry, pool_id); + continue; + } + + if (poolTypeMap[pool_id] == eQuestTypes.None) + poolTypeMap[pool_id] = quest.IsDaily() ? eQuestTypes.Daily : eQuestTypes.Weekly; + + eQuestTypes currType = quest.IsDaily() ? eQuestTypes.Daily : eQuestTypes.Weekly; + + if (poolTypeMap[pool_id] != currType) + { + Log.outError(LogFilter.Sql, "`pool_quest` quest {0} is {1} but pool ({2}) is specified for {3}, mixing not allowed, skipped.", + entry, currType, pool_id, poolTypeMap[pool_id]); + continue; + } + + creBounds = mQuestCreatureRelation.LookupByKey(entry); + goBounds = mQuestGORelation.LookupByKey(entry); + + if (creBounds.Empty() && goBounds.Empty()) + { + Log.outError(LogFilter.Sql, "`pool_quest` lists entry ({0}) as member of pool ({1}) but is not started anywhere, skipped.", entry, pool_id); + continue; + } + + PoolTemplateData pPoolTemplate = mPoolTemplate[pool_id]; + PoolObject plObject = new PoolObject(entry, 0.0f); + + if (!mPoolQuestGroups.ContainsKey(pool_id)) + mPoolQuestGroups[pool_id] = new PoolGroup(); + + PoolGroup questgroup = mPoolQuestGroups[pool_id]; + questgroup.SetPoolId(pool_id); + questgroup.AddEntry(plObject, pPoolTemplate.MaxLimit); + + mQuestSearchMap.Add(entry, pool_id); + ++count; + } + while (result.NextRow()); + + Log.outInfo(LogFilter.ServerLoading, "Loaded {0} quests in pools in {1} ms", count, Time.GetMSTimeDiffToNow(oldMSTime)); + } + } + + // The initialize method will spawn all pools not in an event and not in another pool, this is why there is 2 left joins with 2 null checks + Log.outInfo(LogFilter.ServerLoading, "Starting objects pooling system..."); + { + uint oldMSTime = Time.GetMSTime(); + + SQLResult result = DB.World.Query("SELECT DISTINCT pool_template.entry, pool_pool.pool_id, pool_pool.mother_pool FROM pool_template" + + " LEFT JOIN game_event_pool ON pool_template.entry=game_event_pool.pool_entry" + + " LEFT JOIN pool_pool ON pool_template.entry=pool_pool.pool_id WHERE game_event_pool.pool_entry IS NULL"); + + if (result.IsEmpty()) + { + Log.outInfo(LogFilter.ServerLoading, "Pool handling system initialized, 0 pools spawned."); + } + else + { + uint count = 0; + do + { + uint pool_entry = result.Read(0); + uint pool_pool_id = result.Read(1); + + if (!CheckPool(pool_entry)) + { + if (pool_pool_id != 0) + // The pool is a child pool in pool_pool table. Ideally we should remove it from the pool handler to ensure it never gets spawned, + // however that could recursively invalidate entire chain of mother pools. It can be done in the future but for now we'll do nothing. + Log.outError(LogFilter.Sql, "Pool Id {0} has no equal chance pooled entites defined and explicit chance sum is not 100. This broken pool is a child pool of Id {1} and cannot be safely removed.", pool_entry, result.Read(2)); + else + Log.outError(LogFilter.Sql, "Pool Id {0} has no equal chance pooled entites defined and explicit chance sum is not 100. The pool will not be spawned.", pool_entry); + continue; + } + + // Don't spawn child pools, they are spawned recursively by their parent pools + if (pool_pool_id == 0) + { + SpawnPool(pool_entry); + count++; + } + } + while (result.NextRow()); + + Log.outDebug(LogFilter.Pool, "Pool handling system initialized, {0} pools spawned in {1} ms", count, Time.GetMSTimeDiffToNow(oldMSTime)); + + } + } + } + + public void SaveQuestsToDB() + { + SQLTransaction trans = new SQLTransaction(); + + foreach (var questPoolGroup in mPoolQuestGroups.Values) + { + if (questPoolGroup.isEmpty()) + continue; + PreparedStatement stmt = DB.Characters.GetPreparedStatement(CharStatements.DEL_QUEST_POOL_SAVE); + stmt.AddValue(0, questPoolGroup.GetPoolId()); + trans.Append(stmt); + } + + foreach (var pair in mQuestSearchMap) + { + if (IsSpawnedObject(pair.Key)) + { + PreparedStatement stmt = DB.Characters.GetPreparedStatement(CharStatements.INS_QUEST_POOL_SAVE); + stmt.AddValue(0, pair.Value); + stmt.AddValue(1, pair.Key); + trans.Append(stmt); + } + } + + DB.Characters.CommitTransaction(trans); + } + + public void ChangeDailyQuests() + { + foreach (var questPoolGroup in mPoolQuestGroups.Values) + { + Quest quest = Global.ObjectMgr.GetQuestTemplate((uint)questPoolGroup.GetFirstEqualChancedObjectId()); + if (quest != null) + { + if (quest.IsWeekly()) + continue; + + UpdatePool(questPoolGroup.GetPoolId(), 1); // anything non-zero means don't load from db + } + } + + SaveQuestsToDB(); + } + + public void ChangeWeeklyQuests() + { + foreach (var questPoolGroup in mPoolQuestGroups.Values) + { + Quest quest = Global.ObjectMgr.GetQuestTemplate((uint)questPoolGroup.GetFirstEqualChancedObjectId()); + if (quest != null) + { + if (quest.IsDaily()) + continue; + + UpdatePool(questPoolGroup.GetPoolId(), 1); + } + } + + SaveQuestsToDB(); + } + + void SpawnPool(uint pool_id, ulong db_guid) + { + switch (typeof(T).Name) + { + case "Creature": + if (mPoolCreatureGroups.ContainsKey(pool_id) && !mPoolCreatureGroups[pool_id].isEmpty()) + mPoolCreatureGroups[pool_id].SpawnObject(mSpawnedData, mPoolTemplate[pool_id].MaxLimit, db_guid); + break; + case "GameObject": + if (mPoolGameobjectGroups.ContainsKey(pool_id) && !mPoolGameobjectGroups[pool_id].isEmpty()) + mPoolGameobjectGroups[pool_id].SpawnObject(mSpawnedData, mPoolTemplate[pool_id].MaxLimit, db_guid); + break; + case "Pool": + if (mPoolPoolGroups.ContainsKey(pool_id) && !mPoolPoolGroups[pool_id].isEmpty()) + mPoolPoolGroups[pool_id].SpawnObject(mSpawnedData, mPoolTemplate[pool_id].MaxLimit, db_guid); + break; + case "Quest": + if (mPoolQuestGroups.ContainsKey(pool_id) && !mPoolQuestGroups[pool_id].isEmpty()) + mPoolQuestGroups[pool_id].SpawnObject(mSpawnedData, mPoolTemplate[pool_id].MaxLimit, db_guid); + break; + } + } + + public void SpawnPool(uint pool_id) + { + SpawnPool(pool_id, 0); + SpawnPool(pool_id, 0); + SpawnPool(pool_id, 0); + SpawnPool(pool_id, 0); + } + + public void DespawnPool(uint pool_id) + { + if (mPoolCreatureGroups.ContainsKey(pool_id) && !mPoolCreatureGroups[pool_id].isEmpty()) + mPoolCreatureGroups[pool_id].DespawnObject(mSpawnedData); + + if (mPoolGameobjectGroups.ContainsKey(pool_id) && !mPoolGameobjectGroups[pool_id].isEmpty()) + mPoolGameobjectGroups[pool_id].DespawnObject(mSpawnedData); + + if (mPoolPoolGroups.ContainsKey(pool_id) && !mPoolPoolGroups[pool_id].isEmpty()) + mPoolPoolGroups[pool_id].DespawnObject(mSpawnedData); + + if (mPoolQuestGroups.ContainsKey(pool_id) && !mPoolQuestGroups[pool_id].isEmpty()) + mPoolQuestGroups[pool_id].DespawnObject(mSpawnedData); + } + + public bool CheckPool(uint pool_id) + { + return mPoolTemplate.ContainsKey(pool_id) && + (!mPoolGameobjectGroups.ContainsKey(pool_id) || mPoolGameobjectGroups[pool_id].CheckPool()) && + (!mPoolCreatureGroups.ContainsKey(pool_id) || mPoolCreatureGroups[pool_id].CheckPool()) && + (!mPoolPoolGroups.ContainsKey(pool_id) || mPoolPoolGroups[pool_id].CheckPool()) && + (!mPoolQuestGroups.ContainsKey(pool_id) || mPoolQuestGroups[pool_id].CheckPool()); + } + + public void UpdatePool(uint pool_id, ulong db_guid_or_pool_id) + { + uint motherpoolid = IsPartOfAPool(pool_id); + if (motherpoolid != 0) + SpawnPool(motherpoolid, pool_id); + else + SpawnPool(pool_id, db_guid_or_pool_id); + } + + public uint IsPartOfAPool(ulong db_guid) + { + switch (typeof(T).Name) + { + case "Creature": + return mCreatureSearchMap.LookupByKey(db_guid); + case "GameObject": + return mGameobjectSearchMap.LookupByKey(db_guid); + case "Pool": + return mPoolSearchMap.LookupByKey(db_guid); + case "Quest": + return mQuestSearchMap.LookupByKey(db_guid); + } + return 0; + } + + public enum eQuestTypes + { + None = 0, + Daily = 1, + Weekly = 2 + } + + public bool IsSpawnedObject(ulong db_guid_or_pool_id) { return mSpawnedData.IsActiveObject(db_guid_or_pool_id); } + + + public MultiMap mQuestCreatureRelation = new MultiMap(); + public MultiMap mQuestGORelation = new MultiMap(); + + Dictionary mPoolTemplate = new Dictionary(); + Dictionary> mPoolCreatureGroups = new Dictionary>(); + Dictionary> mPoolGameobjectGroups = new Dictionary>(); + Dictionary> mPoolPoolGroups = new Dictionary>(); + Dictionary> mPoolQuestGroups = new Dictionary>(); + Dictionary mCreatureSearchMap = new Dictionary(); + Dictionary mGameobjectSearchMap = new Dictionary(); + Dictionary mPoolSearchMap = new Dictionary(); + Dictionary mQuestSearchMap = new Dictionary(); + + // dynamic data + ActivePoolData mSpawnedData = new ActivePoolData(); + } + + public class PoolGroup + { + public PoolGroup() + { + poolId = 0; + } + + public void AddEntry(PoolObject poolitem, uint maxentries) + { + if (poolitem.chance != 0 && maxentries == 1) + ExplicitlyChanced.Add(poolitem); + else + EqualChanced.Add(poolitem); + } + + public bool CheckPool() + { + if (EqualChanced.Empty()) + { + float chance = 0; + for (int i = 0; i < ExplicitlyChanced.Count; ++i) + chance += ExplicitlyChanced[i].chance; + if (chance != 100 && chance != 0) + return false; + } + return true; + } + + PoolObject RollOne(ActivePoolData spawns, ulong triggerFrom) + { + if (!ExplicitlyChanced.Empty()) + { + float roll = (float)RandomHelper.randChance(); + + for (int i = 0; i < ExplicitlyChanced.Count; ++i) + { + roll -= ExplicitlyChanced[i].chance; + // Triggering object is marked as spawned at this time and can be also rolled (respawn case) + // so this need explicit check for this case + if (roll < 0 && (ExplicitlyChanced[i].guid == triggerFrom || !spawns.IsActiveObject(ExplicitlyChanced[i].guid))) + return ExplicitlyChanced[i]; + } + } + if (!EqualChanced.Empty()) + { + int index = RandomHelper.IRand(0, EqualChanced.Count - 1); + // Triggering object is marked as spawned at this time and can be also rolled (respawn case) + // so this need explicit check for this case + if (EqualChanced[index].guid == triggerFrom || !spawns.IsActiveObject(EqualChanced[index].guid)) + return EqualChanced[index]; + } + + return null; + } + + public void DespawnObject(ActivePoolData spawns, ulong guid = 0) + { + for (int i = 0; i < EqualChanced.Count; ++i) + { + // if spawned + if (spawns.IsActiveObject(EqualChanced[i].guid)) + { + if (guid == 0 || EqualChanced[i].guid == guid) + { + Despawn1Object(EqualChanced[i].guid); + spawns.RemoveObject(EqualChanced[i].guid, poolId); + } + } + } + + for (int i = 0; i < ExplicitlyChanced.Count; ++i) + { + // spawned + if (spawns.IsActiveObject(ExplicitlyChanced[i].guid)) + { + if (guid == 0 || ExplicitlyChanced[i].guid == guid) + { + Despawn1Object(ExplicitlyChanced[i].guid); + spawns.RemoveObject(ExplicitlyChanced[i].guid, poolId); + } + } + } + } + + void Despawn1Object(ulong guid) + { + switch (typeof(T).Name) + { + case "Creature": + { + var data = Global.ObjectMgr.GetCreatureData(guid); + if (data != null) + { + Global.ObjectMgr.RemoveCreatureFromGrid(guid, data); + Map map = Global.MapMgr.CreateBaseMap(data.mapid); + if (!map.Instanceable()) + { + var creatureBounds = map.GetCreatureBySpawnIdStore().LookupByKey(guid); + foreach (var creature in creatureBounds) + creature.AddObjectToRemoveList(); + } + } + break; + } + case "GameObject": + { + var data = Global.ObjectMgr.GetGOData(guid); + if (data != null) + { + Global.ObjectMgr.RemoveGameObjectFromGrid(guid, data); + + Map map = Global.MapMgr.CreateBaseMap(data.mapid); + if (!map.Instanceable()) + { + var gameobjectBounds = map.GetGameObjectBySpawnIdStore().LookupByKey(guid); + foreach (var go in gameobjectBounds) + go.AddObjectToRemoveList(); + } + } + break; + } + case "Pool": + Global.PoolMgr.DespawnPool((uint)guid); + break; + case "Quest": + // Creatures + var questMap = Global.ObjectMgr.GetCreatureQuestRelationMap(); + var qr = Global.PoolMgr.mQuestCreatureRelation.LookupByKey(guid); + foreach (var creature in qr) + { + if (!questMap.ContainsKey(creature)) + continue; + + foreach (var quest in questMap[creature].ToList()) + { + if (quest == guid) + questMap.Remove(creature, quest); + } + } + + // Gameobjects + questMap = Global.ObjectMgr.GetGOQuestRelationMap(); + qr = Global.PoolMgr.mQuestGORelation.LookupByKey(guid); + foreach (var go in qr) + { + if (!questMap.ContainsKey(go)) + continue; + + foreach (var quest in questMap[go]) + { + if (quest == guid) + questMap.Remove(go, quest); + } + } + break; + } + } + + public void RemoveOneRelation(uint child_pool_id) + { + if (typeof(T).Name != "Pool") + return; + + foreach (var poolObject in ExplicitlyChanced) + { + if (poolObject.guid == child_pool_id) + { + ExplicitlyChanced.Remove(poolObject); + break; + } + } + foreach (var poolObject in EqualChanced) + { + if (poolObject.guid == child_pool_id) + { + EqualChanced.Remove(poolObject); + break; + } + } + } + + public void SpawnObject(ActivePoolData spawns, uint limit, ulong triggerFrom) + { + if (typeof(T).Name == "Quest") + { + SpawnQuestObject(spawns, limit, triggerFrom); + return; + } + + ulong lastDespawned = 0; + int count = (int)(limit - spawns.GetActiveObjectCount(poolId)); + + // If triggered from some object respawn this object is still marked as spawned + // and also counted into m_SpawnedPoolAmount so we need increase count to be + // spawned by 1 + if (triggerFrom != 0) + ++count; + + // This will try to spawn the rest of pool, not guaranteed + for (int i = 0; i < count; ++i) + { + PoolObject obj = RollOne(spawns, triggerFrom); + if (obj == null) + continue; + if (obj.guid == lastDespawned) + continue; + + if (obj.guid == triggerFrom) + { + ReSpawn1Object(obj); + triggerFrom = 0; + continue; + } + spawns.ActivateObject(obj.guid, poolId); + Spawn1Object(obj); + + if (triggerFrom != 0) + { + // One spawn one despawn no count increase + DespawnObject(spawns, triggerFrom); + lastDespawned = triggerFrom; + triggerFrom = 0; + } + } + } + + void SpawnQuestObject(ActivePoolData spawns, uint limit, ulong triggerFrom) + { + Log.outDebug(LogFilter.Pool, "PoolGroup: Spawning pool {0}", poolId); + // load state from db + if (triggerFrom == 0) + { + PreparedStatement stmt = DB.Characters.GetPreparedStatement(CharStatements.SEL_POOL_QUEST_SAVE); + stmt.AddValue(0, poolId); + SQLResult result = DB.Characters.Query(stmt); + + if (!result.IsEmpty()) + { + do + { + uint questId = result.Read(0); + spawns.ActivateObject(questId, poolId); + PoolObject tempObj = new PoolObject(questId, 0.0f); + Spawn1Object(tempObj); + --limit; + } while (result.NextRow() && limit != 0); + return; + } + } + + List currentQuests = spawns.GetActiveQuests(); + List newQuests = new List(); + + // always try to select different quests + foreach (var poolObject in EqualChanced) + { + if (spawns.IsActiveObject(poolObject.guid)) + continue; + newQuests.Add(poolObject.guid); + } + + // clear the pool + DespawnObject(spawns); + + // recycle minimal amount of quests if possible count is lower than limit + if (limit > newQuests.Count && !currentQuests.Empty()) + { + do + { + ulong questId = currentQuests.PickRandom(); + newQuests.Add(questId); + currentQuests.Remove(questId); + } while (newQuests.Count < limit && !currentQuests.Empty()); // failsafe + } + + if (newQuests.Empty()) + return; + + // activate random quests + do + { + ulong questId = newQuests.PickRandom(); + spawns.ActivateObject(questId, poolId); + PoolObject tempObj = new PoolObject(questId, 0.0f); + Spawn1Object(tempObj); + newQuests.Remove(questId); + --limit; + } while (limit != 0 && !newQuests.Empty()); + + // if we are here it means the pool is initialized at startup and did not have previous saved state + if (triggerFrom == 0) + Global.PoolMgr.SaveQuestsToDB(); + } + + void Spawn1Object(PoolObject obj) + { + switch (typeof(T).Name) + { + case "Creature": + CreatureData data = Global.ObjectMgr.GetCreatureData(obj.guid); + if (data != null) + { + Global.ObjectMgr.AddCreatureToGrid(obj.guid, data); + + // Spawn if necessary (loaded grids only) + Map map = Global.MapMgr.CreateBaseMap(data.mapid); + // We use spawn coords to spawn + if (!map.Instanceable() && map.IsGridLoaded(data.posX, data.posY)) + { + Creature creature = new Creature(); + if (!creature.LoadCreatureFromDB(obj.guid, map)) + return; + + } + } + break; + case "GameObject": + GameObjectData data_ = Global.ObjectMgr.GetGOData(obj.guid); + if (data_ != null) + { + Global.ObjectMgr.AddGameObjectToGrid(obj.guid, data_); + // Spawn if necessary (loaded grids only) + // this base map checked as non-instanced and then only existed + Map map = Global.MapMgr.CreateBaseMap(data_.mapid); + // We use current coords to unspawn, not spawn coords since creature can have changed grid + if (!map.Instanceable() && map.IsGridLoaded(data_.posX, data_.posY)) + { + GameObject pGameobject = new GameObject(); + if (!pGameobject.LoadGameObjectFromDB(obj.guid, map, false)) + return; + else + { + if (pGameobject.isSpawnedByDefault()) + map.AddToMap(pGameobject); + } + } + } + break; + case "Pool": + Global.PoolMgr.SpawnPool((uint)obj.guid); + break; + case "Quest": + // Creatures + var questMap = Global.ObjectMgr.GetCreatureQuestRelationMap(); + var qr = Global.PoolMgr.mQuestCreatureRelation.LookupByKey(obj.guid); + foreach (var creature in qr) + { + Log.outDebug(LogFilter.Pool, "PoolGroup: Adding quest {0} to creature {1}", obj.guid, creature); + questMap.Add(creature, (uint)obj.guid); + } + + // Gameobjects + questMap = Global.ObjectMgr.GetGOQuestRelationMap(); + qr = Global.PoolMgr.mQuestGORelation.LookupByKey(obj.guid); + foreach (var go in qr) + { + Log.outDebug(LogFilter.Pool, "PoolGroup: Adding quest {0} to GO {1}", obj.guid, go); + questMap.Add(go, (uint)obj.guid); + } + break; + } + } + + void ReSpawn1Object(PoolObject obj) + { + // GameObject/Creature is still on map, nothing to do + } + + public void SetPoolId(uint pool_id) { poolId = pool_id; } + + public bool isEmpty() { return ExplicitlyChanced.Empty() && EqualChanced.Empty(); } + + public ulong GetFirstEqualChancedObjectId() + { + if (EqualChanced.Empty()) + return 0; + return EqualChanced.FirstOrDefault().guid; + } + public uint GetPoolId() { return poolId; } + + uint poolId; + List ExplicitlyChanced = new List(); + List EqualChanced = new List(); + } + + public class ActivePoolData + { + public uint GetActiveObjectCount(uint pool_id) + { + return mSpawnedPools.LookupByKey(pool_id); + } + + public bool IsActiveObject(ulong db_guid) + { + switch (typeof(T).Name) + { + case "Creature": + return mSpawnedCreatures.Contains(db_guid); + case "GameObject": + return mSpawnedGameobjects.Contains(db_guid); + case "Pool": + return mSpawnedPools.ContainsKey(db_guid); + case "Quest": + return mActiveQuests.Contains(db_guid); + default: + return false; + } + } + + public void ActivateObject(ulong db_guid, uint pool_id) + { + switch (typeof(T).Name) + { + case "Creature": + mSpawnedCreatures.Add(db_guid); + break; + case "GameObject": + mSpawnedGameobjects.Add(db_guid); + break; + case "Pool": + mSpawnedPools[db_guid] = 0; + break; + case "Quest": + mActiveQuests.Add(db_guid); + break; + default: + return; + } + if (!mSpawnedPools.ContainsKey(pool_id)) + mSpawnedPools[pool_id] = 0; + + ++mSpawnedPools[pool_id]; + } + + public void RemoveObject(ulong db_guid, uint pool_id) + { + switch (typeof(T).Name) + { + case "Creature": + mSpawnedCreatures.Remove(db_guid); + break; + case "GameObject": + mSpawnedGameobjects.Remove(db_guid); + break; + case "Pool": + mSpawnedPools.Remove(db_guid); + break; + case "Quest": + mActiveQuests.Remove(db_guid); + break; + default: + return; + } + + uint val = mSpawnedPools[pool_id]; + if (val > 0) + --val; + } + + public List GetActiveQuests() { return mActiveQuests; } // a copy of the set + + List mSpawnedCreatures = new List(); + List mSpawnedGameobjects = new List(); + List mActiveQuests = new List(); + Dictionary mSpawnedPools = new Dictionary(); + } + + public class PoolObject + { + public PoolObject(ulong _guid, float _chance) + { + guid = _guid; + chance = Math.Abs(_chance); + } + + public ulong guid; + public float chance; + } + + public struct PoolTemplateData + { + public uint MaxLimit; + } + + public class Pool { } // for Pool of Pool case +} diff --git a/Game/Properties/AssemblyInfo.cs b/Game/Properties/AssemblyInfo.cs new file mode 100644 index 000000000..aebbe5e0d --- /dev/null +++ b/Game/Properties/AssemblyInfo.cs @@ -0,0 +1,36 @@ +using System.Reflection; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; + +// General Information about an assembly is controlled through the following +// set of attributes. Change these attribute values to modify the information +// associated with an assembly. +[assembly: AssemblyTitle("Game")] +[assembly: AssemblyDescription("")] +[assembly: AssemblyConfiguration("")] +[assembly: AssemblyCompany("")] +[assembly: AssemblyProduct("Game")] +[assembly: AssemblyCopyright("Copyright © 2014")] +[assembly: AssemblyTrademark("")] +[assembly: AssemblyCulture("")] + +// Setting ComVisible to false makes the types in this assembly not visible +// to COM components. If you need to access a type in this assembly from +// COM, set the ComVisible attribute to true on that type. +[assembly: ComVisible(false)] + +// The following GUID is for the ID of the typelib if this project is exposed to COM +[assembly: Guid("631fea1e-2b00-436a-b20b-ac5c9921ef2e")] + +// Version information for an assembly consists of the following four values: +// +// Major Version +// Minor Version +// Build Number +// Revision +// +// You can specify all the values or you can default the Build and Revision Numbers +// by using the '*' as shown below: +// [assembly: AssemblyVersion("1.0.*")] +[assembly: AssemblyVersion("1.0.0.0")] +[assembly: AssemblyFileVersion("1.0.0.0")] diff --git a/Game/Quest/Quest.cs b/Game/Quest/Quest.cs new file mode 100644 index 000000000..03f27997f --- /dev/null +++ b/Game/Quest/Quest.cs @@ -0,0 +1,601 @@ +/* + * Copyright (C) 2012-2017 CypherCore + * + * 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, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +using Framework.Collections; +using Framework.Constants; +using Framework.Database; +using Game.DataStorage; +using Game.Entities; +using Game.Network.Packets; +using System; +using System.Collections.Generic; + +namespace Game +{ + public class Quest + { + public Quest(SQLFields fields) + { + Id = fields.Read(0); + Type = (QuestType)fields.Read(1); + Level = fields.Read(2); + PackageID = fields.Read(3); + MinLevel = fields.Read(4); + QuestSortID = fields.Read(5); + QuestInfoID = fields.Read(6); + SuggestedPlayers = fields.Read(7); + NextQuestInChain = fields.Read(8); + RewardXPDifficulty = fields.Read(9); + RewardXPMultiplier = fields.Read(10); + RewardMoney = fields.Read(11); + RewardMoneyDifficulty = fields.Read(12); + RewardMoneyMultiplier = fields.Read(13); + RewardBonusMoney = fields.Read(14); + + for (int i = 0; i < SharedConst.QuestRewardDisplaySpellCount; ++i) + RewardDisplaySpell[i] = fields.Read(15 + i); + + RewardSpell = fields.Read(18); + RewardHonor = fields.Read(19); + RewardKillHonor = fields.Read(20); + SourceItemId = fields.Read(21); + RewardArtifactXPDifficulty = fields.Read(22); + RewardArtifactXPMultiplier = fields.Read(23); + RewardArtifactCategoryID = fields.Read(24); + Flags = (QuestFlags)fields.Read(25); + FlagsEx = (QuestFlagsEx)fields.Read(26); + + for (int i = 0; i < SharedConst.QuestItemDropCount; ++i) + { + RewardItemId[i] = fields.Read(27 + i * 4); + RewardItemCount[i] = fields.Read(28 + i * 4); + ItemDrop[i] = fields.Read(29 + i * 4); + ItemDropQuantity[i] = fields.Read(30 + i * 4); + + if (RewardItemId[i] != 0) + ++_rewItemsCount; + } + + for (int i = 0; i < SharedConst.QuestRewardChoicesCount; ++i) + { + RewardChoiceItemId[i] = fields.Read(43 + i * 3); + RewardChoiceItemCount[i] = fields.Read(44 + i * 3); + RewardChoiceItemDisplayId[i] = fields.Read(45 + i * 3); + + if (RewardChoiceItemId[i] != 0) + ++_rewChoiceItemsCount; + } + + POIContinent = fields.Read(61); + POIx = fields.Read(62); + POIy = fields.Read(63); + POIPriority = fields.Read(64); + + RewardTitleId = fields.Read(65); + RewardArenaPoints = fields.Read(66); + RewardSkillId = fields.Read(67); + RewardSkillPoints = fields.Read(68); + + QuestGiverPortrait = fields.Read(69); + QuestTurnInPortrait = fields.Read(70); + + for (int i = 0; i < SharedConst.QuestRewardReputationsCount; ++i) + { + RewardFactionId[i] = fields.Read(71 + i * 4); + RewardFactionValue[i] = fields.Read(72 + i * 4); + RewardFactionOverride[i] = fields.Read(73 + i * 4); + RewardFactionCapIn[i] = fields.Read(74 + i * 4); + } + + RewardReputationMask = fields.Read(91); + + for (int i = 0; i < SharedConst.QuestRewardCurrencyCount; ++i) + { + RewardCurrencyId[i] = fields.Read(92 + i * 2); + RewardCurrencyCount[i] = fields.Read(93 + i * 2); + + if (RewardCurrencyId[i] != 0) + ++_rewCurrencyCount; + } + + SoundAccept = fields.Read(100); + SoundTurnIn = fields.Read(101); + AreaGroupID = fields.Read(102); + LimitTime = fields.Read(103); + AllowableRaces = fields.Read(104); + QuestRewardID = fields.Read(105); + Expansion = fields.Read(106); + + LogTitle = fields.Read(107); + LogDescription = fields.Read(108); + QuestDescription = fields.Read(109); + AreaDescription = fields.Read(110); + PortraitGiverText = fields.Read(111); + PortraitGiverName = fields.Read(112); + PortraitTurnInText = fields.Read(113); + PortraitTurnInName = fields.Read(114); + QuestCompletionLog = fields.Read(115); + } + + public void LoadQuestDetails(SQLFields fields) + { + for (int i = 0; i < SharedConst.QuestEmoteCount; ++i) + { + ushort emoteId = fields.Read(1 + i); + if (!CliDB.EmotesStorage.ContainsKey(emoteId)) + { + Log.outError(LogFilter.Sql, "Table `quest_details` has non-existing Emote{0} ({1}) set for quest {2}. Skipped.", 1 + i, emoteId, fields.Read(0)); + continue; + } + DetailsEmote[i] = emoteId; + } + + for (int i = 0; i < SharedConst.QuestEmoteCount; ++i) + DetailsEmoteDelay[i] = fields.Read(5 + i); + } + + public void LoadQuestRequestItems(SQLFields fields) + { + EmoteOnComplete = fields.Read(1); + EmoteOnIncomplete = fields.Read(2); + + if (!CliDB.EmotesStorage.ContainsKey(EmoteOnComplete)) + Log.outError(LogFilter.Sql, "Table `quest_request_items` has non-existing EmoteOnComplete ({0}) set for quest {1}.", EmoteOnComplete, fields.Read(0)); + + if (!CliDB.EmotesStorage.ContainsKey(EmoteOnIncomplete)) + Log.outError(LogFilter.Sql, "Table `quest_request_items` has non-existing EmoteOnIncomplete ({0}) set for quest {1}.", EmoteOnIncomplete, fields.Read(0)); + + EmoteOnCompleteDelay = fields.Read(3); + EmoteOnIncompleteDelay = fields.Read(4); + RequestItemsText = fields.Read(5); + } + + public void LoadQuestOfferReward(SQLFields fields) + { + for (int i = 0; i < SharedConst.QuestEmoteCount; ++i) + { + ushort emoteId = fields.Read(1 + i); + if (!CliDB.EmotesStorage.ContainsKey(emoteId)) + { + Log.outError(LogFilter.Sql, "Table `quest_offer_reward` has non-existing Emote{0} ({1}) set for quest {2}. Skipped.", 1 + i, emoteId, fields.Read(0)); + continue; + } + OfferRewardEmote[i] = emoteId; + } + + for (int i = 0; i < SharedConst.QuestEmoteCount; ++i) + OfferRewardEmoteDelay[i] = fields.Read(5 + i); + + OfferRewardText = fields.Read(9); + } + + public void LoadQuestTemplateAddon(SQLFields fields) + { + MaxLevel = fields.Read(1); + AllowableClasses = fields.Read(2); + SourceSpellID = fields.Read(3); + PrevQuestId = fields.Read(4); + NextQuestId = fields.Read(5); + ExclusiveGroup = fields.Read(6); + RewardMailTemplateId = fields.Read(7); + RewardMailDelay = fields.Read(8); + RequiredSkillId = fields.Read(9); + RequiredSkillPoints = fields.Read(10); + RequiredMinRepFaction = fields.Read(11); + RequiredMaxRepFaction = fields.Read(12); + RequiredMinRepValue = fields.Read(13); + RequiredMaxRepValue = fields.Read(14); + SourceItemIdCount = fields.Read(15); + RewardMailSenderEntry = fields.Read(16); + SpecialFlags = (QuestSpecialFlags)fields.Read(17); + + if (SpecialFlags.HasAnyFlag(QuestSpecialFlags.AutoAccept)) + Flags |= QuestFlags.AutoAccept; + } + + public void LoadQuestObjective(SQLFields fields) + { + QuestObjective obj = new QuestObjective(); + obj.ID = fields.Read(0); + obj.QuestID = fields.Read(1); + obj.Type = (QuestObjectiveType)fields.Read(2); + obj.StorageIndex = fields.Read(3); + obj.ObjectID = fields.Read(4); + obj.Amount = fields.Read(5); + obj.Flags = (QuestObjectiveFlags)fields.Read(6); + obj.Flags2 = fields.Read(7); + obj.ProgressBarWeight = fields.Read(8); + obj.Description = fields.Read(9); + + Objectives.Add(obj); + } + + public void LoadQuestObjectiveVisualEffect(SQLFields fields) + { + uint objID = fields.Read(1); + + foreach (QuestObjective obj in Objectives) + { + if (obj.ID == objID) + { + byte effectIndex = fields.Read(3); + if (obj.VisualEffects == null) + obj.VisualEffects = new int[effectIndex + 1]; + + if (effectIndex >= obj.VisualEffects.Length) + Array.Resize(ref obj.VisualEffects, effectIndex + 1); + + obj.VisualEffects[effectIndex] = fields.Read(4); + break; + } + } + } + + public uint XPValue(uint playerLevel) + { + if (playerLevel != 0) + { + uint questLevel = (Level == -1 ? playerLevel : (uint)Level); + QuestXPRecord questXp = CliDB.QuestXPStorage.LookupByKey(questLevel); + if (questXp == null || RewardXPDifficulty >= 10) + return 0; + + float multiplier = 1.0f; + if (questLevel != playerLevel) + multiplier = CliDB.XpGameTable.GetRow(Math.Min(playerLevel, questLevel)).Divisor / CliDB.XpGameTable.GetRow(playerLevel).Divisor; + + int diffFactor = (int)(2 * (questLevel - playerLevel) + 20); + if (diffFactor < 1) + diffFactor = 1; + else if (diffFactor > 10) + diffFactor = 10; + + uint xp = (uint)(diffFactor * questXp.Exp[RewardXPDifficulty] * RewardXPMultiplier / 10 * multiplier); + if (xp <= 100) + xp = 5 * ((xp + 2) / 5); + else if (xp <= 500) + xp = 10 * ((xp + 5) / 10); + else if (xp <= 1000) + xp = 25 * ((xp + 12) / 25); + else + xp = 50 * ((xp + 25) / 50); + + return xp; + } + + return 0; + } + + public uint MoneyValue(uint playerLevel) + { + uint level = Level == -1 ? playerLevel : (uint)Level; + + QuestMoneyRewardRecord money = CliDB.QuestMoneyRewardStorage.LookupByKey(level); + if (money != null) + return (uint)(money.Money[RewardMoneyDifficulty] * RewardMoneyMultiplier); + else + return 0; + } + + public void BuildQuestRewards(QuestRewards rewards, Player player) + { + rewards.ChoiceItemCount = GetRewChoiceItemsCount(); + rewards.ItemCount = GetRewItemsCount(); + rewards.Money = player.GetQuestMoneyReward(this); + rewards.XP = player.GetQuestXPReward(this); + rewards.ArtifactCategoryID = RewardArtifactCategoryID; + rewards.Title = RewardTitleId; + rewards.FactionFlags = RewardReputationMask; + for (int i = 0; i < SharedConst.QuestRewardDisplaySpellCount; ++i) + rewards.SpellCompletionDisplayID[i] = (int)RewardDisplaySpell[i]; + + rewards.SpellCompletionID = RewardSpell; + rewards.SkillLineID = RewardSkillId; + rewards.NumSkillUps = RewardSkillPoints; + rewards.RewardID = QuestRewardID; + + for (int i = 0; i < SharedConst.QuestRewardChoicesCount; ++i) + { + rewards.ChoiceItems[i].ItemID = RewardChoiceItemId[i]; + rewards.ChoiceItems[i].Quantity = RewardChoiceItemCount[i]; + } + + for (int i = 0; i < SharedConst.QuestRewardItemCount; ++i) + { + rewards.ItemID[i] = RewardItemId[i]; + rewards.ItemQty[i] = RewardItemCount[i]; + } + + for (int i = 0; i < SharedConst.QuestRewardReputationsCount; ++i) + { + rewards.FactionID[i] = RewardFactionId[i]; + rewards.FactionOverride[i] = RewardFactionOverride[i]; + rewards.FactionValue[i] = RewardFactionValue[i]; + rewards.FactionCapIn[i] = (int)RewardFactionCapIn[i]; + } + + for (int i = 0; i < SharedConst.QuestRewardCurrencyCount; ++i) + { + rewards.CurrencyID[i] = RewardCurrencyId[i]; + rewards.CurrencyQty[i] = RewardCurrencyCount[i]; + } + } + + public uint GetRewMoneyMaxLevel() + { + // If Quest has flag to not give money on max level, it's 0 + if (HasFlag(QuestFlags.NoMoneyFromXp)) + return 0; + + // Else, return the rewarded copper sum modified by the rate + return (uint)(RewardBonusMoney * WorldConfig.GetFloatValue(WorldCfg.RateMoneyMaxLevelQuest)); + } + + public bool IsAutoAccept() + { + return !WorldConfig.GetBoolValue(WorldCfg.QuestIgnoreAutoAccept) && HasFlag(QuestFlags.AutoAccept); + } + + public bool IsAutoComplete() + { + return !WorldConfig.GetBoolValue(WorldCfg.QuestIgnoreAutoComplete) && Type == QuestType.AutoComplete; + } + + public bool IsRaidQuest(Difficulty difficulty) + { + switch ((QuestInfos)QuestInfoID) + { + case QuestInfos.Raid: + return true; + case QuestInfos.Raid10: + return difficulty == Difficulty.Raid10N || difficulty == Difficulty.Raid10HC; + case QuestInfos.Raid25: + return difficulty == Difficulty.Raid25N || difficulty == Difficulty.Raid25HC; + default: + break; + } + + if (Flags.HasAnyFlag(QuestFlags.Raid)) + return true; + + return false; + } + + public bool IsAllowedInRaid(Difficulty difficulty) + { + if (IsRaidQuest(difficulty)) + return true; + + return WorldConfig.GetBoolValue(WorldCfg.QuestIgnoreRaid); + } + + public uint CalculateHonorGain(uint level) + { + uint honor = 0; + return honor; + } + + public bool CanIncreaseRewardedQuestCounters() + { + // Dungeon Finder/Daily/Repeatable (if not weekly, monthly or seasonal) quests are never considered rewarded serverside. + // This affects counters and client requests for completed quests. + return (!IsDFQuest() && !IsDaily() && (!IsRepeatable() || IsWeekly() || IsMonthly() || IsSeasonal())); + } + + public bool HasFlag(QuestFlags flag) { return (Flags & flag) != 0; } + + public void SetFlag(QuestFlags flag) { Flags |= flag; } + + public bool HasSpecialFlag(QuestSpecialFlags flag) { return (SpecialFlags & flag) != 0; } + + public void SetSpecialFlag(QuestSpecialFlags flag) { SpecialFlags |= flag; } + + // table data accessors: + public bool IsRepeatable() { return SpecialFlags.HasAnyFlag(QuestSpecialFlags.Repeatable); } + public bool IsDaily() { return Flags.HasAnyFlag(QuestFlags.Daily); } + public bool IsWeekly() { return Flags.HasAnyFlag(QuestFlags.Weekly); } + public bool IsMonthly() { return SpecialFlags.HasAnyFlag(QuestSpecialFlags.Monthly); } + public bool IsSeasonal() + { + return (QuestSortID == -(int)QuestSort.Seasonal || QuestSortID == -(int)QuestSort.Special || QuestSortID == -(int)QuestSort.LunarFestival + || QuestSortID == -(int)QuestSort.Midsummer || QuestSortID == -(int)QuestSort.Brewfest || QuestSortID == -(int)QuestSort.LoveIsInTheAir + || QuestSortID == -(int)QuestSort.Noblegarden) && !IsRepeatable(); + } + public bool IsDailyOrWeekly() { return Flags.HasAnyFlag(QuestFlags.Daily | QuestFlags.Weekly); } + public bool IsDFQuest() { return SpecialFlags.HasAnyFlag(QuestSpecialFlags.DfQuest); } + + public uint GetRewChoiceItemsCount() { return _rewChoiceItemsCount; } + public uint GetRewItemsCount() { return _rewItemsCount; } + public uint GetRewCurrencyCount() { return _rewCurrencyCount; } + + #region Fields + public uint Id; + public QuestType Type; + public int Level; + public uint PackageID; + public int MinLevel; + public int QuestSortID; + public uint QuestInfoID; + public uint SuggestedPlayers; + public uint NextQuestInChain { get; set; } + public uint RewardXPDifficulty; + public float RewardXPMultiplier; + public int RewardMoney; + public uint RewardMoneyDifficulty; + public float RewardMoneyMultiplier; + public uint RewardBonusMoney; + public uint[] RewardDisplaySpell = new uint[SharedConst.QuestRewardDisplaySpellCount]; + public uint RewardSpell { get; set; } + public uint RewardHonor; + public uint RewardKillHonor; + public uint RewardArtifactXPDifficulty; + public float RewardArtifactXPMultiplier; + public uint RewardArtifactCategoryID; + public uint SourceItemId { get; set; } + public QuestFlags Flags { get; set; } + public QuestFlagsEx FlagsEx; + public uint[] RewardItemId = new uint[SharedConst.QuestRewardItemCount]; + public uint[] RewardItemCount = new uint[SharedConst.QuestRewardItemCount]; + public uint[] ItemDrop = new uint[SharedConst.QuestItemDropCount]; + public uint[] ItemDropQuantity = new uint[SharedConst.QuestItemDropCount]; + public uint[] RewardChoiceItemId = new uint[SharedConst.QuestRewardChoicesCount]; + public uint[] RewardChoiceItemCount = new uint[SharedConst.QuestRewardChoicesCount]; + public uint[] RewardChoiceItemDisplayId = new uint[SharedConst.QuestRewardChoicesCount]; + public uint POIContinent; + public float POIx; + public float POIy; + public uint POIPriority; + public uint RewardTitleId { get; set; } + public int RewardArenaPoints; + public uint RewardSkillId; + public uint RewardSkillPoints; + public uint QuestGiverPortrait; + public uint QuestTurnInPortrait; + public uint[] RewardFactionId = new uint[SharedConst.QuestRewardReputationsCount]; + public int[] RewardFactionValue = new int[SharedConst.QuestRewardReputationsCount]; + public int[] RewardFactionOverride = new int[SharedConst.QuestRewardReputationsCount]; + public uint[] RewardFactionCapIn = new uint[SharedConst.QuestRewardReputationsCount]; + public uint RewardReputationMask; + public uint[] RewardCurrencyId = new uint[SharedConst.QuestRewardCurrencyCount]; + public uint[] RewardCurrencyCount = new uint[SharedConst.QuestRewardCurrencyCount]; + public uint SoundAccept { get; set; } + public uint SoundTurnIn { get; set; } + public uint AreaGroupID; + public uint LimitTime; + public int AllowableRaces { get; set; } + public uint QuestRewardID; + public int Expansion; + public List Objectives = new List(); + public string LogTitle = ""; + public string LogDescription = ""; + public string QuestDescription = ""; + public string AreaDescription = ""; + public string PortraitGiverText = ""; + public string PortraitGiverName = ""; + public string PortraitTurnInText = ""; + public string PortraitTurnInName = ""; + public string QuestCompletionLog = ""; + + // quest_detais table + public uint[] DetailsEmote = new uint[SharedConst.QuestEmoteCount]; + public uint[] DetailsEmoteDelay = new uint[SharedConst.QuestEmoteCount]; + + // quest_request_items table + public uint EmoteOnComplete; + public uint EmoteOnIncomplete; + public uint EmoteOnCompleteDelay; + public uint EmoteOnIncompleteDelay; + public string RequestItemsText = ""; + + // quest_offer_reward table + public uint[] OfferRewardEmote = new uint[SharedConst.QuestEmoteCount]; + public uint[] OfferRewardEmoteDelay = new uint[SharedConst.QuestEmoteCount]; + public string OfferRewardText = ""; + + // quest_template_addon table (custom data) + public uint MaxLevel; + public uint AllowableClasses { get; set; } + public uint SourceSpellID { get; set; } + public int PrevQuestId; + public int NextQuestId; + public int ExclusiveGroup; + public uint RewardMailTemplateId { get; set; } + public uint RewardMailDelay { get; set; } + public uint RequiredSkillId; + public uint RequiredSkillPoints; + public uint RequiredMinRepFaction; + public int RequiredMinRepValue; + public uint RequiredMaxRepFaction; + public int RequiredMaxRepValue; + public uint SourceItemIdCount; + public uint RewardMailSenderEntry; + public QuestSpecialFlags SpecialFlags; // custom flags, not sniffed/WDB + + public List prevQuests = new List(); + public List prevChainQuests = new List(); + + uint _rewChoiceItemsCount; + uint _rewItemsCount; + uint _rewCurrencyCount; + #endregion + } + + public class QuestStatusData + { + public QuestStatus Status; + public uint Timer; + public int[] ObjectiveData; + } + + public class QuestTemplateLocale + { + public StringArray LogTitle = new StringArray((int)LocaleConstant.Total); + public StringArray LogDescription = new StringArray((int)LocaleConstant.Total); + public StringArray QuestDescription = new StringArray((int)LocaleConstant.Total); + public StringArray AreaDescription = new StringArray((int)LocaleConstant.Total); + public StringArray PortraitGiverText = new StringArray((int)LocaleConstant.Total); + public StringArray PortraitGiverName = new StringArray((int)LocaleConstant.Total); + public StringArray PortraitTurnInText = new StringArray((int)LocaleConstant.Total); + public StringArray PortraitTurnInName = new StringArray((int)LocaleConstant.Total); + public StringArray QuestCompletionLog = new StringArray((int)LocaleConstant.Total); + } + + public class QuestRequestItemsLocale + { + public StringArray CompletionText = new StringArray((int)LocaleConstant.Total); + } + + public class QuestObjectivesLocale + { + public StringArray Description = new StringArray((int)LocaleConstant.Total); + } + + public class QuestOfferRewardLocale + { + public StringArray RewardText = new StringArray((int)LocaleConstant.Total); + } + + public class QuestObjective + { + public uint ID; + public uint QuestID; + public QuestObjectiveType Type; + public sbyte StorageIndex; + public int ObjectID; + public int Amount; + public QuestObjectiveFlags Flags; + public uint Flags2; + public float ProgressBarWeight; + public string Description; + public int[] VisualEffects = new int[0]; + + public bool IsStoringFlag() + { + switch (Type) + { + case QuestObjectiveType.AreaTrigger: + case QuestObjectiveType.WinPetBattleAgainstNpc: + case QuestObjectiveType.DefeatBattlePet: + case QuestObjectiveType.CriteriaTree: + return true; + default: + break; + } + return false; + } + } +} diff --git a/Game/Reputation/ReputationManager.cs b/Game/Reputation/ReputationManager.cs new file mode 100644 index 000000000..7ee02527c --- /dev/null +++ b/Game/Reputation/ReputationManager.cs @@ -0,0 +1,700 @@ +/* + * Copyright (C) 2012-2017 CypherCore + * + * 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, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +using Framework.Constants; +using Framework.Database; +using Game.DataStorage; +using Game.Entities; +using Game.Network.Packets; +using System; +using System.Collections.Generic; + +namespace Game +{ + public class ReputationMgr + { + public ReputationMgr(Player owner) + { + _player = owner; + _visibleFactionCount = 0; + _honoredFactionCount = 0; + _reveredFactionCount = 0; + _exaltedFactionCount = 0; + _sendFactionIncreased = false; + } + + ReputationRank ReputationToRank(int standing) + { + int limit = Reputation_Cap + 1; + for (var rank = ReputationRank.Max - 1; rank >= ReputationRank.Min; --rank) + { + limit -= PointsInRank[(int)rank]; + if (standing >= limit) + return rank; + } + return ReputationRank.Min; + } + + public FactionState GetState(FactionRecord factionEntry) + { + return factionEntry.CanHaveReputation() ? GetState(factionEntry.ReputationIndex) : null; + } + + public bool IsAtWar(uint factionId) + { + var factionEntry = CliDB.FactionStorage.LookupByKey(factionId); + if (factionEntry == null) + return false; + + return IsAtWar(factionEntry); + } + + public bool IsAtWar(FactionRecord factionEntry) + { + if (factionEntry == null) + return false; + + FactionState factionState = GetState(factionEntry); + if (factionState != null) + return (factionState.Flags.HasAnyFlag(FactionFlags.AtWar)); + return false; + } + + public int GetReputation(uint faction_id) + { + var factionEntry = CliDB.FactionStorage.LookupByKey(faction_id); + if (factionEntry == null) + { + Log.outError(LogFilter.Player, "ReputationMgr.GetReputation: Can't get reputation of {0} for unknown faction (faction id) #{1}.", _player.GetName(), faction_id); + return 0; + } + + return GetReputation(factionEntry); + } + + public int GetBaseReputation(FactionRecord factionEntry) + { + if (factionEntry == null) + return 0; + + uint raceMask = _player.getRaceMask(); + uint classMask = _player.getClassMask(); + for (var i = 0; i < 4; i++) + { + if ((Convert.ToBoolean(factionEntry.ReputationRaceMask[i] & raceMask) || + (factionEntry.ReputationRaceMask[i] == 0 && factionEntry.ReputationClassMask[i] != 0)) && + (Convert.ToBoolean(factionEntry.ReputationClassMask[i] & classMask) || + factionEntry.ReputationClassMask[i] == 0)) + return factionEntry.ReputationBase[i]; + } + + // in faction.dbc exist factions with (RepListId >=0, listed in character reputation list) with all BaseRepRaceMask[i] == 0 + return 0; + } + + public int GetReputation(FactionRecord factionEntry) + { + // Faction without recorded reputation. Just ignore. + if (factionEntry == null) + return 0; + + FactionState state = GetState(factionEntry); + if (state != null) + return GetBaseReputation(factionEntry) + state.Standing; + + return 0; + } + + public ReputationRank GetRank(FactionRecord factionEntry) + { + int reputation = GetReputation(factionEntry); + return ReputationToRank(reputation); + } + + ReputationRank GetBaseRank(FactionRecord factionEntry) + { + int reputation = GetBaseReputation(factionEntry); + return ReputationToRank(reputation); + } + + public ReputationRank GetForcedRankIfAny(FactionTemplateRecord factionTemplateEntry) + { + return GetForcedRankIfAny(factionTemplateEntry.Faction); + } + + public void ApplyForceReaction(uint faction_id, ReputationRank rank, bool apply) + { + if (apply) + _forcedReactions[faction_id] = rank; + else + _forcedReactions.Remove(faction_id); + } + + uint GetDefaultStateFlags(FactionRecord factionEntry) + { + if (factionEntry == null) + return 0; + + uint raceMask = _player.getRaceMask(); + uint classMask = _player.getClassMask(); + for (int i = 0; i < 4; i++) + { + if ((Convert.ToBoolean(factionEntry.ReputationRaceMask[i] & raceMask) || + (factionEntry.ReputationRaceMask[i] == 0 && + factionEntry.ReputationClassMask[i] != 0)) && + (Convert.ToBoolean(factionEntry.ReputationClassMask[i] & classMask) || + factionEntry.ReputationClassMask[i] == 0)) + return factionEntry.ReputationFlags[i]; + } + return 0; + } + + public void SendForceReactions() + { + SetForcedReactions setForcedReactions = new SetForcedReactions(); + + foreach (var pair in _forcedReactions) + { + ForcedReaction forcedReaction; + forcedReaction.Faction = (int)pair.Key; + forcedReaction.Reaction = (int)pair.Value; + + setForcedReactions.Reactions.Add(forcedReaction); + } + _player.SendPacket(setForcedReactions); + } + + public void SendState(FactionState faction) + { + SetFactionStanding setFactionStanding = new SetFactionStanding(); + setFactionStanding.ReferAFriendBonus = 0.0f; + setFactionStanding.BonusFromAchievementSystem = 0.0f; + setFactionStanding.Faction.Add(new FactionStandingData((int)faction.ReputationListID, faction.Standing)); + + foreach (var state in _factions.Values) + { + if (state.needSend) + { + state.needSend = false; + if (state.ReputationListID != faction.ReputationListID) + setFactionStanding.Faction.Add(new FactionStandingData((int)state.ReputationListID, state.Standing)); + } + } + + setFactionStanding.ShowVisual = _sendFactionIncreased; + _player.SendPacket(setFactionStanding); + + _sendFactionIncreased = false; // Reset + } + + public void SendInitialReputations() + { + InitializeFactions initFactions = new InitializeFactions(); + + foreach (var pair in _factions) + { + initFactions.FactionFlags[pair.Key] = pair.Value.Flags; + initFactions.FactionStandings[pair.Key] = pair.Value.Standing; + /// @todo faction bonus + pair.Value.needSend = false; + } + + _player.SendPacket(initFactions); + } + + void SendStates() + { + foreach (var faction in _factions) + SendState(faction.Value); + } + + public void SendVisible(FactionState faction, bool visible = true) + { + if (_player.GetSession().PlayerLoading()) + return; + + //make faction visible / not visible in reputation list at client + SetFactionVisible packet = new SetFactionVisible(visible); + packet.FactionIndex = faction.ReputationListID; + _player.SendPacket(packet); + } + + void Initialize() + { + _factions.Clear(); + _visibleFactionCount = 0; + _honoredFactionCount = 0; + _reveredFactionCount = 0; + _exaltedFactionCount = 0; + _sendFactionIncreased = false; + + foreach (var factionEntry in CliDB.FactionStorage.Values) + { + if (factionEntry.CanHaveReputation()) + { + FactionState newFaction = new FactionState(); + newFaction.ID = factionEntry.Id; + newFaction.ReputationListID = (uint)factionEntry.ReputationIndex; + newFaction.Standing = 0; + newFaction.Flags = (FactionFlags)(GetDefaultStateFlags(factionEntry) & 0xFF);//todo fixme for higher value then byte????? + newFaction.needSend = true; + newFaction.needSave = true; + + if (Convert.ToBoolean(newFaction.Flags & FactionFlags.Visible)) + ++_visibleFactionCount; + + UpdateRankCounters(ReputationRank.Hostile, GetBaseRank(factionEntry)); + + _factions[newFaction.ReputationListID] = newFaction; + } + } + } + + public bool ModifyReputation(FactionRecord factionEntry, int standing, bool noSpillover = false) { return SetReputation(factionEntry, standing, true, noSpillover); } + + bool SetReputation(FactionRecord factionEntry, int standing) { return SetReputation(factionEntry, standing, false, false); } + + public bool SetReputation(FactionRecord factionEntry, int standing, bool incremental = true, bool noSpillover = false) + { + Global.ScriptMgr.OnPlayerReputationChange(_player, factionEntry.Id, standing, incremental); + bool res = false; + if (!noSpillover) + { + // if spillover definition exists in DB, override DBC + RepSpilloverTemplate repTemplate = Global.ObjectMgr.GetRepSpillover(factionEntry.Id); + if (repTemplate != null) + { + for (uint i = 0; i < 5; ++i) + { + if (repTemplate.faction[i] != 0) + { + if (_player.GetReputationRank(repTemplate.faction[i]) <= (ReputationRank)repTemplate.faction_rank[i]) + { + // bonuses are already given, so just modify standing by rate + int spilloverRep = (int)(standing * repTemplate.faction_rate[i]); + SetOneFactionReputation(CliDB.FactionStorage.LookupByKey(repTemplate.faction[i]), spilloverRep, incremental); + } + } + } + } + else + { + float spillOverRepOut = standing; + // check for sub-factions that receive spillover + var flist = Global.DB2Mgr.GetFactionTeamList(factionEntry.Id); + // if has no sub-factions, check for factions with same parent + if (flist == null && factionEntry.ParentFactionID != 0 && factionEntry.ParentFactionModOut != 0.0f) + { + spillOverRepOut *= factionEntry.ParentFactionModOut; + FactionRecord parent = CliDB.FactionStorage.LookupByKey(factionEntry.ParentFactionID); + if (parent != null) + { + var parentState = _factions.LookupByKey(parent.ReputationIndex); + // some team factions have own reputation standing, in this case do not spill to other sub-factions + if (parentState != null && parentState.Flags.HasAnyFlag(FactionFlags.Special)) + { + SetOneFactionReputation(parent, (int)spillOverRepOut, incremental); + } + else // spill to "sister" factions + { + flist = Global.DB2Mgr.GetFactionTeamList(factionEntry.ParentFactionID); + } + } + } + if (flist != null) + { + // Spillover to affiliated factions + foreach (var id in flist) + { + FactionRecord factionEntryCalc = CliDB.FactionStorage.LookupByKey(id); + if (factionEntryCalc != null) + { + if (factionEntryCalc == factionEntry || GetRank(factionEntryCalc) > (ReputationRank)factionEntryCalc.ParentFactionCapIn) + continue; + int spilloverRep = (int)(spillOverRepOut * factionEntryCalc.ParentFactionModIn); + if (spilloverRep != 0 || !incremental) + res = SetOneFactionReputation(factionEntryCalc, spilloverRep, incremental); + } + } + } + } + } + + // spillover done, update faction itself + var faction = _factions.LookupByKey(factionEntry.ReputationIndex); + if (faction != null) + { + res = SetOneFactionReputation(factionEntry, standing, incremental); + // only this faction gets reported to client, even if it has no own visible standing + SendState(faction); + } + return res; + } + + public bool SetOneFactionReputation(FactionRecord factionEntry, int standing, bool incremental) + { + var factionState = _factions.LookupByKey((uint)factionEntry.ReputationIndex); + if (factionState != null) + { + int BaseRep = GetBaseReputation(factionEntry); + + if (incremental) + { + // int32 *= float cause one point loss? + standing = (int)(Math.Floor(standing * WorldConfig.GetFloatValue(WorldCfg.RateReputationGain) + 0.5f)); + standing += factionState.Standing + BaseRep; + } + + if (standing > Reputation_Cap) + standing = Reputation_Cap; + else if (standing < Reputation_Bottom) + standing = Reputation_Bottom; + + ReputationRank old_rank = ReputationToRank(factionState.Standing + BaseRep); + ReputationRank new_rank = ReputationToRank(standing); + + factionState.Standing = standing - BaseRep; + factionState.needSend = true; + factionState.needSave = true; + + SetVisible(factionState); + + if (new_rank <= ReputationRank.Hostile) + SetAtWar(factionState, true); + + if (new_rank > old_rank) + _sendFactionIncreased = true; + + UpdateRankCounters(old_rank, new_rank); + + _player.ReputationChanged(factionEntry); + _player.UpdateCriteria(CriteriaTypes.KnownFactions, factionEntry.Id); + _player.UpdateCriteria(CriteriaTypes.GainReputation, factionEntry.Id); + _player.UpdateCriteria(CriteriaTypes.GainExaltedReputation, factionEntry.Id); + _player.UpdateCriteria(CriteriaTypes.GainReveredReputation, factionEntry.Id); + _player.UpdateCriteria(CriteriaTypes.GainHonoredReputation, factionEntry.Id); + + return true; + } + return false; + } + + public void SetVisible(FactionTemplateRecord factionTemplateEntry) + { + if (factionTemplateEntry.Faction == 0) + return; + + var factionEntry = CliDB.FactionStorage.LookupByKey(factionTemplateEntry.Faction); + if (factionEntry.Id != 0) + // Never show factions of the opposing team + if (!Convert.ToBoolean(factionEntry.ReputationRaceMask[1] & _player.getRaceMask()) && factionEntry.ReputationBase[1] == Reputation_Bottom) + SetVisible(factionEntry); + } + + public void SetVisible(FactionRecord factionEntry) + { + if (!factionEntry.CanHaveReputation()) + return; + + var factionState = _factions.LookupByKey((uint)factionEntry.ReputationIndex); + if (factionState == null) + return; + + SetVisible(factionState); + } + + void SetVisible(FactionState faction) + { + // always invisible or hidden faction can't be make visible + // except if faction has FACTION_FLAG_SPECIAL + if (Convert.ToBoolean(faction.Flags & (FactionFlags.InvisibleForced | FactionFlags.Hidden)) && !Convert.ToBoolean(faction.Flags & FactionFlags.Special)) + return; + + // already set + if (Convert.ToBoolean(faction.Flags & FactionFlags.Visible)) + return; + + faction.Flags |= FactionFlags.Visible; + faction.needSend = true; + faction.needSave = true; + + _visibleFactionCount++; + + SendVisible(faction); + } + + public void SetAtWar(uint repListID, bool on) + { + var factionState = _factions.LookupByKey(repListID); + if (factionState == null) + return; + + // always invisible or hidden faction can't change war state + if (Convert.ToBoolean(factionState.Flags & (FactionFlags.InvisibleForced | FactionFlags.Hidden))) + return; + + SetAtWar(factionState, on); + } + + void SetAtWar(FactionState faction, bool atWar) + { + // not allow declare war to own faction + if (atWar && Convert.ToBoolean(faction.Flags & FactionFlags.PeaceForced)) + return; + + // already set + if (((faction.Flags & FactionFlags.AtWar) != 0) == atWar) + return; + + if (atWar) + faction.Flags |= FactionFlags.AtWar; + else + faction.Flags &= ~FactionFlags.AtWar; + + faction.needSend = true; + faction.needSave = true; + } + + public void SetInactive(uint repListID, bool on) + { + var factionState = _factions.LookupByKey(repListID); + if (factionState == null) + return; + + SetInactive(factionState, on); + } + + void SetInactive(FactionState faction, bool inactive) + { + // always invisible or hidden faction can't be inactive + if (inactive && Convert.ToBoolean(faction.Flags & (FactionFlags.InvisibleForced | FactionFlags.Hidden)) || !Convert.ToBoolean(faction.Flags & FactionFlags.Visible)) + return; + + // already set + if (((faction.Flags & FactionFlags.Inactive) != 0) == inactive) + return; + + if (inactive) + faction.Flags |= FactionFlags.Inactive; + else + faction.Flags &= ~FactionFlags.Inactive; + + faction.needSend = true; + faction.needSave = true; + } + + public void LoadFromDB(SQLResult result) + { + // Set initial reputations (so everything is nifty before DB data load) + Initialize(); + + if (!result.IsEmpty()) + { + do + { + var factionEntry = CliDB.FactionStorage.LookupByKey(result.Read(0)); + if (factionEntry != null && factionEntry.CanHaveReputation()) + { + var faction = _factions.LookupByKey((uint)factionEntry.ReputationIndex); + if (faction == null) + continue; + // update standing to current + faction.Standing = result.Read(1); + + // update counters + int BaseRep = GetBaseReputation(factionEntry); + ReputationRank old_rank = ReputationToRank(BaseRep); + ReputationRank new_rank = ReputationToRank(BaseRep + faction.Standing); + UpdateRankCounters(old_rank, new_rank); + + FactionFlags dbFactionFlags = (FactionFlags)result.Read(2); + + if (Convert.ToBoolean(dbFactionFlags & FactionFlags.Visible)) + SetVisible(faction); // have internal checks for forced invisibility + + if (Convert.ToBoolean(dbFactionFlags & FactionFlags.Inactive)) + SetInactive(faction, true); // have internal checks for visibility requirement + + if (Convert.ToBoolean(dbFactionFlags & FactionFlags.AtWar)) // DB at war + SetAtWar(faction, true); // have internal checks for FACTION_FLAG_PEACE_FORCED + else // DB not at war + { + // allow remove if visible (and then not FACTION_FLAG_INVISIBLE_FORCED or FACTION_FLAG_HIDDEN) + if (Convert.ToBoolean(faction.Flags & FactionFlags.Visible)) + SetAtWar(faction, false); // have internal checks for FACTION_FLAG_PEACE_FORCED + } + + // set atWar for hostile + if (GetRank(factionEntry) <= ReputationRank.Hostile) + SetAtWar(faction, true); + + // reset changed flag if values similar to saved in DB + if (faction.Flags == dbFactionFlags) + { + faction.needSend = false; + faction.needSave = false; + } + } + } while (result.NextRow()); + } + } + + public void SaveToDB(SQLTransaction trans) + { + foreach (var factionState in _factions.Values) + { + if (factionState.needSave) + { + PreparedStatement stmt = DB.Characters.GetPreparedStatement(CharStatements.DEL_CHAR_REPUTATION_BY_FACTION); + stmt.AddValue(0, _player.GetGUID().GetCounter()); + stmt.AddValue(1, factionState.ID); + trans.Append(stmt); + + stmt = DB.Characters.GetPreparedStatement(CharStatements.INS_CHAR_REPUTATION_BY_FACTION); + stmt.AddValue(0, _player.GetGUID().GetCounter()); + stmt.AddValue(1, factionState.ID); + stmt.AddValue(2, factionState.Standing); + stmt.AddValue(3, factionState.Flags); + trans.Append(stmt); + + factionState.needSave = false; + } + } + } + + void UpdateRankCounters(ReputationRank old_rank, ReputationRank new_rank) + { + if (old_rank >= ReputationRank.Exalted) + --_exaltedFactionCount; + if (old_rank >= ReputationRank.Revered) + --_reveredFactionCount; + if (old_rank >= ReputationRank.Honored) + --_honoredFactionCount; + + if (new_rank >= ReputationRank.Exalted) + ++_exaltedFactionCount; + if (new_rank >= ReputationRank.Revered) + ++_reveredFactionCount; + if (new_rank >= ReputationRank.Honored) + ++_honoredFactionCount; + } + + public byte GetVisibleFactionCount() { return _visibleFactionCount; } + + public byte GetHonoredFactionCount() { return _honoredFactionCount; } + + public byte GetReveredFactionCount() { return _reveredFactionCount; } + + public byte GetExaltedFactionCount() { return _exaltedFactionCount; } + + public SortedDictionary GetStateList() { return _factions; } + + public FactionState GetState(int id) + { + return _factions.LookupByKey((uint)id); + } + + public uint GetReputationRankStrIndex(FactionRecord factionEntry) + { + return (uint)ReputationRankStrIndex[(int)GetRank(factionEntry)]; + } + + public ReputationRank GetForcedRankIfAny(uint factionId) + { + var forced = _forcedReactions.ContainsKey(factionId); + return forced ? _forcedReactions[factionId] : ReputationRank.None; + } + + // this allows calculating base reputations to offline players, just by race and class + public static int GetBaseReputationOf(FactionRecord factionEntry, Race race, Class playerClass) + { + if (factionEntry == null) + return 0; + + uint raceMask = (1u << ((int)race - 1)); + uint classMask = (1u << ((int)playerClass - 1)); + + for (int i = 0; i < 4; i++) + { + if ((factionEntry.ReputationClassMask[i] == 0 || factionEntry.ReputationClassMask[i].HasAnyFlag((ushort)classMask)) + && (factionEntry.ReputationRaceMask[i] == 0 || factionEntry.ReputationRaceMask[i].HasAnyFlag(raceMask))) + return factionEntry.ReputationBase[i]; + } + + return 0; + } + + #region Fields + Player _player; + byte _visibleFactionCount; + byte _honoredFactionCount; + byte _reveredFactionCount; + byte _exaltedFactionCount; + bool _sendFactionIncreased; //! Play visual effect on next SMSG_SET_FACTION_STANDING sent + #endregion + + public static int[] PointsInRank = { 36000, 3000, 3000, 3000, 6000, 12000, 21000, 1000 }; + public static CypherStrings[] ReputationRankStrIndex = + { + CypherStrings.RepHated, CypherStrings.RepHostile, CypherStrings.RepUnfriendly, CypherStrings.RepNeutral, + CypherStrings.RepFriendly, CypherStrings.RepHonored, CypherStrings.RepRevered, CypherStrings.RepExalted + }; + const int Reputation_Cap = 42999; + const int Reputation_Bottom = -42000; + SortedDictionary _factions = new SortedDictionary(); + Dictionary _forcedReactions = new Dictionary(); + + + } + public class FactionState + { + public uint ID; + public uint ReputationListID; + public int Standing; + public FactionFlags Flags; + public bool needSend; + public bool needSave; + } + public class RepRewardRate + { + public float questRate; // We allow rate = 0.0 in database. For this case, it means that + public float questDailyRate; + public float questWeeklyRate; + public float questMonthlyRate; + public float questRepeatableRate; + public float creatureRate; // no reputation are given at all for this faction/rate type. + public float spellRate; + } + public class ReputationOnKillEntry + { + public uint RepFaction1; + public uint RepFaction2; + public uint ReputationMaxCap1; + public int RepValue1; + public uint ReputationMaxCap2; + public int RepValue2; + public bool IsTeamAward1; + public bool IsTeamAward2; + public bool TeamDependent; + } + public class RepSpilloverTemplate + { + public uint[] faction = new uint[5]; + public float[] faction_rate = new float[5]; + public uint[] faction_rank = new uint[5]; + } +} diff --git a/Game/Scenarios/InstanceScenario.cs b/Game/Scenarios/InstanceScenario.cs new file mode 100644 index 000000000..e5708f2b8 --- /dev/null +++ b/Game/Scenarios/InstanceScenario.cs @@ -0,0 +1,183 @@ +/* + * Copyright (C) 2012-2017 CypherCore + * + * 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, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +using Framework.Constants; +using Framework.Database; +using Game.Achievements; +using Game.DataStorage; +using Game.Maps; +using Game.Network; +using System; +using System.Collections.Generic; + +namespace Game.Scenarios +{ + public class InstanceScenario : Scenario + { + public InstanceScenario(Map map, ScenarioData scenarioData) : base(scenarioData) + { + _map = map; + + //ASSERT(_map); + LoadInstanceData(_map.GetInstanceId()); + + var players = map.GetPlayers(); + foreach (var player in players) + SendScenarioState(player); + } + + public void SaveToDB() + { + if (_criteriaProgress.Empty()) + return; + + DifficultyRecord difficultyEntry = CliDB.DifficultyStorage.LookupByKey(_map.GetDifficultyID()); + if (difficultyEntry == null || difficultyEntry.Flags.HasAnyFlag(DifficultyFlags.ChallengeMode)) // Map should have some sort of "CanSave" boolean that returns whether or not the map is savable. (Challenge modes cannot be saved for example) + return; + + uint id = _map.GetInstanceId(); + if (id == 0) + { + Log.outDebug(LogFilter.Scenario, "Scenario.SaveToDB: Can not save scenario progress without an instance save. Map.GetInstanceId() did not return an instance save."); + return; + } + + SQLTransaction trans = new SQLTransaction(); + foreach (var iter in _criteriaProgress) + { + if (!iter.Value.Changed) + continue; + + Criteria criteria = Global.CriteriaMgr.GetCriteria(iter.Key); + switch (criteria.Entry.Type) + { + // Blizzard only appears to store creature kills + case CriteriaTypes.KillCreature: + break; + default: + continue; + } + + if (iter.Value.Counter != 0) + { + PreparedStatement stmt = DB.Characters.GetPreparedStatement(CharStatements.DEL_SCENARIO_INSTANCE_CRITERIA); + stmt.AddValue(0, id); + stmt.AddValue(1, iter.Key); + trans.Append(stmt); + + stmt = DB.Characters.GetPreparedStatement(CharStatements.INS_SCENARIO_INSTANCE_CRITERIA); + stmt.AddValue(0, id); + stmt.AddValue(1, iter.Key); + stmt.AddValue(2, iter.Value.Counter); + stmt.AddValue(3, (uint)iter.Value.Date); + trans.Append(stmt); + } + + iter.Value.Changed = false; + } + + DB.Characters.CommitTransaction(trans); + } + + void LoadInstanceData(uint instanceId) + { + PreparedStatement stmt = DB.Characters.GetPreparedStatement(CharStatements.SEL_SCENARIO_INSTANCE_CRITERIA_FOR_INSTANCE); + stmt.AddValue(0, instanceId); + + SQLResult result = DB.Characters.Query(stmt); + if (!result.IsEmpty()) + { + SQLTransaction trans = new SQLTransaction(); + long now = Time.UnixTime; + + List criteriaTrees = new List(); + do + { + uint id = result.Read(0); + ulong counter = result.Read(1); + long date = result.Read(2); + + Criteria criteria = Global.CriteriaMgr.GetCriteria(id); + if (criteria == null) + { + // Removing non-existing criteria data for all instances + Log.outError(LogFilter.Scenario, "Removing scenario criteria {0} data from the table `instance_scenario_progress`.", id); + + stmt = DB.Characters.GetPreparedStatement(CharStatements.DEL_SCENARIO_INSTANCE_CRITERIA); + stmt.AddValue(0, instanceId); + stmt.AddValue(1, id); + trans.Append(stmt); + continue; + } + + if (criteria.Entry.StartTimer != 0 && (date + criteria.Entry.StartTimer) < now) + continue; + + switch (criteria.Entry.Type) + { + // Blizzard appears to only stores creatures killed progress for unknown reasons. Either technical shortcoming or intentional + case CriteriaTypes.KillCreature: + break; + default: + continue; + } + + SetCriteriaProgress(criteria, counter, null, ProgressType.Set); + + List trees = Global.CriteriaMgr.GetCriteriaTreesByCriteria(criteria.ID); + if (trees != null) + { + foreach (CriteriaTree tree in trees) + criteriaTrees.Add(tree); + } + } + while (result.NextRow()); + + DB.Characters.CommitTransaction(trans); + + foreach (CriteriaTree tree in criteriaTrees) + { + ScenarioStepRecord step = tree.ScenarioStep; + if (step == null) + continue; + + if (IsCompletedCriteriaTree(tree)) + SetStepState(step, ScenarioStepState.Done); + } + } + } + + public override string GetOwnerInfo() + { + return string.Format("Instance ID {0}", _map.GetInstanceId()); + } + + public override void SendPacket(ServerPacket data) + { + //Hack todo fix me + if (_map == null) + { + return; + } + + _map.SendToPlayers(data); + } + + Map _map; + Dictionary> _stepCriteriaProgress = new Dictionary>(); + } +} diff --git a/Game/Scenarios/Scenario.cs b/Game/Scenarios/Scenario.cs new file mode 100644 index 000000000..2f6fe3015 --- /dev/null +++ b/Game/Scenarios/Scenario.cs @@ -0,0 +1,368 @@ +/* + * Copyright (C) 2012-2017 CypherCore + * + * 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, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +using Framework.Constants; +using Game.Achievements; +using Game.DataStorage; +using Game.Entities; +using Game.Network; +using Game.Network.Packets; +using System.Collections.Generic; + +namespace Game.Scenarios +{ + public class Scenario : CriteriaHandler + { + public Scenario(ScenarioData scenarioData) + { + _data = scenarioData; + _currentstep = null; + + //ASSERT(_data); + + foreach (var step in _data.Steps) + SetStepState(step.Value, ScenarioStepState.NotStarted); + + ScenarioStepRecord firstStep = GetFirstStep(); + if (firstStep != null) + SetStep(firstStep); + else + Log.outError(LogFilter.Scenario, "Scenario.Scenario: Could not launch Scenario (id: {0}), found no valid scenario step", _data.Entry.Id); + } + + ~Scenario() + { + foreach (ObjectGuid guid in _players) + { + Player player = Global.ObjAccessor.FindPlayer(guid); + if (player) + SendBootPlayer(player); + } + + _players.Clear(); + } + + public override void Reset() + { + base.Reset(); + SetStep(GetFirstStep()); + } + + public virtual void CompleteStep(ScenarioStepRecord step) + { + Quest quest = Global.ObjectMgr.GetQuestTemplate(step.QuestRewardID); + if (quest != null) + { + foreach (ObjectGuid guid in _players) + { + Player player = Global.ObjAccessor.FindPlayer(guid); + if (player) + player.RewardQuest(quest, 0, null, false); + } + } + + if (step.IsBonusObjective()) + return; + + ScenarioStepRecord newStep = null; + foreach (var _step in _data.Steps.Values) + { + if (_step.IsBonusObjective()) + continue; + + if (GetStepState(_step) == ScenarioStepState.Done) + continue; + + if (newStep == null || _step.Step < newStep.Step) + newStep = _step; + } + + SetStep(newStep); + if (IsComplete()) + CompleteScenario(); + else + Log.outError(LogFilter.Scenario, "Scenario.CompleteStep: Scenario (id: {0}, step: {1}) was completed, but could not determine new step, or validate scenario completion.", step.ScenarioID, step.Id); + } + + public virtual void CompleteScenario() + { + SendPacket(new ScenarioCompleted(_data.Entry.Id)); + } + + void SetStep(ScenarioStepRecord step) + { + _currentstep = step; + if (step != null) + SetStepState(step, ScenarioStepState.InProgress); + + ScenarioState scenarioState = new ScenarioState(); + BuildScenarioState(scenarioState); + SendPacket(scenarioState); + } + + public virtual void OnPlayerEnter(Player player) + { + _players.Add(player.GetGUID()); + SendScenarioState(player); + } + + public virtual void OnPlayerExit(Player player) + { + _players.Remove(player.GetGUID()); + SendBootPlayer(player); + } + + bool IsComplete() + { + foreach (var step in _data.Steps.Values) + { + if (step.IsBonusObjective()) + continue; + + if (GetStepState(step) != ScenarioStepState.Done) + return false; + } + + return true; + } + + ScenarioStepState GetStepState(ScenarioStepRecord step) + { + if (!_stepStates.ContainsKey(step)) + return ScenarioStepState.Invalid; + + return _stepStates[step]; + } + + public override void SendCriteriaUpdate(Criteria criteria, CriteriaProgress progress, uint timeElapsed, bool timedCompleted) + { + ScenarioProgressUpdate progressUpdate = new ScenarioProgressUpdate(); + progressUpdate.CriteriaProgress.Id = criteria.ID; + progressUpdate.CriteriaProgress.Quantity = progress.Counter; + progressUpdate.CriteriaProgress.Player = progress.PlayerGUID; + progressUpdate.CriteriaProgress.Date = progress.Date; + if (criteria.Entry.StartTimer != 0) + progressUpdate.CriteriaProgress.Flags = timedCompleted ? 1 : 0u; + + progressUpdate.CriteriaProgress.TimeFromStart = timeElapsed; + progressUpdate.CriteriaProgress.TimeFromCreate = 0; + + SendPacket(progressUpdate); + } + + public override bool CanUpdateCriteriaTree(Criteria criteria, CriteriaTree tree, Player referencePlayer) + { + ScenarioStepRecord step = tree.ScenarioStep; + if (step == null) + return false; + + if (step.ScenarioID != _data.Entry.Id) + return false; + + ScenarioStepRecord currentStep = GetStep(); + if (currentStep == null) + return false; + + if (step.IsBonusObjective()) + return true; + + return currentStep == step; + } + + public override bool CanCompleteCriteriaTree(CriteriaTree tree) + { + ScenarioStepRecord step = tree.ScenarioStep; + if (step == null) + return false; + + if (step.ScenarioID != _data.Entry.Id) + return false; + + if (step.IsBonusObjective()) + return !IsComplete(); + + if (step != GetStep()) + return false; + + return true; + } + + public override void CompletedCriteriaTree(CriteriaTree tree, Player referencePlayer) + { + ScenarioStepRecord step = tree.ScenarioStep; + if (step == null) + return; + + if (!step.IsBonusObjective() && step != GetStep()) + return; + + if (GetStepState(step) == ScenarioStepState.Done) + return; + + SetStepState(step, ScenarioStepState.Done); + CompleteStep(step); + } + + public override void SendPacket(ServerPacket data) + { + foreach (ObjectGuid guid in _players) + { + Player player = Global.ObjAccessor.FindPlayer(guid); + if (player) + player.SendPacket(data); + } + } + + void BuildScenarioState(ScenarioState scenarioState) + { + scenarioState.ScenarioID = (int)_data.Entry.Id; + ScenarioStepRecord step = GetStep(); + if (step != null) + scenarioState.CurrentStep = (int)step.Id; + scenarioState.CriteriaProgress = GetCriteriasProgress(); + scenarioState.BonusObjectives = GetBonusObjectivesData(); + // Don't know exactly what this is for, but seems to contain list of scenario steps that we're either on or that are completed + foreach (var state in _stepStates) + { + if (state.Key.IsBonusObjective()) + continue; + + switch (state.Value) + { + case ScenarioStepState.InProgress: + case ScenarioStepState.Done: + break; + case ScenarioStepState.NotStarted: + default: + continue; + } + + scenarioState.PickedSteps.Add(state.Key.Id); + } + scenarioState.ScenarioComplete = IsComplete(); + } + + ScenarioStepRecord GetFirstStep() + { + // Do it like this because we don't know what order they're in inside the container. + ScenarioStepRecord firstStep = null; + foreach (var scenarioStep in _data.Steps.Values) + { + if (scenarioStep.IsBonusObjective()) + continue; + + if (firstStep == null || scenarioStep.Step < firstStep.Step) + firstStep = scenarioStep; + } + + return firstStep; + } + + public void SendScenarioState(Player player) + { + ScenarioState scenarioState = new ScenarioState(); + BuildScenarioState(scenarioState); + player.SendPacket(scenarioState); + } + + List GetBonusObjectivesData() + { + List bonusObjectivesData = new List(); + foreach (var step in _data.Steps.Values) + { + if (!step.IsBonusObjective()) + continue; + + if (Global.CriteriaMgr.GetCriteriaTree(step.CriteriaTreeID) != null) + { + BonusObjectiveData bonusObjectiveData; + bonusObjectiveData.BonusObjectiveID = (int)step.Id; + bonusObjectiveData.ObjectiveComplete = GetStepState(step) == ScenarioStepState.Done; + bonusObjectivesData.Add(bonusObjectiveData); + } + } + + return bonusObjectivesData; + } + + List GetCriteriasProgress() + { + List criteriasProgress = new List(); + + if (!_criteriaProgress.Empty()) + { + foreach (var pair in _criteriaProgress) + { + CriteriaProgressPkt criteriaProgress = new CriteriaProgressPkt(); + criteriaProgress.Id = pair.Key; + criteriaProgress.Quantity = pair.Value.Counter; + criteriaProgress.Date = pair.Value.Date; + criteriaProgress.Player = pair.Value.PlayerGUID; + criteriasProgress.Add(criteriaProgress); + } + } + + return criteriasProgress; + } + + public override List GetCriteriaByType(CriteriaTypes type) + { + return Global.CriteriaMgr.GetScenarioCriteriaByType(type); + } + + void SendBootPlayer(Player player) + { + ScenarioBoot scenarioBoot = new ScenarioBoot(); + scenarioBoot.ScenarioID = (int)_data.Entry.Id; + player.SendPacket(scenarioBoot); + } + + public virtual void Update(uint diff) { } + + public void SetStepState(ScenarioStepRecord step, ScenarioStepState state) { _stepStates[step] = state; } + ScenarioStepRecord GetStep() + { + return _currentstep; + } + + public override void SendCriteriaProgressRemoved(uint criteriaId) { } + public override void AfterCriteriaTreeUpdate(CriteriaTree tree, Player referencePlayer) { } + public override void SendAllData(Player receiver) { } + + List _players = new List(); + ScenarioData _data; + ScenarioStepRecord _currentstep; + Dictionary _stepStates = new Dictionary(); + } + + public enum ScenarioStepState + { + Invalid = 0, + NotStarted = 1, + InProgress = 2, + Done = 3 + } + + enum ScenarioType + { + Scenario = 0, + ChallengeMode = 1, + Solo = 2, + Dungeon = 10, + } + +} diff --git a/Game/Scenarios/ScenarioManager.cs b/Game/Scenarios/ScenarioManager.cs new file mode 100644 index 000000000..c469a71ae --- /dev/null +++ b/Game/Scenarios/ScenarioManager.cs @@ -0,0 +1,266 @@ +/* + * Copyright (C) 2012-2017 CypherCore + * + * 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, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +using Framework.Constants; +using Framework.Database; +using Framework.GameMath; +using Game.Achievements; +using Game.DataStorage; +using Game.Maps; +using System; +using System.Collections.Generic; +namespace Game.Scenarios +{ + public class ScenarioManager : Singleton + { + ScenarioManager() { } + + public InstanceScenario CreateInstanceScenario(Map map, int team) + { + var dbData = _scenarioDBData.LookupByKey(Tuple.Create(map.GetId(), (byte)map.GetDifficultyID())); + // No scenario registered for this map and difficulty in the database + if (dbData == null) + return null; + + uint scenarioID = 0; + switch (team) + { + case TeamId.Alliance: + scenarioID = dbData.Scenario_A; + break; + case TeamId.Horde: + scenarioID = dbData.Scenario_H; + break; + default: + break; + } + + var scenarioData = _scenarioData.LookupByKey(scenarioID); + if (scenarioData == null) + { + Log.outError(LogFilter.Scenario, "Table `scenarios` contained data linking scenario (Id: {0}) to map (Id: {1}), difficulty (Id: {2}) but no scenario data was found related to that scenario Id.", + scenarioID, map.GetId(), map.GetDifficultyID()); + return null; + } + + return new InstanceScenario(map, scenarioData); + } + + public void LoadDBData() + { + _scenarioDBData.Clear(); + + uint oldMSTime = Time.GetMSTime(); + + SQLResult result = DB.World.Query("SELECT map, difficulty, scenario_A, scenario_H FROM scenarios"); + if (result.IsEmpty()) + { + Log.outInfo(LogFilter.ServerLoading, "Loaded 0 scenarios. DB table `scenarios` is empty!"); + return; + } + + do + { + uint mapId = result.Read(0); + byte difficulty = result.Read(1); + + uint scenarioAllianceId = result.Read(2); + if (scenarioAllianceId > 0 && !_scenarioData.ContainsKey(scenarioAllianceId)) + { + Log.outError(LogFilter.Sql, "ScenarioMgr.LoadDBData: DB Table `scenarios`, column scenario_A contained an invalid scenario (Id: {0})!", scenarioAllianceId); + continue; + } + + uint scenarioHordeId = result.Read(3); + if (scenarioHordeId > 0 && !_scenarioData.ContainsKey(scenarioHordeId)) + { + Log.outError(LogFilter.Sql, "ScenarioMgr.LoadDBData: DB Table `scenarios`, column scenario_H contained an invalid scenario (Id: {0})!", scenarioHordeId); + continue; + } + + if (scenarioHordeId == 0) + scenarioHordeId = scenarioAllianceId; + + ScenarioDBData data = new ScenarioDBData(); + data.MapID = mapId; + data.DifficultyID = difficulty; + data.Scenario_A = scenarioAllianceId; + data.Scenario_H = scenarioHordeId; + _scenarioDBData[Tuple.Create(mapId, difficulty)] = data; + } + while (result.NextRow()); + + Log.outInfo(LogFilter.ServerLoading, "Loaded {0} instance scenario entries in {1} ms", _scenarioDBData.Count, Time.GetMSTimeDiffToNow(oldMSTime)); + } + + public void LoadDB2Data() + { + _scenarioData.Clear(); + + Dictionary> scenarioSteps = new Dictionary>(); + uint deepestCriteriaTreeSize = 0; + + foreach (ScenarioStepRecord step in CliDB.ScenarioStepStorage.Values) + { + if (!scenarioSteps.ContainsKey(step.ScenarioID)) + scenarioSteps[step.ScenarioID] = new Dictionary(); + + scenarioSteps[step.ScenarioID][step.Step] = step; + CriteriaTree tree = Global.CriteriaMgr.GetCriteriaTree(step.CriteriaTreeID); + if (tree != null) + { + uint criteriaTreeSize = 0; + CriteriaManager.WalkCriteriaTree(tree, treeFunc => + { + ++criteriaTreeSize; + }); + deepestCriteriaTreeSize = Math.Max(deepestCriteriaTreeSize, criteriaTreeSize); + } + } + + //ASSERT(deepestCriteriaTreeSize < MAX_ALLOWED_SCENARIO_POI_QUERY_SIZE, "MAX_ALLOWED_SCENARIO_POI_QUERY_SIZE must be at least {0}", deepestCriteriaTreeSize + 1); + + foreach (ScenarioRecord scenario in CliDB.ScenarioStorage.Values) + { + ScenarioData data = new ScenarioData(); + data.Entry = scenario; + data.Steps = scenarioSteps.LookupByKey(scenario.Id); + _scenarioData[scenario.Id] = data; + } + } + + public void LoadScenarioPOI() + { + uint oldMSTime = Time.GetMSTime(); + + _scenarioPOIStore.Clear(); // need for reload case + + uint count = 0; + + // 0 1 2 6 7 8 9 10 11 12 + SQLResult result = DB.World.Query("SELECT CriteriaTreeID, BlobIndex, Idx1, MapID, WorldMapAreaId, Floor, Priority, Flags, WorldEffectID, PlayerConditionID FROM scenario_poi ORDER BY CriteriaTreeID, Idx1"); + if (result.IsEmpty()) + { + Log.outInfo(LogFilter.ServerLoading, "Loaded 0 scenario POI definitions. DB table `scenario_poi` is empty."); + return; + } + + // 0 1 2 3 + SQLResult points = DB.World.Query("SELECT CriteriaTreeID, Idx1, X, Y FROM scenario_poi_points ORDER BY CriteriaTreeID DESC, Idx1, Idx2"); + + List[][] POIs = new List[0][]; + + if (!points.IsEmpty()) + { + // The first result should have the highest criteriaTreeId + uint criteriaTreeIdMax = result.Read(0); + POIs = new List[criteriaTreeIdMax + 1][]; + + do + { + int CriteriaTreeID = points.Read(0); + int Idx1 = points.Read(1); + int X = points.Read(2); + int Y = points.Read(3); + + if (POIs[CriteriaTreeID].Length <= Idx1 + 1) + POIs[CriteriaTreeID] = new List[Idx1 + 10]; + + POIs[CriteriaTreeID][Idx1].Add(new Vector2(X, Y)); + } while (points.NextRow()); + } + + do + { + int CriteriaTreeID = result.Read(0); + int BlobIndex = result.Read(1); + int Idx1 = result.Read(2); + int MapID = result.Read(3); + int WorldMapAreaId = result.Read(4); + int Floor = result.Read(5); + int Priority = result.Read(6); + int Flags = result.Read(7); + int WorldEffectID = result.Read(8); + int PlayerConditionID = result.Read(9); + + if (Global.CriteriaMgr.GetCriteriaTree((uint)CriteriaTreeID) == null) + Log.outError(LogFilter.Sql, "`scenario_poi` CriteriaTreeID ({0}) Idx1 ({1}) does not correspond to a valid criteria tree", CriteriaTreeID, Idx1); + + if (CriteriaTreeID < POIs.Length && Idx1 < POIs[CriteriaTreeID].Length) + _scenarioPOIStore.Add((uint)CriteriaTreeID, new ScenarioPOI(BlobIndex, MapID, WorldMapAreaId, Floor, Priority, Flags, WorldEffectID, PlayerConditionID, POIs[CriteriaTreeID][Idx1])); + else + Log.outError(LogFilter.ServerLoading, "Table scenario_poi references unknown scenario poi points for criteria tree id {0} POI id {1}", CriteriaTreeID, BlobIndex); + + ++count; + } while (result.NextRow()); + + Log.outInfo(LogFilter.ServerLoading, "Loaded {0} scenario POI definitions in {1} ms", count, Time.GetMSTimeDiffToNow(oldMSTime)); + } + + public List GetScenarioPOIs(uint CriteriaTreeID) + { + if (!_scenarioPOIStore.ContainsKey(CriteriaTreeID)) + return null; + + return _scenarioPOIStore[CriteriaTreeID]; + } + + Dictionary _scenarioData = new Dictionary(); + MultiMap _scenarioPOIStore = new MultiMap(); + Dictionary, ScenarioDBData> _scenarioDBData = new Dictionary, ScenarioDBData>(); + } + + public class ScenarioData + { + public ScenarioRecord Entry; + public Dictionary Steps = new Dictionary(); + } + + class ScenarioDBData + { + public uint MapID; + public byte DifficultyID; + public uint Scenario_A; + public uint Scenario_H; + } + + public class ScenarioPOI + { + public ScenarioPOI(int blobIndex, int mapID, int worldMapAreaID, int floor, int priority, int flags, int worldEffectID, int playerConditionID, List points) + { + BlobIndex = blobIndex; + MapID = mapID; + WorldMapAreaID = worldMapAreaID; + Floor = floor; + Priority = priority; + Flags = flags; + WorldEffectID = worldEffectID; + PlayerConditionID = playerConditionID; + Points = points; + } + + public int BlobIndex; + public int MapID; + public int WorldMapAreaID; + public int Floor; + public int Priority; + public int Flags; + public int WorldEffectID; + public int PlayerConditionID; + public List Points = new List(); + } +} diff --git a/Game/Scripting/CoreScripts.cs b/Game/Scripting/CoreScripts.cs new file mode 100644 index 000000000..52bd7d844 --- /dev/null +++ b/Game/Scripting/CoreScripts.cs @@ -0,0 +1,707 @@ +/* + * Copyright (C) 2012-2017 CypherCore + * + * 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, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +using Framework.Constants; +using Game.AI; +using Game.BattleGrounds; +using Game.Chat; +using Game.Conditions; +using Game.DataStorage; +using Game.Entities; +using Game.Groups; +using Game.Guilds; +using Game.Maps; +using Game.PvP; +using Game.Spells; +using System; +using System.Collections.Generic; + +namespace Game.Scripting +{ + public class ScriptObject + { + public ScriptObject(string name) + { + _name = name; + } + + public string GetName() { return _name; } + + // Do not override this in scripts; it should be overridden by the various script type classes. It indicates + // whether or not this script type must be assigned in the database. + public virtual bool IsDatabaseBound() { return false; } + + public static T GetInstanceAI(WorldObject obj, string scriptName) where T : class + { + InstanceMap instance = obj.GetMap().ToInstanceMap(); + if (instance != null && instance.GetInstanceScript() != null) + if (instance.GetScriptId() == Global.ObjectMgr.GetScriptId(scriptName)) + return (T)Activator.CreateInstance(typeof(T), new object[] { obj }); + + return null; + } + public static T GetInstanceAI(WorldObject obj) where T : class + { + InstanceMap instance = obj.GetMap().ToInstanceMap(); + if (instance != null && instance.GetInstanceScript() != null) + return (T)Activator.CreateInstance(typeof(T), obj); + + return null; + } + + string _name; + } + + public class SpellScriptLoader : ScriptObject + { + public SpellScriptLoader(string name) : base(name) + { + Global.ScriptMgr.AddScript(this); + } + + public override bool IsDatabaseBound() { return true; } + + // Should return a fully valid SpellScript. + public virtual SpellScript GetSpellScript() { return null; } + + // Should return a fully valid AuraScript. + public virtual AuraScript GetAuraScript() { return null; } + } + + class WorldScript : ScriptObject + { + protected WorldScript(string name) : base(name) + { + Global.ScriptMgr.AddScript(this); + } + + // Called when the open/closed state of the world changes. + public virtual void OnOpenStateChange(bool open) { } + + // Called after the world configuration is (re)loaded. + public virtual void OnConfigLoad(bool reload) { } + + // Called before the message of the day is changed. + public virtual void OnMotdChange(string newMotd) { } + + // Called when a world shutdown is initiated. + public virtual void OnShutdownInitiate(ShutdownExitCode code, ShutdownMask mask) { } + + // Called when a world shutdown is cancelled. + public virtual void OnShutdownCancel() { } + + // Called on every world tick (don't execute too heavy code here). + public virtual void OnUpdate(uint diff) { } + + // Called when the world is started. + public virtual void OnStartup() { } + + // Called when the world is actually shut down. + public virtual void OnShutdown() { } + } + + public class FormulaScript : ScriptObject + { + public FormulaScript(string name) : base(name) { } + + // Called after calculating honor. + public virtual void OnHonorCalculation(float honor, uint level, float multiplier) { } + + // Called after gray level calculation. + public virtual void OnGrayLevelCalculation(uint grayLevel, uint playerLevel) { } + + // Called after calculating experience color. + public virtual void OnColorCodeCalculation(XPColorChar color, uint playerLevel, uint mobLevel) { } + + // Called after calculating zero difference. + public virtual void OnZeroDifferenceCalculation(uint diff, uint playerLevel) { } + + // Called after calculating base experience gain. + public virtual void OnBaseGainCalculation(uint gain, uint playerLevel, uint mobLevel) { } + + // Called after calculating experience gain. + public virtual void OnGainCalculation(uint gain, Player player, Unit unit) { } + + // Called when calculating the experience rate for group experience. + public virtual void OnGroupRateCalculation(float rate, uint count, bool isRaid) { } + } + + public class MapScript : ScriptObject where T : Map + { + public MapScript(string name, uint mapId) : base(name) + { + _mapEntry = CliDB.MapStorage.LookupByKey(mapId); + + if (_mapEntry == null) + Log.outError(LogFilter.Scripts, "Invalid MapScript for {0}; no such map ID.", mapId); + } + + // Gets the MapEntry structure associated with this script. Can return NULL. + public MapRecord GetEntry() { return _mapEntry; } + + // Called when the map is created. + public virtual void OnCreate(T map) { } + + // Called just before the map is destroyed. + public virtual void OnDestroy(T map) { } + + // Called when a grid map is loaded. + public virtual void OnLoadGridMap(T map, GridMap gmap, uint gx, uint gy) { } + + // Called when a grid map is unloaded. + public virtual void OnUnloadGridMap(T map, GridMap gmap, uint gx, uint gy) { } + + // Called when a player enters the map. + public virtual void OnPlayerEnter(T map, Player player) { } + + // Called when a player leaves the map. + public virtual void OnPlayerLeave(T map, Player player) { } + + public virtual void OnUpdate(T obj, uint diff) { } + + MapRecord _mapEntry; + } + + public class WorldMapScript : MapScript + { + public WorldMapScript(string name, uint mapId) : base(name, mapId) + { + if (GetEntry() != null && !GetEntry().IsWorldMap()) + Log.outError(LogFilter.Scripts, "WorldMapScript for map {0} is invalid.", mapId); + + Global.ScriptMgr.AddScript(this); + } + } + + public class InstanceMapScript : MapScript + { + public InstanceMapScript(string name, uint mapId) : base(name, mapId) + { + if (GetEntry() != null && !GetEntry().IsDungeon()) + Log.outError(LogFilter.Scripts, "InstanceMapScript for map {0} is invalid.", mapId); + + Global.ScriptMgr.AddScript(this); + } + + public override bool IsDatabaseBound() { return true; } + + // Gets an InstanceScript object for this instance. + public virtual InstanceScript GetInstanceScript(InstanceMap map) { return null; } + } + + public class BattlegroundMapScript : MapScript + { + public BattlegroundMapScript(string name, uint mapId) : base(name, mapId) + { + if (GetEntry() != null && GetEntry().IsBattleground()) + Log.outError(LogFilter.Scripts, "BattlegroundMapScript for map {0} is invalid.", mapId); + + Global.ScriptMgr.AddScript(this); + } + } + + public class ItemScript : ScriptObject + { + public ItemScript(string name) : base(name) + { + Global.ScriptMgr.AddScript(this); + } + + public override bool IsDatabaseBound() { return true; } + + // Called when a dummy spell effect is triggered on the item. + public virtual bool OnDummyEffect(Unit caster, uint spellId, uint effIndex, Item target) { return false; } + + // Called when a player accepts a quest from the item. + public virtual bool OnQuestAccept(Player player, Item item, Quest quest) { return false; } + + // Called when a player uses the item. + public virtual bool OnUse(Player player, Item item, SpellCastTargets targets, ObjectGuid castId) { return false; } + + // Called when the item expires (is destroyed). + public virtual bool OnExpire(Player player, ItemTemplate proto) { return false; } + } + + public class UnitScript : ScriptObject + { + public UnitScript(string name, bool addToScripts = true) : base(name) + { + if (addToScripts) + Global.ScriptMgr.AddScript(this); + } + + public virtual void OnHeal(Unit healer, Unit reciever, ref uint gain) { } + + public virtual void OnDamage(Unit attacker, Unit victim, ref uint damage) { } + + // Called when DoT's Tick Damage is being Dealt + public virtual void ModifyPeriodicDamageAurasTick(Unit target, Unit attacker, ref uint damage) { } + + // Called when Melee Damage is being Dealt + public virtual void ModifyMeleeDamage(Unit target, Unit attacker, ref uint damage) { } + + // Called when Spell Damage is being Dealt + public virtual void ModifySpellDamageTaken(Unit target, Unit attacker, ref int damage) { } + } + + public class CreatureScript : UnitScript + { + public CreatureScript(string name) : base(name, false) + { + Global.ScriptMgr.AddScript(this); + } + + public override bool IsDatabaseBound() { return true; } + + // Called when a dummy spell effect is triggered on the creature. + public virtual bool OnDummyEffect(Unit caster, uint spellId, uint effIndex, Creature target) { return false; } + + // Called when a player opens a gossip dialog with the creature. + public virtual bool OnGossipHello(Player player, Creature creature) { return false; } + + // Called when a player selects a gossip item in the creature's gossip menu. + public virtual bool OnGossipSelect(Player player, Creature creature, uint sender, uint action) { return false; } + + // Called when a player selects a gossip with a code in the creature's gossip menu. + public virtual bool OnGossipSelectCode(Player player, Creature creature, uint sender, uint action, string code) { return false; } + + // Called when a player accepts a quest from the creature. + public virtual bool OnQuestAccept(Player player, Creature creature, Quest quest) { return false; } + + // Called when a player selects a quest in the creature's quest menu. + public virtual bool OnQuestSelect(Player player, Creature creature, Quest quest) { return false; } + + // Called when a player completes a quest with the creature. + public virtual bool OnQuestComplete(Player player, Creature creature, Quest quest) { return false; } + + // Called when a player selects a quest reward. + public virtual bool OnQuestReward(Player player, Creature creature, Quest quest, uint opt) { return false; } + + // Called when the dialog status between a player and the creature is requested. + public virtual uint GetDialogStatus(Player player, Creature creature) { return (uint)QuestGiverStatus.ScriptedNoStatus; } + + // Called when the creature tries to spawn. Return false to block spawn and re-evaluate on next tick. + public virtual bool CanSpawn(ulong spawnId, uint entry, CreatureTemplate baseTemplate, CreatureTemplate actTemplate, CreatureData cData, Map map) { return true; } + + // Called when a CreatureAI object is needed for the creature. + public virtual CreatureAI GetAI(Creature creature) { return null; } + + public virtual void OnUpdate(Creature obj, uint diff) { } + } + + public class GameObjectScript : ScriptObject + { + public GameObjectScript(string name) : base(name) + { + Global.ScriptMgr.AddScript(this); + } + + public override bool IsDatabaseBound() { return true; } + + // Called when a dummy spell effect is triggered on the gameobject. + public virtual bool OnDummyEffect(Unit caster, uint spellId, uint effIndex, GameObject target) { return false; } + + // Called when a player opens a gossip dialog with the gameobject. + public virtual bool OnGossipHello(Player player, GameObject go) { return false; } + + // Called when a player selects a gossip item in the gameobject's gossip menu. + public virtual bool OnGossipSelect(Player player, GameObject go, uint sender, uint action) { return false; } + + // Called when a player selects a gossip with a code in the gameobject's gossip menu. + public virtual bool OnGossipSelectCode(Player player, GameObject go, uint sender, uint action, string code) { return false; } + + // Called when a player accepts a quest from the gameobject. + public virtual bool OnQuestAccept(Player player, GameObject go, Quest quest) { return false; } + + // Called when a player selects a quest reward. + public virtual bool OnQuestReward(Player player, GameObject go, Quest quest, uint opt) { return false; } + + // Called when the dialog status between a player and the gameobject is requested. + public virtual uint GetDialogStatus(Player player, GameObject go) { return 100; } + + // Called when the game object is destroyed (destructible buildings only). + public virtual void OnDestroyed(GameObject go, Player player) { } + + // Called when the game object is damaged (destructible buildings only). + public virtual void OnDamaged(GameObject go, Player player) { } + + // Called when the game object loot state is changed. + public virtual void OnLootStateChanged(GameObject go, uint state, Unit unit) { } + + // Called when the game object state is changed. + public virtual void OnGameObjectStateChanged(GameObject go, GameObjectState state) { } + + // Called when a GameObjectAI object is needed for the gameobject. + public virtual GameObjectAI GetAI(GameObject go) { return null; } + + public virtual void OnUpdate(GameObject obj, uint diff) { } + } + + public class AreaTriggerScript : ScriptObject + { + public AreaTriggerScript(string name) : base(name) + { + Global.ScriptMgr.AddScript(this); + } + + public override bool IsDatabaseBound() { return true; } + + // Called when the area trigger is activated by a player. + public virtual bool OnTrigger(Player player, AreaTriggerRecord trigger, bool entered) { return false; } + } + + public class BattlegroundScript : ScriptObject + { + public BattlegroundScript(string name) : base(name) + { + Global.ScriptMgr.AddScript(this); + } + + public override bool IsDatabaseBound() { return true; } + + // Should return a fully valid Battlegroundobject for the type ID. + public virtual Battleground BattlegroundGetBattleground() { return null; } + } + + public class OutdoorPvPScript : ScriptObject + { + public OutdoorPvPScript(string name) : base(name) + { + Global.ScriptMgr.AddScript(this); + } + + public override bool IsDatabaseBound() { return true; } + + // Should return a fully valid OutdoorPvP object for the type ID. + public virtual OutdoorPvP GetOutdoorPvP() { return null; } + } + + public class WeatherScript : ScriptObject + { + public WeatherScript(string name) : base(name) + { + Global.ScriptMgr.AddScript(this); + } + + public override bool IsDatabaseBound() { return true; } + + // Called when the weather changes in the zone this script is associated with. + public virtual void OnChange(Weather weather, WeatherState state, float grade) { } + + public virtual void OnUpdate(Weather obj, uint diff) { } + } + + public class AuctionHouseScript : ScriptObject + { + public AuctionHouseScript(string name) : base(name) + { + Global.ScriptMgr.AddScript(this); + } + + // Called when an auction is added to an auction house. + public virtual void OnAuctionAdd(AuctionHouseObject ah, AuctionEntry entry) { } + + // Called when an auction is removed from an auction house. + public virtual void OnAuctionRemove(AuctionHouseObject ah, AuctionEntry entry) { } + + // Called when an auction was succesfully completed. + public virtual void OnAuctionSuccessful(AuctionHouseObject ah, AuctionEntry entry) { } + + // Called when an auction expires. + public virtual void OnAuctionExpire(AuctionHouseObject ah, AuctionEntry entry) { } + } + + public class ConditionScript : ScriptObject + { + public ConditionScript(string name) : base(name) + { + Global.ScriptMgr.AddScript(this); + } + + public override bool IsDatabaseBound() { return true; } + + // Called when a single condition is checked for a player. + public virtual bool OnConditionCheck(Condition condition, ConditionSourceInfo sourceInfo) { return true; } + } + + public class VehicleScript : ScriptObject + { + public VehicleScript(string name) : base(name) + { + Global.ScriptMgr.AddScript(this); + } + + // Called after a vehicle is installed. + public virtual void OnInstall(Vehicle veh) { } + + // Called after a vehicle is uninstalled. + public virtual void OnUninstall(Vehicle veh) { } + + // Called when a vehicle resets. + public virtual void OnReset(Vehicle veh) { } + + // Called after an accessory is installed in a vehicle. + public virtual void OnInstallAccessory(Vehicle veh, Creature accessory) { } + + // Called after a passenger is added to a vehicle. + public virtual void OnAddPassenger(Vehicle veh, Unit passenger, sbyte seatId) { } + + // Called after a passenger is removed from a vehicle. + public virtual void OnRemovePassenger(Vehicle veh, Unit passenger) { } + } + + public class DynamicObjectScript : ScriptObject + { + public DynamicObjectScript(string name) : base(name) + { + Global.ScriptMgr.AddScript(this); + } + + public virtual void OnUpdate(DynamicObject obj, uint diff) { } + } + + public class TransportScript : ScriptObject + { + public TransportScript(string name) : base(name) + { + Global.ScriptMgr.AddScript(this); + } + + public override bool IsDatabaseBound() { return true; } + + // Called when a player boards the transport. + public virtual void OnAddPassenger(Transport transport, Player player) { } + + // Called when a creature boards the transport. + public virtual void OnAddCreaturePassenger(Transport transport, Creature creature) { } + + // Called when a player exits the transport. + public virtual void OnRemovePassenger(Transport transport, Player player) { } + + // Called when a transport moves. + public virtual void OnRelocate(Transport transport, uint waypointId, uint mapId, float x, float y, float z) { } + + public virtual void OnUpdate(Transport obj, uint diff) { } + } + + public class AchievementCriteriaScript : ScriptObject + { + public AchievementCriteriaScript(string name) : base(name) + { + Global.ScriptMgr.AddScript(this); + } + + public override bool IsDatabaseBound() { return true; } + + // Called when an additional criteria is checked. + public virtual bool OnCheck(Player source, Unit target) { return false; } + } + + public class PlayerScript : UnitScript + { + public PlayerScript(string name) : base(name, false) + { + Global.ScriptMgr.AddScript(this); + } + + // Called when a player kills another player + public virtual void OnPVPKill(Player killer, Player killed) { } + + // Called when a player kills a creature + public virtual void OnCreatureKill(Player killer, Creature killed) { } + + // Called when a player is killed by a creature + public virtual void OnPlayerKilledByCreature(Creature killer, Player killed) { } + + // Called when a player's level changes (after the level is applied) + public virtual void OnLevelChanged(Player player, byte oldLevel) { } + + // Called when a player's free talent points change (right before the change is applied) + public virtual void OnFreeTalentPointsChanged(Player player, uint points) { } + + // Called when a player's talent points are reset (right before the reset is done) + public virtual void OnTalentsReset(Player player, bool noCost) { } + + // Called when a player's money is modified (before the modification is done) + public virtual void OnMoneyChanged(Player player, long amount) { } + + // Called when a player gains XP (before anything is given) + public virtual void OnGiveXP(Player player, uint amount, Unit victim) { } + + // Called when a player's reputation changes (before it is actually changed) + public virtual void OnReputationChange(Player player, uint factionId, int standing, bool incremental) { } + + // Called when a duel is requested + public virtual void OnDuelRequest(Player target, Player challenger) { } + + // Called when a duel starts (after 3s countdown) + public virtual void OnDuelStart(Player player1, Player player2) { } + + // Called when a duel ends + public virtual void OnDuelEnd(Player winner, Player loser, DuelCompleteType type) { } + + // The following methods are called when a player sends a chat message. + public virtual void OnChat(Player player, ChatMsg type, Language lang, string msg) { } + + public virtual void OnChat(Player player, ChatMsg type, Language lang, string msg, Player receiver) { } + + public virtual void OnChat(Player player, ChatMsg type, Language lang, string msg, Group group) { } + + public virtual void OnChat(Player player, ChatMsg type, Language lang, string msg, Guild guild) { } + + public virtual void OnChat(Player player, ChatMsg type, Language lang, string msg, Channel channel) { } + + // Both of the below are called on emote opcodes. + public virtual void OnClearEmote(Player player) { } + + public virtual void OnTextEmote(Player player, uint textEmote, uint emoteNum, ObjectGuid guid) { } + + // Called in Spell.Cast. + public virtual void OnSpellCast(Player player, Spell spell, bool skipCheck) { } + + // Called when a player logs in. + public virtual void OnLogin(Player player) { } + + // Called when a player logs out. + public virtual void OnLogout(Player player) { } + + // Called when a player is created. + public virtual void OnCreate(Player player) { } + + // Called when a player is deleted. + public virtual void OnDelete(ObjectGuid guid) { } + + // Called when a player is about to be saved. + public virtual void OnSave(Player player) { } + + // Called when a player is bound to an instance + public virtual void OnBindToInstance(Player player, Difficulty difficulty, uint mapId, bool permanent, BindExtensionState extendState) { } + + // Called when a player switches to a new zone + public virtual void OnUpdateZone(Player player, uint newZone, uint newArea) { } + + // Called when a player changes to a new map (after moving to new map) + public virtual void OnMapChanged(Player player) { } + + // Called after a player's quest status has been changed + public virtual void OnQuestStatusChange(Player player, uint questId) { } + + // Called when a player completes a movie + public virtual void OnMovieComplete(Player player, uint movieId) { } + } + + public class GuildScript : ScriptObject + { + public GuildScript(string name) : base(name) + { + Global.ScriptMgr.AddScript(this); + } + + public override bool IsDatabaseBound() { return false; } + + // Called when a member is added to the guild. + public virtual void OnAddMember(Guild guild, Player player, byte plRank) { } + + // Called when a member is removed from the guild. + public virtual void OnRemoveMember(Guild guild, Player player, bool isDisbanding, bool isKicked) { } + + // Called when the guild MOTD (message of the day) changes. + public virtual void OnMOTDChanged(Guild guild, string newMotd) { } + + // Called when the guild info is altered. + public virtual void OnInfoChanged(Guild guild, string newInfo) { } + + // Called when a guild is created. + public virtual void OnCreate(Guild guild, Player leader, string name) { } + + // Called when a guild is disbanded. + public virtual void OnDisband(Guild guild) { } + + // Called when a guild member withdraws money from a guild bank. + public virtual void OnMemberWitdrawMoney(Guild guild, Player player, ulong amount, bool isRepair) { } + + // Called when a guild member deposits money in a guild bank. + public virtual void OnMemberDepositMoney(Guild guild, Player player, ulong amount) { } + + // Called when a guild member moves an item in a guild bank. + public virtual void OnItemMove(Guild guild, Player player, Item pItem, bool isSrcBank, byte srcContainer, byte srcSlotId, bool isDestBank, byte destContainer, byte destSlotId) { } + + public virtual void OnEvent(Guild guild, byte eventType, ulong playerGuid1, ulong playerGuid2, byte newRank) { } + + public virtual void OnBankEvent(Guild guild, byte eventType, byte tabId, ulong playerGuid, uint itemOrMoney, ushort itemStackCount, byte destTabId) { } + } + + public class GroupScript : ScriptObject + { + public GroupScript(string name) : base(name) + { + Global.ScriptMgr.AddScript(this); + } + + public override bool IsDatabaseBound() { return false; } + + // Called when a member is added to a group. + public virtual void OnAddMember(Group group, ObjectGuid guid) { } + + // Called when a member is invited to join a group. + public virtual void OnInviteMember(Group group, ObjectGuid guid) { } + + // Called when a member is removed from a group. + public virtual void OnRemoveMember(Group group, ObjectGuid guid, RemoveMethod method, ObjectGuid kicker, string reason) { } + + // Called when the leader of a group is changed. + public virtual void OnChangeLeader(Group group, ObjectGuid newLeaderGuid, ObjectGuid oldLeaderGuid) { } + + // Called when a group is disbanded. + public virtual void OnDisband(Group group) { } + } + + public class AreaTriggerEntityScript : ScriptObject + { + public AreaTriggerEntityScript(string name) : base(name) + { + Global.ScriptMgr.AddScript(this); + } + + public override bool IsDatabaseBound() { return true; } + + // Called when a AreaTriggerAI object is needed for the areatrigger. + public virtual AreaTriggerAI GetAI(AreaTrigger at) { return null; } + } + + public class SceneScript : ScriptObject + { + public SceneScript(string name) : base(name) + { + Global.ScriptMgr.AddScript(this); + } + + public override bool IsDatabaseBound() { return true; } + + // Called when a player start a scene + public virtual void OnSceneStart(Player player, uint sceneInstanceID, SceneTemplate sceneTemplate) { } + + // Called when a player receive trigger from scene + public virtual void OnSceneTriggerEvent(Player player, uint sceneInstanceID, SceneTemplate sceneTemplate, string triggerName) { } + + // Called when a scene is canceled + public virtual void OnSceneCancel(Player player, uint sceneInstanceID, SceneTemplate sceneTemplate) { } + + // Called when a scene is completed + public virtual void OnSceneComplete(Player player, uint sceneInstanceID, SceneTemplate sceneTemplate) { } + } +} diff --git a/Game/Scripting/ScriptManager.cs b/Game/Scripting/ScriptManager.cs new file mode 100644 index 000000000..935fa3102 --- /dev/null +++ b/Game/Scripting/ScriptManager.cs @@ -0,0 +1,1420 @@ +/* + * Copyright (C) 2012-2017 CypherCore + * + * 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, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +using Framework.Constants; +using Framework.Database; +using Framework.GameMath; +using Game.AI; +using Game.BattleGrounds; +using Game.Chat; +using Game.Conditions; +using Game.DataStorage; +using Game.Entities; +using Game.Groups; +using Game.Guilds; +using Game.Maps; +using Game.Movement; +using Game.PvP; +using Game.Spells; +using System; +using System.Collections; +using System.Collections.Generic; +using System.Diagnostics.Contracts; +using System.IO; +using System.Linq; +using System.Reflection; + +namespace Game.Scripting +{ + // Manages registration, loading, and execution of Scripts. + public class ScriptManager : Singleton + { + ScriptManager() { } + + //Initialization + public void Initialize() + { + uint oldMSTime = Time.GetMSTime(); + + LoadDatabase(); + + Log.outInfo(LogFilter.ServerLoading, "Loading C# scripts"); + + FillSpellSummary(); + + if (LoadScripts()) + Log.outInfo(LogFilter.ServerLoading, "Loaded {0} C# scripts in {1} ms", GetScriptCount(), Time.GetMSTimeDiffToNow(oldMSTime)); + } + + public bool LoadScripts(bool reload = false) + { + if (!File.Exists("Scripts.dll")) + { + Log.outError(LogFilter.ServerLoading, "Cant find file {0}, Scripts will not be loaded."); + return false; + } + + Assembly asm = Assembly.LoadFile(Path.GetFullPath("Scripts.dll")); + if (asm == null) + { + Log.outError(LogFilter.ServerLoading, "Error Loading Scripts.dll, Scripts will not be loaded."); + return false; + } + + foreach (var type in asm.GetTypes()) + { + var attributes = (ScriptAttribute[])type.GetCustomAttributes(true); + if (attributes.Empty()) + { + var baseType = type.BaseType; + while (baseType != null) + { + if (baseType == typeof(ScriptObject)) + Log.outWarn(LogFilter.Server, "Script {0} does not have ScriptAttribute", type.Name); + + baseType = baseType.BaseType; + } + } + + var constructors = type.GetConstructors(); + if (constructors.Length == 0) + { + Log.outError(LogFilter.Scripts, "Type: {0} contains no Public Constructors. Can't load script.", type.Name); + break; + } + + foreach (var attribute in attributes) + { + if (!constructors.Any(p => p.GetParameters().Length == attribute.Args.Length)) + { + Log.outError(LogFilter.Scripts, "Type: {0} has ScriptAttribute that does not match paramter count: {1} Can't load script.", type.Name, attribute.Args.Length); + continue; + } + Activator.CreateInstance(type, attribute.Args); + } + } + + return true; + } + + public void LoadDatabase() + { + LoadScriptWaypoints(); + LoadScriptSplineChains(); + } + void LoadScriptWaypoints() + { + uint oldMSTime = Time.GetMSTime(); + + // Drop Existing Waypoint list + + m_mPointMoveMap.Clear(); + + ulong uiCreatureCount = 0; + + // Load Waypoints + SQLResult result = DB.World.Query("SELECT COUNT(entry) FROM script_waypoint GROUP BY entry"); + if (!result.IsEmpty()) + uiCreatureCount = result.Read(0); + + Log.outInfo(LogFilter.ServerLoading, "Loading Script Waypoints for {0} creature(s)...", uiCreatureCount); + + // 0 1 2 3 4 5 + result = DB.World.Query("SELECT entry, pointid, location_x, location_y, location_z, waittime FROM script_waypoint ORDER BY pointid"); + + if (result.IsEmpty()) + { + Log.outInfo(LogFilter.ServerLoading, "Loaded 0 Script Waypoints. DB table `script_waypoint` is empty."); + return; + } + + uint count = 0; + + do + { + ScriptPointMove temp = new ScriptPointMove(); + + temp.uiCreatureEntry = result.Read(0); + uint uiEntry = temp.uiCreatureEntry; + temp.uiPointId = result.Read(1); + temp.fX = result.Read(2); + temp.fY = result.Read(3); + temp.fZ = result.Read(4); + temp.uiWaitTime = result.Read(5); + + CreatureTemplate pCInfo = Global.ObjectMgr.GetCreatureTemplate(temp.uiCreatureEntry); + + if (pCInfo == null) + { + Log.outError(LogFilter.Sql, "TSCR: DB table script_waypoint has waypoint for non-existant creature entry {0}", temp.uiCreatureEntry); + continue; + } + + if (pCInfo.ScriptID == 0) + Log.outError(LogFilter.Sql, "TSCR: DB table script_waypoint has waypoint for creature entry {0}, but creature does not have ScriptName defined and then useless.", temp.uiCreatureEntry); + + m_mPointMoveMap.Add(uiEntry, temp); + ++count; + } + while (result.NextRow()); + + Log.outInfo(LogFilter.ServerLoading, "Loaded {0} Script Waypoint nodes in {1} ms", count, Time.GetMSTimeDiffToNow(oldMSTime)); + + } + void LoadScriptSplineChains() + { + uint oldMSTime = Time.GetMSTime(); + + m_mSplineChainsMap.Clear(); + + // 0 1 2 3 4 + SQLResult resultMeta = DB.World.Query("SELECT entry, chainId, splineId, expectedDuration, msUntilNext FROM script_spline_chain_meta ORDER BY entry asc, chainId asc, splineId asc"); + // 0 1 2 3 4 5 6 + SQLResult resultWP = DB.World.Query("SELECT entry, chainId, splineId, wpId, x, y, z FROM script_spline_chain_waypoints ORDER BY entry asc, chainId asc, splineId asc, wpId asc"); + if (resultMeta.IsEmpty() || resultWP.IsEmpty()) + { + Log.outInfo(LogFilter.ServerLoading, "Loaded spline chain data for 0 chains, consisting of 0 splines with 0 waypoints. DB tables `script_spline_chain_meta` and `script_spline_chain_waypoints` are empty."); + } + else + { + uint chainCount = 0, splineCount = 0, wpCount = 0; + do + { + uint entry = resultMeta.Read(0); + ushort chainId = resultMeta.Read(1); + byte splineId = resultMeta.Read(2); + + var key = Tuple.Create(entry, chainId); + if (!m_mSplineChainsMap.ContainsKey(key)) + m_mSplineChainsMap[key] = new List(); + + var chain = m_mSplineChainsMap[Tuple.Create(entry,chainId)]; + if (splineId != chain.Count) + { + Log.outWarn(LogFilter.ServerLoading, "Creature #{0}: Chain {1} has orphaned spline {2}, skipped.", entry, chainId, splineId); + continue; + } + + uint expectedDuration = resultMeta.Read(3), msUntilNext = resultMeta.Read(4); + chain.Add(new SplineChainLink(expectedDuration, msUntilNext)); + + if (splineId == 0) + ++chainCount; + ++splineCount; + } while (resultMeta.NextRow()); + + do + { + uint entry = resultWP.Read(0); + ushort chainId = resultWP.Read(1); + byte splineId = resultWP.Read(2); + byte wpId = resultWP.Read(3); + float posX = resultWP.Read(4); + float posY = resultWP.Read(5); + float posZ = resultWP.Read(6); + var chain = m_mSplineChainsMap.LookupByKey(Tuple.Create(entry,chainId)); + if (chain == null) + { + Log.outWarn(LogFilter.ServerLoading, "Creature #{0} has waypoint data for spline chain {1}. No such chain exists - entry skipped.", entry, chainId); + continue; + } + + if (splineId >= chain.Count) + { + Log.outWarn(LogFilter.ServerLoading, "Creature #{0} has waypoint data for spline ({1},{2}). The specified chain does not have a spline with this index - entry skipped.", entry, chainId, splineId); + continue; + } + SplineChainLink spline = chain[splineId]; + if (wpId != spline.Points.Count) + { + Log.outWarn(LogFilter.ServerLoading, "Creature #{0} has orphaned waypoint data in spline ({1},{2}) at index {3}. Skipped.", entry, chainId, splineId, wpId); + continue; + } + spline.Points.Add(new Vector3(posX, posY, posZ)); + ++wpCount; + } while (resultWP.NextRow()); + + Log.outInfo(LogFilter.ServerLoading, "Loaded spline chain data for {0} chains, consisting of {1} splines with {2} waypoints in {3} ms", chainCount, splineCount, wpCount, Time.GetMSTimeDiffToNow(oldMSTime)); + } + } + public void FillSpellSummary() + { + UnitAI.FillAISpellInfo(); + + SpellInfo pTempSpell; + var spellStorage = Global.SpellMgr.GetSpellInfoStorage(); + foreach (var i in spellStorage.Keys) + { + spellSummaryStorage[i] = new SpellSummary(); + + pTempSpell = spellStorage.LookupByKey(i); + // This spell doesn't exist. + if (pTempSpell == null) + continue; + + foreach (SpellEffectInfo effect in pTempSpell.GetEffectsForDifficulty(Difficulty.None)) + { + if (effect == null) + continue; + + // Spell targets self. + if (effect.TargetA.GetTarget() == Targets.UnitCaster) + spellSummaryStorage[i].Targets |= 1 << ((int)SelectTargetType.Self - 1); + + // Spell targets a single enemy. + if (effect.TargetA.GetTarget() == Targets.UnitEnemy || effect.TargetA.GetTarget() == Targets.DestEnemy) + spellSummaryStorage[i].Targets |= 1 << ((int)SelectTargetType.SingleEnemy - 1); + + // Spell targets AoE at enemy. + if (effect.TargetA.GetTarget() == Targets.UnitSrcAreaEnemy || effect.TargetA.GetTarget() == Targets.UnitDestAreaEnemy || + effect.TargetA.GetTarget() == Targets.SrcCaster || effect.TargetA.GetTarget() == Targets.DestDynobjEnemy) + spellSummaryStorage[i].Targets |= 1 << ((int)SelectTargetType.AoeEnemy - 1); + + // Spell targets an enemy. + if (effect.TargetA.GetTarget() == Targets.UnitEnemy || effect.TargetA.GetTarget() == Targets.DestEnemy || + effect.TargetA.GetTarget() == Targets.UnitSrcAreaEnemy || effect.TargetA.GetTarget() == Targets.UnitDestAreaEnemy || + effect.TargetA.GetTarget() == Targets.SrcCaster || effect.TargetA.GetTarget() == Targets.DestDynobjEnemy) + spellSummaryStorage[i].Targets |= 1 << ((int)SelectTargetType.AnyEnemy - 1); + + // Spell targets a single friend (or self). + if (effect.TargetA.GetTarget() == Targets.UnitCaster || effect.TargetA.GetTarget() == Targets.UnitAlly || + effect.TargetA.GetTarget() == Targets.UnitParty) + spellSummaryStorage[i].Targets |= 1 << ((int)SelectTargetType.SingleFriend - 1); + + // Spell targets AoE friends. + if (effect.TargetA.GetTarget() == Targets.UnitCasterAreaParty || effect.TargetA.GetTarget() == Targets.UnitLastareaParty || + effect.TargetA.GetTarget() == Targets.SrcCaster) + spellSummaryStorage[i].Targets |= 1 << ((int)SelectTargetType.AoeFriend - 1); + + // Spell targets any friend (or self). + if (effect.TargetA.GetTarget() == Targets.UnitCaster || effect.TargetA.GetTarget() == Targets.UnitAlly || + effect.TargetA.GetTarget() == Targets.UnitParty || effect.TargetA.GetTarget() == Targets.UnitCasterAreaParty || + effect.TargetA.GetTarget() == Targets.UnitLastareaParty || effect.TargetA.GetTarget() == Targets.SrcCaster) + spellSummaryStorage[i].Targets |= 1 << ((int)SelectTargetType.AnyFriend - 1); + + // Make sure that this spell includes a damage effect. + if (effect.Effect == SpellEffectName.SchoolDamage || effect.Effect == SpellEffectName.Instakill || + effect.Effect == SpellEffectName.EnvironmentalDamage || effect.Effect == SpellEffectName.HealthLeech) + spellSummaryStorage[i].Effects |= 1 << ((int)SelectEffect.Damage - 1); + + // Make sure that this spell includes a healing effect (or an apply aura with a periodic heal). + if (effect.Effect == SpellEffectName.Heal || effect.Effect == SpellEffectName.HealMaxHealth || + effect.Effect == SpellEffectName.HealMechanical || (effect.Effect == SpellEffectName.ApplyAura && effect.ApplyAuraName == AuraType.PeriodicHeal)) + spellSummaryStorage[i].Effects |= 1 << ((int)SelectEffect.Healing - 1); + + // Make sure that this spell applies an aura. + if (effect.Effect == SpellEffectName.ApplyAura) + spellSummaryStorage[i].Effects |= 1 << ((int)SelectEffect.Aura - 1); + } + } + } + + public List GetSplineChain(Creature who, ushort chainId) + { + return GetSplineChain(who.GetEntry(), chainId); + } + List GetSplineChain(uint entry, ushort chainId) + { + return m_mSplineChainsMap.LookupByKey(Tuple.Create(entry, chainId)); + } + + public string ScriptsVersion() { return "Integrated Cypher Scripts"; } + + public void IncrementScriptCount() { ++_ScriptCount; } + public uint GetScriptCount() { return _ScriptCount; } + + //Reloading + public void Reload() + { + + } + + //Unloading + public void Unload() + { + foreach (ScriptRegistry scr in ScriptStorage) + scr.Unload(); + } + + //SpellScriptLoader + public List CreateSpellScripts(uint spellId, Spell invoker) + { + var scriptList = new List(); + var bounds = Global.ObjectMgr.GetSpellScriptsBounds(spellId); + + var reg = GetScriptRegistry(); + if (reg == null) + return scriptList; + + foreach (var id in bounds) + { + var tmpscript = reg.GetScriptById(id); + if (tmpscript == null) + continue; + + SpellScript script = tmpscript.GetSpellScript(); + if (script == null) + continue; + + script._Init(tmpscript.GetName(), spellId); + if (!script._Load(invoker)) + continue; + + scriptList.Add(script); + } + + return scriptList; + } + public List CreateAuraScripts(uint spellId, Aura invoker) + { + var scriptList = new List(); + var bounds = Global.ObjectMgr.GetSpellScriptsBounds(spellId); + + var reg = GetScriptRegistry(); + if (reg == null) + return scriptList; + + foreach (var id in bounds) + { + var tmpscript = reg.GetScriptById(id); + if (tmpscript == null) + continue; + + AuraScript script = tmpscript.GetAuraScript(); + if (script == null) + continue; + + script._Init(tmpscript.GetName(), spellId); + if (!script._Load(invoker)) + continue; + + scriptList.Add(script); + } + + return scriptList; + } + public Dictionary CreateSpellScriptLoaders(uint spellId) + { + var scriptDic = new Dictionary(); + var bounds = Global.ObjectMgr.GetSpellScriptsBounds(spellId); + + var reg = GetScriptRegistry(); + if (reg == null) + return scriptDic; + + foreach (var id in bounds) + { + var tmpscript = reg.GetScriptById(id); + if (tmpscript == null) + continue; + + scriptDic.Add(tmpscript, id); + } + + return scriptDic; + } + + //WorldScript + public void OnOpenStateChange(bool open) + { + ForEach(p => p.OnOpenStateChange(open)); + } + public void OnConfigLoad(bool reload) + { + ForEach(p => p.OnConfigLoad(reload)); + } + public void OnMotdChange(string newMotd) + { + ForEach(p => p.OnMotdChange(newMotd)); + } + public void OnShutdownInitiate(ShutdownExitCode code, ShutdownMask mask) + { + ForEach(p => p.OnShutdownInitiate(code, mask)); + } + public void OnShutdownCancel() + { + ForEach(p => p.OnShutdownCancel()); + } + public void OnWorldUpdate(uint diff) + { + ForEach(p => p.OnUpdate(diff)); + } + + //FormulaScript + public void OnHonorCalculation(float honor, uint level, float multiplier) + { + ForEach(p => p.OnHonorCalculation(honor, level, multiplier)); + } + public void OnGrayLevelCalculation(uint grayLevel, uint playerLevel) + { + ForEach(p => p.OnGrayLevelCalculation(grayLevel, playerLevel)); + } + public void OnColorCodeCalculation(XPColorChar color, uint playerLevel, uint mobLevel) + { + ForEach(p => p.OnColorCodeCalculation(color, playerLevel, mobLevel)); + } + public void OnZeroDifferenceCalculation(uint diff, uint playerLevel) + { + ForEach(p => p.OnZeroDifferenceCalculation(diff, playerLevel)); + } + public void OnBaseGainCalculation(uint gain, uint playerLevel, uint mobLevel) + { + ForEach(p => p.OnBaseGainCalculation(gain, playerLevel, mobLevel)); + } + public void OnGainCalculation(uint gain, Player player, Unit unit) + { + Contract.Assert(player != null); + Contract.Assert(unit != null); + + ForEach(p => p.OnGainCalculation(gain, player, unit)); + } + public void OnGroupRateCalculation(float rate, uint count, bool isRaid) + { + ForEach(p => p.OnGroupRateCalculation(rate, count, isRaid)); + } + + //MapScript + public void OnCreateMap(Map map) + { + Contract.Assert(map != null); + var record = map.GetEntry(); + + if (record != null && record.IsWorldMap()) + ForEach(p => p.OnCreate(map)); + + if (record != null && record.IsDungeon()) + ForEach(p => p.OnCreate(map.ToInstanceMap())); + + if (record != null && record.IsBattleground()) + ForEach(p => p.OnCreate(map.ToBattlegroundMap())); + } + public void OnDestroyMap(Map map) + { + Contract.Assert(map != null); + var record = map.GetEntry(); + + if (record != null && record.IsWorldMap()) + ForEach(p => p.OnDestroy(map)); + + if (record != null && record.IsDungeon()) + ForEach(p => p.OnDestroy(map.ToInstanceMap())); + + if (record != null && record.IsBattleground()) + ForEach(p => p.OnDestroy(map.ToBattlegroundMap())); + } + public void OnLoadGridMap(Map map, GridMap gmap, uint gx, uint gy) + { + Contract.Assert(map != null); + Contract.Assert(gmap != null); + var record = map.GetEntry(); + + if (record != null && record.IsWorldMap()) + ForEach(p => p.OnLoadGridMap(map, gmap, gx, gy)); + + if (record != null && record.IsDungeon()) + ForEach(p => p.OnLoadGridMap(map.ToInstanceMap(), gmap, gx, gy)); + + if (record != null && record.IsBattleground()) + ForEach(p => p.OnLoadGridMap(map.ToBattlegroundMap(), gmap, gx, gy)); + + } + public void OnUnloadGridMap(Map map, GridMap gmap, uint gx, uint gy) + { + Contract.Assert(map != null); + Contract.Assert(gmap != null); + var record = map.GetEntry(); + + if (record != null && record.IsWorldMap()) + ForEach(p => p.OnUnloadGridMap(map, gmap, gx, gy)); + + if (record != null && record.IsDungeon()) + ForEach(p => p.OnUnloadGridMap(map.ToInstanceMap(), gmap, gx, gy)); + + if (record != null && record.IsBattleground()) + ForEach(p => p.OnUnloadGridMap(map.ToBattlegroundMap(), gmap, gx, gy)); + } + public void OnPlayerEnterMap(Map map, Player player) + { + Contract.Assert(map != null); + Contract.Assert(player != null); + + ForEach(p => p.OnMapChanged(player)); + + var record = map.GetEntry(); + + if (record != null && record.IsWorldMap()) + ForEach(p => p.OnPlayerEnter(map, player)); + + if (record != null && record.IsDungeon()) + ForEach(p => p.OnPlayerEnter(map.ToInstanceMap(), player)); + + if (record != null && record.IsBattleground()) + ForEach(p => p.OnPlayerEnter(map.ToBattlegroundMap(), player)); + } + public void OnPlayerLeaveMap(Map map, Player player) + { + Contract.Assert(map != null); + var record = map.GetEntry(); + + if (record != null && record.IsWorldMap()) + ForEach(p => p.OnPlayerLeave(map, player)); + + if (record != null && record.IsDungeon()) + ForEach(p => p.OnPlayerLeave(map.ToInstanceMap(), player)); + + if (record != null && record.IsBattleground()) + ForEach(p => p.OnPlayerLeave(map.ToBattlegroundMap(), player)); + } + public void OnMapUpdate(Map map, uint diff) + { + Contract.Assert(map != null); + var record = map.GetEntry(); + + if (record != null && record.IsWorldMap()) + ForEach(p => p.OnUpdate(map, diff)); + + if (record != null && record.IsDungeon()) + ForEach(p => p.OnUpdate(map.ToInstanceMap(), diff)); + + if (record != null && record.IsBattleground()) + ForEach(p => p.OnUpdate(map.ToBattlegroundMap(), diff)); + } + + //InstanceMapScript + public InstanceScript CreateInstanceData(InstanceMap map) + { + Contract.Assert(map != null); + + return RunScriptRet(p => p.GetInstanceScript(map), map.GetScriptId(), null); + } + + //ItemScript + public bool OnDummyEffect(Unit caster, uint spellId, uint effIndex, Item target) + { + Contract.Assert(caster != null); + Contract.Assert(target != null); + + return RunScriptRet(p => p.OnDummyEffect(caster, spellId, effIndex, target), target.GetScriptId()); + } + public bool OnQuestAccept(Player player, Item item, Quest quest) + { + Contract.Assert(player != null); + Contract.Assert(item != null); + Contract.Assert(quest != null); + + return RunScriptRet(p => p.OnQuestAccept(player, item, quest), item.GetScriptId()); + } + public bool OnItemUse(Player player, Item item, SpellCastTargets targets, ObjectGuid castId) + { + Contract.Assert(player); + Contract.Assert(item); + + return RunScriptRet(p => p.OnUse(player, item, targets, castId), item.GetScriptId()); + } + public bool OnItemExpire(Player player, ItemTemplate proto) + { + Contract.Assert(player); + Contract.Assert(proto != null); + + return RunScriptRet(p => p.OnExpire(player, proto), proto.ScriptId); + } + + //CreatureScript + public bool OnDummyEffect(Unit caster, uint spellId, uint effIndex, Creature target) + { + Contract.Assert(caster); + Contract.Assert(target); + + return RunScriptRet(p => p.OnDummyEffect(caster, spellId, effIndex, target), target.GetScriptId()); + } + public bool OnGossipHello(Player player, Creature creature) + { + Contract.Assert(player); + Contract.Assert(creature); + + return RunScriptRet(p => p.OnGossipHello(player, creature), creature.GetScriptId()); + } + public bool OnGossipSelect(Player player, Creature creature, uint sender, uint action) + { + Contract.Assert(player != null); + Contract.Assert(creature != null); + + return RunScriptRet(p => p.OnGossipSelect(player, creature, sender, action), creature.GetScriptId()); + } + public bool OnGossipSelectCode(Player player, Creature creature, uint sender, uint action, string code) + { + Contract.Assert(player != null); + Contract.Assert(creature != null); + Contract.Assert(code != null); + + return RunScriptRet(p => p.OnGossipSelectCode(player, creature, sender, action, code), creature.GetScriptId()); + } + public bool OnQuestAccept(Player player, Creature creature, Quest quest) + { + Contract.Assert(player != null); + Contract.Assert(creature != null); + Contract.Assert(quest != null); + + return RunScriptRet(p => p.OnQuestAccept(player, creature, quest), creature.GetScriptId()); + } + public bool OnQuestSelect(Player player, Creature creature, Quest quest) + { + Contract.Assert(player != null); + Contract.Assert(creature != null); + Contract.Assert(quest != null); + + return RunScriptRet(p => p.OnQuestSelect(player, creature, quest), creature.GetScriptId()); + } + public bool OnQuestComplete(Player player, Creature creature, Quest quest) + { + Contract.Assert(player != null); + Contract.Assert(creature != null); + Contract.Assert(quest != null); + + return RunScriptRet(p => p.OnQuestComplete(player, creature, quest), creature.GetScriptId()); + } + public bool OnQuestReward(Player player, Creature creature, Quest quest, uint opt) + { + Contract.Assert(player != null); + Contract.Assert(creature != null); + Contract.Assert(quest != null); + + return RunScriptRet(p => p.OnQuestReward(player, creature, quest, opt), creature.GetScriptId()); + } + public uint GetDialogStatus(Player player, Creature creature) + { + Contract.Assert(player != null); + Contract.Assert(creature != null); + + player.PlayerTalkClass.ClearMenus(); + return RunScriptRet(p => p.GetDialogStatus(player, creature), creature.GetScriptId(), (uint)QuestGiverStatus.ScriptedNoStatus); + } + public bool CanSpawn(ulong spawnId, uint entry, CreatureTemplate actTemplate, CreatureData cData, Map map) + { + Contract.Assert(actTemplate != null); + + CreatureTemplate baseTemplate = Global.ObjectMgr.GetCreatureTemplate(entry); + if (baseTemplate == null) + baseTemplate = actTemplate; + return RunScriptRet(p => p.CanSpawn(spawnId, entry, baseTemplate, actTemplate, cData, map), cData != null ? cData.ScriptId : baseTemplate.ScriptID, true); + } + public CreatureAI GetCreatureAI(Creature creature) + { + Contract.Assert(creature != null); + + return RunScriptRet(p => p.GetAI(creature), creature.GetScriptId()); + } + public void OnCreatureUpdate(Creature creature, uint diff) + { + Contract.Assert(creature != null); + + RunScript(p => p.OnUpdate(creature, diff), creature.GetScriptId()); + } + + //GameObjectScript + public bool OnDummyEffect(Unit caster, uint spellId, uint effIndex, GameObject target) + { + Contract.Assert(caster != null); + Contract.Assert(target != null); + + return RunScriptRet(p => p.OnDummyEffect(caster, spellId, effIndex, target), target.GetScriptId()); + } + public bool OnGossipHello(Player player, GameObject go) + { + Contract.Assert(player != null); + Contract.Assert(go != null); + + player.PlayerTalkClass.ClearMenus(); + return RunScriptRet(p => p.OnGossipHello(player, go), go.GetScriptId()); + } + public bool OnGossipSelect(Player player, GameObject go, uint sender, uint action) + { + Contract.Assert(player != null); + Contract.Assert(go != null); + + return RunScriptRet(p => p.OnGossipSelect(player, go, sender, action), go.GetScriptId()); + } + public bool OnGossipSelectCode(Player player, GameObject go, uint sender, uint action, string code) + { + Contract.Assert(player != null); + Contract.Assert(go != null); + Contract.Assert(code != null); + + return RunScriptRet(p => p.OnGossipSelectCode(player, go, sender, action, code), go.GetScriptId()); + } + public bool OnQuestAccept(Player player, GameObject go, Quest quest) + { + Contract.Assert(player != null); + Contract.Assert(go != null); + Contract.Assert(quest != null); + + return RunScriptRet(p => p.OnQuestAccept(player, go, quest), go.GetScriptId()); + } + public bool OnQuestReward(Player player, GameObject go, Quest quest, uint opt) + { + Contract.Assert(player != null); + Contract.Assert(go != null); + Contract.Assert(quest != null); + + return RunScriptRet(p => p.OnQuestReward(player, go, quest, opt), go.GetScriptId()); + } + public uint GetDialogStatus(Player player, GameObject go) + { + Contract.Assert(player != null); + Contract.Assert(go != null); + + return RunScriptRet(p => p.GetDialogStatus(player, go), go.GetScriptId(), (uint)QuestGiverStatus.ScriptedNoStatus); + } + public void OnGameObjectDestroyed(GameObject go, Player player) + { + Contract.Assert(go != null); + + RunScript(p => p.OnDestroyed(go, player), go.GetScriptId()); + } + public void OnGameObjectDamaged(GameObject go, Player player) + { + Contract.Assert(go != null); + + RunScript(p => p.OnDamaged(go, player), go.GetScriptId()); + } + public void OnGameObjectLootStateChanged(GameObject go, uint state, Unit unit) + { + Contract.Assert(go != null); + + RunScript(p => p.OnLootStateChanged(go, state, unit), go.GetScriptId()); + } + public void OnGameObjectStateChanged(GameObject go, GameObjectState state) + { + Contract.Assert(go != null); + + RunScript(p => p.OnGameObjectStateChanged(go, state), go.GetScriptId()); + } + public void OnGameObjectUpdate(GameObject go, uint diff) + { + Contract.Assert(go != null); + + RunScript(p => p.OnUpdate(go, diff), go.GetScriptId()); + } + public GameObjectAI GetGameObjectAI(GameObject go) + { + Contract.Assert(go != null); + + return RunScriptRet(p => p.GetAI(go), go.GetScriptId()); + } + + //AreaTriggerScript + public bool OnAreaTrigger(Player player, AreaTriggerRecord trigger, bool entered) + { + Contract.Assert(player != null); + Contract.Assert(trigger != null); + + return RunScriptRet(p => p.OnTrigger(player, trigger, entered), Global.ObjectMgr.GetAreaTriggerScriptId(trigger.Id)); + } + + //BattlegroundScript + public Battleground CreateBattleground(BattlegroundTypeId typeId) + { + // @todo Implement script-side Battlegrounds. + Contract.Assert(false); + return null; + } + + // OutdoorPvPScript + public OutdoorPvP CreateOutdoorPvP(OutdoorPvPData data) + { + Contract.Assert(data != null); + return RunScriptRet(p => p.GetOutdoorPvP(), data.ScriptId, null); + } + + // WeatherScript + public void OnWeatherChange(Weather weather, WeatherState state, float grade) + { + Contract.Assert(weather != null); + RunScript(p => p.OnChange(weather, state, grade), weather.GetScriptId()); + } + public void OnWeatherUpdate(Weather weather, uint diff) + { + Contract.Assert(weather != null); + RunScript(p => p.OnUpdate(weather, diff), weather.GetScriptId()); + } + + // AuctionHouseScript + public void OnAuctionAdd(AuctionHouseObject ah, AuctionEntry entry) + { + Contract.Assert(ah != null); + Contract.Assert(entry != null); + ForEach(p => p.OnAuctionAdd(ah, entry)); + } + public void OnAuctionRemove(AuctionHouseObject ah, AuctionEntry entry) + { + Contract.Assert(ah != null); + Contract.Assert(entry != null); + ForEach(p => p.OnAuctionRemove(ah, entry)); + } + public void OnAuctionSuccessful(AuctionHouseObject ah, AuctionEntry entry) + { + Contract.Assert(ah != null); + Contract.Assert(entry != null); + ForEach(p => p.OnAuctionSuccessful(ah, entry)); + } + public void OnAuctionExpire(AuctionHouseObject ah, AuctionEntry entry) + { + Contract.Assert(ah != null); + Contract.Assert(entry != null); + ForEach(p => p.OnAuctionExpire(ah, entry)); + } + + // ConditionScript + public bool OnConditionCheck(Condition condition, ConditionSourceInfo sourceInfo) + { + Contract.Assert(condition != null); + + return RunScriptRet(p => p.OnConditionCheck(condition, sourceInfo), condition.ScriptId, true); + } + + // VehicleScript + public void OnInstall(Vehicle veh) + { + Contract.Assert(veh != null); + Contract.Assert(veh.GetBase().IsTypeId(TypeId.Unit)); + + RunScript(p => p.OnInstall(veh), veh.GetBase().ToCreature().GetScriptId()); + } + public void OnUninstall(Vehicle veh) + { + Contract.Assert(veh != null); + Contract.Assert(veh.GetBase().IsTypeId(TypeId.Unit)); + + RunScript(p => p.OnUninstall(veh), veh.GetBase().ToCreature().GetScriptId()); + } + public void OnReset(Vehicle veh) + { + Contract.Assert(veh != null); + Contract.Assert(veh.GetBase().IsTypeId(TypeId.Unit)); + + RunScript(p => p.OnReset(veh), veh.GetBase().ToCreature().GetScriptId()); + } + public void OnInstallAccessory(Vehicle veh, Creature accessory) + { + Contract.Assert(veh != null); + Contract.Assert(veh.GetBase().IsTypeId(TypeId.Unit)); + Contract.Assert(accessory != null); + + RunScript(p => p.OnInstallAccessory(veh, accessory), veh.GetBase().ToCreature().GetScriptId()); + } + public void OnAddPassenger(Vehicle veh, Unit passenger, sbyte seatId) + { + Contract.Assert(veh != null); + Contract.Assert(veh.GetBase().IsTypeId(TypeId.Unit)); + Contract.Assert(passenger != null); + + RunScript(p => p.OnAddPassenger(veh, passenger, seatId), veh.GetBase().ToCreature().GetScriptId()); + } + public void OnRemovePassenger(Vehicle veh, Unit passenger) + { + Contract.Assert(veh != null); + Contract.Assert(veh.GetBase().IsTypeId(TypeId.Unit)); + Contract.Assert(passenger != null); + + RunScript(p => p.OnRemovePassenger(veh, passenger), veh.GetBase().ToCreature().GetScriptId()); + } + + // DynamicObjectScript + public void OnDynamicObjectUpdate(DynamicObject dynobj, uint diff) + { + Contract.Assert(dynobj != null); + + ForEach(p => p.OnUpdate(dynobj, diff)); + } + + // TransportScript + public void OnAddPassenger(Transport transport, Player player) + { + Contract.Assert(transport != null); + Contract.Assert(player != null); + + RunScript(p => p.OnAddPassenger(transport, player), transport.GetScriptId()); + } + public void OnAddCreaturePassenger(Transport transport, Creature creature) + { + Contract.Assert(transport != null); + Contract.Assert(creature != null); + + RunScript(p => p.OnAddCreaturePassenger(transport, creature), transport.GetScriptId()); + } + public void OnRemovePassenger(Transport transport, Player player) + { + Contract.Assert(transport != null); + Contract.Assert(player != null); + + RunScript(p => p.OnRemovePassenger(transport, player), transport.GetScriptId()); + } + public void OnTransportUpdate(Transport transport, uint diff) + { + Contract.Assert(transport != null); + + RunScript(p => p.OnUpdate(transport, diff), transport.GetScriptId()); + } + public void OnRelocate(Transport transport, uint waypointId, uint mapId, float x, float y, float z) + { + RunScript(p => p.OnRelocate(transport, waypointId, mapId, x, y, z), transport.GetScriptId()); + } + + // AchievementCriteriaScript + public bool OnCriteriaCheck(uint ScriptId, Player source, Unit target) + { + Contract.Assert(source != null); + // target can be NULL. + + return RunScriptRet(p => p.OnCheck(source, target), ScriptId); + } + + // PlayerScript + public void OnPVPKill(Player killer, Player killed) + { + ForEach(p => p.OnPVPKill(killer, killed)); + } + public void OnCreatureKill(Player killer, Creature killed) + { + ForEach(p => p.OnCreatureKill(killer, killed)); + } + public void OnPlayerKilledByCreature(Creature killer, Player killed) + { + ForEach(p => p.OnPlayerKilledByCreature(killer, killed)); + } + public void OnPlayerLevelChanged(Player player, byte oldLevel) + { + ForEach(p => p.OnLevelChanged(player, oldLevel)); + } + public void OnPlayerFreeTalentPointsChanged(Player player, uint newPoints) + { + ForEach(p => p.OnFreeTalentPointsChanged(player, newPoints)); + } + public void OnPlayerTalentsReset(Player player, bool noCost) + { + ForEach(p => p.OnTalentsReset(player, noCost)); + } + public void OnPlayerMoneyChanged(Player player, long amount) + { + ForEach(p => p.OnMoneyChanged(player, amount)); + } + public void OnGivePlayerXP(Player player, uint amount, Unit victim) + { + ForEach(p => p.OnGiveXP(player, amount, victim)); + } + public void OnPlayerReputationChange(Player player, uint factionID, int standing, bool incremental) + { + ForEach(p => p.OnReputationChange(player, factionID, standing, incremental)); + } + public void OnPlayerDuelRequest(Player target, Player challenger) + { + ForEach(p => p.OnDuelRequest(target, challenger)); + } + public void OnPlayerDuelStart(Player player1, Player player2) + { + ForEach(p => p.OnDuelStart(player1, player2)); + } + public void OnPlayerDuelEnd(Player winner, Player loser, DuelCompleteType type) + { + ForEach(p => p.OnDuelEnd(winner, loser, type)); + } + public void OnPlayerChat(Player player, ChatMsg type, Language lang, string msg) + { + ForEach(p => p.OnChat(player, type, lang, msg)); + } + public void OnPlayerChat(Player player, ChatMsg type, Language lang, string msg, Player receiver) + { + ForEach(p => p.OnChat(player, type, lang, msg, receiver)); + } + public void OnPlayerChat(Player player, ChatMsg type, Language lang, string msg, Group group) + { + ForEach(p => p.OnChat(player, type, lang, msg, group)); + } + public void OnPlayerChat(Player player, ChatMsg type, Language lang, string msg, Guild guild) + { + ForEach(p => p.OnChat(player, type, lang, msg, guild)); + } + public void OnPlayerChat(Player player, ChatMsg type, Language lang, string msg, Channel channel) + { + ForEach(p => p.OnChat(player, type, lang, msg, channel)); + } + public void OnPlayerClearEmote(Player player) + { + ForEach(p => p.OnClearEmote(player)); + } + public void OnPlayerTextEmote(Player player, uint textEmote, uint emoteNum, ObjectGuid guid) + { + ForEach(p => p.OnTextEmote(player, textEmote, emoteNum, guid)); + } + public void OnPlayerSpellCast(Player player, Spell spell, bool skipCheck) + { + ForEach(p => p.OnSpellCast(player, spell, skipCheck)); + } + public void OnPlayerLogin(Player player) + { + ForEach(p => p.OnLogin(player)); + } + public void OnPlayerLogout(Player player) + { + ForEach(p => p.OnLogout(player)); + } + public void OnPlayerCreate(Player player) + { + ForEach(p => p.OnCreate(player)); + } + public void OnPlayerDelete(ObjectGuid guid) + { + ForEach(p => p.OnDelete(guid)); + } + public void OnPlayerSave(Player player) + { + ForEach(p => p.OnSave(player)); + } + public void OnPlayerBindToInstance(Player player, Difficulty difficulty, uint mapid, bool permanent, BindExtensionState extendState) + { + ForEach(p => p.OnBindToInstance(player, difficulty, mapid, permanent, extendState)); + } + public void OnPlayerUpdateZone(Player player, uint newZone, uint newArea) + { + ForEach(p => p.OnUpdateZone(player, newZone, newArea)); + } + public void OnQuestStatusChange(Player player, uint questId) + { + ForEach(p => p.OnQuestStatusChange(player, questId)); + } + public void OnMovieComplete(Player player, uint movieId) + { + ForEach(p => p.OnMovieComplete(player, movieId)); + } + + // GuildScript + public void OnGuildAddMember(Guild guild, Player player, byte plRank) + { + ForEach(p => p.OnAddMember(guild, player, plRank)); + } + public void OnGuildRemoveMember(Guild guild, Player player, bool isDisbanding, bool isKicked) + { + ForEach(p => p.OnRemoveMember(guild, player, isDisbanding, isKicked)); + } + public void OnGuildMOTDChanged(Guild guild, string newMotd) + { + ForEach(p => p.OnMOTDChanged(guild, newMotd)); + } + public void OnGuildInfoChanged(Guild guild, string newInfo) + { + ForEach(p => p.OnInfoChanged(guild, newInfo)); + } + public void OnGuildCreate(Guild guild, Player leader, string name) + { + ForEach(p => p.OnCreate(guild, leader, name)); + } + public void OnGuildDisband(Guild guild) + { + ForEach(p => p.OnDisband(guild)); + } + public void OnGuildMemberWitdrawMoney(Guild guild, Player player, ulong amount, bool isRepair) + { + ForEach(p => p.OnMemberWitdrawMoney(guild, player, amount, isRepair)); + } + public void OnGuildMemberDepositMoney(Guild guild, Player player, ulong amount) + { + ForEach(p => p.OnMemberDepositMoney(guild, player, amount)); + } + public void OnGuildItemMove(Guild guild, Player player, Item pItem, bool isSrcBank, byte srcContainer, byte srcSlotId, bool isDestBank, byte destContainer, byte destSlotId) + { + ForEach(p => p.OnItemMove(guild, player, pItem, isSrcBank, srcContainer, srcSlotId, isDestBank, destContainer, destSlotId)); + } + public void OnGuildEvent(Guild guild, byte eventType, ulong playerGuid1, ulong playerGuid2, byte newRank) + { + ForEach(p => p.OnEvent(guild, eventType, playerGuid1, playerGuid2, newRank)); + } + public void OnGuildBankEvent(Guild guild, byte eventType, byte tabId, ulong playerGuid, uint itemOrMoney, ushort itemStackCount, byte destTabId) + { + ForEach(p => p.OnBankEvent(guild, eventType, tabId, playerGuid, itemOrMoney, itemStackCount, destTabId)); + } + + // GroupScript + public void OnGroupAddMember(Group group, ObjectGuid guid) + { + Contract.Assert(group); + ForEach(p => p.OnAddMember(group, guid)); + } + public void OnGroupInviteMember(Group group, ObjectGuid guid) + { + Contract.Assert(group); + ForEach(p => p.OnInviteMember(group, guid)); + } + public void OnGroupRemoveMember(Group group, ObjectGuid guid, RemoveMethod method, ObjectGuid kicker, string reason) + { + Contract.Assert(group); + ForEach(p => p.OnRemoveMember(group, guid, method, kicker, reason)); + } + public void OnGroupChangeLeader(Group group, ObjectGuid newLeaderGuid, ObjectGuid oldLeaderGuid) + { + Contract.Assert(group); + ForEach(p => p.OnChangeLeader(group, newLeaderGuid, oldLeaderGuid)); + } + public void OnGroupDisband(Group group) + { + Contract.Assert(group); + ForEach(p => p.OnDisband(group)); + } + + // UnitScript + public void OnHeal(Unit healer, Unit reciever, ref uint gain) + { + uint dmg = gain; + ForEach(p => p.OnHeal(healer, reciever, ref dmg)); + gain = dmg; + } + public void OnDamage(Unit attacker, Unit victim, ref uint damage) + { + uint dmg = damage; + ForEach(p => p.OnDamage(attacker, victim, ref dmg)); + damage = dmg; + } + public void ModifyPeriodicDamageAurasTick(Unit target, Unit attacker, ref uint damage) + { + uint dmg = damage; + ForEach(p => p.ModifyPeriodicDamageAurasTick(target, attacker, ref dmg)); + damage = dmg; + } + public void ModifyMeleeDamage(Unit target, Unit attacker, ref uint damage) + { + uint dmg = damage; + ForEach(p => p.ModifyMeleeDamage(target, attacker, ref dmg)); + damage = dmg; + } + public void ModifySpellDamageTaken(Unit target, Unit attacker, ref int damage) + { + int dmg = damage; + ForEach(p => p.ModifySpellDamageTaken(target, attacker, ref dmg)); + damage = dmg; + } + + // AreaTriggerEntityScript + public AreaTriggerAI GetAreaTriggerAI(AreaTrigger areaTrigger) + { + Contract.Assert(areaTrigger); + + return RunScriptRet(p => p.GetAI(areaTrigger), areaTrigger.GetScriptId(), null); + } + + //SceneScript + public void OnSceneStart(Player player, uint sceneInstanceID, SceneTemplate sceneTemplate) + { + Contract.Assert(player); + Contract.Assert(sceneTemplate != null); + + RunScript(script => script.OnSceneStart(player, sceneInstanceID, sceneTemplate), sceneTemplate.ScriptId); + } + public void OnSceneTrigger(Player player, uint sceneInstanceID, SceneTemplate sceneTemplate, string triggerName) + { + Contract.Assert(player); + Contract.Assert(sceneTemplate != null); + + RunScript(script => script.OnSceneTriggerEvent(player, sceneInstanceID, sceneTemplate, triggerName), sceneTemplate.ScriptId); + } + public void OnSceneCancel(Player player, uint sceneInstanceID, SceneTemplate sceneTemplate) + { + Contract.Assert(player); + Contract.Assert(sceneTemplate != null); + + RunScript(script => script.OnSceneCancel(player, sceneInstanceID, sceneTemplate), sceneTemplate.ScriptId); + } + public void OnSceneComplete(Player player, uint sceneInstanceID, SceneTemplate sceneTemplate) + { + Contract.Assert(player); + Contract.Assert(sceneTemplate != null); + + RunScript(script => script.OnSceneComplete(player, sceneInstanceID, sceneTemplate), sceneTemplate.ScriptId); + } + + public void ForEach(Action a) where T : ScriptObject + { + var reg = GetScriptRegistry(); + if (reg == null || reg.Empty()) + return; + + foreach (var script in reg.GetStorage()) + a.Invoke(script); + } + public bool RunScriptRet(Func func, uint id, bool ret = false) where T : ScriptObject + { + return RunScriptRet(func, id, ret); + } + public U RunScriptRet(Func func, uint id, U ret = default(U)) where T : ScriptObject + { + var reg = GetScriptRegistry(); + if (reg == null || reg.Empty()) + return ret; + + var script = reg.GetScriptById(id); + if (script == null) + return ret; + + return func.Invoke(script); + } + public void RunScript(Action a, uint id) where T : ScriptObject + { + var reg = GetScriptRegistry(); + if (reg == null || reg.Empty()) + return; + + var script = reg.GetScriptById(id); + if (script != null) + a.Invoke(script); + } + public void AddScript(T script) where T : ScriptObject + { + Contract.Assert(script != null); + + if (!ScriptStorage.ContainsKey(typeof(T))) + ScriptStorage[typeof(T)] = new ScriptRegistry(); + + GetScriptRegistry().AddScript(script); + } + + public List GetPointMoveList(uint creatureEntry) + { + return m_mPointMoveMap.LookupByKey(creatureEntry); + } + + ScriptRegistry GetScriptRegistry() where T : ScriptObject + { + if (ScriptStorage.ContainsKey(typeof(T))) + return (ScriptRegistry)ScriptStorage[typeof(T)]; + + return null; + } + + uint _ScriptCount; + public Dictionary spellSummaryStorage = new Dictionary(); + Hashtable ScriptStorage = new Hashtable(); + + MultiMap m_mPointMoveMap = new MultiMap(); + + // creature entry + chain ID + MultiMap, SplineChainLink> m_mSplineChainsMap = new MultiMap, SplineChainLink>(); // spline chains + } + + public class ScriptRegistry where TValue : ScriptObject + { + public void AddScript(TValue script) + { + Contract.Assert(script != null); + + if (!script.IsDatabaseBound()) + { + // We're dealing with a code-only script; just add it. + ScriptMap[_scriptIdCounter++] = script; + Global.ScriptMgr.IncrementScriptCount(); + return; + } + + // Get an ID for the script. An ID only exists if it's a script that is assigned in the database + // through a script name (or similar). + uint id = Global.ObjectMgr.GetScriptId(script.GetName()); + if (id != 0) + { + // Try to find an existing script. + bool existing = false; + foreach (var it in ScriptMap) + { + if (it.Value.GetName() == script.GetName()) + { + existing = true; + break; + } + } + + // If the script isn't assigned . assign it! + if (!existing) + { + ScriptMap[id] = script; + Global.ScriptMgr.IncrementScriptCount(); + } + else + { + // If the script is already assigned . delete it! + Log.outError(LogFilter.Scripts, "Script '{0}' already assigned with the same script name, so the script can't work.", script.GetName()); + + Contract.Assert(false); // Error that should be fixed ASAP. + } + } + else + { + // The script uses a script name from database, but isn't assigned to anything. + Log.outError(LogFilter.Sql, "Script named '{0}' does not have a script name assigned in database.", script.GetName()); + return; + } + } + + // Gets a script by its ID (assigned by ObjectMgr). + public TValue GetScriptById(uint id) + { + return ScriptMap.LookupByKey(id); + } + + public bool Empty() + { + return ScriptMap.Empty(); + } + + public List GetStorage() + { + return ScriptMap.Values.ToList(); + } + + public void Unload() + { + foreach (var key in ScriptMap.Keys) + ScriptMap.Remove(key); + } + + // Counter used for code-only scripts. + uint _scriptIdCounter; + Dictionary ScriptMap = new Dictionary(); + } + + public class ScriptPointMove + { + public uint uiCreatureEntry; + public uint uiPointId; + public float fX; + public float fY; + public float fZ; + public uint uiWaitTime; + } + + public class SpellSummary + { + public byte Targets; // set of enum SelectTarget + public byte Effects; // set of enum SelectEffect + } + + [AttributeUsage(AttributeTargets.Class, AllowMultiple = true)] + public class ScriptAttribute : Attribute + { + public ScriptAttribute(params object[] args) + { + Args = args; + } + + public object[] Args { get; private set; } + } +} diff --git a/Game/Scripting/SpellScript.cs b/Game/Scripting/SpellScript.cs new file mode 100644 index 000000000..11de73db1 --- /dev/null +++ b/Game/Scripting/SpellScript.cs @@ -0,0 +1,1401 @@ +/* + * Copyright (C) 2012-2017 CypherCore + * + * 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, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +using Framework.Constants; +using Game.Entities; +using Game.Spells; +using System; +using System.Collections.Generic; +using System.Diagnostics.Contracts; + +namespace Game.Scripting +{ + // helper class from which SpellScript and SpellAura derive, use these classes instead + public class BaseSpellScript + { + // internal use classes & functions + // DO NOT OVERRIDE THESE IN SCRIPTS + public BaseSpellScript() + { + m_currentScriptState = SpellScriptHookType.None; + } + + public virtual bool _Validate(SpellInfo entry) + { + if (!Validate(entry)) + { + Log.outError(LogFilter.Scripts, "Spell `{0}` did not pass Validate() function of script `{1}` - script will be not added to the spell", entry.Id, m_scriptName); + return false; + } + return true; + } + + public bool ValidateSpellInfo(params uint[] spellIds) + { + bool allValid = true; + foreach (uint spellId in spellIds) + { + if (!Global.SpellMgr.HasSpellInfo(spellId)) + { + Log.outError(LogFilter.Scripts, "BaseSpellScript.ValidateSpellInfo: Spell {0} does not exist.", spellId); + allValid = false; + } + } + + return allValid; + } + + public void _Register() + { + m_currentScriptState = SpellScriptHookType.Registration; + Register(); + m_currentScriptState = SpellScriptHookType.None; + } + public void _Unload() + { + m_currentScriptState = SpellScriptHookType.Unloading; + Unload(); + m_currentScriptState = SpellScriptHookType.None; + } + + public void _Init(string scriptname, uint spellId) + { + m_currentScriptState = SpellScriptHookType.None; + m_scriptName = scriptname; + m_scriptSpellId = spellId; + } + public string _GetScriptName() + { + return m_scriptName; + } + + public abstract class EffectHook + { + public EffectHook(uint effIndex) + { + // effect index must be in range <0;2>, allow use of special effindexes + Contract.Assert(_effIndex == SpellConst.EffectAll || _effIndex == SpellConst.EffectFirstFound || _effIndex < SpellConst.MaxEffects); + _effIndex = effIndex; + } + + public uint GetAffectedEffectsMask(SpellInfo spellEntry) + { + uint mask = 0; + if ((_effIndex == SpellConst.EffectAll) || (_effIndex == SpellConst.EffectFirstFound)) + { + for (byte i = 0; i < SpellConst.MaxEffects; ++i) + { + if ((_effIndex == SpellConst.EffectFirstFound) && mask != 0) + return mask; + if (CheckEffect(spellEntry, i)) + mask |= (1u << i); + } + } + else + { + if (CheckEffect(spellEntry, _effIndex)) + mask |= (1u << (int)_effIndex); + } + return mask; + } + + public bool IsEffectAffected(SpellInfo spellEntry, uint effIndex) + { + return Convert.ToBoolean(GetAffectedEffectsMask(spellEntry) & (1 << (int)effIndex)); + } + + public abstract bool CheckEffect(SpellInfo spellEntry, uint effIndex); + + uint _effIndex; + } + + public SpellScriptHookType m_currentScriptState { get; set; } + public AuraScriptHookType m_currentAuraScriptState { get; set; } + public string m_scriptName { get; set; } + public uint m_scriptSpellId { get; set; } + + // + // SpellScript/AuraScript interface base + // these functions are safe to override, see notes below for usage instructions + // + // Function in which handler functions are registered, must be implemented in script + public virtual void Register() { } + // Function called on server startup, if returns false script won't be used in core + // use for: dbc/template data presence/correctness checks + public virtual bool Validate(SpellInfo spellEntry) { return true; } + // Function called when script is created, if returns false script will be unloaded afterwards + // use for: initializing local script variables (DO NOT USE CONSTRUCTOR FOR THIS PURPOSE!) + public virtual bool Load() { return true; } + // Function called when script is destroyed + // use for: deallocating memory allocated by script + public virtual void Unload() { } + } + + public class SpellScript : BaseSpellScript + { + // internal use classes & functions + // DO NOT OVERRIDE THESE IN SCRIPTS + public delegate SpellCastResult SpellCheckCastFnType(); + public delegate void SpellEffectFnType(uint index); + public delegate void SpellBeforeHitFnType(SpellMissInfo missInfo); + public delegate void SpellHitFnType(); + public delegate void SpellCastFnType(); + public delegate void SpellObjectAreaTargetSelectFnType(List targets); + public delegate void SpellObjectTargetSelectFnType(ref WorldObject targets); + public delegate void SpellDestinationTargetSelectFnType(ref SpellDestination dest); + + public class CastHandler + { + public CastHandler(SpellCastFnType _pCastHandlerScript) { pCastHandlerScript = _pCastHandlerScript; } + + public void Call() + { + pCastHandlerScript(); + } + + SpellCastFnType pCastHandlerScript; + } + + public class CheckCastHandler + { + public CheckCastHandler(SpellCheckCastFnType checkCastHandlerScript) + { + _checkCastHandlerScript = checkCastHandlerScript; + } + + public SpellCastResult Call() + { + return _checkCastHandlerScript(); + } + + SpellCheckCastFnType _checkCastHandlerScript; + } + + public class EffectHandler : EffectHook + { + public EffectHandler(SpellEffectFnType pEffectHandlerScript, uint effIndex, SpellEffectName effName) : base(effIndex) + { + _pEffectHandlerScript = pEffectHandlerScript; + _effName = effName; + } + + public override bool CheckEffect(SpellInfo spellEntry, uint effIndex) + { + SpellEffectInfo effect = spellEntry.GetEffect(effIndex); + if (effect == null) + return false; + + if (effect.Effect == 0 && _effName == 0) + return true; + if (effect.Effect == 0) + return false; + return (_effName == SpellEffectName.Any) || (effect.Effect == _effName); + } + + public void Call(uint effIndex) + { + _pEffectHandlerScript(effIndex); + } + + SpellEffectName _effName; + SpellEffectFnType _pEffectHandlerScript; + } + + public class BeforeHitHandler + { + public BeforeHitHandler(SpellBeforeHitFnType pBeforeHitHandlerScript) + { + _pBeforeHitHandlerScript = pBeforeHitHandlerScript; + } + + public void Call(SpellMissInfo missInfo) + { + _pBeforeHitHandlerScript(missInfo); + } + + SpellBeforeHitFnType _pBeforeHitHandlerScript; + } + + public class HitHandler + { + public HitHandler(SpellHitFnType pHitHandlerScript) + { + _pHitHandlerScript = pHitHandlerScript; + } + + public void Call() + { + _pHitHandlerScript(); + } + + SpellHitFnType _pHitHandlerScript; + } + + public class TargetHook : EffectHook + { + public TargetHook(uint effectIndex, Targets targetType, bool area, bool dest = false) : base(effectIndex) + { + _targetType = targetType; + _area = area; + _dest = dest; + } + + public override bool CheckEffect(SpellInfo spellEntry, uint effIndex) + { + if (_targetType == 0) + return false; + + SpellEffectInfo effect = spellEntry.GetEffect(effIndex); + if (effect == null) + return false; + + if (effect.TargetA.GetTarget() != _targetType && effect.TargetB.GetTarget() != _targetType) + return false; + + SpellImplicitTargetInfo targetInfo = new SpellImplicitTargetInfo(_targetType); + switch (targetInfo.GetSelectionCategory()) + { + case SpellTargetSelectionCategories.Channel: // SINGLE + return !_area; + case SpellTargetSelectionCategories.Nearby: // BOTH + return true; + case SpellTargetSelectionCategories.Cone: // AREA + case SpellTargetSelectionCategories.Area: // AREA + return _area; + case SpellTargetSelectionCategories.Default: + switch (targetInfo.GetObjectType()) + { + case SpellTargetObjectTypes.Src: // EMPTY + return false; + case SpellTargetObjectTypes.Dest: // Dest + return _dest; + default: + switch (targetInfo.GetReferenceType()) + { + case SpellTargetReferenceTypes.Caster: // SINGLE + return !_area; + case SpellTargetReferenceTypes.Target: // BOTH + return true; + default: + break; + } + break; + } + break; + default: + break; + } + + return false; + } + + public Targets GetTarget() { return _targetType; } + + Targets _targetType; + bool _area; + bool _dest; + } + + public class ObjectAreaTargetSelectHandler : TargetHook + { + public ObjectAreaTargetSelectHandler(SpellObjectAreaTargetSelectFnType pObjectAreaTargetSelectHandlerScript, uint effIndex, Targets targetType) + : base(effIndex, targetType, true) + { + _pObjectAreaTargetSelectHandlerScript = pObjectAreaTargetSelectHandlerScript; + } + + public void Call(List targets) + { + _pObjectAreaTargetSelectHandlerScript(targets); + } + + SpellObjectAreaTargetSelectFnType _pObjectAreaTargetSelectHandlerScript; + } + + public class ObjectTargetSelectHandler : TargetHook + { + public ObjectTargetSelectHandler(SpellObjectTargetSelectFnType _pObjectTargetSelectHandlerScript, uint _effIndex, Targets _targetType) + : base(_effIndex, _targetType, false) + { + pObjectTargetSelectHandlerScript = _pObjectTargetSelectHandlerScript; + } + + public void Call(ref WorldObject target) + { + pObjectTargetSelectHandlerScript(ref target); + } + + SpellObjectTargetSelectFnType pObjectTargetSelectHandlerScript; + } + + public class DestinationTargetSelectHandler : TargetHook + { + public DestinationTargetSelectHandler(SpellDestinationTargetSelectFnType _DestinationTargetSelectHandlerScript, uint _effIndex, Targets _targetType) + : base(_effIndex, _targetType, false, true) + { + DestinationTargetSelectHandlerScript = _DestinationTargetSelectHandlerScript; + } + + public void Call(ref SpellDestination target) + { + DestinationTargetSelectHandlerScript(ref target); + } + + SpellDestinationTargetSelectFnType DestinationTargetSelectHandlerScript; + } + + public override bool _Validate(SpellInfo entry) + { + foreach (var eff in OnEffectLaunch) + if (eff.GetAffectedEffectsMask(entry) == 0) + Log.outError(LogFilter.Scripts, "Spell `{0}` Effect `{1}` of script `{2}` did not match dbc effect data - handler bound to hook `OnEffectLaunch` of SpellScript won't be executed", entry.Id, eff.ToString(), m_scriptName); + + foreach (var eff in OnEffectLaunchTarget) + if (eff.GetAffectedEffectsMask(entry) == 0) + Log.outError(LogFilter.Scripts, "Spell `{0}` Effect `{1}` of script `{2}` did not match dbc effect data - handler bound to hook `OnEffectLaunchTarget` of SpellScript won't be executed", entry.Id, eff.ToString(), m_scriptName); + + foreach (var eff in OnEffectHit) + if (eff.GetAffectedEffectsMask(entry) == 0) + Log.outError(LogFilter.Scripts, "Spell `{0}` Effect `{1}` of script `{2}` did not match dbc effect data - handler bound to hook `OnEffectHit` of SpellScript won't be executed", entry.Id, eff.ToString(), m_scriptName); + + foreach (var eff in OnEffectHitTarget) + if (eff.GetAffectedEffectsMask(entry) == 0) + Log.outError(LogFilter.Scripts, "Spell `{0}` Effect `{1}` of script `{2}` did not match dbc effect data - handler bound to hook `OnEffectHitTarget` of SpellScript won't be executed", entry.Id, eff.ToString(), m_scriptName); + + foreach (var eff in OnEffectSuccessfulDispel) + if (eff.GetAffectedEffectsMask(entry) == 0) + Log.outError(LogFilter.Scripts, "Spell `{0}` Effect `{1}` of script `{2}` did not match dbc effect data - handler bound to hook `OnEffectSuccessfulDispel` of SpellScript won't be executed", entry.Id, eff.ToString(), m_scriptName); + + foreach (var eff in OnObjectAreaTargetSelect) + if (eff.GetAffectedEffectsMask(entry) == 0) + Log.outError(LogFilter.Scripts, "Spell `{0}` Effect `{1}` of script `{2}` did not match dbc effect data - handler bound to hook `OnObjectAreaTargetSelect` of SpellScript won't be executed", entry.Id, eff.ToString(), m_scriptName); + + foreach (var eff in OnObjectTargetSelect) + if (eff.GetAffectedEffectsMask(entry) == 0) + Log.outError(LogFilter.Scripts, "Spell `{0}` Effect `{1}` of script `{2}` did not match dbc effect data - handler bound to hook `OnObjectTargetSelect` of SpellScript won't be executed", entry.Id, eff.ToString(), m_scriptName); + + return base._Validate(entry); + } + public bool _Load(Spell spell) + { + m_spell = spell; + _PrepareScriptCall(SpellScriptHookType.Loading); + bool load = Load(); + _FinishScriptCall(); + return load; + } + public void _InitHit() + { + m_hitPreventEffectMask = 0; + m_hitPreventDefaultEffectMask = 0; + } + public bool _IsEffectPrevented(uint effIndex) { return Convert.ToBoolean(m_hitPreventEffectMask & (1 << (int)effIndex)); } + public bool _IsDefaultEffectPrevented(uint effIndex) { return Convert.ToBoolean(m_hitPreventDefaultEffectMask & (1 << (int)effIndex)); } + public void _PrepareScriptCall(SpellScriptHookType hookType) + { + m_currentScriptState = hookType; + } + public void _FinishScriptCall() + { + m_currentScriptState = SpellScriptHookType.None; + } + public bool IsInCheckCastHook() + { + return m_currentScriptState == SpellScriptHookType.CheckCast; + } + public bool IsInTargetHook() + { + switch (m_currentScriptState) + { + case SpellScriptHookType.LaunchTarget: + case SpellScriptHookType.EffectHitTarget: + case SpellScriptHookType.EffectSuccessfulDispel: + case SpellScriptHookType.BeforeHit: + case SpellScriptHookType.OnHit: + case SpellScriptHookType.AfterHit: + return true; + } + return false; + } + public bool IsInHitPhase() + { + return (m_currentScriptState >= SpellScriptHookType.EffectHit && m_currentScriptState < SpellScriptHookType.AfterHit + 1); + } + public bool IsInEffectHook() + { + return (m_currentScriptState >= SpellScriptHookType.Launch && m_currentScriptState <= SpellScriptHookType.EffectHitTarget) + || m_currentScriptState == SpellScriptHookType.EffectSuccessfulDispel; + } + + Spell m_spell; + uint m_hitPreventEffectMask; + uint m_hitPreventDefaultEffectMask; + + // SpellScript interface + // hooks to which you can attach your functions + public List BeforeCast = new List(); + public List OnCast = new List(); + public List AfterCast = new List(); + + // where function is SpellCastResult function() + public List OnCheckCast = new List(); + + // where function is void function(uint effIndex) + public List OnEffectLaunch = new List(); + public List OnEffectLaunchTarget = new List(); + public List OnEffectHit = new List(); + public List OnEffectHitTarget = new List(); + public List OnEffectSuccessfulDispel = new List(); + + public List BeforeHit = new List(); + public List OnHit = new List(); + public List AfterHit = new List(); + + // where function is void function(List targets) + public List OnObjectAreaTargetSelect = new List(); + + // where function is void function(ref WorldObject target) + public List OnObjectTargetSelect = new List(); + + // where function is void function(SpellDestination target) + public List OnDestinationTargetSelect = new List(); + + // hooks are executed in following order, at specified event of spell: + // 1. BeforeCast - executed when spell preparation is finished (when cast bar becomes full) before cast is handled + // 2. OnCheckCast - allows to override result of CheckCast function + // 3a. OnObjectAreaTargetSelect - executed just before adding selected targets to final target list (for area targets) + // 3b. OnObjectTargetSelect - executed just before adding selected target to final target list (for single unit targets) + // 4. OnCast - executed just before spell is launched (creates missile) or executed + // 5. AfterCast - executed after spell missile is launched and immediate spell actions are done + // 6. OnEffectLaunch - executed just before specified effect handler call - when spell missile is launched + // 7. OnEffectLaunchTarget - executed just before specified effect handler call - when spell missile is launched - called for each target from spell target map + // 8. OnEffectHit - executed just before specified effect handler call - when spell missile hits dest + // 9. BeforeHit - executed just before spell hits a target - called for each target from spell target map + // 10. OnEffectHitTarget - executed just before specified effect handler call - called for each target from spell target map + // 11. OnHit - executed just before spell deals damage and procs auras - when spell hits target - called for each target from spell target map + // 12. AfterHit - executed just after spell finishes all it's jobs for target - called for each target from spell target map + + // + // methods allowing interaction with Spell object + // + // methods useable during all spell handling phases + public Unit GetCaster() { return m_spell.GetCaster(); } + public Unit GetOriginalCaster() { return m_spell.GetOriginalCaster(); } + public SpellInfo GetSpellInfo() { return m_spell.GetSpellInfo(); } + SpellValue GetSpellValue() { return m_spell.m_spellValue; } + + public SpellEffectInfo GetEffectInfo(uint effIndex) + { + return m_spell.GetEffect(effIndex); + } + + // methods useable after spell is prepared + // accessors to the explicit targets of the spell + // explicit target - target selected by caster (player, game client, or script - DoCast(explicitTarget, ...), required for spell to be cast + // examples: + // -shadowstep - explicit target is the unit you want to go behind of + // -chain heal - explicit target is the unit to be healed first + // -holy nova/arcane explosion - explicit target = null because target you are selecting doesn't affect how spell targets are selected + // you can determine if spell requires explicit targets by dbc columns: + // - Targets - mask of explicit target types + // - ImplicitTargetXX set to TARGET_XXX_TARGET_YYY, _TARGET_ here means that explicit target is used by the effect, so spell needs one too + + // returns: WorldLocation which was selected as a spell destination or null + public WorldLocation GetExplTargetDest() + { + if (m_spell.m_targets.HasDst()) + return m_spell.m_targets.GetDstPos(); + return null; + } + + public void SetExplTargetDest(WorldLocation loc) + { + m_spell.m_targets.SetDst(loc); + } + + // returns: WorldObject which was selected as an explicit spell target or null if there's no target + public WorldObject GetExplTargetWorldObject() { return m_spell.m_targets.GetObjectTarget(); } + + // returns: Unit which was selected as an explicit spell target or null if there's no target + public Unit GetExplTargetUnit() { return m_spell.m_targets.GetUnitTarget(); } + + // returns: GameObject which was selected as an explicit spell target or null if there's no target + GameObject GetExplTargetGObj() { return m_spell.m_targets.GetGOTarget(); } + + // returns: Item which was selected as an explicit spell target or null if there's no target + Item GetExplTargetItem() { return m_spell.m_targets.GetItemTarget(); } + + // methods useable only during spell hit on target, or during spell launch on target: + // returns: target of current effect if it was Unit otherwise null + public Unit GetHitUnit() + { + if (!IsInTargetHook()) + { + Log.outError(LogFilter.Scripts, "Script: `{0}` Spell: `{1}`: function SpellScript.GetHitUnit was called, but function has no effect in current hook!", m_scriptName, m_scriptSpellId); + return null; + } + return m_spell.unitTarget; + } + // returns: target of current effect if it was Creature otherwise null + public Creature GetHitCreature() + { + if (!IsInTargetHook()) + { + Log.outError(LogFilter.Scripts, "Script: `{0}` Spell: `{1}`: function SpellScript.GetHitCreature was called, but function has no effect in current hook!", m_scriptName, m_scriptSpellId); + return null; + } + if (m_spell.unitTarget != null) + return m_spell.unitTarget.ToCreature(); + else + return null; + } + // returns: target of current effect if it was Player otherwise null + public Player GetHitPlayer() + { + if (!IsInTargetHook()) + { + Log.outError(LogFilter.Scripts, "Script: `{0}` Spell: `{1}`: function SpellScript.GetHitPlayer was called, but function has no effect in current hook!", m_scriptName, m_scriptSpellId); + return null; + } + if (m_spell.unitTarget != null) + return m_spell.unitTarget.ToPlayer(); + else + return null; + } + // returns: target of current effect if it was Item otherwise null + Item GetHitItem() + { + if (!IsInTargetHook()) + { + Log.outError(LogFilter.Scripts, "Script: `{0}` Spell: `{1}`: function SpellScript.GetHitItem was called, but function has no effect in current hook!", m_scriptName, m_scriptSpellId); + return null; + } + return m_spell.itemTarget; + } + // returns: target of current effect if it was GameObject otherwise null + public GameObject GetHitGObj() + { + if (!IsInTargetHook()) + { + Log.outError(LogFilter.Scripts, "Script: `{0}` Spell: `{1}`: function SpellScript.GetHitGObj was called, but function has no effect in current hook!", m_scriptName, m_scriptSpellId); + return null; + } + return m_spell.gameObjTarget; + } + // returns: destination of current effect + public WorldLocation GetHitDest() + { + if (!IsInEffectHook()) + { + Log.outError(LogFilter.Scripts, "Script: `{0}` Spell: `{1}`: function SpellScript.GetHitDest was called, but function has no effect in current hook!", m_scriptName, m_scriptSpellId); + return null; + } + return m_spell.destTarget; + } + // setter/getter for for damage done by spell to target of spell hit + // returns damage calculated before hit, and real dmg done after hit + public int GetHitDamage() + { + if (!IsInTargetHook()) + { + Log.outError(LogFilter.Scripts, "Script: `{0}` Spell: `{1}`: function SpellScript.GetHitDamage was called, but function has no effect in current hook!", m_scriptName, m_scriptSpellId); + return 0; + } + return m_spell.m_damage; + } + public void SetHitDamage(int damage) + { + if (!IsInTargetHook()) + { + Log.outError(LogFilter.Scripts, "Script: `{0}` Spell: `{1}`: function SpellScript.SetHitDamage was called, but function has no effect in current hook!", m_scriptName, m_scriptSpellId); + return; + } + m_spell.m_damage = damage; + } + void PreventHitDamage() { SetHitDamage(0); } + // setter/getter for for heal done by spell to target of spell hit + // returns healing calculated before hit, and real dmg done after hit + public int GetHitHeal() + { + if (!IsInTargetHook()) + { + Log.outError(LogFilter.Scripts, "Script: `{0}` Spell: `{1}`: function SpellScript.GetHitHeal was called, but function has no effect in current hook!", m_scriptName, m_scriptSpellId); + return 0; + } + return m_spell.m_healing; + } + public void SetHitHeal(int heal) + { + if (!IsInTargetHook()) + { + Log.outError(LogFilter.Scripts, "Script: `{0}` Spell: `{1}`: function SpellScript.SetHitHeal was called, but function has no effect in current hook!", m_scriptName, m_scriptSpellId); + return; + } + m_spell.m_healing = heal; + } + void PreventHitHeal() { SetHitHeal(0); } + public Spell GetSpell() { return m_spell; } + // returns current spell hit target aura + public Aura GetHitAura() + { + if (!IsInTargetHook()) + { + Log.outError(LogFilter.Scripts, "Script: `{0}` Spell: `{1}`: function SpellScript.GetHitAura was called, but function has no effect in current hook!", m_scriptName, m_scriptSpellId); + return null; + } + if (m_spell.m_spellAura == null) + return null; + if (m_spell.m_spellAura.IsRemoved()) + return null; + return m_spell.m_spellAura; + } + // prevents applying aura on current spell hit target + void PreventHitAura() + { + if (!IsInTargetHook()) + { + Log.outError(LogFilter.Scripts, "Script: `{0}` Spell: `{1}`: function SpellScript.PreventHitAura was called, but function has no effect in current hook!", m_scriptName, m_scriptSpellId); + return; + } + if (m_spell.m_spellAura != null) + m_spell.m_spellAura.Remove(); + } + + // prevents effect execution on current spell hit target + // including other effect/hit scripts + // will not work on aura/damage/heal + // will not work if effects were already handled + public void PreventHitEffect(uint effIndex) + { + if (!IsInHitPhase() && !IsInEffectHook()) + { + Log.outError(LogFilter.Scripts, "Script: `{0}` Spell: `{1}`: function SpellScript.PreventHitEffect was called, but function has no effect in current hook!", m_scriptName, m_scriptSpellId); + return; + } + m_hitPreventEffectMask |= (1u << (int)effIndex); + PreventHitDefaultEffect(effIndex); + } + + // prevents default effect execution on current spell hit target + // will not work on aura/damage/heal effects + // will not work if effects were already handled + public void PreventHitDefaultEffect(uint effIndex) + { + if (!IsInHitPhase() && !IsInEffectHook()) + { + Log.outError(LogFilter.Scripts, "Script: `{0}` Spell: `{1}`: function SpellScript.PreventHitDefaultEffect was called, but function has no effect in current hook!", m_scriptName, m_scriptSpellId); + return; + } + m_hitPreventDefaultEffectMask |= (1u << (int)effIndex); + } + + public SpellEffectInfo GetEffectInfo() + { + Contract.Assert(IsInEffectHook(), string.Format("Script: `{0}` Spell: `{1}`: function SpellScript::GetEffectInfo was called, but function has no effect in current hook!", m_scriptName, m_scriptSpellId)); + + return m_spell.effectInfo; + } + + // method avalible only in EffectHandler method + public int GetEffectValue() + { + if (!IsInEffectHook()) + { + Log.outError(LogFilter.Scripts, "Script: `{0}` Spell: `{1}`: function SpellScript.PreventHitDefaultEffect was called, but function has no effect in current hook!", m_scriptName, m_scriptSpellId); + return 0; + } + return m_spell.damage; + } + + public void SetEffectValue(int value) + { + if (!IsInEffectHook()) + { + Log.outError(LogFilter.Scripts, "Script: `{0}` Spell: `{1}`: function SpellScript.SetEffectValue was called, but function has no effect in current hook!", m_scriptName, m_scriptSpellId); + return; + } + + m_spell.damage = value; + } + + // returns: cast item if present. + public Item GetCastItem() { return m_spell.m_CastItem; } + + // Creates item. Calls Spell.DoCreateItem method. + public void CreateItem(uint effIndex, uint itemId) { m_spell.DoCreateItem(effIndex, itemId); } + + // Returns SpellInfo from the spell that triggered the current one + public SpellInfo GetTriggeringSpell() { return m_spell.m_triggeredByAuraSpell; } + + // finishes spellcast prematurely with selected error message + public void FinishCast(SpellCastResult result) + { + m_spell.SendCastResult(result); + m_spell.finish(result == SpellCastResult.SpellCastOk); + } + + public void SetCustomCastResultMessage(SpellCustomErrors result) + { + if (!IsInCheckCastHook()) + { + Log.outError(LogFilter.Scripts, "Script: `{0}` Spell: `{1}`: function SpellScript.SetCustomCastResultMessage was called while spell not in check cast phase!", m_scriptName, m_scriptSpellId); + return; + } + + m_spell.m_customError = result; + } + } + + public class AuraScript : BaseSpellScript + { + // internal use classes & functions + // DO NOT OVERRIDE THESE IN SCRIPTS + public delegate bool AuraCheckAreaTargetDelegate(Unit target); + public delegate void AuraDispelDelegate(DispelInfo dispelInfo); + public delegate void AuraEffectApplicationModeDelegate(AuraEffect aura, AuraEffectHandleModes auraMode); + public delegate void AuraEffectPeriodicDelegate(AuraEffect aura); + public delegate void AuraEffectUpdatePeriodicDelegate(AuraEffect aura); + public delegate void AuraEffectCalcAmountDelegate(AuraEffect aura, ref int amount, ref bool canBeRecalculated); + public delegate void AuraEffectCalcPeriodicDelegate(AuraEffect aura, bool isPeriodic, int amplitude); + public delegate void AuraEffectCalcSpellModDelegate(AuraEffect aura, ref SpellModifier spellMod); + public delegate void AuraEffectAbsorbDelegate(AuraEffect aura, DamageInfo damageInfo, ref uint absorbAmount); + public delegate void AuraEffectSplitDelegate(AuraEffect aura, DamageInfo damageInfo, uint splitAmount); + public delegate bool AuraCheckProcDelegate(ProcEventInfo info); + public delegate void AuraProcDelegate(ProcEventInfo info); + public delegate void AuraEffectProcDelegate(AuraEffect aura, ProcEventInfo info); + + public class CheckAreaTargetHandler + { + public CheckAreaTargetHandler(AuraCheckAreaTargetDelegate _pHandlerScript) { pHandlerScript = _pHandlerScript; } + public bool Call(Unit target) + { + return pHandlerScript(target); + } + + AuraCheckAreaTargetDelegate pHandlerScript; + } + public class AuraDispelHandler + { + public AuraDispelHandler(AuraDispelDelegate _pHandlerScript) { pHandlerScript = _pHandlerScript; } + public void Call(DispelInfo dispelInfo) + { + pHandlerScript(dispelInfo); + } + + AuraDispelDelegate pHandlerScript; + } + public class EffectBase : EffectHook + { + public EffectBase(uint _effIndex, AuraType _effName) + : base(_effIndex) + { + effAurName = _effName; + } + + public override bool CheckEffect(SpellInfo spellEntry, uint effIndex) + { + SpellEffectInfo effect = spellEntry.GetEffect(effIndex); + if (effect == null) + return false; + + if (effect.ApplyAuraName == 0 && effAurName == 0) + return true; + if (effect.ApplyAuraName == 0) + return false; + return (effAurName == AuraType.Any) || (effect.ApplyAuraName == effAurName); + } + + AuraType effAurName; + } + + public class EffectPeriodicHandler : EffectBase + { + public EffectPeriodicHandler(AuraEffectPeriodicDelegate _pEffectHandlerScript, byte _effIndex, AuraType _effName) + : base(_effIndex, _effName) + { + pEffectHandlerScript = _pEffectHandlerScript; + } + public void Call(AuraEffect _aurEff) + { + pEffectHandlerScript(_aurEff); + } + AuraEffectPeriodicDelegate pEffectHandlerScript; + } + public class EffectUpdatePeriodicHandler : EffectBase + { + public EffectUpdatePeriodicHandler(AuraEffectUpdatePeriodicDelegate _pEffectHandlerScript, byte _effIndex, AuraType _effName) + : base(_effIndex, _effName) + { + pEffectHandlerScript = _pEffectHandlerScript; + } + public void Call(AuraEffect aurEff) { pEffectHandlerScript(aurEff); } + + AuraEffectUpdatePeriodicDelegate pEffectHandlerScript; + } + public class EffectCalcAmountHandler : EffectBase + { + public EffectCalcAmountHandler(AuraEffectCalcAmountDelegate _pEffectHandlerScript, uint _effIndex, AuraType _effName) + : base(_effIndex, _effName) + { + pEffectHandlerScript = _pEffectHandlerScript; + } + public void Call(AuraEffect aurEff, ref int amount, ref bool canBeRecalculated) + { + pEffectHandlerScript(aurEff, ref amount, ref canBeRecalculated); + } + + public AuraEffectCalcAmountDelegate pEffectHandlerScript; + } + public class EffectCalcPeriodicHandler : EffectBase + { + public EffectCalcPeriodicHandler(AuraEffectCalcPeriodicDelegate _pEffectHandlerScript, byte _effIndex, AuraType _effName) + : base(_effIndex, _effName) + { + pEffectHandlerScript = _pEffectHandlerScript; + } + public void Call(AuraEffect aurEff, ref bool isPeriodic, ref int periodicTimer) + { + pEffectHandlerScript(aurEff, isPeriodic, periodicTimer); + } + + AuraEffectCalcPeriodicDelegate pEffectHandlerScript; + } + public class EffectCalcSpellModHandler : EffectBase + { + public EffectCalcSpellModHandler(AuraEffectCalcSpellModDelegate _pEffectHandlerScript, byte _effIndex, AuraType _effName) + : base(_effIndex, _effName) + { + pEffectHandlerScript = _pEffectHandlerScript; + } + public void Call(AuraEffect aurEff, ref SpellModifier spellMod) + { + pEffectHandlerScript(aurEff, ref spellMod); + } + + AuraEffectCalcSpellModDelegate pEffectHandlerScript; + } + public class EffectApplyHandler : EffectBase + { + public EffectApplyHandler(AuraEffectApplicationModeDelegate _pEffectHandlerScript, byte _effIndex, AuraType _effName, AuraEffectHandleModes _mode) + : base(_effIndex, _effName) + { + pEffectHandlerScript = _pEffectHandlerScript; + mode = _mode; + } + public void Call(AuraEffect _aurEff, AuraEffectHandleModes _mode) + { + if (Convert.ToBoolean(_mode & mode)) + pEffectHandlerScript(_aurEff, _mode); + } + + AuraEffectApplicationModeDelegate pEffectHandlerScript; + AuraEffectHandleModes mode; + } + public class EffectAbsorbHandler : EffectBase + { + public EffectAbsorbHandler(AuraEffectAbsorbDelegate _pEffectHandlerScript, byte _effIndex) + : base(_effIndex, AuraType.SchoolAbsorb) + { + pEffectHandlerScript = _pEffectHandlerScript; + } + + public void Call(AuraEffect aurEff, DamageInfo dmgInfo, ref uint absorbAmount) + { + pEffectHandlerScript(aurEff, dmgInfo, ref absorbAmount); + } + + AuraEffectAbsorbDelegate pEffectHandlerScript; + } + public class EffectManaShieldHandler : EffectBase + { + public EffectManaShieldHandler(AuraEffectAbsorbDelegate _pEffectHandlerScript, byte _effIndex) + : base(_effIndex, AuraType.ManaShield) + { + pEffectHandlerScript = _pEffectHandlerScript; + } + public void Call(AuraEffect aurEff, DamageInfo dmgInfo, ref uint absorbAmount) + { + pEffectHandlerScript(aurEff, dmgInfo, ref absorbAmount); + } + + AuraEffectAbsorbDelegate pEffectHandlerScript; + } + public class EffectSplitHandler : EffectBase + { + public EffectSplitHandler(AuraEffectSplitDelegate _pEffectHandlerScript, byte _effIndex) + : base(_effIndex, AuraType.SplitDamagePct) + { + pEffectHandlerScript = _pEffectHandlerScript; + } + public void Call(AuraEffect aurEff, DamageInfo dmgInfo, uint splitAmount) + { + pEffectHandlerScript(aurEff, dmgInfo, splitAmount); + } + + AuraEffectSplitDelegate pEffectHandlerScript; + } + public class CheckProcHandler + { + public CheckProcHandler(AuraCheckProcDelegate handlerScript) + { + _HandlerScript = handlerScript; + } + public bool Call(ProcEventInfo eventInfo) + { + return _HandlerScript(eventInfo); + } + + AuraCheckProcDelegate _HandlerScript; + } + public class AuraProcHandler + { + public AuraProcHandler(AuraProcDelegate handlerScript) + { + _HandlerScript = handlerScript; + } + public void Call(ProcEventInfo eventInfo) + { + _HandlerScript(eventInfo); + } + + AuraProcDelegate _HandlerScript; + } + public class EffectProcHandler : EffectBase + { + public EffectProcHandler(AuraEffectProcDelegate effectHandlerScript, byte effIndex, AuraType effName) : base(effIndex, effName) + { + _EffectHandlerScript = effectHandlerScript; + } + public void Call(AuraEffect aurEff, ProcEventInfo eventInfo) + { + _EffectHandlerScript(aurEff, eventInfo); + } + + AuraEffectProcDelegate _EffectHandlerScript; + } + + public AuraScript() + { + m_aura = null; + m_auraApplication = null; + m_defaultActionPrevented = false; + } + + public override bool _Validate(SpellInfo entry) + { + foreach (var eff in DoCheckAreaTarget) + if (!entry.HasAreaAuraEffect() && !entry.HasEffect(SpellEffectName.PersistentAreaAura) && !entry.HasEffect(SpellEffectName.ApplyAura)) + Log.outError(LogFilter.Scripts, "Spell `{0}` of script `{1}` does not have apply aura effect - handler bound to hook `DoCheckAreaTarget` of AuraScript won't be executed", entry.Id, m_scriptName); + + foreach (var eff in OnDispel) + if (!entry.HasEffect(SpellEffectName.ApplyAura) && !entry.HasAreaAuraEffect()) + Log.outError(LogFilter.Scripts, "Spell `{0}` of script `{1}` does not have apply aura effect - handler bound to hook `OnDispel` of AuraScript won't be executed", entry.Id, m_scriptName); + + foreach (var eff in AfterDispel) + if (!entry.HasEffect(SpellEffectName.ApplyAura) && !entry.HasAreaAuraEffect()) + Log.outError(LogFilter.Scripts, "Spell `{0}` of script `{1}` does not have apply aura effect - handler bound to hook `AfterDispel` of AuraScript won't be executed", entry.Id, m_scriptName); + + foreach (var eff in OnEffectApply) + if (eff.GetAffectedEffectsMask(entry) == 0) + Log.outError(LogFilter.Scripts, "Spell `{0}` Effect `{1}` of script `{2}` did not match dbc effect data - handler bound to hook `OnEffectApply` of AuraScript won't be executed", entry.Id, eff.ToString(), m_scriptName); + + foreach (var eff in OnEffectRemove) + if (eff.GetAffectedEffectsMask(entry) == 0) + Log.outError(LogFilter.Scripts, "Spell `{0}` Effect `{1}` of script `{2}` did not match dbc effect data - handler bound to hook `OnEffectRemove` of AuraScript won't be executed", entry.Id, eff.ToString(), m_scriptName); + + foreach (var eff in AfterEffectApply) + if (eff.GetAffectedEffectsMask(entry) == 0) + Log.outError(LogFilter.Scripts, "Spell `{0}` Effect `{1}` of script `{2}` did not match dbc effect data - handler bound to hook `AfterEffectApply` of AuraScript won't be executed", entry.Id, eff.ToString(), m_scriptName); + + foreach (var eff in AfterEffectRemove) + if (eff.GetAffectedEffectsMask(entry) == 0) + Log.outError(LogFilter.Scripts, "Spell `{0}` Effect `{1}` of script `{2}` did not match dbc effect data - handler bound to hook `AfterEffectRemove` of AuraScript won't be executed", entry.Id, eff.ToString(), m_scriptName); + + foreach (var eff in OnEffectPeriodic) + if (eff.GetAffectedEffectsMask(entry) == 0) + Log.outError(LogFilter.Scripts, "Spell `{0}` Effect `{1}` of script `{2}` did not match dbc effect data - handler bound to hook `OnEffectPeriodic` of AuraScript won't be executed", entry.Id, eff.ToString(), m_scriptName); + + foreach (var eff in OnEffectUpdatePeriodic) + if (eff.GetAffectedEffectsMask(entry) == 0) + Log.outError(LogFilter.Scripts, "Spell `{0}` Effect `{1}` of script `{2}` did not match dbc effect data - handler bound to hook `OnEffectUpdatePeriodic` of AuraScript won't be executed", entry.Id, eff.ToString(), m_scriptName); + + foreach (var eff in DoEffectCalcAmount) + if (eff.GetAffectedEffectsMask(entry) == 0) + Log.outError(LogFilter.Scripts, "Spell `{0}` Effect `{1}` of script `{2}` did not match dbc effect data - handler bound to hook `DoEffectCalcAmount` of AuraScript won't be executed", entry.Id, eff.ToString(), m_scriptName); + + foreach (var eff in DoEffectCalcPeriodic) + if (eff.GetAffectedEffectsMask(entry) == 0) + Log.outError(LogFilter.Scripts, "Spell `{0}` Effect `{1}` of script `{2}` did not match dbc effect data - handler bound to hook `DoEffectCalcPeriodic` of AuraScript won't be executed", entry.Id, eff.ToString(), m_scriptName); + + foreach (var eff in DoEffectCalcSpellMod) + if (eff.GetAffectedEffectsMask(entry) == 0) + Log.outError(LogFilter.Scripts, "Spell `{0}` Effect `{1}` of script `{2}` did not match dbc effect data - handler bound to hook `DoEffectCalcSpellMod` of AuraScript won't be executed", entry.Id, eff.ToString(), m_scriptName); + + foreach (var eff in OnEffectAbsorb) + if (eff.GetAffectedEffectsMask(entry) == 0) + Log.outError(LogFilter.Scripts, "Spell `{0}` Effect `{1}` of script `{2}` did not match dbc effect data - handler bound to hook `OnEffectAbsorb` of AuraScript won't be executed", entry.Id, eff.ToString(), m_scriptName); + + foreach (var eff in AfterEffectAbsorb) + if (eff.GetAffectedEffectsMask(entry) == 0) + Log.outError(LogFilter.Scripts, "Spell `{0}` Effect `{1}` of script `{2}` did not match dbc effect data - handler bound to hook `AfterEffectAbsorb` of AuraScript won't be executed", entry.Id, eff.ToString(), m_scriptName); + + foreach (var eff in OnEffectManaShield) + if (eff.GetAffectedEffectsMask(entry) == 0) + Log.outError(LogFilter.Scripts, "Spell `{0}` Effect `{1}` of script `{2}` did not match dbc effect data - handler bound to hook `OnEffectManaShield` of AuraScript won't be executed", entry.Id, eff.ToString(), m_scriptName); + + foreach (var eff in AfterEffectManaShield) + if (eff.GetAffectedEffectsMask(entry) == 0) + Log.outError(LogFilter.Scripts, "Spell `{0}` Effect `{1}` of script `{2}` did not match dbc effect data - handler bound to hook `AfterEffectManaShield` of AuraScript won't be executed", entry.Id, eff.ToString(), m_scriptName); + + foreach (var eff in OnEffectSplit) + if (eff.GetAffectedEffectsMask(entry) == 0) + Log.outError(LogFilter.Scripts, "Spell `{0}` Effect `{1}` of script `{2}` did not match dbc effect data - handler bound to hook `OnEffectSplit` of AuraScript won't be executed", entry.Id, eff.ToString(), m_scriptName); + + foreach (var eff in DoCheckProc) + if (!entry.HasEffect(SpellEffectName.ApplyAura) && !entry.HasAreaAuraEffect()) + Log.outError(LogFilter.Scripts, "Spell `{0}` of script `{1}` does not have apply aura effect - handler bound to hook `DoCheckProc` of AuraScript won't be executed", entry.Id, m_scriptName); + + foreach (var eff in DoPrepareProc) + if (!entry.HasEffect(SpellEffectName.ApplyAura) && !entry.HasAreaAuraEffect()) + Log.outError(LogFilter.Scripts, "Spell `{0}` of script `{1}` does not have apply aura effect - handler bound to hook `DoPrepareProc` of AuraScript won't be executed", entry.Id, m_scriptName); + + foreach (var eff in OnProc) + if (!entry.HasEffect(SpellEffectName.ApplyAura) && !entry.HasAreaAuraEffect()) + Log.outError(LogFilter.Scripts, "Spell `{0}` of script `{1}` does not have apply aura effect - handler bound to hook `OnProc` of AuraScript won't be executed", entry.Id, m_scriptName); + + foreach (var eff in AfterProc) + if (!entry.HasEffect(SpellEffectName.ApplyAura) && !entry.HasAreaAuraEffect()) + Log.outError(LogFilter.Scripts, "Spell `{0}` of script `{1}` does not have apply aura effect - handler bound to hook `AfterProc` of AuraScript won't be executed", entry.Id, m_scriptName); + + foreach (var eff in OnEffectProc) + if (eff.GetAffectedEffectsMask(entry) == 0) + Log.outError(LogFilter.Scripts, "Spell `{0}` Effect `{1}` of script `{2}` did not match dbc effect data - handler bound to hook `OnEffectProc` of AuraScript won't be executed", entry.Id, eff.ToString(), m_scriptName); + + foreach (var eff in AfterEffectProc) + if (eff.GetAffectedEffectsMask(entry) == 0) + Log.outError(LogFilter.Scripts, "Spell `{0}` Effect `{1}` of script `{2}` did not match dbc effect data - handler bound to hook `AfterEffectProc` of AuraScript won't be executed", entry.Id, eff.ToString(), m_scriptName); + + return base._Validate(entry); + } + public bool _Load(Aura aura) + { + m_aura = aura; + _PrepareScriptCall(AuraScriptHookType.Loading, null); + bool load = Load(); + _FinishScriptCall(); + return load; + } + public void _PrepareScriptCall(AuraScriptHookType hookType, AuraApplication aurApp = null) + { + m_scriptStates.Push(new ScriptStateStore((byte)m_currentScriptState, m_auraApplication, m_defaultActionPrevented)); + m_currentAuraScriptState = hookType; + m_defaultActionPrevented = false; + m_auraApplication = aurApp; + } + public void _FinishScriptCall() + { + ScriptStateStore stateStore = m_scriptStates.Peek(); + m_currentAuraScriptState = (AuraScriptHookType)stateStore._currentScriptState; + m_auraApplication = stateStore._auraApplication; + m_defaultActionPrevented = stateStore._defaultActionPrevented; + m_scriptStates.Pop(); + } + public bool _IsDefaultActionPrevented() + { + switch (m_currentAuraScriptState) + { + case AuraScriptHookType.EffectApply: + case AuraScriptHookType.EffectRemove: + case AuraScriptHookType.EffectPeriodic: + case AuraScriptHookType.EffectAbsorb: + case AuraScriptHookType.EffectSplit: + case AuraScriptHookType.PrepareProc: + case AuraScriptHookType.EffectProc: + return m_defaultActionPrevented; + default: + Contract.Assert(false, "AuraScript._IsDefaultActionPrevented is called in a wrong place"); + return false; + } + } + + Aura m_aura; + AuraApplication m_auraApplication; + bool m_defaultActionPrevented; + + class ScriptStateStore + { + public AuraApplication _auraApplication; + public byte _currentScriptState; + public bool _defaultActionPrevented; + public ScriptStateStore(byte currentScriptState, AuraApplication auraApplication, bool defaultActionPrevented) + { + _auraApplication = auraApplication; + _currentScriptState = currentScriptState; + _defaultActionPrevented = defaultActionPrevented; + } + } + Stack m_scriptStates = new Stack(); + + // AuraScript interface + // hooks to which you can attach your functions + // + // executed when area aura checks if it can be applied on target + // example: OnEffectApply += AuraEffectApplyFn(class.function); + // where function is: bool function (Unit target); + public List DoCheckAreaTarget = new List(); + + // executed when aura is dispelled by a unit + // example: OnDispel += AuraDispelFn(class.function); + // where function is: void function (DispelInfo dispelInfo); + public List OnDispel = new List(); + + // executed after aura is dispelled by a unit + // example: AfterDispel += AuraDispelFn(class.function); + // where function is: void function (DispelInfo dispelInfo); + public List AfterDispel = new List(); + + // executed when aura effect is applied with specified mode to target + // should be used when when effect handler preventing/replacing is needed, do not use this hook for triggering spellcasts/removing auras etc - may be unsafe + // example: OnEffectApply += AuraEffectApplyFn(class.function, EffectIndexSpecifier, EffectAuraNameSpecifier, AuraEffectHandleModes); + // where function is: void function (AuraEffect aurEff, AuraEffectHandleModes mode); + public List OnEffectApply = new List(); + + // executed after aura effect is applied with specified mode to target + // example: AfterEffectApply += AuraEffectApplyFn(class.function, EffectIndexSpecifier, EffectAuraNameSpecifier, AuraEffectHandleModes); + // where function is: void function (AuraEffect aurEff, AuraEffectHandleModes mode); + public List AfterEffectApply = new List(); + + // executed after aura effect is removed with specified mode from target + // should be used when when effect handler preventing/replacing is needed, do not use this hook for triggering spellcasts/removing auras etc - may be unsafe + // example: OnEffectRemove += AuraEffectRemoveFn(class.function, EffectIndexSpecifier, EffectAuraNameSpecifier, AuraEffectHandleModes); + // where function is: void function (AuraEffect aurEff, AuraEffectHandleModes mode); + public List OnEffectRemove = new List(); + + // executed when aura effect is removed with specified mode from target + // example: AfterEffectRemove += AuraEffectRemoveFn(class.function, EffectIndexSpecifier, EffectAuraNameSpecifier, AuraEffectHandleModes); + // where function is: void function (AuraEffect aurEff, AuraEffectHandleModes mode); + public List AfterEffectRemove = new List(); + + // executed when periodic aura effect ticks on target + // example: OnEffectPeriodic += AuraEffectPeriodicFn(class.function, EffectIndexSpecifier, EffectAuraNameSpecifier); + // where function is: void function (AuraEffect aurEff); + public List OnEffectPeriodic = new List(); + + // executed when periodic aura effect is updated + // example: OnEffectUpdatePeriodic += AuraEffectUpdatePeriodicFn(class.function, EffectIndexSpecifier, EffectAuraNameSpecifier); + // where function is: void function (AuraEffect aurEff); + public List OnEffectUpdatePeriodic = new List(); + + // executed when aura effect calculates amount + // example: DoEffectCalcAmount += AuraEffectCalcAmounFn(class.function, EffectIndexSpecifier, EffectAuraNameSpecifier); + // where function is: void function (AuraEffect aurEff, int& amount, bool& canBeRecalculated); + public List DoEffectCalcAmount = new List(); + + // executed when aura effect calculates periodic data + // example: DoEffectCalcPeriodic += AuraEffectCalcPeriodicFn(class.function, EffectIndexSpecifier, EffectAuraNameSpecifier); + // where function is: void function (AuraEffect aurEff, bool& isPeriodic, int& amplitude); + public List DoEffectCalcPeriodic = new List(); + + // executed when aura effect calculates spellmod + // example: DoEffectCalcSpellMod += AuraEffectCalcSpellModFn(class.function, EffectIndexSpecifier, EffectAuraNameSpecifier); + // where function is: void function (AuraEffect aurEff, SpellModifier& spellMod); + public List DoEffectCalcSpellMod = new List(); + + // executed when absorb aura effect is going to reduce damage + // example: OnEffectAbsorb += AuraEffectAbsorbFn(class.function, EffectIndexSpecifier); + // where function is: void function (AuraEffect aurEff, DamageInfo& dmgInfo, uint& absorbAmount); + public List OnEffectAbsorb = new List(); + + // executed after absorb aura effect reduced damage to target - absorbAmount is real amount absorbed by aura + // example: AfterEffectAbsorb += AuraEffectAbsorbFn(class.function, EffectIndexSpecifier); + // where function is: void function (AuraEffect aurEff, DamageInfo& dmgInfo, uint& absorbAmount); + public List AfterEffectAbsorb = new List(); + + // executed when mana shield aura effect is going to reduce damage + // example: OnEffectManaShield += AuraEffectAbsorbFn(class.function, EffectIndexSpecifier); + // where function is: void function (AuraEffect aurEff, DamageInfo& dmgInfo, uint& absorbAmount); + public List OnEffectManaShield = new List(); + + // executed after mana shield aura effect reduced damage to target - absorbAmount is real amount absorbed by aura + // example: AfterEffectManaShield += AuraEffectAbsorbFn(class.function, EffectIndexSpecifier); + // where function is: void function (AuraEffect aurEff, DamageInfo& dmgInfo, uint& absorbAmount); + public List AfterEffectManaShield = new List(); + + // executed when the caster of some spell with split dmg aura gets damaged through it + // example: OnEffectSplit += AuraEffectSplitFn(class.function, EffectIndexSpecifier); + // where function is: void function (AuraEffect aurEff, DamageInfo& dmgInfo, uint& splitAmount); + public List OnEffectSplit = new List(); + + // executed when aura checks if it can proc + // example: DoCheckProc += AuraCheckProcFn(class.function); + // where function is: bool function (ProcEventInfo& eventInfo); + public List DoCheckProc = new List(); + + // executed before aura procs (possibility to prevent charge drop/cooldown) + // example: DoPrepareProc += AuraProcFn(class.function); + // where function is: void function (ProcEventInfo& eventInfo); + public List DoPrepareProc = new List(); + + // executed when aura procs + // example: OnProc += AuraProcFn(class.function); + // where function is: void function (ProcEventInfo& eventInfo); + public List OnProc = new List(); + + // executed after aura proced + // example: AfterProc += AuraProcFn(class.function); + // where function is: void function (ProcEventInfo& eventInfo); + public List AfterProc = new List(); + + // executed when aura effect procs + // example: OnEffectProc += AuraEffectProcFn(class.function, EffectIndexSpecifier, EffectAuraNameSpecifier); + // where function is: void function (AuraEffect aurEff, ProcEventInfo& procInfo); + public List OnEffectProc = new List(); + + // executed after aura effect proced + // example: AfterEffectProc += AuraEffectProcFn(class.function, EffectIndexSpecifier, EffectAuraNameSpecifier); + // where function is: void function (AuraEffect aurEff, ProcEventInfo& procInfo); + public List AfterEffectProc = new List(); + + // AuraScript interface - hook/effect execution manipulators + + // prevents default action of a hook from being executed (works only while called in a hook which default action can be prevented) + public void PreventDefaultAction() + { + switch (m_currentAuraScriptState) + { + case AuraScriptHookType.EffectApply: + case AuraScriptHookType.EffectRemove: + case AuraScriptHookType.EffectPeriodic: + case AuraScriptHookType.EffectAbsorb: + case AuraScriptHookType.EffectSplit: + case AuraScriptHookType.PrepareProc: + case AuraScriptHookType.EffectProc: + m_defaultActionPrevented = true; + break; + default: + Log.outError(LogFilter.Scripts, "Script: `{0}` Spell: `{1}` AuraScript.PreventDefaultAction called in a hook in which the call won't have effect!", m_scriptName, m_scriptSpellId); + break; + } + } + + // AuraScript interface - functions which are redirecting to Aura class + + // returns proto of the spell + public SpellInfo GetSpellInfo() { return m_aura.GetSpellInfo(); } + // returns spellid of the spell + public uint GetId() { return m_aura.GetId(); } + + // returns guid of object which casted the aura (m_originalCaster of the Spell class) + public ObjectGuid GetCasterGUID() { return m_aura.GetCasterGUID(); } + // returns unit which casted the aura or null if not avalible (caster logged out for example) + public Unit GetCaster() { return m_aura.GetCaster(); } + // returns object on which aura was casted, target for non-area auras, area aura source for area auras + public WorldObject GetOwner() { return m_aura.GetOwner(); } + // returns owner if it's unit or unit derived object, null otherwise (only for persistent area auras null is returned) + public Unit GetUnitOwner() { return m_aura.GetUnitOwner(); } + // returns owner if it's dynobj, null otherwise + DynamicObject GetDynobjOwner() { return m_aura.GetDynobjOwner(); } + + // removes aura with remove mode (see AuraRemoveMode enum) + public void Remove(AuraRemoveMode removeMode = 0) { m_aura.Remove(removeMode); } + // returns aura object of script + public Aura GetAura() { return m_aura; } + + // returns type of the aura, may be dynobj owned aura or unit owned aura + AuraObjectType GetAuraType() { return m_aura.GetAuraType(); } + + // aura duration manipulation - when duration goes to 0 aura is removed + int GetDuration() { return m_aura.GetDuration(); } + public void SetDuration(int duration, bool withMods = false) { m_aura.SetDuration(duration, withMods); } + // sets duration to maxduration + void RefreshDuration() { m_aura.RefreshDuration(); } + long GetApplyTime() { return m_aura.GetApplyTime(); } + public int GetMaxDuration() { return m_aura.GetMaxDuration(); } + void SetMaxDuration(int duration) { m_aura.SetMaxDuration(duration); } + int CalcMaxDuration() { return m_aura.CalcMaxDuration(); } + // expired - duration just went to 0 + public bool IsExpired() { return m_aura.IsExpired(); } + // permament - has infinite duration + bool IsPermanent() { return m_aura.IsPermanent(); } + + // charges manipulation - 0 - not charged aura + byte GetCharges() { return m_aura.GetCharges(); } + void SetCharges(byte charges) { m_aura.SetCharges(charges); } + byte CalcMaxCharges() { return m_aura.CalcMaxCharges(); } + bool ModCharges(sbyte num, AuraRemoveMode removeMode = AuraRemoveMode.Default) { return m_aura.ModCharges(num, removeMode); } + // returns true if last charge dropped + bool DropCharge(AuraRemoveMode removeMode = AuraRemoveMode.Default) { return m_aura.DropCharge(removeMode); } + + // stack amount manipulation + public byte GetStackAmount() { return m_aura.GetStackAmount(); } + void SetStackAmount(byte num) { m_aura.SetStackAmount(num); } + public bool ModStackAmount(int num, AuraRemoveMode removeMode = AuraRemoveMode.Default) { return m_aura.ModStackAmount(num, removeMode); } + + // passive - "working in background", not saved, not removed by immunities, not seen by player + bool IsPassive() { return m_aura.IsPassive(); } + // death persistent - not removed on death + bool IsDeathPersistent() { return m_aura.IsDeathPersistent(); } + + // check if aura has effect of given effindex + public bool HasEffect(byte effIndex) { return m_aura.HasEffect(effIndex); } + // returns aura effect of given effect index or null + public AuraEffect GetEffect(byte effIndex) { return m_aura.GetEffect(effIndex); } + + // check if aura has effect of given aura type + bool HasEffectType(AuraType type) + { + return m_aura.HasEffectType(type); + } + + // AuraScript interface - functions which are redirecting to AuraApplication class + // Do not call these in hooks in which AuraApplication is not avalible, otherwise result will differ from expected (the functions will return null) + + // returns currently processed target of an aura + // Return value does not need to be null-checked, the only situation this will (always) + // return null is when the call happens in an unsupported hook, in other cases, it is always valid + public Unit GetTarget() + { + switch (m_currentAuraScriptState) + { + case AuraScriptHookType.EffectApply: + case AuraScriptHookType.EffectRemove: + case AuraScriptHookType.EffectAfterApply: + case AuraScriptHookType.EffectAfterRemove: + case AuraScriptHookType.EffectPeriodic: + case AuraScriptHookType.EffectAbsorb: + case AuraScriptHookType.EffectAfterAbsorb: + case AuraScriptHookType.EffectManaShield: + case AuraScriptHookType.EffectAfterManaShield: + case AuraScriptHookType.EffectSplit: + case AuraScriptHookType.CheckProc: + case AuraScriptHookType.PrepareProc: + case AuraScriptHookType.Proc: + case AuraScriptHookType.AfterProc: + case AuraScriptHookType.EffectProc: + case AuraScriptHookType.EffectAfterProc: + return m_auraApplication.GetTarget(); + default: + Log.outError(LogFilter.Scripts, "Script: `{0}` Spell: `{1}` AuraScript.GetTarget called in a hook in which the call won't have effect!", m_scriptName, m_scriptSpellId); + break; + } + + return null; + } + // returns AuraApplication object of currently processed target + public AuraApplication GetTargetApplication() { return m_auraApplication; } + } +} diff --git a/Game/Server/WorldConfig.cs b/Game/Server/WorldConfig.cs new file mode 100644 index 000000000..2a357ad03 --- /dev/null +++ b/Game/Server/WorldConfig.cs @@ -0,0 +1,959 @@ +/* + * Copyright (C) 2012-2017 CypherCore + * + * 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, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +using Framework.Configuration; +using Framework.Constants; +using System; +using System.Collections.Generic; + +namespace Game +{ + public class WorldConfig : ConfigMgr + { + public static void Load(bool reload = false) + { + if (reload) + Load("WorldServer.conf"); + + // Read support system setting from the config file + Values[WorldCfg.SupportEnabled] = GetDefaultValue("Support.Enabled", true); + Values[WorldCfg.SupportTicketsEnabled] = GetDefaultValue("Support.TicketsEnabled", false); + Values[WorldCfg.SupportBugsEnabled] = GetDefaultValue("Support.BugsEnabled", false); + Values[WorldCfg.SupportComplaintsEnabled] = GetDefaultValue("Support.ComplaintsEnabled", false); + Values[WorldCfg.SupportSuggestionsEnabled] = GetDefaultValue("Support.SuggestionsEnabled", false); + + // Send server info on login? + Values[WorldCfg.EnableSinfoLogin] = GetDefaultValue("Server.LoginInfo", 0); + + // Read all rates from the config file + Action setRegenRate = (WorldCfg rate, string configKey) => + { + Values[rate] = GetDefaultValue(configKey, 1.0f); + if ((float)Values[rate] < 0.0f) + { + Log.outError(LogFilter.ServerLoading, "{0} ({1}) must be > 0. Using 1 instead.", configKey, Values[rate]); + Values[rate] = 1; + } + }; + + setRegenRate(WorldCfg.RateHealth, "Rate.Health"); + setRegenRate(WorldCfg.RatePowerMana, "Rate.Mana"); + setRegenRate(WorldCfg.RatePowerRageIncome, "Rate.Rage.Gain"); + setRegenRate(WorldCfg.RatePowerRageLoss, "Rate.Rage.Loss"); + setRegenRate(WorldCfg.RatePowerFocus, "Rate.Focus"); + setRegenRate(WorldCfg.RatePowerEnergy, "Rate.Energy"); + setRegenRate(WorldCfg.RatePowerComboPointsLoss, "Rate.ComboPoints.Loss"); + setRegenRate(WorldCfg.RatePowerRunicPowerIncome, "Rate.RunicPower.Gain"); + setRegenRate(WorldCfg.RatePowerRunicPowerLoss, "Rate.RunicPower.Loss"); + setRegenRate(WorldCfg.RatePowerSoulShards, "Rate.SoulShards.Loss"); + setRegenRate(WorldCfg.RatePowerLunarPower, "Rate.LunarPower.Loss"); + setRegenRate(WorldCfg.RatePowerHolyPower, "Rate.HolyPower.Loss"); + setRegenRate(WorldCfg.RatePowerMaelstrom, "Rate.Maelstrom.Loss"); + setRegenRate(WorldCfg.RatePowerChi, "Rate.Chi.Loss"); + setRegenRate(WorldCfg.RatePowerInsanity, "Rate.Insanity.Loss"); + setRegenRate(WorldCfg.RatePowerArcaneCharges, "Rate.ArcaneCharges.Loss"); + setRegenRate(WorldCfg.RatePowerFury, "Rate.Fury.Loss"); + setRegenRate(WorldCfg.RatePowerPain, "Rate.Pain.Loss"); + + Values[WorldCfg.RateSkillDiscovery] = GetDefaultValue("Rate.Skill.Discovery", 1.0f); + Values[WorldCfg.RateDropItemPoor] = GetDefaultValue("Rate.Drop.Item.Poor", 1.0f); + Values[WorldCfg.RateDropItemNormal] = GetDefaultValue("Rate.Drop.Item.Normal", 1.0f); + Values[WorldCfg.RateDropItemUncommon] = GetDefaultValue("Rate.Drop.Item.Uncommon", 1.0f); + Values[WorldCfg.RateDropItemRare] = GetDefaultValue("Rate.Drop.Item.Rare", 1.0f); + Values[WorldCfg.RateDropItemEpic] = GetDefaultValue("Rate.Drop.Item.Epic", 1.0f); + Values[WorldCfg.RateDropItemLegendary] = GetDefaultValue("Rate.Drop.Item.Legendary", 1.0f); + Values[WorldCfg.RateDropItemArtifact] = GetDefaultValue("Rate.Drop.Item.Artifact", 1.0f); + Values[WorldCfg.RateDropItemReferenced] = GetDefaultValue("Rate.Drop.Item.Referenced", 1.0f); + Values[WorldCfg.RateDropItemReferencedAmount] = GetDefaultValue("Rate.Drop.Item.ReferencedAmount", 1.0f); + Values[WorldCfg.RateDropMoney] = GetDefaultValue("Rate.Drop.Money", 1.0f); + Values[WorldCfg.RateXpKill] = GetDefaultValue("Rate.XP.Kill", 1.0f); + Values[WorldCfg.RateXpBgKill] = GetDefaultValue("Rate.XP.BattlegroundKill", 1.0f); + Values[WorldCfg.RateXpQuest] = GetDefaultValue("Rate.XP.Quest", 1.0f); + Values[WorldCfg.RateXpExplore] = GetDefaultValue("Rate.XP.Explore", 1.0f); + Values[WorldCfg.RateRepaircost] = GetDefaultValue("Rate.RepairCost", 1.0f); + if ((float)Values[WorldCfg.RateRepaircost] < 0.0f) + { + Log.outError(LogFilter.ServerLoading, "Rate.RepairCost ({0}) must be >=0. Using 0.0 instead.", Values[WorldCfg.RateRepaircost]); + Values[WorldCfg.RateRepaircost] = 0.0f; + } + Values[WorldCfg.RateReputationGain] = GetDefaultValue("Rate.Reputation.Gain", 1.0f); + Values[WorldCfg.RateReputationLowLevelKill] = GetDefaultValue("Rate.Reputation.LowLevel.Kill", 1.0f); + Values[WorldCfg.RateReputationLowLevelQuest] = GetDefaultValue("Rate.Reputation.LowLevel.Quest", 1.0f); + Values[WorldCfg.RateReputationRecruitAFriendBonus] = GetDefaultValue("Rate.Reputation.RecruitAFriendBonus", 0.1f); + Values[WorldCfg.RateCreatureNormalDamage] = GetDefaultValue("Rate.Creature.Normal.Damage", 1.0f); + Values[WorldCfg.RateCreatureEliteEliteDamage] = GetDefaultValue("Rate.Creature.Elite.Elite.Damage", 1.0f); + Values[WorldCfg.RateCreatureEliteRareeliteDamage] = GetDefaultValue("Rate.Creature.Elite.RAREELITE.Damage", 1.0f); + Values[WorldCfg.RateCreatureEliteWorldbossDamage] = GetDefaultValue("Rate.Creature.Elite.WORLDBOSS.Damage", 1.0f); + Values[WorldCfg.RateCreatureEliteRareDamage] = GetDefaultValue("Rate.Creature.Elite.RARE.Damage", 1.0f); + Values[WorldCfg.RateCreatureNormalHp] = GetDefaultValue("Rate.Creature.Normal.HP", 1.0f); + Values[WorldCfg.RateCreatureEliteEliteHp] = GetDefaultValue("Rate.Creature.Elite.Elite.HP", 1.0f); + Values[WorldCfg.RateCreatureEliteRareeliteHp] = GetDefaultValue("Rate.Creature.Elite.RAREELITE.HP", 1.0f); + Values[WorldCfg.RateCreatureEliteWorldbossHp] = GetDefaultValue("Rate.Creature.Elite.WORLDBOSS.HP", 1.0f); + Values[WorldCfg.RateCreatureEliteRareHp] = GetDefaultValue("Rate.Creature.Elite.RARE.HP", 1.0f); + Values[WorldCfg.RateCreatureNormalSpelldamage] = GetDefaultValue("Rate.Creature.Normal.SpellDamage", 1.0f); + Values[WorldCfg.RateCreatureEliteEliteSpelldamage] = GetDefaultValue("Rate.Creature.Elite.Elite.SpellDamage", 1.0f); + Values[WorldCfg.RateCreatureEliteRareeliteSpelldamage] = GetDefaultValue("Rate.Creature.Elite.RAREELITE.SpellDamage", 1.0f); + Values[WorldCfg.RateCreatureEliteWorldbossSpelldamage] = GetDefaultValue("Rate.Creature.Elite.WORLDBOSS.SpellDamage", 1.0f); + Values[WorldCfg.RateCreatureEliteRareSpelldamage] = GetDefaultValue("Rate.Creature.Elite.RARE.SpellDamage", 1.0f); + Values[WorldCfg.RateCreatureAggro] = GetDefaultValue("Rate.Creature.Aggro", 1.0f); + Values[WorldCfg.RateRestIngame] = GetDefaultValue("Rate.Rest.InGame", 1.0f); + Values[WorldCfg.RateRestOfflineInTavernOrCity] = GetDefaultValue("Rate.Rest.Offline.InTavernOrCity", 1.0f); + Values[WorldCfg.RateRestOfflineInWilderness] = GetDefaultValue("Rate.Rest.Offline.InWilderness", 1.0f); + Values[WorldCfg.RateDamageFall] = GetDefaultValue("Rate.Damage.Fall", 1.0f); + Values[WorldCfg.RateAuctionTime] = GetDefaultValue("Rate.Auction.Time", 1.0f); + Values[WorldCfg.RateAuctionDeposit] = GetDefaultValue("Rate.Auction.Deposit", 1.0f); + Values[WorldCfg.RateAuctionCut] = GetDefaultValue("Rate.Auction.Cut", 1.0f); + Values[WorldCfg.RateHonor] = GetDefaultValue("Rate.Honor", 1.0f); + Values[WorldCfg.RateInstanceResetTime] = GetDefaultValue("Rate.InstanceResetTime", 1.0f); + Values[WorldCfg.RateTalent] = GetDefaultValue("Rate.Talent", 1.0f); + if ((float)Values[WorldCfg.RateTalent] < 0.0f) + { + Log.outError(LogFilter.ServerLoading, "Rate.Talent ({0}) must be > 0. Using 1 instead.", Values[WorldCfg.RateTalent]); + Values[WorldCfg.RateTalent] = 1.0f; + } + Values[WorldCfg.RateMovespeed] = GetDefaultValue("Rate.MoveSpeed", 1.0f); + if ((float)Values[WorldCfg.RateMovespeed] < 0.0f) + { + Log.outError(LogFilter.ServerLoading, "Rate.MoveSpeed ({0}) must be > 0. Using 1 instead.", Values[WorldCfg.RateMovespeed]); + Values[WorldCfg.RateMovespeed] = 1.0f; + } + + Values[WorldCfg.RateCorpseDecayLooted] = GetDefaultValue("Rate.Corpse.Decay.Looted", 0.5f); + + Values[WorldCfg.RateTargetPosRecalculationRange] = GetDefaultValue("TargetPosRecalculateRange", 1.5f); + if ((float)Values[WorldCfg.RateTargetPosRecalculationRange] < SharedConst.ContactDistance) + { + Log.outError(LogFilter.ServerLoading, "TargetPosRecalculateRange ({0}) must be >= {1}. Using {1} instead.", Values[WorldCfg.RateTargetPosRecalculationRange], SharedConst.ContactDistance); + Values[WorldCfg.RateTargetPosRecalculationRange] = SharedConst.ContactDistance; + } + else if ((float)Values[WorldCfg.RateTargetPosRecalculationRange] > SharedConst.NominalMeleeRange) + { + Log.outError(LogFilter.ServerLoading, "TargetPosRecalculateRange ({0}) must be <= {1}. Using {1} instead.", + Values[WorldCfg.RateTargetPosRecalculationRange], SharedConst.NominalMeleeRange); + Values[WorldCfg.RateTargetPosRecalculationRange] = SharedConst.NominalMeleeRange; + } + + Values[WorldCfg.RateDurabilityLossOnDeath] = GetDefaultValue("DurabilityLoss.OnDeath", 10.0f); + if ((float)Values[WorldCfg.RateDurabilityLossOnDeath] < 0.0f) + { + Log.outError(LogFilter.ServerLoading, "DurabilityLoss.OnDeath ({0}) must be >=0. Using 0.0 instead.", Values[WorldCfg.RateDurabilityLossOnDeath]); + Values[WorldCfg.RateDurabilityLossOnDeath] = 0.0f; + } + if ((float)Values[WorldCfg.RateDurabilityLossOnDeath] > 100.0f) + { + Log.outError(LogFilter.ServerLoading, "DurabilityLoss.OnDeath ({0}) must be <= 100. Using 100.0 instead.", Values[WorldCfg.RateDurabilityLossOnDeath]); + Values[WorldCfg.RateDurabilityLossOnDeath] = 0.0f; + } + Values[WorldCfg.RateDurabilityLossOnDeath] = (float)Values[WorldCfg.RateDurabilityLossOnDeath] / 100.0f; + + Values[WorldCfg.RateDurabilityLossDamage] = GetDefaultValue("DurabilityLossChance.Damage", 0.5f); + if ((float)Values[WorldCfg.RateDurabilityLossDamage] < 0.0f) + { + Log.outError(LogFilter.ServerLoading, "DurabilityLossChance.Damage ({0}) must be >=0. Using 0.0 instead.", Values[WorldCfg.RateDurabilityLossDamage]); + Values[WorldCfg.RateDurabilityLossDamage] = 0.0f; + } + Values[WorldCfg.RateDurabilityLossAbsorb] = GetDefaultValue("DurabilityLossChance.Absorb", 0.5f); + if ((float)Values[WorldCfg.RateDurabilityLossAbsorb] < 0.0f) + { + Log.outError(LogFilter.ServerLoading, "DurabilityLossChance.Absorb ({0}) must be >=0. Using 0.0 instead.", Values[WorldCfg.RateDurabilityLossAbsorb]); + Values[WorldCfg.RateDurabilityLossAbsorb] = 0.0f; + } + Values[WorldCfg.RateDurabilityLossParry] = GetDefaultValue("DurabilityLossChance.Parry", 0.05f); + if ((float)Values[WorldCfg.RateDurabilityLossParry] < 0.0f) + { + Log.outError(LogFilter.ServerLoading, "DurabilityLossChance.Parry ({0}) must be >=0. Using 0.0 instead.", Values[WorldCfg.RateDurabilityLossParry]); + Values[WorldCfg.RateDurabilityLossParry] = 0.0f; + } + Values[WorldCfg.RateDurabilityLossBlock] = GetDefaultValue("DurabilityLossChance.Block", 0.05f); + if ((float)Values[WorldCfg.RateDurabilityLossBlock] < 0.0f) + { + Log.outError(LogFilter.ServerLoading, "DurabilityLossChance.Block ({0}) must be >=0. Using 0.0 instead.", Values[WorldCfg.RateDurabilityLossBlock]); + Values[WorldCfg.RateDurabilityLossBlock] = 0.0f; + } + Values[WorldCfg.RateMoneyQuest] = GetDefaultValue("Rate.Quest.Money.Reward", 1.0f); + if ((float)Values[WorldCfg.RateMoneyQuest] < 0.0f) + { + Log.outError(LogFilter.ServerLoading, "Rate.Quest.Money.Reward ({0}) must be >=0. Using 0 instead.", Values[WorldCfg.RateMoneyQuest]); + Values[WorldCfg.RateMoneyQuest] = 0.0f; + } + Values[WorldCfg.RateMoneyMaxLevelQuest] = GetDefaultValue("Rate.Quest.Money.Max.Level.Reward", 1.0f); + if ((float)Values[WorldCfg.RateMoneyMaxLevelQuest] < 0.0f) + { + Log.outError(LogFilter.ServerLoading, "Rate.Quest.Money.Max.Level.Reward ({0}) must be >=0. Using 0 instead.", Values[WorldCfg.RateMoneyMaxLevelQuest]); + Values[WorldCfg.RateMoneyMaxLevelQuest] = 0.0f; + } + + // Read other configuration items from the config file + Values[WorldCfg.DurabilityLossInPvp] = GetDefaultValue("DurabilityLoss.InPvP", false); + + Values[WorldCfg.Compression] = GetDefaultValue("Compression", 1); + if ((int)Values[WorldCfg.Compression] < 1 || (int)Values[WorldCfg.Compression] > 9) + { + Log.outError(LogFilter.ServerLoading, "Compression Level ({0}) must be in range 1..9. Using default compression Level (1).", Values[WorldCfg.Compression]); + Values[WorldCfg.Compression] = 1; + } + Values[WorldCfg.AddonChannel] = GetDefaultValue("AddonChannel", true); + Values[WorldCfg.CleanCharacterDb] = GetDefaultValue("CleanCharacterDB", false); + Values[WorldCfg.PersistentCharacterCleanFlags] = GetDefaultValue("PersistentCharacterCleanFlags", 0); + Values[WorldCfg.AuctionGetallDelay] = GetDefaultValue("Auction.GetAllScanDelay", 900); + Values[WorldCfg.AuctionSearchDelay] = GetDefaultValue("Auction.SearchDelay", 300); + if ((int)Values[WorldCfg.AuctionSearchDelay] < 100 || (int)Values[WorldCfg.AuctionSearchDelay] > 10000) + { + Log.outError(LogFilter.ServerLoading, "Auction.SearchDelay ({0}) must be between 100 and 10000. Using default of 300ms", Values[WorldCfg.AuctionSearchDelay]); + Values[WorldCfg.AuctionSearchDelay] = 300; + } + Values[WorldCfg.ChatChannelLevelReq] = GetDefaultValue("ChatLevelReq.Channel", 1); + Values[WorldCfg.ChatWhisperLevelReq] = GetDefaultValue("ChatLevelReq.Whisper", 1); + Values[WorldCfg.ChatEmoteLevelReq] = GetDefaultValue("ChatLevelReq.Emote", 1); + Values[WorldCfg.ChatSayLevelReq] = GetDefaultValue("ChatLevelReq.Say", 1); + Values[WorldCfg.ChatYellLevelReq] = GetDefaultValue("ChatLevelReq.Yell", 1); + Values[WorldCfg.PartyLevelReq] = GetDefaultValue("PartyLevelReq", 1); + Values[WorldCfg.TradeLevelReq] = GetDefaultValue("LevelReq.Trade", 1); + Values[WorldCfg.AuctionLevelReq] = GetDefaultValue("LevelReq.Auction", 1); + Values[WorldCfg.MailLevelReq] = GetDefaultValue("LevelReq.Mail", 1); + Values[WorldCfg.PreserveCustomChannels] = GetDefaultValue("PreserveCustomChannels", false); + Values[WorldCfg.PreserveCustomChannelDuration] = GetDefaultValue("PreserveCustomChannelDuration", 14); + Values[WorldCfg.GridUnload] = GetDefaultValue("GridUnload", true); + Values[WorldCfg.BasemapLoadGrids] = GetDefaultValue("BaseMapLoadAllGrids", false); + if ((bool)Values[WorldCfg.BasemapLoadGrids] && (bool)Values[WorldCfg.GridUnload]) + { + Log.outError(LogFilter.ServerLoading, "BaseMapLoadAllGrids enabled, but GridUnload also enabled. GridUnload must be disabled to enable base map pre-loading. Base map pre-loading disabled"); + Values[WorldCfg.BasemapLoadGrids] = false; + } + Values[WorldCfg.InstancemapLoadGrids] = GetDefaultValue("InstanceMapLoadAllGrids", false); + if ((bool)Values[WorldCfg.InstancemapLoadGrids] && (bool)Values[WorldCfg.GridUnload]) + { + Log.outError(LogFilter.ServerLoading, "InstanceMapLoadAllGrids enabled, but GridUnload also enabled. GridUnload must be disabled to enable instance map pre-loading. Instance map pre-loading disabled"); + Values[WorldCfg.InstancemapLoadGrids] = false; + } + + Values[WorldCfg.IntervalSave] = GetDefaultValue("PlayerSaveInterval", 15 * Time.Minute * Time.InMilliseconds); + Values[WorldCfg.IntervalDisconnectTolerance] = GetDefaultValue("DisconnectToleranceInterval", 0); + Values[WorldCfg.StatsSaveOnlyOnLogout] = GetDefaultValue("PlayerSave.Stats.SaveOnlyOnLogout", true); + + Values[WorldCfg.MinLevelStatSave] = GetDefaultValue("PlayerSave.Stats.MinLevel", 0); + if ((int)Values[WorldCfg.MinLevelStatSave] > SharedConst.MaxLevel) + { + Log.outError(LogFilter.ServerLoading, "PlayerSave.Stats.MinLevel ({0}) must be in range 0..80. Using default, do not save character stats (0).", Values[WorldCfg.MinLevelStatSave]); + Values[WorldCfg.MinLevelStatSave] = 0; + } + + Values[WorldCfg.IntervalGridclean] = GetDefaultValue("GridCleanUpDelay", 5 * Time.Minute * Time.InMilliseconds); + if ((int)Values[WorldCfg.IntervalGridclean] < MapConst.MinGridDelay) + { + Log.outError(LogFilter.ServerLoading, "GridCleanUpDelay ({0}) must be greater {1} Use this minimal value.", Values[WorldCfg.IntervalGridclean], MapConst.MinGridDelay); + Values[WorldCfg.IntervalGridclean] = MapConst.MinGridDelay; + } + + Values[WorldCfg.IntervalMapupdate] = GetDefaultValue("MapUpdateInterval", 100); + if ((int)Values[WorldCfg.IntervalMapupdate] < MapConst.MinMapUpdateDelay) + { + Log.outError(LogFilter.ServerLoading, "MapUpdateInterval ({0}) must be greater {1}. Use this minimal value.", Values[WorldCfg.IntervalMapupdate], MapConst.MinMapUpdateDelay); + Values[WorldCfg.IntervalMapupdate] = MapConst.MinMapUpdateDelay; + } + + Values[WorldCfg.IntervalChangeweather] = GetDefaultValue("ChangeWeatherInterval", 10 * Time.Minute * Time.InMilliseconds); + if (reload) + { + int val = GetDefaultValue("WorldServerPort", 8085); + if (val != (int)Values[WorldCfg.PortWorld]) + Log.outError(LogFilter.ServerLoading, "WorldServerPort option can't be changed at worldserver.conf reload, using current value ({0}).", Values[WorldCfg.PortWorld]); + + val = GetDefaultValue("InstanceServerPort", 8086); + if (val != (int)Values[WorldCfg.PortInstance]) + Log.outError(LogFilter.ServerLoading, "InstanceServerPort option can't be changed at worldserver.conf reload, using current value ({0}).", Values[WorldCfg.PortInstance]); + } + else + { + Values[WorldCfg.PortWorld] = GetDefaultValue("WorldServerPort", 8085); + Values[WorldCfg.PortInstance] = GetDefaultValue("InstanceServerPort", 8086); + } + + Values[WorldCfg.SocketTimeouttime] = GetDefaultValue("SocketTimeOutTime", 900000); + Values[WorldCfg.SessionAddDelay] = GetDefaultValue("SessionAddDelay", 10000); + + Values[WorldCfg.GroupXpDistance] = GetDefaultValue("MaxGroupXPDistance", 74.0f); + Values[WorldCfg.MaxRecruitAFriendDistance] = GetDefaultValue("MaxRecruitAFriendBonusDistance", 100.0f); + + /// @todo Add MonsterSight (with meaning) in worldserver.conf or put them as define + Values[WorldCfg.SightMonster] = GetDefaultValue("MonsterSight", 50.0f); + + if (reload) + { + int val = GetDefaultValue("GameType", 0); + if (val != (int)Values[WorldCfg.GameType]) + Log.outError(LogFilter.ServerLoading, "GameType option can't be changed at worldserver.conf reload, using current value ({0}).", Values[WorldCfg.GameType]); + } + else + Values[WorldCfg.GameType] = GetDefaultValue("GameType", 0); + + if (reload) + { + int val = (int)GetDefaultValue("RealmZone", RealmZones.Development); + if (val != (int)Values[WorldCfg.RealmZone]) + Log.outError(LogFilter.ServerLoading, "RealmZone option can't be changed at worldserver.conf reload, using current value ({0}).", Values[WorldCfg.RealmZone]); + } + else + Values[WorldCfg.RealmZone] = GetDefaultValue("RealmZone", (int)RealmZones.Development); + + Values[WorldCfg.AllowTwoSideInteractionCalendar] = GetDefaultValue("AllowTwoSide.Interaction.Calendar", false); + Values[WorldCfg.AllowTwoSideInteractionChannel] = GetDefaultValue("AllowTwoSide.Interaction.Channel", false); + Values[WorldCfg.AllowTwoSideInteractionGroup] = GetDefaultValue("AllowTwoSide.Interaction.Group", false); + Values[WorldCfg.AllowTwoSideInteractionGuild] = GetDefaultValue("AllowTwoSide.Interaction.Guild", false); + Values[WorldCfg.AllowTwoSideInteractionAuction] = GetDefaultValue("AllowTwoSide.Interaction.Auction", false); + Values[WorldCfg.AllowTwoSideTrade] = GetDefaultValue("AllowTwoSide.Trade", false); + Values[WorldCfg.StrictPlayerNames] = GetDefaultValue("StrictPlayerNames", 0); + Values[WorldCfg.StrictCharterNames] = GetDefaultValue("StrictCharterNames", 0); + Values[WorldCfg.StrictPetNames] = GetDefaultValue("StrictPetNames", 0); + + Values[WorldCfg.MinPlayerName] = GetDefaultValue("MinPlayerName", 2); + if ((int)Values[WorldCfg.MinPlayerName] < 1 || (int)Values[WorldCfg.MinPlayerName] > 12) + { + Log.outError(LogFilter.ServerLoading, "MinPlayerName ({0}) must be in range 1..{1}. Set to 2.", Values[WorldCfg.MinPlayerName], 12); + Values[WorldCfg.MinPlayerName] = 2; + } + + Values[WorldCfg.MinCharterName] = GetDefaultValue("MinCharterName", 2); + if ((int)Values[WorldCfg.MinCharterName] < 1 || (int)Values[WorldCfg.MinCharterName] > 24) + { + Log.outError(LogFilter.ServerLoading, "MinCharterName ({0}) must be in range 1..{1}. Set to 2.", Values[WorldCfg.MinCharterName], 24); + Values[WorldCfg.MinCharterName] = 2; + } + + Values[WorldCfg.MinPetName] = GetDefaultValue("MinPetName", 2); + if ((int)Values[WorldCfg.MinPetName] < 1 || (int)Values[WorldCfg.MinPetName] > 12) + { + Log.outError(LogFilter.ServerLoading, "MinPetName ({0}) must be in range 1..{1}. Set to 2.", Values[WorldCfg.MinPetName], 12); + Values[WorldCfg.MinPetName] = 2; + } + + Values[WorldCfg.CharterCostGuild] = GetDefaultValue("Guild.CharterCost", 1000); + Values[WorldCfg.CharterCostArena2v2] = GetDefaultValue("ArenaTeam.CharterCost.2v2", 800000); + Values[WorldCfg.CharterCostArena3v3] = GetDefaultValue("ArenaTeam.CharterCost.3v3", 1200000); + Values[WorldCfg.CharterCostArena5v5] = GetDefaultValue("ArenaTeam.CharterCost.5v5", 2000000); + + Values[WorldCfg.CharacterCreatingDisabled] = GetDefaultValue("CharacterCreating.Disabled", 0); + Values[WorldCfg.CharacterCreatingDisabledRacemask] = GetDefaultValue("CharacterCreating.Disabled.RaceMask", 0); + Values[WorldCfg.CharacterCreatingDisabledClassmask] = GetDefaultValue("CharacterCreating.Disabled.ClassMask", 0); + + Values[WorldCfg.CharactersPerRealm] = GetDefaultValue("CharactersPerRealm", 12); + if ((int)Values[WorldCfg.CharactersPerRealm] < 1 || (int)Values[WorldCfg.CharactersPerRealm] > 12) + { + Log.outError(LogFilter.ServerLoading, "CharactersPerRealm ({0}) must be in range 1..12. Set to 12.", Values[WorldCfg.CharactersPerRealm]); + Values[WorldCfg.CharactersPerRealm] = 11; + } + + // must be after CharactersPerRealm + Values[WorldCfg.CharactersPerAccount] = GetDefaultValue("CharactersPerAccount", 50); + if ((int)Values[WorldCfg.CharactersPerAccount] < (int)Values[WorldCfg.CharactersPerRealm]) + { + Log.outError(LogFilter.ServerLoading, "CharactersPerAccount ({0}) can't be less than CharactersPerRealm ({1}).", Values[WorldCfg.CharactersPerAccount], Values[WorldCfg.CharactersPerRealm]); + Values[WorldCfg.CharactersPerAccount] = Values[WorldCfg.CharactersPerRealm]; + } + + Values[WorldCfg.DeathKnightsPerRealm] = GetDefaultValue("DeathKnightsPerRealm", 1); + if ((int)Values[WorldCfg.DeathKnightsPerRealm] < 0 || (int)Values[WorldCfg.DeathKnightsPerRealm] > 12) + { + Log.outError(LogFilter.ServerLoading, "DeathKnightsPerRealm ({0}) must be in range 0..12. Set to 1.", Values[WorldCfg.DeathKnightsPerRealm]); + Values[WorldCfg.DeathKnightsPerRealm] = 1; + } + + Values[WorldCfg.CharacterCreatingMinLevelForDeathKnight] = GetDefaultValue("CharacterCreating.MinLevelForDeathKnight", 55); + + Values[WorldCfg.DemonHuntersPerRealm] = GetDefaultValue("DemonHuntersPerRealm", 1); + if ((int)Values[WorldCfg.DemonHuntersPerRealm] < 0 || (int)Values[WorldCfg.DemonHuntersPerRealm] > 12) + { + Log.outError(LogFilter.ServerLoading, "DemonHuntersPerRealm ({0}) must be in range 0..12. Set to 1.", Values[WorldCfg.DemonHuntersPerRealm]); + Values[WorldCfg.DemonHuntersPerRealm] = 1; + } + + Values[WorldCfg.CharacterCreatingMinLevelForDemonHunter] = GetDefaultValue("CharacterCreating.MinLevelForDemonHunter", 70); + + Values[WorldCfg.SkipCinematics] = GetDefaultValue("SkipCinematics", 0); + if ((int)Values[WorldCfg.SkipCinematics] < 0 || (int)Values[WorldCfg.SkipCinematics] > 2) + { + Log.outError(LogFilter.ServerLoading, "SkipCinematics ({0}) must be in range 0..2. Set to 0.", Values[WorldCfg.SkipCinematics]); + Values[WorldCfg.SkipCinematics] = 0; + } + + if (reload) + { + int val = GetDefaultValue("MaxPlayerLevel", SharedConst.DefaultMaxLevel); + if (val != (int)Values[WorldCfg.MaxPlayerLevel]) + Log.outError(LogFilter.ServerLoading, "MaxPlayerLevel option can't be changed at config reload, using current value ({0}).", Values[WorldCfg.MaxPlayerLevel]); + } + else + Values[WorldCfg.MaxPlayerLevel] = GetDefaultValue("MaxPlayerLevel", SharedConst.DefaultMaxLevel); + + if ((int)Values[WorldCfg.MaxPlayerLevel] > SharedConst.MaxLevel) + { + Log.outError(LogFilter.ServerLoading, "MaxPlayerLevel ({0}) must be in range 1..{1}. Set to {1}.", Values[WorldCfg.MaxPlayerLevel], SharedConst.MaxLevel); + Values[WorldCfg.MaxPlayerLevel] = SharedConst.MaxLevel; + } + + Values[WorldCfg.MinDualspecLevel] = GetDefaultValue("MinDualSpecLevel", 40); + + Values[WorldCfg.StartPlayerLevel] = GetDefaultValue("StartPlayerLevel", 1); + if ((int)Values[WorldCfg.StartPlayerLevel] < 1) + { + Log.outError(LogFilter.ServerLoading, "StartPlayerLevel ({0}) must be in range 1..MaxPlayerLevel({1}). Set to 1.", Values[WorldCfg.StartPlayerLevel], Values[WorldCfg.MaxPlayerLevel]); + Values[WorldCfg.StartPlayerLevel] = 1; + } + else if ((int)Values[WorldCfg.StartPlayerLevel] > (int)Values[WorldCfg.MaxPlayerLevel]) + { + Log.outError(LogFilter.ServerLoading, "StartPlayerLevel ({0}) must be in range 1..MaxPlayerLevel({1}). Set to {2}.", Values[WorldCfg.StartPlayerLevel], Values[WorldCfg.MaxPlayerLevel], Values[WorldCfg.MaxPlayerLevel]); + Values[WorldCfg.StartPlayerLevel] = Values[WorldCfg.MaxPlayerLevel]; + } + + Values[WorldCfg.StartDeathKnightPlayerLevel] = GetDefaultValue("StartDeathKnightPlayerLevel", 55); + if ((int)Values[WorldCfg.StartDeathKnightPlayerLevel] < 1) + { + Log.outError(LogFilter.ServerLoading, "StartDeathKnightPlayerLevel ({0}) must be in range 1..MaxPlayerLevel({1}). Set to 55.", + Values[WorldCfg.StartDeathKnightPlayerLevel], Values[WorldCfg.MaxPlayerLevel]); + Values[WorldCfg.StartDeathKnightPlayerLevel] = 55; + } + else if ((int)Values[WorldCfg.StartDeathKnightPlayerLevel] > (int)Values[WorldCfg.MaxPlayerLevel]) + { + Log.outError(LogFilter.ServerLoading, "StartDeathKnightPlayerLevel ({0}) must be in range 1..MaxPlayerLevel({1}). Set to {2}.", + Values[WorldCfg.StartDeathKnightPlayerLevel], Values[WorldCfg.MaxPlayerLevel], Values[WorldCfg.MaxPlayerLevel]); + Values[WorldCfg.StartDeathKnightPlayerLevel] = Values[WorldCfg.MaxPlayerLevel]; + } + + Values[WorldCfg.StartDemonHunterPlayerLevel] = GetDefaultValue("StartDemonHunterPlayerLevel", 98); + if ((int)Values[WorldCfg.StartDemonHunterPlayerLevel] < 98) + { + Log.outError(LogFilter.ServerLoading, "StartDemonHunterPlayerLevel ({0}) must be in range 98..MaxPlayerLevel({1}). Set to 98.", + Values[WorldCfg.StartDemonHunterPlayerLevel], Values[WorldCfg.MaxPlayerLevel]); + Values[WorldCfg.StartDemonHunterPlayerLevel] = 98; + } + else if ((int)Values[WorldCfg.StartDemonHunterPlayerLevel] > (int)Values[WorldCfg.MaxPlayerLevel]) + { + Log.outError(LogFilter.ServerLoading, "StartDemonHunterPlayerLevel ({0}) must be in range 98..MaxPlayerLevel({1}). Set to {2}.", + Values[WorldCfg.StartDemonHunterPlayerLevel], Values[WorldCfg.MaxPlayerLevel], Values[WorldCfg.MaxPlayerLevel]); + Values[WorldCfg.StartDemonHunterPlayerLevel] = Values[WorldCfg.MaxPlayerLevel]; + } + + Values[WorldCfg.StartPlayerMoney] = GetDefaultValue("StartPlayerMoney", 0); + if ((int)Values[WorldCfg.StartPlayerMoney] < 0) + { + Log.outError(LogFilter.ServerLoading, "StartPlayerMoney ({0}) must be in range 0..{1}. Set to {2}.", Values[WorldCfg.StartPlayerMoney], PlayerConst.MaxMoneyAmount, 0); + Values[WorldCfg.StartPlayerMoney] = 0; + } + else if ((int)Values[WorldCfg.StartPlayerMoney] > 0x7FFFFFFF - 1) // TODO: (See MaxMoneyAMOUNT) + { + Log.outError(LogFilter.ServerLoading, "StartPlayerMoney ({0}) must be in range 0..{1}. Set to {2}.", + Values[WorldCfg.StartPlayerMoney], 0x7FFFFFFF - 1, 0x7FFFFFFF - 1); + Values[WorldCfg.StartPlayerMoney] = 0x7FFFFFFF - 1; + } + + Values[WorldCfg.CurrencyResetHour] = GetDefaultValue("Currency.ResetHour", 3); + if ((int)Values[WorldCfg.CurrencyResetHour] > 23) + { + Log.outError(LogFilter.ServerLoading, "StartPlayerMoney ({0}) must be in range 0..{1}. Set to {2}.", Values[WorldCfg.CurrencyResetHour] = 3); + } + Values[WorldCfg.CurrencyResetDay] = GetDefaultValue("Currency.ResetDay", 3); + if ((int)Values[WorldCfg.CurrencyResetDay] > 6) + { + Log.outError(LogFilter.ServerLoading, "Currency.ResetDay ({0}) can't be load. Set to 3.", Values[WorldCfg.CurrencyResetDay]); + Values[WorldCfg.CurrencyResetDay] = 3; + } + Values[WorldCfg.CurrencyResetInterval] = GetDefaultValue("Currency.ResetInterval", 7); + if ((int)Values[WorldCfg.CurrencyResetInterval] <= 0) + { + Log.outError(LogFilter.ServerLoading, "Currency.ResetInterval ({0}) must be > 0, set to default 7.", Values[WorldCfg.CurrencyResetInterval]); + Values[WorldCfg.CurrencyResetInterval] = 7; + } + + Values[WorldCfg.CurrencyStartApexisCrystals] = GetDefaultValue("Currency.StartApexisCrystals", 0); + if ((int)Values[WorldCfg.CurrencyStartApexisCrystals] < 0) + { + Log.outError(LogFilter.ServerLoading, "Currency.StartApexisCrystals ({0}) must be >= 0, set to default 0.", Values[WorldCfg.CurrencyStartApexisCrystals]); + Values[WorldCfg.CurrencyStartApexisCrystals] = 0; + } + Values[WorldCfg.CurrencyMaxApexisCrystals] = GetDefaultValue("Currency.MaxApexisCrystals", 20000); + if ((int)Values[WorldCfg.CurrencyMaxApexisCrystals] < 0) + { + Log.outError(LogFilter.ServerLoading, "Currency.MaxApexisCrystals ({0}) can't be negative. Set to default 20000.", Values[WorldCfg.CurrencyMaxApexisCrystals]); + Values[WorldCfg.CurrencyMaxApexisCrystals] = 20000; + } + Values[WorldCfg.CurrencyMaxApexisCrystals] = (int)Values[WorldCfg.CurrencyMaxApexisCrystals] * 100; //precision mod + + Values[WorldCfg.CurrencyStartJusticePoints] = GetDefaultValue("Currency.StartJusticePoints", 0); + if ((int)Values[WorldCfg.CurrencyStartJusticePoints] < 0) + { + Log.outError(LogFilter.ServerLoading, "Currency.StartJusticePoints ({0}) must be >= 0, set to default 0.", Values[WorldCfg.CurrencyStartJusticePoints]); + Values[WorldCfg.CurrencyStartJusticePoints] = 0; + } + Values[WorldCfg.CurrencyMaxJusticePoints] = GetDefaultValue("Currency.MaxJusticePoints", 4000); + if ((int)Values[WorldCfg.CurrencyMaxJusticePoints] < 0) + { + Log.outError(LogFilter.ServerLoading, "Currency.MaxJusticePoints ({0}) can't be negative. Set to default 4000.", Values[WorldCfg.CurrencyMaxJusticePoints]); + Values[WorldCfg.CurrencyMaxJusticePoints] = 4000; + } + Values[WorldCfg.CurrencyMaxJusticePoints] = (int)Values[WorldCfg.CurrencyMaxJusticePoints] * 100; //precision mod + + Values[WorldCfg.MaxRecruitAFriendBonusPlayerLevel] = GetDefaultValue("RecruitAFriend.MaxLevel", 85); + if ((int)Values[WorldCfg.MaxRecruitAFriendBonusPlayerLevel] > (int)Values[WorldCfg.MaxPlayerLevel]) + { + Log.outError(LogFilter.ServerLoading, "RecruitAFriend.MaxLevel ({0}) must be in the range 0..MaxLevel({1}). Set to {2}.", + Values[WorldCfg.MaxRecruitAFriendBonusPlayerLevel], Values[WorldCfg.MaxPlayerLevel], 85); + Values[WorldCfg.MaxRecruitAFriendBonusPlayerLevel] = 85; + } + + Values[WorldCfg.MaxRecruitAFriendBonusPlayerLevelDifference] = GetDefaultValue("RecruitAFriend.MaxDifference", 4); + Values[WorldCfg.AllTaxiPaths] = GetDefaultValue("AllFlightPaths", false); + Values[WorldCfg.InstantTaxi] = GetDefaultValue("InstantFlightPaths", false); + + Values[WorldCfg.InstanceIgnoreLevel] = GetDefaultValue("Instance.IgnoreLevel", false); + Values[WorldCfg.InstanceIgnoreRaid] = GetDefaultValue("Instance.IgnoreRaid", false); + + Values[WorldCfg.CastUnstuck] = GetDefaultValue("CastUnstuck", true); + Values[WorldCfg.InstanceResetTimeHour] = GetDefaultValue("Instance.ResetTimeHour", 4); + Values[WorldCfg.InstanceUnloadDelay] = GetDefaultValue("Instance.UnloadDelay", 30 * Time.Minute * Time.InMilliseconds); + Values[WorldCfg.DailyQuestResetTimeHour] = GetDefaultValue("Quests.DailyResetTime", 3); + + Values[WorldCfg.MaxPrimaryTradeSkill] = GetDefaultValue("MaxPrimaryTradeSkill", 2); + Values[WorldCfg.MinPetitionSigns] = GetDefaultValue("MinPetitionSigns", 4); + if ((int)Values[WorldCfg.MinPetitionSigns] > 4) + { + Log.outError(LogFilter.ServerLoading, "MinPetitionSigns ({0}) must be in range 0..4. Set to 4.", Values[WorldCfg.MinPetitionSigns]); + Values[WorldCfg.MinPetitionSigns] = 4; + } + + Values[WorldCfg.GmLoginState] = GetDefaultValue("GM.LoginState", 2); + Values[WorldCfg.GmVisibleState] = GetDefaultValue("GM.Visible", 2); + Values[WorldCfg.GmChat] = GetDefaultValue("GM.Chat", 2); + Values[WorldCfg.GmWhisperingTo] = GetDefaultValue("GM.WhisperingTo", 2); + Values[WorldCfg.GmFreezeDuration] = GetDefaultValue("GM.FreezeAuraDuration", 0); + + Values[WorldCfg.GmLevelInGmList] = GetDefaultValue("GM.InGMList.Level", (int)AccountTypes.Administrator); + Values[WorldCfg.GmLevelInWhoList] = GetDefaultValue("GM.InWhoList.Level", (int)AccountTypes.Administrator); + Values[WorldCfg.StartGmLevel] = GetDefaultValue("GM.StartLevel", 1); + if ((int)Values[WorldCfg.StartGmLevel] < (int)Values[WorldCfg.StartPlayerLevel]) + { + Log.outError(LogFilter.ServerLoading, "GM.StartLevel ({0}) must be in range StartPlayerLevel({1})..{2}. Set to {3}.", + Values[WorldCfg.StartGmLevel], Values[WorldCfg.StartPlayerLevel], SharedConst.MaxLevel, Values[WorldCfg.StartPlayerLevel]); + Values[WorldCfg.StartGmLevel] = Values[WorldCfg.StartPlayerLevel]; + } + else if ((int)Values[WorldCfg.StartGmLevel] > SharedConst.MaxLevel) + { + Log.outError(LogFilter.ServerLoading, "GM.StartLevel ({0}) must be in range 1..{1}. Set to {1}.", Values[WorldCfg.StartGmLevel], SharedConst.MaxLevel); + Values[WorldCfg.StartGmLevel] = SharedConst.MaxLevel; + } + Values[WorldCfg.AllowGmGroup] = GetDefaultValue("GM.AllowInvite", false); + Values[WorldCfg.GmLowerSecurity] = GetDefaultValue("GM.LowerSecurity", false); + Values[WorldCfg.ForceShutdownThreshold] = GetDefaultValue("GM.ForceShutdownThreshold", 30); + + Values[WorldCfg.GroupVisibility] = GetDefaultValue("Visibility.GroupMode", 1); + + Values[WorldCfg.MailDeliveryDelay] = GetDefaultValue("MailDeliveryDelay", Time.Hour); + + Values[WorldCfg.UptimeUpdate] = GetDefaultValue("UpdateUptimeInterval", 10); + if ((int)Values[WorldCfg.UptimeUpdate] <= 0) + { + Log.outError(LogFilter.ServerLoading, "UpdateUptimeInterval ({0}) must be > 0, set to default 10.", Values[WorldCfg.UptimeUpdate]); + Values[WorldCfg.UptimeUpdate] = 10; + } + + // log db cleanup interval + Values[WorldCfg.LogdbClearinterval] = GetDefaultValue("LogDB.Opt.ClearInterval", 10); + if ((int)Values[WorldCfg.LogdbClearinterval] <= 0) + { + Log.outError(LogFilter.ServerLoading, "LogDB.Opt.ClearInterval ({0}) must be > 0, set to default 10.", Values[WorldCfg.LogdbClearinterval]); + Values[WorldCfg.LogdbClearinterval] = 10; + } + Values[WorldCfg.LogdbCleartime] = GetDefaultValue("LogDB.Opt.ClearTime", 1209600); // 14 days default + Log.outInfo(LogFilter.ServerLoading, "Will clear `logs` table of entries older than {0} seconds every {1} minutes.", Values[WorldCfg.LogdbCleartime], Values[WorldCfg.LogdbClearinterval]); + + Values[WorldCfg.SkillChanceOrange] = GetDefaultValue("SkillChance.Orange", 100); + Values[WorldCfg.SkillChanceYellow] = GetDefaultValue("SkillChance.Yellow", 75); + Values[WorldCfg.SkillChanceGreen] = GetDefaultValue("SkillChance.Green", 25); + Values[WorldCfg.SkillChanceGrey] = GetDefaultValue("SkillChance.Grey", 0); + + Values[WorldCfg.SkillChanceMiningSteps] = GetDefaultValue("SkillChance.MiningSteps", 75); + Values[WorldCfg.SkillChanceSkinningSteps] = GetDefaultValue("SkillChance.SkinningSteps", 75); + + Values[WorldCfg.SkillProspecting] = GetDefaultValue("SkillChance.Prospecting", false); + Values[WorldCfg.SkillMilling] = GetDefaultValue("SkillChance.Milling", false); + + Values[WorldCfg.SkillGainCrafting] = GetDefaultValue("SkillGain.Crafting", 1); + + Values[WorldCfg.SkillGainGathering] = GetDefaultValue("SkillGain.Gathering", 1); + + Values[WorldCfg.MaxOverspeedPings] = GetDefaultValue("MaxOverspeedPings", 2); + if ((int)Values[WorldCfg.MaxOverspeedPings] != 0 && (int)Values[WorldCfg.MaxOverspeedPings] < 2) + { + Log.outError(LogFilter.ServerLoading, "MaxOverspeedPings ({0}) must be in range 2..infinity (or 0 to disable check). Set to 2.", Values[WorldCfg.MaxOverspeedPings]); + Values[WorldCfg.MaxOverspeedPings] = 2; + } + + Values[WorldCfg.SaveRespawnTimeImmediately] = GetDefaultValue("SaveRespawnTimeImmediately", true); + if (!(bool)Values[WorldCfg.SaveRespawnTimeImmediately]) + { + Log.outWarn(LogFilter.ServerLoading, "SaveRespawnTimeImmediately triggers assertions when Disabled, overridden to Enabled"); + Values[WorldCfg.SaveRespawnTimeImmediately] = true; + } + + Values[WorldCfg.Weather] = GetDefaultValue("ActivateWeather", true); + + Values[WorldCfg.DisableBreathing] = GetDefaultValue("DisableWaterBreath", (int)AccountTypes.Console); + + if (reload) + { + int val = GetDefaultValue("Expansion", (int)Expansion.WarlordsOfDraenor); + if (val != (int)Values[WorldCfg.Expansion]) + Log.outError(LogFilter.ServerLoading, "Expansion option can't be changed at worldserver.conf reload, using current value ({0}).", Values[WorldCfg.Expansion]); + } + else + Values[WorldCfg.Expansion] = GetDefaultValue("Expansion", Expansion.WarlordsOfDraenor); + + Values[WorldCfg.ChatfloodMessageCount] = GetDefaultValue("ChatFlood.MessageCount", 10); + Values[WorldCfg.ChatfloodMessageDelay] = GetDefaultValue("ChatFlood.MessageDelay", 1); + Values[WorldCfg.ChatfloodMuteTime] = GetDefaultValue("ChatFlood.MuteTime", 10); + + Values[WorldCfg.EventAnnounce] = GetDefaultValue("Event.Announce", false); + + Values[WorldCfg.CreatureFamilyFleeAssistanceRadius] = GetDefaultValue("CreatureFamilyFleeAssistanceRadius", 30.0f); + Values[WorldCfg.CreatureFamilyAssistanceRadius] = GetDefaultValue("CreatureFamilyAssistanceRadius", 10.0f); + Values[WorldCfg.CreatureFamilyAssistanceDelay] = GetDefaultValue("CreatureFamilyAssistanceDelay", 1500); + Values[WorldCfg.CreatureFamilyFleeDelay] = GetDefaultValue("CreatureFamilyFleeDelay", 7000); + + Values[WorldCfg.WorldBossLevelDiff] = GetDefaultValue("WorldBossLevelDiff", 3); + + Values[WorldCfg.QuestEnableQuestTracker] = GetDefaultValue("Quests.EnableQuestTracker", false); + + // note: disable value (-1) will assigned as 0xFFFFFFF, to prevent overflow at calculations limit it to max possible player Level MaxLevel(100) + Values[WorldCfg.QuestLowLevelHideDiff] = GetDefaultValue("Quests.LowLevelHideDiff", 4); + if ((int)Values[WorldCfg.QuestLowLevelHideDiff] > SharedConst.MaxLevel) + Values[WorldCfg.QuestLowLevelHideDiff] = SharedConst.MaxLevel; + Values[WorldCfg.QuestHighLevelHideDiff] = GetDefaultValue("Quests.HighLevelHideDiff", 7); + if ((int)Values[WorldCfg.QuestHighLevelHideDiff] > SharedConst.MaxLevel) + Values[WorldCfg.QuestHighLevelHideDiff] = SharedConst.MaxLevel; + Values[WorldCfg.QuestIgnoreRaid] = GetDefaultValue("Quests.IgnoreRaid", false); + Values[WorldCfg.QuestIgnoreAutoAccept] = GetDefaultValue("Quests.IgnoreAutoAccept", false); + Values[WorldCfg.QuestIgnoreAutoComplete] = GetDefaultValue("Quests.IgnoreAutoComplete", false); + + Values[WorldCfg.RandomBgResetHour] = GetDefaultValue("Battleground.Random.ResetHour", 6); + if ((int)Values[WorldCfg.RandomBgResetHour] > 23) + { + Log.outError(LogFilter.ServerLoading, "Battleground.Random.ResetHour ({0}) can't be load. Set to 6.", Values[WorldCfg.RandomBgResetHour]); + Values[WorldCfg.RandomBgResetHour] = 6; + } + + Values[WorldCfg.GuildResetHour] = GetDefaultValue("Guild.ResetHour", 6); + if ((int)Values[WorldCfg.GuildResetHour] > 23) + { + Log.outError(LogFilter.Server, "Guild.ResetHour ({0}) can't be load. Set to 6.", Values[WorldCfg.GuildResetHour]); + Values[WorldCfg.GuildResetHour] = 6; + } + + Values[WorldCfg.DetectPosCollision] = GetDefaultValue("DetectPosCollision", true); + + Values[WorldCfg.RestrictedLfgChannel] = GetDefaultValue("Channel.RestrictedLfg", true); + Values[WorldCfg.TalentsInspecting] = GetDefaultValue("TalentsInspecting", 1); + Values[WorldCfg.ChatFakeMessagePreventing] = GetDefaultValue("ChatFakeMessagePreventing", false); + Values[WorldCfg.ChatStrictLinkCheckingSeverity] = GetDefaultValue("ChatStrictLinkChecking.Severity", 0); + Values[WorldCfg.ChatStrictLinkCheckingKick] = GetDefaultValue("ChatStrictLinkChecking.Kick", 0); + + Values[WorldCfg.CorpseDecayNormal] = GetDefaultValue("Corpse.Decay.NORMAL", 60); + Values[WorldCfg.CorpseDecayRare] = GetDefaultValue("Corpse.Decay.RARE", 300); + Values[WorldCfg.CorpseDecayElite] = GetDefaultValue("Corpse.Decay.ELITE", 300); + Values[WorldCfg.CorpseDecayRareelite] = GetDefaultValue("Corpse.Decay.RAREELITE", 300); + Values[WorldCfg.CorpseDecayWorldboss] = GetDefaultValue("Corpse.Decay.WORLDBOSS", 3600); + + Values[WorldCfg.DeathSicknessLevel] = GetDefaultValue("Death.SicknessLevel", 11); + Values[WorldCfg.DeathCorpseReclaimDelayPvp] = GetDefaultValue("Death.CorpseReclaimDelay.PvP", true); + Values[WorldCfg.DeathCorpseReclaimDelayPve] = GetDefaultValue("Death.CorpseReclaimDelay.PvE", true); + Values[WorldCfg.DeathBonesWorld] = GetDefaultValue("Death.Bones.World", true); + Values[WorldCfg.DeathBonesBgOrArena] = GetDefaultValue("Death.Bones.BattlegroundOrArena", true); + + Values[WorldCfg.DieCommandMode] = GetDefaultValue("Die.Command.Mode", true); + + Values[WorldCfg.ThreatRadius] = GetDefaultValue("ThreatRadius", 60.0f); + + // always use declined names in the russian client + Values[WorldCfg.DeclinedNamesUsed] = + + ((RealmZones)Values[WorldCfg.RealmZone] == RealmZones.Russian) ? true : GetDefaultValue("DeclinedNames", false); + + Values[WorldCfg.ListenRangeSay] = GetDefaultValue("ListenRange.Say", 25.0f); + Values[WorldCfg.ListenRangeTextemote] = GetDefaultValue("ListenRange.TextEmote", 25.0f); + Values[WorldCfg.ListenRangeYell] = GetDefaultValue("ListenRange.Yell", 300.0f); + + Values[WorldCfg.BattlegroundCastDeserter] = GetDefaultValue("Battleground.CastDeserter", true); + Values[WorldCfg.BattlegroundQueueAnnouncerEnable] = GetDefaultValue("Battleground.QueueAnnouncer.Enable", false); + Values[WorldCfg.BattlegroundQueueAnnouncerPlayeronly] = GetDefaultValue("Battleground.QueueAnnouncer.PlayerOnly", false); + Values[WorldCfg.BattlegroundStoreStatisticsEnable] = GetDefaultValue("Battleground.StoreStatistics.Enable", false); + Values[WorldCfg.BattlegroundReportAfk] = GetDefaultValue("Battleground.ReportAFK", 3); + if ((int)Values[WorldCfg.BattlegroundReportAfk] < 1) + { + Log.outError(LogFilter.ServerLoading, "Battleground.ReportAFK ({0}) must be >0. Using 3 instead.", Values[WorldCfg.BattlegroundReportAfk]); + Values[WorldCfg.BattlegroundReportAfk] = 3; + } + if ((int)Values[WorldCfg.BattlegroundReportAfk] > 9) + { + Log.outError(LogFilter.ServerLoading, "Battleground.ReportAFK ({0}) must be <10. Using 3 instead.", Values[WorldCfg.BattlegroundReportAfk]); + Values[WorldCfg.BattlegroundReportAfk] = 3; + } + Values[WorldCfg.BattlegroundInvitationType] = GetDefaultValue("Battleground.InvitationType", 0); + Values[WorldCfg.BattlegroundPrematureFinishTimer] = GetDefaultValue("Battleground.PrematureFinishTimer", 5 * Time.Minute * Time.InMilliseconds); + Values[WorldCfg.BattlegroundPremadeGroupWaitForMatch] = GetDefaultValue("Battleground.PremadeGroupWaitForMatch", 30 * Time.Minute * Time.InMilliseconds); + Values[WorldCfg.BgXpForKill] = GetDefaultValue("Battleground.GiveXPForKills", false); + Values[WorldCfg.ArenaMaxRatingDifference] = GetDefaultValue("Arena.MaxRatingDifference", 150); + Values[WorldCfg.ArenaRatingDiscardTimer] = GetDefaultValue("Arena.RatingDiscardTimer", 10 * Time.Minute * Time.InMilliseconds); + Values[WorldCfg.ArenaRatedUpdateTimer] = GetDefaultValue("Arena.RatedUpdateTimer", 5 * Time.InMilliseconds); + Values[WorldCfg.ArenaQueueAnnouncerEnable] = GetDefaultValue("Arena.QueueAnnouncer.Enable", false); + Values[WorldCfg.ArenaSeasonId] = GetDefaultValue("Arena.ArenaSeason.ID", 15); + Values[WorldCfg.ArenaStartRating] = GetDefaultValue("Arena.ArenaStartRating", 0); + Values[WorldCfg.ArenaStartPersonalRating] = GetDefaultValue("Arena.ArenaStartPersonalRating", 1000); + Values[WorldCfg.ArenaStartMatchmakerRating] = GetDefaultValue("Arena.ArenaStartMatchmakerRating", 1500); + Values[WorldCfg.ArenaSeasonInProgress] = GetDefaultValue("Arena.ArenaSeason.InProgress", false); + Values[WorldCfg.ArenaLogExtendedInfo] = GetDefaultValue("ArenaLog.ExtendedInfo", false); + Values[WorldCfg.ArenaWinRatingModifier1] = GetDefaultValue("Arena.ArenaWinRatingModifier1", 48.0f); + Values[WorldCfg.ArenaWinRatingModifier2] = GetDefaultValue("Arena.ArenaWinRatingModifier2", 24.0f); + Values[WorldCfg.ArenaLoseRatingModifier] = GetDefaultValue("Arena.ArenaLoseRatingModifier", 24.0f); + Values[WorldCfg.ArenaMatchmakerRatingModifier] = GetDefaultValue("Arena.ArenaMatchmakerRatingModifier", 24.0f); + + Values[WorldCfg.OffhandCheckAtSpellUnlearn] = GetDefaultValue("OffhandCheckAtSpellUnlearn", true); + + Values[WorldCfg.CreaturePickpocketRefill] = GetDefaultValue("Creature.PickPocketRefillDelay", 10 * Time.Minute); + Values[WorldCfg.CreatureStopForPlayer] = GetDefaultValue("Creature.MovingStopTimeForPlayer", 3 * Time.Minute * Time.InMilliseconds); + + int clientCacheId = GetDefaultValue("ClientCacheVersion", 0); + if (clientCacheId != 0) + { + // overwrite DB/old value + if (clientCacheId > 0) + { + Values[WorldCfg.ClientCacheVersion] = clientCacheId; + Log.outInfo(LogFilter.ServerLoading, "Client cache version set to: {0}", clientCacheId); + } + else + Log.outError(LogFilter.ServerLoading, "ClientCacheVersion can't be negative {0}, ignored.", clientCacheId); + } + + int hotfixCacheId = GetDefaultValue("HotfixCacheVersion", 0); + if (hotfixCacheId != 0) + { + // overwrite DB/old value + if (hotfixCacheId > 0) + { + Values[WorldCfg.HotfixCacheVersion] = hotfixCacheId; + Log.outInfo(LogFilter.ServerLoading, "Hotfix cache version set to: {0}", hotfixCacheId); + } + else + Log.outError(LogFilter.ServerLoading, "HotfixCacheVersion can't be negative {0}, ignored.", hotfixCacheId); + } + + Values[WorldCfg.GuildNewsLogCount] = GetDefaultValue("Guild.NewsLogRecordsCount", GuildConst.NewsLogMaxRecords); + if ((int)Values[WorldCfg.GuildNewsLogCount] > GuildConst.NewsLogMaxRecords) + Values[WorldCfg.GuildNewsLogCount] = GuildConst.NewsLogMaxRecords; + + Values[WorldCfg.GuildEventLogCount] = GetDefaultValue("Guild.EventLogRecordsCount", GuildConst.EventLogMaxRecords); + if ((int)Values[WorldCfg.GuildEventLogCount] > GuildConst.EventLogMaxRecords) + Values[WorldCfg.GuildEventLogCount] = GuildConst.EventLogMaxRecords; + + Values[WorldCfg.GuildBankEventLogCount] = GetDefaultValue("Guild.BankEventLogRecordsCount", GuildConst.BankLogMaxRecords); + if ((int)Values[WorldCfg.GuildBankEventLogCount] > GuildConst.BankLogMaxRecords) + Values[WorldCfg.GuildBankEventLogCount] = GuildConst.BankLogMaxRecords; + + // Load the CharDelete related config options + Values[WorldCfg.ChardeleteMethod] = GetDefaultValue("CharDelete.Method", 0); + Values[WorldCfg.ChardeleteMinLevel] = GetDefaultValue("CharDelete.MinLevel", 0); + Values[WorldCfg.ChardeleteDeathKnightMinLevel] = GetDefaultValue("CharDelete.DeathKnight.MinLevel", 0); + Values[WorldCfg.ChardeleteDemonHunterMinLevel] = GetDefaultValue("CharDelete.DemonHunter.MinLevel", 0); + Values[WorldCfg.ChardeleteKeepDays] = GetDefaultValue("CharDelete.KeepDays", 30); + + // No aggro from gray mobs + Values[WorldCfg.NoGrayAggroAbove] = GetDefaultValue("NoGrayAggro.Above", 0); + Values[WorldCfg.NoGrayAggroBelow] = GetDefaultValue("NoGrayAggro.Below", 0); + if ((int)Values[WorldCfg.NoGrayAggroAbove] > (int)Values[WorldCfg.MaxPlayerLevel]) + { + Log.outError(LogFilter.ServerLoading, "NoGrayAggro.Above ({0}) must be in range 0..{1}. Set to {1}.", Values[WorldCfg.NoGrayAggroAbove], Values[WorldCfg.MaxPlayerLevel]); + Values[WorldCfg.NoGrayAggroAbove] = Values[WorldCfg.MaxPlayerLevel]; + } + if ((int)Values[WorldCfg.NoGrayAggroBelow] > (int)Values[WorldCfg.MaxPlayerLevel]) + { + Log.outError(LogFilter.ServerLoading, "NoGrayAggro.Below ({0}) must be in range 0..{1}. Set to {1}.", Values[WorldCfg.NoGrayAggroBelow], Values[WorldCfg.MaxPlayerLevel]); + Values[WorldCfg.NoGrayAggroBelow] = Values[WorldCfg.MaxPlayerLevel]; + } + if ((int)Values[WorldCfg.NoGrayAggroAbove] > 0 && (int)Values[WorldCfg.NoGrayAggroAbove] < (int)Values[WorldCfg.NoGrayAggroBelow]) + { + Log.outError(LogFilter.ServerLoading, "NoGrayAggro.Below ({0}) cannot be greater than NoGrayAggro.Above ({1}). Set to {1}.", Values[WorldCfg.NoGrayAggroBelow], Values[WorldCfg.NoGrayAggroAbove]); + Values[WorldCfg.NoGrayAggroBelow] = Values[WorldCfg.NoGrayAggroAbove]; + } + + Values[WorldCfg.EnableMmaps] = GetDefaultValue("mmap.EnablePathFinding", false); + Values[WorldCfg.VmapIndoorCheck] = GetDefaultValue("vmap.EnableIndoorCheck", false); + + Values[WorldCfg.MaxWho] = GetDefaultValue("MaxWhoListReturns", 49); + Values[WorldCfg.StartAllSpells] = GetDefaultValue("PlayerStart.AllSpells", false); + if ((bool)Values[WorldCfg.StartAllSpells]) + Log.outWarn(LogFilter.ServerLoading, "PlayerStart.AllSpells Enabled - may not function as intended!"); + + Values[WorldCfg.HonorAfterDuel] = GetDefaultValue("HonorPointsAfterDuel", 0); + Values[WorldCfg.ResetDuelCooldowns] = GetDefaultValue("ResetDuelCooldowns", false); + Values[WorldCfg.ResetDuelHealthMana] = GetDefaultValue("ResetDuelHealthMana", false); + Values[WorldCfg.StartAllExplored] = GetDefaultValue("PlayerStart.MapsExplored", false); + Values[WorldCfg.StartAllRep] = GetDefaultValue("PlayerStart.AllReputation", false); + Values[WorldCfg.AlwaysMaxskill] = GetDefaultValue("AlwaysMaxWeaponSkill", false); + Values[WorldCfg.PvpTokenEnable] = GetDefaultValue("PvPToken.Enable", false); + Values[WorldCfg.PvpTokenMapType] = GetDefaultValue("PvPToken.MapAllowType", 4); + Values[WorldCfg.PvpTokenId] = GetDefaultValue("PvPToken.ItemID", 29434); + Values[WorldCfg.PvpTokenCount] = GetDefaultValue("PvPToken.ItemCount", 1); + if ((int)Values[WorldCfg.PvpTokenCount] < 1) + Values[WorldCfg.PvpTokenCount] = 1; + + Values[WorldCfg.AllowTrackBothResources] = GetDefaultValue("AllowTrackBothResources", false); + Values[WorldCfg.NoResetTalentCost] = GetDefaultValue("NoResetTalentsCost", false); + Values[WorldCfg.ShowKickInWorld] = GetDefaultValue("ShowKickInWorld", false); + Values[WorldCfg.ShowMuteInWorld] = GetDefaultValue("ShowMuteInWorld", false); + Values[WorldCfg.ShowBanInWorld] = GetDefaultValue("ShowBanInWorld", false); + Values[WorldCfg.IntervalLogUpdate] = GetDefaultValue("RecordUpdateTimeDiffInterval", 60000); + Values[WorldCfg.MinLogUpdate] = GetDefaultValue("MinRecordUpdateTimeDiff", 100); + Values[WorldCfg.Numthreads] = GetDefaultValue("MapUpdate.Threads", 1); + Values[WorldCfg.MaxResultsLookupCommands] = GetDefaultValue("Command.LookupMaxResults", 0); + + // Warden + Values[WorldCfg.WardenEnabled] = GetDefaultValue("Warden.Enabled", false); + Values[WorldCfg.WardenNumMemChecks] = GetDefaultValue("Warden.NumMemChecks", 3); + Values[WorldCfg.WardenNumOtherChecks] = GetDefaultValue("Warden.NumOtherChecks", 7); + Values[WorldCfg.WardenClientBanDuration] = GetDefaultValue("Warden.BanDuration", 86400); + Values[WorldCfg.WardenClientCheckHoldoff] = GetDefaultValue("Warden.ClientCheckHoldOff", 30); + Values[WorldCfg.WardenClientFailAction] = GetDefaultValue("Warden.ClientCheckFailAction", 0); + Values[WorldCfg.WardenClientResponseDelay] = GetDefaultValue("Warden.ClientResponseDelay", 600); + + // Feature System + Values[WorldCfg.FeatureSystemBpayStoreEnabled] = GetDefaultValue("FeatureSystem.BpayStore.Enabled", false); + Values[WorldCfg.FeatureSystemCharacterUndeleteEnabled] = GetDefaultValue("FeatureSystem.CharacterUndelete.Enabled", false); + Values[WorldCfg.FeatureSystemCharacterUndeleteCooldown] = GetDefaultValue("FeatureSystem.CharacterUndelete.Cooldown", 2592000); + + // Dungeon finder + Values[WorldCfg.LfgOptionsmask] = GetDefaultValue("DungeonFinder.OptionsMask", 1); + + // DBC_ItemAttributes + Values[WorldCfg.DbcEnforceItemAttributes] = GetDefaultValue("DBC.EnforceItemAttributes", true); + + // Accountpassword Secruity + Values[WorldCfg.AccPasschangesec] = GetDefaultValue("Account.PasswordChangeSecurity", 0); + + // Random Battleground Rewards + Values[WorldCfg.BgRewardWinnerHonorFirst] = GetDefaultValue("Battleground.RewardWinnerHonorFirst", 27000); + Values[WorldCfg.BgRewardWinnerConquestFirst] = GetDefaultValue("Battleground.RewardWinnerConquestFirst", 10000); + Values[WorldCfg.BgRewardWinnerHonorLast] = GetDefaultValue("Battleground.RewardWinnerHonorLast", 13500); + Values[WorldCfg.BgRewardWinnerConquestLast] = GetDefaultValue("Battleground.RewardWinnerConquestLast", 5000); + Values[WorldCfg.BgRewardLoserHonorFirst] = GetDefaultValue("Battleground.RewardLoserHonorFirst", 4500); + Values[WorldCfg.BgRewardLoserHonorLast] = GetDefaultValue("Battleground.RewardLoserHonorLast", 3500); + + // Max instances per hour + Values[WorldCfg.MaxInstancesPerHour] = GetDefaultValue("AccountInstancesPerHour", 5); + + // Anounce reset of instance to whole party + Values[WorldCfg.InstancesResetAnnounce] = GetDefaultValue("InstancesResetAnnounce", false); + + // Autobroadcast + //AutoBroadcast.On + Values[WorldCfg.AutoBroadcast] = GetDefaultValue("AutoBroadcast.On", false); + Values[WorldCfg.AutoBroadcastCenter] = GetDefaultValue("AutoBroadcast.Center", 0); + Values[WorldCfg.AutoBroadcastInterval] = GetDefaultValue("AutoBroadcast.Timer", 60000); + + // Guild save interval + Values[WorldCfg.GuildSaveInterval] = GetDefaultValue("Guild.SaveInterval", 15); + + // misc + Values[WorldCfg.PdumpNoPaths] = GetDefaultValue("PlayerDump.DisallowPaths", true); + Values[WorldCfg.PdumpNoOverwrite] = GetDefaultValue("PlayerDump.DisallowOverwrite", true); + Values[WorldCfg.UiQuestLevelsInDialogs] = GetDefaultValue("UI.ShowQuestLevelsInDialogs", false); + + // Wintergrasp battlefield + Values[WorldCfg.WintergraspEnable] = GetDefaultValue("Wintergrasp.Enable", false); + Values[WorldCfg.WintergraspPlrMax] = GetDefaultValue("Wintergrasp.PlayerMax", 100); + Values[WorldCfg.WintergraspPlrMin] = GetDefaultValue("Wintergrasp.PlayerMin", 0); + Values[WorldCfg.WintergraspPlrMinLvl] = GetDefaultValue("Wintergrasp.PlayerMinLvl", 77); + Values[WorldCfg.WintergraspBattletime] = GetDefaultValue("Wintergrasp.BattleTimer", 30); + Values[WorldCfg.WintergraspNobattletime] = GetDefaultValue("Wintergrasp.NoBattleTimer", 150); + Values[WorldCfg.WintergraspRestartAfterCrash] = GetDefaultValue("Wintergrasp.CrashRestartTimer", 10); + + // Stats limits + Values[WorldCfg.StatsLimitsEnable] = GetDefaultValue("Stats.Limits.Enable", false); + Values[WorldCfg.StatsLimitsDodge] = GetDefaultValue("Stats.Limits.Dodge", 95.0f); + Values[WorldCfg.StatsLimitsParry] = GetDefaultValue("Stats.Limits.Parry", 95.0f); + Values[WorldCfg.StatsLimitsBlock] = GetDefaultValue("Stats.Limits.Block", 95.0f); + Values[WorldCfg.StatsLimitsCrit] = GetDefaultValue("Stats.Limits.Crit", 95.0f); + + //packet spoof punishment + Values[WorldCfg.PacketSpoofPolicy] = GetDefaultValue("PacketSpoof.Policy", 1);//Kick + Values[WorldCfg.PacketSpoofBanmode] = GetDefaultValue("PacketSpoof.BanMode", (int)BanMode.Account); + if ((int)Values[WorldCfg.PacketSpoofBanmode] == 1 || (int)Values[WorldCfg.PacketSpoofBanmode] > 2) + Values[WorldCfg.PacketSpoofBanmode] = (int)BanMode.Account; + + Values[WorldCfg.PacketSpoofBanduration] = GetDefaultValue("PacketSpoof.BanDuration", 86400); + + Values[WorldCfg.IpBasedActionLogging] = GetDefaultValue("Allow.IP.Based.Action.Logging", false); + + // AHBot + Values[WorldCfg.AhbotUpdateInterval] = GetDefaultValue("AuctionHouseBot.Update.Interval", 20); + + Values[WorldCfg.CalculateCreatureZoneAreaData] = GetDefaultValue("Calculate.Creature.Zone.Area.Data", false); + Values[WorldCfg.CalculateGameobjectZoneAreaData] = GetDefaultValue("Calculate.Gameoject.Zone.Area.Data", false); + + // Black Market + Values[WorldCfg.BlackmarketEnabled] = GetDefaultValue("BlackMarket.Enabled", true); + + Values[WorldCfg.BlackmarketMaxAuctions] = GetDefaultValue("BlackMarket.MaxAuctions", 12); + Values[WorldCfg.BlackmarketUpdatePeriod] = GetDefaultValue("BlackMarket.UpdatePeriod", 24); + + // prevent character rename on character customization + Values[WorldCfg.PreventRenameCustomization] = GetDefaultValue("PreventRenameCharacterOnCustomization", false); + + // Check Invalid Position + Values[WorldCfg.CreatureCheckInvalidPostion] = GetDefaultValue("Creature.CheckInvalidPosition", false); + Values[WorldCfg.GameobjectCheckInvalidPostion] = GetDefaultValue("GameObject.CheckInvalidPosition", false); + + // call ScriptMgr if we're reloading the configuration + if (reload) + Global.ScriptMgr.OnConfigLoad(reload); + } + + public static uint GetUIntValue(WorldCfg confi) + { + return Convert.ToUInt32(Values.LookupByKey(confi)); + } + + public static int GetIntValue(WorldCfg confi) + { + return Convert.ToInt32(Values.LookupByKey(confi)); + } + + public static bool GetBoolValue(WorldCfg confi) + { + return Convert.ToBoolean(Values.LookupByKey(confi)); + } + + public static float GetFloatValue(WorldCfg confi) + { + return Convert.ToSingle(Values.LookupByKey(confi)); + } + + public static void SetValue(WorldCfg confi, object value) + { + Values[confi] = value; + } + + static Dictionary Values = new Dictionary(); + } +} diff --git a/Game/Server/WorldManager.cs b/Game/Server/WorldManager.cs new file mode 100644 index 000000000..7d5c1b966 --- /dev/null +++ b/Game/Server/WorldManager.cs @@ -0,0 +1,2542 @@ +/* + * Copyright (C) 2012-2017 CypherCore + * + * 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, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +using Framework.Collections; +using Framework.Configuration; +using Framework.Constants; +using Framework.Database; +using Game.BattlePets; +using Game.Chat; +using Game.Collision; +using Game.DataStorage; +using Game.Entities; +using Game.Maps; +using Game.Network; +using Game.Network.Packets; +using Game.Spells; +using System; +using System.Collections.Concurrent; +using System.Collections.Generic; +using System.Diagnostics.Contracts; +using System.Linq; + +namespace Game +{ + public class WorldManager : Singleton + { + WorldManager() + { + foreach (WorldTimers timer in Enum.GetValues(typeof(WorldTimers))) + m_timers[timer] = new IntervalTimer(); + + m_allowedSecurityLevel = AccountTypes.Player; + m_gameTime = Time.UnixTime; + m_startTime = m_gameTime; + + _realm = new Realm(); + } + + public Player FindPlayerInZone(uint zone) + { + foreach (var session in m_sessions) + { + Player player = session.Value.GetPlayer(); + if (player == null) + continue; + + if (player.IsInWorld && player.GetZoneId() == zone) + { + // Used by the weather system. We return the player to broadcast the change weather message to him and all players in the zone. + return player; + } + } + return null; + } + + public bool IsClosed() + { + return m_isClosed; + } + + public void SetClosed(bool val) + { + m_isClosed = val; + Global.ScriptMgr.OnOpenStateChange(!val); + } + + public void SetMotd(string motd) + { + Global.ScriptMgr.OnMotdChange(motd); + + m_motd.Clear(); + m_motd.AddRange(motd.Split('@')); + } + + public List GetMotd() + { + return m_motd; + } + + public WorldSession FindSession(uint id) + { + return m_sessions.LookupByKey(id); + } + + bool RemoveSession(uint id) + { + // Find the session, kick the user, but we can't delete session at this moment to prevent iterator invalidation + var session = m_sessions.LookupByKey(id); + + if (session != null) + { + if (session.PlayerLoading()) + return false; + + session.KickPlayer(); + } + + return true; + } + + public void AddSession(WorldSession s) + { + addSessQueue.Enqueue(s); + } + + public void AddInstanceSocket(WorldSocket sock, ulong connectToKey) + { + _linkSocketQueue.Enqueue(Tuple.Create(sock, connectToKey)); + } + + void AddSession_(WorldSession s) + { + Contract.Assert(s != null); + + //NOTE - Still there is race condition in WorldSession* being used in the Sockets + + // kick already loaded player with same account (if any) and remove session + // if player is in loading and want to load again, return + if (!RemoveSession(s.GetAccountId())) + { + s.KickPlayer(); + return; + } + + // decrease session counts only at not reconnection case + bool decrease_session = true; + + // if session already exist, prepare to it deleting at next world update + // NOTE - KickPlayer() should be called on "old" in RemoveSession() + { + var old = m_sessions.LookupByKey(s.GetAccountId()); + if (old != null) + { + // prevent decrease sessions count if session queued + if (RemoveQueuedPlayer(old)) + decrease_session = false; + } + } + + m_sessions[s.GetAccountId()] = s; + + int Sessions = GetActiveAndQueuedSessionCount(); + uint pLimit = GetPlayerAmountLimit(); + int QueueSize = GetQueuedSessionCount(); //number of players in the queue + + //so we don't count the user trying to + //login as a session and queue the socket that we are using + if (decrease_session) + --Sessions; + + if (pLimit > 0 && Sessions >= pLimit && !s.HasPermission(RBACPermissions.SkipQueue) && !HasRecentlyDisconnected(s)) + { + AddQueuedPlayer(s); + UpdateMaxSessionCounters(); + Log.outInfo(LogFilter.Server, "PlayerQueue: Account id {0} is in Queue Position ({1}).", s.GetAccountId(), ++QueueSize); + return; + } + + s.InitializeSession(); + + UpdateMaxSessionCounters(); + + // Updates the population + if (pLimit > 0) + { + float popu = GetActiveSessionCount(); // updated number of users on the server + popu /= pLimit; + popu *= 2; + Log.outInfo(LogFilter.Server, "Server Population ({0}).", popu); + } + } + + void ProcessLinkInstanceSocket(Tuple linkInfo) + { + if (!linkInfo.Item1.IsOpen()) + return; + + ConnectToKey key = new ConnectToKey(); + key.Raw = linkInfo.Item2; + + WorldSession session = FindSession(key.AccountId); + if (!session || session.GetConnectToInstanceKey() != linkInfo.Item2) + { + linkInfo.Item1.SendAuthResponseError(BattlenetRpcErrorCode.TimedOut); + linkInfo.Item1.CloseSocket(); + return; + } + + linkInfo.Item1.SetWorldSession(session); + session.AddInstanceConnection(linkInfo.Item1); + session.HandleContinuePlayerLogin(); + } + + bool HasRecentlyDisconnected(WorldSession session) + { + if (session == null) + return false; + uint tolerance = 0; + if (tolerance != 0) + { + foreach (var disconnect in m_disconnects) + { + if ((disconnect.Value - Time.UnixTime) < tolerance) + { + if (disconnect.Key == session.GetAccountId()) + return true; + } + else + m_disconnects.Remove(disconnect.Key); + } + } + return false; + } + + uint GetQueuePos(WorldSession sess) + { + uint position = 1; + + foreach (var iter in m_QueuedPlayer) + { + if (iter != sess) + ++position; + else + return position; + } + return 0; + } + + void AddQueuedPlayer(WorldSession sess) + { + sess.SetInQueue(true); + m_QueuedPlayer.Add(sess); + + // The 1st SMSG_AUTH_RESPONSE needs to contain other info too. + sess.SendAuthResponse(BattlenetRpcErrorCode.Ok, true, GetQueuePos(sess)); + } + + bool RemoveQueuedPlayer(WorldSession sess) + { + // sessions count including queued to remove (if removed_session set) + int sessions = GetActiveSessionCount(); + + uint position = 1; + + // search to remove and count skipped positions + bool found = false; + + foreach (var iter in m_QueuedPlayer) + { + if (iter != sess) + ++position; + else + { + sess.SetInQueue(false); + sess.ResetTimeOutTime(); + m_QueuedPlayer.Remove(iter); + found = true; // removing queued session + break; + } + } + + // iter point to next socked after removed or end() + // position store position of removed socket and then new position next socket after removed + + // if session not queued then we need decrease sessions count + if (!found && sessions != 0) + --sessions; + + // accept first in queue + if ((m_playerLimit == 0 || sessions < m_playerLimit) && !m_QueuedPlayer.Empty()) + { + WorldSession pop_sess = m_QueuedPlayer.First(); + pop_sess.InitializeSession(); + + m_QueuedPlayer.RemoveAt(0); + + // update iter to point first queued socket or end() if queue is empty now + position = 1; + } + + // update position from iter to end() + // iter point to first not updated socket, position store new position + foreach (var iter in m_QueuedPlayer) + { + iter.SendAuthWaitQue(++position); + } + + return found; + } + + public void SetInitialWorldSettings() + { + // Server startup begin + uint startupBegin = Time.GetMSTime(); + + LoadRealmInfo(); + + LoadConfigSettings(); + + // Initialize Allowed Security Level + LoadDBAllowedSecurityLevel(); + + Global.ObjectMgr.SetHighestGuids(); + + /*if (!Global.MapMgr.ExistMapAndVMap(0, -6240.32f, 331.033f) || !Global.MapMgr.ExistMapAndVMap(0, -8949.95f, -132.493f) + || !Global.MapMgr.ExistMapAndVMap(1, -618.518f, -4251.67f) || !Global.MapMgr.ExistMapAndVMap(0, 1676.35f, 1677.45f) + || !Global.MapMgr.ExistMapAndVMap(1, 10311.3f, 832.463f) || !Global.MapMgr.ExistMapAndVMap(1, -2917.58f, -257.98f) + || (WorldConfig.GetIntValue(WorldCfg.Expansion) != 0 && (!Global.MapMgr.ExistMapAndVMap(530, 10349.6f, -6357.29f) || !Global.MapMgr.ExistMapAndVMap(530, -3961.64f, -13931.2f)))) + { + Log.outError(LogFilter.ServerLoading, "Unable to load critical files - server shutting down !!!"); + ShutdownServ(0, 0, ShutdownExitCode.Error); + return; + }*/ + + // Initialize pool manager + Global.PoolMgr.Initialize(); + + // Initialize game event manager + Global.GameEventMgr.Initialize(); + + Log.outInfo(LogFilter.ServerLoading, "Loading Cypher Strings..."); + Global.ObjectMgr.LoadCypherStrings(); + + // not send custom type REALM_FFA_PVP to realm list + RealmType server_type = IsFFAPvPRealm() ? RealmType.PVP : (RealmType)WorldConfig.GetIntValue(WorldCfg.GameType); + uint realm_zone = WorldConfig.GetUIntValue(WorldCfg.RealmZone); + + DB.Login.Execute("UPDATE realmlist SET icon = {0}, timezone = {1} WHERE id = '{2}'", (byte)server_type, realm_zone, _realm.Id.Realm); // One-time query + + Log.outInfo(LogFilter.ServerLoading, "Initialize DataStorage..."); + // Load DB2s + CliDB.LoadStores(_dataPath, m_defaultDbcLocale); + + Log.outInfo(LogFilter.ServerLoading, "Loading hotfix info..."); + Global.DB2Mgr.LoadHotfixData(); + + //- Load M2 fly by cameras + M2Storage.LoadM2Cameras(_dataPath); + + //- Load GameTables + CliDB.LoadGameTables(_dataPath); + + //Load weighted graph on taxi nodes path + Global.TaxiPathGraph.Initialize(); + + Global.MMapMgr.Initialize(); + + Log.outInfo(LogFilter.ServerLoading, "Loading SpellInfo Storage..."); + Global.SpellMgr.LoadSpellInfoStore(); + + Log.outInfo(LogFilter.ServerLoading, "Loading SpellInfo corrections..."); + Global.SpellMgr.LoadSpellInfoCorrections(); + + Log.outInfo(LogFilter.ServerLoading, "Loading SkillLineAbility Data..."); + Global.SpellMgr.LoadSkillLineAbilityMap(); + + Log.outInfo(LogFilter.ServerLoading, "Loading SpellInfo custom attributes..."); + Global.SpellMgr.LoadSpellInfoCustomAttributes(); + + Log.outInfo(LogFilter.ServerLoading, "Loading PetFamilySpellsStore Data..."); + Global.SpellMgr.LoadPetFamilySpellsStore(); + + Log.outInfo(LogFilter.ServerLoading, "Loading GameObject models..."); + GameObjectModel.LoadGameObjectModelList(); + + Log.outInfo(LogFilter.ServerLoading, "Loading Script Names..."); + Global.ObjectMgr.LoadScriptNames(); + + Log.outInfo(LogFilter.ServerLoading, "Loading Instance Template..."); + Global.ObjectMgr.LoadInstanceTemplate(); + + // Must be called before `creature_respawn`/`gameobject_respawn` tables + Log.outInfo(LogFilter.ServerLoading, "Loading instances..."); + Global.InstanceSaveMgr.LoadInstances(); + + Log.outInfo(LogFilter.ServerLoading, "Loading Localization strings..."); + uint oldMSTime = Time.GetMSTime(); + Global.ObjectMgr.LoadCreatureLocales(); + Global.ObjectMgr.LoadGameObjectLocales(); + Global.ObjectMgr.LoadQuestTemplateLocale(); + Global.ObjectMgr.LoadQuestOfferRewardLocale(); + Global.ObjectMgr.LoadQuestRequestItemsLocale(); + Global.ObjectMgr.LoadQuestObjectivesLocale(); + Global.ObjectMgr.LoadPageTextLocales(); + Global.ObjectMgr.LoadGossipMenuItemsLocales(); + Global.ObjectMgr.LoadPointOfInterestLocales(); + Log.outInfo(LogFilter.ServerLoading, "Localization strings loaded in {0} ms", Time.GetMSTimeDiffToNow(oldMSTime)); + + Log.outInfo(LogFilter.ServerLoading, "Loading Account Roles and Permissions..."); + Global.AccountMgr.LoadRBAC(); + + Log.outInfo(LogFilter.ServerLoading, "Loading Page Texts..."); + Global.ObjectMgr.LoadPageTexts(); + + Log.outInfo(LogFilter.ServerLoading, "Loading GameObject Template..."); + Global.ObjectMgr.LoadGameObjectTemplate(); + + Log.outInfo(LogFilter.ServerLoading, "Loading Game Object template addons..."); + Global.ObjectMgr.LoadGameObjectTemplateAddons(); + + Log.outInfo(LogFilter.ServerLoading, "Loading Transport Templates..."); + Global.TransportMgr.LoadTransportTemplates(); + + Log.outInfo(LogFilter.ServerLoading, "Loading Transport animations and rotations..."); + Global.TransportMgr.LoadTransportAnimationAndRotation(); + + Log.outInfo(LogFilter.ServerLoading, "Loading Spell Rank Data..."); + Global.SpellMgr.LoadSpellRanks(); + + Log.outInfo(LogFilter.ServerLoading, "Loading Spell Required Data..."); + Global.SpellMgr.LoadSpellRequired(); + + Log.outInfo(LogFilter.ServerLoading, "Loading Spell Group types..."); + Global.SpellMgr.LoadSpellGroups(); + + Log.outInfo(LogFilter.ServerLoading, "Loading Spell Learn Skills..."); + Global.SpellMgr.LoadSpellLearnSkills(); + + Log.outInfo(LogFilter.ServerLoading, "Loading Spell Learn Spells..."); + Global.SpellMgr.LoadSpellLearnSpells(); + + Log.outInfo(LogFilter.ServerLoading, "Loading Spell Proc Event conditions..."); + Global.SpellMgr.LoadSpellProcEvents(); + + Log.outInfo(LogFilter.ServerLoading, "Loading Spell Proc conditions and data..."); + Global.SpellMgr.LoadSpellProcs(); + + Log.outInfo(LogFilter.ServerLoading, "Loading Aggro Spells Definitions..."); + Global.SpellMgr.LoadSpellThreats(); + + Log.outInfo(LogFilter.ServerLoading, "Loading Spell Group Stack Rules..."); + Global.SpellMgr.LoadSpellGroupStackRules(); + + Log.outInfo(LogFilter.ServerLoading, "Loading NPC Texts..."); + Global.ObjectMgr.LoadNPCText(); + + Log.outInfo(LogFilter.ServerLoading, "Loading Enchant Spells Proc datas..."); + Global.SpellMgr.LoadSpellEnchantProcData(); + + Log.outInfo(LogFilter.ServerLoading, "Loading Item Random Enchantments Table..."); + ItemEnchantment.LoadRandomEnchantmentsTable(); + + Log.outInfo(LogFilter.ServerLoading, "Loading Disables"); // must be before loading quests and items + Global.DisableMgr.LoadDisables(); + + Log.outInfo(LogFilter.ServerLoading, "Loading Items..."); // must be after LoadRandomEnchantmentsTable and LoadPageTexts + Global.ObjectMgr.LoadItemTemplates(); + + Log.outInfo(LogFilter.ServerLoading, "Loading Item set names..."); // must be after LoadItemPrototypes + Global.ObjectMgr.LoadItemTemplateAddon(); + + Log.outInfo(LogFilter.ServerLoading, "Loading Item Scripts..."); // must be after LoadItemPrototypes + Global.ObjectMgr.LoadItemScriptNames(); + + Log.outInfo(LogFilter.ServerLoading, "Loading Creature Model Based Info Data..."); + Global.ObjectMgr.LoadCreatureModelInfo(); + + Log.outInfo(LogFilter.ServerLoading, "Loading Creature templates..."); + Global.ObjectMgr.LoadCreatureTemplates(); + + Log.outInfo(LogFilter.ServerLoading, "Loading Equipment templates..."); + Global.ObjectMgr.LoadEquipmentTemplates(); + + Log.outInfo(LogFilter.ServerLoading, "Loading Creature template addons..."); + Global.ObjectMgr.LoadCreatureTemplateAddons(); + + Log.outInfo(LogFilter.ServerLoading, "Loading Reputation Reward Rates..."); + Global.ObjectMgr.LoadReputationRewardRate(); + + Log.outInfo(LogFilter.ServerLoading, "Loading Creature Reputation OnKill Data..."); + Global.ObjectMgr.LoadReputationOnKill(); + + Log.outInfo(LogFilter.ServerLoading, "Loading Reputation Spillover Data..."); + Global.ObjectMgr.LoadReputationSpilloverTemplate(); + + Log.outInfo(LogFilter.ServerLoading, "Loading Points Of Interest Data..."); + Global.ObjectMgr.LoadPointsOfInterest(); + + Log.outInfo(LogFilter.ServerLoading, "Loading Creature Base Stats..."); + Global.ObjectMgr.LoadCreatureClassLevelStats(); + + Log.outInfo(LogFilter.ServerLoading, "Loading Creature Data..."); + Global.ObjectMgr.LoadCreatures(); + + Log.outInfo(LogFilter.ServerLoading, "Loading Temporary Summon Data..."); + Global.ObjectMgr.LoadTempSummons(); // must be after LoadCreatureTemplates() and LoadGameObjectTemplates() + + Log.outInfo(LogFilter.ServerLoading, "Loading pet levelup spells..."); + Global.SpellMgr.LoadPetLevelupSpellMap(); + + Log.outInfo(LogFilter.ServerLoading, "Loading pet default spells additional to levelup spells..."); + Global.SpellMgr.LoadPetDefaultSpells(); + + Log.outInfo(LogFilter.ServerLoading, "Loading Creature Addon Data..."); + Global.ObjectMgr.LoadCreatureAddons(); + + Log.outInfo(LogFilter.ServerLoading, "Loading GameObjects..."); + Global.ObjectMgr.LoadGameobjects(); + + Log.outInfo(LogFilter.ServerLoading, "Loading GameObject Addon Data..."); + Global.ObjectMgr.LoadGameObjectAddons(); // must be after LoadGameObjectTemplate() and LoadGameobjects() + + Log.outInfo(LogFilter.ServerLoading, "Loading GameObject Quest Items..."); + Global.ObjectMgr.LoadGameObjectQuestItems(); + + Log.outInfo(LogFilter.ServerLoading, "Loading Creature Quest Items..."); + Global.ObjectMgr.LoadCreatureQuestItems(); + + Log.outInfo(LogFilter.ServerLoading, "Loading Creature Linked Respawn..."); + Global.ObjectMgr.LoadLinkedRespawn(); // must be after LoadCreatures(), LoadGameObjects() + + Log.outInfo(LogFilter.ServerLoading, "Loading Weather Data..."); + Global.WeatherMgr.LoadWeatherData(); + + Log.outInfo(LogFilter.ServerLoading, "Loading Quests..."); + Global.ObjectMgr.LoadQuests(); + + Log.outInfo(LogFilter.ServerLoading, "Checking Quest Disables"); + Global.DisableMgr.CheckQuestDisables(); // must be after loading quests + + Log.outInfo(LogFilter.ServerLoading, "Loading Quest POI"); + Global.ObjectMgr.LoadQuestPOI(); + + Log.outInfo(LogFilter.ServerLoading, "Loading Quests Starters and Enders..."); + Global.ObjectMgr.LoadQuestStartersAndEnders(); // must be after quest load + + Log.outInfo(LogFilter.ServerLoading, "Loading Quest Greetings..."); + Global.ObjectMgr.LoadQuestGreetings(); + + Log.outInfo(LogFilter.ServerLoading, "Loading Objects Pooling Data..."); + Global.PoolMgr.LoadFromDB(); + + Log.outInfo(LogFilter.ServerLoading, "Loading Game Event Data..."); // must be after loading pools fully + Global.GameEventMgr.LoadFromDB(); + + Log.outInfo(LogFilter.ServerLoading, "Loading NPCSpellClick Data..."); // must be after LoadQuests + Global.ObjectMgr.LoadNPCSpellClickSpells(); + + Log.outInfo(LogFilter.ServerLoading, "Loading Vehicle Template Accessories..."); + Global.ObjectMgr.LoadVehicleTemplateAccessories(); // must be after LoadCreatureTemplates() and LoadNPCSpellClickSpells() + + Log.outInfo(LogFilter.ServerLoading, "Loading Vehicle Accessories..."); + Global.ObjectMgr.LoadVehicleAccessories(); // must be after LoadCreatureTemplates() and LoadNPCSpellClickSpells() + + Log.outInfo(LogFilter.ServerLoading, "Loading SpellArea Data..."); // must be after quest load + Global.SpellMgr.LoadSpellAreas(); + + Log.outInfo(LogFilter.ServerLoading, "Loading AreaTrigger definitions..."); + Global.ObjectMgr.LoadAreaTriggerTeleports(); + + Log.outInfo(LogFilter.ServerLoading, "Loading Access Requirements..."); + Global.ObjectMgr.LoadAccessRequirements(); // must be after item template load + + Log.outInfo(LogFilter.ServerLoading, "Loading Quest Area Triggers..."); + Global.ObjectMgr.LoadQuestAreaTriggers(); // must be after LoadQuests + + Log.outInfo(LogFilter.ServerLoading, "Loading Tavern Area Triggers..."); + Global.ObjectMgr.LoadTavernAreaTriggers(); + + Log.outInfo(LogFilter.ServerLoading, "Loading AreaTrigger script names..."); + Global.ObjectMgr.LoadAreaTriggerScripts(); + + Log.outInfo(LogFilter.ServerLoading, "Loading LFG entrance positions..."); // Must be after areatriggers + Global.LFGMgr.LoadLFGDungeons(); + + Log.outInfo(LogFilter.ServerLoading, "Loading Dungeon boss data..."); + Global.ObjectMgr.LoadInstanceEncounters(); + + Log.outInfo(LogFilter.ServerLoading, "Loading LFG rewards..."); + Global.LFGMgr.LoadRewards(); + + Log.outInfo(LogFilter.ServerLoading, "Loading Graveyard-zone links..."); + Global.ObjectMgr.LoadGraveyardZones(); + + Log.outInfo(LogFilter.ServerLoading, "Loading spell pet auras..."); + Global.SpellMgr.LoadSpellPetAuras(); + + Log.outInfo(LogFilter.ServerLoading, "Loading Spell target coordinates..."); + Global.SpellMgr.LoadSpellTargetPositions(); + + Log.outInfo(LogFilter.ServerLoading, "Loading enchant custom attributes..."); + Global.SpellMgr.LoadEnchantCustomAttr(); + + Log.outInfo(LogFilter.ServerLoading, "Loading linked spells..."); + Global.SpellMgr.LoadSpellLinked(); + + Log.outInfo(LogFilter.ServerLoading, "Loading Player Create Data..."); + Global.ObjectMgr.LoadPlayerInfo(); + + Log.outInfo(LogFilter.ServerLoading, "Loading Exploration BaseXP Data..."); + Global.ObjectMgr.LoadExplorationBaseXP(); + + Log.outInfo(LogFilter.ServerLoading, "Loading Pet Name Parts..."); + Global.ObjectMgr.LoadPetNames(); + + Log.outInfo(LogFilter.ServerLoading, "Loading AreaTrigger Templates..."); + Global.AreaTriggerDataStorage.LoadAreaTriggerTemplates(); + + Log.outInfo(LogFilter.ServerLoading, "Loading Conversation Templates..."); + Global.ConversationDataStorage.LoadConversationTemplates(); + + Log.outInfo(LogFilter.ServerLoading, "Loading Scenes Templates..."); + Global.ObjectMgr.LoadSceneTemplates(); + + CharacterDatabaseCleaner.CleanDatabase(); + + Log.outInfo(LogFilter.ServerLoading, "Loading the max pet number..."); + Global.ObjectMgr.LoadPetNumber(); + + Log.outInfo(LogFilter.ServerLoading, "Loading pet level stats..."); + Global.ObjectMgr.LoadPetLevelInfo(); + + Log.outInfo(LogFilter.ServerLoading, "Loading Player level dependent mail rewards..."); + Global.ObjectMgr.LoadMailLevelRewards(); + + // Loot tables + Loots.LootManager.LoadLootTables(); + + Log.outInfo(LogFilter.ServerLoading, "Loading Skill Discovery Table..."); + SkillDiscovery.LoadSkillDiscoveryTable(); + + Log.outInfo(LogFilter.ServerLoading, "Loading Skill Extra Item Table..."); + SkillExtraItems.LoadSkillExtraItemTable(); + + Log.outInfo(LogFilter.ServerLoading, "Loading Skill Perfection Data Table..."); + SkillPerfectItems.LoadSkillPerfectItemTable(); + + Log.outInfo(LogFilter.ServerLoading, "Loading Skill Fishing base level requirements..."); + Global.ObjectMgr.LoadFishingBaseSkillLevel(); + + Log.outInfo(LogFilter.ServerLoading, "Loading skill tier info..."); + Global.ObjectMgr.LoadSkillTiers(); + + Log.outInfo(LogFilter.ServerLoading, "Loading Criteria Modifier trees..."); + Global.CriteriaMgr.LoadCriteriaModifiersTree(); + Log.outInfo(LogFilter.ServerLoading, "Loading Criteria Lists..."); + Global.CriteriaMgr.LoadCriteriaList(); + Log.outInfo(LogFilter.ServerLoading, "Loading Criteria Data..."); + Global.CriteriaMgr.LoadCriteriaData(); + Log.outInfo(LogFilter.ServerLoading, "Loading Achievements..."); + Global.AchievementMgr.LoadAchievementReferenceList(); + Log.outInfo(LogFilter.ServerLoading, "Loading Achievement Rewards..."); + Global.AchievementMgr.LoadRewards(); + Log.outInfo(LogFilter.ServerLoading, "Loading Achievement Reward Locales..."); + Global.AchievementMgr.LoadRewardLocales(); + Log.outInfo(LogFilter.ServerLoading, "Loading Completed Achievements..."); + Global.AchievementMgr.LoadCompletedAchievements(); + + // Load dynamic data tables from the database + Log.outInfo(LogFilter.ServerLoading, "Loading Item Auctions..."); + Global.AuctionMgr.LoadAuctionItems(); + + Log.outInfo(LogFilter.ServerLoading, "Loading Auctions..."); + Global.AuctionMgr.LoadAuctions(); + + if (WorldConfig.GetBoolValue(WorldCfg.BlackmarketEnabled)) + { + Log.outInfo(LogFilter.ServerLoading, "Loading Black Market Templates..."); + Global.BlackMarketMgr.LoadTemplates(); + + Log.outInfo(LogFilter.ServerLoading, "Loading Black Market Auctions..."); + Global.BlackMarketMgr.LoadAuctions(); + } + + Log.outInfo(LogFilter.ServerLoading, "Loading Guild rewards..."); + Global.GuildMgr.LoadGuildRewards(); + + Log.outInfo(LogFilter.ServerLoading, "Loading Guilds..."); + Global.GuildMgr.LoadGuilds(); + + Global.GuildFinderMgr.LoadFromDB(); + + Log.outInfo(LogFilter.ServerLoading, "Loading ArenaTeams..."); + Global.ArenaTeamMgr.LoadArenaTeams(); + + Log.outInfo(LogFilter.ServerLoading, "Loading Groups..."); + Global.GroupMgr.LoadGroups(); + + Log.outInfo(LogFilter.ServerLoading, "Loading ReservedNames..."); + Global.ObjectMgr.LoadReservedPlayersNames(); + + Log.outInfo(LogFilter.ServerLoading, "Loading GameObjects for quests..."); + Global.ObjectMgr.LoadGameObjectForQuests(); + + Log.outInfo(LogFilter.ServerLoading, "Loading BattleMasters..."); + Global.BattlegroundMgr.LoadBattleMastersEntry(); + + Log.outInfo(LogFilter.ServerLoading, "Loading GameTeleports..."); + Global.ObjectMgr.LoadGameTele(); + + Log.outInfo(LogFilter.ServerLoading, "Loading Gossip menu..."); + Global.ObjectMgr.LoadGossipMenu(); + + Log.outInfo(LogFilter.ServerLoading, "Loading Gossip menu options..."); + Global.ObjectMgr.LoadGossipMenuItems(); + + Log.outInfo(LogFilter.ServerLoading, "Loading Vendors..."); + Global.ObjectMgr.LoadVendors(); // must be after load CreatureTemplate and ItemTemplate + + Log.outInfo(LogFilter.ServerLoading, "Loading Trainers..."); + Global.ObjectMgr.LoadTrainerSpell(); + + Log.outInfo(LogFilter.ServerLoading, "Loading Waypoints..."); + Global.WaypointMgr.Load(); + + Log.outInfo(LogFilter.ServerLoading, "Loading SmartAI Waypoints..."); + Global.SmartAIMgr.LoadWaypointFromDB(); + + Log.outInfo(LogFilter.ServerLoading, "Loading Creature Formations..."); + FormationMgr.LoadCreatureFormations(); + + Log.outInfo(LogFilter.ServerLoading, "Loading World States..."); // must be loaded before Battleground, outdoor PvP and conditions + LoadWorldStates(); + + Log.outInfo(LogFilter.ServerLoading, "Loading Terrain Phase definitions..."); + Global.ObjectMgr.LoadTerrainPhaseInfo(); + + Log.outInfo(LogFilter.ServerLoading, "Loading Terrain Swap Default definitions..."); + Global.ObjectMgr.LoadTerrainSwapDefaults(); + + Log.outInfo(LogFilter.ServerLoading, "Loading Terrain World Map definitions..."); + Global.ObjectMgr.LoadTerrainWorldMaps(); + + Log.outInfo(LogFilter.ServerLoading, "Loading Phase Area definitions..."); + Global.ObjectMgr.LoadAreaPhases(); + + Log.outInfo(LogFilter.ServerLoading, "Loading Conditions..."); + Global.ConditionMgr.LoadConditions(); + + Log.outInfo(LogFilter.ServerLoading, "Loading faction change achievement pairs..."); + Global.ObjectMgr.LoadFactionChangeAchievements(); + + Log.outInfo(LogFilter.ServerLoading, "Loading faction change spell pairs..."); + Global.ObjectMgr.LoadFactionChangeSpells(); + + Log.outInfo(LogFilter.ServerLoading, "Loading faction change item pairs..."); + Global.ObjectMgr.LoadFactionChangeItems(); + + Log.outInfo(LogFilter.ServerLoading, "Loading faction change quest pairs..."); + Global.ObjectMgr.LoadFactionChangeQuests(); + + Log.outInfo(LogFilter.ServerLoading, "Loading faction change reputation pairs..."); + Global.ObjectMgr.LoadFactionChangeReputations(); + + Log.outInfo(LogFilter.ServerLoading, "Loading faction change title pairs..."); + Global.ObjectMgr.LoadFactionChangeTitles(); + + Log.outInfo(LogFilter.ServerLoading, "Loading mount definitions..."); + CollectionMgr.LoadMountDefinitions(); + + Log.outInfo(LogFilter.ServerLoading, "Loading GM bugs..."); + Global.SupportMgr.LoadBugTickets(); + + Log.outInfo(LogFilter.ServerLoading, "Loading GM complaints..."); + Global.SupportMgr.LoadComplaintTickets(); + + Log.outInfo(LogFilter.ServerLoading, "Loading GM suggestions..."); + Global.SupportMgr.LoadSuggestionTickets(); + + //Log.outInfo(LogFilter.ServerLoading, "Loading GM surveys..."); + //Global.SupportMgr.LoadSurveys(); + + Log.outInfo(LogFilter.ServerLoading, "Loading garrison info..."); + Global.GarrisonMgr.Initialize(); + + // Handle outdated emails (delete/return) + Log.outInfo(LogFilter.ServerLoading, "Returning old mails..."); + Global.ObjectMgr.ReturnOrDeleteOldMails(false); + + Log.outInfo(LogFilter.ServerLoading, "Loading Autobroadcasts..."); + LoadAutobroadcasts(); + + // Load and initialize scripts + Global.ObjectMgr.LoadSpellScripts(); // must be after load Creature/Gameobject(Template/Data) + Global.ObjectMgr.LoadEventScripts(); // must be after load Creature/Gameobject(Template/Data) + Global.ObjectMgr.LoadWaypointScripts(); + + Log.outInfo(LogFilter.ServerLoading, "Loading spell script names..."); + Global.ObjectMgr.LoadSpellScriptNames(); + + Log.outInfo(LogFilter.ServerLoading, "Loading Creature Texts..."); + Global.CreatureTextMgr.LoadCreatureTexts(); + + Log.outInfo(LogFilter.ServerLoading, "Loading Creature Text Locales..."); + Global.CreatureTextMgr.LoadCreatureTextLocales(); + + Log.outInfo(LogFilter.ServerLoading, "Initializing Scripts..."); + Global.ScriptMgr.Initialize(); + Global.ScriptMgr.OnConfigLoad(false); // must be done after the ScriptMgr has been properly initialized + + Log.outInfo(LogFilter.ServerLoading, "Validating spell scripts..."); + Global.ObjectMgr.ValidateSpellScripts(); + + Log.outInfo(LogFilter.ServerLoading, "Loading SmartAI scripts..."); + Global.SmartAIMgr.LoadFromDB(); + + Log.outInfo(LogFilter.ServerLoading, "Loading Calendar data..."); + Global.CalendarMgr.LoadFromDB(); + + // Initialize game time and timers + Log.outInfo(LogFilter.ServerLoading, "Initialize game time and timers"); + m_gameTime = Time.UnixTime; + m_startTime = m_gameTime; + + DB.Login.Execute("INSERT INTO uptime (realmid, starttime, uptime, revision) VALUES({0}, {1}, 0, '{2}')", _realm.Id.Realm, m_startTime, ""); // One-time query + + m_timers[WorldTimers.Weathers].SetInterval(1 * Time.InMilliseconds); + m_timers[WorldTimers.Auctions].SetInterval(Time.Minute * Time.InMilliseconds); + m_timers[WorldTimers.AuctionsPending].SetInterval(250); + + //Update "uptime" table based on configuration entry in minutes. + m_timers[WorldTimers.UpTime].SetInterval(10 * Time.Minute * Time.InMilliseconds); + //erase corpses every 20 minutes + m_timers[WorldTimers.Corpses].SetInterval(20 * Time.Minute * Time.InMilliseconds); + m_timers[WorldTimers.CleanDB].SetInterval(WorldConfig.GetIntValue(WorldCfg.LogdbClearinterval) * Time.Minute * Time.InMilliseconds); + m_timers[WorldTimers.AutoBroadcast].SetInterval(WorldConfig.GetIntValue(WorldCfg.AutoBroadcastInterval)); + // check for chars to delete every day + m_timers[WorldTimers.DeleteChars].SetInterval(Time.Day * Time.InMilliseconds); + // for AhBot + m_timers[WorldTimers.AhBot].SetInterval(WorldConfig.GetIntValue(WorldCfg.AhbotUpdateInterval) * Time.InMilliseconds); // every 20 sec + m_timers[WorldTimers.GuildSave].SetInterval(WorldConfig.GetIntValue(WorldCfg.GuildSaveInterval) * Time.Minute * Time.InMilliseconds); + + m_timers[WorldTimers.Blackmarket].SetInterval(10 * Time.InMilliseconds); + + blackmarket_timer = 0; + + //to set mailtimer to return mails every day between 4 and 5 am + //mailtimer is increased when updating auctions + //one second is 1000 -(tested on win system) + /// @todo Get rid of magic numbers + var localTime = Time.UnixTimeToDateTime(m_gameTime).ToLocalTime(); + mail_timer = ((((localTime.Hour + 20) % 24) * Time.Hour * Time.InMilliseconds) / m_timers[WorldTimers.Auctions].GetInterval()); + //1440 + mail_timer_expires = ((Time.Day * Time.InMilliseconds) / (m_timers[(int)WorldTimers.Auctions].GetInterval())); + Log.outInfo(LogFilter.ServerLoading, "Mail timer set to: {0}, mail return is called every {1} minutes", mail_timer, mail_timer_expires); + + //- Initialize MapManager + Log.outInfo(LogFilter.ServerLoading, "Starting Map System"); + Global.MapMgr.Initialize(); + + Log.outInfo(LogFilter.ServerLoading, "Starting Game Event system..."); + uint nextGameEvent = Global.GameEventMgr.StartSystem(); + m_timers[WorldTimers.Events].SetInterval(nextGameEvent); //depend on next event + + // Delete all characters which have been deleted X days before + Player.DeleteOldCharacters(); + + // Delete all custom channels which haven't been used for PreserveCustomChannelDuration days. + Channel.CleanOldChannelsInDB(); + + Log.outInfo(LogFilter.ServerLoading, "Initializing Opcodes..."); + PacketManager.Initialize(); + + Log.outInfo(LogFilter.ServerLoading, "Starting Arena Season..."); + Global.GameEventMgr.StartArenaSeason(); + + Global.SupportMgr.Initialize(); + + // Initialize Battlegrounds + Log.outInfo(LogFilter.ServerLoading, "Starting BattlegroundSystem"); + Global.BattlegroundMgr.LoadBattlegroundTemplates(); + + // Initialize outdoor pvp + Log.outInfo(LogFilter.ServerLoading, "Starting Outdoor PvP System"); + Global.OutdoorPvPMgr.InitOutdoorPvP(); + + // Initialize Battlefield + Log.outInfo(LogFilter.ServerLoading, "Starting Battlefield System"); + Global.BattleFieldMgr.InitBattlefield(); + + Log.outInfo(LogFilter.ServerLoading, "Loading Transports..."); + Global.TransportMgr.SpawnContinentTransports(); + + // Initialize Warden + Log.outInfo(LogFilter.ServerLoading, "Loading Warden Checks..."); + Global.WardenCheckMgr.LoadWardenChecks(); + + Log.outInfo(LogFilter.ServerLoading, "Loading Warden Action Overrides..."); + Global.WardenCheckMgr.LoadWardenOverrides(); + + Log.outInfo(LogFilter.ServerLoading, "Deleting expired bans..."); + DB.Login.Execute("DELETE FROM ip_banned WHERE unbandate <= UNIX_TIMESTAMP() AND unbandate<>bandate"); // One-time query + + Log.outInfo(LogFilter.ServerLoading, "Calculate next daily quest reset time..."); + InitDailyQuestResetTime(); + + Log.outInfo(LogFilter.ServerLoading, "Calculate next weekly quest reset time..."); + InitWeeklyQuestResetTime(); + + Log.outInfo(LogFilter.ServerLoading, "Calculate next monthly quest reset time..."); + InitMonthlyQuestResetTime(); + + Log.outInfo(LogFilter.ServerLoading, "Calculate random Battlegroundreset time..."); + InitRandomBGResetTime(); + + Log.outInfo(LogFilter.ServerLoading, "Calculate Guild cap reset time..."); + InitGuildResetTime(); + + Log.outInfo(LogFilter.ServerLoading, "Calculate next currency reset time..."); + InitCurrencyResetTime(); + + LoadCharacterInfoStorage(); + + Log.outInfo(LogFilter.ServerLoading, "Loading race and class expansion requirements..."); + Global.ObjectMgr.LoadRaceAndClassExpansionRequirements(); + + Log.outInfo(LogFilter.ServerLoading, "Loading character templates..."); + Global.CharacterTemplateDataStorage.LoadCharacterTemplates(); + + Log.outInfo(LogFilter.ServerLoading, "Loading realm names..."); + Global.ObjectMgr.LoadRealmNames(); + + Log.outInfo(LogFilter.ServerLoading, "Loading battle pets info..."); + BattlePetMgr.Initialize(); + + Log.outInfo(LogFilter.ServerLoading, "Loading scenarios"); + Global.ScenarioMgr.LoadDB2Data(); + Global.ScenarioMgr.LoadDBData(); + + Log.outInfo(LogFilter.ServerLoading, "Loading scenario poi data"); + Global.ScenarioMgr.LoadScenarioPOI(); + + // Preload all cells, if required for the base maps + if (WorldConfig.GetBoolValue(WorldCfg.BasemapLoadGrids)) + { + Global.MapMgr.DoForAllMaps(map => + { + if (!map.Instanceable()) + { + Log.outInfo(LogFilter.ServerLoading, "Pre-loading base map data for map {0}", map.GetId()); + map.LoadAllCells(); + } + }); + } + + uint startupDuration = Time.GetMSTimeDiffToNow(startupBegin); + + Log.outInfo(LogFilter.Server, "World initialized in {0} minutes {1} seconds", (startupDuration / 60000), ((startupDuration % 60000) / 1000)); + + Log.SetRealmId(_realm.Id.Realm); + } + + public void LoadConfigSettings(bool reload = false) + { + WorldConfig.Load(reload); + + m_defaultDbcLocale = (LocaleConstant)ConfigMgr.GetDefaultValue("DBC.Locale", 0); + + if (m_defaultDbcLocale >= LocaleConstant.Total || m_defaultDbcLocale == LocaleConstant.None) + { + Log.outError(LogFilter.ServerLoading, "Incorrect DBC.Locale! Must be >= 0 and < {0} and not {1} (set to 0)", LocaleConstant.Total, LocaleConstant.None); + m_defaultDbcLocale = LocaleConstant.enUS; + } + + Log.outInfo(LogFilter.ServerLoading, "Using {0} DBC Locale", m_defaultDbcLocale); + + Global.WorldMgr.SetPlayerAmountLimit((uint)ConfigMgr.GetDefaultValue("PlayerLimit", 100)); + Global.WorldMgr.SetMotd(ConfigMgr.GetDefaultValue("Motd", "Welcome to a Cypher Core Server.")); + + if (reload) + { + Global.SupportMgr.SetSupportSystemStatus(WorldConfig.GetBoolValue(WorldCfg.SupportEnabled)); + Global.SupportMgr.SetTicketSystemStatus(WorldConfig.GetBoolValue(WorldCfg.SupportTicketsEnabled)); + Global.SupportMgr.SetBugSystemStatus(WorldConfig.GetBoolValue(WorldCfg.SupportBugsEnabled)); + Global.SupportMgr.SetComplaintSystemStatus(WorldConfig.GetBoolValue(WorldCfg.SupportComplaintsEnabled)); + Global.SupportMgr.SetSuggestionSystemStatus(WorldConfig.GetBoolValue(WorldCfg.SupportSuggestionsEnabled)); + + Global.MapMgr.SetMapUpdateInterval(WorldConfig.GetIntValue(WorldCfg.IntervalMapupdate)); + Global.MapMgr.SetGridCleanUpDelay(WorldConfig.GetUIntValue(WorldCfg.IntervalGridclean)); + + m_timers[WorldTimers.UpTime].SetInterval(WorldConfig.GetIntValue(WorldCfg.UptimeUpdate) * Time.Minute * Time.InMilliseconds); + m_timers[WorldTimers.UpTime].Reset(); + + m_timers[WorldTimers.CleanDB].SetInterval(WorldConfig.GetIntValue(WorldCfg.LogdbClearinterval) * Time.Minute * Time.InMilliseconds); + m_timers[WorldTimers.CleanDB].Reset(); + + + m_timers[WorldTimers.AutoBroadcast].SetInterval(WorldConfig.GetIntValue(WorldCfg.AutoBroadcastInterval)); + m_timers[WorldTimers.AutoBroadcast].Reset(); + } + + for (byte i = 0; i < (int)UnitMoveType.Max; ++i) + SharedConst.playerBaseMoveSpeed[i] = SharedConst.baseMoveSpeed[i] * WorldConfig.GetFloatValue(WorldCfg.RateMovespeed); + + var rateCreatureAggro = WorldConfig.GetFloatValue(WorldCfg.RateCreatureAggro); + //visibility on continents + m_MaxVisibleDistanceOnContinents = ConfigMgr.GetDefaultValue("Visibility.Distance.Continents", SharedConst.DefaultVisibilityDistance); + if (m_MaxVisibleDistanceOnContinents < 45 * rateCreatureAggro) + { + Log.outError(LogFilter.ServerLoading, "Visibility.Distance.Continents can't be less max aggro radius {0}", 45 * rateCreatureAggro); + m_MaxVisibleDistanceOnContinents = 45 * rateCreatureAggro; + } + else if (m_MaxVisibleDistanceOnContinents > SharedConst.MaxVisibilityDistance) + { + Log.outError(LogFilter.ServerLoading, "Visibility.Distance.Continents can't be greater {0}", SharedConst.MaxVisibilityDistance); + m_MaxVisibleDistanceOnContinents = SharedConst.MaxVisibilityDistance; + } + + //visibility in instances + m_MaxVisibleDistanceInInstances = ConfigMgr.GetDefaultValue("Visibility.Distance.Instances", SharedConst.DefaultVisibilityInstance); + if (m_MaxVisibleDistanceInInstances < 45 * rateCreatureAggro) + { + Log.outError(LogFilter.ServerLoading, "Visibility.Distance.Instances can't be less max aggro radius {0}", 45 * rateCreatureAggro); + m_MaxVisibleDistanceInInstances = 45 * rateCreatureAggro; + } + else if (m_MaxVisibleDistanceInInstances > SharedConst.MaxVisibilityDistance) + { + Log.outError(LogFilter.ServerLoading, "Visibility.Distance.Instances can't be greater {0}", SharedConst.MaxVisibilityDistance); + m_MaxVisibleDistanceInInstances = SharedConst.MaxVisibilityDistance; + } + + //visibility in BG/Arenas + m_MaxVisibleDistanceInBGArenas = ConfigMgr.GetDefaultValue("Visibility.Distance.BGArenas", SharedConst.DefaultVisibilityBGAreans); + if (m_MaxVisibleDistanceInBGArenas < 45 * rateCreatureAggro) + { + Log.outError(LogFilter.ServerLoading, "Visibility.Distance.BGArenas can't be less max aggro radius {0}", 45 * rateCreatureAggro); + m_MaxVisibleDistanceInBGArenas = 45 * rateCreatureAggro; + } + else if (m_MaxVisibleDistanceInBGArenas > SharedConst.MaxVisibilityDistance) + { + Log.outError(LogFilter.ServerLoading, "Visibility.Distance.BGArenas can't be greater {0}", SharedConst.MaxVisibilityDistance); + m_MaxVisibleDistanceInBGArenas = SharedConst.MaxVisibilityDistance; + } + + m_visibility_notify_periodOnContinents = ConfigMgr.GetDefaultValue("Visibility.Notify.Period.OnContinents", SharedConst.DefaultVisibilityNotifyPeriod); + m_visibility_notify_periodInInstances = ConfigMgr.GetDefaultValue("Visibility.Notify.Period.InInstances", SharedConst.DefaultVisibilityNotifyPeriod); + m_visibility_notify_periodInBGArenas = ConfigMgr.GetDefaultValue("Visibility.Notify.Period.InBGArenas", SharedConst.DefaultVisibilityNotifyPeriod); + + string dataPath = ConfigMgr.GetDefaultValue("DataDir", "./"); + if (reload) + { + if (dataPath != _dataPath) + Log.outError(LogFilter.ServerLoading, "DataDir option can't be changed at worldserver.conf reload, using current value ({0}).", _dataPath); + } + else + { + _dataPath = dataPath; + Log.outInfo(LogFilter.ServerLoading, "Using DataDir {0}", _dataPath); + } + + Log.outInfo(LogFilter.ServerLoading, @"WORLD: MMap data directory is: {0}\mmaps", _dataPath); + + bool EnableIndoor = ConfigMgr.GetDefaultValue("vmap.EnableIndoorCheck", true); + bool EnableLOS = ConfigMgr.GetDefaultValue("vmap.EnableLOS", true); + bool EnableHeight = ConfigMgr.GetDefaultValue("vmap.EnableHeight", true); + + if (!EnableHeight) + Log.outError(LogFilter.ServerLoading, "VMap height checking Disabled! Creatures movements and other various things WILL be broken! Expect no support."); + + Global.VMapMgr.setEnableLineOfSightCalc(EnableLOS); + Global.VMapMgr.setEnableHeightCalc(EnableHeight); + + Log.outInfo(LogFilter.ServerLoading, "VMap support included. LineOfSight: {0}, getHeight: {1}, indoorCheck: {2}", EnableLOS, EnableHeight, EnableIndoor); + Log.outInfo(LogFilter.ServerLoading, @"VMap data directory is: {0}\vmaps", GetDataPath()); + } + + void ResetTimeDiffRecord() + { + if (m_updateTimeCount != 1) + return; + + m_currentTime = Time.GetMSTime(); + } + + void RecordTimeDiff(string text) + { + if (m_updateTimeCount != 1) + return; + + uint thisTime = Time.GetMSTime(); + uint diff = Time.GetMSTimeDiff(m_currentTime, thisTime); + + if (diff > WorldConfig.GetIntValue(WorldCfg.MinLogUpdate)) + Log.outInfo(LogFilter.Server, "Difftime {0}: {1}.", text, diff); + + m_currentTime = thisTime; + } + + public void LoadAutobroadcasts() + { + uint oldMSTime = Time.GetMSTime(); + + m_Autobroadcasts.Clear(); + + PreparedStatement stmt = DB.Login.GetPreparedStatement(LoginStatements.SEL_AUTOBROADCAST); + stmt.AddValue(0, _realm.Id.Realm); + + SQLResult result = DB.Login.Query(stmt); + if (result.IsEmpty()) + { + Log.outInfo(LogFilter.ServerLoading, "Loaded 0 autobroadcasts definitions. DB table `autobroadcast` is empty for this realm!"); + return; + } + + do + { + byte id = result.Read(0); + + m_Autobroadcasts[id] = new Autobroadcast(result.Read(2), result.Read(1)); + + } while (result.NextRow()); + + Log.outInfo(LogFilter.ServerLoading, "Loaded {0} autobroadcast definitions in {1} ms", m_Autobroadcasts.Count, Time.GetMSTimeDiffToNow(oldMSTime)); + } + + public void Update(uint diff) + { + m_updateTime = diff; + + if (diff > 100) + { + if (m_updateTimeSum > 60000) + { + Log.outDebug(LogFilter.Server, "Update time diff: {0}. Players online: {1}.", m_updateTimeSum / m_updateTimeCount, GetActiveSessionCount()); + m_updateTimeSum = m_updateTime; + m_updateTimeCount = 1; + } + else + { + m_updateTimeSum += m_updateTime; + ++m_updateTimeCount; + } + } + + // Update the different timers + for (WorldTimers i = 0; i < WorldTimers.Max; ++i) + { + if (m_timers[i].GetCurrent() >= 0) + m_timers[i].Update(diff); + else + m_timers[i].SetCurrent(0); + } + + _UpdateGameTime(); + + // Handle daily quests reset time + if (m_gameTime > m_NextDailyQuestReset) + { + DailyReset(); + InitDailyQuestResetTime(false); + } + + // Handle weekly quests reset time + if (m_gameTime > m_NextWeeklyQuestReset) + ResetWeeklyQuests(); + + // Handle monthly quests reset time + if (m_gameTime > m_NextMonthlyQuestReset) + ResetMonthlyQuests(); + + if (m_gameTime > m_NextRandomBGReset) + ResetRandomBG(); + + if (m_gameTime > m_NextGuildReset) + ResetGuildCap(); + + if (m_gameTime > m_NextCurrencyReset) + ResetCurrencyWeekCap(); + + //Handle auctions when the timer has passed + if (m_timers[WorldTimers.Auctions].Passed()) + { + m_timers[WorldTimers.Auctions].Reset(); + + // Update mails (return old mails with item, or delete them) + if (++mail_timer > mail_timer_expires) + { + mail_timer = 0; + Global.ObjectMgr.ReturnOrDeleteOldMails(true); + } + + // Handle expired auctions + Global.AuctionMgr.Update(); + } + + if (m_timers[WorldTimers.AuctionsPending].Passed()) + { + m_timers[WorldTimers.AuctionsPending].Reset(); + + //Global.AuctionMgr.UpdatePendingAuctions(); + } + + if (m_timers[WorldTimers.Blackmarket].Passed()) + { + m_timers[WorldTimers.Blackmarket].Reset(); + + ///- Update blackmarket, refresh auctions if necessary + if ((blackmarket_timer * m_timers[WorldTimers.Blackmarket].GetInterval() >= WorldConfig.GetIntValue(WorldCfg.BlackmarketUpdatePeriod) * Time.Hour * Time.InMilliseconds) || blackmarket_timer == 0) + { + Global.BlackMarketMgr.RefreshAuctions(); + blackmarket_timer = 1; // timer is 0 on startup + } + else + { + ++blackmarket_timer; + Global.BlackMarketMgr.Update(); + } + } + + //Handle session updates when the timer has passed + ResetTimeDiffRecord(); + UpdateSessions(diff); + RecordTimeDiff("UpdateSessions"); + + //Handle weather updates when the timer has passed + if (m_timers[WorldTimers.Weathers].Passed()) + { + m_timers[WorldTimers.Weathers].Reset(); + Global.WeatherMgr.Update((uint)m_timers[WorldTimers.Weathers].GetInterval()); + } + + ///
  • Update uptime table + if (m_timers[WorldTimers.UpTime].Passed()) + { + uint tmpDiff = (uint)(m_gameTime - m_startTime); + uint maxOnlinePlayers = GetMaxPlayerCount(); + + m_timers[WorldTimers.UpTime].Reset(); + + PreparedStatement stmt = DB.Login.GetPreparedStatement(LoginStatements.UPD_UPTIME_PLAYERS); + + stmt.AddValue(0, tmpDiff); + stmt.AddValue(1, maxOnlinePlayers); + stmt.AddValue(2, _realm.Id.Realm); + stmt.AddValue(3, m_startTime); + + DB.Login.Execute(stmt); + } + + ///
  • Clean logs table + if (WorldConfig.GetIntValue(WorldCfg.LogdbCleartime) > 0) // if not enabled, ignore the timer + { + if (m_timers[WorldTimers.CleanDB].Passed()) + { + m_timers[WorldTimers.CleanDB].Reset(); + + PreparedStatement stmt = DB.Login.GetPreparedStatement(LoginStatements.DEL_OLD_LOGS); + stmt.AddValue(0, WorldConfig.GetIntValue(WorldCfg.LogdbCleartime)); + stmt.AddValue(1, 0); + stmt.AddValue(2, GetRealm().Id.Realm); + + DB.Login.Execute(stmt); + } + } + + ResetTimeDiffRecord(); + Global.MapMgr.Update(diff); + RecordTimeDiff("UpdateMapMgr"); + + if (WorldConfig.GetBoolValue(WorldCfg.AutoBroadcast)) + { + if (m_timers[WorldTimers.AutoBroadcast].Passed()) + { + m_timers[WorldTimers.AutoBroadcast].Reset(); + SendAutoBroadcast(); + } + } + + Global.BattlegroundMgr.Update(diff); + RecordTimeDiff("UpdateBattlegroundMgr"); + + Global.OutdoorPvPMgr.Update(diff); + RecordTimeDiff("UpdateOutdoorPvPMgr"); + + Global.BattleFieldMgr.Update(diff); + RecordTimeDiff("BattlefieldMgr"); + + //- Delete all characters which have been deleted X days before + if (m_timers[WorldTimers.DeleteChars].Passed()) + { + m_timers[WorldTimers.DeleteChars].Reset(); + Player.DeleteOldCharacters(); + } + + Global.LFGMgr.Update(diff); + RecordTimeDiff("UpdateLFGMgr"); + + Global.GroupMgr.Update(diff); + RecordTimeDiff("GroupMgr"); + + // execute callbacks from sql queries that were queued recently + ProcessQueryCallbacks(); + RecordTimeDiff("ProcessQueryCallbacks"); + + // Erase corpses once every 20 minutes + if (m_timers[WorldTimers.Corpses].Passed()) + { + m_timers[WorldTimers.Corpses].Reset(); + Global.MapMgr.DoForAllMaps(map => map.RemoveOldCorpses()); + } + + // Process Game events when necessary + if (m_timers[WorldTimers.Events].Passed()) + { + m_timers[WorldTimers.Events].Reset(); // to give time for Update() to be processed + uint nextGameEvent = Global.GameEventMgr.Update(); + m_timers[WorldTimers.Events].SetInterval(nextGameEvent); + m_timers[WorldTimers.Events].Reset(); + } + + if (m_timers[WorldTimers.GuildSave].Passed()) + { + m_timers[WorldTimers.GuildSave].Reset(); + Global.GuildMgr.SaveGuilds(); + } + + // update the instance reset times + Global.InstanceSaveMgr.Update(); + + Global.ScriptMgr.OnWorldUpdate(diff); + } + + public void ForceGameEventUpdate() + { + m_timers[WorldTimers.Events].Reset(); // to give time for Update() to be processed + uint nextGameEvent = Global.GameEventMgr.Update(); + m_timers[WorldTimers.Events].SetInterval(nextGameEvent); + m_timers[WorldTimers.Events].Reset(); + } + + public void SendGlobalMessage(ServerPacket packet, WorldSession self = null, Team team = 0) + { + foreach (var session in m_sessions.Values) + { + if (session.GetPlayer() != null && session.GetPlayer().IsInWorld && session != self && + (team == 0 || session.GetPlayer().GetTeam() == team)) + { + session.SendPacket(packet); + } + } + } + + public void SendGlobalGMMessage(ServerPacket packet, WorldSession self = null, Team team = 0) + { + foreach (var session in m_sessions.Values) + { + // check if session and can receive global GM Messages and its not self + if (session == null || session == self || !session.HasPermission(RBACPermissions.ReceiveGlobalGmTextmessage)) + continue; + + // Player should be in world + Player player = session.GetPlayer(); + if (player == null || !player.IsInWorld) + continue; + + // Send only to same team, if team is given + if (team == 0 || player.GetTeam() == team) + session.SendPacket(packet); + } + } + + // Send a System Message to all players (except self if mentioned) + public void SendWorldText(CypherStrings string_id, params object[] args) + { + WorldWorldTextBuilder wt_builder = new WorldWorldTextBuilder((uint)string_id, args); + var wt_do = new LocalizedPacketListDo(wt_builder); + foreach (var session in m_sessions.Values) + { + if (session == null || !session.GetPlayer() || !session.GetPlayer().IsInWorld) + continue; + + wt_do.Invoke(session.GetPlayer()); + } + } + + // Send a System Message to all GMs (except self if mentioned) + public void SendGMText(CypherStrings string_id, params object[] args) + { + var wt_builder = new WorldWorldTextBuilder((uint)string_id, args); + var wt_do = new LocalizedPacketListDo(wt_builder); + foreach (var session in m_sessions.Values) + { + // Session should have permissions to receive global gm messages + if (session == null || !session.HasPermission(RBACPermissions.ReceiveGlobalGmTextmessage)) + continue; + + // Player should be in world + Player player = session.GetPlayer(); + if (!player || !player.IsInWorld) + continue; + + wt_do.Invoke(player); + } + } + + // Send a packet to all players (or players selected team) in the zone (except self if mentioned) + public bool SendZoneMessage(uint zone, ServerPacket packet, WorldSession self = null, uint team = 0) + { + bool foundPlayerToSend = false; + packet.Write(); + foreach (var session in m_sessions.Values) + { + if (session != null && session.GetPlayer() && session.GetPlayer().IsInWorld && + session.GetPlayer().GetZoneId() == zone && session != self && (team == 0 || (uint)session.GetPlayer().GetTeam() == team)) + { + session.SendPacket(packet, false); + foundPlayerToSend = true; + } + } + + return foundPlayerToSend; + } + + // Send a System Message to all players in the zone (except self if mentioned) + public void SendZoneText(uint zone, string text, WorldSession self = null, uint team = 0) + { + ChatPkt data = new ChatPkt(); + data.Initialize(ChatMsg.System, Language.Universal, null, null, text); + SendZoneMessage(zone, data, self, team); + } + + public void KickAll() + { + m_QueuedPlayer.Clear(); // prevent send queue update packet and login queued sessions + + // session not removed at kick and will removed in next update tick + foreach (var session in m_sessions.Values) + session.KickPlayer(); + } + + void KickAllLess(AccountTypes sec) + { + // session not removed at kick and will removed in next update tick + foreach (var session in m_sessions.Values) + if (session.GetSecurity() < sec) + session.KickPlayer(); + } + + /// Ban an account or ban an IP address, duration will be parsed using TimeStringToSecs if it is positive, otherwise permban + public BanReturn BanAccount(BanMode mode, string nameOrIP, string duration, string reason, string author) + { + uint duration_secs = Time.TimeStringToSecs(duration); + return BanAccount(mode, nameOrIP, duration_secs, reason, author); + } + + /// Ban an account or ban an IP address, duration is in seconds if positive, otherwise permban + public BanReturn BanAccount(BanMode mode, string nameOrIP, uint duration_secs, string reason, string author) + { + SQLResult resultAccounts; + PreparedStatement stmt = null; + + // Update the database with ban information + switch (mode) + { + case BanMode.IP: + // No SQL injection with prepared statements + stmt = DB.Login.GetPreparedStatement(LoginStatements.SEL_ACCOUNT_BY_IP); + stmt.AddValue(0, nameOrIP); + resultAccounts = DB.Login.Query(stmt); + stmt = DB.Login.GetPreparedStatement(LoginStatements.INS_IP_BANNED); + stmt.AddValue(0, nameOrIP); + stmt.AddValue(1, duration_secs); + stmt.AddValue(2, author); + stmt.AddValue(3, reason); + DB.Login.Execute(stmt); + break; + case BanMode.Account: + // No SQL injection with prepared statements + stmt = DB.Login.GetPreparedStatement(LoginStatements.SEL_ACCOUNT_ID_BY_NAME); + stmt.AddValue(0, nameOrIP); + resultAccounts = DB.Login.Query(stmt); + break; + case BanMode.Character: + // No SQL injection with prepared statements + stmt = DB.Characters.GetPreparedStatement(CharStatements.SEL_ACCOUNT_BY_NAME); + stmt.AddValue(0, nameOrIP); + resultAccounts = DB.Characters.Query(stmt); + break; + default: + return BanReturn.SyntaxError; + } + + if (resultAccounts == null) + { + if (mode == BanMode.IP) + return BanReturn.Success; // ip correctly banned but nobody affected (yet) + else + return BanReturn.Notfound; // Nobody to ban + } + + // Disconnect all affected players (for IP it can be several) + SQLTransaction trans = new SQLTransaction(); + do + { + uint account = resultAccounts.Read(0); + + if (mode != BanMode.IP) + { + // make sure there is only one active ban + stmt = DB.Login.GetPreparedStatement(LoginStatements.UPD_ACCOUNT_NOT_BANNED); + stmt.AddValue(0, account); + trans.Append(stmt); + // No SQL injection with prepared statements + stmt = DB.Login.GetPreparedStatement(LoginStatements.INS_ACCOUNT_BANNED); + stmt.AddValue(0, account); + stmt.AddValue(1, duration_secs); + stmt.AddValue(2, author); + stmt.AddValue(3, reason); + trans.Append(stmt); + } + + WorldSession sess = FindSession(account); + if (sess) + { + if (sess.GetPlayerName() != author) + sess.KickPlayer(); + } + } while (resultAccounts.NextRow()); + + DB.Login.CommitTransaction(trans); + + return BanReturn.Success; + } + + /// Remove a ban from an account or IP address + public bool RemoveBanAccount(BanMode mode, string nameOrIP) + { + PreparedStatement stmt = null; + if (mode == BanMode.IP) + { + stmt = DB.Login.GetPreparedStatement(LoginStatements.DEL_IP_NOT_BANNED); + stmt.AddValue(0, nameOrIP); + DB.Login.Execute(stmt); + } + else + { + uint account = 0; + if (mode == BanMode.Account) + account = Global.AccountMgr.GetId(nameOrIP); + else if (mode == BanMode.Character) + { + stmt = DB.Characters.GetPreparedStatement(CharStatements.SEL_ACCOUNT_BY_NAME); + stmt.AddValue(0, nameOrIP); + + SQLResult result = DB.Characters.Query(stmt); + if (!result.IsEmpty()) + account = result.Read(0); + } + + if (account == 0) + return false; + + //NO SQL injection as account is uint32 + stmt = DB.Login.GetPreparedStatement(LoginStatements.UPD_ACCOUNT_NOT_BANNED); + stmt.AddValue(0, account); + DB.Login.Execute(stmt); + } + return true; + } + + /// Ban an account or ban an IP address, duration will be parsed using TimeStringToSecs if it is positive, otherwise permban + public BanReturn BanCharacter(string name, string duration, string reason, string author) + { + Player pBanned = Global.ObjAccessor.FindConnectedPlayerByName(name); + ObjectGuid guid; + + uint duration_secs = Time.TimeStringToSecs(duration); + + /// Pick a player to ban if not online + if (!pBanned) + { + guid = ObjectManager.GetPlayerGUIDByName(name); + if (guid.IsEmpty()) + return BanReturn.Notfound; // Nobody to ban + } + else + guid = pBanned.GetGUID(); + + // make sure there is only one active ban + PreparedStatement stmt = DB.Characters.GetPreparedStatement(CharStatements.UPD_CHARACTER_BAN); + stmt.AddValue(0, guid.GetCounter()); + DB.Characters.Execute(stmt); + + stmt = DB.Characters.GetPreparedStatement(CharStatements.INS_CHARACTER_BAN); + stmt.AddValue(0, guid.GetCounter()); + stmt.AddValue(1, duration_secs); + stmt.AddValue(2, author); + stmt.AddValue(3, reason); + DB.Characters.Execute(stmt); + + if (pBanned) + pBanned.GetSession().KickPlayer(); + + return BanReturn.Success; + } + + // Remove a ban from a character + public bool RemoveBanCharacter(string name) + { + Player pBanned = Global.ObjAccessor.FindConnectedPlayerByName(name); + ObjectGuid guid; + + // Pick a player to ban if not online + if (!pBanned) + { + guid = ObjectManager.GetPlayerGUIDByName(name); + if (guid.IsEmpty()) + return false; // Nobody to ban + } + else + guid = pBanned.GetGUID(); + + PreparedStatement stmt = DB.Characters.GetPreparedStatement(CharStatements.UPD_CHARACTER_BAN); + stmt.AddValue(0, guid.GetCounter()); + DB.Characters.Execute(stmt); + return true; + } + + void _UpdateGameTime() + { + // update the time + long thisTime = Time.UnixTime; + uint elapsed = (uint)(thisTime - m_gameTime); + m_gameTime = thisTime; + + //- if there is a shutdown timer + if (!IsStopped && m_ShutdownTimer > 0 && elapsed > 0) + { + //- ... and it is overdue, stop the world + if (m_ShutdownTimer <= elapsed) + { + if (!m_ShutdownMask.HasAnyFlag(ShutdownMask.Idle) || GetActiveAndQueuedSessionCount() == 0) + IsStopped = true; // exist code already set + else + m_ShutdownTimer = 1; // minimum timer value to wait idle state + } + //- ... else decrease it and if necessary display a shutdown countdown to the users + else + { + m_ShutdownTimer -= elapsed; + + ShutdownMsg(); + } + } + } + + public void ShutdownServ(uint time, ShutdownMask options, ShutdownExitCode exitcode, string reason = "") + { + // ignore if server shutdown at next tick + if (IsStopped) + return; + + m_ShutdownMask = options; + m_ExitCode = exitcode; + + // If the shutdown time is 0, set m_stopEvent (except if shutdown is 'idle' with remaining sessions) + if (time == 0) + { + if (!options.HasAnyFlag(ShutdownMask.Idle) || GetActiveAndQueuedSessionCount() == 0) + IsStopped = true; // exist code already set + else + m_ShutdownTimer = 1; //So that the session count is re-evaluated at next world tick + } + // Else set the shutdown timer and warn users + else + { + m_ShutdownTimer = time; + ShutdownMsg(true, null, reason); + } + + Global.ScriptMgr.OnShutdownInitiate(exitcode, options); + } + + public void ShutdownMsg(bool show = false, Player player = null, string reason = "") + { + // not show messages for idle shutdown mode + if (m_ShutdownMask.HasAnyFlag(ShutdownMask.Idle)) + return; + + // Display a message every 12 hours, hours, 5 minutes, minute, 5 seconds and finally seconds + if (show || + (m_ShutdownTimer < 5 * Time.Minute && (m_ShutdownTimer % 15) == 0) || // < 5 min; every 15 sec + (m_ShutdownTimer < 15 * Time.Minute && (m_ShutdownTimer % Time.Minute) == 0) || // < 15 min ; every 1 min + (m_ShutdownTimer < 30 * Time.Minute && (m_ShutdownTimer % (5 * Time.Minute)) == 0) || // < 30 min ; every 5 min + (m_ShutdownTimer < 12 * Time.Hour && (m_ShutdownTimer % Time.Hour) == 0) || // < 12 h ; every 1 h + (m_ShutdownTimer > 12 * Time.Hour && (m_ShutdownTimer % (12 * Time.Hour)) == 0)) // > 12 h ; every 12 h + { + var str = Time.secsToTimeString(m_ShutdownTimer); + if (!reason.IsEmpty()) + str += " - " + reason; + + ServerMessageType msgid = m_ShutdownMask.HasAnyFlag(ShutdownMask.Restart) ? ServerMessageType.RestartTime : ServerMessageType.ShutdownTime; + + SendServerMessage(msgid, str, player); + Log.outDebug(LogFilter.Server, "Server is {0} in {1}", (m_ShutdownMask.HasAnyFlag(ShutdownMask.Restart) ? "restart" : "shuttingdown"), str); + } + } + + public uint ShutdownCancel() + { + // nothing cancel or too later + if (m_ShutdownTimer == 0 || IsStopped) + return 0; + + ServerMessageType msgid = m_ShutdownMask.HasAnyFlag(ShutdownMask.Restart) ? ServerMessageType.RestartCancelled : ServerMessageType.ShutdownCancelled; + + uint oldTimer = m_ShutdownTimer; + m_ShutdownMask = 0; + m_ShutdownTimer = 0; + m_ExitCode = (byte)ShutdownExitCode.Shutdown; // to default value + SendServerMessage(msgid); + + Log.outDebug(LogFilter.Server, "Server {0} cancelled.", (m_ShutdownMask.HasAnyFlag(ShutdownMask.Restart) ? "restart" : "shutdown")); + + Global.ScriptMgr.OnShutdownCancel(); + return oldTimer; + } + + public void SendServerMessage(ServerMessageType messageID, string stringParam = "", Player player = null) + { + ChatServerMessage packet = new ChatServerMessage(); + packet.MessageID = (int)messageID; + if (messageID <= ServerMessageType.String) + packet.StringParam = stringParam; + + if (player) + player.SendPacket(packet); + else + SendGlobalMessage(packet); + } + + public void UpdateSessions(uint diff) + { + Tuple linkInfo; + while (_linkSocketQueue.TryDequeue(out linkInfo)) + ProcessLinkInstanceSocket(linkInfo); + + // Add new sessions + WorldSession sess; + while (addSessQueue.TryDequeue(out sess)) + AddSession_(sess); + + // Then send an update signal to remaining ones + foreach (var pair in m_sessions.ToList()) + { + WorldSession session = pair.Value; + WorldSessionFilter updater = new WorldSessionFilter(session); + if (!session.Update(diff, updater)) // As interval = 0 + { + if (!RemoveQueuedPlayer(session) && session != null && WorldConfig.GetIntValue(WorldCfg.IntervalDisconnectTolerance) != 0) + m_disconnects[session.GetAccountId()] = Time.UnixTime; + + RemoveQueuedPlayer(session); + m_sessions.Remove(pair.Key); + session.Dispose(); + } + } + } + + void SendAutoBroadcast() + { + if (m_Autobroadcasts.Empty()) + return; + + var pair = m_Autobroadcasts.SelectRandomElementByWeight(autoPair => + { + return autoPair.Value.Weight; + }); + + uint abcenter = WorldConfig.GetUIntValue(WorldCfg.AutoBroadcastCenter); + + if (abcenter == 0) + SendWorldText(CypherStrings.AutoBroadcast, pair.Value.Message); + else if (abcenter == 1) + SendGlobalMessage(new PrintNotification(pair.Value.Message)); + else if (abcenter == 2) + { + SendWorldText(CypherStrings.AutoBroadcast, pair.Value.Message); + SendGlobalMessage(new PrintNotification(pair.Value.Message)); + } + + Log.outDebug(LogFilter.Misc, "AutoBroadcast: '{0}'", pair.Value.Message); + } + + public void UpdateRealmCharCount(uint accountId) + { + PreparedStatement stmt = DB.Characters.GetPreparedStatement(CharStatements.SEL_CHARACTER_COUNT); + stmt.AddValue(0, accountId); + _queryProcessor.AddQuery(DB.Characters.AsyncQuery(stmt).WithCallback(_UpdateRealmCharCount)); + } + + void _UpdateRealmCharCount(SQLResult result) + { + if (!result.IsEmpty()) + { + uint Id = result.Read(0); + uint charCount = result.Read(1); + + SQLTransaction trans = new SQLTransaction(); + + PreparedStatement stmt = DB.Login.GetPreparedStatement(LoginStatements.DEL_REALM_CHARACTERS_BY_REALM); + stmt.AddValue(0, Id); + stmt.AddValue(1, _realm.Id.Realm); + trans.Append(stmt); + + stmt = DB.Login.GetPreparedStatement(LoginStatements.INS_REALM_CHARACTERS); + stmt.AddValue(0, charCount); + stmt.AddValue(1, Id); + stmt.AddValue(2, _realm.Id.Realm); + trans.Append(stmt); + + DB.Login.CommitTransaction(trans); + } + } + + void InitWeeklyQuestResetTime() + { + long wstime = getWorldState(WorldStates.WeeklyQuestResetTime); + long curtime = Time.UnixTime; + m_NextWeeklyQuestReset = wstime < curtime ? curtime : wstime; + } + + void InitDailyQuestResetTime(bool loading = true) + { + long mostRecentQuestTime = 0; + + if (loading) + { + SQLResult result = DB.Characters.Query("SELECT MAX(time) FROM character_queststatus_daily"); + if (!result.IsEmpty()) + { + mostRecentQuestTime = result.Read(0); + } + } + + + // FIX ME: client not show day start time + long curTime = Time.UnixTime; + + // current day reset time + long curDayResetTime = Time.GetNextResetUnixTime(WorldConfig.GetIntValue(WorldCfg.DailyQuestResetTimeHour)); + + // last reset time before current moment + long resetTime = (curTime < curDayResetTime) ? curDayResetTime - Time.Day : curDayResetTime; + + // need reset (if we have quest time before last reset time (not processed by some reason) + if (mostRecentQuestTime != 0 && mostRecentQuestTime <= resetTime) + m_NextDailyQuestReset = mostRecentQuestTime; + else // plan next reset time + m_NextDailyQuestReset = (curTime >= curDayResetTime) ? curDayResetTime + Time.Day : curDayResetTime; + } + + void InitMonthlyQuestResetTime() + { + long wstime = getWorldState(WorldStates.MonthlyQuestResetTime); + long curtime = Time.UnixTime; + m_NextMonthlyQuestReset = wstime < curtime ? curtime : wstime; + } + + void InitRandomBGResetTime() + { + long bgtime = getWorldState(WorldStates.BGDailyResetTime); + if (bgtime == 0) + m_NextRandomBGReset = Time.UnixTime; // game time not yet init + + // generate time by config + long curTime = Time.UnixTime; + + // current day reset time + long nextDayResetTime = Time.GetNextResetUnixTime(WorldConfig.GetIntValue(WorldCfg.RandomBgResetHour)); + + // next reset time before current moment + if (curTime >= nextDayResetTime) + nextDayResetTime += Time.Day; + + // normalize reset time + m_NextRandomBGReset = bgtime < curTime ? nextDayResetTime - Time.Day : nextDayResetTime; + + if (bgtime == 0) + setWorldState(WorldStates.BGDailyResetTime, (ulong)m_NextRandomBGReset); + } + + void InitGuildResetTime() + { + long gtime = getWorldState(WorldStates.GuildDailyResetTime); + if (gtime == 0) + m_NextGuildReset = Time.UnixTime; // game time not yet init + + long curTime = Time.UnixTime; + var nextDayResetTime = Time.GetNextResetUnixTime(WorldConfig.GetIntValue(WorldCfg.GuildResetHour)); + + if (curTime >= nextDayResetTime) + nextDayResetTime += Time.Day; + + // normalize reset time + m_NextGuildReset = gtime < curTime ? nextDayResetTime - Time.Day : nextDayResetTime; + + if (gtime == 0) + setWorldState(WorldStates.GuildDailyResetTime, (ulong)m_NextGuildReset); + } + + void InitCurrencyResetTime() + { + long currencytime = getWorldState(WorldStates.CurrencyResetTime); + if (currencytime == 0) + m_NextCurrencyReset = Time.UnixTime; // game time not yet init + + // generate time by config + long curTime = Time.UnixTime; + + var nextWeekResetTime = Time.GetNextResetUnixTime(WorldConfig.GetIntValue(WorldCfg.CurrencyResetDay), WorldConfig.GetIntValue(WorldCfg.CurrencyResetHour)); + + // next reset time before current moment + if (curTime >= nextWeekResetTime) + nextWeekResetTime += WorldConfig.GetIntValue(WorldCfg.CurrencyResetInterval) * Time.Day; + + // normalize reset time + m_NextCurrencyReset = currencytime < curTime ? nextWeekResetTime - WorldConfig.GetIntValue(WorldCfg.CurrencyResetInterval) * Time.Day : nextWeekResetTime; + + if (currencytime == 0) + setWorldState(WorldStates.CurrencyResetTime, (ulong)m_NextCurrencyReset); + } + + void DailyReset() + { + Log.outInfo(LogFilter.Server, "Daily quests reset for all characters."); + + PreparedStatement stmt = DB.Characters.GetPreparedStatement(CharStatements.DEL_RESET_CHARACTER_QUESTSTATUS_DAILY); + DB.Characters.Execute(stmt); + + stmt = DB.Characters.GetPreparedStatement(CharStatements.UPD_CHARACTER_GARRISON_FOLLOWER_ACTIVATIONS); + stmt.AddValue(0, 1); + DB.Characters.Execute(stmt); + + foreach (var session in m_sessions.Values) + if (session.GetPlayer() != null) + session.GetPlayer().DailyReset(); + + // change available dailies + Global.PoolMgr.ChangeDailyQuests(); + } + + void ResetCurrencyWeekCap() + { + DB.Characters.Execute("UPDATE `character_currency` SET `WeeklyQuantity` = 0"); + + foreach (var session in m_sessions.Values) + if (session.GetPlayer() != null) + session.GetPlayer().ResetCurrencyWeekCap(); + + m_NextCurrencyReset = m_NextCurrencyReset + Time.Day * WorldConfig.GetIntValue(WorldCfg.CurrencyResetInterval); + setWorldState(WorldStates.CurrencyResetTime, (ulong)m_NextCurrencyReset); + } + + public void LoadDBAllowedSecurityLevel() + { + PreparedStatement stmt = DB.Login.GetPreparedStatement(LoginStatements.SEL_REALMLIST_SECURITY_LEVEL); + stmt.AddValue(0, _realm.Id.Realm); + SQLResult result = DB.Login.Query(stmt); + + if (!result.IsEmpty()) + SetPlayerSecurityLimit((AccountTypes)result.Read(0)); + } + + public void SetPlayerSecurityLimit(AccountTypes accountType) + { + AccountTypes security = accountType < AccountTypes.Console ? accountType : AccountTypes.Player; + bool update = security > m_allowedSecurityLevel; + m_allowedSecurityLevel = security; + if (update) + KickAllLess(m_allowedSecurityLevel); + } + + void ResetWeeklyQuests() + { + Log.outInfo(LogFilter.Server, "Weekly quests reset for all characters."); + + PreparedStatement stmt = DB.Characters.GetPreparedStatement(CharStatements.DEL_RESET_CHARACTER_QUESTSTATUS_WEEKLY); + DB.Characters.Execute(stmt); + + foreach (var session in m_sessions.Values) + if (session.GetPlayer() != null) + session.GetPlayer().ResetWeeklyQuestStatus(); + + m_NextWeeklyQuestReset = (m_NextWeeklyQuestReset + Time.Week); + setWorldState(WorldStates.WeeklyQuestResetTime, (ulong)m_NextWeeklyQuestReset); + + // change available weeklies + Global.PoolMgr.ChangeWeeklyQuests(); + } + + void ResetMonthlyQuests() + { + Log.outInfo(LogFilter.Server, "Monthly quests reset for all characters."); + + PreparedStatement stmt = DB.Characters.GetPreparedStatement(CharStatements.DEL_RESET_CHARACTER_QUESTSTATUS_MONTHLY); + DB.Characters.Execute(stmt); + + foreach (var session in m_sessions.Values) + if (session.GetPlayer() != null) + session.GetPlayer().ResetMonthlyQuestStatus(); + + long curTime = Time.UnixTime; + + // current day reset time + long curDayResetTime = Time.GetNextResetUnixTime(30, 1, 0); + + // last reset time before current moment + long nextMonthResetTime = (curTime < curDayResetTime) ? curDayResetTime - Time.Day : curDayResetTime; + + // plan next reset time + m_NextMonthlyQuestReset = (curTime >= nextMonthResetTime) ? nextMonthResetTime + Time.Month : nextMonthResetTime; + + setWorldState(WorldStates.MonthlyQuestResetTime, (ulong)m_NextMonthlyQuestReset); + } + + public void ResetEventSeasonalQuests(ushort event_id) + { + PreparedStatement stmt = DB.Characters.GetPreparedStatement(CharStatements.DEL_RESET_CHARACTER_QUESTSTATUS_SEASONAL_BY_EVENT); + stmt.AddValue(0, event_id); + DB.Characters.Execute(stmt); + + foreach (var session in m_sessions.Values) + if (session.GetPlayer()) + session.GetPlayer().ResetSeasonalQuestStatus(event_id); + } + + void ResetRandomBG() + { + Log.outInfo(LogFilter.Server, "Random BG status reset for all characters."); + + PreparedStatement stmt = DB.Characters.GetPreparedStatement(CharStatements.DEL_BATTLEGROUND_RANDOM_ALL); + DB.Characters.Execute(stmt); + + foreach (var session in m_sessions.Values) + if (session.GetPlayer()) + session.GetPlayer().SetRandomWinner(false); + + m_NextRandomBGReset = m_NextRandomBGReset + Time.Day; + setWorldState(WorldStates.BGDailyResetTime, (ulong)m_NextRandomBGReset); + } + + void ResetGuildCap() + { + m_NextGuildReset = (m_NextGuildReset + Time.Day); + setWorldState(WorldStates.GuildDailyResetTime, (ulong)m_NextGuildReset); + ulong week = getWorldState(WorldStates.GuildWeeklyResetTime); + week = week < 7 ? week + 1 : 1; + + Log.outInfo(LogFilter.Server, "Guild Daily Cap reset. Week: {0}", week == 1); + setWorldState(WorldStates.GuildWeeklyResetTime, week); + Global.GuildMgr.ResetTimes(week == 1); + } + + void UpdateMaxSessionCounters() + { + m_maxActiveSessionCount = Math.Max(m_maxActiveSessionCount, (uint)(m_sessions.Count - m_QueuedPlayer.Count)); + m_maxQueuedSessionCount = Math.Max(m_maxQueuedSessionCount, (uint)m_QueuedPlayer.Count); + } + + public string LoadDBVersion() + { + var DBVersion = "Unknown world database."; + + SQLResult result = DB.World.Query("SELECT db_version, cache_id, hotfix_cache_id FROM version LIMIT 1"); + if (!result.IsEmpty()) + { + DBVersion = result.Read(0); + // will be overwrite by config values if different and non-0 + WorldConfig.SetValue(WorldCfg.ClientCacheVersion, result.Read(1)); + WorldConfig.SetValue(WorldCfg.HotfixCacheVersion, result.Read(2)); + } + + return DBVersion; + } + + void UpdateAreaDependentAuras() + { + foreach (var session in m_sessions.Values) + { + if (session.GetPlayer() != null && session.GetPlayer().IsInWorld) + { + session.GetPlayer().UpdateAreaDependentAuras(session.GetPlayer().GetAreaId()); + session.GetPlayer().UpdateZoneDependentAuras(session.GetPlayer().GetZoneId()); + } + } + } + + void LoadWorldStates() + { + uint oldMSTime = Time.GetMSTime(); + + SQLResult result = DB.Characters.Query("SELECT entry, value FROM worldstates"); + + if (result.IsEmpty()) + { + Log.outInfo(LogFilter.ServerLoading, "Loaded 0 world states. DB table `worldstates` is empty!"); + return; + } + + uint count = 0; + do + { + m_worldstates[result.Read(0)] = result.Read(1); + ++count; + } + while (result.NextRow()); + + Log.outInfo(LogFilter.ServerLoading, "Loaded {0} world states in {1} ms", count, Time.GetMSTimeDiffToNow(oldMSTime)); + } + + public void setWorldState(WorldStates index, ulong value) + { + setWorldState((uint)index, value); + } + + public void setWorldState(uint index, object value) + { + PreparedStatement stmt; + + if (m_worldstates.ContainsKey(index)) + { + if (m_worldstates[index] == Convert.ToUInt32(value)) + return; + + stmt = DB.Characters.GetPreparedStatement(CharStatements.UPD_WORLDSTATE); + stmt.AddValue(0, Convert.ToUInt32(value)); + stmt.AddValue(1, index); + } + else + { + stmt = DB.Characters.GetPreparedStatement(CharStatements.INS_WORLDSTATE); + + stmt.AddValue(0, index); + stmt.AddValue(1, Convert.ToUInt32(value)); + } + DB.Characters.Execute(stmt); + m_worldstates[index] = Convert.ToUInt32(value); + } + + public uint getWorldState(WorldStates index) + { + return getWorldState((uint)index); + } + + public uint getWorldState(uint index) + { + return m_worldstates.LookupByKey(index); + } + + void ProcessQueryCallbacks() + { + _queryProcessor.ProcessReadyQueries(); + } + + public CharacterInfo GetCharacterInfo(ObjectGuid guid) { return _characterInfoStorage.LookupByKey(guid); } + + public void LoadCharacterInfoStorage() + { + Log.outInfo(LogFilter.ServerLoading, "Loading character name data"); + + _characterInfoStorage.Clear(); + + SQLResult result = DB.Characters.Query("SELECT guid, name, account, race, gender, class, level, deleteDate FROM characters"); + if (result.IsEmpty()) + { + Log.outInfo(LogFilter.ServerLoading, "No character name data loaded, empty query"); + return; + } + + uint count = 0; + do + { + AddCharacterInfo(ObjectGuid.Create(HighGuid.Player, result.Read(0)), result.Read(2), result.Read(1), + result.Read(4), result.Read(3), result.Read(5), result.Read(6), result.Read(7) != 0); + ++count; + } while (result.NextRow()); + + Log.outInfo(LogFilter.ServerLoading, "Loaded name data for {0} characters", count); + } + + public void AddCharacterInfo(ObjectGuid guid, uint accountId, string name, byte gender, byte race, byte playerClass, byte level, bool isDeleted) + { + CharacterInfo data = new CharacterInfo(); + data.Name = name; + data.AccountId = accountId; + data.RaceID = (Race)race; + data.Sex = (Gender)gender; + data.ClassID = (Class)playerClass; + data.Level = level; + data.IsDeleted = isDeleted; + _characterInfoStorage[guid] = data; + } + + public void DeleteCharacterInfo(ObjectGuid guid) { _characterInfoStorage.Remove(guid); } + + public void UpdateCharacterInfo(ObjectGuid guid, string name, Gender gender = Gender.None, Race race = Race.None) + { + if (!_characterInfoStorage.ContainsKey(guid)) + return; + + var charData = _characterInfoStorage[guid]; + charData.Name = name; + + if (gender != Gender.None) + charData.Sex = gender; + + if (race != Race.None) + charData.RaceID = race; + + InvalidatePlayer data = new InvalidatePlayer(); + data.Guid = guid; + SendGlobalMessage(data); + } + + public void UpdateCharacterInfoLevel(ObjectGuid guid, byte level) + { + if (!_characterInfoStorage.ContainsKey(guid)) + return; + + _characterInfoStorage[guid].Level = level; + } + + public void UpdateCharacterInfoDeleted(ObjectGuid guid, bool deleted, string name = null) + { + if (!_characterInfoStorage.ContainsKey(guid)) + return; + + _characterInfoStorage[guid].IsDeleted = deleted; + + if (!string.IsNullOrEmpty(name)) + _characterInfoStorage[guid].Name = name; + } + + public void ReloadRBAC() + { + // Passive reload, we mark the data as invalidated and next time a permission is checked it will be reloaded + Log.outInfo(LogFilter.Rbac, "World.ReloadRBAC()"); + foreach (var session in m_sessions.Values) + session.InvalidateRBACData(); + } + + public bool HasCharacterInfo(ObjectGuid guid) { return _characterInfoStorage.ContainsKey(guid); } + + public List GetAllSessions() + { + return m_sessions.Values.ToList(); + } + + public int GetActiveAndQueuedSessionCount() { return m_sessions.Count; } + public int GetActiveSessionCount() { return m_sessions.Count - m_QueuedPlayer.Count; } + public int GetQueuedSessionCount() { return m_QueuedPlayer.Count; } + // Get the maximum number of parallel sessions on the server since last reboot + public uint GetMaxQueuedSessionCount() { return m_maxQueuedSessionCount; } + public uint GetMaxActiveSessionCount() { return m_maxActiveSessionCount; } + + public uint GetPlayerCount() { return m_PlayerCount; } + public uint GetMaxPlayerCount() { return m_MaxPlayerCount; } + + public void IncreasePlayerCount() + { + m_PlayerCount++; + m_MaxPlayerCount = Math.Max(m_MaxPlayerCount, m_PlayerCount); + } + public void DecreasePlayerCount() { m_PlayerCount--; } + + public AccountTypes GetPlayerSecurityLimit() { return m_allowedSecurityLevel; } + + public void SetPlayerAmountLimit(uint limit) { m_playerLimit = limit; } + public uint GetPlayerAmountLimit() { return m_playerLimit; } + + /// Get the path where data (dbc, maps) are stored on disk + public string GetDataPath() { return _dataPath; } + + public void SetDataPath(string path) { _dataPath = path; } + + // When server started? + long GetStartTime() { return m_startTime; } + // What time is it? + public long GetGameTime() { return m_gameTime; } + // Uptime (in secs) + public uint GetUptime() { return (uint)(m_gameTime - m_startTime); } + // Update time + public uint GetUpdateTime() { return m_updateTime; } + + public long GetNextDailyQuestsResetTime() { return m_NextDailyQuestReset; } + public long GetNextWeeklyQuestsResetTime() { return m_NextWeeklyQuestReset; } + long GetNextRandomBGResetTime() { return m_NextRandomBGReset; } + + public uint GetConfigMaxSkillValue() + { + int lvl = WorldConfig.GetIntValue(WorldCfg.MaxPlayerLevel); + return (uint)(lvl > 60 ? 300 + ((lvl - 60) * 75) / 10 : lvl * 5); + } + + public bool IsShuttingDown() { return m_ShutdownTimer > 0; } + public uint GetShutDownTimeLeft() { return m_ShutdownTimer; } + + public void StopNow(ShutdownExitCode exitcode = ShutdownExitCode.Error) { IsStopped = true; m_ExitCode = exitcode; } + + public bool IsPvPRealm() + { + RealmType realmtype = (RealmType)WorldConfig.GetIntValue(WorldCfg.GameType); + return (realmtype == RealmType.PVP + || realmtype == RealmType.RPPVP + || realmtype == RealmType.FFAPVP); + } + public bool IsFFAPvPRealm() + { + return WorldConfig.GetIntValue(WorldCfg.GameType) == (int)RealmType.FFAPVP; + } + + public LocaleConstant GetDefaultDbcLocale() { return m_defaultDbcLocale; } + + public bool LoadRealmInfo() + { + SQLResult result = DB.Login.Query("SELECT id, name, address, localAddress, localSubnetMask, port, icon, flag, timezone, allowedSecurityLevel, population, gamebuild, Region, Battlegroup FROM realmlist WHERE id = {0}", _realm.Id.Realm); + if (result.IsEmpty()) + return false; + + _realm.SetName(result.Read(1)); + _realm.ExternalAddress = System.Net.IPAddress.Parse(result.Read(2)); + _realm.LocalAddress = System.Net.IPAddress.Parse(result.Read(3)); + _realm.LocalSubnetMask = System.Net.IPAddress.Parse(result.Read(4)); + _realm.Port = result.Read(5); + _realm.Type = result.Read(6); + _realm.Flags = (RealmFlags)result.Read(7); + _realm.Timezone = result.Read(8); + _realm.AllowedSecurityLevel = (AccountTypes)result.Read(9); + _realm.PopulationLevel = result.Read(10); + _realm.Id.Region = result.Read(12); + _realm.Id.Site = result.Read(13); + _realm.Build = result.Read(11); + return true; + } + + public Realm GetRealm() { return _realm; } + public RealmHandle GetRealmId() { return _realm.Id; } + + public void RemoveOldCorpses() + { + m_timers[WorldTimers.Corpses].SetCurrent(m_timers[WorldTimers.Corpses].GetInterval()); + } + + public uint GetVirtualRealmAddress() + { + return _realm.Id.GetAddress(); + } + + public float GetMaxVisibleDistanceOnContinents() { return m_MaxVisibleDistanceOnContinents; } + public float GetMaxVisibleDistanceInInstances() { return m_MaxVisibleDistanceInInstances; } + public float GetMaxVisibleDistanceInBGArenas() { return m_MaxVisibleDistanceInBGArenas; } + + public int GetVisibilityNotifyPeriodOnContinents() { return m_visibility_notify_periodOnContinents; } + public int GetVisibilityNotifyPeriodInInstances() { return m_visibility_notify_periodInInstances; } + public int GetVisibilityNotifyPeriodInBGArenas() { return m_visibility_notify_periodInBGArenas; } + + public LocaleConstant GetAvailableDbcLocale(LocaleConstant locale) + { + //if (m_availableDbcLocaleMask & (1 << locale)) + //return locale; + //else + return m_defaultDbcLocale; + } + + public CleaningFlags GetCleaningFlags() { return m_CleaningFlags; } + public void SetCleaningFlags(CleaningFlags flags) { m_CleaningFlags = flags; } + + #region Fields + uint m_ShutdownTimer; + ShutdownMask m_ShutdownMask; + ShutdownExitCode m_ExitCode; + public bool IsStopped; + + Dictionary m_Autobroadcasts = new Dictionary(); + + Dictionary _characterInfoStorage = new Dictionary(); + + CleaningFlags m_CleaningFlags; + + float m_MaxVisibleDistanceOnContinents = SharedConst.DefaultVisibilityDistance; + float m_MaxVisibleDistanceInInstances = SharedConst.DefaultVisibilityInstance; + float m_MaxVisibleDistanceInBGArenas = SharedConst.DefaultVisibilityBGAreans; + + int m_visibility_notify_periodOnContinents = SharedConst.DefaultVisibilityNotifyPeriod; + int m_visibility_notify_periodInInstances = SharedConst.DefaultVisibilityNotifyPeriod; + int m_visibility_notify_periodInBGArenas = SharedConst.DefaultVisibilityNotifyPeriod; + + bool m_isClosed; + + long m_startTime; + long m_gameTime; + Dictionary m_timers = new Dictionary(); + long mail_timer; + long mail_timer_expires; + long blackmarket_timer; + uint m_updateTime, m_updateTimeSum; + uint m_updateTimeCount; + uint m_currentTime; + + Dictionary m_sessions = new Dictionary(); + Dictionary m_disconnects = new Dictionary(); + uint m_maxActiveSessionCount; + uint m_maxQueuedSessionCount; + uint m_PlayerCount; + uint m_MaxPlayerCount; + + Dictionary m_worldstates = new Dictionary(); + uint m_playerLimit; + AccountTypes m_allowedSecurityLevel; + LocaleConstant m_defaultDbcLocale; // from config for one from loaded DBC locales + List m_motd = new List(); + + // scheduled reset times + long m_NextDailyQuestReset; + long m_NextWeeklyQuestReset; + long m_NextMonthlyQuestReset; + long m_NextRandomBGReset; + long m_NextGuildReset; + long m_NextCurrencyReset; + + List m_QueuedPlayer = new List(); + ConcurrentQueue addSessQueue = new ConcurrentQueue(); + + ConcurrentQueue> _linkSocketQueue = new ConcurrentQueue>(); + + QueryCallbackProcessor _queryProcessor = new QueryCallbackProcessor(); + + Realm _realm; + + string _dataPath; + #endregion + } + + /// Timers for different object refresh rates + public enum WorldTimers + { + Auctions, + AuctionsPending, + Weathers, + UpTime, + Corpses, + Events, + CleanDB, + AutoBroadcast, + MailBox, + DeleteChars, + AhBot, + PingDB, + GuildSave, + Blackmarket, + Max + } + + public enum ServerMessageType + { + ShutdownTime = 1, + RestartTime = 2, + String = 3, + ShutdownCancelled = 4, + RestartCancelled = 5, + BgShutdownTime = 6, + BgRestartTime = 7, + InstanceShutdownTime = 8, + InstanceRestartTime = 9, + ContentReady = 10, + TicketServicedSoon = 11, + WaitTimeUnavailable = 12, + TicketWaitTime = 13, + } + + [Flags] + public enum ShutdownMask + { + Restart = 1, + Idle = 2, + Force = 4 + } + + public enum ShutdownExitCode + { + Shutdown = 0, + Error = 1, + Restart = 2, + } + + public class CharacterInfo + { + public string Name; + public uint AccountId; + public Class ClassID; + public Race RaceID; + public Gender Sex; + public byte Level; + public bool IsDeleted; + } + + public class WorldWorldTextBuilder : MessageBuilder + { + public WorldWorldTextBuilder(uint textId, params object[] args) + { + i_textId = textId; + i_args = args; + } + + public override void Invoke(List data_list, LocaleConstant loc_idx) + { + string text = Global.ObjectMgr.GetCypherString(i_textId, loc_idx); + + if (i_args != null) + text = string.Format(text, i_args); + + ChatPkt messageChat = new ChatPkt(); + + var lines = new StringArray(text, "\n"); + for (var i = 0; i < lines.Length; ++i) + { + messageChat.Initialize(ChatMsg.System, Language.Universal, null, null, lines[i]); + data_list.Add(messageChat); + } + } + + uint i_textId; + object[] i_args; + } + + struct Autobroadcast + { + public Autobroadcast(string message, byte weight) + { + Message = message; + Weight = weight; + } + + public string Message; + public byte Weight; + } +} diff --git a/Game/Server/WorldSession.cs b/Game/Server/WorldSession.cs new file mode 100644 index 000000000..959f0623a --- /dev/null +++ b/Game/Server/WorldSession.cs @@ -0,0 +1,1052 @@ +/* + * Copyright (C) 2012-2017 CypherCore + * + * 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, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +using Framework.Constants; +using Framework.Database; +using Game.Accounts; +using Game.BattleGrounds; +using Game.BattlePets; +using Game.Entities; +using Game.Guilds; +using Game.Maps; +using Game.Network; +using Game.Network.Packets; +using System; +using System.Collections.Concurrent; +using System.Collections.Generic; +using System.IO; +using System.Numerics; +using System.Text; +using System.Threading.Tasks; + +namespace Game +{ + public partial class WorldSession : IDisposable + { + public WorldSession(uint id, string name, uint battlenetAccountId, WorldSocket sock, AccountTypes sec, Expansion expansion, long mute_time, string os, LocaleConstant locale, uint recruiter, bool isARecruiter) + { + m_muteTime = mute_time; + AntiDOS = new DosProtection(this); + m_Socket[(int)ConnectionType.Realm] = sock; + _security = sec; + _accountId = id; + _accountName = name; + _battlenetAccountId = battlenetAccountId; + m_expansion = expansion; + _os = os; + m_sessionDbcLocale = Global.WorldMgr.GetAvailableDbcLocale(locale); + m_sessionDbLocaleIndex = locale; + recruiterId = recruiter; + isRecruiter = isARecruiter; + expireTime = 60000; // 1 min after socket loss, session is deleted + m_currentBankerGUID = ObjectGuid.Empty; + _battlePetMgr = new BattlePetMgr(this); + _collectionMgr = new CollectionMgr(this); + + m_Address = sock.GetRemoteIpAddress().ToString(); + ResetTimeOutTime(); + DB.Login.Execute("UPDATE account SET online = 1 WHERE id = {0};", GetAccountId()); // One-time query + } + + public void Dispose() + { + // unload player if not unloaded + if (_player) + LogoutPlayer(true); + + /// - If have unclosed socket, close it + for (byte i = 0; i < 2; ++i) + { + if (m_Socket[i] != null) + m_Socket[i].CloseSocket(); + } + + // empty incoming packet queue + WorldPacket packet; + while (_recvQueue.TryDequeue(out packet)) ; + + DB.Login.Execute("UPDATE account SET online = 0 WHERE id = {0};", GetAccountId()); // One-time query + } + + public void LogoutPlayer(bool save) + { + // finish pending transfers before starting the logout + while (_player && _player.IsBeingTeleportedFar()) + HandleMoveWorldportAck(); + + m_playerLogout = true; + m_playerSave = save; + + if (_player) + { + if (!_player.GetLootGUID().IsEmpty()) + DoLootReleaseAll(); + + // If the player just died before logging out, make him appear as a ghost + //FIXME: logout must be delayed in case lost connection with client in time of combat + if (GetPlayer().GetDeathTimer() != 0) + { + _player.getHostileRefManager().deleteReferences(); + _player.BuildPlayerRepop(); + _player.RepopAtGraveyard(); + } + else if (GetPlayer().HasAuraType(AuraType.SpiritOfRedemption)) + { + // this will kill character by SPELL_AURA_SPIRIT_OF_REDEMPTION + _player.RemoveAurasByType(AuraType.ModShapeshift); + _player.KillPlayer(); + _player.BuildPlayerRepop(); + _player.RepopAtGraveyard(); + } + else if (GetPlayer().HasPendingBind()) + { + _player.RepopAtGraveyard(); + _player.SetPendingBind(0, 0); + } + + //drop a flag if player is carrying it + Battleground bg = GetPlayer().GetBattleground(); + if (bg) + bg.EventPlayerLoggedOut(GetPlayer()); + + // Teleport to home if the player is in an invalid instance + if (!_player.m_InstanceValid && !_player.IsGameMaster()) + _player.TeleportTo(_player.GetHomebind()); + + Global.OutdoorPvPMgr.HandlePlayerLeaveZone(_player, _player.GetZoneId()); + + for (uint i = 0; i < SharedConst.MaxPlayerBGQueues; ++i) + { + BattlegroundQueueTypeId bgQueueTypeId = GetPlayer().GetBattlegroundQueueTypeId(i); + if (bgQueueTypeId != 0) + { + _player.RemoveBattlegroundQueueId(bgQueueTypeId); + BattlegroundQueue queue = Global.BattlegroundMgr.GetBattlegroundQueue(bgQueueTypeId); + queue.RemovePlayer(_player.GetGUID(), true); + } + } + + // Repop at GraveYard or other player far teleport will prevent saving player because of not present map + // Teleport player immediately for correct player save + while (_player.IsBeingTeleportedFar()) + HandleMoveWorldportAck(); + + // If the player is in a guild, update the guild roster and broadcast a logout message to other guild members + Guild guild = Global.GuildMgr.GetGuildById(_player.GetGuildId()); + if (guild) + guild.HandleMemberLogout(this); + + // Remove pet + _player.RemovePet(null, PetSaveMode.AsCurrent, true); + + // Clear whisper whitelist + _player.ClearWhisperWhiteList(); + + // empty buyback items and save the player in the database + // some save parts only correctly work in case player present in map/player_lists (pets, etc) + if (save) + { + int eslot; + for (int j = InventorySlots.BuyBackStart; j < InventorySlots.BuyBackEnd; ++j) + { + eslot = j - InventorySlots.BuyBackStart; + _player.SetGuidValue(PlayerFields.InvSlotHead + (j * 4), ObjectGuid.Empty); + _player.SetUInt32Value(PlayerFields.BuyBackPrice1 + eslot, 0); + _player.SetUInt32Value(PlayerFields.BuyBackTimestamp1 + eslot, 0); + } + _player.SaveToDB(); + } + + // Leave all channels before player delete... + _player.CleanupChannels(); + + // If the player is in a group (or invited), remove him. If the group if then only 1 person, disband the group. + _player.UninviteFromGroup(); + + // remove player from the group if he is: + // a) in group; b) not in raid group; c) logging out normally (not being kicked or disconnected) + if (_player.GetGroup() && !_player.GetGroup().isRaidGroup() && m_Socket[(int)ConnectionType.Realm] != null) + _player.RemoveFromGroup(); + + //! Send update to group and reset stored max enchanting level + if (_player.GetGroup()) + { + _player.GetGroup().SendUpdate(); + _player.GetGroup().ResetMaxEnchantingLevel(); + } + + //! Broadcast a logout message to the player's friends + Global.SocialMgr.SendFriendStatus(_player, FriendsResult.Offline, _player.GetGUID(), true); + _player.RemoveSocial(); + + //! Call script hook before deletion + Global.ScriptMgr.OnPlayerLogout(GetPlayer()); + + //! Remove the player from the world + // the player may not be in the world when logging out + // e.g if he got disconnected during a transfer to another map + // calls to GetMap in this case may cause crashes + GetPlayer().CleanupsBeforeDelete(); + Log.outInfo(LogFilter.Player, "Account: {0} (IP: {1}) Logout Character:[{2}] (GUID: {3}) Level: {4}", + GetAccountId(), GetRemoteAddress(), _player.GetName(), _player.GetGUID().ToString(), _player.getLevel()); + + Map map = GetPlayer().GetMap(); + if (map != null) + map.RemovePlayerFromMap(GetPlayer(), true); + + SetPlayer(null); + + //! Send the 'logout complete' packet to the client + //! Client will respond by sending 3x CMSG_CANCEL_TRADE, which we currently dont handle + LogoutComplete logoutComplete = new LogoutComplete(); + SendPacket(logoutComplete); + + //! Since each account can only have one online character at any given time, ensure all characters for active account are marked as offline + PreparedStatement stmt = DB.Characters.GetPreparedStatement(CharStatements.UPD_ACCOUNT_ONLINE); + stmt.AddValue(0, GetAccountId()); + DB.Characters.Execute(stmt); + } + + if (m_Socket[(int)ConnectionType.Instance] != null) + { + m_Socket[(int)ConnectionType.Instance].CloseSocket(); + m_Socket[(int)ConnectionType.Instance] = null; + } + + m_playerLogout = false; + m_playerSave = false; + m_playerRecentlyLogout = true; + SetLogoutStartTime(0); + } + + public bool Update(uint diff, PacketFilter updater) + { + // Update Timeout timer. + UpdateTimeOutTime(diff); + + // Before we process anything: + // If necessary, kick the player from the character select screen + if (IsConnectionIdle()) + m_Socket[(int)ConnectionType.Realm].CloseSocket(); + + WorldPacket firstDelayedPacket = null; + uint processedPackets = 0; + long currentTime = Time.UnixTime; + + WorldPacket packet; + //Check for any packets they was not recived yet. + while (m_Socket[(int)ConnectionType.Realm] != null && !_recvQueue.IsEmpty && (_recvQueue.TryPeek(out packet, updater) && packet != firstDelayedPacket) && _recvQueue.TryDequeue(out packet)) + { + try + { + var handler = PacketManager.GetHandler((ClientOpcodes)packet.GetOpcode()); + switch (handler.sessionStatus) + { + case SessionStatus.Loggedin: + if (!_player) + { + if (!m_playerRecentlyLogout) + { + if (firstDelayedPacket == null) + firstDelayedPacket = packet; + + QueuePacket(packet); + Log.outDebug(LogFilter.Network, "Re-enqueueing packet with opcode {0} with with status OpcodeStatus.Loggedin. Player is currently not in world yet.", (ClientOpcodes)packet.GetOpcode()); + } + break; + } + else if (_player.IsInWorld && AntiDOS.EvaluateOpcode(packet, currentTime)) + handler.Invoke(this, packet); + break; + case SessionStatus.LoggedinOrRecentlyLogout: + if (!_player && !m_playerRecentlyLogout && !m_playerLogout) + LogUnexpectedOpcode(packet, handler.sessionStatus, "the player has not logged in yet and not recently logout"); + else if (AntiDOS.EvaluateOpcode(packet, currentTime)) + handler.Invoke(this, packet); + break; + case SessionStatus.Transfer: + if (!_player) + LogUnexpectedOpcode(packet, handler.sessionStatus, "the player has not logged in yet"); + else if (_player.IsInWorld) + LogUnexpectedOpcode(packet, handler.sessionStatus, "the player is still in world"); + else if (AntiDOS.EvaluateOpcode(packet, currentTime)) + handler.Invoke(this, packet); + break; + case SessionStatus.Authed: + // prevent cheating with skip queue wait + if (m_inQueue) + { + LogUnexpectedOpcode(packet, handler.sessionStatus, "the player not pass queue yet"); + break; + } + + if ((ClientOpcodes)packet.GetOpcode() == ClientOpcodes.EnumCharacters) + m_playerRecentlyLogout = false; + + if (AntiDOS.EvaluateOpcode(packet, currentTime)) + handler.Invoke(this, packet); + break; + default: + Log.outError(LogFilter.Network, "Received not handled opcode {0} from {1}", (ClientOpcodes)packet.GetOpcode(), GetPlayerInfo()); + break; + } + } + catch (InternalBufferOverflowException ex) + { + Log.outError(LogFilter.Network, "InternalBufferOverflowException: {0} while parsing {1} from {2}.", ex.Message, (ClientOpcodes)packet.GetOpcode(), GetPlayerInfo()); + } + catch (EndOfStreamException) + { + Log.outError(LogFilter.Network, "WorldSession:Update EndOfStreamException occured while parsing a packet (opcode: {0}) from client {1}, accountid={2}. Skipped packet.", + (ClientOpcodes)packet.GetOpcode(), GetRemoteAddress(), GetAccountId()); + } + + processedPackets++; + + if (processedPackets > 100) + break; + } + + if (m_Socket[(int)ConnectionType.Realm] != null && m_Socket[(int)ConnectionType.Realm].IsOpen() && _warden != null) + _warden.Update(); + + ProcessQueryCallbacks(); + + if (updater.ProcessUnsafe()) + { + long currTime = Time.UnixTime; + // If necessary, log the player out + if (ShouldLogOut(currTime) && m_playerLoading.IsEmpty()) + LogoutPlayer(true); + + if (m_Socket[(int)ConnectionType.Realm] != null && GetPlayer() && _warden != null) + _warden.Update(); + + //- Cleanup socket if need + if ((m_Socket[(int)ConnectionType.Realm] != null && !m_Socket[(int)ConnectionType.Realm].IsOpen()) || + (m_Socket[(int)ConnectionType.Instance] != null && !m_Socket[(int)ConnectionType.Instance].IsOpen())) + { + expireTime -= expireTime > diff ? diff : expireTime; + if (expireTime < diff || forceExit || !GetPlayer()) + { + if (m_Socket[(int)ConnectionType.Realm] != null) + { + m_Socket[(int)ConnectionType.Realm].CloseSocket(); + m_Socket[(int)ConnectionType.Realm] = null; + } + if (m_Socket[(int)ConnectionType.Instance] != null) + { + m_Socket[(int)ConnectionType.Instance].CloseSocket(); + m_Socket[(int)ConnectionType.Instance] = null; + } + } + } + + if (m_Socket[(int)ConnectionType.Realm] == null) + return false; //Will remove this session from the world session map + } + + return true; + } + + public void QueuePacket(WorldPacket packet) + { + _recvQueue.Enqueue(packet); + } + + void LogUnexpectedOpcode(WorldPacket packet, SessionStatus status, string reason) + { + Log.outError(LogFilter.Network, "Received unexpected opcode {0} Status: {1} Reason: {2} from {3}", (ClientOpcodes)packet.GetOpcode(), status, reason, GetPlayerInfo()); + } + + public void SendPacket(ServerPacket packet, bool writePacket = true) + { + if (packet == null) + return; + + if (packet.GetOpcode() == ServerOpcodes.Unknown || packet.GetOpcode() == ServerOpcodes.Max) + { + Log.outError(LogFilter.Network, "Prevented sending of UnknownOpcode to {0}", GetPlayerInfo()); + return; + } + + ConnectionType conIdx = packet.GetConnection(); + if (conIdx != ConnectionType.Instance && PacketManager.IsInstanceOnlyOpcode(packet.GetOpcode())) + { + Log.outError(LogFilter.Network, "Prevented sending of instance only opcode {0} with connection type {1} to {2}", packet.GetOpcode(), packet.GetConnection(), GetPlayerInfo()); + return; + } + + if (m_Socket[(int)conIdx] == null) + { + Log.outError(LogFilter.Network, "Prevented sending of {0} to non existent socket {1} to {2}", packet.GetOpcode(), conIdx, GetPlayerInfo()); + return; + } + + m_Socket[(int)conIdx].SendPacket(packet, writePacket); + } + + public void AddInstanceConnection(WorldSocket sock) { m_Socket[(int)ConnectionType.Instance] = sock; } + + public void KickPlayer() + { + for (byte i = 0; i < 2; ++i) + { + if (m_Socket[i] != null) + { + m_Socket[i].CloseSocket(); + forceExit = true; + } + } + } + + public bool IsAddonRegistered(string prefix) + { + if (!_filterAddonMessages) // if we have hit the softcap (64) nothing should be filtered + return true; + + if (_registeredAddonPrefixes.Empty()) + return false; + + return _registeredAddonPrefixes.Contains(prefix); + } + + public void LoadTutorialsData(SQLResult result) + { + if (!result.IsEmpty()) + for (var i = 0; i < SharedConst.MaxAccountTutorialValues; i++) + tutorials[i] = result.Read(i); + + tutorialsChanged = false; + } + + public void SaveTutorialsData(SQLTransaction trans) + { + if (!tutorialsChanged) + return; + + PreparedStatement stmt = DB.Characters.GetPreparedStatement(CharStatements.SEL_HAS_TUTORIALS); + stmt.AddValue(0, GetAccountId()); + bool hasTutorials = !DB.Characters.Query(stmt).IsEmpty(); + // Modify data in DB + stmt = DB.Characters.GetPreparedStatement(hasTutorials ? CharStatements.UPD_TUTORIALS : CharStatements.INS_TUTORIALS); + for (var i = 0; i < SharedConst.MaxAccountTutorialValues; ++i) + stmt.AddValue(i, tutorials[i]); + stmt.AddValue(SharedConst.MaxAccountTutorialValues, GetAccountId()); + trans.Append(stmt); + + tutorialsChanged = false; + } + + public void SendConnectToInstance(ConnectToSerial serial) + { + var instanceAddress = Global.WorldMgr.GetRealm().GetAddressForClient(System.Net.IPAddress.Parse(GetRemoteAddress())); + instanceAddress.Port = WorldConfig.GetIntValue(WorldCfg.PortInstance); + + _instanceConnectKey.AccountId = GetAccountId(); + _instanceConnectKey.connectionType = ConnectionType.Instance; + _instanceConnectKey.Key = RandomHelper.URand(0, 0x7FFFFFFF); + + ConnectTo connectTo = new ConnectTo(); + connectTo.Key = _instanceConnectKey.Raw; + connectTo.Serial = serial; + connectTo.Payload.Where = instanceAddress; + connectTo.Con = (byte)ConnectionType.Instance; + + SendPacket(connectTo); + } + + void LoadAccountData(SQLResult result, AccountDataTypes mask) + { + for (int i = 0; i < (int)AccountDataTypes.Max; ++i) + if (Convert.ToBoolean((int)mask & (1 << i))) + _accountData[i] = new AccountData(); + + if (result.IsEmpty()) + return; + + do + { + int type = result.Read(0); + if (type >= (int)AccountDataTypes.Max) + { + Log.outError(LogFilter.Server, "Table `{0}` have invalid account data type ({1}), ignore.", + mask == AccountDataTypes.GlobalCacheMask ? "account_data" : "character_account_data", type); + continue; + } + + if (((int)mask & (1 << type)) == 0) + { + Log.outError(LogFilter.Server, "Table `{0}` have non appropriate for table account data type ({1}), ignore.", + mask == AccountDataTypes.GlobalCacheMask ? "account_data" : "character_account_data", type); + continue; + } + + _accountData[type].Time = result.Read(1); + var bytes = result.Read(2); + var line = Encoding.Default.GetString(bytes); + _accountData[type].Data = line; + } + while (result.NextRow()); + } + + void SetAccountData(AccountDataTypes type, uint time, string data) + { + if (Convert.ToBoolean((1 << (int)type) & (int)AccountDataTypes.GlobalCacheMask)) + { + PreparedStatement stmt = DB.Characters.GetPreparedStatement(CharStatements.REP_ACCOUNT_DATA); + stmt.AddValue(0, GetAccountId()); + stmt.AddValue(1, type); + stmt.AddValue(2, time); + stmt.AddValue(3, data); + DB.Characters.Execute(stmt); + } + else + { + // _player can be NULL and packet received after logout but m_GUID still store correct guid + if (m_GUIDLow == 0) + return; + + PreparedStatement stmt = DB.Characters.GetPreparedStatement(CharStatements.REP_PLAYER_ACCOUNT_DATA); + stmt.AddValue(0, m_GUIDLow); + stmt.AddValue(1, type); + stmt.AddValue(2, time); + stmt.AddValue(3, data); + DB.Characters.Execute(stmt); + } + + _accountData[(int)type].Time = time; + _accountData[(int)type].Data = data; + } + + public void SendTutorialsData() + { + TutorialFlags packet = new TutorialFlags(); + Array.Copy(tutorials, packet.TutorialData, (int)AccountDataTypes.Max); + SendPacket(packet); + } + + public void SendNotification(CypherStrings str, params object[] args) + { + SendNotification(Global.ObjectMgr.GetCypherString(str), args); + } + + public void SendNotification(string str, params object[] args) + { + string message = string.Format(str, args); + if (!string.IsNullOrEmpty(message)) + { + SendPacket(new PrintNotification(message)); + } + } + + public void SetPlayer(Player pl) + { + _player = pl; + + if (_player) + m_GUIDLow = _player.GetGUID().GetCounter(); + } + + public string GetPlayerName() + { + return _player != null ? _player.GetName() : "Unknown"; + } + + public string GetPlayerInfo() + { + StringBuilder ss = new StringBuilder(); + ss.Append("[Player: "); + if (!m_playerLoading.IsEmpty()) + ss.AppendFormat("Logging in: {0}, ", m_playerLoading.ToString()); + else if (_player) + ss.AppendFormat("{0} {1}, ", _player.GetName(), _player.GetGUID().ToString()); + + ss.AppendFormat("Account: {0}]", GetAccountId()); + return ss.ToString(); + } + + public bool PlayerLoading() { return !m_playerLoading.IsEmpty(); } + public bool PlayerLogout() { return m_playerLogout; } + public bool PlayerLogoutWithSave() { return m_playerLogout && m_playerSave; } + public bool PlayerRecentlyLoggedOut() { return m_playerRecentlyLogout; } + public bool PlayerDisconnected() + { + return !(m_Socket[(int)ConnectionType.Realm] != null && m_Socket[(int)ConnectionType.Realm].IsOpen() && + m_Socket[(int)ConnectionType.Instance] != null && m_Socket[(int)ConnectionType.Instance].IsOpen()); + } + + public AccountTypes GetSecurity() { return _security; } + public uint GetAccountId() { return _accountId; } + public ObjectGuid GetAccountGUID() { return ObjectGuid.Create(HighGuid.WowAccount, GetAccountId()); } + public string GetAccountName() { return _accountName; } + public uint GetBattlenetAccountId() { return _battlenetAccountId; } + public ObjectGuid GetBattlenetAccountGUID() { return ObjectGuid.Create(HighGuid.BNetAccount, GetBattlenetAccountId()); } + + public Player GetPlayer() { return _player; } + + void SetSecurity(AccountTypes security) { _security = security; } + + public string GetRemoteAddress() { return m_Address; } + + public Expansion GetExpansion() { return m_expansion; } + public string GetOS() { return _os; } + public void SetInQueue(bool state) { m_inQueue = state; } + + public bool isLogingOut() { return _logoutTime != 0 || m_playerLogout; } + + public ulong GetConnectToInstanceKey() { return _instanceConnectKey.Raw; } + + void SetLogoutStartTime(long requestTime) + { + _logoutTime = requestTime; + } + + bool ShouldLogOut(long currTime) + { + return (_logoutTime > 0 && currTime >= _logoutTime + 20); + } + + void ProcessQueryCallbacks() + { + _queryProcessor.ProcessReadyQueries(); + + if (_realmAccountLoginCallback != null && _realmAccountLoginCallback.IsCompleted && _accountLoginCallback != null && _accountLoginCallback.IsCompleted) + { + InitializeSessionCallback(_realmAccountLoginCallback.Result, _accountLoginCallback.Result); + _realmAccountLoginCallback = null; + _accountLoginCallback = null; + } + + //! HandlePlayerLoginOpcode + if (_charLoginCallback != null && _charLoginCallback.IsCompleted) + { + HandlePlayerLogin((LoginQueryHolder)_charLoginCallback.Result); + _charLoginCallback = null; + } + } + + void InitWarden(BigInteger k) + { + if (_os == "Win") + { + _warden = new WardenWin(); + _warden.Init(this, k); + } + else if (_os == "Wn64") + { + // Not implemented + } + else if (_os == "Mc64") + { + // Not implemented + } + } + + public void LoadPermissions() + { + uint id = GetAccountId(); + AccountTypes secLevel = GetSecurity(); + + Log.outDebug(LogFilter.Rbac, "WorldSession.LoadPermissions [AccountId: {0}, Name: {1}, realmId: {2}, secLevel: {3}]", + id, _accountName, Global.WorldMgr.GetRealm().Id.Realm, secLevel); + + _RBACData = new RBACData(id, _accountName, (int)Global.WorldMgr.GetRealm().Id.Realm, (byte)secLevel); + _RBACData.LoadFromDB(); + } + + public QueryCallback LoadPermissionsAsync() + { + uint id = GetAccountId(); + AccountTypes secLevel = GetSecurity(); + + Log.outDebug(LogFilter.Rbac, "WorldSession.LoadPermissions [AccountId: {0}, Name: {1}, realmId: {2}, secLevel: {3}]", + id, _accountName, Global.WorldMgr.GetRealm().Id.Realm, secLevel); + + _RBACData = new RBACData(id, _accountName, (int)Global.WorldMgr.GetRealm().Id.Realm, (byte)secLevel); + return _RBACData.LoadFromDBAsync(); + } + + public void InitializeSession() + { + AccountInfoQueryHolderPerRealm realmHolder = new AccountInfoQueryHolderPerRealm(); + realmHolder.Initialize(GetAccountId(), GetBattlenetAccountId()); + + AccountInfoQueryHolder holder = new AccountInfoQueryHolder(); + holder.Initialize(GetAccountId(), GetBattlenetAccountId()); + + _realmAccountLoginCallback = DB.Characters.DelayQueryHolder(realmHolder); + _accountLoginCallback = DB.Login.DelayQueryHolder(holder); + } + + void InitializeSessionCallback(SQLQueryHolder realmHolder, SQLQueryHolder holder) + { + LoadAccountData(realmHolder.GetResult(AccountInfoQueryLoad.GlobalAccountDataIndexPerRealm), AccountDataTypes.GlobalCacheMask); + LoadTutorialsData(realmHolder.GetResult(AccountInfoQueryLoad.TutorialsIndexPerRealm)); + _collectionMgr.LoadAccountToys(holder.GetResult(AccountInfoQueryLoad.GlobalAccountToys)); + _collectionMgr.LoadAccountHeirlooms(holder.GetResult(AccountInfoQueryLoad.GlobalAccountHeirlooms)); + _collectionMgr.LoadAccountMounts(holder.GetResult(AccountInfoQueryLoad.Mounts)); + _collectionMgr.LoadAccountItemAppearances(holder.GetResult(AccountInfoQueryLoad.ItemAppearances), holder.GetResult(AccountInfoQueryLoad.ItemFavoriteAppearances)); + + if (!m_inQueue) + SendAuthResponse(BattlenetRpcErrorCode.Ok, false); + else + SendAuthWaitQue(0); + + SetInQueue(false); + ResetTimeOutTime(); + + SendSetTimeZoneInformation(); + SendFeatureSystemStatusGlueScreen(); + SendClientCacheVersion(WorldConfig.GetUIntValue(WorldCfg.ClientCacheVersion)); + SendHotfixList(WorldConfig.GetIntValue(WorldCfg.HotfixCacheVersion)); + SendTutorialsData(); + + SQLResult result = holder.GetResult(AccountInfoQueryLoad.GlobalRealmCharacterCounts); + if (!result.IsEmpty()) + { + do + { + _realmCharacterCounts[new RealmHandle(result.Read(3), result.Read(4), result.Read(2)).GetAddress()] = result.Read(1); + + } while (result.NextRow()); + } + + SetSessionState bnetConnected = new SetSessionState(); + bnetConnected.State = 1; + SendPacket(bnetConnected); + + _battlePetMgr.LoadFromDB(holder.GetResult(AccountInfoQueryLoad.BattlePets), holder.GetResult(AccountInfoQueryLoad.BattlePetSlot)); + + realmHolder = null; + holder = null; + } + + public RBACData GetRBACData() + { + return _RBACData; + } + + public bool HasPermission(RBACPermissions permission) + { + if (_RBACData == null) + LoadPermissions(); + + bool hasPermission = _RBACData.HasPermission(permission); + Log.outDebug(LogFilter.Rbac, "WorldSession:HasPermission [AccountId: {0}, Name: {1}, realmId: {2}]", + _RBACData.GetId(), _RBACData.GetName(), Global.WorldMgr.GetRealm().Id.Realm); + + return hasPermission; + } + + public void InvalidateRBACData() + { + Log.outDebug(LogFilter.Rbac, "WorldSession:Invalidaterbac:RBACData [AccountId: {0}, Name: {1}, realmId: {2}]", + _RBACData.GetId(), _RBACData.GetName(), Global.WorldMgr.GetRealm().Id.Realm); + _RBACData = null; + } + + AccountData GetAccountData(AccountDataTypes type) { return _accountData[(int)type]; } + + uint GetTutorialInt(byte index) { return tutorials[index]; } + + void SetTutorialInt(byte index, uint value) + { + if (tutorials[index] != value) + { + tutorials[index] = value; + tutorialsChanged = true; + } + } + + public LocaleConstant GetSessionDbcLocale() { return m_sessionDbcLocale; } + public LocaleConstant GetSessionDbLocaleIndex() { return m_sessionDbLocaleIndex; } + + public uint GetLatency() { return m_latency; } + public void SetLatency(uint latency) { m_latency = latency; } + public void ResetClientTimeDelay() { m_clientTimeDelay = 0; } + void UpdateTimeOutTime(uint diff) + { + if (diff > m_timeOutTime) + m_timeOutTime = 0; + else + m_timeOutTime -= diff; + } + public void ResetTimeOutTime() { m_timeOutTime = WorldConfig.GetIntValue(WorldCfg.SocketTimeouttime); } + bool IsConnectionIdle() { return (m_timeOutTime <= 0 && !m_inQueue); } + + public uint GetRecruiterId() { return recruiterId; } + public bool IsARecruiter() { return isRecruiter; } + + // Battle Pets + public BattlePetMgr GetBattlePetMgr() { return _battlePetMgr; } + public CollectionMgr GetCollectionMgr() { return _collectionMgr; } + + void ClearRedirectFlag(SessionFlags flag) { m_flags &= ~flag; } + public bool WasRedirected() { return m_flags.HasAnyFlag(SessionFlags.FromRedirect); } + bool HasRedirected() { return m_flags.HasAnyFlag(SessionFlags.HasRedirected); } + + // Battlenet + public Array GetRealmListSecret() { return _realmListSecret; } + void SetRealmListSecret(Array secret) { _realmListSecret = secret; } + public Dictionary GetRealmCharacterCounts() { return _realmCharacterCounts; } + + public static implicit operator bool(WorldSession session) + { + return session != null; + } + + #region Fields + List _legitCharacters = new List(); + ulong m_GUIDLow; + Player _player; + WorldSocket[] m_Socket = new WorldSocket[(int)ConnectionType.Max]; + string m_Address; + + AccountTypes _security; + uint _accountId; + string _accountName; + uint _battlenetAccountId; + Expansion m_expansion; + string _os; + + uint expireTime; + bool forceExit; + + DosProtection AntiDOS; + Warden _warden; // Remains NULL if Warden system is not enabled by config + + long _logoutTime; + bool m_inQueue; + SessionFlags m_flags; + ObjectGuid m_playerLoading; // code processed in LoginPlayer + bool m_playerLogout; // code processed in LogoutPlayer + bool m_playerRecentlyLogout; + bool m_playerSave; + LocaleConstant m_sessionDbcLocale; + LocaleConstant m_sessionDbLocaleIndex; + uint m_latency; + uint m_clientTimeDelay; + AccountData[] _accountData = new AccountData[(int)AccountDataTypes.Max]; + uint[] tutorials = new uint[SharedConst.MaxAccountTutorialValues]; + bool tutorialsChanged; + + Array _realmListSecret = new Array(32); + Dictionary _realmCharacterCounts = new Dictionary(); + Dictionary> _battlenetResponseCallbacks = new Dictionary>(); + uint _battlenetRequestToken; + + List _registeredAddonPrefixes = new List(); + bool _filterAddonMessages; + uint recruiterId; + bool isRecruiter; + + public long m_muteTime; + long m_timeOutTime; + + ConcurrentQueue _recvQueue = new ConcurrentQueue(); + RBACData _RBACData; + + ObjectGuid m_currentBankerGUID; + + CollectionMgr _collectionMgr; + + ConnectToKey _instanceConnectKey; + + BattlePetMgr _battlePetMgr; + + Task> _realmAccountLoginCallback; + Task> _accountLoginCallback; + Task> _charLoginCallback; + + QueryCallbackProcessor _queryProcessor = new QueryCallbackProcessor(); + #endregion + } + + public struct ConnectToKey + { + public ulong Raw + { + get { return ((ulong)AccountId) | ((ulong)connectionType << 32) | (Key << 33); } + set + { + AccountId = (uint)(value & ((1 << 33) - 1)); + connectionType = (ConnectionType)(uint)((value >> 32) & ((1 << 1) - 1)); + Key = (uint)(value >> 33) & ((1 << 30) - 1); + } + } + + public uint AccountId; + public ConnectionType connectionType; + public ulong Key; + } + + public class DosProtection + { + public DosProtection(WorldSession s) + { + Session = s; + _policy = (Policy)WorldConfig.GetIntValue(WorldCfg.PacketSpoofPolicy); + } + //todo fix me + public bool EvaluateOpcode(WorldPacket packet, long time) + { + uint maxPacketCounterAllowed = 0;// GetMaxPacketCounterAllowed(p.GetOpcode()); + + // Return true if there no limit for the opcode + if (maxPacketCounterAllowed == 0) + return true; + + if (!_PacketThrottlingMap.ContainsKey(packet.GetOpcode())) + _PacketThrottlingMap[packet.GetOpcode()] = new PacketCounter(); + + PacketCounter packetCounter = _PacketThrottlingMap[packet.GetOpcode()]; + if (packetCounter.lastReceiveTime != time) + { + packetCounter.lastReceiveTime = time; + packetCounter.amountCounter = 0; + } + + // Check if player is flooding some packets + if (++packetCounter.amountCounter <= maxPacketCounterAllowed) + return true; + + Log.outWarn(LogFilter.Network, "AntiDOS: Account {0}, IP: {1}, Ping: {2}, Character: {3}, flooding packet (opc: {4} (0x{4}), count: {5})", + Session.GetAccountId(), Session.GetRemoteAddress(), Session.GetLatency(), Session.GetPlayerName(), packet.GetOpcode(), packetCounter.amountCounter); + + switch (_policy) + { + case Policy.Log: + return true; + case Policy.Kick: + Log.outInfo(LogFilter.Network, "AntiDOS: Player kicked!"); + return false; + case Policy.Ban: + BanMode bm = (BanMode)WorldConfig.GetIntValue(WorldCfg.PacketSpoofBanmode); + uint duration = WorldConfig.GetUIntValue(WorldCfg.PacketSpoofBanduration); // in seconds + string nameOrIp = ""; + switch (bm) + { + case BanMode.Character: // not supported, ban account + case BanMode.Account: + Global.AccountMgr.GetName(Session.GetAccountId(), out nameOrIp); + break; + case BanMode.IP: + nameOrIp = Session.GetRemoteAddress(); + break; + } + Global.WorldMgr.BanAccount(bm, nameOrIp, duration, "DOS (Packet Flooding/Spoofing", "Server: AutoDOS"); + Log.outInfo(LogFilter.Network, "AntiDOS: Player automatically banned for {0} seconds.", duration); + return false; + } + return true; + } + + Policy _policy; + WorldSession Session; + Dictionary _PacketThrottlingMap = new Dictionary(); + + enum Policy + { + Log, + Kick, + Ban, + } + } + + struct PacketCounter + { + public long lastReceiveTime; + public uint amountCounter; + } + + public struct AccountData + { + public long Time; + public string Data; + } + + class AccountInfoQueryHolderPerRealm : SQLQueryHolder + { + public AccountInfoQueryHolderPerRealm() { } + + public void Initialize(uint accountId, uint battlenetAccountId) + { + PreparedStatement stmt = DB.Characters.GetPreparedStatement(CharStatements.SEL_ACCOUNT_DATA); + stmt.AddValue(0, accountId); + SetQuery(AccountInfoQueryLoad.GlobalAccountDataIndexPerRealm, stmt); + + stmt = DB.Characters.GetPreparedStatement(CharStatements.SEL_TUTORIALS); + stmt.AddValue(0, accountId); + SetQuery(AccountInfoQueryLoad.TutorialsIndexPerRealm, stmt); + } + } + + class AccountInfoQueryHolder : SQLQueryHolder + { + public AccountInfoQueryHolder() { } + + public void Initialize(uint accountId, uint battlenetAccountId) + { + PreparedStatement stmt = DB.Login.GetPreparedStatement(LoginStatements.SEL_ACCOUNT_TOYS); + stmt.AddValue(0, battlenetAccountId); + SetQuery(AccountInfoQueryLoad.GlobalAccountToys, stmt); + + stmt = DB.Login.GetPreparedStatement(LoginStatements.SEL_BATTLE_PETS); + stmt.AddValue(0, battlenetAccountId); + SetQuery(AccountInfoQueryLoad.BattlePets, stmt); + + stmt = DB.Login.GetPreparedStatement(LoginStatements.SEL_BATTLE_PET_SLOTS); + stmt.AddValue(0, battlenetAccountId); + SetQuery(AccountInfoQueryLoad.BattlePetSlot, stmt); + + stmt = DB.Login.GetPreparedStatement(LoginStatements.SEL_ACCOUNT_HEIRLOOMS); + stmt.AddValue(0, battlenetAccountId); + SetQuery(AccountInfoQueryLoad.GlobalAccountHeirlooms, stmt); + + stmt = DB.Login.GetPreparedStatement(LoginStatements.SEL_ACCOUNT_MOUNTS); + stmt.AddValue(0, battlenetAccountId); + SetQuery(AccountInfoQueryLoad.Mounts, stmt); + + stmt = DB.Login.GetPreparedStatement(LoginStatements.SEL_BNET_CHARACTER_COUNTS_BY_ACCOUNT_ID); + stmt.AddValue(0, accountId); + SetQuery(AccountInfoQueryLoad.GlobalRealmCharacterCounts, stmt); + + stmt = DB.Login.GetPreparedStatement(LoginStatements.SEL_BNET_ITEM_APPEARANCES); + stmt.AddValue(0, battlenetAccountId); + SetQuery(AccountInfoQueryLoad.ItemAppearances, stmt); + + stmt = DB.Login.GetPreparedStatement(LoginStatements.SEL_BNET_ITEM_FAVORITE_APPEARANCES); + stmt.AddValue(0, battlenetAccountId); + SetQuery(AccountInfoQueryLoad.ItemFavoriteAppearances, stmt); + } + } + + enum AccountInfoQueryLoad + { + GlobalAccountToys, + BattlePets, + BattlePetSlot, + GlobalAccountHeirlooms, + GlobalRealmCharacterCounts, + Mounts, + ItemAppearances, + ItemFavoriteAppearances, + GlobalAccountDataIndexPerRealm, + TutorialsIndexPerRealm + } +} diff --git a/Game/Services/GameUtilitiesService.cs b/Game/Services/GameUtilitiesService.cs new file mode 100644 index 000000000..64effecda --- /dev/null +++ b/Game/Services/GameUtilitiesService.cs @@ -0,0 +1,164 @@ +/* + * Copyright (C) 2012-2017 CypherCore + * + * 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, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +using Bgs.Protocol.GameUtilities.V1; +using Framework.Constants; +using Framework.Rest; +using Framework.Serialization; +using Game.Services; +using Google.Protobuf; +using System.Collections.Generic; + +namespace Game +{ + public partial class WorldSession + { + [BnetService(NameHash.GameUtilitiesService, 1)] + BattlenetRpcErrorCode HandleProcessClientRequest(ClientRequest request) + { + Bgs.Protocol.Attribute command = null; + Dictionary Params = new Dictionary(); + + for (int i = 0; i < request.Attribute.Count; ++i) + { + Bgs.Protocol.Attribute attr = request.Attribute[i]; + Params[attr.Name] = attr.Value; + if (attr.Name.Contains("Command_")) + command = attr; + } + + if (command == null) + { + Log.outError(LogFilter.SessionRpc, "{0} sent ClientRequest with no command.", GetPlayerInfo()); + return BattlenetRpcErrorCode.RpcMalformedRequest; + } + ClientResponse response = new ClientResponse(); + + var status = BattlenetRpcErrorCode.RpcNotImplemented; + if (command.Name == "Command_RealmListRequest_v1_b9") + status= HandleRealmListRequest(Params, response); + else if (command.Name == "Command_RealmJoinRequest_v1_b9") + status= HandleRealmJoinRequest(Params, response); + + if (status == 0) + SendBattlenetResponse((uint)NameHash.GameUtilitiesService, 1, response); + + return status; + } + + [BnetService(NameHash.GameUtilitiesService, 2)] + BattlenetRpcErrorCode HandlePresenceChannelCreated(PresenceChannelCreatedRequest request) + { + Log.outError(LogFilter.ServiceProtobuf, "{0} Client tried to call not implemented method GameUtilitiesService.PresenceChannelCreated: {1}", GetPlayerInfo(), request.ToString()); + return BattlenetRpcErrorCode.RpcNotImplemented; + } + + [BnetService(NameHash.GameUtilitiesService, 3)] + BattlenetRpcErrorCode HandleGetPlayerVariables(GetPlayerVariablesRequest request) + { + Log.outError(LogFilter.ServiceProtobuf, "{0} Client tried to call not implemented method GameUtilitiesService.GetPlayerVariables: {1}", GetPlayerInfo(), request.ToString()); + return BattlenetRpcErrorCode.RpcNotImplemented; + } + + [BnetService(NameHash.GameUtilitiesService, 6)] + BattlenetRpcErrorCode HandleProcessServerRequest(ServerRequest request) + { + Log.outError(LogFilter.ServiceProtobuf, "{0} Client tried to call not implemented method GameUtilitiesService.ProcessServerRequest: {1}", GetPlayerInfo(), request.ToString()); + return BattlenetRpcErrorCode.RpcNotImplemented; + } + + [BnetService(NameHash.GameUtilitiesService, 7)] + BattlenetRpcErrorCode HandleOnGameAccountOnline(GameAccountOnlineNotification request) + { + Log.outError(LogFilter.ServiceProtobuf, "{0} Client tried to call not implemented method GameUtilitiesService.OnGameAccountOnline: {1}", GetPlayerInfo(), request.ToString()); + return BattlenetRpcErrorCode.RpcNotImplemented; + } + + [BnetService(NameHash.GameUtilitiesService, 8)] + BattlenetRpcErrorCode HandleOnGameAccountOffline(GameAccountOfflineNotification request) + { + Log.outError(LogFilter.ServiceProtobuf, "{0} Client tried to call not implemented method GameUtilitiesService.OnGameAccountOffline: {1}", GetPlayerInfo(), request.ToString()); + return BattlenetRpcErrorCode.RpcNotImplemented; + } + + [BnetService(NameHash.GameUtilitiesService, 9)] + BattlenetRpcErrorCode HandleGetAchievementsFile(GetAchievementsFileRequest request) + { + Log.outError(LogFilter.ServiceProtobuf, "{0} Client tried to call not implemented method GameUtilitiesService.GetAchievementsFile: {1}", GetPlayerInfo(), request.ToString()); + return BattlenetRpcErrorCode.RpcNotImplemented; + } + + [BnetService(NameHash.GameUtilitiesService, 10)] + BattlenetRpcErrorCode HandleGetAllValuesForAttribute(GetAllValuesForAttributeRequest request) + { + if (request.AttributeKey == "Command_RealmListRequest_v1_b9") + { + GetAllValuesForAttributeResponse response = new GetAllValuesForAttributeResponse(); + Global.RealmMgr.WriteSubRegions(response); + SendBattlenetResponse((uint)NameHash.GameUtilitiesService, 10, response); + return BattlenetRpcErrorCode.Ok; + } + + return BattlenetRpcErrorCode.RpcNotImplemented; + } + + BattlenetRpcErrorCode HandleRealmListRequest(Dictionary Params, ClientResponse response) + { + string subRegionId = ""; + var subRegion = Params.LookupByKey("Command_RealmListRequest_v1_b9"); + if (subRegion != null) + subRegionId = subRegion.StringValue; + + var compressed = Global.RealmMgr.GetRealmList(Global.WorldMgr.GetRealm().Build, subRegionId); + if (compressed.Empty()) + return BattlenetRpcErrorCode.UtilServerFailedToSerializeResponse; + + Bgs.Protocol.Attribute attribute = new Bgs.Protocol.Attribute(); + attribute.Name = "Param_RealmList"; + attribute.Value = new Bgs.Protocol.Variant(); + attribute.Value.BlobValue = ByteString.CopyFrom(compressed); + response.Attribute.Add(attribute); + + var realmCharacterCounts = new RealmCharacterCountList(); + foreach (var characterCount in GetRealmCharacterCounts()) + { + RealmCharacterCountEntry countEntry = new RealmCharacterCountEntry(); + countEntry.WowRealmAddress = (int)characterCount.Key; + countEntry.Count = characterCount.Value; + realmCharacterCounts.Counts.Add(countEntry); + } + compressed = Json.Deflate("JSONRealmCharacterCountList", realmCharacterCounts); + + attribute = new Bgs.Protocol.Attribute(); + attribute.Name = "Param_CharacterCountList"; + attribute.Value = new Bgs.Protocol.Variant(); + attribute.Value.BlobValue = ByteString.CopyFrom(compressed); + response.Attribute.Add(attribute); + return BattlenetRpcErrorCode.Ok; + } + + BattlenetRpcErrorCode HandleRealmJoinRequest(Dictionary Params, ClientResponse response) + { + var realmAddress = Params.LookupByKey("Param_RealmAddress"); + if (realmAddress != null) + return Global.RealmMgr.JoinRealm((uint)realmAddress.UintValue, Global.WorldMgr.GetRealm().Build, System.Net.IPAddress.Parse(GetRemoteAddress()), GetRealmListSecret(), + GetSessionDbcLocale(), GetOS(), GetAccountName(), response); + + return BattlenetRpcErrorCode.Ok; + } + } +} diff --git a/Game/Services/ServiceManager.cs b/Game/Services/ServiceManager.cs new file mode 100644 index 000000000..8bfcc9813 --- /dev/null +++ b/Game/Services/ServiceManager.cs @@ -0,0 +1,116 @@ +/* + * Copyright (C) 2012-2017 CypherCore + * + * 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, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +using Framework.Constants; +using Google.Protobuf; +using System; +using System.Collections.Concurrent; +using System.Collections.Generic; +using System.Reflection; + +namespace Game.Services +{ + class ServiceManager + { + public static void Initialize() + { + Assembly currentAsm = Assembly.GetExecutingAssembly(); + foreach (var type in currentAsm.GetTypes()) + { + foreach (var methodInfo in type.GetMethods(BindingFlags.Instance | BindingFlags.NonPublic)) + { + foreach (var msgAttr in methodInfo.GetCustomAttributes()) + { + if (msgAttr == null) + continue; + + if (_clientPacketTable.ContainsKey(msgAttr.Hash) && _clientPacketTable[msgAttr.Hash].ContainsKey(msgAttr.MethodId)) + { + //Log.outError(LogFilter.Network, "Tried to override OpcodeHandler of {0} with {1} (Opcode {2})", _clientPacketTable[msgAttr.Opcode].ToString(), methodInfo.Name, msgAttr.Opcode); + continue; + } + + var parameters = methodInfo.GetParameters(); + if (parameters.Length == 0) + { + Log.outError(LogFilter.Network, "Method: {0} Has no paramters", methodInfo.Name); + continue; + } + + if (!_clientPacketTable.ContainsKey(msgAttr.Hash)) + _clientPacketTable[msgAttr.Hash] = new Dictionary(); + + _clientPacketTable[msgAttr.Hash][msgAttr.MethodId] = new ServiceHandler(methodInfo, parameters[0].ParameterType); + } + } + } + } + + public static ServiceHandler GetHandler(NameHash hash, uint methodId) + { + if (!_clientPacketTable.ContainsKey(hash)) + return null; + + return _clientPacketTable[hash].LookupByKey(methodId); + } + + static ConcurrentDictionary> _clientPacketTable = new ConcurrentDictionary>(); + } + + public class ServiceHandler + { + public ServiceHandler(MethodInfo info, Type type) + { + methodCaller = (Func)GetType().GetMethod("CreateDelegate", BindingFlags.Static | BindingFlags.NonPublic).MakeGenericMethod(type).Invoke(null, new object[] { info }); + packetType = type; + } + + public BattlenetRpcErrorCode Invoke(WorldSession session, CodedInputStream stream) + { + var request = (IMessage)Activator.CreateInstance(packetType); + request.MergeFrom(stream); + + return methodCaller(session, request); + } + + static Func CreateDelegate(MethodInfo method) where P1 : IMessage + { + // create first delegate. It is not fine because its + // signature contains unknown types T and P1 + Func d = (Func)method.CreateDelegate(typeof(Func)); + // create another delegate having necessary signature. + // It encapsulates first delegate with a closure + return delegate (WorldSession target, IMessage p) { return d(target, (P1)p); }; + } + + Func methodCaller; + Type packetType; + } + + [AttributeUsage(AttributeTargets.Method)] + public class BnetServiceAttribute : Attribute + { + public BnetServiceAttribute(NameHash hash, uint methodId) + { + Hash = hash; + MethodId = methodId; + } + + public NameHash Hash { get; } + public uint MethodId { get; } + } +} diff --git a/Game/Spells/Auras/Aura.cs b/Game/Spells/Auras/Aura.cs new file mode 100644 index 000000000..16c66f464 --- /dev/null +++ b/Game/Spells/Auras/Aura.cs @@ -0,0 +1,2704 @@ +/* + * Copyright (C) 2012-2017 CypherCore + * + * 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, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +using Framework.Constants; +using Framework.Dynamic; +using Game.DataStorage; +using Game.Entities; +using Game.Maps; +using Game.Network.Packets; +using Game.Scripting; +using System; +using System.Collections.Generic; +using System.Diagnostics.Contracts; +using System.Linq; + +namespace Game.Spells +{ + public enum AuraObjectType + { + Unit, + DynObj + } + public enum AuraRemoveMode + { + None = 0, + Default = 1, // scripted remove, remove by stack with aura with different ids and sc aura remove + Cancel, + EnemySpell, // dispel and absorb aura destroy + Expire, // aura duration has ended + ByDeath + } + public enum AuraFlags + { + None = 0x00, + NoCaster = 0x01, + Positive = 0x02, + Duration = 0x04, + Scalable = 0x08, + Negative = 0x10, + Unk20 = 0x20 + } + + public class AuraApplication + { + Unit _target; + Aura _base; + AuraRemoveMode _removeMode; // Store info for know remove aura reason + byte _slot; // Aura slot on unit + AuraFlags _flags; // Aura info flag + uint _effectsToApply; // Used only at spell hit to determine which effect should be applied + bool _needClientUpdate; + uint _effectMask; + + public AuraApplication(Unit target, Unit caster, Aura aura, uint effMask) + { + _target = target; + _base = aura; + _removeMode = AuraRemoveMode.None; + _slot = SpellConst.MaxAuras; + _flags = AuraFlags.None; + _effectsToApply = effMask; + _needClientUpdate = false; + + Contract.Assert(GetTarget() != null && GetBase() != null); + + // Try find slot for aura + byte slot = 0; + // Lookup for auras already applied from spell + foreach (AuraApplication visibleAura in GetTarget().GetVisibleAuras()) + { + if (slot < visibleAura.GetSlot()) + break; + + ++slot; + } + + // Register Visible Aura + if (slot < SpellConst.MaxAuras) + { + _slot = slot; + GetTarget().SetVisibleAura(this); + _needClientUpdate = true; + Log.outDebug(LogFilter.Spells, "Aura: {0} Effect: {1} put to unit visible auras slot: {2}", GetBase().GetId(), GetEffectMask(), slot); + } + else + Log.outError(LogFilter.Spells, "Aura: {0} Effect: {1} could not find empty unit visible slot", GetBase().GetId(), GetEffectMask()); + + + _InitFlags(caster, effMask); + } + + public void _Remove() + { + // update for out of range group members + if (GetSlot() < SpellConst.MaxAuras) + { + GetTarget().RemoveVisibleAura(this); + ClientUpdate(true); + } + } + + void _InitFlags(Unit caster, uint effMask) + { + // mark as selfcasted if needed + _flags |= (GetBase().GetCasterGUID() == GetTarget().GetGUID()) ? AuraFlags.NoCaster : AuraFlags.None; + + // aura is casted by self or an enemy + // one negative effect and we know aura is negative + if (IsSelfcasted() || caster == null || !caster.IsFriendlyTo(GetTarget())) + { + bool negativeFound = false; + foreach (SpellEffectInfo effect in GetBase().GetSpellEffectInfos()) + { + if (effect != null && (Convert.ToBoolean((1 << (int)effect.EffectIndex) & effMask) && !GetBase().GetSpellInfo().IsPositiveEffect(effect.EffectIndex))) + { + negativeFound = true; + break; + } + } + _flags |= negativeFound ? AuraFlags.Negative : AuraFlags.Positive; + } + // aura is casted by friend + // one positive effect and we know aura is positive + else + { + bool positiveFound = false; + foreach (SpellEffectInfo effect in GetBase().GetSpellEffectInfos()) + { + if (effect != null && (Convert.ToBoolean((1 << (int)effect.EffectIndex) & effMask) && GetBase().GetSpellInfo().IsPositiveEffect(effect.EffectIndex))) + { + positiveFound = true; + break; + } + } + _flags |= positiveFound ? AuraFlags.Positive : AuraFlags.Negative; + } + + if (GetBase().GetSpellInfo().HasAttribute(SpellAttr8.AuraSendAmount) || + GetBase().HasEffectType(AuraType.ModMaxCharges) || + GetBase().HasEffectType(AuraType.ChargeRecoveryMod) || + GetBase().HasEffectType(AuraType.ChargeRecoveryMultiplier)) + _flags |= AuraFlags.Scalable; + } + + public void _HandleEffect(uint effIndex, bool apply) + { + AuraEffect aurEff = GetBase().GetEffect(effIndex); + if (aurEff == null) + { + Log.outError(LogFilter.Spells, "Aura {0} has no effect at effectIndex {1} but _HandleEffect was called", GetBase().GetSpellInfo().Id, effIndex); + return; + } + Contract.Assert(aurEff != null); + Contract.Assert(HasEffect(effIndex) == (!apply)); + Contract.Assert(Convert.ToBoolean((1 << (int)effIndex) & _effectsToApply)); + Log.outDebug(LogFilter.Spells, "AuraApplication._HandleEffect: {0}, apply: {1}: amount: {2}", aurEff.GetAuraType(), apply, aurEff.GetAmount()); + + if (apply) + { + Contract.Assert(!Convert.ToBoolean(_effectMask & (1 << (int)effIndex))); + _effectMask |= (uint)(1 << (int)effIndex); + aurEff.HandleEffect(this, AuraEffectHandleModes.Real, true); + } + else + { + Contract.Assert(Convert.ToBoolean(_effectMask & (1 << (int)effIndex))); + _effectMask &= ~(uint)(1 << (int)effIndex); + aurEff.HandleEffect(this, AuraEffectHandleModes.Real, false); + } + SetNeedClientUpdate(); + } + + public void SetNeedClientUpdate() + { + if (_needClientUpdate || GetRemoveMode() != AuraRemoveMode.None) + return; + + _needClientUpdate = true; + _target.SetVisibleAuraUpdate(this); + } + + public void BuildUpdatePacket(ref AuraInfo auraInfo, bool remove) + { + Contract.Assert(_target.HasVisibleAura(this) != remove); + + auraInfo.Slot = GetSlot(); + if (remove) + return; + + auraInfo.AuraData.HasValue = true; + + Aura aura = GetBase(); + + AuraDataInfo auraData = auraInfo.AuraData.Value; + auraData.CastID = aura.GetCastGUID(); + auraData.SpellID = (int)aura.GetId(); + auraData.SpellXSpellVisualID = (int)aura.GetSpellXSpellVisualId(); + auraData.Flags = GetFlags(); + if (aura.GetMaxDuration() > 0 && !aura.GetSpellInfo().HasAttribute(SpellAttr5.HideDuration)) + auraData.Flags |= AuraFlags.Duration; + + auraData.ActiveFlags = GetEffectMask(); + if (!aura.GetSpellInfo().HasAttribute(SpellAttr11.ScalesWithItemLevel)) + auraData.CastLevel = aura.GetCasterLevel(); + else + auraData.CastLevel = (ushort)aura.GetCastItemLevel(); + + // send stack amount for aura which could be stacked (never 0 - causes incorrect display) or charges + // stack amount has priority over charges (checked on retail with spell 50262) + auraData.Applications = aura.GetSpellInfo().StackAmount != 0 ? aura.GetStackAmount() : aura.GetCharges(); + if (!auraData.Flags.HasAnyFlag(AuraFlags.NoCaster)) + auraData.CastUnit.Set(aura.GetCasterGUID()); + + if (auraData.Flags.HasAnyFlag(AuraFlags.Duration)) + { + auraData.Duration.Set(aura.GetMaxDuration()); + auraData.Remaining.Set(aura.GetDuration()); + } + + if (auraData.Flags.HasAnyFlag(AuraFlags.Scalable)) + { + auraData.Points = new float[GetBase().GetAuraEffects().Length]; + foreach (AuraEffect effect in GetBase().GetAuraEffects()) + if (effect != null && HasEffect(effect.GetEffIndex())) // Not all of aura's effects have to be applied on every target + auraData.Points[effect.GetEffIndex()] = effect.GetAmount(); + } + } + + public void ClientUpdate(bool remove = false) + { + _needClientUpdate = false; + + AuraUpdate update = new AuraUpdate(); + update.UpdateAll = false; + update.UnitGUID = GetTarget().GetGUID(); + + AuraInfo auraInfo = new AuraInfo(); + BuildUpdatePacket(ref auraInfo, remove); + update.Auras.Add(auraInfo); + + _target.SendMessageToSet(update, true); + } + + public Unit GetTarget() { return _target; } + public Aura GetBase() { return _base; } + + public byte GetSlot() { return _slot; } + public AuraFlags GetFlags() { return _flags; } + public uint GetEffectMask() { return _effectMask; } + public bool HasEffect(uint effect) + { + Contract.Assert(effect < SpellConst.MaxEffects); + return Convert.ToBoolean(_effectMask & (1 << (int)effect)); + } + public bool IsPositive() { return _flags.HasAnyFlag(AuraFlags.Positive); } + bool IsSelfcasted() { return !_flags.HasAnyFlag(AuraFlags.NoCaster); } + public uint GetEffectsToApply() { return _effectsToApply; } + + public void SetRemoveMode(AuraRemoveMode mode) { _removeMode = mode; } + public AuraRemoveMode GetRemoveMode() { return _removeMode; } + public bool HasRemoveMode() { return _removeMode != 0; } + + public bool IsNeedClientUpdate() { return _needClientUpdate; } + } + + public class Aura + { + const int UPDATE_TARGET_MAP_INTERVAL = 500; + + public Aura(SpellInfo spellproto, ObjectGuid castId, WorldObject owner, Unit caster, Item castItem, ObjectGuid casterGUID, int castItemLevel) + { + m_spellInfo = spellproto; + m_castGuid = castId; + m_casterGuid = !casterGUID.IsEmpty() ? casterGUID : caster.GetGUID(); + m_castItemGuid = castItem != null ? castItem.GetGUID() : ObjectGuid.Empty; + m_castItemLevel = castItemLevel; + m_spellXSpellVisualId = caster ? caster.GetCastSpellXSpellVisualId(spellproto) : spellproto.GetSpellXSpellVisualId(); + m_applyTime = Time.UnixTime; + m_owner = owner; + m_timeCla = 0; + m_updateTargetMapInterval = 0; + m_casterLevel = caster != null ? caster.getLevel() : m_spellInfo.SpellLevel; + m_procCharges = 0; + m_stackAmount = 1; + m_isRemoved = false; + m_isSingleTarget = false; + m_isUsingCharges = false; + m_lastProcAttemptTime = (DateTime.Now - TimeSpan.FromSeconds(10)); + m_lastProcSuccessTime = (DateTime.Now - TimeSpan.FromSeconds(120)); + + var powers = Global.DB2Mgr.GetSpellPowers(GetId(), caster ? caster.GetMap().GetDifficultyID() : Difficulty.None); + foreach (var power in powers) + if (power.ManaCostPerSecond != 0 || power.ManaCostPercentagePerSecond > 0.0f) + m_periodicCosts.Add(power); + + if (!m_periodicCosts.Empty()) + m_timeCla = 1 * Time.InMilliseconds; + + m_maxDuration = CalcMaxDuration(caster); + m_duration = m_maxDuration; + m_procCharges = CalcMaxCharges(caster); + m_isUsingCharges = m_procCharges != 0; + // m_casterLevel = cast item level/caster level, caster level should be saved to db, confirmed with sniffs + } + + public T GetScript(string scriptName) where T : AuraScript + { + return (T)GetScriptByName(scriptName); + } + + public AuraScript GetScriptByName(string scriptName) + { + foreach (var auraScript in m_loadedScripts) + if (auraScript._GetScriptName().Equals(scriptName)) + return auraScript; + return null; + } + + public void _InitEffects(uint effMask, Unit caster, int[] baseAmount) + { + // shouldn't be in constructor - functions in AuraEffect.AuraEffect use polymorphism + _spellEffectInfos = m_spellInfo.GetEffectsForDifficulty(GetOwner().GetMap().GetDifficultyID()); + + _effects = new AuraEffect[GetSpellEffectInfos().Length]; + + foreach (SpellEffectInfo effect in GetSpellEffectInfos()) + { + if (effect != null && Convert.ToBoolean(effMask & (1 << (int)effect.EffectIndex))) + _effects[effect.EffectIndex] = new AuraEffect(this, effect.EffectIndex, baseAmount != null ? baseAmount[effect.EffectIndex] : (int?)null, caster); + } + } + + public Unit GetCaster() + { + if (m_owner.GetGUID() == m_casterGuid) + return GetUnitOwner(); + AuraApplication aurApp = GetApplicationOfTarget(m_casterGuid); + if (aurApp != null) + return aurApp.GetTarget(); + + return Global.ObjAccessor.GetUnit(m_owner, m_casterGuid); + } + + public AuraObjectType GetAuraType() + { + return (m_owner.GetTypeId() == TypeId.DynamicObject) ? AuraObjectType.DynObj : AuraObjectType.Unit; + } + + public virtual void _ApplyForTarget(Unit target, Unit caster, AuraApplication auraApp) + { + Contract.Assert(target != null); + Contract.Assert(auraApp != null); + // aura mustn't be already applied on target + //Contract.Assert(!IsAppliedOnTarget(target.GetGUID()) && "Aura._ApplyForTarget: aura musn't be already applied on target"); + + m_applications[target.GetGUID()] = auraApp; + + // set infinity cooldown state for spells + if (caster != null && caster.IsTypeId(TypeId.Player)) + { + if (m_spellInfo.HasAttribute(SpellAttr0.DisabledWhileActive)) + { + Item castItem = !m_castItemGuid.IsEmpty() ? caster.ToPlayer().GetItemByGuid(m_castItemGuid) : null; + caster.GetSpellHistory().StartCooldown(m_spellInfo, castItem != null ? castItem.GetEntry() : 0, null, true); + } + } + } + public virtual void _UnapplyForTarget(Unit target, Unit caster, AuraApplication auraApp) + { + Contract.Assert(target != null); + Contract.Assert(auraApp.HasRemoveMode()); + Contract.Assert(auraApp != null); + + var app = m_applications.LookupByKey(target.GetGUID()); + + /// @todo Figure out why this happens + if (app == null) + { + Log.outError(LogFilter.Spells, "Aura._UnapplyForTarget, target: {0}, caster: {1}, spell: {2} was not found in owners application map!", + target.GetGUID().ToString(), caster ? caster.GetGUID().ToString() : "", auraApp.GetBase().GetSpellInfo().Id); + Contract.Assert(false); + } + + // aura has to be already applied + Contract.Assert(app == auraApp); + m_applications.Remove(target.GetGUID()); + + m_removedApplications.Add(auraApp); + + // reset cooldown state for spells + if (caster != null && GetSpellInfo().IsCooldownStartedOnEvent()) + // note: item based cooldowns and cooldown spell mods with charges ignored (unknown existed cases) + caster.GetSpellHistory().SendCooldownEvent(GetSpellInfo()); + } + + // removes aura from all targets + // and marks aura as removed + public void _Remove(AuraRemoveMode removeMode) + { + Contract.Assert(!m_isRemoved); + m_isRemoved = true; + foreach (var pair in m_applications.ToList()) + { + AuraApplication aurApp = pair.Value; + Unit target = aurApp.GetTarget(); + target._UnapplyAura(aurApp, removeMode); + } + + if (m_dropEvent != null) + { + m_dropEvent.ScheduleAbort(); + m_dropEvent = null; + } + } + + void UpdateTargetMap(Unit caster, bool apply = true) + { + if (IsRemoved()) + return; + + m_updateTargetMapInterval = UPDATE_TARGET_MAP_INTERVAL; + + // fill up to date target list + // target, effMask + Dictionary targets = new Dictionary(); + + FillTargetMap(ref targets, caster); + + List targetsToRemove = new List(); + + // mark all auras as ready to remove + foreach (var app in m_applications) + { + var existing = targets.FirstOrDefault(p => p.Key == app.Value.GetTarget()); + // not found in current area - remove the aura + if (existing.Key == null) + targetsToRemove.Add(app.Value.GetTarget()); + else + { + // needs readding - remove now, will be applied in next update cycle + // (dbcs do not have auras which apply on same type of targets but have different radius, so this is not really needed) + if (app.Value.GetEffectMask() != existing.Value || !CanBeAppliedOn(existing.Key)) + targetsToRemove.Add(app.Value.GetTarget()); + // nothing todo - aura already applied + // remove from auras to register list + targets.Remove(existing.Key); + } + } + // register auras for units + foreach (var unit in targets.Keys.ToList()) + { + // aura mustn't be already applied on target + AuraApplication aurApp = GetApplicationOfTarget(unit.GetGUID()); + if (aurApp != null) + { + // the core created 2 different units with same guid + // this is a major failue, which i can't fix right now + // let's remove one unit from aura list + // this may cause area aura "bouncing" between 2 units after each update + // but because we know the reason of a crash we can remove the assertion for now + if (aurApp.GetTarget() != unit) + { + // remove from auras to register list + targets.Remove(unit); + continue; + } + else + { + // ok, we have one unit twice in target map (impossible, but...) + Contract.Assert(false); + } + } + + var value = targets[unit]; + + bool addUnit = true; + // check target immunities + for (byte effIndex = 0; effIndex < SpellConst.MaxEffects; ++effIndex) + { + if (unit.IsImmunedToSpellEffect(GetSpellInfo(), effIndex)) + value &= (byte)~(1 << effIndex); + } + if (value == 0 || unit.IsImmunedToSpell(GetSpellInfo()) + || !CanBeAppliedOn(unit)) + addUnit = false; + + if (addUnit && !unit.IsHighestExclusiveAura(this, true)) + addUnit = false; + + if (addUnit) + { + // persistent area aura does not hit flying targets + if (GetAuraType() == AuraObjectType.DynObj) + { + if (unit.IsInFlight()) + addUnit = false; + } + // unit auras can not stack with each other + else // (GetAuraType() == UNIT_AURA_TYPE) + { + // Allow to remove by stack when aura is going to be applied on owner + if (unit != m_owner) + { + // check if not stacking aura already on target + // this one prevents unwanted usefull buff loss because of stacking and prevents overriding auras periodicaly by 2 near area aura owners + foreach (var iter in unit.GetAppliedAuras()) + { + Aura aura = iter.Value.GetBase(); + if (!CanStackWith(aura)) + { + addUnit = false; + break; + } + } + } + } + } + if (!addUnit) + targets.Remove(unit); + else + { + // owner has to be in world, or effect has to be applied to self + if (!m_owner.IsSelfOrInSameMap(unit)) + { + /// @todo There is a crash caused by shadowfiend load addon + Log.outFatal(LogFilter.Spells, "Aura {0}: Owner {1} (map {2}) is not in the same map as target {3} (map {4}).", GetSpellInfo().Id, + m_owner.GetName(), m_owner.IsInWorld ? (int)m_owner.GetMap().GetId() : -1, + unit.GetName(), unit.IsInWorld ? (int)unit.GetMap().GetId() : -1); + } + unit._CreateAuraApplication(this, value); + } + } + + // remove auras from units no longer needing them + foreach (var unit in targetsToRemove) + { + AuraApplication aurApp = GetApplicationOfTarget(unit.GetGUID()); + if (aurApp != null) + unit._UnapplyAura(aurApp, AuraRemoveMode.Default); + } + + if (!apply) + return; + + // apply aura effects for units + foreach (var pair in targets) + { + AuraApplication aurApp = GetApplicationOfTarget(pair.Key.GetGUID()); + if (aurApp != null) + { + // owner has to be in world, or effect has to be applied to self + Contract.Assert((!m_owner.IsInWorld && m_owner == pair.Key) || m_owner.IsInMap(pair.Key)); + pair.Key._ApplyAura(aurApp, pair.Value); + } + } + } + + // targets have to be registered and not have effect applied yet to use this function + public void _ApplyEffectForTargets(uint effIndex) + { + // prepare list of aura targets + List targetList = new List(); + foreach (var app in m_applications.Values) + { + if (Convert.ToBoolean(app.GetEffectsToApply() & (1 << (int)effIndex)) && !app.HasEffect(effIndex)) + targetList.Add(app.GetTarget()); + } + + // apply effect to targets + foreach (var unit in targetList) + { + if (GetApplicationOfTarget(unit.GetGUID()) != null) + { + // owner has to be in world, or effect has to be applied to self + Contract.Assert((!GetOwner().IsInWorld && GetOwner() == unit) || GetOwner().IsInMap(unit)); + unit._ApplyAuraEffect(this, effIndex); + } + } + } + + public void UpdateOwner(uint diff, WorldObject owner) + { + Contract.Assert(owner == m_owner); + + Unit caster = GetCaster(); + // Apply spellmods for channeled auras + // used for example when triggered spell of spell:10 is modded + Spell modSpell = null; + Player modOwner = null; + if (caster != null) + { + modOwner = caster.GetSpellModOwner(); + if (modOwner != null) + { + modSpell = modOwner.FindCurrentSpellBySpellId(GetId()); + if (modSpell != null) + modOwner.SetSpellModTakingSpell(modSpell, true); + } + } + + Update(diff, caster); + + if (m_updateTargetMapInterval <= diff) + UpdateTargetMap(caster); + else + m_updateTargetMapInterval -= (int)diff; + + // update aura effects + foreach (AuraEffect effect in GetAuraEffects()) + if (effect != null) + effect.Update(diff, caster); + + // remove spellmods after effects update + if (modSpell != null) + modOwner.SetSpellModTakingSpell(modSpell, false); + + _DeleteRemovedApplications(); + } + + void Update(uint diff, Unit caster) + { + if (m_duration > 0) + { + m_duration -= (int)diff; + if (m_duration < 0) + m_duration = 0; + + // handle manaPerSecond/manaPerSecondPerLevel + if (m_timeCla != 0) + { + if (m_timeCla > diff) + m_timeCla -= (int)diff; + else if (caster != null) + { + if (!m_periodicCosts.Empty()) + { + m_timeCla += (int)(1000 - diff); + + foreach (SpellPowerRecord power in m_periodicCosts) + { + if (power.RequiredAura != 0 && !caster.HasAura(power.RequiredAura)) + continue; + + int manaPerSecond = (int)power.ManaCostPerSecond; + if (power.PowerType != PowerType.Health) + manaPerSecond += MathFunctions.CalculatePct(caster.GetMaxPower(power.PowerType), power.ManaCostPercentagePerSecond); + else + manaPerSecond += (int)MathFunctions.CalculatePct(caster.GetMaxHealth(), power.ManaCostPercentagePerSecond); + + if (manaPerSecond != 0) + { + if (power.PowerType == PowerType.Health) + { + if ((int)caster.GetHealth() > manaPerSecond) + caster.ModifyHealth(-manaPerSecond); + else + Remove(); + } + else if (caster.GetPower(power.PowerType) >= manaPerSecond) + caster.ModifyPower(power.PowerType, -manaPerSecond); + else + Remove(); + } + } + } + } + } + } + } + + int CalcMaxDuration(Unit caster) + { + Player modOwner = null; + int maxDuration; + + if (caster != null) + { + modOwner = caster.GetSpellModOwner(); + maxDuration = caster.CalcSpellDuration(m_spellInfo); + } + else + maxDuration = m_spellInfo.GetDuration(); + + if (IsPassive() && m_spellInfo.DurationEntry == null) + maxDuration = -1; + + // IsPermanent() checks max duration (which we are supposed to calculate here) + if (maxDuration != -1 && modOwner != null) + modOwner.ApplySpellMod(GetId(), SpellModOp.Duration, ref maxDuration); + return maxDuration; + } + + public void SetDuration(int duration, bool withMods = false) + { + if (withMods) + { + Unit caster = GetCaster(); + if (caster) + { + Player modOwner = caster.GetSpellModOwner(); + if (modOwner) + modOwner.ApplySpellMod(GetId(), SpellModOp.Duration, ref duration); + } + } + + m_duration = duration; + SetNeedClientUpdateForTargets(); + } + + public void RefreshDuration(bool withMods = false) + { + Unit caster = GetCaster(); + if (withMods && caster) + { + int duration = m_spellInfo.GetMaxDuration(); + // Calculate duration of periodics affected by haste. + if (caster.HasAuraTypeWithAffectMask(AuraType.PeriodicHaste, m_spellInfo) || m_spellInfo.HasAttribute(SpellAttr5.HasteAffectDuration)) + duration = (int)(duration * caster.GetFloatValue(UnitFields.ModCastSpeed)); + + SetMaxDuration(duration); + SetDuration(duration); + } + else + SetDuration(GetMaxDuration()); + + if (!m_periodicCosts.Empty()) + m_timeCla = 1 * Time.InMilliseconds; + } + + void RefreshTimers() + { + m_maxDuration = CalcMaxDuration(); + bool resetPeriodic = true; + if (m_spellInfo.HasAttribute(SpellAttr8.DontResetPeriodicTimer)) + { + int minPeriod = m_maxDuration; + for (byte i = 0; i < SpellConst.MaxEffects; ++i) + { + AuraEffect eff = GetEffect(i); + if (eff != null) + { + int period = eff.GetPeriod(); + if (period != 0) + minPeriod = Math.Min(period, minPeriod); + } + } + + // If only one tick remaining, roll it over into new duration + if (GetDuration() <= minPeriod) + { + m_maxDuration += GetDuration(); + resetPeriodic = false; + } + } + + RefreshDuration(); + Unit caster = GetCaster(); + for (byte i = 0; i < SpellConst.MaxEffects; ++i) + if (HasEffect(i)) + GetEffect(i).CalculatePeriodic(caster, resetPeriodic, false); + } + + public void SetCharges(int charges) + { + if (m_procCharges == charges) + return; + m_procCharges = (byte)charges; + m_isUsingCharges = m_procCharges != 0; + SetNeedClientUpdateForTargets(); + } + + public bool ModCharges(int num, AuraRemoveMode removeMode = AuraRemoveMode.Default) + { + if (IsUsingCharges()) + { + int charges = m_procCharges + num; + int maxCharges = CalcMaxCharges(); + + // limit charges (only on charges increase, charges may be changed manually) + if ((num > 0) && (charges > maxCharges)) + charges = maxCharges; + // we're out of charges, remove + else if (charges <= 0) + { + Remove(removeMode); + return true; + } + + SetCharges((byte)charges); + } + return false; + } + + public void ModChargesDelayed(int num, AuraRemoveMode removeMode = AuraRemoveMode.Default) + { + m_dropEvent = null; + ModCharges(num, removeMode); + } + + public void DropChargeDelayed(uint delay, AuraRemoveMode removeMode = AuraRemoveMode.Default) + { + // aura is already during delayed charge drop + if (m_dropEvent != null) + return; + + // only units have events + Unit owner = m_owner.ToUnit(); + if (!owner) + return; + + m_dropEvent = new ChargeDropEvent(this, removeMode); + owner.m_Events.AddEvent(m_dropEvent, owner.m_Events.CalculateTime(delay)); + } + + public void SetStackAmount(byte stackAmount) + { + m_stackAmount = stackAmount; + Unit caster = GetCaster(); + + List applications = GetApplicationList(); + foreach (var appt in applications) + if (!appt.HasRemoveMode()) + HandleAuraSpecificMods(appt, caster, false, true); + + foreach (AuraEffect effect in GetAuraEffects()) + if (effect != null) + effect.ChangeAmount(effect.CalculateAmount(caster), false, true); + + foreach (var app in applications) + { + if (!app.HasRemoveMode()) + { + HandleAuraSpecificMods(app, caster, true, true); + HandleAuraSpecificPeriodics(app, caster); + } + } + + SetNeedClientUpdateForTargets(); + } + + public bool ModStackAmount(int num, AuraRemoveMode removeMode = AuraRemoveMode.Default) + { + int stackAmount = m_stackAmount + num; + + // limit the stack amount (only on stack increase, stack amount may be changed manually) + if ((num > 0) && (stackAmount > (int)m_spellInfo.StackAmount)) + { + // not stackable aura - set stack amount to 1 + if (m_spellInfo.StackAmount == 0) + stackAmount = 1; + else + stackAmount = (int)m_spellInfo.StackAmount; + } + // we're out of stacks, remove + else if (stackAmount <= 0) + { + Remove(removeMode); + return true; + } + + bool refresh = stackAmount >= GetStackAmount(); + + // Update stack amount + SetStackAmount((byte)stackAmount); + + if (refresh) + { + RefreshSpellMods(); + RefreshTimers(); + + // reset charges + SetCharges(CalcMaxCharges()); + // FIXME: not a best way to synchronize charges, but works + for (byte i = 0; i < SpellConst.MaxEffects; ++i) + { + AuraEffect aurEff = GetEffect(i); + if (aurEff != null) + if (aurEff.GetAuraType() == AuraType.AddFlatModifier || aurEff.GetAuraType() == AuraType.AddPctModifier) + { + SpellModifier mod = aurEff.GetSpellModifier(); + if (mod != null) + mod.charges = GetCharges(); + } + } + } + SetNeedClientUpdateForTargets(); + return false; + } + + void RefreshSpellMods() + { + foreach (var app in m_applications.Values) + { + Player player = app.GetTarget().ToPlayer(); + if (player != null) + player.RestoreAllSpellMods(0, this); + } + } + + public bool HasMoreThanOneEffectForType(AuraType auraType) + { + uint count = 0; + foreach (SpellEffectInfo effect in GetSpellEffectInfos()) + { + if (effect != null && HasEffect(effect.EffectIndex) && effect.ApplyAuraName == auraType) + ++count; + } + + return count > 1; + } + + public bool IsArea() + { + foreach (SpellEffectInfo effect in GetSpellEffectInfos()) + { + if (effect != null && HasEffect(effect.EffectIndex) && effect.IsAreaAuraEffect()) + return true; + } + return false; + } + + public bool IsPassive() + { + return m_spellInfo.IsPassive(); + } + + public bool IsDeathPersistent() + { + return GetSpellInfo().IsDeathPersistent(); + } + + public bool CanBeSaved() + { + if (IsPassive()) + return false; + + if (GetCasterGUID() != GetOwner().GetGUID()) + if (GetSpellInfo().IsSingleTarget()) + return false; + + // Can't be saved - aura handler relies on calculated amount and changes it + if (HasEffectType(AuraType.ConvertRune)) + return false; + + // No point in saving this, since the stable dialog can't be open on aura load anyway. + if (HasEffectType(AuraType.OpenStable)) + return false; + + // Can't save vehicle auras, it requires both caster & target to be in world + if (HasEffectType(AuraType.ControlVehicle)) + return false; + + // Incanter's Absorbtion - considering the minimal duration and problems with aura stacking + // we skip saving this aura + // Also for some reason other auras put as MultiSlot crash core on keeping them after restart, + // so put here only these for which you are sure they get removed + switch (GetId()) + { + case 44413: // Incanter's Absorption + case 40075: // Fel Flak Fire + case 55849: // Power Spark + return false; + } + + // When a druid logins, he doesnt have either eclipse power, nor the marker auras, nor the eclipse buffs. Dont save them. + if (GetId() == 67483 || GetId() == 67484 || GetId() == 48517 || GetId() == 48518) + return false; + + // don't save auras removed by proc system + if (IsUsingCharges() && GetCharges() == 0) + return false; + + return true; + } + + public bool IsSingleTargetWith(Aura aura) + { + // Same spell? + if (GetSpellInfo().IsRankOf(aura.GetSpellInfo())) + return true; + + SpellSpecificType spec = GetSpellInfo().GetSpellSpecific(); + // spell with single target specific types + switch (spec) + { + case SpellSpecificType.Judgement: + case SpellSpecificType.MagePolymorph: + if (aura.GetSpellInfo().GetSpellSpecific() == spec) + return true; + break; + default: + break; + } + + if (HasEffectType(AuraType.ControlVehicle) && aura.HasEffectType(AuraType.ControlVehicle)) + return true; + + return false; + } + + public void UnregisterSingleTarget() + { + Contract.Assert(m_isSingleTarget); + Unit caster = GetCaster(); + Contract.Assert(caster != null); + caster.GetSingleCastAuras().Remove(this); + SetIsSingleTarget(false); + } + + public int CalcDispelChance(Unit auraTarget, bool offensive) + { + // we assume that aura dispel chance is 100% on start + // need formula for level difference based chance + int resistChance = 0; + + // Apply dispel mod from aura caster + Unit caster = GetCaster(); + if (caster != null) + { + Player modOwner = caster.GetSpellModOwner(); + if (modOwner != null) + modOwner.ApplySpellMod(GetId(), SpellModOp.ResistDispelChance, ref resistChance); + } + + // Dispel resistance from target SPELL_AURA_MOD_DISPEL_RESIST + // Only affects offensive dispels + if (offensive && auraTarget != null) + resistChance += auraTarget.GetTotalAuraModifier(AuraType.ModDispelResist); + + resistChance = resistChance < 0 ? 0 : resistChance; + resistChance = resistChance > 100 ? 100 : resistChance; + return 100 - resistChance; + } + + public AuraKey GenerateKey(out uint recalculateMask) + { + AuraKey key = new AuraKey(GetCasterGUID(), GetCastItemGUID(), GetId(), 0); + recalculateMask = 0; + for (int i = 0; i < _effects.Length; ++i) + { + AuraEffect effect = _effects[i]; + if (effect != null) + { + key.EffectMask |= 1u << i; + if (effect.CanBeRecalculated()) + recalculateMask |= 1u << i; + } + } + + return key; + } + + public void SetLoadedState(int maxduration, int duration, int charges, byte stackamount, uint recalculateMask, int[] amount) + { + m_maxDuration = maxduration; + m_duration = duration; + m_procCharges = (byte)charges; + m_isUsingCharges = m_procCharges != 0; + m_stackAmount = stackamount; + Unit caster = GetCaster(); + foreach (AuraEffect effect in GetAuraEffects()) + { + if (effect == null) + continue; + + effect.SetAmount(amount[effect.GetEffIndex()]); + effect.SetCanBeRecalculated(Convert.ToBoolean(recalculateMask & (1 << effect.GetEffIndex()))); + effect.CalculatePeriodic(caster, false, true); + effect.CalculateSpellMod(); + effect.RecalculateAmount(caster); + } + } + + public bool HasEffectType(AuraType type) + { + foreach (var eff in GetAuraEffects()) + if (eff != null && HasEffect(eff.GetEffIndex()) && eff.GetAuraType() == type) + return true; + + return false; + } + + public void RecalculateAmountOfEffects() + { + Contract.Assert(!IsRemoved()); + Unit caster = GetCaster(); + foreach (AuraEffect effect in GetAuraEffects()) + if (effect != null && !IsRemoved()) + effect.RecalculateAmount(caster); + } + + public void HandleAllEffects(AuraApplication aurApp, AuraEffectHandleModes mode, bool apply) + { + Contract.Assert(!IsRemoved()); + foreach (AuraEffect effect in GetAuraEffects()) + if (effect != null && !IsRemoved()) + effect.HandleEffect(aurApp, mode, apply); + } + + public List GetApplicationList() + { + var applicationList = new List(); + foreach (var app in m_applications.Values) + { + if (app.GetEffectMask() != 0) + applicationList.Add(app); + } + + return applicationList; + } + + public void SetNeedClientUpdateForTargets() + { + foreach (var app in m_applications.Values) + app.SetNeedClientUpdate(); + } + + // trigger effects on real aura apply/remove + public void HandleAuraSpecificMods(AuraApplication aurApp, Unit caster, bool apply, bool onReapply) + { + Unit target = aurApp.GetTarget(); + AuraRemoveMode removeMode = aurApp.GetRemoveMode(); + // handle spell_area table + var saBounds = Global.SpellMgr.GetSpellAreaForAuraMapBounds(GetId()); + if (saBounds != null) + { + uint zone, area; + target.GetZoneAndAreaId(out zone, out area); + + foreach (var bound in saBounds) + { + // some auras remove at aura remove + if (!bound.IsFitToRequirements((Player)target, zone, area)) + target.RemoveAurasDueToSpell(bound.spellId); + // some auras applied at aura apply + else if (bound.autocast) + { + if (!target.HasAura(bound.spellId)) + target.CastSpell(target, bound.spellId, true); + } + } + } + + // handle spell_linked_spell table + if (!onReapply) + { + // apply linked auras + if (apply) + { + var spellTriggered = Global.SpellMgr.GetSpellLinked((int)GetId() + (int)SpellLinkedType.Aura); + if (spellTriggered != null) + { + foreach (var spell in spellTriggered) + { + if (spell < 0) + target.ApplySpellImmune(GetId(), SpellImmunity.Id, (uint)-spell, true); + else if (caster != null) + caster.AddAura((uint)spell, target); + } + } + } + else + { + // remove linked auras + var spellTriggered = Global.SpellMgr.GetSpellLinked(-(int)GetId()); + if (spellTriggered != null) + { + foreach (var spell in spellTriggered) + { + if (spell < 0) + target.RemoveAurasDueToSpell((uint)-spell); + else if (removeMode != AuraRemoveMode.ByDeath) + target.CastSpell(target, (uint)spell, true, null, null, GetCasterGUID()); + } + } + spellTriggered = Global.SpellMgr.GetSpellLinked((int)GetId() + (int)SpellLinkedType.Aura); + if (spellTriggered != null) + { + foreach (var id in spellTriggered) + { + if (id < 0) + target.ApplySpellImmune(GetId(), SpellImmunity.Id, (uint)-id, false); + else + target.RemoveAura((uint)id, GetCasterGUID(), 0, removeMode); + } + } + } + } + else if (apply) + { + // modify stack amount of linked auras + var spellTriggered = Global.SpellMgr.GetSpellLinked((int)GetId() + (int)SpellLinkedType.Aura); + if (spellTriggered != null) + { + foreach (var id in spellTriggered) + { + if (id > 0) + { + Aura triggeredAura = target.GetAura((uint)id, GetCasterGUID()); + if (triggeredAura != null) + triggeredAura.ModStackAmount(GetStackAmount() - triggeredAura.GetStackAmount()); + } + } + } + } + + // mods at aura apply + if (apply) + { + switch (GetSpellInfo().SpellFamilyName) + { + case SpellFamilyNames.Generic: + switch (GetId()) + { + case 32474: // Buffeting Winds of Susurrus + if (target.IsTypeId(TypeId.Player)) + target.ToPlayer().ActivateTaxiPathTo(506, GetId()); + break; + case 33572: // Gronn Lord's Grasp, becomes stoned + if (GetStackAmount() >= 5 && !target.HasAura(33652)) + target.CastSpell(target, 33652, true); + break; + case 50836: //Petrifying Grip, becomes stoned + if (GetStackAmount() >= 5 && !target.HasAura(50812)) + target.CastSpell(target, 50812, true); + break; + case 60970: // Heroic Fury (remove Intercept cooldown) + if (target.IsTypeId(TypeId.Player)) + target.GetSpellHistory().ResetCooldown(20252, true); + break; + } + break; + case SpellFamilyNames.Druid: + if (caster == null) + break; + // Rejuvenation + if (GetSpellInfo().SpellFamilyFlags[0].HasAnyFlag(0x10u) && GetEffect(0) != null) + { + // Druid T8 Restoration 4P Bonus + if (caster.HasAura(64760)) + { + int heal = GetEffect(0).GetAmount(); + caster.CastCustomSpell(target, 64801, heal, 0, 0, true, null, GetEffect(0)); + } + } + break; + case SpellFamilyNames.Rogue: + // Sprint (skip non player casted spells by category) + if (GetSpellInfo().SpellFamilyFlags[0].HasAnyFlag(0x40u) && GetSpellInfo().GetCategory() == 44) + // in official maybe there is only one icon? + if (target.HasAura(58039)) // Glyph of Blurred Speed + target.CastSpell(target, 61922, true); // Sprint (waterwalk) + break; + } + } + // mods at aura remove + else + { + switch (GetSpellInfo().SpellFamilyName) + { + case SpellFamilyNames.Generic: + switch (GetId()) + { + case 61987: // Avenging Wrath + // Remove the immunity shield marker on Avenging Wrath removal if Forbearance is not present + if (target.HasAura(61988) && !target.HasAura(25771)) + target.RemoveAura(61988); + break; + case 72368: // Shared Suffering + case 72369: + if (caster != null) + { + AuraEffect aurEff = GetEffect(0); + if (aurEff != null) + { + int remainingDamage = (int)(aurEff.GetAmount() * (aurEff.GetTotalTicks() - aurEff.GetTickNumber())); + if (remainingDamage > 0) + caster.CastCustomSpell(caster, 72373, 0, remainingDamage, 0, true); + } + } + break; + } + break; + case SpellFamilyNames.Mage: + switch (GetId()) + { + case 66: // Invisibility + if (removeMode != AuraRemoveMode.Expire) + break; + target.CastSpell(target, 32612, true, null, GetEffect(1)); + target.CombatStop(); + break; + default: + break; + } + break; + case SpellFamilyNames.Priest: + if (caster == null) + break; + // Power word: shield + if (removeMode == AuraRemoveMode.EnemySpell && GetSpellInfo().SpellFamilyFlags[0].HasAnyFlag(0x00000001u)) + { + // Rapture + Aura aura = caster.GetAuraOfRankedSpell(47535); + if (aura != null) + { + // check cooldown + if (caster.IsTypeId(TypeId.Player)) + { + if (caster.GetSpellHistory().HasCooldown(aura.GetId())) + { + // This additional check is needed to add a minimal delay before cooldown in in effect + // to allow all bubbles broken by a single damage source proc mana return + if (caster.GetSpellHistory().GetRemainingCooldown(aura.GetSpellInfo()) <= 11 * Time.InMilliseconds) + break; + } + else // and add if needed + caster.GetSpellHistory().AddCooldown(aura.GetId(), 0, TimeSpan.FromSeconds(12)); + } + + // effect on caster + AuraEffect aurEff = aura.GetEffect(0); + if (aurEff != null) + { + float multiplier = aurEff.GetAmount(); + int basepoints0 = MathFunctions.CalculatePct(caster.GetMaxPower(PowerType.Mana), multiplier); + caster.CastCustomSpell(caster, 47755, basepoints0, 0, 0, true); + } + } + } + break; + case SpellFamilyNames.Rogue: + // Remove Vanish on stealth remove + if (GetId() == 1784) + target.RemoveAurasWithFamily(SpellFamilyNames.Rogue, new FlagArray128(0x0000800, 0, 0), target.GetGUID()); + break; + case SpellFamilyNames.Paladin: + // Remove the immunity shield marker on Forbearance removal if AW marker is not present + if (GetId() == 25771 && target.HasAura(61988) && !target.HasAura(61987)) + target.RemoveAura(61988); + break; + case SpellFamilyNames.Hunter: + // Glyph of Freezing Trap + if (GetSpellInfo().SpellFamilyFlags[0].HasAnyFlag(0x00000008u)) + if (caster != null && caster.HasAura(56845)) + target.CastSpell(target, 61394, true); + break; + } + } + + // mods at aura apply or remove + switch (GetSpellInfo().SpellFamilyName) + { + case SpellFamilyNames.Rogue: + // Stealth + if (GetSpellInfo().SpellFamilyFlags[0].HasAnyFlag(0x00400000u)) + { + // Master of subtlety + AuraEffect aurEff = target.GetAuraEffect(31223, 0); + if (aurEff != null) + { + if (!apply) + target.CastSpell(target, 31666, true); + else + { + int basepoints0 = aurEff.GetAmount(); + target.CastCustomSpell(target, 31665, basepoints0, 0, 0, true); + } + } + break; + } + break; + case SpellFamilyNames.Hunter: + switch (GetId()) + { + case 19574: // Bestial Wrath + // The Beast Within cast on owner if talent present + Unit owner = target.GetOwner(); + if (owner != null) + { + // Search talent + if (owner.HasAura(34692)) + { + if (apply) + owner.CastSpell(owner, 34471, true, null, GetEffect(0)); + else + owner.RemoveAurasDueToSpell(34471); + } + } + break; + } + break; + case SpellFamilyNames.Paladin: + switch (GetId()) + { + case 31821: + // Aura Mastery Triggered Spell Handler + // If apply Concentration Aura . trigger . apply Aura Mastery Immunity + // If remove Concentration Aura . trigger . remove Aura Mastery Immunity + // If remove Aura Mastery . trigger . remove Aura Mastery Immunity + // Do effects only on aura owner + if (GetCasterGUID() != target.GetGUID()) + break; + + if (apply) + { + if ((GetSpellInfo().Id == 31821 && target.HasAura(19746, GetCasterGUID())) || (GetSpellInfo().Id == 19746 && target.HasAura(31821))) + target.CastSpell(target, 64364, true); + } + else + target.RemoveAurasDueToSpell(64364, GetCasterGUID()); + break; + case 31842: // Divine Favor + // Item - Paladin T10 Holy 2P Bonus + if (target.HasAura(70755)) + { + if (apply) + target.CastSpell(target, 71166, true); + else + target.RemoveAurasDueToSpell(71166); + } + break; + } + break; + case SpellFamilyNames.Warlock: + // Drain Soul - If the target is at or below 25% health, Drain Soul causes four times the normal damage + if (GetSpellInfo().SpellFamilyFlags[0].HasAnyFlag(0x00004000u)) + { + if (caster == null) + break; + if (apply) + { + if (target != caster && !target.HealthAbovePct(25)) + caster.CastSpell(caster, 100001, true); + } + else + { + if (target != caster) + caster.RemoveAurasDueToSpell(GetId()); + else + caster.RemoveAurasDueToSpell(100001); + } + } + break; + } + } + + public void HandleAuraSpecificPeriodics(AuraApplication aurApp, Unit caster) + { + Unit target = aurApp.GetTarget(); + + if (!caster || aurApp.HasRemoveMode()) + return; + + foreach (AuraEffect effect in GetAuraEffects()) + { + if (effect == null || effect.IsAreaAuraEffect() || effect.IsEffect(SpellEffectName.PersistentAreaAura)) + continue; + + switch (effect.GetSpellEffectInfo().ApplyAuraName) + { + case AuraType.PeriodicDamage: + case AuraType.PeriodicDamagePercent: + case AuraType.PeriodicLeech: + { + // ignore non positive values (can be result apply spellmods to aura damage + uint damage = (uint)Math.Max(effect.GetAmount(), 0); + + // Script Hook For HandlePeriodicDamageAurasTick -- Allow scripts to change the Damage pre class mitigation calculations + Global.ScriptMgr.ModifyPeriodicDamageAurasTick(target, caster, ref damage); + + effect.SetDonePct(caster.SpellDamagePctDone(target, m_spellInfo, DamageEffectType.DOT)); // Calculate done percentage first! + effect.SetDamage((int)(caster.SpellDamageBonusDone(target, m_spellInfo, damage, DamageEffectType.DOT, effect.GetSpellEffectInfo(), GetStackAmount()) * effect.GetDonePct())); + effect.SetCritChance(caster.GetUnitSpellCriticalChance(target, m_spellInfo, m_spellInfo.GetSchoolMask())); + break; + } + case AuraType.PeriodicHeal: + case AuraType.ObsModHealth: + { + // ignore non positive values (can be result apply spellmods to aura damage + uint damage = (uint)Math.Max(effect.GetAmount(), 0); + + // Script Hook For HandlePeriodicDamageAurasTick -- Allow scripts to change the Damage pre class mitigation calculations + Global.ScriptMgr.ModifyPeriodicDamageAurasTick(target, caster, ref damage); + + effect.SetDonePct(caster.SpellHealingPctDone(target, m_spellInfo)); // Calculate done percentage first! + effect.SetDamage((int)(caster.SpellHealingBonusDone(target, m_spellInfo, damage, DamageEffectType.DOT, effect.GetSpellEffectInfo(), GetStackAmount()) * effect.GetDonePct())); + effect.SetCritChance(caster.GetUnitSpellCriticalChance(target, m_spellInfo, m_spellInfo.GetSchoolMask())); + break; + } + default: + break; + } + } + } + + bool CanBeAppliedOn(Unit target) + { + // unit not in world or during remove from world + if (!target.IsInWorld || target.IsDuringRemoveFromWorld()) + { + // area auras mustn't be applied + if (GetOwner() != target) + return false; + // not selfcasted single target auras mustn't be applied + if (GetCasterGUID() != GetOwner().GetGUID() && GetSpellInfo().IsSingleTarget()) + return false; + return true; + } + else + return CheckAreaTarget(target); + } + + bool CheckAreaTarget(Unit target) + { + return CallScriptCheckAreaTargetHandlers(target); + } + + public bool CanStackWith(Aura existingAura) + { + // Can stack with self + if (this == existingAura) + return true; + + // Dynobj auras always stack + if (GetAuraType() == AuraObjectType.DynObj || existingAura.GetAuraType() == AuraObjectType.DynObj) + return true; + + SpellInfo existingSpellInfo = existingAura.GetSpellInfo(); + bool sameCaster = GetCasterGUID() == existingAura.GetCasterGUID(); + + // passive auras don't stack with another rank of the spell cast by same caster + if (IsPassive() && sameCaster && (m_spellInfo.IsDifferentRankOf(existingSpellInfo) || (m_spellInfo.Id == existingSpellInfo.Id && m_castItemGuid.IsEmpty()))) + return false; + + foreach (SpellEffectInfo effect in existingAura.GetSpellEffectInfos()) + { + // prevent remove triggering aura by triggered aura + if (effect != null && effect.TriggerSpell == GetId()) + return true; + } + + foreach (SpellEffectInfo effect in GetSpellEffectInfos()) + { + // prevent remove triggered aura by triggering aura refresh + if (effect != null && effect.TriggerSpell == existingAura.GetId()) + return true; + } + + // check spell specific stack rules + if (m_spellInfo.IsAuraExclusiveBySpecificWith(existingSpellInfo) + || (sameCaster && m_spellInfo.IsAuraExclusiveBySpecificPerCasterWith(existingSpellInfo))) + return false; + + // check spell group stack rules + switch (Global.SpellMgr.CheckSpellGroupStackRules(m_spellInfo, existingSpellInfo)) + { + case SpellGroupStackRule.Exclusive: + case SpellGroupStackRule.ExclusiveHighest: // if it reaches this point, existing aura is lower/equal + return false; + case SpellGroupStackRule.ExclusiveFromSameCaster: + if (sameCaster) + return false; + break; + case SpellGroupStackRule.Default: + case SpellGroupStackRule.ExclusiveSameEffect: + default: + break; + } + + if (m_spellInfo.SpellFamilyName != existingSpellInfo.SpellFamilyName) + return true; + + if (!sameCaster) + { + // Channeled auras can stack if not forbidden by db or aura type + if (existingAura.GetSpellInfo().IsChanneled()) + return true; + + if (m_spellInfo.HasAttribute(SpellAttr3.StackForDiffCasters)) + return true; + + // check same periodic auras + for (byte i = 0; i < SpellConst.MaxEffects; i++) + { + SpellEffectInfo effect = GetSpellEffectInfo(i); + if (effect == null) + continue; + + switch (effect.ApplyAuraName) + { + // DOT or HOT from different casters will stack + case AuraType.PeriodicDamage: + case AuraType.PeriodicDummy: + case AuraType.PeriodicHeal: + case AuraType.PeriodicTriggerSpell: + case AuraType.PeriodicEnergize: + case AuraType.PeriodicManaLeech: + case AuraType.PeriodicLeech: + case AuraType.PowerBurn: + case AuraType.ObsModPower: + case AuraType.ObsModHealth: + case AuraType.PeriodicTriggerSpellWithValue: + SpellEffectInfo existingEffect = GetSpellEffectInfo(i); + // periodic auras which target areas are not allowed to stack this way (replenishment for example) + if (effect.IsTargetingArea() || (existingEffect != null && existingEffect.IsTargetingArea())) + break; + return true; + default: + break; + } + } + } + + if (HasEffectType(AuraType.ControlVehicle) && existingAura.HasEffectType(AuraType.ControlVehicle)) + { + Vehicle veh = null; + if (GetOwner().ToUnit()) + veh = GetOwner().ToUnit().GetVehicleKit(); + + if (!veh) // We should probably just let it stack. Vehicle system will prevent undefined behaviour later + return true; + + if (veh.GetAvailableSeatCount() == 0) + return false; // No empty seat available + + return true; // Empty seat available (skip rest) + } + + if (HasEffectType(AuraType.ShowConfirmationPrompt) || HasEffectType(AuraType.ShowConfirmationPromptWithDifficulty)) + if (existingAura.HasEffectType(AuraType.ShowConfirmationPrompt) || existingAura.HasEffectType(AuraType.ShowConfirmationPromptWithDifficulty)) + return false; + + // spell of same spell rank chain + if (m_spellInfo.IsRankOf(existingSpellInfo)) + { + // don't allow passive area auras to stack + if (m_spellInfo.IsMultiSlotAura() && !IsArea()) + return true; + if (!GetCastItemGUID().IsEmpty() && !existingAura.GetCastItemGUID().IsEmpty()) + if (GetCastItemGUID() != existingAura.GetCastItemGUID() && m_spellInfo.HasAttribute(SpellCustomAttributes.EnchantProc)) + return true; + // same spell with same caster should not stack + return false; + } + + return true; + } + + public bool IsProcOnCooldown(DateTime now) + { + return m_procCooldown > now; + } + + public void AddProcCooldown(DateTime cooldownEnd) + { + m_procCooldown = cooldownEnd; + } + + void PrepareProcToTrigger(AuraApplication aurApp, ProcEventInfo eventInfo, DateTime now) + { + bool prepare = CallScriptPrepareProcHandlers(aurApp, eventInfo); + if (!prepare) + return; + + // take one charge, aura expiration will be handled in Aura.TriggerProcOnEvent (if needed) + if (IsUsingCharges()) + { + --m_procCharges; + SetNeedClientUpdateForTargets(); + } + + SpellProcEntry procEntry = Global.SpellMgr.GetSpellProcEntry(GetId()); + + Contract.Assert(procEntry != null); + + // cooldowns should be added to the whole aura (see 51698 area aura) + AddProcCooldown(now + TimeSpan.FromMilliseconds(procEntry.Cooldown)); + } + + bool IsProcTriggeredOnEvent(AuraApplication aurApp, ProcEventInfo eventInfo, DateTime now) + { + SpellProcEntry procEntry = Global.SpellMgr.GetSpellProcEntry(GetId()); + // only auras with spell proc entry can trigger proc + if (procEntry == null) + return false; + + // check if we have charges to proc with + if (IsUsingCharges() && GetCharges() == 0) + return false; + + // check proc cooldown + if (IsProcOnCooldown(now)) + return false; + + /// @todo + // something about triggered spells triggering, and add extra attack effect + + // do checks against db data + if (!Global.SpellMgr.CanSpellTriggerProcOnEvent(procEntry, eventInfo)) + return false; + + // do checks using conditions table + if (!Global.ConditionMgr.IsObjectMeetingNotGroupedConditions(ConditionSourceType.SpellProc, GetId(), eventInfo.GetActor(), eventInfo.GetActionTarget())) + return false; + + // AuraScript Hook + bool check = CallScriptCheckProcHandlers(aurApp, eventInfo); + if (!check) + return false; + + /// @todo + // do allow additional requirements for procs + // this is needed because this is the last moment in which you can prevent aura charge drop on proc + // and possibly a way to prevent default checks (if there're going to be any) + + // Check if current equipment meets aura requirements + // do that only for passive spells + /// @todo this needs to be unified for all kinds of auras + Unit target = aurApp.GetTarget(); + if (IsPassive() && target.IsTypeId(TypeId.Player)) + { + if (GetSpellInfo().EquippedItemClass == ItemClass.Weapon) + { + if (target.ToPlayer().IsInFeralForm()) + return false; + + if (eventInfo.GetDamageInfo() != null) + { + WeaponAttackType attType = eventInfo.GetDamageInfo().GetAttackType(); + Item item = null; + if (attType == WeaponAttackType.BaseAttack || attType == WeaponAttackType.RangedAttack) + item = target.ToPlayer().GetUseableItemByPos(InventorySlots.Bag0, EquipmentSlot.MainHand); + else if (attType == WeaponAttackType.OffAttack) + item = target.ToPlayer().GetUseableItemByPos(InventorySlots.Bag0, EquipmentSlot.OffHand); + + if (item == null || item.IsBroken() || item.GetTemplate().GetClass() != ItemClass.Weapon || !Convert.ToBoolean((1 << (int)item.GetTemplate().GetSubClass()) & GetSpellInfo().EquippedItemSubClassMask)) + return false; + } + } + else if (GetSpellInfo().EquippedItemClass == ItemClass.Armor) + { + // Check if player is wearing shield + Item item = target.ToPlayer().GetUseableItemByPos(InventorySlots.Bag0, EquipmentSlot.OffHand); + if (item == null || item.IsBroken() || item.GetTemplate().GetClass() != ItemClass.Armor || !Convert.ToBoolean((1 << (int)item.GetTemplate().GetSubClass()) & GetSpellInfo().EquippedItemSubClassMask)) + return false; + } + } + + return RandomHelper.randChance(CalcProcChance(procEntry, eventInfo)); + } + + float CalcProcChance(SpellProcEntry procEntry, ProcEventInfo eventInfo) + { + float chance = procEntry.Chance; + // calculate chances depending on unit with caster's data + // so talents modifying chances and judgements will have properly calculated proc chance + Unit caster = GetCaster(); + if (caster != null) + { + // calculate ppm chance if present and we're using weapon + if (eventInfo.GetDamageInfo() != null && procEntry.ProcsPerMinute != 0) + { + uint WeaponSpeed = caster.GetBaseAttackTime(eventInfo.GetDamageInfo().GetAttackType()); + chance = caster.GetPPMProcChance(WeaponSpeed, procEntry.ProcsPerMinute, GetSpellInfo()); + } + // apply chance modifer aura, applies also to ppm chance (see improved judgement of light spell) + Player modOwner = caster.GetSpellModOwner(); + if (modOwner != null) + modOwner.ApplySpellMod(GetId(), SpellModOp.ChanceOfSuccess, ref chance); + } + return chance; + } + + void TriggerProcOnEvent(AuraApplication aurApp, ProcEventInfo eventInfo) + { + CallScriptProcHandlers(aurApp, eventInfo); + + for (byte i = 0; i < SpellConst.MaxEffects; ++i) + { + if (aurApp.HasEffect(i)) + // OnEffectProc / AfterEffectProc hooks handled in AuraEffect.HandleProc() + GetEffect(i).HandleProc(aurApp, eventInfo); + } + + CallScriptAfterProcHandlers(aurApp, eventInfo); + + // Remove aura if we've used last charge to proc + if (IsUsingCharges() && GetCharges() == 0) + Remove(); + } + + public float CalcPPMProcChance(Unit actor) + { + // Formula see http://us.battle.net/wow/en/forum/topic/8197741003#1 + float ppm = m_spellInfo.CalcProcPPM(actor, m_castItemLevel); + float averageProcInterval = 60.0f / ppm; + + var currentTime = DateTime.Now; + float secondsSinceLastAttempt = Math.Min((float)(currentTime - m_lastProcAttemptTime).TotalSeconds, 10.0f); + float secondsSinceLastProc = Math.Min((float)(currentTime - m_lastProcSuccessTime).TotalSeconds, 1000.0f); + + float chance = Math.Max(1.0f, 1.0f + ((secondsSinceLastProc / averageProcInterval - 1.5f) * 3.0f)) * ppm * secondsSinceLastAttempt / 60.0f; + MathFunctions.RoundToInterval(ref chance, 0.0f, 1.0f); + return chance * 100.0f; + } + + void _DeleteRemovedApplications() + { + m_removedApplications.Clear(); + } + + public void LoadScripts() + { + m_loadedScripts = Global.ScriptMgr.CreateAuraScripts(m_spellInfo.Id, this); + foreach (var script in m_loadedScripts.ToList()) + { + Log.outDebug(LogFilter.Spells, "Aura.LoadScripts: Script `{0}` for aura `{1}` is loaded now", script._GetScriptName(), m_spellInfo.Id); + script.Register(); + } + } + + public virtual void Remove(AuraRemoveMode removeMode = AuraRemoveMode.Default) { } + #region CallScripts + + bool CallScriptCheckAreaTargetHandlers(Unit target) + { + bool result = true; + foreach (var auraScript in m_loadedScripts) + { + auraScript._PrepareScriptCall(AuraScriptHookType.CheckAreaTarget); + + foreach (var hook in auraScript.DoCheckAreaTarget) + result &= hook.Call(target); + + auraScript._FinishScriptCall(); + } + return result; + } + + public void CallScriptDispel(DispelInfo dispelInfo) + { + foreach (var auraScript in m_loadedScripts) + { + auraScript._PrepareScriptCall(AuraScriptHookType.Dispel); + + foreach (var hook in auraScript.OnDispel) + hook.Call(dispelInfo); + + auraScript._FinishScriptCall(); + } + } + + public void CallScriptAfterDispel(DispelInfo dispelInfo) + { + foreach (var auraScript in m_loadedScripts) + { + auraScript._PrepareScriptCall(AuraScriptHookType.AfterDispel); + + foreach (var hook in auraScript.AfterDispel) + hook.Call(dispelInfo); + + auraScript._FinishScriptCall(); + } + } + + public bool CallScriptEffectApplyHandlers(AuraEffect aurEff, AuraApplication aurApp, AuraEffectHandleModes mode) + { + bool preventDefault = false; + foreach (var auraScript in m_loadedScripts) + { + auraScript._PrepareScriptCall(AuraScriptHookType.EffectApply, aurApp); + + foreach (var eff in auraScript.OnEffectApply) + if (eff.IsEffectAffected(m_spellInfo, aurEff.GetEffIndex())) + eff.Call(aurEff, mode); + + if (!preventDefault) + preventDefault = auraScript._IsDefaultActionPrevented(); + + auraScript._FinishScriptCall(); + } + + return preventDefault; + } + + public bool CallScriptEffectRemoveHandlers(AuraEffect aurEff, AuraApplication aurApp, AuraEffectHandleModes mode) + { + bool preventDefault = false; + foreach (var auraScript in m_loadedScripts) + { + auraScript._PrepareScriptCall(AuraScriptHookType.EffectRemove, aurApp); + + foreach (var eff in auraScript.OnEffectRemove) + if (eff.IsEffectAffected(m_spellInfo, aurEff.GetEffIndex())) + eff.Call(aurEff, mode); + + if (!preventDefault) + preventDefault = auraScript._IsDefaultActionPrevented(); + + auraScript._FinishScriptCall(); + } + return preventDefault; + } + + public void CallScriptAfterEffectApplyHandlers(AuraEffect aurEff, AuraApplication aurApp, AuraEffectHandleModes mode) + { + foreach (var auraScript in m_loadedScripts) + { + auraScript._PrepareScriptCall(AuraScriptHookType.EffectAfterApply, aurApp); + + foreach (var eff in auraScript.AfterEffectApply) + if (eff.IsEffectAffected(m_spellInfo, aurEff.GetEffIndex())) + eff.Call(aurEff, mode); + + auraScript._FinishScriptCall(); + } + } + + public void CallScriptAfterEffectRemoveHandlers(AuraEffect aurEff, AuraApplication aurApp, AuraEffectHandleModes mode) + { + foreach (var auraScript in m_loadedScripts) + { + auraScript._PrepareScriptCall(AuraScriptHookType.EffectAfterRemove, aurApp); + + foreach (var eff in auraScript.AfterEffectRemove) + if (eff.IsEffectAffected(m_spellInfo, aurEff.GetEffIndex())) + eff.Call(aurEff, mode); + + auraScript._FinishScriptCall(); + } + } + + public bool CallScriptEffectPeriodicHandlers(AuraEffect aurEff, AuraApplication aurApp) + { + bool preventDefault = false; + foreach (var auraScript in m_loadedScripts) + { + auraScript._PrepareScriptCall(AuraScriptHookType.EffectPeriodic, aurApp); + + foreach (var eff in auraScript.OnEffectPeriodic) + if (eff.IsEffectAffected(m_spellInfo, aurEff.GetEffIndex())) + eff.Call(aurEff); + + if (!preventDefault) + preventDefault = auraScript._IsDefaultActionPrevented(); + + auraScript._FinishScriptCall(); + } + + return preventDefault; + } + + public void CallScriptEffectUpdatePeriodicHandlers(AuraEffect aurEff) + { + foreach (var auraScript in m_loadedScripts) + { + auraScript._PrepareScriptCall(AuraScriptHookType.EffectUpdatePeriodic); + + foreach (var eff in auraScript.OnEffectUpdatePeriodic) + if (eff.IsEffectAffected(m_spellInfo, aurEff.GetEffIndex())) + eff.Call(aurEff); + + auraScript._FinishScriptCall(); + } + } + + public void CallScriptEffectCalcAmountHandlers(AuraEffect aurEff, ref int amount, ref bool canBeRecalculated) + { + foreach (var auraScript in m_loadedScripts) + { + auraScript._PrepareScriptCall(AuraScriptHookType.EffectCalcAmount); + foreach (var eff in auraScript.DoEffectCalcAmount) + { + if (eff.IsEffectAffected(m_spellInfo, aurEff.GetEffIndex())) + eff.Call(aurEff, ref amount, ref canBeRecalculated); + } + + auraScript._FinishScriptCall(); + } + } + + public void CallScriptEffectCalcPeriodicHandlers(AuraEffect aurEff, ref bool isPeriodic, ref int amplitude) + { + foreach (var auraScript in m_loadedScripts) + { + auraScript._PrepareScriptCall(AuraScriptHookType.EffectCalcPeriodic); + + foreach (var eff in auraScript.DoEffectCalcPeriodic) + if (eff.IsEffectAffected(m_spellInfo, aurEff.GetEffIndex())) + eff.Call(aurEff, ref isPeriodic, ref amplitude); + + auraScript._FinishScriptCall(); + } + } + + public void CallScriptEffectCalcSpellModHandlers(AuraEffect aurEff, ref SpellModifier spellMod) + { + foreach (var auraScript in m_loadedScripts) + { + auraScript._PrepareScriptCall(AuraScriptHookType.EffectCalcSpellmod); + + foreach (var eff in auraScript.DoEffectCalcSpellMod) + if (eff.IsEffectAffected(m_spellInfo, aurEff.GetEffIndex())) + eff.Call(aurEff, ref spellMod); + + auraScript._FinishScriptCall(); + } + } + + public void CallScriptEffectAbsorbHandlers(AuraEffect aurEff, AuraApplication aurApp, DamageInfo dmgInfo, ref uint absorbAmount, ref bool defaultPrevented) + { + foreach (var auraScript in m_loadedScripts) + { + auraScript._PrepareScriptCall(AuraScriptHookType.EffectAbsorb, aurApp); + + foreach (var eff in auraScript.OnEffectAbsorb) + if (eff.IsEffectAffected(m_spellInfo, aurEff.GetEffIndex())) + eff.Call(aurEff, dmgInfo, ref absorbAmount); + + if (!defaultPrevented) + defaultPrevented = auraScript._IsDefaultActionPrevented(); + + auraScript._FinishScriptCall(); + } + } + + public void CallScriptEffectAfterAbsorbHandlers(AuraEffect aurEff, AuraApplication aurApp, DamageInfo dmgInfo, ref uint absorbAmount) + { + foreach (var auraScript in m_loadedScripts) + { + auraScript._PrepareScriptCall(AuraScriptHookType.EffectAfterAbsorb, aurApp); + + foreach (var eff in auraScript.AfterEffectAbsorb) + if (eff.IsEffectAffected(m_spellInfo, aurEff.GetEffIndex())) + eff.Call(aurEff, dmgInfo, ref absorbAmount); + + auraScript._FinishScriptCall(); + } + } + + public void CallScriptEffectManaShieldHandlers(AuraEffect aurEff, AuraApplication aurApp, DamageInfo dmgInfo, ref uint absorbAmount, bool defaultPrevented) + { + foreach (var auraScript in m_loadedScripts) + { + auraScript._PrepareScriptCall(AuraScriptHookType.EffectManaShield, aurApp); + + foreach (var eff in auraScript.OnEffectManaShield) + if (eff.IsEffectAffected(m_spellInfo, aurEff.GetEffIndex())) + eff.Call(aurEff, dmgInfo, ref absorbAmount); + + auraScript._FinishScriptCall(); + } + } + + public void CallScriptEffectAfterManaShieldHandlers(AuraEffect aurEff, AuraApplication aurApp, DamageInfo dmgInfo, ref uint absorbAmount) + { + foreach (var auraScript in m_loadedScripts) + { + auraScript._PrepareScriptCall(AuraScriptHookType.EffectAfterManaShield, aurApp); + + foreach (var eff in auraScript.AfterEffectManaShield) + if (eff.IsEffectAffected(m_spellInfo, aurEff.GetEffIndex())) + eff.Call(aurEff, dmgInfo, ref absorbAmount); + + auraScript._FinishScriptCall(); + } + } + + public void CallScriptEffectSplitHandlers(AuraEffect aurEff, AuraApplication aurApp, DamageInfo dmgInfo, uint splitAmount) + { + foreach (var auraScript in m_loadedScripts) + { + auraScript._PrepareScriptCall(AuraScriptHookType.EffectSplit, aurApp); + + foreach (var eff in auraScript.OnEffectSplit) + if (eff.IsEffectAffected(m_spellInfo, aurEff.GetEffIndex())) + eff.Call(aurEff, dmgInfo, splitAmount); + + auraScript._FinishScriptCall(); + } + } + + public bool CallScriptCheckProcHandlers(AuraApplication aurApp, ProcEventInfo eventInfo) + { + bool result = true; + foreach (var auraScript in m_loadedScripts) + { + auraScript._PrepareScriptCall(AuraScriptHookType.CheckProc, aurApp); + + foreach (var hook in auraScript.DoCheckProc) + result &= hook.Call(eventInfo); + + auraScript._FinishScriptCall(); + } + + return result; + } + + public bool CallScriptPrepareProcHandlers(AuraApplication aurApp, ProcEventInfo eventInfo) + { + bool prepare = true; + foreach (var auraScript in m_loadedScripts) + { + auraScript._PrepareScriptCall(AuraScriptHookType.PrepareProc, aurApp); + + foreach (var eff in auraScript.DoPrepareProc) + eff.Call(eventInfo); + + if (prepare) + prepare = !auraScript._IsDefaultActionPrevented(); + + auraScript._FinishScriptCall(); + } + + return prepare; + } + + public bool CallScriptProcHandlers(AuraApplication aurApp, ProcEventInfo eventInfo) + { + bool handled = false; + foreach (var auraScript in m_loadedScripts) + { + auraScript._PrepareScriptCall(AuraScriptHookType.Proc, aurApp); + + foreach (var hook in auraScript.OnProc) + hook.Call(eventInfo); + + handled |= auraScript._IsDefaultActionPrevented(); + auraScript._FinishScriptCall(); + } + + return handled; + } + + public void CallScriptAfterProcHandlers(AuraApplication aurApp, ProcEventInfo eventInfo) + { + foreach (var auraScript in m_loadedScripts) + { + auraScript._PrepareScriptCall(AuraScriptHookType.AfterProc, aurApp); + + foreach (var hook in auraScript.AfterProc) + hook.Call(eventInfo); + + auraScript._FinishScriptCall(); + } + } + + public bool CallScriptEffectProcHandlers(AuraEffect aurEff, AuraApplication aurApp, ProcEventInfo eventInfo) + { + bool preventDefault = false; + foreach (var auraScript in m_loadedScripts) + { + auraScript._PrepareScriptCall(AuraScriptHookType.EffectProc, aurApp); + + foreach (var eff in auraScript.OnEffectProc) + if (eff.IsEffectAffected(m_spellInfo, aurEff.GetEffIndex())) + eff.Call(aurEff, eventInfo); + + if (!preventDefault) + preventDefault = auraScript._IsDefaultActionPrevented(); + + auraScript._FinishScriptCall(); + } + return preventDefault; + } + + public void CallScriptAfterEffectProcHandlers(AuraEffect aurEff, AuraApplication aurApp, ProcEventInfo eventInfo) + { + foreach (var auraScript in m_loadedScripts) + { + auraScript._PrepareScriptCall(AuraScriptHookType.EffectAfterProc, aurApp); + + foreach (var eff in auraScript.AfterEffectProc) + if (eff.IsEffectAffected(m_spellInfo, aurEff.GetEffIndex())) + eff.Call(aurEff, eventInfo); + + auraScript._FinishScriptCall(); + } + } + + #endregion + + public SpellInfo GetSpellInfo() { return m_spellInfo; } + public uint GetId() { return m_spellInfo.Id; } + public ObjectGuid GetCastGUID() { return m_castGuid; } + public ObjectGuid GetCasterGUID() { return m_casterGuid; } + public ObjectGuid GetCastItemGUID() { return m_castItemGuid; } + public int GetCastItemLevel() { return m_castItemLevel; } + public uint GetSpellXSpellVisualId() { return m_spellXSpellVisualId; } + public WorldObject GetOwner() { return m_owner; } + public Unit GetUnitOwner() + { + Contract.Assert(GetAuraType() == AuraObjectType.Unit); + return m_owner.ToUnit(); + } + public DynamicObject GetDynobjOwner() + { + Contract.Assert(GetAuraType() == AuraObjectType.DynObj); + return m_owner.ToDynamicObject(); + } + + public void _RegisterForTargets() + { + Unit caster = GetCaster(); + UpdateTargetMap(caster, false); + } + public void ApplyForTargets() + { + Unit caster = GetCaster(); + UpdateTargetMap(caster, true); + } + + public long GetApplyTime() { return m_applyTime; } + public int GetMaxDuration() { return m_maxDuration; } + public void SetMaxDuration(int duration) { m_maxDuration = duration; } + public int CalcMaxDuration() { return CalcMaxDuration(GetCaster()); } + public int GetDuration() { return m_duration; } + public bool IsExpired() { return GetDuration() == 0 && m_dropEvent == null; } + public bool IsPermanent() { return m_maxDuration == -1; } + + public byte GetCharges() { return m_procCharges; } + public byte CalcMaxCharges() { return CalcMaxCharges(GetCaster()); } + byte CalcMaxCharges(Unit caster) + { + uint maxProcCharges = m_spellInfo.ProcCharges; + var procEntry = Global.SpellMgr.GetSpellProcEntry(GetId()); + if (procEntry != null) + maxProcCharges = procEntry.Charges; + + if (caster != null) + { + Player modOwner = caster.GetSpellModOwner(); + if (modOwner != null) + modOwner.ApplySpellMod(GetId(), SpellModOp.Charges, ref maxProcCharges); + } + return (byte)maxProcCharges; + } + public bool DropCharge(AuraRemoveMode removeMode = AuraRemoveMode.Default) { return ModCharges(-1, removeMode); } + + public byte GetStackAmount() { return m_stackAmount; } + public byte GetCasterLevel() { return (byte)m_casterLevel; } + + public bool IsRemovedOnShapeLost(Unit target) + { + return GetCasterGUID() == target.GetGUID() + && m_spellInfo.Stances != 0 + && !m_spellInfo.HasAttribute(SpellAttr2.NotNeedShapeshift) + && !m_spellInfo.HasAttribute(SpellAttr0.NotShapeshift); + } + public bool IsRemoved() { return m_isRemoved; } + + public bool IsSingleTarget() { return m_isSingleTarget; } + public void SetIsSingleTarget(bool val) { m_isSingleTarget = val; } + + public bool HasEffect(uint index) + { + return GetEffect(index) != null; + } + public AuraEffect GetEffect(uint index) + { + if (index >= _effects.Length) + return null; + + return _effects[index]; + } + public uint GetEffectMask() + { + uint effMask = 0; + foreach (AuraEffect effect in GetAuraEffects()) + if (effect != null) + effMask |= (uint)(1 << effect.GetEffIndex()); + return effMask; + } + + public Dictionary GetApplicationMap() { return m_applications; } + public AuraApplication GetApplicationOfTarget(ObjectGuid guid) { return m_applications.LookupByKey(guid); } + public bool IsAppliedOnTarget(ObjectGuid guid) { return m_applications.ContainsKey(guid); } + + public bool IsUsingCharges() { return m_isUsingCharges; } + public void SetUsingCharges(bool val) { m_isUsingCharges = val; } + + public virtual void FillTargetMap(ref Dictionary targets, Unit caster) { } + + public AuraEffect[] GetAuraEffects() { return _effects; } + + public SpellEffectInfo[] GetSpellEffectInfos() { return _spellEffectInfos; } + public SpellEffectInfo GetSpellEffectInfo(uint index) + { + if (index >= _spellEffectInfos.Length) + return null; + + return _spellEffectInfos[index]; + } + + public void SetLastProcAttemptTime(DateTime lastProcAttemptTime) { m_lastProcAttemptTime = lastProcAttemptTime; } + public void SetLastProcSuccessTime(DateTime lastProcSuccessTime) { m_lastProcSuccessTime = lastProcSuccessTime; } + + //Static Methods + public static uint BuildEffectMaskForOwner(SpellInfo spellProto, uint avalibleEffectMask, WorldObject owner) + { + Contract.Assert(spellProto != null); + Contract.Assert(owner != null); + uint effMask = 0; + switch (owner.GetTypeId()) + { + case TypeId.Unit: + case TypeId.Player: + foreach (SpellEffectInfo effect in spellProto.GetEffectsForDifficulty(owner.GetMap().GetDifficultyID())) + { + if (effect != null && effect.IsUnitOwnedAuraEffect()) + effMask |= (uint)(1 << (int)effect.EffectIndex); + } + break; + case TypeId.DynamicObject: + foreach (SpellEffectInfo effect in spellProto.GetEffectsForDifficulty(owner.GetMap().GetDifficultyID())) + { + if (effect != null && effect.Effect == SpellEffectName.PersistentAreaAura) + effMask |= (uint)(1 << (int)effect.EffectIndex); + } + break; + default: + break; + } + return (effMask & avalibleEffectMask); + } + public static Aura TryRefreshStackOrCreate(SpellInfo spellproto, ObjectGuid castId, uint tryEffMask, WorldObject owner, Unit caster, int[] baseAmount = null, Item castItem = null, ObjectGuid casterGUID = default(ObjectGuid), int castItemLevel = -1) + { + bool throwway; + return TryRefreshStackOrCreate(spellproto, castId, tryEffMask, owner, caster, out throwway, baseAmount, castItem, casterGUID); + } + public static Aura TryRefreshStackOrCreate(SpellInfo spellproto, ObjectGuid castId, uint tryEffMask, WorldObject owner, Unit caster, out bool refresh, int[] baseAmount, Item castItem = null, ObjectGuid casterGUID = default(ObjectGuid), int castItemLevel = -1) + { + Contract.Assert(spellproto != null); + Contract.Assert(owner != null); + Contract.Assert(caster || !casterGUID.IsEmpty()); + Contract.Assert(tryEffMask <= SpellConst.MaxEffectMask); + refresh = false; + uint effMask = BuildEffectMaskForOwner(spellproto, tryEffMask, owner); + if (effMask == 0) + return null; + Aura foundAura = owner.ToUnit()._TryStackingOrRefreshingExistingAura(spellproto, effMask, caster, baseAmount, castItem, casterGUID, castItemLevel); + if (foundAura != null) + { + // we've here aura, which script triggered removal after modding stack amount + // check the state here, so we won't create new Aura object + if (foundAura.IsRemoved()) + return null; + + refresh = true; + return foundAura; + } + else + return Create(spellproto, castId, effMask, owner, caster, baseAmount, castItem, casterGUID, castItemLevel); + } + public static Aura TryCreate(SpellInfo spellproto, ObjectGuid castId, uint tryEffMask, WorldObject owner, Unit caster, int[] baseAmount, Item castItem = null, ObjectGuid casterGUID = default(ObjectGuid), int castItemLevel = -1) + { + Contract.Assert(spellproto != null); + Contract.Assert(owner != null); + Contract.Assert(caster != null || !casterGUID.IsEmpty()); + Contract.Assert(tryEffMask <= SpellConst.MaxEffectMask); + uint effMask = BuildEffectMaskForOwner(spellproto, tryEffMask, owner); + if (effMask == 0) + return null; + return Create(spellproto, castId, effMask, owner, caster, baseAmount, castItem, casterGUID, castItemLevel); + } + public static Aura Create(SpellInfo spellproto, ObjectGuid castId, uint effMask, WorldObject owner, Unit caster, int[] baseAmount, Item castItem, ObjectGuid casterGUID, int castItemLevel) + { + Contract.Assert(effMask != 0); + Contract.Assert(spellproto != null); + Contract.Assert(owner != null); + Contract.Assert(caster != null || !casterGUID.IsEmpty()); + Contract.Assert(effMask <= SpellConst.MaxEffectMask); + // try to get caster of aura + if (!casterGUID.IsEmpty()) + { + if (owner.GetGUID() == casterGUID) + caster = owner.ToUnit(); + else + caster = Global.ObjAccessor.GetUnit(owner, casterGUID); + } + else + casterGUID = caster.GetGUID(); + + // check if aura can be owned by owner + if (owner.isTypeMask(TypeMask.Unit)) + if (!owner.IsInWorld || owner.ToUnit().IsDuringRemoveFromWorld()) + // owner not in world so don't allow to own not self casted single target auras + if (casterGUID != owner.GetGUID() && spellproto.IsSingleTarget()) + return null; + + Aura aura = null; + switch (owner.GetTypeId()) + { + case TypeId.Unit: + case TypeId.Player: + aura = new UnitAura(spellproto, castId, effMask, owner, caster, baseAmount, castItem, casterGUID, castItemLevel); + break; + case TypeId.DynamicObject: + aura = new DynObjAura(spellproto, castId, effMask, owner, caster, baseAmount, castItem, casterGUID, castItemLevel); + break; + default: + Contract.Assert(false); + return null; + } + // aura can be removed in Unit:_AddAura call + if (aura.IsRemoved()) + return null; + return aura; + } + + #region Fields + List m_loadedScripts = new List(); + SpellInfo m_spellInfo; + ObjectGuid m_castGuid; + ObjectGuid m_casterGuid; + ObjectGuid m_castItemGuid; + int m_castItemLevel; + uint m_spellXSpellVisualId; + long m_applyTime; + WorldObject m_owner; + + int m_maxDuration; // Max aura duration + int m_duration; // Current time + int m_timeCla; // Timer for power per sec calcultion + List m_periodicCosts = new List();// Periodic costs + int m_updateTargetMapInterval; // Timer for UpdateTargetMapOfEffect + + uint m_casterLevel; // Aura level (store caster level for correct show level dep amount) + byte m_procCharges; // Aura charges (0 for infinite) + byte m_stackAmount; // Aura stack amount + + //might need to be arrays still + SpellEffectInfo[] _spellEffectInfos; + AuraEffect[] _effects; + Dictionary m_applications = new Dictionary(); + + bool m_isRemoved; + bool m_isSingleTarget; // true if it's a single target spell and registered at caster - can change at spell steal for example + bool m_isUsingCharges; + + ChargeDropEvent m_dropEvent; + + DateTime m_procCooldown; + DateTime m_lastProcAttemptTime; + DateTime m_lastProcSuccessTime; + + List m_removedApplications = new List(); + #endregion + } + + public class UnitAura : Aura + { + public UnitAura(SpellInfo spellproto, ObjectGuid castId, uint effMask, WorldObject owner, Unit caster, int[] baseAmount, Item castItem, ObjectGuid casterGUID, int castItemLevel) + : base(spellproto, castId, owner, caster, castItem, casterGUID, castItemLevel) + { + m_AuraDRGroup = DiminishingGroup.None; + LoadScripts(); + _InitEffects(effMask, caster, baseAmount); + GetUnitOwner()._AddAura(this, caster); + } + + public override void _ApplyForTarget(Unit target, Unit caster, AuraApplication aurApp) + { + base._ApplyForTarget(target, caster, aurApp); + + // register aura diminishing on apply + if (m_AuraDRGroup != DiminishingGroup.None) + target.ApplyDiminishingAura(m_AuraDRGroup, true); + } + public override void _UnapplyForTarget(Unit target, Unit caster, AuraApplication aurApp) + { + base._UnapplyForTarget(target, caster, aurApp); + + // unregister aura diminishing (and store last time) + if (m_AuraDRGroup != DiminishingGroup.None) + target.ApplyDiminishingAura(m_AuraDRGroup, false); + } + public override void Remove(AuraRemoveMode removeMode = AuraRemoveMode.Default) + { + if (IsRemoved()) + return; + GetUnitOwner().RemoveOwnedAura(this, removeMode); + } + + public override void FillTargetMap(ref Dictionary targets, Unit caster) + { + foreach (SpellEffectInfo effect in GetSpellEffectInfos()) + { + if (effect == null || !HasEffect(effect.EffectIndex)) + continue; + + List targetList = new List(); + // non-area aura + if (effect.Effect == SpellEffectName.ApplyAura) + { + targetList.Add(GetUnitOwner()); + } + else + { + float radius = effect.CalcRadius(caster); + + if (!GetUnitOwner().HasUnitState(UnitState.Isolated)) + { + switch (effect.Effect) + { + case SpellEffectName.ApplyAreaAuraParty: + case SpellEffectName.ApplyAreaAuraRaid: + { + targetList.Add(GetUnitOwner()); + var u_check = new AnyGroupedUnitInObjectRangeCheck(GetUnitOwner(), GetUnitOwner(), radius, effect.Effect == SpellEffectName.ApplyAreaAuraRaid); + var searcher = new UnitListSearcher(GetUnitOwner(), targetList, u_check); + Cell.VisitAllObjects(GetUnitOwner(), searcher, radius); + break; + } + case SpellEffectName.ApplyAreaAuraFriend: + { + targetList.Add(GetUnitOwner()); + var u_check = new AnyFriendlyUnitInObjectRangeCheck(GetUnitOwner(), GetUnitOwner(), radius); + var searcher = new UnitListSearcher(GetUnitOwner(), targetList, u_check); + Cell.VisitAllObjects(GetUnitOwner(), searcher, radius); + break; + } + case SpellEffectName.ApplyAreaAuraEnemy: + { + var u_check = new AnyAoETargetUnitInObjectRangeCheck(GetUnitOwner(), GetUnitOwner(), radius); + var searcher = new UnitListSearcher(GetUnitOwner(), targetList, u_check); + Cell.VisitAllObjects(GetUnitOwner(), searcher, radius); + break; + } + case SpellEffectName.ApplyAreaAuraPet: + targetList.Add(GetUnitOwner()); + goto case SpellEffectName.ApplyAreaAuraOwner; + case SpellEffectName.ApplyAreaAuraOwner: + { + Unit owner = GetUnitOwner().GetCharmerOrOwner(); + if (owner != null) + if (GetUnitOwner().IsWithinDistInMap(owner, radius)) + targetList.Add(owner); + break; + } + } + } + } + + foreach (var unit in targetList) + { + if (targets.ContainsKey(unit)) + targets[unit] |= (byte)(1 << (int)effect.EffectIndex); + else + targets[unit] = (byte)(1 << (int)effect.EffectIndex); + } + } + } + + // Allow Apply Aura Handler to modify and access m_AuraDRGroup + public void SetDiminishGroup(DiminishingGroup group) { m_AuraDRGroup = group; } + public DiminishingGroup GetDiminishGroup() { return m_AuraDRGroup; } + + DiminishingGroup m_AuraDRGroup; // Diminishing + } + + public class DynObjAura : Aura + { + public DynObjAura(SpellInfo spellproto, ObjectGuid castId, uint effMask, WorldObject owner, Unit caster, int[] baseAmount, Item castItem, ObjectGuid casterGUID, int castItemLevel) + : base(spellproto, castId, owner, caster, castItem, casterGUID, castItemLevel) + { + LoadScripts(); + Contract.Assert(GetDynobjOwner() != null); + Contract.Assert(GetDynobjOwner().IsInWorld); + Contract.Assert(GetDynobjOwner().GetMap() == caster.GetMap()); + _InitEffects(effMask, caster, baseAmount); + GetDynobjOwner().SetAura(this); + + } + + public override void Remove(AuraRemoveMode removeMode = AuraRemoveMode.Default) + { + if (IsRemoved()) + return; + _Remove(removeMode); + } + + public override void FillTargetMap(ref Dictionary targets, Unit caster) + { + Unit dynObjOwnerCaster = GetDynobjOwner().GetCaster(); + float radius = GetDynobjOwner().GetRadius(); + + foreach (SpellEffectInfo effect in GetSpellEffectInfos()) + { + if (effect == null || !HasEffect(effect.EffectIndex)) + continue; + + List targetList = new List(); + if (effect.TargetB.GetTarget() == Targets.DestDynobjAlly || effect.TargetB.GetTarget() == Targets.UnitDestAreaAlly) + { + var u_check = new AnyFriendlyUnitInObjectRangeCheck(GetDynobjOwner(), dynObjOwnerCaster, radius); + var searcher = new UnitListSearcher(GetDynobjOwner(), targetList, u_check); + Cell.VisitAllObjects(GetDynobjOwner(), searcher, radius); + } + else + { + var u_check = new AnyAoETargetUnitInObjectRangeCheck(GetDynobjOwner(), dynObjOwnerCaster, radius); + var searcher = new UnitListSearcher(GetDynobjOwner(), targetList, u_check); + Cell.VisitAllObjects(GetDynobjOwner(), searcher, radius); + } + + foreach (var unit in targetList) + { + if (targets.ContainsKey(unit)) + targets[unit] |= (byte)(1 << (int)effect.EffectIndex); + else + targets[unit] = (byte)(1 << (int)effect.EffectIndex); + } + } + } + } + + public class ChargeDropEvent : BasicEvent + { + public ChargeDropEvent(Aura aura, AuraRemoveMode mode) + { + _base = aura; + _mode = mode; + } + + public override bool Execute(ulong e_time, uint p_time) + { + // _base is always valid (look in Aura._Remove()) + _base.ModChargesDelayed(-1, _mode); + return true; + } + + Aura _base; + AuraRemoveMode _mode; + } + + public class AuraKey : IEquatable + { + public AuraKey(ObjectGuid caster, ObjectGuid item, uint spellId, uint effectMask) + { + Caster = caster; + Item = item; + SpellId = spellId; + EffectMask = effectMask; + } + + public static bool operator ==(AuraKey first, AuraKey other) + { + if (ReferenceEquals(first, other)) + return true; + + if ((object)first == null || (object)other == null) + return false; + + return first.Equals(other); + } + + public static bool operator !=(AuraKey first, AuraKey other) + { + return !(first == other); + } + + public override bool Equals(object obj) + { + return obj != null && obj is AuraKey && Equals((AuraKey)obj); + } + + public bool Equals(AuraKey other) + { + return other.Caster == Caster && other.Item == Item && other.SpellId == SpellId && other.EffectMask == EffectMask; + } + + public override int GetHashCode() + { + return new { Caster, Item, SpellId, EffectMask }.GetHashCode(); + } + + public ObjectGuid Caster; + public ObjectGuid Item; + public uint SpellId; + public uint EffectMask; + } + + public class AuraLoadEffectInfo + { + public int[] Amounts = new int[SpellConst.MaxEffects]; + public int[] BaseAmounts = new int[SpellConst.MaxEffects]; + } +} diff --git a/Game/Spells/Auras/AuraEffect.cs b/Game/Spells/Auras/AuraEffect.cs new file mode 100644 index 000000000..0e64da304 --- /dev/null +++ b/Game/Spells/Auras/AuraEffect.cs @@ -0,0 +1,6259 @@ +/* + * Copyright (C) 2012-2017 CypherCore + * + * 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, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +using Framework.Constants; +using Framework.Dynamic; +using Game.BattleFields; +using Game.BattleGrounds; +using Game.DataStorage; +using Game.Entities; +using Game.Loots; +using Game.Maps; +using Game.Network.Packets; +using System; +using System.Collections.Generic; +using System.Diagnostics.Contracts; +using System.Linq; + +namespace Game.Spells +{ + public class AuraEffect + { + public AuraEffect(Aura abase, uint effindex, int? baseAmount, Unit caster) + { + auraBase = abase; + m_spellInfo = abase.GetSpellInfo(); + _effectInfo = abase.GetSpellEffectInfo(effindex); + m_baseAmount = baseAmount.HasValue ? baseAmount.Value : abase.GetSpellEffectInfo(effindex).BasePoints; + m_donePct = 1.0f; + m_effIndex = (byte)effindex; + m_canBeRecalculated = true; + m_isPeriodic = false; + + CalculatePeriodic(caster, true, false); + m_amount = CalculateAmount(caster); + CalculateSpellMod(); + } + + void GetTargetList(out List targetList) + { + targetList = new List(); + var targetMap = GetBase().GetApplicationMap(); + // remove all targets which were not added to new list - they no longer deserve area aura + foreach (var app in targetMap.Values) + { + if (app.HasEffect(GetEffIndex())) + targetList.Add(app.GetTarget()); + } + } + void GetApplicationList(out List applicationList) + { + applicationList = new List(); + var targetMap = GetBase().GetApplicationMap(); + foreach (var app in targetMap.Values) + { + if (app.HasEffect(GetEffIndex())) + applicationList.Add(app); + } + } + + public int CalculateAmount(Unit caster) + { + // default amount calculation + int amount = 0; + + if (!m_spellInfo.HasAttribute(SpellAttr8.MasterySpecialization) || MathFunctions.fuzzyEq(GetSpellEffectInfo().BonusCoefficient, 0.0f)) + amount = GetSpellEffectInfo().CalcValue(caster, m_baseAmount, GetBase().GetOwner().ToUnit(), GetBase().GetCastItemLevel()); + else if (caster != null && caster.IsTypeId(TypeId.Player)) + amount = (int)(caster.GetFloatValue(PlayerFields.Mastery) * GetSpellEffectInfo().BonusCoefficient); + + // check item enchant aura cast + if (amount == 0 && caster != null) + { + ObjectGuid itemGUID = GetBase().GetCastItemGUID(); + if (!itemGUID.IsEmpty()) + { + Player playerCaster = caster.ToPlayer(); + if (playerCaster != null) + { + Item castItem = playerCaster.GetItemByGuid(itemGUID); + if (castItem != null) + { + if (castItem.GetItemSuffixFactor() != 0) + { + var item_rand_suffix = CliDB.ItemRandomSuffixStorage.LookupByKey((uint)Math.Abs(castItem.GetItemRandomPropertyId())); + if (item_rand_suffix != null) + { + for (int k = 0; k < ItemConst.MaxItemRandomProperties; k++) + { + var pEnchant = CliDB.SpellItemEnchantmentStorage.LookupByKey(item_rand_suffix.Enchantment[k]); + if (pEnchant != null) + { + for (int t = 0; t < ItemConst.MaxItemEnchantmentEffects; t++) + { + if (pEnchant.EffectSpellID[t] == m_spellInfo.Id) + { + amount = (int)((item_rand_suffix.AllocationPct[k] * castItem.GetItemSuffixFactor()) / 10000); + break; + } + } + } + + if (amount != 0) + break; + } + } + } + } + } + } + } + + // custom amount calculations go here + switch (GetAuraType()) + { + // crowd control auras + case AuraType.ModConfuse: + case AuraType.ModFear: + case AuraType.ModStun: + case AuraType.ModRoot: + case AuraType.Transform: + case AuraType.ModRoot2: + m_canBeRecalculated = false; + if (m_spellInfo.ProcFlags == 0) + break; + amount = (int)(GetBase().GetUnitOwner().CountPctFromMaxHealth(10)); + break; + case AuraType.SchoolAbsorb: + case AuraType.ManaShield: + m_canBeRecalculated = false; + break; + case AuraType.Mounted: + uint mountType = (uint)GetMiscValueB(); + MountRecord mountEntry = Global.DB2Mgr.GetMount(GetId()); + if (mountEntry != null) + mountType = mountEntry.MountTypeId; + + var mountCapability = GetBase().GetUnitOwner().GetMountCapability(mountType); + if (mountCapability != null) + { + amount = (int)mountCapability.Id; + m_canBeRecalculated = false; + } + break; + case AuraType.ShowConfirmationPromptWithDifficulty: + if (caster) + amount = (int)caster.GetMap().GetDifficultyID(); + m_canBeRecalculated = false; + break; + default: + break; + } + + GetBase().CallScriptEffectCalcAmountHandlers(this, ref amount, ref m_canBeRecalculated); + amount *= GetBase().GetStackAmount(); + return amount; + } + public void CalculatePeriodic(Unit caster, bool resetPeriodicTimer = true, bool load = false) + { + m_period = (int)GetSpellEffectInfo().ApplyAuraPeriod; + + // prepare periodics + switch (GetAuraType()) + { + case AuraType.ObsModPower: + // 3 spells have no amplitude set + if (m_period == 0) + m_period = 1 * Time.InMilliseconds; + break; + case AuraType.PeriodicDamage: + case AuraType.PeriodicHeal: + case AuraType.ObsModHealth: + case AuraType.PeriodicTriggerSpell: + case AuraType.PeriodicEnergize: + case AuraType.PeriodicLeech: + case AuraType.PeriodicHealthFunnel: + case AuraType.PeriodicManaLeech: + case AuraType.PeriodicDamagePercent: + case AuraType.PowerBurn: + case AuraType.PeriodicDummy: + case AuraType.PeriodicTriggerSpellWithValue: + m_isPeriodic = true; + break; + default: + break; + } + + GetBase().CallScriptEffectCalcPeriodicHandlers(this, ref m_isPeriodic, ref m_period); + + if (!m_isPeriodic) + return; + + Player modOwner = caster != null ? caster.GetSpellModOwner() : null; + + // Apply casting time mods + if (m_period != 0) + { + // Apply periodic time mod + if (modOwner != null) + modOwner.ApplySpellMod(GetId(), SpellModOp.ActivationTime, ref m_period); + + if (caster != null) + { + // Haste modifies periodic time of channeled spells + if (m_spellInfo.IsChanneled()) + caster.ModSpellDurationTime(m_spellInfo, ref m_period); + else if (m_spellInfo.HasAttribute(SpellAttr5.HasteAffectDuration)) + m_period = (int)(m_period * caster.GetFloatValue(UnitFields.ModCastHaste)); + } + } + + if (load) // aura loaded from db + { + m_tickNumber = (uint)(m_period != 0 ? GetBase().GetDuration() / m_period : 0); + m_periodicTimer = m_period != 0 ? GetBase().GetDuration() % m_period : 0; + if (m_spellInfo.HasAttribute(SpellAttr5.StartPeriodicAtApply)) + ++m_tickNumber; + } + else // aura just created or reapplied + { + m_tickNumber = 0; + // reset periodic timer on aura create or on reapply when aura isn't dot + // possibly we should not reset periodic timers only when aura is triggered by proc + // or maybe there's a spell attribute somewhere + if (resetPeriodicTimer) + { + m_periodicTimer = 0; + // Start periodic on next tick or at aura apply + if (m_period != 0 && !m_spellInfo.HasAttribute(SpellAttr5.StartPeriodicAtApply)) + m_periodicTimer += m_period; + } + } + } + public void CalculateSpellMod() + { + switch (GetAuraType()) + { + case AuraType.AddFlatModifier: + case AuraType.AddPctModifier: + if (m_spellmod == null) + { + m_spellmod = new SpellModifier(GetBase()); + m_spellmod.op = (SpellModOp)GetMiscValue(); + + m_spellmod.type = GetAuraType() == AuraType.AddPctModifier ? SpellModType.Pct : SpellModType.Flat; + m_spellmod.spellId = GetId(); + m_spellmod.mask = GetSpellEffectInfo().SpellClassMask; + m_spellmod.charges = GetBase().GetCharges(); + } + m_spellmod.value = GetAmount(); + break; + default: + break; + } + GetBase().CallScriptEffectCalcSpellModHandlers(this, ref m_spellmod); + } + public void ChangeAmount(int newAmount, bool mark = true, bool onStackOrReapply = false) + { + // Reapply if amount change + AuraEffectHandleModes handleMask = 0; + if (newAmount != GetAmount()) + handleMask |= AuraEffectHandleModes.ChangeAmount; + if (onStackOrReapply) + handleMask |= AuraEffectHandleModes.Reapply; + + if (handleMask == 0) + return; + + List effectApplications = new List(); + GetApplicationList(out effectApplications); + + foreach (var appt in effectApplications) + if (appt.HasEffect(GetEffIndex())) + HandleEffect(appt, handleMask, false); + + if (Convert.ToBoolean(handleMask & AuraEffectHandleModes.ChangeAmount)) + { + if (!mark) + m_amount = newAmount; + else + SetAmount(newAmount); + CalculateSpellMod(); + } + + foreach (var appt in effectApplications) + if (appt.HasEffect(GetEffIndex())) + HandleEffect(appt, handleMask, true); + + if (GetSpellInfo().HasAttribute(SpellAttr8.AuraSendAmount)) + GetBase().SetNeedClientUpdateForTargets(); + } + + public void HandleEffect(AuraApplication aurApp, AuraEffectHandleModes mode, bool apply) + { + // check if call is correct, we really don't want using bitmasks here (with 1 exception) + Contract.Assert(mode == AuraEffectHandleModes.Real || mode == AuraEffectHandleModes.SendForClient + || mode == AuraEffectHandleModes.ChangeAmount || mode == AuraEffectHandleModes.Stat + || mode == AuraEffectHandleModes.Skill || mode == AuraEffectHandleModes.Reapply + || mode == (AuraEffectHandleModes.ChangeAmount | AuraEffectHandleModes.Reapply)); + + // register/unregister effect in lists in case of real AuraEffect apply/remove + // registration/unregistration is done always before real effect handling (some effect handlers code is depending on this) + if (mode.HasAnyFlag(AuraEffectHandleModes.Real)) + aurApp.GetTarget()._RegisterAuraEffect(this, apply); + + // real aura apply/remove, handle modifier + if (mode.HasAnyFlag(AuraEffectHandleModes.ChangeAmountMask)) + ApplySpellMod(aurApp.GetTarget(), apply); + + // call scripts helping/replacing effect handlers + bool prevented = false; + if (apply) + prevented = GetBase().CallScriptEffectApplyHandlers(this, aurApp, mode); + else + prevented = GetBase().CallScriptEffectRemoveHandlers(this, aurApp, mode); + + // check if script events have removed the aura or if default effect prevention was requested + if ((apply && aurApp.HasRemoveMode()) || prevented) + return; + + Global.SpellMgr.GetAuraEffectHandler(GetAuraType()).Invoke(this, aurApp, mode, apply); + + // check if script events have removed the aura or if default effect prevention was requested + if (apply && aurApp.HasRemoveMode()) + return; + + // call scripts triggering additional events after apply/remove + if (apply) + GetBase().CallScriptAfterEffectApplyHandlers(this, aurApp, mode); + else + GetBase().CallScriptAfterEffectRemoveHandlers(this, aurApp, mode); + } + public void HandleEffect(Unit target, AuraEffectHandleModes mode, bool apply) + { + AuraApplication aurApp = GetBase().GetApplicationOfTarget(target.GetGUID()); + Contract.Assert(aurApp != null); + HandleEffect(aurApp, mode, apply); + } + + void ApplySpellMod(Unit target, bool apply) + { + if (m_spellmod == null || !target.IsTypeId(TypeId.Player)) + return; + + target.ToPlayer().AddSpellMod(m_spellmod, apply); + + // Auras with charges do not mod amount of passive auras + if (GetBase().IsUsingCharges()) + return; + // reapply some passive spells after add/remove related spellmods + // Warning: it is a dead loop if 2 auras each other amount-shouldn't happen + switch ((SpellModOp)GetMiscValue()) + { + case SpellModOp.AllEffects: + case SpellModOp.Effect1: + case SpellModOp.Effect2: + case SpellModOp.Effect3: + case SpellModOp.Effect4: + case SpellModOp.Effect5: + { + ObjectGuid guid = target.GetGUID(); + foreach (var iter in target.GetAppliedAuras()) + { + if (iter.Value == null) + continue; + Aura aura = iter.Value.GetBase(); + // only passive and permament auras-active auras should have amount set on spellcast and not be affected + // if aura is casted by others, it will not be affected + if ((aura.IsPassive() || aura.IsPermanent()) && aura.GetCasterGUID() == guid && aura.GetSpellInfo().IsAffectedBySpellMod(m_spellmod)) + { + AuraEffect aurEff; + if ((SpellModOp)GetMiscValue() == SpellModOp.AllEffects) + { + for (byte i = 0; i < SpellConst.MaxEffects; ++i) + { + if ((aurEff = aura.GetEffect(i)) != null) + aurEff.RecalculateAmount(); + } + } + else if ((SpellModOp)GetMiscValue() == SpellModOp.Effect1) + { + aurEff = aura.GetEffect(0); + if (aurEff != null) + aurEff.RecalculateAmount(); + } + else if ((SpellModOp)GetMiscValue() == SpellModOp.Effect2) + { + aurEff = aura.GetEffect(1); + if (aurEff != null) + aurEff.RecalculateAmount(); + } + else if ((SpellModOp)GetMiscValue() == SpellModOp.Effect3) + { + aurEff = aura.GetEffect(2); + if (aurEff != null) + aurEff.RecalculateAmount(); + } + else if ((SpellModOp)GetMiscValue() == SpellModOp.Effect4) + { + aurEff = aura.GetEffect(3); + if (aurEff != null) + aurEff.RecalculateAmount(); + } + else if ((SpellModOp)GetMiscValue() == SpellModOp.Effect5) + { + aurEff = aura.GetEffect(4); + if (aurEff != null) + aurEff.RecalculateAmount(); + } + } + } + } + break; + default: + break; + } + } + + public void Update(uint diff, Unit caster) + { + if (m_isPeriodic && (GetBase().GetDuration() >= 0 || GetBase().IsPassive() || GetBase().IsPermanent())) + { + if (m_periodicTimer > diff) + m_periodicTimer -= (int)diff; + else // tick also at m_periodicTimer == 0 to prevent lost last tick in case max m_duration == (max m_periodicTimer)*N + { + ++m_tickNumber; + // update before tick (aura can be removed in TriggerSpell or PeriodicTick calls) + m_periodicTimer += m_period - (int)diff; + UpdatePeriodic(caster); + + List effectApplications = new List(); + GetApplicationList(out effectApplications); + // tick on targets of effects + foreach (var appt in effectApplications) + if (appt.HasEffect(GetEffIndex())) + PeriodicTick(appt, caster); + } + } + } + void UpdatePeriodic(Unit caster) + { + switch (GetAuraType()) + { + case AuraType.PeriodicDummy: + switch (GetSpellInfo().SpellFamilyName) + { + case SpellFamilyNames.Generic: + switch (GetId()) + { + // Drink + case 430: + case 431: + case 432: + case 1133: + case 1135: + case 1137: + case 10250: + case 22734: + case 27089: + case 34291: + case 43182: + case 43183: + case 46755: + case 49472: // Drink Coffee + case 57073: + case 61830: + case 69176: + case 72623: + case 80166: + case 80167: + case 87958: + case 87959: + case 92736: + case 92797: + case 92800: + case 92803: + if (caster == null || !caster.IsTypeId(TypeId.Player)) + return; + // Get SPELL_AURA_MOD_POWER_REGEN aura from spell + AuraEffect aurEff = GetBase().GetEffect(0); + if (aurEff != null) + { + if (aurEff.GetAuraType() != AuraType.ModPowerRegen) + { + m_isPeriodic = false; + Log.outError(LogFilter.Spells, "Aura {0} structure has been changed - first aura is no longer SPELL_AURA_MOD_POWER_REGEN", GetId()); + } + else + { + // default case - not in arena + if (!caster.ToPlayer().InArena()) + { + aurEff.ChangeAmount(GetAmount()); + m_isPeriodic = false; + } + else + { + // ********************************************** + // This feature uses only in arenas + // ********************************************** + // Here need increase mana regen per tick (6 second rule) + // on 0 tick - 0 (handled in 2 second) + // on 1 tick - 166% (handled in 4 second) + // on 2 tick - 133% (handled in 6 second) + + // Apply bonus for 1 - 4 tick + switch (m_tickNumber) + { + case 1: // 0% + aurEff.ChangeAmount(0); + break; + case 2: // 166% + aurEff.ChangeAmount(GetAmount() * 5 / 3); + break; + case 3: // 133% + aurEff.ChangeAmount(GetAmount() * 4 / 3); + break; + default: // 100% - normal regen + aurEff.ChangeAmount(GetAmount()); + // No need to update after 4th tick + m_isPeriodic = false; + break; + } + } + } + } + break; + case 58549: // Tenacity + case 59911: // Tenacity (vehicle) + GetBase().RefreshDuration(); + break; + default: + break; + } + break; + case SpellFamilyNames.Mage: + if (GetId() == 55342)// Mirror Image + m_isPeriodic = false; + break; + case SpellFamilyNames.Deathknight: + // Chains of Ice + if (GetSpellInfo().SpellFamilyFlags[1].HasAnyFlag(0x00004000u)) + { + // Get 0 effect aura + AuraEffect slow = GetBase().GetEffect(0); + if (slow != null) + { + int newAmount = slow.GetAmount() + GetAmount(); + if (newAmount > 0) + newAmount = 0; + slow.ChangeAmount(newAmount); + } + return; + } + break; + default: + break; + } + break; + default: + break; + } + GetBase().CallScriptEffectUpdatePeriodicHandlers(this); + } + + bool CanPeriodicTickCrit(Unit caster) + { + Contract.Assert(caster); + + return caster.HasAuraTypeWithAffectMask(AuraType.AbilityPeriodicCrit, m_spellInfo); + } + + public bool IsAffectingSpell(SpellInfo spell) + { + if (spell == null) + return false; + // Check family name + if (spell.SpellFamilyName != m_spellInfo.SpellFamilyName) + return false; + + // Check EffectClassMask + if (GetSpellEffectInfo().SpellClassMask & spell.SpellFamilyFlags) + return true; + + return false; + } + + void SendTickImmune(Unit target, Unit caster) + { + if (caster != null) + caster.SendSpellDamageImmune(target, m_spellInfo.Id, true); + } + + void PeriodicTick(AuraApplication aurApp, Unit caster) + { + bool prevented = GetBase().CallScriptEffectPeriodicHandlers(this, aurApp); + if (prevented) + return; + + Unit target = aurApp.GetTarget(); + + switch (GetAuraType()) + { + case AuraType.PeriodicDummy: + HandlePeriodicDummyAuraTick(target, caster); + break; + case AuraType.PeriodicTriggerSpell: + HandlePeriodicTriggerSpellAuraTick(target, caster); + break; + case AuraType.PeriodicTriggerSpellWithValue: + HandlePeriodicTriggerSpellWithValueAuraTick(target, caster); + break; + case AuraType.PeriodicDamage: + case AuraType.PeriodicDamagePercent: + HandlePeriodicDamageAurasTick(target, caster); + break; + case AuraType.PeriodicLeech: + HandlePeriodicHealthLeechAuraTick(target, caster); + break; + case AuraType.PeriodicHealthFunnel: + HandlePeriodicHealthFunnelAuraTick(target, caster); + break; + case AuraType.PeriodicHeal: + case AuraType.ObsModHealth: + HandlePeriodicHealAurasTick(target, caster); + break; + case AuraType.PeriodicManaLeech: + HandlePeriodicManaLeechAuraTick(target, caster); + break; + case AuraType.ObsModPower: + HandleObsModPowerAuraTick(target, caster); + break; + case AuraType.PeriodicEnergize: + HandlePeriodicEnergizeAuraTick(target, caster); + break; + case AuraType.PowerBurn: + HandlePeriodicPowerBurnAuraTick(target, caster); + break; + default: + break; + } + } + + public void HandleProc(AuraApplication aurApp, ProcEventInfo eventInfo) + { + bool prevented = GetBase().CallScriptEffectProcHandlers(this, aurApp, eventInfo); + if (prevented) + return; + + switch (GetAuraType()) + { + case AuraType.ProcTriggerSpell: + HandleProcTriggerSpellAuraProc(aurApp, eventInfo); + break; + case AuraType.ProcTriggerSpellWithValue: + HandleProcTriggerSpellWithValueAuraProc(aurApp, eventInfo); + break; + case AuraType.ProcTriggerDamage: + HandleProcTriggerDamageAuraProc(aurApp, eventInfo); + break; + case AuraType.RaidProcFromCharge: + HandleRaidProcFromChargeAuraProc(aurApp, eventInfo); + break; + case AuraType.RaidProcFromChargeWithValue: + HandleRaidProcFromChargeWithValueAuraProc(aurApp, eventInfo); + break; + case AuraType.ProcOnPowerAmount: + case AuraType.ProcOnPowerAmount2: + HandleProcTriggerSpellOnPowerAmountAuraProc(aurApp, eventInfo); + break; + default: + break; + } + + GetBase().CallScriptAfterEffectProcHandlers(this, aurApp, eventInfo); + } + + void HandleShapeshiftBoosts(Unit target, bool apply) + { + uint spellId = 0; + uint spellId2 = 0; + uint spellId3 = 0; + uint spellId4 = 0; + + switch ((ShapeShiftForm)GetMiscValue()) + { + case ShapeShiftForm.CatForm: + spellId = 3025; + spellId2 = 48629; + spellId3 = 106840; + spellId4 = 113636; + break; + case ShapeShiftForm.TreeOfLife: + spellId = 5420; + spellId2 = 81097; + break; + case ShapeShiftForm.TravelForm: + spellId = 5419; + break; + case ShapeShiftForm.AquaticForm: + spellId = 5421; + break; + case ShapeShiftForm.BearForm: + spellId = 1178; + spellId2 = 21178; + spellId3 = 106829; + spellId4 = 106899; + break; + case ShapeShiftForm.FlightForm: + spellId = 33948; + spellId2 = 34764; + break; + case ShapeShiftForm.FlightFormEpic: + spellId = 40122; + spellId2 = 40121; + break; + case ShapeShiftForm.SpiritOfRedemption: + spellId = 27792; + spellId2 = 27795; + break; + case ShapeShiftForm.Shadowform: + if (target.HasAura(107906)) // Glyph of Shadow + spellId = 107904; + else if (target.HasAura(126745)) // Glyph of Shadowy Friends + spellId = 142024; + else + spellId = 107903; + break; + case ShapeShiftForm.GhostWolf: + if (target.HasAura(58135)) // Glyph of Spectral Wolf + spellId = 160942; + break; + default: + break; + } + + if (apply) + { + if (spellId != 0) + target.CastSpell(target, spellId, true, null, this); + + if (spellId2 != 0) + target.CastSpell(target, spellId2, true, null, this); + + if (spellId3 != 0) + target.CastSpell(target, spellId3, true, null, this); + + if (spellId4 != 0) + target.CastSpell(target, spellId4, true, null, this); + + if (target.IsTypeId(TypeId.Player)) + { + Player plrTarget = target.ToPlayer(); + + var sp_list = plrTarget.GetSpellMap(); + foreach (var pair in sp_list) + { + if (pair.Value.State == PlayerSpellState.Removed || pair.Value.Disabled) + continue; + + if (pair.Key == spellId || pair.Key == spellId2 || pair.Key == spellId3 || pair.Key == spellId4) + continue; + + SpellInfo spellInfo = Global.SpellMgr.GetSpellInfo(pair.Key); + if (spellInfo == null || !(spellInfo.IsPassive() || spellInfo.HasAttribute(SpellAttr0.HiddenClientside))) + continue; + + // always valid? + if (spellInfo.HasAttribute(SpellAttr8.MasterySpecialization) && !plrTarget.IsCurrentSpecMasterySpell(spellInfo)) + continue; + + if (Convert.ToBoolean(spellInfo.Stances & (1ul << (GetMiscValue() - 1)))) + target.CastSpell(target, pair.Key, true, null, this); + } + } + } + else + { + if (spellId != 0) + target.RemoveOwnedAura(spellId, target.GetGUID()); + if (spellId2 != 0) + target.RemoveOwnedAura(spellId2, target.GetGUID()); + if (spellId3 != 0) + target.RemoveOwnedAura(spellId3, target.GetGUID()); + if (spellId4 != 0) + target.RemoveOwnedAura(spellId4, target.GetGUID()); + + var shapeshifts = target.GetAuraEffectsByType(AuraType.ModShapeshift); + AuraEffect newAura = null; + // Iterate through all the shapeshift auras that the target has, if there is another aura with SPELL_AURA_MOD_SHAPESHIFT, then this aura is being removed due to that one being applied + foreach (var eff in shapeshifts) + { + if (eff != this) + { + newAura = eff; + break; + } + } + + foreach (var app in target.GetAppliedAuras()) + { + if (app.Value == null) + continue; + + // Use the new aura to see on what stance the target will be + ulong newStance = newAura != null ? (1ul << (newAura.GetMiscValue() - 1)) : 0; + + // If the stances are not compatible with the spell, remove it + if (app.Value.GetBase().IsRemovedOnShapeLost(target) && !Convert.ToBoolean(app.Value.GetBase().GetSpellInfo().Stances & newStance)) + target.RemoveAura(app); + } + } + } + + public Unit GetCaster() { return auraBase.GetCaster(); } + public ObjectGuid GetCasterGUID() { return auraBase.GetCasterGUID(); } + public Aura GetBase() { return auraBase; } + public SpellModifier GetSpellModifier() { return m_spellmod; } + + public SpellInfo GetSpellInfo() { return m_spellInfo; } + public uint GetId() { return m_spellInfo.Id; } + public byte GetEffIndex() { return m_effIndex; } + public int GetBaseAmount() { return m_baseAmount; } + public int GetPeriod() { return m_period; } + + public int GetMiscValueB() { return GetSpellEffectInfo().MiscValueB; } + public int GetMiscValue() { return GetSpellEffectInfo().MiscValue; } + public AuraType GetAuraType() { return GetSpellEffectInfo().ApplyAuraName; } + public int GetAmount() { return m_amount; } + public bool HasAmount() { return m_amount != 0; } + public void SetAmount(int _amount) { m_amount = _amount; m_canBeRecalculated = false; } + + int GetPeriodicTimer() { return m_periodicTimer; } + public void SetPeriodicTimer(int periodicTimer) { m_periodicTimer = periodicTimer; } + + void RecalculateAmount() + { + if (!CanBeRecalculated()) + return; + ChangeAmount(CalculateAmount(GetCaster()), false); + } + public void RecalculateAmount(Unit caster) + { + if (!CanBeRecalculated()) + return; + ChangeAmount(CalculateAmount(caster), false); + } + + public bool CanBeRecalculated() { return m_canBeRecalculated; } + public void SetCanBeRecalculated(bool val) { m_canBeRecalculated = val; } + + public void SetDamage(int val) { m_damage = val; } + public int GetDamage() { return m_damage; } + public void SetCritChance(float val) { m_critChance = val; } + public float GetCritChance() { return m_critChance; } + public void SetDonePct(float val) { m_donePct = val; } + public float GetDonePct() { return m_donePct; } + + public uint GetTickNumber() { return m_tickNumber; } + public int GetTotalTicks() + { + return m_period != 0 ? (GetBase().GetMaxDuration() / m_period) : 1; + } + public void ResetPeriodic(bool resetPeriodicTimer = false) + { + if (resetPeriodicTimer) + m_periodicTimer = m_period; + m_tickNumber = 0; + } + + public bool IsPeriodic() { return m_isPeriodic; } + void SetPeriodic(bool isPeriodic) { m_isPeriodic = isPeriodic; } + bool HasSpellClassMask() { return GetSpellEffectInfo().SpellClassMask; } + + public SpellEffectInfo GetSpellEffectInfo() { return _effectInfo; } + + public bool IsEffect() { return _effectInfo.Effect != 0; } + public bool IsEffect(SpellEffectName effectName) { return _effectInfo.Effect == effectName; } + public bool IsAreaAuraEffect() + { + if (_effectInfo.Effect == SpellEffectName.ApplyAreaAuraParty || + _effectInfo.Effect == SpellEffectName.ApplyAreaAuraRaid || + _effectInfo.Effect == SpellEffectName.ApplyAreaAuraFriend || + _effectInfo.Effect == SpellEffectName.ApplyAreaAuraEnemy || + _effectInfo.Effect == SpellEffectName.ApplyAreaAuraPet || + _effectInfo.Effect == SpellEffectName.ApplyAreaAuraOwner) + return true; + return false; + } + + #region Fields + Aura auraBase; + SpellInfo m_spellInfo; + SpellEffectInfo _effectInfo; + public int m_baseAmount; + + int m_amount; + int m_damage; + float m_critChance; + float m_donePct; + + SpellModifier m_spellmod; + + int m_periodicTimer; + int m_period; + uint m_tickNumber; + + byte m_effIndex; + bool m_canBeRecalculated; + bool m_isPeriodic; + #endregion + + #region AuraEffect Handlers + [AuraEffectHandler(AuraType.None)] + [AuraEffectHandler(AuraType.Unk46)] + [AuraEffectHandler(AuraType.Unk48)] + [AuraEffectHandler(AuraType.PetDamageMulti)] + [AuraEffectHandler(AuraType.DetectAmore)] + [AuraEffectHandler(AuraType.ModCriticalThreat)] + [AuraEffectHandler(AuraType.ModCooldown)] + [AuraEffectHandler(AuraType.Unk214)] + [AuraEffectHandler(AuraType.ModDetaunt)] + [AuraEffectHandler(AuraType.ModSpellDamageFromHealing)] + [AuraEffectHandler(AuraType.ModTargetResistBySpellClass)] + [AuraEffectHandler(AuraType.ModDamageDoneForMechanic)] + [AuraEffectHandler(AuraType.BlockSpellFamily)] + [AuraEffectHandler(AuraType.Strangulate)] + [AuraEffectHandler(AuraType.ModCritChanceForCaster)] + [AuraEffectHandler(AuraType.Unk311)] + [AuraEffectHandler(AuraType.AnimReplacementSet)] + [AuraEffectHandler(AuraType.ModSpellPowerPct)] + [AuraEffectHandler(AuraType.Unk324)] + [AuraEffectHandler(AuraType.ModBlind)] + [AuraEffectHandler(AuraType.Unk335)] + [AuraEffectHandler(AuraType.ModFlyingRestrictions)] + [AuraEffectHandler(AuraType.IncreaseSkillGainChance)] + [AuraEffectHandler(AuraType.ModResurrectedHealthByGuildMember)] + [AuraEffectHandler(AuraType.ModAutoattackDamage)] + [AuraEffectHandler(AuraType.ModSpellCooldownByHaste)] + [AuraEffectHandler(AuraType.ModGatheringItemsGainedPercent)] + [AuraEffectHandler(AuraType.Unk351)] + [AuraEffectHandler(AuraType.Unk352)] + [AuraEffectHandler(AuraType.ModCamouflage)] + [AuraEffectHandler(AuraType.Unk354)] + [AuraEffectHandler(AuraType.Unk356)] + [AuraEffectHandler(AuraType.EnableBoss1UnitFrame)] + [AuraEffectHandler(AuraType.WorgenAlteredForm)] + [AuraEffectHandler(AuraType.Unk359)] + [AuraEffectHandler(AuraType.ProcTriggerSpellCopy)] + [AuraEffectHandler(AuraType.OverrideAutoattackWithMeleeSpell)] + [AuraEffectHandler(AuraType.ModNextSpell)] + [AuraEffectHandler(AuraType.MaxFarClipPlane)] + [AuraEffectHandler(AuraType.EnablePowerBarTimer)] + [AuraEffectHandler(AuraType.SetFairFarClip)] + void HandleUnused(AuraApplication aurApp, AuraEffectHandleModes mode, bool apply) { } + + /**************************************/ + /*** VISIBILITY & PHASES ***/ + /**************************************/ + [AuraEffectHandler(AuraType.ModInvisibilityDetect)] + void HandleModInvisibilityDetect(AuraApplication aurApp, AuraEffectHandleModes mode, bool apply) + { + if (!mode.HasAnyFlag(AuraEffectHandleModes.ChangeAmountMask)) + return; + + Unit target = aurApp.GetTarget(); + InvisibilityType type = (InvisibilityType)GetMiscValue(); + + if (apply) + { + target.m_invisibilityDetect.AddFlag(type); + target.m_invisibilityDetect.AddValue(type, GetAmount()); + } + else + { + if (!target.HasAuraType(AuraType.ModInvisibilityDetect)) + target.m_invisibilityDetect.DelFlag(type); + + target.m_invisibilityDetect.AddValue(type, -GetAmount()); + } + + // call functions which may have additional effects after chainging state of unit + target.UpdateObjectVisibility(); + } + + [AuraEffectHandler(AuraType.ModInvisibility)] + void HandleModInvisibility(AuraApplication aurApp, AuraEffectHandleModes mode, bool apply) + { + if (!mode.HasAnyFlag(AuraEffectHandleModes.ChangeAmountSendForClientMask)) + return; + + Unit target = aurApp.GetTarget(); + InvisibilityType type = (InvisibilityType)GetMiscValue(); + + if (apply) + { + // apply glow vision + if (target.IsTypeId(TypeId.Player)) + target.SetByteFlag(PlayerFields.FieldBytes2, PlayerFieldOffsets.FieldBytes2OffsetAuraVision, PlayerFieldByte2Flags.InvisibilityGlow); + + target.m_invisibility.AddFlag(type); + target.m_invisibility.AddValue(type, GetAmount()); + } + else + { + if (!target.HasAuraType(AuraType.ModInvisibility)) + { + // if not have different invisibility auras. + // remove glow vision + if (target.IsTypeId(TypeId.Player)) + target.RemoveByteFlag(PlayerFields.FieldBytes2, PlayerFieldOffsets.FieldBytes2OffsetAuraVision, PlayerFieldByte2Flags.InvisibilityGlow); + + target.m_invisibility.DelFlag(type); + } + else + { + bool found = false; + var invisAuras = target.GetAuraEffectsByType(AuraType.ModInvisibility); + foreach (var eff in invisAuras) + { + if (GetMiscValue() == eff.GetMiscValue()) + { + found = true; + break; + } + } + if (!found) + target.m_invisibility.DelFlag(type); + } + + target.m_invisibility.AddValue(type, -GetAmount()); + } + + // call functions which may have additional effects after chainging state of unit + if (apply && mode.HasAnyFlag(AuraEffectHandleModes.Real)) + { + // drop flag at invisibiliy in bg + target.RemoveAurasWithInterruptFlags(SpellAuraInterruptFlags.ImmuneOrLostSelection); + } + target.UpdateObjectVisibility(); + } + + [AuraEffectHandler(AuraType.ModStealthDetect)] + void HandleModStealthDetect(AuraApplication aurApp, AuraEffectHandleModes mode, bool apply) + { + if (!mode.HasAnyFlag(AuraEffectHandleModes.ChangeAmountMask)) + return; + + Unit target = aurApp.GetTarget(); + StealthType type = (StealthType)GetMiscValue(); + + if (apply) + { + target.m_stealthDetect.AddFlag(type); + target.m_stealthDetect.AddValue(type, GetAmount()); + } + else + { + if (!target.HasAuraType(AuraType.ModStealthDetect)) + target.m_stealthDetect.DelFlag(type); + + target.m_stealthDetect.AddValue(type, -GetAmount()); + } + + // call functions which may have additional effects after chainging state of unit + target.UpdateObjectVisibility(); + } + + [AuraEffectHandler(AuraType.ModStealth)] + void HandleModStealth(AuraApplication aurApp, AuraEffectHandleModes mode, bool apply) + { + if (!mode.HasAnyFlag(AuraEffectHandleModes.ChangeAmountSendForClientMask)) + return; + + Unit target = aurApp.GetTarget(); + StealthType type = (StealthType)GetMiscValue(); + + if (apply) + { + target.m_stealth.AddFlag(type); + target.m_stealth.AddValue(type, GetAmount()); + target.SetStandFlags(UnitStandFlags.Creep); + if (target.IsTypeId(TypeId.Player)) + target.SetByteFlag(PlayerFields.FieldBytes2, PlayerFieldOffsets.FieldBytes2OffsetAuraVision, PlayerFieldByte2Flags.Stealth); + } + else + { + target.m_stealth.AddValue(type, -GetAmount()); + + if (!target.HasAuraType(AuraType.ModStealth)) // if last SPELL_AURA_MOD_STEALTH + { + target.m_stealth.DelFlag(type); + + target.RemoveStandFlags(UnitStandFlags.Creep); + if (target.IsTypeId(TypeId.Player)) + target.RemoveByteFlag(PlayerFields.FieldBytes2, PlayerFieldOffsets.FieldBytes2OffsetAuraVision, PlayerFieldByte2Flags.Stealth); + } + } + + // call functions which may have additional effects after chainging state of unit + if (apply && mode.HasAnyFlag(AuraEffectHandleModes.Real)) + { + // drop flag at stealth in bg + target.RemoveAurasWithInterruptFlags(SpellAuraInterruptFlags.ImmuneOrLostSelection); + } + target.UpdateObjectVisibility(); + } + + [AuraEffectHandler(AuraType.ModStealthLevel)] + void HandleModStealthLevel(AuraApplication aurApp, AuraEffectHandleModes mode, bool apply) + { + if (!mode.HasAnyFlag(AuraEffectHandleModes.ChangeAmountMask)) + return; + + Unit target = aurApp.GetTarget(); + StealthType type = (StealthType)GetMiscValue(); + + if (apply) + target.m_stealth.AddValue(type, GetAmount()); + else + target.m_stealth.AddValue(type, -GetAmount()); + + // call functions which may have additional effects after chainging state of unit + target.UpdateObjectVisibility(); + } + + [AuraEffectHandler(AuraType.SpiritOfRedemption)] + void HandleSpiritOfRedemption(AuraApplication aurApp, AuraEffectHandleModes mode, bool apply) + { + if (!mode.HasAnyFlag(AuraEffectHandleModes.Real)) + return; + + Unit target = aurApp.GetTarget(); + + if (!target.IsTypeId(TypeId.Player)) + return; + + // prepare spirit state + if (apply) + { + if (target.IsTypeId(TypeId.Player)) + { + // disable breath/etc timers + target.ToPlayer().StopMirrorTimers(); + + // set stand state (expected in this form) + if (!target.IsStandState()) + target.SetStandState(UnitStandStateType.Stand); + } + + target.SetHealth(1); + } + // die at aura end + else if (target.IsAlive()) + // call functions which may have additional effects after chainging state of unit + target.setDeathState(DeathState.JustDied); + } + + [AuraEffectHandler(AuraType.Ghost)] + void HandleAuraGhost(AuraApplication aurApp, AuraEffectHandleModes mode, bool apply) + { + if (!mode.HasAnyFlag(AuraEffectHandleModes.SendForClientMask)) + return; + + Unit target = aurApp.GetTarget(); + + if (!target.IsTypeId(TypeId.Player)) + return; + + if (apply) + { + target.SetFlag(PlayerFields.Flags, PlayerFlags.Ghost); + target.m_serverSideVisibility.SetValue(ServerSideVisibilityType.Ghost, GhostVisibilityType.Ghost); + target.m_serverSideVisibilityDetect.SetValue(ServerSideVisibilityType.Ghost, GhostVisibilityType.Ghost); + } + else + { + if (target.HasAuraType(AuraType.Ghost)) + return; + + target.RemoveFlag(PlayerFields.Flags, PlayerFlags.Ghost); + target.m_serverSideVisibility.SetValue(ServerSideVisibilityType.Ghost, GhostVisibilityType.Alive); + target.m_serverSideVisibilityDetect.SetValue(ServerSideVisibilityType.Ghost, GhostVisibilityType.Alive); + } + } + + [AuraEffectHandler(AuraType.Phase)] + void HandlePhase(AuraApplication aurApp, AuraEffectHandleModes mode, bool apply) + { + if (!mode.HasAnyFlag(AuraEffectHandleModes.Real)) + return; + + Unit target = aurApp.GetTarget(); + + var oldPhases = target.GetPhases(); + target.SetInPhase((uint)GetMiscValueB(), false, apply); + + // call functions which may have additional effects after chainging state of unit + // phase auras normally not expected at BG but anyway better check + if (apply) + { + // drop flag at invisibiliy in bg + target.RemoveAurasWithInterruptFlags(SpellAuraInterruptFlags.ImmuneOrLostSelection); + } + + Player player = target.ToPlayer(); + if (player) + { + if (player.IsInWorld) + player.GetMap().SendUpdateTransportVisibility(player, oldPhases); + player.SendUpdatePhasing(); + } + + // need triggering visibility update base at phase update of not GM invisible (other GMs anyway see in any phases) + if (target.IsVisible()) + target.UpdateObjectVisibility(); + } + + [AuraEffectHandler(AuraType.PhaseGroup)] + void HandlePhaseGroup(AuraApplication aurApp, AuraEffectHandleModes mode, bool apply) + { + if (!mode.HasAnyFlag(AuraEffectHandleModes.Real)) + return; + + Unit target = aurApp.GetTarget(); + var oldPhases = target.GetPhases(); + var phases = Global.DB2Mgr.GetPhasesForGroup((uint)GetMiscValueB()); + foreach (var phase in phases) + target.SetInPhase(phase, false, apply); + // call functions which may have additional effects after chainging state of unit + // phase auras normally not expected at BG but anyway better check + if (apply) + { + // drop flag at invisibiliy in bg + target.RemoveAurasWithInterruptFlags(SpellAuraInterruptFlags.ImmuneOrLostSelection); + } + + Player player = target.ToPlayer(); + if (player) + { + if (player.IsInWorld) + player.GetMap().SendUpdateTransportVisibility(player, oldPhases); + player.SendUpdatePhasing(); + } + + // need triggering visibility update base at phase update of not GM invisible (other GMs anyway see in any phases) + if (target.IsVisible()) + target.UpdateObjectVisibility(); + } + + /**********************/ + /*** UNIT MODEL ***/ + /**********************/ + [AuraEffectHandler(AuraType.ModShapeshift)] + void HandleAuraModShapeshift(AuraApplication aurApp, AuraEffectHandleModes mode, bool apply) + { + if (!mode.HasAnyFlag(AuraEffectHandleModes.Real)) + return; + + SpellShapeshiftFormRecord shapeInfo = CliDB.SpellShapeshiftFormStorage.LookupByKey(GetMiscValue()); + //ASSERT(shapeInfo, "Spell {0} uses unknown ShapeshiftForm (%u).", GetId(), GetMiscValue()); + + Unit target = aurApp.GetTarget(); + ShapeShiftForm form = (ShapeShiftForm)GetMiscValue(); + + uint modelid = target.GetModelForForm(form); + + if (apply) + { + // remove polymorph before changing display id to keep new display id + switch (form) + { + case ShapeShiftForm.CatForm: + case ShapeShiftForm.TreeOfLife: + case ShapeShiftForm.TravelForm: + case ShapeShiftForm.AquaticForm: + case ShapeShiftForm.BearForm: + case ShapeShiftForm.FlightFormEpic: + case ShapeShiftForm.FlightForm: + case ShapeShiftForm.MoonkinForm: + { + // remove movement affects + target.RemoveMovementImpairingAuras(); + + // and polymorphic affects + if (target.IsPolymorphed()) + target.RemoveAurasDueToSpell(target.GetTransForm()); + break; + } + default: + break; + } + + // remove other shapeshift before applying a new one + target.RemoveAurasByType(AuraType.ModShapeshift, ObjectGuid.Empty, GetBase()); + + // stop handling the effect if it was removed by linked event + if (aurApp.HasRemoveMode()) + return; + + target.SetShapeshiftForm(form); + if (modelid > 0) + { + SpellInfo transformSpellInfo = Global.SpellMgr.GetSpellInfo(target.GetTransForm()); + if (transformSpellInfo == null || !GetSpellInfo().IsPositive()) + target.SetDisplayId(modelid); + } + } + else + { + // reset model id if no other auras present + // may happen when aura is applied on linked event on aura removal + if (!target.HasAuraType(AuraType.ModShapeshift)) + { + target.SetShapeshiftForm(ShapeShiftForm.None); + if (target.GetClass() == Class.Druid) + { + // Remove movement impairing effects also when shifting out + target.RemoveMovementImpairingAuras(); + } + } + + if (modelid > 0) + target.RestoreDisplayId(); + + switch (form) + { + // Nordrassil Harness - bonus + case ShapeShiftForm.BearForm: + case ShapeShiftForm.CatForm: + AuraEffect dummy = target.GetAuraEffect(37315, 0); + if (dummy != null) + target.CastSpell(target, 37316, true, null, dummy); + break; + // Nordrassil Regalia - bonus + case ShapeShiftForm.MoonkinForm: + dummy = target.GetAuraEffect(37324, 0); + if (dummy != null) + target.CastSpell(target, 37325, true, null, dummy); + break; + default: + break; + } + } + + // adding/removing linked auras + // add/remove the shapeshift aura's boosts + HandleShapeshiftBoosts(target, apply); + + if (target.IsTypeId(TypeId.Player)) + target.ToPlayer().InitDataForForm(); + else + target.UpdateDisplayPower(); + + if (target.GetClass() == Class.Druid) + { + // Dash + AuraEffect aurEff = target.GetAuraEffect(AuraType.ModIncreaseSpeed, SpellFamilyNames.Druid, new FlagArray128(0, 0, 0x8)); + if (aurEff != null) + aurEff.RecalculateAmount(); + + // Disarm handling + // If druid shifts while being disarmed we need to deal with that since forms aren't affected by disarm + // and also HandleAuraModDisarm is not triggered + if (!target.CanUseAttackType(WeaponAttackType.BaseAttack)) + { + Item pItem = target.ToPlayer().GetItemByPos(InventorySlots.Bag0, EquipmentSlot.MainHand); + if (pItem != null) + target.ToPlayer()._ApplyWeaponDamage(EquipmentSlot.MainHand, pItem, apply); + } + } + + // stop handling the effect if it was removed by linked event + if (apply && aurApp.HasRemoveMode()) + return; + + if (target.IsTypeId(TypeId.Player)) + { + // Learn spells for shapeshift form - no need to send action bars or add spells to spellbook + for (byte i = 0; i < SpellConst.MaxShapeshift; ++i) + { + if (shapeInfo.PresetSpellID[i] == 0) + continue; + if (apply) + target.ToPlayer().AddTemporarySpell(shapeInfo.PresetSpellID[i]); + else + target.ToPlayer().RemoveTemporarySpell(shapeInfo.PresetSpellID[i]); + } + } + } + + [AuraEffectHandler(AuraType.Transform)] + void HandleAuraTransform(AuraApplication aurApp, AuraEffectHandleModes mode, bool apply) + { + if (!mode.HasAnyFlag(AuraEffectHandleModes.SendForClientMask)) + return; + + Unit target = aurApp.GetTarget(); + + if (apply) + { + // update active transform spell only when transform or shapeshift not set or not overwriting negative by positive case + if (target.GetModelForForm(target.GetShapeshiftForm()) == 0 || !GetSpellInfo().IsPositive()) + { + // special case (spell specific functionality) + if (GetMiscValue() == 0) + { + switch (GetId()) + { + // Orb of Deception + case 16739: + { + if (!target.IsTypeId(TypeId.Player)) + return; + + switch (target.GetRace()) + { + // Blood Elf + case Race.BloodElf: + target.SetDisplayId(target.GetGender() == Gender.Female ? 17830 : 17829u); + break; + // Orc + case Race.Orc: + target.SetDisplayId(target.GetGender() == Gender.Female ? 10140 : 10139u); + break; + // Troll + case Race.Troll: + target.SetDisplayId(target.GetGender() == Gender.Female ? 10134 : 10135u); + break; + // Tauren + case Race.Tauren: + target.SetDisplayId(target.GetGender() == Gender.Female ? 10147 : 10136u); + break; + // Undead + case Race.Undead: + target.SetDisplayId(target.GetGender() == Gender.Female ? 10145 : 10146u); + break; + // Draenei + case Race.Draenei: + target.SetDisplayId(target.GetGender() == Gender.Female ? 17828 : 17827u); + break; + // Dwarf + case Race.Dwarf: + target.SetDisplayId(target.GetGender() == Gender.Female ? 10142 : 10141u); + break; + // Gnome + case Race.Gnome: + target.SetDisplayId(target.GetGender() == Gender.Female ? 10149 : 10148u); + break; + // Human + case Race.Human: + target.SetDisplayId(target.GetGender() == Gender.Female ? 10138 : 10137u); + break; + // Night Elf + case Race.NightElf: + target.SetDisplayId(target.GetGender() == Gender.Female ? 10144 : 10143u); + break; + default: + break; + } + break; + } + // Murloc costume + case 42365: + target.SetDisplayId(21723); + break; + // Dread Corsair + case 50517: + // Corsair Costume + case 51926: + { + if (!target.IsTypeId(TypeId.Player)) + return; + + switch (target.GetRace()) + { + // Blood Elf + case Race.BloodElf: + target.SetDisplayId(target.GetGender() == Gender.Female ? 25043 : 25032u); + break; + // Orc + case Race.Orc: + target.SetDisplayId(target.GetGender() == Gender.Female ? 25050 : 25039u); + break; + // Troll + case Race.Troll: + target.SetDisplayId(target.GetGender() == Gender.Female ? 25052 : 25041u); + break; + // Tauren + case Race.Tauren: + target.SetDisplayId(target.GetGender() == Gender.Female ? 25051 : 25040u); + break; + // Undead + case Race.Undead: + target.SetDisplayId(target.GetGender() == Gender.Female ? 25053 : 25042u); + break; + // Draenei + case Race.Draenei: + target.SetDisplayId(target.GetGender() == Gender.Female ? 25044 : 25033u); + break; + // Dwarf + case Race.Dwarf: + target.SetDisplayId(target.GetGender() == Gender.Female ? 25045 : 25034u); + break; + // Gnome + case Race.Gnome: + target.SetDisplayId(target.GetGender() == Gender.Female ? 25035 : 25046u); + break; + // Human + case Race.Human: + target.SetDisplayId(target.GetGender() == Gender.Female ? 25037 : 25048u); + break; + // Night Elf + case Race.NightElf: + target.SetDisplayId(target.GetGender() == Gender.Female ? 25038 : 25049u); + break; + default: + break; + } + break; + } + // Pygmy Oil + case 53806: + target.SetDisplayId(22512); + break; + // Honor the Dead + case 65386: + case 65495: + target.SetDisplayId(target.GetGender() == Gender.Male ? 29203 : 29204u); + break; + // Darkspear Pride + case 75532: + target.SetDisplayId(target.GetGender() == Gender.Male ? 31737 : 31738u); + break; + // Gnomeregan Pride + case 75531: + target.SetDisplayId(target.GetGender() == Gender.Male ? 31654 : 31655u); + break; + default: + break; + } + } + else + { + CreatureTemplate ci = Global.ObjectMgr.GetCreatureTemplate((uint)GetMiscValue()); + if (ci == null) + { + target.SetDisplayId(16358); // pig pink ^_^ + Log.outError(LogFilter.Spells, "Auras: unknown creature id = {0} (only need its modelid) From Spell Aura Transform in Spell ID = {1}", GetMiscValue(), GetId()); + } + else + { + uint model_id = 0; + uint modelid = ObjectManager.ChooseDisplayId(ci); + if (modelid != 0) + model_id = modelid; // Will use the default model here + + target.SetDisplayId(model_id); + + // Dragonmaw Illusion (set mount model also) + if (GetId() == 42016 && target.GetMountID() != 0 && !target.GetAuraEffectsByType(AuraType.ModIncreaseMountedFlightSpeed).Empty()) + target.SetUInt32Value(UnitFields.MountDisplayId, 16314); + } + } + } + + // update active transform spell only when transform or shapeshift not set or not overwriting negative by positive case + SpellInfo transformSpellInfo = Global.SpellMgr.GetSpellInfo(target.GetTransForm()); + if (transformSpellInfo == null || !GetSpellInfo().IsPositive() || transformSpellInfo.IsPositive()) + target.setTransForm(GetId()); + + // polymorph case + if (mode.HasAnyFlag(AuraEffectHandleModes.Real) && target.IsTypeId(TypeId.Player) && target.IsPolymorphed()) + { + // for players, start regeneration after 1s (in polymorph fast regeneration case) + // only if caster is Player (after patch 2.4.2) + if (GetCasterGUID().IsPlayer()) + target.ToPlayer().setRegenTimerCount(1 * Time.InMilliseconds); + + //dismount polymorphed target (after patch 2.4.2) + if (target.IsMounted()) + target.RemoveAurasByType(AuraType.Mounted); + } + } + else + { + if (target.GetTransForm() == GetId()) + target.setTransForm(0); + + target.RestoreDisplayId(); + + // Dragonmaw Illusion (restore mount model) + if (GetId() == 42016 && target.GetMountID() == 16314) + { + if (!target.GetAuraEffectsByType(AuraType.Mounted).Empty()) + { + int cr_id = target.GetAuraEffectsByType(AuraType.Mounted)[0].GetMiscValue(); + CreatureTemplate ci = Global.ObjectMgr.GetCreatureTemplate((uint)cr_id); + if (ci != null) + { + uint displayID = ObjectManager.ChooseDisplayId(ci); + Global.ObjectMgr.GetCreatureModelRandomGender(ref displayID); + + target.SetUInt32Value(UnitFields.MountDisplayId, displayID); + } + } + } + } + } + + [AuraEffectHandler(AuraType.ModScale)] + [AuraEffectHandler(AuraType.ModScale2)] + void HandleAuraModScale(AuraApplication aurApp, AuraEffectHandleModes mode, bool apply) + { + if (!mode.HasAnyFlag(AuraEffectHandleModes.ChangeAmountSendForClientMask)) + return; + + Unit target = aurApp.GetTarget(); + + float scale = target.GetFloatValue(ObjectFields.ScaleX); + MathFunctions.ApplyPercentModFloatVar(ref scale, GetAmount(), apply); + target.SetObjectScale(scale); + } + + [AuraEffectHandler(AuraType.CloneCaster)] + void HandleAuraCloneCaster(AuraApplication aurApp, AuraEffectHandleModes mode, bool apply) + { + if (!mode.HasAnyFlag(AuraEffectHandleModes.SendForClientMask)) + return; + + Unit target = aurApp.GetTarget(); + + if (apply) + { + Unit caster = GetCaster(); + if (caster == null || caster == target) + return; + + // What must be cloned? at least display and scale + target.SetDisplayId(caster.GetDisplayId()); + //target.SetObjectScale(caster.GetFloatValue(OBJECT_FIELD_SCALE_X)); // we need retail info about how scaling is handled (aura maybe?) + target.SetFlag(UnitFields.Flags2, UnitFlags2.MirrorImage); + } + else + { + target.SetDisplayId(target.GetNativeDisplayId()); + target.RemoveFlag(UnitFields.Flags2, UnitFlags2.MirrorImage); + } + } + + /************************/ + /*** FIGHT ***/ + /************************/ + [AuraEffectHandler(AuraType.FeignDeath)] + void HandleFeignDeath(AuraApplication aurApp, AuraEffectHandleModes mode, bool apply) + { + if (!mode.HasAnyFlag(AuraEffectHandleModes.Real)) + return; + + Unit target = aurApp.GetTarget(); + + if (apply) + { + List targets = new List(); + var u_check = new AnyUnfriendlyUnitInObjectRangeCheck(target, target, target.GetMap().GetVisibilityRange()); + var searcher = new UnitListSearcher(target, targets, u_check); + + Cell.VisitAllObjects(target, searcher, target.GetMap().GetVisibilityRange()); + foreach (var unit in targets) + { + if (!unit.HasUnitState(UnitState.Casting)) + continue; + + for (var i = CurrentSpellTypes.Generic; i < CurrentSpellTypes.Max; i++) + { + if (unit.GetCurrentSpell(i) != null + && unit.GetCurrentSpell(i).m_targets.GetUnitTargetGUID() == target.GetGUID()) + { + unit.InterruptSpell(i, false); + } + } + } + target.CombatStop(); + target.RemoveAurasWithInterruptFlags(SpellAuraInterruptFlags.ImmuneOrLostSelection); + + // prevent interrupt message + if (GetCasterGUID() == target.GetGUID() && target.GetCurrentSpell(CurrentSpellTypes.Generic) != null) + target.FinishSpell(CurrentSpellTypes.Generic, false); + target.InterruptNonMeleeSpells(true); + target.getHostileRefManager().deleteReferences(); + + // stop handling the effect if it was removed by linked event + if (aurApp.HasRemoveMode()) + return; + // blizz like 2.0.x + target.SetFlag(UnitFields.Flags, UnitFlags.Unk29); + // blizz like 2.0.x + target.SetFlag(UnitFields.Flags2, UnitFlags2.FeignDeath); + // blizz like 2.0.x + target.SetFlag(ObjectFields.DynamicFlags, UnitDynFlags.Dead); + + target.AddUnitState(UnitState.Died); + } + else + { + // blizz like 2.0.x + target.RemoveFlag(UnitFields.Flags, UnitFlags.Unk29); + // blizz like 2.0.x + target.RemoveFlag(UnitFields.Flags2, UnitFlags2.FeignDeath); + // blizz like 2.0.x + target.RemoveFlag(ObjectFields.DynamicFlags, UnitDynFlags.Dead); + + target.ClearUnitState(UnitState.Died); + } + } + + [AuraEffectHandler(AuraType.ModUnattackable)] + void HandleModUnattackable(AuraApplication aurApp, AuraEffectHandleModes mode, bool apply) + { + if (!mode.HasAnyFlag(AuraEffectHandleModes.SendForClientMask)) + return; + + Unit target = aurApp.GetTarget(); + + // do not remove unit flag if there are more than this auraEffect of that kind on unit on unit + if (!apply && target.HasAuraType(AuraType.ModUnattackable)) + return; + + target.ApplyModFlag(UnitFields.Flags, UnitFlags.NonAttackable, apply); + + // call functions which may have additional effects after chainging state of unit + if (apply && mode.HasAnyFlag(AuraEffectHandleModes.Real)) + { + target.CombatStop(); + target.RemoveAurasWithInterruptFlags(SpellAuraInterruptFlags.ImmuneOrLostSelection); + } + } + + [AuraEffectHandler(AuraType.ModDisarm)] + void HandleAuraModDisarm(AuraApplication aurApp, AuraEffectHandleModes mode, bool apply) + { + if (!mode.HasAnyFlag(AuraEffectHandleModes.Real)) + return; + + Unit target = aurApp.GetTarget(); + + AuraType type = GetAuraType(); + + //Prevent handling aura twice + if ((apply) ? target.GetAuraEffectsByType(type).Count > 1 : target.HasAuraType(type)) + return; + UnitFields field; + uint flag, slot; + WeaponAttackType attType; + switch (type) + { + case AuraType.ModDisarm: + field = UnitFields.Flags; + flag = (uint)UnitFlags.Disarmed; + slot = EquipmentSlot.MainHand; + attType = WeaponAttackType.BaseAttack; + break; + case AuraType.ModDisarmOffhand: + field = UnitFields.Flags2; + flag = (uint)UnitFlags2.DisarmOffhand; + slot = EquipmentSlot.OffHand; + attType = WeaponAttackType.OffAttack; + break; + case AuraType.ModDisarmRanged: + field = UnitFields.Flags2; + flag = (uint)UnitFlags2.DisarmRanged; + slot = EquipmentSlot.MainHand; + attType = WeaponAttackType.RangedAttack; + break; + default: + return; + } + + // if disarm aura is to be removed, remove the flag first to reapply damage/aura mods + if (!apply) + target.RemoveFlag(field, flag); + + // Handle damage modification, shapeshifted druids are not affected + if (target.IsTypeId(TypeId.Player) && !target.IsInFeralForm()) + { + Player player = target.ToPlayer(); + + Item item = player.GetItemByPos(InventorySlots.Bag0, (byte)slot); + if (item != null) + { + WeaponAttackType attacktype = Player.GetAttackBySlot((byte)slot, item.GetTemplate().GetInventoryType()); + + player.ApplyItemDependentAuras(item, !apply); + if (attacktype < WeaponAttackType.Max) + player._ApplyWeaponDamage(slot, item, !apply); + } + } + + // if disarm effects should be applied, wait to set flag until damage mods are unapplied + if (apply) + target.SetFlag(field, flag); + + if (target.IsTypeId(TypeId.Unit) && target.ToCreature().GetCurrentEquipmentId() != 0) + target.UpdateDamagePhysical(attType); + } + + [AuraEffectHandler(AuraType.ModSilence)] + void HandleAuraModSilence(AuraApplication aurApp, AuraEffectHandleModes mode, bool apply) + { + if (!mode.HasAnyFlag(AuraEffectHandleModes.Real)) + return; + + Unit target = aurApp.GetTarget(); + + if (apply) + { + target.SetFlag(UnitFields.Flags, UnitFlags.Silenced); + + // call functions which may have additional effects after chainging state of unit + // Stop cast only spells vs PreventionType & SPELL_PREVENTION_TYPE_SILENCE + for (var i = CurrentSpellTypes.Melee; i < CurrentSpellTypes.Max; ++i) + { + Spell spell = target.GetCurrentSpell(i); + if (spell != null) + if (spell.m_spellInfo.PreventionType.HasAnyFlag(SpellPreventionType.Silence)) + // Stop spells on prepare or casting state + target.InterruptSpell(i, false); + } + } + else + { + // do not remove unit flag if there are more than this auraEffect of that kind on unit on unit + if (target.HasAuraType(AuraType.ModSilence) || target.HasAuraType(AuraType.ModPacifySilence)) + return; + + target.RemoveFlag(UnitFields.Flags, UnitFlags.Silenced); + } + } + + [AuraEffectHandler(AuraType.ModPacify)] + void HandleAuraModPacify(AuraApplication aurApp, AuraEffectHandleModes mode, bool apply) + { + if (!mode.HasAnyFlag(AuraEffectHandleModes.SendForClientMask)) + return; + + Unit target = aurApp.GetTarget(); + + if (apply) + target.SetFlag(UnitFields.Flags, UnitFlags.Pacified); + else + { + // do not remove unit flag if there are more than this auraEffect of that kind on unit on unit + if (target.HasAuraType(AuraType.ModPacify) || target.HasAuraType(AuraType.ModPacifySilence)) + return; + target.RemoveFlag(UnitFields.Flags, UnitFlags.Pacified); + } + } + + [AuraEffectHandler(AuraType.ModPacifySilence)] + void HandleAuraModPacifyAndSilence(AuraApplication aurApp, AuraEffectHandleModes mode, bool apply) + { + if (!mode.HasAnyFlag(AuraEffectHandleModes.SendForClientMask)) + return; + + Unit target = aurApp.GetTarget(); + + // Vengeance of the Blue Flight (@todo REMOVE THIS!) + /// @workaround + if (m_spellInfo.Id == 45839) + { + if (apply) + target.SetFlag(UnitFields.Flags, UnitFlags.NonAttackable); + else + target.RemoveFlag(UnitFields.Flags, UnitFlags.NonAttackable); + } + if (!(apply)) + { + // do not remove unit flag if there are more than this auraEffect of that kind on unit on unit + if (target.HasAuraType(AuraType.ModPacifySilence)) + return; + } + HandleAuraModPacify(aurApp, mode, apply); + HandleAuraModSilence(aurApp, mode, apply); + } + + [AuraEffectHandler(AuraType.AllowOnlyAbility)] + void HandleAuraAllowOnlyAbility(AuraApplication aurApp, AuraEffectHandleModes mode, bool apply) + { + if (!mode.HasAnyFlag(AuraEffectHandleModes.SendForClientMask)) + return; + + Unit target = aurApp.GetTarget(); + + if (target.IsTypeId(TypeId.Player)) + { + if (apply) + target.SetFlag(PlayerFields.Flags, PlayerFlags.AllowOnlyAbility); + else + { + // do not remove unit flag if there are more than this auraEffect of that kind on unit on unit + if (target.HasAuraType(AuraType.AllowOnlyAbility)) + return; + target.RemoveFlag(PlayerFields.Flags, PlayerFlags.AllowOnlyAbility); + } + } + } + + [AuraEffectHandler(AuraType.ModNoActions)] + void HandleAuraModNoActions(AuraApplication aurApp, AuraEffectHandleModes mode, bool apply) + { + if (!mode.HasAnyFlag(AuraEffectHandleModes.Real)) + return; + + Unit target = aurApp.GetTarget(); + + if (apply) + { + target.SetFlag(UnitFields.Flags2, UnitFlags2.NoActions); + + // call functions which may have additional effects after chainging state of unit + // Stop cast only spells vs PreventionType & SPELL_PREVENTION_TYPE_SILENCE + for (var i = CurrentSpellTypes.Melee; i < CurrentSpellTypes.Max; ++i) + { + Spell spell = target.GetCurrentSpell(i); + if (spell) + if (spell.m_spellInfo.PreventionType.HasAnyFlag(SpellPreventionType.NoActions)) + // Stop spells on prepare or casting state + target.InterruptSpell(i, false); + } + } + else + { + // do not remove unit flag if there are more than this auraEffect of that kind on unit on unit + if (target.HasAuraType(AuraType.ModNoActions)) + return; + + target.RemoveFlag(UnitFields.Flags2, UnitFlags2.NoActions); + } + } + + /****************************/ + /*** TRACKING ***/ + /****************************/ + [AuraEffectHandler(AuraType.TrackCreatures)] + void HandleAuraTrackCreatures(AuraApplication aurApp, AuraEffectHandleModes mode, bool apply) + { + if (!mode.HasAnyFlag(AuraEffectHandleModes.SendForClientMask)) + return; + + Unit target = aurApp.GetTarget(); + + if (!target.IsTypeId(TypeId.Player)) + return; + + if (apply) + target.SetFlag(PlayerFields.TrackCreatures, 1 << (GetMiscValue() - 1)); + else + target.RemoveFlag(PlayerFields.TrackCreatures, 1 << (GetMiscValue() - 1)); + } + + [AuraEffectHandler(AuraType.TrackResources)] + void HandleAuraTrackResources(AuraApplication aurApp, AuraEffectHandleModes mode, bool apply) + { + if (!mode.HasAnyFlag(AuraEffectHandleModes.SendForClientMask)) + return; + + Unit target = aurApp.GetTarget(); + + if (!target.IsTypeId(TypeId.Player)) + return; + + if (apply) + target.SetFlag(PlayerFields.TrackResources, 1 << (GetMiscValue() - 1)); + else + target.RemoveFlag(PlayerFields.TrackResources, 1 << (GetMiscValue() - 1)); + } + + [AuraEffectHandler(AuraType.TrackStealthed)] + void HandleAuraTrackStealthed(AuraApplication aurApp, AuraEffectHandleModes mode, bool apply) + { + if (!mode.HasAnyFlag(AuraEffectHandleModes.SendForClientMask)) + return; + + Unit target = aurApp.GetTarget(); + + if (!target.IsTypeId(TypeId.Player)) + return; + + if (!(apply)) + { + // do not remove unit flag if there are more than this auraEffect of that kind on unit on unit + if (target.HasAuraType(GetAuraType())) + return; + } + target.ApplyModFlag(PlayerFields.LocalFlags, PlayerLocalFlags.TrackStealthed, apply); + } + + [AuraEffectHandler(AuraType.ModStalked)] + void HandleAuraModStalked(AuraApplication aurApp, AuraEffectHandleModes mode, bool apply) + { + if (!mode.HasAnyFlag(AuraEffectHandleModes.SendForClientMask)) + return; + + Unit target = aurApp.GetTarget(); + + // used by spells: Hunter's Mark, Mind Vision, Syndicate Tracker (MURP) DND + if (apply) + target.SetFlag(ObjectFields.DynamicFlags, UnitDynFlags.TrackUnit); + else + { + // do not remove unit flag if there are more than this auraEffect of that kind on unit on unit + if (!target.HasAuraType(GetAuraType())) + target.RemoveFlag(ObjectFields.DynamicFlags, UnitDynFlags.TrackUnit); + } + + // call functions which may have additional effects after chainging state of unit + target.UpdateObjectVisibility(); + } + + [AuraEffectHandler(AuraType.Untrackable)] + void HandleAuraUntrackable(AuraApplication aurApp, AuraEffectHandleModes mode, bool apply) + { + if (!mode.HasAnyFlag(AuraEffectHandleModes.SendForClientMask)) + return; + + Unit target = aurApp.GetTarget(); + + if (apply) + target.SetByteFlag(UnitFields.Bytes1, UnitBytes1Offsets.VisFlag, UnitStandFlags.Untrackable); + else + { + // do not remove unit flag if there are more than this auraEffect of that kind on unit on unit + if (target.HasAuraType(GetAuraType())) + return; + target.RemoveByteFlag(UnitFields.Bytes1, UnitBytes1Offsets.VisFlag, UnitStandFlags.Untrackable); + } + } + + /****************************/ + /*** SKILLS & TALENTS ***/ + /****************************/ + [AuraEffectHandler(AuraType.ModSkill)] + [AuraEffectHandler(AuraType.ModSkill2)] + void HandleAuraModSkill(AuraApplication aurApp, AuraEffectHandleModes mode, bool apply) + { + if (!mode.HasAnyFlag((AuraEffectHandleModes.ChangeAmountMask | AuraEffectHandleModes.Skill))) + return; + + Player target = aurApp.GetTarget().ToPlayer(); + if (target == null) + return; + + SkillType prot = (SkillType)GetMiscValue(); + int points = GetAmount(); + + if (prot == SkillType.Defense) + return; + + target.ModifySkillBonus(prot, (apply ? points : -points), GetAuraType() == AuraType.ModSkillTalent); + } + + /****************************/ + /*** MOVEMENT ***/ + /****************************/ + [AuraEffectHandler(AuraType.Mounted)] + void HandleAuraMounted(AuraApplication aurApp, AuraEffectHandleModes mode, bool apply) + { + if (!mode.HasAnyFlag(AuraEffectHandleModes.SendForClientMask)) + return; + + Unit target = aurApp.GetTarget(); + + if (apply) + { + uint creatureEntry = (uint)GetMiscValue(); + uint displayId = 0; + uint vehicleId = 0; + + MountRecord mountEntry = Global.DB2Mgr.GetMount(GetId()); + if (mountEntry != null) + { + var mountDisplays = Global.DB2Mgr.GetMountDisplays(mountEntry.Id); + if (mountDisplays != null) + { + List usableDisplays = mountDisplays.Where(mountDisplay => + { + Player playerTarget = target.ToPlayer(); + if (playerTarget) + { + PlayerConditionRecord playerCondition = CliDB.PlayerConditionStorage.LookupByKey(mountDisplay.PlayerConditionID); + if (playerCondition != null) + return ConditionManager.IsPlayerMeetingCondition(playerTarget, playerCondition); + } + + return true; + }).ToList(); + + if (!usableDisplays.Empty()) + displayId = usableDisplays.PickRandom().DisplayID; + } + // TODO: CREATE TABLE mount_vehicle (mountId, vehicleCreatureId) for future mounts that are vehicles (new mounts no longer have proper data in MiscValue) + //if (MountVehicle const* mountVehicle = sObjectMgr->GetMountVehicle(mountEntry->Id)) + // creatureEntry = mountVehicle->VehicleCreatureId; + } + + CreatureTemplate creatureInfo = Global.ObjectMgr.GetCreatureTemplate(creatureEntry); + if (creatureInfo != null) + { + vehicleId = creatureInfo.VehicleId; + + if (displayId == 0 || vehicleId != 0) + { + displayId = ObjectManager.ChooseDisplayId(creatureInfo); + Global.ObjectMgr.GetCreatureModelRandomGender(ref displayId); + } + + //some spell has one aura of mount and one of vehicle + foreach (SpellEffectInfo effect in GetBase().GetSpellEffectInfos()) + if (effect != null && GetSpellEffectInfo().Effect == SpellEffectName.Summon && effect.MiscValue == GetMiscValue()) + displayId = 0; + } + + target.Mount(displayId, vehicleId, creatureEntry); + + // cast speed aura + if (mode.HasAnyFlag(AuraEffectHandleModes.Real)) + { + var mountCapability = CliDB.MountCapabilityStorage.LookupByKey(GetAmount()); + if (mountCapability != null) + target.CastSpell(target, mountCapability.SpeedModSpell, true); + } + } + else + { + target.Dismount(); + //some mounts like Headless Horseman's Mount or broom stick are skill based spell + // need to remove ALL arura related to mounts, this will stop client crash with broom stick + // and never endless flying after using Headless Horseman's Mount + if (mode.HasAnyFlag(AuraEffectHandleModes.Real)) + { + target.RemoveAurasByType(AuraType.Mounted); + + // remove speed aura + var mountCapability = CliDB.MountCapabilityStorage.LookupByKey(GetAmount()); + if (mountCapability != null) + target.RemoveAurasDueToSpell(mountCapability.SpeedModSpell, target.GetGUID()); + } + } + } + + [AuraEffectHandler(AuraType.Fly)] + void HandleAuraAllowFlight(AuraApplication aurApp, AuraEffectHandleModes mode, bool apply) + { + if (!mode.HasAnyFlag(AuraEffectHandleModes.SendForClientMask)) + return; + + Unit target = aurApp.GetTarget(); + + if (!apply) + { + // do not remove unit flag if there are more than this auraEffect of that kind on unit on unit + if (target.HasAuraType(GetAuraType()) || target.HasAuraType(AuraType.ModIncreaseMountedFlightSpeed)) + return; + } + + if (target.SetCanFly(apply)) + if (!apply && !target.IsLevitating()) + target.GetMotionMaster().MoveFall(); + } + + [AuraEffectHandler(AuraType.WaterWalk)] + void HandleAuraWaterWalk(AuraApplication aurApp, AuraEffectHandleModes mode, bool apply) + { + if (!mode.HasAnyFlag(AuraEffectHandleModes.SendForClientMask)) + return; + + Unit target = aurApp.GetTarget(); + + if (!apply) + { + // do not remove unit flag if there are more than this auraEffect of that kind on unit on unit + if (target.HasAuraType(GetAuraType())) + return; + } + + target.SetWaterWalking(apply); + } + + [AuraEffectHandler(AuraType.FeatherFall)] + void HandleAuraFeatherFall(AuraApplication aurApp, AuraEffectHandleModes mode, bool apply) + { + if (!mode.HasAnyFlag(AuraEffectHandleModes.SendForClientMask)) + return; + + Unit target = aurApp.GetTarget(); + + if (!apply) + { + // do not remove unit flag if there are more than this auraEffect of that kind on unit on unit + if (target.HasAuraType(GetAuraType())) + return; + } + + target.SetFeatherFall(apply); + + // start fall from current height + if (!apply && target.IsTypeId(TypeId.Player)) + target.ToPlayer().SetFallInformation(0, target.GetPositionZ()); + } + + [AuraEffectHandler(AuraType.Hover)] + void HandleAuraHover(AuraApplication aurApp, AuraEffectHandleModes mode, bool apply) + { + if (!mode.HasAnyFlag(AuraEffectHandleModes.SendForClientMask)) + return; + + Unit target = aurApp.GetTarget(); + + if (!apply) + { + // do not remove unit flag if there are more than this auraEffect of that kind on unit on unit + if (target.HasAuraType(GetAuraType())) + return; + } + + target.SetHover(apply); //! Sets movementflags + } + + [AuraEffectHandler(AuraType.WaterBreathing)] + void HandleWaterBreathing(AuraApplication aurApp, AuraEffectHandleModes mode, bool apply) + { + if (!mode.HasAnyFlag(AuraEffectHandleModes.SendForClientMask)) + return; + + Unit target = aurApp.GetTarget(); + + // update timers in client + if (target.IsTypeId(TypeId.Player)) + target.ToPlayer().UpdateMirrorTimers(); + } + + [AuraEffectHandler(AuraType.ForceMoveForward)] + void HandleForceMoveForward(AuraApplication aurApp, AuraEffectHandleModes mode, bool apply) + { + if (!mode.HasAnyFlag(AuraEffectHandleModes.SendForClientMask)) + return; + + Unit target = aurApp.GetTarget(); + + if (apply) + target.SetFlag(UnitFields.Flags2, UnitFlags2.ForceMove); + else + { + // do not remove unit flag if there are more than this auraEffect of that kind on unit on unit + if (target.HasAuraType(GetAuraType())) + return; + target.RemoveFlag(UnitFields.Flags2, UnitFlags2.ForceMove); + } + } + + [AuraEffectHandler(AuraType.CanTurnWhileFalling)] + void HandleAuraCanTurnWhileFalling(AuraApplication aurApp, AuraEffectHandleModes mode, bool apply) + { + if (!mode.HasAnyFlag(AuraEffectHandleModes.SendForClientMask)) + return; + + Unit target = aurApp.GetTarget(); + + if (!apply) + { + // do not remove unit flag if there are more than this auraEffect of that kind on unit on unit + if (target.HasAuraType(GetAuraType())) + return; + } + + target.SetCanTurnWhileFalling(apply); + } + + /****************************/ + /*** THREAT ***/ + /****************************/ + [AuraEffectHandler(AuraType.ModThreat)] + void HandleModThreat(AuraApplication aurApp, AuraEffectHandleModes mode, bool apply) + { + if (!mode.HasAnyFlag(AuraEffectHandleModes.ChangeAmountMask)) + return; + + Unit target = aurApp.GetTarget(); + for (byte i = 0; i < (int)SpellSchools.Max; ++i) + if (Convert.ToBoolean(GetMiscValue() & (1 << i))) + MathFunctions.ApplyPercentModFloatVar(ref target.m_threatModifier[i], GetAmount(), apply); + } + + [AuraEffectHandler(AuraType.ModTotalThreat)] + void HandleAuraModTotalThreat(AuraApplication aurApp, AuraEffectHandleModes mode, bool apply) + { + if (!mode.HasAnyFlag(AuraEffectHandleModes.ChangeAmountMask)) + return; + + Unit target = aurApp.GetTarget(); + + if (!target.IsAlive() || !target.IsTypeId(TypeId.Player)) + return; + + Unit caster = GetCaster(); + if (caster != null && caster.IsAlive()) + target.getHostileRefManager().addTempThreat(GetAmount(), apply); + } + + [AuraEffectHandler(AuraType.ModTaunt)] + void HandleModTaunt(AuraApplication aurApp, AuraEffectHandleModes mode, bool apply) + { + if (!mode.HasAnyFlag(AuraEffectHandleModes.Real)) + return; + + Unit target = aurApp.GetTarget(); + + if (!target.IsAlive() || !target.CanHaveThreatList()) + return; + + Unit caster = GetCaster(); + if (caster == null || !caster.IsAlive()) + return; + + if (apply) + target.TauntApply(caster); + else + { + // When taunt aura fades out, mob will switch to previous target if current has less than 1.1 * secondthreat + target.TauntFadeOut(caster); + } + } + + /*****************************/ + /*** CONTROL ***/ + /*****************************/ + [AuraEffectHandler(AuraType.ModConfuse)] + void HandleModConfuse(AuraApplication aurApp, AuraEffectHandleModes mode, bool apply) + { + if (!mode.HasAnyFlag(AuraEffectHandleModes.Real)) + return; + + Unit target = aurApp.GetTarget(); + + target.SetControlled(apply, UnitState.Confused); + } + + [AuraEffectHandler(AuraType.ModFear)] + void HandleModFear(AuraApplication aurApp, AuraEffectHandleModes mode, bool apply) + { + if (!mode.HasAnyFlag(AuraEffectHandleModes.Real)) + return; + + Unit target = aurApp.GetTarget(); + + target.SetControlled(apply, UnitState.Fleeing); + } + + [AuraEffectHandler(AuraType.ModStun)] + void HandleAuraModStun(AuraApplication aurApp, AuraEffectHandleModes mode, bool apply) + { + if (!mode.HasAnyFlag(AuraEffectHandleModes.Real)) + return; + + Unit target = aurApp.GetTarget(); + + target.SetControlled(apply, UnitState.Stunned); + } + + [AuraEffectHandler(AuraType.ModRoot)] + [AuraEffectHandler(AuraType.ModRoot2)] + void HandleAuraModRoot(AuraApplication aurApp, AuraEffectHandleModes mode, bool apply) + { + if (!mode.HasAnyFlag(AuraEffectHandleModes.Real)) + return; + + Unit target = aurApp.GetTarget(); + + target.SetControlled(apply, UnitState.Root); + } + + [AuraEffectHandler(AuraType.PreventsFleeing)] + void HandlePreventFleeing(AuraApplication aurApp, AuraEffectHandleModes mode, bool apply) + { + if (!mode.HasAnyFlag(AuraEffectHandleModes.Real)) + return; + + Unit target = aurApp.GetTarget(); + + // Since patch 3.0.2 this mechanic no longer affects fear effects. It will ONLY prevent humanoids from fleeing due to low health. + if (!apply || target.HasAuraType(AuraType.ModFear)) + return; + // TODO: find a way to cancel fleeing for assistance. + // Currently this will only stop creatures fleeing due to low health that could not find nearby allies to flee towards. + target.SetControlled(false, UnitState.Fleeing); + } + + /***************************/ + /*** CHARM ***/ + /***************************/ + [AuraEffectHandler(AuraType.ModPossess)] + void HandleModPossess(AuraApplication aurApp, AuraEffectHandleModes mode, bool apply) + { + if (!mode.HasAnyFlag(AuraEffectHandleModes.Real)) + return; + + Unit target = aurApp.GetTarget(); + + Unit caster = GetCaster(); + + // no support for posession AI yet + if (caster != null && caster.IsTypeId(TypeId.Unit)) + { + HandleModCharm(aurApp, mode, apply); + return; + } + + if (apply) + target.SetCharmedBy(caster, CharmType.Possess, aurApp); + else + target.RemoveCharmedBy(caster); + } + + [AuraEffectHandler(AuraType.ModPossessPet)] + void HandleModPossessPet(AuraApplication aurApp, AuraEffectHandleModes mode, bool apply) + { + // Used by spell "Eyes of the Beast" + + if (!mode.HasAnyFlag(AuraEffectHandleModes.Real)) + return; + + Unit caster = GetCaster(); + if (caster == null || !caster.IsTypeId(TypeId.Player)) + return; + + Unit target = aurApp.GetTarget(); + if (!target.IsTypeId(TypeId.Unit) || !target.IsPet()) + return; + + Pet pet = target.ToPet(); + + if (apply) + { + if (caster.ToPlayer().GetPet() != pet) + return; + + // Must clear current motion or pet leashes back to owner after a few yards + // when under spell 'Eyes of the Beast' + pet.GetMotionMaster().Clear(); + pet.SetCharmedBy(caster, CharmType.Possess, aurApp); + } + else + { + pet.RemoveCharmedBy(caster); + + if (!pet.IsWithinDistInMap(caster, pet.GetMap().GetVisibilityRange())) + pet.Remove(PetSaveMode.NotInSlot, true); + else + { + // Reinitialize the pet bar or it will appear greyed out + caster.ToPlayer().PetSpellInitialize(); + + // Follow owner only if not fighting or owner didn't click "stay" at new location + // This may be confusing because pet bar shows "stay" when under the spell but it retains + // the "follow" flag. Player MUST click "stay" while under the spell. + if (pet.GetVictim() == null && !pet.GetCharmInfo().HasCommandState(CommandStates.Stay)) + { + pet.GetMotionMaster().MoveFollow(caster, SharedConst.PetFollowDist, pet.GetFollowAngle()); + } + } + } + } + + [AuraEffectHandler(AuraType.ModCharm)] + void HandleModCharm(AuraApplication aurApp, AuraEffectHandleModes mode, bool apply) + { + if (!mode.HasAnyFlag(AuraEffectHandleModes.Real)) + return; + + Unit target = aurApp.GetTarget(); + + Unit caster = GetCaster(); + + if (apply) + target.SetCharmedBy(caster, CharmType.Charm, aurApp); + else + target.RemoveCharmedBy(caster); + } + + [AuraEffectHandler(AuraType.AoeCharm)] + void HandleCharmConvert(AuraApplication aurApp, AuraEffectHandleModes mode, bool apply) + { + if (!mode.HasAnyFlag(AuraEffectHandleModes.Real)) + return; + + Unit target = aurApp.GetTarget(); + + Unit caster = GetCaster(); + + if (apply) + target.SetCharmedBy(caster, CharmType.Convert, aurApp); + else + target.RemoveCharmedBy(caster); + } + + /** + * Such auras are applied from a caster(=player) to a vehicle. + * This has been verified using spell #49256 + */ + [AuraEffectHandler(AuraType.ControlVehicle)] + void HandleAuraControlVehicle(AuraApplication aurApp, AuraEffectHandleModes mode, bool apply) + { + if (!mode.HasAnyFlag(AuraEffectHandleModes.ChangeAmountMask)) + return; + + Unit target = aurApp.GetTarget(); + if (!target.IsVehicle()) + return; + + Unit caster = GetCaster(); + if (caster == null || caster == target) + return; + + if (apply) + { + // Currently spells that have base points 0 and DieSides 0 = "0/0" exception are pushed to -1, + // however the idea of 0/0 is to ingore flag VEHICLE_SEAT_FLAG_CAN_ENTER_OR_EXIT and -1 checks for it, + // so this break such spells or most of them. + // Current formula about m_amount: effect base points + dieside - 1 + // TO DO: Reasearch more about 0/0 and fix it. + caster._EnterVehicle(target.GetVehicleKit(), (sbyte)(m_amount - 1), aurApp); + } + else + { + // Remove pending passengers before exiting vehicle - might cause an Uninstall + target.GetVehicleKit().RemovePendingEventsForPassenger(caster); + + if (GetId() == 53111) // Devour Humanoid + { + target.Kill(caster); + if (caster.IsTypeId(TypeId.Unit)) + caster.ToCreature().RemoveCorpse(); + } + + if (!mode.HasAnyFlag(AuraEffectHandleModes.ChangeAmount)) + caster._ExitVehicle(); + else + target.GetVehicleKit().RemovePassenger(caster); // Only remove passenger from vehicle without launching exit movement or despawning the vehicle + + // some SPELL_AURA_CONTROL_VEHICLE auras have a dummy effect on the player - remove them + caster.RemoveAurasDueToSpell(GetId()); + } + } + + /*********************************************************/ + /*** MODIFY SPEED ***/ + /*********************************************************/ + [AuraEffectHandler(AuraType.ModIncreaseSpeed)] + [AuraEffectHandler(AuraType.ModSpeedAlways)] + [AuraEffectHandler(AuraType.ModSpeedNotStack)] + [AuraEffectHandler(AuraType.ModMinimumSpeed)] + void HandleAuraModIncreaseSpeed(AuraApplication aurApp, AuraEffectHandleModes mode, bool apply) + { + if (!mode.HasAnyFlag(AuraEffectHandleModes.ChangeAmountMask)) + return; + + Unit target = aurApp.GetTarget(); + + target.UpdateSpeed(UnitMoveType.Run); + } + + [AuraEffectHandler(AuraType.ModIncreaseMountedSpeed)] + [AuraEffectHandler(AuraType.ModMountedSpeedAlways)] + [AuraEffectHandler(AuraType.ModMountedSpeedNotStack)] + void HandleAuraModIncreaseMountedSpeed(AuraApplication aurApp, AuraEffectHandleModes mode, bool apply) + { + HandleAuraModIncreaseSpeed(aurApp, mode, apply); + } + + [AuraEffectHandler(AuraType.ModIncreaseVehicleFlightSpeed)] + [AuraEffectHandler(AuraType.ModIncreaseMountedFlightSpeed)] + [AuraEffectHandler(AuraType.ModIncreaseFlightSpeed)] + [AuraEffectHandler(AuraType.ModMountedFlightSpeedAlways)] + [AuraEffectHandler(AuraType.ModVehicleSpeedAlways)] + [AuraEffectHandler(AuraType.ModFlightSpeedNotStack)] + void HandleAuraModIncreaseFlightSpeed(AuraApplication aurApp, AuraEffectHandleModes mode, bool apply) + { + if (!mode.HasAnyFlag(AuraEffectHandleModes.ChangeAmountSendForClientMask)) + return; + + Unit target = aurApp.GetTarget(); + if (mode.HasAnyFlag(AuraEffectHandleModes.ChangeAmountMask)) + target.UpdateSpeed(UnitMoveType.Flight); + + //! Update ability to fly + if (GetAuraType() == AuraType.ModIncreaseMountedFlightSpeed) + { + // do not remove unit flag if there are more than this auraEffect of that kind on unit on unit + if (mode.HasAnyFlag(AuraEffectHandleModes.SendForClientMask) && (apply || (!target.HasAuraType(AuraType.ModIncreaseMountedFlightSpeed) && !target.HasAuraType(AuraType.Fly)))) + { + if (target.SetCanFly(apply)) + if (!apply && !target.IsLevitating()) + target.GetMotionMaster().MoveFall(); + } + + //! Someone should clean up these hacks and remove it from this function. It doesn't even belong here. + if (mode.HasAnyFlag(AuraEffectHandleModes.Real)) + { + //Players on flying mounts must be immune to polymorph + if (target.IsTypeId(TypeId.Player)) + target.ApplySpellImmune(GetId(), SpellImmunity.Mechanic, Mechanics.Polymorph, apply); + + // Dragonmaw Illusion (overwrite mount model, mounted aura already applied) + if (apply && target.HasAuraEffect(42016, 0) && target.GetMountID() != 0) + target.SetUInt32Value(UnitFields.MountDisplayId, 16314); + } + } + } + + [AuraEffectHandler(AuraType.ModIncreaseSwimSpeed)] + void HandleAuraModIncreaseSwimSpeed(AuraApplication aurApp, AuraEffectHandleModes mode, bool apply) + { + if (!mode.HasAnyFlag(AuraEffectHandleModes.ChangeAmountMask)) + return; + + Unit target = aurApp.GetTarget(); + + target.UpdateSpeed(UnitMoveType.Swim); + } + + [AuraEffectHandler(AuraType.ModDecreaseSpeed)] + void HandleAuraModDecreaseSpeed(AuraApplication aurApp, AuraEffectHandleModes mode, bool apply) + { + if (!mode.HasAnyFlag(AuraEffectHandleModes.ChangeAmountMask)) + return; + + Unit target = aurApp.GetTarget(); + + target.UpdateSpeed(UnitMoveType.Run); + target.UpdateSpeed(UnitMoveType.Swim); + target.UpdateSpeed(UnitMoveType.Flight); + target.UpdateSpeed(UnitMoveType.RunBack); + target.UpdateSpeed(UnitMoveType.SwimBack); + target.UpdateSpeed(UnitMoveType.FlightBack); + } + + [AuraEffectHandler(AuraType.UseNormalMovementSpeed)] + void HandleAuraModUseNormalSpeed(AuraApplication aurApp, AuraEffectHandleModes mode, bool apply) + { + if (!mode.HasAnyFlag(AuraEffectHandleModes.Real)) + return; + + Unit target = aurApp.GetTarget(); + + target.UpdateSpeed(UnitMoveType.Run); + target.UpdateSpeed(UnitMoveType.Swim); + target.UpdateSpeed(UnitMoveType.Flight); + } + + [AuraEffectHandler(AuraType.ModMinimumSpeedRate)] + void HandleAuraModMinimumSpeedRate(AuraApplication aurApp, AuraEffectHandleModes mode, bool apply) + { + if (!mode.HasAnyFlag(AuraEffectHandleModes.Real)) + return; + + Unit target = aurApp.GetTarget(); + + target.UpdateSpeed(UnitMoveType.Run); + } + + /*********************************************************/ + /*** IMMUNITY ***/ + /*********************************************************/ + [AuraEffectHandler(AuraType.MechanicImmunityMask)] + void HandleModStateImmunityMask(AuraApplication aurApp, AuraEffectHandleModes mode, bool apply) + { + if (!mode.HasAnyFlag(AuraEffectHandleModes.Real)) + return; + + Unit target = aurApp.GetTarget(); + List aura_immunity_list = new List(); + + uint mechanic_immunity_list = (1 << (int)Mechanics.Snare) | (1 << (int)Mechanics.Root) + | (1 << (int)Mechanics.Fear) | (1 << (int)Mechanics.Stun) + | (1 << (int)Mechanics.Sleep) | (1 << (int)Mechanics.Charm) + | (1 << (int)Mechanics.Sapped) | (1 << (int)Mechanics.Horror) + | (1 << (int)Mechanics.Polymorph) | (1 << (int)Mechanics.Disoriented) + | (1 << (int)Mechanics.Freeze) | (1 << (int)Mechanics.Turn); + + int miscVal = GetMiscValue(); + switch (miscVal) + { + case 27: + target.ApplySpellImmune(GetId(), SpellImmunity.Mechanic, Mechanics.Silence, apply); + aura_immunity_list.Add(AuraType.ModSilence); + break; + case 96: + case 1615: + { + if (HasAmount()) + { + target.ApplySpellImmune(GetId(), SpellImmunity.Mechanic, Mechanics.Snare, apply); + target.ApplySpellImmune(GetId(), SpellImmunity.Mechanic, Mechanics.Root, apply); + target.ApplySpellImmune(GetId(), SpellImmunity.Mechanic, Mechanics.Fear, apply); + target.ApplySpellImmune(GetId(), SpellImmunity.Mechanic, Mechanics.Stun, apply); + target.ApplySpellImmune(GetId(), SpellImmunity.Mechanic, Mechanics.Sleep, apply); + target.ApplySpellImmune(GetId(), SpellImmunity.Mechanic, Mechanics.Charm, apply); + target.ApplySpellImmune(GetId(), SpellImmunity.Mechanic, Mechanics.Sapped, apply); + target.ApplySpellImmune(GetId(), SpellImmunity.Mechanic, Mechanics.Horror, apply); + target.ApplySpellImmune(GetId(), SpellImmunity.Mechanic, Mechanics.Polymorph, apply); + target.ApplySpellImmune(GetId(), SpellImmunity.Mechanic, Mechanics.Disoriented, apply); + target.ApplySpellImmune(GetId(), SpellImmunity.Mechanic, Mechanics.Freeze, apply); + target.ApplySpellImmune(GetId(), SpellImmunity.Mechanic, Mechanics.Turn, apply); + aura_immunity_list.Add(AuraType.ModStun); + aura_immunity_list.Add(AuraType.ModDecreaseSpeed); + aura_immunity_list.Add(AuraType.ModRoot); + aura_immunity_list.Add(AuraType.ModConfuse); + aura_immunity_list.Add(AuraType.ModFear); + aura_immunity_list.Add(AuraType.ModRoot2); + } + break; + } + case 679: + { + if (GetId() == 57742) + { + target.ApplySpellImmune(GetId(), SpellImmunity.Mechanic, Mechanics.Snare, apply); + target.ApplySpellImmune(GetId(), SpellImmunity.Mechanic, Mechanics.Root, apply); + target.ApplySpellImmune(GetId(), SpellImmunity.Mechanic, Mechanics.Fear, apply); + target.ApplySpellImmune(GetId(), SpellImmunity.Mechanic, Mechanics.Stun, apply); + target.ApplySpellImmune(GetId(), SpellImmunity.Mechanic, Mechanics.Sleep, apply); + target.ApplySpellImmune(GetId(), SpellImmunity.Mechanic, Mechanics.Charm, apply); + target.ApplySpellImmune(GetId(), SpellImmunity.Mechanic, Mechanics.Sapped, apply); + target.ApplySpellImmune(GetId(), SpellImmunity.Mechanic, Mechanics.Horror, apply); + target.ApplySpellImmune(GetId(), SpellImmunity.Mechanic, Mechanics.Polymorph, apply); + target.ApplySpellImmune(GetId(), SpellImmunity.Mechanic, Mechanics.Disoriented, apply); + target.ApplySpellImmune(GetId(), SpellImmunity.Mechanic, Mechanics.Freeze, apply); + target.ApplySpellImmune(GetId(), SpellImmunity.Mechanic, Mechanics.Turn, apply); + aura_immunity_list.Add(AuraType.ModStun); + aura_immunity_list.Add(AuraType.ModDecreaseSpeed); + aura_immunity_list.Add(AuraType.ModRoot); + aura_immunity_list.Add(AuraType.ModConfuse); + aura_immunity_list.Add(AuraType.ModFear); + aura_immunity_list.Add(AuraType.ModRoot2); + } + break; + } + case 1557: + { + if (GetId() == 64187) + { + mechanic_immunity_list = (1 << (int)Mechanics.Stun); + target.ApplySpellImmune(GetId(), SpellImmunity.Mechanic, Mechanics.Stun, apply); + aura_immunity_list.Add(AuraType.ModStun); + } + else + { + target.ApplySpellImmune(GetId(), SpellImmunity.Mechanic, Mechanics.Snare, apply); + target.ApplySpellImmune(GetId(), SpellImmunity.Mechanic, Mechanics.Root, apply); + target.ApplySpellImmune(GetId(), SpellImmunity.Mechanic, Mechanics.Fear, apply); + target.ApplySpellImmune(GetId(), SpellImmunity.Mechanic, Mechanics.Stun, apply); + target.ApplySpellImmune(GetId(), SpellImmunity.Mechanic, Mechanics.Sleep, apply); + target.ApplySpellImmune(GetId(), SpellImmunity.Mechanic, Mechanics.Charm, apply); + target.ApplySpellImmune(GetId(), SpellImmunity.Mechanic, Mechanics.Sapped, apply); + target.ApplySpellImmune(GetId(), SpellImmunity.Mechanic, Mechanics.Horror, apply); + target.ApplySpellImmune(GetId(), SpellImmunity.Mechanic, Mechanics.Polymorph, apply); + target.ApplySpellImmune(GetId(), SpellImmunity.Mechanic, Mechanics.Disoriented, apply); + target.ApplySpellImmune(GetId(), SpellImmunity.Mechanic, Mechanics.Freeze, apply); + target.ApplySpellImmune(GetId(), SpellImmunity.Mechanic, Mechanics.Turn, apply); + aura_immunity_list.Add(AuraType.ModStun); + aura_immunity_list.Add(AuraType.ModDecreaseSpeed); + aura_immunity_list.Add(AuraType.ModRoot); + aura_immunity_list.Add(AuraType.ModConfuse); + aura_immunity_list.Add(AuraType.ModFear); + aura_immunity_list.Add(AuraType.ModRoot2); + } + break; + } + case 1614: + case 1694: + { + target.ApplySpellImmune(GetId(), SpellImmunity.Effect, SpellEffectName.AttackMe, apply); + aura_immunity_list.Add(AuraType.ModTaunt); + break; + } + case 1630: + { + if (!HasAmount()) + { + target.ApplySpellImmune(GetId(), SpellImmunity.Effect, SpellEffectName.AttackMe, apply); + aura_immunity_list.Add(AuraType.ModTaunt); + } + else + { + target.ApplySpellImmune(GetId(), SpellImmunity.Mechanic, Mechanics.Snare, apply); + target.ApplySpellImmune(GetId(), SpellImmunity.Mechanic, Mechanics.Root, apply); + target.ApplySpellImmune(GetId(), SpellImmunity.Mechanic, Mechanics.Fear, apply); + target.ApplySpellImmune(GetId(), SpellImmunity.Mechanic, Mechanics.Stun, apply); + target.ApplySpellImmune(GetId(), SpellImmunity.Mechanic, Mechanics.Sleep, apply); + target.ApplySpellImmune(GetId(), SpellImmunity.Mechanic, Mechanics.Charm, apply); + target.ApplySpellImmune(GetId(), SpellImmunity.Mechanic, Mechanics.Sapped, apply); + target.ApplySpellImmune(GetId(), SpellImmunity.Mechanic, Mechanics.Horror, apply); + target.ApplySpellImmune(GetId(), SpellImmunity.Mechanic, Mechanics.Polymorph, apply); + target.ApplySpellImmune(GetId(), SpellImmunity.Mechanic, Mechanics.Disoriented, apply); + target.ApplySpellImmune(GetId(), SpellImmunity.Mechanic, Mechanics.Freeze, apply); + target.ApplySpellImmune(GetId(), SpellImmunity.Mechanic, Mechanics.Turn, apply); + aura_immunity_list.Add(AuraType.ModStun); + aura_immunity_list.Add(AuraType.ModDecreaseSpeed); + aura_immunity_list.Add(AuraType.ModRoot); + aura_immunity_list.Add(AuraType.ModConfuse); + aura_immunity_list.Add(AuraType.ModFear); + aura_immunity_list.Add(AuraType.ModRoot2); + } + break; + } + case 477: + case 1733: + { + if (!HasAmount()) + { + target.ApplySpellImmune(GetId(), SpellImmunity.Mechanic, Mechanics.Snare, apply); + target.ApplySpellImmune(GetId(), SpellImmunity.Mechanic, Mechanics.Root, apply); + target.ApplySpellImmune(GetId(), SpellImmunity.Mechanic, Mechanics.Fear, apply); + target.ApplySpellImmune(GetId(), SpellImmunity.Mechanic, Mechanics.Stun, apply); + target.ApplySpellImmune(GetId(), SpellImmunity.Mechanic, Mechanics.Sleep, apply); + target.ApplySpellImmune(GetId(), SpellImmunity.Mechanic, Mechanics.Charm, apply); + target.ApplySpellImmune(GetId(), SpellImmunity.Mechanic, Mechanics.Sapped, apply); + target.ApplySpellImmune(GetId(), SpellImmunity.Mechanic, Mechanics.Horror, apply); + target.ApplySpellImmune(GetId(), SpellImmunity.Mechanic, Mechanics.Polymorph, apply); + target.ApplySpellImmune(GetId(), SpellImmunity.Mechanic, Mechanics.Disoriented, apply); + target.ApplySpellImmune(GetId(), SpellImmunity.Mechanic, Mechanics.Freeze, apply); + target.ApplySpellImmune(GetId(), SpellImmunity.Mechanic, Mechanics.Turn, apply); + target.ApplySpellImmune(GetId(), SpellImmunity.Effect, SpellEffectName.KnockBack, apply); + target.ApplySpellImmune(GetId(), SpellImmunity.Effect, SpellEffectName.KnockBackDest, apply); + aura_immunity_list.Add(AuraType.ModStun); + aura_immunity_list.Add(AuraType.ModDecreaseSpeed); + aura_immunity_list.Add(AuraType.ModRoot); + aura_immunity_list.Add(AuraType.ModConfuse); + aura_immunity_list.Add(AuraType.ModFear); + aura_immunity_list.Add(AuraType.ModRoot2); + } + break; + } + case 878: + { + if (GetAmount() == 1) + { + mechanic_immunity_list = (1 << (int)Mechanics.Snare) | (1 << (int)Mechanics.Stun) + | (1 << (int)Mechanics.Disoriented) | (1 << (int)Mechanics.Freeze); + + target.ApplySpellImmune(GetId(), SpellImmunity.Mechanic, Mechanics.Snare, apply); + target.ApplySpellImmune(GetId(), SpellImmunity.Mechanic, Mechanics.Stun, apply); + target.ApplySpellImmune(GetId(), SpellImmunity.Mechanic, Mechanics.Disoriented, apply); + target.ApplySpellImmune(GetId(), SpellImmunity.Mechanic, Mechanics.Freeze, apply); + aura_immunity_list.Add(AuraType.ModStun); + aura_immunity_list.Add(AuraType.ModDecreaseSpeed); + } + break; + } + default: + break; + } + + if (aura_immunity_list.Empty()) + { + if (Convert.ToBoolean(miscVal & (1 << 10))) + aura_immunity_list.Add(AuraType.ModStun); + if (Convert.ToBoolean(miscVal & (1 << 1))) + aura_immunity_list.Add(AuraType.Transform); + + // These flag can be recognized wrong: + if (Convert.ToBoolean(miscVal & (1 << 6))) + aura_immunity_list.Add(AuraType.ModDecreaseSpeed); + if (Convert.ToBoolean(miscVal & (1 << 0))) + { + aura_immunity_list.Add(AuraType.ModRoot); + aura_immunity_list.Add(AuraType.ModRoot2); + } + if (Convert.ToBoolean(miscVal & (1 << 2))) + aura_immunity_list.Add(AuraType.ModConfuse); + if (Convert.ToBoolean(miscVal & (1 << 9))) + aura_immunity_list.Add(AuraType.ModFear); + if (Convert.ToBoolean(miscVal & (1 << 7))) + aura_immunity_list.Add(AuraType.ModDisarm); + } + + // apply immunities + foreach (var iter in aura_immunity_list) + target.ApplySpellImmune(GetId(), SpellImmunity.State, iter, apply); + + if (apply && GetSpellInfo().HasAttribute(SpellAttr1.DispelAurasOnImmunity)) + { + target.RemoveAurasWithMechanic(mechanic_immunity_list, AuraRemoveMode.Default, GetId()); + foreach (var iter in aura_immunity_list) + target.RemoveAurasByType(iter); + } + } + + [AuraEffectHandler(AuraType.MechanicImmunity)] + void HandleModMechanicImmunity(AuraApplication aurApp, AuraEffectHandleModes mode, bool apply) + { + if (!mode.HasAnyFlag(AuraEffectHandleModes.Real)) + return; + + Unit target = aurApp.GetTarget(); + uint mechanic; + + switch (GetId()) + { + case 34471: // The Beast Within + case 19574: // Bestial Wrath + mechanic = (uint)Mechanics.ImmuneToMovementImpairmentAndLossControlMask; + target.ApplySpellImmune(GetId(), SpellImmunity.Mechanic, Mechanics.Charm, apply); + target.ApplySpellImmune(GetId(), SpellImmunity.Mechanic, Mechanics.Disoriented, apply); + target.ApplySpellImmune(GetId(), SpellImmunity.Mechanic, Mechanics.Fear, apply); + target.ApplySpellImmune(GetId(), SpellImmunity.Mechanic, Mechanics.Root, apply); + target.ApplySpellImmune(GetId(), SpellImmunity.Mechanic, Mechanics.Sleep, apply); + target.ApplySpellImmune(GetId(), SpellImmunity.Mechanic, Mechanics.Snare, apply); + target.ApplySpellImmune(GetId(), SpellImmunity.Mechanic, Mechanics.Stun, apply); + target.ApplySpellImmune(GetId(), SpellImmunity.Mechanic, Mechanics.Freeze, apply); + target.ApplySpellImmune(GetId(), SpellImmunity.Mechanic, Mechanics.Knockout, apply); + target.ApplySpellImmune(GetId(), SpellImmunity.Mechanic, Mechanics.Polymorph, apply); + target.ApplySpellImmune(GetId(), SpellImmunity.Mechanic, Mechanics.Banish, apply); + target.ApplySpellImmune(GetId(), SpellImmunity.Mechanic, Mechanics.Shackle, apply); + target.ApplySpellImmune(GetId(), SpellImmunity.Mechanic, Mechanics.Turn, apply); + target.ApplySpellImmune(GetId(), SpellImmunity.Mechanic, Mechanics.Horror, apply); + target.ApplySpellImmune(GetId(), SpellImmunity.Mechanic, Mechanics.Daze, apply); + target.ApplySpellImmune(GetId(), SpellImmunity.Mechanic, Mechanics.Sapped, apply); + break; + case 42292: // PvP trinket + case 59752: // Every Man for Himself + case 53490: // Bullheaded + mechanic = (uint)Mechanics.ImmuneToMovementImpairmentAndLossControlMask; + // Actually we should apply immunities here, too, but the aura has only 100 ms duration, so there is practically no point + break; + case 54508: // Demonic Empowerment + mechanic = (1 << (int)Mechanics.Snare) | (1 << (int)Mechanics.Root); + target.ApplySpellImmune(GetId(), SpellImmunity.Mechanic, Mechanics.Snare, apply); + target.ApplySpellImmune(GetId(), SpellImmunity.Mechanic, Mechanics.Root, apply); + target.ApplySpellImmune(GetId(), SpellImmunity.Mechanic, Mechanics.Stun, apply); + break; + default: + if (GetMiscValue() < 1) + return; + mechanic = (uint)(1 << GetMiscValue()); + target.ApplySpellImmune(GetId(), SpellImmunity.Mechanic, GetMiscValue(), apply); + break; + } + + if (apply && GetSpellInfo().HasAttribute(SpellAttr1.DispelAurasOnImmunity)) + target.RemoveAurasWithMechanic(mechanic, AuraRemoveMode.Default, GetId()); + } + + [AuraEffectHandler(AuraType.EffectImmunity)] + void HandleAuraModEffectImmunity(AuraApplication aurApp, AuraEffectHandleModes mode, bool apply) + { + if (!mode.HasAnyFlag(AuraEffectHandleModes.Real)) + return; + + Unit target = aurApp.GetTarget(); + + target.ApplySpellImmune(GetId(), SpellImmunity.Effect, GetMiscValue(), apply); + + // when removing flag aura, handle flag drop + Player player = target.ToPlayer(); + if (!apply && player != null && (GetSpellInfo().AuraInterruptFlags.HasAnyFlag(SpellAuraInterruptFlags.ImmuneOrLostSelection))) + { + if (player.InBattleground()) + { + Battleground bg = player.GetBattleground(); + if (bg) + bg.EventPlayerDroppedFlag(player); + } + else + Global.OutdoorPvPMgr.HandleDropFlag(player, GetSpellInfo().Id); + } + } + + [AuraEffectHandler(AuraType.StateImmunity)] + void HandleAuraModStateImmunity(AuraApplication aurApp, AuraEffectHandleModes mode, bool apply) + { + if (!mode.HasAnyFlag(AuraEffectHandleModes.Real)) + return; + + Unit target = aurApp.GetTarget(); + + target.ApplySpellImmune(GetId(), SpellImmunity.State, GetMiscValue(), apply); + + if (apply && GetSpellInfo().HasAttribute(SpellAttr1.DispelAurasOnImmunity)) + target.RemoveAurasByType((AuraType)GetMiscValue(), ObjectGuid.Empty, GetBase()); + } + + [AuraEffectHandler(AuraType.SchoolImmunity)] + void HandleAuraModSchoolImmunity(AuraApplication aurApp, AuraEffectHandleModes mode, bool apply) + { + if (!mode.HasAnyFlag(AuraEffectHandleModes.Real)) + return; + + Unit target = aurApp.GetTarget(); + + target.ApplySpellImmune(GetId(), SpellImmunity.School, (uint)GetMiscValue(), (apply)); + + if (GetSpellInfo().Mechanic == Mechanics.Banish) + { + if (apply) + target.AddUnitState(UnitState.Isolated); + else + { + bool banishFound = false; + var banishAuras = target.GetAuraEffectsByType(GetAuraType()); + foreach (var eff in banishAuras) + { + if (eff.GetSpellInfo().Mechanic == Mechanics.Banish) + { + banishFound = true; + break; + } + } + + if (!banishFound) + target.ClearUnitState(UnitState.Isolated); + } + } + + if (apply && GetMiscValue() == (int)SpellSchoolMask.Normal) + target.RemoveAurasWithInterruptFlags(SpellAuraInterruptFlags.ImmuneOrLostSelection); + + // remove all flag auras (they are positive, but they must be removed when you are immune) + if (GetSpellInfo().HasAttribute(SpellAttr1.DispelAurasOnImmunity) + && GetSpellInfo().HasAttribute(SpellAttr2.DamageReducedShield)) + target.RemoveAurasWithInterruptFlags(SpellAuraInterruptFlags.ImmuneOrLostSelection); + + /// @todo optimalize this cycle - use RemoveAurasWithInterruptFlags call or something else + if ((apply) && GetSpellInfo().HasAttribute(SpellAttr1.DispelAurasOnImmunity) && GetSpellInfo().IsPositive()) //Only positive immunity removes auras + { + int schoolMask = GetMiscValue(); + target.RemoveAppliedAuras(aurApp1 => + { + SpellInfo spell = aurApp1.GetBase().GetSpellInfo(); + return Convert.ToBoolean((int)spell.GetSchoolMask() & schoolMask)//Check for school mask + && GetSpellInfo().CanDispelAura(spell) + && !aurApp1.IsPositive() //Don't remove positive spells + && !spell.IsPassive() // Don't remove passive auras + && spell.Id != GetId(); //Don't remove self + }); + } + } + + [AuraEffectHandler(AuraType.DamageImmunity)] + void HandleAuraModDmgImmunity(AuraApplication aurApp, AuraEffectHandleModes mode, bool apply) + { + if (!mode.HasAnyFlag(AuraEffectHandleModes.Real)) + return; + + Unit target = aurApp.GetTarget(); + + target.ApplySpellImmune(GetId(), SpellImmunity.Damage, (uint)GetMiscValue(), apply); + } + + [AuraEffectHandler(AuraType.DispelImmunity)] + void HandleAuraModDispelImmunity(AuraApplication aurApp, AuraEffectHandleModes mode, bool apply) + { + if (!mode.HasAnyFlag(AuraEffectHandleModes.Real)) + return; + + Unit target = aurApp.GetTarget(); + + target.ApplySpellDispelImmunity(m_spellInfo, (DispelType)GetMiscValue(), (apply)); + } + + /*********************************************************/ + /*** MODIFY STATS ***/ + /*********************************************************/ + + /********************************/ + /*** RESISTANCE ***/ + /********************************/ + [AuraEffectHandler(AuraType.ModResistance)] + void HandleAuraModResistance(AuraApplication aurApp, AuraEffectHandleModes mode, bool apply) + { + if (!mode.HasAnyFlag((AuraEffectHandleModes.ChangeAmountMask | AuraEffectHandleModes.Stat))) + return; + + Unit target = aurApp.GetTarget(); + + for (byte x = (byte)SpellSchools.Normal; x < (byte)SpellSchools.Max; x++) + { + if (Convert.ToBoolean(GetMiscValue() & (1 << x))) + { + target.HandleStatModifier((UnitMods.ResistanceStart + x), UnitModifierType.TotalValue, GetAmount(), apply); + if (target.IsTypeId(TypeId.Player) || target.IsPet()) + target.ApplyResistanceBuffModsMod((SpellSchools)x, GetAmount() > 0, GetAmount(), apply); + } + } + } + + [AuraEffectHandler(AuraType.ModBaseResistancePct)] + void HandleAuraModBaseResistancePCT(AuraApplication aurApp, AuraEffectHandleModes mode, bool apply) + { + if (!mode.HasAnyFlag((AuraEffectHandleModes.ChangeAmountMask | AuraEffectHandleModes.Stat))) + return; + + Unit target = aurApp.GetTarget(); + + // only players have base stats + if (!target.IsTypeId(TypeId.Player)) + { + //pets only have base armor + if (target.IsPet() && Convert.ToBoolean(GetMiscValue() & (int)SpellSchoolMask.Normal)) + target.HandleStatModifier(UnitMods.Armor, UnitModifierType.BasePCT, GetAmount(), apply); + } + else + { + for (byte x = (byte)SpellSchools.Normal; x < (byte)SpellSchools.Max; x++) + { + if (Convert.ToBoolean(GetMiscValue() & (1 << x))) + target.HandleStatModifier(UnitMods.ResistanceStart + x, UnitModifierType.BasePCT, GetAmount(), apply); + } + } + } + + [AuraEffectHandler(AuraType.ModResistancePct)] + void HandleModResistancePercent(AuraApplication aurApp, AuraEffectHandleModes mode, bool apply) + { + if (!mode.HasAnyFlag((AuraEffectHandleModes.ChangeAmountMask | AuraEffectHandleModes.Stat))) + return; + + Unit target = aurApp.GetTarget(); + int spellGroupVal = target.GetHighestExclusiveSameEffectSpellGroupValue(this, AuraType.ModResistancePct); + if (Math.Abs(spellGroupVal) >= Math.Abs(GetAmount())) + return; + + for (byte i = (byte)SpellSchools.Normal; i < (byte)SpellSchools.Max; i++) + { + if (Convert.ToBoolean(GetMiscValue() & (1 << i))) + { + if (spellGroupVal != 0) + { + target.HandleStatModifier((UnitMods.ResistanceStart + i), UnitModifierType.TotalPCT, (float)spellGroupVal, !apply); + if (target.IsTypeId(TypeId.Player) || target.IsPet()) + { + target.ApplyResistanceBuffModsPercentMod((SpellSchools)i, true, spellGroupVal, !apply); + target.ApplyResistanceBuffModsPercentMod((SpellSchools)i, false, spellGroupVal, !apply); + } + + } + target.HandleStatModifier((UnitMods.ResistanceStart + i), UnitModifierType.TotalPCT, GetAmount(), apply); + if (target.IsTypeId(TypeId.Player) || target.IsPet()) + { + target.ApplyResistanceBuffModsPercentMod((SpellSchools)i, true, GetAmount(), apply); + target.ApplyResistanceBuffModsPercentMod((SpellSchools)i, false, GetAmount(), apply); + } + } + } + } + + [AuraEffectHandler(AuraType.ModBaseResistance)] + void HandleModBaseResistance(AuraApplication aurApp, AuraEffectHandleModes mode, bool apply) + { + if (!mode.HasAnyFlag((AuraEffectHandleModes.ChangeAmountMask | AuraEffectHandleModes.Stat))) + return; + + Unit target = aurApp.GetTarget(); + + // only players have base stats + if (!target.IsTypeId(TypeId.Player)) + { + //only pets have base stats + if (target.IsPet() && Convert.ToBoolean(GetMiscValue() & (int)SpellSchoolMask.Normal)) + target.HandleStatModifier(UnitMods.Armor, UnitModifierType.TotalValue, GetAmount(), apply); + } + else + { + for (byte i = (byte)SpellSchools.Normal; i < (byte)SpellSchools.Max; i++) + if (Convert.ToBoolean(GetMiscValue() & (1 << i))) + target.HandleStatModifier(UnitMods.ResistanceStart + i, UnitModifierType.TotalValue, GetAmount(), apply); + } + } + + [AuraEffectHandler(AuraType.ModTargetResistance)] + void HandleModTargetResistance(AuraApplication aurApp, AuraEffectHandleModes mode, bool apply) + { + if (!mode.HasAnyFlag((AuraEffectHandleModes.ChangeAmountMask | AuraEffectHandleModes.Stat))) + return; + + Unit target = aurApp.GetTarget(); + + // applied to damage as HandleNoImmediateEffect in Unit.CalcAbsorbResist and Unit.CalcArmorReducedDamage + + // show armor penetration + if (target.IsTypeId(TypeId.Player) && Convert.ToBoolean(GetMiscValue() & (int)SpellSchoolMask.Normal)) + target.ApplyModUInt32Value(PlayerFields.ModTargetPhysicalResistance, GetAmount(), apply); + + // show as spell penetration only full spell penetration bonuses (all resistances except armor and holy + if (target.IsTypeId(TypeId.Player) && ((SpellSchoolMask)GetMiscValue() & SpellSchoolMask.Spell) == SpellSchoolMask.Spell) + target.ApplyModUInt32Value(PlayerFields.ModTargetResistance, GetAmount(), apply); + } + + /********************************/ + /*** STAT ***/ + /********************************/ + [AuraEffectHandler(AuraType.ModStat)] + void HandleAuraModStat(AuraApplication aurApp, AuraEffectHandleModes mode, bool apply) + { + if (!mode.HasAnyFlag((AuraEffectHandleModes.ChangeAmountMask | AuraEffectHandleModes.Stat))) + return; + + if (GetMiscValue() < -2 || GetMiscValue() > 4) + { + Log.outError(LogFilter.Spells, "WARNING: Spell {0} effect {1} has an unsupported misc value ({2}) for SPELL_AURA_MOD_STAT ", GetId(), GetEffIndex(), GetMiscValue()); + return; + } + + Unit target = aurApp.GetTarget(); + int spellGroupVal = target.GetHighestExclusiveSameEffectSpellGroupValue(this, AuraType.ModStat, true, GetMiscValue()); + if (Math.Abs(spellGroupVal) >= Math.Abs(GetAmount())) + return; + + for (var i = Stats.Strength; i < Stats.Max; i++) + { + // -1 or -2 is all stats (misc < -2 checked in function beginning) + if (GetMiscValue() < 0 || GetMiscValue() == (int)i) + { + if (spellGroupVal != 0) + { + target.HandleStatModifier((UnitMods.StatStart + (int)i), UnitModifierType.TotalValue, (float)spellGroupVal, !apply); + if (target.IsTypeId(TypeId.Player) || target.IsPet()) + target.ApplyStatBuffMod(i, spellGroupVal, !apply); + } + + target.HandleStatModifier(UnitMods.StatStart + (int)i, UnitModifierType.TotalValue, GetAmount(), apply); + if (target.IsTypeId(TypeId.Player) || target.IsPet()) + target.ApplyStatBuffMod(i, GetAmount(), apply); + } + } + } + + [AuraEffectHandler(AuraType.ModPercentStat)] + void HandleModPercentStat(AuraApplication aurApp, AuraEffectHandleModes mode, bool apply) + { + if (!mode.HasAnyFlag((AuraEffectHandleModes.ChangeAmountMask | AuraEffectHandleModes.Stat))) + return; + + Unit target = aurApp.GetTarget(); + + if (GetMiscValue() < -1 || GetMiscValue() > 4) + { + Log.outError(LogFilter.Spells, "WARNING: Misc Value for SPELL_AURA_MOD_PERCENT_STAT not valid"); + return; + } + + // only players have base stats + if (!target.IsTypeId(TypeId.Player)) + return; + + for (int i = (int)Stats.Strength; i < (int)Stats.Max; ++i) + { + if (GetMiscValue() == i || GetMiscValue() == -1) + target.HandleStatModifier(UnitMods.StatStart + i, UnitModifierType.BasePCT, (float)m_amount, apply); + } + } + + [AuraEffectHandler(AuraType.ModSpellDamageOfStatPercent)] + void HandleModSpellDamagePercentFromStat(AuraApplication aurApp, AuraEffectHandleModes mode, bool apply) + { + if (!mode.HasAnyFlag((AuraEffectHandleModes.ChangeAmountMask | AuraEffectHandleModes.Stat))) + return; + + Unit target = aurApp.GetTarget(); + + if (!target.IsTypeId(TypeId.Player)) + return; + + // Magic damage modifiers implemented in Unit.SpellDamageBonus + // This information for client side use only + // Recalculate bonus + target.ToPlayer().UpdateSpellDamageAndHealingBonus(); + } + + [AuraEffectHandler(AuraType.ModSpellHealingOfStatPercent)] + void HandleModSpellHealingPercentFromStat(AuraApplication aurApp, AuraEffectHandleModes mode, bool apply) + { + if (!mode.HasAnyFlag((AuraEffectHandleModes.ChangeAmountMask | AuraEffectHandleModes.Stat))) + return; + + Unit target = aurApp.GetTarget(); + + if (!target.IsTypeId(TypeId.Player)) + return; + + // Recalculate bonus + target.ToPlayer().UpdateSpellDamageAndHealingBonus(); + } + + [AuraEffectHandler(AuraType.ModSpellDamageOfAttackPower)] + void HandleModSpellDamagePercentFromAttackPower(AuraApplication aurApp, AuraEffectHandleModes mode, bool apply) + { + if (!mode.HasAnyFlag((AuraEffectHandleModes.ChangeAmountMask | AuraEffectHandleModes.Stat))) + return; + + Unit target = aurApp.GetTarget(); + + if (!target.IsTypeId(TypeId.Player)) + return; + + // Magic damage modifiers implemented in Unit.SpellDamageBonus + // This information for client side use only + // Recalculate bonus + target.ToPlayer().UpdateSpellDamageAndHealingBonus(); + } + + [AuraEffectHandler(AuraType.ModSpellHealingOfAttackPower)] + void HandleModSpellHealingPercentFromAttackPower(AuraApplication aurApp, AuraEffectHandleModes mode, bool apply) + { + if (!mode.HasAnyFlag((AuraEffectHandleModes.ChangeAmountMask | AuraEffectHandleModes.Stat))) + return; + + Unit target = aurApp.GetTarget(); + + if (!target.IsTypeId(TypeId.Player)) + return; + + // Recalculate bonus + target.ToPlayer().UpdateSpellDamageAndHealingBonus(); + } + + [AuraEffectHandler(AuraType.ModHealingDone)] + void HandleModHealingDone(AuraApplication aurApp, AuraEffectHandleModes mode, bool apply) + { + if (!mode.HasAnyFlag((AuraEffectHandleModes.ChangeAmountMask | AuraEffectHandleModes.Stat))) + return; + + Unit target = aurApp.GetTarget(); + + if (!target.IsTypeId(TypeId.Player)) + return; + // implemented in Unit.SpellHealingBonus + // this information is for client side only + target.ToPlayer().UpdateSpellDamageAndHealingBonus(); + } + + [AuraEffectHandler(AuraType.ModTotalStatPercentage)] + void HandleModTotalPercentStat(AuraApplication aurApp, AuraEffectHandleModes mode, bool apply) + { + if (!mode.HasAnyFlag((AuraEffectHandleModes.ChangeAmountMask | AuraEffectHandleModes.Stat))) + return; + + Unit target = aurApp.GetTarget(); + int spellGroupVal = target.GetHighestExclusiveSameEffectSpellGroupValue(this, AuraType.ModTotalStatPercentage, true, -1); + if (Math.Abs(spellGroupVal) >= Math.Abs(GetAmount())) + return; + + if (spellGroupVal != 0) + { + for (int i = (int)Stats.Strength; i < (int)Stats.Max; i++) + { + if (GetMiscValue() == i || GetMiscValue() == -1) // affect the same stats + { + target.HandleStatModifier((UnitMods.StatStart + i), UnitModifierType.TotalPCT, spellGroupVal, !apply); + if (target.IsTypeId(TypeId.Player) || target.IsPet()) + target.ApplyStatPercentBuffMod((Stats)i, spellGroupVal, !apply); + } + } + } + + // save current health state + float healthPct = target.GetHealthPct(); + bool alive = target.IsAlive(); + + for (int i = (int)Stats.Strength; i < (int)Stats.Max; i++) + { + if (Convert.ToBoolean(GetMiscValueB() & 1 << i) || GetMiscValueB() == 0) // 0 is also used for all stats + { + int spellGroupVal2 = target.GetHighestExclusiveSameEffectSpellGroupValue(this, AuraType.ModTotalStatPercentage, true, i); + if (Math.Abs(spellGroupVal2) >= Math.Abs(GetAmount())) + continue; + + if (spellGroupVal2 != 0) + { + target.HandleStatModifier(UnitMods.StatStart + i, UnitModifierType.TotalPCT, spellGroupVal2, !apply); + if (target.IsTypeId(TypeId.Player) || target.IsPet()) + target.ApplyStatPercentBuffMod((Stats)i, spellGroupVal2, !apply); + } + + target.HandleStatModifier(UnitMods.StatStart + i, UnitModifierType.TotalPCT, GetAmount(), apply); + if (target.IsTypeId(TypeId.Player) || target.IsPet()) + target.ApplyStatPercentBuffMod((Stats)i, GetAmount(), apply); + } + } + + // recalculate current HP/MP after applying aura modifications (only for spells with SPELL_ATTR0_UNK4 0x00000010 flag) + // this check is total bullshit i think + if (Convert.ToBoolean(GetMiscValueB() & 1 << (int)Stats.Stamina) && m_spellInfo.HasAttribute(SpellAttr0.Ability)) + target.SetHealth((uint)Math.Max((healthPct * target.GetMaxHealth() * 0.01f), (alive ? 1 : 0))); + } + + [AuraEffectHandler(AuraType.ModResistanceOfStatPercent)] + void HandleAuraModResistenceOfStatPercent(AuraApplication aurApp, AuraEffectHandleModes mode, bool apply) + { + if (!mode.HasAnyFlag((AuraEffectHandleModes.ChangeAmountMask | AuraEffectHandleModes.Stat))) + return; + + Unit target = aurApp.GetTarget(); + + if (!target.IsTypeId(TypeId.Player)) + return; + + if (GetMiscValue() != (int)SpellSchoolMask.Normal) + { + // support required adding replace UpdateArmor by loop by UpdateResistence at intellect update + // and include in UpdateResistence same code as in UpdateArmor for aura mod apply. + Log.outError(LogFilter.Spells, "Aura SPELL_AURA_MOD_RESISTANCE_OF_STAT_PERCENT(182) does not work for non-armor type resistances!"); + return; + } + + // Recalculate Armor + target.UpdateArmor(); + } + + [AuraEffectHandler(AuraType.ModExpertise)] + void HandleAuraModExpertise(AuraApplication aurApp, AuraEffectHandleModes mode, bool apply) + { + if (!mode.HasAnyFlag((AuraEffectHandleModes.ChangeAmountMask | AuraEffectHandleModes.Stat))) + return; + + Unit target = aurApp.GetTarget(); + + if (!target.IsTypeId(TypeId.Player)) + return; + + target.ToPlayer().UpdateExpertise(WeaponAttackType.BaseAttack); + target.ToPlayer().UpdateExpertise(WeaponAttackType.OffAttack); + } + + [AuraEffectHandler(AuraType.ModStatBonusPct)] + void HandleModStatBonusPercent(AuraApplication aurApp, AuraEffectHandleModes mode, bool apply) + { + if (!mode.HasAnyFlag(AuraEffectHandleModes.ChangeAmountMask | AuraEffectHandleModes.Stat)) + return; + + Unit target = aurApp.GetTarget(); + + if (GetMiscValue() < -1 || GetMiscValue() > 4) + { + Log.outError(LogFilter.Spells, "WARNING: Misc Value for SPELL_AURA_MOD_STAT_BONUS_PCT not valid"); + return; + } + + // only players have base stats + if (!target.IsTypeId(TypeId.Player)) + return; + + for (Stats stat = Stats.Strength; stat < Stats.Max; ++stat) + { + if (GetMiscValue() == (int)stat || GetMiscValue() == -1) + { + target.HandleStatModifier(UnitMods.StatStart + (int)stat, UnitModifierType.BasePCTExcludeCreate, m_amount, apply); + target.ApplyStatPercentBuffMod(stat, m_amount, apply); + } + } + } + + [AuraEffectHandler(AuraType.OverrideSpellPowerByApPct)] + void HandleOverrideSpellPowerByAttackPower(AuraApplication aurApp, AuraEffectHandleModes mode, bool apply) + { + if (!mode.HasAnyFlag(AuraEffectHandleModes.ChangeAmountMask | AuraEffectHandleModes.Stat)) + return; + + Player target = aurApp.GetTarget().ToPlayer(); + if (!target) + return; + + target.ApplyModSignedFloatValue(PlayerFields.OverrideSpellPowerByApPct, m_amount, apply); + target.UpdateSpellDamageAndHealingBonus(); + } + + [AuraEffectHandler(AuraType.OverrideAttackPowerBySpPct)] + void HandleOverrideAttackPowerBySpellPower(AuraApplication aurApp, AuraEffectHandleModes mode, bool apply) + { + if (!mode.HasAnyFlag(AuraEffectHandleModes.ChangeAmountMask | AuraEffectHandleModes.Stat)) + return; + + Player target = aurApp.GetTarget().ToPlayer(); + if (!target) + return; + + target.ApplyModSignedFloatValue(PlayerFields.OverrideApBySpellPowerPercent, m_amount, apply); + target.UpdateAttackPowerAndDamage(); + target.UpdateAttackPowerAndDamage(true); + } + + /********************************/ + /*** HEAL & ENERGIZE ***/ + /********************************/ + [AuraEffectHandler(AuraType.ModPowerRegen)] + void HandleModPowerRegen(AuraApplication aurApp, AuraEffectHandleModes mode, bool apply) + { + if (!mode.HasAnyFlag((AuraEffectHandleModes.ChangeAmountMask | AuraEffectHandleModes.Stat))) + return; + + Unit target = aurApp.GetTarget(); + + if (!target.IsTypeId(TypeId.Player)) + return; + + // Update manaregen value + if (GetMiscValue() == (int)PowerType.Mana) + target.ToPlayer().UpdateManaRegen(); + else if (GetMiscValue() == (int)PowerType.Runes) + target.ToPlayer().UpdateAllRunesRegen(); + // other powers are not immediate effects - implemented in Player.Regenerate, Creature.Regenerate + } + + [AuraEffectHandler(AuraType.ModPowerRegenPercent)] + void HandleModPowerRegenPCT(AuraApplication aurApp, AuraEffectHandleModes mode, bool apply) + { + HandleModPowerRegen(aurApp, mode, apply); + } + + [AuraEffectHandler(AuraType.ModManaRegenFromStat)] + void HandleModManaRegen(AuraApplication aurApp, AuraEffectHandleModes mode, bool apply) + { + if (!mode.HasAnyFlag((AuraEffectHandleModes.ChangeAmountMask | AuraEffectHandleModes.Stat))) + return; + + Unit target = aurApp.GetTarget(); + + if (!target.IsTypeId(TypeId.Player)) + return; + + //Note: an increase in regen does NOT cause threat. + target.ToPlayer().UpdateManaRegen(); + } + + [AuraEffectHandler(AuraType.ModIncreaseHealth)] + [AuraEffectHandler(AuraType.ModIncreaseHealth2)] + [AuraEffectHandler(AuraType.ModMaxHealth)] + void HandleAuraModIncreaseHealth(AuraApplication aurApp, AuraEffectHandleModes mode, bool apply) + { + if (!mode.HasAnyFlag((AuraEffectHandleModes.ChangeAmountMask | AuraEffectHandleModes.Stat))) + return; + + Unit target = aurApp.GetTarget(); + + if (apply) + { + target.HandleStatModifier(UnitMods.Health, UnitModifierType.TotalValue, GetAmount(), apply); + target.ModifyHealth(GetAmount()); + } + else + { + if (target.GetHealth() > 0) + { + int value = (int)Math.Min(target.GetHealth() - 1, (ulong)GetAmount()); + target.ModifyHealth(-value); + } + + target.HandleStatModifier(UnitMods.Health, UnitModifierType.TotalValue, GetAmount(), apply); + } + } + + void HandleAuraModIncreaseMaxHealth(AuraApplication aurApp, AuraEffectHandleModes mode, bool apply) + { + if (!mode.HasAnyFlag((AuraEffectHandleModes.ChangeAmountMask | AuraEffectHandleModes.Stat))) + return; + + Unit target = aurApp.GetTarget(); + + float percent = target.GetHealthPct(); + + target.HandleStatModifier(UnitMods.Health, UnitModifierType.TotalValue, GetAmount(), apply); + + // refresh percentage + if (target.GetHealth() > 0) + { + uint newHealth = (uint)Math.Max(target.CountPctFromMaxHealth((int)percent), 1); + target.SetHealth(newHealth); + } + } + + [AuraEffectHandler(AuraType.ModIncreaseEnergy)] + void HandleAuraModIncreaseEnergy(AuraApplication aurApp, AuraEffectHandleModes mode, bool apply) + { + if (!mode.HasAnyFlag((AuraEffectHandleModes.ChangeAmountMask | AuraEffectHandleModes.Stat))) + return; + + Unit target = aurApp.GetTarget(); + + PowerType powerType = (PowerType)GetMiscValue(); + // do not check power type, we can always modify the maximum + // as the client will not see any difference + // also, placing conditions that may change during the aura duration + // inside effect handlers is not a good idea + + UnitMods unitMod = (UnitMods.PowerStart + (int)powerType); + + target.HandleStatModifier(unitMod, UnitModifierType.TotalValue, GetAmount(), apply); + } + + [AuraEffectHandler(AuraType.ModIncreaseEnergyPercent)] + void HandleAuraModIncreaseEnergyPercent(AuraApplication aurApp, AuraEffectHandleModes mode, bool apply) + { + if (!mode.HasAnyFlag((AuraEffectHandleModes.ChangeAmountMask | AuraEffectHandleModes.Stat))) + return; + + Unit target = aurApp.GetTarget(); + + PowerType powerType = (PowerType)GetMiscValue(); + // do not check power type, we can always modify the maximum + // as the client will not see any difference + // also, placing conditions that may change during the aura duration + // inside effect handlers is not a good idea + + UnitMods unitMod = UnitMods.PowerStart + (int)powerType; + float amount = GetAmount(); + + if (apply) + { + target.HandleStatModifier(unitMod, UnitModifierType.TotalPCT, amount, apply); + target.ModifyPowerPct(powerType, amount, apply); + } + else + { + target.ModifyPowerPct(powerType, amount, apply); + target.HandleStatModifier(unitMod, UnitModifierType.TotalPCT, amount, apply); + } + } + + [AuraEffectHandler(AuraType.ModIncreaseHealthPercent)] + void HandleAuraModIncreaseHealthPercent(AuraApplication aurApp, AuraEffectHandleModes mode, bool apply) + { + if (!mode.HasAnyFlag((AuraEffectHandleModes.ChangeAmountMask | AuraEffectHandleModes.Stat))) + return; + + Unit target = aurApp.GetTarget(); + + // Unit will keep hp% after MaxHealth being modified if unit is alive. + float percent = target.GetHealthPct(); + target.HandleStatModifier(UnitMods.Health, UnitModifierType.TotalPCT, GetAmount(), apply); + + if (target.GetHealth() > 0) + { + uint newHealth = (uint)Math.Max(target.CountPctFromMaxHealth((int)percent), 1); + target.SetHealth(newHealth); + } + } + + [AuraEffectHandler(AuraType.ModBaseHealthPct)] + void HandleAuraIncreaseBaseHealthPercent(AuraApplication aurApp, AuraEffectHandleModes mode, bool apply) + { + if (!mode.HasAnyFlag((AuraEffectHandleModes.ChangeAmountMask | AuraEffectHandleModes.Stat))) + return; + + Unit target = aurApp.GetTarget(); + + target.HandleStatModifier(UnitMods.Health, UnitModifierType.BasePCT, GetAmount(), apply); + } + + [AuraEffectHandler(AuraType.ModBaseManaPct)] + void HandleAuraModIncreaseBaseManaPercent(AuraApplication aurApp, AuraEffectHandleModes mode, bool apply) + { + if (!mode.HasAnyFlag(AuraEffectHandleModes.ChangeAmountMask | AuraEffectHandleModes.Stat)) + return; + + aurApp.GetTarget().HandleStatModifier(UnitMods.Mana, UnitModifierType.BasePCT, GetAmount(), apply); + } + + [AuraEffectHandler(AuraType.ModPowerDisplay)] + void HandleAuraModPowerDisplay(AuraApplication aurApp, AuraEffectHandleModes mode, bool apply) + { + if (!mode.HasAnyFlag(AuraEffectHandleModes.RealOrReapplyMask)) + return; + + if (GetMiscValue() >= (int)PowerType.Max) + return; + + if (apply) + aurApp.GetTarget().RemoveAurasByType(GetAuraType(), ObjectGuid.Empty, GetBase()); + + aurApp.GetTarget().UpdateDisplayPower(); + } + + [AuraEffectHandler(AuraType.ModOverridePowerDisplay)] + void HandleAuraModOverridePowerDisplay(AuraApplication aurApp, AuraEffectHandleModes mode, bool apply) + { + if (!mode.HasAnyFlag(AuraEffectHandleModes.Real)) + return; + + PowerDisplayRecord powerDisplay = CliDB.PowerDisplayStorage.LookupByKey(GetMiscValue()); + if (powerDisplay == null) + return; + + Unit target = aurApp.GetTarget(); + if (target.GetPowerIndex((PowerType)powerDisplay.PowerType) == (int)PowerType.Max) + return; + + if (apply) + { + target.RemoveAurasByType(GetAuraType(), ObjectGuid.Empty, GetBase()); + target.SetUInt32Value(UnitFields.OverrideDisplayPowerId, powerDisplay.Id); + } + else + target.SetUInt32Value(UnitFields.OverrideDisplayPowerId, 0); + } + + /********************************/ + /*** FIGHT ***/ + /********************************/ + [AuraEffectHandler(AuraType.ModParryPercent)] + void HandleAuraModParryPercent(AuraApplication aurApp, AuraEffectHandleModes mode, bool apply) + { + if (!mode.HasAnyFlag((AuraEffectHandleModes.ChangeAmountMask | AuraEffectHandleModes.Stat))) + return; + + Unit target = aurApp.GetTarget(); + + if (!target.IsTypeId(TypeId.Player)) + return; + + target.ToPlayer().UpdateParryPercentage(); + } + + [AuraEffectHandler(AuraType.ModDodgePercent)] + void HandleAuraModDodgePercent(AuraApplication aurApp, AuraEffectHandleModes mode, bool apply) + { + if (!mode.HasAnyFlag((AuraEffectHandleModes.ChangeAmountMask | AuraEffectHandleModes.Stat))) + return; + + Unit target = aurApp.GetTarget(); + + if (!target.IsTypeId(TypeId.Player)) + return; + + target.ToPlayer().UpdateDodgePercentage(); + } + + [AuraEffectHandler(AuraType.ModBlockPercent)] + void HandleAuraModBlockPercent(AuraApplication aurApp, AuraEffectHandleModes mode, bool apply) + { + if (!mode.HasAnyFlag((AuraEffectHandleModes.ChangeAmountMask | AuraEffectHandleModes.Stat))) + return; + + Unit target = aurApp.GetTarget(); + + if (!target.IsTypeId(TypeId.Player)) + return; + + target.ToPlayer().UpdateBlockPercentage(); + } + + [AuraEffectHandler(AuraType.InterruptRegen)] + void HandleAuraModRegenInterrupt(AuraApplication aurApp, AuraEffectHandleModes mode, bool apply) + { + HandleModManaRegen(aurApp, mode, apply); + } + + [AuraEffectHandler(AuraType.ModWeaponCritPercent)] + void HandleAuraModWeaponCritPercent(AuraApplication aurApp, AuraEffectHandleModes mode, bool apply) + { + if (!mode.HasAnyFlag((AuraEffectHandleModes.ChangeAmountMask | AuraEffectHandleModes.Stat))) + return; + + Player target = aurApp.GetTarget().ToPlayer(); + + if (!target) + return; + + target.HandleBaseModValue(BaseModGroup.CritPercentage, BaseModType.FlatMod, GetAmount(), apply); + target.HandleBaseModValue(BaseModGroup.OffhandCritPercentage, BaseModType.FlatMod, GetAmount(), apply); + target.HandleBaseModValue(BaseModGroup.RangedCritPercentage, BaseModType.FlatMod, GetAmount(), apply); + } + + [AuraEffectHandler(AuraType.ModHitChance)] + void HandleModHitChance(AuraApplication aurApp, AuraEffectHandleModes mode, bool apply) + { + if (!mode.HasAnyFlag((AuraEffectHandleModes.ChangeAmountMask | AuraEffectHandleModes.Stat))) + return; + + Unit target = aurApp.GetTarget(); + + if (target.IsTypeId(TypeId.Player)) + { + target.ToPlayer().UpdateMeleeHitChances(); + target.ToPlayer().UpdateRangedHitChances(); + } + else + { + target.m_modMeleeHitChance += (apply) ? GetAmount() : (-GetAmount()); + target.m_modRangedHitChance += (apply) ? GetAmount() : (-GetAmount()); + } + } + + [AuraEffectHandler(AuraType.ModSpellHitChance)] + void HandleModSpellHitChance(AuraApplication aurApp, AuraEffectHandleModes mode, bool apply) + { + if (!mode.HasAnyFlag((AuraEffectHandleModes.ChangeAmountMask | AuraEffectHandleModes.Stat))) + return; + + Unit target = aurApp.GetTarget(); + + if (target.IsTypeId(TypeId.Player)) + target.ToPlayer().UpdateSpellHitChances(); + else + target.m_modSpellHitChance += (apply) ? GetAmount() : (-GetAmount()); + } + + [AuraEffectHandler(AuraType.ModSpellCritChance)] + void HandleModSpellCritChance(AuraApplication aurApp, AuraEffectHandleModes mode, bool apply) + { + if (!mode.HasAnyFlag((AuraEffectHandleModes.ChangeAmountMask | AuraEffectHandleModes.Stat))) + return; + + Unit target = aurApp.GetTarget(); + + if (target.IsTypeId(TypeId.Player)) + target.ToPlayer().UpdateSpellCritChance(); + else + target.m_baseSpellCritChance += (apply) ? GetAmount() : -GetAmount(); + } + + [AuraEffectHandler(AuraType.ModCritPct)] + void HandleAuraModCritPct(AuraApplication aurApp, AuraEffectHandleModes mode, bool apply) + { + if (!mode.HasAnyFlag((AuraEffectHandleModes.ChangeAmountMask | AuraEffectHandleModes.Stat))) + return; + + Unit target = aurApp.GetTarget(); + + if (!target.IsTypeId(TypeId.Player)) + { + target.m_baseSpellCritChance += (apply) ? GetAmount() : -GetAmount(); + return; + } + + target.ToPlayer().HandleBaseModValue(BaseModGroup.CritPercentage, BaseModType.FlatMod, GetAmount(), apply); + target.ToPlayer().HandleBaseModValue(BaseModGroup.OffhandCritPercentage, BaseModType.FlatMod, GetAmount(), apply); + target.ToPlayer().HandleBaseModValue(BaseModGroup.RangedCritPercentage, BaseModType.FlatMod, GetAmount(), apply); + + // included in Player.UpdateSpellCritChance calculation + target.ToPlayer().UpdateSpellCritChance(); + } + + /********************************/ + /*** ATTACK SPEED ***/ + /********************************/ + [AuraEffectHandler(AuraType.HasteSpells)] + [AuraEffectHandler(AuraType.ModCastingSpeedNotStack)] + void HandleModCastingSpeed(AuraApplication aurApp, AuraEffectHandleModes mode, bool apply) + { + if (!mode.HasAnyFlag((AuraEffectHandleModes.ChangeAmountMask | AuraEffectHandleModes.Stat))) + return; + + Unit target = aurApp.GetTarget(); + + target.ApplyCastTimePercentMod(GetAmount(), apply); + } + + [AuraEffectHandler(AuraType.ModMeleeRangedHaste)] + [AuraEffectHandler(AuraType.ModMeleeRangedHaste2)] + void HandleModMeleeRangedSpeedPct(AuraApplication aurApp, AuraEffectHandleModes mode, bool apply) + { + if (!mode.HasAnyFlag((AuraEffectHandleModes.ChangeAmountMask | AuraEffectHandleModes.Stat))) + return; + + //! ToDo: Haste auras with the same handler _CAN'T_ stack together + Unit target = aurApp.GetTarget(); + + target.ApplyAttackTimePercentMod(WeaponAttackType.BaseAttack, GetAmount(), apply); + target.ApplyAttackTimePercentMod(WeaponAttackType.OffAttack, GetAmount(), apply); + target.ApplyAttackTimePercentMod(WeaponAttackType.RangedAttack, GetAmount(), apply); + } + + [AuraEffectHandler(AuraType.MeleeSlow)] + [AuraEffectHandler(AuraType.ModSpeedSlowAll)] + void HandleModCombatSpeedPct(AuraApplication aurApp, AuraEffectHandleModes mode, bool apply) + { + if (!mode.HasAnyFlag((AuraEffectHandleModes.ChangeAmountMask | AuraEffectHandleModes.Stat))) + return; + + Unit target = aurApp.GetTarget(); + int spellGroupVal = target.GetHighestExclusiveSameEffectSpellGroupValue(this, AuraType.MeleeSlow); + if (Math.Abs(spellGroupVal) >= Math.Abs(GetAmount())) + return; + + if (spellGroupVal != 0) + { + target.ApplyCastTimePercentMod(spellGroupVal, !apply); + target.ApplyAttackTimePercentMod(WeaponAttackType.BaseAttack, spellGroupVal, !apply); + target.ApplyAttackTimePercentMod(WeaponAttackType.OffAttack, spellGroupVal, !apply); + target.ApplyAttackTimePercentMod(WeaponAttackType.RangedAttack, spellGroupVal, !apply); + } + + target.ApplyCastTimePercentMod(m_amount, apply); + target.ApplyAttackTimePercentMod(WeaponAttackType.BaseAttack, GetAmount(), apply); + target.ApplyAttackTimePercentMod(WeaponAttackType.OffAttack, GetAmount(), apply); + target.ApplyAttackTimePercentMod(WeaponAttackType.RangedAttack, GetAmount(), apply); + } + + [AuraEffectHandler(AuraType.ModAttackspeed)] + void HandleModAttackSpeed(AuraApplication aurApp, AuraEffectHandleModes mode, bool apply) + { + if (!mode.HasAnyFlag((AuraEffectHandleModes.ChangeAmountMask | AuraEffectHandleModes.Stat))) + return; + + Unit target = aurApp.GetTarget(); + + target.ApplyAttackTimePercentMod(WeaponAttackType.BaseAttack, GetAmount(), apply); + target.UpdateDamagePhysical(WeaponAttackType.BaseAttack); + } + + [AuraEffectHandler(AuraType.ModMeleeHaste)] + [AuraEffectHandler(AuraType.ModMeleeHaste2)] + [AuraEffectHandler(AuraType.ModMeleeHaste3)] + void HandleModMeleeSpeedPct(AuraApplication aurApp, AuraEffectHandleModes mode, bool apply) + { + if (!mode.HasAnyFlag((AuraEffectHandleModes.ChangeAmountMask | AuraEffectHandleModes.Stat))) + return; + + //! ToDo: Haste auras with the same handler _CAN'T_ stack together + Unit target = aurApp.GetTarget(); + int spellGroupVal = target.GetHighestExclusiveSameEffectSpellGroupValue(this, AuraType.ModMeleeHaste); + if (Math.Abs(spellGroupVal) >= Math.Abs(GetAmount())) + return; + + if (spellGroupVal != 0) + { + target.ApplyAttackTimePercentMod(WeaponAttackType.BaseAttack, spellGroupVal, !apply); + target.ApplyAttackTimePercentMod(WeaponAttackType.OffAttack, spellGroupVal, !apply); + } + target.ApplyAttackTimePercentMod(WeaponAttackType.BaseAttack, GetAmount(), apply); + target.ApplyAttackTimePercentMod(WeaponAttackType.OffAttack, GetAmount(), apply); + } + + [AuraEffectHandler(AuraType.ModRangedHaste)] + [AuraEffectHandler(AuraType.ModRangedHaste2)] + void HandleAuraModRangedHaste(AuraApplication aurApp, AuraEffectHandleModes mode, bool apply) + { + if (!mode.HasAnyFlag((AuraEffectHandleModes.ChangeAmountMask | AuraEffectHandleModes.Stat))) + return; + + //! ToDo: Haste auras with the same handler _CAN'T_ stack together + Unit target = aurApp.GetTarget(); + + target.ApplyAttackTimePercentMod(WeaponAttackType.RangedAttack, GetAmount(), apply); + } + + /********************************/ + /*** COMBAT RATING ***/ + /********************************/ + [AuraEffectHandler(AuraType.ModRating)] + void HandleModRating(AuraApplication aurApp, AuraEffectHandleModes mode, bool apply) + { + if (!mode.HasAnyFlag((AuraEffectHandleModes.ChangeAmountMask | AuraEffectHandleModes.Stat))) + return; + + Unit target = aurApp.GetTarget(); + + if (!target.IsTypeId(TypeId.Player)) + return; + + for (int rating = 0; rating < (int)CombatRating.Max; ++rating) + if (Convert.ToBoolean(GetMiscValue() & (1 << rating))) + target.ToPlayer().ApplyRatingMod((CombatRating)rating, GetAmount(), apply); + } + + [AuraEffectHandler(AuraType.ModRatingFromStat)] + void HandleModRatingFromStat(AuraApplication aurApp, AuraEffectHandleModes mode, bool apply) + { + if (!mode.HasAnyFlag((AuraEffectHandleModes.ChangeAmountMask | AuraEffectHandleModes.Stat))) + return; + + Unit target = aurApp.GetTarget(); + + if (!target.IsTypeId(TypeId.Player)) + return; + + // Just recalculate ratings + for (int rating = 0; rating < (int)CombatRating.Max; ++rating) + if (Convert.ToBoolean(GetMiscValue() & (1 << rating))) + target.ToPlayer().UpdateRating((CombatRating)rating); + } + + [AuraEffectHandler(AuraType.ModRatingPct)] + void HandleModRatingPct(AuraApplication aurApp, AuraEffectHandleModes mode, bool apply) + { + if (!mode.HasAnyFlag(AuraEffectHandleModes.ChangeAmountMask | AuraEffectHandleModes.Stat)) + return; + + Unit target = aurApp.GetTarget(); + + if (!target.IsTypeId(TypeId.Player)) + return; + + // Just recalculate ratings + for (int rating = 0; rating < (int)CombatRating.Max; ++rating) + if (Convert.ToBoolean(GetMiscValue() & (1 << rating))) + target.ToPlayer().UpdateRating((CombatRating)rating); + } + + /********************************/ + /*** ATTACK POWER ***/ + /********************************/ + [AuraEffectHandler(AuraType.ModAttackPower)] + void HandleAuraModAttackPower(AuraApplication aurApp, AuraEffectHandleModes mode, bool apply) + { + if (!mode.HasAnyFlag((AuraEffectHandleModes.ChangeAmountMask | AuraEffectHandleModes.Stat))) + return; + + Unit target = aurApp.GetTarget(); + + target.HandleStatModifier(UnitMods.AttackPower, UnitModifierType.TotalValue, GetAmount(), apply); + } + + [AuraEffectHandler(AuraType.ModRangedAttackPower)] + void HandleAuraModRangedAttackPower(AuraApplication aurApp, AuraEffectHandleModes mode, bool apply) + { + if (!mode.HasAnyFlag((AuraEffectHandleModes.ChangeAmountMask | AuraEffectHandleModes.Stat))) + return; + + Unit target = aurApp.GetTarget(); + + if ((target.getClassMask() & (uint)Class.ClassMaskWandUsers) != 0) + return; + + target.HandleStatModifier(UnitMods.AttackPowerRanged, UnitModifierType.TotalValue, GetAmount(), apply); + } + + [AuraEffectHandler(AuraType.ModAttackPowerPct)] + void HandleAuraModAttackPowerPercent(AuraApplication aurApp, AuraEffectHandleModes mode, bool apply) + { + if (!mode.HasAnyFlag((AuraEffectHandleModes.ChangeAmountMask | AuraEffectHandleModes.Stat))) + return; + + Unit target = aurApp.GetTarget(); + + //UNIT_FIELD_ATTACK_POWER_MULTIPLIER = multiplier - 1 + target.HandleStatModifier(UnitMods.AttackPower, UnitModifierType.TotalPCT, GetAmount(), apply); + } + + [AuraEffectHandler(AuraType.ModRangedAttackPowerPct)] + void HandleAuraModRangedAttackPowerPercent(AuraApplication aurApp, AuraEffectHandleModes mode, bool apply) + { + if (!mode.HasAnyFlag((AuraEffectHandleModes.ChangeAmountMask | AuraEffectHandleModes.Stat))) + return; + + Unit target = aurApp.GetTarget(); + + if ((target.getClassMask() & (uint)Class.ClassMaskWandUsers) != 0) + return; + + //UNIT_FIELD_RANGED_ATTACK_POWER_MULTIPLIER = multiplier - 1 + target.HandleStatModifier(UnitMods.AttackPowerRanged, UnitModifierType.TotalPCT, GetAmount(), apply); + } + + [AuraEffectHandler(AuraType.ModAttackPowerOfArmor)] + void HandleAuraModAttackPowerOfArmor(AuraApplication aurApp, AuraEffectHandleModes mode, bool apply) + { + if (!mode.HasAnyFlag((AuraEffectHandleModes.ChangeAmountMask | AuraEffectHandleModes.Stat))) + return; + + Unit target = aurApp.GetTarget(); + + // Recalculate bonus + if (target.IsTypeId(TypeId.Player)) + target.ToPlayer().UpdateAttackPowerAndDamage(false); + } + + /********************************/ + /*** DAMAGE BONUS ***/ + /********************************/ + [AuraEffectHandler(AuraType.ModDamageDone)] + void HandleModDamageDone(AuraApplication aurApp, AuraEffectHandleModes mode, bool apply) + { + if (!mode.HasAnyFlag((AuraEffectHandleModes.ChangeAmountMask | AuraEffectHandleModes.Stat))) + return; + + Unit target = aurApp.GetTarget(); + + if ((GetMiscValue() & (int)SpellSchoolMask.Normal) != 0) + { + target.HandleStatModifier(UnitMods.DamageMainHand, UnitModifierType.TotalValue, GetAmount(), apply); + target.HandleStatModifier(UnitMods.DamageOffHand, UnitModifierType.TotalValue, GetAmount(), apply); + target.HandleStatModifier(UnitMods.DamageRanged, UnitModifierType.TotalValue, GetAmount(), apply); + } + + // Magic damage modifiers implemented in Unit::SpellBaseDamageBonusDone + // This information for client side use only + if (target.IsTypeId(TypeId.Player)) + { + PlayerFields baseField = GetAmount() >= 0 ? PlayerFields.ModDamageDonePos : PlayerFields.ModDamageDoneNeg; + for (int i = 0; i < (int)SpellSchools.Max; ++i) + { + if (Convert.ToBoolean(GetMiscValue() & (1 << i))) + target.ApplyModUInt32Value(baseField + i, GetAmount(), apply); + } + + Guardian pet = target.ToPlayer().GetGuardianPet(); + if (pet) + pet.UpdateAttackPowerAndDamage(); + } + } + + [AuraEffectHandler(AuraType.ModDamagePercentDone)] + void HandleModDamagePercentDone(AuraApplication aurApp, AuraEffectHandleModes mode, bool apply) + { + if (!mode.HasAnyFlag((AuraEffectHandleModes.ChangeAmountMask | AuraEffectHandleModes.Stat))) + return; + + Unit target = aurApp.GetTarget(); + int spellGroupVal = target.GetHighestExclusiveSameEffectSpellGroupValue(this, AuraType.ModDamagePercentDone); + if (Math.Abs(spellGroupVal) >= Math.Abs(GetAmount())) + return; + + if (Convert.ToBoolean(GetMiscValue() & (int)SpellSchoolMask.Normal)) + { + if (spellGroupVal != 0) + { + target.HandleStatModifier(UnitMods.DamageMainHand, UnitModifierType.TotalPCT, spellGroupVal, !apply); + target.HandleStatModifier(UnitMods.DamageOffHand, UnitModifierType.TotalPCT, spellGroupVal, !apply); + target.HandleStatModifier(UnitMods.DamageRanged, UnitModifierType.TotalPCT, spellGroupVal, !apply); + } + + target.HandleStatModifier(UnitMods.DamageMainHand, UnitModifierType.TotalPCT, GetAmount(), apply); + target.HandleStatModifier(UnitMods.DamageOffHand, UnitModifierType.TotalPCT, GetAmount(), apply); + target.HandleStatModifier(UnitMods.DamageRanged, UnitModifierType.TotalPCT, GetAmount(), apply); + } + + if (target.IsTypeId(TypeId.Player)) + { + for (int i = 0; i < (int)SpellSchools.Max; ++i) + { + if (Convert.ToBoolean(GetMiscValue() & (1 << i))) + { + if (spellGroupVal != 0) + target.ApplyPercentModFloatValue(PlayerFields.ModDamageDonePct + i, spellGroupVal, !apply); + + target.ApplyPercentModFloatValue(PlayerFields.ModDamageDonePct + i, GetAmount(), apply); + } + } + } + } + + [AuraEffectHandler(AuraType.ModOffhandDamagePct)] + void HandleModOffhandDamagePercent(AuraApplication aurApp, AuraEffectHandleModes mode, bool apply) + { + if (!mode.HasAnyFlag((AuraEffectHandleModes.ChangeAmountMask | AuraEffectHandleModes.Stat))) + return; + + Unit target = aurApp.GetTarget(); + + target.HandleStatModifier(UnitMods.DamageOffHand, UnitModifierType.TotalPCT, GetAmount(), apply); + } + + [AuraEffectHandler(AuraType.ModShieldBlockvalue)] + void HandleShieldBlockValue(AuraApplication aurApp, AuraEffectHandleModes mode, bool apply) + { + if (!mode.HasAnyFlag((AuraEffectHandleModes.ChangeAmountMask | AuraEffectHandleModes.Stat))) + return; + + Unit target = aurApp.GetTarget(); + + BaseModType modType = BaseModType.FlatMod; + if (GetAuraType() == AuraType.ModShieldBlockvaluePct) + modType = BaseModType.PCTmod; + + if (target.IsTypeId(TypeId.Player)) + target.ToPlayer().HandleBaseModValue(BaseModGroup.ShieldBlockValue, modType, GetAmount(), apply); + } + + /********************************/ + /*** POWER COST ***/ + /********************************/ + [AuraEffectHandler(AuraType.ModPowerCostSchoolPct)] + void HandleModPowerCostPCT(AuraApplication aurApp, AuraEffectHandleModes mode, bool apply) + { + if (!mode.HasAnyFlag(AuraEffectHandleModes.ChangeAmountMask)) + return; + + Unit target = aurApp.GetTarget(); + + float amount = MathFunctions.CalculatePct(1.0f, GetAmount()); + for (int i = 0; i < (int)SpellSchools.Max; ++i) + if (Convert.ToBoolean(GetMiscValue() & (1 << i))) + target.ApplyModSignedFloatValue(UnitFields.PowerCostMultiplier + i, amount, apply); + } + + [AuraEffectHandler(AuraType.ModPowerCostSchool)] + void HandleModPowerCost(AuraApplication aurApp, AuraEffectHandleModes mode, bool apply) + { + if (!mode.HasAnyFlag(AuraEffectHandleModes.ChangeAmountMask)) + return; + + Unit target = aurApp.GetTarget(); + + for (int i = 0; i < (int)SpellSchools.Max; ++i) + if (Convert.ToBoolean(GetMiscValue() & (1 << i))) + target.ApplyModUInt32Value(UnitFields.PowerCostModifier + i, GetAmount(), apply); + } + + [AuraEffectHandler(AuraType.ArenaPreparation)] + void HandleArenaPreparation(AuraApplication aurApp, AuraEffectHandleModes mode, bool apply) + { + if (!mode.HasAnyFlag(AuraEffectHandleModes.Real)) + return; + + Unit target = aurApp.GetTarget(); + + if (apply) + target.SetFlag(UnitFields.Flags, UnitFlags.Preparation); + else + { + // do not remove unit flag if there are more than this auraEffect of that kind on unit on unit + if (target.HasAuraType(GetAuraType())) + return; + target.RemoveFlag(UnitFields.Flags, UnitFlags.Preparation); + } + } + + [AuraEffectHandler(AuraType.NoReagentUse)] + void HandleNoReagentUseAura(AuraApplication aurApp, AuraEffectHandleModes mode, bool apply) + { + if (!mode.HasAnyFlag(AuraEffectHandleModes.Real)) + return; + + Unit target = aurApp.GetTarget(); + + if (!target.IsTypeId(TypeId.Player)) + return; + + FlagArray128 mask = new FlagArray128(); + var noReagent = target.GetAuraEffectsByType(AuraType.NoReagentUse); + foreach (var eff in noReagent) + { + SpellEffectInfo effect = eff.GetSpellEffectInfo(); + if (effect != null) + mask |= effect.SpellClassMask; + } + + target.SetUInt32Value(PlayerFields.NoReagentCost1, mask[0]); + target.SetUInt32Value(PlayerFields.NoReagentCost1 + 1, mask[1]); + target.SetUInt32Value(PlayerFields.NoReagentCost1 + 2, mask[2]); + } + + [AuraEffectHandler(AuraType.RetainComboPoints)] + void HandleAuraRetainComboPoints(AuraApplication aurApp, AuraEffectHandleModes mode, bool apply) + { + if (!mode.HasAnyFlag(AuraEffectHandleModes.Real)) + return; + + Unit target = aurApp.GetTarget(); + + if (!target.IsTypeId(TypeId.Player)) + return; + + // combo points was added in SPELL_EFFECT_ADD_COMBO_POINTS handler + // remove only if aura expire by time (in case combo points amount change aura removed without combo points lost) + if (!apply && aurApp.GetRemoveMode() == AuraRemoveMode.Expire) + target.ToPlayer().AddComboPoints((sbyte)-GetAmount()); + } + + /*********************************************************/ + /*** OTHERS ***/ + /*********************************************************/ + [AuraEffectHandler(AuraType.Dummy)] + void HandleAuraDummy(AuraApplication aurApp, AuraEffectHandleModes mode, bool apply) + { + if (!mode.HasAnyFlag((AuraEffectHandleModes.ChangeAmountMask | AuraEffectHandleModes.Reapply))) + return; + + Unit target = aurApp.GetTarget(); + + Unit caster = GetCaster(); + + if (mode.HasAnyFlag(AuraEffectHandleModes.Real)) + { + // pet auras + PetAura petSpell = Global.SpellMgr.GetPetAura(GetId(), m_effIndex); + if (petSpell != null) + { + if (apply) + target.AddPetAura(petSpell); + else + target.RemovePetAura(petSpell); + } + } + + if (mode.HasAnyFlag(AuraEffectHandleModes.Real | AuraEffectHandleModes.Reapply)) + { + // AT APPLY + if (apply) + { + switch (GetId()) + { + case 1515: // Tame beast + // FIX_ME: this is 2.0.12 threat effect replaced in 2.1.x by dummy aura, must be checked for correctness + if (caster != null && target.CanHaveThreatList()) + target.AddThreat(caster, 10.0f); + break; + case 13139: // net-o-matic + // root to self part of (root_target.charge.root_self sequence + if (caster != null) + caster.CastSpell(caster, 13138, true, null, this); + break; + case 34026: // kill command + { + Unit pet = target.GetGuardianPet(); + if (pet == null) + break; + + target.CastSpell(target, 34027, true, null, this); + + // set 3 stacks and 3 charges (to make all auras not disappear at once) + Aura owner_aura = target.GetAura(34027, GetCasterGUID()); + Aura pet_aura = pet.GetAura(58914, GetCasterGUID()); + if (owner_aura != null) + { + owner_aura.SetStackAmount((byte)owner_aura.GetSpellInfo().StackAmount); + if (pet_aura != null) + { + pet_aura.SetCharges(0); + pet_aura.SetStackAmount((byte)owner_aura.GetSpellInfo().StackAmount); + } + } + break; + } + case 37096: // Blood Elf Illusion + { + if (caster != null) + { + if (caster.GetGender() == Gender.Female) + caster.CastSpell(target, 37095, true, null, this); // Blood Elf Disguise + else + caster.CastSpell(target, 37093, true, null, this); + } + break; + } + case 39850: // Rocket Blast + if (RandomHelper.randChance(20)) // backfire stun + target.CastSpell(target, 51581, true, null, this); + break; + case 43873: // Headless Horseman Laugh + target.PlayDistanceSound(11965); + break; + case 46354: // Blood Elf Illusion + if (caster != null) + { + if (caster.GetGender() == Gender.Female) + caster.CastSpell(target, 46356, true, null, this); + else + caster.CastSpell(target, 46355, true, null, this); + } + break; + case 46361: // Reinforced Net + if (caster != null) + target.GetMotionMaster().MoveFall(); + break; + } + } + // AT REMOVE + else + { + if ((GetSpellInfo().IsQuestTame()) && caster != null && caster.IsAlive() && target.IsAlive()) + { + uint finalSpelId = 0; + switch (GetId()) + { + case 19548: + finalSpelId = 19597; + break; + case 19674: + finalSpelId = 19677; + break; + case 19687: + finalSpelId = 19676; + break; + case 19688: + finalSpelId = 19678; + break; + case 19689: + finalSpelId = 19679; + break; + case 19692: + finalSpelId = 19680; + break; + case 19693: + finalSpelId = 19684; + break; + case 19694: + finalSpelId = 19681; + break; + case 19696: + finalSpelId = 19682; + break; + case 19697: + finalSpelId = 19683; + break; + case 19699: + finalSpelId = 19685; + break; + case 19700: + finalSpelId = 19686; + break; + case 30646: + finalSpelId = 30647; + break; + case 30653: + finalSpelId = 30648; + break; + case 30654: + finalSpelId = 30652; + break; + case 30099: + finalSpelId = 30100; + break; + case 30102: + finalSpelId = 30103; + break; + case 30105: + finalSpelId = 30104; + break; + } + + if (finalSpelId != 0) + caster.CastSpell(target, finalSpelId, true, null, this); + } + + switch (m_spellInfo.SpellFamilyName) + { + case SpellFamilyNames.Generic: + switch (GetId()) + { + case 2584: // Waiting to Resurrect + // Waiting to resurrect spell cancel, we must remove player from resurrect queue + if (target.IsTypeId(TypeId.Player)) + { + Battleground bg = target.ToPlayer().GetBattleground(); + if (bg) + bg.RemovePlayerFromResurrectQueue(target.GetGUID()); + BattleField bf = Global.BattleFieldMgr.GetBattlefieldToZoneId(target.GetZoneId()); + if (bf != null) + bf.RemovePlayerFromResurrectQueue(target.GetGUID()); + } + break; + case 36730: // Flame Strike + { + target.CastSpell(target, 36731, true, null, this); + break; + } + case 44191: // Flame Strike + { + if (target.GetMap().IsDungeon()) + { + uint spellId = (uint)(target.GetMap().IsHeroic() ? 46163 : 44190); + + target.CastSpell(target, spellId, true, null, this); + } + break; + } + case 43681: // Inactive + { + if (!target.IsTypeId(TypeId.Player) || aurApp.GetRemoveMode() != AuraRemoveMode.Expire) + return; + + if (target.GetMap().IsBattleground()) + target.ToPlayer().LeaveBattleground(); + break; + } + case 42783: // Wrath of the Astromancer + target.CastSpell(target, (uint)GetAmount(), true, null, this); + break; + case 46308: // Burning Winds casted only at creatures at spawn + target.CastSpell(target, 47287, true, null, this); + break; + case 52172: // Coyote Spirit Despawn Aura + case 60244: // Blood Parrot Despawn Aura + target.CastSpell((Unit)null, (uint)GetAmount(), true, null, this); + break; + case 91604: // Restricted Flight Area + if (aurApp.GetRemoveMode() == AuraRemoveMode.Expire) + target.CastSpell(target, 58601, true); + break; + } + break; + case SpellFamilyNames.Deathknight: + // Summon Gargoyle (Dismiss Gargoyle at remove) + if (GetId() == 61777) + target.CastSpell(target, (uint)GetAmount(), true); + break; + default: + break; + } + } + } + + // AT APPLY & REMOVE + + switch (m_spellInfo.SpellFamilyName) + { + case SpellFamilyNames.Generic: + { + if (!mode.HasAnyFlag(AuraEffectHandleModes.Real)) + break; + switch (GetId()) + { + // Recently Bandaged + case 11196: + target.ApplySpellImmune(GetId(), SpellImmunity.Mechanic, GetMiscValue(), apply); + break; + // Unstable Power + case 24658: + { + uint spellId = 24659; + if (apply && caster != null) + { + SpellInfo spell = Global.SpellMgr.GetSpellInfo(spellId); + + for (uint i = 0; i < spell.StackAmount; ++i) + caster.CastSpell(target, spell.Id, true, null, null, GetCasterGUID()); + break; + } + target.RemoveAurasDueToSpell(spellId); + break; + } + // Restless Strength + case 24661: + { + uint spellId = 24662; + if (apply && caster != null) + { + SpellInfo spell = Global.SpellMgr.GetSpellInfo(spellId); + for (uint i = 0; i < spell.StackAmount; ++i) + caster.CastSpell(target, spell.Id, true, null, null, GetCasterGUID()); + break; + } + target.RemoveAurasDueToSpell(spellId); + break; + } + // Tag Murloc + case 30877: + { + // Tag/untag Blacksilt Scout + target.SetEntry((uint)(apply ? 17654 : 17326)); + break; + } + case 57819: // Argent Champion + case 57820: // Ebon Champion + case 57821: // Champion of the Kirin Tor + case 57822: // Wyrmrest Champion + { + if (!caster || !caster.IsTypeId(TypeId.Player)) + break; + + uint FactionID = 0; + + if (apply) + { + switch (m_spellInfo.Id) + { + case 57819: + FactionID = 1106; // Argent Crusade + break; + case 57820: + FactionID = 1098;// Knights of the Ebon Blade + break; + case 57821: + FactionID = 1090; // Kirin Tor + break; + case 57822: + FactionID = 1091; // The Wyrmrest Accord + break; + } + } + caster.ToPlayer().SetChampioningFaction(FactionID); + break; + } + // LK Intro VO (1) + case 58204: + if (target.IsTypeId(TypeId.Player)) + { + // Play part 1 + if (apply) + target.PlayDirectSound(14970, target.ToPlayer()); + // continue in 58205 + else + target.CastSpell(target, 58205, true); + } + break; + // LK Intro VO (2) + case 58205: + if (target.IsTypeId(TypeId.Player)) + { + // Play part 2 + if (apply) + target.PlayDirectSound(14971, target.ToPlayer()); + // Play part 3 + else + target.PlayDirectSound(14972, target.ToPlayer()); + } + break; + } + break; + } + } + } + + [AuraEffectHandler(AuraType.ChannelDeathItem)] + void HandleChannelDeathItem(AuraApplication aurApp, AuraEffectHandleModes mode, bool apply) + { + if (!mode.HasAnyFlag(AuraEffectHandleModes.Real)) + return; + + if (apply || aurApp.GetRemoveMode() != AuraRemoveMode.ByDeath) + return; + + Unit caster = GetCaster(); + + if (caster == null || !caster.IsTypeId(TypeId.Player)) + return; + + Player plCaster = caster.ToPlayer(); + Unit target = aurApp.GetTarget(); + + // Item amount + if (GetAmount() <= 0) + return; + + if (GetSpellEffectInfo().ItemType == 0) + return; + + // Soul Shard + if (GetSpellEffectInfo().ItemType == 6265) + { + // Soul Shard only from units that grant XP or honor + if (!plCaster.isHonorOrXPTarget(target) || + (target.IsTypeId(TypeId.Unit) && !target.ToCreature().isTappedBy(plCaster))) + return; + } + + //Adding items + uint noSpaceForCount = 0; + uint count = (uint)m_amount; + + List dest = new List(); + InventoryResult msg = plCaster.CanStoreNewItem(ItemConst.NullBag, ItemConst.NullSlot, dest, GetSpellEffectInfo().ItemType, count, out noSpaceForCount); + if (msg != InventoryResult.Ok) + { + count -= noSpaceForCount; + plCaster.SendEquipError(msg, null, null, GetSpellEffectInfo().ItemType); + if (count == 0) + return; + } + + Item newitem = plCaster.StoreNewItem(dest, GetSpellEffectInfo().ItemType, true); + if (newitem == null) + { + plCaster.SendEquipError(InventoryResult.ItemNotFound); + return; + } + plCaster.SendNewItem(newitem, count, true, true); + } + + [AuraEffectHandler(AuraType.BindSight)] + void HandleBindSight(AuraApplication aurApp, AuraEffectHandleModes mode, bool apply) + { + if (!mode.HasAnyFlag(AuraEffectHandleModes.Real)) + return; + + Unit target = aurApp.GetTarget(); + + Unit caster = GetCaster(); + + if (caster == null || !caster.IsTypeId(TypeId.Player)) + return; + + caster.ToPlayer().SetViewpoint(target, apply); + } + + [AuraEffectHandler(AuraType.ForceReaction)] + void HandleForceReaction(AuraApplication aurApp, AuraEffectHandleModes mode, bool apply) + { + if (!mode.HasAnyFlag(AuraEffectHandleModes.ChangeAmountMask)) + return; + + Unit target = aurApp.GetTarget(); + + Player player = target.ToPlayer(); + if (player == null) + return; + + uint factionId = (uint)GetMiscValue(); + ReputationRank factionRank = (ReputationRank)m_amount; + + player.GetReputationMgr().ApplyForceReaction(factionId, factionRank, apply); + player.GetReputationMgr().SendForceReactions(); + + // stop fighting if at apply forced rank friendly or at remove real rank friendly + if ((apply && factionRank >= ReputationRank.Friendly) || (!apply && player.GetReputationRank(factionId) >= ReputationRank.Friendly)) + player.StopAttackFaction(factionId); + } + + [AuraEffectHandler(AuraType.Empathy)] + void HandleAuraEmpathy(AuraApplication aurApp, AuraEffectHandleModes mode, bool apply) + { + if (!mode.HasAnyFlag(AuraEffectHandleModes.Real)) + return; + + Unit target = aurApp.GetTarget(); + if (!apply) + { + // do not remove unit flag if there are more than this auraEffect of that kind on unit on unit + if (target.HasAuraType(GetAuraType())) + return; + } + + if (target.GetCreatureType() == CreatureType.Beast) + target.ApplyModUInt32Value(ObjectFields.DynamicFlags, (int)UnitDynFlags.SpecialInfo, apply); + } + + [AuraEffectHandler(AuraType.ModFaction)] + void HandleAuraModFaction(AuraApplication aurApp, AuraEffectHandleModes mode, bool apply) + { + if (!mode.HasAnyFlag(AuraEffectHandleModes.Real)) + return; + + Unit target = aurApp.GetTarget(); + + if (apply) + { + target.SetFaction((uint)GetMiscValue()); + if (target.IsTypeId(TypeId.Player)) + target.RemoveFlag(UnitFields.Flags, UnitFlags.PvpAttackable); + } + else + { + target.RestoreFaction(); + if (target.IsTypeId(TypeId.Player)) + target.SetFlag(UnitFields.Flags, UnitFlags.PvpAttackable); + } + } + + [AuraEffectHandler(AuraType.ComprehendLanguage)] + void HandleComprehendLanguage(AuraApplication aurApp, AuraEffectHandleModes mode, bool apply) + { + if (!mode.HasAnyFlag(AuraEffectHandleModes.SendForClientMask)) + return; + + Unit target = aurApp.GetTarget(); + + if (apply) + target.SetFlag(UnitFields.Flags2, UnitFlags2.ComprehendLang); + else + { + if (target.HasAuraType(GetAuraType())) + return; + + target.RemoveFlag(UnitFields.Flags2, UnitFlags2.ComprehendLang); + } + } + + [AuraEffectHandler(AuraType.Linked)] + void HandleAuraLinked(AuraApplication aurApp, AuraEffectHandleModes mode, bool apply) + { + Unit target = aurApp.GetTarget(); + + uint triggeredSpellId = GetSpellEffectInfo().TriggerSpell; + SpellInfo triggeredSpellInfo = Global.SpellMgr.GetSpellInfo(triggeredSpellId); + if (triggeredSpellInfo == null) + return; + + Unit caster = triggeredSpellInfo.NeedsToBeTriggeredByCaster(m_spellInfo, target.GetMap().GetDifficultyID()) ? GetCaster() : target; + if (!caster) + return; + + if (mode.HasAnyFlag(AuraEffectHandleModes.Real)) + { + if (apply) + { + // If amount avalible cast with basepoints (Crypt Fever for example) + if (GetAmount() != 0) + caster.CastCustomSpell(target, triggeredSpellId, m_amount, 0, 0, true, null, this); + else + caster.CastSpell(target, triggeredSpellId, true, null, this); + } + else + { + ObjectGuid casterGUID = triggeredSpellInfo.NeedsToBeTriggeredByCaster(m_spellInfo, caster.GetMap().GetDifficultyID()) ? GetCasterGUID() : target.GetGUID(); + target.RemoveAura(triggeredSpellId, casterGUID, 0, aurApp.GetRemoveMode()); + } + } + else if (mode.HasAnyFlag(AuraEffectHandleModes.Reapply) && apply) + { + ObjectGuid casterGUID = triggeredSpellInfo.NeedsToBeTriggeredByCaster(m_spellInfo, caster.GetMap().GetDifficultyID()) ? GetCasterGUID() : target.GetGUID(); + // change the stack amount to be equal to stack amount of our aura + Aura triggeredAura = target.GetAura(triggeredSpellId, casterGUID); + if (triggeredAura != null) + triggeredAura.ModStackAmount(GetBase().GetStackAmount() - triggeredAura.GetStackAmount()); + } + } + + [AuraEffectHandler(AuraType.OpenStable)] + void HandleAuraOpenStable(AuraApplication aurApp, AuraEffectHandleModes mode, bool apply) + { + if (!mode.HasAnyFlag(AuraEffectHandleModes.Real)) + return; + + Unit target = aurApp.GetTarget(); + + if (!target.IsTypeId(TypeId.Player) || !target.IsInWorld) + return; + + if (apply) + target.ToPlayer().GetSession().SendStablePet(target.GetGUID()); + + // client auto close stable dialog at !apply aura + } + + [AuraEffectHandler(AuraType.ModFakeInebriate)] + void HandleAuraModFakeInebriation(AuraApplication aurApp, AuraEffectHandleModes mode, bool apply) + { + if (!mode.HasAnyFlag(AuraEffectHandleModes.ChangeAmountMask)) + return; + + Unit target = aurApp.GetTarget(); + + if (apply) + { + target.m_invisibilityDetect.AddFlag(InvisibilityType.Drunk); + target.m_invisibilityDetect.AddValue(InvisibilityType.Drunk, GetAmount()); + + if (target.IsTypeId(TypeId.Player)) + { + int oldval = target.ToPlayer().GetInt32Value(PlayerFields.FakeInebriation); + target.ToPlayer().SetInt32Value(PlayerFields.FakeInebriation, oldval + GetAmount()); + } + } + else + { + bool removeDetect = !target.HasAuraType(AuraType.ModFakeInebriate); + + target.m_invisibilityDetect.AddValue(InvisibilityType.Drunk, -GetAmount()); + + if (target.IsTypeId(TypeId.Player)) + { + int oldval = target.ToPlayer().GetInt32Value(PlayerFields.FakeInebriation); + target.ToPlayer().SetInt32Value(PlayerFields.FakeInebriation, oldval - GetAmount()); + + if (removeDetect) + removeDetect = target.ToPlayer().GetDrunkValue() == 0; + } + + if (removeDetect) + target.m_invisibilityDetect.DelFlag(InvisibilityType.Drunk); + } + + // call functions which may have additional effects after chainging state of unit + target.UpdateObjectVisibility(); + } + + [AuraEffectHandler(AuraType.OverrideSpells)] + void HandleAuraOverrideSpells(AuraApplication aurApp, AuraEffectHandleModes mode, bool apply) + { + if (!mode.HasAnyFlag(AuraEffectHandleModes.Real)) + return; + + Player target = aurApp.GetTarget().ToPlayer(); + + if (target == null || !target.IsInWorld) + return; + + uint overrideId = (uint)GetMiscValue(); + + if (apply) + { + target.SetUInt16Value(PlayerFields.FieldBytes3, PlayerFieldOffsets.FieldBytes3OffsetOverrideSpellsIdUint16Offset, (ushort)overrideId); + OverrideSpellDataRecord overrideSpells = CliDB.OverrideSpellDataStorage.LookupByKey(overrideId); + if (overrideSpells != null) + { + for (byte i = 0; i < SharedConst.MaxOverrideSpell; ++i) + { + uint spellId = overrideSpells.SpellID[i]; + if (spellId != 0) + target.AddTemporarySpell(spellId); + } + } + } + else + { + target.SetUInt16Value(PlayerFields.FieldBytes3, PlayerFieldOffsets.FieldBytes3OffsetOverrideSpellsIdUint16Offset, 0); + OverrideSpellDataRecord overrideSpells = CliDB.OverrideSpellDataStorage.LookupByKey(overrideId); + if (overrideSpells != null) + { + for (byte i = 0; i < SharedConst.MaxOverrideSpell; ++i) + { + uint spellId = overrideSpells.SpellID[i]; + if (spellId != 0) + target.RemoveTemporarySpell(spellId); + } + } + } + } + + [AuraEffectHandler(AuraType.SetVehicleId)] + void HandleAuraSetVehicle(AuraApplication aurApp, AuraEffectHandleModes mode, bool apply) + { + if (!mode.HasAnyFlag(AuraEffectHandleModes.Real)) + return; + + Unit target = aurApp.GetTarget(); + + if (!target.IsInWorld) + return; + + int vehicleId = GetMiscValue(); + + if (apply) + { + if (!target.CreateVehicleKit((uint)vehicleId, 0)) + return; + } + else if (target.GetVehicleKit() != null) + target.RemoveVehicleKit(); + + if (!target.IsTypeId(TypeId.Player)) + return; + + if (apply) + target.ToPlayer().SendOnCancelExpectedVehicleRideAura(); + } + + [AuraEffectHandler(AuraType.PreventResurrection)] + void HandlePreventResurrection(AuraApplication aurApp, AuraEffectHandleModes mode, bool apply) + { + if (!mode.HasAnyFlag(AuraEffectHandleModes.Real)) + return; + + if (!aurApp.GetTarget().IsTypeId(TypeId.Player)) + return; + + if (apply) + aurApp.GetTarget().RemoveByteFlag(PlayerFields.LocalFlags, 0, PlayerLocalFlags.ReleaseTimer); + else if (!aurApp.GetTarget().GetMap().Instanceable()) + aurApp.GetTarget().SetByteFlag(PlayerFields.LocalFlags, 0, PlayerLocalFlags.ReleaseTimer); + } + + [AuraEffectHandler(AuraType.Mastery)] + void HandleMastery(AuraApplication aurApp, AuraEffectHandleModes mode, bool apply) + { + if (!mode.HasAnyFlag(AuraEffectHandleModes.Real)) + return; + + Player target = aurApp.GetTarget().ToPlayer(); + if (target == null) + return; + + target.UpdateMastery(); + } + + void HandlePeriodicDummyAuraTick(Unit target, Unit caster) + { + switch (GetSpellInfo().SpellFamilyName) + { + case SpellFamilyNames.Generic: + switch (GetId()) + { + case 66149: // Bullet Controller Periodic - 10 Man + case 68396: // Bullet Controller Periodic - 25 Man + { + if (caster == null) + break; + + caster.CastCustomSpell(66152, SpellValueMod.MaxTargets, RandomHelper.IRand(1, 6), target, true); + caster.CastCustomSpell(66153, SpellValueMod.MaxTargets, RandomHelper.IRand(1, 6), target, true); + break; + } + case 62292: // Blaze (Pool of Tar) + // should we use custom damage? + target.CastSpell((Unit)null, GetSpellEffectInfo().TriggerSpell, true); + break; + case 62399: // Overload Circuit + if (target.GetMap().IsDungeon() && target.GetAppliedAuras().Count(p => p.Key == 62399) >= (target.GetMap().IsHeroic() ? 4 : 2)) + { + target.CastSpell(target, 62475, true); // System Shutdown + Unit veh = target.GetVehicleBase(); + if (veh != null) + veh.CastSpell(target, 62475, true); + } + break; + case 64821: // Fuse Armor (Razorscale) + if (GetBase().GetStackAmount() == GetSpellInfo().StackAmount) + { + target.CastSpell(target, 64774, true, null, null, GetCasterGUID()); + target.RemoveAura(64821); + } + break; + } + break; + case SpellFamilyNames.Mage: + { + // Mirror Image + if (GetId() == 55342) + // Set name of summons to name of caster + target.CastSpell((Unit)null, GetSpellEffectInfo().TriggerSpell, true); + break; + } + case SpellFamilyNames.Druid: + { + switch (GetSpellInfo().Id) + { + // Frenzied Regeneration + case 22842: + { + // Converts up to 10 rage per second into health for $d. Each point of rage is converted into ${$m2/10}.1% of max health. + // Should be manauser + if (target.getPowerType() != PowerType.Rage) + break; + int rage = target.GetPower(PowerType.Rage); + // Nothing todo + if (rage == 0) + break; + int mod = (rage < 100) ? rage : 100; + int points = target.CalculateSpellDamage(target, GetSpellInfo(), 1); + int regen = (int)((long)target.GetMaxHealth() * (mod * points / 10) / 1000); + target.CastCustomSpell(target, 22845, regen, 0, 0, true, null, this); + target.SetPower(PowerType.Rage, (rage - mod)); + break; + } + } + break; + } + case SpellFamilyNames.Rogue: + { + switch (GetSpellInfo().Id) + { + // Master of Subtlety + case 31666: + if (!target.HasAuraType(AuraType.ModStealth)) + target.RemoveAurasDueToSpell(31665); + break; + // Overkill + case 58428: + if (!target.HasAuraType(AuraType.ModStealth)) + target.RemoveAurasDueToSpell(58427); + break; + } + break; + } + case SpellFamilyNames.Hunter: + { + // Explosive Shot + if (GetSpellInfo().SpellFamilyFlags[1].HasAnyFlag(0x80000000)) + { + if (caster != null) + caster.CastCustomSpell(53352, SpellValueMod.BasePoint0, m_amount, target, true, null, this); + break; + } + switch (GetSpellInfo().Id) + { + // Feeding Frenzy Rank 1 + case 53511: + if (target.GetVictim() != null && target.GetVictim().HealthBelowPct(35)) + target.CastSpell(target, 60096, true, null, this); + return; + // Feeding Frenzy Rank 2 + case 53512: + if (target.GetVictim() != null && target.GetVictim().HealthBelowPct(35)) + target.CastSpell(target, 60097, true, null, this); + return; + default: + break; + } + break; + } + case SpellFamilyNames.Shaman: + if (GetId() == 52179) // Astral Shift + { + // Periodic need for remove visual on stun/fear/silence lost + if (!Convert.ToBoolean(target.GetUInt32Value(UnitFields.Flags) & (uint)(UnitFlags.Stunned | UnitFlags.Fleeing | UnitFlags.Silenced))) + target.RemoveAurasDueToSpell(52179); + break; + } + break; + case SpellFamilyNames.Deathknight: + switch (GetId()) + { + case 49016: // Hysteria + uint damage = (uint)target.CountPctFromMaxHealth(1); + target.DealDamage(target, damage, null, DamageEffectType.NoDamage, SpellSchoolMask.Normal, null, false); + break; + } + break; + default: + break; + } + } + + void HandlePeriodicTriggerSpellAuraTick(Unit target, Unit caster) + { + // generic casting code with custom spells and target/caster customs + uint triggerSpellId = GetSpellEffectInfo().TriggerSpell; + + SpellInfo triggeredSpellInfo = Global.SpellMgr.GetSpellInfo(triggerSpellId); + SpellInfo auraSpellInfo = GetSpellInfo(); + uint auraId = auraSpellInfo.Id; + + // specific code for cases with no trigger spell provided in field + if (triggeredSpellInfo == null) + { + switch (auraSpellInfo.SpellFamilyName) + { + case SpellFamilyNames.Generic: + { + switch (auraId) + { + // Brood Affliction: Bronze + case 23170: + triggerSpellId = 23171; + break; + // Restoration + case 24379: + case 23493: + { + if (caster != null) + { + uint heal = (uint)caster.CountPctFromMaxHealth(10); + caster.HealBySpell(target, auraSpellInfo, heal); + int mana = caster.GetMaxPower(PowerType.Mana); + if (mana != 0) + { + mana /= 10; + caster.EnergizeBySpell(caster, 23493, mana, PowerType.Mana); + } + } + return; + } + // Nitrous Boost + case 27746: + if (caster != null && target.GetPower(PowerType.Mana) >= 10) + { + target.ModifyPower(PowerType.Mana, -10); + target.SendEnergizeSpellLog(caster, 27746, 10, 0, PowerType.Mana); + } + else + target.RemoveAurasDueToSpell(27746); + return; + // Frost Blast + case 27808: + if (caster != null) + caster.CastCustomSpell(29879, SpellValueMod.BasePoint0, (int)target.CountPctFromMaxHealth(21), target, true, null, this); + return; + // Inoculate Nestlewood Owlkin + case 29528: + if (!target.IsTypeId(TypeId.Unit)) // prevent error reports in case ignored player target + return; + break; + // Feed Captured Animal + case 29917: + triggerSpellId = 29916; + break; + // Extract Gas + case 30427: + { + // move loot to player inventory and despawn target + if (caster != null && caster.IsTypeId(TypeId.Player) && + target.IsTypeId(TypeId.Unit) && + target.ToCreature().GetCreatureTemplate().CreatureType == CreatureType.GasCloud) + { + Player player = caster.ToPlayer(); + Creature creature = target.ToCreature(); + // missing lootid has been reported on startup - just return + if (creature.GetCreatureTemplate().SkinLootId == 0) + return; + + player.AutoStoreLoot(creature.GetCreatureTemplate().SkinLootId, LootStorage.Skinning, true); + + creature.DespawnOrUnsummon(); + } + return; + } + // Quake + case 30576: + triggerSpellId = 30571; + break; + // Doom + /// @todo effect trigger spell may be independant on spell targets, and executed in spell finish phase + // so instakill will be naturally done before trigger spell + case 31347: + { + target.CastSpell(target, 31350, true, null, this); + target.KillSelf(); + return; + } + // Spellcloth + case 31373: + { + // Summon Elemental after create item + target.SummonCreature(17870, 0, 0, 0, target.GetOrientation(), TempSummonType.DeadDespawn, 0); + return; + } + // Flame Quills + case 34229: + { + // cast 24 spells 34269-34289, 34314-34316 + for (uint spell_id = 34269; spell_id != 34290; ++spell_id) + target.CastSpell(target, spell_id, true, null, this); + for (uint spell_id = 34314; spell_id != 34317; ++spell_id) + target.CastSpell(target, spell_id, true, null, this); + return; + } + // Remote Toy + case 37027: + triggerSpellId = 37029; + break; + // Eye of Grillok + case 38495: + triggerSpellId = 38530; + break; + // Absorb Eye of Grillok (Zezzak's Shard) + case 38554: + { + if (caster == null || !target.IsTypeId(TypeId.Unit)) + return; + + caster.CastSpell(caster, 38495, true, null, this); + + Creature creatureTarget = target.ToCreature(); + + creatureTarget.DespawnOrUnsummon(); + return; + } + // Tear of Azzinoth Summon Channel - it's not really supposed to do anything, and this only prevents the console spam + case 39857: + triggerSpellId = 39856; + break; + // Personalized Weather + case 46736: + triggerSpellId = 46737; + break; + } + break; + } + case SpellFamilyNames.Shaman: + { + switch (auraId) + { + // Lightning Shield (The Earthshatterer set trigger after cast Lighting Shield) + case 28820: + { + // Need remove self if Lightning Shield not active + if (target.GetAuraEffect(AuraType.ProcTriggerSpell, SpellFamilyNames.Shaman, new FlagArray128(0x400, 0, 0)) == null) + target.RemoveAurasDueToSpell(28820); + return; + } + // Totemic Mastery (Skyshatter Regalia (Shaman Tier 6) - bonus) + case 38443: + { + bool all = true; + for (int i = (int)SummonSlot.Totem; i < SharedConst.MaxSummonSlot; ++i) + { + if (target.m_SummonSlot[i].IsEmpty()) + { + all = false; + break; + } + } + + if (all) + target.CastSpell(target, 38437, true, null, this); + else + target.RemoveAurasDueToSpell(38437); + return; + } + } + break; + } + default: + break; + } + } + else + { + // Spell exist but require custom code + switch (auraId) + { + // Pursuing Spikes (Anub'arak) + case 65920: + case 65922: + case 65923: + { + Unit permafrostCaster = null; + Aura permafrostAura = target.GetAura(66193); + if (permafrostAura == null) + permafrostAura = target.GetAura(67855); + if (permafrostAura == null) + permafrostAura = target.GetAura(67856); + if (permafrostAura == null) + permafrostAura = target.GetAura(67857); + + if (permafrostAura != null) + permafrostCaster = permafrostAura.GetCaster(); + + if (permafrostCaster != null) + { + Creature permafrostCasterCreature = permafrostCaster.ToCreature(); + if (permafrostCasterCreature != null) + permafrostCasterCreature.DespawnOrUnsummon(3000); + + target.CastSpell(target, 66181, false); + target.RemoveAllAuras(); + Creature targetCreature = target.ToCreature(); + if (targetCreature != null) + targetCreature.DisappearAndDie(); + } + break; + } + // Mana Tide + case 16191: + target.CastCustomSpell(target, triggerSpellId, m_amount, 0, 0, true, null, this); + return; + // Negative Energy Periodic + case 46284: + target.CastCustomSpell(triggerSpellId, SpellValueMod.MaxTargets, (int)(m_tickNumber / 10 + 1), null, true, null, this); + return; + // Poison (Grobbulus) + case 28158: + case 54362: + // Slime Pool (Dreadscale & Acidmaw) + case 66882: + target.CastCustomSpell(triggerSpellId, SpellValueMod.RadiusMod, (int)((((float)m_tickNumber / 60) * 0.9f + 0.1f) * 10000 * 2 / 3), null, true, null, this); + return; + // Slime Spray - temporary here until preventing default effect works again + // added on 9.10.2010 + case 69508: + { + if (caster != null) + caster.CastSpell(target, triggerSpellId, true, null, null, caster.GetGUID()); + return; + } + case 24745: // Summon Templar, Trigger + case 24747: // Summon Templar Fire, Trigger + case 24757: // Summon Templar Air, Trigger + case 24759: // Summon Templar Earth, Trigger + case 24761: // Summon Templar Water, Trigger + case 24762: // Summon Duke, Trigger + case 24766: // Summon Duke Fire, Trigger + case 24769: // Summon Duke Air, Trigger + case 24771: // Summon Duke Earth, Trigger + case 24773: // Summon Duke Water, Trigger + case 24785: // Summon Royal, Trigger + case 24787: // Summon Royal Fire, Trigger + case 24791: // Summon Royal Air, Trigger + case 24792: // Summon Royal Earth, Trigger + case 24793: // Summon Royal Water, Trigger + { + // All this spells trigger a spell that requires reagents; if the + // triggered spell is cast as "triggered", reagents are not consumed + if (caster != null) + caster.CastSpell(target, triggerSpellId, false); + return; + } + } + } + + // Reget trigger spell proto + triggeredSpellInfo = Global.SpellMgr.GetSpellInfo(triggerSpellId); + + if (triggeredSpellInfo != null) + { + Unit triggerCaster = triggeredSpellInfo.NeedsToBeTriggeredByCaster(m_spellInfo, target.GetMap().GetDifficultyID()) ? caster : target; + if (triggerCaster != null) + { + triggerCaster.CastSpell(target, triggeredSpellInfo, true, null, this); + Log.outDebug(LogFilter.Spells, "AuraEffect.HandlePeriodicTriggerSpellAuraTick: Spell {0} Trigger {1}", GetId(), triggeredSpellInfo.Id); + } + } + else + { + Creature c = target.ToCreature(); + if (c == null || caster == null || !Global.ScriptMgr.OnDummyEffect(caster, GetId(), GetEffIndex(), target.ToCreature()) || + !c.GetAI().sOnDummyEffect(caster, GetId(), GetEffIndex())) + Log.outDebug(LogFilter.Spells, "AuraEffect.HandlePeriodicTriggerSpellAuraTick: Spell {0} has non-existent spell {1} in EffectTriggered[{2}] and is therefor not triggered.", GetId(), triggerSpellId, GetEffIndex()); + } + } + + void HandlePeriodicTriggerSpellWithValueAuraTick(Unit target, Unit caster) + { + uint triggerSpellId = GetSpellEffectInfo().TriggerSpell; + SpellInfo triggeredSpellInfo = Global.SpellMgr.GetSpellInfo(triggerSpellId); + if (triggeredSpellInfo != null) + { + Unit triggerCaster = triggeredSpellInfo.NeedsToBeTriggeredByCaster(m_spellInfo, target.GetMap().GetDifficultyID()) ? caster : target; + if (triggerCaster != null) + { + int basepoints = GetAmount(); + triggerCaster.CastCustomSpell(target, triggerSpellId, basepoints, basepoints, basepoints, true, null, this); + Log.outDebug(LogFilter.Spells, "AuraEffect.HandlePeriodicTriggerSpellWithValueAuraTick: Spell {0} Trigger {1}", GetId(), triggeredSpellInfo.Id); + } + } + else + Log.outDebug(LogFilter.Spells, "AuraEffect.HandlePeriodicTriggerSpellWithValueAuraTick: Spell {0} has non-existent spell {1} in EffectTriggered[{2}] and is therefor not triggered.", GetId(), triggerSpellId, GetEffIndex()); + } + + void HandlePeriodicDamageAurasTick(Unit target, Unit caster) + { + if (!caster || !target.IsAlive()) + return; + + if (target.HasUnitState(UnitState.Isolated) || target.IsImmunedToDamage(GetSpellInfo())) + { + SendTickImmune(target, caster); + return; + } + + // Consecrate ticks can miss and will not show up in the combat log + if (GetSpellEffectInfo().Effect == SpellEffectName.PersistentAreaAura && + caster.SpellHitResult(target, GetSpellInfo(), false) != SpellMissInfo.None) + return; + + // some auras remove at specific health level or more + if (GetAuraType() == AuraType.PeriodicDamage) + { + switch (GetSpellInfo().Id) + { + case 43093: + case 31956: + case 38801: // Grievous Wound + case 35321: + case 38363: + case 39215: // Gushing Wound + if (target.IsFullHealth()) + { + target.RemoveAurasDueToSpell(GetSpellInfo().Id); + return; + } + break; + case 38772: // Grievous Wound + { + SpellEffectInfo effect = GetSpellInfo().GetEffect(1); + if (effect != null) + { + int percent = effect.CalcValue(caster); + if (!target.HealthBelowPct(percent)) + { + target.RemoveAurasDueToSpell(GetSpellInfo().Id); + return; + } + } + break; + } + } + } + + uint absorb = 0; + uint resist = 0; + CleanDamage cleanDamage = new CleanDamage(0, 0, WeaponAttackType.BaseAttack, MeleeHitOutcome.Normal); + + // AOE spells are not affected by the new periodic system. + bool isAreaAura = GetSpellEffectInfo().IsAreaAuraEffect() || GetSpellEffectInfo().IsEffect(SpellEffectName.PersistentAreaAura); + // ignore non positive values (can be result apply spellmods to aura damage + uint damage = (uint)(isAreaAura ? Math.Max(GetAmount(), 0) : m_damage); + + // Script Hook For HandlePeriodicDamageAurasTick -- Allow scripts to change the Damage pre class mitigation calculations + if (isAreaAura) + Global.ScriptMgr.ModifyPeriodicDamageAurasTick(target, caster, ref damage); + + if (GetAuraType() == AuraType.PeriodicDamage) + { + if (isAreaAura) + damage = (uint)(caster.SpellDamageBonusDone(target, GetSpellInfo(), damage, DamageEffectType.DOT, GetSpellEffectInfo(), GetBase().GetStackAmount()) * caster.SpellDamagePctDone(target, m_spellInfo, DamageEffectType.DOT)); + damage = target.SpellDamageBonusTaken(caster, GetSpellInfo(), damage, DamageEffectType.DOT, GetSpellEffectInfo(), GetBase().GetStackAmount()); + + // Calculate armor mitigation + if (caster.IsDamageReducedByArmor(GetSpellInfo().GetSchoolMask(), GetSpellInfo(), (sbyte)GetEffIndex())) + { + uint damageReductedArmor = caster.CalcArmorReducedDamage(caster, target, damage, GetSpellInfo()); + cleanDamage.mitigated_damage += damage - damageReductedArmor; + damage = damageReductedArmor; + } + + // There is a Chance to make a Soul Shard when Drain soul does damage + if (GetSpellInfo().SpellFamilyName == SpellFamilyNames.Warlock && GetSpellInfo().SpellFamilyFlags[0].HasAnyFlag(0x00004000u)) + { + if (caster.IsTypeId(TypeId.Player) && caster.ToPlayer().isHonorOrXPTarget(target)) + caster.CastSpell(caster, 95810, true, null, this); + } + if (GetSpellInfo().SpellFamilyName == SpellFamilyNames.Generic) + { + switch (GetId()) + { + case 70911: // Unbound Plague + case 72854: // Unbound Plague + case 72855: // Unbound Plague + case 72856: // Unbound Plague + damage *= (uint)Math.Pow(1.25f, m_tickNumber); + break; + default: + break; + } + } + } + else + damage = (uint)target.CountPctFromMaxHealth((int)damage); + + if (!m_spellInfo.HasAttribute(SpellAttr4.FixedDamage)) + { + if (GetSpellEffectInfo().IsTargetingArea() || isAreaAura) + { + damage = (uint)(damage * target.GetTotalAuraMultiplierByMiscMask(AuraType.ModAoeDamageAvoidance, (uint)m_spellInfo.SchoolMask)); + if (!caster.IsTypeId(TypeId.Player)) + damage = (uint)(damage * target.GetTotalAuraMultiplierByMiscMask(AuraType.ModCreatureAoeDamageAvoidance, (uint)m_spellInfo.SchoolMask)); + } + } + + bool crit = false; + if (CanPeriodicTickCrit(caster)) + crit = RandomHelper.randChance(isAreaAura ? caster.GetUnitSpellCriticalChance(target, m_spellInfo, m_spellInfo.GetSchoolMask()) : m_critChance); + + if (crit) + damage = caster.SpellCriticalDamageBonus(m_spellInfo, damage, target); + + if (!GetSpellInfo().HasAttribute(SpellAttr4.FixedDamage)) + caster.ApplyResilience(target, ref damage); + + caster.CalcAbsorbResist(target, GetSpellInfo().GetSchoolMask(), DamageEffectType.DOT, damage, ref absorb, ref resist, GetSpellInfo()); + caster.DealDamageMods(target, ref damage, ref absorb); + + // Set trigger flag + ProcFlags procAttacker = ProcFlags.DonePeriodic; + ProcFlags procVictim = ProcFlags.TakenPeriodic; + ProcFlagsExLegacy procEx = (crit ? ProcFlagsExLegacy.CriticalHit : ProcFlagsExLegacy.NormalHit) | ProcFlagsExLegacy.InternalDot; + damage = (damage <= absorb + resist) ? 0 : (damage - absorb - resist); + if (damage != 0) + procVictim |= ProcFlags.TakenDamage; + + int overkill = (int)(damage - target.GetHealth()); + if (overkill < 0) + overkill = 0; + + SpellPeriodicAuraLogInfo pInfo = new SpellPeriodicAuraLogInfo(this, damage, overkill, absorb, resist, 0.0f, crit); + + caster.ProcDamageAndSpell(target, procAttacker, procVictim, procEx, damage, WeaponAttackType.BaseAttack, GetSpellInfo()); + + caster.DealDamage(target, damage, cleanDamage, DamageEffectType.DOT, GetSpellInfo().GetSchoolMask(), GetSpellInfo(), true); + target.SendPeriodicAuraLog(pInfo); + } + + void HandlePeriodicHealthLeechAuraTick(Unit target, Unit caster) + { + if (!caster || !target.IsAlive()) + return; + + if (target.HasUnitState(UnitState.Isolated) || target.IsImmunedToDamage(GetSpellInfo())) + { + SendTickImmune(target, caster); + return; + } + + if (GetSpellEffectInfo().Effect == SpellEffectName.PersistentAreaAura && + caster.SpellHitResult(target, GetSpellInfo(), false) != SpellMissInfo.None) + return; + + uint absorb = 0; + uint resist = 0; + CleanDamage cleanDamage = new CleanDamage(0, 0, WeaponAttackType.BaseAttack, MeleeHitOutcome.Normal); + + bool isAreaAura = GetSpellEffectInfo().IsAreaAuraEffect() || GetSpellEffectInfo().IsEffect(SpellEffectName.PersistentAreaAura); + // ignore negative values (can be result apply spellmods to aura damage + uint damage = (uint)(isAreaAura ? Math.Max(GetAmount(), 0) : m_damage); + + if (isAreaAura) + { + // Script Hook For HandlePeriodicDamageAurasTick -- Allow scripts to change the Damage pre class mitigation calculations + Global.ScriptMgr.ModifyPeriodicDamageAurasTick(target, caster, ref damage); + damage = (uint)(caster.SpellDamageBonusDone(target, GetSpellInfo(), damage, DamageEffectType.DOT, GetSpellEffectInfo(), GetBase().GetStackAmount()) * caster.SpellDamagePctDone(target, m_spellInfo, DamageEffectType.DOT)); + } + damage = target.SpellDamageBonusTaken(caster, GetSpellInfo(), damage, DamageEffectType.DOT, GetSpellEffectInfo(), GetBase().GetStackAmount()); + + // Calculate armor mitigation + if (caster.IsDamageReducedByArmor(GetSpellInfo().GetSchoolMask(), GetSpellInfo(), (sbyte)GetEffIndex())) + { + uint damageReductedArmor = caster.CalcArmorReducedDamage(caster, target, damage, GetSpellInfo()); + cleanDamage.mitigated_damage += damage - damageReductedArmor; + damage = damageReductedArmor; + } + + if (!m_spellInfo.HasAttribute(SpellAttr4.FixedDamage)) + { + if (GetSpellEffectInfo().IsTargetingArea() || isAreaAura) + { + damage = (uint)(damage * target.GetTotalAuraMultiplierByMiscMask(AuraType.ModAoeDamageAvoidance, (uint)m_spellInfo.SchoolMask)); + if (!caster.IsTypeId(TypeId.Player)) + damage = (uint)(damage * target.GetTotalAuraMultiplierByMiscMask(AuraType.ModCreatureAoeDamageAvoidance, (uint)m_spellInfo.SchoolMask)); + } + } + + bool crit = false; + if (CanPeriodicTickCrit(caster)) + crit = RandomHelper.randChance(isAreaAura ? caster.GetUnitSpellCriticalChance(target, m_spellInfo, m_spellInfo.GetSchoolMask()) : m_critChance); + + if (crit) + damage = caster.SpellCriticalDamageBonus(m_spellInfo, damage, target); + + if (!GetSpellInfo().HasAttribute(SpellAttr4.FixedDamage)) + caster.ApplyResilience(target, ref damage); + + caster.CalcAbsorbResist(target, GetSpellInfo().GetSchoolMask(), DamageEffectType.DOT, damage, ref absorb, ref resist, m_spellInfo); + + SpellNonMeleeDamage log = new SpellNonMeleeDamage(caster, target, GetId(), GetBase().GetSpellXSpellVisualId(), GetSpellInfo().GetSchoolMask(), GetBase().GetCastGUID()); + log.damage = damage - absorb - resist; + log.absorb = absorb; + log.resist = resist; + log.periodicLog = true; + if (crit) + log.HitInfo |= SpellHitType.Crit; + + // Set trigger flag + ProcFlags procAttacker = ProcFlags.DonePeriodic; + ProcFlags procVictim = ProcFlags.TakenPeriodic; + ProcFlagsExLegacy procEx = (crit ? ProcFlagsExLegacy.CriticalHit : ProcFlagsExLegacy.NormalHit) | ProcFlagsExLegacy.InternalDot; + damage = (damage <= absorb + resist) ? 0 : (damage - absorb - resist); + if (damage != 0) + procVictim |= ProcFlags.TakenDamage; + if (caster.IsAlive()) + caster.ProcDamageAndSpell(target, procAttacker, procVictim, procEx, damage, WeaponAttackType.BaseAttack, GetSpellInfo()); + int new_damage = (int)caster.DealDamage(target, damage, cleanDamage, DamageEffectType.DOT, GetSpellInfo().GetSchoolMask(), GetSpellInfo(), false); + if (caster.IsAlive()) + { + float gainMultiplier = GetSpellEffectInfo().CalcValueMultiplier(caster); + + uint heal = (caster.SpellHealingBonusDone(caster, GetSpellInfo(), (uint)(new_damage * gainMultiplier), DamageEffectType.DOT, GetSpellEffectInfo(), GetBase().GetStackAmount())); + heal = (caster.SpellHealingBonusTaken(caster, GetSpellInfo(), heal, DamageEffectType.DOT, GetSpellEffectInfo(), GetBase().GetStackAmount())); + + int gain = caster.HealBySpell(caster, GetSpellInfo(), heal); + caster.getHostileRefManager().threatAssist(caster, gain * 0.5f, GetSpellInfo()); + } + + caster.SendSpellNonMeleeDamageLog(log); + } + + void HandlePeriodicHealthFunnelAuraTick(Unit target, Unit caster) + { + if (caster == null || !caster.IsAlive() || !target.IsAlive()) + return; + + if (target.HasUnitState(UnitState.Isolated)) + { + SendTickImmune(target, caster); + return; + } + + uint damage = (uint)Math.Max(GetAmount(), 0); + // do not kill health donator + if (caster.GetHealth() < damage) + damage = (uint)caster.GetHealth() - 1; + if (damage == 0) + return; + + caster.ModifyHealth(-(int)damage); + Log.outDebug(LogFilter.Spells, "PeriodicTick: donator {0} target {1} damage {2}.", caster.GetEntry(), target.GetEntry(), damage); + + float gainMultiplier = GetSpellEffectInfo().CalcValueMultiplier(caster); + + damage = (uint)(damage * gainMultiplier); + + caster.HealBySpell(target, GetSpellInfo(), damage); + } + + void HandlePeriodicHealAurasTick(Unit target, Unit caster) + { + if (!caster || !target.IsAlive()) + return; + + if (target.HasUnitState(UnitState.Isolated)) + { + SendTickImmune(target, caster); + return; + } + + // heal for caster damage (must be alive) + if (target != caster && GetSpellInfo().HasAttribute(SpellAttr2.HealthFunnel) && !caster.IsAlive()) + return; + + // don't regen when permanent aura target has full power + if (GetBase().IsPermanent() && target.IsFullHealth()) + return; + + bool isAreaAura = GetSpellEffectInfo().IsAreaAuraEffect() || GetSpellEffectInfo().IsEffect(SpellEffectName.PersistentAreaAura); + // ignore negative values (can be result apply spellmods to aura damage + int damage = isAreaAura ? Math.Max(GetAmount(), 0) : m_damage; + + if (GetAuraType() == AuraType.ObsModHealth) + { + // Taken mods + float TakenTotalMod = 1.0f; + + // Tenacity increase healing % taken + AuraEffect Tenacity = target.GetAuraEffect(58549, 0); + if (Tenacity != null) + MathFunctions.AddPct(ref TakenTotalMod, Tenacity.GetAmount()); + + // Healing taken percent + float minval = target.GetMaxNegativeAuraModifier(AuraType.ModHealingPct); + if (minval != 0) + MathFunctions.AddPct(ref TakenTotalMod, minval); + + float maxval = target.GetMaxPositiveAuraModifier(AuraType.ModHealingPct); + if (maxval != 0) + MathFunctions.AddPct(ref TakenTotalMod, maxval); + + TakenTotalMod = Math.Max(TakenTotalMod, 0.0f); + + damage = (int)target.CountPctFromMaxHealth(damage); + damage = (int)(damage * TakenTotalMod); + } + else + { + // Wild Growth = amount + (6 - 2*doneTicks) * ticks* amount / 100 + if (m_spellInfo.SpellFamilyName == SpellFamilyNames.Druid && m_spellInfo.SpellFamilyFlags & new FlagArray128(0, 0x04000000, 0, 0)) + { + int addition = (int)((damage * GetTotalTicks()) * ((6 - (2 * (GetTickNumber() - 1))) / 100)); + + // Item - Druid T10 Restoration 2P Bonus + AuraEffect aurEff = caster.GetAuraEffect(70658, 0); + if (aurEff != null) + // divided by 50 instead of 100 because calculated as for every 2 tick + addition += Math.Abs((addition * aurEff.GetAmount()) / 50); + + damage += addition; + } + if (isAreaAura) + damage = (int)(caster.SpellHealingBonusDone(target, GetSpellInfo(), (uint)damage, DamageEffectType.DOT, GetSpellEffectInfo(), GetBase().GetStackAmount()) * caster.SpellHealingPctDone(target, m_spellInfo)); + damage = (int)target.SpellHealingBonusTaken(caster, GetSpellInfo(), (uint)damage, DamageEffectType.DOT, GetSpellEffectInfo(), GetBase().GetStackAmount()); + } + + bool crit = false; + if (CanPeriodicTickCrit(caster)) + crit = RandomHelper.randChance(isAreaAura ? caster.GetUnitSpellCriticalChance(target, m_spellInfo, m_spellInfo.GetSchoolMask()) : m_critChance); + + if (crit) + damage = caster.SpellCriticalHealingBonus(m_spellInfo, damage, target); + + Log.outDebug(LogFilter.Spells, "PeriodicTick: {0} (TypeId: {1}) heal of {2} (TypeId: {3}) for {4} health inflicted by {5}", + GetCasterGUID().ToString(), GetCaster().GetTypeId(), target.GetGUID().ToString(), target.GetTypeId(), damage, GetId()); + + uint absorb = 0; + uint heal = (uint)damage; + caster.CalcHealAbsorb(target, GetSpellInfo(), ref heal, ref absorb); + int gain = caster.DealHeal(target, heal); + + SpellPeriodicAuraLogInfo pInfo = new SpellPeriodicAuraLogInfo(this, heal, (int)(heal - gain), absorb, 0, 0.0f, crit); + target.SendPeriodicAuraLog(pInfo); + + target.getHostileRefManager().threatAssist(caster, gain * 0.5f, GetSpellInfo()); + + ProcFlags procAttacker = ProcFlags.DonePeriodic; + ProcFlags procVictim = ProcFlags.TakenPeriodic; + ProcFlagsExLegacy procEx = (crit ? ProcFlagsExLegacy.CriticalHit : ProcFlagsExLegacy.NormalHit) | ProcFlagsExLegacy.InternalHot; + // ignore item heals + if (GetBase().GetCastItemGUID().IsEmpty()) + caster.ProcDamageAndSpell(target, procAttacker, procVictim, procEx, (uint)damage, WeaponAttackType.BaseAttack, GetSpellInfo()); + } + + void HandlePeriodicManaLeechAuraTick(Unit target, Unit caster) + { + PowerType powerType = (PowerType)GetMiscValue(); + + if (caster == null || !caster.IsAlive() || !target.IsAlive() || target.getPowerType() != powerType) + return; + + if (target.HasUnitState(UnitState.Isolated) || target.IsImmunedToDamage(GetSpellInfo())) + { + SendTickImmune(target, caster); + return; + } + + if (GetSpellEffectInfo().Effect == SpellEffectName.PersistentAreaAura && + caster.SpellHitResult(target, GetSpellInfo(), false) != SpellMissInfo.None) + return; + + // ignore negative values (can be result apply spellmods to aura damage + int drainAmount = Math.Max(m_amount, 0); + + int drainedAmount = -target.ModifyPower(powerType, -drainAmount); + float gainMultiplier = GetSpellEffectInfo().CalcValueMultiplier(caster); + + SpellPeriodicAuraLogInfo pInfo = new SpellPeriodicAuraLogInfo(this, (uint)drainedAmount, 0, 0, 0, gainMultiplier, false); + + int gainAmount = (int)(drainedAmount * gainMultiplier); + int gainedAmount = 0; + if (gainAmount != 0) + { + gainedAmount = caster.ModifyPower(powerType, gainAmount); + target.AddThreat(caster, gainedAmount * 0.5f, GetSpellInfo().GetSchoolMask(), GetSpellInfo()); + } + + // Drain Mana + if (m_spellInfo.SpellFamilyName == SpellFamilyNames.Warlock && m_spellInfo.SpellFamilyFlags[0].HasAnyFlag(0x00000010)) + { + int manaFeedVal = 0; + AuraEffect aurEff = GetBase().GetEffect(1); + if (aurEff != null) + manaFeedVal = aurEff.GetAmount(); + // Mana Feed - Drain Mana + if (manaFeedVal > 0) + { + int feedAmount = MathFunctions.CalculatePct(gainedAmount, manaFeedVal); + caster.CastCustomSpell(caster, 32554, feedAmount, 0, 0, true, null, this); + } + } + + target.SendPeriodicAuraLog(pInfo); + } + + void HandleObsModPowerAuraTick(Unit target, Unit caster) + { + PowerType powerType; + if (GetMiscValue() == (int)PowerType.All) + powerType = target.getPowerType(); + else + powerType = (PowerType)GetMiscValue(); + + if (!target.IsAlive() || target.GetMaxPower(powerType) == 0) + return; + + if (target.HasUnitState(UnitState.Isolated)) + { + SendTickImmune(target, caster); + return; + } + + // don't regen when permanent aura target has full power + if (GetBase().IsPermanent() && target.GetPower(powerType) == target.GetMaxPower(powerType)) + return; + + // ignore negative values (can be result apply spellmods to aura damage + int amount = Math.Max(m_amount, 0) * target.GetMaxPower(powerType) / 100; + + SpellPeriodicAuraLogInfo pInfo = new SpellPeriodicAuraLogInfo(this, (uint)amount, 0, 0, 0, 0.0f, false); + + int gain = target.ModifyPower(powerType, amount); + + if (caster != null) + target.getHostileRefManager().threatAssist(caster, gain * 0.5f, GetSpellInfo()); + + target.SendPeriodicAuraLog(pInfo); + } + + void HandlePeriodicEnergizeAuraTick(Unit target, Unit caster) + { + PowerType powerType = (PowerType)GetMiscValue(); + if (!target.IsAlive() || target.GetMaxPower(powerType) == 0) + return; + + if (target.HasUnitState(UnitState.Isolated)) + { + SendTickImmune(target, caster); + return; + } + + // don't regen when permanent aura target has full power + if (GetBase().IsPermanent() && target.GetPower(powerType) == target.GetMaxPower(powerType)) + return; + + // ignore negative values (can be result apply spellmods to aura damage + int amount = Math.Max(m_amount, 0); + + SpellPeriodicAuraLogInfo pInfo = new SpellPeriodicAuraLogInfo(this, (uint)amount, 0, 0, 0, 0.0f, false); + int gain = target.ModifyPower(powerType, amount); + + if (caster != null) + target.getHostileRefManager().threatAssist(caster, gain * 0.5f, GetSpellInfo()); + + target.SendPeriodicAuraLog(pInfo); + } + + void HandlePeriodicPowerBurnAuraTick(Unit target, Unit caster) + { + PowerType powerType = (PowerType)GetMiscValue(); + + if (caster == null || !target.IsAlive() || target.getPowerType() != powerType) + return; + + if (target.HasUnitState(UnitState.Isolated) || target.IsImmunedToDamage(GetSpellInfo())) + { + SendTickImmune(target, caster); + return; + } + + // ignore negative values (can be result apply spellmods to aura damage + int damage = Math.Max(m_amount, 0); + + uint gain = (uint)(-target.ModifyPower(powerType, -damage)); + + float dmgMultiplier = GetSpellEffectInfo().CalcValueMultiplier(caster); + + SpellInfo spellProto = GetSpellInfo(); + // maybe has to be sent different to client, but not by SMSG_PERIODICAURALOG + SpellNonMeleeDamage damageInfo = new SpellNonMeleeDamage(caster, target, spellProto.Id, GetBase().GetSpellXSpellVisualId(), spellProto.SchoolMask, GetBase().GetCastGUID()); + // no SpellDamageBonus for burn mana + caster.CalculateSpellDamageTaken(damageInfo, (int)(gain * dmgMultiplier), spellProto); + + caster.DealDamageMods(damageInfo.target, ref damageInfo.damage, ref damageInfo.absorb); + + // Set trigger flag + ProcFlags procAttacker = ProcFlags.DonePeriodic; + ProcFlags procVictim = ProcFlags.TakenPeriodic; + ProcFlagsExLegacy procEx = Unit.createProcExtendMask(damageInfo, SpellMissInfo.None) | ProcFlagsExLegacy.InternalDot; + if (damageInfo.damage != 0) + procVictim |= ProcFlags.TakenDamage; + + caster.ProcDamageAndSpell(damageInfo.target, procAttacker, procVictim, procEx, damageInfo.damage, WeaponAttackType.BaseAttack, spellProto); + + caster.DealSpellDamage(damageInfo, true); + caster.SendSpellNonMeleeDamageLog(damageInfo); + } + + void HandleProcTriggerSpellAuraProc(AuraApplication aurApp, ProcEventInfo eventInfo) + { + Unit triggerCaster = aurApp.GetTarget(); + Unit triggerTarget = eventInfo.GetProcTarget(); + + uint triggerSpellId = GetSpellEffectInfo().TriggerSpell; + SpellInfo triggeredSpellInfo = Global.SpellMgr.GetSpellInfo(triggerSpellId); + if (triggeredSpellInfo != null) + { + Log.outDebug(LogFilter.Spells, "AuraEffect.HandleProcTriggerSpellAuraProc: Triggering spell {0} from aura {1} proc", triggeredSpellInfo.Id, GetId()); + triggerCaster.CastSpell(triggerTarget, triggeredSpellInfo, true, null, this); + } + else + Log.outDebug(LogFilter.Spells, "AuraEffect.HandleProcTriggerSpellAuraProc: Could not trigger spell {0} from aura {1} proc, because the spell does not have an entry in Spell.dbc.", triggerSpellId, GetId()); + } + + void HandleProcTriggerSpellWithValueAuraProc(AuraApplication aurApp, ProcEventInfo eventInfo) + { + Unit triggerCaster = aurApp.GetTarget(); + Unit triggerTarget = eventInfo.GetProcTarget(); + + uint triggerSpellId = GetSpellEffectInfo().TriggerSpell; + SpellInfo triggeredSpellInfo = Global.SpellMgr.GetSpellInfo(triggerSpellId); + if (triggeredSpellInfo != null) + { + int basepoints0 = GetAmount(); + Log.outDebug(LogFilter.Spells, "AuraEffect.HandleProcTriggerSpellWithValueAuraProc: Triggering spell {0} with value {1} from aura {2} proc", triggeredSpellInfo.Id, basepoints0, GetId()); + triggerCaster.CastCustomSpell(triggerTarget, triggerSpellId, basepoints0, 0, 0, true, null, this); + } + else + Log.outDebug(LogFilter.Spells, "AuraEffect.HandleProcTriggerSpellWithValueAuraProc: Could not trigger spell {0} from aura {1} proc, because the spell does not have an entry in Spell.dbc.", triggerSpellId, GetId()); + } + + public void HandleProcTriggerDamageAuraProc(AuraApplication aurApp, ProcEventInfo eventInfo) + { + Unit target = aurApp.GetTarget(); + Unit triggerTarget = eventInfo.GetProcTarget(); + SpellNonMeleeDamage damageInfo = new SpellNonMeleeDamage(target, triggerTarget, GetId(), GetBase().GetSpellXSpellVisualId(), GetSpellInfo().SchoolMask, GetBase().GetCastGUID()); + int damage = (int)target.SpellDamageBonusDone(triggerTarget, GetSpellInfo(), (uint)GetAmount(), DamageEffectType.SpellDirect, GetSpellEffectInfo()); + damage = (int)triggerTarget.SpellDamageBonusTaken(target, GetSpellInfo(), (uint)damage, DamageEffectType.SpellDirect, GetSpellEffectInfo()); + target.CalculateSpellDamageTaken(damageInfo, damage, GetSpellInfo()); + target.DealDamageMods(damageInfo.target, ref damageInfo.damage, ref damageInfo.absorb); + target.DealSpellDamage(damageInfo, true); + target.SendSpellNonMeleeDamageLog(damageInfo); + } + + void HandleRaidProcFromChargeAuraProc(AuraApplication aurApp, ProcEventInfo eventInfo) + { + Unit target = aurApp.GetTarget(); + + uint triggerSpellId; + switch (GetId()) + { + case 57949: // Shiver + triggerSpellId = 57952; + //animationSpellId = 57951; dummy effects for jump spell have unknown use (see also 41637) + break; + case 59978: // Shiver + triggerSpellId = 59979; + break; + case 43593: // Cold Stare + triggerSpellId = 43594; + break; + default: + Log.outDebug(LogFilter.Spells, "AuraEffect.HandleRaidProcFromChargeAuraProc: received not handled spell: {0}", GetId()); + return; + } + + int jumps = GetBase().GetCharges(); + + // current aura expire on proc finish + GetBase().SetCharges(0); + GetBase().SetUsingCharges(true); + + // next target selection + if (jumps > 0) + { + Unit caster = GetCaster(); + if (caster != null) + { + float radius = GetSpellEffectInfo().CalcRadius(caster); + Unit triggerTarget = target.GetNextRandomRaidMemberOrPet(radius); + if (triggerTarget != null) + { + target.CastSpell(triggerTarget, GetSpellInfo(), true, null, this, GetCasterGUID()); + Aura aura = triggerTarget.GetAura(GetId(), GetCasterGUID()); + if (aura != null) + aura.SetCharges((byte)jumps); + } + } + } + + Log.outDebug(LogFilter.Spells, "AuraEffect.HandleRaidProcFromChargeAuraProc: Triggering spell {0} from aura {1} proc", triggerSpellId, GetId()); + target.CastSpell(target, triggerSpellId, true, null, this, GetCasterGUID()); + } + + void HandleRaidProcFromChargeWithValueAuraProc(AuraApplication aurApp, ProcEventInfo eventInfo) + { + Unit target = aurApp.GetTarget(); + + // Currently only Prayer of Mending + if (!(GetSpellInfo().SpellFamilyName == SpellFamilyNames.Priest) && GetSpellInfo().SpellFamilyFlags[1].HasAnyFlag(0x20)) + { + Log.outDebug(LogFilter.Spells, "AuraEffect.HandleRaidProcFromChargeWithValueAuraProc: received not handled spell: {0}", GetId()); + return; + } + uint triggerSpellId = 33110; + + int value = GetAmount(); + + int jumps = GetBase().GetCharges(); + + // current aura expire on proc finish + GetBase().SetCharges(0); + GetBase().SetUsingCharges(true); + + // next target selection + if (jumps > 0) + { + Unit caster = GetCaster(); + if (caster != null) + { + float radius = GetSpellEffectInfo().CalcRadius(caster); + Unit triggerTarget = target.GetNextRandomRaidMemberOrPet(radius); + if (triggerTarget != null) + { + target.CastCustomSpell(triggerTarget, GetId(), value, 0, 0, true, null, this, GetCasterGUID()); + Aura aura = triggerTarget.GetAura(GetId(), GetCasterGUID()); + if (aura != null) + aura.SetCharges((byte)jumps); + } + } + } + + Log.outDebug(LogFilter.Spells, "AuraEffect.HandleRaidProcFromChargeWithValueAuraProc: Triggering spell {0} from aura {1} proc", triggerSpellId, GetId()); + target.CastCustomSpell(target, triggerSpellId, value, 0, 0, true, null, this, GetCasterGUID()); + } + + public void HandleProcTriggerSpellOnPowerAmountAuraProc(AuraApplication aurApp, ProcEventInfo eventInfo) + { + // Power amount required to proc the spell + int powerAmountRequired = GetAmount(); + // Power type required to proc + PowerType powerRequired = (PowerType)GetSpellInfo().GetEffect(GetEffIndex()).MiscValue; + + if (powerRequired == 0 || powerAmountRequired == 0) + { + Log.outError(LogFilter.Spells, "AuraEffect.HandleProcTriggerSpellOnPowerAmountAuraProc: Spell {0} have 0 PowerAmountRequired in EffectAmount[{1}] or 0 PowerRequired in EffectMiscValue", GetId(), GetEffIndex()); + return /*false*/; + } + + Unit triggerCaster = aurApp.GetTarget(); + Unit triggerTarget = eventInfo.GetProcTarget(); + + if (triggerCaster.GetPower(powerRequired) != powerAmountRequired) + return /*false*/; + + uint triggerSpellId = GetSpellInfo().GetEffect(GetEffIndex()).TriggerSpell; + SpellInfo triggeredSpellInfo = Global.SpellMgr.GetSpellInfo(triggerSpellId); + if (triggeredSpellInfo != null) + { + Log.outDebug(LogFilter.Spells, "AuraEffect.HandleProcTriggerSpellOnPowerAmountAuraProc: Triggering spell {0} from aura {1} proc", triggeredSpellInfo.Id, GetId()); + triggerCaster.CastSpell(triggerTarget, triggeredSpellInfo, true, null, this); + } + else + Log.outDebug(LogFilter.Spells, "AuraEffect.HandleProcTriggerSpellOnPowerAmountAuraProc: Could not trigger spell {0} from aura {1} proc, because the spell does not have an entry in Spell.dbc.", triggerSpellId, GetId()); + } + + [AuraEffectHandler(AuraType.ForceWeather)] + void HandleAuraForceWeather(AuraApplication aurApp, AuraEffectHandleModes mode, bool apply) + { + if (!mode.HasAnyFlag(AuraEffectHandleModes.Real)) + return; + + Player target = aurApp.GetTarget().ToPlayer(); + + if (target == null) + return; + + if (apply) + { + WeatherPkt weather = new WeatherPkt((WeatherState)GetMiscValue(), 1.0f); + target.SendPacket(weather); + } + else + { + // send weather for current zone + Weather weather = Global.WeatherMgr.FindWeather(target.GetZoneId()); + if (weather != null) + weather.SendWeatherUpdateToPlayer(target); + else + { + if (Global.WeatherMgr.AddWeather(target.GetZoneId()) == null) + { + // send fine weather packet to remove old weather + Global.WeatherMgr.SendFineWeatherUpdateToPlayer(target); + } + } + } + } + + [AuraEffectHandler(AuraType.EnableAltPower)] + void HandleEnableAltPower(AuraApplication aurApp, AuraEffectHandleModes mode, bool apply) + { + if (!mode.HasAnyFlag(AuraEffectHandleModes.Real)) + return; + + int altPowerId = GetMiscValue(); + UnitPowerBarRecord powerEntry = CliDB.UnitPowerBarStorage.LookupByKey(altPowerId); + if (powerEntry == null) + return; + + if (apply) + aurApp.GetTarget().SetMaxPower(PowerType.AlternatePower, (int)powerEntry.MaxPower); + else + aurApp.GetTarget().SetMaxPower(PowerType.AlternatePower, 0); + } + + [AuraEffectHandler(AuraType.ModSpellCategoryCooldown)] + void HandleModSpellCategoryCooldown(AuraApplication aurApp, AuraEffectHandleModes mode, bool apply) + { + if (!mode.HasAnyFlag(AuraEffectHandleModes.Real)) + return; + + Player player = aurApp.GetTarget().ToPlayer(); + if (player) + player.SendSpellCategoryCooldowns(); + } + + [AuraEffectHandler(AuraType.ShowConfirmationPrompt)] + [AuraEffectHandler(AuraType.ShowConfirmationPromptWithDifficulty)] + void HandleShowConfirmationPrompt(AuraApplication aurApp, AuraEffectHandleModes mode, bool apply) + { + if (!mode.HasAnyFlag(AuraEffectHandleModes.Real)) + return; + + Player player = aurApp.GetTarget().ToPlayer(); + if (!player) + return; + + if (apply) + player.AddTemporarySpell(_effectInfo.TriggerSpell); + else + player.RemoveTemporarySpell(_effectInfo.TriggerSpell); + } + + [AuraEffectHandler(AuraType.OverridePetSpecs)] + void HandleOverridePetSpecs(AuraApplication aurApp, AuraEffectHandleModes mode, bool apply) + { + if (!mode.HasAnyFlag(AuraEffectHandleModes.Real)) + return; + + Player player = aurApp.GetTarget().ToPlayer(); + if (!player) + return; + + if (player.GetClass() != Class.Hunter) + return; + + Pet pet = player.GetPet(); + if (!pet) + return; + + ChrSpecializationRecord currSpec = CliDB.ChrSpecializationStorage.LookupByKey(pet.GetSpecialization()); + if (currSpec == null) + return; + + pet.SetSpecialization(Global.DB2Mgr.GetChrSpecializationByIndex(apply ? Class.Max : 0, currSpec.OrderIndex).Id); + } + + [AuraEffectHandler(AuraType.AllowUsingGameobjectsWhileMounted)] + void HandleAllowUsingGameobjectsWhileMounted(AuraApplication aurApp, AuraEffectHandleModes mode, bool apply) + { + if (!mode.HasAnyFlag(AuraEffectHandleModes.Real)) + return; + + if (!aurApp.GetTarget().IsTypeId(TypeId.Player)) + return; + + if (apply) + aurApp.GetTarget().SetFlag(PlayerFields.LocalFlags, PlayerLocalFlags.CanUseObjectsMounted); + else if (!aurApp.GetTarget().HasAuraType(AuraType.AllowUsingGameobjectsWhileMounted)) + aurApp.GetTarget().RemoveFlag(PlayerFields.LocalFlags, PlayerLocalFlags.CanUseObjectsMounted); + } + + [AuraEffectHandler(AuraType.PlayScene)] + void HandlePlayScene(AuraApplication aurApp, AuraEffectHandleModes mode, bool apply) + { + if (!mode.HasAnyFlag(AuraEffectHandleModes.Real)) + return; + + Player player = aurApp.GetTarget().ToPlayer(); + if (!player) + return; + + uint sceneId = (uint)GetMiscValue(); + + if (apply) + player.GetSceneMgr().PlayScene(sceneId); + else + { + SceneTemplate sceneTemplate = Global.ObjectMgr.GetSceneTemplate(sceneId); + player.GetSceneMgr().CancelSceneByPackageId(sceneTemplate.ScenePackageId); + } + } + + [AuraEffectHandler(AuraType.AreaTrigger)] + void HandleCreateAreaTrigger(AuraApplication aurApp, AuraEffectHandleModes mode, bool apply) + { + if (!mode.HasAnyFlag(AuraEffectHandleModes.Real)) + return; + + Unit target = aurApp.GetTarget(); + + if (apply) + { + AreaTrigger areaTrigger = new AreaTrigger(); + if (!areaTrigger.CreateAreaTrigger((uint)GetMiscValue(), GetCaster(), target, GetSpellInfo(), target, GetBase().GetDuration(), GetBase().GetSpellXSpellVisualId(), ObjectGuid.Empty, this)) + areaTrigger.Dispose(); + } + else + { + Unit caster = GetCaster(); + if (caster) + caster.RemoveAreaTrigger(this); + } + } + #endregion + } +} diff --git a/Game/Spells/Skills/SkillDiscovery.cs b/Game/Spells/Skills/SkillDiscovery.cs new file mode 100644 index 000000000..a9d7cb055 --- /dev/null +++ b/Game/Spells/Skills/SkillDiscovery.cs @@ -0,0 +1,240 @@ +/* + * Copyright (C) 2012-2017 CypherCore + * + * 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, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +using Framework.Constants; +using Framework.Database; +using Game.Entities; +using System.Collections.Generic; +using System.Linq; +using System.Text; + +namespace Game.Spells +{ + public class SkillDiscovery + { + public static void LoadSkillDiscoveryTable() + { + uint oldMSTime = Time.GetMSTime(); + + SkillDiscoveryStorage.Clear(); // need for reload + + // 0 1 2 3 + SQLResult result = DB.World.Query("SELECT spellId, reqSpell, reqSkillValue, chance FROM skill_discovery_template"); + + if (result.IsEmpty()) + { + Log.outError(LogFilter.ServerLoading, "Loaded 0 skill discovery definitions. DB table `skill_discovery_template` is empty."); + return; + } + + uint count = 0; + + StringBuilder ssNonDiscoverableEntries = new StringBuilder(); + List reportedReqSpells = new List(); + + do + { + uint spellId = result.Read(0); + int reqSkillOrSpell = result.Read(1); + uint reqSkillValue = result.Read(2); + float chance = result.Read(3); + + if (chance <= 0) // chance + { + ssNonDiscoverableEntries.AppendFormat("spellId = {0} reqSkillOrSpell = {1} reqSkillValue = {2} chance = {3} (chance problem)\n", spellId, reqSkillOrSpell, reqSkillValue, chance); + continue; + } + + if (reqSkillOrSpell > 0) // spell case + { + uint absReqSkillOrSpell = (uint)reqSkillOrSpell; + SpellInfo reqSpellInfo = Global.SpellMgr.GetSpellInfo(absReqSkillOrSpell); + if (reqSpellInfo == null) + { + if (!reportedReqSpells.Contains(absReqSkillOrSpell)) + { + Log.outError(LogFilter.Sql, "Spell (ID: {0}) have not existed spell (ID: {1}) in `reqSpell` field in `skill_discovery_template` table", spellId, reqSkillOrSpell); + reportedReqSpells.Add(absReqSkillOrSpell); + } + continue; + } + + // mechanic discovery + if (reqSpellInfo.Mechanic != Mechanics.Discovery && + // explicit discovery ability + !reqSpellInfo.IsExplicitDiscovery()) + { + if (!reportedReqSpells.Contains(absReqSkillOrSpell)) + { + Log.outError(LogFilter.Sql, "Spell (ID: {0}) not have MECHANIC_DISCOVERY (28) value in Mechanic field in spell.dbc" + + " and not 100%% chance random discovery ability but listed for spellId {1} (and maybe more) in `skill_discovery_template` table", + absReqSkillOrSpell, spellId); + reportedReqSpells.Add(absReqSkillOrSpell); + } + continue; + } + + SkillDiscoveryStorage.Add(reqSkillOrSpell, new SkillDiscoveryEntry(spellId, reqSkillValue, chance)); + } + else if (reqSkillOrSpell == 0) // skill case + { + var bounds = Global.SpellMgr.GetSkillLineAbilityMapBounds(spellId); + + if (bounds.Empty()) + { + Log.outError(LogFilter.Sql, "Spell (ID: {0}) not listed in `SkillLineAbility.dbc` but listed with `reqSpell`=0 in `skill_discovery_template` table", spellId); + continue; + } + + foreach (var _spell_idx in bounds) + SkillDiscoveryStorage.Add(-(int)_spell_idx.SkillLine, new SkillDiscoveryEntry(spellId, reqSkillValue, chance)); + } + else + { + Log.outError(LogFilter.Sql, "Spell (ID: {0}) have negative value in `reqSpell` field in `skill_discovery_template` table", spellId); + continue; + } + + ++count; + } + while (result.NextRow()); + + if (ssNonDiscoverableEntries.Length != 0) + Log.outError(LogFilter.Sql, "Some items can't be successfully discovered: have in chance field value < 0.000001 in `skill_discovery_template` DB table . List:\n{0}", ssNonDiscoverableEntries.ToString()); + + // report about empty data for explicit discovery spells + foreach (var spellEntry in Global.SpellMgr.GetSpellInfoStorage().Values) + { + // skip not explicit discovery spells + if (!spellEntry.IsExplicitDiscovery()) + continue; + + if (!SkillDiscoveryStorage.ContainsKey((int)spellEntry.Id)) + Log.outError(LogFilter.Sql, "Spell (ID: {0}) is 100% chance random discovery ability but not have data in `skill_discovery_template` table", spellEntry.Id); + } + + Log.outInfo(LogFilter.ServerLoading, "Loaded {0} skill discovery definitions in {1} ms", count, Time.GetMSTimeDiffToNow(oldMSTime)); + } + + public static uint GetExplicitDiscoverySpell(uint spellId, Player player) + { + // explicit discovery spell chances (always success if case exist) + // in this case we have both skill and spell + var tab = SkillDiscoveryStorage.LookupByKey((int)spellId); + if (tab.Empty()) + return 0; + + var bounds = Global.SpellMgr.GetSkillLineAbilityMapBounds(spellId); + uint skillvalue = !bounds.Empty() ? (uint)player.GetSkillValue((SkillType)bounds.FirstOrDefault().SkillLine) : 0; + + float full_chance = 0; + foreach (var item_iter in tab) + if (item_iter.reqSkillValue <= skillvalue) + if (!player.HasSpell(item_iter.spellId)) + full_chance += item_iter.chance; + + float rate = full_chance / 100.0f; + float roll = (float)RandomHelper.randChance() * rate; // roll now in range 0..full_chance + + foreach (var item_iter in tab) + { + if (item_iter.reqSkillValue > skillvalue) + continue; + + if (player.HasSpell(item_iter.spellId)) + continue; + + if (item_iter.chance > roll) + return item_iter.spellId; + + roll -= item_iter.chance; + } + + return 0; + } + + public static bool HasDiscoveredAllSpells(uint spellId, Player player) + { + var tab = SkillDiscoveryStorage.LookupByKey((int)spellId); + if (tab.Empty()) + return true; + + foreach (var item_iter in tab) + if (!player.HasSpell(item_iter.spellId)) + return false; + + return true; + } + + public static uint GetSkillDiscoverySpell(uint skillId, uint spellId, Player player) + { + uint skillvalue = skillId != 0 ? (uint)player.GetSkillValue((SkillType)skillId) : 0; + + // check spell case + var tab = SkillDiscoveryStorage.LookupByKey((int)spellId); + + if (!tab.Empty()) + { + foreach (var item_iter in tab) + { + if (RandomHelper.randChance(item_iter.chance * WorldConfig.GetFloatValue(WorldCfg.RateSkillDiscovery)) && + item_iter.reqSkillValue <= skillvalue && + !player.HasSpell(item_iter.spellId)) + return item_iter.spellId; + } + + return 0; + } + + if (skillId == 0) + return 0; + + // check skill line case + tab = SkillDiscoveryStorage.LookupByKey(-(int)skillId); + if (!tab.Empty()) + { + foreach (var item_iter in tab) + { + if (RandomHelper.randChance(item_iter.chance * WorldConfig.GetFloatValue(WorldCfg.RateSkillDiscovery)) && + item_iter.reqSkillValue <= skillvalue && + !player.HasSpell(item_iter.spellId)) + return item_iter.spellId; + } + + return 0; + } + + return 0; + } + + static MultiMap SkillDiscoveryStorage = new MultiMap(); + } + + public class SkillDiscoveryEntry + { + public SkillDiscoveryEntry(uint _spellId = 0, uint req_skill_val = 0, float _chance = 0) + { + spellId = _spellId; + reqSkillValue = req_skill_val; + chance = _chance; + } + + public uint spellId; // discavered spell + public uint reqSkillValue; // skill level limitation + public float chance; // chance + } +} diff --git a/Game/Spells/Skills/SkillExtraItems.cs b/Game/Spells/Skills/SkillExtraItems.cs new file mode 100644 index 000000000..b43264030 --- /dev/null +++ b/Game/Spells/Skills/SkillExtraItems.cs @@ -0,0 +1,227 @@ +/* + * Copyright (C) 2012-2017 CypherCore + * + * 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, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +using Framework.Database; +using Game.Entities; +using System.Collections.Generic; + +namespace Game.Spells +{ + public class SkillExtraItems + { + // loads the extra item creation info from DB + public static void LoadSkillExtraItemTable() + { + uint oldMSTime = Time.GetMSTime(); + + SkillExtraItemStorage.Clear(); // need for reload + + // 0 1 2 3 + SQLResult result = DB.World.Query("SELECT spellId, requiredSpecialization, additionalCreateChance, additionalMaxNum FROM skill_extra_item_template"); + + if (result.IsEmpty()) + { + Log.outError(LogFilter.ServerLoading, "Loaded 0 spell specialization definitions. DB table `skill_extra_item_template` is empty."); + return; + } + + uint count = 0; + do + { + uint spellId = result.Read(0); + + if (!Global.SpellMgr.HasSpellInfo(spellId)) + { + Log.outError(LogFilter.Sql, "Skill specialization {0} has non-existent spell id in `skill_extra_item_template`!", spellId); + continue; + } + + uint requiredSpecialization = result.Read(1); + if (!Global.SpellMgr.HasSpellInfo(requiredSpecialization)) + { + Log.outError(LogFilter.Sql, "Skill specialization {0} have not existed required specialization spell id {1} in `skill_extra_item_template`!", spellId, requiredSpecialization); + continue; + } + + float additionalCreateChance = result.Read(2); + if (additionalCreateChance <= 0.0f) + { + Log.outError(LogFilter.Sql, "Skill specialization {0} has too low additional create chance in `skill_extra_item_template`!", spellId); + continue; + } + + byte additionalMaxNum = result.Read(3); + if (additionalMaxNum == 0) + { + Log.outError(LogFilter.Sql, "Skill specialization {0} has 0 max number of extra items in `skill_extra_item_template`!", spellId); + continue; + } + + SkillExtraItemEntry skillExtraItemEntry = new SkillExtraItemEntry(); + skillExtraItemEntry.requiredSpecialization = requiredSpecialization; + skillExtraItemEntry.additionalCreateChance = additionalCreateChance; + skillExtraItemEntry.additionalMaxNum = additionalMaxNum; + + SkillExtraItemStorage[spellId] = skillExtraItemEntry; + ++count; + } + while (result.NextRow()); + + Log.outInfo(LogFilter.ServerLoading, "Loaded {0} spell specialization definitions in {1} ms", count, Time.GetMSTimeDiffToNow(oldMSTime)); + } + + public static bool CanCreateExtraItems(Player player, uint spellId, ref float additionalChance, ref byte additionalMax) + { + // get the info for the specified spell + var specEntry = SkillExtraItemStorage.LookupByKey(spellId); + if (specEntry == null) + return false; + + // the player doesn't have the required specialization, return false + if (!player.HasSpell(specEntry.requiredSpecialization)) + return false; + + // set the arguments to the appropriate values + additionalChance = specEntry.additionalCreateChance; + additionalMax = specEntry.additionalMaxNum; + + // enable extra item creation + return true; + } + + static Dictionary SkillExtraItemStorage = new Dictionary(); + } + + class SkillExtraItemEntry + { + public SkillExtraItemEntry(uint rS = 0, float aCC = 0f, byte aMN = 0) + { + requiredSpecialization = rS; + additionalCreateChance = aCC; + additionalMaxNum = aMN; + } + + // the spell id of the specialization required to create extra items + public uint requiredSpecialization; + // the chance to create one additional item + public float additionalCreateChance; + // maximum number of extra items created per crafting + public byte additionalMaxNum; + } + + public class SkillPerfectItems + { + // loads the perfection proc info from DB + public static void LoadSkillPerfectItemTable() + { + uint oldMSTime = Time.GetMSTime(); + + SkillPerfectItemStorage.Clear(); // reload capability + + // 0 1 2 3 + SQLResult result = DB.World.Query("SELECT spellId, requiredSpecialization, perfectCreateChance, perfectItemType FROM skill_perfect_item_template"); + if (result.IsEmpty()) + { + Log.outError(LogFilter.ServerLoading, "Loaded 0 spell perfection definitions. DB table `skill_perfect_item_template` is empty."); + return; + } + + uint count = 0; + do + { + uint spellId = result.Read(0); + if (!Global.SpellMgr.HasSpellInfo(spellId)) + { + Log.outError(LogFilter.Sql, "Skill perfection data for spell {0} has non-existent spell id in `skill_perfect_item_template`!", spellId); + continue; + } + + uint requiredSpecialization = result.Read(1); + if (!Global.SpellMgr.HasSpellInfo(requiredSpecialization)) + { + Log.outError(LogFilter.Sql, "Skill perfection data for spell {0} has non-existent required specialization spell id {1} in `skill_perfect_item_template`!", spellId, requiredSpecialization); + continue; + } + + float perfectCreateChance = result.Read(2); + if (perfectCreateChance <= 0.0f) + { + Log.outError(LogFilter.Sql, "Skill perfection data for spell {0} has impossibly low proc chance in `skill_perfect_item_template`!", spellId); + continue; + } + + uint perfectItemType = result.Read(3); + if (Global.ObjectMgr.GetItemTemplate(perfectItemType) == null) + { + Log.outError(LogFilter.Sql, "Skill perfection data for spell {0} references non-existent perfect item id {1} in `skill_perfect_item_template`!", spellId, perfectItemType); + continue; + } + + SkillPerfectItemStorage[spellId] = new SkillPerfectItemEntry(requiredSpecialization, perfectCreateChance, perfectItemType); + + ++count; + } + while (result.NextRow()); + + Log.outInfo(LogFilter.ServerLoading, "Loaded {0} spell perfection definitions in {1} ms", count, Time.GetMSTimeDiffToNow(oldMSTime)); + } + + public static bool CanCreatePerfectItem(Player player, uint spellId, ref float perfectCreateChance, ref uint perfectItemType) + { + var entry = SkillPerfectItemStorage.LookupByKey(spellId); + // no entry in DB means no perfection proc possible + if (entry == null) + return false; + + // if you don't have the spell needed, then no procs for you + if (!player.HasSpell(entry.requiredSpecialization)) + return false; + + // set values as appropriate + perfectCreateChance = entry.perfectCreateChance; + perfectItemType = entry.perfectItemType; + + // and tell the caller to start rolling the dice + return true; + } + + static Dictionary SkillPerfectItemStorage = new Dictionary(); + } + + // struct to store information about perfection procs + // one entry per spell + class SkillPerfectItemEntry + { + public SkillPerfectItemEntry(uint rS = 0, float pCC = 0f, uint pIT = 0) + { + requiredSpecialization = rS; + perfectCreateChance = pCC; + perfectItemType = pIT; + } + + // the spell id of the spell required - it's named "specialization" to conform with SkillExtraItemEntry + public uint requiredSpecialization; + // perfection proc chance + public float perfectCreateChance; + // itemid of the resulting perfect item + public uint perfectItemType; + } + + + + +} diff --git a/Game/Spells/Spell.cs b/Game/Spells/Spell.cs new file mode 100644 index 000000000..fb3427754 --- /dev/null +++ b/Game/Spells/Spell.cs @@ -0,0 +1,7748 @@ +/* + * Copyright (C) 2012-2017 CypherCore + * + * 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, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +using Framework.Constants; +using Framework.Dynamic; +using Game.BattleFields; +using Game.BattleGrounds; +using Game.Conditions; +using Game.DataStorage; +using Game.Entities; +using Game.Loots; +using Game.Maps; +using Game.Movement; +using Game.Network.Packets; +using Game.Scripting; +using System; +using System.Collections.Generic; +using System.Diagnostics.Contracts; +using System.Linq; +using System.Runtime.InteropServices; + +namespace Game.Spells +{ + public partial class Spell : IDisposable + { + public Spell(Unit caster, SpellInfo info, TriggerCastFlags triggerFlags, ObjectGuid originalCasterGUID = default(ObjectGuid), bool skipcheck = false) + { + m_spellInfo = info; + m_caster = (info.HasAttribute(SpellAttr6.CastByCharmer) && caster.GetCharmerOrOwner() != null ? caster.GetCharmerOrOwner() : caster); + m_spellValue = new SpellValue(caster.GetMap().GetDifficultyID(), m_spellInfo); + m_preGeneratedPath = new PathGenerator(m_caster); + m_castItemLevel = -1; + _effects = info.GetEffectsForDifficulty(caster.GetMap().GetDifficultyID()); + m_castId = ObjectGuid.Create(HighGuid.Cast, SpellCastSource.Normal, m_caster.GetMapId(), m_spellInfo.Id, m_caster.GetMap().GenerateLowGuid(HighGuid.Cast)); + m_SpellVisual = caster.GetCastSpellXSpellVisualId(m_spellInfo); + + m_customError = SpellCustomErrors.None; + m_skipCheck = skipcheck; + m_fromClient = false; + m_needComboPoints = m_spellInfo.NeedsComboPoints(); + + // Get data for type of attack + switch (m_spellInfo.DmgClass) + { + case SpellDmgClass.Melee: + if (m_spellInfo.HasAttribute(SpellAttr3.ReqOffhand)) + m_attackType = WeaponAttackType.OffAttack; + else + m_attackType = WeaponAttackType.BaseAttack; + break; + case SpellDmgClass.Ranged: + m_attackType = m_spellInfo.IsRangedWeaponSpell() ? WeaponAttackType.RangedAttack : WeaponAttackType.BaseAttack; + break; + default: + // Wands + if (m_spellInfo.HasAttribute(SpellAttr2.AutorepeatFlag)) + m_attackType = WeaponAttackType.RangedAttack; + else + m_attackType = WeaponAttackType.BaseAttack; + break; + } + + m_spellSchoolMask = m_spellInfo.GetSchoolMask(); // Can be override for some spell (wand shoot for example) + + if (m_attackType == WeaponAttackType.RangedAttack) + { + if ((m_caster.getClassMask() & (uint)Class.ClassMaskWandUsers) != 0 && m_caster.IsTypeId(TypeId.Player)) + { + Item pItem = m_caster.ToPlayer().GetWeaponForAttack(WeaponAttackType.RangedAttack); + if (pItem != null) + m_spellSchoolMask = (SpellSchoolMask)(1 << (int)pItem.GetTemplate().GetDamageType()); + } + } + + if (!originalCasterGUID.IsEmpty()) + m_originalCasterGUID = originalCasterGUID; + else + m_originalCasterGUID = m_caster.GetGUID(); + + if (m_originalCasterGUID == m_caster.GetGUID()) + m_originalCaster = m_caster; + else + { + m_originalCaster = Global.ObjAccessor.GetUnit(m_caster, m_originalCasterGUID); + if (m_originalCaster != null && !m_originalCaster.IsInWorld) + m_originalCaster = null; + } + + m_spellState = SpellState.None; + _triggeredCastFlags = triggerFlags; + if (m_spellInfo.HasAttribute(SpellAttr4.CanCastWhileCasting)) + _triggeredCastFlags = _triggeredCastFlags | TriggerCastFlags.IgnoreCastInProgress | TriggerCastFlags.CastDirectly; + + effectHandleMode = SpellEffectHandleMode.Launch; + m_diminishLevel = DiminishingLevels.Level1; + m_diminishGroup = DiminishingGroup.None; + + //Auto Shot & Shoot (wand) + m_autoRepeat = m_spellInfo.IsAutoRepeatRangedSpell(); + + // Determine if spell can be reflected back to the caster + // Patch 1.2 notes: Spell Reflection no longer reflects abilities + m_canReflect = m_spellInfo.DmgClass == SpellDmgClass.Magic && !m_spellInfo.HasAttribute(SpellAttr0.Ability) + && !m_spellInfo.HasAttribute(SpellAttr1.CantBeReflected) && !m_spellInfo.HasAttribute(SpellAttr0.UnaffectedByInvulnerability) + && !m_spellInfo.IsPassive() && !m_spellInfo.IsPositive(); + + CleanupTargetList(); + + for (var i = 0; i < SpellConst.MaxEffects; ++i) + m_destTargets[i] = new SpellDestination(m_caster); + + //not sure needed. + m_targets = new SpellCastTargets(); + m_appliedMods = new List(); + } + + public virtual void Dispose() + { + // unload scripts + foreach (var script in m_loadedScripts) + script._Unload(); + + if (m_referencedFromCurrentSpell && m_selfContainer && m_selfContainer == this) + { + // Clean the reference to avoid later crash. + // If this error is repeating, we may have to add an ASSERT to better track down how we get into this case. + Log.outError(LogFilter.Spells, "SPELL: deleting spell for spell ID {0}. However, spell still referenced.", m_spellInfo.Id); + m_selfContainer = null; + } + + if (m_caster && m_caster.GetTypeId() == TypeId.Player) + Contract.Assert(m_caster.ToPlayer().m_spellModTakingSpell != this); + } + + void InitExplicitTargets(SpellCastTargets targets) + { + m_targets = targets; + m_targets.SetOrigUnitTarget(m_targets.GetUnitTarget()); + // this function tries to correct spell explicit targets for spell + // client doesn't send explicit targets correctly sometimes - we need to fix such spells serverside + // this also makes sure that we correctly send explicit targets to client (removes redundant data) + SpellCastTargetFlags neededTargets = m_spellInfo.GetExplicitTargetMask(); + + WorldObject target = m_targets.GetObjectTarget(); + if (target != null) + { + // check if object target is valid with needed target flags + // for unit case allow corpse target mask because player with not released corpse is a unit target + if ((target.ToUnit() && !Convert.ToBoolean(neededTargets & (SpellCastTargetFlags.UnitMask | SpellCastTargetFlags.CorpseMask))) + || (target.IsTypeId(TypeId.GameObject) && !Convert.ToBoolean(neededTargets & SpellCastTargetFlags.GameobjectMask)) + || (target.IsTypeId(TypeId.Corpse) && !Convert.ToBoolean(neededTargets & SpellCastTargetFlags.CorpseMask))) + m_targets.RemoveObjectTarget(); + } + else + { + // try to select correct unit target if not provided by client or by serverside cast + if (Convert.ToBoolean(neededTargets & SpellCastTargetFlags.UnitMask)) + { + Unit unit = null; + // try to use player selection as a target + Player playerCaster = m_caster.ToPlayer(); + if (playerCaster != null) + { + // selection has to be found and to be valid target for the spell + Unit selectedUnit = Global.ObjAccessor.GetUnit(m_caster, playerCaster.GetTarget()); + if (selectedUnit != null) + if (m_spellInfo.CheckExplicitTarget(m_caster, selectedUnit) == SpellCastResult.SpellCastOk) + unit = selectedUnit; + } + // try to use attacked unit as a target + else if ((m_caster.IsTypeId(TypeId.Unit)) && Convert.ToBoolean(neededTargets & (SpellCastTargetFlags.UnitEnemy | SpellCastTargetFlags.Unit))) + unit = m_caster.GetVictim(); + + // didn't find anything - let's use self as target + if (unit == null && Convert.ToBoolean(neededTargets & (SpellCastTargetFlags.UnitRaid | SpellCastTargetFlags.UnitParty | SpellCastTargetFlags.UnitAlly))) + unit = m_caster; + + m_targets.SetUnitTarget(unit); + } + } + + // check if spell needs dst target + if (Convert.ToBoolean(neededTargets & SpellCastTargetFlags.DestLocation)) + { + // and target isn't set + if (!m_targets.HasDst()) + { + // try to use unit target if provided + WorldObject targett = targets.GetObjectTarget(); + if (targett != null) + m_targets.SetDst(targett); + // or use self if not available + else + m_targets.SetDst(m_caster); + } + } + else + m_targets.RemoveDst(); + + if (Convert.ToBoolean(neededTargets & SpellCastTargetFlags.SourceLocation)) + { + if (!targets.HasSrc()) + m_targets.SetSrc(m_caster); + } + else + m_targets.RemoveSrc(); + } + + void SelectExplicitTargets() + { + // here go all explicit target changes made to explicit targets after spell prepare phase is finished + Unit target = m_targets.GetUnitTarget(); + if (target != null) + { + // check for explicit target redirection, for Grounding Totem for example + if (m_spellInfo.GetExplicitTargetMask().HasAnyFlag(SpellCastTargetFlags.UnitEnemy) + || (m_spellInfo.GetExplicitTargetMask().HasAnyFlag(SpellCastTargetFlags.Unit) && !m_caster.IsFriendlyTo(target))) + { + Unit redirect; + switch (m_spellInfo.DmgClass) + { + case SpellDmgClass.Magic: + redirect = m_caster.GetMagicHitRedirectTarget(target, m_spellInfo); + break; + case SpellDmgClass.Melee: + case SpellDmgClass.Ranged: + redirect = m_caster.GetMeleeHitRedirectTarget(target, m_spellInfo); + break; + default: + redirect = null; + break; + } + if (redirect != null && (redirect != target)) + m_targets.SetUnitTarget(redirect); + } + } + } + + public void SelectSpellTargets() + { + // select targets for cast phase + SelectExplicitTargets(); + + uint processedAreaEffectsMask = 0; + foreach (SpellEffectInfo effect in GetEffects()) + { + if (effect == null) + continue; + + // not call for empty effect. + // Also some spells use not used effect targets for store targets for dummy effect in triggered spells + if (!effect.IsEffect()) + continue; + + // set expected type of implicit targets to be sent to client + SpellCastTargetFlags implicitTargetMask = SpellInfo.GetTargetFlagMask(effect.TargetA.GetObjectType()) | SpellInfo.GetTargetFlagMask(effect.TargetB.GetObjectType()); + if (Convert.ToBoolean(implicitTargetMask & SpellCastTargetFlags.Unit)) + m_targets.SetTargetFlag(SpellCastTargetFlags.Unit); + if (Convert.ToBoolean(implicitTargetMask & (SpellCastTargetFlags.Gameobject | SpellCastTargetFlags.GameobjectItem))) + m_targets.SetTargetFlag(SpellCastTargetFlags.Gameobject); + + SelectEffectImplicitTargets(effect.EffectIndex, effect.TargetA, processedAreaEffectsMask); + SelectEffectImplicitTargets(effect.EffectIndex, effect.TargetB, processedAreaEffectsMask); + + // Select targets of effect based on effect type + // those are used when no valid target could be added for spell effect based on spell target type + // some spell effects use explicit target as a default target added to target map (like SPELL_EFFECT_LEARN_SPELL) + // some spell effects add target to target map only when target type specified (like SPELL_EFFECT_WEAPON) + // some spell effects don't add anything to target map (confirmed with sniffs) (like SPELL_EFFECT_DESTROY_ALL_TOTEMS) + SelectEffectTypeImplicitTargets(effect.EffectIndex); + + if (m_targets.HasDst()) + AddDestTarget(m_targets.GetDst(), effect.EffectIndex); + + if (m_spellInfo.IsChanneled()) + { + uint mask = (1u << (int)effect.EffectIndex); + foreach (var ihit in m_UniqueTargetInfo) + { + if (Convert.ToBoolean(ihit.effectMask & mask)) + { + m_channelTargetEffectMask |= mask; + break; + } + } + } + else if (m_auraScaleMask != 0) + { + bool checkLvl = !m_UniqueTargetInfo.Empty(); + foreach (var ihit in m_UniqueTargetInfo) + { + // remove targets which did not pass min level check + if (m_auraScaleMask != 0 && ihit.effectMask == m_auraScaleMask) + { + // Do not check for selfcast + if (!ihit.scaleAura && ihit.targetGUID != m_caster.GetGUID()) + { + m_UniqueTargetInfo.Remove(ihit); + continue; + } + } + } + if (checkLvl && m_UniqueTargetInfo.Empty()) + { + SendCastResult(SpellCastResult.Lowlevel); + finish(false); + } + } + } + + if (m_targets.HasDst()) + { + if (m_targets.HasTraj()) + { + float speed = m_targets.GetSpeedXY(); + if (speed > 0.0f) + m_delayMoment = (ulong)Math.Floor(m_targets.GetDist2d() / speed * 1000.0f); + } + else if (m_spellInfo.Speed > 0.0f) + { + float dist = m_caster.GetDistance(m_targets.GetDstPos()); + if (!m_spellInfo.HasAttribute(SpellAttr9.SpecialDelayCalculation)) + m_delayMoment = (ulong)Math.Floor(dist / m_spellInfo.Speed * 1000.0f); + else + m_delayMoment = (ulong)(m_spellInfo.Speed * 1000.0f); + } + } + } + + void SelectEffectImplicitTargets(uint effIndex, SpellImplicitTargetInfo targetType, uint processedEffectMask) + { + if (targetType.GetTarget() == 0) + return; + + uint effectMask = (uint)(1 << (int)effIndex); + // set the same target list for all effects + // some spells appear to need this, however this requires more research + switch (targetType.GetSelectionCategory()) + { + case SpellTargetSelectionCategories.Nearby: + case SpellTargetSelectionCategories.Cone: + case SpellTargetSelectionCategories.Area: + // targets for effect already selected + if (Convert.ToBoolean(effectMask & processedEffectMask)) + return; + SpellEffectInfo _effect = GetEffect(effIndex); + if (_effect != null) + { + // choose which targets we can select at once + foreach (SpellEffectInfo effect in GetEffects()) + { + if (effect == null || effect.EffectIndex <= effIndex) + continue; + + if (effect.IsEffect() && + _effect.TargetA.GetTarget() == effect.TargetA.GetTarget() && + _effect.TargetB.GetTarget() == effect.TargetB.GetTarget() && + _effect.ImplicitTargetConditions == effect.ImplicitTargetConditions && + _effect.CalcRadius(m_caster) == effect.CalcRadius(m_caster) && + CheckScriptEffectImplicitTargets(effIndex, effect.EffectIndex)) + { + effectMask |= (uint)(1 << (int)effect.EffectIndex); + } + } + } + processedEffectMask |= effectMask; + break; + default: + break; + } + + switch (targetType.GetSelectionCategory()) + { + case SpellTargetSelectionCategories.Channel: + SelectImplicitChannelTargets(effIndex, targetType); + break; + case SpellTargetSelectionCategories.Nearby: + SelectImplicitNearbyTargets(effIndex, targetType, effectMask); + break; + case SpellTargetSelectionCategories.Cone: + SelectImplicitConeTargets(effIndex, targetType, effectMask); + break; + case SpellTargetSelectionCategories.Area: + SelectImplicitAreaTargets(effIndex, targetType, effectMask); + break; + case SpellTargetSelectionCategories.Default: + switch (targetType.GetObjectType()) + { + case SpellTargetObjectTypes.Src: + switch (targetType.GetReferenceType()) + { + case SpellTargetReferenceTypes.Caster: + m_targets.SetSrc(m_caster); + break; + default: + Contract.Assert(false, "Spell.SelectEffectImplicitTargets: received not implemented select target reference type for TARGET_TYPE_OBJECT_SRC"); + break; + } + break; + case SpellTargetObjectTypes.Dest: + switch (targetType.GetReferenceType()) + { + case SpellTargetReferenceTypes.Caster: + SelectImplicitCasterDestTargets(effIndex, targetType); + break; + case SpellTargetReferenceTypes.Target: + SelectImplicitTargetDestTargets(effIndex, targetType); + break; + case SpellTargetReferenceTypes.Dest: + SelectImplicitDestDestTargets(effIndex, targetType); + break; + default: + Contract.Assert(false, "Spell.SelectEffectImplicitTargets: received not implemented select target reference type for TARGET_TYPE_OBJECT_DEST"); + break; + } + break; + default: + switch (targetType.GetReferenceType()) + { + case SpellTargetReferenceTypes.Caster: + SelectImplicitCasterObjectTargets(effIndex, targetType); + break; + case SpellTargetReferenceTypes.Target: + SelectImplicitTargetObjectTargets(effIndex, targetType); + break; + default: + Contract.Assert(false, "Spell.SelectEffectImplicitTargets: received not implemented select target reference type for TARGET_TYPE_OBJECT"); + break; + } + break; + } + break; + case SpellTargetSelectionCategories.Nyi: + Log.outDebug(LogFilter.Spells, "SPELL: target type {0}, found in spellID {1}, effect {2} is not implemented yet!", m_spellInfo.Id, effIndex, targetType.GetTarget()); + break; + default: + Contract.Assert(false, "Spell.SelectEffectImplicitTargets: received not implemented select target category"); + break; + } + } + + void SelectImplicitChannelTargets(uint effIndex, SpellImplicitTargetInfo targetType) + { + if (targetType.GetReferenceType() != SpellTargetReferenceTypes.Caster) + { + Contract.Assert(false, "Spell.SelectImplicitChannelTargets: received not implemented target reference type"); + return; + } + + Spell channeledSpell = m_originalCaster.GetCurrentSpell(CurrentSpellTypes.Channeled); + if (channeledSpell == null) + { + Log.outDebug(LogFilter.Spells, "Spell.SelectImplicitChannelTargets: cannot find channel spell for spell ID {0}, effect {1}", m_spellInfo.Id, effIndex); + return; + + } + + switch (targetType.GetTarget()) + { + case Targets.UnitChannelTarget: + { + foreach (ObjectGuid channelTarget in m_originalCaster.GetChannelObjects()) + { + WorldObject target = Global.ObjAccessor.GetUnit(m_caster, channelTarget); + CallScriptObjectTargetSelectHandlers(ref target, effIndex, targetType); + // unit target may be no longer avalible - teleported out of map for example + if (target && target.IsTypeId(TypeId.Unit)) + AddUnitTarget(target.ToUnit(), 1u << (int)effIndex); + else + Log.outDebug(LogFilter.Spells, "SPELL: cannot find channel spell target for spell ID {0}, effect {1}", m_spellInfo.Id, effIndex); + } + break; + } + case Targets.DestChannelTarget: + { + if (channeledSpell.m_targets.HasDst()) + m_targets.SetDst(channeledSpell.m_targets); + else + { + var channelObjects = m_originalCaster.GetChannelObjects(); + WorldObject target = channelObjects.Count > 0 ? Global.ObjAccessor.GetWorldObject(m_caster, channelObjects[0]) : null; + if (target != null) + { + CallScriptObjectTargetSelectHandlers(ref target, effIndex, targetType); + if (target) + { + SpellDestination dest = new SpellDestination(target); + CallScriptDestinationTargetSelectHandlers(ref dest, effIndex, targetType); + m_targets.SetDst(dest); + } + } + else + Log.outDebug(LogFilter.Spells, "SPELL: cannot find channel spell destination for spell ID {0}, effect {1}", m_spellInfo.Id, effIndex); + } + break; + } + case Targets.DestChannelCaster: + { + SpellDestination dest = new SpellDestination(channeledSpell.GetCaster()); + CallScriptDestinationTargetSelectHandlers(ref dest, effIndex, targetType); + m_targets.SetDst(dest); + break; + } + default: + Contract.Assert(false, "Spell.SelectImplicitChannelTargets: received not implemented target type"); + break; + } + } + + void SelectImplicitNearbyTargets(uint effIndex, SpellImplicitTargetInfo targetType, uint effMask) + { + if (targetType.GetReferenceType() != SpellTargetReferenceTypes.Caster) + { + Contract.Assert(false, "Spell.SelectImplicitNearbyTargets: received not implemented target reference type"); + return; + } + + SpellEffectInfo effect = GetEffect(effIndex); + if (effect == null) + return; + + float range = 0.0f; + switch (targetType.GetCheckType()) + { + case SpellTargetCheckTypes.Enemy: + range = m_spellInfo.GetMaxRange(false, m_caster, this); + break; + case SpellTargetCheckTypes.Ally: + case SpellTargetCheckTypes.Party: + case SpellTargetCheckTypes.Raid: + case SpellTargetCheckTypes.RaidClass: + range = m_spellInfo.GetMaxRange(true, m_caster, this); + break; + case SpellTargetCheckTypes.Entry: + case SpellTargetCheckTypes.Default: + range = m_spellInfo.GetMaxRange(m_spellInfo.IsPositive(), m_caster, this); + break; + default: + Contract.Assert(false, "Spell.SelectImplicitNearbyTargets: received not implemented selection check type"); + break; + } + + List condList = effect.ImplicitTargetConditions; + + // handle emergency case - try to use other provided targets if no conditions provided + if (targetType.GetCheckType() == SpellTargetCheckTypes.Entry && (condList == null || condList.Empty())) + { + Log.outDebug(LogFilter.Spells, "Spell.SelectImplicitNearbyTargets: no conditions entry for target with TARGET_CHECK_ENTRY of spell ID {0}, effect {1} - selecting default targets", m_spellInfo.Id, effIndex); + switch (targetType.GetObjectType()) + { + case SpellTargetObjectTypes.Gobj: + if (m_spellInfo.RequiresSpellFocus != 0) + { + if (focusObject != null) + AddGOTarget(focusObject, effMask); + return; + } + break; + case SpellTargetObjectTypes.Dest: + if (m_spellInfo.RequiresSpellFocus != 0) + { + if (focusObject != null) + { + SpellDestination dest = new SpellDestination(focusObject); + CallScriptDestinationTargetSelectHandlers(ref dest, effIndex, targetType); + m_targets.SetDst(dest); + } + return; + } + break; + default: + break; + } + } + + WorldObject target = SearchNearbyTarget(range, targetType.GetObjectType(), targetType.GetCheckType(), condList); + if (target == null) + { + Log.outDebug(LogFilter.Spells, "Spell.SelectImplicitNearbyTargets: cannot find nearby target for spell ID {0}, effect {1}", m_spellInfo.Id, effIndex); + return; + } + + CallScriptObjectTargetSelectHandlers(ref target, effIndex, targetType); + + switch (targetType.GetObjectType()) + { + case SpellTargetObjectTypes.Unit: + Unit unitTarget = target.ToUnit(); + if (unitTarget != null) + AddUnitTarget(unitTarget, effMask, true, false); + break; + case SpellTargetObjectTypes.Gobj: + GameObject gobjTarget = target.ToGameObject(); + if (gobjTarget != null) + AddGOTarget(gobjTarget, effMask); + break; + case SpellTargetObjectTypes.Dest: + SpellDestination dest = new SpellDestination(target); + CallScriptDestinationTargetSelectHandlers(ref dest, effIndex, targetType); + m_targets.SetDst(dest); + break; + default: + Contract.Assert(false, "Spell.SelectImplicitNearbyTargets: received not implemented target object type"); + break; + } + + SelectImplicitChainTargets(effIndex, targetType, target, effMask); + } + + void SelectImplicitConeTargets(uint effIndex, SpellImplicitTargetInfo targetType, uint effMask) + { + if (targetType.GetReferenceType() != SpellTargetReferenceTypes.Caster) + { + Contract.Assert(false, "Spell.SelectImplicitConeTargets: received not implemented target reference type"); + return; + } + List targets = new List(); + SpellTargetObjectTypes objectType = targetType.GetObjectType(); + SpellTargetCheckTypes selectionType = targetType.GetCheckType(); + SpellEffectInfo effect = GetEffect(effIndex); + if (effect == null) + return; + + var condList = effect.ImplicitTargetConditions; + float coneAngle = MathFunctions.PiOver2; + float radius = effect.CalcRadius(m_caster) * m_spellValue.RadiusMod; + + GridMapTypeMask containerTypeMask = GetSearcherTypeMask(objectType, condList); + if (containerTypeMask != 0) + { + var spellCone = new WorldObjectSpellConeTargetCheck(coneAngle, radius, m_caster, m_spellInfo, selectionType, condList); + var searcher = new WorldObjectListSearcher(m_caster, targets, spellCone, containerTypeMask); + SearchTargets(searcher, containerTypeMask, m_caster, m_caster.GetPosition(), radius); + + CallScriptObjectAreaTargetSelectHandlers(targets, effIndex, targetType); + + if (!targets.Empty()) + { + // Other special target selection goes here + uint maxTargets = m_spellValue.MaxAffectedTargets; + if (maxTargets != 0) + targets.RandomResize(maxTargets); + + foreach (var obj in targets) + { + Unit unitTarget = obj.ToUnit(); + GameObject gObjTarget = obj.ToGameObject(); + if (unitTarget) + AddUnitTarget(unitTarget, effMask, false); + else if (gObjTarget) + AddGOTarget(gObjTarget, effMask); + } + } + } + } + + void SelectImplicitAreaTargets(uint effIndex, SpellImplicitTargetInfo targetType, uint effMask) + { + Unit referer = null; + switch (targetType.GetReferenceType()) + { + case SpellTargetReferenceTypes.Src: + case SpellTargetReferenceTypes.Dest: + case SpellTargetReferenceTypes.Caster: + referer = m_caster; + break; + case SpellTargetReferenceTypes.Target: + referer = m_targets.GetUnitTarget(); + break; + case SpellTargetReferenceTypes.Last: + { + // find last added target for this effect + foreach (var target in m_UniqueTargetInfo) + { + if (Convert.ToBoolean(target.effectMask & (1 << (int)effIndex))) + { + referer = Global.ObjAccessor.GetUnit(m_caster, target.targetGUID); + break; + } + } + break; + } + default: + Contract.Assert(false, "Spell.SelectImplicitAreaTargets: received not implemented target reference type"); + return; + } + if (referer == null) + return; + + Position center = null; + switch (targetType.GetReferenceType()) + { + case SpellTargetReferenceTypes.Src: + center = m_targets.GetSrcPos(); + break; + case SpellTargetReferenceTypes.Dest: + center = m_targets.GetDstPos(); + break; + case SpellTargetReferenceTypes.Caster: + case SpellTargetReferenceTypes.Target: + case SpellTargetReferenceTypes.Last: + center = referer.GetPosition(); + break; + default: + Contract.Assert(false, "Spell.SelectImplicitAreaTargets: received not implemented target reference type"); + return; + } + List targets = new List(); + SpellEffectInfo effect = GetEffect(effIndex); + if (effect == null) + return; + float radius = effect.CalcRadius(m_caster) * m_spellValue.RadiusMod; + SearchAreaTargets(targets, radius, center, referer, targetType.GetObjectType(), targetType.GetCheckType(), effect.ImplicitTargetConditions); + + CallScriptObjectAreaTargetSelectHandlers(targets, effIndex, targetType); + + if (!targets.Empty()) + { + // Other special target selection goes here + uint maxTargets = m_spellValue.MaxAffectedTargets; + if (maxTargets != 0) + targets.RandomResize(maxTargets); + + foreach (var obj in targets) + { + Unit unitTarget = obj.ToUnit(); + GameObject gObjTarget = obj.ToGameObject(); + if (unitTarget) + AddUnitTarget(unitTarget, effMask, false, true, center); + else if (gObjTarget) + AddGOTarget(gObjTarget, effMask); + } + } + } + + void SelectImplicitCasterDestTargets(uint effIndex, SpellImplicitTargetInfo targetType) + { + SpellDestination dest = new SpellDestination(m_caster); + + switch (targetType.GetTarget()) + { + case Targets.DestCaster: + break; + case Targets.DestHome: + Player playerCaster = m_caster.ToPlayer(); + if (playerCaster != null) + dest = new SpellDestination(playerCaster.GetHomebind().posX, playerCaster.GetHomebind().posY, playerCaster.GetHomebind().posZ, playerCaster.GetOrientation(), playerCaster.GetHomebind().GetMapId()); + break; + case Targets.DestDb: + SpellTargetPosition st = Global.SpellMgr.GetSpellTargetPosition(m_spellInfo.Id, effIndex); + if (st != null) + { + /// @todo fix this check + if (m_spellInfo.HasEffect(SpellEffectName.TeleportUnitsOld) || m_spellInfo.HasEffect(SpellEffectName.Bind)) + dest = new SpellDestination(st.target_X, st.target_Y, st.target_Z, st.target_Orientation, st.target_mapId); + else if (st.target_mapId == m_caster.GetMapId()) + dest = new SpellDestination(st.target_X, st.target_Y, st.target_Z, st.target_Orientation); + } + else + { + Log.outDebug(LogFilter.Spells, "SPELL: unknown target coordinates for spell ID {0}", m_spellInfo.Id); + WorldObject target = m_targets.GetObjectTarget(); + if (target) + dest = new SpellDestination(target); + } + break; + case Targets.DestCasterFishing: + { + float min_dis = m_spellInfo.GetMinRange(true); + float max_dis = m_spellInfo.GetMaxRange(true); + float dis = (float)RandomHelper.NextDouble() * (max_dis - min_dis) + min_dis; + float x, y, z; + float angle = (float)RandomHelper.NextDouble() * (MathFunctions.PI * 35.0f / 180.0f) - (float)(Math.PI * 17.5f / 180.0f); + m_caster.GetClosePoint(out x, out y, out z, SharedConst.DefaultWorldObjectSize, dis, angle); + + float ground = z; + float liquidLevel = m_caster.GetMap().GetWaterOrGroundLevel(m_caster.GetPhases(), x, y, z, ref ground); + if (liquidLevel <= ground) // When there is no liquid Map.GetWaterOrGroundLevel returns ground level + { + SendCastResult(SpellCastResult.NotHere); + SendChannelUpdate(0); + finish(false); + return; + } + + if (ground + 0.75 > liquidLevel) + { + SendCastResult(SpellCastResult.TooShallow); + SendChannelUpdate(0); + finish(false); + return; + } + + dest = new SpellDestination(x, y, liquidLevel, m_caster.GetOrientation()); + break; + } + default: + SpellEffectInfo effect = GetEffect(effIndex); + if (effect != null) + { + float dist = effect.CalcRadius(m_caster); + float angl = targetType.CalcDirectionAngle(); + float objSize = m_caster.GetObjectSize(); + + switch (targetType.GetTarget()) + { + case Targets.DestCasterSummon: + dist = SharedConst.PetFollowDist; + break; + case Targets.DestCasterRandom: + if (dist > objSize) + dist = objSize + (dist - objSize) * (float)RandomHelper.NextDouble(); + break; + case Targets.DestCasterFrontLeft: + case Targets.DestCasterBackLeft: + case Targets.DestCasterFrontRight: + case Targets.DestCasterBackRight: + { + float DefaultTotemDistance = 3.0f; + if (!effect.HasRadius() && !effect.HasMaxRadius()) + dist = DefaultTotemDistance; + break; + } + default: + break; + } + + if (dist < objSize) + dist = objSize; + + Position pos = dest.Position; + m_caster.MovePositionToFirstCollision(ref pos, dist, angl); + + dest.Relocate(pos); + } + break; + } + CallScriptDestinationTargetSelectHandlers(ref dest, effIndex, targetType); + m_targets.SetDst(dest); + } + + void SelectImplicitTargetDestTargets(uint effIndex, SpellImplicitTargetInfo targetType) + { + WorldObject target = m_targets.GetObjectTarget(); + + SpellDestination dest = new SpellDestination(target); + + switch (targetType.GetTarget()) + { + case Targets.DestEnemy: + case Targets.DestAny: + break; + default: + SpellEffectInfo effect = GetEffect(effIndex); + if (effect != null) + { + float angle = targetType.CalcDirectionAngle(); + float objSize = target.GetObjectSize(); + float dist = effect.CalcRadius(m_caster); + if (dist < objSize) + dist = objSize; + else if (targetType.GetTarget() == Targets.DestRandom) + dist = objSize + (dist - objSize) * (float)RandomHelper.NextDouble(); + + Position pos = dest.Position; + target.MovePositionToFirstCollision(ref pos, dist, angle); + + dest.Relocate(pos); + } + break; + } + + CallScriptDestinationTargetSelectHandlers(ref dest, effIndex, targetType); + m_targets.SetDst(dest); + } + + void SelectImplicitDestDestTargets(uint effIndex, SpellImplicitTargetInfo targetType) + { + // set destination to caster if no dest provided + // can only happen if previous destination target could not be set for some reason + // (not found nearby target, or channel target for example + // maybe we should abort the spell in such case? + CheckDst(); + + SpellDestination dest = m_targets.GetDst(); + + switch (targetType.GetTarget()) + { + case Targets.DestDynobjEnemy: + case Targets.DestDynobjAlly: + case Targets.DestDynobjNone: + case Targets.DestDest: + return; + case Targets.DestTraj: + SelectImplicitTrajTargets(effIndex); + return; + default: + SpellEffectInfo effect = GetEffect(effIndex); + if (effect != null) + { + float angle = targetType.CalcDirectionAngle(); + float dist = effect.CalcRadius(m_caster); + if (targetType.GetTarget() == Targets.DestRandom) + dist *= (float)RandomHelper.NextDouble(); + + Position pos = m_targets.GetDstPos(); + m_caster.MovePositionToFirstCollision(ref pos, dist, angle); + + dest.Relocate(pos); + } + break; + } + + CallScriptDestinationTargetSelectHandlers(ref dest, effIndex, targetType); + m_targets.ModDst(dest); + } + + void SelectImplicitCasterObjectTargets(uint effIndex, SpellImplicitTargetInfo targetType) + { + WorldObject target = null; + bool checkIfValid = true; + + switch (targetType.GetTarget()) + { + case Targets.UnitCaster: + target = m_caster; + checkIfValid = false; + break; + case Targets.UnitMaster: + target = m_caster.GetCharmerOrOwner(); + break; + case Targets.UnitPet: + target = m_caster.GetGuardianPet(); + break; + case Targets.UnitSummoner: + if (m_caster.IsSummon()) + target = m_caster.ToTempSummon().GetSummoner(); + break; + case Targets.UnitVehicle: + target = m_caster.GetVehicleBase(); + break; + case Targets.UnitPassenger0: + case Targets.UnitPassenger1: + case Targets.UnitPassenger2: + case Targets.UnitPassenger3: + case Targets.UnitPassenger4: + case Targets.UnitPassenger5: + case Targets.UnitPassenger6: + case Targets.UnitPassenger7: + if (m_caster.IsTypeId(TypeId.Unit) && m_caster.ToCreature().IsVehicle()) + target = m_caster.GetVehicleKit().GetPassenger((sbyte)(targetType.GetTarget() - Targets.UnitPassenger0)); + break; + default: + break; + } + + CallScriptObjectTargetSelectHandlers(ref target, effIndex, targetType); + + if (target != null && target.ToUnit()) + AddUnitTarget(target.ToUnit(), (uint)(1 << (int)effIndex), checkIfValid); + } + + void SelectImplicitTargetObjectTargets(uint effIndex, SpellImplicitTargetInfo targetType) + { + WorldObject target = m_targets.GetObjectTarget(); + + CallScriptObjectTargetSelectHandlers(ref target, effIndex, targetType); + + Item item = m_targets.GetItemTarget(); + if (target != null) + { + if (target.ToUnit()) + AddUnitTarget(target.ToUnit(), (uint)(1 << (int)effIndex), true, false); + else if (target.IsTypeId(TypeId.GameObject)) + AddGOTarget(target.ToGameObject(), (uint)(1 << (int)effIndex)); + + SelectImplicitChainTargets(effIndex, targetType, target, (uint)(1 << (int)effIndex)); + } + // Script hook can remove object target and we would wrongly land here + else if (item != null) + AddItemTarget(item, (uint)(1 << (int)effIndex)); + } + + void SelectImplicitChainTargets(uint effIndex, SpellImplicitTargetInfo targetType, WorldObject target, uint effMask) + { + SpellEffectInfo effect = GetEffect(effIndex); + if (effect == null) + return; + + uint maxTargets = effect.ChainTargets; + Player modOwner = m_caster.GetSpellModOwner(); + if (modOwner) + modOwner.ApplySpellMod(m_spellInfo.Id, SpellModOp.JumpTargets, ref maxTargets, this); + + if (maxTargets > 1) + { + // mark damage multipliers as used + foreach (SpellEffectInfo eff in GetEffects()) + if (eff != null && Convert.ToBoolean(effMask & (1 << (int)eff.EffectIndex))) + m_damageMultipliers[eff.EffectIndex] = 1.0f; + m_applyMultiplierMask |= (byte)effMask; + + List targets = new List(); + SearchChainTargets(targets, maxTargets - 1, target, targetType.GetObjectType(), targetType.GetCheckType(), effect.ImplicitTargetConditions, targetType.GetTarget() == Targets.UnitChainhealAlly); + + // Chain primary target is added earlier + CallScriptObjectAreaTargetSelectHandlers(targets, effIndex, targetType); + + foreach (var obj in targets) + { + Unit unitTarget = obj.ToUnit(); + if (unitTarget) + AddUnitTarget(unitTarget, effMask, false); + } + } + } + + float tangent(float x) + { + x = (float)Math.Tan(x); + if (x < 100000.0f && x > -100000.0f) return x; + if (x >= 100000.0f) return 100000.0f; + if (x <= 100000.0f) return -100000.0f; + return 0.0f; + } + + void SelectImplicitTrajTargets(uint effIndex) + { + if (!m_targets.HasTraj()) + return; + + float dist2d = m_targets.GetDist2d(); + if (dist2d == 0) + return; + + float srcToDestDelta = m_targets.GetDstPos().posZ - m_targets.GetSrcPos().posZ; + + List targets = new List(); + var spellTraj = new WorldObjectSpellTrajTargetCheck(dist2d, m_targets.GetSrcPos(), m_caster, m_spellInfo); + var searcher = new WorldObjectListSearcher(m_caster, targets, spellTraj); + SearchTargets(searcher, GridMapTypeMask.All, m_caster, m_targets.GetSrcPos(), dist2d); + if (targets.Empty()) + return; + + targets.Sort(new ObjectDistanceOrderPred(m_caster)); + + float b = tangent(m_targets.GetPitch()); + float a = (srcToDestDelta - dist2d * b) / (dist2d * dist2d); + if (a > -0.0001f) + a = 0; + Log.outError(LogFilter.Spells, "Spell.SelectTrajTargets: a {0} b {1}", a, b); + + float bestDist = m_spellInfo.GetMaxRange(false); + + foreach (var obj in targets) + { + if (!m_caster.HasInLine(obj, 5.0f)) + continue; + + if (m_spellInfo.CheckTarget(m_caster, obj, true) != SpellCastResult.SpellCastOk) + continue; + + Unit unitTarget = obj.ToUnit(); + if (unitTarget) + { + if (m_caster == obj || m_caster.IsOnVehicle(unitTarget) || unitTarget.GetVehicle()) + continue; + + Creature creatureTarget = unitTarget.ToCreature(); + if (creatureTarget) + { + if (!creatureTarget.GetCreatureTemplate().TypeFlags.HasAnyFlag(CreatureTypeFlags.CanCollideWithMissiles)) + continue; + } + } + + float size = Math.Max(obj.GetObjectSize() * 0.7f, 1.0f); // 1/sqrt(3) + // @todo all calculation should be based on src instead of m_caster + float objDist2d = (float)(m_targets.GetSrcPos().GetExactDist2d(obj) * Math.Cos(m_targets.GetSrcPos().GetRelativeAngle(obj))); + float dz = obj.GetPositionZ() - m_targets.GetSrcPos().posZ; + + Log.outError(LogFilter.Spells, "Spell.SelectTrajTargets: check {0}, dist between {1} {2}, height between {3} {4}.", obj.GetEntry(), objDist2d - size, objDist2d + size, dz - size, dz + size); + + float dist = objDist2d - size; + float height = dist * (a * dist + b); + Log.outError(LogFilter.Spells, "Spell.SelectTrajTargets: dist {0}, height {1}.", dist, height); + if (dist < bestDist && height < dz + size && height > dz - size) + { + bestDist = dist > 0 ? dist : 0; + break; + } + + if (a == 0) + { + height = dz - size; + dist = height / b; + + if (dist > bestDist) + continue; + if (dist < objDist2d + size && dist > objDist2d - size) + { + bestDist = dist; + break; + } + + height = dz + size; + dist = height / b; + + if (dist > bestDist) + continue; + if (dist < objDist2d + size && dist > objDist2d - size) + { + bestDist = dist; + break; + } + + continue; + } + + height = dz - size; + float sqrt1 = b * b + 4 * a * height; + if (sqrt1 > 0) + { + sqrt1 = (float)Math.Sqrt(sqrt1); + dist = (sqrt1 - b) / (2 * a); + + if (dist > bestDist) + continue; + if (dist < objDist2d + size && dist > objDist2d - size) + { + bestDist = dist; + break; + } + } + + height = dz + size; + float sqrt2 = b * b + 4 * a * height; + if (sqrt2 > 0) + { + sqrt2 = (float)Math.Sqrt(sqrt2); + dist = (sqrt2 - b) / (2 * a); + + if (dist > bestDist) + continue; + if (dist < objDist2d + size && dist > objDist2d - size) + { + bestDist = dist; + break; + } + + dist = (-sqrt2 - b) / (2 * a); + + if (dist > bestDist) + continue; + if (dist < objDist2d + size && dist > objDist2d - size) + { + bestDist = dist; + break; + } + } + + if (sqrt1 > 0) + { + dist = (-sqrt1 - b) / (2 * a); + + if (dist > bestDist) + continue; + if (dist < objDist2d + size && dist > objDist2d - size) + { + bestDist = dist; + break; + } + } + } + + if (m_targets.GetSrcPos().GetExactDist2d(m_targets.GetDstPos()) > bestDist) + { + float x = (float)(m_targets.GetSrcPos().posX + Math.Cos(m_caster.GetOrientation()) * bestDist); + float y = (float)(m_targets.GetSrcPos().posY + Math.Sin(m_caster.GetOrientation()) * bestDist); + float z = m_targets.GetSrcPos().posZ + bestDist * (a * bestDist + b); + var obj = targets[0]; + if (obj != null) + { + float distSq = obj.GetExactDistSq(x, y, z); + float sizeSq = obj.GetObjectSize(); + sizeSq *= sizeSq; + if (distSq > sizeSq) + { + float factor = 1 - (float)Math.Sqrt(sizeSq / distSq); + x += factor * (obj.GetPositionX() - x); + y += factor * (obj.GetPositionY() - y); + z += factor * (obj.GetPositionZ() - z); + + distSq = obj.GetExactDistSq(x, y, z); + } + } + + Position trajDst = new Position(); + trajDst.Relocate(x, y, z, m_caster.GetOrientation()); + SpellDestination dest = m_targets.GetDst(); + dest.Relocate(trajDst); + + CallScriptDestinationTargetSelectHandlers(ref dest, effIndex, new SpellImplicitTargetInfo(Targets.DestTraj)); + m_targets.ModDst(dest); + } + + Vehicle veh = m_caster.GetVehicleKit(); + if (veh != null) + veh.SetLastShootPos(m_targets.GetDstPos()); + } + + void SelectEffectTypeImplicitTargets(uint effIndex) + { + // special case for SPELL_EFFECT_SUMMON_RAF_FRIEND and SPELL_EFFECT_SUMMON_PLAYER + /// @todo this is a workaround - target shouldn't be stored in target map for those spells + SpellEffectInfo effect = GetEffect(effIndex); + if (effect == null) + return; + switch (effect.Effect) + { + case SpellEffectName.SummonRafFriend: + case SpellEffectName.SummonPlayer: + if (m_caster.IsTypeId(TypeId.Player) && !m_caster.GetTarget().IsEmpty()) + { + WorldObject target1 = Global.ObjAccessor.FindPlayer(m_caster.GetTarget()); + + CallScriptObjectTargetSelectHandlers(ref target1, effIndex, new SpellImplicitTargetInfo()); + + if (target1 != null && target1.IsTypeId(TypeId.Player)) + AddUnitTarget(target1.ToUnit(), (uint)(1 << (int)effIndex), false); + } + return; + default: + break; + } + + // select spell implicit targets based on effect type + if (effect.GetImplicitTargetType() == 0) + return; + + SpellCastTargetFlags targetMask = effect.GetMissingTargetMask(); + + if (targetMask == 0) + return; + + WorldObject target = null; + + switch (effect.GetImplicitTargetType()) + { + // add explicit object target or self to the target map + case SpellEffectImplicitTargetTypes.Explicit: + // player which not released his spirit is Unit, but target flag for it is TARGET_FLAG_CORPSE_MASK + if (Convert.ToBoolean(targetMask & (SpellCastTargetFlags.UnitMask | SpellCastTargetFlags.CorpseMask))) + { + Unit unitTarget = m_targets.GetUnitTarget(); + if (unitTarget != null) + target = unitTarget; + else if (Convert.ToBoolean(targetMask & SpellCastTargetFlags.CorpseMask)) + { + Corpse corpseTarget = m_targets.GetCorpseTarget(); + if (corpseTarget != null) + { + // @todo this is a workaround - corpses should be added to spell target map too, but we can't do that so we add owner instead + Player owner = Global.ObjAccessor.FindPlayer(corpseTarget.GetOwnerGUID()); + if (owner != null) + target = owner; + } + } + else //if (targetMask & TARGET_FLAG_UNIT_MASK) + target = m_caster; + } + if (Convert.ToBoolean(targetMask & SpellCastTargetFlags.ItemMask)) + { + Item itemTarget = m_targets.GetItemTarget(); + if (itemTarget != null) + AddItemTarget(itemTarget, (uint)(1 << (int)effIndex)); + return; + } + if (Convert.ToBoolean(targetMask & SpellCastTargetFlags.GameobjectMask)) + target = m_targets.GetGOTarget(); + break; + // add self to the target map + case SpellEffectImplicitTargetTypes.Caster: + if (Convert.ToBoolean(targetMask & SpellCastTargetFlags.UnitMask)) + target = m_caster; + break; + default: + break; + } + + CallScriptObjectTargetSelectHandlers(ref target, effIndex, new SpellImplicitTargetInfo()); + + if (target != null) + { + if (target.ToUnit()) + AddUnitTarget(target.ToUnit(), (uint)(1 << (int)effIndex), false); + else if (target.IsTypeId(TypeId.GameObject)) + AddGOTarget(target.ToGameObject(), (uint)(1 << (int)effIndex)); + } + } + + public GridMapTypeMask GetSearcherTypeMask(SpellTargetObjectTypes objType, List condList) + { + // this function selects which containers need to be searched for spell target + GridMapTypeMask retMask = GridMapTypeMask.All; + + // filter searchers based on searched object type + switch (objType) + { + case SpellTargetObjectTypes.Unit: + case SpellTargetObjectTypes.UnitAndDest: + case SpellTargetObjectTypes.Corpse: + case SpellTargetObjectTypes.CorpseEnemy: + case SpellTargetObjectTypes.CorpseAlly: + retMask &= GridMapTypeMask.Player | GridMapTypeMask.Corpse | GridMapTypeMask.Creature; + break; + case SpellTargetObjectTypes.Gobj: + case SpellTargetObjectTypes.GobjItem: + retMask &= GridMapTypeMask.GameObject; + break; + default: + break; + } + if (!m_spellInfo.HasAttribute(SpellAttr2.CanTargetDead)) + retMask &= ~GridMapTypeMask.Corpse; + if (m_spellInfo.HasAttribute(SpellAttr3.OnlyTargetPlayers)) + retMask &= GridMapTypeMask.Corpse | GridMapTypeMask.Player; + if (m_spellInfo.HasAttribute(SpellAttr3.OnlyTargetGhosts)) + retMask &= GridMapTypeMask.Player; + + if (condList != null) + retMask &= Global.ConditionMgr.GetSearcherTypeMaskForConditionList(condList); + return retMask; + } + + void SearchTargets(Notifier notifier, GridMapTypeMask containerMask, Unit referer, Position pos, float radius) + { + if (containerMask == 0) + return; + + bool searchInGrid = containerMask.HasAnyFlag(GridMapTypeMask.Creature | GridMapTypeMask.GameObject); + bool searchInWorld = containerMask.HasAnyFlag(GridMapTypeMask.Creature | GridMapTypeMask.Player | GridMapTypeMask.Corpse); + if (searchInGrid || searchInWorld) + { + float x, y; + x = pos.GetPositionX(); + y = pos.GetPositionY(); + + CellCoord p = GridDefines.ComputeCellCoord(x, y); + Cell cell = new Cell(p); + cell.SetNoCreate(); + + Map map = referer.GetMap(); + + if (searchInWorld) + Cell.VisitWorldObjects(x, y, map, notifier, radius); + + if (searchInGrid) + Cell.VisitGridObjects(x, y, map, notifier, radius); + } + } + + WorldObject SearchNearbyTarget(float range, SpellTargetObjectTypes objectType, SpellTargetCheckTypes selectionType, List condList) + { + GridMapTypeMask containerTypeMask = GetSearcherTypeMask(objectType, condList); + if (containerTypeMask == 0) + return null; + var check = new WorldObjectSpellNearbyTargetCheck(range, m_caster, m_spellInfo, selectionType, condList); + var searcher = new WorldObjectLastSearcher(m_caster, check, containerTypeMask); + SearchTargets(searcher, containerTypeMask, m_caster, m_caster.GetPosition(), range); + return searcher.GetTarget(); + } + + void SearchAreaTargets(List targets, float range, Position position, Unit referer, SpellTargetObjectTypes objectType, SpellTargetCheckTypes selectionType, List condList) + { + var containerTypeMask = GetSearcherTypeMask(objectType, condList); + if (containerTypeMask == 0) + return; + var check = new WorldObjectSpellAreaTargetCheck(range, position, m_caster, referer, m_spellInfo, selectionType, condList); + var searcher = new WorldObjectListSearcher(m_caster, targets, check, containerTypeMask); + SearchTargets(searcher, containerTypeMask, m_caster, position, range); + } + + void SearchChainTargets(List targets, uint chainTargets, WorldObject target, SpellTargetObjectTypes objectType, SpellTargetCheckTypes selectType, List condList, bool isChainHeal) + { + // max dist for jump target selection + float jumpRadius = 0.0f; + switch (m_spellInfo.DmgClass) + { + case SpellDmgClass.Ranged: + // 7.5y for multi shot + jumpRadius = 7.5f; + break; + case SpellDmgClass.Melee: + // 5y for swipe, cleave and similar + jumpRadius = 5.0f; + break; + case SpellDmgClass.None: + case SpellDmgClass.Magic: + // 12.5y for chain heal spell since 3.2 patch + if (isChainHeal) + jumpRadius = 12.5f; + // 10y as default for magic chain spells + else + jumpRadius = 10.0f; + break; + } + + Player modOwner = m_caster.GetSpellModOwner(); + if (modOwner) + modOwner.ApplySpellMod(m_spellInfo.Id, SpellModOp.JumpDistance, ref jumpRadius, this); + + // chain lightning/heal spells and similar - allow to jump at larger distance and go out of los + bool isBouncingFar = (m_spellInfo.HasAttribute(SpellAttr4.AreaTargetChain) + || m_spellInfo.DmgClass == SpellDmgClass.None + || m_spellInfo.DmgClass == SpellDmgClass.Magic); + + // max dist which spell can reach + float searchRadius = jumpRadius; + if (isBouncingFar) + searchRadius *= chainTargets; + + List tempTargets = new List(); + SearchAreaTargets(tempTargets, searchRadius, target.GetPosition(), m_caster, objectType, selectType, condList); + tempTargets.Remove(target); + + // remove targets which are always invalid for chain spells + // for some spells allow only chain targets in front of caster (swipe for example) + if (!isBouncingFar) + { + foreach (var obj in tempTargets.ToList()) + { + if (!m_caster.HasInArc(MathFunctions.PI, obj)) + tempTargets.Remove(obj); + } + } + + while (chainTargets != 0) + { + // try to get unit for next chain jump + var found = tempTargets.LastOrDefault(); + // get unit with highest hp deficit in dist + if (isChainHeal) + { + uint maxHPDeficit = 0; + foreach (var obj in tempTargets) + { + Unit unitTarget = obj.ToUnit(); + if (unitTarget != null) + { + uint deficit = (uint)(unitTarget.GetMaxHealth() - unitTarget.GetHealth()); + if ((deficit > maxHPDeficit || found == null) && target.IsWithinDist(unitTarget, jumpRadius) && target.IsWithinLOSInMap(unitTarget)) + { + found = obj; + maxHPDeficit = deficit; + } + } + } + } + // get closest object + else + { + foreach (var obj in tempTargets) + { + if (found == null) + { + if ((!isBouncingFar || target.IsWithinDist(obj, jumpRadius)) && target.IsWithinLOSInMap(obj)) + found = obj; + } + else if (target.GetDistanceOrder(obj, found) && target.IsWithinLOSInMap(obj)) + found = obj; + } + } + // not found any valid target - chain ends + if (found == null) + break; + target = found; + tempTargets.Remove(found); + targets.Add(target); + --chainTargets; + } + } + + GameObject SearchSpellFocus() + { + var check = new GameObjectFocusCheck(m_caster, m_spellInfo.RequiresSpellFocus); + var searcher = new GameObjectSearcher(m_caster, check); + SearchTargets(searcher, GridMapTypeMask.GameObject, m_caster, m_caster, m_caster.GetVisibilityRange()); + return searcher.GetTarget(); + } + + void prepareDataForTriggerSystem(AuraEffect triggeredByAura) + { + //========================================================================================== + // Now fill data for trigger system, need know: + // can spell trigger another or not (m_canTrigger) + // Create base triggers flags for Attacker and Victim (m_procAttacker, m_procVictim and m_procEx) + //========================================================================================== + + m_procVictim = m_procAttacker = 0; + // Get data for type of attack and fill base info for trigger + switch (m_spellInfo.DmgClass) + { + case SpellDmgClass.Melee: + m_procAttacker = ProcFlags.DoneSpellMeleeDmgClass; + if (m_attackType == WeaponAttackType.OffAttack) + m_procAttacker |= ProcFlags.DoneOffHandAttack; + else + m_procAttacker |= ProcFlags.DoneMainHandAttack; + m_procVictim = ProcFlags.TakenSpellMeleeDmgClass; + break; + case SpellDmgClass.Ranged: + // Auto attack + if (m_spellInfo.HasAttribute(SpellAttr2.AutorepeatFlag)) + { + m_procAttacker = ProcFlags.DoneRangedAutoAttack; + m_procVictim = ProcFlags.TakenRangedAutoAttack; + } + else // Ranged spell attack + { + m_procAttacker = ProcFlags.DoneSpellRangedDmgClass; + m_procVictim = ProcFlags.TakenSpellRangedDmgClass; + } + break; + default: + if (m_spellInfo.EquippedItemClass == ItemClass.Weapon && + Convert.ToBoolean(m_spellInfo.EquippedItemSubClassMask & (1 << (int)ItemSubClassWeapon.Wand)) + && m_spellInfo.HasAttribute(SpellAttr2.AutorepeatFlag)) // Wands auto attack + { + m_procAttacker = ProcFlags.DoneRangedAutoAttack; + m_procVictim = ProcFlags.TakenRangedAutoAttack; + } + break; + // For other spells trigger procflags are set in Spell.DoAllEffectOnTarget + // Because spell positivity is dependant on target + } + m_procEx = ProcFlagsExLegacy.None; + + // Hunter trap spells - activation proc for Lock and Load, Entrapment and Misdirection + if (m_spellInfo.SpellFamilyName == SpellFamilyNames.Hunter && + (m_spellInfo.SpellFamilyFlags[0].HasAnyFlag(0x18u) || // Freezing and Frost Trap, Freezing Arrow + m_spellInfo.Id == 57879 || // Snake Trap - done this way to avoid double proc + m_spellInfo.SpellFamilyFlags[2].HasAnyFlag(0x00024000u))) // Explosive and Immolation Trap + + m_procAttacker |= ProcFlags.DoneTrapActivation; + + //Effects which are result of aura proc from triggered spell cannot proc to prevent chain proc of these spells + + // Hellfire Effect - trigger as DOT + if (m_spellInfo.SpellFamilyName == SpellFamilyNames.Warlock && m_spellInfo.SpellFamilyFlags[0].HasAnyFlag(0x00000040u)) + { + m_procAttacker = ProcFlags.DonePeriodic; + m_procVictim = ProcFlags.TakenPeriodic; + } + + // Ranged autorepeat attack is set as triggered spell - ignore it + if (!Convert.ToBoolean(m_procAttacker & ProcFlags.DoneRangedAutoAttack)) + { + if (_triggeredCastFlags.HasAnyFlag(TriggerCastFlags.DisallowProcEvents) && + (m_spellInfo.HasAttribute(SpellAttr2.TriggeredCanTriggerProc) || + m_spellInfo.HasAttribute(SpellAttr3.TriggeredCanTriggerProc2))) + m_procEx |= ProcFlagsExLegacy.InternalCantProc; + else if (_triggeredCastFlags.HasAnyFlag(TriggerCastFlags.DisallowProcEvents)) + m_procEx |= ProcFlagsExLegacy.InternalTriggered; + } + // Totem casts require spellfamilymask defined in spell_proc_event to proc + if (m_originalCaster != null && m_caster != m_originalCaster && m_caster.IsTypeId(TypeId.Unit) && m_caster.IsTotem() && m_caster.IsControlledByPlayer()) + m_procEx |= ProcFlagsExLegacy.InternalReqFamily; + } + + public void CleanupTargetList() + { + m_UniqueTargetInfo.Clear(); + m_UniqueGOTargetInfo.Clear(); + m_UniqueItemInfo.Clear(); + m_delayMoment = 0; + } + + void AddUnitTarget(Unit target, uint effectMask, bool checkIfValid = true, bool Implicit = true, Position losPosition = null) + { + uint validEffectMask = 0; + foreach (SpellEffectInfo effect in GetEffects()) + if (effect != null && (effectMask & (1 << (int)effect.EffectIndex)) != 0 && CheckEffectTarget(target, effect, losPosition)) + validEffectMask |= 1u << (int)effect.EffectIndex; + + effectMask &= validEffectMask; + + // no effects left + if (effectMask == 0) + return; + + if (checkIfValid) + if (m_spellInfo.CheckTarget(m_caster, target, Implicit) != SpellCastResult.SpellCastOk) + return; + + // Check for effect immune skip if immuned + foreach (SpellEffectInfo effect in GetEffects()) + if (effect != null && target.IsImmunedToSpellEffect(m_spellInfo, effect.EffectIndex)) + effectMask &= ~(uint)(1 << (int)effect.EffectIndex); + + ObjectGuid targetGUID = target.GetGUID(); + + // Lookup target in already in list + foreach (var ihit in m_UniqueTargetInfo) + { + if (targetGUID == ihit.targetGUID) // Found in list + { + ihit.effectMask |= effectMask; // Immune effects removed from mask + ihit.scaleAura = false; + if (m_auraScaleMask != 0 && ihit.effectMask == m_auraScaleMask && m_caster != target) + { + SpellInfo auraSpell = Global.SpellMgr.GetSpellInfo(Global.SpellMgr.GetFirstSpellInChain(m_spellInfo.Id)); + if (target.getLevel() + 10 >= auraSpell.SpellLevel) + ihit.scaleAura = true; + } + return; + } + } + + // This is new target calculate data for him + + // Get spell hit result on target + TargetInfo targetInfo = new TargetInfo(); + targetInfo.targetGUID = targetGUID; // Store target GUID + targetInfo.effectMask = effectMask; // Store all effects not immune + targetInfo.processed = false; // Effects not apply on target + targetInfo.alive = target.IsAlive(); + targetInfo.damage = 0; + targetInfo.crit = false; + targetInfo.scaleAura = false; + if (m_auraScaleMask != 0 && targetInfo.effectMask == m_auraScaleMask && m_caster != target) + { + SpellInfo auraSpell = Global.SpellMgr.GetSpellInfo(Global.SpellMgr.GetFirstSpellInChain(m_spellInfo.Id)); + if (target.getLevel() + 10 >= auraSpell.SpellLevel) + targetInfo.scaleAura = true; + } + + // Calculate hit result + if (m_originalCaster != null) + { + targetInfo.missCondition = m_originalCaster.SpellHitResult(target, m_spellInfo, m_canReflect); + if (m_skipCheck && targetInfo.missCondition != SpellMissInfo.Immune) + targetInfo.missCondition = SpellMissInfo.None; + } + else + targetInfo.missCondition = SpellMissInfo.Evade; + + // Spell have speed - need calculate incoming time + // Incoming time is zero for self casts. At least I think so. + if (m_spellInfo.Speed > 0.0f && m_caster != target) + { + // calculate spell incoming interval + /// @todo this is a hack + float dist = m_caster.GetDistance(target.GetPositionX(), target.GetPositionY(), target.GetPositionZ()); + + if (dist < 5.0f) + dist = 5.0f; + + if (!(m_spellInfo.HasAttribute(SpellAttr9.SpecialDelayCalculation))) + targetInfo.timeDelay = (ulong)Math.Floor((dist / m_spellInfo.Speed) * 1000.0f); + else + targetInfo.timeDelay = (ulong)(m_spellInfo.Speed * 1000.0f); + + // Calculate minimum incoming time + if (m_delayMoment == 0 || m_delayMoment > targetInfo.timeDelay) + m_delayMoment = targetInfo.timeDelay; + } + else + targetInfo.timeDelay = 0L; + + // If target reflect spell back to caster + if (targetInfo.missCondition == SpellMissInfo.Reflect) + { + // Calculate reflected spell result on caster + targetInfo.reflectResult = m_caster.SpellHitResult(m_caster, m_spellInfo, m_canReflect); + + if (targetInfo.reflectResult == SpellMissInfo.Reflect) // Impossible reflect again, so simply deflect spell + targetInfo.reflectResult = SpellMissInfo.Parry; + + // Increase time interval for reflected spells by 1.5 + targetInfo.timeDelay += targetInfo.timeDelay >> 1; + } + else + targetInfo.reflectResult = SpellMissInfo.None; + + // Add target to list + m_UniqueTargetInfo.Add(targetInfo); + } + + void AddGOTarget(GameObject go, uint effectMask) + { + uint validEffectMask = 0; + foreach (SpellEffectInfo effect in GetEffects()) + if (effect != null && (effectMask & (1 << (int)effect.EffectIndex)) != 0 && CheckEffectTarget(go, effect)) + validEffectMask |= (uint)(1 << (int)effect.EffectIndex); + + effectMask &= validEffectMask; + + // no effects left + if (effectMask == 0) + return; + + ObjectGuid targetGUID = go.GetGUID(); + + // Lookup target in already in list + foreach (var ihit in m_UniqueGOTargetInfo) + { + if (targetGUID == ihit.targetGUID) // Found in list + { + ihit.effectMask |= effectMask; // Add only effect mask + return; + } + } + + // This is new target calculate data for him + GOTargetInfo target = new GOTargetInfo(); + target.targetGUID = targetGUID; + target.effectMask = effectMask; + target.processed = false; // Effects not apply on target + + // Spell have speed - need calculate incoming time + if (m_spellInfo.Speed > 0.0f) + { + // calculate spell incoming interval + float dist = m_caster.GetDistance(go.GetPositionX(), go.GetPositionY(), go.GetPositionZ()); + if (dist < 5.0f) + dist = 5.0f; + + if (!m_spellInfo.HasAttribute(SpellAttr9.SpecialDelayCalculation)) + target.timeDelay = (ulong)Math.Floor(dist / m_spellInfo.Speed * 1000.0f); + else + target.timeDelay = (ulong)(m_spellInfo.Speed * 1000.0f); + + if (m_delayMoment == 0 || m_delayMoment > target.timeDelay) + m_delayMoment = target.timeDelay; + } + else + target.timeDelay = 0L; + + // Add target to list + m_UniqueGOTargetInfo.Add(target); + } + + void AddItemTarget(Item item, uint effectMask) + { + uint validEffectMask = 0; + foreach (SpellEffectInfo effect in GetEffects()) + if (effect != null && (effectMask & (1 << (int)effect.EffectIndex)) != 0 && CheckEffectTarget(item, effect)) + validEffectMask |= 1u << (int)effect.EffectIndex; + + // no effects left + if (effectMask == 0) + return; + + // Lookup target in already in list + foreach (var ihit in m_UniqueItemInfo) + { + if (item == ihit.item) // Found in list + { + ihit.effectMask |= effectMask; // Add only effect mask + return; + } + } + + // This is new target add data + + ItemTargetInfo target = new ItemTargetInfo(); + target.item = item; + target.effectMask = effectMask; + + m_UniqueItemInfo.Add(target); + } + + void AddDestTarget(SpellDestination dest, uint effIndex) + { + m_destTargets[effIndex] = dest; + } + + void DoAllEffectOnTarget(TargetInfo target) + { + if (target == null || target.processed) + return; + + target.processed = true; // Target checked in apply effects procedure + + // Get mask of effects for target + uint mask = target.effectMask; + + Unit unit = m_caster.GetGUID() == target.targetGUID ? m_caster : Global.ObjAccessor.GetUnit(m_caster, target.targetGUID); + if (!unit && target.targetGUID.IsPlayer()) // only players may be targeted across maps + { + uint farMask = 0; + // create far target mask + foreach (SpellEffectInfo effect in GetEffects()) + { + if (effect != null && effect.IsFarUnitTargetEffect()) + if (Convert.ToBoolean((1 << (int)effect.EffectIndex) & mask)) + farMask |= (1u << (int)effect.EffectIndex); + } + + if (farMask == 0) + return; + + // find unit in world + unit = Global.ObjAccessor.FindPlayer(target.targetGUID); + if (unit == null) + return; + + // do far effects on the unit + // can't use default call because of threading, do stuff as fast as possible + foreach (SpellEffectInfo effect in GetEffects()) + if (effect != null && Convert.ToBoolean(farMask & (1 << (int)effect.EffectIndex))) + HandleEffects(unit, null, null, effect.EffectIndex, SpellEffectHandleMode.HitTarget); + return; + } + + if (!unit) + return; + + if (unit.IsAlive() != target.alive) + return; + + if (getState() == SpellState.Delayed && !m_spellInfo.IsPositive() && (Time.GetMSTime() - target.timeDelay) <= unit.m_lastSanctuaryTime) + return; // No missinfo in that case + + // Get original caster (if exist) and calculate damage/healing from him data + Unit caster = m_originalCaster ?? m_caster; + + // Skip if m_originalCaster not avaiable + if (caster == null) + return; + + SpellMissInfo missInfo = target.missCondition; + + // Need init unitTarget by default unit (can changed in code on reflect) + // Or on missInfo != SPELL_MISS_NONE unitTarget undefined (but need in trigger subsystem) + unitTarget = unit; + + // Reset damage/healing counter + m_damage = target.damage; + m_healing = -target.damage; + + // Fill base trigger info + ProcFlags procAttacker = m_procAttacker; + ProcFlags procVictim = m_procVictim; + ProcFlagsExLegacy procEx = m_procEx; + + m_spellAura = null; + + //Spells with this flag cannot trigger if effect is cast on self + bool canEffectTrigger = !m_spellInfo.HasAttribute(SpellAttr3.CantTriggerProc) && unitTarget.CanProc() + && (CanExecuteTriggersOnHit(mask) || missInfo == SpellMissInfo.Immune || missInfo == SpellMissInfo.Immune2); + + Unit spellHitTarget = null; + + if (missInfo == SpellMissInfo.None) // In case spell hit target, do all effect on that target + spellHitTarget = unit; + else if (missInfo == SpellMissInfo.Reflect) // In case spell reflect from target, do all effect on caster (if hit) + { + if (target.reflectResult == SpellMissInfo.None) // If reflected spell hit caster . do all effect on him + { + spellHitTarget = m_caster; + if (m_caster.IsTypeId(TypeId.Unit)) + m_caster.ToCreature().LowerPlayerDamageReq((uint)target.damage); + } + } + + PrepareScriptHitHandlers(); + CallScriptBeforeHitHandlers(missInfo); + + bool enablePvP = false; // need to check PvP state before spell effects, but act on it afterwards + + if (spellHitTarget != null) + { + // if target is flagged for pvp also flag caster if a player + if (unit.IsPvP() && m_caster.IsTypeId(TypeId.Player)) + enablePvP = true; // Decide on PvP flagging now, but act on it later. + SpellMissInfo missInfo2 = DoSpellHitOnUnit(spellHitTarget, mask, target.scaleAura); + if (missInfo2 != SpellMissInfo.None) + { + if (missInfo2 != SpellMissInfo.Miss) + m_caster.SendSpellMiss(unit, m_spellInfo.Id, missInfo2); + m_damage = 0; + spellHitTarget = null; + } + } + + // Do not take combo points on dodge and miss + if (missInfo != SpellMissInfo.None && m_needComboPoints && + m_targets.GetUnitTargetGUID() == target.targetGUID) + { + m_needComboPoints = false; + // Restore spell mods for a miss/dodge/parry Cold Blood + /// @todo check how broad this rule should be + if (m_caster.IsTypeId(TypeId.Player) && (missInfo == SpellMissInfo.Miss || + missInfo == SpellMissInfo.Dodge || missInfo == SpellMissInfo.Parry)) + m_caster.ToPlayer().RestoreSpellMods(this, 14177); + } + + // Trigger info was not filled in spell.preparedatafortriggersystem - we do it now + if (canEffectTrigger && procAttacker == 0 && procVictim == 0) + { + bool positive = true; + if (m_damage > 0) + positive = false; + else if (m_healing == 0) + positive = m_spellInfo.IsPositive(); + + switch (m_spellInfo.DmgClass) + { + case SpellDmgClass.Magic: + if (positive) + { + procAttacker |= ProcFlags.DoneSpellMagicDmgClassPos; + procVictim |= ProcFlags.TakenSpellMagicDmgClassPos; + } + else + { + procAttacker |= ProcFlags.DoneSpellMagicDmgClassNeg; + procVictim |= ProcFlags.TakenSpellMagicDmgClassNeg; + } + break; + case SpellDmgClass.None: + if (positive) + { + procAttacker |= ProcFlags.DoneSpellNoneDmgClassPos; + procVictim |= ProcFlags.TakenSpellNoneDmgClassPos; + } + else + { + procAttacker |= ProcFlags.DoneSpellNoneDmgClassNeg; + procVictim |= ProcFlags.TakenSpellNoneDmgClassNeg; + } + break; + } + } + CallScriptOnHitHandlers(); + + // All calculated do it! + // Do healing and triggers + if (m_healing > 0) + { + bool crit = target.crit; + uint addhealth = (uint)m_healing; + if (crit) + { + procEx |= ProcFlagsExLegacy.CriticalHit; + addhealth = (uint)caster.SpellCriticalHealingBonus(m_spellInfo, (int)addhealth, null); + } + else + procEx |= ProcFlagsExLegacy.NormalHit; + + int gain = caster.HealBySpell(unitTarget, m_spellInfo, addhealth, crit); + unitTarget.getHostileRefManager().threatAssist(caster, gain * 0.5f, m_spellInfo); + m_healing = gain; + + // Do triggers for unit (reflect triggers passed on hit phase for correct drop charge) + if (canEffectTrigger && missInfo != SpellMissInfo.Reflect) + caster.ProcDamageAndSpell(unitTarget, procAttacker, procVictim, procEx, addhealth, m_attackType, m_spellInfo, m_triggeredByAuraSpell); + } + // Do damage and triggers + else if (m_damage > 0) + { + // Fill base damage struct (unitTarget - is real spell target) + SpellNonMeleeDamage damageInfo = new SpellNonMeleeDamage(caster, unitTarget, m_spellInfo.Id, m_SpellVisual, m_spellSchoolMask, m_castId); + + // Add bonuses and fill damageInfo struct + caster.CalculateSpellDamageTaken(damageInfo, m_damage, m_spellInfo, m_attackType, target.crit); + caster.DealDamageMods(damageInfo.target, ref damageInfo.damage, ref damageInfo.absorb); + + procEx |= Unit.createProcExtendMask(damageInfo, missInfo); + procVictim |= ProcFlags.TakenDamage; + + // Do triggers for unit (reflect triggers passed on hit phase for correct drop charge) + if (canEffectTrigger && missInfo != SpellMissInfo.Reflect) + { + caster.ProcDamageAndSpell(unitTarget, procAttacker, procVictim, procEx, damageInfo.damage, m_attackType, m_spellInfo, m_triggeredByAuraSpell); + if (caster.IsTypeId(TypeId.Player) && !m_spellInfo.HasAttribute(SpellAttr0.StopAttackTarget) && + (m_spellInfo.DmgClass == SpellDmgClass.Melee || m_spellInfo.DmgClass == SpellDmgClass.Ranged)) + caster.ToPlayer().CastItemCombatSpell(unitTarget, m_attackType, procVictim, procEx); + } + + m_damage = (int)damageInfo.damage; + + caster.DealSpellDamage(damageInfo, true); + + // Send log damage message to client + caster.SendSpellNonMeleeDamageLog(damageInfo); + } + // Passive spell hits/misses or active spells only misses (only triggers) + else + { + // Fill base damage struct (unitTarget - is real spell target) + SpellNonMeleeDamage damageInfo = new SpellNonMeleeDamage(caster, unitTarget, m_spellInfo.Id, m_SpellVisual, m_spellSchoolMask); + procEx |= Unit.createProcExtendMask(damageInfo, missInfo); + // Do triggers for unit (reflect triggers passed on hit phase for correct drop charge) + if (canEffectTrigger && missInfo != SpellMissInfo.Reflect) + caster.ProcDamageAndSpell(unit, procAttacker, procVictim, procEx, 0, m_attackType, m_spellInfo, m_triggeredByAuraSpell); + + // Failed Pickpocket, reveal rogue + if (missInfo == SpellMissInfo.Resist && m_spellInfo.HasAttribute(SpellCustomAttributes.PickPocket) && unitTarget.IsTypeId(TypeId.Unit)) + { + m_caster.RemoveAurasWithInterruptFlags(SpellAuraInterruptFlags.Talk); + if (unitTarget.ToCreature().IsAIEnabled) + unitTarget.ToCreature().GetAI().AttackStart(m_caster); + } + } + + if (missInfo != SpellMissInfo.Evade && !m_caster.IsFriendlyTo(unit) && (!m_spellInfo.IsPositive() || m_spellInfo.HasEffect(SpellEffectName.Dispel))) + { + m_caster.CombatStart(unit, m_spellInfo.HasInitialAggro()); + + if (!unit.IsStandState()) + unit.SetStandState(UnitStandStateType.Stand); + } + + // Check for SPELL_ATTR7_INTERRUPT_ONLY_NONPLAYER + if (missInfo == SpellMissInfo.None && m_spellInfo.HasAttribute(SpellAttr7.InterruptOnlyNonplayer) && !unit.IsTypeId(TypeId.Player)) + caster.CastSpell(unit, 32747, true); + + if (spellHitTarget != null) + { + //AI functions + if (spellHitTarget.IsTypeId(TypeId.Unit)) + if (spellHitTarget.ToCreature().IsAIEnabled) + spellHitTarget.ToCreature().GetAI().SpellHit(m_caster, m_spellInfo); + + if (m_caster.IsTypeId(TypeId.Unit) && m_caster.ToCreature().IsAIEnabled) + m_caster.ToCreature().GetAI().SpellHitTarget(spellHitTarget, m_spellInfo); + + // Needs to be called after dealing damage/healing to not remove breaking on damage auras + DoTriggersOnSpellHit(spellHitTarget, mask); + + // if target is fallged for pvp also flag caster if a player + if (enablePvP) + m_caster.ToPlayer().UpdatePvP(true); + + CallScriptAfterHitHandlers(); + } + } + + SpellMissInfo DoSpellHitOnUnit(Unit unit, uint effectMask, bool scaleAura) + { + if (unit == null || effectMask == 0) + return SpellMissInfo.Evade; + + // For delayed spells immunity may be applied between missile launch and hit - check immunity for that case + if (m_spellInfo.Speed != 0.0f && (unit.IsImmunedToDamage(m_spellInfo) || unit.IsImmunedToSpell(m_spellInfo))) + return SpellMissInfo.Immune; + + // disable effects to which unit is immune + SpellMissInfo returnVal = SpellMissInfo.Immune; + foreach (SpellEffectInfo effect in GetEffects()) + if (effect != null && Convert.ToBoolean(effectMask & (1 << (int)effect.EffectIndex))) + if (unit.IsImmunedToSpellEffect(m_spellInfo, effect.EffectIndex)) + effectMask &= (uint)~(1 << (int)effect.EffectIndex); + + if (effectMask == 0) + return returnVal; + + Player player = unit.ToPlayer(); + if (player != null) + { + player.StartCriteriaTimer(CriteriaTimedTypes.SpellTarget, m_spellInfo.Id); + player.UpdateCriteria(CriteriaTypes.BeSpellTarget, m_spellInfo.Id, 0, 0, m_caster); + player.UpdateCriteria(CriteriaTypes.BeSpellTarget2, m_spellInfo.Id); + } + + player = m_caster.ToPlayer(); + if (player != null) + { + player.StartCriteriaTimer(CriteriaTimedTypes.SpellCaster, m_spellInfo.Id); + player.UpdateCriteria(CriteriaTypes.CastSpell2, m_spellInfo.Id, 0, 0, unit); + } + + if (m_caster != unit) + { + // Recheck UNIT_FLAG_NON_ATTACKABLE for delayed spells + if (m_spellInfo.Speed > 0.0f && unit.HasFlag(UnitFields.Flags, UnitFlags.NonAttackable) && unit.GetCharmerOrOwnerGUID() != m_caster.GetGUID()) + return SpellMissInfo.Evade; + + if (m_caster._IsValidAttackTarget(unit, m_spellInfo)) + { + unit.RemoveAurasWithInterruptFlags(SpellAuraInterruptFlags.Hitbyspell); + if (!m_spellInfo.HasAttribute(SpellCustomAttributes.DontBreakStealth)) + unit.RemoveAurasByType(AuraType.ModStealth); + } + else if (m_caster.IsFriendlyTo(unit)) + { + // for delayed spells ignore negative spells (after duel end) for friendly targets + /// @todo this cause soul transfer bugged + // 63881 - Malady of the Mind jump spell (Yogg-Saron) + if (m_spellInfo.Speed > 0.0f && unit.IsTypeId(TypeId.Player) && !m_spellInfo.IsPositive() && m_spellInfo.Id != 63881) + return SpellMissInfo.Evade; + + // assisting case, healing and resurrection + if (unit.HasUnitState(UnitState.AttackPlayer)) + { + m_caster.SetContestedPvP(); + if (m_caster.IsTypeId(TypeId.Player)) + m_caster.ToPlayer().UpdatePvP(true); + } + if (unit.IsInCombat() && m_spellInfo.HasInitialAggro()) + { + m_caster.SetInCombatState(unit.GetCombatTimer() > 0, unit); + unit.getHostileRefManager().threatAssist(m_caster, 0.0f); + } + } + } + + uint aura_effmask = 0; + foreach (SpellEffectInfo effect in GetEffects()) + if (effect != null && (Convert.ToBoolean(effectMask & (1 << (int)effect.EffectIndex)) && effect.IsUnitOwnedAuraEffect())) + aura_effmask |= (1u << (int)effect.EffectIndex); + + // Get Data Needed for Diminishing Returns, some effects may have multiple auras, so this must be done on spell hit, not aura add + m_diminishGroup = Global.SpellMgr.GetDiminishingReturnsGroupForSpell(m_spellInfo); + if (m_diminishGroup != 0 && aura_effmask != 0) + { + + m_diminishLevel = unit.GetDiminishing(m_diminishGroup); + DiminishingReturnsType type = Global.SpellMgr.GetDiminishingReturnsGroupType(m_diminishGroup); + // Increase Diminishing on unit, current informations for actually casts will use values above + if ((type == DiminishingReturnsType.Player && unit.GetCharmerOrOwnerPlayerOrPlayerItself() != null) || type == DiminishingReturnsType.All) + unit.IncrDiminishing(m_diminishGroup); + } + + if (aura_effmask != 0) + { + // Select rank for aura with level requirements only in specific cases + // Unit has to be target only of aura effect, both caster and target have to be players, target has to be other than unit target + SpellInfo aurSpellInfo = m_spellInfo; + int[] basePoints = new int[SpellConst.MaxEffects]; + if (scaleAura) + { + aurSpellInfo = m_spellInfo.GetAuraRankForLevel(unitTarget.getLevel()); + Contract.Assert(aurSpellInfo != null); + foreach (SpellEffectInfo effect in aurSpellInfo.GetEffectsForDifficulty(0)) + { + if (effect == null) + continue; + + basePoints[effect.EffectIndex] = effect.BasePoints; + SpellEffectInfo myEffect = GetEffect(effect.EffectIndex); + if (myEffect != null && myEffect.Effect != effect.Effect) + { + aurSpellInfo = m_spellInfo; + break; + } + } + } + + if (m_originalCaster != null) + { + bool refresh = false; + m_spellAura = Aura.TryRefreshStackOrCreate(aurSpellInfo, m_castId, (byte)effectMask, unit, + m_originalCaster, out refresh, (aurSpellInfo == m_spellInfo) ? m_spellValue.EffectBasePoints : basePoints, m_CastItem, ObjectGuid.Empty, m_castItemLevel); + if (m_spellAura != null) + { + // Set aura stack amount to desired value + if (m_spellValue.AuraStackAmount > 1) + { + if (!refresh) + m_spellAura.SetStackAmount(m_spellValue.AuraStackAmount); + else + m_spellAura.ModStackAmount(m_spellValue.AuraStackAmount); + } + + // Now Reduce spell duration using data received at spell hit + int duration = m_spellAura.GetMaxDuration(); + int limitduration = m_diminishGroup != 0 ? Global.SpellMgr.GetDiminishingReturnsLimitDuration(aurSpellInfo) : 0; + float diminishMod = unit.ApplyDiminishingToDuration(m_diminishGroup, duration, m_originalCaster, m_diminishLevel, limitduration); + + // unit is immune to aura if it was diminished to 0 duration + if (diminishMod == 0.0f) + { + m_spellAura.Remove(); + bool found = false; + foreach (SpellEffectInfo effect in GetEffects()) + if (effect != null && (Convert.ToBoolean(effectMask & (1 << (int)effect.EffectIndex)) && effect.Effect != SpellEffectName.ApplyAura)) + found = true; + if (!found) + return SpellMissInfo.Immune; + } + else + { + ((UnitAura)m_spellAura).SetDiminishGroup(m_diminishGroup); + + bool positive = m_spellAura.GetSpellInfo().IsPositive(); + AuraApplication aurApp = m_spellAura.GetApplicationOfTarget(m_originalCaster.GetGUID()); + if (aurApp != null) + positive = aurApp.IsPositive(); + + duration = m_originalCaster.ModSpellDuration(aurSpellInfo, unit, duration, positive, effectMask); + + if (duration > 0) + { + // Haste modifies duration of channeled spells + if (m_spellInfo.IsChanneled()) + m_originalCaster.ModSpellDurationTime(aurSpellInfo, ref duration, this); + else if (m_spellInfo.HasAttribute(SpellAttr5.HasteAffectDuration)) + { + int origDuration = duration; + duration = 0; + foreach (SpellEffectInfo effect in GetEffects()) + { + if (effect != null) + { + AuraEffect eff = m_spellAura.GetEffect(effect.EffectIndex); + if (eff != null) + { + int period = eff.GetPeriod(); + if (period != 0) // period is hastened by UNIT_MOD_CAST_SPEED + duration = Math.Max(Math.Max(origDuration / period, 1) * period, duration); + } + } + } + + // if there is no periodic effect + if (duration == 0) + duration = (int)(origDuration * m_originalCaster.GetFloatValue(UnitFields.ModCastSpeed)); + } + } + + if (duration != m_spellAura.GetMaxDuration()) + { + m_spellAura.SetMaxDuration(duration); + m_spellAura.SetDuration(duration); + } + m_spellAura._RegisterForTargets(); + } + } + } + } + + foreach (SpellEffectInfo effect in GetEffects()) + if (effect != null && Convert.ToBoolean(effectMask & (1 << (int)effect.EffectIndex))) + HandleEffects(unit, null, null, effect.EffectIndex, SpellEffectHandleMode.HitTarget); + + return SpellMissInfo.None; + } + + void DoTriggersOnSpellHit(Unit unit, uint effMask) + { + // Apply additional spell effects to target + /// @todo move this code to scripts + if (m_preCastSpell != 0) + { + // Paladin immunity shields + if (m_preCastSpell == 61988) + { + // Cast Forbearance + m_caster.CastSpell(unit, 25771, true); + // Cast Avenging Wrath Marker + unit.CastSpell(unit, 61987, true); + } + + // Avenging Wrath + if (m_preCastSpell == 61987) + // Cast the serverside immunity shield marker + m_caster.CastSpell(unit, 61988, true); + + if (Global.SpellMgr.GetSpellInfo(m_preCastSpell) != null) + // Blizz seems to just apply aura without bothering to cast + m_caster.AddAura(m_preCastSpell, unit); + } + + // handle SPELL_AURA_ADD_TARGET_TRIGGER auras + // this is executed after spell proc spells on target hit + // spells are triggered for each hit spell target + // info confirmed with retail sniffs of permafrost and shadow weaving + if (!m_hitTriggerSpells.Empty()) + { + int _duration = 0; + foreach (var hit in m_hitTriggerSpells) + { + if (CanExecuteTriggersOnHit(effMask, hit.triggeredByAura) && RandomHelper.randChance(hit.chance)) + { + m_caster.CastSpell(unit, hit.triggeredSpell, true); + Log.outDebug(LogFilter.Spells, "Spell {0} triggered spell {1} by SPELL_AURA_ADD_TARGET_TRIGGER aura", m_spellInfo.Id, hit.triggeredSpell.Id); + + // SPELL_AURA_ADD_TARGET_TRIGGER auras shouldn't trigger auras without duration + // set duration of current aura to the triggered spell + if (hit.triggeredSpell.GetDuration() == -1) + { + Aura triggeredAur = unit.GetAura(hit.triggeredSpell.Id, m_caster.GetGUID()); + if (triggeredAur != null) + { + // get duration from aura-only once + if (_duration == 0) + { + Aura aur = unit.GetAura(m_spellInfo.Id, m_caster.GetGUID()); + _duration = aur != null ? aur.GetDuration() : -1; + } + triggeredAur.SetDuration(_duration); + } + } + } + } + } + + // trigger linked auras remove/apply + // @todo remove/cleanup this, as this table is not documented and people are doing stupid things with it + var spellTriggered = Global.SpellMgr.GetSpellLinked((int)m_spellInfo.Id + (int)SpellLinkedType.Hit); + if (spellTriggered != null) + { + foreach (var id in spellTriggered) + { + if (id < 0) + unit.RemoveAurasDueToSpell((uint)-(id)); + else + unit.CastSpell(unit, (uint)id, true, null, null, m_caster.GetGUID()); + } + } + } + + void DoAllEffectOnTarget(GOTargetInfo target) + { + if (target.processed) // Check target + return; + + target.processed = true; // Target checked in apply effects procedure + + uint effectMask = target.effectMask; + if (effectMask == 0) + return; + + GameObject go = m_caster.GetMap().GetGameObject(target.targetGUID); + if (go == null) + return; + + PrepareScriptHitHandlers(); + CallScriptBeforeHitHandlers(SpellMissInfo.None); + + foreach (SpellEffectInfo effect in GetEffects()) + if (effect != null && Convert.ToBoolean(effectMask & (1 << (int)effect.EffectIndex))) + HandleEffects(null, null, go, effect.EffectIndex, SpellEffectHandleMode.HitTarget); + + CallScriptOnHitHandlers(); + CallScriptAfterHitHandlers(); + } + + void DoAllEffectOnTarget(ItemTargetInfo target) + { + uint effectMask = target.effectMask; + if (target.item == null || effectMask == 0) + return; + + PrepareScriptHitHandlers(); + CallScriptBeforeHitHandlers(SpellMissInfo.None); + + foreach (SpellEffectInfo effect in GetEffects()) + if (effect != null && Convert.ToBoolean(effectMask & (1 << (int)effect.EffectIndex))) + HandleEffects(null, target.item, null, effect.EffectIndex, SpellEffectHandleMode.HitTarget); + + CallScriptOnHitHandlers(); + + CallScriptAfterHitHandlers(); + } + + bool UpdateChanneledTargetList() + { + // Not need check return true + if (m_channelTargetEffectMask == 0) + return true; + + uint channelTargetEffectMask = m_channelTargetEffectMask; + uint channelAuraMask = 0; + foreach (SpellEffectInfo effect in GetEffects()) + if (effect != null && effect.Effect == SpellEffectName.ApplyAura) + channelAuraMask |= (1u << (int)effect.EffectIndex); + + channelAuraMask &= channelTargetEffectMask; + + float range = 0; + if (channelAuraMask != 0) + { + range = m_spellInfo.GetMaxRange(m_spellInfo.IsPositive()); + Player modOwner = m_caster.GetSpellModOwner(); + if (modOwner != null) + modOwner.ApplySpellMod(m_spellInfo.Id, SpellModOp.Range, ref range, this); + } + + foreach (var ihit in m_UniqueTargetInfo) + { + if (ihit.missCondition == SpellMissInfo.None && Convert.ToBoolean(channelTargetEffectMask & ihit.effectMask)) + { + Unit unit = m_caster.GetGUID() == ihit.targetGUID ? m_caster : Global.ObjAccessor.GetUnit(m_caster, ihit.targetGUID); + + if (unit == null) + continue; + + if (IsValidDeadOrAliveTarget(unit)) + { + if (Convert.ToBoolean(channelAuraMask & ihit.effectMask)) + { + var b = unit.GetGUID(); + AuraApplication aurApp = unit.GetAuraApplication(m_spellInfo.Id, m_originalCasterGUID); + if (aurApp != null) + { + if (m_caster != unit && !m_caster.IsWithinDistInMap(unit, range)) + { + ihit.effectMask &= ~aurApp.GetEffectMask(); + unit.RemoveAura(aurApp); + continue; + } + } + else // aura is dispelled + continue; + } + + channelTargetEffectMask &= (byte)~ihit.effectMask; // remove from need alive mask effect that have alive target + } + } + } + + // is all effects from m_needAliveTargetMask have alive targets + return channelTargetEffectMask == 0; + } + + public void prepare(SpellCastTargets targets, AuraEffect triggeredByAura = null) + { + if (m_CastItem != null) + { + m_castItemGUID = m_CastItem.GetGUID(); + m_castItemEntry = m_CastItem.GetEntry(); + + Player owner = m_CastItem.GetOwner(); + if (owner) + m_castItemLevel = (int)m_CastItem.GetItemLevel(owner); + else if (m_CastItem.GetOwnerGUID() == m_caster.GetGUID()) + m_castItemLevel = (int)m_CastItem.GetItemLevel(m_caster.ToPlayer()); + else + { + SendCastResult(SpellCastResult.EquippedItem); + finish(false); + return; + } + } + + InitExplicitTargets(targets); + + // Fill aura scaling information + if (m_caster.IsControlledByPlayer() && !m_spellInfo.IsPassive() && m_spellInfo.SpellLevel != 0 && !m_spellInfo.IsChanneled() && !Convert.ToBoolean(_triggeredCastFlags & TriggerCastFlags.IgnoreAuraScaling)) + { + foreach (SpellEffectInfo effect in GetEffects()) + { + if (effect != null && effect.Effect == SpellEffectName.ApplyAura) + { + // Change aura with ranks only if basepoints are taken from spellInfo and aura is positive + if (m_spellInfo.IsPositiveEffect(effect.EffectIndex)) + { + m_auraScaleMask |= (byte)(1 << (int)effect.EffectIndex); + if (m_spellValue.EffectBasePoints[effect.EffectIndex] != effect.BasePoints) + { + m_auraScaleMask = 0; + break; + } + } + } + } + } + + m_spellState = SpellState.Preparing; + + if (triggeredByAura != null) + { + m_triggeredByAuraSpell = triggeredByAura.GetSpellInfo(); + m_castItemLevel = triggeredByAura.GetBase().GetCastItemLevel(); + } + + // create and add update event for this spell + SpellEvent Event = new SpellEvent(this); + m_caster.m_Events.AddEvent(Event, m_caster.m_Events.CalculateTime(1)); + + //Prevent casting at cast another spell (ServerSide check) + if (!Convert.ToBoolean(_triggeredCastFlags & TriggerCastFlags.IgnoreCastInProgress) && m_caster.IsNonMeleeSpellCast(false, true, true) && !m_castId.IsEmpty()) + { + SendCastResult(SpellCastResult.SpellInProgress); + finish(false); + return; + } + + if (Global.DisableMgr.IsDisabledFor(DisableType.Spell, m_spellInfo.Id, m_caster)) + { + SendCastResult(SpellCastResult.SpellUnavailable); + finish(false); + return; + } + LoadScripts(); + + if (m_caster.IsTypeId(TypeId.Player)) + m_caster.ToPlayer().SetSpellModTakingSpell(this, true); + // Fill cost data (not use power for item casts + if (!m_CastItem) + m_powerCost = m_spellInfo.CalcPowerCost(m_caster, m_spellSchoolMask); + if (m_caster.IsTypeId(TypeId.Player)) + m_caster.ToPlayer().SetSpellModTakingSpell(this, false); + + // Set combo point requirement + if (Convert.ToBoolean(_triggeredCastFlags & TriggerCastFlags.IgnoreComboPoints) || m_CastItem != null || m_caster.m_playerMovingMe == null) + m_needComboPoints = false; + + SpellCastResult result = CheckCast(true); + // target is checked in too many locations and with different results to handle each of them + // handle just the general SPELL_FAILED_BAD_TARGETS result which is the default result for most DBC target checks + if (Convert.ToBoolean(_triggeredCastFlags & TriggerCastFlags.IgnoreTargetCheck) && result == SpellCastResult.BadTargets) + result = SpellCastResult.SpellCastOk; + if (result != SpellCastResult.SpellCastOk && !IsAutoRepeat()) //always cast autorepeat dummy for triggering + { + // Periodic auras should be interrupted when aura triggers a spell which can't be cast + // for example bladestorm aura should be removed on disarm as of patch 3.3.5 + // channeled periodic spells should be affected by this (arcane missiles, penance, etc) + // a possible alternative sollution for those would be validating aura target on unit state change + if (triggeredByAura != null && triggeredByAura.IsPeriodic() && !triggeredByAura.GetBase().IsPassive()) + { + SendChannelUpdate(0); + triggeredByAura.GetBase().SetDuration(0); + } + + if (m_caster.IsTypeId(TypeId.Player)) + { + m_caster.ToPlayer().RestoreSpellMods(this); + // cleanup after mod system + // triggered spell pointer can be not removed in some cases + m_caster.ToPlayer().SetSpellModTakingSpell(this, false); + } + + SendCastResult(result); + + finish(false); + return; + } + + // Prepare data for triggers + prepareDataForTriggerSystem(triggeredByAura); + + if (m_caster.IsTypeId(TypeId.Player)) + { + if (!m_caster.ToPlayer().GetCommandStatus(PlayerCommandStates.Casttime)) + { + m_caster.ToPlayer().SetSpellModTakingSpell(this, true); + m_casttime = m_spellInfo.CalcCastTime(m_caster.getLevel(), this); + m_caster.ToPlayer().SetSpellModTakingSpell(this, false); + } + else + m_casttime = 0; + } + else + m_casttime = m_spellInfo.CalcCastTime(m_caster.getLevel(), this); + + if (m_caster.IsTypeId(TypeId.Unit) && !m_caster.HasFlag(UnitFields.Flags, UnitFlags.PlayerControlled)) // _UNIT actually means creature. for some reason. + { + if (!(IsNextMeleeSwingSpell() || IsAutoRepeat() || _triggeredCastFlags.HasAnyFlag(TriggerCastFlags.IgnoreSetFacing))) + { + if (m_targets.GetObjectTarget() && m_caster != m_targets.GetObjectTarget()) + m_caster.ToCreature().FocusTarget(this, m_targets.GetObjectTarget()); + else if (m_spellInfo.HasAttribute(SpellAttr5.DontTurnDuringCast)) + m_caster.ToCreature().FocusTarget(this, null); + } + } + + // don't allow channeled spells / spells with cast time to be casted while moving + // exception are only channeled spells that have no casttime and SPELL_ATTR5_CAN_CHANNEL_WHEN_MOVING + // (even if they are interrupted on moving, spells with almost immediate effect get to have their effect processed before movement interrupter kicks in) + // don't cancel spells which are affected by a SPELL_AURA_CAST_WHILE_WALKING effect + if (((m_spellInfo.IsChanneled() || m_casttime != 0) && m_caster.IsTypeId(TypeId.Player) && !(m_caster.IsCharmed() && m_caster.GetCharmerGUID().IsCreature()) && m_caster.isMoving() && + m_spellInfo.InterruptFlags.HasAnyFlag(SpellInterruptFlags.Movement) && !m_caster.HasAuraTypeWithAffectMask(AuraType.CastWhileWalking, m_spellInfo))) + { + // 1. Is a channel spell, 2. Has no casttime, 3. And has flag to allow movement during channel + if (!(m_spellInfo.IsChanneled() && m_casttime == 0 && m_spellInfo.HasAttribute(SpellAttr5.CanChannelWhenMoving))) + { + SendCastResult(SpellCastResult.Moving); + finish(false); + return; + } + } + + // set timer base at cast time + ReSetTimer(); + + Log.outDebug(LogFilter.Spells, "Spell.prepare: spell id {0} source {1} caster {2} customCastFlags {3} mask {4}", m_spellInfo.Id, m_caster.GetEntry(), m_originalCaster != null ? (int)m_originalCaster.GetEntry() : -1, _triggeredCastFlags, m_targets.GetTargetMask()); + + //Containers for channeled spells have to be set + // @todoApply this to all casted spells if needed + // Why check duration? 29350: channelled triggers channelled + if (_triggeredCastFlags.HasAnyFlag(TriggerCastFlags.CastDirectly) && (!m_spellInfo.IsChanneled() || m_spellInfo.GetMaxDuration() == 0)) + cast(true); + else + { + // stealth must be removed at cast starting (at show channel bar) + // skip triggered spell (item equip spell casting and other not explicit character casts/item uses) + if (!_triggeredCastFlags.HasAnyFlag(TriggerCastFlags.IgnoreAuraInterruptFlags) && m_spellInfo.IsBreakingStealth()) + { + m_caster.RemoveAurasWithInterruptFlags(SpellAuraInterruptFlags.Cast); + foreach (SpellEffectInfo effect in GetEffects()) + { + if (effect != null && effect.GetUsedTargetObjectType() == SpellTargetObjectTypes.Unit) + { + m_caster.RemoveAurasWithInterruptFlags(SpellAuraInterruptFlags.SpellAttack); + break; + } + } + } + + m_caster.SetCurrentCastSpell(this); + + SendSpellStart(); + + if (!_triggeredCastFlags.HasAnyFlag(TriggerCastFlags.IgnoreGCD)) + TriggerGlobalCooldown(); + + //item: first cast may destroy item and second cast causes crash + if (m_casttime == 0 && m_spellInfo.StartRecoveryTime == 0 && m_castItemGUID.IsEmpty() && GetCurrentContainer() == CurrentSpellTypes.Generic) + cast(true); + } + } + + public void cancel() + { + if (m_spellState == SpellState.Finished) + return; + + SpellState oldState = m_spellState; + m_spellState = SpellState.Finished; + + m_autoRepeat = false; + switch (oldState) + { + case SpellState.Preparing: + CancelGlobalCooldown(); + if (m_caster.IsTypeId(TypeId.Player)) + m_caster.ToPlayer().RestoreSpellMods(this); + goto case SpellState.Delayed; + case SpellState.Delayed: + SendInterrupted(0); + SendCastResult(SpellCastResult.Interrupted); + break; + + case SpellState.Casting: + foreach (var ihit in m_UniqueTargetInfo) + { + if (ihit.missCondition == SpellMissInfo.None) + { + Unit unit = m_caster.GetGUID() == ihit.targetGUID ? m_caster : Global.ObjAccessor.GetUnit(m_caster, ihit.targetGUID); + if (unit != null) + unit.RemoveOwnedAura(m_spellInfo.Id, m_originalCasterGUID, 0, AuraRemoveMode.Cancel); + } + } + + SendChannelUpdate(0); + SendInterrupted(0); + SendCastResult(SpellCastResult.Interrupted); + + // spell is canceled-take mods and clear list + if (m_caster.IsTypeId(TypeId.Player)) + m_caster.ToPlayer().RemoveSpellMods(this); + + m_appliedMods.Clear(); + break; + + default: + break; + } + + SetReferencedFromCurrent(false); + if (m_selfContainer != null && m_selfContainer == this) + m_selfContainer = null; + + m_caster.RemoveDynObject(m_spellInfo.Id); + if (m_spellInfo.IsChanneled()) // if not channeled then the object for the current cast wasn't summoned yet + m_caster.RemoveGameObject(m_spellInfo.Id, true); + + //set state back so finish will be processed + m_spellState = oldState; + + finish(false); + } + + public void cast(bool skipCheck = false) + { + if (!UpdatePointers()) + { + // cancel the spell if UpdatePointers() returned false, something wrong happened there + cancel(); + return; + } + + // cancel at lost explicit target during cast + if (!m_targets.GetObjectTargetGUID().IsEmpty() && m_targets.GetObjectTarget() == null) + { + cancel(); + return; + } + + Player playerCaster = m_caster.ToPlayer(); + if (playerCaster != null) + { + // now that we've done the basic check, now run the scripts + // should be done before the spell is actually executed + Global.ScriptMgr.OnPlayerSpellCast(playerCaster, this, skipCheck); + + // As of 3.0.2 pets begin attacking their owner's target immediately + // Let any pets know we've attacked something. Check DmgClass for harmful spells only + // This prevents spells such as Hunter's Mark from triggering pet attack + if (m_spellInfo.DmgClass != SpellDmgClass.None) + { + Pet playerPet = playerCaster.GetPet(); + if (playerPet != null) + if (playerPet.IsAlive() && playerPet.isControlled() && Convert.ToBoolean(m_targets.GetTargetMask() & SpellCastTargetFlags.Unit)) + playerPet.GetAI().OwnerAttacked(m_targets.GetUnitTarget()); + } + } + + SetExecutedCurrently(true); + + if (!Convert.ToBoolean(_triggeredCastFlags & TriggerCastFlags.IgnoreSetFacing)) + if (m_caster.IsTypeId(TypeId.Unit) && m_targets.GetObjectTarget() != null && m_caster != m_targets.GetObjectTarget()) + m_caster.SetInFront(m_targets.GetObjectTarget()); + + // Should this be done for original caster? + if (m_caster.IsTypeId(TypeId.Player)) + { + // Set spell which will drop charges for triggered cast spells + // if not successfully casted, will be remove in finish(false) + m_caster.ToPlayer().SetSpellModTakingSpell(this, true); + } + + CallScriptBeforeCastHandlers(); + + // skip check if done already (for instant cast spells for example) + if (!skipCheck) + { + SpellCastResult castResult = CheckCast(false); + if (castResult != SpellCastResult.SpellCastOk) + { + SendCastResult(castResult); + SendInterrupted(0); + //restore spell mods + if (m_caster.IsTypeId(TypeId.Player)) + { + m_caster.ToPlayer().RestoreSpellMods(this); + // cleanup after mod system + m_caster.ToPlayer().SetSpellModTakingSpell(this, false); + } + finish(false); + SetExecutedCurrently(false); + return; + } + + // additional check after cast bar completes (must not be in CheckCast) + // if trade not complete then remember it in trade data + if (Convert.ToBoolean(m_targets.GetTargetMask() & SpellCastTargetFlags.TradeItem)) + { + if (m_caster.IsTypeId(TypeId.Player)) + { + TradeData my_trade = m_caster.ToPlayer().GetTradeData(); + if (my_trade != null) + { + if (!my_trade.IsInAcceptProcess()) + { + // Spell will be casted at completing the trade. Silently ignore at this place + my_trade.SetSpell(m_spellInfo.Id, m_CastItem); + SendCastResult(SpellCastResult.DontReport); + SendInterrupted(0); + m_caster.ToPlayer().RestoreSpellMods(this); + // cleanup after mod system + m_caster.ToPlayer().SetSpellModTakingSpell(this, false); + finish(false); + SetExecutedCurrently(false); + return; + } + } + } + } + } + + // if the spell allows the creature to turn while casting, then adjust server-side orientation to face the target now + // client-side orientation is handled by the client itself, as the cast target is targeted due to Creature::FocusTarget + if (m_caster.IsTypeId(TypeId.Unit) && !m_caster.HasFlag(UnitFields.Flags, UnitFlags.PlayerControlled)) + { + if (!m_spellInfo.HasAttribute(SpellAttr5.DontTurnDuringCast)) + { + WorldObject objTarget = m_targets.GetObjectTarget(); + if (objTarget != null) + m_caster.SetInFront(objTarget); + } + } + + SelectSpellTargets(); + + // Spell may be finished after target map check + if (m_spellState == SpellState.Finished) + { + SendInterrupted(0); + //restore spell mods + if (m_caster.IsTypeId(TypeId.Player)) + { + m_caster.ToPlayer().RestoreSpellMods(this); + // cleanup after mod system + m_caster.ToPlayer().SetSpellModTakingSpell(this, false); + } + finish(false); + SetExecutedCurrently(false); + return; + } + + if (m_spellInfo.HasAttribute(SpellAttr1.DismissPet)) + { + Creature pet = ObjectAccessor.GetCreature(m_caster, m_caster.GetPetGUID()); + if (pet) + pet.DespawnOrUnsummon(); + } + + PrepareTriggersExecutedOnHit(); + + CallScriptOnCastHandlers(); + + // traded items have trade slot instead of guid in m_itemTargetGUID + // set to real guid to be sent later to the client + m_targets.UpdateTradeSlotItem(); + + Player player = m_caster.ToPlayer(); + if (player != null) + { + if (!Convert.ToBoolean(_triggeredCastFlags & TriggerCastFlags.IgnoreCastItem) && m_CastItem != null) + { + player.StartCriteriaTimer(CriteriaTimedTypes.Item, m_CastItem.GetEntry()); + player.UpdateCriteria(CriteriaTypes.UseItem, m_CastItem.GetEntry()); + } + + player.UpdateCriteria(CriteriaTypes.CastSpell, m_spellInfo.Id); + } + + Item targetItem = m_targets.GetItemTarget(); + if (!Convert.ToBoolean(_triggeredCastFlags & TriggerCastFlags.IgnorePowerAndReagentCost)) + { + // Powers have to be taken before SendSpellGo + TakePower(); + TakeReagents(); // we must remove reagents before HandleEffects to allow place crafted item in same slot + } + else if (targetItem != null) + { + // Not own traded item (in trader trade slot) req. reagents including triggered spell case + if (targetItem.GetOwnerGUID() != m_caster.GetGUID()) + TakeReagents(); + } + + // CAST SPELL + SendSpellCooldown(); + + PrepareScriptHitHandlers(); + + HandleLaunchPhase(); + + // we must send smsg_spell_go packet before m_castItem delete in TakeCastItem()... + SendSpellGo(); + + // Okay, everything is prepared. Now we need to distinguish between immediate and evented delayed spells + if ((m_spellInfo.Speed > 0.0f && !m_spellInfo.IsChanneled()) || m_spellInfo.HasAttribute(SpellAttr4.Unk4)) + { + // Remove used for cast item if need (it can be already NULL after TakeReagents call + // in case delayed spell remove item at cast delay start + TakeCastItem(); + + // Okay, maps created, now prepare flags + m_immediateHandled = false; + m_spellState = SpellState.Delayed; + SetDelayStart(0); + + if (m_caster.HasUnitState(UnitState.Casting) && !m_caster.IsNonMeleeSpellCast(false, false, true)) + m_caster.ClearUnitState(UnitState.Casting); + } + else + { + // Immediate spell, no big deal + handle_immediate(); + } + + CallScriptAfterCastHandlers(); + + var spell_triggered = Global.SpellMgr.GetSpellLinked((int)m_spellInfo.Id); + if (spell_triggered != null) + { + foreach (var spellId in spell_triggered) + { + if (spellId < 0) + m_caster.RemoveAurasDueToSpell((uint)-spellId); + else + m_caster.CastSpell(m_targets.GetUnitTarget() ?? m_caster, (uint)spellId, true); + } + } + + if (m_caster.IsTypeId(TypeId.Player)) + { + m_caster.ToPlayer().SetSpellModTakingSpell(this, false); + + //Clear spell cooldowns after every spell is cast if .cheat cooldown is enabled. + if (m_caster.ToPlayer().GetCommandStatus(PlayerCommandStates.Cooldown)) + { + m_caster.GetSpellHistory().ResetCooldown(m_spellInfo.Id, true); + m_caster.GetSpellHistory().RestoreCharge(m_spellInfo.ChargeCategoryId); + } + } + + SetExecutedCurrently(false); + + Creature creatureCaster = m_caster.ToCreature(); + if (creatureCaster) + creatureCaster.ReleaseFocus(this); + } + + void handle_immediate() + { + // start channeling if applicable + if (m_spellInfo.IsChanneled()) + { + int duration = m_spellInfo.GetDuration(); + if (duration > 0) + { + // First mod_duration then haste - see Missile Barrage + // Apply duration mod + Player modOwner = m_caster.GetSpellModOwner(); + if (modOwner != null) + modOwner.ApplySpellMod(m_spellInfo.Id, SpellModOp.Duration, ref duration); + // Apply haste mods + m_caster.ModSpellDurationTime(m_spellInfo, ref duration, this); + + m_spellState = SpellState.Casting; + m_caster.AddInterruptMask((uint)m_spellInfo.ChannelInterruptFlags); + SendChannelStart((uint)duration); + } + else if (duration == -1) + { + m_spellState = SpellState.Casting; + m_caster.AddInterruptMask((uint)m_spellInfo.ChannelInterruptFlags); + SendChannelStart((uint)duration); + } + } + + PrepareTargetProcessing(); + + // process immediate effects (items, ground, etc.) also initialize some variables + _handle_immediate_phase(); + + foreach (var ihit in m_UniqueTargetInfo) + DoAllEffectOnTarget(ihit); + + foreach (var ihit in m_UniqueGOTargetInfo) + DoAllEffectOnTarget(ihit); + + FinishTargetProcessing(); + + // spell is finished, perform some last features of the spell here + _handle_finish_phase(); + + // Remove used for cast item if need (it can be already NULL after TakeReagents call + TakeCastItem(); + + if (m_spellState != SpellState.Casting) + finish(true); // successfully finish spell cast (not last in case autorepeat or channel spell) + } + + public ulong handle_delayed(ulong t_offset) + { + if (!UpdatePointers()) + { + // finish the spell if UpdatePointers() returned false, something wrong happened there + finish(false); + return 0; + } + + if (m_caster.IsTypeId(TypeId.Player)) + m_caster.ToPlayer().SetSpellModTakingSpell(this, true); + + ulong next_time = 0; + + PrepareTargetProcessing(); + + if (!m_immediateHandled) + { + _handle_immediate_phase(); + m_immediateHandled = true; + } + + bool single_missile = m_targets.HasDst(); + + // now recheck units targeting correctness (need before any effects apply to prevent adding immunity at first effect not allow apply second spell effect and similar cases) + foreach (var ihit in m_UniqueTargetInfo) + { + if (!ihit.processed) + { + if (single_missile || ihit.timeDelay <= t_offset) + { + ihit.timeDelay = t_offset; + DoAllEffectOnTarget(ihit); + } + else if (next_time == 0 || ihit.timeDelay < next_time) + next_time = ihit.timeDelay; + } + } + + // now recheck gameobject targeting correctness + foreach (var ighit in m_UniqueGOTargetInfo) + { + if (!ighit.processed) + { + if (single_missile || ighit.timeDelay <= t_offset) + DoAllEffectOnTarget(ighit); + else if (next_time == 0 || ighit.timeDelay < next_time) + next_time = ighit.timeDelay; + } + } + + FinishTargetProcessing(); + + if (m_caster.IsTypeId(TypeId.Player)) + m_caster.ToPlayer().SetSpellModTakingSpell(this, false); + + // All targets passed - need finish phase + if (next_time == 0) + { + // spell is finished, perform some last features of the spell here + _handle_finish_phase(); + + finish(true); // successfully finish spell cast + + // return zero, spell is finished now + return 0; + } + else + { + // spell is unfinished, return next execution time + return next_time; + } + } + + void _handle_immediate_phase() + { + m_spellAura = null; + // initialize Diminishing Returns Data + m_diminishLevel = DiminishingLevels.Level1; + m_diminishGroup = DiminishingGroup.None; + + // handle some immediate features of the spell here + HandleThreatSpells(); + + PrepareScriptHitHandlers(); + + // handle effects with SPELL_EFFECT_HANDLE_HIT mode + foreach (SpellEffectInfo effect in GetEffects()) + { + // don't do anything for empty effect + if (effect == null || !effect.IsEffect()) + continue; + + // call effect handlers to handle destination hit + HandleEffects(null, null, null, effect.EffectIndex, SpellEffectHandleMode.Hit); + } + + // process items + foreach (var ihit in m_UniqueItemInfo) + DoAllEffectOnTarget(ihit); + + if (m_originalCaster == null) + return; + // Handle procs on cast + // @todo finish new proc system:P + if (m_UniqueTargetInfo.Empty() && m_targets.HasDst()) + { + var procAttacker = m_procAttacker; + if (procAttacker == 0) + procAttacker |= ProcFlags.DoneSpellMagicDmgClassPos; + + // Proc the spells that have DEST target + m_originalCaster.ProcDamageAndSpell(null, procAttacker, 0, m_procEx | ProcFlagsExLegacy.NormalHit, 0, WeaponAttackType.BaseAttack, m_spellInfo, m_triggeredByAuraSpell); + } + } + + void _handle_finish_phase() + { + if (m_caster.m_playerMovingMe != null) + { + // Take for real after all targets are processed + if (m_needComboPoints) + m_caster.m_playerMovingMe.ClearComboPoints(); + + // Real add combo points from effects + if (m_comboPointGain != 0) + m_caster.m_playerMovingMe.GainSpellComboPoints(m_comboPointGain); + } + + if (m_caster.m_extraAttacks != 0 && m_spellInfo.HasEffect(SpellEffectName.AddExtraAttacks)) + { + Unit victim = Global.ObjAccessor.GetUnit(m_caster, m_targets.GetOrigUnitTargetGUID()); + if (victim) + m_caster.HandleProcExtraAttackFor(victim); + else + m_caster.m_extraAttacks = 0; + } + // @todo trigger proc phase finish here + } + + void SendSpellCooldown() + { + if (m_CastItem) + m_caster.GetSpellHistory().HandleCooldowns(m_spellInfo, m_CastItem, this); + else + m_caster.GetSpellHistory().HandleCooldowns(m_spellInfo, m_castItemEntry, this); + } + + public void update(uint difftime) + { + if (!UpdatePointers()) + { + // cancel the spell if UpdatePointers() returned false, something wrong happened there + cancel(); + return; + } + + if (!m_targets.GetUnitTargetGUID().IsEmpty() && m_targets.GetUnitTarget() == null) + { + Log.outDebug(LogFilter.Spells, "Spell {0} is cancelled due to removal of target.", m_spellInfo.Id); + cancel(); + return; + } + + // check if the player caster has moved before the spell finished + // with the exception of spells affected with SPELL_AURA_CAST_WHILE_WALKING effect + SpellEffectInfo effect = GetEffect(0); + if ((m_caster.IsTypeId(TypeId.Player) && m_timer != 0) && + m_caster.isMoving() && m_spellInfo.InterruptFlags.HasAnyFlag(SpellInterruptFlags.Movement) && + ((effect != null && effect.Effect != SpellEffectName.Stuck) || !m_caster.HasUnitMovementFlag(MovementFlag.FallingFar)) && + !m_caster.HasAuraTypeWithAffectMask(AuraType.CastWhileWalking, m_spellInfo)) + { + // don't cancel for melee, autorepeat, triggered and instant spells + if (!IsNextMeleeSwingSpell() && !IsAutoRepeat() && !IsTriggered() && !(IsChannelActive() && m_spellInfo.HasAttribute(SpellAttr5.CanChannelWhenMoving))) + { + // if charmed by creature, trust the AI not to cheat and allow the cast to proceed + // @todo this is a hack, "creature" movesplines don't differentiate turning/moving right now + // however, checking what type of movement the spline is for every single spline would be really expensive + if (!m_caster.GetCharmerGUID().IsCreature()) + cancel(); + } + } + + switch (m_spellState) + { + case SpellState.Preparing: + { + if (m_timer > 0) + { + if (difftime >= m_timer) + m_timer = 0; + else + m_timer -= (int)difftime; + } + + if (m_timer == 0 && !IsNextMeleeSwingSpell() && !IsAutoRepeat()) + // don't CheckCast for instant spells - done in spell.prepare, skip duplicate checks, needed for range checks for example + cast(m_casttime == 0); + break; + } + case SpellState.Casting: + { + if (m_timer != 0) + { + // check if there are alive targets left + if (!UpdateChanneledTargetList()) + { + Log.outDebug(LogFilter.Spells, "Channeled spell {0} is removed due to lack of targets", m_spellInfo.Id); + SendChannelUpdate(0); + finish(); + } + + if (m_timer > 0) + { + if (difftime >= m_timer) + m_timer = 0; + else + m_timer -= (int)difftime; + } + } + + if (m_timer == 0) + { + SendChannelUpdate(0); + finish(); + } + break; + } + default: + break; + } + } + + public void finish(bool ok = true) + { + if (!m_caster) + return; + + if (m_spellState == SpellState.Finished) + return; + m_spellState = SpellState.Finished; + + if (m_spellInfo.IsChanneled()) + m_caster.UpdateInterruptMask(); + + if (m_caster.HasUnitState(UnitState.Casting) && !m_caster.IsNonMeleeSpellCast(false, false, true)) + m_caster.ClearUnitState(UnitState.Casting); + + // Unsummon summon as possessed creatures on spell cancel + if (m_spellInfo.IsChanneled() && m_caster.IsTypeId(TypeId.Player)) + { + Unit charm = m_caster.GetCharm(); + if (charm != null) + if (charm.IsTypeId(TypeId.Unit) && charm.ToCreature().HasUnitTypeMask(UnitTypeMask.Puppet) + && charm.GetUInt32Value(UnitFields.CreatedBySpell) == m_spellInfo.Id) + ((Puppet)charm).UnSummon(); + } + + if (m_caster.IsTypeId(TypeId.Unit)) + m_caster.ToCreature().ReleaseFocus(this); + + if (!ok) + return; + + if (m_caster.IsTypeId(TypeId.Unit) && m_caster.ToCreature().IsSummon()) + { + // Unsummon statue + uint spell = m_caster.GetUInt32Value(UnitFields.CreatedBySpell); + SpellInfo spellInfo = Global.SpellMgr.GetSpellInfo(spell); + if (spellInfo != null && spellInfo.IconFileDataId == 134230) + { + Log.outDebug(LogFilter.Spells, "Statue {0} is unsummoned in spell {1} finish", m_caster.GetGUID().ToString(), m_spellInfo.Id); + m_caster.setDeathState(DeathState.JustDied); + return; + } + } + + if (IsAutoActionResetSpell()) + { + bool found = false; + var vIgnoreReset = m_caster.GetAuraEffectsByType(AuraType.IgnoreMeleeReset); + foreach (var i in vIgnoreReset) + { + if (i.IsAffectingSpell(m_spellInfo)) + { + found = true; + break; + } + } + if (!found && !m_spellInfo.HasAttribute(SpellAttr2.NotResetAutoActions)) + { + m_caster.resetAttackTimer(WeaponAttackType.BaseAttack); + if (m_caster.haveOffhandWeapon()) + m_caster.resetAttackTimer(WeaponAttackType.OffAttack); + m_caster.resetAttackTimer(WeaponAttackType.RangedAttack); + } + } + + // potions disabled by client, send event "not in combat" if need + if (m_caster.IsTypeId(TypeId.Player)) + { + if (m_triggeredByAuraSpell == null) + m_caster.ToPlayer().UpdatePotionCooldown(this); + } + + Player modOwner = m_caster.GetSpellModOwner(); + if (modOwner) + { + // triggered spell pointer can be not set in some cases + // this is needed for proper apply of triggered spell mods + modOwner.SetSpellModTakingSpell(this, true); + + // Take mods after trigger spell (needed for 14177 to affect 48664) + // mods are taken only on succesfull cast and independantly from targets of the spell + modOwner.RemoveSpellMods(this); + modOwner.SetSpellModTakingSpell(this, false); + } + + // Stop Attack for some spells + if (m_spellInfo.HasAttribute(SpellAttr0.StopAttackTarget)) + m_caster.AttackStop(); + } + + static void FillSpellCastFailedArgs(T packet, ObjectGuid castId, SpellInfo spellInfo, SpellCastResult result, SpellCustomErrors customError, uint[] misc, Player caster) where T : CastFailedBase + { + packet.CastID = castId; + packet.SpellID = (int)spellInfo.Id; + packet.Reason = result; + + switch (result) + { + case SpellCastResult.NotReady: + packet.FailedArg1 = 0; // unknown (value 1 update cooldowns on client flag) + break; + case SpellCastResult.RequiresSpellFocus: + packet.FailedArg1 = (int)spellInfo.RequiresSpellFocus; // SpellFocusObject.dbc id + break; + case SpellCastResult.RequiresArea: // AreaTable.dbc id + // hardcode areas limitation case + switch (spellInfo.Id) + { + case 41617: // Cenarion Mana Salve + case 41619: // Cenarion Healing Salve + packet.FailedArg1 = 3905; + break; + case 41618: // Bottled Nethergon Energy + case 41620: // Bottled Nethergon Vapor + packet.FailedArg1 = 3842; + break; + case 45373: // Bloodberry Elixir + packet.FailedArg1 = 4075; + break; + default: // default case (don't must be) + packet.FailedArg1 = 0; + break; + } + break; + case SpellCastResult.Totems: + if (spellInfo.Totem[0] != 0) + packet.FailedArg1 = (int)spellInfo.Totem[0]; + if (spellInfo.Totem[1] != 0) + packet.FailedArg2 = (int)spellInfo.Totem[1]; + break; + case SpellCastResult.TotemCategory: + if (spellInfo.TotemCategory[0] != 0) + packet.FailedArg1 = (int)spellInfo.TotemCategory[0]; + if (spellInfo.TotemCategory[1] != 0) + packet.FailedArg2 = (int)spellInfo.TotemCategory[1]; + break; + case SpellCastResult.EquippedItemClass: + case SpellCastResult.EquippedItemClassMainhand: + case SpellCastResult.EquippedItemClassOffhand: + packet.FailedArg1 = (int)spellInfo.EquippedItemClass; + packet.FailedArg2 = spellInfo.EquippedItemSubClassMask; + break; + case SpellCastResult.TooManyOfItem: + { + uint item = 0; + foreach (SpellEffectInfo effect in spellInfo.GetEffectsForDifficulty(caster.GetMap().GetDifficultyID())) + if (effect.ItemType != 0) + item = effect.ItemType; + ItemTemplate proto = Global.ObjectMgr.GetItemTemplate(item); + if (proto != null && proto.GetItemLimitCategory() != 0) + packet.FailedArg1 = (int)proto.GetItemLimitCategory(); + break; + } + case SpellCastResult.PreventedByMechanic: + packet.FailedArg1 = (int)spellInfo.GetAllEffectsMechanicMask(); // SpellMechanic.dbc id + break; + case SpellCastResult.NeedExoticAmmo: + packet.FailedArg1 = spellInfo.EquippedItemSubClassMask; // seems correct... + break; + case SpellCastResult.NeedMoreItems: + packet.FailedArg1 = 0; // Item id + packet.FailedArg2 = 0; // Item count? + break; + case SpellCastResult.MinSkill: + packet.FailedArg1 = 0; // SkillLine.dbc id + packet.FailedArg2 = 0; // required skill value + break; + case SpellCastResult.FishingTooLow: + packet.FailedArg1 = 0; // required fishing skill + break; + case SpellCastResult.CustomError: + packet.FailedArg1 = (int)customError; + break; + case SpellCastResult.Silenced: + packet.FailedArg1 = 0; // Unknown + break; + case SpellCastResult.Reagents: + { + uint missingItem = 0; + for (uint i = 0; i < SpellConst.MaxReagents; i++) + { + if (spellInfo.Reagent[i] <= 0) + continue; + + uint itemid = (uint)spellInfo.Reagent[i]; + uint itemcount = spellInfo.ReagentCount[i]; + + if (!caster.HasItemCount(itemid, itemcount)) + { + missingItem = itemid; + break; + } + } + + packet.FailedArg1 = (int)missingItem; // first missing item + break; + } + case SpellCastResult.CantUntalent: + { + if (misc != null) + { + TalentRecord talent = CliDB.TalentStorage.LookupByKey(misc[0]); + if (talent != null) + packet.FailedArg1 = (int)talent.SpellID; + } + break; + } + // TODO: SPELL_FAILED_NOT_STANDING + default: + break; + } + } + + public void SendCastResult(SpellCastResult result) + { + if (result == SpellCastResult.SpellCastOk) + return; + + if (!m_caster.IsTypeId(TypeId.Player)) + return; + + if (m_caster.ToPlayer().GetSession().PlayerLoading()) // don't send cast results at loading time + return; + + if (_triggeredCastFlags.HasAnyFlag(TriggerCastFlags.DontReportCastError)) + result = SpellCastResult.DontReport; + + CastFailed castFailed = new CastFailed(); + castFailed.SpellXSpellVisualID = (int)m_SpellVisual; + FillSpellCastFailedArgs(castFailed, m_castId, m_spellInfo, result, m_customError, m_misc.GetRawData(), m_caster.ToPlayer()); + m_caster.ToPlayer().SendPacket(castFailed); + } + + public void SendPetCastResult(SpellCastResult result) + { + if (result == SpellCastResult.SpellCastOk) + return; + + Unit owner = m_caster.GetCharmerOrOwner(); + if (!owner || !owner.IsTypeId(TypeId.Player)) + return; + + if (_triggeredCastFlags.HasAnyFlag(TriggerCastFlags.DontReportCastError)) + result = SpellCastResult.DontReport; + + PetCastFailed petCastFailed = new PetCastFailed(); + FillSpellCastFailedArgs(petCastFailed, m_castId, m_spellInfo, result, SpellCustomErrors.None, m_misc.GetRawData(), owner.ToPlayer()); + owner.ToPlayer().SendPacket(petCastFailed); + } + + public static void SendCastResult(Player caster, SpellInfo spellInfo, uint spellVisual, ObjectGuid cast_count, SpellCastResult result, SpellCustomErrors customError = SpellCustomErrors.None, uint[] misc = null) + { + if (result == SpellCastResult.SpellCastOk) + return; + + CastFailed packet = new CastFailed(); + packet.SpellXSpellVisualID = (int)spellVisual; + FillSpellCastFailedArgs(packet, cast_count, spellInfo, result, customError, misc, caster); + caster.SendPacket(packet); + } + + void SendSpellStart() + { + if (!IsNeedSendToClient()) + return; + + SpellCastFlags castFlags = SpellCastFlags.HasTrajectory; + uint schoolImmunityMask = m_caster.GetSchoolImmunityMask(); + uint mechanicImmunityMask = m_caster.GetMechanicImmunityMask(); + if (schoolImmunityMask != 0 || mechanicImmunityMask != 0) + castFlags |= SpellCastFlags.Immunity; + + if (((IsTriggered() && !m_spellInfo.IsAutoRepeatRangedSpell()) || m_triggeredByAuraSpell != null) && !m_fromClient) + castFlags |= SpellCastFlags.Pending; + + if (m_spellInfo.HasAttribute(SpellAttr0.ReqAmmo) || m_spellInfo.HasAttribute(SpellCustomAttributes.NeedsAmmoData)) + castFlags |= SpellCastFlags.Projectile; + + if ((m_caster.IsTypeId(TypeId.Player) || (m_caster.IsTypeId(TypeId.Unit) && m_caster.IsPet())) && m_powerCost.Any(cost => cost.Power != PowerType.Health)) + castFlags |= SpellCastFlags.PowerLeftSelf; + + if (m_powerCost.Any(cost => cost.Power == PowerType.Runes)) + castFlags |= SpellCastFlags.NoGCD; // not needed, but Blizzard sends it + + SpellStart packet = new SpellStart(); + SpellCastData castData = packet.Cast; + + if (m_CastItem) + castData.CasterGUID = m_CastItem.GetGUID(); + else + castData.CasterGUID = m_caster.GetGUID(); + + castData.CasterUnit = m_caster.GetGUID(); + castData.CastID = m_castId; + castData.OriginalCastID = m_originalCastId; + castData.SpellID = (int)m_spellInfo.Id; + castData.SpellXSpellVisualID = m_SpellVisual; + castData.CastFlags = castFlags; + castData.CastFlagsEx = m_castFlagsEx; + castData.CastTime = (uint)m_casttime; + + m_targets.Write(castData.Target); + + if (castFlags.HasAnyFlag(SpellCastFlags.PowerLeftSelf)) + { + foreach (SpellPowerCost cost in m_powerCost) + { + SpellPowerData powerData; + powerData.Type = cost.Power; + powerData.Cost = m_caster.GetPower(cost.Power); + castData.RemainingPower.Add(powerData); + } + } + + if (castFlags.HasAnyFlag(SpellCastFlags.RuneList)) // rune cooldowns list + { + castData.RemainingRunes.HasValue = true; + + RuneData runeData = castData.RemainingRunes.Value; + //TODO: There is a crash caused by a spell with CAST_FLAG_RUNE_LIST casted by a creature + //The creature is the mover of a player, so HandleCastSpellOpcode uses it as the caster + + Player player = m_caster.ToPlayer(); + if (player) + { + runeData.Start = m_runesState; // runes state before + runeData.Count = player.GetRunesState(); // runes state after + for (byte i = 0; i < PlayerConst.MaxRunes; ++i) + { + // float casts ensure the division is performed on floats as we need float result + float baseCd = player.GetRuneBaseCooldown(); + runeData.Cooldowns.Add((byte)((baseCd - player.GetRuneCooldown(i)) / baseCd * 255)); // rune cooldown passed + } + } + else + { + runeData.Start = 0; + runeData.Count = 0; + for (byte i = 0; i < PlayerConst.MaxRunes; ++i) + runeData.Cooldowns.Add(0); + } + } + + UpdateSpellCastDataAmmo(castData.Ammo); + + if (castFlags.HasAnyFlag(SpellCastFlags.Immunity)) + { + castData.Immunities.School = schoolImmunityMask; + castData.Immunities.Value = mechanicImmunityMask; + } + + /** @todo implement heal prediction packet data + if (castFlags & CAST_FLAG_HEAL_PREDICTION) + { + castData.Predict.BeconGUID = ?? + castData.Predict.Points = 0; + castData.Predict.Type = 0; + }**/ + + m_caster.SendMessageToSet(packet, true); + } + + void SendSpellGo() + { + // not send invisible spell casting + if (!IsNeedSendToClient()) + return; + + Log.outDebug(LogFilter.Spells, "Sending SMSG_SPELL_GO id={0}", m_spellInfo.Id); + + SpellCastFlags castFlags = SpellCastFlags.Unk9; + + // triggered spells with spell visual != 0 + if (((IsTriggered() && !m_spellInfo.IsAutoRepeatRangedSpell()) || m_triggeredByAuraSpell != null) && !m_fromClient) + castFlags |= SpellCastFlags.Pending; + + if (m_spellInfo.HasAttribute(SpellAttr0.ReqAmmo) || m_spellInfo.HasAttribute(SpellCustomAttributes.NeedsAmmoData)) + castFlags |= SpellCastFlags.Projectile; // arrows/bullets visual + + if ((m_caster.IsTypeId(TypeId.Player) || (m_caster.IsTypeId(TypeId.Unit) && m_caster.IsPet())) && m_powerCost.Any(cost => cost.Power != PowerType.Health)) + castFlags |= SpellCastFlags.PowerLeftSelf; // should only be sent to self, but the current messaging doesn't make that possible + + if ((m_caster.IsTypeId(TypeId.Player)) && (m_caster.GetClass() == Class.Deathknight) && + m_powerCost.Any(cost => cost.Power == PowerType.Runes) && !_triggeredCastFlags.HasAnyFlag(TriggerCastFlags.IgnorePowerAndReagentCost)) + { + castFlags |= SpellCastFlags.NoGCD; // same as in SMSG_SPELL_START + castFlags |= SpellCastFlags.RuneList; // rune cooldowns list + } + + if (m_spellInfo.HasEffect(SpellEffectName.ActivateRune)) + castFlags |= SpellCastFlags.RuneList; // rune cooldowns list + + if (m_targets.HasTraj()) + castFlags |= SpellCastFlags.AdjustMissile; + + if (m_spellInfo.StartRecoveryTime == 0) + castFlags |= SpellCastFlags.NoGCD; + + SpellGo packet = new SpellGo(); + SpellCastData castData = packet.Cast; + + if (m_CastItem != null) + castData.CasterGUID = m_CastItem.GetGUID(); + else + castData.CasterGUID = m_caster.GetGUID(); + + castData.CasterUnit = m_caster.GetGUID(); + castData.CastID = m_castId; + castData.OriginalCastID = m_originalCastId; + castData.SpellID = (int)m_spellInfo.Id; + castData.SpellXSpellVisualID = m_SpellVisual; + castData.CastFlags = castFlags; + castData.CastFlagsEx = m_castFlagsEx; + castData.CastTime = Time.GetMSTime(); + + castData.HitTargets = new List(); + UpdateSpellCastDataTargets(castData); + + m_targets.Write(castData.Target); + + if (Convert.ToBoolean(castFlags & SpellCastFlags.PowerLeftSelf)) + { + castData.RemainingPower = new List(); + foreach (SpellPowerCost cost in m_powerCost) + { + SpellPowerData powerData; + powerData.Type = cost.Power; + powerData.Cost = m_caster.GetPower(cost.Power); + castData.RemainingPower.Add(powerData); + } + } + + if (Convert.ToBoolean(castFlags & SpellCastFlags.RuneList)) // rune cooldowns list + { + castData.RemainingRunes.HasValue = true; + RuneData runeData = castData.RemainingRunes.Value; + //TODO: There is a crash caused by a spell with CAST_FLAG_RUNE_LIST casted by a creature + //The creature is the mover of a player, so HandleCastSpellOpcode uses it as the caster + Player player = m_caster.ToPlayer(); + if (player) + { + runeData.Start = m_runesState; // runes state before + runeData.Count = player.GetRunesState(); // runes state after + for (byte i = 0; i < PlayerConst.MaxRunes; ++i) + { + // float casts ensure the division is performed on floats as we need float result + float baseCd = player.GetRuneBaseCooldown(); + runeData.Cooldowns.Add((byte)((baseCd - player.GetRuneCooldown(i)) / baseCd * 255)); // rune cooldown passed + } + } + else + { + runeData.Start = 0; + runeData.Count = 0; + for (byte i = 0; i < PlayerConst.MaxRunes; ++i) + runeData.Cooldowns.Add(0); + } + } + + if (Convert.ToBoolean(castFlags & SpellCastFlags.AdjustMissile)) + { + castData.MissileTrajectory.TravelTime = (uint)m_delayMoment; + castData.MissileTrajectory.Pitch = m_targets.GetPitch(); + } + + packet.LogData.Initialize(this); + + m_caster.SendCombatLogMessage(packet); + } + + // Writes miss and hit targets for a SMSG_SPELL_GO packet + void UpdateSpellCastDataTargets(SpellCastData data) + { + // This function also fill data for channeled spells: + // m_needAliveTargetMask req for stop channelig if one target die + foreach (var targetInfo in m_UniqueTargetInfo) + { + if (targetInfo.effectMask == 0) // No effect apply - all immuned add state + // possibly SPELL_MISS_IMMUNE2 for this?? + targetInfo.missCondition = SpellMissInfo.Immune2; + + if (targetInfo.missCondition == SpellMissInfo.None) // hits + { + data.HitTargets.Add(targetInfo.targetGUID); + + m_channelTargetEffectMask |= targetInfo.effectMask; + } + else // misses + { + data.MissTargets.Add(targetInfo.targetGUID); + + SpellMissStatus missStatus = new SpellMissStatus(); + missStatus.Reason = (byte)targetInfo.missCondition; + if (targetInfo.missCondition == SpellMissInfo.Reflect) + missStatus.ReflectStatus = (byte)targetInfo.reflectResult; + + data.MissStatus.Add(missStatus); + } + } + + foreach (GOTargetInfo targetInfo in m_UniqueGOTargetInfo) + data.HitTargets.Add(targetInfo.targetGUID); // Always hits + + // Reset m_needAliveTargetMask for non channeled spell + if (!m_spellInfo.IsChanneled()) + m_channelTargetEffectMask = 0; + } + + void UpdateSpellCastDataAmmo(SpellAmmo ammo) + { + InventoryType ammoInventoryType = 0; + uint ammoDisplayID = 0; + + if (m_caster.IsTypeId(TypeId.Player)) + { + Item pItem = m_caster.ToPlayer().GetWeaponForAttack(WeaponAttackType.RangedAttack); + if (pItem) + { + ammoInventoryType = pItem.GetTemplate().GetInventoryType(); + if (ammoInventoryType == InventoryType.Thrown) + ammoDisplayID = pItem.GetDisplayId(m_caster.ToPlayer()); + else if (m_caster.HasAura(46699)) // Requires No Ammo + { + ammoDisplayID = 5996; // normal arrow + ammoInventoryType = InventoryType.Ammo; + } + } + } + else + { + for (byte i = 0; i < 3; ++i) + { + uint itemId = m_caster.GetVirtualItemId(i); + if (itemId != 0) + { + ItemRecord itemEntry = CliDB.ItemStorage.LookupByKey(itemId); + if (itemEntry != null) + { + if (itemEntry.Class == ItemClass.Weapon) + { + switch ((ItemSubClassWeapon)itemEntry.SubClass) + { + case ItemSubClassWeapon.Thrown: + ammoDisplayID = Global.DB2Mgr.GetItemDisplayId(itemId, m_caster.GetVirtualItemAppearanceMod(i)); + ammoInventoryType = itemEntry.inventoryType; + break; + case ItemSubClassWeapon.Bow: + case ItemSubClassWeapon.Crossbow: + ammoDisplayID = 5996; // is this need fixing? + ammoInventoryType = InventoryType.Ammo; + break; + case ItemSubClassWeapon.Gun: + ammoDisplayID = 5998; // is this need fixing? + ammoInventoryType = InventoryType.Ammo; + break; + } + + if (ammoDisplayID != 0) + break; + } + } + } + } + } + + ammo.DisplayID = (int)ammoDisplayID; + ammo.InventoryType = (sbyte)ammoInventoryType; + } + + void SendSpellExecuteLog() + { + SpellExecuteLog spellExecuteLog = new SpellExecuteLog(); + + spellExecuteLog.Caster = m_caster.GetGUID(); + spellExecuteLog.SpellID = m_spellInfo.Id; + + foreach (SpellEffectInfo effect in GetEffects()) + { + if (effect == null) + continue; + + if (_powerDrainTargets[effect.EffectIndex].Empty() && _extraAttacksTargets[effect.EffectIndex].Empty() && + _durabilityDamageTargets[effect.EffectIndex].Empty() && _genericVictimTargets[effect.EffectIndex].Empty() && + _tradeSkillTargets[effect.EffectIndex].Empty() && _feedPetTargets[effect.EffectIndex].Empty()) + continue; + + SpellExecuteLog.SpellLogEffect spellLogEffect = new SpellExecuteLog.SpellLogEffect(); + spellLogEffect.Effect = (int)effect.Effect; + spellLogEffect.PowerDrainTargets = _powerDrainTargets[effect.EffectIndex]; + spellLogEffect.ExtraAttacksTargets = _extraAttacksTargets[effect.EffectIndex]; + spellLogEffect.DurabilityDamageTargets = _durabilityDamageTargets[effect.EffectIndex]; + spellLogEffect.GenericVictimTargets = _genericVictimTargets[effect.EffectIndex]; + spellLogEffect.TradeSkillTargets = _tradeSkillTargets[effect.EffectIndex]; + spellLogEffect.FeedPetTargets = _feedPetTargets[effect.EffectIndex]; + spellExecuteLog.Effects.Add(spellLogEffect); + } + + if (!spellExecuteLog.Effects.Empty()) + m_caster.SendCombatLogMessage(spellExecuteLog); + + CleanupExecuteLogList(); + } + + void CleanupExecuteLogList() + { + _powerDrainTargets.Clear(); + _extraAttacksTargets.Clear(); + _durabilityDamageTargets.Clear(); + _genericVictimTargets.Clear(); + _tradeSkillTargets.Clear(); + _feedPetTargets.Clear(); + } + + void ExecuteLogEffectTakeTargetPower(uint effIndex, Unit target, PowerType powerType, uint points, float amplitude) + { + SpellLogEffectPowerDrainParams spellLogEffectPowerDrainParams; + + spellLogEffectPowerDrainParams.Victim = target.GetGUID(); + spellLogEffectPowerDrainParams.Points = points; + spellLogEffectPowerDrainParams.PowerType = (uint)powerType; + spellLogEffectPowerDrainParams.Amplitude = amplitude; + + _powerDrainTargets.Add(effIndex, spellLogEffectPowerDrainParams); + } + + void ExecuteLogEffectExtraAttacks(uint effIndex, Unit victim, uint numAttacks) + { + SpellLogEffectExtraAttacksParams spellLogEffectExtraAttacksParams; + spellLogEffectExtraAttacksParams.Victim = victim.GetGUID(); + spellLogEffectExtraAttacksParams.NumAttacks = numAttacks; + + _extraAttacksTargets.Add(effIndex, spellLogEffectExtraAttacksParams); + } + + void ExecuteLogEffectInterruptCast(uint effIndex, Unit victim, uint spellId) + { + SpellInterruptLog data = new SpellInterruptLog(); + data.Caster = m_caster.GetGUID(); + data.Victim = victim.GetGUID(); + data.InterruptedSpellID = m_spellInfo.Id; + data.SpellID = spellId; + + m_caster.SendMessageToSet(data, true); + } + + void ExecuteLogEffectDurabilityDamage(uint effIndex, Unit victim, int itemId, int amount) + { + SpellLogEffectDurabilityDamageParams spellLogEffectDurabilityDamageParams; + spellLogEffectDurabilityDamageParams.Victim = victim.GetGUID(); + spellLogEffectDurabilityDamageParams.ItemID = itemId; + spellLogEffectDurabilityDamageParams.Amount = amount; + + _durabilityDamageTargets.Add(effIndex, spellLogEffectDurabilityDamageParams); + } + + void ExecuteLogEffectOpenLock(uint effIndex, WorldObject obj) + { + SpellLogEffectGenericVictimParams spellLogEffectGenericVictimParams; + spellLogEffectGenericVictimParams.Victim = obj.GetGUID(); + + _genericVictimTargets.Add(effIndex, spellLogEffectGenericVictimParams); + } + + void ExecuteLogEffectCreateItem(uint effIndex, uint entry) + { + SpellLogEffectTradeSkillItemParams spellLogEffectTradeSkillItemParams; + spellLogEffectTradeSkillItemParams.ItemID = (int)entry; + + _tradeSkillTargets.Add(effIndex, spellLogEffectTradeSkillItemParams); + } + + void ExecuteLogEffectDestroyItem(uint effIndex, uint entry) + { + SpellLogEffectFeedPetParams spellLogEffectFeedPetParams; + spellLogEffectFeedPetParams.ItemID = (int)entry; + + _feedPetTargets.Add(effIndex, spellLogEffectFeedPetParams); + } + + void ExecuteLogEffectSummonObject(uint effIndex, WorldObject obj) + { + SpellLogEffectGenericVictimParams spellLogEffectGenericVictimParams; + spellLogEffectGenericVictimParams.Victim = obj.GetGUID(); + + _genericVictimTargets.Add(effIndex, spellLogEffectGenericVictimParams); + } + + void ExecuteLogEffectUnsummonObject(uint effIndex, WorldObject obj) + { + SpellLogEffectGenericVictimParams spellLogEffectGenericVictimParams; + spellLogEffectGenericVictimParams.Victim = obj.GetGUID(); + + _genericVictimTargets.Add(effIndex, spellLogEffectGenericVictimParams); + } + + void ExecuteLogEffectResurrect(uint effIndex, Unit target) + { + SpellLogEffectGenericVictimParams spellLogEffectGenericVictimParams; + spellLogEffectGenericVictimParams.Victim = target.GetGUID(); + + _genericVictimTargets.Add(effIndex, spellLogEffectGenericVictimParams); + } + + void SendInterrupted(byte result) + { + SpellFailure failurePacket = new SpellFailure(); + failurePacket.CasterUnit = m_caster.GetGUID(); + failurePacket.CastID = m_castId; + failurePacket.SpellID = m_spellInfo.Id; + failurePacket.SpellXSpellVisualID = m_SpellVisual; + failurePacket.Reason = result; + m_caster.SendMessageToSet(failurePacket, true); + + SpellFailedOther failedPacket = new SpellFailedOther(); + failedPacket.CasterUnit = m_caster.GetGUID(); + failedPacket.CastID = m_castId; + failedPacket.SpellID = m_spellInfo.Id; + failedPacket.Reason = result; + m_caster.SendMessageToSet(failedPacket, true); + } + + public void SendChannelUpdate(uint time) + { + if (time == 0) + { + m_caster.ClearDynamicValue(UnitDynamicFields.ChannelObjects); + m_caster.SetUInt32Value(UnitFields.ChannelSpell, 0); + } + + SpellChannelUpdate spellChannelUpdate = new SpellChannelUpdate(); + spellChannelUpdate.CasterGUID = m_caster.GetGUID(); + spellChannelUpdate.TimeRemaining = (int)time; + m_caster.SendMessageToSet(spellChannelUpdate, true); + } + + void SendChannelStart(uint duration) + { + SpellChannelStart spellChannelStart = new SpellChannelStart(); + spellChannelStart.CasterGUID = m_caster.GetGUID(); + spellChannelStart.SpellID = (int)m_spellInfo.Id; + spellChannelStart.SpellXSpellVisualID = (int)m_SpellVisual; + spellChannelStart.ChannelDuration = duration; + + uint schoolImmunityMask = m_caster.GetSchoolImmunityMask(); + uint mechanicImmunityMask = m_caster.GetMechanicImmunityMask(); + + if (schoolImmunityMask != 0 || mechanicImmunityMask != 0) + { + spellChannelStart.InterruptImmunities.HasValue = true; + spellChannelStart.InterruptImmunities.Value.SchoolImmunities = (int)schoolImmunityMask; + spellChannelStart.InterruptImmunities.Value.Immunities = (int)mechanicImmunityMask; + } + m_caster.SendMessageToSet(spellChannelStart, true); + + m_timer = (int)duration; + foreach (TargetInfo target in m_UniqueTargetInfo) + m_caster.AddChannelObject(target.targetGUID); + + foreach (GOTargetInfo target in m_UniqueGOTargetInfo) + m_caster.AddChannelObject(target.targetGUID); + + m_caster.SetUInt32Value(UnitFields.ChannelSpell, m_spellInfo.Id); + m_caster.SetUInt32Value(UnitFields.ChannelSpellXSpellVisual, m_SpellVisual); + } + + void SendResurrectRequest(Player target) + { + // get ressurector name for creature resurrections, otherwise packet will be not accepted + // for player resurrections the name is looked up by guid + string sentName = (m_caster.IsTypeId(TypeId.Player) ? "" : m_caster.GetName(target.GetSession().GetSessionDbLocaleIndex())); + + ResurrectRequest resurrectRequest = new ResurrectRequest(); + resurrectRequest.ResurrectOffererGUID = m_caster.GetGUID(); + resurrectRequest.ResurrectOffererVirtualRealmAddress = Global.WorldMgr.GetVirtualRealmAddress(); + + Pet pet = target.GetPet(); + if (pet) + { + CharmInfo charmInfo = pet.GetCharmInfo(); + if (charmInfo != null) + resurrectRequest.PetNumber = charmInfo.GetPetNumber(); + } + + resurrectRequest.SpellID = m_spellInfo.Id; + + //packet.ReadBit("UseTimer"); /// @todo: 6.x Has to be implemented + resurrectRequest.Sickness = !m_caster.IsTypeId(TypeId.Player); // "you'll be afflicted with resurrection sickness" + + resurrectRequest.Name = sentName; + + target.SendPacket(resurrectRequest); + } + + void TakeCastItem() + { + if (m_CastItem == null || !m_caster.IsTypeId(TypeId.Player)) + return; + + // not remove cast item at triggered spell (equipping, weapon damage, etc) + if (Convert.ToBoolean(_triggeredCastFlags & TriggerCastFlags.IgnoreCastItem)) + return; + + ItemTemplate proto = m_CastItem.GetTemplate(); + if (proto == null) + { + // This code is to avoid a crash + // I'm not sure, if this is really an error, but I guess every item needs a prototype + Log.outError(LogFilter.Spells, "Cast item has no item prototype {0}", m_CastItem.GetGUID().ToString()); + return; + } + + bool expendable = false; + bool withoutCharges = false; + + for (int i = 0; i < proto.Effects.Count && i < 5; ++i) + { + if (proto.Effects[i].SpellID != 0) + { + // item has limited charges + if (proto.Effects[i].Charges != 0) + { + if (proto.Effects[i].Charges < 0) + expendable = true; + + int charges = m_CastItem.GetSpellCharges(i); + + // item has charges left + if (charges != 0) + { + if (charges > 0) + --charges; + else + ++charges; + + if (proto.GetMaxStackSize() == 1) + m_CastItem.SetSpellCharges(i, charges); + m_CastItem.SetState(ItemUpdateState.Changed, m_caster.ToPlayer()); + } + + // all charges used + withoutCharges = (charges == 0); + } + } + } + + if (expendable && withoutCharges) + { + uint count = 1; + m_caster.ToPlayer().DestroyItemCount(m_CastItem, ref count, true); + + // prevent crash at access to deleted m_targets.GetItemTarget + if (m_CastItem == m_targets.GetItemTarget()) + m_targets.SetItemTarget(null); + + m_CastItem = null; + m_castItemGUID.Clear(); + m_castItemEntry = 0; + } + } + + void TakePower() + { + if (m_CastItem != null || m_triggeredByAuraSpell != null) + return; + + //Don't take power if the spell is cast while .cheat power is enabled. + if (m_caster.IsTypeId(TypeId.Player)) + { + if (m_caster.ToPlayer().GetCommandStatus(PlayerCommandStates.Power)) + return; + } + + foreach (SpellPowerCost cost in m_powerCost) + { + bool hit = true; + if (m_caster.IsTypeId(TypeId.Player)) + { + if (cost.Power == PowerType.Rage || cost.Power == PowerType.Energy || cost.Power == PowerType.Runes) + { + ObjectGuid targetGUID = m_targets.GetUnitTargetGUID(); + if (!targetGUID.IsEmpty()) + { + foreach (var ihit in m_UniqueTargetInfo) + { + if (ihit.targetGUID == targetGUID) + { + if (ihit.missCondition != SpellMissInfo.None) + { + hit = false; + //lower spell cost on fail (by talent aura) + Player modOwner = m_caster.ToPlayer().GetSpellModOwner(); + if (modOwner) + modOwner.ApplySpellMod(m_spellInfo.Id, SpellModOp.SpellCostRefundOnFail, ref cost.Amount); + } + break; + } + } + } + } + } + + if (cost.Power == PowerType.Runes) + { + TakeRunePower(hit); + continue; + } + + if (cost.Amount == 0) + continue; + + // health as power used + if (cost.Power == PowerType.Health) + { + m_caster.ModifyHealth(-cost.Amount); + continue; + } + + if (cost.Power >= PowerType.Max) + { + Log.outError(LogFilter.Spells, "Spell.TakePower: Unknown power type '{0}'", cost.Power); + continue; + } + + if (hit) + m_caster.ModifyPower(cost.Power, -cost.Amount); + else + m_caster.ModifyPower(cost.Power, -RandomHelper.IRand(0, cost.Amount / 4)); + } + } + + SpellCastResult CheckRuneCost() + { + var runeCost = m_powerCost.Find(cost => cost.Power == PowerType.Runes); + if (runeCost == null) + return SpellCastResult.SpellCastOk; + + Player player = m_caster.ToPlayer(); + if (!player) + return SpellCastResult.SpellCastOk; + + if (player.GetClass() != Class.Deathknight) + return SpellCastResult.SpellCastOk; + + int readyRunes = 0; + for (byte i = 0; i < player.GetMaxPower(PowerType.Runes); ++i) + if (player.GetRuneCooldown(i) == 0) + ++readyRunes; + + if (readyRunes < runeCost.Amount) + return SpellCastResult.NoPower; // not sure if result code is correct + + return SpellCastResult.SpellCastOk; + } + + void TakeRunePower(bool didHit) + { + if (!m_caster.IsTypeId(TypeId.Player) || m_caster.GetClass() != Class.Deathknight) + return; + + Player player = m_caster.ToPlayer(); + m_runesState = player.GetRunesState(); // store previous state + + int runeCost = m_powerCost.Find(cost => cost.Power == PowerType.Runes).Amount; + + for (byte i = 0; i < player.GetMaxPower(PowerType.Runes); ++i) + { + if (player.GetRuneCooldown(i) == 0 && runeCost > 0) + { + player.SetRuneCooldown(i, didHit ? player.GetRuneBaseCooldown() : RuneCooldowns.Miss, true); + --runeCost; + } + } + } + + void TakeReagents() + { + if (!m_caster.IsTypeId(TypeId.Player)) + return; + + ItemTemplate castItemTemplate = m_CastItem?.GetTemplate(); + + // do not take reagents for these item casts + if (castItemTemplate != null && Convert.ToBoolean(castItemTemplate.GetFlags() & ItemFlags.NoReagentCost)) + return; + + Player p_caster = m_caster.ToPlayer(); + if (p_caster.CanNoReagentCast(m_spellInfo)) + return; + + for (int x = 0; x < SpellConst.MaxReagents; ++x) + { + if (m_spellInfo.Reagent[x] <= 0) + continue; + + uint itemid = (uint)m_spellInfo.Reagent[x]; + uint itemcount = m_spellInfo.ReagentCount[x]; + + // if CastItem is also spell reagent + if (castItemTemplate != null && castItemTemplate.GetId() == itemid) + { + for (int s = 0; s < castItemTemplate.Effects.Count && s < 5; ++s) + { + // CastItem will be used up and does not count as reagent + int charges = m_CastItem.GetSpellCharges(s); + if (castItemTemplate.Effects[s].Charges < 0 && Math.Abs(charges) < 2) + { + ++itemcount; + break; + } + } + + m_CastItem = null; + m_castItemGUID.Clear(); + m_castItemEntry = 0; + } + + // if GetItemTarget is also spell reagent + if (m_targets.GetItemTargetEntry() == itemid) + m_targets.SetItemTarget(null); + + p_caster.DestroyItemCount(itemid, itemcount, true); + } + } + + void HandleThreatSpells() + { + if (m_UniqueTargetInfo.Empty()) + return; + + if (!m_spellInfo.HasInitialAggro()) + return; + + float threat = 0.0f; + SpellThreatEntry threatEntry = Global.SpellMgr.GetSpellThreatEntry(m_spellInfo.Id); + if (threatEntry != null) + { + if (threatEntry.apPctMod != 0.0f) + threat += threatEntry.apPctMod * m_caster.GetTotalAttackPowerValue(WeaponAttackType.BaseAttack); + + threat += threatEntry.flatMod; + } + else if (!m_spellInfo.HasAttribute(SpellCustomAttributes.NoInitialThreat)) + threat += m_spellInfo.SpellLevel; + + // past this point only multiplicative effects occur + if (threat == 0.0f) + return; + + // since 2.0.1 threat from positive effects also is distributed among all targets, so the overall caused threat is at most the defined bonus + threat /= m_UniqueTargetInfo.Count; + + foreach (var ihit in m_UniqueTargetInfo) + { + float threatToAdd = threat; + if (ihit.missCondition != SpellMissInfo.None) + threatToAdd = 0.0f; + + Unit target = Global.ObjAccessor.GetUnit(m_caster, ihit.targetGUID); + if (target == null) + continue; + + // positive spells distribute threat among all units that are in combat with target, like healing + if (m_spellInfo._IsPositiveSpell()) + target.getHostileRefManager().threatAssist(m_caster, threatToAdd, m_spellInfo); + // for negative spells threat gets distributed among affected targets + else + { + if (!target.CanHaveThreatList()) + continue; + + target.AddThreat(m_caster, threatToAdd, m_spellInfo.GetSchoolMask(), m_spellInfo); + } + } + Log.outDebug(LogFilter.Spells, "Spell {0}, added an additional {1} threat for {2} {3} target(s)", m_spellInfo.Id, threat, m_spellInfo._IsPositiveSpell() ? "assisting" : "harming", m_UniqueTargetInfo.Count); + } + + void HandleEffects(Unit pUnitTarget, Item pItemTarget, GameObject pGOTarget, uint i, SpellEffectHandleMode mode) + { + effectHandleMode = mode; + unitTarget = pUnitTarget; + itemTarget = pItemTarget; + gameObjTarget = pGOTarget; + destTarget = m_destTargets[i].Position; + + effectInfo = GetEffect(i); + if (effectInfo == null) + { + Log.outError(LogFilter.Spells, "Spell: {0} HandleEffects at EffectIndex: {1} missing effect", m_spellInfo.Id, i); + return; + } + SpellEffectName eff = effectInfo.Effect; + + Log.outDebug(LogFilter.Spells, "Spell: {0} Effect : {1}", m_spellInfo.Id, eff); + + damage = CalculateDamage(i, unitTarget, out variance); + + bool preventDefault = CallScriptEffectHandlers(i, mode); + + if (!preventDefault && eff < SpellEffectName.TotalSpellEffects) + Global.SpellMgr.GetSpellEffectHandler(eff).Invoke(this, i); + } + + public SpellCastResult CheckCast(bool strict) + { + SpellCastResult castResult = SpellCastResult.SpellCastOk; + // check death state + if (!m_caster.IsAlive() && !m_spellInfo.IsPassive() && !(m_spellInfo.HasAttribute(SpellAttr0.CastableWhileDead) || (IsTriggered() && m_triggeredByAuraSpell == null))) + return SpellCastResult.CasterDead; + + // check cooldowns to prevent cheating + if (!m_spellInfo.IsPassive()) + { + if (m_caster.IsTypeId(TypeId.Player)) + { + //can cast triggered (by aura only?) spells while have this flag + if (!_triggeredCastFlags.HasAnyFlag(TriggerCastFlags.IgnoreCasterAurastate) && m_caster.ToPlayer().HasFlag(PlayerFields.Flags, PlayerFlags.AllowOnlyAbility)) + return SpellCastResult.SpellInProgress; + + // check if we are using a potion in combat for the 2nd+ time. Cooldown is added only after caster gets out of combat + if (m_caster.ToPlayer().GetLastPotionId() != 0 && m_CastItem && (m_CastItem.IsPotion() || m_spellInfo.IsCooldownStartedOnEvent())) + return SpellCastResult.NotReady; + } + + if (!m_caster.GetSpellHistory().IsReady(m_spellInfo, m_castItemEntry, IsIgnoringCooldowns())) + { + if (m_triggeredByAuraSpell != null) + return SpellCastResult.DontReport; + else + return SpellCastResult.NotReady; + } + } + + if (m_spellInfo.HasAttribute(SpellAttr7.IsCheatSpell) && !m_caster.HasFlag(UnitFields.Flags2, UnitFlags2.AllowCheatSpells)) + { + m_customError = SpellCustomErrors.GmOnly; + return SpellCastResult.CustomError; + } + + // Check global cooldown + if (strict && !Convert.ToBoolean(_triggeredCastFlags & TriggerCastFlags.IgnoreGCD) && HasGlobalCooldown()) + return !m_spellInfo.HasAttribute(SpellAttr0.DisabledWhileActive) ? SpellCastResult.NotReady : SpellCastResult.DontReport; + + // only triggered spells can be processed an ended Battleground + if (!IsTriggered() && m_caster.IsTypeId(TypeId.Player)) + { + Battleground bg = m_caster.ToPlayer().GetBattleground(); + if (bg) + if (bg.GetStatus() == BattlegroundStatus.WaitLeave) + return SpellCastResult.DontReport; + } + + if (m_caster.IsTypeId(TypeId.Player) && Global.VMapMgr.isLineOfSightCalcEnabled()) + { + if (m_spellInfo.HasAttribute(SpellAttr0.OutdoorsOnly) && + !m_caster.GetMap().IsOutdoors(m_caster.posX, m_caster.posY, m_caster.posZ)) + return SpellCastResult.OnlyOutdoors; + + if (m_spellInfo.HasAttribute(SpellAttr0.IndoorsOnly) && + m_caster.GetMap().IsOutdoors(m_caster.posX, m_caster.posY, m_caster.posZ)) + return SpellCastResult.OnlyIndoors; + } + + // only check at first call, Stealth auras are already removed at second call + // for now, ignore triggered spells + if (strict && !Convert.ToBoolean(_triggeredCastFlags & TriggerCastFlags.IgnoreShapeshift)) + { + bool checkForm = true; + // Ignore form req aura + var ignore = m_caster.GetAuraEffectsByType(AuraType.ModIgnoreShapeshift); + foreach (var aura in ignore) + { + if (!aura.IsAffectingSpell(m_spellInfo)) + continue; + checkForm = false; + break; + } + if (checkForm) + { + // Cannot be used in this stance/form + SpellCastResult shapeError = m_spellInfo.CheckShapeshift(m_caster.GetShapeshiftForm()); + if (shapeError != SpellCastResult.SpellCastOk) + return shapeError; + + if (m_spellInfo.HasAttribute(SpellAttr0.OnlyStealthed) && !m_caster.HasStealthAura()) + return SpellCastResult.OnlyStealthed; + } + } + + var blockSpells = m_caster.GetAuraEffectsByType(AuraType.BlockSpellFamily); + foreach (var block in blockSpells) + if (block.GetMiscValue() == (int)m_spellInfo.SpellFamilyName) + return SpellCastResult.SpellUnavailable; + + bool reqCombat = true; + var stateAuras = m_caster.GetAuraEffectsByType(AuraType.AbilityIgnoreAurastate); + foreach (var aura in stateAuras) + { + if (aura.IsAffectingSpell(m_spellInfo)) + { + m_needComboPoints = false; + if (aura.GetMiscValue() == 1) + { + reqCombat = false; + break; + } + } + } + + // caster state requirements + // not for triggered spells (needed by execute) + if (!Convert.ToBoolean(_triggeredCastFlags & TriggerCastFlags.IgnoreCasterAurastate)) + { + if (m_spellInfo.CasterAuraState != 0 && !m_caster.HasAuraState(m_spellInfo.CasterAuraState, m_spellInfo, m_caster)) + return SpellCastResult.CasterAurastate; + if (m_spellInfo.CasterAuraStateNot != 0 && m_caster.HasAuraState(m_spellInfo.CasterAuraStateNot, m_spellInfo, m_caster)) + return SpellCastResult.CasterAurastate; + + // Note: spell 62473 requres casterAuraSpell = triggering spell + if (m_spellInfo.CasterAuraSpell != 0 && !m_caster.HasAura(m_spellInfo.CasterAuraSpell)) + return SpellCastResult.CasterAurastate; + if (m_spellInfo.ExcludeCasterAuraSpell != 0 && m_caster.HasAura(m_spellInfo.ExcludeCasterAuraSpell)) + return SpellCastResult.CasterAurastate; + + if (reqCombat && m_caster.IsInCombat() && !m_spellInfo.CanBeUsedInCombat()) + return SpellCastResult.AffectingCombat; + } + + // cancel autorepeat spells if cast start when moving + // (not wand currently autorepeat cast delayed to moving stop anyway in spell update code) + // Do not cancel spells which are affected by a SPELL_AURA_CAST_WHILE_WALKING effect + if (m_caster.IsTypeId(TypeId.Player) && m_caster.ToPlayer().isMoving() && (!m_caster.IsCharmed() || !m_caster.GetCharmerGUID().IsCreature()) && !m_caster.HasAuraTypeWithAffectMask(AuraType.CastWhileWalking, m_spellInfo)) + { + // skip stuck spell to allow use it in falling case and apply spell limitations at movement + SpellEffectInfo effect = GetEffect(0); + if ((!m_caster.HasUnitMovementFlag(MovementFlag.FallingFar) || (effect != null && effect.Effect != SpellEffectName.Stuck)) && + (IsAutoRepeat() || (m_spellInfo.AuraInterruptFlags & SpellAuraInterruptFlags.NotSeated) != 0)) + return SpellCastResult.Moving; + } + + // Check vehicle flags + if (!Convert.ToBoolean(_triggeredCastFlags & TriggerCastFlags.IgnoreCasterMountedOrOnVehicle)) + { + SpellCastResult vehicleCheck = m_spellInfo.CheckVehicle(m_caster); + if (vehicleCheck != SpellCastResult.SpellCastOk) + return vehicleCheck; + } + + // check spell cast conditions from database + { + ConditionSourceInfo condInfo = new ConditionSourceInfo(m_caster, m_targets.GetObjectTarget()); + if (!Global.ConditionMgr.IsObjectMeetingNotGroupedConditions(ConditionSourceType.Spell, m_spellInfo.Id, condInfo)) + { + // mLastFailedCondition can be NULL if there was an error processing the condition in Condition.Meets (i.e. wrong data for ConditionTarget or others) + if (condInfo.mLastFailedCondition != null && condInfo.mLastFailedCondition.ErrorType != 0) + { + if (condInfo.mLastFailedCondition.ErrorType == (uint)SpellCastResult.CustomError) + m_customError = (SpellCustomErrors)condInfo.mLastFailedCondition.ErrorTextId; + return (SpellCastResult)condInfo.mLastFailedCondition.ErrorType; + } + + if (condInfo.mLastFailedCondition == null || condInfo.mLastFailedCondition.ConditionTarget == 0) + return SpellCastResult.CasterAurastate; + return SpellCastResult.BadTargets; + } + } + + // Don't check explicit target for passive spells (workaround) (check should be skipped only for learn case) + // those spells may have incorrect target entries or not filled at all (for example 15332) + // such spells when learned are not targeting anyone using targeting system, they should apply directly to caster instead + // also, such casts shouldn't be sent to client + if (!(m_spellInfo.IsPassive() && (m_targets.GetUnitTarget() == null || m_targets.GetUnitTarget() == m_caster))) + { + // Check explicit target for m_originalCaster - todo: get rid of such workarounds + castResult = m_spellInfo.CheckExplicitTarget(m_originalCaster ?? m_caster, m_targets.GetObjectTarget(), m_targets.GetItemTarget()); + if (castResult != SpellCastResult.SpellCastOk) + return castResult; + } + + Unit unitTarget = m_targets.GetUnitTarget(); + if (unitTarget != null) + { + castResult = m_spellInfo.CheckTarget(m_caster, unitTarget, false); + if (castResult != SpellCastResult.SpellCastOk) + return castResult; + + // If it's not a melee spell, check if vision is obscured by SPELL_AURA_INTERFERE_TARGETTING + if (m_spellInfo.DmgClass != SpellDmgClass.Melee) + { + foreach (var auraEffect in m_caster.GetAuraEffectsByType(AuraType.InterfereTargetting)) + if (!m_caster.IsFriendlyTo(auraEffect.GetCaster()) && !unitTarget.HasAura(auraEffect.GetId(), auraEffect.GetCasterGUID())) + return SpellCastResult.VisionObscured; + + foreach (var auraEffect in unitTarget.GetAuraEffectsByType(AuraType.InterfereTargetting)) + if (!m_caster.IsFriendlyTo(auraEffect.GetCaster()) && (!unitTarget.HasAura(auraEffect.GetId(), auraEffect.GetCasterGUID()) || !m_caster.HasAura(auraEffect.GetId(), auraEffect.GetCasterGUID()))) + return SpellCastResult.VisionObscured; + } + + if (unitTarget != m_caster) + { + // Must be behind the target + if (m_spellInfo.HasAttribute(SpellCustomAttributes.ReqCasterBehindTarget) && unitTarget.HasInArc(MathFunctions.PI, m_caster)) + return SpellCastResult.NotBehind; + + // Target must be facing you + if (m_spellInfo.HasAttribute(SpellCustomAttributes.ReqTargetFacingCaster) && !unitTarget.HasInArc(MathFunctions.PI, m_caster)) + return SpellCastResult.NotInfront; + + if (m_caster.GetEntry() != SharedConst.WorldTrigger) // Ignore LOS for gameobjects casts (wrongly casted by a trigger) + { + WorldObject losTarget = m_caster; + if (IsTriggered() && m_triggeredByAuraSpell != null) + { + DynamicObject dynObj = m_caster.GetDynObject(m_triggeredByAuraSpell.Id); + if (dynObj) + losTarget = dynObj; + } + + if (!m_spellInfo.HasAttribute(SpellAttr2.CanTargetNotInLos) && !Global.DisableMgr.IsDisabledFor(DisableType.Spell, m_spellInfo.Id, null, DisableFlags.SpellLOS) + && !unitTarget.IsWithinLOSInMap(losTarget)) + return SpellCastResult.LineOfSight; + } + } + } + + // Check for line of sight for spells with dest + if (m_targets.HasDst()) + { + float x, y, z; + m_targets.GetDstPos().GetPosition(out x, out y, out z); + + if (!m_spellInfo.HasAttribute(SpellAttr2.CanTargetDead) && !Global.DisableMgr.IsDisabledFor(DisableType.Spell, m_spellInfo.Id, null, DisableFlags.SpellLOS) + && !m_caster.IsWithinLOS(x, y, z)) + return SpellCastResult.LineOfSight; + } + + // check pet presence + foreach (SpellEffectInfo effect in GetEffects()) + { + if (effect != null && effect.TargetA.GetTarget() == Targets.UnitPet) + { + if (m_caster.GetGuardianPet() == null) + { + if (m_triggeredByAuraSpell != null) // not report pet not existence for triggered spells + return SpellCastResult.DontReport; + else + return SpellCastResult.NoPet; + } + break; + } + } + + // Spell casted only on Battleground + if (m_spellInfo.HasAttribute(SpellAttr3.Battleground) && m_caster.IsTypeId(TypeId.Player)) + if (!m_caster.ToPlayer().InBattleground()) + return SpellCastResult.OnlyBattlegrounds; + + // do not allow spells to be cast in arenas or rated Battlegrounds + Player player = m_caster.ToPlayer(); + if (player != null) + if (player.InArena()/* || player.InRatedBattleground() NYI*/) + { + castResult = CheckArenaAndRatedBattlegroundCastRules(); + if (castResult != SpellCastResult.SpellCastOk) + return castResult; + } + + // zone check + if (m_caster.IsTypeId(TypeId.Unit) || !m_caster.ToPlayer().IsGameMaster()) + { + uint zone, area; + m_caster.GetZoneAndAreaId(out zone, out area); + + SpellCastResult locRes = m_spellInfo.CheckLocation(m_caster.GetMapId(), zone, area, m_caster.ToPlayer()); + if (locRes != SpellCastResult.SpellCastOk) + return locRes; + } + + // not let players cast spells at mount (and let do it to creatures) + if (m_caster.IsMounted() && m_caster.IsTypeId(TypeId.Player) && !Convert.ToBoolean(_triggeredCastFlags & TriggerCastFlags.IgnoreCasterMountedOrOnVehicle) && + !m_spellInfo.IsPassive() && !m_spellInfo.HasAttribute(SpellAttr0.CastableWhileMounted)) + { + if (m_caster.IsInFlight()) + return SpellCastResult.NotOnTaxi; + else + return SpellCastResult.NotMounted; + } + + // check spell focus object + if (m_spellInfo.RequiresSpellFocus != 0) + { + focusObject = SearchSpellFocus(); + if (!focusObject) + return SpellCastResult.RequiresSpellFocus; + } + + castResult = SpellCastResult.SpellCastOk; + + // always (except passive spells) check items (focus object can be required for any type casts) + if (!m_spellInfo.IsPassive()) + { + castResult = CheckItems(); + if (castResult != SpellCastResult.SpellCastOk) + return castResult; + } + + // Triggered spells also have range check + // @todo determine if there is some flag to enable/disable the check + castResult = CheckRange(strict); + if (castResult != SpellCastResult.SpellCastOk) + return castResult; + + if (!Convert.ToBoolean(_triggeredCastFlags & TriggerCastFlags.IgnorePowerAndReagentCost)) + { + castResult = CheckPower(); + if (castResult != SpellCastResult.SpellCastOk) + return castResult; + } + + if (!Convert.ToBoolean(_triggeredCastFlags & TriggerCastFlags.IgnoreCasterAuras)) + { + castResult = CheckCasterAuras(); + if (castResult != SpellCastResult.SpellCastOk) + return castResult; + } + + // script hook + castResult = CallScriptCheckCastHandlers(); + if (castResult != SpellCastResult.SpellCastOk) + return castResult; + + foreach (SpellEffectInfo effect in GetEffects()) + { + if (effect == null) + continue; + + // for effects of spells that have only one target + switch (effect.Effect) + { + case SpellEffectName.Dummy: + { + if (m_spellInfo.Id == 19938) // Awaken Peon + { + Unit unit = m_targets.GetUnitTarget(); + if (unit == null || !unit.HasAura(17743)) + return SpellCastResult.BadTargets; + } + else if (m_spellInfo.Id == 31789) // Righteous Defense + { + if (!m_caster.IsTypeId(TypeId.Player)) + return SpellCastResult.DontReport; + + Unit target1 = m_targets.GetUnitTarget(); + if (target1 == null || !target1.IsFriendlyTo(m_caster) || target1.getAttackers().Empty()) + return SpellCastResult.BadTargets; + + } + break; + } + case SpellEffectName.LearnSpell: + { + if (!m_caster.IsTypeId(TypeId.Player)) + return SpellCastResult.BadTargets; + + if (effect.TargetA.GetTarget() != Targets.UnitPet) + break; + + Pet pet = m_caster.ToPlayer().GetPet(); + + if (pet == null) + return SpellCastResult.NoPet; + + SpellInfo learn_spellproto = Global.SpellMgr.GetSpellInfo(effect.TriggerSpell); + + if (learn_spellproto == null) + return SpellCastResult.NotKnown; + + if (m_spellInfo.SpellLevel > pet.getLevel()) + return SpellCastResult.Lowlevel; + + break; + } + case SpellEffectName.UnlockGuildVaultTab: + { + if (!m_caster.IsTypeId(TypeId.Player)) + return SpellCastResult.BadTargets; + var guild = m_caster.ToPlayer().GetGuild(); + if (guild != null) + if (guild.GetLeaderGUID() != m_caster.ToPlayer().GetGUID()) + return SpellCastResult.CantDoThatRightNow; + break; + } + case SpellEffectName.LearnPetSpell: + { + // check target only for unit target case + Unit target = m_targets.GetUnitTarget(); + if (target != null) + { + if (!m_caster.IsTypeId(TypeId.Player)) + return SpellCastResult.BadTargets; + + Pet pet = target.ToPet(); + if (pet == null || pet.GetOwner() != m_caster) + return SpellCastResult.BadTargets; + + SpellInfo learn_spellproto = Global.SpellMgr.GetSpellInfo(effect.TriggerSpell); + + if (learn_spellproto == null) + return SpellCastResult.NotKnown; + + if (m_spellInfo.SpellLevel > pet.getLevel()) + return SpellCastResult.Lowlevel; + } + break; + } + case SpellEffectName.ApplyGlyph: + { + if (!m_caster.IsTypeId(TypeId.Player)) + return SpellCastResult.GlyphNoSpec; + + Player caster = m_caster.ToPlayer(); + if (!caster.HasSpell(m_misc.SpellId)) + return SpellCastResult.NotKnown; + + uint glyphId = (uint)effect.MiscValue; + if (glyphId != 0) + { + GlyphPropertiesRecord glyphProperties = CliDB.GlyphPropertiesStorage.LookupByKey(glyphId); + if (glyphProperties == null) + return SpellCastResult.InvalidGlyph; + + List glyphBindableSpells = Global.DB2Mgr.GetGlyphBindableSpells(glyphId); + if (glyphBindableSpells.Empty()) + return SpellCastResult.InvalidGlyph; + + if (!glyphBindableSpells.Contains(m_misc.SpellId)) + return SpellCastResult.InvalidGlyph; + + List glyphRequiredSpecs = Global.DB2Mgr.GetGlyphRequiredSpecs(glyphId); + if (!glyphRequiredSpecs.Empty()) + { + if (caster.GetUInt32Value(PlayerFields.CurrentSpecId) == 0) + return SpellCastResult.GlyphNoSpec; + + if (!glyphRequiredSpecs.Contains(caster.GetUInt32Value(PlayerFields.CurrentSpecId))) + return SpellCastResult.GlyphInvalidSpec; + } + + uint replacedGlyph = 0; + foreach (uint activeGlyphId in caster.GetGlyphs(caster.GetActiveTalentGroup())) + { + List activeGlyphBindableSpells = Global.DB2Mgr.GetGlyphBindableSpells(activeGlyphId); + if (!activeGlyphBindableSpells.Empty()) + { + if (activeGlyphBindableSpells.Contains(m_misc.SpellId)) + { + replacedGlyph = activeGlyphId; + break; + } + } + } + + foreach (uint activeGlyphId in caster.GetGlyphs(caster.GetActiveTalentGroup())) + { + if (activeGlyphId == replacedGlyph) + continue; + + if (activeGlyphId == glyphId) + return SpellCastResult.UniqueGlyph; + + if (CliDB.GlyphPropertiesStorage.LookupByKey(activeGlyphId).GlyphExclusiveCategoryID == glyphProperties.GlyphExclusiveCategoryID) + return SpellCastResult.GlyphExclusiveCategory; + } + } + break; + } + case SpellEffectName.FeedPet: + { + if (!m_caster.IsTypeId(TypeId.Player)) + return SpellCastResult.BadTargets; + + Item foodItem = m_targets.GetItemTarget(); + if (!foodItem) + return SpellCastResult.BadTargets; + + Pet pet = m_caster.ToPlayer().GetPet(); + if (!pet) + return SpellCastResult.NoPet; + + if (!pet.HaveInDiet(foodItem.GetTemplate())) + return SpellCastResult.WrongPetFood; + + if (pet.GetCurrentFoodBenefitLevel(foodItem.GetTemplate().GetBaseItemLevel()) == 0) + return SpellCastResult.FoodLowlevel; + + if (m_caster.IsInCombat() || pet.IsInCombat()) + return SpellCastResult.AffectingCombat; + + break; + } + case SpellEffectName.PowerBurn: + case SpellEffectName.PowerDrain: + { + // Can be area effect, Check only for players and not check if target - caster (spell can have multiply drain/burn effects) + if (m_caster.IsTypeId(TypeId.Player)) + { + Unit target1 = m_targets.GetUnitTarget(); + if (target1 != null) + if (target1 != m_caster && unitTarget.getPowerType() != (PowerType)effect.MiscValue) + return SpellCastResult.BadTargets; + } + break; + } + case SpellEffectName.Charge: + { + if (m_caster.HasUnitState(UnitState.Root)) + return SpellCastResult.Rooted; + + if (GetSpellInfo().NeedsExplicitUnitTarget()) + { + Unit target1 = m_targets.GetUnitTarget(); + if (target1 == null) + return SpellCastResult.DontReport; + + + float objSize = target1.GetObjectSize(); + float range = m_spellInfo.GetMaxRange(true, m_caster, this) * 1.5f + objSize; // can't be overly strict + + m_preGeneratedPath.SetPathLengthLimit(range); + //first try with raycast, if it fails fall back to normal path + float targetObjectSize = Math.Min(target1.GetObjectSize(), 4.0f); + bool result = m_preGeneratedPath.CalculatePath(target1.GetPositionX(), target1.GetPositionY(), target1.GetPositionZ() + targetObjectSize, false, true); + if (m_preGeneratedPath.GetPathType().HasAnyFlag(PathType.Short)) + return SpellCastResult.OutOfRange; + else if (!result || m_preGeneratedPath.GetPathType().HasAnyFlag(PathType.NoPath | PathType.Incomplete)) + { + result = m_preGeneratedPath.CalculatePath(target1.GetPositionX(), target1.GetPositionY(), target1.GetPositionZ() + targetObjectSize, false, false); + if (m_preGeneratedPath.GetPathType().HasAnyFlag(PathType.Short)) + return SpellCastResult.OutOfRange; + else if (!result || m_preGeneratedPath.GetPathType().HasAnyFlag(PathType.NoPath | PathType.Incomplete)) + return SpellCastResult.NoPath; + else if (m_preGeneratedPath.IsInvalidDestinationZ(target1)) // Check position z, if not in a straight line + return SpellCastResult.NoPath; + } + else if (m_preGeneratedPath.IsInvalidDestinationZ(target1)) // Check position z, if in a straight line + return SpellCastResult.NoPath; + + m_preGeneratedPath.ReducePathLenghtByDist(objSize); //move back + } + break; + } + case SpellEffectName.Skinning: + { + if (!m_caster.IsTypeId(TypeId.Player) || m_targets.GetUnitTarget() == null || !m_targets.GetUnitTarget().IsTypeId(TypeId.Unit)) + return SpellCastResult.BadTargets; + + if (!Convert.ToBoolean(m_targets.GetUnitTarget().GetUInt32Value(UnitFields.Flags) & (uint)UnitFlags.Skinnable)) + return SpellCastResult.TargetUnskinnable; + + Creature creature = m_targets.GetUnitTarget().ToCreature(); + if (!creature.IsCritter() && !creature.loot.isLooted()) + return SpellCastResult.TargetNotLooted; + + SkillType skill = creature.GetCreatureTemplate().GetRequiredLootSkill(); + + ushort skillValue = m_caster.ToPlayer().GetSkillValue(skill); + uint TargetLevel = m_targets.GetUnitTarget().getLevel(); + int ReqValue = (int)(skillValue < 100 ? (TargetLevel - 10) * 10 : TargetLevel * 5); + if (ReqValue > skillValue) + return SpellCastResult.LowCastlevel; + + break; + } + case SpellEffectName.OpenLock: + { + if (effect.TargetA.GetTarget() != Targets.GameobjectTarget && + effect.TargetA.GetTarget() != Targets.GameobjectItemTarget) + break; + + if (!m_caster.IsTypeId(TypeId.Player) // only players can open locks, gather etc. + // we need a go target in case of TARGET_GAMEOBJECT_TARGET + || (effect.TargetA.GetTarget() == Targets.GameobjectTarget && m_targets.GetGOTarget() == null)) + return SpellCastResult.BadTargets; + + Item pTempItem = null; + if (Convert.ToBoolean(m_targets.GetTargetMask() & SpellCastTargetFlags.TradeItem)) + { + TradeData pTrade = m_caster.ToPlayer().GetTradeData(); + if (pTrade != null) + pTempItem = pTrade.GetTraderData().GetItem((TradeSlots)m_targets.GetItemTargetGUID().GetLowValue()); + } + else if (Convert.ToBoolean(m_targets.GetTargetMask() & SpellCastTargetFlags.Item)) + pTempItem = m_caster.ToPlayer().GetItemByGuid(m_targets.GetItemTargetGUID()); + + // we need a go target, or an openable item target in case of TARGET_GAMEOBJECT_ITEM_TARGET + if (effect.TargetA.GetTarget() == Targets.GameobjectItemTarget && + m_targets.GetGOTarget() == null && (pTempItem == null || pTempItem.GetTemplate().GetLockID() == 0 || !pTempItem.IsLocked())) + return SpellCastResult.BadTargets; + + if (m_spellInfo.Id != 1842 || (m_targets.GetGOTarget() != null && + m_targets.GetGOTarget().GetGoInfo().type != GameObjectTypes.Trap)) + if (m_caster.ToPlayer().InBattleground() && // In Battlegroundplayers can use only flags and banners + !m_caster.ToPlayer().CanUseBattlegroundObject(m_targets.GetGOTarget())) + return SpellCastResult.TryAgain; + + // get the lock entry + uint lockId = 0; + GameObject go = m_targets.GetGOTarget(); + Item itm = m_targets.GetItemTarget(); + if (go != null) + { + lockId = go.GetGoInfo().GetLockId(); + if (lockId == 0) + return SpellCastResult.BadTargets; + } + else if (itm != null) + lockId = itm.GetTemplate().GetLockID(); + + SkillType skillId = SkillType.None; + int reqSkillValue = 0; + int skillValue = 0; + + // check lock compatibility + SpellCastResult res = CanOpenLock(effect.EffectIndex, lockId, skillId, ref reqSkillValue, ref skillValue); + if (res != SpellCastResult.SpellCastOk) + return res; + break; + } + case SpellEffectName.ResurrectPet: + { + Creature pet = m_caster.GetGuardianPet(); + + if (pet != null && pet.IsAlive()) + return SpellCastResult.AlreadyHaveSummon; + + break; + } + // This is generic summon effect + case SpellEffectName.Summon: + { + var SummonProperties = CliDB.SummonPropertiesStorage.LookupByKey(effect.MiscValueB); + if (SummonProperties == null) + break; + + switch (SummonProperties.Category) + { + case SummonCategory.Pet: + if (!m_spellInfo.HasAttribute(SpellAttr1.DismissPet) && !m_caster.GetPetGUID().IsEmpty()) + return SpellCastResult.AlreadyHaveSummon; + break; + case SummonCategory.Puppet: + if (!m_caster.GetCharmGUID().IsEmpty()) + return SpellCastResult.AlreadyHaveCharm; + break; + } + break; + } + case SpellEffectName.CreateTamedPet: + { + if (m_targets.GetUnitTarget() != null) + { + if (!m_targets.GetUnitTarget().IsTypeId(TypeId.Player)) + return SpellCastResult.BadTargets; + if (!m_spellInfo.HasAttribute(SpellAttr1.DismissPet) && !m_targets.GetUnitTarget().GetPetGUID().IsEmpty()) + return SpellCastResult.AlreadyHaveSummon; + } + break; + } + case SpellEffectName.SummonPet: + { + if (!m_caster.GetPetGUID().IsEmpty()) //let warlock do a replacement summon + { + if (m_caster.IsTypeId(TypeId.Player)) + { + if (strict) //starting cast, trigger pet stun (cast by pet so it doesn't attack player) + { + Pet pet = m_caster.ToPlayer().GetPet(); + if (pet != null) + pet.CastSpell(pet, 32752, true, null, null, pet.GetGUID()); + } + } + else if (!m_spellInfo.HasAttribute(SpellAttr1.DismissPet)) + return SpellCastResult.AlreadyHaveSummon; + } + + if (!m_caster.GetCharmGUID().IsEmpty()) + return SpellCastResult.AlreadyHaveCharm; + break; + } + case SpellEffectName.SummonPlayer: + { + if (!m_caster.IsTypeId(TypeId.Player)) + return SpellCastResult.BadTargets; + if (m_caster.ToPlayer().GetTarget().IsEmpty()) + return SpellCastResult.BadTargets; + + Player target = Global.ObjAccessor.FindPlayer(m_caster.ToPlayer().GetTarget()); + if (target == null || m_caster.ToPlayer() == target || (!target.IsInSameRaidWith(m_caster.ToPlayer()) && m_spellInfo.Id != 48955)) // refer-a-friend spell + return SpellCastResult.BadTargets; + + if (target.HasSummonPending()) + return SpellCastResult.SummonPending; + + // check if our map is dungeon + MapRecord map = CliDB.MapStorage.LookupByKey(m_caster.GetMapId()); + if (map.IsDungeon()) + { + uint mapId = m_caster.GetMap().GetId(); + Difficulty difficulty = m_caster.GetMap().GetDifficultyID(); + if (map.IsRaid()) + { + InstanceBind targetBind = target.GetBoundInstance(mapId, difficulty); + if (targetBind != null) + { + InstanceBind casterBind = m_caster.ToPlayer().GetBoundInstance(mapId, difficulty); + if (casterBind != null) + if (targetBind.perm && targetBind.save != casterBind.save) + return SpellCastResult.TargetLockedToRaidInstance; + } + } + InstanceTemplate instance = Global.ObjectMgr.GetInstanceTemplate(mapId); + if (instance == null) + return SpellCastResult.TargetNotInInstance; + if (!target.Satisfy(Global.ObjectMgr.GetAccessRequirement(mapId, difficulty), mapId)) + return SpellCastResult.BadTargets; + } + break; + } + // RETURN HERE + case SpellEffectName.SummonRafFriend: + { + if (!m_caster.IsTypeId(TypeId.Player)) + return SpellCastResult.BadTargets; + + Player playerCaster = m_caster.ToPlayer(); + // + if (playerCaster.GetTarget().IsEmpty()) + return SpellCastResult.BadTargets; + + Player target1 = Global.ObjAccessor.FindPlayer(playerCaster.GetTarget()); + + if (target1 == null || + !(target1.GetSession().GetRecruiterId() == playerCaster.GetSession().GetAccountId() || target1.GetSession().GetAccountId() == playerCaster.GetSession().GetRecruiterId())) + return SpellCastResult.BadTargets; + + break; + } + case SpellEffectName.Leap: + case SpellEffectName.TeleportUnitsFaceCaster: + { + //Do not allow to cast it before BG starts. + if (m_caster.IsTypeId(TypeId.Player)) + { + Battleground bg = m_caster.ToPlayer().GetBattleground(); + if (bg) + if (bg.GetStatus() != BattlegroundStatus.InProgress) + return SpellCastResult.TryAgain; + } + break; + } + case SpellEffectName.StealBeneficialBuff: + { + if (m_targets.GetUnitTarget() == m_caster) + return SpellCastResult.BadTargets; + break; + } + case SpellEffectName.LeapBack: + { + if (m_caster.HasUnitState(UnitState.Root)) + { + if (m_caster.IsTypeId(TypeId.Player)) + return SpellCastResult.Rooted; + else + return SpellCastResult.DontReport; + } + break; + } + case SpellEffectName.TalentSpecSelect: + { + ChrSpecializationRecord spec = CliDB.ChrSpecializationStorage.LookupByKey(m_misc.SpecializationId); + Player playerCaster = m_caster.ToPlayer(); + if (!playerCaster) + return SpellCastResult.TargetNotPlayer; + + if (spec == null || (spec.ClassID != (uint)m_caster.GetClass() && !spec.IsPetSpecialization())) + return SpellCastResult.NoSpec; + + if (spec.IsPetSpecialization()) + { + Pet pet = player.GetPet(); + if (!pet || pet.getPetType() != PetType.Hunter || pet.GetCharmInfo() == null) + return SpellCastResult.NoPet; + } + + // can't change during already started arena/Battleground + Battleground bg = player.GetBattleground(); + if (bg) + if (bg.GetStatus() == BattlegroundStatus.InProgress) + return SpellCastResult.NotInBattleground; + break; + } + case SpellEffectName.RemoveTalent: + { + if (!m_caster.IsTypeId(TypeId.Player)) + return SpellCastResult.BadTargets; + + TalentRecord talent = CliDB.TalentStorage.LookupByKey(m_misc.TalentId); + if (talent == null) + return SpellCastResult.DontReport; + if (m_caster.GetSpellHistory().HasCooldown(talent.SpellID)) + return SpellCastResult.CantUntalent; + break; + } + case SpellEffectName.GiveArtifactPower: + case SpellEffectName.GiveArtifactPowerNoBonus: + { + if (!m_caster.IsTypeId(TypeId.Player)) + return SpellCastResult.BadTargets; + + Aura artifactAura = m_caster.GetAura(PlayerConst.ArtifactsAllWeaponsGeneralWeaponEquippedPassive); + if (artifactAura == null) + return SpellCastResult.NoArtifactEquipped; + Item artifact = m_caster.ToPlayer().GetItemByGuid(artifactAura.GetCastItemGUID()); + if (artifact == null) + return SpellCastResult.NoArtifactEquipped; + if (effect.Effect == SpellEffectName.GiveArtifactPower) + { + ArtifactRecord artifactEntry = CliDB.ArtifactStorage.LookupByKey(artifact.GetTemplate().GetArtifactID()); + if (artifactEntry == null || artifactEntry.ArtifactCategoryID != effect.MiscValue) + return SpellCastResult.WrongArtifactEquipped; + } + break; + } + default: + break; + } + } + + foreach (SpellEffectInfo effect in GetEffects()) + { + if (effect == null) + continue; + + switch (effect.ApplyAuraName) + { + case AuraType.ModPossessPet: + { + if (!m_caster.IsTypeId(TypeId.Player)) + return SpellCastResult.NoPet; + + Pet pet = m_caster.ToPlayer().GetPet(); + if (pet == null) + return SpellCastResult.NoPet; + + if (!pet.GetCharmerGUID().IsEmpty()) + return SpellCastResult.Charmed; + break; + } + case AuraType.ModPossess: + case AuraType.ModCharm: + case AuraType.AoeCharm: + { + if (!m_caster.GetCharmerGUID().IsEmpty()) + return SpellCastResult.Charmed; + + if (effect.ApplyAuraName == AuraType.ModCharm + || effect.ApplyAuraName == AuraType.ModPossess) + { + if (!m_spellInfo.HasAttribute(SpellAttr1.DismissPet) && !m_caster.GetPetGUID().IsEmpty()) + return SpellCastResult.AlreadyHaveSummon; + + if (!m_caster.GetCharmGUID().IsEmpty()) + return SpellCastResult.AlreadyHaveCharm; + } + + Unit target1 = m_targets.GetUnitTarget(); + if (target1 != null) + { + if (target1.IsTypeId(TypeId.Unit) && target1.ToCreature().IsVehicle()) + return SpellCastResult.BadImplicitTargets; + + if (target1.IsMounted()) + return SpellCastResult.CantBeCharmed; + + if (!target1.GetCharmerGUID().IsEmpty()) + return SpellCastResult.Charmed; + + if (target1.GetOwner() != null && target1.GetOwner().IsTypeId(TypeId.Player)) + return SpellCastResult.TargetIsPlayerControlled; + + int damage = CalculateDamage(effect.EffectIndex, target1); + if (damage != 0 && target1.getLevel() > damage) + return SpellCastResult.Highlevel; + } + + break; + } + case AuraType.Mounted: + { + if (m_caster.IsInWater() && m_spellInfo.HasAura(m_caster.GetMap().GetDifficultyID(), AuraType.ModIncreaseMountedFlightSpeed)) + return SpellCastResult.OnlyAbovewater; + + // Ignore map check if spell have AreaId. AreaId already checked and this prevent special mount spells + bool allowMount = !m_caster.GetMap().IsDungeon() || m_caster.GetMap().IsBattlegroundOrArena(); + InstanceTemplate it = Global.ObjectMgr.GetInstanceTemplate(m_caster.GetMapId()); + if (it != null) + allowMount = it.AllowMount; + if (m_caster.IsTypeId(TypeId.Player) && !allowMount && m_spellInfo.RequiredAreasID == 0) + return SpellCastResult.NoMountsAllowed; + + if (m_caster.IsInDisallowedMountForm()) + return SpellCastResult.NotShapeshift; + + break; + } + case AuraType.RangedAttackPowerAttackerBonus: + { + if (m_targets.GetUnitTarget() == null) + return SpellCastResult.BadImplicitTargets; + + // can be casted at non-friendly unit or own pet/charm + if (m_caster.IsFriendlyTo(m_targets.GetUnitTarget())) + return SpellCastResult.TargetFriendly; + + break; + } + case AuraType.Fly: + case AuraType.ModIncreaseFlightSpeed: + { + // not allow cast fly spells if not have req. skills (all spells is self target) + // allow always ghost flight spells + if (m_originalCaster != null && m_originalCaster.IsTypeId(TypeId.Player) && m_originalCaster.IsAlive()) + { + BattleField Bf = Global.BattleFieldMgr.GetBattlefieldToZoneId(m_originalCaster.GetZoneId()); + var area = CliDB.AreaTableStorage.LookupByKey(m_originalCaster.GetAreaId()); + if (area != null) + if (area.Flags[0].HasAnyFlag(AreaFlags.NoFlyZone) || (Bf != null && !Bf.CanFlyIn())) + return SpellCastResult.NotHere; + } + break; + } + case AuraType.PeriodicManaLeech: + { + if (effect.IsTargetingArea()) + break; + + if (m_targets.GetUnitTarget() == null) + return SpellCastResult.BadImplicitTargets; + + if (!m_caster.IsTypeId(TypeId.Player) || m_CastItem != null) + break; + + if (m_targets.GetUnitTarget().getPowerType() != PowerType.Mana) + return SpellCastResult.BadTargets; + + break; + } + default: + break; + } + } + + // check trade slot case (last, for allow catch any another cast problems) + if (Convert.ToBoolean(m_targets.GetTargetMask() & SpellCastTargetFlags.TradeItem)) + { + if (m_CastItem != null) + return SpellCastResult.ItemEnchantTradeWindow; + + if (!m_caster.IsTypeId(TypeId.Player)) + return SpellCastResult.NotTrading; + + TradeData my_trade = m_caster.ToPlayer().GetTradeData(); + + if (my_trade == null) + return SpellCastResult.NotTrading; + + TradeSlots slot = (TradeSlots)m_targets.GetItemTargetGUID().GetLowValue(); + if (slot != TradeSlots.NonTraded) + return SpellCastResult.BadTargets; + + if (!IsTriggered()) + if (my_trade.GetSpell() != 0) + return SpellCastResult.ItemAlreadyEnchanted; + } + + // check if caster has at least 1 combo point for spells that require combo points + if (m_needComboPoints) + { + Player plrCaster = m_caster.ToPlayer(); + if (plrCaster != null) + if (plrCaster.GetComboPoints() == 0) + return SpellCastResult.NoComboPoints; + } + + // all ok + return SpellCastResult.SpellCastOk; + } + + public SpellCastResult CheckPetCast(Unit target) + { + if (m_caster.HasUnitState(UnitState.Casting) && !Convert.ToBoolean(_triggeredCastFlags & TriggerCastFlags.IgnoreCastInProgress)) //prevent spellcast interruption by another spellcast + return SpellCastResult.SpellInProgress; + + // dead owner (pets still alive when owners ressed?) + Unit owner = m_caster.GetCharmerOrOwner(); + if (owner != null) + if (!owner.IsAlive()) + return SpellCastResult.CasterDead; + + if (target == null && m_targets.GetUnitTarget() != null) + target = m_targets.GetUnitTarget(); + + if (m_spellInfo.NeedsExplicitUnitTarget()) + { + if (target == null) + return SpellCastResult.BadImplicitTargets; + m_targets.SetUnitTarget(target); + } + + // cooldown + Creature creatureCaster = m_caster.ToCreature(); + if (creatureCaster) + if (creatureCaster.GetSpellHistory().HasCooldown(m_spellInfo.Id)) + return SpellCastResult.NotReady; + + // Check if spell is affected by GCD + if (m_spellInfo.StartRecoveryCategory > 0) + if (m_caster.GetCharmInfo() != null && m_caster.GetSpellHistory().HasGlobalCooldown(m_spellInfo)) + return SpellCastResult.NotReady; + + return CheckCast(true); + } + + SpellCastResult CheckCasterAuras() + { + // spells totally immuned to caster auras (wsg flag drop, give marks etc) + if (m_spellInfo.HasAttribute(SpellAttr6.IgnoreCasterAuras)) + return SpellCastResult.SpellCastOk; + + int school_immune = 0; + uint mechanic_immune = 0; + uint dispel_immune = 0; + + // Check if the spell grants school or mechanic immunity. + // We use bitmasks so the loop is done only once and not on every aura check below. + if (m_spellInfo.HasAttribute(SpellAttr1.DispelAurasOnImmunity)) + { + foreach (SpellEffectInfo effect in GetEffects()) + { + if (effect == null) + continue; + + if (effect.ApplyAuraName == AuraType.SchoolImmunity) + school_immune |= effect.MiscValue; + else if (effect.ApplyAuraName == AuraType.MechanicImmunity) + mechanic_immune |= (uint)(1 << effect.MiscValue); + else if (effect.ApplyAuraName == AuraType.DispelImmunity) + dispel_immune |= SpellInfo.GetDispelMask((DispelType)effect.MiscValue); + } + // immune movement impairment and loss of control + if (m_spellInfo.Id == 42292 || m_spellInfo.Id == 59752 || m_spellInfo.Id == 19574 || m_spellInfo.Id == 53490) + mechanic_immune = (uint)Mechanics.ImmuneToMovementImpairmentAndLossControlMask; + } + + bool usableInStun = m_spellInfo.HasAttribute(SpellAttr5.UsableWhileStunned); + + // Glyph of Pain Suppression + // Allow Pain Suppression and Guardian Spirit to be cast while stunned + // there is no other way to handle it + if ((m_spellInfo.Id == 33206 || m_spellInfo.Id == 47788) && !m_caster.HasAura(63248)) + usableInStun = false; + + // Check whether the cast should be prevented by any state you might have. + SpellCastResult prevented_reason = SpellCastResult.SpellCastOk; + // Have to check if there is a stun aura. Otherwise will have problems with ghost aura apply while logging out + UnitFlags unitflag = (UnitFlags)m_caster.GetUInt32Value(UnitFields.Flags); // Get unit state + if (Convert.ToBoolean(unitflag & UnitFlags.Stunned)) + { + // spell is usable while stunned, check if caster has allowed stun auras, another stun types must prevent cast spell + if (usableInStun) + { + uint allowedStunMask = + 1 << (int)Mechanics.Stun + | 1 << (int)Mechanics.Freeze + | 1 << (int)Mechanics.Sapped + | 1 << (int)Mechanics.Sleep; + + bool foundNotStun = false; + var stunAuras = m_caster.GetAuraEffectsByType(AuraType.ModStun); + foreach (var auraEffect in stunAuras) + { + uint mechanicMask = auraEffect.GetSpellInfo().GetAllEffectsMechanicMask(); + if (mechanicMask != 0 && !Convert.ToBoolean(mechanicMask & allowedStunMask)) + { + foundNotStun = true; + break; + } + } + if (foundNotStun) + prevented_reason = SpellCastResult.Stunned; + } + else + prevented_reason = SpellCastResult.Stunned; + } + else if (unitflag.HasAnyFlag(UnitFlags.Confused) && !m_spellInfo.HasAttribute(SpellAttr5.UsableWhileConfused)) + prevented_reason = SpellCastResult.Confused; + else if (unitflag.HasAnyFlag(UnitFlags.Fleeing) && !m_spellInfo.HasAttribute(SpellAttr5.UsableWhileFeared)) + prevented_reason = SpellCastResult.Fleeing; + else if (unitflag.HasAnyFlag(UnitFlags.Silenced) && m_spellInfo.PreventionType.HasAnyFlag(SpellPreventionType.Silence)) + prevented_reason = SpellCastResult.Silenced; + else if (unitflag.HasAnyFlag(UnitFlags.Pacified) && m_spellInfo.PreventionType.HasAnyFlag(SpellPreventionType.Pacify)) + prevented_reason = SpellCastResult.Pacified; + else if (m_caster.HasFlag(UnitFields.Flags2, UnitFlags2.NoActions) && m_spellInfo.PreventionType.HasAnyFlag(SpellPreventionType.NoActions)) + prevented_reason = SpellCastResult.NoActions; + + // Attr must make flag drop spell totally immune from all effects + if (prevented_reason != SpellCastResult.SpellCastOk) + { + if (school_immune != 0 || mechanic_immune != 0 || dispel_immune != 0) + { + //Checking auras is needed now, because you are prevented by some state but the spell grants immunity. + foreach (var pair in m_caster.GetAppliedAuras()) + { + Aura aura = pair.Value.GetBase(); + SpellInfo auraInfo = aura.GetSpellInfo(); + if (Convert.ToBoolean(auraInfo.GetAllEffectsMechanicMask() & mechanic_immune)) + continue; + if (Convert.ToBoolean((int)auraInfo.GetSchoolMask() & school_immune) && !auraInfo.HasAttribute(SpellAttr1.UnaffectedBySchoolImmune)) + continue; + if (Convert.ToBoolean((int)auraInfo.GetDispelMask() & dispel_immune)) + continue; + + //Make a second check for spell failed so the right SPELL_FAILED message is returned. + //That is needed when your casting is prevented by multiple states and you are only immune to some of them. + foreach (SpellEffectInfo effect in GetEffects()) + { + if (effect == null) + continue; + + AuraEffect part = aura.GetEffect(effect.EffectIndex); + if (part != null) + { + switch (part.GetAuraType()) + { + case AuraType.ModStun: + if (!usableInStun || !Convert.ToBoolean(auraInfo.GetAllEffectsMechanicMask() & (1 << (int)Mechanics.Stun))) + return SpellCastResult.Stunned; + break; + case AuraType.ModConfuse: + if (!m_spellInfo.HasAttribute(SpellAttr5.UsableWhileConfused)) + return SpellCastResult.Confused; + break; + case AuraType.ModFear: + if (!m_spellInfo.HasAttribute(SpellAttr5.UsableWhileFeared)) + return SpellCastResult.Fleeing; + break; + case AuraType.ModSilence: + case AuraType.ModPacify: + case AuraType.ModPacifySilence: + if (m_spellInfo.PreventionType.HasAnyFlag(SpellPreventionType.Pacify)) + return SpellCastResult.Pacified; + else if (m_spellInfo.PreventionType.HasAnyFlag(SpellPreventionType.Silence)) + return SpellCastResult.Silenced; + break; + default: + break; + } + } + + } + } + } + // You are prevented from casting and the spell casted does not grant immunity. Return a failed error. + else + return prevented_reason; + } + return SpellCastResult.SpellCastOk; + } + + SpellCastResult CheckArenaAndRatedBattlegroundCastRules() + { + bool isRatedBattleground = false; // NYI + bool isArena = !isRatedBattleground; + + // check USABLE attributes + // USABLE takes precedence over NOT_USABLE + if (isRatedBattleground && m_spellInfo.HasAttribute(SpellAttr9.UsableInRatedBattlegrounds)) + return SpellCastResult.SpellCastOk; + + if (isArena && m_spellInfo.HasAttribute(SpellAttr4.UsableInArena)) + return SpellCastResult.SpellCastOk; + + // check NOT_USABLE attributes + if (m_spellInfo.HasAttribute(SpellAttr4.NotUsableInArenaOrRatedBg)) + return isArena ? SpellCastResult.NotInArena : SpellCastResult.NotInBattleground; + + if (isArena && m_spellInfo.HasAttribute(SpellAttr9.NotUsableInArena)) + return SpellCastResult.NotInArena; + + // check cooldowns + uint spellCooldown = m_spellInfo.GetRecoveryTime(); + if (isArena && spellCooldown > 10 * Time.Minute * Time.InMilliseconds) // not sure if still needed + return SpellCastResult.NotInArena; + + if (isRatedBattleground && spellCooldown > 15 * Time.Minute * Time.InMilliseconds) + return SpellCastResult.NotInBattleground; + + return SpellCastResult.SpellCastOk; + } + + public bool CanAutoCast(Unit target) + { + if (!target) + return (CheckPetCast(target) == SpellCastResult.SpellCastOk); + + ObjectGuid targetguid = target.GetGUID(); + + // check if target already has the same or a more powerful aura + foreach (SpellEffectInfo effect in GetEffects()) + { + if (effect == null) + continue; + + if (!effect.IsAura()) + continue; + + AuraType auraType = effect.ApplyAuraName; + var auras = target.GetAuraEffectsByType(auraType); + foreach (var eff in auras) + { + if (GetSpellInfo().Id == eff.GetSpellInfo().Id) + return false; + + switch (Global.SpellMgr.CheckSpellGroupStackRules(GetSpellInfo(), eff.GetSpellInfo())) + { + case SpellGroupStackRule.Exclusive: + return false; + case SpellGroupStackRule.ExclusiveFromSameCaster: + if (GetCaster() == eff.GetCaster()) + return false; + break; + case SpellGroupStackRule.ExclusiveSameEffect: // this one has further checks, but i don't think they're necessary for autocast logic + case SpellGroupStackRule.ExclusiveHighest: + if (Math.Abs(effect.BasePoints) <= Math.Abs(eff.GetAmount())) + return false; + break; + case SpellGroupStackRule.Default: + default: + break; + } + } + } + + SpellCastResult result = CheckPetCast(target); + + if (result == SpellCastResult.SpellCastOk || result == SpellCastResult.UnitNotInfront) + { + // do not check targets for ground-targeted spells (we target them on top of the intended target anyway) + if (GetSpellInfo().ExplicitTargetMask.HasAnyFlag((uint)SpellCastTargetFlags.DestLocation)) + return true; + SelectSpellTargets(); + //check if among target units, our WANTED target is as well (.only self cast spells return false) + foreach (var ihit in m_UniqueTargetInfo) + if (ihit.targetGUID == targetguid) + return true; + } + // either the cast failed or the intended target wouldn't be hit + return false; + } + + SpellCastResult CheckRange(bool strict) + { + // Don't check for instant cast spells + if (!strict && m_casttime == 0) + return SpellCastResult.SpellCastOk; + + var pair = GetMinMaxRange(strict); + float minRange = pair.Item1; + float maxRange = pair.Item2; + + // get square values for sqr distance checks + minRange *= minRange; + maxRange *= maxRange; + + Unit target = m_targets.GetUnitTarget(); + if (target && target != m_caster) + { + if (m_caster.GetExactDistSq(target) > maxRange) + return SpellCastResult.OutOfRange; + + if (minRange > 0.0f && m_caster.GetExactDistSq(target) < minRange) + return SpellCastResult.OutOfRange; + + if (m_caster.IsTypeId(TypeId.Player) && + ((m_spellInfo.FacingCasterFlags.HasAnyFlag(1u) && !m_caster.HasInArc((float)Math.PI, target)) + && !m_caster.IsWithinBoundaryRadius(target))) + return SpellCastResult.UnitNotInfront; + } + + if (m_targets.HasDst() && !m_targets.HasTraj()) + { + if (m_caster.GetExactDistSq(m_targets.GetDstPos()) > maxRange) + return SpellCastResult.OutOfRange; + if (minRange > 0.0f && m_caster.GetExactDistSq(m_targets.GetDstPos()) < minRange) + return SpellCastResult.OutOfRange; + } + + return SpellCastResult.SpellCastOk; + } + + Tuple GetMinMaxRange(bool strict) + { + float rangeMod = 0.0f; + float minRange = 0.0f; + float maxRange = 0.0f; + + if (strict && IsNextMeleeSwingSpell()) + { + maxRange = 100.0f; + return Tuple.Create(minRange, maxRange); + } + + if (m_spellInfo.RangeEntry != null) + { + Unit target = m_targets.GetUnitTarget(); + if (m_spellInfo.RangeEntry.Flags.HasAnyFlag(SpellRangeFlag.Melee)) + { + rangeMod = m_caster.GetMeleeRange(target ? target : m_caster); // when the target is not a unit, take the caster's combat reach as the target's combat reach. + } + else + { + float meleeRange = 0.0f; + if (m_spellInfo.RangeEntry.Flags.HasAnyFlag(SpellRangeFlag.Ranged)) + meleeRange = m_caster.GetMeleeRange(target ? target : m_caster); // when the target is not a unit, take the caster's combat reach as the target's combat reach. + + minRange = m_caster.GetSpellMinRangeForTarget(target, m_spellInfo) + meleeRange; + maxRange = m_caster.GetSpellMaxRangeForTarget(target, m_spellInfo); + + if (target || m_targets.GetCorpseTarget()) + { + rangeMod = m_caster.GetCombatReach() + (target ? target.GetCombatReach() : m_caster.GetCombatReach()); + + if (minRange > 0.0f && !m_spellInfo.RangeEntry.Flags.HasAnyFlag(SpellRangeFlag.Ranged)) + minRange += rangeMod; + } + } + + if (target && m_caster.isMoving() && target.isMoving() && !m_caster.IsWalking() && !target.IsWalking() && + (m_spellInfo.RangeEntry.Flags.HasAnyFlag(SpellRangeFlag.Melee) || target.IsTypeId(TypeId.Player))) + rangeMod += 8.0f / 3.0f; + } + + if (m_spellInfo.HasAttribute(SpellAttr0.ReqAmmo) && m_caster.IsTypeId(TypeId.Player)) + { + Item ranged = m_caster.ToPlayer().GetWeaponForAttack(WeaponAttackType.RangedAttack, true); + if (ranged) + maxRange *= ranged.GetTemplate().GetRangedModRange() * 0.01f; + } + + Player modOwner = m_caster.GetSpellModOwner(); + if (modOwner) + modOwner.ApplySpellMod(m_spellInfo.Id, SpellModOp.Range, ref maxRange, this); + + maxRange += rangeMod; + + return Tuple.Create(minRange, maxRange); + } + + SpellCastResult CheckPower() + { + // item cast not used power + if (m_CastItem != null) + return SpellCastResult.SpellCastOk; + + foreach (SpellPowerCost cost in m_powerCost) + { + // health as power used - need check health amount + if (cost.Power == PowerType.Health) + { + if (m_caster.GetHealth() <= (uint)cost.Amount) + return SpellCastResult.CasterAurastate; + continue; + } + // Check valid power type + if (cost.Power >= PowerType.Max) + { + Log.outError(LogFilter.Spells, "Spell.CheckPower: Unknown power type '{0}'", cost.Power); + return SpellCastResult.Unknown; + } + + //check rune cost only if a spell has PowerType == POWER_RUNES + if (cost.Power == PowerType.Runes) + { + SpellCastResult failReason = CheckRuneCost(); + if (failReason != SpellCastResult.SpellCastOk) + return failReason; + } + + // Check power amount + if (m_caster.GetPower(cost.Power) < cost.Amount) + return SpellCastResult.NoPower; + } + + return SpellCastResult.SpellCastOk; + } + + SpellCastResult CheckItems() + { + Player player = m_caster.ToPlayer(); + + if (!player) + return SpellCastResult.SpellCastOk; + + if (m_CastItem == null) + { + if (!m_castItemGUID.IsEmpty()) + return SpellCastResult.ItemNotReady; + } + else + { + uint itemid = m_CastItem.GetEntry(); + if (!player.HasItemCount(itemid)) + return SpellCastResult.ItemNotReady; + + ItemTemplate proto = m_CastItem.GetTemplate(); + if (proto == null) + return SpellCastResult.ItemNotReady; + + for (int i = 0; i < proto.Effects.Count && i < 5; ++i) + if (proto.Effects[i].Charges > 0) + if (m_CastItem.GetSpellCharges(i) == 0) + return SpellCastResult.NoChargesRemain; + + // consumable cast item checks + if (proto.GetClass() == ItemClass.Consumable && m_targets.GetUnitTarget() != null) + { + // such items should only fail if there is no suitable effect at all - see Rejuvenation Potions for example + SpellCastResult failReason = SpellCastResult.SpellCastOk; + foreach (SpellEffectInfo effect in GetEffects()) + { + // skip check, pet not required like checks, and for TARGET_UNIT_PET m_targets.GetUnitTarget() is not the real target but the caster + if (effect == null || effect.TargetA.GetTarget() == Targets.UnitPet) + continue; + + if (effect.Effect == SpellEffectName.Heal) + { + if (m_targets.GetUnitTarget().IsFullHealth()) + { + failReason = SpellCastResult.AlreadyAtFullHealth; + continue; + } + else + { + failReason = SpellCastResult.SpellCastOk; + break; + } + } + + // Mana Potion, Rage Potion, Thistle Tea(Rogue), ... + if (effect.Effect == SpellEffectName.Energize) + { + if (effect.MiscValue < 0 || effect.MiscValue >= (int)PowerType.Max) + { + failReason = SpellCastResult.AlreadyAtFullPower; + continue; + } + + PowerType power = (PowerType)effect.MiscValue; + if (m_targets.GetUnitTarget().GetPower(power) == m_targets.GetUnitTarget().GetMaxPower(power)) + { + failReason = SpellCastResult.AlreadyAtFullPower; + continue; + } + else + { + failReason = SpellCastResult.SpellCastOk; + break; + } + } + } + if (failReason != SpellCastResult.SpellCastOk) + return failReason; + } + } + + // check target item + if (!m_targets.GetItemTargetGUID().IsEmpty()) + { + if (!m_caster.IsTypeId(TypeId.Player)) + return SpellCastResult.BadTargets; + + if (m_targets.GetItemTarget() == null) + return SpellCastResult.ItemGone; + + if (!m_targets.GetItemTarget().IsFitToSpellRequirements(m_spellInfo)) + return SpellCastResult.EquippedItemClass; + } + // if not item target then required item must be equipped + else + { + if (!Convert.ToBoolean(_triggeredCastFlags & TriggerCastFlags.IgnoreEquippedItemRequirement)) + if (m_caster.IsTypeId(TypeId.Player) && !m_caster.ToPlayer().HasItemFitToSpellRequirements(m_spellInfo)) + return SpellCastResult.EquippedItemClass; + } + + // do not take reagents for these item casts + if (!(m_CastItem != null && Convert.ToBoolean(m_CastItem.GetTemplate().GetFlags() & ItemFlags.NoReagentCost))) + { + bool checkReagents = !Convert.ToBoolean(_triggeredCastFlags & TriggerCastFlags.IgnorePowerAndReagentCost) && !player.CanNoReagentCast(m_spellInfo); + // Not own traded item (in trader trade slot) requires reagents even if triggered spell + if (!checkReagents) + { + Item targetItem = m_targets.GetItemTarget(); + if (targetItem != null) + if (targetItem.GetOwnerGUID() != m_caster.GetGUID()) + checkReagents = true; + } + + // check reagents (ignore triggered spells with reagents processed by original spell) and special reagent ignore case. + if (checkReagents) + { + for (byte i = 0; i < SpellConst.MaxReagents; i++) + { + if (m_spellInfo.Reagent[i] <= 0) + continue; + + uint itemid = (uint)m_spellInfo.Reagent[i]; + uint itemcount = m_spellInfo.ReagentCount[i]; + + // if CastItem is also spell reagent + if (m_CastItem != null && m_CastItem.GetEntry() == itemid) + { + ItemTemplate proto = m_CastItem.GetTemplate(); + if (proto == null) + return SpellCastResult.ItemNotReady; + for (int s = 0; s < proto.Effects.Count && s < 5; ++s) + { + // CastItem will be used up and does not count as reagent + int charges = m_CastItem.GetSpellCharges(s); + if (proto.Effects[s].Charges < 0 && Math.Abs(charges) < 2) + { + ++itemcount; + break; + } + } + } + if (!player.HasItemCount(itemid, itemcount)) + return SpellCastResult.Reagents; + } + } + + // check totem-item requirements (items presence in inventory) + uint totems = 2; + for (int i = 0; i < 2; ++i) + { + if (m_spellInfo.Totem[i] != 0) + { + if (player.HasItemCount(m_spellInfo.Totem[i])) + { + totems -= 1; + continue; + } + } else + totems -= 1; + } + if (totems != 0) + return SpellCastResult.Totems; + + // Check items for TotemCategory (items presence in inventory) + uint totemCategory = 2; + for (byte i = 0; i < 2; ++i) + { + if (m_spellInfo.TotemCategory[i] != 0) + { + if (player.HasItemTotemCategory(m_spellInfo.TotemCategory[i])) + { + totemCategory -= 1; + continue; + } + } + else + totemCategory -= 1; + } + + if (totemCategory != 0) + return SpellCastResult.TotemCategory; + } + + // special checks for spell effects + foreach (SpellEffectInfo effect in GetEffects()) + { + if (effect == null) + continue; + + switch (effect.Effect) + { + case SpellEffectName.CreateItem: + case SpellEffectName.CreateLoot: + { + if (!IsTriggered() && effect.ItemType != 0) + { + List dest = new List(); + InventoryResult msg = player.CanStoreNewItem(ItemConst.NullBag, ItemConst.NullSlot, dest, effect.ItemType, 1); + if (msg != InventoryResult.Ok) + { + ItemTemplate pProto = Global.ObjectMgr.GetItemTemplate(effect.ItemType); + // @todo Needs review + if (pProto != null && pProto.GetItemLimitCategory() == 0) + { + player.SendEquipError(msg, null, null, effect.ItemType); + return SpellCastResult.DontReport; + } + else + { + SpellEffectInfo efi; + if (!(m_spellInfo.SpellFamilyName == SpellFamilyNames.Mage && m_spellInfo.SpellFamilyFlags[0].HasAnyFlag(0x40000000u))) + return SpellCastResult.TooManyOfItem; + else if (!player.HasItemCount(effect.ItemType)) + return SpellCastResult.TooManyOfItem; + else if ((efi = GetEffect(1)) != null) + player.CastSpell(m_caster, (uint)efi.CalcValue(), false); // move this to anywhere + return SpellCastResult.DontReport; + } + } + } + break; + } + case SpellEffectName.EnchantItem: + if (effect.ItemType != 0 && m_targets.GetItemTarget() != null && m_targets.GetItemTarget().IsVellum()) + { + // cannot enchant vellum for other player + if (m_targets.GetItemTarget().GetOwner() != m_caster) + return SpellCastResult.NotTradeable; + // do not allow to enchant vellum from scroll made by vellum-prevent exploit + if (m_CastItem != null && Convert.ToBoolean(m_CastItem.GetTemplate().GetFlags() & ItemFlags.NoReagentCost)) + return SpellCastResult.TotemCategory; + List dest = new List(); + InventoryResult msg = player.CanStoreNewItem(ItemConst.NullBag, ItemConst.NullSlot, dest, effect.ItemType, 1); + if (msg != InventoryResult.Ok) + { + player.SendEquipError(msg, null, null, effect.ItemType); + return SpellCastResult.DontReport; + } + } + goto case SpellEffectName.EnchantItemPrismatic; + case SpellEffectName.EnchantItemPrismatic: + { + Item targetItem = m_targets.GetItemTarget(); + if (targetItem == null) + return SpellCastResult.ItemNotFound; + + if (targetItem.GetTemplate().GetBaseItemLevel() < m_spellInfo.BaseLevel) + return SpellCastResult.Lowlevel; + + bool isItemUsable = false; + ItemTemplate proto = targetItem.GetTemplate(); + for (byte e = 0; e < proto.Effects.Count; ++e) + { + if (proto.Effects[e].SpellID != 0 && proto.Effects[e].Trigger == ItemSpelltriggerType.OnUse) + { + isItemUsable = true; + break; + } + } + + var enchantEntry = CliDB.SpellItemEnchantmentStorage.LookupByKey(effect.MiscValue); + // do not allow adding usable enchantments to items that have use effect already + if (enchantEntry != null) + { + for (var s = 0; s < ItemConst.MaxItemEnchantmentEffects; ++s) + { + switch (enchantEntry.Effect[s]) + { + case ItemEnchantmentType.UseSpell: + if (isItemUsable) + return SpellCastResult.OnUseEnchant; + break; + case ItemEnchantmentType.PrismaticSocket: + { + uint numSockets = 0; + for (uint socket = 0; socket < ItemConst.MaxGemSockets; ++socket) + if (targetItem.GetSocketColor(socket) != 0) + ++numSockets; + + if (numSockets == ItemConst.MaxGemSockets || targetItem.GetEnchantmentId(EnchantmentSlot.Prismatic) != 0) + return SpellCastResult.MaxSockets; + break; + } + } + } + } + + // Not allow enchant in trade slot for some enchant type + if (targetItem.GetOwner() != m_caster) + { + if (enchantEntry == null) + return SpellCastResult.Error; + if (enchantEntry.Flags.HasAnyFlag(EnchantmentSlotMask.CanSouldBound)) + return SpellCastResult.NotTradeable; + } + break; + } + case SpellEffectName.EnchantItemTemporary: + { + Item item = m_targets.GetItemTarget(); + if (item == null) + return SpellCastResult.ItemNotFound; + // Not allow enchant in trade slot for some enchant type + if (item.GetOwner() != m_caster) + { + int enchant_id = effect.MiscValue; + var pEnchant = CliDB.SpellItemEnchantmentStorage.LookupByKey(enchant_id); + if (pEnchant == null) + return SpellCastResult.Error; + if (pEnchant.Flags.HasAnyFlag(EnchantmentSlotMask.CanSouldBound)) + return SpellCastResult.NotTradeable; + } + break; + } + case SpellEffectName.EnchantHeldItem: + // check item existence in effect code (not output errors at offhand hold item effect to main hand for example + break; + case SpellEffectName.Disenchant: + { + if (m_targets.GetItemTarget() == null) + return SpellCastResult.CantBeDisenchanted; + + // prevent disenchanting in trade slot + if (m_targets.GetItemTarget().GetOwnerGUID() != m_caster.GetGUID()) + return SpellCastResult.CantBeDisenchanted; + + ItemTemplate itemProto = m_targets.GetItemTarget().GetTemplate(); + if (itemProto == null) + return SpellCastResult.CantBeDisenchanted; + + int item_quality = (int)itemProto.GetQuality(); + // 2.0.x addon: Check player enchanting level against the item disenchanting requirements + uint item_disenchantskilllevel = itemProto.RequiredDisenchantSkill; + if (item_disenchantskilllevel == Convert.ToUInt32(-1)) + return SpellCastResult.CantBeDisenchanted; + if (item_disenchantskilllevel > player.GetSkillValue(SkillType.Enchanting)) + return SpellCastResult.LowCastlevel; + if (item_quality > 4 || item_quality < 2) + return SpellCastResult.CantBeDisenchanted; + if (itemProto.GetClass() != ItemClass.Weapon && itemProto.GetClass() != ItemClass.Armor) + return SpellCastResult.CantBeDisenchanted; + if (itemProto.DisenchantID == 0) + return SpellCastResult.CantBeDisenchanted; + break; + } + case SpellEffectName.Prospecting: + { + if (m_targets.GetItemTarget() == null) + return SpellCastResult.CantBeProspected; + //ensure item is a prospectable ore + if (!Convert.ToBoolean(m_targets.GetItemTarget().GetTemplate().GetFlags() & ItemFlags.IsProspectable)) + return SpellCastResult.CantBeProspected; + //prevent prospecting in trade slot + if (m_targets.GetItemTarget().GetOwnerGUID() != m_caster.GetGUID()) + return SpellCastResult.CantBeProspected; + //Check for enough skill in jewelcrafting + uint item_prospectingskilllevel = m_targets.GetItemTarget().GetTemplate().GetRequiredSkillRank(); + if (item_prospectingskilllevel > player.GetSkillValue(SkillType.Jewelcrafting)) + return SpellCastResult.LowCastlevel; + //make sure the player has the required ores in inventory + if (m_targets.GetItemTarget().GetCount() < 5) + return SpellCastResult.NeedMoreItems; + + if (!LootStorage.Prospecting.HaveLootFor(m_targets.GetItemTargetEntry())) + return SpellCastResult.CantBeProspected; + + break; + } + case SpellEffectName.Milling: + { + if (m_targets.GetItemTarget() == null) + return SpellCastResult.CantBeMilled; + //ensure item is a millable herb + if (!(m_targets.GetItemTarget().GetTemplate().GetFlags().HasAnyFlag(ItemFlags.IsMillable))) + return SpellCastResult.CantBeMilled; + //prevent milling in trade slot + if (m_targets.GetItemTarget().GetOwnerGUID() != m_caster.GetGUID()) + return SpellCastResult.CantBeMilled; + //Check for enough skill in inscription + uint item_millingskilllevel = m_targets.GetItemTarget().GetTemplate().GetRequiredSkillRank(); + if (item_millingskilllevel > player.GetSkillValue(SkillType.Inscription)) + return SpellCastResult.LowCastlevel; + //make sure the player has the required herbs in inventory + if (m_targets.GetItemTarget().GetCount() < 5) + return SpellCastResult.NeedMoreItems; + + if (!LootStorage.Milling.HaveLootFor(m_targets.GetItemTargetEntry())) + return SpellCastResult.CantBeMilled; + + break; + } + case SpellEffectName.WeaponDamage: + case SpellEffectName.WeaponDamageNoschool: + { + if (!m_caster.IsTypeId(TypeId.Player)) + return SpellCastResult.TargetNotPlayer; + + if (m_attackType != WeaponAttackType.RangedAttack) + break; + + Item pItem = m_caster.ToPlayer().GetWeaponForAttack(m_attackType); + if (pItem == null || pItem.IsBroken()) + return SpellCastResult.EquippedItem; + break; + } + case SpellEffectName.CreateManaGem: + { + uint item_id = effect.ItemType; + ItemTemplate proto = Global.ObjectMgr.GetItemTemplate(item_id); + + if (proto == null) + return SpellCastResult.ItemAtMaxCharges; + + Item pitem = player.GetItemByEntry(item_id); + if (pitem != null) + { + for (int x = 0; x < proto.Effects.Count && x < 5; ++x) + if (proto.Effects[x].Charges != 0 && pitem.GetSpellCharges(x) == proto.Effects[x].Charges) + return SpellCastResult.ItemAtMaxCharges; + } + break; + } + default: + break; + } + } + + // check weapon presence in slots for main/offhand weapons + if (!Convert.ToBoolean(_triggeredCastFlags & TriggerCastFlags.IgnoreEquippedItemRequirement) && m_spellInfo.EquippedItemClass >= 0) + { + // main hand weapon required + if (m_spellInfo.HasAttribute(SpellAttr3.MainHand)) + { + Item item = m_caster.ToPlayer().GetWeaponForAttack(WeaponAttackType.BaseAttack); + + // skip spell if no weapon in slot or broken + if (item == null || item.IsBroken()) + return SpellCastResult.EquippedItemClass; + + // skip spell if weapon not fit to triggered spell + if (!item.IsFitToSpellRequirements(m_spellInfo)) + return SpellCastResult.EquippedItemClass; + } + + // offhand hand weapon required + if (m_spellInfo.HasAttribute(SpellAttr3.ReqOffhand)) + { + Item item = m_caster.ToPlayer().GetWeaponForAttack(WeaponAttackType.OffAttack); + + // skip spell if no weapon in slot or broken + if (item == null || item.IsBroken()) + return SpellCastResult.EquippedItemClass; + + // skip spell if weapon not fit to triggered spell + if (!item.IsFitToSpellRequirements(m_spellInfo)) + return SpellCastResult.EquippedItemClass; + } + } + + return SpellCastResult.SpellCastOk; + } + + public void Delayed() // only called in DealDamage() + { + if (m_caster == null) + return; + + if (isDelayableNoMore()) // Spells may only be delayed twice + return; + + //check pushback reduce + int delaytime = 500; // spellcasting delay is normally 500ms + int delayReduce = 100; // must be initialized to 100 for percent modifiers + m_caster.ToPlayer().ApplySpellMod(m_spellInfo.Id, SpellModOp.NotLoseCastingTime, ref delayReduce, this); + delayReduce += m_caster.GetTotalAuraModifier(AuraType.ReducePushback) - 100; + if (delayReduce >= 100) + return; + + MathFunctions.AddPct(ref delaytime, -delayReduce); + + if (m_timer + delaytime > m_casttime) + { + delaytime = m_casttime - m_timer; + m_timer = m_casttime; + } + else + m_timer += delaytime; + + Log.outDebug(LogFilter.Spells, "Spell {0} partially interrupted for ({1}) ms at damage", m_spellInfo.Id, delaytime); + + SpellDelayed spellDelayed = new SpellDelayed(); + spellDelayed.Caster = m_caster.GetGUID(); + spellDelayed.ActualDelay = delaytime; + + m_caster.SendMessageToSet(spellDelayed, true); + } + + public void DelayedChannel() + { + if (m_caster == null || !m_caster.IsTypeId(TypeId.Player) || getState() != SpellState.Casting) + return; + + if (isDelayableNoMore()) // Spells may only be delayed twice + return; + + //check pushback reduce + int delaytime = MathFunctions.CalculatePct(m_spellInfo.GetDuration(), 25); // channeling delay is normally 25% of its time per hit + int delayReduce = 100; // must be initialized to 100 for percent modifiers + m_caster.ToPlayer().ApplySpellMod(m_spellInfo.Id, SpellModOp.NotLoseCastingTime, ref delayReduce, this); + delayReduce += m_caster.GetTotalAuraModifier(AuraType.ReducePushback) - 100; + if (delayReduce >= 100) + return; + + MathFunctions.AddPct(ref delaytime, -delayReduce); + + if (m_timer <= delaytime) + { + delaytime = m_timer; + m_timer = 0; + } + else + m_timer -= delaytime; + + Log.outDebug(LogFilter.Spells, "Spell {0} partially interrupted for {1} ms, new duration: {2} ms", m_spellInfo.Id, delaytime, m_timer); + + foreach (var ihit in m_UniqueTargetInfo) + if (ihit.missCondition == SpellMissInfo.None) + { + Unit unit = (m_caster.GetGUID() == ihit.targetGUID) ? m_caster : Global.ObjAccessor.GetUnit(m_caster, ihit.targetGUID); + if (unit != null) + unit.DelayOwnedAuras(m_spellInfo.Id, m_originalCasterGUID, delaytime); + } + + // partially interrupt persistent area auras + DynamicObject dynObj = m_caster.GetDynObject(m_spellInfo.Id); + if (dynObj != null) + dynObj.Delay(delaytime); + + SendChannelUpdate((uint)m_timer); + } + + bool UpdatePointers() + { + if (m_originalCasterGUID == m_caster.GetGUID()) + m_originalCaster = m_caster; + else + { + m_originalCaster = Global.ObjAccessor.GetUnit(m_caster, m_originalCasterGUID); + if (m_originalCaster != null && !m_originalCaster.IsInWorld) + m_originalCaster = null; + } + + if (!m_castItemGUID.IsEmpty() && m_caster.IsTypeId(TypeId.Player)) + { + m_CastItem = m_caster.ToPlayer().GetItemByGuid(m_castItemGUID); + m_castItemLevel = -1; + // cast item not found, somehow the item is no longer where we expected + if (!m_CastItem) + return false; + + // check if the item is really the same, in case it has been wrapped for example + if (m_castItemEntry != m_CastItem.GetEntry()) + return false; + + m_castItemLevel = (int)m_CastItem.GetItemLevel(m_caster.ToPlayer()); + } + + m_targets.Update(m_caster); + + // further actions done only for dest targets + if (!m_targets.HasDst()) + return true; + + // cache last transport + WorldObject transport = null; + + // update effect destinations (in case of moved transport dest target) + foreach (SpellEffectInfo effect in GetEffects()) + { + if (effect == null) + continue; + + SpellDestination dest = m_destTargets[effect.EffectIndex]; + if (dest.TransportGUID.IsEmpty()) + continue; + + if (transport == null || transport.GetGUID() != dest.TransportGUID) + transport = Global.ObjAccessor.GetWorldObject(m_caster, dest.TransportGUID); + + if (transport != null) + { + dest.Position.Relocate(transport.GetPosition()); + dest.Position.RelocateOffset(dest.TransportOffset); + } + } + + return true; + } + + public CurrentSpellTypes GetCurrentContainer() + { + if (IsNextMeleeSwingSpell()) + return CurrentSpellTypes.Melee; + else if (IsAutoRepeat()) + return CurrentSpellTypes.AutoRepeat; + else if (m_spellInfo.IsChanneled()) + return CurrentSpellTypes.Channeled; + else + return CurrentSpellTypes.Generic; + } + + bool CheckEffectTarget(Unit target, SpellEffectInfo effect, Position losPosition) + { + if (!effect.IsEffect()) + return false; + + switch (effect.ApplyAuraName) + { + case AuraType.ModPossess: + case AuraType.ModCharm: + case AuraType.ModPossessPet: + case AuraType.AoeCharm: + if (target.IsTypeId(TypeId.Unit) && target.IsVehicle()) + return false; + if (target.IsMounted()) + return false; + if (!target.GetCharmerGUID().IsEmpty()) + return false; + int damage = CalculateDamage(effect.EffectIndex, target); + if (damage != 0) + if (target.getLevel() > damage) + return false; + break; + default: + break; + } + + // check for ignore LOS on the effect itself + if (m_spellInfo.HasAttribute(SpellAttr2.CanTargetNotInLos) || Global.DisableMgr.IsDisabledFor(DisableType.Spell, m_spellInfo.Id, null, DisableFlags.SpellLOS)) + return true; + + // if spell is triggered, need to check for LOS disable on the aura triggering it and inherit that behaviour + if (IsTriggered() && m_triggeredByAuraSpell != null && (m_triggeredByAuraSpell.HasAttribute(SpellAttr2.CanTargetNotInLos) || Global.DisableMgr.IsDisabledFor(DisableType.Spell, m_triggeredByAuraSpell.Id, null, DisableFlags.SpellLOS))) + return true; + + // @todo shit below shouldn't be here, but it's temporary + if (losPosition != null) + return target.IsWithinLOS(losPosition.GetPositionX(), losPosition.GetPositionY(), losPosition.GetPositionZ()); + else + { + // Get GO cast coordinates if original caster . GO + WorldObject caster = null; + if (m_originalCasterGUID.IsGameObject()) + caster = m_caster.GetMap().GetGameObject(m_originalCasterGUID); + if (!caster) + caster = m_caster; + if (target != m_caster && !target.IsWithinLOSInMap(caster)) + return false; + } + + return true; + } + + bool CheckEffectTarget(GameObject target, SpellEffectInfo effect) + { + if (!effect.IsEffect()) + return false; + + switch (effect.Effect) + { + case SpellEffectName.GameObjectDamage: + case SpellEffectName.GameobjectRepair: + case SpellEffectName.GameobjectSetDestructionState: + if (target.GetGoType() != GameObjectTypes.DestructibleBuilding) + return false; + break; + default: + break; + } + + return true; + } + + bool CheckEffectTarget(Item target, SpellEffectInfo effect) + { + if (!effect.IsEffect()) + return false; + + return true; + } + + bool IsNextMeleeSwingSpell() + { + return m_spellInfo.HasAttribute(SpellAttr0.OnNextSwing); + } + + bool IsAutoActionResetSpell() + { + // @todo changed SPELL_INTERRUPT_FLAG_AUTOATTACK . SPELL_INTERRUPT_FLAG_INTERRUPT to fix compile - is this check correct at all? + if (IsTriggered() || !m_spellInfo.InterruptFlags.HasAnyFlag(SpellInterruptFlags.Interrupt)) + return false; + + if (m_casttime == 0 && m_spellInfo.HasAttribute(SpellAttr6.NotResetSwingIfInstant)) + return false; + + return true; + } + + bool IsNeedSendToClient() + { + return m_SpellVisual != 0 || m_spellInfo.IsChanneled() || + m_spellInfo.HasAttribute(SpellAttr8.AuraSendAmount) || m_spellInfo.Speed > 0.0f || (m_triggeredByAuraSpell == null && !IsTriggered()); + } + + bool HaveTargetsForEffect(byte effect) + { + foreach (var targetInfo in m_UniqueTargetInfo) + if (Convert.ToBoolean(targetInfo.effectMask & (1 << effect))) + return true; + + foreach (var targetInfo in m_UniqueGOTargetInfo) + if (Convert.ToBoolean(targetInfo.effectMask & (1 << effect))) + return true; + + foreach (var targetInfo in m_UniqueItemInfo) + if (Convert.ToBoolean(targetInfo.effectMask & (1 << effect))) + return true; + + return false; + } + + bool IsValidDeadOrAliveTarget(Unit target) + { + if (target.IsAlive()) + return !m_spellInfo.IsRequiringDeadTarget(); + if (m_spellInfo.IsAllowingDeadTarget()) + return true; + return false; + } + + void HandleLaunchPhase() + { + // handle effects with SPELL_EFFECT_HANDLE_LAUNCH mode + foreach (SpellEffectInfo effect in GetEffects()) + { + // don't do anything for empty effect + if (effect == null || !effect.IsEffect()) + continue; + + HandleEffects(null, null, null, effect.EffectIndex, SpellEffectHandleMode.Launch); + } + + float[] multiplier = new float[SpellConst.MaxEffects]; + foreach (SpellEffectInfo effect in GetEffects()) + if (effect != null && Convert.ToBoolean(m_applyMultiplierMask & (1 << (int)effect.EffectIndex))) + multiplier[effect.EffectIndex] = effect.CalcDamageMultiplier(m_originalCaster, this); + + bool usesAmmo = m_spellInfo.HasAttribute(SpellCustomAttributes.DirectDamage); + + foreach (var ihit in m_UniqueTargetInfo) + { + TargetInfo target = ihit; + + uint mask = target.effectMask; + if (mask == 0) + continue; + + DoAllEffectOnLaunchTarget(target, multiplier); + } + } + + void DoAllEffectOnLaunchTarget(TargetInfo targetInfo, float[] multiplier) + { + Unit unit = null; + // In case spell hit target, do all effect on that target + if (targetInfo.missCondition == SpellMissInfo.None) + unit = m_caster.GetGUID() == targetInfo.targetGUID ? m_caster : Global.ObjAccessor.GetUnit(m_caster, targetInfo.targetGUID); + // In case spell reflect from target, do all effect on caster (if hit) + else if (targetInfo.missCondition == SpellMissInfo.Reflect && targetInfo.reflectResult == SpellMissInfo.None) + unit = m_caster; + if (unit == null) + return; + + foreach (SpellEffectInfo effect in GetEffects()) + { + if (effect != null && Convert.ToBoolean(targetInfo.effectMask & (1 << (int)effect.EffectIndex))) + { + m_damage = 0; + m_healing = 0; + + HandleEffects(unit, null, null, effect.EffectIndex, SpellEffectHandleMode.LaunchTarget); + + if (m_damage > 0) + { + if (effect.IsTargetingArea() || effect.IsAreaAuraEffect() || effect.IsEffect(SpellEffectName.PersistentAreaAura)) + { + m_damage = (int)(m_damage * unit.GetTotalAuraMultiplierByMiscMask(AuraType.ModAoeDamageAvoidance, (uint)m_spellInfo.SchoolMask)); + if (!m_caster.IsTypeId(TypeId.Player)) + m_damage = (int)(m_damage * unit.GetTotalAuraMultiplierByMiscMask(AuraType.ModCreatureAoeDamageAvoidance, (uint)m_spellInfo.SchoolMask)); + + if (m_caster.IsTypeId(TypeId.Player)) + { + int targetAmount = m_UniqueTargetInfo.Count; + if (targetAmount > 10) + m_damage = m_damage * 10 / targetAmount; + } + } + } + + if (Convert.ToBoolean(m_applyMultiplierMask & (1 << (int)effect.EffectIndex))) + { + m_damage = (int)(m_damage * m_damageMultipliers[effect.EffectIndex]); + m_damageMultipliers[effect.EffectIndex] *= multiplier[effect.EffectIndex]; + } + targetInfo.damage += m_damage; + } + } + + Player modOwner = m_caster.GetSpellModOwner(); + if (modOwner) + modOwner.SetSpellModTakingSpell(this, true); + + targetInfo.crit = m_caster.IsSpellCrit(unit, m_spellInfo, m_spellSchoolMask, m_attackType); + + modOwner = m_caster.GetSpellModOwner(); + if (modOwner) + modOwner.SetSpellModTakingSpell(this, false); + } + + SpellCastResult CanOpenLock(uint effIndex, uint lockId, SkillType skillId, ref int reqSkillValue, ref int skillValue) + { + if (lockId == 0) // possible case for GO and maybe for items. + return SpellCastResult.SpellCastOk; + + // Get LockInfo + var lockInfo = CliDB.LockStorage.LookupByKey(lockId); + + if (lockInfo == null) + return SpellCastResult.BadTargets; + + SpellEffectInfo effect = GetEffect(effIndex); + if (effect == null) + return SpellCastResult.BadTargets; // no idea about correct error + + bool reqKey = false; // some locks not have reqs + + for (int j = 0; j < SharedConst.MaxLockCase; ++j) + { + switch ((LockKeyType)lockInfo.LockType[j]) + { + // check key item (many fit cases can be) + case LockKeyType.Item: + if (lockInfo.Index[j] != 0 && m_CastItem && m_CastItem.GetEntry() == lockInfo.Index[j]) + return SpellCastResult.SpellCastOk; + reqKey = true; + break; + // check key skill (only single first fit case can be) + case LockKeyType.Skill: + { + reqKey = true; + + // wrong locktype, skip + if (effect.MiscValue != lockInfo.Index[j]) + continue; + + skillId = SharedConst.SkillByLockType((LockType)lockInfo.Index[j]); + + if (skillId != SkillType.None || lockInfo.Index[j] == (uint)LockType.Picklock) + { + reqSkillValue = lockInfo.Skill[j]; + + // castitem check: rogue using skeleton keys. the skill values should not be added in this case. + skillValue = 0; + if (!m_CastItem && m_caster.IsTypeId(TypeId.Player)) + skillValue = m_caster.ToPlayer().GetSkillValue(skillId); + else if (lockInfo.Index[j] == (uint)LockType.Picklock) + skillValue = (int)m_caster.getLevel() * 5; + + // skill bonus provided by casting spell (mostly item spells) + // add the effect base points modifier from the spell cast (cheat lock / skeleton key etc.) + if (effect.TargetA.GetTarget() == Targets.GameobjectItemTarget || effect.TargetB.GetTarget() == Targets.GameobjectItemTarget) + skillValue += effect.CalcValue(); + + if (skillValue < reqSkillValue) + return SpellCastResult.LowCastlevel; + } + + return SpellCastResult.SpellCastOk; + } + } + } + + if (reqKey) + return SpellCastResult.BadTargets; + + return SpellCastResult.SpellCastOk; + } + + public void SetSpellValue(SpellValueMod mod, int value) + { + if (mod < SpellValueMod.End) + { + SpellEffectInfo effect = GetEffect((uint)mod); + if (effect != null) + m_spellValue.EffectBasePoints[(int)mod] = effect.CalcBaseValue(value); + return; + } + + switch (mod) + { + case SpellValueMod.RadiusMod: + m_spellValue.RadiusMod = (float)value / 10000; + break; + case SpellValueMod.MaxTargets: + m_spellValue.MaxAffectedTargets = (uint)value; + break; + case SpellValueMod.AuraStack: + m_spellValue.AuraStackAmount = (byte)value; + break; + } + } + + void PrepareTargetProcessing() + { + } + + void FinishTargetProcessing() + { + SendSpellExecuteLog(); + } + + void LoadScripts() + { + m_loadedScripts = Global.ScriptMgr.CreateSpellScripts(m_spellInfo.Id, this); + foreach (var script in m_loadedScripts) + { + Log.outDebug(LogFilter.Spells, "Spell.LoadScripts: Script `{0}` for spell `{1}` is loaded now", script._GetScriptName(), m_spellInfo.Id); + script.Register(); + } + } + + void CallScriptBeforeCastHandlers() + { + foreach (var script in m_loadedScripts) + { + script._PrepareScriptCall(SpellScriptHookType.BeforeCast); + + foreach (var hook in script.BeforeCast) + hook.Call(); + + script._FinishScriptCall(); + } + } + + void CallScriptOnCastHandlers() + { + foreach (var script in m_loadedScripts) + { + script._PrepareScriptCall(SpellScriptHookType.OnCast); + + foreach (var hook in script.OnCast) + hook.Call(); + + script._FinishScriptCall(); + } + } + + void CallScriptAfterCastHandlers() + { + foreach (var script in m_loadedScripts) + { + script._PrepareScriptCall(SpellScriptHookType.AfterCast); + + foreach (var hook in script.AfterCast) + hook.Call(); + + script._FinishScriptCall(); + } + } + + SpellCastResult CallScriptCheckCastHandlers() + { + SpellCastResult retVal = SpellCastResult.SpellCastOk; + foreach (var script in m_loadedScripts) + { + script._PrepareScriptCall(SpellScriptHookType.CheckCast); + + foreach (var hook in script.OnCheckCast) + { + SpellCastResult tempResult = hook.Call(); + if (tempResult != SpellCastResult.SpellCastOk) + retVal = tempResult; + } + + script._FinishScriptCall(); + } + return retVal; + } + + void PrepareScriptHitHandlers() + { + foreach (var script in m_loadedScripts) + script._InitHit(); + } + + bool CallScriptEffectHandlers(uint effIndex, SpellEffectHandleMode mode) + { + // execute script effect handler hooks and check if effects was prevented + bool preventDefault = false; + foreach (var script in m_loadedScripts) + { + SpellScriptHookType hookType; + List effList; + switch (mode) + { + case SpellEffectHandleMode.Launch: + effList = script.OnEffectLaunch; + hookType = SpellScriptHookType.Launch; + break; + case SpellEffectHandleMode.LaunchTarget: + effList = script.OnEffectLaunchTarget; + hookType = SpellScriptHookType.LaunchTarget; + break; + case SpellEffectHandleMode.Hit: + effList = script.OnEffectHit; + hookType = SpellScriptHookType.EffectHit; + break; + case SpellEffectHandleMode.HitTarget: + effList = script.OnEffectHitTarget; + hookType = SpellScriptHookType.EffectHitTarget; + break; + default: + Contract.Assert(false); + return false; + } + script._PrepareScriptCall(hookType); + foreach (var eff in effList) + { + // effect execution can be prevented + if (!script._IsEffectPrevented(effIndex) && eff.IsEffectAffected(m_spellInfo, effIndex)) + eff.Call(effIndex); + } + + if (!preventDefault) + preventDefault = script._IsDefaultEffectPrevented(effIndex); + + script._FinishScriptCall(); + } + return preventDefault; + } + + void CallScriptSuccessfulDispel(uint effIndex) + { + foreach (var script in m_loadedScripts) + { + script._PrepareScriptCall(SpellScriptHookType.EffectSuccessfulDispel); + + foreach (var hook in script.OnEffectSuccessfulDispel) + hook.Call(effIndex); + + script._FinishScriptCall(); + } + } + + void CallScriptBeforeHitHandlers(SpellMissInfo missInfo) + { + foreach (var script in m_loadedScripts) + { + script._PrepareScriptCall(SpellScriptHookType.BeforeHit); + + foreach (var hook in script.BeforeHit) + hook.Call(missInfo); + + script._FinishScriptCall(); + } + } + + void CallScriptOnHitHandlers() + { + foreach (var script in m_loadedScripts) + { + script._PrepareScriptCall(SpellScriptHookType.OnHit); + + foreach (var hook in script.OnHit) + hook.Call(); + + script._FinishScriptCall(); + } + } + + void CallScriptAfterHitHandlers() + { + foreach (var script in m_loadedScripts) + { + script._PrepareScriptCall(SpellScriptHookType.AfterHit); + + foreach (var hook in script.AfterHit) + hook.Call(); + + script._FinishScriptCall(); + } + } + + void CallScriptObjectAreaTargetSelectHandlers(List targets, uint effIndex, SpellImplicitTargetInfo targetType) + { + foreach (var script in m_loadedScripts) + { + script._PrepareScriptCall(SpellScriptHookType.ObjectAreaTargetSelect); + + foreach (var hook in script.OnObjectAreaTargetSelect) + if (hook.IsEffectAffected(m_spellInfo, effIndex) && targetType.GetTarget() == hook.GetTarget()) + hook.Call(targets); + + script._FinishScriptCall(); + } + } + + void CallScriptObjectTargetSelectHandlers(ref WorldObject target, uint effIndex, SpellImplicitTargetInfo targetType) + { + foreach (var script in m_loadedScripts) + { + script._PrepareScriptCall(SpellScriptHookType.ObjectTargetSelect); + + foreach (var hook in script.OnObjectTargetSelect) + if (hook.IsEffectAffected(m_spellInfo, effIndex) && targetType.GetTarget() == hook.GetTarget()) + hook.Call(ref target); + + script._FinishScriptCall(); + } + } + + void CallScriptDestinationTargetSelectHandlers(ref SpellDestination target, uint effIndex, SpellImplicitTargetInfo targetType) + { + foreach (var script in m_loadedScripts) + { + script._PrepareScriptCall(SpellScriptHookType.DestinationTargetSelect); + + foreach (var hook in script.OnDestinationTargetSelect) + if (hook.IsEffectAffected(m_spellInfo, effIndex) && targetType.GetTarget() == hook.GetTarget()) + hook.Call(ref target); + + script._FinishScriptCall(); + } + } + + bool CheckScriptEffectImplicitTargets(uint effIndex, uint effIndexToCheck) + { + // Skip if there are not any script + if (m_loadedScripts.Empty()) + return true; + + foreach (var script in m_loadedScripts) + { + foreach (var hook in script.OnObjectTargetSelect) + if ((hook.IsEffectAffected(m_spellInfo, effIndex) && !hook.IsEffectAffected(m_spellInfo, effIndexToCheck)) || + (!hook.IsEffectAffected(m_spellInfo, effIndex) && hook.IsEffectAffected(m_spellInfo, effIndexToCheck))) + return false; + + foreach (var hook in script.OnObjectAreaTargetSelect) + if ((hook.IsEffectAffected(m_spellInfo, effIndex) && !hook.IsEffectAffected(m_spellInfo, effIndexToCheck)) || + (!hook.IsEffectAffected(m_spellInfo, effIndex) && hook.IsEffectAffected(m_spellInfo, effIndexToCheck))) + return false; + } + return true; + } + + bool CanExecuteTriggersOnHit(uint effMask, SpellInfo triggeredByAura = null) + { + bool only_on_caster = (triggeredByAura != null && triggeredByAura.HasAttribute(SpellAttr4.ProcOnlyOnCaster)); + // If triggeredByAura has SPELL_ATTR4_PROC_ONLY_ON_CASTER then it can only proc on a casted spell with TARGET_UNIT_CASTER + foreach (SpellEffectInfo effect in GetEffects()) + { + if (effect != null && (Convert.ToBoolean(effMask & (1 << (int)effect.EffectIndex)) && (!only_on_caster || (effect.TargetA.GetTarget() == Targets.UnitCaster)))) + return true; + } + return false; + } + + void PrepareTriggersExecutedOnHit() + { + // @todo move this to scripts + if (m_spellInfo.SpellFamilyName != 0) + { + SpellInfo excludeCasterSpellInfo = Global.SpellMgr.GetSpellInfo(m_spellInfo.ExcludeCasterAuraSpell); + if (excludeCasterSpellInfo != null && !excludeCasterSpellInfo.IsPositive()) + m_preCastSpell = m_spellInfo.ExcludeCasterAuraSpell; + SpellInfo excludeTargetSpellInfo = Global.SpellMgr.GetSpellInfo(m_spellInfo.ExcludeTargetAuraSpell); + if (excludeTargetSpellInfo != null && !excludeTargetSpellInfo.IsPositive()) + m_preCastSpell = m_spellInfo.ExcludeTargetAuraSpell; + } + + // @todo move this to scripts + switch (m_spellInfo.SpellFamilyName) + { + case SpellFamilyNames.Mage: + { + // Permafrost + if (m_spellInfo.SpellFamilyFlags[1].HasAnyFlag(0x00001000u) || m_spellInfo.SpellFamilyFlags[0].HasAnyFlag(0x00100220u)) + m_preCastSpell = 68391; + break; + } + } + + // handle SPELL_AURA_ADD_TARGET_TRIGGER auras: + // save auras which were present on spell caster on cast, to prevent triggered auras from affecting caster + // and to correctly calculate proc chance when combopoints are present + var targetTriggers = m_caster.GetAuraEffectsByType(AuraType.AddTargetTrigger); + foreach (var eff in targetTriggers) + { + if (!eff.IsAffectingSpell(m_spellInfo)) + continue; + + SpellInfo auraSpellInfo = eff.GetSpellInfo(); + byte auraSpellIdx = eff.GetEffIndex(); + SpellEffectInfo auraEffect = auraSpellInfo.GetEffect(m_caster.GetMap().GetDifficultyID(), auraSpellIdx); + if (auraEffect != null) + { + SpellInfo spellInfo = Global.SpellMgr.GetSpellInfo(auraEffect.TriggerSpell); + if (spellInfo != null) + { + // calculate the chance using spell base amount, because aura amount is not updated on combo-points change + // this possibly needs fixing + int auraBaseAmount = eff.GetBaseAmount(); + // proc chance is stored in effect amount + int chance = m_caster.CalculateSpellDamage(null, auraSpellInfo, auraSpellIdx, auraBaseAmount); + // build trigger and add to the list + HitTriggerSpell spellTriggerInfo = new HitTriggerSpell(); + spellTriggerInfo.triggeredSpell = spellInfo; + spellTriggerInfo.triggeredByAura = auraSpellInfo; + spellTriggerInfo.chance = chance * eff.GetBase().GetStackAmount(); + + m_hitTriggerSpells.Add(spellTriggerInfo); + } + } + } + } + + bool HasGlobalCooldown() + { + // Only players or controlled units have global cooldown + if (!m_caster.IsTypeId(TypeId.Player) && m_caster.GetCharmInfo() == null) + return false; + + return m_caster.GetSpellHistory().HasGlobalCooldown(m_spellInfo); + } + + void TriggerGlobalCooldown() + { + int gcd = (int)m_spellInfo.StartRecoveryTime; + if (gcd == 0 || m_spellInfo.StartRecoveryCategory == 0) + return; + + // Only players or controlled units have global cooldown + if (!m_caster.IsTypeId(TypeId.Player) && m_caster.GetCharmInfo() == null) + return; + + if (m_caster.IsTypeId(TypeId.Player)) + if (m_caster.ToPlayer().GetCommandStatus(PlayerCommandStates.Cooldown)) + return; + + // Global cooldown can't leave range 1..1.5 secs + // There are some spells (mostly not casted directly by player) that have < 1 sec and > 1.5 sec global cooldowns + // but as tests show are not affected by any spell mods. + if (m_spellInfo.StartRecoveryTime >= 750 && m_spellInfo.StartRecoveryTime <= 1500) + { + // gcd modifier auras are applied only to own spells and only players have such mods + if (m_caster.IsTypeId(TypeId.Player)) + m_caster.ToPlayer().ApplySpellMod(m_spellInfo.Id, SpellModOp.GlobalCooldown, ref gcd, this); + + bool isMeleeOrRangedSpell = m_spellInfo.DmgClass == SpellDmgClass.Melee || m_spellInfo.DmgClass == SpellDmgClass.Ranged || + m_spellInfo.HasAttribute(SpellAttr0.ReqAmmo) || m_spellInfo.HasAttribute(SpellAttr0.Ability); + + // Apply haste rating + if (gcd > 750 && ((m_spellInfo.StartRecoveryCategory == 133 && !isMeleeOrRangedSpell) || m_caster.HasAuraTypeWithAffectMask(AuraType.ModGlobalCooldownByHaste, m_spellInfo))) + gcd = Math.Min(Math.Max((int)(gcd * m_caster.GetFloatValue(UnitFields.ModCastHaste)), 750), 1500); + + if (gcd > 750 && m_caster.HasAuraTypeWithAffectMask(AuraType.ModGlobalCooldownByHasteRegen, m_spellInfo)) + gcd = Math.Min(Math.Max((int)(gcd * m_caster.GetFloatValue(UnitFields.ModHasteRegen)), 750), 1500); + } + + m_caster.GetSpellHistory().AddGlobalCooldown(m_spellInfo, (uint)gcd); + } + + void CancelGlobalCooldown() + { + if (m_spellInfo.StartRecoveryTime == 0) + return; + + // Cancel global cooldown when interrupting current cast + if (m_caster.GetCurrentSpell(CurrentSpellTypes.Generic) != this) + return; + + // Only players or controlled units have global cooldown + if (!m_caster.IsTypeId(TypeId.Player) && m_caster.GetCharmInfo() == null) + return; + + m_caster.GetSpellHistory().CancelGlobalCooldown(m_spellInfo); + } + + List m_loadedScripts = new List(); + + int CalculateDamage(uint i, Unit target) + { + return m_caster.CalculateSpellDamage(target, m_spellInfo, i, m_spellValue.EffectBasePoints[i], m_castItemLevel); + } + int CalculateDamage(uint i, Unit target, out float variance) + { + return m_caster.CalculateSpellDamage(target, m_spellInfo, i, out variance, m_spellValue.EffectBasePoints[i], m_castItemLevel); + } + public SpellState getState() + { + return m_spellState; + } + public void setState(SpellState state) + { + m_spellState = state; + } + + void CheckSrc() + { + if (!m_targets.HasSrc()) m_targets.SetSrc(m_caster); + } + void CheckDst() + { + if (!m_targets.HasDst()) m_targets.SetDst(m_caster); + } + + public int GetCastTime() + { + return m_casttime; + } + bool IsAutoRepeat() + { + return m_autoRepeat; + } + void SetAutoRepeat(bool rep) + { + m_autoRepeat = rep; + } + void ReSetTimer() + { + m_timer = m_casttime > 0 ? m_casttime : 0; + } + + public bool IsTriggered() { return _triggeredCastFlags.HasAnyFlag(TriggerCastFlags.FullMask); } + public bool IsIgnoringCooldowns() { return _triggeredCastFlags.HasAnyFlag(TriggerCastFlags.IgnoreSpellAndCategoryCD); } + public bool IsChannelActive() { return m_caster.GetUInt32Value(UnitFields.ChannelSpell) != 0; } + + public bool IsDeletable() + { + return !m_referencedFromCurrentSpell && !m_executedCurrently; + } + public void SetReferencedFromCurrent(bool yes) + { + m_referencedFromCurrentSpell = yes; + } + public bool IsInterruptable() + { + return !m_executedCurrently; + } + void SetExecutedCurrently(bool yes) + { + m_executedCurrently = yes; + } + public ulong GetDelayStart() + { + return m_delayStart; + } + public void SetDelayStart(ulong m_time) + { + m_delayStart = m_time; + } + public ulong GetDelayMoment() + { + return m_delayMoment; + } + + public Unit GetCaster() + { + return m_caster; + } + public Unit GetOriginalCaster() + { + return m_originalCaster; + } + public SpellInfo GetSpellInfo() + { + return m_spellInfo; + } + public List GetPowerCost() + { + return m_powerCost; + } + + bool isDelayableNoMore() + { + if (m_delayAtDamageCount >= 2) + return true; + + m_delayAtDamageCount++; + return false; + } + + bool DontReport() + { + return Convert.ToBoolean(_triggeredCastFlags & TriggerCastFlags.DontReportCastError); + } + + public SpellInfo GetTriggeredByAuraSpell() { return m_triggeredByAuraSpell; } + + SpellEffectInfo[] GetEffects() { return _effects; } + public SpellEffectInfo GetEffect(uint index) + { + if (index >= _effects.Length) + return null; + + return _effects[index]; + } + + bool HasEffect(SpellEffectName effect) + { + foreach (SpellEffectInfo eff in GetEffects()) + { + if (eff != null && eff.IsEffect(effect)) + return true; + } + return false; + } + + public static implicit operator bool (Spell spell) + { + return spell != null; + } + + #region Fields + MultiMap _powerDrainTargets = new MultiMap(); + MultiMap _extraAttacksTargets = new MultiMap(); + MultiMap _durabilityDamageTargets = new MultiMap(); + MultiMap _genericVictimTargets = new MultiMap(); + MultiMap _tradeSkillTargets = new MultiMap(); + MultiMap _feedPetTargets = new MultiMap(); + PathGenerator m_preGeneratedPath; + + public SpellInfo m_spellInfo; + public Item m_CastItem; + public ObjectGuid m_castItemGUID; + public uint m_castItemEntry; + public int m_castItemLevel; + public ObjectGuid m_castId; + public ObjectGuid m_originalCastId; + public bool m_fromClient; + public SpellCastFlagsEx m_castFlagsEx; + public SpellMisc m_misc; + public uint m_SpellVisual; + public uint m_preCastSpell; + public SpellCastTargets m_targets; + public sbyte m_comboPointGain; + public SpellCustomErrors m_customError; + + public List m_appliedMods; + + Unit m_caster; + public SpellValue m_spellValue; + ObjectGuid m_originalCasterGUID; + Unit m_originalCaster; + public Spell m_selfContainer; + + //Spell data + SpellSchoolMask m_spellSchoolMask; // Spell school (can be overwrite for some spells (wand shoot for example) + WeaponAttackType m_attackType; // For weapon based attack + + List m_powerCost = new List(); + int m_casttime; // Calculated spell cast time initialized only in Spell.prepare + bool m_canReflect; // can reflect this spell? + bool m_autoRepeat; + byte m_runesState; + byte m_delayAtDamageCount; + + // Delayed spells system + ulong m_delayStart; // time of spell delay start, filled by event handler, zero = just started + ulong m_delayMoment; // moment of next delay call, used internally + bool m_immediateHandled; // were immediate actions handled? (used by delayed spells only) + + // These vars are used in both delayed spell system and modified immediate spell system + bool m_referencedFromCurrentSpell; + bool m_executedCurrently; + bool m_needComboPoints; + uint m_applyMultiplierMask; + float[] m_damageMultipliers = new float[SpellConst.MaxEffects]; + + // Current targets, to be used in SpellEffects (MUST BE USED ONLY IN SPELL EFFECTS) + public Unit unitTarget; + public Item itemTarget; + public GameObject gameObjTarget; + public WorldLocation destTarget; + public int damage; + float variance; + SpellEffectHandleMode effectHandleMode; + public SpellEffectInfo effectInfo; + // used in effects handlers + public Aura m_spellAura; + + // this is set in Spell Hit, but used in Apply Aura handler + DiminishingLevels m_diminishLevel; + DiminishingGroup m_diminishGroup; + + // ------------------------------------------- + GameObject focusObject; + + // Damage and healing in effects need just calculate + public int m_damage; // Damge in effects count here + public int m_healing; // Healing in effects count here + + // ****************************************** + // Spell trigger system + // ****************************************** + ProcFlags m_procAttacker; // Attacker trigger flags + ProcFlags m_procVictim; // Victim trigger flags + ProcFlagsExLegacy m_procEx; + + // ***************************************** + // Spell target subsystem + // ***************************************** + // Targets store structures and data + List m_UniqueTargetInfo = new List(); + uint m_channelTargetEffectMask; // Mask req. alive targets + + List m_UniqueGOTargetInfo = new List(); + + List m_UniqueItemInfo = new List(); + + SpellDestination[] m_destTargets = new SpellDestination[SpellConst.MaxEffects]; + + List m_hitTriggerSpells = new List(); + + SpellState m_spellState; + int m_timer; + + TriggerCastFlags _triggeredCastFlags; + + // if need this can be replaced by Aura copy + // we can't store original aura link to prevent access to deleted auras + // and in same time need aura data and after aura deleting. + public SpellInfo m_triggeredByAuraSpell; + + SpellEffectInfo[] _effects = new SpellEffectInfo[SpellConst.MaxEffects]; + + bool m_skipCheck; + byte m_auraScaleMask; + #endregion + } + + [StructLayout(LayoutKind.Explicit)] + public struct SpellMisc + { + // Alternate names for this value + [FieldOffset(0)] + public uint TalentId; + + [FieldOffset(0)] + public uint SpellId; + + [FieldOffset(0)] + public uint SpecializationId; + + // SPELL_EFFECT_SET_FOLLOWER_QUALITY + // SPELL_EFFECT_INCREASE_FOLLOWER_ITEM_LEVEL + // SPELL_EFFECT_INCREASE_FOLLOWER_EXPERIENCE + // SPELL_EFFECT_RANDOMIZE_FOLLOWER_ABILITIES + // SPELL_EFFECT_LEARN_FOLLOWER_ABILITY + [FieldOffset(0)] + public uint FollowerId; + + [FieldOffset(4)] + public uint FollowerAbilityId; // only SPELL_EFFECT_LEARN_FOLLOWER_ABILITY + + // SPELL_EFFECT_FINISH_GARRISON_MISSION + [FieldOffset(0)] + public uint GarrMissionId; + + // SPELL_EFFECT_UPGRADE_HEIRLOOM + [FieldOffset(0)] + public uint ItemId; + + [FieldOffset(0)] + public uint Data0; + + [FieldOffset(4)] + public uint Data1; + + public uint[] GetRawData() + { + return new uint[] { Data0, Data1 }; + } + } + + public struct HitTriggerSpell + { + public SpellInfo triggeredSpell; + public SpellInfo triggeredByAura; + // ubyte triggeredByEffIdx This might be needed at a later stage - No need known for now + public int chance; + } + + public enum SpellEffectHandleMode + { + Launch, + LaunchTarget, + Hit, + HitTarget + } + + public class SkillStatusData + { + public SkillStatusData(uint _pos, SkillState state) + { + Pos = (byte)_pos; + State = state; + } + public byte Pos; + public SkillState State; + } + + public class SpellChainNode + { + public SpellInfo prev; + public SpellInfo next; + public SpellInfo first; + public SpellInfo last; + public byte rank; + } + + public class SpellLearnSkillNode + { + public SkillType skill; + public ushort step; + public ushort value; // 0 - max skill value for player level + public ushort maxvalue; // 0 - max skill value for player level + } + + public class SpellLearnSpellNode + { + public uint Spell; + public uint OverridesSpell; + public bool Active; // show in spellbook or not + public bool AutoLearned; // This marks the spell as automatically learned from another source that - will only be used for unlearning + } + + public class SpellDestination + { + public SpellDestination() + { + Position = new WorldLocation(); + TransportGUID = ObjectGuid.Empty; + TransportOffset = new Position(); + } + + public SpellDestination(float x, float y, float z, float orientation = 0.0f, uint mapId = 0xFFFFFFFF) : this() + { + Position.Relocate(x, y, z, orientation); + TransportGUID = ObjectGuid.Empty; + Position.SetMapId(mapId); + } + + public SpellDestination(Position pos) : this() + { + Position.Relocate(pos); + TransportGUID = ObjectGuid.Empty; + } + + public SpellDestination(WorldObject wObj) : this() + { + TransportGUID = wObj.GetTransGUID(); + TransportOffset.Relocate(wObj.GetTransOffsetX(), wObj.GetTransOffsetY(), wObj.GetTransOffsetZ(), wObj.GetTransOffsetO()); + Position.Relocate(wObj.GetPosition()); + } + + public void Relocate(Position pos) + { + if (!TransportGUID.IsEmpty()) + { + Position offset; + Position.GetPositionOffsetTo(pos, out offset); + TransportOffset.RelocateOffset(offset); + } + Position.Relocate(pos); + } + + public void RelocateOffset(Position offset) + { + if (!TransportGUID.IsEmpty()) + TransportOffset.RelocateOffset(offset); + + Position.RelocateOffset(offset); + } + + public WorldLocation Position; + public ObjectGuid TransportGUID; + public Position TransportOffset; + } + + public class TargetInfo + { + public ObjectGuid targetGUID; + public ulong timeDelay; + public SpellMissInfo missCondition; + public SpellMissInfo reflectResult; + public uint effectMask; + public bool processed; + public bool alive; + public bool crit; + public bool scaleAura; + public int damage; + } + public class GOTargetInfo + { + public ObjectGuid targetGUID; + public ulong timeDelay; + public uint effectMask; + public bool processed; + } + + public class ItemTargetInfo + { + public Item item; + public uint effectMask; + } + + public class SpellValue + { + public SpellValue(Difficulty difficulty, SpellInfo proto) + { + var effects = proto.GetEffectsForDifficulty(difficulty); + Contract.Assert(effects.Length <= SpellConst.MaxEffects); + foreach (SpellEffectInfo effect in effects) + if (effect != null) + EffectBasePoints[effect.EffectIndex] = effect.BasePoints; + + MaxAffectedTargets = proto.MaxAffectedTargets; + RadiusMod = 1.0f; + AuraStackAmount = 1; + } + + public int[] EffectBasePoints = new int[SpellConst.MaxEffects]; + public uint MaxAffectedTargets; + public float RadiusMod; + public byte AuraStackAmount; + } + + // Spell modifier (used for modify other spells) + public class SpellModifier + { + public SpellModifier(Aura _ownerAura = null) + { + op = SpellModOp.Damage; + type = SpellModType.Flat; + charges = 0; + value = 0; + mask = new FlagArray128(); + spellId = 0; + ownerAura = _ownerAura; + } + + public SpellModOp op { get; set; } + public SpellModType type { get; set; } + public short charges { get; set; } + public int value { get; set; } + public FlagArray128 mask { get; set; } + public uint spellId { get; set; } + public Aura ownerAura { get; set; } + } + + public class WorldObjectSpellTargetCheck : ICheck + { + public WorldObjectSpellTargetCheck(Unit caster, Unit referer, SpellInfo spellInfo, + SpellTargetCheckTypes selectionType, List condList) + { + _caster = caster; + _referer = referer; + _spellInfo = spellInfo; + _targetSelectionType = selectionType; + _condList = condList; + + if (condList != null) + _condSrcInfo = new ConditionSourceInfo(null, caster); + else + _condSrcInfo = null; + } + + public virtual bool Invoke(WorldObject obj) + { + if (_spellInfo.CheckTarget(_caster, obj, true) != SpellCastResult.SpellCastOk) + return false; + Unit unitTarget = obj.ToUnit(); + Corpse corpseTarget = obj.ToCorpse(); + if (corpseTarget != null) + { + // use ofter for party/assistance checks + Player owner = Global.ObjAccessor.FindPlayer(corpseTarget.GetOwnerGUID()); + if (owner != null) + unitTarget = owner; + else + return false; + } + if (unitTarget != null) + { + switch (_targetSelectionType) + { + case SpellTargetCheckTypes.Enemy: + if (unitTarget.IsTotem()) + return false; + if (!_caster._IsValidAttackTarget(unitTarget, _spellInfo)) + return false; + break; + case SpellTargetCheckTypes.Ally: + if (unitTarget.IsTotem()) + return false; + if (!_caster._IsValidAssistTarget(unitTarget, _spellInfo)) + return false; + break; + case SpellTargetCheckTypes.Party: + if (unitTarget.IsTotem()) + return false; + if (!_caster._IsValidAssistTarget(unitTarget, _spellInfo)) + return false; + if (!_referer.IsInPartyWith(unitTarget)) + return false; + break; + case SpellTargetCheckTypes.RaidClass: + case SpellTargetCheckTypes.Raid: + if (_referer.GetClass() != unitTarget.GetClass()) + return false; + if (unitTarget.IsTotem()) + return false; + if (!_caster._IsValidAssistTarget(unitTarget, _spellInfo)) + return false; + if (!_referer.IsInRaidWith(unitTarget)) + return false; + break; + default: + break; + } + } + if (_condSrcInfo == null) + return true; + _condSrcInfo.mConditionTargets[0] = obj; + return Global.ConditionMgr.IsObjectMeetToConditions(_condSrcInfo, _condList); + } + + public Unit _caster { get; set; } + Unit _referer; + public SpellInfo _spellInfo { get; set; } + SpellTargetCheckTypes _targetSelectionType; + ConditionSourceInfo _condSrcInfo; + List _condList; + } + + public class WorldObjectSpellNearbyTargetCheck : WorldObjectSpellTargetCheck + { + float _range; + Position _position; + public WorldObjectSpellNearbyTargetCheck(float range, Unit caster, SpellInfo spellInfo, + SpellTargetCheckTypes selectionType, List condList) + : base(caster, caster, spellInfo, selectionType, condList) + { + _range = range; + _position = caster.GetPosition(); + } + + public override bool Invoke(WorldObject target) + { + float dist = target.GetDistance(_position); + if (dist < _range && base.Invoke(target)) + { + _range = dist; + return true; + } + return false; + } + } + + public class WorldObjectSpellAreaTargetCheck : WorldObjectSpellTargetCheck + { + float _range; + Position _position; + public WorldObjectSpellAreaTargetCheck(float range, Position position, Unit caster, + Unit referer, SpellInfo spellInfo, SpellTargetCheckTypes selectionType, List condList) + : base(caster, referer, spellInfo, selectionType, condList) + { + _range = range; + _position = position; + + } + + public override bool Invoke(WorldObject target) + { + if (!target.IsWithinDist3d(_position, _range) && !(target.IsTypeId(TypeId.GameObject) && target.ToGameObject().IsInRange(_position.posX, _position.posY, _position.posZ, _range))) + return false; + return base.Invoke(target); + } + } + + public class WorldObjectSpellConeTargetCheck : WorldObjectSpellAreaTargetCheck + { + public WorldObjectSpellConeTargetCheck(float coneAngle, float range, Unit caster, SpellInfo spellInfo, SpellTargetCheckTypes selectionType, List condList) + : base(range, caster.GetPosition(), caster, caster, spellInfo, selectionType, condList) + { + _coneAngle = coneAngle; + } + + public override bool Invoke(WorldObject target) + { + if (_spellInfo.HasAttribute(SpellCustomAttributes.ConeBack)) + { + if (!_caster.isInBack(target, _coneAngle)) + return false; + } + else if (_spellInfo.HasAttribute(SpellCustomAttributes.ConeLine)) + { + if (!_caster.HasInLine(target, _caster.GetObjectSize() + target.GetObjectSize())) + return false; + } + else + { + if (!_caster.IsWithinBoundaryRadius(target.ToUnit())) + if (!_caster.isInFront(target, _coneAngle)) + return false; + } + return base.Invoke(target); + } + + float _coneAngle; + } + + public class WorldObjectSpellTrajTargetCheck : WorldObjectSpellAreaTargetCheck + { + public WorldObjectSpellTrajTargetCheck(float range, Position position, Unit caster, SpellInfo spellInfo) + : base(range, position, caster, caster, spellInfo, SpellTargetCheckTypes.Default, null) { } + + public override bool Invoke(WorldObject target) + { + // return all targets on missile trajectory (0 - size of a missile) + if (!_caster.HasInLine(target, target.GetObjectSize())) + return false; + return base.Invoke(target); + } + } + + public class SpellEvent : BasicEvent + { + public SpellEvent(Spell spell) + { + m_Spell = spell; + } + + public override bool Execute(ulong e_time, uint p_time) + { + // update spell if it is not finished + if (m_Spell.getState() != SpellState.Finished) + m_Spell.update(p_time); + + // check spell state to process + switch (m_Spell.getState()) + { + case SpellState.Finished: + { + // spell was finished, check deletable state + if (m_Spell.IsDeletable()) + { + // check, if we do have unfinished triggered spells + return true; // spell is deletable, finish event + } + // event will be re-added automatically at the end of routine) + break; + } + case SpellState.Delayed: + { + // first, check, if we have just started + if (m_Spell.GetDelayStart() != 0) + { + // run the spell handler and think about what we can do next + ulong t_offset = e_time - m_Spell.GetDelayStart(); + ulong n_offset = m_Spell.handle_delayed(t_offset); + if (n_offset != 0) + { + // re-add us to the queue + m_Spell.GetCaster().m_Events.AddEvent(this, m_Spell.GetDelayStart() + n_offset, false); + return false; // event not complete + } + // event complete + // finish update event will be re-added automatically at the end of routine) + } + else + { + // delaying had just started, record the moment + m_Spell.SetDelayStart(e_time); + // handle effects on caster if the spell has travel time but also affects the caster in some way + if (!m_Spell.m_targets.HasDst()) + { + ulong n_offset = m_Spell.handle_delayed(0); + Contract.Assert(n_offset == m_Spell.GetDelayMoment()); + } + // re-plan the event for the delay moment + m_Spell.GetCaster().m_Events.AddEvent(this, e_time + m_Spell.GetDelayMoment(), false); + return false; // event not complete + } + break; + } + default: + { + // all other states + // event will be re-added automatically at the end of routine) + break; + } + } + + // spell processing not complete, plan event on the next update interval + m_Spell.GetCaster().m_Events.AddEvent(this, e_time + 1, false); + return false; // event not complete + } + public override void Abort(ulong e_time) + { + // oops, the spell we try to do is aborted + if (m_Spell.getState() != SpellState.Finished) + m_Spell.cancel(); + } + public override bool IsDeletable() + { + return m_Spell.IsDeletable(); + } + + Spell m_Spell; + } +} diff --git a/Game/Spells/SpellCastTargets.cs b/Game/Spells/SpellCastTargets.cs new file mode 100644 index 000000000..facdd154c --- /dev/null +++ b/Game/Spells/SpellCastTargets.cs @@ -0,0 +1,440 @@ +/* + * Copyright (C) 2012-2017 CypherCore + * + * 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, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +using Framework.Constants; +using Game.Entities; +using Game.Network.Packets; +using System; +using System.Diagnostics.Contracts; + +namespace Game.Spells +{ + public class SpellCastTargets + { + public SpellCastTargets() + { + m_strTarget = ""; + + m_src = new SpellDestination(); + m_dst = new SpellDestination(); + } + + public SpellCastTargets(Unit caster, SpellCastRequest spellCastRequest) + { + m_targetMask = spellCastRequest.Target.Flags; + m_objectTargetGUID = spellCastRequest.Target.Unit; + m_itemTargetGUID = spellCastRequest.Target.Item; + m_strTarget = spellCastRequest.Target.Name; + + m_src = new SpellDestination(); + m_dst = new SpellDestination(); + + if (spellCastRequest.Target.SrcLocation.HasValue) + { + m_src.TransportGUID = spellCastRequest.Target.SrcLocation.Value.Transport; + Position pos; + if (!m_src.TransportGUID.IsEmpty()) + pos = m_src.TransportOffset; + else + pos = m_src.Position; + + pos.Relocate(spellCastRequest.Target.SrcLocation.Value.Location); + if (spellCastRequest.Target.Orientation.HasValue) + pos.SetOrientation(spellCastRequest.Target.Orientation.Value); + } + + if (spellCastRequest.Target.DstLocation.HasValue) + { + m_dst.TransportGUID = spellCastRequest.Target.DstLocation.Value.Transport; + Position pos; + if (!m_dst.TransportGUID.IsEmpty()) + pos = m_dst.TransportOffset; + else + pos = m_dst.Position; + + pos.Relocate(spellCastRequest.Target.DstLocation.Value.Location); + if (spellCastRequest.Target.Orientation.HasValue) + pos.SetOrientation(spellCastRequest.Target.Orientation.Value); + } + + SetPitch(spellCastRequest.MissileTrajectory.Pitch); + SetSpeed(spellCastRequest.MissileTrajectory.Speed); + + Update(caster); + } + + public void Write(SpellTargetData data) + { + data.Flags = m_targetMask; + + if (m_targetMask.HasAnyFlag(SpellCastTargetFlags.Unit | SpellCastTargetFlags.CorpseAlly | SpellCastTargetFlags.Gameobject | SpellCastTargetFlags.CorpseEnemy | SpellCastTargetFlags.UnitMinipet)) + data.Unit = m_objectTargetGUID; + + if (m_targetMask.HasAnyFlag(SpellCastTargetFlags.Item | SpellCastTargetFlags.TradeItem) && m_itemTarget) + data.Item = m_itemTarget.GetGUID(); + + if (m_targetMask.HasAnyFlag(SpellCastTargetFlags.SourceLocation)) + { + data.SrcLocation.HasValue = true; + TargetLocation target = new TargetLocation(); + target.Transport = m_src.TransportGUID; // relative position guid here - transport for example + if (!m_src.TransportGUID.IsEmpty()) + target.Location = m_src.TransportOffset; + else + target.Location = m_src.Position; + + data.SrcLocation.Value = target; + } + + if (Convert.ToBoolean(m_targetMask & SpellCastTargetFlags.DestLocation)) + { + data.DstLocation.HasValue = true; + TargetLocation target = new TargetLocation(); + target.Transport = m_dst.TransportGUID; // relative position guid here - transport for example + if (!m_dst.TransportGUID.IsEmpty()) + target.Location = m_dst.TransportOffset; + else + target.Location = m_dst.Position; + + data.DstLocation.Value = target; + } + + if (Convert.ToBoolean(m_targetMask & SpellCastTargetFlags.String)) + data.Name = m_strTarget; + } + + public ObjectGuid GetOrigUnitTargetGUID() + { + switch (m_origObjectTargetGUID.GetHigh()) + { + case HighGuid.Player: + case HighGuid.Vehicle: + case HighGuid.Creature: + case HighGuid.Pet: + return m_origObjectTargetGUID; + default: + return ObjectGuid.Empty; + } + } + + public void SetOrigUnitTarget(Unit target) + { + if (!target) + return; + + m_origObjectTargetGUID = target.GetGUID(); + } + + public ObjectGuid GetUnitTargetGUID() + { + if (m_objectTargetGUID.IsUnit()) + return m_objectTargetGUID; + + return ObjectGuid.Empty; + } + + public Unit GetUnitTarget() + { + if (m_objectTarget) + return m_objectTarget.ToUnit(); + return null; + } + + public void SetUnitTarget(Unit target) + { + if (target == null) + return; + + m_objectTarget = target; + m_objectTargetGUID = target.GetGUID(); + m_targetMask |= SpellCastTargetFlags.Unit; + } + + ObjectGuid GetGOTargetGUID() + { + if (m_objectTargetGUID.IsAnyTypeGameObject()) + return m_objectTargetGUID; + + return ObjectGuid.Empty; + } + + public GameObject GetGOTarget() + { + if (m_objectTarget != null) + return m_objectTarget.ToGameObject(); + return null; + } + + public void SetGOTarget(GameObject target) + { + if (target == null) + return; + + m_objectTarget = target; + m_objectTargetGUID = target.GetGUID(); + m_targetMask |= SpellCastTargetFlags.Gameobject; + } + + public ObjectGuid GetCorpseTargetGUID() + { + if (m_objectTargetGUID.IsCorpse()) + return m_objectTargetGUID; + + return ObjectGuid.Empty; + } + + public Corpse GetCorpseTarget() + { + if (m_objectTarget != null) + return m_objectTarget.ToCorpse(); + return null; + } + + public WorldObject GetObjectTarget() + { + return m_objectTarget; + } + + public ObjectGuid GetObjectTargetGUID() + { + return m_objectTargetGUID; + } + + public void RemoveObjectTarget() + { + m_objectTarget = null; + m_objectTargetGUID.Clear(); + m_targetMask &= ~(SpellCastTargetFlags.UnitMask | SpellCastTargetFlags.CorpseMask | SpellCastTargetFlags.GameobjectMask); + } + + public void SetItemTarget(Item item) + { + if (item == null) + return; + + m_itemTarget = item; + m_itemTargetGUID = item.GetGUID(); + m_itemTargetEntry = item.GetEntry(); + m_targetMask |= SpellCastTargetFlags.Item; + } + + public void SetTradeItemTarget(Player caster) + { + m_itemTargetGUID = ObjectGuid.TradeItem; + m_itemTargetEntry = 0; + m_targetMask |= SpellCastTargetFlags.TradeItem; + + Update(caster); + } + + public void UpdateTradeSlotItem() + { + if (m_itemTarget != null && Convert.ToBoolean(m_targetMask & SpellCastTargetFlags.TradeItem)) + { + m_itemTargetGUID = m_itemTarget.GetGUID(); + m_itemTargetEntry = m_itemTarget.GetEntry(); + } + } + + public SpellDestination GetSrc() + { + return m_src; + } + + public Position GetSrcPos() + { + return m_src.Position; + } + + void SetSrc(float x, float y, float z) + { + m_src = new SpellDestination(x, y, z); + m_targetMask |= SpellCastTargetFlags.SourceLocation; + } + + void SetSrc(Position pos) + { + m_src = new SpellDestination(pos); + m_targetMask |= SpellCastTargetFlags.SourceLocation; + } + + public void SetSrc(WorldObject wObj) + { + m_src = new SpellDestination(wObj); + m_targetMask |= SpellCastTargetFlags.SourceLocation; + } + + public void ModSrc(Position pos) + { + Contract.Assert(m_targetMask.HasAnyFlag(SpellCastTargetFlags.SourceLocation)); + m_src.Relocate(pos); + } + + public void RemoveSrc() + { + m_targetMask &= ~SpellCastTargetFlags.SourceLocation; + } + + public SpellDestination GetDst() + { + return m_dst; + } + + public WorldLocation GetDstPos() + { + return m_dst.Position; + } + + public void SetDst(float x, float y, float z, float orientation, uint mapId = 0xFFFFFFFF) + { + m_dst = new SpellDestination(x, y, z, orientation, mapId); + m_targetMask |= SpellCastTargetFlags.DestLocation; + } + + public void SetDst(Position pos) + { + m_dst = new SpellDestination(pos); + m_targetMask |= SpellCastTargetFlags.DestLocation; + } + + public void SetDst(WorldObject wObj) + { + m_dst = new SpellDestination(wObj); + m_targetMask |= SpellCastTargetFlags.DestLocation; + } + + public void SetDst(SpellDestination spellDest) + { + m_dst = spellDest; + m_targetMask |= SpellCastTargetFlags.DestLocation; + } + + public void SetDst(SpellCastTargets spellTargets) + { + m_dst = spellTargets.m_dst; + m_targetMask |= SpellCastTargetFlags.DestLocation; + } + + public void ModDst(Position pos) + { + Contract.Assert(m_targetMask.HasAnyFlag(SpellCastTargetFlags.DestLocation)); + m_dst.Relocate(pos); + } + + public void ModDst(SpellDestination spellDest) + { + Contract.Assert(m_targetMask.HasAnyFlag(SpellCastTargetFlags.DestLocation)); + m_dst = spellDest; + } + + public void RemoveDst() + { + m_targetMask &= ~SpellCastTargetFlags.DestLocation; + } + + public void Update(Unit caster) + { + m_objectTarget = !m_objectTargetGUID.IsEmpty() ? ((m_objectTargetGUID == caster.GetGUID()) ? caster : Global.ObjAccessor.GetWorldObject(caster, m_objectTargetGUID)) : null; + + m_itemTarget = null; + if (caster is Player) + { + Player player = caster.ToPlayer(); + if (m_targetMask.HasAnyFlag(SpellCastTargetFlags.Item)) + m_itemTarget = player.GetItemByGuid(m_itemTargetGUID); + else if (m_targetMask.HasAnyFlag(SpellCastTargetFlags.TradeItem)) + { + if (m_itemTargetGUID == ObjectGuid.TradeItem) // here it is not guid but slot. Also prevents hacking slots + { + TradeData pTrade = player.GetTradeData(); + if (pTrade != null) + m_itemTarget = pTrade.GetTraderData().GetItem(TradeSlots.NonTraded); + } + } + + if (m_itemTarget != null) + m_itemTargetEntry = m_itemTarget.GetEntry(); + } + + // update positions by transport move + if (HasSrc() && !m_src.TransportGUID.IsEmpty()) + { + WorldObject transport = Global.ObjAccessor.GetWorldObject(caster, m_src.TransportGUID); + if (transport != null) + { + m_src.Position.Relocate(transport.GetPosition()); + m_src.Position.RelocateOffset(m_src.TransportOffset); + } + } + + if (HasDst() && !m_dst.TransportGUID.IsEmpty()) + { + WorldObject transport = Global.ObjAccessor.GetWorldObject(caster, m_dst.TransportGUID); + if (transport != null) + { + m_dst.Position.Relocate(transport.GetPosition()); + m_dst.Position.RelocateOffset(m_dst.TransportOffset); + } + } + } + + public SpellCastTargetFlags GetTargetMask() { return m_targetMask; } + public void SetTargetMask(SpellCastTargetFlags newMask) { m_targetMask = newMask; } + public void SetTargetFlag(SpellCastTargetFlags flag) { m_targetMask |= flag; } + + public ObjectGuid GetItemTargetGUID() { return m_itemTargetGUID; } + public Item GetItemTarget() { return m_itemTarget; } + public uint GetItemTargetEntry() { return m_itemTargetEntry; } + + public bool HasSrc() { return Convert.ToBoolean(m_targetMask & SpellCastTargetFlags.SourceLocation); } + public bool HasDst() { return Convert.ToBoolean(m_targetMask & SpellCastTargetFlags.DestLocation); } + public bool HasTraj() { return m_speed != 0; } + + + public float GetPitch() { return m_pitch; } + public void SetPitch(float pitch) { m_pitch = pitch; } + float GetSpeed() { return m_speed; } + public void SetSpeed(float speed) { m_speed = speed; } + + public float GetDist2d() { return m_src.Position.GetExactDist2d(m_dst.Position); } + public float GetSpeedXY() { return (float)(m_speed * Math.Cos(m_pitch)); } + public float GetSpeedZ() { return (float)(m_speed * Math.Sin(m_pitch)); } + + public string GetTargetString() { return m_strTarget; } + + #region Fields + SpellCastTargetFlags m_targetMask; + + // objects (can be used at spell creating and after Update at casting) + WorldObject m_objectTarget; + Item m_itemTarget; + + // object GUID/etc, can be used always + ObjectGuid m_origObjectTargetGUID; + ObjectGuid m_objectTargetGUID; + ObjectGuid m_itemTargetGUID; + uint m_itemTargetEntry; + + SpellDestination m_src; + SpellDestination m_dst; + + float m_pitch; + float m_speed; + string m_strTarget; + #endregion + } +} diff --git a/Game/Spells/SpellEffects.cs b/Game/Spells/SpellEffects.cs new file mode 100644 index 000000000..7433e4322 --- /dev/null +++ b/Game/Spells/SpellEffects.cs @@ -0,0 +1,5867 @@ +/* + * Copyright (C) 2012-2017 CypherCore + * + * 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, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +using Framework.Constants; +using Framework.Dynamic; +using Framework.GameMath; +using Game.BattleGrounds; +using Game.BattlePets; +using Game.Combat; +using Game.DataStorage; +using Game.Entities; +using Game.Garrisons; +using Game.Groups; +using Game.Guilds; +using Game.Loots; +using Game.Maps; +using Game.Movement; +using Game.Network.Packets; +using System; +using System.Collections.Generic; +using System.Diagnostics.Contracts; +using System.Linq; + +namespace Game.Spells +{ + public partial class Spell + { + [SpellEffectHandler(SpellEffectName.Null)] + [SpellEffectHandler(SpellEffectName.Portal)] + [SpellEffectHandler(SpellEffectName.BindSight)] + [SpellEffectHandler(SpellEffectName.CallPet)] + [SpellEffectHandler(SpellEffectName.Effect171)] + [SpellEffectHandler(SpellEffectName.Effect177)] + [SpellEffectHandler(SpellEffectName.PortalTeleport)] + [SpellEffectHandler(SpellEffectName.RitualBase)] + [SpellEffectHandler(SpellEffectName.RitualActivatePortal)] + [SpellEffectHandler(SpellEffectName.Dodge)] + [SpellEffectHandler(SpellEffectName.Evade)] + [SpellEffectHandler(SpellEffectName.Weapon)] + [SpellEffectHandler(SpellEffectName.Defense)] + [SpellEffectHandler(SpellEffectName.SpellDefense)] + [SpellEffectHandler(SpellEffectName.Language)] + [SpellEffectHandler(SpellEffectName.Spawn)] + [SpellEffectHandler(SpellEffectName.Stealth)] + [SpellEffectHandler(SpellEffectName.Detect)] + [SpellEffectHandler(SpellEffectName.ForceCriticalHit)] + [SpellEffectHandler(SpellEffectName.Attack)] + [SpellEffectHandler(SpellEffectName.ThreatAll)] + [SpellEffectHandler(SpellEffectName.Effect112)] + [SpellEffectHandler(SpellEffectName.TeleportGraveyard)] + [SpellEffectHandler(SpellEffectName.Effect122)] + [SpellEffectHandler(SpellEffectName.Effect175)] + [SpellEffectHandler(SpellEffectName.Effect178)] + void EffectUnused(uint effIndex) { } + + void EffectResurrectNew(uint effIndex) + { + if (effectHandleMode != SpellEffectHandleMode.HitTarget) + return; + + if (unitTarget == null || unitTarget.IsAlive()) + return; + + if (!unitTarget.IsTypeId(TypeId.Player)) + return; + + if (!unitTarget.IsInWorld) + return; + + Player target = unitTarget.ToPlayer(); + + if (target.IsResurrectRequested()) // already have one active request + return; + + int health = damage; + int mana = effectInfo.MiscValue; + ExecuteLogEffectResurrect(effIndex, target); + target.SetResurrectRequestData(m_caster, (uint)health, (uint)mana, 0); + SendResurrectRequest(target); + } + + [SpellEffectHandler(SpellEffectName.Instakill)] + void EffectInstaKill(uint effIndex) + { + if (effectHandleMode != SpellEffectHandleMode.HitTarget) + return; + + if (unitTarget == null || !unitTarget.IsAlive()) + return; + + if (unitTarget.IsTypeId(TypeId.Player)) + if (unitTarget.ToPlayer().GetCommandStatus(PlayerCommandStates.God)) + return; + + if (m_caster == unitTarget) // prevent interrupt message + finish(); + + SpellInstakillLog data = new SpellInstakillLog(); + data.Target = unitTarget.GetGUID(); + data.Caster = m_caster.GetGUID(); + data.SpellID = m_spellInfo.Id; + m_caster.SendMessageToSet(data, true); + + m_caster.DealDamage(unitTarget, (uint)unitTarget.GetHealth(), null, DamageEffectType.NoDamage, SpellSchoolMask.Normal, null, false); + } + + void EffectEnvironmentalDMG(uint effIndex) + { + if (effectHandleMode != SpellEffectHandleMode.HitTarget) + return; + + if (unitTarget == null || !unitTarget.IsAlive()) + return; + + uint absorb = 0; + uint resist = 0; + + m_caster.CalcAbsorbResist(unitTarget, m_spellInfo.GetSchoolMask(), DamageEffectType.SpellDirect, (uint)damage, ref absorb, ref resist, m_spellInfo); + + SpellNonMeleeDamage log = new SpellNonMeleeDamage(m_caster, unitTarget, m_spellInfo.Id, m_SpellVisual, m_spellInfo.GetSchoolMask(), m_castId); + log.damage = (uint)(damage - absorb - resist); + log.absorb = absorb; + log.resist = resist; + if (unitTarget.IsTypeId(TypeId.Player)) + unitTarget.ToPlayer().EnvironmentalDamage(EnviromentalDamage.Fire, (uint)damage); + + m_caster.SendSpellNonMeleeDamageLog(log); + } + + [SpellEffectHandler(SpellEffectName.SchoolDamage)] + void EffectSchoolDmg(uint effIndex) + { + if (effectHandleMode != SpellEffectHandleMode.LaunchTarget) + return; + + if (unitTarget != null && unitTarget.IsAlive()) + { + bool apply_direct_bonus = true; + + // Meteor like spells (divided damage to targets) + if (m_spellInfo.HasAttribute(SpellCustomAttributes.ShareDamage)) + { + int count = m_UniqueTargetInfo.Count(targetInfo => + { + return Convert.ToBoolean(targetInfo.effectMask & (1 << (int)effIndex)); + }); + + // divide to all targets + if (count != 0) + damage /= count; + } + + switch (m_spellInfo.SpellFamilyName) + { + case SpellFamilyNames.Generic: + { + switch (m_spellInfo.Id) // better way to check unknown + { + // Consumption + case 28865: + damage = m_caster.GetMap().GetDifficultyID() == Difficulty.None ? 2750 : 4250; + break; + // percent from health with min + case 25599: // Thundercrash + { + damage = (int)unitTarget.GetHealth() / 2; + if (damage < 200) + damage = 200; + break; + } + // arcane charge. must only affect demons (also undead?) + case 45072: + { + if (unitTarget.GetCreatureType() != CreatureType.Demon + && unitTarget.GetCreatureType() != CreatureType.Undead) + return; + break; + } + // Gargoyle Strike + case 51963: + { + // about +4 base spell dmg per level + damage = (int)(m_caster.getLevel() - 60) * 4 + 60; + break; + } + } + break; + } + case SpellFamilyNames.Warrior: + { + // Victory Rush + if (m_spellInfo.Id == 34428) + MathFunctions.ApplyPct(ref damage, m_caster.GetTotalAttackPowerValue(WeaponAttackType.BaseAttack)); + // Shockwave + else if (m_spellInfo.Id == 46968) + { + int pct = m_caster.CalculateSpellDamage(unitTarget, m_spellInfo, 2); + if (pct > 0) + damage += (int)MathFunctions.CalculatePct(m_caster.GetTotalAttackPowerValue(WeaponAttackType.BaseAttack), pct); + break; + } + break; + } + case SpellFamilyNames.Warlock: + { + break; + } + case SpellFamilyNames.Priest: + { + break; + } + case SpellFamilyNames.Druid: + { + // Ferocious Bite + if (m_caster.IsTypeId(TypeId.Player) && m_spellInfo.SpellFamilyFlags[3].HasAnyFlag(0x1000u)) + { + // converts each extra point of energy ( up to 25 energy ) into additional damage + int energy = -(m_caster.ModifyPower(PowerType.Energy, -25)); + // 25 energy = 100% more damage + MathFunctions.AddPct(ref damage, energy * 4); + } + break; + } + case SpellFamilyNames.Deathknight: + { + // Blood Boil - bonus for diseased targets + if (m_spellInfo.SpellFamilyFlags[0].HasAnyFlag(0x00040000u)) + { + if (unitTarget.GetAuraEffect(AuraType.PeriodicDamage, SpellFamilyNames.Deathknight, new FlagArray128(0, 0, 0x00000002), m_caster.GetGUID()) != null) + { + damage += m_damage / 2; + damage += (int)(m_caster.GetTotalAttackPowerValue(WeaponAttackType.BaseAttack) * 0.035f); + } + } + break; + } + } + + if (m_originalCaster != null && apply_direct_bonus) + { + uint bonus = m_originalCaster.SpellDamageBonusDone(unitTarget, m_spellInfo, (uint)damage, DamageEffectType.SpellDirect, effectInfo); + damage = (int)(bonus + (bonus * variance)); + damage = (int)unitTarget.SpellDamageBonusTaken(m_originalCaster, m_spellInfo, (uint)damage, DamageEffectType.SpellDirect, effectInfo); + } + + m_damage += damage; + } + } + + [SpellEffectHandler(SpellEffectName.Dummy)] + void EffectDummy(uint effIndex) + { + if (effectHandleMode != SpellEffectHandleMode.HitTarget) + return; + + if (!unitTarget && !gameObjTarget && !itemTarget) + return; + + // selection by spell family + switch (m_spellInfo.SpellFamilyName) + { + case SpellFamilyNames.Paladin: + switch (m_spellInfo.Id) + { + case 31789: // Righteous Defense (step 1) + { + // Clear targets for eff 1 + foreach (var hit in m_UniqueTargetInfo) + hit.effectMask &= ~Convert.ToUInt32(1 << 1); + + // not empty (checked), copy + var attackers = unitTarget.getAttackers(); + + // remove invalid attackers + foreach (var att in attackers) + if (!att.IsValidAttackTarget(m_caster)) + attackers.Remove(att); + + // selected from list 3 + int maxTargets = Math.Min(3, attackers.Count); + for (uint i = 0; i < maxTargets; ++i) + { + Unit attacker = attackers.PickRandom(); + AddUnitTarget(attacker, 1 << 1); + attackers.Remove(attacker); + } + + // now let next effect cast spell at each target. + return; + } + } + break; + } + + // pet auras + PetAura petSpell = Global.SpellMgr.GetPetAura(m_spellInfo.Id, (byte)effIndex); + if (petSpell != null) + { + m_caster.AddPetAura(petSpell); + return; + } + + // normal DB scripted effect + Log.outDebug(LogFilter.Spells, "Spell ScriptStart spellid {0} in EffectDummy({1})", m_spellInfo.Id, effIndex); + m_caster.GetMap().ScriptsStart(ScriptsType.Spell, (uint)((int)m_spellInfo.Id | (int)(effIndex << 24)), m_caster, unitTarget); + + // Script based implementation. Must be used only for not good for implementation in core spell effects + // So called only for not proccessed cases + if (gameObjTarget) + Global.ScriptMgr.OnDummyEffect(m_caster, m_spellInfo.Id, effIndex, gameObjTarget); + else if (unitTarget && unitTarget.IsTypeId(TypeId.Unit)) + Global.ScriptMgr.OnDummyEffect(m_caster, m_spellInfo.Id, effIndex, unitTarget.ToCreature()); + else if (itemTarget) + Global.ScriptMgr.OnDummyEffect(m_caster, m_spellInfo.Id, effIndex, itemTarget); + } + + [SpellEffectHandler(SpellEffectName.TriggerSpell)] + [SpellEffectHandler(SpellEffectName.TriggerSpellWithValue)] + void EffectTriggerSpell(uint effIndex) + { + if (effectHandleMode != SpellEffectHandleMode.LaunchTarget + && effectHandleMode != SpellEffectHandleMode.Launch) + return; + + uint triggered_spell_id = effectInfo.TriggerSpell; + + // @todo move those to spell scripts + if (effectInfo.Effect == SpellEffectName.TriggerSpell && effectHandleMode == SpellEffectHandleMode.LaunchTarget) + { + // special cases + switch (triggered_spell_id) + { + // Vanish (not exist) + case 18461: + { + unitTarget.RemoveMovementImpairingAuras(); + unitTarget.RemoveAurasByType(AuraType.ModStalked); + return; + } + // Demonic Empowerment -- succubus + case 54437: + { + unitTarget.RemoveMovementImpairingAuras(); + unitTarget.RemoveAurasByType(AuraType.ModStalked); + unitTarget.RemoveAurasByType(AuraType.ModStun); + + // Cast Lesser Invisibility + unitTarget.CastSpell(unitTarget, 7870, true); + return; + } + // Brittle Armor - (need add max stack of 24575 Brittle Armor) + case 29284: + { + // Brittle Armor + SpellInfo spell = Global.SpellMgr.GetSpellInfo(24575); + if (spell == null) + return; + + for (uint j = 0; j < spell.StackAmount; ++j) + m_caster.CastSpell(unitTarget, spell.Id, true); + return; + } + // Mercurial Shield - (need add max stack of 26464 Mercurial Shield) + case 29286: + { + // Mercurial Shield + SpellInfo spell = Global.SpellMgr.GetSpellInfo(26464); + if (spell == null) + return; + + for (uint j = 0; j < spell.StackAmount; ++j) + m_caster.CastSpell(unitTarget, spell.Id, true); + return; + } + // Cloak of Shadows + case 35729: + { + uint dispelMask = SpellInfo.GetDispelMask(DispelType.ALL); + foreach (var iter in unitTarget.GetAppliedAuras()) + { + // remove all harmful spells on you... + SpellInfo spell = iter.Value.GetBase().GetSpellInfo(); + if ((spell.DmgClass == SpellDmgClass.Magic // only affect magic spells + || (Convert.ToBoolean(spell.GetDispelMask() & dispelMask)) + // ignore positive and passive auras + && !iter.Value.IsPositive() && !iter.Value.GetBase().IsPassive())) + { + m_caster.RemoveAura(iter); + } + } + return; + } + } + } + + // normal case + SpellInfo spellInfo = Global.SpellMgr.GetSpellInfo(triggered_spell_id); + if (spellInfo == null) + { + Log.outDebug(LogFilter.Spells, "Spell.EffectTriggerSpell spell {0} tried to trigger unknown spell {1}", m_spellInfo.Id, triggered_spell_id); + return; + } + + SpellCastTargets targets = new SpellCastTargets(); + if (effectHandleMode == SpellEffectHandleMode.LaunchTarget) + { + if (!spellInfo.NeedsToBeTriggeredByCaster(m_spellInfo, m_caster.GetMap().GetDifficultyID())) + return; + targets.SetUnitTarget(unitTarget); + } + else //if (effectHandleMode == SpellEffectHandleMode.Launch) + { + if (spellInfo.NeedsToBeTriggeredByCaster(m_spellInfo, m_caster.GetMap().GetDifficultyID()) && effectInfo.GetProvidedTargetMask().HasAnyFlag(SpellCastTargetFlags.UnitMask)) + return; + + if (spellInfo.GetExplicitTargetMask().HasAnyFlag(SpellCastTargetFlags.DestLocation)) + targets.SetDst(m_targets); + + targets.SetUnitTarget(m_caster); + } + + Dictionary values = new Dictionary(); + // set basepoints for trigger with value effect + if (effectInfo.Effect == SpellEffectName.TriggerSpellWithValue) + { + values.Add(SpellValueMod.BasePoint0, damage); + values.Add(SpellValueMod.BasePoint1, damage); + values.Add(SpellValueMod.BasePoint2, damage); + } + + // original caster guid only for GO cast + m_caster.CastSpell(targets, spellInfo, values, TriggerCastFlags.FullMask, null, null, m_originalCasterGUID); + } + + [SpellEffectHandler(SpellEffectName.TriggerMissile)] + [SpellEffectHandler(SpellEffectName.TriggerMissileSpellWithValue)] + void EffectTriggerMissileSpell(uint effIndex) + { + if (effectHandleMode != SpellEffectHandleMode.HitTarget + && effectHandleMode != SpellEffectHandleMode.Hit) + return; + + uint triggered_spell_id = effectInfo.TriggerSpell; + + // normal case + SpellInfo spellInfo = Global.SpellMgr.GetSpellInfo(triggered_spell_id); + if (spellInfo == null) + { + Log.outDebug(LogFilter.Spells, "Spell.EffectTriggerMissileSpell spell {0} tried to trigger unknown spell {1}", m_spellInfo.Id, triggered_spell_id); + return; + } + + SpellCastTargets targets = new SpellCastTargets(); + if (effectHandleMode == SpellEffectHandleMode.HitTarget) + { + if (!spellInfo.NeedsToBeTriggeredByCaster(m_spellInfo, m_caster.GetMap().GetDifficultyID())) + return; + targets.SetUnitTarget(unitTarget); + } + else //if (effectHandleMode == SpellEffectHandleMode.Hit) + { + if (spellInfo.NeedsToBeTriggeredByCaster(m_spellInfo, m_caster.GetMap().GetDifficultyID()) && effectInfo.GetProvidedTargetMask().HasAnyFlag(SpellCastTargetFlags.UnitMask)) + return; + + if (spellInfo.GetExplicitTargetMask().HasAnyFlag(SpellCastTargetFlags.DestLocation)) + targets.SetDst(m_targets); + + targets.SetUnitTarget(m_caster); + } + + Dictionary values = new Dictionary(); + // set basepoints for trigger with value effect + if (effectInfo.Effect == SpellEffectName.TriggerMissileSpellWithValue) + { + // maybe need to set value only when basepoints == 0? + values.Add(SpellValueMod.BasePoint0, damage); + values.Add(SpellValueMod.BasePoint1, damage); + values.Add(SpellValueMod.BasePoint2, damage); + } + + // original caster guid only for GO cast + m_caster.CastSpell(targets, spellInfo, values, TriggerCastFlags.FullMask, null, null, m_originalCasterGUID); + } + + [SpellEffectHandler(SpellEffectName.ForceCast)] + [SpellEffectHandler(SpellEffectName.ForceCastWithValue)] + [SpellEffectHandler(SpellEffectName.ForceCast2)] + void EffectForceCast(uint effIndex) + { + if (effectHandleMode != SpellEffectHandleMode.HitTarget) + return; + + if (unitTarget == null) + return; + + uint triggered_spell_id = effectInfo.TriggerSpell; + + // normal case + SpellInfo spellInfo = Global.SpellMgr.GetSpellInfo(triggered_spell_id); + + if (spellInfo == null) + { + Log.outError(LogFilter.Spells, "Spell.EffectForceCast of spell {0}: triggering unknown spell id {1}", m_spellInfo.Id, triggered_spell_id); + return; + } + + if (effectInfo.Effect == SpellEffectName.ForceCast && damage != 0) + { + switch (m_spellInfo.Id) + { + case 52588: // Skeletal Gryphon Escape + case 48598: // Ride Flamebringer Cue + unitTarget.RemoveAura((uint)damage); + break; + case 52463: // Hide In Mine Car + case 52349: // Overtake + unitTarget.CastCustomSpell(unitTarget, spellInfo.Id, damage, 0, 0, true, null, null, m_originalCasterGUID); + return; + } + } + + Dictionary values = new Dictionary(); + // set basepoints for trigger with value effect + if (effectInfo.Effect == SpellEffectName.ForceCastWithValue) + { + // maybe need to set value only when basepoints == 0? + values.Add(SpellValueMod.BasePoint0, damage); + values.Add(SpellValueMod.BasePoint1, damage); + values.Add(SpellValueMod.BasePoint2, damage); + } + + SpellCastTargets targets = new SpellCastTargets(); + targets.SetUnitTarget(m_caster); + + unitTarget.CastSpell(targets, spellInfo, values, TriggerCastFlags.FullMask); + } + + [SpellEffectHandler(SpellEffectName.TriggerSpell2)] + void EffectTriggerRitualOfSummoning(uint effIndex) + { + if (effectHandleMode != SpellEffectHandleMode.Hit) + return; + + uint triggered_spell_id = effectInfo.TriggerSpell; + SpellInfo spellInfo = Global.SpellMgr.GetSpellInfo(triggered_spell_id); + if (spellInfo == null) + { + Log.outError(LogFilter.Spells, "EffectTriggerRitualOfSummoning of spell {0}: triggering unknown spell id {1}", m_spellInfo.Id, triggered_spell_id); + return; + } + + finish(); + + m_caster.CastSpell(null, spellInfo, false); + } + + [SpellEffectHandler(SpellEffectName.Jump)] + void EffectJump(uint effIndex) + { + if (effectHandleMode != SpellEffectHandleMode.LaunchTarget) + return; + + if (m_caster.IsInFlight()) + return; + + if (unitTarget == null) + return; + + float x, y, z; + unitTarget.GetContactPoint(m_caster, out x, out y, out z, SharedConst.ContactDistance); + + float speedXY, speedZ; + CalculateJumpSpeeds(effectInfo, m_caster.GetExactDist2d(x, y), out speedXY, out speedZ); + JumpArrivalCastArgs arrivalCast = new JumpArrivalCastArgs(); + arrivalCast.SpellId = effectInfo.TriggerSpell; + arrivalCast.Target = unitTarget.GetGUID(); + m_caster.GetMotionMaster().MoveJump(x, y, z, 0.0f, speedXY, speedZ, EventId.Jump, false, arrivalCast); + } + + [SpellEffectHandler(SpellEffectName.JumpDest)] + void EffectJumpDest(uint effIndex) + { + if (effectHandleMode != SpellEffectHandleMode.Launch) + return; + + if (m_caster.IsInFlight()) + return; + + if (!m_targets.HasDst()) + return; + + float speedXY, speedZ; + CalculateJumpSpeeds(effectInfo, m_caster.GetExactDist2d(destTarget), out speedXY, out speedZ); + JumpArrivalCastArgs arrivalCast = new JumpArrivalCastArgs(); + arrivalCast.SpellId = effectInfo.TriggerSpell; + m_caster.GetMotionMaster().MoveJump(destTarget, speedXY, speedZ, EventId.Jump, !m_targets.GetObjectTargetGUID().IsEmpty(), arrivalCast); + } + + void CalculateJumpSpeeds(SpellEffectInfo effInfo, float dist, out float speedXY, out float speedZ) + { + if (effInfo.MiscValue != 0) + speedZ = (float)effInfo.MiscValue / 10; + else if (effInfo.MiscValueB != 0) + speedZ = (float)effInfo.MiscValueB / 10; + else + speedZ = 10.0f; + + speedXY = dist * 10.0f / speedZ; + } + + [SpellEffectHandler(SpellEffectName.TeleportUnits)] + void EffectTeleportUnits(uint effIndex) + { + if (effectHandleMode != SpellEffectHandleMode.HitTarget) + return; + + if (unitTarget == null || unitTarget.IsInFlight()) + return; + + // If not exist data for dest location - return + if (!m_targets.HasDst()) + { + Log.outError(LogFilter.Spells, "Spell.EffectTeleportUnits - does not have a destination for spellId {0}.", m_spellInfo.Id); + return; + } + + // Init dest coordinates + uint mapid = destTarget.GetMapId(); + if (mapid == 0xFFFFFFFF) + mapid = unitTarget.GetMapId(); + float x, y, z, orientation; + destTarget.GetPosition(out x, out y, out z, out orientation); + if (orientation == 0 && m_targets.GetUnitTarget() != null) + orientation = m_targets.GetUnitTarget().GetOrientation(); + Log.outDebug(LogFilter.Spells, "Spell.EffectTeleportUnits - teleport unit to {0} {1} {2} {3} {4}\n", mapid, x, y, z, orientation); + + Player player = unitTarget.ToPlayer(); + if (player) + { + // Custom loading screen + uint customLoadingScreenId = (uint)effectInfo.MiscValue; + if (customLoadingScreenId != 0) + player.SendPacket(new CustomLoadScreen(m_spellInfo.Id, customLoadingScreenId)); + + player.TeleportTo(mapid, x, y, z, orientation, unitTarget == m_caster ? TeleportToOptions.Spell | TeleportToOptions.NotLeaveCombat : 0); + } + else if (mapid == unitTarget.GetMapId()) + unitTarget.NearTeleportTo(x, y, z, orientation, unitTarget == m_caster); + else + { + Log.outError(LogFilter.Spells, "Spell.EffectTeleportUnits - spellId {0} attempted to teleport creature to a different map.", m_spellInfo.Id); + return; + } + + // post effects for TARGET_DEST_DB + switch (m_spellInfo.Id) + { + // Dimensional Ripper - Everlook + case 23442: + { + int r = RandomHelper.IRand(0, 119); + if (r >= 70) // 7/12 success + { + if (r < 100) // 4/12 evil twin + m_caster.CastSpell(m_caster, 23445, true); + else // 1/12 fire + m_caster.CastSpell(m_caster, 23449, true); + } + return; + } + // Ultrasafe Transporter: Toshley's Station + case 36941: + { + if (RandomHelper.randChance(50)) // 50% success + { + int rand_eff = RandomHelper.IRand(1, 7); + switch (rand_eff) + { + case 1: + // soul split - evil + m_caster.CastSpell(m_caster, 36900, true); + break; + case 2: + // soul split - good + m_caster.CastSpell(m_caster, 36901, true); + break; + case 3: + // Increase the size + m_caster.CastSpell(m_caster, 36895, true); + break; + case 4: + // Decrease the size + m_caster.CastSpell(m_caster, 36893, true); + break; + case 5: + // Transform + { + if (m_caster.ToPlayer().GetTeam() == Team.Alliance) + m_caster.CastSpell(m_caster, 36897, true); + else + m_caster.CastSpell(m_caster, 36899, true); + break; + } + case 6: + // chicken + m_caster.CastSpell(m_caster, 36940, true); + break; + case 7: + // evil twin + m_caster.CastSpell(m_caster, 23445, true); + break; + } + } + return; + } + // Dimensional Ripper - Area 52 + case 36890: + { + if (RandomHelper.randChance(50)) // 50% success + { + int rand_eff = RandomHelper.IRand(1, 4); + switch (rand_eff) + { + case 1: + // soul split - evil + m_caster.CastSpell(m_caster, 36900, true); + break; + case 2: + // soul split - good + m_caster.CastSpell(m_caster, 36901, true); + break; + case 3: + // Increase the size + m_caster.CastSpell(m_caster, 36895, true); + break; + case 4: + // Transform + { + if (m_caster.ToPlayer().GetTeam() == Team.Alliance) + m_caster.CastSpell(m_caster, 36897, true); + else + m_caster.CastSpell(m_caster, 36899, true); + break; + } + } + } + return; + } + } + } + + [SpellEffectHandler(SpellEffectName.ApplyAura)] + void EffectApplyAura(uint effIndex) + { + if (effectHandleMode != SpellEffectHandleMode.HitTarget) + return; + + if (m_spellAura == null || unitTarget == null) + return; + + Contract.Assert(unitTarget == m_spellAura.GetOwner()); + m_spellAura._ApplyEffectForTargets(effIndex); + } + + [SpellEffectHandler(SpellEffectName.ApplyAreaAuraEnemy)] + [SpellEffectHandler(SpellEffectName.ApplyAreaAuraFriend)] + [SpellEffectHandler(SpellEffectName.ApplyAreaAuraOwner)] + [SpellEffectHandler(SpellEffectName.ApplyAreaAuraParty)] + [SpellEffectHandler(SpellEffectName.ApplyAreaAuraPet)] + [SpellEffectHandler(SpellEffectName.ApplyAreaAuraRaid)] + void EffectApplyAreaAura(uint effIndex) + { + if (effectHandleMode != SpellEffectHandleMode.HitTarget) + return; + + if (m_spellAura == null || unitTarget == null) + return; + Contract.Assert(unitTarget == m_spellAura.GetOwner()); + m_spellAura._ApplyEffectForTargets(effIndex); + } + + [SpellEffectHandler(SpellEffectName.UnlearnSpecialization)] + void EffectUnlearnSpecialization(uint effIndex) + { + if (effectHandleMode != SpellEffectHandleMode.HitTarget) + return; + + if (unitTarget == null || !unitTarget.IsTypeId(TypeId.Player)) + return; + + Player player = unitTarget.ToPlayer(); + uint spellToUnlearn = effectInfo.TriggerSpell; + + player.RemoveSpell(spellToUnlearn); + + Log.outDebug(LogFilter.Spells, "Spell: Player {0} has unlearned spell {1} from NpcGUID: {2}", player.GetGUID().ToString(), spellToUnlearn, m_caster.GetGUID().ToString()); + } + + [SpellEffectHandler(SpellEffectName.PowerDrain)] + void EffectPowerDrain(uint effIndex) + { + if (effectHandleMode != SpellEffectHandleMode.HitTarget) + return; + + if (effectInfo.MiscValue < 0 || effectInfo.MiscValue >= (byte)PowerType.Max) + return; + + PowerType powerType = (PowerType)effectInfo.MiscValue; + + if (unitTarget == null || !unitTarget.IsAlive() || unitTarget.getPowerType() != powerType || damage < 0) + return; + + // add spell damage bonus + uint bonus = m_caster.SpellDamageBonusDone(unitTarget, m_spellInfo, (uint)damage, DamageEffectType.SpellDirect, effectInfo); + damage = (int)(bonus + (bonus * variance)); + damage = (int)unitTarget.SpellDamageBonusTaken(m_caster, m_spellInfo, (uint)damage, DamageEffectType.SpellDirect, effectInfo); + + int newDamage = -(unitTarget.ModifyPower(powerType, -damage)); + + float gainMultiplier = 0.0f; + + // Don't restore from self drain + if (m_caster != unitTarget) + { + gainMultiplier = effectInfo.CalcValueMultiplier(m_originalCaster, this); + + int gain = (int)(newDamage * gainMultiplier); + + m_caster.EnergizeBySpell(m_caster, m_spellInfo.Id, gain, powerType); + } + ExecuteLogEffectTakeTargetPower(effIndex, unitTarget, powerType, (uint)newDamage, gainMultiplier); + } + + [SpellEffectHandler(SpellEffectName.SendEvent)] + void EffectSendEvent(uint effIndex) + { + // we do not handle a flag dropping or clicking on flag in Battlegroundby sendevent system + if (effectHandleMode != SpellEffectHandleMode.HitTarget + && effectHandleMode != SpellEffectHandleMode.Hit) + return; + + WorldObject target = null; + + // call events for object target if present + if (effectHandleMode == SpellEffectHandleMode.HitTarget) + { + if (unitTarget != null) + target = unitTarget; + else if (gameObjTarget != null) + target = gameObjTarget; + } + else // if (effectHandleMode == SpellEffectHandleMode.Hit) + { + // let's prevent executing effect handler twice in case when spell effect is capable of targeting an object + // this check was requested by scripters, but it has some downsides: + // now it's impossible to script (using sEventScripts) a cast which misses all targets + // or to have an ability to script the moment spell hits dest (in a case when there are object targets present) + if (effectInfo.GetProvidedTargetMask().HasAnyFlag(SpellCastTargetFlags.UnitMask | SpellCastTargetFlags.GameobjectMask)) + return; + // some spells have no target entries in dbc and they use focus target + if (focusObject != null) + target = focusObject; + // @todo there should be a possibility to pass dest target to event script + } + + Log.outDebug(LogFilter.Spells, "Spell ScriptStart {0} for spellid {1} in EffectSendEvent ", effectInfo.MiscValue, m_spellInfo.Id); + + ZoneScript zoneScript = m_caster.GetZoneScript(); + InstanceScript instanceScript = m_caster.GetInstanceScript(); + if (zoneScript != null) + zoneScript.ProcessEvent(target, (uint)effectInfo.MiscValue); + else if (instanceScript != null) // needed in case Player is the caster + instanceScript.ProcessEvent(target, (uint)effectInfo.MiscValue); + + m_caster.GetMap().ScriptsStart(ScriptsType.Event, (uint)effectInfo.MiscValue, m_caster, target); + } + + [SpellEffectHandler(SpellEffectName.PowerBurn)] + void EffectPowerBurn(uint effIndex) + { + if (effectHandleMode != SpellEffectHandleMode.HitTarget) + return; + + if (effectInfo.MiscValue < 0 || effectInfo.MiscValue >= (int)PowerType.Max) + return; + + PowerType powerType = (PowerType)effectInfo.MiscValue; + + if (unitTarget == null || !unitTarget.IsAlive() || unitTarget.getPowerType() != powerType || damage < 0) + return; + + // burn x% of target's mana, up to maximum of 2x% of caster's mana (Mana Burn) + if (m_spellInfo.Id == 8129) + { + int maxDamage = MathFunctions.CalculatePct(m_caster.GetMaxPower(powerType), damage * 2); + damage = MathFunctions.CalculatePct(unitTarget.GetMaxPower(powerType), damage); + damage = Math.Min(damage, maxDamage); + } + + int newDamage = -(unitTarget.ModifyPower(powerType, -damage)); + + // NO - Not a typo - EffectPowerBurn uses effect value multiplier - not effect damage multiplier + float dmgMultiplier = effectInfo.CalcValueMultiplier(m_originalCaster, this); + + // add log data before multiplication (need power amount, not damage) + ExecuteLogEffectTakeTargetPower(effIndex, unitTarget, powerType, (uint)newDamage, 0.0f); + + newDamage = (int)(newDamage * dmgMultiplier); + + m_damage += newDamage; + } + + [SpellEffectHandler(SpellEffectName.Heal)] + void EffectHeal(uint effIndex) + { + if (effectHandleMode != SpellEffectHandleMode.LaunchTarget) + return; + + if (unitTarget != null && unitTarget.IsAlive() && damage >= 0) + { + // Try to get original caster + Unit caster = !m_originalCasterGUID.IsEmpty() ? m_originalCaster : m_caster; + + // Skip if m_originalCaster not available + if (caster == null) + return; + + int addhealth = damage; + + // Vessel of the Naaru (Vial of the Sunwell trinket) + if (m_spellInfo.Id == 45064) + { + // Amount of heal - depends from stacked Holy Energy + int damageAmount = 0; + AuraEffect aurEff = m_caster.GetAuraEffect(45062, 0); + if (aurEff != null) + { + damageAmount += aurEff.GetAmount(); + m_caster.RemoveAurasDueToSpell(45062); + } + + addhealth += damageAmount; + } + // Runic Healing Injector (heal increased by 25% for engineers - 3.2.0 patch change) + else if (m_spellInfo.Id == 67489) + { + Player player = m_caster.ToPlayer(); + if (player != null) + if (player.HasSkill(SkillType.Engineering)) + MathFunctions.AddPct(ref addhealth, 25); + } + // Swiftmend - consumes Regrowth or Rejuvenation + else if (m_spellInfo.TargetAuraState == AuraStateType.Swiftmend && unitTarget.HasAuraState(AuraStateType.Swiftmend, m_spellInfo, m_caster)) + { + var RejorRegr = unitTarget.GetAuraEffectsByType(AuraType.PeriodicHeal); + // find most short by duration + AuraEffect targetAura = null; + foreach (var eff in RejorRegr) + { + if (eff.GetSpellInfo().SpellFamilyName == SpellFamilyNames.Druid + && eff.GetSpellInfo().SpellFamilyFlags[0].HasAnyFlag(0x50u)) + { + if (targetAura == null || eff.GetBase().GetDuration() < targetAura.GetBase().GetDuration()) + targetAura = eff; + } + } + + if (targetAura == null) + { + Log.outError(LogFilter.Spells, "Target(GUID: {0}) has aurastate AURA_STATE_SWIFTMEND but no matching aura.", unitTarget.GetGUID()); + return; + } + + int tickheal = targetAura.GetAmount(); + Unit auraCaster = targetAura.GetCaster(); + if (auraCaster != null) + tickheal = (int)auraCaster.SpellHealingBonusDone(unitTarget, targetAura.GetSpellInfo(), (uint)tickheal, DamageEffectType.DOT, effectInfo); + //It is said that talent bonus should not be included + + int tickcount = 0; + // Rejuvenation + if (targetAura.GetSpellInfo().SpellFamilyFlags[0].HasAnyFlag(0x10u)) + tickcount = 4; + // Regrowth + else + tickcount = 6; + + addhealth += tickheal * tickcount; + + // Glyph of Swiftmend + if (!caster.HasAura(54824)) + unitTarget.RemoveAura(targetAura.GetId(), targetAura.GetCasterGUID()); + } + // Death Pact - return pct of max health to caster + else if (m_spellInfo.SpellFamilyName == SpellFamilyNames.Deathknight && m_spellInfo.SpellFamilyFlags[0].HasAnyFlag(0x00080000u)) + addhealth = (int)caster.SpellHealingBonusDone(unitTarget, m_spellInfo, (uint)caster.CountPctFromMaxHealth(damage), DamageEffectType.Heal, effectInfo); + else + { + addhealth = (int)caster.SpellHealingBonusDone(unitTarget, m_spellInfo, (uint)addhealth, DamageEffectType.Heal, effectInfo); + uint bonus = caster.SpellHealingBonusDone(unitTarget, m_spellInfo, (uint)addhealth, DamageEffectType.Heal, effectInfo); + damage = (int)(bonus + (bonus * variance)); + } + + addhealth = (int)unitTarget.SpellHealingBonusTaken(caster, m_spellInfo, (uint)addhealth, DamageEffectType.Heal, effectInfo); + + // Remove Grievious bite if fully healed + if (unitTarget.HasAura(48920) && ((uint)(unitTarget.GetHealth() + (ulong)addhealth) >= unitTarget.GetMaxHealth())) + unitTarget.RemoveAura(48920); + + m_damage -= addhealth; + } + } + + [SpellEffectHandler(SpellEffectName.HealPct)] + void EffectHealPct(uint effIndex) + { + if (effectHandleMode != SpellEffectHandleMode.HitTarget) + return; + + if (unitTarget == null || !unitTarget.IsAlive() || damage < 0) + return; + + // Skip if m_originalCaster not available + if (m_originalCaster == null) + return; + + uint heal = m_originalCaster.SpellHealingBonusDone(unitTarget, m_spellInfo, (uint)unitTarget.CountPctFromMaxHealth(damage), DamageEffectType.Heal, effectInfo); + heal = unitTarget.SpellHealingBonusTaken(m_originalCaster, m_spellInfo, heal, DamageEffectType.Heal, effectInfo); + + m_healing += (int)heal; + } + + [SpellEffectHandler(SpellEffectName.HealMechanical)] + void EffectHealMechanical(uint effIndex) + { + if (effectHandleMode != SpellEffectHandleMode.HitTarget) + return; + + if (unitTarget == null || !unitTarget.IsAlive() || damage < 0) + return; + + // Skip if m_originalCaster not available + if (m_originalCaster == null) + return; + + uint heal = m_originalCaster.SpellHealingBonusDone(unitTarget, m_spellInfo, (uint)damage, DamageEffectType.Heal, effectInfo); + heal += (uint)(heal * variance); + + m_healing += (int)unitTarget.SpellHealingBonusTaken(m_originalCaster, m_spellInfo, heal, DamageEffectType.Heal, effectInfo); + } + + [SpellEffectHandler(SpellEffectName.HealthLeech)] + void EffectHealthLeech(uint effIndex) + { + if (effectHandleMode != SpellEffectHandleMode.HitTarget) + return; + + if (unitTarget == null || !unitTarget.IsAlive() || damage < 0) + return; + + uint bonus = m_caster.SpellDamageBonusDone(unitTarget, m_spellInfo, (uint)damage, DamageEffectType.SpellDirect, effectInfo); + damage = (int)(bonus + (bonus * variance)); + damage = (int)unitTarget.SpellDamageBonusTaken(m_caster, m_spellInfo, (uint)damage, DamageEffectType.SpellDirect, effectInfo); + + Log.outDebug(LogFilter.Spells, "HealthLeech :{0}", damage); + + float healMultiplier = effectInfo.CalcValueMultiplier(m_originalCaster, this); + + m_damage += damage; + // get max possible damage, don't count overkill for heal + uint healthGain = (uint)(-unitTarget.GetHealthGain(-damage) * healMultiplier); + + if (m_caster.IsAlive()) + { + healthGain = m_caster.SpellHealingBonusDone(m_caster, m_spellInfo, healthGain, DamageEffectType.Heal, effectInfo); + healthGain = m_caster.SpellHealingBonusTaken(m_caster, m_spellInfo, healthGain, DamageEffectType.Heal, effectInfo); + + m_caster.HealBySpell(m_caster, m_spellInfo, healthGain); + } + } + + public void DoCreateItem(uint i, uint itemtype, byte context = 0, List bonusListIds = null) + { + if (unitTarget == null || !unitTarget.IsTypeId(TypeId.Player)) + return; + + Player player = unitTarget.ToPlayer(); + + uint newitemid = itemtype; + ItemTemplate pProto = Global.ObjectMgr.GetItemTemplate(newitemid); + if (pProto == null) + { + player.SendEquipError(InventoryResult.ItemNotFound); + return; + } + + // bg reward have some special in code work + BattlegroundTypeId bgType = 0; + switch ((BattlegroundMarks)m_spellInfo.Id) + { + case BattlegroundMarks.SpellAvMarkWinner: + case BattlegroundMarks.SpellAvMarkLoser: + bgType = BattlegroundTypeId.AV; + break; + case BattlegroundMarks.SpellWsMarkWinner: + case BattlegroundMarks.SpellWsMarkLoser: + bgType = BattlegroundTypeId.WS; + break; + case BattlegroundMarks.SpellAbMarkWinner: + case BattlegroundMarks.SpellAbMarkLoser: + bgType = BattlegroundTypeId.AB; + break; + default: + break; + } + + uint num_to_add = (uint)damage; + + if (num_to_add < 1) + num_to_add = 1; + if (num_to_add > pProto.GetMaxStackSize()) + num_to_add = pProto.GetMaxStackSize(); + + // this is bad, should be done using spell_loot_template (and conditions) + + // the chance of getting a perfect result + float perfectCreateChance = 0.0f; + // the resulting perfect item if successful + uint perfectItemType = itemtype; + // get perfection capability and chance + if (SkillPerfectItems.CanCreatePerfectItem(player, m_spellInfo.Id, ref perfectCreateChance, ref perfectItemType)) + if (RandomHelper.randChance(perfectCreateChance)) // if the roll succeeds... + newitemid = perfectItemType; // the perfect item replaces the regular one + + // init items_count to 1, since 1 item will be created regardless of specialization + int items_count = 1; + // the chance to create additional items + float additionalCreateChance = 0.0f; + // the maximum number of created additional items + byte additionalMaxNum = 0; + // get the chance and maximum number for creating extra items + if (SkillExtraItems.CanCreateExtraItems(player, m_spellInfo.Id, ref additionalCreateChance, ref additionalMaxNum)) + { + // roll with this chance till we roll not to create or we create the max num + while (RandomHelper.randChance(additionalCreateChance) && items_count <= additionalMaxNum) + ++items_count; + } + + // really will be created more items + num_to_add *= (uint)items_count; + + // can the player store the new item? + List dest = new List(); + uint no_space = 0; + InventoryResult msg = player.CanStoreNewItem(ItemConst.NullBag, ItemConst.NullSlot, dest, newitemid, num_to_add, out no_space); + if (msg != InventoryResult.Ok) + { + // convert to possible store amount + if (msg == InventoryResult.InvFull || msg == InventoryResult.ItemMaxCount) + num_to_add -= no_space; + else + { + // if not created by another reason from full inventory or unique items amount limitation + player.SendEquipError(msg, null, null, newitemid); + return; + } + } + + if (num_to_add != 0) + { + // create the new item and store it + Item pItem = player.StoreNewItem(dest, newitemid, true, ItemEnchantment.GenerateItemRandomPropertyId(newitemid), null, context, bonusListIds); + + // was it successful? return error if not + if (pItem == null) + { + player.SendEquipError(InventoryResult.ItemNotFound); + return; + } + + // set the "Crafted by ..." property of the item + if (pItem.GetTemplate().GetClass() != ItemClass.Consumable && pItem.GetTemplate().GetClass() != ItemClass.Quest && newitemid != 6265 && newitemid != 6948) + pItem.SetGuidValue(ItemFields.Creator, player.GetGUID()); + + // send info to the client + player.SendNewItem(pItem, num_to_add, true, bgType == 0); + + if (pItem.GetQuality() > ItemQuality.Epic || (pItem.GetQuality() == ItemQuality.Epic && pItem.GetItemLevel(player) >= GuildConst.MinNewsItemLevel)) + { + Guild guild = player.GetGuild(); + if (guild != null) + guild.AddGuildNews(GuildNews.ItemCrafted, player.GetGUID(), 0, pProto.GetId()); + } + + // we succeeded in creating at least one item, so a levelup is possible + if (bgType == 0) + player.UpdateCraftSkill(m_spellInfo.Id); + } + } + + [SpellEffectHandler(SpellEffectName.CreateItem)] + void EffectCreateItem(uint effIndex) + { + if (effectHandleMode != SpellEffectHandleMode.HitTarget) + return; + + DoCreateItem(effIndex, effectInfo.ItemType); + ExecuteLogEffectCreateItem(effIndex, effectInfo.ItemType); + } + + [SpellEffectHandler(SpellEffectName.CreateLoot)] + void EffectCreateItem2(uint effIndex) + { + if (effectHandleMode != SpellEffectHandleMode.HitTarget) + return; + + if (unitTarget == null || !unitTarget.IsTypeId(TypeId.Player)) + return; + + Player player = unitTarget.ToPlayer(); + + uint item_id = effectInfo.ItemType; + + if (item_id != 0) + DoCreateItem(effIndex, item_id); + + // special case: fake item replaced by generate using spell_loot_template + if (m_spellInfo.IsLootCrafting()) + { + if (item_id != 0) + { + if (!player.HasItemCount(item_id)) + return; + + // remove reagent + uint count = 1; + player.DestroyItemCount(item_id, count, true); + + // create some random items + player.AutoStoreLoot(m_spellInfo.Id, LootStorage.Spell); + } + else + player.AutoStoreLoot(m_spellInfo.Id, LootStorage.Spell); // create some random items + + player.UpdateCraftSkill(m_spellInfo.Id); + } + // @todo ExecuteLogEffectCreateItem(i, GetEffect(i].ItemType); + } + + [SpellEffectHandler(SpellEffectName.CreateRandomItem)] + void EffectCreateRandomItem(uint effIndex) + { + if (effectHandleMode != SpellEffectHandleMode.HitTarget) + return; + + if (unitTarget == null || !unitTarget.IsTypeId(TypeId.Player)) + return; + Player player = unitTarget.ToPlayer(); + + // create some random items + player.AutoStoreLoot(m_spellInfo.Id, LootStorage.Spell); + // @todo ExecuteLogEffectCreateItem(i, GetEffect(i].ItemType); + } + + [SpellEffectHandler(SpellEffectName.PersistentAreaAura)] + void EffectPersistentAA(uint effIndex) + { + if (effectHandleMode != SpellEffectHandleMode.Hit) + return; + + if (m_spellAura == null) + { + Unit caster = m_caster.GetEntry() == SharedConst.WorldTrigger ? m_originalCaster : m_caster; + float radius = effectInfo.CalcRadius(caster); + + // Caster not in world, might be spell triggered from aura removal + if (!caster.IsInWorld) + return; + DynamicObject dynObj = new DynamicObject(false); + if (!dynObj.CreateDynamicObject(caster.GetMap().GenerateLowGuid(HighGuid.DynamicObject), caster, m_spellInfo, destTarget, radius, DynamicObjectType.AreaSpell, m_SpellVisual)) + return; + + Aura aura = Aura.TryCreate(m_spellInfo, m_castId, SpellConst.MaxEffectMask, dynObj, caster, m_spellValue.EffectBasePoints, null, ObjectGuid.Empty, m_castItemLevel); + if (aura != null) + { + m_spellAura = aura; + m_spellAura._RegisterForTargets(); + } + else + return; + } + + Contract.Assert(m_spellAura.GetDynobjOwner()); + m_spellAura._ApplyEffectForTargets(effIndex); + } + + [SpellEffectHandler(SpellEffectName.Energize)] + void EffectEnergize(uint effIndex) + { + if (effectHandleMode != SpellEffectHandleMode.HitTarget) + return; + + if (unitTarget == null) + return; + + if (!unitTarget.IsAlive()) + return; + + if (effectInfo.MiscValue < 0 || effectInfo.MiscValue >= (byte)PowerType.Max) + return; + + PowerType power = (PowerType)effectInfo.MiscValue; + if (unitTarget.GetMaxPower(power) == 0) + return; + + // Some level depends spells + int level_multiplier = 0; + int level_diff = 0; + switch (m_spellInfo.Id) + { + case 9512: // Restore Energy + level_diff = (int)m_caster.getLevel() - 40; + level_multiplier = 2; + break; + case 24571: // Blood Fury + level_diff = (int)m_caster.getLevel() - 60; + level_multiplier = 10; + break; + case 24532: // Burst of Energy + level_diff = (int)m_caster.getLevel() - 60; + level_multiplier = 4; + break; + case 31930: // Judgements of the Wise + case 63375: // Primal Wisdom + case 68082: // Glyph of Seal of Command + damage = (int)MathFunctions.CalculatePct(unitTarget.GetCreateMana(), damage); + break; + case 67490: // Runic Mana Injector (mana gain increased by 25% for engineers - 3.2.0 patch change) + { + Player player = m_caster.ToPlayer(); + if (player != null) + if (player.HasSkill(SkillType.Engineering)) + MathFunctions.AddPct(ref damage, 25); + break; + } + default: + break; + } + + if (level_diff > 0) + damage -= level_multiplier * level_diff; + + if (damage < 0 && power != PowerType.LunarPower) + return; + + m_caster.EnergizeBySpell(unitTarget, m_spellInfo.Id, damage, power); + + // Mad Alchemist's Potion + if (m_spellInfo.Id == 45051) + { + // find elixirs on target + bool guardianFound = false; + bool battleFound = false; + foreach (var app in unitTarget.GetAppliedAuras()) + { + uint spell_id = app.Value.GetBase().GetId(); + if (!guardianFound) + if (Global.SpellMgr.IsSpellMemberOfSpellGroup(spell_id, SpellGroup.ElixirGuardian)) + guardianFound = true; + if (!battleFound) + if (Global.SpellMgr.IsSpellMemberOfSpellGroup(spell_id, SpellGroup.ElixirBattle)) + battleFound = true; + if (battleFound && guardianFound) + break; + } + + // get all available elixirs by mask and spell level + List avalibleElixirs = new List(); + if (!guardianFound) + Global.SpellMgr.GetSetOfSpellsInSpellGroup(SpellGroup.ElixirGuardian, out avalibleElixirs); + if (!battleFound) + Global.SpellMgr.GetSetOfSpellsInSpellGroup(SpellGroup.ElixirBattle, out avalibleElixirs); + foreach (int spellId in avalibleElixirs) + { + SpellInfo spellInfo = Global.SpellMgr.GetSpellInfo((uint)spellId); + if (spellInfo.SpellLevel < m_spellInfo.SpellLevel || spellInfo.SpellLevel > unitTarget.getLevel()) + avalibleElixirs.Remove(spellId); + else if (Global.SpellMgr.IsSpellMemberOfSpellGroup((uint)spellId, SpellGroup.ElixirShattrath)) + avalibleElixirs.Remove(spellId); + else if (Global.SpellMgr.IsSpellMemberOfSpellGroup((uint)spellId, SpellGroup.ElixirUnstable)) + avalibleElixirs.Remove(spellId); + } + + if (!avalibleElixirs.Empty()) + { + // cast random elixir on target + m_caster.CastSpell(unitTarget, (uint)avalibleElixirs.PickRandom(), true, m_CastItem); + } + } + } + + [SpellEffectHandler(SpellEffectName.EnergizePct)] + void EffectEnergizePct(uint effIndex) + { + if (effectHandleMode != SpellEffectHandleMode.HitTarget) + return; + + if (unitTarget == null) + return; + if (!unitTarget.IsAlive()) + return; + + if (effectInfo.MiscValue < 0 || effectInfo.MiscValue >= (byte)PowerType.Max) + return; + + PowerType power = (PowerType)effectInfo.MiscValue; + uint maxPower = (uint)unitTarget.GetMaxPower(power); + if (maxPower == 0) + return; + + int gain = (int)MathFunctions.CalculatePct(maxPower, damage); + m_caster.EnergizeBySpell(unitTarget, m_spellInfo.Id, gain, power); + } + + void SendLoot(ObjectGuid guid, LootType loottype) + { + Player player = m_caster.ToPlayer(); + if (player == null) + return; + + if (gameObjTarget != null) + { + // Players shouldn't be able to loot gameobjects that are currently despawned + if (!gameObjTarget.isSpawned() && !player.IsGameMaster()) + { + Log.outError(LogFilter.Spells, "Possible hacking attempt: Player {0} [{1}] tried to loot a gameobject [{2}] which is on respawn time without being in GM mode!", + player.GetName(), player.GetGUID().ToString(), gameObjTarget.GetGUID().ToString()); + return; + } + // special case, already has GossipHello inside so return and avoid calling twice + if (gameObjTarget.GetGoType() == GameObjectTypes.Goober) + { + gameObjTarget.Use(m_caster); + return; + } + + if (Global.ScriptMgr.OnGossipHello(player, gameObjTarget)) + return; + + if (gameObjTarget.GetAI().GossipHello(player, true)) + return; + + switch (gameObjTarget.GetGoType()) + { + case GameObjectTypes.Door: + case GameObjectTypes.Button: + gameObjTarget.UseDoorOrButton(0, false, player); + return; + + case GameObjectTypes.QuestGiver: + player.PrepareGossipMenu(gameObjTarget, gameObjTarget.GetGoInfo().QuestGiver.gossipID, true); + player.SendPreparedGossip(gameObjTarget); + return; + + case GameObjectTypes.SpellFocus: + // triggering linked GO + uint trapEntry = gameObjTarget.GetGoInfo().SpellFocus.linkedTrap; + if (trapEntry != 0) + gameObjTarget.TriggeringLinkedGameObject(trapEntry, m_caster); + return; + case GameObjectTypes.Chest: + // @todo possible must be moved to loot release (in different from linked triggering) + if (gameObjTarget.GetGoInfo().Chest.triggeredEvent != 0) + { + Log.outDebug(LogFilter.Spells, "Chest ScriptStart id {0} for GO {1}", gameObjTarget.GetGoInfo().Chest.triggeredEvent, gameObjTarget.GetSpawnId()); + player.GetMap().ScriptsStart(ScriptsType.Event, gameObjTarget.GetGoInfo().Chest.triggeredEvent, player, gameObjTarget); + } + + // triggering linked GO + uint _trapEntry = gameObjTarget.GetGoInfo().Chest.linkedTrap; + if (_trapEntry != 0) + gameObjTarget.TriggeringLinkedGameObject(_trapEntry, m_caster); + break; + // Don't return, let loots been taken + default: + break; + } + } + + // Send loot + player.SendLoot(guid, loottype); + } + + [SpellEffectHandler(SpellEffectName.OpenLock)] + void EffectOpenLock(uint effIndex) + { + if (effectHandleMode != SpellEffectHandleMode.HitTarget) + return; + + if (!m_caster.IsTypeId(TypeId.Player)) + { + Log.outDebug(LogFilter.Spells, "WORLD: Open Lock - No Player Caster!"); + return; + } + + Player player = m_caster.ToPlayer(); + + uint lockId = 0; + ObjectGuid guid = ObjectGuid.Empty; + + // Get lockId + if (gameObjTarget != null) + { + GameObjectTemplate goInfo = gameObjTarget.GetGoInfo(); + // Arathi Basin banner opening. // @todo Verify correctness of this check + if ((goInfo.type == GameObjectTypes.Button && goInfo.Button.noDamageImmune != 0) || + (goInfo.type == GameObjectTypes.Goober && goInfo.Goober.requireLOS != 0)) + { + //CanUseBattlegroundObject() already called in CheckCast() + // in Battlegroundcheck + Battleground bg = player.GetBattleground(); + if (bg) + { + bg.EventPlayerClickedOnFlag(player, gameObjTarget); + return; + } + } + else if (goInfo.type == GameObjectTypes.FlagStand) + { + //CanUseBattlegroundObject() already called in CheckCast() + // in Battlegroundcheck + Battleground bg = player.GetBattleground(); + if (bg) + { + if (bg.GetTypeID(true) == BattlegroundTypeId.EY) + bg.EventPlayerClickedOnFlag(player, gameObjTarget); + return; + } + } + else if (m_spellInfo.Id == 1842 && gameObjTarget.GetGoInfo().type == GameObjectTypes.Trap && gameObjTarget.GetOwner() != null) + { + gameObjTarget.SetLootState(LootState.JustDeactivated); + return; + } + // @todo Add script for spell 41920 - Filling, becouse server it freze when use this spell + // handle outdoor pvp object opening, return true if go was registered for handling + // these objects must have been spawned by outdoorpvp! + else if (gameObjTarget.GetGoInfo().type == GameObjectTypes.Goober && Global.OutdoorPvPMgr.HandleOpenGo(player, gameObjTarget)) + return; + lockId = goInfo.GetLockId(); + guid = gameObjTarget.GetGUID(); + } + else if (itemTarget != null) + { + lockId = itemTarget.GetTemplate().GetLockID(); + guid = itemTarget.GetGUID(); + } + else + { + Log.outDebug(LogFilter.Spells, "WORLD: Open Lock - No GameObject/Item Target!"); + return; + } + + SkillType skillId = SkillType.None; + int reqSkillValue = 0; + int skillValue = 0; + + SpellCastResult res = CanOpenLock(effIndex, lockId, skillId, ref reqSkillValue, ref skillValue); + if (res != SpellCastResult.SpellCastOk) + { + SendCastResult(res); + return; + } + + if (gameObjTarget != null) + SendLoot(guid, LootType.Skinning); + else if (itemTarget != null) + itemTarget.SetFlag(ItemFields.Flags, ItemFieldFlags.Unlocked); + + // not allow use skill grow at item base open + if (m_CastItem == null && skillId != SkillType.None) + { + // update skill if really known + uint pureSkillValue = player.GetPureSkillValue(skillId); + if (pureSkillValue != 0) + { + if (gameObjTarget != null) + { + // Allow one skill-up until respawned + if (!gameObjTarget.IsInSkillupList(player.GetGUID()) && + player.UpdateGatherSkill(skillId, pureSkillValue, (uint)reqSkillValue)) + gameObjTarget.AddToSkillupList(player.GetGUID()); + } + else if (itemTarget != null) + { + // Do one skill-up + player.UpdateGatherSkill(skillId, pureSkillValue, (uint)reqSkillValue); + } + } + } + ExecuteLogEffectOpenLock(effIndex, gameObjTarget != null ? gameObjTarget : (WorldObject)itemTarget); + } + + [SpellEffectHandler(SpellEffectName.SummonChangeItem)] + void EffectSummonChangeItem(uint effIndex) + { + if (effectHandleMode != SpellEffectHandleMode.Hit) + return; + + if (!m_caster.IsTypeId(TypeId.Player)) + return; + + Player player = m_caster.ToPlayer(); + + // applied only to using item + if (m_CastItem == null) + return; + + // ... only to item in own inventory/bank/equip_slot + if (m_CastItem.GetOwnerGUID() != player.GetGUID()) + return; + + uint newitemid = effectInfo.ItemType; + if (newitemid == 0) + return; + + ushort pos = m_CastItem.GetPos(); + + Item pNewItem = Item.CreateItem(newitemid, 1, player); + if (pNewItem == null) + return; + + for (var j = EnchantmentSlot.Perm; j <= EnchantmentSlot.Temp; ++j) + if (m_CastItem.GetEnchantmentId(j) != 0) + pNewItem.SetEnchantment(j, m_CastItem.GetEnchantmentId(j), m_CastItem.GetEnchantmentDuration(j), m_CastItem.GetEnchantmentCharges(j)); + + if (m_CastItem.GetUInt32Value(ItemFields.Durability) < m_CastItem.GetUInt32Value(ItemFields.MaxDurability)) + { + double lossPercent = 1 - m_CastItem.GetUInt32Value(ItemFields.Durability) / (double)m_CastItem.GetUInt32Value(ItemFields.MaxDurability); + player.DurabilityLoss(pNewItem, lossPercent); + } + + if (player.IsInventoryPos(pos)) + { + List dest = new List(); + InventoryResult msg = player.CanStoreItem(m_CastItem.GetBagSlot(), m_CastItem.GetSlot(), dest, pNewItem, true); + if (msg == InventoryResult.Ok) + { + player.DestroyItem(m_CastItem.GetBagSlot(), m_CastItem.GetSlot(), true); + + // prevent crash at access and unexpected charges counting with item update queue corrupt + if (m_CastItem == m_targets.GetItemTarget()) + m_targets.SetItemTarget(null); + + m_CastItem = null; + m_castItemGUID.Clear(); + m_castItemEntry = 0; + m_castItemLevel = -1; + + player.StoreItem(dest, pNewItem, true); + return; + } + } + else if (Player.IsBankPos(pos)) + { + List dest = new List(); + InventoryResult msg = player.CanBankItem(m_CastItem.GetBagSlot(), m_CastItem.GetSlot(), dest, pNewItem, true); + if (msg == InventoryResult.Ok) + { + player.DestroyItem(m_CastItem.GetBagSlot(), m_CastItem.GetSlot(), true); + + // prevent crash at access and unexpected charges counting with item update queue corrupt + if (m_CastItem == m_targets.GetItemTarget()) + m_targets.SetItemTarget(null); + + m_CastItem = null; + m_castItemGUID.Clear(); + m_castItemEntry = 0; + m_castItemLevel = -1; + + player.BankItem(dest, pNewItem, true); + return; + } + } + else if (Player.IsEquipmentPos(pos)) + { + ushort dest; + + player.DestroyItem(m_CastItem.GetBagSlot(), m_CastItem.GetSlot(), true); + + InventoryResult msg = player.CanEquipItem(m_CastItem.GetSlot(), out dest, pNewItem, true); + + if (msg == InventoryResult.Ok || msg == InventoryResult.ClientLockedOut) + { + if (msg == InventoryResult.ClientLockedOut) + dest = EquipmentSlot.MainHand; + + // prevent crash at access and unexpected charges counting with item update queue corrupt + if (m_CastItem == m_targets.GetItemTarget()) + m_targets.SetItemTarget(null); + + m_CastItem = null; + m_castItemGUID.Clear(); + m_castItemEntry = 0; + m_castItemLevel = -1; + + player.EquipItem(dest, pNewItem, true); + player.AutoUnequipOffhandIfNeed(); + return; + } + } + } + + [SpellEffectHandler(SpellEffectName.Proficiency)] + void EffectProficiency(uint effIndex) + { + if (effectHandleMode != SpellEffectHandleMode.Hit) + return; + + if (!m_caster.IsTypeId(TypeId.Player)) + return; + Player p_target = m_caster.ToPlayer(); + + uint subClassMask = (uint)m_spellInfo.EquippedItemSubClassMask; + if (m_spellInfo.EquippedItemClass == ItemClass.Weapon && !Convert.ToBoolean(p_target.GetWeaponProficiency() & subClassMask)) + { + p_target.AddWeaponProficiency(subClassMask); + p_target.SendProficiency(ItemClass.Weapon, p_target.GetWeaponProficiency()); + } + if (m_spellInfo.EquippedItemClass == ItemClass.Armor && !Convert.ToBoolean(p_target.GetArmorProficiency() & subClassMask)) + { + p_target.AddArmorProficiency(subClassMask); + p_target.SendProficiency(ItemClass.Armor, p_target.GetArmorProficiency()); + } + } + + [SpellEffectHandler(SpellEffectName.Summon)] + void EffectSummonType(uint effIndex) + { + if (effectHandleMode != SpellEffectHandleMode.Hit) + return; + + uint entry = (uint)effectInfo.MiscValue; + if (entry == 0) + return; + + SummonPropertiesRecord properties = CliDB.SummonPropertiesStorage.LookupByKey(effectInfo.MiscValueB); + if (properties == null) + { + Log.outError(LogFilter.Spells, "EffectSummonType: Unhandled summon type {0}", effectInfo.MiscValueB); + return; + } + + if (m_originalCaster == null) + return; + + int duration = m_spellInfo.CalcDuration(m_originalCaster); + + TempSummon summon = null; + + // determine how many units should be summoned + uint numSummons; + + // some spells need to summon many units, for those spells number of summons is stored in effect value + // however so far noone found a generic check to find all of those (there's no related data in summonproperties.dbc + // and in spell attributes, possibly we need to add a table for those) + // so here's a list of MiscValueB values, which is currently most generic check + switch (effectInfo.MiscValueB) + { + case 64: + case 61: + case 1101: + case 66: + case 648: + case 2301: + case 1061: + case 1261: + case 629: + case 181: + case 715: + case 1562: + case 833: + case 1161: + case 713: + numSummons = (uint)(damage > 0 ? damage : 1); + break; + default: + numSummons = 1; + break; + } + + switch (properties.Category) + { + case SummonCategory.Wild: + case SummonCategory.Ally: + case SummonCategory.Unk: + if (Convert.ToBoolean(properties.Flags & 512)) + { + SummonGuardian(effIndex, entry, properties, numSummons); + break; + } + switch (properties.Type) + { + case SummonType.Pet: + case SummonType.Guardian: + case SummonType.Guardian2: + case SummonType.Minion: + SummonGuardian(effIndex, entry, properties, numSummons); + break; + // Summons a vehicle, but doesn't force anyone to enter it (see SUMMON_CATEGORY_VEHICLE) + case SummonType.Vehicle: + case SummonType.Vehicle2: + summon = m_caster.GetMap().SummonCreature(entry, destTarget, properties, (uint)duration, m_originalCaster, m_spellInfo.Id); + break; + case SummonType.LightWell: + case SummonType.Totem: + { + summon = m_caster.GetMap().SummonCreature(entry, destTarget, properties, (uint)duration, m_originalCaster, m_spellInfo.Id); + if (summon == null || !summon.IsTotem()) + return; + + // Mana Tide Totem + if (m_spellInfo.Id == 16190) + damage = (int)m_caster.CountPctFromMaxHealth(10); + + if (damage != 0) // if not spell info, DB values used + { + summon.SetMaxHealth((uint)damage); + summon.SetHealth((uint)damage); + } + break; + } + case SummonType.Minipet: + { + summon = m_caster.GetMap().SummonCreature(entry, destTarget, properties, (uint)duration, m_originalCaster, m_spellInfo.Id); + if (summon == null || !summon.HasUnitTypeMask(UnitTypeMask.Minion)) + return; + + summon.SelectLevel(); // some summoned creaters have different from 1 DB data for level/hp + summon.SetUInt64Value(UnitFields.NpcFlags, (ulong)summon.GetCreatureTemplate().Npcflag); + + summon.SetFlag(UnitFields.Flags, UnitFlags.ImmuneToPc | UnitFlags.ImmuneToNpc); + + summon.GetAI().EnterEvadeMode(); + break; + } + default: + { + float radius = effectInfo.CalcRadius(); + + TempSummonType summonType = (duration == 0) ? TempSummonType.DeadDespawn : TempSummonType.TimedDespawn; + + for (uint count = 0; count < numSummons; ++count) + { + Position pos = new Position(); + if (count == 0) + pos = destTarget; + else + // randomize position for multiple summons + m_caster.GetRandomPoint(destTarget, radius, out pos); + + summon = m_originalCaster.SummonCreature(entry, pos, summonType, (uint)duration); + if (summon == null) + continue; + + if (properties.Category == SummonCategory.Ally) + { + summon.SetOwnerGUID(m_originalCaster.GetGUID()); + summon.SetFaction(m_originalCaster.getFaction()); + summon.SetUInt32Value(UnitFields.CreatedBySpell, m_spellInfo.Id); + } + + ExecuteLogEffectSummonObject(effIndex, summon); + } + return; + } + }//switch + break; + case SummonCategory.Pet: + SummonGuardian(effIndex, entry, properties, numSummons); + break; + case SummonCategory.Puppet: + summon = m_caster.GetMap().SummonCreature(entry, destTarget, properties, (uint)duration, m_originalCaster, m_spellInfo.Id); + break; + case SummonCategory.Vehicle: + // Summoning spells (usually triggered by npc_spellclick) that spawn a vehicle and that cause the clicker + // to cast a ride vehicle spell on the summoned unit. + summon = m_originalCaster.GetMap().SummonCreature(entry, destTarget, properties, (uint)duration, m_caster, m_spellInfo.Id); + if (summon == null || !summon.IsVehicle()) + return; + + // The spell that this effect will trigger. It has SPELL_AURA_CONTROL_VEHICLE + uint spellId = SharedConst.VehicleSpellRideHardcoded; + SpellInfo spellInfo = Global.SpellMgr.GetSpellInfo((uint)effectInfo.CalcValue()); + if (spellInfo != null && spellInfo.HasAura(m_originalCaster.GetMap().GetDifficultyID(), AuraType.ControlVehicle)) + spellId = spellInfo.Id; + + // Hard coded enter vehicle spell + m_originalCaster.CastSpell(summon, spellId, true); + + uint faction = properties.Faction; + if (faction == 0) + faction = m_originalCaster.getFaction(); + + summon.SetFaction(faction); + break; + } + + if (summon != null) + { + summon.SetCreatorGUID(m_originalCaster.GetGUID()); + ExecuteLogEffectSummonObject(effIndex, summon); + } + } + + [SpellEffectHandler(SpellEffectName.LearnSpell)] + void EffectLearnSpell(uint effIndex) + { + if (effectHandleMode != SpellEffectHandleMode.HitTarget) + return; + + if (unitTarget == null) + return; + + if (!unitTarget.IsTypeId(TypeId.Player)) + { + if (unitTarget.IsPet()) + EffectLearnPetSpell(effIndex); + return; + } + + Player player = unitTarget.ToPlayer(); + + uint spellToLearn = (m_spellInfo.Id == 483 || m_spellInfo.Id == 55884) ? (uint)damage : effectInfo.TriggerSpell; + player.LearnSpell(spellToLearn, false); + + if (m_spellInfo.Id == 55884) + { + BattlePetMgr battlePetMgr = player.GetSession().GetBattlePetMgr(); + if (battlePetMgr != null) + { + foreach (var entry in CliDB.BattlePetSpeciesStorage.Values) + { + if (entry.SummonSpellID == spellToLearn) + { + battlePetMgr.AddPet(entry.Id, entry.CreatureID); + player.UpdateCriteria(CriteriaTypes.OwnBattlePetCount); + break; + } + } + } + } + + Log.outDebug(LogFilter.Spells, "Spell: Player {0} has learned spell {1} from NpcGUID={2}", player.GetGUID().ToString(), spellToLearn, m_caster.GetGUID().ToString()); + } + + [SpellEffectHandler(SpellEffectName.Dispel)] + void EffectDispel(uint effIndex) + { + if (effectHandleMode != SpellEffectHandleMode.HitTarget) + return; + + if (unitTarget == null) + return; + + // Create dispel mask by dispel type + uint dispel_type = (uint)effectInfo.MiscValue; + uint dispelMask = SpellInfo.GetDispelMask((DispelType)dispel_type); + + List dispel_list = unitTarget.GetDispellableAuraList(m_caster, dispelMask); + if (dispel_list.Empty()) + return; + + // Ok if exist some buffs for dispel try dispel it + List success_list = new List(); + + DispelFailed dispelFailed = new DispelFailed(); + dispelFailed.CasterGUID = m_caster.GetGUID(); + dispelFailed.VictimGUID = unitTarget.GetGUID(); + dispelFailed.SpellID = m_spellInfo.Id; + + // dispel N = damage buffs (or while exist buffs for dispel) + for (int count = 0; count < damage && !dispel_list.Empty();) + { + // Random select buff for dispel + var pair = dispel_list[RandomHelper.IRand(0, dispel_list.Count - 1)]; + + int chance = pair.aura.CalcDispelChance(unitTarget, !unitTarget.IsFriendlyTo(m_caster)); + // 2.4.3 Patch Notes: "Dispel effects will no longer attempt to remove effects that have 100% dispel resistance." + if (chance == 0) + { + dispel_list.Remove(pair); + continue; + } + else + { + if (RandomHelper.randChance(chance)) + { + bool alreadyListed = false; + foreach (var successPair in success_list) + { + var successValue = successPair.value; + if (successPair.aura.GetId() == pair.aura.GetId()) + { + ++successPair.value; + alreadyListed = true; + } + } + if (!alreadyListed) + success_list.Add(new DispelCharges(pair.aura, 1)); + --pair.value; + if (pair.value <= 0) + dispel_list.Remove(pair); + } + else + dispelFailed.FailedSpells.Add(pair.aura.GetId()); + + ++count; + } + } + + if (!dispelFailed.FailedSpells.Empty()) + m_caster.SendMessageToSet(dispelFailed, true); + + if (success_list.Empty()) + return; + + SpellDispellLog spellDispellLog = new SpellDispellLog(); + spellDispellLog.IsBreak = false; // TODO: use me + spellDispellLog.IsSteal = false; + + spellDispellLog.TargetGUID = unitTarget.GetGUID(); + spellDispellLog.CasterGUID = m_caster.GetGUID(); + spellDispellLog.DispelledBySpellID = m_spellInfo.Id; + + foreach (var dispellCharge in success_list) + { + var dispellData = new SpellDispellData(); + dispellData.SpellID = dispellCharge.aura.GetId(); + dispellData.Harmful = false; // TODO: use me + //dispellData.Rolled = none; // TODO: use me + //dispellData.Needed = none; // TODO: use me + + unitTarget.RemoveAurasDueToSpellByDispel(dispellCharge.aura.GetId(), m_spellInfo.Id, dispellCharge.aura.GetCasterGUID(), m_caster, dispellCharge.value); + + spellDispellLog.DispellData.Add(dispellData); + } + m_caster.SendMessageToSet(spellDispellLog, true); + + CallScriptSuccessfulDispel(effIndex); + } + + [SpellEffectHandler(SpellEffectName.DualWield)] + void EffectDualWield(uint effIndex) + { + if (effectHandleMode != SpellEffectHandleMode.HitTarget) + return; + + unitTarget.SetCanDualWield(true); + if (unitTarget.IsTypeId(TypeId.Unit)) + unitTarget.ToCreature().UpdateDamagePhysical(WeaponAttackType.OffAttack); + } + + [SpellEffectHandler(SpellEffectName.Pull)] + void EffectPull(uint effIndex) + { + // @todo create a proper pull towards distract spell center for distract + EffectUnused(effIndex); + } + + [SpellEffectHandler(SpellEffectName.Distract)] + void EffectDistract(uint effIndex) + { + if (effectHandleMode != SpellEffectHandleMode.HitTarget) + return; + + // Check for possible target + if (unitTarget == null || unitTarget.IsInCombat()) + return; + + // target must be OK to do this + if (unitTarget.HasUnitState(UnitState.Confused | UnitState.Stunned | UnitState.Fleeing)) + return; + + unitTarget.SetFacingTo(unitTarget.GetAngle(destTarget)); + unitTarget.ClearUnitState(UnitState.Moving); + + if (unitTarget.IsTypeId(TypeId.Unit)) + unitTarget.GetMotionMaster().MoveDistract((uint)(damage * Time.InMilliseconds)); + } + + [SpellEffectHandler(SpellEffectName.Pickpocket)] + void EffectPickPocket(uint effIndex) + { + if (effectHandleMode != SpellEffectHandleMode.HitTarget) + return; + + if (!m_caster.IsTypeId(TypeId.Player)) + return; + + // victim must be creature and attackable + if (unitTarget == null || !unitTarget.IsTypeId(TypeId.Unit) || m_caster.IsFriendlyTo(unitTarget)) + return; + + // victim have to be alive and humanoid or undead + if (unitTarget.IsAlive() && (unitTarget.GetCreatureTypeMask() & (uint)CreatureType.MaskHumanoidOrUndead) != 0) + m_caster.ToPlayer().SendLoot(unitTarget.GetGUID(), LootType.Pickpocketing); + } + + [SpellEffectHandler(SpellEffectName.AddFarsight)] + void EffectAddFarsight(uint effIndex) + { + if (effectHandleMode != SpellEffectHandleMode.Hit) + return; + + if (!m_caster.IsTypeId(TypeId.Player)) + return; + + float radius = effectInfo.CalcRadius(); + int duration = m_spellInfo.CalcDuration(m_caster); + // Caster not in world, might be spell triggered from aura removal + if (!m_caster.IsInWorld) + return; + + DynamicObject dynObj = new DynamicObject(true); + if (!dynObj.CreateDynamicObject(m_caster.GetMap().GenerateLowGuid(HighGuid.DynamicObject), m_caster, m_spellInfo, destTarget, radius, DynamicObjectType.FarsightFocus, m_SpellVisual)) + return; + + dynObj.SetDuration(duration); + dynObj.SetCasterViewpoint(); + } + + [SpellEffectHandler(SpellEffectName.UntrainTalents)] + void EffectUntrainTalents(uint effIndex) + { + if (effectHandleMode != SpellEffectHandleMode.HitTarget) + return; + + if (unitTarget == null || m_caster.IsTypeId(TypeId.Player)) + return; + + ObjectGuid guid = m_caster.GetGUID(); + if (!guid.IsEmpty()) // the trainer is the caster + unitTarget.ToPlayer().SendRespecWipeConfirm(guid, unitTarget.ToPlayer().GetNextResetTalentsCost()); + } + + [SpellEffectHandler(SpellEffectName.TeleportUnitsFaceCaster)] + void EffectTeleUnitsFaceCaster(uint effIndex) + { + if (effectHandleMode != SpellEffectHandleMode.HitTarget) + return; + + if (unitTarget == null) + return; + + if (unitTarget.IsInFlight()) + return; + + float dis = effectInfo.CalcRadius(m_caster); + + float fx, fy, fz; + m_caster.GetClosePoint(out fx, out fy, out fz, unitTarget.GetObjectSize(), dis); + + unitTarget.NearTeleportTo(fx, fy, fz, -m_caster.GetOrientation(), unitTarget == m_caster); + } + + [SpellEffectHandler(SpellEffectName.SkillStep)] + void EffectLearnSkill(uint effIndex) + { + if (effectHandleMode != SpellEffectHandleMode.HitTarget) + return; + + if (!unitTarget.IsTypeId(TypeId.Player)) + return; + + if (damage < 0) + return; + + uint skillid = (uint)effectInfo.MiscValue; + SkillRaceClassInfoRecord rcEntry = Global.DB2Mgr.GetSkillRaceClassInfo(skillid, unitTarget.GetRace(), unitTarget.GetClass()); + if (rcEntry == null) + return; + + SkillTiersEntry tier = Global.ObjectMgr.GetSkillTier(rcEntry.SkillTierID); + if (tier == null) + return; + ushort skillval = unitTarget.ToPlayer().GetPureSkillValue((SkillType)skillid); + unitTarget.ToPlayer().SetSkill(skillid, (uint)effectInfo.CalcValue(), Math.Max(skillval, (ushort)1), tier.Value[damage - 1]); + } + + [SpellEffectHandler(SpellEffectName.PlayMovie)] + void EffectPlayMovie(uint effIndex) + { + if (effectHandleMode != SpellEffectHandleMode.HitTarget) + return; + + if (!unitTarget.IsTypeId(TypeId.Player)) + return; + + uint movieId = (uint)effectInfo.MiscValue; + if (!CliDB.MovieStorage.ContainsKey(movieId)) + return; + + unitTarget.ToPlayer().SendMovieStart(movieId); + } + + [SpellEffectHandler(SpellEffectName.TradeSkill)] + void EffectTradeSkill(uint effIndex) + { + if (effectHandleMode != SpellEffectHandleMode.Hit) + return; + + if (!m_caster.IsTypeId(TypeId.Player)) + return; + // uint skillid = GetEffect(i].MiscValue; + // ushort skillmax = unitTarget.ToPlayer().(skillid); + // m_caster.ToPlayer().SetSkill(skillid, skillval?skillval:1, skillmax+75); + } + + [SpellEffectHandler(SpellEffectName.EnchantItem)] + void EffectEnchantItemPerm(uint effIndex) + { + if (effectHandleMode != SpellEffectHandleMode.HitTarget) + return; + + if (itemTarget == null) + return; + + Player player = m_caster.ToPlayer(); + if (player == null) + return; + + // Handle vellums + if (itemTarget.IsVellum()) + { + // destroy one vellum from stack + uint count = 1; + player.DestroyItemCount(itemTarget, ref count, true); + unitTarget = player; + // and add a scroll + DoCreateItem(effIndex, effectInfo.ItemType); + itemTarget = null; + m_targets.SetItemTarget(null); + } + else + { + // do not increase skill if vellum used + if (!(m_CastItem && m_CastItem.GetTemplate().GetFlags().HasAnyFlag(ItemFlags.NoReagentCost))) + player.UpdateCraftSkill(m_spellInfo.Id); + + uint enchant_id = (uint)effectInfo.MiscValue; + if (enchant_id == 0) + return; + + SpellItemEnchantmentRecord pEnchant = CliDB.SpellItemEnchantmentStorage.LookupByKey(enchant_id); + if (pEnchant == null) + return; + + // item can be in trade slot and have owner diff. from caster + Player item_owner = itemTarget.GetOwner(); + if (item_owner == null) + return; + + if (item_owner != player && player.GetSession().HasPermission(RBACPermissions.LogGmTrade)) + { + Log.outCommand(player.GetSession().GetAccountId(), "GM {0} (Account: {1}) enchanting(perm): {2} (Entry: {3}) for player: {4} (Account: {5})", + player.GetName(), player.GetSession().GetAccountId(), itemTarget.GetTemplate().GetName(), itemTarget.GetEntry(), item_owner.GetName(), item_owner.GetSession().GetAccountId()); + } + + // remove old enchanting before applying new if equipped + item_owner.ApplyEnchantment(itemTarget, EnchantmentSlot.Perm, false); + + itemTarget.SetEnchantment(EnchantmentSlot.Perm, enchant_id, 0, 0, m_caster.GetGUID()); + + // add new enchanting if equipped + item_owner.ApplyEnchantment(itemTarget, EnchantmentSlot.Perm, true); + + item_owner.RemoveTradeableItem(itemTarget); + itemTarget.ClearSoulboundTradeable(item_owner); + } + } + + [SpellEffectHandler(SpellEffectName.EnchantItemPrismatic)] + void EffectEnchantItemPrismatic(uint effIndex) + { + if (effectHandleMode != SpellEffectHandleMode.HitTarget) + return; + + if (itemTarget == null) + return; + + Player player = m_caster.ToPlayer(); + if (player == null) + return; + + uint enchantId = (uint)effectInfo.MiscValue; + if (enchantId == 0) + return; + + SpellItemEnchantmentRecord enchant = CliDB.SpellItemEnchantmentStorage.LookupByKey(enchantId); + if (enchant == null) + return; + + // support only enchantings with add socket in this slot + { + bool add_socket = false; + for (byte i = 0; i < ItemConst.MaxItemEnchantmentEffects; ++i) + { + if (enchant.Effect[i] == ItemEnchantmentType.PrismaticSocket) + { + add_socket = true; + break; + } + } + if (!add_socket) + { + Log.outError(LogFilter.Spells, "Spell.EffectEnchantItemPrismatic: attempt apply enchant spell {0} with SPELL_EFFECT_ENCHANT_ITEM_PRISMATIC ({1}) but without ITEM_ENCHANTMENT_TYPE_PRISMATIC_SOCKET ({2}), not suppoted yet.", + m_spellInfo.Id, SpellEffectName.EnchantItemPrismatic, ItemEnchantmentType.PrismaticSocket); + return; + } + } + + // item can be in trade slot and have owner diff. from caster + Player item_owner = itemTarget.GetOwner(); + if (item_owner == null) + return; + + if (item_owner != player && player.GetSession().HasPermission(RBACPermissions.LogGmTrade)) + { + Log.outCommand(player.GetSession().GetAccountId(), "GM {0} (Account: {1}) enchanting(perm): {2} (Entry: {3}) for player: {4} (Account: {5})", + player.GetName(), player.GetSession().GetAccountId(), itemTarget.GetTemplate().GetName(), itemTarget.GetEntry(), item_owner.GetName(), item_owner.GetSession().GetAccountId()); + } + + // remove old enchanting before applying new if equipped + item_owner.ApplyEnchantment(itemTarget, EnchantmentSlot.Prismatic, false); + + itemTarget.SetEnchantment(EnchantmentSlot.Prismatic, enchantId, 0, 0, m_caster.GetGUID()); + + // add new enchanting if equipped + item_owner.ApplyEnchantment(itemTarget, EnchantmentSlot.Prismatic, true); + + item_owner.RemoveTradeableItem(itemTarget); + itemTarget.ClearSoulboundTradeable(item_owner); + } + + [SpellEffectHandler(SpellEffectName.EnchantItemTemporary)] + void EffectEnchantItemTmp(uint effIndex) + { + if (effectHandleMode != SpellEffectHandleMode.HitTarget) + return; + + Player player = m_caster.ToPlayer(); + if (player == null) + return; + + if (itemTarget == null) + return; + + uint enchant_id = (uint)effectInfo.MiscValue; + + if (enchant_id == 0) + { + Log.outError(LogFilter.Spells, "Spell {0} Effect {1} (SPELL_EFFECT_ENCHANT_ITEM_TEMPORARY) have 0 as enchanting id", m_spellInfo.Id, effIndex); + return; + } + + SpellItemEnchantmentRecord pEnchant = CliDB.SpellItemEnchantmentStorage.LookupByKey(enchant_id); + if (pEnchant == null) + { + Log.outError(LogFilter.Spells, "Spell {0} Effect {1} (SPELL_EFFECT_ENCHANT_ITEM_TEMPORARY) have not existed enchanting id {2}", m_spellInfo.Id, effIndex, enchant_id); + return; + } + + // select enchantment duration + uint duration; + + // rogue family enchantments exception by duration + if (m_spellInfo.Id == 38615) + duration = 1800; // 30 mins + // other rogue family enchantments always 1 hour (some have spell damage=0, but some have wrong data in EffBasePoints) + else if (m_spellInfo.SpellFamilyName == SpellFamilyNames.Rogue) + duration = 3600; // 1 hour + // shaman family enchantments + else if (m_spellInfo.SpellFamilyName == SpellFamilyNames.Shaman) + duration = 3600; // 30 mins + // other cases with this SpellVisual already selected + else if (m_spellInfo.GetSpellVisual() == 215) + duration = 1800; // 30 mins + // some fishing pole bonuses except Glow Worm which lasts full hour + else if (m_spellInfo.GetSpellVisual() == 563 && m_spellInfo.Id != 64401) + duration = 600; // 10 mins + else if (m_spellInfo.Id == 29702) + duration = 300; // 5 mins + else if (m_spellInfo.Id == 37360) + duration = 300; // 5 mins + // default case + else + duration = 3600; // 1 hour + + // item can be in trade slot and have owner diff. from caster + Player item_owner = itemTarget.GetOwner(); + if (item_owner == null) + return; + + if (item_owner != player && player.GetSession().HasPermission(RBACPermissions.LogGmTrade)) + { + Log.outCommand(player.GetSession().GetAccountId(), "GM {0} (Account: {1}) enchanting(temp): {2} (Entry: {3}) for player: {4} (Account: {5})", + player.GetName(), player.GetSession().GetAccountId(), itemTarget.GetTemplate().GetName(), itemTarget.GetEntry(), item_owner.GetName(), item_owner.GetSession().GetAccountId()); + } + + // remove old enchanting before applying new if equipped + item_owner.ApplyEnchantment(itemTarget, EnchantmentSlot.Temp, false); + + itemTarget.SetEnchantment(EnchantmentSlot.Temp, enchant_id, duration * 1000, 0, m_caster.GetGUID()); + + // add new enchanting if equipped + item_owner.ApplyEnchantment(itemTarget, EnchantmentSlot.Temp, true); + } + + [SpellEffectHandler(SpellEffectName.Tamecreature)] + void EffectTameCreature(uint effIndex) + { + if (effectHandleMode != SpellEffectHandleMode.HitTarget) + return; + + if (!m_caster.GetPetGUID().IsEmpty()) + return; + + if (unitTarget == null) + return; + + if (!unitTarget.IsTypeId(TypeId.Unit)) + return; + + Creature creatureTarget = unitTarget.ToCreature(); + + if (creatureTarget.IsPet()) + return; + + if (m_caster.GetClass() != Class.Hunter) + return; + + // cast finish successfully + finish(); + + Pet pet = m_caster.CreateTamedPetFrom(creatureTarget, m_spellInfo.Id); + if (pet == null) // in very specific state like near world end/etc. + return; + + // "kill" original creature + creatureTarget.DespawnOrUnsummon(); + + uint level = (creatureTarget.getLevel() < (m_caster.getLevel() - 5)) ? (m_caster.getLevel() - 5) : creatureTarget.getLevel(); + + // prepare visual effect for levelup + pet.SetUInt32Value(UnitFields.Level, level - 1); + + // add to world + pet.GetMap().AddToMap(pet.ToCreature()); + + // visual effect for levelup + pet.SetUInt32Value(UnitFields.Level, level); + + // caster have pet now + m_caster.SetMinion(pet, true); + + if (m_caster.IsTypeId(TypeId.Player)) + { + pet.SavePetToDB(PetSaveMode.AsCurrent); + m_caster.ToPlayer().PetSpellInitialize(); + } + } + + [SpellEffectHandler(SpellEffectName.SummonPet)] + void EffectSummonPet(uint effIndex) + { + if (effectHandleMode != SpellEffectHandleMode.Hit) + return; + + Player owner = null; + if (m_originalCaster != null) + { + owner = m_originalCaster.ToPlayer(); + if (owner == null && m_originalCaster.IsTotem()) + owner = m_originalCaster.GetCharmerOrOwnerPlayerOrPlayerItself(); + } + + uint petentry = (uint)effectInfo.MiscValue; + + if (owner == null) + { + SummonPropertiesRecord properties = CliDB.SummonPropertiesStorage.LookupByKey(67); + if (properties != null) + SummonGuardian(effIndex, petentry, properties, 1); + return; + } + + Pet OldSummon = owner.GetPet(); + + // if pet requested type already exist + if (OldSummon != null) + { + if (petentry == 0 || OldSummon.GetEntry() == petentry) + { + // pet in corpse state can't be summoned + if (OldSummon.IsDead()) + return; + + Contract.Assert(OldSummon.GetMap() == owner.GetMap()); + + float px, py, pz; + owner.GetClosePoint(out px, out py, out pz, OldSummon.GetObjectSize()); + + OldSummon.NearTeleportTo(px, py, pz, OldSummon.GetOrientation()); + + if (owner.IsTypeId(TypeId.Player) && OldSummon.isControlled()) + owner.ToPlayer().PetSpellInitialize(); + + return; + } + + if (owner.IsTypeId(TypeId.Player)) + owner.ToPlayer().RemovePet(OldSummon, (OldSummon.getPetType() == PetType.Hunter ? PetSaveMode.AsDeleted : PetSaveMode.NotInSlot), false); + else + return; + } + + float x, y, z; + owner.GetClosePoint(out x, out y, out z, owner.GetObjectSize()); + Pet pet = owner.SummonPet(petentry, x, y, z, owner.Orientation, PetType.Summon, 0); + if (!pet) + return; + + if (m_caster.IsTypeId(TypeId.Unit)) + { + if (m_caster.IsTotem()) + pet.SetReactState(ReactStates.Aggressive); + else + pet.SetReactState(ReactStates.Defensive); + } + + pet.SetUInt32Value(UnitFields.CreatedBySpell, m_spellInfo.Id); + + // generate new name for summon pet + string new_name = Global.ObjectMgr.GeneratePetName(petentry); + if (!string.IsNullOrEmpty(new_name)) + pet.SetName(new_name); + + ExecuteLogEffectSummonObject(effIndex, pet); + } + + [SpellEffectHandler(SpellEffectName.LearnPetSpell)] + void EffectLearnPetSpell(uint effIndex) + { + if (effectHandleMode != SpellEffectHandleMode.HitTarget) + return; + + if (unitTarget == null) + return; + + if (unitTarget.ToPlayer() != null) + { + EffectLearnSpell(effIndex); + return; + } + Pet pet = unitTarget.ToPet(); + if (pet == null) + return; + + SpellInfo learn_spellproto = Global.SpellMgr.GetSpellInfo(effectInfo.TriggerSpell); + if (learn_spellproto == null) + return; + + pet.learnSpell(learn_spellproto.Id); + pet.SavePetToDB(PetSaveMode.AsCurrent); + pet.GetOwner().PetSpellInitialize(); + } + + [SpellEffectHandler(SpellEffectName.AttackMe)] + void EffectTaunt(uint effIndex) + { + if (effectHandleMode != SpellEffectHandleMode.HitTarget) + return; + + if (unitTarget == null) + return; + + // this effect use before aura Taunt apply for prevent taunt already attacking target + // for spell as marked "non effective at already attacking target" + if (unitTarget == null || !unitTarget.CanHaveThreatList() + || unitTarget.GetVictim() == m_caster) + { + SendCastResult(SpellCastResult.DontReport); + return; + } + + if (!unitTarget.GetThreatManager().getOnlineContainer().empty()) + { + // Also use this effect to set the taunter's threat to the taunted creature's highest value + float myThreat = unitTarget.GetThreatManager().getThreat(m_caster); + float topThreat = unitTarget.GetThreatManager().getOnlineContainer().getMostHated().getThreat(); + if (topThreat > myThreat) + unitTarget.GetThreatManager().addThreat(m_caster, topThreat - myThreat); + + //Set aggro victim to caster + HostileReference forcedVictim = unitTarget.GetThreatManager().getOnlineContainer().getReferenceByTarget(m_caster); + if (forcedVictim != null) + unitTarget.GetThreatManager().setCurrentVictim(forcedVictim); + } + + if (unitTarget.ToCreature().IsAIEnabled && !unitTarget.ToCreature().HasReactState(ReactStates.Passive)) + unitTarget.ToCreature().GetAI().AttackStart(m_caster); + } + + [SpellEffectHandler(SpellEffectName.WeaponDamageNoschool)] + [SpellEffectHandler(SpellEffectName.WeaponPercentDamage)] + [SpellEffectHandler(SpellEffectName.WeaponDamage)] + [SpellEffectHandler(SpellEffectName.NormalizedWeaponDmg)] + void EffectWeaponDmg(uint effIndex) + { + if (effectHandleMode != SpellEffectHandleMode.LaunchTarget) + return; + + if (unitTarget == null || !unitTarget.IsAlive()) + return; + + // multiple weapon dmg effect workaround + // execute only the last weapon damage + // and handle all effects at once + for (var i = effIndex + 1; i < SpellConst.MaxEffects; ++i) + { + var effect = GetEffect(i); + if (effect == null) + continue; + + switch (effect.Effect) + { + case SpellEffectName.WeaponDamage: + case SpellEffectName.WeaponDamageNoschool: + case SpellEffectName.NormalizedWeaponDmg: + case SpellEffectName.WeaponPercentDamage: + return; // we must calculate only at last weapon effect + } + } + + // some spell specific modifiers + float totalDamagePercentMod = 1.0f; // applied to final bonus+weapon damage + int fixed_bonus = 0; + int spell_bonus = 0; // bonus specific for spell + + switch (m_spellInfo.SpellFamilyName) + { + case SpellFamilyNames.Warrior: + { + // Devastate (player ones) + if (m_spellInfo.SpellFamilyFlags[1].HasAnyFlag(0x40u)) + { + // Player can apply only 58567 Sunder Armor effect. + bool needCast = !unitTarget.HasAura(58567, m_caster.GetGUID()); + if (needCast) + m_caster.CastSpell(unitTarget, 58567, true); + + Aura aur = unitTarget.GetAura(58567, m_caster.GetGUID()); + if (aur != null) + { + int num = (needCast ? 0 : 1); + if (num != 0) + aur.ModStackAmount(num); + fixed_bonus += (aur.GetStackAmount() - 1) * CalculateDamage(2, unitTarget); + } + } + break; + } + case SpellFamilyNames.Rogue: + { + // Hemorrhage + if (m_spellInfo.SpellFamilyFlags[0].HasAnyFlag(0x2000000u)) + { + if (m_caster.IsTypeId(TypeId.Player)) + m_caster.ToPlayer().AddComboPoints(1, this); + // 50% more damage with daggers + if (m_caster.IsTypeId(TypeId.Player)) + { + Item item = m_caster.ToPlayer().GetWeaponForAttack(m_attackType, true); + if (item != null) + if (item.GetTemplate().GetSubClass() == (uint)ItemSubClassWeapon.Dagger) + totalDamagePercentMod *= 1.5f; + } + } + break; + } + case SpellFamilyNames.Shaman: + { + // Skyshatter Harness item set bonus + // Stormstrike + AuraEffect aurEff = m_caster.IsScriptOverriden(m_spellInfo, 5634); + if (aurEff != null) + m_caster.CastSpell(m_caster, 38430, true, null, aurEff); + break; + } + case SpellFamilyNames.Druid: + { + // Mangle (Cat): CP + if (m_spellInfo.SpellFamilyFlags[1].HasAnyFlag(0x400u)) + { + if (m_caster.IsTypeId(TypeId.Player)) + m_caster.ToPlayer().AddComboPoints(1, this); + } + break; + } + case SpellFamilyNames.Hunter: + { + // Kill Shot - bonus damage from Ranged Attack Power + if (m_spellInfo.SpellFamilyFlags[1].HasAnyFlag(0x800000u)) + spell_bonus += (int)(0.45f * m_caster.GetTotalAttackPowerValue(WeaponAttackType.RangedAttack)); + break; + } + case SpellFamilyNames.Deathknight: + { + // Blood Strike + if (m_spellInfo.SpellFamilyFlags[0].HasAnyFlag(0x400000u)) + { + SpellEffectInfo effect = GetEffect(2); + if (effect != null) + { + float bonusPct = effect.CalcValue(m_caster) * unitTarget.GetDiseasesByCaster(m_caster.GetGUID()) / 2.0f; + // Death Knight T8 Melee 4P Bonus + AuraEffect aurEff = m_caster.GetAuraEffect(64736, 0); + if (aurEff != null) + MathFunctions.AddPct(ref bonusPct, aurEff.GetAmount()); + MathFunctions.AddPct(ref totalDamagePercentMod, bonusPct); + } + break; + } + break; + } + } + + bool normalized = false; + float weaponDamagePercentMod = 1.0f; + foreach (SpellEffectInfo effect in GetEffects()) + { + if (effect == null) + continue; + + switch (effect.Effect) + { + case SpellEffectName.WeaponDamage: + case SpellEffectName.WeaponDamageNoschool: + fixed_bonus += CalculateDamage(effect.EffectIndex, unitTarget); + break; + case SpellEffectName.NormalizedWeaponDmg: + fixed_bonus += CalculateDamage(effect.EffectIndex, unitTarget); + normalized = true; + break; + case SpellEffectName.WeaponPercentDamage: + MathFunctions.ApplyPct(ref weaponDamagePercentMod, CalculateDamage(effect.EffectIndex, unitTarget)); + break; + default: + break; // not weapon damage effect, just skip + } + } + + // if (addPctMods) { percent mods are added in Unit::CalculateDamage } else { percent mods are added in Unit::MeleeDamageBonusDone } + // this distinction is neccessary to properly inform the client about his autoattack damage values from Script_UnitDamage + bool addPctMods = !m_spellInfo.HasAttribute(SpellAttr6.NoDonePctDamageMods) && m_spellSchoolMask.HasAnyFlag(SpellSchoolMask.Normal); + if (addPctMods) + { + UnitMods unitMod; + switch (m_attackType) + { + default: + case WeaponAttackType.BaseAttack: + unitMod = UnitMods.DamageMainHand; + break; + case WeaponAttackType.OffAttack: + unitMod = UnitMods.DamageOffHand; + break; + case WeaponAttackType.RangedAttack: + unitMod = UnitMods.DamageRanged; + break; + } + + float weapon_total_pct = m_caster.GetModifierValue(unitMod, UnitModifierType.TotalPCT); + if (fixed_bonus != 0) + fixed_bonus = (int)(fixed_bonus * weapon_total_pct); + if (spell_bonus != 0) + spell_bonus = (int)(spell_bonus * weapon_total_pct); + } + + uint weaponDamage = m_caster.CalculateDamage(m_attackType, normalized, addPctMods); + + // Sequence is important + foreach (SpellEffectInfo effect in GetEffects()) + { + if (effect == null) + continue; + + // We assume that a spell have at most one fixed_bonus + // and at most one weaponDamagePercentMod + switch (effect.Effect) + { + case SpellEffectName.WeaponDamage: + case SpellEffectName.WeaponDamageNoschool: + case SpellEffectName.NormalizedWeaponDmg: + weaponDamage += (uint)fixed_bonus; + break; + case SpellEffectName.WeaponPercentDamage: + weaponDamage = (uint)(weaponDamage * weaponDamagePercentMod); + break; + default: + break; // not weapon damage effect, just skip + } + } + + weaponDamage += (uint)spell_bonus; + weaponDamage = (uint)(weaponDamage * totalDamagePercentMod); + + // prevent negative damage + uint eff_damage = Math.Max(weaponDamage, 0); + + // Add melee damage bonuses (also check for negative) + uint damage = m_caster.MeleeDamageBonusDone(unitTarget, eff_damage, m_attackType, m_spellInfo); + + m_damage += (int)unitTarget.MeleeDamageBonusTaken(m_caster, damage, m_attackType, m_spellInfo); + } + + [SpellEffectHandler(SpellEffectName.Threat)] + void EffectThreat(uint effIndex) + { + if (effectHandleMode != SpellEffectHandleMode.HitTarget) + return; + + if (unitTarget == null || !unitTarget.IsAlive() || !m_caster.IsAlive()) + return; + + if (!unitTarget.CanHaveThreatList()) + return; + + unitTarget.AddThreat(m_caster, damage); + } + + [SpellEffectHandler(SpellEffectName.HealMaxHealth)] + void EffectHealMaxHealth(uint effIndex) + { + if (effectHandleMode != SpellEffectHandleMode.HitTarget) + return; + + if (unitTarget == null || !unitTarget.IsAlive()) + return; + + int addhealth = 0; + + // damage == 0 - heal for caster max health + if (damage == 0) + addhealth = (int)m_caster.GetMaxHealth(); + else + addhealth = (int)(unitTarget.GetMaxHealth() - unitTarget.GetHealth()); + + m_healing += addhealth; + } + + [SpellEffectHandler(SpellEffectName.InterruptCast)] + void EffectInterruptCast(uint effIndex) + { + if (effectHandleMode != SpellEffectHandleMode.HitTarget) + return; + + if (unitTarget == null || !unitTarget.IsAlive()) + return; + + // @todo not all spells that used this effect apply cooldown at school spells + // also exist case: apply cooldown to interrupted cast only and to all spells + // there is no CURRENT_AUTOREPEAT_SPELL spells that can be interrupted + for (var i = CurrentSpellTypes.Generic; i < CurrentSpellTypes.AutoRepeat; ++i) + { + Spell spell = unitTarget.GetCurrentSpell(i); + if (spell != null) + { + SpellInfo curSpellInfo = spell.m_spellInfo; + // check if we can interrupt spell + if ((spell.getState() == SpellState.Casting + || (spell.getState() == SpellState.Preparing && spell.GetCastTime() > 0.0f)) + && (curSpellInfo.PreventionType.HasAnyFlag(SpellPreventionType.Silence)) + && ((i == CurrentSpellTypes.Generic && curSpellInfo.InterruptFlags.HasAnyFlag(SpellInterruptFlags.Interrupt)) + || (i == CurrentSpellTypes.Channeled && curSpellInfo.ChannelInterruptFlags.HasAnyFlag(SpellChannelInterruptFlags.Interrupt)))) + { + if (m_originalCaster != null) + { + int duration = m_spellInfo.GetDuration(); + unitTarget.GetSpellHistory().LockSpellSchool(curSpellInfo.GetSchoolMask(), (uint)unitTarget.ModSpellDuration(m_spellInfo, unitTarget, duration, false, (uint)(1 << (int)effIndex))); + } + ExecuteLogEffectInterruptCast(effIndex, unitTarget, curSpellInfo.Id); + unitTarget.InterruptSpell(i, false); + } + } + } + } + + [SpellEffectHandler(SpellEffectName.SummonObjectWild)] + void EffectSummonObjectWild(uint effIndex) + { + if (effectHandleMode != SpellEffectHandleMode.Hit) + return; + + uint gameobject_id = (uint)effectInfo.MiscValue; + + GameObject pGameObj = new GameObject(); + + WorldObject target = focusObject; + if (target == null) + target = m_caster; + + float x, y, z; + if (m_targets.HasDst()) + destTarget.GetPosition(out x, out y, out z); + else + m_caster.GetClosePoint(out x, out y, out z, SharedConst.DefaultWorldObjectSize); + + Map map = target.GetMap(); + Quaternion rotation = new Quaternion(Matrix3.fromEulerAnglesZYX(target.GetOrientation(), 0.0f, 0.0f)); + if (!pGameObj.Create(gameobject_id, map, m_caster.GetPhaseMask(), new Position(x, y, z, target.GetOrientation()), rotation, 255, GameObjectState.Ready)) + return; + + pGameObj.CopyPhaseFrom(m_caster); + + int duration = m_spellInfo.CalcDuration(m_caster); + + pGameObj.SetRespawnTime(duration > 0 ? duration / Time.InMilliseconds : 0); + pGameObj.SetSpellId(m_spellInfo.Id); + + ExecuteLogEffectSummonObject(effIndex, pGameObj); + + // Wild object not have owner and check clickable by players + map.AddToMap(pGameObj); + + if (pGameObj.GetGoType() == GameObjectTypes.FlagDrop) + { + Player player = m_caster.ToPlayer(); + if (player != null) + { + Battleground bg = player.GetBattleground(); + if (bg) + bg.SetDroppedFlagGUID(pGameObj.GetGUID(), (int)(player.GetTeam() == Team.Alliance ? TeamId.Horde : TeamId.Alliance)); + } + } + + uint linkedEntry = pGameObj.GetGoInfo().GetLinkedGameObjectEntry(); + if (linkedEntry != 0) + { + GameObject linkedGO = new GameObject(); + if (linkedGO.Create(linkedEntry, map, m_caster.GetPhaseMask(), new Position(x, y, z, target.GetOrientation()), rotation, 255, GameObjectState.Ready)) + { + linkedGO.CopyPhaseFrom(m_caster); + + linkedGO.SetRespawnTime(duration > 0 ? duration / Time.InMilliseconds : 0); + linkedGO.SetSpellId(m_spellInfo.Id); + + ExecuteLogEffectSummonObject(effIndex, linkedGO); + + // Wild object not have owner and check clickable by players + map.AddToMap(linkedGO); + } + else + linkedGO.Dispose(); + } + } + + [SpellEffectHandler(SpellEffectName.ScriptEffect)] + void EffectScriptEffect(uint effIndex) + { + if (effectHandleMode != SpellEffectHandleMode.HitTarget) + return; + + // @todo we must implement hunter pet summon at login there (spell 6962) + + switch (m_spellInfo.SpellFamilyName) + { + case SpellFamilyNames.Generic: + { + switch (m_spellInfo.Id) + { + case 45204: // Clone Me! + m_caster.CastSpell(unitTarget, (uint)damage, true); + break; + case 55693: // Remove Collapsing Cave Aura + if (unitTarget == null) + return; + unitTarget.RemoveAurasDueToSpell((uint)effectInfo.CalcValue()); + break; + // Bending Shinbone + case 8856: + { + if (itemTarget == null && !m_caster.IsTypeId(TypeId.Player)) + return; + + uint spell_id = RandomHelper.Rand32(20) != 0 ? 8854u : 8855u; + + m_caster.CastSpell(m_caster, spell_id, true, null); + return; + } + // Brittle Armor - need remove one 24575 Brittle Armor aura + case 24590: + unitTarget.RemoveAuraFromStack(24575); + return; + // Mercurial Shield - need remove one 26464 Mercurial Shield aura + case 26465: + unitTarget.RemoveAuraFromStack(26464); + return; + // Shadow Flame (All script effects, not just end ones to prevent player from dodging the last triggered spell) + case 22539: + case 22972: + case 22975: + case 22976: + case 22977: + case 22978: + case 22979: + case 22980: + case 22981: + case 22982: + case 22983: + case 22984: + case 22985: + { + if (unitTarget == null || !unitTarget.IsAlive()) + return; + + // Onyxia Scale Cloak + if (unitTarget.HasAura(22683)) + return; + + // Shadow Flame + m_caster.CastSpell(unitTarget, 22682, true); + return; + } + // Mirren's Drinking Hat + case 29830: + { + uint item = 0; + switch (RandomHelper.IRand(1, 6)) + { + case 1: + case 2: + case 3: + item = 23584; + break; // Loch Modan Lager + case 4: + case 5: + item = 23585; + break; // Stouthammer Lite + case 6: + item = 23586; + break; // Aerie Peak Pale Ale + } + if (item != 0) + DoCreateItem(effIndex, item); + break; + } + case 20589: // Escape artist + case 30918: // Improved Sprint + { + // Removes snares and roots. + unitTarget.RemoveMovementImpairingAuras(); + break; + } + // Plant Warmaul Ogre Banner + case 32307: + Player caster = m_caster.ToPlayer(); + if (caster != null) + { + caster.RewardPlayerAndGroupAtEvent(18388, unitTarget); + Creature target = unitTarget.ToCreature(); + if (target != null) + { + target.setDeathState(DeathState.Corpse); + target.RemoveCorpse(); + } + } + break; + // Mug Transformation + case 41931: + { + if (!m_caster.IsTypeId(TypeId.Player)) + return; + + byte bag = 19; + byte slot = 0; + Item item = null; + + while (bag != 0) // 256 = 0 due to var type + { + item = m_caster.ToPlayer().GetItemByPos(bag, slot); + if (item != null && item.GetEntry() == 38587) + break; + + ++slot; + if (slot == 39) + { + slot = 0; + ++bag; + } + } + if (bag != 0) + { + if (m_caster.ToPlayer().GetItemByPos(bag, slot).GetCount() == 1) m_caster.ToPlayer().RemoveItem(bag, slot, true); + else m_caster.ToPlayer().GetItemByPos(bag, slot).SetCount(m_caster.ToPlayer().GetItemByPos(bag, slot).GetCount() - 1); + // Spell 42518 (Braufest - Gratisprobe des Braufest herstellen) + m_caster.CastSpell(m_caster, 42518, true); + return; + } + break; + } + // Brutallus - Burn + case 45141: + case 45151: + { + //Workaround for Range ... should be global for every ScriptEffect + float radius = effectInfo.CalcRadius(); + if (unitTarget != null && unitTarget.IsTypeId(TypeId.Player) && unitTarget.GetDistance(m_caster) >= radius && !unitTarget.HasAura(46394) && unitTarget != m_caster) + unitTarget.CastSpell(unitTarget, 46394, true); + + break; + } + // Goblin Weather Machine + case 46203: + { + if (unitTarget == null) + return; + + uint spellId = 0; + switch (RandomHelper.IRand(1, 4)) + { + case 0: spellId = 46740; break; + case 1: spellId = 46739; break; + case 2: spellId = 46738; break; + case 3: spellId = 46736; break; + } + unitTarget.CastSpell(unitTarget, spellId, true); + break; + } + // 5, 000 Gold + case 46642: + { + if (unitTarget == null || !unitTarget.IsTypeId(TypeId.Player)) + return; + + unitTarget.ToPlayer().ModifyMoney(5000 * MoneyConstants.Gold); + + break; + } + // Death Knight Initiate Visual + case 51519: + { + if (unitTarget == null || !unitTarget.IsTypeId(TypeId.Unit)) + return; + + uint iTmpSpellId = 0; + switch (unitTarget.GetDisplayId()) + { + case 25369: iTmpSpellId = 51552; break; // bloodelf female + case 25373: iTmpSpellId = 51551; break; // bloodelf male + case 25363: iTmpSpellId = 51542; break; // draenei female + case 25357: iTmpSpellId = 51541; break; // draenei male + case 25361: iTmpSpellId = 51537; break; // dwarf female + case 25356: iTmpSpellId = 51538; break; // dwarf male + case 25372: iTmpSpellId = 51550; break; // forsaken female + case 25367: iTmpSpellId = 51549; break; // forsaken male + case 25362: iTmpSpellId = 51540; break; // gnome female + case 25359: iTmpSpellId = 51539; break; // gnome male + case 25355: iTmpSpellId = 51534; break; // human female + case 25354: iTmpSpellId = 51520; break; // human male + case 25360: iTmpSpellId = 51536; break; // nightelf female + case 25358: iTmpSpellId = 51535; break; // nightelf male + case 25368: iTmpSpellId = 51544; break; // orc female + case 25364: iTmpSpellId = 51543; break; // orc male + case 25371: iTmpSpellId = 51548; break; // tauren female + case 25366: iTmpSpellId = 51547; break; // tauren male + case 25370: iTmpSpellId = 51545; break; // troll female + case 25365: iTmpSpellId = 51546; break; // troll male + default: return; + } + + unitTarget.CastSpell(unitTarget, iTmpSpellId, true); + Creature npc = unitTarget.ToCreature(); + npc.LoadEquipment(); + return; + } + // Emblazon Runeblade + case 51770: + { + if (m_originalCaster == null) + return; + + m_originalCaster.CastSpell(m_originalCaster, (uint)damage, false); + break; + } + // Deathbolt from Thalgran Blightbringer + // reflected by Freya's Ward + // Retribution by Sevenfold Retribution + case 51854: + { + if (unitTarget == null) + return; + if (unitTarget.HasAura(51845)) + unitTarget.CastSpell(m_caster, 51856, true); + else + m_caster.CastSpell(unitTarget, 51855, true); + break; + } + // Summon Ghouls On Scarlet Crusade + case 51904: + { + if (!m_targets.HasDst()) + return; + + float x, y, z; + float radius = effectInfo.CalcRadius(); + for (byte i = 0; i < 15; ++i) + { + m_caster.GetRandomPoint(destTarget, radius, out x, out y, out z); + m_caster.CastSpell(x, y, z, 54522, true); + } + break; + } + case 52173: // Coyote Spirit Despawn + case 60243: // Blood Parrot Despawn + if (unitTarget.IsTypeId(TypeId.Unit) && unitTarget.ToCreature().IsSummon()) + unitTarget.ToTempSummon().UnSummon(); + return; + case 52479: // Gift of the Harvester + if (unitTarget != null && m_originalCaster != null) + m_originalCaster.CastSpell(unitTarget, Convert.ToBoolean(RandomHelper.IRand(0, 1)) ? (uint)damage : 52505, true); + return; + case 53110: // Devour Humanoid + if (unitTarget != null) + unitTarget.CastSpell(m_caster, (uint)damage, true); + return; + case 57347: // Retrieving (Wintergrasp RP-GG pickup spell) + { + if (unitTarget == null || !unitTarget.IsTypeId(TypeId.Unit) || !m_caster.IsTypeId(TypeId.Player)) + return; + + unitTarget.ToCreature().DespawnOrUnsummon(); + + return; + } + case 57349: // Drop RP-GG (Wintergrasp RP-GG at death drop spell) + { + if (!m_caster.IsTypeId(TypeId.Player)) + return; + + // Delete item from inventory at death + m_caster.ToPlayer().DestroyItemCount((uint)damage, 5, true); + + return; + } + case 58418: // Portal to Orgrimmar + case 58420: // Portal to Stormwind + { + if (unitTarget == null || !unitTarget.IsTypeId(TypeId.Player) || effIndex != 0) + return; + + uint spellID = (uint)GetEffect(0).CalcValue(); + uint questID = (uint)GetEffect(1).CalcValue(); + + if (unitTarget.ToPlayer().GetQuestStatus(questID) == QuestStatus.Complete) + unitTarget.CastSpell(unitTarget, spellID, true); + + return; + } + case 58941: // Rock Shards + if (unitTarget != null && m_originalCaster != null) + { + for (uint i = 0; i < 3; ++i) + { + m_originalCaster.CastSpell(unitTarget, 58689, true); + m_originalCaster.CastSpell(unitTarget, 58692, true); + } + if (m_originalCaster.GetMap().GetDifficultyID() == Difficulty.None) + { + m_originalCaster.CastSpell(unitTarget, 58695, true); + m_originalCaster.CastSpell(unitTarget, 58696, true); + } + else + { + m_originalCaster.CastSpell(unitTarget, 60883, true); + m_originalCaster.CastSpell(unitTarget, 60884, true); + } + } + return; + case 58983: // Big Blizzard Bear + { + if (unitTarget == null || !unitTarget.IsTypeId(TypeId.Player)) + return; + + // Prevent stacking of mounts and client crashes upon dismounting + unitTarget.RemoveAurasByType(AuraType.Mounted); + + // Triggered spell id dependent on riding skilll + ushort skillval = unitTarget.ToPlayer().GetSkillValue(SkillType.Riding); + if (skillval != 0) + { + if (skillval >= 150) + unitTarget.CastSpell(unitTarget, 58999, true); + else + unitTarget.CastSpell(unitTarget, 58997, true); + } + return; + } + case 59317: // Teleporting + { + + if (unitTarget == null || !unitTarget.IsTypeId(TypeId.Player)) + return; + + // return from top + if (unitTarget.ToPlayer().GetAreaId() == 4637) + unitTarget.CastSpell(unitTarget, 59316, true); + // teleport atop + else + unitTarget.CastSpell(unitTarget, 59314, true); + + return; + } + case 62482: // Grab Crate + { + if (unitTarget != null) + { + Unit seat = m_caster.GetVehicleBase(); + if (seat != null) + { + Unit parent = seat.GetVehicleBase(); + if (parent != null) + { + // @todo a hack, range = 11, should after some time cast, otherwise too far + m_caster.CastSpell(parent, 62496, true); + unitTarget.CastSpell(parent, (uint)GetEffect(0).CalcValue()); + } + } + } + return; + } + case 60123: // Lightwell + { + if (!m_caster.IsTypeId(TypeId.Unit) || !m_caster.ToCreature().IsSummon()) + return; + + uint spell_heal; + + switch (m_caster.GetEntry()) + { + case 31897: spell_heal = 7001; break; + case 31896: spell_heal = 27873; break; + case 31895: spell_heal = 27874; break; + case 31894: spell_heal = 28276; break; + case 31893: spell_heal = 48084; break; + case 31883: spell_heal = 48085; break; + default: + Log.outError(LogFilter.Spells, "Unknown Lightwell spell caster {0}", m_caster.GetEntry()); + return; + } + + // proc a spellcast + Aura chargesAura = m_caster.GetAura(59907); + if (chargesAura != null) + { + m_caster.CastSpell(unitTarget, spell_heal, true, null, null, m_caster.ToTempSummon().GetSummonerGUID()); + if (chargesAura.ModCharges(-1)) + m_caster.ToTempSummon().UnSummon(); + } + + return; + } + // Stoneclaw Totem + case 55328: // Rank 1 + case 55329: // Rank 2 + case 55330: // Rank 3 + case 55332: // Rank 4 + case 55333: // Rank 5 + case 55335: // Rank 6 + case 55278: // Rank 7 + case 58589: // Rank 8 + case 58590: // Rank 9 + case 58591: // Rank 10 + { + int basepoints0 = damage; + // Cast Absorb on totems + for (byte slot = (int)SummonSlot.Totem; slot < SharedConst.MaxTotemSlot; ++slot) + { + if (unitTarget.m_SummonSlot[slot].IsEmpty()) + continue; + + Creature totem = unitTarget.GetMap().GetCreature(unitTarget.m_SummonSlot[slot]); + if (totem != null && totem.IsTotem()) + { + m_caster.CastCustomSpell(totem, 55277, basepoints0, 0, 0, true); + } + } + // Glyph of Stoneclaw Totem + AuraEffect aur = unitTarget.GetAuraEffect(63298, 0); + if (aur != null) + { + basepoints0 *= aur.GetAmount(); + m_caster.CastCustomSpell(unitTarget, 55277, basepoints0, 0, 0, true); + } + break; + } + case 45668: // Ultra-Advanced Proto-Typical Shortening Blaster + { + if (unitTarget == null || !unitTarget.IsTypeId(TypeId.Unit)) + return; + + if (RandomHelper.randChance(50)) // chance unknown, using 50 + return; + + uint[] spellPlayer = new uint[5] + { + 45674, // Bigger! + 45675, // Shrunk + 45678, // Yellow + 45682, // Ghost + 45684 // Polymorph + }; + + uint[] spellTarget = new uint[5] + { + 45673, // Bigger! + 45672, // Shrunk + 45677, // Yellow + 45681, // Ghost + 45683 // Polymorph + }; + + m_caster.CastSpell(m_caster, spellPlayer[RandomHelper.IRand(0, 4)], true); + unitTarget.CastSpell(unitTarget, spellTarget[RandomHelper.IRand(0, 4)], true); + break; + } + } + break; + } + case SpellFamilyNames.Paladin: + { + // Judgement (seal trigger) + if (m_spellInfo.GetCategory() == (int)SpellCategories.Judgement) + { + if (unitTarget == null || !unitTarget.IsAlive()) + return; + uint spellId1 = 0; + uint spellId2 = 0; + + // Judgement self add switch + switch (m_spellInfo.Id) + { + case 53407: spellId1 = 20184; break; // Judgement of Justice + case 20271: // Judgement of Light + case 57774: spellId1 = 20185; break; // Judgement of Light + case 53408: spellId1 = 20186; break; // Judgement of Wisdom + default: + Log.outError(LogFilter.Spells, "Unsupported Judgement (seal trigger) spell (Id: {0}) in Spell.EffectScriptEffect", m_spellInfo.Id); + return; + } + // all seals have aura dummy in 2 effect + foreach (var app in m_caster.GetAppliedAuras()) + { + Aura aura = app.Value.GetBase(); + if (aura.GetSpellInfo().GetSpellSpecific() == SpellSpecificType.Seal) + { + AuraEffect aureff = aura.GetEffect(2); + if (aureff != null) + if (aureff.GetAuraType() == AuraType.Dummy) + { + if (Global.SpellMgr.GetSpellInfo((uint)aureff.GetAmount()) != null) + spellId2 = (uint)aureff.GetAmount(); + break; + } + if (spellId2 == 0) + { + switch (app.Key) + { + // Seal of light, Seal of wisdom, Seal of justice + case 20165: + case 20166: + case 20164: + spellId2 = 54158; + break; + } + } + break; + } + } + if (spellId1 != 0) + m_caster.CastSpell(unitTarget, spellId1, true); + if (spellId2 != 0) + m_caster.CastSpell(unitTarget, spellId2, true); + return; + } + break; + } + } + + // normal DB scripted effect + Log.outDebug(LogFilter.Spells, "Spell ScriptStart spellid {0} in EffectScriptEffect({1})", m_spellInfo.Id, effIndex); + m_caster.GetMap().ScriptsStart(ScriptsType.Spell, (uint)((int)m_spellInfo.Id | (int)(effIndex << 24)), m_caster, unitTarget); + } + + [SpellEffectHandler(SpellEffectName.Sanctuary)] + [SpellEffectHandler(SpellEffectName.Sanctuary2)] + void EffectSanctuary(uint effIndex) + { + if (effectHandleMode != SpellEffectHandleMode.HitTarget) + return; + + if (unitTarget == null) + return; + + unitTarget.getHostileRefManager().UpdateVisibility(); + + var attackers = unitTarget.getAttackers(); + foreach (var unit in attackers) + { + if (!unit.CanSeeOrDetect(unitTarget)) + unit.AttackStop(); + } + + unitTarget.m_lastSanctuaryTime = Time.GetMSTime(); + + // Vanish allows to remove all threat and cast regular stealth so other spells can be used + if (m_caster.IsTypeId(TypeId.Player) + && m_spellInfo.SpellFamilyName == SpellFamilyNames.Rogue + && m_spellInfo.SpellFamilyFlags[0].HasAnyFlag(0x00000800u)) + { + m_caster.ToPlayer().RemoveAurasByType(AuraType.ModRoot); + m_caster.ToPlayer().RemoveAurasByType(AuraType.ModRoot2); + // Overkill + if (m_caster.ToPlayer().HasSpell(58426)) + m_caster.CastSpell(m_caster, 58427, true); + } + } + + [SpellEffectHandler(SpellEffectName.AddComboPoints)] + void EffectAddComboPoints(uint effIndex) + { + if (effectHandleMode != SpellEffectHandleMode.HitTarget) + return; + + if (unitTarget == null) + return; + + if (m_caster.m_playerMovingMe == null) + return; + + if (damage <= 0) + return; + + m_caster.m_playerMovingMe.AddComboPoints((sbyte)damage, this); + } + + [SpellEffectHandler(SpellEffectName.Duel)] + void EffectDuel(uint effIndex) + { + if (effectHandleMode != SpellEffectHandleMode.HitTarget) + return; + + if (unitTarget == null || !m_caster.IsTypeId(TypeId.Player) || !unitTarget.IsTypeId(TypeId.Player)) + return; + Player caster = m_caster.ToPlayer(); + Player target = unitTarget.ToPlayer(); + + // caster or target already have requested duel + if (caster.duel != null || target.duel != null || target.GetSocial() == null || target.GetSocial().HasIgnore(caster.GetGUID())) + return; + + // Players can only fight a duel in zones with this flag + AreaTableRecord casterAreaEntry = CliDB.AreaTableStorage.LookupByKey(caster.GetAreaId()); + if (casterAreaEntry != null && !casterAreaEntry.Flags[0].HasAnyFlag(AreaFlags.AllowDuels)) + { + SendCastResult(SpellCastResult.NoDueling); // Dueling isn't allowed here + return; + } + + AreaTableRecord targetAreaEntry = CliDB.AreaTableStorage.LookupByKey(target.GetAreaId()); + if (targetAreaEntry != null && !targetAreaEntry.Flags[0].HasAnyFlag(AreaFlags.AllowDuels)) + { + SendCastResult(SpellCastResult.NoDueling); // Dueling isn't allowed here + return; + } + + //CREATE DUEL FLAG OBJECT + GameObject pGameObj = new GameObject(); + + int gameobject_id = effectInfo.MiscValue; + + Position pos = new Position() + { + posX = m_caster.GetPositionX() + (unitTarget.GetPositionX() - m_caster.GetPositionX()) / 2, + posY = m_caster.GetPositionY() + (unitTarget.GetPositionY() - m_caster.GetPositionY()) / 2, + posZ = m_caster.GetPositionZ(), + Orientation = m_caster.GetOrientation() + }; + + Map map = m_caster.GetMap(); + Quaternion rotation = new Quaternion(Matrix3.fromEulerAnglesZYX(pos.GetOrientation(), 0.0f, 0.0f)); + if (!pGameObj.Create((uint)gameobject_id, map, m_caster.GetPhaseMask(), pos, rotation, 0, GameObjectState.Ready)) + return; + + pGameObj.CopyPhaseFrom(m_caster); + + pGameObj.SetUInt32Value(GameObjectFields.Faction, m_caster.getFaction()); + pGameObj.SetUInt32Value(GameObjectFields.Level, m_caster.getLevel() + 1); + int duration = m_spellInfo.CalcDuration(m_caster); + pGameObj.SetRespawnTime(duration > 0 ? duration / Time.InMilliseconds : 0); + pGameObj.SetSpellId(m_spellInfo.Id); + + ExecuteLogEffectSummonObject(effIndex, pGameObj); + + m_caster.AddGameObject(pGameObj); + map.AddToMap(pGameObj); + //END + + // Send request + DuelRequested packet = new DuelRequested(); + packet.ArbiterGUID = pGameObj.GetGUID(); + packet.RequestedByGUID = caster.GetGUID(); + packet.RequestedByWowAccount = caster.GetSession().GetAccountGUID(); + packet.Write(); + + caster.SendPacket(packet, false); + target.SendPacket(packet, false); + + // create duel-info + DuelInfo duel = new DuelInfo(); + duel.initiator = caster; + duel.opponent = target; + duel.startTime = 0; + duel.startTimer = 0; + duel.isMounted = (GetSpellInfo().Id == 62875); // Mounted Duel + caster.duel = duel; + + DuelInfo duel2 = new DuelInfo(); + duel2.initiator = caster; + duel2.opponent = caster; + duel2.startTime = 0; + duel2.startTimer = 0; + duel2.isMounted = (GetSpellInfo().Id == 62875); // Mounted Duel + target.duel = duel2; + + caster.SetGuidValue(PlayerFields.DuelArbiter, pGameObj.GetGUID()); + target.SetGuidValue(PlayerFields.DuelArbiter, pGameObj.GetGUID()); + + Global.ScriptMgr.OnPlayerDuelRequest(target, caster); + } + + [SpellEffectHandler(SpellEffectName.Stuck)] + void EffectStuck(uint effIndex) + { + if (effectHandleMode != SpellEffectHandleMode.Hit) + return; + + if (!WorldConfig.GetBoolValue(WorldCfg.CastUnstuck)) + return; + + Player player = m_caster.ToPlayer(); + if (player == null) + return; + + Log.outDebug(LogFilter.Spells, "Spell Effect: Stuck"); + Log.outInfo(LogFilter.Spells, "Player {0} (guid {1}) used auto-unstuck future at map {2} ({3}, {4}, {5})", player.GetName(), player.GetGUID().ToString(), player.GetMapId(), player.GetPositionX(), player.GetPositionY(), player.GetPositionZ()); + + if (player.IsInFlight()) + return; + + // if player is dead without death timer is teleported to graveyard, otherwise not apply the effect + if (player.IsDead()) + { + if (player.GetDeathTimer() == 0) + player.RepopAtGraveyard(); + + return; + } + + // the player dies if hearthstone is in cooldown, else the player is teleported to home + if (player.GetSpellHistory().HasCooldown(8690)) + { + player.KillSelf(); + return; + } + + player.TeleportTo(player.GetHomebind(), TeleportToOptions.Spell); + + // Stuck spell trigger Hearthstone cooldown + SpellInfo spellInfo = Global.SpellMgr.GetSpellInfo(8690); + if (spellInfo == null) + return; + + Spell spell = new Spell(player, spellInfo, TriggerCastFlags.FullMask); + spell.SendSpellCooldown(); + } + + [SpellEffectHandler(SpellEffectName.SummonPlayer)] + void EffectSummonPlayer(uint effIndex) + { + // workaround - this effect should not use target map + if (effectHandleMode != SpellEffectHandleMode.HitTarget) + return; + + if (unitTarget == null || !unitTarget.IsTypeId(TypeId.Player)) + return; + + unitTarget.ToPlayer().SendSummonRequestFrom(m_caster); + } + + [SpellEffectHandler(SpellEffectName.ActivateObject)] + void EffectActivateObject(uint effIndex) + { + if (effectHandleMode != SpellEffectHandleMode.HitTarget) + return; + + if (gameObjTarget == null) + return; + + ScriptInfo activateCommand = new ScriptInfo(); + activateCommand.command = ScriptCommands.ActivateObject; + + // int unk = effectInfo.MiscValue; // This is set for EffectActivateObject spells; needs research + + gameObjTarget.GetMap().ScriptCommandStart(activateCommand, 0, m_caster, gameObjTarget); + } + + [SpellEffectHandler(SpellEffectName.ApplyGlyph)] + void EffectApplyGlyph(uint effIndex) + { + if (effectHandleMode != SpellEffectHandleMode.Hit) + return; + + Player player = m_caster.ToPlayer(); + if (player == null) + return; + + List glyphs = player.GetGlyphs(player.GetActiveTalentGroup()); + int replacedGlyph = glyphs.Count; + for (int i = 0; i < glyphs.Count; ++i) + { + List activeGlyphBindableSpells = Global.DB2Mgr.GetGlyphBindableSpells(glyphs[i]); + if (activeGlyphBindableSpells.Contains(m_misc.SpellId)) + { + replacedGlyph = i; + player.RemoveAurasDueToSpell(CliDB.GlyphPropertiesStorage.LookupByKey(glyphs[i]).SpellID); + break; + } + } + + uint glyphId = (uint)effectInfo.MiscValue; + if (replacedGlyph < glyphs.Count) + { + if (glyphId != 0) + glyphs[replacedGlyph] = glyphId; + else + glyphs.RemoveAt(replacedGlyph); + } + else if (glyphId != 0) + glyphs.Add(glyphId); + + GlyphPropertiesRecord glyphProperties = CliDB.GlyphPropertiesStorage.LookupByKey(glyphId); + if (glyphProperties != null) + player.CastSpell(player, glyphProperties.SpellID, true); + + ActiveGlyphs activeGlyphs = new ActiveGlyphs(); + activeGlyphs.Glyphs.Add(new GlyphBinding(m_misc.SpellId, (ushort)glyphId)); + activeGlyphs.IsFullUpdate = false; + player.SendPacket(activeGlyphs); + } + + [SpellEffectHandler(SpellEffectName.EnchantHeldItem)] + void EffectEnchantHeldItem(uint effIndex) + { + if (effectHandleMode != SpellEffectHandleMode.HitTarget) + return; + + // this is only item spell effect applied to main-hand weapon of target player (players in area) + if (unitTarget == null || !unitTarget.IsTypeId(TypeId.Player)) + return; + + Player item_owner = unitTarget.ToPlayer(); + Item item = item_owner.GetItemByPos(InventorySlots.Bag0, EquipmentSlot.MainHand); + + if (item == null) + return; + + // must be equipped + if (!item.IsEquipped()) + return; + + if (effectInfo.MiscValue != 0) + { + uint enchant_id = (uint)effectInfo.MiscValue; + int duration = m_spellInfo.GetDuration(); //Try duration index first .. + if (duration == 0) + duration = damage;//+1; //Base points after .. + if (duration == 0) + duration = 10 * Time.InMilliseconds; //10 seconds for enchants which don't have listed duration + + if (m_spellInfo.Id == 14792) // Venomhide Poison + duration = 5 * Time.Minute * Time.InMilliseconds; + + SpellItemEnchantmentRecord pEnchant = CliDB.SpellItemEnchantmentStorage.LookupByKey(enchant_id); + if (pEnchant == null) + return; + + // Always go to temp enchantment slot + EnchantmentSlot slot = EnchantmentSlot.Temp; + + // Enchantment will not be applied if a different one already exists + if (item.GetEnchantmentId(slot) != 0 && item.GetEnchantmentId(slot) != enchant_id) + return; + + // Apply the temporary enchantment + item.SetEnchantment(slot, enchant_id, (uint)duration, 0, m_caster.GetGUID()); + item_owner.ApplyEnchantment(item, slot, true); + } + } + + [SpellEffectHandler(SpellEffectName.Disenchant)] + void EffectDisEnchant(uint effIndex) + { + if (effectHandleMode != SpellEffectHandleMode.HitTarget) + return; + + if (itemTarget == null || itemTarget.GetTemplate().DisenchantID == 0) + return; + + Player caster = m_caster.ToPlayer(); + if (caster != null) + { + caster.UpdateCraftSkill(m_spellInfo.Id); + caster.SendLoot(itemTarget.GetGUID(), LootType.Disenchanting); + } + + // item will be removed at disenchanting end + } + + [SpellEffectHandler(SpellEffectName.Inebriate)] + void EffectInebriate(uint effIndex) + { + if (effectHandleMode != SpellEffectHandleMode.HitTarget) + return; + + if (unitTarget == null || !unitTarget.IsTypeId(TypeId.Player)) + return; + + Player player = unitTarget.ToPlayer(); + byte currentDrunk = player.GetDrunkValue(); + int drunkMod = damage; + if (currentDrunk + drunkMod > 100) + { + currentDrunk = 100; + if (RandomHelper.randChance() < 25.0f) + player.CastSpell(player, 67468, false); // Drunken Vomit + } + else + currentDrunk += (byte)drunkMod; + + player.SetDrunkValue(currentDrunk, m_CastItem != null ? m_CastItem.GetEntry() : 0); + } + + [SpellEffectHandler(SpellEffectName.FeedPet)] + void EffectFeedPet(uint effIndex) + { + if (effectHandleMode != SpellEffectHandleMode.HitTarget) + return; + + Player player = m_caster.ToPlayer(); + if (player == null) + return; + + Item foodItem = itemTarget; + if (foodItem == null) + return; + + Pet pet = player.GetPet(); + if (pet == null) + return; + + if (!pet.IsAlive()) + return; + + int benefit = (int)pet.GetCurrentFoodBenefitLevel(foodItem.GetTemplate().GetBaseItemLevel()); + if (benefit <= 0) + return; + + ExecuteLogEffectDestroyItem(effIndex, foodItem.GetEntry()); + + uint count = 1; + player.DestroyItemCount(foodItem, ref count, true); + // @todo fix crash when a spell has two effects, both pointed at the same item target + + m_caster.CastCustomSpell(pet, effectInfo.TriggerSpell, benefit, 0, 0, true); + } + + [SpellEffectHandler(SpellEffectName.DismissPet)] + void EffectDismissPet(uint effIndex) + { + if (effectHandleMode != SpellEffectHandleMode.HitTarget) + return; + + if (unitTarget == null || !unitTarget.IsPet()) + return; + + Pet pet = unitTarget.ToPet(); + + ExecuteLogEffectUnsummonObject(effIndex, pet); + pet.GetOwner().RemovePet(pet, PetSaveMode.NotInSlot); + } + + [SpellEffectHandler(SpellEffectName.SummonObjectSlot1)] + void EffectSummonObject(uint effIndex) + { + if (effectHandleMode != SpellEffectHandleMode.Hit) + return; + + uint go_id = (uint)effectInfo.MiscValue; + byte slot = (byte)(effectInfo.Effect - SpellEffectName.SummonObjectSlot1); + ObjectGuid guid = m_caster.m_ObjectSlot[slot]; + if (!guid.IsEmpty()) + { + GameObject obj = m_caster.GetMap().GetGameObject(guid); + if (obj != null) + { + // Recast case - null spell id to make auras not be removed on object remove from world + if (m_spellInfo.Id == obj.GetSpellId()) + obj.SetSpellId(0); + m_caster.RemoveGameObject(obj, true); + } + m_caster.m_ObjectSlot[slot].Clear(); + } + + GameObject go = new GameObject(); + + float x, y, z; + // If dest location if present + if (m_targets.HasDst()) + destTarget.GetPosition(out x, out y, out z); + // Summon in random point all other units if location present + else + m_caster.GetClosePoint(out x, out y, out z, SharedConst.DefaultWorldObjectSize); + + Map map = m_caster.GetMap(); + Quaternion rotation = new Quaternion(Matrix3.fromEulerAnglesZYX(m_caster.GetOrientation(), 0.0f, 0.0f)); + if (!go.Create(go_id, map, m_caster.GetPhaseMask(), new Position(x, y, z, m_caster.GetOrientation()), rotation, 255, GameObjectState.Ready)) + return; + + go.CopyPhaseFrom(m_caster); + + int duration = m_spellInfo.CalcDuration(m_caster); + go.SetRespawnTime(duration > 0 ? duration / Time.InMilliseconds : 0); + go.SetSpellId(m_spellInfo.Id); + m_caster.AddGameObject(go); + + ExecuteLogEffectSummonObject(effIndex, go); + + map.AddToMap(go); + + m_caster.m_ObjectSlot[slot] = go.GetGUID(); + } + + [SpellEffectHandler(SpellEffectName.Resurrect)] + void EffectResurrect(uint effIndex) + { + if (effectHandleMode != SpellEffectHandleMode.HitTarget) + return; + + if (unitTarget == null || !unitTarget.IsTypeId(TypeId.Player)) + return; + + if (unitTarget.IsAlive() || !unitTarget.IsInWorld) + return; + + Player target = unitTarget.ToPlayer(); + + if (target.IsResurrectRequested()) // already have one active request + return; + + uint health = (uint)target.CountPctFromMaxHealth(damage); + uint mana = (uint)MathFunctions.CalculatePct(target.GetMaxPower(PowerType.Mana), damage); + + ExecuteLogEffectResurrect(effIndex, target); + + target.SetResurrectRequestData(m_caster, health, mana, 0); + SendResurrectRequest(target); + } + + [SpellEffectHandler(SpellEffectName.AddExtraAttacks)] + void EffectAddExtraAttacks(uint effIndex) + { + if (effectHandleMode != SpellEffectHandleMode.HitTarget) + return; + + if (!unitTarget || !unitTarget.IsAlive()) + return; + + if (unitTarget.m_extraAttacks != 0) + return; + + unitTarget.m_extraAttacks = (uint)damage; + + ExecuteLogEffectExtraAttacks(effIndex, unitTarget, (uint)damage); + } + + [SpellEffectHandler(SpellEffectName.Parry)] + void EffectParry(uint effIndex) + { + if (effectHandleMode != SpellEffectHandleMode.Hit) + return; + + if (m_caster.IsTypeId(TypeId.Player)) + m_caster.ToPlayer().SetCanParry(true); + } + + [SpellEffectHandler(SpellEffectName.Block)] + void EffectBlock(uint effIndex) + { + if (effectHandleMode != SpellEffectHandleMode.Hit) + return; + + if (m_caster.IsTypeId(TypeId.Player)) + m_caster.ToPlayer().SetCanBlock(true); + } + + [SpellEffectHandler(SpellEffectName.Leap)] + void EffectLeap(uint effIndex) + { + if (effectHandleMode != SpellEffectHandleMode.HitTarget) + return; + + if (unitTarget == null || unitTarget.IsInFlight()) + return; + + if (!m_targets.HasDst()) + return; + + Position pos = destTarget.GetPosition(); + pos = unitTarget.GetFirstCollisionPosition(unitTarget.GetDistance(pos.posX, pos.posY, pos.posZ + 2.0f), 0.0f); + unitTarget.NearTeleportTo(pos.posX, pos.posY, pos.posZ, pos.Orientation, unitTarget == m_caster); + } + + [SpellEffectHandler(SpellEffectName.Reputation)] + void EffectReputation(uint effIndex) + { + if (effectHandleMode != SpellEffectHandleMode.HitTarget) + return; + + if (unitTarget == null || !unitTarget.IsTypeId(TypeId.Player)) + return; + + Player player = unitTarget.ToPlayer(); + + int repChange = damage; + + int factionId = effectInfo.MiscValue; + + FactionRecord factionEntry = CliDB.FactionStorage.LookupByKey(factionId); + if (factionEntry == null) + return; + + repChange = player.CalculateReputationGain(ReputationSource.Spell, 0, repChange, factionId); + + player.GetReputationMgr().ModifyReputation(factionEntry, repChange); + } + + [SpellEffectHandler(SpellEffectName.QuestComplete)] + void EffectQuestComplete(uint effIndex) + { + if (effectHandleMode != SpellEffectHandleMode.HitTarget) + return; + + if (unitTarget == null || !unitTarget.IsTypeId(TypeId.Player)) + return; + Player player = unitTarget.ToPlayer(); + + uint questId = (uint)effectInfo.MiscValue; + if (questId != 0) + { + Quest quest = Global.ObjectMgr.GetQuestTemplate(questId); + if (quest == null) + return; + + ushort logSlot = player.FindQuestSlot(questId); + if (logSlot < SharedConst.MaxQuestLogSize) + player.AreaExploredOrEventHappens(questId); + else if (player.CanTakeQuest(quest, false)) // Check if the quest has already been turned in. + player.SetRewardedQuest(questId); // If not, set status to rewarded without broadcasting it to client. + } + } + + [SpellEffectHandler(SpellEffectName.ForceDeselect)] + void EffectForceDeselect(uint effIndex) + { + if (effectHandleMode != SpellEffectHandleMode.Hit) + return; + + ClearTarget clearTarget = new ClearTarget(); + clearTarget.Guid = m_caster.GetGUID(); + m_caster.SendMessageToSet(clearTarget, true); + } + + [SpellEffectHandler(SpellEffectName.SelfResurrect)] + void EffectSelfResurrect(uint effIndex) + { + if (effectHandleMode != SpellEffectHandleMode.Hit) + return; + + if (m_caster == null || m_caster.IsAlive()) + return; + if (!m_caster.IsTypeId(TypeId.Player)) + return; + if (!m_caster.IsInWorld) + return; + + uint health = 0; + int mana = 0; + + // flat case + if (damage < 0) + { + health = (uint)-damage; + mana = effectInfo.MiscValue; + } + // percent case + else + { + health = (uint)m_caster.CountPctFromMaxHealth(damage); + if (m_caster.GetMaxPower(PowerType.Mana) > 0) + mana = MathFunctions.CalculatePct(m_caster.GetMaxPower(PowerType.Mana), damage); + } + + Player player = m_caster.ToPlayer(); + player.ResurrectPlayer(0.0f); + + player.SetHealth(health); + player.SetPower(PowerType.Mana, mana); + player.SetPower(PowerType.Rage, 0); + player.SetPower(PowerType.Energy, player.GetMaxPower(PowerType.Energy)); + player.SetPower(PowerType.Focus, 0); + + player.SpawnCorpseBones(); + } + + [SpellEffectHandler(SpellEffectName.Skinning)] + void EffectSkinning(uint effIndex) + { + if (effectHandleMode != SpellEffectHandleMode.HitTarget) + return; + + if (!unitTarget.IsTypeId(TypeId.Unit)) + return; + if (!m_caster.IsTypeId(TypeId.Player)) + return; + + Creature creature = unitTarget.ToCreature(); + int targetLevel = (int)creature.getLevel(); + + SkillType skill = creature.GetCreatureTemplate().GetRequiredLootSkill(); + + m_caster.ToPlayer().SendLoot(creature.GetGUID(), LootType.Skinning); + creature.RemoveFlag(UnitFields.Flags, UnitFlags.Skinnable); + creature.SetFlag(ObjectFields.DynamicFlags, UnitDynFlags.Lootable); + + uint reqValue = (uint)(targetLevel < 10 ? 0 : targetLevel < 20 ? (targetLevel - 10) * 10 : targetLevel * 5); + + uint skillValue = m_caster.ToPlayer().GetPureSkillValue(skill); + + // Double chances for elites + m_caster.ToPlayer().UpdateGatherSkill(skill, skillValue, reqValue, (uint)(creature.isElite() ? 2 : 1)); + } + + [SpellEffectHandler(SpellEffectName.Charge)] + void EffectCharge(uint effIndex) + { + if (unitTarget == null) + return; + + if (effectHandleMode == SpellEffectHandleMode.LaunchTarget) + { + float speed = MathFunctions.fuzzyGt(m_spellInfo.Speed, 0.0f) ? m_spellInfo.Speed : MotionMaster.SPEED_CHARGE; + SpellEffectExtraData spellEffectExtraData = null; + if (effectInfo.MiscValueB != 0) + { + spellEffectExtraData = new SpellEffectExtraData(); + spellEffectExtraData.Target = unitTarget.GetGUID(); + spellEffectExtraData.SpellVisualId = (uint)effectInfo.MiscValueB; + } + // Spell is not using explicit target - no generated path + if (m_preGeneratedPath.GetPathType() == PathType.Blank) + { + Position pos = unitTarget.GetFirstCollisionPosition(unitTarget.GetObjectSize(), unitTarget.GetRelativeAngle(m_caster.GetPosition())); + m_caster.GetMotionMaster().MoveCharge(pos.posX, pos.posY, pos.posZ, speed, EventId.Charge, false, unitTarget, spellEffectExtraData); + } + else + m_caster.GetMotionMaster().MoveCharge(m_preGeneratedPath, speed, unitTarget, spellEffectExtraData); + } + + if (effectHandleMode == SpellEffectHandleMode.HitTarget) + { + // not all charge effects used in negative spells + if (!m_spellInfo.IsPositive() && m_caster.IsTypeId(TypeId.Player)) + m_caster.Attack(unitTarget, true); + + if (effectInfo.TriggerSpell != 0) + m_caster.CastSpell(unitTarget, effectInfo.TriggerSpell, true, null, null, m_originalCasterGUID); + } + } + + [SpellEffectHandler(SpellEffectName.ChargeDest)] + void EffectChargeDest(uint effIndex) + { + if (destTarget == null) + return; + + if (effectHandleMode == SpellEffectHandleMode.Launch) + { + Position pos = destTarget.GetPosition(); + float angle = m_caster.GetRelativeAngle(pos.posX, pos.posY); + float dist = m_caster.GetDistance(pos); + pos = m_caster.GetFirstCollisionPosition(dist, angle); + + m_caster.GetMotionMaster().MoveCharge(pos.posX, pos.posY, pos.posZ); + } + else if (effectHandleMode == SpellEffectHandleMode.Hit) + { + if (effectInfo.TriggerSpell != 0) + m_caster.CastSpell(destTarget.GetPositionX(), destTarget.GetPositionY(), destTarget.GetPositionZ(), effectInfo.TriggerSpell, true, null, null, m_originalCasterGUID); + } + } + + [SpellEffectHandler(SpellEffectName.KnockBack)] + [SpellEffectHandler(SpellEffectName.KnockBackDest)] + void EffectKnockBack(uint effIndex) + { + if (effectHandleMode != SpellEffectHandleMode.HitTarget) + return; + + if (!unitTarget) + return; + + Creature creatureTarget = unitTarget.ToCreature(); + if (creatureTarget) + if (creatureTarget.isWorldBoss() || creatureTarget.IsDungeonBoss()) + return; + + // Spells with SPELL_EFFECT_KNOCK_BACK (like Thunderstorm) can't knockback target if target has ROOT/STUN + if (unitTarget.HasUnitState(UnitState.Root | UnitState.Stunned)) + return; + + // Instantly interrupt non melee spells being casted + if (unitTarget.IsNonMeleeSpellCast(true)) + unitTarget.InterruptNonMeleeSpells(true); + + float ratio = 0.1f; + float speedxy = effectInfo.MiscValue * ratio; + float speedz = damage * ratio; + if (speedxy < 0.1f && speedz < 0.1f) + return; + + float x, y; + if (effectInfo.Effect == SpellEffectName.KnockBackDest) + { + if (m_targets.HasDst()) + destTarget.GetPosition(out x, out y); + else + return; + } + else //if (GetEffect(i].Effect == SPELL_EFFECT_KNOCK_BACK) + { + m_caster.GetPosition(out x, out y); + } + + unitTarget.KnockbackFrom(x, y, speedxy, speedz); + } + + [SpellEffectHandler(SpellEffectName.LeapBack)] + void EffectLeapBack(uint effIndex) + { + if (effectHandleMode != SpellEffectHandleMode.LaunchTarget) + return; + + if (unitTarget == null) + return; + + float speedxy = effectInfo.MiscValue / 10.0f; + float speedz = damage / 10.0f; + // Disengage + unitTarget.JumpTo(speedxy, speedz, m_spellInfo.IconFileDataId != 132572); + } + + [SpellEffectHandler(SpellEffectName.ClearQuest)] + void EffectQuestClear(uint effIndex) + { + if (effectHandleMode != SpellEffectHandleMode.HitTarget) + return; + + if (unitTarget == null || !unitTarget.IsTypeId(TypeId.Player)) + return; + Player player = unitTarget.ToPlayer(); + + uint quest_id = (uint)effectInfo.MiscValue; + + Quest quest = Global.ObjectMgr.GetQuestTemplate(quest_id); + + if (quest == null) + return; + + // Player has never done this quest + if (player.GetQuestStatus(quest_id) == QuestStatus.None) + return; + + // remove all quest entries for 'entry' from quest log + for (byte slot = 0; slot < SharedConst.MaxQuestLogSize; ++slot) + { + uint logQuest = player.GetQuestSlotQuestId(slot); + if (logQuest == quest_id) + { + player.SetQuestSlot(slot, 0); + + // we ignore unequippable quest items in this case, it's still be equipped + player.TakeQuestSourceItem(logQuest, false); + + if (quest.HasFlag(QuestFlags.Pvp)) + { + player.pvpInfo.IsHostile = player.pvpInfo.IsInHostileArea || player.HasPvPForcingQuest(); + player.UpdatePvPState(); + } + } + } + + player.RemoveActiveQuest(quest_id, false); + player.RemoveRewardedQuest(quest_id); + + Global.ScriptMgr.OnQuestStatusChange(player, quest_id); + } + + [SpellEffectHandler(SpellEffectName.SendTaxi)] + void EffectSendTaxi(uint effIndex) + { + if (effectHandleMode != SpellEffectHandleMode.HitTarget) + return; + + if (unitTarget == null || !unitTarget.IsTypeId(TypeId.Player)) + return; + + unitTarget.ToPlayer().ActivateTaxiPathTo((uint)effectInfo.MiscValue, m_spellInfo.Id); + } + + [SpellEffectHandler(SpellEffectName.PullTowards)] + [SpellEffectHandler(SpellEffectName.PullTowardsDest)] + void EffectPullTowards(uint effIndex) + { + if (effectHandleMode != SpellEffectHandleMode.HitTarget) + return; + + if (!unitTarget) + return; + + Position pos = new Position(); + if (effectInfo.Effect == SpellEffectName.PullTowardsDest) + { + if (m_targets.HasDst()) + pos.Relocate(destTarget); + else + return; + } + else + { + pos.Relocate(m_caster.GetPosition()); + } + + float speedXY = effectInfo.MiscValue * 0.1f; + float speedZ = (float)(unitTarget.GetDistance(pos) / speedXY * 0.5f * MotionMaster.gravity); + + unitTarget.GetMotionMaster().MoveJump(pos, speedXY, speedZ); + } + + [SpellEffectHandler(SpellEffectName.ChangeRaidMarker)] + void EffectChangeRaidMarker(uint effIndex) + { + if (effectHandleMode != SpellEffectHandleMode.Hit) + return; + + Player player = m_caster.ToPlayer(); + if (!player || !m_targets.HasDst()) + return; + + Group group = player.GetGroup(); + if (!group || (group.isRaidGroup() && !group.IsLeader(player.GetGUID()) && !group.IsAssistant(player.GetGUID()))) + return; + + float x, y, z; + destTarget.GetPosition(out x, out y, out z); + + group.AddRaidMarker((byte)damage, player.GetMapId(), x, y, z); + } + + [SpellEffectHandler(SpellEffectName.DispelMechanic)] + void EffectDispelMechanic(uint effIndex) + { + if (effectHandleMode != SpellEffectHandleMode.HitTarget) + return; + + if (unitTarget == null) + return; + + int mechanic = effectInfo.MiscValue; + + List> dispel_list = new List>(); + + var auras = unitTarget.GetOwnedAuras(); + foreach (var pair in auras) + { + Aura aura = pair.Value; + if (aura.GetApplicationOfTarget(unitTarget.GetGUID()) == null) + continue; + + if (RandomHelper.randChance(aura.CalcDispelChance(unitTarget, !unitTarget.IsFriendlyTo(m_caster)))) + if (Convert.ToBoolean(aura.GetSpellInfo().GetAllEffectsMechanicMask() & (1 << mechanic))) + dispel_list.Add(new KeyValuePair(aura.GetId(), aura.GetCasterGUID())); + } + + while (!dispel_list.Empty()) + { + unitTarget.RemoveAura(dispel_list[0].Key, dispel_list[0].Value, 0, AuraRemoveMode.EnemySpell); + dispel_list.RemoveAt(0); + } + } + + [SpellEffectHandler(SpellEffectName.ResurrectPet)] + void EffectSummonDeadPet(uint effIndex) + { + if (effectHandleMode != SpellEffectHandleMode.Hit) + return; + + Player player = m_caster.ToPlayer(); + if (player == null) + return; + + Pet pet = player.GetPet(); + if (pet != null && pet.IsAlive()) + return; + + if (damage < 0) + return; + + float x, y, z; + player.GetPosition(out x, out y, out z); + if (pet == null) + { + player.SummonPet(0, x, y, z, player.Orientation, PetType.Summon, 0); + pet = player.GetPet(); + } + if (pet == null) + return; + + player.GetMap().CreatureRelocation(pet, x, y, z, player.GetOrientation()); + + pet.SetUInt32Value(ObjectFields.DynamicFlags, (uint)UnitDynFlags.HideModel); + pet.RemoveFlag(UnitFields.Flags, UnitFlags.Skinnable); + pet.setDeathState(DeathState.Alive); + pet.ClearUnitState(UnitState.AllState); + pet.SetHealth(pet.CountPctFromMaxHealth(damage)); + + pet.InitializeAI(); + player.PetSpellInitialize(); + pet.SavePetToDB(PetSaveMode.AsCurrent); + } + + [SpellEffectHandler(SpellEffectName.DestroyAllTotems)] + void EffectDestroyAllTotems(uint effIndex) + { + if (effectHandleMode != SpellEffectHandleMode.Hit) + return; + + int mana = 0; + for (byte slot = (int)SummonSlot.Totem; slot < SharedConst.MaxTotemSlot; ++slot) + { + if (m_caster.m_SummonSlot[slot].IsEmpty()) + continue; + + Creature totem = m_caster.GetMap().GetCreature(m_caster.m_SummonSlot[slot]); + if (totem != null && totem.IsTotem()) + { + uint spell_id = totem.GetUInt32Value(UnitFields.CreatedBySpell); + SpellInfo spellInfo = Global.SpellMgr.GetSpellInfo(spell_id); + if (spellInfo != null) + { + var costs = spellInfo.CalcPowerCost(m_caster, spellInfo.GetSchoolMask()); + var m = costs.Find(cost => cost.Power == PowerType.Mana); + if (m != null) + mana += m.Amount; + } + totem.ToTotem().UnSummon(); + } + } + MathFunctions.ApplyPct(ref mana, damage); + if (mana != 0) + m_caster.CastCustomSpell(m_caster, 39104, mana, 0, 0, true); + } + + [SpellEffectHandler(SpellEffectName.DurabilityDamage)] + void EffectDurabilityDamage(uint effIndex) + { + if (effectHandleMode != SpellEffectHandleMode.HitTarget) + return; + + if (unitTarget == null || !unitTarget.IsTypeId(TypeId.Player)) + return; + + int slot = effectInfo.MiscValue; + + // -1 means all player equipped items and -2 all items + if (slot < 0) + { + unitTarget.ToPlayer().DurabilityPointsLossAll(damage, (slot < -1)); + ExecuteLogEffectDurabilityDamage(effIndex, unitTarget, -1, -1); + return; + } + + // invalid slot value + if (slot >= InventorySlots.BagEnd) + return; + + Item item = unitTarget.ToPlayer().GetItemByPos(InventorySlots.Bag0, (byte)slot); + if (item != null) + { + unitTarget.ToPlayer().DurabilityPointsLoss(item, damage); + ExecuteLogEffectDurabilityDamage(effIndex, unitTarget, (int)item.GetEntry(), slot); + } + } + + [SpellEffectHandler(SpellEffectName.DurabilityDamagePct)] + void EffectDurabilityDamagePCT(uint effIndex) + { + if (effectHandleMode != SpellEffectHandleMode.HitTarget) + return; + + if (unitTarget == null || !unitTarget.IsTypeId(TypeId.Player)) + return; + + int slot = effectInfo.MiscValue; + + // FIXME: some spells effects have value -1/-2 + // Possibly its mean -1 all player equipped items and -2 all items + if (slot < 0) + { + unitTarget.ToPlayer().DurabilityLossAll(damage / 100.0f, (slot < -1)); + return; + } + + // invalid slot value + if (slot >= InventorySlots.BagEnd) + return; + + if (damage <= 0) + return; + + Item item = unitTarget.ToPlayer().GetItemByPos(InventorySlots.Bag0, (byte)slot); + if (item != null) + unitTarget.ToPlayer().DurabilityLoss(item, damage / 100.0f); + } + + [SpellEffectHandler(SpellEffectName.ModifyThreatPercent)] + void EffectModifyThreatPercent(uint effIndex) + { + if (effectHandleMode != SpellEffectHandleMode.HitTarget) + return; + + if (unitTarget == null) + return; + + unitTarget.GetThreatManager().modifyThreatPercent(m_caster, damage); + } + + [SpellEffectHandler(SpellEffectName.TransDoor)] + void EffectTransmitted(uint effIndex) + { + if (effectHandleMode != SpellEffectHandleMode.Hit) + return; + + uint name_id = (uint)effectInfo.MiscValue; + + var overrideSummonedGameObjects = m_caster.GetAuraEffectsByType(AuraType.OverrideSummonedObject); + foreach (AuraEffect aurEff in overrideSummonedGameObjects) + { + if (aurEff.GetMiscValue() == name_id) + { + name_id = (uint)aurEff.GetMiscValueB(); + break; + } + } + + GameObjectTemplate goinfo = Global.ObjectMgr.GetGameObjectTemplate(name_id); + if (goinfo == null) + { + Log.outError(LogFilter.Sql, "Gameobject (Entry: {0}) not exist and not created at spell (ID: {1}) cast", name_id, m_spellInfo.Id); + return; + } + + float fx, fy, fz; + + if (m_targets.HasDst()) + destTarget.GetPosition(out fx, out fy, out fz); + //FIXME: this can be better check for most objects but still hack + else if (effectInfo.HasRadius() && m_spellInfo.Speed == 0) + { + float dis = effectInfo.CalcRadius(m_originalCaster); + m_caster.GetClosePoint(out fx, out fy, out fz, SharedConst.DefaultWorldObjectSize, dis); + } + else + { + //GO is always friendly to it's creator, get range for friends + float min_dis = m_spellInfo.GetMinRange(true); + float max_dis = m_spellInfo.GetMaxRange(true); + float dis = (float)RandomHelper.NextDouble() * (max_dis - min_dis) + min_dis; + + m_caster.GetClosePoint(out fx, out fy, out fz, SharedConst.DefaultWorldObjectSize, dis); + } + + Map cMap = m_caster.GetMap(); + // if gameobject is summoning object, it should be spawned right on caster's position + if (goinfo.type == GameObjectTypes.Ritual) + m_caster.GetPosition(out fx, out fy, out fz); + + GameObject pGameObj = new GameObject(); + + Position pos = new Position(fx, fy, fz, m_caster.GetOrientation()); + Quaternion rotation = new Quaternion(Matrix3.fromEulerAnglesZYX(m_caster.GetOrientation(), 0.0f, 0.0f)); + if (!pGameObj.Create(name_id, cMap, m_caster.GetPhaseMask(), pos, rotation, 255, GameObjectState.Ready)) + return; + + pGameObj.CopyPhaseFrom(m_caster); + + int duration = m_spellInfo.CalcDuration(m_caster); + + switch (goinfo.type) + { + case GameObjectTypes.FishingNode: + { + m_caster.AddChannelObject(pGameObj.GetGUID()); + m_caster.AddGameObject(pGameObj); // will removed at spell cancel + + // end time of range when possible catch fish (FISHING_BOBBER_READY_TIME..GetDuration(m_spellInfo)) + // start time == fish-FISHING_BOBBER_READY_TIME (0..GetDuration(m_spellInfo)-FISHING_BOBBER_READY_TIME) + int lastSec = 0; + switch (RandomHelper.IRand(0, 3)) + { + case 0: lastSec = 3; break; + case 1: lastSec = 7; break; + case 2: lastSec = 13; break; + case 3: lastSec = 17; break; + } + + duration = duration - lastSec * Time.InMilliseconds + 5 * Time.InMilliseconds; + break; + } + case GameObjectTypes.Ritual: + { + if (m_caster.IsTypeId(TypeId.Player)) + { + pGameObj.AddUniqueUse(m_caster.ToPlayer()); + m_caster.AddGameObject(pGameObj); // will be removed at spell cancel + } + break; + } + case GameObjectTypes.DuelArbiter: // 52991 + m_caster.AddGameObject(pGameObj); + break; + case GameObjectTypes.FishingHole: + case GameObjectTypes.Chest: + default: + break; + } + + pGameObj.SetRespawnTime(duration > 0 ? duration / Time.InMilliseconds : 0); + pGameObj.SetOwnerGUID(m_caster.GetGUID()); + pGameObj.SetSpellId(m_spellInfo.Id); + + ExecuteLogEffectSummonObject(effIndex, pGameObj); + + Log.outDebug(LogFilter.Spells, "AddObject at SpellEfects.cpp EffectTransmitted"); + + cMap.AddToMap(pGameObj); + uint linkedEntry = pGameObj.GetGoInfo().GetLinkedGameObjectEntry(); + if (linkedEntry != 0) + { + GameObject linkedGO = new GameObject(); + if (linkedGO.Create(linkedEntry, cMap, m_caster.GetPhaseMask(), pos, rotation, 255, GameObjectState.Ready)) + { + linkedGO.CopyPhaseFrom(m_caster); + + linkedGO.SetRespawnTime(duration > 0 ? duration / Time.InMilliseconds : 0); + linkedGO.SetSpellId(m_spellInfo.Id); + linkedGO.SetOwnerGUID(m_caster.GetGUID()); + ExecuteLogEffectSummonObject(effIndex, linkedGO); + + linkedGO.GetMap().AddToMap(linkedGO); + } + else + { + linkedGO = null; + return; + } + } + } + + [SpellEffectHandler(SpellEffectName.Prospecting)] + void EffectProspecting(uint effIndex) + { + if (effectHandleMode != SpellEffectHandleMode.HitTarget) + return; + + Player player = m_caster.ToPlayer(); + if (player == null) + return; + + if (itemTarget == null || !itemTarget.GetTemplate().GetFlags().HasAnyFlag(ItemFlags.IsProspectable)) + return; + + if (itemTarget.GetCount() < 5) + return; + + if (WorldConfig.GetBoolValue(WorldCfg.SkillProspecting)) + { + uint SkillValue = player.GetPureSkillValue(SkillType.Jewelcrafting); + uint reqSkillValue = itemTarget.GetTemplate().GetRequiredSkillRank(); + player.UpdateGatherSkill(SkillType.Jewelcrafting, SkillValue, reqSkillValue); + } + + player.SendLoot(itemTarget.GetGUID(), LootType.Prospecting); + } + + [SpellEffectHandler(SpellEffectName.Milling)] + void EffectMilling(uint effIndex) + { + if (effectHandleMode != SpellEffectHandleMode.HitTarget) + return; + + Player player = m_caster.ToPlayer(); + if (player == null) + return; + + if (itemTarget == null || !itemTarget.GetTemplate().GetFlags().HasAnyFlag(ItemFlags.IsMillable)) + return; + + if (itemTarget.GetCount() < 5) + return; + + if (WorldConfig.GetBoolValue(WorldCfg.SkillMilling)) + { + uint SkillValue = player.GetPureSkillValue(SkillType.Inscription); + uint reqSkillValue = itemTarget.GetTemplate().GetRequiredSkillRank(); + player.UpdateGatherSkill(SkillType.Inscription, SkillValue, reqSkillValue); + } + + player.SendLoot(itemTarget.GetGUID(), LootType.Milling); + } + + [SpellEffectHandler(SpellEffectName.Skill)] + void EffectSkill(uint effIndex) + { + if (effectHandleMode != SpellEffectHandleMode.Hit) + return; + + Log.outDebug(LogFilter.Spells, "WORLD: SkillEFFECT"); + } + + /* There is currently no need for this effect. We handle it in Battleground.cpp + If we would handle the resurrection here, the spiritguide would instantly disappear as the + player revives, and so we wouldn't see the spirit heal visual effect on the npc. + This is why we use a half sec delay between the visual effect and the resurrection itself */ + void EffectSpiritHeal(uint effIndex) + { + if (effectHandleMode != SpellEffectHandleMode.HitTarget) + return; + } + + // remove insignia spell effect + [SpellEffectHandler(SpellEffectName.SkinPlayerCorpse)] + void EffectSkinPlayerCorpse(uint effIndex) + { + if (effectHandleMode != SpellEffectHandleMode.HitTarget) + return; + + Log.outDebug(LogFilter.Spells, "Effect: SkinPlayerCorpse"); + + Player player = m_caster.ToPlayer(); + Player target = unitTarget.ToPlayer(); + if (player == null || target == null || target.IsAlive()) + return; + + target.RemovedInsignia(player); + } + + [SpellEffectHandler(SpellEffectName.StealBeneficialBuff)] + void EffectStealBeneficialBuff(uint effIndex) + { + if (effectHandleMode != SpellEffectHandleMode.HitTarget) + return; + + Log.outDebug(LogFilter.Spells, "Effect: StealBeneficialBuff"); + + if (unitTarget == null || unitTarget == m_caster) // can't steal from self + return; + + List> steal_list = new List>(); + + // Create dispel mask by dispel type + uint dispelMask = SpellInfo.GetDispelMask((DispelType)effectInfo.MiscValue); + var auras = unitTarget.GetOwnedAuras(); + foreach (var map in auras) + { + Aura aura = map.Value; + AuraApplication aurApp = aura.GetApplicationOfTarget(unitTarget.GetGUID()); + if (aurApp == null) + continue; + + if (Convert.ToBoolean(aura.GetSpellInfo().GetDispelMask() & dispelMask)) + { + // Need check for passive? this + if (!aurApp.IsPositive() || aura.IsPassive() || aura.GetSpellInfo().HasAttribute(SpellAttr4.NotStealable)) + continue; + + // The charges / stack amounts don't count towards the total number of auras that can be dispelled. + // Ie: A dispel on a target with 5 stacks of Winters Chill and a Polymorph has 1 / (1 + 1) . 50% chance to dispell + // Polymorph instead of 1 / (5 + 1) . 16%. + bool dispelCharges = aura.GetSpellInfo().HasAttribute(SpellAttr7.DispelCharges); + byte charges = dispelCharges ? aura.GetCharges() : aura.GetStackAmount(); + if (charges > 0) + steal_list.Add(Tuple.Create(aura, charges)); + } + } + + if (steal_list.Empty()) + return; + + // Ok if exist some buffs for dispel try dispel it + List> success_list = new List>(); + + DispelFailed dispelFailed = new DispelFailed(); + dispelFailed.CasterGUID = m_caster.GetGUID(); + dispelFailed.VictimGUID = unitTarget.GetGUID(); + dispelFailed.SpellID = m_spellInfo.Id; + + // dispel N = damage buffs (or while exist buffs for dispel) + for (int count = 0; count < damage && !steal_list.Empty();) + { + // Random select buff for dispel + var pair = steal_list[RandomHelper.IRand(0, steal_list.Count - 1)]; + + int chance = pair.Item1.CalcDispelChance(unitTarget, !unitTarget.IsFriendlyTo(m_caster)); + // 2.4.3 Patch Notes: "Dispel effects will no longer attempt to remove effects that have 100% dispel resistance." + if (chance == 0) + { + steal_list.Remove(pair); + continue; + } + else + { + if (RandomHelper.randChance(chance)) + { + success_list.Add(new KeyValuePair(pair.Item1.GetId(), pair.Item1.GetCasterGUID())); + var temp = pair.Item2; + --temp; + pair = Tuple.Create(pair.Item1, temp); + if (pair.Item2 <= 0) + steal_list.Remove(pair); + } + else + dispelFailed.FailedSpells.Add(pair.Item1.GetId()); + + ++count; + } + } + + if (!dispelFailed.FailedSpells.Empty()) + m_caster.SendMessageToSet(dispelFailed, true); + + if (success_list.Empty()) + return; + + SpellDispellLog spellDispellLog = new SpellDispellLog(); + spellDispellLog.IsBreak = false; // TODO: use me + spellDispellLog.IsSteal = true; + + spellDispellLog.TargetGUID = unitTarget.GetGUID(); + spellDispellLog.CasterGUID = m_caster.GetGUID(); + spellDispellLog.DispelledBySpellID = m_spellInfo.Id; + + foreach (var dispell in success_list) + { + var dispellData = new SpellDispellData(); + dispellData.SpellID = dispell.Key; + dispellData.Harmful = false; // TODO: use me + //dispellData.Rolled = none; // TODO: use me + //dispellData.Needed = none; // TODO: use me + + unitTarget.RemoveAurasDueToSpellBySteal(dispell.Key, dispell.Value, m_caster); + + spellDispellLog.DispellData.Add(dispellData); + } + m_caster.SendMessageToSet(spellDispellLog, true); + } + + [SpellEffectHandler(SpellEffectName.KillCredit)] + void EffectKillCreditPersonal(uint effIndex) + { + if (effectHandleMode != SpellEffectHandleMode.HitTarget) + return; + + if (unitTarget == null || !unitTarget.IsTypeId(TypeId.Player)) + return; + + unitTarget.ToPlayer().KilledMonsterCredit((uint)effectInfo.MiscValue); + } + + [SpellEffectHandler(SpellEffectName.KillCredit2)] + void EffectKillCredit(uint effIndex) + { + if (effectHandleMode != SpellEffectHandleMode.HitTarget) + return; + + if (unitTarget == null || !unitTarget.IsTypeId(TypeId.Player)) + return; + + int creatureEntry = effectInfo.MiscValue; + if (creatureEntry == 0) + { + if (m_spellInfo.Id == 42793) // Burn Body + creatureEntry = 24008; // Fallen Combatant + } + + if (creatureEntry != 0) + unitTarget.ToPlayer().RewardPlayerAndGroupAtEvent((uint)creatureEntry, unitTarget); + } + + [SpellEffectHandler(SpellEffectName.QuestFail)] + void EffectQuestFail(uint effIndex) + { + if (effectHandleMode != SpellEffectHandleMode.HitTarget) + return; + + if (unitTarget == null || !unitTarget.IsTypeId(TypeId.Player)) + return; + + unitTarget.ToPlayer().FailQuest((uint)effectInfo.MiscValue); + } + + [SpellEffectHandler(SpellEffectName.QuestStart)] + void EffectQuestStart(uint effIndex) + { + if (effectHandleMode != SpellEffectHandleMode.HitTarget) + return; + + if (!unitTarget) + return; + + Player player = unitTarget.ToPlayer(); + if (!player) + return; + + Quest qInfo = Global.ObjectMgr.GetQuestTemplate((uint)effectInfo.MiscValue); + if (qInfo != null) + { + if (!player.CanTakeQuest(qInfo, false)) + return; + + if (qInfo.IsAutoAccept() && player.CanAddQuest(qInfo, false)) + player.AddQuestAndCheckCompletion(qInfo, null); + + player.PlayerTalkClass.SendQuestGiverQuestDetails(qInfo, player.GetGUID(), true); + } + } + + [SpellEffectHandler(SpellEffectName.ActivateRune)] + void EffectActivateRune(uint effIndex) + { + if (effectHandleMode != SpellEffectHandleMode.Launch) + return; + + if (!m_caster.IsTypeId(TypeId.Player)) + return; + + Player player = m_caster.ToPlayer(); + + if (player.GetClass() != Class.Deathknight) + return; + + // needed later + m_runesState = m_caster.ToPlayer().GetRunesState(); + + uint count = (uint)damage; + if (count == 0) + count = 1; + + // first restore fully depleted runes + for (byte j = 0; j < player.GetMaxPower(PowerType.Runes) && count > 0; ++j) + { + if (player.GetRuneCooldown(j) == player.GetRuneBaseCooldown()) + { + player.SetRuneCooldown(j, 0); + --count; + } + } + + // then the rest if we still got something left + for (byte j = 0; j < player.GetMaxPower(PowerType.Runes) && count > 0; ++j) + { + player.SetRuneCooldown(j, 0); + --count; + } + } + + [SpellEffectHandler(SpellEffectName.CreateTamedPet)] + void EffectCreateTamedPet(uint effIndex) + { + if (effectHandleMode != SpellEffectHandleMode.HitTarget) + return; + + if (unitTarget == null || !unitTarget.IsTypeId(TypeId.Player) || !unitTarget.GetPetGUID().IsEmpty() || unitTarget.GetClass() != Class.Hunter) + return; + + uint creatureEntry = (uint)effectInfo.MiscValue; + Pet pet = unitTarget.CreateTamedPetFrom(creatureEntry, m_spellInfo.Id); + if (pet == null) + return; + + // relocate + float px, py, pz; + unitTarget.GetClosePoint(out px, out py, out pz, pet.GetObjectSize(), SharedConst.PetFollowDist, pet.GetFollowAngle()); + pet.Relocate(px, py, pz, unitTarget.GetOrientation()); + + // add to world + pet.GetMap().AddToMap(pet.ToCreature()); + + // unitTarget has pet now + unitTarget.SetMinion(pet, true); + + if (unitTarget.IsTypeId(TypeId.Player)) + { + pet.SavePetToDB(PetSaveMode.AsCurrent); + unitTarget.ToPlayer().PetSpellInitialize(); + } + } + + [SpellEffectHandler(SpellEffectName.DiscoverTaxi)] + void EffectDiscoverTaxi(uint effIndex) + { + if (effectHandleMode != SpellEffectHandleMode.HitTarget) + return; + + if (unitTarget == null || !unitTarget.IsTypeId(TypeId.Player)) + return; + uint nodeid = (uint)effectInfo.MiscValue; + if (CliDB.TaxiNodesStorage.ContainsKey(nodeid)) + unitTarget.ToPlayer().GetSession().SendDiscoverNewTaxiNode(nodeid); + } + + [SpellEffectHandler(SpellEffectName.TitanGrip)] + void EffectTitanGrip(uint effIndex) + { + if (effectHandleMode != SpellEffectHandleMode.Hit) + return; + + if (m_caster.IsTypeId(TypeId.Player)) + m_caster.ToPlayer().SetCanTitanGrip(true, (uint)effectInfo.MiscValue); + } + + [SpellEffectHandler(SpellEffectName.RedirectThreat)] + void EffectRedirectThreat(uint effIndex) + { + if (effectHandleMode != SpellEffectHandleMode.HitTarget) + return; + + if (unitTarget != null) + m_caster.SetRedirectThreat(unitTarget.GetGUID(), (uint)damage); + } + + [SpellEffectHandler(SpellEffectName.GameObjectDamage)] + void EffectGameObjectDamage(uint effIndex) + { + if (effectHandleMode != SpellEffectHandleMode.HitTarget) + return; + + if (gameObjTarget == null) + return; + + Unit caster = m_originalCaster; + if (caster == null) + return; + + FactionTemplateRecord casterFaction = caster.GetFactionTemplateEntry(); + FactionTemplateRecord targetFaction = CliDB.FactionTemplateStorage.LookupByKey(gameObjTarget.GetUInt32Value(GameObjectFields.Faction)); + // Do not allow to damage GO's of friendly factions (ie: Wintergrasp Walls/Ulduar Storm Beacons) + if (targetFaction == null || (casterFaction != null && targetFaction != null && !casterFaction.IsFriendlyTo(targetFaction))) + gameObjTarget.ModifyHealth(-damage, caster, GetSpellInfo().Id); + } + + [SpellEffectHandler(SpellEffectName.GameobjectRepair)] + void EffectGameObjectRepair(uint effIndex) + { + if (effectHandleMode != SpellEffectHandleMode.HitTarget) + return; + + if (gameObjTarget == null) + return; + + gameObjTarget.ModifyHealth(damage, m_caster); + } + + [SpellEffectHandler(SpellEffectName.GameobjectSetDestructionState)] + void EffectGameObjectSetDestructionState(uint effIndex) + { + if (effectHandleMode != SpellEffectHandleMode.HitTarget) + return; + + if (gameObjTarget == null || m_originalCaster == null) + return; + + Player player = m_originalCaster.GetCharmerOrOwnerPlayerOrPlayerItself(); + gameObjTarget.SetDestructibleState((GameObjectDestructibleState)effectInfo.MiscValue, player, true); + } + + void SummonGuardian(uint i, uint entry, SummonPropertiesRecord properties, uint numGuardians) + { + Unit caster = m_originalCaster; + if (caster == null) + return; + + if (caster.IsTotem()) + caster = caster.ToTotem().GetOwner(); + + // in another case summon new + uint level = caster.getLevel(); + + // level of pet summoned using engineering item based at engineering skill level + if (m_CastItem != null && caster.IsTypeId(TypeId.Player)) + { + ItemTemplate proto = m_CastItem.GetTemplate(); + if (proto != null) + if (proto.GetRequiredSkill() == (uint)SkillType.Engineering) + { + ushort skill202 = caster.ToPlayer().GetSkillValue(SkillType.Engineering); + if (skill202 != 0) + level = (uint)(skill202 / 5); + } + } + + float radius = 5.0f; + int duration = m_spellInfo.CalcDuration(m_originalCaster); + + TempSummonType summonType = (duration == 0) ? TempSummonType.DeadDespawn : TempSummonType.TimedDespawn; + Map map = caster.GetMap(); + + for (uint count = 0; count < numGuardians; ++count) + { + Position pos = new Position(); + if (count == 0) + pos = destTarget; + else + // randomize position for multiple summons + m_caster.GetRandomPoint(destTarget, radius, out pos); + + TempSummon summon = map.SummonCreature(entry, pos, properties, (uint)duration, caster, m_spellInfo.Id); + if (summon == null) + return; + if (summon.HasUnitTypeMask(UnitTypeMask.Guardian)) + ((Guardian)summon).InitStatsForLevel(level); + + if (properties != null && properties.Category == SummonCategory.Ally) + summon.SetFaction(caster.getFaction()); + + if (summon.HasUnitTypeMask(UnitTypeMask.Minion) && m_targets.HasDst()) + ((Minion)summon).SetFollowAngle(m_caster.GetAngle(summon.GetPosition())); + + if (summon.GetEntry() == 27893) + { + uint weapon = m_caster.GetUInt32Value(PlayerFields.VisibleItem + (EquipmentSlot.MainHand * 2)); + if (weapon != 0) + { + summon.SetDisplayId(11686); + summon.SetVirtualItem(0, weapon); + } + else + summon.SetDisplayId(1126); + } + + summon.GetAI().EnterEvadeMode(); + + ExecuteLogEffectSummonObject(i, summon); + } + } + + [SpellEffectHandler(SpellEffectName.AllowRenamePet)] + void EffectRenamePet(uint effIndex) + { + if (effectHandleMode != SpellEffectHandleMode.HitTarget) + return; + + if (unitTarget == null || !unitTarget.IsTypeId(TypeId.Unit) || + !unitTarget.IsPet() || unitTarget.ToPet().getPetType() != PetType.Hunter) + return; + + unitTarget.SetByteFlag(UnitFields.Bytes2, UnitBytes2Offsets.PetFlags, UnitPetFlags.CanBeRenamed); + } + + [SpellEffectHandler(SpellEffectName.PlayMusic)] + void EffectPlayMusic(uint effIndex) + { + if (effectHandleMode != SpellEffectHandleMode.HitTarget) + return; + + if (unitTarget == null || !unitTarget.IsTypeId(TypeId.Player)) + return; + + uint soundid = (uint)effectInfo.MiscValue; + + if (!CliDB.SoundKitStorage.ContainsKey(soundid)) + { + Log.outError(LogFilter.Spells, "EffectPlayMusic: Sound (Id: {0}) not exist in spell {1}.", soundid, m_spellInfo.Id); + return; + } + + unitTarget.ToPlayer().SendPacket(new PlayMusic(soundid)); + } + + [SpellEffectHandler(SpellEffectName.TalentSpecSelect)] + void EffectActivateSpec(uint effIndex) + { + if (effectHandleMode != SpellEffectHandleMode.HitTarget) + return; + + if (unitTarget == null || !unitTarget.IsTypeId(TypeId.Player)) + return; + + Player player = unitTarget.ToPlayer(); + uint specID = m_misc.SpecializationId; + ChrSpecializationRecord spec = CliDB.ChrSpecializationStorage.LookupByKey(specID); + + // Safety checks done in Spell::CheckCast + if (!spec.IsPetSpecialization()) + player.ActivateTalentGroup(spec); + else + player.GetPet().SetSpecialization(specID); + } + + [SpellEffectHandler(SpellEffectName.PlaySound)] + void EffectPlaySound(uint effIndex) + { + if (effectHandleMode != SpellEffectHandleMode.HitTarget) + return; + + if (!unitTarget) + return; + + Player player = unitTarget.ToPlayer(); + if (!player) + return; + + switch (m_spellInfo.Id) + { + case 91604: // Restricted Flight Area + player.GetSession().SendNotification(CypherStrings.ZoneNoflyzone); + break; + default: + break; + } + + uint soundId = (uint)effectInfo.MiscValue; + + if (!CliDB.SoundKitStorage.ContainsKey(soundId)) + { + Log.outError(LogFilter.Spells, "EffectPlaySound: Sound (Id: {0}) not exist in spell {1}.", soundId, m_spellInfo.Id); + return; + } + + player.PlayDirectSound(soundId, player); + } + + [SpellEffectHandler(SpellEffectName.RemoveAura)] + void EffectRemoveAura(uint effIndex) + { + if (effectHandleMode != SpellEffectHandleMode.HitTarget) + return; + + if (unitTarget == null) + return; + // there may be need of specifying casterguid of removed auras + unitTarget.RemoveAurasDueToSpell(effectInfo.TriggerSpell); + } + + [SpellEffectHandler(SpellEffectName.DamageFromMaxHealthPCT)] + void EffectDamageFromMaxHealthPCT(uint effIndex) + { + if (effectHandleMode != SpellEffectHandleMode.HitTarget) + return; + + if (unitTarget == null) + return; + + m_damage += (int)unitTarget.CountPctFromMaxHealth(damage); + } + + [SpellEffectHandler(SpellEffectName.GiveCurrency)] + void EffectGiveCurrency(uint effIndex) + { + if (effectHandleMode != SpellEffectHandleMode.HitTarget) + return; + + if (unitTarget == null || !unitTarget.IsTypeId(TypeId.Player)) + return; + + if (!CliDB.CurrencyTypesStorage.ContainsKey(effectInfo.MiscValue)) + return; + + unitTarget.ToPlayer().ModifyCurrency((CurrencyTypes)effectInfo.MiscValue, damage); + } + + [SpellEffectHandler(SpellEffectName.CastButton)] + void EffectCastButtons(uint effIndex) + { + if (effectHandleMode != SpellEffectHandleMode.Hit) + return; + + if (!m_caster.IsTypeId(TypeId.Player)) + return; + + Player p_caster = m_caster.ToPlayer(); + int button_id = effectInfo.MiscValue + 132; + int n_buttons = effectInfo.MiscValueB; + + for (; n_buttons != 0; --n_buttons, ++button_id) + { + ActionButton ab = p_caster.GetActionButton((byte)button_id); + if (ab == null || ab.GetButtonType() != ActionButtonType.Spell) + continue; + + //! Action button data is unverified when it's set so it can be "hacked" + //! to contain invalid spells, so filter here. + uint spell_id = ab.GetAction(); + if (spell_id == 0) + continue; + + SpellInfo spellInfo = Global.SpellMgr.GetSpellInfo(spell_id); + if (spellInfo == null) + continue; + + if (!p_caster.HasSpell(spell_id) || p_caster.GetSpellHistory().HasCooldown(spell_id)) + continue; + + if (!spellInfo.HasAttribute(SpellAttr9.SummonPlayerTotem)) + continue; + + TriggerCastFlags triggerFlags = (TriggerCastFlags.IgnoreGCD | TriggerCastFlags.IgnoreCastInProgress | TriggerCastFlags.CastDirectly | TriggerCastFlags.DontReportCastError); + m_caster.CastSpell(m_caster, spell_id, triggerFlags); + } + } + + [SpellEffectHandler(SpellEffectName.CreateManaGem)] + void EffectRechargeManaGem(uint effIndex) + { + if (effectHandleMode != SpellEffectHandleMode.HitTarget) + return; + + if (unitTarget == null || !unitTarget.IsTypeId(TypeId.Player)) + return; + + Player player = m_caster.ToPlayer(); + + if (player == null) + return; + + uint item_id = GetEffect(0).ItemType; + + ItemTemplate proto = Global.ObjectMgr.GetItemTemplate(item_id); + if (proto == null) + { + player.SendEquipError(InventoryResult.ItemNotFound); + return; + } + + Item pItem = player.GetItemByEntry(item_id); + if (pItem != null) + { + for (int x = 0; x < proto.Effects.Count && x < 5; ++x) + pItem.SetSpellCharges(x, proto.Effects[x].Charges); + pItem.SetState(ItemUpdateState.Changed, player); + } + } + + [SpellEffectHandler(SpellEffectName.Bind)] + void EffectBind(uint effIndex) + { + if (effectHandleMode != SpellEffectHandleMode.HitTarget) + return; + + if (unitTarget == null || !unitTarget.IsTypeId(TypeId.Player)) + return; + + Player player = unitTarget.ToPlayer(); + + WorldLocation homeLoc = new WorldLocation(); + uint areaId = player.GetAreaId(); + + if (effectInfo.MiscValue != 0) + areaId = (uint)effectInfo.MiscValue; + + if (m_targets.HasDst()) + homeLoc.WorldRelocate(destTarget); + else + { + homeLoc.Relocate(player.GetPosition()); + homeLoc.SetMapId(player.GetMapId()); + } + + player.SetHomebind(homeLoc, areaId); + player.SendBindPointUpdate(); + + Log.outDebug(LogFilter.Spells, "EffectBind: New homebind MapId: {0}, AreaId: {1}, {2}, ", homeLoc.GetMapId(), areaId, homeLoc); + + // zone update + PlayerBound packet = new PlayerBound(m_caster.GetGUID(), areaId); + player.SendPacket(packet); + } + + [SpellEffectHandler(SpellEffectName.SummonRafFriend)] + void EffectSummonRaFFriend(uint effIndex) + { + if (effectHandleMode != SpellEffectHandleMode.HitTarget) + return; + + if (!m_caster.IsTypeId(TypeId.Player) || unitTarget == null || !unitTarget.IsTypeId(TypeId.Player)) + return; + + m_caster.CastSpell(unitTarget, effectInfo.TriggerSpell, true); + } + + [SpellEffectHandler(SpellEffectName.UnlockGuildVaultTab)] + void EffectUnlockGuildVaultTab(uint effIndex) + { + if (effectHandleMode != SpellEffectHandleMode.Hit) + return; + + // Safety checks done in Spell.CheckCast + Player caster = m_caster.ToPlayer(); + Guild guild = caster.GetGuild(); + if (guild != null) + guild.HandleBuyBankTab(caster.GetSession(), (byte)(effectInfo.BasePoints - 1)); // Bank tabs start at zero internally + } + + [SpellEffectHandler(SpellEffectName.ResurrectWithAura)] + void EffectResurrectWithAura(uint effIndex) + { + if (effectHandleMode != SpellEffectHandleMode.HitTarget) + return; + + if (unitTarget == null || !unitTarget.IsInWorld) + return; + + Player target = unitTarget.ToPlayer(); + if (target == null) + return; + + if (unitTarget.IsAlive()) + return; + + if (target.IsResurrectRequested()) // already have one active request + return; + + uint health = (uint)target.CountPctFromMaxHealth(damage); + uint mana = (uint)MathFunctions.CalculatePct(target.GetMaxPower(PowerType.Mana), damage); + uint resurrectAura = 0; + if (Global.SpellMgr.GetSpellInfo(effectInfo.TriggerSpell) != null) + resurrectAura = effectInfo.TriggerSpell; + + if (resurrectAura != 0 && target.HasAura(resurrectAura)) + return; + + ExecuteLogEffectResurrect(effIndex, target); + target.SetResurrectRequestData(m_caster, health, mana, resurrectAura); + SendResurrectRequest(target); + } + + [SpellEffectHandler(SpellEffectName.CreateAreaTrigger)] + void EffectCreateAreaTrigger(uint effIndex) + { + if (effectHandleMode != SpellEffectHandleMode.Hit) + return; + + if (!m_targets.HasDst()) + return; + + // trigger entry/miscvalue relation is currently unknown, for now use MiscValue as trigger entry + uint triggerEntry = (uint)effectInfo.MiscValue; + + int duration = GetSpellInfo().CalcDuration(GetCaster()); + AreaTrigger areaTrigger = new AreaTrigger(); + if (!areaTrigger.CreateAreaTrigger((uint)effectInfo.MiscValue, GetCaster(), null, GetSpellInfo(), destTarget.GetPosition(), duration, m_SpellVisual, m_castId)) + areaTrigger.Dispose(); + } + + [SpellEffectHandler(SpellEffectName.RemoveTalent)] + void EffectRemoveTalent(uint effIndex) + { + if (effectHandleMode != SpellEffectHandleMode.HitTarget) + return; + + TalentRecord talent = CliDB.TalentStorage.LookupByKey(m_misc.TalentId); + if (talent == null) + return; + + Player player = unitTarget ? unitTarget.ToPlayer() : null; + if (player == null) + return; + + player.RemoveTalent(talent); + player.SendTalentsInfoData(); + } + + [SpellEffectHandler(SpellEffectName.DestroyItem)] + void EffectDestroyItem(uint effIndex) + { + if (effectHandleMode != SpellEffectHandleMode.HitTarget) + return; + + if (!unitTarget || !unitTarget.IsTypeId(TypeId.Player)) + return; + + Player player = unitTarget.ToPlayer(); + SpellEffectInfo effect = GetEffect(effIndex); + uint itemId = effect.ItemType; + Item item = player.GetItemByEntry(itemId); + if (item) + player.DestroyItem(item.GetBagSlot(), item.GetSlot(), true); + } + + [SpellEffectHandler(SpellEffectName.LearnGarrisonBuilding)] + void EffectLearnGarrisonBuilding(uint effIndex) + { + if (effectHandleMode != SpellEffectHandleMode.HitTarget) + return; + + if (!unitTarget || !unitTarget.IsTypeId(TypeId.Player)) + return; + + Garrison garrison = unitTarget.ToPlayer().GetGarrison(); + if (garrison != null) + garrison.LearnBlueprint((uint)GetEffect(effIndex).MiscValue); + } + + [SpellEffectHandler(SpellEffectName.CreateGarrison)] + void EffectCreateGarrison(uint effIndex) + { + if (effectHandleMode != SpellEffectHandleMode.HitTarget) + return; + + if (!unitTarget || !unitTarget.IsTypeId(TypeId.Player)) + return; + + unitTarget.ToPlayer().CreateGarrison((uint)GetEffect(effIndex).MiscValue); + } + + [SpellEffectHandler(SpellEffectName.CreateConversation)] + void EffectCreateConversation(uint effIndex) + { + if (effectHandleMode != SpellEffectHandleMode.Hit) + return; + + if (!m_targets.HasDst()) + return; + + Conversation.CreateConversation((uint)effectInfo.MiscValue, GetCaster(), destTarget.GetPosition(), new List() { GetCaster().GetGUID() }, GetSpellInfo()); + } + + [SpellEffectHandler(SpellEffectName.AddGarrisonFollower)] + void EffectAddGarrisonFollower(uint effIndex) + { + if (effectHandleMode != SpellEffectHandleMode.HitTarget) + return; + + if (!unitTarget || !unitTarget.IsTypeId(TypeId.Player)) + return; + + Garrison garrison = unitTarget.ToPlayer().GetGarrison(); + if (garrison != null) + garrison.AddFollower((uint)GetEffect(effIndex).MiscValue); + } + + [SpellEffectHandler(SpellEffectName.CreateHeirloomItem)] + void EffectCreateHeirloomItem(uint effIndex) + { + if (effectHandleMode != SpellEffectHandleMode.HitTarget) + return; + + Player player = m_caster.ToPlayer(); + if (!player) + return; + + CollectionMgr collectionMgr = player.GetSession().GetCollectionMgr(); + if (collectionMgr == null) + return; + + List bonusList = new List(); + bonusList.Add(collectionMgr.GetHeirloomBonus(m_misc.Data0)); + + DoCreateItem(effIndex, m_misc.Data0, 0, bonusList); + ExecuteLogEffectCreateItem(effIndex, m_misc.Data0); + } + + [SpellEffectHandler(SpellEffectName.ActivateGarrisonBuilding)] + void EffectActivateGarrisonBuilding(uint effIndex) + { + if (effectHandleMode != SpellEffectHandleMode.HitTarget) + return; + + if (!unitTarget || !unitTarget.IsTypeId(TypeId.Player)) + return; + + Garrison garrison = unitTarget.ToPlayer().GetGarrison(); + if (garrison != null) + garrison.ActivateBuilding((uint)GetEffect(effIndex).MiscValue); + } + + [SpellEffectHandler(SpellEffectName.HealBattlepetPct)] + void EffectHealBattlePetPct(uint effIndex) + { + if (effectHandleMode != SpellEffectHandleMode.HitTarget) + return; + + if (!unitTarget || !unitTarget.IsTypeId(TypeId.Player)) + return; + + BattlePetMgr battlePetMgr = unitTarget.ToPlayer().GetSession().GetBattlePetMgr(); + if (battlePetMgr != null) + battlePetMgr.HealBattlePetsPct((byte)GetEffect(effIndex).BasePoints); + } + + [SpellEffectHandler(SpellEffectName.EnableBattlePets)] + void EffectEnableBattlePets(uint effIndex) + { + if (effectHandleMode != SpellEffectHandleMode.HitTarget) + return; + + if (!unitTarget || !unitTarget.IsTypeId(TypeId.Player)) + return; + + Player plr = unitTarget.ToPlayer(); + plr.SetFlag(PlayerFields.Flags, PlayerFlags.PetBattlesUnlocked); + plr.GetSession().GetBattlePetMgr().UnlockSlot(0); + } + + [SpellEffectHandler(SpellEffectName.UncageBattlepet)] + void EffectUncageBattlePet(uint effIndex) + { + if (effectHandleMode != SpellEffectHandleMode.Hit) + return; + + if (!m_CastItem || !m_caster || !m_caster.IsTypeId(TypeId.Player)) + return; + + Player plr = m_caster.ToPlayer(); + + // are we allowed to learn battle pets without it? + /*if (plr.HasFlag(PLAYER_FLAGS, PLAYER_FLAGS_PET_BATTLES_UNLOCKED)) + return; // send some error*/ + + uint speciesId = m_CastItem.GetModifier(ItemModifier.BattlePetSpeciesId); + ushort breed = (ushort)(m_CastItem.GetModifier(ItemModifier.BattlePetBreedData) & 0xFFFFFF); + byte quality = (byte)((m_CastItem.GetModifier(ItemModifier.BattlePetBreedData) >> 24) & 0xFF); + ushort level = (ushort)m_CastItem.GetModifier(ItemModifier.BattlePetLevel); + uint creatureId = m_CastItem.GetModifier(ItemModifier.BattlePetDisplayId); + + BattlePetSpeciesRecord speciesEntry = CliDB.BattlePetSpeciesStorage.LookupByKey(speciesId); + if (speciesEntry == null) + return; + + BattlePetMgr battlePetMgr = plr.GetSession().GetBattlePetMgr(); + if (battlePetMgr == null) + return; + + ushort maxLearnedLevel = 0; + + foreach (var pet in battlePetMgr.GetLearnedPets()) + maxLearnedLevel = Math.Max(pet.PacketInfo.Level, maxLearnedLevel); + + // TODO: This means if you put your highest lvl pet into cage, you won't be able to uncage it again which is probably wrong. + // We will need to store maxLearnedLevel somewhere to avoid this behaviour. + if (maxLearnedLevel < level) + { + battlePetMgr.SendError(BattlePetError.TooHighLevelToUncage, creatureId); // or speciesEntry.CreatureID + SendCastResult(SpellCastResult.CantAddBattlePet); + return; + } + + if (battlePetMgr.GetPetCount(speciesId) >= SharedConst.MaxBattlePetsPerSpecies) + { + battlePetMgr.SendError(BattlePetError.CantHaveMorePetsOfThatType, creatureId); // or speciesEntry.CreatureID + SendCastResult(SpellCastResult.CantAddBattlePet); + return; + } + + if (!plr.HasSpell(speciesEntry.SummonSpellID)) + plr.LearnSpell(speciesEntry.SummonSpellID, false); + + battlePetMgr.AddPet(speciesId, creatureId, breed, quality, level); + plr.DestroyItem(m_CastItem.GetBagSlot(), m_CastItem.GetSlot(), true); + m_CastItem = null; + } + + [SpellEffectHandler(SpellEffectName.UpgradeHeirloom)] + void EffectUpgradeHeirloom(uint effIndex) + { + if (effectHandleMode != SpellEffectHandleMode.Hit) + return; + + Player player = m_caster.ToPlayer(); + if (player) + { + CollectionMgr collectionMgr = player.GetSession().GetCollectionMgr(); + if (collectionMgr != null) + collectionMgr.UpgradeHeirloom(m_misc.Data0, m_castItemEntry); + } + } + + [SpellEffectHandler(SpellEffectName.ApplyEnchantIllusion)] + void EffectApplyEnchantIllusion(uint effIndex) + { + if (effectHandleMode != SpellEffectHandleMode.HitTarget) + return; + + if (!itemTarget) + return; + + Player player = m_caster.ToPlayer(); + if (!player || player.GetGUID() != itemTarget.GetOwnerGUID()) + return; + + itemTarget.SetState(ItemUpdateState.Changed, player); + itemTarget.SetModifier(ItemModifier.EnchantIllusionAllSpecs, (uint)effectInfo.MiscValue); + if (itemTarget.IsEquipped()) + player.SetUInt16Value(PlayerFields.VisibleItem + 1 + (itemTarget.GetSlot() * 2), 1, itemTarget.GetVisibleItemVisual(player)); + + player.RemoveTradeableItem(itemTarget); + itemTarget.ClearSoulboundTradeable(player); + } + + [SpellEffectHandler(SpellEffectName.UpdatePlayerPhase)] + void EffectUpdatePlayerPhase(uint effIndex) + { + if (effectHandleMode != SpellEffectHandleMode.HitTarget) + return; + + if (!unitTarget || !unitTarget.IsTypeId(TypeId.Player)) + return; + + unitTarget.UpdateAreaAndZonePhase(); + } + + [SpellEffectHandler(SpellEffectName.UpdateZoneAurasPhases)] + void EffectUpdateZoneAurasAndPhases(uint effIndex) + { + if (effectHandleMode != SpellEffectHandleMode.HitTarget) + return; + + if (!unitTarget || !unitTarget.IsTypeId(TypeId.Player)) + return; + + unitTarget.ToPlayer().UpdateAreaDependentAuras(unitTarget.GetAreaId()); + } + + [SpellEffectHandler(SpellEffectName.GiveArtifactPower)] + void EffectGiveArtifactPower(uint effIndex) + { + if (effectHandleMode != SpellEffectHandleMode.LaunchTarget) + return; + + if (!m_caster.IsTypeId(TypeId.Player)) + return; + + Aura artifactAura = m_caster.GetAura(PlayerConst.ArtifactsAllWeaponsGeneralWeaponEquippedPassive); + if (artifactAura != null) + { + Item artifact = m_caster.ToPlayer().GetItemByGuid(artifactAura.GetCastItemGUID()); + if (artifact) + artifact.GiveArtifactXp((ulong)damage, m_CastItem, (uint)effectInfo.MiscValue); + } + } + + [SpellEffectHandler(SpellEffectName.GiveArtifactPowerNoBonus)] + void EffectGiveArtifactPowerNoBonus(uint effIndex) + { + if (effectHandleMode != SpellEffectHandleMode.LaunchTarget) + return; + + if (!unitTarget || !m_caster.IsTypeId(TypeId.Player)) + return; + + Aura artifactAura = unitTarget.GetAura(PlayerConst.ArtifactsAllWeaponsGeneralWeaponEquippedPassive); + if (artifactAura != null) + { + Item artifact = unitTarget.ToPlayer().GetItemByGuid(artifactAura.GetCastItemGUID()); + if (artifact) + artifact.GiveArtifactXp((ulong)damage, m_CastItem, 0); + } + } + + [SpellEffectHandler(SpellEffectName.PlayScene)] + void EffectPlayScene(uint effIndex) + { + if (effectHandleMode != SpellEffectHandleMode.Hit) + return; + + if (m_caster.GetTypeId() != TypeId.Player) + return; + + m_caster.ToPlayer().GetSceneMgr().PlayScene((uint)effectInfo.MiscValue, destTarget); + } + + [SpellEffectHandler(SpellEffectName.GiveHonor)] + void EffectGiveHonor(uint effIndex) + { + if (effectHandleMode != SpellEffectHandleMode.HitTarget) + return; + + if (!unitTarget || unitTarget.GetTypeId() != TypeId.Player) + return; + + PvPCredit packet = new PvPCredit(); + packet.Honor = damage; + packet.OriginalHonor = damage; + + Player playerTarget = unitTarget.ToPlayer(); + playerTarget.AddHonorXP((uint)damage); + playerTarget.SendPacket(packet); + } + } + + public class DispelCharges + { + public DispelCharges(Aura _aura, byte _value) + { + aura = _aura; + value = _value; + } + + public Aura aura; + public byte value; + } +} diff --git a/Game/Spells/SpellHistory.cs b/Game/Spells/SpellHistory.cs new file mode 100644 index 000000000..79ed43c1f --- /dev/null +++ b/Game/Spells/SpellHistory.cs @@ -0,0 +1,983 @@ +/* + * Copyright (C) 2012-2017 CypherCore + * + * 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, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +using Framework.Constants; +using Framework.Database; +using Game.DataStorage; +using Game.Entities; +using Game.Network.Packets; +using System; +using System.Collections.Generic; +using System.Linq; + +namespace Game.Spells +{ + public class SpellHistory + { + public SpellHistory(Unit owner) + { + _owner = owner; + } + + public void LoadFromDB(SQLResult cooldownsResult, SQLResult chargesResult) where T : WorldObject + { + if (!cooldownsResult.IsEmpty()) + { + do + { + uint spellId = cooldownsResult.Read(0); + if (!Global.SpellMgr.HasSpellInfo(spellId)) + continue; + + int index = (typeof(T) == typeof(Pet) ? 1 : 2); + + CooldownEntry cooldownEntry = new CooldownEntry(); + cooldownEntry.SpellId = spellId; + cooldownEntry.CooldownEnd = Time.UnixTimeToDateTime(cooldownsResult.Read(index++)); + cooldownEntry.ItemId = 0; + cooldownEntry.CategoryId = cooldownsResult.Read(index++); + cooldownEntry.CategoryEnd = Time.UnixTimeToDateTime(cooldownsResult.Read(index++)); + + _spellCooldowns[spellId] = cooldownEntry; + if (cooldownEntry.CategoryId != 0) + _categoryCooldowns[cooldownEntry.CategoryId] = _spellCooldowns[spellId]; + + } while (cooldownsResult.NextRow()); + } + + if (!chargesResult.IsEmpty()) + { + do + { + uint categoryId = chargesResult.Read(0); + + if (!CliDB.SpellCategoryStorage.ContainsKey(categoryId)) + continue; + + ChargeEntry charges; + charges.RechargeStart = Time.UnixTimeToDateTime(chargesResult.Read(1)); + charges.RechargeEnd = Time.UnixTimeToDateTime(chargesResult.Read(2)); + _categoryCharges.Add(categoryId, charges); + + } while (chargesResult.NextRow()); + } + } + + public void SaveToDB(SQLTransaction trans) where T : WorldObject + { + PreparedStatement stmt; + if (typeof(T) == typeof(Pet)) + { + stmt = DB.Characters.GetPreparedStatement(CharStatements.DEL_PET_SPELL_COOLDOWNS); + stmt.AddValue(0, _owner.GetCharmInfo().GetPetNumber()); + trans.Append(stmt); + + stmt = DB.Characters.GetPreparedStatement(CharStatements.DEL_PET_SPELL_CHARGES); + stmt.AddValue(0, _owner.GetCharmInfo().GetPetNumber()); + trans.Append(stmt); + + byte index = 0; + foreach (var pair in _spellCooldowns) + { + if (!pair.Value.OnHold) + { + index = 0; + stmt = DB.Characters.GetPreparedStatement(CharStatements.INS_PET_SPELL_COOLDOWN); + stmt.AddValue(index++, _owner.GetCharmInfo().GetPetNumber()); + stmt.AddValue(index++, pair.Key); + stmt.AddValue(index++, (uint)Time.DateTimeToUnixTime(pair.Value.CooldownEnd)); + stmt.AddValue(index++, pair.Value.CategoryId); + stmt.AddValue(index++, (uint)Time.DateTimeToUnixTime(pair.Value.CategoryEnd)); + trans.Append(stmt); + + } + } + + foreach (var pair in _categoryCharges) + { + index = 0; + stmt = DB.Characters.GetPreparedStatement(CharStatements.INS_PET_SPELL_CHARGES); + stmt.AddValue(index++, _owner.GetCharmInfo().GetPetNumber()); + stmt.AddValue(index++, pair.Key); + stmt.AddValue(index++, (uint)Time.DateTimeToUnixTime(pair.Value.RechargeStart)); + stmt.AddValue(index++, (uint)Time.DateTimeToUnixTime(pair.Value.RechargeEnd)); + trans.Append(stmt); + } + } + else + { + stmt = DB.Characters.GetPreparedStatement(CharStatements.DEL_CHAR_SPELL_COOLDOWNS); + stmt.AddValue(0, _owner.GetGUID().GetCounter()); + trans.Append(stmt); + + stmt = DB.Characters.GetPreparedStatement(CharStatements.DEL_CHAR_SPELL_CHARGES); + stmt.AddValue(0, _owner.GetGUID().GetCounter()); + trans.Append(stmt); + + byte index = 0; + foreach (var pair in _spellCooldowns) + { + if (!pair.Value.OnHold) + { + index = 0; + stmt = DB.Characters.GetPreparedStatement(CharStatements.INS_CHAR_SPELL_COOLDOWN); + stmt.AddValue(index++, _owner.GetGUID().GetCounter()); + stmt.AddValue(index++, pair.Key); + stmt.AddValue(index++, pair.Value.ItemId); + stmt.AddValue(index++, (uint)Time.DateTimeToUnixTime(pair.Value.CooldownEnd)); + stmt.AddValue(index++, pair.Value.CategoryId); + stmt.AddValue(index++, (uint)Time.DateTimeToUnixTime(pair.Value.CategoryEnd)); + trans.Append(stmt); + + } + } + + foreach (var pair in _categoryCharges) + { + index = 0; + stmt = DB.Characters.GetPreparedStatement(CharStatements.INS_CHAR_SPELL_CHARGES); + stmt.AddValue(index++, _owner.GetGUID().GetCounter()); + stmt.AddValue(index++, pair.Key); + stmt.AddValue(index++, (uint)Time.DateTimeToUnixTime(pair.Value.RechargeStart)); + stmt.AddValue(index++, (uint)Time.DateTimeToUnixTime(pair.Value.RechargeEnd)); + trans.Append(stmt); + } + } + } + + public void Update() + { + DateTime now = DateTime.Now; + foreach (var pair in _categoryCooldowns.ToList()) + { + if (pair.Value.CategoryEnd < now) + _categoryCooldowns.Remove(pair.Key); + } + + foreach (var pair in _spellCooldowns.ToList()) + { + if (pair.Value.CooldownEnd < now) + { + _categoryCooldowns.Remove(pair.Value.CategoryId); + _spellCooldowns.Remove(pair.Key); + } + } + + foreach (var key in _categoryCharges.Keys) + { + var chargeRefreshTimes = _categoryCharges[key]; + while (!chargeRefreshTimes.Empty() && chargeRefreshTimes.FirstOrDefault().RechargeEnd <= now) + chargeRefreshTimes.RemoveAt(0); + } + } + + public void HandleCooldowns(SpellInfo spellInfo, Item item, Spell spell = null) + { + HandleCooldowns(spellInfo, item ? item.GetEntry() : 0u, spell); + } + + public void HandleCooldowns(SpellInfo spellInfo, uint itemId, Spell spell = null) + { + if (ConsumeCharge(spellInfo.ChargeCategoryId)) + return; + + Player player = _owner.ToPlayer(); + if (player) + { + // potions start cooldown until exiting combat + ItemTemplate itemTemplate = Global.ObjectMgr.GetItemTemplate(itemId); + if (itemTemplate != null) + { + if (itemTemplate.IsPotion() || spellInfo.IsCooldownStartedOnEvent()) + { + player.SetLastPotionId(itemId); + return; + } + } + } + + if (spellInfo.IsCooldownStartedOnEvent() || spellInfo.IsPassive() || (spell && spell.IsIgnoringCooldowns())) + return; + + StartCooldown(spellInfo, itemId, spell); + } + + public bool IsReady(SpellInfo spellInfo, uint itemId = 0, bool ignoreCategoryCooldown = false) + { + if (spellInfo.PreventionType.HasAnyFlag(SpellPreventionType.Silence)) + if (IsSchoolLocked(spellInfo.GetSchoolMask())) + return false; + + if (HasCooldown(spellInfo.Id, itemId, ignoreCategoryCooldown)) + return false; + + if (!HasCharge(spellInfo.ChargeCategoryId)) + return false; + + return true; + } + + public void WritePacket(SendSpellHistory sendSpellHistory) + { + DateTime now = DateTime.Now; + foreach (var p in _spellCooldowns) + { + SpellHistoryEntry historyEntry = new SpellHistoryEntry(); + historyEntry.SpellID = p.Key; + historyEntry.ItemID = p.Value.ItemId; + + if (p.Value.OnHold) + historyEntry.OnHold = true; + else + { + TimeSpan cooldownDuration = p.Value.CooldownEnd - now; + if (cooldownDuration.TotalMilliseconds <= 0) + continue; + + historyEntry.RecoveryTime = (int)cooldownDuration.TotalMilliseconds; + TimeSpan categoryDuration = p.Value.CategoryEnd - now; + if (categoryDuration.TotalMilliseconds > 0) + { + historyEntry.Category = p.Value.CategoryId; + historyEntry.CategoryRecoveryTime = (int)categoryDuration.TotalMilliseconds; + } + } + + sendSpellHistory.Entries.Add(historyEntry); + } + } + + public void WritePacket(SendSpellCharges sendSpellCharges) + { + DateTime now = DateTime.Now; + foreach (var key in _categoryCharges.Keys) + { + var p = _categoryCharges[key]; + if (!p.Empty()) + { + TimeSpan cooldownDuration = p.FirstOrDefault().RechargeEnd - now; + if (cooldownDuration.TotalMilliseconds <= 0) + continue; + + SpellChargeEntry chargeEntry = new SpellChargeEntry(); + chargeEntry.Category = key; + chargeEntry.NextRecoveryTime = (uint)cooldownDuration.TotalMilliseconds; + chargeEntry.ConsumedCharges = (byte)p.Count; + sendSpellCharges.Entries.Add(chargeEntry); + } + } + } + + public void WritePacket(PetSpells petSpells) + { + DateTime now = DateTime.Now; + + foreach (var pair in _spellCooldowns) + { + PetSpellCooldown petSpellCooldown = new PetSpellCooldown(); + petSpellCooldown.SpellID = pair.Key; + petSpellCooldown.Category = (ushort)pair.Value.CategoryId; + + if (!pair.Value.OnHold) + { + var cooldownDuration = pair.Value.CooldownEnd - now; + if (cooldownDuration.TotalMilliseconds <= 0) + continue; + + petSpellCooldown.Duration = (uint)cooldownDuration.TotalMilliseconds; + var categoryDuration = pair.Value.CategoryEnd - now; + if (categoryDuration.TotalMilliseconds > 0) + petSpellCooldown.CategoryDuration = (uint)categoryDuration.TotalMilliseconds; + } + else + petSpellCooldown.CategoryDuration = 0x80000000; + + petSpells.Cooldowns.Add(petSpellCooldown); + } + + foreach (var key in _categoryCharges.Keys) + { + var list = _categoryCharges[key]; + if (!list.Empty()) + { + var cooldownDuration = list.FirstOrDefault().RechargeEnd - now; + if (cooldownDuration.TotalMilliseconds <= 0) + continue; + + PetSpellHistory petChargeEntry = new PetSpellHistory(); + petChargeEntry.CategoryID = key; + petChargeEntry.RecoveryTime = (uint)cooldownDuration.TotalMilliseconds; + petChargeEntry.ConsumedCharges = (sbyte)list.Count; + + petSpells.SpellHistory.Add(petChargeEntry); + } + } + } + + public void StartCooldown(SpellInfo spellInfo, uint itemId, Spell spell = null, bool onHold = false) + { + // init cooldown values + uint categoryId = 0; + int cooldown = -1; + int categoryCooldown = -1; + + GetCooldownDurations(spellInfo, itemId, ref cooldown, ref categoryId, ref categoryCooldown); + + DateTime curTime = DateTime.Now; + DateTime catrecTime; + DateTime recTime; + bool needsCooldownPacket = false; + + // overwrite time for selected category + if (onHold) + { + // use +MONTH as infinite cooldown marker + catrecTime = categoryCooldown > 0 ? (curTime + PlayerConst.InfinityCooldownDelay) : curTime; + recTime = cooldown > 0 ? (curTime + PlayerConst.InfinityCooldownDelay) : catrecTime; + } + else + { + // shoot spells used equipped item cooldown values already assigned in GetAttackTime(RANGED_ATTACK) + // prevent 0 cooldowns set by another way + if (cooldown <= 0 && categoryCooldown <= 0 && (categoryId == 76 || (spellInfo.IsAutoRepeatRangedSpell() && spellInfo.Id != 75))) + cooldown = (int)_owner.GetUInt32Value(UnitFields.RangedAttackTime); + + // Now we have cooldown data (if found any), time to apply mods + Player modOwner = _owner.GetSpellModOwner(); + if (modOwner) + { + if (cooldown > 0) + modOwner.ApplySpellMod(spellInfo.Id, SpellModOp.Cooldown, ref cooldown, spell); + + if (categoryCooldown > 0 && !spellInfo.HasAttribute(SpellAttr6.IgnoreCategoryCooldownMods)) + modOwner.ApplySpellMod(spellInfo.Id, SpellModOp.Cooldown, ref categoryCooldown, spell); + } + + if (_owner.HasAuraTypeWithAffectMask(AuraType.ModSpellCooldownByHaste, spellInfo)) + { + cooldown = (int)(cooldown * _owner.GetFloatValue(UnitFields.ModCastHaste)); + categoryCooldown = (int)(categoryCooldown * _owner.GetFloatValue(UnitFields.ModCastHaste)); + } + + if (_owner.HasAuraTypeWithAffectMask(AuraType.ModCooldownByHasteRegen, spellInfo)) + { + cooldown = (int)(cooldown * _owner.GetFloatValue(UnitFields.ModHasteRegen)); + categoryCooldown = (int)(categoryCooldown * _owner.GetFloatValue(UnitFields.ModHasteRegen)); + } + + int cooldownMod = _owner.GetTotalAuraModifier(AuraType.ModCooldown); + if (cooldownMod != 0) + { + // Apply SPELL_AURA_MOD_COOLDOWN only to own spells + Player playerOwner = GetPlayerOwner(); + if (!playerOwner || playerOwner.HasSpell(spellInfo.Id)) + { + needsCooldownPacket = true; + cooldown += cooldownMod * Time.InMilliseconds; // SPELL_AURA_MOD_COOLDOWN does not affect category cooldows, verified with shaman shocks + } + } + + // Apply SPELL_AURA_MOD_SPELL_CATEGORY_COOLDOWN modifiers + // Note: This aura applies its modifiers to all cooldowns of spells with set category, not to category cooldown only + if (categoryId != 0) + { + int categoryModifier = _owner.GetTotalAuraModifierByMiscValue(AuraType.ModSpellCategoryCooldown, (int)categoryId); + if (categoryModifier != 0) + { + if (cooldown > 0) + cooldown += categoryModifier; + + if (categoryCooldown > 0) + categoryCooldown += categoryModifier; + } + + SpellCategoryRecord categoryEntry = CliDB.SpellCategoryStorage.LookupByKey(categoryId); + if (categoryEntry.Flags.HasAnyFlag(SpellCategoryFlags.CooldownExpiresAtDailyReset)) + categoryCooldown = (int)(Time.UnixTimeToDateTime(Global.WorldMgr.GetNextDailyQuestsResetTime()) - DateTime.Now).TotalMilliseconds; + } + + // replace negative cooldowns by 0 + if (cooldown < 0) + cooldown = 0; + + if (categoryCooldown < 0) + categoryCooldown = 0; + + // no cooldown after applying spell mods + if (cooldown == 0 && categoryCooldown == 0) + return; + + catrecTime = categoryCooldown != 0 ? curTime + TimeSpan.FromMilliseconds(categoryCooldown) : curTime; + recTime = cooldown != 0 ? curTime + TimeSpan.FromMilliseconds(cooldown) : catrecTime; + } + + // self spell cooldown + if (recTime != curTime) + { + AddCooldown(spellInfo.Id, itemId, recTime, categoryId, catrecTime, onHold); + + if (needsCooldownPacket) + { + Player playerOwner = GetPlayerOwner(); + if (playerOwner) + { + SpellCooldownPkt spellCooldown = new SpellCooldownPkt(); + spellCooldown.Caster = _owner.GetGUID(); + spellCooldown.Flags = SpellCooldownFlags.None; + spellCooldown.SpellCooldowns.Add(new SpellCooldownStruct(spellInfo.Id, (uint)cooldown)); + playerOwner.SendPacket(spellCooldown); + } + } + } + } + + public void SendCooldownEvent(SpellInfo spellInfo, uint itemId = 0, Spell spell = null, bool startCooldown = true) + { + Player player = GetPlayerOwner(); + if (player) + { + uint category = spellInfo.GetCategory(); + GetCooldownDurations(spellInfo, itemId, ref category); + + var categoryEntry = _categoryCooldowns.LookupByKey(category); + if (categoryEntry != null && categoryEntry.SpellId != spellInfo.Id) + { + player.SendPacket(new CooldownEvent(player != _owner, categoryEntry.SpellId)); + + if (startCooldown) + StartCooldown(Global.SpellMgr.GetSpellInfo(categoryEntry.SpellId), itemId, spell); + } + + player.SendPacket(new CooldownEvent(player != _owner, spellInfo.Id)); + } + + // start cooldowns at server side, if any + if (startCooldown) + StartCooldown(spellInfo, itemId, spell); + } + + public void AddCooldown(uint spellId, uint itemId, TimeSpan cooldownDuration) + { + DateTime now = DateTime.Now; + AddCooldown(spellId, itemId, now + cooldownDuration, 0, now); + } + + public void AddCooldown(uint spellId, uint itemId, DateTime cooldownEnd, uint categoryId, DateTime categoryEnd, bool onHold = false) + { + CooldownEntry cooldownEntry = new CooldownEntry(); + cooldownEntry.SpellId = spellId; + cooldownEntry.CooldownEnd = cooldownEnd; + cooldownEntry.ItemId = itemId; + cooldownEntry.CategoryId = categoryId; + cooldownEntry.CategoryEnd = categoryEnd; + cooldownEntry.OnHold = onHold; + _spellCooldowns[spellId] = cooldownEntry; + + if (categoryId != 0) + _categoryCooldowns[categoryId] = cooldownEntry; + } + + public void ModifyCooldown(uint spellId, int cooldownModMs) + { + TimeSpan offset = TimeSpan.FromMilliseconds(cooldownModMs); + ModifyCooldown(spellId, offset); + } + + public void ModifyCooldown(uint spellId, TimeSpan offset) + { + var cooldownEntry = _spellCooldowns.LookupByKey(spellId); + if (offset.TotalMilliseconds == 0 || cooldownEntry == null) + return; + + DateTime now = DateTime.Now; + + if (cooldownEntry.CooldownEnd + offset > now) + cooldownEntry.CooldownEnd += offset; + else + { + _categoryCooldowns.Remove(cooldownEntry.CategoryId); + _spellCooldowns.Remove(spellId); + } + + Player playerOwner = GetPlayerOwner(); + if (playerOwner) + { + ModifyCooldown modifyCooldown = new ModifyCooldown(); + modifyCooldown.IsPet = _owner != playerOwner; + modifyCooldown.SpellID = spellId; + modifyCooldown.DeltaTime = (int)offset.TotalMilliseconds; + playerOwner.SendPacket(modifyCooldown); + } + } + + public void ResetCooldown(uint spellId, bool update = false) + { + var entry = _spellCooldowns.LookupByKey(spellId); + if (entry == null) + return; + + if (update) + { + Player playerOwner = GetPlayerOwner(); + if (playerOwner) + { + ClearCooldown clearCooldown = new ClearCooldown(); + clearCooldown.IsPet = _owner != playerOwner; + clearCooldown.SpellID = spellId; + clearCooldown.ClearOnHold = false; + playerOwner.SendPacket(clearCooldown); + } + } + + _categoryCooldowns.Remove(entry.CategoryId); + _spellCooldowns.Remove(spellId); + } + + public void ResetCooldowns(Func, bool> predicate, bool update = false) + { + List resetCooldowns = new List(); + foreach (var pair in _spellCooldowns) + { + if (predicate(pair)) + { + resetCooldowns.Add(pair.Key); + ResetCooldown(pair.Key, false); + } + } + + if (update && !resetCooldowns.Empty()) + SendClearCooldowns(resetCooldowns); + } + + public void ResetAllCooldowns() + { + Player playerOwner = GetPlayerOwner(); + if (playerOwner) + { + List cooldowns = new List(); + foreach (var id in _spellCooldowns.Keys) + cooldowns.Add(id); + + SendClearCooldowns(cooldowns); + } + + _categoryCooldowns.Clear(); + _spellCooldowns.Clear(); + } + + public bool HasCooldown(uint spellId, uint itemId = 0, bool ignoreCategoryCooldown = false) + { + return HasCooldown(Global.SpellMgr.GetSpellInfo(spellId), itemId, ignoreCategoryCooldown); + } + + public bool HasCooldown(SpellInfo spellInfo, uint itemId = 0, bool ignoreCategoryCooldown = false) + { + if (_spellCooldowns.ContainsKey(spellInfo.Id)) + return true; + + if (ignoreCategoryCooldown) + return false; + + uint category = 0; + GetCooldownDurations(spellInfo, itemId, ref category); + + if (category == 0) + category = spellInfo.GetCategory(); + + if (category == 0) + return false; + + return _categoryCooldowns.ContainsKey(category); + } + + public uint GetRemainingCooldown(SpellInfo spellInfo) + { + DateTime end; + var entry = _spellCooldowns.LookupByKey(spellInfo.Id); + if (entry != null) + end = entry.CooldownEnd; + else + { + var cooldownEntry = _categoryCooldowns.LookupByKey(spellInfo.GetCategory()); + if (cooldownEntry == null) + return 0; + + end = cooldownEntry.CategoryEnd; + } + + DateTime now = DateTime.Now; + if (end < now) + return 0; + + var remaining = end - now; + return (uint)remaining.TotalMilliseconds; + } + + public void LockSpellSchool(SpellSchoolMask schoolMask, uint lockoutTime) + { + DateTime now = DateTime.Now; + DateTime lockoutEnd = now + TimeSpan.FromMilliseconds(lockoutTime); + for (int i = 0; i < (int)SpellSchools.Max; ++i) + if (Convert.ToBoolean((SpellSchoolMask)(1 << i) & schoolMask)) + _schoolLockouts[i] = lockoutEnd; + + List knownSpells = new List(); + Player plrOwner = _owner.ToPlayer(); + if (plrOwner) + { + foreach (var p in plrOwner.GetSpellMap()) + if (p.Value.State != PlayerSpellState.Removed) + knownSpells.Add(p.Key); + } + else if (_owner.IsPet()) + { + Pet petOwner = _owner.ToPet(); + foreach (var p in petOwner.m_spells) + if (p.Value.state != PetSpellState.Removed) + knownSpells.Add(p.Key); + } + else + { + Creature creatureOwner = _owner.ToCreature(); + for (byte i = 0; i < SharedConst.MaxCreatureSpells; ++i) + if (creatureOwner.m_spells[i] != 0) + knownSpells.Add(creatureOwner.m_spells[i]); + } + + SpellCooldownPkt spellCooldown = new SpellCooldownPkt(); + spellCooldown.Caster = _owner.GetGUID(); + spellCooldown.Flags = SpellCooldownFlags.None; + foreach (uint spellId in knownSpells) + { + SpellInfo spellInfo = Global.SpellMgr.GetSpellInfo(spellId); + if (spellInfo.IsCooldownStartedOnEvent()) + continue; + + if (!spellInfo.PreventionType.HasAnyFlag(SpellPreventionType.Silence)) + continue; + + if (Convert.ToBoolean(schoolMask & spellInfo.GetSchoolMask()) && GetRemainingCooldown(spellInfo) < lockoutTime) + { + spellCooldown.SpellCooldowns.Add(new SpellCooldownStruct(spellId, lockoutTime)); + AddCooldown(spellId, 0, lockoutEnd, 0, now); + } + } + + Player player = GetPlayerOwner(); + if (player) + if (!spellCooldown.SpellCooldowns.Empty()) + player.SendPacket(spellCooldown); + } + + public bool IsSchoolLocked(SpellSchoolMask schoolMask) + { + DateTime now = DateTime.Now; + for (int i = 0; i < (int)SpellSchools.Max; ++i) + if (Convert.ToBoolean((SpellSchoolMask)(1 << i) & schoolMask)) + if (_schoolLockouts[i] > now) + return true; + + return false; + } + + public bool ConsumeCharge(uint chargeCategoryId) + { + if (!CliDB.SpellCategoryStorage.ContainsKey(chargeCategoryId)) + return false; + + int chargeRecovery = GetChargeRecoveryTime(chargeCategoryId); + if (chargeRecovery > 0 && GetMaxCharges(chargeCategoryId) > 0) + { + DateTime recoveryStart; + var charges = _categoryCharges.LookupByKey(chargeCategoryId); + if (charges.Empty()) + recoveryStart = DateTime.Now; + else + recoveryStart = charges.LastOrDefault().RechargeEnd; + + charges.Add(new ChargeEntry(recoveryStart, TimeSpan.FromMilliseconds(chargeRecovery))); + return true; + } + + return false; + } + + public void RestoreCharge(uint chargeCategoryId) + { + var chargeList = _categoryCharges.LookupByKey(chargeCategoryId); + if (!chargeList.Empty()) + { + chargeList.RemoveAt(0); + + Player player = GetPlayerOwner(); + if (player) + { + SetSpellCharges setSpellCharges = new SetSpellCharges(); + setSpellCharges.Category = chargeCategoryId; + if (!chargeList.Empty()) + setSpellCharges.NextRecoveryTime = (uint)(chargeList.FirstOrDefault().RechargeEnd - DateTime.Now).TotalMilliseconds; + setSpellCharges.ConsumedCharges = (byte)chargeList.Count; + setSpellCharges.IsPet = player != _owner; + + player.SendPacket(setSpellCharges); + } + } + } + + public void ResetCharges(uint chargeCategoryId) + { + var chargeList = _categoryCharges.LookupByKey(chargeCategoryId); + if (!chargeList.Empty()) + { + _categoryCharges.Remove(chargeCategoryId); + + Player player = GetPlayerOwner(); + if (player) + { + ClearSpellCharges clearSpellCharges = new ClearSpellCharges(); + clearSpellCharges.IsPet = _owner != player; + clearSpellCharges.Category = chargeCategoryId; + player.SendPacket(clearSpellCharges); + } + } + } + + public void ResetAllCharges() + { + _categoryCharges.Clear(); + + Player player = GetPlayerOwner(); + if (player) + { + ClearAllSpellCharges clearAllSpellCharges = new ClearAllSpellCharges(); + clearAllSpellCharges.IsPet = _owner != player; + player.SendPacket(clearAllSpellCharges); + } + } + + public bool HasCharge(uint chargeCategoryId) + { + if (!CliDB.SpellCategoryStorage.ContainsKey(chargeCategoryId)) + return true; + + // Check if the spell is currently using charges (untalented warlock Dark Soul) + int maxCharges = GetMaxCharges(chargeCategoryId); + if (maxCharges <= 0) + return true; + + var chargeList = _categoryCharges.LookupByKey(chargeCategoryId); + return chargeList.Empty() || chargeList.Count < maxCharges; + } + + public int GetMaxCharges(uint chargeCategoryId) + { + SpellCategoryRecord chargeCategoryEntry = CliDB.SpellCategoryStorage.LookupByKey(chargeCategoryId); + if (chargeCategoryEntry == null) + return 0; + + uint charges = chargeCategoryEntry.MaxCharges; + charges += (uint)_owner.GetTotalAuraModifierByMiscValue(AuraType.ModMaxCharges, (int)chargeCategoryId); + return (int)charges; + } + + public int GetChargeRecoveryTime(uint chargeCategoryId) + { + SpellCategoryRecord chargeCategoryEntry = CliDB.SpellCategoryStorage.LookupByKey(chargeCategoryId); + if (chargeCategoryEntry == null) + return 0; + + int recoveryTime = chargeCategoryEntry.ChargeRecoveryTime; + recoveryTime += _owner.GetTotalAuraModifierByMiscValue(AuraType.ChargeRecoveryMod, (int)chargeCategoryId); + + float recoveryTimeF = recoveryTime; + recoveryTimeF *= _owner.GetTotalAuraMultiplierByMiscValue(AuraType.ChargeRecoveryMultiplier, (int)chargeCategoryId); + + if (_owner.HasAuraType(AuraType.ChargeRecoveryAffectedByHaste)) + recoveryTimeF *= _owner.GetFloatValue(UnitFields.ModCastHaste); + + if (_owner.HasAuraType(AuraType.ChargeRecoveryAffectedByHasteRegen)) + recoveryTimeF *= _owner.GetFloatValue(UnitFields.ModHasteRegen); + + return (int)Math.Floor(recoveryTimeF); + } + + public bool HasGlobalCooldown(SpellInfo spellInfo) + { + return _globalCooldowns.ContainsKey(spellInfo.StartRecoveryCategory) && _globalCooldowns[spellInfo.StartRecoveryCategory] > DateTime.Now; + } + + public void AddGlobalCooldown(SpellInfo spellInfo, uint duration) + { + _globalCooldowns[spellInfo.StartRecoveryCategory] = DateTime.Now + TimeSpan.FromMilliseconds(duration); + } + + public void CancelGlobalCooldown(SpellInfo spellInfo) + { + _globalCooldowns[spellInfo.StartRecoveryCategory] = new DateTime(); + } + + public Player GetPlayerOwner() + { + return _owner.GetCharmerOrOwnerPlayerOrPlayerItself(); + } + + public void SendClearCooldowns(List cooldowns) + { + Player playerOwner = GetPlayerOwner(); + if (playerOwner) + { + ClearCooldowns clearCooldowns = new ClearCooldowns(); + clearCooldowns.IsPet = _owner != playerOwner; + clearCooldowns.SpellID = cooldowns; + playerOwner.SendPacket(clearCooldowns); + } + } + + void GetCooldownDurations(SpellInfo spellInfo, uint itemId, ref uint categoryId) + { + int notUsed = 0; + GetCooldownDurations(spellInfo, itemId, ref notUsed, ref categoryId, ref notUsed); + } + + void GetCooldownDurations(SpellInfo spellInfo, uint itemId, ref int cooldown, ref uint categoryId, ref int categoryCooldown) + { + int tmpCooldown = -1; + uint tmpCategoryId = 0; + int tmpCategoryCooldown = -1; + + // cooldown information stored in ItemEffect.db2, overriding normal cooldown and category + if (itemId != 0) + { + ItemTemplate proto = Global.ObjectMgr.GetItemTemplate(itemId); + if (proto != null) + { + foreach (ItemEffectRecord itemEffect in proto.Effects) + { + if (itemEffect.SpellID == spellInfo.Id) + { + tmpCooldown = itemEffect.Cooldown; + tmpCategoryId = itemEffect.Category; + tmpCategoryCooldown = itemEffect.CategoryCooldown; + break; + } + } + } + } + + // if no cooldown found above then base at DBC data + if (tmpCooldown < 0 && tmpCategoryCooldown < 0) + { + tmpCooldown = (int)spellInfo.RecoveryTime; + tmpCategoryId = spellInfo.GetCategory(); + tmpCategoryCooldown = (int)spellInfo.CategoryRecoveryTime; + } + + cooldown = tmpCooldown; + categoryId = tmpCategoryId; + categoryCooldown = tmpCategoryCooldown; + } + + public void SaveCooldownStateBeforeDuel() + { + _spellCooldownsBeforeDuel = _spellCooldowns; + } + + public void RestoreCooldownStateAfterDuel() + { + Player player = _owner.ToPlayer(); + if (player) + { + // add all profession CDs created while in duel (if any) + foreach (var c in _spellCooldowns) + { + SpellInfo spellInfo = Global.SpellMgr.GetSpellInfo(c.Key); + + if (spellInfo.RecoveryTime > 10 * Time.Minute * Time.InMilliseconds || spellInfo.CategoryRecoveryTime > 10 * Time.Minute * Time.InMilliseconds) + _spellCooldownsBeforeDuel[c.Key] = _spellCooldowns[c.Key]; + } + + // check for spell with onHold active before and during the duel + foreach (var pair in _spellCooldownsBeforeDuel) + { + if (!pair.Value.OnHold && _spellCooldowns.ContainsKey(pair.Key) && !_spellCooldowns[pair.Key].OnHold) + _spellCooldowns[pair.Key] = _spellCooldownsBeforeDuel[pair.Key]; + } + + // update the client: restore old cooldowns + SpellCooldownPkt spellCooldown = new SpellCooldownPkt(); + spellCooldown.Caster = _owner.GetGUID(); + spellCooldown.Flags = SpellCooldownFlags.IncludeEventCooldowns; + + foreach (var c in _spellCooldowns) + { + DateTime now = DateTime.Now; + uint cooldownDuration = c.Value.CooldownEnd > now ? (uint)(c.Value.CooldownEnd - now).TotalMilliseconds : 0; + + // cooldownDuration must be between 0 and 10 minutes in order to avoid any visual bugs + if (cooldownDuration <= 0 || cooldownDuration > 10 * Time.Minute * Time.InMilliseconds || c.Value.OnHold) + continue; + + spellCooldown.SpellCooldowns.Add(new SpellCooldownStruct(c.Key, cooldownDuration)); + } + + player.SendPacket(spellCooldown); + } + } + + Unit _owner; + Dictionary _spellCooldowns = new Dictionary(); + Dictionary _spellCooldownsBeforeDuel = new Dictionary(); + Dictionary _categoryCooldowns = new Dictionary(); + DateTime[] _schoolLockouts = new DateTime[(int)SpellSchools.Max]; + MultiMap _categoryCharges = new MultiMap(); + Dictionary _globalCooldowns = new Dictionary(); + + public class CooldownEntry + { + public uint SpellId; + public DateTime CooldownEnd; + public uint ItemId; + public uint CategoryId; + public DateTime CategoryEnd; + public bool OnHold; + } + + struct ChargeEntry + { + public ChargeEntry(DateTime startTime, TimeSpan rechargeTime) + { + RechargeStart = startTime; + RechargeEnd = startTime + rechargeTime; + } + public ChargeEntry(DateTime startTime, DateTime endTime) + { + RechargeStart = startTime; + RechargeEnd = endTime; + } + + public DateTime RechargeStart; + public DateTime RechargeEnd; + } + + class CooldownDurations + { + public int Cooldown = -1; + public uint CategoryId = 0; + public int CategoryCooldown = -1; + } + } +} diff --git a/Game/Spells/SpellInfo.cs b/Game/Spells/SpellInfo.cs new file mode 100644 index 000000000..f07a90802 --- /dev/null +++ b/Game/Spells/SpellInfo.cs @@ -0,0 +1,3690 @@ +/* + * Copyright (C) 2012-2017 CypherCore + * + * 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, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +using Framework.Constants; +using Framework.Dynamic; +using Game.BattleGrounds; +using Game.Conditions; +using Game.DataStorage; +using Game.Entities; +using Game.Maps; +using System; +using System.Collections.Generic; +using System.Linq; + +namespace Game.Spells +{ + public class SpellInfo + { + public SpellInfo(SpellInfoLoadHelper data, Dictionary effectsMap, MultiMap visuals, Dictionary effectScaling) + { + Id = data.Entry.Id; + + _effects = new Dictionary(); + + // SpellDifficultyEntry + if (effectsMap != null) + { + foreach (var pair in effectsMap) + { + if (!_effects.ContainsKey(pair.Key)) + _effects[pair.Key] = new SpellEffectInfo[pair.Value.Length]; + + for (int i = 0; i < pair.Value.Length; ++i) + { + SpellEffectRecord effect = pair.Value[i]; + if (effect == null) + continue; + + var scaling = effectScaling.LookupByKey(effect.Id); + _effects[pair.Key][effect.EffectIndex] = new SpellEffectInfo(scaling, this, effect.EffectIndex, effect); + } + } + } + + SpellName = data.Entry.Name; + + SpellMiscRecord _misc = data.Misc; + if (_misc != null) + { + Attributes = (SpellAttr0)_misc.Attributes; + AttributesEx = (SpellAttr1)_misc.AttributesEx; + AttributesEx2 = (SpellAttr2)_misc.AttributesExB; + AttributesEx3 = (SpellAttr3)_misc.AttributesExC; + AttributesEx4 = (SpellAttr4)_misc.AttributesExD; + AttributesEx5 = (SpellAttr5)_misc.AttributesExE; + AttributesEx6 = (SpellAttr6)_misc.AttributesExF; + AttributesEx7 = (SpellAttr7)_misc.AttributesExG; + AttributesEx8 = (SpellAttr8)_misc.AttributesExH; + AttributesEx9 = (SpellAttr9)_misc.AttributesExI; + AttributesEx10 = (SpellAttr10)_misc.AttributesExJ; + AttributesEx11 = (SpellAttr11)_misc.AttributesExK; + AttributesEx12 = (SpellAttr12)_misc.AttributesExL; + AttributesEx13 = (SpellAttr13)_misc.AttributesExM; + CastTimeEntry = CliDB.SpellCastTimesStorage.LookupByKey(_misc.CastingTimeIndex); + DurationEntry = CliDB.SpellDurationStorage.LookupByKey(_misc.DurationIndex); + RangeIndex = _misc.RangeIndex; + RangeEntry = CliDB.SpellRangeStorage.LookupByKey(_misc.RangeIndex); + Speed = _misc.Speed; + SchoolMask = (SpellSchoolMask)_misc.SchoolMask; + AttributesCu = 0; + + IconFileDataId = _misc.IconFileDataID; + ActiveIconFileDataId = _misc.ActiveIconFileDataID; + } + + if (visuals != null) + _visuals = visuals; + // sort all visuals so that the ones without a condition requirement are last on the list + foreach (var key in _visuals.Keys) + _visuals[key].OrderByDescending(x => x.PlayerConditionID); + + // SpellScalingEntry + SpellScalingRecord _scaling = data.Scaling; + if (_scaling != null) + { + Scaling._Class = _scaling.ScalingClass; + Scaling.MinScalingLevel = _scaling.MinScalingLevel; + Scaling.MaxScalingLevel = _scaling.MaxScalingLevel; + Scaling.ScalesFromItemLevel = _scaling.ScalesFromItemLevel; + } + + // SpellAuraOptionsEntry + SpellAuraOptionsRecord _options = data.AuraOptions; + if (_options != null) + { + SpellProcsPerMinuteRecord _ppm = CliDB.SpellProcsPerMinuteStorage.LookupByKey(_options.SpellProcsPerMinuteID); + ProcFlags = (ProcFlags)_options.ProcTypeMask; + ProcChance = _options.ProcChance; + ProcCharges = _options.ProcCharges; + ProcCooldown = _options.ProcCategoryRecovery; + ProcBasePPM = _ppm != null ? _ppm.BaseProcRate : 0.0f; + ProcPPMMods = Global.DB2Mgr.GetSpellProcsPerMinuteMods(_options.SpellProcsPerMinuteID); + StackAmount = _options.CumulativeAura; + } + + // SpellAuraRestrictionsEntry + SpellAuraRestrictionsRecord _aura = data.AuraRestrictions; + if (_aura != null) + { + CasterAuraState = (AuraStateType)_aura.CasterAuraState; + TargetAuraState = (AuraStateType)_aura.TargetAuraState; + CasterAuraStateNot = (AuraStateType)_aura.ExcludeCasterAuraState; + TargetAuraStateNot = (AuraStateType)_aura.ExcludeTargetAuraState; + CasterAuraSpell = _aura.CasterAuraSpell; + TargetAuraSpell = _aura.TargetAuraSpell; + ExcludeCasterAuraSpell = _aura.ExcludeCasterAuraSpell; + ExcludeTargetAuraSpell = _aura.ExcludeTargetAuraSpell; + } + + RequiredAreasID = -1; + // SpellCastingRequirementsEntry + SpellCastingRequirementsRecord _castreq = data.CastingRequirements; + if (_castreq != null) + { + RequiresSpellFocus = _castreq.RequiresSpellFocus; + FacingCasterFlags = _castreq.FacingCasterFlags; + RequiredAreasID = _castreq.RequiredAreasID; + } + + // SpellCategoriesEntry + SpellCategoriesRecord _categorie = data.Categories; + if (_categorie != null) + { + CategoryId = _categorie.Category; + Dispel = (DispelType)_categorie.DispelType; + Mechanic = (Mechanics)_categorie.Mechanic; + StartRecoveryCategory = _categorie.StartRecoveryCategory; + DmgClass = (SpellDmgClass)_categorie.DefenseType; + PreventionType = (SpellPreventionType)_categorie.PreventionType; + ChargeCategoryId = _categorie.ChargeCategory; + } + + // SpellClassOptionsEntry + SpellFamilyFlags = new FlagArray128(); + SpellClassOptionsRecord _class = data.ClassOptions; + if (_class != null) + { + SpellFamilyName = (SpellFamilyNames)_class.SpellClassSet; + SpellFamilyFlags = _class.SpellClassMask; + } + + // SpellCooldownsEntry + SpellCooldownsRecord _cooldowns = data.Cooldowns; + if (_cooldowns != null) + { + RecoveryTime = _cooldowns.RecoveryTime; + CategoryRecoveryTime = _cooldowns.CategoryRecoveryTime; + StartRecoveryTime = _cooldowns.StartRecoveryTime; + } + + EquippedItemClass = ItemClass.None; + EquippedItemSubClassMask = -1; + EquippedItemInventoryTypeMask = -1; + // SpellEquippedItemsEntry + SpellEquippedItemsRecord _equipped = data.EquippedItems; + if (_equipped != null) + { + EquippedItemClass = (ItemClass)_equipped.EquippedItemClass; + EquippedItemSubClassMask = _equipped.EquippedItemSubClassMask; + EquippedItemInventoryTypeMask = _equipped.EquippedItemInventoryTypeMask; + } + + // SpellInterruptsEntry + SpellInterruptsRecord _interrupt = data.Interrupts; + if (_interrupt != null) + { + InterruptFlags = (SpellInterruptFlags)_interrupt.InterruptFlags; + // TODO: 6.x these flags have 2 parts + AuraInterruptFlags = (SpellAuraInterruptFlags)_interrupt.AuraInterruptFlags[0]; + ChannelInterruptFlags = (SpellChannelInterruptFlags)_interrupt.ChannelInterruptFlags[0]; + } + + // SpellLevelsEntry + SpellLevelsRecord _levels = data.Levels; + if (_levels != null) + { + MaxLevel = _levels.MaxLevel; + BaseLevel = _levels.BaseLevel; + SpellLevel = _levels.SpellLevel; + } + + // SpellPowerEntry + PowerCosts = Global.DB2Mgr.GetSpellPowers(Id, Difficulty.None, out _hasPowerDifficultyData); + + // SpellReagentsEntry + SpellReagentsRecord _reagents = data.Reagents; + for (var i = 0; i < SpellConst.MaxReagents; ++i) + { + Reagent[i] = _reagents != null ? _reagents.Reagent[i] : 0; + ReagentCount[i] = _reagents != null ? _reagents.ReagentCount[i] : 0u; + } + + // SpellShapeshiftEntry + SpellShapeshiftRecord _shapeshift = data.Shapeshift; + if (_shapeshift != null) + { + Stances = MathFunctions.MakePair64(_shapeshift.ShapeshiftMask[0], _shapeshift.ShapeshiftMask[1]); + StancesNot = MathFunctions.MakePair64(_shapeshift.ShapeshiftExclude[0], _shapeshift.ShapeshiftExclude[1]); + } + + // SpellTargetRestrictionsEntry + SpellTargetRestrictionsRecord _target = data.TargetRestrictions; + if (_target != null) + { + targets = (SpellCastTargetFlags)_target.Targets; + TargetCreatureType = _target.TargetCreatureType; + MaxAffectedTargets = _target.MaxAffectedTargets; + MaxTargetLevel = _target.MaxTargetLevel; + } + + // SpellTotemsEntry + SpellTotemsRecord _totem = data.Totems; + for (var i = 0; i < 2; ++i) + { + TotemCategory[i] = _totem != null ? _totem.RequiredTotemCategoryID[i] : 0u; + Totem[i] = _totem != null ? _totem.Totem[i] : 0; + } + ChainEntry = null; + ExplicitTargetMask = 0; + } + + public bool HasEffect(Difficulty difficulty, SpellEffectName effect) + { + var effects = GetEffectsForDifficulty(difficulty); + foreach (var eff in effects) + { + if (eff != null && eff.IsEffect(effect)) + return true; + } + + return false; + } + + public bool HasEffect(SpellEffectName effect) + { + foreach (var pair in _effects) + { + foreach (SpellEffectInfo eff in pair.Value) + { + if (eff != null && eff.IsEffect(effect)) + return true; + } + + } + return false; + } + + public bool HasAura(Difficulty difficulty, AuraType aura) + { + var effects = GetEffectsForDifficulty(difficulty); + foreach (SpellEffectInfo effect in effects) + { + if (effect != null && effect.IsAura(aura)) + return true; + } + return false; + } + + public bool HasAreaAuraEffect(Difficulty difficulty) + { + var effects = GetEffectsForDifficulty(difficulty); + foreach (SpellEffectInfo effect in effects) + { + if (effect != null && effect.IsAreaAuraEffect()) + return true; + } + + return false; + } + + public bool HasAreaAuraEffect() + { + foreach (var pair in _effects) + { + foreach (SpellEffectInfo effect in pair.Value) + { + if (effect != null && effect.IsAreaAuraEffect()) + return true; + } + } + return false; + } + + public bool IsExplicitDiscovery() + { + SpellEffectInfo effect0 = GetEffect(Difficulty.None, 0); + SpellEffectInfo effect1 = GetEffect(Difficulty.None, 1); + + return ((effect0 != null && (effect0.Effect == SpellEffectName.CreateRandomItem || effect0.Effect == SpellEffectName.CreateLoot)) + && effect1 != null && effect1.Effect == SpellEffectName.ScriptEffect) + || Id == 64323; + } + + public bool IsLootCrafting() + { + return HasEffect(SpellEffectName.CreateRandomItem) || HasEffect(SpellEffectName.CreateLoot); + } + + public bool IsQuestTame() + { + SpellEffectInfo effect0 = GetEffect(Difficulty.None, 0); + SpellEffectInfo effect1 = GetEffect(Difficulty.None, 1); + return effect0 != null && effect1 != null && effect0.Effect == SpellEffectName.Threat && effect1.Effect == SpellEffectName.ApplyAura + && effect1.ApplyAuraName == AuraType.Dummy; + } + + public bool IsProfession(Difficulty difficulty = Difficulty.None) + { + var effects = GetEffectsForDifficulty(difficulty); + foreach (SpellEffectInfo effect in effects) + { + if (effect != null && effect.Effect == SpellEffectName.Skill) + { + uint skill = (uint)effect.MiscValue; + + if (Global.SpellMgr.IsProfessionSkill(skill)) + return true; + } + } + return false; + } + + public bool IsPrimaryProfession(Difficulty difficulty) + { + var effects = GetEffectsForDifficulty(difficulty); + foreach (SpellEffectInfo effect in effects) + { + if (effect != null && effect.Effect == SpellEffectName.Skill) + { + uint skill = (uint)effect.MiscValue; + + if (Global.SpellMgr.IsPrimaryProfessionSkill(skill)) + return true; + } + } + return false; + } + + public bool IsPrimaryProfessionFirstRank(Difficulty difficulty = Difficulty.None) + { + return IsPrimaryProfession(difficulty) && GetRank() == 1; + } + + public bool IsAbilityOfSkillType(SkillType skillType) + { + var bounds = Global.SpellMgr.GetSkillLineAbilityMapBounds(Id); + + foreach (var spell_idx in bounds) + if (spell_idx.SkillLine == (uint)skillType) + return true; + + return false; + } + + public bool IsAffectingArea(Difficulty difficulty) + { + var effects = GetEffectsForDifficulty(difficulty); + foreach (SpellEffectInfo effect in effects) + { + if (effect != null && effect.IsEffect() && (effect.IsTargetingArea() || effect.IsEffect(SpellEffectName.PersistentAreaAura) || effect.IsAreaAuraEffect())) + return true; + } + return false; + } + + // checks if spell targets are selected from area, doesn't include spell effects in check (like area wide auras for example) + public bool IsTargetingArea(Difficulty difficulty) + { + var effects = GetEffectsForDifficulty(difficulty); + foreach (SpellEffectInfo effect in effects) + { + if (effect != null && effect.IsEffect() && effect.IsTargetingArea()) + return true; + } + return false; + } + + public bool NeedsExplicitUnitTarget() + { + return Convert.ToBoolean(GetExplicitTargetMask() & SpellCastTargetFlags.UnitMask); + } + + public bool NeedsToBeTriggeredByCaster(SpellInfo triggeringSpell, Difficulty difficulty) + { + if (NeedsExplicitUnitTarget()) + return true; + + if (triggeringSpell.IsChanneled()) + { + SpellCastTargetFlags mask = 0; + var effects = GetEffectsForDifficulty(difficulty); + foreach (SpellEffectInfo effect in effects) + { + if (effect != null && (effect.TargetA.GetTarget() != Targets.UnitCaster && effect.TargetA.GetTarget() != Targets.DestCaster + && effect.TargetB.GetTarget() != Targets.UnitCaster && effect.TargetB.GetTarget() != Targets.DestCaster)) + { + mask |= effect.GetProvidedTargetMask(); + } + } + + if (mask.HasAnyFlag(SpellCastTargetFlags.UnitMask)) + return true; + } + + return false; + } + + public bool IsPassive() + { + return HasAttribute(SpellAttr0.Passive); + } + + public bool IsAutocastable() + { + if (IsPassive()) + return false; + if (HasAttribute(SpellAttr1.UnautocastableByPet)) + return false; + return true; + } + + public bool IsStackableWithRanks() + { + if (IsPassive()) + return false; + + // All stance spells. if any better way, change it. + var effects = GetEffectsForDifficulty(Difficulty.None); + foreach (SpellEffectInfo effect in effects) + { + if (effect == null) + continue; + + switch (SpellFamilyName) + { + case SpellFamilyNames.Paladin: + // Paladin aura Spell + if (effect.Effect == SpellEffectName.ApplyAreaAuraRaid) + return false; + break; + case SpellFamilyNames.Druid: + // Druid form Spell + if (effect.Effect == SpellEffectName.ApplyAura && + effect.ApplyAuraName == AuraType.ModShapeshift) + return false; + break; + } + } + return true; + } + + public bool IsPassiveStackableWithRanks() + { + return IsPassive() && !HasEffect(SpellEffectName.ApplyAura); + } + + public bool IsMultiSlotAura() + { + return IsPassive() || Id == 55849 || Id == 40075 || Id == 44413; // Power Spark, Fel Flak Fire, Incanter's Absorption + } + + public bool IsStackableOnOneSlotWithDifferentCasters() + { + // TODO: Re-verify meaning of SPELL_ATTR3_STACK_FOR_DIFF_CASTERS and update conditions here + return StackAmount > 1 && !IsChanneled() && !HasAttribute(SpellAttr3.StackForDiffCasters); + } + + public bool IsCooldownStartedOnEvent() + { + if (HasAttribute(SpellAttr0.DisabledWhileActive)) + return true; + + SpellCategoryRecord category = CliDB.SpellCategoryStorage.LookupByKey(CategoryId); + return category != null && category.Flags.HasAnyFlag(SpellCategoryFlags.CooldownStartsOnEvent); + } + + public bool IsDeathPersistent() + { + return HasAttribute(SpellAttr3.DeathPersistent); + } + + public bool IsRequiringDeadTarget() + { + return HasAttribute(SpellAttr3.OnlyTargetGhosts); + } + + public bool IsAllowingDeadTarget() + { + return HasAttribute(SpellAttr2.CanTargetDead) + || Convert.ToBoolean(targets & (SpellCastTargetFlags.CorpseAlly | SpellCastTargetFlags.CorpseEnemy | SpellCastTargetFlags.UnitDead)); + } + + public bool IsGroupBuff() + { + var effects = GetEffectsForDifficulty(Difficulty.None); + foreach (SpellEffectInfo effect in effects) + { + if (effect == null) + continue; + + switch (effect.TargetA.GetCheckType()) + { + case SpellTargetCheckTypes.Party: + case SpellTargetCheckTypes.Raid: + case SpellTargetCheckTypes.RaidClass: + return true; + default: + break; + } + } + + return false; + } + + public bool CanBeUsedInCombat() + { + return !HasAttribute(SpellAttr0.CantUsedInCombat); + } + + public bool IsPositive() + { + return !HasAttribute(SpellCustomAttributes.Negative); + } + + public bool IsPositiveEffect(uint effIndex) + { + switch (effIndex) + { + default: + case 0: + return !HasAttribute(SpellCustomAttributes.NegativeEff0); + case 1: + return !HasAttribute(SpellCustomAttributes.NegativeEff1); + case 2: + return !HasAttribute(SpellCustomAttributes.NegativeEff2); + } + } + + public bool IsChanneled() + { + return HasAttribute(SpellAttr1.Channeled1 | SpellAttr1.Channeled2); + } + + public bool NeedsComboPoints() + { + return HasAttribute(SpellAttr1.ReqComboPoints1 | SpellAttr1.ReqComboPoints2); + } + + public bool IsBreakingStealth() + { + return !HasAttribute(SpellAttr1.NotBreakStealth); + } + + public bool IsRangedWeaponSpell() + { + return (SpellFamilyName == SpellFamilyNames.Hunter && !SpellFamilyFlags[1].HasAnyFlag(0x10000000u)) // for 53352, cannot find better way + || Convert.ToBoolean(EquippedItemSubClassMask & (int)ItemSubClassWeapon.MaskRanged); + } + + public bool IsAutoRepeatRangedSpell() + { + return HasAttribute(SpellAttr2.AutorepeatFlag); + } + + public bool HasInitialAggro() + { + return !(HasAttribute(SpellAttr1.NoThreat) || HasAttribute(SpellAttr3.NoInitialAggro)); + } + + bool IsAffectedBySpellMods() + { + return !HasAttribute(SpellAttr3.NoDoneBonus); + } + + public bool IsAffectedBySpellMod(SpellModifier mod) + { + if (!IsAffectedBySpellMods()) + return false; + + SpellInfo affectSpell = Global.SpellMgr.GetSpellInfo(mod.spellId); + // False if affect_spell == NULL or spellFamily not equal + if (affectSpell == null || affectSpell.SpellFamilyName != SpellFamilyName) + return false; + + // true + if (mod.mask & SpellFamilyFlags) + return true; + + return false; + } + + public bool CanPierceImmuneAura(SpellInfo aura) + { + // these spells pierce all avalible spells (Resurrection Sickness for example) + if (HasAttribute(SpellAttr0.UnaffectedByInvulnerability)) + return true; + + // these spells (Cyclone for example) can pierce all... | ...but not these (Divine shield, Ice block, Cyclone and Banish for example) + if (HasAttribute(SpellAttr1.UnaffectedBySchoolImmune) + && !(aura != null && (aura.Mechanic == Mechanics.Immune_Shield || aura.Mechanic == Mechanics.Invulnerability || aura.Mechanic == Mechanics.Banish))) + return true; + + return false; + } + + public bool CanDispelAura(SpellInfo aura) + { + // These spells (like Mass Dispel) can dispell all auras + if (HasAttribute(SpellAttr0.UnaffectedByInvulnerability) && !aura.IsDeathPersistent()) + return true; + + // These auras (like Divine Shield) can't be dispelled + if (aura.HasAttribute(SpellAttr0.UnaffectedByInvulnerability)) + return false; + + // These auras (Cyclone for example) are not dispelable + if (aura.HasAttribute(SpellAttr1.UnaffectedBySchoolImmune)) + return false; + + return true; + } + + public bool IsSingleTarget() + { + // all other single target spells have if it has AttributesEx5 + if (HasAttribute(SpellAttr5.SingleTargetSpell)) + return true; + + switch (GetSpellSpecific()) + { + case SpellSpecificType.Judgement: + return true; + default: + break; + } + + return false; + } + + public bool IsAuraExclusiveBySpecificWith(SpellInfo spellInfo) + { + SpellSpecificType spellSpec1 = GetSpellSpecific(); + SpellSpecificType spellSpec2 = spellInfo.GetSpellSpecific(); + switch (spellSpec1) + { + case SpellSpecificType.WarlockArmor: + case SpellSpecificType.MageArmor: + case SpellSpecificType.ElementalShield: + case SpellSpecificType.MagePolymorph: + case SpellSpecificType.Presence: + case SpellSpecificType.Charm: + case SpellSpecificType.Scroll: + case SpellSpecificType.WarriorEnrage: + case SpellSpecificType.MageArcaneBrillance: + case SpellSpecificType.PriestDivineSpirit: + return spellSpec1 == spellSpec2; + case SpellSpecificType.Food: + return spellSpec2 == SpellSpecificType.Food + || spellSpec2 == SpellSpecificType.FoodAndDrink; + case SpellSpecificType.Drink: + return spellSpec2 == SpellSpecificType.Drink + || spellSpec2 == SpellSpecificType.FoodAndDrink; + case SpellSpecificType.FoodAndDrink: + return spellSpec2 == SpellSpecificType.Food + || spellSpec2 == SpellSpecificType.Drink + || spellSpec2 == SpellSpecificType.FoodAndDrink; + default: + return false; + } + } + + public bool IsAuraExclusiveBySpecificPerCasterWith(SpellInfo spellInfo) + { + SpellSpecificType spellSpec = GetSpellSpecific(); + switch (spellSpec) + { + case SpellSpecificType.Seal: + case SpellSpecificType.Hand: + case SpellSpecificType.Aura: + case SpellSpecificType.Sting: + case SpellSpecificType.Curse: + case SpellSpecificType.Bane: + case SpellSpecificType.Aspect: + case SpellSpecificType.Judgement: + case SpellSpecificType.WarlockCorruption: + return spellSpec == spellInfo.GetSpellSpecific(); + default: + return false; + } + } + + public SpellCastResult CheckShapeshift(ShapeShiftForm form) + { + // talents that learn spells can have stance requirements that need ignore + // (this requirement only for client-side stance show in talent description) + /* TODO: 6.x fix this in proper way (probably spell flags/attributes?) + if (CliDB.GetTalentSpellCost(Id) > 0 && + (Effects[0].Effect == SpellEffects.LearnSpell || Effects[1].Effect == SpellEffects.LearnSpell || Effects[2].Effect == SpellEffects.LearnSpell)) + return SpellCastResult.SpellCastOk; + */ + + //if (HasAttribute(SPELL_ATTR13_ACTIVATES_REQUIRED_SHAPESHIFT)) + // return SPELL_CAST_OK; + + ulong stanceMask = (form != 0 ? 1ul << ((int)form - 1) : 0); + + if (Convert.ToBoolean(stanceMask & StancesNot)) // can explicitly not be casted in this stance + return SpellCastResult.NotShapeshift; + + if (Convert.ToBoolean(stanceMask & Stances)) // can explicitly be casted in this stance + return SpellCastResult.SpellCastOk; + + bool actAsShifted = false; + SpellShapeshiftFormRecord shapeInfo = null; + if (form > 0) + { + shapeInfo = CliDB.SpellShapeshiftFormStorage.LookupByKey(form); + if (shapeInfo == null) + { + Log.outError(LogFilter.Spells, "GetErrorAtShapeshiftedCast: unknown shapeshift {0}", form); + return SpellCastResult.SpellCastOk; + } + actAsShifted = !shapeInfo.Flags.HasAnyFlag(SpellShapeshiftFormFlags.IsNotAShapeshift); + } + + if (actAsShifted) + { + if (HasAttribute(SpellAttr0.NotShapeshift) || (shapeInfo != null && shapeInfo.Flags.HasAnyFlag(SpellShapeshiftFormFlags.PreventUsingOwnSkills))) // not while shapeshifted + return SpellCastResult.NotShapeshift; + else if (Stances != 0) // needs other shapeshift + return SpellCastResult.OnlyShapeshift; + } + else + { + // needs shapeshift + if (!HasAttribute(SpellAttr2.NotNeedShapeshift) && Stances != 0) + return SpellCastResult.OnlyShapeshift; + } + + return SpellCastResult.SpellCastOk; + } + + public SpellCastResult CheckLocation(uint map_id, uint zone_id, uint area_id, Player player) + { + // normal case + if (RequiredAreasID > 0) + { + bool found = false; + List areaGroupMembers = Global.DB2Mgr.GetAreasForGroup((uint)RequiredAreasID); + foreach (uint areaId in areaGroupMembers) + { + if (areaId == zone_id || areaId == area_id) + { + found = true; + break; + } + } + + if (!found) + return SpellCastResult.IncorrectArea; + } + + // continent limitation (virtual continent) + if (HasAttribute(SpellAttr4.CastOnlyInOutland)) + { + uint v_map = Global.DB2Mgr.GetVirtualMapForMapAndZone(map_id, zone_id); + var map = CliDB.MapStorage.LookupByKey(v_map); + if (map == null || map.ExpansionID < 1 || !map.IsContinent()) + return SpellCastResult.IncorrectArea; + } + + var mapEntry = CliDB.MapStorage.LookupByKey(map_id); + + // raid instance limitation + if (HasAttribute(SpellAttr6.NotInRaidInstance)) + { + if (mapEntry == null || mapEntry.IsRaid()) + return SpellCastResult.NotInRaidInstance; + } + + // DB base check (if non empty then must fit at least single for allow) + var saBounds = Global.SpellMgr.GetSpellAreaMapBounds(Id); + if (!saBounds.Empty()) + { + foreach (var bound in saBounds) + { + if (bound.IsFitToRequirements(player, zone_id, area_id)) + return SpellCastResult.SpellCastOk; + } + return SpellCastResult.IncorrectArea; + } + + // bg spell checks + switch (Id) + { + case 23333: // Warsong Flag + case 23335: // Silverwing Flag + return map_id == 489 && player != null && player.InBattleground() ? SpellCastResult.SpellCastOk : SpellCastResult.RequiresArea; + case 34976: // Netherstorm Flag + return map_id == 566 && player != null && player.InBattleground() ? SpellCastResult.SpellCastOk : SpellCastResult.RequiresArea; + case 2584: // Waiting to Resurrect + case 22011: // Spirit Heal Channel + case 22012: // Spirit Heal + case 24171: // Resurrection Impact Visual + case 42792: // Recently Dropped Flag + case 43681: // Inactive + case 44535: // Spirit Heal (mana) + if (mapEntry == null) + return SpellCastResult.IncorrectArea; + + return zone_id == 4197 || (mapEntry.IsBattleground() && player != null && player.InBattleground()) ? SpellCastResult.SpellCastOk : SpellCastResult.RequiresArea; + case 44521: // Preparation + { + if (player == null) + return SpellCastResult.RequiresArea; + + if (mapEntry == null) + return SpellCastResult.IncorrectArea; + + if (!mapEntry.IsBattleground()) + return SpellCastResult.RequiresArea; + + Battleground bg = player.GetBattleground(); + return bg && bg.GetStatus() == BattlegroundStatus.WaitJoin ? SpellCastResult.SpellCastOk : SpellCastResult.RequiresArea; + } + case 32724: // Gold Team (Alliance) + case 32725: // Green Team (Alliance) + case 35774: // Gold Team (Horde) + case 35775: // Green Team (Horde) + if (mapEntry == null) + return SpellCastResult.IncorrectArea; + + return mapEntry.IsBattleArena() && player != null && player.InBattleground() ? SpellCastResult.SpellCastOk : SpellCastResult.RequiresArea; + case 32727: // Arena Preparation + { + if (player == null) + return SpellCastResult.RequiresArea; + + if (mapEntry == null) + return SpellCastResult.IncorrectArea; + + if (!mapEntry.IsBattleArena()) + return SpellCastResult.RequiresArea; + + Battleground bg = player.GetBattleground(); + return bg && bg.GetStatus() == BattlegroundStatus.WaitJoin ? SpellCastResult.SpellCastOk : SpellCastResult.RequiresArea; + } + } + + // aura limitations + if (player) + { + foreach (SpellEffectInfo effect in GetEffectsForDifficulty(player.GetMap().GetDifficultyID())) + { + if (effect == null || !effect.IsAura()) + continue; + + switch (effect.ApplyAuraName) + { + case AuraType.Fly: + { + var bounds = Global.SpellMgr.GetSkillLineAbilityMapBounds(Id); + foreach (var skillRecord in bounds) + { + if (skillRecord.SkillLine == (int)SkillType.Mounts) + if (!player.CanFlyInZone(map_id, zone_id)) + return SpellCastResult.IncorrectArea; + } + break; + } + case AuraType.Mounted: + { + uint mountType = (uint)effect.MiscValueB; + MountRecord mountEntry = Global.DB2Mgr.GetMount(Id); + if (mountEntry != null) + mountType = mountEntry.MountTypeId; + if (mountType != 0 && player.GetMountCapability(mountType) == null) + return SpellCastResult.NotHere; + break; + } + } + } + } + + return SpellCastResult.SpellCastOk; + } + + public SpellCastResult CheckTarget(Unit caster, WorldObject target, bool Implicit = true) + { + if (HasAttribute(SpellAttr1.CantTargetSelf) && caster == target) + return SpellCastResult.BadTargets; + + // check visibility - ignore stealth for implicit (area) targets + if (!HasAttribute(SpellAttr6.CanTargetInvisible) && !caster.CanSeeOrDetect(target, Implicit)) + return SpellCastResult.BadTargets; + + Unit unitTarget = target.ToUnit(); + + // creature/player specific target checks + if (unitTarget != null) + { + if (HasAttribute(SpellAttr1.CantTargetInCombat)) + { + if (unitTarget.IsInCombat()) + return SpellCastResult.TargetAffectingCombat; + // player with active pet counts as a player in combat + else if (unitTarget.IsTypeId(TypeId.Player)) + { + Pet pet = unitTarget.ToPlayer().GetPet(); + if (pet) + if (pet.GetVictim() && !pet.HasUnitState(UnitState.Controlled)) + return SpellCastResult.TargetAffectingCombat; + } + } + + // only spells with SPELL_ATTR3_ONLY_TARGET_GHOSTS can target ghosts + if (HasAttribute(SpellAttr3.OnlyTargetGhosts) != unitTarget.HasAuraType(AuraType.Ghost)) + { + if (HasAttribute(SpellAttr3.OnlyTargetGhosts)) + return SpellCastResult.TargetNotGhost; + else + return SpellCastResult.BadTargets; + } + + if (caster != unitTarget) + { + if (caster.IsTypeId(TypeId.Player)) + { + // Do not allow these spells to target creatures not tapped by us (Banish, Polymorph, many quest spells) + if (HasAttribute(SpellAttr2.CantTargetTapped)) + { + Creature targetCreature = unitTarget.ToCreature(); + if (targetCreature != null) + if (targetCreature.hasLootRecipient() && targetCreature.isTappedBy(caster.ToPlayer())) + return SpellCastResult.CantCastOnTapped; + } + + if (HasAttribute(SpellCustomAttributes.PickPocket)) + { + if (unitTarget.IsTypeId(TypeId.Player)) + return SpellCastResult.BadTargets; + else if ((unitTarget.GetCreatureTypeMask() & (uint)CreatureType.MaskHumanoidOrUndead) == 0) + return SpellCastResult.TargetNoPockets; + } + + // Not allow disarm unarmed player + if (Mechanic == Mechanics.Disarm) + { + if (unitTarget.IsTypeId(TypeId.Player)) + { + Player player = unitTarget.ToPlayer(); + if (player.GetWeaponForAttack(WeaponAttackType.BaseAttack) == null || !player.IsUseEquipedWeapon(true)) + return SpellCastResult.TargetNoWeapons; + } + else if (unitTarget.GetVirtualItemId(0) == 0) + return SpellCastResult.TargetNoWeapons; + } + } + } + } + // corpse specific target checks + else if (target.IsTypeId(TypeId.Corpse)) + { + Corpse corpseTarget = target.ToCorpse(); + // cannot target bare bones + if (corpseTarget.GetCorpseType() == CorpseType.Bones) + return SpellCastResult.BadTargets; + // we have to use owner for some checks (aura preventing resurrection for example) + Player owner = Global.ObjAccessor.FindPlayer(corpseTarget.GetOwnerGUID()); + if (owner != null) + unitTarget = owner; + // we're not interested in corpses without owner + else + return SpellCastResult.BadTargets; + } + // other types of objects - always valid + else + return SpellCastResult.SpellCastOk; + + // corpseOwner and unit specific target checks + if (HasAttribute(SpellAttr3.OnlyTargetPlayers) && !unitTarget.IsTypeId(TypeId.Player)) + return SpellCastResult.TargetNotPlayer; + + if (!IsAllowingDeadTarget() && !unitTarget.IsAlive()) + return SpellCastResult.TargetsDead; + + // check this flag only for implicit targets (chain and area), allow to explicitly target units for spells like Shield of Righteousness + if (Implicit && HasAttribute(SpellAttr6.CantTargetCrowdControlled) && !unitTarget.CanFreeMove()) + return SpellCastResult.BadTargets; + + if (!CheckTargetCreatureType(unitTarget)) + { + if (target.IsTypeId(TypeId.Player)) + return SpellCastResult.TargetIsPlayer; + else + return SpellCastResult.BadTargets; + } + + // check GM mode and GM invisibility - only for player casts (npc casts are controlled by AI) and negative spells + if (unitTarget != caster && (caster.IsControlledByPlayer() || !IsPositive()) && unitTarget.IsTypeId(TypeId.Player)) + { + if (!unitTarget.ToPlayer().IsVisible()) + return SpellCastResult.BmOrInvisgod; + + if (unitTarget.ToPlayer().IsGameMaster()) + return SpellCastResult.BmOrInvisgod; + } + + // not allow casting on flying player + if (unitTarget.HasUnitState(UnitState.InFlight) && !HasAttribute(SpellCustomAttributes.AllowInflightTarget)) + return SpellCastResult.BadTargets; + + /* TARGET_UNIT_MASTER gets blocked here for passengers, because the whole idea of this check is to + not allow passengers to be implicitly hit by spells, however this target type should be an exception, + if this is left it kills spells that award kill credit from vehicle to master (few spells), + the use of these 2 covers passenger target check, logically, if vehicle cast this to master it should always hit + him, because it would be it's passenger, there's no such case where this gets to fail legitimacy, this problem + cannot be solved from within the check in other way since target type cannot be called for the spell currently + Spell examples: [ID - 52864 Devour Water, ID - 52862 Devour Wind, ID - 49370 Wyrmrest Defender: Destabilize Azure Dragonshrine Effect] */ + if (!caster.IsVehicle() && !(caster.GetCharmerOrOwner() == target)) + { + if (TargetAuraState != 0 && !unitTarget.HasAuraState(TargetAuraState, this, caster)) + return SpellCastResult.TargetAurastate; + + if (TargetAuraStateNot != 0 && unitTarget.HasAuraState(TargetAuraStateNot, this, caster)) + return SpellCastResult.TargetAurastate; + } + + if (TargetAuraSpell != 0 && !unitTarget.HasAura(TargetAuraSpell)) + return SpellCastResult.TargetAurastate; + + if (ExcludeTargetAuraSpell != 0 && unitTarget.HasAura(ExcludeTargetAuraSpell)) + return SpellCastResult.TargetAurastate; + + if (unitTarget.HasAuraType(AuraType.PreventResurrection)) + if (HasEffect(caster.GetMap().GetDifficultyID(), SpellEffectName.SelfResurrect) || HasEffect(caster.GetMap().GetDifficultyID(), SpellEffectName.Resurrect)) + return SpellCastResult.TargetCannotBeResurrected; + + if (HasAttribute(SpellAttr8.BattleResurrection)) + { + Map map = caster.GetMap(); + if (map) + { + InstanceMap iMap = map.ToInstanceMap(); + if (iMap) + { + InstanceScript instance = iMap.GetInstanceScript(); + if (instance != null) + if (instance.GetCombatResurrectionCharges() == 0 && instance.IsEncounterInProgress()) + return SpellCastResult.TargetCannotBeResurrected; + } + } + } + + return SpellCastResult.SpellCastOk; + } + + public SpellCastResult CheckExplicitTarget(Unit caster, WorldObject target, Item itemTarget = null) + { + SpellCastTargetFlags neededTargets = GetExplicitTargetMask(); + if (target == null) + { + if (Convert.ToBoolean(neededTargets & (SpellCastTargetFlags.UnitMask | SpellCastTargetFlags.GameobjectMask | SpellCastTargetFlags.CorpseMask))) + if (!Convert.ToBoolean(neededTargets & SpellCastTargetFlags.GameobjectItem) || itemTarget == null) + return SpellCastResult.BadTargets; + return SpellCastResult.SpellCastOk; + } + Unit unitTarget = target.ToUnit(); + if (unitTarget != null) + { + if (Convert.ToBoolean(neededTargets & (SpellCastTargetFlags.UnitEnemy | SpellCastTargetFlags.UnitAlly | SpellCastTargetFlags.UnitRaid | SpellCastTargetFlags.UnitParty | SpellCastTargetFlags.UnitMinipet | SpellCastTargetFlags.UnitPassenger))) + { + if (Convert.ToBoolean(neededTargets & SpellCastTargetFlags.UnitEnemy)) + if (caster._IsValidAttackTarget(unitTarget, this)) + return SpellCastResult.SpellCastOk; + if (neededTargets.HasAnyFlag(SpellCastTargetFlags.UnitAlly) + || (neededTargets.HasAnyFlag(SpellCastTargetFlags.UnitParty) && caster.IsInPartyWith(unitTarget)) + || (neededTargets.HasAnyFlag(SpellCastTargetFlags.UnitRaid) && caster.IsInRaidWith(unitTarget))) + if (caster._IsValidAssistTarget(unitTarget, this)) + return SpellCastResult.SpellCastOk; + if (Convert.ToBoolean(neededTargets & SpellCastTargetFlags.UnitMinipet)) + if (unitTarget.GetGUID() == caster.GetCritterGUID()) + return SpellCastResult.SpellCastOk; + if (Convert.ToBoolean(neededTargets & SpellCastTargetFlags.UnitPassenger)) + if (unitTarget.IsOnVehicle(caster)) + return SpellCastResult.SpellCastOk; + return SpellCastResult.BadTargets; + } + } + return SpellCastResult.SpellCastOk; + } + + public SpellCastResult CheckVehicle(Unit caster) + { + // All creatures should be able to cast as passengers freely, restriction and attribute are only for players + if (!caster.IsTypeId(TypeId.Player)) + return SpellCastResult.SpellCastOk; + + Vehicle vehicle = caster.GetVehicle(); + if (vehicle) + { + VehicleSeatFlags checkMask = 0; + foreach (SpellEffectInfo effect in GetEffectsForDifficulty(caster.GetMap().GetDifficultyID())) + { + if (effect != null && effect.ApplyAuraName == AuraType.ModShapeshift) + { + var shapeShiftFromEntry = CliDB.SpellShapeshiftFormStorage.LookupByKey((uint)effect.MiscValue); + if (shapeShiftFromEntry != null && !shapeShiftFromEntry.Flags.HasAnyFlag(SpellShapeshiftFormFlags.IsNotAShapeshift)) + checkMask |= VehicleSeatFlags.Uncontrolled; + break; + } + } + + if (HasAura(caster.GetMap().GetDifficultyID(), AuraType.Mounted)) + checkMask |= VehicleSeatFlags.CanCastMountSpell; + + if (checkMask == 0) + checkMask = VehicleSeatFlags.CanAttack; + + var vehicleSeat = vehicle.GetSeatForPassenger(caster); + if (!HasAttribute(SpellAttr6.CastableWhileOnVehicle) && !HasAttribute(SpellAttr0.CastableWhileMounted) + && (vehicleSeat.Flags[0] & (uint)checkMask) != (uint)checkMask) + return SpellCastResult.CantDoThatRightNow; + + // Can only summon uncontrolled minions/guardians when on controlled vehicle + if (vehicleSeat.Flags[0].HasAnyFlag((uint)(VehicleSeatFlags.CanControl | VehicleSeatFlags.Unk2))) + { + foreach (SpellEffectInfo effect in GetEffectsForDifficulty(caster.GetMap().GetDifficultyID())) + { + if (effect == null || effect.Effect != SpellEffectName.Summon) + continue; + + var props = CliDB.SummonPropertiesStorage.LookupByKey(effect.MiscValueB); + if (props != null && props.Category != SummonCategory.Wild) + return SpellCastResult.CantDoThatRightNow; + } + } + } + + return SpellCastResult.SpellCastOk; + } + + public bool CheckTargetCreatureType(Unit target) + { + // Curse of Doom & Exorcism: not find another way to fix spell target check :/ + if (SpellFamilyName == SpellFamilyNames.Warlock && GetCategory() == 1179) + { + // not allow cast at player + if (target.IsTypeId(TypeId.Player)) + return false; + else + return true; + } + + // if target is magnet (i.e Grounding Totem) the check is skipped + //if (target.IsMagnet()) + //return true; + + uint creatureType = target.GetCreatureTypeMask(); + return TargetCreatureType == 0 || creatureType == 0 || Convert.ToBoolean(creatureType & TargetCreatureType); + } + + public SpellSchoolMask GetSchoolMask() + { + return SchoolMask; + } + + public uint GetAllEffectsMechanicMask() + { + uint mask = 0; + if (Mechanic != 0) + mask |= (uint)(1 << (int)Mechanic); + + foreach (var pair in _effects) + { + foreach (SpellEffectInfo effect in pair.Value) + { + if (effect != null && effect.IsEffect() && effect.Mechanic != 0) + mask |= (uint)(1 << (int)effect.Mechanic); + } + } + return mask; + } + + public uint GetEffectMechanicMask(byte effIndex) + { + uint mask = 0; + if (Mechanic != 0) + mask |= (uint)(1 << (int)Mechanic); + + foreach (var pair in _effects) + { + foreach (SpellEffectInfo effect in pair.Value) + { + if (effect != null && effect.EffectIndex == effIndex && effect.IsEffect() && effect.Mechanic != 0) + mask |= (uint)(1 << (int)effect.Mechanic); + } + } + return mask; + } + + public uint GetSpellMechanicMaskByEffectMask(uint effectMask) + { + uint mask = 0; + if (Mechanic != 0) + mask |= (uint)(1 << (int)Mechanic); + + foreach (var pair in _effects) + { + foreach (SpellEffectInfo effect in pair.Value) + { + if (effect != null && Convert.ToBoolean(effectMask & (1 << (int)effect.EffectIndex)) && effect.Mechanic != 0) + mask |= (uint)(1 << (int)effect.Mechanic); + } + } + return mask; + } + + public Mechanics GetEffectMechanic(uint effIndex, Difficulty difficulty) + { + SpellEffectInfo effect = GetEffect(difficulty, effIndex); + if (effect != null && effect.IsEffect() && effect.Mechanic != 0) + return effect.Mechanic; + if (Mechanic != 0) + return Mechanic; + return Mechanics.None; + } + + public uint GetDispelMask() + { + return GetDispelMask(Dispel); + } + + public static uint GetDispelMask(DispelType type) + { + // If dispel all + if (type == DispelType.ALL) + return (uint)DispelType.AllMask; + else + return (uint)(1 << (int)type); + } + + public SpellCastTargetFlags GetExplicitTargetMask() + { + return (SpellCastTargetFlags)ExplicitTargetMask; + } + + public AuraStateType GetAuraState(Difficulty difficulty) + { + // Seals + if (GetSpellSpecific() == SpellSpecificType.Seal) + return AuraStateType.Judgement; + + // Conflagrate aura state on Immolate and Shadowflame + if (SpellFamilyName == SpellFamilyNames.Warlock && + // Immolate + (SpellFamilyFlags[0].HasAnyFlag(4u) || + // Shadowflame + SpellFamilyFlags[2].HasAnyFlag(2u))) + return AuraStateType.Conflagrate; + + // Faerie Fire (druid versions) + if (SpellFamilyName == SpellFamilyNames.Druid && SpellFamilyFlags[0].HasAnyFlag(0x400u)) + return AuraStateType.FaerieFire; + + // Sting (hunter's pet ability) + if (GetCategory() == 1133) + return AuraStateType.FaerieFire; + + // Victorious + if (SpellFamilyName == SpellFamilyNames.Warrior && SpellFamilyFlags[1].HasAnyFlag(0x40000u)) + return AuraStateType.WarriorVictoryRush; + + // Swiftmend state on Regrowth & Rejuvenation + if (SpellFamilyName == SpellFamilyNames.Druid && SpellFamilyFlags[0].HasAnyFlag(0x50u)) + return AuraStateType.Swiftmend; + + // Deadly poison aura state + if (SpellFamilyName == SpellFamilyNames.Rogue && SpellFamilyFlags[0].HasAnyFlag(0x10000u)) + return AuraStateType.DeadlyPoison; + + // Enrage aura state + if (Dispel == DispelType.Enrage) + return AuraStateType.Enrage; + + // Bleeding aura state + if (Convert.ToBoolean(GetAllEffectsMechanicMask() & 1 << (int)Mechanics.Bleed)) + return AuraStateType.Bleeding; + + if (Convert.ToBoolean(GetSchoolMask() & SpellSchoolMask.Frost)) + { + foreach (SpellEffectInfo effect in GetEffectsForDifficulty(difficulty)) + if (effect != null && effect.IsAura() && (effect.ApplyAuraName == AuraType.ModStun + || effect.ApplyAuraName == AuraType.ModRoot)) + return AuraStateType.Frozen; + } + + switch (Id) + { + case 71465: // Divine Surge + case 50241: // Evasive Charges + return AuraStateType.Unk22; + default: + break; + } + + return AuraStateType.None; + } + + public SpellSpecificType GetSpellSpecific() + { + switch (SpellFamilyName) + { + case SpellFamilyNames.Generic: + { + // Food / Drinks (mostly) + if (AuraInterruptFlags.HasAnyFlag(SpellAuraInterruptFlags.NotSeated)) + { + bool food = false; + bool drink = false; + foreach (var pair in _effects) + { + foreach (SpellEffectInfo effect in pair.Value) + { + if (effect == null || !effect.IsAura()) + continue; + switch (effect.ApplyAuraName) + { + // Food + case AuraType.ModRegen: + case AuraType.ObsModHealth: + food = true; + break; + // Drink + case AuraType.ModPowerRegen: + case AuraType.ObsModPower: + drink = true; + break; + default: + break; + } + } + } + + if (food && drink) + return SpellSpecificType.FoodAndDrink; + else if (food) + return SpellSpecificType.Food; + else if (drink) + return SpellSpecificType.Drink; + } + // scrolls effects + else + { + SpellInfo firstRankSpellInfo = GetFirstRankSpell(); + switch (firstRankSpellInfo.Id) + { + case 8118: // Strength + case 8099: // Stamina + case 8112: // Spirit + case 8096: // Intellect + case 8115: // Agility + case 8091: // Armor + return SpellSpecificType.Scroll; + case 12880: // Enrage (Enrage) + case 57518: // Enrage (Wrecking Crew) + return SpellSpecificType.WarriorEnrage; + } + } + break; + } + case SpellFamilyNames.Mage: + { + // family flags 18(Molten), 25(Frost/Ice), 28(Mage) + if (SpellFamilyFlags[0].HasAnyFlag(0x12040000u)) + return SpellSpecificType.MageArmor; + + // Arcane brillance and Arcane intelect (normal check fails because of flags difference) + if (SpellFamilyFlags[0].HasAnyFlag(0x400u)) + return SpellSpecificType.MageArcaneBrillance; + SpellEffectInfo effect = GetEffect(Difficulty.None, 0); + if (effect != null && SpellFamilyFlags[0].HasAnyFlag(0x1000000u) && effect.ApplyAuraName == AuraType.ModConfuse) + return SpellSpecificType.MagePolymorph; + + break; + } + case SpellFamilyNames.Warrior: + { + if (Id == 12292) // Death Wish + return SpellSpecificType.WarriorEnrage; + + break; + } + case SpellFamilyNames.Warlock: + { + // Warlock (Bane of Doom | Bane of Agony | Bane of Havoc) + if (Id == 603 || Id == 980 || Id == 80240) + return SpellSpecificType.Bane; + + // only warlock curses have this + if (Dispel == DispelType.Curse) + return SpellSpecificType.Curse; + + // Warlock (Demon Armor | Demon Skin | Fel Armor) + if (SpellFamilyFlags[1].HasAnyFlag(0x20000020u) || SpellFamilyFlags[2].HasAnyFlag(0x00000010u)) + return SpellSpecificType.WarlockArmor; + + //seed of corruption and corruption + if (SpellFamilyFlags[1].HasAnyFlag(0x10u) || SpellFamilyFlags[0].HasAnyFlag(0x2u)) + return SpellSpecificType.WarlockCorruption; + break; + } + case SpellFamilyNames.Priest: + { + // Divine Spirit and Prayer of Spirit + if (SpellFamilyFlags[0].HasAnyFlag(0x20u)) + return SpellSpecificType.PriestDivineSpirit; + + break; + } + case SpellFamilyNames.Hunter: + { + // only hunter stings have this + if (Dispel == DispelType.Poison) + return SpellSpecificType.Sting; + + // only hunter aspects have this (but not all aspects in hunter family) + if (SpellFamilyFlags & new FlagArray128(0x00380000, 0x00440000, 0x00001010)) + return SpellSpecificType.Aspect; + + break; + } + case SpellFamilyNames.Paladin: + { + // Collection of all the seal family flags. No other paladin spell has any of those. + if (SpellFamilyFlags[1].HasAnyFlag(0x26000C00u)) + return SpellSpecificType.Seal; + + if (SpellFamilyFlags[0].HasAnyFlag(0x00002190u)) + return SpellSpecificType.Hand; + + // Judgement + if (Id == 20271) + return SpellSpecificType.Judgement; + + // only paladin auras have this (for palaldin class family) + if (SpellFamilyFlags[2].HasAnyFlag(0x00000020u)) + return SpellSpecificType.Aura; + + break; + } + case SpellFamilyNames.Shaman: + { + // family flags 10 (Lightning), 42 (Earth), 37 (Water), proc shield from T2 8 pieces bonus + if (SpellFamilyFlags[1].HasAnyFlag(0x420u) || SpellFamilyFlags[0].HasAnyFlag(0x00000400u) || Id == 23552) + return SpellSpecificType.ElementalShield; + + break; + } + case SpellFamilyNames.Deathknight: + if (Id == 48266 || Id == 48263 || Id == 48265) + return SpellSpecificType.Presence; + break; + } + + foreach (var pair in _effects) + { + foreach (SpellEffectInfo effect in pair.Value) + { + if (effect != null && effect.Effect == SpellEffectName.ApplyAura) + { + switch (effect.ApplyAuraName) + { + case AuraType.ModCharm: + case AuraType.ModPossessPet: + case AuraType.ModPossess: + case AuraType.AoeCharm: + return SpellSpecificType.Charm; + case AuraType.TrackCreatures: + // @workaround For non-stacking tracking spells (We need generic solution) + if (Id == 30645) // Gas Cloud Tracking + return SpellSpecificType.Normal; + break; + case AuraType.TrackResources: + case AuraType.TrackStealthed: + return SpellSpecificType.Tracker; + } + } + } + } + + return SpellSpecificType.Normal; + } + + public float GetMinRange(bool positive = false) + { + if (RangeEntry == null) + return 0.0f; + if (positive) + return RangeEntry.MinRangeFriend; + return RangeEntry.MinRangeHostile; + } + + public float GetMaxRange(bool positive = false, Unit caster = null, Spell spell = null) + { + if (RangeEntry == null) + return 0.0f; + float range; + if (positive) + range = RangeEntry.MaxRangeFriend; + else + range = RangeEntry.MaxRangeHostile; + if (caster != null) + { + Player modOwner = caster.GetSpellModOwner(); + if (modOwner != null) + modOwner.ApplySpellMod(Id, SpellModOp.Range, ref range, spell); + } + return range; + } + + public int CalcDuration(Unit caster = null) + { + int duration = GetDuration(); + + if (caster) + { + Player modOwner = caster.GetSpellModOwner(); + if (modOwner) + modOwner.ApplySpellMod(Id, SpellModOp.Duration, ref duration); + } + + return duration; + } + + public int GetDuration() + { + if (DurationEntry == null) + return 0; + return (DurationEntry.Duration == -1) ? -1 : Math.Abs(DurationEntry.Duration); + } + + public int GetMaxDuration() + { + if (DurationEntry == null) + return 0; + return (DurationEntry.MaxDuration == -1) ? -1 : Math.Abs(DurationEntry.MaxDuration); + } + + public int CalcCastTime(uint level = 0, Spell spell = null) + { + int castTime = 0; + if (CastTimeEntry != null) + { + int calcLevel = spell != null ? (int)spell.GetCaster().getLevel() : 0; + if (MaxLevel != 0 && calcLevel > MaxLevel) + calcLevel = (int)MaxLevel; + + if (HasAttribute(SpellAttr13.Unk17)) + calcLevel *= 5; + + if (MaxLevel != 0 && calcLevel > MaxLevel) + calcLevel = (int)MaxLevel; + + if (BaseLevel != 0) + calcLevel -= (int)BaseLevel; + + if (calcLevel < 0) + calcLevel = 0; + + castTime = (int)(CastTimeEntry.CastTime + CastTimeEntry.CastTimePerLevel * level); + if (castTime < CastTimeEntry.MinCastTime) + castTime = CastTimeEntry.MinCastTime; + } + + if (castTime <= 0) + return 0; + + if (spell != null) + spell.GetCaster().ModSpellCastTime(this, ref castTime, spell); + + if (HasAttribute(SpellAttr0.ReqAmmo) && (!IsAutoRepeatRangedSpell()) && !HasAttribute(SpellAttr9.AimedShot)) + castTime += 500; + + return (castTime > 0) ? castTime : 0; + } + + public uint GetMaxTicks(Difficulty difficulty) + { + int DotDuration = GetDuration(); + if (DotDuration == 0) + return 1; + + // 200% limit + if (DotDuration > 30000) + DotDuration = 30000; + + foreach (SpellEffectInfo effect in GetEffectsForDifficulty(difficulty)) + { + if (effect != null && effect.Effect == SpellEffectName.ApplyAura) + { + switch (effect.ApplyAuraName) + { + case AuraType.PeriodicDamage: + case AuraType.PeriodicDamagePercent: + case AuraType.PeriodicHeal: + case AuraType.ObsModHealth: + case AuraType.ObsModPower: + case AuraType.Unk48: + case AuraType.PowerBurn: + case AuraType.PeriodicLeech: + case AuraType.PeriodicManaLeech: + case AuraType.PeriodicEnergize: + case AuraType.PeriodicDummy: + case AuraType.PeriodicTriggerSpell: + case AuraType.PeriodicTriggerSpellWithValue: + case AuraType.PeriodicHealthFunnel: + if (effect.ApplyAuraPeriod != 0) + return (uint)(DotDuration / effect.ApplyAuraPeriod); + break; + } + } + } + + return 6; + } + + public uint GetRecoveryTime() + { + return RecoveryTime > CategoryRecoveryTime ? RecoveryTime : CategoryRecoveryTime; + } + + public List CalcPowerCost(Unit caster, SpellSchoolMask schoolMask) + { + List costs = new List(); + + var collector = new Action>(powers => + { + int healthCost = 0; + + foreach (SpellPowerRecord power in powers) + { + if (power.RequiredAura != 0 && !caster.HasAura(power.RequiredAura)) + continue; + + // Spell drain all exist power on cast (Only paladin lay of Hands) + if (HasAttribute(SpellAttr1.DrainAllPower)) + { + // If power type - health drain all + if (power.PowerType == PowerType.Health) + { + healthCost = (int)caster.GetHealth(); + continue; + } + // Else drain all power + if (power.PowerType < PowerType.Max) + { + SpellPowerCost cost = new SpellPowerCost(); + cost.Power = power.PowerType; + cost.Amount = caster.GetPower(cost.Power); + costs.Add(cost); + continue; + } + + Log.outError(LogFilter.Spells, "SpellInfo.GetCostDataList: Unknown power type '{0}' in spell {1}", power.PowerType, Id); + continue; + } + + // Base powerCost + int powerCost = (int)power.ManaCost; + // PCT cost from total amount + if (power.ManaCostPercentage != 0) + { + switch (power.PowerType) + { + // health as power used + case PowerType.Health: + powerCost += (int)MathFunctions.CalculatePct(caster.GetMaxHealth(), power.ManaCostPercentage); + break; + case PowerType.Mana: + powerCost += (int)MathFunctions.CalculatePct(caster.GetCreateMana(), power.ManaCostPercentage); + break; + case PowerType.Rage: + case PowerType.Focus: + case PowerType.Energy: + powerCost += MathFunctions.CalculatePct(caster.GetMaxPower(power.PowerType), power.ManaCostPercentage); + break; + case PowerType.Runes: + case PowerType.RunicPower: + Log.outDebug(LogFilter.Spells, "GetCostDataList: Not implemented yet!"); + break; + default: + Log.outError(LogFilter.Spells, "GetCostDataList: Unknown power type '{0}' in spell {1}", power.PowerType, Id); + continue; + } + } + + if (power.HealthCostPercentage != 0) + healthCost += (int)MathFunctions.CalculatePct(caster.GetMaxHealth(), power.HealthCostPercentage); + + if (power.PowerType != PowerType.Health) + { + // Flat mod from caster auras by spell school and power type + var auras = caster.GetAuraEffectsByType(AuraType.ModPowerCostSchool); + foreach (var eff in auras) + { + if (!Convert.ToBoolean(eff.GetMiscValue() & (int)schoolMask)) + continue; + + if (!Convert.ToBoolean(eff.GetMiscValueB() & (1 << (int)power.PowerType))) + continue; + + powerCost += eff.GetAmount(); + } + } + + // Shiv - costs 20 + weaponSpeed*10 energy (apply only to non-triggered spell with energy cost) + if (HasAttribute(SpellAttr4.SpellVsExtendCost)) + { + uint speed = 0; + SpellShapeshiftFormRecord ss = CliDB.SpellShapeshiftFormStorage.LookupByKey(caster.GetShapeshiftForm()); + if (ss != null) + speed = ss.CombatRoundTime; + else + { + WeaponAttackType slot = WeaponAttackType.BaseAttack; + if (HasAttribute(SpellAttr3.ReqOffhand)) + slot = WeaponAttackType.OffAttack; + + speed = caster.GetBaseAttackTime(slot); + } + + powerCost += (int)(speed / 100); + } + + // Apply cost mod by spell + Player modOwner = caster.GetSpellModOwner(); + if (modOwner) + { + if (power.PowerIndex == 0) + modOwner.ApplySpellMod(Id, SpellModOp.Cost, ref powerCost); + else if (power.PowerIndex == 1) + modOwner.ApplySpellMod(Id, SpellModOp.SpellCost2, ref powerCost); + } + + if (!caster.IsControlledByPlayer() && MathFunctions.fuzzyEq(power.ManaCostPercentage, 0.0f) && SpellLevel != 0) + { + if (HasAttribute(SpellAttr0.LevelDamageCalculation)) + { + GtNpcManaCostScalerRecord spellScaler = CliDB.NpcManaCostScalerGameTable.GetRow(SpellLevel); + GtNpcManaCostScalerRecord casterScaler = CliDB.NpcManaCostScalerGameTable.GetRow(caster.getLevel()); + if (spellScaler != null && casterScaler != null) + powerCost *= (int)(casterScaler.Scaler / spellScaler.Scaler); + } + } + + // PCT mod from user auras by spell school and power type + var aurasPct = caster.GetAuraEffectsByType(AuraType.ModPowerCostSchoolPct); + foreach (var eff in aurasPct) + { + if (!Convert.ToBoolean(eff.GetMiscValue() & (int)schoolMask)) + continue; + + if (!Convert.ToBoolean(eff.GetMiscValueB() & (1 << (int)power.PowerType))) + continue; + + powerCost += MathFunctions.CalculatePct(powerCost, eff.GetAmount()); + } + + if (power.PowerType == PowerType.Health) + { + healthCost += powerCost; + continue; + } + + bool found = false; + for (var i = 0; i < costs.Count; ++i) + { + var cost = costs[i]; + if (cost.Power == power.PowerType) + { + cost.Amount += powerCost; + found = true; + } + } + + if (!found) + { + SpellPowerCost cost = new SpellPowerCost(); + cost.Power = power.PowerType; + cost.Amount = powerCost; + costs.Add(cost); + } + } + + if (healthCost > 0) + { + SpellPowerCost cost = new SpellPowerCost(); + cost.Power = PowerType.Health; + cost.Amount = healthCost; + costs.Add(cost); + } + }); + + if (!_hasPowerDifficultyData) // optimization - use static data for 99.5% cases (4753 of 4772 in build 6.1.0.19702) + collector(PowerCosts); + else + collector(Global.DB2Mgr.GetSpellPowers(Id, caster.GetMap().GetDifficultyID())); + + // POWER_RUNES is handled by SpellRuneCost.db2, and cost.Amount is always 0 (see Spell.TakeRunePower) + costs.RemoveAll(cost => cost.Power != PowerType.Runes && cost.Amount <= 0); + return costs; + } + + float CalcPPMHasteMod(SpellProcsPerMinuteModRecord mod, Unit caster) + { + float haste = caster.GetFloatValue(UnitFields.ModHaste); + float rangedHaste = caster.GetFloatValue(UnitFields.ModRangedHaste); + float spellHaste = caster.GetFloatValue(UnitFields.ModCastHaste); + float regenHaste = caster.GetFloatValue(UnitFields.ModHasteRegen); + + switch (mod.Param) + { + case 1: + return (1.0f / haste - 1.0f) * mod.Coeff; + case 2: + return (1.0f / rangedHaste - 1.0f) * mod.Coeff; + case 3: + return (1.0f / spellHaste - 1.0f) * mod.Coeff; + case 4: + return (1.0f / regenHaste - 1.0f) * mod.Coeff; + case 5: + return (1.0f / Math.Min(Math.Min(Math.Min(haste, rangedHaste), spellHaste), regenHaste) - 1.0f) * mod.Coeff; + default: + break; + } + + return 0.0f; + } + + float CalcPPMCritMod(SpellProcsPerMinuteModRecord mod, Unit caster) + { + if (!caster.IsTypeId(TypeId.Player)) + return 0.0f; + + float crit = caster.GetFloatValue(PlayerFields.CritPercentage); + float rangedCrit = caster.GetFloatValue(PlayerFields.RangedCritPercentage); + float spellCrit = caster.GetFloatValue(PlayerFields.SpellCritPercentage1); + + switch (mod.Param) + { + case 1: + return crit * mod.Coeff * 0.01f; + case 2: + return rangedCrit * mod.Coeff * 0.01f; + case 3: + return spellCrit * mod.Coeff * 0.01f; + case 4: + return Math.Min(Math.Min(crit, rangedCrit), spellCrit) * mod.Coeff * 0.01f; + default: + break; + } + + return 0.0f; + } + + float CalcPPMItemLevelMod(SpellProcsPerMinuteModRecord mod, int itemLevel) + { + if (itemLevel == mod.Param) + return 0.0f; + + float itemLevelPoints = ItemEnchantment.GetRandomPropertyPoints((uint)itemLevel, ItemQuality.Rare, InventoryType.Chest, 0); + float basePoints = ItemEnchantment.GetRandomPropertyPoints(mod.Param, ItemQuality.Rare, InventoryType.Chest, 0); + if (itemLevelPoints == basePoints) + return 0.0f; + + return ((itemLevelPoints / basePoints) - 1.0f) * mod.Coeff; + } + + public float CalcProcPPM(Unit caster, int itemLevel) + { + float ppm = ProcBasePPM; + if (!caster) + return ppm; + + foreach (SpellProcsPerMinuteModRecord mod in ProcPPMMods) + { + switch (mod.Type) + { + case SpellProcsPerMinuteModType.Haste: + { + ppm *= 1.0f + CalcPPMHasteMod(mod, caster); + break; + } + case SpellProcsPerMinuteModType.Crit: + { + ppm *= 1.0f + CalcPPMCritMod(mod, caster); + break; + } + case SpellProcsPerMinuteModType.Class: + { + if (caster.getClassMask().HasAnyFlag(mod.Param)) + ppm *= 1.0f + mod.Coeff; + break; + } + case SpellProcsPerMinuteModType.Spec: + { + Player plrCaster = caster.ToPlayer(); + if (plrCaster) + if (plrCaster.GetUInt32Value(PlayerFields.CurrentSpecId) == mod.Param) + ppm *= 1.0f + mod.Coeff; + break; + } + case SpellProcsPerMinuteModType.Race: + { + if (caster.getRaceMask().HasAnyFlag(mod.Param)) + ppm *= 1.0f + mod.Coeff; + break; + } + case SpellProcsPerMinuteModType.ItemLevel: + { + ppm *= 1.0f + CalcPPMItemLevelMod(mod, itemLevel); + break; + } + case SpellProcsPerMinuteModType.Battleground: + { + if (caster.GetMap().IsBattlegroundOrArena()) + ppm *= 1.0f + mod.Coeff; + break; + } + default: + break; + } + } + + return ppm; + } + + public bool IsRanked() + { + return ChainEntry != null; + } + + public byte GetRank() + { + if (ChainEntry == null) + return 1; + return ChainEntry.rank; + } + + public SpellInfo GetFirstRankSpell() + { + if (ChainEntry == null) + return this; + return ChainEntry.first; + } + SpellInfo GetLastRankSpell() + { + if (ChainEntry == null) + return null; + return ChainEntry.last; + } + public SpellInfo GetNextRankSpell() + { + if (ChainEntry == null) + return null; + return ChainEntry.next; + } + SpellInfo GetPrevRankSpell() + { + if (ChainEntry == null) + return null; + return ChainEntry.prev; + } + + public SpellInfo GetAuraRankForLevel(uint level) + { + // ignore passive spells + if (IsPassive()) + return this; + + bool needRankSelection = false; + foreach (SpellEffectInfo effect in GetEffectsForDifficulty(Difficulty.None)) + { + if (effect != null && IsPositiveEffect(effect.EffectIndex) && + (effect.Effect == SpellEffectName.ApplyAura || + effect.Effect == SpellEffectName.ApplyAreaAuraParty || + effect.Effect == SpellEffectName.ApplyAreaAuraRaid) && + effect.Scaling.Coefficient != 0) + { + needRankSelection = true; + break; + } + } + + // not required + if (!needRankSelection) + return this; + + for (SpellInfo nextSpellInfo = this; nextSpellInfo != null; nextSpellInfo = nextSpellInfo.GetPrevRankSpell()) + { + // if found appropriate level + if ((level + 10) >= nextSpellInfo.SpellLevel) + return nextSpellInfo; + + // one rank less then + } + + // not found + return null; + } + + public bool IsRankOf(SpellInfo spellInfo) + { + return GetFirstRankSpell() == spellInfo.GetFirstRankSpell(); + } + + public bool IsDifferentRankOf(SpellInfo spellInfo) + { + if (Id == spellInfo.Id) + return false; + return IsRankOf(spellInfo); + } + + public bool IsHighRankOf(SpellInfo spellInfo) + { + if (ChainEntry != null && spellInfo.ChainEntry != null) + { + if (ChainEntry.first == spellInfo.ChainEntry.first) + if (ChainEntry.rank > spellInfo.ChainEntry.rank) + return true; + } + return false; + } + + public uint GetSpellXSpellVisualId(Unit caster = null) + { + if (caster) + { + Difficulty difficulty = caster.GetMap().GetDifficultyID(); + DifficultyRecord difficultyEntry = CliDB.DifficultyStorage.LookupByKey(difficulty); + while (difficultyEntry != null) + { + var visualList = _visuals.LookupByKey(difficulty); + if (visualList != null) + { + foreach (SpellXSpellVisualRecord visual in visualList) + { + PlayerConditionRecord playerCondition = CliDB.PlayerConditionStorage.LookupByKey(visual.PlayerConditionID); + if (playerCondition == null || (caster.IsTypeId(TypeId.Player) && ConditionManager.IsPlayerMeetingCondition(caster.ToPlayer(), playerCondition))) + return visual.Id; + } + } + + difficultyEntry = CliDB.DifficultyStorage.LookupByKey(difficultyEntry.FallbackDifficultyID); + } + } + + var defaultList = _visuals.LookupByKey(Difficulty.None); + if (defaultList != null) + { + foreach (var visual in defaultList) + { + PlayerConditionRecord playerCondition = CliDB.PlayerConditionStorage.LookupByKey(visual.PlayerConditionID); + if (playerCondition == null || (caster && caster.IsTypeId(TypeId.Player) && ConditionManager.IsPlayerMeetingCondition(caster.ToPlayer(), playerCondition))) + return visual.Id; + } + } + + return 0; + } + + public uint GetSpellVisual(Unit caster = null) + { + var visual = CliDB.SpellXSpellVisualStorage.LookupByKey(GetSpellXSpellVisualId(caster)); + if (visual != null) + { + //if (visual->LowViolenceSpellVisualID && forPlayer->GetViolenceLevel() operator 2) + // return visual->LowViolenceSpellVisualID; + + return visual.SpellVisualID; + } + + return 0; + } + + public void _InitializeExplicitTargetMask() + { + bool srcSet = false; + bool dstSet = false; + SpellCastTargetFlags targetMask = targets; + // prepare target mask using effect target entries + foreach (var pair in _effects) + { + foreach (SpellEffectInfo effect in pair.Value) + { + if (effect == null || !effect.IsEffect()) + continue; + + targetMask |= effect.TargetA.GetExplicitTargetMask(srcSet, dstSet); + targetMask |= effect.TargetB.GetExplicitTargetMask(srcSet, dstSet); + + // add explicit target flags based on spell effects which have SpellEffectImplicitTargetTypes.Explicit and no valid target provided + if (effect.GetImplicitTargetType() != SpellEffectImplicitTargetTypes.Explicit) + continue; + + // extend explicit target mask only if valid targets for effect could not be provided by target types + SpellCastTargetFlags effectTargetMask = effect.GetMissingTargetMask(srcSet, dstSet, (uint)targetMask); + + // don't add explicit object/dest flags when spell has no max range + if (GetMaxRange(true) == 0.0f && GetMaxRange(false) == 0.0f) + effectTargetMask &= ~(SpellCastTargetFlags.UnitMask | SpellCastTargetFlags.Gameobject | SpellCastTargetFlags.CorpseMask | SpellCastTargetFlags.DestLocation); + targetMask |= effectTargetMask; + } + } + + ExplicitTargetMask = (uint)targetMask; + } + + public bool _IsPositiveEffect(uint effIndex, bool deep) + { + // not found a single positive spell with this attribute + if (HasAttribute(SpellAttr0.Negative1)) + return false; + + switch (SpellFamilyName) + { + case SpellFamilyNames.Generic: + switch (Id) + { + case 29214: // Wrath of the Plaguebringer + case 34700: // Allergic Reaction + case 54836: // Wrath of the Plaguebringer + return false; + case 30877: // Tag Murloc + case 61716: // Rabbit Costume + case 61734: // Noblegarden Bunny + case 62344: // Fists of Stone + case 61819: // Manabonked! (item) + case 61834: // Manabonked! (minigob) + case 73523: // Rigor Mortis + return true; + } + break; + case SpellFamilyNames.Priest: + switch (Id) + { + case 64844: // Divine Hymn + case 64904: // Hymn of Hope + case 47585: // Dispersion + return true; + } + break; + case SpellFamilyNames.Rogue: + switch (Id) + { + // Envenom must be considered as a positive effect even though it deals damage + case 32645: // Envenom + return true; + } + break; + } + + if (Mechanic == Mechanics.Immune_Shield) + return true; + + // Special case: effects which determine positivity of whole spell + foreach (var pair in _effects) + { + foreach (SpellEffectInfo effect in pair.Value) + { + if (effect != null && effect.IsAura() && effect.ApplyAuraName == AuraType.ModStealth) + return true; + } + } + + foreach (var pair in _effects) + { + foreach (SpellEffectInfo effect in pair.Value) + { + if (effect == null || effect.EffectIndex != effIndex) + continue; + + switch (effect.Effect) + { + case SpellEffectName.Dummy: + // some explicitly required dummy effect sets + switch (Id) + { + case 28441: + return false; // AB Effect 000 + } + break; + // always positive effects (check before target checks that provided non-positive result in some case for positive effects) + case SpellEffectName.Heal: + case SpellEffectName.LearnSpell: + case SpellEffectName.SkillStep: + case SpellEffectName.HealPct: + case SpellEffectName.EnergizePct: + return true; + case SpellEffectName.ApplyAreaAuraEnemy: + return false; + + // non-positive aura use + case SpellEffectName.ApplyAura: + case SpellEffectName.ApplyAreaAuraFriend: + { + switch (effect.ApplyAuraName) + { + case AuraType.ModDamageDone: // dependent from bas point sign (negative . negative) + case AuraType.ModStat: + case AuraType.ModSkill: + case AuraType.ModSkill2: + case AuraType.ModDodgePercent: + case AuraType.ModHealingPct: + case AuraType.ModHealingDone: + case AuraType.ModHealingDonePercent: + if (effect.CalcValue() < 0) + return false; + break; + case AuraType.ModDamageTaken: // dependent from bas point sign (positive . negative) + if (effect.CalcValue() > 0) + return false; + break; + case AuraType.ModCritPct: + case AuraType.ModSpellCritChance: + if (effect.CalcValue() > 0) + return true; // some expected positive spells have SPELL_ATTR1_NEGATIVE + break; + case AuraType.AddTargetTrigger: + return true; + case AuraType.PeriodicTriggerSpellWithValue: + case AuraType.PeriodicTriggerSpell: + if (!deep) + { + var spellTriggeredProto = Global.SpellMgr.GetSpellInfo(effect.TriggerSpell); + if (spellTriggeredProto != null) + { + // negative targets of main spell return early + foreach (var pair2 in spellTriggeredProto._effects) + { + foreach (SpellEffectInfo eff in pair2.Value) + { + if (eff == null || eff.Effect == 0) + continue; + // if non-positive trigger cast targeted to positive target this main cast is non-positive + // this will place this spell auras as debuffs + if (_IsPositiveTarget(eff.TargetA.getTarget(), eff.TargetB.getTarget()) && !spellTriggeredProto._IsPositiveEffect(eff.EffectIndex, true)) + return false; + } + } + } + } + break; + case AuraType.ProcTriggerSpell: + // many positive auras have negative triggered spells at damage for example and this not make it negative (it can be canceled for example) + break; + case AuraType.ModStun: //have positive and negative spells, we can't sort its correctly at this moment. + bool more = false; + foreach (var pair2 in _effects) + { + foreach (SpellEffectInfo eff in pair2.Value) + { + if (eff != null && eff.EffectIndex != 0) + { + more = true; + break; + } + } + } + + if (effIndex == 0 && !more) + return false; // but all single stun aura spells is negative + break; + case AuraType.ModPacifySilence: + if (Id == 24740) // Wisp Costume + return true; + return false; + case AuraType.ModRoot: + case AuraType.ModRoot2: + case AuraType.ModSilence: + case AuraType.Ghost: + case AuraType.PeriodicLeech: + case AuraType.ModStalked: + case AuraType.PeriodicDamagePercent: + case AuraType.PreventResurrection: + return false; + case AuraType.PeriodicDamage: // used in positive spells also. + // part of negative spell if casted at self (prevent cancel) + if (effect.TargetA.GetTarget() == Targets.UnitCaster) + return false; + break; + case AuraType.ModDecreaseSpeed: // used in positive spells also + // part of positive spell if casted at self + if (effect.TargetA.GetTarget() != Targets.UnitCaster) + return false; + // but not this if this first effect (didn't find better check) + if (HasAttribute(SpellAttr0.Negative1) && effIndex == 0) + return false; + break; + case AuraType.MechanicImmunity: + { + // non-positive immunities + switch ((Mechanics)effect.MiscValue) + { + case Mechanics.Bandage: + case Mechanics.Shield: + case Mechanics.Mount: + case Mechanics.Invulnerability: + return false; + } + break; + } + case AuraType.AddFlatModifier: // mods + case AuraType.AddPctModifier: + { + // non-positive mods + switch ((SpellModOp)effect.MiscValue) + { + case SpellModOp.Cost: // dependent from bas point sign (negative . positive) + if (effect.CalcValue() > 0) + { + if (!deep) + { + bool negative = true; + for (uint i = 0; i < SpellConst.MaxEffects; ++i) + { + if (i != effIndex) + { + if (_IsPositiveEffect(i, true)) + { + negative = false; + break; + } + } + } + if (negative) + return false; + } + } + break; + } + break; + } + } + break; + } + } + + + + // non-positive targets + if (!_IsPositiveTarget(effect.TargetA.getTarget(), effect.TargetB.getTarget())) + return false; + + // negative spell if triggered spell is negative + if (!deep && effect.ApplyAuraName != 0 && effect.TriggerSpell != 0) + { + SpellInfo spellTriggeredProto = Global.SpellMgr.GetSpellInfo(effect.TriggerSpell); + if (spellTriggeredProto != null) + if (!spellTriggeredProto._IsPositiveSpell()) + return false; + } + } + } + // ok, positive + return true; + } + + public bool _IsPositiveSpell() + { + // spells with at least one negative effect are considered negative + // some self-applied spells have negative effects but in self casting case negative check ignored. + for (byte i = 0; i < SpellConst.MaxEffects; ++i) + if (!_IsPositiveEffect(i, true)) + return false; + return true; + } + + bool _IsPositiveTarget(uint targetA, uint targetB) + { + // non-positive targets + switch ((Targets)targetA) + { + case Targets.UnitNearbyEnemy: + case Targets.UnitEnemy: + case Targets.UnitSrcAreaEnemy: + case Targets.UnitDestAreaEnemy: + case Targets.UnitConeEnemy24: + case Targets.UnitConeEnemy104: + case Targets.DestDynobjEnemy: + case Targets.DestEnemy: + return false; + default: + break; + } + if (targetB != 0) + return _IsPositiveTarget(targetB, 0); + return true; + } + + public void _UnloadImplicitTargetConditionLists() + { + // find the same instances of ConditionList and delete them. + foreach (var pair in _effects) + { + for (uint i = 0; i < pair.Value.Length; ++i) + { + SpellEffectInfo effect = pair.Value[i]; + if (effect == null) + continue; + + var cur = effect.ImplicitTargetConditions; + if (cur == null) + continue; + + for (var j = i; j < pair.Value.Length; ++j) + { + SpellEffectInfo eff = pair.Value[j]; + if (eff != null && eff.ImplicitTargetConditions == cur) + eff.ImplicitTargetConditions = null; + } + } + } + } + + public static SpellCastTargetFlags GetTargetFlagMask(SpellTargetObjectTypes objType) + { + switch (objType) + { + case SpellTargetObjectTypes.Dest: + return SpellCastTargetFlags.DestLocation; + case SpellTargetObjectTypes.UnitAndDest: + return SpellCastTargetFlags.DestLocation | SpellCastTargetFlags.Unit; + case SpellTargetObjectTypes.CorpseAlly: + return SpellCastTargetFlags.CorpseAlly; + case SpellTargetObjectTypes.CorpseEnemy: + return SpellCastTargetFlags.CorpseEnemy; + case SpellTargetObjectTypes.Corpse: + return SpellCastTargetFlags.CorpseAlly | SpellCastTargetFlags.CorpseEnemy; + case SpellTargetObjectTypes.Unit: + return SpellCastTargetFlags.Unit; + case SpellTargetObjectTypes.Gobj: + return SpellCastTargetFlags.Gameobject; + case SpellTargetObjectTypes.GobjItem: + return SpellCastTargetFlags.GameobjectItem; + case SpellTargetObjectTypes.Item: + return SpellCastTargetFlags.Item; + case SpellTargetObjectTypes.Src: + return SpellCastTargetFlags.SourceLocation; + default: + return SpellCastTargetFlags.None; + } + } + + public uint GetCategory() + { + return CategoryId; + } + + public SpellEffectInfo[] GetEffectsForDifficulty(Difficulty difficulty) + { + SpellEffectInfo[] effList = new SpellEffectInfo[SpellConst.MaxEffects]; + + // downscale difficulty if original was not found + DifficultyRecord difficultyEntry = CliDB.DifficultyStorage.LookupByKey(difficulty); + while (difficultyEntry != null) + { + var effectArray = _effects.LookupByKey(difficulty); + if (effectArray != null) + { + foreach (SpellEffectInfo effect in effectArray) + { + if (effect == null) + continue; + + if (effList[effect.EffectIndex] == null) + effList[effect.EffectIndex] = effect; + } + } + + difficultyEntry = CliDB.DifficultyStorage.LookupByKey(difficultyEntry.FallbackDifficultyID); + } + + // DIFFICULTY_NONE effects are the default effects, always active if current difficulty's effects don't overwrite + var effects = _effects.LookupByKey(Difficulty.None); + if (effects != null) + { + foreach (SpellEffectInfo effect in effects) + { + if (effect == null) + continue; + + if (effList[effect.EffectIndex] == null) + effList[effect.EffectIndex] = effect; + } + } + + return effList; + } + + public SpellEffectInfo GetEffect(uint index) { return GetEffect(Difficulty.None, index); } + public SpellEffectInfo GetEffect(Difficulty difficulty, uint index) + { + DifficultyRecord difficultyEntry = CliDB.DifficultyStorage.LookupByKey(difficulty); + while (difficultyEntry != null) + { + var effectArray = _effects.LookupByKey(difficulty); + if (effectArray != null) + if (effectArray.Length > index && effectArray[index] != null) + return effectArray[index]; + + difficultyEntry = CliDB.DifficultyStorage.LookupByKey(difficultyEntry.FallbackDifficultyID); + } + + var spellEffectInfos = _effects.LookupByKey(Difficulty.None); + if (spellEffectInfos != null) + if (spellEffectInfos.Length > index) + return spellEffectInfos[index]; + + return null; + } + + public bool HasAttribute(SpellAttr0 attribute) { return Convert.ToBoolean(Attributes & attribute); } + public bool HasAttribute(SpellAttr1 attribute) { return Convert.ToBoolean(AttributesEx & attribute); } + public bool HasAttribute(SpellAttr2 attribute) { return Convert.ToBoolean(AttributesEx2 & attribute); } + public bool HasAttribute(SpellAttr3 attribute) { return Convert.ToBoolean(AttributesEx3 & attribute); } + public bool HasAttribute(SpellAttr4 attribute) { return Convert.ToBoolean(AttributesEx4 & attribute); } + public bool HasAttribute(SpellAttr5 attribute) { return Convert.ToBoolean(AttributesEx5 & attribute); } + public bool HasAttribute(SpellAttr6 attribute) { return Convert.ToBoolean(AttributesEx6 & attribute); } + public bool HasAttribute(SpellAttr7 attribute) { return Convert.ToBoolean(AttributesEx7 & attribute); } + public bool HasAttribute(SpellAttr8 attribute) { return Convert.ToBoolean(AttributesEx8 & attribute); } + public bool HasAttribute(SpellAttr9 attribute) { return Convert.ToBoolean(AttributesEx9 & attribute); } + public bool HasAttribute(SpellAttr10 attribute) { return Convert.ToBoolean(AttributesEx10 & attribute); } + public bool HasAttribute(SpellAttr11 attribute) { return Convert.ToBoolean(AttributesEx11 & attribute); } + public bool HasAttribute(SpellAttr12 attribute) { return Convert.ToBoolean(AttributesEx12 & attribute); } + public bool HasAttribute(SpellAttr13 attribute) { return Convert.ToBoolean(AttributesEx13 & attribute); } + public bool HasAttribute(SpellCustomAttributes attribute) { return Convert.ToBoolean(AttributesCu & attribute); } + + #region Fields + public uint Id; + uint CategoryId; + public DispelType Dispel { get; set; } + public Mechanics Mechanic { get; set; } + public SpellAttr0 Attributes { get; set; } + public SpellAttr1 AttributesEx { get; set; } + public SpellAttr2 AttributesEx2 { get; set; } + public SpellAttr3 AttributesEx3 { get; set; } + public SpellAttr4 AttributesEx4 { get; set; } + public SpellAttr5 AttributesEx5 { get; set; } + public SpellAttr6 AttributesEx6 { get; set; } + public SpellAttr7 AttributesEx7 { get; set; } + public SpellAttr8 AttributesEx8 { get; set; } + public SpellAttr9 AttributesEx9 { get; set; } + public SpellAttr10 AttributesEx10 { get; set; } + public SpellAttr11 AttributesEx11 { get; set; } + public SpellAttr12 AttributesEx12 { get; set; } + public SpellAttr13 AttributesEx13 { get; set; } + public SpellCustomAttributes AttributesCu { get; set; } + public ulong Stances { get; set; } + public ulong StancesNot { get; set; } + public SpellCastTargetFlags targets { get; set; } + public uint TargetCreatureType { get; set; } + public uint RequiresSpellFocus { get; set; } + public uint FacingCasterFlags { get; set; } + public AuraStateType CasterAuraState { get; set; } + public AuraStateType TargetAuraState { get; set; } + public AuraStateType CasterAuraStateNot { get; set; } + public AuraStateType TargetAuraStateNot { get; set; } + public uint CasterAuraSpell { get; set; } + public uint TargetAuraSpell { get; set; } + public uint ExcludeCasterAuraSpell { get; set; } + public uint ExcludeTargetAuraSpell { get; set; } + public SpellCastTimesRecord CastTimeEntry { get; set; } + public uint RecoveryTime { get; set; } + public uint CategoryRecoveryTime { get; set; } + public uint StartRecoveryCategory { get; set; } + public uint StartRecoveryTime { get; set; } + public SpellInterruptFlags InterruptFlags { get; set; } + public SpellAuraInterruptFlags AuraInterruptFlags { get; set; } + public SpellChannelInterruptFlags ChannelInterruptFlags { get; set; } + public ProcFlags ProcFlags { get; set; } + public uint ProcChance { get; set; } + public uint ProcCharges { get; set; } + public uint ProcCooldown { get; set; } + public float ProcBasePPM { get; set; } + List ProcPPMMods = new List(); + public uint MaxLevel { get; set; } + public uint BaseLevel { get; set; } + public uint SpellLevel { get; set; } + public SpellDurationRecord DurationEntry { get; set; } + public List PowerCosts = new List(); + public uint RangeIndex { get; set; } + public SpellRangeRecord RangeEntry { get; set; } + public float Speed { get; set; } + public uint StackAmount { get; set; } + public uint[] Totem = new uint[SpellConst.MaxTotems]; + public int[] Reagent = new int[SpellConst.MaxReagents]; + public uint[] ReagentCount = new uint[SpellConst.MaxReagents]; + public ItemClass EquippedItemClass { get; set; } + public int EquippedItemSubClassMask { get; set; } + public int EquippedItemInventoryTypeMask { get; set; } + public uint[] TotemCategory = new uint[SpellConst.MaxTotems]; + public uint IconFileDataId { get; set; } + public uint ActiveIconFileDataId { get; set; } + public LocalizedString SpellName { get; set; } + public uint MaxTargetLevel { get; set; } + public uint MaxAffectedTargets { get; set; } + public SpellFamilyNames SpellFamilyName { get; set; } + public FlagArray128 SpellFamilyFlags { get; set; } + public SpellDmgClass DmgClass { get; set; } + public SpellPreventionType PreventionType { get; set; } + public int RequiredAreasID { get; set; } + public SpellSchoolMask SchoolMask { get; set; } + public uint ChargeCategoryId; + // SpellScalingEntry + public ScalingInfo Scaling; + public uint ExplicitTargetMask { get; set; } + public SpellChainNode ChainEntry { get; set; } + + internal Dictionary _effects; + MultiMap _visuals = new MultiMap(); + bool _hasPowerDifficultyData; + #endregion + + public struct ScalingInfo + { + public int _Class { get; set; } + public uint MinScalingLevel; + public uint MaxScalingLevel; + public uint ScalesFromItemLevel; + } + } + + public class SpellEffectInfo + { + public SpellEffectInfo(SpellEffectScalingRecord spellEffectScaling, SpellInfo spellInfo, uint effIndex, SpellEffectRecord _effect) + { + _spellInfo = spellInfo; + EffectIndex = effIndex; + + TargetA = new SpellImplicitTargetInfo(); + TargetB = new SpellImplicitTargetInfo(); + SpellClassMask = new FlagArray128(); + + if (_effect != null) + { + EffectIndex = _effect.EffectIndex; + Effect = (SpellEffectName)_effect.Effect; + ApplyAuraName = (AuraType)_effect.EffectAura; + ApplyAuraPeriod = _effect.EffectAuraPeriod; + DieSides = (int)_effect.EffectDieSides; + RealPointsPerLevel = _effect.EffectRealPointsPerLevel; + BasePoints = (int)_effect.EffectBasePoints; + PointsPerResource = _effect.EffectPointsPerResource; + Amplitude = _effect.EffectAmplitude; + ChainAmplitude = _effect.EffectChainAmplitude; + BonusCoefficient = _effect.EffectBonusCoefficient; + MiscValue = _effect.EffectMiscValue; + MiscValueB = _effect.EffectMiscValueB; + Mechanic = (Mechanics)_effect.EffectMechanic; + PositionFacing = _effect.EffectPosFacing; + TargetA = new SpellImplicitTargetInfo((Targets)_effect.ImplicitTarget[0]); + TargetB = new SpellImplicitTargetInfo((Targets)_effect.ImplicitTarget[1]); + RadiusEntry = CliDB.SpellRadiusStorage.LookupByKey(_effect.EffectRadiusIndex); + MaxRadiusEntry = CliDB.SpellRadiusStorage.LookupByKey(_effect.EffectRadiusMaxIndex); + ChainTargets = _effect.EffectChainTargets; + ItemType = _effect.EffectItemType; + TriggerSpell = _effect.EffectTriggerSpell; + SpellClassMask = _effect.EffectSpellClassMask; + BonusCoefficientFromAP = _effect.BonusCoefficientFromAP; + } + + ImplicitTargetConditions = null; + + Scaling.Coefficient = spellEffectScaling != null ? spellEffectScaling.Coefficient : 0.0f; + Scaling.Variance = spellEffectScaling != null ? spellEffectScaling.Variance : 0.0f; + Scaling.ResourceCoefficient = spellEffectScaling != null ? spellEffectScaling.ResourceCoefficient : 0.0f; + } + + public bool IsEffect() + { + return Effect != 0; + } + + public bool IsEffect(SpellEffectName effectName) + { + return Effect == effectName; + } + + public bool IsAura() + { + return (IsUnitOwnedAuraEffect() || Effect == SpellEffectName.PersistentAreaAura) && ApplyAuraName != 0; + } + + public bool IsAura(AuraType aura) + { + return IsAura() && ApplyAuraName == aura; + } + + public bool IsTargetingArea() + { + return TargetA.IsArea() || TargetB.IsArea(); + } + + public bool IsAreaAuraEffect() + { + if (Effect == SpellEffectName.ApplyAreaAuraParty || + Effect == SpellEffectName.ApplyAreaAuraRaid || + Effect == SpellEffectName.ApplyAreaAuraFriend || + Effect == SpellEffectName.ApplyAreaAuraEnemy || + Effect == SpellEffectName.ApplyAreaAuraPet || + Effect == SpellEffectName.ApplyAreaAuraOwner) + return true; + return false; + } + + public bool IsFarUnitTargetEffect() + { + return (Effect == SpellEffectName.SummonPlayer) + || (Effect == SpellEffectName.SummonRafFriend) + || (Effect == SpellEffectName.Resurrect) + || (Effect == SpellEffectName.SkinPlayerCorpse); + } + + bool IsFarDestTargetEffect() + { + return Effect == SpellEffectName.TeleportUnitsOld; + } + + public bool IsUnitOwnedAuraEffect() + { + return IsAreaAuraEffect() || Effect == SpellEffectName.ApplyAura; + } + + public int CalcValue(Unit caster = null, int? bp = null, Unit target = null, int itemLevel = -1) + { + float throwAway; + return CalcValue(out throwAway, caster, bp, target, itemLevel); + } + + public int CalcValue(out float variance, Unit caster = null, int? bp = null, Unit target = null, int itemLevel = -1) + { + variance = 0.0f; + float basePointsPerLevel = RealPointsPerLevel; + int basePoints = bp.HasValue ? bp.Value : BasePoints; + float comboDamage = PointsPerResource; + + float value; + // base amount modification based on spell lvl vs caster lvl + if (Scaling.Coefficient != 0.0f) + { + uint level = _spellInfo.SpellLevel; + if (target && _spellInfo.IsPositiveEffect(EffectIndex) && (Effect == SpellEffectName.ApplyAura)) + level = target.getLevel(); + else if (caster) + level = caster.getLevel(); + + if (!_spellInfo.HasAttribute(SpellAttr11.ScalesWithItemLevel) && _spellInfo.HasAttribute(SpellAttr10.UseSpellBaseLevelForScaling)) + level = _spellInfo.BaseLevel; + + if (_spellInfo.Scaling.MinScalingLevel != 0 && _spellInfo.Scaling.MinScalingLevel > level) + level = _spellInfo.Scaling.MinScalingLevel; + + if (_spellInfo.Scaling.MaxScalingLevel != 0 && _spellInfo.Scaling.MaxScalingLevel < level) + level = _spellInfo.Scaling.MaxScalingLevel; + + value = 0.0f; + if (level > 0) + { + if (_spellInfo.Scaling._Class == 0) + return 0; + + if (_spellInfo.Scaling.ScalesFromItemLevel == 0) + { + if (!_spellInfo.HasAttribute(SpellAttr11.ScalesWithItemLevel)) + value = CliDB.GetSpellScalingColumnForClass(CliDB.SpellScalingGameTable.GetRow(level), _spellInfo.Scaling._Class); + else + { + uint effectiveItemLevel = (uint)(itemLevel != -1 ? itemLevel : 1); + value = ItemEnchantment.GetRandomPropertyPoints(effectiveItemLevel, ItemQuality.Rare, InventoryType.Chest, 0); + if (IsAura() && ApplyAuraName == AuraType.ModRating) + { + GtCombatRatingsMultByILvlRecord ratingMult = CliDB.CombatRatingsMultByILvlGameTable.GetRow(effectiveItemLevel); + if (ratingMult != null) + value *= ratingMult.ArmorMultiplier; + } + } + } + else + value = ItemEnchantment.GetRandomPropertyPoints(_spellInfo.Scaling.ScalesFromItemLevel, ItemQuality.Rare, InventoryType.Chest, 0); + } + + value *= Scaling.Coefficient; + if (value != 0.0f && value < 1.0f) + value = 1.0f; + + if (Scaling.Variance != 0f) + { + float delta = Math.Abs(Scaling.Variance * 0.5f); + float valueVariance = RandomHelper.FRand(-delta, delta); + value += value * valueVariance; + + variance = valueVariance; + } + + basePoints = (int)Math.Round(value); + + if (Scaling.ResourceCoefficient != 0f) + comboDamage = Scaling.ResourceCoefficient * value; + } + else + { + if (caster != null) + { + int level = (int)caster.getLevel(); + if (level > _spellInfo.MaxLevel && _spellInfo.MaxLevel > 0) + level = (int)_spellInfo.MaxLevel; + else if (level < _spellInfo.BaseLevel) + level = (int)_spellInfo.BaseLevel; + if (!_spellInfo.IsPassive()) + level -= (int)_spellInfo.SpellLevel; + + basePoints += (int)(level * basePointsPerLevel); + } + + // roll in a range <1;EffectDieSides> as of patch 3.3.3 + int randomPoints = DieSides; + switch (randomPoints) + { + case 0: + break; + case 1: + basePoints += 1; + break; // range 1..1 + default: + { + // range can have positive (1..rand) and negative (rand..1) values, so order its for irand + int randvalue = (randomPoints >= 1) ? RandomHelper.IRand(1, randomPoints) : RandomHelper.IRand(randomPoints, 1); + + basePoints += randvalue; + break; + } + } + } + + value = basePoints; + + // random damage + if (caster != null) + { + // bonus amount from combo points + if (caster.m_playerMovingMe != null && comboDamage != 0) + { + byte comboPoints = caster.m_playerMovingMe.GetComboPoints(); + if (comboPoints != 0) + value += comboDamage * comboPoints; + } + + value = caster.ApplyEffectModifiers(_spellInfo, EffectIndex, value); + + if (!caster.IsControlledByPlayer() && _spellInfo.SpellLevel != 0 && _spellInfo.SpellLevel != caster.getLevel() && + basePointsPerLevel == 0 && _spellInfo.HasAttribute(SpellAttr0.LevelDamageCalculation)) + { + bool canEffectScale = false; + switch (Effect) + { + case SpellEffectName.SchoolDamage: + case SpellEffectName.Dummy: + case SpellEffectName.PowerDrain: + case SpellEffectName.HealthLeech: + case SpellEffectName.Heal: + case SpellEffectName.WeaponDamage: + case SpellEffectName.PowerBurn: + case SpellEffectName.ScriptEffect: + case SpellEffectName.NormalizedWeaponDmg: + case SpellEffectName.ForceCastWithValue: + case SpellEffectName.TriggerSpellWithValue: + case SpellEffectName.TriggerMissileSpellWithValue: + canEffectScale = true; + break; + default: + break; + } + + switch (ApplyAuraName) + { + case AuraType.PeriodicDamage: + case AuraType.Dummy: + case AuraType.PeriodicHeal: + case AuraType.DamageShield: + case AuraType.ProcTriggerDamage: + case AuraType.PeriodicLeech: + case AuraType.PeriodicManaLeech: + case AuraType.SchoolAbsorb: + case AuraType.PeriodicTriggerSpellWithValue: + canEffectScale = true; + break; + default: + break; + } + + if (canEffectScale) + { + GtNpcManaCostScalerRecord spellScaler = CliDB.NpcManaCostScalerGameTable.GetRow(_spellInfo.SpellLevel); + GtNpcManaCostScalerRecord casterScaler = CliDB.NpcManaCostScalerGameTable.GetRow(caster.getLevel()); + if (spellScaler != null && casterScaler != null) + value *= casterScaler.Scaler / spellScaler.Scaler; + } + } + } + return (int)value; + } + + public int CalcBaseValue(int value) + { + if (DieSides == 0) + return value; + else + return value - 1; + } + + public float CalcValueMultiplier(Unit caster, Spell spell = null) + { + float multiplier = Amplitude; + Player modOwner = (caster != null ? caster.GetSpellModOwner() : null); + if (modOwner != null) + modOwner.ApplySpellMod(_spellInfo.Id, SpellModOp.ValueMultiplier, ref multiplier, spell); + return multiplier; + } + + public float CalcDamageMultiplier(Unit caster, Spell spell = null) + { + float multiplierPercent = ChainAmplitude * 100.0f; + Player modOwner = (caster != null ? caster.GetSpellModOwner() : null); + if (modOwner != null) + modOwner.ApplySpellMod(_spellInfo.Id, SpellModOp.DamageMultiplier, ref multiplierPercent, spell); + return multiplierPercent / 100.0f; + } + + public bool HasRadius() + { + return RadiusEntry != null; + } + + public bool HasMaxRadius() + { + return MaxRadiusEntry != null; + } + + public float CalcRadius(Unit caster = null, Spell spell = null) + { + SpellRadiusRecord entry = RadiusEntry; + if (!HasRadius() && HasMaxRadius()) + entry = MaxRadiusEntry; + + if (entry == null) + return 0.0f; + + float radius = entry.RadiusMin; + + // Client uses max if min is 0 + if (radius == 0.0f) + radius = entry.RadiusMax; + + if (caster != null) + { + radius += entry.RadiusPerLevel * caster.getLevel(); + radius = Math.Min(radius, entry.RadiusMax); + Player modOwner = caster.GetSpellModOwner(); + if (modOwner != null) + modOwner.ApplySpellMod(_spellInfo.Id, SpellModOp.Radius, ref radius, spell); + } + + return radius; + } + + public SpellCastTargetFlags GetProvidedTargetMask() + { + return SpellInfo.GetTargetFlagMask(TargetA.GetObjectType()) | SpellInfo.GetTargetFlagMask(TargetB.GetObjectType()); + } + + public SpellCastTargetFlags GetMissingTargetMask(bool srcSet = false, bool dstSet = false, uint mask = 0) + { + var effImplicitTargetMask = SpellInfo.GetTargetFlagMask(GetUsedTargetObjectType()); + var providedTargetMask = (SpellInfo.GetTargetFlagMask(TargetA.GetObjectType()) | SpellInfo.GetTargetFlagMask(TargetB.GetObjectType()) | (SpellCastTargetFlags)mask); + + // remove all flags covered by effect target mask + if (Convert.ToBoolean(providedTargetMask & SpellCastTargetFlags.UnitMask)) + effImplicitTargetMask &= ~SpellCastTargetFlags.UnitMask; + if (Convert.ToBoolean(providedTargetMask & SpellCastTargetFlags.CorpseMask)) + effImplicitTargetMask &= ~(SpellCastTargetFlags.UnitMask | SpellCastTargetFlags.CorpseMask); + if (Convert.ToBoolean(providedTargetMask & SpellCastTargetFlags.GameobjectItem)) + effImplicitTargetMask &= ~(SpellCastTargetFlags.GameobjectItem | SpellCastTargetFlags.Gameobject | SpellCastTargetFlags.Item); + if (Convert.ToBoolean(providedTargetMask & SpellCastTargetFlags.Gameobject)) + effImplicitTargetMask &= ~(SpellCastTargetFlags.Gameobject | SpellCastTargetFlags.GameobjectItem); + if (Convert.ToBoolean(providedTargetMask & SpellCastTargetFlags.Item)) + effImplicitTargetMask &= ~(SpellCastTargetFlags.Item | SpellCastTargetFlags.GameobjectItem); + if (dstSet || Convert.ToBoolean(providedTargetMask & SpellCastTargetFlags.DestLocation)) + effImplicitTargetMask &= ~SpellCastTargetFlags.DestLocation; + if (srcSet || Convert.ToBoolean(providedTargetMask & SpellCastTargetFlags.SourceLocation)) + effImplicitTargetMask &= ~SpellCastTargetFlags.SourceLocation; + + return effImplicitTargetMask; + } + + public SpellEffectImplicitTargetTypes GetImplicitTargetType() + { + return _data[(int)Effect].ImplicitTargetType; + } + + public SpellTargetObjectTypes GetUsedTargetObjectType() + { + return _data[(int)Effect].UsedTargetObjectType; + } + + public class StaticData + { + public StaticData(SpellEffectImplicitTargetTypes implicittarget, SpellTargetObjectTypes usedtarget) + { + ImplicitTargetType = implicittarget; + UsedTargetObjectType = usedtarget; + } + + public SpellEffectImplicitTargetTypes ImplicitTargetType; // defines what target can be added to effect target list if there's no valid target type provided for effect + public SpellTargetObjectTypes UsedTargetObjectType; // defines valid target object type for spell effect + } + + static StaticData[] _data = new StaticData[(int)SpellEffectName.TotalSpellEffects] + { + // implicit target type used target object type + new StaticData(SpellEffectImplicitTargetTypes.None, SpellTargetObjectTypes.None), // 0 + new StaticData(SpellEffectImplicitTargetTypes.Explicit, SpellTargetObjectTypes.Unit), // 1 SPELL_EFFECT_INSTAKILL + new StaticData(SpellEffectImplicitTargetTypes.Explicit, SpellTargetObjectTypes.Unit), // 2 SPELL_EFFECT_SCHOOL_DAMAGE + new StaticData(SpellEffectImplicitTargetTypes.None, SpellTargetObjectTypes.None), // 3 SPELL_EFFECT_DUMMY + new StaticData(SpellEffectImplicitTargetTypes.None, SpellTargetObjectTypes.None), // 4 SPELL_EFFECT_PORTAL_TELEPORT + new StaticData(SpellEffectImplicitTargetTypes.Explicit, SpellTargetObjectTypes.UnitAndDest), // 5 SPELL_EFFECT_TELEPORT_UNITS + new StaticData(SpellEffectImplicitTargetTypes.Explicit, SpellTargetObjectTypes.Unit), // 6 SPELL_EFFECT_APPLY_AURA + new StaticData(SpellEffectImplicitTargetTypes.Explicit, SpellTargetObjectTypes.Unit), // 7 SPELL_EFFECT_ENVIRONMENTAL_DAMAGE + new StaticData(SpellEffectImplicitTargetTypes.Explicit, SpellTargetObjectTypes.Unit), // 8 SPELL_EFFECT_POWER_DRAIN + new StaticData(SpellEffectImplicitTargetTypes.Explicit, SpellTargetObjectTypes.Unit), // 9 SPELL_EFFECT_HEALTH_LEECH + new StaticData(SpellEffectImplicitTargetTypes.Explicit, SpellTargetObjectTypes.Unit), // 10 SPELL_EFFECT_HEAL + new StaticData(SpellEffectImplicitTargetTypes.Explicit, SpellTargetObjectTypes.Unit), // 11 SPELL_EFFECT_BIND + new StaticData(SpellEffectImplicitTargetTypes.None, SpellTargetObjectTypes.None), // 12 SPELL_EFFECT_PORTAL + new StaticData(SpellEffectImplicitTargetTypes.None, SpellTargetObjectTypes.None), // 13 SPELL_EFFECT_RITUAL_BASE + new StaticData(SpellEffectImplicitTargetTypes.None, SpellTargetObjectTypes.None), // 14 SPELL_EFFECT_RITUAL_SPECIALIZE + new StaticData(SpellEffectImplicitTargetTypes.None, SpellTargetObjectTypes.None), // 15 SPELL_EFFECT_RITUAL_ACTIVATE_PORTAL + new StaticData(SpellEffectImplicitTargetTypes.Explicit, SpellTargetObjectTypes.Unit), // 16 SPELL_EFFECT_QUEST_COMPLETE + new StaticData(SpellEffectImplicitTargetTypes.Explicit, SpellTargetObjectTypes.Unit), // 17 SPELL_EFFECT_WEAPON_DAMAGE_NOSCHOOL + new StaticData(SpellEffectImplicitTargetTypes.Explicit, SpellTargetObjectTypes.CorpseAlly), // 18 SPELL_EFFECT_RESURRECT + new StaticData(SpellEffectImplicitTargetTypes.Explicit, SpellTargetObjectTypes.Unit), // 19 SPELL_EFFECT_ADD_EXTRA_ATTACKS + new StaticData(SpellEffectImplicitTargetTypes.None, SpellTargetObjectTypes.Unit), // 20 SPELL_EFFECT_DODGE + new StaticData(SpellEffectImplicitTargetTypes.None, SpellTargetObjectTypes.Unit), // 21 SPELL_EFFECT_EVADE + new StaticData(SpellEffectImplicitTargetTypes.None, SpellTargetObjectTypes.Unit), // 22 SPELL_EFFECT_PARRY + new StaticData(SpellEffectImplicitTargetTypes.None, SpellTargetObjectTypes.Unit), // 23 SPELL_EFFECT_BLOCK + new StaticData(SpellEffectImplicitTargetTypes.Explicit, SpellTargetObjectTypes.Unit), // 24 SPELL_EFFECT_CREATE_ITEM + new StaticData(SpellEffectImplicitTargetTypes.None, SpellTargetObjectTypes.Unit), // 25 SPELL_EFFECT_WEAPON + new StaticData(SpellEffectImplicitTargetTypes.None, SpellTargetObjectTypes.Unit), // 26 SPELL_EFFECT_DEFENSE + new StaticData(SpellEffectImplicitTargetTypes.Explicit, SpellTargetObjectTypes.Dest), // 27 SPELL_EFFECT_PERSISTENT_AREA_AURA + new StaticData(SpellEffectImplicitTargetTypes.Explicit, SpellTargetObjectTypes.Dest), // 28 SPELL_EFFECT_SUMMON + new StaticData(SpellEffectImplicitTargetTypes.Explicit, SpellTargetObjectTypes.UnitAndDest), // 29 SPELL_EFFECT_LEAP + new StaticData(SpellEffectImplicitTargetTypes.None, SpellTargetObjectTypes.Unit), // 30 SPELL_EFFECT_ENERGIZE + new StaticData(SpellEffectImplicitTargetTypes.Explicit, SpellTargetObjectTypes.Unit), // 31 SPELL_EFFECT_WEAPON_PERCENT_DAMAGE + new StaticData(SpellEffectImplicitTargetTypes.None, SpellTargetObjectTypes.None), // 32 SPELL_EFFECT_TRIGGER_MISSILE + new StaticData(SpellEffectImplicitTargetTypes.Explicit, SpellTargetObjectTypes.GobjItem), // 33 SPELL_EFFECT_OPEN_LOCK + new StaticData(SpellEffectImplicitTargetTypes.None, SpellTargetObjectTypes.Unit), // 34 SPELL_EFFECT_SUMMON_CHANGE_ITEM + new StaticData(SpellEffectImplicitTargetTypes.Explicit, SpellTargetObjectTypes.Unit), // 35 SPELL_EFFECT_APPLY_AREA_AURA_PARTY + new StaticData(SpellEffectImplicitTargetTypes.Explicit, SpellTargetObjectTypes.Unit), // 36 SPELL_EFFECT_LEARN_SPELL + new StaticData(SpellEffectImplicitTargetTypes.None, SpellTargetObjectTypes.Unit), // 37 SPELL_EFFECT_SPELL_DEFENSE + new StaticData(SpellEffectImplicitTargetTypes.Explicit, SpellTargetObjectTypes.Unit), // 38 SPELL_EFFECT_DISPEL + new StaticData(SpellEffectImplicitTargetTypes.None, SpellTargetObjectTypes.Unit), // 39 SPELL_EFFECT_LANGUAGE + new StaticData(SpellEffectImplicitTargetTypes.Explicit, SpellTargetObjectTypes.Unit), // 40 SPELL_EFFECT_DUAL_WIELD + new StaticData(SpellEffectImplicitTargetTypes.Explicit, SpellTargetObjectTypes.Unit), // 41 SPELL_EFFECT_JUMP + new StaticData(SpellEffectImplicitTargetTypes.None, SpellTargetObjectTypes.Dest), // 42 SPELL_EFFECT_JUMP_DEST + new StaticData(SpellEffectImplicitTargetTypes.Explicit, SpellTargetObjectTypes.UnitAndDest), // 43 SPELL_EFFECT_TELEPORT_UNITS_FACE_CASTER + new StaticData(SpellEffectImplicitTargetTypes.Explicit, SpellTargetObjectTypes.Unit), // 44 SPELL_EFFECT_SKILL_STEP + new StaticData(SpellEffectImplicitTargetTypes.Explicit, SpellTargetObjectTypes.Unit), // 45 SPELL_EFFECT_ADD_HONOR + new StaticData(SpellEffectImplicitTargetTypes.None, SpellTargetObjectTypes.Unit), // 46 SPELL_EFFECT_SPAWN + new StaticData(SpellEffectImplicitTargetTypes.None, SpellTargetObjectTypes.Unit), // 47 SPELL_EFFECT_TRADE_SKILL + new StaticData(SpellEffectImplicitTargetTypes.None, SpellTargetObjectTypes.Unit), // 48 SPELL_EFFECT_STEALTH + new StaticData(SpellEffectImplicitTargetTypes.None, SpellTargetObjectTypes.Unit), // 49 SPELL_EFFECT_DETECT + new StaticData(SpellEffectImplicitTargetTypes.Explicit, SpellTargetObjectTypes.Dest), // 50 SPELL_EFFECT_TRANS_DOOR + new StaticData(SpellEffectImplicitTargetTypes.None, SpellTargetObjectTypes.Unit), // 51 SPELL_EFFECT_FORCE_CRITICAL_HIT + new StaticData(SpellEffectImplicitTargetTypes.None, SpellTargetObjectTypes.Unit), // 52 SPELL_EFFECT_GUARANTEE_HIT + new StaticData(SpellEffectImplicitTargetTypes.Explicit, SpellTargetObjectTypes.Item), // 53 SPELL_EFFECT_ENCHANT_ITEM + new StaticData(SpellEffectImplicitTargetTypes.Explicit, SpellTargetObjectTypes.Item), // 54 SPELL_EFFECT_ENCHANT_ITEM_TEMPORARY + new StaticData(SpellEffectImplicitTargetTypes.Explicit, SpellTargetObjectTypes.Unit), // 55 SPELL_EFFECT_TAMECREATURE + new StaticData(SpellEffectImplicitTargetTypes.Explicit, SpellTargetObjectTypes.Dest), // 56 SPELL_EFFECT_SUMMON_PET + new StaticData(SpellEffectImplicitTargetTypes.Explicit, SpellTargetObjectTypes.Unit), // 57 SPELL_EFFECT_LEARN_PET_SPELL + new StaticData(SpellEffectImplicitTargetTypes.Explicit, SpellTargetObjectTypes.Unit), // 58 SPELL_EFFECT_WEAPON_DAMAGE + new StaticData(SpellEffectImplicitTargetTypes.Explicit, SpellTargetObjectTypes.Unit), // 59 SPELL_EFFECT_CREATE_RANDOM_ITEM + new StaticData(SpellEffectImplicitTargetTypes.None, SpellTargetObjectTypes.Unit), // 60 SPELL_EFFECT_PROFICIENCY + new StaticData(SpellEffectImplicitTargetTypes.None, SpellTargetObjectTypes.None), // 61 SPELL_EFFECT_SEND_EVENT + new StaticData(SpellEffectImplicitTargetTypes.Explicit, SpellTargetObjectTypes.Unit), // 62 SPELL_EFFECT_POWER_BURN + new StaticData(SpellEffectImplicitTargetTypes.Explicit, SpellTargetObjectTypes.Unit), // 63 SPELL_EFFECT_THREAT + new StaticData(SpellEffectImplicitTargetTypes.None, SpellTargetObjectTypes.None), // 64 SPELL_EFFECT_TRIGGER_SPELL + new StaticData(SpellEffectImplicitTargetTypes.Explicit, SpellTargetObjectTypes.Unit), // 65 SPELL_EFFECT_APPLY_AREA_AURA_RAID + new StaticData(SpellEffectImplicitTargetTypes.Explicit, SpellTargetObjectTypes.Unit), // 66 SPELL_EFFECT_CREATE_MANA_GEM + new StaticData(SpellEffectImplicitTargetTypes.Explicit, SpellTargetObjectTypes.Unit), // 67 SPELL_EFFECT_HEAL_MAX_HEALTH + new StaticData(SpellEffectImplicitTargetTypes.Explicit, SpellTargetObjectTypes.Unit), // 68 SPELL_EFFECT_INTERRUPT_CAST + new StaticData(SpellEffectImplicitTargetTypes.Explicit, SpellTargetObjectTypes.UnitAndDest), // 69 SPELL_EFFECT_DISTRACT + new StaticData(SpellEffectImplicitTargetTypes.Explicit, SpellTargetObjectTypes.Unit), // 70 SPELL_EFFECT_PULL + new StaticData(SpellEffectImplicitTargetTypes.Explicit, SpellTargetObjectTypes.Unit), // 71 SPELL_EFFECT_PICKPOCKET + new StaticData(SpellEffectImplicitTargetTypes.Explicit, SpellTargetObjectTypes.Dest), // 72 SPELL_EFFECT_ADD_FARSIGHT + new StaticData(SpellEffectImplicitTargetTypes.Explicit, SpellTargetObjectTypes.Unit), // 73 SPELL_EFFECT_UNTRAIN_TALENTS + new StaticData(SpellEffectImplicitTargetTypes.None, SpellTargetObjectTypes.Unit), // 74 SPELL_EFFECT_APPLY_GLYPH + new StaticData(SpellEffectImplicitTargetTypes.Explicit, SpellTargetObjectTypes.Unit), // 75 SPELL_EFFECT_HEAL_MECHANICAL + new StaticData(SpellEffectImplicitTargetTypes.Explicit, SpellTargetObjectTypes.Dest), // 76 SPELL_EFFECT_SUMMON_OBJECT_WILD + new StaticData(SpellEffectImplicitTargetTypes.None, SpellTargetObjectTypes.None), // 77 SPELL_EFFECT_SCRIPT_EFFECT + new StaticData(SpellEffectImplicitTargetTypes.None, SpellTargetObjectTypes.Unit), // 78 SPELL_EFFECT_ATTACK + new StaticData(SpellEffectImplicitTargetTypes.None, SpellTargetObjectTypes.Unit), // 79 SPELL_EFFECT_SANCTUARY + new StaticData(SpellEffectImplicitTargetTypes.Explicit, SpellTargetObjectTypes.Unit), // 80 SPELL_EFFECT_ADD_COMBO_POINTS + new StaticData(SpellEffectImplicitTargetTypes.Explicit, SpellTargetObjectTypes.Dest), // 81 SPELL_EFFECT_CREATE_HOUSE + new StaticData(SpellEffectImplicitTargetTypes.Explicit, SpellTargetObjectTypes.Unit), // 82 SPELL_EFFECT_BIND_SIGHT + new StaticData(SpellEffectImplicitTargetTypes.Explicit, SpellTargetObjectTypes.UnitAndDest), // 83 SPELL_EFFECT_DUEL + new StaticData(SpellEffectImplicitTargetTypes.None, SpellTargetObjectTypes.Unit), // 84 SPELL_EFFECT_STUCK + new StaticData(SpellEffectImplicitTargetTypes.None, SpellTargetObjectTypes.None), // 85 SPELL_EFFECT_SUMMON_PLAYER + new StaticData(SpellEffectImplicitTargetTypes.Explicit, SpellTargetObjectTypes.Gobj), // 86 SPELL_EFFECT_ACTIVATE_OBJECT + new StaticData(SpellEffectImplicitTargetTypes.Explicit, SpellTargetObjectTypes.Gobj), // 87 SPELL_EFFECT_GAMEOBJECT_DAMAGE + new StaticData(SpellEffectImplicitTargetTypes.Explicit, SpellTargetObjectTypes.Gobj), // 88 SPELL_EFFECT_GAMEOBJECT_REPAIR + new StaticData(SpellEffectImplicitTargetTypes.Explicit, SpellTargetObjectTypes.Gobj), // 89 SPELL_EFFECT_GAMEOBJECT_SET_DESTRUCTION_STATE + new StaticData(SpellEffectImplicitTargetTypes.Explicit, SpellTargetObjectTypes.Unit), // 90 SPELL_EFFECT_KILL_CREDIT + new StaticData(SpellEffectImplicitTargetTypes.Explicit, SpellTargetObjectTypes.Unit), // 91 SPELL_EFFECT_THREAT_ALL + new StaticData(SpellEffectImplicitTargetTypes.Explicit, SpellTargetObjectTypes.Unit), // 92 SPELL_EFFECT_ENCHANT_HELD_ITEM + new StaticData(SpellEffectImplicitTargetTypes.None, SpellTargetObjectTypes.Unit), // 93 SPELL_EFFECT_FORCE_DESELECT + new StaticData(SpellEffectImplicitTargetTypes.None, SpellTargetObjectTypes.Unit), // 94 SPELL_EFFECT_SELF_RESURRECT + new StaticData(SpellEffectImplicitTargetTypes.Explicit, SpellTargetObjectTypes.Unit), // 95 SPELL_EFFECT_SKINNING + new StaticData(SpellEffectImplicitTargetTypes.Explicit, SpellTargetObjectTypes.Unit), // 96 SPELL_EFFECT_CHARGE + new StaticData(SpellEffectImplicitTargetTypes.None, SpellTargetObjectTypes.Unit), // 97 SPELL_EFFECT_CAST_BUTTON + new StaticData(SpellEffectImplicitTargetTypes.Explicit, SpellTargetObjectTypes.Unit), // 98 SPELL_EFFECT_KNOCK_BACK + new StaticData(SpellEffectImplicitTargetTypes.Explicit, SpellTargetObjectTypes.Item), // 99 SPELL_EFFECT_DISENCHANT + new StaticData(SpellEffectImplicitTargetTypes.Explicit, SpellTargetObjectTypes.Unit), // 100 SPELL_EFFECT_INEBRIATE + new StaticData(SpellEffectImplicitTargetTypes.Explicit, SpellTargetObjectTypes.Item), // 101 SPELL_EFFECT_FEED_PET + new StaticData(SpellEffectImplicitTargetTypes.Explicit, SpellTargetObjectTypes.Unit), // 102 SPELL_EFFECT_DISMISS_PET + new StaticData(SpellEffectImplicitTargetTypes.Explicit, SpellTargetObjectTypes.Unit), // 103 SPELL_EFFECT_REPUTATION + new StaticData(SpellEffectImplicitTargetTypes.Explicit, SpellTargetObjectTypes.Dest), // 104 SPELL_EFFECT_SUMMON_OBJECT_SLOT1 + new StaticData(SpellEffectImplicitTargetTypes.Explicit, SpellTargetObjectTypes.Dest), // 105 SPELL_EFFECT_SUMMON_OBJECT_SLOT2 + new StaticData(SpellEffectImplicitTargetTypes.Explicit, SpellTargetObjectTypes.Dest), // 106 SPELL_EFFECT_CHANGE_RAID_MARKER + new StaticData(SpellEffectImplicitTargetTypes.Explicit, SpellTargetObjectTypes.Dest), // 107 SPELL_EFFECT_SUMMON_OBJECT_SLOT4 + new StaticData(SpellEffectImplicitTargetTypes.Explicit, SpellTargetObjectTypes.Unit), // 108 SPELL_EFFECT_DISPEL_MECHANIC + new StaticData(SpellEffectImplicitTargetTypes.Explicit, SpellTargetObjectTypes.Dest), // 109 SPELL_EFFECT_SUMMON_DEAD_PET + new StaticData(SpellEffectImplicitTargetTypes.None, SpellTargetObjectTypes.Unit), // 110 SPELL_EFFECT_DESTROY_ALL_TOTEMS + new StaticData(SpellEffectImplicitTargetTypes.Explicit, SpellTargetObjectTypes.Unit), // 111 SPELL_EFFECT_DURABILITY_DAMAGE + new StaticData(SpellEffectImplicitTargetTypes.Explicit, SpellTargetObjectTypes.Unit), // 112 SPELL_EFFECT_112 + new StaticData(SpellEffectImplicitTargetTypes.Explicit, SpellTargetObjectTypes.CorpseAlly), // 113 SPELL_EFFECT_RESURRECT_NEW + new StaticData(SpellEffectImplicitTargetTypes.Explicit, SpellTargetObjectTypes.Unit), // 114 SPELL_EFFECT_ATTACK_ME + new StaticData(SpellEffectImplicitTargetTypes.Explicit, SpellTargetObjectTypes.Unit), // 115 SPELL_EFFECT_DURABILITY_DAMAGE_PCT + new StaticData(SpellEffectImplicitTargetTypes.Explicit, SpellTargetObjectTypes.CorpseEnemy), // 116 SPELL_EFFECT_SKIN_PLAYER_CORPSE + new StaticData(SpellEffectImplicitTargetTypes.Explicit, SpellTargetObjectTypes.Unit), // 117 SPELL_EFFECT_SPIRIT_HEAL + new StaticData(SpellEffectImplicitTargetTypes.None, SpellTargetObjectTypes.Unit), // 118 SPELL_EFFECT_SKILL + new StaticData(SpellEffectImplicitTargetTypes.Explicit, SpellTargetObjectTypes.Unit), // 119 SPELL_EFFECT_APPLY_AREA_AURA_PET + new StaticData(SpellEffectImplicitTargetTypes.Explicit, SpellTargetObjectTypes.Unit), // 120 SPELL_EFFECT_TELEPORT_GRAVEYARD + new StaticData(SpellEffectImplicitTargetTypes.Explicit, SpellTargetObjectTypes.Unit), // 121 SPELL_EFFECT_NORMALIZED_WEAPON_DMG + new StaticData(SpellEffectImplicitTargetTypes.None, SpellTargetObjectTypes.None), // 122 SPELL_EFFECT_122 + new StaticData(SpellEffectImplicitTargetTypes.Explicit, SpellTargetObjectTypes.Unit), // 123 SPELL_EFFECT_SEND_TAXI + new StaticData(SpellEffectImplicitTargetTypes.Explicit, SpellTargetObjectTypes.Unit), // 124 SPELL_EFFECT_PULL_TOWARDS + new StaticData(SpellEffectImplicitTargetTypes.Explicit, SpellTargetObjectTypes.Unit), // 125 SPELL_EFFECT_MODIFY_THREAT_PERCENT + new StaticData(SpellEffectImplicitTargetTypes.Explicit, SpellTargetObjectTypes.Unit), // 126 SPELL_EFFECT_STEAL_BENEFICIAL_BUFF + new StaticData(SpellEffectImplicitTargetTypes.Explicit, SpellTargetObjectTypes.Item), // 127 SPELL_EFFECT_PROSPECTING + new StaticData(SpellEffectImplicitTargetTypes.Explicit, SpellTargetObjectTypes.Unit), // 128 SPELL_EFFECT_APPLY_AREA_AURA_FRIEND + new StaticData(SpellEffectImplicitTargetTypes.Explicit, SpellTargetObjectTypes.Unit), // 129 SPELL_EFFECT_APPLY_AREA_AURA_ENEMY + new StaticData(SpellEffectImplicitTargetTypes.Explicit, SpellTargetObjectTypes.Unit), // 130 SPELL_EFFECT_REDIRECT_THREAT + new StaticData(SpellEffectImplicitTargetTypes.None, SpellTargetObjectTypes.Unit), // 131 SPELL_EFFECT_PLAY_SOUND + new StaticData(SpellEffectImplicitTargetTypes.Explicit, SpellTargetObjectTypes.Unit), // 132 SPELL_EFFECT_PLAY_MUSIC + new StaticData(SpellEffectImplicitTargetTypes.Explicit, SpellTargetObjectTypes.Unit), // 133 SPELL_EFFECT_UNLEARN_SPECIALIZATION + new StaticData(SpellEffectImplicitTargetTypes.None, SpellTargetObjectTypes.Unit), // 134 SPELL_EFFECT_KILL_CREDIT2 + new StaticData(SpellEffectImplicitTargetTypes.Explicit, SpellTargetObjectTypes.Dest), // 135 SPELL_EFFECT_CALL_PET + new StaticData(SpellEffectImplicitTargetTypes.Explicit, SpellTargetObjectTypes.Unit), // 136 SPELL_EFFECT_HEAL_PCT + new StaticData(SpellEffectImplicitTargetTypes.Explicit, SpellTargetObjectTypes.Unit), // 137 SPELL_EFFECT_ENERGIZE_PCT + new StaticData(SpellEffectImplicitTargetTypes.Explicit, SpellTargetObjectTypes.Unit), // 138 SPELL_EFFECT_LEAP_BACK + new StaticData(SpellEffectImplicitTargetTypes.Explicit, SpellTargetObjectTypes.Unit), // 139 SPELL_EFFECT_CLEAR_QUEST + new StaticData(SpellEffectImplicitTargetTypes.Explicit, SpellTargetObjectTypes.Unit), // 140 SPELL_EFFECT_FORCE_CAST + new StaticData(SpellEffectImplicitTargetTypes.Explicit, SpellTargetObjectTypes.Unit), // 141 SPELL_EFFECT_FORCE_CAST_WITH_VALUE + new StaticData(SpellEffectImplicitTargetTypes.Explicit, SpellTargetObjectTypes.Unit), // 142 SPELL_EFFECT_TRIGGER_SPELL_WITH_VALUE + new StaticData(SpellEffectImplicitTargetTypes.Explicit, SpellTargetObjectTypes.Unit), // 143 SPELL_EFFECT_APPLY_AREA_AURA_OWNER + new StaticData(SpellEffectImplicitTargetTypes.Explicit, SpellTargetObjectTypes.UnitAndDest), // 144 SPELL_EFFECT_KNOCK_BACK_DEST + new StaticData(SpellEffectImplicitTargetTypes.Explicit, SpellTargetObjectTypes.UnitAndDest), // 145 SPELL_EFFECT_PULL_TOWARDS_DEST + new StaticData(SpellEffectImplicitTargetTypes.Explicit, SpellTargetObjectTypes.Unit), // 146 SPELL_EFFECT_ACTIVATE_RUNE + new StaticData(SpellEffectImplicitTargetTypes.Explicit, SpellTargetObjectTypes.Unit), // 147 SPELL_EFFECT_QUEST_FAIL + new StaticData(SpellEffectImplicitTargetTypes.None, SpellTargetObjectTypes.None), // 148 SPELL_EFFECT_TRIGGER_MISSILE_SPELL_WITH_VALUE + new StaticData(SpellEffectImplicitTargetTypes.Explicit, SpellTargetObjectTypes.Dest), // 149 SPELL_EFFECT_CHARGE_DEST + new StaticData(SpellEffectImplicitTargetTypes.Explicit, SpellTargetObjectTypes.Unit), // 150 SPELL_EFFECT_QUEST_START + new StaticData(SpellEffectImplicitTargetTypes.None, SpellTargetObjectTypes.None), // 151 SPELL_EFFECT_TRIGGER_SPELL_2 + new StaticData(SpellEffectImplicitTargetTypes.None, SpellTargetObjectTypes.None), // 152 SPELL_EFFECT_SUMMON_RAF_FRIEND + new StaticData(SpellEffectImplicitTargetTypes.Explicit, SpellTargetObjectTypes.Unit), // 153 SPELL_EFFECT_CREATE_TAMED_PET + new StaticData(SpellEffectImplicitTargetTypes.Explicit, SpellTargetObjectTypes.Unit), // 154 SPELL_EFFECT_DISCOVER_TAXI + new StaticData(SpellEffectImplicitTargetTypes.None, SpellTargetObjectTypes.Unit), // 155 SPELL_EFFECT_TITAN_GRIP + new StaticData(SpellEffectImplicitTargetTypes.Explicit, SpellTargetObjectTypes.Item), // 156 SPELL_EFFECT_ENCHANT_ITEM_PRISMATIC + new StaticData(SpellEffectImplicitTargetTypes.Explicit, SpellTargetObjectTypes.Unit), // 157 SPELL_EFFECT_CREATE_ITEM_2 + new StaticData(SpellEffectImplicitTargetTypes.Explicit, SpellTargetObjectTypes.Item), // 158 SPELL_EFFECT_MILLING + new StaticData(SpellEffectImplicitTargetTypes.Explicit, SpellTargetObjectTypes.Unit), // 159 SPELL_EFFECT_ALLOW_RENAME_PET + new StaticData(SpellEffectImplicitTargetTypes.Explicit, SpellTargetObjectTypes.Unit), // 160 SPELL_EFFECT_160 + new StaticData(SpellEffectImplicitTargetTypes.Explicit, SpellTargetObjectTypes.Unit), // 161 SPELL_EFFECT_TALENT_SPEC_COUNT + new StaticData(SpellEffectImplicitTargetTypes.Explicit, SpellTargetObjectTypes.Unit), // 162 SPELL_EFFECT_TALENT_SPEC_SELECT + new StaticData(SpellEffectImplicitTargetTypes.Explicit, SpellTargetObjectTypes.Unit), // 163 SPELL_EFFECT_163 + new StaticData(SpellEffectImplicitTargetTypes.Explicit, SpellTargetObjectTypes.Unit), // 164 SPELL_EFFECT_REMOVE_AURA + new StaticData(SpellEffectImplicitTargetTypes.Explicit, SpellTargetObjectTypes.Unit), // 165 SPELL_EFFECT_165 + new StaticData(SpellEffectImplicitTargetTypes.Caster, SpellTargetObjectTypes.Unit), // 166 SPELL_EFFECT_GIVE_CURRENCY + new StaticData(SpellEffectImplicitTargetTypes.Explicit, SpellTargetObjectTypes.Unit), // 167 SPELL_EFFECT_167 + new StaticData(SpellEffectImplicitTargetTypes.Explicit, SpellTargetObjectTypes.Unit), // 168 SPELL_EFFECT_168 + new StaticData(SpellEffectImplicitTargetTypes.Caster, SpellTargetObjectTypes.Unit), // 169 SPELL_EFFECT_DESTROY_ITEM + new StaticData(SpellEffectImplicitTargetTypes.Explicit, SpellTargetObjectTypes.Unit), // 170 SPELL_EFFECT_170 + new StaticData(SpellEffectImplicitTargetTypes.None, SpellTargetObjectTypes.Dest), // 171 SPELL_EFFECT_171 + new StaticData(SpellEffectImplicitTargetTypes.Explicit, SpellTargetObjectTypes.Unit), // 172 SPELL_EFFECT_172 + new StaticData(SpellEffectImplicitTargetTypes.None, SpellTargetObjectTypes.None), // 173 SPELL_EFFECT_UNLOCK_GUILD_VAULT_TAB + new StaticData(SpellEffectImplicitTargetTypes.Explicit, SpellTargetObjectTypes.Unit), // 174 SPELL_EFFECT_174 + new StaticData(SpellEffectImplicitTargetTypes.None, SpellTargetObjectTypes.None), // 175 SPELL_EFFECT_175 + new StaticData(SpellEffectImplicitTargetTypes.Explicit, SpellTargetObjectTypes.Unit), // 176 SPELL_EFFECT_176 + new StaticData(SpellEffectImplicitTargetTypes.Explicit, SpellTargetObjectTypes.Unit), // 177 SPELL_EFFECT_177 + new StaticData(SpellEffectImplicitTargetTypes.None, SpellTargetObjectTypes.None), // 178 SPELL_EFFECT_178 + new StaticData(SpellEffectImplicitTargetTypes.None, SpellTargetObjectTypes.Dest), // 179 SPELL_EFFECT_CREATE_AREATRIGGER + new StaticData(SpellEffectImplicitTargetTypes.None, SpellTargetObjectTypes.None), // 180 SPELL_EFFECT_UPDATE_AREATRIGGER + new StaticData(SpellEffectImplicitTargetTypes.Caster, SpellTargetObjectTypes.Unit), // 181 SPELL_EFFECT_REMOVE_TALENT + new StaticData(SpellEffectImplicitTargetTypes.None, SpellTargetObjectTypes.None), // 182 SPELL_EFFECT_182 + new StaticData(SpellEffectImplicitTargetTypes.None, SpellTargetObjectTypes.None), // 183 SPELL_EFFECT_183 + new StaticData(SpellEffectImplicitTargetTypes.None, SpellTargetObjectTypes.None), // 184 SPELL_EFFECT_REPUTATION_2 + new StaticData(SpellEffectImplicitTargetTypes.None, SpellTargetObjectTypes.None), // 185 SPELL_EFFECT_185 + new StaticData(SpellEffectImplicitTargetTypes.None, SpellTargetObjectTypes.None), // 186 SPELL_EFFECT_186 + new StaticData(SpellEffectImplicitTargetTypes.None, SpellTargetObjectTypes.None), // 187 SPELL_EFFECT_RANDOMIZE_ARCHAEOLOGY_DIGSITES + new StaticData(SpellEffectImplicitTargetTypes.None, SpellTargetObjectTypes.None), // 188 SPELL_EFFECT_188 + new StaticData(SpellEffectImplicitTargetTypes.None, SpellTargetObjectTypes.None), // 189 SPELL_EFFECT_LOOT + new StaticData(SpellEffectImplicitTargetTypes.None, SpellTargetObjectTypes.None), // 190 SPELL_EFFECT_190 + new StaticData(SpellEffectImplicitTargetTypes.None, SpellTargetObjectTypes.None), // 191 SPELL_EFFECT_TELEPORT_TO_DIGSITE + new StaticData(SpellEffectImplicitTargetTypes.None, SpellTargetObjectTypes.None), // 192 SPELL_EFFECT_192 + new StaticData(SpellEffectImplicitTargetTypes.None, SpellTargetObjectTypes.None), // 193 SPELL_EFFECT_193 + new StaticData(SpellEffectImplicitTargetTypes.None, SpellTargetObjectTypes.None), // 194 SPELL_EFFECT_194 + new StaticData(SpellEffectImplicitTargetTypes.None, SpellTargetObjectTypes.None), // 195 SPELL_EFFECT_195 + new StaticData(SpellEffectImplicitTargetTypes.None, SpellTargetObjectTypes.None), // 196 SPELL_EFFECT_196 + new StaticData(SpellEffectImplicitTargetTypes.None, SpellTargetObjectTypes.None), // 197 SPELL_EFFECT_197 + new StaticData(SpellEffectImplicitTargetTypes.None, SpellTargetObjectTypes.Dest), // 198 SPELL_EFFECT_PLAY_SCENE + new StaticData(SpellEffectImplicitTargetTypes.None, SpellTargetObjectTypes.None), // 199 SPELL_EFFECT_199 + new StaticData(SpellEffectImplicitTargetTypes.None, SpellTargetObjectTypes.None), // 200 SPELL_EFFECT_HEAL_BATTLEPET_PCT + new StaticData(SpellEffectImplicitTargetTypes.None, SpellTargetObjectTypes.None), // 201 SPELL_EFFECT_ENABLE_BATTLE_PETS + new StaticData(SpellEffectImplicitTargetTypes.None, SpellTargetObjectTypes.None), // 202 SPELL_EFFECT_202 + new StaticData(SpellEffectImplicitTargetTypes.None, SpellTargetObjectTypes.None), // 203 SPELL_EFFECT_203 + new StaticData(SpellEffectImplicitTargetTypes.None, SpellTargetObjectTypes.None), // 204 SPELL_EFFECT_CHANGE_BATTLEPET_QUALITY + new StaticData(SpellEffectImplicitTargetTypes.None, SpellTargetObjectTypes.None), // 205 SPELL_EFFECT_LAUNCH_QUEST_CHOICE + new StaticData(SpellEffectImplicitTargetTypes.Explicit, SpellTargetObjectTypes.Item), // 206 SPELL_EFFECT_ALTER_ITEM + new StaticData(SpellEffectImplicitTargetTypes.None, SpellTargetObjectTypes.None), // 207 SPELL_EFFECT_LAUNCH_QUEST_TASK + new StaticData(SpellEffectImplicitTargetTypes.None, SpellTargetObjectTypes.None), // 208 SPELL_EFFECT_208 + new StaticData(SpellEffectImplicitTargetTypes.None, SpellTargetObjectTypes.None), // 209 SPELL_EFFECT_209 + new StaticData(SpellEffectImplicitTargetTypes.None, SpellTargetObjectTypes.None), // 210 SPELL_EFFECT_LEARN_GARRISON_BUILDING + new StaticData(SpellEffectImplicitTargetTypes.None, SpellTargetObjectTypes.None), // 211 SPELL_EFFECT_LEARN_GARRISON_SPECIALIZATION + new StaticData(SpellEffectImplicitTargetTypes.None, SpellTargetObjectTypes.None), // 212 SPELL_EFFECT_212 + new StaticData(SpellEffectImplicitTargetTypes.None, SpellTargetObjectTypes.None), // 213 SPELL_EFFECT_213 + new StaticData(SpellEffectImplicitTargetTypes.None, SpellTargetObjectTypes.None), // 214 SPELL_EFFECT_CREATE_GARRISON + new StaticData(SpellEffectImplicitTargetTypes.None, SpellTargetObjectTypes.None), // 215 SPELL_EFFECT_UPGRADE_CHARACTER_SPELLS + new StaticData(SpellEffectImplicitTargetTypes.None, SpellTargetObjectTypes.None), // 216 SPELL_EFFECT_CREATE_SHIPMENT + new StaticData(SpellEffectImplicitTargetTypes.None, SpellTargetObjectTypes.None), // 217 SPELL_EFFECT_UPGRADE_GARRISON + new StaticData(SpellEffectImplicitTargetTypes.None, SpellTargetObjectTypes.None), // 218 SPELL_EFFECT_218 + new StaticData(SpellEffectImplicitTargetTypes.None, SpellTargetObjectTypes.None), // 219 SPELL_EFFECT_CREATE_CONVERSATION + new StaticData(SpellEffectImplicitTargetTypes.None, SpellTargetObjectTypes.None), // 220 SPELL_EFFECT_ADD_GARRISON_FOLLOWER + new StaticData(SpellEffectImplicitTargetTypes.None, SpellTargetObjectTypes.None), // 221 SPELL_EFFECT_221 + new StaticData(SpellEffectImplicitTargetTypes.None, SpellTargetObjectTypes.None), // 222 SPELL_EFFECT_CREATE_HEIRLOOM_ITEM + new StaticData(SpellEffectImplicitTargetTypes.None, SpellTargetObjectTypes.None), // 223 SPELL_EFFECT_CHANGE_ITEM_BONUSES + new StaticData(SpellEffectImplicitTargetTypes.None, SpellTargetObjectTypes.None), // 224 SPELL_EFFECT_ACTIVATE_GARRISON_BUILDING + new StaticData(SpellEffectImplicitTargetTypes.None, SpellTargetObjectTypes.None), // 225 SPELL_EFFECT_GRANT_BATTLEPET_LEVEL + new StaticData(SpellEffectImplicitTargetTypes.None, SpellTargetObjectTypes.None), // 226 SPELL_EFFECT_226 + new StaticData(SpellEffectImplicitTargetTypes.None, SpellTargetObjectTypes.None), // 227 SPELL_EFFECT_227 + new StaticData(SpellEffectImplicitTargetTypes.None, SpellTargetObjectTypes.None), // 228 SPELL_EFFECT_228 + new StaticData(SpellEffectImplicitTargetTypes.None, SpellTargetObjectTypes.None), // 229 SPELL_EFFECT_SET_FOLLOWER_QUALITY + new StaticData(SpellEffectImplicitTargetTypes.None, SpellTargetObjectTypes.None), // 230 SPELL_EFFECT_INCREASE_FOLLOWER_ITEM_LEVEL + new StaticData(SpellEffectImplicitTargetTypes.None, SpellTargetObjectTypes.None), // 231 SPELL_EFFECT_INCREASE_FOLLOWER_EXPERIENCE + new StaticData(SpellEffectImplicitTargetTypes.None, SpellTargetObjectTypes.None), // 232 SPELL_EFFECT_REMOVE_PHASE + new StaticData(SpellEffectImplicitTargetTypes.None, SpellTargetObjectTypes.None), // 233 SPELL_EFFECT_RANDOMIZE_FOLLOWER_ABILITIES + new StaticData(SpellEffectImplicitTargetTypes.None, SpellTargetObjectTypes.None), // 234 SPELL_EFFECT_234 + new StaticData(SpellEffectImplicitTargetTypes.None, SpellTargetObjectTypes.None), // 235 SPELL_EFFECT_235 + new StaticData(SpellEffectImplicitTargetTypes.None, SpellTargetObjectTypes.None), // 236 SPELL_EFFECT_GIVE_EXPERIENCE + new StaticData(SpellEffectImplicitTargetTypes.None, SpellTargetObjectTypes.None), // 237 SPELL_EFFECT_GIVE_RESTED_EXPERIENCE_BONUS + new StaticData(SpellEffectImplicitTargetTypes.None, SpellTargetObjectTypes.None), // 238 SPELL_EFFECT_INCREASE_SKILL + new StaticData(SpellEffectImplicitTargetTypes.None, SpellTargetObjectTypes.None), // 239 SPELL_EFFECT_END_GARRISON_BUILDING_CONSTRUCTION + new StaticData(SpellEffectImplicitTargetTypes.None, SpellTargetObjectTypes.None), // 240 SPELL_EFFECT_240 + new StaticData(SpellEffectImplicitTargetTypes.None, SpellTargetObjectTypes.None), // 241 SPELL_EFFECT_241 + new StaticData(SpellEffectImplicitTargetTypes.None, SpellTargetObjectTypes.None), // 242 SPELL_EFFECT_242 + new StaticData(SpellEffectImplicitTargetTypes.Explicit, SpellTargetObjectTypes.Item), // 243 SPELL_EFFECT_APPLY_ENCHANT_ILLUSION + new StaticData(SpellEffectImplicitTargetTypes.None, SpellTargetObjectTypes.None), // 244 SPELL_EFFECT_LEARN_FOLLOWER_ABILITY + new StaticData(SpellEffectImplicitTargetTypes.None, SpellTargetObjectTypes.None), // 245 SPELL_EFFECT_UPGRADE_HEIRLOOM + new StaticData(SpellEffectImplicitTargetTypes.None, SpellTargetObjectTypes.None), // 246 SPELL_EFFECT_FINISH_GARRISON_MISSION + new StaticData(SpellEffectImplicitTargetTypes.None, SpellTargetObjectTypes.None), // 247 SPELL_EFFECT_ADD_GARRISON_MISSION + new StaticData(SpellEffectImplicitTargetTypes.None, SpellTargetObjectTypes.None), // 248 SPELL_EFFECT_FINISH_SHIPMENT + new StaticData(SpellEffectImplicitTargetTypes.None, SpellTargetObjectTypes.None), // 249 SPELL_EFFECT_249 + new StaticData(SpellEffectImplicitTargetTypes.None, SpellTargetObjectTypes.None), // 250 SPELL_EFFECT_TAKE_SCREENSHOT + new StaticData(SpellEffectImplicitTargetTypes.None, SpellTargetObjectTypes.None), // 251 SPELL_EFFECT_SET_GARRISON_CACHE_SIZE + new StaticData(SpellEffectImplicitTargetTypes.None, SpellTargetObjectTypes.None), // 252 SPELL_EFFECT_252 + new StaticData(SpellEffectImplicitTargetTypes.Explicit, SpellTargetObjectTypes.Unit), // 253 SPELL_EFFECT_GIVE_HONOR + new StaticData(SpellEffectImplicitTargetTypes.None, SpellTargetObjectTypes.None), // 254 SPELL_EFFECT_254 + new StaticData(SpellEffectImplicitTargetTypes.None, SpellTargetObjectTypes.None) // 255 SPELL_EFFECT_255 + }; + + #region Fields + SpellInfo _spellInfo; + public uint EffectIndex; + + public SpellEffectName Effect; + public AuraType ApplyAuraName; + public uint ApplyAuraPeriod; + public int DieSides; + public float RealPointsPerLevel; + public int BasePoints; + public float PointsPerResource; + public float Amplitude; + public float ChainAmplitude; + public float BonusCoefficient; + public int MiscValue; + public int MiscValueB; + public Mechanics Mechanic; + public float PositionFacing; + public SpellImplicitTargetInfo TargetA; + public SpellImplicitTargetInfo TargetB; + public SpellRadiusRecord RadiusEntry; + public SpellRadiusRecord MaxRadiusEntry; + public uint ChainTargets; + public uint ItemType; + public uint TriggerSpell; + public FlagArray128 SpellClassMask; + public float BonusCoefficientFromAP; + public List ImplicitTargetConditions; + public ScalingInfo Scaling; + #endregion + + public struct ScalingInfo + { + public float Coefficient; + public float Variance; + public float ResourceCoefficient; + } + } + + public class SpellImplicitTargetInfo + { + public SpellImplicitTargetInfo(Targets target = 0) + { + _target = target; + } + + public bool IsArea() + { + return GetSelectionCategory() == SpellTargetSelectionCategories.Area || GetSelectionCategory() == SpellTargetSelectionCategories.Cone; + } + + public SpellTargetSelectionCategories GetSelectionCategory() + { + return _data[(int)_target].SelectionCategory; + } + + public SpellTargetReferenceTypes GetReferenceType() + { + return _data[(int)_target].ReferenceType; + } + + public SpellTargetObjectTypes GetObjectType() + { + return _data[(int)_target].ObjectType; + } + + public SpellTargetCheckTypes GetCheckType() + { + return _data[(int)_target].SelectionCheckType; + } + + SpellTargetDirectionTypes GetDirectionType() + { + return _data[(int)_target].DirectionType; + } + + public float CalcDirectionAngle() + { + float pi = MathFunctions.PI; + switch (GetDirectionType()) + { + case SpellTargetDirectionTypes.Front: + return 0.0f; + case SpellTargetDirectionTypes.Back: + return pi; + case SpellTargetDirectionTypes.Right: + return -pi / 2; + case SpellTargetDirectionTypes.Left: + return pi / 2; + case SpellTargetDirectionTypes.FrontRight: + return -pi / 4; + case SpellTargetDirectionTypes.BackRight: + return -3 * pi / 4; + case SpellTargetDirectionTypes.BackLeft: + return 3 * pi / 4; + case SpellTargetDirectionTypes.FrontLeft: + return pi / 4; + case SpellTargetDirectionTypes.Random: + return (float)RandomHelper.NextDouble() * (2 * pi); + default: + return 0.0f; + } + } + + public Targets GetTarget() + { + return _target; + } + public uint getTarget() + { + return (uint)_target; + } + + public SpellCastTargetFlags GetExplicitTargetMask(bool srcSet, bool dstSet) + { + SpellCastTargetFlags targetMask = 0; + if (GetTarget() == Targets.DestTraj) + { + if (!srcSet) + targetMask = SpellCastTargetFlags.SourceLocation; + if (!dstSet) + targetMask |= SpellCastTargetFlags.DestLocation; + } + else + { + switch (GetReferenceType()) + { + case SpellTargetReferenceTypes.Src: + if (srcSet) + break; + targetMask = SpellCastTargetFlags.SourceLocation; + break; + case SpellTargetReferenceTypes.Dest: + if (dstSet) + break; + targetMask = SpellCastTargetFlags.DestLocation; + break; + case SpellTargetReferenceTypes.Target: + switch (GetObjectType()) + { + case SpellTargetObjectTypes.Gobj: + targetMask = SpellCastTargetFlags.Gameobject; + break; + case SpellTargetObjectTypes.GobjItem: + targetMask = SpellCastTargetFlags.GameobjectItem; + break; + case SpellTargetObjectTypes.UnitAndDest: + case SpellTargetObjectTypes.Unit: + case SpellTargetObjectTypes.Dest: + switch (GetCheckType()) + { + case SpellTargetCheckTypes.Enemy: + targetMask = SpellCastTargetFlags.UnitEnemy; + break; + case SpellTargetCheckTypes.Ally: + targetMask = SpellCastTargetFlags.UnitAlly; + break; + case SpellTargetCheckTypes.Party: + targetMask = SpellCastTargetFlags.UnitParty; + break; + case SpellTargetCheckTypes.Raid: + targetMask = SpellCastTargetFlags.UnitRaid; + break; + case SpellTargetCheckTypes.Passenger: + targetMask = SpellCastTargetFlags.UnitPassenger; + break; + case SpellTargetCheckTypes.RaidClass: + default: + targetMask = SpellCastTargetFlags.Unit; + break; + } + break; + default: + break; + } + break; + default: + break; + } + } + + switch (GetObjectType()) + { + case SpellTargetObjectTypes.Src: + srcSet = true; + break; + case SpellTargetObjectTypes.Dest: + case SpellTargetObjectTypes.UnitAndDest: + dstSet = true; + break; + default: + break; + } + return targetMask; + } + + Targets _target; + + public struct StaticData + { + public StaticData(SpellTargetObjectTypes obj, SpellTargetReferenceTypes reference, + SpellTargetSelectionCategories selection, SpellTargetCheckTypes selectionCheck, SpellTargetDirectionTypes direction) + { + ObjectType = obj; + ReferenceType = reference; + SelectionCategory = selection; + SelectionCheckType = selectionCheck; + DirectionType = direction; + } + public SpellTargetObjectTypes ObjectType; // type of object returned by target type + public SpellTargetReferenceTypes ReferenceType; // defines which object is used as a reference when selecting target + public SpellTargetSelectionCategories SelectionCategory; + public SpellTargetCheckTypes SelectionCheckType; // defines selection criteria + public SpellTargetDirectionTypes DirectionType; // direction for cone and dest targets + } + + static StaticData[] _data = new StaticData[(int)Targets.TotalSpellTargets] + { + new StaticData(SpellTargetObjectTypes.None, SpellTargetReferenceTypes.None, SpellTargetSelectionCategories.Nyi, SpellTargetCheckTypes.Default, SpellTargetDirectionTypes.None), // 0 + new StaticData(SpellTargetObjectTypes.Unit, SpellTargetReferenceTypes.Caster, SpellTargetSelectionCategories.Default, SpellTargetCheckTypes.Default, SpellTargetDirectionTypes.None), // 1 TARGET_UNIT_CASTER + new StaticData(SpellTargetObjectTypes.Unit, SpellTargetReferenceTypes.Caster, SpellTargetSelectionCategories.Nearby, SpellTargetCheckTypes.Enemy, SpellTargetDirectionTypes.None), // 2 TARGET_UNIT_NEARBY_ENEMY + new StaticData(SpellTargetObjectTypes.Unit, SpellTargetReferenceTypes.Caster, SpellTargetSelectionCategories.Nearby, SpellTargetCheckTypes.Party, SpellTargetDirectionTypes.None), // 3 TARGET_UNIT_NEARBY_PARTY + new StaticData(SpellTargetObjectTypes.Unit, SpellTargetReferenceTypes.Caster, SpellTargetSelectionCategories.Nearby, SpellTargetCheckTypes.Ally, SpellTargetDirectionTypes.None), // 4 TARGET_UNIT_NEARBY_ALLY + new StaticData(SpellTargetObjectTypes.Unit, SpellTargetReferenceTypes.Caster, SpellTargetSelectionCategories.Default, SpellTargetCheckTypes.Default, SpellTargetDirectionTypes.None), // 5 TARGET_UNIT_PET + new StaticData(SpellTargetObjectTypes.Unit, SpellTargetReferenceTypes.Target, SpellTargetSelectionCategories.Default, SpellTargetCheckTypes.Enemy, SpellTargetDirectionTypes.None), // 6 TARGET_UNIT_TARGET_ENEMY + new StaticData(SpellTargetObjectTypes.Unit, SpellTargetReferenceTypes.Src, SpellTargetSelectionCategories.Area, SpellTargetCheckTypes.Entry, SpellTargetDirectionTypes.None), // 7 TARGET_UNIT_SRC_AREA_ENTRY + new StaticData(SpellTargetObjectTypes.Unit, SpellTargetReferenceTypes.Dest, SpellTargetSelectionCategories.Area, SpellTargetCheckTypes.Entry, SpellTargetDirectionTypes.None), // 8 TARGET_UNIT_DEST_AREA_ENTRY + new StaticData(SpellTargetObjectTypes.Dest, SpellTargetReferenceTypes.Caster, SpellTargetSelectionCategories.Default, SpellTargetCheckTypes.Default, SpellTargetDirectionTypes.None), // 9 TARGET_DEST_HOME + new StaticData(SpellTargetObjectTypes.None, SpellTargetReferenceTypes.None, SpellTargetSelectionCategories.Nyi, SpellTargetCheckTypes.Default, SpellTargetDirectionTypes.None), // 10 + new StaticData(SpellTargetObjectTypes.Unit, SpellTargetReferenceTypes.Src, SpellTargetSelectionCategories.Nyi, SpellTargetCheckTypes.Default, SpellTargetDirectionTypes.None), // 11 TARGET_UNIT_SRC_AREA_UNK_11 + new StaticData(SpellTargetObjectTypes.None, SpellTargetReferenceTypes.None, SpellTargetSelectionCategories.Nyi, SpellTargetCheckTypes.Default, SpellTargetDirectionTypes.None), // 12 + new StaticData(SpellTargetObjectTypes.None, SpellTargetReferenceTypes.None, SpellTargetSelectionCategories.Nyi, SpellTargetCheckTypes.Default, SpellTargetDirectionTypes.None), // 13 + new StaticData(SpellTargetObjectTypes.None, SpellTargetReferenceTypes.None, SpellTargetSelectionCategories.Nyi, SpellTargetCheckTypes.Default, SpellTargetDirectionTypes.None), // 14 + new StaticData(SpellTargetObjectTypes.Unit, SpellTargetReferenceTypes.Src, SpellTargetSelectionCategories.Area, SpellTargetCheckTypes.Enemy, SpellTargetDirectionTypes.None), // 15 TARGET_UNIT_SRC_AREA_ENEMY + new StaticData(SpellTargetObjectTypes.Unit, SpellTargetReferenceTypes.Dest, SpellTargetSelectionCategories.Area, SpellTargetCheckTypes.Enemy, SpellTargetDirectionTypes.None), // 16 TARGET_UNIT_DEST_AREA_ENEMY + new StaticData(SpellTargetObjectTypes.Dest, SpellTargetReferenceTypes.Caster, SpellTargetSelectionCategories.Default, SpellTargetCheckTypes.Default, SpellTargetDirectionTypes.None), // 17 TARGET_DEST_DB + new StaticData(SpellTargetObjectTypes.Dest, SpellTargetReferenceTypes.Caster, SpellTargetSelectionCategories.Default, SpellTargetCheckTypes.Default, SpellTargetDirectionTypes.None), // 18 TARGET_DEST_CASTER + new StaticData(SpellTargetObjectTypes.None, SpellTargetReferenceTypes.None, SpellTargetSelectionCategories.Nyi, SpellTargetCheckTypes.Default, SpellTargetDirectionTypes.None), // 19 + new StaticData(SpellTargetObjectTypes.Unit, SpellTargetReferenceTypes.Caster, SpellTargetSelectionCategories.Area, SpellTargetCheckTypes.Party, SpellTargetDirectionTypes.None), // 20 TARGET_UNIT_CASTER_AREA_PARTY + new StaticData(SpellTargetObjectTypes.Unit, SpellTargetReferenceTypes.Target, SpellTargetSelectionCategories.Default, SpellTargetCheckTypes.Ally, SpellTargetDirectionTypes.None), // 21 TARGET_UNIT_TARGET_ALLY + new StaticData(SpellTargetObjectTypes.Src, SpellTargetReferenceTypes.Caster, SpellTargetSelectionCategories.Default, SpellTargetCheckTypes.Default, SpellTargetDirectionTypes.None), // 22 TARGET_SRC_CASTER + new StaticData(SpellTargetObjectTypes.Gobj, SpellTargetReferenceTypes.Target, SpellTargetSelectionCategories.Default, SpellTargetCheckTypes.Default, SpellTargetDirectionTypes.None), // 23 TARGET_GAMEOBJECT_TARGET + new StaticData(SpellTargetObjectTypes.Unit, SpellTargetReferenceTypes.Caster, SpellTargetSelectionCategories.Cone, SpellTargetCheckTypes.Enemy, SpellTargetDirectionTypes.Front), // 24 TARGET_UNIT_CONE_ENEMY_24 + new StaticData(SpellTargetObjectTypes.Unit, SpellTargetReferenceTypes.Target, SpellTargetSelectionCategories.Default, SpellTargetCheckTypes.Default, SpellTargetDirectionTypes.None), // 25 TARGET_UNIT_TARGET_ANY + new StaticData(SpellTargetObjectTypes.GobjItem, SpellTargetReferenceTypes.Target, SpellTargetSelectionCategories.Default, SpellTargetCheckTypes.Default, SpellTargetDirectionTypes.None), // 26 TARGET_GAMEOBJECT_ITEM_TARGET + new StaticData(SpellTargetObjectTypes.Unit, SpellTargetReferenceTypes.Caster, SpellTargetSelectionCategories.Default, SpellTargetCheckTypes.Default, SpellTargetDirectionTypes.None), // 27 TARGET_UNIT_MASTER + new StaticData(SpellTargetObjectTypes.Dest, SpellTargetReferenceTypes.Dest, SpellTargetSelectionCategories.Default, SpellTargetCheckTypes.Enemy, SpellTargetDirectionTypes.None), // 28 TARGET_DEST_DYNOBJ_ENEMY + new StaticData(SpellTargetObjectTypes.Dest, SpellTargetReferenceTypes.Dest, SpellTargetSelectionCategories.Default, SpellTargetCheckTypes.Ally, SpellTargetDirectionTypes.None), // 29 TARGET_DEST_DYNOBJ_ALLY + new StaticData(SpellTargetObjectTypes.Unit, SpellTargetReferenceTypes.Src, SpellTargetSelectionCategories.Area, SpellTargetCheckTypes.Ally, SpellTargetDirectionTypes.None), // 30 TARGET_UNIT_SRC_AREA_ALLY + new StaticData(SpellTargetObjectTypes.Unit, SpellTargetReferenceTypes.Dest, SpellTargetSelectionCategories.Area, SpellTargetCheckTypes.Ally, SpellTargetDirectionTypes.None), // 31 TARGET_UNIT_DEST_AREA_ALLY + new StaticData(SpellTargetObjectTypes.Dest, SpellTargetReferenceTypes.Caster, SpellTargetSelectionCategories.Default, SpellTargetCheckTypes.Default, SpellTargetDirectionTypes.FrontLeft), // 32 TARGET_DEST_CASTER_SUMMON + new StaticData(SpellTargetObjectTypes.Unit, SpellTargetReferenceTypes.Src, SpellTargetSelectionCategories.Area, SpellTargetCheckTypes.Party, SpellTargetDirectionTypes.None), // 33 TARGET_UNIT_SRC_AREA_PARTY + new StaticData(SpellTargetObjectTypes.Unit, SpellTargetReferenceTypes.Dest, SpellTargetSelectionCategories.Area, SpellTargetCheckTypes.Party, SpellTargetDirectionTypes.None), // 34 TARGET_UNIT_DEST_AREA_PARTY + new StaticData(SpellTargetObjectTypes.Unit, SpellTargetReferenceTypes.Target, SpellTargetSelectionCategories.Default, SpellTargetCheckTypes.Party, SpellTargetDirectionTypes.None), // 35 TARGET_UNIT_TARGET_PARTY + new StaticData(SpellTargetObjectTypes.Dest, SpellTargetReferenceTypes.Caster, SpellTargetSelectionCategories.Nyi, SpellTargetCheckTypes.Default, SpellTargetDirectionTypes.None), // 36 TARGET_DEST_CASTER_UNK_36 + new StaticData(SpellTargetObjectTypes.Unit, SpellTargetReferenceTypes.Last, SpellTargetSelectionCategories.Area, SpellTargetCheckTypes.Party, SpellTargetDirectionTypes.None), // 37 TARGET_UNIT_LASTTARGET_AREA_PARTY + new StaticData(SpellTargetObjectTypes.Unit, SpellTargetReferenceTypes.Caster, SpellTargetSelectionCategories.Nearby, SpellTargetCheckTypes.Entry, SpellTargetDirectionTypes.None), // 38 TARGET_UNIT_NEARBY_ENTRY + new StaticData(SpellTargetObjectTypes.Dest, SpellTargetReferenceTypes.Caster, SpellTargetSelectionCategories.Default, SpellTargetCheckTypes.Default, SpellTargetDirectionTypes.None), // 39 TARGET_DEST_CASTER_FISHING + new StaticData(SpellTargetObjectTypes.Gobj, SpellTargetReferenceTypes.Caster, SpellTargetSelectionCategories.Nearby, SpellTargetCheckTypes.Entry, SpellTargetDirectionTypes.None), // 40 TARGET_GAMEOBJECT_NEARBY_ENTRY + new StaticData(SpellTargetObjectTypes.Dest, SpellTargetReferenceTypes.Caster, SpellTargetSelectionCategories.Default, SpellTargetCheckTypes.Default, SpellTargetDirectionTypes.FrontRight), // 41 TARGET_DEST_CASTER_FRONT_RIGHT + new StaticData(SpellTargetObjectTypes.Dest, SpellTargetReferenceTypes.Caster, SpellTargetSelectionCategories.Default, SpellTargetCheckTypes.Default, SpellTargetDirectionTypes.BackRight), // 42 TARGET_DEST_CASTER_BACK_RIGHT + new StaticData(SpellTargetObjectTypes.Dest, SpellTargetReferenceTypes.Caster, SpellTargetSelectionCategories.Default, SpellTargetCheckTypes.Default, SpellTargetDirectionTypes.BackLeft), // 43 TARGET_DEST_CASTER_BACK_LEFT + new StaticData(SpellTargetObjectTypes.Dest, SpellTargetReferenceTypes.Caster, SpellTargetSelectionCategories.Default, SpellTargetCheckTypes.Default, SpellTargetDirectionTypes.FrontLeft), // 44 TARGET_DEST_CASTER_FRONT_LEFT + new StaticData(SpellTargetObjectTypes.Unit, SpellTargetReferenceTypes.Target, SpellTargetSelectionCategories.Default, SpellTargetCheckTypes.Ally, SpellTargetDirectionTypes.None), // 45 TARGET_UNIT_TARGET_CHAINHEAL_ALLY + new StaticData(SpellTargetObjectTypes.Dest, SpellTargetReferenceTypes.Caster, SpellTargetSelectionCategories.Nearby, SpellTargetCheckTypes.Entry, SpellTargetDirectionTypes.None), // 46 TARGET_DEST_NEARBY_ENTRY + new StaticData(SpellTargetObjectTypes.Dest, SpellTargetReferenceTypes.Caster, SpellTargetSelectionCategories.Default, SpellTargetCheckTypes.Default, SpellTargetDirectionTypes.Front), // 47 TARGET_DEST_CASTER_FRONT + new StaticData(SpellTargetObjectTypes.Dest, SpellTargetReferenceTypes.Caster, SpellTargetSelectionCategories.Default, SpellTargetCheckTypes.Default, SpellTargetDirectionTypes.Back), // 48 TARGET_DEST_CASTER_BACK + new StaticData(SpellTargetObjectTypes.Dest, SpellTargetReferenceTypes.Caster, SpellTargetSelectionCategories.Default, SpellTargetCheckTypes.Default, SpellTargetDirectionTypes.Right), // 49 TARGET_DEST_CASTER_RIGHT + new StaticData(SpellTargetObjectTypes.Dest, SpellTargetReferenceTypes.Caster, SpellTargetSelectionCategories.Default, SpellTargetCheckTypes.Default, SpellTargetDirectionTypes.Left), // 50 TARGET_DEST_CASTER_LEFT + new StaticData(SpellTargetObjectTypes.Gobj, SpellTargetReferenceTypes.Src, SpellTargetSelectionCategories.Area, SpellTargetCheckTypes.Default, SpellTargetDirectionTypes.None), // 51 TARGET_GAMEOBJECT_SRC_AREA + new StaticData(SpellTargetObjectTypes.Gobj, SpellTargetReferenceTypes.Dest, SpellTargetSelectionCategories.Area, SpellTargetCheckTypes.Default, SpellTargetDirectionTypes.None), // 52 TARGET_GAMEOBJECT_DEST_AREA + new StaticData(SpellTargetObjectTypes.Dest, SpellTargetReferenceTypes.Target, SpellTargetSelectionCategories.Default, SpellTargetCheckTypes.Enemy, SpellTargetDirectionTypes.None), // 53 TARGET_DEST_TARGET_ENEMY + new StaticData(SpellTargetObjectTypes.Unit, SpellTargetReferenceTypes.Caster, SpellTargetSelectionCategories.Cone, SpellTargetCheckTypes.Enemy, SpellTargetDirectionTypes.Front), // 54 TARGET_UNIT_CONE_ENEMY_54 + new StaticData(SpellTargetObjectTypes.Dest, SpellTargetReferenceTypes.Caster, SpellTargetSelectionCategories.Default, SpellTargetCheckTypes.Default, SpellTargetDirectionTypes.None), // 55 TARGET_DEST_CASTER_FRONT_LEAP + new StaticData(SpellTargetObjectTypes.Unit, SpellTargetReferenceTypes.Caster, SpellTargetSelectionCategories.Area, SpellTargetCheckTypes.Raid, SpellTargetDirectionTypes.None), // 56 TARGET_UNIT_CASTER_AREA_RAID + new StaticData(SpellTargetObjectTypes.Unit, SpellTargetReferenceTypes.Target, SpellTargetSelectionCategories.Default, SpellTargetCheckTypes.Raid, SpellTargetDirectionTypes.None), // 57 TARGET_UNIT_TARGET_RAID + new StaticData(SpellTargetObjectTypes.Unit, SpellTargetReferenceTypes.Caster, SpellTargetSelectionCategories.Nearby, SpellTargetCheckTypes.Raid, SpellTargetDirectionTypes.None), // 58 TARGET_UNIT_NEARBY_RAID + new StaticData(SpellTargetObjectTypes.Unit, SpellTargetReferenceTypes.Caster, SpellTargetSelectionCategories.Cone, SpellTargetCheckTypes.Ally, SpellTargetDirectionTypes.Front), // 59 TARGET_UNIT_CONE_ALLY + new StaticData(SpellTargetObjectTypes.Unit, SpellTargetReferenceTypes.Caster, SpellTargetSelectionCategories.Cone, SpellTargetCheckTypes.Entry, SpellTargetDirectionTypes.Front), // 60 TARGET_UNIT_CONE_ENTRY + new StaticData(SpellTargetObjectTypes.Unit, SpellTargetReferenceTypes.Target, SpellTargetSelectionCategories.Area, SpellTargetCheckTypes.RaidClass, SpellTargetDirectionTypes.None), // 61 TARGET_UNIT_TARGET_AREA_RAID_CLASS + new StaticData(SpellTargetObjectTypes.None, SpellTargetReferenceTypes.None, SpellTargetSelectionCategories.Nyi, SpellTargetCheckTypes.Default, SpellTargetDirectionTypes.None), // 62 TARGET_UNK_62 + new StaticData(SpellTargetObjectTypes.Dest, SpellTargetReferenceTypes.Target, SpellTargetSelectionCategories.Default, SpellTargetCheckTypes.Default, SpellTargetDirectionTypes.None), // 63 TARGET_DEST_TARGET_ANY + new StaticData(SpellTargetObjectTypes.Dest, SpellTargetReferenceTypes.Target, SpellTargetSelectionCategories.Default, SpellTargetCheckTypes.Default, SpellTargetDirectionTypes.Front), // 64 TARGET_DEST_TARGET_FRONT + new StaticData(SpellTargetObjectTypes.Dest, SpellTargetReferenceTypes.Target, SpellTargetSelectionCategories.Default, SpellTargetCheckTypes.Default, SpellTargetDirectionTypes.Back), // 65 TARGET_DEST_TARGET_BACK + new StaticData(SpellTargetObjectTypes.Dest, SpellTargetReferenceTypes.Target, SpellTargetSelectionCategories.Default, SpellTargetCheckTypes.Default, SpellTargetDirectionTypes.Right), // 66 TARGET_DEST_TARGET_RIGHT + new StaticData(SpellTargetObjectTypes.Dest, SpellTargetReferenceTypes.Target, SpellTargetSelectionCategories.Default, SpellTargetCheckTypes.Default, SpellTargetDirectionTypes.Left), // 67 TARGET_DEST_TARGET_LEFT + new StaticData(SpellTargetObjectTypes.Dest, SpellTargetReferenceTypes.Target, SpellTargetSelectionCategories.Default, SpellTargetCheckTypes.Default, SpellTargetDirectionTypes.FrontRight), // 68 TARGET_DEST_TARGET_FRONT_RIGHT + new StaticData(SpellTargetObjectTypes.Dest, SpellTargetReferenceTypes.Target, SpellTargetSelectionCategories.Default, SpellTargetCheckTypes.Default, SpellTargetDirectionTypes.BackRight), // 69 TARGET_DEST_TARGET_BACK_RIGHT + new StaticData(SpellTargetObjectTypes.Dest, SpellTargetReferenceTypes.Target, SpellTargetSelectionCategories.Default, SpellTargetCheckTypes.Default, SpellTargetDirectionTypes.BackLeft), // 70 TARGET_DEST_TARGET_BACK_LEFT + new StaticData(SpellTargetObjectTypes.Dest, SpellTargetReferenceTypes.Target, SpellTargetSelectionCategories.Default, SpellTargetCheckTypes.Default, SpellTargetDirectionTypes.FrontLeft), // 71 TARGET_DEST_TARGET_FRONT_LEFT + new StaticData(SpellTargetObjectTypes.Dest, SpellTargetReferenceTypes.Caster, SpellTargetSelectionCategories.Default, SpellTargetCheckTypes.Default, SpellTargetDirectionTypes.Random), // 72 TARGET_DEST_CASTER_RANDOM + new StaticData(SpellTargetObjectTypes.Dest, SpellTargetReferenceTypes.Caster, SpellTargetSelectionCategories.Default, SpellTargetCheckTypes.Default, SpellTargetDirectionTypes.Random), // 73 TARGET_DEST_CASTER_RADIUS + new StaticData(SpellTargetObjectTypes.Dest, SpellTargetReferenceTypes.Target, SpellTargetSelectionCategories.Default, SpellTargetCheckTypes.Default, SpellTargetDirectionTypes.Random), // 74 TARGET_DEST_TARGET_RANDOM + new StaticData(SpellTargetObjectTypes.Dest, SpellTargetReferenceTypes.Target, SpellTargetSelectionCategories.Default, SpellTargetCheckTypes.Default, SpellTargetDirectionTypes.Random), // 75 TARGET_DEST_TARGET_RADIUS + new StaticData(SpellTargetObjectTypes.Dest, SpellTargetReferenceTypes.Caster, SpellTargetSelectionCategories.Channel, SpellTargetCheckTypes.Default, SpellTargetDirectionTypes.None), // 76 TARGET_DEST_CHANNEL_TARGET + new StaticData(SpellTargetObjectTypes.Unit, SpellTargetReferenceTypes.Caster, SpellTargetSelectionCategories.Channel, SpellTargetCheckTypes.Default, SpellTargetDirectionTypes.None), // 77 TARGET_UNIT_CHANNEL_TARGET + new StaticData(SpellTargetObjectTypes.Dest, SpellTargetReferenceTypes.Dest, SpellTargetSelectionCategories.Default, SpellTargetCheckTypes.Default, SpellTargetDirectionTypes.Front), // 78 TARGET_DEST_DEST_FRONT + new StaticData(SpellTargetObjectTypes.Dest, SpellTargetReferenceTypes.Dest, SpellTargetSelectionCategories.Default, SpellTargetCheckTypes.Default, SpellTargetDirectionTypes.Back), // 79 TARGET_DEST_DEST_BACK + new StaticData(SpellTargetObjectTypes.Dest, SpellTargetReferenceTypes.Dest, SpellTargetSelectionCategories.Default, SpellTargetCheckTypes.Default, SpellTargetDirectionTypes.Right), // 80 TARGET_DEST_DEST_RIGHT + new StaticData(SpellTargetObjectTypes.Dest, SpellTargetReferenceTypes.Dest, SpellTargetSelectionCategories.Default, SpellTargetCheckTypes.Default, SpellTargetDirectionTypes.Left), // 81 TARGET_DEST_DEST_LEFT + new StaticData(SpellTargetObjectTypes.Dest, SpellTargetReferenceTypes.Dest, SpellTargetSelectionCategories.Default, SpellTargetCheckTypes.Default, SpellTargetDirectionTypes.FrontRight), // 82 TARGET_DEST_DEST_FRONT_RIGHT + new StaticData(SpellTargetObjectTypes.Dest, SpellTargetReferenceTypes.Dest, SpellTargetSelectionCategories.Default, SpellTargetCheckTypes.Default, SpellTargetDirectionTypes.BackRight), // 83 TARGET_DEST_DEST_BACK_RIGHT + new StaticData(SpellTargetObjectTypes.Dest, SpellTargetReferenceTypes.Dest, SpellTargetSelectionCategories.Default, SpellTargetCheckTypes.Default, SpellTargetDirectionTypes.BackLeft), // 84 TARGET_DEST_DEST_BACK_LEFT + new StaticData(SpellTargetObjectTypes.Dest, SpellTargetReferenceTypes.Dest, SpellTargetSelectionCategories.Default, SpellTargetCheckTypes.Default, SpellTargetDirectionTypes.FrontLeft), // 85 TARGET_DEST_DEST_FRONT_LEFT + new StaticData(SpellTargetObjectTypes.Dest, SpellTargetReferenceTypes.Dest, SpellTargetSelectionCategories.Default, SpellTargetCheckTypes.Default, SpellTargetDirectionTypes.Random), // 86 TARGET_DEST_DEST_RANDOM + new StaticData(SpellTargetObjectTypes.Dest, SpellTargetReferenceTypes.Dest, SpellTargetSelectionCategories.Default, SpellTargetCheckTypes.Default, SpellTargetDirectionTypes.None), // 87 TARGET_DEST_DEST + new StaticData(SpellTargetObjectTypes.Dest, SpellTargetReferenceTypes.Dest, SpellTargetSelectionCategories.Default, SpellTargetCheckTypes.Default, SpellTargetDirectionTypes.None), // 88 TARGET_DEST_DYNOBJ_NONE + new StaticData(SpellTargetObjectTypes.Dest, SpellTargetReferenceTypes.Dest, SpellTargetSelectionCategories.Default, SpellTargetCheckTypes.Default, SpellTargetDirectionTypes.None), // 89 TARGET_DEST_TRAJ + new StaticData(SpellTargetObjectTypes.Unit, SpellTargetReferenceTypes.Target, SpellTargetSelectionCategories.Default, SpellTargetCheckTypes.Default, SpellTargetDirectionTypes.None), // 90 TARGET_UNIT_TARGET_MINIPET + new StaticData(SpellTargetObjectTypes.Dest, SpellTargetReferenceTypes.Dest, SpellTargetSelectionCategories.Default, SpellTargetCheckTypes.Default, SpellTargetDirectionTypes.Random), // 91 TARGET_DEST_DEST_RADIUS + new StaticData(SpellTargetObjectTypes.Unit, SpellTargetReferenceTypes.Caster, SpellTargetSelectionCategories.Default, SpellTargetCheckTypes.Default, SpellTargetDirectionTypes.None), // 92 TARGET_UNIT_SUMMONER + new StaticData(SpellTargetObjectTypes.Corpse,SpellTargetReferenceTypes.Src, SpellTargetSelectionCategories.Area, SpellTargetCheckTypes.Enemy, SpellTargetDirectionTypes.None), // 93 TARGET_CORPSE_SRC_AREA_ENEMY + new StaticData(SpellTargetObjectTypes.Unit, SpellTargetReferenceTypes.Caster, SpellTargetSelectionCategories.Default, SpellTargetCheckTypes.Default, SpellTargetDirectionTypes.None), // 94 TARGET_UNIT_VEHICLE + new StaticData(SpellTargetObjectTypes.Unit, SpellTargetReferenceTypes.Target, SpellTargetSelectionCategories.Default, SpellTargetCheckTypes.Passenger,SpellTargetDirectionTypes.None), // 95 TARGET_UNIT_TARGET_PASSENGER + new StaticData(SpellTargetObjectTypes.Unit, SpellTargetReferenceTypes.Caster, SpellTargetSelectionCategories.Default, SpellTargetCheckTypes.Default, SpellTargetDirectionTypes.None), // 96 TARGET_UNIT_PASSENGER_0 + new StaticData(SpellTargetObjectTypes.Unit, SpellTargetReferenceTypes.Caster, SpellTargetSelectionCategories.Default, SpellTargetCheckTypes.Default, SpellTargetDirectionTypes.None), // 97 TARGET_UNIT_PASSENGER_1 + new StaticData(SpellTargetObjectTypes.Unit, SpellTargetReferenceTypes.Caster, SpellTargetSelectionCategories.Default, SpellTargetCheckTypes.Default, SpellTargetDirectionTypes.None), // 98 TARGET_UNIT_PASSENGER_2 + new StaticData(SpellTargetObjectTypes.Unit, SpellTargetReferenceTypes.Caster, SpellTargetSelectionCategories.Default, SpellTargetCheckTypes.Default, SpellTargetDirectionTypes.None), // 99 TARGET_UNIT_PASSENGER_3 + new StaticData(SpellTargetObjectTypes.Unit, SpellTargetReferenceTypes.Caster, SpellTargetSelectionCategories.Default, SpellTargetCheckTypes.Default, SpellTargetDirectionTypes.None), // 100 TARGET_UNIT_PASSENGER_4 + new StaticData(SpellTargetObjectTypes.Unit, SpellTargetReferenceTypes.Caster, SpellTargetSelectionCategories.Default, SpellTargetCheckTypes.Default, SpellTargetDirectionTypes.None), // 101 TARGET_UNIT_PASSENGER_5 + new StaticData(SpellTargetObjectTypes.Unit, SpellTargetReferenceTypes.Caster, SpellTargetSelectionCategories.Default, SpellTargetCheckTypes.Default, SpellTargetDirectionTypes.None), // 102 TARGET_UNIT_PASSENGER_6 + new StaticData(SpellTargetObjectTypes.Unit, SpellTargetReferenceTypes.Caster, SpellTargetSelectionCategories.Default, SpellTargetCheckTypes.Default, SpellTargetDirectionTypes.None), // 103 TARGET_UNIT_PASSENGER_7 + new StaticData(SpellTargetObjectTypes.Unit, SpellTargetReferenceTypes.Caster, SpellTargetSelectionCategories.Cone, SpellTargetCheckTypes.Enemy, SpellTargetDirectionTypes.Front), // 104 TARGET_UNIT_CONE_ENEMY_104 + new StaticData(SpellTargetObjectTypes.Unit, SpellTargetReferenceTypes.None, SpellTargetSelectionCategories.Nyi, SpellTargetCheckTypes.Default, SpellTargetDirectionTypes.None), // 105 TARGET_UNIT_UNK_105 + new StaticData(SpellTargetObjectTypes.Dest, SpellTargetReferenceTypes.Caster, SpellTargetSelectionCategories.Channel, SpellTargetCheckTypes.Default, SpellTargetDirectionTypes.None), // 106 TARGET_DEST_CHANNEL_CASTER + new StaticData(SpellTargetObjectTypes.None, SpellTargetReferenceTypes.Dest, SpellTargetSelectionCategories.Nyi, SpellTargetCheckTypes.Default, SpellTargetDirectionTypes.None), // 107 TARGET_UNK_DEST_AREA_UNK_107 + new StaticData(SpellTargetObjectTypes.Gobj, SpellTargetReferenceTypes.Caster, SpellTargetSelectionCategories.Cone, SpellTargetCheckTypes.Default, SpellTargetDirectionTypes.Front), // 108 TARGET_GAMEOBJECT_CONE + new StaticData(SpellTargetObjectTypes.None, SpellTargetReferenceTypes.None, SpellTargetSelectionCategories.Nyi, SpellTargetCheckTypes.Default, SpellTargetDirectionTypes.None), // 109 + new StaticData(SpellTargetObjectTypes.Unit, SpellTargetReferenceTypes.Caster, SpellTargetSelectionCategories.Cone, SpellTargetCheckTypes.Default, SpellTargetDirectionTypes.Front), // 110 TARGET_DEST_UNK_110 + new StaticData(SpellTargetObjectTypes.None, SpellTargetReferenceTypes.None, SpellTargetSelectionCategories.Nyi, SpellTargetCheckTypes.Default, SpellTargetDirectionTypes.None), // 111 + new StaticData(SpellTargetObjectTypes.Dest, SpellTargetReferenceTypes.Caster, SpellTargetSelectionCategories.Default, SpellTargetCheckTypes.Default, SpellTargetDirectionTypes.None), // 112 + new StaticData(SpellTargetObjectTypes.None, SpellTargetReferenceTypes.None, SpellTargetSelectionCategories.Nyi, SpellTargetCheckTypes.Default, SpellTargetDirectionTypes.None), // 113 + new StaticData(SpellTargetObjectTypes.None, SpellTargetReferenceTypes.None, SpellTargetSelectionCategories.Nyi, SpellTargetCheckTypes.Default, SpellTargetDirectionTypes.None), // 114 + new StaticData(SpellTargetObjectTypes.None, SpellTargetReferenceTypes.None, SpellTargetSelectionCategories.Nyi, SpellTargetCheckTypes.Default, SpellTargetDirectionTypes.None), // 115 + new StaticData(SpellTargetObjectTypes.None, SpellTargetReferenceTypes.None, SpellTargetSelectionCategories.Nyi, SpellTargetCheckTypes.Default, SpellTargetDirectionTypes.None), // 116 + new StaticData(SpellTargetObjectTypes.None, SpellTargetReferenceTypes.None, SpellTargetSelectionCategories.Nyi, SpellTargetCheckTypes.Default, SpellTargetDirectionTypes.None), // 117 + new StaticData(SpellTargetObjectTypes.Unit, SpellTargetReferenceTypes.Caster, SpellTargetSelectionCategories.Area, SpellTargetCheckTypes.Default, SpellTargetDirectionTypes.None), // 118 + new StaticData(SpellTargetObjectTypes.Unit, SpellTargetReferenceTypes.Caster, SpellTargetSelectionCategories.Area, SpellTargetCheckTypes.Raid, SpellTargetDirectionTypes.None), // 119 + new StaticData(SpellTargetObjectTypes.Unit, SpellTargetReferenceTypes.None, SpellTargetSelectionCategories.Area, SpellTargetCheckTypes.Default, SpellTargetDirectionTypes.None), // 120 + new StaticData(SpellTargetObjectTypes.Unit, SpellTargetReferenceTypes.Target, SpellTargetSelectionCategories.Default, SpellTargetCheckTypes.Default, SpellTargetDirectionTypes.None), // 121 + new StaticData(SpellTargetObjectTypes.None, SpellTargetReferenceTypes.None, SpellTargetSelectionCategories.Nyi, SpellTargetCheckTypes.Default, SpellTargetDirectionTypes.None), // 122 + new StaticData(SpellTargetObjectTypes.None, SpellTargetReferenceTypes.None, SpellTargetSelectionCategories.Nyi, SpellTargetCheckTypes.Default, SpellTargetDirectionTypes.None), // 123 + new StaticData(SpellTargetObjectTypes.None, SpellTargetReferenceTypes.None, SpellTargetSelectionCategories.Nyi, SpellTargetCheckTypes.Default, SpellTargetDirectionTypes.None), // 124 + new StaticData(SpellTargetObjectTypes.None, SpellTargetReferenceTypes.None, SpellTargetSelectionCategories.Nyi, SpellTargetCheckTypes.Default, SpellTargetDirectionTypes.None), // 125 + new StaticData(SpellTargetObjectTypes.None, SpellTargetReferenceTypes.None, SpellTargetSelectionCategories.Nyi, SpellTargetCheckTypes.Default, SpellTargetDirectionTypes.None), // 126 + new StaticData(SpellTargetObjectTypes.None, SpellTargetReferenceTypes.None, SpellTargetSelectionCategories.Nyi, SpellTargetCheckTypes.Default, SpellTargetDirectionTypes.None), // 127 + new StaticData(SpellTargetObjectTypes.None, SpellTargetReferenceTypes.None, SpellTargetSelectionCategories.Nyi, SpellTargetCheckTypes.Default, SpellTargetDirectionTypes.None), // 128 + new StaticData(SpellTargetObjectTypes.None, SpellTargetReferenceTypes.None, SpellTargetSelectionCategories.Nyi, SpellTargetCheckTypes.Default, SpellTargetDirectionTypes.None), // 129 + new StaticData(SpellTargetObjectTypes.None, SpellTargetReferenceTypes.None, SpellTargetSelectionCategories.Nyi, SpellTargetCheckTypes.Default, SpellTargetDirectionTypes.None), // 130 + new StaticData(SpellTargetObjectTypes.None, SpellTargetReferenceTypes.None, SpellTargetSelectionCategories.Nyi, SpellTargetCheckTypes.Default, SpellTargetDirectionTypes.None), // 131 + new StaticData(SpellTargetObjectTypes.None, SpellTargetReferenceTypes.None, SpellTargetSelectionCategories.Nyi, SpellTargetCheckTypes.Default, SpellTargetDirectionTypes.None), // 132 + new StaticData(SpellTargetObjectTypes.None, SpellTargetReferenceTypes.None, SpellTargetSelectionCategories.Nyi, SpellTargetCheckTypes.Default, SpellTargetDirectionTypes.None), // 133 + new StaticData(SpellTargetObjectTypes.None, SpellTargetReferenceTypes.None, SpellTargetSelectionCategories.Nyi, SpellTargetCheckTypes.Default, SpellTargetDirectionTypes.None), // 134 + new StaticData(SpellTargetObjectTypes.None, SpellTargetReferenceTypes.None, SpellTargetSelectionCategories.Nyi, SpellTargetCheckTypes.Default, SpellTargetDirectionTypes.None), // 135 + new StaticData(SpellTargetObjectTypes.None, SpellTargetReferenceTypes.None, SpellTargetSelectionCategories.Nyi, SpellTargetCheckTypes.Default, SpellTargetDirectionTypes.None), // 136 + new StaticData(SpellTargetObjectTypes.None, SpellTargetReferenceTypes.None, SpellTargetSelectionCategories.Nyi, SpellTargetCheckTypes.Default, SpellTargetDirectionTypes.None), // 137 + new StaticData(SpellTargetObjectTypes.None, SpellTargetReferenceTypes.None, SpellTargetSelectionCategories.Nyi, SpellTargetCheckTypes.Default, SpellTargetDirectionTypes.None), // 138 + new StaticData(SpellTargetObjectTypes.None, SpellTargetReferenceTypes.None, SpellTargetSelectionCategories.Nyi, SpellTargetCheckTypes.Default, SpellTargetDirectionTypes.None), // 139 + new StaticData(SpellTargetObjectTypes.None, SpellTargetReferenceTypes.None, SpellTargetSelectionCategories.Nyi, SpellTargetCheckTypes.Default, SpellTargetDirectionTypes.None), // 140 + new StaticData(SpellTargetObjectTypes.None, SpellTargetReferenceTypes.None, SpellTargetSelectionCategories.Nyi, SpellTargetCheckTypes.Default, SpellTargetDirectionTypes.None), // 141 + new StaticData(SpellTargetObjectTypes.None, SpellTargetReferenceTypes.None, SpellTargetSelectionCategories.Nyi, SpellTargetCheckTypes.Default, SpellTargetDirectionTypes.None), // 142 + new StaticData(SpellTargetObjectTypes.None, SpellTargetReferenceTypes.None, SpellTargetSelectionCategories.Nyi, SpellTargetCheckTypes.Default, SpellTargetDirectionTypes.None), // 143 + new StaticData(SpellTargetObjectTypes.None, SpellTargetReferenceTypes.None, SpellTargetSelectionCategories.Nyi, SpellTargetCheckTypes.Default, SpellTargetDirectionTypes.None), // 144 + new StaticData(SpellTargetObjectTypes.None, SpellTargetReferenceTypes.None, SpellTargetSelectionCategories.Nyi, SpellTargetCheckTypes.Default, SpellTargetDirectionTypes.None), // 145 + new StaticData(SpellTargetObjectTypes.None, SpellTargetReferenceTypes.None, SpellTargetSelectionCategories.Nyi, SpellTargetCheckTypes.Default, SpellTargetDirectionTypes.None), // 146 + new StaticData(SpellTargetObjectTypes.None, SpellTargetReferenceTypes.None, SpellTargetSelectionCategories.Nyi, SpellTargetCheckTypes.Default, SpellTargetDirectionTypes.None), // 147 + new StaticData(SpellTargetObjectTypes.None, SpellTargetReferenceTypes.None, SpellTargetSelectionCategories.Nyi, SpellTargetCheckTypes.Default, SpellTargetDirectionTypes.None) // 148) + }; + } + + public class SpellPowerCost + { + public PowerType Power; + public int Amount; + } +} + diff --git a/Game/Spells/SpellManager.cs b/Game/Spells/SpellManager.cs new file mode 100644 index 000000000..ea16fa3b8 --- /dev/null +++ b/Game/Spells/SpellManager.cs @@ -0,0 +1,3671 @@ +/* + * Copyright (C) 2012-2017 CypherCore + * + * 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, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +using Framework.Constants; +using Framework.Database; +using Framework.Dynamic; +using Game.BattleFields; +using Game.BattleGrounds; +using Game.DataStorage; +using Game.Movement; +using Game.Spells; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Reflection; + +namespace Game.Entities +{ + public sealed class SpellManager : Singleton + { + SpellManager() + { + Assembly currentAsm = Assembly.GetExecutingAssembly(); + foreach (var type in currentAsm.GetTypes()) + { + foreach (var methodInfo in type.GetMethods(BindingFlags.Instance | BindingFlags.NonPublic)) + { + foreach (var auraEffect in methodInfo.GetCustomAttributes()) + { + if (auraEffect == null) + continue; + + var parameters = methodInfo.GetParameters(); + if (parameters.Length < 3) + { + Log.outError(LogFilter.ServerLoading, "Method: {0} has wrong parameter count: {1} Should be 3. Can't load AuraEffect.", methodInfo.Name, parameters.Length); + continue; + } + + if (parameters[0].ParameterType != typeof(AuraApplication) || parameters[1].ParameterType != typeof(AuraEffectHandleModes) || parameters[2].ParameterType != typeof(bool)) + { + Log.outError(LogFilter.ServerLoading, "Method: {0} has wrong parameter Types: ({1}, {2}, {3}) Should be (AuraApplication, AuraEffectHandleModes, Bool). Can't load AuraEffect.", + methodInfo.Name, parameters[0].ParameterType, parameters[1].ParameterType, parameters[2].ParameterType); + continue; + } + + if (AuraEffectHandlers.ContainsKey(auraEffect.auraType)) + { + Log.outError(LogFilter.ServerLoading, "Tried to override AuraEffectHandler of {0} with {1} (AuraType {2}).", AuraEffectHandlers[auraEffect.auraType].ToString(), methodInfo.Name, auraEffect.auraType); + continue; + } + + AuraEffectHandlers.Add(auraEffect.auraType, (AuraEffectHandler)methodInfo.CreateDelegate(typeof(AuraEffectHandler))); + + } + + foreach (var spellEffect in methodInfo.GetCustomAttributes()) + { + if (spellEffect == null) + continue; + + var parameters = methodInfo.GetParameters(); + if (parameters.Length < 1) + { + Log.outError(LogFilter.ServerLoading, "Method: {0} has wrong parameter count: {1} Should be 1. Can't load SpellEffect.", methodInfo.Name, parameters.Length); + continue; + } + + if (parameters[0].ParameterType != typeof(uint)) + { + Log.outError(LogFilter.ServerLoading, "Method: {0} has wrong parameter Types: ({1}) Should be (uint). Can't load SpellEffect.", methodInfo.Name, parameters[0].ParameterType); + continue; + } + + if (SpellEffectsHandlers.ContainsKey(spellEffect.effectName)) + { + Log.outError(LogFilter.ServerLoading, "Tried to override SpellEffectsHandler of {0} with {1} (EffectName {2}).", SpellEffectsHandlers[spellEffect.effectName].ToString(), methodInfo.Name, spellEffect.effectName); + continue; + } + + SpellEffectsHandlers.Add(spellEffect.effectName, (SpellEffectHandler)methodInfo.CreateDelegate(typeof(SpellEffectHandler))); + } + } + } + } + + public bool IsSpellValid(SpellInfo spellInfo, Player player = null, bool msg = true) + { + // not exist + if (spellInfo == null) + return false; + + bool need_check_reagents = false; + + // check effects + foreach (SpellEffectInfo effect in spellInfo.GetEffectsForDifficulty(Difficulty.None)) + { + if (effect == null) + continue; + + switch (effect.Effect) + { + case 0: + continue; + + // craft spell for crafting non-existed item (break client recipes list show) + case SpellEffectName.CreateItem: + case SpellEffectName.CreateLoot: + { + if (effect.ItemType == 0) + { + // skip auto-loot crafting spells, its not need explicit item info (but have special fake items sometime) + if (!spellInfo.IsLootCrafting()) + { + if (msg) + { + if (player) + player.SendSysMessage("Craft spell {0} not have create item entry.", spellInfo.Id); + else + Log.outError(LogFilter.Spells, "Craft spell {0} not have create item entry.", spellInfo.Id); + } + return false; + } + + } + // also possible IsLootCrafting case but fake item must exist anyway + else if (Global.ObjectMgr.GetItemTemplate(effect.ItemType) == null) + { + if (msg) + { + if (player) + player.SendSysMessage("Craft spell {0} create not-exist in DB item (Entry: {1}) and then...", spellInfo.Id, effect.ItemType); + else + Log.outError(LogFilter.Spells, "Craft spell {0} create not-exist in DB item (Entry: {1}) and then...", spellInfo.Id, effect.ItemType); + } + return false; + } + + need_check_reagents = true; + break; + } + case SpellEffectName.LearnSpell: + { + SpellInfo spellInfo2 = Global.SpellMgr.GetSpellInfo(effect.TriggerSpell); + if (!IsSpellValid(spellInfo2, player, msg)) + { + if (msg) + { + if (player != null) + player.SendSysMessage("Spell {0} learn to broken spell {1}, and then...", spellInfo.Id, effect.TriggerSpell); + else + Log.outError(LogFilter.Spells, "Spell {0} learn to invalid spell {1}, and then...", spellInfo.Id, effect.TriggerSpell); + } + return false; + } + break; + } + } + } + + if (need_check_reagents) + { + for (int j = 0; j < SpellConst.MaxReagents; ++j) + { + if (spellInfo.Reagent[j] > 0 && Global.ObjectMgr.GetItemTemplate((uint)spellInfo.Reagent[j]) == null) + { + if (msg) + { + if (player != null) + player.SendSysMessage("Craft spell {0} have not-exist reagent in DB item (Entry: {1}) and then...", spellInfo.Id, spellInfo.Reagent[j]); + else + Log.outError(LogFilter.Spells, "Craft spell {0} have not-exist reagent in DB item (Entry: {1}) and then...", spellInfo.Id, spellInfo.Reagent[j]); + } + return false; + } + } + } + + return true; + } + + public SpellChainNode GetSpellChainNode(uint spell_id) + { + return mSpellChains.LookupByKey(spell_id); + } + + public uint GetFirstSpellInChain(uint spell_id) + { + var node = GetSpellChainNode(spell_id); + if (node != null) + return node.first.Id; + + return spell_id; + } + + public uint GetLastSpellInChain(uint spell_id) + { + var node = GetSpellChainNode(spell_id); + if (node != null) + return node.last.Id; + + return spell_id; + } + + public uint GetNextSpellInChain(uint spell_id) + { + var node = GetSpellChainNode(spell_id); + if (node != null) + if (node.next != null) + return node.next.Id; + + return 0; + } + + public uint GetPrevSpellInChain(uint spell_id) + { + var node = GetSpellChainNode(spell_id); + if (node != null) + if (node.prev != null) + return node.prev.Id; + + return 0; + } + + public byte GetSpellRank(uint spell_id) + { + var node = GetSpellChainNode(spell_id); + if (node != null) + return node.rank; + + return 0; + } + + public uint GetSpellWithRank(uint spell_id, uint rank, bool strict = false) + { + var node = GetSpellChainNode(spell_id); + if (node != null) + { + if (rank != node.rank) + return GetSpellWithRank(node.rank < rank ? node.next.Id : node.prev.Id, rank, strict); + } + else if (strict && rank > 1) + return 0; + + return spell_id; + } + + public List GetSpellsRequiredForSpellBounds(uint spell_id) + { + return mSpellReq.LookupByKey(spell_id); + } + + public List GetSpellsRequiringSpellBounds(uint spell_id) + { + return mSpellsReqSpell.LookupByKey(spell_id); + } + + public bool IsSpellRequiringSpell(uint spellid, uint req_spellid) + { + var spellsRequiringSpell = GetSpellsRequiringSpellBounds(req_spellid); + + foreach (var spell in spellsRequiringSpell) + { + if (spell == spellid) + return true; + } + return false; + } + + public SpellLearnSkillNode GetSpellLearnSkill(uint spell_id) + { + return mSpellLearnSkills.LookupByKey(spell_id); + } + + public List GetSpellLearnSpellMapBounds(uint spell_id) + { + return mSpellLearnSpells.LookupByKey(spell_id); + } + + bool IsSpellLearnSpell(uint spell_id) + { + return mSpellLearnSpells.ContainsKey(spell_id); + } + + public bool IsSpellLearnToSpell(uint spell_id1, uint spell_id2) + { + var bounds = GetSpellLearnSpellMapBounds(spell_id1); + foreach (var bound in bounds) + if (bound.Spell == spell_id2) + return true; + return false; + } + + public SpellTargetPosition GetSpellTargetPosition(uint spell_id, uint effIndex) + { + return mSpellTargetPositions.LookupByKey(new KeyValuePair(spell_id, effIndex)); + } + + public List GetSpellSpellGroupMapBounds(uint spell_id) + { + return mSpellSpellGroup.LookupByKey(GetFirstSpellInChain(spell_id)); + } + + public bool IsSpellMemberOfSpellGroup(uint spellid, SpellGroup groupid) + { + var spellGroup = GetSpellSpellGroupMapBounds(spellid); + foreach (var group in spellGroup) + { + if (group == groupid) + return true; + } + return false; + } + + List GetSpellGroupSpellMapBounds(SpellGroup group_id) + { + return mSpellGroupSpell.LookupByKey(group_id); + } + + public void GetSetOfSpellsInSpellGroup(SpellGroup group_id, out List foundSpells) + { + List usedGroups = new List(); + GetSetOfSpellsInSpellGroup(group_id, out foundSpells, ref usedGroups); + } + + void GetSetOfSpellsInSpellGroup(SpellGroup group_id, out List foundSpells, ref List usedGroups) + { + foundSpells = new List(); + if (usedGroups.Find(p => p == group_id) == 0) + return; + + usedGroups.Add(group_id); + + var groupSpell = GetSpellGroupSpellMapBounds(group_id); + foreach (var group in groupSpell) + { + if (group < 0) + { + SpellGroup currGroup = (SpellGroup)Math.Abs(group); + GetSetOfSpellsInSpellGroup(currGroup, out foundSpells, ref usedGroups); + } + else + { + foundSpells.Add(group); + } + } + } + + public bool AddSameEffectStackRuleSpellGroups(SpellInfo spellInfo, int amount, out Dictionary groups) + { + groups = new Dictionary(); + uint spellId = spellInfo.GetFirstRankSpell().Id; + var spellGroup = GetSpellSpellGroupMapBounds(spellId); + // Find group with SPELL_GROUP_STACK_RULE_EXCLUSIVE_SAME_EFFECT if it belongs to one + foreach (var group in spellGroup) + { + var found = mSpellGroupStack.FirstOrDefault(p => p.Key == group); + if (found.Key != SpellGroup.None) + { + if (found.Value == SpellGroupStackRule.ExclusiveSameEffect) + { + // Put the highest amount in the map + if (groups.FirstOrDefault(p => p.Key == group).Key == SpellGroup.None) + groups.Add(group, amount); + else + { + int curr_amount = groups[group]; + // Take absolute value because this also counts for the highest negative aura + if (Math.Abs(curr_amount) < Math.Abs(amount)) + groups[group] = amount; + } + // return because a spell should be in only one SPELL_GROUP_STACK_RULE_EXCLUSIVE_SAME_EFFECT group + return true; + } + } + } + // Not in a SPELL_GROUP_STACK_RULE_EXCLUSIVE_SAME_EFFECT group, so return false + return false; + } + + public SpellGroupStackRule CheckSpellGroupStackRules(SpellInfo spellInfo1, SpellInfo spellInfo2) + { + uint spellid_1 = spellInfo1.GetFirstRankSpell().Id; + uint spellid_2 = spellInfo2.GetFirstRankSpell().Id; + if (spellid_1 == spellid_2) + return SpellGroupStackRule.Default; + // find SpellGroups which are common for both spells + var spellGroup1 = GetSpellSpellGroupMapBounds(spellid_1); + List groups = new List(); + foreach (var group in spellGroup1) + { + if (IsSpellMemberOfSpellGroup(spellid_2, group)) + { + bool add = true; + var groupSpell = GetSpellGroupSpellMapBounds(group); + foreach (var group2 in groupSpell) + { + if (group2 < 0) + { + SpellGroup currGroup = (SpellGroup)Math.Abs(group2); + if (IsSpellMemberOfSpellGroup(spellid_1, currGroup) && IsSpellMemberOfSpellGroup(spellid_2, currGroup)) + { + add = false; + break; + } + } + } + if (add) + groups.Add(group); + } + } + + SpellGroupStackRule rule = SpellGroupStackRule.Default; + + foreach (var group in groups) + { + var found = mSpellGroupStack.LookupByKey(group); + if (found != 0) + rule = found; + if (rule != 0) + break; + } + return rule; + } + + public SpellGroupStackRule GetSpellGroupStackRule(SpellGroup group) + { + if (mSpellGroupStack.ContainsKey(group)) + return mSpellGroupStack.LookupByKey(group); + + return SpellGroupStackRule.Default; + } + + public SpellProcEventEntry GetSpellProcEvent(uint spellId) + { + return mSpellProcEventMap.LookupByKey(spellId); + } + + public bool IsSpellProcEventCanTriggeredBy(SpellInfo spellProto, SpellProcEventEntry spellProcEvent, ProcFlags EventProcFlag, SpellInfo procSpell, ProcFlags procFlags, ProcFlagsExLegacy procExtra, bool active) + { + // No extra req need + ProcFlagsExLegacy procEvent_procEx = ProcFlagsExLegacy.None; + + // check prockFlags for condition + if (!procFlags.HasAnyFlag(EventProcFlag)) + return false; + + bool hasFamilyMask = false; + + // Quick Check - If PROC_FLAG_TAKEN_DAMAGE is set for aura and procSpell dealt damage, proc no matter what kind of spell that deals the damage. + if (procFlags.HasAnyFlag(ProcFlags.TakenDamage) && EventProcFlag.HasAnyFlag(ProcFlags.TakenDamage)) + return true; + + if (procFlags.HasAnyFlag(ProcFlags.DonePeriodic) && EventProcFlag.HasAnyFlag(ProcFlags.DonePeriodic)) + { + if (procExtra.HasAnyFlag(ProcFlagsExLegacy.InternalHot)) + { + if (EventProcFlag == ProcFlags.DonePeriodic) + { + // no aura with only PROC_FLAG_DONE_PERIODIC and spellFamilyName == 0 can proc from a HOT. + if (spellProto.SpellFamilyName == 0) + return false; + } + // Aura must have positive procflags for a HOT to proc + else if (!EventProcFlag.HasAnyFlag(ProcFlags.DoneSpellMagicDmgClassPos | ProcFlags.DoneSpellNoneDmgClassPos)) + return false; + } + // Aura must have negative or neutral(PROC_FLAG_DONE_PERIODIC only) procflags for a DOT to proc + else if (EventProcFlag != ProcFlags.DonePeriodic) + if (!EventProcFlag.HasAnyFlag(ProcFlags.DoneSpellMagicDmgClassNeg | ProcFlags.DoneSpellNoneDmgClassNeg)) + return false; + } + + if (procFlags.HasAnyFlag(ProcFlags.TakenPeriodic) && EventProcFlag.HasAnyFlag(ProcFlags.TakenPeriodic)) + { + if (procExtra.HasAnyFlag(ProcFlagsExLegacy.InternalHot)) + { + // No aura that only has PROC_FLAG_TAKEN_PERIODIC can proc from a HOT. + if (EventProcFlag == ProcFlags.TakenPeriodic) + return false; + // Aura must have positive procflags for a HOT to proc + if (!EventProcFlag.HasAnyFlag(ProcFlags.TakenSpellMagicDmgClassPos | ProcFlags.TakenSpellNoneDmgClassPos)) + return false; + } + // Aura must have negative or neutral(PROC_FLAG_TAKEN_PERIODIC only) procflags for a DOT to proc + else if (EventProcFlag != ProcFlags.TakenPeriodic) + if (!EventProcFlag.HasAnyFlag(ProcFlags.TakenSpellMagicDmgClassNeg | ProcFlags.TakenSpellNoneDmgClassNeg)) + return false; + } + // Trap casts are active by default + if (procFlags.HasAnyFlag(ProcFlags.DoneTrapActivation)) + active = true; + + // Always trigger for this + if (procFlags.HasAnyFlag(ProcFlags.Killed | ProcFlags.Kill | ProcFlags.Death)) + return true; + + if (spellProcEvent != null) // Exist event data + { + // Store extra req + procEvent_procEx = (ProcFlagsExLegacy)spellProcEvent.procEx; + + // For melee triggers + if (procSpell == null) + { + // Check (if set) for school (melee attack have Normal school) + if (spellProcEvent.schoolMask != 0 && (spellProcEvent.schoolMask & SpellSchoolMask.Normal) == 0) + return false; + } + else // For spells need check school/spell family/family mask + { + // Check (if set) for school + if (spellProcEvent.schoolMask != 0 && (spellProcEvent.schoolMask & procSpell.SchoolMask) == 0) + return false; + + // Check (if set) for spellFamilyName + if (spellProcEvent.spellFamilyName != 0 && (spellProcEvent.spellFamilyName != procSpell.SpellFamilyName)) + return false; + + // spellFamilyName is Ok need check for spellFamilyMask if present + if (spellProcEvent.spellFamilyMask != null) + { + if (!(spellProcEvent.spellFamilyMask & procSpell.SpellFamilyFlags)) + return false; + hasFamilyMask = true; + // Some spells are not considered as active even with have spellfamilyflags + if (!procEvent_procEx.HasAnyFlag(ProcFlagsExLegacy.OnlyActiveSpell)) + active = true; + } + } + } + + if (procExtra.HasAnyFlag(ProcFlagsExLegacy.InternalReqFamily)) + { + if (!hasFamilyMask) + return false; + } + + // Check for extra req (if none) and hit/crit + if (procEvent_procEx == ProcFlagsExLegacy.None) + { + // No extra req, so can trigger only for hit/crit - spell has to be active + if (procExtra.HasAnyFlag(ProcFlagsExLegacy.NormalHit | ProcFlagsExLegacy.CriticalHit) && active) + return true; + } + else // Passive spells hits here only if resist/reflect/immune/evade + { + if (procExtra.HasAnyFlag(ProcFlagsExLegacy.AuraProcMask)) + { + // if spell marked as procing only from not active spells + if (active && procEvent_procEx.HasAnyFlag(ProcFlagsExLegacy.NotActiveSpell)) + return false; + // if spell marked as procing only from active spells + if (!active && procEvent_procEx.HasAnyFlag(ProcFlagsExLegacy.OnlyActiveSpell)) + return false; + // Exist req for PROC_EX_EX_TRIGGER_ALWAYS + if (procEvent_procEx.HasAnyFlag(ProcFlagsExLegacy.ExTriggerAlways)) + return true; + // PROC_EX_NOT_ACTIVE_SPELL and PROC_EX_ONLY_ACTIVE_SPELL flags handle: if passed checks before + if ((procExtra.HasAnyFlag(ProcFlagsExLegacy.NormalHit | ProcFlagsExLegacy.CriticalHit) && (procEvent_procEx & ProcFlagsExLegacy.AuraProcMask) == 0)) + return true; + } + // Check Extra Requirement like (hit/crit/miss/resist/parry/dodge/block/immune/reflect/absorb and other) + if (procEvent_procEx.HasAnyFlag(procExtra)) + return true; + } + return false; + } + + public SpellProcEntry GetSpellProcEntry(uint spellId) + { + return mSpellProcMap.LookupByKey(spellId); + } + + public bool CanSpellTriggerProcOnEvent(SpellProcEntry procEntry, ProcEventInfo eventInfo) + { + // proc type doesn't match + if (!Convert.ToBoolean(eventInfo.GetTypeMask() & procEntry.ProcFlags)) + return false; + + // check XP or honor target requirement + if ((procEntry.AttributesMask & 0x0000010) != 0) + { + Player actor = eventInfo.GetActor().ToPlayer(); + if (actor) + if (eventInfo.GetActionTarget() && !actor.isHonorOrXPTarget(eventInfo.GetActionTarget())) + return false; + } + + // always trigger for these types + if ((eventInfo.GetTypeMask() & (ProcFlags.Killed | ProcFlags.Kill | ProcFlags.Death)) != 0) + return true; + + // check school mask (if set) for other trigger types + if (procEntry.SchoolMask != 0 && !Convert.ToBoolean(eventInfo.GetSchoolMask() & procEntry.SchoolMask)) + return false; + + // check spell family name/flags (if set) for spells + if (eventInfo.GetTypeMask().HasAnyFlag(ProcFlags.PeriodicMask | ProcFlags.SpellMask | ProcFlags.DoneTrapActivation)) + { + SpellInfo eventSpellInfo = eventInfo.GetSpellInfo(); + + if (procEntry.SpellFamilyName != 0 && eventSpellInfo != null && (procEntry.SpellFamilyName != eventSpellInfo.SpellFamilyName)) + return false; + + if (procEntry.SpellFamilyMask != null && eventSpellInfo != null && !(procEntry.SpellFamilyMask & eventSpellInfo.SpellFamilyFlags)) + return false; + } + + // check spell type mask (if set) + if ((eventInfo.GetTypeMask() & (ProcFlags.SpellMask | ProcFlags.PeriodicMask)) != 0) + { + if (procEntry.SpellTypeMask != 0 && !Convert.ToBoolean(eventInfo.GetSpellTypeMask() & procEntry.SpellTypeMask)) + return false; + } + + // check spell phase mask + if ((eventInfo.GetTypeMask() & ProcFlags.ReqSpellPhaseMask) != 0) + { + if (!Convert.ToBoolean(eventInfo.GetSpellPhaseMask() & procEntry.SpellPhaseMask)) + return false; + } + + // check hit mask (on taken hit or on done hit, but not on spell cast phase) + if ((eventInfo.GetTypeMask() & ProcFlags.TakenHitMask) != 0 || ((eventInfo.GetTypeMask() & ProcFlags.DoneHitMask) != 0 + && !Convert.ToBoolean(eventInfo.GetSpellPhaseMask() & ProcFlagsSpellPhase.Cast))) + { + ProcFlagsHit hitMask = procEntry.HitMask; + // get default values if hit mask not set + if (hitMask == 0) + { + // for taken procs allow normal + critical hits by default + if ((eventInfo.GetTypeMask() & ProcFlags.TakenHitMask) != 0) + hitMask |= ProcFlagsHit.Normal | ProcFlagsHit.Critical; + // for done procs allow normal + critical + absorbs by default + else + hitMask |= ProcFlagsHit.Normal | ProcFlagsHit.Critical | ProcFlagsHit.Absorb; + } + if (!Convert.ToBoolean(eventInfo.GetHitMask() & hitMask)) + return false; + } + + return true; + } + + public SpellThreatEntry GetSpellThreatEntry(uint spellID) + { + var spellthreat = mSpellThreatMap.LookupByKey(spellID); + if (spellthreat != null) + return spellthreat; + else + { + uint firstSpell = GetFirstSpellInChain(spellID); + return mSpellThreatMap.LookupByKey(firstSpell); + } + } + + public List GetSkillLineAbilityMapBounds(uint spell_id) + { + return mSkillLineAbilityMap.LookupByKey(spell_id); + } + + public PetAura GetPetAura(uint spell_id, byte eff) + { + return mSpellPetAuraMap.LookupByKey((spell_id << 8) + eff); + } + + public SpellEnchantProcEntry GetSpellEnchantProcEvent(uint enchId) + { + return mSpellEnchantProcEventMap.LookupByKey(enchId); + } + + public bool IsArenaAllowedEnchancment(uint ench_id) + { + return mEnchantCustomAttr.LookupByKey((int)ench_id); + } + + public List GetSpellLinked(int spell_id) + { + return mSpellLinkedMap.LookupByKey(spell_id); + } + + public MultiMap GetPetLevelupSpellList(CreatureFamily petFamily) + { + return mPetLevelupSpellMap.LookupByKey(petFamily); + } + + public PetDefaultSpellsEntry GetPetDefaultSpellsEntry(int id) + { + return mPetDefaultSpellsMap.LookupByKey(id); + } + + public List GetSpellAreaMapBounds(uint spell_id) + { + return mSpellAreaMap.LookupByKey(spell_id); + } + + public List GetSpellAreaForQuestMapBounds(uint quest_id) + { + return mSpellAreaForQuestMap.LookupByKey(quest_id); + } + + public List GetSpellAreaForQuestEndMapBounds(uint quest_id) + { + return mSpellAreaForQuestEndMap.LookupByKey(quest_id); + } + + public List GetSpellAreaForAuraMapBounds(uint spell_id) + { + return mSpellAreaForAuraMap.LookupByKey(spell_id); + } + + public List GetSpellAreaForAreaMapBounds(uint area_id) + { + return mSpellAreaForAreaMap.LookupByKey(area_id); + } + + public List GetSpellAreaForQuestAreaMapBounds(uint area_id, uint quest_id) + { + return mSpellAreaForQuestAreaMap.LookupByKey(Tuple.Create(area_id, quest_id)); + } + + public DiminishingGroup GetDiminishingReturnsGroupForSpell(SpellInfo spellproto) + { + if (spellproto.IsPositive()) + return DiminishingGroup.None; + + if (spellproto.HasAura(Difficulty.None, AuraType.ModTaunt)) + return DiminishingGroup.Taunt; + + switch (spellproto.Id) + { + case 64803: // Entrapment + case 135373: // Entrapment + return DiminishingGroup.Root; + case 24394: // Intimidation + return DiminishingGroup.Stun; + case 118345: // Pulverize (Primal Earth Elemental) + return DiminishingGroup.Stun; + case 118905: // Static Charge (Capacitor Totem) + return DiminishingGroup.Stun; + case 108199: // Gorefiend's Grasp + return DiminishingGroup.AOEKnockback; + default: + break; + } + + // Explicit Diminishing Groups + switch (spellproto.SpellFamilyName) + { + case SpellFamilyNames.Generic: + break; + case SpellFamilyNames.Mage: + { + // Frostjaw -- 102051 + if (spellproto.SpellFamilyFlags[2].HasAnyFlag(0x40000u)) + return DiminishingGroup.Silence; + + // Frost Nova -- 122 + if (spellproto.SpellFamilyFlags[0].HasAnyFlag(0x40u)) + return DiminishingGroup.Root; + // Ice Ward -- 111340 + if (spellproto.SpellFamilyFlags[0].HasAnyFlag(0x80000u) && spellproto.SpellFamilyFlags[2].HasAnyFlag(0x2000u)) + return DiminishingGroup.Root; + // Freeze (Water Elemental) -- 33395 + if (spellproto.SpellFamilyFlags[2].HasAnyFlag(0x200u)) + return DiminishingGroup.Root; + + // Deep Freeze -- 44572 + if (spellproto.SpellFamilyFlags[1].HasAnyFlag(0x100000u)) + return DiminishingGroup.Stun; + + // Dragon's Breath -- 31661 + if (spellproto.SpellFamilyFlags[0].HasAnyFlag(0x800000u)) + return DiminishingGroup.Incapacitate; + // Polymorph -- 118 + if (spellproto.SpellFamilyFlags[0].HasAnyFlag(0x1000000u)) + return DiminishingGroup.Incapacitate; + // Ring of Frost -- 82691 + if (spellproto.SpellFamilyFlags[2].HasAnyFlag(0x40u)) + return DiminishingGroup.Incapacitate; + // Ice Nova -- 157997 + if (spellproto.SpellFamilyFlags[2].HasAnyFlag(0x800000u)) + return DiminishingGroup.Incapacitate; + break; + } + case SpellFamilyNames.Warrior: + { + // Shockwave -- 132168 + if (spellproto.SpellFamilyFlags[1].HasAnyFlag(0x8000u)) + return DiminishingGroup.Stun; + // Storm Bolt -- 132169 + if (spellproto.SpellFamilyFlags[2].HasAnyFlag(0x1000u)) + return DiminishingGroup.Stun; + + // Intimidating Shout -- 5246 + if (spellproto.SpellFamilyFlags[0].HasAnyFlag(0x40000u)) + return DiminishingGroup.Disorient; + + // Hamstring -- 1715, 8 seconds in PvP (6.0) + if (spellproto.SpellFamilyFlags[0].HasAnyFlag(0x2u)) + return DiminishingGroup.LimitOnly; + break; + } + case SpellFamilyNames.Warlock: + { + // Mortal Coil -- 6789 + if (spellproto.SpellFamilyFlags[0].HasAnyFlag(0x80000u)) + return DiminishingGroup.Incapacitate; + // Banish -- 710 + if (spellproto.SpellFamilyFlags[1].HasAnyFlag(0x8000000u)) + return DiminishingGroup.Incapacitate; + + // Fear -- 118699 + if (spellproto.SpellFamilyFlags[1].HasAnyFlag(0x400u)) + return DiminishingGroup.Disorient; + // Howl of Terror -- 5484 + if (spellproto.SpellFamilyFlags[1].HasAnyFlag(0x8u)) + return DiminishingGroup.Disorient; + + // Shadowfury -- 30283 + if (spellproto.SpellFamilyFlags[1].HasAnyFlag(0x1000u)) + return DiminishingGroup.Stun; + // Summon Infernal -- 22703 + if (spellproto.SpellFamilyFlags[0].HasAnyFlag(0x1000u)) + return DiminishingGroup.Stun; + break; + } + case SpellFamilyNames.WarlockPet: + { + // Fellash -- 115770 + // Whiplash -- 6360 + if (spellproto.SpellFamilyFlags[0].HasAnyFlag(0x8000000u)) + return DiminishingGroup.AOEKnockback; + + // Mesmerize (Shivarra pet) -- 115268 + // Seduction (Succubus pet) -- 6358 + if (spellproto.SpellFamilyFlags[0].HasAnyFlag(0x2000000u)) + return DiminishingGroup.Disorient; + + // Axe Toss (Felguard pet) -- 89766 + if (spellproto.SpellFamilyFlags[1].HasAnyFlag(0x4u)) + return DiminishingGroup.Stun; + break; + } + case SpellFamilyNames.Druid: + { + // Maim -- 22570 + if (spellproto.SpellFamilyFlags[1].HasAnyFlag(0x80u)) + return DiminishingGroup.Stun; + // Mighty Bash -- 5211 + if (spellproto.SpellFamilyFlags[0].HasAnyFlag(0x2000u)) + return DiminishingGroup.Stun; + // Rake -- 163505 -- no flags on the stun + if (spellproto.Id == 163505) + return DiminishingGroup.Stun; + + // Incapacitating Roar -- 99, no flags on the stun, 14 + if (spellproto.SpellFamilyFlags[1].HasAnyFlag(0x1u)) + return DiminishingGroup.Incapacitate; + + // Cyclone -- 33786 + if (spellproto.SpellFamilyFlags[1].HasAnyFlag(0x20u)) + return DiminishingGroup.Disorient; + + // Typhoon -- 61391 + if (spellproto.SpellFamilyFlags[1].HasAnyFlag(0x1000000u)) + return DiminishingGroup.AOEKnockback; + // Ursol's Vortex -- 118283, no family flags + if (spellproto.Id == 118283) + return DiminishingGroup.AOEKnockback; + + // Entangling Roots -- 339 + if (spellproto.SpellFamilyFlags[0].HasAnyFlag(0x200u)) + return DiminishingGroup.Root; + // Mass Entanglement -- 102359 + if (spellproto.SpellFamilyFlags[2].HasAnyFlag(0x4u)) + return DiminishingGroup.Root; + + // Faerie Fire -- 770, 20 seconds in PvP (6.0) + if (spellproto.SpellFamilyFlags[0].HasAnyFlag(0x400u)) + return DiminishingGroup.LimitOnly; + break; + } + case SpellFamilyNames.Rogue: + { + // Cheap Shot -- 1833 + if (spellproto.SpellFamilyFlags[0].HasAnyFlag(0x400u)) + return DiminishingGroup.Stun; + // Kidney Shot -- 408 + if (spellproto.SpellFamilyFlags[0].HasAnyFlag(0x200000u)) + return DiminishingGroup.Stun; + + // Gouge -- 1776 + if (spellproto.SpellFamilyFlags[0].HasAnyFlag(0x8u)) + return DiminishingGroup.Incapacitate; + // Sap -- 6770 + if (spellproto.SpellFamilyFlags[0].HasAnyFlag(0x80u)) + return DiminishingGroup.Incapacitate; + + // Blind -- 2094 + if (spellproto.SpellFamilyFlags[0].HasAnyFlag(0x1000000u)) + return DiminishingGroup.Disorient; + + // Garrote -- 1330 + if (spellproto.SpellFamilyFlags[1].HasAnyFlag(0x20000000u)) + return DiminishingGroup.Silence; + break; + } + case SpellFamilyNames.Hunter: + { + // Charge (Tenacity pet) -- 53148, no flags + if (spellproto.Id == 53148) + return DiminishingGroup.Root; + // Narrow Escape -- 136634, no flags + if (spellproto.Id == 136634) + return DiminishingGroup.Root; + + // Binding Shot -- 117526, no flags + if (spellproto.Id == 117526) + return DiminishingGroup.Stun; + + // Freezing Trap -- 3355 + if (spellproto.SpellFamilyFlags[0].HasAnyFlag(0x8u)) + return DiminishingGroup.Incapacitate; + // Wyvern Sting -- 19386 + if (spellproto.SpellFamilyFlags[1].HasAnyFlag(0x1000u)) + return DiminishingGroup.Incapacitate; + break; + } + case SpellFamilyNames.Paladin: + { + // Repentance -- 20066 + if (spellproto.SpellFamilyFlags[0].HasAnyFlag(0x4u)) + return DiminishingGroup.Incapacitate; + + // Turn Evil -- 10326 + if (spellproto.SpellFamilyFlags[1].HasAnyFlag(0x800000u)) + return DiminishingGroup.Disorient; + + // Avenger's Shield -- 31935 + if (spellproto.SpellFamilyFlags[0].HasAnyFlag(0x4000u)) + return DiminishingGroup.Silence; + + // Fist of Justice -- 105593 + // Hammer of Justice -- 853 + if (spellproto.SpellFamilyFlags[0].HasAnyFlag(0x800u)) + return DiminishingGroup.Stun; + // Holy Wrath -- 119072 + if (spellproto.SpellFamilyFlags[1].HasAnyFlag(0x200000u)) + return DiminishingGroup.Stun; + break; + } + case SpellFamilyNames.Shaman: + { + // Hex -- 51514 + if (spellproto.SpellFamilyFlags[1].HasAnyFlag(0x8000u)) + return DiminishingGroup.Incapacitate; + + // Thunderstorm -- 51490 + if (spellproto.SpellFamilyFlags[1].HasAnyFlag(0x2000u)) + return DiminishingGroup.AOEKnockback; + // Earthgrab Totem -- 64695 + if (spellproto.SpellFamilyFlags[2].HasAnyFlag(0x4000u)) + return DiminishingGroup.Root; + break; + } + case SpellFamilyNames.Deathknight: + { + // Strangulate -- 47476 + if (spellproto.SpellFamilyFlags[0].HasAnyFlag(0x200u)) + return DiminishingGroup.Silence; + + // Asphyxiate -- 108194 + if (spellproto.SpellFamilyFlags[2].HasAnyFlag(0x100000u)) + return DiminishingGroup.Stun; + // Gnaw (Ghoul) -- 91800, no flags + if (spellproto.Id == 91800) + return DiminishingGroup.Stun; + // Monstrous Blow (Ghoul w/ Dark Transformation active) -- 91797 + if (spellproto.Id == 91797) + return DiminishingGroup.Stun; + break; + } + case SpellFamilyNames.Priest: + { + // Dominate Mind -- 605 + if (spellproto.SpellFamilyFlags[0].HasAnyFlag(0x20000u) && spellproto.GetSpellVisual() == 39068) + return DiminishingGroup.Incapacitate; + // Holy Word: Chastise -- 88625 + if (spellproto.SpellFamilyFlags[2].HasAnyFlag(0x20u)) + return DiminishingGroup.Incapacitate; + // Psychic Horror -- 64044 + if (spellproto.SpellFamilyFlags[2].HasAnyFlag(0x2000u)) + return DiminishingGroup.Incapacitate; + + // Psychic Scream -- 8122 + if (spellproto.SpellFamilyFlags[0].HasAnyFlag(0x10000u)) + return DiminishingGroup.Disorient; + + // Silence -- 15487 + if (spellproto.SpellFamilyFlags[1].HasAnyFlag(0x200000u) && spellproto.SchoolMask == (SpellSchoolMask)32) + return DiminishingGroup.Silence; + break; + } + case SpellFamilyNames.Monk: + { + // Disable -- 116706, no flags + if (spellproto.Id == 116706) + return DiminishingGroup.Root; + + // Charging Ox Wave -- 119392 + if (spellproto.SpellFamilyFlags[1].HasAnyFlag(0x10000u)) + return DiminishingGroup.Stun; + // Fists of Fury -- 120086 + if (spellproto.SpellFamilyFlags[1].HasAnyFlag(0x800000u) && !spellproto.SpellFamilyFlags[2].HasAnyFlag(0x8u)) + return DiminishingGroup.Stun; + // Leg Sweep -- 119381 + if (spellproto.SpellFamilyFlags[1].HasAnyFlag(0x200u)) + return DiminishingGroup.Stun; + + // Incendiary Breath (honor talent) -- 202274, no flags + if (spellproto.Id == 202274) + return DiminishingGroup.Incapacitate; + // Paralysis -- 115078 + if (spellproto.SpellFamilyFlags[2].HasAnyFlag(0x800000u)) + return DiminishingGroup.Incapacitate; + break; + } + default: + break; + } + + return DiminishingGroup.None; + } + + public DiminishingReturnsType GetDiminishingReturnsGroupType(DiminishingGroup group) + { + switch (group) + { + case DiminishingGroup.Taunt: + case DiminishingGroup.Stun: + return DiminishingReturnsType.All; + case DiminishingGroup.LimitOnly: + case DiminishingGroup.None: + return DiminishingReturnsType.None; + default: + return DiminishingReturnsType.Player; + } + } + + public DiminishingLevels GetDiminishingReturnsMaxLevel(DiminishingGroup group) + { + switch (group) + { + case DiminishingGroup.Taunt: + return DiminishingLevels.TauntImmune; + case DiminishingGroup.AOEKnockback: + return DiminishingLevels.Level2; + default: + return DiminishingLevels.Immune; + } + } + + public int GetDiminishingReturnsLimitDuration(SpellInfo spellproto) + { + // Explicit diminishing duration + switch (spellproto.SpellFamilyName) + { + case SpellFamilyNames.Druid: + { + // Faerie Fire - 20 seconds in PvP (6.0) + if (spellproto.SpellFamilyFlags[0].HasAnyFlag(0x400u)) + return 20 * Time.InMilliseconds; + break; + } + case SpellFamilyNames.Hunter: + { + // Binding Shot - 3 seconds in PvP (6.0) + if (spellproto.Id == 117526) + return 3 * Time.InMilliseconds; + // Wyvern Sting - 6 seconds in PvP (6.0) + if (spellproto.SpellFamilyFlags[1].HasAnyFlag(0x1000u)) + return 6 * Time.InMilliseconds; + break; + } + case SpellFamilyNames.Monk: + { + // Paralysis - 4 seconds in PvP regardless of if they are facing you (6.0) + if (spellproto.SpellFamilyFlags[2].HasAnyFlag(0x800000u)) + return 4 * Time.InMilliseconds; + break; + } + default: + break; + } + + return 8 * Time.InMilliseconds; + } + + void UnloadSpellInfoChains() + { + foreach (var chain in mSpellChains) + mSpellInfoMap[chain.Key].ChainEntry = null; + + mSpellChains.Clear(); + } + + #region Loads + public void LoadSpellRanks() + { + uint oldMSTime = Time.GetMSTime(); + + Dictionary chains = new Dictionary(); + List hasPrev = new List(); + foreach (SkillLineAbilityRecord skillAbility in CliDB.SkillLineAbilityStorage.Values) + { + if (skillAbility.SupercedesSpell == 0) + continue; + + if (!HasSpellInfo(skillAbility.SupercedesSpell) || !HasSpellInfo(skillAbility.SpellID)) + continue; + + chains[skillAbility.SupercedesSpell] = skillAbility.SpellID; + hasPrev.Add(skillAbility.SpellID); + } + + // each key in chains that isn't present in hasPrev is a first rank + foreach (var pair in chains) + { + if (hasPrev.Contains(pair.Key)) + continue; + + SpellInfo first = GetSpellInfo(pair.Key); + SpellInfo next = GetSpellInfo(pair.Value); + + if (first == null || next != null) + continue; + + mSpellChains[pair.Key].first = first; + mSpellChains[pair.Key].prev = null; + mSpellChains[pair.Key].next = next; + mSpellChains[pair.Key].last = next; + mSpellChains[pair.Key].rank = 1; + mSpellInfoMap[pair.Key].ChainEntry = mSpellChains[pair.Key]; + + mSpellChains[pair.Value].first = first; + mSpellChains[pair.Value].prev = first; + mSpellChains[pair.Value].next = null; + mSpellChains[pair.Value].last = next; + mSpellChains[pair.Value].rank = 2; + mSpellInfoMap[pair.Value].ChainEntry = mSpellChains[pair.Value]; + + byte rank = 3; + var nextPair = chains.Find(pair.Value); + while (nextPair.Key != 0) + { + SpellInfo prev = GetSpellInfo(nextPair.Key); // already checked in previous iteration (or above, in case this is the first one) + SpellInfo last = GetSpellInfo(nextPair.Value); + if (last == null) + break; + + mSpellChains[nextPair.Key].next = last; + + mSpellChains[nextPair.Value].first = first; + mSpellChains[nextPair.Value].prev = prev; + mSpellChains[nextPair.Value].next = null; + mSpellChains[nextPair.Value].last = last; + mSpellChains[nextPair.Value].rank = rank++; + mSpellInfoMap[nextPair.Value].ChainEntry = mSpellChains[nextPair.Value]; + + // fill 'last' + do + { + mSpellChains[prev.Id].last = last; + prev = mSpellChains[prev.Id].prev; + } while (prev != null); + + nextPair = chains.Find(nextPair.Value); + } + } + + Log.outInfo(LogFilter.ServerLoading, "Loaded {0} spell rank records in {1}ms", mSpellChains.Count, Time.GetMSTimeDiffToNow(oldMSTime)); + } + + public void LoadSpellRequired() + { + uint oldMSTime = Time.GetMSTime(); + + mSpellsReqSpell.Clear(); // need for reload case + mSpellReq.Clear(); // need for reload case + + // 0 1 + SQLResult result = DB.World.Query("SELECT spell_id, req_spell from spell_required"); + + if (result.IsEmpty()) + { + Log.outInfo(LogFilter.ServerLoading, "Loaded 0 spell required records. DB table `spell_required` is empty."); + + return; + } + + uint count = 0; + do + { + uint spell_id = result.Read(0); + uint spell_req = result.Read(1); + + // check if chain is made with valid first spell + SpellInfo spell = GetSpellInfo(spell_id); + if (spell == null) + { + Log.outError(LogFilter.Sql, "spell_id {0} in `spell_required` table is not found in dbcs, skipped", spell_id); + continue; + } + + SpellInfo req_spell = GetSpellInfo(spell_req); + if (req_spell == null) + { + Log.outError(LogFilter.Sql, "req_spell {0} in `spell_required` table is not found in dbcs, skipped", spell_req); + continue; + } + + if (spell.IsRankOf(req_spell)) + { + Log.outError(LogFilter.Sql, "req_spell {0} and spell_id {1} in `spell_required` table are ranks of the same spell, entry not needed, skipped", spell_req, spell_id); + continue; + } + + if (IsSpellRequiringSpell(spell_id, spell_req)) + { + Log.outError(LogFilter.Sql, "duplicated entry of req_spell {0} and spell_id {1} in `spell_required`, skipped", spell_req, spell_id); + continue; + } + + mSpellReq.Add(spell_id, spell_req); + mSpellsReqSpell.Add(spell_req, spell_id); + ++count; + } while (result.NextRow()); + + Log.outInfo(LogFilter.ServerLoading, "Loaded {0} spell required records in {1} ms", count, Time.GetMSTimeDiffToNow(oldMSTime)); + + } + + public void LoadSpellLearnSkills() + { + mSpellLearnSkills.Clear(); + + // search auto-learned skills and add its to map also for use in unlearn spells/talents + uint dbc_count = 0; + foreach (var spell in mSpellInfoMap.Values) + { + foreach (SpellEffectInfo effect in spell.GetEffectsForDifficulty(Difficulty.None)) + { + if (effect == null) + continue; + + SpellLearnSkillNode dbc_node = new SpellLearnSkillNode(); + switch (effect.Effect) + { + case SpellEffectName.Skill: + dbc_node.skill = (SkillType)effect.MiscValue; + dbc_node.step = (ushort)effect.CalcValue(); + if (dbc_node.skill != SkillType.Riding) + dbc_node.value = 1; + else + dbc_node.value = (ushort)(dbc_node.step * 75); + dbc_node.maxvalue = (ushort)(dbc_node.step * 75); + break; + case SpellEffectName.DualWield: + dbc_node.skill = SkillType.DualWield; + dbc_node.step = 1; + dbc_node.value = 1; + dbc_node.maxvalue = 1; + break; + default: + continue; + } + + mSpellLearnSkills.Add(spell.Id, dbc_node); + ++dbc_count; + break; + } + } + Log.outInfo(LogFilter.ServerLoading, "Loaded {0} Spell Learn Skills from DBC", dbc_count); + } + + public void LoadSpellLearnSpells() + { + uint oldMSTime = Time.GetMSTime(); + + mSpellLearnSpells.Clear(); + + // 0 1 2 + SQLResult result = DB.World.Query("SELECT entry, SpellID, Active FROM spell_learn_spell"); + if (result.IsEmpty()) + { + Log.outError(LogFilter.ServerLoading, "Loaded 0 spell learn spells. DB table `spell_learn_spell` is empty."); + return; + } + uint count = 0; + do + { + uint spell_id = result.Read(0); + + var node = new SpellLearnSpellNode(); + node.Spell = result.Read(1); + node.OverridesSpell = 0; + node.Active = result.Read(2); + node.AutoLearned = false; + + SpellInfo spellInfo = GetSpellInfo(spell_id); + if (spellInfo == null) + { + Log.outError(LogFilter.Sql, "Spell {0} listed in `spell_learn_spell` does not exist", spell_id); + continue; + } + + if (GetSpellInfo(node.Spell) == null) + { + Log.outError(LogFilter.Sql, "Spell {0} listed in `spell_learn_spell` learning not existed spell {1}", spell_id, node.Spell); + continue; + } + + if (spellInfo.HasAttribute(SpellCustomAttributes.IsTalent)) + { + Log.outError(LogFilter.Sql, "Spell {0} listed in `spell_learn_spell` attempt learning talent spell {1}, skipped", spell_id, node.Spell); + continue; + } + + mSpellLearnSpells.Add(spell_id, node); + ++count; + } while (result.NextRow()); + + // search auto-learned spells and add its to map also for use in unlearn spells/talents + uint dbc_count = 0; + foreach (var entry in mSpellInfoMap.Values) + { + foreach (SpellEffectInfo effect in entry.GetEffectsForDifficulty(Difficulty.None)) + { + if (effect != null && effect.Effect == SpellEffectName.LearnSpell) + { + var dbc_node = new SpellLearnSpellNode(); + dbc_node.Spell = effect.TriggerSpell; + dbc_node.Active = true; // all dbc based learned spells is active (show in spell book or hide by client itself) + dbc_node.OverridesSpell = 0; + + // ignore learning not existed spells (broken/outdated/or generic learnig spell 483 + if (GetSpellInfo(dbc_node.Spell) == null) + continue; + + // talent or passive spells or skill-step spells auto-cast and not need dependent learning, + // pet teaching spells must not be dependent learning (cast) + // other required explicit dependent learning + dbc_node.AutoLearned = effect.TargetA.GetTarget() == Targets.UnitPet || entry.HasAttribute(SpellCustomAttributes.IsTalent) || entry.IsPassive() || entry.HasEffect(SpellEffectName.SkillStep); + + var db_node_bounds = GetSpellLearnSpellMapBounds(entry.Id); + + bool found = false; + foreach (var bound in db_node_bounds) + { + if (bound.Spell == dbc_node.Spell) + { + Log.outError(LogFilter.Sql, "Spell {0} auto-learn spell {1} in spell.dbc then the record in `spell_learn_spell` is redundant, please fix DB.", + entry.SpellName, dbc_node.Spell); + found = true; + break; + } + } + + if (!found) // add new spell-spell pair if not found + { + mSpellLearnSpells.Add(entry.Id, dbc_node); + ++dbc_count; + } + } + } + } + + foreach (var spellLearnSpell in CliDB.SpellLearnSpellStorage.Values) + { + if (GetSpellInfo(spellLearnSpell.SpellID) == null) + continue; + + var db_node_bounds = mSpellLearnSpells.LookupByKey(spellLearnSpell.LearnSpellID); + bool found = false; + foreach (var spellNode in db_node_bounds) + { + if (spellNode.Spell == spellLearnSpell.SpellID) + { + Log.outError(LogFilter.Sql, "Found redundant record (entry: {0}, SpellID: {1}) in `spell_learn_spell`, spell added automatically from SpellLearnSpell.db2", spellLearnSpell.LearnSpellID, spellLearnSpell.SpellID); + found = true; + break; + } + } + + if (found) + continue; + + // Check if it is already found in Spell.dbc, ignore silently if yes + var dbc_node_bounds = GetSpellLearnSpellMapBounds(spellLearnSpell.LearnSpellID); + found = false; + foreach (var spellNode in dbc_node_bounds) + { + if (spellNode.Spell == spellLearnSpell.SpellID) + { + found = true; + break; + } + } + + if (found) + continue; + + SpellLearnSpellNode dbcLearnNode = new SpellLearnSpellNode(); + dbcLearnNode.Spell = spellLearnSpell.SpellID; + dbcLearnNode.OverridesSpell = spellLearnSpell.OverridesSpellID; + dbcLearnNode.Active = true; + dbcLearnNode.AutoLearned = false; + + mSpellLearnSpells.Add(spellLearnSpell.LearnSpellID, dbcLearnNode); + ++dbc_count; + } + + Log.outInfo(LogFilter.ServerLoading, "Loaded {0} spell learn spells, {1} found in Spell.dbc in {2} ms", count, dbc_count, Time.GetMSTimeDiffToNow(oldMSTime)); + } + + public void LoadSpellTargetPositions() + { + uint oldMSTime = Time.GetMSTime(); + + mSpellTargetPositions.Clear(); // need for reload case + + // 0 1 2 3 4 5 + SQLResult result = DB.World.Query("SELECT ID, EffectIndex, MapID, PositionX, PositionY, PositionZ FROM spell_target_position"); + if (result.IsEmpty()) + { + Log.outInfo(LogFilter.ServerLoading, "Loaded 0 spell target coordinates. DB table `spell_target_position` is empty."); + return; + } + + uint count = 0; + do + { + uint spellId = result.Read(0); + uint effIndex = result.Read(1); + + SpellTargetPosition st = new SpellTargetPosition(); + st.target_mapId = result.Read(2); + st.target_X = result.Read(3); + st.target_Y = result.Read(4); + st.target_Z = result.Read(5); + + var mapEntry = CliDB.MapStorage.LookupByKey(st.target_mapId); + if (mapEntry == null) + { + Log.outError(LogFilter.Sql, "Spell (ID: {0}, EffectIndex: {1}) is using a non-existant MapID (ID: {2})", spellId, effIndex, st.target_mapId); + continue; + } + + if (st.target_X == 0 && st.target_Y == 0 && st.target_Z == 0) + { + Log.outError(LogFilter.Sql, "Spell (ID: {0}, EffectIndex: {1}) target coordinates not provided.", spellId, effIndex); + continue; + } + + SpellInfo spellInfo = GetSpellInfo(spellId); + if (spellInfo == null) + { + Log.outError(LogFilter.Sql, "Spell (ID: {0}) listed in `spell_target_position` does not exist.", spellId); + continue; + } + + SpellEffectInfo effect = spellInfo.GetEffect(effIndex); + if (effect == null) + { + Log.outError(LogFilter.Sql, "Spell (Id: {0}, effIndex: {1}) listed in `spell_target_position` does not have an effect at index {2}.", spellId, effIndex, effIndex); + continue; + } + + if (effect.TargetA.GetTarget() == Targets.DestDb || effect.TargetB.GetTarget() == Targets.DestDb) + { + var key = new KeyValuePair(spellId, effIndex); + mSpellTargetPositions[key] = st; + ++count; + } + else + { + Log.outError(LogFilter.Sql, "Spell (Id: {0}, effIndex: {1}) listed in `spell_target_position` does not have target TARGET_DEST_DB (17).", spellId, effIndex); + continue; + } + + } + while (result.NextRow()); + + Log.outInfo(LogFilter.ServerLoading, "Loaded {0} spell teleport coordinates in {1} ms", count, Time.GetMSTimeDiffToNow(oldMSTime)); + } + + public void LoadSpellGroups() + { + uint oldMSTime = Time.GetMSTime(); + + mSpellSpellGroup.Clear(); // need for reload case + mSpellGroupSpell.Clear(); + + // 0 1 + SQLResult result = DB.World.Query("SELECT id, spell_id FROM spell_group"); + if (result.IsEmpty()) + { + Log.outInfo(LogFilter.ServerLoading, "Loaded 0 spell group definitions. DB table `spell_group` is empty."); + return; + } + + List groups = new List(); + uint count = 0; + do + { + uint group_id = result.Read(0); + if (group_id <= 1000 && group_id >= (uint)SpellGroup.CoreRangeMax) + { + Log.outError(LogFilter.Sql, "SpellGroup id {0} listed in `spell_group` is in core range, but is not defined in core!", group_id); + continue; + } + int spell_id = result.Read(1); + + groups.Add(group_id); + mSpellGroupSpell.Add((SpellGroup)group_id, spell_id); + + } while (result.NextRow()); + + foreach (var group in mSpellGroupSpell.KeyValueList) + { + if (group.Value < 0) + { + if (!groups.Contains((uint)Math.Abs(group.Value))) + { + Log.outError(LogFilter.Sql, "SpellGroup id {0} listed in `spell_group` does not exist", Math.Abs(group.Value)); + mSpellGroupSpell.Remove(group.Key); + } + } + else + { + SpellInfo spellInfo = GetSpellInfo((uint)group.Value); + + if (spellInfo == null) + { + Log.outError(LogFilter.Sql, "Spell {0} listed in `spell_group` does not exist", group.Value); + mSpellGroupSpell.Remove(group.Key); + } + else if (spellInfo.GetRank() > 1) + { + Log.outError(LogFilter.Sql, "Spell {0} listed in `spell_group` is not first rank of spell", group.Value); + mSpellGroupSpell.Remove(group.Key); + } + } + } + + foreach (var group in groups) + { + List spells; + GetSetOfSpellsInSpellGroup((SpellGroup)group, out spells); + + foreach (var spell in spells) + { + ++count; + mSpellSpellGroup.Add((uint)spell, (SpellGroup)group); + } + } + + Log.outInfo(LogFilter.ServerLoading, "Loaded {0} spell group definitions in {1} ms", count, Time.GetMSTimeDiffToNow(oldMSTime)); + } + + public void LoadSpellGroupStackRules() + { + uint oldMSTime = Time.GetMSTime(); + + mSpellGroupStack.Clear(); // need for reload case + + // 0 1 + SQLResult result = DB.World.Query("SELECT group_id, stack_rule FROM spell_group_stack_rules"); + if (result.IsEmpty()) + { + Log.outInfo(LogFilter.ServerLoading, "Loaded 0 spell group stack rules. DB table `spell_group_stack_rules` is empty."); + return; + } + + uint count = 0; + do + { + uint group_id = result.Read(0); + byte stack_rule = result.Read(1); + if (stack_rule >= (byte)SpellGroupStackRule.Max) + { + Log.outError(LogFilter.Sql, "SpellGroupStackRule {0} listed in `spell_group_stack_rules` does not exist", stack_rule); + continue; + } + + var spellGroup = GetSpellGroupSpellMapBounds((SpellGroup)group_id); + + if (spellGroup == null) + { + Log.outError(LogFilter.Sql, "SpellGroup id {0} listed in `spell_group_stack_rules` does not exist", group_id); + continue; + } + + mSpellGroupStack[(SpellGroup)group_id] = (SpellGroupStackRule)stack_rule; + + ++count; + } while (result.NextRow()); + + Log.outInfo(LogFilter.ServerLoading, "Loaded {0} spell group stack rules in {1} ms", count, Time.GetMSTimeDiffToNow(oldMSTime)); + } + + public void LoadSpellProcEvents() + { + uint oldMSTime = Time.GetMSTime(); + + mSpellProcEventMap.Clear(); // need for reload case + + // 0 1 2 3 4 5 6 7 8 9 10 11 + SQLResult result = DB.World.Query("SELECT entry, SchoolMask, SpellFamilyName, SpellFamilyMask0, SpellFamilyMask1, SpellFamilyMask2, SpellFamilyMask3, procFlags, procEx, ppmRate, CustomChance, Cooldown FROM spell_proc_event"); + if (result.IsEmpty()) + { + Log.outInfo(LogFilter.ServerLoading, "Loaded 0 spell proc event conditions. DB table `spell_proc_event` is empty."); + return; + } + + uint count = 0; + do + { + int spellId = result.Read(0); + + bool allRanks = false; + if (spellId < 0) + { + allRanks = true; + spellId = -spellId; + } + + SpellInfo spellInfo = GetSpellInfo((uint)spellId); + if (spellInfo == null) + { + Log.outError(LogFilter.Sql, "Spell {0} listed in `spell_proc_event` does not exist", spellId); + continue; + } + + if (allRanks) + { + if (!spellInfo.IsRanked()) + Log.outError(LogFilter.Sql, "Spell {0} listed in `spell_proc_event` with all ranks, but spell has no ranks.", spellId); + + if (spellInfo.GetFirstRankSpell().Id != spellId) + { + Log.outError(LogFilter.Sql, "Spell {0} listed in `spell_proc_event` is not first rank of spell.", spellId); + continue; + } + } + + SpellProcEventEntry spellProcEvent = new SpellProcEventEntry(); + + spellProcEvent.schoolMask = (SpellSchoolMask)result.Read(1); + spellProcEvent.spellFamilyName = (SpellFamilyNames)result.Read(2); + spellProcEvent.spellFamilyMask = new FlagArray128(result.Read(3), result.Read(4), result.Read(5), result.Read(6)); + spellProcEvent.procFlags = result.Read(7); + spellProcEvent.procEx = result.Read(8); + spellProcEvent.ppmRate = result.Read(9); + spellProcEvent.customChance = result.Read(10); + spellProcEvent.cooldown = result.Read(11); + + while (spellInfo != null) + { + if (mSpellProcEventMap.ContainsKey(spellInfo.Id)) + { + Log.outError(LogFilter.Sql, "Spell {0} listed in `spell_proc_event` already has its first rank in table.", spellInfo.Id); + break; + } + + if (spellInfo.ProcFlags == 0 && spellProcEvent.procFlags == 0) + Log.outError(LogFilter.Sql, "Spell {0} listed in `spell_proc_event` probably not triggered spell", spellInfo.Id); + + mSpellProcEventMap[spellInfo.Id] = spellProcEvent; + + if (allRanks) + spellInfo = spellInfo.GetNextRankSpell(); + else + break; + } + + ++count; + } while (result.NextRow()); + + Log.outInfo(LogFilter.ServerLoading, "Loaded {0} extra spell proc event conditions in {1} ms", count, Time.GetMSTimeDiffToNow(oldMSTime)); + } + + public void LoadSpellProcs() + { + uint oldMSTime = Time.GetMSTime(); + + mSpellProcMap.Clear(); // need for reload case + + // 0 1 2 3 4 5 6 + SQLResult result = DB.World.Query("SELECT SpellId, SchoolMask, SpellFamilyName, SpellFamilyMask0, SpellFamilyMask1, SpellFamilyMask2, SpellFamilyMask3, " + + // 7 8 9 10 11 12 13 14 15 + "ProcFlags, SpellTypeMask, SpellPhaseMask, HitMask, AttributesMask, ProcsPerMinute, Chance, Cooldown, Charges FROM spell_proc"); + if (result.IsEmpty()) + { + Log.outInfo(LogFilter.ServerLoading, "Loaded 0 spell proc conditions and data. DB table `spell_proc` is empty."); + return; + } + + uint count = 0; + do + { + int spellId = result.Read(0); + + bool allRanks = false; + if (spellId < 0) + { + allRanks = true; + spellId = -spellId; + } + + SpellInfo spellInfo = GetSpellInfo((uint)spellId); + if (spellInfo == null) + { + Log.outError(LogFilter.Sql, "Spell {0} listed in `spell_proc` does not exist", spellId); + continue; + } + + if (allRanks) + { + if (spellInfo.GetFirstRankSpell().Id != (uint)spellId) + { + Log.outError(LogFilter.Sql, "Spell {0} listed in `spell_proc` is not first rank of spell.", spellId); + continue; + } + } + + SpellProcEntry baseProcEntry = new SpellProcEntry(); + + baseProcEntry.SchoolMask = (SpellSchoolMask)result.Read(1); + baseProcEntry.SpellFamilyName = (SpellFamilyNames)result.Read(2); + baseProcEntry.SpellFamilyMask = new FlagArray128(result.Read(3), result.Read(4), result.Read(5), result.Read(6)); + baseProcEntry.ProcFlags = (ProcFlags)result.Read(7); + baseProcEntry.SpellTypeMask = (ProcFlagsSpellType)result.Read(8); + baseProcEntry.SpellPhaseMask = (ProcFlagsSpellPhase)result.Read(9); + baseProcEntry.HitMask = (ProcFlagsHit)result.Read(10); + baseProcEntry.AttributesMask = result.Read(11); + baseProcEntry.ProcsPerMinute = result.Read(12); + baseProcEntry.Chance = result.Read(13); + baseProcEntry.Cooldown = result.Read(14); + baseProcEntry.Charges = result.Read(15); + + while (spellInfo != null) + { + if (mSpellProcMap.ContainsKey(spellInfo.Id)) + { + Log.outError(LogFilter.Sql, "Spell {0} listed in `spell_proc` has duplicate entry in the table", spellInfo.Id); + break; + } + SpellProcEntry procEntry = baseProcEntry; + + // take defaults from dbcs + if (procEntry.ProcFlags == 0) + procEntry.ProcFlags = spellInfo.ProcFlags; + if (procEntry.Charges == 0) + procEntry.Charges = spellInfo.ProcCharges; + if (procEntry.Chance == 0 && procEntry.ProcsPerMinute == 0) + procEntry.Chance = spellInfo.ProcChance; + + // validate data + if (Convert.ToBoolean(procEntry.SchoolMask & ~SpellSchoolMask.All)) + Log.outError(LogFilter.Sql, "`spell_proc` table entry for spellId {0} has wrong `SchoolMask` set: {1}", spellInfo.Id, procEntry.SchoolMask); + if (procEntry.SpellFamilyName != 0 && ((int)procEntry.SpellFamilyName < 3 || (int)procEntry.SpellFamilyName > 17 || (int)procEntry.SpellFamilyName == 14 || (int)procEntry.SpellFamilyName == 16)) + Log.outError(LogFilter.Sql, "`spell_proc` table entry for spellId {0} has wrong `SpellFamilyName` set: {1}", spellInfo.Id, procEntry.SpellFamilyName); + if (procEntry.Chance < 0) + { + Log.outError(LogFilter.Sql, "`spell_proc` table entry for spellId {0} has negative value in `Chance` field", spellInfo.Id); + procEntry.Chance = 0; + } + if (procEntry.ProcsPerMinute < 0) + { + Log.outError(LogFilter.Sql, "`spell_proc` table entry for spellId {0} has negative value in `ProcsPerMinute` field", spellInfo.Id); + procEntry.ProcsPerMinute = 0; + } + if (procEntry.Chance == 0 && procEntry.ProcsPerMinute == 0) + Log.outError(LogFilter.Sql, "`spell_proc` table entry for spellId {0} doesn't have `Chance` and `ProcsPerMinute` values defined, proc will not be triggered", spellInfo.Id); + if (procEntry.ProcFlags == 0) + Log.outError(LogFilter.Sql, "`spell_proc` table entry for spellId {0} doesn't have `ProcFlags` value defined, proc will not be triggered", spellInfo.Id); + if (Convert.ToBoolean(procEntry.SpellTypeMask & ~ProcFlagsSpellType.MaskAll)) + Log.outError(LogFilter.Sql, "`spell_proc` table entry for spellId {0} has wrong `SpellTypeMask` set: {1}", spellInfo.Id, procEntry.SpellTypeMask); + if (procEntry.SpellTypeMask != 0 && !Convert.ToBoolean(procEntry.ProcFlags & (ProcFlags.SpellMask | ProcFlags.PeriodicMask))) + Log.outError(LogFilter.Sql, "`spell_proc` table entry for spellId {0} has `SpellTypeMask` value defined, but it won't be used for defined `ProcFlags` value", spellInfo.Id); + if (procEntry.SpellPhaseMask == 0 && Convert.ToBoolean(procEntry.ProcFlags & ProcFlags.ReqSpellPhaseMask)) + Log.outError(LogFilter.Sql, "`spell_proc` table entry for spellId {0} doesn't have `SpellPhaseMask` value defined, but it's required for defined `ProcFlags` value, proc will not be triggered", spellInfo.Id); + if (Convert.ToBoolean(procEntry.SpellPhaseMask & ~ProcFlagsSpellPhase.MaskAll)) + Log.outError(LogFilter.Sql, "`spell_proc` table entry for spellId {0} has wrong `SpellPhaseMask` set: {1}", spellInfo.Id, procEntry.SpellPhaseMask); + if (procEntry.SpellPhaseMask != 0 && !Convert.ToBoolean(procEntry.ProcFlags & ProcFlags.ReqSpellPhaseMask)) + Log.outError(LogFilter.Sql, "`spell_proc` table entry for spellId {0} has `SpellPhaseMask` value defined, but it won't be used for defined `ProcFlags` value", spellInfo.Id); + if (Convert.ToBoolean(procEntry.HitMask & ~ProcFlagsHit.MaskAll)) + Log.outError(LogFilter.Sql, "`spell_proc` table entry for spellId {0} has wrong `HitMask` set: {1}", spellInfo.Id, procEntry.HitMask); + if (procEntry.HitMask != 0 && !(Convert.ToBoolean(procEntry.ProcFlags & ProcFlags.TakenHitMask) || (Convert.ToBoolean(procEntry.ProcFlags & ProcFlags.DoneHitMask) && (procEntry.SpellPhaseMask == 0 || Convert.ToBoolean(procEntry.SpellPhaseMask & (ProcFlagsSpellPhase.Hit | ProcFlagsSpellPhase.Finish)))))) + Log.outError(LogFilter.Sql, "`spell_proc` table entry for spellId {0} has `HitMask` value defined, but it won't be used for defined `ProcFlags` and `SpellPhaseMask` values", spellInfo.Id); + + mSpellProcMap.Add(spellInfo.Id, procEntry); + ++count; + + if (allRanks) + spellInfo = spellInfo.GetNextRankSpell(); + else + break; + } + } while (result.NextRow()); + + Log.outInfo(LogFilter.ServerLoading, "Loaded {0} spell proc conditions and data in {1} ms", count, Time.GetMSTimeDiffToNow(oldMSTime)); + } + + public void LoadSpellThreats() + { + uint oldMSTime = Time.GetMSTime(); + + mSpellThreatMap.Clear(); // need for reload case + + // 0 1 2 3 + SQLResult result = DB.World.Query("SELECT entry, flatMod, pctMod, apPctMod FROM spell_threat"); + if (result.IsEmpty()) + { + Log.outInfo(LogFilter.ServerLoading, "Loaded 0 aggro generating spells. DB table `spell_threat` is empty."); + return; + } + uint count = 0; + do + { + uint entry = result.Read(0); + + if (GetSpellInfo(entry) == null) + { + Log.outError(LogFilter.Sql, "Spell {0} listed in `spell_threat` does not exist", entry); + continue; + } + + SpellThreatEntry ste = new SpellThreatEntry(); + ste.flatMod = result.Read(1); + ste.pctMod = result.Read(2); + ste.apPctMod = result.Read(3); + + mSpellThreatMap[entry] = ste; + count++; + } while (result.NextRow()); + + Log.outInfo(LogFilter.ServerLoading, "Loaded {0} SpellThreatEntries in {1} ms", count, Time.GetMSTimeDiffToNow(oldMSTime)); + } + + public void LoadSkillLineAbilityMap() + { + uint oldMSTime = Time.GetMSTime(); + + mSkillLineAbilityMap.Clear(); + + foreach (var skill in CliDB.SkillLineAbilityStorage.Values) + mSkillLineAbilityMap.Add(skill.SpellID, skill); + + Log.outInfo(LogFilter.ServerLoading, "Loaded {0} SkillLineAbility MultiMap Data in {1} ms", mSkillLineAbilityMap.Count, Time.GetMSTimeDiffToNow(oldMSTime)); + } + + public void LoadSpellPetAuras() + { + uint oldMSTime = Time.GetMSTime(); + + mSpellPetAuraMap.Clear(); // need for reload case + + // 0 1 2 3 + SQLResult result = DB.World.Query("SELECT spell, effectId, pet, aura FROM spell_pet_auras"); + if (result.IsEmpty()) + { + Log.outInfo(LogFilter.ServerLoading, "Loaded 0 spell pet auras. DB table `spell_pet_auras` is empty."); + return; + } + + uint count = 0; + do + { + uint spell = result.Read(0); + byte eff = result.Read(1); + uint pet = result.Read(2); + uint aura = result.Read(3); + + var petAura = mSpellPetAuraMap.LookupByKey((spell << 8) + eff); + if (petAura != null) + petAura.AddAura(pet, aura); + else + { + SpellInfo spellInfo = GetSpellInfo(spell); + if (spellInfo == null) + { + Log.outError(LogFilter.Sql, "Spell {0} listed in `spell_pet_auras` does not exist", spell); + continue; + } + SpellEffectInfo effect = spellInfo.GetEffect(eff); + if (effect == null) + { + Log.outError(LogFilter.Spells, "Spell {0} listed in `spell_pet_auras` does not have effect at index {1}", spell, eff); + continue; + } + + if (effect.Effect != SpellEffectName.Dummy && (effect.Effect != SpellEffectName.ApplyAura || effect.ApplyAuraName != AuraType.Dummy)) + { + Log.outError(LogFilter.Spells, "Spell {0} listed in `spell_pet_auras` does not have dummy aura or dummy effect", spell); + continue; + } + + SpellInfo spellInfo2 = GetSpellInfo(aura); + if (spellInfo2 == null) + { + Log.outError(LogFilter.Sql, "Aura {0} listed in `spell_pet_auras` does not exist", aura); + continue; + } + + PetAura pa = new PetAura(pet, aura, effect.TargetA.GetTarget() == Targets.UnitPet, effect.CalcValue()); + mSpellPetAuraMap[(spell << 8) + eff] = pa; + } + ++count; + } while (result.NextRow()); + + Log.outInfo(LogFilter.ServerLoading, "Loaded {0} spell pet auras in {1} ms", count, Time.GetMSTimeDiffToNow(oldMSTime)); + } + + // Fill custom data about enchancments + public void LoadEnchantCustomAttr() + { + uint oldMSTime = Time.GetMSTime(); + + uint count = 0; + foreach (var spellInfo in mSpellInfoMap.Values) + { + // @todo find a better check + if (!spellInfo.HasAttribute(SpellAttr2.PreserveEnchantInArena) || !spellInfo.HasAttribute(SpellAttr0.NotShapeshift)) + continue; + + foreach (SpellEffectInfo effect in spellInfo.GetEffectsForDifficulty(Difficulty.None)) + { + if (effect != null && effect.Effect == SpellEffectName.EnchantItemTemporary) + { + int enchId = effect.MiscValue; + var ench = CliDB.SpellItemEnchantmentStorage.LookupByKey((uint)enchId); + if (ench == null) + continue; + mEnchantCustomAttr[enchId] = true; + ++count; + break; + } + } + } + + Log.outInfo(LogFilter.ServerLoading, "Loaded {0} custom enchant attributes in {1} ms", count, Time.GetMSTimeDiffToNow(oldMSTime)); + } + + public void LoadSpellEnchantProcData() + { + uint oldMSTime = Time.GetMSTime(); + + mSpellEnchantProcEventMap.Clear(); // need for reload case + + // 0 1 2 3 + SQLResult result = DB.World.Query("SELECT entry, customChance, PPMChance, procEx FROM spell_enchant_proc_data"); + if (result.IsEmpty()) + { + Log.outInfo(LogFilter.ServerLoading, "Loaded 0 spell enchant proc event conditions. DB table `spell_enchant_proc_data` is empty."); + return; + } + + uint count = 0; + do + { + uint enchantId = result.Read(0); + + var ench = CliDB.SpellItemEnchantmentStorage.LookupByKey(enchantId); + if (ench == null) + { + Log.outError(LogFilter.Sql, "Enchancment {0} listed in `spell_enchant_proc_data` does not exist", enchantId); + continue; + } + + SpellEnchantProcEntry spe = new SpellEnchantProcEntry(); + spe.customChance = result.Read(1); + spe.PPMChance = result.Read(2); + spe.procEx = (ProcFlagsExLegacy)result.Read(3); + + mSpellEnchantProcEventMap[enchantId] = spe; + + ++count; + } while (result.NextRow()); + + Log.outInfo(LogFilter.ServerLoading, "Loaded {0} enchant proc data definitions in {1} ms", count, Time.GetMSTimeDiffToNow(oldMSTime)); + } + + public void LoadSpellLinked() + { + uint oldMSTime = Time.GetMSTime(); + + mSpellLinkedMap.Clear(); // need for reload case + + // 0 1 2 + SQLResult result = DB.World.Query("SELECT spell_trigger, spell_effect, type FROM spell_linked_spell"); + if (result.IsEmpty()) + { + Log.outInfo(LogFilter.ServerLoading, "Loaded 0 linked spells. DB table `spell_linked_spell` is empty."); + return; + } + + uint count = 0; + do + { + int trigger = result.Read(0); + int effect = result.Read(1); + int type = result.Read(2); + + SpellInfo spellInfo = GetSpellInfo((uint)Math.Abs(trigger)); + if (spellInfo == null) + { + Log.outError(LogFilter.Sql, "Spell {0} listed in `spell_linked_spell` does not exist", Math.Abs(trigger)); + continue; + } + spellInfo = GetSpellInfo((uint)Math.Abs(effect)); + if (spellInfo == null) + { + Log.outError(LogFilter.Sql, "Spell {0} listed in `spell_linked_spell` does not exist", Math.Abs(effect)); + continue; + } + + if (type != 0) //we will find a better way when more types are needed + { + if (trigger > 0) + trigger += 200000 * type; + else + trigger -= 200000 * type; + } + mSpellLinkedMap.Add(trigger, effect); + + ++count; + } while (result.NextRow()); + + Log.outInfo(LogFilter.ServerLoading, "Loaded {0} linked spells in {1} ms", count, Time.GetMSTimeDiffToNow(oldMSTime)); + } + + public void LoadPetLevelupSpellMap() + { + uint oldMSTime = Time.GetMSTime(); + + mPetLevelupSpellMap.Clear(); // need for reload case + + uint count = 0; + uint family_count = 0; + + foreach (var creatureFamily in CliDB.CreatureFamilyStorage.Values) + { + for (byte j = 0; j < 2; ++j) + { + if (creatureFamily.SkillLine[j] == 0) + continue; + + foreach (var skillLine in CliDB.SkillLineAbilityStorage.Values) + { + if (skillLine.SkillLine != creatureFamily.SkillLine[j]) + continue; + + if (skillLine.AcquireMethod != AbilytyLearnType.OnSkillLearn) + continue; + + SpellInfo spell = GetSpellInfo(skillLine.SpellID); + if (spell == null) // not exist or triggered or talent + continue; + + if (spell.SpellLevel == 0) + continue; + + if (!mPetLevelupSpellMap.ContainsKey(creatureFamily.Id)) + mPetLevelupSpellMap.Add(creatureFamily.Id, new MultiMap()); + + var spellSet = mPetLevelupSpellMap.LookupByKey(creatureFamily.Id); + if (spellSet.Count == 0) + ++family_count; + + spellSet.Add(spell.SpellLevel, spell.Id); + ++count; + } + } + } + + Log.outInfo(LogFilter.ServerLoading, "Loaded {0} pet levelup and default spells for {1} families in {2} ms", count, family_count, Time.GetMSTimeDiffToNow(oldMSTime)); + } + + public void LoadPetDefaultSpells() + { + uint oldMSTime = Time.GetMSTime(); + + mPetDefaultSpellsMap.Clear(); + + uint countCreature = 0; + + Log.outInfo(LogFilter.ServerLoading, "Loading summonable creature templates..."); + oldMSTime = Time.GetMSTime(); + + // different summon spells + foreach (var spellEntry in mSpellInfoMap.Values) + { + foreach (SpellEffectInfo effect in spellEntry.GetEffectsForDifficulty(Difficulty.None)) + { + if (effect != null && (effect.Effect == SpellEffectName.Summon || effect.Effect == SpellEffectName.SummonPet)) + { + int creature_id = effect.MiscValue; + CreatureTemplate cInfo = Global.ObjectMgr.GetCreatureTemplate((uint)creature_id); + if (cInfo == null) + continue; + + // get default pet spells from creature_template + uint petSpellsId = cInfo.Entry; + if (mPetDefaultSpellsMap.LookupByKey(cInfo.Entry) != null) + continue; + + PetDefaultSpellsEntry petDefSpells = new PetDefaultSpellsEntry(); + for (byte j = 0; j < SharedConst.MaxCreatureSpellDataSlots; ++j) + petDefSpells.spellid[j] = cInfo.Spells[j]; + + if (LoadPetDefaultSpells_helper(cInfo, petDefSpells)) + { + mPetDefaultSpellsMap[petSpellsId] = petDefSpells; + ++countCreature; + } + } + } + } + + Log.outInfo(LogFilter.ServerLoading, "Loaded {0} summonable creature templates in {1} ms", countCreature, Time.GetMSTimeDiffToNow(oldMSTime)); + } + bool LoadPetDefaultSpells_helper(CreatureTemplate cInfo, PetDefaultSpellsEntry petDefSpells) + { + // skip empty list; + bool have_spell = false; + for (byte j = 0; j < SharedConst.MaxCreatureSpellDataSlots; ++j) + { + if (petDefSpells.spellid[j] != 0) + { + have_spell = true; + break; + } + } + if (!have_spell) + return false; + + // remove duplicates with levelupSpells if any + var levelupSpells = cInfo.Family != 0 ? GetPetLevelupSpellList(cInfo.Family) : null; + if (levelupSpells != null) + { + for (byte j = 0; j < SharedConst.MaxCreatureSpellDataSlots; ++j) + { + if (petDefSpells.spellid[j] == 0) + continue; + + foreach (var pair in levelupSpells) + { + if (pair.Value == petDefSpells.spellid[j]) + { + petDefSpells.spellid[j] = 0; + break; + } + } + } + } + + // skip empty list; + have_spell = false; + for (byte j = 0; j < SharedConst.MaxCreatureSpellDataSlots; ++j) + { + if (petDefSpells.spellid[j] != 0) + { + have_spell = true; + break; + } + } + + return have_spell; + } + + public void LoadSpellAreas() + { + uint oldMSTime = Time.GetMSTime(); + + mSpellAreaMap.Clear(); // need for reload case + mSpellAreaForQuestMap.Clear(); + mSpellAreaForQuestEndMap.Clear(); + mSpellAreaForAuraMap.Clear(); + + // 0 1 2 3 4 5 6 7 8 9 + SQLResult result = DB.World.Query("SELECT spell, area, quest_start, quest_start_status, quest_end_status, quest_end, aura_spell, racemask, gender, autocast FROM spell_area"); + if (result.IsEmpty()) + { + Log.outInfo(LogFilter.ServerLoading, "Loaded 0 spell area requirements. DB table `spell_area` is empty."); + + return; + } + + uint count = 0; + do + { + uint spell = result.Read(0); + + SpellArea spellArea = new SpellArea(); + spellArea.spellId = spell; + spellArea.areaId = result.Read(1); + spellArea.questStart = result.Read(2); + spellArea.questStartStatus = result.Read(3); + spellArea.questEndStatus = result.Read(4); + spellArea.questEnd = result.Read(5); + spellArea.auraSpell = result.Read(6); + spellArea.raceMask = result.Read(7); + spellArea.gender = (Gender)result.Read(8); + spellArea.autocast = result.Read(9); + + SpellInfo spellInfo = GetSpellInfo(spell); + if (spellInfo != null) + { + if (spellArea.autocast) + spellInfo.Attributes |= SpellAttr0.CantCancel; + } + else + { + Log.outError(LogFilter.Sql, "Spell {0} listed in `spell_area` does not exist", spell); + continue; + } + + { + bool ok = true; + var sa_bounds = GetSpellAreaMapBounds(spellArea.spellId); + foreach (var bound in sa_bounds) + { + if (spellArea.spellId != bound.spellId) + continue; + if (spellArea.areaId != bound.areaId) + continue; + if (spellArea.questStart != bound.questStart) + continue; + if (spellArea.auraSpell != bound.auraSpell) + continue; + if ((spellArea.raceMask & bound.raceMask) == 0) + continue; + if (spellArea.gender != bound.gender) + continue; + + // duplicate by requirements + ok = false; + break; + } + + if (!ok) + { + Log.outError(LogFilter.Sql, "Spell {0} listed in `spell_area` already listed with similar requirements.", spell); + continue; + } + } + + if (spellArea.areaId != 0 && !CliDB.AreaTableStorage.ContainsKey(spellArea.areaId)) + { + Log.outError(LogFilter.Sql, "Spell {0} listed in `spell_area` have wrong area ({1}) requirement", spell, spellArea.areaId); + continue; + } + + if (spellArea.questStart != 0 && Global.ObjectMgr.GetQuestTemplate(spellArea.questStart) == null) + { + Log.outError(LogFilter.Sql, "Spell {0} listed in `spell_area` have wrong start quest ({1}) requirement", spell, spellArea.questStart); + continue; + } + + if (spellArea.questEnd != 0) + { + if (Global.ObjectMgr.GetQuestTemplate(spellArea.questEnd) == null) + { + Log.outError(LogFilter.Sql, "Spell {0} listed in `spell_area` have wrong end quest ({1}) requirement", spell, spellArea.questEnd); + continue; + } + } + + if (spellArea.auraSpell != 0) + { + SpellInfo info = GetSpellInfo((uint)Math.Abs(spellArea.auraSpell)); + if (info == null) + { + Log.outError(LogFilter.Sql, "Spell {0} listed in `spell_area` have wrong aura spell ({1}) requirement", spell, Math.Abs(spellArea.auraSpell)); + continue; + } + + if (Math.Abs(spellArea.auraSpell) == spellArea.spellId) + { + Log.outError(LogFilter.Sql, "Spell {0} listed in `spell_area` have aura spell ({1}) requirement for itself", spell, Math.Abs(spellArea.auraSpell)); + continue; + } + + // not allow autocast chains by auraSpell field (but allow use as alternative if not present) + if (spellArea.autocast && spellArea.auraSpell > 0) + { + bool chain = false; + var saBound = GetSpellAreaForAuraMapBounds(spellArea.spellId); + foreach (var bound in saBound) + { + if (bound.autocast && bound.auraSpell > 0) + { + chain = true; + break; + } + } + + if (chain) + { + Log.outError(LogFilter.Sql, "Spell {0} listed in `spell_area` have aura spell ({1}) requirement that itself autocast from aura", spell, spellArea.auraSpell); + continue; + } + + var saBound2 = GetSpellAreaMapBounds((uint)spellArea.auraSpell); + foreach (var bound in saBound2) + { + if (bound.autocast && bound.auraSpell > 0) + { + chain = true; + break; + } + } + + if (chain) + { + Log.outError(LogFilter.Sql, "Spell {0} listed in `spell_area` have aura spell ({1}) requirement that itself autocast from aura", spell, spellArea.auraSpell); + continue; + } + } + } + + if (spellArea.raceMask != 0 && (spellArea.raceMask & (uint)Race.RaceMaskAllPlayable) == 0) + { + Log.outError(LogFilter.Sql, "Spell {0} listed in `spell_area` have wrong race mask ({1}) requirement", spell, spellArea.raceMask); + continue; + } + + if (spellArea.gender != Gender.None && spellArea.gender != Gender.Female && spellArea.gender != Gender.Male) + { + Log.outError(LogFilter.Sql, "Spell {0} listed in `spell_area` have wrong gender ({1}) requirement", spell, spellArea.gender); + continue; + } + mSpellAreaMap.Add(spell, spellArea); + var sa = mSpellAreaMap[spell]; + + // for search by current zone/subzone at zone/subzone change + if (spellArea.areaId != 0) + mSpellAreaForAreaMap.AddRange(spellArea.areaId, sa); + + // for search at quest start/reward + if (spellArea.questStart != 0) + mSpellAreaForQuestMap.AddRange(spellArea.questStart, sa); + + // for search at quest start/reward + if (spellArea.questEnd != 0) + mSpellAreaForQuestEndMap.AddRange(spellArea.questEnd, sa); + + // for search at aura apply + if (spellArea.auraSpell != 0) + mSpellAreaForAuraMap.AddRange((uint)Math.Abs(spellArea.auraSpell), sa); + + if (spellArea.areaId != 0 && spellArea.questStart != 0) + mSpellAreaForQuestAreaMap.AddRange(Tuple.Create(spellArea.areaId, spellArea.questStart), sa); + + if (spellArea.areaId != 0 && spellArea.questEnd != 0) + mSpellAreaForQuestAreaMap.AddRange(Tuple.Create(spellArea.areaId, spellArea.questEnd), sa); + + ++count; + } while (result.NextRow()); + + Log.outInfo(LogFilter.ServerLoading, "Loaded {0} spell area requirements in {1} ms", count, Time.GetMSTimeDiffToNow(oldMSTime)); + } + + public void LoadSpellInfoStore() + { + uint oldMSTime = Time.GetMSTime(); + + mSpellInfoMap.Clear(); + Dictionary loadData = new Dictionary(); + + Dictionary> effectsBySpell = new Dictionary>(); + Dictionary> visualsBySpell = new Dictionary>(); + Dictionary spellEffectScallingByEffectId = new Dictionary(); + foreach (var effect in CliDB.SpellEffectStorage.Values) + { + /*Contract.Assert(effect.EffectIndex < MAX_SPELL_EFFECTS, "MAX_SPELL_EFFECTS must be at least {0}", effect.EffectIndex); + Contract.Assert(effect.Effect < TOTAL_SPELL_EFFECTS, "TOTAL_SPELL_EFFECTS must be at least {0}", effect.Effect); + Contract.Assert(effect.EffectAura < TOTAL_AURAS, "TOTAL_AURAS must be at least {0}", effect.EffectAura); + Contract.Assert(effect.ImplicitTarget[0] < TOTAL_SPELL_TARGETS, "TOTAL_SPELL_TARGETS must be at least {0}", effect.ImplicitTarget[0]); + Contract.Assert(effect.ImplicitTarget[1] < TOTAL_SPELL_TARGETS, "TOTAL_SPELL_TARGETS must be at least {0}", effect.ImplicitTarget[1]);*/ + + if (!effectsBySpell.ContainsKey(effect.SpellID)) + effectsBySpell[effect.SpellID] = new Dictionary(); + + if(effect.DifficultyID > (int)Difficulty.Max) + { + + } + + if (!effectsBySpell[effect.SpellID].ContainsKey(effect.DifficultyID)) + effectsBySpell[effect.SpellID][effect.DifficultyID] = new SpellEffectRecord[SpellConst.MaxEffects]; + + effectsBySpell[effect.SpellID][effect.DifficultyID][effect.EffectIndex] = effect; + } + + foreach (var spell in CliDB.SpellStorage.Values) + loadData[spell.Id] = new SpellInfoLoadHelper(); + + foreach (SpellAuraOptionsRecord auraOptions in CliDB.SpellAuraOptionsStorage.Values) + if (auraOptions.DifficultyID == 0) // TODO: implement + loadData[auraOptions.SpellID].AuraOptions = auraOptions; + + foreach (SpellAuraRestrictionsRecord auraRestrictions in CliDB.SpellAuraRestrictionsStorage.Values) + if (auraRestrictions.DifficultyID == 0) // TODO: implement + loadData[auraRestrictions.SpellID].AuraRestrictions = auraRestrictions; + + foreach (SpellCastingRequirementsRecord castingRequirements in CliDB.SpellCastingRequirementsStorage.Values) + loadData[castingRequirements.SpellID].CastingRequirements = castingRequirements; + + foreach (SpellCategoriesRecord categories in CliDB.SpellCategoriesStorage.Values) + if (categories.DifficultyID == 0) // TODO: implement + loadData[categories.SpellID].Categories = categories; + + foreach (SpellClassOptionsRecord classOptions in CliDB.SpellClassOptionsStorage.Values) + loadData[classOptions.SpellID].ClassOptions = classOptions; + + foreach (SpellCooldownsRecord cooldowns in CliDB.SpellCooldownsStorage.Values) + if (cooldowns.DifficultyID == 0) // TODO: implement + loadData[cooldowns.SpellID].Cooldowns = cooldowns; + + foreach (SpellEffectScalingRecord spellEffectScaling in CliDB.SpellEffectScalingStorage.Values) + spellEffectScallingByEffectId[spellEffectScaling.SpellEffectID] = spellEffectScaling; + + foreach (SpellEquippedItemsRecord equippedItems in CliDB.SpellEquippedItemsStorage.Values) + loadData[equippedItems.SpellID].EquippedItems = equippedItems; + + foreach (SpellInterruptsRecord interrupts in CliDB.SpellInterruptsStorage.Values) + if (interrupts.DifficultyID == 0) // TODO: implement + loadData[interrupts.SpellID].Interrupts = interrupts; + + foreach (SpellLevelsRecord levels in CliDB.SpellLevelsStorage.Values) + if (levels.DifficultyID == 0) // TODO: implement + loadData[levels.SpellID].Levels = levels; + + foreach (SpellReagentsRecord reagents in CliDB.SpellReagentsStorage.Values) + loadData[reagents.SpellID].Reagents = reagents; + + foreach (SpellScalingRecord scaling in CliDB.SpellScalingStorage.Values) + loadData[scaling.SpellID].Scaling = scaling; + + foreach (SpellShapeshiftRecord shapeshift in CliDB.SpellShapeshiftStorage.Values) + loadData[shapeshift.SpellID].Shapeshift = shapeshift; + + foreach (SpellTargetRestrictionsRecord targetRestrictions in CliDB.SpellTargetRestrictionsStorage.Values) + if (targetRestrictions.DifficultyID == 0) // TODO: implement + loadData[targetRestrictions.SpellID].TargetRestrictions = targetRestrictions; + + foreach (SpellTotemsRecord totems in CliDB.SpellTotemsStorage.Values) + loadData[totems.SpellID].Totems = totems; + + foreach (var visual in CliDB.SpellXSpellVisualStorage.Values) + { + if (!visualsBySpell.ContainsKey(visual.SpellID)) + visualsBySpell[visual.SpellID] = new MultiMap(); + + visualsBySpell[visual.SpellID].Add(visual.DifficultyID, visual); + } + + foreach (var spellEntry in CliDB.SpellStorage.Values) + { + loadData[spellEntry.Id].Entry = spellEntry; + loadData[spellEntry.Id].Misc = CliDB.SpellMiscStorage.LookupByKey(spellEntry.MiscID); + mSpellInfoMap[spellEntry.Id] = new SpellInfo(loadData[spellEntry.Id], effectsBySpell.LookupByKey(spellEntry.Id), visualsBySpell.LookupByKey(spellEntry.Id), spellEffectScallingByEffectId); + } + + Log.outInfo(LogFilter.ServerLoading, "Loaded SpellInfo store in {0} ms", Time.GetMSTimeDiffToNow(oldMSTime)); + } + + public void UnloadSpellInfoImplicitTargetConditionLists() + { + foreach (var spell in mSpellInfoMap.Values) + spell._UnloadImplicitTargetConditionLists(); + + } + + public void LoadSpellInfoCustomAttributes() + { + uint oldMSTime = Time.GetMSTime(); + uint oldMSTime2 = oldMSTime; + + SQLResult result = DB.World.Query("SELECT entry, attributes FROM spell_custom_attr"); + if (result.IsEmpty()) + Log.outInfo(LogFilter.ServerLoading, "Loaded 0 spell custom attributes from DB. DB table `spell_custom_attr` is empty."); + else + { + uint count = 0; + do + { + uint spellId = result.Read(0); + uint attributes = result.Read(1); + + SpellInfo spellInfo = GetSpellInfo(spellId); + if (spellInfo == null) + { + Log.outError(LogFilter.Sql, "Table `spell_custom_attr` has wrong spell (entry: {0}), ignored.", spellId); + continue; + } + + // TODO: validate attributes + if (attributes.HasAnyFlag((uint)SpellCustomAttributes.ShareDamage)) + { + if (!spellInfo.HasEffect(SpellEffectName.SchoolDamage)) + { + Log.outError(LogFilter.Sql, "Spell {0} listed in table `spell_custom_attr` with SPELL_ATTR0_CU_SHARE_DAMAGE has no SPELL_EFFECT_SCHOOL_DAMAGE, ignored.", spellId); + continue; + } + } + + spellInfo.AttributesCu |= (SpellCustomAttributes)attributes; + ++count; + } while (result.NextRow()); + + Log.outInfo(LogFilter.ServerLoading, "Loaded {0} spell custom attributes from DB in {1} ms", count, Time.GetMSTimeDiffToNow(oldMSTime2)); + } + + List talentSpells = new List(); + foreach (var talentInfo in CliDB.TalentStorage.Values) + talentSpells.Add(talentInfo.SpellID); + + foreach (var spellInfo in mSpellInfoMap.Values) + { + foreach (SpellEffectInfo effect in spellInfo.GetEffectsForDifficulty(Difficulty.None)) + { + if (effect == null) + continue; + + switch (effect.ApplyAuraName) + { + case AuraType.PeriodicHeal: + case AuraType.PeriodicDamage: + case AuraType.PeriodicDamagePercent: + case AuraType.PeriodicLeech: + case AuraType.PeriodicManaLeech: + case AuraType.PeriodicHealthFunnel: + case AuraType.PeriodicEnergize: + case AuraType.ObsModHealth: + case AuraType.ObsModPower: + case AuraType.PowerBurn: + spellInfo.AttributesCu |= SpellCustomAttributes.NoInitialThreat; + break; + } + + switch (effect.Effect) + { + case SpellEffectName.SchoolDamage: + case SpellEffectName.WeaponDamage: + case SpellEffectName.WeaponDamageNoschool: + case SpellEffectName.NormalizedWeaponDmg: + case SpellEffectName.WeaponPercentDamage: + case SpellEffectName.Heal: + spellInfo.AttributesCu |= SpellCustomAttributes.DirectDamage; + break; + case SpellEffectName.PowerDrain: + case SpellEffectName.PowerBurn: + case SpellEffectName.HealMaxHealth: + case SpellEffectName.HealthLeech: + case SpellEffectName.HealPct: + case SpellEffectName.EnergizePct: + case SpellEffectName.Energize: + case SpellEffectName.HealMechanical: + spellInfo.AttributesCu |= SpellCustomAttributes.NoInitialThreat; + break; + case SpellEffectName.Charge: + case SpellEffectName.ChargeDest: + case SpellEffectName.Jump: + case SpellEffectName.JumpDest: + case SpellEffectName.LeapBack: + spellInfo.AttributesCu |= SpellCustomAttributes.Charge; + break; + case SpellEffectName.Pickpocket: + spellInfo.AttributesCu |= SpellCustomAttributes.PickPocket; + break; + case SpellEffectName.EnchantItem: + case SpellEffectName.EnchantItemTemporary: + case SpellEffectName.EnchantItemPrismatic: + case SpellEffectName.EnchantHeldItem: + { + // only enchanting profession enchantments procs can stack + if (IsPartOfSkillLine(SkillType.Enchanting, spellInfo.Id)) + { + uint enchantId = (uint)effect.MiscValue; + var enchant = CliDB.SpellItemEnchantmentStorage.LookupByKey(enchantId); + for (var s = 0; s < ItemConst.MaxItemEnchantmentEffects; ++s) + { + if (enchant.Effect[s] != ItemEnchantmentType.CombatSpell) + continue; + + SpellInfo procInfo = GetSpellInfo(enchant.EffectSpellID[s]); + if (procInfo == null) + continue; + + // if proced directly from enchantment, not via proc aura + // NOTE: Enchant Weapon - Blade Ward also has proc aura spell and is proced directly + // however its not expected to stack so this check is good + if (procInfo.HasAura(Difficulty.None, AuraType.ProcTriggerSpell)) + continue; + + procInfo.AttributesCu |= SpellCustomAttributes.EnchantProc; + } + } + break; + } + } + } + + if (!spellInfo._IsPositiveEffect(0, false)) + spellInfo.AttributesCu |= SpellCustomAttributes.NegativeEff0; + + if (!spellInfo._IsPositiveEffect(1, false)) + spellInfo.AttributesCu |= SpellCustomAttributes.NegativeEff1; + + if (!spellInfo._IsPositiveEffect(2, false)) + spellInfo.AttributesCu |= SpellCustomAttributes.NegativeEff2; + + if (spellInfo.GetSpellVisual() == 3879) + spellInfo.AttributesCu |= SpellCustomAttributes.ConeBack; + + if (talentSpells.Contains(spellInfo.Id)) + spellInfo.AttributesCu |= SpellCustomAttributes.IsTalent; + + spellInfo._InitializeExplicitTargetMask(); + } + + Log.outInfo(LogFilter.ServerLoading, "Loaded spell custom attributes in {0} ms", Time.GetMSTimeDiffToNow(oldMSTime)); + } + + public void LoadSpellInfoCorrections() + { + uint oldMSTime = Time.GetMSTime(); + + foreach (var spellInfo in mSpellInfoMap.Values) + { + foreach (SpellEffectInfo effect in spellInfo.GetEffectsForDifficulty(Difficulty.None)) + { + if (effect == null) + continue; + + switch (effect.Effect) + { + case SpellEffectName.Charge: + case SpellEffectName.ChargeDest: + case SpellEffectName.Jump: + case SpellEffectName.JumpDest: + case SpellEffectName.LeapBack: + if (spellInfo.Speed == 0 && spellInfo.SpellFamilyName == 0) + spellInfo.Speed = MotionMaster.SPEED_CHARGE; + break; + } + } + + if (spellInfo.ActiveIconFileDataId == 135754) // flight + spellInfo.Attributes |= SpellAttr0.Passive; + + switch (spellInfo.Id) + { + case 63026: // Summon Aspirant Test NPC (HACK: Target shouldn't be changed) + case 63137: // Summon Valiant Test (HACK: Target shouldn't be changed; summon position should be untied from spell destination) + spellInfo.GetEffect(0).TargetA = new SpellImplicitTargetInfo(Targets.DestDb); + break; + case 42436: // Drink! (Brewfest) + spellInfo.GetEffect(0).TargetA = new SpellImplicitTargetInfo(Targets.UnitAny); + break; + case 52611: // Summon Skeletons + case 52612: // Summon Skeletons + spellInfo.GetEffect(0).MiscValueB = 64; + break; + case 40244: // Simon Game Visual + case 40245: // Simon Game Visual + case 40246: // Simon Game Visual + case 40247: // Simon Game Visual + case 42835: // Spout, remove damage effect, only anim is needed + spellInfo.GetEffect(0).Effect = 0; + break; + case 30657: // Quake + spellInfo.GetEffect(0).TriggerSpell = 30571; + break; + case 30541: // Blaze (needs conditions entry) + spellInfo.GetEffect(0).TargetA = new SpellImplicitTargetInfo(Targets.UnitEnemy); + spellInfo.GetEffect(0).TargetB = new SpellImplicitTargetInfo(); + break; + case 63665: // Charge (Argent Tournament emote on riders) + case 31298: // Sleep (needs target selection script) + case 51904: // Summon Ghouls On Scarlet Crusade (this should use conditions table, script for this spell needs to be fixed) + case 68933: // Wrath of Air Totem rank 2 (Aura) + case 29200: // Purify Helboar Meat + spellInfo.GetEffect(0).TargetA = new SpellImplicitTargetInfo(Targets.UnitCaster); + spellInfo.GetEffect(0).TargetB = new SpellImplicitTargetInfo(); + break; + case 31344: // Howl of Azgalor + spellInfo.GetEffect(0).RadiusEntry = CliDB.SpellRadiusStorage.LookupByKey(EffectRadiusIndex.Yards100); // 100yards instead of 50000?! + break; + case 42818: // Headless Horseman - Wisp Flight Port + case 42821: // Headless Horseman - Wisp Flight Missile + spellInfo.RangeEntry = CliDB.SpellRangeStorage.LookupByKey(6); // 100 yards + break; + case 36350: //They Must Burn Bomb Aura (self) + spellInfo.GetEffect(0).TriggerSpell = 36325; // They Must Burn Bomb Drop (DND) + break; + case 61407: // Energize Cores + case 62136: // Energize Cores + case 54069: // Energize Cores + case 56251: // Energize Cores + spellInfo.GetEffect(0).TargetA = new SpellImplicitTargetInfo(Targets.UnitSrcAreaEntry); + break; + case 50785: // Energize Cores + case 59372: // Energize Cores + spellInfo.GetEffect(0).TargetA = new SpellImplicitTargetInfo(Targets.UnitSrcAreaEnemy); + break; + case 63320: // Glyph of Life Tap + // Entries were not updated after spell effect change, we have to do that manually :/ + spellInfo.AttributesEx3 |= SpellAttr3.CanProcWithTriggered; + break; + case 5308: // Execute + spellInfo.AttributesEx3 |= SpellAttr3.CantTriggerProc; + break; + case 31347: // Doom + case 36327: // Shoot Arcane Explosion Arrow + case 39365: // Thundering Storm + case 41071: // Raise Dead (HACK) + case 42442: // Vengeance Landing Cannonfire + case 42611: // Shoot + case 44978: // Wild Magic + case 45001: // Wild Magic + case 45002: // Wild Magic + case 45004: // Wild Magic + case 45006: // Wild Magic + case 45010: // Wild Magic + case 45761: // Shoot Gun + case 45863: // Cosmetic - Incinerate to Random Target + case 48246: // Ball of Flame + case 41635: // Prayer of Mending + case 44869: // Spectral Blast + case 45027: // Revitalize + case 45976: // Muru Portal Channel + case 52124: // Sky Darkener Assault + case 52479: // Gift of the Harvester + case 61588: // Blazing Harpoon + case 55479: // Force Obedience + case 28560: // Summon Blizzard (Sapphiron) + case 53096: // Quetz'lun's Judgment + case 70743: // AoD Special + case 70614: // AoD Special - Vegard + case 4020: // Safirdrang's Chill + case 52438: // Summon Skittering Swarmer (Force Cast) + case 52449: // Summon Skittering Infector (Force Cast) + case 53609: // Summon Anub'ar Assassin (Force Cast) + case 53457: // Summon Impale Trigger (AoE) + spellInfo.MaxAffectedTargets = 1; + break; + case 36384: // Skartax Purple Beam + spellInfo.MaxAffectedTargets = 2; + break; + case 28542: // Life Drain - Sapphiron + case 29213: // Curse of the Plaguebringer - Noth + case 29576: // Multi-Shot + case 37790: // Spread Shot + case 39992: // Needle Spine + case 40816: // Saber Lash + case 41303: // Soul Drain + case 41376: // Spite + case 45248: // Shadow Blades + case 46771: // Flame Sear + case 66588: // Flaming Spear + spellInfo.MaxAffectedTargets = 3; + break; + case 38310: // Multi-Shot + case 53385: // Divine Storm (Damage) + spellInfo.MaxAffectedTargets = 4; + break; + case 42005: // Bloodboil + case 38296: // Spitfire Totem + case 37676: // Insidious Whisper + case 46008: // Negative Energy + case 45641: // Fire Bloom + case 55665: // Life Drain - Sapphiron (H) + case 28796: // Poison Bolt Volly - Faerlina + spellInfo.MaxAffectedTargets = 5; + break; + case 54835: // Curse of the Plaguebringer - Noth (H) + spellInfo.MaxAffectedTargets = 8; + break; + case 40827: // Sinful Beam + case 40859: // Sinister Beam + case 40860: // Vile Beam + case 40861: // Wicked Beam + case 54098: // Poison Bolt Volly - Faerlina (H) + spellInfo.MaxAffectedTargets = 10; + break; + case 50312: // Unholy Frenzy + spellInfo.MaxAffectedTargets = 15; + break; + case 33711: // Murmur's Touch + case 38794: + spellInfo.MaxAffectedTargets = 1; + spellInfo.GetEffect(0).TriggerSpell = 33760; + break; + case 17941: // Shadow Trance + case 22008: // Netherwind Focus + case 34477: // Misdirection + case 48108: // Hot Streak + case 51124: // Killing Machine + case 57761: // Fireball! + case 64823: // Item - Druid T8 Balance 4P Bonus + case 88819: // Daybreak + spellInfo.ProcCharges = 1; + break; + case 44544: // Fingers of Frost + spellInfo.GetEffect(0).SpellClassMask = new FlagArray128(685904631, 1151048, 0, 0); + break; + case 53257: // Cobra Strikes + spellInfo.ProcCharges = 2; + spellInfo.StackAmount = 0; + break; + case 28200: // Ascendance (Talisman of Ascendance trinket) + spellInfo.ProcCharges = 6; + break; + case 37408: // Oscillation Field + spellInfo.AttributesEx3 |= SpellAttr3.StackForDiffCasters; + break; + case 51852: // The Eye of Acherus (no spawn in phase 2 in db) + spellInfo.GetEffect(0).MiscValue |= 1; + break; + case 51912: // Crafty's Ultra-Advanced Proto-Typical Shortening Blaster + spellInfo.GetEffect(0).ApplyAuraPeriod = 3000; + break; + case 30421: // Nether Portal - Perseverence + spellInfo.GetEffect(2).BasePoints += 30000; + break; + case 41913: // Parasitic Shadowfiend Passive + spellInfo.GetEffect(0).ApplyAuraName = AuraType.Dummy; // proc debuff, and summon infinite fiends + break; + case 27892: // To Anchor 1 + case 27928: // To Anchor 1 + case 27935: // To Anchor 1 + case 27915: // Anchor to Skulls + case 27931: // Anchor to Skulls + case 27937: // Anchor to Skulls + spellInfo.RangeEntry = CliDB.SpellRangeStorage.LookupByKey(13); + break; + // target allys instead of enemies, target A is src_caster, spells with effect like that have ally target + // this is the only known exception, probably just wrong data + case 29214: // Wrath of the Plaguebringer + case 54836: // Wrath of the Plaguebringer + spellInfo.GetEffect(0).TargetB = new SpellImplicitTargetInfo(Targets.UnitSrcAreaAlly); + spellInfo.GetEffect(1).TargetB = new SpellImplicitTargetInfo(Targets.UnitSrcAreaAlly); + break; + case 15290: // Vampiric Embrace + spellInfo.AttributesEx3 |= SpellAttr3.NoInitialAggro; + break; + case 8145: // Tremor Totem (instant pulse) + spellInfo.AttributesEx2 |= SpellAttr2.CanTargetNotInLos; + spellInfo.AttributesEx5 |= SpellAttr5.StartPeriodicAtApply; + break; + case 6474: // Earthbind Totem (instant pulse) + spellInfo.AttributesEx5 |= SpellAttr5.StartPeriodicAtApply; + break; + case 70728: // Exploit Weakness (needs target selection script) + case 70840: // Devious Minds (needs target selection script) + spellInfo.GetEffect(0).TargetA = new SpellImplicitTargetInfo(Targets.UnitCaster); + spellInfo.GetEffect(0).TargetB = new SpellImplicitTargetInfo(Targets.UnitPet); + break; + case 45602: // Ride Carpet + spellInfo.GetEffect(0).BasePoints = 0;// force seat 0, vehicle doesn't have the required seat flags for "no seat specified (-1)" + break; + case 61719: // Easter Lay Noblegarden Egg Aura - Interrupt flags copied from aura which this aura is linked with + spellInfo.AuraInterruptFlags = SpellAuraInterruptFlags.Hitbyspell | SpellAuraInterruptFlags.TakeDamage; + break; + case 71838: // Drain Life - Bryntroll Normal + case 71839: // Drain Life - Bryntroll Heroic + spellInfo.AttributesEx2 |= SpellAttr2.CantCrit; + break; + case 56606: // Ride Jokkum + case 61791: // Ride Vehicle (Yogg-Saron) + /// @todo: remove this when basepoints of all Ride Vehicle auras are calculated correctly + spellInfo.GetEffect(0).BasePoints = 1; + break; + case 59630: // Black Magic + spellInfo.Attributes |= SpellAttr0.Passive; + break; + case 48278: // Paralyze + spellInfo.AttributesEx3 |= SpellAttr3.StackForDiffCasters; + break; + case 51798: // Brewfest - Relay Race - Intro - Quest Complete + case 47134: // Quest Complete + //! HACK: This spell break quest complete for alliance and on retail not used °_O + spellInfo.GetEffect(0).Effect = 0; + break; + case 85123: // Siege Cannon (Tol Barad) + spellInfo.GetEffect(0).RadiusEntry = CliDB.SpellRadiusStorage.LookupByKey(EffectRadiusIndex.Yards200); + spellInfo.GetEffect(0).TargetA = new SpellImplicitTargetInfo(Targets.UnitSrcAreaEntry); + break; + case 198300: // Gathering Storms + spellInfo.ProcCharges = 1; // override proc charges, has 0 (unlimited) in db2 + break; + case 42490: // Energized! + case 42492: // Cast Energized + case 43115: // Plague Vial + spellInfo.AttributesEx |= SpellAttr1.NoThreat; + break; + case 29726: // Test Ribbon Pole Channel + spellInfo.InterruptFlags &= ~SpellInterruptFlags.Interrupt;//AURA_INTERRUPT_FLAG_CAST + break; + case 42767: // Sic'em + spellInfo.GetEffect(0).TargetA = new SpellImplicitTargetInfo(Targets.UnitNearbyEntry); + break; + // VIOLET HOLD SPELLS + // + case 54258: // Water Globule (Ichoron) + case 54264: // Water Globule (Ichoron) + case 54265: // Water Globule (Ichoron) + case 54266: // Water Globule (Ichoron) + case 54267: // Water Globule (Ichoron) + // in 3.3.5 there is only one radius in dbc which is 0 yards in this case + // use max radius from 4.3.4 + spellInfo.GetEffect(0).RadiusEntry = CliDB.SpellRadiusStorage.LookupByKey(EffectRadiusIndex.Yards25); + break; + // ENDOF VIOLET HOLD + // + // ULDUAR SPELLS + // + case 62374: // Pursued (Flame Leviathan) + spellInfo.GetEffect(0).RadiusEntry = CliDB.SpellRadiusStorage.LookupByKey(EffectRadiusIndex.Yards50000); // 50000yd + break; + case 63342: // Focused Eyebeam Summon Trigger (Kologarn) + spellInfo.MaxAffectedTargets = 1; + break; + case 65584: // Growth of Nature (Freya) + case 64381: // Strength of the Pack (Auriaya) + spellInfo.AttributesEx3 |= SpellAttr3.StackForDiffCasters; + break; + case 63018: // Searing Light (XT-002) + case 65121: // Searing Light (25m) (XT-002) + case 63024: // Gravity Bomb (XT-002) + case 64234: // Gravity Bomb (25m) (XT-002) + spellInfo.MaxAffectedTargets = 1; + break; + case 62834: // Boom (XT-002) + // This hack is here because we suspect our implementation of spell effect execution on targets + // is done in the wrong order. We suspect that 0 needs to be applied on all targets, + // then 1, etc - instead of applying each effect on target1, then target2, etc. + // The above situation causes the visual for this spell to be bugged, so we remove the instakill + // effect and implement a script hack for that. + spellInfo.GetEffect(1).Effect = 0; + break; + case 64386: // Terrifying Screech (Auriaya) + case 64389: // Sentinel Blast (Auriaya) + case 64678: // Sentinel Blast (Auriaya) + spellInfo.DurationEntry = CliDB.SpellDurationStorage.LookupByKey(28); // 5 seconds, wrong DBC data? + break; + case 64321: // Potent Pheromones (Freya) + // spell should dispel area aura, but doesn't have the attribute + // may be db data bug, or blizz may keep reapplying area auras every update with checking immunity + // that will be clear if we get more spells with problem like this + spellInfo.AttributesEx |= SpellAttr1.DispelAurasOnImmunity; + break; + case 63414: // Spinning Up (Mimiron) + spellInfo.GetEffect(0).TargetB = new SpellImplicitTargetInfo(Targets.UnitCaster); + spellInfo.ChannelInterruptFlags = 0; + break; + case 63036: // Rocket Strike (Mimiron) + spellInfo.Speed = 0; + break; + case 64668: // Magnetic Field (Mimiron) + spellInfo.Mechanic = Mechanics.None; + break; + case 64468: // Empowering Shadows (Yogg-Saron) + case 64486: // Empowering Shadows (Yogg-Saron) + spellInfo.MaxAffectedTargets = 3; // same for both modes? + break; + case 62301: // Cosmic Smash (Algalon the Observer) + spellInfo.MaxAffectedTargets = 1; + break; + case 64598: // Cosmic Smash (Algalon the Observer) + spellInfo.MaxAffectedTargets = 3; + break; + case 62293: // Cosmic Smash (Algalon the Observer) + spellInfo.GetEffect(0).TargetB = new SpellImplicitTargetInfo(Targets.DestCaster); + break; + case 62311: // Cosmic Smash (Algalon the Observer) + case 64596: // Cosmic Smash (Algalon the Observer) + spellInfo.RangeEntry = CliDB.SpellRangeStorage.LookupByKey(6); // 100yd + break; + case 64014: // Expedition Base Camp Teleport + case 64024: // Conservatory Teleport + case 64025: // Halls of Invention Teleport + case 64028: // Colossal Forge Teleport + case 64029: // Shattered Walkway Teleport + case 64030: // Antechamber Teleport + case 64031: // Scrapyard Teleport + case 64032: // Formation Grounds Teleport + case 65042: // Prison of Yogg-Saron Teleport + spellInfo.GetEffect(0).TargetA = new SpellImplicitTargetInfo(Targets.DestDb); + break; + // ENDOF ULDUAR SPELLS + // + // TRIAL OF THE CRUSADER SPELLS + // + case 66258: // Infernal Eruption + // increase duration from 15 to 18 seconds because caster is already + // unsummoned when spell missile hits the ground so nothing happen in result + spellInfo.DurationEntry = CliDB.SpellDurationStorage.LookupByKey(85); + break; + // ENDOF TRIAL OF THE CRUSADER SPELLS + // + // ICECROWN CITADEL SPELLS + // + // THESE SPELLS ARE WORKING CORRECTLY EVEN WITHOUT THIS HACK + // THE ONLY REASON ITS HERE IS THAT CURRENT GRID SYSTEM + // DOES NOT ALLOW FAR OBJECT SELECTION (dist > 333) + case 70781: // Light's Hammer Teleport + case 70856: // Oratory of the Damned Teleport + case 70857: // Rampart of Skulls Teleport + case 70858: // Deathbringer's Rise Teleport + case 70859: // Upper Spire Teleport + case 70860: // Frozen Throne Teleport + case 70861: // Sindragosa's Lair Teleport + spellInfo.GetEffect(0).TargetA = new SpellImplicitTargetInfo(Targets.DestDb); + break; + case 71169: // Shadow's Fate + spellInfo.AttributesEx3 |= SpellAttr3.StackForDiffCasters; + break; + case 72347: // Lock Players and Tap Chest + spellInfo.AttributesEx3 &= ~SpellAttr3.NoInitialAggro; + break; + case 72723: // Resistant Skin (Deathbringer Saurfang adds) + // this spell initially granted Shadow damage immunity, however it was removed but the data was left in client + spellInfo.GetEffect(2).Effect = 0; + break; + case 70460: // Coldflame Jets (Traps after Saurfang) + spellInfo.DurationEntry = CliDB.SpellDurationStorage.LookupByKey(1); // 10 seconds + break; + case 71412: // Green Ooze Summon (Professor Putricide) + case 71415: // Orange Ooze Summon (Professor Putricide) + spellInfo.GetEffect(0).TargetA = new SpellImplicitTargetInfo(Targets.UnitAny); + break; + case 71159: // Awaken Plagued Zombies + spellInfo.DurationEntry = CliDB.SpellDurationStorage.LookupByKey(21); + break; + case 70530: // Volatile Ooze Beam Protection (Professor Putricide) + spellInfo.GetEffect(0).Effect = SpellEffectName.ApplyAura; // for an unknown reason this was SPELL_EFFECT_APPLY_AREA_AURA_RAID + break; + // THIS IS HERE BECAUSE COOLDOWN ON CREATURE PROCS IS NOT IMPLEMENTED + case 71604: // Mutated Strength (Professor Putricide) + spellInfo.GetEffect(1).Effect = 0; + break; + case 70911: // Unbound Plague (Professor Putricide) (needs target selection script) + spellInfo.GetEffect(0).TargetB = new SpellImplicitTargetInfo(Targets.UnitEnemy); + break; + case 71708: // Empowered Flare (Blood Prince Council) + spellInfo.AttributesEx3 |= SpellAttr3.NoDoneBonus; + break; + case 71266: // Swarming Shadows + spellInfo.RequiredAreasID = 0; // originally, these require area 4522, which is... outside of Icecrown Citadel + break; + case 70602: // Corruption + spellInfo.AttributesEx3 |= SpellAttr3.StackForDiffCasters; + break; + case 70715: // Column of Frost (visual marker) + spellInfo.DurationEntry = CliDB.SpellDurationStorage.LookupByKey(32); // 6 seconds (missing) + break; + case 71085: // Mana Void (periodic aura) + spellInfo.DurationEntry = CliDB.SpellDurationStorage.LookupByKey(9); // 30 seconds (missing) + break; + case 70936: // Summon Suppressor (needs target selection script) + spellInfo.GetEffect(0).TargetA = new SpellImplicitTargetInfo(Targets.UnitAny); + spellInfo.GetEffect(0).TargetB = new SpellImplicitTargetInfo(); + break; + case 70598: // Sindragosa's Fury + spellInfo.GetEffect(0).TargetA = new SpellImplicitTargetInfo(Targets.DestDest); + break; + case 69846: // Frost Bomb + spellInfo.Speed = 0.0f; // This spell's summon happens instantly + break; + case 71614: // Ice Lock + spellInfo.Mechanic = Mechanics.Stun; + break; + case 72762: // Defile + spellInfo.DurationEntry = CliDB.SpellDurationStorage.LookupByKey(559); // 53 seconds + break; + case 72743: // Defile + spellInfo.DurationEntry = CliDB.SpellDurationStorage.LookupByKey(22); // 45 seconds + break; + case 72754: // Defile + spellInfo.GetEffect(0).RadiusEntry = CliDB.SpellRadiusStorage.LookupByKey(EffectRadiusIndex.Yards200); // 200yd + spellInfo.GetEffect(1).RadiusEntry = CliDB.SpellRadiusStorage.LookupByKey(EffectRadiusIndex.Yards200); // 200yd + break; + case 69198: // Raging Spirit Visual + spellInfo.RangeEntry = CliDB.SpellRangeStorage.LookupByKey(13); // 50000yd + break; + case 73655: // Harvest Soul + spellInfo.AttributesEx3 |= SpellAttr3.NoDoneBonus; + break; + case 73540: // Summon Shadow Trap + spellInfo.DurationEntry = CliDB.SpellDurationStorage.LookupByKey(23); // 90 seconds + break; + case 73530: // Shadow Trap (visual) + spellInfo.DurationEntry = CliDB.SpellDurationStorage.LookupByKey(28); // 5 seconds + break; + case 74302: // Summon Spirit Bomb + spellInfo.MaxAffectedTargets = 2; + break; + case 73579: // Summon Spirit Bomb + spellInfo.GetEffect(0).RadiusEntry = CliDB.SpellRadiusStorage.LookupByKey(EffectRadiusIndex.Yards25); // 25yd + break; + case 72376: // Raise Dead + spellInfo.MaxAffectedTargets = 3; + break; + case 71809: // Jump + spellInfo.RangeEntry = CliDB.SpellRangeStorage.LookupByKey(3); // 20yd + spellInfo.GetEffect(0).RadiusEntry = CliDB.SpellRadiusStorage.LookupByKey(EffectRadiusIndex.Yards25); // 25yd + break; + case 72405: // Broken Frostmourne + spellInfo.GetEffect(1).RadiusEntry = CliDB.SpellRadiusStorage.LookupByKey(EffectRadiusIndex.Yards200); // 200yd + break; + // ENDOF ICECROWN CITADEL SPELLS + // + // RUBY SANCTUM SPELLS + // + case 74799: // Soul Consumption + spellInfo.GetEffect(1).RadiusEntry = CliDB.SpellRadiusStorage.LookupByKey(EffectRadiusIndex.Yards12); + break; + case 75509: // Twilight Mending + spellInfo.AttributesEx6 |= SpellAttr6.CanTargetInvisible; + spellInfo.AttributesEx2 |= SpellAttr2.CanTargetNotInLos; + break; + case 75888: // Awaken Flames + spellInfo.AttributesEx |= SpellAttr1.CantTargetSelf; + break; + // ENDOF RUBY SANCTUM SPELLS + // + // EYE OF ETERNITY SPELLS + // All spells below work even without these changes. The LOS attribute is due to problem + // from collision between maps & gos with active destroyed state. + case 57473: // Arcane Storm bonus explicit visual spell + case 57431: // Summon Static Field + case 56091: // Flame Spike (Wyrmrest Skytalon) + case 56092: // Engulf in Flames (Wyrmrest Skytalon) + case 57090: // Revivify (Wyrmrest Skytalon) + case 57143: // Life Burst (Wyrmrest Skytalon) + spellInfo.AttributesEx2 |= SpellAttr2.CanTargetNotInLos; + break; + // This would never crit on retail and it has attribute for SPELL_ATTR3_NO_DONE_BONUS because is handled from player, + // until someone figures how to make scions not critting without hack and without making them main casters this should stay here. + case 63934: // Arcane Barrage (cast by players and NONMELEEDAMAGELOG with caster Scion of Eternity (original caster)). + spellInfo.AttributesEx2 |= SpellAttr2.CantCrit; + break; + // ENDOF EYE OF ETERNITY SPELLS + // + case 40055: // Introspection + case 40165: // Introspection + case 40166: // Introspection + case 40167: // Introspection + spellInfo.Attributes |= SpellAttr0.Negative1; + break; + // Stonecore spells + case 95284: // Teleport (from entrance to Slabhide) + case 95285: // Teleport (from Slabhide to entrance) + spellInfo.GetEffect(0).TargetB = new SpellImplicitTargetInfo(Targets.DestDb); + break; + // Halls Of Origination spells + // Temple Guardian Anhuur + case 76606: // Disable Beacon Beams L + case 76608: // Disable Beacon Beams R + // Little hack, Increase the radius so it can hit the Cave In Stalkers in the platform. + spellInfo.GetEffect(0).MaxRadiusEntry = CliDB.SpellRadiusStorage.LookupByKey(EffectRadiusIndex.Yards45); + break; + case 75323: // Reverberating Hymn + // Aura is refreshed at 3 seconds, and the tick should happen at the fourth. + spellInfo.AttributesEx8 |= SpellAttr8.DontResetPeriodicTimer; + break; + case 24314: // Threatening Gaze + spellInfo.AuraInterruptFlags |= SpellAuraInterruptFlags.Cast | SpellAuraInterruptFlags.Move | SpellAuraInterruptFlags.Jump; + break; + case 5420: // Tree of Life (Passive) + spellInfo.Stances = 1 << ((int)ShapeShiftForm.TreeOfLife - 1); + break; + case 49376: // Feral Charge (Cat Form) + spellInfo.AttributesEx3 &= ~SpellAttr3.CantTriggerProc; + break; + case 96942: // Gaze of Occu'thar + spellInfo.AttributesEx &= ~SpellAttr1.Channeled1; + break; + case 75610: // Evolution + spellInfo.MaxAffectedTargets = 1; + break; + case 75697: // Evolution + spellInfo.GetEffect(0).TargetA = new SpellImplicitTargetInfo(Targets.UnitSrcAreaEntry); + break; + // ISLE OF CONQUEST SPELLS + // + case 66551: // Teleport + spellInfo.RangeEntry = CliDB.SpellRangeStorage.LookupByKey(13); // 50000yd + break; + // ENDOF ISLE OF CONQUEST SPELLS + // + case 102445: // Summon Master Li Fei + spellInfo.GetEffect(0).TargetA = new SpellImplicitTargetInfo(Targets.DestDb); + break; + } + } + + SummonPropertiesRecord properties = CliDB.SummonPropertiesStorage.LookupByKey(121); + if (properties != null) + properties.Type = SummonType.Totem; + properties = CliDB.SummonPropertiesStorage.LookupByKey(647); // 52893 + if (properties != null) + properties.Type = SummonType.Totem; + properties = CliDB.SummonPropertiesStorage.LookupByKey(628); + if (properties != null) // Hungry Plaguehound + properties.Category = SummonCategory.Pet; + + Log.outInfo(LogFilter.ServerLoading, "Loaded SpellInfo corrections in {0} ms", Time.GetMSTimeDiffToNow(oldMSTime)); + } + + public void LoadPetFamilySpellsStore() + { + Dictionary levelsBySpell = new Dictionary(); + foreach (SpellLevelsRecord levels in CliDB.SpellLevelsStorage.Values) + if (levels.DifficultyID == 0) + levelsBySpell[levels.SpellID] = levels; + + foreach (var skillLine in CliDB.SkillLineAbilityStorage.Values) + { + SpellInfo spellInfo = GetSpellInfo(skillLine.SpellID); + if (spellInfo == null) + continue; + + var levels = levelsBySpell.LookupByKey(skillLine.SpellID); + if (levels != null && levels.SpellLevel != 0) + continue; + + if (spellInfo.IsPassive()) + { + foreach (CreatureFamilyRecord cFamily in CliDB.CreatureFamilyStorage.Values) + { + if (skillLine.SkillLine != cFamily.SkillLine[0] && skillLine.SkillLine != cFamily.SkillLine[1]) + continue; + + if (skillLine.AcquireMethod != AbilytyLearnType.OnSkillLearn) + continue; + + Global.SpellMgr.PetFamilySpellsStorage.Add(cFamily.Id, spellInfo.Id); + } + } + } + } + #endregion + + // SpellInfo object management + public SpellInfo GetSpellInfo(uint spellId) + { + return mSpellInfoMap.LookupByKey(spellId); + } + public bool HasSpellInfo(uint spellId) + { + return mSpellInfoMap.ContainsKey(spellId); + } + public Dictionary GetSpellInfoStorage() + { + return mSpellInfoMap; + } + + //Extra Shit + public SpellEffectHandler GetSpellEffectHandler(SpellEffectName eff) + { + if (!SpellEffectsHandlers.ContainsKey(eff)) + { + Log.outError(LogFilter.Spells, "No defined handler for SpellEffect {0}", eff); + return SpellEffectsHandlers[SpellEffectName.Null]; + } + + return SpellEffectsHandlers[eff]; + } + + public AuraEffectHandler GetAuraEffectHandler(AuraType type) + { + if (!AuraEffectHandlers.ContainsKey(type)) + { + Log.outError(LogFilter.Spells, "No defined handler for AuraEffect {0}", type); + return AuraEffectHandlers[AuraType.None]; + } + + return AuraEffectHandlers[type]; + } + + public SkillRangeType GetSkillRangeType(SkillRaceClassInfoRecord rcEntry) + { + SkillLineRecord skill = CliDB.SkillLineStorage.LookupByKey(rcEntry.SkillID); + if (skill == null) + return SkillRangeType.None; + + if (Global.ObjectMgr.GetSkillTier(rcEntry.SkillTierID) != null) + return SkillRangeType.Rank; + + if (rcEntry.SkillID == (uint)SkillType.Runeforging) + return SkillRangeType.Mono; + + switch (skill.CategoryID) + { + case SkillCategory.Armor: + return SkillRangeType.Mono; + case SkillCategory.Languages: + return SkillRangeType.Language; + } + return SkillRangeType.Level; + } + + public bool IsPrimaryProfessionSkill(uint skill) + { + SkillLineRecord pSkill = CliDB.SkillLineStorage.LookupByKey(skill); + return pSkill != null && pSkill.CategoryID == SkillCategory.Profession; + } + + public bool IsWeaponSkill(uint skill) + { + var pSkill = CliDB.SkillLineStorage.LookupByKey(skill); + return pSkill != null && pSkill.CategoryID == SkillCategory.Weapon; + } + + public bool IsProfessionOrRidingSkill(uint skill) + { + return IsProfessionSkill(skill) || skill == (uint)SkillType.Riding; + } + + public bool IsProfessionSkill(uint skill) + { + return IsPrimaryProfessionSkill(skill) || skill == (uint)SkillType.Fishing || skill == (uint)SkillType.Cooking || skill == (uint)SkillType.FirstAid; + } + + public bool IsPartOfSkillLine(SkillType skillId, uint spellId) + { + var skillBounds = GetSkillLineAbilityMapBounds(spellId); + if (skillBounds != null) + { + foreach (var skill in skillBounds) + if (skill.SkillLine == (uint)skillId) + return true; + } + + return false; + } + + public SpellSchools GetFirstSchoolInMask(SpellSchoolMask mask) + { + for (int i = 0; i < (int)SpellSchools.Max; ++i) + if (Convert.ToBoolean((int)mask & (1 << i))) + return (SpellSchools)i; + + return SpellSchools.Normal; + } + + #region Fields + //private: + Dictionary mSpellChains = new Dictionary(); + MultiMap mSpellsReqSpell = new MultiMap(); + MultiMap mSpellReq = new MultiMap(); + Dictionary mSpellLearnSkills = new Dictionary(); + MultiMap mSpellLearnSpells = new MultiMap(); + Dictionary, SpellTargetPosition> mSpellTargetPositions = new Dictionary, SpellTargetPosition>(); + MultiMap mSpellSpellGroup = new MultiMap(); + MultiMap mSpellGroupSpell = new MultiMap(); + Dictionary mSpellGroupStack = new Dictionary(); + Dictionary mSpellProcEventMap = new Dictionary(); + Dictionary mSpellProcMap = new Dictionary(); + Dictionary mSpellThreatMap = new Dictionary(); + Dictionary mSpellPetAuraMap = new Dictionary(); + MultiMap mSpellLinkedMap = new MultiMap(); + Dictionary mSpellEnchantProcEventMap = new Dictionary(); + Dictionary mEnchantCustomAttr = new Dictionary(); + MultiMap mSpellAreaMap = new MultiMap(); + MultiMap mSpellAreaForQuestMap = new MultiMap(); + MultiMap mSpellAreaForQuestEndMap = new MultiMap(); + MultiMap mSpellAreaForAuraMap = new MultiMap(); + MultiMap mSpellAreaForAreaMap = new MultiMap(); + MultiMap, SpellArea> mSpellAreaForQuestAreaMap = new MultiMap, SpellArea>(); + MultiMap mSkillLineAbilityMap = new MultiMap(); + Dictionary> mPetLevelupSpellMap = new Dictionary>(); + Dictionary mPetDefaultSpellsMap = new Dictionary(); // only spells not listed in related mPetLevelupSpellMap entry + Dictionary mSpellInfoMap = new Dictionary(); + + public delegate void AuraEffectHandler(AuraEffect effect, AuraApplication aurApp, AuraEffectHandleModes mode, bool apply); + Dictionary AuraEffectHandlers = new Dictionary(); + public delegate void SpellEffectHandler(Spell spell, uint effectIndex); + Dictionary SpellEffectsHandlers = new Dictionary(); + + public MultiMap PetFamilySpellsStorage = new MultiMap(); + #endregion + } + + [AttributeUsage(AttributeTargets.Method, AllowMultiple = true)] + public class AuraEffectHandlerAttribute : Attribute + { + public AuraEffectHandlerAttribute(AuraType type) + { + auraType = type; + } + + public AuraType auraType { get; set; } + } + + [AttributeUsage(AttributeTargets.Method, AllowMultiple = true)] + public class SpellEffectHandlerAttribute : Attribute + { + public SpellEffectHandlerAttribute(SpellEffectName effectName) + { + this.effectName = effectName; + } + + public SpellEffectName effectName { get; set; } + } + + public class SpellInfoLoadHelper + { + public SpellRecord Entry; + + public SpellAuraOptionsRecord AuraOptions; + public SpellAuraRestrictionsRecord AuraRestrictions; + public SpellCastingRequirementsRecord CastingRequirements; + public SpellCategoriesRecord Categories; + public SpellClassOptionsRecord ClassOptions; + public SpellCooldownsRecord Cooldowns; + public SpellEquippedItemsRecord EquippedItems; + public SpellInterruptsRecord Interrupts; + public SpellLevelsRecord Levels; + public SpellMiscRecord Misc; + public SpellReagentsRecord Reagents; + public SpellScalingRecord Scaling; + public SpellShapeshiftRecord Shapeshift; + public SpellTargetRestrictionsRecord TargetRestrictions; + public SpellTotemsRecord Totems; + } + + public class SpellThreatEntry + { + public int flatMod; // flat threat-value for this Spell - default: 0 + public float pctMod; // threat-multiplier for this Spell - default: 1.0f + public float apPctMod; // Pct of AP that is added as Threat - default: 0.0f + } + + public class SpellProcEntry + { + public SpellSchoolMask SchoolMask { get; set; } // if nonzero - bitmask for matching proc condition based on spell's school + public SpellFamilyNames SpellFamilyName { get; set; } // if nonzero - for matching proc condition based on candidate spell's SpellFamilyName + public FlagArray128 SpellFamilyMask { get; set; } // if nonzero - bitmask for matching proc condition based on candidate spell's SpellFamilyFlags + public ProcFlags ProcFlags { get; set; } // if nonzero - owerwrite procFlags field for given Spell.dbc entry, bitmask for matching proc condition, see enum ProcFlags + public ProcFlagsSpellType SpellTypeMask { get; set; } // if nonzero - bitmask for matching proc condition based on candidate spell's damage/heal effects, see enum ProcFlagsSpellType + public ProcFlagsSpellPhase SpellPhaseMask { get; set; } // if nonzero - bitmask for matching phase of a spellcast on which proc occurs, see enum ProcFlagsSpellPhase + public ProcFlagsHit HitMask { get; set; } // if nonzero - bitmask for matching proc condition based on hit result, see enum ProcFlagsHit + public uint AttributesMask { get; set; } // bitmask, see ProcAttributes + public float ProcsPerMinute { get; set; } // if nonzero - chance to proc is equal to value * aura caster's weapon speed / 60 + public float Chance { get; set; } // if nonzero - owerwrite procChance field for given Spell.dbc entry, defines chance of proc to occur, not used if ProcsPerMinute set + public uint Cooldown { get; set; } // if nonzero - cooldown in secs for aura proc, applied to aura + public uint Charges { get; set; } // if nonzero - owerwrite procCharges field for given Spell.dbc entry, defines how many times proc can occur before aura remove, 0 - infinite + } + + public class SpellProcEventEntry + { + public SpellSchoolMask schoolMask; // if nonzero - bit mask for matching proc condition based on spell candidate's school: Fire=2, Mask=1<<(2-1)=2 + public SpellFamilyNames spellFamilyName; // if nonzero - for matching proc condition based on candidate spell's SpellFamilyNamer value + public FlagArray128 spellFamilyMask; // if nonzero - for matching proc condition based on candidate spell's SpellFamilyFlags (like auras 107 and 108 do) + public uint procFlags; // bitmask for matching proc event + public uint procEx; // proc Extend info (see ProcFlagsEx) + public float ppmRate; // for melee (ranged?) damage spells - proc rate per minute. if zero, falls back to flat chance from Spell.dbc + public float customChance; // Owerride chance (in most cases for debug only) + public uint cooldown; // hidden cooldown used for some spell proc events, applied to _triggered_spell_ + } + + public class PetDefaultSpellsEntry + { + public uint[] spellid = new uint[4]; + } + + public class SpellArea + { + public uint spellId; + public uint areaId; // zone/subzone/or 0 is not limited to zone + public uint questStart; // quest start (quest must be active or rewarded for spell apply) + public uint questEnd; // quest end (quest must not be rewarded for spell apply) + public int auraSpell; // spell aura must be applied for spell apply)if possitive) and it must not be applied in other case + public uint raceMask; // can be applied only to races + public Gender gender; // can be applied only to gender + public uint questStartStatus; // QuestStatus that quest_start must have in order to keep the spell + public uint questEndStatus; // QuestStatus that the quest_end must have in order to keep the spell (if the quest_end's status is different than this, the spell will be dropped) + public bool autocast; // if true then auto applied at area enter, in other case just allowed to cast + + // helpers + public bool IsFitToRequirements(Player player, uint newZone, uint newArea) + { + if (gender != Gender.None) // not in expected gender + if (player == null || gender != player.GetGender()) + return false; + + if (raceMask != 0) // not in expected race + if (player == null || !Convert.ToBoolean(raceMask & player.getRaceMask())) + return false; + + if (areaId != 0) // not in expected zone + if (newZone != areaId && newArea != areaId) + return false; + + if (questStart != 0) // not in expected required quest state + if (player == null || (((1 << (int)player.GetQuestStatus(questStart)) & questStartStatus) == 0)) + return false; + + if (questEnd != 0) // not in expected forbidden quest state + if (player == null || (((1 << (int)player.GetQuestStatus(questEnd)) & questEndStatus) == 0)) + return false; + + if (auraSpell != 0) // not have expected aura + if (player == null || (auraSpell > 0 && !player.HasAura((uint)auraSpell)) || (auraSpell < 0 && player.HasAura((uint)-auraSpell))) + return false; + + if (player) + { + Battleground bg = player.GetBattleground(); + if (bg) + return bg.IsSpellAllowed(spellId, player); + } + + // Extra conditions -- leaving the possibility add extra conditions... + switch (spellId) + { + case 91604: // No fly Zone - Wintergrasp + { + if (!player) + return false; + + BattleField Bf = Global.BattleFieldMgr.GetBattlefieldToZoneId(player.GetZoneId()); + if (Bf == null || Bf.CanFlyIn() || (!player.HasAuraType(AuraType.ModIncreaseMountedFlightSpeed) && !player.HasAuraType(AuraType.Fly))) + return false; + break; + } + case 56618: // Horde Controls Factory Phase Shift + case 56617: // Alliance Controls Factory Phase Shift + { + if (!player) + return false; + + BattleField bf = Global.BattleFieldMgr.GetBattlefieldToZoneId(player.GetZoneId()); + + if (bf == null || bf.GetTypeId() != (int)BattleFieldTypes.WinterGrasp) + return false; + + // team that controls the workshop in the specified area + uint team = bf.GetData(newArea); + + if (team == TeamId.Horde) + return spellId == 56618; + else if (team == TeamId.Alliance) + return spellId == 56617; + break; + } + case 57940: // Essence of Wintergrasp - Northrend + case 58045: // Essence of Wintergrasp - Wintergrasp + { + if (!player) + return false; + + BattleField battlefieldWG = Global.BattleFieldMgr.GetBattlefieldByBattleId(1); + if (battlefieldWG != null) + return battlefieldWG.IsEnabled() && (player.GetTeamId() == battlefieldWG.GetDefenderTeam()) && !battlefieldWG.IsWarTime(); + break; + } + case 74411: // Battleground- Dampening + { + if (!player) + return false; + + BattleField bf = Global.BattleFieldMgr.GetBattlefieldToZoneId(player.GetZoneId()); + if (bf != null) + return bf.IsWarTime(); + break; + } + } + return true; + } + } + + public class PetAura + { + public PetAura() + { + removeOnChangePet = false; + damage = 0; + } + + public PetAura(uint petEntry, uint aura, bool _removeOnChangePet, int _damage) + { + removeOnChangePet = _removeOnChangePet; + damage = _damage; + + auras[petEntry] = aura; + } + + public uint GetAura(uint petEntry) + { + var auraId = auras.LookupByKey(petEntry); + if (auraId != 0) + return auraId; + + auraId = auras.LookupByKey(0); + if (auraId != 0) + return auraId; + + return 0; + } + + public void AddAura(uint petEntry, uint aura) + { + auras[petEntry] = aura; + } + + public bool IsRemovedOnChangePet() + { + return removeOnChangePet; + } + + public int GetDamage() + { + return damage; + } + + Dictionary auras = new Dictionary(); + bool removeOnChangePet; + int damage; + } + + public class SpellEnchantProcEntry + { + public uint customChance; + public float PPMChance; + public ProcFlagsExLegacy procEx; + } + + public class SpellTargetPosition + { + public uint target_mapId; + public float target_X; + public float target_Y; + public float target_Z; + public float target_Orientation; + } +} diff --git a/Game/SupportSystem/SupportManager.cs b/Game/SupportSystem/SupportManager.cs new file mode 100644 index 000000000..4a5517aa0 --- /dev/null +++ b/Game/SupportSystem/SupportManager.cs @@ -0,0 +1,386 @@ +/* + * Copyright (C) 2012-2017 CypherCore + * + * 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, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +using Framework.Constants; +using Framework.Database; +using Game.Chat; +using Game.Entities; +using System.Collections.Generic; +using System.Linq; + +namespace Game.SupportSystem +{ + public class SupportManager : Singleton + { + SupportManager() { } + + public void Initialize() + { + SetSupportSystemStatus(WorldConfig.GetBoolValue(WorldCfg.SupportEnabled)); + SetTicketSystemStatus(WorldConfig.GetBoolValue(WorldCfg.SupportTicketsEnabled)); + SetBugSystemStatus(WorldConfig.GetBoolValue(WorldCfg.SupportBugsEnabled)); + SetComplaintSystemStatus(WorldConfig.GetBoolValue(WorldCfg.SupportComplaintsEnabled)); + SetSuggestionSystemStatus(WorldConfig.GetBoolValue(WorldCfg.SupportSuggestionsEnabled)); + } + + public T GetTicket(uint Id) where T : Ticket + { + switch (typeof(T).Name) + { + case "BugTicket": + return _bugTicketList.LookupByKey(Id) as T; + case "ComplaintTicket": + return _complaintTicketList.LookupByKey(Id) as T; + case "SuggestionTicket": + return _suggestionTicketList.LookupByKey(Id) as T; + } + + return default(T); + } + + public uint GetOpenTicketCount() where T : Ticket + { + switch (typeof(T).Name) + { + case "BugTicket": + return _openBugTicketCount; + case "ComplaintTicket": + return _openComplaintTicketCount; + case "SuggestionTicket": + return _openSuggestionTicketCount; + } + return 0; + } + + public void LoadBugTickets() + { + uint oldMSTime = Time.GetMSTime(); + _bugTicketList.Clear(); + + _lastBugId = 0; + _openBugTicketCount = 0; + + PreparedStatement stmt = DB.Characters.GetPreparedStatement(CharStatements.SEL_GM_BUGS); + SQLResult result = DB.Characters.Query(stmt); + if (result.IsEmpty()) + { + Log.outInfo(LogFilter.ServerLoading, "Loaded 0 GM bugs. DB table `gm_bug` is empty!"); + return; + } + + uint count = 0; + do + { + BugTicket bug = new BugTicket(); + bug.LoadFromDB(result.GetFields()); + + if (!bug.IsClosed()) + ++_openBugTicketCount; + + uint id = bug.GetId(); + if (_lastBugId < id) + _lastBugId = id; + + _bugTicketList[id] = bug; + ++count; + } while (result.NextRow()); + + Log.outInfo(LogFilter.ServerLoading, "Loaded {0} GM bugs in {1} ms", count, Time.GetMSTimeDiffToNow(oldMSTime)); + } + + public void LoadComplaintTickets() + { + uint oldMSTime = Time.GetMSTime(); + _complaintTicketList.Clear(); + + _lastComplaintId = 0; + _openComplaintTicketCount = 0; + + PreparedStatement stmt = DB.Characters.GetPreparedStatement(CharStatements.SEL_GM_COMPLAINTS); + SQLResult result = DB.Characters.Query(stmt); + if (result.IsEmpty()) + { + Log.outInfo(LogFilter.ServerLoading, "Loaded 0 GM complaints. DB table `gm_complaint` is empty!"); + return; + } + + uint count = 0; + PreparedStatement chatLogStmt; + SQLResult chatLogResult; + do + { + ComplaintTicket complaint = new ComplaintTicket(); + complaint.LoadFromDB(result.GetFields()); + + if (!complaint.IsClosed()) + ++_openComplaintTicketCount; + + uint id = complaint.GetId(); + if (_lastComplaintId < id) + _lastComplaintId = id; + + chatLogStmt = DB.Characters.GetPreparedStatement(CharStatements.SEL_GM_COMPLAINT_CHATLINES); + chatLogStmt.AddValue(0, id); + chatLogResult = DB.Characters.Query(stmt); + + if (!chatLogResult.IsEmpty()) + { + do + { + complaint.LoadChatLineFromDB(chatLogResult.GetFields()); + } while (chatLogResult.NextRow()); + } + + _complaintTicketList[id] = complaint; + ++count; + } while (result.NextRow()); + + Log.outInfo(LogFilter.ServerLoading, "Loaded {0} GM complaints in {1} ms", count, Time.GetMSTimeDiffToNow(oldMSTime)); + } + + public void LoadSuggestionTickets() + { + uint oldMSTime = Time.GetMSTime(); + _suggestionTicketList.Clear(); + + _lastSuggestionId = 0; + _openSuggestionTicketCount = 0; + + PreparedStatement stmt = DB.Characters.GetPreparedStatement(CharStatements.SEL_GM_SUGGESTIONS); + SQLResult result = DB.Characters.Query(stmt); + if (result.IsEmpty()) + { + Log.outInfo(LogFilter.ServerLoading, "Loaded 0 GM suggestions. DB table `gm_suggestion` is empty!"); + return; + } + + uint count = 0; + do + { + SuggestionTicket suggestion = new SuggestionTicket(); + suggestion.LoadFromDB(result.GetFields()); + + if (!suggestion.IsClosed()) + ++_openSuggestionTicketCount; + + uint id = suggestion.GetId(); + if (_lastSuggestionId < id) + _lastSuggestionId = id; + + _suggestionTicketList[id] = suggestion; + ++count; + } while (result.NextRow()); + + Log.outInfo(LogFilter.ServerLoading, "Loaded {0} GM suggestions in {1} ms", count, Time.GetMSTimeDiffToNow(oldMSTime)); + } + + public void AddTicket(T ticket)where T : Ticket + { + switch (typeof(T).Name) + { + case "BugTicket": + _bugTicketList[ticket.GetId()] = ticket as BugTicket; + if (!ticket.IsClosed()) + ++_openBugTicketCount; + break; + case "ComplaintTicket": + _complaintTicketList[ticket.GetId()] = ticket as ComplaintTicket; + if (!ticket.IsClosed()) + ++_openComplaintTicketCount; + break; + case "SuggestionTicket": + _suggestionTicketList[ticket.GetId()] = ticket as SuggestionTicket; + if (!ticket.IsClosed()) + ++_openSuggestionTicketCount; + break; + } + + ticket.SaveToDB(); + } + + public void RemoveTicket(uint ticketId) where T : Ticket + { + T ticket = GetTicket(ticketId); + if (ticket != null) + { + ticket.DeleteFromDB(); + + switch (typeof(T).Name) + { + case "BugTicket": + _bugTicketList.Remove(ticketId); + break; + case "ComplaintTicket": + _complaintTicketList.Remove(ticketId); + break; + case "SuggestionTicket": + _suggestionTicketList.Remove(ticketId); + break; + } + } + } + + public void CloseTicket(uint ticketId, ObjectGuid closedBy) where T : Ticket + { + T ticket = GetTicket(ticketId); + if (ticket != null) + { + ticket.SetClosedBy(closedBy); + if (!closedBy.IsEmpty()) + { + switch (typeof(T).Name) + { + case "BugTicket": + --_openBugTicketCount; + break; + case "ComplaintTicket": + --_openComplaintTicketCount; + break; + case "SuggestionTicket": + --_openSuggestionTicketCount; + break; + } + } + ticket.SaveToDB(); + } + } + + public void ResetTickets() where T : Ticket + { + PreparedStatement stmt; + switch (typeof(T).Name) + { + case "BugTicket": + _bugTicketList.Clear(); + + _lastBugId = 0; + + stmt = DB.Characters.GetPreparedStatement(CharStatements.DEL_ALL_GM_BUGS); + DB.Characters.Execute(stmt); + break; + case "ComplaintTicket": + _complaintTicketList.Clear(); + + _lastComplaintId = 0; + + SQLTransaction trans = new SQLTransaction(); + trans.Append(DB.Characters.GetPreparedStatement(CharStatements.DEL_ALL_GM_COMPLAINTS)); + trans.Append(DB.Characters.GetPreparedStatement(CharStatements.DEL_ALL_GM_COMPLAINT_CHATLOGS)); + DB.Characters.CommitTransaction(trans); + break; + case "SuggestionTicket": + _suggestionTicketList.Clear(); + + _lastSuggestionId = 0; + + stmt = DB.Characters.GetPreparedStatement(CharStatements.DEL_ALL_GM_SUGGESTIONS); + DB.Characters.Execute(stmt); + break; + } + + + } + + public void ShowList(CommandHandler handler) where T : Ticket + { + handler.SendSysMessage(CypherStrings.CommandTicketshowlist); + switch (typeof(T).Name) + { + case "BugTicket": + foreach (var ticket in _bugTicketList.Values) + if (!ticket.IsClosed()) + handler.SendSysMessage(ticket.FormatViewMessageString(handler)); + break; + case "ComplaintTicket": + foreach (var ticket in _complaintTicketList.Values) + if (!ticket.IsClosed()) + handler.SendSysMessage(ticket.FormatViewMessageString(handler)); + break; + case "SuggestionTicket": + foreach (var ticket in _suggestionTicketList.Values) + if (!ticket.IsClosed()) + handler.SendSysMessage(ticket.FormatViewMessageString(handler)); + break; + } + } + + public void ShowClosedList(CommandHandler handler) where T : Ticket + { + handler.SendSysMessage(CypherStrings.CommandTicketshowclosedlist); + switch (typeof(T).Name) + { + case "BugTicket": + foreach (var ticket in _bugTicketList.Values) + if (ticket.IsClosed()) + handler.SendSysMessage(ticket.FormatViewMessageString(handler)); + break; + case "ComplaintTicket": + foreach (var ticket in _complaintTicketList.Values) + if (ticket.IsClosed()) + handler.SendSysMessage(ticket.FormatViewMessageString(handler)); + break; + case "SuggestionTicket": + foreach (var ticket in _suggestionTicketList.Values) + if (ticket.IsClosed()) + handler.SendSysMessage(ticket.FormatViewMessageString(handler)); + break; + } + } + + long GetAge(ulong t) { return (Time.UnixTime - (long)t) / Time.Day; } + + IEnumerable> GetComplaintsByPlayerGuid(ObjectGuid playerGuid) + { + return _complaintTicketList.Where(ticket => ticket.Value.GetPlayerGuid() == playerGuid); + } + + public bool GetSupportSystemStatus() { return _supportSystemStatus; } + public bool GetTicketSystemStatus() { return _supportSystemStatus && _ticketSystemStatus; } + public bool GetBugSystemStatus() { return _supportSystemStatus && _bugSystemStatus; } + public bool GetComplaintSystemStatus() { return _supportSystemStatus && _complaintSystemStatus; } + public bool GetSuggestionSystemStatus() { return _supportSystemStatus && _suggestionSystemStatus; } + public ulong GetLastChange() { return _lastChange; } + + public void SetSupportSystemStatus(bool status) { _supportSystemStatus = status; } + public void SetTicketSystemStatus(bool status) { _ticketSystemStatus = status; } + public void SetBugSystemStatus(bool status) { _bugSystemStatus = status; } + public void SetComplaintSystemStatus(bool status) { _complaintSystemStatus = status; } + public void SetSuggestionSystemStatus(bool status) { _suggestionSystemStatus = status; } + + public void UpdateLastChange() { _lastChange = (ulong)Time.UnixTime; } + + public uint GenerateBugId() { return ++_lastBugId; } + public uint GenerateComplaintId() { return ++_lastComplaintId; } + public uint GenerateSuggestionId() { return ++_lastSuggestionId; } + + bool _supportSystemStatus; + bool _ticketSystemStatus; + bool _bugSystemStatus; + bool _complaintSystemStatus; + bool _suggestionSystemStatus; + Dictionary _bugTicketList = new Dictionary(); + Dictionary _complaintTicketList = new Dictionary(); + Dictionary _suggestionTicketList = new Dictionary(); + uint _lastBugId; + uint _lastComplaintId; + uint _lastSuggestionId; + uint _openBugTicketCount; + uint _openComplaintTicketCount; + uint _openSuggestionTicketCount; + ulong _lastChange; + } +} diff --git a/Game/SupportSystem/SupportTickets.cs b/Game/SupportSystem/SupportTickets.cs new file mode 100644 index 000000000..972d8ee43 --- /dev/null +++ b/Game/SupportSystem/SupportTickets.cs @@ -0,0 +1,454 @@ +/* + * Copyright (C) 2012-2017 CypherCore + * + * 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, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +using Framework.Constants; +using Framework.Database; +using Framework.GameMath; +using Game.Chat; +using Game.Entities; +using Game.Network.Packets; +using System.Text; + +namespace Game.SupportSystem +{ + public class Ticket + { + protected uint _id; + protected ObjectGuid _playerGuid; + protected uint _mapId; + protected Vector3 _pos; + protected ulong _createTime; + protected ObjectGuid _closedBy; // 0 = Open, -1 = Console, playerGuid = player abandoned ticket, other = GM who closed it. + protected ObjectGuid _assignedTo; + protected string _comment; + + public Ticket() { } + public Ticket(Player player) + { + _createTime = (ulong)Time.UnixTime; + _playerGuid = player.GetGUID(); + } + + public void TeleportTo(Player player) + { + player.TeleportTo(_mapId, _pos.X, _pos.Y, _pos.Z, 0.0f, 0); + } + + public virtual string FormatViewMessageString(CommandHandler handler, bool detailed = false) { return ""; } + public virtual string FormatViewMessageString(CommandHandler handler, string closedName, string assignedToName, string unassignedName, string deletedName) + { + StringBuilder ss = new StringBuilder(); + ss.Append(handler.GetParsedString(CypherStrings.CommandTicketlistguid, _id)); + ss.Append(handler.GetParsedString(CypherStrings.CommandTicketlistname, GetPlayerName())); + + if (!string.IsNullOrEmpty(closedName)) + ss.Append(handler.GetParsedString(CypherStrings.CommandTicketclosed, closedName)); + if (!string.IsNullOrEmpty(assignedToName)) + ss.Append(handler.GetParsedString(CypherStrings.CommandTicketlistassignedto, assignedToName)); + if (!string.IsNullOrEmpty(unassignedName)) + ss.Append(handler.GetParsedString(CypherStrings.CommandTicketlistunassigned, unassignedName)); + if (!string.IsNullOrEmpty(deletedName)) + ss.Append(handler.GetParsedString(CypherStrings.CommandTicketdeleted, deletedName)); + return ss.ToString(); + } + + public bool IsClosed() { return !_closedBy.IsEmpty(); } + bool IsFromPlayer(ObjectGuid guid) { return guid == _playerGuid; } + public bool IsAssigned() { return !_assignedTo.IsEmpty(); } + public bool IsAssignedTo(ObjectGuid guid) { return guid == _assignedTo; } + public bool IsAssignedNotTo(ObjectGuid guid) { return IsAssigned() && !IsAssignedTo(guid); } + + public uint GetId() { return _id; } + public ObjectGuid GetPlayerGuid() { return _playerGuid; } + public Player GetPlayer() { return Global.ObjAccessor.FindConnectedPlayer(_playerGuid); } + public string GetPlayerName() + { + string name = ""; + if (!_playerGuid.IsEmpty()) + ObjectManager.GetPlayerNameByGUID(_playerGuid, out name); + + return name; + } + public Player GetAssignedPlayer() { return Global.ObjAccessor.FindConnectedPlayer(_assignedTo); } + public ObjectGuid GetAssignedToGUID() { return _assignedTo; } + public string GetAssignedToName() + { + string name; + if (!_assignedTo.IsEmpty()) + if (ObjectManager.GetPlayerNameByGUID(_assignedTo, out name)) + return name; + + return ""; + } + string GetComment() { return _comment; } + + public virtual void SetAssignedTo(ObjectGuid guid, bool IsAdmin = false) { _assignedTo = guid; } + public virtual void SetUnassigned() { _assignedTo.Clear(); } + public void SetClosedBy(ObjectGuid value) { _closedBy = value; } + public void SetComment(string comment) { _comment = comment; } + public void SetPosition(uint mapId, Vector3 pos) + { + _mapId = mapId; + _pos = pos; + } + + public virtual void LoadFromDB(SQLFields fields) { } + public virtual void SaveToDB() { } + public virtual void DeleteFromDB() { } + } + + public class BugTicket : Ticket + { + float _facing; + string _note; + + + public BugTicket() : base() + { + _note = ""; + } + + public BugTicket(Player player) : base(player) + { + _note = ""; + _id = Global.SupportMgr.GenerateBugId(); + } + + public override void LoadFromDB(SQLFields fields) + { + byte idx = 0; + _id = fields.Read(idx); + _playerGuid = ObjectGuid.Create(HighGuid.Player, fields.Read(++idx)); + _note = fields.Read(++idx); + _createTime = fields.Read(++idx); + _mapId = fields.Read(++idx); + _pos = new Vector3(fields.Read(++idx), fields.Read(++idx), fields.Read(++idx)); + _facing = fields.Read(++idx); + + long closedBy = fields.Read(++idx); + if (closedBy == 0) + _closedBy = ObjectGuid.Empty; + else if (closedBy < 0) + _closedBy.SetRawValue(0, (ulong)closedBy); + else + _closedBy = ObjectGuid.Create(HighGuid.Player, (ulong)closedBy); + + ulong assignedTo = fields.Read(++idx); + if (assignedTo == 0) + _assignedTo = ObjectGuid.Empty; + else + _assignedTo = ObjectGuid.Create(HighGuid.Player, assignedTo); + + _comment = fields.Read(++idx); + } + + public override void SaveToDB() + { + byte idx = 0; + PreparedStatement stmt = DB.Characters.GetPreparedStatement(CharStatements.REP_GM_BUG); + stmt.AddValue(idx, _id); + stmt.AddValue(++idx, _playerGuid.GetCounter()); + stmt.AddValue(++idx, _note); + stmt.AddValue(++idx, _mapId); + stmt.AddValue(++idx, _pos.X); + stmt.AddValue(++idx, _pos.Y); + stmt.AddValue(++idx, _pos.Z); + stmt.AddValue(++idx, _facing); + stmt.AddValue(++idx, _closedBy.GetCounter()); + stmt.AddValue(++idx, _assignedTo.GetCounter()); + stmt.AddValue(++idx, _comment); + + DB.Characters.Execute(stmt); + } + + public override void DeleteFromDB() + { + PreparedStatement stmt = DB.Characters.GetPreparedStatement(CharStatements.DEL_GM_BUG); + stmt.AddValue(0, _id); + DB.Characters.Execute(stmt); + } + + public override string FormatViewMessageString(CommandHandler handler, bool detailed = false) + { + ulong curTime = (ulong)Time.UnixTime; + + StringBuilder ss = new StringBuilder(); + ss.Append(handler.GetParsedString(CypherStrings.CommandTicketlistguid, _id)); + ss.Append(handler.GetParsedString(CypherStrings.CommandTicketlistname, GetPlayerName())); + ss.Append(handler.GetParsedString(CypherStrings.CommandTicketlistagecreate, Time.secsToTimeString(curTime - _createTime, true, false))); + + if (!_assignedTo.IsEmpty()) + ss.Append(handler.GetParsedString(CypherStrings.CommandTicketlistassignedto, GetAssignedToName())); + + if (detailed) + { + ss.Append(handler.GetParsedString(CypherStrings.CommandTicketlistmessage, _note)); + if (!string.IsNullOrEmpty(_comment)) + ss.Append(handler.GetParsedString(CypherStrings.CommandTicketlistcomment, _comment)); + } + return ss.ToString(); + } + + string GetNote() { return _note; } + + public void SetFacing(float facing) { _facing = facing; } + public void SetNote(string note) { _note = note; } + } + + public class ComplaintTicket : Ticket + { + float _facing; + ObjectGuid _targetCharacterGuid; + GMSupportComplaintType _complaintType; + SupportTicketSubmitComplaint.SupportTicketChatLog _chatLog; + string _note; + + public ComplaintTicket() : base() + { + _note = ""; + } + + public ComplaintTicket(Player player) : base(player) + { + _note = ""; + _id = Global.SupportMgr.GenerateComplaintId(); + } + + public override void LoadFromDB(SQLFields fields) + { + byte idx = 0; + _id = fields.Read(idx); + _playerGuid = ObjectGuid.Create(HighGuid.Player, fields.Read(++idx)); + _note = fields.Read(++idx); + _createTime = fields.Read(++idx); + _mapId = fields.Read(++idx); + _pos = new Vector3(fields.Read(++idx), fields.Read(++idx), fields.Read(++idx)); + _facing = fields.Read(++idx); + _targetCharacterGuid = ObjectGuid.Create(HighGuid.Player, fields.Read(++idx)); + _complaintType = (GMSupportComplaintType)fields.Read(++idx); + int reportLineIndex = fields.Read(++idx); + if (reportLineIndex != -1) + _chatLog.ReportLineIndex.Set((uint)reportLineIndex); + + long closedBy = fields.Read(++idx); + if (closedBy == 0) + _closedBy = ObjectGuid.Empty; + else if (closedBy < 0) + _closedBy.SetRawValue(0, (ulong)closedBy); + else + _closedBy = ObjectGuid.Create(HighGuid.Player, (ulong)closedBy); + + ulong assignedTo = fields.Read(++idx); + if (assignedTo == 0) + _assignedTo = ObjectGuid.Empty; + else + _assignedTo = ObjectGuid.Create(HighGuid.Player, assignedTo); + + _comment = fields.Read(++idx); + } + + public void LoadChatLineFromDB(SQLFields fields) + { + _chatLog.Lines.Add(new SupportTicketSubmitComplaint.SupportTicketChatLine(fields.Read(0), fields.Read(1))); + } + + public override void SaveToDB() + { + var trans = new SQLTransaction(); + + byte idx = 0; + PreparedStatement stmt = DB.Characters.GetPreparedStatement(CharStatements.REP_GM_COMPLAINT); + stmt.AddValue(idx, _id); + stmt.AddValue(++idx, _playerGuid.GetCounter()); + stmt.AddValue(++idx, _note); + stmt.AddValue(++idx, _mapId); + stmt.AddValue(++idx, _pos.X); + stmt.AddValue(++idx, _pos.Y); + stmt.AddValue(++idx, _pos.Z); + stmt.AddValue(++idx, _facing); + stmt.AddValue(++idx, _targetCharacterGuid.GetCounter()); + stmt.AddValue(++idx, _complaintType); + if (_chatLog.ReportLineIndex.HasValue) + stmt.AddValue(++idx, _chatLog.ReportLineIndex.Value); + else + stmt.AddValue(++idx, -1); // empty ReportLineIndex + stmt.AddValue(++idx, _closedBy.GetCounter()); + stmt.AddValue(++idx, _assignedTo.GetCounter()); + stmt.AddValue(++idx, _comment); + trans.Append(stmt); + + uint lineIndex = 0; + foreach (var c in _chatLog.Lines) + { + idx = 0; + stmt = DB.Characters.GetPreparedStatement(CharStatements.INS_GM_COMPLAINT_CHATLINE); + stmt.AddValue(idx, _id); + stmt.AddValue(++idx, lineIndex); + stmt.AddValue(++idx, c.Timestamp); + stmt.AddValue(++idx, c.Text); + + trans.Append(stmt); + ++lineIndex; + } + + DB.Characters.CommitTransaction(trans); + } + + public override void DeleteFromDB() + { + PreparedStatement stmt = DB.Characters.GetPreparedStatement(CharStatements.DEL_GM_COMPLAINT); + stmt.AddValue(0, _id); + DB.Characters.Execute(stmt); + + stmt = DB.Characters.GetPreparedStatement(CharStatements.DEL_GM_COMPLAINT_CHATLOG); + stmt.AddValue(0, _id); + DB.Characters.Execute(stmt); + } + + public override string FormatViewMessageString(CommandHandler handler, bool detailed = false) + { + ulong curTime = (ulong)Time.UnixTime; + + StringBuilder ss = new StringBuilder(); + ss.Append(handler.GetParsedString(CypherStrings.CommandTicketlistguid, _id)); + ss.Append(handler.GetParsedString(CypherStrings.CommandTicketlistname, GetPlayerName())); + ss.Append(handler.GetParsedString(CypherStrings.CommandTicketlistagecreate, Time.secsToTimeString(curTime - _createTime, true, false))); + + if (!_assignedTo.IsEmpty()) + ss.Append(handler.GetParsedString(CypherStrings.CommandTicketlistassignedto, GetAssignedToName())); + + if (detailed) + { + ss.Append(handler.GetParsedString(CypherStrings.CommandTicketlistmessage, _note)); + if (!string.IsNullOrEmpty(_comment)) + ss.Append(handler.GetParsedString(CypherStrings.CommandTicketlistcomment, _comment)); + } + return ss.ToString(); + } + + ObjectGuid GetTargetCharacterGuid() { return _targetCharacterGuid; } + GMSupportComplaintType GetComplaintType() { return _complaintType; } + string GetNote() { return _note; } + + public void SetFacing(float facing) { _facing = facing; } + public void SetTargetCharacterGuid(ObjectGuid targetCharacterGuid) + { + _targetCharacterGuid = targetCharacterGuid; + } + public void SetComplaintType(GMSupportComplaintType type) { _complaintType = type; } + public void SetChatLog(SupportTicketSubmitComplaint.SupportTicketChatLog log) { _chatLog = log; } + public void SetNote(string note) { _note = note; } + } + + public class SuggestionTicket : Ticket + { + float _facing; + string _note; + + public SuggestionTicket() : base() + { + _note = ""; + } + + public SuggestionTicket(Player player) : base(player) + { + _note = ""; + _id = Global.SupportMgr.GenerateSuggestionId(); + } + + public override void LoadFromDB(SQLFields fields) + { + byte idx = 0; + _id = fields.Read(idx); + _playerGuid = ObjectGuid.Create(HighGuid.Player, fields.Read(++idx)); + _note = fields.Read(++idx); + _createTime = fields.Read(++idx); + _mapId = fields.Read(++idx); + _pos = new Vector3(fields.Read(++idx), fields.Read(++idx), fields.Read(++idx)); + _facing = fields.Read(++idx); + + long closedBy = fields.Read(++idx); + if (closedBy == 0) + _closedBy = ObjectGuid.Empty; + else if (closedBy < 0) + _closedBy.SetRawValue(0, (ulong)closedBy); + else + _closedBy = ObjectGuid.Create(HighGuid.Player, (ulong)closedBy); + + ulong assignedTo = fields.Read(++idx); + if (assignedTo == 0) + _assignedTo = ObjectGuid.Empty; + else + _assignedTo = ObjectGuid.Create(HighGuid.Player, assignedTo); + + _comment = fields.Read(++idx); + } + + public override void SaveToDB() + { + byte idx = 0; + PreparedStatement stmt = DB.Characters.GetPreparedStatement(CharStatements.REP_GM_SUGGESTION); + stmt.AddValue(idx, _id); + stmt.AddValue(++idx, _playerGuid.GetCounter()); + stmt.AddValue(++idx, _note); + stmt.AddValue(++idx, _mapId); + stmt.AddValue(++idx, _pos.X); + stmt.AddValue(++idx, _pos.Y); + stmt.AddValue(++idx, _pos.Z); + stmt.AddValue(++idx, _facing); + stmt.AddValue(++idx, _closedBy.GetCounter()); + stmt.AddValue(++idx, _assignedTo.GetCounter()); + stmt.AddValue(++idx, _comment); + + DB.Characters.Execute(stmt); + } + + public override void DeleteFromDB() + { + PreparedStatement stmt = DB.Characters.GetPreparedStatement(CharStatements.DEL_GM_SUGGESTION); + stmt.AddValue(0, _id); + DB.Characters.Execute(stmt); + } + + public override string FormatViewMessageString(CommandHandler handler, bool detailed = false) + { + ulong curTime = (ulong)Time.UnixTime; + + StringBuilder ss = new StringBuilder(); + ss.Append(handler.GetParsedString(CypherStrings.CommandTicketlistguid, _id)); + ss.Append(handler.GetParsedString(CypherStrings.CommandTicketlistname, GetPlayerName())); + ss.Append(handler.GetParsedString(CypherStrings.CommandTicketlistagecreate, Time.secsToTimeString(curTime - _createTime, true, false))); + + string name; + if (ObjectManager.GetPlayerNameByGUID(_assignedTo, out name)) + ss.Append(handler.GetParsedString(CypherStrings.CommandTicketlistassignedto, name)); + + if (detailed) + { + ss.Append(handler.GetParsedString(CypherStrings.CommandTicketlistmessage, _note)); + if (!string.IsNullOrEmpty(_comment)) + ss.Append(handler.GetParsedString(CypherStrings.CommandTicketlistcomment, _comment)); + } + return ss.ToString(); + } + + string GetNote() { return _note; } + public void SetNote(string note) { _note = note; } + + public void SetFacing(float facing) { _facing = facing; } + } +} diff --git a/Game/Text/ChatTextBuilder.cs b/Game/Text/ChatTextBuilder.cs new file mode 100644 index 000000000..25a688fe5 --- /dev/null +++ b/Game/Text/ChatTextBuilder.cs @@ -0,0 +1,83 @@ +/* + * Copyright (C) 2012-2017 CypherCore + * + * 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, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +using Framework.Constants; +using Game.DataStorage; +using Game.Entities; +using Game.Network; +using Game.Network.Packets; +using System.Collections.Generic; + +namespace Game.Chat +{ + public class MessageBuilder + { + public virtual ServerPacket Invoke(LocaleConstant locale = LocaleConstant.enUS) { return null; } + public virtual void Invoke(List data, LocaleConstant locale = LocaleConstant.enUS) { } + } + + public class BroadcastTextBuilder : MessageBuilder + { + public BroadcastTextBuilder(Unit obj, ChatMsg msgtype, uint id, WorldObject target = null, uint achievementId = 0) + { + _source = obj; + _msgType = msgtype; + _textId = id; + _target = target; + _achievementId = achievementId; + } + + public override ServerPacket Invoke(LocaleConstant locale) + { + BroadcastTextRecord bct = CliDB.BroadcastTextStorage.LookupByKey(_textId); + var packet = new ChatPkt(); + packet.Initialize(_msgType, bct != null ? (Language)bct.Language : Language.Universal, _source, _target, bct != null ? Global.DB2Mgr.GetBroadcastTextValue(bct, locale, _source.GetGender()) : "", _achievementId, "", locale); + return packet; + } + + Unit _source; + ChatMsg _msgType; + uint _textId; + WorldObject _target; + uint _achievementId; + } + + public class CustomChatTextBuilder : MessageBuilder + { + public CustomChatTextBuilder(WorldObject obj, ChatMsg msgType, string text, Language language = Language.Universal, WorldObject target = null) + { + _source = obj; + _msgType = msgType; + _text = text; + _language = language; + _target = target; + } + + public override ServerPacket Invoke(LocaleConstant locale) + { + var packet = new ChatPkt(); + packet.Initialize(_msgType, _language, _source, _target, _text, 0, "", locale); + return packet; + } + + WorldObject _source; + ChatMsg _msgType; + string _text; + Language _language; + WorldObject _target; + } +} diff --git a/Game/Text/CreatureTextManager.cs b/Game/Text/CreatureTextManager.cs new file mode 100644 index 000000000..622a5f993 --- /dev/null +++ b/Game/Text/CreatureTextManager.cs @@ -0,0 +1,685 @@ +/* + * Copyright (C) 2012-2017 CypherCore + * + * 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, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +using Framework.Collections; +using Framework.Constants; +using Framework.Database; +using Game.Chat; +using Game.DataStorage; +using Game.Entities; +using Game.Groups; +using Game.Maps; +using Game.Network; +using Game.Network.Packets; +using System.Collections.Generic; +using System.Diagnostics.Contracts; + +namespace Game +{ + public sealed class CreatureTextManager : Singleton + { + CreatureTextManager() { } + + public void LoadCreatureTexts() + { + uint oldMSTime = Time.GetMSTime(); + + mTextMap.Clear(); // for reload case + //all currently used temp texts are NOT reset + + PreparedStatement stmt = DB.World.GetPreparedStatement(WorldStatements.SEL_CREATURE_TEXT); + SQLResult result = DB.World.Query(stmt); + + if (result.IsEmpty()) + { + Log.outInfo(LogFilter.ServerLoading, "Loaded 0 ceature texts. DB table `creature_texts` is empty."); + return; + } + + uint textCount = 0; + uint creatureCount = 0; + + do + { + CreatureTextEntry temp = new CreatureTextEntry(); + + temp.entry = result.Read(0); + temp.group = result.Read(1); + temp.id = result.Read(2); + temp.text = result.Read(3); + temp.type = (ChatMsg)result.Read(4); + temp.lang = (Language)result.Read(5); + temp.probability = result.Read(6); + temp.emote = (Emote)result.Read(7); + temp.duration = result.Read(8); + temp.sound = result.Read(9); + temp.BroadcastTextId = result.Read(10); + temp.TextRange = (CreatureTextRange)result.Read(11); + + if (temp.sound != 0) + { + if (!CliDB.SoundKitStorage.ContainsKey(temp.sound)) + { + Log.outError(LogFilter.Sql, "GossipManager: Entry {0}, Group {1} in table `creature_texts` has Sound {2} but sound does not exist.", temp.entry, temp.group, temp.sound); + temp.sound = 0; + } + } + if (ObjectManager.GetLanguageDescByID(temp.lang) == null) + { + Log.outError(LogFilter.Sql, "GossipManager: Entry {0}, Group {1} in table `creature_texts` using Language {2} but Language does not exist.", temp.entry, temp.group, temp.lang); + temp.lang = Language.Universal; + } + if (temp.type >= ChatMsg.Max) + { + Log.outError(LogFilter.Sql, "GossipManager: Entry {0}, Group {1} in table `creature_texts` has Type {2} but this Chat Type does not exist.", temp.entry, temp.group, temp.type); + temp.type = ChatMsg.Say; + } + if (temp.emote != 0) + { + if (!CliDB.EmotesStorage.ContainsKey((uint)temp.emote)) + { + Log.outError(LogFilter.Sql, "GossipManager: Entry {0}, Group {1} in table `creature_texts` has Emote {2} but emote does not exist.", temp.entry, temp.group, temp.emote); + temp.emote = Emote.OneshotNone; + } + } + + if (temp.BroadcastTextId != 0) + { + if (!CliDB.BroadcastTextStorage.ContainsKey(temp.BroadcastTextId)) + { + Log.outError(LogFilter.Sql, "CreatureTextMgr: Entry {0}, Group {1}, Id {2} in table `creature_texts` has non-existing or incompatible BroadcastTextId {3}.", temp.entry, temp.group, temp.id, temp.BroadcastTextId); + temp.BroadcastTextId = 0; + } + } + + if (temp.TextRange > CreatureTextRange.World) + { + Log.outError(LogFilter.Sql, "CreatureTextMgr: Entry {0}, Group {1}, Id {2} in table `creature_text` has incorrect TextRange {3}.", temp.entry, temp.group, temp.id, temp.TextRange); + temp.TextRange = CreatureTextRange.Normal; + } + + if (!mTextMap.ContainsKey(temp.entry)) + { + mTextMap[temp.entry] = new MultiMap(); + ++creatureCount; + } + + mTextMap[temp.entry].Add(temp.group, temp); + ++textCount; + } while (result.NextRow()); + + Log.outInfo(LogFilter.ServerLoading, "Loaded {0} creature texts for {1} creatures in {2} ms", textCount, creatureCount, Time.GetMSTimeDiffToNow(oldMSTime)); + } + + public void LoadCreatureTextLocales() + { + uint oldMSTime = Time.GetMSTime(); + + mLocaleTextMap.Clear(); // for reload case + + SQLResult result = DB.World.Query("SELECT entry, groupid, id, text_loc1, text_loc2, text_loc3, text_loc4, text_loc5, text_loc6, text_loc7, text_loc8 FROM locales_creature_text"); + + if (result.IsEmpty()) + return; + + uint textCount = 0; + + do + { + CreatureTextLocale loc = new CreatureTextLocale(); + for (byte locale = 1; locale < (int)LocaleConstant.OldTotal; ++locale) + { + ObjectManager.AddLocaleString(result.Read(3 + locale - 1), (LocaleConstant)locale, loc.Text); + } + + mLocaleTextMap[new CreatureTextId(result.Read(0), result.Read(1), result.Read(2))] = loc; + ++textCount; + } while (result.NextRow()); + + Log.outInfo(LogFilter.ServerLoading, "Loaded {0} creature localized texts in {1} ms", textCount, Time.GetMSTimeDiffToNow(oldMSTime)); + } + + public uint SendChat(Creature source, byte textGroup, WorldObject whisperTarget = null, ChatMsg msgType = ChatMsg.Addon, Language language = Language.Addon, + CreatureTextRange range = CreatureTextRange.Normal, uint sound = 0, Team team = Team.Other, bool gmOnly = false, Player srcPlr = null) + { + if (source == null) + return 0; + + var sList = mTextMap.LookupByKey(source.GetEntry()); + if (sList == null) + { + Log.outError(LogFilter.Sql, "GossipManager: Could not find Text for Creature({0}) Entry {1} in 'creature_text' table. Ignoring.", source.GetName(), source.GetEntry()); + return 0; + } + + var textGroupContainer = sList.LookupByKey(textGroup); + if (textGroupContainer.Empty()) + { + Log.outError(LogFilter.ChatSystem, "GossipManager: Could not find TextGroup {0} for Creature({1}) GuidLow {2} Entry {3}. Ignoring.", textGroup, source.GetName(), source.GetGUID().ToString(), source.GetEntry()); + return 0; + } + + List tempGroup = new List(); + var repeatGroup = source.GetTextRepeatGroup(textGroup); + + foreach (var entry in textGroupContainer) + if (!repeatGroup.Contains(entry.id)) + tempGroup.Add(entry); + + if (tempGroup.Empty()) + { + source.ClearTextRepeatGroup(textGroup); + tempGroup = textGroupContainer; + } + + var textEntry = tempGroup.SelectRandomElementByWeight(t => { return t.probability; }); + + ChatMsg finalType = (msgType == ChatMsg.Addon) ? textEntry.type : msgType; + Language finalLang = (language == Language.Addon) ? textEntry.lang : language; + uint finalSound = textEntry.sound; + if (sound != 0) + finalSound = sound; + else + { + BroadcastTextRecord bct = CliDB.BroadcastTextStorage.LookupByKey(textEntry.BroadcastTextId); + if (bct != null) + { + uint broadcastTextSoundId = bct.SoundID[source.GetGender() == Gender.Female ? 1 : 0]; + if (broadcastTextSoundId != 0) + finalSound = broadcastTextSoundId; + } + } + + if (range == CreatureTextRange.Normal) + range = textEntry.TextRange; + + if (finalSound != 0) + SendSound(source, finalSound, finalType, whisperTarget, range, team, gmOnly); + + Unit finalSource = source; + if (srcPlr) + finalSource = srcPlr; + + if (textEntry.emote != 0) + SendEmote(finalSource, textEntry.emote); + + if (srcPlr) + { + PlayerTextBuilder builder = new PlayerTextBuilder(source, finalSource, finalSource.GetGender(), finalType, textEntry.group, textEntry.id, finalLang, whisperTarget); + SendChatPacket(finalSource, builder, finalType, whisperTarget, range, team, gmOnly); + } + else + { + CreatureTextBuilder builder = new CreatureTextBuilder(finalSource, finalSource.GetGender(), finalType, textEntry.group, textEntry.id, finalLang, whisperTarget); + SendChatPacket(finalSource, builder, finalType, whisperTarget, range, team, gmOnly); + } + + source.SetTextRepeatId(textGroup, textEntry.id); + return textEntry.duration; + } + + float GetRangeForChatType(ChatMsg msgType) + { + float dist = WorldConfig.GetFloatValue(WorldCfg.ListenRangeSay); + switch (msgType) + { + case ChatMsg.MonsterYell: + dist = WorldConfig.GetFloatValue(WorldCfg.ListenRangeYell); + break; + case ChatMsg.MonsterEmote: + case ChatMsg.RaidBossEmote: + dist = WorldConfig.GetFloatValue(WorldCfg.ListenRangeTextemote); + break; + default: + break; + } + + return dist; + } + + void SendSound(Creature source, uint sound, ChatMsg msgType, WorldObject whisperTarget = null, CreatureTextRange range = CreatureTextRange.Normal, Team team = Team.Other, bool gmOnly = false) + { + if (sound == 0 || !source) + return; + + SendNonChatPacket(source, new PlaySound(source.GetGUID(), sound), msgType, whisperTarget, range, team, gmOnly); + } + + void SendNonChatPacket(WorldObject source, ServerPacket data, ChatMsg msgType, WorldObject whisperTarget, CreatureTextRange range, Team team, bool gmOnly) + { + float dist = GetRangeForChatType(msgType); + + switch (msgType) + { + case ChatMsg.MonsterParty: + if (!whisperTarget) + return; + + Player whisperPlayer = whisperTarget.ToPlayer(); + if (whisperPlayer) + { + Group group = whisperPlayer.GetGroup(); + if (group) + group.BroadcastWorker(player => player.SendPacket(data)); + } + return; + case ChatMsg.MonsterWhisper: + case ChatMsg.RaidBossWhisper: + { + if (range == CreatureTextRange.Normal)//ignores team and gmOnly + { + if (!whisperTarget || !whisperTarget.IsTypeId(TypeId.Player)) + return; + + whisperTarget.ToPlayer().SendPacket(data); + return; + } + break; + } + default: + break; + } + + switch (range) + { + case CreatureTextRange.Area: + { + uint areaId = source.GetAreaId(); + var players = source.GetMap().GetPlayers(); + foreach (var pl in players) + if (pl.GetAreaId() == areaId && (team == 0 || pl.GetTeam() == team) && (!gmOnly || pl.IsGameMaster())) + pl.SendPacket(data); + return; + } + case CreatureTextRange.Zone: + { + uint zoneId = source.GetZoneId(); + var players = source.GetMap().GetPlayers(); + foreach (var pl in players) + if (pl.GetZoneId() == zoneId && (team == 0 || pl.GetTeam() == team) && (!gmOnly || pl.IsGameMaster())) + pl.SendPacket(data); + return; + } + case CreatureTextRange.Map: + { + var players = source.GetMap().GetPlayers(); + foreach (var pl in players) + if ((team == 0 || pl.GetTeam() == team) && (!gmOnly || pl.IsGameMaster())) + pl.SendPacket(data); + return; + } + case CreatureTextRange.World: + { + var smap = Global.WorldMgr.GetAllSessions(); + foreach (var session in smap) + { + Player player = session.GetPlayer(); + if (player != null) + if ((team == 0 || player.GetTeam() == team) && (!gmOnly || player.IsGameMaster())) + player.SendPacket(data); + } + return; + } + case CreatureTextRange.Normal: + default: + break; + } + + source.SendMessageToSetInRange(data, dist, true); + } + + void SendEmote(Unit source, Emote emote) + { + if (!source) + return; + + source.HandleEmoteCommand(emote); + } + + public bool TextExist(uint sourceEntry, byte textGroup) + { + if (sourceEntry == 0) + return false; + + var textHolder = mTextMap.LookupByKey(sourceEntry); + if (textHolder == null) + { + Log.outDebug(LogFilter.Unit, "CreatureTextMgr.TextExist: Could not find Text for Creature (entry {0}) in 'creature_text' table.", sourceEntry); + return false; + } + + var textEntryList = textHolder.LookupByKey(textGroup); + if (textEntryList.Empty()) + { + Log.outDebug(LogFilter.Unit, "CreatureTextMgr.TextExist: Could not find TextGroup {0} for Creature (entry {1}).", textGroup, sourceEntry); + return false; + } + + return true; + } + + public string GetLocalizedChatString(uint entry, Gender gender, byte textGroup, uint id, LocaleConstant locale = LocaleConstant.enUS) + { + var multiMap = mTextMap.LookupByKey(entry); + if (multiMap == null) + return ""; + + var creatureTextEntryList = multiMap.LookupByKey(textGroup); + if (creatureTextEntryList.Empty()) + return ""; + + CreatureTextEntry creatureTextEntry = null; + for (var i = 0; i != creatureTextEntryList.Count; ++i) + { + creatureTextEntry = creatureTextEntryList[i]; + if (creatureTextEntry.id == id) + break; + } + + if (creatureTextEntry == null) + return ""; + + if (locale > LocaleConstant.Max) + locale = LocaleConstant.enUS; + + string baseText = ""; + BroadcastTextRecord bct = CliDB.BroadcastTextStorage.LookupByKey(creatureTextEntry.BroadcastTextId); + + if (bct != null) + baseText = Global.DB2Mgr.GetBroadcastTextValue(bct, locale, gender); + else + baseText = creatureTextEntry.text; + + if (locale != LocaleConstant.enUS && bct == null) + { + var creatureTextLocale = mLocaleTextMap.LookupByKey(new CreatureTextId(entry, textGroup, id)); + if (creatureTextLocale != null) + ObjectManager.GetLocaleString(creatureTextLocale.Text, locale, ref baseText); + } + + return baseText; + } + + public void SendChatPacket(WorldObject source, MessageBuilder builder, ChatMsg msgType, WorldObject whisperTarget = null, CreatureTextRange range = CreatureTextRange.Normal, Team team = Team.Other, bool gmOnly = false) + { + if (source == null) + return; + + var localizer = new CreatureTextLocalizer(builder, msgType); + + switch (msgType) + { + case ChatMsg.MonsterWhisper: + case ChatMsg.RaidBossWhisper: + { + if (range == CreatureTextRange.Normal) //ignores team and gmOnly + { + if (!whisperTarget || !whisperTarget.IsTypeId(TypeId.Player)) + return; + + localizer.Invoke(whisperTarget.ToPlayer()); + return; + } + break; + } + default: + break; + } + + switch (range) + { + case CreatureTextRange.Area: + { + uint areaId = source.GetAreaId(); + var players = source.GetMap().GetPlayers(); + foreach (var pl in players) + if (pl.GetAreaId() == areaId && (team == 0 || pl.GetTeam() == team) && (!gmOnly || pl.IsGameMaster())) + localizer.Invoke(pl); + return; + } + case CreatureTextRange.Zone: + { + uint zoneId = source.GetZoneId(); + var players = source.GetMap().GetPlayers(); + foreach (var pl in players) + if (pl.GetZoneId() == zoneId && (team == 0 || pl.GetTeam() == team) && (!gmOnly || pl.IsGameMaster())) + localizer.Invoke(pl); + return; + } + case CreatureTextRange.Map: + { + var players = source.GetMap().GetPlayers(); + foreach (var pl in players) + if ((team == 0 || pl.GetTeam() == team) && (!gmOnly || pl.IsGameMaster())) + localizer.Invoke(pl); + return; + } + case CreatureTextRange.World: + { + var smap = Global.WorldMgr.GetAllSessions(); + foreach (var session in smap) + { + Player player = session.GetPlayer(); + if (player != null) + if ((team == 0 || player.GetTeam() == team) && (!gmOnly || player.IsGameMaster())) + localizer.Invoke(player); + } + return; + } + case CreatureTextRange.Normal: + default: + break; + } + + float dist = GetRangeForChatType(msgType); + var worker = new PlayerDistWorker(source, dist, localizer); + Cell.VisitWorldObjects(source, worker, dist); + } + + Dictionary> mTextMap = new Dictionary>(); + Dictionary mLocaleTextMap = new Dictionary(); + } + + public class CreatureTextHolder + { + public CreatureTextHolder(uint entry) + { + Entry = entry; + Groups = new MultiMap(); + } + + public void AddText(uint group, CreatureTextEntry entry) + { + Groups.Add(group, entry); + } + + public List GetGroupList(uint group) + { + return Groups.LookupByKey(group); + } + + uint Entry; + MultiMap Groups; + } + + public class CreatureTextRepeatHolder + { + public CreatureTextRepeatHolder(ulong guid) + { + Guid = guid; + Groups = new MultiMap(); + } + + public void AddText(byte group, byte entry) + { + Groups.Add(group, entry); + } + + public List GetGroupList(byte group) + { + return Groups.LookupByKey(group); + } + + ulong Guid; + MultiMap Groups; + } + + public class CreatureTextEntry + { + public uint entry; + public byte group; + public byte id; + public string text; + public ChatMsg type; + public Language lang; + public float probability; + public Emote emote; + public uint duration; + public uint sound; + public uint BroadcastTextId; + public CreatureTextRange TextRange; + } + public class CreatureTextLocale + { + public StringArray Text = new StringArray((int)LocaleConstant.Total); + } + public class CreatureTextId + { + public CreatureTextId(uint e, uint g, uint i) + { + entry = e; + textGroup = g; + textId = i; + } + + uint entry; + uint textGroup; + uint textId; + } + + public enum CreatureTextRange + { + Normal = 0, + Area = 1, + Zone = 2, + Map = 3, + World = 4 + } + + public class CreatureTextLocalizer : IDoWork + { + public CreatureTextLocalizer(MessageBuilder builder, ChatMsg msgType) + { + _builder = builder; + _msgType = msgType; + } + + public void Invoke(Player player) + { + LocaleConstant loc_idx = player.GetSession().GetSessionDbLocaleIndex(); + ServerPacket messageTemplate; + + // create if not cached yet + if (!_packetCache.ContainsKey(loc_idx)) + { + messageTemplate = _builder.Invoke(loc_idx); + _packetCache[loc_idx] = messageTemplate; + } + else + messageTemplate = _packetCache[loc_idx]; + + ChatPkt message = (ChatPkt)messageTemplate; + switch (_msgType) + { + case ChatMsg.MonsterWhisper: + case ChatMsg.RaidBossWhisper: + message.SetReceiver(player, loc_idx); + break; + default: + break; + } + + player.SendPacket(message); + } + + Dictionary _packetCache = new Dictionary(); + MessageBuilder _builder; + ChatMsg _msgType; + } + + public class CreatureTextBuilder : MessageBuilder + { + public CreatureTextBuilder(WorldObject obj, Gender gender, ChatMsg msgtype, byte textGroup, uint id, Language language, WorldObject target) + { + _source = obj; + _gender = gender; + _msgType = msgtype; + _textGroup = textGroup; + _textId = id; + _language = language; + _target = target; + } + + public override ServerPacket Invoke(LocaleConstant locale = LocaleConstant.enUS) + { + string text = Global.CreatureTextMgr.GetLocalizedChatString(_source.GetEntry(), _gender, _textGroup, _textId, locale); + var packet = new ChatPkt(); + packet.Initialize(_msgType, _language, _source, _target, text, 0, "", locale); + return packet; + } + + WorldObject _source; + Gender _gender; + ChatMsg _msgType; + byte _textGroup; + uint _textId; + Language _language; + WorldObject _target; + } + + public class PlayerTextBuilder : MessageBuilder + { + public PlayerTextBuilder(WorldObject obj, WorldObject speaker, Gender gender, ChatMsg msgtype, byte textGroup, uint id, Language language, WorldObject target) + { + _source = obj; + _gender = gender; + _talker = speaker; + _msgType = msgtype; + _textGroup = textGroup; + _textId = id; + _language = language; + _target = target; + } + + public override ServerPacket Invoke(LocaleConstant loc_idx = LocaleConstant.enUS) + { + string text = Global.CreatureTextMgr.GetLocalizedChatString(_source.GetEntry(), _gender, _textGroup, _textId, loc_idx); + var packet = new ChatPkt(); + packet.Initialize(_msgType, _language, _talker, _target, text, 0, "", loc_idx); + return packet; + } + + WorldObject _source; + WorldObject _talker; + Gender _gender; + ChatMsg _msgType; + byte _textGroup; + uint _textId; + Language _language; + WorldObject _target; + } +} \ No newline at end of file diff --git a/Game/Tools/CharacterDatabaseCleaner.cs b/Game/Tools/CharacterDatabaseCleaner.cs new file mode 100644 index 000000000..eeae487e8 --- /dev/null +++ b/Game/Tools/CharacterDatabaseCleaner.cs @@ -0,0 +1,172 @@ +/* + * Copyright (C) 2012-2017 CypherCore + * + * 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, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +using Framework.Constants; +using Framework.Database; +using Game.DataStorage; +using Game.Spells; +using System; +using System.Collections.Generic; +using System.Text; + +namespace Game +{ + class CharacterDatabaseCleaner + { + public static void CleanDatabase() + { + // config to disable + if (!WorldConfig.GetBoolValue(WorldCfg.CleanCharacterDb)) + return; + + Log.outInfo(LogFilter.Server, "Cleaning character database..."); + + uint oldMSTime = Time.GetMSTime(); + + // check flags which clean ups are necessary + SQLResult result = DB.Characters.Query("SELECT value FROM worldstates WHERE entry = {0}", (uint)WorldStates.CleaningFlags); + if (result.IsEmpty()) + return; + + CleaningFlags flags = (CleaningFlags)result.Read(0); + + // clean up + if (flags.HasAnyFlag(CleaningFlags.AchievementProgress)) + CleanCharacterAchievementProgress(); + + if (flags.HasAnyFlag(CleaningFlags.Skills)) + CleanCharacterSkills(); + + if (flags.HasAnyFlag(CleaningFlags.Spells)) + CleanCharacterSpell(); + + if (flags.HasAnyFlag(CleaningFlags.Talents)) + CleanCharacterTalent(); + + if (flags.HasAnyFlag(CleaningFlags.Queststatus)) + CleanCharacterQuestStatus(); + + // NOTE: In order to have persistentFlags be set in worldstates for the next cleanup, + // you need to define them at least once in worldstates. + flags &= (CleaningFlags) WorldConfig.GetIntValue(WorldCfg.PersistentCharacterCleanFlags); + DB.Characters.Execute("UPDATE worldstates SET value = {0} WHERE entry = {1}", flags, (uint)WorldStates.CleaningFlags); + + Global.WorldMgr.SetCleaningFlags(flags); + + Log.outInfo(LogFilter.ServerLoading, "Cleaned character database in {0} ms", Time.GetMSTimeDiffToNow(oldMSTime)); + } + + delegate bool CheckFor(uint id); + + static void CheckUnique(string column, string table, CheckFor check) + { + SQLResult result = DB.Characters.Query("SELECT DISTINCT {0} FROM {1}", column, table); + if (result.IsEmpty()) + { + Log.outInfo(LogFilter.Sql, "Table {0} is empty.", table); + return; + } + + bool found = false; + StringBuilder ss = new StringBuilder(); + do + { + uint id = result.Read(0); + if (!check(id)) + { + if (!found) + { + ss.AppendFormat("DELETE FROM {0} WHERE {1} IN(", table, column); + found = true; + } + else + ss.Append(','); + + ss.Append(id); + } + } + while (result.NextRow()); + + if (found) + { + ss.Append(')'); + DB.Characters.Execute(ss.ToString()); + } + } + + static bool AchievementProgressCheck(uint criteria) + { + return Global.CriteriaMgr.GetCriteria(criteria) != null; + } + + static void CleanCharacterAchievementProgress() + { + CheckUnique("criteria", "character_achievement_progress", AchievementProgressCheck); + } + + static bool SkillCheck(uint skill) + { + return CliDB.SkillLineStorage.ContainsKey(skill); + } + + static void CleanCharacterSkills() + { + CheckUnique("skill", "character_skills", SkillCheck); + } + + static bool SpellCheck(uint spell_id) + { + SpellInfo spellInfo = Global.SpellMgr.GetSpellInfo(spell_id); + return spellInfo != null && !spellInfo.HasAttribute(SpellCustomAttributes.IsTalent); + } + + static void CleanCharacterSpell() + { + CheckUnique("spell", "character_spell", SpellCheck); + } + + static bool TalentCheck(uint talent_id) + { + TalentRecord talentInfo = CliDB.TalentStorage.LookupByKey(talent_id); + if (talentInfo == null) + return false; + + return CliDB.ChrSpecializationStorage.ContainsKey(talentInfo.SpecID); + } + + static void CleanCharacterTalent() + { + DB.Characters.Execute("DELETE FROM character_talent WHERE talentGroup > {0}", PlayerConst.MaxSpecializations); + CheckUnique("talentId", "character_talent", TalentCheck); + } + + static void CleanCharacterQuestStatus() + { + DB.Characters.Execute("DELETE FROM character_queststatus WHERE status = 0"); + } + } + + [Flags] + public enum CleaningFlags + { + AchievementProgress = 0x1, + Skills = 0x2, + Spells = 0x4, + Talents = 0x8, + Queststatus = 0x10 + } +} diff --git a/Game/Warden/Warden.cs b/Game/Warden/Warden.cs new file mode 100644 index 000000000..cdd103b4f --- /dev/null +++ b/Game/Warden/Warden.cs @@ -0,0 +1,220 @@ +/* + * Copyright (C) 2012-2017 CypherCore + * + * 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, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +using Framework.Constants; +using Framework.Cryptography; +using Framework.IO; +using Game.Network.Packets; +using System; +using System.Numerics; +using System.Security.Cryptography; + +namespace Game +{ + public abstract class Warden + { + public Warden() + { + _inputCrypto = new SARC4(); + _outputCrypto = new SARC4(); + _checkTimer = 10 * Time.InMilliseconds; + } + + public abstract void Init(WorldSession session, BigInteger k); + public abstract ClientWardenModule GetModuleForClient(); + public abstract void InitializeModule(); + public abstract void RequestHash(); + public abstract void HandleHashResult(ByteBuffer buff); + public abstract void RequestData(); + public abstract void HandleData(ByteBuffer buff); + + public void SendModuleToClient() + { + Log.outDebug(LogFilter.Warden, "Send module to client"); + + uint sizeLeft = _module.CompressedSize; + int pos = 0; + uint burstSize; + while (sizeLeft > 0) + { + WardenModuleTransfer transfer = new WardenModuleTransfer(); + + burstSize = sizeLeft < 500 ? sizeLeft : 500u; + transfer.Command = WardenOpcodes.Smsg_ModuleCache; + transfer.DataSize = (ushort)burstSize; + Buffer.BlockCopy(_module.CompressedData, pos, transfer.Data, 0, (int)burstSize); + sizeLeft -= burstSize; + pos += (int)burstSize; + + WardenDataServer packet = new WardenDataServer(); + packet.Data = EncryptData(transfer.Write()); + _session.SendPacket(packet); + } + } + + public void RequestModule() + { + Log.outDebug(LogFilter.Warden, "Request module"); + + // Create packet structure + WardenModuleUse request = new WardenModuleUse(); + request.Command = WardenOpcodes.Smsg_ModuleUse; + + request.ModuleId = _module.Id; + request.ModuleKey = _module.Key; + request.Size = _module.CompressedSize; + + WardenDataServer packet = new WardenDataServer(); + packet.Data = EncryptData(request.Write()); + _session.SendPacket(packet); + } + + public void Update() + { + if (_initialized) + { + uint currentTimestamp = Time.GetMSTime(); + uint diff = currentTimestamp - _previousTimestamp; + _previousTimestamp = currentTimestamp; + + if (_dataSent) + { + uint maxClientResponseDelay = WorldConfig.GetUIntValue(WorldCfg.WardenClientResponseDelay); + if (maxClientResponseDelay > 0) + { + // Kick player if client response delays more than set in config + if (_clientResponseTimer > maxClientResponseDelay * Time.InMilliseconds) + { + Log.outWarn(LogFilter.Warden, "{0} (latency: {1}, IP: {2}) exceeded Warden module response delay for more than {3} - disconnecting client", + _session.GetPlayerInfo(), _session.GetLatency(), _session.GetRemoteAddress(), Time.secsToTimeString(maxClientResponseDelay, true)); + _session.KickPlayer(); + } + else + _clientResponseTimer += diff; + } + } + else + { + if (diff >= _checkTimer) + { + RequestData(); + } + else + _checkTimer -= diff; + } + } + } + + public byte[] DecryptData(byte[] buffer) + { + _inputCrypto.ProcessBuffer(buffer, buffer.Length); + return buffer; + } + + public ByteBuffer EncryptData(byte[] buffer) + { + _outputCrypto.ProcessBuffer(buffer, buffer.Length); + return new ByteBuffer(buffer); + } + + public bool IsValidCheckSum(uint checksum, byte[] data, ushort length) + { + uint newChecksum = BuildChecksum(data, length); + + if (checksum != newChecksum) + { + Log.outDebug(LogFilter.Warden, "CHECKSUM IS NOT VALID"); + return false; + } + else + { + Log.outDebug(LogFilter.Warden, "CHECKSUM IS VALID"); + return true; + } + } + + public uint BuildChecksum(byte[] data, uint length) + { + SHA1 sha = SHA1.Create(); + + var hash = sha.ComputeHash(data, 0, (int)length); + uint checkSum = 0; + for (byte i = 0; i < 5; ++i) + checkSum = checkSum ^ BitConverter.ToUInt32(hash, i * 4); + + return checkSum; + } + + public string Penalty(WardenCheck check = null) + { + WardenActions action; + + if (check != null) + action = check.Action; + else + action = (WardenActions)WorldConfig.GetIntValue(WorldCfg.WardenClientFailAction); + + switch (action) + { + case WardenActions.Log: + return "None"; + case WardenActions.Kick: + _session.KickPlayer(); + return "Kick"; + case WardenActions.Ban: + { + string duration = WorldConfig.GetIntValue(WorldCfg.WardenClientBanDuration) + "s"; + string accountName; + Global.AccountMgr.GetName(_session.GetAccountId(), out accountName); + string banReason = "Warden Anticheat Violation"; + // Check can be NULL, for example if the client sent a wrong signature in the warden packet (CHECKSUM FAIL) + if (check != null) + banReason += ": " + check.Comment + " (CheckId: " + check.CheckId + ")"; + + Global.WorldMgr.BanAccount(BanMode.Account, accountName, duration, banReason, "Server"); + return "Ban"; + } + default: + break; + } + return "Undefined"; + } + + internal WorldSession _session; + internal byte[] _inputKey = new byte[16]; + internal byte[] _outputKey = new byte[16]; + internal byte[] _seed = new byte[16]; + internal SARC4 _inputCrypto; + internal SARC4 _outputCrypto; + internal uint _checkTimer; // Timer for sending check requests + internal uint _clientResponseTimer; // Timer for client response delay + internal bool _dataSent; + internal uint _previousTimestamp; + internal ClientWardenModule _module; + internal bool _initialized; + } + + public class ClientWardenModule + { + public byte[] Id = new byte[16]; + public byte[] Key = new byte[16]; + public uint CompressedSize; + public byte[] CompressedData; + } + + +} diff --git a/Game/Warden/WardenKeyGeneration.cs b/Game/Warden/WardenKeyGeneration.cs new file mode 100644 index 000000000..c7e435aea --- /dev/null +++ b/Game/Warden/WardenKeyGeneration.cs @@ -0,0 +1,68 @@ +/* + * Copyright (C) 2012-2017 CypherCore + * + * 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, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +using System.Linq; +using System.Security.Cryptography; + +namespace Game +{ + class SHA1Randx + { + public SHA1Randx(byte[] buff) + { + int halfSize = buff.Length / 2; + + sh = SHA1.Create(); + o1 = sh.ComputeHash(buff, 0, halfSize); + + sh = SHA1.Create(); + o2 = sh.ComputeHash(buff.Skip(halfSize).ToArray(), 0, buff.Length - halfSize); + + FillUp(); + } + + public void Generate(byte[] buf, uint sz) + { + for (uint i = 0; i < sz; ++i) + { + if (taken == 20) + FillUp(); + + buf[i] = o0[taken]; + taken++; + } + } + + + void FillUp() + { + sh = SHA1.Create(); + sh.ComputeHash(o1, 0, 20); + sh.ComputeHash(o0, 0, 20); + o0 = sh.ComputeHash(o2, 0, 20); + + taken = 0; + } + + SHA1 sh; + uint taken; + byte[] o0 = new byte[20]; + byte[] o1 = new byte[20]; + byte[] o2 = new byte[20]; + } + +} diff --git a/Game/Warden/WardenModuleWin.cs b/Game/Warden/WardenModuleWin.cs new file mode 100644 index 000000000..abbe934ec --- /dev/null +++ b/Game/Warden/WardenModuleWin.cs @@ -0,0 +1,1225 @@ +/* + * Copyright (C) 2012-2017 CypherCore + * + * 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, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +namespace Game +{ + struct WardenModuleWin + { + public static byte[] Module = + { + 0x21, 0x6C, 0xBB, 0x6A, 0xB9, 0xAF, 0xE8, 0xA1, 0x9A, 0x79, 0x18, 0xF1, 0x27, 0x9A, 0x14, 0xF5, + 0x42, 0x5D, 0x00, 0xBA, 0x74, 0x57, 0x0E, 0xAF, 0xD2, 0xAE, 0x8E, 0x51, 0xA5, 0xC1, 0x17, 0x4E, + 0xEC, 0x7F, 0xAC, 0xBC, 0xDD, 0x42, 0x87, 0xA6, 0xF9, 0xC1, 0x4A, 0xCE, 0xB5, 0xDD, 0xB3, 0xF8, + 0x06, 0x52, 0x9D, 0xF2, 0xAE, 0x2A, 0x49, 0x2B, 0x7B, 0x72, 0x5F, 0x40, 0x01, 0x16, 0xDB, 0x98, + 0x77, 0x1F, 0xE3, 0x61, 0x5A, 0x99, 0x39, 0x66, 0x2F, 0x2F, 0x35, 0xBF, 0x00, 0x20, 0xE4, 0xE0, + 0x8F, 0x98, 0xAD, 0xFC, 0xDB, 0x7D, 0xDD, 0xE8, 0x7A, 0xE9, 0x21, 0x6C, 0x86, 0x80, 0x3A, 0xE7, + 0x75, 0x66, 0x0E, 0xD9, 0xC6, 0xC5, 0xD8, 0xD8, 0xA9, 0x9B, 0x84, 0x09, 0xF0, 0xB3, 0x7C, 0x72, + 0x53, 0xE2, 0x29, 0xF9, 0x64, 0x8C, 0x17, 0xFD, 0x90, 0xAE, 0x48, 0x5A, 0x30, 0xFA, 0x30, 0xB7, + 0x54, 0x58, 0x59, 0x61, 0xA0, 0x24, 0x57, 0xE4, 0xF0, 0x05, 0xA2, 0x3F, 0x7A, 0xA6, 0x51, 0x7E, + 0x4F, 0x35, 0xF4, 0xE5, 0xFF, 0x98, 0xE9, 0x3E, 0x1F, 0xF1, 0x66, 0x48, 0x5B, 0xF8, 0xCF, 0x3B, + 0x37, 0x3B, 0x60, 0x47, 0x1C, 0xF7, 0xBE, 0x59, 0x70, 0x3A, 0x03, 0x4C, 0xE6, 0xBD, 0xB6, 0xED, + 0x46, 0xAF, 0x04, 0x5A, 0xAA, 0xC4, 0x30, 0xCF, 0x0B, 0x05, 0x86, 0xA9, 0x56, 0x5B, 0x09, 0xF0, + 0x92, 0x59, 0xC8, 0x48, 0x58, 0x33, 0xBF, 0x81, 0x70, 0x51, 0x92, 0x98, 0xE9, 0x4F, 0x2B, 0xDD, + 0x5B, 0x0A, 0x42, 0x51, 0x73, 0x97, 0xF6, 0x66, 0x00, 0xF2, 0x04, 0x50, 0x4A, 0x54, 0x62, 0x7A, + 0x00, 0x10, 0x5B, 0xAD, 0x0E, 0xFC, 0xE1, 0x44, 0x55, 0x4E, 0x21, 0x76, 0xC6, 0x4B, 0xD5, 0x2E, + 0xF5, 0xF4, 0x0E, 0xFC, 0x55, 0xE1, 0xDE, 0x32, 0x20, 0x22, 0x03, 0x98, 0xF0, 0xD3, 0x4A, 0xE5, + 0x45, 0x49, 0x0C, 0xE8, 0x76, 0xE1, 0xBF, 0x4A, 0x53, 0xD3, 0xCE, 0x64, 0xCF, 0x82, 0xCF, 0x67, + 0xA9, 0x3F, 0xF1, 0x06, 0x24, 0x33, 0x0A, 0x98, 0x6A, 0x5C, 0xAB, 0xEE, 0x7F, 0x64, 0xE3, 0x65, + 0x1F, 0xF6, 0xBE, 0xAF, 0x9C, 0x63, 0x53, 0x54, 0x06, 0x84, 0x4F, 0xF2, 0x7D, 0xC5, 0xBD, 0x70, + 0xC7, 0x6F, 0x59, 0x93, 0x7C, 0xEE, 0xC0, 0xCA, 0xF5, 0x56, 0x21, 0x58, 0x5B, 0xD8, 0x1D, 0xB4, + 0xB7, 0x60, 0x72, 0x46, 0xA2, 0x60, 0x3F, 0xC3, 0x30, 0xAB, 0xE0, 0x3B, 0xAC, 0x9E, 0xB9, 0x64, + 0xA8, 0xEF, 0xD0, 0xE1, 0xE5, 0xE1, 0x83, 0x48, 0xB8, 0x35, 0xA7, 0x12, 0x11, 0x00, 0xC9, 0x29, + 0x1A, 0x41, 0x1D, 0x3B, 0xFB, 0x33, 0x0B, 0x66, 0x45, 0x86, 0x61, 0x70, 0x35, 0x4C, 0x5F, 0x64, + 0x8E, 0xF5, 0x57, 0xBE, 0xA6, 0xFA, 0x49, 0xC9, 0x89, 0xA4, 0x74, 0x02, 0x9E, 0xE7, 0x67, 0x14, + 0x8B, 0xB9, 0xE7, 0xA8, 0xB0, 0x75, 0x65, 0x22, 0xB7, 0xCD, 0xFA, 0x55, 0x18, 0x00, 0x55, 0xB5, + 0x09, 0x70, 0x7E, 0x05, 0x0B, 0x11, 0x42, 0xF0, 0x80, 0x58, 0x2F, 0xFE, 0x3B, 0x2C, 0x4A, 0xCA, + 0x50, 0x34, 0xD0, 0x6F, 0xF1, 0xCC, 0x7F, 0x35, 0xD7, 0x9B, 0xF2, 0x97, 0x16, 0xFE, 0x5F, 0x6C, + 0x94, 0xDC, 0xAC, 0x63, 0xC8, 0x85, 0x2E, 0x60, 0x41, 0x34, 0x89, 0xD3, 0xDD, 0xBF, 0x2D, 0x69, + 0xC2, 0xF7, 0x74, 0xB2, 0x56, 0xD9, 0x76, 0xA2, 0x35, 0x30, 0x60, 0x35, 0xAB, 0x50, 0x21, 0xAE, + 0xFC, 0xFA, 0x19, 0x06, 0xDE, 0x89, 0x46, 0xFF, 0x34, 0x2F, 0x19, 0x84, 0x85, 0xF5, 0x1E, 0x60, + 0x91, 0x0D, 0x8C, 0x94, 0xDB, 0x2E, 0xEE, 0x4D, 0x0A, 0x29, 0x64, 0x81, 0xAD, 0x4C, 0xBF, 0x41, + 0x51, 0x04, 0xCD, 0x1C, 0x5C, 0x89, 0xD3, 0x3A, 0x91, 0xD9, 0x5C, 0x7E, 0xF3, 0xE9, 0x5B, 0x9E, + 0xC2, 0xD2, 0xE4, 0xD5, 0xEF, 0x47, 0x63, 0xD2, 0xD4, 0x19, 0x3E, 0xDF, 0xCF, 0xA4, 0x10, 0x47, + 0x16, 0x93, 0xC2, 0xF2, 0x22, 0xED, 0x1D, 0x9E, 0x21, 0x63, 0xC4, 0xCB, 0x89, 0xBB, 0x3E, 0xF7, + 0x89, 0x68, 0x6D, 0x2C, 0xDF, 0x2C, 0x6F, 0xB2, 0x8D, 0x75, 0xF8, 0xC6, 0x57, 0x98, 0x47, 0x86, + 0x40, 0x72, 0xBB, 0xD7, 0x32, 0xCD, 0x7A, 0x15, 0x64, 0x83, 0xD9, 0x50, 0x2E, 0xDE, 0x0C, 0x8C, + 0x30, 0xCD, 0xB0, 0x64, 0xD6, 0x7F, 0xE7, 0xAD, 0x6E, 0x01, 0x3E, 0x14, 0x8A, 0x24, 0x86, 0x1F, + 0x92, 0xAB, 0x1A, 0xE5, 0xD6, 0xBF, 0x64, 0xE5, 0xF6, 0x34, 0x62, 0xD5, 0x92, 0x5B, 0x64, 0xC4, + 0xFC, 0x1B, 0x7F, 0xA0, 0x13, 0xC1, 0xD4, 0xEB, 0x92, 0x90, 0xEF, 0x8C, 0x5E, 0x75, 0x4B, 0x78, + 0x56, 0x5F, 0xA2, 0x55, 0x4B, 0x1B, 0xE3, 0xDD, 0x80, 0x7E, 0xCF, 0x69, 0x97, 0x5A, 0x76, 0xD5, + 0xAA, 0x7D, 0x85, 0x73, 0x10, 0x4E, 0x79, 0x9A, 0x10, 0x10, 0x01, 0xD8, 0xD7, 0x85, 0x41, 0x8D, + 0x3D, 0xEA, 0xE3, 0x59, 0xD9, 0x31, 0x4B, 0x23, 0xC6, 0x53, 0x11, 0xA6, 0x35, 0x29, 0x64, 0x7E, + 0x28, 0xD7, 0x8D, 0x03, 0x70, 0x5C, 0x53, 0xA7, 0x6D, 0x81, 0x30, 0x7B, 0xDF, 0x2B, 0xAE, 0xAB, + 0x6F, 0x52, 0x93, 0xCF, 0xDD, 0x00, 0x35, 0xE9, 0x65, 0x4A, 0x04, 0x79, 0x11, 0x30, 0xCA, 0xC7, + 0xD2, 0xF3, 0x34, 0xAC, 0x32, 0x1F, 0xE4, 0xE5, 0x83, 0x12, 0x66, 0xD6, 0xA6, 0xE4, 0xEB, 0x67, + 0x7E, 0xDD, 0x64, 0x3E, 0xF1, 0x0C, 0xE6, 0x1C, 0xB6, 0xE1, 0xB0, 0x2B, 0xE7, 0x83, 0xB4, 0x4A, + 0x82, 0xDD, 0xC1, 0x22, 0x3F, 0x03, 0x38, 0x90, 0xB2, 0xA9, 0x7B, 0x60, 0x57, 0xF9, 0xDD, 0x04, + 0x60, 0x5D, 0x7C, 0x2C, 0xD3, 0xE6, 0xFE, 0x02, 0x5A, 0x7D, 0x2A, 0x48, 0x81, 0x42, 0x20, 0x84, + 0xFF, 0x1D, 0xCC, 0x64, 0x11, 0x70, 0xE5, 0x4F, 0x9F, 0xE0, 0x11, 0xFB, 0xF0, 0xE2, 0xC4, 0x9B, + 0x11, 0x30, 0x7F, 0x2F, 0x7F, 0xA1, 0xB1, 0xBC, 0x5F, 0x29, 0x21, 0xDF, 0xB4, 0xEB, 0xB2, 0x4F, + 0xAA, 0x2D, 0x95, 0x60, 0x47, 0x78, 0x37, 0x67, 0xCD, 0xFA, 0x36, 0x17, 0x8F, 0x64, 0x15, 0xAF, + 0x04, 0xAA, 0x5C, 0x76, 0x23, 0x07, 0x64, 0x96, 0xB2, 0x5A, 0xCF, 0x03, 0xDC, 0xC3, 0x2D, 0xFB, + 0x0D, 0x2D, 0xA8, 0xBD, 0xCE, 0x58, 0xF8, 0x44, 0x75, 0xA1, 0x07, 0xAA, 0xDF, 0x25, 0xDC, 0x25, + 0x32, 0xCF, 0xA8, 0x92, 0xC5, 0xC0, 0xD5, 0x70, 0x21, 0x19, 0x6F, 0x32, 0xCA, 0x16, 0xFA, 0x8C, + 0xB2, 0x86, 0xF6, 0xD5, 0x2E, 0xD9, 0x0A, 0x9C, 0x96, 0xDB, 0x4D, 0xA4, 0x11, 0x02, 0x4C, 0x33, + 0x99, 0xB3, 0x5E, 0x45, 0x5C, 0xF1, 0x99, 0x61, 0x04, 0x20, 0xC9, 0xC8, 0xB3, 0xB4, 0xD3, 0x8B, + 0x24, 0x78, 0x55, 0x2E, 0xB7, 0x48, 0x43, 0x17, 0xBF, 0xB9, 0x2A, 0xCC, 0xD5, 0x4A, 0x49, 0xB2, + 0x4E, 0x1E, 0xC7, 0x45, 0x4E, 0x55, 0xC4, 0xBC, 0xB1, 0xD2, 0xA6, 0x62, 0xE9, 0x95, 0xBB, 0xCD, + 0x87, 0xD2, 0x7C, 0xE5, 0xC6, 0x77, 0x5B, 0xFF, 0xAF, 0xDD, 0x44, 0x9D, 0x3D, 0x10, 0x05, 0x3C, + 0x77, 0x7A, 0xF8, 0x84, 0x0D, 0x04, 0x55, 0xB5, 0x20, 0xEA, 0xD2, 0x3F, 0x04, 0x33, 0xFF, 0xE2, + 0x01, 0x3B, 0x51, 0x46, 0x3E, 0x34, 0xAA, 0x40, 0xCA, 0x7C, 0x85, 0x46, 0x57, 0xD9, 0xB7, 0xE1, + 0xDC, 0x65, 0xEF, 0x11, 0x66, 0x0B, 0xBA, 0xD8, 0x37, 0x66, 0x96, 0x64, 0x54, 0x8B, 0x05, 0x81, + 0x47, 0x87, 0x70, 0xA8, 0x54, 0xC6, 0x02, 0x08, 0xB4, 0xAD, 0x69, 0x3C, 0xC0, 0x03, 0x69, 0x19, + 0xE6, 0xAC, 0x13, 0xD5, 0x3F, 0x2C, 0x9D, 0x61, 0xCC, 0xA8, 0x60, 0xB5, 0x60, 0x85, 0x19, 0x5B, + 0x3E, 0x82, 0xCA, 0x34, 0x70, 0x06, 0xCD, 0x37, 0x3E, 0x91, 0x2F, 0x49, 0x82, 0x0A, 0x87, 0xE8, + 0x09, 0x18, 0xB3, 0xF4, 0x16, 0xA4, 0xA5, 0x46, 0xC5, 0x76, 0x6D, 0x73, 0xB1, 0x93, 0xDC, 0x69, + 0xF4, 0x49, 0xF8, 0x2F, 0x97, 0xB4, 0xAA, 0x19, 0x4A, 0x60, 0xD2, 0xF9, 0x7B, 0x5A, 0xF0, 0x57, + 0x45, 0xC6, 0xA2, 0x64, 0x37, 0x56, 0xED, 0x6B, 0x5E, 0x1D, 0x9E, 0x35, 0x5E, 0xFE, 0xDE, 0x04, + 0x08, 0x3B, 0x62, 0x40, 0x82, 0xD4, 0xF9, 0xF3, 0x54, 0x95, 0x3E, 0x57, 0x49, 0x3A, 0x41, 0x41, + 0xDA, 0xD4, 0x78, 0x80, 0xC7, 0xF1, 0xDE, 0xA7, 0xFF, 0xDC, 0x53, 0xC9, 0x3A, 0x37, 0xA5, 0x83, + 0xDE, 0xE8, 0x51, 0x33, 0x7B, 0xE6, 0xAC, 0xA5, 0xC4, 0x7D, 0x34, 0xB0, 0x99, 0x0D, 0x03, 0x34, + 0x0F, 0x8D, 0xB8, 0x10, 0x4E, 0x30, 0x38, 0x10, 0xFC, 0x62, 0xC3, 0xC0, 0xF5, 0x67, 0x69, 0xF5, + 0x23, 0xB4, 0xF2, 0x6B, 0x79, 0xA8, 0xEE, 0xF0, 0xDD, 0x06, 0xA1, 0x50, 0x41, 0x3E, 0x1D, 0x04, + 0xB0, 0xB7, 0xC8, 0x58, 0x20, 0x72, 0xF6, 0x41, 0x53, 0x58, 0xAA, 0xAB, 0xA1, 0x19, 0xB0, 0x99, + 0xE4, 0x35, 0x44, 0x32, 0xF0, 0x34, 0x52, 0x4E, 0xD0, 0xBC, 0x1D, 0xD2, 0x14, 0x6A, 0x22, 0x46, + 0x93, 0x52, 0xFF, 0xD5, 0xD9, 0xEF, 0x19, 0xAF, 0xFB, 0x9A, 0x0D, 0x4A, 0x14, 0x49, 0xAE, 0xC0, + 0x09, 0x5E, 0x40, 0x36, 0x63, 0xE7, 0xC3, 0xFA, 0x84, 0x1C, 0xBF, 0x65, 0x17, 0xED, 0xED, 0x49, + 0x93, 0xB9, 0xCD, 0x85, 0x3F, 0x95, 0x57, 0xF9, 0xE7, 0x59, 0x55, 0x59, 0xED, 0x3A, 0xA1, 0x90, + 0x24, 0x36, 0xB8, 0x38, 0x91, 0x57, 0x59, 0xDB, 0xDE, 0x8E, 0x8B, 0x3A, 0xBB, 0x31, 0x76, 0x39, + 0x0D, 0x20, 0x84, 0x8A, 0x05, 0x5A, 0xC1, 0x93, 0x04, 0x5E, 0x9E, 0x2E, 0x41, 0x13, 0xC9, 0x7F, + 0xAB, 0x45, 0x86, 0x99, 0xB2, 0x5C, 0x7E, 0x1A, 0x1C, 0x9C, 0x7C, 0xCF, 0x60, 0x38, 0xF7, 0x02, + 0x8A, 0xE6, 0x74, 0x4F, 0x31, 0x67, 0x99, 0x96, 0xC3, 0x79, 0xEB, 0x19, 0x07, 0xAD, 0xBB, 0xD2, + 0xD7, 0x51, 0x75, 0xDD, 0xD5, 0x44, 0xC2, 0x9A, 0xDE, 0x01, 0x68, 0x8C, 0xBF, 0x11, 0xFE, 0x5F, + 0xB9, 0xCF, 0x25, 0x35, 0xD2, 0xF7, 0x29, 0xE0, 0x63, 0x0F, 0x0A, 0xCA, 0xB9, 0x38, 0xB6, 0xDA, + 0xDD, 0x66, 0xD9, 0x5F, 0x67, 0x42, 0x15, 0xD8, 0x1C, 0xD1, 0x1C, 0xCC, 0xD6, 0xB9, 0x29, 0x85, + 0x99, 0xD6, 0xDD, 0xCE, 0x8E, 0x3D, 0x63, 0x1C, 0x31, 0x32, 0x37, 0xCD, 0xE1, 0xF8, 0xEA, 0x4E, + 0x31, 0x25, 0xF7, 0x2D, 0xE1, 0xCA, 0x36, 0x5B, 0xEC, 0xAC, 0x2B, 0xB0, 0xA5, 0x69, 0x3B, 0x4C, + 0xAE, 0xD7, 0x15, 0xF7, 0x7F, 0x5C, 0x5F, 0x82, 0xF0, 0x1D, 0x44, 0x62, 0xA0, 0x72, 0x57, 0xAD, + 0xB3, 0xD7, 0x1B, 0xA9, 0xE2, 0x76, 0xFF, 0x18, 0xA6, 0x3E, 0xF9, 0xF5, 0x6F, 0xB5, 0x13, 0x26, + 0x72, 0x0F, 0xF4, 0xE1, 0x7D, 0x43, 0x86, 0x48, 0x42, 0x0B, 0x94, 0x96, 0xBF, 0x0E, 0x32, 0x1C, + 0xE0, 0x18, 0x69, 0xA9, 0xAE, 0x83, 0x6F, 0x36, 0xE7, 0x04, 0x20, 0xEE, 0x34, 0xFF, 0x21, 0xE9, + 0xBA, 0x3B, 0x38, 0x48, 0x2D, 0x81, 0x38, 0x48, 0x25, 0xE5, 0x4A, 0xAD, 0x81, 0xA9, 0xE8, 0x33, + 0x0A, 0x4C, 0x60, 0xAE, 0xBB, 0xCC, 0x24, 0x96, 0x44, 0xF3, 0x1A, 0x2A, 0x89, 0xCB, 0xD9, 0x5E, + 0x89, 0x4C, 0xFD, 0x62, 0xA8, 0x38, 0x1E, 0x73, 0x63, 0xB3, 0xF1, 0x3E, 0xAC, 0xB1, 0x0B, 0x5D, + 0x10, 0xE5, 0xCC, 0x88, 0x2F, 0x9D, 0x57, 0xF1, 0x33, 0xA6, 0x50, 0x13, 0x2C, 0x54, 0x81, 0x1B, + 0x90, 0xD8, 0x6F, 0x7C, 0x02, 0x86, 0xA8, 0x02, 0x5F, 0xCE, 0x24, 0x22, 0x76, 0x73, 0xE8, 0x66, + 0xDC, 0x7F, 0x8F, 0x69, 0x4E, 0xBB, 0x63, 0xEB, 0xCD, 0x38, 0xE6, 0xEE, 0x84, 0x70, 0x97, 0x2F, + 0xD1, 0x77, 0xEE, 0x63, 0xE4, 0x2D, 0x42, 0xDE, 0x17, 0x95, 0x18, 0xB5, 0xA9, 0x4D, 0xFD, 0x2A, + 0xA8, 0x07, 0x1F, 0xCC, 0x3F, 0x22, 0x3B, 0x03, 0x49, 0xCF, 0x83, 0x83, 0x85, 0xAA, 0x87, 0xA0, + 0xC6, 0x30, 0x8C, 0x8F, 0x91, 0x88, 0x67, 0xA7, 0x1F, 0xE0, 0xE2, 0x40, 0x1E, 0xE9, 0x12, 0x2E, + 0x5C, 0x33, 0x44, 0x29, 0x0D, 0xEA, 0x34, 0x8B, 0x1A, 0x48, 0x80, 0x67, 0x45, 0x2A, 0xF2, 0x82, + 0xD8, 0x78, 0x7E, 0x36, 0x59, 0x6C, 0x32, 0x58, 0x3B, 0x0F, 0x2C, 0x0B, 0xD1, 0xFA, 0x67, 0x2B, + 0x0C, 0xFF, 0x16, 0x57, 0x04, 0x38, 0x51, 0x4F, 0x34, 0xEE, 0x94, 0x8B, 0xBB, 0x7B, 0xBE, 0xEA, + 0x37, 0xB5, 0x9F, 0xBA, 0xDE, 0xC1, 0xC6, 0x34, 0x2D, 0x36, 0xE6, 0xC8, 0x67, 0x6A, 0x74, 0x56, + 0xB5, 0x05, 0x53, 0xAE, 0x5C, 0x94, 0x83, 0xF9, 0xE0, 0xD7, 0x21, 0xC2, 0x71, 0x4B, 0x0F, 0x9C, + 0x64, 0x1C, 0xF4, 0x6A, 0xF8, 0xE3, 0x3C, 0x8F, 0xD2, 0x20, 0xCF, 0x14, 0xAC, 0x21, 0xF5, 0x2A, + 0xE7, 0xEC, 0x5C, 0x49, 0xAB, 0x21, 0xF2, 0x41, 0x2C, 0x3B, 0xA0, 0x49, 0x43, 0xF3, 0x14, 0xFE, + 0x68, 0x7C, 0x83, 0x05, 0xF3, 0x71, 0x77, 0x02, 0x2B, 0xD4, 0x94, 0x2B, 0x28, 0x5E, 0x4A, 0x5E, + 0x6E, 0x81, 0x1A, 0xD3, 0xDA, 0x58, 0x9F, 0xD6, 0x7B, 0x6D, 0xAD, 0x14, 0xBC, 0x60, 0xFC, 0xC4, + 0x83, 0x0A, 0x8E, 0x9B, 0x8D, 0x5B, 0x24, 0x77, 0x10, 0x34, 0x78, 0xC9, 0x8F, 0xA5, 0x2D, 0x0F, + 0x6A, 0x88, 0x7F, 0x24, 0x40, 0x46, 0x25, 0x3A, 0xDE, 0xB9, 0x9E, 0xA2, 0xE7, 0x8D, 0x52, 0xA2, + 0xFF, 0xDE, 0xB4, 0x95, 0xDB, 0x05, 0x2E, 0xDF, 0x29, 0x28, 0xB5, 0x76, 0xD6, 0x1D, 0x09, 0x45, + 0x69, 0x29, 0xF9, 0x95, 0xAA, 0x36, 0x71, 0xD9, 0x3F, 0xFF, 0x6B, 0x04, 0xFE, 0xED, 0x63, 0xC8, + 0x3C, 0x4B, 0x6B, 0x0B, 0xF3, 0xD8, 0x71, 0x15, 0xDA, 0xC0, 0xC9, 0xE2, 0x0D, 0x87, 0x94, 0x61, + 0xBE, 0xEF, 0x79, 0x92, 0x4C, 0x14, 0x92, 0xBF, 0x0C, 0x4E, 0xA0, 0x1B, 0x58, 0x00, 0x30, 0xF6, + 0xD0, 0x09, 0xD6, 0x1E, 0x81, 0xA0, 0xE7, 0xFD, 0xFD, 0xFF, 0x21, 0x47, 0xAB, 0xDE, 0x67, 0xC6, + 0xF4, 0x19, 0x60, 0x0C, 0x49, 0xE5, 0xC4, 0xBD, 0x64, 0x05, 0xED, 0x89, 0xD7, 0xBD, 0x74, 0xF7, + 0xD4, 0xCC, 0x4B, 0x9E, 0xEB, 0x6E, 0xB7, 0x87, 0xB6, 0x31, 0x07, 0xCB, 0x6E, 0x0D, 0xDF, 0x3A, + 0xD8, 0x64, 0x1F, 0xF9, 0x2C, 0xEE, 0xC0, 0x61, 0x22, 0x0E, 0x5A, 0x20, 0x0C, 0xD4, 0x4F, 0xC5, + 0x2D, 0x82, 0x63, 0x39, 0x36, 0x34, 0x07, 0xC6, 0x23, 0xBC, 0xF1, 0x56, 0xC6, 0x8C, 0x39, 0x23, + 0x43, 0xFF, 0xEC, 0xBE, 0x95, 0x7B, 0xC7, 0xFD, 0xA9, 0x99, 0x3D, 0xDF, 0x50, 0x28, 0x39, 0xCA, + 0x80, 0xCF, 0x1C, 0xE7, 0x81, 0x06, 0xB4, 0x43, 0x55, 0xFB, 0xB0, 0xA4, 0x5D, 0x78, 0x39, 0x71, + 0x88, 0xEC, 0xBB, 0x01, 0x69, 0x5E, 0x85, 0x97, 0x1F, 0xEB, 0x6C, 0x82, 0x07, 0xF4, 0x00, 0x1B, + 0x90, 0x03, 0x1B, 0x92, 0x9A, 0x4A, 0x95, 0x9D, 0x45, 0x45, 0x65, 0x6F, 0x8B, 0x70, 0x4C, 0xFE, + 0x48, 0x94, 0x5B, 0x71, 0x86, 0x70, 0x45, 0xB7, 0x85, 0xD9, 0x59, 0x29, 0x94, 0x47, 0x5A, 0x17, + 0x96, 0xA1, 0x0E, 0xFD, 0x72, 0x71, 0xE1, 0x3F, 0x7B, 0xE6, 0x59, 0x2A, 0x91, 0x5A, 0x6B, 0x82, + 0xB1, 0xA1, 0x31, 0xC2, 0xE4, 0xE3, 0xB9, 0x8E, 0x5A, 0x68, 0xC1, 0x18, 0xD4, 0x4B, 0x02, 0x2E, + 0x50, 0x3D, 0x73, 0x53, 0x2B, 0x13, 0x77, 0x19, 0xC7, 0x03, 0x0B, 0xB5, 0xAD, 0x5C, 0x0B, 0x6B, + 0x66, 0x12, 0xF0, 0xE8, 0x67, 0xB7, 0xF7, 0x88, 0x64, 0xA8, 0x2C, 0x51, 0xA7, 0xF8, 0x5E, 0xB3, + 0x39, 0xEC, 0x1B, 0xDF, 0x6C, 0x89, 0x16, 0xFD, 0x51, 0x26, 0x29, 0x8D, 0x0E, 0xBE, 0x1B, 0xCE, + 0x61, 0xE5, 0x22, 0x09, 0xC1, 0x0F, 0xAD, 0x0C, 0xA1, 0x61, 0xF8, 0x49, 0x29, 0x11, 0x7C, 0x93, + 0x0C, 0xBE, 0xD0, 0x11, 0x6F, 0x24, 0x4E, 0x4B, 0xF5, 0xEF, 0x41, 0x3D, 0x0C, 0x69, 0xC6, 0xA6, + 0xBF, 0x87, 0x68, 0xFF, 0x2F, 0x76, 0xD9, 0xFD, 0x1D, 0x8E, 0x9F, 0x80, 0x19, 0x3B, 0x35, 0x8B, + 0x2D, 0xDB, 0x5C, 0x3E, 0x86, 0xE8, 0xBF, 0xF1, 0x30, 0x88, 0xE4, 0x80, 0xD0, 0x49, 0xC3, 0x50, + 0xF8, 0x1E, 0xCE, 0xDA, 0xAC, 0x2E, 0x3F, 0x97, 0x51, 0x12, 0x89, 0x47, 0x5D, 0xF4, 0xD4, 0x77, + 0x93, 0x66, 0x74, 0xE3, 0x4C, 0xCD, 0xB4, 0xC8, 0x00, 0x85, 0x64, 0x8C, 0x04, 0x72, 0x1C, 0x14, + 0xDA, 0x77, 0xD7, 0x1D, 0x39, 0xC3, 0x65, 0xD0, 0x28, 0x51, 0xCA, 0x91, 0xAE, 0x2D, 0xCD, 0x50, + 0x0D, 0x1B, 0xA4, 0xF1, 0x5D, 0x4C, 0x28, 0x1C, 0x57, 0xE5, 0x00, 0xEC, 0xA4, 0x02, 0x3B, 0xCA, + 0x70, 0xF3, 0x8B, 0x3C, 0x4F, 0xEE, 0x9D, 0x08, 0xFF, 0x66, 0x31, 0xCE, 0x37, 0x93, 0x90, 0x3D, + 0x6E, 0xDE, 0xB9, 0xCF, 0x35, 0xAA, 0xF0, 0x43, 0x3E, 0x6B, 0x19, 0xEC, 0x69, 0x7A, 0xF0, 0xC6, + 0xC3, 0x7D, 0x49, 0x89, 0x43, 0xCC, 0x2C, 0x20, 0x2E, 0xB8, 0x5D, 0xCC, 0xDB, 0x39, 0xCB, 0x0B, + 0x76, 0xD8, 0xAC, 0xD6, 0x2A, 0x76, 0x59, 0x7F, 0x6A, 0x1B, 0xCD, 0x4A, 0x93, 0xCA, 0x42, 0x5F, + 0xC7, 0x98, 0xFA, 0xC9, 0x30, 0x2E, 0x9F, 0x8F, 0xE5, 0xB5, 0x37, 0x41, 0x19, 0xF4, 0xA0, 0xE5, + 0xDA, 0x7D, 0x3C, 0xF5, 0x61, 0xCC, 0x98, 0x27, 0xED, 0xE4, 0x5C, 0x0E, 0x7C, 0x1B, 0x33, 0x38, + 0x77, 0x20, 0x92, 0xC9, 0xD3, 0x38, 0xC3, 0x03, 0x2C, 0xAF, 0xE2, 0x77, 0x34, 0x4B, 0xE2, 0x1C, + 0x9F, 0xE4, 0x4D, 0xAB, 0x12, 0xFE, 0xCD, 0xB3, 0x2C, 0xD3, 0xE2, 0x42, 0xB8, 0xE7, 0xE0, 0x14, + 0x88, 0x31, 0xB1, 0xDC, 0x35, 0xDE, 0xCD, 0x3D, 0x3B, 0xDF, 0x6C, 0x00, 0xA3, 0x48, 0xA6, 0x71, + 0x7E, 0xC6, 0x3A, 0xE8, 0x07, 0xCE, 0xC8, 0xE7, 0xDC, 0xB1, 0x98, 0x17, 0xDB, 0x75, 0x20, 0xFE, + 0x38, 0xC7, 0x1F, 0x02, 0x8E, 0xE4, 0x91, 0x79, 0x3D, 0xC0, 0x50, 0x2D, 0xC7, 0x49, 0x33, 0x6C, + 0xDF, 0x3C, 0xE9, 0x42, 0xE9, 0x27, 0xBC, 0x39, 0x38, 0xAA, 0x98, 0x16, 0x6E, 0x1F, 0x71, 0xDF, + 0x3B, 0xBA, 0x15, 0x2F, 0x69, 0xEB, 0x06, 0x5C, 0xFB, 0xF8, 0x4C, 0x83, 0x2C, 0x28, 0xC2, 0x19, + 0xAD, 0x04, 0xA1, 0xB0, 0x7F, 0xF1, 0x9C, 0x84, 0x38, 0x42, 0x45, 0x3E, 0x1E, 0xC7, 0x95, 0xD5, + 0xAF, 0x35, 0x7E, 0x2A, 0xCC, 0x06, 0x9D, 0x9A, 0xCF, 0xC2, 0x56, 0xE6, 0x73, 0x7F, 0x7E, 0x9D, + 0x9B, 0x01, 0x27, 0x76, 0x14, 0x8C, 0x3E, 0x3A, 0xBD, 0x2E, 0x6C, 0x7C, 0xB7, 0xF2, 0x9A, 0x92, + 0x41, 0xBC, 0xD0, 0x48, 0xF6, 0xE6, 0x16, 0x62, 0x01, 0x4D, 0x3D, 0x8E, 0xD2, 0x98, 0x8F, 0x61, + 0x70, 0x7C, 0x41, 0xCC, 0xCA, 0x3D, 0x3E, 0x0B, 0x70, 0xC3, 0x9F, 0x9D, 0x3E, 0x33, 0x50, 0x2B, + 0xB0, 0x47, 0xC8, 0xA3, 0xAA, 0x55, 0xBA, 0x16, 0x3B, 0xF4, 0x07, 0x98, 0x1B, 0x6C, 0x49, 0x6A, + 0xA5, 0xB4, 0x7A, 0xBE, 0x28, 0x37, 0xF2, 0x55, 0x69, 0x09, 0x7A, 0xEC, 0x94, 0x1C, 0x60, 0xE3, + 0xB5, 0x89, 0x07, 0x58, 0x43, 0xA3, 0x3F, 0x1D, 0x94, 0x20, 0x49, 0x5E, 0xC1, 0xB7, 0x4E, 0x2C, + 0x75, 0x95, 0x54, 0x91, 0x4A, 0x01, 0x90, 0xF8, 0xF1, 0x81, 0xC6, 0x4C, 0x9A, 0x63, 0x20, 0x55, + 0x65, 0x8D, 0x30, 0xA2, 0xD4, 0xC7, 0xAF, 0x18, 0xA5, 0x83, 0xB6, 0x68, 0x1B, 0x35, 0x13, 0x6D, + 0xC6, 0x77, 0x5A, 0x04, 0xFB, 0xD5, 0xBD, 0x2B, 0x0D, 0x55, 0x5E, 0xEC, 0x7A, 0x80, 0x17, 0x01, + 0x9C, 0x4F, 0x55, 0x39, 0x57, 0x9F, 0x31, 0x9E, 0xB9, 0xB1, 0x35, 0xD5, 0x2F, 0xB9, 0xF3, 0x6A, + 0x9C, 0x30, 0xEA, 0x1B, 0xE9, 0x34, 0x4A, 0x2F, 0xB1, 0x36, 0x9C, 0xF0, 0x8A, 0xE9, 0x62, 0xDB, + 0x06, 0x32, 0x64, 0x39, 0x58, 0x29, 0xBD, 0xB1, 0x2C, 0x06, 0x56, 0x54, 0xAF, 0x6B, 0x97, 0x5A, + 0x7D, 0x49, 0xB6, 0xDF, 0x06, 0xFC, 0x9F, 0x06, 0x64, 0x89, 0xF5, 0xF4, 0xC4, 0x55, 0x02, 0x19, + 0xAA, 0xC9, 0x1D, 0x8A, 0x5E, 0x3D, 0xA7, 0x13, 0xEC, 0x52, 0x29, 0x8B, 0x6E, 0xC5, 0xA0, 0x62, + 0x9E, 0x89, 0x96, 0xDF, 0x5E, 0x74, 0x69, 0x53, 0x75, 0xCA, 0xF0, 0x95, 0x0E, 0xF8, 0x42, 0xB6, + 0x06, 0x62, 0xDA, 0x92, 0x48, 0xC3, 0x37, 0x50, 0x59, 0xDF, 0x59, 0xAF, 0xAF, 0x0E, 0x2E, 0x84, + 0xE5, 0x2F, 0x3C, 0xC9, 0x7E, 0x2A, 0xE5, 0xA9, 0x41, 0xB1, 0x51, 0x82, 0xC6, 0x42, 0xEC, 0x65, + 0xFD, 0xCB, 0x54, 0x29, 0x33, 0xC2, 0xE5, 0x5E, 0x10, 0xFB, 0x9E, 0x19, 0xE2, 0x75, 0x53, 0x43, + 0x50, 0xD5, 0x10, 0x8E, 0xBC, 0xC6, 0x2A, 0x0A, 0x8D, 0x7F, 0x4A, 0xF6, 0x07, 0x28, 0xA1, 0xEB, + 0x14, 0x1C, 0xD3, 0xE9, 0x63, 0x55, 0xC7, 0xD2, 0xE8, 0xB2, 0x3D, 0x17, 0x84, 0x63, 0xF9, 0x11, + 0xA4, 0x11, 0xE0, 0xA1, 0x83, 0x11, 0x11, 0xD2, 0xA0, 0x8C, 0x61, 0x74, 0x36, 0x63, 0xE9, 0xE8, + 0x98, 0x4C, 0x20, 0x38, 0x1F, 0xA5, 0x15, 0x60, 0x3E, 0x5C, 0x1B, 0xE6, 0xDE, 0xC1, 0x70, 0x7F, + 0xCB, 0x92, 0x76, 0x05, 0xD8, 0x63, 0xDF, 0x01, 0x7E, 0xF2, 0xF1, 0x01, 0xBD, 0xCE, 0x9A, 0x1E, + 0x50, 0x7F, 0xB4, 0xF4, 0x49, 0x5D, 0x7F, 0xCA, 0x86, 0x83, 0x2F, 0x63, 0x33, 0xEF, 0x4F, 0x35, + 0xA1, 0xBF, 0xB6, 0xCD, 0x25, 0xBA, 0x0E, 0xB9, 0xA3, 0x96, 0x41, 0xD4, 0x90, 0xFF, 0xEB, 0xF7, + 0x4F, 0x93, 0xC8, 0xD7, 0x04, 0x62, 0x6E, 0x88, 0xD7, 0x71, 0x0E, 0x0E, 0x37, 0x50, 0xCE, 0xFA, + 0x9B, 0xBD, 0x5B, 0xB0, 0xB7, 0x0F, 0x70, 0x75, 0x0A, 0x5D, 0xFC, 0x69, 0xB3, 0x07, 0x11, 0x9B, + 0xE8, 0x13, 0x0D, 0xBF, 0x08, 0x33, 0x59, 0x20, 0x4A, 0xBD, 0x27, 0x76, 0xED, 0xF2, 0x36, 0x0B, + 0xEA, 0xBC, 0x78, 0x17, 0xBD, 0x6E, 0x4F, 0x37, 0xD3, 0x26, 0x86, 0x85, 0xB1, 0x71, 0xEA, 0x55, + 0x73, 0xAA, 0x7C, 0xA2, 0x36, 0x75, 0xD2, 0xCD, 0xE4, 0xFC, 0xE7, 0xCE, 0x5E, 0xB1, 0xE5, 0xF4, + 0x65, 0xD7, 0x8F, 0x34, 0x07, 0x66, 0xA7, 0x4B, 0xCE, 0x55, 0xB0, 0x71, 0xFD, 0x20, 0x03, 0x5C, + 0xBA, 0x28, 0x0B, 0x71, 0x4D, 0x93, 0xB9, 0x77, 0x5E, 0x46, 0x6A, 0xCB, 0xD7, 0x0A, 0x59, 0x2E, + 0x26, 0x49, 0x0A, 0x36, 0x0F, 0x03, 0x6F, 0x32, 0xD7, 0xF0, 0xEC, 0x53, 0xF6, 0x0B, 0x1E, 0x08, + 0x27, 0x37, 0x69, 0xC5, 0x9F, 0x6C, 0x76, 0x27, 0x4C, 0x7A, 0x7C, 0xB7, 0xC8, 0x1B, 0xC4, 0x79, + 0xA1, 0xE3, 0xE0, 0xF3, 0x3B, 0x20, 0x07, 0x0C, 0xE4, 0xAB, 0x10, 0x6F, 0xA4, 0xFA, 0x7F, 0x08, + 0x6F, 0x5C, 0xAE, 0x06, 0xC5, 0x6D, 0x09, 0x1E, 0x2D, 0xDD, 0x80, 0xA7, 0x7B, 0x9E, 0xE6, 0x44, + 0x79, 0x3D, 0x55, 0x12, 0x51, 0x87, 0x4D, 0x4A, 0x66, 0x32, 0x2D, 0x4E, 0x17, 0x30, 0xAF, 0x77, + 0x7A, 0x8B, 0x44, 0x8D, 0xA4, 0xF8, 0x70, 0xC1, 0x99, 0x55, 0x3D, 0x4B, 0x08, 0x40, 0x2F, 0xA2, + 0xEA, 0xAA, 0x94, 0x24, 0xC3, 0xBD, 0x5C, 0x68, 0x35, 0x8D, 0x71, 0xA8, 0x3E, 0xBA, 0x00, 0x70, + 0x28, 0xB8, 0x10, 0x20, 0x8F, 0x5C, 0xE4, 0xC4, 0xBB, 0x22, 0x59, 0xA2, 0x35, 0x5F, 0x7A, 0x3D, + 0xF1, 0x24, 0x70, 0x1C, 0xE3, 0x3E, 0xED, 0x26, 0x07, 0xD7, 0x82, 0x4B, 0x80, 0x3B, 0x0C, 0xE4, + 0xAB, 0xCF, 0x71, 0x97, 0x87, 0x22, 0x2B, 0x53, 0x27, 0x99, 0x29, 0x10, 0x41, 0x30, 0xE8, 0x28, + 0xD2, 0x48, 0xAC, 0x25, 0x40, 0xBF, 0xDB, 0xED, 0x3A, 0xF4, 0x5D, 0x6E, 0x66, 0x1A, 0x08, 0xFF, + 0xEE, 0x49, 0x36, 0xD7, 0x68, 0x1E, 0xD7, 0xAB, 0xEC, 0xD6, 0x84, 0x1C, 0x8D, 0x35, 0x2D, 0x10, + 0x3C, 0x9C, 0x77, 0x12, 0xB3, 0x09, 0x5F, 0x0B, 0x2A, 0xB3, 0xCF, 0x8E, 0xE0, 0xF1, 0xAA, 0x71, + 0xB1, 0xE3, 0x58, 0x5C, 0xFF, 0xD1, 0x34, 0xE0, 0xBF, 0x20, 0x6D, 0x42, 0x86, 0xCA, 0x97, 0x1B, + 0x76, 0x2F, 0x08, 0x29, 0xEC, 0xD5, 0xDD, 0x04, 0x36, 0xFC, 0xCA, 0x39, 0xBE, 0x28, 0x8C, 0x1F, + 0x0D, 0x56, 0x77, 0xB7, 0xE0, 0x23, 0x41, 0x1E, 0xB4, 0x29, 0x17, 0x7A, 0xAF, 0xF9, 0x30, 0xCF, + 0xF1, 0xFE, 0xF4, 0x62, 0x32, 0xD9, 0xDB, 0x56, 0x9A, 0x2B, 0x31, 0xBF, 0xA5, 0x15, 0x19, 0x1E, + 0xEA, 0xCB, 0x5D, 0x6B, 0x65, 0x9A, 0x06, 0x1F, 0x9C, 0x0E, 0xC0, 0x5D, 0x8C, 0xFB, 0xD5, 0xCF, + 0xA4, 0x18, 0xA1, 0x0C, 0xAD, 0xD3, 0x98, 0x09, 0x3A, 0x86, 0x7F, 0x1C, 0x60, 0xC9, 0xFB, 0x42, + 0xF2, 0x37, 0x4E, 0xF0, 0x91, 0x02, 0x33, 0x41, 0xDE, 0xDB, 0xF8, 0x8E, 0x44, 0x00, 0xC5, 0x94, + 0x21, 0x39, 0x91, 0x0A, 0xB5, 0xC4, 0x44, 0xAC, 0x04, 0xF8, 0xB7, 0xA1, 0x13, 0x70, 0xA7, 0xEF, + 0x23, 0xBD, 0xF6, 0x12, 0x34, 0x30, 0x3A, 0x70, 0x81, 0x21, 0xE7, 0x66, 0xB8, 0x55, 0x00, 0xAF, + 0xC1, 0xC3, 0x56, 0x3D, 0xAB, 0x3D, 0xCA, 0x16, 0x4F, 0x6B, 0x3E, 0x69, 0xEF, 0xF8, 0xCA, 0x7B, + 0x65, 0x1C, 0xF3, 0xD9, 0xE8, 0xB0, 0xF6, 0xF3, 0x18, 0x9E, 0xDF, 0x45, 0xC7, 0xAF, 0xCE, 0xC8, + 0x5E, 0x51, 0x94, 0x76, 0x23, 0x80, 0xF8, 0x49, 0x9B, 0xB9, 0x7C, 0x2F, 0x3C, 0xE6, 0xB5, 0x2F, + 0xAD, 0xCC, 0xE7, 0xE7, 0x1E, 0x08, 0xBF, 0xFA, 0x70, 0x2C, 0xB3, 0xED, 0x0C, 0x29, 0x7D, 0xB0, + 0xBE, 0xE7, 0x91, 0x39, 0x73, 0xC2, 0x80, 0x77, 0x2F, 0x91, 0x1D, 0x2F, 0x45, 0x1E, 0x41, 0xE4, + 0x45, 0x2A, 0x7E, 0x93, 0xCE, 0x6D, 0x65, 0x18, 0x76, 0x61, 0x15, 0x05, 0x24, 0x0E, 0x65, 0xD6, + 0x19, 0x7A, 0xFF, 0x02, 0x94, 0xFB, 0x2D, 0x14, 0xE4, 0xA3, 0x9C, 0xFC, 0x48, 0x29, 0x3A, 0x7F, + 0x36, 0x4F, 0x18, 0xD5, 0x5B, 0x99, 0x4C, 0x97, 0x20, 0x36, 0x77, 0xA6, 0x75, 0xE3, 0x44, 0x92, + 0x47, 0x72, 0xEA, 0x1D, 0x00, 0x5A, 0x1D, 0xAF, 0x12, 0xAC, 0x26, 0xE9, 0x1E, 0x4C, 0x89, 0xCC, + 0x56, 0x01, 0x22, 0x4D, 0x45, 0x44, 0xAC, 0xB6, 0x75, 0xEF, 0x3F, 0x2B, 0x35, 0xC6, 0x06, 0x12, + 0xF6, 0xDB, 0xF1, 0x55, 0xF7, 0x05, 0xB0, 0xC0, 0x16, 0x13, 0x60, 0xAA, 0x01, 0x68, 0x1A, 0xCF, + 0xA3, 0xDE, 0xC2, 0xED, 0x60, 0xB9, 0x38, 0x0A, 0x78, 0x7C, 0x5A, 0x96, 0x70, 0x3E, 0x1E, 0xDC, + 0xCD, 0x80, 0xDE, 0x5B, 0x63, 0x94, 0x01, 0x9D, 0x68, 0x02, 0xB9, 0x64, 0xBC, 0x89, 0xCA, 0xB4, + 0x12, 0xD7, 0x5E, 0x20, 0xC7, 0xBD, 0x39, 0x21, 0xAD, 0x74, 0x3A, 0x04, 0x8F, 0x5F, 0xE2, 0x55, + 0xE2, 0xA4, 0x8F, 0xE0, 0xFB, 0x9D, 0xBD, 0x67, 0xCF, 0xD8, 0x93, 0x6C, 0x84, 0xE7, 0xB6, 0xCE, + 0xBD, 0x7B, 0xDA, 0x93, 0x18, 0x70, 0x6B, 0x48, 0xBA, 0x0E, 0x66, 0x09, 0x2E, 0x91, 0x55, 0x38, + 0x84, 0x02, 0x18, 0x1D, 0x49, 0xDE, 0x25, 0xB3, 0x7E, 0xE8, 0xD0, 0x6E, 0xDD, 0x13, 0x8F, 0xA4, + 0x95, 0x17, 0x01, 0x0D, 0x93, 0xB0, 0xD8, 0xBD, 0x0C, 0xCA, 0x48, 0x62, 0xFA, 0xF5, 0xEA, 0xC5, + 0x71, 0x21, 0x00, 0xEC, 0x3A, 0x88, 0x26, 0xA1, 0x52, 0xBA, 0xBF, 0x2A, 0x70, 0xEB, 0xF7, 0x2B, + 0x43, 0xF4, 0xF6, 0xE3, 0xD0, 0x63, 0x1A, 0xA1, 0x0C, 0x00, 0xFE, 0xF9, 0x12, 0xE1, 0xED, 0x2A, + 0xFD, 0x19, 0x4E, 0x51, 0x22, 0xA0, 0x4C, 0x09, 0x2F, 0x0B, 0x8A, 0x57, 0xFA, 0x3E, 0xF3, 0x02, + 0xE3, 0xF0, 0x8F, 0x17, 0x6E, 0xC1, 0x45, 0x34, 0x95, 0x61, 0x22, 0x9E, 0x72, 0xA9, 0x50, 0x77, + 0x07, 0x64, 0xEE, 0x52, 0x03, 0x10, 0xBA, 0x09, 0xF9, 0x45, 0x29, 0x58, 0x46, 0x24, 0xE7, 0x0F, + 0x21, 0xE0, 0xC8, 0xC8, 0x69, 0xCB, 0x4C, 0xD8, 0x39, 0x0E, 0x0C, 0x24, 0x68, 0x46, 0x1E, 0xD9, + 0x7A, 0x8C, 0xB2, 0x91, 0xF4, 0x1B, 0x96, 0xDE, 0x63, 0xFF, 0xE7, 0xCB, 0x86, 0x9F, 0xCD, 0xFB, + 0xBF, 0x67, 0xBE, 0x46, 0xF7, 0x0E, 0x1F, 0x1D, 0x77, 0x4F, 0x66, 0x4F, 0x4F, 0x09, 0x4E, 0x79, + 0x33, 0x80, 0x66, 0xA5, 0xD0, 0x47, 0xAD, 0x50, 0x3D, 0x45, 0xE5, 0x15, 0xCB, 0x05, 0xA9, 0xC8, + 0xFB, 0x0F, 0x00, 0xB6, 0x9F, 0xF7, 0xC2, 0x1B, 0x15, 0x2B, 0xD1, 0x01, 0xA2, 0x5A, 0xFB, 0x26, + 0x3D, 0x9E, 0xAC, 0x37, 0x2C, 0x0B, 0x3A, 0xD3, 0xE8, 0x99, 0xAF, 0xB0, 0x12, 0x17, 0x06, 0x0C, + 0x7B, 0xF1, 0x6D, 0xB5, 0x8D, 0x18, 0xE4, 0x32, 0x3F, 0x51, 0xC2, 0x20, 0x20, 0xC6, 0x47, 0x22, + 0x08, 0x94, 0x32, 0x99, 0x17, 0x4A, 0x50, 0x36, 0x1E, 0xA2, 0x88, 0xCE, 0x01, 0xAF, 0x78, 0xF5, + 0x6B, 0xF2, 0xA2, 0x0C, 0x8E, 0xC5, 0xE4, 0x31, 0x9C, 0x28, 0xA4, 0x7F, 0x4E, 0x64, 0x1D, 0xF5, + 0xC1, 0x1A, 0x68, 0xE2, 0xF4, 0x3A, 0x99, 0xBC, 0xD3, 0x31, 0xF9, 0xD8, 0x58, 0x7B, 0xB1, 0xB7, + 0x7D, 0x57, 0x2B, 0x7D, 0xAC, 0x4A, 0x43, 0x9E, 0xB2, 0x50, 0x96, 0x06, 0x99, 0x17, 0x89, 0x6A, + 0xA7, 0x2E, 0xC2, 0xB9, 0xA2, 0xBB, 0x96, 0xD4, 0x03, 0xD5, 0xF2, 0xB4, 0xA7, 0x78, 0xE6, 0x65, + 0x31, 0xD2, 0x43, 0x75, 0x4A, 0xD1, 0xB5, 0xE6, 0x07, 0x98, 0x27, 0xAA, 0xBD, 0xCD, 0x32, 0xF1, + 0x80, 0xCE, 0x9E, 0xCD, 0xF2, 0xA1, 0x50, 0xD0, 0x88, 0x02, 0xF0, 0x1C, 0x10, 0x70, 0xAA, 0xA5, + 0xDF, 0x70, 0x32, 0x7E, 0x89, 0xAE, 0x51, 0x37, 0x84, 0x13, 0x18, 0xCE, 0x7D, 0x4C, 0x8A, 0x16, + 0x99, 0xA2, 0x42, 0x9D, 0x5D, 0x9C, 0x81, 0x86, 0x4D, 0x15, 0x96, 0xF0, 0xE6, 0xE1, 0x38, 0x11, + 0xA6, 0x8A, 0x15, 0x14, 0xF7, 0x13, 0xAD, 0x33, 0x81, 0xB5, 0xF4, 0x65, 0x87, 0x87, 0x6F, 0x97, + 0x2F, 0x5D, 0xED, 0xEC, 0xA7, 0xB6, 0x91, 0xE2, 0xF3, 0x7B, 0xE5, 0xC8, 0x7E, 0x3A, 0x26, 0x54, + 0x9C, 0xC3, 0xD3, 0x6C, 0x4B, 0x6A, 0x78, 0x48, 0xF3, 0x0E, 0xCF, 0xBF, 0x9A, 0xC8, 0x60, 0x46, + 0x0B, 0x6C, 0x92, 0x6B, 0x88, 0x6F, 0x42, 0x39, 0xB0, 0xC2, 0x43, 0x8D, 0xA6, 0x4A, 0xF8, 0xF5, + 0x1E, 0x23, 0x74, 0xF7, 0x15, 0xB2, 0x15, 0xEB, 0x5A, 0x2A, 0xCA, 0xA5, 0x2C, 0xCC, 0x3C, 0x7D, + 0x63, 0x65, 0x7F, 0x3A, 0xA8, 0x35, 0xB0, 0x77, 0x54, 0x1A, 0xCB, 0xA5, 0x07, 0x1E, 0x2C, 0x60, + 0x3C, 0x66, 0x32, 0x55, 0x75, 0xEB, 0x57, 0x35, 0xE2, 0xD3, 0xC2, 0x73, 0x5D, 0xF7, 0xC2, 0xB6, + 0xEE, 0x45, 0x1C, 0x19, 0xE6, 0xF9, 0x23, 0x24, 0x23, 0xBA, 0x77, 0x6B, 0x93, 0x73, 0xA0, 0x9C, + 0xF9, 0xF0, 0x59, 0xE7, 0xB4, 0x60, 0xC3, 0xA6, 0x01, 0xEA, 0xC7, 0x52, 0x2B, 0xDC, 0xDC, 0x96, + 0x0F, 0x3C, 0xB0, 0x19, 0x19, 0xE1, 0x52, 0xB6, 0x17, 0x91, 0x2A, 0x4D, 0xC3, 0xFC, 0x44, 0x33, + 0x5F, 0x9D, 0x36, 0x51, 0x3C, 0x02, 0x6D, 0x68, 0x23, 0x64, 0x1B, 0xA0, 0xA3, 0xD7, 0xEA, 0x64, + 0x60, 0xB9, 0xEB, 0xC5, 0x3F, 0xB5, 0x52, 0xC8, 0xC4, 0xC8, 0x73, 0x36, 0x73, 0x28, 0x67, 0xF1, + 0x2A, 0x3C, 0xA6, 0x8A, 0xDB, 0x99, 0x81, 0x90, 0xDF, 0xD7, 0x4C, 0x1F, 0xD1, 0xD9, 0x0D, 0xCE, + 0x6C, 0xD8, 0x8A, 0x03, 0xB4, 0x70, 0x3A, 0x07, 0x2E, 0x2E, 0x5E, 0xA5, 0x5C, 0xBF, 0x51, 0x36, + 0x97, 0x42, 0xA5, 0x76, 0x2A, 0xCA, 0x0A, 0x51, 0x5D, 0x06, 0x78, 0x0E, 0xCF, 0x9E, 0x93, 0x59, + 0x5C, 0x17, 0x05, 0xB6, 0xF2, 0x0D, 0x02, 0xD6, 0x2D, 0x2E, 0x20, 0x62, 0x8D, 0xF7, 0x38, 0xE0, + 0xC1, 0x5E, 0x17, 0x72, 0x4D, 0xA4, 0x2F, 0x5B, 0xDC, 0xC6, 0x40, 0x82, 0x34, 0x04, 0x39, 0x69, + 0xF8, 0xBC, 0xB1, 0x79, 0x54, 0xD5, 0x1E, 0x2D, 0xD8, 0x8C, 0x90, 0x8D, 0xB4, 0xE3, 0x61, 0xB7, + 0x1D, 0xA2, 0x3C, 0xFB, 0x6A, 0x38, 0x98, 0x06, 0xDA, 0x56, 0x2C, 0xBF, 0x9B, 0x14, 0x76, 0xE6, + 0x3C, 0x01, 0x57, 0xCC, 0xC2, 0x08, 0x0C, 0xBC, 0x10, 0x09, 0x67, 0xAB, 0x01, 0x2A, 0x32, 0x6C, + 0x81, 0x2C, 0xAB, 0xD3, 0xEC, 0x7D, 0x87, 0x48, 0x16, 0x28, 0xAC, 0x1D, 0x61, 0x11, 0x31, 0x87, + 0xD6, 0x2B, 0xB0, 0x36, 0xB1, 0x18, 0xDD, 0xE7, 0xD0, 0x46, 0x57, 0x93, 0xFC, 0xDF, 0xD2, 0x3A, + 0x37, 0x49, 0x42, 0xDB, 0xE6, 0x45, 0x46, 0x22, 0xB0, 0xF2, 0x92, 0xEE, 0x52, 0x94, 0x9F, 0xFE, + 0xB1, 0xD2, 0x33, 0x45, 0xAD, 0xC9, 0x6D, 0x11, 0x79, 0x57, 0xF1, 0x80, 0xF4, 0x07, 0xAE, 0xDF, + 0x11, 0x6C, 0x85, 0x58, 0x49, 0x2F, 0x13, 0x81, 0xB9, 0x66, 0x73, 0xAB, 0x84, 0x94, 0x36, 0xC4, + 0xC6, 0x23, 0x5F, 0xC5, 0x36, 0xC6, 0xBE, 0x8E, 0x6B, 0xE9, 0x97, 0xF0, 0xAC, 0xB4, 0xF1, 0x11, + 0x43, 0xB4, 0xD2, 0xC0, 0x79, 0x5E, 0x88, 0x72, 0xC7, 0x46, 0x6B, 0x22, 0xC7, 0xF2, 0x7B, 0x61, + 0xC8, 0xFA, 0x39, 0x65, 0x45, 0x97, 0xF0, 0xC7, 0xCE, 0x74, 0x09, 0x9F, 0x5D, 0xB7, 0x68, 0xF2, + 0x2E, 0x6E, 0x2D, 0x42, 0x56, 0x9C, 0xED, 0xC5, 0x5A, 0x57, 0xD9, 0x53, 0x5A, 0xB4, 0xE8, 0x15, + 0x07, 0x1B, 0xFB, 0x31, 0x40, 0x14, 0x95, 0x77, 0x33, 0x74, 0x71, 0x73, 0x7B, 0xFA, 0xA7, 0xBF, + 0x51, 0xF8, 0x3D, 0xE6, 0xB1, 0xD0, 0x42, 0x25, 0x52, 0xFC, 0x4F, 0x1A, 0xA6, 0x4D, 0xAF, 0xCD, + 0x13, 0x62, 0x7A, 0xBF, 0x22, 0x98, 0xD6, 0x07, 0x9C, 0xAE, 0x5E, 0xFC, 0x96, 0xEC, 0x0E, 0x79, + 0x84, 0x1F, 0x73, 0x60, 0x6C, 0x02, 0x6C, 0xE5, 0xB7, 0xFD, 0x7A, 0x4B, 0x8D, 0x0D, 0xC0, 0xD7, + 0x0A, 0x70, 0x6E, 0xE1, 0x51, 0x0E, 0x8C, 0xAA, 0x02, 0x6A, 0xCF, 0x61, 0x04, 0xBD, 0x53, 0x9D, + 0xE0, 0xB5, 0x28, 0x1E, 0x24, 0xBA, 0x97, 0x13, 0x0C, 0x6E, 0x93, 0x71, 0xE2, 0x68, 0xEC, 0x73, + 0x2C, 0xEC, 0x80, 0xB2, 0x16, 0xD5, 0x38, 0xC6, 0x3B, 0xCE, 0xEB, 0xB9, 0x42, 0xBE, 0x37, 0xB5, + 0x39, 0x31, 0x00, 0x5F, 0xB6, 0xD1, 0xB6, 0xD9, 0x57, 0x34, 0x82, 0x12, 0x07, 0x05, 0x04, 0x4B, + 0x5E, 0xB8, 0xC7, 0x6F, 0xA3, 0x01, 0xB9, 0x1D, 0xFF, 0x5F, 0x52, 0xBF, 0x6E, 0x7B, 0xA8, 0xC3, + 0x6E, 0xAC, 0x00, 0xCD, 0x0A, 0xAB, 0x7D, 0x4E, 0x63, 0x43, 0xCE, 0x10, 0x21, 0x38, 0x42, 0x88, + 0x8D, 0xA7, 0x46, 0x7F, 0x74, 0x1F, 0x1D, 0x5F, 0x25, 0xD2, 0xC0, 0x18, 0x7D, 0x40, 0x61, 0x36, + 0x06, 0xB5, 0x09, 0xCA, 0xC6, 0xAD, 0xD6, 0x9E, 0xED, 0x45, 0xF6, 0x95, 0x32, 0x07, 0x84, 0x71, + 0xC8, 0x35, 0xB0, 0x81, 0x97, 0xC9, 0x60, 0xDE, 0xFD, 0x8E, 0x90, 0x67, 0xD7, 0x23, 0x51, 0x28, + 0x90, 0xA5, 0x6E, 0xB6, 0x59, 0x88, 0xD1, 0x8D, 0xCD, 0x17, 0xA3, 0x48, 0xE3, 0x3F, 0x00, 0x4E, + 0x9B, 0x21, 0xD5, 0xA4, 0x5A, 0xF0, 0xA0, 0xBA, 0x40, 0xB7, 0xBB, 0xE1, 0x3D, 0x16, 0x9E, 0xEE, + 0xBB, 0x9E, 0xB2, 0x91, 0xBD, 0x39, 0x77, 0xD6, 0xB5, 0x9C, 0xB5, 0xE8, 0xCF, 0x7D, 0x8C, 0x83, + 0x82, 0x1A, 0xBA, 0x11, 0xDA, 0xF3, 0x96, 0xDD, 0x09, 0x20, 0x9F, 0xEB, 0xAE, 0x39, 0xAC, 0x7C, + 0xF2, 0x41, 0x98, 0x21, 0x6B, 0x8D, 0x19, 0xFF, 0x36, 0x5E, 0x82, 0xAC, 0xEE, 0x1E, 0x0E, 0x77, + 0x63, 0x14, 0x4E, 0x87, 0xE8, 0x22, 0x01, 0xD4, 0xC4, 0xF3, 0xE6, 0x49, 0xE6, 0x25, 0x64, 0x5C, + 0x54, 0x4B, 0x10, 0xE7, 0xCD, 0x17, 0x8F, 0xFA, 0xA3, 0x4D, 0xCA, 0x49, 0xCA, 0x4D, 0x33, 0xBC, + 0x29, 0x71, 0x4D, 0xF9, 0x0D, 0x74, 0x01, 0xAC, 0x79, 0xA7, 0xD7, 0x75, 0xD3, 0x9B, 0x04, 0xEE, + 0xCB, 0xCD, 0x51, 0xCC, 0xAA, 0x68, 0xFB, 0x41, 0xD3, 0x2D, 0xC1, 0xC8, 0x72, 0xDC, 0x69, 0xBE, + 0x0A, 0x74, 0xFF, 0xA8, 0x0C, 0xB4, 0xE1, 0x1A, 0xD3, 0x30, 0x21, 0xA9, 0x34, 0xCC, 0xB5, 0xE9, + 0xCF, 0x38, 0x48, 0x3B, 0xFC, 0xD6, 0x88, 0xD8, 0xB7, 0x3D, 0x71, 0xE4, 0x36, 0xA2, 0xE6, 0x03, + 0x02, 0xE3, 0xFB, 0x68, 0x0F, 0x07, 0x3B, 0x80, 0x30, 0x1C, 0xF4, 0x88, 0x0D, 0x86, 0x1F, 0x83, + 0x4D, 0x93, 0xD4, 0x10, 0xB1, 0xFF, 0x2C, 0xCB, 0xBE, 0x8E, 0xA8, 0xDB, 0x09, 0xE5, 0xF7, 0x9C, + 0x82, 0x48, 0xE0, 0xC8, 0x2C, 0x7B, 0xA1, 0x46, 0x89, 0xE9, 0x0D, 0x82, 0x6F, 0xC1, 0xEA, 0xA5, + 0x84, 0x82, 0x33, 0x26, 0x4A, 0xB6, 0x84, 0x60, 0x21, 0x00, 0x89, 0x20, 0x84, 0x14, 0x7A, 0xDF, + 0x7B, 0xEB, 0x45, 0x6B, 0x76, 0xE6, 0xDB, 0x53, 0xBE, 0x6A, 0x95, 0xE1, 0xFE, 0x6A, 0x79, 0x07, + 0xBC, 0x9D, 0xB3, 0x37, 0x67, 0xAF, 0xC2, 0x1E, 0x2B, 0xFF, 0x9F, 0xC5, 0xF5, 0x54, 0xE0, 0x29, + 0x44, 0xA4, 0x2A, 0x6F, 0xB7, 0x52, 0x17, 0x2C, 0xB1, 0x72, 0x6E, 0x9F, 0x30, 0x9D, 0x42, 0x41, + 0xF6, 0xD5, 0x14, 0x3E, 0x32, 0x59, 0x42, 0xAD, 0x7A, 0x68, 0x53, 0xF9, 0x99, 0xFD, 0x30, 0xC0, + 0x68, 0xB6, 0x97, 0xD9, 0x1B, 0x9A, 0xF6, 0xB9, 0x06, 0xE2, 0x2E, 0x27, 0x60, 0xE9, 0x1A, 0xBD, + 0x88, 0xCD, 0xAD, 0xE0, 0xCB, 0xED, 0x76, 0x2B, 0x46, 0x24, 0xB0, 0x48, 0xBA, 0x55, 0x9B, 0xBD, + 0x6D, 0xF2, 0xF7, 0x8C, 0x59, 0x4E, 0xB6, 0xE4, 0x89, 0xE1, 0xD4, 0x97, 0x85, 0x15, 0x27, 0xAE, + 0xD0, 0x9A, 0x72, 0x98, 0xB0, 0x6F, 0x06, 0xB9, 0xFC, 0xFD, 0x0D, 0x51, 0x11, 0x7E, 0x02, 0x66, + 0xD2, 0xE7, 0x25, 0xD4, 0x4D, 0xAE, 0x78, 0x9F, 0x8E, 0x69, 0xDD, 0x43, 0x80, 0x2F, 0xE6, 0x6E, + 0x46, 0xD7, 0x1A, 0x05, 0x8F, 0x4B, 0x5E, 0xF7, 0x4E, 0x09, 0x9B, 0xAF, 0x4E, 0x2B, 0x14, 0x91, + 0x59, 0x67, 0xFF, 0xFF, 0xAA, 0x08, 0xE7, 0x25, 0x42, 0x4E, 0x17, 0xD6, 0xDF, 0xD8, 0x23, 0x45, + 0xB4, 0xE2, 0x15, 0xE8, 0xDB, 0xA8, 0x55, 0x81, 0x9B, 0xE3, 0x3F, 0x09, 0x0C, 0x16, 0x19, 0xE6, + 0xE3, 0x7F, 0x1D, 0xB6, 0xBB, 0x14, 0x3C, 0x58, 0xBB, 0x69, 0x5F, 0x7A, 0x1A, 0x51, 0x45, 0xEE, + 0xDB, 0xA5, 0x7F, 0x53, 0x27, 0x04, 0xA0, 0x60, 0x76, 0x7A, 0xAD, 0x29, 0x7A, 0x8B, 0x49, 0x4C, + 0x6D, 0x26, 0x01, 0x45, 0x9B, 0x2F, 0xC8, 0x6B, 0xE0, 0x11, 0x1E, 0xCE, 0x35, 0x18, 0xDA, 0x6A, + 0x7E, 0x14, 0x56, 0xFB, 0x19, 0xE2, 0xBC, 0xAF, 0xE9, 0x62, 0xF9, 0xD4, 0xB7, 0x21, 0x1D, 0x45, + 0x10, 0xB7, 0xF3, 0x10, 0x80, 0xD0, 0xA9, 0x20, 0x12, 0xFB, 0xFA, 0xB9, 0xF6, 0x9B, 0x32, 0xA9, + 0x68, 0x58, 0xD9, 0x97, 0xDD, 0x4D, 0xDB, 0x67, 0x95, 0x35, 0xFE, 0xFA, 0x9A, 0xB2, 0x8D, 0x39, + 0x32, 0xD0, 0x5F, 0x6E, 0x74, 0x62, 0x3F, 0xC0, 0xC9, 0x24, 0x49, 0xC9, 0x65, 0x27, 0x88, 0x52, + 0x60, 0xBB, 0x6B, 0x52, 0xAC, 0x35, 0x90, 0x47, 0xF8, 0x34, 0xF4, 0x8E, 0x9E, 0x43, 0xE6, 0x28, + 0xA5, 0x04, 0xA9, 0x10, 0x09, 0x4F, 0xE0, 0x2E, 0x3E, 0x12, 0xD9, 0xC3, 0xC3, 0xF0, 0xAB, 0x30, + 0x18, 0x13, 0x6C, 0x17, 0x06, 0x2C, 0x03, 0x60, 0x04, 0x5D, 0x0E, 0xC8, 0x7F, 0x80, 0x4B, 0xAD, + 0xAF, 0x34, 0x2B, 0xDC, 0x94, 0x1F, 0x68, 0x0A, 0xAB, 0xA3, 0xD7, 0x19, 0x23, 0x02, 0x8F, 0xBD, + 0xB9, 0x33, 0xD3, 0x93, 0x66, 0xC9, 0x19, 0x18, 0xEF, 0x08, 0x0C, 0xEE, 0xDB, 0xB3, 0x5E, 0x55, + 0xB2, 0xDC, 0xBB, 0x90, 0x02, 0x2B, 0x90, 0x67, 0x41, 0x3E, 0x65, 0xA0, 0x9B, 0xC4, 0x5D, 0x81, + 0xDC, 0x64, 0x82, 0xA9, 0x86, 0xA7, 0xB1, 0x1C, 0x6C, 0x7B, 0xA2, 0x07, 0xF1, 0xE0, 0x8E, 0x4F, + 0xBF, 0x07, 0x20, 0x48, 0x05, 0xE5, 0x1D, 0xB6, 0xD8, 0x83, 0x45, 0x7C, 0xAD, 0x84, 0x32, 0x94, + 0xCA, 0x88, 0x96, 0xAA, 0x07, 0xE8, 0x7B, 0x0A, 0x89, 0x46, 0x98, 0x2F, 0x93, 0x65, 0xEB, 0x7B, + 0x79, 0x50, 0x8C, 0x8D, 0x01, 0x6D, 0xCE, 0xB4, 0x5E, 0x1E, 0x74, 0x6D, 0xC3, 0x29, 0x0B, 0x34, + 0xB6, 0xB8, 0xE7, 0x9C, 0x2D, 0x71, 0x49, 0x65, 0x07, 0x1A, 0x7D, 0x04, 0x74, 0x42, 0xD7, 0x0D, + 0x96, 0x80, 0x85, 0xFC, 0x5D, 0x29, 0x79, 0x54, 0x8F, 0x08, 0x2A, 0x7F, 0xF2, 0xB8, 0x87, 0x13, + 0x29, 0x6E, 0xC4, 0xB7, 0x99, 0xBB, 0xC5, 0x6C, 0x4D, 0x01, 0x38, 0xB5, 0xFF, 0x93, 0xEC, 0x0F, + 0x96, 0xA5, 0x47, 0x78, 0xD1, 0xC0, 0x63, 0x61, 0xE0, 0x2D, 0xE4, 0x56, 0x7C, 0xAC, 0x77, 0x30, + 0x21, 0x55, 0x32, 0xFD, 0x4E, 0xC0, 0x31, 0x9B, 0x7C, 0x37, 0x04, 0x8B, 0xAB, 0x95, 0x03, 0xAC, + 0x22, 0x9E, 0x1F, 0x86, 0x2A, 0xB5, 0xD9, 0x32, 0x56, 0xCC, 0x4E, 0xE5, 0x1A, 0x70, 0x65, 0x5B, + 0x32, 0xC7, 0x1D, 0x96, 0x73, 0x62, 0x49, 0xB3, 0xC5, 0xA1, 0x83, 0xEB, 0x32, 0x6B, 0x6E, 0x17, + 0xC2, 0xD2, 0xBA, 0x90, 0x3B, 0xB5, 0x99, 0x18, 0x34, 0x4D, 0x15, 0x57, 0x19, 0xCD, 0x3C, 0xE1, + 0xCF, 0x55, 0x4A, 0x44, 0xD0, 0xFD, 0xD1, 0x29, 0xB5, 0x86, 0xA1, 0xAA, 0xB0, 0x6C, 0x30, 0xEE, + 0x14, 0xC2, 0x9E, 0x02, 0x31, 0xDF, 0x13, 0x0D, 0xC6, 0xFA, 0x9F, 0xC1, 0x17, 0xF1, 0x52, 0x08, + 0x8B, 0xBB, 0x81, 0xB8, 0x92, 0x7B, 0x19, 0x0F, 0x5E, 0x7A, 0xDF, 0xEB, 0x86, 0x8C, 0x5F, 0x6C, + 0x7A, 0xE9, 0xF1, 0x26, 0x55, 0x80, 0xFF, 0xBC, 0x6A, 0x0A, 0xBC, 0x23, 0xAB, 0xE8, 0x8E, 0xC3, + 0xA5, 0xD7, 0xFD, 0x52, 0x73, 0x68, 0x4B, 0x56, 0x7F, 0x60, 0x4A, 0x68, 0x84, 0x30, 0xE1, 0x1F, + 0x0C, 0x10, 0x41, 0x71, 0xFC, 0x10, 0xDF, 0x62, 0xCC, 0x4D, 0xD6, 0x2A, 0x7F, 0xB9, 0xAF, 0x46, + 0x94, 0x3A, 0xD7, 0x0F, 0x12, 0x2C, 0xB8, 0x17, 0x1F, 0x56, 0xF3, 0xCD, 0xA0, 0xE7, 0xBF, 0xA4, + 0xFB, 0xC5, 0xE8, 0x17, 0x4B, 0x8A, 0xE5, 0x3E, 0x96, 0x22, 0x17, 0x07, 0xA3, 0x17, 0x0A, 0x77, + 0x98, 0xF8, 0x9B, 0x59, 0x5C, 0x2F, 0xC9, 0x73, 0xA4, 0x5A, 0x17, 0x1F, 0xBD, 0x56, 0x3E, 0xA2, + 0xE6, 0x8F, 0x34, 0xF2, 0xE0, 0x20, 0x37, 0xE4, 0x98, 0xE1, 0xEC, 0xC4, 0x1E, 0x81, 0x13, 0x17, + 0x21, 0x95, 0x88, 0x60, 0x04, 0xDA, 0x91, 0xB9, 0x22, 0xF8, 0x64, 0x87, 0x8D, 0x32, 0x60, 0x37, + 0x33, 0x2E, 0x2B, 0x95, 0x43, 0x0C, 0x10, 0xDF, 0xFC, 0x64, 0x56, 0x89, 0x32, 0x47, 0xA3, 0x8F, + 0xF1, 0x3E, 0x34, 0x63, 0x35, 0xD9, 0x41, 0xD8, 0x1A, 0x23, 0x88, 0x39, 0x6D, 0x23, 0x2A, 0x20, + 0xCE, 0xFB, 0x80, 0x0F, 0x59, 0xB7, 0xFB, 0x1E, 0x24, 0xF5, 0x8A, 0x78, 0x2B, 0xE8, 0x13, 0x52, + 0x34, 0x5B, 0x65, 0x64, 0xAB, 0x78, 0x4D, 0x5C, 0x79, 0x3B, 0xF2, 0x7D, 0x1F, 0x5B, 0xA9, 0x37, + 0xAE, 0x4C, 0x9E, 0x30, 0x6B, 0x39, 0x3D, 0x75, 0x06, 0xCE, 0xFE, 0x87, 0xB7, 0x1B, 0x9C, 0x9F, + 0x44, 0x7E, 0x98, 0xFF, 0x3B, 0xA6, 0x71, 0x48, 0xE3, 0x07, 0x8C, 0x5E, 0x95, 0x96, 0x04, 0xC1, + 0xBF, 0x7A, 0x18, 0x06, 0xC2, 0xD2, 0x24, 0xD6, 0xC9, 0x4D, 0x65, 0xCE, 0x18, 0x8F, 0x8B, 0x0D, + 0xFC, 0x66, 0x40, 0xB1, 0xE6, 0xE5, 0xC5, 0xDE, 0xAE, 0x2E, 0x84, 0x3F, 0xBA, 0x16, 0x5A, 0x63, + 0x72, 0x0F, 0x3C, 0x82, 0x4A, 0xD7, 0x54, 0x54, 0x60, 0x1B, 0x6A, 0x16, 0x2D, 0xDA, 0x0F, 0xF9, + 0x61, 0xD2, 0x53, 0x2B, 0xE4, 0x22, 0x0E, 0x1D, 0x08, 0x69, 0x5D, 0x4D, 0x4D, 0x3E, 0x99, 0xBE, + 0x8A, 0x83, 0xA2, 0x5A, 0x68, 0x8B, 0xBB, 0x6A, 0xA5, 0x31, 0xB9, 0x65, 0xA6, 0x55, 0xD1, 0x09, + 0x8D, 0x6B, 0xAB, 0xD8, 0xF1, 0x06, 0x62, 0xA8, 0x1A, 0xDA, 0xA4, 0x4B, 0x68, 0xA9, 0xB8, 0xA5, + 0x9D, 0xD1, 0xAA, 0x42, 0x8E, 0x67, 0xA8, 0xC6, 0x29, 0x94, 0x69, 0x38, 0xA0, 0x66, 0x84, 0xBB, + 0x73, 0x3B, 0xFC, 0x7D, 0x6B, 0xCD, 0x39, 0x8F, 0x1C, 0x6C, 0xE0, 0x58, 0x97, 0x75, 0xB7, 0x09, + 0x40, 0x68, 0x45, 0xCD, 0x97, 0x78, 0x1A, 0x81, 0xA9, 0x6D, 0x6C, 0x59, 0xB8, 0x0C, 0x7D, 0x94, + 0x46, 0x23, 0xCC, 0xD4, 0x2D, 0x71, 0x95, 0x7F, 0x9F, 0x08, 0xE0, 0xE5, 0xF9, 0xC0, 0x2C, 0xC4, + 0x09, 0x27, 0x7C, 0x62, 0x5E, 0xF4, 0xB6, 0xAA, 0x9D, 0x18, 0x10, 0xCE, 0xCB, 0xCA, 0xFC, 0xC2, + 0x12, 0x5A, 0xC2, 0xC7, 0xFA, 0x47, 0x3B, 0x4A, 0x5C, 0xC7, 0x52, 0xCA, 0x97, 0xD4, 0xC3, 0x90, + 0x1D, 0x04, 0x50, 0x92, 0xFF, 0xCC, 0xA9, 0x85, 0x4D, 0x1F, 0x73, 0xE3, 0x5B, 0x4D, 0x20, 0xCA, + 0x46, 0x89, 0xD5, 0x26, 0x2B, 0xF5, 0x6B, 0x2A, 0x0B, 0x9C, 0x36, 0x15, 0x9A, 0xB2, 0x15, 0xC1, + 0xAF, 0x38, 0x3D, 0xA5, 0x4B, 0x47, 0x56, 0x32, 0x90, 0x60, 0x93, 0x5D, 0x8C, 0xE4, 0x3D, 0x3A, + 0x00, 0xB2, 0x84, 0x92, 0xE7, 0x9C, 0x09, 0xD4, 0x55, 0x01, 0xFC, 0xFC, 0x3C, 0x0B, 0x6B, 0x0B, + 0xD4, 0x39, 0x7B, 0x88, 0x40, 0x08, 0xDE, 0x2D, 0xFC, 0x9E, 0xEF, 0xFE, 0xCA, 0x45, 0xB6, 0x8F, + 0xDD, 0x59, 0x49, 0x16, 0x9B, 0x26, 0x88, 0x7F, 0x83, 0xA0, 0x29, 0x14, 0xA6, 0x96, 0x51, 0x1D, + 0x36, 0xCF, 0x7D, 0x01, 0x2E, 0xC3, 0xC5, 0xC2, 0x49, 0xAB, 0x70, 0xAC, 0x66, 0x08, 0xA4, 0xB7, + 0xB5, 0x37, 0x34, 0xEB, 0xD1, 0xA1, 0x52, 0xB1, 0xF8, 0x1C, 0x88, 0x36, 0x32, 0x00, 0xA4, 0x5B, + 0x3B, 0x93, 0x34, 0x20, 0x5F, 0xA9, 0x9B, 0x1E, 0xA6, 0xF9, 0xFC, 0xC5, 0x34, 0x2E, 0x64, 0xCE, + 0x97, 0x44, 0x71, 0x0D, 0x09, 0x89, 0xF2, 0x68, 0x41, 0xF9, 0x64, 0xA0, 0xFC, 0xE2, 0x43, 0x14, + 0x77, 0xB1, 0x68, 0x2C, 0xE6, 0xCB, 0xD4, 0x82, 0xE0, 0xF1, 0x93, 0x00, 0x50, 0x9F, 0x14, 0x6F, + 0x78, 0xDC, 0x7B, 0xC2, 0xD6, 0x31, 0x29, 0x85, 0xA6, 0xEB, 0x50, 0xEC, 0xA6, 0xDD, 0xAA, 0x50, + 0x65, 0x94, 0xEE, 0x68, 0xC3, 0x11, 0xAA, 0xB7, 0xA7, 0xEE, 0xBB, 0x39, 0x08, 0xA6, 0xE8, 0xC5, + 0x4E, 0x52, 0x84, 0xDD, 0xE6, 0x16, 0xF5, 0xC3, 0xAC, 0xB0, 0xBE, 0x3F, 0xA0, 0xC9, 0x1F, 0x17, + 0xC0, 0x8D, 0x7C, 0x80, 0x27, 0xAE, 0xBB, 0x47, 0x32, 0x94, 0x01, 0xCB, 0x72, 0x12, 0xCB, 0x74, + 0x56, 0x58, 0x17, 0x30, 0x57, 0x6C, 0x94, 0x08, 0xD4, 0x60, 0x50, 0x41, 0x35, 0xAB, 0xBD, 0x0B, + 0xA7, 0x43, 0x1B, 0x53, 0x19, 0xBA, 0x05, 0x67, 0xAF, 0x4C, 0xAD, 0x76, 0xBA, 0x7D, 0x75, 0x8E, + 0x64, 0x0C, 0xDD, 0xFB, 0xD9, 0x84, 0x3F, 0xB0, 0x57, 0x4D, 0x8C, 0xA0, 0x0F, 0xD9, 0xE0, 0x53, + 0xB9, 0x1D, 0xAE, 0xE1, 0xCC, 0x9E, 0xD5, 0x79, 0xDA, 0xB4, 0x0C, 0x0B, 0xDD, 0x95, 0x28, 0xDD, + 0x7F, 0x73, 0x43, 0x83, 0xC5, 0x45, 0x14, 0x00, 0xBA, 0x00, 0x9C, 0xC0, 0xC8, 0x62, 0x34, 0x66, + 0xF9, 0x78, 0x57, 0x0B, 0x9F, 0x85, 0xEE, 0x49, 0xF6, 0xA9, 0x86, 0x05, 0x6B, 0x0E, 0x1F, 0x26, + 0xD0, 0xD9, 0xEB, 0xA8, 0x5B, 0x9B, 0xCD, 0x4E, 0x25, 0x07, 0xE1, 0xE0, 0x60, 0xA0, 0xFB, 0x17, + 0x7C, 0x41, 0xAA, 0x20, 0xFE, 0x83, 0x25, 0x3A, 0x9A, 0x02, 0x87, 0x0A, 0x71, 0x87, 0xE5, 0xD3, + 0xC1, 0xDC, 0x85, 0xC8, 0xFA, 0x71, 0x2A, 0xCF, 0xA1, 0xF7, 0x44, 0x13, 0x9C, 0x03, 0x56, 0xC3, + 0x7A, 0xEE, 0x51, 0x35, 0x3C, 0x27, 0x30, 0xF3, 0x3E, 0x31, 0x5F, 0x00, 0x51, 0xA7, 0x1C, 0x92, + 0xA4, 0xE1, 0xC3, 0x43, 0x12, 0x03, 0x3C, 0xEE, 0xD3, 0xFA, 0x1C, 0x6A, 0x0F, 0xE0, 0x45, 0xBB, + 0x3B, 0x81, 0xF1, 0x37, 0x46, 0x9C, 0x6E, 0x21, 0x74, 0xFA, 0x93, 0x52, 0xF4, 0x57, 0x95, 0x81, + 0xD3, 0x57, 0x44, 0x5E, 0xF0, 0x54, 0x18, 0x3C, 0xFB, 0x3A, 0xE7, 0x10, 0x67, 0xF2, 0x20, 0x24, + 0x09, 0xD2, 0x6D, 0xAB, 0xC2, 0xBA, 0x3C, 0x30, 0xE9, 0x65, 0xF1, 0x50, 0xFB, 0x11, 0xB6, 0xCF, + 0x85, 0x7B, 0x6A, 0x4A, 0x56, 0x59, 0x59, 0xB7, 0xDE, 0xFB, 0xC8, 0x39, 0x6A, 0x52, 0x6D, 0xE6, + 0xB7, 0xC7, 0x7A, 0x62, 0x01, 0x25, 0x3D, 0x54, 0x54, 0xB4, 0xF2, 0xBA, 0xF9, 0xEE, 0xE3, 0x59, + 0xD0, 0x74, 0xB5, 0xBF, 0xDF, 0x3E, 0x3F, 0x87, 0x64, 0x82, 0xD9, 0xD5, 0xF9, 0xE8, 0xBB, 0xC5, + 0xA5, 0x61, 0x91, 0x9C, 0x2C, 0x99, 0xC0, 0x39, 0xB3, 0xEF, 0x33, 0x5E, 0x3E, 0x1E, 0x00, 0xC6, + 0x5A, 0x90, 0x1C, 0x50, 0x43, 0x3D, 0x4B, 0xA1, 0x3F, 0x46, 0xEB, 0xBA, 0x86, 0xA4, 0xEA, 0xE1, + 0xA9, 0x40, 0x97, 0x5A, 0x80, 0x97, 0x36, 0x1C, 0xA8, 0x19, 0x4E, 0x0D, 0xF8, 0xCB, 0x1C, 0xC7, + 0xD4, 0x1C, 0xB1, 0x4C, 0x2E, 0xDB, 0x2D, 0x96, 0x1E, 0xBA, 0xEB, 0x3D, 0xDE, 0x7D, 0xC7, 0x2E, + 0xF8, 0x36, 0x54, 0x5C, 0x94, 0xD0, 0x5A, 0x0E, 0x5D, 0xF6, 0x4D, 0x35, 0xD2, 0xC1, 0x52, 0xC7, + 0x3B, 0x58, 0x43, 0xEB, 0xB6, 0x54, 0xBA, 0xA5, 0xF1, 0x86, 0xDB, 0x23, 0xAB, 0x6A, 0x42, 0x00, + 0x90, 0xD2, 0x0C, 0x76, 0x32, 0xA0, 0xC2, 0xE3, 0x10, 0x0E, 0x0C, 0x8A, 0x7C, 0xA5, 0x5F, 0xC9, + 0x4E, 0x79, 0x6E, 0x38, 0x0D, 0xA1, 0xD8, 0x7E, 0x90, 0xDD, 0xA4, 0x35, 0x33, 0xBF, 0xCE, 0x69, + 0x8F, 0x93, 0xBC, 0xB4, 0xC8, 0xD2, 0xD1, 0xD8, 0x2F, 0x31, 0xF8, 0x0B, 0x12, 0x8B, 0xA2, 0xAA, + 0x7B, 0x36, 0x5F, 0x66, 0x0D, 0xF6, 0x34, 0x0F, 0xA7, 0x6A, 0xF3, 0x52, 0x4A, 0xB3, 0xCE, 0x83, + 0xB5, 0x57, 0x11, 0x74, 0xBF, 0x1D, 0x5E, 0xA4, 0x18, 0x84, 0xC6, 0xE4, 0xAC, 0x42, 0x93, 0x82, + 0x99, 0xF1, 0x4B, 0xE2, 0x07, 0x0E, 0x0C, 0xAD, 0xC4, 0x7E, 0x24, 0xC7, 0xF9, 0x22, 0x34, 0x31, + 0x0B, 0xC9, 0xBF, 0xA8, 0x74, 0xE9, 0xDE, 0xE8, 0x61, 0xDC, 0xC2, 0x49, 0x95, 0x78, 0x6F, 0x2D, + 0x46, 0x76, 0xD8, 0x2F, 0xA9, 0x56, 0x00, 0x38, 0x74, 0x54, 0xBB, 0x66, 0xE5, 0x9B, 0xA1, 0xAB, + 0xE4, 0x1E, 0x46, 0x71, 0x90, 0xC1, 0xF8, 0x16, 0x8A, 0x8F, 0x76, 0xE6, 0x4F, 0x06, 0xE8, 0xE8, + 0xAA, 0x25, 0xF2, 0x75, 0x3A, 0x0D, 0xBD, 0xF6, 0x40, 0xEE, 0x64, 0xE0, 0xF4, 0xD5, 0xBB, 0x76, + 0x7A, 0x8B, 0x43, 0xD8, 0x75, 0xD3, 0xAF, 0x1A, 0xE7, 0x59, 0x5E, 0x8E, 0xC8, 0xE4, 0xD9, 0x7C, + 0x3E, 0x02, 0x4D, 0xBE, 0x00, 0xD9, 0x6F, 0x46, 0xF1, 0x4A, 0x5B, 0x33, 0x97, 0x6E, 0x54, 0x5A, + 0x3A, 0x41, 0x6F, 0xC0, 0xB7, 0x3E, 0x78, 0xE5, 0xCF, 0x75, 0x1C, 0xEE, 0xD8, 0xA1, 0xEE, 0xD0, + 0x37, 0x94, 0xFE, 0x63, 0x1B, 0x2F, 0x63, 0x7A, 0xFE, 0x22, 0xCD, 0x32, 0xE1, 0xB6, 0xF8, 0x21, + 0x33, 0xDA, 0xCE, 0xB4, 0x91, 0x25, 0x21, 0x67, 0xA2, 0x6D, 0x5D, 0x49, 0xBD, 0x77, 0x92, 0x60, + 0xA3, 0x56, 0xBF, 0x1E, 0x1B, 0xF8, 0xE9, 0x40, 0xC5, 0xBF, 0x06, 0xFC, 0x14, 0xBC, 0xBC, 0x62, + 0xC0, 0xCB, 0x8D, 0x67, 0x7E, 0xDD, 0xB9, 0xCE, 0x66, 0xE2, 0x52, 0xC1, 0x21, 0x68, 0x93, 0xB4, + 0x6F, 0xFC, 0x81, 0x1F, 0x41, 0xD7, 0x7F, 0x10, 0xCF, 0x35, 0x9B, 0x72, 0xC6, 0xBC, 0x05, 0x5D, + 0x7D, 0x63, 0x09, 0xB4, 0xA8, 0x62, 0xAA, 0x42, 0x51, 0xC1, 0xC0, 0xF0, 0x2D, 0xE2, 0xBE, 0x6D, + 0x54, 0x53, 0x55, 0x7B, 0x39, 0x0A, 0xB0, 0x2A, 0xE0, 0x45, 0x0A, 0xEF, 0xD7, 0x7E, 0xB9, 0xAF, + 0xB9, 0xDA, 0x22, 0x5D, 0x65, 0xD5, 0x39, 0x0F, 0xE4, 0x2B, 0x8E, 0xAA, 0x79, 0xC9, 0xFB, 0xF0, + 0x00, 0x51, 0xE6, 0x59, 0x3F, 0x12, 0x54, 0x4A, 0x29, 0x23, 0xD3, 0x6A, 0x9F, 0xB9, 0x0D, 0x99, + 0x1E, 0x8A, 0xF6, 0x55, 0xD3, 0xDC, 0x8A, 0x48, 0xC3, 0xE5, 0x16, 0xDA, 0xFF, 0xB0, 0x3B, 0x92, + 0x49, 0xD6, 0x00, 0xB1, 0x13, 0x8E, 0xC2, 0x3F, 0x7C, 0xF9, 0x48, 0x55, 0xD5, 0xAC, 0xEA, 0x8A, + 0xC1, 0x5C, 0xA9, 0x48, 0xEA, 0x71, 0xDA, 0x99, 0xE9, 0x49, 0xBA, 0xD8, 0x1F, 0xF2, 0xB2, 0x51, + 0x5D, 0x13, 0xC7, 0x6A, 0x82, 0x8E, 0x64, 0x3A, 0x11, 0x56, 0x66, 0x24, 0x2D, 0xC1, 0x7D, 0x3A, + 0xB2, 0x45, 0xB6, 0xAE, 0x10, 0x8F, 0xBD, 0xD6, 0x9F, 0xAB, 0x44, 0xA7, 0x4A, 0x5D, 0x92, 0x7D, + 0x8F, 0xE5, 0x59, 0x4A, 0x10, 0x85, 0xFD, 0x3C, 0x40, 0x3B, 0xBF, 0xDF, 0xA7, 0x3A, 0x1D, 0xB5, + 0x67, 0x23, 0xF9, 0xAC, 0x59, 0x31, 0x2F, 0xD9, 0xD6, 0xF5, 0xEA, 0xD1, 0xDE, 0xAE, 0xFA, 0x44, + 0xFD, 0xE0, 0xBE, 0xE3, 0xF7, 0xEA, 0xD5, 0xF0, 0x26, 0x41, 0xE5, 0x3D, 0xBE, 0xAA, 0xFC, 0x57, + 0x49, 0xAE, 0x3E, 0x70, 0x8F, 0x9D, 0xF1, 0xB6, 0x32, 0x7D, 0xE7, 0x21, 0x4A, 0x7E, 0x99, 0xD7, + 0x90, 0xFE, 0xC5, 0xB2, 0xE8, 0xAC, 0x6D, 0xF7, 0x3C, 0xD3, 0x1E, 0x61, 0xF4, 0xFF, 0x8C, 0x13, + 0x5A, 0x7F, 0x87, 0x66, 0x47, 0x84, 0xF8, 0x3B, 0x0B, 0x70, 0xCF, 0xBA, 0x0C, 0x87, 0x93, 0x62, + 0x65, 0x1E, 0x47, 0xFC, 0x96, 0x25, 0x46, 0x01, 0xE0, 0xCE, 0xE2, 0x41, 0xC6, 0x38, 0x90, 0x0D, + 0xE3, 0xC7, 0x60, 0x4E, 0x08, 0x0F, 0x02, 0xF5, 0xB8, 0x70, 0x27, 0x89, 0x29, 0x6E, 0x79, 0x85, + 0x12, 0xA4, 0xCA, 0x6C, 0x69, 0xBE, 0x52, 0xFF, 0xBD, 0xCF, 0x3E, 0x07, 0xA8, 0x7B, 0x00, 0x44, + 0xE0, 0x3B, 0xA6, 0x50, 0xFF, 0xF9, 0xDA, 0x0D, 0xEB, 0xCC, 0x70, 0x21, 0x20, 0x5F, 0xF4, 0xAF, + 0x1B, 0x2C, 0xF8, 0x63, 0x9C, 0xB9, 0x8F, 0x0B, 0xBF, 0x1C, 0xA5, 0x85, 0xA6, 0x9A, 0x0A, 0x93, + 0x1E, 0x8B, 0xA3, 0x80, 0x63, 0x30, 0x24, 0xE1, 0xF0, 0xB6, 0x7B, 0x93, 0xC5, 0x72, 0xE9, 0x49, + 0x91, 0x1D, 0xB0, 0x77, 0xC0, 0xA2, 0x1D, 0xC9, 0x66, 0x90, 0xF7, 0x58, 0x92, 0x87, 0x4F, 0xB0, + 0x0D, 0x0A, 0x48, 0x16, 0x5F, 0x7D, 0xF4, 0xA6, 0xC9, 0x80, 0xD2, 0x38, 0xD6, 0xC7, 0xEE, 0x73, + 0xA6, 0xA8, 0x57, 0xC9, 0xAA, 0x32, 0x6A, 0x3C, 0xA7, 0x9F, 0x89, 0x79, 0x8B, 0xD9, 0x6B, 0x8C, + 0xB1, 0x26, 0x5D, 0x4B, 0xE9, 0xF0, 0x9D, 0xFA, 0xC0, 0xD3, 0xEA, 0x82, 0xDA, 0x7C, 0xCB, 0x43, + 0x90, 0x74, 0x24, 0xC6, 0xBD, 0x5B, 0x87, 0x29, 0xCA, 0xEC, 0x6E, 0xBA, 0x7C, 0x41, 0xF9, 0x99, + 0x0A, 0x92, 0xFA, 0x43, 0xAE, 0xE7, 0xF9, 0xFB, 0x55, 0x5B, 0x3A, 0xCC, 0x1C, 0xC5, 0x20, 0x37, + 0x53, 0x4A, 0x83, 0xC6, 0x79, 0x5A, 0x42, 0xF9, 0x23, 0x62, 0xA1, 0x3A, 0x42, 0xCE, 0x51, 0xC5, + 0x5D, 0xC9, 0x99, 0x1F, 0x82, 0xE7, 0x43, 0x72, 0x46, 0x70, 0x80, 0x25, 0x65, 0x98, 0x78, 0xC2, + 0xF9, 0xD4, 0x07, 0x2D, 0xAB, 0x79, 0x7D, 0x45, 0xC3, 0x0B, 0xEE, 0x18, 0xBB, 0x3C, 0x33, 0xE5, + 0x8B, 0xE5, 0x2A, 0x04, 0x53, 0x7C, 0x92, 0x92, 0x3E, 0x77, 0xE6, 0xB5, 0x8A, 0x7C, 0xAC, 0x3F, + 0xEA, 0xFC, 0x19, 0x64, 0xFD, 0xB4, 0xA3, 0x33, 0xCC, 0xBB, 0xE3, 0x5F, 0xBA, 0xAB, 0x9F, 0x2A, + 0x4E, 0x71, 0x96, 0x4D, 0x8D, 0x33, 0x39, 0x02, 0x0F, 0x6B, 0xFB, 0xC7, 0x76, 0x0D, 0xC4, 0x9D, + 0xB0, 0x6C, 0xA3, 0x91, 0x32, 0x23, 0x60, 0xF9, 0x53, 0x3C, 0x48, 0xCF, 0x54, 0x4A, 0x34, 0x6A, + 0x90, 0xB8, 0xDB, 0xB6, 0xFD, 0xD8, 0xB9, 0x79, 0xF1, 0x5D, 0x64, 0xFC, 0x2C, 0x6E, 0xA2, 0xD2, + 0x4D, 0x37, 0x56, 0xCB, 0x8D, 0xE9, 0xC9, 0x02, 0x3A, 0x7F, 0x53, 0x75, 0x98, 0x46, 0xF9, 0x8E, + 0xE2, 0x00, 0x05, 0x20, 0x8E, 0xAD, 0xAA, 0x38, 0x5F, 0x6A, 0x34, 0x16, 0x2E, 0x25, 0xFF, 0x7F, + 0xE2, 0x10, 0x2D, 0x49, 0x2C, 0xEF, 0xB5, 0xE5, 0x8A, 0x2A, 0x1F, 0x6F, 0x6C, 0x49, 0xF7, 0x78, + 0x80, 0xCA, 0xFA, 0x14, 0x5D, 0xAE, 0xA9, 0xCD, 0xC5, 0xB8, 0xA8, 0xDC, 0xFF, 0x84, 0xC5, 0x80, + 0x8E, 0x98, 0x5F, 0x7E, 0xF6, 0x26, 0xBB, 0x35, 0xF7, 0xA7, 0x40, 0x46, 0x83, 0x26, 0xDF, 0x3B, + 0x64, 0xE0, 0x67, 0x7A, 0xBE, 0x08, 0xF4, 0xE6, 0x1A, 0xF8, 0xFD, 0xBA, 0x0E, 0xBE, 0xB2, 0x28, + 0x6C, 0x45, 0xEA, 0xB1, 0x6C, 0xA8, 0x8E, 0x4F, 0xB8, 0xBC, 0x82, 0x0A, 0xD1, 0x84, 0xAB, 0x03, + 0x6C, 0x30, 0x85, 0xA1, 0x2C, 0x72, 0xA3, 0x08, 0x25, 0x4C, 0x97, 0x32, 0xEB, 0xAD, 0x0E, 0x3E, + 0xE8, 0x8E, 0x2B, 0xF0, 0xCF, 0x13, 0xCA, 0xD1, 0x53, 0xC6, 0xCD, 0x58, 0x98, 0xDE, 0xB0, 0x7E, + 0x0D, 0xBF, 0x94, 0x3E, 0xA7, 0x1C, 0x84, 0xBC, 0xB3, 0x9B, 0x6D, 0x54, 0x32, 0x39, 0x89, 0xF8, + 0x02, 0xAF, 0xBC, 0xB2, 0x53, 0x3B, 0x43, 0xF2, 0xCC, 0xC9, 0x05, 0xF2, 0xC4, 0x88, 0x37, 0x6E, + 0xE1, 0xA1, 0x55, 0x82, 0x7E, 0xBE, 0x83, 0xE0, 0x0B, 0xF8, 0x96, 0x45, 0x61, 0xC1, 0x96, 0x28, + 0x6D, 0x64, 0xCF, 0xF9, 0xC1, 0xC7, 0x3A, 0x18, 0xF3, 0x9A, 0x69, 0x2B, 0x07, 0x57, 0x55, 0xE8, + 0x09, 0xCB, 0x33, 0xC5, 0x4F, 0xBF, 0x0F, 0x9A, 0x22, 0xB1, 0xB3, 0x50, 0x15, 0xA3, 0xCB, 0x8D, + 0x6E, 0x29, 0x56, 0x89, 0x64, 0xAF, 0x5B, 0x0D, 0xD4, 0xE2, 0x6F, 0x6A, 0x38, 0xBD, 0xD8, 0xA1, + 0x7D, 0x0A, 0x6F, 0x7B, 0x07, 0x89, 0x5B, 0xD2, 0xFB, 0x34, 0x5F, 0xA9, 0x0F, 0x41, 0x18, 0xD3, + 0x99, 0xFD, 0xA8, 0x88, 0xFD, 0x4B, 0x9B, 0xCA, 0xAE, 0x5A, 0xB0, 0xEE, 0x23, 0x0F, 0x4B, 0x5C, + 0x99, 0xEA, 0x29, 0xF3, 0xF6, 0x97, 0x5F, 0xF9, 0xAF, 0x28, 0x4F, 0xEC, 0xD6, 0x01, 0x69, 0x8B, + 0x65, 0x08, 0x55, 0xCA, 0x05, 0xC0, 0xB4, 0xB2, 0x64, 0x2A, 0xF5, 0x3E, 0xDA, 0xA2, 0xD2, 0xBB, + 0xDF, 0x17, 0xFF, 0xCF, 0x9E, 0xDE, 0x8A, 0x1E, 0x5F, 0xFD, 0xEA, 0x12, 0x0F, 0xFF, 0x96, 0x20, + 0x0A, 0x15, 0xE6, 0x9B, 0x4F, 0x12, 0x0B, 0xCC, 0x39, 0x4D, 0xFD, 0xD4, 0x7A, 0xDA, 0x24, 0x11, + 0x2D, 0x93, 0x92, 0x9F, 0x3E, 0x3F, 0xA2, 0x9B, 0xAD, 0x98, 0x13, 0xE2, 0x5F, 0xF2, 0x7E, 0x84, + 0xA1, 0x43, 0xAB, 0x76, 0xB8, 0xFC, 0xA0, 0x01, 0x17, 0x38, 0xB3, 0x33, 0x13, 0x4A, 0xF5, 0x15, + 0xA5, 0x56, 0x66, 0xC5, 0x44, 0x0C, 0x88, 0x75, 0x76, 0xA5, 0x7E, 0x57, 0xD4, 0x22, 0xE5, 0x32, + 0x99, 0x60, 0x99, 0x7C, 0x65, 0x29, 0xBA, 0xB0, 0x5B, 0x1F, 0x84, 0x98, 0x0C, 0x06, 0x8A, 0xAE, + 0x96, 0x63, 0x91, 0xB1, 0x75, 0xE6, 0xE6, 0x7A, 0xBF, 0x1E, 0xB5, 0xB6, 0x76, 0x1B, 0xE2, 0xBF, + 0xB4, 0x1F, 0x4D, 0x33, 0xF9, 0x9D, 0x9C, 0x79, 0xE6, 0xF5, 0xA3, 0x8C, 0x72, 0xFE, 0xAE, 0xB1, + 0x13, 0x09, 0x20, 0x91, 0x7C, 0x11, 0x99, 0x68, 0x1A, 0xA7, 0x6B, 0x3F, 0x88, 0xC7, 0xF5, 0x94, + 0x0D, 0xBC, 0x3B, 0xA5, 0x14, 0xD7, 0x8B, 0xA0, 0xA0, 0x70, 0xB4, 0xC2, 0x4E, 0x2F, 0xA8, 0xB1, + 0x0D, 0x7B, 0x8C, 0xD4, 0x96, 0xC1, 0xD1, 0xC5, 0x13, 0x67, 0x24, 0x16, 0x3C, 0xC0, 0xFD, 0x79, + 0x3C, 0x11, 0x69, 0x03, 0xF6, 0x55, 0xF7, 0xF2, 0x09, 0x8B, 0x49, 0x5B, 0xDA, 0x05, 0x3C, 0xDB, + 0x1C, 0x01, 0x1F, 0xDC, 0x4D, 0xE2, 0x09, 0xB6, 0x1F, 0x5F, 0xE2, 0xB0, 0xDF, 0x77, 0xF8, 0x83, + 0x59, 0xCF, 0xEE, 0x8B, 0x14, 0x12, 0xEB, 0xBC, 0x9B, 0xB4, 0x38, 0xFD, 0x38, 0x53, 0xC9, 0xA1, + 0x4E, 0xF6, 0x80, 0xA8, 0xE5, 0x25, 0x89, 0x97, 0xCF, 0x94, 0x06, 0xE4, 0x25, 0xDF, 0x86, 0x46, + 0xA2, 0x54, 0xA7, 0x04, 0xB5, 0xCA, 0x67, 0xF1, 0x95, 0x65, 0x57, 0xE1, 0x38, 0x61, 0xCB, 0x20, + 0xE5, 0x98, 0xF4, 0x07, 0x95, 0x25, 0xBD, 0xBE, 0x9F, 0x87, 0x76, 0x4D, 0x52, 0x96, 0xAE, 0x82, + 0xAE, 0x2C, 0x3C, 0xB6, 0x7C, 0x1A, 0x36, 0xE5, 0x34, 0x13, 0x1B, 0x72, 0x52, 0x8F, 0xFF, 0xE7, + 0x6B, 0x83, 0xDB, 0x88, 0x4A, 0xDB, 0x95, 0x37, 0xFA, 0x9D, 0xA2, 0x49, 0xDB, 0x5A, 0xE3, 0x5C, + 0x95, 0xC7, 0xF8, 0xE0, 0x14, 0x38, 0xB2, 0xCD, 0x09, 0xF4, 0x2A, 0x2A, 0xF7, 0x1A, 0xA1, 0x8E, + 0xB8, 0xBC, 0x3B, 0x51, 0x9A, 0xE4, 0xD1, 0xCF, 0xA7, 0xD1, 0xF9, 0x63, 0x0F, 0x98, 0x3D, 0x61, + 0x51, 0xEC, 0x1B, 0x67, 0x68, 0x88, 0x25, 0x65, 0x0B, 0xA6, 0x32, 0xA2, 0xCD, 0x93, 0xE1, 0x16, + 0x01, 0x12, 0xB2, 0xDA, 0x35, 0xBA, 0x52, 0x66, 0x46, 0x44, 0x8D, 0xB3, 0x19, 0x33, 0xC1, 0x1F, + 0x47, 0x6C, 0x48, 0x7B, 0x5C, 0x8C, 0xA8, 0x68, 0x74, 0xDE, 0x7C, 0xB4, 0xDF, 0x05, 0x54, 0x35, + 0x8A, 0xFE, 0x78, 0xB5, 0x05, 0x78, 0xC3, 0xB4, 0x85, 0x12, 0x88, 0xBB, 0x49, 0x17, 0x46, 0x5D, + 0x7D, 0x1F, 0xF4, 0xB5, 0xD9, 0xEF, 0x62, 0xBA, 0xC4, 0x86, 0x61, 0x0B, 0xE0, 0xC5, 0xEE, 0x69, + 0xEC, 0xF9, 0x52, 0x93, 0x3F, 0xC7, 0x69, 0xE4, 0xD2, 0x9C, 0xE0, 0xEB, 0xB5, 0x5A, 0x55, 0xCE, + 0x87, 0xA3, 0x1C, 0x52, 0x2E, 0xC5, 0x99, 0x92, 0x7F, 0x10, 0x06, 0xC4, 0xA2, 0x5B, 0x77, 0x3D, + 0x53, 0xE8, 0xCA, 0xB5, 0x3B, 0x18, 0x58, 0x54, 0xC6, 0x63, 0xDB, 0x1D, 0xB6, 0x75, 0xD2, 0x69, + 0x64, 0x8A, 0x69, 0x0E, 0xF9, 0x57, 0x41, 0x4C, 0xC2, 0xF3, 0x59, 0x0A, 0x60, 0x76, 0x67, 0x4A, + 0xE6, 0xE8, 0x63, 0xB5, 0x0A, 0x39, 0xDE, 0x95, 0xD6, 0xFB, 0xD4, 0xEC, 0xA3, 0xBD, 0x1A, 0xB1, + 0x8F, 0x54, 0x1C, 0xD7, 0x39, 0x50, 0x8F, 0x92, 0x0D, 0x33, 0x9F, 0x49, 0x10, 0xB2, 0x73, 0x87, + 0x83, 0xF0, 0x72, 0x9D, 0xE7, 0xEA, 0x14, 0xC7, 0x5A, 0x23, 0x6F, 0x54, 0x3E, 0xB5, 0x86, 0x6D, + 0xD6, 0xE2, 0x3E, 0x97, 0x96, 0x5F, 0xF4, 0x1A, 0xFD, 0x8B, 0x96, 0x9B, 0x14, 0xD7, 0x25, 0x3A, + 0x96, 0x25, 0x7B, 0xBE, 0x32, 0x46, 0xC3, 0x20, 0x4E, 0x01, 0x98, 0x0A, 0x27, 0x53, 0x58, 0xFA, + 0xAF, 0x14, 0xE6, 0x6B, 0x99, 0x32, 0x85, 0x87, 0x8F, 0xDA, 0x09, 0x7C, 0x92, 0x9D, 0x4C, 0x87, + 0xF6, 0xB3, 0x67, 0x61, 0xA2, 0x7C, 0x25, 0x5D, 0x4E, 0xA7, 0x6F, 0xF0, 0xCB, 0x6C, 0x6A, 0xC3, + 0xE2, 0x19, 0x33, 0xBE, 0x73, 0x95, 0xE1, 0xBA, 0x39, 0x09, 0x7F, 0xAE, 0x72, 0x8A, 0x4E, 0x74, + 0xA1, 0xBF, 0x5B, 0x1D, 0x34, 0x89, 0xF0, 0x94, 0xE6, 0x84, 0x3C, 0x64, 0x29, 0x04, 0x07, 0x34, + 0xC3, 0x32, 0xEF, 0xF6, 0xE5, 0x24, 0x54, 0x09, 0xFA, 0x81, 0xDB, 0xF1, 0xCF, 0xE5, 0xDB, 0x98, + 0x27, 0xC0, 0xEB, 0xDA, 0x10, 0x73, 0x74, 0x76, 0xCA, 0xD7, 0xFE, 0xDF, 0x82, 0x63, 0x0F, 0x31, + 0x03, 0xBE, 0x10, 0xF4, 0xF6, 0x76, 0xDD, 0x27, 0xAD, 0xE4, 0xC1, 0xFA, 0xC5, 0x5A, 0x71, 0x8D, + 0x59, 0x39, 0x41, 0x6C, 0xDD, 0xFB, 0x4C, 0x5C, 0xB0, 0xB8, 0xF9, 0x67, 0x9C, 0xD7, 0x90, 0x44, + 0xE2, 0xF3, 0x39, 0x4C, 0x84, 0x1A, 0x13, 0x3F, 0xF2, 0xDF, 0x77, 0xA5, 0xF6, 0xAB, 0x69, 0x37, + 0x18, 0x56, 0x03, 0x86, 0xE9, 0xB7, 0xC8, 0xD8, 0x5A, 0xA1, 0x87, 0x00, 0xBC, 0x14, 0x44, 0xFF, + 0x21, 0xDD, 0xAC, 0x99, 0xD2, 0x78, 0xDF, 0x0C, 0xF3, 0xAC, 0x5F, 0xF4, 0x56, 0xE4, 0xAB, 0xCF, + 0x5F, 0x1C, 0x60, 0x7E, 0xFA, 0xDA, 0x36, 0x8A, 0xF2, 0xD4, 0x80, 0x64, 0xC5, 0x54, 0x53, 0xCA, + 0xF3, 0x80, 0x9A, 0x3C, 0x7C, 0x7B, 0x32, 0x30, 0x14, 0xB7, 0x17, 0x9B, 0x42, 0x7C, 0x94, 0x0D, + 0xC4, 0x43, 0x5B, 0xB0, 0x86, 0xE9, 0x1F, 0x80, 0xCD, 0x45, 0x97, 0x3D, 0x8A, 0xD0, 0x22, 0x91, + 0xA0, 0x14, 0xA5, 0xD7, 0x71, 0x07, 0x8D, 0xAB, 0x69, 0xE8, 0x38, 0x98, 0xEE, 0x70, 0x3D, 0x7B, + 0x86, 0x13, 0xDE, 0xAF, 0xE5, 0x89, 0x5A, 0x5F, 0x1F, 0xF9, 0xA5, 0x3F, 0xED, 0x62, 0xE6, 0x65, + 0x3B, 0x86, 0xF9, 0x76, 0xD6, 0x5A, 0x57, 0x54, 0x8B, 0x0D, 0x39, 0xEC, 0x9F, 0x00, 0xBF, 0x4E, + 0xF8, 0x62, 0x51, 0x83, 0x74, 0x16, 0x00, 0x3A, 0x4F, 0x71, 0x18, 0x73, 0xF0, 0x41, 0x71, 0xB4, + 0xDC, 0x79, 0xFC, 0x32, 0xDB, 0x38, 0x0C, 0x3F, 0x1B, 0x66, 0xA4, 0x27, 0xED, 0xA7, 0xE2, 0xE6, + 0xB0, 0x51, 0xF6, 0xBD, 0xEF, 0x2E, 0x0E, 0x10, 0x8F, 0x1D, 0x40, 0xDF, 0x85, 0x67, 0xC7, 0x25, + 0x11, 0x7F, 0x50, 0x99, 0xC8, 0xAE, 0xDF, 0x6A, 0xAF, 0x70, 0x8C, 0xD4, 0xB5, 0x6A, 0xA5, 0x21, + 0x1C, 0xBF, 0x0C, 0x75, 0xA2, 0x40, 0x03, 0x17, 0x58, 0x8C, 0x84, 0x4D, 0x82, 0x29, 0xE5, 0x7C, + 0x05, 0xA1, 0xAF, 0x48, 0x07, 0xD1, 0xF7, 0x53, 0xBC, 0x02, 0xF7, 0xCD, 0x60, 0x35, 0xEE, 0x04, + 0x03, 0xE1, 0x3A, 0xAA, 0x71, 0x54, 0x5B, 0xDD, 0x86, 0x68, 0xFB, 0xC6, 0xBC, 0xCF, 0xCD, 0x55, + 0xBC, 0x0E, 0x0D, 0x8D, 0x7B, 0x70, 0x1F, 0xE4, 0xEC, 0x2C, 0x91, 0x22, 0xF6, 0x55, 0xC9, 0x07, + 0x0F, 0x26, 0x60, 0x4F, 0xB0, 0x27, 0xFA, 0xAB, 0xBF, 0x3B, 0xF1, 0x3F, 0x52, 0xB8, 0xC7, 0x7B, + 0x52, 0xBF, 0x6E, 0xA4, 0x87, 0xCD, 0x40, 0x62, 0x4D, 0xDA, 0xD1, 0x37, 0x77, 0x44, 0xB3, 0x1D, + 0xD6, 0x2F, 0x9A, 0xA8, 0x61, 0x54, 0x6A, 0x1E, 0x2E, 0xE2, 0xC0, 0xBF, 0x7D, 0xAD, 0xB9, 0xDB, + 0x52, 0x5C, 0x0F, 0x15, 0x7F, 0x40, 0x8B, 0xC3, 0x4E, 0xC5, 0xC3, 0x59, 0x5A, 0x19, 0x3F, 0xA1, + 0xB3, 0x58, 0x3A, 0xC2, 0x06, 0x5B, 0x16, 0xF8, 0xEA, 0xFA, 0xB6, 0x8D, 0x93, 0xFF, 0xC4, 0x96, + 0x2C, 0x9F, 0xD0, 0xAB, 0x5A, 0x2B, 0x81, 0x17, 0xA9, 0x71, 0x38, 0x0F, 0x01, 0xEF, 0x5A, 0xC0, + 0xE9, 0x9F, 0x8B, 0x63, 0x47, 0x89, 0x50, 0xC2, 0xFB, 0x8A, 0x8B, 0x9F, 0xEE, 0xC4, 0xC4, 0x7A, + 0xDC, 0xAD, 0xC3, 0x6A, 0x70, 0xA4, 0x71, 0x53, 0xFD, 0xA9, 0x0D, 0xC0, 0x62, 0x23, 0xB8, 0x9D, + 0xAE, 0xA2, 0x12, 0x0B, 0x18, 0x42, 0xB6, 0x93, 0x2A, 0x85, 0x60, 0x09, 0x59, 0x69, 0x52, 0x1F, + 0x1E, 0xBA, 0xBF, 0x0F, 0xA6, 0x8E, 0xB0, 0x8A, 0x03, 0xCA, 0xC5, 0x1C, 0x8E, 0x89, 0xF2, 0x50, + 0xD7, 0x1A, 0xA9, 0x63, 0x83, 0x0F, 0x6D, 0x06, 0x27, 0x1F, 0x40, 0xDA, 0x0B, 0x9E, 0xEB, 0x1F, + 0x7E, 0x4F, 0x8A, 0xF0, 0x38, 0x86, 0x08, 0xC6, 0x3A, 0xFF, 0x29, 0xCC, 0xA7, 0x10, 0x27, 0xF3, + 0x99, 0x3E, 0x52, 0xF3, 0x0F, 0x83, 0x93, 0x9F, 0x63, 0xE8, 0x23, 0x41, 0x71, 0x98, 0x25, 0x1B, + 0xC9, 0x89, 0x15, 0x8F, 0xC8, 0x72, 0x35, 0x72, 0x7D, 0xF2, 0x36, 0x31, 0x64, 0xF5, 0x3A, 0x4C, + 0x15, 0x9B, 0x30, 0x77, 0x36, 0x03, 0xB9, 0xEE, 0xCA, 0x61, 0x22, 0x7C, 0xCF, 0xEE, 0x6C, 0xE4, + 0xEC, 0x83, 0x67, 0x10, 0x07, 0x7A, 0x21, 0x97, 0xDF, 0xC3, 0x2A, 0x27, 0x47, 0xDB, 0x1A, 0x76, + 0x2D, 0xA5, 0x9D, 0xD7, 0x39, 0x54, 0x4F, 0x62, 0x9C, 0xFD, 0x77, 0x0E, 0xB0, 0x4A, 0x0A, 0xD9, + 0x46, 0xC2, 0x28, 0x49, 0xB0, 0x53, 0x99, 0x2A, 0xC2, 0x81, 0xF9, 0x8A, 0xE1, 0x01, 0xDA, 0xCC, + 0x31, 0x60, 0x9B, 0x32, 0x4B, 0x69, 0x2B, 0x89, 0x00, 0xDA, 0xCD, 0x41, 0x0B, 0x13, 0xC0, 0x3C, + 0x4A, 0xD0, 0x32, 0x0A, 0x45, 0x31, 0x54, 0x38, 0x9E, 0x74, 0x56, 0x60, 0x8A, 0xFA, 0x0C, 0x0C, + 0xC8, 0xDC, 0x4E, 0x12, 0x9A, 0xD0, 0x2B, 0xAC, 0xF3, 0x16, 0xD3, 0xE4, 0x5C, 0xA8, 0x3B, 0x8A, + 0x8E, 0x04, 0x4B, 0x09, 0xDE, 0x91, 0x1C, 0xF2, 0x3D, 0xB5, 0x27, 0x23, 0x9A, 0x5F, 0xCA, 0xCE, + 0x1D, 0x81, 0x25, 0xD3, 0x0B, 0x07, 0x3B, 0xA2, 0xED, 0xC1, 0x68, 0xA3, 0x10, 0x1E, 0x49, 0xED, + 0x2B, 0x02, 0xD4, 0x65, 0x6B, 0xDE, 0xF7, 0xE8, 0x3D, 0xC3, 0x41, 0x1E, 0x75, 0xDB, 0xD0, 0xE4, + 0xA7, 0xFF, 0xFC, 0xB3, 0x0D, 0xAE, 0x72, 0x6D, 0xF2, 0x16, 0xF9, 0x4C, 0x9B, 0x2C, 0x83, 0x55, + 0x53, 0x32, 0xB1, 0x4E, 0xE7, 0x7E, 0x7F, 0xF6, 0xBE, 0xE4, 0x7A, 0xF3, 0xDB, 0x73, 0xA5, 0xDC, + 0xB3, 0x1F, 0x1B, 0x9E, 0x93, 0x58, 0x58, 0x4C, 0xDB, 0xED, 0x8C, 0x02, 0xB7, 0x43, 0x10, 0x7F, + 0x32, 0xF0, 0xFC, 0xD2, 0xDA, 0x18, 0xA6, 0x74, 0x80, 0x12, 0x9C, 0xBB, 0xB9, 0xA9, 0x03, 0x76, + 0xC9, 0x4E, 0xE0, 0xE3, 0x63, 0x96, 0xC8, 0x32, 0x04, 0x06, 0x15, 0x52, 0xD1, 0xB6, 0x03, 0x1B, + 0x5D, 0xF2, 0x40, 0x43, 0x37, 0xCF, 0x7C, 0xF5, 0xC4, 0xAB, 0x8B, 0x83, 0x63, 0x5D, 0xE3, 0x3B, + 0x3B, 0x66, 0xE6, 0x19, 0xEF, 0x51, 0x6B, 0x60, 0x6F, 0x3E, 0x71, 0x3D, 0x8E, 0x5A, 0x77, 0xD2, + 0x57, 0x9A, 0x06, 0xE9, 0xBB, 0x8A, 0x50, 0x58, 0x90, 0xF1, 0x17, 0xD7, 0x18, 0x9C, 0x24, 0x65, + 0x3D, 0x40, 0xA5, 0xA1, 0xD8, 0x24, 0x4B, 0xAA, 0x8A, 0x47, 0x3F, 0x0D, 0xB6, 0x60, 0xE6, 0x75, + 0xCD, 0x45, 0x6D, 0x54, 0xD9, 0x67, 0x65, 0xE0, 0x04, 0xCC, 0xBE, 0xCC, 0xAA, 0x8E, 0x3D, 0xB9, + 0x1E, 0x03, 0x25, 0x25, 0x31, 0x88, 0x36, 0xF6, 0x52, 0xD7, 0x4E, 0x75, 0xB5, 0xEA, 0x05, 0x83, + 0x59, 0xAD, 0xAF, 0xF4, 0x13, 0xCE, 0x5D, 0xF4, 0x52, 0x98, 0xBC, 0x63, 0xFE, 0xB0, 0xC5, 0x55, + 0xBD, 0x5A, 0x65, 0x3E, 0xAF, 0x24, 0x4C, 0x5E, 0xEA, 0x9A, 0x44, 0x32, 0x0E, 0x39, 0xCA, 0x60, + 0xB2, 0xBF, 0x62, 0x40, 0x19, 0x57, 0xDE, 0x7C, 0x70, 0xD7, 0xC3, 0x0E, 0x9A, 0x98, 0x6F, 0x26, + 0x22, 0x45, 0xC4, 0xD1, 0x8E, 0x6B, 0xC9, 0x3F, 0x1E, 0x71, 0x4F, 0x2E, 0x86, 0x42, 0x73, 0x00, + 0xEB, 0x98, 0x22, 0x03, 0x1F, 0x6F, 0xC3, 0xAF, 0xD0, 0x55, 0x8F, 0x95, 0x6F, 0x8E, 0x4B, 0xF0, + 0x21, 0x1D, 0xA7, 0xB1, 0xE8, 0x6B, 0xC7, 0x8A, 0xAD, 0x69, 0xD7, 0x6A, 0x7F, 0x09, 0x3A, 0x9F, + 0xBF, 0x30, 0x27, 0x18, 0x00, 0x11, 0xF2, 0x96, 0xAB, 0x57, 0xD3, 0x67, 0x5D, 0x2A, 0x36, 0xCF, + 0xE2, 0x05, 0xBB, 0x01, 0x83, 0x0B, 0xC7, 0x6D, 0xE9, 0xFD, 0x5A, 0xC8, 0x0D, 0x9C, 0xC0, 0xA2, + 0x41, 0x8D, 0x0E, 0x53, 0xB2, 0xD2, 0x2B, 0xD8, 0xE5, 0x4C, 0xEE, 0x81, 0x52, 0xED, 0xE8, 0xEA, + 0xF1, 0xF3, 0x2A, 0x5D, 0xEC, 0x2D, 0x0E, 0xFD, 0x76, 0x26, 0x4C, 0x25, 0x78, 0x26, 0x6F, 0x2F, + 0xF7, 0x31, 0xAB, 0xC0, 0x6C, 0x80, 0x1F, 0x2B, 0x8B, 0x1E, 0xC1, 0x09, 0x46, 0x5E, 0x94, 0x19, + 0xED, 0x07, 0x94, 0xEC, 0xCF, 0x3B, 0xC6, 0x51, 0x29, 0xC2, 0x87, 0xD4, 0x55, 0xD0, 0xAD, 0x82, + 0x66, 0x27, 0x61, 0x18, 0xDD, 0xB8, 0xBD, 0xF9, 0xE1, 0xCA, 0xAA, 0xBA, 0x49, 0x39, 0xD6, 0x43, + 0xA9, 0x10, 0x12, 0x7C, 0xA6, 0x82, 0xD8, 0xDB, 0x11, 0x76, 0x9D, 0xF0, 0x92, 0xFB, 0x31, 0xC1, + 0x9C, 0x31, 0x78, 0x1C, 0x11, 0xD6, 0xF3, 0x1F, 0x14, 0x39, 0xC6, 0x33, 0x46, 0x58, 0x8C, 0xE9, + 0x2B, 0x87, 0x94, 0xA6, 0xFA, 0x55, 0x5A, 0x3C, 0x39, 0x60, 0xD0, 0x26, 0x0F, 0xB3, 0x56, 0x18, + 0x77, 0x9C, 0x1B, 0x01, 0xBA, 0xE6, 0xB2, 0x1E, 0x8B, 0xA0, 0x63, 0x2D, 0x7E, 0xA2, 0x30, 0xFC, + 0xDF, 0x31, 0x99, 0xB2, 0xD6, 0x1E, 0xD3, 0xAB, 0xB7, 0xFA, 0x72, 0xB0, 0x6F, 0xE5, 0x6B, 0x9F, + 0xAF, 0xB4, 0xF0, 0x53, 0x06, 0x9C, 0xB0, 0x98, 0x6B, 0xF4, 0x5A, 0x52, 0xDB, 0xD0, 0x13, 0xED, + 0x73, 0x70, 0x11, 0xBB, 0xD0, 0x98, 0x58, 0xCA, 0x29, 0x44, 0xCB, 0x7C, 0x2D, 0x43, 0xDE, 0x4F, + 0xBF, 0x13, 0x06, 0xDD, 0x3C, 0x69, 0x49, 0xD2, 0xD7, 0xA7, 0x24, 0x9E, 0x0D, 0x3C, 0x8C, 0x73, + 0x0F, 0xB2, 0x4F, 0x16, 0x83, 0x7E, 0x7B, 0x3A, 0xD4, 0x49, 0x42, 0x26, 0x9C, 0x6F, 0xAF, 0xD6, + 0x73, 0xEF, 0x29, 0x3D, 0x1A, 0x21, 0x58, 0x48, 0xF7, 0xEE, 0xD2, 0xC8, 0x06, 0x4D, 0xB2, 0x3D, + 0x1E, 0xD6, 0xF6, 0xF3, 0x25, 0xC8, 0xD5, 0xF4, 0xB6, 0x07, 0x2C, 0xB0, 0x03, 0xDE, 0x83, 0xE0, + 0x1C, 0x68, 0x2E, 0x78, 0xB6, 0xDA, 0x99, 0xA6, 0xBD, 0xE8, 0x1D, 0x47, 0x6E, 0x7A, 0x4C, 0xC5, + 0xE4, 0x54, 0xEA, 0xDB, 0xB1, 0x6F, 0x9F, 0x53, 0xA6, 0x41, 0xE2, 0x24, 0x6C, 0x9C, 0x88, 0xF7, + 0x88, 0xA8, 0x90, 0x0D, 0x34, 0x61, 0xAA, 0x7D, 0x52, 0x5F, 0x8D, 0x81, 0xBE, 0xC9, 0x3E, 0x36, + 0x8D, 0x69, 0x81, 0x0D, 0x24, 0x7D, 0xCE, 0x03, 0xB8, 0x4E, 0x9C, 0xFD, 0x5A, 0x4A, 0x45, 0xAF, + 0x45, 0xB5, 0xFF, 0x49, 0xEA, 0x6C, 0xF7, 0xB9, 0xE5, 0xC1, 0xA6, 0x57, 0xF3, 0xCA, 0xCC, 0x46, + 0xD2, 0x20, 0xB3, 0xB1, 0xC0, 0x18, 0xEE, 0x82, 0xE4, 0x00, 0x3C, 0xA7, 0x8B, 0xA6, 0x3A, 0xDA, + 0x82, 0x53, 0x3C, 0x42, 0x4C, 0x3B, 0x16, 0xD0, 0x3E, 0x0E, 0xBA, 0x36, 0xA6, 0xEA, 0x36, 0x16, + 0x80, 0xA3, 0x51, 0xFD, 0xF8, 0xFA, 0xE5, 0x75, 0x64, 0x17, 0x0C, 0xE5, 0xAE, 0xB2, 0xE5, 0x14, + 0xC1, 0xA9, 0xAE, 0x3A, 0x7E, 0x4D, 0x0E, 0x5C, 0x67, 0x31, 0x02, 0xD2, 0x4D, 0xB3, 0xD6, 0xC6, + 0xE6, 0x86, 0xC2, 0x62, 0xE6, 0xB6, 0x53, 0x2C, 0x29, 0x72, 0x90, 0x55, 0xAC, 0x1C, 0xE7, 0x7F, + 0x7F, 0xA3, 0xE8, 0x21, 0xF0, 0x2D, 0x3E, 0x8A, 0xA3, 0xA2, 0xA2, 0x29, 0xEC, 0x36, 0x4B, 0x89, + 0x1C, 0xB6, 0xC1, 0xB9, 0x4A, 0x8F, 0x65, 0xDF, 0x97, 0x29, 0x0F, 0x0B, 0xB1, 0x61, 0x24, 0xA2, + 0xD2, 0x57, 0x7A, 0x99, 0xF7, 0x1D, 0xC8, 0xB4, 0xDD, 0xEE, 0x7A, 0xBE, 0x2E, 0x46, 0x9A, 0xF6, + 0x92, 0x13, 0xAC, 0x98, 0x64, 0x4C, 0xDD, 0x6B, 0x03, 0xD3, 0xF4, 0x14, 0xC9, 0x7D, 0xCE, 0x9B, + 0xF7, 0xD4, 0x80, 0xB1, 0x2A, 0x66, 0x33, 0xA4, 0xE2, 0x45, 0x99, 0x95, 0x77, 0xE1, 0x3E, 0x8A, + 0x43, 0x85, 0xD9, 0x79, 0x69, 0x0D, 0x55, 0xCF, 0x09, 0xF4, 0xFC, 0x07, 0x39, 0x20, 0x26, 0xB4, + 0x8B, 0xD0, 0x60, 0xFB, 0x42, 0x41, 0xEF, 0x02, 0x5F, 0x0D, 0xC7, 0x7F, 0x09, 0xFA, 0x26, 0x94, + 0x7A, 0xCD, 0xDE, 0x43, 0x86, 0x44, 0xD6, 0xC6, 0xB1, 0x77, 0x13, 0xB1, 0x08, 0xC9, 0xDD, 0x99, + 0xF0, 0xED, 0x7B, 0xB0, 0xAB, 0x31, 0x6D, 0x5B, 0x66, 0xA3, 0x53, 0xE2, 0x3B, 0x0F, 0x31, 0xA2, + 0x63, 0x6E, 0x05, 0xAD, 0xC6, 0x74, 0xC5, 0x2D, 0xB3, 0x79, 0xEB, 0x66, 0xF3, 0x6B, 0x79, 0x02, + 0x7A, 0x7D, 0x49, 0x59, 0x30, 0x04, 0x29, 0xD8, 0x5E, 0xF7, 0xF3, 0x6C, 0x5E, 0xC2, 0xA6, 0xFF, + 0x1B, 0x5A, 0xFE, 0x01, 0x54, 0x40, 0xEF, 0xA0, 0xD5, 0xDB, 0x47, 0xED, 0x2A, 0x9B, 0x85, 0x12, + 0x9F, 0x74, 0x5D, 0xB7, 0xA6, 0x80, 0x22, 0x34, 0x87, 0x81, 0x7E, 0x15, 0x87, 0x62, 0x03, 0x87, + 0x62, 0x6B, 0x85, 0xF1, 0x37, 0x55, 0x55, 0x31, 0x18, 0xBD, 0xEF, 0x57, 0x1D, 0x21, 0x5E, 0x87, + 0x37, 0x53, 0x61, 0x7B, 0xA5, 0x80, 0x71, 0x2C, 0x72, 0x83, 0x64, 0x73, 0xF2, 0x4C, 0xA0, 0x3F, + 0x4C, 0x3E, 0x21, 0xF3, 0x96, 0xBB, 0x9C, 0x05, 0xE1, 0x12, 0x47, 0xA2, 0x6E, 0xF6, 0x67, 0xFE, + 0x1B, 0x3F, 0x74, 0xA5, 0x6B, 0x2E, 0xB3, 0x77, 0x02, 0x64, 0x30, 0xAF, 0x6C, 0x64, 0x2E, 0x69, + 0x92, 0xA5, 0xD3, 0xA8, 0xEE, 0xEF, 0xB7, 0x89, 0xD9, 0x31, 0x1B, 0x61, 0xC8, 0x91, 0xF1, 0xC5, + 0x59, 0x7D, 0xCE, 0xDF, 0xFB, 0xF2, 0x10, 0xF7, 0x42, 0x02, 0xEC, 0x70, 0x2F, 0xE4, 0x02, 0xB4, + 0xFB, 0xA0, 0xFC, 0x83, 0x58, 0x68, 0xA2, 0x8E, 0x01, 0x8B, 0x10, 0xF6, 0x30, 0x86, 0xAA, 0x5A, + 0xC4, 0x95, 0x61, 0x02, 0x8E, 0xF2, 0xB7, 0x6B, 0x6C, 0x80, 0x5C, 0xCB, 0x11, 0x66, 0x97, 0x60, + 0x70, 0xD4, 0x06, 0xA9, 0xC7, 0x80, 0xE3, 0x2C, 0xA4, 0xE1, 0xAA, 0x34, 0x92, 0x13, 0x57, 0x05, + 0xF9, 0x70, 0xF3, 0xF4, 0x80, 0x73, 0xF2, 0x16, 0xDA, 0xBD, 0x39, 0x51, 0xD1, 0x40, 0xC1, 0x59, + 0x04, 0xCD, 0xE4, 0x79, 0x63, 0x24, 0x69, 0xC6, 0x99, 0x2F, 0x4D, 0x0D, 0x1C, 0x9C, 0x15, 0xED, + 0x41, 0x47, 0x12, 0x78, 0x6F, 0x8E, 0xFF, 0xA7, 0x34, 0xB8, 0x8B, 0x0C, 0x70, 0x96, 0x07, 0x0B, + 0x49, 0x42, 0x59, 0xFE, 0x5A, 0x08, 0x76, 0x36, 0x3D, 0x7D, 0xBA, 0x10, 0x1B, 0x11, 0xB4, 0x6B, + 0x1C, 0x59, 0x60, 0x02, 0x36, 0x30, 0xD9, 0xEC, 0xBA, 0xEE, 0x22, 0xFD, 0x0F, 0x2E, 0xA2, 0xE2, + 0x70, 0x9C, 0x0D, 0x18, 0x58, 0x88, 0x4A, 0x0D, 0xE7, 0x0E, 0xEF, 0xD8, 0x6F, 0xA7, 0x70, 0x12, + 0x9D, 0x06, 0x4C, 0x8C, 0xAD, 0x1A, 0x28, 0x01, 0xB6, 0x23, 0x53, 0x76, 0xDD, 0x3F, 0x17, 0x87, + 0x0B, 0xC1, 0xEB, 0x9C, 0x44, 0x67, 0x1B, 0x79, 0xE0, 0x67, 0xB2, 0x2A, 0x99, 0x72, 0xEB, 0x4C, + 0xB4, 0x17, 0x2E, 0xB5, 0xE8, 0x52, 0x1E, 0xCB, 0x3A, 0x3D, 0xF7, 0xF2, 0x21, 0xC7, 0xF1, 0x29, + 0x97, 0x22, 0x31, 0x2C, 0x39, 0x8A, 0xAF, 0x47, 0xF9, 0x3B, 0xA9, 0x8B, 0x2B, 0xEF, 0xE2, 0xF0, + 0x11, 0x59, 0xFC, 0xE4, 0x56, 0xFF, 0x4E, 0xA7, 0x92, 0xE5, 0xE5, 0x26, 0x16, 0xC8, 0x2C, 0x35, + 0xD2, 0x70, 0x9F, 0xAE, 0xA7, 0x08, 0x16, 0x2B, 0x64, 0xA1, 0xF6, 0xF3, 0xC1, 0x43, 0x27, 0x92, + 0x46, 0x4E, 0xA2, 0x01, 0x72, 0x18, 0x08, 0xAB, 0x22, 0x42, 0x1B, 0x9E, 0x35, 0xAE, 0xB3, 0xE6, + 0x42, 0x1B, 0x49, 0x31, 0xBB, 0xA4, 0xD0, 0xD6, 0x7A, 0xEB, 0xFE, 0x32, 0x37, 0x7F, 0xE6, 0x2F, + 0x75, 0x54, 0x1D, 0x88, 0x9F, 0x81, 0xEE, 0xF0, 0x97, 0xC9, 0x12, 0x80, 0x47, 0x5C, 0xCA, 0x3B, + 0x45, 0xF4, 0x63, 0xC3, 0x16, 0x2D, 0x7E, 0xCD, 0x80, 0xC2, 0xBA, 0x50, 0x1D, 0xC2, 0x31, 0x72, + 0xF0, 0x27, 0x97, 0xB0, 0xF4, 0x69, 0x43, 0x2B, 0x26, 0x89, 0x28, 0xFC, 0xBD, 0x03, 0x6A, 0x4A, + 0x22, 0x5D, 0xD3, 0x7D, 0xEC, 0xC8, 0xEC, 0x7B, 0x15, 0xFA, 0x05, 0x5D, 0x73, 0x6B, 0x5B, 0x1B, + 0xC9, 0x83, 0xD0, 0xFA, 0x24, 0xBA, 0x97, 0x11, 0x30, 0x04, 0xD3, 0x11, 0xCE, 0x24, 0xBD, 0x71, + 0xB7, 0xAA, 0xB2, 0xC2, 0x43, 0xC2, 0x67, 0x3B, 0x9C, 0x28, 0x62, 0x52, 0xF0, 0xFA, 0xCB, 0xFA, + 0xDF, 0x4F, 0xC9, 0x23, 0xD1, 0x94, 0xE1, 0x5F, 0x2A, 0xF2, 0xC7, 0x5F, 0x76, 0x6B, 0x86, 0x28, + 0x29, 0xF1, 0x54, 0x4F, 0x7A, 0x4D, 0xFD, 0xD0, 0x51, 0xFA, 0xBC, 0x6F, 0x7B, 0x44, 0xE5, 0xB0, + 0xF3, 0xC0, 0x34, 0x80, 0x6D, 0xE6, 0xDD, 0xCD, 0x7F, 0x67, 0x7B, 0x15, 0xC5, 0xE5, 0x14, 0x64, + 0x80, 0x81, 0xD9, 0x47, 0xCE, 0x71, 0x62, 0x94, 0xCE, 0x41, 0x61, 0xFA, 0xDD, 0x5A, 0x6D, 0xC1, + 0x28, 0x87, 0x39, 0xC4, 0xBC, 0x89, 0x3A, 0x99, 0x18, 0x80, 0xDC, 0x50, 0x72, 0xCF, 0x67, 0x4D, + 0x77, 0x6D, 0x6A, 0xB4, 0x17, 0x85, 0xD6, 0x2B, 0xC3, 0x4A, 0x7C, 0xD1, 0xF3, 0x0E, 0xA4, 0x8F, + 0x3B, 0xDA, 0x8A, 0x7B, 0x0A, 0x37, 0x7D, 0x36, 0xEC, 0x89, 0x87, 0xD9, 0x88, 0xB7, 0xC9, 0x1F, + 0xEB, 0xEE, 0x25, 0x46, 0xA9, 0x3B, 0x19, 0x16, 0x17, 0x2D, 0x0F, 0x8C, 0xEB, 0x19, 0xF3, 0x47, + 0xC7, 0x21, 0xA8, 0x1E, 0x7F, 0xC4, 0xE3, 0x6B, 0x96, 0x1D, 0x63, 0xDD, 0xC2, 0xEF, 0xB2, 0x65, + 0x60, 0x07, 0xE5, 0xD3, 0x48, 0x49, 0x9A, 0xF2, 0xB0, 0x76, 0x2D, 0xFB, 0x68, 0xF8, 0xAD, 0xE9, + 0x3C, 0x52, 0x37, 0x9A, 0xD1, 0xAE, 0x16, 0x37, 0x2E, 0xB3, 0x63, 0xE0, 0x66, 0xB1, 0x4D, 0x49, + 0x83, 0xD2, 0xEA, 0x20, 0xFD, 0x43, 0x84, 0x5B, 0x22, 0xBC, 0x0C, 0xA1, 0x85, 0x28, 0xF7, 0x45, + 0xF1, 0x2A, 0xC2, 0xFE, 0x87, 0x4C, 0xFC, 0x0A, 0x5B, 0xD9, 0x84, 0x7A, 0xAC, 0x8C, 0xBD, 0xDF, + 0xDC, 0xA5, 0xFC, 0xD0, 0x85, 0x65, 0xA2, 0x73, 0x1C, 0x7C, 0xFD, 0xF9, 0xBA, 0x1E, 0xBD, 0x5F, + 0x06, 0xE8, 0xFC, 0x62, 0xD1, 0xF7, 0x13, 0x52, 0xE3, 0xC2, 0xEB, 0xEE, 0x0E, 0x7E, 0x9D, 0x8A, + 0x19, 0x3E, 0xA2, 0x62, 0x06, 0xC5, 0xA1, 0xDC, 0x6B, 0x4A, 0x59, 0xA6, 0x57, 0x73, 0xCB, 0x57, + 0x16, 0x99, 0xFB, 0x93, 0x36, 0xDF, 0x0E, 0x1A, 0x38, 0xD4, 0x89, 0x02, 0x79, 0xB6, 0xA8, 0x52, + 0xCB, 0x2E, 0x96, 0xD4, 0xD8, 0x52, 0xA9, 0x7C, 0xE3, 0x97, 0x47, 0x6F, 0x6E, 0x82, 0x6D, 0x3A, + 0x01, 0x8F, 0x2F, 0x59, 0xDF, 0x93, 0xBA, 0x52, 0xC4, 0x46, 0xDA, 0xC5, 0x0C, 0x2E, 0x40, 0xB8, + 0x37, 0x55, 0x40, 0xB8, 0x13, 0xCD, 0x51, 0x96, 0xCE, 0x4A, 0x6C, 0x0D, 0xF9, 0x5A, 0xE6, 0x34, + 0x95, 0xF0, 0xFF, 0x54, 0x93, 0x05, 0x9D, 0x56, 0x94, 0xF7, 0x23, 0x90, 0x00, 0x44, 0xA7, 0xD9, + 0x86, 0x71, 0xB2, 0xFD, 0x58, 0x19, 0xBB, 0xEC, 0x6B, 0x90, 0x07, 0x2B, 0x7A, 0x20, 0x4B, 0xFD, + 0x0E, 0xB8, 0xF8, 0x00, 0x13, 0xC2, 0xF2, 0x32, 0x39, 0xED, 0x71, 0x59, 0x71, 0xC4, 0xDC, 0xE5, + 0xC2, 0xA9, 0x2B, 0x7B, 0x52, 0x00, 0xCB, 0x49, 0xB4, 0x00, 0xE6, 0x98, 0x13, 0xC1, 0x2F, 0x3A, + 0x19, 0x86, 0xD5, 0x74, 0xCD, 0xBC, 0xBA, 0x4C, 0x48, 0x00, 0x8E, 0x22, 0xD5, 0xAC, 0x11, 0xEF, + 0xC8, 0xCD, 0x99, 0xBA, 0xBC, 0x0F, 0xD4, 0xC0, 0xAE, 0x15, 0x2F, 0x82, 0x6D, 0x4D, 0x44, 0x10, + 0xF1, 0xF5, 0x55, 0xF0, 0x00, 0xAF, 0xEA, 0x96, 0x1F, 0xE6, 0xA6, 0x9A, 0x78, 0xDD, 0x6D, 0xD5, + 0xDF, 0xA4, 0x4D, 0xAE, 0x05, 0xEE, 0x12, 0x9F, 0x9B, 0x99, 0xAC, 0xE4, 0xBE, 0x9E, 0x56, 0xCC, + 0x4B, 0x6D, 0x26, 0xB9, 0x56, 0x9B, 0x9B, 0x52, 0x6D, 0x14, 0x28, 0x7D, 0xE8, 0x75, 0xCB, 0x59, + 0xC0, 0x50, 0x69, 0x6B, 0xD7, 0x68, 0x06, 0xA8, 0x38, 0xD1, 0xBB, 0xD9, 0xA3, 0x3D, 0xA1, 0xBD, + 0x7D, 0x9E, 0xD2, 0xB9, 0x5D, 0x35, 0x02, 0xE0, 0xD4, 0xAF, 0x66, 0x56, 0x7D, 0xF4, 0x88, 0x74, + 0x3C, 0x28, 0x2C, 0xBA, 0x20, 0xC1, 0x2A, 0x93, 0xA9, 0xBA, 0x90, 0xE8, 0xC2, 0xE9, 0x15, 0xC8, + 0x8F, 0xBD, 0x27, 0xDE, 0x21, 0xBF, 0x56, 0x69, 0x93, 0x72, 0x7E, 0xE4, 0xE7, 0x7D, 0xE8, 0x4D, + 0xBF, 0x24, 0xA2, 0x77, 0xC0, 0xE0, 0x30, 0xD9, 0xEE, 0x82, 0xCD, 0x21, 0x60, 0xFC, 0xD1, 0x6F, + 0xB4, 0x40, 0x01, 0xFE, 0xF4, 0x73, 0xAF, 0xBB, 0xF6, 0x2B, 0x41, 0xFC, 0xDE, 0x74, 0x78, 0xE5, + 0x7E, 0xAB, 0xC9, 0xED, 0x63, 0x4B, 0x3E, 0x1A, 0x27, 0xCD, 0xBF, 0x85, 0xA4, 0x70, 0x92, 0x36, + 0x56, 0x70, 0x4A, 0xEE, 0x45, 0xAB, 0x33, 0x0A, 0x76, 0xB8, 0x0B, 0xEA, 0x55, 0xC7, 0x4E, 0xD7, + 0xD9, 0xC7, 0x10, 0x8D, 0x09, 0xFA, 0x10, 0x30, 0x62, 0x20, 0xE5, 0xBC, 0xFB, 0x74, 0x72, 0xA6, + 0x77, 0xB5, 0xBB, 0xD7, 0xD6, 0x60, 0x3A, 0x6B, 0x5D, 0xF5, 0x35, 0xF4, 0x84, 0xA0, 0x19, 0x49, + 0x36, 0x00, 0xC9, 0x35, 0xCC, 0xF7, 0xFE, 0x92, 0xF9, 0x4B, 0x8B, 0xB3, 0xBE, 0x98, 0xBF, 0xF4, + 0x25, 0xB1, 0x62, 0x4D, 0x41, 0xA0, 0xF7, 0x36, 0x6C, 0x86, 0x3C, 0x4F, 0xC4, 0xF8, 0xF2, 0xFC, + 0xE6, 0x34, 0x30, 0x95, 0x32, 0x9D, 0xE5, 0x85, 0x70, 0xD8, 0xB8, 0xD6, 0xD9, 0x42, 0xB9, 0x05, + 0x6E, 0x4B, 0x6F, 0x29, 0x33, 0x8E, 0xE4, 0x31, 0x3C, 0x21, 0x7B, 0x19, 0x31, 0x97, 0xD5, 0x62, + 0x6B, 0xF6, 0x98, 0x3C, 0xA5, 0x54, 0x18, 0x16, 0x03, 0x49, 0x51, 0x1F, 0x13, 0x37, 0xFB, 0x18, + 0x9D, 0xCF, 0x16, 0x43, 0x34, 0x7D, 0xB6, 0x5C, 0x9E, 0x74, 0x4D, 0x66, 0x23, 0x99, 0xDE, 0x6E, + 0x57, 0xFE, 0x47, 0x0D, 0x3B, 0x59, 0x3F, 0x01, 0x79, 0x75, 0x0D, 0x78, 0x62, 0x4A, 0xA5, 0xF2, + 0xF8, 0xD6, 0xAE, 0x11, 0x85, 0x88, 0x13, 0x98, 0x07, 0x0B, 0x92, 0x99, 0xC6, 0x9F, 0x85, 0x24, + 0xB1, 0x48, 0xEF, 0x59, 0x11, 0xCB, 0xAB, 0x92, 0xE4, 0x58, 0x50, 0xAA, 0xE8, 0x97, 0xD5, 0xCD, + 0x63, 0x57, 0x61, 0x62, 0x06, 0xBD, 0x26, 0x08, 0x0E, 0xC6, 0x42, 0x98, 0x8E, 0x82, 0x13, 0xD7, + 0xB8, 0x18, 0x7B, 0xAA, 0xFD, 0x42, 0x51, 0x07, 0xDE, 0x6F, 0xCF, 0xE7, 0x87, 0xF6, 0xBF, 0x15, + 0x8D, 0x8E, 0xFD, 0xA9, 0x4A, 0x45, 0x81, 0x57, 0x8F, 0x22, 0x4D, 0x63, 0x21, 0x4E, 0x41, 0x3C, + 0xCD, 0x77, 0xF5, 0xEC, 0xB8, 0x15, 0x8A, 0xBC, 0x69, 0x22, 0x98, 0xE5, 0xA4, 0x66, 0x47, 0xC5, + 0x9F, 0xE9, 0x9D, 0x06, 0xD8, 0xD2, 0x37, 0xA0, 0x4C, 0xDC, 0x33, 0xD4, 0xE7, 0xED, 0x77, 0x7D, + 0xA2, 0x20, 0x81, 0xB2, 0x46, 0xC5, 0xF6, 0xF5, 0x2A, 0x76, 0x50, 0xAF, 0xC2, 0x5A, 0xC7, 0x8F, + 0xD8, 0x3C, 0x4F, 0x2F, 0xE3, 0x56, 0x04, 0xB2, 0x6C, 0x8C, 0x0D, 0x84, 0xD7, 0xE0, 0xB5, 0x45, + 0xB2, 0x93, 0x34, 0xA0, 0xC2, 0xF4, 0x3A, 0x28, 0xDC, 0x50, 0xE8, 0xF3, 0xF0, 0x7C, 0x67, 0x1A, + 0x11, 0xD7, 0x8D, 0xB2, 0x71, 0x3D, 0xB3, 0x68, 0x49, 0x19, 0x29, 0x54, 0xDF, 0x44, 0xB9, 0x48, + 0xDD, 0x9E, 0xCE, 0xCD, 0x2D, 0x69, 0xB6, 0x18, 0xAD, 0xBC, 0xDC, 0xDF, 0x1F, 0x48, 0x04, 0x16, + 0xD6, 0x3A, 0x39, 0x7E, 0x30, 0xDB, 0x9D, 0xE1, 0x14, 0xF2, 0x5F, 0xDB, 0x1D, 0xBB, 0x7F, 0x4F, + 0x49, 0xBF, 0x4D, 0x2B, 0x9B, 0x5C, 0x7E, 0xA8, 0x1B, 0x79, 0x4F, 0x29, 0xA2, 0xF8, 0xBA, 0x36, + 0x86, 0x10, 0xFA, 0x7C, 0x45, 0x99, 0xE9, 0xD1, 0xDB, 0x93, 0xDB, 0x2A, 0xD5, 0xA8, 0x61, 0x48, + 0xBA, 0xD9, 0x40, 0xD7, 0xAE, 0xE5, 0x35, 0xAA, 0xF0, 0xA5, 0xFD, 0xC5, 0x64, 0x0B, 0x31, 0xA5, + 0x9D, 0x83, 0x31, 0xF6, 0xB8, 0xB5, 0x18, 0x3E, 0xB4, 0x2A, 0x8E, 0x70, 0x7A, 0xB8, 0xAC, 0xB5, + 0x92, 0xDD, 0x12, 0xA7, 0x9D, 0x41, 0xB1, 0x16, 0xE8, 0x45, 0x63, 0x1B, 0xB9, 0x73, 0xF1, 0x19, + 0xF6, 0xAE, 0x0F, 0xF3, 0xF7, 0x60, 0xED, 0x27, 0x70, 0x12, 0x8C, 0x12, 0x3A, 0xCF, 0xA6, 0xDC, + 0xBD, 0x17, 0x82, 0x05, 0x6E, 0x10, 0x04, 0xD4, 0xE7, 0x10, 0x9B, 0x83, 0x55, 0xB0, 0xED, 0x1B, + 0xF9, 0x7D, 0x75, 0x8E, 0xB2, 0x26, 0xA7, 0xBD, 0xE2, 0x0F, 0x29, 0x25, 0x6C, 0xFD, 0xC9, 0xC4, + 0x72, 0xE1, 0x1C, 0xDC, 0x9E, 0xBE, 0x69, 0xB5, 0xE8, 0x31, 0x83, 0x03, 0x5A, 0xEA, 0xA3, 0x52, + 0xD7, 0x51, 0x8A, 0x76, 0xB8, 0x83, 0xF0, 0x5E, 0xDC, 0xFC, 0xED, 0x80, 0x36, 0x7A, 0x39, 0xC6, + 0xFC, 0xF3, 0xC3, 0xE1, 0x5D, 0x15, 0x05, 0x30, 0xF6, 0x74, 0x93, 0x45, 0xB5, 0x2B, 0x0B, 0x63, + 0x2E, 0xF1, 0x24, 0x4B, 0x16, 0xD9, 0x61, 0xDB, 0x97, 0x9A, 0xC8, 0xFF, 0xCE, 0x74, 0xE9, 0x39, + 0x93, 0x00, 0xF8, 0xE0, 0x50, 0x62, 0x07, 0x0A, 0x6B, 0xB4, 0xBA, 0x2B, 0x61, 0x09, 0x25, 0x58, + 0x18, 0x0A, 0xB8, 0xDA, 0x8D, 0xFA, 0x8C, 0x4A, 0x41, 0xBE, 0xA4, 0x51, 0x9B, 0x83, 0x14, 0xC4, + 0xB5, 0x81, 0xB0, 0xA1, 0x35, 0x48, 0x34, 0xEB, 0xAF, 0x74, 0xF4, 0x61, 0xC5, 0x00, 0x39, 0x7D, + 0xAD, 0xF1, 0x07, 0x9C, 0x43, 0x20, 0x8F, 0x4B, 0x3D, 0xD3, 0x7C, 0x91, 0x1D, 0xC3, 0x3B, 0x06, + 0x50, 0x59, 0xA9, 0xC9, 0xA3, 0x23, 0xC9, 0xA6, 0x8A, 0x29, 0x84, 0x0D, 0x7D, 0xEE, 0x56, 0x5D, + 0xA1, 0xB6, 0xA3, 0xE7, 0x1F, 0x2A, 0xFB, 0x95, 0x7B, 0xFC, 0x9F, 0x9A, 0x18, 0xEA, 0xFF, 0xFD, + 0x61, 0x75, 0x3A, 0xF2, 0x22, 0xFF, 0xEB, 0xA2, 0x7C, 0x5A, 0x16, 0xB1, 0xE9, 0x93, 0xB0, 0x4D, + 0xA7, 0xA4, 0xB0, 0xAF, 0x70, 0x8C, 0x7E, 0x7D, 0xE8, 0x4F, 0xAA, 0x9D, 0x81, 0xD6, 0xF8, 0xB5, + 0xB6, 0x32, 0xA5, 0x31, 0xA1, 0x55, 0x11, 0x3A, 0x01, 0x13, 0x38, 0x3A, 0x41, 0x7A, 0x79, 0x31, + 0x3C, 0x13, 0xF5, 0x2C, 0x97, 0x28, 0xBD, 0x1F, 0x2E, 0x68, 0xAB, 0xA1, 0x52, 0x5F, 0xBB, 0x2A, + 0xCB, 0x11, 0xFE, 0x4A, 0x34, 0x30, 0xCD, 0xDD, 0x38, 0xAC, 0xED, 0xD0, 0x53, 0xC6, 0xC4, 0x33, + 0xE5, 0x17, 0x41, 0xC8, 0x7D, 0x14, 0x9C, 0x8C, 0x2D, 0x59, 0x61, 0xB1, 0xEC, 0x6A, 0x48, 0xAD, + 0xC7, 0x42, 0x79, 0x9B, 0x63, 0xDB, 0xB0, 0xAD, 0xAF, 0x44, 0x85, 0x2E, 0x24, 0x11, 0xE6, 0x1E, + 0x56, 0xB2, 0x1B, 0x2E, 0x98, 0x7E, 0x64, 0x3D, 0x98, 0x69, 0x51, 0x05, 0x3D, 0x5C, 0x0A, 0x85, + 0xCA, 0x58, 0x0E, 0x11, 0x86, 0xA7, 0xD6, 0xAC, 0xFF, 0x21, 0x12, 0x9F, 0xA1, 0x52, 0xE4, 0xD9, + 0xD5, 0xAC, 0x9B, 0xE2, 0x4B, 0xF0, 0x57, 0xFA, 0xFC, 0x05, 0x84, 0xB6, 0xD9, 0xB0, 0xCE, 0x5E, + 0xA0, 0x73, 0x32, 0xCE, 0x15, 0x29, 0xE8, 0x8F, 0xC4, 0xD9, 0x22, 0x3A, 0x9D, 0x71, 0xC5, 0x37, + 0x2B, 0x33, 0x76, 0x63, 0x49, 0x52, 0x8A, 0x2E, 0x89, 0x6D, 0x5A, 0x04, 0x83, 0xC1, 0xD9, 0x46, + 0xC3, 0x5C, 0x93, 0x6C, 0x3B, 0xDB, 0xFC, 0x1C, 0xC1, 0x65, 0x3B, 0x2C, 0xAD, 0xD5, 0x78, 0x9A, + 0x3A, 0xE4, 0xC3, 0x04, 0x11, 0xD8, 0xBD, 0x63, 0xA9, 0x93, 0x01, 0xF1, 0x42, 0x89, 0x5E, 0x66, + 0x95, 0xF2, 0x75, 0x38, 0x30, 0x4B, 0x39, 0x41, 0x49, 0x54, 0x8E, 0x12, 0x36, 0xFB, 0x9A, 0xA0, + 0x61, 0x13, 0x1C, 0xC2, 0xB2, 0x30, 0x7E, 0xD5, 0x22, 0xEB, 0xA3, 0xB6, 0x69, 0x2E, 0x0F, 0x0B, + 0xB0, 0x29, 0xF3, 0x24, 0x5A, 0x29, 0xA6, 0x3F, 0xC9, 0xC1, 0x56, 0xC5, 0x8E, 0xAA, 0x0B, 0x17, + 0x44, 0xBC, 0xFF, 0xE3, 0x12, 0xB8, 0x3E, 0xA9, 0x16, 0xF9, 0x7F, 0x57, 0x92, 0x7C, 0x0C, 0xFD, + 0x87, 0x9D, 0x70, 0xED, 0x81, 0xD0, 0x77, 0x01, 0x77, 0x71, 0xF5, 0x6C, 0x7F, 0xCC, 0xE4, 0xF0, + 0xAD, 0x27, 0x66, 0x2D, 0x16, 0x7B, 0x4E, 0x95, 0xF9, 0x59, 0xAA, 0x8A, 0x83, 0xE3, 0x1E, 0xD9, + 0x6E, 0x9C, 0x84, 0xEB, 0xBD, 0x5B, 0x33, 0x68, 0xA9, 0x3F, 0xDA, 0x69, 0xCB, 0xF1, 0xC7, 0x64, + 0x99, 0x27, 0xEF, 0xFF, 0xE2, 0x37, 0x34, 0x2D, 0x3C, 0x92, 0xAF, 0x7B, 0xE6, 0x6D, 0x7E, 0xF9, + 0xD2, 0x95, 0x14, 0xF1, 0x01, 0x93, 0xF2, 0xBD, 0xDF, 0x59, 0x67, 0xF6, 0x2D, 0x36, 0xBB, 0x8E, + 0x16, 0xCA, 0x07, 0xEC, 0x34, 0xA0, 0xE2, 0x16, 0x6C, 0xEC, 0x23, 0x41, 0x87, 0x7A, 0x66, 0x82, + 0x09, 0x6A, 0xFE, 0x21, 0xB3, 0x9A, 0xC7, 0x12, 0x0D, 0x07, 0xEB, 0xCF, 0xCB, 0x44, 0x9B, 0xA3, + 0xA1, 0xD9, 0x07, 0x76, 0x1D, 0x93, 0x96, 0x1B, 0x6D, 0xA1, 0x65, 0x6D, 0xB2, 0x62, 0x81, 0x55, + 0xFD, 0xE1, 0x76, 0xF2, 0x6F, 0x9A, 0x71, 0x9F, 0xFC, 0x48, 0x8D, 0x58, 0x1D, 0x3E, 0xFE, 0xBC, + 0x02, 0xB5, 0x36, 0xEE, 0x58, 0x35, 0x82, 0xF3, 0x51, 0x93, 0x35, 0x7E, 0xFA, 0xAF, 0x7E, 0x56, + 0x7E, 0xE8, 0x59, 0x7E, 0x90, 0x73, 0x9B, 0x0E, 0x4D, 0x0D, 0x84, 0x75, 0x30, 0x29, 0xCA, 0xE7, + 0x2C, 0xB3, 0xAC, 0x53, 0x3C, 0xC3, 0x7D, 0x55, 0xCD, 0xE0, 0xB9, 0xF8, 0x44, 0x02, 0xF2, 0x2B, + 0xB7, 0xD1, 0x83, 0x75, 0xDC, 0x96, 0xE7, 0x62, 0x5A, 0x58, 0x63, 0x2D, 0xC1, 0x33, 0xD2, 0x74, + 0x27, 0x05, 0xFD, 0xB7, 0x69, 0x63, 0x34, 0x81, 0xDB, 0xC1, 0x24, 0x10, 0x3A, 0xC2, 0xE2, 0xE9, + 0x75, 0xF8, 0x90, 0x50, 0x3A, 0x3C, 0xAF, 0xCD, 0x4E, 0x78, 0xB0, 0xFA, 0x5F, 0x2C, 0x09, 0x90, + 0xFE, 0xDA, 0xE4, 0x99, 0x8E, 0x3E, 0x63, 0x74, 0x3C, 0x6C, 0xD6, 0xC5, 0x2A, 0x1A, 0xBD, 0x95, + 0xA3, 0xAB, 0xF3, 0xEF, 0x1E, 0x95, 0x02, 0x8E, 0xCB, 0x23, 0xED, 0x11, 0xDB, 0x42, 0x45, 0xD5, + 0x5D, 0xDF, 0xF6, 0xE5, 0x98, 0xB4, 0x46, 0x49, 0x1A, 0xC6, 0x87, 0xFC, 0xA1, 0x79, 0x15, 0xD8, + 0x84, 0x99, 0xFE, 0x0B, 0x33, 0x30, 0x21, 0xA7, 0x59, 0x0A, 0x35, 0xE6, 0x68, 0x62, 0x55, 0x58, + 0xB4, 0x74, 0xDC, 0x80, 0xA4, 0xAD, 0x97, 0x57, 0x08, 0x9D, 0xC9, 0x9C, 0x1C, 0x0E, 0xD6, 0xE5, + 0xE3, 0xC8, 0x13, 0x02, 0xA8, 0xB9, 0x06, 0x27, 0x55, 0x89, 0xE7, 0xA3, 0xBF, 0xB0, 0xA6, 0xD0, + 0x92, 0xAD, 0xDA, 0xFE, 0x9E, 0x38, 0x2B, 0x88, 0x18, 0x7E, 0x3E, 0x90, 0xC3, 0xC0, 0x90, 0xAB, + 0x10, 0x21, 0x49, 0x47, 0xD1, 0x9D, 0x0A, 0xE9, 0x9F, 0xF9, 0xBB, 0x44, 0x83, 0x43, 0xBE, 0xBB, + 0x72, 0xD3, 0x06, 0xB4, 0x1B, 0x11, 0x03, 0xB8, 0xF5, 0xE5, 0x85, 0x1C, 0x4D, 0x7B, 0x0E, 0x65, + 0x33, 0xB0, 0x06, 0xD4, 0x58, 0x1F, 0xC9, 0x52, 0x27, 0xAD, 0xC9, 0xDE, 0x33, 0x4D, 0xC5, 0x45, + 0x6F, 0x11, 0xE1, 0x1D, 0xB9, 0x26, 0xED, 0x5B, 0x9B, 0xF6, 0xFC, 0x87, 0xF7, 0x96, 0x58, 0x5D, + 0xD7, 0x10, 0x15, 0x59, 0x1E, 0x3C, 0xFC, 0xF2, 0x8C, 0x7B, 0xB8, 0x70, 0x35, 0xD2, 0xB3, 0xA2, + 0xF1, 0xE7, 0x9D, 0xC6, 0xAE, 0x37, 0x71, 0xDA, 0x04, 0x32, 0x09, 0x33, 0xF7, 0x7D, 0x45, 0x33, + 0x9F, 0x5C, 0xD6, 0x66, 0xEC, 0x68, 0xCA, 0xEC, 0xBF, 0x4C, 0xF8, 0xA1, 0x4D, 0x5C, 0x41, 0x0E, + 0x69, 0x1F, 0x96, 0xE8, 0xD9, 0x76, 0xB9, 0xEA, 0x04, 0xEF, 0x91, 0xD4, 0xD7, 0x36, 0xEF, 0xE9, + 0xE6, 0x7B, 0x81, 0x31, 0xFD, 0x4E, 0x15, 0x17, 0xF1, 0x25, 0x86, 0x4C, 0x94, 0x2A, 0xAA, 0x50, + 0xB0, 0x98, 0x19, 0xB4, 0x34, 0x81, 0xE2, 0x61, 0xE6, 0xEE, 0x9C, 0x70, 0x72, 0x38, 0xB8, 0xE8, + 0xA6, 0xE1, 0x9F, 0x24, 0xDC, 0xD2, 0x9D, 0xC3, 0x94, 0x43, 0x65, 0x5B, 0x22, 0x45, 0xF2, 0x75, + 0x33, 0x6D, 0xB9, 0x60, 0xC6, 0x9F, 0x0A, 0xC1, 0x1F, 0xFC, 0x8B, 0x7B, 0xAB, 0x71, 0x61, 0xAE, + 0xD2, 0x1B, 0xF6, 0x79, 0x3D, 0x63, 0x1F, 0x4F, 0x1C, 0xDD, 0x4B, 0x50, 0x02, 0x97, 0xCD, 0xF4, + 0xC7, 0x0B, 0xFC, 0xB3, 0xD1, 0x21, 0xD3, 0x73, 0x1B, 0x4B, 0xE8, 0x9F, 0x3D, 0x16, 0x88, 0x0E, + 0x7F, 0xCC, 0x26, 0xA6, 0xF5, 0x8F, 0x69, 0x7F, 0xAC, 0xE8, 0x3F, 0x7B, 0x29, 0x81, 0xDA, 0x14, + 0x8F, 0x98, 0x78, 0x11, 0x34, 0x46, 0x60, 0xAD, 0x9E, 0xB3, 0x69, 0xFD, 0x74, 0x95, 0x3A, 0x14, + 0x30, 0xBE, 0x34, 0xE2, 0xAC, 0x54, 0x81, 0x70, 0x1F, 0x47, 0xD7, 0x5A, 0xDE, 0x81, 0x29, 0x17, + 0x9B, 0x3F, 0xC7, 0x57, 0x56, 0x26, 0xCD, 0xF8, 0x3B, 0x4E, 0x94, 0x4E, 0xA4, 0x09, 0x46, 0x1A, + 0x6D, 0xA3, 0x44, 0x51, 0xA9, 0x9F, 0x97, 0x88, 0x5E, 0x5E, 0x70, 0xCA, 0xDE, 0xA4, 0xAF, 0x6D, + 0xBC, 0xCE, 0xF6, 0x67, 0x81, 0x10, 0xD3, 0xF5, 0xB8, 0xC9, 0x49, 0xF3, 0x98, 0x71, 0x2D, 0x29, + 0xBD, 0x34, 0x08, 0x25, 0x1B, 0x9A, 0x6F, 0xC3, 0x4D, 0xC3, 0x9F, 0x5E, 0xE2, 0xA2, 0xF3, 0x82, + 0x52, 0xA9, 0xF6, 0x79, 0x14, 0x6F, 0xD0, 0x12, 0xB6, 0x0D, 0x4C, 0xAC, 0x3E, 0x2A, 0x8C, 0x86, + 0xE1, 0xFF, 0x1C, 0x49, 0x43, 0xF2, 0x1D, 0x34, 0xA3, 0x0F, 0xA3, 0x9B, 0x2C, 0xF3, 0x9C, 0xFC, + 0x23, 0x4B, 0xE0, 0xCB, 0xE6, 0xBE, 0x7E, 0x8B, 0x51, 0x2D, 0x32, 0x92, 0xCC, 0xC6, 0xDB, 0x36, + 0x1B, 0x83, 0x80, 0x64, 0xD2, 0x23, 0x1C, 0x60, 0x90, 0x1C, 0x71, 0x06, 0x0F, 0x30, 0xC0, 0xDF, + 0xFE, 0xF7, 0x50, 0x5A, 0xF8, 0x9C, 0x97, 0x29, 0xFB, 0x7B, 0xEB, 0x91, 0x6A, 0x42, 0xE4, 0xAB, + 0x62, 0xB4, 0x1F, 0x14, 0xEC, 0x1A, 0x9F, 0xC4, 0x98, 0x77, 0x85, 0x8B, 0xF1, 0x15, 0x2B, 0x2C, + 0x3F, 0xAB, 0x3A, 0x5E, 0xD3, 0x50, 0x32, 0xDD, 0x23, 0x81, 0x89, 0x3B, 0xB8, 0xFD, 0x49, 0x78, + 0xA9, 0x35, 0x20, 0x2D, 0xBC, 0xFC, 0x10, 0x8A, 0x08, 0xA0, 0x57, 0x24, 0xFA, 0x29, 0xD3, 0x9C, + 0xC0, 0xC5, 0x06, 0xF2, 0x7D, 0xC4, 0x45, 0x38, 0x1F, 0xC6, 0xE4, 0xC8, 0x23, 0x65, 0xD2, 0x5A, + 0xE5, 0xCD, 0x63, 0x14, 0x56, 0x03, 0xAA, 0x29, 0x82, 0xAB, 0x67, 0x92, 0x2C, 0x71, 0x67, 0x6A, + 0x36, 0xF9, 0xE1, 0x89, 0xD2, 0x98, 0x88, 0x97, 0x65, 0x72, 0xE4, 0x3C, 0x5A, 0x1D, 0xB7, 0x34, + 0x4D, 0x01, 0x91, 0x5D, 0xC4, 0xAF, 0x88, 0x2F, 0xD1, 0x27, 0x26, 0x36, 0x16, 0x98, 0x39, 0x9A, + 0xEA, 0xFA, 0xDF, 0x3B, 0x7F, 0xD8, 0x76, 0x95, 0xDB, 0x03, 0x82, 0x86, 0xD0, 0x6D, 0x15, 0xD0, + 0xF0, 0xB9, 0xCC, 0xB9, 0xD6, 0xE1, 0x9C, 0xC7, 0xAE, 0x3C, 0xA8, 0xB9, 0x49, 0xA3, 0xC4, 0xB3, + 0x24, 0x3A, 0xA3, 0x50, 0x7E, 0xDF, 0xBE, 0x5F, 0xAF, 0xC3, 0x7C, 0x3C, 0xC7, 0x39, 0xD9, 0x29, + 0x06, 0xD5, 0x0B, 0xE8, 0xFA, 0x80, 0xC2, 0xCE, 0xA8, 0xD9, 0x20, 0xCE, 0x28, 0x0A, 0xDE, 0x15, + 0xB5, 0x0D, 0x9A, 0xB8, 0xB0, 0x7B, 0x33, 0xB8, 0x35, 0xEB, 0x4E, 0xDE, 0xF5, 0x9F, 0x66, 0x79, + 0x52, 0x44, 0xD2, 0x49, 0x3A, 0x94, 0xE2, 0xCB, 0x83, 0x22, 0x7D, 0xC6, 0x1C, 0xEB, 0x44, 0x28, + 0xE9, 0x38, 0x8F, 0xB7, 0xF6, 0x3D, 0x39, 0x8F, 0x64, 0x9F, 0x86, 0x85, 0x27, 0x7E, 0xBF, 0xD8, + 0xC3, 0x8B, 0x70, 0xF9, 0x7A, 0xE6, 0x19, 0xAF, 0xA8, 0x8F, 0x6C, 0x38, 0xE2, 0xB4, 0x1E, 0xF3, + 0xDD, 0xFD, 0x6C, 0xB4, 0x04, 0xB4, 0xD1, 0x37, 0xF4, 0x59, 0xFF, 0x5A, 0x8B, 0x73, 0x6B, 0x62, + 0x6C, 0x91, 0x23, 0x96, 0x61, 0xEC, 0x9F, 0xF6, 0x83, 0x18, 0xD9, 0xEE, 0xEF, 0x00, 0xB0, 0xEB, + 0x8D, 0xC6, 0x35, 0xCE, 0xB4, 0xE0, 0xBB, 0x84, 0xD3, 0xD7, 0xDB, 0x77, 0x4A, 0x07, 0xE7, 0x3B, + 0xF7, 0x12, 0xF8, 0x9D, 0xF5, 0x0A, 0xDF, 0xF4, 0x1B, 0x00, 0x1A, 0x77, 0xD9, 0x03, 0x21, 0xC2, + 0x46, 0xF7, 0x7E, 0x4B, 0xE2, 0xC1, 0xF1, 0x8D, 0xFE, 0x61, 0xB9, 0xD4, 0x0B, 0xC9, 0x65, 0x82, + 0xCF, 0x7F, 0x3D, 0x24, 0x9E, 0xC9, 0x97, 0x30, 0xE9, 0x81, 0xB4, 0x6B, 0xA5, 0x6A, 0xA7, 0xED, + 0xE3, 0x8F, 0x88, 0x09, 0x78, 0x36, 0x6A, 0x14, 0xB9, 0xBB, 0x93, 0xE6, 0xA2, 0x07, 0x72, 0x81, + 0xD2, 0x41, 0x75, 0x28, 0x50, 0x57, 0xDE, 0x68, 0x04, 0xB1, 0x0F, 0x1F, 0xCF, 0x0F, 0x3E, 0xCB, + 0x44, 0x3D, 0x2A, 0x01, 0xC1, 0x38, 0x61, 0x8E, 0xA1, 0x13, 0xB8, 0x82, 0x3F, 0xAC, 0xA7, 0x31, + 0x7B, 0xF7, 0xA7, 0xCD, 0xBF, 0x15, 0xD3, 0xC2, 0xCC, 0xDC, 0xD0, 0xB2, 0xC1, 0x44, 0xBA, 0x82, + 0x85, 0x2D, 0x59, 0x53, 0xE4, 0xBE, 0x6B, 0x69, 0x8F, 0x5C, 0x20, 0x38, 0x76, 0xE0, 0x49, 0x59, + 0x32, 0x0A, 0x90, 0x42, 0x13, 0x35, 0x24, 0x71, 0xD9, 0x98, 0x9A, 0x0F, 0x0A, 0x83, 0x90, 0x47, + 0xD0, 0x57, 0x29, 0xC8, 0xF5, 0x5B, 0xA8, 0x5D, 0x41, 0xCD, 0x61, 0x98, 0x5C, 0x28, 0x1B, 0xF2, + 0x9B, 0x31, 0x3F, 0x14, 0x79, 0x37, 0xEA, 0xDD, 0x42, 0xD3, 0x59, 0x20, 0x0D, 0x5A, 0x3D, 0x59, + 0x84, 0x1A, 0x48, 0x28, 0x6D, 0x48, 0x3B, 0x0C, 0x87, 0xB0, 0xBB, 0xEF, 0x72, 0x91, 0x85, 0xE0, + 0x9A, 0x2C, 0x78, 0xAF, 0xFE, 0x0D, 0x6D, 0x9A, 0x99, 0x10, 0x16, 0x7D, 0x42, 0x76, 0xA0, 0xAA, + 0xC7, 0xF9, 0x11, 0x5C, 0x04, 0x83, 0x0A, 0xA9, 0x48, 0x65, 0x28, 0x81, 0x65, 0x2E, 0xA7, 0x5A, + 0xFE, 0xDB, 0x10, 0x56, 0xEA, 0x19, 0x67, 0xC2, 0x61, 0xBB, 0x9B, 0x6B, 0x48, 0x4F, 0x22, 0x4C, + 0xB7, 0xEC, 0x15, 0x84, 0x08, 0x1A, 0xFE, 0x08, 0x46, 0xCA, 0x72, 0x10, 0x30, 0x81, 0xAE, 0xE7, + 0x1B, 0xAA, 0x2F, 0x1B, 0x22, 0x6A, 0x89, 0x95, 0x34, 0x9F, 0x53, 0xE4, 0xDA, 0x0B, 0x1B, 0x56, + 0xB4, 0xB7, 0x71, 0x99, 0xB8, 0x77, 0x91, 0xEB, 0x59, 0x61, 0x71, 0x84, 0xAF, 0x55, 0xBD, 0x33, + 0x1C, 0xD9, 0x7A, 0x40, 0xD3, 0x7B, 0x6C, 0xB0, 0xA4, 0x96, 0x07, 0x57, 0x49, 0x9C, 0xC1, 0x0B, + 0x23, 0xF3, 0xE5, 0x36, 0xA4, 0xE8, 0xE1, 0xEC, 0x51, 0xF1, 0xBB, 0x7B, 0x40, 0x4B, 0x05, 0xD1, + 0x3D, 0xD6, 0x6D, 0x22, 0x7C, 0x9D, 0x51, 0x28, 0x70, 0xF6, 0xB3, 0x0C, 0x6F, 0xB2, 0xE1, 0xBC, + 0xFF, 0xB8, 0xA3, 0x92, 0x7C, 0x0A, 0xA3, 0x36, 0x6D, 0xF8, 0xFC, 0x27, 0x45, 0x58, 0x94, 0xE0, + 0x0A, 0x90, 0xF8, 0x2A, 0xF3, 0x02, 0xA4, 0x98, 0xD8, 0xEF, 0x1C, 0x53, 0xE2, 0x47, 0xC7, 0x20, + 0xB2, 0x6F, 0xB7, 0x39, 0x5A, 0xA9, 0x49, 0xC2, 0x65, 0x18, 0x50, 0xBD, 0x7F, 0xB0, 0xDE, 0xBB, + 0x2F, 0x56, 0xDD, 0x62, 0x85, 0xAC, 0x3B, 0x7E, 0xCD, 0x00, 0x88, 0x7A, 0x22, 0xC5, 0x03, 0x03, + 0x26, 0xC4, 0x27, 0x09, 0xE8, 0x16, 0x36, 0xB7, 0x0B, 0xDF, 0xDA, 0x21, 0xEB, 0x59, 0xE8, 0x6D, + 0x96, 0x2D, 0x18, 0x89, 0xD4, 0x4E, 0x5A, 0x66, 0x63, 0x31, 0x15, 0xF0, 0x48, 0x50, 0x7A, 0xF3, + 0xA7, 0x82, 0xE0, 0x91, 0x04, 0xB2, 0x16, 0xAC, 0xA1, 0xD4, 0xC1, 0xE4, 0xBD, 0xC9, 0x43, 0x83, + 0xD9, 0x18, 0x11, 0x50, 0xC0, 0x93, 0x9D, 0x6B, 0x17, 0x63, 0xC8, 0xA3, 0xAF, 0xAC, 0xBF, 0x29, + 0x65, 0xA6, 0x4A, 0xE4, 0xCD, 0xE2, 0x89, 0x59, 0xF8, 0x96, 0xF8, 0x3B, 0x80, 0xCA, 0x68, 0xF2, + 0xBF, 0xBE, 0xFD, 0xD0, 0x84, 0x88, 0xE3, 0x3D, 0x08, 0x8A, 0x08, 0x39, 0xDC, 0x88, 0x71, 0x61, + 0x76, 0xAD, 0xB6, 0xBE, 0xC0, 0xA4, 0x28, 0xEE, 0x38, 0x8F, 0x2E, 0x8D, 0x3A, 0x28, 0xAE, 0xEB, + 0x93, 0x7E, 0xF4, 0x2E, 0x5F, 0x3B, 0xEA, 0x37, 0x9A, 0x53, 0x6C, 0xC5, 0x3A, 0x2F, 0x7F, 0xA9, + 0x80, 0x85, 0x18, 0xFE, 0x14, 0x0C, 0x37, 0xA7, 0xE2, 0x78, 0x90, 0x20, 0x1F, 0x41, 0x1C, 0x03, + 0xD1, 0xD0, 0x8C, 0xB2, 0x2D, 0x4C, 0x32, 0x81, 0x63, 0xF6, 0x6F, 0xD3, 0x7F, 0xB4, 0x6D, 0xA8, + 0xD6, 0xE0, 0x56, 0x84, 0xAD, 0x37, 0x96, 0x44, 0xB4, 0xAE, 0x6F, 0x49, 0x3E, 0x30, 0x0C, 0x79, + 0xE3, 0x93, 0xBA, 0xD2, 0x10, 0x71, 0xAC, 0x7C, 0x06, 0x89, 0x26, 0xCB, 0x72, 0x9E, 0xC4, 0xCC, + 0xBB, 0xA1, 0xDE, 0x9B, 0x66, 0x75, 0x6F, 0xAC, 0x51, 0x93, 0xE8, 0xF4, 0x43, 0xE4, 0x79, 0xF2, + 0x50, 0x67, 0x3D, 0x1B, 0x33, 0xB5, 0xEC, 0x93, 0x10, 0xCE, 0x78, 0x3C, 0x0C, 0x25, 0x44, 0x30, + 0x3C, 0xAB, 0x40, 0x6F, 0x4B, 0x54, 0x0C, 0x38, 0xD4, 0xEF, 0x18, 0xED, 0xA5, 0x75, 0x1E, 0x9C, + 0x06, 0x42, 0x11, 0x9D, 0xEC, 0xE8, 0x81, 0x38, 0xBE, 0xEF, 0x21, 0xB5, 0x0A, 0xC3, 0x38, 0x94, + 0x0C, 0x5A, 0xE2, 0x47, 0xDC, 0x2E, 0x82, 0xC3, 0xA6, 0x9D, 0xA6, 0x54, 0x5E, 0xE6, 0x7C, 0xFE, + 0x7E, 0x7B, 0x86, 0x9A, 0x54, 0xEC, 0xCD, 0xE5, 0x9F, 0xF7, 0x23, 0xDE, 0x06, 0x90, 0x93, 0xB6, + 0xCC, 0xC8, 0x3D, 0x75, 0xA6, 0x43, 0xEA, 0x1F, 0x44, 0xAB, 0xBB, 0x15, 0xC4, 0xAB, 0xB1, 0xDF, + 0xE9, 0x30, 0xB4, 0xB2, 0x3B, 0x5B, 0xA6, 0x57, 0xEE, 0x25, 0x4B, 0x83, 0x26, 0xE4, 0x6B, 0xE3, + 0x35, 0x62, 0xA5, 0xD2, 0xB6, 0xDE, 0xFD, 0x5F, 0x8C, 0x21, 0xF8, 0x15, 0x41, 0x1C, 0x61, 0xCD, + 0x78, 0x0F, 0x61, 0x9E, 0x0C, 0x7E, 0x88, 0xE3, 0x34, 0x7F, 0x0B, 0x00, 0x5D, 0x8A, 0x37, 0x5F, + 0xFB, 0x65, 0xB8, 0xEC, 0x6C, 0x6A, 0x7E, 0x8F, 0x70, 0x6A, 0x17, 0xFB, 0x6B, 0x9D, 0xD8, 0xC3, + 0xD5, 0x13, 0x49, 0xEC, 0xD0, 0xEC, 0x1B, 0xC7, 0x79, 0xBB, 0xD2, 0x89, 0x96, 0x39, 0x1C, 0xF3, + 0x70, 0xEA, 0xF0, 0xEA, 0x6B, 0x45, 0x51, 0xDB, 0x1A, 0x61, 0x60, 0x98, 0x1C, 0x7A, 0xA6, 0x48, + 0x65, 0xDA, 0x85, 0xBE, 0x6B, 0xC4, 0x27, 0x2F, 0x76, 0x6D, 0x5F, 0x4C, 0xDE, 0x92, 0xA1, 0xB3, + 0xD4, 0x11, 0xB4, 0x0B, 0x3A, 0x4C, 0x73, 0xC3, 0xAA, 0x9C, 0x0F, 0x95, 0x9B, 0x2C, 0x67, 0x02, + 0x47, 0xCD, 0xE3, 0x75, 0x84, 0x60, 0x5E, 0x17, 0xAB, 0xF0, 0xBC, 0xBE, 0xB4, 0xAA, 0x4C, 0xEA, + 0x11, 0x52, 0x87, 0xAC, 0x16, 0x56, 0x06, 0x1F, 0xC1, 0x97, 0xF9, 0xAB, 0x26, 0xD9, 0xCC, 0x58, + 0x01, 0xFF, 0x44, 0x21, 0xFE, 0x5D, 0x53, 0x0B, 0xA2, 0xAF, 0xBC, 0x9D, 0x63, 0x25, 0x87, 0x66, + 0xB3, 0x79, 0xB4, 0x9F, 0x49, 0xE6, 0x6E, 0xCB, 0x8B, 0x39, 0x8C, 0x46, 0x60, 0x3D, 0x5B, 0xEC, + 0x08, 0x1B, 0xB2, 0xEC, 0xA2, 0xAA, 0x9A, 0xDA, 0xAA, 0xAD, 0x25, 0xAB, 0x45, 0x99, 0x63, 0x23, + 0x6C, 0x53, 0x87, 0xA0, 0x5A, 0x6C, 0xD6, 0xE3, 0x6C, 0x34, 0x42, 0x0F, 0xF3, 0xA0, 0x23, 0xD3, + 0x16, 0x05, 0xAB, 0x05, 0x14, 0xA8, 0xA9, 0x02, 0x23, 0xFC, 0xC0, 0xED, 0x75, 0xC5, 0x43, 0x7C, + 0x1E, 0xB1, 0x69, 0xB6, 0x01, 0x96, 0x7B, 0x9E, 0x12, 0x1C, 0x72, 0x42, 0x0D, 0xAB, 0x0B, 0x79, + 0x22, 0xF3, 0xF0, 0xEB, 0x71, 0x83, 0x42, 0x3C, 0x11, 0xC7, 0x4F, 0x87, 0xFF, 0x5D, 0x19, 0x0B, + 0xE4, 0x1E, 0xC3, 0xA2, 0xD7, 0x0E, 0x9C, 0x3C, 0x7D, 0xFB, 0x4F, 0xDA, 0x99, 0x9F, 0xF8, 0x3E, + 0xD4, 0xA0, 0xA2, 0xF8, 0x83, 0x33, 0x1E, 0xE2, 0x1C, 0x52, 0x9D, 0xB8, 0x63, 0x8F, 0xA1, 0x4D, + 0x68, 0xAE, 0xCE, 0xD4, 0x12, 0x8E, 0x96, 0xA1, 0x78, 0x27, 0x6A, 0x29, 0x11, 0x19, 0x4C, 0x94, + 0x89, 0x0F, 0xFB, 0x7C, 0xA8, 0xAC, 0x7E, 0x84, 0x44, 0xC8, 0x7A, 0x18, 0xAE, 0x34, 0x0F, 0x6E, + 0x90, 0xDB, 0x1E, 0x79, 0xFA, 0xE8, 0xAF, 0x71, 0xE4, 0x80, 0x24, 0x53, 0x2F, 0xB6, 0xC2, 0x12, + 0x1F, 0xA0, 0x44, 0x98, 0x40, 0xAE, 0x01, 0xD6, 0xFB, 0x86, 0xBB, 0xC6, 0xF7, 0x2F, 0x38, 0x41, + 0xE3, 0x2E, 0x1F, 0x2E, 0x7C, 0x90, 0x1F, 0xD9, 0xD3, 0x34, 0x86, 0xC8, 0xA3, 0x8E, 0xAB, 0x5E, + 0x05, 0x55, 0x71, 0x5B, 0x21, 0xB7, 0x2F, 0x9E, 0x56, 0x32, 0x56, 0xD1, 0xDC, 0xC9, 0xDA, 0xDE, + 0xAC, 0x1D, 0x91, 0x0E, 0x4D, 0x1F, 0x4F, 0xE7, 0x31, 0xFE, 0x9A, 0x3F, 0x18, 0xAD, 0x85, 0x4E, + 0x66, 0x95, 0x76, 0x67, 0x6A, 0xC8, 0x90, 0x6A, 0xB5, 0xB0, 0x7D, 0xB2, 0xB3, 0xD7, 0xD6, 0x42, + 0xF1, 0x63, 0x52, 0xBD, 0x72, 0xE1, 0xD2, 0x63, 0x9A, 0x00, 0x45, 0x8E, 0x77, 0xCF, 0x93, 0xCF, + 0x1A, 0x31, 0xAA, 0x16, 0xA9, 0xCF, 0x45, 0xE3, 0x99, 0x3E, 0x6C, 0x70, 0xE9, 0x93, 0x83, 0x78, + 0x11, 0xB8, 0x80, 0xEA, 0x75, 0x5A, 0x87, 0xCE, 0x1D, 0xCD, 0x12, 0x5F, 0x1F, 0x6F, 0x86, 0x25, + 0x5E, 0x95, 0x22, 0x07, 0xFE, 0x29, 0xDA, 0x93, 0x52, 0x41, 0x62, 0x79, 0x06, 0x06, 0x83, 0xAE, + 0x42, 0x35, 0x5C, 0x9E, 0x01, 0xF8, 0xFA, 0x3F, 0x07, 0x15, 0xCA, 0x02, 0x69, 0x8E, 0xEA, 0x9E, + 0xEF, 0xF4, 0x99, 0x26, 0x73, 0x42, 0xC2, 0xEC, 0x0F, 0xEC, 0x03, 0x8F, 0x66, 0xC1, 0x88, 0x76, + 0xC5, 0xC2, 0x55, 0x46, 0x59, 0xC4, 0x28, 0x55, 0x93, 0xBF, 0x7C, 0x1C, 0x5C, 0xA0, 0xBA, 0x8A, + 0xCA, 0x8A, 0x81, 0x6C, 0xA3, 0xDE, 0x2D, 0x63, 0xC5, 0xCA, 0x0B, 0x8E, 0x99, 0xC9, 0xE4, 0x7E, + 0x32, 0x36, 0xD8, 0xFA, 0x80, 0xA5, 0x93, 0x4F, 0x5C, 0x97, 0x33, 0x74, 0xD2, 0x03, 0xFF, 0xFD, + 0x57, 0x84, 0x26, 0x62, 0x6A, 0x4C, 0x15, 0x45, 0xB9, 0x7C, 0xCD, 0xD8, 0x7B, 0xC5, 0x70, 0xA1, + 0xCB, 0xB4, 0xDE, 0xA6, 0xBD, 0xE0, 0xE9, 0x49, 0xB7, 0xEA, 0x21, 0x17, 0xCD, 0x0C, 0xA7, 0xF2, + 0x9B, 0xB6, 0x26, 0x06, 0xB1, 0x63, 0x3D, 0x0D, 0x1A, 0xAE, 0x57, 0xF1, 0x53, 0xDA, 0x61, 0x47, + 0x7E, 0x66, 0xC0, 0x2A, 0xAC, 0x7E, 0xE8, 0xE9, 0x4F, 0xC2, 0x21, 0x01, 0x0B, 0x9C, 0xA7, 0x98, + 0x05, 0x3A, 0x34, 0x68, 0x19, 0x26, 0x35, 0x57, 0xF7, 0xF8, 0xE0, 0x77, 0x7B, 0x42, 0x8D, 0xE0, + 0xD3, 0x08, 0x49, 0x62, 0x1D, 0x86, 0xBE, 0xC8, 0xDC, 0x2F, 0xE0, 0xFF, 0xC4, 0x6E, 0xBD, 0xEC, + 0x8B, 0x33, 0xF1, 0x61, 0x6E, 0x96, 0xEB, 0xEA, 0x87, 0x4C, 0xA3, 0x5F, 0x97, 0x92, 0x22, 0x0C, + 0x36, 0x05, 0xA5, 0xC1, 0xF6, 0xB8, 0xF5, 0xE0, 0xE4, 0x91, 0x73, 0x73, 0xD0, 0x38, 0x81, 0x24, + 0x13, 0xB4, 0x4B, 0xAB, 0x4C, 0xBB, 0x15, 0x56, 0xC2, 0x66, 0xF9, 0x27, 0xEF, 0xE0, 0x3D, 0x4E, + 0xDB, 0x12, 0xCB, 0xC1, 0xDE, 0x57, 0x1B, 0x7F, 0x69, 0x10, 0xE4, 0x6E, 0xF7, 0xCC, 0x0F, 0x64, + 0x31, 0x6A, 0xBF, 0x49, 0x6C, 0xF8, 0x9E, 0xC9, 0x37, 0xF9, 0x26, 0x32, 0x4A, 0x26, 0x97, 0x99, + 0x30, 0xA6, 0x86, 0x82, 0xCE, 0x04, 0x8B, 0xFB, 0x39, 0x3B, 0xEF, 0x05, 0xAE, 0x3F, 0xFD, 0xEA, + 0x21, 0xA7, 0xF9, 0xD8, 0x2D, 0x34, 0x42, 0xE4, 0xDC, 0x40, 0x1C, 0x82, 0x94, 0x89, 0xC4, 0x74, + 0x0C, 0xF6, 0x76, 0x65, 0x13, 0x29, 0x85, 0xE7, 0xF6, 0x8C, 0x19, 0x3D, 0xC2, 0x69, 0x79, 0x07, + 0x8C, 0x47, 0x24, 0x24, 0x4C, 0xD6, 0x41, 0x93, 0x70, 0x40, 0x08, 0x6D, 0x49, 0x4D, 0x92, 0xAA, + 0x9D, 0x02, 0xAB, 0x53, 0x8D, 0x8C, 0x96, 0x75, 0x54, 0xD0, 0xA2, 0x45, 0xBA, 0x19, 0x81, 0xFE, + 0x20, 0xCC, 0x93, 0x78, 0x4D, 0xBB, 0xA2, 0x21, 0xAB, 0xB5, 0x1B, 0x47, 0x0C, 0xA2, 0x34, 0x24, + 0x1E, 0xF3, 0x40, 0xF3, 0xC8, 0x79, 0xAE, 0x00, 0x40, 0xC3, 0xC4, 0x1B, 0x41, 0x5E, 0xF4, 0x08, + 0x7D, 0x15, 0xF1, 0xDF, 0xE2, 0x34, 0x78, 0xC3, 0x2A, 0x91, 0x8D, 0xC1, 0xA7, 0xF4, 0xD0, 0xF9, + 0xE7, 0x4E, 0x9F, 0xDC, 0x56, 0x16, 0x6C, 0x5B, 0x48, 0x10, 0x7E, 0xE9, 0xA6, 0x3F, 0x3B, 0xD0, + 0xFD, 0x12, 0x46, 0x1B, 0xFF, 0x61, 0x76, 0x61, 0xCC, 0x24, 0x1C, 0x94, 0xDF, 0x77, 0x6F, 0xAE, + 0x8E, 0xEE, 0x96, 0xF6, 0x9B, 0xB4, 0xE9, 0xA5, 0x1E, 0x6D, 0x7C, 0x2C, 0x43, 0xFB, 0x74, 0xF3, + 0x23, 0x86, 0xE6, 0x07, 0xC7, 0x36, 0xE3, 0xED, 0xAF, 0xDD, 0x75, 0x61, 0xCD, 0xFE, 0xEE, 0x4B, + 0x82, 0xF7, 0xC7, 0x66, 0x2B, 0x4C, 0xFF, 0x97, 0x77, 0x1A, 0xF5, 0xA5, 0x55, 0xF2, 0xBD, 0xC0, + 0xD4, 0xD8, 0x07, 0x2A, 0x92, 0xB0, 0x04, 0x56, 0x55, 0x3F, 0x1B, 0xCA, 0x21, 0x25, 0x3F, 0xB9, + 0x81, 0x01, 0x94, 0x43, 0xEE, 0x83, 0xD5, 0x10, 0xB4, 0x13, 0x19, 0xC0, 0x06, 0xBA, 0xEF, 0xA3, + 0xE4, 0xEC, 0xD8, 0x66, 0x30, 0x58, 0xA2, 0xE7, 0x81, 0x65, 0xDA, 0xA1, 0x35, 0x28, 0x4B, 0x47, + 0x17, 0x95, 0xA3, 0x8C, 0x67, 0x44, 0xC8, 0x8B, 0x1B, 0x5F, 0xE9, 0x0E, 0x99, 0xA3, 0x39, 0xAF, + 0x5C, 0xCA, 0x0D, 0x68, 0xAD, 0xE0, 0x91, 0xF3, 0x95, 0x54, 0x02, 0xFF, 0xC4, 0x23, 0xA1, 0x2E, + 0xCE, 0xD2, 0xFC, 0x34, 0x5A, 0xF0, 0x83, 0x82, 0x54, 0x07, 0xDE, 0x21, 0x0D, 0xE3, 0xDF, 0x86, + 0xC1, 0x6E, 0xAB, 0x95, 0x28, 0x90, 0xED, 0x3B, 0x93, 0x95, 0x9E, 0xDE, 0xBA, 0x3E, 0x32, 0x85, + 0x0F, 0xB9, 0x62, 0x68, 0x00, 0x93, 0x38, 0xF9, 0x6B, 0x36, 0x5E, 0xAB, 0x9F, 0xDD, 0x84, 0x97, + 0xB8, 0xAB, 0xDA, 0x29, 0x88, 0xEC, 0x9D, 0xFA, 0xAA, 0x34, 0x2B, 0x1E, 0xC0, 0x6C, 0x53, 0xD4, + 0x55, 0x40, 0x45, 0x9F, 0xEC, 0x0A, 0x90, 0x51, 0xC5, 0x7A, 0x8A, 0xF6, 0xEE, 0x62, 0xC2, 0xE9, + 0x57, 0x2A, 0x1D, 0x8F, 0xB6, 0x83, 0xF9, 0x44, 0x42, 0x33, 0x2A, 0x3D, 0xC7, 0x46, 0xDA, 0xCA, + 0xBC, 0x6B, 0x49, 0x7F, 0x94, 0xB0, 0x6C, 0x80, 0xEC, 0x76, 0x96, 0x12, 0xB9, 0x05, 0x47, 0x96, + 0x84, 0x98, 0xCF, 0x8F, 0x21, 0xF5, 0x6B, 0x86, 0xB8, 0xD4, 0x80, 0xBA, 0xEA, 0x3C, 0xF6, 0x7A, + 0x98, 0xE9, 0x1A, 0x5E, 0x2C, 0x69, 0x1A, 0x2A, 0x31, 0xB9, 0xE6, 0x28, 0xA3, 0xCA, 0xC2, 0x70, + 0xDB, 0xF0, 0x30, 0x28, 0xD3, 0xD6, 0x17, 0xAD, 0x73, 0x8D, 0xB3, 0xF5, 0xFD, 0xA3, 0x6D, 0x8A, + 0xAE, 0x7A, 0x69, 0xD7, 0x6E, 0x4C, 0x29, 0x44, 0xBD, 0x57, 0xBA, 0xC9, 0xFD, 0xA1, 0xEF, 0xA9, + 0xA0, 0x98, 0x39, 0x7A, 0x05, 0x61, 0x46, 0x55, 0x77, 0x5C, 0x1A, 0x39, 0x38, 0x98, 0xFA, 0x79, + 0x9A, 0xBC, 0x68, 0xDB, 0x29, 0x51, 0xDD, 0x2C, 0xEC, 0xFA, 0x61, 0x93, 0x93, 0x44, 0xF2, 0x7E, + 0xD7, 0xA2, 0x79, 0xF1, 0xBD, 0x19, 0x81, 0x36, 0x04, 0x3A, 0x26, 0x20, 0x07, 0x3E, 0x01, 0x8E, + 0x16, 0xE0, 0x6F, 0xF6, 0x29, 0xB8, 0x0B, 0xAC, 0x37, 0x19, 0x39, 0x91, 0x09, 0x23, 0xA6, 0x9C, + 0xAD, 0x08, 0x70, 0xA8, 0x66, 0x0A, 0x22, 0xA2, 0x2E, 0xC5, 0xB9, 0x7F, 0x58, 0xC0, 0x2F, 0x07, + 0x61, 0xB9, 0x2D, 0x3F, 0xA9, 0xB1, 0x67, 0x52, 0xC1, 0x1C, 0x2C, 0xB4, 0xFC, 0x02, 0xA8, 0x4F, + 0x71, 0x87, 0x7F, 0x42, 0x35, 0x93, 0x25, 0xF5, 0x81, 0x07, 0xF9, 0x75, 0x01, 0xBE, 0x08, 0x15, + 0xC5, 0xD1, 0xED, 0x91, 0xB6, 0x0B, 0xC8, 0x8B, 0x4D, 0x62, 0x54, 0xD7, 0x14, 0x9C, 0x3E, 0xEA, + 0x15, 0x3E, 0x91, 0x4F, 0x2F, 0xB5, 0x5C, 0x5A, 0x13, 0x6D, 0x24, 0xE5, 0xB1, 0xA2, 0xFC, 0xAF, + 0x5F, 0x85, 0x13, 0x52, 0x9F, 0x80, 0x19, 0xBB, 0xB7, 0x9A, 0xC6, 0x92, 0x49, 0x2D, 0x28, 0xA7, + 0xA2, 0x28, 0xFA, 0x4A, 0x7B, 0xBA, 0x99, 0x15, 0xA9, 0xF3, 0x51, 0xED, 0xA5, 0xD7, 0x9A, 0xC1, + 0x7A, 0x1E, 0x77, 0x57, 0xBB, 0xA7, 0x25, 0x10, 0xE9, 0x69, 0xAC, 0x50, 0xEF, 0xEC, 0x85, 0x02, + 0x52, 0xC9, 0x29, 0xE9, 0xCB, 0xC8, 0xD0, 0x2D, 0x43, 0xAD, 0x26, 0x93, 0xA8, 0x12, 0xE3, 0xEB, + 0xB1, 0x1E, 0xA2, 0x8D, 0xE4, 0xF7, 0x8F, 0x4B, 0x55, 0xE7, 0xD7, 0x98, 0x3B, 0x7B, 0x85, 0x16, + 0xEE, 0x5A, 0xD8, 0x61, 0x65, 0x57, 0xBC, 0x74, 0x62, 0xD3, 0xDC, 0x7C, 0x6D, 0xCC, 0x56, 0xB0, + 0x3B, 0xA7, 0xE9, 0x10, 0xDE, 0x6A, 0xF4, 0x3A, 0xEC, 0x7E, 0x2E, 0xD0, 0x1E, 0x81, 0x48, 0xD3, + 0xEC, 0xD7, 0xC5, 0xDB, 0x16, 0xBD, 0xD5, 0x5B, 0xAD, 0x8E, 0x13, 0x5A, 0x2A, 0x9E, 0x1A, 0x96, + 0xC3, 0x7E, 0x23, 0xAD, 0xA7, 0x45, 0xE0, 0xCE, 0xA4, 0x52, 0x0C, 0x2A, 0x2E, 0x84, 0x9D, 0xB3, + 0xB4, 0x21, 0x18, 0xA7, 0xCF, 0x57, 0xA3, 0xFE, 0xA1, 0x27, 0x99, 0xCE, 0x48, 0x1E, 0xA7, 0xDB, + 0x62, 0x13, 0x9B, 0x19, 0xE3, 0xBF, 0xAA, 0xA2, 0x9D, 0x29, 0xC9, 0x92, 0xD1, 0x5A, 0x43, 0x4E, + 0xC4, 0xF8, 0xB4, 0xD9, 0xFC, 0xBD, 0x1A, 0xBB, 0x4D, 0x23, 0x99, 0xF3, 0x86, 0xE6, 0xBC, 0xB7, + 0x03, 0xE1, 0xA9, 0xD7, 0xDF, 0x5C, 0x15, 0x56, 0xB4, 0x63, 0xC2, 0x71, 0x6D, 0x15, 0xF1, 0x85, + 0xB6, 0xFF, 0x85, 0x4B, 0x6C, 0x36, 0xDB, 0xA8, 0x07, 0x22, 0x92, 0x4F, 0xD5, 0xA3, 0x2B, 0x40, + 0x8F, 0x6D, 0x89, 0xE3, 0x3E, 0xA2, 0x40, 0xAE, 0x80, 0xA5, 0x3A, 0xD2, 0x5D, 0x7E, 0x74, 0x6A, + 0x94, 0xED, 0xA3, 0xF2, 0x4C, 0x2E, 0x57, 0xF2, 0xBE, 0x8B, 0x25, 0xEF, 0x87, 0x0C, 0x05, 0x99, + 0x27, 0x5E, 0xA5, 0xDE, 0xAE, 0x94, 0x49, 0xFD, 0x7A, 0x62, 0xA7, 0x74, 0x58, 0x8A, 0x1A, 0xED, + 0x15, 0x23, 0x1D, 0x83, 0xD7, 0xA4, 0x6B, 0x4F, 0x3F, 0x9C, 0xBB, 0x5B, 0x27, 0xAD, 0x5C, 0x7E, + 0xA2, 0xE0, 0xBF, 0x39, 0x0C, 0x73, 0xB1, 0x48, 0x07, 0xC1, 0x3B, 0xD5, 0xA6, 0x2D, 0x94, 0x20, + 0x6D, 0xE6, 0x91, 0xAD, 0xCC, 0xDE, 0x4A, 0x3C, 0x4C, 0x02, 0x92, 0xCD, 0x41, 0x46, 0x3C, 0x88, + 0x5A, 0xB6, 0xF6, 0x8F, 0x85, 0x05, 0x7E, 0x75, 0xAC, 0x92, 0x92, 0x99, 0xF4, 0x36, 0x21, 0xE9, + 0x0D, 0x19, 0x02, 0xA1, 0xF0, 0xF1, 0xDB, 0xD8, 0x8F, 0x48, 0x11, 0x5D, 0x84, 0x80, 0x24, 0xD8, + 0xEE, 0x57, 0xB2, 0x3A, 0xE6, 0x0E, 0xC5, 0xA1, 0x26, 0xF9, 0x0C, 0x2E, 0x6C, 0x3A, 0x7A, 0x8B, + 0x0B, 0x9B, 0x3D, 0x2E, 0xAF, 0x26, 0x7D, 0x02, 0x63, 0xC8, 0x0D, 0x24, 0x7D, 0x36, 0x19, 0xAB, + 0xAC, 0xA9, 0x10, 0xA0, 0x53, 0x25, 0x4C, 0xC7, 0x4C, 0x28, 0x4A, 0xC3, 0x38, 0x92, 0xE2, 0x3D, + 0xF3, 0xE1, 0x93, 0xDE, 0x3E, 0x77, 0xAC, 0xEF, 0x6A, 0x08, 0x44, 0xE8, 0x20, 0x18, 0xA3, 0xA0, + 0x90, 0x56, 0xDD, 0xAB, 0x77, 0x7D, 0x36, 0xC2, 0x91, 0xB5, 0x44, 0x8C, 0xD4, 0x57, 0x2C, 0x81, + 0xA1, 0xB9, 0xD9, 0x59, 0x50, 0x8A, 0x76, 0x88, 0x5A, 0x5E, 0x45, 0x99, 0xAC, 0x8C, 0x40, 0x14, + 0x46, 0x28, 0x77, 0xED, 0x1C, 0x7C, 0x59, 0x36, 0x83, 0x4A, 0xA7, 0x0A, 0x71, 0x9C, 0x3B, 0x02, + 0x43, 0x50, 0x74, 0x85, 0xE3, 0xD4, 0x0A, 0x3B, 0x1B, 0xE2, 0xD7, 0x1F, 0x79, 0x78, 0x4B, 0x00, + 0x8E, 0x0A, 0x99, 0xDF, 0x14, 0x17, 0xCD, 0xB8, 0xCF, 0x21, 0xB3, 0x85, 0x38, 0xDE, 0x01, 0xBA, + 0x1B, 0x95, 0x4B, 0x97, 0x2B, 0xB0, 0xC9, 0xED, 0x45, 0xCE, 0x22, 0x5B, 0x8E, 0x04, 0x91, 0x07, + 0x58, 0xC8, 0xB5, 0xA7, 0x06, 0x62, 0x9D, 0xC4, 0xAC, 0x1E, 0x08, 0xFD, 0xEA, 0xB7, 0x4D, 0x0B, + 0xD2, 0x79, 0xA8, 0xEF, 0x4F, 0xBE, 0x80, 0xE6, 0x55, 0x44, 0x7B, 0x59, 0x5E, 0x9C, 0x92, 0x6A, + 0x92, 0xF7, 0xE7, 0x78, 0xFB, 0x46, 0xA1, 0xF4, 0x1E, 0x36, 0x8B, 0xE2, 0x86, 0xA2, 0xE1, 0x19, + 0xB5, 0x29, 0xEA, 0xD2, 0x1D, 0x0B, 0x68, 0xC5, 0x2F, 0x4F, 0x48, 0x6A, 0xC8, 0x92, 0xCE, 0x64, + 0x9D, 0xA5, 0x86, 0xA5, 0x05, 0x40, 0xCE, 0xD7, 0x6C, 0x69, 0x9F, 0x6C, 0xB2, 0xA3, 0x11, 0x08, + 0xEF, 0x9B, 0xCD, 0x10, 0x07, 0x7B, 0x9E, 0x25, 0xF4, 0x1A, 0x2B, 0x21, 0x7E, 0xA5, 0xB9, 0xE1, + 0x33, 0x52, 0xFA, 0x04, 0x09, 0xD4, 0x78, 0xCB, 0x56, 0xE6, 0x55, 0x2C, 0xB4, 0x5D, 0xBB, 0x40, + 0x34, 0xE5, 0x23, 0x30, 0xB8, 0x65, 0x19, 0xF1, 0x5A, 0x08, 0xF2, 0xF4, 0x86, 0x45, 0xB7, 0x87, + 0x17, 0xFA, 0x68, 0x6A, 0x1F, 0x7E, 0x69, 0xDA, 0x89, 0x8E, 0xCA, 0xB6, 0xFF, 0x9F, 0x4E, 0x6F, + 0x25, 0x46, 0x46, 0xF6, 0x7B, 0x1E, 0xB3, 0x3D, 0x2C, 0x8C, 0x01, 0xA9, 0x7B, 0xDA, 0xE9, 0x4D, + 0x6E, 0x89, 0x9D, 0x0F, 0x3F, 0x9F, 0x15, 0x3C, 0xFE, 0x35, 0x61, 0x2A, 0x45, 0xC2, 0xA9, 0xC5, + 0x5C, 0x51, 0xCC, 0x6B, 0xCC, 0x2F, 0xA7, 0x60, 0x03, 0x71, 0xF0, 0xB3, 0xBF, 0x7B, 0x76, 0xCC, + 0x89, 0x2C, 0x31, 0x79, 0xE6, 0xDC, 0x7C, 0x39, 0x24, 0xD8, 0x1A, 0x98, 0x1F, 0x98, 0xCD, 0xD4, + 0x7E, 0x04, 0xF2, 0x92, 0x8D, 0x23, 0x03, 0x5F, 0xF3, 0x05, 0x3B, 0xB0, 0x0A, 0xF0, 0x7A, 0xCA, + 0x76, 0xCB, 0xD0, 0xBA, 0xA0, 0x7F, 0xBA, 0x0D, 0x68, 0x60, 0xC3, 0xEF, 0xDC, 0x44, 0x2E, 0x40, + 0x24, 0x9D, 0xCB, 0x1D, 0x5A, 0x0C, 0x51, 0x66, 0x1A, 0x2A, 0x68, 0xA6, 0x83, 0x20, 0x79, 0x9B, + 0x24, 0xEA, 0x10, 0x5A, 0xB4, 0x39, 0x58, 0xAC, 0xA0, 0x45, 0x3B, 0x16, 0xBE, 0x24, 0x59, 0x1D, + 0x18, 0x5C, 0xD8, 0xCD, 0xFE, 0x16, 0x5C, 0x84, 0x5C, 0x2D, 0x7D, 0x28, 0xC9, 0xCE, 0x9D, 0x38, + 0xF6, 0x2F, 0x9A, 0x8A, 0x93, 0x95, 0xDC, 0x73, 0x48, 0xFD, 0xF5, 0xAB, 0xF4, 0x06, 0xB4, 0x11, + 0x79, 0x7B, 0xF7, 0x75, 0x73, 0xC2, 0x2D, 0x9C, 0x91, 0x7E, 0x51, 0x12, 0x54, 0xAC, 0x55, 0x29, + 0x48, 0x7F, 0x50, 0x15, 0xC2, 0x79, 0x05, 0x3C, 0x8F, 0xB4, 0xAE, 0xDD, 0x28, 0x09, 0xCC, 0x1D, + 0xDE, 0xEF, 0x82, 0xFD, 0x64, 0xD1, 0x5A, 0xFE, 0x06, 0x18, 0x7C, 0xF6, 0x32, 0x67, 0xDB, 0xF1, + 0x55, 0xF4, 0x09, 0x33, 0xB4, 0x91, 0xA7, 0x68, 0xE7, 0xA3, 0x60, 0x93, 0xE6, 0x36, 0xDE, 0x28, + 0xB3, 0xBD, 0x90, 0x46, 0x7F, 0xE5, 0xAF, 0x3C, 0xB6, 0x5E, 0xF2, 0x98, 0xE6, 0x28, 0x07, 0xA9, + 0x21, 0x4C, 0xAA, 0xF7, 0x95, 0xD9, 0x25, 0x51, 0x7F, 0x26, 0x8F, 0xC9, 0xD6, 0x65, 0xCA, 0x07, + 0x82, 0x1F, 0x8E, 0xBF, 0xBA, 0x65, 0xF7, 0xB1, 0x51, 0x85, 0x99, 0x23, 0x4E, 0x48, 0x2E, 0x7B, + 0xC8, 0x09, 0xC4, 0x93, 0xB9, 0xDB, 0x6B, 0xAF, 0xCA, 0xC7, 0x0A, 0xC5, 0x6D, 0xD0, 0xC2, 0xD4, + 0xF5, 0xA1, 0x5E, 0x45, 0x28, 0x54, 0x1D, 0x87, 0xC8, 0x83, 0xAF, 0x9E, 0xB7, 0xC7, 0x4D, 0x48, + 0x3B, 0x49, 0x23, 0x8B, 0x23, 0x6A, 0x2D, 0xD6, 0x30, 0xAD, 0xD3, 0xFA, 0x35, 0x67, 0xF0, 0x0D, + 0x3A, 0xDC, 0x42, 0x57, 0xBE, 0xE6, 0x5B, 0x26, 0x0B, 0x30, 0x45, 0x7E, 0x71, 0x5D, 0x82, 0xE7, + 0x40, 0x45, 0x58, 0xBB, 0xF5, 0x07, 0xEC, 0x36, 0x47, 0xF7, 0x98, 0x05, 0x70, 0x83, 0x17, 0x9D, + 0xDD, 0x4A, 0x4A, 0xB7, 0xB5, 0xBC, 0x8B, 0xF5, 0x08, 0x47, 0x74, 0xF3, 0x0F, 0x3C, 0xB0, 0xC7, + 0x30, 0x88, 0xCF, 0xA2, 0xE1, 0xC3, 0x13, 0x52, 0x20, 0x2D, 0xAD, 0xB3, 0x99, 0x37, 0x71, 0x91, + 0xBB, 0x3F, 0x31, 0xAC, 0xEC, 0xB8, 0x2A, 0x84, 0x12, 0x34, 0x84, 0xE5, 0xBF, 0x47, 0x94, 0xD6, + 0x9A, 0x1F, 0xEB, 0x34, 0xAF, 0xDA, 0x93, 0x5D, 0x22, 0x88, 0x27, 0x1A, 0x04, 0x13, 0xAA, 0x06, + 0x52, 0x8C, 0x44, 0xCB, 0xCC, 0x70, 0x7A, 0xBF, 0xC3, 0x3D, 0x21, 0x71, 0x99, 0x4F, 0x06, 0x42, + 0x8B, 0x7F, 0xDA, 0xBC, 0xAF, 0x7C, 0x24, 0x94, 0x3F, 0xC9, 0xB9, 0xF0, 0xFB, 0x9C, 0xFB, 0x94, + 0xFC, 0x7F, 0xA0, 0x2F, 0x20, 0x4D, 0x57, 0x06, 0xDB, 0xA8, 0xC4, 0xBD, 0x02, 0x6C, 0xDD, 0x00, + 0x8C, 0x84, 0xEF, 0x63, 0xDB, 0x45, 0x34, 0x21, 0x69, 0x73, 0x53, 0x22, 0xD3, 0x61, 0xA6, 0x7A, + 0xDE, 0xC1, 0x66, 0xFD, 0x3B, 0x95, 0x13, 0x76, 0x06, 0xE4, 0x76, 0x0E, 0x63, 0xF2, 0x66, 0x56, + 0xE3, 0x80, 0x55, 0x57, 0xF3, 0x88, 0xA2, 0x5E, 0x84, 0x29, 0xFD, 0x44, 0x78, 0xA9, 0xF0, 0x3A, + 0xF9, 0xE7, 0xDD, 0x66, 0xE1, 0x48, 0xA4, 0xD7, 0x15, 0x06, 0x47, 0xBA, 0x1E, 0x70, 0xA0, 0xA2, + 0xBC, 0x79, 0xCF, 0x9B, 0xA6, 0x61, 0x95, 0x4F, 0xF8, 0x46, 0xA7, 0xD5, 0xB5, 0x5F, 0x66, 0xDD, + 0xE2, 0x5A, 0x8B, 0x29, 0xBA, 0x02, 0xC8, 0x1A, 0x17, 0xFD, 0x72, 0x45, 0xED, 0x63, 0x46, 0x64, + 0x5E, 0xC9, 0x24, 0x8D, 0x91, 0x38, 0xD1, 0xEF, 0xDF, 0x82, 0x09, 0xB0, 0x5B, 0x8C, 0x43, 0x5F, + 0xF9, 0x05, 0x34, 0x1E, 0x2B, 0x4D, 0xA8, 0xC3, 0xE5, 0x9A, 0xD5, 0x95, 0x1B, 0xC0, 0xB9, 0x30, + 0x55, 0xB1, 0xEF, 0x53, 0xD2, 0x86, 0x65, 0x96, 0xD1, 0x3C, 0x75, 0x9E, 0x43, 0xED, 0x72, 0x92, + 0x5F, 0xC3, 0xF8, 0x4C, 0x14, 0xA7, 0x77, 0x22, 0x48, 0x5E, 0xD0, 0x02, 0x32, 0xB4, 0x5F, 0xA9, + 0xCD, 0x9C, 0xF6, 0x46, 0x60, 0xEF, 0x4D, 0x14, 0x84, 0x6E, 0xC5, 0x2C, 0xA6, 0xB2, 0xFB, 0x38, + 0xD1, 0x0C, 0xB2, 0x87, 0xE8, 0x64, 0x1E, 0xFD, 0xC4, 0x28, 0x08, 0x37, 0x61, 0x85, 0x11, 0x44, + 0x76, 0xC9, 0x9F, 0xAD, 0x6D, 0xBC, 0xC0, 0xC8, 0x02, 0xC8, 0xDA, 0x22, 0x1D, 0xB3, 0x54, 0x5F, + 0x38, 0x0D, 0x1D, 0xC0, 0x78, 0x46, 0x7C, 0x46, 0x05, 0xC1, 0x51, 0x9A, 0xF7, 0x7E, 0x51, 0x75, + 0x32, 0xD6, 0xB1, 0x7E, 0x8B, 0x3A, 0xB9, 0xA8, 0x12, 0xAF, 0xF8, 0x20, 0x69, 0xED, 0x37, 0xB4, + 0xD2, 0x7D, 0x7C, 0x08, 0xE4, 0x74, 0xD8, 0x18, 0x2B, 0x6E, 0x65, 0x21, 0xC4, 0x8C, 0xF0, 0xB2, + 0x1A, 0x7D, 0xDD, 0xA5, 0xE9, 0xC3, 0x88, 0xEE, 0x7E, 0x80, 0xE3, 0x4B, 0x3A, 0x56, 0x3D, 0x4B, + 0x75, 0x62, 0xE1, 0xCE, 0xB6, 0xD5, 0xF1, 0xFC, 0x0C, 0x0A, 0x66, 0x10, 0xD2, 0xC0, 0xF3, 0xD3, + 0xCA, 0xFE, 0xD6, 0x73, 0xE4, 0x21, 0xDA, 0xED, 0xE4, 0xE4, 0x5A, 0xAC, 0x31, 0x6D, 0x84, 0x8E, + 0x24, 0x56, 0x6B, 0x09, 0x14, 0x09, 0x81, 0xD6, 0xC6, 0x92, 0x2B, 0xE5, 0x2F, 0x61, 0xCE, 0xD3, + 0xBD, 0x31, 0x10, 0x56, 0x4C, 0x68, 0x18, 0xA2, 0x4E, 0xBF, 0x22, 0x71, 0x77, 0x4A, 0xEC, 0x3F, + 0x8A, 0x10, 0xF9, 0x62, 0x7B, 0x4F, 0x7E, 0xE3, 0x16, 0x23, 0x3C, 0x4A, 0x7B, 0xD9, 0xCC, 0xA1, + 0x13, 0x09, 0x31, 0xD8, 0xD1, 0x23, 0xC4, 0xAD, 0x48, 0xD6, 0xC7, 0xCF, 0xB6, 0xE2, 0x5E, 0x53, + 0xAF, 0x45, 0xF4, 0xE4, 0x82, 0xDC, 0xB3, 0x5D, 0x19, 0x4A, 0x71, 0xBE, 0x75, 0x1D, 0x82, 0x9C, + 0xCD, 0x1F, 0x1E, 0xCE, 0xE1, 0xB6, 0x94, 0xED, 0x9A, 0x3A, 0x1A, 0x66, 0xFF, 0x5C, 0x43, 0x7D, + 0x51, 0x46, 0x09, 0xBB, 0xD0, 0x5D, 0x1A, 0x81, 0x98, 0x9A, 0xAC, 0x74, 0x94, 0xD3, 0x05, 0x55, + 0xE1, 0xE4, 0x2A, 0x43, 0xCC, 0xC8, 0x2C, 0x10, 0xA7, 0xE8, 0xAD, 0x5F, 0x02, 0xDF, 0x3B, 0x10, + 0x33, 0x43, 0x8A, 0x92, 0xF9, 0xCF, 0x12, 0x04, 0x60, 0xCD, 0xA0, 0x30, 0xA9, 0xE8, 0x32, 0x30, + 0x80, 0x8B, 0xF2, 0x09, 0xA4, 0x91, 0xFA, 0x3B, 0xBD, 0x1D, 0x54, 0xFD, 0xF8, 0xCF, 0x74, 0x70, + 0x50, 0x9B, 0x8D, 0x40, 0xBD, 0xC7, 0x3D, 0x4F, 0x03, 0x7B, 0x63, 0x32, 0xCE, 0x8B, 0x5B, 0x7C, + 0x9A, 0xE0, 0x3A, 0x37, 0x38, 0xCA, 0x31, 0x21, 0xDB, 0x4F, 0xF6, 0xF0, 0xDB, 0x4E, 0xE5, 0xDC, + 0x69, 0x4B, 0xAE, 0x28, 0x01, 0xF2, 0x46, 0x38, 0x6A, 0x9A, 0x50, 0x2D, 0xF4, 0x36, 0x5D, 0xEF, + 0x25, 0xB1, 0x31, 0xCA, 0x58, 0x5E, 0x4B, 0xE6, 0xAC, 0xCF, 0x0C, 0x20, 0x84, 0x7F, 0xF5, 0xC1, + 0x1A, 0xE5, 0x94, 0xC3, 0x3D, 0x77, 0x8E, 0xA3, 0x9B, 0xAF, 0x96, 0xBF, 0xD1, 0xEC, 0x35, 0x17, + 0x15, 0xDF, 0x2B, 0x00, 0xCD, 0x2C, 0xDB, 0xD3, 0x31, 0x8A, 0x6D, 0xE0, 0xB3, 0x1F, 0x71, 0xB4, + 0xA4, 0xCA, 0xFA, 0x40, 0xEB, 0x99, 0x4C, 0xFB, 0xFE, 0x9D, 0xBA, 0x26, 0x0F, 0x1B, 0x6D, 0xE6, + 0xB6, 0x2C, 0xAD, 0xF4, 0xD8, 0x12, 0xC0, 0xA3, 0xA4, 0x65, 0x10, 0x1B, 0xCD, 0xFD, 0x0A, 0xB0, + 0x52, 0x71, 0x56, 0xD4, 0x01, 0x49, 0xC9, 0x68, 0x06, 0xA1, 0xD3, 0x61, 0x8A, 0xC1, 0x07, 0x1B, + 0x06, 0x48, 0x78, 0x1B, 0x96, 0xCB, 0x7B, 0xC5, 0xF4, 0x8B, 0x27, 0x93, 0xF1, 0x10, 0x40, 0xB3, + 0x36, 0xA3, 0xA8, 0x19, 0xF3, 0x1B, 0x1B, 0xA4, 0xCD, 0x19, 0x18, 0x25, 0x7C, 0x24, 0xAC, 0x05, + 0xD1, 0xB2, 0xA1, 0x1E, 0x0B, 0xB3, 0xDA, 0x82, 0x3E, 0x78, 0x87, 0x12, 0x8F, 0xE6, 0xB9, 0x59, + 0x02, 0xDC, 0x15, 0x31, 0xA3, 0xAE, 0x22, 0x2F, 0xB7, 0x60, 0x08, 0x42, 0xA0, 0x4B, 0x94, 0xA1, + 0x1A, 0xD9, 0x32, 0x40, 0x2C, 0x33, 0x5A, 0x62, 0xB3, 0xDB, 0x9C, 0x04, 0x3B, 0xCE, 0x40, 0xCD, + 0x3C, 0x29, 0xDB, 0x1C, 0x98, 0xC7, 0x7B, 0x09, 0x65, 0x91, 0xCD, 0xB9, 0x71, 0x80, 0xF2, 0x14, + 0x90, 0x27, 0xA5, 0xC7, 0x16, 0xBB, 0xC8, 0x6D, 0x79, 0x0F, 0xC7, 0x06, 0x4F, 0xFD, 0xEF, 0x3D, + 0x7E, 0xA9, 0x1A, 0x20, 0x4A, 0x94, 0x3F, 0x84, 0xA1, 0x97, 0xE1, 0x99, 0xF5, 0x9E, 0xB2, 0x46, + 0x23, 0xC3, 0x48, 0x46, 0xC0, 0x2C, 0x7D, 0x64, 0x45, 0x7B, 0xA0, 0xBF, 0x5F, 0x14, 0xBC, 0xB2, + 0x17, 0x87, 0x68, 0x5C, 0x17, 0xCE, 0xCA, 0xEA, 0x73, 0x3C, 0xAE, 0x6C, 0x8C, 0x4A, 0x2C, 0xFB, + 0x77, 0x52, 0xE7, 0xA1, 0xFF, 0x6C, 0x23, 0x05, 0x1E, 0x69, 0x22, 0xF1, 0xDA, 0x6B, 0xB6, 0x01, + 0x44, 0xDE, 0xEB, 0x80, 0xB7, 0x84, 0x5B, 0xC7, 0xEA, 0x59, 0x5B, 0x3F, 0x23, 0x7E, 0x10, 0x00, + 0x27, 0x5C, 0x6A, 0x7B, 0xEB, 0xFF, 0xDF, 0x82, 0xEE, 0x85, 0x6C, 0xA5, 0x1A, 0xBF, 0xEB, 0x64, + 0xBB, 0x10, 0x46, 0x40, 0x51, 0x29, 0x2C, 0x6A, 0xD2, 0xF5, 0x58, 0x61, 0x5F, 0x77, 0x31, 0xBD, + 0x59, 0x0A, 0x1B, 0xF5, 0xC2, 0xFA, 0x9A, 0xB4, 0x59, 0xF4, 0x0A, 0xD1, 0x68, 0xCC, 0x21, 0x44, + 0xDC, 0x70, 0x80, 0xD1, 0x67, 0xCC, 0x24, 0x22, 0xC0, 0x77, 0x04, 0xED, 0xA3, 0xB4, 0x23, 0xC8, + 0xAD, 0x5E, 0x18, 0x64, 0x57, 0x89, 0x52, 0xDD, 0x25, 0x6C, 0x38, 0xCE, 0x5D, 0x45, 0x42, 0x18, + 0xA3, 0xE2, 0xD8, 0x6E, 0x21, 0x5F, 0xBB, 0x9D, 0xCA, 0x90, 0x57, 0x85, 0x0C, 0xD5, 0x56, 0xC8, + 0x12, 0x39, 0x27, 0x44, 0x77, 0xE6, 0x59, 0xE7, 0x08, 0xB0, 0x4E, 0x80, 0xBD, 0xE3, 0xE6, 0x8B, + 0xE1, 0x4B, 0x6E, 0xCE, 0xA9, 0x5C, 0x8E, 0xF2, 0xE2, 0xFE, 0x18, 0xFB, 0x74, 0xD6, 0x3C, 0x76, + 0xB8, 0xB0, 0x90, 0x00, 0x3A, 0xF4, 0xE4, 0xB7, 0xFD, 0x05, 0xA3, 0x7A, 0xD9, 0xF7, 0x0E, 0x18, + 0x66, 0xBC, 0x9A, 0x47, 0x18, 0x80, 0x4F, 0x4A, 0x6B, 0x9C, 0xF8, 0x48, 0x16, 0x49, 0x3F, 0x21, + 0xB4, 0x20, 0x59, 0x51, 0xDA, 0xD1, 0x4B, 0xE8, 0x5E, 0x48, 0x95, 0x77, 0x0A, 0x82, 0xA1, 0x8F, + 0xD2, 0x77, 0xC9, 0xC5, 0xE2, 0x79, 0x24, 0x87, 0x34, 0x0C, 0x9E, 0x17, 0x6F, 0x7B, 0xEA, 0x14, + 0x79, 0xF6, 0x0C, 0x95, 0x5F, 0x1C, 0x61, 0x01, 0x61, 0x6D, 0xD1, 0xF2, 0x6A, 0xA8, 0x09, 0xAA, + 0x0A, 0x6E, 0xF9, 0x28, 0x69, 0x49, 0x14, 0x60, 0x6D, 0xA3, 0xCF, 0x5E, 0x4C, 0x34, 0x8E, 0xFC, + 0x7E, 0x3E, 0x72, 0x3E, 0x02, 0x78, 0x3D, 0x1B, 0xA4, 0x93, 0x46, 0x39, 0x46, 0x0E, 0xF0, 0xE3, + 0x46, 0xF0, 0x4C, 0x2F, 0xE2, 0x14, 0x93, 0xF0, 0x2A, 0x5B, 0xE8, 0x05, 0xF7, 0x10, 0x3A, 0x6D, + 0x03, 0xA0, 0x5D, 0xF8, 0x5D, 0xD1, 0x4C, 0x58, 0x7B, 0xBF, 0xAB, 0x52, 0xC1, 0x6E, 0x72, 0x1E, + 0x60, 0x8C, 0x33, 0x4E, 0x22, 0xE7, 0x12, 0x51, 0x0B, 0xFE, 0x22, 0xDA, 0x8A, 0x53, 0xA7, 0xC6, + 0x9A, 0x66, 0x92, 0x76, 0x46, 0x3C, 0xC5, 0x72, 0x55, 0x6C, 0xD2, 0x8D, 0xF1, 0xD5, 0x6C, 0x09, + 0xEA, 0x2D, 0x1A, 0xF9, 0x99, 0x5E, 0x65, 0xCC, 0x6D, 0x55, 0x5F, 0x46, 0x66, 0xC3, 0xBB, 0xBE, + 0x1C, 0x53, 0x40, 0x3B, 0xE9, 0x15, 0x6C, 0xD6, 0x94, 0x8C, 0x5D, 0xB4, 0x4A, 0xDC, 0x2F, 0x2F, + 0xC1, 0xA8, 0xE1, 0xDF, 0x7A, 0xB5, 0x6D, 0xE2, 0xF8, 0xDB, 0xAA, 0x5D, 0x3D, 0x80, 0x5D, 0x33, + 0x6F, 0xCD, 0x5A, 0x84, 0xBC, 0x2D, 0x6E, 0x28, 0x97, 0x07, 0xA6, 0xF1, 0x0B, 0x22, 0x23, 0x58, + 0xDF, 0x50, 0x15, 0x73, 0xD6, 0x75, 0x63, 0xB6, 0x0F, 0xD8, 0x75, 0x9B, 0xF9, 0x9D, 0xBF, 0xE6, + 0xAF, 0xF8, 0xBF, 0xB4, 0x3A, 0xD4, 0x02, 0x3B, 0x8A, 0x4D, 0xF6, 0x44, 0x0E, 0x7F, 0x2B, 0xDB, + 0x9C, 0xAC, 0xD6, 0xB8, 0xC5, 0xB5, 0xEF, 0x1B, 0x67, 0xB0, 0x10, 0xCC, 0x1E, 0x24, 0xD4, 0x05, + 0x06, 0x6D, 0x3A, 0xFE, 0x95, 0x4C, 0x00, 0x8A, 0xD2, 0x53, 0x1B, 0x7A, 0xDE, 0xA0, 0x9F, 0x2D, + 0x10, 0x43, 0x2D, 0xA3, 0x8E, 0x66, 0x36, 0x27, 0x77, 0x05, 0x26, 0x78, 0x21, 0x09, 0x18, 0xD0, + 0x2E, 0x86, 0x1E, 0x56, 0x3B, 0x71, 0x3A, 0x46, 0x24, 0x7C, 0x90, 0x1E, 0x42, 0x36, 0x11, 0xA1, + 0x7C, 0x04, 0x80, 0x42, 0x0F, 0xA8, 0x4E, 0x07, 0xA1, 0x7B, 0x55, 0x5F, 0xA8, 0x8A, 0x1D, 0x36, + 0x9C, 0x16, 0xBD, 0xE0, 0x63, 0x0A, 0xE3, 0xC5, 0xF9, 0x55, 0xA4, 0xE9, 0x75, 0x7D, 0xD5, 0x82, + 0x32, 0x1E, 0x6B, 0x05, 0xE4, 0xF9, 0x8A, 0x8C, 0x18, 0x0E, 0xA0, 0xDF, 0x23, 0x4D, 0xA8, 0x4E, + 0xF0, 0x85, 0xFF, 0x04, 0xC5, 0xFE, 0x1E, 0x8A, 0x3F, 0xBF, 0x54, 0x05, 0x82, 0x8A, 0x0C, 0xDF, + 0xCA, 0x0A, 0xD0, 0x43, 0x76, 0xE5, 0x49, 0x55, 0x36, 0x24, 0x6F, 0x2F, 0x20, 0x58, 0x3A, 0xFE, + 0x62, 0x3A, 0x11, 0xAA, 0x2F, 0xB2, 0x25, 0x6F, 0x0B, 0x9D, 0xD8, 0xCC, 0xC5, 0x26, 0x16, 0x2E, + 0x74, 0x56, 0xBE, 0x99, 0xF4, 0xE7, 0x90, 0x88, 0x65, 0x1E, 0x6C, 0xAA, 0xB4, 0x2C, 0xCF, 0x12, + 0x63, 0x86, 0x8E, 0x9C, 0xF5, 0x75, 0x5A, 0x71, 0xBF, 0xFB, 0x54, 0xF3, 0x86, 0x61, 0xC5, 0x59, + 0xB4, 0xD5, 0x8E, 0x5D, 0x91, 0xA0, 0xB0, 0x9B, 0xB1, 0x95, 0xCE, 0x45, 0x0C, 0x4A, 0xFC, 0xE0, + 0x56, 0x73, 0x96, 0xC1, 0xAA, 0x0A, 0x65, 0x42, 0x42, 0xC1, 0xC7, 0x2D, 0x6E, 0xC8, 0x4A, 0x7F, + 0x40, 0x53, 0x0C, 0x7A, 0x99, 0x96, 0x59, 0xFA, 0xEC, 0xAD, 0xA1, 0xF0, 0xAB, 0xFE, 0xB6, 0x58, + 0x49, 0x65, 0xF4, 0x29, 0x2A, 0x21, 0x42, 0x93, 0x0A, 0xED, 0x38, 0xC0, 0x33, 0xFD, 0xCF, 0x8E, + 0xC1, 0xEC, 0x3A, 0xF9, 0x1F, 0xEA, 0x8F, 0xA2, 0xEA, 0xAE, 0xC4, 0xE7, 0x43, 0xCB, 0x53, 0xF1, + 0x77, 0xC9, 0x6C, 0x61, 0x35, 0xE2, 0xED, 0x25, 0x68, 0xF6, 0x8E, 0x06, 0xD6, 0x41, 0x87, 0x58, + 0x8A, 0xE4, 0x5F, 0x80, 0x59, 0xC7, 0x21, 0xAC, 0xC1, 0x95, 0xC8, 0xBA, 0xF9, 0x84, 0x1F, 0x70, + 0x15, 0x1C, 0xB1, 0xF1, 0x2B, 0xB4, 0xB7, 0xA0, 0x4B, 0xE3, 0xB3, 0xD4, 0x3C, 0x9C, 0x01, 0xB2, + 0x4A, 0xE5, 0x47, 0x39, 0x10, 0x32, 0xE0, 0x0E, 0x1C, 0xE9, 0x3E, 0x2E, 0xDD, 0x12, 0x2C, 0xDF, + 0xB7, 0x47, 0xE8, 0x88, 0x53, 0x28, 0xF2, 0xC6, 0x11, 0x12, 0x15, 0x4A, 0x60, 0x14, 0xA4, 0x78, + 0x54, 0x8E, 0x6A, 0x77, 0xD6, 0x74, 0xC2, 0xCD, 0x4B, 0xF9, 0xC2, 0x84, 0xE8, 0xD6, 0xED, 0x4D, + 0xB3, 0x0C, 0x32, 0x39, 0xCF, 0xB9, 0xF9, 0x0B, 0xC3, 0x52, 0xF5, 0x6E, 0x9A, 0x38, 0x84, 0xED, + 0x0D, 0x83, 0xFA, 0x83, 0x2D, 0xF4, 0xB3, 0xEE, 0x71, 0xE0, 0x47, 0x48, 0xE6, 0x1A, 0x0B, 0xD9, + 0x54, 0x74, 0xB8, 0x39, 0xA1, 0x4C, 0xC7, 0x3C, 0x52, 0x02, 0x07, 0x61, 0x12, 0x1B, 0x49, 0x0B, + 0x7D, 0x08, 0xAA, 0xEA, 0xB0, 0x3A, 0x05, 0x65, 0x9A, 0xEA, 0x68, 0x0C, 0x5B, 0xA6, 0x37, 0xC6, + 0x5F, 0x10, 0x7D, 0xC8, 0xAE, 0xCA, 0x65, 0x1F, 0x16, 0x80, 0x38, 0xF1, 0xB7, 0xB5, 0xDB, 0x1D, + 0x37, 0x5A, 0x1D, 0xEB, 0x7B, 0x2C, 0xA1, 0x7B, 0x72, 0xCE, 0x0D, 0x34, 0xB4, 0x17, 0x23, 0x52, + 0x8B, 0x3A, 0xD6, 0xEC, 0xE5, 0x8D, 0x23, 0x6A, 0xCF, 0x34, 0xDE, 0x02, 0x5A, 0xA4, 0x54, 0xFF, + 0x85, 0x85, 0x3E, 0x33, 0x87, 0xF9, 0x27, 0x59, 0xE2, 0x32, 0xAB, 0x8D, 0xBA, 0x8A, 0x92, 0xEB, + 0x5D, 0xA7, 0xF6, 0x6A, 0xDF, 0x32, 0xAD, 0xAC, 0x70, 0xCF, 0x91, 0xA9, 0x8E, 0x4C, 0x39, 0x71, + 0x4C, 0x1B, 0xBF, 0xD1, 0xD0, 0x68, 0x19, 0x9C, 0x8A, 0x7B, 0x57, 0x52, 0x40, 0xCE, 0xCC, 0x86, + 0xB7, 0x0E, 0x3D, 0x5E, 0xAD, 0xD0, 0x2B, 0xD4, 0x58, 0x5C, 0x5B, 0xD8, 0x00, 0x7F, 0x42, 0x99, + 0x84, 0x5D, 0x2D, 0x86, 0x40, 0x10, 0x35, 0x15, 0x05, 0x67, 0xDE, 0x22, 0x6C, 0xB4, 0x5C, 0x7B, + 0xCA, 0xDF, 0xF4, 0x1D, 0xD2, 0xCB, 0x34, 0x02, 0x6C, 0x22, 0x10, 0x4F, 0x7F, 0xDF, 0x18, 0xFF, + 0x5A, 0x81, 0x4C, 0xAC, 0xF7, 0xF3, 0xF1, 0x5D, 0xBA, 0x72, 0x36, 0x26, 0x88, 0xAE, 0xCA, 0xE0, + 0x79, 0x84, 0x68, 0x86, 0xCE, 0x35, 0xAF, 0x27, 0xB4, 0x21, 0xFD, 0x05, 0xB1, 0x38, 0x17, 0x7D, + 0x9B, 0x5A, 0x12, 0x29, 0x76, 0xE5, 0xA0, 0x39, 0x0B, 0xAD, 0x4C, 0x33, 0xCB, 0x45, 0x59, 0x82, + 0xB7, 0x03, 0xD2, 0x1A, 0xF7, 0x2F, 0x54, 0x43, 0x92, 0xC9, 0x8B, 0x6F, 0x6C, 0x6C, 0x1B, 0x10, + 0xEE, 0x97, 0x38, 0xA5, 0x8B, 0x16, 0xDE, 0xFC, 0xDA, 0x83, 0x4B, 0x39, 0x79, 0x17, 0xB7, 0x5A, + 0x59, 0x01, 0x11, 0xC0, 0xC6, 0x36, 0xD5, 0xBA, 0xB1, 0x46, 0x9E, 0x6B, 0xA1, 0xCB, 0x96, 0x7E, + 0x56, 0x91, 0x87, 0x68, 0x59, 0x8E, 0xCF, 0xB5, 0x58, 0x24, 0x2C, 0xD9, 0x0A, 0x06, 0x25, 0xCB, + 0x8C, 0xE6, 0x02, 0xE3, 0x5A, 0x19, 0x4D, 0x8F, 0x43, 0x5B, 0x40, 0x3F, 0x7D, 0x50, 0x24, 0x90, + 0x71, 0x3E, 0x88, 0x96, 0x3C, 0xCE, 0x2C, 0x80, 0x35, 0x68, 0x3E, 0x21, 0x67, 0x8A, 0x03, 0x68, + 0x49, 0x6B, 0xFA, 0xE2, 0x5A, 0xC7, 0xFF, 0x9C, 0xDF, 0x0D, 0xD9, 0xB5, 0x12, 0x07, 0x35, 0x7B, + 0x35, 0xDC, 0xF7, 0x12, 0x55, 0x71, 0x8A, 0x9F, 0x68, 0x66, 0x2A, 0x72, 0x55, 0x14, 0x82, 0xE2, + 0xBC, 0x3A, 0x39, 0xA7, 0x91, 0xA9, 0x91, 0xC8, 0x2B, 0x5F, 0x0A, 0x09, 0xFD, 0xE0, 0x6B, 0x58, + 0x85, 0x58, 0x1D, 0xCD, 0xEA, 0xAE, 0xBA, 0xA5, 0x49, 0xFF, 0x69, 0x4A, 0x10, 0xDD, 0x5A, 0xE0, + 0x0E, 0xD3, 0x9C, 0x0B, 0xD8, 0x28, 0x2E, 0xCA, 0x8E, 0x9F, 0x29, 0x84, 0xC8, 0xCA, 0x47, 0x79, + 0xB9, 0xCC, 0x71, 0xC6, 0xE1, 0xC1, 0x4D, 0x15, 0xB6, 0x1F, 0x92, 0x13, 0x2C, 0x83, 0xBE, 0x0D, + 0x03, 0x0A, 0x7C, 0x02, 0x7C, 0x2C, 0xFA, 0x8E, 0xC8, 0x23, 0xE1, 0xFD, 0x83, 0xDC, 0x61, 0xDB, + 0x1C, 0xE4, 0x3F, 0xE8, 0xA9, 0x1C, 0x55, 0x1E, 0xB2, 0xF1, 0x47, 0x6C, 0x5A, 0xD8, 0xD9, 0xD4, + 0xA8, 0xBF, 0x4B, 0xF9, 0x0A, 0xD5, 0xBF, 0x1D, 0x88, 0x30, 0x12, 0x80, 0x72, 0x49, 0x6C, 0x91, + 0x70, 0x6A, 0xB7, 0x4D, 0x86, 0x34, 0x8F, 0x57, 0x1A, 0x6A, 0x16, 0x7B, 0xA5, 0x5D, 0x3C, 0x74, + 0x8E, 0x03, 0x1D, 0x53, 0xD6, 0x1C, 0x75, 0x83, 0x2C, 0xF7, 0x5E, 0xF6, 0xE0, 0xC9, 0x25, 0x11, + 0x9F, 0x98, 0x73, 0xD2, 0xBE, 0x50, 0x56, 0xD6, 0x73, 0x38, 0xC8, 0x30, 0x5A, 0xB5, 0xF6, 0x67, + 0x37, 0x75, 0x7E, 0x99, 0x71, 0xC7, 0x30, 0x26, 0x0D, 0x43, 0xA2, 0x0B, 0xF0, 0xC2, 0xCC, 0x38, + 0xDF, 0x0F, 0x37, 0x25, 0xA0, 0x4F, 0x18, 0x5A, 0x40, 0x75, 0x2D, 0xFC, 0x52, 0xFF, 0x37, 0xD5, + 0x38, 0x06, 0xC6, 0x62, 0x13, 0xA0, 0x8E, 0x7A, 0xFA, 0xDD, 0xB9, 0x25, 0xBE, 0xBC, 0x1F, 0x02, + 0x4C, 0x56, 0xA1, 0xBA, 0x08, 0xBB, 0x65, 0xAE, 0xAC, 0x14, 0x4E, 0x65, 0xD7, 0xD1, 0x99, 0xFF, + 0x14, 0x33, 0x9F, 0x18, 0x1D, 0x54, 0x70, 0x6F, 0xBC, 0x85, 0xB4, 0x49, 0x82, 0x4C, 0x0A, 0xA6, + 0xE8, 0x10, 0x61, 0x8F, 0xED, 0xFD, 0x7F, 0x10, 0xAA, 0xAC, 0x13, 0xB5, 0xE2, 0x5D, 0xF3, 0x69, + 0x2B, 0xC3, 0xE1, 0xC2, 0x89, 0x9A, 0x46, 0xF0, 0x97, 0x34, 0xFC, 0x93, 0x31, 0x45, 0xE9, 0x7A, + 0xD4, 0x46, 0x1A, 0x31, 0xFF, 0x65, 0x39, 0xAE, 0xCE, 0xC5, 0x40, 0x2F, 0xD0, 0xCB, 0x1E, 0xC6, + 0x86, 0xDC, 0x2F, 0x72, 0x34, 0x35, 0x28, 0xAD, 0x96, 0xC3, 0xBE, 0xE4, 0xC8, 0xA5, 0xB6, 0x6B, + 0xBD, 0xFB, 0x9B, 0xE5, 0x16, 0x01, 0x4A, 0x55, 0xA2, 0xB0, 0xE8, 0x66, 0x4A, 0xC1, 0xE3, 0xC0, + 0xB9, 0x87, 0xF4, 0xD6, 0x8C, 0xF4, 0x61, 0x42, 0xA4, 0xBF, 0x73, 0x03, 0x67, 0xBD, 0x2F, 0x30, + 0x6E, 0x97, 0x45, 0x0F, 0xC6, 0xBD, 0xCA, 0x2E, 0x69, 0x72, 0x41, 0x07, 0x38, 0x74, 0x18, 0xB1, + 0xCB, 0x5C, 0xD3, 0xC2, 0x1D, 0xB7, 0xF2, 0xAF, 0xEC, 0x00, 0xCD, 0x4F, 0x6F, 0xDE, 0xB2, 0xAC, + 0x44, 0x4C, 0x1A, 0x56, 0xC3, 0x93, 0x21, 0x57, 0x8C, 0x6B, 0x1C, 0xD7, 0xF9, 0xC7, 0xC0, 0xA9, + 0xF7, 0xE4, 0xA3, 0xD8, 0x58, 0x9B, 0x35, 0xE6, 0x00, 0x7C, 0xE2, 0x12, 0xD0, 0x53, 0x61, 0x1F, + 0xC6, 0xA5, 0x81, 0x7A, 0x52, 0xEF, 0x09, 0x36, 0x1A, 0x62, 0x3C, 0x92, 0xD2, 0x65, 0x43, 0xF6, + 0xDF, 0x0B, 0x89, 0x7E, 0xC6, 0xB9, 0x47, 0xF4, 0x52, 0x14, 0x0C, 0x44, 0x4D, 0x23, 0x55, 0x6F, + 0x80, 0xC2, 0xBE, 0xC5, 0x59, 0xF8, 0xB5, 0x27, 0xE6, 0xE8, 0xF0, 0x32, 0x91, 0x3A, 0x59, 0x2B, + 0x1D, 0xF9, 0xAC, 0xF9, 0x2E, 0xF3, 0xB9, 0x36, 0x8C, 0x1A, 0x5D, 0x92, 0x33, 0xEA, 0xA6, 0xAF, + 0x71, 0x24, 0xE2, 0x7F, 0x18, 0xB1, 0x23, 0x3C, 0x53, 0xF3, 0xCC, 0x5D, 0xD4, 0x2D, 0x2B, 0x7A, + 0xF8, 0x94, 0x04, 0xC0, 0x8C, 0x65, 0x4D, 0x3A, 0xFA, 0xCF, 0xF9, 0x07, 0xC2, 0xD7, 0x75, 0x87, + 0x47, 0x9E, 0x6E, 0xA0, 0x79, 0x81, 0x6E, 0x03, 0xC7, 0xD2, 0x75, 0xF6, 0xC3, 0x85, 0x22, 0x28, + 0x1B, 0x39, 0x63, 0xCC, 0x12, 0x7E, 0x4A, 0xC3, 0x2A, 0x45, 0x85, 0xA9, 0x54, 0xDF, 0xB2, 0x33, + 0xEF, 0x76, 0x25, 0x31, 0xC9, 0xCD, 0xDA, 0x14, 0xE7, 0xD8, 0x4D, 0x18, 0xCE, 0xAB, 0xE7, 0x85, + 0xDD, 0x95, 0x8D, 0x36, 0xA1, 0x18, 0x87, 0x5E, 0xB6, 0x75, 0x2F, 0x3B, 0x97, 0x09, 0x18, 0x47, + 0xC8, 0x90, 0x37, 0xA3, 0xB2, 0xD2, 0x09, 0x3C, 0x5E, 0x6C, 0x2E, 0x72, 0x38, 0x08, 0x24, 0x99, + 0x90, 0xD0, 0x86, 0xC7, 0xD3, 0xFB, 0x4E, 0xA2, 0xDF, 0xC7, 0x26, 0x4D, 0x8E, 0x81, 0x98, 0x19, + 0x15, 0xD0, 0x4C, 0xB8, 0x44, 0xF7, 0x53, 0x1C, 0x0F, 0xAF, 0x78, 0x4C, 0x20, 0xB8, 0xCC, 0xBB, + 0x20, 0x60, 0xEC, 0x55, 0x70, 0xBD, 0xE9, 0x02, 0x63, 0x9F, 0x1F, 0xA7, 0xD5, 0x27, 0x18, 0x33, + 0x29, 0xA8, 0x33, 0x6E, 0xCB, 0x80, 0x40, 0x2D, 0x52, 0xDF, 0x6C, 0x78, 0x8F, 0xA6, 0x1D, 0xCF, + 0xE8, 0xB9, 0x54, 0x6B + }; + + public static byte[] ModuleKey = + { + 0xAE, 0x25, 0xBC, 0x51, 0x06, 0x3B, 0x77, 0xBD, 0x36, 0x3C, 0x3E, 0xFE, 0x0F, 0xC1, 0x73, 0xF9 + }; + + public static byte[] Seed = + { + 0x4D, 0x80, 0x8D, 0x2C, 0x77, 0xD9, 0x05, 0xC4, 0x1A, 0x63, 0x80, 0xEC, 0x08, 0x58, 0x6A, 0xFE + }; + + public static byte[] ServerKeySeed = + { + 0xC2, 0xB7, 0xAD, 0xED, 0xFC, 0xCC, 0xA9, 0xC2, 0xBF, 0xB3, 0xF8, 0x56, 0x02, 0xBA, 0x80, 0x9B + }; + + public static byte[] ClientKeySeed = + { + 0x7F, 0x96, 0xEE, 0xFD, 0xA5, 0xB6, 0x3D, 0x20, 0xA4, 0xDF, 0x8E, 0x00, 0xCB, 0xF4, 0x83, 0x04 + }; + + public static byte[] ClientKeySeedHash = + { + 0x56, 0x8C, 0x05, 0x4C, 0x78, 0x1A, 0x97, 0x2A, 0x60, 0x37, 0xA2, 0x29, 0x0C, 0x22, 0xB5, 0x25, 0x71, 0xA0, 0x6F, 0x4E + }; + } + +} diff --git a/Game/Warden/WardenWin.cs b/Game/Warden/WardenWin.cs new file mode 100644 index 000000000..3450714c5 --- /dev/null +++ b/Game/Warden/WardenWin.cs @@ -0,0 +1,455 @@ +/* + * Copyright (C) 2012-2017 CypherCore + * + * 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, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +using Framework.Constants; +using Framework.Cryptography; +using Framework.IO; +using Game.Network.Packets; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Numerics; + +namespace Game +{ + class WardenWin : Warden + { + public override void Init(WorldSession session, BigInteger k) + { + _session = session; + // Generate Warden Key + SHA1Randx WK = new SHA1Randx(k.ToByteArray()); + WK.Generate(_inputKey, 16); + WK.Generate(_outputKey, 16); + + _seed = WardenModuleWin.Seed; + + _inputCrypto.PrepareKey(_inputKey); + _outputCrypto.PrepareKey(_outputKey); + Log.outDebug(LogFilter.Warden, "Server side warden for client {0} initializing...", session.GetAccountId()); + Log.outDebug(LogFilter.Warden, "C->S Key: {0}", _inputKey.ToHexString()); + Log.outDebug(LogFilter.Warden, "S->C Key: {0}", _outputKey.ToHexString()); + Log.outDebug(LogFilter.Warden, " Seed: {0}", _seed.ToHexString()); + Log.outDebug(LogFilter.Warden, "Loading Module..."); + + _module = GetModuleForClient(); + + Log.outDebug(LogFilter.Warden, "Module Key: {0}", _module.Key.ToHexString()); + Log.outDebug(LogFilter.Warden, "Module ID: {0}", _module.Id.ToHexString()); + RequestModule(); + } + + public override ClientWardenModule GetModuleForClient() + { + ClientWardenModule mod = new ClientWardenModule(); + + uint length = (uint)WardenModuleWin.Module.Length; + + // data assign + mod.CompressedSize = length; + mod.CompressedData = WardenModuleWin.Module; + mod.Key = WardenModuleWin.ModuleKey; + + // md5 hash + System.Security.Cryptography.MD5 ctx = System.Security.Cryptography.MD5.Create(); + ctx.Initialize(); + ctx.TransformBlock(mod.CompressedData, 0, mod.CompressedData.Length, mod.CompressedData, 0); + ctx.TransformBlock(mod.Id, 0, mod.Id.Length, mod.Id, 0); + + return mod; + } + + public override void InitializeModule() + { + Log.outDebug(LogFilter.Warden, "Initialize module"); + + // Create packet structure + WardenInitModuleRequest Request = new WardenInitModuleRequest(); + Request.Command1 = WardenOpcodes.Smsg_ModuleInitialize; + Request.Size1 = 20; + Request.Unk1 = 1; + Request.Unk2 = 0; + Request.Type = 1; + Request.String_library1 = 0; + Request.Function1[0] = 0x00024F80; // 0x00400000 + 0x00024F80 SFileOpenFile + Request.Function1[1] = 0x000218C0; // 0x00400000 + 0x000218C0 SFileGetFileSize + Request.Function1[2] = 0x00022530; // 0x00400000 + 0x00022530 SFileReadFile + Request.Function1[3] = 0x00022910; // 0x00400000 + 0x00022910 SFileCloseFile + Request.CheckSumm1 = BuildChecksum(BitConverter.GetBytes(Request.Unk1), 20); + + Request.Command2 = WardenOpcodes.Smsg_ModuleInitialize; + Request.Size2 = 8; + Request.Unk3 = 4; + Request.Unk4 = 0; + Request.String_library2 = 0; + Request.Function2 = 0x00419D40; // 0x00400000 + 0x00419D40 FrameScript::GetText + Request.Function2_set = 1; + Request.CheckSumm2 = BuildChecksum(BitConverter.GetBytes(Request.Unk2), 8); + + Request.Command3 = WardenOpcodes.Smsg_ModuleInitialize; + Request.Size3 = 8; + Request.Unk5 = 1; + Request.Unk6 = 1; + Request.String_library3 = 0; + Request.Function3 = 0x0046AE20; // 0x00400000 + 0x0046AE20 PerformanceCounter + Request.Function3_set = 1; + Request.CheckSumm3 = BuildChecksum(BitConverter.GetBytes(Request.Unk5), 8); + + WardenDataServer packet = new WardenDataServer(); + packet.Data = EncryptData(Request.Write()); + _session.SendPacket(packet); + } + + public override void RequestHash() + { + Log.outDebug(LogFilter.Warden, "Request hash"); + + // Create packet structure + WardenHashRequest Request = new WardenHashRequest(); + Request.Command = WardenOpcodes.Smsg_HashRequest; + Request.Seed = _seed; + + WardenDataServer packet = new WardenDataServer(); + packet.Data = EncryptData(Request.Write()); + _session.SendPacket(packet); + } + + public override void HandleHashResult(ByteBuffer buff) + { + // Verify key + if (buff.ReadBytes(20) != WardenModuleWin.ClientKeySeedHash) + { + Log.outWarn(LogFilter.Warden, "{0} failed hash reply. Action: {0}", _session.GetPlayerInfo(), Penalty()); + return; + } + + Log.outDebug(LogFilter.Warden, "Request hash reply: succeed"); + + // Change keys here + _inputKey = WardenModuleWin.ClientKeySeed; + _outputKey = WardenModuleWin.ServerKeySeed; + + _inputCrypto.PrepareKey(_inputKey); + _outputCrypto.PrepareKey(_outputKey); + + _initialized = true; + + _previousTimestamp = Time.GetMSTime(); + } + + public override void RequestData() + { + Log.outDebug(LogFilter.Warden, "Request data"); + + // If all checks were done, fill the todo list again + if (_memChecksTodo.Empty()) + _memChecksTodo.AddRange(Global.WardenCheckMgr.MemChecksIdPool); + + if (_otherChecksTodo.Empty()) + _otherChecksTodo.AddRange(Global.WardenCheckMgr.OtherChecksIdPool); + + _serverTicks = Time.GetMSTime(); + + ushort id; + WardenCheckType type; + WardenCheck wd; + _currentChecks.Clear(); + + // Build check request + for (uint i = 0; i < WorldConfig.GetUIntValue(WorldCfg.WardenNumMemChecks); ++i) + { + // If todo list is done break loop (will be filled on next Update() run) + if (_memChecksTodo.Empty()) + break; + + // Get check id from the end and remove it from todo + id = _memChecksTodo.Last(); + _memChecksTodo.Remove(id); + + // Add the id to the list sent in this cycle + _currentChecks.Add(id); + } + + ByteBuffer buffer = new ByteBuffer(); + buffer.WriteUInt8(WardenOpcodes.Smsg_CheatChecksRequest); + + for (uint i = 0; i < WorldConfig.GetUIntValue(WorldCfg.WardenNumOtherChecks); ++i) + { + // If todo list is done break loop (will be filled on next Update() run) + if (_otherChecksTodo.Empty()) + break; + + // Get check id from the end and remove it from todo + id = _otherChecksTodo.Last(); + _otherChecksTodo.Remove(id); + + // Add the id to the list sent in this cycle + _currentChecks.Add(id); + + wd = Global.WardenCheckMgr.GetWardenDataById(id); + + switch (wd.Type) + { + case WardenCheckType.MPQ: + case WardenCheckType.LuaStr: + case WardenCheckType.Driver: + buffer.WriteUInt8(wd.Str.Length); + buffer.WriteString(wd.Str); + break; + default: + break; + } + } + + byte xorByte = _inputKey[0]; + + // Add TIMING_CHECK + buffer.WriteUInt8(0x00); + buffer.WriteUInt8((int)WardenCheckType.Timing ^ xorByte); + + byte index = 1; + + foreach (var checkId in _currentChecks) + { + wd = Global.WardenCheckMgr.GetWardenDataById(checkId); + + type = wd.Type; + buffer.WriteUInt8((int)type ^ xorByte); + switch (type) + { + case WardenCheckType.Memory: + { + buffer.WriteUInt8(0x00); + buffer.WriteUInt32(wd.Address); + buffer.WriteUInt8(wd.Length); + break; + } + case WardenCheckType.PageA: + case WardenCheckType.PageB: + { + buffer.WriteBytes(wd.Data.ToByteArray()); + buffer.WriteUInt32(wd.Address); + buffer.WriteUInt8(wd.Length); + break; + } + case WardenCheckType.MPQ: + case WardenCheckType.LuaStr: + { + buffer.WriteUInt8(index++); + break; + } + case WardenCheckType.Driver: + { + buffer.WriteBytes(wd.Data.ToByteArray()); + buffer.WriteUInt8(index++); + break; + } + case WardenCheckType.Module: + { + uint seed = RandomHelper.Rand32(); + buffer.WriteUInt32(seed); + HmacHash hmac = new HmacHash(BitConverter.GetBytes(seed)); + hmac.Finish(wd.Str); + buffer.WriteBytes(hmac.Digest); + break; + } + /*case PROC_CHECK: + { + buff.append(wd.i.AsByteArray(0, false).get(), wd.i.GetNumBytes()); + buff << uint8(index++); + buff << uint8(index++); + buff << uint32(wd.Address); + buff << uint8(wd.Length); + break; + }*/ + default: + break; // Should never happen + } + } + buffer.WriteUInt8(xorByte); + + WardenDataServer packet = new WardenDataServer(); + packet.Data = EncryptData(buffer.GetData()); + _session.SendPacket(packet); + + _dataSent = true; + + string stream = "Sent check id's: "; + foreach (var checkId in _currentChecks) + stream += checkId + " "; + + Log.outDebug(LogFilter.Warden, stream); + } + + public override void HandleData(ByteBuffer buff) + { + Log.outDebug(LogFilter.Warden, "Handle data"); + + _dataSent = false; + _clientResponseTimer = 0; + + ushort Length = buff.ReadUInt16(); + uint Checksum = buff.ReadUInt32(); + + if (!IsValidCheckSum(Checksum, buff.GetData(), Length)) + { + Log.outWarn(LogFilter.Warden, "{0} failed checksum. Action: {1}", _session.GetPlayerInfo(), Penalty()); + return; + } + + // TIMING_CHECK + { + byte result = buff.ReadUInt8(); + /// @todo test it. + if (result == 0x00) + { + Log.outWarn(LogFilter.Warden, "{0} failed timing check. Action: {1}", _session.GetPlayerInfo(), Penalty()); + return; + } + + uint newClientTicks = buff.ReadUInt32(); + + uint ticksNow = Time.GetMSTime(); + uint ourTicks = newClientTicks + (ticksNow - _serverTicks); + + Log.outDebug(LogFilter.Warden, "ServerTicks {0}", ticksNow); // Now + Log.outDebug(LogFilter.Warden, "RequestTicks {0}", _serverTicks); // At request + Log.outDebug(LogFilter.Warden, "Ticks {0}", newClientTicks); // At response + Log.outDebug(LogFilter.Warden, "Ticks diff {0}", ourTicks - newClientTicks); + } + + BigInteger rs; + WardenCheck rd; + WardenCheckType type; + ushort checkFailed = 0; + + foreach (var id in _currentChecks) + { + rd = Global.WardenCheckMgr.GetWardenDataById(id); + rs = Global.WardenCheckMgr.GetWardenResultById(id); + + type = rd.Type; + switch (type) + { + case WardenCheckType.Memory: + { + byte Mem_Result = buff.ReadUInt8(); + + if (Mem_Result != 0) + { + Log.outDebug(LogFilter.Warden, "RESULT MEM_CHECK not 0x00, CheckId {0} account Id {1}", id, _session.GetAccountId()); + checkFailed = id; + continue; + } + + if (buff.ReadBytes(rd.Length).Compare(rs.ToByteArray())) + { + Log.outDebug(LogFilter.Warden, "RESULT MEM_CHECK fail CheckId {0} account Id {1}", id, _session.GetAccountId()); + checkFailed = id; + continue; + } + + Log.outDebug(LogFilter.Warden, "RESULT MEM_CHECK passed CheckId {0} account Id {1}", id, _session.GetAccountId()); + break; + } + case WardenCheckType.PageA: + case WardenCheckType.PageB: + case WardenCheckType.Driver: + case WardenCheckType.Module: + { + byte value = 0xE9; + if (buff.ReadUInt8() != value) + { + if (type == WardenCheckType.PageA || type == WardenCheckType.PageB) + Log.outDebug(LogFilter.Warden, "RESULT PAGE_CHECK fail, CheckId {0} account Id {1}", id, _session.GetAccountId()); + if (type == WardenCheckType.Module) + Log.outDebug(LogFilter.Warden, "RESULT MODULE_CHECK fail, CheckId {0} account Id {1}", id, _session.GetAccountId()); + if (type == WardenCheckType.Driver) + Log.outDebug(LogFilter.Warden, "RESULT DRIVER_CHECK fail, CheckId {0} account Id {1}", id, _session.GetAccountId()); + checkFailed = id; + continue; + } + + if (type == WardenCheckType.PageA || type == WardenCheckType.PageB) + Log.outDebug(LogFilter.Warden, "RESULT PAGE_CHECK passed CheckId {0} account Id {1}", id, _session.GetAccountId()); + else if (type == WardenCheckType.Module) + Log.outDebug(LogFilter.Warden, "RESULT MODULE_CHECK passed CheckId {0} account Id {1}", id, _session.GetAccountId()); + else if (type == WardenCheckType.Driver) + Log.outDebug(LogFilter.Warden, "RESULT DRIVER_CHECK passed CheckId {0} account Id {1}", id, _session.GetAccountId()); + break; + } + case WardenCheckType.LuaStr: + { + byte Lua_Result = buff.ReadUInt8(); + + if (Lua_Result != 0) + { + Log.outDebug(LogFilter.Warden, "RESULT LUA_STR_CHECK fail, CheckId {0} account Id {1}", id, _session.GetAccountId()); + checkFailed = id; + continue; + } + + byte luaStrLen = buff.ReadUInt8(); + if (luaStrLen != 0) + Log.outDebug(LogFilter.Warden, "Lua string: {0}", buff.ReadString(luaStrLen)); + + Log.outDebug(LogFilter.Warden, "RESULT LUA_STR_CHECK passed, CheckId {0} account Id {1}", id, _session.GetAccountId()); + break; + } + case WardenCheckType.MPQ: + { + byte Mpq_Result = buff.ReadUInt8(); + + if (Mpq_Result != 0) + { + Log.outDebug(LogFilter.Warden, "RESULT MPQ_CHECK not 0x00 account id {0}", _session.GetAccountId()); + checkFailed = id; + continue; + } + + if (!buff.ReadBytes(20).Compare(rs.ToByteArray())) // SHA1 + { + Log.outDebug(LogFilter.Warden, "RESULT MPQ_CHECK fail, CheckId {0} account Id {1}", id, _session.GetAccountId()); + checkFailed = id; + continue; + } + + Log.outDebug(LogFilter.Warden, "RESULT MPQ_CHECK passed, CheckId {0} account Id {1}", id, _session.GetAccountId()); + break; + } + default: // Should never happen + break; + } + } + + if (checkFailed > 0) + { + WardenCheck check = Global.WardenCheckMgr.GetWardenDataById(checkFailed); + Log.outWarn(LogFilter.Warden, "{0} failed Warden check {1}. Action: {2}", _session.GetPlayerInfo(), checkFailed, Penalty(check)); + } + + // Set hold off timer, minimum timer should at least be 1 second + uint holdOff = WorldConfig.GetUIntValue(WorldCfg.WardenClientCheckHoldoff); + _checkTimer = (holdOff < 1 ? 1 : holdOff) * Time.InMilliseconds; + } + + uint _serverTicks; + List _otherChecksTodo = new List(); + List _memChecksTodo = new List(); + List _currentChecks = new List(); + } +} diff --git a/Game/Warden/WargenCheckManager.cs b/Game/Warden/WargenCheckManager.cs new file mode 100644 index 000000000..f7358d6f2 --- /dev/null +++ b/Game/Warden/WargenCheckManager.cs @@ -0,0 +1,211 @@ +/* + * Copyright (C) 2012-2017 CypherCore + * + * 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, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +using Framework.Constants; +using Framework.Database; +using System; +using System.Collections.Generic; +using System.Numerics; + +namespace Game +{ + public class WargenCheckManager : Singleton + { + WargenCheckManager() { } + + public void LoadWardenChecks() + { + // Check if Warden is enabled by config before loading anything + if (!WorldConfig.GetBoolValue(WorldCfg.WardenEnabled)) + { + Log.outInfo(LogFilter.Warden, "Warden disabled, loading checks skipped."); + return; + } + + // 0 1 2 3 4 5 6 7 + SQLResult result = DB.World.Query("SELECT id, type, data, result, address, length, str, comment FROM warden_checks ORDER BY id ASC"); + if (result.IsEmpty()) + { + Log.outInfo(LogFilter.ServerLoading, "Loaded 0 Warden checks. DB table `warden_checks` is empty!"); + return; + } + + uint count = 0; + do + { + ushort id = result.Read(0); + WardenCheckType checkType = (WardenCheckType)result.Read(1); + string data = result.Read(2); + string checkResult = result.Read(3); + uint address = result.Read(4); + byte length = result.Read(5); + string str = result.Read(6); + string comment = result.Read(7); + + WardenCheck wardenCheck = new WardenCheck(); + wardenCheck.Type = checkType; + wardenCheck.CheckId = id; + + // Initialize action with default action from config + wardenCheck.Action = (WardenActions)WorldConfig.GetIntValue(WorldCfg.WardenClientFailAction); + + if (checkType == WardenCheckType.PageA || checkType == WardenCheckType.PageB || checkType == WardenCheckType.Driver) + { + wardenCheck.Data = new BigInteger(data.ToByteArray()); + int len = data.Length / 2; + + if (wardenCheck.Data.ToByteArray().Length < len) + { + byte[] temp = wardenCheck.Data.ToByteArray(); + Array.Reverse(temp); + wardenCheck.Data = new BigInteger(temp); + } + } + + if (checkType == WardenCheckType.Memory || checkType == WardenCheckType.Module) + MemChecksIdPool.Add(id); + else + OtherChecksIdPool.Add(id); + + if (checkType == WardenCheckType.Memory || checkType == WardenCheckType.PageA || checkType == WardenCheckType.PageB || checkType == WardenCheckType.Proc) + { + wardenCheck.Address = address; + wardenCheck.Length = length; + } + + // PROC_CHECK support missing + if (checkType == WardenCheckType.Memory || checkType == WardenCheckType.MPQ || checkType == WardenCheckType.LuaStr || checkType == WardenCheckType.Driver || checkType == WardenCheckType.Module) + wardenCheck.Str = str; + + CheckStore[id] = wardenCheck; + + if (checkType == WardenCheckType.MPQ || checkType == WardenCheckType.Memory) + { + BigInteger Result = new BigInteger(checkResult.ToByteArray()); + int len = checkResult.Length / 2; + if (Result.ToByteArray().Length < len) + { + byte[] temp = Result.ToByteArray(); + Array.Reverse(temp); + Result = new BigInteger(temp); + } + CheckResultStore[id] = Result; + } + + if (comment.IsEmpty()) + wardenCheck.Comment = "Undocumented Check"; + else + wardenCheck.Comment = comment; + + ++count; + } + while (result.NextRow()); + + Log.outInfo(LogFilter.ServerLoading, "Loaded {0} warden checks.", count); + } + + public void LoadWardenOverrides() + { + // Check if Warden is enabled by config before loading anything + if (!WorldConfig.GetBoolValue(WorldCfg.WardenEnabled)) + { + Log.outInfo(LogFilter.Warden, "Warden disabled, loading check overrides skipped."); + return; + } + + // 0 1 + SQLResult result = DB.Characters.Query("SELECT wardenId, action FROM warden_action"); + if (result.IsEmpty()) + { + Log.outInfo(LogFilter.ServerLoading, "Loaded 0 Warden action overrides. DB table `warden_action` is empty!"); + return; + } + + uint count = 0; + do + { + ushort checkId = result.Read(0); + WardenActions action = (WardenActions)result.Read(1); + + // Check if action value is in range (0-2, see WardenActions enum) + if (action > WardenActions.Ban) + Log.outError(LogFilter.Warden, "Warden check override action out of range (ID: {0}, action: {1})", checkId, action); + // Check if check actually exists before accessing the CheckStore vector + else if (checkId > CheckStore.Count) + Log.outError(LogFilter.Warden, "Warden check action override for non-existing check (ID: {0}, action: {1}), skipped", checkId, action); + else + { + CheckStore[checkId].Action = action; + ++count; + } + } + while (result.NextRow()); + + Log.outInfo(LogFilter.ServerLoading, "Loaded {0} warden action overrides.", count); + } + + public WardenCheck GetWardenDataById(ushort Id) + { + if (Id < CheckStore.Count) + return CheckStore[Id]; + + return null; + } + + public BigInteger GetWardenResultById(ushort Id) + { + return CheckResultStore.LookupByKey(Id); + } + + public List MemChecksIdPool = new List(); + public List OtherChecksIdPool = new List(); + List CheckStore = new List(); + Dictionary CheckResultStore = new Dictionary(); + } + + public enum WardenActions + { + Log, + Kick, + Ban + } + + public enum WardenCheckType + { + Memory = 0xF3, // 243: byte moduleNameIndex + uint Offset + byte Len (check to ensure memory isn't modified) + PageA = 0xB2, // 178: uint Seed + byte[20] SHA1 + uint Addr + byte Len (scans all pages for specified hash) + PageB = 0xBF, // 191: uint Seed + byte[20] SHA1 + uint Addr + byte Len (scans only pages starts with MZ+PE headers for specified hash) + MPQ = 0x98, // 152: byte fileNameIndex (check to ensure MPQ file isn't modified) + LuaStr = 0x8B, // 139: byte luaNameIndex (check to ensure LUA string isn't used) + Driver = 0x71, // 113: uint Seed + byte[20] SHA1 + byte driverNameIndex (check to ensure driver isn't loaded) + Timing = 0x57, // 87: empty (check to ensure GetTickCount() isn't detoured) + Proc = 0x7E, // 126: uint Seed + byte[20] SHA1 + byte moluleNameIndex + byte procNameIndex + uint Offset + byte Len (check to ensure proc isn't detoured) + Module = 0xD9 // 217: uint Seed + byte[20] SHA1 (check to ensure module isn't injected) + } + + public class WardenCheck + { + public WardenCheckType Type; + public BigInteger Data; + public uint Address; // PROC_CHECK, MEM_CHECK, PAGE_CHECK + public byte Length; // PROC_CHECK, MEM_CHECK, PAGE_CHECK + public string Str; // LUA, MPQ, DRIVER + public string Comment; + public ushort CheckId; + public WardenActions Action; + } +} diff --git a/Game/Weather/WeatherManager.cs b/Game/Weather/WeatherManager.cs new file mode 100644 index 000000000..d87445ff9 --- /dev/null +++ b/Game/Weather/WeatherManager.cs @@ -0,0 +1,468 @@ +/* + * Copyright (C) 2012-2017 CypherCore + * + * 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, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +using Framework.Database; +using Game.Entities; +using Game.Network.Packets; +using System.Collections.Generic; +using System.Linq; + +namespace Game +{ + public class WeatherManager : Singleton + { + WeatherManager() { } + + /// Find a Weather object by the given zoneid + public Weather FindWeather(uint id) + { + return m_weathers.LookupByKey(id); + } + + void RemoveWeather(uint id) + { + m_weathers.Remove(id); + } + + public Weather AddWeather(uint zone_id) + { + WeatherData weatherChances = GetWeatherData(zone_id); + + // zone does not have weather, ignore + if (weatherChances == null) + return null; + + Weather w = new Weather(zone_id, weatherChances); + w.ReGenerate(); + w.UpdateWeather(); + + return w; + } + + public void LoadWeatherData() + { + uint oldMSTime = Time.GetMSTime(); + + uint count = 0; + + SQLResult result = DB.World.Query("SELECT zone, spring_rain_chance, spring_snow_chance, spring_storm_chance," + + "summer_rain_chance, summer_snow_chance, summer_storm_chance, fall_rain_chance, fall_snow_chance, fall_storm_chance," + + "winter_rain_chance, winter_snow_chance, winter_storm_chance, ScriptName FROM game_weather"); + + if (result.IsEmpty()) + { + Log.outError(LogFilter.ServerLoading, "Loaded 0 weather definitions. DB table `game_weather` is empty."); + return; + } + + do + { + uint zone_id = result.Read(0); + + WeatherData wzc = new WeatherData(); + + for (byte season = 0; season < 4; ++season) + { + wzc.data[season].rainChance = result.Read(season * (4 - 1) + 1); + wzc.data[season].snowChance = result.Read(season * (4 - 1) + 2); + wzc.data[season].stormChance = result.Read(season * (4 - 1) + 3); + + if (wzc.data[season].rainChance > 100) + { + wzc.data[season].rainChance = 25; + Log.outError(LogFilter.Sql, "Weather for zone {0} season {1} has wrong rain chance > 100%", zone_id, season); + } + + if (wzc.data[season].snowChance > 100) + { + wzc.data[season].snowChance = 25; + Log.outError(LogFilter.Sql, "Weather for zone {0} season {1} has wrong snow chance > 100%", zone_id, season); + } + + if (wzc.data[season].stormChance > 100) + { + wzc.data[season].stormChance = 25; + Log.outError(LogFilter.Sql, "Weather for zone {0} season {1} has wrong storm chance > 100%", zone_id, season); + } + } + + wzc.ScriptId = Global.ObjectMgr.GetScriptId(result.Read(13)); + mWeatherZoneMap[zone_id] = wzc; + ++count; + } + while (result.NextRow()); + + Log.outInfo(LogFilter.ServerLoading, "Loaded {0} weather definitions in {1} ms", count, Time.GetMSTimeDiffToNow(oldMSTime)); + } + + public void SendFineWeatherUpdateToPlayer(Player player) + { + WeatherPkt weather = new WeatherPkt(WeatherState.Fine); + player.SendPacket(weather); + } + + public void Update(uint diff) + { + foreach (var pair in m_weathers.ToList()) + { + if (!pair.Value.Update(diff)) + m_weathers.Remove(pair.Key); + } + } + + WeatherData GetWeatherData(uint zone_id) + { + return mWeatherZoneMap.LookupByKey(zone_id); + } + + Dictionary m_weathers = new Dictionary(); + Dictionary mWeatherZoneMap = new Dictionary(); + } + + public class Weather + { + public Weather(uint zone, WeatherData weatherChances) + { + m_zone = zone; + m_weatherChances = weatherChances; + m_timer.SetInterval(10 * Time.Minute * Time.InMilliseconds); + m_type = WeatherType.Fine; + m_grade = 0; + + //Log.outInfo(LogFilter.General, "WORLD: Starting weather system for zone {0} (change every {1} minutes).", m_zone, (m_timer.GetInterval() / (Time.Minute * Time.InMilliseconds))); + } + + public bool Update(uint diff) + { + if (m_timer.GetCurrent() >= 0) + m_timer.Update(diff); + else + m_timer.SetCurrent(0); + + // If the timer has passed, ReGenerate the weather + if (m_timer.Passed()) + { + m_timer.Reset(); + // update only if Regenerate has changed the weather + if (ReGenerate()) + { + // Weather will be removed if not updated (no players in zone anymore) + if (!UpdateWeather()) + return false; + } + } + + Global.ScriptMgr.OnWeatherUpdate(this, diff); + return true; + } + + public bool ReGenerate() + { + if (m_weatherChances == null) + { + m_type = WeatherType.Fine; + m_grade = 0.0f; + return false; + } + + /// Weather statistics: + // 30% - no change + // 30% - weather gets better (if not fine) or change weather type + // 30% - weather worsens (if not fine) + // 10% - radical change (if not fine) + uint u = RandomHelper.URand(0, 99); + + if (u < 30) + return false; + + // remember old values + WeatherType old_type = m_type; + float old_grade = m_grade; + + long gtime = Global.WorldMgr.GetGameTime(); + var ltime = Time.UnixTimeToDateTime(gtime).ToLocalTime(); + uint season = (uint)((ltime.DayOfYear - 78 + 365) / 91) % 4; + + string[] seasonName = { "spring", "summer", "fall", "winter" }; + + Log.outError(LogFilter.Server, "Generating a change in {0} weather for zone {1}.", seasonName[season], m_zone); + + if ((u < 60) && (m_grade < 0.33333334f)) // Get fair + { + m_type = WeatherType.Fine; + m_grade = 0.0f; + } + + if ((u < 60) && (m_type != WeatherType.Fine)) // Get better + { + m_grade -= 0.33333334f; + return true; + } + + if ((u < 90) && (m_type != WeatherType.Fine)) // Get worse + { + m_grade += 0.33333334f; + return true; + } + + if (m_type != WeatherType.Fine) + { + /// Radical change: + // if light . heavy + // if medium . change weather type + // if heavy . 50% light, 50% change weather type + + if (m_grade < 0.33333334f) + { + m_grade = 0.9999f; // go nuts + return true; + } + else + { + if (m_grade > 0.6666667f) + { + // Severe change, but how severe? + uint rnd = RandomHelper.URand(0, 99); + if (rnd < 50) + { + m_grade -= 0.6666667f; + return true; + } + } + m_type = WeatherType.Fine; // clear up + m_grade = 0; + } + } + + // At this point, only weather that isn't doing anything remains but that have weather data + uint chance1 = m_weatherChances.data[season].rainChance; + uint chance2 = chance1 + m_weatherChances.data[season].snowChance; + uint chance3 = chance2 + m_weatherChances.data[season].stormChance; + uint rn = RandomHelper.URand(1, 100); + if (rn <= chance1) + m_type = WeatherType.Rain; + else if (rn <= chance2) + m_type = WeatherType.Snow; + else if (rn <= chance3) + m_type = WeatherType.Storm; + else + m_type = WeatherType.Fine; + + // New weather statistics (if not fine): + // 85% light + // 7% medium + // 7% heavy + // If fine 100% sun (no fog) + + if (m_type == WeatherType.Fine) + { + m_grade = 0.0f; + } + else if (u < 90) + { + m_grade = (float)RandomHelper.NextDouble() * 0.3333f; + } + else + { + // Severe change, but how severe? + rn = RandomHelper.URand(0, 99); + if (rn < 50) + m_grade = (float)RandomHelper.NextDouble() * 0.3333f + 0.3334f; + else + m_grade = (float)RandomHelper.NextDouble() * 0.3333f + 0.6667f; + } + + // return true only in case weather changes + return m_type != old_type || m_grade != old_grade; + } + + public void SendWeatherUpdateToPlayer(Player player) + { + WeatherPkt weather = new WeatherPkt(GetWeatherState(), m_grade); + player.SendPacket(weather); + } + + public bool UpdateWeather() + { + Player player = Global.WorldMgr.FindPlayerInZone(m_zone); + if (player == null) + return false; + + // Send the weather packet to all players in this zone + if (m_grade >= 1) + m_grade = 0.9999f; + else if (m_grade < 0) + m_grade = 0.0001f; + + WeatherState state = GetWeatherState(); + + WeatherPkt weather = new WeatherPkt(state, m_grade); + + //- Returns false if there were no players found to update + if (!Global.WorldMgr.SendZoneMessage(m_zone, weather)) + return false; + + // Log the event + string wthstr; + switch (state) + { + case WeatherState.Fog: + wthstr = "fog"; + break; + case WeatherState.LightRain: + wthstr = "light rain"; + break; + case WeatherState.MediumRain: + wthstr = "medium rain"; + break; + case WeatherState.HeavyRain: + wthstr = "heavy rain"; + break; + case WeatherState.LightSnow: + wthstr = "light snow"; + break; + case WeatherState.MediumSnow: + wthstr = "medium snow"; + break; + case WeatherState.HeavySnow: + wthstr = "heavy snow"; + break; + case WeatherState.LightSandstorm: + wthstr = "light sandstorm"; + break; + case WeatherState.MediumSandstorm: + wthstr = "medium sandstorm"; + break; + case WeatherState.HeavySandstorm: + wthstr = "heavy sandstorm"; + break; + case WeatherState.Thunders: + wthstr = "thunders"; + break; + case WeatherState.BlackRain: + wthstr = "blackrain"; + break; + case WeatherState.Fine: + default: + wthstr = "fine"; + break; + } + Log.outInfo(LogFilter.Server, "Change the weather of zone {0} to {1}.", m_zone, wthstr); + + Global.ScriptMgr.OnWeatherChange(this, state, m_grade); + return true; + } + + public void SetWeather(WeatherType type, float grade) + { + if (m_type == type && m_grade == grade) + return; + + m_type = type; + m_grade = grade; + UpdateWeather(); + } + + WeatherState GetWeatherState() + { + if (m_grade < 0.27f) + return WeatherState.Fine; + + switch (m_type) + { + case WeatherType.Rain: + if (m_grade < 0.40f) + return WeatherState.LightRain; + else if (m_grade < 0.70f) + return WeatherState.MediumRain; + else + return WeatherState.HeavyRain; + case WeatherType.Snow: + if (m_grade < 0.40f) + return WeatherState.LightSnow; + else if (m_grade < 0.70f) + return WeatherState.MediumSnow; + else + return WeatherState.HeavySnow; + case WeatherType.Storm: + if (m_grade < 0.40f) + return WeatherState.LightSandstorm; + else if (m_grade < 0.70f) + return WeatherState.MediumSandstorm; + else + return WeatherState.HeavySandstorm; + case WeatherType.BlackRain: + return WeatherState.BlackRain; + case WeatherType.Thunders: + return WeatherState.Thunders; + case WeatherType.Fine: + default: + return WeatherState.Fine; + } + } + + public uint GetZone() { return m_zone; } + public uint GetScriptId() { return m_weatherChances.ScriptId; } + + uint m_zone; + WeatherType m_type; + float m_grade; + IntervalTimer m_timer = new IntervalTimer(); + WeatherData m_weatherChances; + } + + public class WeatherData + { + public WeatherSeasonChances[] data = new WeatherSeasonChances[4]; + public uint ScriptId; + } + + public struct WeatherSeasonChances + { + public uint rainChance; + public uint snowChance; + public uint stormChance; + } + + public enum WeatherState + { + Fine = 0, + Fog = 1, // Used in some instance encounters. + LightRain = 3, + MediumRain = 4, + HeavyRain = 5, + LightSnow = 6, + MediumSnow = 7, + HeavySnow = 8, + LightSandstorm = 22, + MediumSandstorm = 41, + HeavySandstorm = 42, + Thunders = 86, + BlackRain = 90, + BlackSnow = 106 + } + + public enum WeatherType + { + Fine = 0, + Rain = 1, + Snow = 2, + Storm = 3, + Thunders = 86, + BlackRain = 90 + } +} diff --git a/LICENSE b/LICENSE new file mode 100644 index 000000000..94a9ed024 --- /dev/null +++ b/LICENSE @@ -0,0 +1,674 @@ + GNU GENERAL PUBLIC LICENSE + Version 3, 29 June 2007 + + Copyright (C) 2007 Free Software Foundation, Inc. + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + Preamble + + The GNU General Public License is a free, copyleft license for +software and other kinds of works. + + The licenses for most software and other practical works are designed +to take away your freedom to share and change the works. By contrast, +the GNU General Public License is intended to guarantee your freedom to +share and change all versions of a program--to make sure it remains free +software for all its users. We, the Free Software Foundation, use the +GNU General Public License for most of our software; it applies also to +any other work released this way by its authors. You can apply it to +your programs, too. + + When we speak of free software, we are referring to freedom, not +price. Our General Public Licenses are designed to make sure that you +have the freedom to distribute copies of free software (and charge for +them if you wish), that you receive source code or can get it if you +want it, that you can change the software or use pieces of it in new +free programs, and that you know you can do these things. + + To protect your rights, we need to prevent others from denying you +these rights or asking you to surrender the rights. Therefore, you have +certain responsibilities if you distribute copies of the software, or if +you modify it: responsibilities to respect the freedom of others. + + For example, if you distribute copies of such a program, whether +gratis or for a fee, you must pass on to the recipients the same +freedoms that you received. You must make sure that they, too, receive +or can get the source code. And you must show them these terms so they +know their rights. + + Developers that use the GNU GPL protect your rights with two steps: +(1) assert copyright on the software, and (2) offer you this License +giving you legal permission to copy, distribute and/or modify it. + + For the developers' and authors' protection, the GPL clearly explains +that there is no warranty for this free software. For both users' and +authors' sake, the GPL requires that modified versions be marked as +changed, so that their problems will not be attributed erroneously to +authors of previous versions. + + Some devices are designed to deny users access to install or run +modified versions of the software inside them, although the manufacturer +can do so. This is fundamentally incompatible with the aim of +protecting users' freedom to change the software. The systematic +pattern of such abuse occurs in the area of products for individuals to +use, which is precisely where it is most unacceptable. Therefore, we +have designed this version of the GPL to prohibit the practice for those +products. If such problems arise substantially in other domains, we +stand ready to extend this provision to those domains in future versions +of the GPL, as needed to protect the freedom of users. + + Finally, every program is threatened constantly by software patents. +States should not allow patents to restrict development and use of +software on general-purpose computers, but in those that do, we wish to +avoid the special danger that patents applied to a free program could +make it effectively proprietary. To prevent this, the GPL assures that +patents cannot be used to render the program non-free. + + The precise terms and conditions for copying, distribution and +modification follow. + + TERMS AND CONDITIONS + + 0. Definitions. + + "This License" refers to version 3 of the GNU General Public License. + + "Copyright" also means copyright-like laws that apply to other kinds of +works, such as semiconductor masks. + + "The Program" refers to any copyrightable work licensed under this +License. Each licensee is addressed as "you". "Licensees" and +"recipients" may be individuals or organizations. + + To "modify" a work means to copy from or adapt all or part of the work +in a fashion requiring copyright permission, other than the making of an +exact copy. The resulting work is called a "modified version" of the +earlier work or a work "based on" the earlier work. + + A "covered work" means either the unmodified Program or a work based +on the Program. + + To "propagate" a work means to do anything with it that, without +permission, would make you directly or secondarily liable for +infringement under applicable copyright law, except executing it on a +computer or modifying a private copy. Propagation includes copying, +distribution (with or without modification), making available to the +public, and in some countries other activities as well. + + To "convey" a work means any kind of propagation that enables other +parties to make or receive copies. Mere interaction with a user through +a computer network, with no transfer of a copy, is not conveying. + + An interactive user interface displays "Appropriate Legal Notices" +to the extent that it includes a convenient and prominently visible +feature that (1) displays an appropriate copyright notice, and (2) +tells the user that there is no warranty for the work (except to the +extent that warranties are provided), that licensees may convey the +work under this License, and how to view a copy of this License. If +the interface presents a list of user commands or options, such as a +menu, a prominent item in the list meets this criterion. + + 1. Source Code. + + The "source code" for a work means the preferred form of the work +for making modifications to it. "Object code" means any non-source +form of a work. + + A "Standard Interface" means an interface that either is an official +standard defined by a recognized standards body, or, in the case of +interfaces specified for a particular programming language, one that +is widely used among developers working in that language. + + The "System Libraries" of an executable work include anything, other +than the work as a whole, that (a) is included in the normal form of +packaging a Major Component, but which is not part of that Major +Component, and (b) serves only to enable use of the work with that +Major Component, or to implement a Standard Interface for which an +implementation is available to the public in source code form. A +"Major Component", in this context, means a major essential component +(kernel, window system, and so on) of the specific operating system +(if any) on which the executable work runs, or a compiler used to +produce the work, or an object code interpreter used to run it. + + The "Corresponding Source" for a work in object code form means all +the source code needed to generate, install, and (for an executable +work) run the object code and to modify the work, including scripts to +control those activities. However, it does not include the work's +System Libraries, or general-purpose tools or generally available free +programs which are used unmodified in performing those activities but +which are not part of the work. For example, Corresponding Source +includes interface definition files associated with source files for +the work, and the source code for shared libraries and dynamically +linked subprograms that the work is specifically designed to require, +such as by intimate data communication or control flow between those +subprograms and other parts of the work. + + The Corresponding Source need not include anything that users +can regenerate automatically from other parts of the Corresponding +Source. + + The Corresponding Source for a work in source code form is that +same work. + + 2. Basic Permissions. + + All rights granted under this License are granted for the term of +copyright on the Program, and are irrevocable provided the stated +conditions are met. This License explicitly affirms your unlimited +permission to run the unmodified Program. The output from running a +covered work is covered by this License only if the output, given its +content, constitutes a covered work. This License acknowledges your +rights of fair use or other equivalent, as provided by copyright law. + + You may make, run and propagate covered works that you do not +convey, without conditions so long as your license otherwise remains +in force. You may convey covered works to others for the sole purpose +of having them make modifications exclusively for you, or provide you +with facilities for running those works, provided that you comply with +the terms of this License in conveying all material for which you do +not control copyright. Those thus making or running the covered works +for you must do so exclusively on your behalf, under your direction +and control, on terms that prohibit them from making any copies of +your copyrighted material outside their relationship with you. + + Conveying under any other circumstances is permitted solely under +the conditions stated below. Sublicensing is not allowed; section 10 +makes it unnecessary. + + 3. Protecting Users' Legal Rights From Anti-Circumvention Law. + + No covered work shall be deemed part of an effective technological +measure under any applicable law fulfilling obligations under article +11 of the WIPO copyright treaty adopted on 20 December 1996, or +similar laws prohibiting or restricting circumvention of such +measures. + + When you convey a covered work, you waive any legal power to forbid +circumvention of technological measures to the extent such circumvention +is effected by exercising rights under this License with respect to +the covered work, and you disclaim any intention to limit operation or +modification of the work as a means of enforcing, against the work's +users, your or third parties' legal rights to forbid circumvention of +technological measures. + + 4. Conveying Verbatim Copies. + + You may convey verbatim copies of the Program's source code as you +receive it, in any medium, provided that you conspicuously and +appropriately publish on each copy an appropriate copyright notice; +keep intact all notices stating that this License and any +non-permissive terms added in accord with section 7 apply to the code; +keep intact all notices of the absence of any warranty; and give all +recipients a copy of this License along with the Program. + + You may charge any price or no price for each copy that you convey, +and you may offer support or warranty protection for a fee. + + 5. Conveying Modified Source Versions. + + You may convey a work based on the Program, or the modifications to +produce it from the Program, in the form of source code under the +terms of section 4, provided that you also meet all of these conditions: + + a) The work must carry prominent notices stating that you modified + it, and giving a relevant date. + + b) The work must carry prominent notices stating that it is + released under this License and any conditions added under section + 7. This requirement modifies the requirement in section 4 to + "keep intact all notices". + + c) You must license the entire work, as a whole, under this + License to anyone who comes into possession of a copy. This + License will therefore apply, along with any applicable section 7 + additional terms, to the whole of the work, and all its parts, + regardless of how they are packaged. This License gives no + permission to license the work in any other way, but it does not + invalidate such permission if you have separately received it. + + d) If the work has interactive user interfaces, each must display + Appropriate Legal Notices; however, if the Program has interactive + interfaces that do not display Appropriate Legal Notices, your + work need not make them do so. + + A compilation of a covered work with other separate and independent +works, which are not by their nature extensions of the covered work, +and which are not combined with it such as to form a larger program, +in or on a volume of a storage or distribution medium, is called an +"aggregate" if the compilation and its resulting copyright are not +used to limit the access or legal rights of the compilation's users +beyond what the individual works permit. Inclusion of a covered work +in an aggregate does not cause this License to apply to the other +parts of the aggregate. + + 6. Conveying Non-Source Forms. + + You may convey a covered work in object code form under the terms +of sections 4 and 5, provided that you also convey the +machine-readable Corresponding Source under the terms of this License, +in one of these ways: + + a) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by the + Corresponding Source fixed on a durable physical medium + customarily used for software interchange. + + b) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by a + written offer, valid for at least three years and valid for as + long as you offer spare parts or customer support for that product + model, to give anyone who possesses the object code either (1) a + copy of the Corresponding Source for all the software in the + product that is covered by this License, on a durable physical + medium customarily used for software interchange, for a price no + more than your reasonable cost of physically performing this + conveying of source, or (2) access to copy the + Corresponding Source from a network server at no charge. + + c) Convey individual copies of the object code with a copy of the + written offer to provide the Corresponding Source. This + alternative is allowed only occasionally and noncommercially, and + only if you received the object code with such an offer, in accord + with subsection 6b. + + d) Convey the object code by offering access from a designated + place (gratis or for a charge), and offer equivalent access to the + Corresponding Source in the same way through the same place at no + further charge. You need not require recipients to copy the + Corresponding Source along with the object code. If the place to + copy the object code is a network server, the Corresponding Source + may be on a different server (operated by you or a third party) + that supports equivalent copying facilities, provided you maintain + clear directions next to the object code saying where to find the + Corresponding Source. Regardless of what server hosts the + Corresponding Source, you remain obligated to ensure that it is + available for as long as needed to satisfy these requirements. + + e) Convey the object code using peer-to-peer transmission, provided + you inform other peers where the object code and Corresponding + Source of the work are being offered to the general public at no + charge under subsection 6d. + + A separable portion of the object code, whose source code is excluded +from the Corresponding Source as a System Library, need not be +included in conveying the object code work. + + A "User Product" is either (1) a "consumer product", which means any +tangible personal property which is normally used for personal, family, +or household purposes, or (2) anything designed or sold for incorporation +into a dwelling. In determining whether a product is a consumer product, +doubtful cases shall be resolved in favor of coverage. For a particular +product received by a particular user, "normally used" refers to a +typical or common use of that class of product, regardless of the status +of the particular user or of the way in which the particular user +actually uses, or expects or is expected to use, the product. A product +is a consumer product regardless of whether the product has substantial +commercial, industrial or non-consumer uses, unless such uses represent +the only significant mode of use of the product. + + "Installation Information" for a User Product means any methods, +procedures, authorization keys, or other information required to install +and execute modified versions of a covered work in that User Product from +a modified version of its Corresponding Source. The information must +suffice to ensure that the continued functioning of the modified object +code is in no case prevented or interfered with solely because +modification has been made. + + If you convey an object code work under this section in, or with, or +specifically for use in, a User Product, and the conveying occurs as +part of a transaction in which the right of possession and use of the +User Product is transferred to the recipient in perpetuity or for a +fixed term (regardless of how the transaction is characterized), the +Corresponding Source conveyed under this section must be accompanied +by the Installation Information. But this requirement does not apply +if neither you nor any third party retains the ability to install +modified object code on the User Product (for example, the work has +been installed in ROM). + + The requirement to provide Installation Information does not include a +requirement to continue to provide support service, warranty, or updates +for a work that has been modified or installed by the recipient, or for +the User Product in which it has been modified or installed. Access to a +network may be denied when the modification itself materially and +adversely affects the operation of the network or violates the rules and +protocols for communication across the network. + + Corresponding Source conveyed, and Installation Information provided, +in accord with this section must be in a format that is publicly +documented (and with an implementation available to the public in +source code form), and must require no special password or key for +unpacking, reading or copying. + + 7. Additional Terms. + + "Additional permissions" are terms that supplement the terms of this +License by making exceptions from one or more of its conditions. +Additional permissions that are applicable to the entire Program shall +be treated as though they were included in this License, to the extent +that they are valid under applicable law. If additional permissions +apply only to part of the Program, that part may be used separately +under those permissions, but the entire Program remains governed by +this License without regard to the additional permissions. + + When you convey a copy of a covered work, you may at your option +remove any additional permissions from that copy, or from any part of +it. (Additional permissions may be written to require their own +removal in certain cases when you modify the work.) You may place +additional permissions on material, added by you to a covered work, +for which you have or can give appropriate copyright permission. + + Notwithstanding any other provision of this License, for material you +add to a covered work, you may (if authorized by the copyright holders of +that material) supplement the terms of this License with terms: + + a) Disclaiming warranty or limiting liability differently from the + terms of sections 15 and 16 of this License; or + + b) Requiring preservation of specified reasonable legal notices or + author attributions in that material or in the Appropriate Legal + Notices displayed by works containing it; or + + c) Prohibiting misrepresentation of the origin of that material, or + requiring that modified versions of such material be marked in + reasonable ways as different from the original version; or + + d) Limiting the use for publicity purposes of names of licensors or + authors of the material; or + + e) Declining to grant rights under trademark law for use of some + trade names, trademarks, or service marks; or + + f) Requiring indemnification of licensors and authors of that + material by anyone who conveys the material (or modified versions of + it) with contractual assumptions of liability to the recipient, for + any liability that these contractual assumptions directly impose on + those licensors and authors. + + All other non-permissive additional terms are considered "further +restrictions" within the meaning of section 10. If the Program as you +received it, or any part of it, contains a notice stating that it is +governed by this License along with a term that is a further +restriction, you may remove that term. If a license document contains +a further restriction but permits relicensing or conveying under this +License, you may add to a covered work material governed by the terms +of that license document, provided that the further restriction does +not survive such relicensing or conveying. + + If you add terms to a covered work in accord with this section, you +must place, in the relevant source files, a statement of the +additional terms that apply to those files, or a notice indicating +where to find the applicable terms. + + Additional terms, permissive or non-permissive, may be stated in the +form of a separately written license, or stated as exceptions; +the above requirements apply either way. + + 8. Termination. + + You may not propagate or modify a covered work except as expressly +provided under this License. Any attempt otherwise to propagate or +modify it is void, and will automatically terminate your rights under +this License (including any patent licenses granted under the third +paragraph of section 11). + + However, if you cease all violation of this License, then your +license from a particular copyright holder is reinstated (a) +provisionally, unless and until the copyright holder explicitly and +finally terminates your license, and (b) permanently, if the copyright +holder fails to notify you of the violation by some reasonable means +prior to 60 days after the cessation. + + Moreover, your license from a particular copyright holder is +reinstated permanently if the copyright holder notifies you of the +violation by some reasonable means, this is the first time you have +received notice of violation of this License (for any work) from that +copyright holder, and you cure the violation prior to 30 days after +your receipt of the notice. + + Termination of your rights under this section does not terminate the +licenses of parties who have received copies or rights from you under +this License. If your rights have been terminated and not permanently +reinstated, you do not qualify to receive new licenses for the same +material under section 10. + + 9. Acceptance Not Required for Having Copies. + + You are not required to accept this License in order to receive or +run a copy of the Program. Ancillary propagation of a covered work +occurring solely as a consequence of using peer-to-peer transmission +to receive a copy likewise does not require acceptance. However, +nothing other than this License grants you permission to propagate or +modify any covered work. These actions infringe copyright if you do +not accept this License. Therefore, by modifying or propagating a +covered work, you indicate your acceptance of this License to do so. + + 10. Automatic Licensing of Downstream Recipients. + + Each time you convey a covered work, the recipient automatically +receives a license from the original licensors, to run, modify and +propagate that work, subject to this License. You are not responsible +for enforcing compliance by third parties with this License. + + An "entity transaction" is a transaction transferring control of an +organization, or substantially all assets of one, or subdividing an +organization, or merging organizations. If propagation of a covered +work results from an entity transaction, each party to that +transaction who receives a copy of the work also receives whatever +licenses to the work the party's predecessor in interest had or could +give under the previous paragraph, plus a right to possession of the +Corresponding Source of the work from the predecessor in interest, if +the predecessor has it or can get it with reasonable efforts. + + You may not impose any further restrictions on the exercise of the +rights granted or affirmed under this License. For example, you may +not impose a license fee, royalty, or other charge for exercise of +rights granted under this License, and you may not initiate litigation +(including a cross-claim or counterclaim in a lawsuit) alleging that +any patent claim is infringed by making, using, selling, offering for +sale, or importing the Program or any portion of it. + + 11. Patents. + + A "contributor" is a copyright holder who authorizes use under this +License of the Program or a work on which the Program is based. The +work thus licensed is called the contributor's "contributor version". + + A contributor's "essential patent claims" are all patent claims +owned or controlled by the contributor, whether already acquired or +hereafter acquired, that would be infringed by some manner, permitted +by this License, of making, using, or selling its contributor version, +but do not include claims that would be infringed only as a +consequence of further modification of the contributor version. For +purposes of this definition, "control" includes the right to grant +patent sublicenses in a manner consistent with the requirements of +this License. + + Each contributor grants you a non-exclusive, worldwide, royalty-free +patent license under the contributor's essential patent claims, to +make, use, sell, offer for sale, import and otherwise run, modify and +propagate the contents of its contributor version. + + In the following three paragraphs, a "patent license" is any express +agreement or commitment, however denominated, not to enforce a patent +(such as an express permission to practice a patent or covenant not to +sue for patent infringement). To "grant" such a patent license to a +party means to make such an agreement or commitment not to enforce a +patent against the party. + + If you convey a covered work, knowingly relying on a patent license, +and the Corresponding Source of the work is not available for anyone +to copy, free of charge and under the terms of this License, through a +publicly available network server or other readily accessible means, +then you must either (1) cause the Corresponding Source to be so +available, or (2) arrange to deprive yourself of the benefit of the +patent license for this particular work, or (3) arrange, in a manner +consistent with the requirements of this License, to extend the patent +license to downstream recipients. "Knowingly relying" means you have +actual knowledge that, but for the patent license, your conveying the +covered work in a country, or your recipient's use of the covered work +in a country, would infringe one or more identifiable patents in that +country that you have reason to believe are valid. + + If, pursuant to or in connection with a single transaction or +arrangement, you convey, or propagate by procuring conveyance of, a +covered work, and grant a patent license to some of the parties +receiving the covered work authorizing them to use, propagate, modify +or convey a specific copy of the covered work, then the patent license +you grant is automatically extended to all recipients of the covered +work and works based on it. + + A patent license is "discriminatory" if it does not include within +the scope of its coverage, prohibits the exercise of, or is +conditioned on the non-exercise of one or more of the rights that are +specifically granted under this License. You may not convey a covered +work if you are a party to an arrangement with a third party that is +in the business of distributing software, under which you make payment +to the third party based on the extent of your activity of conveying +the work, and under which the third party grants, to any of the +parties who would receive the covered work from you, a discriminatory +patent license (a) in connection with copies of the covered work +conveyed by you (or copies made from those copies), or (b) primarily +for and in connection with specific products or compilations that +contain the covered work, unless you entered into that arrangement, +or that patent license was granted, prior to 28 March 2007. + + Nothing in this License shall be construed as excluding or limiting +any implied license or other defenses to infringement that may +otherwise be available to you under applicable patent law. + + 12. No Surrender of Others' Freedom. + + If conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot convey a +covered work so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you may +not convey it at all. For example, if you agree to terms that obligate you +to collect a royalty for further conveying from those to whom you convey +the Program, the only way you could satisfy both those terms and this +License would be to refrain entirely from conveying the Program. + + 13. Use with the GNU Affero General Public License. + + Notwithstanding any other provision of this License, you have +permission to link or combine any covered work with a work licensed +under version 3 of the GNU Affero General Public License into a single +combined work, and to convey the resulting work. The terms of this +License will continue to apply to the part which is the covered work, +but the special requirements of the GNU Affero General Public License, +section 13, concerning interaction through a network will apply to the +combination as such. + + 14. Revised Versions of this License. + + The Free Software Foundation may publish revised and/or new versions of +the GNU General Public License from time to time. Such new versions will +be similar in spirit to the present version, but may differ in detail to +address new problems or concerns. + + Each version is given a distinguishing version number. If the +Program specifies that a certain numbered version of the GNU General +Public License "or any later version" applies to it, you have the +option of following the terms and conditions either of that numbered +version or of any later version published by the Free Software +Foundation. If the Program does not specify a version number of the +GNU General Public License, you may choose any version ever published +by the Free Software Foundation. + + If the Program specifies that a proxy can decide which future +versions of the GNU General Public License can be used, that proxy's +public statement of acceptance of a version permanently authorizes you +to choose that version for the Program. + + Later license versions may give you additional or different +permissions. However, no additional obligations are imposed on any +author or copyright holder as a result of your choosing to follow a +later version. + + 15. Disclaimer of Warranty. + + THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY +APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT +HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY +OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, +THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR +PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM +IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF +ALL NECESSARY SERVICING, REPAIR OR CORRECTION. + + 16. Limitation of Liability. + + IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING +WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS +THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY +GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE +USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF +DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD +PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), +EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF +SUCH DAMAGES. + + 17. Interpretation of Sections 15 and 16. + + If the disclaimer of warranty and limitation of liability provided +above cannot be given local legal effect according to their terms, +reviewing courts shall apply local law that most closely approximates +an absolute waiver of all civil liability in connection with the +Program, unless a warranty or assumption of liability accompanies a +copy of the Program in return for a fee. + + END OF TERMS AND CONDITIONS + + How to Apply These Terms to Your New Programs + + If you develop a new program, and you want it to be of the greatest +possible use to the public, the best way to achieve this is to make it +free software which everyone can redistribute and change under these terms. + + To do so, attach the following notices to the program. It is safest +to attach them to the start of each source file to most effectively +state the exclusion of warranty; and each file should have at least +the "copyright" line and a pointer to where the full notice is found. + + + Copyright (C) + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . + +Also add information on how to contact you by electronic and paper mail. + + If the program does terminal interaction, make it output a short +notice like this when it starts in an interactive mode: + + Copyright (C) + This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'. + This is free software, and you are welcome to redistribute it + under certain conditions; type `show c' for details. + +The hypothetical commands `show w' and `show c' should show the appropriate +parts of the General Public License. Of course, your program's commands +might be different; for a GUI interface, you would use an "about box". + + You should also get your employer (if you work as a programmer) or school, +if any, to sign a "copyright disclaimer" for the program, if necessary. +For more information on this, and how to apply and follow the GNU GPL, see +. + + The GNU General Public License does not permit incorporating your program +into proprietary programs. If your program is a subroutine library, you +may consider it more useful to permit linking proprietary applications with +the library. If this is what you want to do, use the GNU Lesser General +Public License instead of this License. But first, please read +. diff --git a/Libs/Google.Protobuf.dll b/Libs/Google.Protobuf.dll new file mode 100644 index 0000000000000000000000000000000000000000..f9f0b48c0673ee98c1d977ea7420b2610942a158 GIT binary patch literal 296448 zcmce<37lO;l|Np&_w{?-?{#3i!|ojP^SsZ*y;ovM4^tJc2N_{NwV{$6^?n9t+Rf2-xX^UEZP+xs7GH=k{Ja?$5! zo$%zM(_V9NZQ$}KyeQgm*}!=lHg68M3~an$AlkZl;Nr~#N1XKPfy=`4FBoZSEADPl zpM0b-C(QEApD*hjbFKZ%EFLJ$T4qcgROL1I9rqzUfb@GvshJ~bPw8fcw-P zU=Dn(ej`EoKe^jSX5rrvz%BQMzp-p*&jZFZr^=i0d`#+j zWXlCtY{Bz)CM;j%6?CWn4H&b2B&tQ{Aya4rjRybCBrnOo)#$E~=z>c_0FqYo6^rZo zDR74+wKR1F<%)l#%`;8riGpVyf177oBsaNiduPr}CXX|wI+rz>)hL~1Oxd*O`dfE+ z#r)753VyVo>#jkPuxcI4VxhJRxQ zJD26m5O6^(Wm747w_e<}rc~`VUjDXLFYgs@yVol;-)r(+W5ohW@>=m{s5eXGfbJ|C zuLcJ5#j42<9_9u6N&B8ZfwJ5na1*wKyTI1bQcgf6za9Cg3>@PGzx84*LYZe~qpV5F z0%&NyvSU_KuYxjUx_aUMXmrb`jOmN(&qARwg#;!T1&l2g{7X=-^hXfo<-q|cA8zx4 zmADH>)usULD`S8qA8c3aBuysV6Q;>51Pt^NV!vf0da3C7`ARN86M2&t*izLLAQrv?^uAdMI=CYLH6`uWznLIru@jkFL!RHJPqb zb&EHY_vh}IRh`w<+KjA9sk*_@?OPq$F$=oT)}p0{S2}amL8E2m+-(Ysgg|vRm75f< z&y;grW1YEj&I_qdJ93@*a(?*hXcN6bInacuzWKpCKsbeS{bb9wqCY$Vh-K}W5!6Lr zludtE(du%k(psHUXuhq@bn#L?#uCly2hiW@KbQJooY$9~`wb@J+CJ z6$H*c2N$%Lq>KFuA#$j(r(3IomR8RThEPTOzf1c+uS#JdO*7B|U(W0?3-<&c1U6ti zMd;K>^hcyn8vPrpsjRvoKMGU&vbFhuccKJEqCE;hGt)IDiUdp4)-o^&2v{6iqmuDj z1iz_nG%fwttn1A0r0$Y7SGQ0u49zv=e9MB3>rthwY?sMQju??}sX7EqPg_lN z3)L9Ninc~@(kzoN6^?|ML^qe8`3V_nEcY} zZeFmDBznDIH4?2}b8sr|rrM&!FRfY~^3BJ97j_%8dE*@4=l~h!^5BhAH+N7AF>`>u z?Z9$Xbp?b&g(=e7v)5SkEf(d4gvk4v}AtA;4nQiROB`@Pa8a5fb`+*wi6O!rB;EX&LCz=YYk0G9Gpu--_JKqmFoJ{qs@gXys6* z0ovbGI-9GQ%Y+q2D6M*vDYRAlO&M!I&IWy$k&5Nw@DGFUfV-wO2>9h7ph!|u z|9p%=rT%hIaOO;P#p^R&u`>iy`iH&1{wYrBpHjIL_fHZ1gN|YUw3b_^@1LBx51hc? z=DHn(S1p>RMA+Fjgw`57-sa zL)HB)DrysuiR0j=#d0CRL1q+3#o}0PHR?;MQRSvV&`4kFINS#;t#CC*ta4)F1a+J8 z{fB_OQbHa~b>0-PN~+`+j1{3RM2z7x{0)m(*G|4!0lbS+<5qRvj=WZVYReZEZd~8o zKWF3mmi}%yPj>O;=GMhY{mn(Kve{zTY*B2+8S{iqG`U3HB?=Aglym(98`qbc z`U70^{mXDI^pB$bW-R_>ZuMpTo|E>w&iA_3U9iC#KGFu;W#?CxZHfz7c09^` z{Ei%)DLA#K_=!`iQWe%M%}2vwjv0$(q?1mU72D)&8Jh;Xpy?6bB)hPM(ntPit3>lU(&T1N<3ZsaaeofT*56w z3N2s>@5cDkRH81Y4>_|E?Cdc`K3v^m?bRhhjhzdfCQ1dn@G0D8`=t--*nfs^84wTk z6}6qdI{GO5F&{HbtW%3AMp04`<{InCsrRogI+UG)uqAdiHnhU{5;$l}`#dcbU!x?} z>+#jiYw665E&^%5q+Ot9=75L5wnf9$S*n#zh%Rv+%lS&V5F;j-F2Ss1s-t#ltd#SV zIRP|2>z>AdPxCVsjbAQ+W5Vz7*f+xuYO{ifg|<;9b(x8n(&!n`W;& zmD|e|guAf3EE-LZb+#J-!9Wy;pMHLJ$Gjbt&%m<}_aewo%WZDHn1w;@nb&&D-{;XZ z%e?k0-1q#yqt_j7oo{v5R4+qEq*SwQmp$F$TXVA9b|C_-;+MJE-Sb=41h3|tU~%DD z`wF2N`h*__zk_NIr~={1kP(LS5f-0BnxHr=TOtqsrm^r83Q{vAuYgQhFQ;v1^)xd; zH{MUwt*yGRt=3X$x%FZ#5f!6NwE9J-+L7;Vd8xI;Yaj+GaBE-?$Mr!f-;-E>Rq|oiFukZ@3@$?5QZ;Y-izTiws4J=Vc0of- zSC^y9lQ`fI+tD>?c&f-fxDf5+$5L%1^oL(f==T^G;XV)O*Zh38D(LeJ*5-B{B>{4Q z&{+A{baEh%)8&t)^J898GLG|7a)6AI&tQnbC(eA1!G<+G;jErP2FszWGnhmubHl z4UWE%Ntahe~h?0%1vqtwczl`w5qzn_w#*j-%{zC$PZI z!SO@fhQW4(E^uoan?MXD=XFhBiJu$%MGWpo{|kqQF{!Fn%xT4nt$6FjTCqth9_IOV zC~&S4T&@ith3!z?KnO0?90!t{0@CC_WE|`bNyF5mPsd5I#?rg&lNc~>oWyU!z(e$0 zlA;n=g88(b#Q-xXf;7Oxwa=$<$6Ny6CpeA6*2Uf6Kej+};1GiH#zhQ*ZB4J(5@^ij z$QN;GGi@#Kt=L{mzJi_E8H&1QF6y4Ss0Vw)RYXVfmEPcX6&ON=ey~kgp?%mLe8BZl zA5O;-;QiocfvzI1?Qc?WGbmM63X;4R-EE4Iy7+lElo zbp*-C1)Fwg7jg>`&IwP4FlG4eMVEJh6P1XtWz260&j7&F_ZkSTt%du2m^6leY^sZF zLls3~#VFOnGf@Q&t%@6=F$&vYKirbG3dNmGUb!i_O_&}$7aWO2RcaB?drTOUd~~bj z0{Q4hsDubZ1g6Q?Nv^HTQ*5=_ysy$#JfmcG~N;`aiE)G?1QZe;U$-c1FRfq+N0KV&z5qBPU6gfe6d0=Px`zPWI19rMq3)RHH2S zsZM)3Yok&nKs#d@+nKo#4d1KhR@Py)lj31vnuqgV5f4z80ml#0K`Om0l^%6zGfI8` zzs~lIV*|v`EHH30b0OH9xe)w9)S@qp&7@>)wT12!!aBhnv|=-gQ@rM;{eTalcDfE4Qq@b@N_L^1D~!$Y znhI-h9!jC5vSp&ljRSQI`{H&huE{3bmU9oV`5$8Uy$A-xE@(`zEWC7W^!oeHOULxu z0R}e(m05O3sw`(HW0_x_-$C&fOl=Dybl6rE?@45UfD!rCX0e~k2WdMsM|;7Y>=bXD zZk0~f+pAuLGi~zRK+>fG1BqI?g1doJ_vR6hD&<^wOyhU3|4v8LZzBB^ zQ@TswVnk=f6$^uB$eE)_?#jstheI)QJT3&<0EDwQTV< zEIvnyS%Nm!u?>Vz(FT%@Z6MqLKDK0$OcKd@LBf*y)@<<`SiC`sS%Nk|e7?3(+Bk1& z8_*xL@wzOMYl-B1LBf*y>$AnzvG@WhW(nGWT=TUHr46j48u`#RuE-*}K52s`^((W* zZ%o=?3EBWXGqiC$`eT3Lf_nsH?jC)DP~EpSKAa5&weIerTyw` z#WyFkv!p(eExs|Koh86~Nd_1o<9NtQ|v5iPJ19-_KOX_dP7T?0+P>NZCHqK-lSRbK{*Ur?&wOJ&$CT*~! zeqFZsElC?JK^st}8QNeU93XsLpGERkB8h}hmek*vExwJ#H7RDvLa(+ZCf@a}%IAh` z#oH6wSyF#fw)kxc?JNP_tr@(OyVCpSY{j=H^s=OWW48E?gkF|FCcu=hy$;B|+Uw&c z;=WxeGPx;>kSS4+2%ObgpNUjzn zEUDj;Exwz@6H?3)v~e2Ss7o8yWZO6bvN{lL)Fzpq&rQ4m_q;!GEp9k#na5hLSi7Ei zJMwR2`UXwEiD?8lAekF+m1Q*h6z0twM0__Pmxyl0ortg?QbhGzkdjFO%go}AJk~CX zb;%@4>Tk^!W4)#gEtzCVsyXySlk86(%=X}w6wwfmKvXBNBqPCCiN(aj)rpCJ2h&1) z$E+RmUO>#aP`eX(@qU!zJ%o7gLVk?*Zae_*J*@vuP2bD(eVTq3)9=Q`Bi>&L-uK`! z#_JgG5KBqn(cfF?niPvCXiW@3f_lDterdcjLZWBnS4K? zt0O6RFzzMrh(VN%oPtil51v|QD?`{MOG5Tn%IT%TIj9KKv8z6T%;G z`&{c#gq<`=b};jX50h3qC=^_%EP% zl7*?qWq2HT55#;6N8OT2^j9Fee@RzVXyd&*nkV<3Xg9g{MtjS>FFHi-^P^YEeL-}V z+!sa{%e}uP3N=xTwrZjjjccMcx=|Bt(OWdp9^Ijd+0pwnF(>-8Cd$#5HPI10sfo_$ zCz_ZW{Z13}qQ7XO5;e7x>i(_>8)Qs$M~gJk6YZsm-sm7r^hL*OVt%wn6APk?G_f$+ zs)_#SI!z47Aw3{m}uXTyYC2B6Q#U-}7#Oqw*^)7LROI+y^ zSGmNvOI+;|6E0DAiEE-Si0B3;VS%D=X!;FI|4`G{GW|>HRegugye9YWgOoU#)5QYk1v5)9~3$6lxm2nTaMe4adzy zH)|U1n~Cn$G#oS&eNfZz+f4L1O~W@c(N{GM*UUsuX&U~SiC)k&{4*2%yQblvndtAD zhF@l)mN~)~d@&PsX&SDWiI!*@{+NkYXc}&ri4N5?d@~cBq-nTlCOSvc@Y76miKgMM zndnNUC-=rIfHQeGyvGNZ zdUZ}GJ7#6xse_+n-#_u?{jMW8pVz($EPTx@_%Yp%eE2w?b(505^wA)#iEw`OCJ-3p z;My+DMRzid{OVGl74qRXn=l9G;U9WA&GCa@5T69UbAIp>dC*wT0^lqVrJXf40dqf-Q>fs z(S@lFgZu|TvOdDKsRTb|Lwbk3urK_438&4Q?V%OH-GXC=IKpl8+)BUs+OOm_ zOz~ge2Y#U!JUd#w1`f6CmEjQRq05Vf{5z6<<+dgARMwD?{+&1<#|Xvj7Hg zc!K`+*erQ}mh^Vv#k#IGtyB9ZsOrpBI-5p&0Aa8*Clv5}Sh3YT^{<1Zxi3E7bQK}p zZ=(=VOz8=X)HXBv1JrGiGjNyTy*vldyl!Y|VQ8S-)HSBf?NtU!%Onq?@~|E@Guf*= z3tIs$yk2N&2%iMNQf?k5dBJy)rv=sXOm$tJ$9#tl1y`K6Dv>3$?0L2)6?Pw77M2DL^gMTL1-h=mAs}P}GG4ZU_5FvE2?h>cx>A$XkiO zN&MCDcOm{xwD+~R!$k22qT#H-zq$BhK4A!-zK?Q>@E_QOhJ~(M`N;RWJz2xbTbr%b zaevDOKqO|lwfY+WflH9J8{p+150#$gKX8FXR6WLj;8K@9(0>3XZ5Gc*v{v`>AJ`l} zEcPD=@vxXESlGcgSzD{62=8EDUV94Cx=POd=FD3>!CCNE2Rin9y#TR+{5Iy3?ZwtfRl{TMc^y=K1dHSAd710&FF=KfKny`irymNv6TZ7 zb}gnBzuYn{rWEr$_M6*!kfM??cpw&XpM|mJ&?7GxCV1_@FPE%1s=}Bkgf6^)F5H<> z*hQdXx05<5k)^J!+&1(&%U*OzhO^H0a(fgq>j3aMJReW5kDpj}qkE#9W|wCV^(Sol z<#v@!!cVmXN^LHt>(SiSlIla}obnu)a4)xo8NT`lrrKqq`_Muz{2`j>6kJs0QG&t2R1^z1mpPhiem<6ZW zrNIAHz<1N}^n2Auft&Ok#%6QM;c5w=fHoDv)u`H;OS{2-gz5A0sZJu60>3BSE#lOR zql07dDKFEHPMNkM4gi;P&h5ubUT{Xc#Lr%e>M8e{@zqBm_mg%h_m?S{SHe}6k#nYH zBsPwIVY>M*!wmS~TH$|jKbicbY1!Ebkf{**A{12lDsFtyKRVIW2#Ss2in zetur_)^1QE?k&udT_X4e_!I0=_!C?{#Y4)j7Y;CYJtfD6Wh^85Y)(n__4lBVmGD{6 zpIR3c^K^Ikl6mx#mui`cc-}9T5v`WzwWc@Y#-RI{ORL1ajhp_H(GUnR!MzjGLza?&pc39VBU1q;m zsfnOs+#z8%N-Ky*||r~4rqM+aM|UgJU8m}fNMw`zy^=4%~TI?<+O~P zR-smJgAmnKiGGZv#oXw|_$||o4ghoXHT2!ek6EwO#l- z2Ys>yfAf&$Kdx23i09MTkDhbHE5*^bBGdS8ojvd#JjvU|a4jV6Q@BUPXqvr$Oeri#CG_2f_5rb%Mbw;{Q(5k8qGKh2?3qRnW4cr~_pgS1HgH zADCS*X5U3fd>pimoP%Xye&9Jc`6H)FlEouOS5HFj=fL^CcoS*hvq+4rkYeVI1l488 zeF>|)kp+_b*O!c0b0iig@errraBA8TS$%zB0JNIr&4mF>gJ$nz3InA9eV&c`-lK(q zz&?-gIVcRQx6kMExw|m1iO+3?fe?2WxS(XHV>q8|TSB8q>$-@ilqB$A&k*ORUMtUq zBE&t$F-pJSV%+7Q7q!iEw!(`#k@ovr&+DJPE;s`+RlGKT;q=+P(`RQ<4B{}QZ{u-^ zqmc{$%q|5{+#7={Z%(Iv)kop9N~GH?;TLf=)iX>}K12uP zZABO%oEyX;$KbzF4P$p-MP%n4DfvHIg2T%I`yV`XHd&+yg?*W5sk$|XqQ5& zP1R4z81oGl#$3NN$keJK=Ti)gKgF1G;X6PRjyH!-;oe&4mU~0gQ>s3oec`3fFoX=o^%qyn_kj58h6KNLgsEVTovVG1!rr3Mf$ppKy@ z+Dxz-%R;=GT1JJrxVPZ$OM8emo%oM&F0-v!k50{m3Rf9*95AKKxsRzrFESJ+k0L+iBr(nq}K2?S%E% z8nMF8v6Wo)L@ca4O&%WIM;Rm9P`s-57I==_@Bb6Bu-~83U+(%3cQd!Au3TU?n}&$aU^2;P8s>b# zHNc_ zW)zN&DfFaDweM`{_gI=*m7`~S(fqbc&~p~#V+85VARvWKBP`SnfHH+X0c&ef{mVIh z^sp18dZ@cdT&Rczte$EL^Q8_oUTfu(T6w3vO=^wHd0VY7dL#NMrBJDf2?LojJ{I(B zNXb}%)mG_h>~lb&*1)xx>Iao)2`jFg+IXN9U!jh^+eo~%-#}v#+)xi8Jt5O3!P@*K z!7pw>E8d7fyaKT@uBxP3P70c_$zv_liXKN37Bv)e-->~P%(NQQ_#G+_x4UW#=hKd2 z5+8nlhLWCS76&C4#aI;=I*0)me6~Y9m51S}pi3BXVL#AfaxTX#%b*LElW-60HW#{N zF{|b=i81~nIu388NnA*Rc+cslLcQ{5MNfu$5`}P~0gt-h1%p zqiW+H^9eJYy2H0OqgZaifdn{|`XS}^xdlMr@;OQQzJ@<3w ziCt`c?uSy2X1E$Ho5coiO>aYI&T4(`VHVL;|Lmog{&4iN_Bj0w{h|o{t>e)kG)>o> z?W{g|jaGlYo~Mgjl-XWOEaK|je99psq8hw$CHd2XiD#B5Q zV}r4T5eT1eS}>>xbR=iKhBhxuk1J7)ccb)2_``@dZ@?dCeBO`5AKtbWHQ=3pP*=$( z4B_3q9c7~@;DX~6qkL5kDNgmUh0mRg$Id3~4s$;mYqjae4C?K{`qPga=gUn$c2J4| zD0Qb9KPY|Dmcmgi*05$M1`u@>+X(fyWnjz?mL=U1bqKOb=n%~vn znF+R-8U|kU7YBd?*m?-VedljKyjCVbpZ;ab=eXnnE+_(DU>ba;`f4xwdn3wjL5VRB zl-&tRn~YJ)qG~#%=z4G9{Sbw2=e`7XI-8zfiqSZ0L6#poK+`PZvthGjY<|;JUND`M zqcKSTm+5q4^8}!VF@$X$YV&mSl+)huZaF^7_NhE~Ht|`uTKTN!HKrHrAp7BCSRK4Z z@QP1joeR5kJQk#@TGZheBbLEAHw1C$su0UDS>SVVh*5tVU=<+Mcli*Dhhp??l!dU@ z#SqJiBK%;e8DXTq#PFn)^>M@#?CFPb);a8XnE)x-!3*kB>7MYgKZ~h}fEX(J*oNEsK z^Ghos5;ob@Ij=l#MusrYKMuRvxNQpOP;krCtKMA-Fy;Iaa07@)EpE*7-54m-if_=( zV|RhLzhk7}rv+plpJ^dca5tDKD5W-8ZXS=M>*epszPI_NgxR7WBIw)sa?5om^Ll9c-KtonA4M6A9SM zY~u&@=VKfA9=Hft?gXfrw;?k334MHxY58;XVHhL81aGj4=Gp)*Q?YxkoXW#DDJ!k` zAl^eL#TT!|jBvWZ5ks7ZOwJfe0O&kZ7}~FSco{~pJ!!YtLt1Um25d3K4>(XLOF^8w zb4Tx}ouIpaojq}f1z!J#vHnBXJf#^tFv#E5C@w`gPXtoyFn3)5d8KEcu2L5k z2WYUZB|bFA{ek^q4fJSjr=G0j+WSn5HLN1a(H+224md%EWqQ90P%So z3-Gl@fKlH%V(MFFS*+}=@YtOd?y}t=nVOXrSg6Gw!hHd=W)3x|7v|O zDyf)e1-Sy#Gcyd9-wM+^*UQ$|Z%bp%4jVLnxNDrU!$`L0)W_k}yWb|ze6&e;EUGmB zs?;VLk>Eu^*1?N{1kp3G9l$4kr{`~lYvu+0X+AujA2C=Sqo}`J%=h=NReEs98ELWj zo)9A80=TB~5F$8E*VI;EYo(4^K?ibCQQrEbo#kTx@)VExBN5(^wO}9abMS|Ll`k4B z#l42Var|9}znk&*HvEmg0~a~xTN?cUlRQXT8hs~|47HR--^%2Ei(nCjvgimT1Duw` zv4|_8W2VhtAB{;q$(BW$R>D3aDjgneLSd=8D!N|oE24YkUX4B@_uZne%e^o98SW~q z#{D;hurUrid58bCqT#_v?bpP4)@^5g59f@R<|59bjVnpzj|UPU}j z63pAGyb?RsDv<3kcx99PG!1i`g5x1&`KTp-9DrH)qO06m@qv2#%D3qMGV~St9DS8~ z=f;m#qpNs)5t~)|6H9oT<574H5FHr8sR8Q5&g#Ng@g1PK0LB*1%dY!Jumzp5Zf@*EiPJZ&=F5{fc>h=!5n2!)t{Q zt01u6D7Pybail&k2>LPg2=9+cx?%#Ri%;lhPpJov`G3vLDxYPQOhQ-$0z7%Y{ zC+JC}FkMIVprOuw4RtuvNSy{a>9iE?nczlW9zLstiH#nZuHE1dkTwE(f27d0aoU&> zz+MSi)DWJ5*NhKN;_l%~0`NJCwfVRN2ayc)(>RMJ5G==M2x2*7_|hISH(ZE|byGyn zL%=;gRn7^leuyC>Y>vOidREp`>!k$FdgNQ$OVi`pNC|zHuqnY4cJXw0JB+`h@@oX9 zGvWu|KEKO8l@67^e0d_%34moA4B=fZvXo6}owS>cUxUwtc||(+mB6(C--O^>urA#v z%+E=F&w1(m67nHu^kvURf4!D)EHS^%{Q8Ng=<-|5n~@K#D4Ra_`u=e+t$cm|ak+O# z2k#~u`O)X)-Wx679nZl4*pVL%#IMlYaxTY@2@?CYw%HACP^6VkJ5UL(0WpeR{g!KN#+Nq0&)0mRH}rF5@mID z6^2VP;j`{ZcyQtrAk$Z(CSEPc{Xip5>D=H2XX?=K#|fi={y2dI62nBs34M<@uzjyK z=8&^Z<`*Pi>Pw?1Vw%-0NcstaQg|b*+C#zL0Lu}gO;ShFezyn}I~vLQLHBB|WxHwL z@JQd211a*w-%&4Ei#mSDt3)~Br|=hG`n}+6Dd080q5wN2a3om7ABiz^KSiH0DeG@8 zbm3Pbh(*F32rt5gX~&BNl0{d31Imd^KBJ6sxGpT0EG@UcxRA(FWfnY()=J+ zNF(P6fUd>W);T^bem~2)Dh#C=Ch!#JV6K z2&O6Dkg=gb=^$=k3DyyNUQyfdtUs7Z_&O@eOp9mXy!7tZX6+~t@8zUG4s57^(wL6Wxl zJG6qnSY@JnK~)xj2M4n`JXvP(!7aD(bv+Ygdr9XPagUaJQDq5yxJ{6$>UfUdH?iY7 z<6|C7IP^?)X^r+_iN+EA;6glvBH1OLoOTFBtfIo3>kmti>L-|+YfDHC4iOY5X~%T^ zlGtH{4O4dbLl76*!f$ELaRzfO+NZ(%f!w>J6PNP2CwfBez0qNN@VPH~MDFvW{Q^EO zh(0Cvh0)-ie8zWF-mi&bv~W;zO3_`KXpNveglUVmX`(&K4@u7KXp)Jvo$nSqzoZiU z0|EeH0bbh7-#@p<*?mlX*6wcxzyim{(}J)o`l;Nzquu`iyVbQZajzHC^&?6J9j9Kn>3#+WbzA|PbQ|r&HNbm%?e9S1$HS|o8S|eaP$2( zg(1V4c5cyp@}1P>lhn=5=`vs@ofLGw{j)LVR^qU8I(8{bqd!Ncoz~?u`%mH73vhe` zGdr(tW-kEtb1W0N*q$9mcW_UK^E(0FE&vpe?dcH>iIREq+^kFIXM461nS;G{^@U! zx73tLe?qdXKcQ?&AaMhK8%gxm`649V)wRBeWZ81jV?=)74-u`Xa*S5g*dZdhm?uSk z-2mltXH!Shjtb%no@*UHkBQ^_YU2}sFzws;<{9vYSR;L#G-p$M8!5EDjrL@HExIe* z8xlp|O4rrTVa{^Nk?wN5TK{Kp?EeVY4jZ)?Ttgsqk_KZS;U8-*S_kdK`uS{Ng*ISi z0AaD(Xk3l?VO^?;e*5M-;PE!{NZ%^WW$arqGxn{}YcxN~-#%!JU&as)l|d}uk);ah zIE+0o(a+dROZ8|6-gMDE4InIa(3CCZ3jTm#O?R!LnZ>b9-uB;-w12 zbKpod@F-rJu(%bd8ipd)u5kydwvIB#52cSVuc$-DBbJ&MTq#J@>Z-QlL*zzvAo&1S| zDngPU`*21}RbeIRtk$yHzDhFBjVp_As#s|`fj~2|;=oT_QRBl=swhs-XLS2OK_~35 zLb*&+S>L#=Wu+%?oTzQ4@6CU8{IC}`BA=&^cMy=&a1gvWNgtrUBcPAMpM|_RqZ2$6r-bR4HV|9{4%+4wF3t^ezOS}e|GIfYQP(l7A^Mlxz=$d)Wdm~Uw z-T$)g!6qQH$xD;u0+Ty&O$b7NoX92wqd&fx zXccX#EC?C8^6N zshjm1#38=dNlA6{-ypwRi978#{0Nz8zu^fyd%>-!>4&_g{f3W8*;}QISDpBs(*V?{ zdApSIs`Iz=7;0Ydb}8aD>oJUZ!8@dgSFM$D7bpg+9L@_#xeIqoMP8?M7i4_=6okEl zeA8bbPi~gLe8Y)dvfNZozFfKW2_(yvlQYb59jM17US7`yhhS_own5fhT{*r%?2_qf zlS4V#%lh(=)W@VoUahy_1!Rg&1!X~}g3q1J9nB3+g*$VR_K&kZ#Y^D-cG0N}_fvcd zk%RRDg0vj0zmUXBq|iZ94Axobj%R&lh6pxua>>ZW`Ui>cA@Xs!R3z|1=|coDHfB3) zA|$`Qg`!$c^PEVEx|Vf5D6`o4;GFe6$l^}Qg1$$CF6i{m%-HwvGIEH$3i6+VA+z8M zw!T|pK^Z;Z@KDUmPSp}#NwTeH`(HV8PK{Fnde@4DIVtxe{uY>Lehl93N{w&$AKyfl zj$@~n=u4T-gw3}A7Xi|p4JHLZc)ZO5SPrFt>ELa)U<#`OBAnh~L1I3oh)=Y_q_pnk{6ED1)| zRN;jzAK55Q$H?VLJZwwsF^Ej6=f}??0{jRc+>!mzm~vjl5Bpj7MA)qE6PF8b;zx+( z((Y8ZL>Ll3f>t~0N3=GtTuwjYGxbNrbND$uht>2P4yEV786SQ@;wVu6?VLfs;BLyJ zeg!gRYM99{=$_dxcmOh%x-J%uG3oq*tC@6D{|#R7LG3?(oG=pt=#LXhuqC&cFtr~X|!R!W_~=~HXt%rZs!`w z((+WicqZGPiG`0unChjWl}_@%}q7QGmgh^)n9vQ<9R2kOga{w-KS^3~X1z$_#J{dmqAdmd^?Gex=x|75O2he((d=hW-NGWz_cfhxkk=n_$ z@8|J*W^S*{wFcXOPgO3Aoh#;tn5)Dz*fy)_xZ}zP#uUy!6d`R>}Jj5WY-16)!jn{pbfrdWtL zyo^D?!-1 z1@CMV9E-AIUaOr|vWasZ>OcQSt@<`dL0g%gEB31t0=O;nKdqtmo>1gQ6d8T$8ItiP& zi}i@UH=Jsq1fO z@%-2-CyHfGkI%=v4^42+U9g<@?&war_eA9teC~~I=6z>#`Qmsk{}CjJS$pbH7}B8g zCg<(@sWvUFpD!t^-f? zYNvzF2?Xeqh2JJG_&oqXq(9Dr@C*l%_-_x82Cw$-4w3UsJ|Ov0O{RlJ{Snsx=xV9{ z90WPOmGx{tivA3z^=H5njFy};L5>=Osq3;X;Bva9mE5kJJAX30o}G&Q0P^bB5#G#& zV`#$ak}v1@1Sj-Qp%iEhfUrj;h{z6)WxNfq+&0+*1M@#Sx%!hjwtz zJ59`4tTx3xc>=oGB;T0$pvb)bD3n-!59}+V&HqkqJ{wh+OtORryQ=bmN=#}i^d}Qq zo1=@NbNFy}{&}c!?OZ?ykYF`f&(!a!-pzLUSi|?|5#l;Tt1|2vU53i~)xO{dfW=4F z<+D`bi>!$9N_KtS=48E1MVLpFE~$r(ayff4+Uwfj!p9! zexY!W)Q9*%rF@9L$$f~Q@inQAg+}(<`v|`d-_&JYAJ{{UGuXNA575}I;D4OnzN@Y> z{twD{17ua(h|460WPdTh+A2RuHA;_@U}Y{$Cf@%(D_=~?uAY#(7; zY&C zS_>f)YrA%zYP;lsAt3UB)R|(op2T$fm>8%(a`2qT!E-(b5A`nbb9B657c|zzHdC8r z$4mcgr`ShRu`ysCm3w!zYLw4C(dXpe8|`%fpZlT@%YA;d+e$t!i0+g7!l-K%pK&hk zHcb?x(t(mwir%P+*61Zov_)4QBt`AfpO~2H)^*)7AfcY^fg6SQ#|Pq|Gj_@^LzMBr zoOduV&9&#H^%OKtxxsE&;6(tABS^au7|iZ3_wMKcjWA03APZ50J$n zB={b40RS5PaTbI)VW}XA@9{CU6Z0G*=X-oy@}*ilw$ia6>4P3u>*f0%K^N-yA+O;@ zAm?TI9#ZM$`yS-guis6MUxOxQ@I45nKoSnv9_7Th+2C+&0xhYR9E6}A zMMQeYkIv14r#H>dCWk}#;gizQo1%mDb=*t+z7ZZfRe5aTxE4Ovf(EVX5BYrv&ScyF zOd^l@>H1LbY3k2U)mJ}+x@6@k&EXrvR_o?UtLW0$Z*+bL@?hIs&uaO901ob9Jon1E z&YvJNeLmycc=iISp(>+`Li5-r2tgTjQZ+LPM6(nVDN3oLR#ATk&s&ZuaJhsNom5v< zTCJEm(Mi=+1)kIINbciY1h7+C2`}aWHKaqF%PSTV3>79Q9U(CI0HqC%lV=Hzcfgz=b}q&ENKt$WgKTH&_M%WW)zJbO?<{l_+(8ai zB><2MdTugu)pJwS_=u!@cb0`Rgsoerr8sHY2~8-=Z}_Ht)og73m*DyKM0db(V`!eN z+v5w085~0@JmXM~L-XW4jKl?s`9}1SK)o@L@PAbb2>e5K1R98eD2Mo%1rA{Bmm50>+Fj@$7&(tmgCm#n zwtN-(1=G0=G>l?RjDnCr+MABOQ?w$! zVl#@i@U79Dn@qtF7Ev0~V-K(V4ZdZMzVsy~qw{MrIW4WxAHX|2!ho1^(c4aNBCxJp zCj6b03%4_tS3*$$SKcJ=pj%qL3~xI?2m>aDuC)bIQw#ACeGD z&K8jKvV3@@-kN*~^FdFhxATl2N)z=DkmIdrVg?_cPzv-q0PWg`PZ2JPp*D7x@xv4` zuO~4OTxyWuO}{5_1nEgpmcLP1t^iQ&N?azX81Ug^=Y5hKsc)mae_r}XKDlT6D3c$# z-f^m zdmW1Bsf+UDUE~p}4*b$$um%*q{Bsi|FpiOZLB`I9Q#7a7EchDMF4w>OtvBKK2E37o zv<#Dv0^tNN7&2JK<|cTVD)ZVouCkLYYzW5-s|r#%x6CJ0b}%`GA<6{CHo*C$1AXEA zRT0wpt`!|$r=$E@;z-Bm8_!emqk+DB-p_fe_PoRfDd076Q>Vu5k3}2zu?@!5h%0qo zf{*F*62z69m*CU?r}Gl7wMM6m`SE!P+opBOlJgSGpYgne!;D($lQ1K7Ucw@c&r9(6 zWuKQwA(uFv;9d_59OwyVDHFsSrA+kV6`q$6tf})7f~neQP#zlmgkM>{L=QnMTYv)+ z?h}$_Z&C8`NLXI1tW>wBo)hTfyk^e{NE%B4a;B<0=K@}BQzT~&(zx{8 zg2TnFYn(YyTs%zjOi%+}HACVHyfR!dUBvZlBd(J{*TXUfMYFv?y5kZw2w6buu+M%d zjF%qqsDA>(6kos6QE(~W;VH@^EAg-gU$E4Z9vr(^dGaJ1ntw0;drt^&j)?)IoiG4puN#r@~6E!AkVmgEdz{E7Bi8^9Eo|W(?XDU(W1&k#zY3Gn~cX84C;^2Z5s% zdeXuu6`pO|*S3@RcHUqTGhY(9_yHviF`r>Xwrw50XT>UKE?S%2nTzI*Sy2t5&?5-f zf?_>E(d_G~istBpD7AUApM?)a9+HzK(U3h>@o_-x^jHO|publh?)n>_O{OfmL2p6( zk?O8G(j~tsDO&kh{kt+(|1an2>p)uVdR!(s5e<9>6}!4Bl}cCbjm)G5xw)eRygJWf z)g8~X?FL^Fq@}<+=DWFA`W2vw6YYP=y*s+}2tM~jzm|J%wEjpw_eIajeSUP>QG8wy zeOK-aqhpTdGh*qFX`&b%c#Pzfq6an68ZA3ka@wMgXrevZ{W!^)9lc8vbE59!C8r$S zu8EGQ^#sZ3jBe1x+z7hEw&q1wX`&MSSrc8+<`bo;JNm6AdZLXdNltI{Q%&?mXPhiK z^P}yWSP&g|isUSe9@j*FbnvN?GZ1}V6N{qdua=y}(Z`rbyBhK9@Hjmnufub*3?cXd z#Io=elx5EIxWCW6AiuZE3a&(V$02_A!ny1Vz}b05n(VPJt|u?ZPkAwZd5rHi;Oj5C zPL=3CI`vnAs5|%$bErraA7ne&*;?EOw8?eEd9W>2aVbO z!}!5&A7Gndh06>62?WJEoQ}b@f;agBfWG+vjo?CrUFGk$r7!^HYd96Mj;7^!1b5|5p}tQ`D!|APcJJZAg~S_o zH(;brz$zmtCos^Edk^n1CMy$P=gl3)r>%c2=;1#vxEeTd_s5BALJ<1nL>9g&M(d9g zrJmgozjAdgg^c`GVBZd*OL`_f&Yy}4%}>Ch;X)q>$b0mjjByJrkszdwoB*{ zW*hcsNuv1B#PO)$5PAjH;rM{&lh{oDO`1=dGx;}bKADhw@qPJZ`;TqiqA=tof#H+D zWPM*5lQ%lf)c1V`IM8tH`%0|#tH?~pYN0delxtAa4|#Pv$Bf3_$ui;9_RyQrLzsRg zd__O_N>4hn-;LP1E@FF-7qH*_kk>dS>t3qETlP0dDX;2$yKrr0 zKl=`l2$t0MCjF@`U5@Fk%)fTA|XpXc)svP^60@g zyzr*qECg*%KkWSYEM_jN2R`=%^z4}>^K`85rJdsoqzh(%qnVMODrPxq4=sgxSTE1Y7wW zY_P@X7DPqeSosQQzBV-$5En%_3#*L=`en%>z5$>ZRBP2TS(a$s=U)K zLDY_sgEWYGJcFo<@jjUZQ7__c*{V08MiNAQGZ7$6x5ElRte+*3A<3FEW3D(oow6J*oC)MXjlw&c1eae)_0dT);V`@ea(OM?}-}; zUo&631&!5i#buK78}@7IIt25<+a$*Z-dBGoFi&z+^e;}mlEy&+^C{z!bL7`3$ND#r zE5j1`)6S7Q*~h&d4JUO^B%%0+Iq;i6ircq&la0p-Y#_PhQ%vfs>px0zVICa7PnmQx zsM_-z6g9Nw?V=2r=h%iem()CkDG70$+8s_1&gMa?jPH=fWd7n)s_qjCL)sGO@0@E;uQwH%IZeb~g>0p^3!`ikfrlf5?rP{W8!xUcv!>1I?P09l$ zDlkq)mKI8PI$D??(_&#M{pnz(SE#?~n3(w1A9X#WdUWJ0{vr$d#>T|NFIbMuv|q3o z&t7mRYWg9sE;2^HATMQiOBt_8aEztDrTPc&lu}-`l$%>D{g#y8C#AgVi;8p#Sjs8U z3*IfIylN?(0+!w?rSFweUbB7y-rj$o6!EG>ZX0*Z6Rozo?K~0wrUbp1m0s`xsmQCg z$lsf_F~AQ=5wBXrpPZc<1H>B;2CS(cLV00c!feaEVLr*;FxMS!e>b4qbncS(=JBL% z1oS2WqWAzL9u?ol=W}PiBOjm(<@GB{(0M%a5V0}&+BXO!8S!mZzM#J`1WK{HeQ76c>;EJnQ1(I$WTkEWpX56M%(rn;E8Nr7Q?r}X z$#()o@>gNvaatTR$_-y`WG7qC(gzOK^?}==PCiY<+*F zFSxo-4`lj8zSMx)r^dCV*b$QH9jXgkS{uVkvBK(J&Qa(Gm}~U~6TMe=jnA<^hEE{i z5gi5jYB8Tv#a8_B);XW!h;ml&;u|Rvu_0$e&TvQj33z1NmyEC{t!Kt!tVvLI7rDj*`#_gE08zZ9sH ziahVPrLi=nh)==$r6^;T)pHIr+DZ+SN+Q$`T6|MPDj*`!4-1IuCgpfNb#s)HnfS@e z_Z=?u&)K&P2M)v^o--0vPc8I!k4(@zUcBl$z%jLa)s0N;U+7!a#bZx;1vm zr3gjwIc#&}RNIvh!`XbiqzVNzIW(3!e+?1Jo>YeP-e-^zc zb-!*s=NEv?tKEjSCw?KxfXRPlvNG{I-V#??+FMVl;-42hsG8=F6UT%g^2doJygf$e zj}w(Ty@J#q{|z#1cC3CrZ)tofu=@+F4Er_|hei^7oL1cpxnll2v$QC{$v<3N4I&q0Q8p0mE< z{m4xFinoiPzkr&4$g4O?u;VsFL}6Qz@KiP8C(#Ri@& znd*O;IfT@QfeSKFVX7A3J3z58)k4_lPC_Q5JNlG~?l24TB>AenadgMBkVJQchp$NE zGA4b-m~DIq$2ZV3c(b~LUkECW?7_LIV`Nzs(VpIsgJsUbCxnn0;B$rGZkQd(?+x0J zjxZ(B8ySQu|2xT+d~YzNCOSzldSlr^AH>lcVF%G0-ls%wXic)S2ckDeF?#dg^`~U^ z$aD6%3$51f#$}Q-10puu6A2bzGM%L6X1*{;#ntmxoKLn>&+0ECPfXZM^GVOl-wpaU znv%NiyFtGPG8=yRBa?3S_?8#oToLuaA1BO&0Q%$q!89+(X9b-qm`d4N1P(X{V6)T3 zc+=;t1fLMm5Sw5V@f?s79MWE5ui0>_otqSftqV-!nqRh^o^e#D9j`%-Fw?Oem-kf9 zvqoILv5z<0FxNv}c}-hzSmCFofLFD%^b=`<7keTNE0ad^?W{2AI}`0c&GvalhApJV zAZ@T@Ib+I(bfLgcj_?T^-*(LlPh2{{T4)#K-I+~24{gsDnPpm@VpD|%w-B;4Oj$89 z->t`#`TBpNBh<{Q-?hO`+AkAPu2}hG z-e1S}NKVo&ObJ+woV1DYD60j_9mcqnNrT^)vP_SPxr1){{Jl#1-rSjDkcuNcG&^%f z7W39SQcbK6yiaR=q_yf2K8v?1EmY^h#;^;0Wl0TBgZDXn4_r@Bh0l|=(0JTKbtWGi}SwiPcx3{uy<2l{6qOB;xvtDO9C7EDN~KmOlH zTX*r-C=b_Q6FvA1A(ggP$nOWDgSB6{2C_OU=%nEW`@j}r`|t+AA_gU{ z+m}=N?_6tY2gX3hwZxG=4|h!34y^KmU!jg4@|v~-_ysPq=7+q--k385cCb??k3buu z{~YVYl2S#Eb;)u*6=_d=Dv_NX+V-0xX&ajMl|1?V3p=jUtxOr$Qs{go+nycQ$>$=a zW%Yv~O7U?{aC~qar&~Z}9jMGq#?* zDeDj!aRhU4iav90uTu{7zaYzv*H-gy zv&0?6`JVVF$Ywcm(G~sH9=+IjI*)wxL_d{#Z*;~PeC~_3%YA-y+?jk{5IrvUh0(!d ze8y3b&ohxe{$jsS*5Bezx9rbRgB4CO_!9&fzlxeYbb*5-qcL0#KLt7yT=(_^Vz_xb zf$Q16GKO2a6S&^(h$UG1_TLFy-}VDzxMOw#H-Gyfj z&kUFP84C7&V!wYV`taA3KBV{K{)isg)tQw)14eWS4$Xff4RZYPzXRa}FFF>~raZ&8 zyxLz}1!s>dk}q`<8*3{!`dXqb|8N-8#-b-P<6C^K#&`AqiaLJCtGHBlwd`CbnYWe$ z&%cls+EGHnsOmqd0xLxKhy^@h+<~#Rq3AwZ=y)jzFIl@uyiJ-v6|1$LEpf<~AYmjU z(#|mf>VJgwX&0wr09_dH@;;RN{Zr5EgF0fXlXv#_Q1+)VC6a!|)1ptq*nlkk=V?jrQbur)z{vy+6Unh(UV*ZZknR3#9^zNKIF0`b>9*R?b0*G^(4{Ka*rUwaDq z(61czi{X&RIZ5bg` z`o%};*8`BDn>`)<)6JbJ{c@NZ^^5u87Hhnw=$ES%>lZVEGXTyF%$K2GDFkA@r2a6F zynOxIoBAbHrq!>15&i0>etm%Y^+Denh)!S#=gl@UDAFI%}$zgE$Ht#%k*fqor?I)2FOjQT~L_?6QyLh2qZoijdB zz4W|B9JiU-ek}zqJ4aefjq1g0_tqBq@J+@aOWM~pD4&y9lDuX@+3bmS8f@2mWmAb4 z)sngg1A(ZP*pOY?E(!EVooRLJN1|JO)U6Luw?0hWq8g=i3sGs&K#73#nTlrEYzUxDF&Ww@SOxt@$(S)>_c)bn9%PAG*~> z-Qp#!Ti6((-*t|w;QB_#x70~=%T{jGEm@s!bQoTNZmmNdym*M~jJmZuYykXfofip- zIf+J#Nh;PMDaCTMPd6vhH@7y6RU4IxdG>S?{VmVH?=&A_yIeoocHKFf zs2*hrgo+Zead>>ILyM?GAEyp|f;vQXN$JpwqC@Sw(xC-2>(C_> zQx(75>SD}ufGmzNBXDt<@C3zT!2~b1ea4ahP4#NCLz3+qsUpZ5;>d!fQNIr6yjXLX zUV(mHjyitGYdS`piC=Jzk$&bHl8~wJCgZ@8WrMv*zt3#HhJerNSsEMkExR`^g;%K+ z&V}WE<%WL{B+uQ*}|E1@oJE4buoZ+^Anvwme$3(@C03C zft03n@!wS!uW?A6F8Y!$RT^{=Giz0`H0q*64zG8ZUV$#Y0d@S4SNRF>>0)UaT_mLY zeQ)Rhby2eZ-|1o++Z0`t!mDS{#dOVB7nvEXLyalxf$4Rz0g33M;J6luUV$!Rg`W?k z&a}GtQ_;mesEePbE`El(NWDvRu`aqeH>Hbn%>^al9kKk^-LgU6D2)c!Fc#kxyr$GUUKF{{|{+z0w+gNzK?ghXJFhkQG*96V7xE9P&`rN|9PIb zs=H@)7sBtq`J|`%t+%S)dh31Pdh6^*EYO`EbfzFv(uXWCH62_h$h7nx3rtUO%RS5L zN$;>gZ#wTRL1v`4SYT#4?QB72r8ik%cG`B1Aal~IEwFw19}CP)FFRLJed#k6n3rB~ zo*+AZeWwKm(w;<+UDMZFV7Ih=gCM)7*IHmny43=Eq*q*`s6EraSYT;- zQ7Xt@>8~vClJtzKAcN@>7T7yI?ovUPrQfu`P6%vya#s3n3!I%E`5HmaNxy7?bJOM53vyoi zfCbJ^_k67&7o;Dvz=dgigCG~Bw^`ugbk2=}tWV!+fh3)LlOP+?>n(6eT6&!zX*z6y zYWhzLT$-k@SJcMz_Y91+E;r4u%N;;ZMeA}m!LViPZHK%83G>6#y_;8Lc(($tuhW|y ze|mZq6X1CI8Ty@>UZ&q!>D%->JH1c8bJB0>cl-1w`kk9TqhFXE`|vx=PU(#zzN7iz z_l(U4W;8rR8nR_fPs4nroZ<4EnGWcEmMe31dXV1dr0evXpJ=~K@1Wt03{1n3c61aW zPU%0M75FJw=QADX4xX{>a<-c9MY-+Wk>ZttF7O7TVrqB0NXG`3(z8^gcUX&EH%n`X+8(nPqq$GR*IwPL-lJyUV}K4x*;_SuX3W$@@As zADLzJ5oENzHwRl>Ms{h$jPz-}&rJX6GO~KiPQ$m7ddMu{xoBc-EEi2I=D3%v0-c+W z^1%3OC^xfLmQPM#*XE-$#2*4NyCtGi1^2od&KOm*Nt<7qCHw;tX3}qz_l`=i9WD&< zTK~UoNiccyFB^{4X~Ckw2$I-z;WZk>b-FG>obs9 zHZHs%jNbqR)FG-dBKd?zvGG7BX!w(j4d3W9@?*niY;4G6Dxi5BsGk00R+ytB=aV1@H&rwb1vUTFi@X+*27JROH0s^R?}hJ4R1w3ytnqy)Vw`&tiDgKi z0@4fLlUN}9e#@2f7MNFd>xAQcXQe>QrW7L{zwD2en@up0U%XBgU1L31NS7- zLyG5e3pQ(1l%GFKer5uyCKBIzK+g!_aV9^{vlrssSp%|tFGpJbx$``;k+BPI&!F8S zgUewNd=LcIcg%we75prryD0b&gNqczWoYen91KCS&ohX_4nE&O2K~Nn(GNY>AI|@Q zHi<6jsEe4WMndYMCaRGVyM9JAMK#jH&&4^)q8h0(far^Aq>Ud-b5uq(QkZ;+0MQ!N zNN4ge0irmnk=o?{2oT*-ZBGE)F4DU|VsbIebo_A=3pLv-kGz|a2f@0;_qa&bw$4Xk zZssr2v+8X?ae7vqTdT3!f;Y_*5}xfhwfSE0W-MH?E5p4*<%xF<;wrqRiLzD3No;ZQ{I9Lp=7$G;B1;u)*U@E*puBW0BE>1SPeE*`g#Anp#u z@u@hSFk$RIj8&^RxyBAo9m4WQ^3mWxVDmUR|FT2iNh{_6;()+*a&qqx{K0(q=B~k< z9(={+!1J5T;eH{o<(yu8)gkaeED>%N0-Mn3$=4i$KctVVhS-o!X&!O0{+#~F_>i)y z&u6wb*&o%%2?lCRl<=9w^6a@gM|Q7b`83u|>Yk*iY&u)^153*RwgK9D3EQA)K3gE` zNvt(|Ja*1a%ea*5~rXtnqxLuG;ZSDf}PDH^+x<= z4 zEAx~8QtGU#UUa3;33&Ofg5AYA`_ln3`|2eYa;PbFvGx7nEfFM(?r5ezLnEGkKnM-i z)?E=*Ft6W3jT`N$gOHoffIee2E_tVs3M~P(n4FIEV8KcQUCO2DQA0J@y6>vN11R^<`z^rO=B*0`%gY{tl zAnvIZJ|X7NZbShtnGF6z-Vgp1Ast6pwsaLv5-r8i`u3rB1IB|zQ-(h1LbHbMW~h$u z0q{ir{GkV2ME}rN89IxH_Aw2nWXp!IA1A0Fzg5g{#SoUM6tQv$>+A}x8d}T{t^-)Z z{8kUmbrEZZ&5stLohHxUN_XQkU!UtW^2H`5_Ac=vl$pHiDK}M!s zC$oJ9F*eJ(OH-aF1Sj>tSDTv#Jt_D@23O0Q8a*XA{4L@aY}k(kzscaP-QBT`yw!1R zgHi6-##wn2qo-(WU|_PL9VU*3n3G^;VGGOR;VyFw%rF=*U)xlZHx5gv!OJ@A8wX)< z4Qa*SFmAfUrpCn_ru-N=SAT-X2$-5(Ne$s2KDI5*!x(~P8v#*`h1=*uWJEQRU;q&l)kug9X+%;~BRMvt z5n)k{L?yq-$ct(uE%{|eWK?@80B%V0D5yx@&Q|ya=>a*UKKWHvkEq5v*tm*kKa7-7!e?=uF_&?Y8z()Fpy~X5p!DLQIY1yIoiup_Kh$_i2;``fuCKTp z{7~aFLLh6MwE2O8A8Onp1j?P0zF)f>IMkp!f2DESiQ;cutiK8Dv@oU^MhTzUP{0o| zmWL8aCz!u=Y5gDrr=7`fC#8NA+)#Ss65b`Ma4lz<24Z5~Uo88A zPRmzEe-Q=O!qmNq`a5_bLh?`hm+qcj#BqU~(}Bw>$HQeEx)vA&%pZEA3-u4Zhaub_ zwT#~7k|FwPL|JBTten!v9Bkzf%_3u05B*vp?`N8)==@AEdhkggpc<62$i&Q?zAqWP zpjhTqe%Rmz^?H)kc3;CPD%rDySB8DVnZPRZ%K15$ojaLx8BQX(?VQB@fmzVEzbemu z0L6C`!@mj0YXAybcd~$#G)&FgB~-XZ^`VTxt=&RvI zk=&u9t%1vz186jGZ9wiN()Pe@%)LaK9=MHMKaZjDl@VH=z7<+oj&~w}76i9mam74d zKmZL$-)g&QmZrn?+gyK%*l!P?0HC_(@qEbVIe+s6WH(QX!KVJ44+$H^o3Bo>pVRo0 zLQXf-&&Y%8^59u{@H__Hw0Ic*h|1fK_L(04NkRIJ6k`5`@l%A+QDb>^6&lr`B&9kZ zfRg~DBC3&!x=4v?B*SQlYNVqsVxk%esf(JZb`HaQARkgy7im%L0)(B%_!pF)+(VD? zSy4xuY;)Kp`9;+&06YJuVQ!nG3f?p)OL*oFmduA@RfH@5i*E)0tHt~;)-%W9TLGtR zIIMb(!?yyC^Ha%}vFIW^qs!rvWndp4b^W&THYxGACqB#A{x0d;%2SZ{<`J+n!Ec&DK@gBxMLCPrMv*?e4@re7G;`r1~ z_iWx9Tm2FH3&rwjvECDXM6th8ET0zZ-O!IH_BV><)24;J56S^{7(cC8J}uTepty(j z_ln}vqP+LHCXD~6C_b%3?|Nd6`WZ#>X{mC*Go~U$37?tII=0^l(G1$yF7!LeuR@uCjaImr6JwV3K}rj-e@NPjb}w?U?-jc4oM4*o+tFg9!8{wV)g?cn@ltE8Jw95wGP z4tPwqk+m2np7See3HP@WsiA;`=^P^EpH5{1W|!UiX7iKI`DJg6$9*F~RbDvz%en>zwK{ivRm}NfEE`~eTUG2yAq;3)Iu*}0}%VsrhXjY02aq}B) z6>TNMG|8`rAqyr_Pw3Y->4~|~1ZIAHQCBRQ9M8_xWy%7cTQqqcp9xRKuB9z<+YksT zAdKR(VG&#)qCrK-VZ$Q0fYs0rWUyfoT)^_GLe?4rA%!Z9Vya;goM)5i3)RsOWTmq4 z$5ia8=1I; z4t}f%5<>^y;Xr$U$XIPWNIIBs2p~C|m6vC)bh9cKWZA0SD*TqZebPcLP+6I?%ne_1 zhGF99YMsI*xoV2WnQV+t?&bJo{qRpTDA|L9l7Dhg@-Gfb=={r9;&`Bg zazBi*q`6AtjR&6Ry!2MItK2MqX6#~Q<~{lTN5UAbZ_YX+>WYnO3Bt~qZb5wVO+4Z< zG4u7?PSO@uCjhDf8vC~za~jJRBs}v4yOf8D%Y8u>K87!-UAt|*H1h@5fJa&{cD6xx z<}}|+79mZP@F@og#?~$&j9IlP;WKlOUg%yj=Hfj8^ttpT1oD0WX}2i`Mu~UM3wVJ! zvW9!filvP50Tk&wL|5LZ?q4Nacj)`BBf+a|PO4Y}5}OGouwbL}x>&Rrm09R)?&o~u z`+E8=t_|#7vZexLoRohFg}^wg&GX3^E6Y#DJPMpTK`=}#)fy>zTuH4yFZNzC6{~y6 zTv1M?^P-^AjB+Z?{-ZFZpnJ)1g&!sn_M*N%M}7S_^_9w*>FXrvD|)Qw*VpGcgWiog zN)`N_qZpuL0p~v`s=krIhP`PAg7^FjB+0D^XNqg5Gd=~$I@$NJ(gY*p6T^u(`%OBkOy>u>klMDaE+b$Hio_*!fMU&RG7YB zhPq;&A(J)f`#HsX2-k*j57I;lpGAFN9*P}~DOY+i;us+cUWRhr9D1?TQT9QR-afDe zqTx&NO#X)|JKlo^XEA1Fp0TL$7K3w{r~7)+*DaQq*$VGZ zLd4xh6QVv1=_oen1e6G?LRreKioJQO@)T0l34&aa5Nqt@ zX=P^n3$6tnn!l}FqRCr#U+ENoU+Hf7eWfqWyaiEH;S^?p+ac+Zy>Uc1dy^9Zd&8)4 z?9B-F;Nr?p*qgm+Zz5z@E#WajbkXdGUo(toh#WENhM${D>)vjci55R}<bO-!e`OuV0LDaBKXXwnVi8PXQG79aSaab%{eH4I_p4t!x9QAK_^2XGb2`< zjr2K>!D0*Z7-|-1hvsIORBhud&{Y5N!SQWS7|{M+WAhY6N=DfrMLHWKDnOI7IQds} zNSz;xd(xmK9E9g)NhEr+1X+%Pi$TM~q=9y+NJVCs2+ZtKktb$^SZ+RG^(ALxa5YF~ zqx?zxCckR0}vnjW)K~KPr|w$~Gy| z`$sH38omb4q#ad;29n#LVU&L)YP=yDnH_?E#KNqs4wOaz$Q1@6)r=v9D9#m2Ki&vW<5(a2kA?{v$juR&%m_}V;pBZHYaQaS6uKmLXB zDr=&s#15Y?Ett{kEoQ`eP6RC^u5&Md7(x}=9@=_KX=gZ2g@@`3Q180Mqk|I9Xf zb&Mz4+ZyL*v1nDM$zBnj*(--j(#FCiycw6IkA+KkhC9dX9qT7t**m@!=*R6c95b?p zIS$`OmxMPtrO8E5FlUvwq zZSQ!+@u@iV;&k?~ws)eU__QcLw8YvTn>0%J%nU0h>6sAUm^@jre3}EvnR!OxQ`e^| zicgF3ONrTVRWb)(DcGKTrvFH~o9wYb-54BlZI873cxWWeus}YZ74Y%!m>4gRiE8$6@dX)Kh+XV=YJ7 zf7RFfrp5X?lb@d*QKZK#)*PH^^imb+`xMGAcmVbgDgS1Ycws$=HLI-$vkzBQDX9pYAI*F#izKU zGs#vI-{R90r8=X&ZjI$Wgyh1{TB`4I4{#Y}%D_wbGizWuKi@Qe;Bc+23|z#Y-3B)1 z6B3&;=UzN1EN_Sfh2;&^Sj!u(j;`;`Ylyy=QLgWu$DH@#%8F`a%Nu%EHAXFO4Du^Z z>xc0v2uxqBVD~(k-E%j)XZE}Kcwnyj;x_{(vn_ko3nq+Emh3bS(edj;bzg92ujBN4`ST!-Y$v+^x$sViz zW8sn|8LsQk$r|CR&*xi#CWk|o{U{Q`X{sOLS^XR? zMP@8q!ZX}?##_pjaB07%Ks&7d(Z-eZvu^NGWq#I^SVi7}>569wz6xN`&$=J)VZ0tG zqlC|*p9L8}+y=$*nfqDv!iE)_Dwa=ki`e@x_6)^ds#rcPmaZ0KS19%}#qybZTa4XF zu~#UT&)nf+Y+13J6w9Yk#T_M#Wv3R#S1OiIOUs=PjD1A0!;0n8V!0EEv9_aZM6rBY zEVm#r*1XQE70ai^`V-3VI63xcXun+d!3Yn?}8!o+0OJi*@oU{gpWm^Q6TRYvK@WA^En0b@oY)@ zA4ta_zmSf0RcUMY}D*a=Xxm?!$#GX3syic#aK4#RJJE$^-_#wqiRnXWA##uWxGyglQLE> z#aK3NMmJlRbPMR|W(#K+Y-C~xXc(BXP_N(2Clv=1Hy$A(xNlU-(Dz=It!@#R`PB_A>UXl8l;JAc7pdl? z1(_2>^G2Yny~(TK3Bh1Vqq98Ku(Tz_GZ7{C z#&9T81so?Z*#O1N2)h%tz_9rLB{gkbOaZQmUOX| zv!WzNY|jiLiDAoS5VbkmD1)fg*a{h>p?%12RDFi&f1&(_R*@wU7Qq>ovjWx@J!lIY zPMW$MOu=^a@zTb18~%aot5_?WQQc z#aFqgtN|6pxA+wTqX#GAEgMQpR0sbK zNCuK{4*uMMhQJ{W;RbmwD;p?FPzN7!pqBtCmJkhoxQHd;8GKt2B%v96Wf3G{scg*h zmZVqC&fX<4_&B2=0_j!S)ozsjIb@cN(rJ${?$BQt-)xc|)$RjMoezwUF}f(#`9SyqhWSAF zL5BH2n0eY*nITlbkBKM9x2e9(6XD@D=21B!PGedYSV-$|SagoVx6wExDaYa4XdIH7 z@$J4AHa5EpTr1AKX%76I02aq)H{d;tZ$Zi^;Zws3IOlGb z`<;s5)5aTqJ0->&w^KVjwF)0JJjAF1DMancG%SgTyBT1Wv8I0sh9*OQz;^LiTcgs;OGh_M5 z?*P5pGifBkKW_Ne7|G5&EfVV7;(YG}ffHNWY}(~Hw9B-%&32jX40Dm$<%P1#p9fUt zVqCl2F|J+yC^)IKcVL$Fi^vE+U*?a^rGAS+H}Z!U?=S?V)brMytn5& z%g(EORZ8}p)D+S>drp#a9KIEBV#|m0<~V#S;JAEPg&c=(qj6ZZ9EWeCaaiRXhi?TO z*DsI@!fEaojwJiP;JIP_!tc-~I-6FUv!l;d>=(YOhPxLjahDUGzF)}aus*3cKJ%_2 zpTqi;V)<0t06tQlwf1HeAuwkBxHRD_pdp#a^1-`q_RoK3s1%A>j|0(}=Firv&r-c7L@83t?Yx~Y6gl)wzb^U=@Rc%6yWA)YvuyF zwP@ZWx>h8iSr#Il9#GzEvgwRa-)q{6sp;p#pcg}kY)&2HZ(bu~dT27M4o{?i0t7S8 z5v~Y^%?=|XuYuzD8sF~{W#lW0Z}9^zO47)BD2i|KgDxtokfQh&KcuJzO|)*8{C^z8 zTc7(`gw*HGz-<_F2lll-m%S|d+@mWyVF7gMBpfswSWVDw11DFyG4LFCS=J4opLfGm z4u3RQqZzN9m5q+n0acF6-bHrhpzK{Wtn8U3r}Zv$(yEF&X_*ykrm;Ke-K~@UjXLQg z*hz2CPI@joX?DHMois($>1YQS(XT#wGk`MP=yfbB}_$4=itX1-VMzW^7X zSi3|uMp%c*P`>xEOZ{2(8$Ki7U%GyU$Yb^^N3mad%%?KB-4}H#o*U9xj^?|$nWH1e z*=p!k(NbGpy~t}3bwGD>i+X43UifZq6*2s6*4WK7njM(q^X%b$-^4N`r-RCv{U`iA zTl31#KTR=wN4WDSDck!4SF&AMM8P&^5jnZTj*_DSJ!8tz;{SuE_ge|k(G;RS3eh|Y z5oM!Uh*+7F;)CEQ@Me5K-p?I!z?gX@+v0o@P4zXP;@4PBV3`uY4P=ugq{veT8m>@jrad zrmvLU;vB-5=YgL^nkeD3sIO2;7Uk;VmC~!M8yHY0m!J&EF2@DT#pxu24T>kGIK|tB z{p(ASuh}u>+U#sI`%I}s>F`fLZ)2Z@J}|hGx=X!swB>#zQ&}RXN7;9s2a!%XpX*V@ zSmgDHnZ!!dPY6?I~w&-q2@L<`d3lr^4BI>Dk`UA9#x zn2Xa11{-ujOxbpwSeEMqvw}`=*_g}g`7$#jGQ|+N!@mO2nPRZNY0wKtAM}E2x#(Y* z$kJ?^Ubqa@jW8US@t|H5n3e0r+DtDFAz$XTVSVX^bVC_RHwqORTQ@!o-T1w9<3#Gl z0_w&>>INmgSvSa-qHYXGH+}=?^Xtasadl$~o5j9KY%_g-KxReV;HFaP#?y$X^8wu; zlIaFRxo*s{{$je%v`IIZI@b-pjn)ljIHqpER|w1TXY|src<^M@HnAMoN!H7&ZMDFnKLG<{#;poeCgNZE7w&{k;K)S(jJOdBvMuA!X z|8zqcN;e7>8ecd5Al*2Ly0J5LBc^Up;+u7Yj4A5IuF{P^0{Z;A(LJti?8IiFZaf}V zcR{iBKO-}@ANg4Z?aRsk&Y`%gAy*#Q4f-4Ty!b+6WmIE6Nq?4ORAX*+Wf|4}#xNhy zvwtwm2g0~rrc~zxVHR0exlxT}*7+#t5|(Q@7TeiZ!0X}ef~$O|W5xRlM26r^drV1L zwr^~P#ug$|xEjxq-a`2^4TqHHID9MM^cW6nl;iNNfD;)GYuZFtH~ zxRBq9aft0v+~c~MHsvLrOV(c7K3^_Ay4I|qHL;r#gE&r#e+X@l!_CMTr;G7y+~)dP z#V%4TpEkPmAwGX|zO9eCm7HnrY^M6eo@LR1}~2 z*4E~+>eHZV3FSj`FcQ&jceIC9DC`8{Hba4YJS)+2!PW>5h8LpzQbhr&nqbxGtd>^NxtxnR6$HoP35ZX~;yqhXO^YWWJ~4|Q z%@D~LI*Y!ETH|iu0BdF05S)E&-PFUkrtLS@>_6+AZ=-X^oMh8~kGj8&w#kjRFlYZ1 zK*k1V5e6tJrv5%}ut6QGxKKgvoYW}rK#^j>Vi#FpfFf1@U0vijwHG4e;@utMC@u&^ zOxVMrG&Dc$jZnmar9y$79dmAf9?v>W8Cb@7m01Ia@n_z^F%@hy+ubJjk0)sNfzv9O z>R&dXE9X`WY{*BH*ceq*uwBWv${8GHYIl>^R9VIM-O=u`I8#~9cZ}q5`}Yk0QO?GxbT-gav{!e>V}vdQP9*ai zr137?6-7f}J}>9>_mH2l6)<%> z&g+YMXM$gNUSCDvUUJlUDT=@jV&)kIbMULR0?q3a%aHa0(hGlcvq1PuELT6DX>%Xr zd~>rv%pj9B3(#VIbMsH2Wo!8iO3&UvsxHH0go1%@Zt@$mF{)4*VU4UVwx@2X@bMRi_ti`0KNIHKufgAEk_Py&;WI}EkAmx=Z-bD7uzC>aspDo5YzjJ|qw^w#7 zCo2)?aK*N&P`Fj_+FD()z}*dg$>Q&A>A=~sGW(78;3?|lgvk)-U{bK_zB;0ca-!e^ zfG;yWVBMoBL2?A^QQr7t5b}OLI$Cewk6p~s60~+zE_cNzM*T9rg*1dJU(9 zsgo_iJ@_4B9iqO01P6TD+so7CX2Y?Xi>|V#oM2k3-SIj?yCh}2cLca+C?YG3*ibc> zi*-!Uw(&eL3w7opGP+j#2Bq0Io~e(!r}yjo)xE9!g7$;EWOxo(e)Bc9s2t<~o!idEy4^vXN9T zm|5M!Vg(OBW4UHAZ^rLGFG^1MHvgggHiym*RAU%pGM_V3{N)1 zaB6w0;TJcz&uR+(LkY zvVMSTCd)l-uMlk`Ic=;~IXMmqjC+$IpMv440_GCHA1(!R(Fg}Hk0|{Rs7P>%p&hKA z4wdSa%*60iD){coH&K|B?*!F#`apZs(Sr{vJkVmhBow zK*hZ1LL(!~8HT32)ZGFJT}!IEOB0hLA;}AAKAGOI%DmEpGX+b7Hq(0zoEJ2ujmAog zZz8e;%OA(drcN9@9UEZ+#hrr+#@DtL<5HlPHL8;ma z>P9G^;9pB{0BX8Ia+I6|`ftQtKeO90WidM_w?<)Wax#Fe$tif=&ZwZZcTGoYnH@%F z+u|q7)l(7UmgXS84SgGK6!Lvo100tO8T>ws5NJix&rA+Bltg!Y8uEitvxqvgK*!1y z{SS=BZi!DH7ajFt{kTv3K-8~#5d^-yj5$7LS+4QNo-OX0%v(Uoq*8o3exMrlzXAbt z)F%^vQcHa*e_QIff}s>8Gq{v^2Ffs}ZhCSrw`J8DBAngRw`A6lUo<5kF$S1W9gy zeBwAu#$DejuLn6KDwi+0O~cq ziA3Uf5rjFWn^{%UUgTas@fk!6rzQ*e8ol||#w3v*gP6~m7EQW(b9vxH|* zLf*kH?ka#QliiCxRj>u_MqNx>QUH^q*t<^~7Y6sDO;?Kl?d9IJ2T!3m;y>noXlvkX z2AYz_ba*fe|L5ZWeEeUC|M2$%c&fot{9lIu`{VyX_)pjOX#B4n16#SKx1)00jE>5x znH`lQXLVE#o83`4Xii6E`Su-^edcyl2Ky#FeyM?9|tddEvgU zj>@dQNgb8xecN?ZCihM5sC1-#?|>zF476=(?cdMUiCZqFt>XWo(@!_Wsg!it1`bTVRH0a6x3ecdo7d+s*qdZ94}1PqjDXNKTesT z1;+-h8()S*b|qD35caj3MpXt|H?Fk^x#W!Mc2RWp9tNQ!l>A&icC|`00zhs`572F~#rl9aB&_#c>m@Apc{36zeL$V!4T*#cn{7 zvFS#!#ILitf(o<0qmccgtXcz|c}b8jxhcf{XO3NLgXAJG9<4&*TC|J(Hb}N&a)?gO zL9+daIMUN@^_7GHc^h)6OZzEHC9WX^N1H6s#K3KWhu9KK>#WpWQC~rdu*S&bY})4X zwD=tS!m91aDk5`>EuhCR1KzuhUyt;Q=ZEp@_}f(ut6LGENK`uT^0(s4=iQ5T4w-%b zJm`BV=&P=mk20xT|C?GB8{xRSqW*U>8%BN^p_A|(Vg8otd-A7+bF}T3;;zBj)pJ2| z9g|})qNSEeE%7^0RDEx}PlAYWC?6*0;)%XE`7mS!4oX4?@iK7O+y^%OXFD$Z%`w)IjYws*Zo2=n5t#|5ci{ zEKS>@*0y}YYXT-{!l*U_~(JG(Y#%A}8Z`(G=f`W)71pNK>$7Qb&+h-Vong7*uj;V&x1_o;>YPwUa#*$6RuZaf`jkFd1<0Z3PT zwc*G&A#YA#o1n;R0l$>|Y%eF9&=Nuak|161Uhmidza=;cF2{+$uU;#YkJiarr=?kqN00`4&XW9u>-*DE%BLb!Q#@wX_c1vZj@hNIU0T!;fE96Qa^$} zk(_qe{3~eIYalhoz2%iUCixioRTPWJmp66@m@sAJ*DP{VV~2p*P)2^)B5RDilG(y# ztkf|*jc%yMpW*-Dv0!oU!r^}-u(do)%sMa^j4&pKZ(ZTR)=G5*z&d8LnUX*8!>k47 zvN2RAlNtsLVf7MZRLAr#Wrp*_C!k%{uwAOJKt`BuPQDC1?WinhO}>IaXHag_*rYAF z8u5I44f1zy&~9nvw8SSw)2NgN-jWIFOX~rlY#HFpYO*n+-up-Z?H-)&VEW*)MT=s zY%2 zyli}P{HcDv#bH6F|io9cBpwL<$>}H?PBNs{;{Lte>M%-xC^5%sQv36xB60wwYw*(`03o{X_%|f{Bt#*@ zrgmpKeQ+*s_XgQ!hMBx@&h9j;$J%E2=>}&h!soi>I^dr{c=ZNW7*>RJOdjPUMW5oj zjd{M?XiHv=C^)KcBh_u(NEX6BGSl*dh8`m2_n2h*1!5?2am=*7`z4oyEmG|S{B(Ad#BEq)z-K{qqGcl=Ds ztzmTw$Z-BJx{Q-aKbZa^n$urn883UWWpDv_oH8(RSV?X! zmQm*1d~jA%8PWTFJ#vcEZV$vvLSDJf)-ZV+a_qS!c{_frUTtCTjo43wicP#NzD4DA zmu6&jU4X^l7zem3$vYV5?~Q2*4hAh}k4sCj*qo;Y7Vg6=--#^cCN1f?aUN(Gp{9gXpbDDj9Mdk*J zKomo`$y*UmqjLt)qvQY>B)GIAbT@BBQ8D&aRqAJEnH0TA*h3PSt+g-yk3Fj8ZTK{I=kk0*mNSW-LfsYkWIWVXOi_ zqg-jx6lGxN#SoLq;_fr@Iv-=SjcK^g0oM9+Li1fY=rSKi<=|RKJEtd?4~GuH_tzJT4_q$0!ea{dJ^)qsr}V zM#7^2EG#GAzz;sSjAzi>iOtJBxz8GD5pN^&bcynOW}Xo-$L(FJ-_D@zvQf0b+{Bxa6lB3bc2>&O$MCiZ9y4|OVx+meIZa8eY!jQ4u#w}K zDbRYXEmRf;Qzn4_-$n+Fw8ERiR0bv^5d%Cd*hrdMlkWoHam&f~6b*7wb_rLJ2Ei4o z_weJ0kS+N>0$a;Jz_a>8!bTVqF4)>ifv7&g$CG%dRhb8-2!fYe9!~*Y$m2)E-9p$1 zW3WE(V?uty$4~Jv!JwXSePEOE;6%t-j5bD1@uhiT%`GQCM*zEPrc5wRn4qSJP4r!- z_e8_QtKV-oJ?P&*x38R|tBKZJ)o!4!WHM?t7+xe~A^wFY^0Xk_&WgqlrND~rzrgG4 zF9CDHll+QdYuiOA?@E>Dk~>LQG)*G!GAk#)MiN*oi8*^V+JUH){Kj%J*?K+lsxvQ_ z)eXObZ)vMm(9v_LqgUlRD&fxxl~yd;p|Ma(HIDjC@PHk)Y`PxKxA!FtA`US6n8LIoG8~* zv2POJYm$j?O=u|Kb!j1%>9#Dxni8V3R$?TVWEHtRx2?+Oy9ZNQEttw`ab_Pz&c@_c zpCg0Sx90O!JWDXb`jo1FKzT61$sh3>L}mLlO;}F;gm-MXz|)L<0D1f~-V%5fIBL82 zQQ8ptN3>tBDCSk@#yToPP+4|kuuW(%)-m}D9(q%ip5>d*`<2L>MAY~TOJ{xBgJ`V@ z4SgERwt7tu4m;JiQ>Z=BSCbZN0rOtLsIJP!$ZFasMiAlsu!ENbVtTNQlW#=|Ta4+V zur*Tc<8wh7_qxTWDZu@|>UTeF`+p1luG{wO=cX zEQ1Yu-nLc$ii+oY8U_QPtJs$xLOa})Kkg6h^11&F=@)_^_G28QT&ui?{|<1WANU7e ztN$cygfa1<$TdC;4>)b^16HN(~3+46;D_0>|h<_0r2X%oIP56bHFN5blV8Ki-CBbA0B z%T~bq=LS`bg1CIwXeVoojS>vce53g`wLV<&$%H&w?K0K3X=v$7Ntj{y^wxh{dWTa z!_V+UJk$w5a?XSgE~xFy_!2t2I+4l(ZhRQ;o!BwJuiGtE*E$zR5s1)T!t%r|NjrkK zQ_rzC82bY-g2BFL=3n9>`QmTGau}daDDJ?-1_AEXvNeiVORkfJz-lig_k;S5a(pj- z(BH3wpS2tjT@x|ItL|!P5KT;00)$Qq`>SF4J`L2k6B%=}1SY958?5a}i+>uS!#NTF zFaSqNMj@BgU2xaY;-AYWvUyHyOd&5xDsu82>ea{uS16LVQPPDto1u*2NeE9Zm1TT! z@Z=nnx2C(arn7P^@E-vF)E3LkP)?d!?!l?h9l(KZM2L&6?<4b)&j5K1I^1sTSSD2& z6R}@o@wd9+oU7BMU%L*}TyK2a4&`$OoRqF`MPjE3J)pV4#5Pw%sZd1MIL$N>4TBL~ z7*9r)pi`|x87>9jaq!v(zMye%fc193OUZrYN=eM=D0$}K^kvC0Cu&Ws^Tb-#YuO&y zJb^(#8Pmh6e~S_KDP#jVc3Vuc+GEel;_u*|f23-yEC_sNS!%~UmwGJTeVry8^=Jt& zi5lEs^zzPVJ zUlz9gIPVYk{Gp~2gD_Y;VTYq9bg}aNoWSluu0j8@o@y@w3$POsKTwFq{?QsGo{2cu zxv=l)L>_LuIm^e;ZeyM?cpcldhbJ!pgeJ)hQ`5-h`1nC^s+|^PFW-%In?Z@hbN7-w z1T@z(Xn2D2BBM+VyMhP&;Dq%&8nnH^ zYkD>xQw}ae-s<;D1zQpIEa`w*q!MinF;!@5t6$^Tc{X61oYXYh5@$yvDlt<=UU$WS zs3Zl=QaY0&+raVB8&$uaC6j!)ZiD=&qAfRXDI9jREtX<}sa~s@IFw1=jx)}9njJ)8 zoFO^o+}w+#Q+R9(l#g5H>q9>sGIE0ZF9n<0?^;th>f`nqjT$#Cn&byFbCDM&VVr7;Cp!bTGUga-dSQ&k zp2APvc*6Qb7|WaASRTwKR0IuQ97oLq_CnkgJgQsrV+5?9QR7GGQi?xjH?XJzKdOrE z(4WUKc-eeaMBBR4vM~J~b?fs`P+Ba?#zCO#O_a;w^;i+{t##A*=*l9Z@nL*>zz)uPsV;L=Z-w zcb;@&@+8@bQ9)%Q5-p}s!h=jOX;xm)>27sj{IxJag|UIJ)%;9)P;_n2f$tL~3y?m! zhVfk6TZlKh32M6@{30ui=R%>im*qHO^p1-!fJD1E1%2mRKy!6xq--y5z5xAb-d2c&eP}H61BJ*Wi=1r)eXtODVHVkI zksm5Vo|#3?u*i25A|V*IO5W=t-YYB@EBCD?j=dVziDyDHitu!4i2{ zkSjf8FAsUShoGZ39N1w?1p{G_S9l1_jzO;Q5G-~XWiGIh+&%bFR12%FH&a6P3VN#dP+s(g$hS#u!rI&UyB{2+35bpF z#RKLiKRFlGi5QmB%`h_YQOLZm6r6KC4}5y7*^}>~sP^GofXXq~M9jG@1#OrfCWt4M z_rfb35AGN5K|!BHK^+#`n!H4~!In-Koz{u*AYY$q4X1?3-Uu#ia|Xrc6T5@GSIQ-m zeSi$;&(;K!WxyYXhi8kf@cFRldKVX6hY+z~gfTJPnaVKSzaq}ZiL2`N7d_CSn7dm13a! z5Ocd#nd2ztLYh2WH~=YM=3gi3wLil9eP$5WSMLGk^^X(QTgMtRiXuW!au9I27cL6U zhM|0$(L#h-ek0z~5LfF22loLA=wUX=y~wZwv(^VA1BfHs)OkTTBVF=A2=^mwlyH^` zil?FeU4!V%Imdknu-PN_ox0+)$P0mGD`2b{n7H;}xUazv-GA&^2>74x=mblB^VS|b z8(|oiU{}EJb-xTh-+vfi=zEUC@V5Lv2k8d;fsCS09>M`}qvF(|3lSXbwutphXCnHX zq0jp0^+VWR5DfkW4@6u#gh^scR~xzu8LA@nAxweeI6e`7i$~ZuIUUZ~);CXlQ+zmY zISMMqhBY=$-|~UhdDyrTR9fbZATvwxPG%N=9n9w(5%F}*?dfzcFcg8cKv6>NZi%O> zSyCjyzPN{2{CC-Ei0M%bwvl7%;A6-n?fg*IrgN3tgs0bpC!Q>Ph&i%9EM1c$$KPX) zF(w!x08llb-|acdya3=ro-LSB=zkQTyLD?2)?R2Ty32E&l0i$^ ziZ@`!Gn6x$+y|}OM4z@*-#B?Y^|iW^Gq||%ks9Svrau8G$tQr_+1592YYDT96&DqM z2UMl_D)@$8chg1N4RrTzi;qS=Rzh3feCN6DgcCySX{Pv$-;9KYWP>X7D687~P60Ya z49;!*C|J}Pw6?`hp&%Px^UFzNw5^@aGhKNA2v^zH@l(+q!|J1f4Umq_VoTZ~egb zd`s6J47CZS<3Ic=op$Cy+sbZz*oSg_-zlAyRdX?o)z{fsIkc~xM48=f+y>ElT5+iJHZ?QMN~ z>x4Rd4-FnB25o)2t;NV8Y3n8sSyhe{02RMvI zDJT*?S_;au*6{{IO}5O!q4?Fq z)N}O>;Lgxed^ldZTLz+@N-N%0;teEY_O={9#=M|2+mzYa<7Bq@fNjj+;BhkGfn(~N zNcy%&0-M=YYYvh-+sOmRddCTvC@sg%yB#em!H)ld7Ma?2O z<-XnaD7T^;^D2?0x{etuRU2mP(0kchx5hP7}dizxQh``1@Y+r_^&?roMi;CIz`Qm%h@1vaZ4UZvBVP2_!VBQY!dPnGI^&f&8 z1K0)6#Y2N$42+(D$c{X~*jd#R;bs8zBZ9D>grG|d^b>*}G!WL+i1l-Vu!(|LA286b z2ztms4;tvV1Yx@*v7R*0?+N-mLH$wuFTfxbi2c3AKEqfn?lZxJP$fM}(3cD}%|L%A z=urdhWT1Z$^bG?YYM}oR^i2buVxa#K^eqEjX`m2hdStVK-fW;2f*v!_eFkbH2s;Hy z7fK-qItaoRL4vRakDy5eVUr+1*q%X9H$m7bNDvmZ37SUG;|9VQmY`mOzHcDRh!8Z3 zpdT0rgJpuYC+LR;g4hr=kDwAA;bTpX-7K6Q}4IH$f7(rm{x z(Z+|M_?QoecaLob(fj!tpN*pN!BE=^N!v3Q_^?~P4DIckhl|bvtcJ0GHOib*aM-AY z(E;`}l}gn*sZIfeNSCGojIBzgDRHYpjlVjLn!OxS zjDZ5XIgBvC5z^pxvzJU2>7X1}W4VI>rzy6(2ln+S7|u<$gj~p;HR=3BU+-srqF)#WJ+?NyOR)VRw$C^WP7C_#7N!rr5;%8|W|UDl|BNd`He&k| zcD1&;65Kv!92qIxej3)u3cD>ON{)ueaDl_-DklZ#<~xu*`e+s4&WI?94thv62CA=S z%kYc{TZVTWHOTi%k*UU(mWR`4LRDquu?!p+1Or6PjTx@3&&8{NdjrPpg?)Kp&i-%bQ(oEg{pl~dASlV{FlIYy6x6jsU#>YjkZ)YNeTfBhxI2K!~ zAI~40i^UJBDPDEr7Y#x=B3J|!Q=Vuk*I3H6O6hs*Y!Okk+1F}g`L-fo@vpFVErVry zpKXoKcvoXS%7QjviccTbSZI`-}h@P!#vCynJ+>^0oM^ zPRZ#=qY2>WuhyKbS`%+zg{*SP8Au)Y%01OsIh1epJ2;3hw)&kD*1iI==QYt&v*Ol6(C~&CI^wH>QZZWsN|mawqZ1g0cccThg|yI{>3O50SzAp zFL9;~a5OCD65z#`D*U&1Pk%UF2?A_ZdKnNZI1Pu((GYT5UI^4;eElqhFrCe8dh=v8 zB6Nh9wNQE-r6Zc<(1D;fjC@$jI?Mnb1g7uWH=)zxtM|J{796mi3homH@k(As1I-y{x1$BHEaf9enRJ z@Yd;%qcw5I5QZI0om_xCvCV2=V)?P^g$P&LvHM?0-6ib+p*~fR_eYg?%(?l(C>14O zUM`Aly2S>6w)v0gIQ?aIFsI4X?X%RR1JQ^$2BmWAB(i8%KF0DQ&2BoqwmWg|B77L5 zBwLc#xT*)`j8G@y&%rBI_s~Ou>}u zd=%;Vxawd1j8E+M963A2q{gj*2tO?9M+5khk3TlEB{fLuFkhh!J=A;cMhfYG3%!yRw2tMz+HPD`eQ4xcF23>xbV2`Py3E1y|`; zFM=vpF#>EKp{95_rViXA{lUEA^XpIV`1$y{2GbX%Iv)tby(-oD@a3}4 zy4UpQn?A9Z52rs&n(GGNM(Yo=%yC=+&HD3wO3e3sDKA2QHX}`x@Y$q4-&O>l+pIs# zCD$Lm3_DME)wI_?qhqGA~Qd5W2f{JRThfGh`M5<+GPy$QP7J| znoj-HXXtgxY_5{4pr-HLF-P6WK)rP16y}`kR#r~YmyS06h`RL~U)qb%t)C-Jl<=t! zwB>!%FBQS(^Xb+hpz~qcOZH97#a(X64r&s*Ac3$Jtlo*4L3Y?RG03Bt`ztC_t*l=j zt8c=_6Ps6QEC~w6FNU>eJ(SASHZJgl1&vZK0^Qdw)n-w!FY?a%r>1(Ck`b7Flgsi& zT`+SwgWrO6TXH3H?CYBPrDewA0&}E5%JUbrMUn#=or&)LFmxfp$+(9u; z$Mh0*Ov=&}3+}kfX_R8!hmPqj5U#D|U8z_bs92X!v8X~$mZ?|wNv|&VV%Vft%eO(V z{*0PPgq3zV^5kDw#e8&oeG@Wv!8(K7ov@*PRUUkW5RNDAwG7%2!|rlA&ay&7KPzrQ zHJM-Xg)HZ&##{}c{G%H4wgRFWGq0hGlkO9qmFaLv*jTuPXSjD59i-ls*JvBI&^B!K)SGSiB>46%rgMHP&ii%@ z-Ugs^U2)#GWAKe^B-PJx^~>N5#@)_HTo#J8j=?TYi`VpF*M#vuQD&6zX&-;&!ZoAz z4ddq&#ivDa#hOv`L$!iy3#@%;*Bdt(jP{GU-YA{bcO%v8W@*Ysm?l!`6_bL3>d`I< z2x-2erEL)=`f9l!`HABZ0T`(y{`BC()Y{AFQaTTFtECAv>9mz8pS!Y zglf=JGIza56}V?O1ZLI}S(AfC&sr-jJHrqLuLNC9_VS7bvHMKz?p;{=kt$STcZb5JtA~yK=hMCFA-Q7FSu>C=v!-$`OVPx$1$V zG5%d-Ye|<3jf|rwD<@WW5h(Q^7UZ6d{;k7r1>V;35*m1zb==%d15aCyPkY@8^brat zJ~F5<#j+CjjboSEbE1k_{ z?t(YjXTmd^>&8YbUAUTC;9H@5heN7zoQ%ds8~tP2=xLr(v(enb_O^nJ*4%>5G(pWY zw{S@)4mmzGT4#K+w+s2K0e&Aaoa8K3O!RurEWqb`2=a}Npr65DxrkLaLPwGBc{~s7y8Rx- zHY?6yM8T^;o9v0>&hV?y0?z&#DVVk4;M`f;DVYrwo!;6yI-Q}Nks%SD4d5KPXb}3K z`8Re6_X9ZElM&GLFxyhLm;5rSyrFGkni)|7Gb3tkW{k6F5|WMUj8lk4=WNj=A&d4N zHpk>RhoJAO_`VP?I2n8eW4_24ZH8*c;gMk5CT^I+lzIl{Doz4x+#c&b4x2ao4EXc_^{jFU z&k5$~;lN>=C4_TMmI3pP67=gXV;pz(NtAaDG1>){Cm}paVD??1JgDUQtA_rhYvSUO zFn$K)`fHn{p!iCJqlp;}!?$U#V#TXm%(4Fwv{ptpCJ?a@-=1K<1HB=^MTF&^zzJ3- zOc_Dr`E~Iuy-PjQuj5zfnD zE#Luo0{~SBdb5EzeFo3~L2oe-2mb)=P7u3QB;=q2pgjqq`Lq&$4CZ$ z9Lry}=fG|Qd+fHy?t2r`5?qAma_pYoaTsd@_#HV5zU;A=rsuCemu_va2#2kCf5DE& z9~&IEJPf$qZpTB9Kl~v4o`v@_j>Y?mixSjGe<5mi;=6CFOa%Lb=O*mIQ@kwwwfO%_ z{0DIXggW?TDG2_Hcd&}zpbP#F@5F;fB3x-nVpRAuMlP&{{vlj*l~{?mO$*H zonYl&j6;J5E8-ce%fUbNyy*X9?9Ah;s=9~2_dW->3?eED8Y&1XDk`Jg3zvCJ5m7D z^>MarJ9+qL_)?=S`;N4Up05nGWznf9Q|toU*s|yBP&=#}O3%PcnYQd4m46&MxS1^* zBRSm(+lm={Hq4fN+jptaf&CMNbtidac^4Cw%%)n`e6i%53BBF6 z#p5*Svn`tjHnwcZ2$T=HE;XK1dDz(nOZ-QnY#xGgYZQ(}BxU1Shx;M6Y%v|%Q@Q6; zXp;jT;JvMoZD%$ttj$noHVvdLDjjz4OFWI zcyne+p6z06*?-!jO}X9u5N9^r4olvGoH(;7l%+V5g?5;$y3UyA^^~!zL-2Ty8Ry=Q zsg8lR>r_u-T|UqMF*+96)D(4H&#o!OZ||7*)I^53tMbL3TMrDKI~@|WSPjkAV3#Z1Tld-%P< zCiw6BK~!)4Sfl~)ODCvHCS3-bLb|KLs2c)rLB$rjc0iiIii!R(A?3l-PFQae)q9!k zAl=R|)XibLNOy;H^Vtc~xn-m7b#{t$LrC`qyGpwCA*frxu9L2YbS12jbk13*Tg?6; z-7eBCW6cGmf*tREZ}2^~9KJ@!e)bxHmMazg<%jeR^CI0@qV+6ZU|v2Ng1T~+4g}A@ zlFxEBlq~C_QMZXbMLHAdK4C9Xd%dAA_t-WzlPq&OqvZ}oiS3aNvc;t9LRmV?J|X%W z`e|Y}SPjuEGtzJDM`~dMweSb~m2~aM@=tb`=voi7Y=kpAjBi&rqz9}u(M}@4yAW+A zvf-gb6+{M}O!O=HbmUJFeL&>QClP&1EwtdXw0y|K;&7*M6LaNo;Rfx6j6iD5;SvqV zB^s$Me~)Mgk%{jm`X&l>L-=W;2+~dF_lX7(P30~+dhsF3<32=xQoUEWiRdEHJl>z^ z_o3)>8J|FOm1rAZM)WyRC9fcAH4H6}@~??JiE8<2B0*Hge1qFq27r;NcJMzCLgE|k+Ya;Ln|l3 zDf}(wET$3pr#XeEa~Cm#==T9guHt1IT>V$cXB+V<>H3q-UCaYw?4-9-_&u1%Z;|d4 z=wJ^~G=+L*79ZKHg&oWpbnu=zv5P3Szob3X&sjiIc)X}2FKelVc=09C6rwb7oah~} zG{M^Vk;s91JycwdQ!Ri(>Cev1f`qq#H=>Wr=&_&L?Ubf)2n861_a>tq2cwtYpSCOMF=CixQxS^L!YQpo9P1fX@yS%MsIUUjxf4a29A{@c(VL0_Z#VfAbumT-&)oYvFMe)XlKf zyj<9aEs>rRFWG9K%Y(~06MIv$EfedBDv6eha-yYh{%B$= zL#15;zyeB@lh19cr9|pg^y;>Zw)nFWcNP#_@ zeSif0+)ebMs3wAYB;a$aI7vR6QN2&ZX)B+*AED1got18n_{oYs7Z)Bs}OBl_>u$GyWn;Y zN9^CKSkWob){0IG4=egXbhM%~qLUTX ziOyDZR`^=ckD`a&!}xv@y{vRd4|V56fR*l1Ixm8)bQeUZ6ZPmgR z5oe`)l&*?IE6Z!*Nh`}o>AFa<(%ldPtmvi~WJR|`h86uRhFP`nix^?0LwcBr+hVkp z4(Xxpju>O5Lvn=K(*SE_27KCP+u^E;gp>02{H~Z_hwJD*l$hYNcM4HoT1WT9vvvY> zPOxj3*dJn=m1To?(W-^N#ca|!k(c}86)Som3hZ#*UIwRd7OTy*s;6o5NVgHL&P>ck zd!5?5PCJpUwwQEqp9ysK+EVg4hjtVvZH<)|XKfwn;Gt2lY^iOugZ~X?dmz3Utc|wW z%903lJ85TVr)`CL@O$E2acy=$f_|>1es%}Kb-TSi>io1FM5b{_(Lgqkm!CUgdkKop z5cSn+D7!hG(Q<%xmgtWVq_K*=fIayh8?UI$8)=fF4~U*6g7uz>ElgLGABOazc86O3 zis&`X)?fp@9tmS>Vv95ngXYz=moL@2TG2ACyA>_hdRoy6&EJYvYC(p9`NP^TaSUMX zv-5j!hAnaQVh1t{dvkU!U^$fBC3!8t z-fnRIaIhJi--+aOskYgY`G_uHbqsoiprKea6c z=?tEOSa;6_y$9$2?Xei-VRw|5Kn~2`m4)(&RFu6y2C!0(WguToLD_$3DP(3;#wyVE z0m}gPe%4x$t4N+6wgHa!4SOHt;tn5zd~?`Vvaf<;x1L{vOz48Ls1M3Nhok&}eGrSS17R~&J`AQqSqnEPHkW>ObsX z{%=8lI4f>p9d9hyOokXa+vw5{j>nKq?-olOhve^uwJ;>VZwN}?fv9Z{a&Uh7K%CnN zARp!})&Jq~P_XPlv5#t<1+lxec*aWm9B99%3`awTWi4CVqGs62@$Pw)nU}%lDayP} z>mu;5+GR17uYz(N#%@S{FOomU;c;6!&LjB~NX|ZNRtDeOedb(FwKhX7JL*sG)}Mkm zyB1$q_2+BQ{_hL~u-}4?`47y$5``6Gb9$6X7 zFRte(mg}HxOZ`ct{4|IB{I7XsJ(nIG*}GtWAx1{p2*;n$yz1JL2MozSMYAZ)1v6va z2kZH7B>Q7D%5RgOH=E&1`|la`H;q&}t%XIDTL$wGW3+{~to_%6Hh^_-Z64so0-L*o z_P@RT-w}2CmpAJu{_O`_@9{7DrWG9=Ajf5JeirrTc0VInHffJh&tt4g`Uar4{#GUZ zXat8?m1L65SgR6@^mf0=Rwc`+B-g6M-VSYEwknZ%E3zudx7v9YLHP%e?Ep5dRVn1q z&v`Ai`vH_&=Q)!`q@fw^HuGT|1K_JXTxBg<;w+j-B{i+Lfrr~IK7+ciQOQjz;VrQw z4pvP7`@YqeU^B=0Fq90ZlH09LK*<}WN3I>rir}*>6zNALdm6o9)^NCEUd( zP>wH$J+L_(@1`dQZu{rtD*p46Y6)WhljM-Shi z9u8{NDbS0#IQIZQ-@@JqpHQwLQ1U_ZNGQRXgFQ@!9klZN9oe7wm;JMpjpdYqxpcgSj+e*{}&3l7Ev zqx}3Y|6Wk{f0cKK@@~}Ux&Phg{}FEx*ju;zzkHhiKWq~IKWtLL2G?T%`%kN(L0)iH zmIWmV^htD^X2_Rx?AbgM_Na|??A#pBRrXNEzH&YWBZ%h-oLz9vhvV<*_!!j1xi<;C z&7c@pxIPd5Tf58-!tx?0pHJlbn`_ zguc5$)(kob9u~m)uop}8M$0P^D91;C2ihlnPJoQfI0I62z6>%d?FPv4(YHYscc}-N znsz^kvlo4Cfc)G01jzH=A{cf#@W$EA{&sM@s4e<&Z|4NZGuop6H`=ubZaR-z=P81R zkO>L-$+WQ4~cuumO4436*h%>wDw59KS-lR&=FZU#te4>BihpLhw% ztF8JI=kW@ZjE|lV@}&-MLc6^>V^)WN^kTEREQRv-yR3rp)HKZJ_~=!=7k%CAbZ|HR~qGae6(- zIqV;h@9Kt-Hmr`d0$HnjfQ;Z>KziEr26=&whtly_VS%0Fwl8?;ic?TstF)MFLQgbp5fJ$nGM7r;&q zDh!Q<{E zUmAs1nrAX9LcG}Fl~t-3_H(49XkPC?^_@gkV39kvvJVj^ufgS4iF@c?a@V%lEEZRBqK@2kxVAp zpX4BtLrIP#IfmqTl9NeJB{_rSOC(<*Sx9m|$u~(ZC0R=HU6SiaZX{Vj@?(;plKiX> ztgpT}`l)G^ec)ShSber^PR0#*dZJ6zS5Pu11NY}o2AqHrsckAh5rjFZ>C=YwbNbe* z?wi5zWPx9qKh`}B{yV1QI-Lc34%+_+_RF-3P`)Si!yd4c!(8=Z$6B6%<1gTDj~ClH z>INM1@OvQ5A-3@U5c~W6;$zDUJumo>ht9ZCj({A>le@Hm`;d)YIcwh)KaXaPE_B3p zle%%%&31_++TVs2>sUR=TKyl8Z`vC|o94hrqgsXz$^U0K%K2kjTFT2u?Sh%Mgyf%Y zc-$uw7R#M!Bvd$`R=()5zY%gvZm!-k<6wP`NAkY>KgWBwoXQ z8G`Z?Hh zzK$p-8c-hTgYtL~N~aiO=2jN*?NC%W-K{na%4#Bg&uY*wDdD4TZEC zI9K7UUY$`#g7zag8w_AZSG*Ej-5js`y3_g0GgNYwN|sYT2aty?beu)U)8ROPt)sgW zec}1cD5v^eT92bMG zTG2nomqH!bEeov-b!6_Hs6Fj(OmX)-KDmh1S@1U{Aokf5~`9P#<{C z2rZF*5A|h6_>K@FVxyiC#$GIosK}6H-ftVgDv0J9Y6Et_e5_JB*O8fU-B{OTNmNgC zg=co*@cZ_j@ZAPJ1HCE$DW0f^%_c9)6m=%5_C?Dg7TF#xv+=b6tbpB*b~FYskM2@8 z+0(@s$jpi+4R;0Fqv(*MJ5a5nTaNo}1KBM_j`o>$flP-dZLtMLmL7^?3_BbHS+JrH z`;4~79&0Y>%QF;3bIFDzYcV<8`eq?%T<&b&=uNSLsY;<*za%*X4`t9&$%p8p|+ar|{6|lH|F-8;HiH~kV zy`|j}jqoKBd^d$un%)=a6#TOZj{7I+Ht>8_wNcWIJ~r^X%=z@h>I!zScbYMREmQQC ze}*xVl{ZnZ;l?Odspx}VJzGVyQ;ItG3Tzd_bZ8Q1`53T_W$ud3^b9n{F+W9N{+Y&j z7Okj_f3Qmeo1kd8f3`7^;df!^Wn1rTV-j1hsMvp!F_~2>YSk;y_#`{0sIPxutG=v3 z(ZQa9t@<&1N(8<1hkm9oPemO1*`Gx#ng_a6mZiuSybNG@iuyncY3!7uJrL1Ab`-uX zMV}9P2eul-ej&oWpdfTGyHA8W^cvfA<`;pMNK=hN+0#UF*BH)Tu+U872v%&N%b}y# zTNcVUX0i%Nv`>s>*Cnw6&tl_P)~IOC@P)>4Y*!?-7VGk5NWpFf3U=C5zOJ$#x#)-^B(Yrw|Y!a(e^dYR&$*f+{)9I^>Q!(dEbcOFSpR=@n#lg-nm32~x{V;-RJ;E;|pxC()}IqzVSsi+Cm>0^Vl>CZ8yFI*QX5rirC))dyFr$Ef%UW z7O-aNZ8-tc}S(wuj<2+UpD`_jlSIpAmBrSyu&S#bJl2&(n-}pKc2~Ff^ ze1kbFDoZa2UBEmow8nNJyG68=Ei@c4mM}vi)?3P+bv$NV%zTInSV%w4maqv#a@SwN zN{NbCLNMy~DEb!uJ!}cPB+0OEM5f&m);yL7m1wRZ-+jEzCU#p<*Obp3H?cny)%ULldZ6glzNdlg2B6QmhMT=I?KZLIirT}c z?qjKV{WDa+-vOIk_`H2c3%%`aA5x7m$Mlc}!THi2 zSqYZu#c@s4Y9q_!#LN+zVV4Kfb4z-78Ixg_cu{C|c@+>+}r%F~xw3NAf&NS_3 zYZNW+h_px1Z@rODDJmL`RIjLK0Fui|8%X^A^vU6!$Of{@zIMQOaB4oGe2#X$pw3PMkvfK0x zTR&3jp6T<2>07o<(Fnf-rlZVdH0p}jjPBogI)PG@W5<6y=QFXgbSkv!(6@pr6?KrzLe9vC(v%RgRbRQ@4$#i)_I& zNOM_9=6Ta)<}*Rkf5zT4U1hf>A}wX_2Hi7VXFDe&EoR@0yl=Y6dQOqlw!KZ*&n$%q zGv7Sy4jV^=*>wfVC7R0`J3bNiD=W58r?9)Mny3JDJ;Ls>k(V_TjC0TFs4HU6bV&`ZoX!n+_%QRLhcsZi1N;Yh0#y-T!B(WEh`JFKV+(IrJMj6z+bB;bQE@%Zc!e>@ZkX z6UnPcKYmRSUPbog@x|EQ0l0<@b^@A@gx8eE!+P>nL~>{7#nWF$-BPxz@0qY(e2Sv# zjEiBt`D;XT4F};$BY-a?DlnXcvzY*1Ds_y9{2CU>4R4^Ax$LLRKf>T}ZQir%(^QX53~3BAS!ijnogv&py}PclHS_od7{m%zW7NZ=c)o?4 z!ejUz3%Q2J@dib28(rZEY~w;%&pgH>Jc-X%G$_(FJeluQ6bjUr>u*ZSO+nt_DZD2U zegbt5@6V$pvFlxYTcq+Vq9S(L7!sb!ms!XhK7dzSC@wsW-%+&L7#BW}yD!2PuwLKr zK|ESf>&SuOgZUIm(BAGA>Aak1F0^NFo58Cs^(s6wIQ^tDc^Mi$gu{uR@;N$uD9={3 z**H3U7%#Etd>x+Rm5N$NJ{>-s*AuN^Gy6;qAHh9f!@yZL!)~F=g4y}xhG+7T zitG~tjafWbQDVTX@G*RWBtz(cx#8pZdLlWK$8mZK2;6Zw)NelM(w1NgE5LFIP%hDV z`*!fzKaRgaggZ269+myqdR-k1O>k{&D__Mr} zXa#c!*#T5;p}p|aKz|U)d`{!8E3qDCaBlcC?nl(5J69@ohx#1=U4tUb^)&AB7V2cK zr}1DSnd@o1*h1ferR&>hiMc)=KArm!6&dopf}N)Gctr*9yy$d3Own@#e+Yk`PgPVl z^b%09q8?$vPS5i-ibBI&*zS~PNhR@`CRyR?O=_P)24U)|DEbg}!3FmcN^DG{(2y;D)Pau-Hp2c4wlDVG6_gJVC z)bm@1UNG0b=Gi=+NalJrAE^j)J)2KeloT55^fE72G#uvq%Y2ohUjoP5%;DPb=5mDN2NT`CR`G_NB-$GH|6apSvi+(JbJ;if|kXc(fuMhXS5L zB*)=ZK7|O!p{IE+&t8u{1@(Vo#RY(7sRl9_m&Pa(ogB$?mfN0km|=; zpq-Ko^TYDZZ}U5f!p-x|?{M>0thd7OTiAT_yS$iaDcj+{+`O7QeS*5BtYhT6=C!<< z=nBW{;`RKN(gh7%Z(h&e+=iBN#5V8>A~|9kc-14i`0Z$kBUWMFz(*>=5!=A?iR6fF zcx1#j@Rg*)@%t3&ttY~H{keG~-$NubzmeA|U0KGL=5p@zDYhq9>L%`^2v_PRb(SYH zzlobkCs*qGJY7+Z@l*5re5!?VOds$yL^9e6zK2LgTfu7|(IxL_inhjF!Lt-$v=zLN zNJd-1-yxFovx0B2P%YFuOoY*%F>mIVq|Wec#(AJdA{qN;?y(cw!&!I3{2`w}BxC=G zFHnTBf5g`-OZ@Eri0@H^vu+E&q^QPNYu>_Lc1fT3+5a&gMucm} zH|#+#NcX_flL)hPJN#2_lsdyZ@cI5JPg7J5d)=ozn@Hwt2QO6h$}(&tcJjkYhbwg# z*FVP=g<_zd`=}`8@^uAl_zIMe9Nm8)%2Mgag<+DG_B9;5#MqBkgT^hvjiwwQI8RCM;zmm ziR4Is&x@rFM*2Sy-}5z!aHPNIRYY>6zvpL&}Aqbr*_?n@-S)bT2% zJJj#Hh&ou4gmpVFsIm@Rg!tEc6yf>Hk9-x8%;!&h4-tNnT#GozJ&$5vx*zROD<(QP}0^>9CH9eI}@R)n+sF26%0XZc-jcN#6_ zEQb~>)Dh}c5#jiCiLB>!Qb%X$^}JpYo~74w!w=*I_Hw_-d)!melW-<*j~f-?mCZe# zrUitwuEcV0>)XTcwQClStq(8vb9>I}9nXThI5x=s?# zfQAmNK7>+>c1+vym5DC>iQ`oJUK{=YYujJea7+u(*8&P>Q0r+m8Smrl|9{v627q zsft3zO#qtRL@}9m5BM@g(J?mgHmEg<@a_QzQC-o@eKk|Ml|!$pIl)T~T9 zAzUtCKZ^{bvTWduVIGPGxsA8cL~xVNZKY8Y(TZ?vbumoQ;!eR%x|piyy-qIh&c{MU zCcj`O8?j1JU-+Rd8?l8*j;*aYN`zzkT%?`wxrBX@V`~tzFH6F)HHc+Ia%>I4<0|Up z*xHM!*Cf>#lOyfL&g)H-V{#A=ZXn6AbrgO#k#KBZh;$V3ig0Wl#RMWbwvOTzB008> zVw;8Xp`Pb0^nznMFVaaEiR7IHCy}NI$H7TtD`GttMm7_(6=4ROiDim#bu<$dig0x_ z6FU{*>TniE72)b|7Ij22gUv-F5oWM7(nVDMj6IVXY$4Krk%Sp+A*K+?47L!iw^1iE z*ivj$RAbDKY$>MRkvd!*twhAHNHT-2Vm1+GaCM}sSf&Uw=qh#+$qc%RZ;50EUBzt+ zZGw6}zsVLBdT)trEzCqRgRMonBFtcGF+ouXtbjHmUlFc=He!Jy%%_`JPbBl=CaQ=q zFP}!bi}<_Pp3F;IQLYH{(pFRw$-J}`{`InF_}S4;tWs2C+!WbP==Y=!^YVmviAd(f zLzEL?UOtcX5S5BBFCO9&k<5#S_?t-P#Y4FKjy{nNLcMq*oLS#QwihF%j&|YpB3BXS zrM)Otbg0{V;T^;pMaR390#y*nOmq~7i7*qjk)Fc;4{T3n!b{{U!c2IHQX-iNFVUa~ zGto(;HDEpb6g?Q(NmN@X$K)-{f1*xi!bc1vlAl#RV(KHh34fy{X6a0%kH}YqS@IF< ziDZ_1g!R1=AMqLKFiV%AUM&%3=~iTCaZBn9hq}2KI}5!LTPQLd?^X=tLL@WMMfek8 zCVq?TD)N*L&(D0tPDPjrUvX4f;`y1caJi2@Xi&+gs-#yN5XrGMi7iAJZAg?!R4c-0P2v`jjMnr>v?ih93odBA zM?pOgB8)aMDog}R9YpJH3==7ej(1xEG>k|_8!qxxy|RpyD6^wQLhN2jSx<{ zj0mHR5YdV-+DMV7sKyu-6)Cn@D902fej}36Mhh2wK?$wf!BNq|R}n@VEmDYNw9$`5 z8!g6>4###B)SFF&(Pl@*h-Fd-(PrAkhzdo=yVG(|ftDoGS7 z!f2C3IgyMu>5*uY#22K)XlFsaQ$!f;t5M0~j?_W4I~#h(&5hzA3(0Q7K}Ag>p>&#rs4u+Eh_Z zgfn?jRH`_q2%}9Ex&wNV(WX8UZK`;JbQtYhP%oGWqg@jrMrZUQzZV@LW)aC~M~YG+jP_8}NU=o`Mmti}63J*sikn0-TKMhf=I9gY zIMj0|!f5NFMhSnZgJ?S$M~P%b_$}}#F_K6|J6gw0rj?d%Dyc0_Kuz;4im}q@=4;7B0T?`BpMX` z+#%R$vT*UDoEiS?;KC*gA4SW@oHtJqW<~4Abc>!M(iQC@nxg2YcOd8r6;1Mq09vK! z*$&A-+Z5e54F)=_sBGwHpi7EA9-0kwN0BjVa&(R`bdo*m7c~vYRgq^_u+y``Pf?F7 zc!QsaCX(a#oX8@=T{tg#sxbJV7x|epO?Y&cgyT0&1QW@XI!!F-f;xGwI9;6TDyhck z5j|a`cWa^?)AM3v4;km+>NRD5wh$fQbmn(K!Xg<`- z?9$&S0CUlb*ZUiUh0eo>SY$;`hfs);c3 zRnd7O*dJS$nV%`@6=CLQ3a0=WE$$jKMWZ5IIWLJ@fl^mvoFDy?sHaD?PI0H6RZ~TfJ{b z=Zj!PpLyQ{idM88?hF-(VT$%cdj(>OqQkImUloOlPQ$u=Rg@~i`!;jM7Dag9X0E7G zgm;Y!MV%tNYg8z%DZ+a}uL)+7{lt4guL)O0WkVaIi-ez|kB5qwB4H$w<33LeBf@dF zk0}-vVdzDU`+VUFIc_==m@hEnm|YzA`Jy&L5{~=pA~{MDd|E`mE~=uND97}M@QFo| z!YK~h zlTSA+6rPI8hIWm4Qy3L}JhUfJJdw=AB9TpmnFx+45!FhEpB;;Zdp!D-S8j`iKatGD zVo{<9GqFTmQ&eMY7qdiUB|OZ;Qc+7JGqFtEA;L_A$1D?uM6{H9&obdhBr~y0#1Y9% zEECxlN`QLXh%gf=G0VkasiRCR7nc;_oL?>)6dmd|C}xFlNs?JA8#)TeSJB5q#{mTs z$-Jx-=|q^9Nin73l+s~d-V(;-hk1ERq!G!yyd^3XVP4)Aeor=KA|VFec50y<(>o%* zFY07oR*769%*(WxRbqi6oFS{k79yFKRpKC#%*!fKXQ5e8&!eAg53U1Z-W9<_axZvS zq$t9?yeqO4;a>2b$Ww%S!F!@mQQ6SqnAKvHqK}8Z2~!4mC5$5G$%!gu?)KOkO6x$Tx?14A#Dr(soo~;n)6nS=rcZ`dAMMltV5l--k zE9Nr^bXx>I@QPG6^m@$4!l>xup|^qJ6>Wj1*tUw1imKo#wyk1%@ng2azyC@!l?a7_}Q!#9)B%G0-im600^Ph?-!%!#BrFMwmrzF)F>tc2Y z-{DP^W7;V+_)n^)%yGfoV{uJUdvEw}9l=Iny&^-fw+s7BxGK6eXuQoH;ipIp zUTNGTjEeN&@iw1}G)0|)R~kPTBNhGVG~Q;f$W`>a(@NuBk*}zEf4kT(M5&^V{hI-; zSL6@@{dfH0kSE5kSc{me4AXX{52WR33 z#1=(iaE@|NR4Yn_bCiRkPSGT@YizZsCzA85TDWG)5i85+82hzo%tDg$tVYz0m4x%G zM${9@c~&FRpO*D-g&z{v#!IR(W=9_qJ0~-b?YDdy_k}NeUbUB75cN1FrT%;gGlDHR#Yj%d>$A6&!HurdvA?CE~+e)V>%&b zOhui{=Sfjcg!v4OJt-;`VLnfaOGGlCC&k}HGM^_!=V|Cgo{66n{zRw~F(<_uBFt`M z^eM4Z>I~C6$HblzM-|QPoCH*-2*3M0E$S7Gf()J(@IW@SP-H0ToErOsa96arb2^ZZ zq8xa4!x>>#^g{MZ;~9~x2uGz(WGTW?sS{Ha-GOA zM7umBIVu-L3K5RV*w~9ATM>@RMNv#7N9CeeO(aL#mAxifW8KW3CF< zmmbD0(*z2NP5yp32943l1@OWAF4D)hZWGkvMj*Gc1sw|Xax+5aypry>quVN|@=A|(9 zS5d48^YW{xAd-3cReVV#^YW`WWuZ4?eiiU2Jk7d=-o-J$33s9*1IGTF@K=Pf|0c|e zF!sA5ok+%hSL705>~F@_i&IL6vELK!`7#q2`#s@LBxAoPaus3hzl#b*HO4n%eiwHv zlw!q}I`Hi%V+#SI7P>ZYdqc-YER$K8(Fl#1qNb8^r=e82f!u zrKrYO6?0!03T4mmeEuJiLnO!jfmlO?v2TcdAa*Lk*dK^GA{qMwahFKO{y-RBL!U^u zV;%^9B3$p+W0;mKbu{iw%Tk1~Gi{0@jGb$RL^5`+ttZ0Rx5Ns~P$Z+p*flL#5stg2 zWf95PHLY9`#;$AU6xA4S$LN~Ryoa&dX!D6=?6%q-B8+`UtgTk72xGU^8i-`PN@tetjL>foIH8=z~7Cie(-g4f&6CohIq;5QKMG*?Ay{oIWP z%}>#%eoKIiieB{FZ)>lmDO%uXXRy~sDzfeFVsy}Q6}9PJ43w`Zzk4U6qgJY@r28tM z3Pl6p=~E}IO3_$&`qW7~MI^_enbtsr<8V6GS-bQ4!*OV?U3)_kjze?!iDe`?4$U>s zg_3X_Tr`h2CDj=B#ky#&i<&6M)I$56NRC5G&8Gx)I1cAxTWatR5kxo+EwwBnISwtg z7m4H?Y^g1<(6s25S~(Gp!{q2zTBXz(W{2H~ZKa(elJT|D8kA*O#=TfqEq*b!CqGkL zYZDYOPQxV9QwiY*iw_Jy?qawn4UwbhJ7IDQ@C+G&MK zhwJDGty~et_k>odEb%k-3GI|3jL$=ZpFn~Z@J!A&&O`IFP>!j+mP;h#>!7VA!vF2; z9M?gsBq}n5!M#H-4P3zAfoAyJnwNG;da;j%dxu`y9YtoZUU6QUVL7&kUV?$v5EZb} zLFTwlnqdX$*or=>VcyzdMV&jw#rbFpR!ZH1KKwdH5LsVqA;d9f;tiiv`fUd~U$LD#ISxZ;sm{ApH)}}~e9(}(BDpB-HrxS4z zS``uYZT03f@RQ}hgs^cMH4}<`+LXt*OG}Av-f{mH-V@^fb{^ipI9= zWE`Q5Av$mW1dPK7ZK9&quu?~8Q;6moj`evLyyVJy?59qtVIwp{xs0LG_@DTZ+Ek(f z%ETB=--NmqY;A{6*1&wS7c#1&r6emO8`l zK7ZSc*N!ROp?=@SKcoGi=&o#|t+Rq*m?q?4P_p^tDtHVOH9v%{|m8nfWZE$r=)83%=aHakl_qqwQ0K z8Jweitt?;nf&YNj{!(;+sK;i?3++8~v>-+M(|RV%(ZUt&hB-J#i&KPiaE{hb(UTcL zU^!S4+uJD;=mkX%eG?L1(Oy?HYCvjOzP5#EF3p|-ZNY~WAML^gn#V`7FVA*JNhr{Q z6}9aAys1D-QDh{_R&)yPTo!1Biryw#ON6-|1YXWbf-GH(c};5|k{NtWbKipP$-ca% z)k%VSSqZOc#*bxRLI*g<7i(#fXvB)OEJcX}auViiYlyJD840gzcZg&Qi!_(5(hKg2 zi!@(F`_l>&7HQFnLi#TP8b&1hvPe5cg!{{K(9QWodcl37L@QE+_l!%lMUtR(_zh6) z10w9ps)QxlVIsM|EY~h6!g;-1YgB~udb#Gh4O>{swhu2$Sgz$M8r5uT!V0ZU(Ko~Q zC9KpYY?qcP9luQ|)pildb@bLFv-~ZsnsjoOzoXqygtL5=)*4JU8%a!dk6T68ompZ$S0ZlIGGnEqMoeL7%_It<(M@lC7`T9ClI* z3|lYLo*=^ac>bBNQR_s6Zzy>H6iiga7PNOvEZ2rfOXfbfW#T4ns)alf-`DCCB@OD9 z_<>e$p`gSH&AbcSlX=;!r4cP*Gd#QjSaKeq3t0mU_bPDvCB@4?U9z1 zJ;Q8vY3Yi5{C;Y-TU#Z`KHT`T;~wp(qSuVyIexCCd`|W3I}RW0`-N7b$T4FSP%Tjr zvvaF-{X#SEl`WK})^@4XvWVmxMk}>EMU&c$O03jMlx3F~&h}{)N_QdS51)P7B}FqL zCnfIJ8Wgqf`G-%Hw(JYpmldNk?Y`2sDawGmpmojYnJ$Y9)#WMXq=KTJzX1b(ea7;(AD{B9fnDhqc>8@Lx%7l5GxahA+|b zJfAcq)#e+`tY~KHV4H8Xk&4##d{R)lrX^SP!m(!ZR)N!0IKWJ&n5@$i3HdPVMp1P)b_BeaaHWAFvQHeiU z2*>@LR-$~)AM%IKIqgG54KVKKwMyj^NBW|6P7#juMT?h8m=hPZdQ}f+-6hTSfE*Qk zuk9r*m`KivOIo@joD-L|Tt#@bbXi+QgsbXmi_4n-LG*$%wMs>odOy?hsy3k- zbp`DEUeC0=rkx@xVuKF-Z9banstG4Vg>Tor_)bclN zH4(n=_a)oA+7=?2iM!fSBDpr}wQEX;d8yZekH}ub^VcowwWEq|`uBCXr}=#&b>lN< zcl%x2qR4l+4QtT+zD1o}D}QRkh_1knlGW@_ZIziEZ?Cit63v})EqlzYr8;KqDQ;KHU+)4D*FDTk7 z&L(!!Zz$Rh^}O|ZML$8k&U&MwV^FV)Zd;47&t*Z{ABn!Yv!b7E?k9HB-4(qHx*ocx zA~)OniGF%_MUG5Q>ZJ!N8lXEQ_14XbCbB;g1N3-B#rplkKs{B_4yYHb4^gxa>V@c8 zihA%CNul}#Mb0*ENk)CTqA|QfQkeddqIeshq;TDO#3J-UrMs^AIz;Fz6$RKD9U}D# zMRoRl9iktp7o%4yT@}=e(QgsSIT8DaWvt$yEK6;S4zar3ahbuL;4}Ua%LLt3>HYwp z33?|*QP7t}Jy6jNsF(amy(jf(rE8|!IzFinR+M4e!LhGCQ4w=6I;K2QufIND>3sCQ z4*m7TMEJ(R9!aTsDUpmQRo_5VWN4EC*YEm=itNS(faRx>V2!Obrs@YQ6q+K`Vy^2E`{0(r-$lYm349A4J%CTGC+spGS1fPGUWHngZVTJy>t82wNYl zx0eL1+pxiUPYVr6O4kPv$ry$_68jK+1nK6oY;APX5Pg!Oe{4o4J*Cf7bl84$(n!5f z(L#sONtyZ@MPr>tCymuVP&6CpY5h}0JAo$XUrB@49@KkBi44qw+^dGo; z{-U0%D7PPHdHNM1nccicvYV&>M!LDoNB3=!r|Xx{%m21?A>CZoNxT=Hr@KAI(py;` zVEMvhEMHZY zOZdL#dHO;{2gDikOnsSEy_t_>cc#8ZS+3)^!)NLnA7lB6#j-=hOnvubEDtElSdk2t zM;>E&+TwErSe|=~@=c57X0TlT7|XTF zGFQ6@mgSGJ+@>rCiO~@IXOFQws4VyM3b6dmOsu zuF4s*m5%~TJEH$RLs}`zbgdXH+damzi^Xy;SoVC3rO9G>9W0|CW0|5XV?}lBOnvZU zEHjm5OEDR;H2yJ`(=C?G;%4eICBb<}S<*~>0TF(OxjAW;UP&acOlRwN6yde#%Q`GD zrqSK{nT94hz*~6!)8Q4RJHVYhwJFx}tX-^yKCGJ4A9Wt$f7i zN`0fUOm{NIKdLL$KTw0?c%r$6 zZSboA>-3S5K=(wKb^2_gD|C;gOkbr4@3EBWTNI6hI}&Ajl_ICisX+2w&UlB%EBPVe z9i9z(owVfhhjdNepf@UNi0qlXQ7`@(dxrOBeEc?9yj1q=n!HKhNjiCN=6(IBvTT66 zBkww<~KiLxmkZxS=O-Yoj3nK_TB_Ks$zQ|tuytIbcX~& z5+EZ9L!g-hf<|p5NWvt6fPf4gl7P`L2Ngwa1h2#!XK+B&AmB9`96>=r8pR=kGbk!Z zBcMcaKykdN@7w$AhDf+R@B6Laf4#NdgH_+R_pV)4yXw@LI_K0e?-H$28`u6ZvqrRk z>oFZ4H@C1xV`ix*%(qxWEbZ}CEpw-6w2huL1K&{V``t;C+>R&Bi)~FCjxF}2IaBh6 zV~ag$E?~`$?8!ex_N2Ll^A@Q?s(1dA=B=XbHFNWyGVc-XF86@^r_D{ahREg(T4%m3 zd1VQM^VgfcZ|#vt?lTfvsc2g7-JWO66%p<13-g~f*Re*=YfQ}FVD1o&p4Zr5e#@Hu zyv7Ez@wycoOy^N*fyQMT%K2EMUkhjCZ#47lJXhPvSmn>0AewKor8b%ui&mb3b^Xks zXtPr+^_*GFntkMZ-mGJdj*6A}o6Okns6G41mCa_UX!KjYMkDdExrsG`&BIaD;76R(-GShGjsHS-46sPCG3&0KLpTZM8V)@URi$*(te+j*{= z<8nOp=C`7)h>KPAruj3qj+N)moNB#pCX3eQ%)9!&ZgynN9*ON{K5H}*Pvq|~>m-lP zjc=Ih7kgZ2B;GKSShGjs4YN`-8i_Z}kZ7;>U6uc)8UL$2O7z6wTjmVb?2&letY(cy z;@SMS&DEmONW5*n#+p46Z<}G(?2&le^!=NAgmKx5a@DNSNW7N+j=9>-bB)N!_P=9p z5^Y>R(sqh=}WA<78 zhvo!3kH_povr@ES_`C0iX0>P&@OR%2%^J~m;Ja)enHxlVAK!lZ$lN9x?QtKQdqkr> z?ql;XYxbD!F!fPg>*Oa6dCEId73FGG^p|W?uCdo;RHEtcK^!f8n|Ogy$phT=^HCYsK?L z?KOC=`wP#Ic$ONU!BZ(sjZeT*a%%2HbTxhTWTi~?1Y`@}fu`J0(f=Ll>NzfJ`zHpd zw(Oo%iewGnDJQfoQ%6(oB|uFzmcPs)%Zwp}kDftzOEGyC=bp5T=Ul=_i}!JU|E$Hb!w- zjj?Yc*|&0wHa_kri>u53IkZQnaf=@nQ0oDVDzVJ_hh?Y)E8ad)s7;xw@9LkWI|mniu$u`sHaYAG1Yv}={#+_rK&mizw67N zXJ+FWV#oQvg*v`*PBXR>&>j;V-#d@Ukx{Z+e+@HPR~HN=|CQZn?`UfG4FA8gTli`< z)o;%Vdz9=wvGIu67JDwel1BcgqOR^Zo9s4D^U0{}7<5(3`;l!AO`)i4**-Sf@&p`#HQru! z?+o=d$A5A(bk&}ZHq?r)9^+$?E!&>9JT!%R^!F{~uPH)ZO4+U0^^>gQOXuW6mWbT6 z-Nr52@@IVXwq?8iZ+RSS`Ci^$_GsG{do)P4&l-{0OLkptFQI;|0h(%n9oyhpLw#Z-PT|n+jTj%+qE@5 zmfB}PIveWh%SL@{>SM=T)12yWD$5y-xAjRm;(2m!(s}Q*?V8%ov&*hiYUVMQ8d$cs z@89>NKI-b^7)IpA$1(e8oRCF*vCrU*N87g8Xh&|#c2qRGbd|_wrRjX$r_qS?^i{rc zXjf1DU!K-#8`>FII=C8#`f1SoZKRRzRGR91sltaTWnnRq#W6@O(SXAucl-2pGq|y ztEQH}?&*K3`M)jozvut=YyST|?GgCv`SW*1>F@Y7ooS6N|7{Kbt$*XT{*L_L*8jil zZ^!(9rj@4S^WV1Hw1)p@mj6k+|C6$fqy0Pe{2ibFwocps@7Mgl;n_Imrt;ru@qg}f zX$D=}Om0he1okgWZAa0Wn(P|eZPdkL-@n-S^aWJ*D!#%$m#;i+*}m_v_+Egnb#*n~ zzT3hzv#ze-d$HE+|Jm8p%1PN!^V*YTDt_UyTQt;$iFBVrvEf?_{2PVcihUJ*dQVN2 zmQoFNoF}h=BUi%qXrI2UrDFMt#r{3S9^uUG|NB7CD2r$PwI=8mHMVP1~}!_%oQ9 znzGUEyWOHauk3ki&(*FNB~3NOzu!lDgzftOx>Pl`Y-H~+$B}H~QNBm8aS&heoQ zze|uMGEW-INx0XDj6BJayKGxdnxdbQ+t~jg&#s&MQYjnl-+QmZ_KH{1pF}XqwyfNN(5J*xvZB0FC=)ZwvA?Wb2R|**f-p;z=JI z&9LoG_nYDWH+}38KFye<(I_>w|0jRj-nTiG>dJS!gt|&9A&Z@Rc^1j`-#7>(x%4aX zpMQ1yKWVXlXWPMNSo@c^c10A=E@flns^Ao6b$-sj0K!V~?J$8gfXsf2p%e(RfDAadcgw$#sQ& zGRq6rw(#Gl?6bLc z7Flx9i^x&*KOJ2g9}}X6k$+Q6=|J{2Tz_M3`+RMWwmsi$Y2{<$RcEyv$%k}5(u0WdAtWEt zBS?=SWvL$ODI_1#Gf2;&2QMJ`kX}T3MfFmzBl(ctM0!`{sP~b4NFO16qI#=Ok$gy> zA$_6FP+ueYkiJ9u5pT8qisVE39ZA>vDF>2I^;1#c80}2e9La|fO8~dV4v~uFQ)j8R z;PzUs>V)J|xvDF;r*^jLgXB|Zt24ppXy>T&kbD^F^TC6)bJcJppE_5K1dq}BtMN!a zY}HBNDcX5zDv}Rd@;4}4+>Kr5=X5e|?^Yu~q8pbH-qttlt zB)tf0?iWEXQdk*ZU8av#LG2>77=J3iQJ)0)Vx(E>c72w*6X|B8Jfbo*20~mo0rb@~M4T#eE-ERu4mVD|-Gb4rr-XEN1|turJW+3;h`TGZruwGLB;` z25PFD<@t?`t>}c=j0=IORTv?Ksr@Dv)X(r+taHXvb+&@%HREuD=0M{oC5ACgT~=Dn-wR zF0iKKnb0bJE(eHbK#y|#zvwqjY3Z2FJ+0So%A93s#>ighVHLE2CHW4`=-+F&qgLHg zbP@11PciV_UY9sL7@sR08&$UsHI7!sJHwxXJSg`?AhnnQORRDmCF8$!^e}Ep|2J@O zKew}o5l;6wH>w-)O`?seEa84ohUdS|t)z;m^4>*@{v`a0De50Duo3JR? z>8e#VlM-A#j2`2<18*rh8|$-8&6(_U8;i#dL+&j_BV8*{=X}>{#yZAzz-6vYEN^4n z!MKy{dssfmc!cp7+mEwsI;b`eV?1z~E1BgiMjtTWP4;|u&I!vhS1!wWj6)fVfXiGH zST1F(0Oq^Np6{+aVOi!{!17|oYR09&Wv&%0uV$=cT*vlJKuv9ObaE#|ZF1ze(}46w z>o)e;LicZf`Hv+iVh4{(|5AP~=KIldiodDKA%z0Z2YakF&;u#NY2AicMG#PJyF zJmR3YR*$j&F~{@GpMy;AsvdLDJF3SV^lmEE`Er!$q&iI}`It_6FVzFfe3$8@w^HLp zrgu`4S@r>6j>>iR?CExD+MDUKqcv?{$(7N$&WwqRfoHaIJ9C{Mw7vm8$6BWWA8fq@ z_TO6F4(!)@8E{{##nE}FM^kyumECTKOz*W0<@$4h*do$OOm`}EchXb9#EEBGxy}?k zd!Fm;F!cH8B4=FsJCLs%{9*J2$X`J|HtbmRP?k%bUrca#(4x=nD0SkyZ+KDQmc%5_ zhkV2!O!pKR^b})1K1$41B=^_4b%<3JPTF58owN_qesP1R06vx~Fzy*r1DWPK|K>E)&YkCx9YUYz(U@qsJ7jnDq~B#y`Ez2nO+AxvsF~PFFkIf$M~b3`5HZO zKVPFK?cIh^I>@`&N$>g>7_YZS&BjN-+qj-;C#@N=6#Y8pUBU8d9=+90XX2SC+hcrO z%ssLf_ZUAmW~FgmYGq6v`_#cF2R?;3K4cry+J6$W4)*!3V#xoF*~Ic}$Vtt%v0M(h zce5QVk23~&Cp8;qY$zeTo$(W3Kcm~Q5?H3SzoOYr_M8uSS+hMNZzy@H*?jmD4)Q+L z%x$dA+1l(N;@{P*O8dRnAI)lw_;FZSSerlA5&NWZR$ou-MuWZ>wO_k^e2ZAOv8Pu? zEJfR%<*qE}#8R9?VEN*rKx`GZNOkNxMttBx}WX?&hR zsUG7u$Br}BjD9_KBOgEaW5hId1W~yi$GC=L&h1mu;*Mc9c8oj1wH;@R=^~$o6C*r6 z&Vx5o0$#VV8vSw`pLf4Aj^@d#xOf+hZ8Gq=xGbQie2h7aSuR=|!^fDznB}51B7BUq z)tb@$n&+^b<)YOve2h7aSuR>T!^fDznB}5XDtwGNj9D&PZNtZy!qpprjO_=3INox!2RYh<9PL4l_8`Z3oc&EVMY3OOUie~5%;C}X zEnDeZ&V9dS2F}_a!*W~7*DdLo@RVb}MkAS_55d>6_wzBqgR=8o@lwM(!`mh#L)KKX zn`TS0n`TI|o908Zo8~~Wo3?who3?cpm-TVk9F}ug&I97;$~EM14S8Hc9@mh^HRN#( zL%CEDmzuzGDa#csSF*f-<;5(|=NVGXmZfZ2!SZUB%Q?4>E$i5_iREo97jy0ow(Mlf z9+nTXe1zp=EFWjtjG|HX05#=_qS5d~(P((0Xf!-gG#Z{L8VyerjYd2?vG;MQEH33^ z%wf!B%wx>r+I);TjJb??j9J`@k1>ZamoZQ5)Orrbkjt3Im?g;jDaV$>n9G>Qn8nfh z7;_kN8S}(Wy~yFd`j;fTgf^h;zQp#AtIDuO)WvpPVM2quX3xLnXEoQlz<)tjIV0krAQ>!_W)!h1O zZgDlYvYKmN&9&8WsdZdx6U*CJ-T~Cq4z6Jb*RX?Y*ugdI;2L&t4LiBi9xio|XAN%ULY@Sk7TNm*qT`hq7G6@&uMk zS*~EYlH~Jd6=J0}On!^jCX%;PrrrEL}n&$9=XxjRVxl}cmTFRxCa;c?UYAKgm z%B7ZasijXIZuKbTh(|T1^+VNgno0%T+@i3YmvU@zVLY9YRxA7&; zm~=Lw`;O1MC%6i<**TVq_t3RdmWR%*J`cU4ig#Lu$E5XOPr`K1>L~Kbg?+xOP-@K>{-fK!B`2@R3%5UfaR)VD63j_v85LXF;AT+OZ2F|K1gYNQr_mwwb3 zTiiTj6D%=l+kiMvaSS^khvIj7=x%fm*L;v`-pM{Y5d-c_^+^d+tt04Zj&+Rtdz^ER zbA+1aPW>ZeHhS%7TgCT@_vk4@+qaD~$6{67d-Um(ySKeZAJ``scxLnbwx)MhhTGv` z>|s7V_W8E`%-!rcu+NKa(v^9hm*&7wE}I9`RH6QJ%Gk8odLiQMVYWk$3iTGKp})EC+;HYfeP`bW zU?y+DLTo`zOG8weR*3tHmHJ01e`FMKJ({+&@35>QZ|&rwtO`UmH>;APTHvL(VHSAl zy_X5zn=;*wA}?JHR(ffEk{pUJ^3psk^3t3v@;>UX$y&^oYOcB3>+14p)>1FsJ5+mL z9P)KmwU^!vUBSILYE(^W(QYyKYq6JpF<9-rVe+VU6_6*kTa9+9Me22d$@@D>y%W9K zyDK}#s_@R6ycT7t-8%1D^+vloZyWC?KzgHe9oN5!aT~Br`#SH1T0W58Fx|oSA}_5% zxRd2Q?307L?<*Y#x$i|7hn?*!yfgzBGcNVgT~CFVe&49@(%deB<*xXp-ltOsM-{Q< z2k^_64_4@7jwIn`<){={lq4n2FdBoylXtg&V>bI0H=9eHN<4M1e z#KNDB3Gp$s-dcPN?VIs2v}Rj;3~l$E7>az4gYGW(IOv{qkE1N1BkrlE^`JZJAF}&K z<#7Bth!c0IF?7G57sGd{F?82IG=}ctk2@RMW6rQ=Q4HP1Pl%yA@lwVL*fDcrXkJys zyx1B0i#cYft5YSnUWwS=i7qq`O-SjqfGsQaH%4T4T8vV(MOL)S?NrV3Qn5Te3Ij$2vBEv}3Cae~{i3As7eCXp$Ix|q-WZbx0r#NKWP>55 z$Fuslchm64Iv@AaTjj?wmu6YVF_-8L_nqPS)-msA^ecq--RAFiKIYxhV_EdJm<|HT@>Z+Y>At&O@9rD!tK)g)i%IEld$cbm z+d(*g(&Ffa+Athd7Gjm{N^6{XcF}kr-9b$E#hJ9?>pfao!er+-Y1h6WAAva)u&lUylDvqU2>YyOvrW89LQ-#^C71h z$w$2NnwNCRb@F^@MwsiQ8IseC(1pKuH1F!dFZzkVzA71*t1^H^sy%S5>fAL(tybNE z5393)bt)hDgc=20r>+EUP}c%?sKxN_uC3|nRyo=ukp0@@zXep`b)rA ze7`hG#p`bXll1ptN!IrP)AfVE9GxR%9IH3L5@2~2V_=Ti20 znDGgCMyXBu*T9he6L6cZy1CSA+}2L6c{h8884q*K$GB!?P|Y5LYK}Lk=5&Li>S$0e zx*ODbU-t2HZoWZdJHp_tW4O^usSyLLFj@ew;Ict3Tg_$f=CUsWl|&_#w5lp#_o){jQNaX882p>Wm4ozJp>YVxI{Pioevs+tHDP$SWNw zzy%KK%VGzOUbUkwaH*pMaD~GMTbs^cXG{pxb=hF`Vj|> z&oRz5oxEk89IcZgNp@0HSx$=F=cGt-oK#O9V-b5!;M`KSRB{aqSgv*shUZe2SF>El z@+L0z8kc(8NqyhVt%SM7!%m9lTPOAXxE+s+qFUmjJ+YeQTU`|4QWr&aw~K06;i5Qe zTom(a7uE2vi)yHI(O5mv@cDgthEjRU^sqLJL;qLF;qO=W{o z)Z*eOZas=;MHIDoYZSG(G>TfhJBnId5k)Q5L{X{LQPkqYQ8C@z>TT5ERy$FHTkS>- zZnXzBxK$W6xYa?fAwHT~Ns6XclB21W^k`}&E1FvA7)`DCqN$Y%4|&e=kY}ZbJQuJW zWO=cN{FitrhH4M>-Zs2-0aXldqwZ4sO zd(A_w@9TwUv zPt!}iaCxa09xwGG)=MoWu_c`?K9+M>_OqPFaz1+w^-@nqcqz6bFU2<2OR-JxQs43G zCt4}>QfwD{DYgnP#Wu@Ju~m90wky08+X63*QqW7WE%s7u)!f#t+!n4 zidK&^w@|*}q_Hf0pX#9z8KM)D-d}WL(u+kWCVipk#H6njotX5kq7#!| zU@3Jk+A9}c1g3shiC!yQFT5X2@exybx5F-1g$snsg{y>Xh3kd)gSj7I>PLgv-FW|(>MIZ~7p@Yn6|NU{M@jp_ z1;SOrwZiqn4Z_*c(vEPsaFuYqaD%YhBkc$m2$u_23D*kO3m*VKsz#4$5Z&#SdV~vv z%Z00iYlU9|(|o8G{eb8Vq8l;xJkAu(2GjTr5WPV35-^Q-x#(5GwZaD^zd>}PneFce zQ+=7DXNx{S^a9aKL@yV8k?2*TuM)jh^m^e2VRx*w1Ex5$MK2I87p@Xs1tz~*(d&f| zh`m8{BTo7O=6;BtE&2e_3q&swy61__FRif95{*rLL@Bzth5MAL(Z`v+~up3Nq zq>G*@dbY4%>;r@g#2yg6M7Uh+LD3fpSBbqw^p)W8-L{NaC3>yoheY4X`R|T+N%VTj zKOlO8=mwrKr?}n1nP8sBq7M)*5H6AYa?uwFR|&6@{94iLg&Ty8B-w6Y-fp62i#`BM z=j{T~OC-Ns^hKgqiM~qomxSxZ-XLtWlJ>yVUbb+7aJldzF!j4i^i`tQivE)5^`bWj z8?B{&FxM|U0DK4PD-gY0I0)u;M6VUD7j6)Cw~_jU3xvystAuNX>xCPH-N`b(VBVgh z4-maT^b*m_MPDR(mFTNPuNA#sxIx&BXMVZg!Ue+R!d1ex!u7%p!fw1{!sUevgv*7i zglmQCg&Tz3coLn<3l|8N3s(u(3fBua2)omzyl{bVxp0+mt#G|?gRnb8$_p0=mkU=3 z*9zAQHwe4&HV(%xTp(O7TqRs9Trb=p?8ci_TpqlN=C|kt!sWtM!nMNn!VSXiEU8Dh zKsW%V`BN@>Q1mL%YlZ8D8-(5Mq(0#S;d0?B;acH(;Ra!MdnqqmAY3k7C0qlh>!(`L zL!#G<9u~bpbk)K3cY|r%(nZe}-7k89=mF8oMGuNzC3>xJy>NrDyQ9P}Tp(O7TqRs9 zTrb=p?9P_*!Ue+R!d1ex!u7%p!tPE|UbsNGT)0ZOR=8d`EZiWB$EbOn+`{R?*}?_F z<-%3MwZb9cdf~8egD@UQwd)s77tR*;3kQUQ!ZpI_UG4II;ec?sn{8L!ZB7^V3kQUQ z!ZpI7NPZ8yTu``1IM7S%!ZpGn;jl1%ShnYvuwOVVj7P`qeBqFASQro0+xfzN;ec>Z zxF({XVf)tzhlB%tZF^9-MmQuK7RH0Wc6-8p;jA<5`{%$}Qjc(ra7Z{T+#rk})44vk zaJq1|uwS@9I3Qdu92Bk+t`V*kt`}|)cAssxmn~c%TrM0Gt`e>ht`!am*9$iY<1u@? zAHwOv*}{I|0^xvgxo}XpO1MV2RyZVFFB}$b5O$v{{Sqz^E*Gv6PWRh>e&K*{NH{Ev zhs^EmEbJE!2nU60BKmo@e~oZR7_Sf5^$7cg1HwV!8sU{-I*$<3?*SpPZxuT+*~4Pr zFLq+G|4c7O!obv6O)ctE$sHeq-Tgu zOnQIOiAgUOotX46n8pvUY1sV%(|BoM%1;-2hS-U@UeWuD9spB4#bPHWzo6I`ik+D3 zHDX^Wc4D%J#J*MR#AFYPeZSa=$&S}axZQMNzi<#t*8?@eA>ra7cK?W}d=Sj@M>r%L z7;4*t!VAGXkHA!}M(iQsurOX%vEvc;3kNTdc7#L1cs<4T7xoJWgoDC0!Xe>sB!8sT zD;yHu52kpDDPC=qY*#Sp8KM)DUMxB>=?g_CCVi#o#H9O2+wBH~gTgh!A>ptvUih;6 zDI5|G3#$tyf2_@Z;ec>ZxJEc692QpN>~b~2A>nYuKHkm`35SK%MbfUYUpOEfjO0&{ zxP$}3YNC`E_6rAugTgh!A>nXDpJbO03#-Yt9uN)+*9eD%!@?>c?F;*b!@_uh$R2m$ zpm2?FNH{F4rij0=UpOGFN+e&{FI*!W5)KQiQt=n|3kQUQk^C~-&o3MhUI`xG?a+vj z=v!I;X+&7`{j9qR)l|E@1|HunsW4si4Awgq`bF>0dcVSe=*6rT6b40K$ofTvHKMO% zeR|q42WLL`uLGS(HF8_IkHCdkZ_>ft~V%LBaD|W zIera1zS~bD(nZf;-Bsupy+7+og#povS?^dF6n!D<{R(SDU&(qwVMz3?tY1_Z7JWbK z(+jmrsoe#I8O%2o_GiAYu$cMr!XTK&cOmOr3Ts4P$@<%cA1=VYf zOb1i>4Av7y`b7^27qk85kwMWHvi{)68qrs>{>;db=wadgY{!}+(`Ed?gh5ngE@DUA57%}!a?C0;gE1xSk1Kk z(}hFAVPU+kXxAqk6s{2t35SK%Y^hJ!AJOO7_6#tcZ;1K)Aol)ZCnkG9>@~t6;jplp zEA0sTg#*IDNPea5=NAqLXU((kQ%Z$vF0=hY!eL=`xwIqf7Y+yqBl%ZIJHi2Byok)> zmoDrV4hRQ@YlK6>;Yj{f5|?mTICzb1uMrLjhlSO((yp*yI3TQok}vER4h!RhCwAPz ze&K*{P`E}o6v@BNE*BK85e_U8yKs$gNH{F47E8Ore&MjNx?b{y@nW|fr?6i*ARH8~ z5e^B5Bl$PjTZw~1Z2MmQuK7RHP3c3i@K;jl1%gtqgA152e|;TqwPa9CI^lXAj- z;c&!$r=9N?R(FfPuwOVJ92Bk*4he@N`S;l6YlK6>c%k2pU)V1k5Dp602#18jk^Fn5 zUg3~%aD~`~L&9NUb)U2=>=zCQV}SuXE@8iLNH{F49uPlazi>b}C|ncCud)3D!a-rY zVr{o4>=zCQ2Zd{dL&D)m{)2Y;8sU(zf0eW+92Bk*4he^a@kpc{hj2(ZEUebp`Jsnw z4hyS)NjYJ^a6mXHTocKE*!B+y2ZckzVPU+0Z^t9-7Y+yqg=-@Dk4U}3LE(^aSQvlQ zx9b)53kQUQ!ZpI-C+zc}deY`}VZU(jY1FVto;F{nnah1ECa_w~)?rir|ceVQ|_ip#^?#WU2Mjea_MBg3V(bL7#*E85N z%Co?8ljliK$n%rO?TzuKdb@ag;a!`l-dW!3yf=IA@ZRf9jLC@U5;HJnQp~iND`T#Y zsg7A0^GM8_F}q?$HoLglv(3WI`o)fly(D&i?ESH;Vryd`kKGpgR_xB$k7GZJJsSH{ ztcvr-<;3-m%a0o#R}>eBE04P(?&`Sff2R4C=G&UT*Zh;_KQ&h^;#;J)$ZBy$i}PEIZZWCFjV+e8c)G>57O%H> zuSLiBy!heqW%1X?|1&*&@At&>|1Ze7%ReCt5# zsjVwoU)TD!)=##6zV+7DyIX(R`e^Ipty{IpY?IxlTbqJ56WUB^Gri4?ZI-rK-lnF_ z<~DD)dB4s6Hb1uUCZ{InCzm8oPoA4RKY2y+v&ru!f0gV?$xP{!lAAIpr6T3pl$9y# zQl3lMlM*7 z+Oug<>9Og<(#NGwOTRz;>GX~1T{EU;+?8=(#+r<$GhWJgGh=7Q(Tv!(v)it0`(@jf znQb!rWcJS-mU&6$;>UJC3ozwod_S@PYZ2v`j zX9r)0q7K(}c(lVi9e(UEt>dDOOFC}u_*TdL9glXjvc1_YvqxlKn0;~fW!a0e@5)}4 z{blyDPU}0p(P>YoPdk0x>DNx_oqKflcP{QctMg@@ukBpbd1dF9I@fnj^qt`=^3C_% zDlF?E(Z2}tTl%J(aNd3z?KL9V1wdbz*;?Ru|7{G*4)X$`xWif zSghtWPIXWdQF0PWPQjlUD^wRX3oA6uMlF|NHJZy+PZd|WJJ)nJ9C zm8vf;GWw}nw6a#6g%fVBT94I{HmY;fW~_p=Rr%G+sy|k8Iu9#34NxBj1p{k2EOr4<(S7&J#sQ%grtgKY1hG-+{?G3D^RD_k3 zMq?$Vu~Pc<7T8EW{)@!p>NSmWx)~-=Iw4i!Rdl4%p)vKM_>uR^QU44iZfc9dw zpM6;2=b*M%HE5q;rKEjWB`K`vlV6FY-{<37cR4=;Tb3RNR`ta|tFgHjaCZ+kFna>! z7Be2Zhb=3NNC8c(>AaYix8ofxm2Nb>Y{`M^gz4F`_uJQDaZ_hL!{ z)j72{p|1nsojKGOJK6yw#y~ziWCC!&h%(@3!zrG!GpL?leV4M`_L;@Jmljhb{et6Lk=~u+vE046MN%*@Rw6^ zr)C&KsJ0kj<2hrGc3v9QxwV+)^TWkS@Y&4xO=bq<8CgEyoYXUb8E2jc%o{rlc)_`& zfZYOiJmVlYt@->E^0!O9)~L3x(n?_8*PGUM%o{r&_;K2`z?(BDhQmW{gxs_?dn^Cc zdnt0Kw_6T;m-n*0sT6}fqwM)+&#}C*HSjq+YDe)8Vayxr7^>7Ke0-~6JUoQfHf=l_t$2I& z<>RVj6wQ{yS{)M;QNFKczxydMXcu0p+ z+8RBcJl`C4vh3G<_KEoSIK@BM?krgB{e4-x{*bTa^P}DFagH-mcF-whBlf~m>=hiX zT|>m@qEmeSoLlynxikMVcm654NtugaS=5=f(Q`i9yKnW;zTdhN9Wfs5MEl)TKA#>Q za`P!=gJ<3i%i$rbPO-my=2}>Sd~CG$!7+SPoSLx#_G?-G=xk~+;C}%Y+vnG;ZII8* zqBHMVqhE&{@b5a2YtP2+BR+;@z=-`PJb&u`Ib?f}2>8D`;s5Ed?;(G}^I?sjwr>T? zA7uUl`D>boD7s=kMTvpEK>UhX$&|ZgVpojt(TOA<9`XaQ z@o{X>Wa|6E6#LBh$6w~UhyS^3O&skVX=hUWhljK{CAT<-&SKZ}BYbcid2VN%GtNH0 zq@312z;pGZekY@fpF;8f>gxdiFL1@H<37y5n#vBaj{7kuuo+hK)Nwx*4Q!7)G#&R; z&43+of2ONpxI4oa8*s;_7bThdVJHE8o&rio4-1Ov9V?g^)+$ zPE1!txDV6SXzfDa7~G5DI~ceV)6_*kd<{UG2zeqogC%5=PUUk0qi z9hs))0d=f@TMoP&_h$Hp2JX%@H6N&BP28D~uLA0LPk%P>YHcno*8p|3M4Jb!(k=(y zsLco7tX+lNTY$Q{Rl6GUZ9pA&gx5m81E{N|+Cs?7fI99F7eT%YsN=2w>w))ZH^8zS zsH@dl6>yFAPvAq^O~6OATaf!GP*-)@ZIIUj@r6L`4&X*@8SpvnF5vUpJ-{v6z3>SE z@g+d*KFBWuu?4jUfV;Jozz?-mz>l;wz|XaR0UNYh;1}AXz%RA6z;Cq2fycEcfxl}{ z1OL$01C{DW~>sx>=^sT@|{Uu)!yc)QAW&Be^`C&(>AwIM>Hh{U)_+6p^+2qZuKxk~2B5BP(=`oqKsSJQ=oauU z9m{sAyMfr^x*PIxpsw!Kv5==)0o2ufx)<{OKwUkcH-lUQ#CL=BILHqIb+t-w0eLkL z^F(h6d`M4(BaTnog0t+xTL(^G)!^)%o{Jp;MV0Wn+jOyDNH9dNVW0rnSw zm_K?pFJJCA>x9eSiJM?bAxAh*#eFup3-Su9O-vi<(q4x%UqMrfWr}qPf^|OEn z^|Rq~2&k*i^m8G94%F3Ay+81KeE{&7J`nhWJ_z`WUI3q8ftX{)5TIoY13HWgfKf&v za-)H|YH5rDCK#iEiN=M%HpV#QCIfLiG%f;mFeU;!8k2!OqZqkefVw)vC;|30%7Fch zX~46Ma^#)^)K$K5DX_qp4jgRE1P(W5BliLzW|T1(INq2CyvVp5IN6ww+yD^YjyA3W zUTRzotT3(x&N3DvcQz0+%2)(kU|bKp+PDF@(5OQ0bwFKJ8~+5}Y}^FA#kd7{t8p7} zsc{GJeq$N%0pl)Ujd2ffwQ(=Xt^r~W823T`7Z7v6cmP;ytOPz{tO7o2tO3>;{{pTx zYJra#j{+Yz)}quCK%5bb$00uj#LO|CguD)@s~3!?fm@9Az>x7QaGS9axi16pWd`GU z;H$=F;A_SfV7;*w_`2~DaJ%s`aEI|K{NDiT>P@2__?EF9_@VIz@FU|b;K#;0z_9Ti zJof{2^{KH7@&O=bk?{fKLqNt>ARh(l>O12Qj{@V& z?}5$DAAkwwPsmLK;!J1$0&Hde80Nb0Ij`N*q;IAT`ftZt~1-T0l zXFbyixf>93(sTpQG^2rMnO@+zW;5jafx7B%#sSYWTfi~^h;zN!5;)vU1YTga0**4< zAh!sJGp3mWc?=LoMKcZZSRiJynE`n`5Hs1#gggPLtBGbi$diD&y4dUhEH|@(mzbS_ zGt4f?oe9L;HoHNd4b;^fvj^n4K%8gIUXbSjah^4ML%tk{^Q?IW&=9RHVo9&e)C$$p8|Dtz+4FY z%3K8e+Poh4jd=s`d$S5Y$ACD-n*Rj;VcrB()-6EIx(#SqcfiL1#I>ch3>atK1#E8J z18ia43rw``gHI9=Gu(Orm}#v9W?8F%?W{GxZ0ldZPF5{EI|DJptw$ku0b+(*Yaw?7 z>Z-f-IOHBcUG=n{gxm{=?}S@VL+%a4oVV5k{noR<{?pkEV)-K?D>jU7G)`!4rtdD`$ zT6=*(Yaei-wI8_1Ism-EIs{x|eGaU$8h|%iUjnPGuYfmM-vDpHdhs}JTi*k3vwi^H zZv6zj!}4_Fqk#&QBzV&!PeJu4cx%JKqNTg`xL ztT^C9Rtw<2td_vFRwD3ms}=rc`UDV1a;pvSDJuo|w3P;2XJr7_TbaO(Ry*KxRtMno zRyJ^x)fxD*)dl#9)eZQn)dTpN)eBf}^#;CfodMi#^#ks(&H}z+oeg}`Iv4ns)gQRa z8UTFX8VLNr8U);J6#zf9h5$ddh5`3j7XbHJg}?*WDBwYBH1LphA@DP69I(N<2z~hi zi1} zV;uhkUg)?9SmL+^KBYiil{sz$PIKG=yx6e}SnjwBxt9QO^l;n*e8O=r@JYvgz-Ju~ z05>{T0-tlN0?u=;0bb_(7w{%$E%0XNqrh97Yk{{q9|zv7^Bu_V0d=+4`5y2S=Puwr=Lf(8&JU4$5Qwem{220QK%8@(dx1xt`+(m$ z_XB@)9svI2JOupN`8hm)0b-jv8zBE1i2Dxbmq3T>E1=W$4bX*^^>pQNeGl}yet>5T z5O*K0pCHEqany4C0=YR*SIMq_LrwweD%JHH>{mYM_1#oAU&aIgFf@Eo-pc&_>t z*k7ePmuTlHKX8Bw0`t@}z=7&_;Q1=cwFGz1LxK6~GGKwaA2?X;1mgQD?j`uU<^?Y^z;T6dCmZi@$>_Z^_&G9?>QSd!E-Kfl83&P67UQFPVo!` zmU;#Or+Ny27kh@_w~3q4${e+U@paEI$U9KO9Q7vr=U`o?LBMxC7XWv93W4u?Mgezw zMgu?cTnOCb83+8ta}mCwlG`i=Ur!m)Y%*|UGy0NBQL_@@m}X_bvCXF8w~RT>mf-KJ zcLVpT7l5CrgTQ?%Hg*aA&N>*_8h^X)fWJrQ;%~@MivYy`A1g@2#J$57dY0 zqxFe;nLb^gr(dmKuivCE)9=&Q=xg=$`eyxQ{SAGWzE?k_f2IGR|E61p*Jx>^810NM zMsMS6W1un27;Q{6%8cp8JmYHPdgCTznQ@=7##n2tH#QqD8*do9jJ?Jo<16C_<2S=H zy=F@@#cXGGF?*Y5n*+^Z=4f-GS!PZ*=b2ZV*PAz)%gp=CHRf7#y}8+Z*?hy?W$rZ( znO~Vdn7^5p<+WN`?X3CM4c2Ye1J+t=y|u&IY3;ENSl?K><1EMf&Lhs*u4`ROT(`KM zcWraM>3ZLF$n}-$N7rwzXm<;DvOCk=!`;t)o_n%;hWj%2weEkom%3ND*SH^bKj(hY zz1{t;JM2E}KI;D29TU|p${#f%YHZZZsB5FvM!gvIdsJL>r|9#dhewZ&o)&#c^n=ll zM86t+F#7lC3Qwgc(R-oyP48jv?_O8Tt1%5Rj%Jz7{LL0Mt7-O1Gb=7Ju1j3MxS4T} z#yuVPX59O6`{M3u{y_7?&A)AKweYr>-{QI!%Ui5!vAIRK#jh=T#plKs#ZQc%8NVm~ zQ2h7t6Ix!{va;oEEtj{9OK6?YFJVr?RS6#^e4219p;cnr#Qem<#PY;BiMJ&#Pu!gN zO5&l!uM@pV2}!+^&Pke>G&Sj(q#KeRNP0LalvJPeancV-zb7TMN@>-;Rkv1sTh+B% z-)dj0!>x|C>d?A->zdZJtzT{ZcI!`D>usXj^lj6>&8RkW+bn2P*Jgd2SKIVRJ~Me_ z@The!?H>9`A7@9FUV{*m~8MkCSn(=%_Lq=)aJKFAT>&nc?9G^KQ^Qz37GGEH< zmo*@3a8_y7)mhb9&t&b+`Yg+Am)Gv4c0aXqwr|#cTKlWpU*G@l#Vkx zuI~6`$Mqf0&AvJNvFvBEU&wwX`_1h4vp>mh)~Ri$UY*8un$l@Sr`4V6I=$Mdb>~ky zH}iGz<@nb5Hv4w@KJoqN`@@&mCA~}EF8#X{by;zrsix~@kiI>KM=0=*a8?&@5Bi$k z$$!(jtZ7c#pKW*aZEAb49oZ^T-#q*tdl`O}y&UNZr1?l!B3*^F0O@M{QhN=4mAw`z zh-YUOB3*~yXBXl5nZ-!gs|58AJWX>0&PGe{{7eAnSbK=Q?1&noAAuc%{W`# zg5P3q#j`TEA>EE&WbeSSX(^tQS%&9i?nJr^&&u45XJzievog!^oXov=PG$w3lerJi z$=r|UWFEkgtOn1?ti*FN58^qQRjNN8<~iJRC>A3-D|42=y$EksH)VJclz9UsW5W zo=4h*r+qdfy@1yMx8PO35T5$kiYITyBcppH{LF61l4*iQ!Grh#|!b!qD;jsF$m}X5_ZObOVPeuP!^n>E}t?+lk z$Cyp^BXbg-L9*NVRX9qs_0hr?GMj1~a}s{rw)3yi9-B<gcRS*qxV1>j&vK+W6hT9M`Fil4>cd7J&N>X^X2*l zEuKgDEqbf?G1^!0%k|MMm+NInPqiGQElL=pbxItgIa-a;HY2@+RFCu)Qs35Nv~!U1 zkXp1Eqb)`{FL{hMHD!!;FVg!dJEC@^hNFCGJEDfBg`++|I)bF7?}%C-6^`1T9*zp9 z|7J#K{9&eL?1)Os_%!;bn9kPtwwj^<47Abx9D$XuF&tz zT4uFtw?coo-E#er_RIC}I&9IqcKqHN)^WMsC;N!!-mI^YzC~KDAIhHZ7}e=o$4#BK z=ziZ@j%mJkkPIAeuxM*gP34}` zZ}Q|G-6wZfx#!QEbAI_1Wu<*`&Y2RibuB5WoH>W|GuVf`BhI5L=akQwMtOEt$>hm- z<#T6M6we<}Q9O6<3I85Fr1nvbt7juOF1hE;uPmF(EtBD#Q))Zu-c$TX7tE-XP}wxC zta9>@viWuuY@ym&7*tj{ym)%qu;N*>B;rUV=gpj1L2cUpr?h#Zk{-RJ&GRc}7FTkN zY@r4sS%{`rPs%!x(<4Vp<;|QorJ{_Bv56ubQ(Q4my300>m8GJYgXhki!9lTpauf7C zMelWro^y)cn{}$01#I6@C!(~oPU-L|<$Ly~4xDVi*C~3=DSGcy^nR>U`z%D}mpx2& z-YEf`*4VQTg+bM@K;7VpWY@#ClQH%@BjU^goN1oet|zj3^(CiK^XFEUP47BjW<^C= zNoD!W8FRZ9oK#?+HNR*kZ&7xq09ZhsC#D`7PPX2o-=9aU=P7!xQ}mou^fOqemRO*! zo@mOp%$YfTSlQgU#nbG`6LAw8MT?#dm^q`exO~Q3o@8P}T?1##n|@N`o%F&v*eA+( z2S+(-uX4`3lFC1uyGD)yaKWxb)s)RCrsF~>_M9Fkjv`BZkE1NZ-V&;spVjRR_7(TC}LPQ)crq3##6VYVHu-ytSm{VFdr>ykP#(}fv6<3P? zsLJA!OHtv#D`w5XE{y{al>U4qqIORnilB`wD=xiYM#cO;8L6_0@=6#fin2NIlek9| z&zZ}5!O5K!x%tI&FCJA^NwrgF!6$o8g^Op6DKEPm(iFSJ$Rtx2&MB`fJE5J}S9|s3 zz~pGGissKMi^yCeqM$%TB%e`J?P;&_%1SWaK9NQKN{)FM=KGA)0Py0aHlZ4Y9xg;OQ0!V25&&LPY z{eIdh8V7tAQHw70{RWoPH)a8r>JO5^FkZeCkCbPg}I7GyLpfEnXlSZ2cHiJlN6p)j6mNX?pQex?|1UK+hrOxtfzD~B%^LyN33bU3ajBgN~j z=-e&DMh_Zy?-mPM5E|omi}8k5gfUhqd8S#v?iP(X{oJ4Cceb%KHLvGbb82*P0mG)L zf{nX{ME;x81RBdr%HL3k=B}t#>5F%Z$@wx!O9s=*qf!kC+BvdVNtU7zZ_Ynh9Mi0r zQQCz4#sakKFb^$>RXp^%%$E>S>z5x$Fqq((vl4R+Y2D*67CkXd)*}t6l^&X&TX7igqF1G%GbkrpGmO(<13&0E{;# zMjy;9h1eVgr{8351jxdCbGixv%{Ohl3Xx(#RzpMV)fkY8YZkzi%~0?~4?3jrj=)l$pDQ^pA!fv3;m^W6=Os zcNvFJmLLY1yIb6AOit@D-8>C{Fv{70iwhfz4x5=5?^){T+}ymg`rYEeoCHB*QJksq zyS(5ToIg;nhUX^c?-nA?y~d)DRr>7o+YJd70W~K_=f+1D$4@nyV~f)ZT3b$xjcM>n z9i7oad17XIv?-PzJ3WVXbYihFN8FfS&!!ZGxp4)Y_Z29KHa<*O#iVd*1uUqLP|)0 zr9}w{WlfO4^HRfJn_h%Iv!hEI%K3TyBE^_FQr{FqN~IXHC%~6H*`_9@o+hnwuSh5@ zhc4K{tnj_p$873dS!`(0QorPgm>xSN^;hCyZfwPQE00U`lxef03#aFn7L{vEV>dl# z$|l_Rwm=s~XYY-V7MJh6F5~&c1e0837%gly&f@e*BQ&kTZ#TwIN&7ACG#2MCH0CCk zrh*FN)5FrnOAhfU`DdGB^NTan_plm##a zr(Qvj79KCa-sUexoPq8h+@x<++-pC%x4at7;oEv zS-I;bCM(3VNIqTAqP28-d92YGZ;TiCVXrmD9@w&2O`3h6CH#TbFlD>G!0*E1{PMJ( z5DPU+K?HaR1`;~l6wyobOL!7)$mX4EvP{gW#BlSJ1T~?z%rQ4!ENT6nG2hp<#*!9{ zaS6aT9$0`nHH<-1wAQkyw-`Mgh0EJhjTw4qwT#L=ae8@bR6VhlFR>JvkZw0tnLFaJ z#&~6JY-(|S4w1fDZbzh20?--s+KgHeQ-_P)j8;|X#m3@fqYcQ*oS2hgIXbBoT1KI9 zMhSndF*Eb(-27W}s76`RaP#c^{Nzl-4A6TICbFcLtBclW?%%z8QYdLH_gY_WgJ5eW z@wssxBQ=cCuKeP>9Q8{p>m*+))qpl-gPH~Bv`IZ3 z46imxuAb&h)FzW*-3IO`QG1)js$6X)S7@^}L2|zpNYAdxx{)AllX`k~VW|}?aX~TJ zM?RrVxI`~kVX?3c;^~(fZv{GvDh|4Se|q7(Kaip{#9 zJ3Z%X0#BEUC$yO|UYyVtj2>rX&>H+z0xTXDkRIF^nVxM}WJpTDqJ=JDinv*HMHZ)p ztn@{54-{t`OBjozS~3D;jItq2^U9I(rU*&0PEt~e%zD*B^NWt?P}KurIM-p)?Iq2w zOgF|FhF+LI%4^Pw;3OH=8aO(qEaNV)$~f*AQvmRW3`t$5V;IL4#tg;Gl$*wDH_KL@ z4@0Gl+?Z>QPIv~Swj3Xp9>etbd~%dnS{|2nk!L*{-6)nZ#oXQF4*clwksDv@>-=n5 zm;w)(9TUT9AO=Sk#rees2H1%=WxryPi=$^3WGs(snl+YRo6^=sN{hP;yhI6%Z2 z#Ne3#kvmY_r_8?2AC3$C;cJbFuCZ zD%x-$$t{xwC>)2Z1W;|6t&EVBewHa^+{yqDCyUzRAL7Z`&Y8jIrE_5;d@I68>&0x}@a z3pw~3frpDrG8_xrWwd#6V{RJL>vVH$bU|>sP@KPR51O$63q%P?%fDf5M6pC$l*(*D zU|2i|4GW(Y-;7S;kh17oF+gK%Wf;^bXCc5zK{Sx8m`rgsBz*Y^s;ohxrXg>(`+DWB zzuz#W+|q}e6-6CUU-pDaZL`;!3#~Uy8;)iDEJKN*yk(kUa!i$wpgr3N#;|zF(jft4 zrfx4A2z3kTN8a&RCctXGUj~=kA0(;>Jf>01kVwQP_U8I2#vpL*B#(_SyxOr}Ra_ahn#l?BN|1za7%)gbQzBrFGw0}eDsm8qr zlar0blZ*3j*;<9yqEVo~znr6PeMf-G1!eN&+|k^KUE z-*R=0&a9g&vZ->VDx@bg7FDWwbGkXL^Hq|bhpfCba2@F~L7G7&@3qb30Jz)r ztW-fBx9RJOR8~VpR;P+iR5svD%GE%IRVv^XLTWjWXD#R8X4=%Ve&b0?6{Hv)V1E=75n!?V*Lt z*JHp~CUY)XSHk4;dd-(vI-jCpmvFdL+ibNOT7a~3rM3xcIa`3_*#Vq z1V(rhN3yY!Cc9QN(WjA?-gH1T`l2&;=zDZoUu(?HzuC48_gZg|o;j{VuAq0v6I%GT zY?VS6_p??8%8!)1@O+-jk51(fmo_;>Vz*kTJpEAx$745nS;OMvFB&MvB7h?PW&!5q zT+Zc?mMkR}v993M({3qV_*1fCtvviuIy~Ert)9t5 zzR>^iMox&J%jY1M*#3kC?n;pBsf3HhtwP&wRVFtlj$D=%(x%K#F^CK)YK?o|{`Dr&PQ6$X<7YL_Rp zS>0S34q+&qj2F{wec!)s_q#Y4owOBMB#P&~vY?zN#4bu(_UFpIhU#wWfHZMi=md4^+TaXjtph!x_z5^8~3xjZ3>tTQFR>45x{1hsL=Lpti>l2JOQ z(_zB8h+)sE(dMPbvIXbDGwK#(ThjkP#1VNd?Q}!IDJVO!CKFB;n+#F{7%|r$EG*0~ zF4-OpFffwuqIwv{q;xBky!Zgc1M!rM<*dWVr;VmsPM|~8BQ;jK4)fCOplqMXS6*$r z1sI4CRJOxGKc%l{D|bns3YVEmSMz zFy;w|-0bw9=RL1|S8HGRh0yf}_nHoqCR1uPEQO~R=EtUFAzd6@(i2+Y=7{D@D#il*JNGD_MWVjJbq$fn!_-b?=|gyTLufz0rvG|qUz^_PSK2^=iehX76Uit z7A)>Q4mdZ9Hf=k%sQTkarm0dB+?tf-c%AUj;(W8ok?X>gEK|uZjyCVRph(e?`O?gm zo|nN#ZClWaoNu}iEc__e=!_P86Fj(;78Ql)Cf)GESnLAF#(V@U)JAMN(Qv2FnAp3# zh^s-t=q#(!#F`~E^$q(OIjX|u$N|r)ncm?Eim-9$7RYF~!K--khL6*l|N+FXwl|rP=(1jIG zSDK zcH1n`XB63nn=;I&C+#3%DrX_F7$@4aL2J=?-q#ol&BYkY0>)St5@XpjN;Ekxv_UV^ zeQsQHzJTc(ddlCFT~siRxUN%|wm{(SwaCbP8DuxZV6Fw}NGz^UJ9FZpC5x->Hyf9= zKUj=5Q7g_&FNXa_Fw`;5;<~z8NISA7Z4bwI&qIhEcMKKtwi!^IpI6q49Bi}#rTfNW z!|7Ra%9uuhKM0rZJ7?X&ep3@y0IgH{oI6E_cPpi?}4pc*07++ zhXagcUw?}z)|AAcF-jKI0+Yo`iJieB#u*4+km@$OkinAZ%79@Xx|x9_2V}wLfc!XU z2G=%s7PGDJES}l1?j%k!qcDXsi(j5&g|T)amk6opq|?y~GYW2yE5dXxBMWA`hb+=P z>PLu+;me%2vLkQW1u@BN2kdooDu)M^Gl%LVfC`+Me=wJ2mB^H?tmV3qil11V)XbS% zvNYQuO=<2IPVtzRMJhqls01mXPN&qMz-yK|x3;9Pan%B}&5W`688?Vh^7NdJf~Tm& zkG!NQ9uZ8C%hIJMhq9A>qg_0Ml8)Svvvtc-_LR zUUT<3MHQxF6wV{_=4=?%?$Nv_>A5JsDktY7D0nfow;N|uVy))kpPygFPF}QaBjfdO zxz|}9Pqf4hmntk6^wtvu)5VXs< zk>t&hyIzBYtg!Q`215n;xS+cnStM>2kIV;CSVnGGl?ch!EksIdOgedX zP1c1V=1d4;FG)blZZkC(L)y9=l9`@)oV{!fauw*_t2~=bop9DOhgvuXsH1t54ZJ+= zjGjYkCwB6rz$H1z6b7BJ$zk*te6_2UgUh2aIZ#?OHoYHI$rDOV#3Xc{k`7Vi&{z@F z%i-~uL=Hm(ujhjiTVU#lRtas~$#K%0tJRz~+4H&sv(iKkW**dGy)@Lz9E6n=N^Yv!qSs9L!SJ0z|EC0XqM-fNQ%NdEG&CX({6BOA9#PK*+0! zPD?p|Tguco0`hX@4nPiMWZp!c<-oIc0wRY*b5E%HWv)E2=9kKoYEGv-sqSXV)7JH| zLTW#9dJp zkh#V3rY^X5xqf%9O&0A5*tNPmX{2_aE>DXN^!#RBo_Ef*|6a+{n8oG6@JfpG9=m~;=N?LT@pAA5-O9`36B+A?=K~YUYv_uL zommuf>fDLpeUS>0 z=$#Ew)Ws0Qe={P5EJPQ*a5!)(P4rD&^^KL8lSr|99L?6#aT}iV18U|Lu$OgcCnlqy zgJNFqhr?t)FY08s8*Wkl_AY(IYr{B zosxp*8Z)vZ8NY@Zy7((zZ-V?GYU7O#4#ffp9M^jV> zd(~~(szl_c2_e1KBV5^0S7qsvYBoX{vy)r_YOXPD`FS2%P7vGJ!f95f>#C66MwW7z zOU+V~g`%zSn64N$mMNo^+eszV&(2hHYHn8n@%*@7 zj4n*Xlr@0aMr)N@A{#7b#b~&3od%PwRQc_~6&Tw}&7$bWHZiX^#5J5DytZ!RIxELylThWs~fTcrLf6hL3K?1_oLZa4w|8424V=Z=y)O@nUl_B+?hp5 zt?EveqXs&0uPLkEUQmV;Wvm&wChea#jhl_|9(0)=UhPD238X;7kZXd4(K82?%-W7& zt#L6Zr+OJ2jpxF`ZJlhp59fO&I=Y-k=h8(TLpCUV@l^%}3!LCswNU{L!**~dWbmGq zwcv_s(G;zr78TKIZiUwNzd4>E&ycPenb)Wpj;zYT!B%0@HaP;7<1nPVaNcnB%>&wj zQ5}-?vswjfx)EvGpwj_^HVN8hn13s{AxoW!664wJyJ9@Pb%+#se%bLcG+MOalCl-u z*gC`<%IcMNMO^2J?D+omr4^!=Op6JT%85wir#EU-%cJvLru3k+hHRoc%p8WPV3fBG zS;+1tc*V&f(G0Icz@+oEFdPZM)b&_h6jsn~7KVi9T(aukWG`RZvfj^G_=@1+vl4qc z89P5)o?>m9`^--9_#Q3q^z=#SK;_gKWDldlg^>1cEvIVq018U<7<>6LhS#o)+_Pfs zR-SAzF`P@`k-MFsQ z7Yn}8m%-x%!H^uMt`2AWn@u)BW-(CYrfo{-m|@Xu*;dT> zFRo`f$d+%tuSFWNZ>|O2@|QxGxC&;ryZd9rmz5X`f;vWnFo;nkSq#HJ#`Y0lTpS~$ z#;{vv*B54{mmV!n6JjnN@NkzdpI8+?L> zLs)JYiJlE9?7=V*ZKYg9ey$zULTY6(xrXbIP2yykWM zA4ZUn79Af(y*iBfQTKva#unfvQc>HdB}ZorSUD*|W0ud)8J}3J*m;dy^pzZSIJeW5X-(c#Fb%)}C4>!T&vyfu<) zB*ygCD&xsQ*V8>fP7hg?G7UWzRy%A(2XNxnLwILWk?M_pX?OPKi5gULEqk2`t`M(4 z+5GCLdrxT1Hl#h(B})yciu9JSCC@R#F`?pIW7%J^s2#VXAID~p19?d=Sk(``3OFG` zT;wfAh#jlBTt>^g3$Yt9-|z`e2x=g8*N*qE@qn`>4z%NFE*nk**nKjtIr%JZx*f9CnScf$~BZJ0r|YUnzWHcg#AL+tc2QYeHT88Z;I;L0^o zV}W*pE=jb*yn1FU`KY+ei^?rg_Cj{z^g_kE zpmW*9xc-}#WDG%07dRFY^8k?4vF8kt5!*lv>k91crwHY_(+*-W;ya1qor!XOyxF)U zO(mj*)>7PWtk~1~n!CHbcNK_MY(q>m(VOKwg9kOwEX5q)1xA90sMXX5k4nbn(b7NX zsiXed)^XJ}&Cz^J3yus+cAHvJq!Uwm(_SVMO}b@;(j z45g}D-g=@T%wLTc=Xof#;DycIdvYbtR~u?3s|WO!SFxstdHt-v;x4B7b3%t;JY*Dy zO+2cu#Gu(*4jqLi)?8`ZdbDNpvJLPYdtum0ca3wH6Hw&CN93fIto!n1aJPtQMlSpb8ad?lMrV8t_TI z?d?A2ck4e9{EDWzOLI6_6!Nh05?k%m^LXrZNIrfX_Ac-P89;CiJ(VZvhp3^A?uZM&Dp6 zPF&7ngZhTh1AQxKN#9Spr}RO6nQ2?PqJ|K^`zN+*-u8Tv~a(m{zB(7iZz5KJs&d~K%qo` zU^Duw*Y+5VNQt8W)qyge&~MOcqz`(bXHl&FgUuRC71LE8fB2 zDkt?}n?a#V>a-yX;TU*FiNQ?Bjj8OS{^}(Rs)70`<+6yKyb|j&2?r;m@Ai032Vwl9(zl5!v8-1ob(Tj!h^~Owc#os zl!m_0wxFD2mVaEB7OQ{zxG5sJ4opCmXE$2j_6+v8-R9V?MZb-StRdQS(LQ9C| z1OO=*-nk2|ak1oxM~+%jJw62{)Mm=Ns4zq=R{ttZL1pF=J&qKGwy_#|JF7I94~F-q zkbaaR4W=fPs1uoXRB~+8a*nFQo}YTtlz{Ci&FmCPYrp=Q`W35&jxKh5j)2O7AP9qb z(R5x_){$dAMqxlxKFo1qco$)W`q25PRFPxOjq9%?o~Cm*&A~CTG=}15tgii>cHsFf| zFsYC3Z&jtBGwy;qvXLx+ws9)JQmu0z5}aOVt|2d|As5omX<9LPqGsO6-M7?8Qp#SS9xHO6(IwvGa3Ukoc9}rQf+qx~EK73N~26 ztx<_7N11k1xaYAP@1y!^ZJw6neN+-MPs{N>D*2hG3Gb{ueXKW&9k0YjD>3ya%Q;qw zjaOoM|Bh;1fi@q?a=aiRlj?dwBTJ6YWw1|Bs%-703RPaxSm$XuRbJ9pee<-ODlh3v zzj<0tm6!Ad;5;p-%1inxaGsV^M7aQsOxMd!(xG6n)9| z(0thWE1Kg)*B2EUl#NyFM71q;ev;q2EN8jY$Y(l>VpBGCl;6?RK;pB+CbiGy`*|&b zm-XdkYIfVN=$(_Cz+x*tvQbM|XV^c*o(Z+d(yef>E3682X?(lXlkQ7Oy;KzYsS8Z! z^q;cDf#>u!=qjw^vo{1u_JZRWbuTw#yyp+pT2vJxCZf&cNWjXi!%35t;Dp)`)oZLh zG#wNS7Igd%w1isiZ9zTn9kwC%39UU9DD+g(vFDXCN3kbiRP?=y8#Mzdk~JBAoSafAl5)}(HX-L#Q#+En=qbt`(Z(A1MI>Ski< zLrw$tZjrm8G80-wiXld*RTHE)aDeNx(eu*UbK~HhzodL{_UFYT`hOZb92&Wg2~{8; zFZ9(ohRJJ@XyEiIjCY_Tt`*itluh*6)|w+l<0up0G+GVd1jm-?dXBXR9I{Pn?9YwBTz1AAXr z&2Un2FoqaAOkL(caj@MwQ+#aeTyFF{(K>?ptjXz|FwR$>;#2RXh7;KYxi;42v)4*~?522jMstK2ALp4{w8$&g#73?qK3H!~`MT-4 zCWbR}gHd6c0G!pL;u^4?}q*F`e;!ctM|uyL$A?w+V`sHlI^_nIoVPjXSZHH(V|KL z_K1r2G{Z-Pqe~HdhwMKTEj-(rXPrLn_gx2Myw_SBM9SZjL}ty0^Ym4dqWI{F<~M(z zlMC^pesPjNrPcST;++0|Qh3bjmtBsIt~-)^~dIAwVaL9hZvF2zICVf=YZ;eTaBg z`_Ue^XH?^di;iE3G>Sw18P${8^5L)23ye_h!O7{n{hl;wyDyo+Pm9H#kpm-VXu{adc1odQI@tn!sK@uLt9~T|Lu9v2tD|=8IFs z)llPUVRE8)ReK)iRO2y~JX_op=ZzP)go#eDE0tDG$lWt32;n2je^TjpRQ8_A-Be%5 zH(chssQPDR48E%T&&!GPrdqzI8jiK}xjn9TgwK%5o)Ja$rGrYJZ>Xg+qVIY2K%X)+ zT1|;pZ;M{XWHdt4BWm%E(N;oy#i_>;lou*B-4-@Cg{Q-hsHIyp0H(+HhbkKRyxN-dqUmeS}wP#juax1GVzJ{x?tpI8ltO8R<60e1QEiKgigPz|$B%48 zmA0y;KP8Ow`dD&kA(loubUYQ?J1i=pVCqG_!V?9Pe1}C%)VJdOF`xHX}6uxaE zI$_V_qCGSArdAn!PEEX#mtR)@^&Mg5KOVSpSrjT(HsZ!O6K|;Id@jEvEG9L#hQ)^` zgxwjnG^%+!tOayPDc6)AZa=CPT4ye0UscN|g$p^rdCG7)r}DQ|BfNgf>Tar!XY_xo zGxoMx9#P9Th1IC)Juc~Y+OS1d(D%30GA+&+we`|OYjNdgY>^pFi?6AL2WoNBq|Ql= z(jB#=FE49eNmZ2ed0wM)Te*kS;z{e{eU0@cm6(?!d(3b=YU6%MC2uK?+(HIiQ@o*> zdezEbFUv!tbL!(pH1a+tS&o!|8mXnL7SB28q{egB#`BJ5^Qdr{QXjwsnw@MJrS=#U zD>>)n7%)yVq6SoZQ8YNOv67Hl;RIsc)30ck+@N^ z>K(SCk5dt+@Q~*0P1ShST5RQ*N#O#=oK-0#@N1$MoSCT3sDatM>R^`M)P^T=_NHbh z)IKBXCRu(f%-4IWQ|~qxWm(Vkf;XErtTTEiw8jw$e8%QElK6;XFWA#ta7>`h9kn*8 zIg;vOw50l*42v~gQZL}RW5Vfw!weK>0^Uo=|0t?(_4%&cm!%EGEFOS15ojsZA_T{GTfcK!T0 zX+1tFR^X&zt@cR0n;NM!OZ2jEaq8yMFmjr&+0cmj?K05igWeWx6rGo3L*5_PY(#Ev zl}h)8$*^kmwW_KeTvbb>N<|tS5!J8i?;AE2=Oj5>N8xHwTy3`|9aHUh#0k3dAY9=% zv`3ydG~1#+^SoMOekc9pC1y)6bYsmg9=WJr&Y|GalFszVWsPbyE{We%dOFNoy#Km# zq7R_i4k?TQ*$@_@P#=d(bx}evmF&ANUiM8Rt@Kae;(S9T{ftgctNCCx^}1?9O8`e| zn=FbCkRzUh#aOS^@Qd~IHg)t~6TTd@@HyKmz1P>n?}&oF`rnfLKqkZY^)>ps<9$Kr za%*Tg;5?>2=KPx0FNa4B38{ZU^|r(0qbBQ5s%5m&VT}s8&;Z`BxV~SdlDSrPLHOnN zRW5(n8oVcZFRMML>I-`6ee6554+b>>9BI!|JY@RI6dVmeE0Y*-^YkBSB?s`uZ+M5Z04 zG0Z(Js-3fmdQ+;zJrlktA~%%ddBLYNaie*5I45UnaZ z%O-g1hFes25#jEHi6b?v5uTUQHKY;0qjB(M`M#M&-U2mV3LNs5gawNTPAF7L)W{PO z6{x0g%;j}C#sw)xWW=S82|RO56hgtr?QloS!ck#|k+UShpT^;++B~T?wuj) zF5j4=rc{1&%<7AOv7eE_d@fZ@lhMoDqCfLY7dq8jOtU)IZ`J98afmy0TGYb`#LUDz zJ)-%5x%#?VU>3e@<8a5;qBwt|g9HOBIY@?0X4GX*l@m~0V#^r+^}gJ<5sb28OxWDi zI^;$kbNIf>Fw%^Ek}36k(4eV&Cv1j8Z|8twB^j!imzXpdOwWrB7!5eK$eA0)7=y2g z%3C#aa6drTSRZcmA@%vPQF&Uc^JJj*O|^zf>MrBcqPV-BXH~;dgoKa0wWt4Jffh+yE*l@EKE2l)& zDlfOI;f(N)4v_a^^p6D2#NAF$ht)DG$grduoVB2F!xWD$lsffLkiG90L~qv1t!qLT zG#9u>;xv9uEhp;SuzFedZi^bYkGQLcJiICj;YOb~`Grx13#G~jP|?fHRmQt@W%jwT z9VbA2c3?Q)Gg{4PjfclE-PwjnJdXhhN4_eFJEOFVdNP{y`KsoG-ajDPUo+mpk#<@= z{#o=(y^olyJh+z6O8eCgI5IA7eq1zj zuPPfai^Jd}W>u8W=hV|_ZG*zYTi09OtJYF{a6QAW)LZ(J<{{1Hz$Nt??K0b*c??)Q zt&+ptHQ{zw20uW*IH!}r9`n4&xTPF^fLqngDy;Wa!8pHA^raevTi=@+IX|bPn61KY zA#Kf4a)$)Eh0kv09+uN~SL3>Lo)(w;?7XR2JEOkTTR_qw<4zhs;P?b%_$yzP-DP)C z5|zzOeP6_8KYWNbh2tDaH7(BZoHsRNuw!!P-qzPVTzi)_674Z=(Sp=6PLM_6{Ac9? zVeTOD+!5|Bd|X^(avq@z{(o(&75j7djoE6e|4zH(Fzyp!p98jK$C0NqtNS`~#^vZo z)symRxiYE{-_uO~6>_AQoDQqM?k>yq&zhYQIyf{V!fb*X4~7hE>=dgMC3bUm?kKMr_=%-YWK>&F1oOn=T5xbAO~Yt zLD?yF)s}sM^_)kM)9O2=c}q8}W#2iC`_CKiuqEYs^S#_zVL7dj->U{c zEk0pMqp$3+x*H3p!zs~eSYuRQR5x~mRNYpbrMO_6cudMaqcWyZ8B&XvG&6By`J(5_ zvn!_5keg}H0@tjrtJn@i)!$z|KN4Mem>RjcO?;ykQBhtr6(yRouZeFtf1u9{ixSBU zKPimY+n>aIiH6};&Sj5NK7+@ZyksaH!X z;p52;W;7TXTTJ^&&4;)P<9lH2b5pNAA*^3!?FF^G)$;;5)!i`2_r9)Ki!<7@ahdo) z3Ml)VnC0#uzy%!DI!*_08VVKglKMcOXVs@$dg4-OXB1z^`gmQK(_>`QY1I{JN8hg+ zCd-=XNXC<@>z3s0O(}xcWv2L!IOktpR8BXFecx`WQsRg>;EGD&kh`cbX57hg6oy3k zYr>neX1MgOiKffSe^oK|h&g+P>gmRI!h~@?qOvHZr_^7Zgh){&Kk7B7F3u?Zs%k`r z-BijA;f$1d-D*B*bGNnTyxxl%Tk2&+;fd3vSgF>0R%thdCF@VZ-;J6fwV01Ld=W=6(&9))i#oN>2nS5XONIew zFis06c)yClh+4U?oVWG7p&aZ=;s(8<{O(LXWf&&TyCEvy5$#@6j+^>ltVB-1^p1Pd zRo*_Tc5xHKXR(EAD%-xVv=IC19810E{BYI<)$zP2rfWo!+eidI+lC7W4Ud`5`8N!> ztE%aY;xp^$W^*Uz~GGPx_8rLq5N%vZsalN#(k)I_SZD!Qe1>neky3 z99O+iVn$SpIT&~31k!KbD7!Hxv_OqM&S1=F#Cd&N96BW!D+X;L*L|LzRExCiGyb^s z)nyR*;H+!HA1!EDeE6cmNC@E3smUm8zAowCizCmS+_(^u z>zHw7b=-K3Nt&VSSz9@gw($z1R~?wv%PNLumbPBno$o};$sMt{1bJ)McHHG@BLxkR zv%&2)c{n$Y+XEdmKSKuw$&u@N)^<;Cji}EJ8{Z?gI&$ItMsUPjE62U=u-_U7xeP?J zue#HjBh@n~n`!pw5lU<$@d$YJ1#vrCy$21*%n{J&@f!SpU^nx5+0oh`HZw+;~SZV zOggUL5tG@-BG*lR7MC)T3J&uVXmy9!33I*u**T?(XSRF>uJ5wA)L&8{x9DTs=U5b1 z;HL58FPvL1`f?|3aWIt`Rz5E3>TFvdTiD$C;4DtDpW720ZNc+oXYAg0N-4bZ0vB=B zjeA=8a1tl4hdXFn?d$bVMR6GByjBm~Zhnga>$vVAKtqXcdY*pbJ>QhBpG_OHl|*kQ z53+GuPqgYJw_R$-gAMgfE7o+kAh2z)2witYKHwqXtebDg&~n@wT>q8{>zG?fY+7<) z0E-aahHX>VI9s<<+$B6N8mDcRbgsBqVe1q-@tmHnEu)9M_Zq2>t*5glQ(sljSxb+o zjkh&QiFR?#+njpL?O}~B)PsB0t8uHN!ImL4a^iSWeI3$x;B#uX8MQI>=qu|-q+Q(P zj+>zM(V*qcYs6I=smEDGP7eCkK%(AO`^ve<^V&r4S@m9zVI7q;!{>n3=^H~UdLnhx zjjVVW>s95;4`Z>;<1dQ`z|#FB_Ze>Yb`mdps$=pojY6wTE#BDdWN|tUl%MbZ8O{|; zo7(Q7EF=o&Cy`-n@p2mtSqIJXEyRsIxUF8TVMzwGsMA`H(ovbyfs^nj;gxhf;JC(- z+ZovK_?oz7!&Y)EmkuK&U*mbr8>F;LP2W(%<64!wt$tACF6U?Q5#h~R(LD6yb!edH zH9yj*tVp}Or#a@fj{D(KdG{4>Tmw|wif)rO+L9+|N|-(`T&ri}-Oqw7W}DjmEZk#5 z%HccX$qQUKw&1rV{Ae5ceY|x6)$x;YWPqbR6PwiYs>&jzInd;Ov|A>-czqWwp3%yU zZ^)?%N8y$9C!BR(eHap@qeh7r!|(ra^ussK7!5YL>$f)0UGdMci5RUOyiS|@cyrrh zYvavmy-V>czD?4i$48$-=mcmtNSDyabmRZHs(Y)EZ&9#w-*gJKaI{U(dWbX z`JFTON+ud@&pyKk?f15rbo>NmAC+@X-WwGF$7O zdaU5B`G&64ZouXqAo7xBAgE?c%fp2#wn46Q9d8&+K; zOZ&Yg_p{)2^p%@K3mi>7Z}ZM?O=0a$yk~1Sw{@8duEXG^rlsiDWDlrn_1u;W$?xrP z=!yBr4h{a)tw_=KGLSt!v{N`D-f+erisbjhm2l9qWC!{D@K@sPcAT?SzQKpM-l|vU zoY#e)ANTY-rY_SMN54&p7ps1si{ruVeG>V6&R6{Ef#I!F^{B}M?ls4w8_gdE@?b-iyeuunZ2I8SnFfP0sX3s z;?Uo`EcEqc6mlL*j^k_GIssGPT}+t1)JA62IWON`#IL!PS;Efg8O=WQFJF7OKZ^u9 zruh+dRoAsfy5Fs8=vtw|R4e0`r1t(PO#V91f<93%YftQ?nGdf?;i*C;3v&lMz1jU5;6 zIALRMowK4f%u!7a6M|WFcR6Z+7p!J<20!ds)lzS0CG+EDm#j~%)UWgO&#z@Z$HKhz z-GzKNd8?yS_t?YRtw*BK#5eM|rsKx!a=fn5@U@Mbc7D*=XKC7f_8oKH_Y$=t#(8sT z_+I+xhk6qxV8~i`(j<^xEiBy=dn#(Xet3^#;2d+@XjW`={)BxZU#r?#fG(|lRv~B8 zTRN~_;(l~~$Go~9?KkutQ%1*kO0j!#oz$(Q_I6dAZ(G~HJ}0c)!{+;&BVj$R%Vj?h zja+ZoY)X2aTT|CWZTAIjeXsq3$+mNvebo~*esnrNJ%HvozV3RzHJzkYtK~I z>L;Gs?R%e=m;0zjlwD+e0=ic%i;i(^zbkx4_9pwy?A$Vr!{uyyRkh?h>T%x_Yum$1p*F-Ddy`(-)hj2Ed{SG;A*P&+iMX4E&P5VBMD925W z3A@K6@==tcoug2VsM3b{^C{J?mW;b5Gom@iOmX+Kwfc z&L!l#1DvkdY+a5$i02g6TYK_%v@qMo-ZO@+yU4sD48J8iq?~k$KDzhXm#v-7y&Tr( zJJLyfyr+#?!_toOs}1-y(fZbZ_e-5NQ=8HT-7~ku*X#2Y(HYw@+KfSaXg!A@R_HOF$ ztEx)|%P& z=RO2{39U8f_1;!pPTN#3zM)t%Z;FB^u<=`n-t?38Rk@h__?kZm9?C2C;|ZmfEB)D!b1Y&l{I%XH@gF^qf}}d#%y}lEQbF zpuWGYhZdMSyhSY1mfa{kJG6}UGOrY77xx~Zccc!wAft{PXPnuMXW#7&%g-fRe1d+x zmmXl5A60#?n{4Ci`l!X3BWG2HcekC?`jp=HlSV$`21oJsL-ed=+iQ(-o43P#TrDuJ z>;b%Bn7LOfx4$=jpJzL-RHS}8KN;^2Og1EMHseDD$S9XhoONPOp!s9F_=!jCF?!(l zgP;lT8KjSOUW6Kn7Zc|o>li10zYi_47wh|xc-W&~Y)1JQU-;&<+HA)+NRq@kXrpH~ zjJ1!~y1g|j9X z(jKpSF}WYZ%E7%IzbSBQ)0)m{Y}kKde*11O`;Ts8;A!{S!n^n!?lhQKw_f_}RB#-x z8K<1LS>v;Vna`_NIHdt+IPTnSx+S{Z7XPPl#5zU7IEKtNpRK$d5zOL^1AK_IOwDiv z-rboDM`jhfRZ~jg?5xjM<|UlODB*GS_eZN1t-kXZ_o?wtZ~vSDC$x@fUa;=E zj^efYiqHKHtfbs8&YbwT%_eRfBt6dM0ppBSkC)%?lnohY;2Gfz4D&IbWrlCvlR6_> z;6LFcGA9%{AO4zTDmncwL%dbNT?IeMOg_$T`#Ht)qBe8hOX#cRMhj#CUIl-H9+vSz%A6{5#5w-<0kW-eIm4=q<{!-p{7gt+G3F)5OqTJ58qC99kyavu+ z=n&5ZBX5&->iZ7c-Gj}kSsx|r^kW(g_eQ4O1gLvMvT;Q7)^BdnH>B8zQP@4`*R;;E z$}I%mO*cH-Yg2x*30(x=KBu{SE#u~(YeVd<(dT5ftXEUSAf0TY+`RD@Qm%cub}LV8 z`Db5ym^E9mlH$sP4BO7y^QJ}x4G;fm;(0WD;P?*E`tti+=wR$Ikj||ceEZmex%bhn zmAXILPs^WIDQ>{wF~platuR&)o>%Aj$f?N(yguJ>qllKHw>aix)vL}P@NUfqC%S%% zzw?H;hBa+#k|y@~hWdbRgM~I?S_?a)tWEifjyChQFsRC3w;8uDee0(>wl9A-(~|1* zI?|+AX(xXfN6zrzA?3wc>6Au08YEu>dNmp&>$>ZreligalG&GD{lsgJ*q^{Q9J2M9 zb=NgJ)?wasl4ZVZBk9u-U|&(|FSGrs(4K zHD8UWkE^eJoDZ!^QPwIfvS?|$6iwcRtxLo0>fxL-VEoc)jHrokR~l|d7NX;_7u#OT z<<1GW9!{Bzg7Y}9ilv9O+MdU^!l~HOaH^j`Z0`@*9~T4bb1+z^NQ=ft@tV?RnL}z6EzM+_rSv z1e>#dzbf5D^&9-$=b6%6!AFRd8+Feam2{t8^a*f2)4fUTCe_c_V2?*%FMZ=}G@KW5 zPmT8}-q)f>NsmXjnUdtMzLqfeTh{xKPO$jid_33hcMtf;V(g2{?^ev?>1pmF-52F} z`vg|Uu;w#;$>;C1X0eYFeL7|1`?hHa?M~w& z!;4C6C)e5uN{A9P7{lX!}Q^ITA$@x$|$<9<_E;|p@F*iU_spEJD7 zI`3K8SE44o`STi0-*~{3%PNk729p z^Evg27SK(YUD1Q%x7_hY#*_c|%tAkDRQ7LIAYGAt@xI1=J3|GB`Tc0dgYT)h|2fWv zc*^dyT8uR$4;+2rL<^Q8mY$zW@$+H$kn3;SLYrdrkcm(m-b?5C;_XF0J;^=c$fJp) z@XZgI1UeOH;W}&D^Wlu&jHvDVS9t8|UtoaR>)$0qOFpf??%TzqzR?{re1h;1uOWQZ zII{hHyElwR#qIi6Nq7-)>lZEZTWYy}cHR1XM=Q8%MQJ>X-uOc3;*l$&9QUP1MTLew zc+CB1c76Ot^_6?@XlK#PNyl)}68xD8&m@kDE# z*Mu3~L0U}r%IV9P_@j8gf6>G-qpmFf`|4@sUGqKH)iO8P<-_i#r@E28hc{{Q!a>#A zTVk|7hRU*{Ozbzk6JGkFOZqxYae)5vtwUZL8$Q~s8}YkZb8zVT19_se_5h=y{5{hT4GZs25 zNy7T;HJyfN*W&Es?9n-=GeZ9624)WXqkPnuZx}&yeDvUe9I!Fi&-sM~>nmI-IkRU- zc>_21aki_B^)heXCQomaG{wOaqR))#dP{Y~<4z$?I6JpbiwEHRMM+$^h!2(^%eV`{ znPqMVxa6q33F3oDjY*YQH^*b_y|8jKK3UyQu-`DL_G0-;s$NX&;{o3x74C{Zc~elD z>!pm_e?cucR|KioODSGzTJ&BQ)sR=k2QH{3K2{(3pRYVJ?-*Zk%Lhl)4)1h@I>LEf-xM(i>m@;)nFk zRo<06sKPTwCXna5?+h7(#cM!#GwajJK|WZ84$*P%0^c|Hcf0c4aYB%S8u^}lzL-5D z2xa&VwtxSCuk92&Kco+079ZUzF3S7DR`bU8)XJx~Tc(2J`#E6BM2UT7`a=g{b3E1T z1L1x5*OvIpdikwS0hN3|L&MSoh5cnGEJe_&yErAzJoVk@TAaaNwAe_o$dzFVBo8+GppgXc8K`M7cw56&+3q#OI{`^JzBK_$#SKrc+%E zBCg@!dF6GC_`va9qbbx`Ry#gN5HXJTgkVf$S5aEO>prNLE8ADypBq-5oD-q|WhSB4 z@iGTEjd3hi|KY}{LIPwEgAb}RHqcHA$jt}fkz~#k{&MDga&BIG{3foW1Y^>sfAQHT zIfy&!GuXa*xE9Ie_q9SqzZ{ujB)}38Bi=b#V!JRl@BGQx*9P&IE>_ywUafUJ^WF*9 zXFe2$t95!H#Ijy#s>jcG#s1_|zTQq&@@y6VG?GslOEKA5`=a2D??cKb2XyE-wh;#K zMWSHVdSz1a?QO=A>4QFiCKgxL)Z0N5N^a9w%#Q+4U%m@TYu|GvR3i*T5Uyi`9ROysB1?q+n2(=&VQiRA)$#ukEDVVVUZ@ zjolCFAn|6krn4fCD;z)8?j%LoVA%N*2`wPC87wEKch1vE+o;uCwsw4x50pEG5ZT3( z3oLI1c63RMFyo`z_k{C={`#5-LF>{xGQ*%reAbg_tk4GaP)dVvu|5bKq(pS`)_AAl z#`UL(r`n#|sMiUnunOF8#=WWjyV6@{S|;Iw(!hxc94Q^eBIB~4{!EB|zNTY(;0WdL z1LY1YJyOr9R(v{7|5!`<$24uB6_X{dm8p*6h0RK{46Rh!-AWlgfS%*JIhK49-pAAx z?@H-UY=$Ki#o(!7`q|COwZ4A8((Z}P%BPXKD-4*c_pALL-mC^R?f5a-yT-}R7q}rR zxE$ZSCd@ew^3_Tn+pJ_1%9ZwxwqGXeqCb@_Uf>0S(M<7gLttmX&n}64DVh(QNXep? zGRTEu$fZcD@Qyjqoskzqf8qtEEgE}s%lY4-oprO;<&3bK;?xM$oYK+k+ON4ojS@iCJyvG=Sb z?o2{1M+Zmo0a{vbB~jZSsgCt2wOp6W%Jsslg0s}tZA+F#Ur%VCr4n@jNmO+6qDYPY zBQ6C|PpG?iEY{_kI7%YDE_zWsnB&D~p*Q`fByNqm1>!V}B~eOpd0XsBOLnpPSBFGg zUluN_%IL$xs{)zPg3x+kP@bTPz`z`c3mcl_gqm=(2a^m$A;zNqjt0WGzCqk|`rN=s zULq|xllA2*PA{)8jsQOAvQQL$4+-g9*;B8kCyW&+mUep9*B9goYhHd>)kxjtHCxmH8pRH3R zrN?pb@uFr%#1#t^9xLCX6l_+W-b6psps^c*r{T4|x;<=s#GwMAd&U(ou3@&#y6736 zHw0!&U7!D1@M;F1FLX>bGfkHq({0vX#dOo!w>?5>f}^xLPr|A1ZM=Qqq~~pFySzQR zA`igC$EZzx@2Rh@#JcNkUP=bsWPLqd#}1*6ON50I6(322doIq~XysRGd2K7Rl^T!? z$U@wLNQJb_PYWaDA}u2+i50(8$F3blQqe8oW`tr#FNW1VyzK|7f)cez}4yRSSl#OeL zS6V2jcQb4*qqjEd)P$w%>nY1Fs=~T`o&5Q=T%BRQ9{Bu_Z9Ue$op$uwh^IGfLpw3P z?!onAqH}U|!i)J@?-~Fqq|M!r)khqu#tIs1s%sn?ur)2;5 z-V2ww5G1ak<%8$K)a#?%)5_y&7n_uzh=1rAxvbWk9*`4^7$jHsqQYp5`wUhItzd0J z=w!2iB}|OZMON5HHqPme#^PX7ml+HMvtBCyDataS3@lhrT3bnBj_M+V?Uaa0cd;kc zSWIF;Cc6~J9&|v>@qH^_&|E=-no*g=s>yv*k!4-QtS_yF{`{Y3uDZ+b?DN{ObYE6F5)c?*=$e z;5>nUBfxnA=L!6q0nQURPvGAQaGt<<0{?>m=LwuA@NWk=PvAU(e<#3s0_O?*y8+G< zI8Wf;3viymc>@1_fb#^-6Zj7ToF{Oez<(IvJc08B{-Xfr37jYJ9|t&3;5>ooF{Oe)U(gw z_M{%-z#k28p1^qme>}i>0_O?*P=NCU&J*}xfb#^-6ZnS%oF{Oez@H9qp1^qme>T8* z0_O?*NPzPM&J*~H0nQURPv9>FI8Wd_fxjH!Jc08B{z`!J1kMxqsQ~8*oG0*e0nQUR zPr)B9IozIt+mrkkLVlj)=SlwMke?^{d6NH%!|h3a;^hB=!|h3akMDoyCp`xT)}B+w z1ru~@pYB)uM#sVZYqvWO6bCwxlm`m^D%L&N+401ILejIdD<8TnsVJ%Ofp}f?j4;yJ)L`torX53_XyV0 zv$JzYx0Zpf?v5V)C-TJ3&L?&%+`qHv=zn78&Z4ukpYRhq`*(FKX<)Aiq{xn<7`UN) zJG%$Fl&`0!yIUQ)-LbQ0um~-y=F?QaueZCqQ-6B&uX|^YGVknBDb>=`vtzd&J4lAo z{r!p$e4hAjQFOQJ@7>wm)!W&-w|jSIFMUuYo!#ACo%`Q;yk~cJPp?4y*;RD)_a8c> z0)0D*j=tS$sB`aLb*6Kd(mEBQgM0LUceilp?C(@}L;z6Q(bGAwcVKT{_a4Q2sZHQV zy8DQE?fSJ;J%xla2s!n5SI?fEoqL4N&R+iP>F(VV|4_N=QBQh0)dPi3gF(pBRqD&B4a8_tb7Cu&ar+V1A zTlJ}=_@`qxnJA3+j zz{vYXx9AGPeyF2g%=t#=z`lN)FpsMTtADw3;L(0z{6=SgkCYDOIW)LKnaOrQJYpFL zyrBuAf%J+7_Npvd2KF*}sAOQTSH8DLwH*?>hBiAxo1L*WCeRz;Pqv6zPsjyTtvYIX zLj<&3&bEg-dPA<>n5);4dw-!F%00bSg-~{!cZb5eW8vMFzT2D7a2Sd9gj{=Ku07Vo z9?QEY))o4?4>B0K=xA?r?)AE2*ZV?UeKA*`~5-?0KWpE1*Je;QN&Zh;^&pTdGm$t^Gi82oc7a zCIvfC?Cz-6>hT z`kP2n_2|Q$k9Y4Lc-SMxy3G=5|9^MNnou8qPo^l#1W7F?w`J2>#gPH8J;YL5-)z5^ZN(h}IKH5C>)(CKOi6@ai+ZX?kznJx_U=g=NPqBW7n zy7wAJ%FjPW;SeJ`pb)xJMiqI-QBAGVUNmF{j>JdBWj2W3!R6$A7NEfj^!7tMiHep8noJJ)Y>= zzxFTt#U!hLOM_#~-ao)pt1>I1OWpo;Nk$>k*uVD2@W`p1-5ufxl%qbW6jJDV`b~@L zx5uGFhfpv?W)Y%~0_P;A&_{MV4@+rN6;eUaA*GdSo)3IpTqPCtQB+DTV~9a%yw94@ zedke44ngc`i_uO2aEUxCE@RPB85heW2qWaBsuhPDs*me;U~SoEfJmoq-jWN%fM1Ss z;mfo;g@(l+>j1yKy(Wj`fI-jh?s}5dt-IKP%8GgqmQ#ksz?9Z3mI!c&jUZXqz9lb; zn&D|400PS<@)a`V2NryFA?I#eXDlxmu70&lTBb9rzmN7;{!yDK4Adhzp~)|1H%3xM z)IBnWHPaV6Qw=p3wp>P#S0|A0$3Rg*b=(U|g0V`5zTzVi@fH}lB@-UsA-EhnyaXAS z#97YG@c8ee?T4u3E*1lSLWTI3RN+#rV3`WA6QL~bCM7N#2^3geHIl5Nz)Mkf#@`M1 zO9|`R-MN3QZ&%TwU#%NEcJJOfEBiMfhqg`Yr!gtV={S|N7PV-?SfPkzOs8twWpyx%ovy za~-(9|FL~6CxY8x#MCD{cI=i|(M&|P40mX4v$dMVIwJO!fY?_`guW^kM!1BoeKllo zo}v$5(}E+5S0wnlX)g*RrmO>B+heTgse7H#Jh|Vsuz`n9TW+PPg%lCu!FINc;-HWg z@%UQ6<7+lLUl$I_01o@K5IVovZ-;ALAo_KowEvw~3^5C@{)UC$eMH8exa>DN`;4s+ zNRmje$Rz5OR`4~^{avj_GFDXw?K88!ZtZ?8qVx5D&eu!2y_;sC6@o9`4Y}U6cE3^P z{f1ShxJ=vCKhZyNmVW#Eu^YVC_V3swu9WB{MIqQzVsP~vlJoo5{vUo<|3oZoFcd09 zC7bozUyklKLi^v?J1C>y{-|h)#y4Z=p96|KSz;9g&S~)Q&#~yC2aYJiMa+Bb{;T_h5O)9I2Uag=SI%e-KgswxRw9 z3H5J_%VP}@%|8g`|G?1vc8R7J{Af#s7| zBeea4knaa&z8_k?A6mZD*&l{{KP>b8$nyQj^2y~A>ibd1_oFi3k1gMiV}>7x3_mV2 z{KPVtCyv?hlaS#jWrm+xhM&e7ei}0TG-g=aL6WB5S`Rtbc9g@s7V$ENv1a?)4#sw^ zrz~o#{8UhbLXYD2=AKprsJ!)ys zmVk$!qm;(MT;?=wjXu0(%{|KR!_SqCdcXMH|8U%Xf7$9gV$)XhRbc)9W$&l_Onb{o zr#>p06CyUe&&PKy_05s=-xp&xR5|c_w8W+yGO3A5$cDlJFU6vdhoVl&LuCb?&3|7u zRX|-RSqQC+22=i$H9l+&W&`ekS7L*Mp{Nhqhsy>%oBy7|It=tE+x&1Sn2J12o$&fO zqxV!fa%<-v?HEU1K9*DK>e>fG>d-l2g9pe~w`(nNTERlFRAn~;Ez_}9pXJhxC z4H;akhDJvWxf%Oi*l!Ez!x`1P{~`8kS@V%l@JLx_IlM21q!-Jia`;{fNiUU2FGoyX z4oNSUN#z*45|UmilTO7_r$W-HGU;5P^1~$?*>fe8A1?XYvqxH@R;scm=Ry_dY=$hA z6|WHM2z9A@7UXo-{w8c_~&g zHAs?K60@KM(6mNb27`rqWRUjA>^DyW&WS$^I2mwiU~MV`PD$Hwj62N${7L7d@xo#}L0IaUr04T3gY(%E4eGiYDMSa}h3tF)e6LQ8qdq~}r=#wh| z#c^P5+#g@wC#mNI3iGTFU(z#z{VM;>GJghjT(M{LkBT4e*kOZfxNEQ?s-n!74OIdW z__*3KAb|)R99VnTfCMl?`#V3{Eq&h@W!YRRYs)1AF@(#YXJk}3vJ~9WfEDB`GS<+K z_CeHvTUJ6pztFwIIq1v6$wTj|O%=o=_+>p|FP_AK6Mwg_dxw^}ftOUnz}hd13I_`9 zYC!$JZ4}rWd-ABWk3tq00{8789eXoG)pMiLr-?>Vt5yN_U`NGcp}nd!p=YhN*DB9Ny3SB&IeF+TaqUba&hzpvsu>9jTL96@c(UEFc2%uuqy@Ta!++I9%QHSn6+2Gu&S$%|(G*N_?P#pcrhRC| zeDAx=;FdhryIA|_jsw~Z@31yj*B%jMZ8{Z}M9HJRK+I|`_g5R~En8tTS{?{xIjFk6 zD6?Nxd_L9qXgi=$|KSny5#ZSlLG7;CBA|Va4pZz2n13PmN%*mY6MEPQD{CB+Xz1J{ z=8VY$wwaXSA_sgGIrp`(;Tl|YpI7kx%*nh9S%aF;M!uFjWuw{|5HULTXq$>ka5$P? zz-a)MvxhHQulI>qSx~Cdp`DR7m zL&n{efh+6o{4fHV4KQ}~+A_Yhgr~YS)hv^*f>{$~O=_sq8F4i+q6fuu!c(p%CJlq; zp%1}O4?W8|ZHrpM>?7b9GRgv3FE6s|?@|4SohPi-3{~wRxG^)jzsfG`X5mZv-Lf-s zrDa?~`tHQGiaw&Hl4T`ZEd_P{H_l`r;L#4YK{W(IYweqcmTma{p#^cLcf7w_%pem+ zJotpk6YV&N%M21zcI?tgn=bwD4q5i=Tn8v}P5`4!ri9#Zgp5CR^rA=SGh{}mXpeU9 zT<)@YEe@ubCSYd|^_t)IKrz@Y2D7GhVn=$a(DdCq*ow*5125vb7HSV)(ldAr%$Lm$ z%f4=*0INt7q5>ps0MH=21{ZJ9%Mt?Cjp%rILrVTiB&d8?J66J>c5dw;S`&w?6i}stG`c9>75o0TTE;) z$-H1gMSrlMdpKC5o#s8Vh`eBD=Lfp?$an}b9x~gg(}Ef>t5O5Gt1A| z1WKuXE11_2^V>5Ztd)KL!*gZ@oKjCv3bePW|Bq=e2==mdUb;6Sbb?Z+Ai}83#=+?Pp&19mn`N7Ce3)*&(z4h-|aJH5t%e`Ak8QC1wlI5o!y&)t0Ai{JaptG}UF)f78c zi{jUG8hcl#eiXlU{lw8?M^SX>??+Z2`EP$Z_M2ar_^-PD&JXr<{U^WocOJX{JHPjn zhrc&8|K|>_{PF+1_g{SGe?9nbec-|$|A+ti!~gUje^pB22ma{4{HLE9et71mKm8}e z`~K)-AG!X8^F#mBXMgXp?|yOUfBQ%O?Bc&aaPqsq^Sgij_O9Ps`lo;8?|gA__20a4 z=jNsUg3xTJBoe! z?eSny?0!WD-u>+=dtb>elI%|R3I6Sv;jqrbr#Ay|)rM=FhwT#1Y_Wrb`a7Q0&)^Zg zz4rOy1>KrDR(!npgx=3~TnFFpDI*8%CyE_iG~dln2R~44hYoiXJ9hC?{Ez?9r*@1h z)KmP&|7c~{0{340OTY7{fBL68{`4Ozt>fd3Z_*iM_`m+@Tm1z7!igXM=_d`+JNKnO zA*as3=wJ~)d-!>jpZD{l{%c2^p8f?tNc09t*AU~`8 zXk_&x%y;Oo5D9;}cIi~*u7S_%{|)Whtp0lEu6~7I?$RFm4p}}w(a-ANSFA_>yOi<@ zt9EtYTv^^JlypiJyraWI+9vlI@+>thBMii%Q{lPb!a;kWbfq_{uIpIQ8c>_ec0wsp>ak>}z52uqPM9 z6v3GM_0C;~=-Mk0@Gb!`{(6aVnfV*k{3kKzH)GCkl{vo^)4rvg53leu5Kj~OiXa*UNd z_;8v1!(@+)vSzVUF}pe@Z#OY$bFM6NE@rR}XaLuakaLEgg_u_2SmIKmQ8rh$WHqXh zhfA>$tvPC!x{b1=EthG_G3^Qg(!Nlp*}~!nz*afpSvpGd$Q0S#e@p)#=jY1?GxwyT zt82hC0Prtz}QcgxT+nQ!gfCEv>zG&4pB zt**hGc)rSP3ruY9hnf_(>gQo^q*9Ms2=ifJuVDvF=Zc*?`#NLoj|Tln$6rg3AxF{P64V)Re{Sue}Qi*vW2Uw~lS>b#N+2&i>r3T{hm;dIJUw z+Koe%f-6}lLVhSxic$!Y5fubs5S5A)p$GyBkOxFlQBWT85Yz|-DTwg%eP`~SyH0@@ zgwzL|?97>S&YU@OX71dXxo7U>WqTseV4gvd0UPoJ`_74F1}h9|3{EmQ#bAxWI)e=c zXBeDiaE`$ygO?bbXYeBiuQ0gC;1Yw&46ZQv4TGx;t}(d5;1+^9i>$NoRT;@(Tx{bK zN6D*x+YZ~)K z3nN;v_N%P@Dr>*W+OM8&!*Bf+c_ncKGZXB~!J+O*Q!9Mb zeMcy@-G7e-VTp{QI^hvVr+*h6i#zc1;MXn!0xSlq!RZ*Pwc^kP{GVD8w6y~%x#XAw zD+1A^(&N!hKSg&t1Nwr(C00U@8=Feh0S-~rNzQj55`SH~@WH!WbFmGDEhJJCD4R=(gPBCYA;AHsGn5l zjcPCSzyXwK6w^a5AcD1TY3Q3^C(didJK&Y(Qr^T=WKa}2A=?wafY;XK=WGb5G%Ugc zxW;_LC|)i$e-oaMF&*Rr&+C>5R}k_-0@P?D00cl3N@jF%l_`mexH@PF%ItzQApy6F z27gv~zPY%d7kvYm=R(UQ4C71k#E~!A(E^XA6FBIygPx*ZB%xN4B8O3C2QHyu2;>%m zZx}Sh?9@qAsIBe(x=V?DxWy9hvEn1B9h^i1a-sSH+TRiwKa|B4%m`;=B|4%av@Vcd zB(P^h5~3r(VYAyURYT<<5dFCo#k8_xx1cuIx;L;_hCyXsl53*oJk^{B*o4_TT=8H% zZ%dHQ)6{7mkAhG&M6jjGT$XHQW-GVK<{Rhtauh(#>tQHN?4cIge?xOD(|)yAh(;vk zm2Ht#5m}{3f=yU1ScM@M#4M9SunCTn7Nu5GCDf!iCeUYVEf}hB)CEPsBMti3Y8n8{ zhe@@X`p;_BYPfmATM~|INP_!Cw-$d&i9aRcnE?AKOeL2WAfCMhQdRqk_az1lhU z14Q&8MTAijO>++86+-bI6vzjkN!zNMPD5IHY>FMXxZol87F_={_m(PoOVnTjHQchT zyk)}#U~8&ZzhCUW)#e!bf|7ATWL$8Hzo^7r(1j5J1uhCBL#(Wbq6;4rX_a6EH7@FW zmsGw>lJAmJz-7gFNi!0G0xk=qMZK)4R}}S%P?S2XpiqFxm$6QEwz)YldDb)hl= z>g$?%O;N82l?hOBZ^^Q%&I)Ijh&l|2=c*R+l2j=_2SZ~&hM1avPjBdBZ z=yq~=9I)E~{SMeES$mb7UP+9q1S8xzbWf{$6y76Dj$nADX$8B47Ep`s&nwub5Q0TcH?%wMVo172YoeG}w2SAO;nc5pwx;u3ZYRm{gclC`Mr2rCHfF zSQ?XxNSn4ktQdPWBdw2|0gVVs&Z5d$ltfu27=bmYSuMLoHNrAUTqCTC@74&bPJ&s(jk(uXzUs6$&;v{6l)P(mg|$b`eVq8KM+(pq1}@l;+j z%lD#4dc~+*NaEOvPS!A-L!laGTbvT%p}s)yXv(`)2C)yEh6G^~pjkP$Q@wD*D+P2~1*pS8Cvk(pJ^?NsWTX3%A9MP8c_$YV9$d(@2etuF=M~sP zW-1RDc;qc=fr9_S)8r_-=74zz+;l+9Dq!vzp8=PYWnBu3@M6sZ&!iSr>WWx$6MY|R z7{ixF@Gx%o@D8b?1rgQiUDOkYEz>xRZ}6WmMDzio&S%m7QE1SYA9}t5mT^0w&VN(y* z!R)};ypf5hMdJd}Z8M^EuzIy9T#2>Wi8dtK?F1LS5T}GwT8EwJ5R1tzw|<_*p_b>H zu?XvFXA|%lDe6q76LJU_o#;GITB?PW2N_f&V+{hG(Hmt!g-skU*4jI;y2a5yKb#uy zFcFho@Y`UgTPjzt@v^3uq{k0Org!6^v#4LfWK%44ixPB5*h zBg5jSiS+?0uz~xSV#82{?JeTqytE6qd+@V+!UTBChY(NwsK>*2_(wgkeS!#nOpH+T zA9GuR%A+PbFlONS9eM&d_2d)<+(Z|*|DE&Q)d-*R;WxgVUI;qIn;1K7pfZ8 zv8`(7Rf8}d+fgzT$cw&emCFGfc~^wW1gI-`n1yF9y@ngVrsiJks{m-jueH4w`D9BD zz5#^sEELcB6+|b4=3XB4>&2C`Mk%`)3^GUpiKQomc=79K0SZ^0Td6TG$u(rg=Awlw zi+p+fBJ!=}8Cb9kVSz*}Q!0+mSb3^8u#Ei)F)U7)+0ZEst-G!KUX9w&&{?TtU7fPh z*mF|Q8H8AGNVrMNi9yY=>1cUQBeyhk!69GN$cvPL1pvde#$Iw*FKeU&U7?xgHQ}m; zUWZ}1AEPRMO+(j#=Z1~K-_X!a4c*evZ4KSAAxwcSb8$LqiNV7b3#nS4je!Flnz7pf z{SMek%hh`&9Mz(nY?#by30=Y!&21R;=HLgi2y<4Xn z6x}7Px^e3&oz$$X0_TBR+hI)#XecO5MG4Cq>!{{v;^^QM=@d4si@{}W+hEi|E^Fk3 zgItjs@cO{$#t^e0YO7{Jf>!mX89CQsxFMlxrIUa$jS-A$W68SaNXa|krUR-XXV^*8 z2r$n;z@Tq!+m=Boasg{tWJ#O%#|wRFbEz8ePaMw{7jjRSpWYJ;hND6L zMdK1BR4N$k4~7L{(PQCQBpQl@$SHV2_=RK0SIFiHx&BBdG?44>&qjigOva1{vxxh% zW(Mf~Y!*-<9*<SSS-JL~~|7F_0<5BbjV2pNNOT z!GT~jRLIA&;d~~NjV9ueXnz5vI2z99pg}&7jU|H6WFVT)#PgZVKz<+`4;B&wp-8Y03B@y^{#0pLb*hLe=HZvm9(diWC zS}vnw>hM^4Xl8C=ay&gAelsd!827sjPx|mgYHoV$iLvpIj~NDLo$Z22j!*9$g&+zR z#QxFop>#ufWGvkuZOAZfA#?_$BPdpvv43PHm1LIf81i^s__ibU*gHNx+8}c(JvlOV zprOzMljGA94H6Efrly7tG^)Axa5^>BAcAUbD(t!j^jLZXp9wSC zUB*4O(OX?c^S<#(WFw!#&P|M@_JNOr9-o>U8=pMLDp^T8r%jzcxHmOv7!)*8HSrAq zc2}#%F2DA5!+6gNLkBkIzHk2IryZYZ|K-=#ANkzrS4Up!$v*!;aq9w4@D<{Jl| z{m##B@BZDhPk!^)e=U9R(dQoj!sqt*o*Q~_Gjr?dm7CcI9(m=lKmKK`+gEYlKC|%I zJ@Y^O@}J&#^ul-V`_jwbd#~Ab=dUfF-@NtWdk=kkCi#n(zWD0J*nPfVwg2rq8*3bf zZykGR#&8{+Xi`J6(ffR}(fj#kc%3Du=6)Oh@U_o8G5U>-y&dn^Wvo!`eIJ^gUczWO zJ6~GNXL6P0*`&GjiE61li?O;|!a(@}8D(d4OY@mZd3LFio4xBApUo~TB3JhKQ?rX^ zH5$8{-dE0NsoGoiulUtwt=lt%{W`e~SZ?6M$2RtrD#>E;U}j;_IJT6-jm+%J7mJeP zACEu=eaQ^t_kV8TulAJ)wHpz&K<~;j46q41V00pVJ=4=S1faj~V*`^Y7*6 zH~y}C8C7PNq2iAUl^$>g9pSe9C6RJLD&LB`AAW*-b1>hu%P$Y{Z8B9pyR}SrOm^=-b zFRJI8h1E;_otIS~Ltgc90=|WpdSiM5x!sU!>ybqM#xImO`X2zl{S75QK0s@KLrHxo z!Y^OVvcEuu$rE(O&Wo0N2y*#KeMepdrHAkf%8%{Y1%6soy=I?ZjwzzetoIumjpg?> zl&{_{+=qTC-#Ia6jULDuMR|N_eNik{hC?V{2>uk_SzLu~wBmN0AMTxW@{Aeu_iQZ7 zFFNJKRuiJ*xFw5LWC^zI^tW!d<^NB*2KkuOstdo~#Fo3;QjVPrxSi6bL$D)Xea|;U gs}CFg`#iMoTkvna+3-NEjXiHc-TtrA|J(xq1pbyw-v9sr literal 0 HcmV?d00001 diff --git a/Libs/Logo.png b/Libs/Logo.png new file mode 100644 index 0000000000000000000000000000000000000000..2802ad8629230a6abe0d84f8ccd3b0609a99c329 GIT binary patch literal 44493 zcmd42g;!f&&^4Uk?h=XzcPnneDK5ob3KR`c+})vADQ*Qyk>c(Yr$BLccXxU7d%o{^ z|Au#EZ~wjW+DnpQS5TqydaeKfI{treARr?P1iOg*<%6;e@(MaOJQpz73MbHyt;vwS7mj6~H?HF!Agd@zfw}a@|2G8J{=anRqXzWb|F!Ls2u~bl_^Qffl=%H~!|Q2n=k?UNXUpCH zKRHC}#<=Mv^3h7|YTXKpqE%^@*oi2FnvMu_J_t3wX`A7n${=oE4n&|6@546bi!$28 zsziggvomJd4ZZCN*5OHf^-TF0HSs-&b(d>7Z9t7HA@-~&`F{*pKR%&4;p`cix)N|d zU{*0P_%mwjW^i@|s`9A{rb(u`Pkc!hc+ZE@uPc<#1X0|V2lh72((WGB5nGyJN$DSyU|aRgyh>8n)UyY&5Zv``IEubW%6phg4V3!_iOEi`jF2 z?Jn#g*lV@el$~b)g=YI-W{tWHKINYk(Iy-zz03~Zy*~ReaMsuOEB+QXX5W0xF1)|@ zL+1QvqQLOzHcT0?SLE=r(&>HdfBuChQ-PUDU~2L>^!^xIWozVjmK+0K>^RxJxKdw4K5Er`{0oMdyR{OX}N(da9Kam#)e1gw8 zNRAd3HMg+33eNC!Qb4ZKBZBNH zFO88%PHEP~nrddgB1q!qQ`Dl$$iwZmMo0OVGeXpaKM0tvt);M(8^YG1oH)rv&D_-ze(b}OH}L$0O6uu^PltwF!3 zin^+uJ1f zTGy_OkAlan7@4^h^>&^cAIXcJ&>0CeALB@CHL-EQ$a$jgzd%RsSpNb!a(-=e`$T{4 z^MQGn55w~IvNvYHbGA-bSJ$*vD>3AXq<7<0R6sW))LIa^{m3Gc7?G2{kPpMubeAIv z)FuBC3o#vh5+plVi-C&vj94Ewy;Vy7^5SQ5(K&&u^Gt8ZCFvW-SQ2hf*{PuWZEVo3 zQIFaW5>O|Gpns1*Mn3E6=-|SMDYQYLH{pKR-q;U(ZT+KTuCF)k0~$_;%OLH#)c+n_ zONwZ13W3WbXH|W7WNGznrNr5}B@FqL9o%PC`eXf=j~LuT`O?c`wAzy1z1rS zppCA^yZCwo4HvRap@4T56cnV*q`l2(V70c-V21=WsGy>F!#(qM1c-;Ye^=8qfXAKL zRmvgIYVT&q%cG&aG`l!ySQs@+)X$NKn0R*j)IivaI8S*y0{R8o`HM3;I~*;T1ld6+ znk%FiCH5MxtGu(;BaC%*suSMjhxVbep}yh3rYw8~XYjZnQncPN3A06;hOeIrC7Edt z&_<}FFUM(X|L>O(#JolOt<4^xUA{vbcgbyE>>@)Xe0aZk-uwPNA~$eQdMfj`Uzp|7 z1~w76V2QzdGT42ZA`y4&Z(lK2g^KkY+@PP$Ft146-Y-%|1{$t!Oxy$9u~XNo#@jg? zk@^LjaEt&aD^d}h-`S|aHRXyH&Q?SL4?Hg%H`BPn4aIjBqmO@2Ol$0Jh^%zVbSruP zp&{yL<0Rss_+sm=$qA-)o0pQ&%^tGV z^`Io;3_glIx&UPr5RWO2eMaUpaY7Yi?7kb9ninnr_aIDZ-Id*VFsmK+n>^b8_4kGG zvf5Ao!=NF6qb%}8YDHEA`DFWh+KHB#HP3Q-6a9J^yQdg_0KoT=g*zqY6s;dSFiYZe zJa5Wq&c;yy96Zbr?moJQ0MWjq)~0Bho27Bg#AmF(R(SV8aAuc4KrpV`)3t}{#zAc@ zs$5ZJ9{V?J-{(_~HsXa$8{VtwQkHv;wSbwIk7bj}BPIU3>Ppuhdk4>xxqiK z9b;#2G1%`;IVD1^$4&YgRaCbwH(Q!1qt{RZIVaNw@FP67{5(!V1Mu4{YV`53ATjbk zM}%okga#km8BDtD=Cmz8P74i9#yB(Oc0ZjiM9h2GK zX7m?nZ_k;RMKy`yuYChf^Q#>mr_AvzKInvuUy4&wSTqZlg*Ccq8s}H}{K6IN-}LO< z&g=2hF0$vWs#fiVtEK&-G+=plwH{rSY@xj-Kq2tH;KL zk2|#|`N?Qq&dd6xh>8+N{w$hSOTs1pq3pHY6%SnW0-!ZQabB=WRMe+r$U#~1DMaz= zzR;;14o)IaDKt#pUp%8?ef727G&TqnlxT-%xx=*}eC{b@eZiyvo}0ocw3USGtY{*k(0zMY==2fDhl%yy5)0qaa2YMq2+{X_#BbR9NTUpIar zeEJnUxeCig@8H#jtS{^+TPS_xnEAgrbzWMlN_>S#!@j94weoc1f=WYjU8udY)eiz`JR!#;W?dG9|nT(+irtXn?|K0fpP3wil z-I*LTS=4YR>t6#0=E&?jb`Mj7>k^g{w1pd+Ug^V)!_o0YS@ax88Q#4z>sX zI$2GvlL^jrvx@6r@OP~x<^}kiy$~L+F=^sjY5%(a%OQ4@lW_~M=@_{1;kRqODw7p= zLdwhjZrrmMpWSz?uY3R^F{v^NEG8rV-cnNSg4=m9b$0q;yWF8>?gtw9;aPq?L^xi^ zwJzP)W5e=!)De&;cuOCOPkb`Df&iI%%)0UOy&tSJuyR@g+AvsuyU6YIK|A^zPgbJP zcz`$o$O9qV&tR`a2zy|-%T$QHTz}5W&_lx2yIUsB0mK!U$9Ik;Obcy@^Atn)j&%1^~C6;=6vAbulGOB!JNbUBLb@9bShr0E^r3Z&e z{K~tdY{`n>SE*?noAVEXtU8(BN-SpoC4P#W9iBv^5qVkEBB<@g6b$dwv9Qpdu<&r6 zc9Fd#krWkw(Z}J`G)aLNCMXLsnpY-cg>}{L?KyhdH#~2plRR%|!mwsrCA2&TooGNe z-G_3>6bl{)wWs;P&3A8xge-?l@_{}c?)7y?vC{R=F5rY^fMcV+j731Smh6bu^(MK` zj(>NKRQDGj7nhoJ5!7xe)$eA9$vP3v0yId#r4EXBP!xB2$;u~2{M;@P{(+Qy*wmge zA)NHjB%TRbiedXTVfX+ac|WBrC(XU!jnxoBAlqnHpi^ zh~KO=H{i7ms8HEWccQv*YCPxO>1>Yn_He-Sldl)_LqDQvtQzD>^L)D}fK39aSm~}t zzq07bu z9k#XAIkIYY*Yu?KrkI}7N_|K5GLa-T^p$9F>D##4!#FWJdmc?FucWU3uP0G*>ht3T zyZC?%qtea09A8|WZ?^Hd&a;F^{x^uiW}zkH#(kzHSgjEaxw98;Mb`CJHbC}W5f9p~ z?=Oo;4pcBloz|z0H#)AcW)f&s{;`01v2d0vBb}tRibROfC7QZ2_(*cF{UM0!(~lo~ zB0^}=%-&rB8>fCUZF{T!BEG8Px2i-{cB~0e8)WYByHy@?6v*yG(6t~u_qZK3W^dh9 zn|yqAUuCd}r!``#FA|4gmRu5W{|2tkdd#@5td>Qa@E{Jp?l(oQiv{W&(B^~@&U!ie zmjiYlFjSLg#Ks*AbAiRxV87>@EgPVzwb{JU&l?`i_}rAK+zJi`vb9|RCRJ9Jo^1ae z)U-Tc$(tCpC*Wb#?tYO)6Q^gfU~?Dv4mY0$WzEYCsjvu+9@#0Dd$VkyWRqkK31aZM zu8URq*$h}$V$vF|4HDGkF$PI84W`FwoV^A93j&<^nF7uPClla>W9E6k4Q$I}35VCt z3ug4@T!_Ht4fQlZmAkz&+p{6+3yyFH<4AKU)dye=hR<@Eesl=gfW#$$c}_=NxNB=P(2FQ1kL<;ET9AY~3CF#)(=7x#I^|0TlSG7v4Gc8;+UH_ zM8N@KujqBl3no~P|IMF?a%6%$^;DzlI@RH0J@x|@lQ~ocWgEz0rW(N2Vce+?5lm1L zsH39oqhAhs0}&9$#wzj_57$?-d*}QrO*GMiPHY&^Lby1mREz~>^T9b(J@x&+u41p} zec6S@udSnhT1LZg{Wd}8%0+sg1N?h%kBP|DyBy(w3LocrxIC_Yb&Q(*tE-;~h$+u- z%;|8C&tx?s@VTy`U^uSzq7n2#Pm0qfVAp;9mQI$Nr!e5J@q`cX{R&%Q5>tV=CZu*~ z!UpYlzq7;7UdK2?|BrsV!xpj(3+3GldQNg6XfmNPp7wUO!xcUK-$4fDH zj8D#UIbviBI+D%%X6>^Q735}To)l84pscu*E6>|w-a1KVMPNqVib`AfDgYuZ!WG%+ zyUY7U5_jp=4WQrUca12R!VR-nWH=o>95me;oGa6hj)5urW4DPI^0YXjLq=Oq0$EZA zMbj+OS3qRz>#>1>>5nzdjTL}*xEma~UYG-8<9d8DbBH!%WHKfTnJw0SYl0l`xz1l$ zebAdC;J5fqoovM%2_f*H$KbZ>>~yh|U=bH%B-5^_beXmxI9Fq1HG?uCRBcgf`f(QDi$!9$alGm^;6+uI(>gToEGrw959Ks#4+M41!d+hGR$Ew(KT38$JO9Hter2XpWqNdbi}=(}jH6OIp4ceKGseaN@6B z9S#SS7Gzw8Nv`Bl92R40y33(mwZF8(E~L@?bui<0({5o zFF7V-Ms;%qM8a8uxN1S3wc!4)r&g> z3UyF)v9P28=({A=M+a;wgY)f4#O}!2C&emhBw6|9)CozsWd^Ena6-Xa*0K3`1YAjF zdE0wRe<)Co*P?~)`xUQDO+C+ygngX8vvU-z##`oNy(cGB8f#FXl%g|`4p4(EaDdM} zvj)=toXiRV;3PG?>Ggl|8pQv{;`kEn?A$o3*aP`+2-cmk;au{+B;%(mSS3_gwTZ_J zP`+!X+kdHC`Ef-eA7Py4c^k3&k;G`gQTSe_%-`he1s-2b2VJp+(R&9U^vdvN z=_2S-5i%}(l?afW-HJmvL<3O8A1d23g~dGMr{GzsEOFo93Rnudr+W^$D236b7?A`u z3vkl8-#k?nsqRO69C(RS&eG(Xu@1T5e9bH!N`;=2pbcdX_E5ZqTxdat{?n^T<2Q~0g>Vf3^>#y=XaQhycg6|wr5hY*jCl?dF=4b8$)QBg(|U&Mzl3K7v#xQJF&LqtM@ULjw&Mr! zgqwer;Tkjt5#pfw?i%piKVTnAGm)AW&Nu_2Do5)oIA*P5WP7verFf?;Fp?PNwO1mx&R*&P<1M&US&e!1mUSt92wji@;24Yw~!celL!56Z?-D*HG zGhvrBL#aoBf0tj$Ue}A!C0C0)*uWF?Q!E@v3ImN(vZ= zx@Fh8|D*0V4bc%v4QLFG7A5y#mev0iKKcA!m?hYKDY*7V-$2L(h@7VtT##v5?n>D; zZ4Bw42Lz3u-%AxXzAw)}-Rz?#_VgU@Cchsmt3~->S~vyyH=aHruE;6Ch2sx({GqrW`+=xt6+_JPY}>6ME@`$4$tLj=ywSur0)U2GB@me0XGZWG3qLs|_-p3Q z`KW8*^&dC=Q_Oe03X(t~rbR@smHM9=1RatPoY3@{cUC%f0>ykF3hQ|u!=7y6M`zDD zKtD6TDpEGOh={i-a>vsmD;TZ%-{-vby{>YSBI)#1^gV4pL~9OJHWnN<*p_mIv)-{8 z#WfS2fSlRv`?H{^fqdT+)$t#e!!(1i>@>~!)W_7ntG#KoeO4Ry`&@_!?s4+J#C`6! z&UFR6ORQ=j+LrxGoyCZzj%VCDK7$nNjefsF%&thjj=&CE2n98@Tl&JyMPk%>w=K@s zo>hVcXV2o}lj#R~7nl#aUYY{mFU@hn{Tx9~62v>kj)v8RLwIyW{70jGG;>u}^O{bI-+=}^5`LT38U7i=os%Wv|2Tl}-{Km?Vg$ZAU|k zZup3_4UFc}P3%nlE2v$W1&|p~Yd``D62KpqvS3$Huknm;!LB{sLj@XW>$$eHZfSo=3r6%Be(Bt)PwN@JserpTX4;-zCL?Ra0EvJIuUK(EElyB5 zm+h~(dmc1nBK$B3C?emAv+fxU_u0%HX=&fCQ>S>0J7Y_M)m~*~NEwaT812tQ7z7fq zfUTv4fDVw$N)-N|h04$*u==1)h872k&5>QP8Fm$8z(KUmY(BVyTLKr?P>Cu%YmHV& zNnruvz@R!p{N^Ve`PKI|F{99cCD->F;9C(9OHGiUuO4-xBVYl;$ygkurD0&aQ1q*# z`=_*$a~~72_<*;aEdh5&3NuN$sa0Y@O_NS0qsNu!h*w?vy_&iL#v|5D4 z+#YJjq3?f{oRF7-*(Df(yz1Ik`4=DjhtKZm2MX8>{`5tbVB@(SGgWG!t10&95hlnQ z!=gDF?#F1`K(+OkB@@fj#H{hy*!G8|8h!^m!w2&P@}Mjqj~7jm@FbHiXeC0h-&=~O zRVC$ws~n);_2qop%C~^i(${EGd(-;xadfa{mN+*%Q84z~^t;4K_2OqTcw{L-MpH%u zjYkP1e_mq?eiL}`$l3v~?%I;Dx}yPf7Bw|d7f`YO=_A}P7s)DO7Z;*Wz@GLeW~X}r zNzEW4t^P8sdgt z$xR=PR-su$)?^iq;0%@{p9+=}we^LmN>~4jo&p6PDB4`G-fYlRHsiz>#Be;LwUD%>dNz9?>&R!4Rl%IEarbfLj>_+KAo#%RO>E=3DKG z3?=V+(4)m8IOup#@{#sBVar=#3#qu-v++2Kk`!+8f z^G81${2h6+=#S5vC}*Uu4rPkSxMW~!*k z3#&Z5)Z4BA8{5;i#85%6Pcu=|Hi6&FD13KG_83DqNopjBP$KR=X7!1nPW-T_cNf69 z6tZI9;Y}X{(r93bxk&upc`m~L0ium$G!6v^{ch)6;>o2!od8=W(tpLr#Ut96BIcYN z8*`=wBb`>`$B_>z5`WA7am`bEw2mPP)E^ozT(?fCR8Npm4`kijXOF#j?pI0J^a&(m z2|8a0$dYWuznSJJCPE|4L$!0pIn)!t6+uLMdd^z@dnkyHKn=EY90Yh^4r)b=M*goB zz^UH%df#zbSP&uCx!Efp^mCs}%^Y}mRSO!&H03mq%uNd!f2p+a%pTB!Rj74Ta|d!o zZfpzYO@$kTqXK=-yVOtKkNO^R;hDF`MnW%#^&3xADl!VeqAnw z5wOmf3cq5mJC6fxWW)=x9#Y%YIdD{>;_%bMR)OqR@fDal3I6Es@&q9oPLt9IcnEl0 zOrAdgq)~&uDwa!O{09ka~SN z*&!+_dH=8|UrSt6J;YJza-T~Lgx(prystOaPHmw<9T&w9B7yl5?u`U+eCG{`*dpIqQ#*bIx8WRTK-tUTyx5r86 z=ilO9YYoW>cX6ql8tmp~?cv(f68lU`GvT&4HN&l_K~tJ#O#yGF_N_Aw!&0$bS{w3z z5H3Xu0Y5k&RN_c7XuH zhZEYbBhUknEEfeWGkLhb4V;;()S*L6x6Kj3Y3igz*;3z*s}=iiFZC#&1{6WZBY8XX zq#loVAMo+xS7W|9*gEBuGTu>*ObCQVk}Wy^T1oZil&LFi9mXK2{ciBPOg7va$VncM3D|Wh zl-Y)}G(KvzKMU9@o0{_p-+Ol7)VV#Z=!QPjNY-|Ttr*5blVbOn{!qeWVx>dNge-Mu zYzZvjZeISFzABTSJz<}95qAW9co(`)6#;orulXrv?)Ua;hmMq$ouQxn;SUZV0)E2x zG;>=?iqJe(r$eNsaT7W4wlaIrdRzTqR0ETt zIPIVPT;c_sWz!i!4%b&^-2#CG$}a3NwvAqFr7!Su=y|AkT#T0Ph-S|v?k0Wk!V35I zy)_@7cS`9{@NDF$+Za=xCEO>WS-ei_5rC-}H?k~73;zlxUL`H*MLNtJAc55m(> z7SG3nl7&fS;k+tp(RF8H@P7SxI+a%@mzr%H$)Fz`GD> z`k)>k?1YVtkjw?ruNSm(6pA0O@o6k5OZcn#$FB+Ffclp8b{13-zKe)e3MKKeTiwD) z7fZQ5dgM2F`UT+yx%}IeyYChoQ3Tqv)89Sg%Q2j$?eCd8HvOTP zj*Z|l)cZvWExsB3s8$C4iGuq*@<8G{=A{7VWUdU?c;dLD>iC$+O4qfE6x@Udqgbm$ zw;orTx`W;(x35P2bxvwHJsvIFPeYW|=?jX5m4x5zpuE{{S(4OS^vs)~1}R+Z<-A<2 zh68_0fhE@3%Itxd6TZ7aTru8(%f~=+CczqyE~8OF_)zqT(7zoOOAG=;pah3A8%qD( ze=G4JCmdEg@sn5oaH#=FF(`U-2rcgS97cxy$Y-SdDj%;P3ox`*5bgK{2Pyy;Y39P1 zpeoXWo-^|;VGrkp}vWX6~i=<_PVXzZMgtzGo%ijf2IQ z$34ZkjRDl)x?YsdtB14;CvoSu20(^gE49e3;I)X#b$DPH=7@XA+;(CyNfm-k2lytI z?{8ybqLoAI-5Kd0@th1~1Pn;bi&5iB2&jPb&$LG5!A^#ovgwbk-tH*wQ`qufyov}| z%ya2>#dpK`Z=b49XHcsc>-9w`vKZ_NE)p=R$(^gvZ2mp=N7pO!P|_-spJ|D5~V0(IxRFZin&?K6WHN7N;s=64i54t64s2fWKZ?A2K?m=TaiDv9?w` zuxRj7&JVab7Oeft$1SN1g-%$Vk;$9{AXXex~Lalf(f&5OvXm&|Hwi^fmWft4$ShfJws z>ND6+X3;(;roPjVYRuAgN>2LuiWz`IF+hVXcb+}vS{?Ff`I5&{|G{R*|MVD+LQo($ zPM?7Cvwr_*_m%~w-+JNG1@me;yainfezRMab09i$-T~75s3c=}$pE)iKMJKEw7_mJ z^TLblX7lnDgNmLgxl%tbFlYiTsI>3hZ9*ym^yAxI}VLY5_5OjZ~Z;CeL$3YI{(3bN=nd2;@@(Lx`n=UrFv;|QpC zEPpf8oaOCOD&kyctv#@|_UMvPnB-mct%3KRvO?~A#+XcrvNiPPK`tcEQ7I6x^ND}p zqVwhYCPBb2VQXdBU3k$Q7v9H((c*`gsE0dl0vVZXJ<&TyZqmHZ?+|&!ls&r4qtk@@ zpJGq6(MUw<0#L?tO+v8+k-S3tFis-DBta+YmVfk>XWdoaV=156Gs4{ zp)!ZV$w=(MkQDShP0yQ3 zl8Dnk^e;!FK^UkHhx_<7SKXi>;8r;1A{&b~Y8O#`FN@}gfb(ZUL-M)(Umq*+rt0Ap zwaE_-+agh5VS4Od{uL7KYGvH|=|tVIJP&_GOY(5_A6~dZ^QoOnDp}dDQxW7L2Wv*`= zxkp5OFH}=q7x@LQDuZW4Fu4V}CTWLBy?vlu1u3ec4o=k>PrO^jkUpj+NXg5WAsTAt zPjexnbyL~$ZM;cA+FkLebszqt%UQy*U<)1Rli47LNCPEhlgIKeTjfdH`92EN7yfHn z_f+?kJUps8VO;++Csq40`Stve-g6S2#7OC&s2krp_AIrA5s>Q_RpJx?hULOHqkr#=3lu@rYI{ozJ|L`Re)_A0u%cIzCk8{iIK`^SDFqLHzBKk4|l zNc;h}{HWU@gTD<*3y}`F)lHE-`B{OA5)!|vq8D(@tz1d;PqI_mftG|yUp4K2ocvhn zT~(qE{FN71YUfScn%RTw^&-qLJZSAZvgYLsOxaBQ!W_hl7=P)^hdGr$R8t%8JXkN2 zpa@3?)CjZc@d_}6PT52ldGnQDk7|;+G_2qzG8IHKu4a7&zn29+1i=#c>>rcW(EpKo zG2+8}rEfa`-$z*GBd4Xxr)YLAOr~8q-&Oick*m*Oja6Y!mHT{BpM3TcQk_~sCG=~th*|Qw)ytMDvmT3|| z8HaU(Nol(4B?r1^S#%UQu*VLuD6WadbKMPoXmQca0amd?@T1TK{Ng5BRLG#q6p$$` zllG?gW9F8TtC>Gsyk~GW>N4d|1J3{hAyo@DS}J7 zJ-4lmU*4_3=)x4WzFBnBRqU;Zg&uU)!pi zLstn<|IFu0shK?g+$ud}%5v1P-hzK%Ylj`W>jtcIT;*sy)IJ&(bCX@89fY$?S?@_SAwAiT;69Z*Bjfr}C)6m3VkYx_bI=5#N570I zTLLamX8NynF=l*9ZKu;2WSoX{yqL=D_J*-|sA_oXq?z<=+`*eHe`L6RYLhRBRiry^7GIJ_#1DE$XJGzCe%>XUHavT76R&hix z%i{GsiSMyt;(v~Fn)4HYW~tFLwt^W3jkMdcCS4LgKp{lU$~9?>cW$UzF++&P8{mo2 zw!9vQ_x?{^Y!mD}i#S2%FEwJlK&EwfLVo3ZlVCb8YvlMx3C{ z)9%oT%=yO-{O86trbi6H#|jH4{W;-HiYCeJdAv{3B&fB&8CNT?^}{&KCI+WM-v0ze zkJsTCJ~W+lqW1mXu>&JbD(i3<;OHh^=yonkt);9v@Cf;M8&0yFus~z5?O=<7bkGb2 zz;(3ntjeaMGV3^FHX;9rQINhcN_Y7Nf^YcA$fL_wu$s@b9JnbK#oG=NrJp^x&y?mr z+zHp{BR$rL{%+Pd`Lbj6VYS-*ju$^y)6OkxFg+Q2Ac(O}G@?qVcIgTcgQ!-N zI1wdq98r?`vs9>6tWMEWu?M3CvkvWRo5pGaY0Qy^(jQ!8T|toJXTtmo#Dm@WyzxT|b!;cvnB}+i=|yy`BWlJURq@ z0{ZAsZ)MZp=A<#Q((`@Xp*yyb0kty9Wh z?4IQZI=jT#qh0U9xa298Ca1dzH+v5hlUINA^FRo~uzN-2cOrKH7i{{&ZGimQzYWn~ zk`<~Wfc!?;Q1@nWgB?fK+m-5S+}g{gGwa;DVjSw(X3y#`TuKGNrgtHMY*3%?W;n6a?TJ<=4~r&7F2O?tjMHwnKxG^|>)M4S0S+e<$OCeB;< z$X0xwe9Tu=w?pH6UB0^CQ;p*&!6Y3V2+@4_=@SL`(u0CIE9+P3W+bYZz`IuLVrfFs z0VaIk%olb0zQ-!2Q9s7eP}gS#eME83u@b@aO&Yhz5KHVz4W)*Qw9PReZllh*G)%cR zYI9Ud7;R;uZr?He@|>I|lO@a%(PAg&r+eD$a8Fnnuh;P~o~Urx zN;$I?2LC`3j*W-pzyWEvts7bCo-FNR@>g@xwBtj|eSwzvXQflLh~GXY!QMRA=td~i z?c-{KG$fl^t@H`bI5dq~+BM{{(4}*M6^!yix5+EJ+Gv`8jxtT78*p%cRpz(67FW2N zrkH3JROo6tIIIFZpBkMy>msdH+43KRG%%RMFiw|5LvuZ$*#0*(a_3I|&y2FJgxg;I zL$1>UFX}o@GrEn&tt~CeOMknpbL^=t8;5Xb-NKaCDuERimy7&h zoLDJ!+aUjaJ-lt0Ix&inuu<|CO4L)DYy+gsI<*!cI`kwLDC~n%F zJ&()B)8q4?LBSqnXv@NYxvkxxVDDI>Nb&B`*H1f!^`q1b??{o)Q&&mc16Z(rl^U0G z-+qUqT3zkOg_B9mBYJP;z7>dLxv%7jt+64`6()6%u)I+7y~5~V4!h7+PmzH(D@VUK z#(EBFV@-J~uGS5uduplla&Li^dtj~lJQHc%&pOo<%r%O_1WNmxGOcx09c)W}=ceL0 zf=oVc3QG?C-qPD-6|%9gb4(L^dqBLOVQlpvjq$>VE^C8D$KMw)q;T5jVVvEkWBgrG z7;UCjW_h7o2v0TCXJsymtU||%_~Jy<&+|<**(f&?T_hB@xfItj;@5jv5$N~n5bd&` zc$V256f13ybaxQvSD<7<4C|)l7RTQMrdk`4lP|a|tZaiqCyl=3bV3FD{G}-*7ET}D zKDfu1o;C&jSztaQ$v-@ZNj$zcuRwjQ%Zuo|Y7y%ENhbc}S$*WtbDsiB6$vXbXF2J8 zX(kzx2JqT{6Fv}3E!d#pUL}#$9L=8%ImM6LSHiTgKBOZScbFEol|U%f*{7=5xbIP` zm%WSYU;drxw?`3kux)nxEiDzHNqo<6TL1HaX=vn_&PTE)-0Y$WI5}X`Si}CcP5EF3^EWbF8PjhUnHYqm zfjDY*?rNj`ZJ!m797L{$qv0gfo-Hpk@(YNnRAtF4DOM={up>7Lw7!SnOX}L+_CQV4 zH-$3S?AikNWkSo{A~9M$j&U61yrDIr2VuqO97YalBI-n=Zo#{QGw&>Xe@Ut!qa%L> z;v7;~oQf$osl=Z$fYnast!GCoH@?r(M8edA`tl+-~2l43eL17L8>mU3lC~Fr+ z+do?pQ-6=UFGuUjcrh*v4bPwXQLu$;3|U1}FFkP|yH^Zm*1>35cm?|DaVao^pCC=%dI zLA*DIi|UfwIdl`L^VtvKa5A}1?)pC!6-BcJL0I_{>*FQxkPS0u6BC$}Nr;CdM4j6( zXR;Ys&F^J7*o}ef22N(*b$kCLUyo|e>UbkV6mK!hMYHTRu>xe>Wxui<6pXKltYK5( z+7J-Xc>OrmY{2FtYQlpfS7dm8u^!~# z26ZI#5=Tdhk36=>HP*|&w0^ruzxtjFf2&NuX^8rD+dhGA?#pHrpXksNF0T$#4#f&S@DJfeK z(hWwq1;sqsaxdw1uRN%m$JQ-0ccc;*0y204jD!sjt91#^+mky4Rj!-UJdyUJLDvYe zqUpWWQI#5CrFcikX8ojcjf9i|)#+2u(+Hmh6aW^i(O(9(t%jxKj{8^E3>T zYfF``dyw;X?5zu|x2*QvEtLc$X z;EXEEXPwZ$bK%M-$#{Xontgg)IMIxW>$}pd8#rNix2g>sM51E+3(ol-g#JGan&<{* zcEs@$(dp)5a~=e0e%F=ehi>-5qae{YFC2=gzSgie`veUCCcku~9fw4Omw ze;b#=!GVTTIJHb76Z*>;qhFs>-7)K{%(C*V)*8;BKT-yQs)yaAriZ>r8#!!;kEWA^ z`x$ZG& zB+F>3j4`Y0X8SL_k#*AafAWK;_@{*kB)QJa#&szYmZ#QuO4yN>V22D7&Ez6#%jzxQ z?OjvuccsU=vI?P?6I}omvU;n~TAg14{C_dU+;YAY+^c%T%EQ%V5mN<2Nudmj=(vFx zcaD?pPtc+c1YKM;Yb#V;2OV|H%;Kgnw?0`EDTDdS;w|0ZGM>ACxbIQpU2+q$X#_D+ z;IHo<7>WHX;nK^}FGQDlxdPjJKe3j7IbZyU8B!316=oCalH9mhSy1e)OwSLp{CjXv z#$vQ?@5>o8S8N+^G*l?c5N_TG;CG+3MdxIvTuojx>5^L&-2}W}+w)vEMI6u7L&Tx( zk+%L7xSMphONx0qmLbW`JA~=Ys0i;J#M-rh3?SBQY}Ayp*ImEhUTCgU3X|u zUi(v@WtLY>2JeMr4UTAtiKMVV_o1GPF*WLxjKkwj2s*$w1Lg+IA1 z74^*`xx0sVi(+jm?P3bN$3x3QZKjgZM$|du_U_5qa7mzP0ohBZEN{4JQrn|RH%FHk z0N9WUepL%twz3G{fdwahuv4JB8iX@MYUiIk01Dr(7`Eq|5$##6y1Q`hYgUpY2c>Va z2ZP5*Em<8G;KKE3dUgbd;2ZxB01QF%zS)A|i;1iD3a}1mEX~BB+FK`B4l& zz?6~-4V+tENY3k!1OoB^ zM(BjS0ouI>N;q@5ifRKW`WZ#15%us^E<}z(>YU90q zdlL>oi>%gxHnd_6Jf-WWU0{=btB$|#eNw}f2Z7N zHsj$F?;k_a&!HR?Rb>cuk>;6JDW;+*!mI1kSZ&p;Qt$+IkwbI9%p$-HM-WFXY~j_l zX?*6`Yd19R`wx{Qu_fmi$(G|P7w3QUV6PU4lryxe(cW`l$2Jy4m-SxviL|SiBCESX zDB`N2X4J;D)@n@kz!lXh$(=o8vT4#vSVbdhTh%#;Yc>=2XGaSpp)fZsxj5(7VL*?c zZEpkcWETWoSq5%ZWZ4y$|owjNf zBp{+#f1KoC?Vy-U8fMInpT-NubEj6(38S7zBR27`$GPd05mBbBri@TuytV)n+0Bkn&~ zLco0Zo`;ca#Ucw9rrnvI=U#fND}yz0=~lMOw5_TLnuH`m(Mh?C?1C8DZPi~@A0RRv z7m@m+dyJ0dD^BIHQ@N@1d865V+#C2Dhwms!@>KQwn8j>w$dj!qQT@6a)~$SfLMH^C z{Qd&wmRqj9y?X>c=dv=^%Q~F_3?Zwo$b^s)nodg|d57BGls;HkQ6qevC(F#&ZQ7gF z#lTYp;coRWIi%4xV?Y-;<5oumaMKFPuB;Vs3Ai`ww7|KlOoy&cXNisl^8F#=3UjTG zv7T|O3YEo?JVozgws{PQBB0fgz)S*B2{3sg+RGWocz&QqHxH`~6a+S!trVQyu2SjC z;HTd^iao_qkbo(vCaY(qP|WURwbkg5!0C-ExUeyYR@Av6fVSGM;`HhiPOVL0BW!G| zPoFJ2h9#M9zB$-7dPe<9ft*?M56Tv3G5#&W3*gwokIekIc<`(yGpj>+Js0^ zf-(!BF)k@AEUJpgBKwG}MI+?i(*GuTHYaJG6CQ6v;+Qk=Pb zqX2DYZ5>a(b{3!Q0sdV|1Jz2$z9+KCOehADL~n(hGwuL1*|$$JkVYadC^gNW$H%hd!iLiU9bYr$Vl0~H$AYjsVNk4yKch<0M)m|i_ zyJpD0xocu@OWSo?qp3x2xmCqlr`8jonF=~w2cW@=d*|8|3l^wvMZKv`jvneme=#rs z&EuW|oHn?K=`FM$xm2CUdZ(@$*KZEc2Fd~6cXS9o=a#)Gx(h+^XQ^izQiOBWtBBNi z-9nrj18BzHo!rVFxK={cze+y!XMtqrWT>pcOQDN@4q0b184*7m_-b3IFsHE$Cc>d)ezzJC%6 zPp;!qZO%H#w?exP10yp;omU&nm}@R$Fk8kzwhtvQkAVBgc)slC7lmk1h-$lm)v%7W zb`7B>gVkYcy7By-`wo=w?1?qxkikfH-84Jrbm?V?1@C_uaX(3phpq zJc^I#gjtQLdoJKGDsFPG0w53IlDZ@UxpQmR=Ib$U0&I+!3C_yp zFm_t}0>U+~#|}+gH_GVFmB3o3hS~Zuihd5``2h^)O2~Pc_%NHfqT+;VWt z)aRw`JE^8*rHB&xbD2#e@IUdRL-_nR7Eo^kML&m)PTf7_x65M{g+(oF;gyw(fZBvH ztFywhUwIY(%ZDGt!Lb3$-um&}8N$VlYgT0-T>Vw|wA9|fsdZ&q=5x3=x?lJw0(E?li!dqMX>`C{h#0sM6#XoAd-DQave1>vZ=)& z1Tf!R#e8G6D|%+;>x*V#it{0qld%+=tje># zjE@I)kKQ0Sive1sl2+||NLJuGPM0EFs?On3bA|TGl|CJ`Xy!oA==^yH!k=0KrAx-Lv@tOet@(|Zip>M4wOIyOusOK^VO?~ z)!W6mM8HCO4a-X#7|#u0Bv(eCUqIFi3_ue^Xp0byu!W7VfsIZbtL>W8`bqn;o;zme z9(6k|p*jO7t~96;e2Qreeuyj@GN66y_s`PDXJZal=r7O?go$_^S)3ypd!nOp4Q zt;FZpU2AM$#Z>YiF+$e!aM$?e-2rsuy(8o?f2bk|gTo%Oz7K@JuA;PE!-&sf&MRi? zM56mcF%w+ZcH_RT7LTCLJqzntU$5ir22k{}=+E|{Ln9;s$U<`s%WZ9M74!u;WO>E_w1^3Gq7Zds zZ?ZU|B|2Rdh16q{#!VE?ZQsNaT}$G1hPtA=UAAihw8)}SVsJNmK%z5QVq@!e3Q>hY zn!?2Du%Nr3SoCqOo>`k?neQjC-f3XH(*S4|#UO*hObJClkG>#}JkKETJVP4Hz|lbm zYwbFwYfD&cuR9$n;tp4FhnKDNECpw`89@8rf8t&EjW2!=)lMT$vS(J7Zjw@f009L`QPQOXoBkSSrYxfUa#RUN*B%hdegS5RO0|2=9U@U7sSJcjo_Qo*;M zTZ;i2VG~LQn>_zIlV)0W6eK#|b*xJZFfOdhL^?C9>TxgdSc@w1PQ;4@zrIK6fyZXKrQ z7cR%G)BiN_m|1TX$9qHvFxdx>MansnQa?Dyhvq~4_)KlhPA{PmN|4nQMev3n-w z+2-PQ^(?~lTssD6r0IuZgt^gfVx!Yc_$=88L*@ySbzItGyop~i0Qc`6c}HQCcT~<< zQbq^z_^qFP4@%j5oKYl%(_%vAo{)96?;zzf;MTB#R2-c0f0`^3|I{H6Qk| zuQaBv7nwYnWXmC86$eBRcxc}^I??t&UjsCq^CP;Fl;c($A>ujOR~qY)TVc0Ev6EI+ z$}U9^BECih!vD|SoAuh3WoJU)n9XW-bGqBbjmSu6Mz*eWI!#vEWf#US+f}wPq68#H zfI;wtkU%slnmMzylBF2^WGAkH*F>Y*coYvvEdu?`ikmtDAEU9>$n! zt-a5=O*9!1cE*;DBJ$>qb=F>M&N097jcazq7sPlw@7?X_)Db zZjV`!dy6xz#-h@T7ntVX;!;J2YVjnz>PQ&IjvE zzh~mv9+@}UOhV**t!3fyFMa*_1DGQx3^C!N>G8eG9scOu8~our*Z70CuW+#$Km<7B z@cGrF+3$_BcoO%-*;xk|k?@OOdHtnIz(4zq6+ZK*9Cd{m^zSu#%=gM{v{>MPJ`D&Q zWo@RYUp{?hwm;>%Vf?;46SwcjuxbCKXC_A9 znC-VXd;bBm>(8FYIF3oh^nD5lm~^!9P`7%p|MPgLyFS19>-R>$D@hjL@h+w@eCat& zOY^;JaJvC2?v0ts_xP+haJHu@j@Z{PPit@<^Y~z$t?`*hh2BqY3eeR$8wnT3_dM;@ zfGE#-)I;CZ%$4?oaHOlP#Qj(wUxm$=svHwJ=P7wXquyN7@8um8kz?`;myZh!xI z$5tFg%+K5e2LLV3zh4$+?>$=yI4|@0YhS&O|KyjxrRK+c@C>JQ!SnAh18f;wq@9<}r8+`Yof$6*m?Fo3LKEu+Mv-?W3BrEQ9ZbnY{ z+`TnEd++Gw81@5z#=_Yg#3tUGFFc>QoBxc*hLGDcKKJJ#CVcy^ei`>x^~=3SWAI=9 zE8oCpPflj>b6Uf>DI)*wc#!bse?D(j{;cjdqdtZCcTDSIS|^Hy zf4*j(^_c@Sp1@U%$E*$>uxBoi+ouB#Ito!d^`0)Xl zm>-C@*o<2(!PDP!@9~K;U9 zpS77in}e)t69THoCwS%b-v09#+s1DL%6X>rvu`}cfAwoW17}|RpmM-EWV+H`t3Tgr z2Y(Sj+hZ2^cIRh$0SG$$eCrDj@pu2#pT|*E-O)vQ`%t(GD5V4N{jC4}q5(M5GrPT3 z<3T3>Nnp+n0ouJ2cgG%^>}1kG$K&09HywbLbo&Ph&JzBMU;Sx(>CuZD0r;RrRJzL& z9)+WkUC01!+Pibc0DD8y^Pm6XS3dimp3@wljf}<~AmF>1id%Lnd~5B;gFXNAUwrL_ zjNd-E8oDz{h_`^*{yTh8-97KOby?2V^uhPpi(3aBq<6-5K8UMn zOhBLa$pCGBhTI)jbk6X5-n+Ng@AJCT4*^=FyWanN*!$gaU=Gmo$h+H2UOM>Rw{4)} zfd$m<_m%MTU;NB_UT2?zv%h4YfBC1rfWQA+zly){rO$%Ex0!``IU@Vi@ByW~2M#7C zq;M90`x{@x-~X*&d#QVgiSguO!2k7sZ}9!MhFkyt=avuQ$WB!9yj+;(K?rzsc7i^p znT^ML6TgXGCGKson9D-B4O~mRfVe_`>5W(ME8qAkj_XQW<$1KAINW@Z6OO6^|Isi0 zbNJ8x=1)>zjb&dfn} z97}Y$#2b-bs)_jX_mA=S{_S7Eum1SwL5F~B=C2kHs|{(tJU)L;Qzyr02mT;V@hxU}Px6C#B0Bw! zH1ocbQYL+%b@mtiL(HGB2>c28TsAHKw?Fz0{?C8%J$(1#`u!na0>1X@1N__@ui@vv z@ETTC`OLrjzx>X{`0t5<5MKORe)s90;E%7r|J=WGc5;kg{`yz&)q87v_Nc~D>EI1L zcS?YWM)yDe;Gg~PFZG?j{FSfbUR~hFj%$4Jbct8j75?m1~bAfkmwrGa{ zXAJHwYrOH`1V8h+SMjrNyasQ7G;)O}n;m}p5B?Ot^Z$Jp*B`)G@v~<~`1-34@U721 z#@AnaFzSqcR6e_Y!0&$dZT#U6-o+pM`MY@Y>Vr9mJ7e%uuRp?X{KOmhsn;L9^naK4 zF0+8em$74dnZ5gSZ@h-T^S6HHeZSWsM*P2TKE>~S?`{14_us*H&aXa@JIbo`__;S; z!@vH`uj0Xa@fT*DynD07Z~x&x#XtNo_SZsJ0r<{`~kC-@rFte}u1`E%Dl_ z!n*WuFUUeZ2sr!Z*T06>j~4ji=>nfUs&QHsC|vHcs`cduO z|NL+M41W4^k5O3jo`FC%|BO1GDJ4cT4EWB|EBwI3%o^Bi5v^}~I(W~2i z=`2d`ur3QcK3Uu*@p@JDYw#dn`x;qB`!u9^3(ZZnm0<2+k0!AzTGU zyX{argTffp&cfRSXFhmfYeI(~?4CaN?|AR=;CPMN8I;z-8FMgb_{0C4)&(9dOPtjO zN@oxfqwgYucC#^K)I%6ZDbX83VGRmvB=`d_^?asxl^YN8`{dDE4Ujo`#^`%_&b4n} zZ|3JV>-Nrvw=)LTy!c-C4~c{>CJZT|4GD|7#y39q8T|O`uVRSuckA)*TjeN5;hqpE9s`PCAif0SgM>!KdlfzFeZ@lEnvypg?m z(F`b@L8*Jy8Cf6iIZ2uAhj|D1+)aLRM493hL2QMr##jMKbSLOYI##ZavftVvViy96 zqQsBC_6olA_>pSHu;)f0`;1@>;EjRPJnY7EWCv?_&^KochCRynYIb} zbMM|6mW9W%uvmBlXX(Jd?8E=uuL_)$4wekIeZpl5XhK34 zONUkA6vRIN`6NA@w|&H>4`}tk&H+9jWf?>*y}`QhsJtCB!NXuRM8Ed|)GF z^k7(ei*?~pISVp(XYxGWryLS(jM(-O+dg0$A~t6S@pS5~B~&6*oV`=3RcI>%tPjT7!ZN3Ma|a!dt8gS%>SwVd))8 zXW`7V58xlh-hJ)<8Q$7-D6G}4WEw&Gi&&bJ7+r|C>IPi2Juce;yFQ|e@{EQ!SpdyD zAoJNtc@5|RSr|g)Eox`6)aSC&xU%$44p8%QY}On!H6fta*bqkT5s!%pO{vxh7E3E@ zedRq0XJHSR;xYLM37a9{rVF@gJ6yE^n|?quM06=3#Dti{0wvpw8Fd5B;Ln+yFj{>E z4yCS>+FR7lq1IyVB z*s70S;u+}S*|qdtEenUznp@Y3iP37T+YSLk$eo&#?OJ~3m3MMq-pMnkDF*jL!d2Jf z>8`^?(__;IG($iaqdeoRHH&h=q{(KECRP(8?+i*~QR*IC6i)YIAD;iAM@?Xe>FOaRLO0y8#!z`MI1Z*4nV_d$K! zfVt(mHwNp%;=!Vnj8_59UO?CNup8uhZ*KDSZfClV2&=;4;j$c=1v>+S`G62lCTPYk z0rjdIaM=c2_W_$OU>Bk?8v6s}g)x|}mnSk9kODM<)6Bkf5{Qp0j|X*uS5}oWdTBNU zd%CYKCOmC=yt!#{(GFVbqdh=t4WYIM4;BSpUDqg_fo1;KN(=e@Z2N$x&44Gn4wu~^ z&rr*ieFFLz5R#NvqMC`0l7TZoVFiG!N{8dp<7`plUgdF8dp$7auzEjF z;l1wNG8d*9p2q`*bV|@LrfU-d8EuKe2Z&Bi;E^u6VEZlT(K5LyM7EreB1FpM(i?+vgH=rFPQ3xp^ zrKyrJUy}qGroJvb9xO{dS^>udNsjga4A6&&%eKcm+Yaw+T0CufTs1v*A?)q9bgQN( zd(Dz=FUDK|03ZNKL_t)Uq!uK=(_7O0W);LctV@TpMS=Tug_GK&hLh^VY*0%~j9mzL zy6f=Prp0x8JFeupzQ>ir!)1j>s~X2ufs%NPfB9Nnh`89bcze_0s*|;!c!IKIBt{*T z9uHR)9i%(w#VBy4K6hnqyt}PZ!OlP#lwZ(S6!fV7Dn0qzyds< zY&-nmW{Znvz^)I<%wsI8?>TPkcQPY0C(M|$7KO&N+F2}1kK?kyab4i3kn-oUbf}#B zkpO8w65xzyrHx_C+9XJ9`iScxVAChuXci%CnPlN1XLGZlk@64@zH2ZLkD3TWgeEY$ zVBsj>tnvtBAWX88p7Z%1zw?jwvYmlne(>w*$r+By605>t>8w!P@AF`JFP|%L5jAnjmKJ%BlD?yK$3C6Tpd?J1NpKWUb^#050!)^^Q+9*wnM~qH{j`Rz|AmV2&~?N)Av72QnDpH z@odz`kQB5H0@AF-lhWd8GvMK(#KTpI2g?%casse--BVQN^XX2KgSR&wuG=0vP29q) z?3Z{pjX?LFa~8*iM<3%}IiDT4a@b;#`bvI}!cLsut z!Mbp023fBH3=0Jz*aNAFf!&bs&Zfh4*K3@bMmfn^ao7z4kc=T5(+un}#_yC8V>d)R z*|xas2J~S9>jb&hs&Lq>B7z2^ld3@Jt(N-@3M>B}6VUX+#&sc06KiH84h^PJT5?V@ zDCp=uN+WA)4WY8am>tyxPRatOb%A@!3a3?pRV9qJH}s(~+Mj~6j~J9C9ld{ zy%{2|x`^vO;JTCY(sqbwW2XFhbP&(G#}KAbFQ9R*_^3-_%rrbYP*^u;X-P^Y-lB3w z7t@QGvp$9Q^}UGBlm#47ZwCA(?#J zlV}&12b9)gT?VX6hax|#gZmp2V>3iNX?nc7>+y6uU=yO~weoC865)5@qzqVj$;#|3 z(2WNf2yKkG>H?nZ2Anqm*L~Eo=5#&Lq8K=1QP`~Mps4_(C6#7KxatC)Ha*_i_IR@E zaoK6mifJ?t&f~YT$l6iXi0Gu2*}95M)1-iy5J9swnot;4eHG0P-F@&F6ANhW88=7jR!pgfP6T6mvHao9ztT=bOG;fdn}xY)TOlP zp08PD7QLWqq6}FT-A@T)0nrf&0LW!?8o_x^fP&i53@ITb(M^RI(Hp5M?0PBFw_08U zfOYBj7@zs`4+`|^iddsQ&w`Xx-c89HhR%%lDSJ#lFgP6g%$0h zG=3Qv)#4cfD(A4SJXVE+Cqhhwt;Ux;L*5!f7Z_w3cq@$w&KRu7iKKu4X9%TrSeG70 zrN>j(l;8xj(9!iy$xL{dSKQB^=`Esm=KC-s2KX24Y&M!|m6?`|!j z=>?dLtSS*sNPt^mEsiRW2g?fWpk?zU`!ys1t=5Q!&#FdoVy$T$`IJ&_U`ZqUz0r!+ zbrnVYJ=LX@5TjXP+RZEG9-DMJu)$BCflL%Rb<$m4kRY zMC^trY}zbqBQ~Sv$&FecR#)XYOM*$;!Zc;@Ipbpz|!U5x_{5p z+_MkDkvVzTH%LK|2>8WYku zW^O85gv4kA3sCN*oV@axMw)3Qv{ZQAMO-uymo1}9gv17OJ01d|u%Zh)t2~ZNhuVqu zV!nq{O6UXQrW1zix(jFngA;+-?y*+R7}QQQH%FyM<*evbG`k-(3%zK1yt5tf*0#r! zX25kfU^hf9TS|~C#*(B_@Jv%CsO1UiaK)G`hyY&L$x%C$7?yzAJ5<)9(nI1 z`p8ckQU2A!Smj&Yn+R3_? z3n(}S`XqMA)(?-1G z`|bu=E=(oHu@a#mlC-lu>%T@ug)CwM zc0)unL<}XPP(YeS<}Bc%8F14@bb~Y=Xqq8FPXR;e3>K;{6rHA&Z_SJa7ZJV+-&0a) zMvnQijy7GyWiw!1I#h*&wFZ)}b?wY5?PEWH3oLnu9P%mrRa zX=E;=0xOOQiGUaxT}rra2E0j)mLlcnxGLbS!4e)1S9wk+!H~sO+oKN=&Pt3cR3AGE z{DD1zf7G+HixE4mwn%w2q0wqK&xhlqpk$-=%~Yr!>uzMPUH%EGk1NoRo|TBNVz*z?K6+RKfAU0xTL` zKz$VRy@fZpUweFJ9ex#Z~xjE{-+NZQ;6r?FEPs56AMH@II*=jh5ixC8buvu0h4xb7L(9b-4hfM_BE zN4HC)$ECx)y1=@03S1;>AN0^UZw9=%8SsORFlL$JkHXu-lN^w%!r`ct>m8N)o}7U< z+DWJu1I*GS5E2lyS)~h7mTy!O853hwI2>0Jq}&}WDT6RX*%Mum1IHKx#{@PxDUsk- zT8o8hYsyih_pxHL@r8-b&!V!7!irIAVF;z8QG+L)znO_7HL#Q{o)aOkfZH4&Zn}VG zi0C7Ojc8FG^)|rRy^%1BE3NPP^fZ85<6!B#6<6gzX?e=iRN8M>icIB5TzM zyCK0)K;Z__33-RwIao@2BJ?R`nsv^yhy_Rs`-b*y8%k%e@D2+%YC(X zI9rwIlf>7^!1->#F2t!DpQU^Gq6zS(MPUTki;c|GwO)CPdv&R%-MJ}bz)e4>UQG5* zudKB*J)E&{I#+XD`SJTbT-P8&D2>7S&SOXkg)>-c<5XQZO$dy#wlu!)s168ijM#P& z*X@AIMx+Hd-5||AfpMJ%O)!PMZH90bs^jTX8d;y4UbI#* z0ZV7_XzB3Es=#SA0kgYeYR=I9#a}wX|N0Lv-`6u(FOTrW&wo+Bi*)ZTl#L0CQE0q z@)l=Rf#XV^t2A{mLX>jhMKj>7O^-KkdOU3gY&%Auq~Fr2My$35N0r6>+Tr1{z;P+1 zX8~qr%u4e?GLhqNhk6`S;*35;33b!?y=PJy`@`FN+ws0G=V&62Hdm( zgEkUm7MMs_v`rUq)ye)o3Q>Ol0iXCm0NScv;R~PnoV;_;UP)~i5-ysEg&(9mZ?$Yj zcUzngNIR4r9xNG^Hwcm8ETM9C0HBMScX7rJ8 z-2_||0r%>liHn0_%>2M|$1$O_R;_(32GV8}t(+@|hSEt4zP~JRZ!sl97Q&=03K8Uc zVr2!!@hC$*lC&s-Muqr$}{pDcL{=$PZ9>-2d=CadF_h}Px(FR;< znXCys>*gcXA2Diguyh8i!r-`+fUxogwIe++Ni$3AA#%#unrbNqJuq*2v3H1IES<$^ zWpG?NRNko1*xlmYkR-dk86v*)Qx*Qn@3rqqWA^I9*YNn!V|XKM$5NTI+S{?~=y||4 zDUiDA0^Z#PTr?4zp7G?k!{@Hbm%f)*m!*{ZG`@9_;f$0DZ@PrW$|xwBw4$PIfG{?N zHCj5#8!YRCCf4k{3ApMKx+qcC5g`$eT9U#*fK=^qR=bHcyF+W%1<^EIcM-duk&>1w z&2;!!FiIn0h|GQ;t0I{Li=G$O()XOHms!7HK%BGovhN&lG=unQ6AyFnNT zuZL_Qx}U>?h)6(oVQE=)O>Weh(H@)=P>}#8NX1yznYR{&V^lT@b4L-8FdJV+ z%2+XWeZpoC)@qS>e~$3X3S;RdE>+edM1oPf6VR-Sn+z7tZ-))!lQI%vX#je z3^VU7gUOn~8G^MIE&?$>6z!(>L@2#<{Vlxswt2g+A07XpY^^a8hrE$y69KPI5=~{D zoF=4Q5dGI!=FQqR3mn%Dk5h?0CIuCQM(s5w8w&wOPqsZOXVuCjdo>ymF$yc4t8pcz zerYTsTr~p*l|jhjA+$rn#V(+^Eob+DS4+c5&B5j_Ka=-Vw?=T`@pzC zMB*M(KRba|VgcmdhjH_#;Os;EBz0WxqX47pF5$9EIBx^a+lZT9v^7H{MAe80wTlsy ztqa0QWpP|uti1$#*{n2rA>BGO4|Eg^84O-~gQaKmwam0DEupfKQ8_YqdTYfbC9!Qt zxNIWM+khucME@FqfA)-*W-Yw)SX4_Kt&j2G^dX#cA_gUc$_e|o^y1I+A`6YAl>Mep zc+w<1X(BcQ5R#M}{_J>oc?O`6(#w3(f(YKN=!1~pNXh`CpAM#sdg0vIz%XH9863Qe zjH@<|<;TdPZ(!m*-<;YRoYW5Y7X^+gkJ3%Lr19M)DSPaOgv&Nz(@BYdpjxH;o7JRr zSs0vD7AKX%LW4guBQS~1tW9{b3%G0&nx2uO(YE}_JE}Bh)GZMi^pSv`(NKJ8BTt_B zb>;FFN$KMG>2Cz(6I*SL(0w=YH z0Y*bqZ!kKF8!~$?@|pddvr4&hyU?;*8-S}e;o2n(Ap+X|Pt0HN-i^D% zpmy}d(-LR~9Hfk3W`ZIEA;r;JD5)4bHvx1ZiM>!SEHkO~!DJ3AT3oO}NQ8b6@2gbq zw~%COigIREa#ck1DWDJYj%9gAQWBNJznFPn_Q!NgSzNLX7e}C3zzl z7LdaU2#K)m61giV+5WZR=wwsR~=s*bX77RQy610kFG0^2?zP#P^Zm>Esac)AogDJlT|yIpU-{NA0_thCP&UsQVF))vLNiRi zOEO1-v%pdf2p7uyndh({Y3zPX$bQSMqIRvn~o8p77K~@ zwKj&ZE-da>`QGI4o-;|pni=#V;$}$L^wP z7ng+tgUvKDhaft;QVlVS7p$F+QnTR6U{Q)riZKb;?JRpIwII0_kY@}Rr!;o$P2H`e zrE$?pf>Z^RTmCgkOH4=+V;AEzI^y={fBp~uH9dTQ^Jc*Jt~z}0vco$&F+!EH6OWT0 z7S7_R5)d+^gsV2-ri~alfs+CB0K0AzO4m!{hb3j0Z+rJ;1`4WQV+9jKoJ|fGWZidx zM<%D&ZsK0=eNc_nc1UOkh`tlpD;4L?M=8n5@07%K-!4cja!9HamBUf%wE0FfnUujv z62>P)pdEmIVC@{nC?%jfW;_YV5KY~>EO6Z=bO9imx(~s^#D)Z1cM-sV>n@Fq)O+89 zx52-mT*h>`zw|gI0(k+-&H@W9*A6L53;tc639$;9z+I}5gm>Q%;~vhDIVL~8JA7O(_O+<$LNBQ zt{cO9>_K4($A!Veg~KZ=hx?VqqL4%U)^j652)P3@p$$NYnVF(J#hE3ba=_XPgSjf_ zU4k-$au*V=TKQ>$G!GDA6zwyhAeL}idz>yjjtV#P!I`sW0v5L+;kspPx`YtT*s@Qv zil8-wWkEP9EKVwUu6YdBkQi;$4x&!JV@d`ibTiWdti>br0k~#q9>_jO`(|c$e9YZr zS~w*w;}DKZS>GYuad_x^?jkTGGiKD*(CGJ+v)sa}8@!*oK0j`BmfuA|Qj!9Ul&J1x zvuR{)q1~rEmI-yLhk}nz6Qk=kq8%g?&(jf@c+{L_E1j&f!Am?7&gIZ3z-HSsT3xpm zBpjy#%hDSx3yb>;kE7C}PYg>I;6c3qNGDZjx8rpOJl%!8a-7^6J@Wx_0{Y0<_R@KI z(ypP1>kcT6vbomBn@E(M86#Cs3S?KVShdyN(fJ z#FaAFh7LS5?s%tn#n5p%UKK4(z}^+MZ$dOj}8T zM?k#|<47YZ6=DQMm3KH<t)=8@2)OQ90&xZ%;E@=x1}F-HMPaZkgncs48!eP;_kF_CMxJNW=W?MMQRX?4 zWUwv_?k|KjJDUNs!=|h(-X9XgQ>z!YD6(u{I}3A??K0Nh;H){|s*}N@G=ECW45R^FeQsjdr$)5_tb zwyISWQ_sXfK!41Faf7B$xHJ)k4Je$7{|f{E49v)Ci?QiMulZyvc0w{vh_$<1B^n8c zkkKbeOL%!=g?OTw(WA;}LR&cLpuE47X$rMBS}oZ3#v}c2N@uVxElz5WO(*-ibQ05+ z-oVqO6Ro_(Y3&iT6BJA!j_8r+^~%i#)o?x8ML0W*dg?{iFM{Bm_^VCQ31Q=?o1PA+cy59AyD&61J=) zoK*&=CE=u&g*sCr83|om7Og+Tp0O697_xw;LGOE#tZ)^g;Z` zEb*SNs39rm9;r5LS?D;KI}a)W_ctA5J4op}H+0Q~aE^f55snIj<4W`+Rz+lKYyx34 z129B^ot9XOxl;hi44)+$?O2`{GqPV$NQ@YP;c)PwX4RwsqI4vzh1GYm=c7qlN`Q9T z$@8~rbt0Z>5)iEh)I3v>@X2-F&UznKLHY>5q5#@1h-RwSkzs?DQ-(xpg^Lh(oz~1t<6=FQ2T_^MmU-#ZXCM)CrGo;b)WEL8}W1(an*_jF?R~GA`Qx- zcmp^F$W0OtL*hLq2G4nX7-8WF>(b$L;c!+9d-kj;3YoE8c+qcOmr?C-qB`@-GzX(H zjg$!6UJmr*KB3aN#dp~@=H#dLB15=W7w85c!~x(mT7b-3k+RuywyBV3SlUJa03ZNK zL_t)J&}U`YydUQPguv)od(U~2O-RED;c6GK^!dy&FrG&nmeLuV)E0dx5u$jCiA9;% z17#o*Bd}`|E^I{QA_^B^$bn3*j4-?{7oHjZb;8Sq+(UdYH1Kq&5=^5t@_k*PYL1D zF)9nJ3xnelIIRtiD}&Mi5h4)nBB75)XkG}5mO+5G0w$JDIsl!KzjIh#UQ8#?lc^D`AipUKqzaOu)4rT7cwr%eZP7yIw#6@ia6y1FF&zjtbzc zwm2?D`zMSzPw%{wpEd~VWL4XeTkuJBd9@dIZ(UeDAolMux43r^*z}BzmYNa+*4U|o zl=4t^Su@AAbn})zm!z_0kkAG~7bF;hjl2`u&(OUiEF7@(gv!x#NASoq6cO_-0NXBs zG-xN9MC#cSsx+dZTonQygekl|?n*qGL^DLn%p%Rgmv$o?wKIW2Oy;{q?;m%fmY|OQ zQNUFfGtiP!Q5S`g5#1DH;JMH}8;ed(=83bua{nAABM0BQux)*HR9n%rE?!z3S{#Z6 zC|&}=wOG*N1lQp1?(P-{R=h~i;L_4SacOXe7Asb?P@wYo-Fw%4f4q18`p(SRv-eti z&RJ*8{$@s$OOcA5p`{aECZ?`pth9ME>oTj1)glRDpS(^%UGu1+6Badz0gc5@pJGmz zi6mh)Lx$!KEQ!UPDNJko{lSr=N#gr+e6i~QTb-i&H3)%l?bP5v zRp3Z%r>Py$5!N@%_lVBUYxSmDmr3J5OEbYVXLJ{_wvsDAgFIi6`Xxmf9e!b2%V9Z) zUV})s^Et-wz-t?bQPEW|MLwM^_sNR z+E|d9b@htSE`DtW!l1CGJk^VrB z5GSuv$4Pwn7UG6PUd{bJrk=R#M4;8~4Oads^*M~uh$?0-$-@-0US6CZj=Sy)&aaT< zPj#@?Bit=*WGFMZG7VOKzfl9!J@;9`DrB@)`y|w&oG)aUi<_p3@_fa_N@6LEBMoP8 ziHo^1kiFK>`koB~5)HetvB>X5=!3f~CW6JeKI1G?>&P-!9ThSz6ULMlE~ku}i`6Pb ze>w^T#19t}q5{HGT%Wi+kx#*i!j4pQuC%ssj#zEQuw#hab6{B{SLmc9XKr04r+Y)O zCoL^g+7Yuu-epLK;EqN~7B=MK5n09a7sgP3jxi9Qf=<$aDPo&(E_EL!i%vdfm(q@8 zsF2tYj2IW>rzT|$xaysA1HO#nOw8~!S!wgMRYRb8vsynI6`%Z~#cD#rEcFDnYDATls~D4*8?E&L+d6 zRfGipFq!j5Gd5G+dE^F0EkcipOTAEG48{(&#@!?X8vM6D8(6LaHUd80$rzNF?x&U6 zt$soU?rA$=LZr&09$Tjvrosb?*rx5RR$^o78ubj3whB+Wc<}9oJ>0E4Owr2&1CTBEGX%O2K| zT7I;L7AV9I-i2noD8|v)=J7uf&%zGVfA(NxoC4o7L;Yb_Q474O@FJ!C*PLP^j_SeC zR7IaZM-H-W*{ec`WfR6CnB%e2lkP%cB&3uTG&oE_iZO0be!-=oDH6rI|{KXETIfA=%#|7?m9pv3jTG{SLixMzf~lmk5`MNC zGD}tbek4iikBK%aWYb#D2cVF*EdHCgD2>AAT5`(RKjsz+H4(0qbW<6PshTby`a)k7AjB{=)&IRv526=} z>ZHf7)FcfeWAxiZAwfladOyl(FDA)Ngr_=SZ9J*k@kAgMC-LIzx7E?F?H)N_B>0m( za{1ZGFb}jqN3`6_8~`1OBWo@Dr=vl!Y*C{=uOc^xK#T}}t z%Q1$I4VFhhTolUGkZ5`epvna$FtW=*p3~}NH8E(T*lqGkGs1yF#iW;J+Nhn|GC$arIAA3Sk}#f_@B+ z5aj}lNy0Cpc0bc{KbZ#14&)J;y#Kml%T8oC`$XD)`jSW7v3uSTQz?oB9V6&nE5=?* zheO{kmCg_&HZihXH)BK@GZ*VXkyoNXp!hJt^u|S5o)nZ-N=bVc{N?XgBGP*d7vs_| zZW|d!D@on@)2?;NT-IH%7VFED_HXclVA4 z533XZKvg;^g{G#%zz*rr$@fhhu=#|Uyw#9eYLPn5D2Pe(vAcJiHZ}L2)T!;1I*fXV zd@=WPj}S|0Gmtx9s+YI~np<~fGOvJfKdHtMFX*PR{}x7`qEq2+|rUb{@rzO6w zp+sj#zK>xJhRjZu#3_o1)HJP-Qkz=(j85zntGavWN#L}Tf>oA%pgtc*)ZcIc4QVDZ znT=y`==D=#4_@O@V^LN)*!f3CwB=w*%uF!EFk{epzEMuV47GgpKs(2_XUsAav)_aS zSLGseCqq^Ws|T3c9ywCrb5M$9lFIv1qYG@Q23#SeaiLHGYN%Ec>=TGbIlF8iVFk1k z9~L?~CUpQ5&lEtf5^M^xe$A5g4Sw3qcuWd@u$-h`lJY=f88Vd7#EgqDu|@g(fj>p) zmFU~x73rfUpR0Zjf9?WNhg|b}(>6(LS97>LL2K+{_HDM#_QvGe)91!iJTnrvLiZKl z z2{&^#l9shcBu+6Y!3$}=v%Y{sQ&T%*V(p9cuqHK~&_MxG%h~tbzH;LYYno9nd`j;{ zv)!SR;CoZutX2)63XHNG=P)1N?0H1Uf*`0^E{~0ry1<8F$SdO6#Ai{RW>ax2^rXov z5@7?58yA-%#iNx{ofcN=7&lVC(q^xm=U{uFQ>UG}wR$%Lj-+`f);>i*$kxgiv`Og= zf>w6BMvxOpeEzl`u7I59Ko`;oET14Pd<5R;LD>ZzI37GiNV#4J+DlpUk;$t8+o@hf zw!D-M9tLjgYmgM3Q6HqzSHfebH&Aiqi=v_Td@3lvg;A+I=2XJ{ zby6cQxX_2CDvmF+pLFJji(<^&(NEB6cDgIlDsa&~vGfN7GhM%JXNeu3Lx4n z5}I6Ju|cZ?qgPNXly%mxbO=X9(G%IavN#0UEA}=X5PJxQY)|Q=C`Sy2kd%#Ci_e$i ze2!-H59c@$KbIV}6%I`~0S=}NAJ6fGx&%H9^w7G8KoOzG)u_mrD|;G{B#2$KI& zT0pHxF3BZ-gNbRo8n{eIUKyVg&0DX{TXy61>t!BZJ(j#0U5d3Q%b0+-($W+8ZJxT_ zer#s{6McjHLDcMKiqc}1jf6nQV@w5b62#lUHJa}vfn1eT6Ci&eLHTlgYWK*;Fv(J{ zO-z9bI6Tq0+6o+FN;koT#pXQIazbgpdSWT9KI}*U9+xxWwHOg9tQO)ooZS_~jI)jC zH?1dO>54Ueg}+Qux{kx7DAhNQ&M_+BF2aQ=lAEL(=Nr1;ss#zbN+M>*kY^R^!Q*D` zLfOghCBK2=9hK#+=0vBtkM13VqBoeZE)70*I%KnN?_RCeKK@{Acaf)y`a850l0267 zH*VF#w%OGy#vTn61cafbx5mbtP5;-D-R zYQ9FNgh62fu-Z}e6gxaKXwl(F(A&}84$Fm>g#Eg=aJfCgA`o`hNu+~0hNOKe-*+buRK5o$Co&jW#55ZtLN;8 z>*^czq#DkrLzLB?mp>jn`&H-OT4MGoV2N|#0(9@Pm8D!K)geO;)vNK7N0`T|<9D%E zU9*$4UP4dDQtUJ0%4&M_-Vizm%)3n!pqU>(BN6q|2|}pN!$6qCBpBk3Y8AR)Cwi9W zQH5|85nw^%`^9J6rV7Daf&+&BZPv)!k3Hkl z#9Ve5(Gwx`RJ4MUxpgst2+?vrsUkiVd7IJ%VD5K|6{?l0XpxghULR2Q>C3q=AMXMMLF&CvC`klnqnyPKnI9_snF9s zvSpAVrMt6h)g0^Bt5xsSg51qZSbm~L%fgM-96j`RX>XMWV26J|(HO`_N!Aj(*N`%>7DXZl^z~V*hL92~>GXr^? z!qHcpX1MALXu)+kPcF{$S7M`3|M4`o-BhNkC1#Ae?d52>peUgf6x z^bh$JU;z_Pwz}*WkP~ocN4Sy3?!hZ1Im0s7=tDi)$@&I%-2T8Ts4>_F>@C&6^<_>a zdH=%7%r+a5)>>7Nw7&uJaPjf*fw;7TsiOHd9Z`Icj`Xp6TiYFC-X~5l%|f+cg1IwTxOK z(B>qx6hrp>ugI(WMuJhk+|Dab-l@6~J)DG#?W;E@F`>`o7W1folCPQ*FLo?B*Dxcr z&D?j#=0(z8Cn+nb@4p&1HVr7{d#XsTBph8LEdb)(smt|pl~F;Vz&^HNC&QcerABn+ zd~}(CDRYtm)yyiZq?XJllUU-d3C&!*MP#mSj^M2CAjgnBPicU&AZ=E_*B6>{JyRNQ z?xo6ANP6%+1cPanhHYn^6Sa*bAoG%gEAX~gHu|(NyD?HW;K(@9xnI+9VZJ68~N3HDbiCmb> z37_0c%6ABTCSRnkZpY@eM{1Q?o&MX}Y;PP%-5JLM*Yf*iUHxt9hA(gMk{vw3AlI8n zD_#o%)tpUCPfxq_@?Pj*Jc}iHGkrozdB(YY9DDwbpcaZNYc5YTOTz(Y2)Gx^JhR0j&F=vz?d)Q0@DZkrZsPRgGf z(9THooFw0X_*fS<`burGas3U7Rmb?k*H=g7G)tIeM>_xo%JM!8iEI~Otlz8E7c5vV zRpmGMRM35^96IrYN;(;Mb7d)3QYxzkp7mgCHLmU%=%!28&M3OpE`AKA6&ZLi^W zbzQF3v1fAMx=@zARK1aS$4>U)?^fdZArqN*VB}Z(g>O>yP;@>6d0K3t!vmm|DXtT-$ep(AKE5QDoc}wcmdN{K zL(sM(2)Gi!D3zqk^4uB&QVsq+6qC9{90!A&_qrxhU7KV;X~*UWg{`Y8G1Pnb5kii( z**Z%N0hi$G=^uPi=Fg5Qs*x4|>^SDsVnM6vlsD)}&$%y^#RL+yAfjI(IVb2I)mP7o z%7{Y$i17|ACFgwL0F()h9lYc~zbzP*l)=`@NDc*X8&dEY$_Cr%&7a`}7i@sEC5>Fd z-j2-9+MzLiJ}V;V{KI_!>LmW%MCDMbGW8t>;SXHb8aDbVX>7P$ny`^J)jfJAW{3U; zbD;^62bYJ7TTmas&woPto&mUrLeJ)7oO93YAA~vXaNz)IQ)g6;7erZLn6aO$q4Zt`Q!OQJ zl>oB>ZUTvFHj734^h_iesB|!^W-G&`dSi@9N1zk`s45r|JDpeWd{bwz;f8qjck*W^7r0A3BB@xAlipu5iBOli^CX zCJ5(uZwcEmFTjH3@^V$(F1+3**6lLGZas;-7J)vlRqy>ag2JE~l6- z{!rBnjq)w5cgQ=hgd-=)%N<*$$cl~YAC7vgd_H}E^#Py4MJ{gb7r4feyMARZtftf# zJXsCxv#TOe_pcxaw0P{URZ2^qTYr1Wh{vTj_#xYKY%9Yf(Y9;~CfBVsxwZD@d8h>SQMLJJ6~~X#4JfMJH(@*F#wQgW07kF}HJPh3(yapo0I|q)H&1 zjrxxdsq5)&!5j~@1SKj@7j|i$n+CJrc%rf)zqIJdZaIr61vI(UgJrtl+!SrP5_gRu z5D7umlnVh4Nk+RVJ<9%_s|Cs1lcKDNOp4CH4roFNy~MGcua1`xh#6wIuGdcYSfFqjUc=3nH;Xv-?*&xr>l|X+Bch-Xn z>+yL}O*+@rIuC@>15cV^56Mq(uEin=aO`XZy*r;ml@;DljtQdov64~YpJqON}V0ALEPDySQT5I6%2Ud#riXj;!}^WUTdKrdfe)J z@o?pv1>Ot|aXhg2H@3PCKIs}68;My9MtH$vr%x`$t=6TSdVC< z$Vz%AtJQ4WJ3^*IMw#a*N0|Mk4~5P{pK9WHPpdQuuqn62RX-bd=Umtg98<}%kA ziKNQvNHK*-V_Hnlnhs~QUWu@(CX;Thv1|tV?t%0+3m*y?5e{(yv^{2rX`|#6vz7OU zMV`_EC5=zD424{`eLYt~TqPmGJF6l>XRE(|4RBJFH4f~J2XIveR~~=m99;>ZPO_?e z>WIb@rHii-VoW>XYu!~L=(HM6S3mo!|8&vEC=jcMBjw??xys_aXKE3vNH+U%*TNI zTy@aDvo*)N)_u-uQp%53UK3{BGGyLQ8Xwe`e1;T;Yi8T=1uJD4EuK3>DEU2}3tlMt%CNX{MJ%FYfKU$-OXq9?}c~BUiR-gxD#j2|T2d4404~D>DPM z^M(Q1U;p~j=x3pZ^6Va4u+=!rhN#xS9fl+$FWtXCQr2N!-k6NkMl+S4J_Oh|suvnO zo#+}X5P!)=&@Ru8Sf@mP-|+cRpL?&~?ilm;?QUVQmKv88I|vF=>)e&vv;S;xR2VgxJ5>D^VE zzpKv;u05TpyJ}lY(XIaKe{3MIzAK;$n{_=K=&$gSo8}R0O=~|U4eSt>QK~Prnni5N zcfROn1^moXG!a_occIIeWG`rB=d|^8gVLX#&&-2}Y6V(tQQAQN;4)3?z3+ z*RS6u(UO5bKmk_JGhu$2T4oGWyoPQtksl=$fqV2cc^Z!D=V}5_1&`%3H7~_ZAGjl5 zqaLX+dZ(=}VGfr|I<~ZL#-xyP5-g0pl5QUc8*a|p4lx~u_7LbFjyhgt+DC_6_xNjV z(w=y%G6jnH$yc7#{a_8)zJ` ze^)j5JI`K_6-3$zSlb2KfGoRjm>gaYYAkGm$DVAnP-zCq#Ruf?$`>bdH*DoL2u-}M zcXTNi>DQewD1r7PvU9|i+M8aD*e%d1v{J6coxH*f1U0o+61~cTcP~iuYzkJN;5*Ft zUHfL_UFZ)Xv4rs?>pDO^P{2e0KVLA?@Cf z!)9`yxaX~f%t^>AD^la_S9tn*?t;ef}~Ohc7{6CcJ7e_l;U-Xbzx_+osse67~E>)1DvAG+(Ern-EK zoW=}<)1W$q6pj=bn*hbO{Oa%rl^szT70BG()_fy(6!Ni`VnjQl;4V3|ckHWV)dM!X zn}UE$?3O1V&aueF@6I=XfM@2Ij_duq!zmSoI8m0_xVtmxK-7z3HxC^aNFZAlsC>AUOT*=z#Mtl5u3EF_5TD>G zjO>Xno<*Kg=k;q{4`<2l1BZA+kQQ$_^WaU%nZ8hcL1J8VvcI(gk_>*$&`QQk6k@Oe zG^lIOv{Hg?vT_8RUA6ZM9peUR$Uc@}M`qpDnH58jO7XSX=BuO(6Jc#9-4fB1{Q*4K z&Wkt(f8UD}Z)w&gO1-*pjEVCfALR`SS032ESsQvh=|DQN{%vZ&oN%~{h|p7iry4%< zJ_?$t?c4WasRVu{6E6T|9~+OI=cXC6d#!t!oaTF$uNs;78h>ZXuNm zeB}RN&j8zhb`cHnWWVDa`;&+6k&j(3&2`xOqZ9m2`q(AR4d7Y$%xWwxF%lwkYz4Z* zKfD{T5kT0EPCqv}yH1mO*ZTXUJlMT)m9z6^zjnOxEILluIB>o^%jxK(WWvo1noYw@ zi}gd0!(10@@l8d#W*`5c6@52KKffLUFhU`kkcc7@!kpQ6?i{N#>L*m3rS55y`zS!k~+^>8jh0#cO%22T!X0PCdR zp4%w%6AaAgloY6)9c@T2`DGXmCxv;gyqu^WJmgGOS@*r*@~ps7zcMo4G%r`$|3KB( z7hvTKEn|aNm^7^WJwvG-uQv(Tiw2-x?nlS*wdqbTVAVeX-N zHp&TZjOUO|;{sQ`l~6C(YX@4XM|e2%1PMFu2$pIXWLgBW7s|d^yYjgD9WEp~wyJ1x z)%DL$PoLFl@fPOzTWZy;x3*MBN26jBR&*FhU<({u!83djf$WfdCz)A~K9+Ht#R~hRGL0Y~ zrDM?X1yme*OS>I3Ke+K*&oAMGw zQ@M)(b-Y6si`}|ey8$>Eh+UPEUH#ra^k+G;w533^Uy^Sq7rZ)?Wc>?-M*k(XIa_PF z;`?(OG|~IJ>@3Q&V94#+eeSzcWKv%zUf7kls{xmR^*glWJA?IXMTa*tu7`Mie$|9< zpZ+2v{@kP1;B6oC;Jx50PurAivfpp+Gk+d}!9WQm3z5->nQw+?v3_{p zTn~PkfB7IqBKBuln(>D8zCgL$vsmDClSAf#FixTw(!1<3pGPdwWAT0s$*1mM(bUAT zRNEn{F{)O$Odx~kgVeLy)tXo@t4r|rwgj5QQjbiol5tp%|K3O|pc2&j=xFodXL$JW z$A`_chkBA?V@SK)YAmwL#f-N5&D>$lm;1NoTAATFQ@8B1is3gBzb9w5%&^11Jvc92 zx2V)QLKJ-h`^(^E#KrkN9+bk7o6yvbych1{TjvL94d7wR^}E%xzuY$`Ge3^EKDddT zy$eInsIkpP><|`FDGvqm=(lUS{q*bI+-=gu@>)T}TTuJ-t0Mjp1m8&={9^oc2S59J zBz}i|(2#fbJJI)kDHE8SqJb3RNYnvW0Q{7q)tk2XPi7AXnT^lx=p7l173vWvD7tVj zRhsqOS=(80FUBtSHP4c2!^kIES<@>B#n?OOPY}0FyxVs8OIIRwhqxZWYJ$=B2klQp zVk7LNYtX|p;!}<8G~vA;_IiI!NbbW4hLW{ZOGb1&U&)$LG~R30B{ypPMLD}ejgoR$ zz59v=KGN^8x+pcf)QDm0_v>F`@$4TJ4jXv7l&>=s|7K!y$86`$gmZdKrv#GoPS~$FPC-RjtMa8zdMd|)dKJX=a7S-BKxB7ji*n0O9jQn0rM*kZJJCey>riF%A zP_tZwDJuZu;w?@eZ}1i-LU?%m~1uhEUx2*1zchM<03kH5+U$= z{~GXQE7#sm@a*%#$W4aCF00ASK?2;z!Q7B0!qj(NX?4uyE$yGBRZRNyOMZa8RE4S& zzW|+>N$*nGZCAZ49o45c$G2CXO6csMAROrLRTm{yl3n?m$0D7;*S1XUw7shmp%+^m z1kExf0C}eKCV;)Pnl%(A<{u8&0tny*;v zBb`)taH5Rj!Xpy0N!yAZ?mphGhjwh7#D6Qo#}Rv2i04x{&}RC;*X}0S0W!<`fSV5r zQ#*X2RlnT#uh`#Z5m(2F(q`a3N{mZRnWq~8$tlL}=yc7T%7N$y9tFS$@{^V7un%~>* zZe2RDtGE)+Fr5GFdgRCC(iNZh{_r%0_zR}>NLi*wW0d&6Z%0isn*Ve2dkSAzaoa|b zKyDK2KiXX$QvAn}|9>@pZi}FRfPLL tvj2<32>yHO$G!T0(c`lJTN^<4_oa>QwHVDX&I-ok1y@j=v?3L^i$kNQQNp>NGa3tIi0RcnE!4(cQE6~uB0A`sO zKtwR0pr9g$1_OeEiU=N{94aUthzbgEMNttv@W5Nc|NDK_J=48A8-l<8ev+xGSFhfC z_3C!rx}{2CGh{%S2gVmaQU}bez*U#5z#FJUux0rNq=XjFEkzTot>7xbxmdP z9N&A3zxu4f(^j8-wzqch)H4SCb!QK*IeYM+H@<1`EbsI)hMSx1T>{caAFOFdG#T0* zo1R}9a(hnGcgi$nH0{r(rr{s|j>=h@HVDrkTuo~ezpmsaAVAZ!QxFgMr`};9U#DD3 zRQ{{GL69K)z0ziB$I}D4Kc*rk|1Gw(EMWJyT3Y|)xb1}pH7ym1Pa%9*Bs{$Kj0@Hx zy!20y9C4CY$Q}I~L~e%tihmk_A{+D0f_kNvTR~rp;u`kPIMV|nWz`@vb)fxY3f=*X zj{eQ1UhzlSbSb$WKsD`&Z|U0OM{3&9gPOK6fNjrN^1boHx88JMvAlBj zSAPBH58a=g_tN^)uD>O>@P`k!el)T9souRi{?q^TzQ0-YtDju;*pFU#)8c!-^3>_W z-r3)M(`QCMKK6%)X1u5G(kn_EKC$lhY}Xm1FPKaFT3^_7@b}LA=d-Dvncp~l?cJB` z?7aDs2OeL&>Yjg{a@DOLKlNkpUiQjibDw=*XOmXFZeU~ITtoXT1Z>i@TxEAnvp2$* z*uWnjKj-*m{8W;tFy0=BRS|bG;yVo@j&4H4Cj8V>xIIbDEG-qs>!<&)y5E}*nwjD( z%i9wkMcwpv0+#9RjNdvL%Ub}9@>3JGRWdDaF9fz0C^zb=u3exF->TWv^K~Mj?kyyC z&V4AQnYz0-0+rwBnz>$gQD;pg?3=4=i-C_~&jm^L0hZmajRTZ$_eCIMx%LI-#*0VV=D z-S5-2bKL!bS86x%-od73y8nf=p`1u)+9tBFe~qJxGL0$(N|*@`E#5Q>Bl!Qi>M>I>E;;iq6wNa$4+*}Mh&C1bHM&@ zVDBCb4&@wBL?VJDrBO;FLAQ4gsYvocc0wdEoCOOCs#qwkEdy(%K-1fkezzSMw&~ph z0?6^zgWDkS=#c4c2B4HzxKqp4bUP@p5OutTq~x^#qVlb4=Yt9)&4n^;)I_FjP0qF^ zTlexn%~`n9yK5JpG7X^W}76Xv}fimM}nd?OSOISCQm((umuw8<7}+% z3=pwVUzldpGM%f-dM8@2;&nUHpoH6zdEl2my&Ro3g@iGfJ^RA z)Jmr=TA1mb3iXuc>#mJJL}QXu(Y2B_V>+U1rD-V8xxp&<7%9%Fyb)?x9|}WV>e_Pf zKzq(@I0kXHdn|svH{mB^^$w@3!HtL>P5Etxc8*=_s7LpYntm^VVTa`&jIQ5|jb7{s zk=G^-H9|v`M<2m{HAqr2{SpYEPZC6UQ;uYMtVxP&_gQA7HL=(Q5=T>4eL>6@F?+R)o$_{$lc$0YN|wrhhL z5Gtto=0)f}^!6sFf}z;F2!iWfS*JNs9#%v*zkNhF}DtOK)%#)YOs^;XiLb%EhBa)RYFX=k=BNX zQi3@=Q(+D}{vpt1YSZ25sUp3jfsu3%q&x;3G{qsADmAOb(nS}WoMQ`3Fn)FI&06Ui zW!CE2koMhIl%FG9$K15o0vNh-dcI1vclvjMVYjG4=|kyA%0Rym-C)8+iLwnl{=+~u zC(Bm2OLrNXi+1Fo6br68!6@X9BZnd?#jn z-mJhUou7@s&H$TE0Ci6W9kXyYOeLc0-PKui45db*7M{{E0>olW&R?ou%1ml&%wjg{kKB7?qcztNiS?nQ+L??(LpUT^~$NBTt%ux;6?3B zpr;>4Lr#d*DINgOBPoD*06?#!0OA2)Bm{^DfO#Q6JOH2-t0dw905x6#!~+1TtpbP# z0CYqOARYh~gaGjXfcmE}!~+1DodSpl0JKj95Dx&DYAAqs0Kh~<0mK6UH9|FrI@H`{ z44Ou#k#lUFa}~3kai8GqA*j976Q{R!ez-Z0sxINv5!T&PfSs{as>)diWK%kg5YsyY z@hR`k_-&qOHA|Tw*;@eEnnpcPbFI1BZYwzSXd0{_G$)%iU2Ip*sSX4jbtP9>gBaUT z2oTmTr(2ucx6#w9yJ&AFZHO}wo@mRODG!3!6PWd#4cGKM{1yV+vR`W}Y4#x4(+2#5 zu5s+k`RA~$o&!8&X$8pTM_Ww)T>yIL0ytFcQsC~1?@XHzoOFjU*)@I8RA1PYMJy z{dV|u7YZ}O)R?E3CD0v_7=~1g7!s1%jI=FNHKw(YqttHs%LN^kqekc$5-M8$#S-%- zgvyrx83c++|89X1W78L*RBs(~qr1leFmS@~fbx~{mj9R_60Q&>+m?48@SS;(@^^^0 zyz>!kAmtm8tTd>*bj74Z63f?3W?5iDP3HvI^rb(D=Z0`aHo_4zf*42LJrZcnyu9h} zBa|7GK{3=Q#s<^7KqOva`lkR%RSy_KjB>oUQ+%m0`E0S@^iLN$j7zCFku#NF&Q?;R zHu99|pIsxSvkwi~9BKM%YcXKMs2H2(Ivd8NBW%+7<<02`HLwx=77nPObnT6(y9pPq zIGE?7o6@?`7CP+>OAupt>w#3t27z$|uz=wB8z7G1--KWHLcoGm1hi+iS9IT64``sb zM&}FLVjR77!mR=^Gtn~SzrpHolmF*A{LBTTm+ex^`7V!P>>G(4xZznP4 z%tkTq0#N0EIFl(}$8`LeLh<9ZbgZ3|)7{H-tol<*^+Cpb3qMoxwv{pbnZoNg08U?n z{=syKbc1vbfIilxrcykFF~{O(iWH|X=Ilmk-pQD28pV7HKqVh>ChOwi?Z*CiyRm=R zZtOP1`uv%qv8;`lUi?gn*_|go(;U^ucgV_|G7XF19Re z$Ez^JWQu!iAdcmD7X!=c7RS2;{tZkK!h5`T0_0wbAMag6^e)41`H}c8gR*;f)%JfY zmmk%*c@SI+@;LSej>@w$n0vk(X@zasG+da`<^-ndrsZA^_*RL~y(>r`>uy2tJqXJF zfsWOJ83Q{&pW-*9&j{VyB=qwI$)@gI*@%9)TDdHos8u=U>xnANr$pgfy8Aw)!b$*a z#DuO_@(C;)yor9XL#s#Ao3Fx#$5O(r9Q_ySwCqjY@UA95?0dMd>`gu4!n`sI2k1t^ zJ&S;36}BIzlv)P1M~=$GpYC|qAl(EeCGUqD+1J53a++$^G|YZriOMX9{0j!=03#_*D<4Ek_ZH;e82rfB z1~vCX2xG%z_)vWu%aIB1)0lJgh1(|nc}Ri{6GQA+Y?%085=cU=MV6e*m&LPEAE!P_S><|XVKKkp5RdF>yXo4wp)Lhx3|o39twoe+ zl}HM;bd1V*X$CYWqTdG5JsXRtQ3+y^ixKIE<{yi-K0L& zz~!GxSw>L~LF+QTmZA*ue;U%|hi3$#Pl4#PsCOc=)U8A#(wU^kO-Z3gzTAsNCFj0* zt_zcP%3Rt_x#Qntpg$7Oy&J&1UGvtX3Y2tg9=KOQp&84&5pc`93BTEBN5<`%U=F`f zYdu``HD~MNCy;Wxa=qVCFP&Aw(8df6ik8246*9b2W&aD^3s&2ta?KCq9S%f%m{fO) z1e7e15_hvfX&G3~Da5|DH?-o$Ty0*C9FR`1(4V&^%4|ZV^9(ps| z`#6)C6UNYlmcyz?Mz3rEL}f}b)IE$3cICGyFR4;`*I1R^xbEEs_#k)RM_c^2psdo; zzcv?3Dk?v^bK1WYaPJd9>>BMt9#NsOa^qiK$Brrji}fb*p}d@fUY38Jn?_>U~q zWM@b7#EA&K%n&NDq<9NZ0mA~DS#DN5Gm0m8sCE%;klagR)3xZK1Y2|2l9g*!)=sV& z?Q{RHA+z{%o75Si1#k_7AA{tC?~3BzAMRV>=HN0%QTW>f_@k5HR{#!MkZoV^??t!; z;jh6(->D5w!v8qn=K($+-8sC%_b+g<*{02g%Rj=Oz<&>(MSy1s#2?23jH>cyxHu_d zwlx|4wXB(+gu==*4F8r|04=KxTXywc-SF>Zj*cei zfR$amht__m8cV3FLqhqxPcF^$Uqpsg-H~2cb-oyO*+mzOnJ1TK`1#iDpr|}-0L?+> zK`LsbPsynMST?n#w^f_h`oyte8}HP0g%fG$?KRk>)}B|bUG)_n+HN$z(5K38ySBPg z&o2xt`BS;%xPCA)G>x5r*bg^~m7W|Ylgv_60Rt0^Qx0#kJ~t2UPRx5}^)x8W$dB4E zt2uOKCVF*Is2GZwi7ZYL2+oAoS>RG#uvUk2csr~0a}IhR3TPWRUl5j+K7J}lvraCG z)m~lSW?l#U%02k05(ix$oxIW6>D^1*iELkB$RMN`3pb;N@~oae|?1Ab0+WX1zO?S7fjD zNfz#oNdI|+RRKV12fTuZ%a*e-N9Ny%tGd zO63wbyOSt(r2XSVq9quS`4zM|OGdiH=X@6Ciu-qQVcX9|RrGtrnu`gu4 zaKWpuzFLGGE{!T!1)!b_m$-mibTixbFlEGg*taMj zR%p=bXvuz=G_Z5~IKXR%+37+`_uEA3!{i&R#eoS_Rj*hlz%>XJ_}Si59NIy7%BhS5a_5~zb64L%}9Gcfa~9fNL2$fY5&W_ zWTJG6Wo&vs6ar6&1XM%%J?Q&?B}}Fr(sDWy)_6IT)A4>p(pD}IMw0nG3`SBU(M1hEfYDl$U{$ z_Fls8;6G4JhMR)`n!Yxgg)-2Km>v$WqnKDQY1Q|k3>JXH!gIF0`szeBXTh=$4qZ>Q zc9`y7uoH5onjI9h{dY$-0=@I_|AQ#NW&}d}86a&j{yz&iPCU!OfqOAtvEB{?=Ayj2 zue^njbS~}qUq)s&=hAuq>&j#6e!Pst&dWI2Vw+{AuJ7}H3^B_o*`Vzn`CY1e6pO2j zk)hQ43Dty-8RJ*Cnqk{H+Zk|g%-Q*Y-SF#uw1!bR72ak`dQkRE($8VioA6$RVleGZ zmRfSjdRJ6hZ3z?fV^$2F4wG0ozL>**wWJsbfC-#4urm*lh3(Y;PFVa>7oo3OqmXs&j8)eA3#R{DpsCiN=Mnr< zJ=k67VTbT67_aSQhk(G=T3|km#-G+gu+I|OY2eL2qnz>o2|ASt#IHvW_%paNEh@Dk z^c+K+`jm8|5X34R^(!-eu^HN${DF21Z$cnQ>HZuk)xPp2!cFlT1zrPwEm3~uIzJ^% z8VBrI6EK~Mij^}Qb=)TK)xTy;p!zUZjtcwKETkspMnOYQs_x{odci%lLV?SwN0K1iEtDL+8$k zmI#vY{)tebzP+;swgl)CVB zvPK8iJ+0!~*zLYf{^dCTnPxeiPK+i~9m$E^8q1QiVAf(>%&7xTBUf}4a4sG4aBSF@U4RsCgn<_6%ms%_}gxEt1J_yX<2-JS> zfge+p^MUDM>B}C%r3pEY#o+>H-puFH9)k`}DAl3M6*K{;nJyc-L~~8HNeWn0#P}4R)@qK)X+FUb-d#z- zb%@GwrVsmsa!*Qk#;R{)l|kmpd6BM&uXS( z4O3cJJ7Z!U7M2YD#?Z8}jc0+3v+->Fs{PI+2*Uh7hk%8KXCgRw1X>p6y#g-PKZ13ogqFpfpFsq~BEm~wqqPZ=%-2%g5*C+1D;)SMP2(r_$eRUYkEiu_2F@i4;NB8l8!`7%3O z#eXNQZJxH%AcE{icLRTnXXKAN5Ut=^>Z{2!`|A`~|S+kwmeImNQkVA1FNe`A1kJA|PBwGQnZXOOmgXI}Q7v23GnPGP|dw?pl< z2Z3D%D~YUAw7p06(~dU-PD%xOC19z*ObO_?HlTgz>~yC$6L>=tZP*Xf(Hmh?G4Blk zRF~xqTJ8kR_!MOEgT60cU56@+C2@4>+`%7*!`b>Dkt1{;=Q4psbC6`SH>JzG;Es#i$`~{1{UKKbp$0wP!{(>u|Lo$ygv`#m@e>c#q3O# zrC?QA2D;ugJ6ol$Q&kp0aXcV9TNFRLIlo*KpH^a-?jR~#d6_wI%5dHs*mbzw9GErS zY!0j%MYr%TR46Q!;j%0gRva=1cBZ`L;iNe*2k|ERO0(R8eJRFc=@fkrMp^RhqMB4; zD*;5Mi#!at+-8z8UJ7?VV^W!9y){p6Il|(0c%(& z6f&qG-wsYcvtLB-zNuWyzQDb5RHYWc+dLaAE?)DuD43D%WTk`hh=kt~ed zk7WaxzC86qJQ1pJMG&e>P>T!D(H;!#I>-xUp{!SpCI(JyYl}sl2?pLin(+@~B3BYoM*m$kA~;w=1LsTPD~0)2ClXC53H;lwk&5n`FjToD@HC?I9@v@m>3|DKu#VhPY! zni?hv0dE$3jQ8%O8s)tQ03mN|S}5N00i2%oOH)`AA02fD8QBlK`KL5p+9Rk}C{nxZ z1_7MYu*-dlnYR}L0em66a@q&9lk5Dx&%tN_Fl zEiA~xc0U4_cY`wcW=tk;?GAwm&HQZnf?-lu)?zs;%u`gL4#9vN*1AaE!-5` z_r$6I8WchL6El%nnR&YVB8{{Ih$oTA2k%mI?gG;~$GeDB_M6~jtb<{F(loC9FN(3y zyJ0_&DJ$U6Y(WWj;+ z&-Vd>Zs%x7iPpt<_ppfn@QD8n5&w}9{}TEY=>tJJTb$9ZGuzP7_y<4`FA7-W?W5U zCEc<^6+F_R3lq55-uom!Dr4-@&(DZx{ zJVNnhdt_hn2I#BHE|v__JCp^Z7?r`0CGNiqT2Xs2Fx#*K>K=hC%G!eAp`O_zk4hU$ zXt4SDE=aC|8ixhNJ&eQ>At-Y9jP}&RLY6DjO8qkuOBAL2is4*h_7W^xWS6D_;$JdB z1=SNneVAT!jQOcxHZM&Fn9qe|h6h4Sl>ymUmSWn>G%J>x9@>H3JqPP5j)&aX83XHl zvokYfg}t&8`SZ{^Yz6J}uYm^YP@ratIm4O_SMnDW0dBUEEY{daa^8g4Ik5Rd{T3(b zw=d9deb+{*_l=X(`xw+KeJAHkIs7Yjhxt>rEgc36N+tGE9JrB^!JJ9bCgh!+BaxO% z)klE=Y>Ot?Dq*0MQZ)*|#F9*LaY!*}n*F0(f|9B|JE;5IM^J4AQA*GC675iebR?GM zu>UTi+?iOCO*I!Y2A&CldeZq7&1q}ypQb7v*>9luIy72Hb!d+?Mpa0Js(kAjrpVf2 z=G&>hmmSK9hCWV@nohygJqF4sVOsJgxaCF424?tpgo|@H#ljS77N=0BjB;vq9N~F* zj%5NME+YVQsiklgZMESva%}--70WR!O>JNkC_65}6y-!QE=s8<`tn0z4;}3tlyn1Dmmq*k&e)XqSNn%K|F$sX*|Rs+^)p*2&CXz!OeD1-09%GcdeBv&bnQAR>8DKDS34RsaIapHa~WA!C?Z7| zOm#I#Md7@|FiUgPoWtUrL(MfJ$rLS>R1`4sg*aVICpi10XnFtcT9?8+!W<+RXK*80 zwUmNl;)@PGjXHgd=z#3)A}2K46J$;g%z{ZdlImOfhJpw6HrT_?SQvRwyKTO1MX|ak zOWSLSP|aAMUXEH{>P|aVj;-l*Wc+3)uhOSI$s_ia4Grd({Hd5|TPDtHX9dS~Cg((E zt&bKC5hKd*XgtXAGu+{+pjF#d<{>Z!{lH>0L|*KdiIjO6y9?LoGE(&o_b5;QFlOw` z*9=^hkpWhY+{*by^rb2v$^ylyL>F@dvwE}Sn!k}(M{@qXC~iaSTXi)X1wx!|$H5CW zO!rZQ*k?_;Gj$5$cxMWo%d~fif5ji;B1Q~U0nMjB|_0qBWII-%pKo%__F6JIs zzL5x#LCFF>P~W+aY7VrS392;;@x-{fx9Ug3EtVG}}z$7HB69tqE7JgZ1)^|A|@@o-sQKsoWJvQ~e}a+V#_-P(0r%;$L^SRvJW5 z$4Q?3?^9=-X$RhixYl}Jd4xb8CmlI-<-#Pwc`wu78cI6&PFRk0X+>#>|4kS#I;yfz z08QkhxvsLz0G%0!vbX?U7>BYn0bLr0vJ$AWS5>5d>8S1Sci3MK)L^Z^|7FdOV*xV0 zqIb}iNtjh1;C7GO|0EU2qAMBd2z-i{Ziwvcr{;L2Ptt zSPMC}J6sE~($}lxqs{(iK=YmIUWPtSeqFdO=nItIr;^uiuJO*omuUz z-a%)afxUbzsU($Ot{HY9qL!#5)t+)ue=xK$$=GtWp;a&vIdeI8wZ@jaIEZSy;9v(Y zu3`8fF2+zg*s@NHJOLP)fxTSq)b(XKZNNUPLV`3n8xu_YOoi|L%&~BZO(MjL%=r8S)xLO zPeEdw4OpJRNx2nJIqPuPY}b<$@BXbfoqcyknZ>lF)=yX!WVu8tOHLB=CWlxQp1{ahw zR@Z38eV7@N>Y}5UQKp}qYRslwm;X-*aljQRalNmIBaYak#x*~+X*S2! zb4m9!&@TU4N8EIr(L`|M5$8cSve5pek290TJlQl7FRlhkh7(a3c0#Cx1S6@1aV&#%L9MOgep;`Vd>O$|ythRqIx{BGS<$GoV^JPM zQoo)7?Gqq_QEv0UgdWI!gd(1U`1J{A#7y8dx{)^jF~6TDkeF zXP%0=FXOwf9%wri?MDK=L4035zF*>Tm^OnW>`ENmRW;oYK_n$lrGji=x2|#-@+b1D zESqE1$4w~3Q5mzUR1xl0C$ys>C;wEw9OR4j)LRQW^4P0nN)hMn17`86HO3qqMO0Ou zfH05rW1hi0iY!Qp6qTp;TJv8BDBkEvV>nX@SqV{r|Ajfcqx z4yJw>QIVC)atAayHd-b#to6xj}b73=PSt7?|BP;$S*8Hjfe!!Uu6Iru8FK6Wym zj7{aunWsg9-R3}BG~AO0POlZg=(A!8`mJaJrD`&?L^UexaZgEQ#R+7?FIDcevxkay%W1Rp32@MJ&9`x{nbtCKz9>2d0 zq~!QnEkGlM=|nRj#(#(o;6oIMeP5H#~)GR zD`st=Uu?>IFcrFM%%VYB1av5skxv$D7_3VDonEAJLP_ zd`J{!dN-5GMc{%f_>u?uvsOy&d`?ZFyI*5_FsqayB1!om98UiQ*fnDT{bJC-HDvec z7&C>P_HY#*!xhuJFCl^Q=Dk}1hAswCrkZ;{>oIk)CtCO!wertn#moI3sbXBQB@1D4 zK1)Gy{}zumDvNNB(w18Z>UkzCf^31BFDAR1jp0pD9d!IE zQi(mK^cg(aIz`c3jb@?Mo|$q#hRg=fb--lMgAShX8|HRFse^|hgS$lqI-Kh+D3CK4R$q(|cR zGkYd3qM@eITe$>!^WKRc_A#{Y7sKVB?%obzS&8azlZg0|Z#?Xw8Xmr`!lOI|m|)b0 z?&RICAe|#?a*K71?>h(mRUT)m+;7Kgq+YocHM05`lj@ZV=>I1D-HMFvK2MFQ##n;F zN-Lv^K2n#EwCqS5lgZ+Rz#yYC<|3zeZVkXjv#(MklsJk4vSL?u3-h?)h!rXyo z!q!mfpcSs!HaBZCDWeX$<+7H7BQh<63sG?#NvfBsBmq;_if|}XZwleuTbJsrP4C^* zh)C!D7&WCB)G3dm2F(uq*>+DQGyExR-l=9yaJY6!=igBVcT(_jJ(c2GB3(4Oy9$PPvHwtj~Q6I7;5ZcX!~Km~S$V zs?WlXSUqcJCM{NEOFCruzX`P#{-)1BdVQ2v^D*p0OG|MH?DK zkY{}^0Q{_axw4iiGYY2_jzQ~#Q>J66H90XaDz`u+o(E)xEf406?e8)JS`rb}BL1NO z;@JRsTc8=ICNS>XX*IURUO}NuI!(y?ZXNBl1L@A+{AC@wft5>f!VE(1+t8o8X$>mG}+% zpMEV@R`0gp{=cI4BTB-0tn<7$-#^S%u0o2&{CY7bVc%%xz4yYIkgvA9k3O`+tKrt` zSpAK4Of4kSPb*f!>wp-qhHtv}=-ykPtB_S2^C+y4vuh|r$WYbkeyz1QDQ`ci=Pk~! zp)PyyWC0Sqetu#8o{i*h|JTl6xBHzPVi&a8eyydT^0$!ZXhfqw6^6UuXv(TfD6V3Y;5HO6HfadZjP+wfaYdV_C0MLq)6j4x!>zWdaXNIUq5Q~1#= ztdAvBv@FT-wZNmmhO2q?Z$Dv0Prm(>bZ{+>5rUh?-Q8~WWJ1oux1X%w+fUe^P#0~X z!sl{aWYLf48Cz)_vHUoF_7RB_k2CZm5E$5LY&in@5oadOT!qYXE%>*{D{RIbZ-*`# z!_71-_r3aR6W&u>M;qq5IBm@6(vmfWh9j)N?hyR8C>zVXffswsH#Em=*7#we`9zJFgOVINTPIT zJ^iF~-|t8{9qHmstb3;4i&ATveJ+1!3mSg5^i!7k6LZxx6xMn2trE6n61{C&91d8P{fx;S%Kw*yx^pi2@rwGN;A%&C$QSqp`!z$Dr*qJ;h%}-0~lDNQ||afAY9I?fS8W3(b8_Y zcf*SpZfJ@i^f`peFXF7Twavl2AvlJ<6=xGrS^H_n9<249qRu;mi?-*!as`2JO-rG1 zc~(3ldgUI_s{V+|QaYl|upY7-QYf%*E z4>HQ{-3xDig=!B8?K03C2R*smbZ&qyr-<(Zo-oN;&BetkChgUr@=^_%mqyUjV(SZA zHvE;Da(O1&s`oL(vbpkbGA{O065&2_HC`1a<}V`ujB>p%)bsd7cv1JgFA>zK^mm>jUh(5=Ai&REcr+rn=j+&B5s6n2QjW@J9jdqY-if)4(lBz?bF!K zwACMgP&B{J1lF`4DGKe`DYm-vy7^%t>-F=i+n3$KFtji0`J3Lp{OXP{gs#FohW2Hh zXYcFg`QZP^6L+8SkbxM|{bb7hi9=)M*b<*8B$OdNL(;)SeQnhS`J93AQ`ynvDJ+xj z%*H$$>UJiQL70bM2hV=3GvIj$|ha{-$zGuG5f4xn?=0Qglw&M%gPdy5v+nFa~ zZAvtbxlhnhVUi>BM9d)3J7cCScGu)~9BESprEk@y>O8}WIEF3|A8?DcDNKlWfNNOJ zVVjE8?^38|Rlg&Aw_CrlFR%lg>pGsMeh;fWtNI<`{TlWAdppEE%y(G7>*b4ya-@8# zEZ-+Uay#Yw{ea_>aMeJBWhW`$AHp>~EDOC>`Hn=(x6X5W<@*$H+#5BaKfxqADJ4-t`dxgQ5C|F^Mz!RzLS)h*n{& z|H7|cXZI0?VV_jjXS^7rYgaVTU(*o3F|HyMh_22GRr01;ar*P|YtqBEH z!}8Apl;>(F$OF&e!i<(bO!$6G8`S+dFff0>nL8dKp(E3u>T?7nt3Oe2hGZ9{x|IMt z(*7*nMD?d|jNn5BZHD(CI^b?BzU8YwLj)&oM4(H`bSDCrgn2lHVJ@T7^*+nxJ7)^` zGYVYL$0;+PWcKxOGGIaq;*5>zU5KWC4X26n4m>P+_0`M zn34<(o(!`1hglE98Eex5G~90am3I*Ftp-PFL%x$x{>Q}bcm?Q>DE#t})11P)!B|gP zhuM8oDUok3He)_>2b3Wv%6^^ZQ%iCseF6uls7&>75L~Swx2;M&y7wN;PeL}TEH37Z zWfzr3ZA0;>Oh>Xk*@xXq?AYSVP57n)VDceUl2&VyovKKMnd4#R%GE?hgFdy0IYyJK z;crmegzU#A(&gbx48_574B-XW@~@t0I1x-a!Ps$VfwkNh=*T(iZJ1HUnZ zdzy~B36k07x?4cL)M{>_i!T$qui)fZ!QApe1heh9sfFN^byDt$NX7LbL%y4`F;0t# zNQ*9MG4Ui?Op9-@LXL}&7H>Yi0NFyYI) zlxb4C8MY=KrDt8|yDikd?6!WWRqPntc_4mF9p=Tha_N&(8r#<|QBd zLsqn#u#JPxg{1TQun-#NK`HHXP?~W)Y?b#wUv(w9XRtzIoXJat-m(|Uy&miRK~yO9 z^>pyYDhKMmLPgytaw%;}BAzEUL|Zr#{~&mv0_)>j(H_TN!cVmW+l8vJtIy^Pa&Mv) zmHak%%H{?v-r9Jz)BSo}yfuM%s=aOba9q4UD)B@bJWn4D@!v|FIte<)IvMJK`W%V5 zZaIeq%iKb@AiZG|zU|Cjv8nP8rZeRrb*anbf?Qt)t$Uyi4gKip6{4-RKdot?J76v1g`aeQ7qLFdyd^n`}N}MKxg>AtA{(2fWdqm^<@nt+v zd9O;3%@t|*?DylHgn}F1{j@#my(Q{uP1juOz=$1f>oQW5CXE!44jJrMfj!3HyMvFB=yOZudYbVZxk!eDpRBiZuQ%fiw}DFl^A$eNI|5hp-a z+|^K{vbueNm$F(63ybOKn{0elaCycuNB$5|yn6{XqV~#I$FGX%WZlPux zS7L-PGuS)2M9nqUp|$84oU;0BO-#v(<-G`i(0Q^U&-`SbruXGK3C>K)I9PcU+nl7Nwg~A2SHbZt!GS|8iDsTW`9se?KyYKlH2G{?HXx)%c#Jd{2yL zaW=w#O|-uF_tP$_nYv0ubwrBAsF-+Pg@DZyhoShNW7!6ue#2G~gH{l1EI6k}{Pvss zOkMPJLCrg6X`S=6)^DJqYa09qYpsul{-s)h^AXyl`$A^)xjgfb4*N0e>ivopy=FTr z!42;LNNVHBS2O6-DwxLD>)CCfy{KjDNP?AI0FuA4#}pGfl8gQ3vHTPviD(iD+N8M+{4sDPj2a90bLb=RRl zO0&~j=yoL96M6SQj6W(bI6JY4aPR9V0DPcOGKf!fFes-wlt_#CVF*s{sdrA@=|(Jz*LU$ zdiVCDJd8lN`kVLCa0AIdz$7$m)hp^z$?usyil3jDCe@SEr}_hjJ zSdXL!j|_ATcY-{tY0iA>CQdjMaZ5$8lIE;vMABy&}eKtz>+Z0$<{0cwXRvo z0*~vjzeS%b2WiA3Yr?D0Px3D~r|5qhV)N^CcJ=2#*2npaS6rV8jmz~|Fa8B%{Fm^6 zTJ=}-c2$2(XAsZaYh%?H>DMb|)VAs$;h)@xdrzRF+-@H}qK`A7_auANFz^EgkOwlC zJctJXN+|}7_$m}{Fgv+TM)vH4Y|FCPe)0RMFcniRfrvK}Y3u|8h;2(pcF;0ti@ z?!D=KAD(T!Vt+ODgE%H3gR`{u1XA%8?$jY#8lI+O1FiiK=ti&(&r`F^PL){+;-5x5 zb{2w?b4gw9Q(;?DyB>0L3Pa_TFUTX0pFnhJQIOZ=lpk9r1bW}Z+1@giaT&n*nJ*Le zEx>SFrxsr&A%1IF#Y>Q9ZGbz`gY1iS*l)oINb7l*H%TVi`Hf@I_A+WMA-Aceu6ip0 z$fioEl-OKl+-G8?az=JlwMqrOHsqHVtXa_lP!6DM5vQKm$Y3#1D*wl5pB@Z5d~yNJ zybfZOaNDUg)?t4e&C1eMlW16!lX;3GNqUwG-E9IDVk%+}8th4Mtq9vJo6?|1Joj*rQBnyJw5NDMuDq zn_(ee3?vM8{LmTBRpG?hWdM{XqPGcI!uNw)^(E*GqtygHDz3XX0)h-52y}OEv;}-9 z1M;8_>spUCv`F14(a;kdzDjcjQHR+t(B8ZOviaYxNrl$%0BEe6g&D{e{$%|AU9@v} z>9`95mE=KK!bP|EtVL>Ip?qOS-rsj|I17!O=V~qlZOoN({t%L@9D_Dnn4+%h`7v{>XwqKtm2{jek2YG>c`Q7 zs=S1`aB~_TJq1Gb3FOi!S-432Nq|*d;ht6L2qjz4rx@OkQIhC1UIsq8OZ1lszOBps z4153ujg1kwdN`1E?*pb>v3CT!%kIM44|prKy~%qBQGER#W_$|Ym(`6_<ev%n04IOe~V!6CiN@gAm+{0)B3R{JRU<7yv(l@BA=HnwQK$S%nAz6zM` zs1=YQS>L1F9|OK|ZMvLGe%xk0+`+}kvb~CPsw&a6^pH5?-vYX z5aHlzc+bZ|zhWpHYoCclJr@g2#6mxdCi^)9Cn@^guMu$OrLb`I75Ijj555g}23-E> z<9~(}oDt`%^yzZt%KavA4L-y|8P!1T{RA*I?t4eGE5|hPX{c)=HDqvo0hbRQtUdjS z2^<+9;E?22@>F9_o3{Hwyt>W3%{Fbnqj*$|dz*PNMuKT4PJa)edU4!$2k9BYp1Zpf zeFx}xQl38SdjAci3)d6yA_YrS|4Po@EX4Zfq1q>uUAi;V@rBw>_qV`P#C9PN<2sYj z671Y$Tp)o5K zt<<#y@9$`vS=SA34=BvVkuiJAf1m{09Q?@F1~qqA0I=9Pyl+?El^l%G2l*~IY{si3 zn~1nM)$RKN=&=GouixHG3>Vgd9Z2;Jps4t`XoM@#NK6P69yVHaZ-0o><-dgJfWjd| z$d_cGY&dx1VbkfTB8u1&-8&WeRi>=U-UjuBA3QCHWU_{}UiZI15-JJuP1|S&+*{bz zl%j|r(%5E1Rd?6rc@7AHJZp9hiRu33HGfU#1WnbsF*mIpYlA8NDn}uU8~=d<+Q1+G z(@5bzfS>wISKb_OD(E~-?~V9Th;v*Z?vI~3qTDK;g1DN96(V9_Rr_1{P4rH|d<00H zPp}ANUOxCz7W)bePQm;RX+BMw(3`^c|BK)h#Pn`J?88re@jy+BreLlhrWeD^ODRsl zybMU4uSxHNr1wSAdkqoURQ#XC7%LI;8(u|;lyjZi%ql8YlpUmVe(Dy$aXEL*K z`MpTg*~J|wmbU@PA17vAjh{(2C~02l@*mm(#BY+~pNPnQNNU5_bDA!M{jxt}Zj`3N z%x|zl9bQ^fFhK&}kP0K;RZ}oQ0-vnlH6ZW5qXCa8b>f4>d|*c~Um#}lys2_ep}7~3 z{F7st)3mb7iTUXr!F-vReIrrs)6jeako>pp26qtM9=QZorX~even0JRG-oQR$!A(&;Z36#+7}05};m?5d+9TQD$$IDA!MfH3 zPwl3TLA?L=*Fd_3NZ%*YU4oGasnu;^#H9^L}D}bw@B?CT4oisInW|UwZ(@Kc0xMrOS5!sk2^ojmdp>N06Q; z&%cl-&E8<>q&HFrVbgQpe(UoB`RfbQ4j!otpEbk2-(Jf0 z71Po?62^<^;TnbQ?FsJ`;9fLtiinndyLz;TI}rBB=c5mw$@&amt6jBD&AGY9IuEjK zX(SsK=W4G)VRBUlx#&YqGEZwmcJYPKjv+TYk zkV$HyaT`Y>WD*h$Tg8(-0yRWPRG0I0w?0KJoT~er&Ki-KY&;XRPvcw(nZ%X$X&i}= zNl3I$m1s0dDHoF#32DTgwqqQV zkVcra9|f~ERuM9(Nz;CeBM~wQiS}chCn1e^(tcE!p}q-eq;J}fahilQqDdPvPLq&E zG-*S|F$rmeNfUAscR(6(rwJLyB%~20O-RA4>6?&As-*cCCr3ylax@>~E`ZN{Avk7H29$r-UrLHiTr@6H2$)N+p4zPOcYHy8KDMW0dlAS110 zJlG`WrzwWCf{Eo^Y>!5C3Jghc$^fst_5c8R$vqPtZld`V_i+qi0yBx3lefa(`@}zwdvS9yEcQ3;JV?lj}RAz4(-NUt9%#QHWTs$f5 zeHLO$YtN?SZL2=_en3hGrDR1k2;p5*T&}hK85kvcWFLhW{~T*`E|E{+r&~|SGzMO8 zFCY&2qrl9hJ)C8?%hykIrq>xL1Rls@PU0UTRb?|v8{4Gr<8hw#81nEb=3(8Q$RJ*? zmgfvP9rAGN7N0zcF^LI(KLTbtZZ09wsp9gu-L7{~^JPchIO1ny#> zB?x?mfz}{!Hv?@9IMsILkEBR0Y>i zDHBSrq>>09#}Q##l1h%2K!g@cac@Z}_WC%bke$|5RgolMO#`84wNRR>}@4BsJ#(r zgB!2=kT!PWngM&2dmmM^K_6$S;BnP)76{K=ldReyEXdRKjF$sk;=%YsL;;8g0HzRt zcmSX<0uT=XEJXo`2LP6&0K@|TRap22j;E)L!57em=e7BJqG`Lo;c2;Z(Yf%noaot5 zFN#VraO0Qd5T;yoKr2(ekttt@rj#n9QmW!&N+}DttU$a7&f9?MrcKyLh9h_1WFK@6 z%Ir?`7b=l$&KSm5ChTF$9B3I%72Ex{fOEk=SAH*&-)s3R=Or-0Yj+{7>_OW8ZHPdz zuw{sq3qKo1=x4i7*6+{J{Bb0UQV!UAV;5Qj>b;0|<{s?-2vmBn4e2Y8MFaco7B z6e|P>vKsOSX&s z$>0iTQm%m7#f*O(c&I(=L>Z?^O!q60qk!XiqMdoRF_7_Y2j8M)%n%PhSxkkGb$j;! z&LDOEWG#|fSD{ehqj|$o(J`l3U}9>zo|uZRhp1;2stM!-z`hdPk%G5yJeE8VlM=@iS8R4qeWw$N(bwN@@4N z#Uv0zI4nRS<||u}?M>7{f%m(+a3bh_T2c6B1QnuU@O%PbXX7J}PE1gzBW3lDjpU7P z?{h%NDm*;TPjq|?t9NW(cMwPhE>00iq-D(*o7ZZ1cM}s|p(YSV{U}pi_FPJ&S3>Yy z5^eDu^)C3fG26Qd>CENc&2Uh79k89&0OxbS!JB@_Ksn>z%&L3>j)ku0@k?@;F^U5$ z9L-=2*hRe?U>QKA|5cr?>S893dx1A?FfueiU*kA(z)7$)p-MRZosD zADRQFuNpXgrTFYska|T%uXRXj5dgiCIu3+`G|8Z^!MZ0`3pV4cKGEJsbZlxrLtZ~$ z({X9_j_9xQCMXL}5vXCMroLMDJKVyrtFVJoS54bBpDJ;eQpfIb`1O|Y=Xm_6kuoeP zmYw?z6gKV%v3LjUQQa!!yD|Ndv0KJSRfr-4F9%JMBI{oZUi>8Qa*%iT!KjFLuTN^# zV%ORc;#Q)5P{(TfI^?^-HAAoqz!I5GK06&VQcp0J2 zAm$H1^>cfpE-GsC;8okE0bm6HYTGOToI4rg>d6>)P6m7e=Xhg8e=-^4<;j5bK9iD+ zP6iw<09L|4u+t_3ssPBn(*WUPlSw=ZK&Xi4BmP%we)&o*`qD-3z@-G1tFB>MqH%06 z)L5v@2#hWq+~)8N7x4uE`3#>7bO3O?QyBCG0ENN94*)s}BLYwu9PfZZM`1(&3WH-H zFz6_Z2tZ+QTm%Lkg%JTL436HwprbG%0Ae_WJ2?N|ig-QgJ%#QIrw@Nfrv>+F*zyK3 zS=GE%@D~0I)MQJ_eVo{cS9ocO)bTCL6NrzO;C?Uhmiw}}w%h|OY>x2xf6ToJd}Kwn zKYnv>_r1L&nRJ%!EHg=15-#cP*@7@LYy$!UDgq+Runq$PGb$Hu52zC|4!G;c_7rsl z#0a=QcSPLx4HS8XM{S5W5VcGI?raOcO^nJKk!}A> zbVJoahY$rD5Mc*$R?hfGW6(p~bo{>z|F_`(4*WB}3G=_^b8xsqwpq6Nnoo)!CP7F25J3o^PyC!FxY8^d zem(sab(1&Tv4VK2S8xi=EUU3QR#ewTcx6~X=eQ&4IMp3g$CKT0T-c$7;(FR4g&yRN zQO!k;r504)N9 zoplh|b50umkud-mGk{rf05EO=;+S%0*m*Hmo_g4g{sYCuALzksEczCl^*#f7&wvGC zaxJkce1{m}9hR2LO9pfH(kP%|#acm31((0PuZmL~#JHuLXz$fC&o_2LL3q zjv+^&74B4iCeTyjdzZZtqgj#HV4)F2x>lD2IA$E^0cr>gXf~`kcaQ(gkZQyhBMuZZ zX#wK6+6^B>%vzs*(Uv5(7foUBJ-U-n9*N{D;~Vwkw2Z@b)T`4{CWr8G{P*J@R^XQZ zZup1sU)^^+w-XX(yhDD~?TK4|UAhf%9qQ#->8l?EO)_=Ek+g`AIx>V$cY5)%L!qX~ zWY!IThdMkt&)0HxmbcM&Ljn@;w36gz#0N%lI#;AIvs zL;@U1ip~aCgJSX(Z4+VFB5cwPb}?=$HxH^Jv2>$zkSN-Pa$(x>+IJi`{1(Ge-;4tr zt#cQ7K$5pswga?brh4s8r%E5~J|(`IYLp|=zplxOQRCqqTXwRf9 zN^gzIKI6+>NoYjy5c{w8CpUUJQn}Fu@_QkFg_j!lfc1E11OTs+%!10 ze(TiO_|~b*GxuV~ovt?@pTkhG1*Nk6C?lQbmvfGGU5f)1^_I2gUfeb=(`AQHy12J}OLXx@sU?6ft=hXS3KpJxwv75^e z|rVdyE1bUqWu-G7J)=J=bes19H&AU|YEq@ru>{Ko^#VkTuLfQ4Hv0pvyr& z+*1le-_hWrVZLC3yy;D)-)Wzvq5t=|3Ur7lRlj6Cm2 zG%V*8;~zmZm;*G5Gx2G#Hpnj82FHM0jVbB(TbRBlUV9iO!OzE!eQI(5s_aL?_vH zuw>GidNKMiWD861qUWU}SfqC{_QP)DN_=M-&ke7F69AwvOKe=;dsS$>fDwobRH!L_ zB20~%m46|En@k`_)}+A`1`&|31Y83`N8yzQRZHblvd%&;C#o7MS8zAJoA`yNH=vFm zPwE)4J#oJw2KyJmRq&P6@V*${`cJXy*D;TqS?tQUX~mZyP$?h1RGh4)IL(@1r?WKF zHNgwaB|tAjyw%0{8nWEaxWWRB+6(tVv!tm_J;CD}dd?@bjmXI`60dlwmsWSXy*Q=m z?&YbH%Xd9A{=jl5QpzQ4!>RbIDAtRAhF@79kXwkhw?tvNRg{ZPO5U*LmLDOhp9!V4 z1mEpQhba|-X^`3aY!&`)sCw0~>A|qb>d4#%Q4ICpwMTilipz|(`D7chY zm&18(b+r+a+7C38HYRyN8JZVClf>=84|3{2y8JrxqK zKty#%?9!EuJ#G~XoD75rxtgK4cZu>xn_dygq$rYx;JPfzI#1-$D$rT_LB!sJm$u@E zKm!=7u7Ouh@q?Yn3qiCC*w^IVrt1ANeo^11WyJ6^!+CMC7rg?mf}dGIPZp2LR5nf& zs#)wDpD0AvB46R9jI-X9jlJkb^5GS21PfhlvCviONyP%=GnYq6hyM)f;WA{}VLb*txoYb~Lm*=qZGK6BL`VF6-f&4y&s(EOra0Nn&K@H~ZM-dS@BO+Q8 zWNMGly-^Uy|BF%1r%_J)dyu_x9csU`3Tl^k*73W8&ggoQSc`!&f7x=r#D+rnP`LTW7B^ z-QL8)k6w#7li@UIKM?I^@uU~$(WKWS5a@A_M4;0X0`;cLcpdUhNcE}qq%!(d**W4g zrPZ+HKsgo2J=C8CKH(bRIqG^^ZODMJ!mh)io6h-_0aZKkY#<8rc!h+>xCLp4A4}Ss z+ov6TENNfQw1)|vR7M3G>Zia`&Yd5{rj7A7A>v`CO)s{0Jf5G*?9mM58GTVO(FSfg zJPI{_)Vq3M0Fg}bWj4?(Dw4>YssDyoTct60=OklB=K+@r2UhXD9XB{Ih=s#8BQN`^ zOn04gfx|M;-DH3pEK?ca4eKNWD6-qp_VVaEhGXUh(8CERKJy63e2Fi2qM%z{04ye< zQAwpy4)wNwro64laY1=|5>R}~k&jy5m)*u2&;qkh2cpRXk>}ClMQDD1b~D|T*)#QX zK0Y+Loy{+FwOKkab4S|{e69&ThhM0Dv*JL&CJPV;0L)Kfhy#GL2sl!|(xq_&kJ|+7 zbQ}Rdax-Ry_w!Kere`=EGAJI-g&z-E@=wtk{}Y_6(bmLKEU2q+?TvVT2?3IX{2HEB zh{lZNQc`!S0;}i@YYv-Ucr2@foH3=)#fVR`RXVDvfiUS^iXfvUvXo!Nk2RVCKA za!HqZs+u=mYMx;`M(BusbRb4xz5}kHw*eF$hqByoBYtbo^l&wgEJH(a@6q zr}1SN|J6f`Wn7xu7_^Q1Z%}>&zbRwVHt@eT;X;D7zd}Ze>DrglnL_qVBKvHv<2Ng? z4KR$+O#o)5czUwdi_H+cNjaJW7vsr;LbV*!`*O7&+^C$u>uv=`@d@-XwxXN5S?mn@ z2b7D4mC2=L>L+``Fw`2V!P zH1^9`MUej-X$10(tUXNc=Z0s%Gqx|av1Vmy2(2TpanBT>l#_QOnz1C)x~Dk-9jtO2 zbOt?T#;iBCgg9-qA(o$~+wq2=#Y}*fFeokqUxXI=L=!Cv^fQ6K$5+Y=yi&z@!NXL; zMAu%4?-KB{?&EUhGC5wuc^cXxoP`iB`0)jhTDlN`&9T~SqCZ}av{*vJz7HZNiy64u z9#a#yHmx{Trl-=BgRX`f$C6hXD&gs{00+C6qEM~tYYVjreL|tL*LD!;Gl}&%C&p)a zlb*7<42pb#IAH0J?0zUVqBy#G_+)tB0w21otPY~TI?p2K<9|K=IW_n(!suRUVAjt# z4*u=xVF6yOTR6v=f3&lc{oA>&_3n5#HP`{!U?k`M^zZ_H=+EuqhdDR5JN4%02VTp-{4Rc`dJFRdx6ohM#m`i4aeiPo{l#7U zF!9%k)*FoHN_t*rJO|M8DtdZ5=Lc?tqtq#^F%`$)SDlrS$1}9KIA0`>XeWLL1g;fl z996MAgxkq(V;{+D7aW-Gn1Vdu=n|CxN7oM0{%|u|xidHOBy6pg_cNl~5le3^I|2$s ztq&HCE75tDuFku|;fctn+Ue$Ip3M04xvklqhwNr4{baB2TaZ_HK4NJf%{UhU|1Sam z_y$xg#!z}mfB05J5^LBXymBS&c3-9gmGJ3Es%~iDy3l1Np2c_vVh1}8gMnn%2f^TG z+f9fus89<6O)GP%3sL6XD048i*XCLzmc2H&tD}H7XRv>+y4F5>DzmN5_`?X!$^5x1 zuUTVTeJ&qfNILlW@EQ2FJd64D4?}ZpXb38FhJ;xc?~Ul6>@bAi3=s zw2cC^rWI5S;?n_%&&N-M1}+5gcKTn_^xsAQ7p)&z+z)puS8V0~GawmBo{M{qb}q?^ z8#h4zJ93dvl|0ga`wBvF-k?|D9e4#1Tz$n7NV*0&l#1a6XyQJ2kF{m@b(T6ci&8-mlc%%% z{>G<}L0?y?s}g-0u6(I0`V7AKd{#f7!w2_shZrH8LdR3j>Yy1O?gyP)cB&h<6Lh?8 zz3V~z;Lb)**3G|ymU9>SH)y6#? z(neeINsk^|+I<5lAy%HzML5{x!UeLyE|7cGT^UclfD*&4An3m|o|OLwCG&=!>L1smXguV@e3ylT4O~ zZF^Eqd(xZHO>){z&@0hR^4d*|6l!5Y2m#B4fihvBOo#^&P^dYBcA9hMdu1sI6#2Z9 z|EeQ=2V=~a7zfkM3#bTizAlb}J=4U6n~T|s)@~!Z&H2dXtDvi=^UaPauc8~|L4xww z6*p0yfh-uRxzD{i%?;|ii-E+hk6}gQa%72jr(ko3gbgvn#Az@nhS6n!qdprb+Dh0w zv-&er1urT4t#0FPpgX&TQ??Kn+xx)xLVB=K9EwkLJXHN~8`0%jXT~`dc;3f8HFFVS z$-5wxSvonhWG>M?$gi{7hph)+h70d6Vr?)&2Ac{1eFdQU53yY2&X?=W<)X4=!7|jQ zIp&NXuIobodYPB}56a9sr#;4c_NVKajV^5|^aqbzQCrWw)@wZ}ug*f5SfDI<^>TbZ z#6(%lui;T>OI;}PE`!^xm&3=w*y@StSVlB8RB0&pF|Yxn7^rBIl)Y_C$Gb3PzQ{A1 z(0m2|>$t5ogcnaLm@fA1 z^o_1ya&jb}R4{G2k5fHC&!3`iYz2nWH>@Mv$=J3_D2HA}5SCE?1BK5%11Z{a=&_bV z-5JJdya1`9ud%9@re8NSy`w!%pP)27usux~n$Yx0P^bqWk|) zTdhm%m)PMed-ObK9qRc_)YF|I%ou!&ap$piy5@<+p{D~j>gYDsm|GFHF&I-@eYA!B zx(fsKw~$Xi=*667K8bl%d@JHnLG<;&PkbNzok4w>R~yE|QkB*Hx%k%rdF9=Dkn7td zvK!-da!gjXoLgLNcuoY?2{@R-s3!J-suFXMed#kUHjXhReTI^lM29~b4nXL z{rHl$(3iD^-qIF&M_cHp3H@6uvHbXJZR0-NHZE>wZj-}kTj+spp{KTmUeXr&g0|2( zLNSV^o7|7@Y74!uE%c{^E?^oz&ONbR-G&KW6n9M<=&(%Ci(ai0j61m656WI;_Z$ZH zmxHA{y;`pq;9WU{$)jrFcfnt|6ia5R`+(WVl3hV1FB}|w$Fk&{>j&W+@<9|~yRU)m zGGIF#SSQOD+2G z<4VRYog&0C8y+0fC~}@FkdaHypP2FE&h+>}nW{LAI#1fF;|A{`-w*xZJM0HB%KG2y z2UoRe5kGzv7MT}wJZrhQO?S72k_wCC9!Kb+p7b8(un0QSHty@&LLY34ga4#IO-*-c^15xU@yofBaq3yo}Gd7p;*u&}}k zt+aL*NUzS^SZEW#q^!ZYbc)>A{y~H(YoRNLxmF(kx6( z4Nfm{W41I*whC3fHRE6=&7)SOxbw+PnYtA2#gc1HTj7+Xm{@vXC#;j^Sc39PYu`D5EKc&76nlD3+mNazpVgNH~YfS}1tB()wIs z%^SfgFi43?mx=E{M5uv@0FoJVCnZ&iYTf)YJA7xU2t}o6^y7ktoTMRG>p-Jf6~;N@0L;ih87>g6 z#s3ZPh%y5h;eW>e9e~3yoxrJ6Y=QrK!Kx)Jm@aJ`4Nc|BTa% zYp__Ku;F4(PxH6o>QXi}G7497Wb6ypaLp1nCBvT&p^t@cM9%^z6UchXb2JLfd$j|| z)vNo-)k{iL1auhvAX)_>BM2F0NFE;z!OEoNavG&mTwZttE-xsT=ajt)(pLo=;qC-Q z8!Y2+-ugHWOJwPpjGP((3o#o5^}3!cj2+~@CetT&*8NTyGCh?uOnmJkCZ?Gy;nn3J zB2ITWN2GX?y!u1@KO68fQgHGrOl_+F-k##skGJ6yKmKuBXyGY~dFp>-COOqM-=o?> z&uk0b))snuTj)31Kocz!*Y!NAmf4%qGO_3X1~4rkGBNs5s~&0J9y+*V?DP&ZFZzGD z4gML~@;{}vAG;?m=A?hE4J!X!8*snKGV~Apm*SPcA|;?;r2GigIO=&;zxgHNK8Uz( z_$oAYAzRC3!yPbg_h3?s)Tx0!HM;APdVgns7dAsBGXq+UWM)7OPSny#+(;oBE3EF! zv=bbZy5}v|_zk5clmR~uAYh_512H{w6a6vCAqaY`Uw@z!a8{lv`Iwx%8kMYm40YD|^9j!IMLOK^T-?_oKfVuIWF$`S9OT}t&Tq+`7Q@t`d zkWW>&31Z4Fe=w!-?gx#xGPa3&?=5U~qLQt9{Z*X1cn!$VRSlA=-H}x77h1LY4>hb0 zXC#EcoW*THTbMmkM+Gs34@jjoq|9Mse;XwkB;@uYAc5P9fJCl|gdtYd`JH_O3K4D5rN(ApiY8{| ziq(-IJ_D5#&Zz}R0wvN02JvY&sQxV(pSa)VT;%f*^1vItHv!mK-&(<>L<_%MX10any+Y&`ff5G_X!f-v#%2#kIpgd((}&{Gm9 z_cH_z)>b+0mf{N#VD-Q&;9jVM`SEMoLhoz~{YG2puiHYuiDp<_TH)k&(0$uNk8caT zpe^)eZJ}>%3%$E7^cQWR^KGHSPem;jSzf2wLW5Gf8Xv{Di>#^Vw}Hmn02CG~dTU<{ zz5*8wuHFwO)!N>HveI3Mp{zvdEY?% zwK*3~NphxuK+qgygG3HwU`h=rFo3C{>01!YW=hAsFJ!xXd;oGvDVrJLyOgCS6O-lg z@reuLk>~QdSf1#rdD-+L*Vq(Y1#N<3oT{(osry@JGOaxL`wV01DT37^` zGJX!BCI|9kKHj-79vQL)W%6PRfNa>36FCs}zJ)oF1@rOS7lO%yE%}lC^6}jZ?_ zb#A&1dj7*;fwLil3gB1%(T0W1a?Xz+lg7GVf!XZ{q@G?ui(}I9At(f=r1d^49FVpS zoafd^dbQU1j0Q`Q3C7)8i3^9lO-ybxh_98H*(7E*iJ8Soa1)cACB9W+`bkVbiHVyU zhfPfOl=!0(Ge}|vNz4Fe$W2Uk6Iq%=N2yfwtPL&$Ok8uhr6wi^Nm&D%!#Fu?V&(@2 zt!pl~%*4c$u`H_ubEQHOvoL7W6y|cHCT6izEX(*YS1Kl@7n9P9bGfm+iOSteF{fU0 zrOqVN&cPv5d?%vf!ZNgD2e&en#iB5WJxwDfLRV6L7orwT)b3JuS&VOUrS2rx?j+Z4 zMC~+Dmz0*2x^3L|5W0F{+iuCKonB{Do#Dl0S~mND`6ogXv;PbGgLZWI$@i*EbW8 z&Ugostt~Os(3=+}973J@PA&xhf0O)AnQHl;*Ap)6VZK~bxR`VH#r1>t(lXp)C+BsA zi*w+$g^QuQz;H2i748#U%;~(#a541Kw$SU^LV2~};vC+*cRMIAIb0lM}T4RJ9gc`xE(C~rty1Pz~rF zh9y7zg}5J--|h1IHTgY5e%~p-AC}+G%kRbV`)B#}ajwG;`{Z|wzvQ{B^OvmS{Diu~ zQmZTICCtH*mgGnK( z1GlBjVRjtf#R70Yr%91bQc#63DMW?P6n-VXTT=Kog+FA6eX0^Bg{TsmBEXJ+Nr9K9 z6sus!4*ygsObSscG)1lw|5#GswPQ^IWkz~0)D5T>CWWXL+*Uxa|IFle+XG zNsmd=SL(A#`fOeLY?8jDE`3N+GD-SN{WeK|CC;5mYWLeD{YhQ=k)+oo87K|dBm=f7 z2W*moA*-4WAW5G|QZALP!dbS}FWV&Lr21tf={HF#rHV~bvDL5GB$cH46(kujNd`-U zHp!r^{-8}Vm{fldNy;Y45NK~S;h{=ADb*jcNrsZ@4oj39{>E=x;GZIY$7E=z5arAb|uB8inQ%Sy{^l4Z6o z%WRTmNnMs9i4`xSrBSO~k6KP1wMj;kx{OlE=49#7mF!bNi-m(m5MSOFdLy9=6wSb@ z-WUfm&Re1X{FJ%}=Uztl*zpi_vV}#+@s0T7$T0c=f{Q`phxouGaRhfVR5#?)y9;nV zyV+d;`$S)^+?TJ0rCce$o2GExhu&)p7?kg@EXYR3Jhq-gvX*k#dVa6TIbXjm7ySrD z!Gwa6I1eV?v|IiRcze7my*h=9*mqk4HI8C*2RUBt%uMaw!>fORY3&9MS$X9C6=WlJ zyo)%OMn3xTi~JUi{V+T$G0hOk47S)=qfe$*&xqY8ld}74ZpP;jgrLOk0}Cn_8KO(^ zGl`63V@JFl35@AP_8U~Q@ht#Bjb`MO;RhQKWF0lLW)9QE1^bB1V!qi!WVUO-IxTDU zIOA{?3l_5rvd7447Cf^tcW#0vvgT?daNj0|@YG^vR(5TcGD9u#9+r5+E%CTxvy^Gw zhQ$4vrOeWnc-*a7$}DSvaj#}6Gui^#tQE4lF3?>i=u%~~?M=IJrh-jgxNo4K zvmWW%nL+Fk6IP^Gja>DQrY&51#$=uBS1`AY<2Zn`+?^i|55`9OQnv9k0JrmafYTv1 zu&bS8zGh8dFLpLQ=!_i*(-`H#XF;o9P%caq);Wt-QF=N9o~qTS~Ea`U~F-%LHH z$98*ggq}-~H9YsrZ*&h>qE<_JdFH}icZ zzj`;5FIQk-T^4))z1l%u^b=5YTR+4%o@uM@q9D z0`mUb95>jPcA;LsB7f*uhg&?7^;#?gQOe@=jp~q}%SSHC!RL3}+#$RDF+A}=>Q4Yz zy(fo5(95k69*>GtD|`zihoS5R6u83lSqwNB@Jy2L|AG?vTfPk3TP_1jP zM>*?u=-TUYF}3Y{7Fv(zUc#4>7{HtJHrUCW{{;SSNJc4)*x8%f@i=?i1CnF*W~MoL zYwp8Tq{Y(vFrz{I+sREPu{LtLX3T;zU2``vW;U@)W)rjELQ5~#!Z1cAwnpC;URr4B ztx2AN#j+G6rDIx#b6{c})UiJ61ds;5mO8T|6vp~(rBzpCr3FVj6SEVsWFE%48@-ak zN@Jt$X6}-jvfAn7!YQkzfzFB3Q&u~jTsURb6qvFeCRI&MSuMpEPFXbtrmVb?4XT6H z5Q9Y7UpQq|dSl9}6Y12H)mrTrPFXbt2&EHgODRlQZOs=>Sv5t$>aD3MtF8IMDXXTy zlvQhDw3MA+KMcivT71cS+ypqDXXo?qA9B;>9&e~YRYP>vS`Yx zy#!NMrEqG>Y7tvBWz{5@vMOS!DXT?n(Uet_V9Kh-iqw?VBDQGCs!1?q)k%J8%4&(c zXv(U64pUaGOKQq$>#}Ids!1?q)w-mnthO$TrmUI-Q&u$xrKYU5E{mqDngmnUW2LL4 zrmVIun6jpX0_EV*kb@UPCRAkeF798GV=QxSycKzf<;M5I4?$d*#eK7ke+5EyabK2@ zqI;l>h+e@waY#?{)Z;bGlN(oXqnm71nL`(*PCe&icQC)M43F_^kY!SqEH~L)ndj3U zbDrEKfjl|N_BZo;51`R~#8TF$Bx^U26AKC6Q2GRKeIw@)@pj`+0@YOB><>*`19_JN zJCMv=DE=~H>#{gjcFsjqnY{$@GZVL0o07HieYsX{w_W@K!Maus!OU|>*n*FdbkeIG zDQo42aIO3xKe`-)UwKuh9}jM}d7FvX-T;`@c8Y6RKsV22Y9_6`3qUR^mJe;WFOf38`_y`-G*yn^^x%7UKml1vL7g4BTv|C%D zgSw04!xrl1qzQ9a|H{feY2@W5-`$IN)ROBCoYP71dym5 z0I=--tF~s>+{(r5B*ug39Gc;8vf(#F&*js!EV~q#qbs>I=}Wn(WUs=JXlGu!bu#`7 zWCHwgZy{_q4)!#bMiTk`Su_ZF{-^RClwB>9wcL6#=MTrv#-@|GICs7kO6&l4NypS# zaGTyt>A_cbwcCS9k=S%;Is9wRb^H`Up>~#f%F)+>XnZ+hO8d*fjrdx4Wgb*JuqE0Y z;PKB(h$~BsvcJyc@Now1VKx#({oT$#$oKsPyj7afLmw=^KJMYg6~Qq6BTFNKh3aYO zvF{hxlTGBW0KLv?)$|3gHY7r|&l?P+OA68=wQy`slbYUfPr8Y)Bi9NIJO4P$Ip{KN zBCbi|E;wD2WIVej$tGIVMWw+OL)-6#lT(rlOb&j04=A`e2R8U(=%bvHT$C>d&xHmK zUrswUpCGWOisY2!q8$F2Q7Hp{4uzK&ON0`Jd8b$`(rcV9e{N1anRo=>m0n>-M`W)jl)dBHhe805G9bY znNW|`*Ic|(f#*xEL`Wd&fSKI~@%c};Q~kqmk5=MY8^rHQJO>0a^k|xuL41D_QmwAS z%S7)4I-K28qT?IBSHJzyod)o3^L-b;oZdqg=w8l05>MU7nW=5Y#C}5n`^1V3nP7MA zIQ+<6g<9}BJAOlV_%C3fdWq2;<^j+rO{1*xLzq6H*kfOD$t0{4mO>FYC_KhjgK55|8?F-@fV<;U_>U zn+j=~`rt<`bPX)NSS0#7()4A)Gm-=gLha6u>wT|%$We5^am;_BJAptAYjQFy!>_81 zoO;M;yjmZOXJ|o6#6f#_83{OI1Iz?2l-=-?z?FQihY5~gzA&h2PB11;9+TF>2kp z*o~~OUiRd0y%r($T0=!77xy;f^lF^=_n7BMl9_&zt-^t z1>;o&W^B-*j-BcnZnz3L)N!Jpc>^2XLm-)Za`}Kb0=qwJ9>k$U^ zdIbBzd@9-YOl8wo@!Fi%_!YX&RA$ilzD8d^`h6g8J#Gg6SgDJ!#^cUhjNkq-ytp_& zupjIr{@5WQ&hz@Smv99LckJ^UZN;(^Uk5!LViXteNy^MDlk(Z4O!#>e;D!%FT+Uuf z;4$v_P7rDkZ$DJR2U+^*3K{G6GCO{bcx44IkFNs#8&5&L+Yi$yOOf9XnPi>%hrRHj zly}q%e?YIaN7kv}OCHgCWQuqxb`C5Sk|zA=Bge*_5Z4WF ze8roKJKQ<`!Xraprpgjqd_{YeE4aj-oIP%8<{gSuzy3x(vvg6?he52ATI(V7jQn5 zAHK)>)$o_#iCp+|k)*?0BrTq1j@^E3@7%D| zx;(5rup}EUL5a^wCCN2eB!}Cw4Y%ZI4mIZ@GtNs>nW8g23jec7HzFmAjkz; zDcq>b(AH0r02U$NqKGF66TCcUC-Cxsm!QIFDoeqrEgvltnxgoI+W|EU7nGA| zewv)^JWIS3@ic;1jd@>U#`z?0$^1(+8tWI_TB&1vB+^*_24$2sq-h~qcV=s6X6bY` zH?Z~ePPe=@#A6QylSpJ7Y$>TKXvkvX9`A*}M#79&#%xyjMi$U%}Imm^)r8{_t<~9^>xVt-0i(MG+ytyF!J>nNYt9mj1H08JSQNMf*q~pk?{E;?#I?`gIVb*kf zIS;Yp^(aEGRFfqgSX&1DnCcM>CMkGgEqTa~e~nPp1925PyLspWLpRLew-c)w9`Qxp zZ%I`R-Z!XMNC8S=`&yGy@V_zbhb+oEw*lWEh41f)Rc1*>8gYw^voh;6xXiE(vMIUa z3PgJ~MKoup#{*sR!8K?^O{cZ(&hCQx-4QhqKAEl}(g!H{-QZ=pVtku^WjnS@_e z?vP&#{*K#`+laowzSQ4=)jR9!_IJEF@!?p$8J0yS9tB>a6KQ^STy9l}Kungs8*woS z$JoZ+(=1!X3wJD)rN6_TR*wD)ou<&h5xoNLIG4FaSM?}tuHA8K6P0D~o9(|`Kpn&O zUtca4tL*N=?arAz!b9qUyhZYN_;OP&^SBLp=w#H-L6oh2QxN?(f(@Q$1J6A09CtE~ zqpFOKz*f`7Iq!yWD)(0hyIYS>DPH=_4r4 zkp~8LQliIaqG@+hmd9J^q8N{3x+uQmE9j!+j$cL>Ss=cduAAw)oi2*m_+4~SD#mxw zMS&Q9kuJ)@_+YDz(tzI4-rPn#1GR&c8GsZ7g-_hxfCw4!PIaAmKqbR}_|Sa*6z&t>?aur=67}n2*tq6y3i*`7KZ4q`4A5ZJXI~3C6ofzCDM|i> zNU~(0SNQX-1gzKc*^OA9xQG!Qw|EL*V+>h9Kxh0yIQ2;oY)O!ky7H#{eI-z@^;{GP z#{M&k*zV@zpCOB}TJ#rUNnG3+{!ZQDxnUO?FFYJgFS}*KZg~4cVw&SxHS52gmGKlymkEDIvmdg3FM2=G#G~$!^s$eH&10ADnI40gRxyN?SmJ3fCP-X zjXo5MHx%E+f{$_Izo0Uqi$vR&b94q|zs5qyQxXVRUgFFo5ZFq1xqnQE7kVuHR5?@` zX5hZ;R0QvovQWt^Ls5dfHy~y5Dg>q`#H2xh z?{~w~Sc0FZ2S1wWF&Y|`2!R!OWa@ag(F@Nk%Xh;*{OY+l?n~$*@0)qHTyhYuK}%+8 zqgc|~f!?K}4txvag37sYH6pm#@IZdLv*7{q&4laZ>xYNPHwX{Icj`qR9)3T95HC6i zA3W*$IDEoM@LKe2cH86KnLjv?%n}?;kUc%T!HG$z{MmW$jt_#f>ZSg9|M5uQI2u1Y z2@?2@yKLZ53_Om31PMG`0WU~B^BzFh8D_-+zl9TZqC#s;s5~GRINKrcgNbZ5UPh!KJml=bKdF<9Q4HiK4LUW$8iu21*N} zhJ68#lrD&8QGp0rVJemv4ruOq(TPu*3gYvUSi?52Oju^Df`?bcuwc-^XqPUImJKT= zT6PzYB{SM-i=+ABpoyjj%1^RHKMz#qC71mw;P=OkC_?ymC$%fP7NsCO677U5Jxjq! zQ;SK$Jr~Q+N=HJE%=DjxX93u(c$Z)ZMW0kD?8#1+3wzKR5J%V0J*ThiWIq8fIQK*3 z+_T+q7_q|<{J4#!_?;Cvd4_ykqFqU%deJh*(T?kdqwshUd9b!OE=KA@06qS04U&tE z6a*f8NO-Ut`N8a;OH*z=-1^O$XD~kd}Iu#*lh^vWvXj6><=ulS6~Vs zh>I#dIG#2FwYLT*ZbrBik)k@>iVJpQWgbWK?}fau0{N9AFlQlJg`b6NVffs4#wRtOQ{yXH-PMR2zmqNskYpf^?NB&-stXM) zGmGa1GH!Nu13nx=2<4YxehpmT85UW;0XX8`%91Fmpf#`g@?pB-sxj`@`XD};+GIvf z@(uDNU(t`YBT5i0gN*}=#?5nk-`CfAaYT+0qAw$94*M)Tv61$p47UMo8g`KgjEFHK z6K(>h7WcrQiTCl=0`@>)&RYa{=&rfniT2?Erbm+Vkp$0B;1rf0O_PcT;HT*=M~@@$ z2gvM^L&|*dRg6bad6A#?UB$E94$lkzF`lqQ^@ogy%sA$40nU|wMA!W@eHk84!TS=W z>_W<#11cpf+pqqf-}oa)*V#A-h<9O~b`8GRE1M9SQzew^$x)@jg>`_n|4&GcbD?NY z-qp@QYchaw<~vZ*kq<3(3p>7>akTT{@NFi- zt`WY(Gu~%FMkp+3 zSR+)xy{X)6V&sM*^?9|#7+TXV`p9J*y10zWbO8cMGOqUI2B#lOksmZg;+<};w-z$t zOb-ir3nQVlD*CE=T9$GUGrVGOC5PbdU zQ2e4w$^O6I28lzB0}eiY&N(aYTa zPnUVFspr31W`DSYWpV;;oJ|Ty2xw$!uovlm7zp<$bf?TF-47?Vh3@O=0u7G9*N=|G z@1xP6KfJ1mk2st7i1(kwM}~)g0iVOfA|c%M0wP3x_!yLZ3Hjh^8XU1Mk!HS3!hEn~ zT!F)*WnjLP(5s!)4M`>?--q+YPK(8sib`t7sWWv-lYBG zLA(N{$?0;lTJD%li)1YDC{kVMe>7bv`WSru=n433(tnvVu)3A+`@>x7(<6Ibyj zxb8#R9t*-e3SV>RBCf}gnnG8++9z~Xn%+QikC*&p7}#73MpB%>iIzJ4tD z$T0aY;IkwC5H!`D9J=B!(Z#_jycD?YKI zSQ^K0_uPcVLyHim4T5JurSl;sjQP5UEU8&g75_J@5NJaJjFUrWN(fC1paT-Q3a^# z|M$v1sZH5gdGm9B()J&b7Dw!rrunh4b2e`(5B2$HfCqb$GzCndP!6Y^e~o2jhE2&qhp4u*t4lSL9vqzVI!?TTC&4^tZ=LqV3NmGh>um|&jIhag zeE|lAS&Cbwx~E&GC{MWm_9T4ym(D^W;%z%kfoiPka=ov~0-%cWBr7dLMRWp`tyvIl z3JyO?6oG%%fo|zkAFWSTGr}XLpNgnnfx+@R*baj02BshKa1Tl%{I}q)|E6c7l-j8v z=7os0#6S?reL zE4zs2&tjiP26f|S_CM(^H=c&{v%jW$Mq*0fft@y%rk=&VF`KY)DpOz^Tmm?a0Ad3> zC9V(&?*^{nC-5Ui%B`C5#jAe=EMTSc8<%14^XxAXpJNYmmQ@6C03ZPvL6~8*0a=&l z-KotfzdOUiqm2mKz#OnwQgZr@%}E@f1$FjM;afwy^`mc_Ja&SF6J4-(zC;G{&jQgJ zh%e=xj!X-C3IQJf3SWxg2mIQ;Fz|f}aU!f|`Qtx@ggrg_0vcdXank4O?S4OPz_kgV z&qe!n=s^mE$yO>CfnH)k`Z9w3Iz$lZQz)ZIsfVD`m%(GLf{y_G=nNpTr2E!yp{|btJIzNvrh(Kc^1{g zzX;QJladDii5C1HsYpR9(0ZBrbi3i_&|Gye!2|dj`=9F^_rxY1oD)GMDw%B0K~120 zt>{h{gIk%xFCl|s(6wizb08OC&?tll!$z5oH9t&4na+tuo#Ot^$T++lmp7vfnBYx& zrg*X77ey#^Ru96n*C?SYUvu+QN=a;sgR!ho!!~vb4scl}7k1N??ahXpfasQu*b=t# zGvPSagH#7o-gpwqTV;5o*^CKqRA$-hs=euMql|AL(s5TlnC6s(c84EfX_s+e$8}bt zoW{ANnqXOk_LSxw;F^&ZtsY~*n-LdQ!qV8d1rJ{^wo ziV#*0_N5e};Ic%vY&F7Z?G5@q z^!u*^J4m=4qz{o%*(P4xYZYCBGA4uifZO;aA{G@M@alj~9N zo-C6x@7UvQA90=60e%q6?2?qt))jAbIr3>>?H?tP-qCa5jGl`hSnijgn{S7c)Asuz zap0YSxBgQ+37(xV!;&~+Zjy?wU<$w5-Rp<5aCYW|3h?bh)8GJH}3;=qn+>|THQl5|Q!vQFm_;UIk}YUWPCDo)4eYI+!nG zB57)E&Ij)A)K9f_%R6UXpz^_WwmT!QQaJX%&`vPbTmi$LTdsipfuHQ`AMw%t3)-Om zYsteij3Z~cp{@7{Z@J+MfK2p4{LKCdF{;7X`QeM;(*)65uy#`ag*(GMqnDuTdeMs| zlx2lK2TlC`=vH(O6aPFQR>zpn(xOizv0IjWTZDDOi`~w)tCSZ#@IS{o{@Kz{ju*YO zwX{?{9=+VukrZriCUvk4Iy3bmcLtk&GttYDZFv>QBEDVl&9Fx1Za3Fi2|U&b-BQuV zQ_&0l4C&P*-!yiKlrhK7IG9y92P5shCqj71Dz-br!lPH9m|X(A8m_{SW)iI ze-QCZWv&Nkl3RPEqV##u_YsUudjAFw=I)eD>yghPs1sxpHiT!QS0X4;bOq!_uL4X@ z9Nhrdwgb?T1zZD=(s*;pIKBD+D}y}lfiSvODsQW5nY`Z!7K?ts+9jp9(W_BRTCYN- zt&DrzK{kIEBgsF`Kgl(E&7w?go>Mi=b9v*nh_jP)z#KvQBK4^KQO=>lGj7(&KzYJB z#pj~SY}*D(bh=j%nhQc$hN@Vznf{)(QTuc=k1^e0QXY1>r^~xc%Oe!!!Dh3#yoKqc zyvL9ZI~&nyGOgu37j#Gc2t|2Nn-`b2FrAe5Na>C@hEP!(!oQIjVGaEqdDff7>4Z$m z3qxCmeLWS5zAjMA-y|~kk%` z+c8MaQUv4{p55-swH(`q*%dl176!u%=DxBH+OHYPJK#8KoQ-yaU;B#H(|U{H>*&n& z=4Nt0UvDm0UkZ$0*0^zk{Q^{DQddviQZD>ED8Fa8FYnHfrR0*261FL$IOrqAJr1(H z5!u#n!H8I#FCU)_X>JIMA+}!k74|@OCbbTv);=DAQ`m(l?tzlXES{0)?I43oI)=x= zR<3iebN&Gxw4`F(-{Sn}gbsiy+dQ!^9g+06+Hj*Eg;Bco;Oow?yWw<19-gVK%Cj}Y zyY7ygfNyj&e$WY-*97uBGU-dT5w9`8%(TYHr3*eksxYsz_FH|U?(s;|IF2f#`T1X&6jx=c{6{$tUxS@trNP1_1g92v9s>WN6Q0$Z)H@B!EA zpP9HB=K$Anjv}7nU(qNS${VjoB|6>E1!{&V^;##KZ};?h#!Lo0qL(-6k>bAW8tfP4 z0dH9P{B>9ez=aI*qF8i0CUEi7E;E~LYDXbX4qQ0tg1@pTLTd>qESP+8v;%?0rw8t% z*Q1&^880ElHQ2MtMJo)eQ4z|Ejeoh*^%YzzQIYbHnxgr!6to_kz#2vjysdns& zJO}*Oiz~pTezWX6y?LD2_dt``yz%wsXXunR+c;Z*6L*V8ZvZx>z@S`2$s{OGGpw7z zA#GEf;8xkVVxuWtUTBoE`tU-7_R~YrxfG9Lsh}tnTTp<6y~pk6{Ia`08(hSJ?pguvM^VqYH=u zyNv1(FiY@yVYAM!TMxpmU>EEy=+I@>VKU+exR_cr7S6yM#$p2gGE-IUV?7oA^?xYa@sA?X%1@TkCdb z=xDqcjfNSr`d~tDycHcC!o(js<{l?os-|c>G zLm|kCo$PzE2jWdgb1ds88I_|qpm6FT(hxIj`SNhMKdbJFZ|J(phWlnoLiLxg({BYw zrZ=c_@Vc>{%^OU`C!$-BW3WLow%JNGNkpvSF>izyzzJ-J6)&)uaM^ZGcW2lQYhESS zz=3`oy*Bnl$9c_bobDCqhg{EiJL-TT5i*8(Vmfg%KEtTwR&CP*d%`YcGJYl2u9R%p z0RLQ5;7Tv3qFq^b7jC-xVElvTJ7&I1`9&9F^62%Tf8u!qJtun+P8o|k=z+Zjo1sZ$F(bd7J5&mU%AGF{X4?YLnU6?eG}ldHOP@6+IMG3iZsBc zlep-plF41na8Ci3(uD^C-T7hESH}s*x#k+DKsy`d_fe+)*mI9)c3o2Q;MNOU5SK;O z?MH8=TRq_kNaa?-b@)upX~j*WycHym_G7TnR11j484+c2j}uqXq1=IB;Q;`L&w<#T z9cJys&wVav!E!i^!02Xh>7Ief`Qbu%5IamSI*i}I6&VyXOjPe_253(j|GWD)rwB|n zPgbzpScczOHnK&FMW^ii(O&Q)4Wmd)FKj|fZi+%so9vZ~Z$gufM6bj!J^Ndn3c_LQ z7qBwqUX3zCaDBwRf}oMIN?J^&DqIhceF27|!y=%iKNv{Fl4dJN$C1q`mbXH;l}45p z$Dq|@4hBh&(gdRot1i^UmCX@4oh^evShKQ2RltwiAvgqhU1v>H# ztS#MQ56AsfruAHeGNdc!tYweZ*80v=2GD+gZ#K==ntT@4gRTzxr~!~3Vqt{7!I zrmvl6d07Zefq{AW|1$0_&f$hfN~X&|#<6js-CzsDR?-YhT-+IQP;{FRicA_r6Uc&n zit7gat5zM4YX#z7?(~kEvFH~7UFAze`1le*}Mc1sGHD*1h9g(|a=I z%)GxIwQRf}X(8z;6)Jvz;|_R>Zg?1iI&(lcLNnn2yZJ!2BUr0 z2*1LWaB@ISG9e>XmSbfz@eR^2n(||(Om{|roJdn1pMX}we`$Ot%EtB$O}mB^%KC#_ zHx>MG%>5m8jElp>!7*4L8|(q=Fu{n3c;2wERn{M-$1qu+Nrc04OfdOBPW z+h<@h^k>JBi#x;LOoZ7Mb_ik2$7+ZC!K_cU#11HA5EY|WzBH3;|Zjx zU%wc`6%&sA_8iFo`;|nXOZrpLWQp%-X0%NYkrOl8u8gGc4W^|f*-a>uJ6gnZ7d()G zRtER=Io@Xt9dTAs=H=X9&2{akuzzPza~Z}8<*h|;sM5F-Vg`%t7edb|R+s1e;b7cB zoUJkmQ0EqzF=32mBSJz_Ne%K_Ic3_q@*V?L-8FrY^qwVsnfNb|jJV|-_i}QoV)lGg zoF{HpJ3cpPZo1f6f)X)r$vB4sPwK_sk`WZ5eIXU=^&w%w6>yU)7u`%N$sclJS)J zjhg&mkHiKye|Pv~&|D3ZIFI#9H#~`q#(H`VFm+DiII~dU1#7jclQ;oQDKX2s*;2_s zv!^TsV240!j13C`8y5mjT?jZ007C_7G*hB-baWi{#u$jvJ+Un)9UqjW5Y=JCXm*-b z>;lqm%(rN3B3kDO+;XF&mwsY2OHDZG*K+>&??Kz?eLZi61rBb!FM1Nv1Y_gp)RuS? z(G%gpWKw`0+W0K^c_+7?Svr|?PRm^@W=UwB;qAd7@7vjro%`&ym(G1|Zi?Hn%A?3z z?teTBc=FFwXAX6(7vslYjvc+K9ceXF9U_t{S0Q{JL4ashN(>e3n-?*O8=h!+%jcO4 z=c&S5q_^I6F?{-nwOvRfh=oBp3!B7cOg1PpWF-0!yP84*q@-%^508~^G?Gt;n}w`u za$x0fh^rXSEU32fOiSx>TM-e`>S4+tz9E^|Oya(pRjUz!!va=gJ_C7lQW^Up%9#^O z{_a%2um{#MC-UGmUI|#W4=k_?(JN&pDjRWna^;V-E9R#jUKGbvf$fG%ABCz|q%wl7 zyefb#@HkMqLV3k;{U~Yeq`jNd;IX09EW}GDC}g=b`kRT<>})m=Cg z+L4zlr}S90hN6+0!I<`8<=Z@8s7nNRXabv(4}T`+rdn}7`T($|H5tZMtlNYeS!LLE z_C_tmhA9pV&r=uJIJ5g8%x!!esLf6=8qP$8XCfWeFZZP%$>n>DJHx0EmLPmtB&6LZ zfg~QR)H8o#5naG+qNn3`l4FGH!1kf`oEa7seFBBbWftNsN1ucrdhj_&;71<=f4U~% zQwSiFa7@EqCzFCA5h7aj#E{?PWew*5nwgoxXjJdk zPEnc0LR)!dNujlV1I1yQUnP@cv=2K-WxivUN!?7A3(#ADIj%V}=OyyBb$)GSAZv`Z zFdu&hq&BTE9Dk2}u%fw`tGdN9(Ur|h+n8G9*Bv5#jNFoqJ_{Oykd#+XK-KuC&7&mb z;(85}O)oxsoTjO70YmXCuB!&UiY#G`ZCYNm&i!=Tj_@~5hC3gEl)-zBN|2%lQiJia zFf!ha0qA8|99>!TaG~gs0E=MV;kk&3ww5}wr__7H3{XW=9*h{e;Q{?3 zzgsoZ9{^8xhWJ51z5=+)G3kJUsj&LZ7}0XI3EV+jUNz?8)A$^kYZlvG(9iiBeIEX0 zP^n=QTgc_JQxc88%G~aZLWp!S=qwDb*bt2^2E5r?l9Ot>Sno9!JtjAEMX5yeY*e8y zS6$Yd3tvPhx-R2j1H_+E)2)MInnU1=5m>(;n;UZTv{{(_dKhyVTT89N#Xhckg`jYs zrF#}AH-7~gflA}X33Y@poSuMgWYh;S5jS$L$Wt6>(C?#?cKg>y&0T? z>yK1ccjc3v3lWBI7+(2oRJ%0Y zX?%&i4*8w{?j~Rb0My?-=T_7k^BTyOY;8I#o7hh9<)}gJx%y2#YiG*%ZpL3Gf^+lb~|aS%~@Wx&CU4XvjIUzNf*mw>LA-K(5;^weNTOyKCu%j66maWcEl2v}}c`t}~1;=lAXm(?$P*WGIZRf_Hx5fr=;N7sfm1tY#Y(y?6_0@n(>zGdHk&nTLxg zUj zjJ(Z+%eJzm+$?#jl$E;*!4!Hg;8P%nF#a zV?sQ=jMW2`S^S|k(zX--$>N`EQ{ILhQyt8n5Kpd5(>oK9{W&(0OjU>@Aq#Fj)OEx= z(bi|9ts7qlYCBhhKn>13c22{!gWm9J&^g5Xi^mq^8)3%fM>A};Zy-@u^@4o#O*n9J zU=~1(Ir4iY(_M=n+;;SQe4=k54$d6!g*N#%oSi`~dLiI=XHb4$$~ZUTXA%*lOMauZ zOX9{JU{=3A{iwP*@3F;M{%X6w{a`14huY$ARKN$RM*^`mkA2hJc9Nh2p4B{_qy}t zQxbi_ub09;^b@5W|Z5mR5VyiskpF)2(>Sjm)9UKl`BOj zoNku!pMal-GN8EaAm2>%1vuOAlK+prHvy2dDD(e2eY}0mOr~ch-E$<w65F+B@u^#KOtL~wquIG9nyI!kCS6y#` z^;Q)ApYK!k_Pjj_1SLQH?eD*x)YMb;K6O3y)b-R;RS+>=c{#jbY|9 z_O7`6|7kB^;pLu?c4;&`0l^PAsxeE9D8!Z)eC=0l)|AL!!^IV!IuN*3s^l9jBy z21^t0BgLuG5;Fjgqe2e@@K_gm7$BKx38Fp~x^pM%!p+1(XRZ{2+P6puoUF!&^L!nK&N}r{%yR)jngCfas=2NAnAjT{HHYWMw7~&0# zBQWw)#cd7VTVZCd>Ww$1h4Noy$u#@@zdm8bdNBpcUp1_P-vho&Lsmfbrl`5)XwVDme zpIVIpcpM#i7=Xtyp@#u@92Nvq#A)H6JD{$p_SL>*w)SI6TxP4KdL? z&ILO~){5pyvQm{#D3|fd$FZ7^lvlj+LBSghZpcED+9g7kg`8+`!TYwfkXo04gHmp;$yKnU$yWvs--Ch{4PFz z2xWD1#1ru3iq6_Lre=CqGu}9HiBIJVOv-#m@ESvdN`bg3Ah@pfNpM{7L}_mN zHb*7mQ6MbQE^(dry{`5Nuy2A83LJWxE_0Rqy8T&~npI}yUDTNF3Z-{JxpmYC3(JeX zeoWs*ICS3WBgHP`^nm|J7`=^DxdngMhjmIs!I1zIE1x7Ka;F%`omuMh)!(8a7Rx>{)}ygW zy!;4x@mrcJ3+y-t?R6bOX^W4gsTjl+qxN$#jJUDHetN>rMk`K7gNbwEm3N8;Tu!g7 zte6qy#ERriR&`}NCELHcnyYEh*TERoM(v|GCO+*p;$6F}qP3+@I`94wmA=olLPvc( zKPuvrCr5)LVjIqiCD!2FOi5IIE^n+@EK6g1T)i>94O{S%R$g>?t3B_&j9a&iBNNG+ zjDH?ST8`h9tr6WLQhT4uAcYm*npOSuog>a2T-VS}V?@5F?_PxBn*a0{ZBIq>)b|np zhqZ3=oNXIFO`*jTS2HJ$TYUg)NzhdLau@n8JddW^R6CBk)r!f<8ErX1L?pa)Z71XC z1hh9-PshXG@a3L?M?8-i;q9UieTV zS1Yjh_3hLT3TlMYpZ5)GC}czhuYI4wJ!`Y38NYxf6Py+IorN`DpS(5TJmZnLF+HBI z+|lZZ#=ae2_@$WnaeRs|{7T^C1-h*lyAs(^@QH@-Yau)dP`fjq65k~%pEXXCZo&~v z3ZAaMz<+8!0n8#ImVj*5z6%+VLe-g3z6LrO29=;IbSczjfp5(1)V*j@s|>BSl*^J%8_tLh53aMwe-c%t3oU zAxhM~Lxx-i)76I~va}4c0$SD?U-&XvV1tmWoeStp1hqdXUwiTNb=*pA`kV2Fh{w|H zZl&rTFP(TNsis}3Tz5^XJR?kUOs?EH{gtuHUu$Z_%(Ffadtzak)P-Y=VoxS68|Dna zLn&!^QzB~HY8P9vp8?H{gj#NidR(w7!nqJZgnSGLt?VMKaGZTVDCq9H+CW;*B&`+f zR8#-lbWqQx|*XbA+!Y@Sgs6SklE;yjwF1ODk@Ky+Ax^^#~V< zc!5=r^^d)4mobZt#UeQ7FP)-q;$KtPh8US)$ZVvVpCQ$S6im%nWjRQa19*D`PD=r@ z+84_sF$OVR`>ryX#)Hjk$BAENRiA>|`kR8>yh{9I zV?^bvcv_t%tWK#GMTHtQv8v}aS6?lQGyIeT9)m&jDZVs#=@)J~42`;UqopV?$Q?131iJHo|=$_2ibI(EH<4m$~DV<3V z_e{B%B7KSFV=>J1*F@a8khlim!JfXOwgKUq(TF{7Ig^gdqoKit;$7R98b99IwReiI zsskCgkamRzUWmeAoiPG%%(D5es)7{^zSlg}7r(%zL zuIrE0R~<%K&^}jR^<#)jgGWry%AXat0}%sUf=?dFkYo+)^|^V9+b5l~W2kdnGc+~8 z$=ACee+5;X+{BN(t2{FKO~9}6fy?oANLlfuOlN3;bR(~vPKK|Xs`m}j*VWHy?T~VU zFm2nwc-vC`ChH&3tC;>0e@awt@w&14CO8m4FPch-?07g8`w)447HK8zonzZfFrn${ zq`j-W=#fkAf4f01Sr;lrmMnfHWL(bH?!QqiTJT|ThHdn7MjM)H9{@Q?F!89QJ%YTL zKu6ME*EJ6(llm#Nt^YC(?Yajfz#*AI&)K1tawv9hVDNgm|4e&3;q zb|O(-R&1&MIUs z&iHJ~i0m>gv&eTCL>re+Vut|$;(-+7{*cF)eQKu~| zA|B|lJ{JC`28{WoR%U#;STfUqpGr?Uu_mX^(B3}P^yHE3U>d{@{>HC+y*wwEO$GSueUW;)AO=I1! zXts&kW9e7=x6sjWQIYu2N~+2KiHUR#Q9tv3)Iz_)CtiUs`g;Zv@{jcS8Q14`W(o5= zP-{sLYsV-VW>#t!$IXN;LhyDucWsaAI;t@TZfcNl@k~@61B%rKs0oA%D?t8%3SUAo>_C+ z63=CG<1?uujWO}qESVd0blOIkctdn``)49mi)!veq?@tML5nn@*z*-Sa_~#yu#0_Y zH_+$nzMY>K%bx0F?%E63Oq!Rf?@v^o0)8dUNX5$fO-vYSt10RD#_e0k#l*Ux{&i}- zxR7VQyhC0=J@~7Z$5uS{_R*zETOnOLf%I(KXRkI%oUG`O*x@I|`bKb~O*>0kkwnri zk!53M*E&dJH78H@MN>KmleWwDXvdFyi*~rM48rd?3-7Tbx64B09@i=MlS5Ym*Rx(k zg^W!*uUb;yC(Thy2@dDH=f)R0$wKpN#pAU_o7)t(?Vp?h%^IRvEg+O$UVj9^Z@3fj+~L%kkL%{ac5}CLbTxS6p`*#4c;4 z-HN|;ooOWgiDX~L-iP}XYTYE@?pG9}_QV>>=i*(E%gK838AmEeA0L(ZCAUJkdW^Fz zbLP2=`O&KtXYBBIhVb&( zyKZswJJwH6Cxx+h$18`?DYBM67blGa;ZCmkG{MEDFyagJw?T7W$QK}7=KZ5dWUR@= zl(aRI<)_Y01NeJ&+*{%-)R+$|jukV>6}c*hS1iJYBGJ+#IDamem^d8S>UsDCl`PF= z{HS4c-0nKtC!QL!7Ri^C>7|hn-u2bI^Rvpuv%!@#fo6MKW?3)TI#2Sf z3gPyljq=N7p!*}w(lkQaAmd%6jrdbHBRJZKS6VfMOfB+BEW$p6vANsJHgG&pIRF8i8XewBZBo78(N^BRQtw{^>;JwG6KD%WKqCh2>wa zc_9#c6hTaSARiZRRNfEDaH(=QgXn-wF8gxQ~jTdQ&&U`_g4-fPN!kql> z#&dm06HlhQ8iB`SQ$q`Ad@{Y^brkG;P&99+ibmOW++cX1QfU>eGuu|MECpLEO12+; zG|N_ce8X_Lt8zpuoYd0VLLy&`1}!AfV?(z&q=nWZyC@R`V~)bJG9M-@U&ogb>{6o>WV=3POFAQ&F1jJ3+Kv|icgQ~28ZnJak~>Gxj$ zu;|6Ws)~;C5IwrpeP2`I6n^D$H zml8Ln`+Y{zRzG4^#q;2>ylmQtPs-WKIMKM3+&-mp>UuYMc2{`z4;`;3D*tNXbCv3S zgb#yS+DL?Ae`jT=NTb=^W0T|L!0S+?OSnT|dFEGg4i8r*TE*k}$Y}90rL-eqrmazC zLPiuHY8~@j$A=Zad3`9GtVZj`ooGT`+<*gWGn(7pNP4qY4KUlW`F@z~uvXgpaBP&* zP~>!s=37uZpG{B4jt%)td+XLmP{E%U;Z{~wlg54a5-JCa{@9gPj>-oKhNQM%^PK9V z*k$L?1bU|Bj>?0H>0a7by2ir9G)`;CTf){hfY!Tt8_9@jP?MmyISspWT2q;p3~G67 z_GxauBKf9xNb`wA?cI=%S7Ob+=6qdSfLMDuRroJ_Xl><&->1JiXuhC3(@LF&`{cW9JPm(!SY|-5HEMTuxk}|&0<)|Y}&$R zO8q{pW0z!2?Xx(wQ2}QN=*9vQH?#%JmIi!4`YH?Lco3n3`ae{2P%#QNJRjPa>b7Zg z=M2<)#1l+OjVL4TL>#(MB3{OAfY&ajb6;(W*m--b7!f#Mr$4P+O)ut(tLxf}lPeAS zT;=*hDM-#TFiz*)#dBMX!zB3rRWNd@3ZYtNp& zU4^dp0(6yzd;z-pv!)DiQ=yADEKP<1Xk{sMaF1X|p`(!Jkl=B)23KDmkG*Eu%x4zQ zg|Lkmd%p424@umvUd#of`SpYN-`%@?1u9@0{)zIJyB5x(D5LeQS`=4a0P<-4Jm8Xt zp99?Q;oqMUyKun6FT#J&!!H9K^6>S*D?R)+;8h;J1$fxQKO-JGTbJ19_)z9~GE4dE za^$_^2Xb_H$FJn*G{@b&JCuK4j5JMl8$}pDK68>(Txn2$lBwDn zS`_UhkSXUp?Y>QULfU-=IHXM1bh|+3<elN!% z-ti00Ym^W5j;_ma9A*w!Qnl+VnKu)xX|8956ic6UI9!>+=l4Z3#0yCdnO;n zd~#PSsq;Vs(>aalqZ== z%*d}TWcOG?!~V!GDeV}-hlzOq*vPNsNKA0y2MIVhK0pY8Bfs|^!yX(Q8yZ6^aA<6B zY~)TlGQ2JRyX-GF@Zs}2gV9q5{;k13HJCT3@tM=#`k~Z_I}#I$1~;={T+pmK*}Pa* zU-Ma5V=eEZ^G-T%q%XqKF17;6HuTV*yft8av+Lz;E*-b}dOOwX0&uI6vbOkQT?Usr z=RQoer#?&m#_G?ozt6M3;#I9n)c@Xu{UZ8})Wu2#_4Jcc7o%%DluvV!h28&|t_?HR z=BndPQ@?}#)||TpxjR|SWr}RIA~?@=IP8qOkqD#|o4?&j-8S*+7KM`IeVn?Tuf3N^ zU?Nb^w*qm{x39@w=KJMaKEe(~2b|Nb9Lvlm^zdarmHfV#{4(D=F&^yV{6bH%e7Xfm zme-nHJtAE`eKMCWA2FFqmk*ucdN_}ecHh&ea=nGLoP>S){UI$LLEKk~7USKL=czVI zl?RiD>SS`o{0z?)y@OEQ@uYhzET2dhljqmojNj@IKV5kU_`yyn5;xsMwKq3sRoL@$ zTn@5FN&9PCVqA)6KX2i75Viq}{ti!d#?xN0mypkuSHFS9rS5<$)>Wt43ahIh0qC!* zs{t6Ot2Y7|tg9md7^H{XX1iF74i_e21Qg9}>He^JTBJ)qCjqMKH6Gkays= z7s^9~+X+>l7u)%`r;cV^v~ygc&m2CbHlK_3!P$jRAvHLiD6s?HhWqw?aNm5W%cxyS zI)8V(4gIV8BzDI>5oC{9n#CD>oF0}h#Uqz`oW0bexzywJc(iBz6Zq6$5TVU?mWH{f z-NV20PK)1ooGwW?GhUo;UPAk!Cw#W7V)Hq^Z!=r^TA_!&k9C#1CAw3PoeT}j#wKr- z<$Arry5BymCBkdAtwVNbR3#vz0@L%u0m@bb^-uw|KfzMxr2{@NCe1 zwR_>b9A-D&KVx`wCdBSmR^vd&?&a=9W)JG(_3|cL0eT!UdNhm}eUT%Ay7+vNmbSd{ zA(t?GP+!9DL466k2X%3KgjIsNm^{)%g1UIT4Pyyk2X(P^TaYDO%_CFo3>nnL)c%OE z*gU9djCs-!X1j zd1bsZC$3>|7DnYzc*fc}I}-BPiIjSo#&|hQZ_$8DeX#tk(jj+jf2&Z#7NCi+8>e{d z4jw~t%$O|&ev<+zd(A@4C8|OMo3#XFXov)$=81whAtqWH_ zZQ;=^Z_oX?o@G3D3dfonMrD*pD?;j7=CWSdj3e!0I@HCoTIDplsl6LuZ3CT~{qVg? z7~%WUQnqiaZ{zw%2yuO%4{bgB&an3i>f-Y@hP!`!-WKG46rYEL5RZqj_}jr^y2?+r z(IH+a!wcv^AvbSc4o5Oq-7YI$_LMzy(O3%BTSmmm>^!m830)mY9BP+4%* zzX5zxtj72TOvfwdiq)Q?PXA%?hGK9Wwi*u8m|#9f$U$IBVDeUBhb?1|wG7DzkD@Fu zwRWNJY8U@aF*P>GonttVe5O(%u6&YLDAVq;Cv43;RYn)ba;9ty45fuLlrvU0aiW4V&BB_42IY%fYD;#5jB9;Lxx@aoUG79avdqNIL9HBL7j6IFjTspi`1%R z=RzWooz1vq7;jQ!hR^028btlp%m3ws%6F69WZYm?-dqu$9dv-nTn$)EDTWMZfqFK0WPBU$dmRX~Z^ zQ!Qf+*wF{YVYOyQB_Ft0O;b}-@-}K0jm*A|)H8{|vW@!KB81##N5x8m^+~dAdL>8{ zV!J5|_Z!{(T6z~lMvH2-7ZCA`^~8gFmbI$C1uR?+%Uq!#PELe4@@4p}?pfxd@O7!T zw1!wGmIv14^q5DG-F1DHXFX)wEo6L~{^ouJLYeYFqVf`AToeeoekdO6>W<|YPPzLx z9y^u%|DTi(y~6rmlMh)_u$FWG=d2umwZUN_sl?IuT-0{n%sbXBK4vw zpIC2Qd2JdVsBfYQBird}RyIaSp~Tw1cU5M<9*YD#lYnmb(YEs!1E$*orco?VqHg;! z?V|A-y>88W0U3Lh^6B(Z^Ytr_3mZecehsPAcC&XhF&eMGy5;DP*WcK3MRUU)DW zytK!TzYt?=5m@9gX;K<{Nxwj2<2o4N)udfCnV5egs0g>U7_>LJ2&T9yC2p@0T9(EK z;-#zqK^&xw&hpVW6mqrq(uQ-@csvh7mezKst)!J_;k$khd5XA^be68GE=x-gXN9q? z%^K43oE0?U)SXs~P6hDk<_q~^dE|yd-qMvsj#Q`m*+ssNhpo9VIM;z}?m9FP1$Qb> z(}NtRsrOwTjgf0#`cV#Cof#j+EMOAX<>WlTO2E7P472` zBJS@FUzTufDIqz_e&(p^Dk5XJ^`cA1@$$evB8o_`{fQ_RMR9Jy)+R%^e+#y@ApfHl zY=4AcLjA$R;lGcY{9ir$1Ly=?>%EHMdM6R_;@EFtuI7sc7n@g^SMw!U8Z<&%n6`W~ zfDghO8yp!)z&^}06nZCG>NYo)15uuwZf^Yec;yxF-9%6yfpN#`)B3CS`$o$28rtvn z-;sq}>f$#LB=5$Yj?~49U_6_;S$3{EnjX)l7k(o|HXz2+8OwKWZ)a!a-#K2Mbr;3E zl(_yO!NJQtBs#|2`Vpj8c?kW>YHs1sWV_|z*MjxOt&d$8^{=oE5SXtWQ3{2LZtNM2 z^&cDS93fYl^PWH%*l#N=k5`T)32q4Fq~$f|hua-~hQFV^4v-R+*OA4=vF-o#Sf;h| zbaEry!h=exDILS{{Cz-)lj~4FR~`C!fsweCqRF4wfX|BtIeHoZu$>7 z$8!DZJA|_Hx3ng8IEFuYtDNDwTHE}#u1>_J;XTRMQy0G*1g)#lfHGN++^nl%Vt?_g z2|-^f9U@yw|IS1^e=R<|L3}6<>`l1)-p^aOx#b^9{g0w4N4&$uF3&$mU(l@q?rpuR z3`cma|G<0}39%2KUC_>FZ9l$Vp(Py}TxXwi1L8)7v}ugE(W05WRm>HRWDO<6NH!_1tV`>} z^X9P!;>{KCX50l&3|5y7Ebdn=vMF(Bzp9R{Uy4h7FJj^u->E;f7oodYT6mY3Rc>SA zLzg0W)z*b~x6t;%@H<;35LzcjU&IX=ofi;2qf1`=Mfy~x#)Sd5_bg3qBLFJTrMgsq z0Q!v>0J#CT!K@>Q1AACGmpl2}U(WIu<-~O;%KF`2E|ayr-i+Jo&AOf5Ok|@sYkOaj zj+r~(5q6@p?9Wrq!n!JzwC*?NMR}UY!ua1ORWIng!TE8h|zW?2SvK`xqZ1T}?*(=0l zhpJ<1?hYBv{1VO$L&-Uo8x*4nWoH3Nvlz|rCvTNAWHc%4S>NmA?bjQh2|;{@Qke0X z++N;};`Dyy?Z>vb&i1>WK=w4BHxB%iSnbA;0}u0Wt};^|Y{}b?6YqVxg%`4xR zzlD6+>Rj(#X0F0T_;T>Td>IL`4`2RsJ72a<8vlPjhnR;iUoE~&HeV&nR>#d(iVdZ@ zXP=7hQ_^|EjlvXH>Z@!+5ADfYwTyLK`fi6~N{o{n$WZ{(Wte7N8t4lG~ z^YSV1pY27O)<&+2XM_g2I23s@Gu_Io`Gb00sD1}$XE^`xK21pzwpr$o#C}-lMZupu zw`rR&Em7$CbI)zg(%eR)&ULi8`1Vb7#m(2Ky=r=6ehKHM-O7nJT3*ABaCUB@1*>6O zfXQ3s42K13^syRQZG&|ICZnUq<*j!+3aC4|GPV>WAG0pGr8n? z9dW>QJg(vtFP(OHO;sj3(o{{PCv0uww0e+RRZalYznUWFi8vQOp48@gSj!craGWIA z-ZAn6oI}#c9g7H}W8gjl8{UU@~n%<`;(k8No_pW54n3h%Y>ARf(=KDSap?d(g zvJkolaVry{dkFW#agXeH<*Q@!XSh?Boaa0dj_*vkcY_>V)3KkE)7M#Fg+9iAs+YRS z#^B=o^pGE5?baZ^#4pk(_uVNVT|ndHdC94?b9VUZ#%o4*xvH+K&q)u>&y?bRtyC-) z7al>@j326}r-$cfxZKPg5pKnMOC4GA4NUb1 z1Hr&`t5BPNEZ=VeHpE`;O`q=8=91dGR}AUOtk~RtT2r;%l+212%WUx#?%+WIMsmb{RfAi&v*jC3=T9YE#Ji>I=;lHDmpMo>MF`h%p$dw}LBCfnnf?1qrZ%$;bO8%B~7CSz%6dsgz^$+;h6GQ*j?RnCyf z_A7T5f_RL_VvWb-hQ|&dcfL#f^w%Mmg>vUFEd0LZAM)8_#Al1%;fisI+sm!+Xm6|uoDmIauI@xoYZAq4u@;2GCxr+`h z>CoUx`#k85tI_o-^iIyC8CP0Vlefz0xbpGwh7!9s`Sa_o9~8QH^s$~t<%UO*KVRU3 zFOfe#4#aSa6Zvz}?g5}%b@FEp_{DeXkC#7ZD3-ZH`Exr$D1Xkj(Bc;@!Eo~Dh+g^Q z#r2B;LavX$6CIcz@StZ9xv!`mAlyr}bG|DoZ-*KbP$J4KTE1&o`-^XmMs9 z3FoGP$Vs#LUX5u_-0|+PYp>v>(&toJ2UYoof~rU=RUD8Itt+_2`_(M(ee&^ zZJiNXSo9A0w@v=M|2{h+O}0K;r95z-eS64W9ITfta$%uzIhi=y7^1z{cC64fknX* z=;mH-)P~*-BXvX6X!3pzMU%&N9+*ouKW%QSfQBgK5#+J`t&0d&Ee%o4BgkX>+gb?4 z<_ci)T9h^1?2}a)rJkK0dV{?1beYbPqfV}z1pS*m9j$(+f9N#Lm4~!GnWlMpc={*Y zszVSZosqw7p_Y56f8wGU(S<0b8+3xIv)Kbuwprk>+`Y+WFCb6iEyr^CXohv&5FaIX z=aQvu`RZxB_mIQA=5THT(+3jY?ICQjSLF!GGOiC?UA3Q26C=(O;#vf|_oTbyX(IPg zPoWuaNd|kyZi*-RH(wivO|P9ET!d#jQi)xu#t>E?T zYb8xs)tegZ4YCutk*g?9Z)%{In;}cH{j?F+kyhr(?0K;4y6=II?MT3{`g*NBF%Dr0 zsWnItj{X9FFICGNy-0scv*%qQ+kGpK z?B`SUIQ9r~Ygc)cL-&WmJ2#5EeE5DoQrb*DJJGRN(rQm8JCj*yrLG2!6xJM*$MtfO z#?y?8mQ0Sv9hTM!7{@n0rHZUT8@#PMXo$}v-VNjPS`(CVPp(Rbpqy?0D1U-oM!|_p zP~wjNfi>UdnlW+O0EurNnjM-QoSik+^Eyh&*tuaI5+j^4jm-^Jrj#_jiHA~ybA#qb zt5`*rmZmjeAFXgHUYv17;pwkRb+HJyU%BCPc3&xg2dTZn(Mb>RF5`J7~;&uQUq7d>R`Ow)BdTP zpIhY!^vAG)&QWozBrWk<>JuB^;cu$KZ4-KX+Z?k^S&)qOm5jU-Z1@gml-FkrD@a_= z`TE3dl?f3&F!vvUqBiRVu)tgtaB$yJ!@GRxh; z+|*MNc`8{Q=@;lV%)7j0n+yDft>?Ke)*)0Tl^X_Ghc{=r@))BQG+-Q`weFa=16-tZ zZub(Y590C3n{=TzW9t|1cNG*LiNRgl-yHG-YPV#9LksHcXUn-+pOa+Vd&xQo2``pi0-ebP4^`)~FZZ?VUs6|07DyGB)YZmnc z#JQhtrg|pm8Z1DUMI-B`TT_7TA5*AZR`ywSqx%W&dXq$@0X^;oL75o2r_2ky6X(Yp znke*7ZFQv_-V77i7~jwkKc{=-Mx?Q(E{ZYl+WDdR?H?ArdwzEF?D;u21P)JsEf}zR zC%7{0X?EJ0OpxwBZ4KA;IG=c;8ojl9aOe2T9))!80jd!iqSSB_Hu;^uKFZdXX2=Q5W(*8}m!k0}xw=Hnoa{f^kg zyTvt$6;-B3HdT%CbfR*MB6NJ-bf5PUp!RMBce4Dup^4_<*5T<%z5fJZXl{xpLLCId zY4TfI%c|m7MQ6^56t_|7jYxf4&o+LHw(Oddd}hPxv(1|=XRH@pM)dlqFw@hVJaW)n z)9}(hRAs%TqpUVE+`8jZiZoWYWZ3>S5au?XF19N!4YT1Syvbl<@a|2%D(j3$VcRvHrR}N{DNfPrin;n{c%BFN zL*xBEpG0Ky8XFk4zlF#u4;I!*@xdh+Zrx$itrFR8&Am#q^k|haW!|+l*Y#O6ZV{0h zr*vXTH)f||w=-tkY-5I7tEtlFToulBW+#}gs*WbO{#;)sD7IF1o$I)1JC+@XG;E?r%H0PLT)Hes`<#=B!$DC?I z3rbr|`$+3HA#YFVCF%VzL=s_wNdf9qc73gC#L{I|M$ zD@>H(9w28{kBU<<`!ISN&7q;SjJ7l$D@k`p$;r< ze)25MwKzh~{yA6jkUtPviF31=wSUnbi8kdjE0LL?LUO~bO=X%URdkip-tyAx8~f+# zr#h}~DCGE)+S^QN&No8WYSO&3>Ss$KMZytNv<_R78wE7`+K_gUmt4N?Bhb2U{GK-F zGI98QW&)X|74qI@XYI{U@`BT7O>efjhvc&i0;)sCZBb6o$GFZ&1FY}R8N+!!WvTa^ zp1hIZm$UH=jUBy=*+LqL!*sK~EJxJauo{|$ZoQ1zp>tO+i7K^tL-OQlWT|KK=Bpds zrU)nMAroYJp&&dVJG-lp+jNofa_B>xh3uv?7X5bl1a@tD#G)t9&v%3$7_m1^@G0Ft zYl}RO4@%ynFpUF!lf;Vm`VoQsy??ywK0eM|cyDpeJo#R~9CNQ<4!zee8QyK)Scd6*z9z5VBzf;A zv9g=Jvf`R_3ElN`x|3b0()5G6(4Ag;SZa8t+=a6CGz#tIwYjdKIGySW z`lk}*yT=moCQr~Lxe?{NWJ1@%{7^O$9@(0}HO1BRf3j$v;@WsuYSnD>zXgFP+YmLu z1yL4=n&9Em*8#YB;IyWuggzp6Yit)soaw#XCXG$5TpZ5BW?-eR)XG^)Lt+gA$Qtx< zm_Q=*YHHc&5&BPSnp|nXecaqMX;HGNMO3^iXllk4PuB#1^fC*k#aMXQ>j2 z;yl;1@nRC!wDCAu(vv>X-|Q6GiY70_`nKX|h=x=KZlk92m`Ik#xOV2E<~RA8NHmrc zusGjWEY?OyvDxL~`Mx5k9F7l9MDdqt0Q)(QAs_n18wy+=n?!1F&_$C>l?9FHs@h3a zC3gWCDf*g5v%aR$vae}04nwi-EdN0=p@iKV?fA(v5**Sg4#}j7)5(`6346ehPB-uie4*`}8gd6Sc8ql{yOZj0d{?~|ph4rw8Jzhsd1p>=W!*$zr# zEs`0VJF+JlqIR;FS|mMUeP}{Qy4ZP7A(mWpW*O0s}W_1x~Etzuk}7 zX368UEi({AodL`4Z{3u+w+XlEmR|n=>yONNZtpA@n47vmkA{g(e-FdF1v(`M*FuUl zMxF*w=q1MwNi=w@FNuJnVar44&|zz$2M`WCWdTQJAZmz!&-}Bu!=VvRg`7O@xUYeq9 z7mSy9*@V!(d=KKGDwTjn=_u2m4Aw>n5-E6uV*L(zd>`ugNFafF{)u98{lB-`xV2~7 zrCZV2)^yb1bkOD`mIL-8LdFq4m4#MTF8 z$CKIqnSw7{x>dGpY34D>5>KI4rU_XckE5z@(jR8hW!>#Xm!qzQ6AL%6F=Ur{xvqNp zdu`;>yIzi5@Qlgn)E(+2h5KDMMt!2Oe$=YOo}h7KVLJ9q+WOzX!~UETdf@djRB^b7 zseZEA{dS-Nh3ooOa}U-pF?*=~GP76K-)Q!#`sd6ZuK&Pn4mZ7%S5s~Zc8JkO0@B&> zH9^CIq?ORpP1qr|12XUys$<7jfvvir8NY>-q2NvCQ5qZ^9xO#X`Ui)~-NR9zfx(sK zk@85?YjAL|++FVOkAxc<#s{>h-%9*UG;M#PUses5N8A_f;6%T)K4{K45=*v!EoTX5 zU)<;J)@MClxKTT5iKg@#Xw>S>Q3PM z>*5We4NfwH7BDoEW^_DKAA(WzGXbmblGXaUUov%e%+t%QUSg=;OzG-*t|#lwdOKHO zCSS-Fa*4`0WO#L)oBMZj$JcJ&n7)pi$F55(JQ4q_Rsj{((yy4+@R{+!am}hmZ(~uTHFkxBQ>}2QEsL@ zaC0mK_Sn?Y)n!-hcj%sN6Mum6(iCYYVHm z2P?DmQ{`1_bHUKr22Dj6yne75+{RI*IO~#|rB8IDoQ9$_uP&*<G%YBu!pjZOHcM#O{Gg%vK%#Os(ehaJX?hx_{D+z4;b`tV zr-RY$h7~Ym72pcQB2wkvryh@a2_vu1_U>HuqCbC41=BBy8-FuoY zlELmRvjb=%?WPbmsq*O2ul=*TdpirAhbw3y4;^|t(uKTlY=u0H&8oGRNtW0) zr&kka_o?KyDpvQ*(+c_0nr%xME>Zu-gWXvuF|q$brq5H3wOdgaO5{f{| zpe`am2y@!;#BLA%qfAmJrjW%f12a;zs)EQuL<`7do!eJgrJ(s+tF#Nv8T zuc8};B;jy++iBeq5PQ3=`_e3*{35OcL7l>Bn?|!uX-mnSLD$U*VYoiQ1ZfO$nPmB{ znFP$o!c6;L!CX@oE)zntK9lViqm^(m>XyAOzdRl;!7>|-T9sytzG0EYSCQE*k%jsc2oTwl-x5`f5);vpgs<}-TvKE&7CTn@B*+KIh?6KSLSLmV- zoNQX)q6;h8a5gqC2?G3E)R_a;LP?Wf$d;^t{Y4XCAkUBhqc@pw>m4rHPIw7M!bvdF zNHCmNAuS08Jf;Ha@^6!1h<@A_X0qt3Al~mGbaGXROKNi!tIP+mCEVB(CV6K52B&O>lPDeuqo#lI^HnCHjJzdqiVxw+AyLt zL{CP7r_hkP69yzVicheS5;ba`;(gUY0;?5e?);-U#Mz-(>ohNu*^Ci{|N-$Z! zJu;0;93)c48_M@zzK%v4qSs<2HO;BH9&c>eY989E0fdYv)(;s^4Ztzp!2cY^^F{Mq z04KKb-F-0D7D~&~+nDfi$SN}B12I-rf{qp&t$yC35b*y!HWGCrY-G+hHX1U@No`)= z=6bW$d;y8_MjW%fJXBhozH{9sy8A+A1NS4uG+gN0%!C^wBzC(WV+f%?sGi^V;bGU+ z%|@OkDfcUiKQ-lvgVoO_oM0`j4wlSlf?1Fty_eTncz=SSGNc@{Ks10{BSxRX`aWyX z8~$EnhMOX+d&6&7j{h%N84BAd8*gY_xy_R_B-SOrmKbu?R1ZU$gqa-e;1S#uR8Kt+ zu5w)LxN!1dF$xPXbw?!%_JF23VKYJRDz>nRj+&+=Md#uo-8% zD{WKG-XwrcI#U4F+(D`VNIa?Jo#tJ1Lv(NYu@Ahuwg3-}L7=F`L_xOL5d4m4pDf+V0WLi4zJ*=U|}G z$fF=F4_JzfSo6*(`i%R+j0dLVC_-;`-5pGN4-iF`F>C$FOpvoohdNtP6p~h&tckV? z$!wTgPvZ3+fT>g4DIL1HME5SbRb*zpVv9&^gsyg+bsr#& z)~682^xJxw2fL}pLKlC#3LX7Kx$c@smbyT916~($tm!+vll*lSI2B*)|;jG1%(yF1stn;Cakrrw$(`L@l< z5PGBr&MtIJCkq`_TF=1Q_@tFcx)7-5>6{B41KX@IvC`A-c^6mW*^;j-!$d%xL|4Wy z`qXP^+d5qJff3lUk1!cq_o4e{xt>2zu17mJoMrosEuPDKv(sIQ>`IOhl`tTbgXX zL@BzAwdE{cUDj7J3Bkgnz_vsQIj4|D;c$F^r{tn2!&w}ngXtQI4k+rd@ewcI)s0~X zS_EpSupOC`Bu$gF=u4O#RMU?zr_=d-7V}63#tSjO!<@rl`W4K{Afu=qLsvewPJmao ziw9##?she1V>TU8eJO=HFP`ek7KrLdMmyR15|G`ca}qBgO9(@jZyBC^kjrJc(^X3S z@!y{LfHFUEe+m>WN^i=S zr4Y(>kkA>Wm9^n*X%wu_7+;#DckVY!(qTs#bISw@PKv^@=DOosIqm;rbE;SzM}(yB zWpZw`FN8C*a{smwxZs{G^UW8LWta58pOVsO!$r_WA0$^Uy|kYRa-GpU3EsnvRkM*4 z2M=0`oSkqfW_*gnN)R!0dxAp;EkU0DbO~m6U0|BFt%N))h%6?4ckY#zG6$8dmRrm& zRZCNOa{fhQcGCtnezLwPsGUj=kPFx@9BvP8ak((-x*21Gi8$&frAf!ojwLM$7p(C@ z@?;&IcdL)$**BM^4$I-XPa=(RKEr@_ovWUUuRPz^vN-YleY)Ab z`|XZC{oAni%Z?vPI^+ODn!fw>^clKipvd|y!IPbq+IW{wNAz52=O^6GRB1ju9^h2Q z`Sgde{i}+nOFj;D=1dQ_O_!GXl;-lO^jtWkiK~-Ay7m}ah70OC&LV5zWQ^Nw-QHpQ z9KjJs+2n1d0T(Q6iT2GkB{$4*Qan+f<+por**)nGl45PVd zW}(V$+^L&bhTbYYKr09ju~;KW8Y>L@h;{l+(akU()@r~y&dJ{ z4nRM{0n3ZMIn;L3@kAjvyZfa=9=%9^OS4zVD$swh(`90%LTcj6XrLx%IyiQap}Mlb8YAk{8MKEwh%26oz zf-Em_rCPA3)SK(??XC`)Mo5q@yUQusplFJB}I%NvHM@*9zn+nR8g$c8Yer`C@O+x>06-NQ`&q z*5!fxVFVVxtWLA6tEDMj$gE8kdaCh4rnjd=al4?0QD{^by-YorOTA|2SZz{*pwP4C z@M#Gdpr46HLz0L1H8q@*M5W z@4=1~7c|Ee$?;R61T}^dPq7f(CS9IF1}nw7Ix}aRW+c6taoib_E<>sZrZ!kjmrBi zc{Fp%z2)A%zGMPnUS(T2iRtP})-%ud^PcKJP`+z2l_>6>SdRZ}=wJRrPW@6oc58dw zBO{R8I*$&HD45FC_8aJZw(sFjR|-jCE2)W8idY|{c~ckb_8Y^ft9?QF_mj)<`d;W| zsnM|(x4%wt-Ke;NE(9ctk&$a+d$>z-YGWAd;V%daGDbmW5WCjT484eTF|C(Hd;_i~ zxB%*8_aur#dy>WNuO-2oQpN4p%Px*>rOeWk5Nqity$R7+Kf%(gc%p|Fupf^Rei@^m za4s~+!D~H?20-i$6^#ei(vZ+U-Z^ba3AJL9{vt?nExuUe!_W{K@NVfhZX-PF=76t z=c1qRpK6eU5BIR}-yHFe=I414{}UtlKL8H%uXy#7&AiQif>(N2@xL0QvZ=f(hknAj z%peCJ<6+@{1rzdHDT4Ei2&}n;e!_phK`x$-2)r`_pWw6>|61U%{YLx$zlz|mkKo@1ELw;@qJw_Q^Z5oj_(2{PJ>MPims>yKzt|uLKhnec z{xeLN|6~M5^wv*dS73xASZ6!K{73u$i}|LXa2{fiqyMLY!~944|8qQ!@JA!~e-rVK zw#Vm3{7;JD{|GqDzv9wQc6e{1;8pVEr}AEh3He{;&`&r`gB*OUhlT$in8#y8|7idJ zOx*e@?1Vus{(tna{G;W)I^ut71pjxyVg3^loM_&9@mGB)&W~b3`X2_YpZpgLa`23Y zmH&@pLi$Jh|Fa|bVLZZrW5hoZfv=3fCwN%+-@}CYw>FGh`8f@vpWv|H5dJGMA^pR6 zgww$2Cpc^u!v7K`q`&B(pKzXTkb^@$6#jc7{?YQjB;x-l4-5ZSm@xlQetSv;U-f86t)i&_CMbN4)Y(jJK;PFqo3dh z8|3KuDc~^w(f;tM9!L0xNATYg@sH-`c@h5;BltfA4)d?L^pkzMK@MK&VU_na7?n-@ zpmOLZ{L2h-@G%}1{#P*}zo{S4Px#L;$id?t7XCj({5vA>&Io*phlT%JOql;f1V?G= zr?h%7A^qO~9MbzsXaxT|z+wE+`kIU24@B@^4jl4NwEzFB2>$vA z{=Wf>7NU>npr7)5zCjLtkcU-Y?}_-!t)K8;Y>0y2UIVQ}1w7(U-^;6gt7~u&1 zW9R?>N|-SJ(fTP zLjG4h=qLQ9K@L9F!@~a>Cgith|NqPg{zL@-pCbOz@?IVBKQ)4X7jT&WLfT3;+9=F#pPv ze#*~j203`h!@_?RCZvBDkMJ8FNAS@e7XFtpA^k-M{e<&$gB)D-u<+j(@sF1GB@zEe zMeu(O9OggDZ%>Kfmw<1s%FpL9A^oHM|I;G)M?~=dHsT+p z=L;kL50BvA0UYLE>FKBZY%$2eVLz$wufv4&KRbeRc?90*Vc~xh6VgAdN5x(FFTlr@(AbwChs^3=r89c)LpM`%b|63R1SO46~KZObNKZSoQ|7#cXul}c%e8FN0^7QSZw}Fx1boO_;5* zeSSpI%1fA^Fi&B=!a54;N%>S=h4mHISy*qXJF;L#Ws%YSYT#n4e%)Vy?%Wj`=#~7|cIo zF2npD6URIkb3EoFm_so)VYXqujp@N$i+MceHq12U-IzM&XP6x3D$L24Ph-X~e}kFF z{0K9Mxel`#^Bt1)lHoQwG$righZ=E;~ZVUEPS7jp^b zSC}r$HJC?ZK8KmayaRIq=61|5W)J2}%wEiKm=9vMWA4T*!(4%RDCWbMLolz$oQ3&M zOaZeC^Ek{GFjJU!VJ^b_HztdDG3Jq&PhlQ}c?;%z%nvaGnAc$DFki*2$NU54uQ0#G zq%hCNoP_xpW)yQX<{Zp_VOC&Xfq4?nie1I7V!svpt=Lb;emeG#v44y`ggu1)TI|<0E-*mq&4vD4Tu#C{?6!?7QZ{R!+(U>|{f1ooS---P`% z?5AOWAN%{*CF~ORtFT{%{S@q{V1F6=%h*R@ABFus?Dt_m8~fSVzsCMG_Hyjy*e}I? zDfVNqAA|jQ?9XGb!Cr&?x7dG+UBj+n-+_Gxb{V^j{W|Q|VQ<0Sg8fbGZ(?u6-iZAn z>Br1s)?uE5Nn%dK9FBP^ zrVsN(%!4t{!X${{O3VqEk75qPya97I<~taTF5Sk(DdOfnPfQ=C1G5qn$Mj%wm_bZ{ zDPp=X!R#W>m+~>*`>>X`Uf^+W@!X=I8p*aRlJfF}^XQm%{EfaF(uaYH<>Byyp8M?E z{t@%+UFDG9hHESrU;IBGFHWQEh9b_{T;M9JKXInX$H4bA~hYTY|1oWnqWur~9Xez?T?G4ketdDlEjBrb8p@w6u637&lP-%Sl6B# zCcU3`^*Hlf2(r@|6dCgj3VEJ9GBvVHHE^e`!>n98en$vKb2aClI}Jba_@Jj6PdxVXSgLm> z_YmTqx)=Bpwe!sDGO-J~dK_0p7yy-h*oPWsn8cOUI#= z?bcJPuQ`wA{yzA|zs}9g`lXI)Z;(mr0kdmoOWV#H&HLw>mrwlwPFWwfj9!b*Zj!6m zaggUo( z_;nuO*YyB@%OBv+DAUu6DsW%l3V!Zp_hPZ+fBj2-1518f;eMaLp#%I@KEU6q2l%u4 zPjOH>lg;LG`Mj!ARinaLp!8vXfGk}KF%fN-p6^L z#OLFI$Zt)6KVf?b`+~6TYQ-S=jN~$17{o(1H$+5ax~e1C6%3*x(*@Xs{aDOa%!Qa8 zm>V&-V7`Tk{?wO5(^Z;EPiZL~r7?n0+?z04F*`7~U^I)|@h3zedD52^o}XKXd{BeP zcKu~A-=SGld6gFg9o|}dXvNVI1dX=5wa{qYa>uo9UL>WZ2HtNXm!r2H(enN%-Xkip z|59mvX-$Uxmj|`Jd`wY=`59|@e_Y<7ysFyr{si8wx%Ro7eEu9B+<*01EQ`ANd56)^ zhp|I><-h=5ZTt)#c9>}_bm-)gz0V`LsDANn~2-^HplP@+ON_V#C#ss-c*!)QNW=+!-EtbGcD{aClz1j5kz^T=VCn5f8bC zk$9XX>i1@%WbLLf<7pkjfO^6B4Ci3ixHu-#_1idoQBBpqf>qzk9~VJGZ+}%Tqw`sK zKapJOBjIrt<;E@zO}V;gxrNKa#cLdh)z+?l!bdpc$5@X0e+mRfLm#YN89+2Y z!lTu$%+YFBk0ej}dA`<1->wvI*sg9DTse%4#49gV)hC*F%FAMIHLE-D(lk-+qc@u{Oih7jZgQXfzNaj@3$}7p0(pEcIPCF2tQ>;Nlfk!@(2CRHTq6o`% z`u}6?Jm90Mwzs`ck{LqONEZ-cqy`9~R{;rW1k%VP0R)AROh_PPLS_<(2!hqY8=B! zFK!oypKgi8(xjY1qG-n{WIUyXIFF=+G8T=8w0meQhUxTJbSGlqNrZ?4PeL~xc&4?b zH2C2a-7}_BV=+Rf=~iGb%M58ZW(+PzsE`mN`$6ubPDR^%gEo>qzg^V-Qw2{pu{yGD zIPH+qcJ!NSosu-@|EFrl*QVXT?)a@Eq>CQ_{%`B6f091bA|pFX{ryQ89PdT!u45@e3GI2md`7KRso+($Y4O=-w&gz0DHQBb^g4wq+j6UzC3{ z{B#cQb%^p`Edh1O^^`>%sl_{el-0?EkBv2X%YNgelk_bzh1n^gbXohv;rG${#J8I( z4)b3GdAYF~EVX1g+KOEIltl$*flK6ryHWb%x;QwSZhW+;KgjF&%n%=$8&SAdVfVkp z4G-p*uONv-sl~&MpGt3%kpCP>XKqsue6RP};w6WcCnwxxLIt{l<~I))>t8-ZDE%vo zn8+eCZ{y{kAdyfuDWk_J<8k5qX$U1n6umCVaRKBT#?wvqP4kHHXP8W6jlj8~+>gaP zkR^FR5T)`3b^-$@q(?E2Lw-;Szf!5+&8|;ATyMU}n_q_=Qb)%) zbXU$H72^DJpbeDcEzaE_ z;dJ0W9hbJ0EIUUvXci^xM$R>rh^jsO6_OdXt0QW6MAUv9Q5(NHQo4B&wKWm7n<8rW zMAW_)Q5$nn#4<$GE{~|aE~0i%MD5QJwO!XlDnn{SZB<0=hKSmo5w#yh)W)xkRE9ng zwKF1W&yA?PBck?$h}zh7k;*V4qIPyf?S_cjLlL!qMAY`YI8qt1B5KPcYA=nbeI%mx z(}>#GOCptFP(*EIMD3P{+UFx`zlx~sxIR)DhDFq#6H!|oQG0hp?ZJrJK9@!+!^DW% zU_|YG5w(XSYGW>olv`3nZ9zos@`&2oB5L@@ib!QhjHs=Os67}_ z+y2T(=@vxPu8OGL9Z~yML~Z-4BIPzHqIO0^ZCym|hKSl75w(XSYQK-Djo%chyqzOz z^CD``ji}upQTu5`ZT#j)Wf&1rTNF{-5K((mMD6Z~+V>)A@qAl^omiiU+Nlw>t0HQ5 zM${gOsO@x3q%ssm)UJuBy(Oadorv1@TO#F_8c|ypQG0tt?FSLHao0x5Eh(ZlC!%&` zMD6Z~+Akw&yIvQm3`G&OOCxG`MAUvBQQPABM!DhAL5)hs=V_n)_Ay*w>d#SDdg}v0 z-~6O}U%+2mQ{oFau6$o{sjnu{Ge1z`uc@q{U0G2w$yZ_izW>#AjteEe! zbNBj!auBjsN{YX{sv1w1Q^1is{!pYxWa^VMw16QwlX58v-1IAyJRIz5w zJGqr>hOEJAUo? z{e5K*9^CocPb(k(^yt+O-@GBPyUpa?yDOtU-aXU0^B;rWAN^?3!1EqE&~N6RW9hT@ zBpiNt&ppkmo{F2aa^E>^XFPp0E&Z8$tr5@OJoO(hmPcRxYU#!SZ>%}7>)}@Z)HjFS z6?|*Lv>ET7^vkAqdu}d&@7CxYAKdbfZ$A7!@rRGb4mjcCmqyoo{NBtFpPW7D%+Jae zUG&x1Bm2HC+&bdhmS>)QWc&4leo$Yo{^6o7cmLdd>Swcg{P)=Sk%a-hxYM&6UO(;n30HKPo$$fw?xk;ZYg@Fx$IYj&>GMJ9?|m+q zaN)o$+wuoLP&j?q$dcPft}b|P)I~2nHRiA0&y0Dya^RWs+w4E{ma~e|&VPJC+OeaD z(iWUCCS%Qqxf$m#JeYB6@SF+Lf-h&f|M+6k<>$7|dTrXgocP;YOdj*ozb0Qk@(ORK z)$0ofS8gus^5XiUAMV_9R-c<5oqqB8-<HC&=aIYCxk-7*ZNa_HAW zKN0RB?aXJkw&G#2b-1y39prtjl)4;YK5mB31mH$r1$6(!&AvHEqe{|)J^^8ZxY_tE z{1?K#1`meIBU#V2!BE8m21$^gM)(oP^9RVd-KJd8xF3jorhs?~O)bU4lXm=8uq%BAk5IU_a`$0_ozsSv`+@3z1(xd>Ek! zWx{VRso$Xc6KTATeD^?K4qky*AA}o#`XwWN9`t*VUjxc=FY>tv@)ESmJBZgF`i;md z3hCX9dhA0TKS#O=sJ9odL(tuguzgUjK5##XG`dNfBF+(njY9e@;4TG+qwSX>{qIrl zTOp^R90yQ#H}b==p#IyBus)ReC+Mb2xuG9{bXLNB2>JbmR~Ewjj{I&!9dh9>zZJI- zAL{IwjK@`raZ5OeN2P1<_;mn}fY;)&_ez~~&&J+f1ggZV1i{0D4n^QW$xuov^L1(k zctkfD0ZX6>;NN(#47wUHAFpEMdoH3SBqO^Zp7rtJS@R&C_b!2#&<{Bs?h2h_ss2>r zxPKQo8%an)ex#g(_aGj^u0>g@b*@sfQgtG#?HR8#FM^^51+#OKqRrQNPeq{a;}N$E z|0GZf)K&N=X-Ylp7FvdY3E=@vwpj>XgD0eYcp6hi96DI;Z)%O{&mz0ks&(p)k@G(yNuo)rWuLH53m6 z9E5lG#@>I^2^jNTQ&7HQWGCY-aiL%QWm1WJ^&WXRaVIvsVD9ud7g5^_*2>B(9|(6z5c5b46> zN3IWJ!%Q$41h*mhfK;t0W=BY?Hk~+TWSIH!BqDXPBiVCV29RB~?+_qK##$LtHiKPe zLJ7)Ss>?oyc}c2L&{CC;HEl>LJf$^dh|Y$GnX?5Lj8%U-N?t?6o@t08y(LrCbg_&I znFd~HOLQ*=RTr%HNwTB_@v6k+v?HY=FUeCPq#-vMH756DL`nEhQS2rN{$2WZ0F#4A z0i-W$06xg1#z}w5sw(SJ0F<7U=9D^=;Z>rGEXz*hiSiZ_>6wlQGF6gwbB3=1vJ(4| zcm--VOw70)RC>A;IW5vll0A%zbmLUO$B)3>GY}w%k3Psu4eA9=`lr6p(tJAtcAS8C zHHej`3sQnl`qb)L)T>0;zMh#VTebF*c~^rEB1s=ekno{}J9w&(luO2nbdQ<91#rng z4j>U3BKE2+);}RsjelSF2NBp43LHLlLv{YzPO;8uhVW^Yi-MRTETumc#(0FJ*f{X< z3y&8KXAiGXwI?ESQsc}PQU!?6(HNtJP)m!*(6p`M1@P|K)H{+nz5_u=ORZ(j$KJ>FfG1lQ!Uwx0CZ!6?kSzSUNG@Lo`~`ughOBP2u88#N z@f{VwrUup1Wst#Ej6SfpB7ZmehGApb*=|ayLWMRg5?l?FHO0%F3$*BaTdrRcUZi%h0$L8YA1yE3R{ zSu#_o0O`v#wJR-4z-k0XEu^0%puINR6EnQ60?NXc6NNBsP^0%7H7Kzc+mpt&r8fd5 zO29(2yo`hJ1v{X-atib^52VinY8oPRl?aX1nFt`3TDmKU(qDjJopNDC=0YvKXl#q@ z1x@XIv)nItSUZr1PKh01j75o&QGkDY>32g z$_A+tQ$#m-8@TkFXk;SG8p0bOOptAne!#>J>xLOtd=b-jNaB%N_8OI#8jSFPiN+NqYApkkRyxJ0DGc8i(zq9tVGX$M`3 zpdCZD-)ufYS43Ojogk}{jA?r#Vhp6{<~Q0`yoSKz?7%Yc=jckvc3yURq1|sGddc|9 zn!5l+3|)BsjznfjBC_s>HdAK%+|&Yu?2DL@O7yK=0O@y;rL?B^rQWjS1k^}eo)0)a z6LZ;-fwIclUCq@rwC7!bNP{9rl4{t?=J6x^3Pekf9BrmGjpwXameOZEw(cNQBfn`N^w9bZ@r=MtkE2a)_1AW8*lD3(OzAmkdf zWz+e+7zuRp!o#H8*m}r9cmM&~hnLC>IkN=2apaNUMZ$8#5ztmN__rygAuv)3T`)+N zlCl`_1@P>6{QL-w^G)#X89t61uifEx_cfD^>&IvT5u&1wqBE`BWDi z2gMgakfCSVJr$|}ja6pPZn|Vn#3AtzA|;rOj$F!ziWg~Hv<7Er6GMU1P#PK1HF_&~778GJWX^{|Ck;;`uqRS#b|GYVzKcNZLV?1|CFgZr zBYTB9q*SHi$T2J%8IZD_F_*Ek!2mSt5h$z~nyyjdDQ-pJ#8Adf12?`1E>*AmrwENL zZK?XJafCuFBvVjQnc`-hHfL~|kj*|~D9%bpg^JWPf?OnvjkVWpG1j5uyZMOT=ifz_ z{tM-{7r{sTyWn2Tc-hFyCb;oIOF;dJ$bLEO0)e|W4IF+7B<5$%X;`(64`$4j%)fM;&Tc@E zA;$-i2_MuI_5E#CpN$mJ%t|>0k{MruK9ISRjOx|HRiY2uR^bFcNpfs@Q**f= zQFOD;Lo^wgRY>#iPvEn4@y((7cZfPjatlRmJW$LyGy6;#oKomY)dTljdWKFELL)o; ze8&vm@d!HL_@GVeU=AfR^d6#A%|oPN=4#NcNuKcEwuf~&I?_E%t;-5di zxT&<@eN9RV=85M~!EmuijJQudJP46|jck34ttM)f_Y@%USF(J!?{u zKEi+We{~I-+gIuh*7%C&Ti(3lnqssuo?wuQBTQwzR2>7aq7H2=Jy~MLV}SiAjqMF$ zOqAx;_)C1Hc+kNP;tAGQ`;x3tDaBPKK0REJq;HY0q&6t+k8;=4dun_OYAa9(8SIm@ zJVTQPNM*_^=9K$tlC1Pbaf?fWCq~|#;3Ve%#9y1ez^`7!#(~GlxzA9?bHWgJB!3s>7bA6>r zRu_p};;*X0)RGoP35zSyuB8~2)xN4Et6NIQv$o2&s2Tx%rAP)1KEI+0J&aUN4+}KE z7Ly3=&sm8INkZ1CO*4|QXfhdcoM+@31bhhZEFVc${}g{Mrne+hR)Ks1C|d=ld?_j< zV@hTUdI>`}fSEa9iPZtkgr`!%=Z9|&+P2PTB}ob9z*Qv$$LL(hwv*WwHe=0nOR{Dn zJVs4ebIH&{MLns-!D91(ji)#e@Rw9b6Pd9eEJygTrdn8DQG&&T4OUX=$EZeCY68I| ztHF{ImHNtxQU35A@>P}iOJywDB`iaoN|QW!mA>MD4;7ZR1?!0vP1eBKwR6y=eu*GU zRXv^s3Qvv;iwwMgeqIQ92C)ud@tI&oCJXGZ>7RoRaF&LW+L{_H0vHMQB2ii)iOsH+ z`r@IXs(_4rzbs>(w3-^3h8S3S8lhR{A*~W;Xjuc((QK6-pAIKgL_eW{OFfvJQd!Pm zdn)=dGJF4rD5Xdd%~6c;Ir8s<(r8EySpOxkUWqwseCUU>(y`2YW}|5_tb8>=3@{iB z%>80?|8bLfA?%4$DU+mWLa(o;4(syJq=7Pk=SVZiN-;dW#y=mUwA8mq&-_ZW{K4*c z=8MIeT`w60%Q3XdBqZ7kU4ykUJfb~(?A6jFhfo!zK3y3d)mR2=nUw-7D0R*71t-AV zrBwy7pwX5Um!ViPQ#=@%sI^}0q-|sk72{Zih1OWaQ68`EL*20{{!-r<7{x@>&qf>! zF-K3XurNcGBw2&tp*DedNNRrk!PkcG(KYrq!ELdR(7&OGXI;lmz@DJuJI!_va=FR ztm7NL+8@BeDr+b#9x`E*_nYRgMSR$R(E5ZrRB+qErOID}IbB?tWF^bA7X#zN!+WJ% zjLe<{GunlVlCdt|+O&T7uzH>Z~q$cwk+d?F%jxb6{-ENd1>& zcUL|?1Wyvq|K!F zWuDp9=u|RlbGnxX_%z!nmN)*}8fBv*)5wq(z%<=VjyW$-&MhUmJ5Q zP8h#3`enVwf`wi&wlm3EI7KhPh#-9#(0jblA|s0jW=SE&SZHjHhc)#DdPyo}#gzeH zqI7Sl%K~Nw=?RQHD=_b{Lxdfw#^bOR+Wd)`=$T{YYglU=6LH)y)B6m4ogr(SGf?do z_Ik6z`V_;pPhnNXB9F0`5fYRg@f581{)Gs;4~x6r_}7>P)@OTpUug!Yq9iM^0DWr)g!GEEpx#*_F?^!sAs=J@8|@9H#X|v7{8&o$Qk-P94XM%4G%m2VA+;&H-G11Pnj-f*K1AaTxoLxO zsKz!a$r_SpH=>~m9?d^^Ah`w zIV;!A+$du1Vsz-uR+7~zDJQK!@55!9*J1F({Mg=BmW-~d+WE7gl?!h?qb^B0I}uEH zdn-~d+d;ov%*akiE-dt>1OwjLLA0At48t2*O(yGr0n7W}=ORsK&)BdWN-8TbY5VEL zyrj6K9L?Cy>n{sN$gGoI=*3z&E4(vAvCGUWmfd#1T=&bq0XB1f@uHbDV>7F@zY~>e z*wcKhudfk{-bYK5R-o0n!7(Q&UfI}SDJrf^)@L-G zfZYv@8Q>5>)o_Ml_3>5}R|m?m-2bODk0h&yx%?2*fkU7GENOTlk&oG_obi9S)^Jt; zEIE3*b(J}6GSEZbJ{Zxfx7l%6-SxQ}12r+{GIY_>R=2Q}ngm)1^D2RA3a}JO7h!LK z(5hiB{*P{*2UPC@vSdq^ty@1!t%xVjjiXCt2fl4*H@Nhp0_XDrAda z<_ngTCwX#=l^Nr~8FHd0+Z+;e4?Wpv5KjGT_5(T4xKWKD6L zwM)-cImE!xl{MktMbzbSvNM|jT*k`G6RTow)yT+~5ta4tQu4Cj5m1i?eeELKBRLDu zGq}uOf`XNrb1UpRb!z%TGt{nGa)@pQQs~&qnVM*Zp#^e%XEp$6ekr7LD?!eW#$z<` zv|X-}@WIMzTxMFs|BGm8izF}>=J1HaLJJcozw}brZK2Syk+!n>APyH8v%=!R&frdq zxof1iy2Xv}ZH3>0a!!h2Bhi6XXg;4^E*;l7|GmiyWnr2CV_5E*p!v;a$JsapXsmiU z*9CG#U4>mY4(G%Y1+Y5LFE)3AY(o^YGxb*&huY4w2$DcE>!u4Lrv(U2r(^hs&i1!4%~l-@Ugw%izz{RkG&8n`R{W=|?5 ztMSdj$u>qTvO6ysw}bQ{wYFVoi^l7(4_6lEzL(^N(OA=u7fsFym=r11AKL19)3VZ1 z3dEq-;m~!CT-TT@=qO_GZznPW2jvE4UC zMtAA^m{K%!jjv)(mA(b1OQ_E-ldOlsI^Di1X_|?1i6qxm_GmCUdpz9h;`{)+SI_a) zlhJKzCS}$mPVXkAtz^R@OG{U?PYw+)b9T_x+`{;qdl)!z3^flHY1{>oD@9BybW)%m zttfqKhNC&(mLocJdJRtcuomK){l7kJu?7`Z&8zY+#Lc0r^{Vn2|>t z1pQbQai>?Ga5q|axSz8RwPmeNvNF-J7?K#}`hb|1SGbj;}S_fSgq1o-2x+9IOg-dJFQCC0}R@GII(gT24_~J{0+B zI+grfuQx8GFh4&nr(kBPR#7O(%tr7q!CR1=o#$u@y;7vYd~a@k9O5O9mx9|4FI>}R zPRK13ziBhGGgFXHniP_9P9drwUbZ$ZB`!5BB{MrYOPH06224#$PcFdzi<^*Kl$nzeHz6}) z!pyw<%-sCUf@yKme2top3f*?v&BjB~k%x>qlr1wSHEk;1(=$=oOpJwmyyq4c;5{@% z#f?EABLlC2@-|4`2Fu$Jc^e^bX&E{;TDS=91x0RLrZ+AVvJ8}oxyWu(+BA82<0fTd z6lW!mPs4j!a;j7Z#B|6?OD~Aa%A5pOrVQ1r%$&63d<}0L8ZQ-Zm;!pZIW9BSAWnj> zB!xFUX~d!1Fe?}Rn3X#v)Lz-iUX*MmPQqtmh-aqD;L3+E#!^gQ0OQLL0WOw!yuR5ueVh&U+cVo}MO7Kfpeo1cnymp;SGxO8vk zPpxP~?vQ$If~4@-m~D;Xm>KW%bhCNj!T!0!u@_HoS{~NB0>aJLs7e#dcb3)!^A?jU^F17)VP8PSl$tEnjTf+kg^K( zh{Bwa_^3bz9GMw1r3&(~5+nCqyh(TBeX`c*0bhu=$jCv;g?V~GF3iJH0no=9X(}&tRPb| zVWjkMj+=^3nTDx5GaJTaLflzt`MDUDSxQ}qkGh6zr3bdsgC6soWUQr!))Gt=o!0|X zhnL;u&>4&Ap{?{7TPb>NEj`k(9fy(n!$#5*Au) zs04rw_1LBp-nP>VrLm&g5oSeoAhD~mAZbrCw>+?&;%6+WNMcDrZArC{wx!UBEd|gO z65d!-$Yx+`YwEF$sDudFQ>cqP#+Zr+%&AArsYlEy6kZY)i%}IF%&NyWs~&Apq186kV{EFYkxhl?@mAFn zW>t-wO?gsWX1d4NRZpg5rY$R+vK9ldtXhdR8d7LD8{4YA#G_!KybUt)U~vzTH>nXI z7FI-UVa1Uvdas8z)+0*}G_bH9v9cbqvXE(EJ=((JJzW}8)?Sacv3M67t93bPUXNH= zywS>v+IE`}Da#K0b27A-v9wy26$}n^vBU}4T93A-o{%+#zhh6S)^QzsDgl~UR8J#| z>VY+tV6>?o+Efp0D%4G_sz z^CZ|(T2~LPD@f~#7}~yiw0-r6DTPYl)q=LL#-*1^>CEm4!mAEFe}^ z>$RPQLfcu;*jeo*mKGYZv>t6~MXUSUm{^a2tVB(XtbHLJ8ln=4W)_|W9x<~Lm4?jk!^HEZOkrISkJ}mLZ=Nc1TnmTo&lmYrq^RkFQ84YgcsxM z!Get!45esjem%C;bsY9GZ%nVpF}>oVg$g&mq88)p5#x)5wfTi0%Z!*`QOSPNGj-^I zkserKC_;AFW7}bm2YNN$J8`_F@D(`t!-vyVoqRFd-~odNjuedx&-&wgm+(z*-Igj< z@VKRBw1eBLZx{q5#E#dZ)QIcx-AjdD^?5K_|F%uHjKa)R`27kwVLAN9SNdm5VEjcX ztHqSRcAeQA9|cgq_ZW;H;MBR-p$JbyH3)lyY<%Cs8oc^Jo{O(R$(64{!58B|rK0h| z$I|8dtF;?n+kxK@#rJQ){}Om!0k`Pp;)M^t;P*RC`VtpGv<}JX-_8;lUv-U_slQu& ziY9m;FSq*IasA@BqLH!q#;IO#wZY4+22$r%qaEKI`o^ns;hKY&TW!GmYP{mqX1trY zx4~u7+d*BNdJL{uyyDagj_WPQ_glvm9dA;JQ!U^!F*-S}1jly>U2c^Fm#N!yy5iIv zCtLt7lgdiE;?*{|F2^fQJ&1QHGfG8QEA=Z}Z{y`wQTR}}smF8Ufm!#ZLb;GB#PsXdGN~g=Mro(0GRt1-| zvs*28T+hPw5MFNeJKjyWKKMj)XT031FGirrSHA0QF>ZuKNw zrj|wcsFBf~h)?qv-BWOx_G@vv?Yh8m-Q&0(q$^$}bhYzM>}FDlS8L&t@e;2#JFe5Z zE7c0Gc$Mb3is^EzUA^#qZg{!X5xkq4Z%nXVcO;s6xYe{ITelW2Q|5O4O-VbdAs*AF zajIaTUAM7=Ol!oc_XpcG`gMry>NM0I5kJ9YYTj&^9ru2xogW`=a&xPX9oH!%Y~3)d zGNz}mAB*py!^^GibzJzsoGxj++5}fUUT)>ivEzQ1Yg}keM=>X_r|KHb(mNS9k>&ob$CR$Jh@46n{=D_w3i z(P#JNRps_rYlbE0UllTjPJzpem$7gq;pJ8@$qwN|6mq~esqpNpZn;h4@j_X;+^@-!c&yh3b4seWx}n3%hcn3NB6knde?D%?zk>>a{CcFQGrwGQuMQ{vTabh*_I zxJ)XC9M`9GbyPpXW!C$izuT@9y4!Ja$Lt8SL;7)-)@d;q~n_HxR%r9Ru94@dBmx&;WF#wUvy#C{%McX^Wl<} zFkW2`m$3nV!X+!eTUp2KaPf4-tF~|%yWoM##2DweCeh_q)o@7(;?)wz_X@|g)d}|r zTrv)j@?Z9f*&QxpQ|@qF_t5o{+6R{z_cuAC>38VN8ruWADie1UT&A7pIj-~IG8T3x zTxNB=lBvWiw`FnX(E~2iYOP=|&E6shI+NZ?C*NvE=c%@PHOX-er7KRYL%4-_#i^U{ zZc=`bE;qi7)TA7z-h#{I);!AM+&UF5lW(fy^2b=Z-?loffgXg;jG^6*>v_7|>O;6p zfBfL+y1VR@bK#Qqi&xjcC8@a8eQ=p|@@0mLQ-|S_^2MvZZVQ1dgd2-@2^X)MppXMxE7e$M=Ba`iQQ0 z^&MQYKY$f?Trtg!4%Qwnsd=1AfJ<5}PK|I}8IJ25y4>msxJ;|P>bO3F%hX)O8Q*rQ zGhE}4ayvEFah*+e17 z^@GdoP$ttAr|RG`bLI-TOiAx@TyMf9bIYwhb97@*vQ&4xuoI+ftEz=d{sy~3Gx@X{ zdk^v?d?n?MdQT`Tuv>ivooR!==!#eE;w`R2z36hQp>UaS>5glL<5~chjF))T0GI4g z;?-r2>l(*(tK+)gaXsp|zM#vkeum4eovxEDy{Cy+y&czJ#}$OD5HGj767Nz1x4IrK zGiTyk*t1r4Uxkq3RSDjW>qf`*q!aEHy4-4YOS=~?fy=Ca*EqU^j;nbaOTE=vN^oXd z6C++NfNL6FNCoevr}w~RR*<*pl72G1Wn9MH0m4wcT=2t)uv)0;)HKJ{u1hV$^>~>R zCI)#}&@@nkALPc5#fj$XFijV@+o%T|O*7R^r~EoK&DAMtkizn)U@Ua*Eip!hspiQ2 zdnZg6YGCSgnC9wqYOu&q(_M|yu}*O`J=Gan)63B$sL{HFXFxLvFPBOb4P3LSX{pBH zdjv#tuA@nXM*cQYm zv-F|Q#niOKxycM2<|-%5b=2HKO>cFhD%N2hbi&-N>d^2l)IMlTEq1CUmW;z!8KyZ} zutCSdPh{#az0rafTGFcDP}5I6scwKqsbkcfh8Fx(r+g~TbR^}0(0r-W8A^>yy{!JF zHRB!4tLkf=@&syzs#nzyI^}89oQ9q_GfMKR3)7@P(?V^aCK+Kev}QXsY0ym6ntjw{ zLX(BkwotE8<5C~0%~3a?<=>$O-zKhJiLy>_p+2Uj5Sq_)?mtj7P5q+2)3I8_;Cq$u z!a0|9VRUoE>PJm;Xx2wd3D1B=TEnN>SkEDa7HT3j3sf)5ijgq0si}n~1{&q{wtobnw&_v`$}q+^}#gqdqC)nP_a)5)4=t1-d+yPOM7nBCT0WO@dWrZPO{QgXVI)T&l|2hcdTNJE<99EwB#7M5CrpLL+}I zRgLwP);!~AR#5Y*qge%wr1KHeS*EU{<|k?fSXWu!=yt}Et8;IuwpnhMWwlW!LnEzm zfx1gH==ZMFT#U7;6~eSoBdECynhshsiJB|az1HbkBj21X>4>JM)@-C^GcUW+tze>Je+Q)+9KZ*Qgog zXx@iL#^E?e^C!b(Ihr0(Cd?E^Glm-Z24$&NOUy|V<~&D}L(MuzlMjv5^bTrVsz|~} z-NQ7~b$On4Voi&hsax!IC(OGsGhH$gK7vO6TB;9XHtCeVa>9HJjr7;A)NE6q$2^YK zXrWGtHDz`|W5yDGp;6burM{1mk<&sAax_23ysu;7w@Ml2_ZS&VEmVo4ITrJUuElwd z=C7DbDDCwV&%d~-5I7i5$3jqIzY`pglVBQ?^1IZW3H`RR_tG>X|7^j9gt27 z)eMK`(((_e6QGgubfLxt&8b?`*U_|g_0yUWj^=dN6n6t+O>#8dTr+f-LTY}*NS&)S z6;7CbE*Z%!)DlP2-&L#2vyPf8)F9Vlt=S%?S)w&>glR5tH=y)?LenBn=KCPmI`=Z9 zJQ$ME40Tu8(v&4^#82HQgn1t%Z7qnj_Xs*RRlE)H$(=T}O2~Upt!FF00uI=$${Hk-z4u z)P&;SN~=*5(ZM!9GW3o zGse->xstUe-O<#$vYT0+7AlXLW$HZFjApVMtZ>4d=Q>-*s&h0AF24@5+|ev|E!JT! zbu<^bF4vl?9nHnATeRj@N3-6wLu+<8nhmZ;wdNm=W~1vFt$EtfTpbqjTtqq)yLSI3$Yrdh1RR8sS>dd|IEhpBhM9CBZ*!>pjDxBAkJUu;KBFLT0t z=e|vc*-Xu&sztL0aXpUJ7@D1Uq1MeFMi`tC;bcz!T&h*G3C(dyq;he}CV$OUA~exR zc`2@+y13ZlbxViRQH? znr2eWMk!}E(Olj{lYnc!#<7++(cIre^GPF3OLZZxzk6WwAa_>AfO1c39J=~daH5J) zZ-Kdx$F>qqZkPZb04I}k$R*@v@@{f3`6l@_`3D)_TGF#rAMgjshs*+hfm{e41J#ckMm|~=enWrxPFM*S+t#p+=H!N+;25;i7}5(~f_&#{*;40{7m=5d*MQO<+rf

    E;0 zPv*0UQ=u^W;%r$iz>BOy-2-bgD#z<(X(YKlTXD3SX&l@I9Dp6BU9g|$J2)OK=WCAt zjw6;eA<*AUp zBISeaq4^w+aazHmcUJ*BJGP~-eXvD03Xny!oe)8bOhTvo9;{^{sskO0G_+g|MPVHx z90SL2p#Fw@BW3|v2v62H*D?;u(FB&qD;~_(jLgGF_N*b?JrAMYEJNEnz5EJHM`3J@q1w*D1R_}p zG>Z0YeH#i|qj~s2;Fb>57@v`lzW$J_}*D}yvQgvSjd!Rd8yUhZ z<(T2rJCP5~VJ=R$!iGOVJTFbm7c9cE2o8)F|-a* z%i7EE1$4NkGI1NJRI?xt0y_u3Z_Huexj9vO^Dh3R9Uzf=4aDp6(s(3#L ziJmC#E>CeDFD?iCGH7BnA5*hXg%qJVb!5%&-;|>#fLpa%4kt5?cykDc!^srxy_wis zk3ebF)f%DzL9=$U5@qoi5jv?xpqRsP;NV${aybj( zsB%G853AyoIj)4SLbLuG48F>_TJH8|Ga%L=w9^qx)-O`jO%Gm+x^Gg^Y;b^=j}5U4XLeu z=lym{O!YgbUj3x?!JyxLuYEe`bv{C=<6X4+{c}s`6}>)VXr22o+8^`>%IoZ1^xM5+ zzFu95uK^;*V5P}$S`{N>pxJV%g^KCqs$5Aj9+5Dat%WwUxdNKiaLHEK9&}(Asg2$v z$Y5+rzs_!ro~|d$GVf+PDQK|jrXuee$0F=GMJ^Y7xN^l*E+IP|_^LRBY-fFihDFg3 zK!OG*HoBfhzzFKKs#v%NtpdS_pc;fx%~Qp7w3f@^UGYo`g~32ezISnW*ghY;-#+hk zx~Ex6`{byM^y!K8KYQ%+83kCpu+P%h8q^oLK;OOE34wsd|XXlcz0h~BaR90Zww=)yR=MA^rT_5#Ug4b=`UAF9#5(jga$Dd{<|v57p7aI z@ybvlKoUn+fpoH{aM0$`smNxU%5AKriftsRlrizPNGedrl$WO3V>OlCNbs>t%T)Q0 zm9&Vp8&=a=4Pm8A2%lPG9#Y!r`u4J1cCW%7^`1-?R$^DWwNdYlnpd^3Rm!!sNZXQz zY%RVlZ@XUFPW8~Nmaqvqd|F88S7=TcR$lAq4&DRBR1ERjz{IV&McR$CKL$(t&F%Ly6G9UU@To z#&sXELnY0hH`}RHsyjnese>Uew$p>&43~v-V;ik;jkc725r)7oHdZ&w`SVQGWG5mF z=<$-n3kS1Ta(~+#WP8Tlh3Q$tsIA^UZC;`yZq!K2Q`f1JDtHi`PUD?gV*r0TwZ%1M zzo|=#AV}59lLwwo@&4R*{|H=EpRvRF(vk&DPzc9*2v%zo0oh8i(B6jKu$AD3p6Q1S zdOb}DBP0qwwh{$O+z`VA>gc{dsZ4L7qT8tsLz&*e`pH!x2K9{uYrocf)W|Z_%@2|F z+R$Hw&2eo>*rInf=kV^60jFp5gmp5KQ@pb^^sOQrC?;F=gnR?u$Z$eBJyPZ+CB#S{p`RQ!oXw**?MOxFk90se!#Vo>hts(1FY8{3pPkJmy z!vxnl)%Uc!q-EjFIzuRgnGDBv8DM&);&8fV`5m{R%9Y|dpla09X#O6}D>y>Cs}kSW zIb%oRlkDWwd7Qwr`2?2E8@iZHUZkYDO6bL=7yBw#@0I1EHRV_q72eZq!)$*E&KDj6 z+f2e{i!vF=I3r!bv?0FFbkqzSZ>5qKd#=`8mt|M2cH+kDeJEL=tFVnXmQ%210c~un z?oH`gRJjRe;{a4%cVx@ zsOQENv>wuWDi5@W77Ep^vQk7jz)ilQ%HK#jLgzW1n&hQxj29~x!XOqAd0J{6gTaN& zV>M*pM>>0T18T1<9=8O(VYJq5QW}ncHDJrEes94o+R*#A27IOBAS@;^g+rKz;8=x} zP&r9Gh3$K`$y<9_?_+b1!v547lC8dwhHpzYm&UTT&(FK(gR{^nMuYEG; zcTUow4i+bQ9PmZ)TpHvU zQLUD#oP!wMnHSv!JEN_KUTeu?*il5t=#}LY%Lu|;uUv&tC=h#4RIS9g15h1TzO|ee zXe6ks?s5Xdie8#t9dT&9Hah7({(`@FM1IB&NOiWqr}>0i6( zoW9>W?i?&IXY*wp5Wi>_hn?g0;PAM0H0YwUJZ_zxq0g=pPrGcOG@6~$qoR9;Zn^zI zr-$!&%0OF z$i?io5x0i2{KvzIsi1unllf>h4bVUg5ex7;7zy|!-zRYE*NSW@HiudSy9g{Bv7J;lj>!ui6U8y*H)30 z@JCf}Q8MKm77+8LU#$wBOm;I?DA^RkJHmsYsjY1=tU%DopA1(wy}R4XIa+*ES3v$B z-!SkBhDSHvXi!Kpu2724x>QO^g*r1Y!NLg}0(=2E#a6t4n>QloYy8J%kYp$$VRU$y ziW|Lz4?XIm6oaG89M^@UCWZ`&*<3mmx6slwxYebtLf5X`ZA(lB4Fk@@u@V?Xe2nYC z;10BAMaIt7!!<2MjAjF{!*bF(8~8!H%Ha1Iz-yxrlos~mr|GzTdenbZ+?oZbvA%Wy z1i|5ypxJ?d8blRwvtxm;b}&(1idPMa5S_X+I73)gZG0958W-YMT^Sc+ zt=YH-D)U4$N;!~j)v&RRFYGz|5hqkYm7vGtT!8PD<;)0Yg@P}$>RdGJyjZr*j2qVi z9n4iU6f2`Cu8B#(40RNgLz0|A(xiG1dBkvAJs<;Ituta!vRf{O<1y;5I={L1RQGbx8};%8Y9O>!cMR|%R5(@9*j#kX^M8jGN?`rTQNJt zeg)tJf@7KKaq4+T(ym$(QEdqnVRhrail4PsZPOI0>j#+C0JajX%}Xdl!}vp~Vj=xi z0-AUT(-?&Zg5@e#ftYG7Pv{_8n)>~?e1_!;6pripG-j64f#^&}&v_drfGHui0hhug zwr+(Ovq&g<7V^HfYgxerPf-+ooPqn(w2iY{(Q-R!-)gt_g+D zSEO=z5QSbBy{AqLT?s*x-joAsm}ZF$I0$vVv5Q%Rb?vU*DraQW{S!* zHEwFBnW8eftxD^nB8>#?7&G(@5+1KNsdE(&m&%wPE^^!6iOtr7^+MNUsMG2jG)vQQ z4?11n1UqXU><6QQGL<|kR~ANONK&f{+(1tXGZ~kiPfJ0CUq(K(QWW~R5D%c^DKmhAt4`41#AP%g% z1QTH;K~g|-gfvJ~nG!a^N!dZwPUs@FtB^OW5G@Baq(JpSW{8UQqb533JNCCz9qgS# zD^2C(Td6#ollN%HOq`Ug+1%#7s$2j{M+;lM@mJQ>%9FJgt{yz#jnIwSvjt67+R^kf z*eV7?OvN%#iC~gIWD`8RakyQ#D;#A^rm^KCcwDn_&`!IIBk&NUHeyi$D0v7bW`SLr z7c-vO8DpacA}=4o>2|NKz*oftTW>5nVYY)gK}O%PJ|zj$IRT>e@N5f>AVe%FJyJ{u`sCTYEl+fgYCC=-nRmOL@l83vgaGM|d! zc=2h9AoT`+YSu78+^f;IcsI}Vqpnu+I6e7T#qP2R>cIqu&_&&nVeyTq5BQy}#oS^i zYmiRhyaP0wVA*l1W-!-;w|a*fu-07PmjY7Ea3G4Y1^~sx9l#^l3UhFh=9?ejqTUKa zy(%1b*okAR*{IbhVvMfdju?u-1V18r?#qOp9#GYcV;z_qF}v7w$sd-6De1n@j#W_3 zXh0n>kDB$cZK26Wkm#7zsT#@8WSd$Qb9)^dj;VpX9VMTN1rzkB?Ru9uTK>3CIAlk% zO6!rd5lWS5L^dP_6-GLmKllAp@hqy`zwgY-l^uRX)f`)=&&9eP7tiA6A;OTy1QoZIp`##M;Wrru2>S_9jhcgXv~1tJKWg zsM){4niRuZGgb|kB`fsR;UK-c+1wZ5Hdqw%LZTcz1~ z_=;5{UFF%l{Moh*V>3|m`?ixqs}jyPo>r;?ZSTlY9XEcAMo}u;Oo{Er z9`|4@IPkI2jbxR^YJTpD9xIhal?zqj3UcL-S^SBF+S-w<(tUAp{mJaERYhSyrFSG- z&pP`|b@{#&xd{J0PTR6(vPx^)MReiQdU|LO?sIn}tMuV|W)>~jpXn|#(}?ZPQ62h> z;*7~WVbL0CDhEQ5tl*+bD&Lunrt5KfXVEehmUr@! zEbk>++KYt~J~k+=QFKaHX>k~Fd1F}V=suwIL%Eq|<%Tb|%Wb4JM`c{s#*jSC z%E|E4scK+K<6$(7^j41Q*kQrW<+5s37yZL8TH{-ZDyc$VCM@9DP>ulH&f%q*&6@15 zc4c?XjZD>y@A(BqF|2gX&Q3CjZDae;rttA4)P{>u_R!mEouu-Gc>k?E+mRFY#B)sC7qjmo0Zq8|y`-Aea5B?vDAz=~A92|t9;Sc~+fUbe z+VxJh(pwDwJB>wtc{W{bXKpU%OG{`iw=)~dxRvd@lu3DsJ55|?#!p86af?j^Fl!dk zxWS!}jN=yL;1WXwh2CP;A`_PJLdJk)@l-!cYw}B3M#d2dp>A+P+sw@_28MArn3o+) z3PpDC8%@qaWj5G{j91vf5bU7(J4n0Hi6&X!O`++(L5UOLt;uYnoT33ciRLD8%n9Lc zQn!noy@X!vAWD%4Z4w)#;0B?ot2*)6O&+$Wz!t)Yc)ZToz~K>Rvzj}h-0^jW56mM~ zjbkG+5MtRp+%duMV&mur3DtC$LYS_ybx$&iJNi<{QKczpQ^BMXwo7X-D{Y8}qzCa( zr?L)0viZochOD>LNQWt6!w)RGYodUh^XL2zFfjlw(afg6?ovHdZ(}52e|MJ&y665p z1YS}q3N4h2!QCZ;7YsTD%DFJxT`IEQyf_G2`Q7E1CaP$jUE1H>WwaJ(#C@F1x z`QP1T0@>M}8P@V+mQrdVhGBnpw?XH&YvC`tZPUN(Dtv^tEavCoU1iynaG6ktc9%1N z>(A}a?h;x}Tk;T?IMl8fOL52bad8XnzQOe24x^j3nX%v=2xex4r8Y(_GWL#PSxtIa zobgv{b+^t=r{W|`#tEF1_MtiugJJ=tV5mWFO7f7>4(MAc`m6XOjYA0*uv$F%wm}(h zt)Nm7(=|qw3_&kj583+3rr`PYG(5kK+tGFRCtA|#LFTjVfmN1c!r+tYTNZZA@GXn^ zzS$Oj%d%ELMNJI_N8+#D&C>Q`D`&XOjE=0Zd$4)gd~2Ndu`SmK(J`F>!2QFa_e0c^ z<0Whld#H$eu4Xh;`5^OQB3Cer;j-+{;jUt66P^r&9B2e61{=^h0YljR?1@1z@pSTDG^qWJ_>8nxMohi~yEK#D0(uMoWv4_TTXs%peZ zY2fQJcqTP^cvq}~mK@?LpT|39Hfh~ApI@mUwto4GEau-_y(1EOw$=%?=-}oPBdP-0 zP#@hpZn_fya7$_m*$y!upPRwccENfVxAWqK=5|inzU{1f^S1NS&TVJecI`-J@3vE$ z>%EoVSUH|0$p@D&R3nnDOZ|w>;I&>9bK;bNbpq+M0^HH4Ap_ntKODaa4h`UPC~6KI zCq!&{ZIzDrVE02j3m_;JHkdoGA4h13-~+zuiu!%7w0WLuc)u>{j!Ss&bZ*{t1ayYm?ET^7MR*x2Xs_SCu%grY=z+g$Cpx+gz%7@ePg`Qf*E~I|h%G51gTC;4uc)F9X zRa&0#cFxK5!DNW56dojx?3L=M3>JHpL2dDLs5fi;@mH(v#JDH;wnb~-b}Q1eVAxv1dEc$gqMkiD^vyj zSq7F6BllKph_H?p*|2@UbvN0t!n&Ly;{aE@&*cfN0^*Yv!Imn7{jAGN0sd?Jco?}L z82r7+k^*?B6M{*U^&B!G-<+~WBrg@h=>k4n=HMhGK6DK?_E}1(lOCey3Z1xM@vpC% z4Jf@385dKt&mA!lxWMoRYSVZZjXsRwIn??R3-jW%+aH`>93K~F=be)l9MF614~vV_ z&X@3YrQpfMaleC;D~s$MclXG2&Z+pQecm1L7UHzq=xXrEr36s?PX7q{d1xjJc| z_6xoK!VA@jyjt}O3hd#Hd|x-(^XrIBv4x0Fp3E@9(*3~{Z{q7Dc;l(5f2@GGGTA;@ z1Y7#h#At4IQORb=;b4*YfXZ;M)+lmsxb?)1T=z+_0^hRfpjH7yz&M!|h&QEElG`qL zs=(bqR{=CXVKgdS$CTHRhj+jfW2j`YCnAC8yI@{q3N#lOy#miDp5ngRBe+z#Ww_GK zc!JbGF=#Uhkqh2^UJH)Bn4}d0d5fnBgmJ2jhM!L#jHx6Z7xV}|=G?1-h@(*sO z1^?Q?Wi-Kq`G`$VkBm>TA<=!AT>dd?Y2(hzabk;K-fd?Fw-IP9`qCYBS)nj^r^s>q zCLc6r@GbJaE5S<3?r52JK$*rtZzaC7nX<)OOJ>gH_6;f za4v%59D)Ts)7s^Tl$=G*>_#*zgNnm0sAwrG-kn*m{$`2gu2$xXF~*}AnYrc58bkAo z^*v1`ZN9}Q5o9!rYVJ-rbMhP(MJsHjSaDue`9-^$l;Da1-S~0v^~1|)V{h$Iv@al~ zE)EV=*bT33CuB>jyDT2PW(^t%tH-4}dNpS~E09g9&l6g{)?r!oyu=A6rmLBklI_(x zEvJNvCB~`qir17n1f8C(RpK>6nj50mm3*FRC*G0Nd1~M>uSW5KI9&6X$>ycRx>BS0 z-VMH=WJ$qWT7gn(5@Qbh=-9?0Idf6XT!-7AFI@7Sh2A988rby=O0hc+)zNJ z*j{tjFYm;YF%wJ)hV^+*$7zV=J&9V)1bgV)Oj6%5#q?@mUwcg1^Z9*g4O>G)D0BGv zQqgPmjt5g%;lXV=!dsZdA-y*q>a9w-!GNy9K&KI&&YiJh?)ZZ}lzU9AL;D5o;9*r7 zf&P4cEa#Ri#xqS2m&1m}p%w@>)Ux@>WylD3{i9`f216IfyERa}f4SDvzP%F1%hz~A zY|Z9)55NrCwo5~VDg?jjKAdJ6XaK;4A-I@5nc*lYt_rr^-78;{LgR4LTo{0O{JgQf zb#+(tOPmoe;Uf-l2#Kr+``7Jv_q)e~*1k>3t5-)3+L6gi`3G&eL#>M( z9$)kxwT!zA%^$%9rs71h_G+dv6*xO@HwQrcxV4}4AgjatS-#8RoEBZaW1_6n6V91|0{^#qMEy0+OY!m)sbhz(prv!ysNjH7Cf z_cKF(l{jb)T1i0*_0r)6_GZ(uw4ODDsp&k%;KvVPqx(R#DB&bBsp8WeLJgC&8IyD5*oWzEW{wKoV=+H=;`K*5@PG*Zud?!w3 zJ10(NJ12CyosrLEJJ+YRo$j;QN{?g4Cx2M{IHG8s;L{AN71rdMm2xv4dBc!e1Rk?{ zdNep|^?G=M#v_LuL=Vopk9(dWj~xYWs88Gb{lVi-|Iy%J59xi-+kezPX_0dRvh4MW zF5hvX#(wKqvN?X9x#sAew+Fqm2Ht$*Ar3rE`#iVbDGEVRvTv6C2L& zT!?jIF8IwqIYO>Af#}P12pkwBVO2n3`LIJ`%wQ^x+xYAL(nY7w zYIgeC5)>z>LJm9a6 zzI}EP18rdS=CX$1T7>^tN^0F4`Zk%@A?}N%LF1f86Z>MEw)hf#`Zr z0blFlu-)H(RM^En{00^T{J!>vez^$emn(aJiWhAqxXgxD;#&-IR2F9SsnAO1pJ2nW zQZX8KI>tAmR!U7*^w=Mph<}2NyuP*IG3=BD?1!on@1Z{7OC+bP=0;LLiHYXS$r*5ameQOi-Yb33y#8zEmN{( zapLHd^<>-lvHkn7-8wH$+m9_3H#;8=Ov`rCKImMWc-VHqQr~Z}Q+RNvoA@VueM8o8 z_4n&w4Ws7`3o-%w@*HeC_j-Ev?;!%>fzdLI_5Fa!)!lw81oU2?24#v}wDAl*9#bZ~ z0+M)%NQ@>eC_7xIWS&Zlx(U6Vg_jWS6h123EC+7_SEz%n7MukZZ?~yhw`a-S9{z6N zO`Y=kLmX7;Z@vK_P{bRxO6sTCN8Ti5>!w08Jj5&U#A08_v(DVFfre(5BnpHN=;7DK zTpp-KOtM=GX}xf57cY_xL^8VbJpOhwlsnQBSAQvI<0X!CFCUzKk)_Y3d5dg14ic=8 zbl=vrevmg@TJk(Jwd2jI5Www5wTvpPJA@|&atCYwXjBRLE{rYj&P+2NBUU_QoR_m9 z=pw{SEYy2%J@On|pvQZkmYh&14#=f(GDWKuURrffj9d?=k6MzPRM2Kdm{C{|%~}dF z@mB5g*2FIL6VBMh3^kD6Q}9ii?qPn5ATQC@sR&8;3fg1$BNazfdJ~MET%XpT+qI5- zV^lZ8C#Bbk8|@az2Yj-UIW;1uGAlEH^0(_cu%@o^P1!4T6&a<*ShO&(4DiLhi9;ze zYtXBTbw@b8S+Dpy>8zlHYjDk*7QTprpH``1+^XBsEa>WHi}%*IX-t-2Oi_4cheS;? znG2=_1J;6_@|46bY$deKqhmc7bBcE*-=xl5+F0ASi`AVI_%u78Y>?ywTUeNbJU%v& z>5~!q`JkAT?%c!GVOocGkVb&X$d_ zx@C)qQJBmUx=R&B=H(XKT5#35ZHJv*A9}#odg~tGoq=jRl@;=p1h24M4z1iL+G({y ztiRAzIcA9*H8hiL_CDN4!)H<9>njychk=?+&+7`?#BQZZjia5U|_3BrZ5va%g=X}A5#d{|8lOMG35Q8 z4goaNs8e+spanC`8`VkKl}rS7tDZnV?5VGVcw?E+KtOQA52{$MXFhL`T)F%`_))b- z8Qy6J$4a`F^<@`1lA_pSwR;hz@$YnghF3e#qxQ8Ul*hLcs>LmQ^acS=0YLM$g za>5CAkeQQS4Hxb77qvc{#FcV3r!lKt6g z>RnRAE6Ec<_X^(~Y$S;ncwKOhX{lUlO8JVw{Fgv2UpXEt$qIIZOHSg3I`T3a7sxaS zCblX*RS|5QTwV@0$j2E&ryD41X3~!y6m*#5F^R0V63#YOHmg-nwM&Yv7ibY{D>YfKD-*9=B6sBW{&JqHK3&106Q6F2>A-cyg4fv|+>U$8H zqFh``x(6UFZjftZ(BV_DY=C@RcW!<&E{FIp1&me3kcVT~NS7N~hQI227|2G_;=)re zb;)LUjYGl*c8q_}O*USXRQ*P?ANlKn&|LR z{+r~7UM64G5iCIz24pxOpz&M6?kvO%p14E|-?cED=)T}=hQ9F@bg8AwM@#Jnn8S!V z8E(Xf=G3XNM357jV}~O}OPC|Bn>~HAZ-21lBYl=TF&sG=ahPYNhSLa|p5!Ko%{2N8 zOtqJ>u^VAz>0(hwYEn+;i80q_w8tePLSmR61k+TekJvxSYw4!zLs&syOY@mwovI>J z_p|JP-m%Y*1xW{_JVzrkTKNPTGxMQ^Z5EmnKjCgwiKTrF*w4osX~|juX~PwzsX=3FRq5l#5Iv5s>?eiSfLl}?oEf$bUjMG(20*^=*nlEcQU3@fIz}&eo?JMRf?qM}g(W-cSf96FA~)hrawp`S7VA%Kuct05&A3&u^dvtx9O zAxQ9$Mk1hQ76DtrI8%}-=yEYci<^*$Ze6wHQE5pu-TB*A#Ed9t(wQ7!#l;GJ zr7^_LlgV6Qf5RyWWW{-KNr6|m{Qx=u2>alk&P+MtO*|MxcU^8Ik;GlmO>S{3(3R8c z%>u0{WPBVAF*a&9xX;ZzsT}mtOX!&lPwFqMTXc8U@#J`TSx#$A)~sonU{I4TH=F&~ zns6YZ9|-Q)$H#NJ*@8}#lk<%cli{9DcGZZ~Be3c)uDIh102~jR!%ZX0crTC&>tiP? z7j!ndk4#787`ApXo&J|CoVHn>S~kZY)7!ErYOXbN_Uky;+`TM!_kwxNmyIUCl(Y$( zy?RBE#IDQ>G#CQqxP!Pv-WXPcM>Dr@FI_Xsb;a#wD~Ux*sQINOUIt33iL^B@ZNjI# znH_jtY5F#^;A4E%Pe1IC2M9^DW6c6CkW+IVxTufmE`tHzWGgQNUAzonc)hE@M_k z7~ct%idk=ZP7!gu5>1(qC)6^&H5BN;=AHquAl8jt*(`+C8l6Ynps@Q*Odb*&7(AqP zXBr<+lO_=|93~+|yxI}}9Fkq*Ac>1hf3p;YVh8a9#=^K3u9=?M>EEK2-#Mt$}x!{D71+^ErSA~*7G28$|pcv|-+Cfz5bd63^ zBd8t_fIRDLO)+Ta&E(R)Ome8N##pmiek}nZb<+gArUF(sbZ70SaY_BkiqQmmRZ5yu zK6orKgOLT|Ybu=dC7+B4Q%zE|ao*-32g$<3GFM*C<-s**!%ZtE_^OjaKXEdZ?W*|b zD~6)e7@xr!@SY5>c_XpK>qx1XPF$r+7-Ht&t=<~GHDVFF+zI@X@1`r^3_R>`DAg}c z?QAGo3!cm3gI4=oNX`QBbSTj;Uu_!{MP`t7x)t**U+xcD=X*&t11v>NZdw!9u%@Uu zK}&MDiHoUZ7aA|qmtJYQ$H(lR54S3cWq zVwmG7!J6Z#t+fgKv95AVX7(qf9u~gwC(kEH)0oKj!How_U8K|MUb!yONdaV5k2d;q zFrCy_0v)#ojq0fFjIERg!p?J7QsstYk#CXiDN}EGp~glY!c?0xNFQpz;~kXoQn;g) zx*ZCP(?ydu>V$^1DALKB@q7?TymyhQKm?LJ=&Od%22n;=KE@_oCpG~n1|=+tIigGZ z6-1hd2}}S3rMeR7<*bf&lSA%dglpR@dOA;95tM;1 zw$|HjVbKbjj#K*+MunXd=-8+hAZ*7_8|)qWPB`WYYB0cwuLaHnjH2)MLl&UaQT~{+ zBkY2-f9dvzzQ>NDOy{auIZl>>qK-Mt+ffNz0>g7+RVyG`-KVTy`HamsLW-3ALV&Gt*!|Qm#^Ujds+hl1Xk%$ z%du$iPbVU5%AjCkCRa6bOq|kdN|GhAWmc{(hMuuG1V!xHU!_nyQ4v(28S4 z`0=It7d2kqMj4MfdkTuIFyqI3qb_FZzwS~jg720sRunN(Z{#5%6e^~V*va@oR~%x4 zc}gy2GmqBL{m*1Z>dRxdD=8L-QPYlkDwR~Zd$LrpYi%16{_h4O_?vX`9%GLZfOj4k8N4j_byQS+ju?cKvy3Dejgqp#+AMVZ4@uijfKXV2tlcXkxlXHZkqCxL7u-MkM*( zv#m#rZszDjFlNZzv6HVab~KK%Qf@%Z+WO5jqdbPth0e{mCUh+Wn6R4)M*U}D7E+td z`1(O!UG@nbrc|4Ybyc6!z@t(J*+HJ@&WpBKil!`WNk&sco0iZ()Ry80(8lJ?*koNt zY6VOO_-rU8IW9@S6wgY;aAP~Y0(v@J`CP$xu~{;Kvufg%r7uSQz5!y;Ggz{?Vw+Ds zs?!oin$HCX46FC{n7p0fE5ubFMx?{g?OrYL1#GqtKguuGvUlVeF1m#jH(zjIvcW`v z_WWFG(ju6kU1Ni&7O54feiI-e;C*)z5JqE{{bIG*Qy)qx`S;Me1+Ijcb;rCKNgL6{xNQE+$7jnp=b1`J6ybiI8r5$cG~Pg7QvgcCopsSRW7d|b|&W)=a-e?@(X z(iTx_uvENc#R^yp!#x#N>?n)ZfvNIsm-k%vKkkD{T^rlvX+8k6$AJ5nxX5V}8Ggpu z5X;3?Q&qnC1{WEagJ(dM*jg4D9(#;F*?O%6UovFyvO+mJT6)7A7b?5L(SrH?6KpmS z!-FDM0mj_cnG6Ro!xK!y=|wOtbbI~7{eD_t&qlKUn5+Oj)Jlc4Bb>yki8jWcl(3)k9vU%LjQJ4jN;CaO>|vF^VX$P3}3d5eGBulVrVIOJiy6`L-b2s7TR=| zt`Oa&-x`M8ImM8u#Le-u!lf z!7=&52*kk4rOkl9!d+{`$RLM^ddvd``%oFYdvOT5R3?T^q+8I@3Teh&$F$g7$5oXS z+dvKh$tg9oEMys?5{448t&8NGYn__NPMzdfZ(Jt?5rcCz@^6V|3tL-q%xA6Meur1d z3tc(SmjFO+w`c*orJ)r5 zgw8q+Db2206;q4-A-LGxX(KTV!qc432qS3}6W%PBjU*`^hat&E096d@QmkMb!iSJA z*D|jwP@9=^Ev_}>FJHPk^<$5QgH}UXoyx^2?6ZS&NUzR14I-zdv7G9CAwp&N_@I&u zRzp*Y^FGWOenkY}NFu}WMGC#HCLqI>9LO=$l8clWQF$|DFw0=5wP%MuoYljPrEi5r zJ&e!=tK)@3u8kp;RjgHwCX*~T1oO3NEJj$)_(EOW33@_D#kH9uu5i#*$q~7I1kVB} zhRiVkcKE{nUY3B4eM|rxI7~c^_$b}-tQ`3cCEAFQ;`^*f-0M=`0<%YQ3{hbYUhw(j zPxFv_Hk0=9p0vHLGU7@DmAZX#P5Zi)c!UkN{8*3c_QFIZ%x>mD+QlPhWnJZ#CG?lL zFQnB2nZfBLsH{_?!y+$@x^i<;d}UYWPxWZGo_z0EiH%igtiWb_d|q8&ARaM(oo3C< zK5SSIPD2uLV-G4A6Wg>@QW;D8vaU@@Qbbre|HQG{+w*6uVMWvtQZ(uAfM;{YVo@_J zT2vER5a%gNSV6gqbMxO0cRLJ+JM|lC`getld}OJj9^5BF=SNTEw*ci1@0sK^-ja%{ z4qgKc;;i!ymDb^dufa>5t5zRmCGZl>4&BaSuor&}fHZ&f!`wA!tI%l;IaORRKD-D2>u8xlO{!jXm~84F_SYs_%-rd2=RVe9y7Or&y({e zbqQG3R~k1O4q;dgYgd|en6C+3^FURa$JKOA>6E1uU$FI||2`rtsyns)b5 zA;?rMr1^)_LdcV(&`A1*REm|ULArRy+t~tbPYx(8IIOZ&6D`EbSw|T_7Ap`Uj;f>z zA#GW_Bf)>G*>}>CY-fcv+RDHyL!6qH8RxP|1kxttalVl3YrEA*c8sA{@W9(8vWB)Y zvpfMHN`b0a5B)9^nlR>@PRh!lALcLh8ckHkn{Bwly2d z;}(ii#xFRfsEi(DR3ofRSe30d0C7|xasy&(?Uoz10rhmleAw5j7&zM=4Ia^VqT>^> zS^#H(BB&e(#|IHEk9^Dh5Ty+F;2)yY@TGdLKP|hKFtwj7fb^^chgdk<5t>dbg9=wi zPYA~}dTfLeJ+sA&tp6U_iWUtyUcHqVVtmOF@9W#=E9iYG+oxCJ`&%|YkAKx)=G z;cs0nRWOIJ&JSSenDBg@n!TxikxFa!oWt}qu?p<({%|@?^dzMDXv6Fr>AZX<@YGk* ziNiD-y#%u@B25wxAtpgz4NZw+Az~ui$1_$PPaqJiQ$91NwJ}A=QTrr{IvG8+y+&8JhyJ%-7?0`87AIq{} ze4@gX72I$vJTx|;G_HZiKuHouWk;Y>CoCtXy)$!VU$XXaYih70^axG<4)^%La`dEb zl+%5ETQI2q-E30ZAHLrezNg~qRdwj>wh(NK2VACZ6%x|2qy(Y5MnWSZ31{UWM>crv z*Kxcb8@=90+K3KTdK@qac2EO%!&?lhh+GhkHP!F^3&u%qlF0Ilj_F64j}QEWymoij_M9N+;Dy*uZTFjqp^<$ zk1h@mPjEWeJw5&~8xXG5@XmImBpfW74_ksb#_Md;waK z?t3cN#{uBUK<;U2uZ^p|Fc3_}PrF%x^YiXGQMua-E^0rr6>x{P9}V^{4pTZl5PPgw zh@0<0^T1f_UG#8`C})iLAk;8I#FqP*P#Z~je( zj|O&;!niI@-#eAJLNwBY%7p6n+2HlTn*}}tKO*q(^B#KmaPaW;FBT{GcsyyzC!px$;qL=z-wtWZQ`21rPhh&DGUSl7a zDlVsR?}JLXlsh7P&kVQ&zs6#DL5p-JZ#14>tMr+6UctlD`TWVyMqo8VE4RZJ18%J* zv)HT>ro*q^g{El=lyc3tCg$^TNSon)C7m=N<@g77A21P$`8Vk$8ve?Ri1-K4?`8W~fM0XzE}9mjJkwRA>a)q^b%d z6S=8@khwVQ5fF&2r4(A+g|e3A;elO3kIO4P?*xQU1wudQ#Dk%N60%Cj8_X*}aIAX; zz_T-FC(@`Ue(7)^&F~aHlnKT)y9+>T`UIwomo1{Q*8kvW9NT2|iBR{YPC$wEPr{!o6X?uiNiy_WRI&KM&T2^7L?3+uncg4gd4H|9Q>-JVc?X zE3xGhHUby-XdsK{HYz)+`g)jcn8Q_w2(CiV05ODPS`)g|cFNIZc_=NLfHQL#g!oc| zYo;6s3-I_(%#6oYF?lsEM%nuf-4D@zWx$VKQgB!NMP zw>)oardoeSLZOvLJ0r@oZ4D9MnwaC5HfA+q5ef z#B527EUK>7$rtdtoN0}8pqv<3L8Uq#02|9RQ|vXheO&~H#-G|zI~2!Y7E(I#bxg<5 zR07x)r7+@U@l|?q>gNbRgpYp@6MXYX<*u1=m_WYWh39aXR&p(p=i8_@8i9j~DV!BCe0}X-3Z&$yoJ#nrzVLZ#A*AF6c4~oSsvnpKqNHEZXLoeogWBGeTKjwUCf5{F zTZ7);JC#?|pvba@Z-#SoY?JKyEVaHPp$(loM=!oUwWd1v`QY{9b=uw%2RwcN>*D_2 zMs|=s*L3+tC%aQ2GodI~ER@o%u#a9!hnO{6r6reZAaoy|)8oNj8U6NYe7bC~2_^Hz zZ1a@qXg7`Jpu8HQL$S~5Anj-XaNE8L2b_=PXmWCz4M&6S+Wh!jz+wp^J_R2#17C5k)m1?xxpIH+bnYgW(E?s@?XStmDNyx zy9Tp!APuusp1h-!nTBRe2C1Q|XX+&zM_%A$lD>_;BQJeteJc<3<0t`H7=;xAWcD!r zk@88?;XLV!AHiI$k+K02PrRg`Q{I2sQyjn;WJX60U#REI(~sJbd7sStactH-la|;L z+4l*|Q9W;LQf!Ug2xV*QHHWc$eCjZiPppwfT7E#dhq5`k8gj3+kH#c~<8{F}4t|}e zwA8qG$;4ZMvSMSUA=EGe#Ow7GPxQvwAbklmAiJFJ9fz~y9mxCe3qLZv_Iu2cmtNgF z39-aIDb=qXIFA^O2Nd4Dx`J%%#0s9Z3GZm#cwKnwO_P@Si|ot=sxeEdB%fi=(veKb&1Z{^#$r2;Tevn@41>i=oSiDm+1#-lw2Ptim6b}J z57Xlvth~lQy}ac4Fq`S=K!oxUA8eM7>6_)_UH)eIm|mCX^yxYQ88kgI(o7BU$gWzx zJ#2)+h(d21e{5nhToq6y{#xg_)s!nU^C`y-pR)s@cJPm%nMwL{k}fvqYC>W^Tr`{n zW}LoQ*$pnAIEW+}h zUkJbax`!16;;on7Y`8$TB3DCA3g#y;!y0juYUJNL+pcBX5DrSX)X>KH_UK7`V83F5 zi77UcaMf|-ABn0L)-xr;SbMuNjM_$gc@o|{*9CpsObjv)QEqppVOg5!eOMZ4eJGiWBntR2}O}5*5jhUNY@a8%CMn2y&l-5^X z*z+#KF7?hDsUZ2PJbbT_7OyE20ciFBKwt@0j&K%fc8^`h+!+zwI;6p4QBwkPK_~Pa zK7|-@8_bY@q)#(24#D=r89ZID<>?8Hz$w|amNFsS>aHFNkHgWVUU83Wl?@GG;k1dx zn-BAhK15-#DF}vGLkJ8gd4~YFYwGpbII3In-45B#lc<)Tbd$?&HHypSwmLP)Y;}S&8YT_Zi8Q*MB3_Sc=v^|J-A->Y zNBa%g?4@$qhD`P{dF*3kv0ZXlElxFMu+4O%Rdu4Gwe0mR<*r>am(sSxoTF`7EAmfH zYIn(4yX7ludsDXB>Tst_l@jBb=O{8;JM0*>7$3SNOQrM;IVz+l87joBu}pqy%1$Yk zF(ThWOo}8wiAeElGc~+^jCdq$l8Z)-cod7ig-B#JQYMWjacH+Fv{MY)Ap+UQSGL8U z)A^?8vmy4pMC92PcU~&$B>J);=A`tkQ@ouc@g}7j;DIk(w5gMmSd)^PB27w;b(`YM z$A~h!#F&jVBO$xQmyNW%T;zOBYzgIJnwQ%luGra4qKc5mmY5PS%F9KRC~4%cxIR`q zi4~-euFpVIG|9_sizQiZQzXeUHpG!2cEsJn4$efAmx&=>JBT1V#gB&Q5f|EV;fBcJ z{6bUQh?0e&?-+40SNXphW=&D{Kql!A+LtQ#KiAn;A7er^Zs#+`!1$^2g5#u@fRn@r6bQ7 zT+7BieK~d;H{G;ZHXma^$HEogWqiBBmkn0fJo5AqB`F?q(HCMa!$*GCD?J`D^*b#| zi)hsr)mK5Lc)nPmC02ynPcD>-EB0K@Fu2CJK$t#jvi}(KEtFz|Sip|Ki5$*~#WN2k z|Cx)Mx|w3Js&Kb6!Nh>%;Ff|O`{s1khyvdeF>e*%K`i@hl;ekm7jVXQQQ#y;SH3a( zH5&7`04vn2#e#V%dl8$`q>Dx0?pD^*dI@l9Z(XGnEVMLMU2yVn++J-}<;VpVUvU_U?M&?Wi9L_?{;N)KfnA6w%yn^+SQ(HR*N+crg@!-iM{siN}nIx zO7Kg=Ao0{MWo)R6_-@8KmiEpj`y59}D3XS2;2@QW7Yi~Zq9gWD_5^nAwLe@8M-wjU z@fhF%^!P=!9AU{K|H9bYgy)b|fyGl5?s}M$>7cn?){CAsD(&Ki8>g{5>vdbK0s>0niyL7hihIdb z{PT|7C$4xgzApoc5d-!$*JFSX8eM`WLU_6uU0UO=`(>e~;0F7I6+0dnGaHHh09S@0 z92Bqvz}%865Z(DCe_^R zJ*C04m7V49oJ^BhTrPP-I+DrLD7|^KvaA^x-rhhOapFYsM9&bDe@6HUVH%sSAW<`T z{x^}sXp8+kwBOfcLxT0g^8xTvoROxtl@V<;V$#8+{Mjr_k%Ngr#N0SAwj+6L80*Sd zms4ic7T=nT{PsLUTnW$k@Ip0P8ZfKkU^VenMPfj3ZufSfGD2gUMOX-i7D%f0|(26tv{A&kL3K^qG+ zcNi5}X_1a_PK3{Z%oEHWCtx9Tl8p4y_7m*MB{hPFU7uQ7)IWxs0~v z>c)s5f3*(`ovUtG${8 zrZflLSKrdpjpE~>UI!F+czDoX>}?~3v2_@OZ2e8{#EehOi~Ib!Ub%G{gvsmChl7V0%_|)-b}45 zpI6WG=W@UqWNc77DnEFoXAB?vsX+5?HG*~>t>nK}Gsky+cx~F_P7phtIZg^iaOkSA zTBkxg<4*ikBeA0xQL0y;5vHw+brhAjA?b3KIL?R`tq`@^@?wu zvont&uz@f)2Qk+ZRG!Pc^BO%AS5N%ZtejmpURzYsZ`L&&0~wm4BBxdS6Bv%OwH&2H zqvkO-(Q`tU!vxvH3k-~T(1wH~KhU&lHlp%jT+aFCuyRDq9ErjD!YkUg!$?%{^G{i( z;jJ+Ta1iYMt-0f$FU-o0jc?14P}WiyWfk_y48&youdU)DeU;Cbzm8*bzYGQ!H9n95 zBH$o<*!k>^7KM|oAxwj1vk$j*FKF zNROpw-c*5L40%<6F_|MrTPUVvFR1r{06o+K z1rp~NoNgE^3c=dtK}8-IphGYR6^5_Lvywkg(ykg<;H5i27^LL zeYNp)-o*~sL-!L9u1{DWy$dpXpJDxC(GjKT!u8gGwb2x`M5lrv0g2d}0q3-+oC_Jn zm(v`q@V@yPfb>_)7P6}FRS-}126Jou8H3&MA%qhgfEVF`9bp?r^Qn~vOS)Cd7+YJq z$BP7vAuQhEbPhi7GfWlVM9~|2($+$T3J$`2=Y<07q{FLvN5d z=40T?)KIQ4t0sx0UWuxhoPPA zOfUs6mDY|qcV*u$_*}_$2D%PD$(Sh#VsCnJixhRUBdag}_cCSQZZ2Fo))_F5@8iuA z?NF9>+#?y24}6{1sj=g}tevxD8G3Qhq>|7#TQSSgH=gxSCKI=~)-^%ye2qWsx^kW@ z1~T@6$D84uK5btYjal!xi_tnG+Uy1mi$;!wgQOo+EXGE#vsIWUm#9hOV*dTE1z?+L zfRv1+D{ceTo+Hie+sQ8BVEC%mA@w$bd)9vr~p+U4-R`z9<-c$}*X7x@?$ z`+|PPNLWh`UgelDye8&#I1cAwPdQ#d!SV18Tv!j)5O}++DWi z%Y_NpcQ4K0TtE}gP+a?lKbNBSsP)htyo3b=0G%C$g5o2|NNn*4cCIzWzzUzQx3)x> zk1EmXkWoy~( zMcysmzoY(Qv(I3$?JG_PI2e|{;%vbxfgx{B!`}xKiOKfl~vF&h`7^bE#{)o>M3!;_A zJ!1(@tDe|NES26f1QeWga7mXbxO78d>`cKa0#(VTmQdX!5ibw*QuAtAvtvp3^bQ3~ z-GXjosOA7^8XLATX;JH@fTw5rt}Iq|6G1ARJhk~41YNr4vV-LinshC7u>&pR^jk{w_ zpjhhToJBVb(W6<0;(hwNv$7iEMR^{owF<#3(>@tqj_Zz9tRuOvP70hvmSB#ZtZJ56 z52Q|{L%gTkZ2LH4ea`agYuN#$)K^Qd!Ll?3xjJUWFW5!=qo6znkCec_v&)9H4)EqM znO5)q{x2N1zOeW3;oF~U?;Y%a?v1^@!_V!tKL7gX-g@ixxA$NF!WZ89;uk*uqXF}k z%Omb7W0=fAhh&pj-{S<$_t zc-RmKXKxcSpe&NOTQKQLqtGp~fXdf^!ln1x$d3`kI13dHu-<^d_yc>zeI`(vO_I zGqAWwgsqDaFrgLt)5x3a5pmS0UC{lAesr8MfOVc*pdRJ^{2HM1m^`jseZbIlj<|Y!mQo7w6pErji;E%0p8Bj zAnlKDcsgpIn;>#*Qf+s6fI6L3~Krw2wfapW?-S|oTBf7Nmya2 z;4gUWeIs0^>2M>rS$C{KCE{b~z0Om6u(dv?gxMS7f{JLE#XAp1BM8d7mxICQi~Zhj zf9Kt|2f;~I%T!gI8rQ8v2x-)L_{r*A=Kb(4`jhlC%Im!T$?A;rI=sgIB>j|moiBW{ zI#+p}H$Pb&c#LeZ-};1=9_E$4_z5e$mREZF6IOb?=wvV~06}PVUNQ)Pkl3<_#m8#x3x%Q&b+K;Hch)J5-AXir<__jN24YUnr6yn zRphYE=M%>lhb47t(2n|nvz-?q%%Zdw%IF46s|<-YeFC5Ovt2VQNI#Todq+3*066OWr&mgFltlQDehSpGw#gr ze(Ku0ntCJyL=d;0E;}+nrOb3g+h`d_rr56$g&$s|4t;TbW8$G!ST&8&cVz4-Kvi#A zmWv`N*d#9TK>;Ms!V5@FLF<%O>P*ho&bYc`L{dMSvLFtrrblU%b&{}05Wwri0!={{ z69{*@SwgnQS?Vs#BX@Y%E6SumWNFj6`D>QY2J!0=jEA>|rh+Rg4}&mF zH*o5Do0m`O&CN-@{f4-emsfb}&5c!1h1a&y-u~D|-hBAR8(UShm@n`5^)K$K@!H4K zc=+0`8n5r@_l=KjWmEnC_8XU<`@)V^-u$>$9)4kSjku6t!cDML8sDOKBoqas|A)9U zfwSwX@B4Xgwl_1Hk=`4RC7~JgWLZc;yJcYuBdpQt!CJamB{a7mGW*KN)qi2s&xThCMm2kJ%!YJLgt$Hkns@k;k z`k@-*JG5#0rMkPbqGJ?tDJ<{UR*4>R?6^b~*X^j)_*!8%)M$&GB~rMxoyucNe!u9K z&v0%==tuBMnb?f|DyfO~94#6VQ)1onKa_?hN`!QZElvd zMq93_vD{M;VXn-0-F>rvHG#t~CLw1eS7bp)8#*@2^I}^&+!f`5`NM>Vr#H`N1z&$Q zj5%$jclSGOxY7QeL2X4GaSu3MF{kaN`cXUmeNa0G+0x{G?6A`I`?Q|B&g#Ug2OIfl`ije!L6uh%#Zc!mBP*a^j!*?)hmQC zuIH?9QqVIw%oG)qKwde<^?hEcz+_(J*zXSMslBoKrYSUH#D7hfQbC3zDwkYizC`V; zS6T?wNZyFjHPf9Db;o57D`rSJj;e+&?oQ{Hhq0|3M|Y;z=c3+0KcWw*?0(;tS#^3u zwP+>Sw+RGkpPF0e)~oE(NKBz1<~np`q$Y#**!!9& z%iYtX6!!~vBT6fulZT|IZ`Ph^cZ=$Q?x?AK!=X?jmmKUVR@?zm6+ZKN7F^0m9qJvsd{UeYdz*;RlrKkWvu4*1gqjbq~9>`byQE zaIWEm6I8l`N=XeqU6yB`a`mVzAF`5?29zT2Tel}F(MnUvYxWA8J<3mx0p&TSl)Bg3 zx2lwCy~P|>i;gHIU-Ws8?^9XZHibO&Y_DpA-YQdX3q47>&ZBDYfX5OgAJX5uoLjwL zxxhwWRo)U7I>C?b5LQe2)fTJQt?5^3^oKH!2s<@f*>T9nQcEf4>T+C$ek4=6x^>Pq zZwz%cK=ZCSV9TxC>nTdsf~PNNp4qRfef3=HQnxaCHgCs0)e}1_>Vm%XgOQ%Xpgw4D zvs!OC4=8TX&usp6Y`;7|QDk>XM-DBaaNjI4L6!)ohBYK-sitxTk?M~KvoytVC7TgVbo7Ehn zpRR4c=zOnQ`6i8IYH>lih7>ocS`@1sQ99p>oo>5Y>Dmynr&L=(<+(QcH7x;o9W7j1 z*|!^2-}SQ(Y{XlFV`B{KdZSD-cKr>zT31ZR<&&R_;O8r-6#B< z@hvIv-UhA=n`+Afc;2dhS}RK_6XPS?XSD&}_g@zYd%S`##!{o9H=Pdw3)$nM-~+J4u2gl z)dbBXX@akq(lpMUjLd|mK`3aa-fj@aV=OI9joVzRGl`{b8Ua6goypwv4yDaigwjrh zmf=}-C2AIq5?^ZT0;8q&wo;ajh07wdH~G>nB&89d4Jx6H*@H?sCCn!=vAyi;ddamI zOR@|lH8d(Ua3vfpKBG8OBmCTL(-A$@0bbAgxryY}-t(IIHKRMu3UAt_=v!r@$Bbq~ z;RtTAwd4Vn=*N9&^@wc~#iz36>4ZzoRqeZ1IkaZbxhAwc;XL9I*DIHBvN%xgm}pTw zkGK*v3oeaAl_)q$%{P+UwIx#oj^=qlDKB51`j_`hP9atE;wL7tjPJVceVQEy#Pg3! z79dM+_j2U8Li;uDrkNze@?KlfYKwZL6NVl$D0y%iv?#PVIQE#H;2xz!zFe zY+AUzmn-Q3rAF&RTLGsNdV+B+MfxyFl{-vQpdF#vOiQ2ItW+jZ7-7??$W6-zrF9W+ znu(5`2>+%HCF@eT9o6N!{j|4IyG-^M{UpCto2Br6VnBom`H~pI zs#c`P7H2|90&Rm5&!%jdh%~!Qr}I3i1u+PFFhve%<=1ZbgFc>VQr!tmlAyeyDxy|0 zsOrGZAh`S8Z6ckMA{W$&{X+VHz7YHq*B_qTZMYA{qQ~nI_py_ z`aNY~98ltJ84bIYirg3mN0cgV0W3~GBcZTfPl+h>z1}=!(scT5*yv?G`nZ*EJb!yP8-n^{4@Sx~8Mbqdu%b3}QCI zs9}}8ex8`o$gGE2U56?4ZLAUC%xw`jw`Yxy+@EA<(LDg+4xvSfv*^VcH z8cu2~H_1TLvl(~DDMIu%VQ+P7qO@=V(!RmRa$uIPH~OzL_N82t_O`sw&p3oQb6>yU zpjk(Ku64Jbk>NA<`-fExQ)sh>5SEACzfsfjcAtrF;&-Xro25&K|DzR zULlzV;|VCJG^8Q&5*uaI*Vm_Sq!BK%TNQg=4W1A~gC*=z%{NGQxKUrHq%oe>P$1(O zvKypBN^jUws~?*+%;w41ucv-pRwcq7#ZcE0Gw5p!@)ah9bcOw3&BESY!)xXDaBKD} z90rDM78ngBozeHC%JhicHK4boF7TkrLmY*afpt?e>5z@3+?$ehER?g9dPikl#`bHK zoL8$&aKcc_TX?BlI(6(@FOrdNig;nH5JQMU*MTo#v;K!R$+OT+n2}a8+@_9O9*@y6 zjkZlQ@Zjn;h%6JVYv~p*9-*?${x9NE06ZXyuoG$vQMbKv^oIR>3cWKekeL;G|6nqu_Nc8U5O4QwKPkN!^#jr4 zmRH6Z>z7#YJw&ZBuTy0%yftxLDc71xyqM4;H1_Vek$$fRK%c{6;xPvLJ#T<&$Ak^6 z2rP?OSt$2_n6`?w9dNNpX`umTD2iZe?4PnG)2D5w!!1)sD@O`t)0CSq7eh-=Ys9fA zk}6vzJwWTVbZ?_5_|=qOu7Eq4;;Q+1$3IPvoqk}loQK4H3mhZ#Ewm3+&&If%&fU#- zC_Kp_FPEd-Z;N$3D`{}rW5Bgd_;*tu-P{L`2x%B#rc*h9Iacpz177Q)b(nEZ!SgX<2H6@w8g#OxMx)4El0ESh4C z6e~%AtJPfC!ATtQIT_!k*P%i7x;HDGrE#qo=LZ!gU2M78Fou3$ zhDkFn%t3ky4dkP~+87#kLvLek7Akl5^eRVkT)+u*!wM%Q8M8m85Z39KZIzOka=@HX zn#oTnN^E&AEG#&8DqS|rD=3RKY#4g*rNu6wJgW~f(J|`K%P`o?#(>4ic$3z(K#aU? z2&c8a4~QuG5-(>nN6;DwH!OuY7pN32kXo8FJ_kx|be-^!Fb_%$cSSPvsLEv>lesi1 zw^K5%U*Y(e-Krz%KdnHD4@np>f;i*?4|U7SQ_vpDQ{HT^Qki{blpIP zr)7`hbU_-p^_R+3wzFf~rBl+Y3frYKv@AHq`ZWh|qXwTo79)m9zWN7N3SUuvmjJ@dwN4m5+WS^`vG zW8I?PS~8?Kr074!x`kV9rIpiAdE4CG`&Az^5LXx+_3{`DBhar_jw%N}493P}tx01f z3+~hM6z(LliIHG67z{J}7#wj#vyD zv63EtEG3(Dm24%V7GXpiZDk}(9Cv?kNNrRK6BQ5_Dizy?3Yz92ygZaa)2})EGN+X) zd~X!8X;auXfeM{W>9V1`V6nKB43L?z2r5%pW1;1w@M7dM86Z-LmZP<$_$gDPU?xzUe9IfDzmVTu{MP3Ca>YmG!D``cJCUX}4~1I&XhZzfC)a|mjtcm=!i zRI>15GnAbyD~wVot*Y*uY_!UAU8)7?#tDQiET!hFGY*_(2FVDVwx%y z!5J{GUj-K?80$DB9?3W?e)D3tNk1d5QrucwPeCPvF&kyV2aMG+OKFYUP@XObqd28; zE}vFg8j>QziHFgCMhY_tbrycdyH{&h)sBzd(r#_-6jesCRI6Gw!6gvqi9N|ut!kwN z2jCv_&D{628lmEL)Xo7WLLPG^TK}S48*1mmtV5MJE7r$?i_Z8BLPLoR03+xINVvNa`iEaDaSEXfBe6W(dMl zf_E2rXrbj^U$@4jFg;;GC+XNat=x>fw4wFosVW^{iIi(_6+me?NSH_G^{(r8)k5SQ zG2#px;#4L-NC$nP&sC&d6VD*S5?UE}!(cXd2rZ0Lh3)O|{~9SvJRlgMb_xS7kTA4w z4m_Ydm2rVM-A3O>(?xA-t(E3B;Pi#32W~j+@nUXZ+=5_$I;4YasZMJSsF2z?W+{C% zuLnhWEJ{!ZGZ4z*u!CD2sHrjqrABG8rhNpbrE+xyeIyx%`EGOyCBb!EX9#1wN}(%k zr;Ev5<4(*HDNHg}S|M(!&3W)ai^8b84LGaeK(RAErmPSS$Iu9Ds!nS< zg3(t+YDxIyCcyTf&w(`_Ay5&Y^MUFKlyULAc zFa{OJ60=q+^n{9Zi?K$aS=ymlv{XO9ptlejq>4Lf;mBk3hsyYk3L_umYURgke7N1zf`@%*D8}XCG*xwgN}%dD#xbrX|M^+R>vD(VPus;xmM^c&N29#!8%#J|1#KX;;N^0{&pmC#VWp=C`G< zjZ+{wLYg2;O6j(RxMc2f3Pu{6TOSL-)y!ctG|Z5S9E*_)qZ+%2?`7>f!ReO9d?w%% zy})%QcYo~)`@iaL5>+482w>l$wV4f!4QJuDow_W%**0SAmz$97CwD{}GxXh2?S6D} zQ{!G_Z=bm^oj)XF&PW`R4{OUg8*41YzOE~MrM6qUIj9n+dA)Cc$idh4QVzLR55Ceo zn^nT~Q3*1~qj0AkSI`$bFj$8viA>w1~x;lj@o_&I@ z`Mk>KugkJ$Fgb3n!G|kvjD1&!^FClx`4=b0Lf_f2TyaF$!$M}KBRWI8!*8EvSxDuU z?^Ud6d%Ct*>B6}~w<;Oar_!n<=E}?9MvljLQNv~`b;ZtP(-_=R((O?Ucs{I}A`g&X z6xpk9=10H&qjj18%t-UYRhvaGmDsY zM|F(T>9=s`-`y1VXtPA_VUfQXm!Px!n@Pq1!?aMi-HL_tx|Ns7J7E*QTv4nhA|no5 zohVN>Wbr;y6K4z>u3}gKLumwodnq<_-Tl@t*j6$;K8(S{F|kp3ra4(Yy|?wLc;`eQ zE0j}OXw%fSB{z<_>3_WbBKn{g77%O+YJon_{_uScby#oXnqUB78ZOXpovF4p;&5!l zwT8nHI$yOK(ZNs2VDdBd!je+VD%{8|+6%E?i!&UV2&cU5I0YrKj=Wzxl>1f2X^l6# z$QZd3qFxML+Kw}kIQD6inzNxn*oWmU?QxcvHn#_(KdKdVj4c@IY{KwhMCN zc4tXFKOqsyzHUx;A9T8`O7)CO>6#WMU}`XN&S)|}%5lSKS7y%ZeF{{;2UK!uHOw~9 zG8C=fHc<&EEI7LT`s*snOosGo94#%gPzqghSk7>rNiGqDtm~c>*P}FAb*u6wPd#8J za<+lf%BK~7qyFyH_fEZEufGyNi%}Wka}{neoCL7|mL&pLn$s}M#$$@*oiT(fxK8HK zaK%{<*GVZ4R)(&w)dRRAxQY7PkxX-kN;#!PT^rvMehe55I0JkYyt5$V&8#L{WOZw; z1XJV#o>*Rno?}(lbx=y|LZl6DQ?mAuccH6U+)~C0`hb>W7mz!AsWbHPVZ`b-#YkHDE!|pEJj$_{EH`o{tgNKz zJ$}3b1-aI98b|$f6!k2AoksBn{kM7P7IPxrsKFa3T3QZQ<+$5w@e;<+IEUq;)wZ!9 z=@z4pruYWE0X(?lucXBNQk+6=Q)m__^$a`fbQ8Z)2fkr$N4RvX#Vox46!}a_;~nHB zSGk0jinfLNlX9JO)p}Eje{8~i!UlGZi{YSv?Edsg&{0047bG0zssx#jwyhi4b5J#fJy=eA3Xwl(Ha*GkwgKffLrVEqaG!>4 z_vXw;vMjee8J8^0btYpdpc+@^9s&ttx=1LAkd z@-2n%(cL3nM;}9;vLlL10|qOOF0QnLjq&-Kig@kpJ0f*|zYafE#3YVRasYV0{?Z2a zqH)lae0zO3JJ&e2$^9?3tsaL^yj#sJJuc1|97Tz6?bc+3IIfxqU775CjXiAHI9I<~ zBWFi;?$*)muAAT(_n%I zigKH1sU4mT_s^VEefDn0u#GzWA=4VS)!1n#QkLq9TAbX~mx!%sVTlTb>101mV{@&H zx*g1Lopztk?HX6;>Y)BZbLIK?!r>HQZ-2aR@PKe=`|guD`o&}7!{hf#b3%u@SG<^a z98mX)`(}WkCgY&A5*zalt3v4ZPvPM z(7#JPXpLu(>trV>Im&t^i5%mr)FbhNfYl1h*B)2+6O?ENzWu4f4jbzvExP6A#M5uc zdWTa!(ig>RMc=f9y{jArq6L&!FPDp71;N=)$YQ9ItBsmdocT>&u4Ar!VzuQ|+`i3W0}}Qm-!G zW6Wg&RY&Jxa>WK0^GEdg(q?q>0B6MIgPA7CY!FR3*c$B|P>JI*biYHwy| zwm(O!>jh{Dml8x58i)^~ebUabB}F;3Lz*LG9Qem}V=s0ohYpz8L47D1xmQ8aI>1&Q zUpD^BD`QLLR>j2gv@*{g)vyolw5Bq~BpNN_vJX38=T;cs5B=uigi@`#oeY_fjAMSI z!yMNWx!_jTx)K6>-sNS7TV?W)x~(L;Ta8fKuvaAnN{qD@js-HmI5Zev@<2^qI%)*K zKNm>YeRIsh=t2i^VrYPr{)&j@w3xcM5Q2Mij7IjU-b(y1cM=j!&ROQWjpQ+H{41x^ z^2<1D$wweoLI0! z!bm8ar6Jk9*~+aW*hknF=9>`h>&gm$$AE5FV_8!Ef>rny?d~VDc<@)SY``3*mat)i zG^Vq2y2CuG_#&LzMXTbijMxa##SiipnA|u2ozo3pwmFF&Fe{ywMJp~vQ696!%&+0R zzCd~`8fg}?*Xh%s#=zQR@~zx?tafb(@%XD~9ee<(Xj~2d3_XPJakYq*H4wF2`88+Z zZ{~T5J(~2`VrUKisA2Ig>_fKpy0x`sCR^9Bwo3YE({Z)`fY-{Zt*|3%jkjlY5ezx7 z{ia0Sr!_YgXw#O;lu6H!VS7q^504$&RQX{0XZM5YWTkaK&tvsl9Lj#k3IcbmT zVABD<9dFlbL9Dvx75(h4l_o4Gx=lw_17%G48p_8Sngfr4Lfr-%2ODFXb%#BUS?HS* zRnRJN z_HN7RS`^oPrt+mkekRXL=f1Gcy+sc1d-b1NZVst}hfD9XiT6>xpOosh+TG*sE~U6s zYAf-Gkp8gpU8|fYmG`xJTkqbc17lY2-KF}sD)wzrTkdrm{97{ZH4<1G^xtw^qxg`6 z_iKEPYkcW$%{tY1w}jm$oueNVd`M|G$rXIkw}V!!*9zUQyyX>j<&fz5heLwpOd{lj z#+D`J?fP$MJ)x%2uyyWcN$V5hY#W3Q5a3p>Q)oa`5=rZQM_l(>#aoKC9*Jvp64v6R zacW6A;>e}5oWXPDv_Kqt?wstk;FDsLO?VG-h1IE84q%g4>)oZgWE)k-=3{;;T?y*N zs^%CA8c{9jP%2LvWv%#q3XPA4xYe~&CFP4d6Z=^=m*nt5zGUusg8!nu*f9oLu{n}u zgjGt>&FU4mHFNb{IC&Y{X>6q7iz~<$@=Ca+3usY(!*8_)z`4`JD9Yo=5hb)uuFAuc z!uF|39dcZ_M^0~4AJ9oS9>aQa>?cX;l|ehJw01%%3$%Dsuy!_i2@^CSRznYH24IH; zJ!aZdYGnN^MURfJX)9^i(x`hSrLf!af+Xw(X;$Rmo@lU(7H=zxc5{(g9Q|IivUofR zXz}R!=-ElnXJnt0JddG%<(FoKZGi6p3nJJuit*RKnH7h3b)Rra!TVmr4zO z8imOl94vW#d5=!;@9|Chun6w-b+aWWd?#&grIgo$I_VL2R^#X`9wQ(#SR}WF5!Um@CRFzg47PuiEeD4%T{d>q*Ra zu)fU4YJQX+;dnUW8i9i)>6E{uurxN`tEeeq27^cPZtz zSw@(-)%in=>fcbAE6Hhg39G@cp&QeigKg>7R^|#NR^(Vux*>7bfVp@yD^hlr+p|`y zTOP)-%GV*vHLi!}I;J7l5r>yN4f69dmk)ZM!ZGobO20{Ed|8RQ>yFuOKE$KqO>fed z-IW&8BWpP2b(Ct?3`xd_BK=+nRyWN0SICNLT{rBjN&0n@-Ul|aq6?bW%skxY?dC#8 zdAnCtdy*>pE#F9`uc@r(ca+bZ@>x@xqn3{bvei~4jCbQ5QExjZMd@MRgIO5U;%sIG zAuX__6LxwQpFcj)z>C=@P+7uB$+~DWbIsU5q0|$59Zrfvp`ma)h`aBfroZKsji=^1 z;Q((xD>TRvq?BI;*-UMB=u+RO%%2+UTz2BF;;d7D$F#?^7FRE7&m0&gE^Z zm8!f=q5Vl_AJ=o`Ej9xjrI343o~ z+Vo187Asx&T)4HCXLJkItufLUuz#tCOBbF{ncO|T)@&fwu#j9O%OmEh9>f{CXdxh$F^Ykmfl>U@9zuq8J?@*7} zsLh7-?b-}m5Gk=$+ij@rm`(PM4b%se+YQxD8;9;yTXCj|v6w?VLu0-^;YqXyMDBIcvCv z>Al#lStH(Wxoom;*WhzB_>>3Bg=)Dljq|fL%G~SLBbNA*lnx7pEtGnzg~DLavPpyK z$p#Btxg7Ms@r^UU%w{L%d{{Hgw`lPR_W^fr>R0nuUvu{RZ}>rK(>2mR?sdQJKAQfm z^lzs>mi~kEAEy5({iU?4OVusQFVDX!e_eh@{_eFW);9ehb@x$W%g)5{^KmfH@@vzl z^=;?NuJiNodJn;DUigGv!+to|Te-03I*hS#8jpF*+;?@Wwek6=(!%Pk+-cPdEhc~V z5vCRHqHt|2hVa8~e*44Dy*>`i;d(d9q6H87TDU@&Lu$564|9Z7Qk(0nb@I&}+^;n7 zDSBkvlE}f&I@m9ru(c#O;4KWN(jkSK{b8Fr-3`nm+YmxuSq^~ud0Ib%n-|e#2bMfz z-d~+Buyn&MC9v%QN{Sa{?Ns8FKZ}YGvH5M{$Th$*UBgYly1T{n?~R1-y$WS1&NW+l z{fL4#KgPI@7v?qdI=HmZmg!3l?hX^`96}fnX8op!#mN$s&tL1x@;0sZqrM6e9nVXf z;lzeTV_i@fPh*6Kxfzm7uAKSRW*WAJRn1I}`ED>6yqO-6Y;@dhuEmOdx3+iRPBg?? zMYxkUc1Wb6quRDgtyO8QSHX1912713&^E2ja09G@wWu(ki;;*&W4MOt^4> zl-pH?bfu*?iA5xDCAPAhzb!Sst2hc>e~YlmZU_?-F#WLmj$Hu^5JE)*rxrk}GnY@f zB@8~~!Q^7fFd>c#hLy!6Gq*|T{LWj5!q!eWt;9sNHVjEifo15~%G9=_-`pKe?(-f> zlj$Hmr%}immFs_F&-6~EkVZwjq9ffZi}hGq6E2Rl>j&8D#29ksA}U9xWzZCrQon2m z$+9yegoJjKLOPQzDTTJe%{O)(@%L_Ig-9DCeJj0vVW%_H2DR1Lsor&H z8w)1-DS&3I7@tEew;hrkE+_RKwTLG>B&$P8sdch0ZeuB3S!gM()+T8EU5WBSNGXk0 zRKR4JeYAtKMvN+41T_S=Ssna!*vpoSxWO~@qKDd7l4=A@ldGJ%@Bc7yiYFUH24*G_s-zIGyJPez>5z9mYQ zCM~dp?bdB)3l@mgZ+}Ab(Ho0)t_!6dd1YIaa>55!th} zw-lnERJ1*bm7|`-tGhp*gF))`dle3hmft||!_B(NQr&G~4caION($G!zxotI%Pww#b5TC}7jqcbi|5?SlfOe! zR^xj309=8c#-^W{mc-1brqWJdzc0)9o$(6N;cMfn?;!WwPRdKBqDQpCFqF)blmgN< z50Z{cn9#uNg2`kGW|kRb=CvaYnk%NzdF5fh{HWgD_R1C@g}_L8X|xDQ?X|!gD)XTK zp}!$jN98go&kMfLOWlUbd{6*Aj$2nsy8}%idA~!zIxS(Jz8OI_a$LJY9d@~G5}z2g z$ahZu()Wm8MVA4BBg%scxxs_}(NZvHyXP1$R~q$X>X^b3DUaH(BS8n~&qz+>3pxby zf)fhnwTAN1AF6SU?twI6mbinoKJ*-{g5@8goc)(>*QLj=)S6wjTXTiEcV4;;)wEEm zH@b|`W?d2smDE$p(Zg)UJ%mhNxa6eMBT2KG7Y+24b0``G?^CwfjvnTJXR38uY!lYGvsh!J6c+L*Wd>J;Hhhry)xCSf5N~wH;Dg`~C0zK8O8o z6rP2^g+`U%WYo0W%KF_BoeoXLQA>?B-)gC>7BpjKK2o92*O)vV!PRW^Wk9i5%X{?| zUs5mX)6W3!kxVz|`iRnnwVVYpruOr@I}_2FR!R&z=$y{2VtH{gX2Jaa_YvbZgqB(Q zUSSGORc`;V6R_GX)uVdhUX0&)>Dy4WX40x!Y4<2)_=Q@hoqyG(fdeE2qgpCU{i#lA zEiI*~SzqYAcO2>jJoAd%TP*y5`iHC_JmBt#`hjj>eKqT}n%eNk7=K{Ap{0IbCi8Db zb}JZ&?6=aHp%}B(v{Xjv`Vnf%9xTm7D67=ZEl^s#+Z~!7Qv2a~)ERnambK>Qj(v(T zy$QJluR=Brhz`dWrjE7yRVQOLCG6PfLJib_&kHH(ak>RQKwC#;+l3taR7^_Ba+d`u*TZPS``{#4?~E&$qupqeL%yZ(iUsDgc1P3WF>ywy zA~}li%L$e2@7+-!EM2cp$Ba%=?d={L-fmlSP%W5|J!L0~87m~#d2xdT_gqowZwy{W z`4n_*BpjRiOj20s*qdcEqqSV4gIHr3P%gL_K07ENXi!d9Hqt-ZeVLxFic>DoLtj>` zebRPcCCltOFAbLY=TBwsqSxl% zt(GpQfin2D-oVk37qR|T zjU%^)+{oS}*FZ;Noy2HK+;RhLV??1`xQoq@u$&Wm>3N@8KB0b-D)7`=n2~f>-PYr< zM8}flA^lG-0lH13!fVP%C%NpjMrvF;d>LW0Y*glIQkRGIDi+DZn4k%}?&O&YlLK~M z7VgNIPwZl51sVywVq9egjjBS%txl$1fE#Q(DqJ#-aNzjIRxY81Y^fe8Xm?6&GJV3N z9X&lvMsN1TD3Y)q)*p4e+tbTw*=!ep6{X;2!m^?TLVRr;{i{lUJBGGGl{>7 z9`33xv$Ec#e1%(u6VB2^v^%^U_n{_a47v(Z4c%o_BMvX{=W*8dDU_f7fKQBjT7aU@ z=nH9ei{3{if81r#g_&HLu2Q}&R9<*{U`h^d4zf!st zu_QJPAhspkdiV%58ngkEUWAR6%lDp;5-L8U*@13knpLcKaz@VL;f(jIWwCYU1}P!A zN^9B;W5v9PxiI`HaDI3O8x=zBq~@D;qO-+ID>Z!!m$^1co#h<1O#Z2E^Ql0^)J^}PayR$-$<~K-;4RoDi4wcx663LB&5U`REGLd-6wC{P-;Eyt&}gixRZgher&t=NNVdhT zt{om@y}Wd2Z$@iVX61zwm&uk^^OLf&HpTT-r!2P)Rr4iNm&>~H)Z7Pjhg!)Uw#G-z zql7Fr>FsWcTT-1G>k%nu-T>q`oT+3pZMalkMkrCw&P%23m(Gh8j5Sy8c@RBrzw(@> zw=0iM6$TnE@4vetXszfZw6W6fM4RE`=4phNLuug2R_Wt>j1zM+-hj72-VNxlfxE;1 z%I6Cq!5Vhks?$!X4wFs6qu8f0!DBa~a;tNoJ6+1_=5DC00~v)`RGKfzK-a> zTVE?**m+|ujPAc#U7a%N@qjD4m6d5sa!jlB2jstVey>Dm6*ywX&iJ0$jnUPTMs2Lh zW%CP<2eWoaW^(0gg>|~w58g@F#0IBNIW0$7>w6t!ng!G!@Mdcsgs^wx1D<8 zeC2dt>CMuH>S!z8?AW%8={4!mh)#`ft4vqjZnMoxE9f_vr4(9UtG7qhUZ{=HJ+1b@ zbKTll$}uiU8Ca(GtRWrvM!$m#nI>to>qzwFG+RINfs@bv)hBNKt-1$y{_gQ}eJ=Bx z51qJu`Ss7eGxNmFCx0yag_hShb>`-NG1cLk_547(Jy$&2+-R@aMRm@l8eA&3q`gju zt<#O|^^Li?Pu1sBDc*{IUDw#%(BkS8uaEAAhP=zoJ?r6T1hd6+{{5*2W$12b6wJ*P z_2_QsZlI8~4=O%Es^Sx>f@-=N*7;I^pV2#|@cvZYI@jKmPj@%8cQ>RPGxh48tIyRn zyZVkLdbh$xMCTbvLL5 z*Q)XAdiL6MV{=*=TU@%aE`7p*)5jIRTmSbpf&0&<+6`Hcd;C2vtYq{*t^aitDWD%8 z0;}15yzcg|yEA!bneF9ssZ47=r4YSj=f2>P__&am(f_pm*VPNT`vAK$>q5?G=-V^V zOC}IQ?ohh1E926+L)p1U+tqxH2yg1FM)mO~mu_#jXNG5k`r4j>NYuaV+?UdgE%fv2 zfG-pLZC}G@Q@K8Zo&Y!GV~BVvpL*M({wp)c(8D9+>%gKj%Hd>-?{cosN4{Wad)j|I zoyn_ZPiN5enKh zdxVe|bDfLw^{HGTH}{R~+;`H%WPD3^FbFF2)4pj>`T=@boT0^Np~H2gXh|lcN;?!z zasnII`AjSs79)ZA@vZwGh9RK0V#VX+sF41J0s8YoW>xI#Kt zY;IB+-3^(Ho|-nAd!o6s;xB2DT&~y#<>iXm_Ezy9f^)^rw5X60n(M`^+8On_`oDk= z$T!6_D=ydQug^eY*!nwGUU zH|NeZH#fJr#`Lo8hGsDc{cUj#(bqC6Ue@U9mNl#2u3_0SC0^DbCb6tpFx}ovoVudg z6ttwdU2JGsvr5vxWy_YS9*d&Cu0EqWG8qM?^}ntzJ0)F6@X}T>FbuO#F_(nt48m7s+KvFYBJlc|P zNVPY&H|uqg6CR`Y<|d89dJTa34vE!^oJ1lJp16YuKwsc~Jz$f-{HHy&t#8m2QK-sq zQ2E7eRQC*t){)4M7wn>VEnu0z+y{-?KL{31NK~1uDSDPLZ!BT%SxfUFm98?WWA5!f zsJMfvT~z2!kBs6rNMEfJy5_HK?{07LyySDFX^Z$K$>+Y1{3`BTl1~e_KADPAv`Wtv zceTSW^nlaSv0c8lUHY^a@w8T-LNC6|s1#rIl6;+&>A#{P=3b=axfk1Mp&s3S{+?H* zFqcjc@~meco{7vmKWm?cfvDeN8*^``pPJ7fGS0zU#5q*#mm3TVGU8)+4LXu-z}-5=xqllJ~ePfzXbP+W0;M2BMR z=}fm+POjLiWSLyCNBkf+CW$Jxw%HCn`p~@o*RlaeRF#I$KJvfTG&?{8jn-ID-#Svi6WH zLpNW{hBO&uFCpzAMAc?8?b&=osquWyePKx=q&=rJ9eGeK=}kikP@=0sB8tp_Mm1<@ zNSF-tiQ@uPM@WpuXQh?`l{g!rfigg7@wTg?F2x~Ama7VE?~HmDr=vRoONr3j;iXaY z%L0{nSyi|it5Tj{5p^xj5Jf0cX(p<{!g%qvj;L+)zy~DSTMEk^4(&|@DdE*oFXB*- z+vQQqDuR_^QBbbbwr_~(8dmvGWZAcfI-o7g0VP4S_m!sRr%X4zF41+P}K9H*P(<|dr|R4)vD%=WQ$|L zbm#(2GrqKUQF*rbF6kJ}KHz;8@IL?X9{YHYMogHO6wVg!PpKPHaQvH>CtXUKE6ysZ zEK~-!-HY!o8BCUle!WWyOtWw4`#gM)zPm7PqH>hvqA%xoGjpo=giHWYzObYBmbA}v zQBWkRidu8U^Lo_VqfHNqav{&Y7uiEXpG2BCJeusGj`Jms%cH>_b@ph{!*}~(U)nwT z6z;lIrNuYv0nJ@($v27m{T~&=#B0lERR6^&kg?EL8qDjdhS?ZAp_E?z`g6=Qrmcp$ z$<<4s>yM)cmLes+M~KXH)w?;Ve9OI3m@R53rNdEAdq=*--eg3i8yXb&c5$OdAEqGx z?fc?YEXDc{OnlXXg=M)-;l9Yk6Ro4JDYNy-9>t5I3~7NH2t`J)_)ed`_)cs>#tI!% zhYk5WrVPx1I@jlj4A{U!$gffrKtI7@&c;g+za}r0KxVy zwS}jU>&eb)XEG7$RY;#a)8^{Y=*0!ZAh4-YnRHY}TIQBgXYAn_($7hs&lP{s!}n|G z)zbIN+`z6QLea|KzDQ+eiyz3&z0}^_)sXQyfbW-Kk@nv|2WGzwc&R=EH8cZs5reC5 z1uO!z0onl?i=Ce2q|p|4`k~+H2Vy5t_0m$6Xg6RE;N+62cWFR9Ks5wB`st=#J>v_h zH%%ONr+FF?cfAeKanCNF69Ror^)112j*2M$s_%GO2+S2f?EihFyGmaE_K=T6HmcbE5^?fUdzSc-E5g^{{4_LVmKN`1WPNmRRBTe;#^8Ggk> zo)tHH?%yRD>^TK5tsq6u7U@LU;%}teyHsIgN-Tp|Nve9@m#8syyihnwAZK?Lu;#5AhDs>HH z_=eP6s+ewU;(fP>FP)MdjzOIkEs9ne9bN;?fTBy!yYvN@zUb0#cj+H@62#q6XD|+B z{QAzc`cC^-dZR#lcpjjcol52tj}q2kR48lGtpq-*iIH0c*a1LeP@oV9Qxa9%hp_o^ zkeVwF*yGpzWA5D^`KsqrKHq03@lHsmc&DE>#bIT+H-0_giOXise7&FMclt>aC1n2i z*ArAdUmv;UIvn}t9`VhZd&H|N#i!)%St6+<>L?!X5>p+`6(1`YxG8vR0!H)M2p%sj9?DF)l^< zm@rq*#vJ6VReLsO6lc?otqjY#3_fE1WJDMKPo_S+!2bA?!I{h-=_U3jvpc-Zg%>y* ze?&#$B`PQbm*Y>yMFnMC-UldTcX*i#FHu3tV+HBO3et-eBxnWc#qtyG-*}nRf6tiT zR!=&*IhSfqwWL~8OH$cXPL8^(Qk|)*Q%h6JQp;1XO07tB)h{N`Hv!)Qd>il`z;^-P z1H1%y8Ss6;4*=hQpMMkZGT>W)mjHLF@%48Bz6SUkz~2JC4#>-wMZ4aS`c~@Osqdt| zoBCerrPRx*?-Tegz9#t z1N>jW-vhn@_$J_6fNul71NbiBdw`b!F9W_0_yGWCWxd#itFHsp0~!F0fF=NhQ;+Yk zz6H<D$xfSUok0X={{fW3fyfc=02fP;Wr0KI@W0d55x0vrY$0UQPN0d4~v1M~w1 z0LKBh1MUFa3AhXJX29Km6M%aFZvmVH+zS{4oB|91h5-e@X}}r4S-=S3M*#N$Mge1h zaliy%5^z6Y3NQ_r0n7p(06Yjd2RILS2=G>brmU-f81SQj9|Oz*ihy~*1;9nX+W|ig z_zA!}0Ph6+Gr&&*-UWC!;HLop9Pl2%djbCf@Y8^w0Xzcumw=xIybthmfS(8a0^m`= zF9QA*;Fkc80p1VzWx&4%JP!B(;NJj#1@J+@uLAxp;NJm01o$xE*8u+>@C4u^fd2sa zkAPnX{087Z0e%zkB;ccf-vazL;A4P~1O7AM6M#0G|W=SHOP*d>-%xz<&q)3E+!>F9H4s;C}+11$-IszW{#< zcnLAD|Vm z2+#&-2P_6G0b~I=KnLI|Kqug8z*2zLOvL{HuL7(9bOE{nuLi6HPy*8o-nP^9bE z0A2%F3s?tO4|pwL17IUy6JRr73t%f?8{j&?cEI(39e~#XZUEc}*a>(&;3mKu0J{Kh z1l$bR4d?;v0qh0r1MCMJ02~C|0_X+232-am5a2N22;eB74{#gc7@!|805}e~9dHK# zjj;YMz?%Vg15N<$0lWop5^yhI5O4}G1Q-Ss0H*=Y?*hCV@Kb<)4tNjXy?}oK_-Vk;03HGSOTfhW9tV5?@NWRW0{9@{R{{SP@b3U00(=8ONz)OIa0pADw0N}JpTAu>QP!#_Iuy9)60B8g>0WttB?u!2bS^HyUoCcf$oE6Z5X}0*GR;}F22j8xBo)?<+E+>oL6si>( zdzU+b1v|enfv2S;jC^fMX>S%TSF5aPEkrt*xlb#F)|`D7+!6|rSI|Gn>uXU6IVs1d z_g`{kbdj=7kXYrFjqG#yCt>OW9a!R`wdT;?4pE<9&?Z3ZQlt%uN?i?!d_a__Jt`@} zK{hH!pHYHEE?VRU4U0p)d?-Pv!sCiMLlr)tR7I%-x|5B|@Sj?aEDxa^`Pi&KhUTcG zR!uoA&wE_A3)kUY(N{ZLimkx^a)&!X3;pH8q70>eF+8Py@uA4}&}kc;Qj??4M90Zc z*72wW!b<&$$GFt5Qb{D#E;8Rr4ZKJqy!v*OzDO5FN9hzvNa%$pkkAYNp#))cO4JiZ zCnTT*%<>PJe`P%nmXze6YiaN>!y-+K!sWbt3J;Ip0hld*Hn>#hp7C!lqiR3%9UZ%Ea2JX-#w0?bwf{S04PwkVqqAivt6?b?~)ebT~@14@m zD9o2!^nuS<0lsISw|ABci(j$#ugINGarkHD>w8WQO?R!Q*@m`^#x;{s_dc7FbCe>Ih_ce2Ho4R7asieknO z#uFtfc!H+p`b0s=R6KSs}*Bb^0eyIg1cI{ zhIkxn(*EIBqm@KoTJxTJ9&(msd9G1hf;2q%QqJdW2dY+J<~Nr5#tt#RJxU2)3~Td- zZ{NC&ao^YZj%~za4Hap<1ojc7D!r}?g~*vt@7F;g^VcqeXgMk~qYDBjOB=0E!W=a8 z?S7)Yz*>nA_X2@)U-yKfC%cVBFs$c&9r9wia{dh(OIB_2eoXW2nZUxb^SgzwuJ(+m zewa37=Wo%(oxerz8koFcNV`3xJ{B*wYejSJaTY`Lp{;|W2+d2@NE_>!v-O(1^_rpe z>T@Xxv^Ttz6oKAb@VnE z%AhZKkol!*^$h`K=O^`HTRI2Rjg3^*sLC5vT_ZhE!Py>sefDO)40s9fJ-~MXuf;WeKj7!n0$JX204@AT zbLOoB&@BMT&Ogq$JR>@8+wOoWwaYze?l^X)P8na&wuc0*GdGj$-mNlO5aL;l{TQ3CsE*%kVYAOjTHDK z3q9&KMM`A0v|pYwLjT$|IaR6x8m2o{Q1BBlULo@_WkDE zpZ@MhYxlLk|NPJ1{jI-qa9U-f4#qwl!yjnAVtNGr-N(u*za@$!^Qw1oFd`}?e$GM71G*NCza3sX%MB&b~QAm z1)Heh2_4hWs;$Diy6T(K=`JN#Xm0L{f{R>(s(M5`>k&beoE~W$t-DV%FTb6Q2Q%N(wKc5ZZ!WX)glI}^~n~sJfGc~Y0l>L3AwV# zCACaEtxh}X(<(i?k&fv6hwpNB{tF_%u4U@2(kY>I9>4k8HzB)`Z_&^)N?(T7sqOVm zP4!wZ@1q4$n32U_)~+QLsxSRhd$n9@D(&a=wx~(^I7uYv*dddfe_pz+kcPnELks?r z%zAbyYlq8#jrr&GPh@K&nv+S)PF#?dY>2@8yp{Am5i7wOi#>C4Hc$04_WNy zQ)83by^)edKQUa;g~xf(MD{|8{&*gsHfo9+J!q4j23Wm*Is?`8sdw$&b&Oka2k7$Z z_706;?t(;-%U;N?Z(=isMBFnH+dI+{Mabcwu&CYH)O8 zzHj!_=*ZBmh4TXw_Z7x>o!Ywn^p4XvoZhx=c*oYk?dl=Jet2Yh$hmc*rGb&L0)2dN zaAYQbdSWUckw{s;wbG8{)y>i=m)TP|JNUrJgie00R`A}5@$tgYjM|i+oh}ULPo2-t zoGpY>oO^XjM@|(mIXE>qR+v%q6*oF+S;T)e%d!`4Qw1<$X$TsrU)0=M7qLA5;FL_Kh>VgH_5EXUYN~YINvS#@!syP7IFN!PwAA7Sb*n0of5;E;YBQMJ+c?x zp1tr+eXxzAs9F9M;a7WE)8JR%MZfjqtk)KrQG2o%-V5JH?aW@#EdDvMxq6+kkq+Es zMl#G%7|>v+>$K~K<0r5MW41zO1JfHeUZ=OTQZ}VzQ?ow~xGe4WMirOqe2_NSHq|f7 zoy!fU+wwF7#l<&~0sgXBv3P-uGI8WCB&THAaj-o9afu>hY{wPkadezET=;NAUOJwG zT*zMd2sGfI3R)s$iQnt!4iS1Hr{gc8=Po7!RHT}7(WH^-rX5ZU&r`5yL&K|9``w}- zf^8JS3G9EQ1f~UH^3$h|X#bh#-{i z!WsnYGW5WPWx1wiACw!`UUL7oO!8HNw4Q_otaSMI8@dho#0#!4hw>u!ON~ z`Uls`UHGiWAc8ENyUWk-XW3v5)jaNb(VWiIX(T1muosJ>4b@6yu20&5Z5i}BiPDI? z!Yeh4CHT*DEAvWf3k9hCdXrsTO3Zf0Yl(poeMbL$gUO2Tf)2nHE9m`CpSd;Mr*etLH3Y<_S$zqY?H^*~{2 z*ST}&ZaO|)nCczgb^iSMoB9T)ryrb{8s7ELLl50VzEgwKgs5PwKYb zXc5P)TihhF)?dN8Oq!~E%un^TkH}46Nl*2KQFYuq*)_|ao9iRUX(!M zOM6jaqdMy)G2~x3X7fA-JA7;IML9%BCkE7-$H!tvu@>u{SXdD0wJSbKW#5wZnkuA_ z_m+n;>V#4*nq`Fe(79q;zAlc};k5e1SMG2}z9IgOjy^@xaPCD86o#Wtr77tHG$mDT zDN+a7@|ye6sVHlqgt^Dx^(o!afaWzk%RI~E>-YV@UpS5YDF2`@hrUf!oElV`mej~Rdi|LC} zWa`B86)br#JwU2&o92c&C)KDsLouU%qP#3Yle_2*72%UNg6F>Bkt>x~Y)Yhb5lgnH z*Ke3(EO$69*{#DRk_FlY%}bNcGes{*u!~vrUVn;8{-gFb=IPuQ)i8;?zE?F_B=555 z@SLKAV$o`b%k8M5gji`S8il?P(vG5_(K-+E$fsO^4?Lz;cXx=Znnsk<*(po$A*n!8 zQoN{o0zs&5pqJ)!cmVyPOC+H2mM}frB|T0CCa+QRa8r5ukZK~uQ`Vxi&n{_^U7_By zXeR2}vnF<>=cJXw*LF5yR>iYf)T* zN`OKuMWG-?yR8*n@?y8QF`E2ayFC6lYkwnz`7QbNa{i<`Q3_S559vxr$rqA}Dg7L&-trTt^OFQe^*(sE zFdiBUD|>Kc=4^gk35R{M>HNAoM#i^am!B9PJ-^<$t5W*qR5e$|_M`+NcCmA>O}RDw zXD4Pyha)?*)Es&y<2FvZB&8qv8y%f^Fu!MTX6USQ`%>;^A21!6{8+VW>U@MBa2xQ0F+FW*#w^XmsUMN`9O2A~BwRaKII14^ zpC6wY)KC2AWX1NVBjl&&YQuz5OwXKGb9d*beM&DSV#{22Ii=j|$_vYnOy|cZ@}m>u zX9`pK@j_v^FuXaR?=KYc!xKZZ^x5lH&aF+kYx*XpLd$&F1AF_n^!Am;bYfh+X4oQ< z%Hw?ylAN2nka9nYpJ22wenz9N=FZ@mSSPVHT@V2ePp_xTB{#)jqEI3{p1jhQKUEMR zil_=xV zrrZe{I24bZg6cXSlox3%PJ{h1vXiO3src2KAtR`obn`sHbw z8ky0|TadDYvh8e0XF?WH#-4qjD9skFKBcBRrh1oIVj&&=B+H08}ON zAMP(NX);o3UvW{dO1aKH{87_0q6cet9(7y8`D-aR_rW`u&!-1Rrwh`oqXe6#3**yb zFAuMH`LyX7W=7`MAb4O{AKOT>Xq9VZ_*&rXwPliV)4eNi{$|t+UEk zkUlR|DL^EpLHBWf-U=&BqMlh5Fs>OYx`315-CCHF^!Fc9K2X4X>7O3WAC@asWy_~G z$Y^$^+>*V8sTnCej9OmGkyzj6lho;-a{EDk=N zOdbpn=dMY)?(pTO+E7KQx<)MJUX{$;FF#}r$*v_Q0HRDooF6PB3Jog)v3Go?a7OGy z1R3aF7nY~o^^k&mxx?t&RBn>(8R2WLr(5#(?K9tZ;+MjGh0j$B2*=8r&oG`u+TSE5N8t#Zb`Yl zz2YKMvqLk+>ebW-M6Xi}ht?NJ4N@X`rZf}kjn6G_Mx>soaIoyV5wMouBlD}E2|SQ; zN8>(-b5`Ovf6Avc38pR#J}@{k3hq2pC@Bn*g)`>JS9OtvnBRN0FmzvE?Cp;@qRzdc zZq2H(`^QHmGMsy}_(&y#^==wXoe4ZPLxa*m^97$ACds%B`q4~qehCTB`oW4}ChGgK z!t^AH(jF-RQmqb*4xUlpnWj;tr!3+$5uzWJIXXKwF8ypq4i(8vbzEvCWq9$-yc(D) zh}H&1g#Mms;d5fNFgWhqu9Ukm(UW+C_a`TICkb`JT;^{DF`hIvAXsEDTNsTuEJ3@quGaMX2i!s`B=f+gvJLYTmR( z&~LGTX-H0UOMF=~*JEA3W{iq-V0PRu_)O=IdAXc751@`Qrrb?Yff_lL2Y<_-E+tpN zGhl=nI~@siv@;RA9x6R>mL5NAmF$=}sdIOxTz{#d3`)=jLNYlx)MU*(#hqqtDLkhE>mNQf`%vu#v{PZLcdEvNM(pW9!yJVKUz{D#jwA z_5PHb`-KCN7*cmZU9=!JEz%s;oQXqBryVN{PK)t@+MwHR6w<{*gddUk>5(aERQIWd zK`M|08@jJBgDxc9X=Za7f0M8&DTSiUIFnb~XZ)I+XaJpC8mh#V9}-j*wfmRV?njOC z5|L&aNXJi9bY7A{LyBCRK0kJ9V$^dTjb~weXmkSaleg9Gz9)(5d+${t183Ee;R)p* z8JCJE4wYn=XSL`!J>d;HZ>4w@R_Mb-nmVtB!!!mw=ka2~xgSlr^Ck0YD(2~MacD_H z?9=ZXnSu@iJIv?HweX6*6(WNT^m}`+B_QiBbKKS~Q#Gq;w`o=m> zQVvPViHKfM5yK&YRG^vjTm6SJd259)hy95;$2Ju{bJ^l5D>p;oz84vn0S6ur@uJ45qjy^f42^Wd0gZ9)_ZPPs`e zrXdWKkBqAKd?S*Wld3SVms17i$Z$a%cjCO4YIE-t6S&|lHK`mj)ch1E@vz94h4~C? zpMuP}sfjV4MxthFLTbC%d7dgZVrWSemu9cG-i89d7tIL)@Qo zM-tdf^5-&1;4M$kDt;j5?it^d96S%G4<#IfSM|d0NkK~u{JT$>$Hpp%65oY?xf zLqcamPgY>eQ>zMqC0ua<;}bI|K~;r#2Tf$@n+1-Em_RkNF(yinspFU2ttG#K$zli! z2bwg%`~ohhJQ~LS14a&eq!T1t|b3cw1$xuJakp%R-1Amp~ z&He*r{;Ft3dION&U~Kq_v^Hu<#APu9Rl?fPV>zkh71y3%zyGgGH{LYWv%2D4^Lmjp zUfcQFnwq=)+CS@Yb*)X!inl+D{2p1}vln-0aY9QDEvz|Q+@Uu)=9ON%*c-J@!Jh`) z=sOo{XP~aG(|`MyYtls|Iu>kIEisE@6U z^`?=+i|`oiT;oxa;;cqIuRBo@1!U1^!HFL{yoB(F!ZKokAN8i#TJ4fm&>XHg2IsYB zy#7qP2K?wgxK*aTjDLa{3YKnP5{Io_I+(RsZP5Cr0SsUpfZQ1Gn?Z#b0z&^}nw$K1 z6S`QVtB3s|T#QW$*K{M@`aaykbnDf0>+jO7zk>6bZZ*=aml^G30YBf9wkKq-_#1>P%>ev3` zn=30uNr7WR4o;}#Y%1ydQcG>RvP|Bm9i-deh3lyICg5iXD5py79Z;=sLT^&pO#7;d z&a?wJvs|_L<#oHiEwCG%_%E&--f_+U?xdedW!kUNOlTo{#~^d~?bLi%gkmERF zEJ=JFNc@{J2qgYZp_8~r)+imv!er1GJgu2EKw1;UXpw``YaKG6B;~L+NVosNs_|ax zS-8f{O?b`@G6epzHSTgiBuD&K;TW$b#GVYblywDAiCW{?DEz1ea!GX(8S?kRI))$p zzT|O|;5_wb7|z3fl0<18%X~b140L4>rTp2wAwypDZdrj@YP2|3GAPjlnf6WLO`lc2 zw8le?ZmIAo{3sw}A@y2VD@Rh-wJixB}f7XSqKki;|H_9>!;BI zr%=x6VRwzs)*)Uu<$uI~?vWxrEHSwYOK>>#lurX4LbEp`mT^VeHmZ_-p9(dOWC%k+ zh$Sj=J8Tca5I?P+AgXcO1+=|opi3sYUWO%@+KfQ_&fnUFKC_gprrjl*_Max8>fi19 z(dPY1TxC*BYs-UKLwMW@`(NlaWxv~}!w?l#TB=Ex0x{@QSZbvtY;sh1wXqfzwCMq; zz#sa;8R)cOtMqMxxJ;j+Y0i)bNoquH|0Wt@Px-1ROBPPe3%aq%f=g%vBqD1)Es}-ovCqTy*u-NGE;pz zwir*s3TJ<}P|+b!473IlVoKlJ!0;YFfm@pI0pi~j7>4dKjmIE(1s!T!2V;^=jdN7n zIVPF*OMNAdI*c zryh2cHR4usd_wl2rMimW)FwVhoQcAwkmaoh;D!r07RnXT_FqbkF>c^7Z;%*y6P zj&Sw{Yy@m_9#WeJCGZJV3q;9a*HO}*QlhyIg)_9xgWNZeeAw&W#}VLX(8~eQ2ZRTC zD0}h(?)N~)KnHO{n)E83t$%Z#?Yle&C4|H5E_1&x>0IaQP}`!P0O?q32mmR-PZWjahVPfW_ni^*cAtQ?I zG#g)71siau#?CM#AU$<(roh@%vBft6_XCqdze$2N+(F(qZW=-I8aE6Z-KC6H)rvXzrbdZ4ORD+9KKFnL*Mt!%XVdN**4)VmI&vW zH7^l`=r-sPYM%TNBSfU4DSgq0i*gJlXM~Skgohp{E<~cYFj_ z;}=4$S!f&t8+cix$&BB}KO!6`UQYOZ2y=oFWElB8c}R?shooWY+|X}OPoZA*Bql1; z&Cf|P1_zlj=oUm}7UM&9ZhtaFCNXdJpSTP=e3{LS3DJB=?RKD8cu_EzD8UXgd`1MK zrxzsNh#E#tERc|0I~1Bb&`b8?D9LW@*bX};`n9oxqHWutYh7ZCg4Z(!__NeI!Q*I) zeW~*ysvBYUC?{v*k_kSGn5@Qm@Hx95wgK^ZIxdw7AXA2fm)kE}^9ED=4a)9xL)Zd& zW0-Q_(Qy?*E)(PpAxD;&6wz>V!A$6y#w1sCjBmdiOZ`q*WLAycbPhy;T$pJ4xiis$ zD@%$E?Grl>i-VU{Xr8qP2}0LiI8uG5dBQ62W2&1D7Ff_o?gfJ(YUUs~ry28L_MP|u zjj)py#Jahoja+lb{mkVj=JJzFQ$KV0mAU*1trYxRbE7Q6)d2(KFw<6C1vm8*kTkf` z(VY-hv|9+RBaZap(n!RC4x)o;1!qH zrVSWrWG92W$Pi_Mt#t6`*?5|Fzj-$k)CighmdVb(kaRen34%=UJ&6R-=6BPbNqS)^1txQfJ(Xyd+t%=X(Vjb1;1w?JS~&EBhKY24y_3(O~aI|rCxR0A%P zy$W6jhc2n97x+p6?-WLpRC^;6ybeu>o)|(z@52w->tq_g7J-SlJ?+~U&#aK}XP{%P zSqToLRmTXF`7dCpjge+bKIbuT3n zgR{Tz5JIGjGU!U-L8J(Tz?Zma zzm^OSHtpaiAx@i?FIOJcvh#4?9<}(7Qu2K+eS>tx*SWRyvv|aj-2G zaYrKu+rf_lH+KG`+Bo8G3P+f+csE!fY)s;)m!%aQG_>rX0YxOO@Sve3Wj(D?-At-5 zn+e{Opz_RU9-PKU^26fyK=Q%dDdPJ^qyELpaOn@ z1GDKZ31ryR6psox5gEoEC_It)6yk50Hl>~r6~La?j&O9dx=d&qqsEG8xlWj~P6Qfn zDi#K7_lf7&y#lT$9c`QuZVBDPXOyAa(ca;0ww%(w&#T6M)$Gh2LzJB<>mK<+m?FXe zrHW@N{WDI8B?aKOUZrbc?yKRYwZl)Oq z!ALvDY`R$`g?{oZgxH(3i{cfGbbl67|{Y%zM z$YM9qh0{r)6UN3FqVYXUcqj)#++c*gG2gVxG#c7rW5(we8+hh19ey(Ybe>_uv-yLk zyJQBMKZsm+1H3Tq53ANpb@VE=*V_>OY32=M;HZ-i6UOC03?rX#dL!YAoIXyfY?u9gm}N zhPu%>V)CHmrX-aRe5TPjE1WMIPerxm13?rZYh_7N@+1XFDmNakx1^>I@1T%BP^gffY*bydVT6RX&j9#r&*pGWDxT`Mh6Gf7qlXK#h^hwpb>XY9FjBSZe6d72N!gD%#koul>mK>T3hkgu-+>M|WIrf(&(;N&isAJW{6jQ`T@ zzq^xWUh#)JVZ$gRzN=VhO(Z_;kKPwX*@X^zOJr5b_qc7dA5L$h<}$r4x9ubXN}57! zFQLbn)7rvBi`EuRWWhi8jWOvSPScg>#fi$D*vEij_tLlDAC`NkFr*FI4g^q1ZyRgEc8#jP~F+v)Il2_AWENf}Z$3SF>e%9OsTx2|^IH?=-Ol#; zZx<#(Y%HcmJ6=bZ*V*W9>69j;IAa~3F~SbSyuRY({A7_YTw<(U{`?E)93*7n=;_H* zu)M2lK(he1!IOnlkdVt@%-eSwKDl7tXON#ga%FXEZTaHM8)N@;eQYY1D@<_D&%m9s zK$1iF0Or8gI z<~9GME8HST4`65=oe*QskL7Yxg`BR!oIU29Etf0iD5NguKQlEmUC2-8ad07I;=ejS zJ6*1p=JWO0{OrtBWp-|AzL1|P&d(M~g?uSDS1e5Di*r-6`Fe4tUdh$V)0Jvr2FqNv zl*`x4mD21?sWMy06=$pYx!P>0Uasd0Sm1o-=PNbxpDX95XY=!9Fg2GimkZTuwNR^6 zXY-|*xm>kg%gyJfs`biraXwehSEmY<*&3l|%Jo^~Q|5EELaCfD)bhERTpow|Y;n3= zug{e#Q@KKYerBdJU&+r@rYiM9txUO!)781@sa&~OE6r8&wbJxlZniizoiF6;h4Ng! zRGZF~ij}#!VtKk&%hSpiY2{K^D<3&N`J|ygiH3%*Z!UE;^f55|8(UAt^J5;*%QSQ4 z|1|UDvExiuPo|E?I(0m>sf$mV`Z15{y|eV$#V51$C%qgVFO}DpE0c~v^<+dp-VqJ+ zB_W)kWVUq_^wf)MrPWhcE_XPci-JmX80W6P|C?Vf$|S{F9F}>vhJEkW-F@EKb4SnZ z{PoX1eEvIs^pzWbTlm=@|Jl7CD^BJ2E_}Im`CBj+FMJt()rBKV(3DHXrTHbH-H`tg zlKaAokk#u*B-AcAohcV~*NqDZzFrWe-Q_m9S}m&(zawSh?SGZjd z8fxw6%F0Rb^_~HsYPCtD)CuxGpChgC8)CaLetS$DM^e~lkQ%wa%ubI$?g8!!_a*+n zjFPR7oRTT!cJ?oM!-S7&eUwn^_$r^( z`|9X&d4;-9QYzKIgsF<1JtbF;S!Oq#^H#?7t~M$XdY*qhRp&2SzG~$S3wxRLR=P_1 zarynD_Z9N{DseAbOts!BbydkPdM}fzXDxca+n9Hq7Mr33Ij`VNgyy_AW9II*86#Dx zG5%3q<^%wPSbUaJyPmuqw7rm{+1y6PVF zfo!g~KCTUNjPICthIFow_9p$d!6;U4yDh)l?kIS$K3c$colut;eJixs*hj>1ePxAy U)Z2*fB?)_mE$@@-|6dFIFV*hU!T + * + * 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, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using Game.Scripting; +using Game.Maps; +using Game.Entities; + +namespace Scripts.EasternKingdoms.Deadmines +{ + /*class instance_deadmines : InstanceMapScript + { + public instance_deadmines() : base("instance_deadmines", 36) + { + } + + class instance_deadmines_InstanceMapScript : InstanceScript + { + public instance_deadmines_InstanceMapScript(Map map) : base(map) + { + SetHeaders(DataHeader); + + State = CANNON_NOT_USED; + CannonBlast_Timer = 0; + PiratesDelay_Timer = 0; + } + + public override void Initialize() + { + + } + + public override void Update(uint diff) + { + if (IronCladDoorGUID.IsEmpty() || DefiasCannonGUID.IsEmpty() || DoorLeverGUID.IsEmpty()) + return; + + GameObject pIronCladDoor = instance.GetGameObject(IronCladDoorGUID); + if (!pIronCladDoor) + return; + + switch (State) + { + case CANNON_GUNPOWDER_USED: + CannonBlast_Timer = DATA_CANNON_BLAST_TIMER; + // it's a hack - Mr. Smite should do that but his too far away + //pIronCladDoor.SetName("Mr. Smite"); + //pIronCladDoor.MonsterYell(SAY_MR_SMITE_ALARM1, LANG_UNIVERSAL, NULL); + pIronCladDoor.PlayDirectSound(SOUND_MR_SMITE_ALARM1); + State = CANNON_BLAST_INITIATED; + break; + case CANNON_BLAST_INITIATED: + PiratesDelay_Timer = DATA_PIRATES_DELAY_TIMER; + if (CannonBlast_Timer <= diff) + { + SummonCreatures(); + GameObject pDefiasCannon = instance.GetGameObject(DefiasCannonGUID); + if (pDefiasCannon) + { + pDefiasCannon.SetGoState(GO_STATE_ACTIVE); + pDefiasCannon.PlayDirectSound(SOUND_CANNONFIRE); + } + pIronCladDoor.SetGoState(GO_STATE_ACTIVE_ALTERNATIVE); + pIronCladDoor.PlayDirectSound(SOUND_DESTROYDOOR); + GameObject pDoorLever = instance.GetGameObject(DoorLeverGUID); + if (pDoorLever) + pDoorLever.SetUInt32Value(GAMEOBJECT_FLAGS, 4); + //pIronCladDoor.MonsterYell(SAY_MR_SMITE_ALARM2, LANG_UNIVERSAL, NULL); + pIronCladDoor.PlayDirectSound(SOUND_MR_SMITE_ALARM2); + State = PIRATES_ATTACK; + } + else CannonBlast_Timer -= diff; + break; + case PIRATES_ATTACK: + if (PiratesDelay_Timer <= diff) + { + MoveCreaturesInside(); + State = EVENT_DONE; + } + else PiratesDelay_Timer -= diff; + break; + } + } + + void SummonCreatures() + { + GameObject pIronCladDoor = instance.GetGameObject(IronCladDoorGUID); + if (pIronCladDoor) + { + Creature DefiasPirate1 = pIronCladDoor.SummonCreature(657, pIronCladDoor.GetPositionX() - 2, pIronCladDoor.GetPositionY() - 7, pIronCladDoor.GetPositionZ(), 0, TEMPSUMMON_CORPSE_TIMED_DESPAWN, 3000); + Creature DefiasPirate2 = pIronCladDoor.SummonCreature(657, pIronCladDoor.GetPositionX() + 3, pIronCladDoor.GetPositionY() - 6, pIronCladDoor.GetPositionZ(), 0, TEMPSUMMON_CORPSE_TIMED_DESPAWN, 3000); + Creature DefiasCompanion = pIronCladDoor.SummonCreature(3450, pIronCladDoor.GetPositionX() + 2, pIronCladDoor.GetPositionY() - 6, pIronCladDoor.GetPositionZ(), 0, TEMPSUMMON_CORPSE_TIMED_DESPAWN, 3000); + + DefiasPirate1GUID = DefiasPirate1.GetGUID(); + DefiasPirate2GUID = DefiasPirate2.GetGUID(); + DefiasCompanionGUID = DefiasCompanion.GetGUID(); + } + } + + void MoveCreaturesInside() + { + if (DefiasPirate1GUID.IsEmpty() || DefiasPirate2GUID.IsEmpty() || DefiasCompanionGUID.IsEmpty()) + return; + + Creature pDefiasPirate1 = instance.GetCreature(DefiasPirate1GUID); + Creature pDefiasPirate2 = instance.GetCreature(DefiasPirate2GUID); + Creature pDefiasCompanion = instance.GetCreature(DefiasCompanionGUID); + if (!pDefiasPirate1 || !pDefiasPirate2 || !pDefiasCompanion) + return; + + MoveCreatureInside(pDefiasPirate1); + MoveCreatureInside(pDefiasPirate2); + MoveCreatureInside(pDefiasCompanion); + } + + void MoveCreatureInside(Creature creature) + { + creature.SetWalk(false); + creature.GetMotionMaster().MovePoint(0, -102.7f, -655.9f, creature.GetPositionZ()); + } + + public override void OnGameObjectCreate(GameObject go) + { + switch (go.GetEntry()) + { + case GO_FACTORY_DOOR: FactoryDoorGUID = go.GetGUID(); break; + case GO_IRONCLAD_DOOR: IronCladDoorGUID = go.GetGUID(); break; + case GO_DEFIAS_CANNON: DefiasCannonGUID = go.GetGUID(); break; + case GO_DOOR_LEVER: DoorLeverGUID = go.GetGUID(); break; + case GO_MR_SMITE_CHEST: uiSmiteChestGUID = go.GetGUID(); break; + } + } + + public override void SetData(uint type, uint data) + { + switch (type) + { + case EVENT_STATE: + if (!DefiasCannonGUID.IsEmpty() && !IronCladDoorGUID.IsEmpty()) + State = data; + break; + case EVENT_RHAHKZOR: + if (data == DONE) + { + GameObject go = instance.GetGameObject(FactoryDoorGUID); + if (go) + go.SetGoState(GO_STATE_ACTIVE); + } + break; + } + } + + public override uint GetData(uint type) + { + switch (type) + { + case EVENT_STATE: + return State; + } + + return 0; + } + + public override ObjectGuid GetGuidData(uint data) + { + switch (data) + { + case DATA_SMITE_CHEST: + return uiSmiteChestGUID; + } + + return ObjectGuid::Empty; + } + + ObjectGuid FactoryDoorGUID; + ObjectGuid IronCladDoorGUID; + ObjectGuid DefiasCannonGUID; + ObjectGuid DoorLeverGUID; + ObjectGuid DefiasPirate1GUID; + ObjectGuid DefiasPirate2GUID; + ObjectGuid DefiasCompanionGUID; + + uint State; + uint CannonBlast_Timer; + uint PiratesDelay_Timer; + ObjectGuid uiSmiteChestGUID; + } + + public override InstanceScript GetInstanceScript(InstanceMap map) + { + return new instance_deadmines_InstanceMapScript(map); + } + }*/ +} diff --git a/Scripts/EasternKingdoms/EversongWoods.cs b/Scripts/EasternKingdoms/EversongWoods.cs new file mode 100644 index 000000000..01131e029 --- /dev/null +++ b/Scripts/EasternKingdoms/EversongWoods.cs @@ -0,0 +1,296 @@ +/* + * Copyright (C) 2012-2017 CypherCore + * + * 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, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +using Framework.Constants; +using Framework.GameMath; +using Game; +using Game.AI; +using Game.Entities; +using Game.Scripting; + +namespace Scripts.EasternKingdoms +{ + [Script] + class npc_apprentice_mirveda : CreatureScript + { + public npc_apprentice_mirveda() : base("npc_apprentice_mirveda") { } + + class npc_apprentice_mirvedaAI : ScriptedAI + { + public npc_apprentice_mirvedaAI(Creature creature) + : base(creature) + { + Summons = new SummonList(me); + } + + public override void Reset() + { + SetCombatMovement(false); + KillCount = 0; + PlayerGUID.Clear(); + Summons.DespawnAll(); + } + + public override void sQuestReward(Player player, Quest quest, uint opt) + { + if (quest.Id == QUEST_CORRUPTED_SOIL) + { + me.RemoveFlag64(UnitFields.NpcFlags, NPCFlags.QuestGiver); + _events.ScheduleEvent(EventTalk, 2000); + } + } + + public override void sQuestAccept(Player player, Quest quest) + { + if (quest.Id == QUEST_UNEXPECTED_RESULT) + { + me.SetFaction(FactionCombat); + me.RemoveFlag64(UnitFields.NpcFlags, NPCFlags.QuestGiver); + _events.ScheduleEvent(EventSummon, 1000); + PlayerGUID = player.GetGUID(); + } + } + + public override void EnterCombat(Unit who) + { + _events.ScheduleEvent(EventFireball, 1000); + } + + public override void JustSummoned(Creature summoned) + { + // This is the best I can do because AttackStart does nothing + summoned.GetMotionMaster().MovePoint(1, me.GetPositionX(), me.GetPositionY(), me.GetPositionZ()); + // summoned.AI().AttackStart(me); + Summons.Summon(summoned); + } + + public override void SummonedCreatureDies(Creature summoned, Unit who) + { + Summons.Despawn(summoned); + ++KillCount; + } + + public override void JustDied(Unit killer) + { + me.SetFaction(FactionNormal); + + if (!PlayerGUID.IsEmpty()) + { + Player player = Global.ObjAccessor.GetPlayer(me, PlayerGUID); + if (player) + player.FailQuest(QUEST_UNEXPECTED_RESULT); + } + } + + public override void UpdateAI(uint diff) + { + if (KillCount >= 3 && !PlayerGUID.IsEmpty()) + { + Player player = Global.ObjAccessor.GetPlayer(me, PlayerGUID); + if (player) + { + if (player.GetQuestStatus(QUEST_UNEXPECTED_RESULT) == QuestStatus.Incomplete) + { + player.CompleteQuest(QUEST_UNEXPECTED_RESULT); + me.SetFaction(FactionNormal); + me.SetFlag64(UnitFields.NpcFlags, NPCFlags.QuestGiver); + } + } + } + + _events.Update(diff); + _events.ExecuteEvents(eventId => + { + switch (eventId) + { + case EventTalk: + Talk(SayTestSoil); + _events.ScheduleEvent(EventAddQuestGiverFlag, 7000); + break; + case EventAddQuestGiverFlag: + me.SetFlag64(UnitFields.NpcFlags, NPCFlags.QuestGiver); + break; + case EventSummon: + me.SummonCreature(NPC_GHARZUL, 8749.505f, -7132.595f, 35.31983f, 3.816502f, TempSummonType.CorpseTimedDespawn, 180000); + me.SummonCreature(NPC_ANGERSHADE, 8755.38f, -7131.521f, 35.30957f, 3.816502f, TempSummonType.CorpseTimedDespawn, 180000); + me.SummonCreature(NPC_ANGERSHADE, 8753.199f, -7125.975f, 35.31986f, 3.816502f, TempSummonType.CorpseTimedDespawn, 180000); + break; + case EventFireball: + if (UpdateVictim()) + { + DoCastVictim(SpellFireball, true); // Not casting in combat + _events.ScheduleEvent(EventFireball, 3000); + } + break; + default: + break; + } + }); + DoMeleeAttackIfReady(); + } + + uint KillCount; + ObjectGuid PlayerGUID; + SummonList Summons; + } + + const uint EventTalk = 1; // Quest 8487 + const uint EventAddQuestGiverFlag = 2; // Quest 8487 + const uint EventSummon = 3; // Quest 8488 + const uint EventFireball = 4; // Quest 8488 + + // Creatures + const uint NPC_GHARZUL = 15958; // Quest 8488 + const uint NPC_ANGERSHADE = 15656; // Quest 8488 + + // Spells + const uint SpellTestSoil = 29535; // Quest 8487 + const uint SpellFireball = 20811; // Quest 8488 + + //Texts + const uint SayTestSoil = 0; // Quest 8487 + + // Factions + const uint FactionNormal = 1604; // Quest 8488 + const uint FactionCombat = 232; // Quest 8488 + + // Quest + const uint QUEST_CORRUPTED_SOIL = 8487; + const uint QUEST_UNEXPECTED_RESULT = 8488; + + public override CreatureAI GetAI(Creature creature) + { + return new npc_apprentice_mirvedaAI(creature); + } + } + + [Script] + class npc_infused_crystal : CreatureScript + { + public npc_infused_crystal() : base("npc_infused_crystal") { } + + class npc_infused_crystalAI : ScriptedAI + { + public npc_infused_crystalAI(Creature creature) + : base(creature) + { + SetCombatMovement(false); + } + + public override void Reset() + { + EndTimer = 0; + Completed = false; + Progress = false; + PlayerGUID.Clear(); + WaveTimer = 0; + } + + public override void MoveInLineOfSight(Unit who) + { + if (!Progress && who.IsTypeId(TypeId.Player) && me.IsWithinDistInMap(who, 10.0f)) + { + if (who.ToPlayer().GetQuestStatus(QuestPoweringOurDefenses) == QuestStatus.Incomplete) + { + PlayerGUID = who.GetGUID(); + WaveTimer = 1000; + EndTimer = 60000; + Progress = true; + } + } + } + + public override void JustSummoned(Creature summoned) + { + summoned.GetAI().AttackStart(me); + } + + public override void JustDied(Unit killer) + { + if (!PlayerGUID.IsEmpty() && !Completed) + { + Player player = Global.ObjAccessor.GetPlayer(me, PlayerGUID); + if (player) + player.FailQuest(QuestPoweringOurDefenses); + } + } + + public override void UpdateAI(uint diff) + { + if (EndTimer < diff && Progress) + { + Talk(Emote); + Completed = true; + if (!PlayerGUID.IsEmpty()) + { + Player player = Global.ObjAccessor.GetPlayer(me, PlayerGUID); + if (player) + player.CompleteQuest(QuestPoweringOurDefenses); + } + + me.DealDamage(me, (uint)me.GetHealth(), null, DamageEffectType.Direct, SpellSchoolMask.Normal, null, false); + me.RemoveCorpse(); + } + else EndTimer -= diff; + + if (WaveTimer < diff && !Completed && Progress) + { + uint ran1 = RandomHelper.Rand32() % 8; + uint ran2 = RandomHelper.Rand32() % 8; + uint ran3 = RandomHelper.Rand32() % 8; + me.SummonCreature(NpcEnragedWeaith, SpawnLocations[ran1].X, SpawnLocations[ran1].Y, SpawnLocations[ran1].Z, 0, TempSummonType.TimedOrCorpseDespawn, 10000); + me.SummonCreature(NpcEnragedWeaith, SpawnLocations[ran2].X, SpawnLocations[ran2].Y, SpawnLocations[ran2].Z, 0, TempSummonType.TimedOrCorpseDespawn, 10000); + me.SummonCreature(NpcEnragedWeaith, SpawnLocations[ran3].X, SpawnLocations[ran3].Y, SpawnLocations[ran3].Z, 0, TempSummonType.TimedOrCorpseDespawn, 10000); + WaveTimer = 30000; + } + else WaveTimer -= diff; + } + + uint EndTimer; + uint WaveTimer; + bool Completed; + bool Progress; + ObjectGuid PlayerGUID; + } + + public override CreatureAI GetAI(Creature creature) + { + return new npc_infused_crystalAI(creature); + } + + // Quest + const uint QuestPoweringOurDefenses = 8490; + + // Says + const uint Emote = 0; + + // Creatures + const uint NpcEnragedWeaith = 17086; + + static Vector3[] SpawnLocations = + { + new Vector3(8270.68f, -7188.53f, 139.619f), + new Vector3(8284.27f, -7187.78f, 139.603f), + new Vector3(8297.43f, -7193.53f, 139.603f), + new Vector3(8303.5f, -7201.96f, 139.577f), + new Vector3(8273.22f, -7241.82f, 139.382f), + new Vector3(8254.89f, -7222.12f, 139.603f), + new Vector3(8278.51f, -7242.13f, 139.162f), + new Vector3(8267.97f, -7239.17f, 139.517f) + }; + } +} diff --git a/Scripts/EasternKingdoms/Karazhan/AttumenMidnight.cs b/Scripts/EasternKingdoms/Karazhan/AttumenMidnight.cs new file mode 100644 index 000000000..d35a1a1cd --- /dev/null +++ b/Scripts/EasternKingdoms/Karazhan/AttumenMidnight.cs @@ -0,0 +1,325 @@ +/* + * Copyright (C) 2012-2017 CypherCore + * + * 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, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +using Framework.Constants; +using Game.AI; +using Game.Entities; +using Game.Scripting; +using Game.Spells; +using System; +using System.Collections.Generic; + +namespace Scripts.EasternKingdoms.Karazhan.Midnight +{ + struct Misc + { + public const uint MountedDisplayid = 16040; + + //Attumen (@Todo Use The Summoning Spell Instead Of Creature Id. It Works; But Is Not Convenient For Us) + public const uint SummonAttumen = 15550; + } + + struct TextIds + { + public const uint SayMidnightKill = 0; + public const uint SayAppear = 1; + public const uint SayMount = 2; + + public const uint SayKill = 0; + public const uint SayDisarmed = 1; + public const uint SayDeath = 2; + public const uint SayRandom = 3; + } + + struct SpellIds + { + public const uint Shadowcleave = 29832; + public const uint IntangiblePresence = 29833; + public const uint BerserkerCharge = 26561; //Only When Mounted + } + + [Script] + class boss_attumen : CreatureScript + { + public boss_attumen() : base("boss_attumen") { } + + public class boss_attumenAI : ScriptedAI + { + public boss_attumenAI(Creature creature) : base(creature) + { + CleaveTimer = RandomHelper.URand(10000, 15000); + CurseTimer = 30000; + RandomYellTimer = RandomHelper.URand(30000, 60000); //Occasionally yell + ChargeTimer = 20000; + ResetTimer = 0; + } + + public override void Reset() + { + ResetTimer = 0; + Midnight.Clear(); + } + + public override void EnterEvadeMode(EvadeReason why) + { + base.EnterEvadeMode(why); + ResetTimer = 2000; + } + + public override void EnterCombat(Unit who) { } + + public override void KilledUnit(Unit victim) + { + Talk(TextIds.SayKill); + } + + public override void JustDied(Unit killer) + { + Talk(TextIds.SayDeath); + Unit midnight = Global.ObjAccessor.GetUnit(me, Midnight); + if (midnight) + midnight.KillSelf(); + } + + public override void UpdateAI(uint diff) + { + if (ResetTimer != 0) + { + if (ResetTimer <= diff) + { + ResetTimer = 0; + Unit pMidnight = Global.ObjAccessor.GetUnit(me, Midnight); + if (pMidnight) + { + pMidnight.RemoveFlag(UnitFields.Flags, UnitFlags.NonAttackable); + pMidnight.SetVisible(true); + } + Midnight.Clear(); + me.SetVisible(false); + me.KillSelf(); + } + else ResetTimer -= diff; + } + + //Return since we have no target + if (!UpdateVictim()) + return; + + if (me.HasFlag(UnitFields.Flags, UnitFlags.NonAttackable | UnitFlags.NotSelectable)) + return; + + if (CleaveTimer <= diff) + { + DoCastVictim(SpellIds.Shadowcleave); + CleaveTimer = RandomHelper.URand(10000, 15000); + } + else CleaveTimer -= diff; + + if (CurseTimer <= diff) + { + DoCastVictim(SpellIds.IntangiblePresence); + CurseTimer = 30000; + } + else CurseTimer -= diff; + + if (RandomYellTimer <= diff) + { + Talk(TextIds.SayRandom); + RandomYellTimer = RandomHelper.URand(30000, 60000); + } + else RandomYellTimer -= diff; + + if (me.GetUInt32Value(UnitFields.DisplayId) == Misc.MountedDisplayid) + { + if (ChargeTimer <= diff) + { + var t_list = me.GetThreatManager().getThreatList(); + List target_list = new List(); + foreach (var hostileRefe in t_list) + { + var unit = Global.ObjAccessor.GetUnit(me, hostileRefe.getUnitGuid()); + if (unit && !unit.IsWithinDist(me, SharedConst.AttackDistance, false)) + target_list.Add(unit); + unit = null; + } + Unit target = null; + if (!target_list.Empty()) + target = target_list.PickRandom(); + + DoCast(target, SpellIds.BerserkerCharge); + ChargeTimer = 20000; + } + else ChargeTimer -= diff; + } + else + { + if (HealthBelowPct(25)) + { + Creature pMidnight = ObjectAccessor.GetCreature(me, Midnight); + if (pMidnight && pMidnight.IsTypeId(TypeId.Unit)) + { + ((boss_midnight.boss_midnightAI)pMidnight.GetAI()).Mount(me); + me.SetHealth(pMidnight.GetHealth()); + DoResetThreat(); + } + } + } + + DoMeleeAttackIfReady(); + } + + public override void SpellHit(Unit source, SpellInfo spell) + { + if (spell.Mechanic == Mechanics.Disarm) + Talk(TextIds.SayDisarmed); + } + + public ObjectGuid Midnight; + uint CleaveTimer; + uint CurseTimer; + uint RandomYellTimer; + uint ChargeTimer; //only when mounted + uint ResetTimer; + } + + public override CreatureAI GetAI(Creature creature) + { + return new boss_attumenAI(creature); + } + } + + [Script] + class boss_midnight : CreatureScript + { + public boss_midnight() : base("boss_midnight") { } + + public class boss_midnightAI : ScriptedAI + { + public boss_midnightAI(Creature creature) : base(creature) { } + + public override void Reset() + { + Phase = 1; + Attumen.Clear(); + mountTimer = 0; + + me.RemoveFlag(UnitFields.Flags, UnitFlags.NonAttackable); + me.SetVisible(true); + } + + public override void EnterCombat(Unit who) { } + + public override void KilledUnit(Unit victim) + { + if (Phase == 2) + { + Unit unit = Global.ObjAccessor.GetUnit(me, Attumen); + if (unit) + Talk(TextIds.SayMidnightKill, unit); + } + } + + public override void UpdateAI(uint diff) + { + if (!UpdateVictim()) + return; + + if (Phase == 1 && HealthBelowPct(95)) + { + Phase = 2; + Creature attumen = me.SummonCreature(Misc.SummonAttumen, 0.0f, 0.0f, 0.0f, 0.0f, TempSummonType.TimedOrDeadDespawn, 30000); + if (attumen) + { + Attumen = attumen.GetGUID(); + attumen.GetAI().AttackStart(me.GetVictim()); + SetMidnight(attumen, me.GetGUID()); + Talk(TextIds.SayAppear, attumen); + } + } + else if (Phase == 2 && HealthBelowPct(25)) + { + Unit pAttumen = Global.ObjAccessor.GetUnit(me, Attumen); + if (pAttumen) + Mount(pAttumen); + } + else if (Phase == 3) + { + if (mountTimer != 0) + { + if (mountTimer <= diff) + { + mountTimer = 0; + me.SetVisible(false); + me.GetMotionMaster().MoveIdle(); + Unit pAttumen = Global.ObjAccessor.GetUnit(me, Attumen); + if (pAttumen) + { + pAttumen.SetDisplayId(Misc.MountedDisplayid); + pAttumen.RemoveFlag(UnitFields.Flags, UnitFlags.NonAttackable); + if (pAttumen.GetVictim()) + { + pAttumen.GetMotionMaster().MoveChase(pAttumen.GetVictim()); + pAttumen.SetTarget(pAttumen.GetVictim().GetGUID()); + } + pAttumen.SetObjectScale(1); + } + } + else mountTimer -= diff; + } + } + + if (Phase != 3) + DoMeleeAttackIfReady(); + } + + public void Mount(Unit pAttumen) + { + Talk(TextIds.SayMount, pAttumen); + Phase = 3; + me.SetFlag(UnitFields.Flags, UnitFlags.NonAttackable); + pAttumen.SetFlag(UnitFields.Flags, UnitFlags.NonAttackable); + float angle = me.GetAngle(pAttumen); + float distance = me.GetDistance2d(pAttumen); + float newX = me.GetPositionX() + (float)Math.Cos(angle) * (distance / 2); + float newY = me.GetPositionY() + (float)Math.Sin(angle) * (distance / 2); + float newZ = 50; + me.GetMotionMaster().Clear(); + me.GetMotionMaster().MovePoint(0, newX, newY, newZ); + distance += 10; + newX = me.GetPositionX() + (float)Math.Cos(angle) * (distance / 2); + newY = me.GetPositionY() + (float)Math.Sin(angle) * (distance / 2); + pAttumen.GetMotionMaster().Clear(); + pAttumen.GetMotionMaster().MovePoint(0, newX, newY, newZ); + mountTimer = 1000; + } + + void SetMidnight(Creature pAttumen, ObjectGuid value) + { + ((boss_attumen.boss_attumenAI)pAttumen.GetAI()).Midnight = value; + } + + ObjectGuid Attumen; + byte Phase; + uint mountTimer; + } + + public override CreatureAI GetAI(Creature creature) + { + return new boss_midnightAI(creature); + } + } +} diff --git a/Scripts/EasternKingdoms/Karazhan/ChessEvent.cs b/Scripts/EasternKingdoms/Karazhan/ChessEvent.cs new file mode 100644 index 000000000..9a0d0c9e5 --- /dev/null +++ b/Scripts/EasternKingdoms/Karazhan/ChessEvent.cs @@ -0,0 +1,1703 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using Game.WorldEntities; +using Game.Scripting; +using Game.AI; +using Framework.Constants; +using Framework.ObjectDefines; +using Game.Spells; +using Framework.Util; +using Game.Maps; + +namespace Scripts.EasternKingdoms.Karazhan +{ + + enum blah + { + // texts + EMOTE_LIFT_CURSE = -1532131, + EMOTE_CHEAT = -1532132, + + SOUND_ID_GAME_BEGIN = 10338, + SOUND_ID_LOSE_PAWN_PLAYER_1 = 10339, + SOUND_ID_LOSE_PAWN_PLAYER_2 = 10340, + SOUND_ID_LOSE_PAWN_PLAYER_3 = 10341, + SOUND_ID_LOSE_PAWN_MEDIVH_1 = 10342, + SOUND_ID_LOSE_PAWN_MEDIVH_2 = 10343, + SOUND_ID_LOSE_PAWN_MEDIVH_3 = 10344, + SOUND_ID_LOSE_ROOK_PLAYER = 10345, + SOUND_ID_LOSE_ROOK_MEDIVH = 10346, + SOUND_ID_LOSE_BISHOP_PLAYER = 10347, + SOUND_ID_LOSE_BISHOP_MEDIVH = 10348, + SOUND_ID_LOSE_KNIGHT_PLAYER = 10349, + SOUND_ID_LOSE_KNIGHT_MEDIVH = 10350, + SOUND_ID_LOSE_QUEEN_PLAYER = 10351, + SOUND_ID_LOSE_QUEEN_MEDIVH = 10352, + SOUND_ID_CHECK_PLAYER = 10353, + SOUND_ID_CHECK_MEDIVH = 10354, + SOUND_ID_WIN_PLAYER = 10355, + SOUND_ID_WIN_MEDIVH = 10356, + SOUND_ID_CHEAT_1 = 10357, + SOUND_ID_CHEAT_2 = 10358, + SOUND_ID_CHEAT_3 = 10359, + + // movement spells + SPELL_MOVE_GENERIC = 30012, // spell which sends the signal to move - handled in core + SPELL_MOVE_1 = 32312, // spell which selects AI move square (for short range pieces) + SPELL_MOVE_2 = 37388, // spell which selects AI move square (for long range pieces) + // SPELL_MOVE_PAWN = 37146, // individual move spells (used only by controlled npcs) + // SPELL_MOVE_KNIGHT = 37144, + // SPELL_MOVE_QUEEN = 37148, + // SPELL_MOVE_ROCK = 37151, + // SPELL_MOVE_BISHOP = 37152, + // SPELL_MOVE_KING = 37153, + + // additional movement spells + SPELL_CHANGE_FACING = 30284, // spell which sends the initial facing request - handled in core + SPELL_FACE_SQUARE = 30270, // change facing - finalize facing update + + SPELL_MOVE_TO_SQUARE = 30253, // spell which sends the move response from the square to the piece + SPELL_MOVE_COOLDOWN = 30543, // add some cooldown to movement + SPELL_MOVE_MARKER = 32261, // white beam visual - used to mark the movement as complete + SPELL_DISABLE_SQUARE = 32745, // used by the White / Black triggers on the squares when a chess piece moves into place + SPELL_IS_SQUARE_USED = 39400, // cast when a chess piece moves to another square + // SPELL_SQUARED_OCCUPIED = 39399, // triggered by 39400; used to check if the square is occupied (if hits a target); Missing in 2.4.3 + + // generic spells + SPELL_IN_GAME = 30532, // teleport player near the entrance + SPELL_CONTROL_PIECE = 30019, // control a chess piece + SPELL_RECENTLY_IN_GAME = 30529, // debuff on player after chess piece uncharm + + SPELL_CHESS_AI_ATTACK_TIMER = 32226, // melee action timer - triggers 32225 + SPELL_ACTION_MELEE = 32225, // handle melee attacks + SPELL_MELEE_DAMAGE = 32247, // melee damage spell - used by all chess pieces + // SPELL_AI_SNAPSHOT_TIMER = 37440, // used to trigger spell 32260; purpose and usage unk + // SPELL_DISABLE_SQUARE_SELF = 32260, // used when a piece moves to another square + // SPELL_AI_ACTION_TIMER = 37504, // handle some kind of event check. Cast by npc 17459. Currently the way it works is unk + // SPELL_DISABLE_SQUARE = 30271, // not used + // SPELL_FIND_ENEMY = 32303, // not used + // SPELL_MOVE_NEAR_UNIT = 30417, // not used + // SPELL_GET_EMPTY_SQUARE = 30418, // not used + // SPELL_FACE_NEARBY_ENEMY = 37787, // not used + // SPELL_POST_MOVE_FACING = 38011, // not used + + // melee action spells + SPELL_MELEE_FOOTMAN = 32227, + SPELL_MELEE_WATER_ELEM = 37142, + SPELL_MELEE_CHARGER = 37143, + SPELL_MELEE_CLERIC = 37147, + SPELL_MELEE_CONJURER = 37149, + SPELL_MELEE_KING_LLANE = 37150, + SPELL_MELEE_GRUNT = 32228, + SPELL_MELEE_DAEMON = 37220, + SPELL_MELEE_NECROLYTE = 37337, + SPELL_MELEE_WOLF = 37339, + SPELL_MELEE_WARLOCK = 37345, + SPELL_MELEE_WARCHIEF_BLACKHAND = 37348, + + // cheat spells + SPELL_HAND_OF_MEDIVH_HORDE = 39338, // triggers 39339 + SPELL_HAND_OF_MEDIVH_ALLIANCE = 39342, // triggers 39339 + SPELL_FURY_OF_MEDIVH_HORDE = 39341, // triggers 39343 + SPELL_FURY_OF_MEDIVH_ALLIANCE = 39344, // triggers 39345 + SPELL_FURY_OF_MEDIVH_AURA = 39383, + // SPELL_FULL_HEAL_HORDE = 39334, // spells are not confirmed (probably removed after 2.4.3) + // SPELL_FULL_HEAL_ALLIANCE = 39335, + + // spells used by the chess npcs + SPELL_HEROISM = 37471, // human king + SPELL_SWEEP = 37474, + SPELL_BLOODLUST = 37472, // orc king + SPELL_CLEAVE = 37476, + SPELL_HEROIC_BLOW = 37406, // human pawn + SPELL_SHIELD_BLOCK = 37414, + SPELL_VICIOUS_STRIKE = 37413, // orc pawn + SPELL_WEAPON_DEFLECTION = 37416, + SPELL_SMASH = 37453, // human knight + SPELL_STOMP = 37498, + SPELL_BITE = 37454, // orc knight + SPELL_HOWL = 37502, + SPELL_ELEMENTAL_BLAST = 37462, // human queen + SPELL_RAIN_OF_FIRE = 37465, + SPELL_FIREBALL = 37463, // orc queen + // SPELL_POISON_CLOUD = 37469, + SPELL_POISON_CLOUD_ACTION = 37775, // triggers 37469 - acts as a target selector spell for orc queen + SPELL_HEALING = 37455, // human bishop + SPELL_HOLY_LANCE = 37459, + // SPELL_SHADOW_MEND = 37456, // orc bishop + SPELL_SHADOW_MEND_ACTION = 37824, // triggers 37456 - acts as a target selector spell for orc bishop + SPELL_SHADOW_SPEAR = 37461, + SPELL_GEYSER = 37427, // human rook + SPELL_WATER_SHIELD = 37432, + SPELL_HELLFIRE = 37428, // orc rook + SPELL_FIRE_SHIELD = 37434, + + // spells used to transform side trigger when npc dies + SPELL_TRANSFORM_FOOTMAN = 39350, + SPELL_TRANSFORM_CHARGER = 39352, + SPELL_TRANSFORM_CLERIC = 39353, + SPELL_TRANSFORM_WATER_ELEM = 39354, + SPELL_TRANSFORM_CONJURER = 39355, + SPELL_TRANSFORM_KING_LLANE = 39356, + SPELL_TRANSFORM_GRUNT = 39357, + SPELL_TRANSFORM_WOLF = 39358, + SPELL_TRANSFORM_NECROLYTE = 39359, + SPELL_TRANSFORM_DAEMON = 39360, + SPELL_TRANSFORM_WARLOCK = 39361, + SPELL_TRANSFORM_BLACKHAND = 39362, + + // generic npcs + // NPC_SQUARE_OUTSIDE_B = 17316, // used to check the interior of the board + // NPC_SQUARE_OUTSIDE_W = 17317, // not used in our script; keep for reference only + NPC_FURY_MEDIVH_VISUAL = 22521, // has aura 39383 + + // gossip texts + GOSSIP_ITEM_ORC_GRUNT = -3532006, + GOSSIP_ITEM_ORC_WOLF = -3532007, + GOSSIP_ITEM_SUMMONED_DEAMON = -3532008, + GOSSIP_ITEM_ORC_WARLOCK = -3532009, + GOSSIP_ITEM_ORC_NECROLYTE = -3532010, + GOSSIP_ITEM_WARCHIEF_BLACKHAND = -3532011, + GOSSIP_ITEM_HUMAN_FOOTMAN = -3532012, + GOSSIP_ITEM_HUMAN_CHARGER = -3532013, + GOSSIP_ITEM_WATER_ELEMENTAL = -3532014, + GOSSIP_ITEM_HUMAN_CONJURER = -3532015, + GOSSIP_ITEM_HUMAN_CLERIC = -3532016, + GOSSIP_ITEM_KING_LLANE = -3532017, + GOSSIP_ITEM_RESET_BOARD = -3532018, + + // gossip menu + GOSSIP_MENU_ID_GRUNT = 10425, + GOSSIP_MENU_ID_WOLF = 10439, + GOSSIP_MENU_ID_WARLOCK = 10440, + GOSSIP_MENU_ID_NECROLYTE = 10434, + GOSSIP_MENU_ID_DEAMON = 10426, + GOSSIP_MENU_ID_BLACKHAND = 10442, + GOSSIP_MENU_ID_FOOTMAN = 8952, + GOSSIP_MENU_ID_CHARGER = 10414, + GOSSIP_MENU_ID_CONJURER = 10417, + GOSSIP_MENU_ID_CLERIC = 10416, + GOSSIP_MENU_ID_ELEMENTAL = 10413, + GOSSIP_MENU_ID_LLANE = 10418, + GOSSIP_MENU_ID_MEDIVH = 10506, + GOSSIP_MENU_ID_MEDIVH_BEATEN = 10718, + + // misc + TARGET_TYPE_RANDOM = 1, + TARGET_TYPE_FRIENDLY = 2, + } + + // npc_echo_of_medivh + public class npc_echo_of_medivh : CreatureScript + { + public npc_echo_of_medivh() : base("npc_echo_of_medivh") { } + + class npc_echo_of_medivhAI : ScriptedAI + { + public npc_echo_of_medivhAI(Creature pCreature) : base(pCreature) + { + m_pInstance = (instance_karazhan.instance_karazhan_InstanceMapScript)pCreature.GetInstanceScript(); + Reset(); + } + + public override void Reset() + { + m_uiCheatTimer = 90000; + } + + public override void MoveInLineOfSight(Unit pWho) { } + + public override void AttackStart(Unit pWho) { } + + public override void JustSummoned(Creature pSummoned) + { + if (pSummoned.GetEntry() == NPC_FURY_MEDIVH_VISUAL) + pSummoned.CastSpell(pSummoned, SPELL_FURY_OF_MEDIVH_AURA, true); + } + + public override void UpdateAI(uint uiDiff) + { + if (m_pInstance == null || m_pInstance.GetData(TYPE_CHESS) != IN_PROGRESS) + return; + + if (m_uiCheatTimer < uiDiff) + { + DoCastSpellIfCan(m_creature, RandomHelper.URand(0, 1) ? (m_pInstance.GetPlayerTeam() == ALLIANCE ? SPELL_HAND_OF_MEDIVH_HORDE : SPELL_HAND_OF_MEDIVH_ALLIANCE) : + (m_pInstance.GetPlayerTeam() == ALLIANCE ? SPELL_FURY_OF_MEDIVH_ALLIANCE : SPELL_FURY_OF_MEDIVH_HORDE)); + + switch (RandomHelper.URand(0, 2)) + { + case 0: DoPlaySoundToSet(me, SOUND_ID_CHEAT_1); break; + case 1: DoPlaySoundToSet(me, SOUND_ID_CHEAT_2); break; + case 2: DoPlaySoundToSet(me, SOUND_ID_CHEAT_3); break; + } + + DoScriptText(EMOTE_CHEAT, me); + m_uiCheatTimer = 90000; + } + else + m_uiCheatTimer -= uiDiff; + } + + instance_karazhan.instance_karazhan_InstanceMapScript m_pInstance; + uint m_uiCheatTimer; + } + + public override CreatureAI GetAI(Creature pCreature) + { + return new npc_echo_of_medivhAI(pCreature); + } + + public override bool OnGossipHello(Player player, Creature creature) + { + InstanceScript pInstance = creature.GetInstanceScript(); + if (pInstance != null) + { + if (pInstance.GetData(TYPE_CHESS) != DONE && pInstance.GetData(TYPE_CHESS) != SPECIAL) + player.SEND_GOSSIP_MENU(GOSSIP_MENU_ID_MEDIVH, pCreature.GetObjectGuid()); + else + { + if (pInstance.GetData(TYPE_CHESS) == SPECIAL) + player.ADD_GOSSIP_ITEM_ID(GOSSIP_ICON_CHAT, GOSSIP_ITEM_RESET_BOARD, GOSSIP_SENDER_MAIN, GOSSIP_ACTION_INFO_DEF + 1); + + player.SEND_GOSSIP_MENU(GOSSIP_MENU_ID_MEDIVH_BEATEN, pCreature.GetObjectGuid()); + } + + return true; + } + + return false; + } + + public override bool OnGossipSelect(Player player, Creature creature, uint sender, uint action) + { + if (uiAction == GOSSIP_ACTION_INFO_DEF + 1) + { + // reset the board + InstanceScript pInstance = creature.GetInstanceScript(); + if (pInstance != null) + pInstance.SetData(TYPE_CHESS, DONE); + + player.CLOSE_GOSSIP_MENU(); + } + + return true; + } + } + + //npc_chess_piece_generic + public class npc_chess_piece_generic : CreatureScript + { + public npc_chess_piece_generic() : base("") { } + + public class npc_chess_piece_genericAI : ScriptedAI + { + public npc_chess_piece_genericAI(Creature pCreature) : base(pCreature) + { + m_pInstance = (instance_karazhan.instance_karazhan_InstanceMapScript)pCreature.GetInstanceScript(); + Reset(); + } + + public instance_karazhan.instance_karazhan_InstanceMapScript m_pInstance; + + ObjectGuid m_currentSquareGuid; + + uint m_uiMoveTimer; + uint m_uiMoveCommandTimer; + uint m_uiSpellCommandTimer; + + bool m_bIsPrimarySpell; + float m_fCurrentOrientation; + + public override void Reset() + { + m_uiMoveTimer = 0; + m_uiMoveCommandTimer = 1000; + m_uiSpellCommandTimer = m_creature.HasAura(SPELL_CONTROL_PIECE) ? 0 : 1000; + m_bIsPrimarySpell = true; + + // cancel move timer for player faction npcs or for friendly games + if (m_pInstance) + { + if ((m_pInstance.GetPlayerTeam() == ALLIANCE && m_creature.getFaction() == FACTION_ID_CHESS_ALLIANCE) || + (m_pInstance.GetPlayerTeam() == HORDE && m_creature.getFaction() == FACTION_ID_CHESS_HORDE) || + m_pInstance.GetData(TYPE_CHESS) == DONE) + m_uiMoveCommandTimer = 0; + } + } + + // no default attacking or evading + public override void MoveInLineOfSight(Unit pWho) { } + public override void AttackStart(Unit pWho) { } + public override void EnterEvadeMode() { } + + public override void JustDied(Unit pKiller) + { + if (Creature pSquare = m_creature.GetMap().GetCreature(m_currentSquareGuid)) + pSquare.RemoveAllAuras(); + + // ToDo: remove corpse after 10 sec + } + + void ReceiveAIEvent(SmartEvents eventType, Creature pSender, Unit pInvoker, uint uiMiscValue) + { + // handle move event + if (eventType == AI_EVENT_CUSTOM_A) + { + // clear the current square + if (Creature pSquare = m_creature.GetMap().GetCreature(m_currentSquareGuid)) + pSquare.RemoveAllAuras(); + + m_currentSquareGuid = pInvoker.GetObjectGuid(); + m_uiMoveTimer = 2000; + } + // handle encounter start event + else if (eventType == AI_EVENT_CUSTOM_B) + { + // reset the variables + Reset(); + m_currentSquareGuid = pInvoker.GetObjectGuid(); + + // ToDo: enable this when the scope of the spell is clear + //if (Creature pStalker = m_pInstance.GetSingleCreatureFromStorage(NPC_WAITING_ROOM_STALKER)) + // pStalker.CastSpell(pStalker, SPELL_AI_ACTION_TIMER, true); + + //DoCastSpellIfCan(m_creature, SPELL_AI_SNAPSHOT_TIMER, CAST_TRIGGERED); + DoCastSpellIfCan(m_creature, SPELL_CHESS_AI_ATTACK_TIMER, CAST_TRIGGERED); + + pInvoker.CastSpell(pInvoker, SPELL_DISABLE_SQUARE, true); + pInvoker.CastSpell(pInvoker, SPELL_IS_SQUARE_USED, true); + } + } + + void MovementInform(uint uiMotionType, uint uiPointId) + { + if (uiMotionType != POINT_MOTION_TYPE || !uiPointId) + return; + + // update facing + if (Unit pTarget = GetTargetByType(TARGET_TYPE_RANDOM, 5.0f)) + DoCastSpellIfCan(pTarget, SPELL_CHANGE_FACING); + else + m_creature.SetFacingTo(m_fCurrentOrientation); + } + + public override void SpellHit(Unit pCaster, SpellInfo pSpell) + { + // do a soft reset when the piece is controlled + if (pCaster.GetTypeId() == TYPEID_PLAYER && pSpell.Id == SPELL_CONTROL_PIECE) + Reset(); + } + + // Function which returns a random target by type and range + public Unit GetTargetByType(byte uiType, float fRange, float fArc = MathFunctions.Pi) + { + if (!m_pInstance) + return NULL; + + uint uiTeam = m_creature.getFaction() == FACTION_ID_CHESS_ALLIANCE ? FACTION_ID_CHESS_HORDE : FACTION_ID_CHESS_ALLIANCE; + + // get friendly list for this type + if (uiType == TARGET_TYPE_FRIENDLY) + uiTeam = m_creature.getFaction(); + + // Get the list of enemies + GuidList lTempList; + std::vector vTargets; + vTargets.reserve(lTempList.size()); + + m_pInstance.GetChessPiecesByFaction(lTempList, uiTeam); + for (GuidList::const_iterator itr = lTempList.begin(); itr != lTempList.end(); ++itr) + { + Creature pTemp = m_creature.GetMap().GetCreature(*itr); + if (pTemp && pTemp.isAlive()) + { + // check for specified range targets and angle; Note: to be checked if the angle is right + if (fRange && !m_creature.isInFrontInMap(pTemp, fRange, fArc)) + continue; + + // skip friendly targets which are at full HP + if (uiType == TARGET_TYPE_FRIENDLY && pTemp.GetHealth() == pTemp.GetMaxHealth()) + continue; + + vTargets.push_back(pTemp); + } + } + + if (vTargets.empty()) + return NULL; + + return vTargets[urand(0, vTargets.size() - 1)]; + } + + // Function to get a square as close as possible to the enemy + Unit GetMovementSquare() + { + if (!m_pInstance) + return NULL; + + // define distance based on the spell radius + // this will replace the targeting sysmte of spells SPELL_MOVE_1 and SPELL_MOVE_2 + float fRadius = 10.0f; + std::list lSquaresList; + + // some pieces have special distance + switch (m_creature.GetEntry()) + { + case NPC_HUMAN_CONJURER: + case NPC_ORC_WARLOCK: + case NPC_HUMAN_CHARGER: + case NPC_ORC_WOLF: + fRadius = 15.0f; + break; + } + + // get all available squares for movement + GetCreatureListWithEntryInGrid(lSquaresList, m_creature, NPC_SQUARE_BLACK, fRadius); + GetCreatureListWithEntryInGrid(lSquaresList, m_creature, NPC_SQUARE_WHITE, fRadius); + + if (lSquaresList.empty()) + return NULL; + + // Get the list of enemies + GuidList lTempList; + std::list lEnemies; + + m_pInstance.GetChessPiecesByFaction(lTempList, m_creature.getFaction() == FACTION_ID_CHESS_ALLIANCE ? FACTION_ID_CHESS_HORDE : FACTION_ID_CHESS_ALLIANCE); + for (GuidList::const_iterator itr = lTempList.begin(); itr != lTempList.end(); ++itr) + { + Creature pTemp = m_creature.GetMap().GetCreature(*itr); + if (pTemp && pTemp.isAlive()) + lEnemies.push_back(pTemp); + } + + if (lEnemies.empty()) + return NULL; + + // Sort the enemies by distance and the squares compared to the distance to the closest enemy + lEnemies.sort(ObjectDistanceOrder(m_creature)); + lSquaresList.sort(ObjectDistanceOrder(lEnemies.front())); + + return lSquaresList.front(); + } + + public virtual uint DoCastPrimarySpell() { return 5000; } + public virtual uint DoCastSecondarySpell() { return 5000; } + + public override void UpdateAI(uint uiDiff) + { + if (!m_pInstance || m_pInstance.GetData(TYPE_CHESS) != IN_PROGRESS) + return; + + // issue move command + if (m_uiMoveCommandTimer) + { + if (m_uiMoveCommandTimer <= uiDiff) + { + // just update facing if some enemy is near + if (Unit pTarget = GetTargetByType(TARGET_TYPE_RANDOM, 5.0f)) + DoCastSpellIfCan(pTarget, SPELL_CHANGE_FACING); + else + { + // the npc doesn't have a 100% chance to move; also there should be some GCD check in core for this part + if (roll_chance_i(15)) + { + // Note: in a normal case the target would be chosen using the spells above + // However, because the core doesn't support special targeting, we'll provide explicit target + //uint uiMoveSpell = SPELL_MOVE_1; + //switch (m_creature.GetEntry()) + //{ + // case NPC_HUMAN_CONJURER: + // case NPC_ORC_WARLOCK: + // case NPC_HUMAN_CHARGER: + // case NPC_ORC_WOLF: + // uiMoveSpell = SPELL_MOVE_2; + // break; + //} + //DoCastSpellIfCan(m_creature, uiMoveSpell, CAST_TRIGGERED); + + // workaround which provides specific move target + if (Unit pTarget = GetMovementSquare()) + DoCastSpellIfCan(pTarget, SPELL_MOVE_GENERIC, CAST_TRIGGERED | CAST_INTERRUPT_PREVIOUS); + + m_fCurrentOrientation = m_creature.GetOrientation(); + } + } + + m_uiMoveCommandTimer = 5000; + } + else + m_uiMoveCommandTimer -= uiDiff; + } + + // issue spell command + if (m_uiSpellCommandTimer) + { + if (m_uiSpellCommandTimer <= uiDiff) + { + // alternate the spells and also reset the timer + m_uiSpellCommandTimer = m_bIsPrimarySpell ? DoCastPrimarySpell() : DoCastSecondarySpell(); + m_bIsPrimarySpell = !m_bIsPrimarySpell; + } + else + m_uiSpellCommandTimer -= uiDiff; + } + + // finish move timer + if (m_uiMoveTimer) + { + if (m_uiMoveTimer <= uiDiff) + { + if (Creature pSquare = m_creature.GetMap().GetCreature(m_currentSquareGuid)) + { + DoCastSpellIfCan(pSquare, SPELL_MOVE_MARKER, CAST_TRIGGERED); + m_creature.GetMotionMaster().MovePoint(1, pSquare.GetPositionX(), pSquare.GetPositionY(), pSquare.GetPositionZ()); + } + m_uiMoveTimer = 0; + } + else + m_uiMoveTimer -= uiDiff; + } + + if (!m_creature.SelectHostileTarget() || !m_creature.getVictim()) + return; + } + } + + public override bool OnGossipSelect(Player player, Creature creature, uint sender, uint action) + { + if (uiAction == GOSSIP_ACTION_INFO_DEF + 1) + { + // start event when used on the king + if (ScriptedInstance* pInstance = (ScriptedInstance*)pCreature.GetInstanceData()) + { + // teleport at the entrance and control the chess piece + player.CastSpell(player, SPELL_IN_GAME, true); + player.CastSpell(pCreature, SPELL_CONTROL_PIECE, true); + + if (pInstance.GetData(TYPE_CHESS) == NOT_STARTED) + pInstance.SetData(TYPE_CHESS, IN_PROGRESS); + else if (pInstance.GetData(TYPE_CHESS) == DONE) + pInstance.SetData(TYPE_CHESS, SPECIAL); + } + + player.CLOSE_GOSSIP_MENU(); + } + + return true; + } + + public override bool OnDummyEffect(Unit caster, uint spellId, int effIndex, Creature target) + { + // movement perform spell + if (uiSpellId == SPELL_MOVE_TO_SQUARE && uiEffIndex == EFFECT_INDEX_0) + { + if (pCaster.GetTypeId() == TYPEID_UNIT) + { + pCaster.CastSpell(pCaster, SPELL_DISABLE_SQUARE, true); + pCaster.CastSpell(pCaster, SPELL_IS_SQUARE_USED, true); + + pCreatureTarget.CastSpell(pCreatureTarget, SPELL_MOVE_COOLDOWN, true); + pCreatureTarget.AI().SendAIEvent(AI_EVENT_CUSTOM_A, pCaster, pCreatureTarget); + } + + return true; + } + // generic melee tick + else if (uiSpellId == SPELL_ACTION_MELEE && uiEffIndex == EFFECT_INDEX_0) + { + uint uiMeleeSpell = 0; + + switch (pCreatureTarget.GetEntry()) + { + case NPC_KING_LLANE: uiMeleeSpell = SPELL_MELEE_KING_LLANE; break; + case NPC_HUMAN_CHARGER: uiMeleeSpell = SPELL_MELEE_CHARGER; break; + case NPC_HUMAN_CLERIC: uiMeleeSpell = SPELL_MELEE_CLERIC; break; + case NPC_HUMAN_CONJURER: uiMeleeSpell = SPELL_MELEE_CONJURER; break; + case NPC_HUMAN_FOOTMAN: uiMeleeSpell = SPELL_MELEE_FOOTMAN; break; + case NPC_CONJURED_WATER_ELEMENTAL: uiMeleeSpell = SPELL_MELEE_WATER_ELEM; break; + case NPC_WARCHIEF_BLACKHAND: uiMeleeSpell = SPELL_MELEE_WARCHIEF_BLACKHAND; break; + case NPC_ORC_GRUNT: uiMeleeSpell = SPELL_MELEE_GRUNT; break; + case NPC_ORC_NECROLYTE: uiMeleeSpell = SPELL_MELEE_NECROLYTE; break; + case NPC_ORC_WARLOCK: uiMeleeSpell = SPELL_MELEE_WARLOCK; break; + case NPC_ORC_WOLF: uiMeleeSpell = SPELL_MELEE_WOLF; break; + case NPC_SUMMONED_DAEMON: uiMeleeSpell = SPELL_MELEE_DAEMON; break; + } + + pCreatureTarget.CastSpell(pCreatureTarget, uiMeleeSpell, true); + return true; + } + // square facing + else if (uiSpellId == SPELL_FACE_SQUARE && uiEffIndex == EFFECT_INDEX_0) + { + if (pCaster.GetTypeId() == TYPEID_UNIT) + pCreatureTarget.SetFacingToObject(pCaster); + + return true; + } + + return false; + } + } + + /*###### + ## npc_king_llane + ######*/ + public class npc_king_llane : CreatureScript + { + public npc_king_llane() : base("npc_king_llane") { } + + class npc_king_llaneAI : npc_chess_piece_generic.npc_chess_piece_genericAI + { + public npc_king_llaneAI(Creature pCreature) : base(pCreature) + { + m_bIsAttacked = false; + Reset(); + } + + bool m_bIsAttacked; + + public override void DamageTaken(Unit pDoneBy, ref uint uiDamage) + { + if (!uiDamage || !m_bIsAttacked || !m_pInstance || pDoneBy.GetTypeId() != TYPEID_UNIT) + return; + + if (Creature pMedivh = m_pInstance.GetSingleCreatureFromStorage(NPC_ECHO_MEDIVH)) + { + if (m_pInstance.GetPlayerTeam() == ALLIANCE) + DoPlaySoundToSet(pMedivh, SOUND_ID_CHECK_PLAYER); + else + DoPlaySoundToSet(pMedivh, SOUND_ID_CHECK_MEDIVH); + } + + m_bIsAttacked = true; + } + + public override void JustDied(Unit pKiller) + { + base.JustDied(pKiller); + + if (!m_pInstance) + return; + + Creature pMedivh = m_pInstance.GetSingleCreatureFromStorage(NPC_ECHO_MEDIVH); + if (!pMedivh) + return; + + if (m_pInstance.GetData(TYPE_CHESS) == SPECIAL) + m_pInstance.SetData(TYPE_CHESS, DONE); + else + { + if (m_pInstance.GetPlayerTeam() == HORDE) + { + DoPlaySoundToSet(pMedivh, SOUND_ID_WIN_PLAYER); + DoScriptText(EMOTE_LIFT_CURSE, pMedivh); + + m_pInstance.SetData(TYPE_CHESS, DONE); + } + else + { + DoPlaySoundToSet(pMedivh, SOUND_ID_WIN_MEDIVH); + m_pInstance.SetData(TYPE_CHESS, FAIL); + } + } + + m_pInstance.DoMoveChessPieceToSides(SPELL_TRANSFORM_KING_LLANE, FACTION_ID_CHESS_ALLIANCE, true); + } + + public override uint DoCastPrimarySpell() + { + if (Unit pTarget = GetTargetByType(TARGET_TYPE_RANDOM, 20.0f)) + { + DoCastSpellIfCan(m_creature, SPELL_HEROISM); + + // reset timer based on spell values + SpellInfo pSpell = GetSpellStore().LookupEntry(SPELL_HEROISM); + return pSpell.RecoveryTime ? pSpell.RecoveryTime : pSpell.CategoryRecoveryTime; + } + + return 5000; + } + + public override uint DoCastSecondarySpell() + { + if (Unit pTarget = GetTargetByType(TARGET_TYPE_RANDOM, 10.0f)) + { + DoCastSpellIfCan(m_creature, SPELL_SWEEP); + + // reset timer based on spell values + SpellInfo pSpell = GetSpellStore().LookupEntry(SPELL_SWEEP); + return pSpell.RecoveryTime ? pSpell.RecoveryTime : pSpell.CategoryRecoveryTime; + } + + return 5000; + } + } + + public override CreatureAI GetAI(Creature pCreature) + { + return new npc_king_llaneAI(pCreature); + } + + public override bool OnGossipHello(Player player, Creature creature) + { + if (player.HasAura(SPELL_RECENTLY_IN_GAME) || pCreature.HasAura(SPELL_CONTROL_PIECE)) + return true; + + if (instance_karazhan pInstance = (instance_karazhan)pCreature.GetInstanceData()) + { + if ((pInstance.GetData(TYPE_CHESS) != DONE && player.GetTeam() == ALLIANCE) || pInstance.IsFriendlyGameReady()) + player.ADD_GOSSIP_ITEM_ID(GOSSIP_ICON_CHAT, GOSSIP_ITEM_KING_LLANE, GOSSIP_SENDER_MAIN, GOSSIP_ACTION_INFO_DEF + 1); + } + + player.SEND_GOSSIP_MENU(GOSSIP_MENU_ID_LLANE, pCreature.GetObjectGuid()); + return true; + } + } + + /*###### + ## npc_warchief_blackhand + ######*/ + public class npc_warchief_blackhand : CreatureScript + { + public npc_warchief_blackhand() : base("npc_warchief_blackhand") { } + + class npc_warchief_blackhandAI : npc_chess_piece_generic.npc_chess_piece_genericAI + { + public npc_warchief_blackhandAI(Creature pCreature) : base(pCreature) + { + m_bIsAttacked = false; + Reset(); + } + + bool m_bIsAttacked; + + public override void DamageTaken(Unit pDoneBy, ref uint uiDamage) + { + if (!uiDamage || !m_bIsAttacked || !m_pInstance || pDoneBy.GetTypeId() != TYPEID_UNIT) + return; + + if (Creature pMedivh = m_pInstance.GetSingleCreatureFromStorage(NPC_ECHO_MEDIVH)) + { + if (m_pInstance.GetPlayerTeam() == HORDE) + DoPlaySoundToSet(pMedivh, SOUND_ID_CHECK_PLAYER); + else + DoPlaySoundToSet(pMedivh, SOUND_ID_CHECK_MEDIVH); + } + + m_bIsAttacked = true; + } + + public override void JustDied(Unit pKiller) + { + base.JustDied(pKiller); + + if (!m_pInstance) + return; + + Creature pMedivh = m_pInstance.GetSingleCreatureFromStorage(NPC_ECHO_MEDIVH); + if (!pMedivh) + return; + + if (m_pInstance.GetData(TYPE_CHESS) == SPECIAL) + m_pInstance.SetData(TYPE_CHESS, DONE); + else + { + if (m_pInstance.GetPlayerTeam() == ALLIANCE) + { + DoPlaySoundToSet(pMedivh, SOUND_ID_WIN_PLAYER); + DoScriptText(EMOTE_LIFT_CURSE, pMedivh); + + m_pInstance.SetData(TYPE_CHESS, DONE); + } + else + { + DoPlaySoundToSet(pMedivh, SOUND_ID_WIN_MEDIVH); + m_pInstance.SetData(TYPE_CHESS, FAIL); + } + } + + m_pInstance.DoMoveChessPieceToSides(SPELL_TRANSFORM_BLACKHAND, FACTION_ID_CHESS_HORDE, true); + } + + public override uint DoCastPrimarySpell() + { + if (Unit pTarget = GetTargetByType(TARGET_TYPE_RANDOM, 20.0f)) + { + DoCastSpellIfCan(m_creature, SPELL_BLOODLUST); + + // reset timer based on spell values + SpellInfo pSpell = GetSpellStore().LookupEntry(SPELL_BLOODLUST); + return pSpell.RecoveryTime ? pSpell.RecoveryTime : pSpell.CategoryRecoveryTime; + } + + return 5000; + } + + public override uint DoCastSecondarySpell() + { + if (Unit pTarget = GetTargetByType(TARGET_TYPE_RANDOM, 10.0f)) + { + DoCastSpellIfCan(m_creature, SPELL_CLEAVE); + + // reset timer based on spell values + SpellInfo pSpell = GetSpellStore().LookupEntry(SPELL_CLEAVE); + return pSpell.RecoveryTime ? pSpell.RecoveryTime : pSpell.CategoryRecoveryTime; + } + + return 5000; + } + } + + public override CreatureAI GetAI(Creature pCreature) + { + return new npc_warchief_blackhandAI(pCreature); + } + + public override bool OnGossipHello(Player player, Creature creature) + { + if (player.HasAura(SPELL_RECENTLY_IN_GAME) || pCreature.HasAura(SPELL_CONTROL_PIECE)) + return true; + + if (instance_karazhan pInstance = (instance_karazhan)pCreature.GetInstanceData()) + { + if (pInstance.GetData(TYPE_CHESS) != DONE && player.GetTeam() == HORDE || pInstance.IsFriendlyGameReady()) + player.ADD_GOSSIP_ITEM_ID(GOSSIP_ICON_CHAT, GOSSIP_ITEM_WARCHIEF_BLACKHAND, GOSSIP_SENDER_MAIN, GOSSIP_ACTION_INFO_DEF + 1); + } + + player.SEND_GOSSIP_MENU(GOSSIP_MENU_ID_BLACKHAND, pCreature.GetObjectGuid()); + return true; + } + } + + /*###### + ## npc_human_conjurer + ######*/ + public class npc_human_conjurer : CreatureScript + { + public npc_human_conjurer() : base("npc_human_conjurer") { } + + class npc_human_conjurerAI : npc_chess_piece_generic.npc_chess_piece_genericAI + { + public npc_human_conjurerAI(Creature pCreature) : base(pCreature) { Reset(); } + + public override void JustDied(Unit pKiller) + { + base.JustDied(pKiller); + + if (!m_pInstance) + return; + + Creature pMedivh = m_pInstance.GetSingleCreatureFromStorage(NPC_ECHO_MEDIVH); + if (!pMedivh) + return; + + if (m_pInstance.GetPlayerTeam() == ALLIANCE) + DoPlaySoundToSet(pMedivh, SOUND_ID_LOSE_QUEEN_PLAYER); + else + DoPlaySoundToSet(pMedivh, SOUND_ID_LOSE_QUEEN_MEDIVH); + + m_pInstance.DoMoveChessPieceToSides(SPELL_TRANSFORM_CONJURER, FACTION_ID_CHESS_ALLIANCE); + } + + public override uint DoCastPrimarySpell() + { + if (Unit pTarget = GetTargetByType(TARGET_TYPE_RANDOM, 20.0f)) + { + DoCastSpellIfCan(pTarget, SPELL_ELEMENTAL_BLAST); + + // reset timer based on spell values + SpellInfo pSpell = GetSpellStore().LookupEntry(SPELL_ELEMENTAL_BLAST); + return pSpell.RecoveryTime ? pSpell.RecoveryTime : pSpell.CategoryRecoveryTime; + } + + return 5000; + } + + public override uint DoCastSecondarySpell() + { + if (Unit pTarget = GetTargetByType(TARGET_TYPE_RANDOM, 25.0f)) + { + DoCastSpellIfCan(pTarget, SPELL_RAIN_OF_FIRE); + + // reset timer based on spell values + SpellInfo pSpell = GetSpellStore().LookupEntry(SPELL_RAIN_OF_FIRE); + return pSpell.RecoveryTime ? pSpell.RecoveryTime : pSpell.CategoryRecoveryTime; + } + + return 5000; + } + } + + public override CreatureAI GetAI(Creature pCreature) + { + return new npc_human_conjurerAI(pCreature); + } + + public override bool OnGossipHello(Player player, Creature creature) + { + if (player.HasAura(SPELL_RECENTLY_IN_GAME) || pCreature.HasAura(SPELL_CONTROL_PIECE)) + return true; + + if (ScriptedInstance* pInstance = (ScriptedInstance*)pCreature.GetInstanceData()) + { + if ((pInstance.GetData(TYPE_CHESS) == IN_PROGRESS && player.GetTeam() == ALLIANCE) || pInstance.GetData(TYPE_CHESS) == SPECIAL) + player.ADD_GOSSIP_ITEM_ID(GOSSIP_ICON_CHAT, GOSSIP_ITEM_HUMAN_CONJURER, GOSSIP_SENDER_MAIN, GOSSIP_ACTION_INFO_DEF + 1); + } + + player.SEND_GOSSIP_MENU(GOSSIP_MENU_ID_CONJURER, pCreature.GetObjectGuid()); + return true; + } + } + + /*###### + ## npc_orc_warlock + ######*/ + class npc_orc_warlock : CreatureScript + { + public npc_orc_warlock() : base("npc_orc_warlock") { } + + class npc_orc_warlockAI : npc_chess_piece_generic.npc_chess_piece_genericAI + { + public npc_orc_warlockAI(Creature pCreature) : base(pCreature) { Reset(); } + + public override void JustDied(Unit pKiller) + { + base.JustDied(pKiller); + + if (!m_pInstance) + return; + + Creature pMedivh = m_pInstance.GetSingleCreatureFromStorage(NPC_ECHO_MEDIVH); + if (!pMedivh) + return; + + if (m_pInstance.GetPlayerTeam() == HORDE) + DoPlaySoundToSet(pMedivh, SOUND_ID_LOSE_QUEEN_PLAYER); + else + DoPlaySoundToSet(pMedivh, SOUND_ID_LOSE_QUEEN_MEDIVH); + + m_pInstance.DoMoveChessPieceToSides(SPELL_TRANSFORM_WARLOCK, FACTION_ID_CHESS_HORDE); + } + + public override uint DoCastPrimarySpell() + { + if (Unit pTarget = GetTargetByType(TARGET_TYPE_RANDOM, 20.0f)) + { + DoCastSpellIfCan(pTarget, SPELL_FIREBALL); + + // reset timer based on spell values + SpellInfo pSpell = GetSpellStore().LookupEntry(SPELL_FIREBALL); + return pSpell.RecoveryTime ? pSpell.RecoveryTime : pSpell.CategoryRecoveryTime; + } + + return 5000; + } + + public override uint DoCastSecondarySpell() + { + if (Unit pTarget = GetTargetByType(TARGET_TYPE_RANDOM, 25.0f)) + { + DoCastSpellIfCan(pTarget, SPELL_POISON_CLOUD_ACTION); + + // reset timer based on spell values + SpellInfo pSpell = GetSpellStore().LookupEntry(SPELL_POISON_CLOUD_ACTION); + return pSpell.RecoveryTime ? pSpell.RecoveryTime : pSpell.CategoryRecoveryTime; + } + + return 5000; + } + } + + public override CreatureAI GetAI(Creature pCreature) + { + return new npc_orc_warlockAI(pCreature); + } + + public override bool OnGossipHello(Player player, Creature creature) + { + if (player.HasAura(SPELL_RECENTLY_IN_GAME) || pCreature.HasAura(SPELL_CONTROL_PIECE)) + return true; + + if (ScriptedInstance* pInstance = (ScriptedInstance*)pCreature.GetInstanceData()) + { + if ((pInstance.GetData(TYPE_CHESS) == IN_PROGRESS && player.GetTeam() == HORDE) || pInstance.GetData(TYPE_CHESS) == SPECIAL) + player.ADD_GOSSIP_ITEM_ID(GOSSIP_ICON_CHAT, GOSSIP_ITEM_ORC_WARLOCK, GOSSIP_SENDER_MAIN, GOSSIP_ACTION_INFO_DEF + 1); + } + + player.SEND_GOSSIP_MENU(GOSSIP_MENU_ID_WARLOCK, pCreature.GetObjectGuid()); + return true; + } + } + + /*###### + ## npc_human_footman + ######*/ + class npc_human_footman : CreatureScript + { + public npc_human_footman() : base("npc_human_footman") { } + + class npc_human_footmanAI : npc_chess_piece_generic.npc_chess_piece_genericAI + { + public npc_human_footmanAI(Creature pCreature) : base(pCreature) { Reset(); } + + public override void JustDied(Unit pKiller) + { + base.JustDied(pKiller); + + if (!m_pInstance) + return; + + Creature pMedivh = m_pInstance.GetSingleCreatureFromStorage(NPC_ECHO_MEDIVH); + if (!pMedivh) + return; + + if (m_pInstance.GetPlayerTeam() == ALLIANCE) + { + switch (urand(0, 2)) + { + case 0: DoPlaySoundToSet(pMedivh, SOUND_ID_LOSE_PAWN_PLAYER_1); break; + case 1: DoPlaySoundToSet(pMedivh, SOUND_ID_LOSE_PAWN_PLAYER_2); break; + case 2: DoPlaySoundToSet(pMedivh, SOUND_ID_LOSE_PAWN_PLAYER_3); break; + } + } + else + { + switch (urand(0, 2)) + { + case 0: DoPlaySoundToSet(pMedivh, SOUND_ID_LOSE_PAWN_MEDIVH_1); break; + case 1: DoPlaySoundToSet(pMedivh, SOUND_ID_LOSE_PAWN_MEDIVH_2); break; + case 2: DoPlaySoundToSet(pMedivh, SOUND_ID_LOSE_PAWN_MEDIVH_3); break; + } + } + + m_pInstance.DoMoveChessPieceToSides(SPELL_TRANSFORM_FOOTMAN, FACTION_ID_CHESS_ALLIANCE); + } + + public override uint DoCastPrimarySpell() + { + if (Unit pTarget = GetTargetByType(TARGET_TYPE_RANDOM, 8.0f, M_PI_F / 12)) + { + DoCastSpellIfCan(m_creature, SPELL_HEROIC_BLOW); + + // reset timer based on spell values + SpellInfo pSpell = GetSpellStore().LookupEntry(SPELL_HEROIC_BLOW); + return pSpell.RecoveryTime ? pSpell.RecoveryTime : pSpell.CategoryRecoveryTime; + } + + return 5000; + } + + public override uint DoCastSecondarySpell() + { + if (Unit pTarget = GetTargetByType(TARGET_TYPE_RANDOM, 8.0f)) + { + DoCastSpellIfCan(m_creature, SPELL_SHIELD_BLOCK); + + // reset timer based on spell values + SpellInfo pSpell = GetSpellStore().LookupEntry(SPELL_SHIELD_BLOCK); + return pSpell.RecoveryTime ? pSpell.RecoveryTime : pSpell.CategoryRecoveryTime; + } + + return 5000; + } + } + + public override CreatureAI GetAI(Creature pCreature) + { + return new npc_human_footmanAI(pCreature); + } + + public override bool OnGossipHello(Player player, Creature creature) + { + if (player.HasAura(SPELL_RECENTLY_IN_GAME) || pCreature.HasAura(SPELL_CONTROL_PIECE)) + return true; + + if (ScriptedInstance* pInstance = (ScriptedInstance*)pCreature.GetInstanceData()) + { + if ((pInstance.GetData(TYPE_CHESS) == IN_PROGRESS && player.GetTeam() == ALLIANCE) || pInstance.GetData(TYPE_CHESS) == SPECIAL) + player.ADD_GOSSIP_ITEM_ID(GOSSIP_ICON_CHAT, GOSSIP_ITEM_HUMAN_FOOTMAN, GOSSIP_SENDER_MAIN, GOSSIP_ACTION_INFO_DEF + 1); + } + + player.SEND_GOSSIP_MENU(GOSSIP_MENU_ID_FOOTMAN, pCreature.GetObjectGuid()); + return true; + } + } + + /*###### + ## npc_orc_grunt + ######*/ + class npc_orc_grunt : CreatureScript + { + public npc_orc_grunt() : base("npc_orc_grunt") { } + + class npc_orc_gruntAI : npc_chess_piece_generic.npc_chess_piece_genericAI + { + public npc_orc_gruntAI(Creature pCreature) : base(pCreature) { Reset(); } + + public override void JustDied(Unit pKiller) + { + base.JustDied(pKiller); + + if (!m_pInstance) + return; + + Creature pMedivh = m_pInstance.GetSingleCreatureFromStorage(NPC_ECHO_MEDIVH); + if (!pMedivh) + return; + + if (m_pInstance.GetPlayerTeam() == HORDE) + { + switch (urand(0, 2)) + { + case 0: DoPlaySoundToSet(pMedivh, SOUND_ID_LOSE_PAWN_PLAYER_1); break; + case 1: DoPlaySoundToSet(pMedivh, SOUND_ID_LOSE_PAWN_PLAYER_2); break; + case 2: DoPlaySoundToSet(pMedivh, SOUND_ID_LOSE_PAWN_PLAYER_3); break; + } + } + else + { + switch (urand(0, 2)) + { + case 0: DoPlaySoundToSet(pMedivh, SOUND_ID_LOSE_PAWN_MEDIVH_1); break; + case 1: DoPlaySoundToSet(pMedivh, SOUND_ID_LOSE_PAWN_MEDIVH_2); break; + case 2: DoPlaySoundToSet(pMedivh, SOUND_ID_LOSE_PAWN_MEDIVH_3); break; + } + } + + m_pInstance.DoMoveChessPieceToSides(SPELL_TRANSFORM_GRUNT, FACTION_ID_CHESS_HORDE); + } + + public override uint DoCastPrimarySpell() + { + if (Unit pTarget = GetTargetByType(TARGET_TYPE_RANDOM, 8.0f, M_PI_F / 12)) + { + DoCastSpellIfCan(m_creature, SPELL_VICIOUS_STRIKE); + + // reset timer based on spell values + SpellInfo pSpell = GetSpellStore().LookupEntry(SPELL_VICIOUS_STRIKE); + return pSpell.RecoveryTime ? pSpell.RecoveryTime : pSpell.CategoryRecoveryTime; + } + + return 5000; + } + + public override uint DoCastSecondarySpell() + { + if (Unit pTarget = GetTargetByType(TARGET_TYPE_RANDOM, 8.0f)) + { + DoCastSpellIfCan(m_creature, SPELL_WEAPON_DEFLECTION); + + // reset timer based on spell values + SpellInfo pSpell = GetSpellStore().LookupEntry(SPELL_WEAPON_DEFLECTION); + return pSpell.RecoveryTime ? pSpell.RecoveryTime : pSpell.CategoryRecoveryTime; + } + + return 5000; + } + } + + public override CreatureAI GetAI(Creature pCreature) + { + return new npc_orc_gruntAI(pCreature); + } + + public override bool OnGossipHello(Player player, Creature creature) + { + if (player.HasAura(SPELL_RECENTLY_IN_GAME) || pCreature.HasAura(SPELL_CONTROL_PIECE)) + return true; + + if (ScriptedInstance* pInstance = (ScriptedInstance*)pCreature.GetInstanceData()) + { + if ((pInstance.GetData(TYPE_CHESS) == IN_PROGRESS && player.GetTeam() == HORDE) || pInstance.GetData(TYPE_CHESS) == SPECIAL) + player.ADD_GOSSIP_ITEM_ID(GOSSIP_ICON_CHAT, GOSSIP_ITEM_ORC_GRUNT, GOSSIP_SENDER_MAIN, GOSSIP_ACTION_INFO_DEF + 1); + } + + player.SEND_GOSSIP_MENU(GOSSIP_MENU_ID_GRUNT, pCreature.GetObjectGuid()); + return true; + } + } + + /*###### + ## npc_water_elemental + ######*/ + class npc_water_elemental : CreatureScript + { + public npc_water_elemental() : base("npc_water_elemental") { } + + class npc_water_elementalAI : npc_chess_piece_generic.npc_chess_piece_genericAI + { + public npc_water_elementalAI(Creature pCreature) : base(pCreature) { Reset(); } + + public override void JustDied(Unit pKiller) + { + base.JustDied(pKiller); + + if (!m_pInstance) + return; + + Creature pMedivh = m_pInstance.GetSingleCreatureFromStorage(NPC_ECHO_MEDIVH); + if (!pMedivh) + return; + + if (m_pInstance.GetPlayerTeam() == ALLIANCE) + DoPlaySoundToSet(pMedivh, SOUND_ID_LOSE_ROOK_PLAYER); + else + DoPlaySoundToSet(pMedivh, SOUND_ID_LOSE_ROOK_MEDIVH); + + m_pInstance.DoMoveChessPieceToSides(SPELL_TRANSFORM_WATER_ELEM, FACTION_ID_CHESS_ALLIANCE); + } + + public override uint DoCastPrimarySpell() + { + if (Unit pTarget = GetTargetByType(TARGET_TYPE_RANDOM, 9.0f)) + { + DoCastSpellIfCan(m_creature, SPELL_GEYSER); + + // reset timer based on spell values + SpellInfo pSpell = GetSpellStore().LookupEntry(SPELL_GEYSER); + return pSpell.RecoveryTime ? pSpell.RecoveryTime : pSpell.CategoryRecoveryTime; + } + + return 5000; + } + + public override uint DoCastSecondarySpell() + { + if (Unit pTarget = GetTargetByType(TARGET_TYPE_RANDOM, 9.0f)) + { + DoCastSpellIfCan(m_creature, SPELL_WATER_SHIELD); + + // reset timer based on spell values + SpellInfo pSpell = GetSpellStore().LookupEntry(SPELL_WATER_SHIELD); + return pSpell.RecoveryTime ? pSpell.RecoveryTime : pSpell.CategoryRecoveryTime; + } + + return 5000; + } + } + + public override CreatureAI GetAI(Creature pCreature) + { + return new npc_water_elementalAI(pCreature); + } + + public override bool OnGossipHello(Player player, Creature creature) + { + if (player.HasAura(SPELL_RECENTLY_IN_GAME) || pCreature.HasAura(SPELL_CONTROL_PIECE)) + return true; + + if (ScriptedInstance* pInstance = (ScriptedInstance*)pCreature.GetInstanceData()) + { + if ((pInstance.GetData(TYPE_CHESS) == IN_PROGRESS && player.GetTeam() == ALLIANCE) || pInstance.GetData(TYPE_CHESS) == SPECIAL) + player.ADD_GOSSIP_ITEM_ID(GOSSIP_ICON_CHAT, GOSSIP_ITEM_WATER_ELEMENTAL, GOSSIP_SENDER_MAIN, GOSSIP_ACTION_INFO_DEF + 1); + } + + player.SEND_GOSSIP_MENU(GOSSIP_MENU_ID_ELEMENTAL, pCreature.GetObjectGuid()); + return true; + } + } + + /*###### + ## npc_summoned_daemon + ######*/ + class npc_summoned_daemon : CreatureScript + { + public npc_summoned_daemon() : base("npc_summoned_daemon") { } + + class npc_summoned_daemonAI : npc_chess_piece_generic.npc_chess_piece_genericAI + { + public npc_summoned_daemonAI(Creature pCreature) : base(pCreature) { Reset(); } + + public override void JustDied(Unit pKiller) + { + base.JustDied(pKiller); + + if (!m_pInstance) + return; + + Creature pMedivh = m_pInstance.GetSingleCreatureFromStorage(NPC_ECHO_MEDIVH); + if (!pMedivh) + return; + + if (m_pInstance.GetPlayerTeam() == HORDE) + DoPlaySoundToSet(pMedivh, SOUND_ID_LOSE_ROOK_PLAYER); + else + DoPlaySoundToSet(pMedivh, SOUND_ID_LOSE_ROOK_MEDIVH); + + m_pInstance.DoMoveChessPieceToSides(SPELL_TRANSFORM_DAEMON, FACTION_ID_CHESS_HORDE); + } + + public override uint DoCastPrimarySpell() + { + if (Unit pTarget = GetTargetByType(TARGET_TYPE_RANDOM, 9.0f)) + { + DoCastSpellIfCan(m_creature, SPELL_HELLFIRE); + + // reset timer based on spell values + SpellInfo pSpell = GetSpellStore().LookupEntry(SPELL_HELLFIRE); + return pSpell.RecoveryTime ? pSpell.RecoveryTime : pSpell.CategoryRecoveryTime; + } + + return 5000; + } + + public override uint DoCastSecondarySpell() + { + if (Unit pTarget = GetTargetByType(TARGET_TYPE_RANDOM, 9.0f)) + { + DoCastSpellIfCan(m_creature, SPELL_FIRE_SHIELD); + + // reset timer based on spell values + SpellInfo pSpell = GetSpellStore().LookupEntry(SPELL_FIRE_SHIELD); + return pSpell.RecoveryTime ? pSpell.RecoveryTime : pSpell.CategoryRecoveryTime; + } + + return 5000; + } + } + + public override CreatureAI GetAI(Creature pCreature) + { + return new npc_summoned_daemonAI(pCreature); + } + + public override bool OnGossipHello(Player player, Creature creature) + { + if (player.HasAura(SPELL_RECENTLY_IN_GAME) || pCreature.HasAura(SPELL_CONTROL_PIECE)) + return true; + + if (ScriptedInstance* pInstance = (ScriptedInstance*)pCreature.GetInstanceData()) + { + if ((pInstance.GetData(TYPE_CHESS) == IN_PROGRESS && player.GetTeam() == HORDE) || pInstance.GetData(TYPE_CHESS) == SPECIAL) + player.ADD_GOSSIP_ITEM_ID(GOSSIP_ICON_CHAT, GOSSIP_ITEM_SUMMONED_DEAMON, GOSSIP_SENDER_MAIN, GOSSIP_ACTION_INFO_DEF + 1); + } + + player.SEND_GOSSIP_MENU(GOSSIP_MENU_ID_DEAMON, pCreature.GetObjectGuid()); + return true; + } + } + + /*###### + ## npc_human_charger + ######*/ + class npc_human_charger : CreatureScript + { + public npc_human_charger() : base("npc_human_charger") { } + + class npc_human_chargerAI : npc_chess_piece_generic.npc_chess_piece_genericAI + { + public npc_human_chargerAI(Creature pCreature) : base(pCreature) { Reset(); } + + public override void JustDied(Unit pKiller) + { + base.JustDied(pKiller); + + if (!m_pInstance) + return; + + Creature pMedivh = m_pInstance.GetSingleCreatureFromStorage(NPC_ECHO_MEDIVH); + if (!pMedivh) + return; + + if (m_pInstance.GetPlayerTeam() == ALLIANCE) + DoPlaySoundToSet(pMedivh, SOUND_ID_LOSE_KNIGHT_PLAYER); + else + DoPlaySoundToSet(pMedivh, SOUND_ID_LOSE_KNIGHT_MEDIVH); + + m_pInstance.DoMoveChessPieceToSides(SPELL_TRANSFORM_CHARGER, FACTION_ID_CHESS_ALLIANCE); + } + + public override uint DoCastPrimarySpell() + { + if (Unit pTarget = GetTargetByType(TARGET_TYPE_RANDOM, 8.0f, M_PI_F / 12)) + { + DoCastSpellIfCan(m_creature, SPELL_SMASH); + + // reset timer based on spell values + SpellInfo pSpell = GetSpellStore().LookupEntry(SPELL_SMASH); + return pSpell.RecoveryTime ? pSpell.RecoveryTime : pSpell.CategoryRecoveryTime; + } + + return 5000; + } + + public override uint DoCastSecondarySpell() + { + if (Unit pTarget = GetTargetByType(TARGET_TYPE_RANDOM, 10.0f, M_PI_F / 12)) + { + DoCastSpellIfCan(m_creature, SPELL_STOMP); + + // reset timer based on spell values + SpellInfo pSpell = GetSpellStore().LookupEntry(SPELL_STOMP); + return pSpell.RecoveryTime ? pSpell.RecoveryTime : pSpell.CategoryRecoveryTime; + } + + return 5000; + } + } + + public override CreatureAI GetAI(Creature pCreature) + { + return new npc_human_chargerAI(pCreature); + } + + public override bool OnGossipHello(Player player, Creature creature) + { + if (player.HasAura(SPELL_RECENTLY_IN_GAME) || pCreature.HasAura(SPELL_CONTROL_PIECE)) + return true; + + if (ScriptedInstance* pInstance = (ScriptedInstance*)pCreature.GetInstanceData()) + { + if ((pInstance.GetData(TYPE_CHESS) == IN_PROGRESS && player.GetTeam() == ALLIANCE) || pInstance.GetData(TYPE_CHESS) == SPECIAL) + player.ADD_GOSSIP_ITEM_ID(GOSSIP_ICON_CHAT, GOSSIP_ITEM_HUMAN_CHARGER, GOSSIP_SENDER_MAIN, GOSSIP_ACTION_INFO_DEF + 1); + } + + player.SEND_GOSSIP_MENU(GOSSIP_MENU_ID_CHARGER, pCreature.GetObjectGuid()); + return true; + } + } + + /*###### + ## npc_orc_wolf + ######*/ + class npc_orc_wolf : CreatureScript + { + public npc_orc_wolf() : base("npc_orc_wolf") { } + + class npc_orc_wolfAI : npc_chess_piece_generic.npc_chess_piece_genericAI + { + public npc_orc_wolfAI(Creature pCreature) : base(pCreature) { Reset(); } + + public override void JustDied(Unit pKiller) + { + base.JustDied(pKiller); + + if (!m_pInstance) + return; + + Creature pMedivh = m_pInstance.GetSingleCreatureFromStorage(NPC_ECHO_MEDIVH); + if (!pMedivh) + return; + + if (m_pInstance.GetPlayerTeam() == HORDE) + DoPlaySoundToSet(pMedivh, SOUND_ID_LOSE_KNIGHT_PLAYER); + else + DoPlaySoundToSet(pMedivh, SOUND_ID_LOSE_KNIGHT_MEDIVH); + + m_pInstance.DoMoveChessPieceToSides(SPELL_TRANSFORM_WOLF, FACTION_ID_CHESS_HORDE); + } + + public override uint DoCastPrimarySpell() + { + if (Unit pTarget = GetTargetByType(TARGET_TYPE_RANDOM, 8.0f, M_PI_F / 12)) + { + DoCastSpellIfCan(m_creature, SPELL_BITE); + + // reset timer based on spell values + SpellInfo pSpell = GetSpellStore().LookupEntry(SPELL_BITE); + return pSpell.RecoveryTime ? pSpell.RecoveryTime : pSpell.CategoryRecoveryTime; + } + + return 5000; + } + + public override uint DoCastSecondarySpell() + { + if (Unit pTarget = GetTargetByType(TARGET_TYPE_RANDOM, 10.0f, M_PI_F / 12)) + { + DoCastSpellIfCan(m_creature, SPELL_HOWL); + + // reset timer based on spell values + SpellInfo pSpell = GetSpellStore().LookupEntry(SPELL_HOWL); + return pSpell.RecoveryTime ? pSpell.RecoveryTime : pSpell.CategoryRecoveryTime; + } + + return 5000; + } + } + + public override CreatureAI GetAI(Creature pCreature) + { + return new npc_orc_wolfAI(pCreature); + } + + public override bool OnGossipHello(Player player, Creature creature) + { + if (player.HasAura(SPELL_RECENTLY_IN_GAME) || pCreature.HasAura(SPELL_CONTROL_PIECE)) + return true; + + if (ScriptedInstance* pInstance = (ScriptedInstance*)pCreature.GetInstanceData()) + { + if ((pInstance.GetData(TYPE_CHESS) == IN_PROGRESS && player.GetTeam() == HORDE) || pInstance.GetData(TYPE_CHESS) == SPECIAL) + player.ADD_GOSSIP_ITEM_ID(GOSSIP_ICON_CHAT, GOSSIP_ITEM_ORC_WOLF, GOSSIP_SENDER_MAIN, GOSSIP_ACTION_INFO_DEF + 1); + } + + player.SEND_GOSSIP_MENU(GOSSIP_MENU_ID_WOLF, pCreature.GetObjectGuid()); + return true; + } + } + + /*###### + ## npc_human_cleric + ######*/ + class npc_human_cleric : CreatureScript + { + public npc_human_cleric() : base("npc_human_cleric") { } + + class npc_human_clericAI : npc_chess_piece_generic.npc_chess_piece_genericAI + { + public npc_human_clericAI(Creature pCreature) : base(pCreature) { Reset(); } + + public override void JustDied(Unit pKiller) + { + base.JustDied(pKiller); + + if (!m_pInstance) + return; + + Creature pMedivh = m_pInstance.GetSingleCreatureFromStorage(NPC_ECHO_MEDIVH); + if (!pMedivh) + return; + + if (m_pInstance.GetPlayerTeam() == ALLIANCE) + DoPlaySoundToSet(pMedivh, SOUND_ID_LOSE_BISHOP_PLAYER); + else + DoPlaySoundToSet(pMedivh, SOUND_ID_LOSE_BISHOP_MEDIVH); + + m_pInstance.DoMoveChessPieceToSides(SPELL_TRANSFORM_CLERIC, FACTION_ID_CHESS_ALLIANCE); + } + + public override uint DoCastPrimarySpell() + { + if (Unit pTarget = GetTargetByType(TARGET_TYPE_FRIENDLY, 25.0f)) + { + DoCastSpellIfCan(pTarget, SPELL_HEALING); + + // reset timer based on spell values + SpellInfo pSpell = GetSpellStore().LookupEntry(SPELL_HEALING); + return pSpell.RecoveryTime ? pSpell.RecoveryTime : pSpell.CategoryRecoveryTime; + } + + return 5000; + } + + public override uint DoCastSecondarySpell() + { + if (Unit pTarget = GetTargetByType(TARGET_TYPE_RANDOM, 18.0f, M_PI_F / 12)) + { + DoCastSpellIfCan(m_creature, SPELL_HOLY_LANCE); + + // reset timer based on spell values + SpellInfo pSpell = GetSpellStore().LookupEntry(SPELL_HOLY_LANCE); + return pSpell.RecoveryTime ? pSpell.RecoveryTime : pSpell.CategoryRecoveryTime; + } + + return 5000; + } + } + + public override CreatureAI GetAI(Creature pCreature) + { + return new npc_human_clericAI(pCreature); + } + + public override bool OnGossipHello(Player player, Creature creature) + { + if (player.HasAura(SPELL_RECENTLY_IN_GAME) || pCreature.HasAura(SPELL_CONTROL_PIECE)) + return true; + + if (ScriptedInstance* pInstance = (ScriptedInstance*)pCreature.GetInstanceData()) + { + if ((pInstance.GetData(TYPE_CHESS) == IN_PROGRESS && player.GetTeam() == ALLIANCE) || pInstance.GetData(TYPE_CHESS) == SPECIAL) + player.ADD_GOSSIP_ITEM_ID(GOSSIP_ICON_CHAT, GOSSIP_ITEM_HUMAN_CLERIC, GOSSIP_SENDER_MAIN, GOSSIP_ACTION_INFO_DEF + 1); + } + + player.SEND_GOSSIP_MENU(GOSSIP_MENU_ID_CLERIC, pCreature.GetObjectGuid()); + return true; + } + } + + /*###### + ## npc_orc_necrolyte + ######*/ + class npc_orc_necrolyte : CreatureScript + { + public npc_orc_necrolyte() : base("npc_orc_necrolyte") { } + + class npc_orc_necrolyteAI : npc_chess_piece_generic.npc_chess_piece_genericAI + { + public npc_orc_necrolyteAI(Creature pCreature) : base(pCreature) { Reset(); } + + public override void JustDied(Unit pKiller) + { + base.JustDied(pKiller); + + if (!m_pInstance) + return; + + Creature pMedivh = m_pInstance.GetSingleCreatureFromStorage(NPC_ECHO_MEDIVH); + if (!pMedivh) + return; + + if (m_pInstance.GetPlayerTeam() == HORDE) + DoPlaySoundToSet(pMedivh, SOUND_ID_LOSE_BISHOP_PLAYER); + else + DoPlaySoundToSet(pMedivh, SOUND_ID_LOSE_BISHOP_MEDIVH); + + m_pInstance.DoMoveChessPieceToSides(SPELL_TRANSFORM_NECROLYTE, FACTION_ID_CHESS_HORDE); + } + + public override uint DoCastPrimarySpell() + { + if (Unit pTarget = GetTargetByType(TARGET_TYPE_FRIENDLY, 25.0f)) + { + DoCastSpellIfCan(pTarget, SPELL_SHADOW_MEND_ACTION); + + // reset timer based on spell values + SpellInfo pSpell = GetSpellStore().LookupEntry(SPELL_SHADOW_MEND_ACTION); + return pSpell.RecoveryTime ? pSpell.RecoveryTime : pSpell.CategoryRecoveryTime; + } + + return 5000; + } + + public override uint DoCastSecondarySpell() + { + if (Unit pTarget = GetTargetByType(TARGET_TYPE_RANDOM, 18.0f, M_PI_F / 12)) + { + DoCastSpellIfCan(m_creature, SPELL_SHADOW_SPEAR); + + // reset timer based on spell values + SpellInfo pSpell = GetSpellStore().LookupEntry(SPELL_SHADOW_SPEAR); + return pSpell.RecoveryTime ? pSpell.RecoveryTime : pSpell.CategoryRecoveryTime; + } + + return 5000; + } + } + + public override CreatureAI GetAI(Creature pCreature) + { + return new npc_orc_necrolyteAI(pCreature); + } + + public override bool OnGossipHello(Player player, Creature creature) + { + if (player.HasAura(SPELL_RECENTLY_IN_GAME) || pCreature.HasAura(SPELL_CONTROL_PIECE)) + return true; + + if (ScriptedInstance* pInstance = (ScriptedInstance*)pCreature.GetInstanceData()) + { + if ((pInstance.GetData(TYPE_CHESS) == IN_PROGRESS && player.GetTeam() == HORDE) || pInstance.GetData(TYPE_CHESS) == SPECIAL) + player.ADD_GOSSIP_ITEM_ID(GOSSIP_ICON_CHAT, GOSSIP_ITEM_ORC_NECROLYTE, GOSSIP_SENDER_MAIN, GOSSIP_ACTION_INFO_DEF + 1); + } + + player.SEND_GOSSIP_MENU(GOSSIP_MENU_ID_NECROLYTE, pCreature.GetObjectGuid()); + return true; + } + } +} diff --git a/Scripts/EasternKingdoms/Karazhan/Curator.cs b/Scripts/EasternKingdoms/Karazhan/Curator.cs new file mode 100644 index 000000000..d9ecf9272 --- /dev/null +++ b/Scripts/EasternKingdoms/Karazhan/Curator.cs @@ -0,0 +1,168 @@ +/* + * Copyright (C) 2012-2017 CypherCore + * + * 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, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +using Framework.Constants; +using Game.AI; +using Game.Entities; +using Game.Scripting; +using System; + +namespace Scripts.EasternKingdoms.Karazhan.Curator +{ + struct TextIds + { + public const uint SayAggro = 0; + public const uint SaySummon = 1; + public const uint SayEvocate = 2; + public const uint SayEnrage = 3; + public const uint SayKill = 4; + public const uint SayDeath = 5; + } + + struct SpellIds + { + //Flare spell info + public const uint AstralFlarePassive = 30234; //Visual effect + Flare damage + + //Curator spell info + public const uint HatefulBolt = 30383; + public const uint Evocation = 30254; + public const uint Enrage = 30403; //Arcane Infusion: Transforms Curator and adds damage. + public const uint Berserk = 26662; + } + + [Script] + class boss_curator : CreatureScript + { + public boss_curator() : base("boss_curator") { } + + class boss_curatorAI : ScriptedAI + { + public boss_curatorAI(Creature creature) : base(creature) + { + Initialize(); + } + + void Initialize() + { + _scheduler.Schedule(TimeSpan.FromSeconds(10), task => + { + //Summon Astral Flare + Creature AstralFlare = DoSpawnCreature(17096, RandomHelper.Rand32() % 37, RandomHelper.Rand32() % 37, 0, 0, TempSummonType.TimedDespawnOOC, 5000); + Unit target = SelectTarget(SelectAggroTarget.Random, 0); + + if (AstralFlare && target) + { + AstralFlare.CastSpell(AstralFlare, SpellIds.AstralFlarePassive, false); + AstralFlare.GetAI().AttackStart(target); + } + + //Reduce Mana by 10% of max health + int mana = me.GetMaxPower(PowerType.Mana); + if (mana != 0) + { + mana /= 10; + me.ModifyPower(PowerType.Mana, -mana); + + //if this get's us below 10%, then we evocate (the 10th should be summoned now) + if (me.GetPower(PowerType.Mana) * 100 / me.GetMaxPower(PowerType.Mana) < 10) + { + Talk(TextIds.SayEvocate); + me.InterruptNonMeleeSpells(false); + DoCast(me, SpellIds.Evocation); + _scheduler.DelayAll(TimeSpan.FromSeconds(20)); + //Evocating = true; + //no AddTimer cooldown, this will make first flare appear instantly after evocate end, like expected + return; + } + else + { + if (RandomHelper.URand(0, 1) == 0) + { + Talk(TextIds.SaySummon); + } + } + } + + task.Repeat(); + }); + + _scheduler.Schedule(TimeSpan.FromSeconds(15), task => + { + if (Enraged) + task.Repeat(TimeSpan.FromSeconds(7)); + else + task.Repeat(); + + Unit target = SelectTarget(SelectAggroTarget.TopAggro, 1); + if (target) + DoCast(target, SpellIds.HatefulBolt); + }); + + Enraged = false; + } + + public override void Reset() + { + Initialize(); + + me.ApplySpellImmune(0, SpellImmunity.Damage, SpellSchoolMask.Arcane, true); + } + + public override void KilledUnit(Unit victim) + { + Talk(TextIds.SayKill); + } + + public override void JustDied(Unit killer) + { + Talk(TextIds.SayDeath); + } + + public override void EnterCombat(Unit victim) + { + Talk(TextIds.SayAggro); + } + + public override void UpdateAI(uint diff) + { + if (!UpdateVictim()) + return; + + _scheduler.Update(diff); + + if (!Enraged) + { + if (!HealthAbovePct(15)) + { + Enraged = true; + DoCast(me, SpellIds.Enrage); + Talk(TextIds.SayEnrage); + } + } + + } + + bool Enraged; + } + + public override CreatureAI GetAI(Creature creature) + { + return new boss_curatorAI(creature); + } + } +} diff --git a/Scripts/EasternKingdoms/Karazhan/InstanceKarazhan.cs b/Scripts/EasternKingdoms/Karazhan/InstanceKarazhan.cs new file mode 100644 index 000000000..68b5d1b5d --- /dev/null +++ b/Scripts/EasternKingdoms/Karazhan/InstanceKarazhan.cs @@ -0,0 +1,462 @@ +/* + * Copyright (C) 2012-2017 CypherCore + * + * 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, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +using Framework.Constants; +using Framework.IO; +using Game.Entities; +using Game.Maps; +using Game.Scripting; + +namespace Scripts.EasternKingdoms.Karazhan +{ + struct karazhanConst + { + public const uint MaxEncounter = 12; + + public const uint BossAttumen = 1; + public const uint BossMoroes = 2; + public const uint BossMaiden = 3; + public const uint OptionalBoss = 4; + public const uint BossOpera = 5; + public const uint Curator = 6; + public const uint Aran = 7; + public const uint Terestian = 8; + public const uint Netherspite = 9; + public const uint Chess = 10; + public const uint Malchezzar = 11; + public const uint Nightbane = 12; + + public static Dialogue[] OzDialogue = + { + new Dialogue(0, 6000), + new Dialogue(1, 18000), + new Dialogue(2, 9000), + new Dialogue(3, 15000) + }; + + public static Dialogue[] HoodDialogue = + { + new Dialogue(4, 6000), + new Dialogue(5, 10000), + new Dialogue(6, 14000), + new Dialogue(7, 15000) + }; + + public static Dialogue[] RAJDialogue = + { + new Dialogue(8, 5000), + new Dialogue(9, 7000), + new Dialogue(10, 14000), + new Dialogue(11, 14000) + }; + + // Entries and spawn locations for creatures in Oz event + public static float[][] Spawns = + { + new float[] { 17535, -10896}, // Dorothee + new float[] { 17546, -10891}, // Roar + new float[] { 17547, -10884}, // Tinhead + new float[] { 17543, -10902}, // Strawman + new float[] { 17603, -10892}, // Grandmother + new float[] { 17534, -10900}, // Julianne + }; + + public static float SPAWN_Z = 90.5f; + public static float SPAWN_Y = -1758f; + public static float SPAWN_O = 4.738f; + + public static string SAY_READY = "Splendid, I'm going to get the audience ready. Break a leg!"; + public static string SAY_OZ_INTRO1 = "Finally, everything is in place. Are you ready for your big stage debut?"; + public static string OZ_GOSSIP1 = "I'm not an actor."; + public static string SAY_OZ_INTRO2 = "Don't worry, you'll be fine. You look like a natural!"; + public static string OZ_GOSSIP2 = "Ok, I'll give it a try, then."; + + public static string SAY_RAJ_INTRO1 = "The romantic plays are really tough, but you'll do better this time. You have TALENT. Ready?"; + public static string RAJ_GOSSIP1 = "I've never been more ready."; + + public static string OZ_GM_GOSSIP1 = "[GM] Change event to EVENT_OZ"; + public static string OZ_GM_GOSSIP2 = "[GM] Change event to EVENT_HOOD"; + public static string OZ_GM_GOSSIP3 = "[GM] Change event to EVENT_RAJ"; + + // Barnes + public const uint SpellSpotlight = 25824; + public const uint SpellTuxedo = 32616; + + // Berthold + public const uint SpellTeleport = 39567; + + // Image of Medivh + public const uint SpellFireBall = 30967; + public const uint SpellUberFireball = 30971; + public const uint SpellConflagrationBlast = 30977; + public const uint SpellManaShield = 31635; + + public const uint NpcArcanagos = 17652; + public const uint NpcSpotlight = 19525; + } + + struct Dialogue + { + public Dialogue(int textid, uint timer) + { + TextId = textid; + Timer = timer; + } + + public int TextId; + public uint Timer; + } + + struct DataTypes + { + public const uint OperaPerformance = 13; + public const uint OperaOzDeathcount = 14; + + public const uint Kilrek = 15; + public const uint Terestian = 16; + public const uint Moroes = 17; + public const uint GoCurtains = 18; + public const uint GoStagedoorleft = 19; + public const uint GoStagedoorright = 20; + public const uint GoLibraryDoor = 21; + public const uint GoMassiveDoor = 22; + public const uint GoNetherDoor = 23; + public const uint GoGameDoor = 24; + public const uint GoGameExitDoor = 25; + + public const uint ImageOfMedivh = 26; + public const uint MastersTerraceDoor1 = 27; + public const uint MastersTerraceDoor2 = 28; + public const uint GoSideEntranceDoor = 29; + } + + struct OperaEvents + { + public const uint Oz = 1; + public const uint Hood = 2; + public const uint RAJ = 3; + } + + [Script] + public class instance_karazhan : InstanceMapScript + { + public instance_karazhan() : base("instance_karazhan", 532) { } + + public class instance_karazhan_InstanceMapScript : InstanceScript + { + public instance_karazhan_InstanceMapScript(Map map) : base(map) + { + SetHeaders("KZ"); + + // 1 - OZ, 2 - HOOD, 3 - RAJ, this never gets altered. + m_uiOperaEvent = RandomHelper.URand(1, 3); + m_uiOzDeathCount = 0; + } + + public override bool IsEncounterInProgress() + { + for (byte i = 0; i < karazhanConst.MaxEncounter; ++i) + if (m_auiEncounter[i] == (uint)EncounterState.InProgress) + return true; + + return false; + } + + public override void OnCreatureCreate(Creature creature) + { + switch (creature.GetEntry()) + { + case 17229: + m_uiKilrekGUID = creature.GetGUID(); + break; + case 15688: + m_uiTerestianGUID = creature.GetGUID(); + break; + case 15687: + m_uiMoroesGUID = creature.GetGUID(); + break; + } + } + + public override void SetData(uint type, uint uiData) + { + switch (type) + { + case karazhanConst.BossAttumen: + m_auiEncounter[0] = uiData; + break; + case karazhanConst.BossMoroes: + if (m_auiEncounter[1] == (uint)EncounterState.Done) + break; + m_auiEncounter[1] = uiData; + break; + case karazhanConst.BossMaiden: + m_auiEncounter[2] = uiData; + break; + case karazhanConst.OptionalBoss: + m_auiEncounter[3] = uiData; + break; + case karazhanConst.BossOpera: + m_auiEncounter[4] = uiData; + if (uiData == (uint)EncounterState.Done) + UpdateEncounterStateForKilledCreature(16812, null); + break; + case karazhanConst.Curator: + m_auiEncounter[5] = uiData; + break; + case karazhanConst.Aran: + m_auiEncounter[6] = uiData; + break; + case karazhanConst.Terestian: + m_auiEncounter[7] = uiData; + break; + case karazhanConst.Netherspite: + m_auiEncounter[8] = uiData; + break; + case karazhanConst.Chess: + if (uiData == (uint)EncounterState.Done) + DoRespawnGameObject(DustCoveredChest, Time.Day); + m_auiEncounter[9] = uiData; + break; + case karazhanConst.Malchezzar: + m_auiEncounter[10] = uiData; + break; + case karazhanConst.Nightbane: + if (m_auiEncounter[11] != (uint)EncounterState.Done) + m_auiEncounter[11] = uiData; + break; + case DataTypes.OperaOzDeathcount: + if (uiData == (uint)EncounterState.Special) + ++m_uiOzDeathCount; + else if (uiData == (uint)EncounterState.InProgress) + m_uiOzDeathCount = 0; + break; + } + + if (uiData == (uint)EncounterState.Done) + { + OUT_SAVE_INST_DATA(); + + strSaveData = string.Format("{0} {1} {2} {3} {4} {5} {6} {7} {8} {9} {10} {11}", m_auiEncounter[0], m_auiEncounter[1], m_auiEncounter[2], m_auiEncounter[3], + m_auiEncounter[4], m_auiEncounter[5], m_auiEncounter[6], m_auiEncounter[7], m_auiEncounter[8], m_auiEncounter[9], m_auiEncounter[10], m_auiEncounter[11]); + + SaveToDB(); + OUT_SAVE_INST_DATA_COMPLETE(); + } + } + + public override void SetGuidData(uint identifier, ObjectGuid data) + { + if (identifier == DataTypes.ImageOfMedivh) + ImageGUID = data; + } + + public override void OnGameObjectCreate(GameObject go) + { + switch (go.GetEntry()) + { + case 183932: + m_uiCurtainGUID = go.GetGUID(); + break; + case 184278: + m_uiStageDoorLeftGUID = go.GetGUID(); + if (m_auiEncounter[4] == (uint)EncounterState.Done) + go.SetGoState(GameObjectState.Active); + break; + case 184279: + m_uiStageDoorRightGUID = go.GetGUID(); + if (m_auiEncounter[4] == (uint)EncounterState.Done) + go.SetGoState(GameObjectState.Active); + break; + case 184517: + m_uiLibraryDoor = go.GetGUID(); + break; + case 185521: + m_uiMassiveDoor = go.GetGUID(); + break; + case 184276: + m_uiGamesmansDoor = go.GetGUID(); + break; + case 184277: + m_uiGamesmansExitDoor = go.GetGUID(); + break; + case 185134: + m_uiNetherspaceDoor = go.GetGUID(); + break; + case 184274: + MastersTerraceDoor[0] = go.GetGUID(); + break; + case 184280: + MastersTerraceDoor[1] = go.GetGUID(); + break; + case 184275: + m_uiSideEntranceDoor = go.GetGUID(); + if (m_auiEncounter[4] == (uint)EncounterState.Done) + go.SetFlag(GameObjectFields.Flags, GameObjectFlags.Locked); + else + go.RemoveFlag(GameObjectFields.Flags, GameObjectFlags.Locked); + break; + case 185119: + DustCoveredChest = go.GetGUID(); + break; + } + + switch (m_uiOperaEvent) + { + /// @todo Set Object visibilities for Opera based on performance + case OperaEvents.Oz: + break; + + case OperaEvents.Hood: + break; + + case OperaEvents.RAJ: + break; + } + } + + public override string GetSaveData() + { + return strSaveData; + } + + public override uint GetData(uint uiData) + { + switch (uiData) + { + case karazhanConst.BossAttumen: + return m_auiEncounter[0]; + case karazhanConst.BossMoroes: + return m_auiEncounter[1]; + case karazhanConst.BossMaiden: + return m_auiEncounter[2]; + case karazhanConst.OptionalBoss: + return m_auiEncounter[3]; + case karazhanConst.BossOpera: + return m_auiEncounter[4]; + case karazhanConst.Curator: + return m_auiEncounter[5]; + case karazhanConst.Aran: + return m_auiEncounter[6]; + case karazhanConst.Terestian: + return m_auiEncounter[7]; + case karazhanConst.Netherspite: + return m_auiEncounter[8]; + case karazhanConst.Chess: + return m_auiEncounter[9]; + case karazhanConst.Malchezzar: + return m_auiEncounter[10]; + case karazhanConst.Nightbane: + return m_auiEncounter[11]; + case DataTypes.OperaPerformance: + return m_uiOperaEvent; + case DataTypes.OperaOzDeathcount: + return m_uiOzDeathCount; + } + + return 0; + } + + public override ObjectGuid GetGuidData(uint uiData) + { + switch (uiData) + { + case DataTypes.Kilrek: + return m_uiKilrekGUID; + case DataTypes.Terestian: + return m_uiTerestianGUID; + case DataTypes.Moroes: + return m_uiMoroesGUID; + case DataTypes.GoStagedoorleft: + return m_uiStageDoorLeftGUID; + case DataTypes.GoStagedoorright: + return m_uiStageDoorRightGUID; + case DataTypes.GoCurtains: + return m_uiCurtainGUID; + case DataTypes.GoLibraryDoor: + return m_uiLibraryDoor; + case DataTypes.GoMassiveDoor: + return m_uiMassiveDoor; + case DataTypes.GoSideEntranceDoor: + return m_uiSideEntranceDoor; + case DataTypes.GoGameDoor: + return m_uiGamesmansDoor; + case DataTypes.GoGameExitDoor: + return m_uiGamesmansExitDoor; + case DataTypes.GoNetherDoor: + return m_uiNetherspaceDoor; + case DataTypes.MastersTerraceDoor1: + return MastersTerraceDoor[0]; + case DataTypes.MastersTerraceDoor2: + return MastersTerraceDoor[1]; + case DataTypes.ImageOfMedivh: + return ImageGUID; + } + + return ObjectGuid.Empty; + } + + public override void Load(string str) + { + if (string.IsNullOrEmpty(str)) + { + OUT_LOAD_INST_DATA_FAIL(); + return; + } + + OUT_LOAD_INST_DATA(str); + StringArguments loadStream = new StringArguments(str); + + for (byte i = 0; i < karazhanConst.MaxEncounter; ++i) + { + var state = (EncounterState)loadStream.NextUInt32(); + // Do not load an encounter as "In Progress" - reset it instead. + m_auiEncounter[i] = (uint)(state == EncounterState.InProgress ? EncounterState.NotStarted : state); + } + + OUT_LOAD_INST_DATA_COMPLETE(); + } + + uint[] m_auiEncounter = new uint[karazhanConst.MaxEncounter]; + string strSaveData; + + uint m_uiOperaEvent; + uint m_uiOzDeathCount; + + ObjectGuid m_uiCurtainGUID; + ObjectGuid m_uiStageDoorLeftGUID; + ObjectGuid m_uiStageDoorRightGUID; + ObjectGuid m_uiKilrekGUID; + ObjectGuid m_uiTerestianGUID; + ObjectGuid m_uiMoroesGUID; + ObjectGuid m_uiLibraryDoor; // Door at Shade of Aran + ObjectGuid m_uiMassiveDoor; // Door at Netherspite + ObjectGuid m_uiSideEntranceDoor; // Side Entrance + ObjectGuid m_uiGamesmansDoor; // Door before Chess + ObjectGuid m_uiGamesmansExitDoor; // Door after Chess + ObjectGuid m_uiNetherspaceDoor; // Door at Malchezaar + ObjectGuid[] MastersTerraceDoor = new ObjectGuid[2]; + ObjectGuid ImageGUID; + ObjectGuid DustCoveredChest; + } + + public override InstanceScript GetInstanceScript(InstanceMap map) + { + return new instance_karazhan_InstanceMapScript(map); + } + } +} diff --git a/Scripts/EasternKingdoms/Karazhan/Moroes.cs b/Scripts/EasternKingdoms/Karazhan/Moroes.cs new file mode 100644 index 000000000..99052f811 --- /dev/null +++ b/Scripts/EasternKingdoms/Karazhan/Moroes.cs @@ -0,0 +1,778 @@ +/* + * Copyright (C) 2012-2017 CypherCore + * + * 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, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +using Framework.Constants; +using Game.AI; +using Game.Entities; +using Game.Maps; +using Game.Scripting; +using System.Collections.Generic; + +namespace Scripts.EasternKingdoms.Karazhan.Moroes +{ + struct Misc + { + public static Position[] Locations = + { + new Position(-10991.0f, -1884.33f, 81.73f, 0.614315f), + new Position(-10989.4f, -1885.88f, 81.73f, 0.904913f), + new Position(-10978.1f, -1887.07f, 81.73f, 2.035550f), + new Position(-10975.9f, -1885.81f, 81.73f, 2.253890f), + }; + + public static uint[] Adds = + { + 17007, + 19872, + 19873, + 19874, + 19875, + 19876, + }; + } + + struct TextIds + { + public const uint Aggro = 0; + public const uint Special = 1; + public const uint Kill = 2; + public const uint Death = 3; + } + + struct SpellIds + { + public const uint Vanish = 29448; + public const uint Garrote = 37066; + public const uint Blind = 34694; + public const uint Gouge = 29425; + public const uint Frenzy = 37023; + + // Adds + public const uint Manaburn = 29405; + public const uint Mindfly = 29570; + public const uint Swpain = 34441; + public const uint Shadowform = 29406; + + public const uint Hammerofjustice = 13005; + public const uint Judgementofcommand = 29386; + public const uint Sealofcommand = 29385; + + public const uint Dispelmagic = 15090; + public const uint Greaterheal = 29564; + public const uint Holyfire = 29563; + public const uint Pwshield = 29408; + + public const uint Cleanse = 29380; + public const uint Greaterblessofmight = 29381; + public const uint Holylight = 29562; + public const uint Divineshield = 41367; + + public const uint Hamstring = 9080; + public const uint Mortalstrike = 29572; + public const uint Whirlwind = 29573; + + public const uint Disarm = 8379; + public const uint Heroicstrike = 29567; + public const uint Shieldbash = 11972; + public const uint Shieldwall = 29390; + } + + [Script] + class boss_moroes : CreatureScript + { + public boss_moroes() : base("boss_moroes") { } + + public override CreatureAI GetAI(Creature creature) + { + return GetInstanceAI(creature); + } + + public class boss_moroesAI : ScriptedAI + { + public boss_moroesAI(Creature creature) : base(creature) + { + instance = creature.GetInstanceScript(); + } + + public override void Reset() + { + Vanish_Timer = 30000; + Blind_Timer = 35000; + Gouge_Timer = 23000; + Wait_Timer = 0; + CheckAdds_Timer = 5000; + + Enrage = false; + InVanish = false; + if (me.IsAlive()) + SpawnAdds(); + + instance.SetData(karazhanConst.BossMoroes, (uint)EncounterState.NotStarted); + } + + void StartEvent() + { + instance.SetData(karazhanConst.BossMoroes, (uint)EncounterState.InProgress); + + DoZoneInCombat(); + } + + public override void EnterCombat(Unit who) + { + StartEvent(); + + Talk(TextIds.Aggro); + AddsAttack(); + DoZoneInCombat(); + } + + public override void KilledUnit(Unit victim) + { + Talk(TextIds.Kill); + } + + public override void JustDied(Unit killer) + { + Talk(TextIds.Death); + + instance.SetData(karazhanConst.BossMoroes, (uint)EncounterState.Done); + + DeSpawnAdds(); + + //remove aura from spell Garrote when Moroes dies + instance.DoRemoveAurasDueToSpellOnPlayers(SpellIds.Garrote); + } + + void SpawnAdds() + { + DeSpawnAdds(); + + if (isAddlistEmpty()) + { + List AddList = new List(); + + for (byte i = 0; i < 6; ++i) + AddList.Add(Misc.Adds[i]); + + AddList.RandomResize(4); + + byte c = 0; + for (var i = 0; i != AddList.Count && c < 4; ++i, ++c) + { + uint entry = AddList[i]; + Creature creature = me.SummonCreature(entry, Misc.Locations[c], TempSummonType.CorpseTimedDespawn, 10000); + if (creature) + { + AddGUID[c] = creature.GetGUID(); + AddId[c] = entry; + } + } + } + else + { + for (byte i = 0; i < 4; ++i) + { + Creature creature = me.SummonCreature(AddId[i], Misc.Locations[i], TempSummonType.CorpseTimedDespawn, 10000); + if (creature) + AddGUID[i] = creature.GetGUID(); + } + } + } + + bool isAddlistEmpty() + { + for (byte i = 0; i < 4; ++i) + if (AddId[i] == 0) + return true; + + return false; + } + + void DeSpawnAdds() + { + for (byte i = 0; i < 4; ++i) + { + if (!AddGUID[i].IsEmpty()) + { + Creature temp = ObjectAccessor.GetCreature(me, AddGUID[i]); + if (temp) + temp.DespawnOrUnsummon(); + } + } + } + + void AddsAttack() + { + for (byte i = 0; i < 4; ++i) + { + if (!AddGUID[i].IsEmpty()) + { + Creature temp = ObjectAccessor.GetCreature((me), AddGUID[i]); + if (temp && temp.IsAlive()) + { + temp.GetAI().AttackStart(me.GetVictim()); + DoZoneInCombat(temp); + } + else + EnterEvadeMode(); + } + } + } + + public override void UpdateAI(uint diff) + { + if (!UpdateVictim()) + return; + + if (instance.GetData(karazhanConst.BossMoroes) == 0) + { + EnterEvadeMode(); + return; + } + + if (!Enrage && HealthBelowPct(30)) + { + DoCast(me, SpellIds.Frenzy); + Enrage = true; + } + + if (CheckAdds_Timer <= diff) + { + for (byte i = 0; i < 4; ++i) + { + if (!AddGUID[i].IsEmpty()) + { + Creature temp = ObjectAccessor.GetCreature((me), AddGUID[i]); + if (temp && temp.IsAlive()) + if (!temp.GetVictim()) + temp.GetAI().AttackStart(me.GetVictim()); + } + } + CheckAdds_Timer = 5000; + } else CheckAdds_Timer -= diff; + + if (!Enrage) + { + //Cast Vanish, then Garrote random victim + if (Vanish_Timer <= diff) + { + DoCast(me, SpellIds.Vanish); + InVanish = true; + Vanish_Timer = 30000; + Wait_Timer = 5000; + } else Vanish_Timer -= diff; + + if (Gouge_Timer <= diff) + { + DoCastVictim(SpellIds.Gouge); + Gouge_Timer = 40000; + } else Gouge_Timer -= diff; + + if (Blind_Timer <= diff) + { + List targets = SelectTargetList(5, SelectAggroTarget.Random, me.GetCombatReach() * 5, true); + foreach (var i in targets) + { + + if (!me.IsWithinMeleeRange(i)) + { + DoCast(i, SpellIds.Blind); + break; + } + } + Blind_Timer = 40000; + } + else + Blind_Timer -= diff; + } + + if (InVanish) + { + if (Wait_Timer <= diff) + { + Talk(TextIds.Special); + + Unit target = SelectTarget(SelectAggroTarget.Random, 0, 100, true); + if (target) + target.CastSpell(target, SpellIds.Garrote, true); + + InVanish = false; + } + else + Wait_Timer -= diff; + } + + if (!InVanish) + DoMeleeAttackIfReady(); + } + + InstanceScript instance; + + public ObjectGuid[] AddGUID = new ObjectGuid[4]; + + uint Vanish_Timer; + uint Blind_Timer; + uint Gouge_Timer; + uint Wait_Timer; + uint CheckAdds_Timer; + uint[] AddId = new uint[4]; + + bool InVanish; + bool Enrage; + } + } + + class boss_moroes_guestAI : ScriptedAI + { + public boss_moroes_guestAI(Creature creature) : base(creature) + { + instance = creature.GetInstanceScript(); + } + + public override void Reset() + { + instance.SetData(karazhanConst.BossMoroes, (uint)EncounterState.NotStarted); + } + + public void AcquireGUID() + { + Creature Moroes = ObjectAccessor.GetCreature(me, instance.GetGuidData(DataTypes.Moroes)); + if (Moroes) + { + for (byte i = 0; i < 4; ++i) + { + ObjectGuid GUID = ((boss_moroes.boss_moroesAI)Moroes.GetAI()).AddGUID[i]; + if (!GUID.IsEmpty()) + GuestGUID[i] = GUID; + } + } + + } + + public Unit SelectGuestTarget() + { + ObjectGuid TempGUID = GuestGUID[RandomHelper.Rand32() % 4]; + if (!TempGUID.IsEmpty()) + { + Unit unit = Global.ObjAccessor.GetUnit(me, TempGUID); + if (unit && unit.IsAlive()) + return unit; + } + + return me; + } + + public override void UpdateAI(uint diff) + { + if (instance.GetData(karazhanConst.BossMoroes) == 0) + EnterEvadeMode(); + + DoMeleeAttackIfReady(); + } + + InstanceScript instance; + + ObjectGuid[] GuestGUID = new ObjectGuid[4]; + } + + [Script] + class boss_baroness_dorothea_millstipe : CreatureScript + { + public boss_baroness_dorothea_millstipe() : base("boss_baroness_dorothea_millstipe") { } + + public override CreatureAI GetAI(Creature creature) + { + return GetInstanceAI(creature); + } + + class boss_baroness_dorothea_millstipeAI : boss_moroes_guestAI + { + //Shadow Priest + public boss_baroness_dorothea_millstipeAI(Creature creature) : base(creature) { } + + uint ManaBurn_Timer; + uint MindFlay_Timer; + uint ShadowWordPain_Timer; + + public override void Reset() + { + ManaBurn_Timer = 7000; + MindFlay_Timer = 1000; + ShadowWordPain_Timer = 6000; + + DoCast(me, SpellIds.Shadowform, true); + + base.Reset(); + } + + public override void UpdateAI(uint diff) + { + if (!UpdateVictim()) + return; + + base.UpdateAI(diff); + + if (MindFlay_Timer <= diff) + { + DoCastVictim(SpellIds.Mindfly); + MindFlay_Timer = 12000; // 3 sec channeled + } else MindFlay_Timer -= diff; + + if (ManaBurn_Timer <= diff) + { + Unit target = SelectTarget(SelectAggroTarget.Random, 0, 100, true); + if (target) + if (target.getPowerType() == PowerType.Mana) + DoCast(target, SpellIds.Manaburn); + ManaBurn_Timer = 5000; // 3 sec cast + } else ManaBurn_Timer -= diff; + + if (ShadowWordPain_Timer <= diff) + { + Unit target = SelectTarget(SelectAggroTarget.Random, 0, 100, true); + if (target) + { + DoCast(target, SpellIds.Swpain); + ShadowWordPain_Timer = 7000; + } + } else ShadowWordPain_Timer -= diff; + } + } + } + + [Script] + class boss_baron_rafe_dreuger : CreatureScript + { + public boss_baron_rafe_dreuger() : base("boss_baron_rafe_dreuger") { } + + public override CreatureAI GetAI(Creature creature) + { + return GetInstanceAI(creature); + } + + class boss_baron_rafe_dreugerAI : boss_moroes_guestAI + { + //Retr Pally + public boss_baron_rafe_dreugerAI(Creature creature) : base(creature) { } + + uint HammerOfJustice_Timer; + uint SealOfCommand_Timer; + uint JudgementOfCommand_Timer; + + public override void Reset() + { + HammerOfJustice_Timer = 1000; + SealOfCommand_Timer = 7000; + JudgementOfCommand_Timer = SealOfCommand_Timer + 29000; + + base.Reset(); + } + + public override void UpdateAI(uint diff) + { + if (!UpdateVictim()) + return; + + base.UpdateAI(diff); + + if (SealOfCommand_Timer <= diff) + { + DoCast(me, SpellIds.Sealofcommand); + SealOfCommand_Timer = 32000; + JudgementOfCommand_Timer = 29000; + } else SealOfCommand_Timer -= diff; + + if (JudgementOfCommand_Timer <= diff) + { + DoCastVictim(SpellIds.Judgementofcommand); + JudgementOfCommand_Timer = SealOfCommand_Timer + 29000; + } else JudgementOfCommand_Timer -= diff; + + if (HammerOfJustice_Timer <= diff) + { + DoCastVictim(SpellIds.Hammerofjustice); + HammerOfJustice_Timer = 12000; + } else HammerOfJustice_Timer -= diff; + } + } + } + + [Script] + class boss_lady_catriona_von_indi : CreatureScript + { + public boss_lady_catriona_von_indi() : base("boss_lady_catriona_von_indi") { } + + public override CreatureAI GetAI(Creature creature) + { + return GetInstanceAI(creature); + } + + class boss_lady_catriona_von_indiAI : boss_moroes_guestAI + { + //Holy Priest + public boss_lady_catriona_von_indiAI(Creature creature) : base(creature) { } + + uint DispelMagic_Timer; + uint GreaterHeal_Timer; + uint HolyFire_Timer; + uint PowerWordShield_Timer; + + public override void Reset() + { + DispelMagic_Timer = 11000; + GreaterHeal_Timer = 1500; + HolyFire_Timer = 5000; + PowerWordShield_Timer = 1000; + + AcquireGUID(); + + base.Reset(); + } + + public override void UpdateAI(uint diff) + { + if (!UpdateVictim()) + return; + + base.UpdateAI(diff); + + if (PowerWordShield_Timer <= diff) + { + DoCast(me, SpellIds.Pwshield); + PowerWordShield_Timer = 15000; + } else PowerWordShield_Timer -= diff; + + if (GreaterHeal_Timer <= diff) + { + Unit target = SelectGuestTarget(); + + DoCast(target, SpellIds.Greaterheal); + GreaterHeal_Timer = 17000; + } else GreaterHeal_Timer -= diff; + + if (HolyFire_Timer <= diff) + { + DoCastVictim(SpellIds.Holyfire); + HolyFire_Timer = 22000; + } else HolyFire_Timer -= diff; + + if (DispelMagic_Timer <= diff) + { + Unit target = RandomHelper.RAND(SelectGuestTarget(), SelectTarget(SelectAggroTarget.Random, 0, 100, true)); + if (target) + DoCast(target, SpellIds.Dispelmagic); + + DispelMagic_Timer = 25000; + } else DispelMagic_Timer -= diff; + } + } + } + + [Script] + class boss_lady_keira_berrybuck : CreatureScript + { + public boss_lady_keira_berrybuck() : base("boss_lady_keira_berrybuck") { } + + public override CreatureAI GetAI(Creature creature) + { + return GetInstanceAI(creature); + } + + class boss_lady_keira_berrybuckAI : boss_moroes_guestAI + { + //Holy Pally + public boss_lady_keira_berrybuckAI(Creature creature) : base(creature) { } + + uint Cleanse_Timer; + uint GreaterBless_Timer; + uint HolyLight_Timer; + uint DivineShield_Timer; + + public override void Reset() + { + Cleanse_Timer = 13000; + GreaterBless_Timer = 1000; + HolyLight_Timer = 7000; + DivineShield_Timer = 31000; + + AcquireGUID(); + + base.Reset(); + } + + public override void UpdateAI(uint diff) + { + if (!UpdateVictim()) + return; + + base.UpdateAI(diff); + + if (DivineShield_Timer <= diff) + { + DoCast(me, SpellIds.Divineshield); + DivineShield_Timer = 31000; + } else DivineShield_Timer -= diff; + + if (HolyLight_Timer <= diff) + { + Unit target = SelectGuestTarget(); + + DoCast(target, SpellIds.Holylight); + HolyLight_Timer = 10000; + } else HolyLight_Timer -= diff; + + if (GreaterBless_Timer <= diff) + { + Unit target = SelectGuestTarget(); + + DoCast(target, SpellIds.Greaterblessofmight); + + GreaterBless_Timer = 50000; + } else GreaterBless_Timer -= diff; + + if (Cleanse_Timer <= diff) + { + Unit target = SelectGuestTarget(); + + DoCast(target, SpellIds.Cleanse); + + Cleanse_Timer = 10000; + } else Cleanse_Timer -= diff; + } + } + } + + [Script] + class boss_lord_robin_daris : CreatureScript + { + public boss_lord_robin_daris() : base("boss_lord_robin_daris") { } + + public override CreatureAI GetAI(Creature creature) + { + return GetInstanceAI(creature); + } + + class boss_lord_robin_darisAI : boss_moroes_guestAI + { + //Arms Warr + public boss_lord_robin_darisAI(Creature creature) : base(creature) { } + + uint Hamstring_Timer; + uint MortalStrike_Timer; + uint WhirlWind_Timer; + + public override void Reset() + { + Hamstring_Timer = 7000; + MortalStrike_Timer = 10000; + WhirlWind_Timer = 21000; + + base.Reset(); + } + + public override void UpdateAI(uint diff) + { + if (!UpdateVictim()) + return; + + base.UpdateAI(diff); + + if (Hamstring_Timer <= diff) + { + DoCastVictim(SpellIds.Hamstring); + Hamstring_Timer = 12000; + } else Hamstring_Timer -= diff; + + if (MortalStrike_Timer <= diff) + { + DoCastVictim(SpellIds.Mortalstrike); + MortalStrike_Timer = 18000; + } else MortalStrike_Timer -= diff; + + if (WhirlWind_Timer <= diff) + { + DoCast(me, SpellIds.Whirlwind); + WhirlWind_Timer = 21000; + } else WhirlWind_Timer -= diff; + } + } + } + + [Script] + class boss_lord_crispin_ference : CreatureScript + { + public boss_lord_crispin_ference() : base("boss_lord_crispin_ference") { } + + public override CreatureAI GetAI(Creature creature) + { + return GetInstanceAI(creature); + } + + class boss_lord_crispin_ferenceAI : boss_moroes_guestAI + { + //Arms Warr + public boss_lord_crispin_ferenceAI(Creature creature) : base(creature) { } + + uint Disarm_Timer; + uint HeroicStrike_Timer; + uint ShieldBash_Timer; + uint ShieldWall_Timer; + + public override void Reset() + { + Disarm_Timer = 6000; + HeroicStrike_Timer = 10000; + ShieldBash_Timer = 8000; + ShieldWall_Timer = 4000; + + base.Reset(); + } + + public override void UpdateAI(uint diff) + { + if (!UpdateVictim()) + return; + + base.UpdateAI(diff); + + if (Disarm_Timer <= diff) + { + DoCastVictim(SpellIds.Disarm); + Disarm_Timer = 12000; + } else Disarm_Timer -= diff; + + if (HeroicStrike_Timer <= diff) + { + DoCastVictim(SpellIds.Heroicstrike); + HeroicStrike_Timer = 10000; + } else HeroicStrike_Timer -= diff; + + if (ShieldBash_Timer <= diff) + { + DoCastVictim(SpellIds.Shieldbash); + ShieldBash_Timer = 13000; + } else ShieldBash_Timer -= diff; + + if (ShieldWall_Timer <= diff) + { + DoCast(me, SpellIds.Shieldwall); + ShieldWall_Timer = 21000; + } else ShieldWall_Timer -= diff; + } + } + } +} diff --git a/Scripts/EasternKingdoms/Karazhan/OperaEvent.cs b/Scripts/EasternKingdoms/Karazhan/OperaEvent.cs new file mode 100644 index 000000000..5b4d7edb8 --- /dev/null +++ b/Scripts/EasternKingdoms/Karazhan/OperaEvent.cs @@ -0,0 +1,1795 @@ +/* + * Copyright (C) 2012-2017 CypherCore + * + * 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, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +using Framework.Constants; +using Game.AI; +using Game.Entities; +using Game.Maps; +using Game.Scripting; +using Game.Spells; +using System.Collections.Generic; + +namespace Scripts.EasternKingdoms.Karazhan.OperaEvent +{ + #region Wizard of Oz + struct WizardOfOz + { + public const uint SayDorotheeDeath = 0; + public const uint SayDorotheeSummon = 1; + public const uint SayDorotheeTitoDeath = 2; + public const uint SayDorotheeAggro = 3; + + public const uint SayRoarAggro = 0; + public const uint SayRoarDeath = 1; + public const uint SayRoarSlay = 2; + + public const uint SayStrawmanAggro = 0; + public const uint SayStrawmanDeath = 1; + public const uint SayStrawmanSlay = 2; + + public const uint SayTinheadAggro = 0; + public const uint SayTinheadDeath = 1; + public const uint SayTinheadSlay = 2; + public const uint EmoteRust = 3; + + public const uint SayCroneAggro = 0; + public const uint SayCroneDeath = 1; + public const uint SayCroneSlay = 2; + + // Dorothee + public const uint SpellWaterbolt = 31012; + public const uint SpellScream = 31013; + public const uint SpellSummontito = 31014; + + // Tito + public const uint SpellYipping = 31015; + + // Strawman + public const uint SpellBrainBash = 31046; + public const uint SpellBrainWipe = 31069; + public const uint SpellBurningStraw = 31075; + + // Tinhead + public const uint SpellCleave = 31043; + public const uint SpellRust = 31086; + + // Roar + public const uint SpellMangle = 31041; + public const uint SpellShred = 31042; + public const uint SpellFrightenedScream = 31013; + + // Crone + public const uint SpellChainLightning = 32337; + + // Cyclone + public const uint SpellKnockback = 32334; + public const uint SpellCycloneVisual = 32332; + + public const uint NpcTito = 17548; + public const uint NpcCyclone = 18412; + public const uint NpcCrone = 18168; + } + + class WizardofOzBase : ScriptedAI + { + public WizardofOzBase(Creature creature) : base(creature) { } + + public void SummonCroneIfReady(InstanceScript instance, Creature creature) + { + instance.SetData(DataTypes.OperaOzDeathcount, (uint)EncounterState.Special); // Increment DeathCount + + if (instance.GetData(DataTypes.OperaOzDeathcount) == 4) + { + Creature pCrone = creature.SummonCreature(WizardOfOz.NpcCrone, -10891.96f, -1755.95f, creature.GetPositionZ(), 4.64f, TempSummonType.TimedOrDeadDespawn, Time.Hour * 2 * Time.InMilliseconds); + if (pCrone) + { + if (creature.GetVictim()) + pCrone.GetAI().AttackStart(creature.GetVictim()); + } + } + } + + public bool TitoDied; + public ObjectGuid DorotheeGUID; + public uint AggroTimer; + } + + [Script] + class boss_dorothee : CreatureScript + { + public boss_dorothee() : base("boss_dorothee") { } + + public class boss_dorotheeAI : WizardofOzBase + { + public boss_dorotheeAI(Creature creature) : base(creature) + { + instance = creature.GetInstanceScript(); + } + + public override void Reset() + { + AggroTimer = 500; + + WaterBoltTimer = 5000; + FearTimer = 15000; + SummonTitoTimer = 47500; + + SummonedTito = false; + TitoDied = false; + } + + public override void EnterCombat(Unit who) + { + Talk(WizardOfOz.SayDorotheeAggro); + } + + public override void JustReachedHome() + { + me.DespawnOrUnsummon(); + } + + public override void JustDied(Unit killer) + { + Talk(WizardOfOz.SayDorotheeDeath); + + SummonCroneIfReady(instance, me); + } + + public override void AttackStart(Unit who) + { + if (me.HasFlag(UnitFields.Flags, UnitFlags.NonAttackable)) + return; + + base.AttackStart(who); + } + + public override void MoveInLineOfSight(Unit who) + { + if (me.HasFlag(UnitFields.Flags, UnitFlags.NonAttackable)) + return; + + base.MoveInLineOfSight(who); + } + + public override void UpdateAI(uint diff) + { + if (AggroTimer != 0) + { + if (AggroTimer <= diff) + { + me.RemoveFlag(UnitFields.Flags, UnitFlags.NonAttackable); + AggroTimer = 0; + } + else AggroTimer -= diff; + } + + if (!UpdateVictim()) + return; + + if (WaterBoltTimer <= diff) + { + DoCast(SelectTarget(SelectAggroTarget.Random, 0), WizardOfOz.SpellWaterbolt); + WaterBoltTimer = (uint)(TitoDied ? 1500 : 5000); + } + else WaterBoltTimer -= diff; + + if (FearTimer <= diff) + { + DoCastVictim(WizardOfOz.SpellScream); + FearTimer = 30000; + } + else FearTimer -= diff; + + if (!SummonedTito) + { + if (SummonTitoTimer <= diff) + SummonTito(); + else SummonTitoTimer -= diff; + } + + DoMeleeAttackIfReady(); + } + + void SummonTito() + { + Creature pTito = me.SummonCreature(WizardOfOz.NpcTito, 0.0f, 0.0f, 0.0f, 0.0f, TempSummonType.TimedDespawnOOC, 30000); + if (pTito) + { + Talk(WizardOfOz.SayDorotheeSummon); + DorotheeGUID = me.GetGUID(); + pTito.GetAI().AttackStart(me.GetVictim()); + SummonedTito = true; + TitoDied = false; + } + } + + InstanceScript instance; + + uint WaterBoltTimer; + uint FearTimer; + uint SummonTitoTimer; + + bool SummonedTito; + } + + public override CreatureAI GetAI(Creature creature) + { + return GetInstanceAI(creature); + } + } + + [Script] + class npc_tito : CreatureScript + { + public npc_tito() : base("npc_tito") { } + + public class npc_titoAI : WizardofOzBase + { + public npc_titoAI(Creature creature) : base(creature) { } + + public override void Reset() + { + DorotheeGUID.Clear(); + YipTimer = 10000; + } + + public override void EnterCombat(Unit who) { } + + public override void JustDied(Unit killer) + { + if (!DorotheeGUID.IsEmpty()) + { + Creature Dorothee = ObjectAccessor.GetCreature(me, DorotheeGUID); + if (Dorothee && Dorothee.IsAlive()) + { + TitoDied = true; + Talk(WizardOfOz.SayDorotheeTitoDeath, Dorothee); + } + } + } + + public override void UpdateAI(uint diff) + { + if (!UpdateVictim()) + return; + + if (YipTimer <= diff) + { + DoCastVictim(WizardOfOz.SpellYipping); + YipTimer = 10000; + } + else YipTimer -= diff; + + DoMeleeAttackIfReady(); + } + + uint YipTimer; + } + + public override CreatureAI GetAI(Creature creature) + { + return new npc_titoAI(creature); + } + } + + [Script] + class boss_strawman : CreatureScript + { + public boss_strawman() : base("boss_strawman") { } + + class boss_strawmanAI : WizardofOzBase + { + public boss_strawmanAI(Creature creature) : base(creature) + { + instance = creature.GetInstanceScript(); + } + + public override void Reset() + { + AggroTimer = 13000; + BrainBashTimer = 5000; + BrainWipeTimer = 7000; + } + + public override void AttackStart(Unit who) + { + if (me.HasFlag(UnitFields.Flags, UnitFlags.NonAttackable)) + return; + + base.AttackStart(who); + } + + public override void MoveInLineOfSight(Unit who) + { + if (me.HasFlag(UnitFields.Flags, UnitFlags.NonAttackable)) + return; + + base.MoveInLineOfSight(who); + } + + public override void EnterCombat(Unit who) + { + Talk(WizardOfOz.SayStrawmanAggro); + } + + public override void SpellHit(Unit caster, SpellInfo Spell) + { + if ((Spell.SchoolMask == SpellSchoolMask.Fire) && ((RandomHelper.randChance() % 10) == 0)) + { + DoCast(me, WizardOfOz.SpellBurningStraw, true); + } + } + + public override void JustDied(Unit killer) + { + Talk(WizardOfOz.SayStrawmanDeath); + + SummonCroneIfReady(instance, me); + } + + public override void KilledUnit(Unit victim) + { + Talk(WizardOfOz.SayStrawmanSlay); + } + + public override void UpdateAI(uint diff) + { + if (AggroTimer != 0) + { + if (AggroTimer <= diff) + { + me.RemoveFlag(UnitFields.Flags, UnitFlags.NonAttackable); + AggroTimer = 0; + } + else AggroTimer -= diff; + } + + if (!UpdateVictim()) + return; + + if (BrainBashTimer <= diff) + { + DoCastVictim(WizardOfOz.SpellBrainBash); + BrainBashTimer = 15000; + } + else BrainBashTimer -= diff; + + if (BrainWipeTimer <= diff) + { + Unit target = SelectTarget(SelectAggroTarget.Random, 0, 100, true); + if (target) + DoCast(target, WizardOfOz.SpellBrainWipe); + BrainWipeTimer = 20000; + } + else BrainWipeTimer -= diff; + + DoMeleeAttackIfReady(); + } + + InstanceScript instance; + + uint BrainBashTimer; + uint BrainWipeTimer; + } + + public override CreatureAI GetAI(Creature creature) + { + return GetInstanceAI(creature); + } + } + + [Script] + class boss_tinhead : CreatureScript + { + public boss_tinhead() : base("boss_tinhead") { } + + public override CreatureAI GetAI(Creature creature) + { + return GetInstanceAI(creature); + } + + class boss_tinheadAI : WizardofOzBase + { + public boss_tinheadAI(Creature creature) : base(creature) + { + instance = creature.GetInstanceScript(); + } + + public override void Reset() + { + AggroTimer = 15000; + CleaveTimer = 5000; + RustTimer = 30000; + + RustCount = 0; + } + + public override void EnterCombat(Unit who) + { + Talk(WizardOfOz.SayTinheadAggro); + } + + public override void JustReachedHome() + { + me.DespawnOrUnsummon(); + } + + public override void AttackStart(Unit who) + { + if (me.HasFlag(UnitFields.Flags, UnitFlags.NonAttackable)) + return; + + base.AttackStart(who); + } + + public override void MoveInLineOfSight(Unit who) + { + if (me.HasFlag(UnitFields.Flags, UnitFlags.NonAttackable)) + return; + + base.MoveInLineOfSight(who); + } + + public override void JustDied(Unit killer) + { + Talk(WizardOfOz.SayTinheadDeath); + + SummonCroneIfReady(instance, me); + } + + public override void KilledUnit(Unit victim) + { + Talk(WizardOfOz.SayTinheadSlay); + } + + public override void UpdateAI(uint diff) + { + if (AggroTimer != 0) + { + if (AggroTimer <= diff) + { + me.RemoveFlag(UnitFields.Flags, UnitFlags.NonAttackable); + AggroTimer = 0; + } + else AggroTimer -= diff; + } + + if (!UpdateVictim()) + return; + + if (CleaveTimer <= diff) + { + DoCastVictim(WizardOfOz.SpellCleave); + CleaveTimer = 5000; + } + else CleaveTimer -= diff; + + if (RustCount < 8) + { + if (RustTimer <= diff) + { + ++RustCount; + Talk(WizardOfOz.EmoteRust); + DoCast(me, WizardOfOz.SpellRust); + RustTimer = 6000; + } + else RustTimer -= diff; + } + + DoMeleeAttackIfReady(); + } + + InstanceScript instance; + + uint CleaveTimer; + uint RustTimer; + + byte RustCount; + } + } + + [Script] + class boss_roar : CreatureScript + { + public boss_roar() : base("boss_roar") { } + + class boss_roarAI : WizardofOzBase + { + public boss_roarAI(Creature creature) : base(creature) + { + instance = creature.GetInstanceScript(); + } + + public override void Reset() + { + AggroTimer = 20000; + MangleTimer = 5000; + ShredTimer = 10000; + ScreamTimer = 15000; + } + + public override void MoveInLineOfSight(Unit who) + { + if (me.HasFlag(UnitFields.Flags, UnitFlags.NonAttackable)) + return; + + base.MoveInLineOfSight(who); + } + + public override void AttackStart(Unit who) + { + if (me.HasFlag(UnitFields.Flags, UnitFlags.NonAttackable)) + return; + + base.AttackStart(who); + } + + public override void EnterCombat(Unit who) + { + Talk(WizardOfOz.SayRoarAggro); + } + + public override void JustReachedHome() + { + me.DespawnOrUnsummon(); + } + + public override void JustDied(Unit killer) + { + Talk(WizardOfOz.SayRoarDeath); + + SummonCroneIfReady(instance, me); + } + + public override void KilledUnit(Unit victim) + { + Talk(WizardOfOz.SayRoarSlay); + } + + public override void UpdateAI(uint diff) + { + if (AggroTimer != 0) + { + if (AggroTimer <= diff) + { + me.RemoveFlag(UnitFields.Flags, UnitFlags.NonAttackable); + AggroTimer = 0; + } + else AggroTimer -= diff; + } + + if (!UpdateVictim()) + return; + + if (MangleTimer <= diff) + { + DoCastVictim(WizardOfOz.SpellMangle); + MangleTimer = RandomHelper.URand(5000, 8000); + } + else MangleTimer -= diff; + + if (ShredTimer <= diff) + { + DoCastVictim(WizardOfOz.SpellShred); + ShredTimer = RandomHelper.URand(10000, 15000); + } + else ShredTimer -= diff; + + if (ScreamTimer <= diff) + { + DoCastVictim(WizardOfOz.SpellFrightenedScream); + ScreamTimer = RandomHelper.URand(20000, 30000); + } + else ScreamTimer -= diff; + + DoMeleeAttackIfReady(); + } + + InstanceScript instance; + + uint MangleTimer; + uint ShredTimer; + uint ScreamTimer; + } + + public override CreatureAI GetAI(Creature creature) + { + return GetInstanceAI(creature); + } + } + + [Script] + class boss_crone : CreatureScript + { + public boss_crone() : base("boss_crone") { } + + class boss_croneAI : WizardofOzBase + { + public boss_croneAI(Creature creature) : base(creature) + { + instance = creature.GetInstanceScript(); + } + + public override void Reset() + { + CycloneTimer = 30000; + ChainLightningTimer = 10000; + } + + public override void JustReachedHome() + { + me.DespawnOrUnsummon(); + } + + public override void KilledUnit(Unit victim) + { + Talk(WizardOfOz.SayCroneSlay); + } + + public override void EnterCombat(Unit who) + { + Talk(WizardOfOz.SayCroneAggro); + me.RemoveFlag(UnitFields.Flags, UnitFlags.NonAttackable); + me.RemoveFlag(UnitFields.Flags, UnitFlags.ImmuneToPc); + } + + public override void JustDied(Unit killer) + { + Talk(WizardOfOz.SayCroneDeath); + + instance.SetData(karazhanConst.BossOpera, (uint)EncounterState.Done); + instance.HandleGameObject(instance.GetGuidData(DataTypes.GoStagedoorleft), true); + instance.HandleGameObject(instance.GetGuidData(DataTypes.GoStagedoorright), true); + + GameObject pSideEntrance = instance.instance.GetGameObject(instance.GetGuidData(DataTypes.GoSideEntranceDoor)); + if (pSideEntrance) + pSideEntrance.RemoveFlag(GameObjectFields.Flags, GameObjectFlags.Locked); + } + + public override void UpdateAI(uint diff) + { + if (!UpdateVictim()) + return; + + if (me.HasFlag(UnitFields.Flags, UnitFlags.NonAttackable)) + me.RemoveFlag(UnitFields.Flags, UnitFlags.NonAttackable); + + if (CycloneTimer <= diff) + { + Creature Cyclone = DoSpawnCreature(WizardOfOz.NpcCyclone, RandomHelper.FRand(0, 9), RandomHelper.FRand(0, 9), 0, 0, TempSummonType.TimedDespawn, 15000); + if (Cyclone) + Cyclone.CastSpell(Cyclone, WizardOfOz.SpellCycloneVisual, true); + CycloneTimer = 30000; + } + else CycloneTimer -= diff; + + if (ChainLightningTimer <= diff) + { + DoCastVictim(WizardOfOz.SpellChainLightning); + ChainLightningTimer = 15000; + } + else ChainLightningTimer -= diff; + + DoMeleeAttackIfReady(); + } + + InstanceScript instance; + + uint CycloneTimer; + uint ChainLightningTimer; + } + + public override CreatureAI GetAI(Creature creature) + { + return GetInstanceAI(creature); + } + } + + [Script] + class npc_cyclone : CreatureScript + { + public npc_cyclone() : base("npc_cyclone") { } + + class npc_cycloneAI : ScriptedAI + { + public npc_cycloneAI(Creature creature) : base(creature) { } + + public override void Reset() + { + MoveTimer = 1000; + } + + public override void EnterCombat(Unit who) { } + + public override void MoveInLineOfSight(Unit who) { } + + public override void UpdateAI(uint diff) + { + if (!me.HasAura(WizardOfOz.SpellKnockback)) + DoCast(me, WizardOfOz.SpellKnockback, true); + + if (MoveTimer <= diff) + { + Position pos = me.GetRandomNearPosition(10); + me.GetMotionMaster().MovePoint(0, pos); + MoveTimer = RandomHelper.URand(5000, 8000); + } + else MoveTimer -= diff; + } + + uint MoveTimer; + } + + public override CreatureAI GetAI(Creature creature) + { + return new npc_cycloneAI(creature); + } + } + #endregion + + #region Red Riding Hood + struct RedRidingHood + { + public const uint SayWolfAggro = 0; + public const uint SayWolfSlay = 1; + public const uint SayWolfHood = 2; + public const uint SoundWolfDeath = 9275; + + public const uint SpellLittleRedRidingHood = 30768; + public const uint SpellTerrifyingHowl = 30752; + public const uint SpellWideSwipe = 30761; + + public const uint NpcBigBadWolf = 17521; + } + + [Script] + class npc_grandmother : CreatureScript + { + public npc_grandmother() : base("npc_grandmother") { } + + public override bool OnGossipSelect(Player player, Creature creature, uint sender, uint action) + { + player.PlayerTalkClass.ClearMenus(); + if (action == eTradeskill.GossipActionInfoDef) + { + Creature pBigBadWolf = creature.SummonCreature(RedRidingHood.NpcBigBadWolf, 0.0f, 0.0f, 0.0f, 0.0f, TempSummonType.TimedOrDeadDespawn, Time.Hour * 2 * Time.InMilliseconds); + if (pBigBadWolf) + pBigBadWolf.GetAI().AttackStart(player); + + creature.DespawnOrUnsummon(); + } + + return true; + } + + public override bool OnGossipHello(Player player, Creature creature) + { + player.ADD_GOSSIP_ITEM(GossipOptionIcon.Chat, "What phat lewtz you have grandmother?", eTradeskill.GossipSenderMain, eTradeskill.GossipActionInfoDef); + player.SEND_GOSSIP_MENU(8990, creature.GetGUID()); + + return true; + } + } + + [Script] + class boss_bigbadwolf : CreatureScript + { + public boss_bigbadwolf() : base("boss_bigbadwolf") { } + + class boss_bigbadwolfAI : ScriptedAI + { + public boss_bigbadwolfAI(Creature creature) : base(creature) + { + instance = creature.GetInstanceScript(); + } + + public override void Reset() + { + ChaseTimer = 30000; + FearTimer = RandomHelper.URand(25000, 35000); + SwipeTimer = 5000; + + HoodGUID.Clear(); + TempThreat = 0; + + IsChasing = false; + } + + public override void EnterCombat(Unit who) + { + Talk(RedRidingHood.SayWolfAggro); + } + + public override void KilledUnit(Unit victim) + { + Talk(RedRidingHood.SayWolfSlay); + } + + public override void JustReachedHome() + { + me.DespawnOrUnsummon(); + } + + public override void JustDied(Unit killer) + { + DoPlaySoundToSet(me, RedRidingHood.SoundWolfDeath); + + instance.SetData(karazhanConst.BossOpera, (uint)EncounterState.Done); + instance.HandleGameObject(instance.GetGuidData(DataTypes.GoStagedoorleft), true); + instance.HandleGameObject(instance.GetGuidData(DataTypes.GoStagedoorright), true); + + GameObject pSideEntrance = instance.instance.GetGameObject(instance.GetGuidData(DataTypes.GoSideEntranceDoor)); + if (pSideEntrance) + pSideEntrance.RemoveFlag(GameObjectFields.Flags, GameObjectFlags.Locked); + } + + public override void UpdateAI(uint diff) + { + if (!UpdateVictim()) + return; + + DoMeleeAttackIfReady(); + + if (ChaseTimer <= diff) + { + if (!IsChasing) + { + Unit target = SelectTarget(SelectAggroTarget.Random, 0, 100, true); + if (target) + { + Talk(RedRidingHood.SayWolfHood); + DoCast(target, RedRidingHood.SpellLittleRedRidingHood, true); + TempThreat = DoGetThreat(target); + if (TempThreat != 0.0f) + DoModifyThreatPercent(target, -100); + HoodGUID = target.GetGUID(); + me.AddThreat(target, 1000000.0f); + ChaseTimer = 20000; + IsChasing = true; + } + } + else + { + IsChasing = false; + Unit target = Global.ObjAccessor.GetUnit(me, HoodGUID); + if (target) + { + HoodGUID.Clear(); + if (DoGetThreat(target) != 0f) + DoModifyThreatPercent(target, -100); + me.AddThreat(target, TempThreat); + TempThreat = 0; + } + + ChaseTimer = 40000; + } + } + else ChaseTimer -= diff; + + if (IsChasing) + return; + + if (FearTimer <= diff) + { + DoCastVictim(RedRidingHood.SpellTerrifyingHowl); + FearTimer = RandomHelper.URand(25000, 35000); + } + else FearTimer -= diff; + + if (SwipeTimer <= diff) + { + DoCastVictim(RedRidingHood.SpellWideSwipe); + SwipeTimer = RandomHelper.URand(25000, 30000); + } + else SwipeTimer -= diff; + } + + InstanceScript instance; + + uint ChaseTimer; + uint FearTimer; + uint SwipeTimer; + + ObjectGuid HoodGUID; + float TempThreat; + + bool IsChasing; + } + + public override CreatureAI GetAI(Creature creature) + { + return GetInstanceAI(creature); + } + } + #endregion + + #region Romeo & Juliet + struct JulianneRomulo + { + public const uint SayJulianneAggro = 0; + public const uint SayJulianneEnter = 1; + public const uint SayJulianneDeath01 = 2; + public const uint SayJulianneDeath02 = 3; + public const uint SayJulianneResurrect = 4; + public const uint SayJulianneSlay = 5; + + public const uint SayRomuloAggro = 0; + public const uint SayRomuloDeath = 1; + public const uint SayRomuloEnter = 2; + public const uint SayRomuloResurrect = 3; + public const uint SayRomuloSlay = 4; + + public const uint SpellBlindingPassion = 30890; + public const uint SpellDevotion = 30887; + public const uint SpellEternalAffection = 30878; + public const uint SpellPowerfulAttraction = 30889; + public const uint SpellDrinkPoison = 30907; + + public const uint SpellBackwardLunge = 30815; + public const uint SpellDaring = 30841; + public const uint SpellDeadlySwathe = 30817; + public const uint SpellPoisonThrust = 30822; + + public const uint SpellUndyingLove = 30951; + public const uint SpellResVisual = 24171; + + public const uint NpcRomulo = 17533; + public const int RomuloX = -10900; + public const int RomuloY = -1758; + } + + public enum RAJPhase + { + Julianne = 0, + Romulo = 1, + Both = 2, + } + + public class julianne_romulobase : ScriptedAI + { + public julianne_romulobase(Creature creature) : base(creature) { } + + public override void JustReachedHome() + { + me.DespawnOrUnsummon(); + } + + public override void MoveInLineOfSight(Unit who) + { + if (me.HasFlag(UnitFields.Flags, UnitFlags.NonAttackable)) + return; + + base.MoveInLineOfSight(who); + } + + public void PretendToDie() + { + me.InterruptNonMeleeSpells(true); + me.RemoveAllAuras(); + me.SetHealth(0); + me.SetFlag(UnitFields.Flags, UnitFlags.NotSelectable); + me.GetMotionMaster().MovementExpired(false); + me.GetMotionMaster().MoveIdle(); + me.SetStandState(UnitStandStateType.Dead); + } + + public void Resurrect(Creature target) + { + target.RemoveFlag(UnitFields.Flags, UnitFlags.NotSelectable); + target.SetFullHealth(); + target.SetStandState(UnitStandStateType.Stand); + target.CastSpell(target, JulianneRomulo.SpellResVisual, true); + if (target.GetVictim()) + { + target.GetMotionMaster().MoveChase(target.GetVictim()); + target.GetAI().AttackStart(target.GetVictim()); + } + else + target.GetMotionMaster().Initialize(); + } + + public InstanceScript instance; + + public ObjectGuid JulianneGUID; + public ObjectGuid RomuloGUID; + + public uint EntryYellTimer; + public uint AggroYellTimer; + + public RAJPhase Phase; + + public uint ResurrectSelfTimer; + public uint ResurrectTimer; + public bool JulianneDead; + public bool RomuloDead; + + public bool IsFakingDeath; + } + + [Script] + class boss_julianne : CreatureScript + { + public boss_julianne() : base("boss_julianne") { } + + public class boss_julianneAI : julianne_romulobase + { + public boss_julianneAI(Creature creature) : base(creature) + { + instance = creature.GetInstanceScript(); + EntryYellTimer = 1000; + AggroYellTimer = 10000; + IsFakingDeath = false; + } + + public override void Reset() + { + RomuloGUID.Clear(); + Phase = RAJPhase.Julianne; + + BlindingPassionTimer = 30000; + DevotionTimer = 15000; + EternalAffectionTimer = 25000; + PowerfulAttractionTimer = 5000; + SummonRomuloTimer = 10000; + DrinkPoisonTimer = 0; + ResurrectSelfTimer = 0; + + if (IsFakingDeath) + { + Resurrect(me); + IsFakingDeath = false; + } + + SummonedRomulo = false; + RomuloDead = false; + } + + public override void EnterCombat(Unit who) { } + + public override void AttackStart(Unit who) + { + if (me.HasFlag(UnitFields.Flags, UnitFlags.NonAttackable)) + return; + + base.AttackStart(who); + } + + public override void SpellHit(Unit caster, SpellInfo Spell) + { + if (Spell.Id == JulianneRomulo.SpellDrinkPoison) + { + Talk(JulianneRomulo.SayJulianneDeath01); + DrinkPoisonTimer = 2500; + } + } + + public override void DamageTaken(Unit done_by, ref uint damage) + { + if (damage < me.GetHealth()) + return; + + //anything below only used if incoming damage will kill + + if (Phase == RAJPhase.Julianne) + { + damage = 0; + + //this means already drinking, so return + if (IsFakingDeath) + return; + + me.InterruptNonMeleeSpells(true); + DoCast(me, JulianneRomulo.SpellDrinkPoison); + + IsFakingDeath = true; + //IS THIS USEFULL? Creature Julianne = (Global.ObjAccessor.GetCreature(me, JulianneGUID)); + return; + } + + if (Phase == RAJPhase.Romulo) + { + Log.outError(LogFilter.Scripts, "boss_julianneAI: cannot take damage in PHASE_ROMULO, why was i here?"); + damage = 0; + return; + } + + if (Phase == RAJPhase.Both) + { + Creature Romulo; + //if this is true then we have to kill romulo too + if (RomuloDead) + { + Romulo = ObjectAccessor.GetCreature(me, RomuloGUID); + if (Romulo) + { + Romulo.RemoveFlag(UnitFields.Flags, UnitFlags.NotSelectable); + Romulo.GetMotionMaster().Clear(); + Romulo.setDeathState(DeathState.JustDied); + Romulo.CombatStop(true); + Romulo.DeleteThreatList(); + Romulo.SetUInt32Value(ObjectFields.DynamicFlags, (uint)UnitDynFlags.Lootable); + } + + return; + } + + //if not already returned, then romulo is alive and we can pretend die + Romulo = ObjectAccessor.GetCreature(me, RomuloGUID); + if (Romulo) + { + PretendToDie(); + IsFakingDeath = true; + ((julianne_romulobase)Romulo.GetAI()).ResurrectTimer = 10000; + ((julianne_romulobase)Romulo.GetAI()).JulianneDead = true; + damage = 0; + return; + } + } + Log.outError(LogFilter.Scripts, "boss_julianneAI: DamageTaken reach end of code, that should not happen."); + } + + public override void JustDied(Unit killer) + { + Talk(JulianneRomulo.SayJulianneDeath02); + + instance.SetData(karazhanConst.BossOpera, (uint)EncounterState.Done); + instance.HandleGameObject(instance.GetGuidData(DataTypes.GoStagedoorleft), true); + instance.HandleGameObject(instance.GetGuidData(DataTypes.GoStagedoorright), true); + + GameObject pSideEntrance = instance.instance.GetGameObject(instance.GetGuidData(DataTypes.GoSideEntranceDoor)); + if (pSideEntrance) + pSideEntrance.RemoveFlag(GameObjectFields.Flags, GameObjectFlags.Locked); + } + + public override void KilledUnit(Unit victim) + { + Talk(JulianneRomulo.SayJulianneSlay); + } + + public override void UpdateAI(uint diff) + { + if (EntryYellTimer != 0) + { + if (EntryYellTimer <= diff) + { + Talk(JulianneRomulo.SayJulianneEnter); + EntryYellTimer = 0; + } + else EntryYellTimer -= diff; + } + + if (AggroYellTimer != 0) + { + if (AggroYellTimer <= diff) + { + Talk(JulianneRomulo.SayJulianneAggro); + me.RemoveFlag(UnitFields.Flags, UnitFlags.NonAttackable); + me.SetFaction(16); + AggroYellTimer = 0; + } + else AggroYellTimer -= diff; + } + + if (DrinkPoisonTimer != 0) + { + //will do this 2secs after spell hit. this is time to display visual as expected + if (DrinkPoisonTimer <= diff) + { + PretendToDie(); + Phase = RAJPhase.Romulo; + SummonRomuloTimer = 10000; + DrinkPoisonTimer = 0; + } + else DrinkPoisonTimer -= diff; + } + + if (Phase == RAJPhase.Romulo && !SummonedRomulo) + { + if (SummonRomuloTimer <= diff) + { + Creature pRomulo = me.SummonCreature(JulianneRomulo.NpcRomulo, JulianneRomulo.RomuloX, JulianneRomulo.RomuloY, me.GetPositionZ(), 0, TempSummonType.TimedOrDeadDespawn, Time.Hour * 2 * Time.InMilliseconds); + if (pRomulo) + { + RomuloGUID = pRomulo.GetGUID(); + ((julianne_romulobase)pRomulo.GetAI()).JulianneGUID = me.GetGUID(); + ((julianne_romulobase)pRomulo.GetAI()).Phase = RAJPhase.Romulo; + DoZoneInCombat(pRomulo); + + pRomulo.SetFaction(16); + } + SummonedRomulo = true; + } + else SummonRomuloTimer -= diff; + } + + if (ResurrectSelfTimer != 0) + { + if (ResurrectSelfTimer <= diff) + { + Resurrect(me); + Phase = RAJPhase.Both; + IsFakingDeath = false; + + if (me.GetVictim()) + AttackStart(me.GetVictim()); + + ResurrectSelfTimer = 0; + ResurrectTimer = 1000; + } + else ResurrectSelfTimer -= diff; + } + + if (!UpdateVictim() || IsFakingDeath) + return; + + if (RomuloDead) + { + if (ResurrectTimer <= diff) + { + Creature Romulo = ObjectAccessor.GetCreature(me, RomuloGUID); + if (Romulo && ((julianne_romulobase)Romulo.GetAI()).IsFakingDeath) + { + Talk(JulianneRomulo.SayJulianneResurrect); + Resurrect(Romulo); + ((julianne_romulobase)Romulo.GetAI()).IsFakingDeath = false; + RomuloDead = false; + ResurrectTimer = 10000; + } + } + else ResurrectTimer -= diff; + } + + if (BlindingPassionTimer <= diff) + { + Unit target = SelectTarget(SelectAggroTarget.Random, 0, 100, true); + if (target) + DoCast(target, JulianneRomulo.SpellBlindingPassion); + BlindingPassionTimer = RandomHelper.URand(30000, 45000); + } + else BlindingPassionTimer -= diff; + + if (DevotionTimer <= diff) + { + DoCast(me, JulianneRomulo.SpellDevotion); + DevotionTimer = RandomHelper.URand(15000, 45000); + } + else DevotionTimer -= diff; + + if (PowerfulAttractionTimer <= diff) + { + DoCast(SelectTarget(SelectAggroTarget.Random, 0), JulianneRomulo.SpellPowerfulAttraction); + PowerfulAttractionTimer = RandomHelper.URand(5000, 30000); + } + else PowerfulAttractionTimer -= diff; + + if (EternalAffectionTimer <= diff) + { + if (RandomHelper.URand(0, 1) != 0 && SummonedRomulo) + { + Creature Romulo = ObjectAccessor.GetCreature(me, RomuloGUID); + if (Romulo && Romulo.IsAlive() && !RomuloDead) + DoCast(Romulo, JulianneRomulo.SpellEternalAffection); + } + else DoCast(me, JulianneRomulo.SpellEternalAffection); + + EternalAffectionTimer = RandomHelper.URand(45000, 60000); + } + else EternalAffectionTimer -= diff; + + DoMeleeAttackIfReady(); + } + + uint BlindingPassionTimer; + uint DevotionTimer; + uint EternalAffectionTimer; + uint PowerfulAttractionTimer; + uint SummonRomuloTimer; + uint DrinkPoisonTimer; + + bool SummonedRomulo; + } + + public override CreatureAI GetAI(Creature creature) + { + return GetInstanceAI(creature); + } + } + + [Script] + class boss_romulo : CreatureScript + { + public boss_romulo() : base("boss_romulo") { } + + public class boss_romuloAI : julianne_romulobase + { + public boss_romuloAI(Creature creature) : base(creature) + { + instance = creature.GetInstanceScript(); + EntryYellTimer = 8000; + AggroYellTimer = 15000; + } + + public override void Reset() + { + JulianneGUID.Clear(); + Phase = RAJPhase.Romulo; + + BackwardLungeTimer = 15000; + DaringTimer = 20000; + DeadlySwatheTimer = 25000; + PoisonThrustTimer = 10000; + ResurrectTimer = 10000; + + IsFakingDeath = false; + JulianneDead = false; + } + + public override void DamageTaken(Unit attacker, ref uint damage) + { + if (damage < me.GetHealth()) + return; + + //anything below only used if incoming damage will kill + + if (Phase == RAJPhase.Romulo) + { + Talk(JulianneRomulo.SayRomuloDeath); + PretendToDie(); + IsFakingDeath = true; + Phase = RAJPhase.Both; + + Creature Julianne = ObjectAccessor.GetCreature(me, JulianneGUID); + if (Julianne) + { + ((julianne_romulobase)Julianne.GetAI()).RomuloDead = true; + ((julianne_romulobase)Julianne.GetAI()).ResurrectSelfTimer = 10000; + } + + damage = 0; + return; + } + + if (Phase == RAJPhase.Both) + { + Creature Julianne; + if (JulianneDead) + { + Julianne = ObjectAccessor.GetCreature(me, JulianneGUID); + if (Julianne) + { + Julianne.RemoveFlag(UnitFields.Flags, UnitFlags.NotSelectable); + Julianne.GetMotionMaster().Clear(); + Julianne.setDeathState(DeathState.JustDied); + Julianne.CombatStop(true); + Julianne.DeleteThreatList(); + Julianne.SetUInt32Value(ObjectFields.DynamicFlags, (uint)UnitDynFlags.Lootable); + } + return; + } + + Julianne = ObjectAccessor.GetCreature(me, JulianneGUID); + if (Julianne) + { + PretendToDie(); + IsFakingDeath = true; + ((julianne_romulobase)Julianne.GetAI()).ResurrectTimer = 10000; + ((julianne_romulobase)Julianne.GetAI()).RomuloDead = true; + damage = 0; + return; + } + } + + Log.outError(LogFilter.Scripts, "boss_romuloAI: DamageTaken reach end of code, that should not happen."); + } + + public override void EnterCombat(Unit who) + { + Talk(JulianneRomulo.SayRomuloAggro); + if (!JulianneGUID.IsEmpty()) + { + Creature Julianne = (ObjectAccessor.GetCreature(me, JulianneGUID)); + if (Julianne && Julianne.GetVictim()) + { + me.AddThreat(Julianne.GetVictim(), 1.0f); + AttackStart(Julianne.GetVictim()); + } + } + } + + public override void JustDied(Unit killer) + { + Talk(JulianneRomulo.SayRomuloDeath); + + instance.SetData(karazhanConst.BossOpera, (uint)EncounterState.Done); + instance.HandleGameObject(instance.GetGuidData(DataTypes.GoStagedoorleft), true); + instance.HandleGameObject(instance.GetGuidData(DataTypes.GoStagedoorright), true); + + GameObject pSideEntrance = instance.instance.GetGameObject(instance.GetGuidData(DataTypes.GoSideEntranceDoor)); + if (pSideEntrance) + pSideEntrance.RemoveFlag(GameObjectFields.Flags, GameObjectFlags.Locked); + } + + public override void KilledUnit(Unit victim) + { + Talk(JulianneRomulo.SayRomuloSlay); + } + + public override void UpdateAI(uint diff) + { + if (!UpdateVictim() || IsFakingDeath) + return; + + if (JulianneDead) + { + if (ResurrectTimer <= diff) + { + Creature Julianne = (ObjectAccessor.GetCreature(me, JulianneGUID)); + if (Julianne && ((julianne_romulobase)Julianne.GetAI()).IsFakingDeath) + { + Talk(JulianneRomulo.SayRomuloResurrect); + Resurrect(Julianne); + ((julianne_romulobase)Julianne.GetAI()).IsFakingDeath = false; + JulianneDead = false; + ResurrectTimer = 10000; + } + } + else ResurrectTimer -= diff; + } + + if (BackwardLungeTimer <= diff) + { + Unit target = SelectTarget(SelectAggroTarget.Random, 1, 100, true); + if (target && !me.HasInArc(MathFunctions.PI, target)) + { + DoCast(target, JulianneRomulo.SpellBackwardLunge); + BackwardLungeTimer = RandomHelper.URand(15000, 30000); + } + } + else BackwardLungeTimer -= diff; + + if (DaringTimer <= diff) + { + DoCast(me, JulianneRomulo.SpellDaring); + DaringTimer = RandomHelper.URand(20000, 40000); + } + else DaringTimer -= diff; + + if (DeadlySwatheTimer <= diff) + { + Unit target = SelectTarget(SelectAggroTarget.Random, 0, 100, true); + if (target) + DoCast(target, JulianneRomulo.SpellDeadlySwathe); + DeadlySwatheTimer = RandomHelper.URand(15000, 25000); + } + else DeadlySwatheTimer -= diff; + + if (PoisonThrustTimer <= diff) + { + DoCastVictim(JulianneRomulo.SpellPoisonThrust); + PoisonThrustTimer = RandomHelper.URand(10000, 20000); + } + else PoisonThrustTimer -= diff; + + DoMeleeAttackIfReady(); + } + + uint BackwardLungeTimer; + uint DaringTimer; + uint DeadlySwatheTimer; + uint PoisonThrustTimer; + } + + public override CreatureAI GetAI(Creature creature) + { + return GetInstanceAI(creature); + } + } + #endregion + + [Script] + class npc_barnes : CreatureScript + { + public npc_barnes() : base("npc_barnes") { } + + class npc_barnesAI : npc_escortAI + { + public npc_barnesAI(Creature creature) : base(creature) + { + Initialize(); + instance = creature.GetInstanceScript(); + } + + void Initialize() + { + m_uiSpotlightGUID.Clear(); + + TalkCount = 0; + TalkTimer = 2000; + WipeTimer = 5000; + + PerformanceReady = false; + } + + public override void Reset() + { + Initialize(); + + m_uiEventId = instance.GetData(DataTypes.OperaPerformance); + } + + public void StartEvent() + { + instance.SetData(karazhanConst.BossOpera, (uint)EncounterState.InProgress); + + //resets count for this event, in case earlier failed + if (m_uiEventId == OperaEvents.Oz) + instance.SetData(DataTypes.OperaOzDeathcount, (uint)EncounterState.InProgress); + + Start(false, false); + } + + public override void EnterCombat(Unit who) { } + + public override void WaypointReached(uint waypointId) + { + switch (waypointId) + { + case 0: + DoCast(me, karazhanConst.SpellTuxedo, false); + instance.DoUseDoorOrButton(instance.GetGuidData(DataTypes.GoStagedoorleft)); + break; + case 4: + TalkCount = 0; + SetEscortPaused(true); + + Creature spotlight = me.SummonCreature(karazhanConst.NpcSpotlight, me.GetPositionX(), me.GetPositionY(), me.GetPositionZ(), 0.0f, TempSummonType.TimedOrDeadDespawn, 60000); + if (spotlight) + { + spotlight.SetFlag(UnitFields.Flags, UnitFlags.NotSelectable); + spotlight.CastSpell(spotlight, karazhanConst.SpellSpotlight, false); + m_uiSpotlightGUID = spotlight.GetGUID(); + } + break; + case 8: + instance.DoUseDoorOrButton(instance.GetGuidData(DataTypes.GoStagedoorleft)); + PerformanceReady = true; + break; + case 9: + PrepareEncounter(); + instance.DoUseDoorOrButton(instance.GetGuidData(DataTypes.GoCurtains)); + break; + } + } + + void Talk(uint count) + { + int text = 0; + + switch (m_uiEventId) + { + case OperaEvents.Oz: + if (karazhanConst.OzDialogue[count].TextId != 0) + text = karazhanConst.OzDialogue[count].TextId; + if (karazhanConst.OzDialogue[count].Timer != 0) + TalkTimer = karazhanConst.OzDialogue[count].Timer; + break; + + case OperaEvents.Hood: + if (karazhanConst.HoodDialogue[count].TextId != 0) + text = karazhanConst.HoodDialogue[count].TextId; + if (karazhanConst.HoodDialogue[count].Timer != 0) + TalkTimer = karazhanConst.HoodDialogue[count].Timer; + break; + + case OperaEvents.RAJ: + if (karazhanConst.RAJDialogue[count].TextId != 0) + text = karazhanConst.RAJDialogue[count].TextId; + if (karazhanConst.RAJDialogue[count].Timer != 0) + TalkTimer = karazhanConst.RAJDialogue[count].Timer; + break; + } + + if (text != 0) + base.Talk((uint)text); + } + + void PrepareEncounter() + { + int index = 0; + int count = 0; + + switch (m_uiEventId) + { + case OperaEvents.Oz: + index = 0; + count = 4; + break; + case OperaEvents.Hood: + index = 4; + count = index + 1; + break; + case OperaEvents.RAJ: + index = 5; + count = index + 1; + break; + } + + for (; index < count; ++index) + { + uint entry = (uint)karazhanConst.Spawns[index][0]; + float PosX = karazhanConst.Spawns[index][1]; + + Creature creature = me.SummonCreature(entry, PosX, karazhanConst.SPAWN_Y, karazhanConst.SPAWN_Z, karazhanConst.SPAWN_O, TempSummonType.TimedOrDeadDespawn, Time.Hour * 2 * Time.InMilliseconds); + if (creature) + { + // In case database has bad flags + creature.SetUInt32Value(UnitFields.Flags, 0); + creature.SetFlag(UnitFields.Flags, UnitFlags.NonAttackable); + } + } + + RaidWiped = false; + } + + public override void UpdateAI(uint diff) + { + base.UpdateAI(diff); + + if (HasEscortState(eEscortState.Paused)) + { + if (TalkTimer <= diff) + { + if (TalkCount > 3) + { + Creature pSpotlight = ObjectAccessor.GetCreature(me, m_uiSpotlightGUID); + if (pSpotlight) + pSpotlight.DespawnOrUnsummon(); + + SetEscortPaused(false); + return; + } + + Talk(TalkCount); + ++TalkCount; + } + else + TalkTimer -= diff; + } + + if (PerformanceReady) + { + if (!RaidWiped) + { + if (WipeTimer <= diff) + { + var PlayerList = me.GetMap().GetPlayers(); + if (PlayerList.Empty()) + return; + + RaidWiped = true; + foreach (var player in PlayerList) + { + if (player.IsAlive() && !player.IsGameMaster()) + { + RaidWiped = false; + break; + } + } + + if (RaidWiped) + { + RaidWiped = true; + EnterEvadeMode(); + return; + } + + WipeTimer = 15000; + } + else + WipeTimer -= diff; + } + } + } + + InstanceScript instance; + + ObjectGuid m_uiSpotlightGUID; + + uint TalkCount; + uint TalkTimer; + uint WipeTimer; + public uint m_uiEventId; + + bool PerformanceReady; + public bool RaidWiped; + } + + public override bool OnGossipSelect(Player player, Creature creature, uint sender, uint action) + { + player.PlayerTalkClass.ClearMenus(); + npc_barnesAI pBarnesAI = (npc_barnesAI)creature.GetAI(); + + switch (action) + { + case eTradeskill.GossipActionInfoDef + 1: + player.ADD_GOSSIP_ITEM(GossipOptionIcon.Chat, karazhanConst.OZ_GOSSIP2, eTradeskill.GossipSenderMain, eTradeskill.GossipActionInfoDef + 2); + player.SEND_GOSSIP_MENU(8971, creature.GetGUID()); + break; + case eTradeskill.GossipActionInfoDef + 2: + player.CLOSE_GOSSIP_MENU(); + pBarnesAI.StartEvent(); + break; + case eTradeskill.GossipActionInfoDef + 3: + player.CLOSE_GOSSIP_MENU(); + pBarnesAI.m_uiEventId = OperaEvents.Oz; + Log.outInfo(LogFilter.Scripts, "player (GUID {0}) manually set Opera event to EVENT_OZ", player.GetGUID()); + break; + case eTradeskill.GossipActionInfoDef + 4: + player.CLOSE_GOSSIP_MENU(); + pBarnesAI.m_uiEventId = OperaEvents.Hood; + Log.outInfo(LogFilter.Scripts, "player (GUID {0}) manually set Opera event to EVENT_HOOD", player.GetGUID()); + break; + case eTradeskill.GossipActionInfoDef + 5: + player.CLOSE_GOSSIP_MENU(); + pBarnesAI.m_uiEventId = OperaEvents.RAJ; + Log.outInfo(LogFilter.Scripts, "player (GUID {0}) manually set Opera event to EVENT_RAJ", player.GetGUID()); + break; + } + + return true; + } + + public override bool OnGossipHello(Player player, Creature creature) + { + InstanceScript instance = creature.GetInstanceScript(); + if (instance != null) + { + // Check for death of Moroes and if opera event is not done already + if (instance.GetData(karazhanConst.BossMoroes) == (uint)EncounterState.Done && instance.GetData(karazhanConst.BossOpera) != (uint)EncounterState.Done) + { + player.ADD_GOSSIP_ITEM(GossipOptionIcon.Chat, karazhanConst.OZ_GOSSIP1, eTradeskill.GossipSenderMain, eTradeskill.GossipActionInfoDef + 1); + + if (player.IsGameMaster()) + { + player.ADD_GOSSIP_ITEM(GossipOptionIcon.Dot, karazhanConst.OZ_GM_GOSSIP1, eTradeskill.GossipSenderMain, eTradeskill.GossipActionInfoDef + 3); + player.ADD_GOSSIP_ITEM(GossipOptionIcon.Dot, karazhanConst.OZ_GM_GOSSIP2, eTradeskill.GossipSenderMain, eTradeskill.GossipActionInfoDef + 4); + player.ADD_GOSSIP_ITEM(GossipOptionIcon.Dot, karazhanConst.OZ_GM_GOSSIP3, eTradeskill.GossipSenderMain, eTradeskill.GossipActionInfoDef + 5); + } + + npc_barnesAI pBarnesAI = (npc_barnesAI)creature.GetAI(); + if (pBarnesAI != null) + { + if (!pBarnesAI.RaidWiped) + player.SEND_GOSSIP_MENU(8970, creature.GetGUID()); + else + player.SEND_GOSSIP_MENU(8975, creature.GetGUID()); + + return true; + } + } + } + + player.SEND_GOSSIP_MENU(8978, creature.GetGUID()); + return true; + } + + public override CreatureAI GetAI(Creature creature) + { + return GetInstanceAI(creature); + } + } +} diff --git a/Scripts/EasternKingdoms/ScarletEnclave/Chapter1.cs b/Scripts/EasternKingdoms/ScarletEnclave/Chapter1.cs new file mode 100644 index 000000000..b29ad70b2 --- /dev/null +++ b/Scripts/EasternKingdoms/ScarletEnclave/Chapter1.cs @@ -0,0 +1,384 @@ +/* + * Copyright (C) 2012-2017 CypherCore + * + * 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, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +using Framework.Constants; +using Game; +using Game.AI; +using Game.Entities; +using Game.Scripting; + +namespace Scripts.EasternKingdoms +{ + enum UnworthyInitiatePhase + { + Chained, + ToEquip, + Equiping, + ToAttack, + Attacking + } + + [Script] + class npc_unworthy_initiate : CreatureScript + { + public npc_unworthy_initiate() : base("npc_unworthy_initiate") { } + + public class npc_unworthy_initiateAI : ScriptedAI + { + public npc_unworthy_initiateAI(Creature creature) : base(creature) + { + me.SetReactState(ReactStates.Passive); + if (me.GetCurrentEquipmentId() == 0) + me.SetCurrentEquipmentId((byte)me.GetOriginalEquipmentId()); + } + + public override void Reset() + { + anchorGUID.Clear(); + phase = UnworthyInitiatePhase.Chained; + _events.Reset(); + me.SetFaction(7); + me.SetFlag(UnitFields.Flags, UnitFlags.ImmuneToPc); + me.SetStandState(UnitStandStateType.Kneel); + me.LoadEquipment(0, true); + } + + public override void EnterCombat(Unit who) + { + _events.ScheduleEvent(EventIcyTouch, 1000, 1); + _events.ScheduleEvent(EventPlagueStrike, 3000, 1); + _events.ScheduleEvent(EventBloodStrike, 2000, 1); + _events.ScheduleEvent(EventDeathCoil, 5000, 1); + } + + public override void MovementInform(MovementGeneratorType type, uint id) + { + if (type != MovementGeneratorType.Point) + return; + + if (id == 1) + { + wait_timer = 5000; + me.CastSpell(me, SpellDKInitateVisual, true); + + Player starter = Global.ObjAccessor.GetPlayer(me, playerGUID); + if (starter) + Global.CreatureTextMgr.SendChat(me, (byte)SayEventAttack, null, ChatMsg.Addon, Language.Addon, CreatureTextRange.Normal, 0, Team.Other, false, starter); + + phase = UnworthyInitiatePhase.ToAttack; + } + } + + public void EventStart(Creature anchor, Player target) + { + wait_timer = 5000; + phase = UnworthyInitiatePhase.ToEquip; + + me.SetStandState(UnitStandStateType.Stand); + me.RemoveAurasDueToSpell(SpellSoulPrisonChainSelf); + me.RemoveAurasDueToSpell(SpellSoulPrisonChain); + + float z; + anchor.GetContactPoint(me, out anchorX, out anchorY, out z, 1.0f); + + playerGUID = target.GetGUID(); + Talk(SayEventStart); + } + + public override void UpdateAI(uint diff) + { + switch (phase) + { + case UnworthyInitiatePhase.Chained: + if (anchorGUID.IsEmpty()) + { + Creature anchor = me.FindNearestCreature(29521, 30); + if (anchor) + { + anchor.GetAI().SetGUID(me.GetGUID()); + anchor.CastSpell(me, SpellSoulPrisonChain, true); + anchorGUID = anchor.GetGUID(); + } + else + Log.outError(LogFilter.Scripts, "npc_unworthy_initiateAI: unable to find anchor!"); + + float dist = 99.0f; + GameObject prison = null; + + for (byte i = 0; i < 12; ++i) + { + GameObject temp_prison = me.FindNearestGameObject(acherus_soul_prison[i], 30); + if (temp_prison) + { + if (me.IsWithinDist(temp_prison, dist, false)) + { + dist = me.GetDistance2d(temp_prison); + prison = temp_prison; + } + } + } + + if (prison) + prison.ResetDoorOrButton(); + else + Log.outError(LogFilter.Scripts, "npc_unworthy_initiateAI: unable to find prison!"); + } + break; + case UnworthyInitiatePhase.ToEquip: + if (wait_timer != 0) + { + if (wait_timer > diff) + wait_timer -= diff; + else + { + me.GetMotionMaster().MovePoint(1, anchorX, anchorY, me.GetPositionZ()); + phase = UnworthyInitiatePhase.Equiping; + wait_timer = 0; + } + } + break; + case UnworthyInitiatePhase.ToAttack: + if (wait_timer != 0) + { + if (wait_timer > diff) + wait_timer -= diff; + else + { + me.SetFaction(14); + me.RemoveFlag(UnitFields.Flags, UnitFlags.ImmuneToPc); + phase = UnworthyInitiatePhase.Attacking; + + Player target = Global.ObjAccessor.GetPlayer(me, playerGUID); + if (target) + AttackStart(target); + wait_timer = 0; + } + } + break; + case UnworthyInitiatePhase.Attacking: + if (!UpdateVictim()) + return; + + _events.Update(diff); + _events.ExecuteEvents(eventId => + { + switch (eventId) + { + case EventIcyTouch: + DoCastVictim(SpellIcyTouch); + _events.DelayEvents(1000, 1); + _events.ScheduleEvent(EventIcyTouch, 5000, 1); + break; + case EventPlagueStrike: + DoCastVictim(SpellPlagueStrike); + _events.DelayEvents(1000, 1); + _events.ScheduleEvent(EventPlagueStrike, 5000, 1); + break; + case EventBloodStrike: + DoCastVictim(SpellBloodStrike); + _events.DelayEvents(1000, 1); + _events.ScheduleEvent(EventBloodStrike, 5000, 1); + break; + case EventDeathCoil: + DoCastVictim(SpellDeathCoil); + _events.DelayEvents(1000, 1); + _events.ScheduleEvent(EventDeathCoil, 5000, 1); + break; + } + }); + + DoMeleeAttackIfReady(); + break; + default: + break; + } + } + + ObjectGuid playerGUID; + UnworthyInitiatePhase phase; + uint wait_timer; + float anchorX, anchorY; + ObjectGuid anchorGUID; + } + + public const uint SpellSoulPrisonChainSelf = 54612; + public const uint SpellSoulPrisonChain = 54613; + public const uint SpellDKInitateVisual = 51519; + + public const uint SpellIcyTouch = 52372; + public const uint SpellPlagueStrike = 52373; + public const uint SpellBloodStrike = 52374; + public const uint SpellDeathCoil = 52375; + + public const uint SayEventStart = 0; + public const uint SayEventAttack = 1; + + public const uint EventIcyTouch = 1; + public const uint EventPlagueStrike = 2; + public const uint EventBloodStrike = 3; + public const uint EventDeathCoil = 4; + + public static uint[] acherus_soul_prison = { 191577, 191580, 191581, 191582, 191583, 191584, 191585, 191586, 191587, 191588, 191589, 191590 }; + + public override CreatureAI GetAI(Creature creature) + { + return new npc_unworthy_initiateAI(creature); + } + } + + [Script] + class npc_unworthy_initiate_anchor : CreatureScript + { + public npc_unworthy_initiate_anchor() : base("npc_unworthy_initiate_anchor") { } + + class npc_unworthy_initiate_anchorAI : PassiveAI + { + public npc_unworthy_initiate_anchorAI(Creature creature) : base(creature) { } + + public override void SetGUID(ObjectGuid guid, int id) + { + if (prisonerGUID.IsEmpty()) + prisonerGUID = guid; + } + + public override ObjectGuid GetGUID(int id) + { + return prisonerGUID; + } + + ObjectGuid prisonerGUID; + } + + public override CreatureAI GetAI(Creature creature) + { + return new npc_unworthy_initiate_anchorAI(creature); + } + } + + [Script] + class go_acherus_soul_prison : GameObjectScript + { + public go_acherus_soul_prison() : base("go_acherus_soul_prison") { } + + public override bool OnGossipHello(Player player, GameObject go) + { + Creature anchor = go.FindNearestCreature(29521, 15); + if (anchor) + { + ObjectGuid prisonerGUID = anchor.GetAI().GetGUID(); + if (!prisonerGUID.IsEmpty()) + { + Creature prisoner = ObjectAccessor.GetCreature(player, prisonerGUID); + if (prisoner) + ((npc_unworthy_initiate.npc_unworthy_initiateAI)prisoner.GetAI()).EventStart(anchor, player); + } + } + + return false; + } + + } + + struct EyeOfAcherus + { + public const uint EyeHugeDisplayId = 26320; + public const uint EyeSmallDisplayId = 25499; + + public const uint SpellEyePhasemask = 70889; + public const uint SpellEyeVisual = 51892; + public const uint SpellEyeFlight = 51923; + public const uint SpellEyeFlightBoost = 51890; + public const uint SpellEyeControl = 51852; + + public const string SayEyeLaunched = "Eye of Acherus is launched towards its destination."; + public const string SayEyeUnderControl = "You are now in control of the eye."; + + public static float[] EyeDestination = { 1750.8276f, -5873.788f, 147.2266f }; + } + + [Script] + class npc_eye_of_acherus : CreatureScript + { + public npc_eye_of_acherus() : base("npc_eye_of_acherus") { } + + class npc_eye_of_acherusAI : ScriptedAI + { + public npc_eye_of_acherusAI(Creature creature) : base(creature) + { + Reset(); + } + + uint startTimer; + + public override void Reset() + { + startTimer = 2000; + } + + public override void AttackStart(Unit u) { } + + public override void MoveInLineOfSight(Unit u) { } + + public override void JustDied(Unit killer) + { + Unit charmer = me.GetCharmer(); + if (charmer) + charmer.RemoveAurasDueToSpell(EyeOfAcherus.SpellEyeControl); + } + + public override void UpdateAI(uint diff) + { + if (me.IsCharmed()) + { + if (startTimer <= diff) // fly to start point + { + me.CastSpell(me, EyeOfAcherus.SpellEyePhasemask, true); + me.CastSpell(me, EyeOfAcherus.SpellEyeVisual, true); + me.CastSpell(me, EyeOfAcherus.SpellEyeFlightBoost, true); + me.SetSpeedRate(UnitMoveType.Flight, 4f); + + me.GetMotionMaster().MovePoint(0, EyeOfAcherus.EyeDestination[0], EyeOfAcherus.EyeDestination[1], EyeOfAcherus.EyeDestination[2]); + return; + } + else + startTimer -= diff; + } + else + me.ForcedDespawn(); + } + + public override void MovementInform(MovementGeneratorType type, uint id) + { + if (type != MovementGeneratorType.Point || id != 0) + return; + + me.SetDisplayId(EyeOfAcherus.EyeSmallDisplayId); + + me.CastSpell(me, EyeOfAcherus.SpellEyeFlight, true); + me.Say(EyeOfAcherus.SayEyeUnderControl, Language.Universal); + + if (me.GetCharmer() && me.GetCharmer().IsTypeId(TypeId.Player)) + me.GetCharmer().ToPlayer().SetClientControl(me, true); + } + } + + public override CreatureAI GetAI(Creature creature) + { + return new npc_eye_of_acherusAI(creature); + } + } +} diff --git a/Scripts/EasternKingdoms/TheStockade/BossHogger.cs b/Scripts/EasternKingdoms/TheStockade/BossHogger.cs new file mode 100644 index 000000000..7438cb267 --- /dev/null +++ b/Scripts/EasternKingdoms/TheStockade/BossHogger.cs @@ -0,0 +1,157 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using Game.Entities; +using Game.AI; +using Game.Scripting; +using Framework.Constants; + +namespace Scripts.EasternKingdoms.TheStockade +{ + struct TextIds + { + public const uint SayPull = 0; // Forest Just Setback! + public const uint SayEnrage = 1; // Areatriggermessage: Hogger Enrages! + public const uint SayDeath = 2; // Yiipe! + + public const uint SayWarden1 = 0; // Yell - This Ends Here; Hogger! + public const uint SayWarden2 = 1; // Say - He'S...He'S Dead? + public const uint SayWarden3 = 2; // Say - It'S Simply Too Good To Be True. You Couldn'T Have Killed Him So Easily! + } + + struct SpellIds + { + public const uint ViciousSlice = 86604; + public const uint MaddeningCall = 86620; + public const uint Enrage = 86736; + } + + struct Events + { + public const uint SayWarden1 = 1; + public const uint SayWarden2 = 2; + public const uint SayWarden3 = 3; + } + + [Script] + class boss_hogger : CreatureScript + { + public boss_hogger() : base("boss_hogger") { } + + class boss_hoggerAI : BossAI + { + public boss_hoggerAI(Creature creature) : base(creature, DataTypes.Hogger) { } + + public override void EnterCombat(Unit who) + { + _EnterCombat(); + Talk(TextIds.SayPull); + + _scheduler.SetValidator(() => !me.HasUnitState(UnitState.Casting)); + + _scheduler.Schedule(TimeSpan.FromSeconds(3), TimeSpan.FromSeconds(4), task => + { + DoCastVictim(SpellIds.ViciousSlice); + task.Repeat(TimeSpan.FromSeconds(10), TimeSpan.FromSeconds(14)); + }); + + _scheduler.Schedule(TimeSpan.FromSeconds(1), TimeSpan.FromSeconds(2), task => + { + DoCast(SpellIds.MaddeningCall); + task.Repeat(TimeSpan.FromSeconds(15), TimeSpan.FromSeconds(20)); + }); + } + + public override void JustDied(Unit killer) + { + Talk(TextIds.SayDeath); + _JustDied(); + me.SummonCreature(CreatureIds.WardenThelwater, Misc.WardenThelwaterPos); + } + + public override void JustSummoned(Creature summon) + { + base.JustSummoned(summon); + if (summon.GetEntry() == CreatureIds.WardenThelwater) + summon.GetMotionMaster().MovePoint(0, Misc.WardenThelwaterMovePos); + } + + public override void UpdateAI(uint diff) + { + if (!UpdateVictim()) + return; + + _scheduler.Update(diff); + + if (me.HasUnitState(UnitState.Casting)) + return; + + DoMeleeAttackIfReady(); + } + + public override void DamageTaken(Unit attacker, ref uint damage) + { + if (me.HealthBelowPctDamaged(30, damage) && !_hasEnraged) + { + _hasEnraged = true; + Talk(TextIds.SayEnrage); + DoCastSelf(SpellIds.Enrage); + } + } + + bool _hasEnraged; + } + + public override CreatureAI GetAI(Creature creature) + { + return GetInstanceAI(creature, nameof(instance_the_stockade)); + } + } + + [Script] + class npc_warden_thelwater : CreatureScript + { + public npc_warden_thelwater() : base("npc_warden_thelwater") { } + + class npc_warden_thelwaterAI : ScriptedAI + { + public npc_warden_thelwaterAI(Creature creature) : base(creature) { } + + public override void MovementInform(MovementGeneratorType type, uint id) + { + if (type == MovementGeneratorType.Point && id == 0) + _events.ScheduleEvent(Events.SayWarden1, TimeSpan.FromSeconds(1)); + } + + public override void UpdateAI(uint diff) + { + _events.Update(diff); + + _events.ExecuteEvents(eventId => + { + switch (eventId) + { + case Events.SayWarden1: + Talk(TextIds.SayWarden1); + _events.ScheduleEvent(Events.SayWarden2, TimeSpan.FromSeconds(4)); + break; + case Events.SayWarden2: + Talk(TextIds.SayWarden2); + _events.ScheduleEvent(Events.SayWarden3, TimeSpan.FromSeconds(3)); + break; + case Events.SayWarden3: + Talk(TextIds.SayWarden3); + break; + } + }); + } + } + + public override CreatureAI GetAI(Creature creature) + { + return GetInstanceAI(creature, nameof(instance_the_stockade)); + } + } +} diff --git a/Scripts/EasternKingdoms/TheStockade/InstanceTheStockade.cs b/Scripts/EasternKingdoms/TheStockade/InstanceTheStockade.cs new file mode 100644 index 000000000..4a513ded3 --- /dev/null +++ b/Scripts/EasternKingdoms/TheStockade/InstanceTheStockade.cs @@ -0,0 +1,56 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using Game.Maps; +using Game.Scripting; +using Game.AI; +using Game.Entities; + +namespace Scripts.EasternKingdoms.TheStockade +{ + struct Misc + { + public static Position WardenThelwaterMovePos = new Position(152.019f, 106.198f, -35.1896f, 1.082104f); + public static Position WardenThelwaterPos = new Position(138.369f, 78.2932f, -33.85627f, 1.082104f); + } + + struct DataTypes + { + public const uint RandolphMoloch = 0; + public const uint LordOverheat = 1; + public const uint Hogger = 2; + } + + struct CreatureIds + { + public const uint RandolphMoloch = 46383; + public const uint LordOverheat = 46264; + public const uint Hogger = 46254; + public const uint WardenThelwater = 46409; + public const uint MortimerMoloch = 46482; + } + + + + [Script] + class instance_the_stockade : InstanceMapScript + { + public instance_the_stockade() : base("instance_the_stockade", 34) { } + + class instance_the_stockade_InstanceMapScript : InstanceScript + { + public instance_the_stockade_InstanceMapScript(Map map) : base(map) + { + SetHeaders("SS"); + SetBossNumber(3); + } + } + + public override InstanceScript GetInstanceScript(InstanceMap map) + { + return new instance_the_stockade_InstanceMapScript(map); + } + } +} diff --git a/Scripts/Kalimdor/Durotar.cs b/Scripts/Kalimdor/Durotar.cs new file mode 100644 index 000000000..10df33126 --- /dev/null +++ b/Scripts/Kalimdor/Durotar.cs @@ -0,0 +1,151 @@ +/* + * Copyright (C) 2012-2017 CypherCore + * + * 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, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +using Framework.Constants; +using Framework.Dynamic; +using Game.AI; +using Game.Entities; +using Game.Scripting; +using Game.Spells; +using System; + +namespace Scripts.Kalimdor +{ + [Script] + class npc_lazy_peon : CreatureScript + { + public npc_lazy_peon() : base("npc_lazy_peon") { } + + class npc_lazy_peonAI : NullCreatureAI + { + public npc_lazy_peonAI(Creature creature) : base(creature) { } + + public override void InitializeAI() + { + me.SetWalk(true); + + scheduler.Schedule(TimeSpan.FromSeconds(1), TimeSpan.FromMilliseconds(120000), task => + { + GameObject Lumberpile = me.FindNearestGameObject(GoLumberpile, 20); + if (Lumberpile) + me.GetMotionMaster().MovePoint(1, Lumberpile.GetPositionX() - 1, Lumberpile.GetPositionY(), Lumberpile.GetPositionZ()); + task.Repeat(); + }); + + scheduler.Schedule(TimeSpan.FromMilliseconds(300000), task => + { + me.HandleEmoteCommand(Emote.StateNone); + me.GetMotionMaster().MovePoint(2, me.GetHomePosition()); + task.Repeat(); + }); + } + + public override void MovementInform(MovementGeneratorType type, uint id) + { + switch (id) + { + case 1: + me.HandleEmoteCommand(Emote.StateWorkChopwood); + break; + case 2: + DoCast(me, SpellBuffSleep); + break; + } + } + + public override void SpellHit(Unit caster, SpellInfo spell) + { + if (spell.Id != SpellAwakenPeon) + return; + + Player player = caster.ToPlayer(); + if (player && player.GetQuestStatus(QuestLazyPeons) == QuestStatus.Incomplete) + { + player.KilledMonsterCredit(me.GetEntry(), me.GetGUID()); + Talk(SaySpellHit, caster); + me.RemoveAllAuras(); + GameObject Lumberpile = me.FindNearestGameObject(GoLumberpile, 20); + if (Lumberpile) + me.GetMotionMaster().MovePoint(1, Lumberpile.GetPositionX() - 1, Lumberpile.GetPositionY(), Lumberpile.GetPositionZ()); + } + } + + public override void UpdateAI(uint diff) + { + scheduler.Update(diff); + + //if (!UpdateVictim()) + //return; + + //DoMeleeAttackIfReady(); + } + + const int QuestLazyPeons = 25134; + const int GoLumberpile = 175784; + const uint SpellBuffSleep = 17743; + const int SpellAwakenPeon = 19938; + const int SaySpellHit = 0; + + TaskScheduler scheduler = new TaskScheduler(); + } + + public override CreatureAI GetAI(Creature creature) + { + return new npc_lazy_peonAI(creature); + } + } + + [Script] + class spell_voodoo : SpellScriptLoader + { + public spell_voodoo() : base("spell_voodoo") { } + + class spell_voodoo_SpellScript : SpellScript + { + public override bool Validate(SpellInfo spellInfo) + { + return ValidateSpellInfo(SpellBrew, SpellGhostly, SpellHex1, SpellHex2, SpellHex3, SpellGrow, SpellLaunch); + } + + void HandleDummy(uint effIndex) + { + uint spellid = RandomHelper.RAND(SpellBrew, SpellGhostly, RandomHelper.RAND(SpellHex1, SpellHex2, SpellHex3), SpellGrow, SpellLaunch); + Unit target = GetHitUnit(); + if (target) + GetCaster().CastSpell(target, spellid, false); + } + + public override void Register() + { + OnEffectHitTarget.Add(new EffectHandler(HandleDummy, 0, SpellEffectName.Dummy)); + } + } + + public override SpellScript GetSpellScript() + { + return new spell_voodoo_SpellScript(); + } + + const uint SpellBrew = 16712; // Special Brew + const uint SpellGhostly = 16713; // Ghostly + const uint SpellHex1 = 16707; // Hex + const uint SpellHex2 = 16708; // Hex + const uint SpellHex3 = 16709; // Hex + const uint SpellGrow = 16711; // Grow + const uint SpellLaunch = 16716; // Launch (Whee!) + } +} diff --git a/Scripts/Kalimdor/RageFireChasm.cs b/Scripts/Kalimdor/RageFireChasm.cs new file mode 100644 index 000000000..f7126e958 --- /dev/null +++ b/Scripts/Kalimdor/RageFireChasm.cs @@ -0,0 +1,38 @@ +/* + * Copyright (C) 2012-2017 CypherCore + * + * 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, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +using Game.Maps; +using Game.Scripting; + +namespace Scripts.Kalimdor +{ + [Script] + public class RageFireChasm : InstanceMapScript + { + public RageFireChasm() : base("instance_ragefire_chasm", 389) { } + + public override InstanceScript GetInstanceScript(InstanceMap map) + { + return new RagefireChasmInstanceMapScript(map); + } + + class RagefireChasmInstanceMapScript : InstanceScript + { + public RagefireChasmInstanceMapScript(Map map) : base(map) { } + } + } +} diff --git a/Scripts/Northrend/AzjolNerub/Ahnkahet/BossAmanitar.cs b/Scripts/Northrend/AzjolNerub/Ahnkahet/BossAmanitar.cs new file mode 100644 index 000000000..0409766a0 --- /dev/null +++ b/Scripts/Northrend/AzjolNerub/Ahnkahet/BossAmanitar.cs @@ -0,0 +1,209 @@ +/* + * Copyright (C) 2012-2017 CypherCore + * + * 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, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +using Framework.Constants; +using Game.AI; +using Game.Entities; +using Game.Scripting; +using System; + +namespace Scripts.Northrend.AzjolNerub.Ahnkahet.Amanitar +{ + struct SpellIds + { + public const uint Bash = 57094; // Victim + public const uint EntanglingRoots = 57095; // Random Victim 100y + public const uint Mini = 57055; // Self + public const uint VenomBoltVolley = 57088; // Random Victim 100y + public const uint HealthyMushroomPotentFungus = 56648; // Killer 3y + public const uint PoisonousMushroomPoisonCloud = 57061; // Self - Duration 8 Sec + public const uint PoisonousMushroomVisualArea = 61566; // Self + public const uint PoisonousMushroomVisualAura = 56741; // Self + public const uint PutridMushroom = 31690; // To Make The Mushrooms Visible + public const uint PowerMushroomVisualAura = 56740; + } + + struct CreatureIds + { + public const uint Trigger = 19656; + public const uint HealthyMushroom = 30391; + public const uint PoisonousMushroom = 30435; + } + + [Script] + class boss_amanitar : CreatureScript + { + public boss_amanitar() : base("boss_amanitar") { } + + class boss_amanitarAI : BossAI + { + public boss_amanitarAI(Creature creature) : base(creature, DataTypes.Amanitar) { } + + public override void Reset() + { + _Reset(); + me.SetMeleeDamageSchool(SpellSchools.Nature); + me.ApplySpellImmune(0, SpellImmunity.School, SpellSchoolMask.Nature, true); + } + + public override void EnterCombat(Unit who) + { + _EnterCombat(); + + _scheduler.SetValidator(() => !me.HasUnitState(UnitState.Casting)); + + _scheduler.Schedule(TimeSpan.FromSeconds(5), task => + { + SpawnAdds(); + task.Repeat(TimeSpan.FromSeconds(20)); + }); + _scheduler.Schedule(TimeSpan.FromSeconds(5), TimeSpan.FromSeconds(9), task => + { + DoCast(SelectTarget(SelectAggroTarget.Random, 0, 100, true), SpellIds.EntanglingRoots, true); + task.Repeat(TimeSpan.FromSeconds(10), TimeSpan.FromSeconds(15)); + }); + _scheduler.Schedule(TimeSpan.FromSeconds(10), TimeSpan.FromSeconds(14), task => + { + DoCastVictim(SpellIds.Bash); + task.Repeat(TimeSpan.FromSeconds(7), TimeSpan.FromSeconds(12)); + }); + _scheduler.Schedule(TimeSpan.FromSeconds(12), TimeSpan.FromSeconds(18), task => + { + DoCast(SpellIds.Mini); + task.Repeat(TimeSpan.FromSeconds(25), TimeSpan.FromSeconds(30)); + }); + _scheduler.Schedule(TimeSpan.FromSeconds(15), TimeSpan.FromSeconds(20), task => + { + DoCast(SelectTarget(SelectAggroTarget.Random, 0, 100, true), SpellIds.VenomBoltVolley, true); + task.Repeat(TimeSpan.FromSeconds(18), TimeSpan.FromSeconds(22)); + }); + } + + public override void JustDied(Unit killer) + { + _JustDied(); + instance.DoRemoveAurasDueToSpellOnPlayers(SpellIds.Mini); + } + + void SpawnAdds() + { + int u = 0; + + for (byte i = 0; i < 30; ++i) + { + Position pos = me.GetRandomNearPosition(30.0f); + pos.posZ = me.GetMap().GetHeight(pos.GetPositionX(), pos.GetPositionY(), MapConst.MaxHeight) + 2.0f; + + Creature trigger = me.SummonCreature(CreatureIds.Trigger, pos); + if (trigger) + { + Creature temp1 = trigger.FindNearestCreature(CreatureIds.HealthyMushroom, 4.0f, true); + Creature temp2 = trigger.FindNearestCreature(CreatureIds.PoisonousMushroom, 4.0f, true); + if (temp1 || temp2) + { + trigger.DisappearAndDie(); + } + else + { + u = 1 - u; + trigger.DisappearAndDie(); + me.SummonCreature(u > 0 ? CreatureIds.PoisonousMushroom : CreatureIds.HealthyMushroom, pos, TempSummonType.TimedOrCorpseDespawn, 60 * Time.InMilliseconds); + } + } + } + } + + public override void UpdateAI(uint diff) + { + if (!UpdateVictim()) + return; + + _scheduler.Update(diff); + + if (me.HasUnitState(UnitState.Casting)) + return; + + DoMeleeAttackIfReady(); + } + } + + public override CreatureAI GetAI(Creature creature) + { + return GetInstanceAI(creature); + } + } + + [Script] + class npc_amanitar_mushrooms : CreatureScript + { + public npc_amanitar_mushrooms() : base("npc_amanitar_mushrooms") { } + + class npc_amanitar_mushroomsAI : ScriptedAI + { + public npc_amanitar_mushroomsAI(Creature creature) : base(creature) { } + + public override void Reset() + { + _scheduler.CancelAll(); + _scheduler.SetValidator(() => !me.HasUnitState(UnitState.Casting)); + + _scheduler.Schedule(TimeSpan.FromSeconds(1), task => + { + if (me.GetEntry() == CreatureIds.PoisonousMushroom) + { + DoCast(me, SpellIds.PoisonousMushroomVisualArea, true); + DoCast(me, SpellIds.PoisonousMushroomPoisonCloud); + } + task.Repeat(TimeSpan.FromSeconds(7)); + }); + + me.SetDisplayId(me.GetCreatureTemplate().ModelId2); + DoCast(SpellIds.PutridMushroom); + + if (me.GetEntry() == CreatureIds.PoisonousMushroom) + DoCast(SpellIds.PoisonousMushroomVisualAura); + else + DoCast(SpellIds.PowerMushroomVisualAura); + } + + public override void DamageTaken(Unit attacker, ref uint damage) + { + if (damage >= me.GetHealth() && me.GetEntry() == CreatureIds.HealthyMushroom) + DoCast(me, SpellIds.HealthyMushroomPotentFungus, true); + } + + public override void EnterCombat(Unit who) { } + public override void AttackStart(Unit victim) { } + + public override void UpdateAI(uint diff) + { + if (!UpdateVictim()) + return; + + _scheduler.Update(diff); + + if (me.HasUnitState(UnitState.Casting)) + return; + } + } + + public override CreatureAI GetAI(Creature creature) + { + return new npc_amanitar_mushroomsAI(creature); + } + } +} diff --git a/Scripts/Northrend/AzjolNerub/Ahnkahet/BossElderNadox.cs b/Scripts/Northrend/AzjolNerub/Ahnkahet/BossElderNadox.cs new file mode 100644 index 000000000..de1e3acd5 --- /dev/null +++ b/Scripts/Northrend/AzjolNerub/Ahnkahet/BossElderNadox.cs @@ -0,0 +1,276 @@ +/* + * Copyright (C) 2012-2017 CypherCore + * + * 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, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +using Framework.Constants; +using Game.AI; +using Game.Entities; +using Game.Scripting; +using Game.Spells; +using System; +using System.Collections.Generic; + + +namespace Scripts.Northrend.AzjolNerub.Ahnkahet.ElderNadox +{ + struct SpellIds + { + public const uint BroodPlague = 56130; + public const uint HBroodRage = 59465; + public const uint Enrage = 26662; // Enraged If Too Far Away From Home + public const uint SummonSwarmers = 56119; // 2x 30178 -- 2x Every 10secs + public const uint SummonSwarmGuard = 56120; // 1x 30176 + + // Adds + public const uint SwarmBuff = 56281; + public const uint Sprint = 56354; + } + + struct TextIds + { + public const uint SayAggro = 0; + public const uint SaySlay = 1; + public const uint SayDeath = 2; + public const uint SayEggSac = 3; + public const uint EmoteHatches = 4; + } + + struct Misc + { + public const uint DataRespectYourElders = 6; + } + + [Script] + class boss_elder_nadox : CreatureScript + { + public boss_elder_nadox() : base("boss_elder_nadox") { } + + class boss_elder_nadoxAI : BossAI + { + public boss_elder_nadoxAI(Creature creature) : base(creature, DataTypes.ElderNadox) + { + Initialize(); + } + + void Initialize() + { + GuardianSummoned = false; + GuardianDied = false; + } + + public override void Reset() + { + _Reset(); + Initialize(); + } + + public override void EnterCombat(Unit who) + { + _EnterCombat(); + Talk(TextIds.SayAggro); + + _scheduler.Schedule(TimeSpan.FromSeconds(13), task => + { + DoCast(SelectTarget(SelectAggroTarget.Random, 0, 100, true), SpellIds.BroodPlague, true); + task.Repeat(TimeSpan.FromSeconds(15)); + }); + + _scheduler.Schedule(TimeSpan.FromSeconds(10), task => + { + /// @todo: summoned by egg + DoCast(me, SpellIds.SummonSwarmers); + if (RandomHelper.URand(1, 3) == 3) // 33% chance of dialog + Talk(TextIds.SayEggSac); + task.Repeat(); + }); + + if (IsHeroic()) + { + _scheduler.Schedule(TimeSpan.FromSeconds(12), task => + { + DoCast(SpellIds.HBroodRage); + task.Repeat(TimeSpan.FromSeconds(10), TimeSpan.FromSeconds(50)); + }); + + _scheduler.Schedule(TimeSpan.FromSeconds(5), task => + { + if (me.HasAura(SpellIds.Enrage)) + return; + if (me.GetPositionZ() < 24.0f) + DoCast(me, SpellIds.Enrage, true); + task.Repeat(); + }); + } + } + + public override void SummonedCreatureDies(Creature summon, Unit killer) + { + if (summon.GetEntry() == AKCreatureIds.AhnkaharGuardian) + GuardianDied = true; + } + + public override uint GetData(uint type) + { + if (type == Misc.DataRespectYourElders) + return !GuardianDied ? 1 : 0u; + + return 0; + } + + public override void KilledUnit(Unit who) + { + if (who.IsTypeId(TypeId.Player)) + Talk(TextIds.SaySlay); + } + + public override void JustDied(Unit killer) + { + _JustDied(); + Talk(TextIds.SayDeath); + } + + public override void UpdateAI(uint diff) + { + if (!UpdateVictim()) + return; + + _scheduler.Update(diff); + + if (!GuardianSummoned && me.HealthBelowPct(50)) + { + /// @todo: summoned by egg + Talk(TextIds.EmoteHatches, me); + DoCast(me, SpellIds.SummonSwarmGuard); + GuardianSummoned = true; + } + + DoMeleeAttackIfReady(); + } + + bool GuardianSummoned; + bool GuardianDied; + } + + public override CreatureAI GetAI(Creature creature) + { + return GetInstanceAI(creature, "instance_ahnkahet"); + } + } + + [Script] + class npc_ahnkahar_nerubian : CreatureScript + { + public npc_ahnkahar_nerubian() : base("npc_ahnkahar_nerubian") { } + + class npc_ahnkahar_nerubianAI : ScriptedAI + { + public npc_ahnkahar_nerubianAI(Creature creature) : base(creature) { } + + public override void Reset() + { + _scheduler.CancelAll(); + _scheduler.Schedule(TimeSpan.FromSeconds(13), task => + { + DoCast(me, SpellIds.Sprint); + task.Repeat(TimeSpan.FromSeconds(20)); + }); + } + + public override void UpdateAI(uint diff) + { + if (!UpdateVictim()) + return; + + if (me.HasUnitState(UnitState.Casting)) + return; + + _scheduler.Update(diff); + + DoMeleeAttackIfReady(); + } + } + + public override CreatureAI GetAI(Creature creature) + { + return new npc_ahnkahar_nerubianAI(creature); + } + } + + // 56159 - Swarm + [Script] + class spell_ahn_kahet_swarm : SpellScriptLoader + { + public spell_ahn_kahet_swarm() : base("spell_ahn_kahet_swarm") { } + + class spell_ahn_kahet_swarm_SpellScript : SpellScript + { + public spell_ahn_kahet_swarm_SpellScript() + { + _targetCount = 0; + } + + public override bool Validate(SpellInfo spellInfo) + { + return ValidateSpellInfo(SpellIds.SwarmBuff); + } + + void CountTargets(List targets) + { + _targetCount = targets.Count; + } + + void HandleDummy(uint effIndex) + { + if (_targetCount != 0) + { Aura aura = GetCaster().GetAura(SpellIds.SwarmBuff); + if (aura != null) + { + aura.SetStackAmount((byte)_targetCount); + aura.RefreshDuration(); + } + else + GetCaster().CastCustomSpell(SpellIds.SwarmBuff, SpellValueMod.AuraStack, _targetCount, GetCaster(), TriggerCastFlags.FullMask); + } + else + GetCaster().RemoveAurasDueToSpell(SpellIds.SwarmBuff); + } + + public override void Register() + { + OnObjectAreaTargetSelect.Add(new ObjectAreaTargetSelectHandler(CountTargets, 0, Targets.UnitSrcAreaAlly)); + OnEffectHit.Add(new EffectHandler(HandleDummy, 0, SpellEffectName.Dummy)); + } + + int _targetCount; + } + + public override SpellScript GetSpellScript() + { + return new spell_ahn_kahet_swarm_SpellScript(); + } + } + + [Script] + class achievement_respect_your_elders : AchievementCriteriaScript + { + public achievement_respect_your_elders() : base("achievement_respect_your_elders") { } + + public override bool OnCheck(Player player, Unit target) + { + return target && target.GetAI().GetData(Misc.DataRespectYourElders) != 0; + } + } +} diff --git a/Scripts/Northrend/AzjolNerub/Ahnkahet/BossHeraldVolazj.cs b/Scripts/Northrend/AzjolNerub/Ahnkahet/BossHeraldVolazj.cs new file mode 100644 index 000000000..7ccfa0c14 --- /dev/null +++ b/Scripts/Northrend/AzjolNerub/Ahnkahet/BossHeraldVolazj.cs @@ -0,0 +1,295 @@ +/* + * Copyright (C) 2012-2017 CypherCore + * + * 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, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +using Framework.Constants; +using Game.AI; +using Game.Entities; +using Game.Maps; +using Game.Scripting; +using Game.Spells; +using System.Collections.Generic; +using System.Linq; + +namespace Scripts.Northrend.AzjolNerub.Ahnkahet.HeraldVolazj +{ + struct SpellIds + { + public const uint Insanity = 57496; //Dummy + public const uint InsanityVisual = 57561; + public const uint InsanityTarget = 57508; + public const uint MindFlay = 57941; + public const uint ShadowBoltVolley = 57942; + public const uint Shiver = 57949; + public const uint ClonePlayer = 57507; //Cast On Player During Insanity + public const uint InsanityPhasing1 = 57508; + public const uint InsanityPhasing2 = 57509; + public const uint InsanityPhasing3 = 57510; + public const uint InsanityPhasing4 = 57511; + public const uint InsanityPhasing5 = 57512; + } + + struct TextIds + { + public const uint SayAggro = 0; + public const uint SaySlay = 1; + public const uint SayDeath = 2; + public const uint SayPhase = 3; + } + + struct Misc + { + public const uint AchievQuickDemiseStartEvent = 20382; + } + + [Script] + class boss_volazj : CreatureScript + { + public boss_volazj() : base("boss_volazj") { } + + class boss_volazjAI : ScriptedAI + { + public boss_volazjAI(Creature creature) : base(creature) + { + Summons = new SummonList(me); + + Initialize(); + instance = creature.GetInstanceScript(); + } + + void Initialize() + { + uiMindFlayTimer = 8 * Time.InMilliseconds; + uiShadowBoltVolleyTimer = 5 * Time.InMilliseconds; + uiShiverTimer = 15 * Time.InMilliseconds; + // Used for Insanity handling + insanityHandled = 0; + } + + // returns the percentage of health after taking the given damage. + uint GetHealthPct(uint damage) + { + if (damage > me.GetHealth()) + return 0; + return (uint)(100 * (me.GetHealth() - damage) / me.GetMaxHealth()); + } + + public override void DamageTaken(Unit pAttacker, ref uint damage) + { + if (me.HasFlag(UnitFields.Flags, UnitFlags.NotSelectable)) + damage = 0; + + if ((GetHealthPct(0) >= 66 && GetHealthPct(damage) < 66) || + (GetHealthPct(0) >= 33 && GetHealthPct(damage) < 33)) + { + me.InterruptNonMeleeSpells(false); + DoCast(me, SpellIds.Insanity, false); + } + } + + public override void SpellHitTarget(Unit target, SpellInfo spell) + { + if (spell.Id == SpellIds.Insanity) + { + // Not good target or too many players + if (target.GetTypeId() != TypeId.Player || insanityHandled > 4) + return; + // First target - start channel visual and set self as unnattackable + if (insanityHandled == 0) + { + // Channel visual + DoCast(me, SpellIds.InsanityVisual, true); + // Unattackable + me.SetFlag(UnitFields.Flags, UnitFlags.NotSelectable); + me.SetControlled(true, UnitState.Stunned); + } + + // phase the player + target.CastSpell(target, SpellIds.InsanityTarget + insanityHandled, true); + + SpellInfo spellInfo = Global.SpellMgr.GetSpellInfo(SpellIds.InsanityTarget + insanityHandled); + if (spellInfo == null) + return; + + // summon twisted party members for this target + var players = me.GetMap().GetPlayers(); + foreach (var player in players) + { + if (!player || !player.IsAlive()) + continue; + // Summon clone + Unit summon = me.SummonCreature(AKCreatureIds.TwistedVisage, me.GetPositionX(), me.GetPositionY(), me.GetPositionZ(), me.GetOrientation(), TempSummonType.CorpseDespawn, 0); + if (summon) + { + // clone + player.CastSpell(summon, SpellIds.ClonePlayer, true); + // phase the summon + summon.SetInPhase((uint)spellInfo.GetEffect(0).MiscValueB, true, true); + } + } + ++insanityHandled; + } + } + + void ResetPlayersPhase() + { + var players = me.GetMap().GetPlayers(); + foreach (var player in players) + { + for (uint index = 0; index <= 4; ++index) + player.RemoveAurasDueToSpell(SpellIds.InsanityTarget + index); + } + } + + public override void Reset() + { + Initialize(); + + instance.SetBossState(DataTypes.HeraldVolazj, EncounterState.NotStarted); + instance.DoStopCriteriaTimer(CriteriaTimedTypes.Event, Misc.AchievQuickDemiseStartEvent); + + // Visible for all players in insanity + me.SetInPhase(169, true, true); + for (uint i = 173; i <= 177; ++i) + me.SetInPhase(i, true, true); + + ResetPlayersPhase(); + + // Cleanup + Summons.DespawnAll(); + me.RemoveFlag(UnitFields.Flags, UnitFlags.NotSelectable); + me.SetControlled(false, UnitState.Stunned); + } + + public override void EnterCombat(Unit who) + { + Talk(TextIds.SayAggro); + + instance.SetBossState(DataTypes.HeraldVolazj, EncounterState.InProgress); + instance.DoStartCriteriaTimer(CriteriaTimedTypes.Event, Misc.AchievQuickDemiseStartEvent); + } + + public override void JustSummoned(Creature summon) + { + Summons.Summon(summon); + } + + public override void SummonedCreatureDespawn(Creature summon) + { + uint nextPhase = 0; + Summons.Despawn(summon); + + // Check if all summons in this phase killed + foreach (var guid in Summons) + { + Creature visage = ObjectAccessor.GetCreature(me, guid); + if (visage) + { + // Not all are dead + if (visage.IsInPhase(summon)) + return; + else + { + nextPhase = visage.GetPhases().First(); + break; + } + } + } + + // Roll Insanity + var players = me.GetMap().GetPlayers(); + foreach (var player in players) + { + if (player) + { + for (uint index = 0; index <= 4; ++index) + player.RemoveAurasDueToSpell(SpellIds.InsanityTarget + index); + player.CastSpell(player, SpellIds.InsanityTarget + nextPhase - 173, true); + } + } + } + + public override void UpdateAI(uint diff) + { + //Return since we have no target + if (!UpdateVictim()) + return; + + if (insanityHandled != 0) + { + if (!Summons.Empty()) + return; + + insanityHandled = 0; + me.RemoveFlag(UnitFields.Flags, UnitFlags.NotSelectable); + me.SetControlled(false, UnitState.Stunned); + me.RemoveAurasDueToSpell(SpellIds.InsanityVisual); + } + + if (uiMindFlayTimer <= diff) + { + DoCastVictim(SpellIds.MindFlay); + uiMindFlayTimer = 20 * Time.InMilliseconds; + } else uiMindFlayTimer -= diff; + + if (uiShadowBoltVolleyTimer <= diff) + { + DoCastVictim(SpellIds.ShadowBoltVolley); + uiShadowBoltVolleyTimer = 5 * Time.InMilliseconds; + } else uiShadowBoltVolleyTimer -= diff; + + if (uiShiverTimer <= diff) + { + Unit target = SelectTarget(SelectAggroTarget.Random, 0); + if (target) + DoCast(target, SpellIds.Shiver); + uiShiverTimer = 15 * Time.InMilliseconds; + } else uiShiverTimer -= diff; + + DoMeleeAttackIfReady(); + } + + public override void JustDied(Unit killer) + { + Talk(TextIds.SayDeath); + + instance.SetBossState(DataTypes.HeraldVolazj, EncounterState.Done); + + Summons.DespawnAll(); + ResetPlayersPhase(); + } + + public override void KilledUnit(Unit who) + { + if (who.IsTypeId(TypeId.Player)) + Talk(TextIds.SaySlay); + } + + InstanceScript instance; + + uint uiMindFlayTimer; + uint uiShadowBoltVolleyTimer; + uint uiShiverTimer; + uint insanityHandled; + SummonList Summons; + } + + public override CreatureAI GetAI(Creature creature) + { + return GetInstanceAI(creature); + } + } +} diff --git a/Scripts/Northrend/AzjolNerub/Ahnkahet/BossJedogaShadowseeker.cs b/Scripts/Northrend/AzjolNerub/Ahnkahet/BossJedogaShadowseeker.cs new file mode 100644 index 000000000..9257514cd --- /dev/null +++ b/Scripts/Northrend/AzjolNerub/Ahnkahet/BossJedogaShadowseeker.cs @@ -0,0 +1,602 @@ +/* + * Copyright (C) 2012-2017 CypherCore + * + * 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, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +using Framework.Constants; +using Game.AI; +using Game.Entities; +using Game.Maps; +using Game.Scripting; + +namespace Scripts.Northrend.AzjolNerub.Ahnkahet.JedogaShadowseeker +{ + struct SpellIds + { + public const uint SphereVisual = 56075; + public const uint GiftOfTheHerald = 56219; + public const uint CycloneStrike = 56855; // Self + public const uint LightningBolt = 56891; // 40y + public const uint Thundershock = 56926; // 30y + + public const uint RandomLightningVisual = 56327; + public const uint SacrificeBeam = 56150; + public const uint SacrificeVisual = 56133; + } + + struct TextIds + { + public const uint SayAggro = 0; + public const uint SaySacrifice1 = 1; + public const uint SaySacrifice2 = 2; + public const uint SaySlay = 3; + public const uint SayDeath = 4; + public const uint SayPreaching = 5; + } + struct Misc + { + public const int ActionInitiateKilled = 1; + public const uint DataVolunteerWork = 2; + + public static Position[] JedogaPosition = + { + new Position(372.330994f, -705.278015f, -0.624178f, 5.427970f), + new Position(372.330994f, -705.278015f, -16.179716f, 5.427970f) + }; + } + + [Script] + class boss_jedoga_shadowseeker : CreatureScript + { + public boss_jedoga_shadowseeker() : base("boss_jedoga_shadowseeker") { } + + public class boss_jedoga_shadowseekerAI : ScriptedAI + { + public boss_jedoga_shadowseekerAI(Creature creature) : base(creature) + { + instance = creature.GetInstanceScript(); + bFirstTime = true; + bPreDone = false; + } + + void Initialize() + { + uiOpFerTimer = RandomHelper.URand(15 * Time.InMilliseconds, 20 * Time.InMilliseconds); + + uiCycloneTimer = 3 * Time.InMilliseconds; + uiBoltTimer = 7 * Time.InMilliseconds; + uiThunderTimer = 12 * Time.InMilliseconds; + + bOpFerok = false; + bOpFerokFail = false; + bOnGround = false; + bCanDown = false; + volunteerWork = true; + } + + public override void Reset() + { + Initialize(); + + DoCast(SpellIds.RandomLightningVisual); + + if (!bFirstTime) + instance.SetBossState(DataTypes.JedogaShadowseeker, EncounterState.Fail); + + instance.SetGuidData(DataTypes.PlJedogaTarget, ObjectGuid.Empty); + instance.SetGuidData(DataTypes.AddJedogaVictim, ObjectGuid.Empty); + instance.SetData(DataTypes.JedogaResetInitiands, 0); + MoveUp(); + + bFirstTime = false; + } + + public override void EnterCombat(Unit who) + { + if (instance == null || (who.GetTypeId() == TypeId.Unit && who.GetEntry() == AKCreatureIds.JedogaController)) + return; + + Talk(TextIds.SayAggro); + me.SetInCombatWithZone(); + instance.SetBossState(DataTypes.JedogaShadowseeker, EncounterState.InProgress); + } + + public override void AttackStart(Unit who) + { + if (!who || (who.GetTypeId() == TypeId.Unit && who.GetEntry() == AKCreatureIds.JedogaController)) + return; + + base.AttackStart(who); + } + + public override void KilledUnit(Unit Victim) + { + if (!Victim || !Victim.IsTypeId(TypeId.Player)) + return; + + Talk(TextIds.SaySlay); + } + + public override void JustDied(Unit killer) + { + Talk(TextIds.SayDeath); + instance.SetBossState(DataTypes.JedogaShadowseeker, EncounterState.Done); + } + + public override void DoAction(int action) + { + if (action == Misc.ActionInitiateKilled) + volunteerWork = false; + } + + public override uint GetData(uint type) + { + if (type == Misc.DataVolunteerWork) + return volunteerWork ? 1 : 0u; + + return 0; + } + + public override void MoveInLineOfSight(Unit who) + { + if (instance == null || !who || (who.IsTypeId(TypeId.Unit) && who.GetEntry() == AKCreatureIds.JedogaController)) + return; + + if (!bPreDone && who.IsTypeId(TypeId.Player) && me.GetDistance(who) < 100.0f) + { + Talk(TextIds.SayPreaching); + bPreDone = true; + } + + if (instance.GetBossState(DataTypes.JedogaShadowseeker) != EncounterState.InProgress || !bOnGround) + return; + + if (!me.GetVictim() && me.CanCreatureAttack(who)) + { + float attackRadius = me.GetAttackDistance(who); + if (me.IsWithinDistInMap(who, attackRadius) && me.IsWithinLOSInMap(who)) + { + if (!me.GetVictim()) + { + who.RemoveAurasByType(AuraType.ModStealth); + AttackStart(who); + } + else + { + who.SetInCombatWith(me); + me.AddThreat(who, 0.0f); + } + } + } + } + + void MoveDown() + { + bOpFerokFail = false; + + instance.SetData(DataTypes.JedogaTriggerSwitch, 0); + me.GetMotionMaster().MovePoint(1, Misc.JedogaPosition[1]); + me.ApplySpellImmune(0, SpellImmunity.Damage, SpellSchoolMask.Normal, false); + me.ApplySpellImmune(0, SpellImmunity.Damage, SpellSchoolMask.Magic, false); + me.RemoveFlag(UnitFields.Flags, UnitFlags.NotSelectable | UnitFlags.NonAttackable); + + me.RemoveAurasDueToSpell(SpellIds.SphereVisual); + + bOnGround = true; + + if (UpdateVictim()) + { + AttackStart(me.GetVictim()); + me.GetMotionMaster().MoveChase(me.GetVictim()); + } + else + { + Unit target = Global.ObjAccessor.GetUnit(me, instance.GetGuidData(DataTypes.PlJedogaTarget)); + if (target) + { + AttackStart(target); + instance.SetData(DataTypes.JedogaResetInitiands, 0); + if (instance.GetBossState(DataTypes.JedogaShadowseeker) != EncounterState.InProgress) + EnterCombat(target); + } + else if (!me.IsInCombat()) + EnterEvadeMode(); + } + } + + void MoveUp() + { + me.ApplySpellImmune(0, SpellImmunity.Damage, SpellSchoolMask.Normal, true); + me.ApplySpellImmune(0, SpellImmunity.Damage, SpellSchoolMask.Magic, true); + me.SetFlag(UnitFields.Flags, UnitFlags.NotSelectable | UnitFlags.NonAttackable); + + me.AttackStop(); + me.RemoveAllAuras(); + me.LoadCreaturesAddon(); + me.GetMotionMaster().MovePoint(0, Misc.JedogaPosition[0]); + + instance.SetData(DataTypes.JedogaTriggerSwitch, 1); + if (instance.GetBossState(DataTypes.JedogaShadowseeker) == EncounterState.InProgress) + GetVictimForSacrifice(); + + bOnGround = false; + uiOpFerTimer = RandomHelper.URand(15 * Time.InMilliseconds, 30 * Time.InMilliseconds); + } + + void GetVictimForSacrifice() + { + ObjectGuid victim = instance.GetGuidData(DataTypes.AddJedogaInitiand); + if (!victim.IsEmpty()) + { + Talk(TextIds.SaySacrifice1); + instance.SetGuidData(DataTypes.AddJedogaVictim, victim); + } + else + bCanDown = true; + } + + void Sacrifice() + { + Talk(TextIds.SaySacrifice2); + + me.InterruptNonMeleeSpells(false); + DoCast(me, SpellIds.GiftOfTheHerald, false); + + bOpFerok = false; + bCanDown = true; + } + + public override void UpdateAI(uint diff) + { + if (instance.GetBossState(DataTypes.JedogaShadowseeker) != EncounterState.InProgress && instance.GetData(DataTypes.AllInitiandDead) != 0) + MoveDown(); + + if (bOpFerok && !bOnGround && !bCanDown) + Sacrifice(); + + if (bOpFerokFail && !bOnGround && !bCanDown) + bCanDown = true; + + if (bCanDown) + { + MoveDown(); + bCanDown = false; + } + + if (bOnGround) + { + if (!UpdateVictim()) + return; + + if (uiCycloneTimer <= diff) + { + DoCast(me, SpellIds.CycloneStrike, false); + uiCycloneTimer = RandomHelper.URand(15 * Time.InMilliseconds, 30 * Time.InMilliseconds); + } + else uiCycloneTimer -= diff; + + if (uiBoltTimer <= diff) + { + Unit target = SelectTarget(SelectAggroTarget.Random, 0, 100, true); + if (target) + me.CastSpell(target, SpellIds.LightningBolt, false); + + uiBoltTimer = RandomHelper.URand(15 * Time.InMilliseconds, 30 * Time.InMilliseconds); + } + else uiBoltTimer -= diff; + + if (uiThunderTimer <= diff) + { + Unit target = SelectTarget(SelectAggroTarget.Random, 0, 100, true); + if (target) + me.CastSpell(target, SpellIds.Thundershock, false); + + uiThunderTimer = RandomHelper.URand(15 * Time.InMilliseconds, 30 * Time.InMilliseconds); + } + else uiThunderTimer -= diff; + + if (uiOpFerTimer <= diff) + MoveUp(); + else + uiOpFerTimer -= diff; + + DoMeleeAttackIfReady(); + } + } + + InstanceScript instance; + + uint uiOpFerTimer; + uint uiCycloneTimer; + uint uiBoltTimer; + uint uiThunderTimer; + + bool bPreDone; + public bool bOpFerok; + bool bOnGround; + public bool bOpFerokFail; + bool bCanDown; + bool volunteerWork; + bool bFirstTime; + } + + public override CreatureAI GetAI(Creature creature) + { + return GetInstanceAI(creature); + } + } + + [Script] + class npc_jedoga_twilight_volunteer : CreatureScript + { + public npc_jedoga_twilight_volunteer() : base("npc_jedoga_initiand") { } + + class npc_jedoga_twilight_volunteerAI : ScriptedAI + { + public npc_jedoga_twilight_volunteerAI(Creature creature) : base(creature) + { + Initialize(); + instance = creature.GetInstanceScript(); + } + + void Initialize() + { + bWalking = false; + bCheckTimer = 2 * Time.InMilliseconds; + } + + public override void Reset() + { + Initialize(); + + if (instance.GetBossState(DataTypes.JedogaShadowseeker) != EncounterState.InProgress) + { + me.RemoveAurasDueToSpell(SpellIds.SphereVisual); + me.ApplySpellImmune(0, SpellImmunity.Damage, SpellSchoolMask.Normal, false); + me.ApplySpellImmune(0, SpellImmunity.Damage, SpellSchoolMask.Magic, false); + me.RemoveFlag(UnitFields.Flags, UnitFlags.NotSelectable | UnitFlags.NonAttackable); + } + else + { + DoCast(me, SpellIds.SphereVisual, false); + me.ApplySpellImmune(0, SpellImmunity.Damage, SpellSchoolMask.Normal, true); + me.ApplySpellImmune(0, SpellImmunity.Damage, SpellSchoolMask.Magic, true); + me.SetFlag(UnitFields.Flags, UnitFlags.NotSelectable | UnitFlags.NonAttackable); + } + } + + public override void JustDied(Unit killer) + { + if (!killer || instance == null) + return; + + if (bWalking) + { + Creature boss = ObjectAccessor.GetCreature(me, instance.GetGuidData(DataTypes.JedogaShadowseeker)); + if (boss) + { + if (!boss.GetAI().bOpFerok) + boss.GetAI().bOpFerokFail = true; + + if (killer.IsTypeId(TypeId.Player)) + boss.GetAI().DoAction(Misc.ActionInitiateKilled); + } + + instance.SetGuidData(DataTypes.AddJedogaVictim, ObjectGuid.Empty); + + bWalking = false; + } + if (killer.IsTypeId(TypeId.Player)) + instance.SetGuidData(DataTypes.PlJedogaTarget, killer.GetGUID()); + } + + public override void EnterCombat(Unit who) { } + + public override void AttackStart(Unit victim) + { + if ((instance.GetBossState(DataTypes.JedogaShadowseeker) == EncounterState.InProgress) || !victim) + return; + + base.AttackStart(victim); + } + + public override void MoveInLineOfSight(Unit who) + { + if ((instance.GetBossState(DataTypes.JedogaShadowseeker) == EncounterState.InProgress) || !who) + return; + + base.MoveInLineOfSight(who); + } + + public override void MovementInform(MovementGeneratorType uiType, uint uiPointId) + { + if (uiType != MovementGeneratorType.Point || instance == null) + return; + + switch (uiPointId) + { + case 1: + { + Creature boss = me.GetMap().GetCreature(instance.GetGuidData(DataTypes.JedogaShadowseeker)); + if (boss) + { + boss.GetAI().bOpFerok = true; + boss.GetAI().bOpFerokFail = false; + me.KillSelf(); + } + } + break; + } + } + + public override void UpdateAI(uint diff) + { + if (bCheckTimer <= diff) + { + if (me.GetGUID() == instance.GetGuidData(DataTypes.AddJedogaVictim) && !bWalking) + { + me.RemoveAurasDueToSpell(SpellIds.SphereVisual); + me.ApplySpellImmune(0, SpellImmunity.Damage, SpellSchoolMask.Normal, false); + me.ApplySpellImmune(0, SpellImmunity.Damage, SpellSchoolMask.Magic, false); + me.RemoveFlag(UnitFields.Flags, UnitFlags.NotSelectable | UnitFlags.NonAttackable); + + float distance = me.GetDistance(Misc.JedogaPosition[1]); + + if (distance < 9.0f) + me.SetSpeedRate(UnitMoveType.Walk, 0.5f); + else if (distance < 15.0f) + me.SetSpeedRate(UnitMoveType.Walk, 0.75f); + else if (distance < 20.0f) + me.SetSpeedRate(UnitMoveType.Walk, 1.0f); + + me.GetMotionMaster().Clear(false); + me.GetMotionMaster().MovePoint(1, Misc.JedogaPosition[1]); + bWalking = true; + } + if (!bWalking) + { + if (instance.GetBossState(DataTypes.JedogaShadowseeker) != EncounterState.InProgress && me.HasAura(SpellIds.SphereVisual)) + { + me.RemoveAurasDueToSpell(SpellIds.SphereVisual); + me.ApplySpellImmune(0, SpellImmunity.Damage, SpellSchoolMask.Normal, false); + me.ApplySpellImmune(0, SpellImmunity.Damage, SpellSchoolMask.Magic, false); + me.RemoveFlag(UnitFields.Flags, UnitFlags.NotSelectable | UnitFlags.NonAttackable); + } + if (instance.GetBossState(DataTypes.JedogaShadowseeker) == EncounterState.InProgress && !me.HasAura(SpellIds.SphereVisual)) + { + DoCast(me, SpellIds.SphereVisual, false); + me.ApplySpellImmune(0, SpellImmunity.Damage, SpellSchoolMask.Normal, true); + me.ApplySpellImmune(0, SpellImmunity.Damage, SpellSchoolMask.Magic, true); + me.SetFlag(UnitFields.Flags, UnitFlags.NotSelectable | UnitFlags.NonAttackable); + } + } + bCheckTimer = 2 * Time.InMilliseconds; + } + else bCheckTimer -= diff; + + //Return since we have no target + if (!UpdateVictim()) + return; + + DoMeleeAttackIfReady(); + } + + InstanceScript instance; + + uint bCheckTimer; + bool bWalking; + } + + public override CreatureAI GetAI(Creature creature) + { + return GetInstanceAI(creature); + } + } + + [Script] + class npc_jedoga_controller : CreatureScript + { + public npc_jedoga_controller() : base("npc_jedogas_aufseher_trigger") { } + + class npc_jedoga_controllerAI : ScriptedAI + { + public npc_jedoga_controllerAI(Creature creature) : base(creature) + { + instance = creature.GetInstanceScript(); + bRemoved = false; + bRemoved2 = false; + bCast = false; + bCast2 = false; + + SetCombatMovement(false); + } + + public override void Reset() { } + + public override void EnterCombat(Unit who) { } + + public override void AttackStart(Unit victim) { } + + public override void MoveInLineOfSight(Unit who) { } + + public override void UpdateAI(uint diff) + { + if (!bRemoved && me.GetPositionX() > 440.0f) + { + if (instance.GetBossState(DataTypes.PrinceTaldaram) == EncounterState.Done) + { + me.InterruptNonMeleeSpells(true); + bRemoved = true; + return; + } + if (!bCast) + { + //DoCast(me, JedogaShadowseekerConst.SpellBeamVisualJedogasAufseher1, false); + bCast = true; + } + } + if (!bRemoved2 && me.GetPositionX() < 440.0f) + { + if (!bCast2 && instance.GetData(DataTypes.JedogaTriggerSwitch) != 0) + { + //DoCast(me, JedogaShadowseekerConst.SpellBeamVisualJedogasAufseher2, false); + bCast2 = true; + } + if (bCast2 && instance.GetData(DataTypes.JedogaTriggerSwitch) == 0) + { + me.InterruptNonMeleeSpells(true); + bCast2 = false; + } + if (!bRemoved2 && instance.GetBossState(DataTypes.JedogaShadowseeker) == EncounterState.Done) + { + me.InterruptNonMeleeSpells(true); + bRemoved2 = true; + } + } + } + + InstanceScript instance; + + bool bRemoved; + bool bRemoved2; + bool bCast; + bool bCast2; + } + + public override CreatureAI GetAI(Creature creature) + { + return GetInstanceAI(creature); + } + } + + [Script] + class achievement_volunteer_work : AchievementCriteriaScript + { + public achievement_volunteer_work() : base("achievement_volunteer_work") { } + + public override bool OnCheck(Player player, Unit target) + { + if (!target) + return false; + + Creature Jedoga = target.ToCreature(); + if (Jedoga) + if (Jedoga.GetAI().GetData(Misc.DataVolunteerWork) != 0) + return true; + + return false; + } + } +} diff --git a/Scripts/Northrend/AzjolNerub/Ahnkahet/BossPrinceTaldaram.cs b/Scripts/Northrend/AzjolNerub/Ahnkahet/BossPrinceTaldaram.cs new file mode 100644 index 000000000..db1101c31 --- /dev/null +++ b/Scripts/Northrend/AzjolNerub/Ahnkahet/BossPrinceTaldaram.cs @@ -0,0 +1,476 @@ +/* + * Copyright (C) 2012-2017 CypherCore + * + * 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, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +using Framework.Constants; +using Game.AI; +using Game.Entities; +using Game.Maps; +using Game.Scripting; +using Game.Spells; +using System; + +namespace Scripts.Northrend.AzjolNerub.Ahnkahet.PrinceTaldaram +{ + struct SpellIds + { + public const uint Bloodthirst = 55968; // Trigger Spell + Add Aura + public const uint ConjureFlameSphere = 55931; + public const uint FlameSphereSummon1 = 55895; // 1x 30106 + public const uint FlameSphereSummon2 = 59511; // 1x 31686 + public const uint FlameSphereSummon3 = 59512; // 1x 31687 + public const uint FlameSphereSpawnEffect = 55891; + public const uint FlameSphereVisual = 55928; + public const uint FlameSpherePeriodic = 55926; + public const uint FlameSphereDeathEffect = 55947; + public const uint EmbraceOfTheVampyr = 55959; + public const uint Vanish = 55964; + + public const uint BeamVisual = 60342; + public const uint HoverFall = 60425; + } + + struct CreatureIds + { + public const uint FlameSphere1 = 30106; + public const uint FlameSphere2 = 31686; + public const uint FlameSphere3 = 31687; + } + + struct TextIds + { + public const uint Say1 = 0; + public const uint SayWarning = 1; + public const uint SayAggro = 2; + public const uint SaySlay = 3; + public const uint SayDeath = 4; + public const uint SayFeed = 5; + public const uint SayVanish = 6; + } + + struct Misc + { + public const uint EventConjureFlameSpheres = 1; + public const uint EventBloodthirst = 2; + public const uint EventVanish = 3; + public const uint EventJustVanished = 4; + public const uint EventVanished = 5; + public const uint EventFeeding = 6; + + // Flame Sphere + public const uint EventStartMove = 7; + public const uint EventDespawn = 8; + + public const uint DataEmbraceDmg = 20000; + public const uint DataEmbraceDmgH = 40000; + public const float DataSphereDistance = 25.0f; + public const float DataSphereAngleOffset = MathFunctions.PI / 2; + public const float DataGroundPositionZ = 11.30809f; + } + + [Script] + class boss_prince_taldaram : CreatureScript + { + public boss_prince_taldaram() : base("boss_prince_taldaram") { } + + public class boss_prince_taldaramAI : BossAI + { + public boss_prince_taldaramAI(Creature creature) : base(creature, DataTypes.PrinceTaldaram) + { + me.SetDisableGravity(true); + _embraceTakenDamage = 0; + } + + public override void Reset() + { + _Reset(); + _flameSphereTargetGUID.Clear(); + _embraceTargetGUID.Clear(); + _embraceTakenDamage = 0; + } + + public override void EnterCombat(Unit who) + { + _EnterCombat(); + Talk(TextIds.SayAggro); + _events.ScheduleEvent(Misc.EventBloodthirst, 10000); + _events.ScheduleEvent(Misc.EventVanish, RandomHelper.URand(25000, 35000)); + _events.ScheduleEvent(Misc.EventConjureFlameSpheres, 5000); + } + + public override void JustSummoned(Creature summon) + { + base.JustSummoned(summon); + + switch (summon.GetEntry()) + { + case CreatureIds.FlameSphere1: + case CreatureIds.FlameSphere2: + case CreatureIds.FlameSphere3: + summon.GetAI().SetGUID(_flameSphereTargetGUID); + break; + } + } + + public override void UpdateAI(uint diff) + { + if (!UpdateVictim()) + return; + + _events.Update(diff); + + if (me.HasUnitState(UnitState.Casting)) + return; + + _events.ExecuteEvents(eventId => + { + switch (eventId) + { + case Misc.EventBloodthirst: + DoCast(me, SpellIds.Bloodthirst); + _events.ScheduleEvent(Misc.EventBloodthirst, 10000); + break; + case Misc.EventConjureFlameSpheres: + // random target? + Unit victim = me.GetVictim(); + if (victim) + { + _flameSphereTargetGUID = victim.GetGUID(); + DoCast(victim, SpellIds.ConjureFlameSphere); + } + _events.ScheduleEvent(Misc.EventConjureFlameSpheres, 15000); + break; + case Misc.EventVanish: + { + var players = me.GetMap().GetPlayers(); + uint targets = 0; + foreach (var player in players) + { + if (player && player.IsAlive()) + ++targets; + } + + if (targets > 2) + { + Talk(TextIds.SayVanish); + DoCast(me, SpellIds.Vanish); + me.SetInCombatState(true); // Prevents the boss from resetting + _events.DelayEvents(500); + _events.ScheduleEvent(Misc.EventJustVanished, 500); + Unit embraceTarget = SelectTarget(SelectAggroTarget.Random, 0, 100.0f, true); + if (embraceTarget) + _embraceTargetGUID = embraceTarget.GetGUID(); + } + _events.ScheduleEvent(Misc.EventVanish, RandomHelper.URand(25000, 35000)); + break; + } + case Misc.EventJustVanished: + { + Unit embraceTarget = GetEmbraceTarget(); + if (embraceTarget) + { + me.GetMotionMaster().Clear(); + me.SetSpeedRate(UnitMoveType.Walk, 2.0f); + me.GetMotionMaster().MoveChase(embraceTarget); + } + _events.ScheduleEvent(Misc.EventVanished, 1300); + } + break; + case Misc.EventVanished: + { + Unit embraceTarget = GetEmbraceTarget(); + if (embraceTarget) + DoCast(embraceTarget, SpellIds.EmbraceOfTheVampyr); + Talk(TextIds.SayFeed); + me.GetMotionMaster().Clear(); + me.SetSpeedRate(UnitMoveType.Walk, 1.0f); + me.GetMotionMaster().MoveChase(me.GetVictim()); + _events.ScheduleEvent(Misc.EventFeeding, 20000); + } + break; + case Misc.EventFeeding: + _embraceTargetGUID.Clear(); + break; + default: + break; + } + }); + + DoMeleeAttackIfReady(); + } + + public override void DamageTaken(Unit doneBy, ref uint damage) + { + Unit embraceTarget = GetEmbraceTarget(); + + if (embraceTarget && embraceTarget.IsAlive()) + { + _embraceTakenDamage += damage; + if (_embraceTakenDamage > DungeonMode(Misc.DataEmbraceDmg, Misc.DataEmbraceDmgH)) + { + _embraceTargetGUID.Clear(); + me.CastStop(); + } + } + } + + public override void JustDied(Unit killer) + { + Talk(TextIds.SayDeath); + _JustDied(); + } + + public override void KilledUnit(Unit victim) + { + if (!victim.IsTypeId(TypeId.Player)) + return; + + if (victim.GetGUID() == _embraceTargetGUID) + _embraceTargetGUID.Clear(); + + Talk(TextIds.SaySlay); + } + + public bool CheckSpheres() + { + for (byte i = 0; i < 2; ++i) + { + if (instance.GetData(DataTypes.Sphere1 + i) == 0) + return false; + } + + RemovePrison(); + return true; + } + + Unit GetEmbraceTarget() + { + if (!_embraceTargetGUID.IsEmpty()) + return Global.ObjAccessor.GetUnit(me, _embraceTargetGUID); + + return null; + } + + void RemovePrison() + { + me.RemoveFlag(UnitFields.Flags, UnitFlags.NotSelectable); + me.RemoveAurasDueToSpell(SpellIds.BeamVisual); + me.SetHomePosition(me.GetPositionX(), me.GetPositionY(), Misc.DataGroundPositionZ, me.GetOrientation()); + DoCast(SpellIds.HoverFall); + me.SetDisableGravity(false); + me.GetMotionMaster().MoveLand(0, me.GetHomePosition()); + Talk(TextIds.SayWarning); + instance.HandleGameObject(instance.GetGuidData(DataTypes.PrinceTaldaramPlatform), true); + } + + ObjectGuid _flameSphereTargetGUID; + ObjectGuid _embraceTargetGUID; + uint _embraceTakenDamage; + } + + public override CreatureAI GetAI(Creature creature) + { + return GetInstanceAI(creature); + } + } + + [Script] // 30106, 31686, 31687 - Flame Sphere + class npc_prince_taldaram_flame_sphere : CreatureScript + { + public npc_prince_taldaram_flame_sphere() : base("npc_prince_taldaram_flame_sphere") { } + + class npc_prince_taldaram_flame_sphereAI : ScriptedAI + { + public npc_prince_taldaram_flame_sphereAI(Creature creature) : base(creature) + { + } + + public override void Reset() + { + DoCast(me, SpellIds.FlameSphereSpawnEffect, true); + DoCast(me, SpellIds.FlameSphereVisual, true); + + _flameSphereTargetGUID.Clear(); + _events.Reset(); + _events.ScheduleEvent(Misc.EventStartMove, 3 * Time.InMilliseconds); + _events.ScheduleEvent(Misc.EventDespawn, 13 * Time.InMilliseconds); + } + + public override void SetGUID(ObjectGuid guid, int id = 0) + { + _flameSphereTargetGUID = guid; + } + + public override void EnterCombat(Unit who) { } + + public override void MoveInLineOfSight(Unit who) { } + + public override void UpdateAI(uint diff) + { + _events.Update(diff); + + _events.ExecuteEvents(eventId => + { + switch (eventId) + { + case Misc.EventStartMove: + { + DoCast(me, SpellIds.FlameSpherePeriodic, true); + + /// @todo: find correct values + float angleOffset = 0.0f; + float distOffset = Misc.DataSphereDistance; + + switch (me.GetEntry()) + { + case CreatureIds.FlameSphere1: + break; + case CreatureIds.FlameSphere2: + angleOffset = Misc.DataSphereAngleOffset; + break; + case CreatureIds.FlameSphere3: + angleOffset = -Misc.DataSphereAngleOffset; + break; + default: + return; + } + + Unit sphereTarget = Global.ObjAccessor.GetUnit(me, _flameSphereTargetGUID); + if (!sphereTarget) + return; + + float angle = me.GetAngle(sphereTarget) + angleOffset; + float x = me.GetPositionX() + distOffset * (float)Math.Cos(angle); + float y = me.GetPositionY() + distOffset * (float)Math.Sin(angle); + + /// @todo: correct speed + me.GetMotionMaster().MovePoint(0, x, y, me.GetPositionZ()); + break; + } + case Misc.EventDespawn: + DoCast(me, SpellIds.FlameSphereDeathEffect, true); + me.DespawnOrUnsummon(1000); + break; + default: + break; + } + }); + } + + ObjectGuid _flameSphereTargetGUID; + } + + public override CreatureAI GetAI(Creature creature) + { + return new npc_prince_taldaram_flame_sphereAI(creature); + } + } + + [Script] // 193093, 193094 - Ancient Nerubian Device + class go_prince_taldaram_sphere : GameObjectScript + { + public go_prince_taldaram_sphere() : base("go_prince_taldaram_sphere") { } + + public override bool OnGossipHello(Player player, GameObject go) + { + InstanceScript instance = go.GetInstanceScript(); + if (instance == null) + return false; + + Creature PrinceTaldaram = ObjectAccessor.GetCreature(go, instance.GetGuidData(DataTypes.PrinceTaldaram)); + if (PrinceTaldaram && PrinceTaldaram.IsAlive()) + { + go.SetFlag(GameObjectFields.Flags, GameObjectFlags.NotSelectable); + go.SetGoState(GameObjectState.Active); + + switch (go.GetEntry()) + { + case GameObjectIds.Sphere1: + instance.SetData(DataTypes.Sphere1, (uint)EncounterState.InProgress); + PrinceTaldaram.GetAI().Talk(TextIds.Say1); + break; + case GameObjectIds.Sphere2: + instance.SetData(DataTypes.Sphere2, (uint)EncounterState.InProgress); + PrinceTaldaram.GetAI().Talk(TextIds.Say1); + break; + } + + PrinceTaldaram.GetAI().CheckSpheres(); + } + return true; + } + } + + [Script] // 55931 - Conjure Flame Sphere + class spell_prince_taldaram_conjure_flame_sphere : SpellScriptLoader + { + public spell_prince_taldaram_conjure_flame_sphere() : base("spell_prince_taldaram_conjure_flame_sphere") { } + + class spell_prince_taldaram_conjure_flame_sphere_SpellScript : SpellScript + { + public override bool Validate(SpellInfo spellInfo) + { + return ValidateSpellInfo(SpellIds.FlameSphereSummon1, SpellIds.FlameSphereSummon2, SpellIds.FlameSphereSummon3); + } + + void HandleScript(uint effIndex) + { + Unit caster = GetCaster(); + caster.CastSpell(caster, SpellIds.FlameSphereSummon1, true); + + if (caster.GetMap().IsHeroic()) + { + caster.CastSpell(caster, SpellIds.FlameSphereSummon2, true); + caster.CastSpell(caster, SpellIds.FlameSphereSummon3, true); + } + } + + public override void Register() + { + OnEffectHitTarget.Add(new EffectHandler(HandleScript, 0, SpellEffectName.Dummy)); + } + } + + public override SpellScript GetSpellScript() + { + return new spell_prince_taldaram_conjure_flame_sphere_SpellScript(); + } + } + + [Script] // 55895, 59511, 59512 - Flame Sphere Summon + class spell_prince_taldaram_flame_sphere_summon : SpellScriptLoader + { + public spell_prince_taldaram_flame_sphere_summon() : base("spell_prince_taldaram_flame_sphere_summon") { } + + class spell_prince_taldaram_flame_sphere_summon_SpellScript : SpellScript + { + void SetDest(ref SpellDestination dest) + { + dest.RelocateOffset(new Position(0.0f, 0.0f, 5.5f, 0.0f)); + } + + public override void Register() + { + OnDestinationTargetSelect.Add(new DestinationTargetSelectHandler(SetDest, 0, Targets.DestCaster)); + } + } + + public override SpellScript GetSpellScript() + { + return new spell_prince_taldaram_flame_sphere_summon_SpellScript(); + } + } +} diff --git a/Scripts/Northrend/AzjolNerub/Ahnkahet/InstanceAhnahet.cs b/Scripts/Northrend/AzjolNerub/Ahnkahet/InstanceAhnahet.cs new file mode 100644 index 000000000..d9c5cd4d6 --- /dev/null +++ b/Scripts/Northrend/AzjolNerub/Ahnkahet/InstanceAhnahet.cs @@ -0,0 +1,334 @@ +/* + * Copyright (C) 2012-2017 CypherCore + * + * 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, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +using Framework.Constants; +using Game.Entities; +using Game.Maps; +using Game.Scripting; +using System.Collections.Generic; + +namespace Scripts.Northrend.AzjolNerub.Ahnkahet +{ + [Script] + class instance_ahnkahet : InstanceMapScript + { + public instance_ahnkahet() : base("instance_ahnkahet", 619) { } + + class instance_ahnkahet_InstanceScript : InstanceScript + { + public instance_ahnkahet_InstanceScript(Map map) : base(map) + { + SetHeaders("AK"); + SetBossNumber(DataTypes.HeraldVolazj + 1); + LoadDoorData(new DoorData(GameObjectIds.PrinceTaldaramGate, DataTypes.PrinceTaldaram, DoorType.Passage)); + + SwitchTrigger = 0; + + SpheresState[0] = 0; + SpheresState[1] = 0; + } + + public override void OnCreatureCreate(Creature creature) + { + switch (creature.GetEntry()) + { + case AKCreatureIds.ElderNadox: + ElderNadoxGUID = creature.GetGUID(); + break; + case AKCreatureIds.PrinceTaldaram: + PrinceTaldaramGUID = creature.GetGUID(); + break; + case AKCreatureIds.JedogaShadowseeker: + JedogaShadowseekerGUID = creature.GetGUID(); + break; + case AKCreatureIds.Amanitar: + AmanitarGUID = creature.GetGUID(); + break; + case AKCreatureIds.HeraldVolazj: + HeraldVolazjGUID = creature.GetGUID(); + break; + case AKCreatureIds.Initiand: + InitiandGUIDs.Add(creature.GetGUID()); + break; + default: + break; + } + } + + public override void OnGameObjectCreate(GameObject go) + { + switch (go.GetEntry()) + { + case GameObjectIds.PrinceTaldaramPlatform: + PrinceTaldaramPlatformGUID = go.GetGUID(); + if (GetBossState(DataTypes.PrinceTaldaram) == EncounterState.Done) + HandleGameObject(ObjectGuid.Empty, true, go); + break; + case GameObjectIds.Sphere1: + if (SpheresState[0] != 0) + { + go.SetGoState(GameObjectState.Active); + go.SetFlag(GameObjectFields.Flags, GameObjectFlags.NotSelectable); + } + else + go.RemoveFlag(GameObjectFields.Flags, GameObjectFlags.NotSelectable); + break; + case GameObjectIds.Sphere2: + if (SpheresState[1] != 0) + { + go.SetGoState(GameObjectState.Active); + go.SetFlag(GameObjectFields.Flags, GameObjectFlags.NotSelectable); + } + else + go.RemoveFlag(GameObjectFields.Flags, GameObjectFlags.NotSelectable); + break; + case GameObjectIds.PrinceTaldaramGate: + AddDoor(go, true); + break; + default: + break; + } + } + + public override void OnGameObjectRemove(GameObject go) + { + switch (go.GetEntry()) + { + case GameObjectIds.PrinceTaldaramGate: + AddDoor(go, false); + break; + default: + break; + } + } + + public override void SetData(uint type, uint data) + { + switch (type) + { + case DataTypes.Sphere1: + case DataTypes.Sphere2: + SpheresState[type - DataTypes.Sphere1] = data; + break; + case DataTypes.JedogaTriggerSwitch: + SwitchTrigger = (byte)data; + break; + case DataTypes.JedogaResetInitiands: + foreach (ObjectGuid guid in InitiandGUIDs) + { + Creature creature = instance.GetCreature(guid); + if (creature) + { + creature.Respawn(); + if (!creature.IsInEvadeMode()) + creature.GetAI().EnterEvadeMode(); + } + } + break; + default: + break; + } + } + + public override uint GetData(uint type) + { + switch (type) + { + case DataTypes.Sphere1: + case DataTypes.Sphere2: + return SpheresState[type - DataTypes.Sphere1]; + case DataTypes.AllInitiandDead: + foreach (ObjectGuid guid in InitiandGUIDs) + { + Creature cr = instance.GetCreature(guid); + if (!cr || cr.IsAlive()) + return 0; + } + return 1; + case DataTypes.JedogaTriggerSwitch: + return SwitchTrigger; + default: + break; + } + return 0; + } + + public override void SetGuidData(uint type, ObjectGuid data) + { + switch (type) + { + case DataTypes.AddJedogaVictim: + JedogaSacrifices = data; + break; + case DataTypes.PlJedogaTarget: + JedogaTarget = data; + break; + default: + break; + } + } + + public override ObjectGuid GetGuidData(uint type) + { + switch (type) + { + case DataTypes.ElderNadox: + return ElderNadoxGUID; + case DataTypes.PrinceTaldaram: + return PrinceTaldaramGUID; + case DataTypes.JedogaShadowseeker: + return JedogaShadowseekerGUID; + case DataTypes.Amanitar: + return AmanitarGUID; + case DataTypes.HeraldVolazj: + return HeraldVolazjGUID; + case DataTypes.PrinceTaldaramPlatform: + return PrinceTaldaramPlatformGUID; + case DataTypes.AddJedogaInitiand: + { + List vInitiands = new List(); + foreach (ObjectGuid guid in InitiandGUIDs) + { + Creature cr = instance.GetCreature(guid); + if (cr && cr.IsAlive()) + vInitiands.Add(guid); + } + if (vInitiands.Empty()) + return ObjectGuid.Empty; + + return vInitiands.PickRandom(); + } + case DataTypes.AddJedogaVictim: + return JedogaSacrifices; + case DataTypes.PlJedogaTarget: + return JedogaTarget; + default: + break; + } + return ObjectGuid.Empty; + } + + public override bool SetBossState(uint type, EncounterState state) + { + if (!base.SetBossState(type, state)) + return false; + + switch (type) + { + case DataTypes.JedogaShadowseeker: + if (state == EncounterState.Done) + { + foreach (ObjectGuid guid in InitiandGUIDs) + { + Creature cr = instance.GetCreature(guid); + if (cr) + cr.DespawnOrUnsummon(); + } + } + break; + default: + break; + } + return true; + } + + void WriteSaveDataMore(string data) + { + data += SpheresState[0] + ' ' + SpheresState[1]; + } + + void ReadSaveDataMore(string data) + { + data += SpheresState[0]; + data += SpheresState[1]; + } + + ObjectGuid ElderNadoxGUID; + ObjectGuid PrinceTaldaramGUID; + ObjectGuid JedogaShadowseekerGUID; + ObjectGuid AmanitarGUID; + ObjectGuid HeraldVolazjGUID; + + ObjectGuid PrinceTaldaramPlatformGUID; + ObjectGuid JedogaSacrifices; + ObjectGuid JedogaTarget; + + List InitiandGUIDs = new List(); + + uint[] SpheresState = new uint[2]; + byte SwitchTrigger; + } + + public override InstanceScript GetInstanceScript(InstanceMap map) + { + return new instance_ahnkahet_InstanceScript(map); + } + } + + struct DataTypes + { + // Encounter States/Boss GUIDs + public const uint ElderNadox = 0; + public const uint PrinceTaldaram = 1; + public const uint JedogaShadowseeker = 2; + public const uint Amanitar = 3; + public const uint HeraldVolazj = 4; + + // Additional Data + public const uint Sphere1 = 5; + public const uint Sphere2 = 6; + public const uint PrinceTaldaramPlatform = 7; + public const uint PlJedogaTarget = 8; + public const uint AddJedogaVictim = 9; + public const uint AddJedogaInitiand = 10; + public const uint JedogaTriggerSwitch = 11; + public const uint JedogaResetInitiands = 12; + public const uint AllInitiandDead = 13; + } + + struct AKCreatureIds + { + public const uint ElderNadox = 29309; + public const uint PrinceTaldaram = 29308; + public const uint JedogaShadowseeker = 29310; + public const uint Amanitar = 30258; + public const uint HeraldVolazj = 29311; + + // Elder Nadox + public const uint AhnkaharGuardian = 30176; + public const uint AhnkaharSwarmer = 30178; + + // Jedoga Shadowseeker + public const uint Initiand = 30114; + public const uint JedogaController = 30181; + + // Herald Volazj + //public const uint TwistedVisage1 = 30621, + //public const uint TwistedVisage2 = 30622, + //public const uint TwistedVisage3 = 30623, + //public const uint TwistedVisage4 = 30624, + public const uint TwistedVisage = 30625; + } + + struct GameObjectIds + { + public const uint PrinceTaldaramGate = 192236; + public const uint PrinceTaldaramPlatform = 193564; + public const uint Sphere1 = 193093; + public const uint Sphere2 = 193094; + } +} diff --git a/Scripts/Northrend/AzjolNerub/AzjolNerub/BossAnubarak.cs b/Scripts/Northrend/AzjolNerub/AzjolNerub/BossAnubarak.cs new file mode 100644 index 000000000..1d6e32d0a --- /dev/null +++ b/Scripts/Northrend/AzjolNerub/AzjolNerub/BossAnubarak.cs @@ -0,0 +1,722 @@ +/* + * Copyright (C) 2012-2017 CypherCore + * + * 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, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +using Framework.Constants; +using Game.AI; +using Game.Entities; +using Game.Maps; +using Game.Scripting; +using Game.Spells; +using System; +using System.Collections.Generic; +using System.Linq; + +namespace Scripts.Northrend.AzjolNerub.AzjolNerub.Anubarak +{ + struct SpellIds + { + public const uint Emerge = 53500; + public const uint Submerge = 53421; + public const uint ImpaleAura = 53456; + public const uint ImpaleVisual = 53455; + public const uint ImpaleDamage = 53454; + public const uint LeechingSwarm = 53467; + public const uint Pound = 59433; + public const uint PoundDamage = 59432; + public const uint CarrionBeetles = 53520; + public const uint CarrionBeetle = 53521; + + public const uint SummonDarter = 53599; + public const uint SummonAssassin = 53609; + public const uint SummonGuardian = 53614; + public const uint SummonVenomancer = 53615; + + public const uint Dart = 59349; + public const uint Backstab = 52540; + public const uint AssassinVisual = 53611; + public const uint SunderArmor = 53618; + public const uint PoisonBolt = 53617; + } + + struct CreatureIds + { + public const uint WorldTrigger = 22515; + } + + struct TextIds + { + public const uint SayAggro = 0; + public const uint SaySlay = 1; + public const uint SayDeath = 2; + public const uint SayLocust = 3; + public const uint SaySubmerge = 4; + public const uint SayIntro = 5; + } + + struct EventIds + { + public const uint Pound = 1; + public const uint Impale = 2; + public const uint LeechingSwarm = 3; + public const uint CarrionBeetles = 4; + public const uint Submerge = 5; // Use Event For This So We Don'T Submerge Mid-Cast + public const uint Darter = 6; + public const uint Assassin = 7; + public const uint Guardian = 8; + public const uint Venomancer = 9; + public const uint CloseDoor = 10; + } + + struct Misc + { + public const uint AchievGottaGoStartEvent = 20381; + + public const byte PhaseEmerge = 1; + public const byte PhaseSubmerge = 2; + + public const int GuidTypePet = 0; + public const int GuidTypeImpale = 1; + + public const byte SummonGroupWorldTriggerGuardian = 1; + + public const int ActionPetDied = 1; + public const int ActionPetEvade = 2; + } + + [Script] + class boss_anub_arak : CreatureScript + { + public boss_anub_arak() : base("boss_anub_arak") { } + + class boss_anub_arakAI : BossAI + { + public boss_anub_arakAI(Creature creature) : base(creature, ANDataTypes.Anubarak) { } + + public override void Reset() + { + base.Reset(); + me.RemoveFlag(UnitFields.Flags, UnitFlags.NonAttackable | UnitFlags.NotSelectable); + instance.DoStopCriteriaTimer(CriteriaTimedTypes.Event, Misc.AchievGottaGoStartEvent); + _nextSubmerge = 75; + _petCount = 0; + } + + public override void EnterCombat(Unit who) + { + base.EnterCombat(who); + + GameObject door = instance.GetGameObject(ANDataTypes.AnubarakWall); + if (door) + door.SetGoState(GameObjectState.Active); // open door for now + + Talk(TextIds.SayAggro); + instance.DoStartCriteriaTimer(CriteriaTimedTypes.Event, Misc.AchievGottaGoStartEvent); + + _events.SetPhase(Misc.PhaseEmerge); + _events.ScheduleEvent(EventIds.CloseDoor, TimeSpan.FromSeconds(5)); + _events.ScheduleEvent(EventIds.Pound, TimeSpan.FromSeconds(2), TimeSpan.FromSeconds(4), 0, Misc.PhaseEmerge); + _events.ScheduleEvent(EventIds.LeechingSwarm, TimeSpan.FromSeconds(5), TimeSpan.FromSeconds(7), 0, Misc.PhaseEmerge); + _events.ScheduleEvent(EventIds.CarrionBeetles, TimeSpan.FromSeconds(14), TimeSpan.FromSeconds(17), 0, Misc.PhaseEmerge); + + // set up world triggers + List summoned; + me.SummonCreatureGroup(Misc.SummonGroupWorldTriggerGuardian, out summoned); + if (summoned.Empty()) // something went wrong + { + EnterEvadeMode(EvadeReason.Other); + return; + } + _guardianTrigger = summoned.First().GetGUID(); + + Creature trigger = DoSummon(CreatureIds.WorldTrigger, me.GetPosition(), 0u, TempSummonType.ManualDespawn); + if (trigger) + _assassinTrigger = trigger.GetGUID(); + else + { + EnterEvadeMode(EvadeReason.Other); + return; + } + } + + public override void EnterEvadeMode(EvadeReason why) + { + summons.DespawnAll(); + _DespawnAtEvade(); + } + + public override void UpdateAI(uint diff) + { + if (!UpdateVictim()) + return; + + _events.Update(diff); + + if (me.HasUnitState(UnitState.Casting)) + return; + + _events.ExecuteEvents(eventId => + { + switch (eventId) + { + case EventIds.CloseDoor: + GameObject door = instance.GetGameObject(ANDataTypes.AnubarakWall); + if (door) + door.SetGoState(GameObjectState.Ready); + break; + case EventIds.Pound: + DoCastVictim(SpellIds.Pound); + _events.Repeat(TimeSpan.FromSeconds(26), TimeSpan.FromSeconds(32)); + break; + case EventIds.LeechingSwarm: + Talk(TextIds.SayLocust); + DoCastAOE(SpellIds.LeechingSwarm); + _events.Repeat(TimeSpan.FromSeconds(25), TimeSpan.FromSeconds(28)); + break; + case EventIds.CarrionBeetles: + DoCastAOE(SpellIds.CarrionBeetles); + _events.Repeat(TimeSpan.FromSeconds(24), TimeSpan.FromSeconds(27)); + break; + case EventIds.Impale: + Creature impaleTarget = ObjectAccessor.GetCreature(me, _impaleTarget); + if (impaleTarget) + DoCast(impaleTarget, SpellIds.ImpaleDamage, true); + break; + case EventIds.Submerge: + Talk(TextIds.SaySubmerge); + DoCastSelf(SpellIds.Submerge); + break; + case EventIds.Darter: + { + List triggers = new List(); + me.GetCreatureListWithEntryInGrid(triggers, CreatureIds.WorldTrigger); + if (!triggers.Empty()) + { + var it = triggers.PickRandom(); + it.CastSpell(it, SpellIds.SummonDarter, true); + _events.Repeat(TimeSpan.FromSeconds(11)); + } + else + EnterEvadeMode(EvadeReason.Other); + break; + } + case EventIds.Assassin: + { + Creature trigger = ObjectAccessor.GetCreature(me, _assassinTrigger); + if (trigger) + { + trigger.CastSpell(trigger, SpellIds.SummonAssassin, true); + trigger.CastSpell(trigger, SpellIds.SummonAssassin, true); + if (_assassinCount > 2) + { + _assassinCount -= 2; + _events.Repeat(TimeSpan.FromSeconds(20)); + } + else + _assassinCount = 0; + } + else // something went wrong + EnterEvadeMode(EvadeReason.Other); + break; + } + case EventIds.Guardian: + { + Creature trigger = ObjectAccessor.GetCreature(me, _guardianTrigger); + if (trigger) + { + trigger.CastSpell(trigger, SpellIds.SummonGuardian, true); + trigger.CastSpell(trigger, SpellIds.SummonGuardian, true); + if (_guardianCount > 2) + { + _guardianCount -= 2; + _events.Repeat(TimeSpan.FromSeconds(20)); + } + else + _guardianCount = 0; + } + else + EnterEvadeMode(EvadeReason.Other); + } + break; + case EventIds.Venomancer: + { + Creature trigger = ObjectAccessor.GetCreature(me, _guardianTrigger); + if (trigger) + { + trigger.CastSpell(trigger, SpellIds.SummonVenomancer, true); + trigger.CastSpell(trigger, SpellIds.SummonVenomancer, true); + if (_venomancerCount > 2) + { + _venomancerCount -= 2; + _events.Repeat(TimeSpan.FromSeconds(20)); + } + else + _venomancerCount = 0; + } + else + EnterEvadeMode(EvadeReason.Other); + } + break; + default: + break; + } + + if (me.HasUnitState(UnitState.Casting)) + return; + }); + + + DoMeleeAttackIfReady(); + } + + public override void JustDied(Unit killer) + { + _JustDied(); + Talk(TextIds.SayDeath); + } + + public override void KilledUnit(Unit victim) + { + if (victim.IsTypeId(TypeId.Player)) + Talk(TextIds.SaySlay); + } + + public override void SetGUID(ObjectGuid guid, int type) + { + switch (type) + { + case Misc.GuidTypePet: + { + Creature creature = ObjectAccessor.GetCreature(me, guid); + if (creature) + JustSummoned(creature); + else // something has gone horribly wrong + EnterEvadeMode(EvadeReason.Other); + break; + } + case Misc.GuidTypeImpale: + _impaleTarget = guid; + _events.ScheduleEvent(EventIds.Impale, TimeSpan.FromSeconds(4)); + break; + } + } + + public override void DoAction(int action) + { + switch (action) + { + case Misc.ActionPetDied: + if (_petCount == 0) // underflow check - something has gone horribly wrong + { + EnterEvadeMode(EvadeReason.Other); + return; + } + if (--_petCount == 0) // last pet died, emerge + { + me.RemoveAurasDueToSpell(SpellIds.Submerge); + me.RemoveAurasDueToSpell(SpellIds.ImpaleAura); + me.RemoveFlag(UnitFields.Flags, UnitFlags.NonAttackable | UnitFlags.NotSelectable); + DoCastSelf(SpellIds.Emerge); + _events.SetPhase(Misc.PhaseEmerge); + _events.ScheduleEvent(EventIds.Pound, TimeSpan.FromSeconds(13), TimeSpan.FromSeconds(18), 0, Misc.PhaseEmerge); + _events.ScheduleEvent(EventIds.LeechingSwarm, TimeSpan.FromSeconds(3), TimeSpan.FromSeconds(7), 0, Misc.PhaseEmerge); + _events.ScheduleEvent(EventIds.CarrionBeetles, TimeSpan.FromSeconds(10), TimeSpan.FromSeconds(15), 0, Misc.PhaseEmerge); + } + break; + case Misc.ActionPetEvade: + EnterEvadeMode(EvadeReason.Other); + break; + } + } + + public override void DamageTaken(Unit source, ref uint damage) + { + if (me.HasAura(SpellIds.Submerge)) + damage = 0; + else + if (_nextSubmerge != 0 && me.HealthBelowPctDamaged((int)_nextSubmerge, damage)) + { + _events.CancelEvent(EventIds.Submerge); + _events.ScheduleEvent(EventIds.Submerge, 0, 0, Misc.PhaseEmerge); + _nextSubmerge = _nextSubmerge - 25; + } + } + + public override void SpellHit(Unit whose, SpellInfo spell) + { + if (spell.Id == SpellIds.Submerge) + { + me.SetFlag(UnitFields.Flags, UnitFlags.NonAttackable | UnitFlags.NotSelectable); + me.RemoveAurasDueToSpell(SpellIds.LeechingSwarm); + DoCastSelf(SpellIds.ImpaleAura, true); + + _events.SetPhase(Misc.PhaseSubmerge); + switch (_nextSubmerge) + { + case 50: // first submerge phase + _assassinCount = 4; + _guardianCount = 2; + _venomancerCount = 0; + break; + case 25: // second submerge phase + _assassinCount = 6; + _guardianCount = 2; + _venomancerCount = 2; + break; + case 0: // third submerge phase + _assassinCount = 6; + _guardianCount = 2; + _venomancerCount = 2; + _events.ScheduleEvent(EventIds.Darter, TimeSpan.FromSeconds(0), 0, Misc.PhaseSubmerge); + break; + } + _petCount = (uint)(_guardianCount + _venomancerCount); + if (_assassinCount != 0) + _events.ScheduleEvent(EventIds.Assassin, TimeSpan.FromSeconds(0), 0, Misc.PhaseSubmerge); + if (_guardianCount != 0) + _events.ScheduleEvent(EventIds.Guardian, TimeSpan.FromSeconds(4), 0, Misc.PhaseSubmerge); + if (_venomancerCount != 0) + _events.ScheduleEvent(EventIds.Venomancer, TimeSpan.FromSeconds(20), 0, Misc.PhaseSubmerge); + } + } + + ObjectGuid _impaleTarget; + uint _nextSubmerge; + uint _petCount; + ObjectGuid _guardianTrigger; + ObjectGuid _assassinTrigger; + byte _assassinCount; + byte _guardianCount; + byte _venomancerCount; + } + + public override CreatureAI GetAI(Creature creature) + { + return GetInstanceAI(creature); + } + } + + [Script] + class npc_anubarak_pet_template : ScriptedAI + { + public npc_anubarak_pet_template(Creature creature, bool isLarge) : base(creature) + { + _instance = creature.GetInstanceScript(); + _isLarge = isLarge; + } + + public override void InitializeAI() + { + base.InitializeAI(); + + Creature anubarak = _instance.GetCreature(ANDataTypes.Anubarak); + if (anubarak) + anubarak.GetAI().SetGUID(me.GetGUID(), Misc.GuidTypePet); + else + me.DespawnOrUnsummon(); + } + + public override void JustDied(Unit killer) + { + base.JustDied(killer); + if (_isLarge) + { + Creature anubarak = _instance.GetCreature(ANDataTypes.Anubarak); + if (anubarak) + anubarak.GetAI().DoAction(Misc.ActionPetDied); + } + } + + public override void EnterEvadeMode(EvadeReason why) + { + Creature anubarak = _instance.GetCreature(ANDataTypes.Anubarak); + if (anubarak) + anubarak.GetAI().DoAction(Misc.ActionPetEvade); + else + me.DespawnOrUnsummon(); + } + + protected InstanceScript _instance; + bool _isLarge; + } + + [Script] + class npc_anubarak_anub_ar_darter : CreatureScript + { + public npc_anubarak_anub_ar_darter() : base("npc_anubarak_anub_ar_darter") { } + + class npc_anubarak_anub_ar_darterAI : npc_anubarak_pet_template + { + public npc_anubarak_anub_ar_darterAI(Creature creature) : base(creature, false) { } + + public override void InitializeAI() + { + base.InitializeAI(); + DoCastAOE(SpellIds.Dart); + } + } + + public override CreatureAI GetAI(Creature creature) + { + return GetInstanceAI(creature); + } + } + + [Script] + class npc_anubarak_anub_ar_assassin : CreatureScript + { + public npc_anubarak_anub_ar_assassin() : base("npc_anubarak_anub_ar_assassin") { } + + class npc_anubarak_anub_ar_assassinAI : npc_anubarak_pet_template + { + public npc_anubarak_anub_ar_assassinAI(Creature creature) : base(creature, false) + { + _backstabTimer = 6 * Time.InMilliseconds; + } + + bool IsInBounds(Position jumpTo, List boundary) + { + if (boundary == null) + return true; + foreach (var it in boundary) + if (!it.IsWithinBoundary(jumpTo)) + return false; + return true; + } + + Position GetRandomPositionAround(Creature anubarak) + { + float DISTANCE_MIN = 10.0f; + float DISTANCE_MAX = 30.0f; + double angle = RandomHelper.NextDouble() * 2.0 * Math.PI; + return new Position(anubarak.GetPositionX() + (float)(RandomHelper.FRand(DISTANCE_MIN, DISTANCE_MAX) * Math.Sin(angle)), anubarak.GetPositionY() + (float)(RandomHelper.FRand(DISTANCE_MIN, DISTANCE_MAX) * Math.Cos(angle)), anubarak.GetPositionZ()); + } + + public override void InitializeAI() + { + base.InitializeAI(); + var boundary = _instance.GetBossBoundary(ANDataTypes.Anubarak); + Creature anubarak = _instance.GetCreature(ANDataTypes.Anubarak); + if (anubarak) + { + Position jumpTo; + do + jumpTo = GetRandomPositionAround(anubarak); + while (!IsInBounds(jumpTo, boundary)); + me.GetMotionMaster().MoveJump(jumpTo, 40.0f, 40.0f); + DoCastSelf(SpellIds.AssassinVisual, true); + } + } + + public override void UpdateAI(uint diff) + { + if (!UpdateVictim()) + return; + + if (diff >= _backstabTimer) + { + if (me.GetVictim() && me.GetVictim().isInBack(me)) + DoCastVictim(SpellIds.Backstab); + _backstabTimer = 6 * Time.InMilliseconds; + } + else + _backstabTimer -= diff; + + DoMeleeAttackIfReady(); + } + + public override void MovementInform(MovementGeneratorType type, uint id) + { + if (id == EventId.Jump) + { + me.RemoveAurasDueToSpell(SpellIds.AssassinVisual); + DoZoneInCombat(); + } + } + + uint _backstabTimer; + } + + public override CreatureAI GetAI(Creature creature) + { + return GetInstanceAI(creature); + } + } + + [Script] + class npc_anubarak_anub_ar_guardian : CreatureScript + { + public npc_anubarak_anub_ar_guardian() : base("npc_anubarak_anub_ar_guardian") { } + + class npc_anubarak_anub_ar_guardianAI : npc_anubarak_pet_template + { + public npc_anubarak_anub_ar_guardianAI(Creature creature) : base(creature, true) + { + _sunderTimer = 6 * Time.InMilliseconds; + } + + public override void UpdateAI(uint diff) + { + if (!UpdateVictim()) + return; + + if (diff >= _sunderTimer) + { + DoCastVictim(SpellIds.SunderArmor); + _sunderTimer = 12 * Time.InMilliseconds; + } + else + _sunderTimer -= diff; + + DoMeleeAttackIfReady(); + } + + uint _sunderTimer; + } + + public override CreatureAI GetAI(Creature creature) + { + return GetInstanceAI(creature); + } + } + + [Script] + class npc_anubarak_anub_ar_venomancer : CreatureScript + { + public npc_anubarak_anub_ar_venomancer() : base("npc_anubarak_anub_ar_venomancer") { } + + class npc_anubarak_anub_ar_venomancerAI : npc_anubarak_pet_template + { + public npc_anubarak_anub_ar_venomancerAI(Creature creature) : base(creature, true) + { + _boltTimer = 5 * Time.InMilliseconds; + } + + public override void UpdateAI(uint diff) + { + if (!UpdateVictim()) + return; + + if (diff >= _boltTimer) + { + DoCastVictim(SpellIds.PoisonBolt); + _boltTimer = RandomHelper.URand(2, 3) * Time.InMilliseconds; + } + else + _boltTimer -= diff; + + DoMeleeAttackIfReady(); + } + + uint _boltTimer; + } + + public override CreatureAI GetAI(Creature creature) + { + return GetInstanceAI(creature); + } + } + + [Script] + class npc_anubarak_impale_target : CreatureScript + { + public npc_anubarak_impale_target() : base("npc_anubarak_impale_target") { } + + class npc_anubarak_impale_targetAI : NullCreatureAI + { + public npc_anubarak_impale_targetAI(Creature creature) : base(creature) { } + + public override void InitializeAI() + { + Creature anubarak = me.GetInstanceScript().GetCreature(ANDataTypes.Anubarak); + if (anubarak) + { + DoCastSelf(SpellIds.ImpaleVisual); + me.DespawnOrUnsummon(TimeSpan.FromSeconds(6)); + anubarak.GetAI().SetGUID(me.GetGUID(), Misc.GuidTypeImpale); + } + else + me.DespawnOrUnsummon(); + } + } + + public override CreatureAI GetAI(Creature creature) + { + return GetInstanceAI(creature); + } + } + + [Script] + class spell_anubarak_pound : SpellScriptLoader + { + public spell_anubarak_pound() : base("spell_anubarak_pound") { } + + class spell_anubarak_pound_SpellScript : SpellScript + { + public override bool Validate(SpellInfo spell) + { + return ValidateSpellInfo(SpellIds.PoundDamage); + } + + void HandleDummy(uint effIndex) + { + Unit target = GetHitUnit(); + if (target) + GetCaster().CastSpell(target, SpellIds.PoundDamage, true); + } + + public override void Register() + { + OnEffectHitTarget.Add(new EffectHandler(HandleDummy, 0, SpellEffectName.ApplyAura)); + } + } + + public override SpellScript GetSpellScript() + { + return new spell_anubarak_pound_SpellScript(); + } + } + + [Script] + class spell_anubarak_carrion_beetles : SpellScriptLoader + { + public spell_anubarak_carrion_beetles() : base("spell_anubarak_carrion_beetles") { } + + class spell_anubarak_carrion_beetles_AuraScript : AuraScript + { + public override bool Validate(SpellInfo spell) + { + return ValidateSpellInfo(SpellIds.CarrionBeetle); + } + + void HandlePeriodic(AuraEffect eff) + { + GetCaster().CastSpell(GetCaster(), SpellIds.CarrionBeetle, true); + GetCaster().CastSpell(GetCaster(), SpellIds.CarrionBeetle, true); + } + + public override void Register() + { + OnEffectPeriodic.Add(new EffectPeriodicHandler(HandlePeriodic, 0, AuraType.PeriodicDummy)); + } + } + + public override AuraScript GetAuraScript() + { + return new spell_anubarak_carrion_beetles_AuraScript(); + } + } +} diff --git a/Scripts/Northrend/AzjolNerub/AzjolNerub/BossKrikthirTheGatewatcher.cs b/Scripts/Northrend/AzjolNerub/AzjolNerub/BossKrikthirTheGatewatcher.cs new file mode 100644 index 000000000..7aa578a46 --- /dev/null +++ b/Scripts/Northrend/AzjolNerub/AzjolNerub/BossKrikthirTheGatewatcher.cs @@ -0,0 +1,1003 @@ +/* + * Copyright (C) 2012-2017 CypherCore + * + * 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, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +using Framework.Constants; +using Game.AI; +using Game.Entities; +using Game.Maps; +using Game.Scripting; +using Game.Spells; +using System; +using System.Collections.Generic; +using System.Linq; + +namespace Scripts.Northrend.AzjolNerub.AzjolNerub.KrikthirTheGatewatcher +{ + struct SpellIds + { + // Krik'Thir The Gatewatcher + public const uint SubbossAggroTrigger = 52343; + public const uint Swarm = 52440; + public const uint MindFlay = 52586; + public const uint CurseOfFatigue = 52592; + public const uint Frenzy = 28747; + + // Watchers - Shared + public const uint WebWrap = 52086; + public const uint WebWrapWrapped = 52087; + public const uint InfectedBite = 52469; + + // Watcher Gashra + public const uint Enrage = 52470; + // Watcher Narjil + public const uint BlindingWebs = 52524; + // Watcher Silthik + public const uint PoisonSpray = 52493; + + // Anub'Ar Warrior + public const uint Cleave = 49806; + public const uint Strike = 52532; + + // Anub'Ar Skirmisher + public const uint Charge = 52538; + public const uint Backstab = 52540; + public const uint FixtateTrigger = 52536; + public const uint FixtateTriggered = 52537; + + // Anub'Ar Shadowcaster + public const uint ShadowBolt = 52534; + public const uint ShadowNova = 52535; + + // Skittering Infector + public const uint AcidSplash = 52446; + } + + struct Misc + { + public const uint DataPetGroup = 0; + + // Krik'thir the Gatewatcher + public const uint EventSendGroup = 1; + public const uint EventSwarm = 2; + public const uint EventMindFlay = 3; + public const uint EventFrenzy = 4; + } + + struct ActionIds + { + public const int GashraDied = 0; + public const int NarjilDied = 1; + public const int SilthikDied = 2; + public const int WatcherEngaged = 3; + public const int PetEngaged = 4; + public const int PetEvade = 5; + } + + struct TextIds + { + public const uint SayAggro = 0; + public const uint SaySlay = 1; + public const uint SayDeath = 2; + public const uint SaySwarm = 3; + public const uint SayPrefight = 4; + public const uint SaySendGroup = 5; + } + + [Script] + class boss_krik_thir : CreatureScript + { + public boss_krik_thir() : base("boss_krik_thir") { } + + class boss_krik_thirAI : BossAI + { + public boss_krik_thirAI(Creature creature) : base(creature, ANDataTypes.KrikthirTheGatewatcher) { } + + void SummonAdds() + { + if (instance.GetBossState(ANDataTypes.KrikthirTheGatewatcher) == EncounterState.Done) + return; + + for (byte i = 1; i <= 3; ++i) + { + List summons; + me.SummonCreatureGroup(i, out summons); + + foreach (TempSummon summon in summons) + summon.GetAI().SetData(Misc.DataPetGroup, i); + } + } + + public override void Reset() + { + base.Reset(); + _hadFrenzy = false; + _petsInCombat = false; + _watchersActive = 0; + me.SetReactState(ReactStates.Passive); + } + + public override void InitializeAI() + { + base.InitializeAI(); + SummonAdds(); + } + + public override void JustRespawned() + { + base.JustRespawned(); + SummonAdds(); + } + + public override void KilledUnit(Unit victim) + { + if (victim.IsTypeId(TypeId.Player)) + Talk(TextIds.SaySlay); + } + + public override void JustDied(Unit killer) + { + summons.Clear(); + base.JustDied(killer); + Talk(TextIds.SayDeath); + } + + public override void EnterCombat(Unit who) + { + _petsInCombat = false; + me.SetReactState(ReactStates.Aggressive); + summons.DoZoneInCombat(); + + _events.CancelEvent(Misc.EventSendGroup); + _events.ScheduleEvent(Misc.EventSwarm, TimeSpan.FromSeconds(5)); + _events.ScheduleEvent(Misc.EventMindFlay, TimeSpan.FromSeconds(1), TimeSpan.FromSeconds(3)); + + base.EnterCombat(who); + } + + public override void MoveInLineOfSight(Unit who) + { + if (!me.HasReactState(ReactStates.Passive)) + { + base.MoveInLineOfSight(who); + return; + } + + if (me.CanStartAttack(who, false) && me.IsWithinDistInMap(who, me.GetAttackDistance(who) + me.m_CombatDistance)) + EnterCombat(who); + } + + public override void EnterEvadeMode(EvadeReason why) + { + summons.DespawnAll(); + _DespawnAtEvade(); + } + + public override void DoAction(int action) + { + switch (action) + { + case -ANInstanceMisc.ActionGatewatcherGreet: + if (!_hadGreet && me.IsAlive() && !me.IsInCombat() && !_petsInCombat) + { + _hadGreet = true; + Talk(TextIds.SayPrefight); + } + break; + case ActionIds.GashraDied: + case ActionIds.NarjilDied: + case ActionIds.SilthikDied: + if (_watchersActive == 0) // something is wrong + { + EnterEvadeMode(EvadeReason.Other); + return; + } + if ((--_watchersActive) == 0) // if there are no watchers currently in combat... + _events.RescheduleEvent(Misc.EventSendGroup, TimeSpan.FromSeconds(5)); // ...send the next watcher after the targets sooner + break; + case ActionIds.WatcherEngaged: + ++_watchersActive; + break; + case ActionIds.PetEngaged: + if (_petsInCombat || me.IsInCombat()) + break; + _petsInCombat = true; + Talk(TextIds.SayAggro); + _events.ScheduleEvent(Misc.EventSendGroup, TimeSpan.FromSeconds(70)); + break; + case ActionIds.PetEvade: + EnterEvadeMode(EvadeReason.Other); + break; + } + } + + public override void UpdateAI(uint diff) + { + if (!UpdateVictim() && !_petsInCombat) + return; + + _events.Update(diff); + + if (me.HasUnitState(UnitState.Casting)) + return; + + if (me.HealthBelowPct(10) && !_hadFrenzy) + { + _hadFrenzy = true; + _events.ScheduleEvent(Misc.EventFrenzy, TimeSpan.FromSeconds(1)); + } + + _events.ExecuteEvents(eventId => + { + switch (eventId) + { + case Misc.EventSendGroup: + DoCastAOE(SpellIds.SubbossAggroTrigger, true); + _events.Repeat(TimeSpan.FromSeconds(70)); + break; + case Misc.EventSwarm: + DoCastAOE(SpellIds.Swarm); + Talk(TextIds.SaySwarm); + break; + case Misc.EventMindFlay: + DoCastVictim(SpellIds.MindFlay); + _events.Repeat(TimeSpan.FromSeconds(9), TimeSpan.FromSeconds(11)); + break; + case Misc.EventFrenzy: + DoCastSelf(SpellIds.Frenzy); + DoCastAOE(SpellIds.CurseOfFatigue); + _events.Repeat(TimeSpan.FromSeconds(15)); + break; + } + + if (me.HasUnitState(UnitState.Casting)) + return; + }); + + DoMeleeAttackIfReady(); + } + + public override void SpellHit(Unit whose, SpellInfo spell) + { + if (spell.Id == SpellIds.SubbossAggroTrigger) + DoZoneInCombat(); + } + + public override void SpellHitTarget(Unit who, SpellInfo spell) + { + if (spell.Id == SpellIds.SubbossAggroTrigger) + Talk(TextIds.SaySendGroup); + } + + bool _hadGreet; + bool _hadFrenzy; + bool _petsInCombat; + byte _watchersActive; + } + + public override CreatureAI GetAI(Creature creature) + { + return GetInstanceAI(creature); + } + } + + class npc_gatewatcher_petAI : ScriptedAI + { + public npc_gatewatcher_petAI(Creature creature, bool isWatcher) : base(creature) + { + _instance = creature.GetInstanceScript(); + _isWatcher = isWatcher; + } + + public virtual void _EnterCombat() { } + + public override void EnterCombat(Unit who) + { + if (_isWatcher) + { + _isWatcher = false; + + TempSummon meSummon = me.ToTempSummon(); + if (meSummon) + { + Creature summoner = meSummon.GetSummonerCreatureBase(); + if (summoner) + summoner.GetAI().DoAction(ActionIds.WatcherEngaged); + } + } + + if (me.HasReactState(ReactStates.Passive)) + { + List others = new List(); + me.GetCreatureListWithEntryInGrid(others, 0, 40.0f); + foreach (Creature other in others) + { + if (other.GetAI().GetData(Misc.DataPetGroup) == _petGroup) + { + other.SetReactState(ReactStates.Aggressive); + other.GetAI().AttackStart(who); + } + } + + TempSummon meSummon = me.ToTempSummon(); + if (meSummon) + { + Creature summoner = meSummon.GetSummonerCreatureBase(); + if (summoner) + summoner.GetAI().DoAction(ActionIds.PetEngaged); + } + } + _EnterCombat(); + base.EnterCombat(who); + } + + public override void SetData(uint data, uint value) + { + if (data == Misc.DataPetGroup) + { + _petGroup = value; + me.SetReactState(_petGroup != 0 ? ReactStates.Passive : ReactStates.Aggressive); + } + } + + public override uint GetData(uint data) + { + if (data == Misc.DataPetGroup) + return _petGroup; + return 0; + } + + public override void MoveInLineOfSight(Unit who) + { + if (!me.HasReactState(ReactStates.Passive)) + { + base.MoveInLineOfSight(who); + return; + } + + if (me.CanStartAttack(who, false) && me.IsWithinDistInMap(who, me.GetAttackDistance(who) + me.m_CombatDistance)) + EnterCombat(who); + } + + public override void SpellHit(Unit whose, SpellInfo spell) + { + if (spell.Id == SpellIds.SubbossAggroTrigger) + DoZoneInCombat(); + } + + public override void EnterEvadeMode(EvadeReason why) + { + TempSummon meSummon = me.ToTempSummon(); + if (meSummon) + { + Creature summoner = meSummon.GetSummonerCreatureBase(); + if (summoner) + summoner.GetAI().DoAction(ActionIds.PetEvade); + else + me.DespawnOrUnsummon(); + return; + } + base.EnterEvadeMode(why); + } + + protected InstanceScript _instance; + uint _petGroup; + bool _isWatcher; + } + + [Script] + class npc_watcher_gashra : CreatureScript + { + public npc_watcher_gashra() : base("npc_watcher_gashra") { } + + class npc_watcher_gashraAI : npc_gatewatcher_petAI + { + public npc_watcher_gashraAI(Creature creature) : base(creature, true) + { + _instance = creature.GetInstanceScript(); + me.SetReactState(ReactStates.Passive); + } + + public override void Reset() + { + _scheduler.CancelAll(); + } + + public override void _EnterCombat() + { + _scheduler.SetValidator(() => !me.HasUnitState(UnitState.Casting)); + + _scheduler.Schedule(TimeSpan.FromSeconds(3), TimeSpan.FromSeconds(5), task => + { + DoCastSelf(SpellIds.Enrage); + task.Repeat(TimeSpan.FromSeconds(12), TimeSpan.FromSeconds(20)); + }); + + _scheduler.Schedule(TimeSpan.FromSeconds(16), TimeSpan.FromSeconds(19), task => + { + Unit target = SelectTarget(SelectAggroTarget.Random, 0, 100.0f); + if (target) + DoCast(target, SpellIds.WebWrap); + task.Repeat(TimeSpan.FromSeconds(13), TimeSpan.FromSeconds(19)); + }); + + _scheduler.Schedule(TimeSpan.FromSeconds(7), TimeSpan.FromSeconds(11), task => + { + DoCastVictim(SpellIds.InfectedBite); + task.Repeat(TimeSpan.FromSeconds(23), TimeSpan.FromSeconds(27)); + }); + } + + public override void JustDied(Unit killer) + { + Creature krikthir = _instance.GetCreature(ANDataTypes.KrikthirTheGatewatcher); + if (krikthir && krikthir.IsAlive()) + krikthir.GetAI().DoAction(ActionIds.GashraDied); + } + + public override void UpdateAI(uint diff) + { + if (!UpdateVictim()) + return; + + _scheduler.Update(diff); + + if (me.HasUnitState(UnitState.Casting)) + return; + + DoMeleeAttackIfReady(); + } + } + + public override CreatureAI GetAI(Creature creature) + { + return GetInstanceAI(creature); + } + } + + [Script] + class npc_watcher_narjil : CreatureScript + { + public npc_watcher_narjil() : base("npc_watcher_narjil") { } + + class npc_watcher_narjilAI : npc_gatewatcher_petAI + { + public npc_watcher_narjilAI(Creature creature) : base(creature, true) + { + _instance = creature.GetInstanceScript(); + } + + public override void Reset() + { + _scheduler.CancelAll(); + } + + public override void _EnterCombat() + { + _scheduler.SetValidator(() => !me.HasUnitState(UnitState.Casting)); + + _scheduler.Schedule(TimeSpan.FromSeconds(13), TimeSpan.FromSeconds(18), task => + { + DoCastVictim(SpellIds.BlindingWebs); + task.Repeat(TimeSpan.FromSeconds(23), TimeSpan.FromSeconds(27)); + }); + + _scheduler.Schedule(TimeSpan.FromSeconds(3), TimeSpan.FromSeconds(5), task => + { + Unit target = SelectTarget(SelectAggroTarget.Random, 0, 100, true); + if (target) + DoCast(target, SpellIds.WebWrap); + task.Repeat(TimeSpan.FromSeconds(13), TimeSpan.FromSeconds(19)); + }); + + _scheduler.Schedule(TimeSpan.FromSeconds(7), TimeSpan.FromSeconds(11), task => + { + DoCastVictim(SpellIds.InfectedBite); + task.Repeat(TimeSpan.FromSeconds(20), TimeSpan.FromSeconds(25)); + }); + } + + public override void JustDied(Unit killer) + { + Creature krikthir = _instance.GetCreature(ANDataTypes.KrikthirTheGatewatcher); + if (krikthir && krikthir.IsAlive()) + krikthir.GetAI().DoAction(ActionIds.NarjilDied); + } + + public override void UpdateAI(uint diff) + { + if (!UpdateVictim()) + return; + + _scheduler.Update(diff); + + if (me.HasUnitState(UnitState.Casting)) + return; + + DoMeleeAttackIfReady(); + } + } + + public override CreatureAI GetAI(Creature creature) + { + return GetInstanceAI(creature); + } + } + + [Script] + class npc_watcher_silthik : CreatureScript + { + public npc_watcher_silthik() : base("npc_watcher_silthik") { } + + class npc_watcher_silthikAI : npc_gatewatcher_petAI + { + public npc_watcher_silthikAI(Creature creature) : base(creature, true) + { + _instance = creature.GetInstanceScript(); + } + + public override void Reset() + { + _scheduler.CancelAll(); + } + + public override void _EnterCombat() + { + _scheduler.SetValidator(() => !me.HasUnitState(UnitState.Casting)); + + _scheduler.Schedule(TimeSpan.FromSeconds(16), TimeSpan.FromSeconds(19), task => + { + DoCastVictim(SpellIds.PoisonSpray); + task.Repeat(TimeSpan.FromSeconds(13), TimeSpan.FromSeconds(19)); + }); + + _scheduler.Schedule(TimeSpan.FromSeconds(7), TimeSpan.FromSeconds(11), task => + { + Unit target = SelectTarget(SelectAggroTarget.Random, 0, 100, true); + if (target) + DoCast(target, SpellIds.WebWrap); + task.Repeat(TimeSpan.FromSeconds(13), TimeSpan.FromSeconds(17)); + }); + + _scheduler.Schedule(TimeSpan.FromSeconds(3), TimeSpan.FromSeconds(5), task => + { + DoCastVictim(SpellIds.InfectedBite); + task.Repeat(TimeSpan.FromSeconds(20), TimeSpan.FromSeconds(24)); + }); + } + + public override void JustDied(Unit killer) + { + Creature krikthir = _instance.GetCreature(ANDataTypes.KrikthirTheGatewatcher); + if (krikthir && krikthir.IsAlive()) + krikthir.GetAI().DoAction(ActionIds.SilthikDied); + } + + public override void UpdateAI(uint diff) + { + if (!UpdateVictim()) + return; + + _scheduler.Update(diff); + + if (me.HasUnitState(UnitState.Casting)) + return; + + DoMeleeAttackIfReady(); + } + } + + public override CreatureAI GetAI(Creature creature) + { + return GetInstanceAI(creature); + } + } + + [Script] + class npc_anub_ar_warrior : CreatureScript + { + public npc_anub_ar_warrior() : base("npc_anub_ar_warrior") { } + + class npc_anub_ar_warriorAI : npc_gatewatcher_petAI + { + public npc_anub_ar_warriorAI(Creature creature) : base(creature, false) { } + + public override void Reset() + { + _scheduler.CancelAll(); + } + + public override void _EnterCombat() + { + _scheduler.SetValidator(() => !me.HasUnitState(UnitState.Casting)); + + _scheduler.Schedule(TimeSpan.FromSeconds(7), TimeSpan.FromSeconds(9), task => + { + DoCastVictim(SpellIds.Cleave); + task.Repeat(TimeSpan.FromSeconds(10), TimeSpan.FromSeconds(16)); + }); + + _scheduler.Schedule(TimeSpan.FromSeconds(5), TimeSpan.FromSeconds(10), task => + { + DoCastVictim(SpellIds.Strike); + task.Repeat(TimeSpan.FromSeconds(15), TimeSpan.FromSeconds(19)); + }); + } + + public override void UpdateAI(uint diff) + { + if (!UpdateVictim()) + return; + + _scheduler.Update(diff); + + if (me.HasUnitState(UnitState.Casting)) + return; + + DoMeleeAttackIfReady(); + } + } + + public override CreatureAI GetAI(Creature creature) + { + return GetInstanceAI(creature); + } + } + + [Script] + class npc_anub_ar_skirmisher : CreatureScript + { + public npc_anub_ar_skirmisher() : base("npc_anub_ar_skirmisher") { } + + class npc_anub_ar_skirmisherAI : npc_gatewatcher_petAI + { + public npc_anub_ar_skirmisherAI(Creature creature) : base(creature, false) { } + + public override void Reset() + { + _scheduler.CancelAll(); + } + + public override void _EnterCombat() + { + _scheduler.SetValidator(() => !me.HasUnitState(UnitState.Casting)); + + _scheduler.Schedule(TimeSpan.FromSeconds(6), TimeSpan.FromSeconds(8), task => + { + Unit target = SelectTarget(SelectAggroTarget.Random, 0, 100.0f, true); + if (target) + DoCast(target, SpellIds.Charge); + task.Repeat(TimeSpan.FromSeconds(20), TimeSpan.FromSeconds(25)); + }); + + _scheduler.Schedule(TimeSpan.FromSeconds(7), TimeSpan.FromSeconds(9), task => + { + if (me.GetVictim() && me.GetVictim().isInBack(me)) + DoCastVictim(SpellIds.Backstab); + task.Repeat(TimeSpan.FromSeconds(10), TimeSpan.FromSeconds(13)); + }); + } + + public override void UpdateAI(uint diff) + { + if (!UpdateVictim()) + return; + + _scheduler.Update(diff); + + if (me.HasUnitState(UnitState.Casting)) + return; + + DoMeleeAttackIfReady(); + } + + public override void SpellHitTarget(Unit target, SpellInfo spell) + { + if (spell.Id == SpellIds.Charge && target) + DoCast(target, SpellIds.FixtateTrigger); + } + } + + public override CreatureAI GetAI(Creature creature) + { + return GetInstanceAI(creature); + } + } + + [Script] + class npc_anub_ar_shadowcaster : CreatureScript + { + public npc_anub_ar_shadowcaster() : base("npc_anub_ar_shadowcaster") { } + + class npc_anub_ar_shadowcasterAI : npc_gatewatcher_petAI + { + public npc_anub_ar_shadowcasterAI(Creature creature) : base(creature, false) { } + + public override void Reset() + { + _scheduler.CancelAll(); + } + + public override void _EnterCombat() + { + _scheduler.SetValidator(() => !me.HasUnitState(UnitState.Casting)); + + _scheduler.Schedule(TimeSpan.FromSeconds(4), task => + { + Unit target = SelectTarget(SelectAggroTarget.Random, 0, 100.0f, true); + if (target) + DoCast(target, SpellIds.ShadowBolt); + task.Repeat(TimeSpan.FromSeconds(2), TimeSpan.FromSeconds(4)); + }); + + _scheduler.Schedule(TimeSpan.FromSeconds(10), TimeSpan.FromSeconds(14), task => + { + DoCastVictim(SpellIds.ShadowNova); + task.Repeat(TimeSpan.FromSeconds(10), TimeSpan.FromSeconds(16)); + }); + } + + public override void UpdateAI(uint diff) + { + if (!UpdateVictim()) + return; + + _scheduler.Update(diff); + + if (me.HasUnitState(UnitState.Casting)) + return; + + DoMeleeAttackIfReady(); + } + } + + public override CreatureAI GetAI(Creature creature) + { + return GetInstanceAI(creature); + } + } + + [Script] + class npc_skittering_swarmer : CreatureScript + { + public npc_skittering_swarmer() : base("npc_skittering_swarmer") { } + + class npc_skittering_swarmerAI : ScriptedAI + { + public npc_skittering_swarmerAI(Creature creature) : base(creature) { } + + public override void InitializeAI() + { + base.InitializeAI(); + Creature gatewatcher = me.GetInstanceScript().GetCreature(ANDataTypes.KrikthirTheGatewatcher); + if (gatewatcher) + { + Unit target = gatewatcher.getAttackerForHelper(); + if (target) + AttackStart(target); + gatewatcher.GetAI().JustSummoned(me); + } + } + } + + public override CreatureAI GetAI(Creature creature) + { + return GetInstanceAI(creature); + } + } + + [Script] + class npc_skittering_infector : CreatureScript + { + public npc_skittering_infector() : base("npc_skittering_infector") { } + + class npc_skittering_infectorAI : ScriptedAI + { + public npc_skittering_infectorAI(Creature creature) : base(creature) { } + + public override void InitializeAI() + { + base.InitializeAI(); + Creature gatewatcher = me.GetInstanceScript().GetCreature(ANDataTypes.KrikthirTheGatewatcher); + if (gatewatcher) + { + Unit target = gatewatcher.getAttackerForHelper(); + if (target) + AttackStart(target); + gatewatcher.GetAI().JustSummoned(me); + } + } + + public override void JustDied(Unit killer) + { + DoCastAOE(SpellIds.AcidSplash); + base.JustDied(killer); + } + } + + public override CreatureAI GetAI(Creature creature) + { + return GetInstanceAI(creature); + } + } + + [Script] + class npc_gatewatcher_web_wrap : CreatureScript + { + public npc_gatewatcher_web_wrap() : base("npc_gatewatcher_web_wrap") { } + + class npc_gatewatcher_web_wrapAI : NullCreatureAI + { + public npc_gatewatcher_web_wrapAI(Creature creature) : base(creature) { } + + public override void JustDied(Unit killer) + { + TempSummon meSummon = me.ToTempSummon(); + if (meSummon) + { + Unit summoner = meSummon.GetSummoner(); + if (summoner) + summoner.RemoveAurasDueToSpell(SpellIds.WebWrapWrapped); + } + } + } + + public override CreatureAI GetAI(Creature creature) + { + return GetInstanceAI(creature); + } + } + + [Script] + class spell_gatewatcher_subboss_trigger : SpellScriptLoader + { + public spell_gatewatcher_subboss_trigger() : base("spell_gatewatcher_subboss_trigger") { } + + class spell_gatewatcher_subboss_trigger_SpellScript : SpellScript + { + void HandleTargets(List targetList) + { + // Remove any Watchers that are already in combat + foreach (var obj in targetList.ToList()) + { + Creature creature = obj.ToCreature(); + if (creature) + if (creature.IsAlive() && !creature.IsInCombat()) + continue; + + targetList.Remove(obj); + } + + // Default to Krik'thir himself if he isn't engaged + WorldObject target = null; + if (GetCaster() && !GetCaster().IsInCombat()) + target = GetCaster(); + // Unless there are Watchers that aren't engaged yet + if (!targetList.Empty()) + { + // If there are, pick one of them at random + target = targetList.PickRandom(); + } + // And hit only that one + targetList.Clear(); + if (target) + targetList.Add(target); + } + + public override void Register() + { + OnObjectAreaTargetSelect.Add(new ObjectAreaTargetSelectHandler(HandleTargets, 0, Targets.UnitSrcAreaEntry)); + } + } + + public override SpellScript GetSpellScript() + { + return new spell_gatewatcher_subboss_trigger_SpellScript(); + } + } + + [Script] + class spell_anub_ar_skirmisher_fixtate : SpellScriptLoader + { + public spell_anub_ar_skirmisher_fixtate() : base("spell_anub_ar_skirmisher_fixtate") { } + + class spell_anub_ar_skirmisher_fixtate_SpellScript : SpellScript + { + public override bool Validate(SpellInfo spell) + { + return ValidateSpellInfo(SpellIds.FixtateTriggered); + } + + void HandleScript(uint effIndex) + { + Unit target = GetHitUnit(); + if (target) + target.CastSpell(GetCaster(), SpellIds.FixtateTriggered, true); + } + + public override void Register() + { + OnEffectHitTarget.Add(new EffectHandler(HandleScript, 0, SpellEffectName.ScriptEffect)); + } + } + + public override SpellScript GetSpellScript() + { + return new spell_anub_ar_skirmisher_fixtate_SpellScript(); + } + } + + [Script] + class spell_gatewatcher_web_wrap : SpellScriptLoader + { + public spell_gatewatcher_web_wrap() : base("spell_gatewatcher_web_wrap") { } + + class spell_gatewatcher_web_wrap_AuraScript : AuraScript + { + public override bool Validate(SpellInfo spell) + { + return ValidateSpellInfo(SpellIds.WebWrapWrapped); + } + + void HandleEffectRemove(AuraEffect aurEff, AuraEffectHandleModes mode) + { + if (GetTargetApplication().GetRemoveMode() != AuraRemoveMode.Expire) + return; + + Unit target = GetTarget(); + if (target) + target.CastSpell(target, SpellIds.WebWrapWrapped, true); + } + + public override void Register() + { + OnEffectRemove.Add(new EffectApplyHandler(HandleEffectRemove, 0, AuraType.ModRoot, AuraEffectHandleModes.Real)); + } + } + + public override AuraScript GetAuraScript() + { + return new spell_gatewatcher_web_wrap_AuraScript(); + } + } + + [Script] + class achievement_watch_him_die : AchievementCriteriaScript + { + public achievement_watch_him_die() : base("achievement_watch_him_die") { } + + public override bool OnCheck(Player player, Unit target) + { + if (!target) + return false; + + InstanceScript instance = target.GetInstanceScript(); + if (instance == null) + return false; + + foreach (uint watcherData in new[] { ANDataTypes.WatcherGashra, ANDataTypes.WatcherNarjil, ANDataTypes.WatcherSilthik }) + { + Creature watcher = instance.GetCreature(watcherData); + if (watcher) + if (watcher.IsAlive()) + continue; + return false; + } + + return true; + } + } +} diff --git a/Scripts/Northrend/AzjolNerub/AzjolNerub/BossNadronox.cs b/Scripts/Northrend/AzjolNerub/AzjolNerub/BossNadronox.cs new file mode 100644 index 000000000..e8583e70e --- /dev/null +++ b/Scripts/Northrend/AzjolNerub/AzjolNerub/BossNadronox.cs @@ -0,0 +1,1086 @@ +/* + * Copyright (C) 2012-2017 CypherCore + * + * 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, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +using Framework.Constants; +using Game.AI; +using Game.Combat; +using Game.Entities; +using Game.Maps; +using Game.Scripting; +using Game.Spells; +using System; +using System.Collections.Generic; + +namespace Scripts.Northrend.AzjolNerub.AzjolNerub.Nadronox +{ + struct SpellIds + { + // Hadronox + public const uint WebFrontDoors = 53177; + public const uint WebSideDoors = 53185; + public const uint LeechPoison = 53030; + public const uint LeechPoisonHeal = 53800; + public const uint AcidCloud = 53400; + public const uint WebGrab = 57731; + public const uint PierceArmor = 53418; + + // Anub'Ar Opponent Summoning Spells + public const uint SummonChampionPeriodic = 53035; + public const uint SummonCryptFiendPeriodic = 53037; + public const uint SummonNecromancerPeriodic = 53036; + public const uint SummonChampionTop = 53064; + public const uint SummonCryptFiendTop = 53065; + public const uint SummonNecromancerTop = 53066; + public const uint SummonChampionBottom = 53090; + public const uint SummonCryptFiendBottom = 53091; + public const uint SummonNecromancerBottom = 53092; + + // Anub'Ar Crusher + public const uint Smash = 53318; + public const uint Frenzy = 53801; + + // Anub'Ar Foes - Shared + public const uint Taunt = 53798; + + // Anub'Ar Champion + public const uint Rend = 59343; + public const uint Pummel = 59344; + + // Anub'Ar Crypt Guard + public const uint CrushingWebs = 59347; + public const uint InfectedWound = 59348; + + // Anub'Ar Necromancer + public const uint ShadowBolt = 53333; + public const uint AnimateBones1 = 53334; + public const uint AnimateBones2 = 53336; + } + + enum SummonGroups + { + Crusher1 = 1, + Crusher2 = 2, + Crusher3 = 3 + } + + struct ActionIds + { + public const int HadronoxMove = 1; + public const int CrusherEngaged = 2; + public const int PackWalk = 3; + } + + struct Data + { + public const uint CrusherPackId = 1; + public const uint HadronoxEnteredCombat = 2; + public const uint HadronoxWebbedDoors = 3; + } + + struct CreatureIds + { + public const uint Crusher = 28922; + public const uint WorldtriggerLarge = 23472; + } + + struct TextIds + { + public const uint SayCrusherAggro = 1; + public const uint EmoteCrusherFrenzy = 2; + public const uint EmoteHadronoxMove = 1; + } + + // Movement IDs used by the permanently spawning Anub'ar opponents - they are done in sequence, as one finishes, the next one starts + enum MovementIds + { + None = 0, + Outside, + Downstairs, + Downstairs2, + Hadronox, // this one might have us take a detour to avoid pathfinding "through" the floor... + HadronoxReal // while this one will always make us movechase + } + + struct Misc + { + public static Position[] hadronoxStep = + { + new Position(515.5848f, 544.2007f, 673.6272f), + new Position(562.191f , 514.068f , 696.4448f), + new Position(610.3828f, 518.6407f, 695.9385f), + new Position(530.42f , 560.003f, 733.0308f) + }; + + public static Position[] crusherWaypoints = + { + new Position(529.6913f, 547.1257f, 731.9155f, 4.799650f), + new Position(517.51f , 561.439f , 734.0306f, 4.520403f), + new Position(543.414f , 551.728f , 732.0522f, 3.996804f) + }; + + public static Position[] championWaypoints = + { + new Position(539.2076f, 549.7539f, 732.8668f, 4.55531f), + new Position(527.3098f, 559.5197f, 732.9407f, 4.742493f), + new Position() + }; + + public static Position[] cryptFiendWaypoints = + { + new Position(520.3911f, 548.7895f, 732.0118f, 5.0091f), + new Position(), + new Position(550.9611f, 545.1674f, 731.9031f, 3.996804f) + }; + + public static Position[] necromancerWaypoints = + { + new Position(), + new Position(507.6937f, 563.3471f, 734.8986f, 4.520403f), + new Position(535.1049f, 552.8961f, 732.8441f, 3.996804f), + }; + + public static Position[] initialMoves = + { + new Position(485.314606f, 611.418640f, 771.428406f), + new Position(575.760437f, 611.516418f, 771.427368f), + new Position(588.930725f, 598.233276f, 739.142151f) + }; + + public static Position[] downstairsMoves = + { + new Position(513.574341f, 587.022156f, 736.229065f), + new Position(537.920410f, 580.436157f, 732.796692f), + new Position(601.289246f, 583.259644f, 725.443054f), + }; + + public static Position[] downstairsMoves2 = + { + new Position(571.498718f, 576.978333f, 727.582947f), + new Position(571.498718f, 576.978333f, 727.582947f), + new Position() + }; + } + + [Script] + class boss_hadronox : CreatureScript + { + public boss_hadronox() : base("boss_hadronox") { } + + class boss_hadronoxAI : BossAI + { + public boss_hadronoxAI(Creature creature) : base(creature, ANDataTypes.Hadronox) { } + + bool IsInCombatWithPlayer() + { + List refs = me.GetThreatManager().getThreatList(); + foreach (HostileReference hostileRef in refs) + { + Unit target = hostileRef.getTarget(); + if (target) + if (target.IsControlledByPlayer()) + return true; + } + return false; + } + + void SetStep(byte step) + { + if (_lastPlayerCombatState) + return; + + _step = step; + me.SetHomePosition(Misc.hadronoxStep[step]); + me.GetMotionMaster().Clear(); + me.AttackStop(); + SetCombatMovement(false); + me.GetMotionMaster().MovePoint(0, Misc.hadronoxStep[step]); + } + + void SummonCrusherPack(SummonGroups group) + { + List summoned; + me.SummonCreatureGroup((byte)group, out summoned); + foreach (TempSummon summon in summoned) + { + summon.GetAI().SetData(Data.CrusherPackId, (uint)group); + summon.GetAI().DoAction(ActionIds.PackWalk); + } + } + + public override void MovementInform(MovementGeneratorType type, uint id) + { + if (type != MovementGeneratorType.Point) + return; + SetCombatMovement(true); + AttackStart(me.GetVictim()); + if (_step < Misc.hadronoxStep.Length - 1) + return; + DoCastAOE(SpellIds.WebFrontDoors); + DoCastAOE(SpellIds.WebSideDoors); + _doorsWebbed = true; + DoZoneInCombat(); + } + + public override uint GetData(uint data) + { + if (data == Data.HadronoxEnteredCombat) + return _enteredCombat ? 1 : 0u; + if (data == Data.HadronoxWebbedDoors) + return _doorsWebbed ? 1 : 0u; + return 0; + } + + public override bool CanAIAttack(Unit target) + { + // Prevent Hadronox from going too far from her current home position + if (!target.IsControlledByPlayer() && target.GetDistance(me.GetHomePosition()) > 20.0f) + return false; + return base.CanAIAttack(target); + } + + public override void EnterCombat(Unit who) + { + _scheduler.CancelAll(); + _scheduler.SetValidator(() => !me.HasUnitState(UnitState.Casting)); + + _scheduler.Schedule(TimeSpan.FromSeconds(5), TimeSpan.FromSeconds(7), task => + { + DoCastAOE(SpellIds.LeechPoison); + task.Repeat(TimeSpan.FromSeconds(7), TimeSpan.FromSeconds(9)); + }); + + _scheduler.Schedule(TimeSpan.FromSeconds(7), TimeSpan.FromSeconds(13), task => + { + Unit target = SelectTarget(SelectAggroTarget.Random, 0, 100.0f); + if (target) + DoCast(target, SpellIds.AcidCloud); + task.Repeat(TimeSpan.FromSeconds(16), TimeSpan.FromSeconds(23)); + }); + + _scheduler.Schedule(TimeSpan.FromSeconds(13), TimeSpan.FromSeconds(19), task => + { + DoCastAOE(SpellIds.WebGrab); + task.Repeat(TimeSpan.FromSeconds(20), TimeSpan.FromSeconds(25)); + }); + + _scheduler.Schedule(TimeSpan.FromSeconds(4), TimeSpan.FromSeconds(7), task => + { + DoCastVictim(SpellIds.PierceArmor); + task.Repeat(TimeSpan.FromSeconds(10), TimeSpan.FromSeconds(15)); + }); + + _scheduler.Schedule(TimeSpan.FromSeconds(1), task => + { + if (IsInCombatWithPlayer() != _lastPlayerCombatState) + { + _lastPlayerCombatState = !_lastPlayerCombatState; + if (_lastPlayerCombatState) // we are now in combat with players + { + if (!instance.CheckRequiredBosses(ANDataTypes.Hadronox)) + { + EnterEvadeMode(EvadeReason.SequenceBreak); + return; + } + // cancel current point movement if engaged by players + if (me.GetMotionMaster().GetCurrentMovementGeneratorType() == MovementGeneratorType.Point) + { + me.GetMotionMaster().Clear(); + SetCombatMovement(true); + AttackStart(me.GetVictim()); + } + } + else // we are no longer in combat with players - reset the encounter + EnterEvadeMode(EvadeReason.NoHostiles); + } + task.Repeat(TimeSpan.FromSeconds(1)); + }); + + me.setActive(true); + } + + public override void DoAction(int action) + { + switch (action) + { + case ActionIds.CrusherEngaged: + if (_enteredCombat) + break; + instance.SetBossState(ANDataTypes.Hadronox, EncounterState.InProgress); + _enteredCombat = true; + SummonCrusherPack(SummonGroups.Crusher2); + SummonCrusherPack(SummonGroups.Crusher3); + break; + case ActionIds.HadronoxMove: + if (_step < Misc.hadronoxStep.Length - 1) + { + SetStep((byte)(_step + 1)); + Talk(TextIds.EmoteHadronoxMove); + } + break; + } + } + + public override void EnterEvadeMode(EvadeReason why) + { + List triggers = new List(); + me.GetCreatureListWithEntryInGrid(triggers, CreatureIds.WorldtriggerLarge); + foreach (Creature trigger in triggers) + { + if (trigger.HasAura(SpellIds.SummonChampionPeriodic) || trigger.HasAura(SpellIds.WebFrontDoors) || trigger.HasAura(SpellIds.WebSideDoors)) + _DespawnAtEvade(25, trigger); + } + _DespawnAtEvade(25); + summons.DespawnAll(); + foreach (ObjectGuid gNerubian in _anubar) + { + Creature nerubian = ObjectAccessor.GetCreature(me, gNerubian); + if (nerubian) + nerubian.DespawnOrUnsummon(); + } + } + + public override void SetGUID(ObjectGuid guid, int what) + { + _anubar.Add(guid); + } + + public void Initialize() + { + me.SetFloatValue(UnitFields.BoundingRadius, 9.0f); + me.SetFloatValue(UnitFields.CombatReach, 9.0f); + _enteredCombat = false; + _doorsWebbed = false; + _lastPlayerCombatState = false; + SetStep(0); + SetCombatMovement(true); + SummonCrusherPack(SummonGroups.Crusher1); + } + + public override void InitializeAI() + { + base.InitializeAI(); + if (me.IsAlive()) + Initialize(); + } + + public override void JustRespawned() + { + base.JustRespawned(); + Initialize(); + } + + public override void UpdateAI(uint diff) + { + if (!UpdateVictim()) + return; + + _scheduler.Update(diff); + + if (me.HasUnitState(UnitState.Casting)) + return; + + DoMeleeAttackIfReady(); + } + + // Safeguard to prevent Hadronox dying to NPCs + public override void DamageTaken(Unit who, ref uint damage) + { + if (!who.IsControlledByPlayer() && me.HealthBelowPct(70)) + { + if (me.HealthBelowPctDamaged(5, damage)) + damage = 0; + else + damage *= (uint)((me.GetHealthPct() - 5.0f) / 65.0f); + } + } + + public override void JustSummoned(Creature summon) + { + summons.Summon(summon); + // Do not enter combat with zone + } + + bool _enteredCombat; // has a player entered combat with the first crusher pack? (talk and spawn two more packs) + bool _doorsWebbed; // obvious - have we reached the top and webbed the doors shut? (trigger for hadronox denied achievement) + bool _lastPlayerCombatState; // was there a player in our threat list the last time we checked (we check every second) + byte _step; + List _anubar = new List(); + } + + public override CreatureAI GetAI(Creature creature) + { + return GetInstanceAI(creature); + } + } + + class npc_hadronox_crusherPackAI : ScriptedAI + { + public npc_hadronox_crusherPackAI(Creature creature, Position[] positions) : base(creature) + { + _instance = creature.GetInstanceScript(); + _positions = positions; + _myPack = 0; + _doFacing = false; + } + + public override void DoAction(int action) + { + if (action == ActionIds.PackWalk) + { + switch (_myPack) + { + case SummonGroups.Crusher1: + case SummonGroups.Crusher2: + case SummonGroups.Crusher3: + me.GetMotionMaster().MovePoint(ActionIds.PackWalk, _positions[_myPack - SummonGroups.Crusher1]); + break; + default: + break; + } + } + } + + public override void MovementInform(MovementGeneratorType type, uint id) + { + if (type == MovementGeneratorType.Point && id == ActionIds.PackWalk) + _doFacing = true; + } + + public override void EnterEvadeMode(EvadeReason why) + { + Creature hadronox = _instance.GetCreature(ANDataTypes.Hadronox); + if (hadronox) + hadronox.GetAI().EnterEvadeMode(EvadeReason.Other); + } + + public override uint GetData(uint data) + { + if (data == Data.CrusherPackId) + return (uint)_myPack; + return 0; + } + + public override void SetData(uint data, uint value) + { + if (data == Data.CrusherPackId) + { + _myPack = (SummonGroups)value; + me.SetReactState(_myPack != 0 ? ReactStates.Passive : ReactStates.Aggressive); + } + } + + public override void EnterCombat(Unit who) + { + if (me.HasReactState(ReactStates.Passive)) + { + List others = new List(); + me.GetCreatureListWithEntryInGrid(others, 0, 40.0f); + foreach (Creature other in others) + { + if (other.GetAI().GetData(Data.CrusherPackId) == (uint)_myPack) + { + other.SetReactState(ReactStates.Aggressive); + other.GetAI().AttackStart(who); + } + } + } + _EnterCombat(); + base.EnterCombat(who); + } + + public virtual void _EnterCombat() { } + + public override void MoveInLineOfSight(Unit who) + { + if (!me.HasReactState(ReactStates.Passive)) + { + base.MoveInLineOfSight(who); + return; + } + + if (me.CanStartAttack(who, false) && me.IsWithinDistInMap(who, me.GetAttackDistance(who) + me.m_CombatDistance)) + EnterCombat(who); + } + + public override void UpdateAI(uint diff) + { + if (_doFacing) + { + _doFacing = false; + me.SetFacingTo(_positions[_myPack - SummonGroups.Crusher1].GetOrientation()); + } + + if (!UpdateVictim()) + return; + + _scheduler.Update(diff); + + DoMeleeAttackIfReady(); + } + + protected InstanceScript _instance; + Position[] _positions; + protected SummonGroups _myPack; + bool _doFacing; + } + + [Script] + class npc_anub_ar_crusher : CreatureScript + { + public npc_anub_ar_crusher() : base("npc_anub_ar_crusher") { } + + class npc_anub_ar_crusherAI : npc_hadronox_crusherPackAI + { + public npc_anub_ar_crusherAI(Creature creature) : base(creature, Misc.crusherWaypoints) { } + + public override void _EnterCombat() + { + _scheduler.Schedule(TimeSpan.FromSeconds(8), TimeSpan.FromSeconds(12), task => + { + DoCastVictim(SpellIds.Smash); + task.Repeat(TimeSpan.FromSeconds(13), TimeSpan.FromSeconds(21)); + }); + + if (_myPack != SummonGroups.Crusher1) + return; + + Creature hadronox = _instance.GetCreature(ANDataTypes.Hadronox); + if (hadronox) + { + if (hadronox.GetAI().GetData(Data.HadronoxEnteredCombat) != 0) + return; + hadronox.GetAI().DoAction(ActionIds.CrusherEngaged); + } + + Talk(TextIds.SayCrusherAggro); + } + + public override void DamageTaken(Unit source, ref uint damage) + { + if (_hadFrenzy || !me.HealthBelowPctDamaged(25, damage)) + return; + _hadFrenzy = true; + Talk(TextIds.EmoteCrusherFrenzy); + DoCastSelf(SpellIds.Frenzy); + } + + public override void JustDied(Unit killer) + { + Creature hadronox = _instance.GetCreature(ANDataTypes.Hadronox); + if (hadronox) + hadronox.GetAI().DoAction(ActionIds.HadronoxMove); + base.JustDied(killer); + } + + bool _hadFrenzy; + } + + public override CreatureAI GetAI(Creature creature) + { + return GetInstanceAI(creature); + } + } + + [Script] + class npc_anub_ar_crusher_champion : CreatureScript + { + public npc_anub_ar_crusher_champion() : base("npc_anub_ar_crusher_champion") { } + + class npc_anub_ar_crusher_championAI : npc_hadronox_crusherPackAI + { + public npc_anub_ar_crusher_championAI(Creature creature) : base(creature, Misc.championWaypoints) { } + + public override void _EnterCombat() + { + _scheduler.Schedule(TimeSpan.FromSeconds(4), TimeSpan.FromSeconds(8), task => + { + DoCastVictim(SpellIds.Rend); + task.Repeat(TimeSpan.FromSeconds(12), TimeSpan.FromSeconds(16)); + }); + + _scheduler.Schedule(TimeSpan.FromSeconds(15), TimeSpan.FromSeconds(19), task => + { + DoCastVictim(SpellIds.Pummel); + task.Repeat(TimeSpan.FromSeconds(12), TimeSpan.FromSeconds(17)); + }); + } + } + + public override CreatureAI GetAI(Creature creature) + { + return GetInstanceAI(creature); + } + } + + [Script] + class npc_anub_ar_crusher_crypt_fiend : CreatureScript + { + public npc_anub_ar_crusher_crypt_fiend() : base("npc_anub_ar_crusher_crypt_fiend") { } + + class npc_anub_ar_crusher_crypt_fiendAI : npc_hadronox_crusherPackAI + { + public npc_anub_ar_crusher_crypt_fiendAI(Creature creature) : base(creature, Misc.cryptFiendWaypoints) { } + + public override void _EnterCombat() + { + _scheduler.Schedule(TimeSpan.FromSeconds(4), TimeSpan.FromSeconds(8), task => + { + DoCastVictim(SpellIds.CrushingWebs); + task.Repeat(TimeSpan.FromSeconds(12), TimeSpan.FromSeconds(16)); + }); + + _scheduler.Schedule(TimeSpan.FromSeconds(15), TimeSpan.FromSeconds(19), task => + { + DoCastVictim(SpellIds.InfectedWound); + task.Repeat(TimeSpan.FromSeconds(16), TimeSpan.FromSeconds(25)); + }); + } + } + + public override CreatureAI GetAI(Creature creature) + { + return GetInstanceAI(creature); + } + } + + [Script] + class npc_anub_ar_crusher_necromancer : CreatureScript + { + public npc_anub_ar_crusher_necromancer() : base("npc_anub_ar_crusher_necromancer") { } + + class npc_anub_ar_crusher_necromancerAI : npc_hadronox_crusherPackAI + { + public npc_anub_ar_crusher_necromancerAI(Creature creature) : base(creature, Misc.necromancerWaypoints) { } + + public override void _EnterCombat() + { + _scheduler.Schedule(TimeSpan.FromSeconds(2), TimeSpan.FromSeconds(4), task => + { + DoCastVictim(SpellIds.ShadowBolt); + task.Repeat(TimeSpan.FromSeconds(2), TimeSpan.FromSeconds(5)); + }); + + _scheduler.Schedule(TimeSpan.FromSeconds(37), TimeSpan.FromSeconds(45), task => + { + DoCastVictim(RandomHelper.URand(0, 1) != 0 ? SpellIds.AnimateBones2 : SpellIds.AnimateBones1); + task.Repeat(TimeSpan.FromSeconds(35), TimeSpan.FromSeconds(50)); + }); + } + } + + public override CreatureAI GetAI(Creature creature) + { + return GetInstanceAI(creature); + } + } + + class npc_hadronox_foeAI : ScriptedAI + { + public npc_hadronox_foeAI(Creature creature) : base(creature) + { + _instance = creature.GetInstanceScript(); + _nextMovement = MovementIds.Outside; + _mySpawn = 0; + } + + public override void InitializeAI() + { + base.InitializeAI(); + Creature hadronox = _instance.GetCreature(ANDataTypes.Hadronox); + if (hadronox) + hadronox.GetAI().SetGUID(me.GetGUID()); + } + + public override void MovementInform(MovementGeneratorType type, uint id) + { + if (type == MovementGeneratorType.Point) + _nextMovement = (MovementIds)(id + 1); + } + + public override void EnterEvadeMode(EvadeReason why) + { + me.DespawnOrUnsummon(); + } + + public override void UpdateAI(uint diff) + { + if (_nextMovement != 0) + { + switch (_nextMovement) + { + case MovementIds.Outside: + { + float dist = float.PositiveInfinity; + for (byte spawn = 0; spawn < Misc.initialMoves.Length; ++spawn) + { + float thisDist = Misc.initialMoves[spawn].GetExactDistSq(me); + if (thisDist < dist) + { + _mySpawn = spawn; + dist = thisDist; + } + } + me.GetMotionMaster().MovePoint((uint)MovementIds.Outside, Misc.initialMoves[_mySpawn], false); // do not pathfind here, we have to pass through a "wall" of webbing + break; + } + case MovementIds.Downstairs: + me.GetMotionMaster().MovePoint((uint)MovementIds.Downstairs, Misc.downstairsMoves[_mySpawn]); + break; + case MovementIds.Downstairs2: + if (Misc.downstairsMoves2[_mySpawn].GetPositionX() > 0.0f) // might be unset for this spawn - if yes, skip + { + me.GetMotionMaster().MovePoint((uint)MovementIds.Downstairs2, Misc.downstairsMoves2[_mySpawn]); + break; + } + goto case MovementIds.Hadronox; + // intentional missing break + case MovementIds.Hadronox: + case MovementIds.HadronoxReal: + { + float zCutoff = 702.0f; + Creature hadronox = _instance.GetCreature(ANDataTypes.Hadronox); + if (hadronox && hadronox.IsAlive()) + { + if (_nextMovement != MovementIds.HadronoxReal) + if (hadronox.GetPositionZ() < zCutoff) + { + me.GetMotionMaster().MovePoint((uint)MovementIds.Hadronox, Misc.hadronoxStep[2]); + break; + } + me.GetMotionMaster().MoveChase(hadronox); + } + break; + } + default: + break; + } + _nextMovement = MovementIds.None; + } + + if (!UpdateVictim()) + return; + + _scheduler.Update(diff); + + DoMeleeAttackIfReady(); + } + + InstanceScript _instance; + + MovementIds _nextMovement; + byte _mySpawn; + } + + [Script] + class npc_anub_ar_champion : CreatureScript + { + public npc_anub_ar_champion() : base("npc_anub_ar_champion") { } + + class npc_anub_ar_championAI : npc_hadronox_foeAI + { + public npc_anub_ar_championAI(Creature creature) : base(creature) { } + + public override void EnterCombat(Unit who) + { + _scheduler.Schedule(TimeSpan.FromSeconds(4), TimeSpan.FromSeconds(8), task => + { + DoCastVictim(SpellIds.Rend); + task.Repeat(TimeSpan.FromSeconds(12), TimeSpan.FromSeconds(16)); + }); + + _scheduler.Schedule(TimeSpan.FromSeconds(15), TimeSpan.FromSeconds(19), task => + { + DoCastVictim(SpellIds.Pummel); + task.Repeat(TimeSpan.FromSeconds(12), TimeSpan.FromSeconds(17)); + }); + + _scheduler.Schedule(TimeSpan.FromSeconds(15), TimeSpan.FromSeconds(50), task => + { + DoCastVictim(SpellIds.Taunt); + task.Repeat(TimeSpan.FromSeconds(15), TimeSpan.FromSeconds(50)); + }); + } + } + + public override CreatureAI GetAI(Creature creature) + { + return GetInstanceAI(creature); + } + } + + [Script] + class npc_anub_ar_crypt_fiend : CreatureScript + { + public npc_anub_ar_crypt_fiend() : base("npc_anub_ar_crypt_fiend") { } + + class npc_anub_ar_crypt_fiendAI : npc_hadronox_foeAI + { + public npc_anub_ar_crypt_fiendAI(Creature creature) : base(creature) { } + + public override void EnterCombat(Unit who) + { + _scheduler.Schedule(TimeSpan.FromSeconds(4), TimeSpan.FromSeconds(8), task => + { + DoCastVictim(SpellIds.CrushingWebs); + task.Repeat(TimeSpan.FromSeconds(12), TimeSpan.FromSeconds(16)); + }); + + _scheduler.Schedule(TimeSpan.FromSeconds(15), TimeSpan.FromSeconds(19), task => + { + DoCastVictim(SpellIds.InfectedWound); + task.Repeat(TimeSpan.FromSeconds(16), TimeSpan.FromSeconds(25)); + }); + + _scheduler.Schedule(TimeSpan.FromSeconds(15), TimeSpan.FromSeconds(50), task => + { + DoCastVictim(SpellIds.Taunt); + task.Repeat(TimeSpan.FromSeconds(15), TimeSpan.FromSeconds(50)); + }); + } + } + + public override CreatureAI GetAI(Creature creature) + { + return GetInstanceAI(creature); + } + } + + [Script] + class npc_anub_ar_necromancer : CreatureScript + { + public npc_anub_ar_necromancer() : base("npc_anub_ar_necromancer") { } + + class npc_anub_ar_necromancerAI : npc_hadronox_foeAI + { + public npc_anub_ar_necromancerAI(Creature creature) : base(creature) { } + + public override void EnterCombat(Unit who) + { + _scheduler.Schedule(TimeSpan.FromSeconds(2), TimeSpan.FromSeconds(4), task => + { + DoCastVictim(SpellIds.ShadowBolt); + task.Repeat(TimeSpan.FromSeconds(2), TimeSpan.FromSeconds(5)); + }); + + _scheduler.Schedule(TimeSpan.FromSeconds(37), TimeSpan.FromSeconds(45), task => + { + DoCastVictim(RandomHelper.URand(0, 1) != 0 ? SpellIds.AnimateBones2 : SpellIds.AnimateBones1); + task.Repeat(TimeSpan.FromSeconds(35), TimeSpan.FromSeconds(50)); + }); + + _scheduler.Schedule(TimeSpan.FromSeconds(15), TimeSpan.FromSeconds(50), task => + { + DoCastVictim(SpellIds.Taunt); + task.Repeat(TimeSpan.FromSeconds(15), TimeSpan.FromSeconds(50)); + }); + } + } + + public override CreatureAI GetAI(Creature creature) + { + return GetInstanceAI(creature); + } + } + + class spell_hadronox_periodic_summon_template_AuraScript : AuraScript + { + public spell_hadronox_periodic_summon_template_AuraScript(uint topSpellId, uint bottomSpellId) : base() + { + _topSpellId = topSpellId; + _bottomSpellId = bottomSpellId; + } + + public override bool Validate(SpellInfo spell) + { + return ValidateSpellInfo(_topSpellId, _bottomSpellId); + } + + void HandleApply(AuraEffect eff, AuraEffectHandleModes mode) + { + AuraEffect effect = GetAura().GetEffect(0); + if (effect != null) + effect.SetPeriodicTimer(RandomHelper.IRand(2, 17) * Time.InMilliseconds); + } + + void HandlePeriodic(AuraEffect eff) + { + Unit caster = GetCaster(); + if (!caster) + return; + InstanceScript instance = caster.GetInstanceScript(); + if (instance == null) + return; + if (instance.GetBossState(ANDataTypes.Hadronox) == EncounterState.Done) + GetAura().Remove(); + else + { + if (caster.GetPositionZ() >= 750.0f) + caster.CastSpell(caster, _topSpellId, true); + else + caster.CastSpell(caster, _bottomSpellId, true); + } + } + + public override void Register() + { + AfterEffectApply.Add(new EffectApplyHandler(HandleApply, 0, AuraType.PeriodicDummy, AuraEffectHandleModes.Real)); + OnEffectPeriodic.Add(new EffectPeriodicHandler(HandlePeriodic, 0, AuraType.PeriodicDummy)); + } + + uint _topSpellId; + uint _bottomSpellId; + } + + [Script] + class spell_hadronox_periodic_summon_champion : SpellScriptLoader + { + public spell_hadronox_periodic_summon_champion() : base("spell_hadronox_periodic_summon_champion") { } + + class spell_hadronox_periodic_summon_champion_AuraScript : spell_hadronox_periodic_summon_template_AuraScript + { + public spell_hadronox_periodic_summon_champion_AuraScript() : base(SpellIds.SummonChampionTop, SpellIds.SummonChampionBottom) { } + } + + public override AuraScript GetAuraScript() + { + return new spell_hadronox_periodic_summon_champion_AuraScript(); + } + } + + [Script] + class spell_hadronox_periodic_summon_crypt_fiend : SpellScriptLoader + { + public spell_hadronox_periodic_summon_crypt_fiend() : base("spell_hadronox_periodic_summon_crypt_fiend") { } + + class spell_hadronox_periodic_summon_crypt_fiend_AuraScript : spell_hadronox_periodic_summon_template_AuraScript + { + public spell_hadronox_periodic_summon_crypt_fiend_AuraScript() : base(SpellIds.SummonCryptFiendTop, SpellIds.SummonCryptFiendBottom) { } + } + + public override AuraScript GetAuraScript() + { + return new spell_hadronox_periodic_summon_crypt_fiend_AuraScript(); + } + } + + [Script] + class spell_hadronox_periodic_summon_necromancer : SpellScriptLoader + { + public spell_hadronox_periodic_summon_necromancer() : base("spell_hadronox_periodic_summon_necromancer") { } + + class spell_hadronox_periodic_summon_necromancer_AuraScript : spell_hadronox_periodic_summon_template_AuraScript + { + public spell_hadronox_periodic_summon_necromancer_AuraScript() : base(SpellIds.SummonNecromancerTop, SpellIds.SummonNecromancerBottom) { } + } + + public override AuraScript GetAuraScript() + { + return new spell_hadronox_periodic_summon_necromancer_AuraScript(); + } + } + + [Script] + class spell_hadronox_leeching_poison : SpellScriptLoader + { + public spell_hadronox_leeching_poison() : base("spell_hadronox_leeching_poison") { } + + class spell_hadronox_leeching_poison_AuraScript : AuraScript + { + public override bool Validate(SpellInfo spell) + { + return ValidateSpellInfo(SpellIds.LeechPoisonHeal); + } + + void HandleEffectRemove(AuraEffect aurEff, AuraEffectHandleModes mode) + { + if (GetTargetApplication().GetRemoveMode() != AuraRemoveMode.ByDeath) + return; + + if (GetTarget().IsGuardian()) + return; + + Unit caster = GetCaster(); + if (caster) + caster.CastSpell(caster, SpellIds.LeechPoisonHeal, true); + } + + public override void Register() + { + OnEffectRemove.Add(new EffectApplyHandler(HandleEffectRemove, 0, AuraType.PeriodicLeech, AuraEffectHandleModes.Real)); + } + } + + public override AuraScript GetAuraScript() + { + return new spell_hadronox_leeching_poison_AuraScript(); + } + } + + [Script] + class spell_hadronox_web_doors : SpellScriptLoader + { + public spell_hadronox_web_doors() : base("spell_hadronox_web_doors") { } + + class spell_hadronox_web_doors_SpellScript : SpellScript + { + public override bool Validate(SpellInfo spell) + { + return ValidateSpellInfo(SpellIds.SummonChampionPeriodic, SpellIds.SummonCryptFiendPeriodic, SpellIds.SummonNecromancerPeriodic); + } + + void HandleDummy(uint effIndex) + { + Unit target = GetHitUnit(); + if (target) + { + target.RemoveAurasDueToSpell(SpellIds.SummonChampionPeriodic); + target.RemoveAurasDueToSpell(SpellIds.SummonCryptFiendPeriodic); + target.RemoveAurasDueToSpell(SpellIds.SummonNecromancerPeriodic); + } + } + + public override void Register() + { + OnEffectHitTarget.Add(new EffectHandler(HandleDummy, 0, SpellEffectName.ApplyAura)); + } + } + + public override SpellScript GetSpellScript() + { + return new spell_hadronox_web_doors_SpellScript(); + } + } + + [Script] + class achievement_hadronox_denied : AchievementCriteriaScript + { + public achievement_hadronox_denied() : base("achievement_hadronox_denied") { } + + public override bool OnCheck(Player player, Unit target) + { + if (!target) + return false; + + Creature cTarget = target.ToCreature(); + if (cTarget) + if (cTarget.GetAI().GetData(Data.HadronoxWebbedDoors) == 0) + return true; + + return false; + } + } +} \ No newline at end of file diff --git a/Scripts/Northrend/AzjolNerub/AzjolNerub/InstanceAzjolNerub.cs b/Scripts/Northrend/AzjolNerub/AzjolNerub/InstanceAzjolNerub.cs new file mode 100644 index 000000000..8e39df5fa --- /dev/null +++ b/Scripts/Northrend/AzjolNerub/AzjolNerub/InstanceAzjolNerub.cs @@ -0,0 +1,131 @@ +/* + * Copyright (C) 2012-2017 CypherCore + * + * 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, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +using Framework.Constants; +using Game.Entities; +using Game.Maps; +using Game.Scripting; + +namespace Scripts.Northrend.AzjolNerub.AzjolNerub +{ + [Script] + class instance_azjol_nerub : InstanceMapScript + { + public instance_azjol_nerub() : base(nameof(instance_azjol_nerub), 601) { } + + class instance_azjol_nerub_InstanceScript : InstanceScript + { + public instance_azjol_nerub_InstanceScript(Map map) : base(map) + { + SetHeaders(ANInstanceMisc.DataHeader); + SetBossNumber(ANInstanceMisc.EncounterCount); + LoadBossBoundaries(ANInstanceMisc.boundaries); + LoadDoorData(ANInstanceMisc.doorData); + LoadObjectData(ANInstanceMisc.creatureData, ANInstanceMisc.gameobjectData); + } + + public override void OnUnitDeath(Unit who) + { + base.OnUnitDeath(who); + Creature creature = who.ToCreature(); + if (!creature || creature.IsCritter() || creature.IsControlledByPlayer()) + return; + + Creature gatewatcher = GetCreature(ANDataTypes.KrikthirTheGatewatcher); + if (gatewatcher) + gatewatcher.GetAI().DoAction(-ANInstanceMisc.ActionGatewatcherGreet); + } + } + + public override InstanceScript GetInstanceScript(InstanceMap map) + { + return new instance_azjol_nerub_InstanceScript(map); + } + } + + struct ANDataTypes + { + // Encounter States/Boss Guids + public const uint KrikthirTheGatewatcher = 0; + public const uint Hadronox = 1; + public const uint Anubarak = 2; + + // Additional Data + public const uint WatcherNarjil = 3; + public const uint WatcherGashra = 4; + public const uint WatcherSilthik = 5; + public const uint AnubarakWall = 6; + } + + struct ANCreatureIds + { + public const uint Krikthir = 28684; + public const uint Hadronox = 28921; + public const uint Anubarak = 29120; + + public const uint WatcherNarjil = 28729; + public const uint WatcherGashra = 28730; + public const uint WatcherSilthik = 28731; + } + + struct ANGameObjectIds + { + public const uint KrikthirDoor = 192395; + public const uint AnubarakDoor1 = 192396; + public const uint AnubarakDoor2 = 192397; + public const uint AnubarakDoor3 = 192398; + } + + // These are passed as -action to AI's DoAction to differentiate between them and boss scripts' own actions + struct ANInstanceMisc + { + public const string DataHeader = "AN"; + public const uint EncounterCount = 3; + + public const int ActionGatewatcherGreet = 1; + + public static DoorData[] doorData = + { + new DoorData(ANGameObjectIds.KrikthirDoor, ANDataTypes.KrikthirTheGatewatcher, DoorType.Passage), + new DoorData(ANGameObjectIds.AnubarakDoor1, ANDataTypes.Anubarak, DoorType.Room ), + new DoorData(ANGameObjectIds.AnubarakDoor2, ANDataTypes.Anubarak, DoorType.Room ), + new DoorData(ANGameObjectIds.AnubarakDoor3, ANDataTypes.Anubarak, DoorType.Room ) + }; + + public static ObjectData[] creatureData = + { + new ObjectData(ANCreatureIds.Krikthir, ANDataTypes.KrikthirTheGatewatcher ), + new ObjectData(ANCreatureIds.Hadronox, ANDataTypes.Hadronox ), + new ObjectData(ANCreatureIds.Anubarak, ANDataTypes.Anubarak ), + new ObjectData(ANCreatureIds.WatcherNarjil, ANDataTypes.WatcherGashra ), + new ObjectData(ANCreatureIds.WatcherGashra, ANDataTypes.WatcherSilthik ), + new ObjectData(ANCreatureIds.WatcherSilthik, ANDataTypes.WatcherNarjil ) + }; + + public static ObjectData[] gameobjectData = + { + new ObjectData(ANGameObjectIds.AnubarakDoor3, ANDataTypes.AnubarakWall) + }; + + public static BossBoundaryEntry[] boundaries = + { + new BossBoundaryEntry(ANDataTypes.KrikthirTheGatewatcher, new RectangleBoundary(400.0f, 580.0f, 623.5f, 810.0f)), + new BossBoundaryEntry(ANDataTypes.Hadronox, new ZRangeBoundary(666.0f, 776.0f)), + new BossBoundaryEntry(ANDataTypes.Anubarak, new CircleBoundary(new Position(550.6178f, 253.5917f), 26.0f)) + }; + } +} diff --git a/Scripts/Northrend/CrusadersColiseum/TrialOfTheChampion/GrandChampions.cs b/Scripts/Northrend/CrusadersColiseum/TrialOfTheChampion/GrandChampions.cs new file mode 100644 index 000000000..7b3fb0751 --- /dev/null +++ b/Scripts/Northrend/CrusadersColiseum/TrialOfTheChampion/GrandChampions.cs @@ -0,0 +1,729 @@ +/* + * Copyright (C) 2012-2017 CypherCore + * + * 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, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +using Framework.Constants; +using Framework.Dynamic; +using Game.AI; +using Game.Entities; +using Game.Maps; +using Game.Scripting; +using System; +using System.Collections.Generic; + +namespace Scripts.Northrend.CrusadersColiseum.TrialOfTheChampion +{ + struct TrialOfChampionSpells + { + //Vehicle + public const uint CHARGE = 63010; + public const uint SHIELD_BREAKER = 68504; + public const uint SHIELD = 66482; + + // Marshal Jacob Alerius && Mokra the Skullcrusher || Warrior + public const uint MORTAL_STRIKE = 68783; + public const uint MORTAL_STRIKE_H = 68784; + public const uint BLADESTORM = 63784; + public const uint INTERCEPT = 67540; + public const uint ROLLING_THROW = 47115; //not implemented in the AI yet... + + // Ambrose Boltspark && Eressea Dawnsinger || Mage + public const uint FIREBALL = 66042; + public const uint FIREBALL_H = 68310; + public const uint BLAST_WAVE = 66044; + public const uint BLAST_WAVE_H = 68312; + public const uint HASTE = 66045; + public const uint POLYMORPH = 66043; + public const uint POLYMORPH_H = 68311; + + // Colosos && Runok Wildmane || Shaman + public const uint CHAIN_LIGHTNING = 67529; + public const uint CHAIN_LIGHTNING_H = 68319; + public const uint EARTH_SHIELD = 67530; + public const uint HEALING_WAVE = 67528; + public const uint HEALING_WAVE_H = 68318; + public const uint HEX_OF_MENDING = 67534; + + // Jaelyne Evensong && Zul'tore || Hunter + public const uint DISENGAGE = 68340; //not implemented in the AI yet... + public const uint LIGHTNING_ARROWS = 66083; + public const uint MULTI_SHOT = 66081; + public const uint SHOOT = 65868; + public const uint SHOOT_H = 67988; + + // Lana Stouthammer Evensong && Deathstalker Visceri || Rouge + public const uint EVISCERATE = 67709; + public const uint EVISCERATE_H = 68317; + public const uint FAN_OF_KNIVES = 67706; + public const uint POISON_BOTTLE = 67701; + } + + /* + struct Point + { + float x, y, z; + } + + const Point MovementPoint[] = + { + {746.84f, 623.15f, 411.41f}, + {747.96f, 620.29f, 411.09f}, + {750.23f, 618.35f, 411.09f} + } + */ + + /* + * Generic AI for vehicles used by npcs in ToC, it needs more improvements. * + * Script Complete: 25%. * + */ + + [Script] + class generic_vehicleAI_toc5 : CreatureScript + { + public generic_vehicleAI_toc5() : base("generic_vehicleAI_toc5") { } + + class generic_vehicleAI_toc5AI : npc_escortAI + { + public generic_vehicleAI_toc5AI(Creature creature) : base(creature) + { + Initialize(); + SetDespawnAtEnd(false); + uiWaypointPath = 0; + + instance = creature.GetInstanceScript(); + } + + void Initialize() + { + uiChargeTimer = 5000; + uiShieldBreakerTimer = 8000; + uiBuffTimer = RandomHelper.URand(30000, 60000); + } + + public override void Reset() + { + Initialize(); + } + + public override void SetData(uint uiType, uint uiData) + { + switch (uiType) + { + case 1: + AddWaypoint(0, 747.36f, 634.07f, 411.572f); + AddWaypoint(1, 780.43f, 607.15f, 411.82f); + AddWaypoint(2, 785.99f, 599.41f, 411.92f); + AddWaypoint(3, 778.44f, 601.64f, 411.79f); + uiWaypointPath = 1; + break; + case 2: + AddWaypoint(0, 747.35f, 634.07f, 411.57f); + AddWaypoint(1, 768.72f, 581.01f, 411.92f); + AddWaypoint(2, 763.55f, 590.52f, 411.71f); + uiWaypointPath = 2; + break; + case 3: + AddWaypoint(0, 747.35f, 634.07f, 411.57f); + AddWaypoint(1, 784.02f, 645.33f, 412.39f); + AddWaypoint(2, 775.67f, 641.91f, 411.91f); + uiWaypointPath = 3; + break; + } + + if (uiType <= 3) + Start(false, true); + } + + public override void WaypointReached(uint waypointId) + { + switch (waypointId) + { + case 2: + if (uiWaypointPath == 3 || uiWaypointPath == 2) + instance.SetData((uint)Data.DATA_MOVEMENT_DONE, instance.GetData((uint)Data.DATA_MOVEMENT_DONE) + 1); + break; + case 3: + instance.SetData((uint)Data.DATA_MOVEMENT_DONE, instance.GetData((uint)Data.DATA_MOVEMENT_DONE) + 1); + break; + } + } + + public override void EnterCombat(Unit who) + { + DoCastSpellShield(); + } + + void DoCastSpellShield() + { + for (byte i = 0; i < 3; ++i) + DoCast(me, TrialOfChampionSpells.SHIELD, true); + } + + public override void UpdateAI(uint uiDiff) + { + base.UpdateAI(uiDiff); + + if (!UpdateVictim()) + return; + + if (uiBuffTimer <= uiDiff) + { + if (!me.HasAura(TrialOfChampionSpells.SHIELD)) + DoCastSpellShield(); + + uiBuffTimer = RandomHelper.URand(30000, 45000); + } + else + uiBuffTimer -= uiDiff; + + if (uiChargeTimer <= uiDiff) + { + var players = me.GetMap().GetPlayers(); + if (!players.Empty()) + { + foreach (var player in players) + { + if (player && !player.IsGameMaster() && me.IsInRange(player, 8.0f, 25.0f, false)) + { + DoResetThreat(); + me.AddThreat(player, 1.0f); + DoCast(player, TrialOfChampionSpells.CHARGE); + break; + } + } + } + uiChargeTimer = 5000; + } + else + uiChargeTimer -= uiDiff; + + //dosen't work at all + if (uiShieldBreakerTimer <= uiDiff) + { + Vehicle pVehicle = me.GetVehicleKit(); + if (!pVehicle) + return; + + Unit pPassenger = pVehicle.GetPassenger(0); + if (pPassenger) + { + var players = me.GetMap().GetPlayers(); + if (!players.Empty()) + { + foreach (var player in players) + { + if (player && !player.IsGameMaster() && me.IsInRange(player, 10.0f, 30.0f, false)) + { + pPassenger.CastSpell(player, TrialOfChampionSpells.SHIELD_BREAKER, true); + break; + } + } + } + } + uiShieldBreakerTimer = 7000; + } + else + uiShieldBreakerTimer -= uiDiff; + + DoMeleeAttackIfReady(); + } + + InstanceScript instance; + + uint uiChargeTimer; + uint uiShieldBreakerTimer; + uint uiBuffTimer; + + uint uiWaypointPath; + } + + public override CreatureAI GetAI(Creature creature) + { + return GetInstanceAI(creature); + } + + public static void AggroAllPlayers(Creature temp) + { + var PlList = temp.GetMap().GetPlayers(); + + if (PlList.Empty()) + return; + + foreach (var player in PlList) + { + if (player) + { + if (player.IsGameMaster()) + continue; + + if (player.IsAlive()) + { + temp.RemoveFlag(UnitFields.Flags, UnitFlags.NonAttackable | UnitFlags.ImmuneToPc); + temp.SetReactState(ReactStates.Aggressive); + temp.SetInCombatWith(player); + player.SetInCombatWith(temp); + temp.AddThreat(player, 0.0f); + } + } + } + } + + public static bool GrandChampionsOutVehicle(Creature me) + { + InstanceScript instance = me.GetInstanceScript(); + + if (instance == null) + return false; + + Creature pGrandChampion1 = ObjectAccessor.GetCreature(me, instance.GetGuidData((uint)Data64.DATA_GRAND_CHAMPION_1)); + Creature pGrandChampion2 = ObjectAccessor.GetCreature(me, instance.GetGuidData((uint)Data64.DATA_GRAND_CHAMPION_2)); + Creature pGrandChampion3 = ObjectAccessor.GetCreature(me, instance.GetGuidData((uint)Data64.DATA_GRAND_CHAMPION_3)); + + if (pGrandChampion1 && pGrandChampion2 && pGrandChampion3) + { + if (pGrandChampion1.m_movementInfo.transport.guid.IsEmpty() && + pGrandChampion2.m_movementInfo.transport.guid.IsEmpty() && + pGrandChampion3.m_movementInfo.transport.guid.IsEmpty()) + return true; + } + + return false; + } + } + + abstract class boss_basic_toc5AI : ScriptedAI + { + public boss_basic_toc5AI(Creature creature) : base(creature) + { + Initialize(); + instance = creature.GetInstanceScript(); + + bDone = false; + bHome = false; + + uiPhase = 0; + uiPhaseTimer = 0; + + me.SetReactState(ReactStates.Passive); + // THIS IS A HACK, SHOULD BE REMOVED WHEN THE EVENT IS FULL SCRIPTED + me.SetFlag(UnitFields.Flags, UnitFlags.NonAttackable | UnitFlags.ImmuneToPc); + } + + public abstract void Initialize(); + + public override void Reset() + { + Initialize(); + } + + public override void JustReachedHome() + { + base.JustReachedHome(); + + if (!bHome) + return; + + uiPhaseTimer = 15000; + uiPhase = 1; + + bHome = false; + } + + public override void JustDied(Unit killer) + { + instance.SetData((uint)Data.BOSS_GRAND_CHAMPIONS, (uint)EncounterState.Done); + } + + public override void UpdateAI(uint diff) + { + if (!bDone && generic_vehicleAI_toc5.GrandChampionsOutVehicle(me)) + { + bDone = true; + + if (me.GetGUID() == instance.GetGuidData((uint)Data64.DATA_GRAND_CHAMPION_1)) + me.SetHomePosition(739.678f, 662.541f, 412.393f, 4.49f); + else if (me.GetGUID() == instance.GetGuidData((uint)Data64.DATA_GRAND_CHAMPION_2)) + me.SetHomePosition(746.71f, 661.02f, 411.69f, 4.6f); + else if (me.GetGUID() == instance.GetGuidData((uint)Data64.DATA_GRAND_CHAMPION_3)) + me.SetHomePosition(754.34f, 660.70f, 412.39f, 4.79f); + + EnterEvadeMode(); + bHome = true; + } + + if (uiPhaseTimer <= diff) + { + if (uiPhase == 1) + { + generic_vehicleAI_toc5.AggroAllPlayers(me); + uiPhase = 0; + } + } + else + uiPhaseTimer -= diff; + } + + public bool InVehicle() + { + return !me.m_movementInfo.transport.guid.IsEmpty(); + } + + public TaskScheduler NonCombatEvents = new TaskScheduler(); + + public InstanceScript instance; + + public byte uiPhase; + public uint uiPhaseTimer; + + bool bDone; + public bool bHome; + } + + [Script] + class boss_warrior_toc5 : CreatureScript + { + public boss_warrior_toc5() : base("boss_warrior_toc5") { } + + // Marshal Jacob Alerius && Mokra the Skullcrusher || Warrior + class boss_warrior_toc5AI : boss_basic_toc5AI + { + public boss_warrior_toc5AI(Creature creature) : base(creature) { } + + public override void Initialize() + { + _scheduler.Schedule(TimeSpan.FromSeconds(15), TimeSpan.FromSeconds(20), task => + { + DoCastVictim(TrialOfChampionSpells.BLADESTORM); + task.Repeat(TimeSpan.FromSeconds(15), TimeSpan.FromSeconds(20)); + }); + + _scheduler.Schedule(TimeSpan.FromSeconds(7), task => + { + var players = me.GetMap().GetPlayers(); + if (!players.Empty()) + { + foreach (var player in players) + { + if (player && !player.IsGameMaster() && me.IsInRange(player, 8.0f, 25.0f, false)) + { + DoResetThreat(); + me.AddThreat(player, 5.0f); + DoCast(player, TrialOfChampionSpells.INTERCEPT); + break; + } + } + } + task.Repeat(TimeSpan.FromSeconds(7)); + }); + + _scheduler.Schedule(TimeSpan.FromSeconds(8), TimeSpan.FromSeconds(12), task => + { + DoCastVictim(TrialOfChampionSpells.MORTAL_STRIKE); + task.Repeat(TimeSpan.FromSeconds(8), TimeSpan.FromSeconds(12)); + }); + } + + public override void UpdateAI(uint diff) + { + base.UpdateAI(diff); + + if (!UpdateVictim() || InVehicle()) + return; + + _scheduler.Update(diff); + + DoMeleeAttackIfReady(); + } + + } + + public override CreatureAI GetAI(Creature creature) + { + return GetInstanceAI(creature); + } + } + + [Script] + class boss_mage_toc5 : CreatureScript + { + public boss_mage_toc5() : base("boss_mage_toc5") { } + + // Ambrose Boltspark && Eressea Dawnsinger || Mage + class boss_mage_toc5AI : boss_basic_toc5AI + { + public boss_mage_toc5AI(Creature creature) : base(creature) { } + + public override void Initialize() + { + _scheduler.Schedule(TimeSpan.FromSeconds(5), task => + { + DoCastVictim(TrialOfChampionSpells.FIREBALL); + task.Repeat(TimeSpan.FromSeconds(5)); + }); + + _scheduler.Schedule(TimeSpan.FromSeconds(8), task => + { + Unit target = SelectTarget(SelectAggroTarget.Random, 0); + if (target) + DoCast(target, TrialOfChampionSpells.POLYMORPH); + task.Repeat(TimeSpan.FromSeconds(8)); + }); + + _scheduler.Schedule(TimeSpan.FromSeconds(12), task => + { + DoCastAOE(TrialOfChampionSpells.BLAST_WAVE, false); + task.Repeat(TimeSpan.FromSeconds(13)); + }); + + _scheduler.Schedule(TimeSpan.FromSeconds(22), task => + { + me.InterruptNonMeleeSpells(true); + + DoCast(me, TrialOfChampionSpells.HASTE); + task.Repeat(TimeSpan.FromSeconds(22)); + }); + } + + public override void UpdateAI(uint diff) + { + base.UpdateAI(diff); + + if (!UpdateVictim() || InVehicle()) + return; + + _scheduler.Update(diff); + + DoMeleeAttackIfReady(); + } + } + + public override CreatureAI GetAI(Creature creature) + { + return GetInstanceAI(creature); + } + } + + [Script] + class boss_shaman_toc5 : CreatureScript + { + public boss_shaman_toc5() : base("boss_shaman_toc5") { } + + // Colosos && Runok Wildmane || Shaman + class boss_shaman_toc5AI : boss_basic_toc5AI + { + public boss_shaman_toc5AI(Creature creature) : base(creature) { } + + public override void Initialize() + { + _scheduler.Schedule(TimeSpan.FromSeconds(16), task => + { + Unit target = SelectTarget(SelectAggroTarget.Random, 0); + if (target) + DoCast(target, TrialOfChampionSpells.CHAIN_LIGHTNING); + + task.Repeat(TimeSpan.FromSeconds(16)); + }); + + _scheduler.Schedule(TimeSpan.FromSeconds(12), task => + { + bool bChance = RandomHelper.randChance(50); + + if (!bChance) + { + Unit pFriend = DoSelectLowestHpFriendly(40); + if (pFriend) + DoCast(pFriend, TrialOfChampionSpells.HEALING_WAVE); + } + else + DoCast(me, TrialOfChampionSpells.HEALING_WAVE); + + task.Repeat(TimeSpan.FromSeconds(12)); + }); + + _scheduler.Schedule(TimeSpan.FromSeconds(30), TimeSpan.FromSeconds(35), task => + { + DoCast(me, TrialOfChampionSpells.EARTH_SHIELD); + task.Repeat(TimeSpan.FromSeconds(30), TimeSpan.FromSeconds(35)); + }); + + _scheduler.Schedule(TimeSpan.FromSeconds(20), TimeSpan.FromSeconds(25), task => + { + DoCastVictim(TrialOfChampionSpells.HEX_OF_MENDING, true); + task.Repeat(TimeSpan.FromSeconds(20), TimeSpan.FromSeconds(25)); + }); + } + + public override void EnterCombat(Unit who) + { + DoCast(me, TrialOfChampionSpells.EARTH_SHIELD); + DoCast(who, TrialOfChampionSpells.HEX_OF_MENDING); + } + + public override void UpdateAI(uint diff) + { + base.UpdateAI(diff); + + if (!UpdateVictim() || InVehicle()) + return; + + _scheduler.Update(diff); + + DoMeleeAttackIfReady(); + } + } + + public override CreatureAI GetAI(Creature creature) + { + return GetInstanceAI(creature); + } + } + + [Script] + class boss_hunter_toc5 : CreatureScript + { + public boss_hunter_toc5() : base("boss_hunter_toc5") { } + + // Jaelyne Evensong && Zul'tore || Hunter + class boss_hunter_toc5AI : boss_basic_toc5AI + { + public boss_hunter_toc5AI(Creature creature) : base(creature) { } + + public override void Initialize() + { + _scheduler.Schedule(TimeSpan.FromSeconds(7), task => + { + DoCastAOE(TrialOfChampionSpells.LIGHTNING_ARROWS, false); + task.Repeat(TimeSpan.FromSeconds(7)); + }); + + _scheduler.Schedule(TimeSpan.FromSeconds(12), task => + { + ObjectGuid uiTargetGUID = ObjectGuid.Empty; + Unit target = SelectTarget(SelectAggroTarget.Farthest, 0, 30.0f); + if (target) + { + uiTargetGUID = target.GetGUID(); + DoCast(target, TrialOfChampionSpells.SHOOT); + } + + bool bShoot = true; + task.Repeat(TimeSpan.FromSeconds(12)); + task.Schedule(TimeSpan.FromSeconds(3), task1 => + { + if (bShoot) + { + me.InterruptNonMeleeSpells(true); + + Unit target1 = Global.ObjAccessor.GetUnit(me, uiTargetGUID); + if (target1 && me.IsInRange(target1, 5.0f, 30.0f, false)) + { + DoCast(target1, TrialOfChampionSpells.MULTI_SHOT); + } + else + { + var players = me.GetMap().GetPlayers(); + if (!players.Empty()) + { + foreach (var player in players) + { + if (player && !player.IsGameMaster() && me.IsInRange(player, 5.0f, 30.0f, false)) + { + DoCast(player, TrialOfChampionSpells.MULTI_SHOT); + break; + } + } + } + } + bShoot = false; + } + }); + }); + } + + public override void UpdateAI(uint diff) + { + base.UpdateAI(diff); + + if (!UpdateVictim() || InVehicle()) + return; + + _scheduler.Update(diff); + + DoMeleeAttackIfReady(); + } + } + + public override CreatureAI GetAI(Creature creature) + { + return GetInstanceAI(creature); + } + } + + [Script] + class boss_rouge_toc5 : CreatureScript + { + public boss_rouge_toc5() : base("boss_rouge_toc5") { } + + // Lana Stouthammer Evensong && Deathstalker Visceri || Rouge + class boss_rouge_toc5AI : boss_basic_toc5AI + { + public boss_rouge_toc5AI(Creature creature) : base(creature) { } + + public override void Initialize() + { + _scheduler.Schedule(TimeSpan.FromSeconds(8), task => + { + DoCastVictim(TrialOfChampionSpells.EVISCERATE); + + task.Repeat(TimeSpan.FromSeconds(8)); + }); + + _scheduler.Schedule(TimeSpan.FromSeconds(14), task => + { + DoCastAOE(TrialOfChampionSpells.FAN_OF_KNIVES, false); + + task.Repeat(TimeSpan.FromSeconds(14)); + }); + + _scheduler.Schedule(TimeSpan.FromSeconds(19), task => + { + Unit target = SelectTarget(SelectAggroTarget.Random, 0); + if (target) + DoCast(target, TrialOfChampionSpells.POISON_BOTTLE); + + task.Repeat(TimeSpan.FromSeconds(19)); + }); + } + + public override void UpdateAI(uint diff) + { + base.UpdateAI(diff); + + if (!UpdateVictim() || !me.m_movementInfo.transport.guid.IsEmpty()) + return; + + _scheduler.Update(diff); + + DoMeleeAttackIfReady(); + } + } + + public override CreatureAI GetAI(Creature creature) + { + return GetInstanceAI(creature); + } + } +} \ No newline at end of file diff --git a/Scripts/Northrend/CrusadersColiseum/TrialOfTheChampion/InstanceTrialOfTheChampion.cs b/Scripts/Northrend/CrusadersColiseum/TrialOfTheChampion/InstanceTrialOfTheChampion.cs new file mode 100644 index 000000000..925f2450b --- /dev/null +++ b/Scripts/Northrend/CrusadersColiseum/TrialOfTheChampion/InstanceTrialOfTheChampion.cs @@ -0,0 +1,333 @@ +/* + * Copyright (C) 2012-2017 CypherCore + * + * 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, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +using Framework.Constants; +using Framework.GameMath; +using Framework.IO; +using Game.Entities; +using Game.Maps; +using Game.Scripting; +using System; +using System.Collections.Generic; +using System.Linq; + +namespace Scripts.Northrend.CrusadersColiseum.TrialOfTheChampion +{ + [Script] + class instance_trial_of_the_champion : InstanceMapScript + { + public instance_trial_of_the_champion() : base("instance_trial_of_the_champion", 650) { } + + public override InstanceScript GetInstanceScript(InstanceMap map) + { + return new instance_trial_of_the_champion_InstanceMapScript(map); + } + + class instance_trial_of_the_champion_InstanceMapScript : InstanceScript + { + public instance_trial_of_the_champion_InstanceMapScript(Map map) : base(map) + { + SetHeaders("TC"); + uiMovementDone = 0; + uiGrandChampionsDeaths = 0; + uiArgentSoldierDeaths = 0; + + //bDone = false; + } + + public override bool IsEncounterInProgress() + { + for (byte i = 0; i < 4; ++i) + { + if (m_auiEncounter[i] == EncounterState.InProgress) + return true; + } + + return false; + } + + public override void OnCreatureCreate(Creature creature) + { + var players = instance.GetPlayers(); + Team TeamInInstance = 0; + + if (!players.Empty()) + { + Player player = players.First(); + if (player) + TeamInInstance = player.GetTeam(); + } + + switch (creature.GetEntry()) + { + // Champions + case VehicleIds.MOKRA_SKILLCRUSHER_MOUNT: + if (TeamInInstance == Team.Horde) + creature.UpdateEntry(VehicleIds.MARSHAL_JACOB_ALERIUS_MOUNT); + break; + case VehicleIds.ERESSEA_DAWNSINGER_MOUNT: + if (TeamInInstance == Team.Horde) + creature.UpdateEntry(VehicleIds.AMBROSE_BOLTSPARK_MOUNT); + break; + case VehicleIds.RUNOK_WILDMANE_MOUNT: + if (TeamInInstance == Team.Horde) + creature.UpdateEntry(VehicleIds.COLOSOS_MOUNT); + break; + case VehicleIds.ZUL_TORE_MOUNT: + if (TeamInInstance == Team.Horde) + creature.UpdateEntry(VehicleIds.EVENSONG_MOUNT); + break; + case VehicleIds.DEATHSTALKER_VESCERI_MOUNT: + if (TeamInInstance == Team.Horde) + creature.UpdateEntry(VehicleIds.LANA_STOUTHAMMER_MOUNT); + break; + // Coliseum Announcer || Just NPC_JAEREN must be spawned. + case CreatureIds.JAEREN: + uiAnnouncerGUID = creature.GetGUID(); + if (TeamInInstance == Team.Alliance) + creature.UpdateEntry(CreatureIds.ARELAS); + break; + case VehicleIds.ARGENT_WARHORSE: + case VehicleIds.ARGENT_BATTLEWORG: + VehicleList.Add(creature.GetGUID()); + break; + case CreatureIds.EADRIC: + case CreatureIds.PALETRESS: + uiArgentChampionGUID = creature.GetGUID(); + break; + } + } + + public override void OnGameObjectCreate(GameObject go) + { + switch (go.GetEntry()) + { + case GameObjectIds.MAIN_GATE: + uiMainGateGUID = go.GetGUID(); + break; + case GameObjectIds.CHAMPIONS_LOOT: + case GameObjectIds.CHAMPIONS_LOOT_H: + uiChampionLootGUID = go.GetGUID(); + break; + } + } + + public override void SetData(uint uiType, uint uiData) + { + switch (uiType) + { + case (uint)Data.DATA_MOVEMENT_DONE: + uiMovementDone = (ushort)uiData; + if (uiMovementDone == 3) + { + Creature pAnnouncer = instance.GetCreature(uiAnnouncerGUID); + if (pAnnouncer) + pAnnouncer.GetAI().SetData((uint)Data.DATA_IN_POSITION, 0); + } + break; + case (uint)Data.BOSS_GRAND_CHAMPIONS: + m_auiEncounter[0] = (EncounterState)uiData; + if (uiData == (uint)EncounterState.InProgress) + { + foreach (var guid in VehicleList) + { + Creature summon = instance.GetCreature(guid); + if (summon) + summon.RemoveFromWorld(); + } + } + else if (uiData == (uint)EncounterState.Done) + { + ++uiGrandChampionsDeaths; + if (uiGrandChampionsDeaths == 3) + { + Creature pAnnouncer = instance.GetCreature(uiAnnouncerGUID); + if (pAnnouncer) + { + pAnnouncer.GetMotionMaster().MovePoint(0, 748.309f, 619.487f, 411.171f); + pAnnouncer.SetFlag(UnitFields.NpcFlags, NPCFlags.Gossip); + pAnnouncer.SummonGameObject(instance.IsHeroic() ? GameObjectIds.CHAMPIONS_LOOT_H : GameObjectIds.CHAMPIONS_LOOT, 746.59f, 618.49f, 411.09f, 1.42f, Quaternion.WAxis, 90000); + } + } + } + break; + case (uint)Data.DATA_ARGENT_SOLDIER_DEFEATED: + uiArgentSoldierDeaths = (byte)uiData; + if (uiArgentSoldierDeaths == 9) + { + Creature pBoss = instance.GetCreature(uiArgentChampionGUID); + if (pBoss) + { + pBoss.GetMotionMaster().MovePoint(0, 746.88f, 618.74f, 411.06f); + pBoss.RemoveFlag(UnitFields.Flags, UnitFlags.NonAttackable); + pBoss.SetReactState(ReactStates.Aggressive); + } + } + break; + case (uint)Data.BOSS_ARGENT_CHALLENGE_E: + { + m_auiEncounter[1] = (EncounterState)uiData; + Creature pAnnouncer = instance.GetCreature(uiAnnouncerGUID); + if (pAnnouncer) + { + pAnnouncer.GetMotionMaster().MovePoint(0, 748.309f, 619.487f, 411.171f); + pAnnouncer.SetFlag(UnitFields.NpcFlags, NPCFlags.Gossip); + pAnnouncer.SummonGameObject(instance.IsHeroic() ? GameObjectIds.EADRIC_LOOT_H : GameObjectIds.EADRIC_LOOT, 746.59f, 618.49f, 411.09f, 1.42f, Quaternion.WAxis, 90000); + } + } + break; + case (uint)Data.BOSS_ARGENT_CHALLENGE_P: + { + m_auiEncounter[2] = (EncounterState)uiData; + Creature pAnnouncer = instance.GetCreature(uiAnnouncerGUID); + if (pAnnouncer) + { + pAnnouncer.GetMotionMaster().MovePoint(0, 748.309f, 619.487f, 411.171f); + pAnnouncer.SetFlag(UnitFields.NpcFlags, NPCFlags.Gossip); + pAnnouncer.SummonGameObject(instance.IsHeroic() ? GameObjectIds.PALETRESS_LOOT_H : GameObjectIds.PALETRESS_LOOT, 746.59f, 618.49f, 411.09f, 1.42f, Quaternion.WAxis, 90000); + } + } + break; + } + + if (uiData == (uint)EncounterState.Done) + SaveToDB(); + } + + public override uint GetData(uint uiData) + { + switch (uiData) + { + case (uint)Data.BOSS_GRAND_CHAMPIONS: + return (uint)m_auiEncounter[0]; + case (uint)Data.BOSS_ARGENT_CHALLENGE_E: + return (uint)m_auiEncounter[1]; + case (uint)Data.BOSS_ARGENT_CHALLENGE_P: + return (uint)m_auiEncounter[2]; + case (uint)Data.BOSS_BLACK_KNIGHT: + return (uint)m_auiEncounter[3]; + + case (uint)Data.DATA_MOVEMENT_DONE: + return uiMovementDone; + case (uint)Data.DATA_ARGENT_SOLDIER_DEFEATED: + return uiArgentSoldierDeaths; + } + + return 0; + } + + public override ObjectGuid GetGuidData(uint uiData) + { + switch (uiData) + { + case (uint)Data64.DATA_ANNOUNCER: + return uiAnnouncerGUID; + case (uint)Data64.DATA_MAIN_GATE: + return uiMainGateGUID; + + case (uint)Data64.DATA_GRAND_CHAMPION_1: + return uiGrandChampion1GUID; + case (uint)Data64.DATA_GRAND_CHAMPION_2: + return uiGrandChampion2GUID; + case (uint)Data64.DATA_GRAND_CHAMPION_3: + return uiGrandChampion3GUID; + } + + return ObjectGuid.Empty; + } + + public override void SetGuidData(uint uiType, ObjectGuid uiData) + { + switch (uiType) + { + case (uint)Data64.DATA_GRAND_CHAMPION_1: + uiGrandChampion1GUID = uiData; + break; + case (uint)Data64.DATA_GRAND_CHAMPION_2: + uiGrandChampion2GUID = uiData; + break; + case (uint)Data64.DATA_GRAND_CHAMPION_3: + uiGrandChampion3GUID = uiData; + break; + } + } + + public override string GetSaveData() + { + OUT_SAVE_INST_DATA(); + + string str_data = string.Format("T C {0} {1} {2} {3} {4} {5}", m_auiEncounter[0], m_auiEncounter[1], m_auiEncounter[2], m_auiEncounter[3], uiGrandChampionsDeaths, uiMovementDone); + + OUT_SAVE_INST_DATA_COMPLETE(); + return str_data; + } + + public override void Load(string str) + { + if (str.IsEmpty()) + { + OUT_LOAD_INST_DATA_FAIL(); + return; + } + + OUT_LOAD_INST_DATA(str); + + StringArguments loadStream = new StringArguments(str); + string dataHead = loadStream.NextString(); + + if (dataHead[0] == 'T' && dataHead[1] == 'C') + { + for (byte i = 0; i < 4; ++i) + { + m_auiEncounter[i] = (EncounterState)loadStream.NextUInt32(); + if (m_auiEncounter[i] == EncounterState.InProgress) + m_auiEncounter[i] = EncounterState.NotStarted; + + } + + uiGrandChampionsDeaths = loadStream.NextUInt16(); + uiMovementDone = loadStream.NextUInt16(); + } + else + OUT_LOAD_INST_DATA_FAIL(); + + OUT_LOAD_INST_DATA_COMPLETE(); + } + + EncounterState[] m_auiEncounter = new EncounterState[4]; + + ushort uiMovementDone; + ushort uiGrandChampionsDeaths; + byte uiArgentSoldierDeaths; + + ObjectGuid uiAnnouncerGUID; + ObjectGuid uiMainGateGUID; + //ObjectGuid uiGrandChampionVehicle1GUID; + //ObjectGuid uiGrandChampionVehicle2GUID; + //ObjectGuid uiGrandChampionVehicle3GUID; + ObjectGuid uiGrandChampion1GUID; + ObjectGuid uiGrandChampion2GUID; + ObjectGuid uiGrandChampion3GUID; + ObjectGuid uiChampionLootGUID; + ObjectGuid uiArgentChampionGUID; + + List VehicleList = new List(); + + //bool bDone; + } + } +} diff --git a/Scripts/Northrend/CrusadersColiseum/TrialOfTheChampion/TrialOfTheChampion.cs b/Scripts/Northrend/CrusadersColiseum/TrialOfTheChampion/TrialOfTheChampion.cs new file mode 100644 index 000000000..71191fddd --- /dev/null +++ b/Scripts/Northrend/CrusadersColiseum/TrialOfTheChampion/TrialOfTheChampion.cs @@ -0,0 +1,587 @@ +/* + * Copyright (C) 2012-2017 CypherCore + * + * 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, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +using Framework.Constants; +using Game.AI; +using Game.Entities; +using Game.Maps; +using Game.Scripting; +using System.Collections.Generic; + + +namespace Scripts.Northrend.CrusadersColiseum.TrialOfTheChampion +{ + struct TrialOfTheChampionConst + { + public const uint SAY_INTRO_1 = 0; + public const uint SAY_INTRO_2 = 1; + public const uint SAY_INTRO_3 = 2; + public const uint SAY_AGGRO = 3; + public const uint SAY_PHASE_2 = 4; + public const uint SAY_PHASE_3 = 5; + public const uint SAY_KILL_PLAYER = 6; + public const uint SAY_DEATH = 7; + + public const string GOSSIP_START_EVENT1 = "I'm ready to start challenge."; + public const string GOSSIP_START_EVENT2 = "I'm ready for the next challenge."; + } + + enum Data + { + BOSS_GRAND_CHAMPIONS, + BOSS_ARGENT_CHALLENGE_E, + BOSS_ARGENT_CHALLENGE_P, + BOSS_BLACK_KNIGHT, + DATA_MOVEMENT_DONE, + DATA_LESSER_CHAMPIONS_DEFEATED, + DATA_START, + DATA_IN_POSITION, + DATA_ARGENT_SOLDIER_DEFEATED + }; + + enum Data64 + { + DATA_ANNOUNCER, + DATA_MAIN_GATE, + + DATA_GRAND_CHAMPION_VEHICLE_1, + DATA_GRAND_CHAMPION_VEHICLE_2, + DATA_GRAND_CHAMPION_VEHICLE_3, + + DATA_GRAND_CHAMPION_1, + DATA_GRAND_CHAMPION_2, + DATA_GRAND_CHAMPION_3 + }; + + struct CreatureIds + { + // Horde Champions + public const uint MOKRA = 35572; + public const uint ERESSEA = 35569; + public const uint RUNOK = 35571; + public const uint ZULTORE = 35570; + public const uint VISCERI = 35617; + + // Alliance Champions + public const uint JACOB = 34705; + public const uint AMBROSE = 34702; + public const uint COLOSOS = 34701; + public const uint JAELYNE = 34657; + public const uint LANA = 34703; + + public const uint EADRIC = 35119; + public const uint PALETRESS = 34928; + + public const uint ARGENT_LIGHWIELDER = 35309; + public const uint ARGENT_MONK = 35305; + public const uint PRIESTESS = 35307; + + public const uint BLACK_KNIGHT = 35451; + + public const uint RISEN_JAEREN = 35545; + public const uint RISEN_ARELAS = 35564; + + public const uint JAEREN = 35004; + public const uint ARELAS = 35005; + } + + struct GameObjectIds + { + public const uint MAIN_GATE = 195647; + + public const uint CHAMPIONS_LOOT = 195709; + public const uint CHAMPIONS_LOOT_H = 195710; + + public const uint EADRIC_LOOT = 195374; + public const uint EADRIC_LOOT_H = 195375; + + public const uint PALETRESS_LOOT = 195323; + public const uint PALETRESS_LOOT_H = 195324; + } + + struct VehicleIds + { + //Grand Champions Alliance Vehicles + public const uint MARSHAL_JACOB_ALERIUS_MOUNT = 35637; + public const uint AMBROSE_BOLTSPARK_MOUNT = 35633; + public const uint COLOSOS_MOUNT = 35768; + public const uint EVENSONG_MOUNT = 34658; + public const uint LANA_STOUTHAMMER_MOUNT = 35636; + //Faction Champions (ALLIANCE) + public const uint DARNASSIA_NIGHTSABER = 33319; + public const uint EXODAR_ELEKK = 33318; + public const uint STORMWIND_STEED = 33217; + public const uint GNOMEREGAN_MECHANOSTRIDER = 33317; + public const uint IRONFORGE_RAM = 33316; + //Grand Champions Horde Vehicles + public const uint MOKRA_SKILLCRUSHER_MOUNT = 35638; + public const uint ERESSEA_DAWNSINGER_MOUNT = 35635; + public const uint RUNOK_WILDMANE_MOUNT = 35640; + public const uint ZUL_TORE_MOUNT = 35641; + public const uint DEATHSTALKER_VESCERI_MOUNT = 35634; + //Faction Champions (HORDE) + public const uint FORSAKE_WARHORSE = 33324; + public const uint THUNDER_BLUFF_KODO = 33322; + public const uint ORGRIMMAR_WOLF = 33320; + public const uint SILVERMOON_HAWKSTRIDER = 33323; + public const uint DARKSPEAR_RAPTOR = 33321; + + public const uint ARGENT_WARHORSE = 35644; + public const uint ARGENT_BATTLEWORG = 36558; + + public const uint BLACK_KNIGHT = 35491; + } + + [Script] + class npc_announcer_toc5 : CreatureScript + { + public npc_announcer_toc5() : base("npc_announcer_toc5") { } + + class npc_announcer_toc5AI : ScriptedAI + { + public npc_announcer_toc5AI(Creature creature) : base(creature) + { + instance = creature.GetInstanceScript(); + + uiSummonTimes = 0; + uiLesserChampions = 0; + + uiFirstBoss = 0; + uiSecondBoss = 0; + uiThirdBoss = 0; + + uiArgentChampion = 0; + + uiPhase = 0; + uiTimer = 0; + + me.SetReactState(ReactStates.Passive); + me.SetFlag(UnitFields.Flags, UnitFlags.NonAttackable); + me.SetFlag(UnitFields.NpcFlags, NPCFlags.Gossip); + + SetGrandChampionsForEncounter(); + SetArgentChampion(); + } + + void NextStep(uint uiTimerStep, bool bNextStep = true, byte uiPhaseStep = 0) + { + uiTimer = uiTimerStep; + if (bNextStep) + ++uiPhase; + else + uiPhase = uiPhaseStep; + } + + public override void SetData(uint uiType, uint uiData) + { + switch (uiType) + { + case (uint)Data.DATA_START: + DoSummonGrandChampion(uiFirstBoss); + NextStep(10000, false, 1); + break; + case (uint)Data.DATA_IN_POSITION: //movement EncounterState.Done. + me.GetMotionMaster().MovePoint(1, 735.81f, 661.92f, 412.39f); + GameObject go = ObjectAccessor.GetGameObject(me, instance.GetGuidData((uint)Data64.DATA_MAIN_GATE)); + if (go) + instance.HandleGameObject(go.GetGUID(), false); + NextStep(10000, false, 3); + break; + case (uint)Data.DATA_LESSER_CHAMPIONS_DEFEATED: + { + ++uiLesserChampions; + List TempList = new List(); + if (uiLesserChampions == 3 || uiLesserChampions == 6) + { + switch (uiLesserChampions) + { + case 3: + TempList = Champion2List; + break; + case 6: + TempList = Champion3List; + break; + } + + foreach (var guid in TempList) + { + Creature summon = ObjectAccessor.GetCreature(me, guid); + if (summon) + AggroAllPlayers(summon); + } + } else if (uiLesserChampions == 9) + StartGrandChampionsAttack(); + + break; + } + } + } + + void StartGrandChampionsAttack() + { + Creature pGrandChampion1 = ObjectAccessor.GetCreature(me, uiVehicle1GUID); + Creature pGrandChampion2 = ObjectAccessor.GetCreature(me, uiVehicle2GUID); + Creature pGrandChampion3 = ObjectAccessor.GetCreature(me, uiVehicle3GUID); + + if (pGrandChampion1 && pGrandChampion2 && pGrandChampion3) + { + AggroAllPlayers(pGrandChampion1); + AggroAllPlayers(pGrandChampion2); + AggroAllPlayers(pGrandChampion3); + } + } + + public override void MovementInform(MovementGeneratorType uiType, uint uiPointId) + { + if (uiType != MovementGeneratorType.Point) + return; + + if (uiPointId == 1) + me.SetFacingTo(4.714f); + } + + void DoSummonGrandChampion(uint uiBoss) + { + ++uiSummonTimes; + uint VEHICLE_TO_SUMMON1 = 0; + uint VEHICLE_TO_SUMMON2 = 0; + switch (uiBoss) + { + case 0: + VEHICLE_TO_SUMMON1 = VehicleIds.MOKRA_SKILLCRUSHER_MOUNT; + VEHICLE_TO_SUMMON2 = VehicleIds.ORGRIMMAR_WOLF; + break; + case 1: + VEHICLE_TO_SUMMON1 = VehicleIds.ERESSEA_DAWNSINGER_MOUNT; + VEHICLE_TO_SUMMON2 = VehicleIds.SILVERMOON_HAWKSTRIDER; + break; + case 2: + VEHICLE_TO_SUMMON1 = VehicleIds.RUNOK_WILDMANE_MOUNT; + VEHICLE_TO_SUMMON2 = VehicleIds.THUNDER_BLUFF_KODO; + break; + case 3: + VEHICLE_TO_SUMMON1 = VehicleIds.ZUL_TORE_MOUNT; + VEHICLE_TO_SUMMON2 = VehicleIds.DARKSPEAR_RAPTOR; + break; + case 4: + VEHICLE_TO_SUMMON1 = VehicleIds.DEATHSTALKER_VESCERI_MOUNT; + VEHICLE_TO_SUMMON2 = VehicleIds.FORSAKE_WARHORSE; + break; + default: + return; + } + + Creature pBoss = me.SummonCreature(VEHICLE_TO_SUMMON1, SpawnPosition); + if (pBoss) + { + ObjectGuid uiGrandChampionBoss = ObjectGuid.Empty; + Vehicle pVehicle = pBoss.GetVehicleKit(); + if (pVehicle) + { + Unit unit = pVehicle.GetPassenger(0); + if (unit) + uiGrandChampionBoss = unit.GetGUID(); + } + + switch (uiSummonTimes) + { + case 1: + uiVehicle1GUID = pBoss.GetGUID(); + instance.SetGuidData((uint)Data64.DATA_GRAND_CHAMPION_VEHICLE_1, uiVehicle1GUID); + instance.SetGuidData((uint)Data64.DATA_GRAND_CHAMPION_1, uiGrandChampionBoss); + break; + case 2: + + uiVehicle2GUID = pBoss.GetGUID(); + instance.SetGuidData((uint)Data64.DATA_GRAND_CHAMPION_VEHICLE_2, uiVehicle2GUID); + instance.SetGuidData((uint)Data64.DATA_GRAND_CHAMPION_2, uiGrandChampionBoss); + break; + case 3: + uiVehicle3GUID = pBoss.GetGUID(); + instance.SetGuidData((uint)Data64.DATA_GRAND_CHAMPION_VEHICLE_3, uiVehicle3GUID); + instance.SetGuidData((uint)Data64.DATA_GRAND_CHAMPION_3, uiGrandChampionBoss); + break; + + default: + return; + } + pBoss.GetAI().SetData(uiSummonTimes, 0); + + for (byte i = 0; i < 3; ++i) + { + Creature pAdd = me.SummonCreature(VEHICLE_TO_SUMMON2, SpawnPosition, TempSummonType.CorpseDespawn); + if (pAdd) + { + switch (uiSummonTimes) + { + case 1: + Champion1List.Add(pAdd.GetGUID()); + break; + case 2: + Champion2List.Add(pAdd.GetGUID()); + break; + case 3: + Champion3List.Add(pAdd.GetGUID()); + break; + } + + switch (i) + { + case 0: + pAdd.GetMotionMaster().MoveFollow(pBoss, 2.0f, MathFunctions.PI); + break; + case 1: + pAdd.GetMotionMaster().MoveFollow(pBoss, 2.0f, MathFunctions.PI / 2); + break; + case 2: + pAdd.GetMotionMaster().MoveFollow(pBoss, 2.0f, MathFunctions.PI / 2 + MathFunctions.PI); + break; + } + } + + } + } + } + + void DoStartArgentChampionEncounter() + { + me.GetMotionMaster().MovePoint(1, 735.81f, 661.92f, 412.39f); + + if (me.SummonCreature(uiArgentChampion, SpawnPosition)) + { + for (byte i = 0; i < 3; ++i) + { + Creature lightwielderTrash = me.SummonCreature(CreatureIds.ARGENT_LIGHWIELDER, SpawnPosition); + if (lightwielderTrash) + lightwielderTrash.GetAI().SetData(i, 0); + + Creature monkTrash = me.SummonCreature(CreatureIds.ARGENT_MONK, SpawnPosition); + if (monkTrash) + monkTrash.GetAI().SetData(i, 0); + + Creature priestessTrash = me.SummonCreature(CreatureIds.PRIESTESS, SpawnPosition); + if (priestessTrash) + priestessTrash.GetAI().SetData(i, 0); + } + } + } + + void SetGrandChampionsForEncounter() + { + uiFirstBoss = RandomHelper.URand(0, 4); + + while (uiSecondBoss == uiFirstBoss || uiThirdBoss == uiFirstBoss || uiThirdBoss == uiSecondBoss) + { + uiSecondBoss = RandomHelper.URand(0, 4); + uiThirdBoss = RandomHelper.URand(0, 4); + } + } + + void SetArgentChampion() + { + byte uiTempBoss = (byte)RandomHelper.URand(0, 1); + + switch (uiTempBoss) + { + case 0: + uiArgentChampion = CreatureIds.EADRIC; + break; + case 1: + uiArgentChampion = CreatureIds.PALETRESS; + break; + } + } + + public void StartEncounter() + { + me.RemoveFlag(UnitFields.NpcFlags, NPCFlags.Gossip); + + if (instance.GetData((uint)Data.BOSS_BLACK_KNIGHT) == (uint)EncounterState.NotStarted) + { + if (instance.GetData((uint)Data.BOSS_ARGENT_CHALLENGE_E) == (uint)EncounterState.NotStarted && instance.GetData((uint)Data.BOSS_ARGENT_CHALLENGE_P) == (uint)EncounterState.NotStarted) + { + if (instance.GetData((uint)Data.BOSS_GRAND_CHAMPIONS) == (uint)EncounterState.NotStarted) + SetData((uint)Data.DATA_START, 0); + + if (instance.GetData((uint)Data.BOSS_GRAND_CHAMPIONS) == (uint)EncounterState.Done) + DoStartArgentChampionEncounter(); + } + + if ((instance.GetData((uint)Data.BOSS_GRAND_CHAMPIONS) == (uint)EncounterState.Done && + instance.GetData((uint)Data.BOSS_ARGENT_CHALLENGE_E) == (uint)EncounterState.Done) || + instance.GetData((uint)Data.BOSS_ARGENT_CHALLENGE_P) == (uint)EncounterState.Done) + me.SummonCreature(VehicleIds.BLACK_KNIGHT, 769.834f, 651.915f, 447.035f, 0); + } + } + + void AggroAllPlayers(Creature temp) + { + var PlList = me.GetMap().GetPlayers(); + + if (PlList.Empty()) + return; + + foreach (var player in PlList) + { + if (player.IsGameMaster()) + continue; + + if (player.IsAlive()) + { + temp.SetHomePosition(me.GetPositionX(), me.GetPositionY(), me.GetPositionZ(), me.GetOrientation()); + temp.RemoveFlag(UnitFields.Flags, UnitFlags.NonAttackable); + temp.SetReactState(ReactStates.Aggressive); + temp.SetInCombatWith(player); + player.SetInCombatWith(temp); + temp.AddThreat(player, 0.0f); + } + } + } + + public override void UpdateAI(uint diff) + { + base.UpdateAI(diff); + + if (uiTimer <= diff) + { + switch (uiPhase) + { + case 1: + DoSummonGrandChampion(uiSecondBoss); + NextStep(10000, true); + break; + case 2: + DoSummonGrandChampion(uiThirdBoss); + NextStep(0, false); + break; + case 3: + if (!Champion1List.Empty()) + { + foreach (var guid in Champion1List) + { + Creature summon = ObjectAccessor.GetCreature(me, guid); + if (summon) + AggroAllPlayers(summon); + } + NextStep(0, false); + } + break; + } + } + else + uiTimer -= diff; + + if (!UpdateVictim()) + return; + } + + public override void JustSummoned(Creature summon) + { + if (instance.GetData((uint)Data.BOSS_GRAND_CHAMPIONS) == (uint)EncounterState.NotStarted) + { + summon.SetFlag(UnitFields.Flags, UnitFlags.NonAttackable); + summon.SetReactState(ReactStates.Passive); + } + } + + public override void SummonedCreatureDespawn(Creature summon) + { + switch (summon.GetEntry()) + { + case VehicleIds.DARNASSIA_NIGHTSABER: + case VehicleIds.EXODAR_ELEKK: + case VehicleIds.STORMWIND_STEED: + case VehicleIds.GNOMEREGAN_MECHANOSTRIDER: + case VehicleIds.IRONFORGE_RAM: + case VehicleIds.FORSAKE_WARHORSE: + case VehicleIds.THUNDER_BLUFF_KODO: + case VehicleIds.ORGRIMMAR_WOLF: + case VehicleIds.SILVERMOON_HAWKSTRIDER: + case VehicleIds.DARKSPEAR_RAPTOR: + SetData((uint)Data.DATA_LESSER_CHAMPIONS_DEFEATED, 0); + break; + } + } + + InstanceScript instance; + + byte uiSummonTimes; + byte uiLesserChampions; + + uint uiArgentChampion; + + uint uiFirstBoss; + uint uiSecondBoss; + uint uiThirdBoss; + + uint uiPhase; + uint uiTimer; + + ObjectGuid uiVehicle1GUID; + ObjectGuid uiVehicle2GUID; + ObjectGuid uiVehicle3GUID; + + List Champion1List = new List(); + List Champion2List = new List(); + List Champion3List = new List(); + + Position SpawnPosition = new Position(746.261f, 657.401f, 411.681f, 4.65f); + } + + public override CreatureAI GetAI(Creature creature) + { + return GetInstanceAI(creature); + } + + public override bool OnGossipHello(Player player, Creature creature) + { + InstanceScript instance = creature.GetInstanceScript(); + + if (instance != null && + ((instance.GetData((uint)Data.BOSS_GRAND_CHAMPIONS) == (uint)EncounterState.Done && + instance.GetData((uint)Data.BOSS_BLACK_KNIGHT) == (uint)EncounterState.Done && + instance.GetData((uint)Data.BOSS_ARGENT_CHALLENGE_E) == (uint)EncounterState.Done) || + instance.GetData((uint)Data.BOSS_ARGENT_CHALLENGE_P) == (uint)EncounterState.Done)) + return false; + + if (instance != null && + instance.GetData((uint)Data.BOSS_GRAND_CHAMPIONS) == (uint)EncounterState.NotStarted && + instance.GetData((uint)Data.BOSS_ARGENT_CHALLENGE_E) == (uint)EncounterState.NotStarted && + instance.GetData((uint)Data.BOSS_ARGENT_CHALLENGE_P) == (uint)EncounterState.NotStarted && + instance.GetData((uint)Data.BOSS_BLACK_KNIGHT) == (uint)EncounterState.NotStarted) + player.ADD_GOSSIP_ITEM(GossipOptionIcon.Chat, TrialOfTheChampionConst.GOSSIP_START_EVENT1, eTradeskill.GossipSenderMain, eTradeskill.GossipActionInfoDef + 1); + else if (instance != null) + player.ADD_GOSSIP_ITEM(GossipOptionIcon.Chat, TrialOfTheChampionConst.GOSSIP_START_EVENT2, eTradeskill.GossipSenderMain, eTradeskill.GossipActionInfoDef + 1); + + player.SEND_GOSSIP_MENU(player.GetGossipTextId(creature), creature.GetGUID()); + + return true; + } + + public override bool OnGossipSelect(Player player, Creature creature, uint sender, uint action) + { + player.PlayerTalkClass.ClearMenus(); + if (action == eTradeskill.GossipActionInfoDef + 1) + { + player.CLOSE_GOSSIP_MENU(); + ((npc_announcer_toc5AI)creature.GetAI()).StartEncounter(); + } + + return true; + } + } +} diff --git a/Scripts/Northrend/CrusadersColiseum/TrialOfTheCrusader/LordJaraxxus.cs b/Scripts/Northrend/CrusadersColiseum/TrialOfTheCrusader/LordJaraxxus.cs new file mode 100644 index 000000000..10ac2b530 --- /dev/null +++ b/Scripts/Northrend/CrusadersColiseum/TrialOfTheCrusader/LordJaraxxus.cs @@ -0,0 +1,568 @@ +/* + * Copyright (C) 2012-2017 CypherCore + * + * 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, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +using Framework.Constants; +using Game.AI; +using Game.Entities; +using Game.Maps; +using Game.Scripting; +using Game.Spells; +using System.Collections.Generic; + +namespace Scripts.Northrend.CrusadersColiseum.TrialOfTheCrusader +{ + struct Jaraxxus + { + public const uint SayIntro = 0; + public const uint SayAggro = 1; + public const uint EmoteLegionFlame = 2; + public const uint EmoteNetherPortal = 3; + public const uint SayMistressOfPain = 4; + public const uint EmoteIncinerate = 5; + public const uint SayIncinerate = 6; + public const uint EmoteInfernalEruption = 7; + public const uint SayInfernalEruption = 8; + public const uint SayKillPlayer = 9; + public const uint SayDeath = 10; + public const uint SayBerserk = 11; + + public const uint NpcLegionFlame = 34784; + public const uint NpcInfernalVolcano = 34813; + public const uint NpcFelInfernal = 34815; // Immune To All Cc On Heroic (Stuns; Banish; Interrupt; Etc) + public const uint NpcNetherPortal = 34825; + public const uint NpcMistressOfPain = 34826; + + public const uint SpellLegionFlame = 66197; // Player Should Run Away From Raid Because He Triggers Legion Flame + public const uint SpellLegionFlameEffect = 66201; // Used By Trigger Npc + public const uint SpellNetherPower = 66228; // +20% Of Spell Damage Per Stack; Stackable Up To 5/10 Times; Must Be Dispelled/Stealed + public const uint SpellFelLighting = 66528; // Jumps To Nearby Targets + public const uint SpellFelFireball = 66532; // Does Heavy Damage To The Tank; Interruptable + public const uint SpellIncinerateFlesh = 66237; // Target Must Be Healed Or Will Trigger Burning Inferno + public const uint SpellBurningInferno = 66242; // Triggered By Incinerate Flesh + public const uint SpellInfernalEruption = 66258; // Summons Infernal Volcano + public const uint SpellInfernalEruptionEffect = 66252; // Summons Felflame Infernal (3 At Normal And Inifinity At Heroic) + public const uint SpellNetherPortal = 66269; // Summons Nether Portal + public const uint SpellNetherPortalEffect = 66263; // Summons Mistress Of Pain (1 At Normal And Infinity At Heroic) + + public const uint SpellBerserk = 64238; // Unused + + // Mistress Of Pain Spells + public const uint SpellShivanSlash = 67098; + public const uint SpellSpinningStrike = 66283; + public const uint SpellMistressKiss = 66336; + public const uint SpellFelInferno = 67047; + public const uint SpellFelStreak = 66494; + public const int SpellLordHittin = 66326; // Special Effect Preventing More Specific Spells Be Cast On The Same Player Within 10 Seconds + public const uint SpellMistressKissDamageSilence = 66359; + + // Lord Jaraxxus + public const uint EventFelFireball = 1; + public const uint EventFelLightning = 2; + public const uint EventIncinerateFlesh = 3; + public const uint EventNetherPower = 4; + public const uint EventLegionFlame = 5; + public const uint EventSummonoNetherPortal = 6; + public const uint EventSummonInfernalEruption = 7; + + // Mistress Of Pain + public const uint EventShivanSlash = 8; + public const uint EventSpinningStrike = 9; + public const uint EventMistressKiss = 10; + } + + [Script] + class boss_jaraxxus : CreatureScript + { + public + boss_jaraxxus() : base("boss_jaraxxus") { } + + class boss_jaraxxusAI : BossAI + { + public boss_jaraxxusAI(Creature creature) : base(creature, DataTypes.BossJaraxxus) { } + + public override void Reset() + { + _Reset(); + _events.ScheduleEvent(Jaraxxus.EventFelFireball, 5 * Time.InMilliseconds); + _events.ScheduleEvent(Jaraxxus.EventFelLightning, RandomHelper.URand(10 * Time.InMilliseconds, 15 * Time.InMilliseconds)); + _events.ScheduleEvent(Jaraxxus.EventIncinerateFlesh, RandomHelper.URand(20 * Time.InMilliseconds, 25 * Time.InMilliseconds)); + _events.ScheduleEvent(Jaraxxus.EventNetherPower, 40 * Time.InMilliseconds); + _events.ScheduleEvent(Jaraxxus.EventLegionFlame, 30 * Time.InMilliseconds); + _events.ScheduleEvent(Jaraxxus.EventSummonoNetherPortal, 20 * Time.InMilliseconds); + _events.ScheduleEvent(Jaraxxus.EventSummonInfernalEruption, 80 * Time.InMilliseconds); + } + + public override void JustReachedHome() + { + _JustReachedHome(); + instance.SetBossState(DataTypes.BossJaraxxus, EncounterState.Fail); + DoCast(me, Spells.JaraxxusChains); + me.SetFlag(UnitFields.Flags, UnitFlags.NonAttackable); + } + + public override void KilledUnit(Unit who) + { + if (who.IsTypeId(TypeId.Player)) + { + Talk(Jaraxxus.SayKillPlayer); + instance.SetData(DataTypes.TributeToImmortalityEligible, 0); + } + } + + public override void JustDied(Unit killer) + { + _JustDied(); + Talk(Jaraxxus.SayDeath); + } + + public override void JustSummoned(Creature summoned) + { + summons.Summon(summoned); + } + + public override void EnterCombat(Unit who) + { + _EnterCombat(); + Talk(Jaraxxus.SayAggro); + } + + public override void UpdateAI(uint diff) + { + if (!UpdateVictim()) + return; + + _events.Update(diff); + + if (me.HasUnitState(UnitState.Casting)) + return; + + _events.ExecuteEvents(eventId => + { + switch (eventId) + { + case Jaraxxus.EventFelFireball: + DoCastVictim(Jaraxxus.SpellFelFireball); + _events.ScheduleEvent(Jaraxxus.EventFelFireball, RandomHelper.URand(10 * Time.InMilliseconds, 15 * Time.InMilliseconds)); + return; + case Jaraxxus.EventFelLightning: + { + Unit target = SelectTarget(SelectAggroTarget.Random, 0, 0.0f, true, -Jaraxxus.SpellLordHittin); + if (target) + DoCast(target, Jaraxxus.SpellFelLighting); + _events.ScheduleEvent(Jaraxxus.EventFelLightning, RandomHelper.URand(10 * Time.InMilliseconds, 15 * Time.InMilliseconds)); + return; + } + case Jaraxxus.EventIncinerateFlesh: + { + Unit target = SelectTarget(SelectAggroTarget.Random, 1, 0.0f, true, -Jaraxxus.SpellLordHittin); + if (target) + { + Talk(Jaraxxus.EmoteIncinerate, target); + Talk(Jaraxxus.SayIncinerate); + DoCast(target, Jaraxxus.SpellInfernalEruption); + } + _events.ScheduleEvent(Jaraxxus.EventIncinerateFlesh, RandomHelper.URand(20 * Time.InMilliseconds, 25 * Time.InMilliseconds)); + return; + } + case Jaraxxus.EventNetherPower: + me.CastCustomSpell(Jaraxxus.SpellNetherPower, SpellValueMod.AuraStack, RaidMode(5, 10, 5, 10), me, true); + _events.ScheduleEvent(Jaraxxus.EventNetherPower, 40 * Time.InMilliseconds); + return; + case Jaraxxus.EventLegionFlame: + { + Unit target = SelectTarget(SelectAggroTarget.Random, 1, 0.0f, true, -Jaraxxus.SpellLordHittin); + if (target) + { + Talk(Jaraxxus.EmoteLegionFlame, target); + DoCast(target, Jaraxxus.SpellLegionFlame); + } + _events.ScheduleEvent(Jaraxxus.EventLegionFlame, 30 * Time.InMilliseconds); + return; + } + case Jaraxxus.EventSummonoNetherPortal: + Talk(Jaraxxus.EmoteNetherPortal); + Talk(Jaraxxus.SayMistressOfPain); + DoCast(Jaraxxus.SpellNetherPortal); + _events.ScheduleEvent(Jaraxxus.EventSummonoNetherPortal, 2 * Time.Minute * Time.InMilliseconds); + return; + case Jaraxxus.EventSummonInfernalEruption: + Talk(Jaraxxus.EmoteInfernalEruption); + Talk(Jaraxxus.SayInfernalEruption); + DoCast(Jaraxxus.SpellInfernalEruption); + _events.ScheduleEvent(Jaraxxus.EventSummonInfernalEruption, 2 * Time.Minute * Time.InMilliseconds); + return; + } + }); + + DoMeleeAttackIfReady(); + } + } + + public override CreatureAI GetAI(Creature creature) + { + return GetInstanceAI(creature); + } + } + + [Script] + class npc_legion_flame : CreatureScript + { + public npc_legion_flame() : base("npc_legion_flame") { } + + class npc_legion_flameAI : ScriptedAI + { + public npc_legion_flameAI(Creature creature) : base(creature) + { + SetCombatMovement(false); + _instance = creature.GetInstanceScript(); + } + + public override void Reset() + { + me.SetFlag(UnitFields.Flags, UnitFlags.NonAttackable | UnitFlags.NotSelectable); + me.SetInCombatWithZone(); + DoCast(Jaraxxus.SpellLegionFlameEffect); + } + + public override void UpdateAI(uint diff) + { + UpdateVictim(); + if (_instance.GetBossState(DataTypes.BossJaraxxus) != EncounterState.InProgress) + me.DespawnOrUnsummon(); + } + + InstanceScript _instance; + } + + public override CreatureAI GetAI(Creature creature) + { + return GetInstanceAI(creature); + } + } + + [Script] + class npc_infernal_volcano : CreatureScript + { + public npc_infernal_volcano() : base("npc_infernal_volcano") { } + + class npc_infernal_volcanoAI : ScriptedAI + { + public npc_infernal_volcanoAI(Creature creature) : base(creature) + { + _summons = new SummonList(me); + SetCombatMovement(false); + } + + public override void Reset() + { + me.SetReactState(ReactStates.Passive); + + if (!IsHeroic()) + me.SetFlag(UnitFields.Flags, UnitFlags.NonAttackable | UnitFlags.NotSelectable | UnitFlags.Pacified); + else + me.RemoveFlag(UnitFields.Flags, UnitFlags.NonAttackable | UnitFlags.NotSelectable | UnitFlags.Pacified); + + _summons.DespawnAll(); + } + + public override void IsSummonedBy(Unit summoner) + { + DoCast(Jaraxxus.SpellInfernalEruptionEffect); + } + + public override void JustSummoned(Creature summoned) + { + _summons.Summon(summoned); + // makes immediate corpse despawn of summoned Felflame Infernals + summoned.SetCorpseDelay(0); + } + + public override void JustDied(Unit killer) + { + // used to despawn corpse immediately + me.DespawnOrUnsummon(); + } + + public override void UpdateAI(uint diff) { } + + SummonList _summons; + } + + public override CreatureAI GetAI(Creature creature) + { + return new npc_infernal_volcanoAI(creature); + } + } + + [Script] + class npc_fel_infernal : CreatureScript + { + public npc_fel_infernal() : base("npc_fel_infernal") { } + + class npc_fel_infernalAI : ScriptedAI + { + public npc_fel_infernalAI(Creature creature) : base(creature) + { + _instance = creature.GetInstanceScript(); + } + + public override void Reset() + { + _felStreakTimer = 30 * Time.InMilliseconds; + me.SetInCombatWithZone(); + } + + public override void UpdateAI(uint diff) + { + if (_instance.GetBossState(DataTypes.BossJaraxxus) != EncounterState.InProgress) + { + me.DespawnOrUnsummon(); + return; + } + + if (!UpdateVictim()) + return; + + if (_felStreakTimer <= diff) + { + Unit target = SelectTarget(SelectAggroTarget.Random, 0, 0.0f, true); + if (target) + DoCast(target, Jaraxxus.SpellFelStreak); + _felStreakTimer = 30 * Time.InMilliseconds; + } + else + _felStreakTimer -= diff; + + DoMeleeAttackIfReady(); + } + + uint _felStreakTimer; + InstanceScript _instance; + } + + public override CreatureAI GetAI(Creature creature) + { + return GetInstanceAI(creature); + } + } + + [Script] + class npc_nether_portal : CreatureScript + { + public npc_nether_portal() : base("npc_nether_portal") { } + + class npc_nether_portalAI : ScriptedAI + { + public npc_nether_portalAI(Creature creature) : base(creature) + { + _summons = new SummonList(me); + } + + public override void Reset() + { + me.SetReactState(ReactStates.Passive); + + if (!IsHeroic()) + me.SetFlag(UnitFields.Flags, UnitFlags.NonAttackable | UnitFlags.NotSelectable | UnitFlags.Pacified); + else + me.RemoveFlag(UnitFields.Flags, UnitFlags.NonAttackable | UnitFlags.NotSelectable | UnitFlags.Pacified); + + _summons.DespawnAll(); + } + + public override void IsSummonedBy(Unit summoner) + { + DoCast(Jaraxxus.SpellNetherPortalEffect); + } + + public override void JustSummoned(Creature summoned) + { + _summons.Summon(summoned); + // makes immediate corpse despawn of summoned Mistress of Pain + summoned.SetCorpseDelay(0); + } + + public override void JustDied(Unit killer) + { + // used to despawn corpse immediately + me.DespawnOrUnsummon(); + } + + public override void UpdateAI(uint diff) { } + + SummonList _summons; + } + + public override CreatureAI GetAI(Creature creature) + { + return new npc_nether_portalAI(creature); + } + } + + [Script] + class npc_mistress_of_pain : CreatureScript + { + public npc_mistress_of_pain() : base("npc_mistress_of_pain") { } + + class npc_mistress_of_painAI : ScriptedAI + { + public npc_mistress_of_painAI(Creature creature) : base(creature) + { + _instance = creature.GetInstanceScript(); + _instance.SetData(DataTypes.MistressOfPainCount, DataTypes.Increase); + } + + public override void Reset() + { + _events.ScheduleEvent(Jaraxxus.EventShivanSlash, 30 * Time.InMilliseconds); + _events.ScheduleEvent(Jaraxxus.EventSpinningStrike, 30 * Time.InMilliseconds); + if (IsHeroic()) + _events.ScheduleEvent(Jaraxxus.EventMistressKiss, 15 * Time.InMilliseconds); + me.SetInCombatWithZone(); + } + + public override void JustDied(Unit killer) + { + _instance.SetData(DataTypes.MistressOfPainCount, DataTypes.Decrease); + } + + public override void UpdateAI(uint diff) + { + if (_instance.GetBossState(DataTypes.BossJaraxxus) != EncounterState.InProgress) + { + me.DespawnOrUnsummon(); + return; + } + + if (!UpdateVictim()) + return; + + _events.Update(diff); + + if (me.HasUnitState(UnitState.Casting)) + return; + + _events.ExecuteEvents(eventId => + { + switch (eventId) + { + case Jaraxxus.EventShivanSlash: + DoCastVictim(Jaraxxus.SpellShivanSlash); + _events.ScheduleEvent(Jaraxxus.EventShivanSlash, 30 * Time.InMilliseconds); + return; + case Jaraxxus.EventSpinningStrike: + Unit target = SelectTarget(SelectAggroTarget.Random, 0, 0.0f, true); + if (target) + DoCast(target, Jaraxxus.SpellSpinningStrike); + _events.ScheduleEvent(Jaraxxus.EventSpinningStrike, 30 * Time.InMilliseconds); + return; + case Jaraxxus.EventMistressKiss: + DoCast(me, Jaraxxus.SpellMistressKiss); + _events.ScheduleEvent(Jaraxxus.EventMistressKiss, 30 * Time.InMilliseconds); + return; + default: + break; + } + }); + + DoMeleeAttackIfReady(); + } + + InstanceScript _instance; + } + + public override CreatureAI GetAI(Creature creature) + { + return GetInstanceAI(creature); + } + } + + [Script] + class spell_mistress_kiss : SpellScriptLoader + { + public spell_mistress_kiss() : base("spell_mistress_kiss") { } + + class spell_mistress_kiss_AuraScript : AuraScript + { + public override bool Load() + { + return ValidateSpellInfo(Jaraxxus.SpellMistressKissDamageSilence); + } + + void HandleDummyTick(AuraEffect aurEff) + { + Unit caster = GetCaster(); + Unit target = GetTarget(); + if (caster && target) + { + if (target.HasUnitState(UnitState.Casting)) + { + caster.CastSpell(target, Jaraxxus.SpellMistressKissDamageSilence, true); + target.RemoveAurasDueToSpell(GetSpellInfo().Id); + } + } + } + + public override void Register() + { + OnEffectPeriodic.Add(new EffectPeriodicHandler(HandleDummyTick, 0, AuraType.PeriodicDummy)); + } + } + + public override AuraScript GetAuraScript() + { + return new spell_mistress_kiss_AuraScript(); + } + } + + [Script] + class spell_mistress_kiss_area : SpellScriptLoader + { + public spell_mistress_kiss_area() : base("spell_mistress_kiss_area") { } + + class spell_mistress_kiss_area_SpellScript : SpellScript + { + void FilterTargets(List targets) + { + // get a list of players with mana + targets.RemoveAll(unit => unit.IsTypeId(TypeId.Player) && unit.ToPlayer().getPowerType() == PowerType.Mana); + if (targets.Empty()) + return; + + WorldObject target = targets.PickRandom(); + targets.Clear(); + targets.Add(target); + } + + void HandleScript(uint effIndex) + { + GetCaster().CastSpell(GetHitUnit(), (uint)GetEffectValue(), true); + } + + public override void Register() + { + OnObjectAreaTargetSelect.Add(new ObjectAreaTargetSelectHandler(FilterTargets, 0, Targets.UnitSrcAreaEnemy)); + OnEffectHitTarget.Add(new EffectHandler(HandleScript, 0, SpellEffectName.ScriptEffect)); + } + } + + public override SpellScript GetSpellScript() + { + return new spell_mistress_kiss_area_SpellScript(); + } + } +} \ No newline at end of file diff --git a/Scripts/Northrend/CrusadersColiseum/TrialOfTheCrusader/NorthrendBeasts.cs b/Scripts/Northrend/CrusadersColiseum/TrialOfTheCrusader/NorthrendBeasts.cs new file mode 100644 index 000000000..889a81c70 --- /dev/null +++ b/Scripts/Northrend/CrusadersColiseum/TrialOfTheCrusader/NorthrendBeasts.cs @@ -0,0 +1,1106 @@ +/* + * Copyright (C) 2012-2017 CypherCore + * + * 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, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +using Framework.Constants; +using Game.AI; +using Game.Entities; +using Game.Maps; +using Game.Scripting; +using Game.Spells; + +namespace Scripts.Northrend.CrusadersColiseum.TrialOfTheCrusader +{ + public struct Beasts + { + // Gormok + public const uint EmoteSnobolled = 0; + + // Acidmaw & Dreadscale + public const uint EmoteEnrage = 0; + + // Icehowl + public const uint EmoteTrampleStart = 0; + public const uint EmoteTrampleCrash = 1; + public const uint EmoteTrampleFail = 2; + + public const uint EquipMain = 50760; + public const uint EquipOffhand = 48040; + public const uint EquipRanged = 47267; + public const int EquipDone = -1; + + public const uint ModelAcidmawStationary = 29815; + public const uint ModelAcidmawMobile = 29816; + public const uint ModelDreadscaleStationary = 26935; + public const uint ModelDreadscaleMobile = 24564; + + public const uint NpcSnoboldVassal = 34800; + public const uint NpcFireBomb = 34854; + public const uint NpcSlimePool = 35176; + public const uint MaxSnobolds = 4; + + //Gormok + public const uint SpellImpale = 66331; + public const uint SpellStaggeringStomp = 67648; + public const uint SpellRisingAnger = 66636; + //Snobold + public const uint SpellSnobolled = 66406; + public const uint SpellBatter = 66408; + public const uint SpellFireBomb = 66313; + public const uint SpellFireBomb1 = 66317; + public const uint SpellFireBombDot = 66318; + public const uint SpellHeadCrack = 66407; + + //Acidmaw & Dreadscale + public const uint SpellAcidSpit = 66880; + public const uint SpellParalyticSpray = 66901; + public const uint SpellAcidSpew = 66819; + public const uint SpellParalyticBite = 66824; + public const uint SpellSweep0 = 66794; + public const uint SpellSummonSlimepool = 66883; + public const uint SpellFireSpit = 66796; + public const uint SpellMoltenSpew = 66821; + public const uint SpellBurningBite = 66879; + public const uint SpellBurningSpray = 66902; + public const uint SpellSweep1 = 67646; + public const uint SpellEmerge0 = 66947; + public const uint SpellSubmerge0 = 66948; + public const uint SpellEnrage = 68335; + public const uint SpellSlimePoolEffect = 66882; //In 60s It Diameter Grows From 10y To 40y (R=R+0.25 Per Second) + + //Icehowl + public const uint SpellFerociousButt = 66770; + public const uint SpellMassiveCrash = 66683; + public const uint SpellWhirl = 67345; + public const uint SpellArcticBreath = 66689; + public const uint SpellTrample = 66734; + public const uint SpellFrothingRage = 66759; + public const uint SpellStaggeredDaze = 66758; + + public const int ActionEnableFireBomb = 1; + public const int ActionDisableFireBomb = 2; + + // Gormok + public const uint EventImpale = 1; + public const uint EventStaggeringStomp = 2; + public const uint EventThrow = 3; + + // Snobold + public const uint EventFireBomb = 4; + public const uint EventBatter = 5; + public const uint EventHeadCrack = 6; + + // Acidmaw & Dreadscale + public const uint EventBite = 7; + public const uint EventSpew = 8; + public const uint EventSlimePool = 9; + public const uint EventSpit = 10; + public const uint EventSpray = 11; + public const uint EventSweep = 12; + public const uint EventSubmerge = 13; + public const uint EventEmerge = 14; + public const uint EventSummonAcidmaw = 15; + + // Icehowl + public const uint EventFerociousButt = 16; + public const uint EventMassiveCrash = 17; + public const uint EventWhirl = 18; + public const uint EventArcticBreath = 19; + public const uint EventTrample = 20; + + public const byte PhaseMobile = 1; + public const byte PhaseStationary = 2; + public const byte PhaseSubmerged = 3; + } + + [Script] + class boss_gormok : CreatureScript + { + public boss_gormok() : base("boss_gormok") { } + + class boss_gormokAI : BossAI + { + public boss_gormokAI(Creature creature) : base(creature, DataTypes.BossBeasts) { } + + public override void Reset() + { + _events.ScheduleEvent(Beasts.EventImpale, RandomHelper.URand(8 * Time.InMilliseconds, 10 * Time.InMilliseconds)); + _events.ScheduleEvent(Beasts.EventStaggeringStomp, 15 * Time.InMilliseconds); + _events.ScheduleEvent(Beasts.EventThrow, RandomHelper.URand(15 * Time.InMilliseconds, 30 * Time.InMilliseconds)); + + summons.DespawnAll(); + } + + public override void EnterEvadeMode(EvadeReason why) + { + instance.DoUseDoorOrButton(instance.GetGuidData(GameObjectIds.MainGateDoor)); + base.EnterEvadeMode(why); + } + + public override void MovementInform(MovementGeneratorType type, uint id) + { + if (type != MovementGeneratorType.Point) + return; + + switch (id) + { + case 0: + instance.DoUseDoorOrButton(instance.GetGuidData(GameObjectIds.MainGateDoor)); + me.RemoveFlag(UnitFields.Flags, UnitFlags.NonAttackable | UnitFlags.NotSelectable); + me.SetReactState(ReactStates.Aggressive); + me.SetInCombatWithZone(); + break; + default: + break; + } + } + + public override void JustDied(Unit killer) + { + instance.SetData(DataTypes.TypeNorthrendBeasts, NorthrendBeasts.GormokDone); + } + + public override void JustReachedHome() + { + instance.DoUseDoorOrButton(instance.GetGuidData(GameObjectIds.MainGateDoor)); + instance.SetData(DataTypes.TypeNorthrendBeasts, (uint)EncounterState.Fail); + + me.DespawnOrUnsummon(); + } + + public override void EnterCombat(Unit who) + { + _EnterCombat(); + me.SetInCombatWithZone(); + instance.SetData(DataTypes.TypeNorthrendBeasts, NorthrendBeasts.GormokInProgress); + + for (sbyte i = 0; i < Beasts.MaxSnobolds; i++) + { + Creature pSnobold = DoSpawnCreature(Beasts.NpcSnoboldVassal, 0, 0, 0, 0, TempSummonType.CorpseDespawn, 0); + if (pSnobold) + { + pSnobold.EnterVehicle(me, i); + pSnobold.SetInCombatWithZone(); + pSnobold.GetAI().DoAction(Beasts.ActionEnableFireBomb); + } + } + } + + public override void DamageTaken(Unit who, ref uint damage) + { + // despawn the remaining passengers on death + if (damage >= me.GetHealth()) + { + for (sbyte i = 0; i < Beasts.MaxSnobolds; ++i) + { + Unit pSnobold = me.GetVehicleKit().GetPassenger(i); + if (pSnobold) + pSnobold.ToCreature().DespawnOrUnsummon(); + } + } + } + + public override void UpdateAI(uint diff) + { + if (!UpdateVictim()) + return; + + _events.Update(diff); + + if (me.HasUnitState(UnitState.Casting)) + return; + + _events.ExecuteEvents(eventId => + { + switch (eventId) + { + case Beasts.EventImpale: + DoCastVictim(Beasts.SpellImpale); + _events.ScheduleEvent(Beasts.EventImpale, RandomHelper.URand(8 * Time.InMilliseconds, 10 * Time.InMilliseconds)); + return; + case Beasts.EventStaggeringStomp: + DoCastVictim(Beasts.SpellStaggeringStomp); + _events.ScheduleEvent(Beasts.EventStaggeringStomp, 15 * Time.InMilliseconds); + return; + case Beasts.EventThrow: + for (sbyte i = 0; i < Beasts.MaxSnobolds; ++i) + { + Unit pSnobold = me.GetVehicleKit().GetPassenger(i); + if (pSnobold) + { + pSnobold.ExitVehicle(); + pSnobold.RemoveFlag(UnitFields.Flags, UnitFlags.NonAttackable | UnitFlags.NotSelectable); + pSnobold.ToCreature().SetReactState(ReactStates.Aggressive); + pSnobold.ToCreature().GetAI().DoAction(Beasts.ActionDisableFireBomb); + pSnobold.CastSpell(me, Beasts.SpellRisingAnger, true); + Talk(Beasts.EmoteSnobolled); + break; + } + } + _events.ScheduleEvent(Beasts.EventThrow, RandomHelper.URand(15 * Time.InMilliseconds, 30 * Time.InMilliseconds)); + return; + default: + return; + } + }); + + DoMeleeAttackIfReady(); + } + } + + public override CreatureAI GetAI(Creature creature) + { + return GetInstanceAI(creature); + } + } + + [Script] + class npc_snobold_vassal : CreatureScript + { + public npc_snobold_vassal() : base("npc_snobold_vassal") { } + + class npc_snobold_vassalAI : ScriptedAI + { + public npc_snobold_vassalAI(Creature creature) : base(creature) + { + _targetDied = false; + _instance = creature.GetInstanceScript(); + _instance.SetData(DataTypes.SnoboldCount, DataTypes.Increase); + } + + public override void Reset() + { + _events.ScheduleEvent(Beasts.EventBatter, 5 * Time.InMilliseconds); + _events.ScheduleEvent(Beasts.EventHeadCrack, 25 * Time.InMilliseconds); + + _targetGUID.Clear(); + _targetDied = false; + + //Workaround for Snobold + me.SetFlag(UnitFields.Flags, UnitFlags.NonAttackable | UnitFlags.NotSelectable); + } + + public override void EnterCombat(Unit who) + { + _targetGUID = who.GetGUID(); + me.TauntApply(who); + DoCast(who, Beasts.SpellSnobolled); + } + + public override void DamageTaken(Unit pDoneBy, ref uint uiDamage) + { + if (pDoneBy.GetGUID() == _targetGUID) + uiDamage = 0; + } + + public override void MovementInform(MovementGeneratorType type, uint id) + { + if (type != MovementGeneratorType.Point) + return; + + switch (id) + { + case 0: + if (_targetDied) + me.DespawnOrUnsummon(); + break; + default: + break; + } + } + + public override void JustDied(Unit killer) + { + Unit target = Global.ObjAccessor.GetPlayer(me, _targetGUID); + if (target) + if (target.IsAlive()) + target.RemoveAurasDueToSpell(Beasts.SpellSnobolled); + _instance.SetData(DataTypes.SnoboldCount, DataTypes.Decrease); + } + + public override void DoAction(int action) + { + switch (action) + { + case Beasts.ActionEnableFireBomb: + _events.ScheduleEvent(Beasts.EventFireBomb, RandomHelper.URand(5 * Time.InMilliseconds, 30 * Time.InMilliseconds)); + break; + case Beasts.ActionDisableFireBomb: + _events.CancelEvent(Beasts.EventFireBomb); + break; + default: + break; + } + } + + public override void UpdateAI(uint diff) + { + if (!UpdateVictim() || _targetDied) + return; + + Unit target = Global.ObjAccessor.GetPlayer(me, _targetGUID); + if (target) + { + if (!target.IsAlive()) + { + Unit gormok = ObjectAccessor.GetCreature(me, _instance.GetGuidData(CreatureIds.Gormok)); + if (gormok && gormok.IsAlive()) + { + SetCombatMovement(false); + _targetDied = true; + + // looping through Gormoks seats + for (sbyte i = 0; i < Beasts.MaxSnobolds; i++) + { + if (!gormok.GetVehicleKit().GetPassenger(i)) + { + me.EnterVehicle(gormok, i); + DoAction(Beasts.ActionEnableFireBomb); + break; + } + } + } + else if (target = SelectTarget(SelectAggroTarget.Random, 0, 0.0f, true)) + { + _targetGUID = target.GetGUID(); + me.GetMotionMaster().MoveJump(target, 15.0f, 15.0f); + } + } + } + + _events.Update(diff); + + if (me.HasUnitState(UnitState.Casting)) + return; + + _events.ExecuteEvents(eventId => + { + switch (eventId) + { + case Beasts.EventFireBomb: + { + if (me.GetVehicleBase()) + { + Unit fireTarget = SelectTarget(SelectAggroTarget.Random, 0, -me.GetVehicleBase().GetCombatReach(), true); + if (fireTarget) + me.CastSpell(fireTarget.GetPositionX(), fireTarget.GetPositionY(), fireTarget.GetPositionZ(), Beasts.SpellFireBomb, true); + } + _events.ScheduleEvent(Beasts.EventFireBomb, 20 * Time.InMilliseconds); + return; + } + case Beasts.EventHeadCrack: + // commented out while SPELL_SNOBOLLED gets fixed + //if (Unit target = Global.ObjAccessor.GetPlayer(me, m_uiTargetGUID)) + DoCastVictim(Beasts.SpellHeadCrack); + _events.ScheduleEvent(Beasts.EventHeadCrack, 30 * Time.InMilliseconds); + return; + case Beasts.EventBatter: + // commented out while SPELL_SNOBOLLED gets fixed + //if (Unit target = Global.ObjAccessor.GetPlayer(me, m_uiTargetGUID)) + DoCastVictim(Beasts.SpellBatter); + _events.ScheduleEvent(Beasts.EventBatter, 10 * Time.InMilliseconds); + return; + default: + return; + } + }); + + // do melee attack only when not on Gormoks back + if (!me.GetVehicleBase()) + DoMeleeAttackIfReady(); + } + + InstanceScript _instance; + ObjectGuid _targetGUID; + bool _targetDied; + } + + public override CreatureAI GetAI(Creature creature) + { + return GetInstanceAI(creature); + } + } + + [Script] + class npc_firebomb : CreatureScript + { + public npc_firebomb() : base("npc_firebomb") { } + + class npc_firebombAI : ScriptedAI + { + public npc_firebombAI(Creature creature) : base(creature) + { + _instance = creature.GetInstanceScript(); + } + + public override void Reset() + { + DoCast(me, Beasts.SpellFireBombDot, true); + SetCombatMovement(false); + me.SetReactState(ReactStates.Passive); + me.SetDisplayId(me.GetCreatureTemplate().ModelId2); + } + + public override void UpdateAI(uint diff) + { + if (_instance.GetData(DataTypes.TypeNorthrendBeasts) != NorthrendBeasts.GormokInProgress) + me.DespawnOrUnsummon(); + } + + InstanceScript _instance; + } + + public override CreatureAI GetAI(Creature creature) + { + return GetInstanceAI(creature); + } + } + + [Script] + class boss_acidmaw : CreatureScript + { + public boss_acidmaw() : base("boss_acidmaw") { } + + public class boss_acidmawAI : boss_jormungarAI + { + public boss_acidmawAI(Creature creature) : base(creature) { } + + public override void Reset() + { + base.Reset(); + BiteSpell = Beasts.SpellParalyticBite; + SpewSpell = Beasts.SpellAcidSpew; + SpitSpell = Beasts.SpellAcidSpit; + SpraySpell = Beasts.SpellParalyticSpray; + ModelStationary = Beasts.ModelAcidmawStationary; + ModelMobile = Beasts.ModelAcidmawMobile; + OtherWormEntry = CreatureIds.Dreadscale; + + WasMobile = true; + Emerge(); + } + } + + public override CreatureAI GetAI(Creature creature) + { + return GetInstanceAI(creature); + } + } + + [Script] + class boss_dreadscale : CreatureScript + { + public boss_dreadscale() : base("boss_dreadscale") { } + + public class boss_dreadscaleAI : boss_jormungarAI + { + public boss_dreadscaleAI(Creature creature) : base(creature) + { + } + + public override void Reset() + { + base.Reset(); + BiteSpell = Beasts.SpellBurningBite; + SpewSpell = Beasts.SpellMoltenSpew; + SpitSpell = Beasts.SpellFireSpit; + SpraySpell = Beasts.SpellBurningSpray; + ModelStationary = Beasts.ModelDreadscaleStationary; + ModelMobile = Beasts.ModelDreadscaleMobile; + OtherWormEntry = CreatureIds.Acidmaw; + + _events.SetPhase(Beasts.PhaseMobile); + _events.ScheduleEvent(Beasts.EventSummonAcidmaw, 3 * Time.InMilliseconds); + _events.ScheduleEvent(Beasts.EventSubmerge, 45 * Time.InMilliseconds, 0, Beasts.PhaseMobile); + WasMobile = false; + } + + public override void MovementInform(MovementGeneratorType type, uint id) + { + if (type != MovementGeneratorType.Point) + return; + + switch (id) + { + case 0: + instance.DoUseDoorOrButton(instance.GetGuidData(GameObjectIds.MainGateDoor)); + me.RemoveFlag(UnitFields.Flags, UnitFlags.NonAttackable | UnitFlags.NotSelectable); + me.SetReactState(ReactStates.Aggressive); + me.SetInCombatWithZone(); + break; + default: + break; + } + } + + public override void EnterEvadeMode(EvadeReason why) + { + instance.DoUseDoorOrButton(instance.GetGuidData(GameObjectIds.MainGateDoor)); + base.EnterEvadeMode(why); + } + + public override void JustReachedHome() + { + instance.DoUseDoorOrButton(instance.GetGuidData(GameObjectIds.MainGateDoor)); + + base.JustReachedHome(); + } + } + + public override CreatureAI GetAI(Creature creature) + { + return GetInstanceAI(creature); + } + } + + [Script] + class npc_slime_pool : CreatureScript + { + public npc_slime_pool() : base("npc_slime_pool") { } + + class npc_slime_poolAI : ScriptedAI + { + public npc_slime_poolAI(Creature creature) : base(creature) + { + _instance = creature.GetInstanceScript(); + } + + public override void Reset() + { + _cast = false; + me.SetReactState(ReactStates.Passive); + } + + public override void UpdateAI(uint diff) + { + if (!_cast) + { + _cast = true; + DoCast(me, Beasts.SpellSlimePoolEffect); + } + + if (_instance.GetData(DataTypes.TypeNorthrendBeasts) != NorthrendBeasts.SnakesInProgress && _instance.GetData(DataTypes.TypeNorthrendBeasts) != NorthrendBeasts.SnakesSpecial) + me.DespawnOrUnsummon(); + } + + InstanceScript _instance; + bool _cast; + + } + + public override CreatureAI GetAI(Creature creature) + { + return GetInstanceAI(creature); + } + } + + [Script] + class spell_gormok_fire_bomb : SpellScriptLoader + { + public spell_gormok_fire_bomb() : base("spell_gormok_fire_bomb") { } + + class spell_gormok_fire_bomb_SpellScript : SpellScript + { + void TriggerFireBomb(uint effIndex) + { + Position pos = GetExplTargetDest(); + if (pos != null) + { + Unit caster = GetCaster(); + if (caster) + caster.SummonCreature(Beasts.NpcFireBomb, pos.GetPositionX(), pos.GetPositionY(), pos.GetPositionZ(), 0, TempSummonType.TimedDespawn, 30 * Time.InMilliseconds); + } + } + + public override void Register() + { + OnEffectHit.Add(new EffectHandler(TriggerFireBomb, 0, SpellEffectName.TriggerMissile)); + } + } + + public override SpellScript GetSpellScript() + { + return new spell_gormok_fire_bomb_SpellScript(); + } + } + + [Script] + class boss_icehowl : CreatureScript + { + public boss_icehowl() : base("boss_icehowl") { } + + class boss_icehowlAI : BossAI + { + public boss_icehowlAI(Creature creature) : base(creature, DataTypes.BossBeasts) { } + + public override void Reset() + { + _events.ScheduleEvent(Beasts.EventFerociousButt, RandomHelper.URand(15 * Time.InMilliseconds, 30 * Time.InMilliseconds)); + _events.ScheduleEvent(Beasts.EventArcticBreath, RandomHelper.URand(15 * Time.InMilliseconds, 25 * Time.InMilliseconds)); + _events.ScheduleEvent(Beasts.EventWhirl, RandomHelper.URand(15 * Time.InMilliseconds, 30 * Time.InMilliseconds)); + _events.ScheduleEvent(Beasts.EventMassiveCrash, 30 * Time.InMilliseconds); + _movementFinish = false; + _trampleCast = false; + _trampleTargetGUID.Clear(); + _trampleTargetX = 0; + _trampleTargetY = 0; + _trampleTargetZ = 0; + _stage = 0; + } + + public override void JustDied(Unit killer) + { + _JustDied(); + instance.SetData(DataTypes.TypeNorthrendBeasts, NorthrendBeasts.IcehowlDone); + } + + public override void MovementInform(MovementGeneratorType type, uint id) + { + if (type != MovementGeneratorType.Point && type != MovementGeneratorType.Effect) + return; + + switch (id) + { + case 0: + if (_stage != 0) + { + if (me.GetDistance2d(MiscData.ToCCommonLoc[1].GetPositionX(), MiscData.ToCCommonLoc[1].GetPositionY()) < 6.0f) + // Middle of the room + _stage = 1; + else + { + // Landed from Hop backwards (start trample) + if (Global.ObjAccessor.GetPlayer(me, _trampleTargetGUID)) + _stage = 4; + else + _stage = 6; + } + } + break; + case 1: // Finish trample + _movementFinish = true; + break; + case 2: + instance.DoUseDoorOrButton(instance.GetGuidData(GameObjectIds.MainGateDoor)); + me.RemoveFlag(UnitFields.Flags, UnitFlags.NonAttackable | UnitFlags.NotSelectable); + me.SetReactState(ReactStates.Aggressive); + me.SetInCombatWithZone(); + break; + default: + break; + } + } + + public override void EnterEvadeMode(EvadeReason why) + { + instance.DoUseDoorOrButton(instance.GetGuidData(GameObjectIds.MainGateDoor)); + base.EnterEvadeMode(why); + } + + public override void JustReachedHome() + { + instance.DoUseDoorOrButton(instance.GetGuidData(GameObjectIds.MainGateDoor)); + instance.SetData(DataTypes.TypeNorthrendBeasts, (uint)EncounterState.Fail); + me.DespawnOrUnsummon(); + } + + public override void KilledUnit(Unit who) + { + if (who.IsTypeId(TypeId.Player)) + instance.SetData(DataTypes.TributeToImmortalityEligible, 0); + } + + public override void EnterCombat(Unit who) + { + _EnterCombat(); + instance.SetData(DataTypes.TypeNorthrendBeasts, NorthrendBeasts.IcehowlInProgress); + } + + public override void SpellHitTarget(Unit target, SpellInfo spell) + { + if (spell.Id == Beasts.SpellTrample && target.IsTypeId(TypeId.Player)) + { + if (!_trampleCast) + { + DoCast(me, Beasts.SpellFrothingRage, true); + _trampleCast = true; + } + } + } + + public override void UpdateAI(uint diff) + { + if (!UpdateVictim()) + return; + + _events.Update(diff); + + if (me.HasUnitState(UnitState.Casting)) + return; + + switch (_stage) + { + case 0: + { + _events.ExecuteEvents(eventId => + { + switch (eventId) + { + case Beasts.EventFerociousButt: + DoCastVictim(Beasts.SpellFerociousButt); + _events.ScheduleEvent(Beasts.EventFerociousButt, RandomHelper.URand(15 * Time.InMilliseconds, 30 * Time.InMilliseconds)); + return; + case Beasts.EventArcticBreath: + Unit target = SelectTarget(SelectAggroTarget.Random, 0, 0.0f, true); + if (target) + DoCast(target, Beasts.SpellArcticBreath); + return; + case Beasts.EventWhirl: + DoCastAOE(Beasts.SpellWhirl); + _events.ScheduleEvent(Beasts.EventWhirl, RandomHelper.URand(15 * Time.InMilliseconds, 30 * Time.InMilliseconds)); + return; + case Beasts.EventMassiveCrash: + me.GetMotionMaster().MoveJump(MiscData.ToCCommonLoc[1], 20.0f, 20.0f, 0); // 1: Middle of the room + SetCombatMovement(false); + me.AttackStop(); + _stage = 7; //Invalid (Do nothing more than move) + return; + default: + break; + } + }); + DoMeleeAttackIfReady(); + break; + } + case 1: + DoCastAOE(Beasts.SpellMassiveCrash); + me.StopMoving(); + me.AttackStop(); + _stage = 2; + break; + case 2: + { + Unit target = SelectTarget(SelectAggroTarget.Random, 0, 0.0f, true); + if (target) + { + me.StopMoving(); + me.AttackStop(); + _trampleTargetGUID = target.GetGUID(); + me.SetTarget(_trampleTargetGUID); + _trampleCast = false; + SetCombatMovement(false); + me.SetFlag(UnitFields.Flags, UnitFlags.NonAttackable); + me.SetControlled(true, UnitState.Root); + me.GetMotionMaster().Clear(); + me.GetMotionMaster().MoveIdle(); + _events.ScheduleEvent(Beasts.EventTrample, 4 * Time.InMilliseconds); + _stage = 3; + } + else + _stage = 6; + break; + } + case 3: + _events.ExecuteEvents(eventId => + { + switch (eventId) + { + case Beasts.EventTrample: + { + Unit target = Global.ObjAccessor.GetPlayer(me, _trampleTargetGUID); + if (target) + { + me.StopMoving(); + me.AttackStop(); + _trampleCast = false; + _trampleTargetX = target.GetPositionX(); + _trampleTargetY = target.GetPositionY(); + _trampleTargetZ = target.GetPositionZ(); + // 2: Hop Backwards + me.GetMotionMaster().MoveJump(2 * me.GetPositionX() - _trampleTargetX, 2 * me.GetPositionY() - _trampleTargetY, me.GetPositionZ(), me.GetOrientation(), 30.0f, 20.0f, 0); + me.SetControlled(false, UnitState.Root); + _stage = 7; //Invalid (Do nothing more than move) + } + else + _stage = 6; + break; + } + default: + break; + } + }); + break; + case 4: + { + me.StopMoving(); + me.AttackStop(); + + Player target = Global.ObjAccessor.GetPlayer(me, _trampleTargetGUID); + if (target) + Talk(Beasts.EmoteTrampleStart, target); + + me.GetMotionMaster().MoveCharge(_trampleTargetX, _trampleTargetY, _trampleTargetZ, 42, 1); + me.SetTarget(ObjectGuid.Empty); + _stage = 5; + break; + } + case 5: + if (_movementFinish) + { + DoCastAOE(Beasts.SpellTrample); + _movementFinish = false; + _stage = 6; + return; + } + if (_events.ExecuteEvent() == Beasts.EventTrample) + { + var lPlayers = me.GetMap().GetPlayers(); + foreach (var player in lPlayers) + { + if (player.IsAlive() && player.IsWithinDistInMap(me, 6.0f)) + { + DoCastAOE(Beasts.SpellTrample); + _events.ScheduleEvent(Beasts.EventTrample, 4 * Time.InMilliseconds); + break; + } + } + } + break; + case 6: + if (!_trampleCast) + { + DoCast(me, Beasts.SpellStaggeredDaze); + Talk(Beasts.EmoteTrampleCrash); + } + else + { + DoCast(me, Beasts.SpellFrothingRage, true); + Talk(Beasts.EmoteTrampleFail); + } + me.RemoveFlag(UnitFields.Flags, UnitFlags.NonAttackable); + SetCombatMovement(true); + me.GetMotionMaster().MovementExpired(); + me.GetMotionMaster().Clear(); + me.GetMotionMaster().MoveChase(me.GetVictim()); + AttackStart(me.GetVictim()); + _events.ScheduleEvent(Beasts.EventMassiveCrash, 40 * Time.InMilliseconds); + _events.ScheduleEvent(Beasts.EventArcticBreath, RandomHelper.URand(15 * Time.InMilliseconds, 25 * Time.InMilliseconds)); + _stage = 0; + break; + default: + break; + } + } + + float _trampleTargetX, _trampleTargetY, _trampleTargetZ; + ObjectGuid _trampleTargetGUID; + bool _movementFinish; + bool _trampleCast; + byte _stage; + } + + public override CreatureAI GetAI(Creature creature) + { + return GetInstanceAI(creature); + } + } + + class boss_jormungarAI : BossAI + { + public boss_jormungarAI(Creature creature) : base(creature, DataTypes.BossBeasts) { } + + public override void Reset() + { + Enraged = false; + + _events.ScheduleEvent(Beasts.EventSpit, RandomHelper.URand(15 * Time.InMilliseconds, 30 * Time.InMilliseconds), 0, Beasts.PhaseStationary); + _events.ScheduleEvent(Beasts.EventSpray, RandomHelper.URand(15 * Time.InMilliseconds, 30 * Time.InMilliseconds), 0, Beasts.PhaseStationary); + _events.ScheduleEvent(Beasts.EventSweep, RandomHelper.URand(15 * Time.InMilliseconds, 30 * Time.InMilliseconds), 0, Beasts.PhaseStationary); + _events.ScheduleEvent(Beasts.EventBite, RandomHelper.URand(15 * Time.InMilliseconds, 30 * Time.InMilliseconds), 0, Beasts.PhaseMobile); + _events.ScheduleEvent(Beasts.EventSpew, RandomHelper.URand(15 * Time.InMilliseconds, 30 * Time.InMilliseconds), 0, Beasts.PhaseMobile); + _events.ScheduleEvent(Beasts.EventSlimePool, 15 * Time.InMilliseconds, 0, Beasts.PhaseMobile); + } + + public override void JustDied(Unit killer) + { + Creature otherWorm = ObjectAccessor.GetCreature(me, instance.GetGuidData(OtherWormEntry)); + if (otherWorm) + { + if (!otherWorm.IsAlive()) + { + instance.SetData(DataTypes.TypeNorthrendBeasts, NorthrendBeasts.SnakesDone); + + me.DespawnOrUnsummon(); + otherWorm.DespawnOrUnsummon(); + } + else + instance.SetData(DataTypes.TypeNorthrendBeasts, NorthrendBeasts.SnakesSpecial); + } + } + + public override void JustReachedHome() + { + // prevent losing 2 attempts at once on heroics + if (instance.GetData(DataTypes.TypeNorthrendBeasts) != (uint)EncounterState.Fail) + instance.SetData(DataTypes.TypeNorthrendBeasts, (uint)EncounterState.Fail); + + me.DespawnOrUnsummon(); + } + + public override void KilledUnit(Unit who) + { + if (who.IsTypeId(TypeId.Player)) + instance.SetData(DataTypes.TributeToImmortalityEligible, 0); + } + + public override void EnterCombat(Unit who) + { + _EnterCombat(); + me.SetInCombatWithZone(); + instance.SetData(DataTypes.TypeNorthrendBeasts, NorthrendBeasts.SnakesInProgress); + } + + public override void UpdateAI(uint diff) + { + if (!UpdateVictim()) + return; + + if (!Enraged && instance.GetData(DataTypes.TypeNorthrendBeasts) == NorthrendBeasts.SnakesSpecial) + { + me.RemoveAurasDueToSpell(Beasts.SpellSubmerge0); + me.RemoveFlag(UnitFields.Flags, UnitFlags.NonAttackable | UnitFlags.NotSelectable); + DoCast(Beasts.SpellEnrage); + Enraged = true; + Talk(Beasts.EmoteEnrage); + } + + _events.Update(diff); + + if (me.HasUnitState(UnitState.Casting)) + return; + + _events.ExecuteEvents(eventId => + { + switch (eventId) + { + case Beasts.EventEmerge: + Emerge(); + return; + case Beasts.EventSubmerge: + Submerge(); + return; + case Beasts.EventBite: + DoCastVictim(BiteSpell); + _events.ScheduleEvent(Beasts.EventBite, RandomHelper.URand(15 * Time.InMilliseconds, 30 * Time.InMilliseconds), 0, Beasts.PhaseMobile); + return; + case Beasts.EventSpew: + DoCastAOE(SpewSpell); + _events.ScheduleEvent(Beasts.EventSpew, RandomHelper.URand(15 * Time.InMilliseconds, 30 * Time.InMilliseconds), 0, Beasts.PhaseMobile); + return; + case Beasts.EventSlimePool: + DoCast(me, Beasts.SpellSummonSlimepool); + _events.ScheduleEvent(Beasts.EventSlimePool, 30 * Time.InMilliseconds, 0, Beasts.PhaseMobile); + return; + case Beasts.EventSummonAcidmaw: + Creature acidmaw = me.SummonCreature(CreatureIds.Acidmaw, MiscData.ToCCommonLoc[9].GetPositionX(), MiscData.ToCCommonLoc[9].GetPositionY(), MiscData.ToCCommonLoc[9].GetPositionZ(), 5, TempSummonType.ManualDespawn); + if (acidmaw) + { + acidmaw.RemoveFlag(UnitFields.Flags, UnitFlags.NonAttackable | UnitFlags.NotSelectable); + acidmaw.SetReactState(ReactStates.Aggressive); + acidmaw.SetInCombatWithZone(); + acidmaw.CastSpell(acidmaw, Beasts.SpellEmerge0); + } + return; + case Beasts.EventSpray: + Unit target = SelectTarget(SelectAggroTarget.Random, 0, 0.0f, true); + if (target) + DoCast(target, SpraySpell); + _events.ScheduleEvent(Beasts.EventSpray, RandomHelper.URand(15 * Time.InMilliseconds, 30 * Time.InMilliseconds), 0, Beasts.PhaseStationary); + return; + case Beasts.EventSweep: + DoCastAOE(Beasts.SpellSweep0); + _events.ScheduleEvent(Beasts.EventSweep, RandomHelper.URand(15 * Time.InMilliseconds, 30 * Time.InMilliseconds), 0, Beasts.PhaseStationary); + return; + default: + return; + } + }); + if (_events.IsInPhase(Beasts.PhaseMobile)) + DoMeleeAttackIfReady(); + if (_events.IsInPhase(Beasts.PhaseStationary)) + DoSpellAttackIfReady(SpitSpell); + } + + void Submerge() + { + DoCast(me, Beasts.SpellSubmerge0); + me.RemoveAurasDueToSpell(Beasts.SpellEmerge0); + me.SetInCombatWithZone(); + _events.SetPhase(Beasts.PhaseSubmerged); + _events.ScheduleEvent(Beasts.EventEmerge, 5 * Time.InMilliseconds, 0, Beasts.PhaseSubmerged); + me.SetFlag(UnitFields.Flags, UnitFlags.NonAttackable | UnitFlags.NotSelectable); + me.GetMotionMaster().MovePoint(0, MiscData.ToCCommonLoc[1].GetPositionX() + RandomHelper.FRand(-40.0f, 40.0f), MiscData.ToCCommonLoc[1].GetPositionY() + RandomHelper.FRand(-40.0f, 40.0f), MiscData.ToCCommonLoc[1].GetPositionZ()); + WasMobile = !WasMobile; + } + + public void Emerge() + { + DoCast(me, Beasts.SpellEmerge0); + me.SetDisplayId(ModelMobile); + me.RemoveAurasDueToSpell(Beasts.SpellSubmerge0); + me.RemoveFlag(UnitFields.Flags, UnitFlags.NonAttackable | UnitFlags.NotSelectable); + me.GetMotionMaster().Clear(); + + // if the worm was mobile before submerging, make him stationary now + if (WasMobile) + { + me.SetControlled(true, UnitState.Root); + SetCombatMovement(false); + me.SetDisplayId(ModelStationary); + _events.SetPhase(Beasts.PhaseStationary); + _events.ScheduleEvent(Beasts.EventSubmerge, 45 * Time.InMilliseconds, 0, Beasts.PhaseStationary); + _events.ScheduleEvent(Beasts.EventSpit, RandomHelper.URand(15 * Time.InMilliseconds, 30 * Time.InMilliseconds), 0, Beasts.PhaseStationary); + _events.ScheduleEvent(Beasts.EventSpray, RandomHelper.URand(15 * Time.InMilliseconds, 30 * Time.InMilliseconds), 0, Beasts.PhaseStationary); + _events.ScheduleEvent(Beasts.EventSweep, RandomHelper.URand(15 * Time.InMilliseconds, 30 * Time.InMilliseconds), 0, Beasts.PhaseStationary); + } + else + { + me.SetControlled(false, UnitState.Root); + SetCombatMovement(true); + me.GetMotionMaster().MoveChase(me.GetVictim()); + me.SetDisplayId(ModelMobile); + _events.SetPhase(Beasts.PhaseMobile); + _events.ScheduleEvent(Beasts.EventSubmerge, 45 * Time.InMilliseconds, 0, Beasts.PhaseMobile); + _events.ScheduleEvent(Beasts.EventBite, RandomHelper.URand(15 * Time.InMilliseconds, 30 * Time.InMilliseconds), 0, Beasts.PhaseMobile); + _events.ScheduleEvent(Beasts.EventSpew, RandomHelper.URand(15 * Time.InMilliseconds, 30 * Time.InMilliseconds), 0, Beasts.PhaseMobile); + _events.ScheduleEvent(Beasts.EventSlimePool, 15 * Time.InMilliseconds, 0, Beasts.PhaseMobile); + } + } + + protected uint OtherWormEntry; + protected uint ModelStationary; + protected uint ModelMobile; + + protected uint BiteSpell; + protected uint SpewSpell; + protected uint SpitSpell; + protected uint SpraySpell; + + //protected uint Phase; + protected bool Enraged; + protected bool WasMobile; + } +} diff --git a/Scripts/Northrend/CrusadersColiseum/TrialOfTheCrusader/TrialOfTheCrusader.cs b/Scripts/Northrend/CrusadersColiseum/TrialOfTheCrusader/TrialOfTheCrusader.cs new file mode 100644 index 000000000..ed4142b1d --- /dev/null +++ b/Scripts/Northrend/CrusadersColiseum/TrialOfTheCrusader/TrialOfTheCrusader.cs @@ -0,0 +1,1719 @@ +/* + * Copyright (C) 2012-2017 CypherCore + * + * 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, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +using Framework.Constants; +using Framework.GameMath; +using Framework.IO; +using Game.AI; +using Game.Entities; +using Game.Maps; +using Game.Scripting; + +namespace Scripts.Northrend.CrusadersColiseum.TrialOfTheCrusader +{ + [Script] + class instance_trial_of_the_crusader : InstanceMapScript + { + public instance_trial_of_the_crusader() : base("instance_trial_of_the_crusader", 649) { } + + class instance_trial_of_the_crusader_InstanceMapScript : InstanceScript + { + public instance_trial_of_the_crusader_InstanceMapScript(Map map) : base(map) + { + SetHeaders("TCR"); + SetBossNumber(DataTypes.MaxEncounters); + LoadBossBoundaries(MiscData.boundaries); + TrialCounter = 50; + EventStage = 0; + northrendBeasts = EncounterState.NotStarted; + EventTimer = 1000; + NotOneButTwoJormungarsTimer = 0; + ResilienceWillFixItTimer = 0; + SnoboldCount = 0; + MistressOfPainCount = 0; + TributeToImmortalityEligible = true; + NeedSave = false; + } + + public override bool IsEncounterInProgress() + { + for (byte i = 0; i < DataTypes.MaxEncounters; ++i) + if (GetBossState(i) == EncounterState.InProgress) + return true; + + // Special state is set at Faction Champions after first champ dead, encounter is still in combat + if (GetBossState(DataTypes.BossCrusaders) == EncounterState.Special) + return true; + + return false; + } + + public override void OnPlayerEnter(Player player) + { + if (instance.IsHeroic()) + { + player.SendUpdateWorldState(WorldStateIds.Show, 1); + player.SendUpdateWorldState(WorldStateIds.Count, GetData(DataTypes.TypeCounter)); + } + else + player.SendUpdateWorldState(WorldStateIds.Show, 0); + + // make sure Anub'arak isnt missing and floor is destroyed after a crash + if (GetBossState(DataTypes.BossLichKing) == EncounterState.Done && TrialCounter != 0 && GetBossState(DataTypes.BossAnubarak) != EncounterState.Done) + { + Creature anubArak = ObjectAccessor.GetCreature(player, GetGuidData(CreatureIds.Anubarak)); + if (!anubArak) + anubArak = player.SummonCreature(CreatureIds.Anubarak, MiscData.AnubarakLoc[0].GetPositionX(), MiscData.AnubarakLoc[0].GetPositionY(), MiscData.AnubarakLoc[0].GetPositionZ(), 3, TempSummonType.CorpseTimedDespawn, MiscData.DespawnTime); + + GameObject floor = ObjectAccessor.GetGameObject(player, GetGuidData(GameObjectIds.ArgentColiseumFloor)); + if (floor) + floor.SetDestructibleState(GameObjectDestructibleState.Damaged); + } + } + + void OpenDoor(ObjectGuid guid) + { + if (guid.IsEmpty()) + return; + + GameObject go = instance.GetGameObject(guid); + if (go) + go.SetGoState(GameObjectState.ActiveAlternative); + } + + void CloseDoor(ObjectGuid guid) + { + if (guid.IsEmpty()) + return; + + GameObject go = instance.GetGameObject(guid); + if (go) + go.SetGoState(GameObjectState.Ready); + } + + public override void OnCreatureCreate(Creature creature) + { + switch (creature.GetEntry()) + { + case CreatureIds.Barrent: + BarrentGUID = creature.GetGUID(); + if (TrialCounter == 0) + creature.DespawnOrUnsummon(); + break; + case CreatureIds.Tirion: + TirionGUID = creature.GetGUID(); + break; + case CreatureIds.TirionFordring: + TirionFordringGUID = creature.GetGUID(); + break; + case CreatureIds.Fizzlebang: + FizzlebangGUID = creature.GetGUID(); + break; + case CreatureIds.Garrosh: + GarroshGUID = creature.GetGUID(); + break; + case CreatureIds.Varian: + VarianGUID = creature.GetGUID(); + break; + case CreatureIds.Gormok: + GormokGUID = creature.GetGUID(); + break; + case CreatureIds.Acidmaw: + AcidmawGUID = creature.GetGUID(); + break; + case CreatureIds.Dreadscale: + DreadscaleGUID = creature.GetGUID(); + break; + case CreatureIds.Icehowl: + IcehowlGUID = creature.GetGUID(); + break; + case CreatureIds.Jaraxxus: + JaraxxusGUID = creature.GetGUID(); + break; + case CreatureIds.ChampionsController: + ChampionsControllerGUID = creature.GetGUID(); + break; + case CreatureIds.Darkbane: + DarkbaneGUID = creature.GetGUID(); + break; + case CreatureIds.Lightbane: + LightbaneGUID = creature.GetGUID(); + break; + case CreatureIds.Anubarak: + AnubarakGUID = creature.GetGUID(); + break; + default: + break; + } + } + + public override void OnGameObjectCreate(GameObject go) + { + switch (go.GetEntry()) + { + case GameObjectIds.CrusadersCache10: + if (instance.GetSpawnMode() == Difficulty.Raid10N) + CrusadersCacheGUID = go.GetGUID(); + break; + case GameObjectIds.CrusadersCache25: + if (instance.GetSpawnMode() == Difficulty.Raid25N) + CrusadersCacheGUID = go.GetGUID(); + break; + case GameObjectIds.CrusadersCache10H: + if (instance.GetSpawnMode() == Difficulty.Raid10HC) + CrusadersCacheGUID = go.GetGUID(); + break; + case GameObjectIds.CrusadersCache25H: + if (instance.GetSpawnMode() == Difficulty.Raid25HC) + CrusadersCacheGUID = go.GetGUID(); + break; + case GameObjectIds.ArgentColiseumFloor: + FloorGUID = go.GetGUID(); + break; + case GameObjectIds.MainGateDoor: + MainGateDoorGUID = go.GetGUID(); + break; + case GameObjectIds.EastPortcullis: + EastPortcullisGUID = go.GetGUID(); + break; + case GameObjectIds.WebDoor: + WebDoorGUID = go.GetGUID(); + break; + + case GameObjectIds.TributeChest10h25: + case GameObjectIds.TributeChest10h45: + case GameObjectIds.TributeChest10h50: + case GameObjectIds.TributeChest10h99: + case GameObjectIds.TributeChest25h25: + case GameObjectIds.TributeChest25h45: + case GameObjectIds.TributeChest25h50: + case GameObjectIds.TributeChest25h99: + TributeChestGUID = go.GetGUID(); + break; + default: + break; + } + } + + public override bool SetBossState(uint type, EncounterState state) + { + if (!base.SetBossState(type, state)) + return false; + + switch (type) + { + case DataTypes.BossBeasts: + break; + case DataTypes.BossJaraxxus: + // Cleanup Icehowl + Creature icehowl = instance.GetCreature(IcehowlGUID); + if (icehowl) + icehowl.DespawnOrUnsummon(); + if (state == EncounterState.Done) + EventStage = 2000; + break; + case DataTypes.BossCrusaders: + { + // Cleanup Jaraxxus + Creature jaraxxus = instance.GetCreature(JaraxxusGUID); + if (jaraxxus) + jaraxxus.DespawnOrUnsummon(); + + Creature fizzlebang = instance.GetCreature(FizzlebangGUID); + if (fizzlebang) + fizzlebang.DespawnOrUnsummon(); + + switch (state) + { + case EncounterState.InProgress: + ResilienceWillFixItTimer = 0; + break; + case EncounterState.Special: //Means the first blood + ResilienceWillFixItTimer = 60 * Time.InMilliseconds; + state = EncounterState.InProgress; + break; + case EncounterState.Done: + DoUpdateCriteria(CriteriaTypes.BeSpellTarget, AchievementData.SpellDefeatFactionChampions); + if (ResilienceWillFixItTimer > 0) + DoUpdateCriteria(CriteriaTypes.BeSpellTarget, AchievementData.SpellChampionsKilledInMinute); + DoRespawnGameObject(CrusadersCacheGUID, 7 * Time.Day); + + GameObject cache = instance.GetGameObject(CrusadersCacheGUID); + if (cache) + cache.RemoveFlag(GameObjectFields.Flags, GameObjectFlags.NotSelectable); + EventStage = 3100; + break; + default: + break; + } + break; + } + case DataTypes.BossValkiries: + { + // Cleanup chest + GameObject cache = instance.GetGameObject(CrusadersCacheGUID); + if (cache) + cache.Delete(); + switch (state) + { + case EncounterState.Fail: + if (GetBossState(DataTypes.BossValkiries) == EncounterState.NotStarted) + state = EncounterState.NotStarted; + break; + case EncounterState.Special: + if (GetBossState(DataTypes.BossValkiries) == EncounterState.Special) + state = EncounterState.Done; + break; + case EncounterState.Done: + if (instance.GetPlayers()[0].GetTeam() == Team.Alliance) + EventStage = 4020; + else + EventStage = 4030; + break; + default: + break; + } + break; + } + case DataTypes.BossLichKing: + break; + case DataTypes.BossAnubarak: + switch (state) + { + case EncounterState.Done: + { + EventStage = 6000; + uint tributeChest = 0; + if (instance.GetSpawnMode() == Difficulty.Raid10HC) + { + if (TrialCounter >= 50) + tributeChest = GameObjectIds.TributeChest10h99; + else + { + if (TrialCounter >= 45) + tributeChest = GameObjectIds.TributeChest10h50; + else + { + if (TrialCounter >= 25) + tributeChest = GameObjectIds.TributeChest10h45; + else + tributeChest = GameObjectIds.TributeChest10h25; + } + } + } + else if (instance.GetSpawnMode() == Difficulty.Raid25HC) + { + if (TrialCounter >= 50) + tributeChest = GameObjectIds.TributeChest25h99; + else + { + if (TrialCounter >= 45) + tributeChest = GameObjectIds.TributeChest25h50; + else + { + if (TrialCounter >= 25) + tributeChest = GameObjectIds.TributeChest25h45; + else + tributeChest = GameObjectIds.TributeChest25h25; + } + } + } + + if (tributeChest != 0) + { + Creature tirion = instance.GetCreature(TirionGUID); + if (tirion) + { + GameObject chest = tirion.SummonGameObject(tributeChest, 805.62f, 134.87f, 142.16f, 3.27f, Quaternion.WAxis, Time.Week); + if (chest) + chest.SetRespawnTime((int)chest.GetRespawnDelay()); + } + } + break; + } + default: + break; + } + break; + default: + break; + } + + if (IsEncounterInProgress()) + { + CloseDoor(GetGuidData(GameObjectIds.EastPortcullis)); + CloseDoor(GetGuidData(GameObjectIds.WebDoor)); + } + else + { + OpenDoor(GetGuidData(GameObjectIds.EastPortcullis)); + OpenDoor(GetGuidData(GameObjectIds.WebDoor)); + } + + if (type < DataTypes.MaxEncounters) + { + Log.outInfo(LogFilter.Scripts, "[ToCr] BossState(type {0}) {1} = state {2};", type, GetBossState(type), state); + if (state == EncounterState.Fail) + { + if (instance.IsHeroic()) + { + --TrialCounter; + // decrease attempt counter at wipe + var PlayerList = instance.GetPlayers(); + foreach (var player in PlayerList) + player.SendUpdateWorldState(WorldStateIds.Count, TrialCounter); + + // if theres no more attemps allowed + if (TrialCounter == 0) + { + Unit announcer = instance.GetCreature(GetGuidData(CreatureIds.Barrent)); + if (announcer) + announcer.ToCreature().DespawnOrUnsummon(); + + Creature anubArak = instance.GetCreature(GetGuidData(CreatureIds.Anubarak)); + if (anubArak) + anubArak.DespawnOrUnsummon(); + } + } + NeedSave = true; + EventStage = (uint)(type == DataTypes.BossBeasts ? 666 : 0); + state = EncounterState.NotStarted; + } + + if (state == EncounterState.Done || NeedSave) + { + Unit announcer = instance.GetCreature(GetGuidData(CreatureIds.Barrent)); + if (announcer) + announcer.SetFlag64(UnitFields.NpcFlags, NPCFlags.Gossip); + Save(); + } + } + return true; + } + + public override void SetData(uint type, uint data) + { + switch (type) + { + case DataTypes.TypeCounter: + TrialCounter = data; + data = (uint)EncounterState.Done; + break; + case DataTypes.TypeEvent: + EventStage = data; + data = (uint)EncounterState.NotStarted; + break; + case DataTypes.TypeEventTimer: + EventTimer = data; + data = (uint)EncounterState.NotStarted; + break; + case DataTypes.TypeNorthrendBeasts: + northrendBeasts = (EncounterState)data; + switch (data) + { + case NorthrendBeasts.GormokDone: + EventStage = 200; + SetData(DataTypes.TypeNorthrendBeasts, (uint)EncounterState.InProgress); + break; + case NorthrendBeasts.SnakesInProgress: + NotOneButTwoJormungarsTimer = 0; + break; + case NorthrendBeasts.SnakesSpecial: + NotOneButTwoJormungarsTimer = 10 * Time.InMilliseconds; + break; + case NorthrendBeasts.SnakesDone: + if (NotOneButTwoJormungarsTimer > 0) + DoUpdateCriteria(CriteriaTypes.BeSpellTarget, AchievementData.SpellWormsKilledIn10Seconds); + EventStage = 300; + SetData(DataTypes.TypeNorthrendBeasts, (uint)EncounterState.InProgress); + break; + case NorthrendBeasts.IcehowlDone: + EventStage = 400; + SetData(DataTypes.TypeNorthrendBeasts, (uint)EncounterState.Done); + SetBossState(DataTypes.BossBeasts, EncounterState.Done); + break; + case (uint)EncounterState.Fail: + SetBossState(DataTypes.BossBeasts, EncounterState.Fail); + break; + default: + break; + } + break; + //Achievements + case DataTypes.SnoboldCount: + if (data == DataTypes.Increase) + ++SnoboldCount; + else if (data == DataTypes.Decrease) + --SnoboldCount; + break; + case DataTypes.MistressOfPainCount: + if (data == DataTypes.Increase) + ++MistressOfPainCount; + else if (data == DataTypes.Decrease) + --MistressOfPainCount; + break; + case DataTypes.TributeToImmortalityEligible: + TributeToImmortalityEligible = false; + break; + default: + break; + } + } + + public override ObjectGuid GetGuidData(uint type) + { + switch (type) + { + case CreatureIds.Barrent: + return BarrentGUID; + case CreatureIds.Tirion: + return TirionGUID; + case CreatureIds.TirionFordring: + return TirionFordringGUID; + case CreatureIds.Fizzlebang: + return FizzlebangGUID; + case CreatureIds.Garrosh: + return GarroshGUID; + case CreatureIds.Varian: + return VarianGUID; + case CreatureIds.Gormok: + return GormokGUID; + case CreatureIds.Acidmaw: + return AcidmawGUID; + case CreatureIds.Dreadscale: + return DreadscaleGUID; + case CreatureIds.Icehowl: + return IcehowlGUID; + case CreatureIds.Jaraxxus: + return JaraxxusGUID; + case CreatureIds.ChampionsController: + return ChampionsControllerGUID; + case CreatureIds.Darkbane: + return DarkbaneGUID; + case CreatureIds.Lightbane: + return LightbaneGUID; + case CreatureIds.Anubarak: + return AnubarakGUID; + case GameObjectIds.ArgentColiseumFloor: + return FloorGUID; + case GameObjectIds.MainGateDoor: + return MainGateDoorGUID; + case GameObjectIds.EastPortcullis: + return EastPortcullisGUID; + case GameObjectIds.WebDoor: + return WebDoorGUID; + default: + break; + } + + return ObjectGuid.Empty; + } + + public override uint GetData(uint type) + { + switch (type) + { + case DataTypes.TypeCounter: + return TrialCounter; + case DataTypes.TypeEvent: + return EventStage; + case DataTypes.TypeNorthrendBeasts: + return (uint)northrendBeasts; + case DataTypes.TypeEventTimer: + return EventTimer; + case DataTypes.TypeEventNpc: + switch (EventStage) + { + case 110: + case 140: + case 150: + case 155: + case 200: + case 205: + case 210: + case 220: + case 300: + case 305: + case 310: + case 315: + case 400: + case 666: + case 1010: + case 1180: + case 2000: + case 2030: + case 3000: + case 3001: + case 3060: + case 3061: + case 3090: + case 3091: + case 3092: + case 3100: + case 3110: + case 4000: + case 4010: + case 4015: + case 4016: + case 4040: + case 4050: + case 5000: + case 5005: + case 5020: + case 6000: + case 6005: + case 6010: + return CreatureIds.Tirion; + case 5010: + case 5030: + case 5040: + case 5050: + case 5060: + case 5070: + case 5080: + return CreatureIds.LichKing; + case 120: + case 122: + case 2020: + case 3080: + case 3051: + case 3071: + case 4020: + return CreatureIds.Varian; + case 130: + case 132: + case 2010: + case 3050: + case 3070: + case 3081: + case 4030: + return CreatureIds.Garrosh; + case 1110: + case 1120: + case 1130: + case 1132: + case 1134: + case 1135: + case 1140: + case 1142: + case 1144: + case 1150: + return CreatureIds.Fizzlebang; + default: + return CreatureIds.Tirion; + }; + default: + break; + } + + return 0; + } + + public override void Update(uint diff) + { + if (GetData(DataTypes.TypeNorthrendBeasts) == NorthrendBeasts.SnakesSpecial && NotOneButTwoJormungarsTimer != 0) + { + if (NotOneButTwoJormungarsTimer <= diff) + NotOneButTwoJormungarsTimer = 0; + else + NotOneButTwoJormungarsTimer -= diff; + } + + if (GetBossState(DataTypes.BossCrusaders) == EncounterState.Special && ResilienceWillFixItTimer != 0) + { + if (ResilienceWillFixItTimer <= diff) + ResilienceWillFixItTimer = 0; + else + ResilienceWillFixItTimer -= diff; + } + } + + void Save() + { + OUT_SAVE_INST_DATA(); + + string saveStream = ""; + + for (byte i = 0; i < DataTypes.MaxEncounters; ++i) + saveStream += GetBossState(i) + ' '; + + saveStream += TrialCounter; + SaveDataBuffer = saveStream; + + SaveToDB(); + OUT_SAVE_INST_DATA_COMPLETE(); + NeedSave = false; + } + + public override string GetSaveData() + { + return SaveDataBuffer; + } + + public override void Load(string strIn) + { + if (string.IsNullOrEmpty(strIn)) + { + OUT_LOAD_INST_DATA_FAIL(); + return; + } + + OUT_LOAD_INST_DATA(strIn); + + StringArguments loadStream = new StringArguments(strIn); + + for (byte i = 0; i < DataTypes.MaxEncounters; ++i) + { + EncounterState tmpState = (EncounterState)loadStream.NextUInt32(); + if (tmpState == EncounterState.InProgress || tmpState > EncounterState.Special) + tmpState = EncounterState.NotStarted; + SetBossState(i, tmpState); + } + + TrialCounter = loadStream.NextUInt32(); + EventStage = 0; + + OUT_LOAD_INST_DATA_COMPLETE(); + } + + public override bool CheckAchievementCriteriaMeet(uint criteria_id, Player source, Unit target, uint miscvalue1) + { + switch (criteria_id) + { + case AchievementData.UpperBackPain10Player: + case AchievementData.UpperBackPain10PlayerHeroic: + return SnoboldCount >= 2; + case AchievementData.UpperBackPain25Player: + case AchievementData.UpperBackPain25PlayerHeroic: + return SnoboldCount >= 4; + case AchievementData.ThreeSixtyPainSpike10Player: + case AchievementData.ThreeSixtyPainSpike10PlayerHeroic: + case AchievementData.ThreeSixtyPainSpike25Player: + case AchievementData.ThreeSixtyPainSpike25PlayerHeroic: + return MistressOfPainCount >= 2; + case AchievementData.ATributeToSkill10Player: + case AchievementData.ATributeToSkill25Player: + return TrialCounter >= 25; + case AchievementData.ATributeToMadSkill10Player: + case AchievementData.ATributeToMadSkill25Player: + return TrialCounter >= 45; + case AchievementData.ATributeToInsanity10Player: + case AchievementData.ATributeToInsanity25Player: + case AchievementData.RealmFirstGrandCrusader: + return TrialCounter == 50; + case AchievementData.ATributeToImmortalityAlliance: + case AchievementData.ATributeToImmortalityHorde: + return TrialCounter == 50 && TributeToImmortalityEligible; + case AchievementData.ATributeToDedicatedInsanity: + return false/*uiGrandCrusaderAttemptsLeft == 50 && !bHasAtAnyStagePlayerEquippedTooGoodItem*/; + default: + break; + } + + return false; + } + + uint TrialCounter; + uint EventStage; + uint EventTimer; + EncounterState northrendBeasts; + bool NeedSave; + string SaveDataBuffer; + + ObjectGuid BarrentGUID; + ObjectGuid TirionGUID; + ObjectGuid TirionFordringGUID; + ObjectGuid FizzlebangGUID; + ObjectGuid GarroshGUID; + ObjectGuid VarianGUID; + + ObjectGuid GormokGUID; + ObjectGuid AcidmawGUID; + ObjectGuid DreadscaleGUID; + ObjectGuid IcehowlGUID; + ObjectGuid JaraxxusGUID; + ObjectGuid ChampionsControllerGUID; + ObjectGuid DarkbaneGUID; + ObjectGuid LightbaneGUID; + ObjectGuid AnubarakGUID; + + ObjectGuid CrusadersCacheGUID; + ObjectGuid FloorGUID; + ObjectGuid TributeChestGUID; + ObjectGuid MainGateDoorGUID; + ObjectGuid EastPortcullisGUID; + ObjectGuid WebDoorGUID; + + // Achievement stuff + uint NotOneButTwoJormungarsTimer; + uint ResilienceWillFixItTimer; + byte SnoboldCount; + byte MistressOfPainCount; + bool TributeToImmortalityEligible; + } + + public override InstanceScript GetInstanceScript(InstanceMap map) + { + return new instance_trial_of_the_crusader_InstanceMapScript(map); + } + } + + [Script] + class npc_announcer_toc10 : CreatureScript + { + public npc_announcer_toc10() : base("npc_announcer_toc10") { } + + class npc_announcer_toc10AI : ScriptedAI + { + public npc_announcer_toc10AI(Creature creature) : base(creature) + { + } + + public override void Reset() + { + me.SetFlag(UnitFields.Flags, UnitFlags.NonAttackable); + Creature pAlly = GetClosestCreatureWithEntry(me, CreatureIds.Thrall, 300.0f); + if (pAlly) + pAlly.SetFlag(UnitFields.Flags, UnitFlags.NonAttackable); + + pAlly = GetClosestCreatureWithEntry(me, CreatureIds.Proudmoore, 300.0f); + if (pAlly) + pAlly.SetFlag(UnitFields.Flags, UnitFlags.NonAttackable); + } + + public override void AttackStart(Unit who) { } + } + + public override bool OnGossipHello(Player player, Creature creature) + { + InstanceScript instance = creature.GetInstanceScript(); + if (instance == null) + return true; + + string _message = "We are ready!"; + + if (player.IsInCombat() || instance.IsEncounterInProgress() || instance.GetData(DataTypes.TypeEvent) != 0) + return true; + + byte i = 0; + for (; i < MiscData._GossipMessage.Length; ++i) + { + if ((!MiscData._GossipMessage[i].state && instance.GetBossState(MiscData._GossipMessage[i].encounter) != EncounterState.Done) + || (MiscData._GossipMessage[i].state && instance.GetBossState(MiscData._GossipMessage[i].encounter) == EncounterState.Done)) + { + player.ADD_GOSSIP_ITEM(GossipOptionIcon.Chat, _message, eTradeskill.GossipSenderMain, MiscData._GossipMessage[i].id); + break; + } + } + + if (i >= MiscData._GossipMessage.Length) + return false; + + player.SEND_GOSSIP_MENU((uint)MiscData._GossipMessage[i].msgnum, creature.GetGUID()); + return true; + } + + public override bool OnGossipSelect(Player player, Creature creature, uint sender, uint action) + { + player.PlayerTalkClass.ClearMenus(); + player.CLOSE_GOSSIP_MENU(); + InstanceScript instance = creature.GetInstanceScript(); + if (instance == null) + return true; + + if (instance.GetBossState(DataTypes.BossBeasts) != EncounterState.Done) + { + instance.SetData(DataTypes.TypeEvent, 110); + instance.SetData(DataTypes.TypeNorthrendBeasts, (uint)EncounterState.NotStarted); + instance.SetBossState(DataTypes.BossBeasts, EncounterState.NotStarted); + } + else if (instance.GetBossState(DataTypes.BossJaraxxus) != EncounterState.Done) + { + // if Jaraxxus is spawned, but the raid wiped + Creature jaraxxus = ObjectAccessor.GetCreature(player, instance.GetGuidData(CreatureIds.Jaraxxus)); + if (jaraxxus) + { + jaraxxus.RemoveAurasDueToSpell(Spells.JaraxxusChains); + jaraxxus.RemoveFlag(UnitFields.Flags, UnitFlags.NonAttackable); + jaraxxus.SetReactState(ReactStates.Defensive); + jaraxxus.SetInCombatWithZone(); + } + else + { + instance.SetData(DataTypes.TypeEvent, 1010); + instance.SetBossState(DataTypes.BossJaraxxus, EncounterState.NotStarted); + } + } + else if (instance.GetBossState(DataTypes.BossCrusaders) != EncounterState.Done) + { + if (player.GetTeam() == Team.Alliance) + instance.SetData(DataTypes.TypeEvent, 3000); + else + instance.SetData(DataTypes.TypeEvent, 3001); + instance.SetBossState(DataTypes.BossCrusaders, EncounterState.NotStarted); + } + else if (instance.GetBossState(DataTypes.BossValkiries) != EncounterState.Done) + { + instance.SetData(DataTypes.TypeEvent, 4000); + instance.SetBossState(DataTypes.BossValkiries, EncounterState.NotStarted); + } + else if (instance.GetBossState(DataTypes.BossLichKing) != EncounterState.Done) + { + GameObject floor = ObjectAccessor.GetGameObject(player, instance.GetGuidData(GameObjectIds.ArgentColiseumFloor)); + if (floor) + floor.SetDestructibleState(GameObjectDestructibleState.Damaged); + + creature.CastSpell(creature, Spells.CorpseTeleport, false); + creature.CastSpell(creature, Spells.DestroyFloorKnockup, false); + + Creature anubArak = ObjectAccessor.GetCreature(creature, instance.GetGuidData(CreatureIds.Anubarak)); + if (!anubArak || !anubArak.IsAlive()) + anubArak = creature.SummonCreature(CreatureIds.Anubarak, MiscData.AnubarakLoc[0].GetPositionX(), MiscData.AnubarakLoc[0].GetPositionY(), MiscData.AnubarakLoc[0].GetPositionZ(), 3, TempSummonType.CorpseTimedDespawn, MiscData.DespawnTime); + + instance.SetBossState(DataTypes.BossAnubarak, EncounterState.NotStarted); + + if (creature.IsVisible()) + creature.SetVisible(false); + } + creature.RemoveFlag64(UnitFields.NpcFlags, NPCFlags.Gossip); + return true; + } + + public override CreatureAI GetAI(Creature creature) + { + return new npc_announcer_toc10AI(creature); + } + } + + [Script] + class boss_lich_king_toc : CreatureScript + { + public boss_lich_king_toc() : base("boss_lich_king_toc") { } + + class boss_lich_king_tocAI : ScriptedAI + { + public boss_lich_king_tocAI(Creature creature) : base(creature) + { + _instance = creature.GetInstanceScript(); + } + + public override void Reset() + { + _updateTimer = 0; + me.SetReactState(ReactStates.Passive); + Creature summoned = me.SummonCreature(CreatureIds.Trigger, MiscData.ToCCommonLoc[2].GetPositionX(), MiscData.ToCCommonLoc[2].GetPositionY(), MiscData.ToCCommonLoc[2].GetPositionZ(), 5, TempSummonType.TimedDespawn, 1 * Time.Minute * Time.InMilliseconds); + if (summoned) + { + summoned.CastSpell(summoned, 51807, false); + summoned.SetDisplayId(summoned.GetCreatureTemplate().ModelId2); + } + + _instance.SetBossState(DataTypes.BossLichKing, EncounterState.InProgress); + me.SetWalk(true); + } + + public override void MovementInform(MovementGeneratorType type, uint id) + { + if (type != MovementGeneratorType.Point || _instance == null) + return; + + switch (id) + { + case 0: + _instance.SetData(DataTypes.TypeEvent, 5030); + break; + case 1: + _instance.SetData(DataTypes.TypeEvent, 5050); + break; + default: + break; + } + } + + public override void UpdateAI(uint uiDiff) + { + if (_instance == null) + return; + + if (_instance.GetData(DataTypes.TypeEventNpc) != CreatureIds.LichKing) + return; + + _updateTimer = _instance.GetData(DataTypes.TypeEventTimer); + if (_updateTimer <= uiDiff) + { + switch (_instance.GetData(DataTypes.TypeEvent)) + { + case 5010: + Talk(Texts.Stage_4_02); + _updateTimer = 3 * Time.InMilliseconds; + me.GetMotionMaster().MovePoint(0, MiscData.LichKingLoc[0]); + _instance.SetData(DataTypes.TypeEvent, 5020); + break; + case 5030: + Talk(Texts.Stage_4_04); + me.SetUInt32Value(UnitFields.NpcEmotestate, (uint)Emote.StateTalk); + _updateTimer = 10 * Time.InMilliseconds; + _instance.SetData(DataTypes.TypeEvent, 5040); + break; + case 5040: + me.SetUInt32Value(UnitFields.NpcEmotestate, (uint)Emote.OneshotNone); + me.GetMotionMaster().MovePoint(1, MiscData.LichKingLoc[1]); + _updateTimer = 1 * Time.InMilliseconds; + _instance.SetData(DataTypes.TypeEvent, 0); + break; + case 5050: + me.HandleEmoteCommand(Emote.OneshotExclamation); + _updateTimer = 3 * Time.InMilliseconds; + _instance.SetData(DataTypes.TypeEvent, 5060); + break; + case 5060: + Talk(Texts.Stage_4_05); + me.HandleEmoteCommand(Emote.OneshotKneel); + _updateTimer = (uint)(2.5 * Time.InMilliseconds); + _instance.SetData(DataTypes.TypeEvent, 5070); + break; + case 5070: + me.CastSpell(me, 68198, false); + _updateTimer = (uint)(1.5 * Time.InMilliseconds); + _instance.SetData(DataTypes.TypeEvent, 5080); + break; + case 5080: + { + GameObject go = ObjectAccessor.GetGameObject(me, _instance.GetGuidData(GameObjectIds.ArgentColiseumFloor)); + if (go) + { + go.SetDisplayId(MiscData.DisplayIdDestroyedFloor); + go.SetFlag(GameObjectFields.Flags, GameObjectFlags.Damaged | GameObjectFlags.NoDespawn); + go.SetGoState(GameObjectState.Active); + } + + me.CastSpell(me, Spells.CorpseTeleport, false); + me.CastSpell(me, Spells.DestroyFloorKnockup, false); + + _instance.SetBossState(DataTypes.BossLichKing, EncounterState.Done); + Creature temp = ObjectAccessor.GetCreature(me, _instance.GetGuidData(CreatureIds.Anubarak)); + if (!temp || !temp.IsAlive()) + temp = me.SummonCreature(CreatureIds.Anubarak, MiscData.AnubarakLoc[0].GetPositionX(), MiscData.AnubarakLoc[0].GetPositionY(), MiscData.AnubarakLoc[0].GetPositionZ(), 3, TempSummonType.CorpseTimedDespawn, MiscData.DespawnTime); + + _instance.SetData(DataTypes.TypeEvent, 0); + + me.DespawnOrUnsummon(); + _updateTimer = 20 * Time.InMilliseconds; + break; + } + default: + break; + } + } + else + _updateTimer -= uiDiff; + + _instance.SetData(DataTypes.TypeEventTimer, _updateTimer); + } + + InstanceScript _instance; + uint _updateTimer; + } + + public override CreatureAI GetAI(Creature creature) + { + return GetInstanceAI(creature); + } + } + + [Script] + class npc_fizzlebang_toc : CreatureScript + { + public npc_fizzlebang_toc() : base("npc_fizzlebang_toc") { } + + class npc_fizzlebang_tocAI : ScriptedAI + { + public npc_fizzlebang_tocAI(Creature creature) : base(creature) + { + _summons = new SummonList(me); + _instance = me.GetInstanceScript(); + } + + public override void JustDied(Unit killer) + { + Talk(Texts.Stage_1_06, killer); + _instance.SetData(DataTypes.TypeEvent, 1180); + Creature temp = ObjectAccessor.GetCreature(me, _instance.GetGuidData(CreatureIds.Jaraxxus)); + if (temp) + { + temp.RemoveFlag(UnitFields.Flags, UnitFlags.NonAttackable); + temp.SetReactState(ReactStates.Aggressive); + temp.SetInCombatWithZone(); + } + } + + public override void Reset() + { + me.SetWalk(true); + _portalGUID.Clear(); + me.GetMotionMaster().MovePoint(1, MiscData.ToCCommonLoc[10].GetPositionX(), MiscData.ToCCommonLoc[10].GetPositionY() - 60, MiscData.ToCCommonLoc[10].GetPositionZ()); + } + + public override void MovementInform(MovementGeneratorType type, uint id) + { + if (type != MovementGeneratorType.Point) + return; + + switch (id) + { + case 1: + me.SetWalk(false); + _instance.DoUseDoorOrButton(_instance.GetGuidData(GameObjectIds.MainGateDoor)); + _instance.SetData(DataTypes.TypeEvent, 1120); + _instance.SetData(DataTypes.TypeEventTimer, 1 * Time.InMilliseconds); + break; + default: + break; + } + } + + public override void JustSummoned(Creature summoned) + { + _summons.Summon(summoned); + } + + public override void UpdateAI(uint uiDiff) + { + if (_instance == null) + return; + + if (_instance.GetData(DataTypes.TypeEventNpc) != CreatureIds.Fizzlebang) + return; + + _updateTimer = _instance.GetData(DataTypes.TypeEventTimer); + if (_updateTimer <= uiDiff) + { + switch (_instance.GetData(DataTypes.TypeEvent)) + { + case 1110: + _instance.SetData(DataTypes.TypeEvent, 1120); + _updateTimer = 4 * Time.InMilliseconds; + break; + case 1120: + Talk(Texts.Stage_1_02); + _instance.SetData(DataTypes.TypeEvent, 1130); + _updateTimer = 12 * Time.InMilliseconds; + break; + case 1130: + { + me.GetMotionMaster().MovementExpired(); + Talk(Texts.Stage_1_03); + me.HandleEmoteCommand(Emote.OneshotSpellCastOmni); + Unit pTrigger = me.SummonCreature(CreatureIds.Trigger, MiscData.ToCCommonLoc[1].GetPositionX(), MiscData.ToCCommonLoc[1].GetPositionY(), MiscData.ToCCommonLoc[1].GetPositionZ(), 4.69494f, TempSummonType.ManualDespawn); + if (pTrigger) + { + _triggerGUID = pTrigger.GetGUID(); + pTrigger.SetObjectScale(2.0f); + pTrigger.SetDisplayId(pTrigger.ToCreature().GetCreatureTemplate().ModelId1); + pTrigger.CastSpell(pTrigger, Spells.WilfredPortal, false); + } + _instance.SetData(DataTypes.TypeEvent, 1132); + _updateTimer = 4 * Time.InMilliseconds; + break; + } + case 1132: + me.GetMotionMaster().MovementExpired(); + _instance.SetData(DataTypes.TypeEvent, 1134); + _updateTimer = 4 * Time.InMilliseconds; + break; + case 1134: + { + me.HandleEmoteCommand(Emote.OneshotSpellCastOmni); + Creature pPortal = me.SummonCreature(CreatureIds.WilfredPortal, MiscData.ToCCommonLoc[1].GetPositionX(), MiscData.ToCCommonLoc[1].GetPositionY(), MiscData.ToCCommonLoc[1].GetPositionZ(), 4.71239f, TempSummonType.ManualDespawn); + if (pPortal) + { + pPortal.SetReactState(ReactStates.Passive); + pPortal.SetObjectScale(2.0f); + pPortal.CastSpell(pPortal, Spells.WilfredPortal, false); + _portalGUID = pPortal.GetGUID(); + } + _updateTimer = 4 * Time.InMilliseconds; + _instance.SetData(DataTypes.TypeEvent, 1135); + break; + } + case 1135: + _instance.SetData(DataTypes.TypeEvent, 1140); + _updateTimer = 3 * Time.InMilliseconds; + break; + case 1140: + { + Talk(Texts.Stage_1_04); + Creature temp = me.SummonCreature(CreatureIds.Jaraxxus, MiscData.ToCCommonLoc[1].GetPositionX(), MiscData.ToCCommonLoc[1].GetPositionY(), MiscData.ToCCommonLoc[1].GetPositionZ(), 5.0f, TempSummonType.CorpseTimedDespawn, MiscData.DespawnTime); + if (temp) + { + temp.SetFlag(UnitFields.Flags, UnitFlags.NonAttackable); + temp.SetReactState(ReactStates.Passive); + temp.GetMotionMaster().MovePoint(0, MiscData.ToCCommonLoc[1].GetPositionX(), MiscData.ToCCommonLoc[1].GetPositionY() - 10, MiscData.ToCCommonLoc[1].GetPositionZ()); + } + _instance.SetData(DataTypes.TypeEvent, 1142); + _updateTimer = 5 * Time.InMilliseconds; + break; + } + case 1142: + { + Creature temp = ObjectAccessor.GetCreature(me, _instance.GetGuidData(CreatureIds.Jaraxxus)); + if (temp) + temp.SetTarget(me.GetGUID()); + + Creature pTrigger = ObjectAccessor.GetCreature(me, _triggerGUID); + if (pTrigger) + pTrigger.DespawnOrUnsummon(); + + Creature pPortal = ObjectAccessor.GetCreature(me, _portalGUID); + if (pPortal) + pPortal.DespawnOrUnsummon(); + _instance.SetData(DataTypes.TypeEvent, 1144); + _updateTimer = 10 * Time.InMilliseconds; + break; + } + case 1144: + { + Creature temp = ObjectAccessor.GetCreature(me, _instance.GetGuidData(CreatureIds.Jaraxxus)); + if (temp) + temp.GetAI().Talk(Texts.Stage_1_05); + _instance.SetData(DataTypes.TypeEvent, 1150); + _updateTimer = 5 * Time.InMilliseconds; + break; + } + case 1150: + { + Creature temp = ObjectAccessor.GetCreature(me, _instance.GetGuidData(CreatureIds.Jaraxxus)); + if (temp) + { + //1-shot Fizzlebang + temp.CastSpell(me, 67888, false); + me.SetInCombatWith(temp); + temp.AddThreat(me, 1000.0f); + temp.GetAI().AttackStart(me); + } + _instance.SetData(DataTypes.TypeEvent, 1160); + _updateTimer = 3 * Time.InMilliseconds; + break; + } + } + } + else + _updateTimer -= uiDiff; + _instance.SetData(DataTypes.TypeEventTimer, _updateTimer); + } + + InstanceScript _instance; + SummonList _summons; + uint _updateTimer; + ObjectGuid _portalGUID; + ObjectGuid _triggerGUID; + } + + public override CreatureAI GetAI(Creature creature) + { + return GetInstanceAI(creature); + } + } + + [Script] + class npc_tirion_toc : CreatureScript + { + public npc_tirion_toc() : base("npc_tirion_toc") { } + + class npc_tirion_tocAI : ScriptedAI + { + public npc_tirion_tocAI(Creature creature) : base(creature) + { + _instance = me.GetInstanceScript(); + } + + public override void Reset() { } + + public override void AttackStart(Unit who) { } + + public override void UpdateAI(uint uiDiff) + { + if (_instance == null) + return; + + if (_instance.GetData(DataTypes.TypeEventNpc) != CreatureIds.Tirion) + return; + + _updateTimer = _instance.GetData(DataTypes.TypeEventTimer); + if (_updateTimer <= uiDiff) + { + switch (_instance.GetData(DataTypes.TypeEvent)) + { + case 110: + me.SetUInt32Value(UnitFields.NpcEmotestate, (uint)Emote.OneshotTalk); + Talk(Texts.Stage_0_01); + _updateTimer = 22 * Time.InMilliseconds; + _instance.SetData(DataTypes.TypeEvent, 120); + break; + case 140: + me.SetUInt32Value(UnitFields.NpcEmotestate, (uint)Emote.OneshotTalk); + Talk(Texts.Stage_0_02); + _updateTimer = 5 * Time.InMilliseconds; + _instance.SetData(DataTypes.TypeEvent, 150); + break; + case 150: + { + me.SetUInt32Value(UnitFields.NpcEmotestate, (uint)Emote.OneshotNone); + if (_instance.GetBossState(DataTypes.BossBeasts) != EncounterState.Done) + { + _instance.DoUseDoorOrButton(_instance.GetGuidData(GameObjectIds.MainGateDoor)); + + Creature temp = me.SummonCreature(CreatureIds.Gormok, MiscData.ToCSpawnLoc[0].GetPositionX(), MiscData.ToCSpawnLoc[0].GetPositionY(), MiscData.ToCSpawnLoc[0].GetPositionZ(), 5, TempSummonType.CorpseTimedDespawn, 30 * Time.InMilliseconds); + if (temp) + { + temp.GetMotionMaster().MovePoint(0, MiscData.ToCCommonLoc[5].GetPositionX(), MiscData.ToCCommonLoc[5].GetPositionY(), MiscData.ToCCommonLoc[5].GetPositionZ()); + temp.SetFlag(UnitFields.Flags, UnitFlags.NonAttackable | UnitFlags.NotSelectable); + temp.SetReactState(ReactStates.Passive); + } + } + _updateTimer = 3 * Time.InMilliseconds; + _instance.SetData(DataTypes.TypeEvent, 155); + break; + } + case 155: + // keep the raid in combat for the whole encounter, pauses included + me.SetInCombatWithZone(); + _updateTimer = 5 * Time.InMilliseconds; + _instance.SetData(DataTypes.TypeEvent, 160); + break; + case 200: + { + Talk(Texts.Stage_0_04); + if (_instance.GetBossState(DataTypes.BossBeasts) != EncounterState.Done) + { + _instance.DoUseDoorOrButton(_instance.GetGuidData(GameObjectIds.MainGateDoor)); + Creature temp = me.SummonCreature(CreatureIds.Dreadscale, MiscData.ToCSpawnLoc[1].GetPositionX(), MiscData.ToCSpawnLoc[1].GetPositionY(), MiscData.ToCSpawnLoc[1].GetPositionZ(), 5, TempSummonType.ManualDespawn); + if (temp) + { + temp.GetMotionMaster().MovePoint(0, MiscData.ToCCommonLoc[5].GetPositionX(), MiscData.ToCCommonLoc[5].GetPositionY(), MiscData.ToCCommonLoc[5].GetPositionZ()); + temp.SetFlag(UnitFields.Flags, UnitFlags.NonAttackable | UnitFlags.NotSelectable); + temp.SetReactState(ReactStates.Passive); + } + } + _updateTimer = 5 * Time.InMilliseconds; + _instance.SetData(DataTypes.TypeEvent, 220); + break; + } + case 220: + _instance.SetData(DataTypes.TypeEvent, 230); + break; + case 300: + { + Talk(Texts.Stage_0_05); + if (_instance.GetBossState(DataTypes.BossBeasts) != EncounterState.Done) + { + _instance.DoUseDoorOrButton(_instance.GetGuidData(GameObjectIds.MainGateDoor)); + Creature temp = me.SummonCreature(CreatureIds.Icehowl, MiscData.ToCSpawnLoc[0].GetPositionX(), MiscData.ToCSpawnLoc[0].GetPositionY(), MiscData.ToCSpawnLoc[0].GetPositionZ(), 5, TempSummonType.DeadDespawn); + if (temp) + { + temp.GetMotionMaster().MovePoint(2, MiscData.ToCCommonLoc[5].GetPositionX(), MiscData.ToCCommonLoc[5].GetPositionY(), MiscData.ToCCommonLoc[5].GetPositionZ()); + me.SetFlag(UnitFields.Flags, UnitFlags.NonAttackable | UnitFlags.NotSelectable); + me.SetReactState(ReactStates.Passive); + } + } + _updateTimer = 5 * Time.InMilliseconds; + _instance.SetData(DataTypes.TypeEvent, 315); + break; + } + case 315: + _instance.SetData(DataTypes.TypeEvent, 320); + break; + case 400: + Talk(Texts.Stage_0_06); + me.GetThreatManager().clearReferences(); + _updateTimer = 5 * Time.InMilliseconds; + _instance.SetData(DataTypes.TypeEvent, 0); + break; + case 666: + Talk(Texts.Stage_0_Wipe); + _updateTimer = 5 * Time.InMilliseconds; + _instance.SetData(DataTypes.TypeEvent, 0); + break; + case 1010: + Talk(Texts.Stage_1_01); + _updateTimer = 7 * Time.InMilliseconds; + _instance.DoUseDoorOrButton(_instance.GetGuidData(GameObjectIds.MainGateDoor)); + me.SummonCreature(CreatureIds.Fizzlebang, MiscData.ToCSpawnLoc[0].GetPositionX(), MiscData.ToCSpawnLoc[0].GetPositionY(), MiscData.ToCSpawnLoc[0].GetPositionZ(), 2, TempSummonType.CorpseTimedDespawn, MiscData.DespawnTime); + _instance.SetData(DataTypes.TypeEvent, 0); + break; + case 1180: + Talk(Texts.Stage_1_07); + _updateTimer = 3 * Time.InMilliseconds; + _instance.SetData(DataTypes.TypeEvent, 0); + break; + case 2000: + Talk(Texts.Stage_1_08); + _updateTimer = 18 * Time.InMilliseconds; + _instance.SetData(DataTypes.TypeEvent, 2010); + break; + case 2030: + Talk(Texts.Stage_1_11); + _updateTimer = 5 * Time.InMilliseconds; + _instance.SetData(DataTypes.TypeEvent, 0); + break; + case 3000: + Talk(Texts.Stage_2_01); + _updateTimer = 12 * Time.InMilliseconds; + _instance.SetData(DataTypes.TypeEvent, 3050); + break; + case 3001: + Talk(Texts.Stage_2_01); + _updateTimer = 10 * Time.InMilliseconds; + _instance.SetData(DataTypes.TypeEvent, 3051); + break; + case 3060: + Talk(Texts.Stage_2_03); + _updateTimer = 5 * Time.InMilliseconds; + _instance.SetData(DataTypes.TypeEvent, 3070); + break; + case 3061: + Talk(Texts.Stage_2_03); + _updateTimer = 5 * Time.InMilliseconds; + _instance.SetData(DataTypes.TypeEvent, 3071); + break; + //Summoning crusaders + case 3091: + { + Creature pChampionController = me.SummonCreature(CreatureIds.ChampionsController, MiscData.ToCCommonLoc[1]); + if (pChampionController) + pChampionController.GetAI().SetData(0, (uint)Team.Horde); + _updateTimer = 3 * Time.InMilliseconds; + _instance.SetData(DataTypes.TypeEvent, 3092); + break; + } + //Summoning crusaders + case 3090: + { + Creature pChampionController = me.SummonCreature(CreatureIds.ChampionsController, MiscData.ToCCommonLoc[1]); + if (pChampionController) + pChampionController.GetAI().SetData(0, (uint)Team.Alliance); + _updateTimer = 3 * Time.InMilliseconds; + _instance.SetData(DataTypes.TypeEvent, 3092); + break; + } + case 3092: + { + Creature pChampionController = ObjectAccessor.GetCreature(me, _instance.GetGuidData(CreatureIds.ChampionsController)); + if (pChampionController) + pChampionController.GetAI().SetData(1, (uint)EncounterState.NotStarted); + _instance.SetData(DataTypes.TypeEvent, 3095); + break; + } + //Crusaders battle end + case 3100: + Talk(Texts.Stage_2_06); + _updateTimer = 5 * Time.InMilliseconds; + _instance.SetData(DataTypes.TypeEvent, 0); + break; + case 4000: + Talk(Texts.Stage_3_01); + _updateTimer = 13 * Time.InMilliseconds; + _instance.SetData(DataTypes.TypeEvent, 4010); + break; + case 4010: + { + Talk(Texts.Stage_3_02); + Creature temp = me.SummonCreature(CreatureIds.Lightbane, MiscData.ToCSpawnLoc[1].GetPositionX(), MiscData.ToCSpawnLoc[1].GetPositionY(), MiscData.ToCSpawnLoc[1].GetPositionZ(), 5, TempSummonType.CorpseTimedDespawn, MiscData.DespawnTime); + if (temp) + { + temp.SetVisible(false); + temp.SetReactState(ReactStates.Passive); + temp.SummonCreature(CreatureIds.LightEssence, MiscData.TwinValkyrsLoc[0].GetPositionX(), MiscData.TwinValkyrsLoc[0].GetPositionY(), MiscData.TwinValkyrsLoc[0].GetPositionZ()); + temp.SummonCreature(CreatureIds.LightEssence, MiscData.TwinValkyrsLoc[1].GetPositionX(), MiscData.TwinValkyrsLoc[1].GetPositionY(), MiscData.TwinValkyrsLoc[1].GetPositionZ()); + } + + temp = me.SummonCreature(CreatureIds.Darkbane, MiscData.ToCSpawnLoc[2].GetPositionX(), MiscData.ToCSpawnLoc[2].GetPositionY(), MiscData.ToCSpawnLoc[2].GetPositionZ(), 5, TempSummonType.CorpseTimedDespawn, MiscData.DespawnTime); + if (temp) + { + temp.SetVisible(false); + temp.SetReactState(ReactStates.Passive); + temp.SummonCreature(CreatureIds.DarkEssence, MiscData.TwinValkyrsLoc[2].GetPositionX(), MiscData.TwinValkyrsLoc[2].GetPositionY(), MiscData.TwinValkyrsLoc[2].GetPositionZ()); + temp.SummonCreature(CreatureIds.DarkEssence, MiscData.TwinValkyrsLoc[3].GetPositionX(), MiscData.TwinValkyrsLoc[3].GetPositionY(), MiscData.TwinValkyrsLoc[3].GetPositionZ()); + } + _updateTimer = 3 * Time.InMilliseconds; + _instance.SetData(DataTypes.TypeEvent, 4015); + break; + } + case 4015: + { + _instance.DoUseDoorOrButton(_instance.GetGuidData(GameObjectIds.MainGateDoor)); + Creature temp = ObjectAccessor.GetCreature(me, _instance.GetGuidData(CreatureIds.Lightbane)); + if (temp) + { + temp.GetMotionMaster().MovePoint(1, MiscData.ToCCommonLoc[8].GetPositionX(), MiscData.ToCCommonLoc[8].GetPositionY(), MiscData.ToCCommonLoc[8].GetPositionZ()); + temp.SetVisible(true); + } + + temp = ObjectAccessor.GetCreature(me, _instance.GetGuidData(CreatureIds.Darkbane)); + if (temp) + { + temp.GetMotionMaster().MovePoint(1, MiscData.ToCCommonLoc[9].GetPositionX(), MiscData.ToCCommonLoc[9].GetPositionY(), MiscData.ToCCommonLoc[9].GetPositionZ()); + temp.SetVisible(true); + } + _updateTimer = 10 * Time.InMilliseconds; + _instance.SetData(DataTypes.TypeEvent, 4016); + break; + } + case 4016: + _instance.DoUseDoorOrButton(_instance.GetGuidData(GameObjectIds.MainGateDoor)); + _instance.SetData(DataTypes.TypeEvent, 4017); + break; + case 4040: + _updateTimer = 1 * Time.Minute * Time.InMilliseconds; + _instance.SetData(DataTypes.TypeEvent, 5000); + break; + case 5000: + Talk(Texts.Stage_4_01); + _updateTimer = 10 * Time.InMilliseconds; + _instance.SetData(DataTypes.TypeEvent, 5005); + break; + case 5005: + _updateTimer = 8 * Time.InMilliseconds; + _instance.SetData(DataTypes.TypeEvent, 5010); + me.SummonCreature(CreatureIds.LichKing, MiscData.ToCCommonLoc[2].GetPositionX(), MiscData.ToCCommonLoc[2].GetPositionY(), MiscData.ToCCommonLoc[2].GetPositionZ(), 5); + break; + case 5020: + Talk(Texts.Stage_4_03); + _updateTimer = 1 * Time.InMilliseconds; + _instance.SetData(DataTypes.TypeEvent, 0); + break; + case 6000: + me.SummonCreature(CreatureIds.TirionFordring, MiscData.EndSpawnLoc[0]); + me.SummonCreature(CreatureIds.ArgentMage, MiscData.EndSpawnLoc[1]); + me.SummonGameObject(GameObjectIds.PortalToDalaran, MiscData.EndSpawnLoc[2], Quaternion.WAxis, 0); + _updateTimer = 20 * Time.InMilliseconds; + _instance.SetData(DataTypes.TypeEvent, 6005); + break; + case 6005: + { + Creature tirionFordring = ObjectAccessor.GetCreature(me, _instance.GetGuidData(CreatureIds.TirionFordring)); + if (tirionFordring) + tirionFordring.GetAI().Talk(Texts.Stage_4_06); + _updateTimer = 20 * Time.InMilliseconds; + _instance.SetData(DataTypes.TypeEvent, 6010); + break; + } + case 6010: + if (IsHeroic()) + { + Creature tirionFordring = ObjectAccessor.GetCreature(me, _instance.GetGuidData(CreatureIds.TirionFordring)); + if (tirionFordring) + tirionFordring.GetAI().Talk(Texts.Stage_4_07); + _updateTimer = 1 * Time.Minute * Time.InMilliseconds; + _instance.SetBossState(DataTypes.BossAnubarak, EncounterState.Special); + _instance.SetData(DataTypes.TypeEvent, 6020); + } + else + _instance.SetData(DataTypes.TypeEvent, 6030); + break; + case 6020: + me.DespawnOrUnsummon(); + _updateTimer = 5 * Time.InMilliseconds; + _instance.SetData(DataTypes.TypeEvent, 6030); + break; + default: + break; + } + } + else + _updateTimer -= uiDiff; + _instance.SetData(DataTypes.TypeEventTimer, _updateTimer); + } + + InstanceScript _instance; + uint _updateTimer; + } + + public override CreatureAI GetAI(Creature creature) + { + return GetInstanceAI(creature); + } + } + + [Script] + class npc_garrosh_toc : CreatureScript + { + public npc_garrosh_toc() : base("npc_garrosh_toc") { } + + class npc_garrosh_tocAI : ScriptedAI + { + public npc_garrosh_tocAI(Creature creature) : base(creature) + { + _instance = me.GetInstanceScript(); + } + + public override void Reset() { } + + public override void AttackStart(Unit who) { } + + public override void UpdateAI(uint uiDiff) + { + if (_instance == null) + return; + + if (_instance.GetData(DataTypes.TypeEventNpc) != CreatureIds.Garrosh) + return; + + _updateTimer = _instance.GetData(DataTypes.TypeEventTimer); + if (_updateTimer <= uiDiff) + { + switch (_instance.GetData(DataTypes.TypeEvent)) + { + case 130: + me.SetUInt32Value(UnitFields.NpcEmotestate, (uint)Emote.OneshotTalk); + Talk(Texts.Stage_0_03h); + _updateTimer = 3 * Time.InMilliseconds; + _instance.SetData(DataTypes.TypeEvent, 132); + break; + case 132: + me.SetUInt32Value(UnitFields.NpcEmotestate, (uint)Emote.OneshotNone); + _updateTimer = 5 * Time.InMilliseconds; + _instance.SetData(DataTypes.TypeEvent, 140); + break; + case 2010: + Talk(Texts.Stage_1_09); + _updateTimer = 9 * Time.InMilliseconds; + _instance.SetData(DataTypes.TypeEvent, 2020); + break; + case 3050: + Talk(Texts.Stage_2_02h); + _updateTimer = 15 * Time.InMilliseconds; + _instance.SetData(DataTypes.TypeEvent, 3060); + break; + case 3070: + Talk(Texts.Stage_2_04h); + _updateTimer = 6 * Time.InMilliseconds; + _instance.SetData(DataTypes.TypeEvent, 3080); + break; + case 3081: + Talk(Texts.Stage_2_05h); + _updateTimer = 3 * Time.InMilliseconds; + _instance.SetData(DataTypes.TypeEvent, 3091); + break; + case 4030: + Talk(Texts.Stage_3_03h); + _updateTimer = 5 * Time.InMilliseconds; + _instance.SetData(DataTypes.TypeEvent, 4040); + break; + default: + break; + } + } + else + _updateTimer -= uiDiff; + _instance.SetData(DataTypes.TypeEventTimer, _updateTimer); + } + + InstanceScript _instance; + uint _updateTimer; + } + + public override CreatureAI GetAI(Creature creature) + { + return GetInstanceAI(creature); + } + } + + [Script] + class npc_varian_toc : CreatureScript + { + public npc_varian_toc() : base("npc_varian_toc") { } + + class npc_varian_tocAI : ScriptedAI + { + public npc_varian_tocAI(Creature creature) : base(creature) + { + _instance = me.GetInstanceScript(); + } + + public override void Reset() { } + + public override void AttackStart(Unit who) { } + + public override void UpdateAI(uint uiDiff) + { + if (_instance == null) + return; + + if (_instance.GetData(DataTypes.TypeEventNpc) != CreatureIds.Varian) + return; + + _updateTimer = _instance.GetData(DataTypes.TypeEventTimer); + if (_updateTimer <= uiDiff) + { + switch (_instance.GetData(DataTypes.TypeEvent)) + { + case 120: + me.SetUInt32Value(UnitFields.NpcEmotestate, (uint)Emote.OneshotTalk); + Talk(Texts.Stage_0_03a); + _updateTimer = 2 * Time.InMilliseconds; + _instance.SetData(DataTypes.TypeEvent, 122); + break; + case 122: + me.SetUInt32Value(UnitFields.NpcEmotestate, (uint)Emote.OneshotNone); + _updateTimer = 3 * Time.InMilliseconds; + _instance.SetData(DataTypes.TypeEvent, 130); + break; + case 2020: + Talk(Texts.Stage_1_10); + _updateTimer = 5 * Time.InMilliseconds; + _instance.SetData(DataTypes.TypeEvent, 2030); + break; + case 3051: + Talk(Texts.Stage_2_02a); + _updateTimer = 17 * Time.InMilliseconds; + _instance.SetData(DataTypes.TypeEvent, 3061); + break; + case 3071: + Talk(Texts.Stage_2_04a); + _updateTimer = 5 * Time.InMilliseconds; + _instance.SetData(DataTypes.TypeEvent, 3081); + break; + case 3080: + Talk(Texts.Stage_2_05a); + _updateTimer = 3 * Time.InMilliseconds; + _instance.SetData(DataTypes.TypeEvent, 3090); + break; + case 4020: + Talk(Texts.Stage_3_03a); + _updateTimer = 5 * Time.InMilliseconds; + _instance.SetData(DataTypes.TypeEvent, 4040); + break; + default: + break; + } + } + else + _updateTimer -= uiDiff; + _instance.SetData(DataTypes.TypeEventTimer, _updateTimer); + } + + InstanceScript _instance; + uint _updateTimer; + } + + public override CreatureAI GetAI(Creature creature) + { + return GetInstanceAI(creature); + } + } +} diff --git a/Scripts/Northrend/CrusadersColiseum/TrialOfTheCrusader/TrialOfTheCrusaderConst.cs b/Scripts/Northrend/CrusadersColiseum/TrialOfTheCrusader/TrialOfTheCrusaderConst.cs new file mode 100644 index 000000000..dc672aa6c --- /dev/null +++ b/Scripts/Northrend/CrusadersColiseum/TrialOfTheCrusader/TrialOfTheCrusaderConst.cs @@ -0,0 +1,409 @@ +/* + * Copyright (C) 2012-2017 CypherCore + * + * 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, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +using Framework.Constants; +using Game.Entities; +using Game.Maps; + +namespace Scripts.Northrend.CrusadersColiseum.TrialOfTheCrusader +{ + struct DataTypes + { + public const uint BossBeasts = 0; + public const uint BossJaraxxus = 1; + public const uint BossCrusaders = 2; + public const uint BossValkiries = 3; + public const uint BossLichKing = 4; // Not Really A Boss But Oh Well + public const uint BossAnubarak = 5; + public const uint MaxEncounters = 6; + + public const uint TypeCounter = 8; + public const uint TypeEvent = 9; + + public const uint TypeEventTimer = 101; + public const uint TypeEventNpc = 102; + public const uint TypeNorthrendBeasts = 103; + + public const uint SnoboldCount = 301; + public const uint MistressOfPainCount = 302; + public const uint TributeToImmortalityEligible = 303; + + public const uint Increase = 501; + public const uint Decrease = 502; + } + + struct Spells + { + public const uint WilfredPortal = 68424; + public const uint JaraxxusChains = 67924; + public const uint CorpseTeleport = 69016; + public const uint DestroyFloorKnockup = 68193; + } + + struct WorldStateIds + { + public const uint Show = 4390; + public const uint Count = 4389; + } + + enum AnnouncerMessages + { + Beasts = 724001, + Jaraxxus = 724002, + Crusaders = 724003, + Valkiries = 724004, + LichKing = 724005, + Anubarak = 724006 + } + + struct CreatureIds + { + public const uint Barrent = 34816; + public const uint Tirion = 34996; + public const uint TirionFordring = 36095; + public const uint ArgentMage = 36097; + public const uint Fizzlebang = 35458; + public const uint Garrosh = 34995; + public const uint Varian = 34990; + public const uint LichKing = 35877; + + public const uint Thrall = 34994; + public const uint Proudmoore = 34992; + public const uint WilfredPortal = 17965; + public const uint Trigger = 35651; + + public const uint Icehowl = 34797; + public const uint Gormok = 34796; + public const uint Dreadscale = 34799; + public const uint Acidmaw = 35144; + + public const uint Jaraxxus = 34780; + + public const uint ChampionsController = 34781; + + public const uint AllianceDeathKnight = 34461; + public const uint AllianceDruidBalance = 34460; + public const uint AllianceDruidRestoration = 34469; + public const uint AllianceHunter = 34467; + public const uint AllianceMage = 34468; + public const uint AlliancePaladinHoly = 34465; + public const uint AlliancePaladinRetribution = 34471; + public const uint AlliancePriestDiscipline = 34466; + public const uint AlliancePriestShadow = 34473; + public const uint AllianceRogue = 34472; + public const uint AllianceShamanEnhancement = 34463; + public const uint AllianceShamanRestoration = 34470; + public const uint AllianceWarlock = 34474; + public const uint AllianceWarrior = 34475; + + public const uint HordeDeathKnight = 34458; + public const uint HordeDruidBalance = 34451; + public const uint HordeDruidRestoration = 34459; + public const uint HordeHunter = 34448; + public const uint HordeMage = 34449; + public const uint HordePaladinHoly = 34445; + public const uint HordePaladinRetribution = 34456; + public const uint HordePriestDiscipline = 34447; + public const uint HordePriestShadow = 34441; + public const uint HordeRogue = 34454; + public const uint HordeShamanEnhancement = 34455; + public const uint HordeShamanRestoration = 34444; + public const uint HordeWarlock = 34450; + public const uint HordeWarrior = 34453; + + public const uint Lightbane = 34497; + public const uint Darkbane = 34496; + + public const uint DarkEssence = 34567; + public const uint LightEssence = 34568; + + public const uint Anubarak = 34564; + }; + + struct GameObjectIds + { + public const uint CrusadersCache10 = 195631; + public const uint CrusadersCache25 = 195632; + public const uint CrusadersCache10H = 195633; + public const uint CrusadersCache25H = 195635; + + // Tribute Chest (Heroic) + // 10-Man Modes + public const uint TributeChest10h25 = 195668; // 10man 01-24 Attempts + public const uint TributeChest10h45 = 195667; // 10man 25-44 Attempts + public const uint TributeChest10h50 = 195666; // 10man 45-49 Attempts + public const uint TributeChest10h99 = 195665; // 10man 50 Attempts + // 25-Man Modes + public const uint TributeChest25h25 = 195672; // 25man 01-24 Attempts + public const uint TributeChest25h45 = 195671; // 25man 25-44 Attempts + public const uint TributeChest25h50 = 195670; // 25man 45-49 Attempts + public const uint TributeChest25h99 = 195669; // 25man 50 Attempts + + public const uint ArgentColiseumFloor = 195527; //20943 + public const uint MainGateDoor = 195647; + public const uint EastPortcullis = 195648; + public const uint WebDoor = 195485; + public const uint PortalToDalaran = 195682; + } + + struct AchievementData + { + // Northrend Beasts + public const uint UpperBackPain10Player = 11779; + public const uint UpperBackPain10PlayerHeroic = 11802; + public const uint UpperBackPain25Player = 11780; + public const uint UpperBackPain25PlayerHeroic = 11801; + // Lord Jaraxxus + public const uint ThreeSixtyPainSpike10Player = 11838; + public const uint ThreeSixtyPainSpike10PlayerHeroic = 11861; + public const uint ThreeSixtyPainSpike25Player = 11839; + public const uint ThreeSixtyPainSpike25PlayerHeroic = 11862; + // Tribute + public const uint ATributeToSkill10Player = 12344; + public const uint ATributeToSkill25Player = 12338; + public const uint ATributeToMadSkill10Player = 12347; + public const uint ATributeToMadSkill25Player = 12341; + public const uint ATributeToInsanity10Player = 12349; + public const uint ATributeToInsanity25Player = 12343; + public const uint ATributeToImmortalityHorde = 12358; + public const uint ATributeToImmortalityAlliance = 12359; + public const uint ATributeToDedicatedInsanity = 12360; + public const uint RealmFirstGrandCrusader = 12350; + + // Dummy Spells - Not Existing In Dbc But We Don'T Need That + public const uint SpellWormsKilledIn10Seconds = 68523; + public const uint SpellChampionsKilledInMinute = 68620; + public const uint SpellDefeatFactionChampions = 68184; + public const uint SpellTraitorKing10 = 68186; + public const uint SpellTraitorKing25 = 68515; + + // Timed Events + public const uint EventStartTwinsFight = 21853; + } + + struct NorthrendBeasts + { + public const uint GormokInProgress = 1000; + public const uint GormokDone = 1001; + public const uint SnakesInProgress = 2000; + public const uint DreadscaleSubmerged = 2001; + public const uint AcidmawSubmerged = 2002; + public const uint SnakesSpecial = 2003; + public const uint SnakesDone = 2004; + public const uint IcehowlInProgress = 3000; + public const uint IcehowlDone = 3001; + } + + struct Texts + { + // Highlord Tirion Fordring - 34996 + public const uint Stage_0_01 = 0; + public const uint Stage_0_02 = 1; + public const uint Stage_0_04 = 2; + public const uint Stage_0_05 = 3; + public const uint Stage_0_06 = 4; + public const uint Stage_0_Wipe = 5; + public const uint Stage_1_01 = 6; + public const uint Stage_1_07 = 7; + public const uint Stage_1_08 = 8; + public const uint Stage_1_11 = 9; + public const uint Stage_2_01 = 10; + public const uint Stage_2_03 = 11; + public const uint Stage_2_06 = 12; + public const uint Stage_3_01 = 13; + public const uint Stage_3_02 = 14; + public const uint Stage_4_01 = 15; + public const uint Stage_4_03 = 16; + + // Varian Wrynn + public const uint Stage_0_03a = 0; + public const uint Stage_1_10 = 1; + public const uint Stage_2_02a = 2; + public const uint Stage_2_04a = 3; + public const uint Stage_2_05a = 4; + public const uint Stage_3_03a = 5; + + // Garrosh + public const uint Stage_0_03h = 0; + public const uint Stage_1_09 = 1; + public const uint Stage_2_02h = 2; + public const uint Stage_2_04h = 3; + public const uint Stage_2_05h = 4; + public const uint Stage_3_03h = 5; + + // Wilfred Fizzlebang + public const uint Stage_1_02 = 0; + public const uint Stage_1_03 = 1; + public const uint Stage_1_04 = 2; + public const uint Stage_1_06 = 3; + + // Lord Jaraxxus + public const uint Stage_1_05 = 0; + + // The Lich King + public const uint Stage_4_02 = 0; + public const uint Stage_4_05 = 1; + public const uint Stage_4_04 = 2; + + // Highlord Tirion Fordring - 36095 + public const uint Stage_4_06 = 0; + public const uint Stage_4_07 = 1; + } + + struct MiscData + { + public const uint DespawnTime = 300000; + + public const uint DisplayIdDestroyedFloor = 9060; + + public static BossBoundaryEntry[] boundaries = + { + new BossBoundaryEntry(DataTypes.BossBeasts, new CircleBoundary(new Position(563.26f, 139.6f), 75.0)), + new BossBoundaryEntry(DataTypes.BossJaraxxus, new CircleBoundary(new Position(563.26f, 139.6f), 75.0)), + new BossBoundaryEntry(DataTypes.BossCrusaders, new CircleBoundary(new Position(563.26f, 139.6f), 75.0)), + new BossBoundaryEntry(DataTypes.BossValkiries, new CircleBoundary(new Position(563.26f, 139.6f), 75.0)), + new BossBoundaryEntry(DataTypes.BossAnubarak, new EllipseBoundary(new Position(746.0f, 135.0f), 100.0, 75.0)) + }; + + public static _Messages[] _GossipMessage = + { + new _Messages(AnnouncerMessages.Beasts, eTradeskill.GossipActionInfoDef + 1, false, DataTypes.BossBeasts), + new _Messages(AnnouncerMessages.Jaraxxus, eTradeskill.GossipActionInfoDef + 2, false, DataTypes.BossJaraxxus), + new _Messages(AnnouncerMessages.Crusaders, eTradeskill.GossipActionInfoDef + 3, false, DataTypes.BossCrusaders), + new _Messages(AnnouncerMessages.Valkiries, eTradeskill.GossipActionInfoDef + 4, false, DataTypes.BossValkiries), + new _Messages(AnnouncerMessages.LichKing, eTradeskill.GossipActionInfoDef + 5, false, DataTypes.BossAnubarak), + new _Messages(AnnouncerMessages.Anubarak, eTradeskill.GossipActionInfoDef + 6, true, DataTypes.BossAnubarak) + }; + + public static Position[] ToCSpawnLoc = + { + new Position(563.912f, 261.625f, 394.73f, 4.70437f), // 0 Center + new Position( 575.451f, 261.496f, 394.73f, 4.6541f), // 1 Left + new Position( 549.951f, 261.55f, 394.73f, 4.74835f) // 2 Right + }; + + public static Position[] ToCCommonLoc = + { + new Position(559.257996f, 90.266197f, 395.122986f, 0), // 0 Barrent + + new Position(563.672974f, 139.571f, 393.837006f, 0), // 1 Center + new Position(563.833008f, 187.244995f, 394.5f, 0), // 2 Backdoor + new Position(577.347839f, 195.338888f, 395.14f, 0), // 3 - Right + new Position(550.955933f, 195.338888f, 395.14f, 0), // 4 - Left + new Position(563.833008f, 195.244995f, 394.585561f, 0), // 5 - Center + new Position(573.5f, 180.5f, 395.14f, 0), // 6 Move 0 Right + new Position(553.5f, 180.5f, 395.14f, 0), // 7 Move 0 Left + new Position(573.0f, 170.0f, 395.14f, 0), // 8 Move 1 Right + new Position(555.5f, 170.0f, 395.14f, 0), // 9 Move 1 Left + new Position(563.8f, 216.1f, 395.1f, 0), // 10 Behind the door + + new Position(575.042358f, 195.260727f, 395.137146f, 0), // 5 + new Position(552.248901f, 195.331955f, 395.132658f, 0), // 6 + new Position(573.342285f, 195.515823f, 395.135956f, 0), // 7 + new Position(554.239929f, 195.825577f, 395.137909f, 0), // 8 + new Position(571.042358f, 195.260727f, 395.137146f, 0), // 9 + new Position(556.720581f, 195.015472f, 395.132658f, 0), // 10 + new Position(569.534119f, 195.214478f, 395.139526f, 0), // 11 + new Position(569.231201f, 195.941071f, 395.139526f, 0), // 12 + new Position(558.811610f, 195.985779f, 394.671661f, 0), // 13 + new Position(567.641724f, 195.351501f, 394.659943f, 0), // 14 + new Position(560.633972f, 195.391708f, 395.137543f, 0), // 15 + new Position(565.816956f, 195.477921f, 395.136810f, 0) // 16 + }; + + public static Position[] JaraxxusLoc = + { + new Position(508.104767f, 138.247345f, 395.128052f, 0), // 0 - Fizzlebang start location + new Position(548.610596f, 139.807800f, 394.321838f, 0), // 1 - fizzlebang end + new Position(581.854187f, 138.0f, 394.319f, 0), // 2 - Portal Right + new Position(550.558838f, 138.0f, 394.319f, 0) // 3 - Portal Left + }; + + public static Position[] FactionChampionLoc = + { + new Position(514.231f, 105.569f, 418.234f, 0), // 0 - Horde Initial Pos 0 + new Position(508.334f, 115.377f, 418.234f, 0), // 1 - Horde Initial Pos 1 + new Position(506.454f, 126.291f, 418.234f, 0), // 2 - Horde Initial Pos 2 + new Position(506.243f, 106.596f, 421.592f, 0), // 3 - Horde Initial Pos 3 + new Position(499.885f, 117.717f, 421.557f, 0), // 4 - Horde Initial Pos 4 + + new Position(613.127f, 100.443f, 419.74f, 0), // 5 - Ally Initial Pos 0 + new Position(621.126f, 128.042f, 418.231f, 0), // 6 - Ally Initial Pos 1 + new Position(618.829f, 113.606f, 418.232f, 0), // 7 - Ally Initial Pos 2 + new Position(625.845f, 112.914f, 421.575f, 0), // 8 - Ally Initial Pos 3 + new Position(615.566f, 109.653f, 418.234f, 0), // 9 - Ally Initial Pos 4 + + new Position(535.469f, 113.012f, 394.66f, 0), // 10 - Horde Final Pos 0 + new Position(526.417f, 137.465f, 394.749f, 0), // 11 - Horde Final Pos 1 + new Position(528.108f, 111.057f, 395.289f, 0), // 12 - Horde Final Pos 2 + new Position(519.92f, 134.285f, 395.289f, 0), // 13 - Horde Final Pos 3 + new Position(533.648f, 119.148f, 394.646f, 0), // 14 - Horde Final Pos 4 + new Position(531.399f, 125.63f, 394.708f, 0), // 15 - Horde Final Pos 5 + new Position(528.958f, 131.47f, 394.73f, 0), // 16 - Horde Final Pos 6 + new Position(526.309f, 116.667f, 394.833f, 0), // 17 - Horde Final Pos 7 + new Position(524.238f, 122.411f, 394.819f, 0), // 18 - Horde Final Pos 8 + new Position(521.901f, 128.488f, 394.832f, 0) // 19 - Horde Final Pos 9 + }; + + public static Position[] TwinValkyrsLoc = + { + new Position(586.060242f, 117.514809f, 394.41f, 0), // 0 - Dark essence 1 + new Position(541.602112f, 161.879837f, 394.41f, 0), // 1 - Dark essence 2 + new Position(541.021118f, 117.262932f, 394.41f, 0), // 2 - Light essence 1 + new Position(586.200562f, 162.145523f, 394.41f, 0) // 3 - Light essence 2 + }; + + public static Position[] LichKingLoc = + { + new Position(563.549f, 152.474f, 394.393f, 0), // 0 - Lich king start + new Position(563.547f, 141.613f, 393.908f, 0) // 1 - Lich king end + }; + + public static Position[] AnubarakLoc = + { + new Position(787.932556f, 133.289780f, 142.612152f, 0), // 0 - Anub'arak start location + new Position(695.240051f, 137.834824f, 142.200000f, 0), // 1 - Anub'arak move point location + new Position(694.886353f, 102.484665f, 142.119614f, 0), // 3 - Nerub Spawn + new Position(694.500671f, 185.363968f, 142.117905f, 0), // 5 - Nerub Spawn + new Position(731.987244f, 83.3824690f, 142.119614f, 0), // 2 - Nerub Spawn + new Position(740.184509f, 193.443390f, 142.117584f, 0) // 4 - Nerub Spawn + }; + + public static Position[] EndSpawnLoc = + { + new Position(648.9167f, 131.0208f, 141.6161f, 0), // 0 - Highlord Tirion Fordring + new Position(649.1614f, 142.0399f, 141.3057f, 0), // 1 - Argent Mage + new Position(644.6250f, 149.2743f, 140.6015f, 0) // 2 - Portal to Dalaran + }; + } + + struct _Messages + { + public _Messages(AnnouncerMessages _msg, uint _id, bool _state, uint _encounter) + { + msgnum = _msg; + id = _id; + state = _state; + encounter = _encounter; + } + + public AnnouncerMessages msgnum; + public uint id; + public bool state; + public uint encounter; + } +} diff --git a/Scripts/Northrend/Dalaran.cs b/Scripts/Northrend/Dalaran.cs new file mode 100644 index 000000000..c6472ebb2 --- /dev/null +++ b/Scripts/Northrend/Dalaran.cs @@ -0,0 +1,223 @@ +/* + * Copyright (C) 2012-2017 CypherCore + * + * 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, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +using Framework.Constants; +using Framework.Database; +using Game.AI; +using Game.Entities; +using Game.Mails; +using Game.Scripting; +using System.Collections.Generic; + +namespace Scripts.Northrend +{ + struct DalaranConst + { + //Mageguard + public const uint SpellTrespasserA = 54028; + public const uint SpellTrespasserH = 54029; + + public const uint SpellSunreaverDisguiseFemale = 70973; + public const uint SpellSunreaverDisguiseMale = 70974; + public const uint SpellSilverConenantDisguiseFemale = 70971; + public const uint SpellSilverConenantDisguiseMale = 70972; + + public const int NpcAplleboughA = 29547; + public const int NpcSweetberryH = 29715; + + //Minigob + public const int SpellManabonked = 61834; + public const int SpellTeleportVisual = 51347; + public const int SpellImprovedBlink = 61995; + + public const int EventSelectTarget = 1; + public const int EventBlink = 2; + public const int EventDespawnVisual = 3; + public const int EventDespawn = 4; + + public const int MailMinigobEntry = 264; + public const int MailDeliverDelayMin = 5 * Time.Minute; + public const int MailDeliverDelayMax = 15 * Time.Minute; + } + + [Script] + class npc_mageguard_dalaran : CreatureScript + { + public npc_mageguard_dalaran() : base("npc_mageguard_dalaran") { } + + class npc_mageguard_dalaranAI : ScriptedAI + { + public npc_mageguard_dalaranAI(Creature creature) : base(creature) + { + creature.SetFlag(UnitFields.Flags, UnitFlags.NonAttackable); + creature.ApplySpellImmune(0, SpellImmunity.Damage, SpellSchools.Normal, true); + creature.ApplySpellImmune(0, SpellImmunity.Damage, SpellSchoolMask.Magic, true); + } + + public override void Reset() { } + + public override void EnterCombat(Unit who) { } + + public override void AttackStart(Unit who) { } + + public override void MoveInLineOfSight(Unit who) + { + if (!who || !who.IsInWorld || who.GetZoneId() != 4395) + return; + + if (!me.IsWithinDist(who, 65.0f, false)) + return; + + Player player = who.GetCharmerOrOwnerPlayerOrPlayerItself(); + + if (!player || player.IsGameMaster() || player.IsBeingTeleported() || + // If player has Disguise aura for quest A Meeting With The Magister or An Audience With The Arcanist, do not teleport it away but let it pass + player.HasAura(DalaranConst.SpellSunreaverDisguiseFemale) || player.HasAura(DalaranConst.SpellSunreaverDisguiseMale) || + player.HasAura(DalaranConst.SpellSilverConenantDisguiseFemale) || player.HasAura(DalaranConst.SpellSilverConenantDisguiseMale)) + return; + + switch (me.GetEntry()) + { + case 29254: + if (player.GetTeam() == Team.Horde) // Horde unit found in Alliance area + { + if (GetClosestCreatureWithEntry(me, DalaranConst.NpcAplleboughA, 32.0f)) + { + if (me.isInBackInMap(who, 12.0f)) // In my line of sight, "outdoors", and behind me + DoCast(who, DalaranConst.SpellTrespasserA); // Teleport the Horde unit out + } + else // In my line of sight, and "indoors" + DoCast(who, DalaranConst.SpellTrespasserA); // Teleport the Horde unit out + } + break; + case 29255: + if (player.GetTeam() == Team.Alliance) // Alliance unit found in Horde area + { + if (GetClosestCreatureWithEntry(me, DalaranConst.NpcSweetberryH, 32.0f)) + { + if (me.isInBackInMap(who, 12.0f)) // In my line of sight, "outdoors", and behind me + DoCast(who, DalaranConst.SpellTrespasserH); // Teleport the Alliance unit out + } + else // In my line of sight, and "indoors" + DoCast(who, DalaranConst.SpellTrespasserH); // Teleport the Alliance unit out + } + break; + } + me.SetOrientation(me.GetHomePosition().GetOrientation()); + return; + } + + public override void UpdateAI(uint diff) { } + } + + public override CreatureAI GetAI(Creature creature) + { + return new npc_mageguard_dalaranAI(creature); + } + } + + [Script] + class npc_minigob_manabonk : CreatureScript + { + public npc_minigob_manabonk() : base("npc_minigob_manabonk") { } + + class npc_minigob_manabonkAI : ScriptedAI + { + public npc_minigob_manabonkAI(Creature creature) : base(creature) + { + me.setActive(true); + } + + public override void Reset() + { + me.SetVisible(false); + _events.ScheduleEvent(DalaranConst.EventSelectTarget, Time.InMilliseconds); + } + + Player SelectTargetInDalaran() + { + List PlayerInDalaranList = new List(); + + var players = me.GetMap().GetPlayers(); + foreach (var player in players) + { + if (player.GetZoneId() == me.GetZoneId() && !player.IsFlying() && !player.IsMounted() && !player.IsGameMaster()) + PlayerInDalaranList.Add(player); + } + + if (PlayerInDalaranList.Empty()) + return null; + + return PlayerInDalaranList.PickRandom(); + } + + void SendMailToPlayer(Player player) + { + SQLTransaction trans = new SQLTransaction(); + uint deliverDelay = RandomHelper.URand(DalaranConst.MailDeliverDelayMin, DalaranConst.MailDeliverDelayMax); + new MailDraft(DalaranConst.MailMinigobEntry, true).SendMailTo(trans, new MailReceiver(player), new MailSender(MailMessageType.Creature, me.GetEntry()), MailCheckMask.None, deliverDelay); + DB.Characters.CommitTransaction(trans); + } + + public override void UpdateAI(uint diff) + { + _events.Update(diff); + + _events.ExecuteEvents(eventId => + { + switch (eventId) + { + case DalaranConst.EventSelectTarget: + me.SetVisible(true); + DoCast(me, DalaranConst.SpellTeleportVisual); + Player player = SelectTargetInDalaran(); + if (player) + { + me.NearTeleportTo(player.GetPositionX(), player.GetPositionY(), player.GetPositionZ(), 0.0f); + DoCast(player, DalaranConst.SpellManabonked); + SendMailToPlayer(player); + } + _events.ScheduleEvent(DalaranConst.EventBlink, 3 * Time.InMilliseconds); + break; + case DalaranConst.EventBlink: + { + DoCast(me, DalaranConst.SpellImprovedBlink); + Position pos = me.GetRandomNearPosition(RandomHelper.FRand(15, 40)); + me.GetMotionMaster().MovePoint(0, pos.posX, pos.posY, pos.posZ); + _events.ScheduleEvent(DalaranConst.EventDespawn, 3 * Time.InMilliseconds); + _events.ScheduleEvent(DalaranConst.EventDespawnVisual, (uint)(2.5 * Time.InMilliseconds)); + break; + } + case DalaranConst.EventDespawnVisual: + DoCast(me, DalaranConst.SpellTeleportVisual); + break; + case DalaranConst.EventDespawn: + me.DespawnOrUnsummon(); + break; + default: + break; + } + }); + } + } + + public override CreatureAI GetAI(Creature creature) + { + return new npc_minigob_manabonkAI(creature); + } + } +} diff --git a/Scripts/Northrend/DraktharonKeep/BossKingDred.cs b/Scripts/Northrend/DraktharonKeep/BossKingDred.cs new file mode 100644 index 000000000..ecf251c70 --- /dev/null +++ b/Scripts/Northrend/DraktharonKeep/BossKingDred.cs @@ -0,0 +1,281 @@ +/* + * Copyright (C) 2012-2017 CypherCore + * + * 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, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +using Framework.Constants; +using Game.AI; +using Game.Entities; +using Game.Maps; +using Game.Scripting; +using System; + +namespace Scripts.Northrend.DraktharonKeep.KingDred +{ + struct SpellIds + { + public const uint BellowingRoar = 22686; // Fears The Group; Can Be Resisted/Dispelled + public const uint GrievousBite = 48920; + public const uint ManglingSlash = 48873; // Cast On The Current Tank; Adds Debuf + public const uint FearsomeRoar = 48849; + public const uint PiercingSlash = 48878; // Debuff --> Armor Reduced By 75% + public const uint RaptorCall = 59416; // Dummy + public const uint GutRip = 49710; + public const uint Rend = 13738; + } + + struct Misc + { + public const int ActionRaptorKilled = 1; + public const uint DataRaptorsKilled = 2; + } + + [Script] + class boss_king_dred : CreatureScript + { + public boss_king_dred() : base("boss_king_dred") { } + + class boss_king_dredAI : BossAI + { + public boss_king_dredAI(Creature creature) : base(creature, DTKDataTypes.KingDred) + { + Initialize(); + } + + void Initialize() + { + raptorsKilled = 0; + } + + public override void Reset() + { + Initialize(); + _Reset(); + } + + public override void EnterCombat(Unit who) + { + _EnterCombat(); + + _scheduler.SetValidator(() => !me.HasUnitState(UnitState.Casting)); + + _scheduler.Schedule(TimeSpan.FromSeconds(33), task => + { + DoCastAOE(SpellIds.BellowingRoar); + task.Repeat(); + }); + + _scheduler.Schedule(TimeSpan.FromSeconds(20), task => + { + DoCastVictim(SpellIds.GrievousBite); + task.Repeat(); + }); + + _scheduler.Schedule(TimeSpan.FromSeconds(18.5), task => + { + DoCastVictim(SpellIds.ManglingSlash); + task.Repeat(); + }); + + _scheduler.Schedule(TimeSpan.FromSeconds(10), TimeSpan.FromSeconds(20), task => + { + DoCastAOE(SpellIds.FearsomeRoar); + task.Repeat(); + }); + + _scheduler.Schedule(TimeSpan.FromSeconds(17), task => + { + DoCastVictim(SpellIds.PiercingSlash); + task.Repeat(); + }); + + _scheduler.Schedule(TimeSpan.FromSeconds(20), TimeSpan.FromSeconds(25), task => + { + DoCastVictim(SpellIds.RaptorCall); + + float x, y, z; + me.GetClosePoint(out x, out y, out z, me.GetObjectSize() / 3, 10.0f); + me.SummonCreature(RandomHelper.RAND(DTKCreatureIds.DrakkariGutripper, DTKCreatureIds.DrakkariScytheclaw), x, y, z, 0, TempSummonType.DeadDespawn, 1000); + task.Repeat(); + }); + } + + public override void DoAction(int action) + { + if (action == Misc.ActionRaptorKilled) + ++raptorsKilled; + } + + public override uint GetData(uint type) + { + if (type == Misc.DataRaptorsKilled) + return raptorsKilled; + + return 0; + } + + public override void JustDied(Unit killer) + { + _JustDied(); + } + + public override void UpdateAI(uint diff) + { + if (!UpdateVictim()) + return; + + _scheduler.Update(diff); + + if (me.HasUnitState(UnitState.Casting)) + return; + + DoMeleeAttackIfReady(); + } + + byte raptorsKilled; + } + + public override CreatureAI GetAI(Creature creature) + { + return GetInstanceAI(creature); + } + } + + [Script] + class npc_drakkari_gutripper : CreatureScript + { + public npc_drakkari_gutripper() : base("npc_drakkari_gutripper") { } + + class npc_drakkari_gutripperAI : ScriptedAI + { + public npc_drakkari_gutripperAI(Creature creature) : base(creature) + { + Initialize(); + instance = me.GetInstanceScript(); + } + + void Initialize() + { + _scheduler.Schedule(TimeSpan.FromSeconds(10), TimeSpan.FromSeconds(15), task => + { + DoCastVictim(SpellIds.GutRip, false); + task.Repeat(); + }); + } + + public override void Reset() + { + Initialize(); + } + + public override void UpdateAI(uint diff) + { + if (!UpdateVictim()) + return; + + _scheduler.Update(diff); + + DoMeleeAttackIfReady(); + } + + public override void JustDied(Unit killer) + { + Creature dred = ObjectAccessor.GetCreature(me, instance.GetGuidData(DTKDataTypes.KingDred)); + if (dred) + dred.GetAI().DoAction(Misc.ActionRaptorKilled); + } + + InstanceScript instance; + } + + public override CreatureAI GetAI(Creature creature) + { + return GetInstanceAI(creature); + } + } + + [Script] + class npc_drakkari_scytheclaw : CreatureScript + { + public npc_drakkari_scytheclaw() : base("npc_drakkari_scytheclaw") { } + + class npc_drakkari_scytheclawAI : ScriptedAI + { + public npc_drakkari_scytheclawAI(Creature creature) : base(creature) + { + Initialize(); + instance = me.GetInstanceScript(); + } + + void Initialize() + { + _scheduler.Schedule(TimeSpan.FromSeconds(10), TimeSpan.FromSeconds(15), task => + { + DoCastVictim(SpellIds.Rend, false); + task.Repeat(); + }); + } + + public override void Reset() + { + _scheduler.CancelAll(); + Initialize(); + } + + public override void UpdateAI(uint diff) + { + if (!UpdateVictim()) + return; + + _scheduler.Update(diff); + + DoMeleeAttackIfReady(); + } + + public override void JustDied(Unit killer) + { + Creature dred = ObjectAccessor.GetCreature(me, instance.GetGuidData(DTKDataTypes.KingDred)); + if (dred) + dred.GetAI().DoAction(Misc.ActionRaptorKilled); + } + + InstanceScript instance; + } + + public override CreatureAI GetAI(Creature creature) + { + return GetInstanceAI(creature); + } + } + + [Script] + class achievement_king_dred : AchievementCriteriaScript + { + public achievement_king_dred() : base("achievement_king_dred") { } + + public override bool OnCheck(Player player, Unit target) + { + if (!target) + return false; + + Creature dred = target.ToCreature(); + if (dred) + if (dred.GetAI().GetData(Misc.DataRaptorsKilled) >= 6) + return true; + + return false; + } + } +} diff --git a/Scripts/Northrend/DraktharonKeep/BossNovos.cs b/Scripts/Northrend/DraktharonKeep/BossNovos.cs new file mode 100644 index 000000000..b7ced944a --- /dev/null +++ b/Scripts/Northrend/DraktharonKeep/BossNovos.cs @@ -0,0 +1,440 @@ +/* + * Copyright (C) 2012-2017 CypherCore + * + * 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, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +using Framework.Constants; +using Game.AI; +using Game.Entities; +using Game.Maps; +using Game.Scripting; +using Game.Spells; +using System; + +namespace Scripts.Northrend.DraktharonKeep.Novos +{ + struct TextIds + { + public const uint SayAggro = 0; + public const uint SayKill = 1; + public const uint SayDeath = 2; + public const uint SaySummoningAdds = 3; // Unused + public const uint SayArcaneField = 4; + public const uint EmoteSummoningAdds = 5; // Unused + } + + struct SpellIds + { + public const uint BeamChannel = 52106; + public const uint ArcaneField = 47346; + + public const uint SummonRisenShadowcaster = 49105; + public const uint SummonFetidTrollCorpse = 49103; + public const uint SummonHulkingCorpse = 49104; + public const uint SummonCrystalHandler = 49179; //not used + public const uint SummonCopyOfMinions = 59933; //not used + + public const uint ArcaneBlast = 49198; + public const uint Blizzard = 49034; + public const uint Frostbolt = 49037; + public const uint WrathOfMisery = 50089; + public const uint SummonMinions = 59910; + } + + struct Misc + { + public const int ActionResetCrystals = 0; + public const int ActionActivateCrystal = 1; + public const int ActionDeactivate = 2; + public const uint EventAttack = 3; + public const uint EventSummonMinions = 4; + public const uint EventSummonRisenShadowcaster = 5; + public const uint EventSummonFetidTrollCorpse = 6; + public const uint EventSummonHulkingCorpse = 7; + public const uint EventSummonCrystalHandler = 8; + public const uint DataNovosAchiev = 9; + + public static SummonerInfo[] summoners = + { + new SummonerInfo(EventSummonRisenShadowcaster, 7), + new SummonerInfo(EventSummonFetidTrollCorpse, 3), + new SummonerInfo(EventSummonHulkingCorpse, 30), + new SummonerInfo(EventSummonCrystalHandler, 15) + }; + + public struct SummonerInfo + { + public SummonerInfo(uint _eventid, uint _timer) + { + eventId = _eventid; + timer = _timer; + } + + public uint eventId; + public uint timer; + } + + public static Position[] SummonPositions = + { + new Position(-306.8209f, -703.7687f, 27.2919f, 3.401838f), + new Position(-421.395f, -705.7863f, 28.57594f, 4.830696f), + new Position(-308.1955f, -704.8419f, 27.2919f, 3.010279f), + new Position(-424.1306f, -705.7354f, 28.57594f, 5.325676f) + }; + + public const float MaxYCoordOhNovosMAX = -771.95f; + } + + [Script] + class boss_novos : CreatureScript + { + public boss_novos() : base("boss_novos") { } + + class boss_novosAI : BossAI + { + public boss_novosAI(Creature creature) : base(creature, DTKDataTypes.Novos) + { + Initialize(); + _bubbled = false; + } + + void Initialize() + { + _ohNovos = true; + } + + public override void Reset() + { + _Reset(); + + Initialize(); + SetCrystalsStatus(false); + SetSummonerStatus(false); + SetBubbled(false); + } + + public override void EnterCombat(Unit victim) + { + _EnterCombat(); + Talk(TextIds.SayAggro); + + SetCrystalsStatus(true); + SetSummonerStatus(true); + SetBubbled(true); + } + + public override void AttackStart(Unit target) + { + if (!target) + return; + + if (me.Attack(target, true)) + DoStartNoMovement(target); + } + + public override void KilledUnit(Unit who) + { + if (who.IsTypeId(TypeId.Player)) + Talk(TextIds.SayKill); + } + + public override void JustDied(Unit killer) + { + _JustDied(); + Talk(TextIds.SayDeath); + } + + public override void UpdateAI(uint diff) + { + if (!UpdateVictim() || _bubbled) + return; + + _events.Update(diff); + + if (me.HasUnitState(UnitState.Casting)) + return; + + _events.ExecuteEvents(eventId => + { + switch (eventId) + { + case Misc.EventSummonMinions: + DoCast(SpellIds.SummonMinions); + _events.ScheduleEvent(Misc.EventSummonMinions, 15000); + break; + case Misc.EventAttack: + Unit victim = SelectTarget(SelectAggroTarget.Random); + if (victim) + DoCast(victim, RandomHelper.RAND(SpellIds.ArcaneBlast, SpellIds.Blizzard, SpellIds.Frostbolt, SpellIds.WrathOfMisery)); + _events.ScheduleEvent(Misc.EventAttack, 3000); + break; + default: + break; + } + + if (me.HasUnitState(UnitState.Casting)) + return; + }); + } + + public override void DoAction(int action) + { + if (action == DTKDataTypes.ActionCrystalHandlerDied) + { + Talk(TextIds.SayArcaneField); + SetSummonerStatus(false); + SetBubbled(false); + _events.ScheduleEvent(Misc.EventAttack, 3000); + if (IsHeroic()) + _events.ScheduleEvent(Misc.EventSummonMinions, 15000); + } + } + + public override void MoveInLineOfSight(Unit who) + { + base.MoveInLineOfSight(who); + + if (!_ohNovos || !who || !who.IsTypeId(TypeId.Player) || who.GetPositionY() > Misc.MaxYCoordOhNovosMAX) + return; + + uint entry = who.GetEntry(); + if (entry == DTKCreatureIds.HulkingCorpse || entry == DTKCreatureIds.RisenShadowcaster || entry == DTKCreatureIds.FetidTrollCorpse) + _ohNovos = false; + } + + public override uint GetData(uint type) + { + return type == Misc.DataNovosAchiev && _ohNovos ? 1 : 0u; + } + + public override void JustSummoned(Creature summon) + { + me.Yell(TextIds.SaySummoningAdds, summon); + me.TextEmote(TextIds.EmoteSummoningAdds, summon); + + summon.SelectNearestTargetInAttackDistance(50f); + summons.Summon(summon); + } + + void SetBubbled(bool state) + { + _bubbled = state; + if (!state) + { + if (me.HasFlag(UnitFields.Flags, UnitFlags.NonAttackable)) + me.RemoveFlag(UnitFields.Flags, UnitFlags.NonAttackable); + if (me.HasUnitState(UnitState.Casting)) + me.CastStop(); + } + else + { + if (!me.HasFlag(UnitFields.Flags, UnitFlags.NonAttackable)) + me.SetFlag(UnitFields.Flags, UnitFlags.NonAttackable); + DoCast(SpellIds.ArcaneField); + } + } + + void SetSummonerStatus(bool active) + { + for (byte i = 0; i < 4; i++) + { + ObjectGuid guid = instance.GetGuidData(DTKDataTypes.NovosSummoner1 + i); + if (!guid.IsEmpty()) + { + Creature crystalChannelTarget = ObjectAccessor.GetCreature(me, guid); + if (crystalChannelTarget) + { + if (active) + crystalChannelTarget.GetAI().SetData(Misc.summoners[i].eventId, Misc.summoners[i].timer); + else + crystalChannelTarget.GetAI().Reset(); + } + } + } + } + + void SetCrystalsStatus(bool active) + { + for (byte i = 0; i < 4; i++) + { + ObjectGuid guid = instance.GetGuidData(DTKDataTypes.NovosCrystal1 + i); + if (!guid.IsEmpty()) + { + GameObject crystal = ObjectAccessor.GetGameObject(me, guid); + if (crystal) + SetCrystalStatus(crystal, active); + } + } + } + + void SetCrystalStatus(GameObject crystal, bool active) + { + crystal.SetGoState(active ? GameObjectState.Active : GameObjectState.Ready); + + Creature crystalChannelTarget = crystal.FindNearestCreature(DTKCreatureIds.CrystalChannelTarget, 5.0f); + if (crystalChannelTarget) + { + if (active) + crystalChannelTarget.CastSpell(null, SpellIds.BeamChannel); + else if (crystalChannelTarget.HasUnitState(UnitState.Casting)) + crystalChannelTarget.CastStop(); + } + } + + bool _ohNovos; + bool _bubbled; + } + + public override CreatureAI GetAI(Creature creature) + { + return GetInstanceAI(creature); + } + } + + [Script] + class npc_crystal_channel_target : CreatureScript + { + public npc_crystal_channel_target() : base("npc_crystal_channel_target") { } + + class npc_crystal_channel_targetAI : ScriptedAI + { + public npc_crystal_channel_targetAI(Creature creature) : base(creature) { } + + public override void Reset() + { + _events.Reset(); + _crystalHandlerCount = 0; + } + + public override void UpdateAI(uint diff) + { + _events.Update(diff); + + _events.ExecuteEvents(eventId => + { + switch (eventId) + { + case Misc.EventSummonCrystalHandler: + me.SummonCreature(DTKCreatureIds.CrystalHandler, Misc.SummonPositions[_crystalHandlerCount++]); + if (_crystalHandlerCount < 4) + _events.Repeat(TimeSpan.FromSeconds(15)); + break; + case Misc.EventSummonRisenShadowcaster: + DoCast(SpellIds.SummonRisenShadowcaster); + _events.Repeat(TimeSpan.FromSeconds(7)); + break; + case Misc.EventSummonFetidTrollCorpse: + DoCast(SpellIds.SummonFetidTrollCorpse); + _events.Repeat(TimeSpan.FromSeconds(3)); + break; + case Misc.EventSummonHulkingCorpse: + DoCast(SpellIds.SummonHulkingCorpse); + _events.Repeat(TimeSpan.FromSeconds(30)); + break; + } + }); + } + + public override void SetData(uint id, uint value) + { + _events.ScheduleEvent(id, TimeSpan.FromSeconds(value)); + } + + public override void SummonedCreatureDies(Creature summon, Unit killer) + { + if (_crystalHandlerCount < 4) + return; + + InstanceScript instance = me.GetInstanceScript(); + if (instance != null) + { + ObjectGuid guid = instance.GetGuidData(DTKDataTypes.Novos); + if (!guid.IsEmpty()) + { + Creature novos = ObjectAccessor.GetCreature(me, guid); + if (novos) + novos.GetAI().DoAction(DTKDataTypes.ActionCrystalHandlerDied); + } + } + } + + public override void JustSummoned(Creature summon) + { + InstanceScript instance = me.GetInstanceScript(); + if (instance != null) + { + ObjectGuid guid = instance.GetGuidData(DTKDataTypes.Novos); + if (!guid.IsEmpty()) + { + Creature novos = ObjectAccessor.GetCreature(me, guid); + if (novos) + novos.GetAI().JustSummoned(summon); + } + } + + if (summon) + summon.GetMotionMaster().MovePath(summon.GetEntry() * 100, false); + } + + uint _crystalHandlerCount; + } + + public override CreatureAI GetAI(Creature creature) + { + return GetInstanceAI(creature); + } + } + + [Script] + class achievement_oh_novos : AchievementCriteriaScript + { + public achievement_oh_novos() : base("achievement_oh_novos") { } + + public override bool OnCheck(Player player, Unit target) + { + return target && target.IsTypeId(TypeId.Unit) && target.ToCreature().GetAI().GetData(Misc.DataNovosAchiev) != 0; + } + } + + [Script] + class spell_novos_summon_minions : SpellScriptLoader + { + public spell_novos_summon_minions() : base("spell_novos_summon_minions") { } + + class spell_novos_summon_minions_SpellScript : SpellScript + { + public override bool Validate(SpellInfo spellInfo) + { + return ValidateSpellInfo(SpellIds.SummonCopyOfMinions); + } + + void HandleScript(uint effIndex) + { + for (byte i = 0; i < 2; ++i) + GetCaster().CastSpell((Unit)null, SpellIds.SummonCopyOfMinions, true); + } + + public override void Register() + { + OnEffectHitTarget.Add(new EffectHandler(HandleScript, 0, SpellEffectName.ScriptEffect)); + } + } + + public override SpellScript GetSpellScript() + { + return new spell_novos_summon_minions_SpellScript(); + } + } +} diff --git a/Scripts/Northrend/DraktharonKeep/BossTharonJa.cs b/Scripts/Northrend/DraktharonKeep/BossTharonJa.cs new file mode 100644 index 000000000..3d10ed85e --- /dev/null +++ b/Scripts/Northrend/DraktharonKeep/BossTharonJa.cs @@ -0,0 +1,251 @@ +/* + * Copyright (C) 2012-2017 CypherCore + * + * 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, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +using Framework.Constants; +using Game.AI; +using Game.Entities; +using Game.Scripting; +using Game.Spells; +using System; + +namespace Scripts.Northrend.DraktharonKeep.TharonJa +{ + struct SpellIds + { + // Skeletal Spells (Phase 1) + public const uint CurseOfLife = 49527; + public const uint RainOfFire = 49518; + public const uint ShadowVolley = 49528; + public const uint DecayFlesh = 49356; // Cast At End Of Phase 1; Starts Phase 2 + // Flesh Spells (Phase 2) + public const uint GiftOfTharonJa = 52509; + public const uint ClearGiftOfTharonJa = 53242; + public const uint EyeBeam = 49544; + public const uint LightningBreath = 49537; + public const uint PoisonCloud = 49548; + public const uint ReturnFlesh = 53463; // Channeled Spell Ending Phase Two And Returning To Phase 1. This Ability Will Stun The Party For 6 Seconds. + public const uint AchievementCheck = 61863; + public const uint FleshVisual = 52582; + public const uint Dummy = 49551; + } + + struct EventIds + { + public const uint CurseOfLife = 1; + public const uint RainOfFire = 2; + public const uint ShadowVolley = 3; + + public const uint EyeBeam = 4; + public const uint LightningBreath = 5; + public const uint PoisonCloud = 6; + + public const uint DecayFlesh = 7; + public const uint GoingFlesh = 8; + public const uint ReturnFlesh = 9; + public const uint GoingSkeletal = 10; + } + + struct TextIds + { + public const uint SayAggro = 0; + public const uint SayKill = 1; + public const uint SayFlesh = 2; + public const uint SaySkeleton = 3; + public const uint SayDeath = 4; + } + + struct Misc + { + public const uint ModelFlesh = 27073; + } + + [Script] + class boss_tharon_ja : CreatureScript + { + public boss_tharon_ja() : base("boss_tharon_ja") { } + + class boss_tharon_jaAI : BossAI + { + public boss_tharon_jaAI(Creature creature) : base(creature, DTKDataTypes.TharonJa) { } + + public override void Reset() + { + _Reset(); + me.RestoreDisplayId(); + } + + public override void EnterCombat(Unit who) + { + Talk(TextIds.SayAggro); + _EnterCombat(); + + _events.ScheduleEvent(EventIds.DecayFlesh, TimeSpan.FromSeconds(20)); + _events.ScheduleEvent(EventIds.CurseOfLife, TimeSpan.FromSeconds(1)); + _events.ScheduleEvent(EventIds.RainOfFire, TimeSpan.FromSeconds(14), TimeSpan.FromSeconds(18)); + _events.ScheduleEvent(EventIds.ShadowVolley, TimeSpan.FromSeconds(8), TimeSpan.FromSeconds(10)); + } + + public override void KilledUnit(Unit who) + { + if (who.IsTypeId(TypeId.Player)) + Talk(TextIds.SayKill); + } + + public override void JustDied(Unit killer) + { + _JustDied(); + + Talk(TextIds.SayDeath); + DoCastAOE(SpellIds.ClearGiftOfTharonJa, true); + DoCastAOE(SpellIds.AchievementCheck, true); + } + + public override void UpdateAI(uint diff) + { + if (!UpdateVictim()) + return; + + _events.Update(diff); + + if (me.HasUnitState(UnitState.Casting)) + return; + + _events.ExecuteEvents(eventId => + { + switch (eventId) + { + case EventIds.CurseOfLife: + { + Unit target = SelectTarget(SelectAggroTarget.Random, 0, 100.0f, true); + if (target) + DoCast(target, SpellIds.CurseOfLife); + _events.ScheduleEvent(EventIds.CurseOfLife, TimeSpan.FromSeconds(10), TimeSpan.FromSeconds(15)); + } + return; + case EventIds.ShadowVolley: + DoCastVictim(SpellIds.ShadowVolley); + _events.ScheduleEvent(EventIds.ShadowVolley, TimeSpan.FromSeconds(8), TimeSpan.FromSeconds(10)); + return; + case EventIds.RainOfFire: + { + Unit target = SelectTarget(SelectAggroTarget.Random, 0, 100.0f, true); + if (target) + DoCast(target, SpellIds.RainOfFire); + _events.ScheduleEvent(EventIds.RainOfFire, TimeSpan.FromSeconds(14), TimeSpan.FromSeconds(18)); + } + return; + case EventIds.LightningBreath: + { + Unit target = SelectTarget(SelectAggroTarget.Random, 0, 100.0f, true); + if (target) + DoCast(target, SpellIds.LightningBreath); + _events.ScheduleEvent(EventIds.LightningBreath, TimeSpan.FromSeconds(6), TimeSpan.FromSeconds(7)); + } + return; + case EventIds.EyeBeam: + { + Unit target = SelectTarget(SelectAggroTarget.Random, 0, 100.0f, true); + if (target) + DoCast(target, SpellIds.EyeBeam); + _events.ScheduleEvent(EventIds.EyeBeam, TimeSpan.FromSeconds(4), TimeSpan.FromSeconds(6)); + } + return; + case EventIds.PoisonCloud: + DoCastAOE(SpellIds.PoisonCloud); + _events.ScheduleEvent(EventIds.PoisonCloud, TimeSpan.FromSeconds(10), TimeSpan.FromSeconds(12)); + return; + case EventIds.DecayFlesh: + DoCastAOE(SpellIds.DecayFlesh); + _events.ScheduleEvent(EventIds.GoingFlesh, TimeSpan.FromSeconds(6)); + return; + case EventIds.GoingFlesh: + Talk(TextIds.SayFlesh); + me.SetDisplayId(Misc.ModelFlesh); + DoCastAOE(SpellIds.GiftOfTharonJa, true); + DoCast(me, SpellIds.FleshVisual, true); + DoCast(me, SpellIds.Dummy, true); + + _events.Reset(); + _events.ScheduleEvent(EventIds.ReturnFlesh, TimeSpan.FromSeconds(20)); + _events.ScheduleEvent(EventIds.LightningBreath, TimeSpan.FromSeconds(3), TimeSpan.FromSeconds(4)); + _events.ScheduleEvent(EventIds.EyeBeam, TimeSpan.FromSeconds(4), TimeSpan.FromSeconds(8)); + _events.ScheduleEvent(EventIds.PoisonCloud, TimeSpan.FromSeconds(6), TimeSpan.FromSeconds(7)); + break; + case EventIds.ReturnFlesh: + DoCastAOE(SpellIds.ReturnFlesh); + _events.ScheduleEvent(EventIds.GoingSkeletal, 6000); + return; + case EventIds.GoingSkeletal: + Talk(TextIds.SaySkeleton); + me.RestoreDisplayId(); + DoCastAOE(SpellIds.ClearGiftOfTharonJa, true); + + _events.Reset(); + _events.ScheduleEvent(EventIds.DecayFlesh, TimeSpan.FromSeconds(20)); + _events.ScheduleEvent(EventIds.CurseOfLife, TimeSpan.FromSeconds(1)); + _events.ScheduleEvent(EventIds.RainOfFire, TimeSpan.FromSeconds(14), TimeSpan.FromSeconds(18)); + _events.ScheduleEvent(EventIds.ShadowVolley, TimeSpan.FromSeconds(8), TimeSpan.FromSeconds(10)); + break; + default: + break; + } + + if (me.HasUnitState(UnitState.Casting)) + return; + }); + + DoMeleeAttackIfReady(); + } + } + + public override CreatureAI GetAI(Creature creature) + { + return GetInstanceAI(creature); + } + } + + [Script] + class spell_tharon_ja_clear_gift_of_tharon_ja : SpellScriptLoader + { + public spell_tharon_ja_clear_gift_of_tharon_ja() : base("spell_tharon_ja_clear_gift_of_tharon_ja") { } + + class spell_tharon_ja_clear_gift_of_tharon_ja_SpellScript : SpellScript + { + public override bool Validate(SpellInfo spellInfo) + { + return ValidateSpellInfo(SpellIds.GiftOfTharonJa); + } + + void HandleScript(uint effIndex) + { + Unit target = GetHitUnit(); + if (target) + target.RemoveAura(SpellIds.GiftOfTharonJa); + } + + public override void Register() + { + OnEffectHitTarget.Add(new EffectHandler(HandleScript, 0, SpellEffectName.ScriptEffect)); + } + } + + public override SpellScript GetSpellScript() + { + return new spell_tharon_ja_clear_gift_of_tharon_ja_SpellScript(); + } + } +} diff --git a/Scripts/Northrend/DraktharonKeep/BossTrollgore.cs b/Scripts/Northrend/DraktharonKeep/BossTrollgore.cs new file mode 100644 index 000000000..098cec1ff --- /dev/null +++ b/Scripts/Northrend/DraktharonKeep/BossTrollgore.cs @@ -0,0 +1,335 @@ +/* + * Copyright (C) 2012-2017 CypherCore + * + * 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, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +using Framework.Constants; +using Game.AI; +using Game.Entities; +using Game.Scripting; +using Game.Spells; +using System; + +namespace Scripts.Northrend.DraktharonKeep.Trollgore +{ + struct SpellIds + { + public const uint InfectedWound = 49637; + public const uint Crush = 49639; + public const uint CorpseExplode = 49555; + public const uint CorpseExplodeDamage = 49618; + public const uint Consume = 49380; + public const uint ConsumeBuff = 49381; + public const uint ConsumeBuffH = 59805; + + public const uint SummonInvaderA = 49456; + public const uint SummonInvaderB = 49457; + public const uint SummonInvaderC = 49458; // Can'T Find Any Sniffs + + public const uint InvaderTaunt = 49405; + } + + struct TextIds + { + public const uint SayAggro = 0; + public const uint SayKill = 1; + public const uint SayConsume = 2; + public const uint SayExplode = 3; + public const uint SayDeath = 4; + } + + struct Misc + { + public const uint DataConsumptionJunction = 1; + public const uint PointLanding = 1; + + public static Position Landing = new Position(-263.0534f, -660.8658f, 26.50903f, 0.0f); + } + + [Script] + class boss_trollgore : CreatureScript + { + public boss_trollgore() : base("boss_trollgore") { } + + class boss_trollgoreAI : BossAI + { + public boss_trollgoreAI(Creature creature) : base(creature, DTKDataTypes.Trollgore) + { + Initialize(); + } + + void Initialize() + { + _consumptionJunction = true; + } + + public override void Reset() + { + _Reset(); + Initialize(); + } + + public override void EnterCombat(Unit who) + { + _EnterCombat(); + Talk(TextIds.SayAggro); + + _scheduler.SetValidator(() => !me.HasUnitState(UnitState.Casting)); + + _scheduler.Schedule(TimeSpan.FromSeconds(15), task => + { + Talk(TextIds.SayConsume); + DoCastAOE(SpellIds.Consume); + task.Repeat(); + }); + + _scheduler.Schedule(TimeSpan.FromSeconds(1), TimeSpan.FromSeconds(5), task => + { + DoCastVictim(SpellIds.Crush); + task.Repeat(TimeSpan.FromSeconds(10), TimeSpan.FromSeconds(15)); + }); + + _scheduler.Schedule(TimeSpan.FromSeconds(10), TimeSpan.FromSeconds(60), task => + { + DoCastVictim(SpellIds.InfectedWound); + task.Repeat(TimeSpan.FromSeconds(25), TimeSpan.FromSeconds(35)); + }); + + _scheduler.Schedule(TimeSpan.FromSeconds(3), task => + { + Talk(TextIds.SayExplode); + DoCastAOE(SpellIds.CorpseExplode); + task.Repeat(TimeSpan.FromSeconds(15), TimeSpan.FromSeconds(19)); + }); + + _scheduler.Schedule(TimeSpan.FromSeconds(30), TimeSpan.FromSeconds(40), task => + { + for (byte i = 0; i < 3; ++i) + { + Creature trigger = ObjectAccessor.GetCreature(me, instance.GetGuidData(DTKDataTypes.TrollgoreInvaderSummoner1 + i)); + if (trigger) + trigger.CastSpell(trigger, RandomHelper.RAND(SpellIds.SummonInvaderA, SpellIds.SummonInvaderB, SpellIds.SummonInvaderC), true, null, null, me.GetGUID()); + } + + task.Repeat(); + }); + } + + public override void UpdateAI(uint diff) + { + if (!UpdateVictim()) + return; + + _scheduler.Update(diff); + + if (me.HasUnitState(UnitState.Casting)) + return; + + if (_consumptionJunction) + { + Aura ConsumeAura = me.GetAura(DungeonMode(SpellIds.ConsumeBuff, SpellIds.ConsumeBuffH)); + if (ConsumeAura != null && ConsumeAura.GetStackAmount() > 9) + _consumptionJunction = false; + } + + DoMeleeAttackIfReady(); + } + + public override void JustDied(Unit killer) + { + _JustDied(); + Talk(TextIds.SayDeath); + } + + public override uint GetData(uint type) + { + if (type == Misc.DataConsumptionJunction) + return _consumptionJunction ? 1 : 0u; + + return 0; + } + + public override void KilledUnit(Unit victim) + { + if (victim.IsTypeId(TypeId.Player)) + Talk(TextIds.SayKill); + } + + public override void JustSummoned(Creature summon) + { + summon.GetMotionMaster().MovePoint(Misc.PointLanding, Misc.Landing); + summons.Summon(summon); + } + + bool _consumptionJunction; + } + + public override CreatureAI GetAI(Creature creature) + { + return GetInstanceAI(creature); + } + } + + [Script] + class npc_drakkari_invader : CreatureScript + { + public npc_drakkari_invader() : base("npc_drakkari_invader") { } + + class npc_drakkari_invaderAI : ScriptedAI + { + public npc_drakkari_invaderAI(Creature creature) : base(creature) { } + + public override void MovementInform(MovementGeneratorType type, uint pointId) + { + if (type == MovementGeneratorType.Point && pointId == Misc.PointLanding) + { + me.Dismount(); + me.RemoveFlag(UnitFields.Flags, UnitFlags.ImmuneToPc | UnitFlags.ImmuneToNpc); + DoCastAOE(SpellIds.InvaderTaunt); + } + } + } + + public override CreatureAI GetAI(Creature creature) + { + return GetInstanceAI(creature); + } + } + + [Script] // 49380, 59803 - Consume + class spell_trollgore_consume : SpellScriptLoader + { + public spell_trollgore_consume() : base("spell_trollgore_consume") { } + + class spell_trollgore_consume_SpellScript : SpellScript + { + public override bool Validate(SpellInfo spellInfo) + { + return ValidateSpellInfo(SpellIds.ConsumeBuff); + } + + void HandleConsume(uint effIndex) + { + Unit target = GetHitUnit(); + if (target) + target.CastSpell(GetCaster(), SpellIds.ConsumeBuff, true); + } + + public override void Register() + { + OnEffectHitTarget.Add(new EffectHandler(HandleConsume, 1, SpellEffectName.ScriptEffect)); + } + } + + public override SpellScript GetSpellScript() + { + return new spell_trollgore_consume_SpellScript(); + } + } + + [Script] // 49555, 59807 - Corpse Explode + class spell_trollgore_corpse_explode : SpellScriptLoader + { + public spell_trollgore_corpse_explode() : base("spell_trollgore_corpse_explode") { } + + class spell_trollgore_corpse_explode_AuraScript : AuraScript + { + public override bool Validate(SpellInfo spellInfo) + { + return ValidateSpellInfo(SpellIds.CorpseExplodeDamage); + } + + void PeriodicTick(AuraEffect aurEff) + { + if (aurEff.GetTickNumber() == 2) + { + Unit caster = GetCaster(); + if (caster) + caster.CastSpell(GetTarget(), SpellIds.CorpseExplodeDamage, true, null, aurEff); + } + } + + void HandleRemove(AuraEffect aurEff, AuraEffectHandleModes mode) + { + Creature target = GetTarget().ToCreature(); + if (target) + target.DespawnOrUnsummon(); + } + + public override void Register() + { + OnEffectPeriodic.Add(new EffectPeriodicHandler(PeriodicTick, 0, AuraType.Dummy)); + AfterEffectRemove.Add(new EffectApplyHandler(HandleRemove, 0, AuraType.PeriodicDummy, AuraEffectHandleModes.Real)); + } + } + + public override AuraScript GetAuraScript() + { + return new spell_trollgore_corpse_explode_AuraScript(); + } + } + + [Script] // 49405 - Invader Taunt Trigger + class spell_trollgore_invader_taunt : SpellScriptLoader + { + public spell_trollgore_invader_taunt() : base("spell_trollgore_invader_taunt") { } + + class spell_trollgore_invader_taunt_SpellScript : SpellScript + { + public override bool Validate(SpellInfo spellInfo) + { + return ValidateSpellInfo((uint)spellInfo.GetEffect(0).CalcValue()); + } + + void HandleTaunt(uint effIndex) + { + Unit target = GetHitUnit(); + if (target) + target.CastSpell(GetCaster(), (uint)GetEffectValue(), true); + } + + public override void Register() + { + OnEffectHitTarget.Add(new EffectHandler(HandleTaunt, 0, SpellEffectName.ScriptEffect)); + } + } + + public override SpellScript GetSpellScript() + { + return new spell_trollgore_invader_taunt_SpellScript(); + } + } + + [Script] + class achievement_consumption_junction : AchievementCriteriaScript + { + public achievement_consumption_junction() : base("achievement_consumption_junction") + { + } + + public override bool OnCheck(Player player, Unit target) + { + if (!target) + return false; + + Creature Trollgore = target.ToCreature(); + if (Trollgore) + if (Trollgore.GetAI().GetData(Misc.DataConsumptionJunction) != 0) + return true; + + return false; + } + } +} diff --git a/Scripts/Northrend/DraktharonKeep/InstanceDrakTharonKeep.cs b/Scripts/Northrend/DraktharonKeep/InstanceDrakTharonKeep.cs new file mode 100644 index 000000000..93df3ff0d --- /dev/null +++ b/Scripts/Northrend/DraktharonKeep/InstanceDrakTharonKeep.cs @@ -0,0 +1,224 @@ +/* + * Copyright (C) 2012-2017 CypherCore + * + * 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, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +using Game.Entities; +using Game.Maps; +using Game.Scripting; + +namespace Scripts.Northrend.DraktharonKeep +{ + struct DTKDataTypes + { + // Encounter States/Boss Guids + public const uint Trollgore = 0; + public const uint Novos = 1; + public const uint KingDred = 2; + public const uint TharonJa = 3; + + // Additional Data + //public const uint KingDredAchiev; + + public const uint TrollgoreInvaderSummoner1 = 4; + public const uint TrollgoreInvaderSummoner2 = 5; + public const uint TrollgoreInvaderSummoner3 = 6; + + public const uint NovosCrystal1 = 7; + public const uint NovosCrystal2 = 8; + public const uint NovosCrystal3 = 9; + public const uint NovosCrystal4 = 10; + public const uint NovosSummoner1 = 11; + public const uint NovosSummoner2 = 12; + public const uint NovosSummoner3 = 13; + public const uint NovosSummoner4 = 14; + + public const int ActionCrystalHandlerDied = 15; + } + + struct DTKCreatureIds + { + public const uint Trollgore = 26630; + public const uint Novos = 26631; + public const uint KingDred = 27483; + public const uint TharonJa = 26632; + + // Trollgore + public const uint DrakkariInvaderA = 27709; + public const uint DrakkariInvaderB = 27753; + public const uint DrakkariInvaderC = 27754; + + // Novos + public const uint CrystalChannelTarget = 26712; + public const uint CrystalHandler = 26627; + public const uint HulkingCorpse = 27597; + public const uint FetidTrollCorpse = 27598; + public const uint RisenShadowcaster = 27600; + + // King Dred + public const uint DrakkariGutripper = 26641; + public const uint DrakkariScytheclaw = 26628; + + public const uint WorldTrigger = 22515; + } + + struct DTKGameObjectIds + { + public const uint NovosCrystal1 = 189299; + public const uint NovosCrystal2 = 189300; + public const uint NovosCrystal3 = 189301; + public const uint NovosCrystal4 = 189302; + } + + [Script] + class instance_drak_tharon_keep : InstanceMapScript + { + public instance_drak_tharon_keep() : base(nameof(instance_drak_tharon_keep), 600) { } + + class instance_drak_tharon_keep_InstanceScript : InstanceScript + { + public instance_drak_tharon_keep_InstanceScript(Map map) : base(map) + { + SetHeaders("DTK"); + SetBossNumber(4); + } + + public override void OnCreatureCreate(Creature creature) + { + switch (creature.GetEntry()) + { + case DTKCreatureIds.Trollgore: + TrollgoreGUID = creature.GetGUID(); + break; + case DTKCreatureIds.Novos: + NovosGUID = creature.GetGUID(); + break; + case DTKCreatureIds.KingDred: + KingDredGUID = creature.GetGUID(); + break; + case DTKCreatureIds.TharonJa: + TharonJaGUID = creature.GetGUID(); + break; + case DTKCreatureIds.WorldTrigger: + InitializeTrollgoreInvaderSummoner(creature); + break; + case DTKCreatureIds.CrystalChannelTarget: + InitializeNovosSummoner(creature); + break; + default: + break; + } + } + + public override void OnGameObjectCreate(GameObject go) + { + switch (go.GetEntry()) + { + case DTKGameObjectIds.NovosCrystal1: + NovosCrystalGUIDs[0] = go.GetGUID(); + break; + case DTKGameObjectIds.NovosCrystal2: + NovosCrystalGUIDs[1] = go.GetGUID(); + break; + case DTKGameObjectIds.NovosCrystal3: + NovosCrystalGUIDs[2] = go.GetGUID(); + break; + case DTKGameObjectIds.NovosCrystal4: + NovosCrystalGUIDs[3] = go.GetGUID(); + break; + default: + break; + } + } + + void InitializeTrollgoreInvaderSummoner(Creature creature) + { + float y = creature.GetPositionY(); + float z = creature.GetPositionZ(); + + if (z < 50.0f) + return; + + if (y < -650.0f && y > -660.0f) + TrollgoreInvaderSummonerGuids[0] = creature.GetGUID(); + else if (y < -660.0f && y > -670.0f) + TrollgoreInvaderSummonerGuids[1] = creature.GetGUID(); + else if (y < -675.0f && y > -685.0f) + TrollgoreInvaderSummonerGuids[2] = creature.GetGUID(); + } + + void InitializeNovosSummoner(Creature creature) + { + float x = creature.GetPositionX(); + float y = creature.GetPositionY(); + float z = creature.GetPositionZ(); + + if (x < -374.0f && x > -379.0f && y > -820.0f && y < -815.0f && z < 60.0f && z > 58.0f) + NovosSummonerGUIDs[0] = creature.GetGUID(); + else if (x < -379.0f && x > -385.0f && y > -820.0f && y < -815.0f && z < 60.0f && z > 58.0f) + NovosSummonerGUIDs[1] = creature.GetGUID(); + else if (x < -374.0f && x > -385.0f && y > -827.0f && y < -820.0f && z < 60.0f && z > 58.0f) + NovosSummonerGUIDs[2] = creature.GetGUID(); + else if (x < -338.0f && x > -380.0f && y > -727.0f && y < 721.0f && z < 30.0f && z > 26.0f) + NovosSummonerGUIDs[3] = creature.GetGUID(); + } + + public override ObjectGuid GetGuidData(uint type) + { + switch (type) + { + case DTKDataTypes.Trollgore: + return TrollgoreGUID; + case DTKDataTypes.Novos: + return NovosGUID; + case DTKDataTypes.KingDred: + return KingDredGUID; + case DTKDataTypes.TharonJa: + return TharonJaGUID; + case DTKDataTypes.TrollgoreInvaderSummoner1: + case DTKDataTypes.TrollgoreInvaderSummoner2: + case DTKDataTypes.TrollgoreInvaderSummoner3: + return TrollgoreInvaderSummonerGuids[type - DTKDataTypes.TrollgoreInvaderSummoner1]; + case DTKDataTypes.NovosCrystal1: + case DTKDataTypes.NovosCrystal2: + case DTKDataTypes.NovosCrystal3: + case DTKDataTypes.NovosCrystal4: + return NovosCrystalGUIDs[type - DTKDataTypes.NovosCrystal1]; + case DTKDataTypes.NovosSummoner1: + case DTKDataTypes.NovosSummoner2: + case DTKDataTypes.NovosSummoner3: + case DTKDataTypes.NovosSummoner4: + return NovosSummonerGUIDs[type - DTKDataTypes.NovosSummoner1]; + } + + return ObjectGuid.Empty; + } + + ObjectGuid TrollgoreGUID; + ObjectGuid NovosGUID; + ObjectGuid KingDredGUID; + ObjectGuid TharonJaGUID; + + ObjectGuid[] TrollgoreInvaderSummonerGuids = new ObjectGuid[3]; + ObjectGuid[] NovosCrystalGUIDs = new ObjectGuid[4]; + ObjectGuid[] NovosSummonerGUIDs = new ObjectGuid[4]; + } + + public override InstanceScript GetInstanceScript(InstanceMap map) + { + return new instance_drak_tharon_keep_InstanceScript(map); + } + } +} diff --git a/Scripts/Northrend/Gundrak/BossDrakkariColossus.cs b/Scripts/Northrend/Gundrak/BossDrakkariColossus.cs new file mode 100644 index 000000000..e34d2f878 --- /dev/null +++ b/Scripts/Northrend/Gundrak/BossDrakkariColossus.cs @@ -0,0 +1,444 @@ +/* + * Copyright (C) 2012-2017 CypherCore + * + * 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, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +using Framework.Constants; +using Game.AI; +using Game.Entities; +using Game.Maps; +using Game.Scripting; +using Game.Spells; +using System; +using System.Collections.Generic; + +namespace Scripts.Northrend.Gundrak.DrakkariColossus +{ + struct TextIds + { + // Drakkari Elemental + public const uint EmoteMojo = 0; + public const uint EmoteActivateAltar = 1; + } + + struct SpellIds + { + public const uint Emerge = 54850; + public const uint ElementalSpawnEffect = 54888; + public const uint MojoVolley = 54849; + public const uint SurgeVisual = 54827; + public const uint Merge = 54878; + public const uint MightyBlow = 54719; + public const uint Surge = 54801; + public const uint FreezeAnim = 16245; + public const uint MojoPuddle = 55627; + public const uint MojoWave = 55626; + } + + struct Misc + { + public const uint EventMightyBlow = 1; + public const uint EventSurge = 1; + + public const int ActionSummonElemental = 1; + public const int ActionFreezeColossus = 2; + public const int ActionUnfreezeColossus = 3; + public const int ActionReturnToColossus = 1; + + public const byte ColossusPhaseNormal = 1; + public const byte ColossusPhaseFirstElementalSummon = 2; + public const byte ColossusPhaseSecondElementalSummon = 3; + + public const uint DataColossusPhase = 1; + public const uint DataIntroDone = 2; + } + + [Script] + class boss_drakkari_colossus : CreatureScript + { + public boss_drakkari_colossus() : base("boss_drakkari_colossus") { } + + class boss_drakkari_colossusAI : BossAI + { + public boss_drakkari_colossusAI(Creature creature) : base(creature, GDDataTypes.DrakkariColossus) + { + Initialize(); + me.SetReactState(ReactStates.Passive); + introDone = false; + } + + void Initialize() + { + phase = Misc.ColossusPhaseNormal; + } + + public override void Reset() + { + _Reset(); + + if (GetData(Misc.DataIntroDone) != 0) + { + me.SetReactState(ReactStates.Aggressive); + me.RemoveFlag(UnitFields.Flags, UnitFlags.ImmuneToPc); + me.RemoveAura(SpellIds.FreezeAnim); + } + + _scheduler.SetValidator(() => !me.HasUnitState(UnitState.Casting)); + + _scheduler.Schedule(TimeSpan.FromSeconds(10), TimeSpan.FromSeconds(30), task => + { + DoCastVictim(SpellIds.MightyBlow); + task.Repeat(TimeSpan.FromSeconds(5), TimeSpan.FromSeconds(15)); + }); + + Initialize(); + } + + public override void EnterCombat(Unit who) + { + _EnterCombat(); + me.RemoveAura(SpellIds.FreezeAnim); + } + + public override void JustDied(Unit killer) + { + _JustDied(); + } + + public override void DoAction(int action) + { + switch (action) + { + case Misc.ActionSummonElemental: + DoCast(SpellIds.Emerge); + break; + case Misc.ActionFreezeColossus: + me.GetMotionMaster().Clear(); + me.GetMotionMaster().MoveIdle(); + + me.SetReactState(ReactStates.Passive); + me.SetFlag(UnitFields.Flags, UnitFlags.ImmuneToPc); + DoCast(me, SpellIds.FreezeAnim); + break; + case Misc.ActionUnfreezeColossus: + if (me.GetReactState() == ReactStates.Aggressive) + return; + + me.SetReactState(ReactStates.Aggressive); + me.RemoveFlag(UnitFields.Flags, UnitFlags.ImmuneToPc); + me.RemoveAura(SpellIds.FreezeAnim); + + me.SetInCombatWithZone(); + + if (me.GetVictim()) + me.GetMotionMaster().MoveChase(me.GetVictim(), 0, 0); + break; + } + } + + public override void DamageTaken(Unit attacker, ref uint damage) + { + if (me.HasFlag(UnitFields.Flags, UnitFlags.ImmuneToPc)) + damage = 0; + + if (phase == Misc.ColossusPhaseNormal || + phase == Misc.ColossusPhaseFirstElementalSummon) + { + if (HealthBelowPct(phase == Misc.ColossusPhaseNormal ? 50 : 5)) + { + damage = 0; + phase = (phase == Misc.ColossusPhaseNormal ? Misc.ColossusPhaseFirstElementalSummon : Misc.ColossusPhaseSecondElementalSummon); + DoAction(Misc.ActionFreezeColossus); + DoAction(Misc.ActionSummonElemental); + } + } + } + + public override uint GetData(uint data) + { + if (data == Misc.DataColossusPhase) + return phase; + else if (data == Misc.DataIntroDone) + return introDone ? 1 : 0u; + + return 0; + } + + public override void SetData(uint type, uint data) + { + if (type == Misc.DataIntroDone) + introDone = data != 0; + } + + public override void UpdateAI(uint diff) + { + if (!UpdateVictim()) + return; + + _scheduler.Update(diff); + + if (me.HasUnitState(UnitState.Casting)) + return; + + if (me.GetReactState() == ReactStates.Aggressive) + DoMeleeAttackIfReady(); + } + + public override void JustSummoned(Creature summon) + { + summon.SetInCombatWithZone(); + + if (phase == Misc.ColossusPhaseSecondElementalSummon) + summon.SetHealth(summon.GetMaxHealth() / 2); + } + + byte phase; + bool introDone; + } + + public override CreatureAI GetAI(Creature creature) + { + return GetInstanceAI(creature); + } + } + + [Script] + class boss_drakkari_elemental : CreatureScript + { + public boss_drakkari_elemental() : base("boss_drakkari_elemental") { } + + class boss_drakkari_elementalAI : ScriptedAI + { + public boss_drakkari_elementalAI(Creature creature) : base(creature) + { + DoCast(me, SpellIds.ElementalSpawnEffect); + instance = creature.GetInstanceScript(); + } + + public override void Reset() + { + _scheduler.CancelAll(); + _scheduler.SetValidator(() => !me.HasUnitState(UnitState.Casting)); + + _scheduler.Schedule(TimeSpan.FromSeconds(5), TimeSpan.FromSeconds(15), task => + { + DoCast(SpellIds.SurgeVisual); + Unit target = SelectTarget(SelectAggroTarget.Random, 1, 0.0f, true); + if (target) + DoCast(target, SpellIds.Surge); + task.Repeat(TimeSpan.FromSeconds(5), TimeSpan.FromSeconds(15)); + }); + + me.AddAura(SpellIds.MojoVolley, me); + } + + public override void JustDied(Unit killer) + { + Talk(TextIds.EmoteActivateAltar); + + Creature colossus = instance.GetCreature(GDDataTypes.DrakkariColossus); + if (colossus) + killer.Kill(colossus); + } + + public override void UpdateAI(uint diff) + { + if (!UpdateVictim()) + return; + + _scheduler.Update(diff); + + if (me.HasUnitState(UnitState.Casting)) + return; + + DoMeleeAttackIfReady(); + } + + public override void DoAction(int action) + { + switch (action) + { + case Misc.ActionReturnToColossus: + Talk(TextIds.EmoteMojo); + DoCast(SpellIds.SurgeVisual); + Creature colossus = instance.GetCreature(GDDataTypes.DrakkariColossus); + if (colossus) + // what if the elemental is more than 80 yards from drakkari colossus ? + DoCast(colossus, SpellIds.Merge, true); + break; + } + } + + public override void DamageTaken(Unit attacker, ref uint damage) + { + if (HealthBelowPct(50)) + { + Creature colossus = instance.GetCreature(GDDataTypes.DrakkariColossus); + if (colossus) + { + if (colossus.GetAI().GetData(Misc.DataColossusPhase) == Misc.ColossusPhaseFirstElementalSummon) + { + damage = 0; + + // to prevent spell spaming + if (me.HasUnitState(UnitState.Charging)) + return; + + // not sure about this, the idea of this code is to prevent bug the elemental + // if it is not in a acceptable distance to cast the charge spell. + if (me.GetDistance(colossus) > 80.0f) + { + if (me.GetMotionMaster().GetCurrentMovementGeneratorType() == MovementGeneratorType.Point) + return; + + me.GetMotionMaster().MovePoint(0, colossus.GetPositionX(), colossus.GetPositionY(), colossus.GetPositionZ()); + return; + } + DoAction(Misc.ActionReturnToColossus); + } + } + } + } + + public override void EnterEvadeMode(EvadeReason why) + { + me.DespawnOrUnsummon(); + } + + public override void SpellHitTarget(Unit target, SpellInfo spell) + { + if (spell.Id == SpellIds.Merge) + { + Creature colossus = target.ToCreature(); + if (colossus) + { + colossus.GetAI().DoAction(Misc.ActionUnfreezeColossus); + me.DespawnOrUnsummon(); + } + } + } + + InstanceScript instance; + } + + public override CreatureAI GetAI(Creature creature) + { + return GetInstanceAI(creature); + } + } + + [Script] + class npc_living_mojo : CreatureScript + { + public npc_living_mojo() : base("npc_living_mojo") { } + + class npc_living_mojoAI : ScriptedAI + { + public npc_living_mojoAI(Creature creature) : base(creature) + { + instance = creature.GetInstanceScript(); + } + + public override void Reset() + { + _scheduler.Schedule(TimeSpan.FromSeconds(2), task => + { + DoCastVictim(SpellIds.MojoWave); + task.Repeat(TimeSpan.FromSeconds(15)); + }); + + _scheduler.Schedule(TimeSpan.FromSeconds(7), task => + { + DoCastVictim(SpellIds.MojoPuddle); + task.Repeat(TimeSpan.FromSeconds(18)); + }); + } + + void MoveMojos(Creature boss) + { + List mojosList = new List(); + boss.GetCreatureListWithEntryInGrid(mojosList, me.GetEntry(), 12.0f); + if (!mojosList.Empty()) + { + foreach (var mojo in mojosList) + { + if (mojo) + mojo.GetMotionMaster().MovePoint(1, boss.GetHomePosition()); + } + } + } + + public override void MovementInform(MovementGeneratorType type, uint id) + { + if (type != MovementGeneratorType.Point) + return; + + if (id == 1) + { + Creature colossus = instance.GetCreature(GDDataTypes.DrakkariColossus); + if (colossus) + { + colossus.GetAI().DoAction(Misc.ActionUnfreezeColossus); + if (colossus.GetAI().GetData(Misc.DataIntroDone) == 0) + colossus.GetAI().SetData(Misc.DataIntroDone, 1); + colossus.SetInCombatWithZone(); + me.DespawnOrUnsummon(); + } + } + } + + public override void AttackStart(Unit attacker) + { + if (me.GetMotionMaster().GetCurrentMovementGeneratorType() == MovementGeneratorType.Point) + return; + + // we do this checks to see if the creature is one of the creatures that sorround the boss + Creature colossus = instance.GetCreature(GDDataTypes.DrakkariColossus); + if (colossus) + { + Position homePosition = me.GetHomePosition(); + + float distance = homePosition.GetExactDist(colossus.GetHomePosition()); + + if (distance < 12.0f) + { + MoveMojos(colossus); + me.SetReactState(ReactStates.Passive); + } + else + base.AttackStart(attacker); + } + } + + public override void UpdateAI(uint diff) + { + //Return since we have no target + if (!UpdateVictim()) + return; + + _scheduler.Update(diff); + + DoMeleeAttackIfReady(); + } + + InstanceScript instance; + } + + public override CreatureAI GetAI(Creature creature) + { + return GetInstanceAI(creature); + } + } +} diff --git a/Scripts/Northrend/Gundrak/BossEck.cs b/Scripts/Northrend/Gundrak/BossEck.cs new file mode 100644 index 000000000..156bad60c --- /dev/null +++ b/Scripts/Northrend/Gundrak/BossEck.cs @@ -0,0 +1,128 @@ +/* + * Copyright (C) 2012-2017 CypherCore + * + * 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, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +using Framework.Constants; +using Game.AI; +using Game.Entities; +using Game.Scripting; +using System; + +namespace Scripts.Northrend.Gundrak.EckTheFerocious +{ + struct TextIds + { + public const uint EmoteSpawn = 0; + } + + struct SpellIds + { + public const uint Berserk = 55816; // Eck goes berserk, increasing his attack speed by 150% and all damage he deals by 500%. + public const uint Bite = 55813; // Eck bites down hard, inflicting 150% of his normal damage to an enemy. + public const uint Spit = 55814; // Eck spits toxic bile at enemies in a cone in front of him, inflicting 2970 Nature damage and draining 220 mana every 1 sec for 3 sec. + public const uint Spring1 = 55815; // Eck leaps at a distant target. --> Drops aggro and charges a random player. Tank can simply taunt him back. + public const uint Spring2 = 55837; // Eck leaps at a distant target. + } + + [Script] + class boss_eck : CreatureScript + { + public boss_eck() : base("boss_eck") { } + + class boss_eckAI : BossAI + { + public boss_eckAI(Creature creature) : base(creature, GDDataTypes.EckTheFerocious) + { + Initialize(); + Talk(TextIds.EmoteSpawn); + } + + void Initialize() + { + _berserk = false; + } + + public override void Reset() + { + _Reset(); + Initialize(); + } + + public override void EnterCombat(Unit who) + { + _EnterCombat(); + + _scheduler.SetValidator(() => !me.HasUnitState(UnitState.Casting)); + + _scheduler.Schedule(TimeSpan.FromSeconds(5), task => + { + DoCastVictim(SpellIds.Bite); + task.Repeat(TimeSpan.FromSeconds(8), TimeSpan.FromSeconds(12)); + }); + + _scheduler.Schedule(TimeSpan.FromSeconds(10), task => + { + DoCastVictim(SpellIds.Spit); + task.Repeat(TimeSpan.FromSeconds(6), TimeSpan.FromSeconds(14)); + }); + + _scheduler.Schedule(TimeSpan.FromSeconds(8), task => + { + Unit target = SelectTarget(SelectAggroTarget.Random, 1, 35.0f, true); + if (target) + DoCast(target, RandomHelper.RAND(SpellIds.Spring1, SpellIds.Spring2)); + task.Repeat(TimeSpan.FromSeconds(5), TimeSpan.FromSeconds(10)); + }); + + // 60-90 secs according to wowwiki + _scheduler.Schedule(TimeSpan.FromSeconds(60), TimeSpan.FromSeconds(90), 1, task => + { + DoCast(me, SpellIds.Berserk); + _berserk = true; + }); + } + + public override void DamageTaken(Unit attacker, ref uint damage) + { + if (!_berserk && me.HealthBelowPctDamaged(20, damage)) + { + _scheduler.RescheduleGroup(1, TimeSpan.FromSeconds(1)); + _berserk = true; + } + } + + public override void UpdateAI(uint diff) + { + if (!UpdateVictim()) + return; + + _scheduler.Update(diff); + + if (me.HasUnitState(UnitState.Casting)) + return; + + DoMeleeAttackIfReady(); + } + + bool _berserk; + } + + public override CreatureAI GetAI(Creature creature) + { + return GetInstanceAI(creature); + } + } +} \ No newline at end of file diff --git a/Scripts/Northrend/Gundrak/InstanceGundrak.cs b/Scripts/Northrend/Gundrak/InstanceGundrak.cs new file mode 100644 index 000000000..70e9c1350 --- /dev/null +++ b/Scripts/Northrend/Gundrak/InstanceGundrak.cs @@ -0,0 +1,439 @@ +/* + * Copyright (C) 2012-2017 CypherCore + * + * 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, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +using Framework.Constants; +using Framework.IO; +using Game.Entities; +using Game.Maps; +using Game.Scripting; +using System.Collections.Generic; +using System.Text; + +namespace Scripts.Northrend.Gundrak +{ + struct GDInstanceMisc + { + public const uint TimerStatueActivation = 3500; + + public static DoorData[] doorData = + { + new DoorData(GDGameObjectIds.GalDarahDoor1, GDDataTypes.GalDarah, DoorType.Passage), + new DoorData(GDGameObjectIds.GalDarahDoor2, GDDataTypes.GalDarah, DoorType.Passage), + new DoorData(GDGameObjectIds.GalDarahDoor3, GDDataTypes.GalDarah, DoorType.Room), + new DoorData(GDGameObjectIds.EckTheFerociousDoor, GDDataTypes.Moorabi, DoorType.Passage), + new DoorData(GDGameObjectIds.EckTheFerociousDoorBehind, GDDataTypes.EckTheFerocious, DoorType.Passage), + }; + + public static ObjectData[] creatureData = + { + new ObjectData(GDCreatureIds.DrakkariColossus, GDDataTypes.DrakkariColossus), + }; + + public static ObjectData[] gameObjectData = + { + new ObjectData(GDGameObjectIds.SladRanAltar, GDDataTypes.SladRanAltar), + new ObjectData(GDGameObjectIds.MoorabiAltar, GDDataTypes.MoorabiAltar), + new ObjectData(GDGameObjectIds.DrakkariColossusAltar, GDDataTypes.DrakkariColossusAltar), + new ObjectData(GDGameObjectIds.SladRanStatue, GDDataTypes.SladRanStatue), + new ObjectData(GDGameObjectIds.MoorabiStatue, GDDataTypes.MoorabiStatue), + new ObjectData(GDGameObjectIds.DrakkariColossusStatue, GDDataTypes.DrakkariColossusStatue), + new ObjectData(GDGameObjectIds.GalDarahStatue, GDDataTypes.GalDarahStatue), + new ObjectData(GDGameObjectIds.Trapdoor, GDDataTypes.Trapdoor), + new ObjectData(GDGameObjectIds.Collision, GDDataTypes.Collision) + }; + + public static Position EckSpawnPoint = new Position(1643.877930f, 936.278015f, 107.204948f, 0.668432f); + } + + struct GDDataTypes + { + // Encounter Ids // Encounter States // Boss Guids + public const uint SladRan = 0; + public const uint DrakkariColossus = 1; + public const uint Moorabi = 2; + public const uint GalDarah = 3; + public const uint EckTheFerocious = 4; + + // Additional Objects + public const uint SladRanAltar = 5; + public const uint DrakkariColossusAltar = 6; + public const uint MoorabiAltar = 7; + + public const uint SladRanStatue = 8; + public const uint DrakkariColossusStatue = 9; + public const uint MoorabiStatue = 10; + public const uint GalDarahStatue = 11; + + public const uint Trapdoor = 12; + public const uint Collision = 13; + public const uint Bridge = 14; + + public const uint StatueActivate = 15; + } + + struct GDCreatureIds + { + public const uint SladRan = 29304; + public const uint Moorabi = 29305; + public const uint GalDarah = 29306; + public const uint DrakkariColossus = 29307; + public const uint RuinDweller = 29920; + public const uint EckTheFerocious = 29932; + public const uint AltarTrigger = 30298; + } + + struct GDGameObjectIds + { + public const uint SladRanAltar = 192518; + public const uint MoorabiAltar = 192519; + public const uint DrakkariColossusAltar = 192520; + public const uint SladRanStatue = 192564; + public const uint MoorabiStatue = 192565; + public const uint GalDarahStatue = 192566; + public const uint DrakkariColossusStatue = 192567; + public const uint EckTheFerociousDoor = 192632; + public const uint EckTheFerociousDoorBehind = 192569; + public const uint GalDarahDoor1 = 193208; + public const uint GalDarahDoor2 = 193209; + public const uint GalDarahDoor3 = 192568; + public const uint Trapdoor = 193188; + public const uint Collision = 192633; + } + + struct GDSpellIds + { + public const uint FireBeamMammoth = 57068; + public const uint FireBeamSnake = 57071; + public const uint FireBeamElemental = 57072; + } + + [Script] + class instance_gundrak : InstanceMapScript + { + public instance_gundrak() : base(nameof(instance_gundrak), 604) { } + + class instance_gundrak_InstanceMapScript : InstanceScript + { + public instance_gundrak_InstanceMapScript(Map map) : base(map) + { + SetHeaders("GD"); + SetBossNumber(5); + LoadDoorData(GDInstanceMisc.doorData); + LoadObjectData(GDInstanceMisc.creatureData, GDInstanceMisc.gameObjectData); + + SladRanStatueState = GameObjectState.Active; + DrakkariColossusStatueState = GameObjectState.Active; + MoorabiStatueState = GameObjectState.Active; + } + + public override void OnCreatureCreate(Creature creature) + { + switch (creature.GetEntry()) + { + case GDCreatureIds.RuinDweller: + if (creature.IsAlive()) + DwellerGUIDs.Add(creature.GetGUID()); + break; + default: + break; + } + + base.OnCreatureCreate(creature); + } + + public override void OnGameObjectCreate(GameObject go) + { + switch (go.GetEntry()) + { + case GDGameObjectIds.SladRanAltar: + if (GetBossState(GDDataTypes.SladRan) == EncounterState.Done) + { + if (SladRanStatueState == GameObjectState.Active) + go.RemoveFlag(GameObjectFields.Flags, GameObjectFlags.NotSelectable); + else + go.SetGoState(GameObjectState.Active); + } + break; + case GDGameObjectIds.MoorabiAltar: + if (GetBossState(GDDataTypes.Moorabi) == EncounterState.Done) + { + if (MoorabiStatueState == GameObjectState.Active) + go.RemoveFlag(GameObjectFields.Flags, GameObjectFlags.NotSelectable); + else + go.SetGoState(GameObjectState.Active); + } + break; + case GDGameObjectIds.DrakkariColossusAltar: + if (GetBossState(GDDataTypes.DrakkariColossus) == EncounterState.Done) + { + if (DrakkariColossusStatueState == GameObjectState.Active) + go.RemoveFlag(GameObjectFields.Flags, GameObjectFlags.NotSelectable); + else + go.SetGoState(GameObjectState.Active); + } + break; + case GDGameObjectIds.SladRanStatue: + go.SetGoState(SladRanStatueState); + break; + case GDGameObjectIds.MoorabiStatue: + go.SetGoState(MoorabiStatueState); + break; + case GDGameObjectIds.GalDarahStatue: + go.SetGoState(CheckRequiredBosses(GDDataTypes.GalDarah) ? GameObjectState.ActiveAlternative : GameObjectState.Ready); + break; + case GDGameObjectIds.DrakkariColossusStatue: + go.SetGoState(DrakkariColossusStatueState); + break; + case GDGameObjectIds.EckTheFerociousDoor: + // Don't store door on non-heroic + if (!instance.IsHeroic()) + return; + break; + case GDGameObjectIds.Trapdoor: + go.SetGoState(CheckRequiredBosses(GDDataTypes.GalDarah) ? GameObjectState.Ready : GameObjectState.Active); + break; + case GDGameObjectIds.Collision: + go.SetGoState(CheckRequiredBosses(GDDataTypes.GalDarah) ? GameObjectState.Active : GameObjectState.Ready); + break; + default: + break; + } + + base.OnGameObjectCreate(go); + } + + public override void OnUnitDeath(Unit unit) + { + if (unit.GetEntry() == GDCreatureIds.RuinDweller) + { + DwellerGUIDs.Remove(unit.GetGUID()); + + if (DwellerGUIDs.Empty()) + unit.SummonCreature(GDCreatureIds.EckTheFerocious, GDInstanceMisc.EckSpawnPoint, TempSummonType.CorpseTimedDespawn, 300 * Time.InMilliseconds); + } + } + + public override bool SetBossState(uint type, EncounterState state) + { + if (!base.SetBossState(type, state)) + return false; + + switch (type) + { + case GDDataTypes.SladRan: + if (state == EncounterState.Done) + { + GameObject go = GetGameObject(GDDataTypes.SladRanAltar); + if (go) + go.RemoveFlag(GameObjectFields.Flags, GameObjectFlags.NotSelectable); + } + break; + case GDDataTypes.DrakkariColossus: + if (state == EncounterState.Done) + { + GameObject go = GetGameObject(GDDataTypes.DrakkariColossusAltar); + if (go) + go.RemoveFlag(GameObjectFields.Flags, GameObjectFlags.NotSelectable); + } + break; + case GDDataTypes.Moorabi: + if (state == EncounterState.Done) + { + GameObject go = GetGameObject(GDDataTypes.MoorabiAltar); + if (go) + go.RemoveFlag(GameObjectFields.Flags, GameObjectFlags.NotSelectable); + } + break; + default: + break; + } + + return true; + } + + public override bool CheckRequiredBosses(uint bossId, Player player = null) + { + if (_SkipCheckRequiredBosses(player)) + return true; + + switch (bossId) + { + case GDDataTypes.EckTheFerocious: + if (!instance.IsHeroic() || GetBossState(GDDataTypes.Moorabi) != EncounterState.Done) + return false; + break; + case GDDataTypes.GalDarah: + if (SladRanStatueState != GameObjectState.ActiveAlternative + || DrakkariColossusStatueState != GameObjectState.ActiveAlternative + || MoorabiStatueState != GameObjectState.ActiveAlternative) + return false; + break; + default: + break; + } + + return true; + } + + bool IsBridgeReady() + { + return SladRanStatueState == GameObjectState.Ready && DrakkariColossusStatueState == GameObjectState.Ready && MoorabiStatueState == GameObjectState.Ready; + } + + public override void SetData(uint type, uint data) + { + if (type == GDDataTypes.StatueActivate) + { + switch (data) + { + case GDGameObjectIds.SladRanAltar: + _events.ScheduleEvent(GDDataTypes.SladRanStatue, GDInstanceMisc.TimerStatueActivation); + break; + case GDGameObjectIds.DrakkariColossusAltar: + _events.ScheduleEvent(GDDataTypes.DrakkariColossusStatue, GDInstanceMisc.TimerStatueActivation); + break; + case GDGameObjectIds.MoorabiAltar: + _events.ScheduleEvent(GDDataTypes.MoorabiStatue, GDInstanceMisc.TimerStatueActivation); + break; + default: + break; + } + } + } + + public override void WriteSaveDataMore(StringBuilder data) + { + data.AppendFormat("{0} {1} {2} ", (uint)SladRanStatueState, (uint)DrakkariColossusStatueState, (uint)MoorabiStatueState); + } + + public override void ReadSaveDataMore(StringArguments data) + { + SladRanStatueState = (GameObjectState)data.NextUInt32(); + DrakkariColossusStatueState = (GameObjectState)data.NextUInt32(); + MoorabiStatueState = (GameObjectState)data.NextUInt32(); + + if (IsBridgeReady()) + _events.ScheduleEvent(GDDataTypes.Bridge, GDInstanceMisc.TimerStatueActivation); + } + + void ToggleGameObject(uint type, GameObjectState state) + { + GameObject go = GetGameObject(type); + if (go) + go.SetGoState(state); + + switch (type) + { + case GDDataTypes.SladRanStatue: + SladRanStatueState = state; + break; + case GDDataTypes.DrakkariColossusStatue: + DrakkariColossusStatueState = state; + break; + case GDDataTypes.MoorabiStatue: + MoorabiStatueState = state; + break; + default: + break; + } + } + + public override void Update(uint diff) + { + _events.Update(diff); + + _events.ExecuteEvents(eventId => + { + uint spellId = 0; + uint altarId = 0; + switch (eventId) + { + case GDDataTypes.SladRanStatue: + spellId = GDSpellIds.FireBeamSnake; + altarId = GDDataTypes.SladRanAltar; + break; + case GDDataTypes.DrakkariColossusStatue: + spellId = GDSpellIds.FireBeamElemental; + altarId = GDDataTypes.DrakkariColossusAltar; + break; + case GDDataTypes.MoorabiStatue: + spellId = GDSpellIds.FireBeamMammoth; + altarId = GDDataTypes.MoorabiAltar; + break; + case GDDataTypes.Bridge: + for (uint type = GDDataTypes.SladRanStatue; type <= GDDataTypes.GalDarahStatue; ++type) + ToggleGameObject(type, GameObjectState.ActiveAlternative); + ToggleGameObject(GDDataTypes.Trapdoor, GameObjectState.Ready); + ToggleGameObject(GDDataTypes.Collision, GameObjectState.Active); + SaveToDB(); + return; + default: + return; + } + + GameObject altar = GetGameObject(altarId); + if (altar) + { + Creature trigger = altar.FindNearestCreature(GDCreatureIds.AltarTrigger, 10.0f); + if (trigger) + trigger.CastSpell((Unit)null, spellId, true); + } + + // eventId equals statueId + ToggleGameObject(eventId, GameObjectState.Ready); + + if (IsBridgeReady()) + _events.ScheduleEvent(GDDataTypes.Bridge, GDInstanceMisc.TimerStatueActivation); + + SaveToDB(); + }); + } + + List DwellerGUIDs = new List(); + + GameObjectState SladRanStatueState; + GameObjectState DrakkariColossusStatueState; + GameObjectState MoorabiStatueState; + } + + public override InstanceScript GetInstanceScript(InstanceMap map) + { + return new instance_gundrak_InstanceMapScript(map); + } + } + + [Script] + class go_gundrak_altar : GameObjectScript + { + public go_gundrak_altar() : base("go_gundrak_altar") { } + + public override bool OnGossipHello(Player player, GameObject go) + { + go.SetFlag(GameObjectFields.Flags, GameObjectFlags.NotSelectable); + go.SetGoState(GameObjectState.Active); + + InstanceScript instance = go.GetInstanceScript(); + if (instance != null) + { + instance.SetData(GDDataTypes.StatueActivate, go.GetEntry()); + return true; + } + + return false; + } + } +} diff --git a/Scripts/Northrend/IcecrownCitadel/GunshipBattle.cs b/Scripts/Northrend/IcecrownCitadel/GunshipBattle.cs new file mode 100644 index 000000000..246575ba8 --- /dev/null +++ b/Scripts/Northrend/IcecrownCitadel/GunshipBattle.cs @@ -0,0 +1,2455 @@ +/* + * Copyright (C) 2012-2017 CypherCore + * + * 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, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +using Framework.Constants; +using Framework.Dynamic; +using Framework.GameMath; +using Game; +using Game.AI; +using Game.Entities; +using Game.Maps; +using Game.Movement; +using Game.Network.Packets; +using Game.Scripting; +using Game.Spells; +using System; +using System.Collections.Generic; +using System.Linq; + +namespace Scripts.Northrend.IcecrownCitadel +{ + struct GunshipTexts + { + // High Overlord Saurfang + public const uint SaySaurfangIntro1 = 0; + public const uint SaySaurfangIntro2 = 1; + public const uint SaySaurfangIntro3 = 2; + public const uint SaySaurfangIntro4 = 3; + public const uint SaySaurfangIntro5 = 4; + public const uint SaySaurfangIntro6 = 5; + public const uint SaySaurfangIntroA = 6; + public const uint SaySaurfangBoard = 7; + public const uint SaySaurfangEnterSkybreaker = 8; + public const uint SaySaurfangAxethrowers = 9; + public const uint SaySaurfangRocketeers = 10; + public const uint SaySaurfangMages = 11; + public const byte SaySaurfangVictory = 12; + public const byte SaySaurfangWipe = 13; + + // Muradin Bronzebeard + public const uint SayMuradinIntro1 = 0; + public const uint SayMuradinIntro2 = 1; + public const uint SayMuradinIntro3 = 2; + public const uint SayMuradinIntro4 = 3; + public const uint SayMuradinIntro5 = 4; + public const uint SayMuradinIntro6 = 5; + public const uint SayMuradinIntro7 = 6; + public const uint SayMuradinIntroH = 7; + public const uint SayMuradinBoard = 8; + public const uint SayMuradinEnterOrgrimmsHammer = 9; + public const uint SayMuradinRifleman = 10; + public const uint SayMuradinMortar = 11; + public const uint SayMuradinSorcerers = 12; + public const byte SayMuradinVictory = 13; + public const byte SayMuradinWipe = 14; + + public const byte SayZafodRocketPackActive = 0; + public const byte SayZafodRocketPackDisabled = 1; + + public const byte SayOverheat = 0; + } + + struct GunshipEvents + { + // High Overlord Saurfang + public const uint IntroH1 = 1; + public const uint IntroH2 = 2; + public const uint IntroSummonSkybreaker = 3; + public const uint IntroH3 = 4; + public const uint IntroH4 = 5; + public const uint IntroH5 = 6; + public const uint IntroH6 = 7; + + // Muradin Bronzebeard + public const uint IntroA1 = 1; + public const uint IntroA2 = 2; + public const uint IntroSummonOrgrimsHammer = 3; + public const uint IntroA3 = 4; + public const uint IntroA4 = 5; + public const uint IntroA5 = 6; + public const uint IntroA6 = 7; + public const uint IntroA7 = 8; + + public const uint KeepPlayerInCombat = 9; + public const uint SummonMage = 10; + public const uint Adds = 11; + public const uint AddsBoardYell = 12; + public const uint CheckRifleman = 13; + public const uint CheckMortar = 14; + public const uint Cleave = 15; + + public const uint Bladestorm = 16; + public const uint WoundingStrike = 17; + } + + struct GunshipSpells + { + // Applied On Friendly Transport Npcs + public const uint FriendlyBossDamageMod = 70339; + public const uint CheckForPlayers = 70332; + public const uint GunshipFallTeleport = 67335; + public const uint TeleportPlayersOnResetA = 70446; + public const uint TeleportPlayersOnResetH = 71284; + public const uint TeleportPlayersOnVictory = 72340; + public const uint Achievement = 72959; + public const uint AwardReputationBossKill = 73843; + + // Murading Bronzebeard + // High Overlord Saurfang + public const uint BattleFury = 69637; + public const uint RendingThrow = 70309; + public const uint Cleave = 15284; + public const uint TasteOfBlood = 69634; + + // Applied On Enemy Npcs + public const uint MeleeTargetingOnSkybreaker = 70219; + public const uint MeleeTargetingOnOrgrimsHammer = 70294; + + // Gunship Hull + public const uint ExplosionWipe = 72134; + public const uint ExplosionVictory = 72137; + + // Hostile Npcs + public const uint TeleportToEnemyShip = 70104; + public const uint BattleExperience = 71201; + public const uint Experienced = 71188; + public const uint Veteran = 71193; + public const uint Elite = 71195; + public const uint AddsBerserk = 72525; + + // Skybreaker Sorcerer + // Kor'Kron Battle-Mage + public const uint ShadowChanneling = 43897; + public const uint BelowZero = 69705; + + // Skybreaker Rifleman + // Kor'Kron Axethrower + public const uint Shoot = 70162; + public const uint HurlAxe = 70161; + public const uint BurningPitchA = 70403; + public const uint BurningPitchH = 70397; + public const uint BurningPitch = 69660; + + // Skybreaker Mortar Soldier + // Kor'Kron Rocketeer + public const uint RocketArtilleryA = 70609; + public const uint RocketArtilleryH = 69678; + public const uint BurningPitchDamageA = 70383; + public const uint BurningPitchDamageH = 70374; + + // Skybreaker Marine + // Kor'Kron Reaver + public const uint DesperateResolve = 69647; + + // Skybreaker Sergeant + // Kor'Kron Sergeant + public const uint Bladestorm = 69652; + public const uint WoundingStrike = 69651; + + // + public const uint LockPlayersAndTapChest = 72347; + public const uint OnSkybreakerDeck = 70120; + public const uint OnOrgrimsHammerDeck = 70121; + + // Rocket Pack + public const uint RocketPackDamage = 69193; + public const uint RocketBurst = 69192; + public const uint RocketPackUseable = 70348; + + // Alliance Gunship Cannon + // Team.Horde Gunship Cannon + public const uint Overheat = 69487; + public const uint EjectAllPassengersBelowZero = 68576; + public const uint EjectAllPassengersWipe = 50630; + } + + struct GunshipMiscData + { + public const uint ItemGoblinRocketPack = 49278; + + public const byte PhaseCombat = 0; + public const byte PhaseIntro = 1; + + public const uint MusicEncounter = 17289; + + public static Position SkybreakerAddsSpawnPos = new Position(15.91131f, 0.0f, 20.4628f, MathFunctions.PI); + public static Position OrgrimsHammerAddsSpawnPos = new Position(60.728395f, 0.0f, 38.93467f, MathFunctions.PI); + + // Team.Horde encounter + public static Position SkybreakerTeleportPortal = new Position(6.666975f, 0.013001f, 20.87888f, 0.0f); + public static Position OrgrimsHammerTeleportExit = new Position(7.461699f, 0.158853f, 35.72989f, 0.0f); + + // Alliance encounter + public static Position OrgrimsHammerTeleportPortal = new Position(47.550990f, -0.101778f, 37.61111f, 0.0f); + public static Position SkybreakerTeleportExit = new Position(-17.55738f, -0.090421f, 21.18366f, 0.0f); + + public static SlotInfo[] SkybreakerSlotInfo = + { + new SlotInfo(CreatureIds.SkybreakerSorcerer, -9.479858f, 0.05663967f, 20.77026f, 4.729842f, 0 ), + + new SlotInfo(CreatureIds.SkybreakerSorcerer, 6.385986f, 4.978760f, 20.55417f, 4.694936f, 0 ), + new SlotInfo(CreatureIds.SkybreakerSorcerer, 6.579102f, -4.674561f, 20.55060f, 1.553343f, 0 ), + + new SlotInfo(CreatureIds.SkybreakerRifleman, -29.563900f, -17.95801f, 20.73837f, 4.747295f, 30 ), + new SlotInfo(CreatureIds.SkybreakerRifleman, -18.017210f, -18.82056f, 20.79150f, 4.747295f, 30 ), + new SlotInfo(CreatureIds.SkybreakerRifleman, -9.1193850f, -18.79102f, 20.58887f, 4.712389f, 30 ), + new SlotInfo(CreatureIds.SkybreakerRifleman, -0.3364258f, -18.87183f, 20.56824f, 4.712389f, 30 ), + + new SlotInfo(CreatureIds.SkybreakerRifleman, -34.705810f, -17.67261f, 20.51523f, 4.729842f, 30 ), + new SlotInfo(CreatureIds.SkybreakerRifleman, -23.562010f, -18.28564f, 20.67859f, 4.729842f, 30 ), + new SlotInfo(CreatureIds.SkybreakerRifleman, -13.602780f, -18.74268f, 20.59622f, 4.712389f, 30 ), + new SlotInfo(CreatureIds.SkybreakerRifleman, -4.3350220f, -18.84619f, 20.58234f, 4.712389f, 30 ), + + new SlotInfo(CreatureIds.SkybreakerMortarSoldier, -31.70142f, 18.02783f, 20.77197f, 4.712389f, 30 ), + new SlotInfo(CreatureIds.SkybreakerMortarSoldier, -9.368652f, 18.75806f, 20.65335f, 4.712389f, 30 ), + + new SlotInfo(CreatureIds.SkybreakerMortarSoldier, -20.40851f, 18.40381f, 20.50647f, 4.694936f, 30 ), + new SlotInfo(CreatureIds.SkybreakerMortarSoldier, 0.1585693f, 18.11523f, 20.41949f, 4.729842f, 30 ), + + new SlotInfo(CreatureIds.SkybreakerMarine, SkybreakerTeleportPortal, 0 ), + new SlotInfo(CreatureIds.SkybreakerMarine, SkybreakerTeleportPortal, 0 ), + + new SlotInfo(CreatureIds.SkybreakerMarine, SkybreakerTeleportPortal, 0 ), + new SlotInfo(CreatureIds.SkybreakerMarine, SkybreakerTeleportPortal, 0 ), + + new SlotInfo(CreatureIds.SkybreakerSergeant, SkybreakerTeleportPortal, 0 ), + + new SlotInfo(CreatureIds.SkybreakerSergeant, SkybreakerTeleportPortal, 0 ) + }; + public static SlotInfo[] OrgrimsHammerSlotInfo = + { + new SlotInfo(CreatureIds.KorKronBattleMage, 13.58548f, 0.3867192f, 34.99243f, 1.53589f, 0 ), + + new SlotInfo(CreatureIds.KorKronBattleMage, 47.29290f, -4.308941f, 37.55550f, 1.570796f, 0 ), + new SlotInfo(CreatureIds.KorKronBattleMage, 47.34621f, 4.032004f, 37.70952f, 4.817109f, 0 ), + + new SlotInfo(CreatureIds.KorKronAxeThrower, -12.09280f, 27.65942f, 33.58557f, 1.53589f, 30 ), + new SlotInfo(CreatureIds.KorKronAxeThrower, -3.170555f, 28.30652f, 34.21082f, 1.53589f, 30 ), + new SlotInfo(CreatureIds.KorKronAxeThrower, 14.928040f, 26.18018f, 35.47803f, 1.53589f, 30 ), + new SlotInfo(CreatureIds.KorKronAxeThrower, 24.703310f, 25.36584f, 35.97845f, 1.53589f, 30 ), + + new SlotInfo(CreatureIds.KorKronAxeThrower, -16.65302f, 27.59668f, 33.18726f, 1.53589f, 30 ), + new SlotInfo(CreatureIds.KorKronAxeThrower, -8.084572f, 28.21448f, 33.93805f, 1.53589f, 30 ), + new SlotInfo(CreatureIds.KorKronAxeThrower, 7.594765f, 27.41968f, 35.00775f, 1.53589f, 30 ), + new SlotInfo(CreatureIds.KorKronAxeThrower, 20.763390f, 25.58215f, 35.75287f, 1.53589f, 30 ), + + new SlotInfo(CreatureIds.KorKronRocketeer, -11.44849f, -25.71838f, 33.64343f, 1.518436f, 30 ), + new SlotInfo(CreatureIds.KorKronRocketeer, 12.30336f, -25.69653f, 35.32373f, 1.518436f, 30 ), + + new SlotInfo(CreatureIds.KorKronRocketeer, -0.05931854f, -25.46399f, 34.50592f, 1.518436f, 30 ), + new SlotInfo(CreatureIds.KorKronRocketeer, 27.62149000f, -23.48108f, 36.12708f, 1.518436f, 30 ), + + new SlotInfo(CreatureIds.KorKronReaver, OrgrimsHammerTeleportPortal, 0 ), + new SlotInfo(CreatureIds.KorKronReaver, OrgrimsHammerTeleportPortal, 0 ), + + new SlotInfo(CreatureIds.KorKronReaver, OrgrimsHammerTeleportPortal, 0 ), + new SlotInfo(CreatureIds.KorKronReaver, OrgrimsHammerTeleportPortal, 0 ), + + new SlotInfo(CreatureIds.KorKronSergeant, OrgrimsHammerTeleportPortal, 0 ), + + new SlotInfo(CreatureIds.KorKronSergeant, OrgrimsHammerTeleportPortal, 0 ) + }; + + public const uint MuradinExitPathSize = 10; + public static Vector3[] MuradinExitPath = + { + new Vector3(8.130936f, -0.2699585f, 20.31728f ), + new Vector3(6.380936f, -0.2699585f, 20.31728f ), + new Vector3(3.507703f, 0.02986573f, 20.78463f ), + new Vector3(-2.767633f, 3.743143f, 20.37663f ), + new Vector3(-4.017633f, 4.493143f, 20.12663f ), + new Vector3(-7.242224f, 6.856013f, 20.03468f ), + new Vector3(-7.742224f, 8.606013f, 20.78468f ), + new Vector3(-7.992224f, 9.856013f, 21.28468f ), + new Vector3(-12.24222f, 23.10601f, 21.28468f ), + new Vector3(-14.88477f, 25.20844f, 21.59985f ) + }; + + public const uint SaurfangExitPathSize = 13; + public static Vector3[] SaurfangExitPath = + { + new Vector3(30.43987f, 0.1475817f, 36.10674f ), + new Vector3(21.36141f, -3.056458f, 35.42970f ), + new Vector3(19.11141f, -3.806458f, 35.42970f ), + new Vector3(19.01736f, -3.299440f, 35.39428f ), + new Vector3(18.6747f, -5.862823f, 35.66611f ), + new Vector3(18.6747f, -7.862823f, 35.66611f ), + new Vector3(18.1747f, -17.36282f, 35.66611f ), + new Vector3(18.1747f, -22.61282f, 35.66611f ), + new Vector3(17.9247f, -24.36282f, 35.41611f ), + new Vector3(17.9247f, -26.61282f, 35.66611f ), + new Vector3(17.9247f, -27.86282f, 35.66611f ), + new Vector3(17.9247f, -29.36282f, 35.66611f ), + new Vector3(15.33203f, -30.42621f, 35.93796f ) + }; + } + + struct EncounterActions + { + public const int SpawnMage = 1; + public const int SpawnAllAdds = 2; + public const int ClearSlot = 3; + public const int SetSlot = 4; + public const int ShipVisits = 5; + } + + enum PassengerSlots + { + // Freezing The Cannons + FreezeMage = 0, + + // Channeling The Portal, Refilled With Adds That Board Player'S Ship + Mage1 = 1, + Mage2 = 2, + + // Rifleman + Rifleman1 = 3, + Rifleman2 = 4, + Rifleman3 = 5, + Rifleman4 = 6, + + // Additional Rifleman On 25 Man + Rifleman5 = 7, + Rifleman6 = 8, + Rifleman7 = 9, + Rifleman8 = 10, + + // Mortar + Mortar1 = 11, + Mortar2 = 12, + + // Additional Spawns On 25 Man + Mortar3 = 13, + Mortar4 = 14, + + // Marines + Marine1 = 15, + Marine2 = 16, + + // Additional Spawns On 25 Man + Marine3 = 17, + Marine4 = 18, + + // Sergeants + Sergeant1 = 19, + + // Additional Spawns On 25 Man + Sergeant2 = 20, + + Max + } + + class SlotInfo + { + public SlotInfo(uint _entry, float x, float y, float z, float o, uint _cooldown) + { + Entry = _entry; + TargetPosition = new Position(x, y, z); + Cooldown = _cooldown; + } + public SlotInfo(uint _entry, Position pos, uint _cooldown) + { + Entry = _entry; + TargetPosition = pos; + Cooldown = _cooldown; + } + + public uint Entry; + public Position TargetPosition; + public uint Cooldown; + } + + class PassengerController + { + public PassengerController() + { + ResetSlots(Team.Horde); + } + + public void SetTransport(Transport transport) { _transport = transport; } + + public void ResetSlots(Team team) + { + _transport = null; + _spawnPoint = team == Team.Horde ? GunshipMiscData.OrgrimsHammerAddsSpawnPos : GunshipMiscData.SkybreakerAddsSpawnPos; + _slotInfo = team == Team.Horde ? GunshipMiscData.OrgrimsHammerSlotInfo : GunshipMiscData.SkybreakerSlotInfo; + } + + public bool SummonCreatures(PassengerSlots first, PassengerSlots last) + { + if (!_transport) + return false; + + bool summoned = false; + long now = Time.UnixTime; + for (int i = (int)first; i <= (int)last; ++i) + { + if (_respawnCooldowns[i] > now) + continue; + + if (!_controlledSlots[i].IsEmpty()) + { + Creature current = ObjectAccessor.GetCreature(_transport, _controlledSlots[i]); + if (current && current.IsAlive()) + continue; + } + Creature passenger = _transport.SummonPassenger(_slotInfo[i].Entry, SelectSpawnPoint(), TempSummonType.CorpseTimedDespawn, null, 15000); + if (passenger) + { + _controlledSlots[i] = passenger.GetGUID(); + _respawnCooldowns[i] = 0L; + passenger.GetAI().SetData(EncounterActions.SetSlot, (uint)i); + summoned = true; + } + } + + return summoned; + } + + public void ClearSlot(PassengerSlots slot) + { + _controlledSlots[(int)slot].Clear(); + _respawnCooldowns[(int)slot] = Time.UnixTime + _slotInfo[(int)slot].Cooldown; + } + + public bool SlotsNeedRefill(PassengerSlots first, PassengerSlots last) + { + for (int i = (int)first; i <= (int)last; ++i) + if (_controlledSlots[i].IsEmpty()) + return true; + + return false; + } + + Position SelectSpawnPoint() + { + float angle = RandomHelper.FRand(-MathFunctions.PI * 0.5f, MathFunctions.PI * 0.5f); + return new Position(_spawnPoint.GetPositionX() + 2.0f * (float)Math.Cos(angle), _spawnPoint.GetPositionY() + 2.0f * (float)Math.Sin(angle), + _spawnPoint.GetPositionZ(), _spawnPoint.GetOrientation()); + } + + Transport _transport; + ObjectGuid[] _controlledSlots = new ObjectGuid[(int)PassengerSlots.Max]; + long[] _respawnCooldowns = new long[(int)PassengerSlots.Max]; + Position _spawnPoint; + SlotInfo[] _slotInfo; + } + + class DelayedMovementEvent : BasicEvent + { + public DelayedMovementEvent(Creature owner, Position dest) + { + _owner = owner; + _dest = dest; + } + + public override bool Execute(ulong e_time, uint p_time) + { + if (!_owner.IsAlive()) + return true; + + _owner.GetMotionMaster().MovePoint(EventId.ChargePrepath, _owner, false); + + MoveSplineInit init = new MoveSplineInit(_owner); + init.DisableTransportPathTransformations(); + init.MoveTo(_dest.GetPositionX(), _dest.GetPositionY(), _dest.GetPositionZ(), false); + init.Launch(); + + return true; + } + + Creature _owner; + Position _dest; + } + + class ResetEncounterEvent : BasicEvent + { + public ResetEncounterEvent(Unit caster, uint spellId, ObjectGuid otherTransport) + { + _caster = caster; + _spellId = spellId; + _otherTransport = otherTransport; + } + + public override bool Execute(ulong e_time, uint p_time) + { + _caster.CastSpell(_caster, _spellId, true); + _caster.GetTransport().AddObjectToRemoveList(); + + Transport go = Global.ObjAccessor.FindTransport(_otherTransport); + if (go) + go.AddObjectToRemoveList(); + + return true; + } + + Unit _caster; + uint _spellId; + ObjectGuid _otherTransport; + } + + class BattleExperienceEvent : BasicEvent + { + public BattleExperienceEvent(Creature creature) + { + _creature = creature; + _level = 0; + } + + public override bool Execute(ulong e_time, uint p_time) + { + if (!_creature.IsAlive()) + return true; + + _creature.RemoveAurasDueToSpell(ExperiencedSpells[_level]); + ++_level; + + _creature.CastSpell(_creature, ExperiencedSpells[_level], TriggerCastFlags.FullMask); + if (_level < (_creature.GetMap().IsHeroic() ? 4 : 3)) + { + _creature.m_Events.AddEvent(this, e_time + ExperiencedTimes[_level]); + return false; + } + + return true; + } + + Creature _creature; + int _level; + + public static uint[] ExperiencedSpells = { 0, GunshipSpells.Experienced, GunshipSpells.Veteran, GunshipSpells.Elite, GunshipSpells.AddsBerserk }; + public static uint[] ExperiencedTimes = { 100000, 70000, 60000, 90000, 0 }; + } + + class gunship_npc_AI : ScriptedAI + { + public gunship_npc_AI(Creature creature) + : base(creature) + { + Instance = creature.GetInstanceScript(); + Slot = null; + Index = 0xFFFFFFFF; + + BurningPitchId = Instance.GetData(DataTypes.TeamInInstance) == (uint)Team.Horde ? GunshipSpells.BurningPitchA : GunshipSpells.BurningPitchH; + me.setRegeneratingHealth(false); + } + + public override void SetData(uint type, uint data) + { + if (type == EncounterActions.SetSlot && data < (int)PassengerSlots.Max) + { + SetSlotInfo(data); + + me.SetReactState(ReactStates.Passive); + + float x, y, z, o; + Slot.TargetPosition.GetPosition(out x, out y, out z, out o); + + me.SetTransportHomePosition(Slot.TargetPosition); + float hx = x, hy = y, hz = z, ho = o; + me.GetTransport().CalculatePassengerPosition(ref hx, ref hy, ref hz, ref ho); + me.SetHomePosition(hx, hy, hz, ho); + + me.GetMotionMaster().MovePoint(EventId.ChargePrepath, Slot.TargetPosition, false); + + MoveSplineInit init = new MoveSplineInit(me); + init.DisableTransportPathTransformations(); + init.MoveTo(x, y, z, false); + init.Launch(); + } + } + + public override void EnterEvadeMode(EvadeReason why) + { + if (!me.IsAlive() || !me.IsInCombat()) + return; + + me.DeleteThreatList(); + me.CombatStop(true); + me.GetMotionMaster().MoveTargetedHome(); + } + + public override void JustDied(Unit killer) + { + if (Slot != null) + { + Creature captain = me.FindNearestCreature(Instance.GetData(DataTypes.TeamInInstance) == (uint)Team.Horde ? CreatureIds.IGBMuradinBrozebeard : CreatureIds.IGBHighOverlordSaurfang, 200.0f); + if (captain) + captain.GetAI().SetData(EncounterActions.ClearSlot, Index); + } + } + + public override void MovementInform(MovementGeneratorType type, uint id) + { + if (type != MovementGeneratorType.Point) + return; + + if (id == EventId.ChargePrepath && Slot != null) + { + me.SetFacingTo(Slot.TargetPosition.GetOrientation()); + me.m_Events.AddEvent(new BattleExperienceEvent(me), me.m_Events.CalculateTime(BattleExperienceEvent.ExperiencedTimes[0])); + DoCast(me, GunshipSpells.BattleExperience, true); + me.SetReactState(ReactStates.Aggressive); + } + } + + public override bool CanAIAttack(Unit target) + { + if (Instance.GetBossState(Bosses.GunshipBattle) != EncounterState.InProgress) + return false; + + return target.HasAura(Instance.GetData(DataTypes.TeamInInstance) == (uint)Team.Horde ? GunshipSpells.OnOrgrimsHammerDeck : GunshipSpells.OnSkybreakerDeck); + } + + public void SetSlotInfo(uint index) + { + Index = index; + Slot = ((Instance.GetData(DataTypes.TeamInInstance) == (uint)Team.Horde ? GunshipMiscData.SkybreakerSlotInfo : GunshipMiscData.OrgrimsHammerSlotInfo)[Index]); + } + + public bool SelectVictim() + { + if (Instance.GetBossState(Bosses.GunshipBattle) != EncounterState.InProgress) + { + EnterEvadeMode(EvadeReason.Other); + return false; + } + + if (!me.HasReactState(ReactStates.Passive)) + { + Unit victim = me.SelectVictim(); + if (victim) + AttackStart(victim); + return me.GetVictim(); + } + else if (me.GetThreatManager().isThreatListEmpty()) + { + EnterEvadeMode(EvadeReason.Other); + return false; + } + + return true; + } + + public void TriggerBurningPitch() + { + if (Instance.GetBossState(Bosses.GunshipBattle) == EncounterState.InProgress && + !me.HasUnitState(UnitState.Casting) && !me.HasReactState(ReactStates.Passive) && + !me.GetSpellHistory().HasCooldown(BurningPitchId)) + { + DoCastAOE(BurningPitchId, true); + me.GetSpellHistory().AddCooldown(BurningPitchId, 0, TimeSpan.FromMilliseconds(RandomHelper.URand(3000, 4000))); + } + } + + public InstanceScript Instance; + public SlotInfo Slot; + public uint Index; + public uint BurningPitchId; + } + + [Script] + class npc_gunship : CreatureScript + { + public npc_gunship() : base("npc_gunship") { } + + class npc_gunshipAI : NullCreatureAI + { + public npc_gunshipAI(Creature creature) : base(creature) + { + _teamInInstance = (Team)creature.GetInstanceScript().GetData(DataTypes.TeamInInstance); + _summonedFirstMage = false; + _died = false; + + me.setRegeneratingHealth(false); + } + + public override void DamageTaken(Unit source, ref uint damage) + { + if (damage >= me.GetHealth()) + { + JustDied(null); + damage = (uint)me.GetHealth() - 1; + return; + } + + if (_summonedFirstMage) + return; + + if (me.GetTransport().GetEntry() != (_teamInInstance == Team.Horde ? GameObjectIds.TheSkybreaker_H : GameObjectIds.OrgrimsHammer_A)) + return; + + if (!me.HealthBelowPctDamaged(90, damage)) + return; + + _summonedFirstMage = true; + Creature captain = me.FindNearestCreature(_teamInInstance == Team.Horde ? CreatureIds.IGBMuradinBrozebeard : CreatureIds.IGBHighOverlordSaurfang, 100.0f); + if (captain) + captain.GetAI().DoAction(EncounterActions.SpawnMage); + } + + public override void JustDied(Unit killer) + { + if (_died) + return; + + _died = true; + + bool isVictory = me.GetTransport().GetEntry() == GameObjectIds.TheSkybreaker_H || me.GetTransport().GetEntry() == GameObjectIds.OrgrimsHammer_A; + InstanceScript instance = me.GetInstanceScript(); + instance.SetBossState(Bosses.GunshipBattle, isVictory ? EncounterState.Done : EncounterState.Fail); + Creature creature = me.FindNearestCreature(me.GetEntry() == CreatureIds.OrgrimsHammer ? CreatureIds.TheSkybreaker : CreatureIds.OrgrimsHammer, 200.0f); + if (creature) + { + instance.SendEncounterUnit(EncounterFrameType.Disengage, creature); + creature.RemoveAurasDueToSpell(GunshipSpells.CheckForPlayers); + } + + instance.SendEncounterUnit(EncounterFrameType.Disengage, me); + me.RemoveAurasDueToSpell(GunshipSpells.CheckForPlayers); + + me.GetMap().SetZoneMusic(AreaIds.IcecrownCitadel, 0); + List creatures = new List(); + me.GetCreatureListWithEntryInGrid(creatures, CreatureIds.MartyrStalkerIGBSaurfang, MapConst.SizeofGrids); + foreach (var stalker in creatures) + { + stalker.RemoveAllAuras(); + stalker.DeleteThreatList(); + stalker.CombatStop(true); + } + + + uint explosionSpell = isVictory ? GunshipSpells.ExplosionVictory : GunshipSpells.ExplosionWipe; + creatures.Clear(); + me.GetCreatureListWithEntryInGrid(creatures, CreatureIds.GunshipHull, 200.0f); + foreach (var hull in creatures) + { + if (hull.GetTransport() != me.GetTransport()) + continue; + + hull.CastSpell(hull, explosionSpell, TriggerCastFlags.FullMask); + } + + creatures.Clear(); + me.GetCreatureListWithEntryInGrid(creatures, _teamInInstance == Team.Horde ? CreatureIds.HordeGunshipCannon : CreatureIds.AllianceGunshipCannon, 200.0f); + foreach (var cannon in creatures) + { + if (isVictory) + { + cannon.CastSpell(cannon, GunshipSpells.EjectAllPassengersBelowZero, TriggerCastFlags.FullMask); + cannon.RemoveVehicleKit(); + } + else + cannon.CastSpell(cannon, GunshipSpells.EjectAllPassengersWipe, TriggerCastFlags.FullMask); + } + + uint creatureEntry = CreatureIds.IGBMuradinBrozebeard; + byte textId = isVictory ? GunshipTexts.SayMuradinVictory : GunshipTexts.SayMuradinWipe; + if (_teamInInstance == Team.Horde) + { + creatureEntry = CreatureIds.IGBHighOverlordSaurfang; + textId = isVictory ? GunshipTexts.SaySaurfangVictory : GunshipTexts.SaySaurfangWipe; + } + creature = me.FindNearestCreature(creatureEntry, 100.0f); + if (creature) + creature.GetAI().Talk(textId); + + if (isVictory) + { + Transport go = Global.ObjAccessor.FindTransport(instance.GetGuidData(Bosses.GunshipBattle)); + if (go) + go.EnableMovement(true); + + me.GetTransport().EnableMovement(true); + Creature ship = me.FindNearestCreature(_teamInInstance == Team.Horde ? CreatureIds.OrgrimsHammer : CreatureIds.TheSkybreaker, 200.0f); + if (ship) + { + ship.CastSpell(ship, GunshipSpells.TeleportPlayersOnVictory, TriggerCastFlags.FullMask); + ship.CastSpell(ship, GunshipSpells.Achievement, TriggerCastFlags.FullMask); + ship.CastSpell(ship, GunshipSpells.AwardReputationBossKill, TriggerCastFlags.FullMask); + } + + creatures.Clear(); + me.GetCreatureListWithEntryInGrid(creatures, CreatureIds.SkybreakerMarine, 200.0f); + me.GetCreatureListWithEntryInGrid(creatures, CreatureIds.SkybreakerSergeant, 200.0f); + me.GetCreatureListWithEntryInGrid(creatures, CreatureIds.KorKronReaver, 200.0f); + me.GetCreatureListWithEntryInGrid(creatures, CreatureIds.KorKronSergeant, 200.0f); + foreach (var obj in creatures) + obj.DespawnOrUnsummon(1); + } + else + { + uint teleportSpellId = _teamInInstance == Team.Horde ? GunshipSpells.TeleportPlayersOnResetH : GunshipSpells.TeleportPlayersOnResetA; + me.m_Events.AddEvent(new ResetEncounterEvent(me, teleportSpellId, me.GetInstanceScript().GetGuidData(DataTypes.EnemyGunship)), + me.m_Events.CalculateTime(8000)); + } + + instance.SetBossState(Bosses.GunshipBattle, isVictory ? EncounterState.Done : EncounterState.Fail); + } + + public override void SetGUID(ObjectGuid guid, int id = 0) + { + if (id != EncounterActions.ShipVisits) + return; + + if (!_shipVisits.ContainsKey(guid)) + _shipVisits[guid] = 1; + else + ++_shipVisits[guid]; + } + + public override uint GetData(uint id) + { + if (id != EncounterActions.ShipVisits) + return 0; + + uint max = 0; + foreach (var count in _shipVisits.Values) + max = Math.Max(max, count); + + return max; + } + + Team _teamInInstance; + Dictionary _shipVisits = new Dictionary(); + bool _summonedFirstMage; + bool _died; + } + + public override CreatureAI GetAI(Creature creature) + { + if (!creature.GetTransport()) + return null; + + return InstanceIcecrownCitadel.GetInstanceAI(creature); + } + } + + [Script] + class npc_high_overlord_saurfang_igb : CreatureScript + { + public npc_high_overlord_saurfang_igb() : base("npc_high_overlord_saurfang_igb") { } + + class npc_high_overlord_saurfang_igbAI : ScriptedAI + { + public npc_high_overlord_saurfang_igbAI(Creature creature) + : base(creature) + { + _instance = creature.GetInstanceScript(); + + _controller.ResetSlots(Team.Horde); + _controller.SetTransport(creature.GetTransport()); + me.setRegeneratingHealth(false); + me.m_CombatDistance = 70.0f; + } + + public override void InitializeAI() + { + base.InitializeAI(); + + _events.Reset(); + _firstMageCooldown = Time.UnixTime + 60; + _axethrowersYellCooldown = 0L; + _rocketeersYellCooldown = 0L; + } + + public override void EnterCombat(Unit target) + { + _events.SetPhase(GunshipMiscData.PhaseCombat); + DoCast(me, _instance.GetData(DataTypes.TeamInInstance) == (uint)Team.Horde ? GunshipSpells.FriendlyBossDamageMod : GunshipSpells.MeleeTargetingOnOrgrimsHammer, true); + DoCast(me, GunshipSpells.BattleFury, true); + _events.ScheduleEvent(GunshipEvents.Cleave, RandomHelper.URand(2000, 10000)); + } + + public override void EnterEvadeMode(EvadeReason why) + { + if (!me.IsAlive()) + return; + + me.DeleteThreatList(); + me.CombatStop(true); + me.GetMotionMaster().MoveTargetedHome(); + + Reset(); + } + + public override void DoAction(int action) + { + if (action == Actions.EnemyGunshipTalk) + { + Creature muradin = me.FindNearestCreature(CreatureIds.IGBMuradinBrozebeard, 100.0f); + if (muradin) + muradin.GetAI().DoAction(EncounterActions.SpawnAllAdds); + + Talk(GunshipTexts.SaySaurfangIntro5); + _events.ScheduleEvent(GunshipEvents.IntroH5, 4000); + _events.ScheduleEvent(GunshipEvents.IntroH6, 11000); + _events.ScheduleEvent(GunshipEvents.KeepPlayerInCombat, 1); + + _instance.SetBossState(Bosses.GunshipBattle, EncounterState.InProgress); + // Combat starts now + Creature skybreaker = me.FindNearestCreature(CreatureIds.TheSkybreaker, 100.0f); + if (skybreaker) + _instance.SendEncounterUnit(EncounterFrameType.Engage, skybreaker, 1); + + Creature orgrimsHammer = me.FindNearestCreature(CreatureIds.OrgrimsHammer, 100.0f); + if (orgrimsHammer) + { + _instance.SendEncounterUnit(EncounterFrameType.Engage, orgrimsHammer, 2); + orgrimsHammer.CastSpell(orgrimsHammer, GunshipSpells.CheckForPlayers, TriggerCastFlags.FullMask); + } + + me.GetMap().SetZoneMusic(AreaIds.IcecrownCitadel, GunshipMiscData.MusicEncounter); + } + else if (action == EncounterActions.SpawnMage) + { + long now = Time.UnixTime; + if (_firstMageCooldown > now) + _events.ScheduleEvent(GunshipEvents.SummonMage, (uint)(_firstMageCooldown - now) * Time.InMilliseconds); + else + _events.ScheduleEvent(GunshipEvents.SummonMage, 1); + } + else if (action == EncounterActions.SpawnAllAdds) + { + _events.ScheduleEvent(GunshipEvents.Adds, 12000); + _events.ScheduleEvent(GunshipEvents.CheckRifleman, 13000); + _events.ScheduleEvent(GunshipEvents.CheckMortar, 13000); + if (Is25ManRaid()) + _controller.SummonCreatures(PassengerSlots.Mage1, PassengerSlots.Mortar4); + else + { + _controller.SummonCreatures(PassengerSlots.Mage1, PassengerSlots.Mage2); + _controller.SummonCreatures(PassengerSlots.Mortar1, PassengerSlots.Mortar2); + _controller.SummonCreatures(PassengerSlots.Rifleman1, PassengerSlots.Rifleman4); + } + } + else if (action == Actions.ExitShip) + { + Position pos = new Position(GunshipMiscData.SaurfangExitPath[GunshipMiscData.SaurfangExitPathSize - 1].X, GunshipMiscData.SaurfangExitPath[GunshipMiscData.SaurfangExitPathSize - 1].Y, GunshipMiscData.SaurfangExitPath[GunshipMiscData.SaurfangExitPathSize - 1].Z); + me.GetMotionMaster().MovePoint(EventId.ChargePrepath, pos, false); + + var path = GunshipMiscData.SaurfangExitPath;//, SaurfangExitPath + SaurfangExitPathSize); + + MoveSplineInit init = new MoveSplineInit(me); + init.DisableTransportPathTransformations(); + init.MovebyPath(path, 0); + init.Launch(); + + me.DespawnOrUnsummon(18000); + } + } + + public override void SetData(uint type, uint data) + { + if (type == EncounterActions.ClearSlot) + { + _controller.ClearSlot((PassengerSlots)data); + if (data == (uint)PassengerSlots.FreezeMage) + _events.ScheduleEvent(GunshipEvents.SummonMage, RandomHelper.URand(30000, 33500)); + } + } + + public override void sGossipSelect(Player player, uint menuId, uint gossipListId) + { + me.RemoveFlag64(UnitFields.NpcFlags, NPCFlags.Gossip); + me.GetTransport().EnableMovement(true); + _events.SetPhase(GunshipMiscData.PhaseIntro); + _events.ScheduleEvent(GunshipEvents.IntroH1, 5000, 0, GunshipMiscData.PhaseIntro); + _events.ScheduleEvent(GunshipEvents.IntroH2, 16000, 0, GunshipMiscData.PhaseIntro); + _events.ScheduleEvent(GunshipEvents.IntroSummonSkybreaker, 24600, 0, GunshipMiscData.PhaseIntro); + _events.ScheduleEvent(GunshipEvents.IntroH3, 29600, 0, GunshipMiscData.PhaseIntro); + _events.ScheduleEvent(GunshipEvents.IntroH4, 39200, 0, GunshipMiscData.PhaseIntro); + } + + public override void DamageTaken(Unit u, ref uint damage) + { + if (me.HealthBelowPctDamaged(65, damage) && !me.HasAura(GunshipSpells.TasteOfBlood)) + DoCast(me, GunshipSpells.TasteOfBlood, true); + + if (damage > me.GetHealth()) + damage = (uint)me.GetHealth() - 1; + } + + public override void UpdateAI(uint diff) + { + if (!UpdateVictim() && !_events.IsInPhase(GunshipMiscData.PhaseIntro) && _instance.GetBossState(Bosses.GunshipBattle) != EncounterState.InProgress) + return; + + _events.Update(diff); + + _events.ExecuteEvents(eventId => + { + switch (eventId) + { + case GunshipEvents.IntroH1: + Talk(GunshipTexts.SaySaurfangIntro1); + break; + case GunshipEvents.IntroH2: + Talk(GunshipTexts.SaySaurfangIntro2); + break; + case GunshipEvents.IntroSummonSkybreaker: + Global.TransportMgr.CreateTransport(GameObjectIds.TheSkybreaker_H, 0, me.GetMap()); + break; + case GunshipEvents.IntroH3: + Talk(GunshipTexts.SaySaurfangIntro3); + break; + case GunshipEvents.IntroH4: + Talk(GunshipTexts.SaySaurfangIntro4); + break; + case GunshipEvents.IntroH5: + Creature muradin = me.FindNearestCreature(CreatureIds.IGBMuradinBrozebeard, 100.0f); + if (muradin) + muradin.GetAI().Talk(GunshipTexts.SayMuradinIntroH); + break; + case GunshipEvents.IntroH6: + Talk(GunshipTexts.SaySaurfangIntro6); + break; + case GunshipEvents.KeepPlayerInCombat: + if (_instance.GetBossState(Bosses.GunshipBattle) == EncounterState.InProgress) + { + _instance.DoCastSpellOnPlayers(GunshipSpells.LockPlayersAndTapChest); + _events.ScheduleEvent(GunshipEvents.KeepPlayerInCombat, RandomHelper.URand(5000, 8000)); + } + break; + case GunshipEvents.SummonMage: + Talk(GunshipTexts.SaySaurfangMages); + _controller.SummonCreatures(PassengerSlots.FreezeMage, PassengerSlots.FreezeMage); + break; + case GunshipEvents.Adds: + Talk(GunshipTexts.SaySaurfangEnterSkybreaker); + _controller.SummonCreatures(PassengerSlots.Mage1, PassengerSlots.Mage2); + _controller.SummonCreatures(PassengerSlots.Marine1, Is25ManRaid() ? PassengerSlots.Marine4 : PassengerSlots.Marine2); + _controller.SummonCreatures(PassengerSlots.Sergeant1, Is25ManRaid() ? PassengerSlots.Sergeant2 : PassengerSlots.Sergeant1); + Transport orgrimsHammer = me.GetTransport(); + if (orgrimsHammer) + orgrimsHammer.SummonPassenger(CreatureIds.TeleportPortal, GunshipMiscData.OrgrimsHammerTeleportPortal, TempSummonType.TimedDespawn, null, 21000); + + Transport skybreaker = Global.ObjAccessor.FindTransport(_instance.GetGuidData(Bosses.GunshipBattle)); + if (skybreaker) + skybreaker.SummonPassenger(CreatureIds.TeleportExit, GunshipMiscData.SkybreakerTeleportExit, TempSummonType.TimedDespawn, null, 23000); + + _events.ScheduleEvent(GunshipEvents.AddsBoardYell, 6000); + _events.ScheduleEvent(GunshipEvents.Adds, 60000); + break; + case GunshipEvents.AddsBoardYell: + muradin = me.FindNearestCreature(CreatureIds.IGBMuradinBrozebeard, 200.0f); + if (muradin) + muradin.GetAI().Talk(GunshipTexts.SayMuradinBoard); + break; + case GunshipEvents.CheckRifleman: + if (_controller.SummonCreatures(PassengerSlots.Rifleman1, Is25ManRaid() ? PassengerSlots.Rifleman8 : PassengerSlots.Rifleman4)) + { + if (_axethrowersYellCooldown < Time.UnixTime) + { + Talk(GunshipTexts.SaySaurfangAxethrowers); + _axethrowersYellCooldown = Time.UnixTime + 5; + } + } + _events.ScheduleEvent(GunshipEvents.CheckRifleman, 1000); + break; + case GunshipEvents.CheckMortar: + if (_controller.SummonCreatures(PassengerSlots.Mortar1, Is25ManRaid() ? PassengerSlots.Mortar4 : PassengerSlots.Mortar2)) + { + if (_rocketeersYellCooldown < Time.UnixTime) + { + Talk(GunshipTexts.SaySaurfangRocketeers); + _rocketeersYellCooldown = Time.UnixTime + 5; + } + } + _events.ScheduleEvent(GunshipEvents.CheckMortar, 1000); + break; + case GunshipEvents.Cleave: + DoCastVictim(GunshipSpells.Cleave); + _events.ScheduleEvent(GunshipEvents.Cleave, RandomHelper.URand(2000, 10000)); + break; + default: + break; + } + }); + + if (me.IsWithinMeleeRange(me.GetVictim())) + DoMeleeAttackIfReady(); + else if (me.isAttackReady()) + { + DoCastVictim(GunshipSpells.RendingThrow); + me.resetAttackTimer(); + } + } + + public override bool CanAIAttack(Unit target) + { + return target.HasAura(GunshipSpells.OnOrgrimsHammerDeck) || !target.IsControlledByPlayer(); + } + + PassengerController _controller = new PassengerController(); + InstanceScript _instance; + long _firstMageCooldown; + long _axethrowersYellCooldown; + long _rocketeersYellCooldown; + } + + public override CreatureAI GetAI(Creature creature) + { + return InstanceIcecrownCitadel.GetInstanceAI(creature); + } + } + + [Script] + class npc_muradin_bronzebeard_igb : CreatureScript + { + public npc_muradin_bronzebeard_igb() : base("npc_muradin_bronzebeard_igb") { } + + class npc_muradin_bronzebeard_igbAI : ScriptedAI + { + public npc_muradin_bronzebeard_igbAI(Creature creature) + : base(creature) + { + _instance = creature.GetInstanceScript(); + + _controller.ResetSlots(Team.Alliance); + _controller.SetTransport(creature.GetTransport()); + me.setRegeneratingHealth(false); + me.m_CombatDistance = 70.0f; + } + + public override void InitializeAI() + { + base.InitializeAI(); + + _events.Reset(); + _firstMageCooldown = Time.UnixTime + 60; + _riflemanYellCooldown = 0L; + _mortarYellCooldown = 0L; + } + + public override void EnterCombat(Unit target) + { + _events.SetPhase(GunshipMiscData.PhaseCombat); + DoCast(me, _instance.GetData(DataTypes.TeamInInstance) == (uint)Team.Alliance ? GunshipSpells.FriendlyBossDamageMod : GunshipSpells.MeleeTargetingOnSkybreaker, true); + DoCast(me, GunshipSpells.BattleFury, true); + _events.ScheduleEvent(GunshipEvents.Cleave, RandomHelper.URand(2000, 10000)); + } + + public override void EnterEvadeMode(EvadeReason why) + { + if (!me.IsAlive()) + return; + + me.DeleteThreatList(); + me.CombatStop(true); + me.GetMotionMaster().MoveTargetedHome(); + + Reset(); + } + + public override void DoAction(int action) + { + if (action == Actions.EnemyGunshipTalk) + { + Creature muradin = me.FindNearestCreature(CreatureIds.IGBHighOverlordSaurfang, 100.0f); + if (muradin) + muradin.GetAI().DoAction(EncounterActions.SpawnAllAdds); + + Talk(GunshipTexts.SayMuradinIntro6); + _events.ScheduleEvent(GunshipEvents.IntroA6, 5000); + _events.ScheduleEvent(GunshipEvents.IntroA7, 11000); + _events.ScheduleEvent(GunshipEvents.KeepPlayerInCombat, 1); + + _instance.SetBossState(Bosses.GunshipBattle, EncounterState.InProgress); + // Combat starts now + Creature orgrimsHammer = me.FindNearestCreature(CreatureIds.OrgrimsHammer, 100.0f); + if (orgrimsHammer) + _instance.SendEncounterUnit(EncounterFrameType.Engage, orgrimsHammer, 1); + + Creature skybreaker = me.FindNearestCreature(CreatureIds.TheSkybreaker, 100.0f); + if (skybreaker) + { + _instance.SendEncounterUnit(EncounterFrameType.Engage, skybreaker, 2); + skybreaker.CastSpell(skybreaker, GunshipSpells.CheckForPlayers, TriggerCastFlags.FullMask); + } + + me.GetMap().SetZoneMusic(AreaIds.IcecrownCitadel, GunshipMiscData.MusicEncounter); + } + else if (action == EncounterActions.SpawnMage) + { + long now = Time.UnixTime; + if (_firstMageCooldown < now) + _events.ScheduleEvent(GunshipEvents.SummonMage, (uint)(now - _firstMageCooldown) * Time.InMilliseconds); + else + _events.ScheduleEvent(GunshipEvents.SummonMage, 1); + } + else if (action == EncounterActions.SpawnAllAdds) + { + _events.ScheduleEvent(GunshipEvents.Adds, 12000); + _events.ScheduleEvent(GunshipEvents.CheckRifleman, 13000); + _events.ScheduleEvent(GunshipEvents.CheckMortar, 13000); + if (Is25ManRaid()) + _controller.SummonCreatures(PassengerSlots.Mage1, PassengerSlots.Mortar4); + else + { + _controller.SummonCreatures(PassengerSlots.Mage1, PassengerSlots.Mage2); + _controller.SummonCreatures(PassengerSlots.Mortar1, PassengerSlots.Mortar2); + _controller.SummonCreatures(PassengerSlots.Rifleman1, PassengerSlots.Rifleman4); + } + } + } + + public override void SetData(uint type, uint data) + { + if (type == EncounterActions.ClearSlot) + { + _controller.ClearSlot((PassengerSlots)data); + if (data == (uint)PassengerSlots.FreezeMage) + _events.ScheduleEvent(GunshipEvents.SummonMage, RandomHelper.URand(30000, 33500)); + } + } + + public override void sGossipSelect(Player player, uint menuId, uint gossipListId) + { + me.RemoveFlag64(UnitFields.NpcFlags, NPCFlags.Gossip); + me.GetTransport().EnableMovement(true); + _events.SetPhase(GunshipMiscData.PhaseIntro); + _events.ScheduleEvent(GunshipEvents.IntroA1, 5000); + _events.ScheduleEvent(GunshipEvents.IntroA2, 10000, 0, GunshipMiscData.PhaseIntro); + _events.ScheduleEvent(GunshipEvents.IntroSummonOrgrimsHammer, 28000, 0, GunshipMiscData.PhaseIntro); + _events.ScheduleEvent(GunshipEvents.IntroA3, 33000, 0, GunshipMiscData.PhaseIntro); + _events.ScheduleEvent(GunshipEvents.IntroA4, 39000, 0, GunshipMiscData.PhaseIntro); + _events.ScheduleEvent(GunshipEvents.IntroA5, 45000, 0, GunshipMiscData.PhaseIntro); + } + + public override void DamageTaken(Unit u, ref uint damage) + { + if (me.HealthBelowPctDamaged(65, damage) && me.HasAura(GunshipSpells.TasteOfBlood)) + DoCast(me, GunshipSpells.TasteOfBlood, true); + + if (damage >= me.GetHealth()) + damage = (uint)me.GetHealth() - 1; + } + + public override void UpdateAI(uint diff) + { + if (!UpdateVictim() && !_events.IsInPhase(GunshipMiscData.PhaseIntro) && _instance.GetBossState(Bosses.GunshipBattle) != EncounterState.InProgress) + return; + + _events.Update(diff); + + _events.ExecuteEvents(eventId => + { + switch (eventId) + { + case GunshipEvents.IntroA1: + Talk(GunshipTexts.SayMuradinIntro1); + break; + case GunshipEvents.IntroA2: + Talk(GunshipTexts.SayMuradinIntro2); + break; + case GunshipEvents.IntroSummonOrgrimsHammer: + Global.TransportMgr.CreateTransport(GameObjectIds.OrgrimsHammer_A, 0, me.GetMap()); + break; + case GunshipEvents.IntroA3: + Talk(GunshipTexts.SayMuradinIntro3); + break; + case GunshipEvents.IntroA4: + Talk(GunshipTexts.SayMuradinIntro4); + break; + case GunshipEvents.IntroA5: + Talk(GunshipTexts.SayMuradinIntro5); + break; + case GunshipEvents.IntroA6: + Creature saurfang = me.FindNearestCreature(CreatureIds.IGBHighOverlordSaurfang, 100.0f); + if (saurfang) + saurfang.GetAI().Talk(GunshipTexts.SaySaurfangIntroA); + break; + case GunshipEvents.IntroA7: + Talk(GunshipTexts.SayMuradinIntro7); + break; + case GunshipEvents.KeepPlayerInCombat: + if (_instance.GetBossState(Bosses.GunshipBattle) == EncounterState.InProgress) + { + _instance.DoCastSpellOnPlayers(GunshipSpells.LockPlayersAndTapChest); + _events.ScheduleEvent(GunshipEvents.KeepPlayerInCombat, RandomHelper.URand(5000, 8000)); + } + break; + case GunshipEvents.SummonMage: + Talk(GunshipTexts.SayMuradinSorcerers); + _controller.SummonCreatures(PassengerSlots.FreezeMage, PassengerSlots.FreezeMage); + break; + case GunshipEvents.Adds: + Talk(GunshipTexts.SayMuradinEnterOrgrimmsHammer); + _controller.SummonCreatures(PassengerSlots.Mage1, PassengerSlots.Mage2); + _controller.SummonCreatures(PassengerSlots.Marine1, Is25ManRaid() ? PassengerSlots.Marine4 : PassengerSlots.Marine2); + _controller.SummonCreatures(PassengerSlots.Sergeant1, Is25ManRaid() ? PassengerSlots.Sergeant2 : PassengerSlots.Sergeant1); + + Transport skybreaker = me.GetTransport(); + if (skybreaker) + skybreaker.SummonPassenger(CreatureIds.TeleportPortal, GunshipMiscData.SkybreakerTeleportPortal, TempSummonType.TimedDespawn, null, 21000); + + Transport go = Global.ObjAccessor.FindTransport(_instance.GetGuidData(Bosses.GunshipBattle)); + if (go) + go.SummonPassenger(CreatureIds.TeleportExit, GunshipMiscData.OrgrimsHammerTeleportExit, TempSummonType.TimedDespawn, null, 23000); + + _events.ScheduleEvent(GunshipEvents.AddsBoardYell, 6000); + _events.ScheduleEvent(GunshipEvents.Adds, 60000); + break; + case GunshipEvents.AddsBoardYell: + saurfang = me.FindNearestCreature(CreatureIds.IGBHighOverlordSaurfang, 200.0f); + if (saurfang) + saurfang.GetAI().Talk(GunshipTexts.SaySaurfangBoard); + break; + case GunshipEvents.CheckRifleman: + if (_controller.SummonCreatures(PassengerSlots.Rifleman1, Is25ManRaid() ? PassengerSlots.Rifleman8 : PassengerSlots.Rifleman4)) + { + if (_riflemanYellCooldown < Time.UnixTime) + { + Talk(GunshipTexts.SayMuradinRifleman); + _riflemanYellCooldown = Time.UnixTime + 5; + } + } + _events.ScheduleEvent(GunshipEvents.CheckRifleman, 1000); + break; + case GunshipEvents.CheckMortar: + if (_controller.SummonCreatures(PassengerSlots.Mortar1, Is25ManRaid() ? PassengerSlots.Mortar4 : PassengerSlots.Mortar2)) + { + if (_mortarYellCooldown < Time.UnixTime) + { + Talk(GunshipTexts.SayMuradinMortar); + _mortarYellCooldown = Time.UnixTime + 5; + } + } + _events.ScheduleEvent(GunshipEvents.CheckMortar, 1000); + break; + case GunshipEvents.Cleave: + DoCastVictim(GunshipSpells.Cleave); + _events.ScheduleEvent(GunshipEvents.Cleave, RandomHelper.URand(2000, 10000)); + break; + default: + break; + } + }); + + if (me.IsWithinMeleeRange(me.GetVictim())) + DoMeleeAttackIfReady(); + else if (me.isAttackReady()) + { + DoCastVictim(GunshipSpells.RendingThrow); + me.resetAttackTimer(); + } + } + + public override bool CanAIAttack(Unit target) + { + if (_instance.GetBossState(Bosses.GunshipBattle) != EncounterState.InProgress) + return false; + + return target.HasAura(GunshipSpells.OnSkybreakerDeck) || !target.IsControlledByPlayer(); + } + + PassengerController _controller = new PassengerController(); + InstanceScript _instance; + long _firstMageCooldown; + long _riflemanYellCooldown; + long _mortarYellCooldown; + } + + public override CreatureAI GetAI(Creature creature) + { + return InstanceIcecrownCitadel.GetInstanceAI(creature); + } + } + + [Script] + class npc_zafod_boombox : CreatureScript + { + public npc_zafod_boombox() : base("npc_zafod_boombox") { } + + class npc_zafod_boomboxAI : gunship_npc_AI + { + public npc_zafod_boomboxAI(Creature creature) + : base(creature) { } + + public override void Reset() + { + me.SetReactState(ReactStates.Passive); + } + + public override void sGossipSelect(Player player, uint menuId, uint gossipListId) + { + player.AddItem(GunshipMiscData.ItemGoblinRocketPack, 1); + player.PlayerTalkClass.SendCloseGossip(); + } + + public override void UpdateAI(uint diff) + { + UpdateVictim(); + } + } + + public override CreatureAI GetAI(Creature creature) + { + return InstanceIcecrownCitadel.GetInstanceAI(creature); + } + } + + class npc_gunship_boarding_addAI : gunship_npc_AI + { + public npc_gunship_boarding_addAI(Creature creature) : base(creature) + { + me.m_CombatDistance = 80.0f; + _usedDesperateResolve = false; + } + + public override void SetData(uint type, uint data) + { + // detach from captain + if (type == EncounterActions.SetSlot) + { + SetSlotInfo(data); + + me.SetReactState(ReactStates.Passive); + + me.m_Events.AddEvent(new DelayedMovementEvent(me, Slot.TargetPosition), me.m_Events.CalculateTime(3000 * (Index - (int)PassengerSlots.Marine1))); + + Creature captain = me.FindNearestCreature(Instance.GetData(DataTypes.TeamInInstance) == (uint)Team.Horde ? CreatureIds.IGBMuradinBrozebeard : CreatureIds.IGBHighOverlordSaurfang, 200.0f); + if (captain) + captain.GetAI().SetData(EncounterActions.ClearSlot, Index); + } + } + + public override void MovementInform(MovementGeneratorType type, uint id) + { + if (type != MovementGeneratorType.Point) + return; + + if (id == EventId.ChargePrepath && Slot != null) + { + Position otherTransportPos = Instance.GetData(DataTypes.TeamInInstance) == (uint)Team.Horde ? GunshipMiscData.OrgrimsHammerTeleportExit : GunshipMiscData.SkybreakerTeleportExit; + float x, y, z, o; + otherTransportPos.GetPosition(out x, out y, out z, out o); + + Transport myTransport = me.GetTransport(); + if (!myTransport) + return; + + Transport destTransport = Global.ObjAccessor.FindTransport(Instance.GetGuidData(Bosses.GunshipBattle)); + if (destTransport) + destTransport.CalculatePassengerPosition(ref x, ref y, ref z, ref o); + + float angle = RandomHelper.URand(0, MathFunctions.PI * 2.0f); + x += 2.0f * (float)Math.Cos(angle); + y += 2.0f * (float)Math.Sin(angle); + + me.SetHomePosition(x, y, z, o); + myTransport.CalculatePassengerOffset(ref x, ref y, ref z, ref o); + me.SetTransportHomePosition(x, y, z, o); + + me.m_Events.AddEvent(new BattleExperienceEvent(me), me.m_Events.CalculateTime(BattleExperienceEvent.ExperiencedTimes[0])); + DoCast(me, GunshipSpells.BattleExperience, true); + DoCast(me, GunshipSpells.TeleportToEnemyShip, true); + DoCast(me, Instance.GetData(DataTypes.TeamInInstance) == (uint)Team.Horde ? GunshipSpells.MeleeTargetingOnOrgrimsHammer : GunshipSpells.MeleeTargetingOnSkybreaker, true); + me.GetSpellHistory().AddCooldown(BurningPitchId, 0, TimeSpan.FromSeconds(3)); + + List players = new List(); + var check = new UnitAuraCheck(true, Instance.GetData(DataTypes.TeamInInstance) == (uint)Team.Horde ? GunshipSpells.OnOrgrimsHammerDeck : GunshipSpells.OnSkybreakerDeck); + var searcher = new PlayerListSearcher(me, players, check); + Cell.VisitWorldObjects(me, searcher, 200.0f); + + players.RemoveAll(player => me._IsTargetAcceptable(player) || !me.CanStartAttack(player, true)); + + if (!players.Empty()) + { + players.Sort(new ObjectDistanceOrderPred(me)); + foreach (var pl in players) + me.AddThreat(pl, 1.0f); + + AttackStart(players.First()); + } + + me.SetReactState(ReactStates.Aggressive); + } + } + public override void DamageTaken(Unit attacker, ref uint damage) + { + if (_usedDesperateResolve) + return; + + if (!me.HealthBelowPctDamaged(25, damage)) + return; + + _usedDesperateResolve = true; + DoCast(me, GunshipSpells.DesperateResolve, true); + } + + public override void UpdateAI(uint diff) + { + if (!SelectVictim()) + { + TriggerBurningPitch(); + return; + } + + if (!HasAttackablePlayerNearby()) + TriggerBurningPitch(); + + DoMeleeAttackIfReady(); + } + + public override bool CanAIAttack(Unit target) + { + uint spellId = GunshipSpells.OnSkybreakerDeck; + uint creatureEntry = CreatureIds.IGBMuradinBrozebeard; + if (Instance.GetData(DataTypes.TeamInInstance) == (uint)Team.Horde) + { + spellId = GunshipSpells.OnOrgrimsHammerDeck; + creatureEntry = CreatureIds.IGBHighOverlordSaurfang; + } + + return target.HasAura(spellId) || target.GetEntry() == creatureEntry; + } + + public bool HasAttackablePlayerNearby() + { + List players = new List(); + var check = new UnitAuraCheck(true, Instance.GetData(DataTypes.TeamInInstance) == (uint)Team.Horde ? GunshipSpells.OnOrgrimsHammerDeck : GunshipSpells.OnSkybreakerDeck); + var searcher = new PlayerListSearcher(me, players, check); + Cell.VisitWorldObjects(me, searcher, 200.0f); + + players.RemoveAll(player => !me._IsTargetAcceptable(player) || !me.CanStartAttack(player, true)); + + return !players.Empty(); + } + + bool _usedDesperateResolve; + } + + [Script] + class npc_gunship_boarding_leader : CreatureScript + { + public npc_gunship_boarding_leader() : base("npc_gunship_boarding_leader") { } + + class npc_gunship_boarding_leaderAI : npc_gunship_boarding_addAI + { + public npc_gunship_boarding_leaderAI(Creature creature) + : base(creature) { } + + public override void EnterCombat(Unit target) + { + base.EnterCombat(target); + _events.ScheduleEvent(GunshipEvents.Bladestorm, RandomHelper.URand(13000, 18000)); + _events.ScheduleEvent(GunshipEvents.WoundingStrike, RandomHelper.URand(8000, 10000)); + } + + public override void UpdateAI(uint diff) + { + if (!SelectVictim()) + { + TriggerBurningPitch(); + return; + } + + _events.Update(diff); + + if (me.HasUnitState(UnitState.Casting) || me.HasAura(GunshipSpells.Bladestorm)) + return; + + if (!HasAttackablePlayerNearby()) + TriggerBurningPitch(); + + _events.ExecuteEvents(eventId => + { + switch (eventId) + { + case GunshipEvents.Bladestorm: + DoCastAOE(GunshipSpells.Bladestorm); + _events.ScheduleEvent(GunshipEvents.Bladestorm, RandomHelper.URand(25000, 30000)); + break; + case GunshipEvents.WoundingStrike: + DoCastVictim(GunshipSpells.WoundingStrike); + _events.ScheduleEvent(GunshipEvents.WoundingStrike, RandomHelper.URand(9000, 13000)); + break; + default: + break; + } + }); + + DoMeleeAttackIfReady(); + } + } + + public override CreatureAI GetAI(Creature creature) + { + return InstanceIcecrownCitadel.GetInstanceAI(creature); + } + } + + [Script] + class npc_gunship_boarding_add : CreatureScript + { + public npc_gunship_boarding_add() : base("npc_gunship_boarding_add") { } + + public override CreatureAI GetAI(Creature creature) + { + return InstanceIcecrownCitadel.GetInstanceAI(creature); + } + } + + [Script] + class npc_gunship_gunner : CreatureScript + { + public npc_gunship_gunner() : base("npc_gunship_gunner") { } + + class npc_gunship_gunnerAI : gunship_npc_AI + { + public npc_gunship_gunnerAI(Creature creature) + : base(creature) + { + creature.m_CombatDistance = 200.0f; + } + + public override void AttackStart(Unit target) + { + me.Attack(target, false); + } + + public override void MovementInform(MovementGeneratorType type, uint id) + { + base.MovementInform(type, id); + if (type == MovementGeneratorType.Point && id == EventId.ChargePrepath) + me.SetControlled(true, UnitState.Root); + } + + public override void UpdateAI(uint diff) + { + if (!SelectVictim()) + { + TriggerBurningPitch(); + return; + } + + DoSpellAttackIfReady(me.GetEntry() == CreatureIds.SkybreakerRifleman ? GunshipSpells.Shoot : GunshipSpells.HurlAxe); + } + } + + public override CreatureAI GetAI(Creature creature) + { + return InstanceIcecrownCitadel.GetInstanceAI(creature); + } + } + + [Script] + class npc_gunship_rocketeer : CreatureScript + { + public npc_gunship_rocketeer() : base("npc_gunship_rocketeer") { } + + class npc_gunship_rocketeerAI : gunship_npc_AI + { + public npc_gunship_rocketeerAI(Creature creature) + : base(creature) + { + creature.m_CombatDistance = 200.0f; + } + + public override void MovementInform(MovementGeneratorType type, uint id) + { + base.MovementInform(type, id); + if (type == MovementGeneratorType.Point && id == EventId.ChargePrepath) + me.SetControlled(true, UnitState.Root); + } + + public override void UpdateAI(uint diff) + { + if (!SelectVictim()) + return; + + if (me.HasUnitState(UnitState.Casting)) + return; + + uint spellId = me.GetEntry() == CreatureIds.SkybreakerMortarSoldier ? GunshipSpells.RocketArtilleryA : GunshipSpells.RocketArtilleryH; + if (me.GetSpellHistory().HasCooldown(spellId)) + return; + + DoCastAOE(spellId, true); + me.GetSpellHistory().AddCooldown(spellId, 0, TimeSpan.FromSeconds(9)); + } + } + + public override CreatureAI GetAI(Creature creature) + { + return InstanceIcecrownCitadel.GetInstanceAI(creature); + } + } + + [Script] + class npc_gunship_mage : CreatureScript + { + public npc_gunship_mage() : base("npc_gunship_mage") { } + + class npc_gunship_mageAI : gunship_npc_AI + { + public npc_gunship_mageAI(Creature creature) : base(creature) + { + me.SetReactState(ReactStates.Passive); + } + + public override void EnterEvadeMode(EvadeReason why) { } + + public override void MovementInform(MovementGeneratorType type, uint id) + { + if (type != MovementGeneratorType.Point) + return; + + if (id == EventId.ChargePrepath && Slot != null) + { + SlotInfo[] slots = Instance.GetData(DataTypes.TeamInInstance) == (uint)Team.Horde ? GunshipMiscData.SkybreakerSlotInfo : GunshipMiscData.OrgrimsHammerSlotInfo; + me.SetFacingTo(slots[Index].TargetPosition.GetOrientation()); + switch ((PassengerSlots)Index) + { + case PassengerSlots.FreezeMage: + DoCastAOE(GunshipSpells.BelowZero); + break; + case PassengerSlots.Mage1: + case PassengerSlots.Mage2: + DoCastAOE(GunshipSpells.ShadowChanneling); + break; + default: + break; + } + + me.SetControlled(true, UnitState.Root); + } + } + + public override void UpdateAI(uint diff) + { + UpdateVictim(); + } + + public override bool CanAIAttack(Unit target) + { + return true; + } + } + + public override CreatureAI GetAI(Creature creature) + { + return InstanceIcecrownCitadel.GetInstanceAI(creature); + } + } + + /** @HACK This AI only resets MOVEMENTFLAG_ROOT on the vehicle. + Currently the core always removes MOVEMENTFLAG_ROOT sent from client packets to prevent cheaters from freezing clients of other players + but it actually is a valid flag - needs more research to fix both freezes and keep the flag as is (see WorldSession.ReadMovementInfo) + + Example packet: + ClientToServer: CMSG_FORCE_MOVE_ROOT_ACK (0x00E9) Length: 67 ConnectionIndex: 0 Time: 03/04/2010 03:57:55.000 Number: 471326 + Guid: + Movement Counter: 80 + Movement Flags: OnTransport, Root (2560) + Extra Movement Flags: None (0) + Time: 52291611 + Position: X: -396.0302 Y: 2482.906 Z: 249.86 + Orientation: 1.468665 + Transport GUID: Full: 0x1FC0000000000460 Type: MOTransport Low: 1120 + Transport Position: X: -6.152398 Y: -23.49037 Z: 21.64464 O: 4.827727 + Transport Time: 9926 + Transport Seat: 255 + Fall Time: 824 + */ + [Script] + class npc_gunship_cannon : CreatureScript + { + public npc_gunship_cannon() : base("npc_gunship_cannon") { } + + class npc_gunship_cannonAI : PassiveAI + { + public npc_gunship_cannonAI(Creature creature) + : base(creature) { } + + public override void OnCharmed(bool apply) { } + + public override void PassengerBoarded(Unit passenger, sbyte seat, bool apply) + { + if (!apply) + { + me.SetControlled(false, UnitState.Root); + me.SetControlled(true, UnitState.Root); + } + } + } + + public override CreatureAI GetAI(Creature creature) + { + return new npc_gunship_cannonAI(creature); + } + } + + [Script] + class spell_igb_rocket_pack : SpellScriptLoader + { + public spell_igb_rocket_pack() : base("spell_igb_rocket_pack") { } + + class spell_igb_rocket_pack_AuraScript : AuraScript + { + public override bool Validate(SpellInfo spellInfo) + { + return ValidateSpellInfo(GunshipSpells.RocketPackDamage, GunshipSpells.RocketBurst); + } + + void HandlePeriodic(AuraEffect aurEff) + { + if (GetTarget().moveSpline.Finalized()) + Remove(AuraRemoveMode.Expire); + } + + void HandleRemove(AuraEffect aurEff, AuraEffectHandleModes mode) + { + SpellInfo damageInfo = Global.SpellMgr.GetSpellInfo(GunshipSpells.RocketPackDamage); + GetTarget().CastCustomSpell(GunshipSpells.RocketPackDamage, SpellValueMod.BasePoint0, (int)(2 * (damageInfo.GetEffect(0).CalcValue() + aurEff.GetTickNumber() * aurEff.GetPeriod())), null, TriggerCastFlags.FullMask); + GetTarget().CastSpell(null, GunshipSpells.RocketBurst, TriggerCastFlags.FullMask); + } + + public override void Register() + { + OnEffectPeriodic.Add(new EffectPeriodicHandler(HandlePeriodic, 0, AuraType.PeriodicDummy)); + OnEffectRemove.Add(new EffectApplyHandler(HandleRemove, 0, AuraType.PeriodicDummy, AuraEffectHandleModes.Real)); + } + } + + public override AuraScript GetAuraScript() + { + return new spell_igb_rocket_pack_AuraScript(); + } + } + + [Script] + class spell_igb_rocket_pack_useable : SpellScriptLoader + { + public spell_igb_rocket_pack_useable() : base("spell_igb_rocket_pack_useable") { } + + class spell_igb_rocket_pack_useable_AuraScript : AuraScript + { + public override bool Load() + { + return GetOwner().GetInstanceScript() != null; + } + + bool CheckAreaTarget(Unit target) + { + return target.IsTypeId(TypeId.Player) && GetOwner().GetInstanceScript().GetBossState(Bosses.GunshipBattle) != EncounterState.Done; + } + + void HandleApply(AuraEffect aurEff, AuraEffectHandleModes mode) + { + Creature owner = GetOwner().ToCreature(); + if (owner) + { + Player target = GetTarget().ToPlayer(); + if (target) + if (target.HasItemCount(GunshipMiscData.ItemGoblinRocketPack, 1)) + Global.CreatureTextMgr.SendChat(owner, GunshipTexts.SayZafodRocketPackActive, target, ChatMsg.Addon, Language.Addon, CreatureTextRange.Normal, 0, Team.Other, false, target); + } + } + + void HandleRemove(AuraEffect aurEff, AuraEffectHandleModes mode) + { + Creature owner = GetOwner().ToCreature(); + if (owner) + { + Player target = GetTarget().ToPlayer(); + if (target) + if (target.HasItemCount(GunshipMiscData.ItemGoblinRocketPack, 1)) + Global.CreatureTextMgr.SendChat(owner, GunshipTexts.SayZafodRocketPackDisabled, target, ChatMsg.Addon, Language.Addon, CreatureTextRange.Normal, 0, Team.Other, false, target); + } + } + + public override void Register() + { + DoCheckAreaTarget.Add(new CheckAreaTargetHandler(CheckAreaTarget)); + AfterEffectApply.Add(new EffectApplyHandler(HandleApply, 0, AuraType.Dummy, AuraEffectHandleModes.Real)); + AfterEffectRemove.Add(new EffectApplyHandler(HandleRemove, 0, AuraType.Dummy, AuraEffectHandleModes.Real)); + } + } + + public override AuraScript GetAuraScript() + { + return new spell_igb_rocket_pack_useable_AuraScript(); + } + } + + [Script] + class spell_igb_on_gunship_deck : SpellScriptLoader + { + public spell_igb_on_gunship_deck() : base("spell_igb_on_gunship_deck") { } + + class spell_igb_on_gunship_deck_AuraScript : AuraScript + { + public override bool Load() + { + InstanceScript instance = GetOwner().GetInstanceScript(); + if (instance != null) + _teamInInstance = (Team)instance.GetData(DataTypes.TeamInInstance); + else + _teamInInstance = 0; + return true; + } + + bool CheckAreaTarget(Unit unit) + { + return unit.IsTypeId(TypeId.Player); + } + + void HandleApply(AuraEffect aurEff, AuraEffectHandleModes mode) + { + if (GetSpellInfo().Id == (_teamInInstance == Team.Horde ? GunshipSpells.OnSkybreakerDeck : GunshipSpells.OnOrgrimsHammerDeck)) + { + Creature gunship = GetOwner().FindNearestCreature(_teamInInstance == Team.Horde ? CreatureIds.OrgrimsHammer : CreatureIds.TheSkybreaker, 200.0f); + if (gunship) + gunship.GetAI().SetGUID(GetTarget().GetGUID(), EncounterActions.ShipVisits); + } + } + + public override void Register() + { + DoCheckAreaTarget.Add(new CheckAreaTargetHandler(CheckAreaTarget)); + AfterEffectApply.Add(new EffectApplyHandler(HandleApply, 0, AuraType.Dummy, AuraEffectHandleModes.Real)); + } + + Team _teamInInstance; + } + + public override AuraScript GetAuraScript() + { + return new spell_igb_on_gunship_deck_AuraScript(); + } + } + + [Script] + class spell_igb_periodic_trigger_with_power_cost : SpellScriptLoader + { + public spell_igb_periodic_trigger_with_power_cost() : base("spell_igb_periodic_trigger_with_power_cost") { } + + class spell_igb_periodic_trigger_with_power_cost_AuraScript : AuraScript + { + void HandlePeriodicTick(AuraEffect aurEff) + { + PreventDefaultAction(); + GetTarget().CastSpell(GetTarget(), GetSpellInfo().GetEffect(0).TriggerSpell, (TriggerCastFlags.FullMask & ~TriggerCastFlags.IgnorePowerAndReagentCost)); + } + + public override void Register() + { + OnEffectPeriodic.Add(new EffectPeriodicHandler(HandlePeriodicTick, 0, AuraType.PeriodicTriggerSpell)); + } + } + + public override AuraScript GetAuraScript() + { + return new spell_igb_periodic_trigger_with_power_cost_AuraScript(); + } + } + + [Script] + class spell_igb_cannon_blast : SpellScriptLoader + { + public spell_igb_cannon_blast() : base("spell_igb_cannon_blast") { } + + class spell_igb_cannon_blast_SpellScript : SpellScript + { + public override bool Load() + { + return GetCaster().IsTypeId(TypeId.Unit); + } + + void CheckEnergy() + { + if (GetCaster().GetPower(PowerType.Energy) >= 100) + { + GetCaster().CastSpell(GetCaster(), GunshipSpells.Overheat, TriggerCastFlags.FullMask); + Vehicle vehicle = GetCaster().GetVehicleKit(); + if (vehicle) + { + Unit passenger = vehicle.GetPassenger(0); + if (passenger) + Global.CreatureTextMgr.SendChat(GetCaster().ToCreature(), GunshipTexts.SayOverheat, passenger); + } + } + } + + public override void Register() + { + AfterHit.Add(new HitHandler(CheckEnergy)); + } + } + + public override SpellScript GetSpellScript() + { + return new spell_igb_cannon_blast_SpellScript(); + } + } + + [Script] + class spell_igb_incinerating_blast : SpellScriptLoader + { + public spell_igb_incinerating_blast() : base("spell_igb_incinerating_blast") { } + + class spell_igb_incinerating_blast_SpellScript : SpellScript + { + void StoreEnergy() + { + _energyLeft = (uint)GetCaster().GetPower(PowerType.Energy) - 10; + } + + void RemoveEnergy() + { + GetCaster().SetPower(PowerType.Energy, 0); + } + + void CalculateDamage(uint effIndex) + { + SetEffectValue((int)(GetEffectValue() + _energyLeft * _energyLeft * 8)); + } + + public override void Register() + { + OnCast.Add(new CastHandler(StoreEnergy)); + AfterCast.Add(new CastHandler(RemoveEnergy)); + OnEffectLaunchTarget.Add(new EffectHandler(CalculateDamage, 1, SpellEffectName.SchoolDamage)); + } + + uint _energyLeft; + } + + public override SpellScript GetSpellScript() + { + return new spell_igb_incinerating_blast_SpellScript(); + } + } + + [Script] + class spell_igb_overheat : SpellScriptLoader + { + public spell_igb_overheat() : base("spell_igb_overheat") { } + + class spell_igb_overheat_AuraScript : AuraScript + { + public override bool Load() + { + if (GetAura().GetAuraType() != AuraObjectType.Unit) + return false; + return GetUnitOwner().IsVehicle(); + } + + void SendClientControl(bool value) + { + Vehicle vehicle = GetUnitOwner().GetVehicleKit(); + if (vehicle) + { + Unit passenger = vehicle.GetPassenger(0); + if (passenger) + { + Player player = passenger.ToPlayer(); + if (player) + { + ControlUpdate data = new ControlUpdate(); + data.Guid = GetUnitOwner().GetGUID(); + data.On = value; + player.SendPacket(data); + } + } + } + } + + void HandleApply(AuraEffect aurEff, AuraEffectHandleModes mode) + { + SendClientControl(false); + } + + void HandleRemove(AuraEffect aurEff, AuraEffectHandleModes mode) + { + SendClientControl(true); + } + + public override void Register() + { + AfterEffectApply.Add(new EffectApplyHandler(HandleApply, 0, AuraType.PeriodicTriggerSpell, AuraEffectHandleModes.Real)); + AfterEffectRemove.Add(new EffectApplyHandler(HandleRemove, 0, AuraType.PeriodicTriggerSpell, AuraEffectHandleModes.Real)); + } + } + + public override AuraScript GetAuraScript() + { + return new spell_igb_overheat_AuraScript(); + } + } + + [Script] + class spell_igb_below_zero : SpellScriptLoader + { + public spell_igb_below_zero() : base("spell_igb_below_zero") { } + + class spell_igb_below_zero_SpellScript : SpellScript + { + void RemovePassengers(SpellMissInfo missInfo) + { + if (missInfo != SpellMissInfo.None) + return; + + GetHitUnit().CastSpell(GetHitUnit(), GunshipSpells.EjectAllPassengersBelowZero, TriggerCastFlags.FullMask); + } + + public override void Register() + { + BeforeHit.Add(new BeforeHitHandler(RemovePassengers)); + } + } + + public override SpellScript GetSpellScript() + { + return new spell_igb_below_zero_SpellScript(); + } + } + + [Script] + class spell_igb_teleport_to_enemy_ship : SpellScriptLoader + { + public spell_igb_teleport_to_enemy_ship() : base("spell_igb_teleport_to_enemy_ship") { } + + class spell_igb_teleport_to_enemy_ship_SpellScript : SpellScript + { + void RelocateTransportOffset(uint effIndex) + { + Position dest = GetHitDest(); + Unit target = GetHitUnit(); + if (dest == null || !target || !target.GetTransport()) + return; + + float x, y, z, o; + dest.GetPosition(out x, out y, out z, out o); + target.GetTransport().CalculatePassengerOffset(ref x, ref y, ref z, ref o); + target.m_movementInfo.transport.pos.Relocate(x, y, z, o); + } + + public override void Register() + { + OnEffectHitTarget.Add(new EffectHandler(RelocateTransportOffset, 0, SpellEffectName.TeleportUnitsOld)); + } + } + + public override SpellScript GetSpellScript() + { + return new spell_igb_teleport_to_enemy_ship_SpellScript(); + } + } + + [Script] + class spell_igb_burning_pitch_selector : SpellScriptLoader + { + public spell_igb_burning_pitch_selector() : base("spell_igb_burning_pitch_selector") { } + + class spell_igb_burning_pitch_selector_SpellScript : SpellScript + { + void FilterTargets(List targets) + { + Team team = Team.Horde; + InstanceScript instance = GetCaster().GetInstanceScript(); + if (instance != null) + team = (Team)instance.GetData(DataTypes.TeamInInstance); + + targets.RemoveAll(target => + { + Transport transport = target.GetTransport(); + if (transport) + return transport.GetEntry() != (team == Team.Horde ? GameObjectIds.OrgrimsHammer_H : GameObjectIds.TheSkybreaker_A); + return true; + }); + + if (!targets.Empty()) + { + WorldObject target = targets.PickRandom(); + targets.Clear(); + targets.Add(target); + } + } + + void HandleDummy(uint effIndex) + { + PreventHitDefaultEffect(effIndex); + GetCaster().CastSpell(GetHitUnit(), (uint)GetEffectValue(), TriggerCastFlags.None); + } + + public override void Register() + { + OnObjectAreaTargetSelect.Add(new ObjectAreaTargetSelectHandler(FilterTargets, 0, Targets.UnitSrcAreaEntry)); + OnEffectHitTarget.Add(new EffectHandler(HandleDummy, 0, SpellEffectName.Dummy)); + } + } + + public override SpellScript GetSpellScript() + { + return new spell_igb_burning_pitch_selector_SpellScript(); + } + } + + [Script] + class spell_igb_burning_pitch : SpellScriptLoader + { + public spell_igb_burning_pitch() : base("spell_igb_burning_pitch") { } + + class spell_igb_burning_pitch_SpellScript : SpellScript + { + void HandleDummy(uint effIndex) + { + PreventHitDefaultEffect(effIndex); + GetCaster().CastCustomSpell((uint)GetEffectValue(), SpellValueMod.BasePoint0, 8000, null, TriggerCastFlags.FullMask); + GetHitUnit().CastSpell(GetHitUnit(), GunshipSpells.BurningPitch, TriggerCastFlags.FullMask); + } + + public override void Register() + { + OnEffectHitTarget.Add(new EffectHandler(HandleDummy, 0, SpellEffectName.Dummy)); + } + } + + public override SpellScript GetSpellScript() + { + return new spell_igb_burning_pitch_SpellScript(); + } + } + + [Script] + class spell_igb_rocket_artillery : SpellScriptLoader + { + public spell_igb_rocket_artillery() : base("spell_igb_rocket_artillery") { } + + class spell_igb_rocket_artillery_SpellScript : SpellScript + { + void SelectRandomTarget(List targets) + { + if (!targets.Empty()) + { + WorldObject target = targets.PickRandom(); + targets.Clear(); + targets.Add(target); + } + } + + void HandleScript(uint effIndex) + { + PreventHitDefaultEffect(effIndex); + GetCaster().CastSpell(GetHitUnit(), (uint)GetEffectValue(), TriggerCastFlags.None); + } + + public override void Register() + { + OnObjectAreaTargetSelect.Add(new ObjectAreaTargetSelectHandler(SelectRandomTarget, 0, Targets.UnitSrcAreaEntry)); + OnEffectHitTarget.Add(new EffectHandler(HandleScript, 0, SpellEffectName.ScriptEffect)); + } + } + + public override SpellScript GetSpellScript() + { + return new spell_igb_rocket_artillery_SpellScript(); + } + } + + [Script] + class spell_igb_rocket_artillery_explosion : SpellScriptLoader + { + public spell_igb_rocket_artillery_explosion() : base("spell_igb_rocket_artillery_explosion") { } + + class spell_igb_rocket_artillery_explosion_SpellScript : SpellScript + { + void DamageGunship(uint effIndex) + { + InstanceScript instance = GetCaster().GetInstanceScript(); + if (instance != null) + GetCaster().CastCustomSpell(instance.GetData(DataTypes.TeamInInstance) == (uint)Team.Horde ? GunshipSpells.BurningPitchDamageH : GunshipSpells.BurningPitchDamageA, SpellValueMod.BasePoint0, 5000, null, TriggerCastFlags.FullMask); + } + + public override void Register() + { + OnEffectHit.Add(new EffectHandler(DamageGunship, 0, SpellEffectName.TriggerMissile)); + } + } + + public override SpellScript GetSpellScript() + { + return new spell_igb_rocket_artillery_explosion_SpellScript(); + } + } + + [Script] + class spell_igb_gunship_fall_teleport : SpellScriptLoader + { + public spell_igb_gunship_fall_teleport() : base("spell_igb_gunship_fall_teleport") { } + + class spell_igb_gunship_fall_teleport_SpellScript : SpellScript + { + public override bool Load() + { + return GetCaster().GetInstanceScript() != null; + } + + void SelectTransport(ref WorldObject target) + { + InstanceScript instance = target.GetInstanceScript(); + if (instance != null) + target = Global.ObjAccessor.FindTransport(instance.GetGuidData(Bosses.GunshipBattle)); + } + + void RelocateDest(uint effIndex) + { + if (GetCaster().GetInstanceScript().GetData(DataTypes.TeamInInstance) == (uint)Team.Horde) + GetHitDest().RelocateOffset(new Position(0.0f, 0.0f, 36.0f, 0.0f)); + else + GetHitDest().RelocateOffset(new Position(0.0f, 0.0f, 21.0f, 0.0f)); + } + + public override void Register() + { + OnObjectTargetSelect.Add(new ObjectTargetSelectHandler(SelectTransport, 0, Targets.DestNearbyEntry)); + OnEffectLaunch.Add(new EffectHandler(RelocateDest, 0, SpellEffectName.TeleportUnitsOld)); + } + } + + public override SpellScript GetSpellScript() + { + return new spell_igb_gunship_fall_teleport_SpellScript(); + } + } + + [Script] + class spell_igb_check_for_players : SpellScriptLoader + { + public spell_igb_check_for_players() : base("spell_igb_check_for_players") { } + + class spell_igb_check_for_players_SpellScript : SpellScript + { + public override bool Load() + { + _playerCount = 0; + return GetCaster().IsTypeId(TypeId.Unit); + } + + void CountTargets(List targets) + { + _playerCount = (uint)targets.Count; + } + + void TriggerWipe() + { + if (_playerCount == 0) + GetCaster().ToCreature().GetAI().JustDied(null); + } + + void TeleportPlayer(uint effIndex) + { + if (GetHitUnit().GetPositionZ() < GetCaster().GetPositionZ() - 10.0f) + GetHitUnit().CastSpell(GetHitUnit(), GunshipSpells.GunshipFallTeleport, TriggerCastFlags.FullMask); + } + + public override void Register() + { + OnObjectAreaTargetSelect.Add(new ObjectAreaTargetSelectHandler(CountTargets, 0, Targets.UnitSrcAreaEntry)); + AfterCast.Add(new CastHandler(TriggerWipe)); + OnEffectHitTarget.Add(new EffectHandler(TeleportPlayer, 0, SpellEffectName.Dummy)); + } + + uint _playerCount; + } + + public override SpellScript GetSpellScript() + { + return new spell_igb_check_for_players_SpellScript(); + } + } + + [Script] + class spell_igb_teleport_players_on_victory : SpellScriptLoader + { + public spell_igb_teleport_players_on_victory() : base("spell_igb_teleport_players_on_victory") { } + + class spell_igb_teleport_players_on_victory_SpellScript : SpellScript + { + public override bool Load() + { + return GetCaster().GetInstanceScript() != null; + } + + void FilterTargets(List targets) + { + InstanceScript instance = GetCaster().GetInstanceScript(); + targets.RemoveAll(target => target.GetTransGUID() != instance.GetGuidData(DataTypes.EnemyGunship)); + } + + public override void Register() + { + OnObjectAreaTargetSelect.Add(new ObjectAreaTargetSelectHandler(FilterTargets, 1, Targets.UnitDestAreaEntry)); + } + } + + public override SpellScript GetSpellScript() + { + return new spell_igb_teleport_players_on_victory_SpellScript(); + } + } + + [Script] // 71201 - Battle Experience - proc should never happen, handled in script + class spell_igb_battle_experience_check : SpellScriptLoader + { + public spell_igb_battle_experience_check() : base("spell_igb_battle_experience_check") { } + + class spell_igb_battle_experience_check_AuraScript : AuraScript + { + + bool CheckProc(ProcEventInfo eventInfo) + { + return false; + } + + public override void Register() + { + DoCheckProc.Add(new CheckProcHandler(CheckProc)); + } + } + + public override AuraScript GetAuraScript() + { + return new spell_igb_battle_experience_check_AuraScript(); + } + } + + [Script] + class achievement_im_on_a_boat : AchievementCriteriaScript + { + public achievement_im_on_a_boat() : base("achievement_im_on_a_boat") { } + + public override bool OnCheck(Player source, Unit target) + { + return target.GetAI() != null && target.GetAI().GetData(EncounterActions.ShipVisits) <= 2; + } + } +} diff --git a/Scripts/Northrend/IcecrownCitadel/IcecrownCitadel.cs b/Scripts/Northrend/IcecrownCitadel/IcecrownCitadel.cs new file mode 100644 index 000000000..ca0b41d59 --- /dev/null +++ b/Scripts/Northrend/IcecrownCitadel/IcecrownCitadel.cs @@ -0,0 +1,1948 @@ +/* + * Copyright (C) 2012-2017 CypherCore + * + * 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, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +using Framework.Constants; +using Framework.Dynamic; +using Game.AI; +using Game.DataStorage; +using Game.Entities; +using Game.Maps; +using Game.Scripting; +using Game.Spells; +using System.Collections.Generic; +using System.Linq; + +namespace Scripts.Northrend.IcecrownCitadel +{ + class FrostwingVrykulSearcher : ICheck where T : Unit + { + public FrostwingVrykulSearcher(Creature source, float range) + { + _source = source; + _range = range; + } + + public bool Invoke(T u) + { + if (!u.IsAlive()) + return false; + + switch (u.GetEntry()) + { + case CreatureIds.YmirjarBattleMaiden: + case CreatureIds.YmirjarDeathbringer: + case CreatureIds.YmirjarFrostbinder: + case CreatureIds.YmirjarHuntress: + case CreatureIds.YmirjarWarlord: + break; + default: + return false; + } + + if (!u.IsWithinDist(_source, _range, false)) + return false; + + return true; + } + + Creature _source; + float _range; + } + + class FrostwingGauntletRespawner : IDoWork + { + public void Invoke(Creature creature) + { + switch (creature.GetOriginalEntry()) + { + case CreatureIds.YmirjarBattleMaiden: + case CreatureIds.YmirjarDeathbringer: + case CreatureIds.YmirjarFrostbinder: + case CreatureIds.YmirjarHuntress: + case CreatureIds.YmirjarWarlord: + break; + case CreatureIds.CrokScourgebane: + case CreatureIds.CaptainArnath: + case CreatureIds.CaptainBrandon: + case CreatureIds.CaptainGrondel: + case CreatureIds.CaptainRupert: + creature.GetAI().DoAction(Actions.ResetEvent); + break; + case CreatureIds.SisterSvalna: + creature.GetAI().DoAction(Actions.ResetEvent); + // return, this creature is never dead if event is reset + return; + default: + return; + } + + uint corpseDelay = creature.GetCorpseDelay(); + uint respawnDelay = creature.GetRespawnDelay(); + creature.SetCorpseDelay(1); + creature.SetRespawnDelay(2); + + CreatureData data = creature.GetCreatureData(); + if (data != null) + creature.SetPosition(data.posX, data.posY, data.posZ, data.orientation); + creature.DespawnOrUnsummon(); + + creature.SetCorpseDelay(corpseDelay); + creature.SetRespawnDelay(respawnDelay); + } + } + + class CaptainSurviveTalk : BasicEvent + { + public CaptainSurviveTalk(Creature owner) + { + _owner = owner; + } + + public override bool Execute(ulong currTime, uint diff) + { + _owner.GetAI().Talk(Texts.SayCaptainSurviveTalk); + return true; + } + + Creature _owner; + } + + // at Light's Hammer + [Script] + class npc_highlord_tirion_fordring_lh : CreatureScript + { + public npc_highlord_tirion_fordring_lh() : base("npc_highlord_tirion_fordring_lh") { } + + class npc_highlord_tirion_fordringAI : ScriptedAI + { + public npc_highlord_tirion_fordringAI(Creature creature) + : base(creature) + { + _instance = creature.GetInstanceScript(); + } + + public override void Reset() + { + _events.Reset(); + _theLichKing.Clear(); + _bolvarFordragon.Clear(); + _factionNPC.Clear(); + _damnedKills = 0; + } + + // IMPORTANT NOTE: This is triggered from per-GUID scripts + // of The Damned SAI + public override void SetData(uint type, uint data) + { + if (type == 1 && data == 1) + { + if (++_damnedKills == 2) + { + Creature theLichKing = me.FindNearestCreature(CreatureIds.TheLichKingLh, 150.0f); + if (theLichKing) + { + Creature bolvarFordragon = me.FindNearestCreature(CreatureIds.HighlordBolvarFordragonLh, 150.0f); + if (bolvarFordragon) + { + Creature factionNPC = me.FindNearestCreature(_instance.GetData(DataTypes.TeamInInstance) == (uint)Team.Horde ? CreatureIds.SeHighOverlordSaurfang : CreatureIds.SeMuradinBronzebeard, 50.0f); + if (factionNPC) + { + me.setActive(true); + _theLichKing = theLichKing.GetGUID(); + theLichKing.setActive(true); + _bolvarFordragon = bolvarFordragon.GetGUID(); + bolvarFordragon.setActive(true); + _factionNPC = factionNPC.GetGUID(); + factionNPC.setActive(true); + } + } + } + + if (_bolvarFordragon.IsEmpty() || _theLichKing.IsEmpty() || _factionNPC.IsEmpty()) + return; + + Talk(Texts.SayTirionIntro1); + _events.ScheduleEvent(EventTypes.TirionIntro2, 4000); + _events.ScheduleEvent(EventTypes.TirionIntro3, 14000); + _events.ScheduleEvent(EventTypes.TirionIntro4, 18000); + _events.ScheduleEvent(EventTypes.TirionIntro5, 31000); + _events.ScheduleEvent(EventTypes.LkIntro1, 35000); + _events.ScheduleEvent(EventTypes.TirionIntro6, 51000); + _events.ScheduleEvent(EventTypes.LkIntro2, 58000); + _events.ScheduleEvent(EventTypes.LkIntro3, 74000); + _events.ScheduleEvent(EventTypes.LkIntro4, 86000); + _events.ScheduleEvent(EventTypes.BolvarIntro1, 100000); + _events.ScheduleEvent(EventTypes.LkIntro5, 108000); + + if (_instance.GetData(DataTypes.TeamInInstance) == (uint)Team.Horde) + { + _events.ScheduleEvent(EventTypes.SaurfangIntro1, 120000); + _events.ScheduleEvent(EventTypes.TirionIntroH7, 129000); + _events.ScheduleEvent(EventTypes.SaurfangIntro2, 139000); + _events.ScheduleEvent(EventTypes.SaurfangIntro3, 150000); + _events.ScheduleEvent(EventTypes.SaurfangIntro4, 162000); + _events.ScheduleEvent(EventTypes.SaurfangRun, 170000); + } + else + { + _events.ScheduleEvent(EventTypes.MuradinIntro1, 120000); + _events.ScheduleEvent(EventTypes.MuradinIntro2, 124000); + _events.ScheduleEvent(EventTypes.MuradinIntro3, 127000); + _events.ScheduleEvent(EventTypes.TirionIntroA7, 136000); + _events.ScheduleEvent(EventTypes.MuradinIntro4, 144000); + _events.ScheduleEvent(EventTypes.MuradinIntro5, 151000); + _events.ScheduleEvent(EventTypes.MuradinRun, 157000); + } + } + } + } + + public override void UpdateAI(uint diff) + { + if (_damnedKills != 2) + return; + + _events.Update(diff); + + _events.ExecuteEvents(eventId => + { + Creature temp; + + switch (eventId) + { + case EventTypes.TirionIntro2: + me.HandleEmoteCommand(Emote.OneshotExclamation); + break; + case EventTypes.TirionIntro3: + Talk(Texts.SayTirionIntro2); + break; + case EventTypes.TirionIntro4: + me.HandleEmoteCommand(Emote.OneshotExclamation); + break; + case EventTypes.TirionIntro5: + Talk(Texts.SayTirionIntro3); + break; + case EventTypes.LkIntro1: + me.HandleEmoteCommand(Emote.StateDanceNosheathe); + temp = ObjectAccessor.GetCreature(me, _theLichKing); + if (temp) + temp.GetAI().Talk(Texts.SayLkIntro1); + break; + case EventTypes.TirionIntro6: + Talk(Texts.SayTirionIntro4); + break; + case EventTypes.LkIntro2: + temp = ObjectAccessor.GetCreature(me, _theLichKing); + if (temp) + temp.GetAI().Talk(Texts.SayLkIntro2); + break; + case EventTypes.LkIntro3: + temp = ObjectAccessor.GetCreature(me, _theLichKing); + if (temp) + temp.GetAI().Talk(Texts.SayLkIntro3); + break; + case EventTypes.LkIntro4: + temp = ObjectAccessor.GetCreature(me, _theLichKing); + if (temp) + temp.GetAI().Talk(Texts.SayLkIntro4); + break; + case EventTypes.BolvarIntro1: + temp = ObjectAccessor.GetCreature(me, _bolvarFordragon); + if (temp) + { + temp.GetAI().Talk(Texts.SayBolvarIntro1); + temp.setActive(false); + } + break; + case EventTypes.LkIntro5: + temp = ObjectAccessor.GetCreature(me, _theLichKing); + if (temp) + { + temp.GetAI().Talk(Texts.SayLkIntro5); + temp.setActive(false); + } + break; + case EventTypes.SaurfangIntro1: + temp = ObjectAccessor.GetCreature(me, _factionNPC); + if (temp) + temp.GetAI().Talk(Texts.SaySaurfangIntro1); + break; + case EventTypes.TirionIntroH7: + Talk(Texts.SayTirionIntroH5); + break; + case EventTypes.SaurfangIntro2: + temp = ObjectAccessor.GetCreature(me, _factionNPC); + if (temp) + temp.GetAI().Talk(Texts.SaySaurfangIntro2); + break; + case EventTypes.SaurfangIntro3: + temp = ObjectAccessor.GetCreature(me, _factionNPC); + if (temp) + temp.GetAI().Talk(Texts.SaySaurfangIntro3); + break; + case EventTypes.SaurfangIntro4: + temp = ObjectAccessor.GetCreature(me, _factionNPC); + if (temp) + temp.GetAI().Talk(Texts.SaySaurfangIntro4); + break; + case EventTypes.MuradinRun: + case EventTypes.SaurfangRun: + Creature factionNPC = ObjectAccessor.GetCreature(me, _factionNPC); + if (factionNPC) + factionNPC.GetMotionMaster().MovePath((uint)(factionNPC.GetSpawnId() * 10), false); + me.setActive(false); + _damnedKills = 3; + break; + case EventTypes.MuradinIntro1: + temp = ObjectAccessor.GetCreature(me, _factionNPC); + if (temp) + temp.GetAI().Talk(Texts.SayMuradinIntro1); + break; + case EventTypes.MuradinIntro2: + temp = ObjectAccessor.GetCreature(me, _factionNPC); + if (temp) + temp.HandleEmoteCommand(Emote.OneshotTalk); + break; + case EventTypes.MuradinIntro3: + temp = ObjectAccessor.GetCreature(me, _factionNPC); + if (temp) + temp.HandleEmoteCommand(Emote.OneshotExclamation); + break; + case EventTypes.TirionIntroA7: + Talk(Texts.SayTirionIntroA5); + break; + case EventTypes.MuradinIntro4: + temp = ObjectAccessor.GetCreature(me, _factionNPC); + if (temp) + temp.GetAI().Talk(Texts.SayMuradinIntro2); + break; + case EventTypes.MuradinIntro5: + temp = ObjectAccessor.GetCreature(me, _factionNPC); + if (temp) + temp.GetAI().Talk(Texts.SayMuradinIntro3); + break; + default: + break; + } + }); + } + + InstanceScript _instance; + ObjectGuid _theLichKing; + ObjectGuid _bolvarFordragon; + ObjectGuid _factionNPC; + ushort _damnedKills; + } + + public override CreatureAI GetAI(Creature creature) + { + return InstanceIcecrownCitadel.GetInstanceAI(creature); + } + } + + [Script] + class npc_rotting_frost_giant : CreatureScript + { + public npc_rotting_frost_giant() : base("npc_rotting_frost_giant") { } + + class npc_rotting_frost_giantAI : ScriptedAI + { + public npc_rotting_frost_giantAI(Creature creature) + : base(creature) { } + + public override void Reset() + { + _events.Reset(); + _events.ScheduleEvent(EventTypes.DeathPlague, 15000); + _events.ScheduleEvent(EventTypes.Stomp, RandomHelper.URand(5000, 8000)); + _events.ScheduleEvent(EventTypes.ArcticBreath, RandomHelper.URand(10000, 15000)); + } + + public override void JustDied(Unit killer) + { + _events.Reset(); + } + + public override void UpdateAI(uint diff) + { + if (!UpdateVictim()) + return; + + _events.Update(diff); + + if (me.HasUnitState(UnitState.Casting)) + return; + + _events.ExecuteEvents(eventId => + { + switch (eventId) + { + case EventTypes.DeathPlague: + Unit target = SelectTarget(SelectAggroTarget.Random, 1, 0.0f, true); + if (target) + { + Talk(Texts.EmoteDeathPlagueWarning, target); + DoCast(target, InstanceSpells.DeathPlague); + } + _events.ScheduleEvent(EventTypes.DeathPlague, 15000); + break; + case EventTypes.Stomp: + DoCastVictim(InstanceSpells.Stomp); + _events.ScheduleEvent(EventTypes.Stomp, RandomHelper.URand(15000, 18000)); + break; + case EventTypes.ArcticBreath: + DoCastVictim(InstanceSpells.ArcticBreath); + _events.ScheduleEvent(EventTypes.ArcticBreath, RandomHelper.URand(26000, 33000)); + break; + default: + break; + } + }); + + DoMeleeAttackIfReady(); + } + } + + public override CreatureAI GetAI(Creature creature) + { + return InstanceIcecrownCitadel.GetInstanceAI(creature); + } + } + + [Script] + class npc_frost_freeze_trap : CreatureScript + { + public npc_frost_freeze_trap() : base("npc_frost_freeze_trap") { } + + class npc_frost_freeze_trapAI : ScriptedAI + { + public npc_frost_freeze_trapAI(Creature creature) + : base(creature) + { + SetCombatMovement(false); + } + + public override void DoAction(int action) + { + switch (action) + { + case 1000: + case 11000: + _events.ScheduleEvent(EventTypes.ActivateTrap, (uint)action); + break; + default: + break; + } + } + + public override void UpdateAI(uint diff) + { + _events.Update(diff); + + if (_events.ExecuteEvent() == EventTypes.ActivateTrap) + { + DoCast(me, InstanceSpells.ColdflameJets); + _events.ScheduleEvent(EventTypes.ActivateTrap, 22000); + } + } + } + + public override CreatureAI GetAI(Creature creature) + { + return InstanceIcecrownCitadel.GetInstanceAI(creature); + } + } + + [Script] + class npc_alchemist_adrianna : CreatureScript + { + public npc_alchemist_adrianna() : base("npc_alchemist_adrianna") { } + + public override bool OnGossipHello(Player player, Creature creature) + { + if (!creature.FindCurrentSpellBySpellId(InstanceSpells.HarvestBlightSpecimen) && !creature.FindCurrentSpellBySpellId(InstanceSpells.HarvestBlightSpecimen25)) + if (player.HasAura(InstanceSpells.OrangeBlightResidue) && player.HasAura(InstanceSpells.GreenBlightResidue)) + creature.CastSpell(creature, InstanceSpells.HarvestBlightSpecimen, false); + return false; + } + } + + [Script] + class boss_sister_svalna : CreatureScript + { + public boss_sister_svalna() : base("boss_sister_svalna") { } + + class boss_sister_svalnaAI : BossAI + { + public boss_sister_svalnaAI(Creature creature) + : base(creature, Bosses.SisterSvalna) + { + _isEventInProgress = false; + + } + + public override void InitializeAI() + { + if (!me.IsDead()) + Reset(); + + me.SetReactState(ReactStates.Passive); + } + + public override void Reset() + { + _Reset(); + me.SetReactState(ReactStates.Defensive); + _isEventInProgress = false; + } + + public override void JustDied(Unit killer) + { + _JustDied(); + Talk(Texts.SaySvalnaDeath); + + ulong delay = 1; + for (uint i = 0; i < 4; ++i) + { + Creature crusader = ObjectAccessor.GetCreature(me, instance.GetGuidData(DataTypes.CaptainArnath + i)); + if (crusader) + { + if (crusader.IsAlive() && crusader.GetEntry() == crusader.GetCreatureData().id) + { + crusader.m_Events.AddEvent(new CaptainSurviveTalk(crusader), crusader.m_Events.CalculateTime(delay)); + delay += 6000; + } + } + } + } + + public override void EnterCombat(Unit attacker) + { + _EnterCombat(); + Creature crok = ObjectAccessor.GetCreature(me, instance.GetGuidData(DataTypes.CrokScourgebane)); + if (crok) + crok.GetAI().Talk(Texts.SayCrokCombatSvalna); + _events.ScheduleEvent(EventTypes.SvalnaCombat, 9000); + _events.ScheduleEvent(EventTypes.ImpalingSpear, RandomHelper.URand(40000, 50000)); + _events.ScheduleEvent(EventTypes.AetherShield, RandomHelper.URand(100000, 110000)); + } + + public override void KilledUnit(Unit victim) + { + switch (victim.GetTypeId()) + { + case TypeId.Player: + Talk(Texts.SaySvalnaKill); + break; + case TypeId.Unit: + switch (victim.GetEntry()) + { + case CreatureIds.CaptainArnath: + case CreatureIds.CaptainBrandon: + case CreatureIds.CaptainGrondel: + case CreatureIds.CaptainRupert: + Talk(Texts.SaySvalnaKillCaptain); + break; + default: + break; + } + break; + default: + break; + } + } + + public override void JustReachedHome() + { + _JustReachedHome(); + me.SetReactState(ReactStates.Passive); + me.SetDisableGravity(false); + me.SetHover(false); + } + + public override void DoAction(int action) + { + switch (action) + { + case Actions.KillCaptain: + me.CastCustomSpell(InstanceSpells.CaressOfDeath, SpellValueMod.MaxTargets, 1, me, true); + break; + case Actions.StartGauntlet: + me.setActive(true); + _isEventInProgress = true; + me.SetFlag(UnitFields.Flags, UnitFlags.ImmuneToPc | UnitFlags.ImmuneToNpc); + _events.ScheduleEvent(EventTypes.SvalnaStart, 25000); + break; + case Actions.ResurrectCaptains: + _events.ScheduleEvent(EventTypes.SvalnaResurrect, 7000); + break; + case Actions.CaptainDies: + Talk(Texts.SaySvalnaCaptainDeath); + break; + case Actions.ResetEvent: + me.setActive(false); + Reset(); + break; + default: + break; + } + } + + public override void SpellHit(Unit caster, SpellInfo spell) + { + if (spell.Id == InstanceSpells.HurlSpear && me.HasAura(InstanceSpells.AetherShield)) + { + me.RemoveAurasDueToSpell(InstanceSpells.AetherShield); + Talk(Texts.EmoteSvalnaBrokenShield, caster); + } + } + + public override void MovementInform(MovementGeneratorType type, uint id) + { + if (type != MovementGeneratorType.Effect || id != 1) + return; + + _isEventInProgress = false; + me.setActive(false); + me.RemoveFlag(UnitFields.Flags, UnitFlags.ImmuneToPc | UnitFlags.ImmuneToNpc); + me.SetDisableGravity(false); + me.SetHover(false); + } + + public override void SpellHitTarget(Unit target, SpellInfo spell) + { + switch (spell.Id) + { + case InstanceSpells.ImpalingSpearKill: + me.Kill(target); + break; + case InstanceSpells.ImpalingSpear: + TempSummon summon = target.SummonCreature(CreatureIds.ImpalingSpear, target); + if (summon) + { + Talk(Texts.EmoteSvalnaImpale, target); + summon.CastCustomSpell(SharedConst.VehicleSpellRideHardcoded, SpellValueMod.BasePoint0, 1, target, false); + summon.SetFlag(UnitFields.Flags2, UnitFlags2.Unk1 | UnitFlags2.AllowEnemyInteract); + } + break; + default: + break; + } + } + + public override void UpdateAI(uint diff) + { + if (!UpdateVictim() && !_isEventInProgress) + return; + + _events.Update(diff); + + if (me.HasUnitState(UnitState.Casting)) + return; + + _events.ExecuteEvents(eventId => + { + switch (eventId) + { + case EventTypes.SvalnaStart: + Talk(Texts.SaySvalnaEventStart); + break; + case EventTypes.SvalnaResurrect: + Talk(Texts.SaySvalnaResurrectCaptains); + me.CastSpell(me, InstanceSpells.ReviveChampion, false); + break; + case EventTypes.SvalnaCombat: + me.SetReactState(ReactStates.Defensive); + Talk(Texts.SaySvalnaAggro); + break; + case EventTypes.ImpalingSpear: + Unit target = SelectTarget(SelectAggroTarget.Random, 1, 0.0f, true, -(int)InstanceSpells.ImpalingSpear); + if (target) + { + DoCast(me, InstanceSpells.AetherShield); + DoCast(target, InstanceSpells.ImpalingSpear); + } + _events.ScheduleEvent(EventTypes.ImpalingSpear, RandomHelper.URand(20000, 25000)); + break; + default: + break; + } + }); + + DoMeleeAttackIfReady(); + } + + bool _isEventInProgress; + } + + public override CreatureAI GetAI(Creature creature) + { + return InstanceIcecrownCitadel.GetInstanceAI(creature); + } + } + + [Script] + class npc_crok_scourgebane : CreatureScript + { + public npc_crok_scourgebane() : base("npc_crok_scourgebane") { } + + class npc_crok_scourgebaneAI : npc_escortAI + { + public npc_crok_scourgebaneAI(Creature creature) : base(creature) + { + _instance = creature.GetInstanceScript(); + _respawnTime = creature.GetRespawnDelay(); + _corpseDelay = creature.GetCorpseDelay(); + + SetDespawnAtEnd(false); + SetDespawnAtFar(false); + _isEventActive = false; + _isEventDone = _instance.GetBossState(Bosses.SisterSvalna) == EncounterState.Done; + _didUnderTenPercentText = false; + } + + public override void Reset() + { + _events.Reset(); + _events.ScheduleEvent(EventTypes.ScourgeStrike, RandomHelper.URand(7500, 12500)); + _events.ScheduleEvent(EventTypes.DeathStrike, RandomHelper.URand(25000, 30000)); + me.SetReactState(ReactStates.Defensive); + _didUnderTenPercentText = false; + _wipeCheckTimer = 1000; + } + + public override void DoAction(int action) + { + if (action == Actions.StartGauntlet) + { + if (_isEventDone || !me.IsAlive()) + return; + + _isEventActive = true; + _isEventDone = true; + // Load Grid with Sister Svalna + me.GetMap().LoadGrid(4356.71f, 2484.33f); + Creature svalna = ObjectAccessor.GetCreature(me, _instance.GetGuidData(Bosses.SisterSvalna)); + if (svalna) + svalna.GetAI().DoAction(Actions.StartGauntlet); + Talk(Texts.SayCrokIntro1); + _events.ScheduleEvent(EventTypes.ArnathIntro2, 7000); + _events.ScheduleEvent(EventTypes.CrokIntro3, 14000); + _events.ScheduleEvent(EventTypes.StartPathing, 37000); + me.setActive(true); + for (uint i = 0; i < 4; ++i) + { + Creature crusader = ObjectAccessor.GetCreature(me, _instance.GetGuidData(DataTypes.CaptainArnath + i)); + if (crusader) + crusader.GetAI().DoAction(Actions.StartGauntlet); + } + } + else if (action == Actions.ResetEvent) + { + _isEventActive = false; + _isEventDone = _instance.GetBossState(Bosses.SisterSvalna) == EncounterState.Done; + me.setActive(false); + _aliveTrash.Clear(); + _currentWPid = 0; + } + } + + public override void SetGUID(ObjectGuid guid, int type = 0) + { + if (type == Actions.VrykulDeath) + { + _aliveTrash.Remove(guid); + if (_aliveTrash.Empty()) + { + SetEscortPaused(false); + if (_currentWPid == 4 && _isEventActive) + { + _isEventActive = false; + me.setActive(false); + Talk(Texts.SayCrokFinalWp); + Creature svalna = ObjectAccessor.GetCreature(me, _instance.GetGuidData(Bosses.SisterSvalna)); + if (svalna) + svalna.GetAI().DoAction(Actions.ResurrectCaptains); + } + } + } + } + + public override void WaypointReached(uint waypointId) + { + switch (waypointId) + { + // pause pathing until trash pack is cleared + case 0: + Talk(Texts.SayCrokCombatWp0); + if (!_aliveTrash.Empty()) + SetEscortPaused(true); + break; + case 1: + Talk(Texts.SayCrokCombatWp1); + if (!_aliveTrash.Empty()) + SetEscortPaused(true); + break; + case 4: + if (_aliveTrash.Empty() && _isEventActive) + { + _isEventActive = false; + me.setActive(false); + Talk(Texts.SayCrokFinalWp); + Creature svalna = ObjectAccessor.GetCreature(me, _instance.GetGuidData(Bosses.SisterSvalna)); + if (svalna) + svalna.GetAI().DoAction(Actions.ResurrectCaptains); + } + break; + default: + break; + } + } + + public override void WaypointStart(uint waypointId) + { + _currentWPid = waypointId; + switch (waypointId) + { + case 0: + case 1: + case 4: + { + // get spawns by home position + float minY = 2600.0f; + float maxY = 2650.0f; + if (waypointId == 1) + { + minY -= 50.0f; + maxY -= 50.0f; + // at waypoints 1 and 2 she kills one captain + Creature svalna = ObjectAccessor.GetCreature(me, _instance.GetGuidData(Bosses.SisterSvalna)); + if (svalna) + svalna.GetAI().DoAction(Actions.KillCaptain); + } + else if (waypointId == 4) + { + minY -= 100.0f; + maxY -= 100.0f; + } + + // get all nearby vrykul + List temp = new List(); + var check = new FrostwingVrykulSearcher(me, 80.0f); + var searcher = new CreatureListSearcher(me, temp, check); + Cell.VisitGridObjects(me, searcher, 80.0f); + + _aliveTrash.Clear(); + foreach (var creature in temp) + if (creature.GetHomePosition().GetPositionY() < maxY && creature.GetHomePosition().GetPositionY() > minY) + _aliveTrash.Add(creature.GetGUID()); + break; + } + // at waypoints 1 and 2 she kills one captain + case 2: + Creature svalna1 = ObjectAccessor.GetCreature(me, _instance.GetGuidData(Bosses.SisterSvalna)); + if (svalna1) + svalna1.GetAI().DoAction(Actions.KillCaptain); + break; + default: + break; + } + } + + public override void DamageTaken(Unit attacker, ref uint damage) + { + // check wipe + if (_wipeCheckTimer == 0) + { + _wipeCheckTimer = 1000; + var check = new AnyPlayerInObjectRangeCheck(me, 60.0f); + var searcher = new PlayerSearcher(me, check); + Cell.VisitWorldObjects(me, searcher, 60.0f); + // wipe + if (!searcher.GetTarget()) + { + damage *= 100; + if (damage >= me.GetHealth()) + { + FrostwingGauntletRespawner respawner = new FrostwingGauntletRespawner(); + var worker = new CreatureWorker(me, respawner); + Cell.VisitGridObjects(me, worker, 333.0f); + Talk(Texts.SayCrokDeath); + } + return; + } + } + + if (HealthBelowPct(10)) + { + if (!_didUnderTenPercentText) + { + _didUnderTenPercentText = true; + if (_isEventActive) + Talk(Texts.SayCrokWeakeningGauntlet); + else + Talk(Texts.SayCrokWeakeningSvalna); + } + + damage = 0; + DoCast(me, InstanceSpells.IceboundArmor); + _events.ScheduleEvent(EventTypes.HealthCheck, 1000); + } + } + + void UpdateEscortAI(uint diff) + { + if (_wipeCheckTimer <= diff) + _wipeCheckTimer = 0; + else + _wipeCheckTimer -= diff; + + if (!UpdateVictim() && !_isEventActive) + return; + + _events.Update(diff); + + if (me.HasUnitState(UnitState.Casting)) + return; + + _events.ExecuteEvents(eventId => + { + switch (eventId) + { + case EventTypes.ArnathIntro2: + Creature arnath = ObjectAccessor.GetCreature(me, _instance.GetGuidData(DataTypes.CaptainArnath)); + if (arnath) + arnath.GetAI().Talk(Texts.SayArnathIntro2); + break; + case EventTypes.CrokIntro3: + Talk(Texts.SayCrokIntro3); + break; + case EventTypes.StartPathing: + Start(true, true); + break; + case EventTypes.ScourgeStrike: + DoCastVictim(InstanceSpells.ScourgeStrike); + _events.ScheduleEvent(EventTypes.ScourgeStrike, RandomHelper.URand(10000, 14000)); + break; + case EventTypes.DeathStrike: + if (HealthBelowPct(20)) + DoCastVictim(InstanceSpells.DeathStrike); + _events.ScheduleEvent(EventTypes.DeathStrike, RandomHelper.URand(5000, 10000)); + break; + case EventTypes.HealthCheck: + if (HealthAbovePct(15)) + { + me.RemoveAurasDueToSpell(InstanceSpells.IceboundArmor); + _didUnderTenPercentText = false; + } + else + { + // looks totally hacky to me + me.ModifyHealth((long)me.CountPctFromMaxHealth(5)); + _events.ScheduleEvent(EventTypes.HealthCheck, 1000); + } + break; + default: + break; + } + }); + + DoMeleeAttackIfReady(); + } + + public override bool CanAIAttack(Unit target) + { + // do not see targets inside Frostwing Halls when we are not there + return (me.GetPositionY() > 2660.0f) == (target.GetPositionY() > 2660.0f); + } + + List _aliveTrash = new List(); + InstanceScript _instance; + uint _currentWPid; + uint _wipeCheckTimer; + uint _respawnTime; + uint _corpseDelay; + bool _isEventActive; + bool _isEventDone; + bool _didUnderTenPercentText; + } + + public override CreatureAI GetAI(Creature creature) + { + return InstanceIcecrownCitadel.GetInstanceAI(creature); + } + } + + class npc_argent_captainAI : ScriptedAI + { + public npc_argent_captainAI(Creature creature) + : base(creature) + { + instance = creature.GetInstanceScript(); + _firstDeath = true; + FollowAngle = SharedConst.PetFollowAngle; + FollowDist = SharedConst.PetFollowDist; + IsUndead = false; + } + + public override void JustDied(Unit killer) + { + if (_firstDeath) + { + _firstDeath = false; + Talk(Texts.SayCaptainDeath); + } + else + Talk(Texts.SayCaptainSecondDeath); + } + + public override void KilledUnit(Unit victim) + { + if (victim.IsTypeId(TypeId.Player)) + Talk(Texts.SayCaptainKill); + } + + public override void DoAction(int action) + { + if (action == Actions.StartGauntlet) + { + Creature crok = ObjectAccessor.GetCreature(me, instance.GetGuidData(DataTypes.CrokScourgebane)); + if (crok) + { + me.SetReactState(ReactStates.Defensive); + FollowAngle = me.GetAngle(crok) + me.GetOrientation(); + FollowDist = me.GetDistance2d(crok); + me.GetMotionMaster().MoveFollow(crok, FollowDist, FollowAngle, MovementSlot.Idle); + } + + me.setActive(true); + } + else if (action == Actions.ResetEvent) + { + _firstDeath = true; + } + } + + public override void EnterCombat(Unit target) + { + me.SetHomePosition(me); + if (IsUndead) + DoZoneInCombat(); + } + + public override bool CanAIAttack(Unit target) + { + // do not see targets inside Frostwing Halls when we are not there + return (me.GetPositionY() > 2660.0f) == (target.GetPositionY() > 2660.0f); + } + + public override void EnterEvadeMode(EvadeReason why) + { + // not yet following + if (me.GetMotionMaster().GetMotionSlotType((int)MovementSlot.Idle) != MovementGeneratorType.Chase || IsUndead) + { + base.EnterEvadeMode(why); + return; + } + + if (!_EnterEvadeMode(why)) + return; + + if (!me.GetVehicle()) + { + me.GetMotionMaster().Clear(false); + Creature crok = ObjectAccessor.GetCreature(me, instance.GetGuidData(DataTypes.CrokScourgebane)); + if (crok) + me.GetMotionMaster().MoveFollow(crok, FollowDist, FollowAngle, MovementSlot.Idle); + } + + Reset(); + } + + public override void SpellHit(Unit caster, SpellInfo spell) + { + if (spell.Id == InstanceSpells.ReviveChampion && !IsUndead) + { + IsUndead = true; + me.setDeathState(DeathState.JustRespawned); + uint newEntry = 0; + switch (me.GetEntry()) + { + case CreatureIds.CaptainArnath: + newEntry = CreatureIds.CaptainArnathUndead; + break; + case CreatureIds.CaptainBrandon: + newEntry = CreatureIds.CaptainBrandonUndead; + break; + case CreatureIds.CaptainGrondel: + newEntry = CreatureIds.CaptainGrondelUndead; + break; + case CreatureIds.CaptainRupert: + newEntry = CreatureIds.CaptainRupertUndead; + break; + default: + return; + } + + Talk(Texts.SayCaptainResurrected); + me.UpdateEntry(newEntry, me.GetCreatureData()); + DoCast(me, InstanceSpells.Undeath, true); + } + } + + InstanceScript instance; + float FollowAngle; + float FollowDist; + public bool IsUndead; + bool _firstDeath; + } + + [Script] + class npc_captain_arnath : CreatureScript + { + public npc_captain_arnath() : base("CreatureIds.CaptainArnath") { } + + class npc_captain_arnathAI : npc_argent_captainAI + { + public npc_captain_arnathAI(Creature creature) + : base(creature) { } + + public override void Reset() + { + _events.Reset(); + _events.ScheduleEvent(EventTypes.ArnathFlashHeal, RandomHelper.URand(4000, 7000)); + _events.ScheduleEvent(EventTypes.ArnathPwShield, RandomHelper.URand(8000, 14000)); + _events.ScheduleEvent(EventTypes.ArnathSmite, RandomHelper.URand(3000, 6000)); + if (Is25ManRaid() && IsUndead) + _events.ScheduleEvent(EventTypes.ArnathDominateMind, RandomHelper.URand(22000, 27000)); + } + + public override void UpdateAI(uint diff) + { + if (!UpdateVictim()) + return; + + _events.Update(diff); + + if (me.HasUnitState(UnitState.Casting)) + return; + + _events.ExecuteEvents(eventId => + { + switch (eventId) + { + case EventTypes.ArnathFlashHeal: + Creature target = FindFriendlyCreature(); + if (target) + DoCast(target, InstanceSpells.SpellFlashHeal(IsUndead)); + _events.ScheduleEvent(EventTypes.ArnathFlashHeal, RandomHelper.URand(6000, 9000)); + break; + case EventTypes.ArnathPwShield: + { + List targets = DoFindFriendlyMissingBuff(40.0f, InstanceSpells.SpellPowerWordShield(IsUndead)); + DoCast(targets.PickRandom(), InstanceSpells.SpellPowerWordShield(IsUndead)); + _events.ScheduleEvent(EventTypes.ArnathPwShield, RandomHelper.URand(15000, 20000)); + break; + } + case EventTypes.ArnathSmite: + DoCastVictim(InstanceSpells.SpellSmite(IsUndead)); + _events.ScheduleEvent(EventTypes.ArnathSmite, RandomHelper.URand(4000, 7000)); + break; + case EventTypes.ArnathDominateMind: + Unit target1 = SelectTarget(SelectAggroTarget.Random, 1, 0.0f, true); + if (target1) + DoCast(target1, InstanceSpells.DominateMind); + _events.ScheduleEvent(EventTypes.ArnathDominateMind, RandomHelper.URand(28000, 37000)); + break; + default: + break; + } + }); + + DoMeleeAttackIfReady(); + } + + Creature FindFriendlyCreature() + { + var u_check = new MostHPMissingInRange(me, 60.0f, 0); + var searcher = new CreatureLastSearcher(me, u_check); + Cell.VisitGridObjects(me, searcher, 60.0f); + return searcher.GetTarget(); + } + } + + public override CreatureAI GetAI(Creature creature) + { + return InstanceIcecrownCitadel.GetInstanceAI(creature); + } + } + + [Script] + class npc_captain_brandon : CreatureScript + { + public npc_captain_brandon() : base("CreatureIds.CaptainBrandon") { } + + class npc_captain_brandonAI : npc_argent_captainAI + { + public npc_captain_brandonAI(Creature creature) + : base(creature) { } + + public override void Reset() + { + _events.Reset(); + _events.ScheduleEvent(EventTypes.BrandonCrusaderStrike, RandomHelper.URand(6000, 10000)); + _events.ScheduleEvent(EventTypes.BrandonDivineShield, 500); + _events.ScheduleEvent(EventTypes.BrandonJudgementOfCommand, RandomHelper.URand(8000, 13000)); + if (IsUndead) + _events.ScheduleEvent(EventTypes.BrandonHammerOfBetrayal, RandomHelper.URand(25000, 30000)); + } + + public override void UpdateAI(uint diff) + { + if (!UpdateVictim()) + return; + + _events.Update(diff); + + if (me.HasUnitState(UnitState.Casting)) + return; + + _events.ExecuteEvents(eventId => + { + switch (eventId) + { + case EventTypes.BrandonCrusaderStrike: + DoCastVictim(InstanceSpells.CrusaderStrike); + _events.ScheduleEvent(EventTypes.BrandonCrusaderStrike, RandomHelper.URand(6000, 12000)); + break; + case EventTypes.BrandonDivineShield: + if (HealthBelowPct(20)) + DoCast(me, InstanceSpells.DivineShield); + _events.ScheduleEvent(EventTypes.BrandonDivineShield, 500); + break; + case EventTypes.BrandonJudgementOfCommand: + DoCastVictim(InstanceSpells.JudgementOfCommand); + _events.ScheduleEvent(EventTypes.BrandonJudgementOfCommand, RandomHelper.URand(8000, 13000)); + break; + case EventTypes.BrandonHammerOfBetrayal: + Unit target = SelectTarget(SelectAggroTarget.Random, 1, 0.0f, true); + if (target) + DoCast(target, InstanceSpells.HammerOfBetrayal); + _events.ScheduleEvent(EventTypes.BrandonHammerOfBetrayal, RandomHelper.URand(45000, 60000)); + break; + default: + break; + } + }); + + DoMeleeAttackIfReady(); + } + } + + public override CreatureAI GetAI(Creature creature) + { + return InstanceIcecrownCitadel.GetInstanceAI(creature); + } + } + + [Script] + class npc_captain_grondel : CreatureScript + { + public npc_captain_grondel() : base("CreatureIds.CaptainGrondel") { } + + class npc_captain_grondelAI : npc_argent_captainAI + { + public npc_captain_grondelAI(Creature creature) + : base(creature) { } + + public override void Reset() + { + _events.Reset(); + _events.ScheduleEvent(EventTypes.GrondelChargeCheck, 500); + _events.ScheduleEvent(EventTypes.GrondelMortalStrike, RandomHelper.URand(8000, 14000)); + _events.ScheduleEvent(EventTypes.GrondelSunderArmor, RandomHelper.URand(3000, 12000)); + if (IsUndead) + _events.ScheduleEvent(EventTypes.GrondelConflagration, RandomHelper.URand(12000, 17000)); + } + + public override void UpdateAI(uint diff) + { + if (!UpdateVictim()) + return; + + _events.Update(diff); + + if (me.HasUnitState(UnitState.Casting)) + return; + + _events.ExecuteEvents(eventId => + { + switch (eventId) + { + case EventTypes.GrondelChargeCheck: + DoCastVictim(InstanceSpells.Charge); + _events.ScheduleEvent(EventTypes.GrondelChargeCheck, 500); + break; + case EventTypes.GrondelMortalStrike: + DoCastVictim(InstanceSpells.MortalStrike); + _events.ScheduleEvent(EventTypes.GrondelMortalStrike, RandomHelper.URand(10000, 15000)); + break; + case EventTypes.GrondelSunderArmor: + DoCastVictim(InstanceSpells.SunderArmor); + _events.ScheduleEvent(EventTypes.GrondelSunderArmor, RandomHelper.URand(5000, 17000)); + break; + case EventTypes.GrondelConflagration: + Unit target = SelectTarget(SelectAggroTarget.Random, 0, 0.0f, true); + if (target) + DoCast(target, InstanceSpells.Conflagration); + _events.ScheduleEvent(EventTypes.GrondelConflagration, RandomHelper.URand(10000, 15000)); + break; + default: + break; + } + }); + + DoMeleeAttackIfReady(); + } + } + + public override CreatureAI GetAI(Creature creature) + { + return InstanceIcecrownCitadel.GetInstanceAI(creature); + } + } + + [Script] + class npc_captain_rupert : CreatureScript + { + public npc_captain_rupert() : base("CreatureIds.CaptainRupert") { } + + class npc_captain_rupertAI : npc_argent_captainAI + { + public npc_captain_rupertAI(Creature creature) + : base(creature) + { + } + + public override void Reset() + { + _events.Reset(); + _events.ScheduleEvent(EventTypes.RupertFelIronBomb, RandomHelper.URand(15000, 20000)); + _events.ScheduleEvent(EventTypes.RupertMachineGun, RandomHelper.URand(25000, 30000)); + _events.ScheduleEvent(EventTypes.RupertRocketLaunch, RandomHelper.URand(10000, 15000)); + } + + public override void UpdateAI(uint diff) + { + if (!UpdateVictim()) + return; + + _events.Update(diff); + + if (me.HasUnitState(UnitState.Casting)) + return; + + _events.ExecuteEvents(eventId => + { + Unit target; + switch (eventId) + { + case EventTypes.RupertFelIronBomb: + target = SelectTarget(SelectAggroTarget.Random, 0); + if (target) + DoCast(target, InstanceSpells.SpellFelIronBomb(IsUndead)); + _events.ScheduleEvent(EventTypes.RupertFelIronBomb, RandomHelper.URand(15000, 20000)); + break; + case EventTypes.RupertMachineGun: + target = SelectTarget(SelectAggroTarget.Random, 1); + if (target) + DoCast(target, InstanceSpells.SpellMachineGun(IsUndead)); + _events.ScheduleEvent(EventTypes.RupertMachineGun, RandomHelper.URand(25000, 30000)); + break; + case EventTypes.RupertRocketLaunch: + target = SelectTarget(SelectAggroTarget.Random, 1); + if (target) + DoCast(target, InstanceSpells.SpellRocketLaunch(IsUndead)); + _events.ScheduleEvent(EventTypes.RupertRocketLaunch, RandomHelper.URand(10000, 15000)); + break; + default: + break; + } + }); + + DoMeleeAttackIfReady(); + } + } + + public override CreatureAI GetAI(Creature creature) + { + return InstanceIcecrownCitadel.GetInstanceAI(creature); + } + } + + [Script] + class npc_frostwing_vrykul : CreatureScript + { + public npc_frostwing_vrykul() : base("npc_frostwing_vrykul") { } + + class npc_frostwing_vrykulAI : SmartAI + { + public npc_frostwing_vrykulAI(Creature creature) + : base(creature) { } + + public override bool CanAIAttack(Unit target) + { + // do not see targets inside Frostwing Halls when we are not there + return (me.GetPositionY() > 2660.0f) == (target.GetPositionY() > 2660.0f) && base.CanAIAttack(target); + } + } + + public override CreatureAI GetAI(Creature creature) + { + return new npc_frostwing_vrykulAI(creature); + } + } + + [Script] + class npc_impaling_spear : CreatureScript + { + public npc_impaling_spear() : base("npc_impaling_spear") { } + + class npc_impaling_spearAI : CreatureAI + { + public npc_impaling_spearAI(Creature creature) + : base(creature) + { + } + + public override void Reset() + { + me.SetReactState(ReactStates.Passive); + _vehicleCheckTimer = 500; + } + + public override void UpdateAI(uint diff) + { + if (_vehicleCheckTimer <= diff) + { + _vehicleCheckTimer = 500; + if (!me.GetVehicle()) + me.DespawnOrUnsummon(100); + } + else + _vehicleCheckTimer -= diff; + } + + uint _vehicleCheckTimer; + } + + public override CreatureAI GetAI(Creature creature) + { + return new npc_impaling_spearAI(creature); + } + } + + [Script] + class npc_arthas_teleport_visual : CreatureScript + { + public npc_arthas_teleport_visual() : base("npc_arthas_teleport_visual") { } + + class npc_arthas_teleport_visualAI : NullCreatureAI + { + public npc_arthas_teleport_visualAI(Creature creature) + : base(creature) + { + _instance = creature.GetInstanceScript(); + } + + public override void Reset() + { + _events.Reset(); + if (_instance.GetBossState(Bosses.ProfessorPutricide) == EncounterState.Done && + _instance.GetBossState(Bosses.BloodQueenLanaThel) == EncounterState.Done && + _instance.GetBossState(Bosses.Sindragosa) == EncounterState.Done) + _events.ScheduleEvent(EventTypes.SoulMissile, RandomHelper.URand(1000, 6000)); + } + + void Update(uint diff) + { + if (_events.Empty()) + return; + + _events.Update(diff); + + if (_events.ExecuteEvent() == EventTypes.SoulMissile) + { + DoCastAOE(InstanceSpells.SoulMissile); + _events.ScheduleEvent(EventTypes.SoulMissile, RandomHelper.URand(5000, 7000)); + } + } + + InstanceScript _instance; + } + + public override CreatureAI GetAI(Creature creature) + { + // Distance from the center of the spire + if (creature.GetExactDist2d(4357.052f, 2769.421f) < 100.0f && creature.GetHomePosition().GetPositionZ() < 315.0f) + return InstanceIcecrownCitadel.GetInstanceAI(creature); + + // Default to no script + return null; + } + } + + [Script] + class spell_icc_stoneform : SpellScriptLoader + { + public spell_icc_stoneform() : base("spell_icc_stoneform") { } + + class spell_icc_stoneform_AuraScript : AuraScript + { + void OnApply(AuraEffect aurEff, AuraEffectHandleModes mode) + { + Creature target = GetTarget().ToCreature(); + if (target) + { + target.SetReactState(ReactStates.Passive); + target.SetFlag(UnitFields.Flags, UnitFlags.NotSelectable | UnitFlags.ImmuneToPc); + target.SetUInt32Value(UnitFields.NpcEmotestate, (uint)Emote.OneshotCustomSpell02); + } + } + + void OnRemove(AuraEffect aurEff, AuraEffectHandleModes mode) + { + Creature target = GetTarget().ToCreature(); + if (target) + { + target.SetReactState(ReactStates.Aggressive); + target.RemoveFlag(UnitFields.Flags, UnitFlags.NotSelectable | UnitFlags.ImmuneToPc); + target.SetUInt32Value(UnitFields.NpcEmotestate, 0); + } + } + + public override void Register() + { + OnEffectApply.Add(new EffectApplyHandler(OnApply, 0, AuraType.Dummy, AuraEffectHandleModes.Real)); + OnEffectRemove.Add(new EffectApplyHandler(OnRemove, 0, AuraType.Dummy, AuraEffectHandleModes.Real)); + } + } + + public override AuraScript GetAuraScript() + { + return new spell_icc_stoneform_AuraScript(); + } + } + + [Script] + class spell_icc_sprit_alarm : SpellScriptLoader + { + public spell_icc_sprit_alarm() : base("spell_icc_sprit_alarm") { } + + class spell_icc_sprit_alarm_SpellScript : SpellScript + { + public const int AwakenWard1 = 22900; + public const int AwakenWard2 = 22907; + public const int AwakenWard3 = 22908; + public const int AwakenWard4 = 22909; + + void HandleEvent(uint effIndex) + { + PreventHitDefaultEffect(effIndex); + uint trapId = 0; + switch (GetSpellInfo().GetEffect(effIndex).MiscValue) + { + case AwakenWard1: + trapId = GameObjectIds.SpiritAlarm1; + break; + case AwakenWard2: + trapId = GameObjectIds.SpiritAlarm2; + break; + case AwakenWard3: + trapId = GameObjectIds.SpiritAlarm3; + break; + case AwakenWard4: + trapId = GameObjectIds.SpiritAlarm4; + break; + default: + return; + } + + GameObject trap = GetCaster().FindNearestGameObject(trapId, 5.0f); + if (trap) + trap.SetRespawnTime((int)trap.GetGoInfo().GetAutoCloseTime()); + + List wards = new List(); + GetCaster().GetCreatureListWithEntryInGrid(wards, CreatureIds.DeathboundWard, 150.0f); + wards.Sort(new ObjectDistanceOrderPred(GetCaster())); + foreach (var creature in wards) + { + if (creature.IsAlive() && creature.HasAura(InstanceSpells.StoneForm)) + { + creature.GetAI().Talk(Texts.SayTrapActivate); + creature.RemoveAurasDueToSpell(InstanceSpells.StoneForm); + Unit target = creature.SelectNearestTarget(150.0f); + if (target) + creature.GetAI().AttackStart(target); + + break; + } + } + } + + public override void Register() + { + OnEffectHit.Add(new EffectHandler(HandleEvent, 2, SpellEffectName.SendEvent)); + } + } + + public override SpellScript GetSpellScript() + { + return new spell_icc_sprit_alarm_SpellScript(); + } + } + + [Script] + class spell_frost_giant_death_plague : SpellScriptLoader + { + public spell_frost_giant_death_plague() : base("spell_frost_giant_death_plague") { } + + class spell_frost_giant_death_plague_SpellScript : SpellScript + { + public override bool Load() + { + _failed = false; + return true; + } + + // First effect + void CountTargets(List targets) + { + targets.Remove(GetCaster()); + _failed = targets.Empty(); + } + + // Second effect + void FilterTargets(List targets) + { + // Select valid targets for jump + targets.RemoveAll(obj => + { + if (obj == GetCaster()) + return true; + + if (!obj.IsTypeId(TypeId.Player)) + return true; + + if (obj.ToUnit().HasAura(InstanceSpells.RecentlyInfected) || obj.ToUnit().HasAura(InstanceSpells.DeathPlagueAura)) + return true; + + return false; + }); + + if (!targets.Empty()) + { + WorldObject target = targets.PickRandom(); + targets.Clear(); + targets.Add(target); + } + + targets.Add(GetCaster()); + } + + void HandleScript(uint effIndex) + { + PreventHitDefaultEffect(effIndex); + if (GetHitUnit() != GetCaster()) + GetCaster().CastSpell(GetHitUnit(), InstanceSpells.DeathPlagueAura, true); + else if (_failed) + GetCaster().CastSpell(GetCaster(), InstanceSpells.DeathPlagueKill, true); + } + + public override void Register() + { + OnObjectAreaTargetSelect.Add(new ObjectAreaTargetSelectHandler(CountTargets, 0, Targets.UnitSrcAreaAlly)); + OnObjectAreaTargetSelect.Add(new ObjectAreaTargetSelectHandler(FilterTargets, 1, Targets.UnitSrcAreaAlly)); + OnEffectHitTarget.Add(new EffectHandler(HandleScript, 1, SpellEffectName.ScriptEffect)); + } + + bool _failed; + } + + public override SpellScript GetSpellScript() + { + return new spell_frost_giant_death_plague_SpellScript(); + } + } + + [Script] + class spell_icc_harvest_blight_specimen : SpellScriptLoader + { + public spell_icc_harvest_blight_specimen() : base("spell_icc_harvest_blight_specimen") { } + + class spell_icc_harvest_blight_specimen_SpellScript : SpellScript + { + void HandleScript(uint effIndex) + { + PreventHitDefaultEffect(effIndex); + GetHitUnit().RemoveAurasDueToSpell((uint)GetEffectValue()); + } + + void HandleQuestComplete(uint effIndex) + { + GetHitUnit().RemoveAurasDueToSpell((uint)GetEffectValue()); + } + + public override void Register() + { + OnEffectHitTarget.Add(new EffectHandler(HandleScript, 0, SpellEffectName.ScriptEffect)); + OnEffectHitTarget.Add(new EffectHandler(HandleQuestComplete, 1, SpellEffectName.QuestComplete)); + } + } + + public override SpellScript GetSpellScript() + { + return new spell_icc_harvest_blight_specimen_SpellScript(); + } + } + + [Script] + class spell_svalna_revive_champion : SpellScriptLoader + { + public spell_svalna_revive_champion() : base("spell_svalna_revive_champion") { } + + class spell_svalna_revive_champion_SpellScript : SpellScript + { + void RemoveAliveTarget(List targets) + { + targets.RemoveAll(obj => + { + Unit unit = obj.ToUnit(); + if (unit) + return unit.IsAlive(); + + return true; + }); + targets = targets.PickRandom(2).ToList(); + } + + void Land(uint effIndex) + { + Creature caster = GetCaster().ToCreature(); + if (!caster) + return; + + Position pos = caster.GetNearPosition(5.0f, 0.0f); + caster.SetHomePosition(pos); + caster.GetMotionMaster().MoveLand(1, pos); + } + + public override void Register() + { + OnObjectAreaTargetSelect.Add(new ObjectAreaTargetSelectHandler(RemoveAliveTarget, 0, Targets.UnitDestAreaEntry)); + OnEffectHit.Add(new EffectHandler(Land, 0, SpellEffectName.ScriptEffect)); + } + } + + public override SpellScript GetSpellScript() + { + return new spell_svalna_revive_champion_SpellScript(); + } + } + + [Script] + class spell_svalna_remove_spear : SpellScriptLoader + { + public spell_svalna_remove_spear() : base("spell_svalna_remove_spear") { } + + class spell_svalna_remove_spear_SpellScript : SpellScript + { + void HandleScript(uint effIndex) + { + PreventHitDefaultEffect(effIndex); + Creature target = GetHitCreature(); + if (target) + { + Unit vehicle = target.GetVehicleBase(); + if (vehicle) + vehicle.RemoveAurasDueToSpell(InstanceSpells.ImpalingSpear); + target.DespawnOrUnsummon(1); + } + } + + public override void Register() + { + OnEffectHitTarget.Add(new EffectHandler(HandleScript, 0, SpellEffectName.ScriptEffect)); + } + } + + public override SpellScript GetSpellScript() + { + return new spell_svalna_remove_spear_SpellScript(); + } + } + + // 72585 - Soul Missile + [Script] + class spell_icc_soul_missile : SpellScriptLoader + { + public spell_icc_soul_missile() : base("spell_icc_soul_missile") { } + + class spell_icc_soul_missile_SpellScript : SpellScript + { + void RelocateDest(ref SpellDestination dest) + { + Position offset = new Position(0.0f, 0.0f, 200.0f, 0.0f); + dest.RelocateOffset(offset); + } + + public override void Register() + { + OnDestinationTargetSelect.Add(new DestinationTargetSelectHandler(RelocateDest, 0, Targets.DestCaster)); + } + } + + public override SpellScript GetSpellScript() + { + return new spell_icc_soul_missile_SpellScript(); + } + } + + [Script] + class at_icc_saurfang_portal : AreaTriggerScript + { + public at_icc_saurfang_portal() : base("at_icc_saurfang_portal") { } + + public override bool OnTrigger(Player player, AreaTriggerRecord areaTrigger, bool entered) + { + InstanceScript instance = player.GetInstanceScript(); + if (instance == null || instance.GetBossState(Bosses.DeathbringerSaurfang) != EncounterState.Done) + return true; + + player.TeleportTo(631, 4126.35f, 2769.23f, 350.963f, 0.0f); + + if (instance.GetData(DataTypes.ColdflameJets) == (uint)EncounterState.NotStarted) + { + // Process relocation now, to preload the grid and initialize traps + player.GetMap().PlayerRelocation(player, 4126.35f, 2769.23f, 350.963f, 0.0f); + + instance.SetData(DataTypes.ColdflameJets, (uint)EncounterState.InProgress); + List traps = new List(); + player.GetCreatureListWithEntryInGrid(traps, CreatureIds.FrostFreezeTrap, 120.0f); + traps.Sort(new ObjectDistanceOrderPred(player)); + bool instant = false; + foreach (var creature in traps) + { + creature.GetAI().DoAction(instant ? 1000 : 11000); + instant = !instant; + } + } + + return true; + } + } + + [Script] + class at_icc_shutdown_traps : AreaTriggerScript + { + public at_icc_shutdown_traps() : base("at_icc_shutdown_traps") { } + + public override bool OnTrigger(Player player, AreaTriggerRecord areaTrigger, bool entered) + { + InstanceScript instance = player.GetInstanceScript(); + if (instance != null) + instance.SetData(DataTypes.UpperSpireTeleAct, (uint)EncounterState.Done); + + return true; + } + } + + [Script] + class at_icc_start_blood_quickening : AreaTriggerScript + { + public at_icc_start_blood_quickening() : base("at_icc_start_blood_quickening") { } + + public override bool OnTrigger(Player player, AreaTriggerRecord areaTrigger, bool entered) + { + InstanceScript instance = player.GetInstanceScript(); + if (instance != null) + if (instance.GetData(DataTypes.BloodQuickeningState) == (uint)EncounterState.NotStarted) + instance.SetData(DataTypes.BloodQuickeningState, (uint)EncounterState.InProgress); + return true; + } + } + + [Script] + class at_icc_start_frostwing_gauntlet : AreaTriggerScript + { + public at_icc_start_frostwing_gauntlet() : base("at_icc_start_frostwing_gauntlet") { } + + public override bool OnTrigger(Player player, AreaTriggerRecord areaTrigger, bool entered) + { + InstanceScript instance = player.GetInstanceScript(); + if (instance != null) + { + Creature crok = ObjectAccessor.GetCreature(player, instance.GetGuidData(DataTypes.CrokScourgebane)); + if (crok) + crok.GetAI().DoAction(Actions.StartGauntlet); + } + return true; + } + } + + [Script("spell_svalna_caress_of_death", 70196u)] + class spell_trigger_spell_from_caster : SpellScriptLoader + { + public spell_trigger_spell_from_caster(string scriptName, uint triggerId) : base(scriptName) + { + _triggerId = triggerId; + } + + class spell_trigger_spell_from_caster_SpellScript : SpellScript + { + public spell_trigger_spell_from_caster_SpellScript(uint triggerId) + { + _triggerId = triggerId; + } + + public override bool Validate(SpellInfo spell) + { + return ValidateSpellInfo(_triggerId); + } + + void HandleTrigger() + { + GetCaster().CastSpell(GetHitUnit(), _triggerId, true); + } + + public override void Register() + { + AfterHit.Add(new HitHandler(HandleTrigger)); + } + + uint _triggerId; + } + + public override SpellScript GetSpellScript() + { + return new spell_trigger_spell_from_caster_SpellScript(_triggerId); + } + + + uint _triggerId; + } +} diff --git a/Scripts/Northrend/IcecrownCitadel/IcecrownCitadelConst.cs b/Scripts/Northrend/IcecrownCitadel/IcecrownCitadelConst.cs new file mode 100644 index 000000000..611cdf520 --- /dev/null +++ b/Scripts/Northrend/IcecrownCitadel/IcecrownCitadelConst.cs @@ -0,0 +1,733 @@ +/* + * Copyright (C) 2012-2017 CypherCore + * + * 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, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +namespace Scripts.Northrend.IcecrownCitadel +{ + struct IccConst + { + public const uint WeeklyNPCs = 9; + } + + struct Bosses + { + public const uint LordMarrowgar = 0; + public const uint LadyDeathwhisper = 1; + public const uint GunshipBattle = 2; + public const uint DeathbringerSaurfang = 3; + public const uint Festergut = 4; + public const uint Rotface = 5; + public const uint ProfessorPutricide = 6; + public const uint BloodPrinceCouncil = 7; + public const uint BloodQueenLanaThel = 8; + public const uint SisterSvalna = 9; + public const uint ValithriaDreamwalker = 10; + public const uint Sindragosa = 11; + public const uint TheLichKing = 12; + + public const uint MaxEncounters = 13; + } + + struct Texts + { + // Highlord Tirion Fordring (At Light'S Hammer) + public const uint SayTirionIntro1 = 0; + public const uint SayTirionIntro2 = 1; + public const uint SayTirionIntro3 = 2; + public const uint SayTirionIntro4 = 3; + public const uint SayTirionIntroH5 = 4; + public const uint SayTirionIntroA5 = 5; + + // The Lich King (At Light'S Hammer) + public const uint SayLkIntro1 = 0; + public const uint SayLkIntro2 = 1; + public const uint SayLkIntro3 = 2; + public const uint SayLkIntro4 = 3; + public const uint SayLkIntro5 = 4; + + // Highlord Bolvar Fordragon (At Light'S Hammer) + public const uint SayBolvarIntro1 = 0; + + // High Overlord Saurfang (At Light'S Hammer) + public const uint SaySaurfangIntro1 = 15; + public const uint SaySaurfangIntro2 = 16; + public const uint SaySaurfangIntro3 = 17; + public const uint SaySaurfangIntro4 = 18; + + // Muradin Bronzebeard (At Light'S Hammer) + public const uint SayMuradinIntro1 = 13; + public const uint SayMuradinIntro2 = 14; + public const uint SayMuradinIntro3 = 15; + + // Deathbound Ward + public const uint SayTrapActivate = 0; + + // Rotting Frost Giant + public const uint EmoteDeathPlagueWarning = 0; + + // Sister Svalna + public const uint SaySvalnaKillCaptain = 1; // Happens When She Kills A Captain + public const uint SaySvalnaKill = 4; + public const uint SaySvalnaCaptainDeath = 5; // Happens When A Captain Resurrected By Her Dies + public const uint SaySvalnaDeath = 6; + public const uint EmoteSvalnaImpale = 7; + public const uint EmoteSvalnaBrokenShield = 8; + + public const uint SayCrokIntro1 = 0; // Ready Your Arms; My Argent Brothers. The Vrykul Will Protect The Frost Queen With Their Lives. + public const uint SayArnathIntro2 = 5; // Even Dying Here Beats Spending Another Day Collecting Reagents For That Madman; Finklestein. + public const uint SayCrokIntro3 = 1; // Enough Idle Banter! Our Champions Have Arrived - Support Them As We Push Our Way Through The Hall! + public const uint SaySvalnaEventStart = 0; // You May Have Once Fought Beside Me; Crok; But Now You Are Nothing More Than A Traitor. Come; Your Second Death Approaches! + public const uint SayCrokCombatWp0 = 2; // Draw Them Back To Us; And We'Ll Assist You. + public const uint SayCrokCombatWp1 = 3; // Quickly; Push On! + public const uint SayCrokFinalWp = 4; // Her Reinforcements Will Arrive Shortly; We Must Bring Her Down Quickly! + public const uint SaySvalnaResurrectCaptains = 2; // Foolish Crok. You Brought My Reinforcements With You. Arise; Argent Champions; And Serve The Lich King In Death! + public const uint SayCrokCombatSvalna = 5; // I'Ll Draw Her Attacks. Return Our Brothers To Their Graves; Then Help Me Bring Her Down! + public const uint SaySvalnaAggro = 3; // Come; Scourgebane. I'Ll Show The Master Which Of Us Is Truly Worthy Of The Title Of "Champion"! + public const uint SayCaptainDeath = 0; + public const uint SayCaptainResurrected = 1; + public const uint SayCaptainKill = 2; + public const uint SayCaptainSecondDeath = 3; + public const uint SayCaptainSurviveTalk = 4; + public const uint SayCrokWeakeningGauntlet = 6; + public const uint SayCrokWeakeningSvalna = 7; + public const uint SayCrokDeath = 8; + } + + struct InstanceSpells + { + // Rotting Frost Giant + public const uint DeathPlague = 72879; + public const uint DeathPlagueAura = 72865; + public const uint RecentlyInfected = 72884; + public const uint DeathPlagueKill = 72867; + public const uint Stomp = 64652; + public const uint ArcticBreath = 72848; + + // Frost Freeze Trap + public const uint ColdflameJets = 70460; + + // Alchemist Adrianna + public const uint HarvestBlightSpecimen = 72155; + public const uint HarvestBlightSpecimen25 = 72162; + + // Crok Scourgebane + public const uint IceboundArmor = 70714; + public const uint ScourgeStrike = 71488; + public const uint DeathStrike = 71489; + + // Sister Svalna + public const uint CaressOfDeath = 70078; + public const uint ImpalingSpearKill = 70196; + public const uint ReviveChampion = 70053; + public const uint Undeath = 70089; + public const uint ImpalingSpear = 71443; + public const uint AetherShield = 71463; + public const uint HurlSpear = 71466; + + // Captain Arnath + public const uint DominateMind = 14515; + public const uint FlashHealNormal = 71595; + public const uint PowerWordShieldNormal = 71548; + public const uint SmiteNormal = 71546; + public const uint FlashHealUndead = 71782; + public const uint PowerWordShieldUndead = 71780; + public const uint SmiteUndead = 71778; + public static uint SpellFlashHeal(bool isUndead) { return isUndead ? InstanceSpells.FlashHealUndead : InstanceSpells.FlashHealNormal; } + public static uint SpellPowerWordShield(bool isUndead) { return isUndead ? InstanceSpells.PowerWordShieldUndead : InstanceSpells.PowerWordShieldNormal; } + public static uint SpellSmite(bool isUndead) { return isUndead ? InstanceSpells.SmiteUndead : InstanceSpells.SmiteNormal; } + + // Captain Brandon + public const uint CrusaderStrike = 71549; + public const uint DivineShield = 71550; + public const uint JudgementOfCommand = 71551; + public const uint HammerOfBetrayal = 71784; + + // Captain Grondel + public const uint Charge = 71553; + public const uint MortalStrike = 71552; + public const uint SunderArmor = 71554; + public const uint Conflagration = 71785; + + // Captain Rupert + public const uint FelIronBombNormal = 71592; + public const uint MachineGunNormal = 71594; + public const uint RocketLaunchNormal = 71590; + public const uint FelIronBombUndead = 71787; + public const uint MachineGunUndead = 71788; + public const uint RocketLaunchUndead = 71786; + public static uint SpellFelIronBomb(bool isUndead) { return isUndead ? InstanceSpells.FelIronBombUndead : InstanceSpells.FelIronBombNormal; } + public static uint SpellMachineGun(bool isUndead) { return isUndead ? InstanceSpells.MachineGunUndead : InstanceSpells.MachineGunNormal; } + public static uint SpellRocketLaunch(bool isUndead) { return isUndead ? InstanceSpells.RocketLaunchUndead : InstanceSpells.RocketLaunchNormal; } + + // Invisible Stalker (Float; Uninteractible; Largeaoi) + public const uint SoulMissile = 72585; + + public const uint Berserk = 26662; + public const uint Berserk2 = 47008; + + // Deathbound Ward + public const uint StoneForm = 70733; + + // Residue Rendezvous + public const uint OrangeBlightResidue = 72144; + public const uint GreenBlightResidue = 72145; + + // The Lich King + public const uint ArthasTeleporterCeremony = 72915; + public const uint FrostmourneTeleportVisual = 73078; + + // Shadowmourne questline + public const uint UnsatedCraving = 71168; + public const uint ShadowsFate = 71169; + } + + struct EventTypes + { + // Highlord Tirion Fordring (At Light'S Hammer) + // The Lich King (At Light'S Hammer) + // Highlord Bolvar Fordragon (At Light'S Hammer) + // High Overlord Saurfang (At Light'S Hammer) + // Muradin Bronzebeard (At Light'S Hammer) + public const uint TirionIntro2 = 1; + public const uint TirionIntro3 = 2; + public const uint TirionIntro4 = 3; + public const uint TirionIntro5 = 4; + public const uint LkIntro1 = 5; + public const uint TirionIntro6 = 6; + public const uint LkIntro2 = 7; + public const uint LkIntro3 = 8; + public const uint LkIntro4 = 9; + public const uint BolvarIntro1 = 10; + public const uint LkIntro5 = 11; + public const uint SaurfangIntro1 = 12; + public const uint TirionIntroH7 = 13; + public const uint SaurfangIntro2 = 14; + public const uint SaurfangIntro3 = 15; + public const uint SaurfangIntro4 = 16; + public const uint SaurfangRun = 17; + public const uint MuradinIntro1 = 18; + public const uint MuradinIntro2 = 19; + public const uint MuradinIntro3 = 20; + public const uint TirionIntroA7 = 21; + public const uint MuradinIntro4 = 22; + public const uint MuradinIntro5 = 23; + public const uint MuradinRun = 24; + + // Rotting Frost Giant + public const uint DeathPlague = 25; + public const uint Stomp = 26; + public const uint ArcticBreath = 27; + + // Frost Freeze Trap + public const uint ActivateTrap = 28; + + // Crok Scourgebane + public const uint ScourgeStrike = 29; + public const uint DeathStrike = 30; + public const uint HealthCheck = 31; + public const uint CrokIntro3 = 32; + public const uint StartPathing = 33; + + // Sister Svalna + public const uint ArnathIntro2 = 34; + public const uint SvalnaStart = 35; + public const uint SvalnaResurrect = 36; + public const uint SvalnaCombat = 37; + public const uint ImpalingSpear = 38; + public const uint AetherShield = 39; + + // Captain Arnath + public const uint ArnathFlashHeal = 40; + public const uint ArnathPwShield = 41; + public const uint ArnathSmite = 42; + public const uint ArnathDominateMind = 43; + + // Captain Brandon + public const uint BrandonCrusaderStrike = 44; + public const uint BrandonDivineShield = 45; + public const uint BrandonJudgementOfCommand = 46; + public const uint BrandonHammerOfBetrayal = 47; + + // Captain Grondel + public const uint GrondelChargeCheck = 48; + public const uint GrondelMortalStrike = 49; + public const uint GrondelSunderArmor = 50; + public const uint GrondelConflagration = 51; + + // Captain Rupert + public const uint RupertFelIronBomb = 52; + public const uint RupertMachineGun = 53; + public const uint RupertRocketLaunch = 54; + + // Invisible Stalker (Float; Uninteractible; Largeaoi) + public const uint SoulMissile = 55; + } + + struct DataTypes + { + // Additional Data + public const uint SaurfangEventNpc = 13; + public const uint BonedAchievement = 14; + public const uint OozeDanceAchievement = 15; + public const uint PutricideTable = 16; + public const uint NauseaAchievement = 17; + public const uint OrbWhispererAchievement = 18; + public const uint PrinceKelesethGuid = 19; + public const uint PrinceTaldaramGuid = 20; + public const uint PrinceValanarGuid = 21; + public const uint BloodPrincesControl = 22; + public const uint SindragosaFrostwyrms = 23; + public const uint Spinestalker = 24; + public const uint Rimefang = 25; + public const uint ColdflameJets = 26; + public const uint TeamInInstance = 27; + public const uint BloodQuickeningState = 28; + public const uint HeroicAttempts = 29; + public const uint CrokScourgebane = 30; + public const uint CaptainArnath = 31; + public const uint CaptainBrandon = 32; + public const uint CaptainGrondel = 33; + public const uint CaptainRupert = 34; + public const uint ValithriaTrigger = 35; + public const uint ValithriaLichKing = 36; + public const uint HighlordTirionFordring = 37; + public const uint ArthasPlatform = 38; + public const uint TerenasMenethil = 39; + public const uint EnemyGunship = 40; + public const uint UpperSpireTeleAct = 41; + } + + struct WorldStates + { + public const uint ShowTimer = 4903; + public const uint ExecutionTime = 4904; + public const uint ShowAttempts = 4940; + public const uint AttemptsRemaining = 4941; + public const uint AttemptsMax = 4942; + } + + struct CreatureIds + { + // At Light'S Hammer + public const uint HighlordTirionFordringLh = 37119; + public const uint TheLichKingLh = 37181; + public const uint HighlordBolvarFordragonLh = 37183; + public const uint KorKronGeneral = 37189; + public const uint AllianceCommander = 37190; + public const uint Tortunok = 37992; // Druid Armor H + public const uint AlanaMoonstrike = 37999; // Druid Armor A + public const uint GerardoTheSuave = 37993; // Hunter Armor H + public const uint TalanMoonstrike = 37998; // Hunter Armor A + public const uint UvlusBanefire = 38284; // Mage Armor H + public const uint MalfusGrimfrost = 38283; // Mage Armor A + public const uint IkfirusTheVile = 37991; // Rogue Armor H + public const uint Yili = 37997; // Rogue Armor A + public const uint VolGuk = 38841; // Shaman Armor H + public const uint Jedebia = 38840; // Shaman Armor A + public const uint HaraggTheUnseen = 38181; // Warlock Armor H + public const uint NibyTheAlmighty = 38182; // Warlock Armor N + public const uint GarroshHellscream = 39372; + public const uint KingVarianWrynn = 39371; + public const uint DeathboundWard = 37007; + public const uint LadyJainaProudmooreQuest = 38606; + public const uint MuradinBronzaBeardQuest = 38607; + public const uint UtherTheLightBringerQuest = 38608; + public const uint LadySylvanasWindrunnerQuest = 38609; + + // Weekly Quests + public const uint InfiltratorMinchar = 38471; + public const uint KorKronLieutenant = 38491; + public const uint SkybreakerLieutenant = 38492; + public const uint RottingFrostGiant10 = 38490; + public const uint RottingFrostGiant25 = 38494; + public const uint AlchemistAdrianna = 38501; + public const uint AlrinTheAgile = 38551; + public const uint InfiltratorMincharBq = 38558; + public const uint MincharBeamStalker = 38557; + public const uint ValithriaDreamwalkerQuest = 38589; + + // Lord Marrowgar + public const uint LordMarrowgar = 36612; + public const uint Coldflame = 36672; + public const uint BoneSpike = 36619; + + // Lady Deathwhisper + public const uint LadyDeathwhisper = 36855; + public const uint CultFanatic = 37890; + public const uint DeformedFanatic = 38135; + public const uint ReanimatedFanatic = 38009; + public const uint CultAdherent = 37949; + public const uint EmpoweredAdherent = 38136; + public const uint ReanimatedAdherent = 38010; + public const uint VengefulShade = 38222; + + // Icecrown Gunship Battle + public const uint MartyrStalkerIGBSaurfang = 38569; + public const uint AllianceGunshipCannon = 36838; + public const uint HordeGunshipCannon = 36839; + public const uint SkybreakerDeckhand = 36970; + public const uint OrgrimsHammerCrew = 36971; + public const uint IGBHighOverlordSaurfang = 36939; + public const uint IGBMuradinBrozebeard = 36948; + public const uint TheSkybreaker = 37540; + public const uint OrgrimsHammer = 37215; + public const uint GunshipHull = 37547; + public const uint TeleportPortal = 37227; + public const uint TeleportExit = 37488; + public const uint SkybreakerSorcerer = 37116; + public const uint SkybreakerRifleman = 36969; + public const uint SkybreakerMortarSoldier = 36978; + public const uint SkybreakerMarine = 36950; + public const uint SkybreakerSergeant = 36961; + public const uint KorKronBattleMage = 37117; + public const uint KorKronAxeThrower = 36968; + public const uint KorKronRocketeer = 36982; + public const uint KorKronReaver = 36957; + public const uint KorKronSergeant = 36960; + public const uint ZafodBoombox = 37184; + public const uint HighCaptainJustinBartlett = 37182; + public const uint SkyReaverKormBlackscar = 37833; + + // Deathbringer Saurfang + public const uint DeathbringerSaurfang = 37813; + public const uint BloodBeast = 38508; + public const uint SeJainaProudmoore = 37188; // Se Means Saurfang Event + public const uint SeMuradinBronzebeard = 37200; + public const uint SeKingVarianWrynn = 37879; + public const uint SeHighOverlordSaurfang = 37187; + public const uint SeKorKronReaver = 37920; + public const uint SeSkybreakerMarine = 37830; + public const uint FrostFreezeTrap = 37744; + + // Festergut + public const uint Festergut = 36626; + public const uint GasDummy = 36659; + public const uint MalleableOozeStalker = 38556; + + // Rotface + public const uint Rotface = 36627; + public const uint OozeSprayStalker = 37986; + public const uint PuddleStalker = 37013; + public const uint UnstableExplosionStalker = 38107; + public const uint VileGasStalker = 38548; + + // Professor Putricide + public const uint ProfessorPutricide = 36678; + public const uint AbominationWingMadScientistStalker = 37824; + public const uint GrowingOozePuddle = 37690; + public const uint GasCloud = 37562; + public const uint VolatileOoze = 37697; + public const uint ChokingGasBomb = 38159; + public const uint TearGasTargetStalker = 38317; + public const uint MutatedAbomination10 = 37672; + public const uint MutatedAbomination25 = 38285; + + // Blood Prince Council + public const uint PrinceKeleseth = 37972; + public const uint PrinceTaldaram = 37973; + public const uint PrinceValanar = 37970; + public const uint BloodOrbController = 38008; + public const uint FloatingTrigger = 30298; + public const uint DarkNucleus = 38369; + public const uint BallOfFlame = 38332; + public const uint BallOfInfernoFlame = 38451; + public const uint KineticBombTarget = 38458; + public const uint KineticBomb = 38454; + public const uint ShockVortex = 38422; + + // Blood-Queen Lana'Thel + public const uint BloodQueenLanaThel = 37955; + + // Frostwing Halls Gauntlet Event + public const uint CrokScourgebane = 37129; + public const uint CaptainArnath = 37122; + public const uint CaptainBrandon = 37123; + public const uint CaptainGrondel = 37124; + public const uint CaptainRupert = 37125; + public const uint CaptainArnathUndead = 37491; + public const uint CaptainBrandonUndead = 37493; + public const uint CaptainGrondelUndead = 37494; + public const uint CaptainRupertUndead = 37495; + public const uint YmirjarBattleMaiden = 37132; + public const uint YmirjarDeathbringer = 38125; + public const uint YmirjarFrostbinder = 37127; + public const uint YmirjarHuntress = 37134; + public const uint YmirjarWarlord = 37133; + public const uint SisterSvalna = 37126; + public const uint ImpalingSpear = 38248; + + // Valithria Dreamwalker + public const uint ValithriaDreamwalker = 36789; + public const uint GreenDragonCombatTrigger = 38752; + public const uint RisenArchmage = 37868; + public const uint BlazingSkeleton = 36791; + public const uint Suppresser = 37863; + public const uint BlisteringZombie = 37934; + public const uint GluttonousAbomination = 37886; + public const uint ManaVoid = 38068; + public const uint ColumnOfFrost = 37918; + public const uint RotWorm = 37907; + public const uint TheLichKingValithria = 16980; + public const uint DreamPortalPreEffect = 38186; + public const uint NightmarePortalPreEffect = 38429; + public const uint DreamPortal = 37945; + public const uint NightmarePortal = 38430; + + // Sindragosa + public const uint Sindragosa = 36853; + public const uint Spinestalker = 37534; + public const uint Rimefang = 37533; + public const uint FrostwardenHandler = 37531; + public const uint FrostwingWhelp = 37532; + public const uint IcyBlast = 38223; + public const uint FrostBomb = 37186; + public const uint IceTomb = 36980; + + // The Lich King + public const uint TheLichKing = 36597; + public const uint HighlordTirionFordringLk = 38995; + public const uint TerenasMenethilFrostmourne = 36823; + public const uint SpiritWarden = 36824; + public const uint TerenasMenethilFrostmourneH = 39217; + public const uint ShamblingHorror = 37698; + public const uint DrudgeGhoul = 37695; + public const uint IceSphere = 36633; + public const uint RagingSpirit = 36701; + public const uint Defile = 38757; + public const uint ValkyrShadowguard = 36609; + public const uint VileSpirit = 37799; + public const uint WickedSpirit = 39190; + public const uint StrangulateVehicle = 36598; + public const uint WorldTrigger = 22515; + public const uint WorldTriggerInfiniteAoi = 36171; + public const uint SpiritBomb = 39189; + public const uint FrostmourneTrigger = 38584; + + // Generic + public const uint InvisibleStalker = 30298; + } + + struct GameObjectIds + { + // ICC Teleporters + public const uint TransporterLichKing = 202223; + public const uint TransporterUpperSpire = 202235; + public const uint TransporterLightsHammer = 202242; + public const uint TransporterRampart = 202243; + public const uint TransporterDeathBringer = 202244; + public const uint TransporterOratory = 202245; + public const uint TransporterSindragosa = 202246; + + // Lower Spire Trash + public const uint SpiritAlarm1 = 201814; + public const uint SpiritAlarm2 = 201815; + public const uint SpiritAlarm3 = 201816; + public const uint SpiritAlarm4 = 201817; + + // Lord Marrogar + public const uint DoodadIcecrownIcewall02 = 201910; + public const uint LordMarrowgarIcewall = 201911; + public const uint LordMarrowgarSEntrance = 201857; + + // Lady Deathwhisper + public const uint OratoryOfTheDamnedEntrance = 201563; + public const uint LadyDeathwhisperElevator = 202220; + + // Icecrown Gunship Battle - Horde raid + public const uint OrgrimsHammer_H = 201812; + public const uint TheSkybreaker_H = 201811; + public const uint GunshipArmory_H_10N = 202178; + public const uint GunshipArmory_H_25N = 202180; + public const uint GunshipArmory_H_10H = 202177; + public const uint GunshipArmory_H_25H = 202179; + + // Icecrown Gunship Battle - Alliance raid + public const uint OrgrimsHammer_A = 201581; + public const uint TheSkybreaker_A = 201580; + public const uint GunshipArmory_A_10N = 201873; + public const uint GunshipArmory_A_25N = 201874; + public const uint GunshipArmory_A_10H = 201872; + public const uint GunshipArmory_A_25H = 201875; + + // Deathbringer Saurfang + public const uint SaurfangSDoor = 201825; + public const uint DeathbringerSCache10n = 202239; + public const uint DeathbringerSCache25n = 202240; + public const uint DeathbringerSCache10h = 202238; + public const uint DeathbringerSCache25h = 202241; + + // Professor Putricide + public const uint OrangePlagueMonsterEntrance = 201371; + public const uint GreenPlagueMonsterEntrance = 201370; + public const uint ScientistAirlockDoorCollision = 201612; + public const uint ScientistAirlockDoorOrange = 201613; + public const uint ScientistAirlockDoorGreen = 201614; + public const uint DoodadIcecrownOrangetubes02 = 201617; + public const uint DoodadIcecrownGreentubes02 = 201618; + public const uint ScientistEntrance = 201372; + public const uint DrinkMe = 201584; + public const uint PlagueSigil = 202182; + + // Blood Prince Council + public const uint CrimsonHallDoor = 201376; + public const uint BloodElfCouncilDoor = 201378; + public const uint BloodElfCouncilDoorRight = 201377; + + // Blood-Queen Lana'Thel + public const uint DoodadIcecrownBloodprinceDoor01 = 201746; + public const uint DoodadIcecrownGrate01 = 201755; + public const uint BloodwingSigil = 202183; + + // Valithria Dreamwalker + public const uint GreenDragonBossEntrance = 201375; + public const uint GreenDragonBossExit = 201374; + public const uint DoodadIcecrownRoostportcullis01 = 201380; + public const uint DoodadIcecrownRoostportcullis02 = 201381; + public const uint DoodadIcecrownRoostportcullis03 = 201382; + public const uint DoodadIcecrownRoostportcullis04 = 201383; + public const uint CacheOfTheDreamwalker10n = 201959; + public const uint CacheOfTheDreamwalker25n = 202339; + public const uint CacheOfTheDreamwalker10h = 202338; + public const uint CacheOfTheDreamwalker25h = 202340; + + // Sindragosa + public const uint SindragosaEntranceDoor = 201373; + public const uint SindragosaShortcutEntranceDoor = 201369; + public const uint SindragosaShortcutExitDoor = 201379; + public const uint IceWall = 202396; + public const uint IceBlock = 201722; + public const uint SigilOfTheFrostwing = 202181; + + // The Lich King + public const uint ArthasPlatform = 202161; + public const uint ArthasPrecipice = 202078; + public const uint DoodadIcecrownThronefrostywind01 = 202188; + public const uint DoodadIcecrownThronefrostyedge01 = 202189; + public const uint DoodadIceshardStanding02 = 202141; + public const uint DoodadIceshardStanding01 = 202142; + public const uint DoodadIceshardStanding03 = 202143; + public const uint DoodadIceshardStanding04 = 202144; + public const uint DoodadIcecrownSnowedgewarning01 = 202190; + public const uint FrozenLavaman = 202436; + public const uint LavamanPillarsChained = 202437; + public const uint LavamanPillarsUnchained = 202438; + } + + struct AchievementCriteriaIds + { + // Lord Marrowgar + public const uint Boned10n = 12775; + public const uint Boned25n = 12962; + public const uint Boned10h = 13393; + public const uint Boned25h = 13394; + + // Rotface + public const uint DancesWithOozes10 = 12984; + public const uint DancesWithOozes25 = 12966; + public const uint DancesWithOozes10H = 12985; + public const uint DancesWithOozes25H = 12983; + + // Professor Putricide + public const uint Nausea10 = 12987; + public const uint Nausea25 = 12968; + public const uint Nausea10H = 12988; + public const uint Nausea25H = 12981; + + // Blood Prince Council + public const uint OrbWhisperer10 = 13033; + public const uint OrbWhisperer25 = 12969; + public const uint OrbWhisperer10H = 13034; + public const uint OrbWhisperer25H = 13032; + + // Blood-Queen Lana'Thel + public const uint KillLanaThel10m = 13340; + public const uint KillLanaThel25m = 13360; + public const uint OnceBittenTwiceShy10 = 12780; + public const uint OnceBittenTwiceShy25 = 13012; + public const uint OnceBittenTwiceShy10V = 13011; + public const uint OnceBittenTwiceShy25V = 13013; + } + + struct WeeklyQuestIds + { + public const uint Deprogramming10 = 24869; + public const uint Deprogramming25 = 24875; + public const uint SecuringTheRamparts10 = 24870; + public const uint SecuringTheRamparts25 = 24877; + public const uint ResidueRendezvous10 = 24873; + public const uint ResidueRendezvous25 = 24878; + public const uint BloodQuickening10 = 24874; + public const uint BloodQuickening25 = 24879; + public const uint RespiteForATornmentedSoul10 = 24872; + public const uint RespiteForATornmentedSoul25 = 24880; + } + + struct Actions + { + // Icecrown Gunship Battle + public const int EnemyGunshipTalk = -369390; + public const int ExitShip = -369391; + + // Festergut + public const int FestergutCombat = -366260; + public const int FestergutGas = -366261; + public const int FestergutDeath = -366262; + + // Rotface + public const int RotfaceCombat = -366270; + public const int RotfaceOoze = -366271; + public const int RotfaceDeath = -366272; + public const int ChangePhase = -366780; + + // Blood-Queen Lana'Thel + public const int KillMinchar = -379550; + + // Frostwing Halls Gauntlet Event + public const int VrykulDeath = 37129; + + // Sindragosa + public const int StartFrostwyrm = -368530; + public const int TriggerAsphyxiation = -368531; + + // The Lich King + public const int RestoreLight = -72262; + public const int FrostmourneIntro = -36823; + + // Sister Svalna + public const int KillCaptain = 1; + public const int StartGauntlet = 2; + public const int ResurrectCaptains = 3; + public const int CaptainDies = 4; + public const int ResetEvent = 5; + } + + struct TeleporterSpells + { + public const uint LIGHT_S_HAMMER_TELEPORT = 70781; + public const uint ORATORY_OF_THE_DAMNED_TELEPORT = 70856; + public const uint RAMPART_OF_SKULLS_TELEPORT = 70857; + public const uint DEATHBRINGER_S_RISE_TELEPORT = 7085; + public const uint UPPER_SPIRE_TELEPORT = 70859; + public const uint FROZEN_THRONE_TELEPORT = 70860; + public const uint SINDRAGOSA_S_LAIR_TELEPORT = 70861; + } + + struct AreaIds + { + public const uint IcecrownCitadel = 4812; + public const uint TheFrozenThrone = 4859; + } +} diff --git a/Scripts/Northrend/IcecrownCitadel/IcecrownCitadelTeleport.cs b/Scripts/Northrend/IcecrownCitadel/IcecrownCitadelTeleport.cs new file mode 100644 index 000000000..c1ff34777 --- /dev/null +++ b/Scripts/Northrend/IcecrownCitadel/IcecrownCitadelTeleport.cs @@ -0,0 +1,106 @@ +/* + * Copyright (C) 2012-2017 CypherCore + * + * 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, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +using Framework.Constants; +using Game.AI; +using Game.DataStorage; +using Game.Entities; +using Game.Maps; +using Game.Scripting; +using Game.Spells; + +namespace Scripts.Northrend.IcecrownCitadel +{ + [Script] + class icecrown_citadel_teleport : GameObjectScript + { + public icecrown_citadel_teleport() : base("icecrown_citadel_teleport") { } + + public class icecrown_citadel_teleportAI : GameObjectAI + { + public icecrown_citadel_teleportAI(GameObject go) : base(go) { } + + public override bool GossipSelect(Player player, uint menuId, uint gossipListId) + { + if (gossipListId >= TeleportSpells.Length) + return false; + + player.PlayerTalkClass.ClearMenus(); + player.CLOSE_GOSSIP_MENU(); + SpellInfo spell = Global.SpellMgr.GetSpellInfo(TeleportSpells[gossipListId]); + if (spell == null) + return false; + + if (player.IsInCombat()) + { + ObjectGuid castId = ObjectGuid.Create(HighGuid.Cast, SpellCastSource.Normal, player.GetMapId(), spell.Id, player.GetMap().GenerateLowGuid(HighGuid.Cast)); + Spell.SendCastResult(player, spell, 0, castId, SpellCastResult.AffectingCombat); + return true; + } + + return true; + } + } + + public override GameObjectAI GetAI(GameObject go) + { + return GetInstanceAI(go, "instance_icecrown_citadel"); + } + + public const uint GOSSIP_SENDER_ICC_PORT = 631; + + static uint[] TeleportSpells = + { + TeleporterSpells.LIGHT_S_HAMMER_TELEPORT, // 0 + TeleporterSpells.ORATORY_OF_THE_DAMNED_TELEPORT, // 1 + 0, // 2 + TeleporterSpells.RAMPART_OF_SKULLS_TELEPORT, // 3 + TeleporterSpells.DEATHBRINGER_S_RISE_TELEPORT, // 4 + TeleporterSpells.UPPER_SPIRE_TELEPORT, // 5 + TeleporterSpells.SINDRAGOSA_S_LAIR_TELEPORT // 6 + }; + } + + [Script] + class at_frozen_throne_teleport : AreaTriggerScript + { + public at_frozen_throne_teleport() : base("at_frozen_throne_teleport") { } + + public override bool OnTrigger(Player player, AreaTriggerRecord areaTrigger, bool entered) + { + if (player.IsInCombat()) + { + SpellInfo spell = Global.SpellMgr.GetSpellInfo(TeleporterSpells.FROZEN_THRONE_TELEPORT); + if (spell == null) + { + ObjectGuid castId = ObjectGuid.Create(HighGuid.Cast, SpellCastSource.Normal, player.GetMapId(), spell.Id, player.GetMap().GenerateLowGuid(HighGuid.Cast)); + Spell.SendCastResult(player, spell, 0, castId, SpellCastResult.AffectingCombat); + } + return true; + } + InstanceScript instance = player.GetInstanceScript(); + if (instance != null) + if (instance.GetBossState(Bosses.ProfessorPutricide) == EncounterState.Done && + instance.GetBossState(Bosses.BloodQueenLanaThel) == EncounterState.Done && + instance.GetBossState(Bosses.Sindragosa) == EncounterState.Done && + instance.GetBossState(Bosses.TheLichKing) != EncounterState.InProgress) + player.CastSpell(player, TeleporterSpells.FROZEN_THRONE_TELEPORT, true); + + return true; + } + } +} diff --git a/Scripts/Northrend/IcecrownCitadel/InstanceIcecrownCitadel.cs b/Scripts/Northrend/IcecrownCitadel/InstanceIcecrownCitadel.cs new file mode 100644 index 000000000..9cba8763a --- /dev/null +++ b/Scripts/Northrend/IcecrownCitadel/InstanceIcecrownCitadel.cs @@ -0,0 +1,1593 @@ +/* + * Copyright (C) 2012-2017 CypherCore + * + * 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, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +using Framework.Constants; +using Framework.IO; +using Game; +using Game.AI; +using Game.Entities; +using Game.Maps; +using Game.Network.Packets; +using Game.Scripting; +using System.Collections.Generic; +using System.Linq; +using System.Text; + +namespace Scripts.Northrend.IcecrownCitadel +{ + [Script] + public class InstanceIcecrownCitadel : InstanceMapScript + { + public struct EventIds + { + public const uint PlayersGunshipSpawn = 22663; + public const uint PlayersGunshipCombat = 22664; + public const uint PlayersGunshipSaurfang = 22665; + public const uint EnemyGunshipCombat = 22860; + public const uint EnemyGunshipDespawn = 22861; + public const uint Quake = 23437; + public const uint SeconsRemorselessWinter = 23507; + public const uint TeleportToFrostmourne = 23617; + } + + public struct TimedEvents + { + public const uint UpdateExecutionTime = 1; + public const uint QuakeShatter = 2; + public const uint RebuildPlatform = 3; + public const uint RespawnGunship = 4; + } + + public static BossBoundaryEntry[] boundaries = + { + new BossBoundaryEntry(Bosses.LordMarrowgar, new CircleBoundary(new Position(-428.0f, 2211.0f), 95.0)), + new BossBoundaryEntry(Bosses.LordMarrowgar, new RectangleBoundary(-430.0f, -330.0f, 2110.0f, 2310.0f) ), + new BossBoundaryEntry(Bosses.LadyDeathwhisper, new RectangleBoundary(-670.0f, -520.0f, 2145.0f, 2280.0f) ), + new BossBoundaryEntry(Bosses.DeathbringerSaurfang, new RectangleBoundary(-565.0f, -465.0f, 2160.0f, 2260.0f) ), + + new BossBoundaryEntry(Bosses.Rotface, new RectangleBoundary(4385.0f, 4505.0f, 3082.0f, 3195.0f) ), + new BossBoundaryEntry(Bosses.Festergut, new RectangleBoundary(4205.0f, 4325.0f, 3082.0f, 3195.0f) ), + new BossBoundaryEntry(Bosses.ProfessorPutricide, new ParallelogramBoundary(new Position(4356.0f, 3290.0f), new Position(4435.0f, 3194.0f), new Position(4280.0f, 3194.0f)) ), + new BossBoundaryEntry(Bosses.ProfessorPutricide, new RectangleBoundary(4280.0f, 4435.0f, 3150.0f, 4360.0f) ), + + new BossBoundaryEntry(Bosses.BloodPrinceCouncil, new EllipseBoundary(new Position(4660.95f, 2769.194f), 85.0, 60.0) ), + new BossBoundaryEntry(Bosses.BloodQueenLanaThel, new CircleBoundary(new Position(4595.93f, 2769.365f), 64.0) ), + + new BossBoundaryEntry(Bosses.SisterSvalna, new RectangleBoundary(4291.0f, 4423.0f, 2438.0f, 2653.0f) ), + new BossBoundaryEntry(Bosses.ValithriaDreamwalker, new RectangleBoundary(4112.5f, 4293.5f, 2385.0f, 2585.0f) ), + new BossBoundaryEntry(Bosses.Sindragosa, new EllipseBoundary(new Position(4408.6f, 2484.0f), 100.0, 75.0) ) + }; + + public static DoorData[] doorData = + { + new DoorData(GameObjectIds.LordMarrowgarSEntrance, Bosses.LordMarrowgar, DoorType.Room ), + new DoorData(GameObjectIds.LordMarrowgarIcewall, Bosses.LordMarrowgar, DoorType.Passage ), + new DoorData(GameObjectIds.DoodadIcecrownIcewall02, Bosses.LordMarrowgar, DoorType.Passage ), + new DoorData(GameObjectIds.OratoryOfTheDamnedEntrance, Bosses.LadyDeathwhisper, DoorType.Room ), + new DoorData(GameObjectIds.SaurfangSDoor, Bosses.DeathbringerSaurfang, DoorType.Passage ), + new DoorData(GameObjectIds.OrangePlagueMonsterEntrance, Bosses.Festergut, DoorType.Room ), + new DoorData(GameObjectIds.GreenPlagueMonsterEntrance, Bosses.Rotface, DoorType.Room ), + new DoorData(GameObjectIds.ScientistEntrance, Bosses.ProfessorPutricide, DoorType.Room ), + new DoorData(GameObjectIds.CrimsonHallDoor, Bosses.BloodPrinceCouncil, DoorType.Room ), + new DoorData(GameObjectIds.BloodElfCouncilDoor, Bosses.BloodPrinceCouncil, DoorType.Passage ), + new DoorData(GameObjectIds.BloodElfCouncilDoorRight, Bosses.BloodPrinceCouncil, DoorType.Passage ), + new DoorData(GameObjectIds.DoodadIcecrownBloodprinceDoor01, Bosses.BloodQueenLanaThel, DoorType.Room ), + new DoorData(GameObjectIds.DoodadIcecrownGrate01, Bosses.BloodQueenLanaThel, DoorType.Passage ), + new DoorData(GameObjectIds.GreenDragonBossEntrance, Bosses.SisterSvalna, DoorType.Passage ), + new DoorData(GameObjectIds.GreenDragonBossEntrance, Bosses.ValithriaDreamwalker, DoorType.Room ), + new DoorData(GameObjectIds.GreenDragonBossExit, Bosses.ValithriaDreamwalker, DoorType.Passage ), + new DoorData(GameObjectIds.DoodadIcecrownRoostportcullis01, Bosses.ValithriaDreamwalker, DoorType.SpawnHole), + new DoorData(GameObjectIds.DoodadIcecrownRoostportcullis02, Bosses.ValithriaDreamwalker, DoorType.SpawnHole), + new DoorData(GameObjectIds.DoodadIcecrownRoostportcullis03, Bosses.ValithriaDreamwalker, DoorType.SpawnHole), + new DoorData(GameObjectIds.DoodadIcecrownRoostportcullis04, Bosses.ValithriaDreamwalker, DoorType.SpawnHole), + new DoorData(GameObjectIds.SindragosaEntranceDoor, Bosses.Sindragosa, DoorType.Room ), + new DoorData(GameObjectIds.SindragosaShortcutEntranceDoor, Bosses.Sindragosa, DoorType.Passage ), + new DoorData(GameObjectIds.SindragosaShortcutExitDoor, Bosses.Sindragosa, DoorType.Passage ), + new DoorData(GameObjectIds.IceWall , Bosses.Sindragosa, DoorType.Room ), + new DoorData(GameObjectIds.IceWall, Bosses.Sindragosa, DoorType.Room ), + new DoorData(0, 0, DoorType.Room ), // END + }; + + public class WeeklyQuest + { + public WeeklyQuest(uint entry, uint id1, uint id2) + { + creatureEntry = entry; + questId[0] = id1; + questId[1] = id2; + } + + public uint creatureEntry; + public uint[] questId = new uint[2]; // 10 and 25 man versions + } + + public static WeeklyQuest[] WeeklyQuestData = + { + new WeeklyQuest(CreatureIds.InfiltratorMinchar, WeeklyQuestIds.Deprogramming10, WeeklyQuestIds.Deprogramming25 ), // Deprogramming + new WeeklyQuest(CreatureIds.KorKronLieutenant, WeeklyQuestIds.SecuringTheRamparts10, WeeklyQuestIds.SecuringTheRamparts25 ), // Securing the Ramparts + new WeeklyQuest(CreatureIds.RottingFrostGiant10, WeeklyQuestIds.SecuringTheRamparts10, WeeklyQuestIds.SecuringTheRamparts25 ), // Securing the Ramparts + new WeeklyQuest(CreatureIds.RottingFrostGiant25, WeeklyQuestIds.SecuringTheRamparts10, WeeklyQuestIds.SecuringTheRamparts25 ), // Securing the Ramparts + new WeeklyQuest(CreatureIds.AlchemistAdrianna, WeeklyQuestIds.ResidueRendezvous10, WeeklyQuestIds.ResidueRendezvous25 ), // Residue Rendezvous + new WeeklyQuest(CreatureIds.AlrinTheAgile, WeeklyQuestIds.BloodQuickening10, WeeklyQuestIds.BloodQuickening25 ), // Blood Quickening + new WeeklyQuest(CreatureIds.InfiltratorMincharBq, WeeklyQuestIds.BloodQuickening10, WeeklyQuestIds.BloodQuickening25 ), // Blood Quickening + new WeeklyQuest(CreatureIds.MincharBeamStalker, WeeklyQuestIds.BloodQuickening10, WeeklyQuestIds.BloodQuickening25 ), // Blood Quickening + new WeeklyQuest(CreatureIds.ValithriaDreamwalkerQuest, WeeklyQuestIds.RespiteForATornmentedSoul10, WeeklyQuestIds.RespiteForATornmentedSoul25), // Respite for a Tormented Soul + }; + + // NPCs spawned at Light's Hammer on Lich King dead + static Position JainaSpawnPos = new Position(-48.65278f, 2211.026f, 27.98586f, 3.124139f); + static Position MuradinSpawnPos = new Position(-47.34549f, 2208.087f, 27.98586f, 3.106686f); + static Position UtherSpawnPos = new Position(-26.58507f, 2211.524f, 30.19898f, 3.124139f); + static Position SylvanasSpawnPos = new Position(-41.45833f, 2222.891f, 27.98586f, 3.647738f); + + public InstanceIcecrownCitadel() : base("instance_icecrown_citadel", 631) { } + + class instance_icecrown_citadel_InstanceMapScript : InstanceScript + { + public instance_icecrown_citadel_InstanceMapScript(InstanceMap map) : base(map) + { + SetHeaders("IC"); + SetBossNumber(Bosses.MaxEncounters); + LoadBossBoundaries(boundaries); + LoadDoorData(doorData); + //HeroicAttempts = IccConst.MaxHeroicAttempts; + IsBonedEligible = true; + IsOozeDanceEligible = true; + IsNauseaEligible = true; + IsOrbWhispererEligible = true; + ColdflameJetsState = EncounterState.NotStarted; + UpperSpireTeleporterActiveState = EncounterState.NotStarted; + BloodQuickeningState = EncounterState.NotStarted; + } + + // A function to help reduce the number of lines for teleporter management. + void SetTeleporterState(GameObject go, bool usable) + { + if (usable) + { + go.RemoveFlag(GameObjectFields.Flags, GameObjectFlags.NotSelectable); + go.SetGoState(GameObjectState.Active); + } + else + { + go.SetFlag(GameObjectFields.Flags, GameObjectFlags.NotSelectable); + go.SetGoState(GameObjectState.Ready); + } + } + + public override void FillInitialWorldStates(InitWorldStates packet) + { + packet.AddState(WorldStates.ShowTimer, BloodQuickeningState == EncounterState.InProgress ? 1 : 0); + packet.AddState(WorldStates.ExecutionTime, BloodQuickeningMinutes); + //packet.AddState(WorldStates.ShowAttempts, instance.IsHeroic() ? 1 : 0); + //packet.AddState(WorldStates.AttemptsRemaining, (int)HeroicAttempts); + //packet.AddState(WorldStates.AttemptsMax, (int)IccConst.MaxHeroicAttempts); + } + + public override void OnPlayerEnter(Player player) + { + if (TeamInInstance == 0) + TeamInInstance = player.GetTeam(); + + if (GetBossState(Bosses.LadyDeathwhisper) == EncounterState.Done && GetBossState(Bosses.GunshipBattle) != EncounterState.Done) + SpawnGunship(); + } + + public override void OnCreatureCreate(Creature creature) + { + if (TeamInInstance == 0) + { + var players = instance.GetPlayers(); + if (!players.Empty()) + { + Player player = players.FirstOrDefault(); + if (player) + TeamInInstance = player.GetTeam(); + } + } + + switch (creature.GetEntry()) + { + case CreatureIds.KorKronGeneral: + if (TeamInInstance == Team.Alliance) + creature.UpdateEntry(CreatureIds.AllianceCommander); + break; + case CreatureIds.KorKronLieutenant: + if (TeamInInstance == Team.Alliance) + creature.UpdateEntry(CreatureIds.SkybreakerLieutenant); + break; + case CreatureIds.Tortunok: + if (TeamInInstance == Team.Alliance) + creature.UpdateEntry(CreatureIds.AlanaMoonstrike); + break; + case CreatureIds.GerardoTheSuave: + if (TeamInInstance == Team.Alliance) + creature.UpdateEntry(CreatureIds.TalanMoonstrike); + break; + case CreatureIds.UvlusBanefire: + if (TeamInInstance == Team.Alliance) + creature.UpdateEntry(CreatureIds.MalfusGrimfrost); + break; + case CreatureIds.IkfirusTheVile: + if (TeamInInstance == Team.Alliance) + creature.UpdateEntry(CreatureIds.Yili); + break; + case CreatureIds.VolGuk: + if (TeamInInstance == Team.Alliance) + creature.UpdateEntry(CreatureIds.Jedebia); + break; + case CreatureIds.HaraggTheUnseen: + if (TeamInInstance == Team.Alliance) + creature.UpdateEntry(CreatureIds.NibyTheAlmighty); + break; + case CreatureIds.GarroshHellscream: + if (TeamInInstance == Team.Alliance) + creature.UpdateEntry(CreatureIds.KingVarianWrynn); + break; + case CreatureIds.DeathbringerSaurfang: + DeathbringerSaurfangGUID = creature.GetGUID(); + break; + case CreatureIds.AllianceGunshipCannon: + case CreatureIds.HordeGunshipCannon: + creature.SetControlled(true, UnitState.Root); + break; + case CreatureIds.SeHighOverlordSaurfang: + if (TeamInInstance == Team.Alliance) + creature.UpdateEntry(CreatureIds.SeMuradinBronzebeard, creature.GetCreatureData()); + goto case CreatureIds.SeMuradinBronzebeard; + // no break; + case CreatureIds.SeMuradinBronzebeard: + DeathbringerSaurfangEventGUID = creature.GetGUID(); + creature.LastUsedScriptID = creature.GetScriptId(); + break; + case CreatureIds.SeKorKronReaver: + if (TeamInInstance == Team.Alliance) + creature.UpdateEntry(CreatureIds.SeSkybreakerMarine); + break; + case CreatureIds.Festergut: + FestergutGUID = creature.GetGUID(); + break; + case CreatureIds.Rotface: + RotfaceGUID = creature.GetGUID(); + break; + case CreatureIds.ProfessorPutricide: + ProfessorPutricideGUID = creature.GetGUID(); + break; + case CreatureIds.PrinceKeleseth: + BloodCouncilGUIDs[0] = creature.GetGUID(); + break; + case CreatureIds.PrinceTaldaram: + BloodCouncilGUIDs[1] = creature.GetGUID(); + break; + case CreatureIds.PrinceValanar: + BloodCouncilGUIDs[2] = creature.GetGUID(); + break; + case CreatureIds.BloodOrbController: + BloodCouncilControllerGUID = creature.GetGUID(); + break; + case CreatureIds.BloodQueenLanaThel: + BloodQueenLanaThelGUID = creature.GetGUID(); + break; + case CreatureIds.CrokScourgebane: + CrokScourgebaneGUID = creature.GetGUID(); + break; + // we can only do this because there are no gaps in their entries + case CreatureIds.CaptainArnath: + case CreatureIds.CaptainBrandon: + case CreatureIds.CaptainGrondel: + case CreatureIds.CaptainRupert: + CrokCaptainGUIDs[creature.GetEntry() - CreatureIds.CaptainArnath] = creature.GetGUID(); + break; + case CreatureIds.SisterSvalna: + SisterSvalnaGUID = creature.GetGUID(); + break; + case CreatureIds.ValithriaDreamwalker: + ValithriaDreamwalkerGUID = creature.GetGUID(); + break; + case CreatureIds.TheLichKingValithria: + ValithriaLichKingGUID = creature.GetGUID(); + break; + case CreatureIds.GreenDragonCombatTrigger: + ValithriaTriggerGUID = creature.GetGUID(); + break; + case CreatureIds.Sindragosa: + SindragosaGUID = creature.GetGUID(); + break; + case CreatureIds.Spinestalker: + SpinestalkerGUID = creature.GetGUID(); + break; + case CreatureIds.Rimefang: + RimefangGUID = creature.GetGUID(); + break; + case CreatureIds.InvisibleStalker: + // Teleporter visual at center + if (creature.GetExactDist2d(4357.052f, 2769.421f) < 10.0f) + creature.CastSpell(creature, InstanceSpells.ArthasTeleporterCeremony, false); + break; + case CreatureIds.TheLichKing: + TheLichKingGUID = creature.GetGUID(); + break; + case CreatureIds.HighlordTirionFordringLk: + HighlordTirionFordringGUID = creature.GetGUID(); + break; + case CreatureIds.TerenasMenethilFrostmourne: + case CreatureIds.TerenasMenethilFrostmourneH: + TerenasMenethilGUID = creature.GetGUID(); + break; + case CreatureIds.WickedSpirit: + // Remove corpse as soon as it dies (and respawn 10 seconds later) + creature.SetCorpseDelay(0); + creature.SetReactState(ReactStates.Passive); + break; + default: + break; + } + } + + public override void OnCreatureRemove(Creature creature) + { + if (creature.GetEntry() == CreatureIds.Sindragosa) + SindragosaGUID.Clear(); + } + + // Weekly quest spawn prevention + public override uint GetCreatureEntry(ulong guidLow, CreatureData data) + { + uint entry = data.id; + switch (entry) + { + case CreatureIds.InfiltratorMinchar: + case CreatureIds.KorKronLieutenant: + case CreatureIds.AlchemistAdrianna: + case CreatureIds.AlrinTheAgile: + case CreatureIds.InfiltratorMincharBq: + case CreatureIds.MincharBeamStalker: + case CreatureIds.ValithriaDreamwalkerQuest: + { + for (byte questIndex = 0; questIndex < IccConst.WeeklyNPCs; ++questIndex) + { + if (WeeklyQuestData[questIndex].creatureEntry == entry) + { + byte diffIndex = (byte)((int)instance.GetSpawnMode() & 1); + if (!Global.PoolMgr.IsSpawnedObject(WeeklyQuestData[questIndex].questId[diffIndex])) + entry = 0; + break; + } + } + break; + } + case CreatureIds.HordeGunshipCannon: + case CreatureIds.OrgrimsHammerCrew: + case CreatureIds.SkyReaverKormBlackscar: + if (TeamInInstance == Team.Alliance) + return 0; + break; + case CreatureIds.AllianceGunshipCannon: + case CreatureIds.SkybreakerDeckhand: + case CreatureIds.HighCaptainJustinBartlett: + if (TeamInInstance == Team.Horde) + return 0; + break; + case CreatureIds.ZafodBoombox: + GameObjectTemplate go = Global.ObjectMgr.GetGameObjectTemplate(GameObjectIds.TheSkybreaker_A); + if (go != null) + if ((TeamInInstance == Team.Alliance && data.mapid == go.MoTransport.SpawnMap) || + (TeamInInstance == Team.Horde && data.mapid != go.MoTransport.SpawnMap)) + return entry; + return 0; + case CreatureIds.IGBMuradinBrozebeard: + if ((TeamInInstance == Team.Alliance && data.posX > 10.0f) || + (TeamInInstance == Team.Horde && data.posX < 10.0f)) + return entry; + return 0; + default: + break; + } + + return entry; + } + + public override uint GetGameObjectEntry(ulong spawnId, uint entry) + { + switch (entry) + { + case GameObjectIds.GunshipArmory_H_10N: + case GameObjectIds.GunshipArmory_H_25N: + case GameObjectIds.GunshipArmory_H_10H: + case GameObjectIds.GunshipArmory_H_25H: + if (TeamInInstance == Team.Alliance) + return 0; + break; + case GameObjectIds.GunshipArmory_A_10N: + case GameObjectIds.GunshipArmory_A_25N: + case GameObjectIds.GunshipArmory_A_10H: + case GameObjectIds.GunshipArmory_A_25H: + if (TeamInInstance == Team.Horde) + return 0; + break; + default: + break; + } + + return entry; + } + + public override void OnUnitDeath(Unit unit) + { + Creature creature = unit.ToCreature(); + if (!creature) + return; + + switch (creature.GetEntry()) + { + case CreatureIds.YmirjarBattleMaiden: + case CreatureIds.YmirjarDeathbringer: + case CreatureIds.YmirjarFrostbinder: + case CreatureIds.YmirjarHuntress: + case CreatureIds.YmirjarWarlord: + Creature crok = instance.GetCreature(CrokScourgebaneGUID); + if (crok) + crok.GetAI().SetGUID(creature.GetGUID(), Actions.VrykulDeath); + break; + case CreatureIds.FrostwingWhelp: + if (FrostwyrmGUIDs.Empty()) + return; + + if (creature.GetAI().GetData(1/*DATA_FROSTWYRM_OWNER*/) == DataTypes.Spinestalker) + { + SpinestalkerTrash.Remove(creature.GetSpawnId()); + if (SpinestalkerTrash.Empty()) + { + Creature spinestalk = instance.GetCreature(SpinestalkerGUID); + if (spinestalk) + spinestalk.GetAI().DoAction(Actions.StartFrostwyrm); + } + } + else + { + RimefangTrash.Remove(creature.GetSpawnId()); + if (RimefangTrash.Empty()) + { + Creature spinestalk = instance.GetCreature(RimefangGUID); + if (spinestalk) + spinestalk.GetAI().DoAction(Actions.StartFrostwyrm); + } + } + break; + case CreatureIds.Rimefang: + case CreatureIds.Spinestalker: + { + if (instance.IsHeroic() && HeroicAttempts == 0) + return; + + if (GetBossState(Bosses.Sindragosa) == EncounterState.Done) + return; + + FrostwyrmGUIDs.Remove(creature.GetSpawnId()); + if (FrostwyrmGUIDs.Empty()) + { + instance.LoadGrid(Sindragosa.SindragosaSpawnPos.GetPositionX(), Sindragosa.SindragosaSpawnPos.GetPositionY()); + Creature boss = instance.SummonCreature(CreatureIds.Sindragosa, Sindragosa.SindragosaSpawnPos); + if (boss) + boss.GetAI().DoAction(Actions.StartFrostwyrm); + } + break; + } + default: + break; + } + } + + public override void OnGameObjectCreate(GameObject go) + { + switch (go.GetEntry()) + { + case GameObjectIds.DoodadIcecrownIcewall02: + case GameObjectIds.LordMarrowgarIcewall: + case GameObjectIds.LordMarrowgarSEntrance: + case GameObjectIds.OratoryOfTheDamnedEntrance: + case GameObjectIds.OrangePlagueMonsterEntrance: + case GameObjectIds.GreenPlagueMonsterEntrance: + case GameObjectIds.ScientistEntrance: + case GameObjectIds.CrimsonHallDoor: + case GameObjectIds.BloodElfCouncilDoor: + case GameObjectIds.BloodElfCouncilDoorRight: + case GameObjectIds.DoodadIcecrownBloodprinceDoor01: + case GameObjectIds.DoodadIcecrownGrate01: + case GameObjectIds.GreenDragonBossEntrance: + case GameObjectIds.GreenDragonBossExit: + case GameObjectIds.DoodadIcecrownRoostportcullis02: + case GameObjectIds.DoodadIcecrownRoostportcullis03: + case GameObjectIds.SindragosaEntranceDoor: + case GameObjectIds.SindragosaShortcutEntranceDoor: + case GameObjectIds.SindragosaShortcutExitDoor: + case GameObjectIds.IceWall: + AddDoor(go, true); + break; + // these 2 gates are functional only on 25man modes + case GameObjectIds.DoodadIcecrownRoostportcullis01: + case GameObjectIds.DoodadIcecrownRoostportcullis04: + if (instance.Is25ManRaid()) + AddDoor(go, true); + break; + case GameObjectIds.LadyDeathwhisperElevator: + LadyDeathwisperElevatorGUID = go.GetGUID(); + if (GetBossState(Bosses.LadyDeathwhisper) == EncounterState.Done) + go.SetTransportState(GameObjectState.TransportActive); + break; + case GameObjectIds.TheSkybreaker_H: + case GameObjectIds.OrgrimsHammer_A: + EnemyGunshipGUID = go.GetGUID(); + break; + case GameObjectIds.GunshipArmory_H_10N: + case GameObjectIds.GunshipArmory_H_25N: + case GameObjectIds.GunshipArmory_H_10H: + case GameObjectIds.GunshipArmory_H_25H: + case GameObjectIds.GunshipArmory_A_10N: + case GameObjectIds.GunshipArmory_A_25N: + case GameObjectIds.GunshipArmory_A_10H: + case GameObjectIds.GunshipArmory_A_25H: + GunshipArmoryGUID = go.GetGUID(); + break; + case GameObjectIds.SaurfangSDoor: + DeathbringerSaurfangDoorGUID = go.GetGUID(); + AddDoor(go, true); + break; + case GameObjectIds.DeathbringerSCache10n: + case GameObjectIds.DeathbringerSCache25n: + case GameObjectIds.DeathbringerSCache10h: + case GameObjectIds.DeathbringerSCache25h: + DeathbringersCacheGUID = go.GetGUID(); + break; + case GameObjectIds.TransporterLichKing: + TeleporterLichKingGUID = go.GetGUID(); + if (GetBossState(Bosses.ProfessorPutricide) == EncounterState.Done && GetBossState(Bosses.BloodQueenLanaThel) == EncounterState.Done && GetBossState(Bosses.Sindragosa) == EncounterState.Done) + go.SetGoState(GameObjectState.Active); + break; + case GameObjectIds.TransporterUpperSpire: + TeleporterUpperSpireGUID = go.GetGUID(); + if (GetBossState(Bosses.DeathbringerSaurfang) != EncounterState.Done || GetData(DataTypes.UpperSpireTeleAct) != (uint)EncounterState.Done) + SetTeleporterState(go, false); + else + SetTeleporterState(go, true); + break; + case GameObjectIds.TransporterLightsHammer: + TeleporterLightsHammerGUID = go.GetGUID(); + SetTeleporterState(go, GetBossState(Bosses.LordMarrowgar) == EncounterState.Done); + break; + case GameObjectIds.TransporterRampart: + TeleporterRampartsGUID = go.GetGUID(); + SetTeleporterState(go, GetBossState(Bosses.LadyDeathwhisper) == EncounterState.Done); + break; + case GameObjectIds.TransporterDeathBringer: + TeleporterDeathBringerGUID = go.GetGUID(); + SetTeleporterState(go, GetBossState(Bosses.GunshipBattle) == EncounterState.Done); + break; + case GameObjectIds.TransporterOratory: + TeleporterOratoryGUID = go.GetGUID(); + SetTeleporterState(go, GetBossState(Bosses.LordMarrowgar) == EncounterState.Done); + break; + case GameObjectIds.TransporterSindragosa: + TeleporterSindragosaGUID = go.GetGUID(); + SetTeleporterState(go, GetBossState(Bosses.ValithriaDreamwalker) == EncounterState.Done); + break; + case GameObjectIds.PlagueSigil: + PlagueSigilGUID = go.GetGUID(); + if (GetBossState(Bosses.ProfessorPutricide) == EncounterState.Done) + HandleGameObject(PlagueSigilGUID, false, go); + break; + case GameObjectIds.BloodwingSigil: + BloodwingSigilGUID = go.GetGUID(); + if (GetBossState(Bosses.BloodQueenLanaThel) == EncounterState.Done) + HandleGameObject(BloodwingSigilGUID, false, go); + break; + case GameObjectIds.SigilOfTheFrostwing: + FrostwingSigilGUID = go.GetGUID(); + if (GetBossState(Bosses.Sindragosa) == EncounterState.Done) + HandleGameObject(FrostwingSigilGUID, false, go); + break; + case GameObjectIds.ScientistAirlockDoorCollision: + PutricideCollisionGUID = go.GetGUID(); + if (GetBossState(Bosses.Festergut) == EncounterState.Done && GetBossState(Bosses.Rotface) == EncounterState.Done) + HandleGameObject(PutricideCollisionGUID, true, go); + break; + case GameObjectIds.ScientistAirlockDoorOrange: + PutricideGateGUIDs[0] = go.GetGUID(); + if (GetBossState(Bosses.Festergut) == EncounterState.Done && GetBossState(Bosses.Rotface) == EncounterState.Done) + go.SetGoState(GameObjectState.ActiveAlternative); + else if (GetBossState(Bosses.Festergut) == EncounterState.Done) + HandleGameObject(PutricideGateGUIDs[1], false, go); + break; + case GameObjectIds.ScientistAirlockDoorGreen: + PutricideGateGUIDs[1] = go.GetGUID(); + if (GetBossState(Bosses.Rotface) == EncounterState.Done && GetBossState(Bosses.Festergut) == EncounterState.Done) + go.SetGoState(GameObjectState.ActiveAlternative); + else if (GetBossState(Bosses.Rotface) == EncounterState.Done) + HandleGameObject(PutricideGateGUIDs[1], false, go); + break; + case GameObjectIds.DoodadIcecrownOrangetubes02: + PutricidePipeGUIDs[0] = go.GetGUID(); + if (GetBossState(Bosses.Festergut) == EncounterState.Done) + HandleGameObject(PutricidePipeGUIDs[0], true, go); + break; + case GameObjectIds.DoodadIcecrownGreentubes02: + PutricidePipeGUIDs[1] = go.GetGUID(); + if (GetBossState(Bosses.Rotface) == EncounterState.Done) + HandleGameObject(PutricidePipeGUIDs[1], true, go); + break; + case GameObjectIds.DrinkMe: + PutricideTableGUID = go.GetGUID(); + break; + case GameObjectIds.CacheOfTheDreamwalker10n: + case GameObjectIds.CacheOfTheDreamwalker25n: + case GameObjectIds.CacheOfTheDreamwalker10h: + case GameObjectIds.CacheOfTheDreamwalker25h: + Creature valithria = instance.GetCreature(ValithriaDreamwalkerGUID); + if (valithria) + go.SetLootRecipient(valithria.GetLootRecipient(), valithria.GetLootRecipientGroup()); + go.RemoveFlag(GameObjectFields.Flags, GameObjectFlags.Locked | GameObjectFlags.NotSelectable | GameObjectFlags.NoDespawn); + break; + case GameObjectIds.ArthasPlatform: + ArthasPlatformGUID = go.GetGUID(); + break; + case GameObjectIds.ArthasPrecipice: + ArthasPrecipiceGUID = go.GetGUID(); + break; + case GameObjectIds.DoodadIcecrownThronefrostyedge01: + FrozenThroneEdgeGUID = go.GetGUID(); + break; + case GameObjectIds.DoodadIcecrownThronefrostywind01: + FrozenThroneWindGUID = go.GetGUID(); + break; + case GameObjectIds.DoodadIcecrownSnowedgewarning01: + FrozenThroneWarningGUID = go.GetGUID(); + break; + case GameObjectIds.FrozenLavaman: + FrozenBolvarGUID = go.GetGUID(); + if (GetBossState(Bosses.TheLichKing) == EncounterState.Done) + go.SetRespawnTime(7 * Time.Day); + break; + case GameObjectIds.LavamanPillarsChained: + PillarsChainedGUID = go.GetGUID(); + if (GetBossState(Bosses.TheLichKing) == EncounterState.Done) + go.SetRespawnTime(7 * Time.Day); + break; + case GameObjectIds.LavamanPillarsUnchained: + PillarsUnchainedGUID = go.GetGUID(); + if (GetBossState(Bosses.TheLichKing) == EncounterState.Done) + go.SetRespawnTime(7 * Time.Day); + break; + default: + break; + } + } + + public override void OnGameObjectRemove(GameObject go) + { + switch (go.GetEntry()) + { + case GameObjectIds.DoodadIcecrownIcewall02: + case GameObjectIds.LordMarrowgarIcewall: + case GameObjectIds.LordMarrowgarSEntrance: + case GameObjectIds.OratoryOfTheDamnedEntrance: + case GameObjectIds.SaurfangSDoor: + case GameObjectIds.OrangePlagueMonsterEntrance: + case GameObjectIds.GreenPlagueMonsterEntrance: + case GameObjectIds.ScientistEntrance: + case GameObjectIds.CrimsonHallDoor: + case GameObjectIds.BloodElfCouncilDoor: + case GameObjectIds.BloodElfCouncilDoorRight: + case GameObjectIds.DoodadIcecrownBloodprinceDoor01: + case GameObjectIds.DoodadIcecrownGrate01: + case GameObjectIds.GreenDragonBossEntrance: + case GameObjectIds.GreenDragonBossExit: + case GameObjectIds.DoodadIcecrownRoostportcullis01: + case GameObjectIds.DoodadIcecrownRoostportcullis02: + case GameObjectIds.DoodadIcecrownRoostportcullis03: + case GameObjectIds.DoodadIcecrownRoostportcullis04: + case GameObjectIds.SindragosaEntranceDoor: + case GameObjectIds.SindragosaShortcutEntranceDoor: + case GameObjectIds.SindragosaShortcutExitDoor: + case GameObjectIds.IceWall: + AddDoor(go, false); + break; + case GameObjectIds.TheSkybreaker_A: + case GameObjectIds.OrgrimsHammer_H: + GunshipGUID.Clear(); + break; + default: + break; + } + } + + public override uint GetData(uint type) + { + switch (type) + { + case DataTypes.SindragosaFrostwyrms: + return (uint)FrostwyrmGUIDs.Count; + case DataTypes.Spinestalker: + return (uint)SpinestalkerTrash.Count; + case DataTypes.Rimefang: + return (uint)RimefangTrash.Count; + case DataTypes.ColdflameJets: + return (uint)ColdflameJetsState; + case DataTypes.UpperSpireTeleAct: + return (uint)UpperSpireTeleporterActiveState; + case DataTypes.TeamInInstance: + return (uint)TeamInInstance; + case DataTypes.BloodQuickeningState: + return (uint)BloodQuickeningState; + case DataTypes.HeroicAttempts: + return HeroicAttempts; + default: + break; + } + + return 0; + } + + public override ObjectGuid GetGuidData(uint type) + { + switch (type) + { + case Bosses.GunshipBattle: + return GunshipGUID; + case DataTypes.EnemyGunship: + return EnemyGunshipGUID; + case Bosses.DeathbringerSaurfang: + return DeathbringerSaurfangGUID; + case DataTypes.SaurfangEventNpc: + return DeathbringerSaurfangEventGUID; + case GameObjectIds.SaurfangSDoor: + return DeathbringerSaurfangDoorGUID; + case Bosses.Festergut: + return FestergutGUID; + case Bosses.Rotface: + return RotfaceGUID; + case Bosses.ProfessorPutricide: + return ProfessorPutricideGUID; + case DataTypes.PutricideTable: + return PutricideTableGUID; + case DataTypes.PrinceKelesethGuid: + return BloodCouncilGUIDs[0]; + case DataTypes.PrinceTaldaramGuid: + return BloodCouncilGUIDs[1]; + case DataTypes.PrinceValanarGuid: + return BloodCouncilGUIDs[2]; + case DataTypes.BloodPrincesControl: + return BloodCouncilControllerGUID; + case Bosses.BloodQueenLanaThel: + return BloodQueenLanaThelGUID; + case DataTypes.CrokScourgebane: + return CrokScourgebaneGUID; + case DataTypes.CaptainArnath: + case DataTypes.CaptainBrandon: + case DataTypes.CaptainGrondel: + case DataTypes.CaptainRupert: + return CrokCaptainGUIDs[type - DataTypes.CaptainArnath]; + case Bosses.SisterSvalna: + return SisterSvalnaGUID; + case Bosses.ValithriaDreamwalker: + return ValithriaDreamwalkerGUID; + case DataTypes.ValithriaLichKing: + return ValithriaLichKingGUID; + case DataTypes.ValithriaTrigger: + return ValithriaTriggerGUID; + case Bosses.Sindragosa: + return SindragosaGUID; + case DataTypes.Spinestalker: + return SpinestalkerGUID; + case DataTypes.Rimefang: + return RimefangGUID; + case Bosses.TheLichKing: + return TheLichKingGUID; + case DataTypes.HighlordTirionFordring: + return HighlordTirionFordringGUID; + case DataTypes.ArthasPlatform: + return ArthasPlatformGUID; + case DataTypes.TerenasMenethil: + return TerenasMenethilGUID; + default: + break; + } + + return ObjectGuid.Empty; + } + + public override bool SetBossState(uint type, EncounterState state) + { + if (!base.SetBossState(type, state)) + return false; + + switch (type) + { + case Bosses.LordMarrowgar: + if (state == EncounterState.Done) + { + GameObject teleporter = instance.GetGameObject(TeleporterLightsHammerGUID); + if (teleporter) + SetTeleporterState(teleporter, true); + + teleporter = instance.GetGameObject(TeleporterOratoryGUID); + if (teleporter) + SetTeleporterState(teleporter, true); + } + break; + case Bosses.LadyDeathwhisper: + if (state == EncounterState.Done) + { + GameObject teleporter = instance.GetGameObject(TeleporterRampartsGUID); + if (teleporter) + SetTeleporterState(teleporter, true); + + GameObject elevator = instance.GetGameObject(LadyDeathwisperElevatorGUID); + if (elevator) + elevator.SetTransportState(GameObjectState.TransportActive); + + SpawnGunship(); + } + break; + case Bosses.GunshipBattle: + if (state == EncounterState.Done) + { + GameObject teleporter = instance.GetGameObject(TeleporterDeathBringerGUID); + if (teleporter) + SetTeleporterState(teleporter, true); + + GameObject loot = instance.GetGameObject(GunshipArmoryGUID); + if (loot) + loot.RemoveFlag(GameObjectFields.Flags, GameObjectFlags.Locked | GameObjectFlags.NotSelectable | GameObjectFlags.NoDespawn); + } + else if (state == EncounterState.Fail) + _events.ScheduleEvent(TimedEvents.RespawnGunship, 30000); + break; + case Bosses.DeathbringerSaurfang: + switch (state) + { + case EncounterState.Done: + { + GameObject loot = instance.GetGameObject(DeathbringersCacheGUID); + if (loot) + { + Creature deathbringer = instance.GetCreature(DeathbringerSaurfangGUID); + if (deathbringer) + loot.SetLootRecipient(deathbringer.GetLootRecipient(), deathbringer.GetLootRecipientGroup()); + loot.RemoveFlag(GameObjectFields.Flags, GameObjectFlags.Locked | GameObjectFlags.NotSelectable | GameObjectFlags.NoDespawn); + } + + GameObject teleporter = instance.GetGameObject(TeleporterUpperSpireGUID); + if (teleporter) + SetTeleporterState(teleporter, true); + + teleporter = instance.GetGameObject(TeleporterDeathBringerGUID); + if (teleporter) + SetTeleporterState(teleporter, true); + break; + } + case EncounterState.NotStarted: + { + GameObject teleporter = instance.GetGameObject(TeleporterDeathBringerGUID); + if (teleporter) + SetTeleporterState(teleporter, true); + break; + } + case EncounterState.InProgress: + { + GameObject teleporter = instance.GetGameObject(TeleporterDeathBringerGUID); + if (teleporter) + SetTeleporterState(teleporter, false); + break; + } + default: + break; + } + break; + case Bosses.Festergut: + if (state == EncounterState.Done) + { + if (GetBossState(Bosses.Rotface) == EncounterState.Done) + { + HandleGameObject(PutricideCollisionGUID, true); + GameObject go = instance.GetGameObject(PutricideGateGUIDs[0]); + if (go) + go.SetGoState(GameObjectState.ActiveAlternative); + + go = instance.GetGameObject(PutricideGateGUIDs[1]); + if (go) + go.SetGoState(GameObjectState.ActiveAlternative); + } + else + HandleGameObject(PutricideGateGUIDs[0], false); + HandleGameObject(PutricidePipeGUIDs[0], true); + } + break; + case Bosses.Rotface: + if (state == EncounterState.Done) + { + if (GetBossState(Bosses.Festergut) == EncounterState.Done) + { + HandleGameObject(PutricideCollisionGUID, true); + GameObject go = instance.GetGameObject(PutricideGateGUIDs[0]); + if (go) + go.SetGoState(GameObjectState.ActiveAlternative); + + go = instance.GetGameObject(PutricideGateGUIDs[1]); + if (go) + go.SetGoState(GameObjectState.ActiveAlternative); + } + else + HandleGameObject(PutricideGateGUIDs[1], false); + HandleGameObject(PutricidePipeGUIDs[1], true); + } + break; + case Bosses.ProfessorPutricide: + HandleGameObject(PlagueSigilGUID, state != EncounterState.Done); + if (state == EncounterState.Done) + CheckLichKingAvailability(); + if (instance.IsHeroic()) + { + if (state == EncounterState.Fail && HeroicAttempts != 0) + { + --HeroicAttempts; + DoUpdateWorldState(WorldStates.AttemptsRemaining, HeroicAttempts); + if (HeroicAttempts == 0) + { + Creature putricide = instance.GetCreature(ProfessorPutricideGUID); + if (putricide) + putricide.DespawnOrUnsummon(); + } + } + } + break; + case Bosses.BloodQueenLanaThel: + HandleGameObject(BloodwingSigilGUID, state != EncounterState.Done); + if (state == EncounterState.Done) + CheckLichKingAvailability(); + if (instance.IsHeroic()) + { + if (state == EncounterState.Fail && HeroicAttempts != 0) + { + --HeroicAttempts; + DoUpdateWorldState(WorldStates.AttemptsRemaining, HeroicAttempts); + if (HeroicAttempts == 0) + { + Creature bq = instance.GetCreature(BloodQueenLanaThelGUID); + if (bq) + bq.DespawnOrUnsummon(); + } + } + } + break; + case Bosses.ValithriaDreamwalker: + if (state == EncounterState.Done) + { + if (Global.PoolMgr.IsSpawnedObject(WeeklyQuestData[8].questId[(int)instance.GetSpawnMode() & 1])) + instance.SummonCreature(CreatureIds.ValithriaDreamwalkerQuest, ValithriaDreamwalker.ValithriaSpawnPos); + + GameObject teleporter = instance.GetGameObject(TeleporterSindragosaGUID); + if (teleporter) + SetTeleporterState(teleporter, true); + } + break; + case Bosses.Sindragosa: + HandleGameObject(FrostwingSigilGUID, state != EncounterState.Done); + if (state == EncounterState.Done) + CheckLichKingAvailability(); + if (instance.IsHeroic()) + { + if (state == EncounterState.Fail && HeroicAttempts != 0) + { + --HeroicAttempts; + DoUpdateWorldState(WorldStates.AttemptsRemaining, HeroicAttempts); + if (HeroicAttempts == 0) + { + Creature sindra = instance.GetCreature(SindragosaGUID); + if (sindra) + sindra.DespawnOrUnsummon(); + } + } + } + break; + case Bosses.TheLichKing: + { + // set the platform as active object to dramatically increase visibility range + // note: "active" gameobjects do not block grid unloading + GameObject precipice = instance.GetGameObject(ArthasPrecipiceGUID); + if (precipice) + precipice.setActive(state == EncounterState.InProgress); + + GameObject platform = instance.GetGameObject(ArthasPlatformGUID); + if (platform) + platform.setActive(state == EncounterState.InProgress); + + if (instance.IsHeroic()) + { + if (state == EncounterState.Fail && HeroicAttempts != 0) + { + --HeroicAttempts; + DoUpdateWorldState(WorldStates.AttemptsRemaining, HeroicAttempts); + if (HeroicAttempts == 0) + { + Creature theLichKing = instance.GetCreature(TheLichKingGUID); + if (theLichKing) + theLichKing.DespawnOrUnsummon(); + } + } + } + + if (state == EncounterState.Done) + { + GameObject bolvar = instance.GetGameObject(FrozenBolvarGUID); + if (bolvar) + bolvar.SetRespawnTime(7 * Time.Day); + + GameObject pillars = instance.GetGameObject(PillarsChainedGUID); + if (pillars) + pillars.SetRespawnTime(7 * Time.Day); + + pillars = instance.GetGameObject(PillarsUnchainedGUID); + if (pillars) + pillars.SetRespawnTime(7 * Time.Day); + + instance.SummonCreature(CreatureIds.LadyJainaProudmooreQuest, JainaSpawnPos); + instance.SummonCreature(CreatureIds.MuradinBronzaBeardQuest, MuradinSpawnPos); + instance.SummonCreature(CreatureIds.UtherTheLightBringerQuest, UtherSpawnPos); + instance.SummonCreature(CreatureIds.LadySylvanasWindrunnerQuest, SylvanasSpawnPos); + } + break; + } + default: + break; + } + return true; + } + + void SpawnGunship() + { + if (GunshipGUID.IsEmpty()) + { + SetBossState(Bosses.GunshipBattle, EncounterState.NotStarted); + uint gunshipEntry = TeamInInstance == Team.Horde ? GameObjectIds.OrgrimsHammer_H : GameObjectIds.TheSkybreaker_A; + Transport gunship = Global.TransportMgr.CreateTransport(gunshipEntry, 0, instance); + if (gunship) + GunshipGUID = gunship.GetGUID(); + } + } + + public override void SetData(uint type, uint data) + { + switch (type) + { + case DataTypes.BonedAchievement: + IsBonedEligible = data != 0 ? true : false; + break; + case DataTypes.OozeDanceAchievement: + IsOozeDanceEligible = data != 0 ? true : false; + break; + case DataTypes.NauseaAchievement: + IsNauseaEligible = data != 0 ? true : false; + break; + case DataTypes.OrbWhispererAchievement: + IsOrbWhispererEligible = data != 0 ? true : false; + break; + case DataTypes.SindragosaFrostwyrms: + FrostwyrmGUIDs.Add(data); + break; + case DataTypes.Spinestalker: + SpinestalkerTrash.Add(data); + break; + case DataTypes.Rimefang: + RimefangTrash.Add(data); + break; + case DataTypes.ColdflameJets: + ColdflameJetsState = (EncounterState)data; + if (ColdflameJetsState == EncounterState.Done) + SaveToDB(); + break; + case DataTypes.BloodQuickeningState: + { + // skip if nothing changes + if (BloodQuickeningState == (EncounterState)data) + break; + + // 5 is the index of Blood Quickening + if (!Global.PoolMgr.IsSpawnedObject(WeeklyQuestData[5].questId[(int)instance.GetSpawnMode() & 1])) + break; + + switch ((EncounterState)data) + { + case EncounterState.InProgress: + _events.ScheduleEvent(TimedEvents.UpdateExecutionTime, 60000); + BloodQuickeningMinutes = 30; + DoUpdateWorldState(WorldStates.ShowTimer, 1); + DoUpdateWorldState(WorldStates.ExecutionTime, BloodQuickeningMinutes); + break; + case EncounterState.Done: + _events.CancelEvent(TimedEvents.UpdateExecutionTime); + BloodQuickeningMinutes = 0; + DoUpdateWorldState(WorldStates.ShowTimer, 0); + break; + default: + break; + } + + BloodQuickeningState = (EncounterState)data; + SaveToDB(); + break; + } + case DataTypes.UpperSpireTeleAct: + UpperSpireTeleporterActiveState = (EncounterState)data; + if (UpperSpireTeleporterActiveState == EncounterState.Done) + { + GameObject go = instance.GetGameObject(TeleporterUpperSpireGUID); + if (go) + SetTeleporterState(go, true); + SaveToDB(); + } + break; + default: + break; + } + } + + public override bool CheckAchievementCriteriaMeet(uint criteria_id, Player source, Unit target, uint miscvalue1) + { + switch (criteria_id) + { + case AchievementCriteriaIds.Boned10n: + case AchievementCriteriaIds.Boned25n: + case AchievementCriteriaIds.Boned10h: + case AchievementCriteriaIds.Boned25h: + return IsBonedEligible; + case AchievementCriteriaIds.DancesWithOozes10: + case AchievementCriteriaIds.DancesWithOozes25: + case AchievementCriteriaIds.DancesWithOozes10H: + case AchievementCriteriaIds.DancesWithOozes25H: + return IsOozeDanceEligible; + case AchievementCriteriaIds.Nausea10: + case AchievementCriteriaIds.Nausea25: + case AchievementCriteriaIds.Nausea10H: + case AchievementCriteriaIds.Nausea25H: + return IsNauseaEligible; + case AchievementCriteriaIds.OrbWhisperer10: + case AchievementCriteriaIds.OrbWhisperer25: + case AchievementCriteriaIds.OrbWhisperer10H: + case AchievementCriteriaIds.OrbWhisperer25H: + return IsOrbWhispererEligible; + // Only one criteria for both modes, need to do it like this + case AchievementCriteriaIds.KillLanaThel10m: + case AchievementCriteriaIds.OnceBittenTwiceShy10: + case AchievementCriteriaIds.OnceBittenTwiceShy10V: + return instance.ToInstanceMap().GetMaxPlayers() == 10; + case AchievementCriteriaIds.KillLanaThel25m: + case AchievementCriteriaIds.OnceBittenTwiceShy25: + case AchievementCriteriaIds.OnceBittenTwiceShy25V: + return instance.ToInstanceMap().GetMaxPlayers() == 25; + default: + break; + } + + return false; + } + + public override bool CheckRequiredBosses(uint bossId, Player player = null) + { + if (player && player.GetSession().HasPermission(RBACPermissions.SkipCheckInstanceRequiredBosses)) + return true; + + switch (bossId) + { + case Bosses.TheLichKing: + if (!CheckPlagueworks(bossId)) + return false; + if (!CheckCrimsonHalls(bossId)) + return false; + if (!CheckFrostwingHalls(bossId)) + return false; + break; + case Bosses.Sindragosa: + case Bosses.ValithriaDreamwalker: + if (!CheckFrostwingHalls(bossId)) + return false; + break; + case Bosses.BloodQueenLanaThel: + case Bosses.BloodPrinceCouncil: + if (!CheckCrimsonHalls(bossId)) + return false; + break; + case Bosses.Festergut: + case Bosses.Rotface: + case Bosses.ProfessorPutricide: + if (!CheckPlagueworks(bossId)) + return false; + break; + default: + break; + } + + if (!CheckLowerSpire(bossId)) + return false; + + return true; + } + + bool CheckPlagueworks(uint bossId) + { + switch (bossId) + { + case Bosses.TheLichKing: + if (GetBossState(Bosses.ProfessorPutricide) != EncounterState.Done) + return false; + goto case Bosses.ProfessorPutricide; + // no break + case Bosses.ProfessorPutricide: + if (GetBossState(Bosses.Festergut) != EncounterState.Done || GetBossState(Bosses.Rotface) != EncounterState.Done) + return false; + break; + default: + break; + } + + return true; + } + + bool CheckCrimsonHalls(uint bossId) + { + switch (bossId) + { + case Bosses.TheLichKing: + if (GetBossState(Bosses.BloodQueenLanaThel) != EncounterState.Done) + return false; + goto case Bosses.BloodQueenLanaThel; + // no break + case Bosses.BloodQueenLanaThel: + if (GetBossState(Bosses.BloodPrinceCouncil) != EncounterState.Done) + return false; + break; + default: + break; + } + + return true; + } + + bool CheckFrostwingHalls(uint bossId) + { + switch (bossId) + { + case Bosses.TheLichKing: + if (GetBossState(Bosses.Sindragosa) != EncounterState.Done) + return false; + goto case Bosses.Sindragosa; + // no break + case Bosses.Sindragosa: + if (GetBossState(Bosses.ValithriaDreamwalker) != EncounterState.Done) + return false; + break; + default: + break; + } + + return true; + } + + bool CheckLowerSpire(uint bossId) + { + switch (bossId) + { + case Bosses.TheLichKing: + case Bosses.Sindragosa: + case Bosses.BloodQueenLanaThel: + case Bosses.ProfessorPutricide: + case Bosses.ValithriaDreamwalker: + case Bosses.BloodPrinceCouncil: + case Bosses.Rotface: + case Bosses.Festergut: + if (GetBossState(Bosses.DeathbringerSaurfang) != EncounterState.Done) + return false; + goto case Bosses.DeathbringerSaurfang; + // no break + case Bosses.DeathbringerSaurfang: + if (GetBossState(Bosses.GunshipBattle) != EncounterState.Done) + return false; + goto case Bosses.GunshipBattle; + // no break + case Bosses.GunshipBattle: + if (GetBossState(Bosses.LadyDeathwhisper) != EncounterState.Done) + return false; + goto case Bosses.LadyDeathwhisper; + // no break + case Bosses.LadyDeathwhisper: + if (GetBossState(Bosses.LordMarrowgar) != EncounterState.Done) + return false; + break; + } + + return true; + } + + void CheckLichKingAvailability() + { + if (GetBossState(Bosses.ProfessorPutricide) == EncounterState.Done && GetBossState(Bosses.BloodQueenLanaThel) == EncounterState.Done && GetBossState(Bosses.Sindragosa) == EncounterState.Done) + { + GameObject teleporter = instance.GetGameObject(TeleporterLichKingGUID); + if (teleporter) + { + teleporter.SetGoState(GameObjectState.Active); + + List stalkers = new List(); + teleporter.GetCreatureListWithEntryInGrid(stalkers, CreatureIds.InvisibleStalker, 100.0f); + if (stalkers.Empty()) + return; + + stalkers.Sort(new ObjectDistanceOrderPred(teleporter)); + stalkers.FirstOrDefault().CastSpell((Unit)null, InstanceSpells.ArthasTeleporterCeremony, false); + stalkers.RemoveAt(0); + foreach (var creature in stalkers) + creature.GetAI().Reset(); + } + } + } + + public override void WriteSaveDataMore(StringBuilder data) + { + data.AppendFormat("{0} {1} {2} {3} {4}", HeroicAttempts, ColdflameJetsState, BloodQuickeningState, BloodQuickeningMinutes, UpperSpireTeleporterActiveState); + } + + public override void ReadSaveDataMore(StringArguments data) + { + HeroicAttempts = data.NextUInt32(); + + EncounterState temp = (EncounterState)data.NextUInt32(); + if (temp == EncounterState.InProgress) + ColdflameJetsState = EncounterState.NotStarted; + else + ColdflameJetsState = temp != 0 ? EncounterState.Done : EncounterState.NotStarted; + + temp = (EncounterState)data.NextUInt32(); + BloodQuickeningState = temp != 0 ? EncounterState.Done : EncounterState.NotStarted; // DONE means finished (not success/fail) + BloodQuickeningMinutes = data.NextUInt16(); + + temp = (EncounterState)data.NextUInt32(); + UpperSpireTeleporterActiveState = temp != 0 ? EncounterState.Done : EncounterState.NotStarted; + } + + public override void Update(uint diff) + { + if (BloodQuickeningState != EncounterState.InProgress && GetBossState(Bosses.TheLichKing) != EncounterState.InProgress && GetBossState(Bosses.GunshipBattle) != EncounterState.Fail) + return; + + _events.Update(diff); + + _events.ExecuteEvents(eventId => + { + switch (eventId) + { + case TimedEvents.UpdateExecutionTime: + { + --BloodQuickeningMinutes; + if (BloodQuickeningMinutes != 0) + { + _events.ScheduleEvent(TimedEvents.UpdateExecutionTime, 60000); + DoUpdateWorldState(WorldStates.ShowTimer, 1); + DoUpdateWorldState(WorldStates.ExecutionTime, BloodQuickeningMinutes); + } + else + { + BloodQuickeningState = EncounterState.Done; + DoUpdateWorldState(WorldStates.ShowTimer, 0); + Creature bq = instance.GetCreature(BloodQueenLanaThelGUID); + if (bq) + bq.GetAI().DoAction(Actions.KillMinchar); + } + SaveToDB(); + break; + } + case TimedEvents.QuakeShatter: + { + GameObject platform = instance.GetGameObject(ArthasPlatformGUID); + if (platform) + platform.SetDestructibleState(GameObjectDestructibleState.Damaged); + + GameObject edge = instance.GetGameObject(FrozenThroneEdgeGUID); + if (edge) + edge.SetGoState(GameObjectState.Active); + + GameObject wind = instance.GetGameObject(FrozenThroneWindGUID); + if (wind) + wind.SetGoState(GameObjectState.Ready); + + GameObject warning = instance.GetGameObject(FrozenThroneWarningGUID); + if (warning) + warning.SetGoState(GameObjectState.Ready); + + Creature theLichKing = instance.GetCreature(TheLichKingGUID); + if (theLichKing) + theLichKing.GetAI().DoAction(Actions.RestoreLight); + break; + } + case TimedEvents.RebuildPlatform: + GameObject platform1 = instance.GetGameObject(ArthasPlatformGUID); + if (platform1) + platform1.SetDestructibleState(GameObjectDestructibleState.Rebuilding); + + GameObject edge1 = instance.GetGameObject(FrozenThroneEdgeGUID); + if (edge1) + edge1.SetGoState(GameObjectState.Ready); + + GameObject wind1 = instance.GetGameObject(FrozenThroneWindGUID); + if (wind1) + wind1.SetGoState(GameObjectState.Active); + break; + case TimedEvents.RespawnGunship: + SpawnGunship(); + break; + default: + break; + } + }); + } + + public override void ProcessEvent(WorldObject source, uint eventId) + { + switch (eventId) + { + case EventIds.EnemyGunshipDespawn: + if (GetBossState(Bosses.GunshipBattle) == EncounterState.Done) + source.AddObjectToRemoveList(); + break; + case EventIds.EnemyGunshipCombat: + Creature captain = source.FindNearestCreature(TeamInInstance == Team.Horde ? CreatureIds.IGBHighOverlordSaurfang : CreatureIds.IGBMuradinBrozebeard, 100.0f); + if (captain) + captain.GetAI().DoAction(Actions.EnemyGunshipTalk); + goto case EventIds.PlayersGunshipSpawn; + // no break; + case EventIds.PlayersGunshipSpawn: + case EventIds.PlayersGunshipCombat: + GameObject go = source.ToGameObject(); + if (go) + { + Transport transport = go.ToTransport(); + if (transport) + transport.EnableMovement(false); + } + break; + case EventIds.PlayersGunshipSaurfang: + Creature _captain = source.FindNearestCreature(TeamInInstance == Team.Horde ? CreatureIds.IGBHighOverlordSaurfang : CreatureIds.IGBMuradinBrozebeard, 100.0f); + if (_captain) + _captain.GetAI().DoAction(Actions.ExitShip); + GameObject _go = source.ToGameObject(); + if (_go) + { + Transport transport = _go.ToTransport(); + if (transport) + transport.EnableMovement(false); + } + break; + case EventIds.Quake: + GameObject warning = instance.GetGameObject(FrozenThroneWarningGUID); + if (warning) + warning.SetGoState(GameObjectState.Active); + _events.ScheduleEvent(TimedEvents.QuakeShatter, 5000); + break; + case EventIds.SeconsRemorselessWinter: + GameObject platform = instance.GetGameObject(ArthasPlatformGUID); + if (platform) + { + platform.SetDestructibleState(GameObjectDestructibleState.Destroyed); + _events.ScheduleEvent(TimedEvents.RebuildPlatform, 1500); + } + break; + case EventIds.TeleportToFrostmourne: // Harvest Soul (normal mode) + Creature terenas = instance.SummonCreature(CreatureIds.TerenasMenethilFrostmourne, TheLichKing.TerenasSpawn, null, 63000); + if (terenas) + { + terenas.GetAI().DoAction(Actions.FrostmourneIntro); + List triggers = new List(); + terenas.GetCreatureListWithEntryInGrid(triggers, CreatureIds.WorldTriggerInfiniteAoi, 100.0f); + if (!triggers.Empty()) + { + triggers.Sort(new ObjectDistanceOrderPred(terenas, false)); + Unit visual = triggers.FirstOrDefault(); + visual.CastSpell(visual, InstanceSpells.FrostmourneTeleportVisual, true); + } + Creature warden = instance.SummonCreature(CreatureIds.SpiritWarden, TheLichKing.SpiritWardenSpawn, null, 63000); + if (warden) + { + terenas.GetAI().AttackStart(warden); + warden.AddThreat(terenas, 300000.0f); + } + } + break; + } + } + + ObjectGuid LadyDeathwisperElevatorGUID; + ObjectGuid GunshipGUID; + ObjectGuid EnemyGunshipGUID; + ObjectGuid GunshipArmoryGUID; + ObjectGuid DeathbringerSaurfangGUID; + ObjectGuid DeathbringerSaurfangDoorGUID; + ObjectGuid DeathbringerSaurfangEventGUID; // Muradin Bronzebeard or High Overlord Saurfang + ObjectGuid DeathbringersCacheGUID; + ObjectGuid TeleporterLichKingGUID; + ObjectGuid TeleporterUpperSpireGUID; + ObjectGuid TeleporterLightsHammerGUID; + ObjectGuid TeleporterRampartsGUID; + ObjectGuid TeleporterDeathBringerGUID; + ObjectGuid TeleporterOratoryGUID; + ObjectGuid TeleporterSindragosaGUID; + ObjectGuid PlagueSigilGUID; + ObjectGuid BloodwingSigilGUID; + ObjectGuid FrostwingSigilGUID; + ObjectGuid[] PutricidePipeGUIDs = new ObjectGuid[2]; + ObjectGuid[] PutricideGateGUIDs = new ObjectGuid[2]; + ObjectGuid PutricideCollisionGUID; + ObjectGuid FestergutGUID; + ObjectGuid RotfaceGUID; + ObjectGuid ProfessorPutricideGUID; + ObjectGuid PutricideTableGUID; + ObjectGuid[] BloodCouncilGUIDs = new ObjectGuid[3]; + ObjectGuid BloodCouncilControllerGUID; + ObjectGuid BloodQueenLanaThelGUID; + ObjectGuid CrokScourgebaneGUID; + ObjectGuid[] CrokCaptainGUIDs = new ObjectGuid[4]; + ObjectGuid SisterSvalnaGUID; + ObjectGuid ValithriaDreamwalkerGUID; + ObjectGuid ValithriaLichKingGUID; + ObjectGuid ValithriaTriggerGUID; + ObjectGuid SindragosaGUID; + ObjectGuid SpinestalkerGUID; + ObjectGuid RimefangGUID; + ObjectGuid TheLichKingGUID; + ObjectGuid HighlordTirionFordringGUID; + ObjectGuid TerenasMenethilGUID; + ObjectGuid ArthasPlatformGUID; + ObjectGuid ArthasPrecipiceGUID; + ObjectGuid FrozenThroneEdgeGUID; + ObjectGuid FrozenThroneWindGUID; + ObjectGuid FrozenThroneWarningGUID; + ObjectGuid FrozenBolvarGUID; + ObjectGuid PillarsChainedGUID; + ObjectGuid PillarsUnchainedGUID; + Team TeamInInstance; + EncounterState ColdflameJetsState; + EncounterState UpperSpireTeleporterActiveState; + List FrostwyrmGUIDs = new List(); + List SpinestalkerTrash = new List(); + List RimefangTrash = new List(); + EncounterState BloodQuickeningState; + uint HeroicAttempts; + ushort BloodQuickeningMinutes; + bool IsBonedEligible; + bool IsOozeDanceEligible; + bool IsNauseaEligible; + bool IsOrbWhispererEligible; + } + + public override InstanceScript GetInstanceScript(InstanceMap map) + { + return new instance_icecrown_citadel_InstanceMapScript(map); + } + + public static T GetInstanceAI(Creature creature) where T : CreatureAI + { + return GetInstanceAI(creature, "instance_icecrown_citadel"); + } + } +} diff --git a/Scripts/Northrend/IcecrownCitadel/LadyDeathwhisper.cs b/Scripts/Northrend/IcecrownCitadel/LadyDeathwhisper.cs new file mode 100644 index 000000000..fbb36f636 --- /dev/null +++ b/Scripts/Northrend/IcecrownCitadel/LadyDeathwhisper.cs @@ -0,0 +1,1107 @@ +/* + * Copyright (C) 2012-2017 CypherCore + * + * 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, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +using Framework.Constants; +using Framework.Dynamic; +using Game; +using Game.AI; +using Game.DataStorage; +using Game.Entities; +using Game.Groups; +using Game.Maps; +using Game.Scripting; +using Game.Spells; +using System.Collections.Generic; +using System.Linq; + +namespace Scripts.Northrend.IcecrownCitadel +{ + struct LadyTexts + { + // Lady Deathwhisper + public const uint SAY_INTRO_1 = 0; + public const uint SAY_INTRO_2 = 1; + public const uint SAY_INTRO_3 = 2; + public const uint SAY_INTRO_4 = 3; + public const uint SAY_INTRO_5 = 4; + public const uint SAY_INTRO_6 = 5; + public const uint SAY_INTRO_7 = 6; + public const uint SAY_AGGRO = 7; + public const uint SAY_PHASE_2 = 8; + public const uint EMOTE_PHASE_2 = 9; + public const uint SAY_DOMINATE_MIND = 10; + public const uint SAY_DARK_EMPOWERMENT = 11; + public const uint SAY_DARK_TRANSFORMATION = 12; + public const uint SAY_ANIMATE_DEAD = 13; + public const uint SAY_KILL = 14; + public const uint SAY_BERSERK = 15; + public const uint SAY_DEATH = 16; + + // Darnavan + public const uint SAY_DARNAVAN_AGGRO = 0; + public const uint SAY_DARNAVAN_RESCUED = 1; + } + + struct LadySpells + { + // Lady Deathwhisper + public const uint MANA_BARRIER = 70842; + public const uint SHADOW_BOLT = 71254; + public const uint DEATH_AND_DECAY = 71001; + public const uint DOMINATE_MIND_H = 71289; + public const uint FROSTBOLT = 71420; + public const uint FROSTBOLT_VOLLEY = 72905; + public const uint TOUCH_OF_INSIGNIFICANCE = 71204; + public const uint SUMMON_SHADE = 71363; + public const uint SHADOW_CHANNELING = 43897; // Prefight; during intro + public const uint DARK_TRANSFORMATION_T = 70895; + public const uint DARK_EMPOWERMENT_T = 70896; + public const uint DARK_MARTYRDOM_T = 70897; + + // Achievement + public const uint FULL_HOUSE = 72827; // does not exist in dbc but still can be used for criteria check + + // Both Adds + public const uint TELEPORT_VISUAL = 41236; + + // Fanatics + public const uint DARK_TRANSFORMATION = 70900; + public const uint NECROTIC_STRIKE = 70659; + public const uint SHADOW_CLEAVE = 70670; + public const uint VAMPIRIC_MIGHT = 70674; + public const uint FANATIC_S_DETERMINATION = 71235; + public const uint DARK_MARTYRDOM_FANATIC = 71236; + + // Adherents + public const uint DARK_EMPOWERMENT = 70901; + public const uint FROST_FEVER = 67767; + public const uint DEATHCHILL_BOLT = 70594; + public const uint DEATHCHILL_BLAST = 70906; + public const uint CURSE_OF_TORPOR = 71237; + public const uint SHORUD_OF_THE_OCCULT = 70768; + public const uint ADHERENT_S_DETERMINATION = 71234; + public const uint DARK_MARTYRDOM_ADHERENT = 70903; + + // Vengeful Shade + public const uint VENGEFUL_BLAST = 71544; + public const uint VENGEFUL_BLAST_PASSIVE = 71494; + public const uint VENGEFUL_BLAST_25N = 72010; + public const uint VENGEFUL_BLAST_10H = 72011; + public const uint VENGEFUL_BLAST_25H = 72012; + + // Darnavan + public const uint BLADESTORM = 65947; + public const uint CHARGE = 65927; + public const uint INTIMIDATING_SHOUT = 65930; + public const uint MORTAL_STRIKE = 65926; + public const uint SHATTERING_THROW = 65940; + public const uint SUNDER_ARMOR = 65936; + } + + struct LadyEventTypes + { + // Lady Deathwhisper + public const uint INTRO_2 = 1; + public const uint INTRO_3 = 2; + public const uint INTRO_4 = 3; + public const uint INTRO_5 = 4; + public const uint INTRO_6 = 5; + public const uint INTRO_7 = 6; + public const uint BERSERK = 7; + public const uint DEATH_AND_DECAY = 8; + public const uint DOMINATE_MIND_H = 9; + + // Phase 1 only + public const uint P1_SUMMON_WAVE = 10; + public const uint P1_SHADOW_BOLT = 11; + public const uint P1_EMPOWER_CULTIST = 12; + public const uint P1_REANIMATE_CULTIST = 13; + + // Phase 2 only + public const uint P2_SUMMON_WAVE = 14; + public const uint P2_FROSTBOLT = 15; + public const uint P2_FROSTBOLT_VOLLEY = 16; + public const uint P2_TOUCH_OF_INSIGNIFICANCE = 17; + public const uint P2_SUMMON_SHADE = 18; + + // Shared adds events + public const uint CULTIST_DARK_MARTYRDOM = 19; + + // Cult Fanatic + public const uint FANATIC_NECROTIC_STRIKE = 20; + public const uint FANATIC_SHADOW_CLEAVE = 21; + public const uint FANATIC_VAMPIRIC_MIGHT = 22; + + // Cult Adherent + public const uint ADHERENT_FROST_FEVER = 23; + public const uint ADHERENT_DEATHCHILL = 24; + public const uint ADHERENT_CURSE_OF_TORPOR = 25; + public const uint ADHERENT_SHORUD_OF_THE_OCCULT = 26; + + // Darnavan + public const uint DARNAVAN_BLADESTORM = 27; + public const uint DARNAVAN_CHARGE = 28; + public const uint DARNAVAN_INTIMIDATING_SHOUT = 29; + public const uint DARNAVAN_MORTAL_STRIKE = 30; + public const uint DARNAVAN_SHATTERING_THROW = 31; + public const uint DARNAVAN_SUNDER_ARMOR = 32; + } + + struct DeprogrammingData + { + public const uint NPC_DARNAVAN_10 = 38472; + public const uint NPC_DARNAVAN_25 = 38485; + public const uint NPC_DARNAVAN_CREDIT_10 = 39091; + public const uint NPC_DARNAVAN_CREDIT_25 = 39092; + + public const int ACTION_COMPLETE_QUEST = -384720; + public const uint POINT_DESPAWN = 384721; + } + + struct LadyConst + { + public const byte PhaseAll = 0; + public const byte PhaseIntro = 1; + public const byte PhaseOne = 2; + public const byte PhaseTwo = 3; + + public const uint GUIDCultist = 1; + + public static uint[] SummonEntries = { CreatureIds.CultFanatic, CreatureIds.CultAdherent }; + + public static Position[] SummonPositions = + { + new Position(-578.7066f, 2154.167f, 51.01529f, 1.692969f), // 1 Left Door 1 (Cult Fanatic) + new Position(-598.9028f, 2155.005f, 51.01530f, 1.692969f), // 2 Left Door 2 (Cult Adherent) + new Position(-619.2864f, 2154.460f, 51.01530f, 1.692969f), // 3 Left Door 3 (Cult Fanatic) + new Position(-578.6996f, 2269.856f, 51.01529f, 4.590216f), // 4 Right Door 1 (Cult Adherent) + new Position(-598.9688f, 2269.264f, 51.01529f, 4.590216f), // 5 Right Door 2 (Cult Fanatic) + new Position(-619.4323f, 2268.523f, 51.01530f, 4.590216f), // 6 Right Door 3 (Cult Adherent) + new Position(-524.2480f, 2211.920f, 62.90960f, 3.141592f), // 7 Upper (Random Cultist) + }; + } + + class DaranavanMoveEvent : BasicEvent + { + public DaranavanMoveEvent(Creature darnavan) + { + _darnavan = darnavan; + } + + public override bool Execute(ulong time, uint diff) + { + _darnavan.GetMotionMaster().MovePoint(DeprogrammingData.POINT_DESPAWN, LadyConst.SummonPositions[6]); + return true; + } + + Creature _darnavan; + } + + [Script] + class boss_lady_deathwhisper : CreatureScript + { + public boss_lady_deathwhisper() : base("boss_lady_deathwhisper") { } + + public class boss_lady_deathwhisperAI : BossAI + { + public boss_lady_deathwhisperAI(Creature creature) + : base(creature, Bosses.LadyDeathwhisper) + { + _dominateMindCount = RaidMode(0, 1, 1, 3); + _introDone = false; + + } + + public override void Reset() + { + _Reset(); + me.SetPower(PowerType.Mana, me.GetMaxPower(PowerType.Mana)); + _events.SetPhase(LadyConst.PhaseOne); + _waveCounter = 0; + _nextVengefulShadeTargetGUID.Clear(); + _darnavanGUID.Clear(); + DoCast(me, LadySpells.SHADOW_CHANNELING); + me.RemoveAurasDueToSpell(InstanceSpells.Berserk); + me.RemoveAurasDueToSpell(LadySpells.MANA_BARRIER); + me.ApplySpellImmune(0, SpellImmunity.State, AuraType.ModTaunt, false); + me.ApplySpellImmune(0, SpellImmunity.Effect, SpellEffectName.AttackMe, false); + } + + public override void MoveInLineOfSight(Unit who) + { + if (!_introDone && me.IsWithinDistInMap(who, 110.0f)) + { + _introDone = true; + Talk(LadyTexts.SAY_INTRO_1); + _events.SetPhase(LadyConst.PhaseIntro); + _events.ScheduleEvent(LadyEventTypes.INTRO_2, 11000, 0, LadyConst.PhaseIntro); + _events.ScheduleEvent(LadyEventTypes.INTRO_3, 21000, 0, LadyConst.PhaseIntro); + _events.ScheduleEvent(LadyEventTypes.INTRO_4, 31500, 0, LadyConst.PhaseIntro); + _events.ScheduleEvent(LadyEventTypes.INTRO_5, 39500, 0, LadyConst.PhaseIntro); + _events.ScheduleEvent(LadyEventTypes.INTRO_6, 48500, 0, LadyConst.PhaseIntro); + _events.ScheduleEvent(LadyEventTypes.INTRO_7, 58000, 0, LadyConst.PhaseIntro); + } + } + + public override void AttackStart(Unit victim) + { + if (me.HasFlag(UnitFields.Flags, UnitFlags.NonAttackable)) + return; + + if (victim && me.Attack(victim, true) && !_events.IsInPhase(LadyConst.PhaseOne)) + me.GetMotionMaster().MoveChase(victim); + } + + public override void EnterCombat(Unit who) + { + if (!instance.CheckRequiredBosses(Bosses.LadyDeathwhisper, who.ToPlayer())) + { + EnterEvadeMode(); + instance.DoCastSpellOnPlayers(TeleporterSpells.LIGHT_S_HAMMER_TELEPORT); + return; + } + + me.setActive(true); + DoZoneInCombat(); + + _events.Reset(); + _events.SetPhase(LadyConst.PhaseOne); + // phase-independent events + _events.ScheduleEvent(LadyEventTypes.BERSERK, 600000); + _events.ScheduleEvent(LadyEventTypes.DEATH_AND_DECAY, 10000); + // phase one only + _events.ScheduleEvent(LadyEventTypes.P1_SUMMON_WAVE, 5000, 0, LadyConst.PhaseOne); + _events.ScheduleEvent(LadyEventTypes.P1_SHADOW_BOLT, RandomHelper.URand(5500, 6000), 0, LadyConst.PhaseOne); + _events.ScheduleEvent(LadyEventTypes.P1_EMPOWER_CULTIST, RandomHelper.URand(20000, 30000), 0, LadyConst.PhaseOne); + _events.ScheduleEvent(LadyEventTypes.P1_REANIMATE_CULTIST, RandomHelper.URand(10000, 20000), 0, LadyConst.PhaseOne); + if (GetDifficulty() != Difficulty.Raid10N) + _events.ScheduleEvent(LadyEventTypes.DOMINATE_MIND_H, 27000); + + Talk(LadyTexts.SAY_AGGRO); + DoStartNoMovement(who); + me.RemoveAurasDueToSpell(LadySpells.SHADOW_CHANNELING); + DoCast(me, LadySpells.MANA_BARRIER, true); + + instance.SetBossState(Bosses.LadyDeathwhisper, EncounterState.InProgress); + } + + public override void JustDied(Unit killer) + { + Talk(LadyTexts.SAY_DEATH); + + List livingAddEntries = new List(); + // Full House achievement + foreach (var guid in summons) + { + Unit unit = Global.ObjAccessor.GetUnit(me, guid); + if (unit) + if (unit.IsAlive() && unit.GetEntry() != CreatureIds.VengefulShade) + livingAddEntries.Add(unit.GetEntry()); + } + + if (livingAddEntries.Count >= 5) + instance.DoUpdateCriteria(CriteriaTypes.BeSpellTarget, LadySpells.FULL_HOUSE, 0, me); + + Creature darnavan = ObjectAccessor.GetCreature(me, _darnavanGUID); + if (darnavan) + { + if (darnavan.IsAlive()) + { + darnavan.SetFaction(35); + darnavan.CombatStop(true); + darnavan.GetMotionMaster().MoveIdle(); + darnavan.SetReactState(ReactStates.Passive); + darnavan.m_Events.AddEvent(new DaranavanMoveEvent(darnavan), darnavan.m_Events.CalculateTime(10000)); + darnavan.GetAI().Talk(LadyTexts.SAY_DARNAVAN_RESCUED); + Player owner = killer.GetCharmerOrOwnerPlayerOrPlayerItself(); + if (owner) + { + Group group = owner.GetGroup(); + if (group) + { + for (GroupReference groupRefe = group.GetFirstMember(); groupRefe != null; groupRefe = groupRefe.next()) + { + Player member = groupRefe.GetSource(); + if (member) + member.KilledMonsterCredit(NPC_DARNAVAN_CREDIT, ObjectGuid.Empty); + } + } + else + owner.KilledMonsterCredit(NPC_DARNAVAN_CREDIT, ObjectGuid.Empty); + } + } + } + + _JustDied(); + } + + public override void JustReachedHome() + { + _JustReachedHome(); + instance.SetBossState(Bosses.LadyDeathwhisper, EncounterState.Fail); + + summons.DespawnAll(); + Creature darnavan = ObjectAccessor.GetCreature(me, _darnavanGUID); + if (darnavan) + { + darnavan.DespawnOrUnsummon(); + _darnavanGUID.Clear(); + } + } + + public override void KilledUnit(Unit victim) + { + if (victim.IsTypeId(TypeId.Player)) + Talk(LadyTexts.SAY_KILL); + } + + public override void DamageTaken(Unit damageDealer, ref uint damage) + { + // phase transition + if (_events.IsInPhase(LadyConst.PhaseOne) && damage > (uint)me.GetPower(PowerType.Mana)) + { + Talk(LadyTexts.SAY_PHASE_2); + Talk(LadyTexts.EMOTE_PHASE_2); + DoStartMovement(me.GetVictim()); + damage -= (uint)me.GetPower(PowerType.Mana); + me.SetPower(PowerType.Mana, 0); + me.RemoveAurasDueToSpell(LadySpells.MANA_BARRIER); + _events.SetPhase(LadyConst.PhaseTwo); + _events.ScheduleEvent(LadyEventTypes.P2_FROSTBOLT, RandomHelper.URand(10000, 12000), 0, LadyConst.PhaseTwo); + _events.ScheduleEvent(LadyEventTypes.P2_FROSTBOLT_VOLLEY, RandomHelper.URand(19000, 21000), 0, LadyConst.PhaseTwo); + _events.ScheduleEvent(LadyEventTypes.P2_TOUCH_OF_INSIGNIFICANCE, RandomHelper.URand(6000, 9000), 0, LadyConst.PhaseTwo); + _events.ScheduleEvent(LadyEventTypes.P2_SUMMON_SHADE, RandomHelper.URand(12000, 15000), 0, LadyConst.PhaseTwo); + // on heroic mode Lady Deathwhisper is immune to taunt effects in phase 2 and continues summoning adds + if (IsHeroic()) + { + me.ApplySpellImmune(0, SpellImmunity.State, AuraType.ModTaunt, true); + me.ApplySpellImmune(0, SpellImmunity.Effect, SpellEffectName.AttackMe, true); + _events.ScheduleEvent(LadyEventTypes.P2_SUMMON_WAVE, 45000, 0, LadyConst.PhaseTwo); + } + } + } + + public override void JustSummoned(Creature summon) + { + if (summon.GetEntry() == NPC_DARNAVAN) + _darnavanGUID = summon.GetGUID(); + else + summons.Summon(summon); + + Unit target = null; + if (summon.GetEntry() == CreatureIds.VengefulShade) + { + target = Global.ObjAccessor.GetUnit(me, _nextVengefulShadeTargetGUID); // Vengeful Shade + _nextVengefulShadeTargetGUID.Clear(); + } + else + target = SelectTarget(SelectAggroTarget.Random); // Wave adds + + summon.GetAI().AttackStart(target); // CAN be NULL + } + + public override void UpdateAI(uint diff) + { + if ((!UpdateVictim() && !_events.IsInPhase(LadyConst.PhaseIntro))) + return; + + _events.Update(diff); + + if (me.HasUnitState(UnitState.Casting) && !_events.IsInPhase(LadyConst.PhaseIntro)) + return; + + Unit target; + + _events.ExecuteEvents(eventId => + { + + switch (eventId) + { + case LadyEventTypes.INTRO_2: + Talk(LadyTexts.SAY_INTRO_2); + break; + case LadyEventTypes.INTRO_3: + Talk(LadyTexts.SAY_INTRO_3); + break; + case LadyEventTypes.INTRO_4: + Talk(LadyTexts.SAY_INTRO_4); + break; + case LadyEventTypes.INTRO_5: + Talk(LadyTexts.SAY_INTRO_5); + break; + case LadyEventTypes.INTRO_6: + Talk(LadyTexts.SAY_INTRO_6); + break; + case LadyEventTypes.INTRO_7: + Talk(LadyTexts.SAY_INTRO_7); + break; + case LadyEventTypes.DEATH_AND_DECAY: + target = SelectTarget(SelectAggroTarget.Random); + if (target) + DoCast(target, LadySpells.DEATH_AND_DECAY); + _events.ScheduleEvent(LadyEventTypes.DEATH_AND_DECAY, RandomHelper.URand(22000, 30000)); + break; + case LadyEventTypes.DOMINATE_MIND_H: + Talk(LadyTexts.SAY_DOMINATE_MIND); + for (byte i = 0; i < _dominateMindCount; i++) + { + target = SelectTarget(SelectAggroTarget.Random, 1, 0.0f, true, -(int)LadySpells.DOMINATE_MIND_H); + if (target) + DoCast(target, LadySpells.DOMINATE_MIND_H); + } + _events.ScheduleEvent(LadyEventTypes.DOMINATE_MIND_H, RandomHelper.URand(40000, 45000)); + break; + case LadyEventTypes.P1_SUMMON_WAVE: + SummonWaveP1(); + _events.ScheduleEvent(LadyEventTypes.P1_SUMMON_WAVE, (uint)(IsHeroic() ? 45000 : 60000), 0, LadyConst.PhaseOne); + break; + case LadyEventTypes.P1_SHADOW_BOLT: + target = SelectTarget(SelectAggroTarget.Random); + if (target) + DoCast(target, LadySpells.SHADOW_BOLT); + _events.ScheduleEvent(LadyEventTypes.P1_SHADOW_BOLT, RandomHelper.URand(5000, 8000), 0, LadyConst.PhaseOne); + break; + case LadyEventTypes.P1_REANIMATE_CULTIST: + ReanimateCultist(); + _events.ScheduleEvent(LadyEventTypes.P1_REANIMATE_CULTIST, RandomHelper.URand(6000, 25000), 0, LadyConst.PhaseOne); + break; + case LadyEventTypes.P1_EMPOWER_CULTIST: + EmpowerCultist(); + _events.ScheduleEvent(LadyEventTypes.P1_EMPOWER_CULTIST, RandomHelper.URand(18000, 25000), 0, LadyConst.PhaseOne); + break; + case LadyEventTypes.P2_FROSTBOLT: + DoCastVictim(LadySpells.FROSTBOLT); + _events.ScheduleEvent(LadyEventTypes.P2_FROSTBOLT, RandomHelper.URand(10000, 11000), 0, LadyConst.PhaseTwo); + break; + case LadyEventTypes.P2_FROSTBOLT_VOLLEY: + DoCastAOE(LadySpells.FROSTBOLT_VOLLEY); + _events.ScheduleEvent(LadyEventTypes.P2_FROSTBOLT_VOLLEY, RandomHelper.URand(13000, 15000), 0, LadyConst.PhaseTwo); + break; + case LadyEventTypes.P2_TOUCH_OF_INSIGNIFICANCE: + DoCastVictim(LadySpells.TOUCH_OF_INSIGNIFICANCE); + _events.ScheduleEvent(LadyEventTypes.P2_TOUCH_OF_INSIGNIFICANCE, RandomHelper.URand(9000, 13000), 0, LadyConst.PhaseTwo); + break; + case LadyEventTypes.P2_SUMMON_SHADE: + Unit shadeTarget = SelectTarget(SelectAggroTarget.Random, 1); + if (shadeTarget) + { + _nextVengefulShadeTargetGUID = shadeTarget.GetGUID(); + DoCast(shadeTarget, LadySpells.SUMMON_SHADE); + } + _events.ScheduleEvent(LadyEventTypes.P2_SUMMON_SHADE, RandomHelper.URand(18000, 23000), 0, LadyConst.PhaseTwo); + break; + case LadyEventTypes.P2_SUMMON_WAVE: + SummonWaveP2(); + _events.ScheduleEvent(LadyEventTypes.P2_SUMMON_WAVE, 45000, 0, LadyConst.PhaseTwo); + break; + case LadyEventTypes.BERSERK: + DoCast(me, InstanceSpells.Berserk); + Talk(LadyTexts.SAY_BERSERK); + break; + } + }); + + // We should not melee attack when barrier is up + if (me.HasAura(LadySpells.MANA_BARRIER)) + return; + + DoMeleeAttackIfReady(); + } + + // summoning function for first phase + void SummonWaveP1() + { + byte addIndex = (byte)(_waveCounter & 1); + byte addIndexOther = (byte)(addIndex ^ 1); + + // Summon first add, replace it with Darnavan if weekly quest is active + if (_waveCounter != 0 || !Global.PoolMgr.IsSpawnedObject(QUEST_DEPROGRAMMING)) + Summon(LadyConst.SummonEntries[addIndex], LadyConst.SummonPositions[addIndex * 3]); + else + Summon(NPC_DARNAVAN, LadyConst.SummonPositions[addIndex * 3]); + + Summon(LadyConst.SummonEntries[addIndexOther], LadyConst.SummonPositions[addIndex * 3 + 1]); + Summon(LadyConst.SummonEntries[addIndex], LadyConst.SummonPositions[addIndex * 3 + 2]); + if (Is25ManRaid()) + { + Summon(LadyConst.SummonEntries[addIndexOther], LadyConst.SummonPositions[addIndexOther * 3]); + Summon(LadyConst.SummonEntries[addIndex], LadyConst.SummonPositions[addIndexOther * 3 + 1]); + Summon(LadyConst.SummonEntries[addIndexOther], LadyConst.SummonPositions[addIndexOther * 3 + 2]); + Summon(LadyConst.SummonEntries[RandomHelper.IRand(0, 1)], LadyConst.SummonPositions[6]); + } + + ++_waveCounter; + } + + // summoning function for second phase + void SummonWaveP2() + { + if (Is25ManRaid()) + { + byte addIndex = (byte)(_waveCounter & 1); + Summon(LadyConst.SummonEntries[addIndex], LadyConst.SummonPositions[addIndex * 3]); + Summon(LadyConst.SummonEntries[addIndex ^ 1], LadyConst.SummonPositions[addIndex * 3 + 1]); + Summon(LadyConst.SummonEntries[addIndex], LadyConst.SummonPositions[addIndex * 3 + 2]); + } + else + Summon(LadyConst.SummonEntries[RandomHelper.IRand(0, 1)], LadyConst.SummonPositions[6]); + + ++_waveCounter; + } + + // helper for summoning wave mobs + void Summon(uint entry, Position pos) + { + TempSummon summon = me.SummonCreature(entry, pos, TempSummonType.CorpseTimedDespawn, 10000); + if (summon) + summon.GetAI().DoCast(summon, LadySpells.TELEPORT_VISUAL); + } + + void ReanimateCultist() + { + if (summons.Empty()) + return; + + List temp = new List(); + foreach (var guid in summons.ToList()) + { + Creature cre = ObjectAccessor.GetCreature(me, guid); + if (cre) + if (cre.IsAlive() && (cre.GetEntry() == CreatureIds.CultFanatic || cre.GetEntry() == CreatureIds.CultAdherent)) + temp.Add(cre); + } + + if (temp.Empty()) + return; + + Creature cultist = temp.PickRandom(); + DoCast(cultist, LadySpells.DARK_MARTYRDOM_T, true); + } + + void EmpowerCultist() + { + if (summons.Empty()) + return; + + List temp = new List(); + foreach (var guid in summons) + { + Creature cre = ObjectAccessor.GetCreature(me, guid); + if (cre) + if (cre.IsAlive() && (cre.GetEntry() == CreatureIds.CultFanatic || cre.GetEntry() == CreatureIds.CultAdherent)) + temp.Add(cre); + } + + // noone to empower + if (temp.Empty()) + return; + + // select random cultist + Creature cultist = temp.PickRandom(); + DoCast(cultist, cultist.GetEntry() == CreatureIds.CultFanatic ? LadySpells.DARK_TRANSFORMATION_T : LadySpells.DARK_EMPOWERMENT_T, true); + Talk(cultist.GetEntry() == CreatureIds.CultFanatic ? LadyTexts.SAY_DARK_TRANSFORMATION : LadyTexts.SAY_DARK_EMPOWERMENT); + } + + ObjectGuid _nextVengefulShadeTargetGUID; + ObjectGuid _darnavanGUID; + uint _waveCounter; + byte _dominateMindCount; + bool _introDone; + + uint NPC_DARNAVAN { get { return RaidMode(DeprogrammingData.NPC_DARNAVAN_10, DeprogrammingData.NPC_DARNAVAN_25, DeprogrammingData.NPC_DARNAVAN_10, DeprogrammingData.NPC_DARNAVAN_25); } } + uint NPC_DARNAVAN_CREDIT { get { return RaidMode(DeprogrammingData.NPC_DARNAVAN_CREDIT_10, DeprogrammingData.NPC_DARNAVAN_CREDIT_25, DeprogrammingData.NPC_DARNAVAN_CREDIT_10, DeprogrammingData.NPC_DARNAVAN_CREDIT_25); } } + uint QUEST_DEPROGRAMMING { get { return RaidMode(WeeklyQuestIds.Deprogramming10, WeeklyQuestIds.Deprogramming25, WeeklyQuestIds.Deprogramming10, WeeklyQuestIds.Deprogramming25); } } + } + + public override CreatureAI GetAI(Creature creature) + { + return InstanceIcecrownCitadel.GetInstanceAI(creature); + } + } + + [Script] + class npc_cult_fanatic : CreatureScript + { + public npc_cult_fanatic() : base("npc_cult_fanatic") { } + + class npc_cult_fanaticAI : ScriptedAI + { + public npc_cult_fanaticAI(Creature creature) : base(creature) { } + + public override void Reset() + { + _events.Reset(); + _events.ScheduleEvent(LadyEventTypes.FANATIC_NECROTIC_STRIKE, RandomHelper.URand(10000, 12000)); + _events.ScheduleEvent(LadyEventTypes.FANATIC_SHADOW_CLEAVE, RandomHelper.URand(14000, 16000)); + _events.ScheduleEvent(LadyEventTypes.FANATIC_VAMPIRIC_MIGHT, RandomHelper.URand(20000, 27000)); + } + + public override void SpellHit(Unit caster, SpellInfo spell) + { + switch (spell.Id) + { + case LadySpells.DARK_TRANSFORMATION: + me.UpdateEntry(CreatureIds.DeformedFanatic); + break; + case LadySpells.DARK_TRANSFORMATION_T: + if (me.HasFlag(ObjectFields.DynamicFlags, UnitDynFlags.Dead)) + break; + me.InterruptNonMeleeSpells(true); + DoCast(me, LadySpells.DARK_TRANSFORMATION); + break; + case LadySpells.DARK_MARTYRDOM_T: + me.InterruptNonMeleeSpells(true); + DoCast(me, LadySpells.DARK_MARTYRDOM_FANATIC); + break; + case LadySpells.DARK_MARTYRDOM_FANATIC: // 10nm + case 72495: // 25nm + case 72496: // 10hc + case 72497: // 25hc + me.SetFlag(UnitFields.Flags, UnitFlags.RemoveClientControl | UnitFlags.Pacified | UnitFlags.NonAttackable | UnitFlags.Unk29); + me.SetFlag(ObjectFields.DynamicFlags, UnitDynFlags.Dead); + me.SetFlag(UnitFields.Flags2, UnitFlags2.FeignDeath); + me.SetReactState(ReactStates.Passive); + me.AttackStop(); + _events.ScheduleEvent(LadyEventTypes.CULTIST_DARK_MARTYRDOM, 4000); + break; + default: + break; + + } + } + + public override void UpdateAI(uint diff) + { + if (!UpdateVictim()) + return; + + _events.Update(diff); + + if (me.HasUnitState(UnitState.Casting)) + return; + + _events.ExecuteEvents(eventId => + { + switch (eventId) + { + case LadyEventTypes.FANATIC_NECROTIC_STRIKE: + DoCastVictim(LadySpells.NECROTIC_STRIKE); + _events.ScheduleEvent(LadyEventTypes.FANATIC_NECROTIC_STRIKE, RandomHelper.URand(11000, 13000)); + break; + case LadyEventTypes.FANATIC_SHADOW_CLEAVE: + DoCastVictim(LadySpells.SHADOW_CLEAVE); + _events.ScheduleEvent(LadyEventTypes.FANATIC_SHADOW_CLEAVE, RandomHelper.URand(9500, 11000)); + break; + case LadyEventTypes.FANATIC_VAMPIRIC_MIGHT: + DoCast(me, LadySpells.VAMPIRIC_MIGHT); + _events.ScheduleEvent(LadyEventTypes.FANATIC_VAMPIRIC_MIGHT, RandomHelper.URand(20000, 27000)); + break; + case LadyEventTypes.CULTIST_DARK_MARTYRDOM: + if (me.IsSummon()) + { + Unit owner = me.ToTempSummon().GetSummoner(); + if (owner) + if (owner.ToCreature()) + owner.ToCreature().GetAI().Talk(LadyTexts.SAY_ANIMATE_DEAD); + } + me.UpdateEntry(CreatureIds.ReanimatedFanatic); + me.RemoveFlag(UnitFields.Flags, UnitFlags.RemoveClientControl | UnitFlags.Pacified | UnitFlags.NonAttackable | UnitFlags.Unk29); + me.RemoveFlag(ObjectFields.DynamicFlags, UnitDynFlags.Dead); + me.RemoveFlag(UnitFields.Flags2, UnitFlags2.FeignDeath); + DoCast(me, LadySpells.FANATIC_S_DETERMINATION); + me.SetReactState(ReactStates.Aggressive); + AttackStart(SelectTarget(SelectAggroTarget.Random)); + break; + } + }); + + DoMeleeAttackIfReady(); + } + } + + public override CreatureAI GetAI(Creature creature) + { + return InstanceIcecrownCitadel.GetInstanceAI(creature); + } + } + + [Script] + class npc_cult_adherent : CreatureScript + { + public npc_cult_adherent() : base("npc_cult_adherent") { } + + class npc_cult_adherentAI : ScriptedAI + { + public npc_cult_adherentAI(Creature creature) : base(creature) { } + + public override void Reset() + { + _events.Reset(); + _events.ScheduleEvent(LadyEventTypes.ADHERENT_FROST_FEVER, RandomHelper.URand(10000, 12000)); + _events.ScheduleEvent(LadyEventTypes.ADHERENT_DEATHCHILL, RandomHelper.URand(14000, 16000)); + _events.ScheduleEvent(LadyEventTypes.ADHERENT_CURSE_OF_TORPOR, RandomHelper.URand(14000, 16000)); + _events.ScheduleEvent(LadyEventTypes.ADHERENT_SHORUD_OF_THE_OCCULT, RandomHelper.URand(32000, 39000)); + } + + public override void SpellHit(Unit caster, SpellInfo spell) + { + switch (spell.Id) + { + case LadySpells.DARK_EMPOWERMENT: + me.UpdateEntry(CreatureIds.EmpoweredAdherent); + break; + case LadySpells.DARK_EMPOWERMENT_T: + if (me.HasFlag(ObjectFields.DynamicFlags, UnitDynFlags.Dead)) + break; + me.InterruptNonMeleeSpells(true); + DoCast(me, LadySpells.DARK_EMPOWERMENT); + break; + case LadySpells.DARK_MARTYRDOM_T: + me.InterruptNonMeleeSpells(true); + DoCast(me, LadySpells.DARK_MARTYRDOM_ADHERENT); + break; + case LadySpells.DARK_MARTYRDOM_ADHERENT: // 10nm + case 72498: // 25nm + case 72499: // 10hc + case 72500: // 25hc + me.SetFlag(UnitFields.Flags, UnitFlags.RemoveClientControl | UnitFlags.Pacified | UnitFlags.NonAttackable | UnitFlags.Unk29); + me.SetFlag(ObjectFields.DynamicFlags, UnitDynFlags.Dead); + me.SetFlag(UnitFields.Flags2, UnitFlags2.FeignDeath); + me.SetReactState(ReactStates.Passive); + me.AttackStop(); + _events.ScheduleEvent(LadyEventTypes.CULTIST_DARK_MARTYRDOM, 4000); + break; + default: + break; + } + } + + public override void UpdateAI(uint diff) + { + if (!UpdateVictim()) + return; + + _events.Update(diff); + + if (me.HasUnitState(UnitState.Casting)) + return; + + _events.ExecuteEvents(eventId => + { + switch (eventId) + { + case LadyEventTypes.ADHERENT_FROST_FEVER: + DoCastVictim(LadySpells.FROST_FEVER); + _events.ScheduleEvent(LadyEventTypes.ADHERENT_FROST_FEVER, RandomHelper.URand(9000, 13000)); + break; + case LadyEventTypes.ADHERENT_DEATHCHILL: + if (me.GetEntry() == CreatureIds.EmpoweredAdherent) + DoCastVictim(LadySpells.DEATHCHILL_BLAST); + else + DoCastVictim(LadySpells.DEATHCHILL_BOLT); + _events.ScheduleEvent(LadyEventTypes.ADHERENT_DEATHCHILL, RandomHelper.URand(9000, 13000)); + break; + case LadyEventTypes.ADHERENT_CURSE_OF_TORPOR: + Unit target = SelectTarget(SelectAggroTarget.Random, 1); + if (target) + DoCast(target, LadySpells.CURSE_OF_TORPOR); + _events.ScheduleEvent(LadyEventTypes.ADHERENT_CURSE_OF_TORPOR, RandomHelper.URand(9000, 13000)); + break; + case LadyEventTypes.ADHERENT_SHORUD_OF_THE_OCCULT: + DoCast(me, LadySpells.SHORUD_OF_THE_OCCULT); + _events.ScheduleEvent(LadyEventTypes.ADHERENT_SHORUD_OF_THE_OCCULT, RandomHelper.URand(27000, 32000)); + break; + case LadyEventTypes.CULTIST_DARK_MARTYRDOM: + if (me.IsSummon()) + { + Unit owner = me.ToTempSummon().GetSummoner(); + if (owner) + if (owner.ToCreature()) + owner.ToCreature().GetAI().Talk(LadyTexts.SAY_ANIMATE_DEAD); + } + me.UpdateEntry(CreatureIds.ReanimatedAdherent); + me.RemoveFlag(UnitFields.Flags, UnitFlags.RemoveClientControl | UnitFlags.Pacified | UnitFlags.NonAttackable | UnitFlags.Unk29); + me.RemoveFlag(ObjectFields.DynamicFlags, UnitDynFlags.Dead); + me.RemoveFlag(UnitFields.Flags2, UnitFlags2.FeignDeath); + DoCast(me, LadySpells.FANATIC_S_DETERMINATION); + me.SetReactState(ReactStates.Aggressive); + AttackStart(SelectTarget(SelectAggroTarget.Random)); + break; + } + }); + + DoMeleeAttackIfReady(); + } + } + + public override CreatureAI GetAI(Creature creature) + { + return InstanceIcecrownCitadel.GetInstanceAI(creature); + } + } + + [Script] + class npc_vengeful_shade : CreatureScript + { + public npc_vengeful_shade() : base("npc_vengeful_shade") { } + + class npc_vengeful_shadeAI : ScriptedAI + { + public npc_vengeful_shadeAI(Creature creature) : base(creature) + { + me.SetFlag(UnitFields.Flags, UnitFlags.NonAttackable | UnitFlags.NotSelectable); + } + + public override void Reset() + { + me.AddAura(LadySpells.VENGEFUL_BLAST_PASSIVE, me); + } + + public override void SpellHitTarget(Unit target, SpellInfo spell) + { + switch (spell.Id) + { + case LadySpells.VENGEFUL_BLAST: + case LadySpells.VENGEFUL_BLAST_25N: + case LadySpells.VENGEFUL_BLAST_10H: + case LadySpells.VENGEFUL_BLAST_25H: + me.KillSelf(); + break; + default: + break; + } + } + } + + public override CreatureAI GetAI(Creature creature) + { + return InstanceIcecrownCitadel.GetInstanceAI(creature); + } + } + + [Script] + class npc_darnavan : CreatureScript + { + public npc_darnavan() : base("npc_darnavan") { } + + class npc_darnavanAI : ScriptedAI + { + public npc_darnavanAI(Creature creature) + : base(creature) + { + } + + public override void Reset() + { + _events.Reset(); + _events.ScheduleEvent(LadyEventTypes.DARNAVAN_BLADESTORM, 10000); + _events.ScheduleEvent(LadyEventTypes.DARNAVAN_INTIMIDATING_SHOUT, RandomHelper.URand(20000, 25000)); + _events.ScheduleEvent(LadyEventTypes.DARNAVAN_MORTAL_STRIKE, RandomHelper.URand(25000, 30000)); + _events.ScheduleEvent(LadyEventTypes.DARNAVAN_SUNDER_ARMOR, RandomHelper.URand(5000, 8000)); + _canCharge = true; + _canShatter = true; + } + + public override void JustDied(Unit killer) + { + _events.Reset(); + Player owner = killer.GetCharmerOrOwnerPlayerOrPlayerItself(); + if (owner) + { + Group group = owner.GetGroup(); + if (group) + { + for (GroupReference groupRefe = group.GetFirstMember(); groupRefe != null; groupRefe = groupRefe.next()) + { + Player member = groupRefe.GetSource(); + if (member) + member.FailQuest(QUEST_DEPROGRAMMING); + } + } + else + owner.FailQuest(QUEST_DEPROGRAMMING); + } + } + + public override void MovementInform(MovementGeneratorType type, uint id) + { + if (type != MovementGeneratorType.Point || id != DeprogrammingData.POINT_DESPAWN) + return; + + me.DespawnOrUnsummon(); + } + + public override void EnterCombat(Unit victim) + { + Talk(LadyTexts.SAY_DARNAVAN_AGGRO); + } + + public override void UpdateAI(uint diff) + { + if (!UpdateVictim()) + return; + + _events.Update(diff); + + if (me.HasUnitState(UnitState.Casting)) + return; + + if (_canShatter && me.GetVictim() && me.GetVictim().IsImmunedToDamage(SpellSchoolMask.Normal)) + { + DoCastVictim(LadySpells.SHATTERING_THROW); + _canShatter = false; + _events.ScheduleEvent(LadyEventTypes.DARNAVAN_SHATTERING_THROW, 30000); + return; + } + + if (_canCharge && !me.IsWithinMeleeRange(me.GetVictim())) + { + DoCastVictim(LadySpells.CHARGE); + _canCharge = false; + _events.ScheduleEvent(LadyEventTypes.DARNAVAN_CHARGE, 20000); + return; + } + + _events.ExecuteEvents(eventId => + { + switch (eventId) + { + case LadyEventTypes.DARNAVAN_BLADESTORM: + DoCast(LadySpells.BLADESTORM); + _events.ScheduleEvent(LadyEventTypes.DARNAVAN_BLADESTORM, RandomHelper.URand(90000, 100000)); + break; + case LadyEventTypes.DARNAVAN_CHARGE: + _canCharge = true; + break; + case LadyEventTypes.DARNAVAN_INTIMIDATING_SHOUT: + DoCast(LadySpells.INTIMIDATING_SHOUT); + _events.ScheduleEvent(LadyEventTypes.DARNAVAN_INTIMIDATING_SHOUT, RandomHelper.URand(90000, 120000)); + break; + case LadyEventTypes.DARNAVAN_MORTAL_STRIKE: + DoCastVictim(LadySpells.MORTAL_STRIKE); + _events.ScheduleEvent(LadyEventTypes.DARNAVAN_MORTAL_STRIKE, RandomHelper.URand(15000, 30000)); + break; + case LadyEventTypes.DARNAVAN_SHATTERING_THROW: + _canShatter = true; + break; + case LadyEventTypes.DARNAVAN_SUNDER_ARMOR: + DoCastVictim(LadySpells.SUNDER_ARMOR); + _events.ScheduleEvent(LadyEventTypes.DARNAVAN_SUNDER_ARMOR, RandomHelper.URand(3000, 7000)); + break; + } + }); + + DoMeleeAttackIfReady(); + } + + uint QUEST_DEPROGRAMMING { get { return RaidMode(WeeklyQuestIds.Deprogramming10, WeeklyQuestIds.Deprogramming25, WeeklyQuestIds.Deprogramming10, WeeklyQuestIds.Deprogramming25); } } + + bool _canCharge; + bool _canShatter; + } + + public override CreatureAI GetAI(Creature creature) + { + return InstanceIcecrownCitadel.GetInstanceAI(creature); + } + } + + [Script] + class spell_deathwhisper_mana_barrier : SpellScriptLoader + { + public spell_deathwhisper_mana_barrier() : base("spell_deathwhisper_mana_barrier") { } + + class spell_deathwhisper_mana_barrier_AuraScript : AuraScript + { + void HandlePeriodicTick(AuraEffect aurEff) + { + PreventDefaultAction(); + Unit caster = GetCaster(); + if (caster) + { + int missingHealth = (int)(caster.GetMaxHealth() - caster.GetHealth()); + caster.ModifyHealth(missingHealth); + caster.ModifyPower(PowerType.Mana, -missingHealth); + } + } + + public override void Register() + { + OnEffectPeriodic.Add(new EffectPeriodicHandler(HandlePeriodicTick, 0, AuraType.PeriodicTriggerSpell)); + } + } + + public override AuraScript GetAuraScript() + { + return new spell_deathwhisper_mana_barrier_AuraScript(); + } + } + + [Script] + class spell_cultist_dark_martyrdom : SpellScriptLoader + { + public spell_cultist_dark_martyrdom() : base("spell_cultist_dark_martyrdom") { } + + class spell_cultist_dark_martyrdom_SpellScript : SpellScript + { + void HandleEffect(uint effIndex) + { + if (GetCaster().IsSummon()) + { + Unit owner = GetCaster().ToTempSummon().GetSummoner(); + if (owner) + owner.GetAI().SetGUID(GetCaster().GetGUID(), (int)LadyConst.GUIDCultist); + } + + GetCaster().KillSelf(); + GetCaster().SetDisplayId(GetCaster().GetEntry() == CreatureIds.CultFanatic ? 38009 : 38010u); + } + + public override void Register() + { + OnEffectHitTarget.Add(new EffectHandler(HandleEffect, 2, SpellEffectName.ForceDeselect)); + } + } + + public override SpellScript GetSpellScript() + { + return new spell_cultist_dark_martyrdom_SpellScript(); + } + } + + [Script] + class at_lady_deathwhisper_entrance : AreaTriggerScript + { + public at_lady_deathwhisper_entrance() : base("at_lady_deathwhisper_entrance") { } + + public override bool OnTrigger(Player player, AreaTriggerRecord areaTrigger, bool entered) + { + InstanceScript instance = player.GetInstanceScript(); + if (instance != null) + { + if (instance.GetBossState(Bosses.LadyDeathwhisper) != EncounterState.Done) + { + Creature ladyDeathwhisper = ObjectAccessor.GetCreature(player, instance.GetGuidData(Bosses.LadyDeathwhisper)); + if (ladyDeathwhisper) + ladyDeathwhisper.GetAI().DoAction(0); + } + } + + return true; + } + } +} diff --git a/Scripts/Northrend/IcecrownCitadel/LordMarrowgar.cs b/Scripts/Northrend/IcecrownCitadel/LordMarrowgar.cs new file mode 100644 index 000000000..7cecfdfc2 --- /dev/null +++ b/Scripts/Northrend/IcecrownCitadel/LordMarrowgar.cs @@ -0,0 +1,734 @@ +/* + * Copyright (C) 2012-2017 CypherCore + * + * 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, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +using Framework.Constants; +using Game.AI; +using Game.Entities; +using Game.Maps; +using Game.Movement; +using Game.Scripting; +using Game.Spells; +using System; +using System.Collections.Generic; +using System.Diagnostics.Contracts; + +namespace Scripts.Northrend.IcecrownCitadel +{ + namespace Marrorgar + { + struct Texts + { + public const int SayEnterZone = 0; + public const int SayAggro = 1; + public const int SayBoneStorm = 2; + public const int SayBonespike = 3; + public const int SayKill = 4; + public const int SayDeath = 5; + public const int SayBerserk = 6; + public const int EmoteBoneStorm = 7; + } + + struct Spells + { + // Lord Marrowgar + public const uint BoneSlice = 69055; + public const uint BoneStorm = 69076; + public const uint BoneSpikeGraveyard = 69057; + public const uint ColdflameNormal = 69140; + public const uint ColdflameBoneStorm = 72705; + + // Bone Spike + public const uint Impaled = 69065; + public const uint RideVehicle = 46598; + + // Coldflame + public const uint ColdflamePassive = 69145; + public const uint ColdflameSummon = 69147; + } + + struct Misc + { + public static uint[] BoneSpikeSummonId = { 69062, 72669, 72670 }; + + public const uint EventBoneSpikeGraveyard = 1; + public const uint EventColdflame = 2; + public const uint EventBoneStormBegin = 3; + public const uint EventBoneStormMove = 4; + public const uint EventBoneStormEnd = 5; + public const uint EventEnableBoneSlice = 6; + public const uint EventEnrage = 7; + public const uint EventWarnBoneStorm = 8; + + public const uint EventColdflameTrigger = 9; + public const uint EventFailBoned = 10; + + public const uint EventGroupSpecial = 1; + + public const uint PointTargetBonestormPlayer = 36612631; + public const uint PointTargetColdflame = 36672631; + + public const int DataColdflameGuid = 0; + + // Manual Marking For Targets Hit By Bone Slice As No Aura Exists For This Purpose + // These Units Are The Tanks In This Encounter + // And Should Be Immune To Bone Spike Graveyard + public const int DataSpikeImmune = 1; + //DataSpikeImmune1; = 2; // Reserved & Used + //DataSpikeImmune2; = 3; // Reserved & Used + + public const int ActionClearSpikeImmunities = 1; + + public const uint MaxBoneSpikeImmune = 3; + } + + class BoneSpikeTargetSelector : ISelector + { + public BoneSpikeTargetSelector(UnitAI ai) + { + _ai = ai; + } + + public bool Check(Unit unit) + { + if (!unit.IsTypeId(TypeId.Player)) + return false; + + if (unit.HasAura(Spells.Impaled)) + return false; + + // Check if it is one of the tanks soaking Bone Slice + for (int i = 0; i < Misc.MaxBoneSpikeImmune; ++i) + if (unit.GetGUID() == _ai.GetGUID(Misc.DataSpikeImmune + i)) + return false; + + return true; + } + + UnitAI _ai; + } + + [Script] + class boss_lord_marrowgar : CreatureScript + { + public boss_lord_marrowgar() : base("boss_lord_marrowgar") { } + + public class boss_lord_marrowgarAI : BossAI + { + public boss_lord_marrowgarAI(Creature creature) : base(creature, Bosses.LordMarrowgar) + { + _boneStormDuration = RaidMode(20000, 30000, 20000, 30000); + _baseSpeed = creature.GetSpeedRate(UnitMoveType.Run); + _coldflameLastPos.Relocate(creature); + _introDone = false; + _boneSlice = false; + } + + public override void Reset() + { + _Reset(); + me.SetSpeedRate(UnitMoveType.Run, _baseSpeed); + me.RemoveAurasDueToSpell(Spells.BoneStorm); + me.RemoveAurasDueToSpell(InstanceSpells.Berserk); + + _events.ScheduleEvent(Misc.EventEnableBoneSlice, 10000); + _events.ScheduleEvent(Misc.EventBoneSpikeGraveyard, 15000, Misc.EventGroupSpecial); + _events.ScheduleEvent(Misc.EventColdflame, 5000, Misc.EventGroupSpecial); + _events.ScheduleEvent(Misc.EventWarnBoneStorm, RandomHelper.URand(45000, 50000)); + _events.ScheduleEvent(Misc.EventEnrage, 600000); + _boneSlice = false; + _boneSpikeImmune.Clear(); + } + + public override void EnterCombat(Unit who) + { + Talk(Texts.SayAggro); + + me.setActive(true); + DoZoneInCombat(); + instance.SetBossState(Bosses.LordMarrowgar, EncounterState.InProgress); + } + + public override void JustDied(Unit killer) + { + Talk(Texts.SayDeath); + + _JustDied(); + } + + public override void JustReachedHome() + { + _JustReachedHome(); + instance.SetBossState(Bosses.LordMarrowgar, EncounterState.Fail); + instance.SetData(DataTypes.BonedAchievement, 1); // reset + } + + public override void KilledUnit(Unit victim) + { + if (victim.IsTypeId(TypeId.Player)) + Talk(Texts.SayKill); + } + + public override void MoveInLineOfSight(Unit who) + { + if (!_introDone && me.IsWithinDistInMap(who, 70.0f)) + { + Talk(Texts.SayEnterZone); + _introDone = true; + } + } + + public override void UpdateAI(uint diff) + { + if (!UpdateVictim()) + return; + + _events.Update(diff); + + if (me.HasUnitState(UnitState.Casting)) + return; + + _events.ExecuteEvents(eventId => + { + switch (eventId) + { + case Misc.EventBoneSpikeGraveyard: + if (IsHeroic() || !me.HasAura(Spells.BoneStorm)) + DoCast(me, Spells.BoneSpikeGraveyard); + _events.ScheduleEvent(Misc.EventBoneSpikeGraveyard, RandomHelper.URand(15000, 20000), Misc.EventGroupSpecial); + break; + case Misc.EventColdflame: + _coldflameLastPos.Relocate(me); + _coldflameTarget.Clear(); + if (!me.HasAura(Spells.BoneStorm)) + DoCastAOE(Spells.ColdflameNormal); + else + DoCast(me, Spells.ColdflameBoneStorm); + _events.ScheduleEvent(Misc.EventColdflame, 5000, Misc.EventGroupSpecial); + break; + case Misc.EventWarnBoneStorm: + _boneSlice = false; + Talk(Texts.SayBoneStorm); + me.FinishSpell(CurrentSpellTypes.Melee, false); + DoCast(me, Spells.BoneStorm); + _events.DelayEvents(3000, Misc.EventGroupSpecial); + _events.ScheduleEvent(Misc.EventBoneStormBegin, 3050); + _events.ScheduleEvent(Misc.EventWarnBoneStorm, RandomHelper.URand(90000, 95000)); + break; + case Misc.EventBoneStormBegin: + Aura pStorm = me.GetAura(Spells.BoneStorm); + if (pStorm != null) + pStorm.SetDuration((int)_boneStormDuration); + me.SetSpeedRate(UnitMoveType.Run, _baseSpeed * 3.0f); + Talk(Texts.SayBoneStorm); + _events.ScheduleEvent(Misc.EventBoneStormEnd, _boneStormDuration + 1); + goto case Misc.EventBoneStormMove; + // no break here + case Misc.EventBoneStormMove: + { + _events.ScheduleEvent(Misc.EventBoneStormMove, _boneStormDuration / 3); + Unit unit = SelectTarget(SelectAggroTarget.Random, 0, new NonTankTargetSelector(me)); + if (!unit) + unit = SelectTarget(SelectAggroTarget.Random, 0, 0.0f, true); + if (unit) + me.GetMotionMaster().MovePoint(Misc.PointTargetBonestormPlayer, unit); + break; + } + case Misc.EventBoneStormEnd: + if (me.GetMotionMaster().GetCurrentMovementGeneratorType() == MovementGeneratorType.Point) + me.GetMotionMaster().MovementExpired(); + me.GetMotionMaster().MoveChase(me.GetVictim()); + me.SetSpeedRate(UnitMoveType.Run, _baseSpeed); + _events.CancelEvent(Misc.EventBoneStormMove); + _events.ScheduleEvent(Misc.EventEnableBoneSlice, 10000); + if (!IsHeroic()) + _events.RescheduleEvent(Misc.EventBoneSpikeGraveyard, 15000, Misc.EventGroupSpecial); + break; + case Misc.EventEnableBoneSlice: + _boneSlice = true; + break; + case Misc.EventEnrage: + DoCast(me, Texts.SayBerserk, true); + Talk(Texts.SayBerserk); + break; + } + }); + + // We should not melee attack when storming + if (me.HasAura(Spells.BoneStorm)) + return; + + // 10 seconds since encounter start Bone Slice replaces melee attacks + if (_boneSlice && !me.GetCurrentSpell(CurrentSpellTypes.Melee)) + DoCastVictim(Spells.BoneSlice); + + DoMeleeAttackIfReady(); + } + + public override void MovementInform(MovementGeneratorType type, uint id) + { + if (type != MovementGeneratorType.Point || id != Misc.PointTargetBonestormPlayer) + return; + + // lock movement + me.GetMotionMaster().MoveIdle(); + } + + public Position GetLastColdflamePosition() + { + return _coldflameLastPos; + } + + public override ObjectGuid GetGUID(int type = 0) + { + switch (type) + { + case Misc.DataColdflameGuid: + return _coldflameTarget; + case Misc.DataSpikeImmune + 0: + case Misc.DataSpikeImmune + 1: + case Misc.DataSpikeImmune + 2: + { + int index = type - Misc.DataSpikeImmune; + if (index < _boneSpikeImmune.Count) + return _boneSpikeImmune[index]; + + break; + } + } + + return ObjectGuid.Empty; + } + + public override void SetGUID(ObjectGuid guid, int type = 0) + { + switch (type) + { + case Misc.DataColdflameGuid: + _coldflameTarget = guid; + break; + case Misc.DataSpikeImmune: + _boneSpikeImmune.Add(guid); + break; + } + } + + public override void DoAction(int action) + { + if (action != Misc.ActionClearSpikeImmunities) + return; + + _boneSpikeImmune.Clear(); + } + + Position _coldflameLastPos = new Position(); + List _boneSpikeImmune = new List(); + ObjectGuid _coldflameTarget; + uint _boneStormDuration; + float _baseSpeed; + bool _introDone; + bool _boneSlice; + } + + public override CreatureAI GetAI(Creature creature) + { + return InstanceIcecrownCitadel.GetInstanceAI(creature); + } + } + + [Script] + class npc_coldflame : CreatureScript + { + public npc_coldflame() : base("npc_coldflame") { } + + class npc_coldflameAI : ScriptedAI + { + public npc_coldflameAI(Creature creature) : base(creature) { } + + public override void IsSummonedBy(Unit owner) + { + if (!owner.IsTypeId(TypeId.Player)) + return; + + Position pos = new Position(); + var marrowgarAI = (boss_lord_marrowgar.boss_lord_marrowgarAI)owner.GetAI(); + if (marrowgarAI != null) + pos.Relocate(marrowgarAI.GetLastColdflamePosition()); + else + pos.Relocate(owner); + + if (owner.HasAura(Spells.BoneStorm)) + { + float ang = Position.NormalizeOrientation(pos.GetAngle(me)); + me.SetOrientation(ang); + owner.GetNearPoint2D(out pos.posX, out pos.posY, 5.0f - owner.GetObjectSize(), ang); + } + else + { + Player target = Global.ObjAccessor.GetPlayer(owner, owner.GetAI().GetGUID(Misc.DataColdflameGuid)); + if (!target) + { + me.DespawnOrUnsummon(); + return; + } + + float ang = Position.NormalizeOrientation(pos.GetAngle(target)); + me.SetOrientation(ang); + owner.GetNearPoint2D(out pos.posX, out pos.posY, 15.0f - owner.GetObjectSize(), ang); + } + + me.NearTeleportTo(pos.GetPositionX(), pos.GetPositionY(), me.GetPositionZ(), me.GetOrientation()); + DoCast(Spells.ColdflameSummon); + _events.ScheduleEvent(Misc.EventColdflameTrigger, 500); + } + + public override void UpdateAI(uint diff) + { + _events.Update(diff); + + if (_events.ExecuteEvent() == Misc.EventColdflameTrigger) + { + Position newPos = me.GetNearPosition(5.0f, 0.0f); + me.NearTeleportTo(newPos.GetPositionX(), newPos.GetPositionY(), me.GetPositionZ(), me.GetOrientation()); + DoCast(Spells.ColdflameSummon); + _events.ScheduleEvent(Misc.EventColdflameTrigger, 500); + } + } + } + + public override CreatureAI GetAI(Creature creature) + { + return InstanceIcecrownCitadel.GetInstanceAI(creature); + } + } + + [Script] + class npc_bone_spike : CreatureScript + { + public npc_bone_spike() : base("npc_bone_spike") { } + + class npc_bone_spikeAI : ScriptedAI + { + public npc_bone_spikeAI(Creature creature) + : base(creature) + { + _hasTrappedUnit = false; + Contract.Assert(creature.GetVehicleKit()); + + SetCombatMovement(false); + } + + public override void JustDied(Unit killer) + { + TempSummon summ = me.ToTempSummon(); + if (summ) + { + Unit trapped = summ.GetSummoner(); + if (trapped) + trapped.RemoveAurasDueToSpell(Spells.Impaled); + } + + me.DespawnOrUnsummon(); + } + + public override void KilledUnit(Unit victim) + { + me.DespawnOrUnsummon(); + victim.RemoveAurasDueToSpell(Spells.Impaled); + } + + public override void IsSummonedBy(Unit summoner) + { + DoCast(summoner, Spells.Impaled); + summoner.CastSpell(me, Spells.RideVehicle, true); + _events.ScheduleEvent(Misc.EventFailBoned, 8000); + _hasTrappedUnit = true; + } + + public override void PassengerBoarded(Unit passenger, sbyte seat, bool apply) + { + if (!apply) + return; + + // @HACK - Change passenger offset to the one taken directly from sniffs + // Remove this when proper calculations are implemented. + // This fixes healing spiked people + MoveSplineInit init = new MoveSplineInit(passenger); + init.DisableTransportPathTransformations(); + init.MoveTo(-0.02206125f, -0.02132235f, 5.514783f, false); + init.Launch(); + } + + public override void UpdateAI(uint diff) + { + if (!_hasTrappedUnit) + return; + + _events.Update(diff); + + if (_events.ExecuteEvent() == Misc.EventFailBoned) + { + InstanceScript instance = me.GetInstanceScript(); + if (instance != null) + instance.SetData(DataTypes.BonedAchievement, 0); + } + } + + bool _hasTrappedUnit; + } + + public override CreatureAI GetAI(Creature creature) + { + return InstanceIcecrownCitadel.GetInstanceAI(creature); + } + } + + [Script] + class spell_marrowgar_coldflame : SpellScriptLoader + { + public spell_marrowgar_coldflame() : base("spell_marrowgar_coldflame") { } + + class spell_marrowgar_coldflame_SpellScript : SpellScript + { + void SelectTarget(List targets) + { + targets.Clear(); + // select any unit but not the tank (by owners threatlist) + Unit target = GetCaster().GetAI().SelectTarget(SelectAggroTarget.Random, 1, -GetCaster().GetObjectSize(), true, -(int)Spells.Impaled); + if (!target) + target = GetCaster().GetAI().SelectTarget(SelectAggroTarget.Random, 0, 0.0f, true); // or the tank if its solo + if (!target) + return; + + GetCaster().GetAI().SetGUID(target.GetGUID(), Misc.DataColdflameGuid); + + targets.Add(target); + } + + void HandleScriptEffect(uint effIndex) + { + PreventHitDefaultEffect(effIndex); + GetCaster().CastSpell(GetHitUnit(), (uint)GetEffectValue(), true); + } + + public override void Register() + { + OnObjectAreaTargetSelect.Add(new ObjectAreaTargetSelectHandler(SelectTarget, 0, Targets.UnitDestAreaEnemy)); + OnEffectHitTarget.Add(new EffectHandler(HandleScriptEffect, 0, SpellEffectName.ScriptEffect)); + } + } + + public override SpellScript GetSpellScript() + { + return new spell_marrowgar_coldflame_SpellScript(); + } + } + + [Script] + class spell_marrowgar_coldflame_bonestorm : SpellScriptLoader + { + public spell_marrowgar_coldflame_bonestorm() : base("spell_marrowgar_coldflame_bonestorm") { } + + class spell_marrowgar_coldflame_SpellScript : SpellScript + { + void HandleScriptEffect(uint effIndex) + { + PreventHitDefaultEffect(effIndex); + for (byte i = 0; i < 4; ++i) + GetCaster().CastSpell(GetHitUnit(), (uint)(GetEffectValue() + i), true); + } + + public override void Register() + { + OnEffectHitTarget.Add(new EffectHandler(HandleScriptEffect, 0, SpellEffectName.ScriptEffect)); + } + } + + public override SpellScript GetSpellScript() + { + return new spell_marrowgar_coldflame_SpellScript(); + } + } + + [Script] + class spell_marrowgar_coldflame_damage : SpellScriptLoader + { + public spell_marrowgar_coldflame_damage() : base("spell_marrowgar_coldflame_damage") { } + + class spell_marrowgar_coldflame_damage_AuraScript : AuraScript + { + bool CanBeAppliedOn(Unit target) + { + if (target.HasAura(Spells.Impaled)) + return false; + + if (target.GetExactDist2d(GetOwner()) > GetSpellInfo().GetEffect(target.GetMap().GetDifficultyID(), 0).CalcRadius()) + return false; + + Aura aur = target.GetAura(GetId()); + if (aur != null) + if (aur.GetOwner() != GetOwner()) + return false; + + return true; + } + + public override void Register() + { + DoCheckAreaTarget.Add(new CheckAreaTargetHandler(CanBeAppliedOn)); + } + } + + public override AuraScript GetAuraScript() + { + return new spell_marrowgar_coldflame_damage_AuraScript(); + } + } + + [Script] + class spell_marrowgar_bone_spike_graveyard : SpellScriptLoader + { + public spell_marrowgar_bone_spike_graveyard() : base("spell_marrowgar_bone_spike_graveyard") { } + + class spell_marrowgar_bone_spike_graveyard_SpellScript : SpellScript + { + public override bool Validate(SpellInfo spell) + { + return ValidateSpellInfo(Misc.BoneSpikeSummonId); + } + + public override bool Load() + { + return GetCaster().IsTypeId(TypeId.Unit) && GetCaster().IsAIEnabled; + } + + SpellCastResult CheckCast() + { + return GetCaster().GetAI().SelectTarget(SelectAggroTarget.Random, 0, new BoneSpikeTargetSelector(GetCaster().GetAI())) ? SpellCastResult.SpellCastOk : SpellCastResult.NoValidTargets; + } + + void HandleSpikes(uint effIndex) + { + PreventHitDefaultEffect(effIndex); + Creature marrowgar = GetCaster().ToCreature(); + if (marrowgar) + { + CreatureAI marrowgarAI = marrowgar.GetAI(); + byte boneSpikeCount = (byte)(Convert.ToBoolean((int)GetCaster().GetMap().GetSpawnMode() & 1) ? 3 : 1); + + List targets = marrowgarAI.SelectTargetList(new BoneSpikeTargetSelector(marrowgarAI), boneSpikeCount, SelectAggroTarget.Random); + if (targets.Empty()) + return; + + uint i = 0; + foreach (var target in targets) + { + target.CastSpell(target, Misc.BoneSpikeSummonId[i], true); + i++; + } + + marrowgarAI.Talk(Texts.SayBonespike); + } + } + + public override void Register() + { + OnCheckCast.Add(new CheckCastHandler(CheckCast)); + OnEffectHitTarget.Add(new EffectHandler(HandleSpikes, 1, SpellEffectName.ApplyAura)); + } + } + + public override SpellScript GetSpellScript() + { + return new spell_marrowgar_bone_spike_graveyard_SpellScript(); + } + } + + [Script] + class spell_marrowgar_bone_storm : SpellScriptLoader + { + public spell_marrowgar_bone_storm() : base("spell_marrowgar_bone_storm") { } + + class spell_marrowgar_bone_storm_SpellScript : SpellScript + { + void RecalculateDamage() + { + SetHitDamage((int)(GetHitDamage() / Math.Max(Math.Sqrt(GetHitUnit().GetExactDist2d(GetCaster())), 1.0f))); + } + + public override void Register() + { + OnHit.Add(new HitHandler(RecalculateDamage)); + } + } + + public override SpellScript GetSpellScript() + { + return new spell_marrowgar_bone_storm_SpellScript(); + } + } + + [Script] + class spell_marrowgar_bone_slice : SpellScriptLoader + { + public spell_marrowgar_bone_slice() : base("spell_marrowgar_bone_slice") { } + + class spell_marrowgar_bone_slice_SpellScript : SpellScript + { + public override bool Load() + { + _targetCount = 0; + return true; + } + + void ClearSpikeImmunities() + { + GetCaster().GetAI().DoAction(Misc.ActionClearSpikeImmunities); + } + + void CountTargets(List targets) + { + _targetCount = (uint)Math.Min(targets.Count, GetSpellInfo().MaxAffectedTargets); + } + + void SplitDamage() + { + // Mark the unit as hit, even if the spell missed or was dodged/parried + GetCaster().GetAI().SetGUID(GetHitUnit().GetGUID(), Misc.DataSpikeImmune); + + if (_targetCount == 0) + return; // This spell can miss all targets + + SetHitDamage((int)(GetHitDamage() / _targetCount)); + } + + public override void Register() + { + BeforeCast.Add(new CastHandler(ClearSpikeImmunities)); + OnObjectAreaTargetSelect.Add(new ObjectAreaTargetSelectHandler(CountTargets, 0, Targets.UnitDestAreaEnemy)); + OnHit.Add(new HitHandler(SplitDamage)); + } + + uint _targetCount; + } + + public override SpellScript GetSpellScript() + { + return new spell_marrowgar_bone_slice_SpellScript(); + } + } + } +} diff --git a/Scripts/Northrend/IcecrownCitadel/ProfessorPutricide.cs b/Scripts/Northrend/IcecrownCitadel/ProfessorPutricide.cs new file mode 100644 index 000000000..bc519edd4 --- /dev/null +++ b/Scripts/Northrend/IcecrownCitadel/ProfessorPutricide.cs @@ -0,0 +1,1524 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using Game.Entities; +using Game.AI; +using Game.Scripting; +using Game.Spells; +using Framework.Constants; +using Game.Maps; +using Game.DataStorage; + +namespace Scripts.Northrend.IcecrownCitadel +{ + using static IccConst; + using static ProfessorPutricideConst; + + class ProfessorPutricideConst + { + // Festergut + public const uint EventFestergutDies = 1; + public const uint EventFestergutGoo = 2; + + public const uint SayFestergutGaseousBlight = 0; + public const uint SayFestergutDeath = 1; + + public const uint SpellReleaseGasVisual = 69125; + public const uint SpellGaseousBlightLarge = 69157; + public const uint SpellGaseousBlightMedium = 69162; + public const uint SpellGaseousBlightSmall = 69164; + public const uint SpellMalleableGooH = 72296; + public const uint SpellMalleableGooSummon = 72299; + + + // Rotface + public const uint EventRotfaceDies = 3; + public const uint EventRotfaceOozeFlood = 5; + + public const uint SayRotfaceOozeFlood = 2; + public const uint SayRotfaceDeath = 3; + + // Professor Putricide + public const uint EventBerserk = 6; // All Phases + public const uint EventSlimePuddle = 7; // All Phases + public const uint EventUnstableExperiment = 8; // P1 && P2 + public const uint EventTearGas = 9; // Phase Transition Not Heroic + public const uint EventResumeAttack = 10; + public const uint EventMalleableGoo = 11; + public const uint EventChokingGasBomb = 12; + public const uint EventUnboundPlague = 13; + public const uint EventMutatedPlague = 14; + public const uint EventPhaseTransition = 15; + + public const uint SayAggro = 4; + public const uint EmoteUnstableExperiment = 5; + public const uint SayPhaseTransitionHeroic = 6; + public const uint SayTransform1 = 7; + public const uint SayTransform2 = 8; // Always Used For Phase2 Change; Do Not Group With public const uint SayTransform1 + public const uint EmoteMalleableGoo = 9; + public const uint EmoteChokingGasBomb = 10; + public const uint SayKill = 11; + public const uint SayBerserk = 12; + public const uint SayDeath = 13; + + public const uint SpellSlimePuddleTrigger = 70341; + public const uint SpellMalleableGoo = 70852; + public const uint SpellUnstableExperiment = 70351; + public const uint SpellTearGas = 71617; // Phase Transition + public const uint SpellTearGasCreature = 71618; + public const uint SpellTearGasCancel = 71620; + public const uint SpellTearGasPeriodicTrigger = 73170; + public const uint SpellCreateConcoction = 71621; + public const uint SpellGuzzlePotions = 71893; + public const uint SpellOozeTankProtection = 71770; // Protects The Tank + public const uint SpellChokingGasBomb = 71255; + public const uint SpellOozeVariable = 74118; + public const uint SpellGasVariable = 74119; + public const uint SpellUnboundPlague = 70911; + public const uint SpellUnboundPlagueSearcher = 70917; + public const uint SpellPlagueSickness = 70953; + public const uint SpellUnboundPlagueProtection = 70955; + public const uint SpellMutatedPlague = 72451; + public const uint SpellMutatedPlagueClear = 72618; + + // Slime Puddle + public const uint SpellGrowStacker = 70345; + public const uint SpellGrow = 70347; + public const uint SpellSlimePuddleAura = 70343; + + // Gas Cloud + public const uint SpellGaseousBloatProc = 70215; + public const uint SpellGaseousBloat = 70672; + public const uint SpellGaseousBloatProtection = 70812; + public const uint SpellExpungedGas = 70701; + + // Volatile Ooze + public const uint SpellOozeEruption = 70492; + public const uint SpellVolatileOozeAdhesive = 70447; + public const uint SpellOozeEruptionSearchPeriodic = 70457; + public const uint SpellVolatileOozeProtection = 70530; + + // Choking Gas Bomb + public const uint SpellChokingGasBombPeriodic = 71259; + public const uint SpellChokingGasExplosionTrigger = 71280; + + // Mutated Abomination Vehicle + public const uint SpellAbominationVehiclePowerDrain = 70385; + public const uint SpellMutatedTransformation = 70311; + public const uint SpellMutatedTransformationDamage = 70405; + public const uint SpellMutatedTransformationName = 72401; + + // Unholy Infusion + public const uint SpellUnholyInfusionCredit = 71518; + + public static Position festergutWatchPos = new Position(4324.820f, 3166.03f, 389.3831f, 3.316126f); //emote 432 (release gas) + public static Position rotfaceWatchPos = new Position(4390.371f, 3164.50f, 389.3890f, 5.497787f); //emote 432 (release ooze) + public static Position tablePos = new Position(4356.190f, 3262.90f, 389.4820f, 1.483530f); + + //Points + public const uint PointFestergut = 366260; + public const uint PointRotface = 366270; + public const uint PointTable = 366780; + + //Data + public const uint DataExperimentStage = 1; + public const uint DataPhase = 2; + public const uint DataAbomination = 3; + } + + enum Phases + { + None = 0, + Festergut = 1, + Rotface = 2, + Combat1 = 4, + Combat2 = 5, + Combat3 = 6 + } + + class AbominationDespawner + { + public AbominationDespawner(Unit owner) + { + _owner = owner; + } + + public bool Invoke(ObjectGuid guid) + { + Unit summon = Global.ObjAccessor.GetUnit(_owner, guid); + if (summon) + { + if (summon.GetEntry() == NPC_MUTATED_ABOMINATION_10 || summon.GetEntry() == NPC_MUTATED_ABOMINATION_25) + { + Vehicle veh = summon.GetVehicleKit(); + if (veh) + veh.RemoveAllPassengers(); // also despawns the vehicle + + // Found unit is Mutated Abomination, remove it + return true; + } + + // Found unit is not Mutated Abomintaion, leave it + return false; + } + + // No unit found, remove from SummonList + return true; + } + + Unit _owner; + } + + struct RotfaceHeightCheck + { + public RotfaceHeightCheck(Creature rotface) + { + _rotface = rotface; + } + + public bool Invoke(Creature stalker) + { + return stalker.GetPositionZ() < _rotface.GetPositionZ() + 5.0f; + } + + Creature _rotface; + } + + class boss_professor_putricide : CreatureScript + { + public boss_professor_putricide() : base("boss_professor_putricide") { } + + class boss_professor_putricideAI : BossAI + { + public boss_professor_putricideAI(Creature creature) : base(creature, DataTypes.ProfessorPutricide) + { + _baseSpeed = creature.GetSpeedRate(UnitMoveType.Run); + _experimentState = EXPERIMENT_STATE_OOZE; + + _phase = Phases.None; + _oozeFloodStage = 0; + } + + public override void Reset() + { + if (!(_events.IsInPhase((byte)Phases.Rotface) || _events.IsInPhase((byte)Phases.Festergut))) + instance.SetBossState(DataTypes.ProfessorPutricide, EncounterState.NotStarted); + instance.SetData(DataTypes.NauseaAchievement, 1); + + _events.Reset(); + summons.DespawnAll(); + SetPhase(Phases.Combat1); + _experimentState = EXPERIMENT_STATE_OOZE; + me.SetReactState(ReactStates.Defensive); + me.SetWalk(false); + if (me.GetMotionMaster().GetCurrentMovementGeneratorType() == MovementGeneratorType.Point) + me.GetMotionMaster().MovementExpired(); + + if (instance.GetBossState(DATA_ROTFACE) == EncounterState.Done && instance.GetBossState(DataTypes.Festergut) == EncounterState.Done) + me.RemoveFlag(UnitFields.Flags, UnitFlags.ImmuneToPc | UnitFlags.NotSelectable); + } + + public override void EnterCombat(Unit who) + { + if (_events.IsInPhase((byte)Phases.Rotface) || _events.IsInPhase((byte)Phases.Festergut)) + return; + + if (!instance.CheckRequiredBosses(DataTypes.ProfessorPutricide, who.ToPlayer())) + { + EnterEvadeMode(); + instance.DoCastSpellOnPlayers(LIGHT_S_HAMMER_TELEPORT); + return; + } + + me.setActive(true); + _events.Reset(); + _events.ScheduleEvent(EVENT_BERSERK, 600000); + _events.ScheduleEvent(EVENT_SLIME_PUDDLE, 10000); + _events.ScheduleEvent(EVENT_UNSTABLE_EXPERIMENT, RandomHelper.URand(30000, 35000)); + if (IsHeroic()) + _events.ScheduleEvent(EVENT_UNBOUND_PLAGUE, 20000); + + SetPhase(Phases.Combat1); + Talk(SAY_AGGRO); + DoCast(me, SPELL_OOZE_TANK_PROTECTION, true); + DoZoneInCombat(me); + + instance.SetBossState(DataTypes.ProfessorPutricide, EncounterState.InProgress); + } + + public override void JustReachedHome() + { + _JustReachedHome(); + me.SetWalk(false); + if (_events.IsInPhase((byte)Phases.Combat1) || _events.IsInPhase((byte)Phases.Combat2) || _events.IsInPhase((byte)Phases.Combat3)) + instance.SetBossState(DataTypes.ProfessorPutricide, EncounterState.Fail); + } + + public override void KilledUnit(Unit victim) + { + if (victim.IsPlayer()) + Talk(SAY_KILL); + } + + public override void JustDied(Unit killer) + { + _JustDied(); + Talk(SAY_DEATH); + + if (Is25ManRaid() && me.HasAura(SPELL_SHADOWS_FATE)) + DoCastAOE(SPELL_UNHOLY_INFUSION_CREDIT, true); + + DoCast(SPELL_MUTATED_PLAGUE_CLEAR); + } + + public override void JustSummoned(Creature summon) + { + summons.Summon(summon); + switch (summon.GetEntry()) + { + case NPC_MALLEABLE_OOZE_STALKER: + DoCast(summon, SPELL_MALLEABLE_GOO_H); + return; + case NPC_GROWING_OOZE_PUDDLE: + summon.CastSpell(summon, SPELL_GROW_STACKER, true); + summon.CastSpell(summon, SPELL_SLIME_PUDDLE_AURA, true); + // blizzard casts this spell 7 times initially (confirmed in sniff) + for (byte i = 0; i < 7; ++i) + summon.CastSpell(summon, SPELL_GROW, true); + break; + case NPC_GAS_CLOUD: + // no possible aura seen in sniff adding the aurastate + summon.ModifyAuraState(AuraStateType.Unk22, true); + summon.CastSpell(summon, SPELL_GASEOUS_BLOAT_PROC, true); + summon.ApplySpellImmune(0, SpellImmunity.Effect, SpellEffectName.KnockBack, true); + summon.SetReactState(ReactStates.Passive); + break; + case NPC_VOLATILE_OOZE: + // no possible aura seen in sniff adding the aurastate + summon.ModifyAuraState(AuraStateType.Unk19, true); + summon.CastSpell(summon, SPELL_OOZE_ERUPTION_SEARCH_PERIODIC, true); + summon.ApplySpellImmune(0, SpellImmunity.Effect, SpellEffectName.KnockBack, true); + summon.SetReactState(ReactStates.Passive); + break; + case NPC_CHOKING_GAS_BOMB: + summon.CastSpell(summon, SPELL_CHOKING_GAS_BOMB_PERIODIC, true); + summon.CastSpell(summon, SPELL_CHOKING_GAS_EXPLOSION_TRIGGER, true); + return; + case NPC_MUTATED_ABOMINATION_10: + case NPC_MUTATED_ABOMINATION_25: + return; + default: + break; + } + + if (me.IsInCombat()) + DoZoneInCombat(summon); + } + + public override void DamageTaken(Unit attacker, ref uint damage) + { + switch (_phase) + { + case Phases.Combat1: + if (HealthAbovePct(80)) + return; + me.SetReactState(ReactStates.Passive); + DoAction(SharedActions.ChangePhase); + break; + case Phases.Combat2: + if (HealthAbovePct(35)) + return; + me.SetReactState(ReactStates.Passive); + DoAction(SharedActions.ChangePhase); + break; + default: + break; + } + } + + public override void MovementInform(MovementGeneratorType type, uint id) + { + if (type != MovementGeneratorType.Point) + return; + switch (id) + { + case POINT_FESTERGUT: + instance.SetBossState(DataTypes.Festergut, EncounterState.InProgress); // needed here for delayed gate close + me.SetSpeed(UnitMoveType.Run, _baseSpeed, true); + DoAction(SharedActions.FestergutGas); + Creature festergut = ObjectAccessor.GetCreature(me, instance.GetGuidData(DataTypes.Festergut)); + if (festergut) + festergut.CastSpell(festergut, SPELL_GASEOUS_BLIGHT_LARGE, false, null, null, festergut.GetGUID()); + break; + case POINT_ROTFACE: + instance.SetBossState(DATA_ROTFACE, EncounterState.InProgress); // needed here for delayed gate close + me.SetSpeed(UnitMoveType.Run, _baseSpeed, true); + DoAction(SharedActions.RotfaceOoze); + _events.ScheduleEvent(EVENT_ROTFACE_OOZE_FLOOD, 25000, 0, Phases.Rotface); + break; + case POINT_TABLE: + // stop attack + me.GetMotionMaster().MoveIdle(); + me.SetSpeed(UnitMoveType.Run, _baseSpeed, true); + GameObject table = ObjectAccessor.GetGameObject(me, instance.GetGuidData(DataTypes.PutricideTable)); + if (table) + me.SetFacingToObject(table); + // operating on new phase already + switch (_phase) + { + case Phases.Combat2: + { + SpellInfo spell = Global.SpellMgr.GetSpellInfo(SPELL_CREATE_CONCOCTION); + DoCast(me, SPELL_CREATE_CONCOCTION); + _events.ScheduleEvent(EVENT_PHASE_TRANSITION, spell.CalcCastTime() + 100); + break; + } + case Phases.Combat3: + { + SpellInfo spell = Global.SpellMgr.GetSpellInfo(SPELL_GUZZLE_POTIONS); + DoCast(me, SPELL_GUZZLE_POTIONS); + _events.ScheduleEvent(EVENT_PHASE_TRANSITION, spell.CalcCastTime() + 100); + break; + } + default: + break; + } + break; + default: + break; + } + } + + public override void DoAction(int action) + { + switch (action) + { + case SharedActions.FestergutCombat: + SetPhase(Phases.Festergut); + me.SetSpeed(UnitMoveType.Run, _baseSpeed * 2.0f, true); + me.GetMotionMaster().MovePoint(POINT_FESTERGUT, ProfessorPutricideConst.festergutWatchPos); + me.SetReactState(ReactStates.Passive); + DoZoneInCombat(me); + if (IsHeroic()) + _events.ScheduleEvent(EVENT_FESTERGUT_GOO, RandomHelper.URand(13000, 18000), 0, Phases.Festergut); + break; + case SharedActions.FestergutGas: + Talk(SAY_FESTERGUT_GASEOUS_BLIGHT); + DoCast(me, SPELL_RELEASE_GAS_VISUAL, true); + break; + case SharedActions.FestergutDeath: + _events.ScheduleEvent(EVENT_FESTERGUT_DIES, 4000, 0, Phases.Festergut); + break; + case SharedActions.RotfaceCombat: + { + SetPhase(Phases.Rotface); + me.SetSpeed(UnitMoveType.Run, _baseSpeed * 2.0f, true); + me.GetMotionMaster().MovePoint(POINT_ROTFACE, rotfaceWatchPos); + me.SetReactState(ReactStates.Passive); + _oozeFloodStage = 0; + DoZoneInCombat(me); + // init random sequence of floods + Creature rotface = ObjectAccessor.GetCreature(me, instance.GetGuidData(DATA_ROTFACE)); + if (rotface) + { + List list = new List(); + rotface.GetCreatureListWithEntryInGrid(list, NPC_PUDDLE_STALKER, 50.0f); + list.RemoveAll(new RotfaceHeightCheck(rotface).Invoke); + if (list.Count() > 4) + { + list.Sort(new ObjectDistanceOrderPred(rotface)); + list.RemoveRange(4, list.Count - 1); + } + + byte i = 0; + while (!list.Empty()) + { + var itr = list[RandomHelper.IRand(0, list.Count - 1)]; + _oozeFloodDummyGUIDs[i++] = itr.GetGUID(); + list.Remove(itr); + } + } + break; + } + case SharedActions.RotfaceOoze: + Talk(SAY_ROTFACE_OOZE_FLOOD); + Creature dummy = ObjectAccessor.GetCreature(me, _oozeFloodDummyGUIDs[_oozeFloodStage]); + if (dummy) + dummy.CastSpell(dummy, oozeFloodSpells[_oozeFloodStage], true, null, null, me.GetGUID()); // cast from self for LoS (with prof's GUID for logs) + if (++_oozeFloodStage == 4) + _oozeFloodStage = 0; + break; + case SharedActions.RotfaceDeath: + _events.ScheduleEvent(EVENT_ROTFACE_DIES, 4500, 0, Phases.Rotface); + break; + case SharedActions.ChangePhase: + me.SetSpeed(UnitMoveType.Run, _baseSpeed * 2.0f, true); + _events.DelayEvents(30000); + me.AttackStop(); + if (!IsHeroic()) + { + DoCast(me, SPELL_TEAR_GAS); + _events.ScheduleEvent(EVENT_TEAR_GAS, 2500); + } + else + { + Talk(SAY_PHASE_TRANSITION_HEROIC); + DoCast(me, SPELL_UNSTABLE_EXPERIMENT, true); + DoCast(me, SPELL_UNSTABLE_EXPERIMENT, true); + // cast variables + if (Is25ManRaid()) + { + List targetList = new List(); + { + var threatlist = me.GetThreatManager().getThreatList(); + foreach (var itr in threatlist) + if (itr.getTarget().IsPlayer()) + targetList.Add(itr.getTarget()); + } + + int half = targetList.Count / 2; + // half gets ooze variable + while (half < targetList.Count) + { + var itr = targetList[RandomHelper.IRand(0, targetList.Count - 1)]; + itr.CastSpell(itr, SPELL_OOZE_VARIABLE, true); + targetList.Remove(itr); + } + // and half gets gas + foreach (var itr in targetList) + itr.CastSpell(itr, SPELL_GAS_VARIABLE, true); + } + me.GetMotionMaster().MovePoint(POINT_TABLE, ProfessorPutricideConst.tablePos); + } + switch (_phase) + { + case Phases.Combat1: + SetPhase(Phases.Combat2); + _events.ScheduleEvent(EVENT_MALLEABLE_GOO, RandomHelper.URand(21000, 26000)); + _events.ScheduleEvent(EVENT_CHOKING_GAS_BOMB, RandomHelper.URand(35000, 40000)); + break; + case Phases.Combat2: + SetPhase(Phases.Combat3); + _events.ScheduleEvent(EVENT_MUTATED_PLAGUE, 25000); + _events.CancelEvent(EVENT_UNSTABLE_EXPERIMENT); + break; + default: + break; + } + break; + default: + break; + } + } + + public override uint GetData(uint type) + { + switch (type) + { + case DataExperimentStage: + return _experimentState ? 1 : 0u; + case DataPhase: + return (uint)_phase; + case DataAbomination: + return (summons.HasEntry(NPC_MUTATED_ABOMINATION_10) || summons.HasEntry(NPC_MUTATED_ABOMINATION_25)) ? 1 : 0u; + default: + break; + } + + return 0; + } + + public override void SetData(uint id, uint data) + { + if (id == DataExperimentStage) + _experimentState = data != 0; + } + + public override void UpdateAI(uint diff) + { + if ((!(_events.IsInPhase((byte)Phases.Rotface) || _events.IsInPhase((byte)Phases.Festergut)) && !UpdateVictim()) || !CheckInRoom()) + return; + + _events.Update(diff); + + if (me.HasUnitState(UnitState.Casting)) + return; + _events.ExecuteEvents(eventId => + { + switch (eventId) + { + case EVENT_FESTERGUT_DIES: + Talk(SAY_FESTERGUT_DEATH); + EnterEvadeMode(); + break; + case EVENT_FESTERGUT_GOO: + me.CastCustomSpell(SPELL_MALLEABLE_GOO_SUMMON, SpellValueMod.MaxTargets, 1, null, true); + _events.ScheduleEvent(EVENT_FESTERGUT_GOO, (Is25ManRaid() ? 10000 : 30000) + RandomHelper.URand(0, 5000), 0, Phases.Festergut); + break; + case EVENT_ROTFACE_DIES: + Talk(SAY_ROTFACE_DEATH); + EnterEvadeMode(); + break; + case EVENT_ROTFACE_OOZE_FLOOD: + DoAction(SharedActions.RotfaceOoze); + _events.ScheduleEvent(EVENT_ROTFACE_OOZE_FLOOD, 25000, 0, Phases.Rotface); + break; + case EVENT_BERSERK: + Talk(SAY_BERSERK); + DoCast(me, SPELL_BERSERK2); + break; + case EVENT_SLIME_PUDDLE: + { + List targets = SelectTargetList(2, SelectAggroTarget.Random, 0.0f, true); + if (!targets.Empty()) + { + foreach (var itr in targets) + DoCast(itr, SPELL_SLIME_PUDDLE_TRIGGER); + } + _events.ScheduleEvent(EVENT_SLIME_PUDDLE, 35000); + break; + } + case EVENT_UNSTABLE_EXPERIMENT: + Talk(EMOTE_UNSTABLE_EXPERIMENT); + DoCast(me, SPELL_UNSTABLE_EXPERIMENT); + _events.ScheduleEvent(EVENT_UNSTABLE_EXPERIMENT, RandomHelper.URand(35000, 40000)); + break; + case EVENT_TEAR_GAS: + me.GetMotionMaster().MovePoint(POINT_TABLE, ProfessorPutricideConst.tablePos); + DoCast(me, SPELL_TEAR_GAS_PERIODIC_TRIGGER, true); + break; + case EVENT_RESUME_ATTACK: + me.SetReactState(ReactStates.Defensive); + AttackStart(me.GetVictim()); + // remove Tear Gas + me.RemoveAurasDueToSpell(SPELL_TEAR_GAS_PERIODIC_TRIGGER); + instance.DoRemoveAurasDueToSpellOnPlayers(71615); + DoCastAOE(SPELL_TEAR_GAS_CANCEL); + instance.DoRemoveAurasDueToSpellOnPlayers(SPELL_GAS_VARIABLE); + instance.DoRemoveAurasDueToSpellOnPlayers(SPELL_OOZE_VARIABLE); + break; + case EVENT_MALLEABLE_GOO: + if (Is25ManRaid()) + { + List targets = SelectTargetList(2, SelectAggroTarget.Random, -7.0f, true); + if (!targets.Empty()) + { + Talk(EMOTE_MALLEABLE_GOO); + foreach (var itr in targets) + DoCast(itr, SPELL_MALLEABLE_GOO); + } + } + else + { + Unit target = SelectTarget(SelectAggroTarget.Random, 1, -7.0f, true); + if (target) + { + Talk(EMOTE_MALLEABLE_GOO); + DoCast(target, SPELL_MALLEABLE_GOO); + } + } + _events.ScheduleEvent(EVENT_MALLEABLE_GOO, RandomHelper.URand(25000, 30000)); + break; + case EVENT_CHOKING_GAS_BOMB: + Talk(EMOTE_CHOKING_GAS_BOMB); + DoCast(me, SPELL_CHOKING_GAS_BOMB); + _events.ScheduleEvent(EVENT_CHOKING_GAS_BOMB, RandomHelper.URand(35000, 40000)); + break; + case EVENT_UNBOUND_PLAGUE: + { + Unit target = SelectTarget(SelectAggroTarget.Random, 0, new NonTankTargetSelector(me)); + if (target) + { + DoCast(target, SPELL_UNBOUND_PLAGUE); + DoCast(target, SPELL_UNBOUND_PLAGUE_SEARCHER); + } + _events.ScheduleEvent(EVENT_UNBOUND_PLAGUE, 90000); + } + break; + case EVENT_MUTATED_PLAGUE: + DoCastVictim(SPELL_MUTATED_PLAGUE); + _events.ScheduleEvent(EVENT_MUTATED_PLAGUE, 10000); + break; + case EVENT_PHASE_TRANSITION: + { + switch (_phase) + { + case Phases.Combat2: + { + Creature face = me.FindNearestCreature(NPC_TEAR_GAS_TARGET_STALKER, 50.0f); + if (face) + me.SetFacingToObject(face); + me.HandleEmoteCommand(EMOTE_ONESHOT_KNEEL); + Talk(SAY_TRANSFORM_1); + _events.ScheduleEvent(EVENT_RESUME_ATTACK, 5500, 0, Phases.Combat2); + } + break; + case Phases.Combat3: + { Creature face = me.FindNearestCreature(NPC_TEAR_GAS_TARGET_STALKER, 50.0f); + if (face) + me.SetFacingToObject(face); + me.HandleEmoteCommand(EMOTE_ONESHOT_KNEEL); + Talk(SAY_TRANSFORM_2); + summons.DespawnIf(new AbominationDespawner(me).Invoke); + _events.ScheduleEvent(EVENT_RESUME_ATTACK, 8500, 0, Phases.Combat3); + } + break; + default: + break; + } + } + break; + default: + break; + } + }); + + DoMeleeAttackIfReady(); + } + + void SetPhase(Phases newPhase) + { + _phase = newPhase; + _events.SetPhase((byte)newPhase); + } + + ObjectGuid[] _oozeFloodDummyGUIDs = new ObjectGuid[4]; + Phases _phase; // external of EventMap because event phase gets reset on evade + float _baseSpeed; + byte _oozeFloodStage; + bool _experimentState; + } + + public override CreatureAI GetAI(Creature creature) + { + return GetIcecrownCitadelAI(creature); + } + } + + class npc_putricide_oozeAI : ScriptedAI + { + public npc_putricide_oozeAI(Creature creature, uint hitTargetSpellId) : base(creature) + { + _hitTargetSpellId = hitTargetSpellId; + _newTargetSelectTimer = 0; + + } + + public override void SpellHitTarget(Unit target, SpellInfo spell) + { + if (_newTargetSelectTimer == 0 && spell.Id == _hitTargetSpellId) + _newTargetSelectTimer = 1000; + } + + public override void SpellHit(Unit caster, SpellInfo spell) + { + if (spell.Id == SPELL_TEAR_GAS_CREATURE) + _newTargetSelectTimer = 1000; + } + + public override void UpdateAI(uint diff) + { + if (!UpdateVictim() && _newTargetSelectTimer == 0) + return; + + if (_newTargetSelectTimer == 0 && !me.IsNonMeleeSpellCast(false, false, true, false, true)) + _newTargetSelectTimer = 1000; + + DoMeleeAttackIfReady(); + + if (_newTargetSelectTimer == 0) + return; + + if (me.HasAura(SPELL_TEAR_GAS_CREATURE)) + return; + + if (_newTargetSelectTimer <= diff) + { + _newTargetSelectTimer = 0; + CastMainSpell(); + } + else + _newTargetSelectTimer -= diff; + } + + public virtual void CastMainSpell() { } + + uint _hitTargetSpellId; + uint _newTargetSelectTimer; + } + + class npc_volatile_ooze : CreatureScript + { + public npc_volatile_ooze() : base("npc_volatile_ooze") { } + + class npc_volatile_oozeAI : npc_putricide_oozeAI + { + public npc_volatile_oozeAI(Creature creature) : base(creature, SPELL_OOZE_ERUPTION) + { + } + + public override void CastMainSpell() + { + me.CastSpell(me, ProfessorPutricideConst.SpellVolatileOozeAdhesive, false); + } + } + + public override CreatureAI GetAI(Creature creature) + { + return GetIcecrownCitadelAI(creature); + } + } + + class npc_gas_cloud : CreatureScript + { + public npc_gas_cloud() : base("npc_gas_cloud") { } + + class npc_gas_cloudAI : npc_putricide_oozeAI + { + public npc_gas_cloudAI(Creature creature) : base(creature, SPELL_EXPUNGED_GAS) + { + _newTargetSelectTimer = 0; + } + + public override void CastMainSpell() + { + me.CastCustomSpell(SPELL_GASEOUS_BLOAT, SpellValueMod.AuraStack, 10, me, false); + } + + uint _newTargetSelectTimer; + } + + public override CreatureAI GetAI(Creature creature) + { + return GetIcecrownCitadelAI(creature); + } + } + + class spell_putricide_gaseous_bloat : SpellScriptLoader + { + public spell_putricide_gaseous_bloat() : base("spell_putricide_gaseous_bloat") { } + + class spell_putricide_gaseous_bloat_AuraScript : AuraScript + { + void HandleExtraEffect(AuraEffect aurEff) + { + Unit target = GetTarget(); + Unit caster = GetCaster(); + if (caster) + { + target.RemoveAuraFromStack(GetSpellInfo().Id, GetCasterGUID()); + if (!target.HasAura(GetId())) + caster.CastCustomSpell(SPELL_GASEOUS_BLOAT, SpellValueMod.AuraStack, 10, caster, false); + } + } + + public override void Register() + { + OnEffectPeriodic.Add(new EffectPeriodicHandler(HandleExtraEffect, 0, SPELL_AURA_PERIODIC_DAMAGE)); + } + } + + public override AuraScript GetAuraScript() + { + return new spell_putricide_gaseous_bloat_AuraScript(); + } + } + + class spell_putricide_ooze_channel : SpellScriptLoader + { + public spell_putricide_ooze_channel() : base("spell_putricide_ooze_channel") { } + + class spell_putricide_ooze_channel_SpellScript : SpellScript + { + public spell_putricide_ooze_channel_SpellScript() + { + _target = null; + } + + public override bool Validate(SpellInfo spell) + { + if (spell.ExcludeTargetAuraSpell == 0) + return false; + if (!Global.SpellMgr.HasSpellInfo(spell.ExcludeTargetAuraSpell)) + return false; + return true; + } + + // set up initial variables and check if caster is creature + // this will let use safely use ToCreature() casts in entire script + public override bool Load() + { + return GetCaster().IsTypeId(TypeId.Unit); + } + + void SelectTarget(List targets) + { + if (targets.Empty()) + { + FinishCast(SpellCastResult.NoValidTargets); + GetCaster().ToCreature().DespawnOrUnsummon(1); // despawn next update + return; + } + + WorldObject target = targets.PickRandom(); + targets.Clear(); + targets.Add(target); + _target = target; + } + + void SetTarget(List targets) + { + targets.Clear(); + if (_target) + targets.Add(_target); + } + + void StartAttack() + { + GetCaster().ClearUnitState(UnitState.Casting); + GetCaster().DeleteThreatList(); + GetCaster().ToCreature().GetAI().AttackStart(GetHitUnit()); + GetCaster().AddThreat(GetHitUnit(), 500000000.0f); // value seen in sniff + } + + public override void Register() + { + OnObjectAreaTargetSelect.Add(new ObjectAreaTargetSelectHandler(SelectTarget, 0, Targets.UnitSrcAreaEnemy)); + OnObjectAreaTargetSelect.Add(new ObjectAreaTargetSelectHandler(SetTarget, 1, Targets.UnitSrcAreaEnemy)); + OnObjectAreaTargetSelect.Add(new ObjectAreaTargetSelectHandler(SetTarget, 2, Targets.UnitSrcAreaEnemy)); + AfterHit.Add(new HitHandler(StartAttack)); + } + + WorldObject _target; + } + + public override SpellScript GetSpellScript() + { + return new spell_putricide_ooze_channel_SpellScript(); + } + } + + class ExactDistanceCheck + { + public ExactDistanceCheck(Unit source, float dist) + { + _source = source; + _dist = dist; + } + + public bool Invoke(WorldObject unit) + { + return _source.GetExactDist2d(unit) > _dist; + } + + Unit _source; + float _dist; + } + + class spell_putricide_slime_puddle : SpellScriptLoader + { + public spell_putricide_slime_puddle() : base("spell_putricide_slime_puddle") { } + + class spell_putricide_slime_puddle_SpellScript : SpellScript + { + void ScaleRange(List targets) + { + targets.RemoveAll(new ExactDistanceCheck(GetCaster(), 2.5f * GetCaster().GetObjectScale()).Invoke); + } + + public override void Register() + { + OnObjectAreaTargetSelect.Add(new ObjectAreaTargetSelectHandler(ScaleRange, 0, Targets.UnitDestAreaEntry)); + OnObjectAreaTargetSelect.Add(new ObjectAreaTargetSelectHandler(ScaleRange, 1, Targets.UnitDestAreaEntry)); + } + } + + public override SpellScript GetSpellScript() + { + return new spell_putricide_slime_puddle_SpellScript(); + } + } + + // this is here only because on retail you dont actually enter HEROIC mode for ICC + class spell_putricide_slime_puddle_aura : SpellScriptLoader + { + public spell_putricide_slime_puddle_aura() : base("spell_putricide_slime_puddle_aura") { } + + class spell_putricide_slime_puddle_aura_SpellScript : SpellScript + { + void ReplaceAura() + { + Unit target = GetHitUnit(); + if (target) + GetCaster().AddAura(Convert.ToBoolean((int)GetCaster().GetMap().GetSpawnMode() & 1) ? 72456 : 70346u, target); + } + + public override void Register() + { + OnHit.Add(new HitHandler(ReplaceAura)); + } + } + + public override SpellScript GetSpellScript() + { + return new spell_putricide_slime_puddle_aura_SpellScript(); + } + } + + class spell_putricide_unstable_experiment : SpellScriptLoader + { + public spell_putricide_unstable_experiment() : base("spell_putricide_unstable_experiment") { } + + class spell_putricide_unstable_experiment_SpellScript : SpellScript + { + void HandleScript(uint effIndex) + { + PreventHitDefaultEffect(effIndex); + if (!GetCaster().IsTypeId(TypeId.Unit)) + return; + + Creature creature = GetCaster().ToCreature(); + + uint stage = creature.GetAI().GetData(DataExperimentStage); + creature.GetAI().SetData(DataExperimentStage, stage ^ 1); + + Creature target = null; + List creList = new List(); + GetCaster().GetCreatureListWithEntryInGrid(creList, NPC_ABOMINATION_WING_MAD_SCIENTIST_STALKER, 200.0f); + // 2 of them are spawned at green place - weird trick blizz + foreach (var itr in creList) + { + target = itr; + List tmp = new List(); + target.GetCreatureListWithEntryInGrid(tmp, NPC_ABOMINATION_WING_MAD_SCIENTIST_STALKER, 10.0f); + if ((stage == 0 && tmp.Count > 1) || (stage != 0 && tmp.Count == 1)) + break; + } + + GetCaster().CastSpell(target, (uint)GetSpellInfo().GetEffect(stage).CalcValue(), true, null, null, GetCaster().GetGUID()); + } + + public override void Register() + { + OnEffectHitTarget.Add(new EffectHandler(HandleScript, 0, SpellEffectName.ScriptEffect)); + } + } + + public override SpellScript GetSpellScript() + { + return new spell_putricide_unstable_experiment_SpellScript(); + } + } + + class spell_putricide_ooze_eruption_searcher : SpellScriptLoader + { + public spell_putricide_ooze_eruption_searcher() : base("spell_putricide_ooze_eruption_searcher") { } + + class spell_putricide_ooze_eruption_searcher_SpellScript : SpellScript + { + void HandleDummy(uint effIndex) + { + if (GetHitUnit().HasAura(SPELL_VOLATILE_OOZE_ADHESIVE)) + { + GetHitUnit().RemoveAurasDueToSpell(SPELL_VOLATILE_OOZE_ADHESIVE, GetCaster().GetGUID(), 0, AuraRemoveMode.EnemySpell); + GetCaster().CastSpell(GetHitUnit(), SPELL_OOZE_ERUPTION, true); + } + } + + public override void Register() + { + OnEffectHitTarget.Add(new EffectHandler(HandleDummy, 0, SpellEffectName.Dummy)); + } + } + + public override SpellScript GetSpellScript() + { + return new spell_putricide_ooze_eruption_searcher_SpellScript(); + } + } + + class spell_putricide_choking_gas_bomb : SpellScriptLoader + { + public spell_putricide_choking_gas_bomb() : base("spell_putricide_choking_gas_bomb") { } + + class spell_putricide_choking_gas_bomb_SpellScript : SpellScript + { + void HandleScript(uint effIndex) + { + uint skipIndex = RandomHelper.URand(0, 2); + foreach (SpellEffectInfo effect in GetSpellInfo().GetEffectsForDifficulty(GetCaster().GetMap().GetDifficultyID())) + { + if (effect == null || effect.EffectIndex == skipIndex) + continue; + + uint spellId = (uint)effect.CalcValue(); + GetCaster().CastSpell(GetCaster(), spellId, true, null, null, GetCaster().GetGUID()); + } + } + + public override void Register() + { + OnEffectHitTarget.Add(new EffectHandler(HandleScript, 0, SpellEffectName.ScriptEffect)); + } + } + + public override SpellScript GetSpellScript() + { + return new spell_putricide_choking_gas_bomb_SpellScript(); + } + } + + class spell_putricide_unbound_plague : SpellScriptLoader + { + public spell_putricide_unbound_plague() : base("spell_putricide_unbound_plague") { } + + class spell_putricide_unbound_plague_SpellScript : SpellScript + { + public override bool Validate(SpellInfo spell) + { + if (!Global.SpellMgr.GetSpellInfo(SPELL_UNBOUND_PLAGUE)) + return false; + if (!Global.SpellMgr.GetSpellInfo(SPELL_UNBOUND_PLAGUE_SEARCHER)) + return false; + return true; + } + + void FilterTargets(List targets) + { + AuraEffect eff = GetCaster().GetAuraEffect(SPELL_UNBOUND_PLAGUE_SEARCHER, 0); + if (eff != null) + { + if (eff.GetTickNumber() < 2) + { + targets.Clear(); + return; + } + } + + + targets.RemoveAll(new UnitAuraCheck(true, SPELL_UNBOUND_PLAGUE)); + targets.RandomResize(1); + } + + void HandleScript(uint effIndex) + { + if (!GetHitUnit()) + return; + + InstanceScript instance = GetCaster().GetInstanceScript(); + if (instance == null) + return; + + if (!GetHitUnit().HasAura(SPELL_UNBOUND_PLAGUE)) + { + Creature professor = ObjectAccessor.GetCreature(GetCaster(), instance.GetGuidData(DataTypes.ProfessorPutricide)); + if (professor) + { + Aura oldPlague = GetCaster().GetAura(SPELL_UNBOUND_PLAGUE, professor.GetGUID()); + if (oldPlague != null) + { + Aura newPlague = professor.AddAura(SPELL_UNBOUND_PLAGUE, GetHitUnit()); + if (newPlague != null) + { + newPlague.SetMaxDuration(oldPlague.GetMaxDuration()); + newPlague.SetDuration(oldPlague.GetDuration()); + oldPlague.Remove(); + GetCaster().RemoveAurasDueToSpell(SPELL_UNBOUND_PLAGUE_SEARCHER); + GetCaster().CastSpell(GetCaster(), SPELL_PLAGUE_SICKNESS, true); + GetCaster().CastSpell(GetCaster(), SPELL_UNBOUND_PLAGUE_PROTECTION, true); + professor.CastSpell(GetHitUnit(), SPELL_UNBOUND_PLAGUE_SEARCHER, true); + } + } + } + } + } + + public override void Register() + { + OnObjectAreaTargetSelect.Add(new ObjectAreaTargetSelectHandler(FilterTargets, 0, Targets.UnitSrcAreaAlly)); + OnEffectHitTarget.Add(new EffectHandler(HandleScript, 0, SpellEffectName.ScriptEffect)); + } + } + + public override SpellScript GetSpellScript() + { + return new spell_putricide_unbound_plague_SpellScript(); + } + } + + class spell_putricide_eat_ooze : SpellScriptLoader + { + public spell_putricide_eat_ooze() : base("spell_putricide_eat_ooze") { } + + class spell_putricide_eat_ooze_SpellScript : SpellScript + { + void SelectTarget(List targets) + { + if (targets.Empty()) + return; + + targets.Sort(new ObjectDistanceOrderPred(GetCaster())); + WorldObject target = targets[0]; + targets.Clear(); + targets.Add(target); + } + + void HandleScript(uint effIndex) + { + Creature target = GetHitCreature(); + if (!target) + return; + + Aura grow = target.GetAura((uint)GetEffectValue()); + if (grow != null) + { + if (grow.GetStackAmount() < 3) + { + target.RemoveAurasDueToSpell(SPELL_GROW_STACKER); + target.RemoveAura(grow); + target.DespawnOrUnsummon(1); + } + else + grow.ModStackAmount(-3); + } + } + + public override void Register() + { + OnEffectHitTarget.Add(new EffectHandler(HandleScript, 0, SpellEffectName.ScriptEffect)); + OnObjectAreaTargetSelect.Add(new ObjectAreaTargetSelectHandler(SelectTarget, 0, Targets.UnitDestAreaEntry)); + } + } + + public override SpellScript GetSpellScript() + { + return new spell_putricide_eat_ooze_SpellScript(); + } + } + + class spell_putricide_mutated_plague : SpellScriptLoader + { + public spell_putricide_mutated_plague() : base("spell_putricide_mutated_plague") { } + + class spell_putricide_mutated_plague_AuraScript : AuraScript + { + void HandleTriggerSpell(AuraEffect aurEff) + { + PreventDefaultAction(); + Unit caster = GetCaster(); + if (!caster) + return; + + uint triggerSpell = GetSpellInfo().GetEffect(aurEff.GetEffIndex()).TriggerSpell; + SpellInfo spell = Global.SpellMgr.GetSpellInfo(triggerSpell); + + int damage = spell.GetEffect(0).CalcValue(caster); + float multiplier = 2.0f; + if (Convert.ToBoolean((int)GetTarget().GetMap().GetSpawnMode() & 1)) + multiplier = 3.0f; + + damage *= (int)Math.Pow(multiplier, GetStackAmount()); + damage = (int)(damage * 1.5f); + + GetTarget().CastCustomSpell(triggerSpell, SpellValueMod.BasePoint0, damage, GetTarget(), true, null, aurEff, GetCasterGUID()); + } + + void OnRemove(AuraEffect aurEff, AuraEffectHandleModes mode) + { + uint healSpell = (uint)GetSpellInfo().GetEffect(0).CalcValue(); + SpellInfo healSpellInfo = Global.SpellMgr.GetSpellInfo(healSpell); + + if (healSpellInfo == null) + return; + + int heal = healSpellInfo.GetEffect(0).CalcValue() * GetStackAmount(); + GetTarget().CastCustomSpell(healSpell, SpellValueMod.BasePoint0, heal, GetTarget(), true, null, null, GetCasterGUID()); + } + + public override void Register() + { + OnEffectPeriodic.Add(new EffectPeriodicHandler(HandleTriggerSpell, 0, SPELL_AURA_PERIODIC_TRIGGER_SPELL)); + AfterEffectRemove.Add(new EffectApplyHandler(OnRemove, 0, SPELL_AURA_PERIODIC_TRIGGER_SPELL, AuraEffectHandleModes.Real)); + } + } + + public override AuraScript GetAuraScript() + { + return new spell_putricide_mutated_plague_AuraScript(); + } + } + + class spell_putricide_mutation_init : SpellScriptLoader + { + public spell_putricide_mutation_init() : base("spell_putricide_mutation_init") { } + + class spell_putricide_mutation_init_SpellScript : SpellScript + { + SpellCastResult CheckRequirementInternal(SpellCustomErrors extendedError) + { + InstanceScript instance = GetExplTargetUnit().GetInstanceScript(); + if (instance == null) + return SpellCastResult.CantDoThatRightNow; + + Creature professor = ObjectAccessor.GetCreature(GetExplTargetUnit(), instance.GetGuidData(DataTypes.ProfessorPutricide)); + if (!professor) + return SpellCastResult.CantDoThatRightNow; + + if (professor.GetAI().GetData(DataPhase) == (uint)Phases.Combat3 || !professor.IsAlive()) + { + extendedError = SpellCustomErrors.AllPotionsUsed; + return SpellCastResult.CustomError; + } + + if (professor.GetAI().GetData(DataAbomination) != 0) + { + extendedError = SpellCustomErrors.TooManyAbominations; + return SpellCastResult.CustomError; + } + + return SpellCastResult.SpellCastOk; + } + + SpellCastResult CheckRequirement() + { + if (!GetExplTargetUnit()) + return SpellCastResult.BadTargets; + + if (!GetExplTargetUnit().IsPlayer()) + return SpellCastResult.TargetNotPlayer; + + SpellCustomErrors extension = SpellCustomErrors.None; + SpellCastResult result = CheckRequirementInternal(extension); + if (result != SpellCastResult.SpellCastOk) + { + Spell.SendCastResult(GetExplTargetUnit().ToPlayer(), GetSpellInfo(), 0, result, extension); + return result; + } + + return SpellCastResult.SpellCastOk; + } + + public override void Register() + { + OnCheckCast.Add(new CheckCastHandler(CheckRequirement)); + } + } + + class spell_putricide_mutation_init_AuraScript : AuraScript + { + void OnRemove(AuraEffect aurEff, AuraEffectHandleModes mode) + { + uint spellId = 70311; + if (Convert.ToBoolean((int)GetTarget().GetMap().GetSpawnMode() & 1)) + spellId = 71503; + + GetTarget().CastSpell(GetTarget(), spellId, true); + } + + public override void Register() + { + AfterEffectRemove.Add(new EffectApplyHandler(OnRemove, 0, SPELL_AURA_DUMMY, AuraEffectHandleModes.Real)); + } + } + + public override SpellScript GetSpellScript() + { + return new spell_putricide_mutation_init_SpellScript(); + } + + public override AuraScript GetAuraScript() + { + return new spell_putricide_mutation_init_AuraScript(); + } + } + + class spell_putricide_mutated_transformation_dismiss : SpellScriptLoader + { + public spell_putricide_mutated_transformation_dismiss() : base("spell_putricide_mutated_transformation_dismiss") { } + + class spell_putricide_mutated_transformation_dismiss_AuraScript : AuraScript + { + void OnRemove(AuraEffect aurEff, AuraEffectHandleModes mode) + { + Vehicle veh = GetTarget().GetVehicleKit(); + if (veh) + veh.RemoveAllPassengers(); + } + + public override void Register() + { + AfterEffectRemove.Add(new EffectApplyHandler(OnRemove, 0, SPELL_AURA_PERIODIC_TRIGGER_SPELL, AuraEffectHandleModes.Real)); + } + } + + public override AuraScript GetAuraScript() + { + return new spell_putricide_mutated_transformation_dismiss_AuraScript(); + } + } + + class spell_putricide_mutated_transformation : SpellScriptLoader + { + public spell_putricide_mutated_transformation() : base("spell_putricide_mutated_transformation") { } + + class spell_putricide_mutated_transformation_SpellScript : SpellScript + { + void HandleSummon(uint effIndex) + { + PreventHitDefaultEffect(effIndex); + Unit caster = GetOriginalCaster(); + if (!caster) + return; + + InstanceScript instance = caster.GetInstanceScript(); + if (instance == null) + return; + + Creature putricide = ObjectAccessor.GetCreature(caster, instance.GetGuidData(DataTypes.ProfessorPutricide)); + if (!putricide) + return; + + if (putricide.GetAI().GetData(DataAbomination) != 0) + { + Player player = caster.ToPlayer(); + if (player) + Spell.SendCastResult(player, GetSpellInfo(), 0, SpellCastResult.CustomError, SpellCustomErrors.TooManyAbominations); + return; + } + + uint entry = (uint)GetSpellInfo().GetEffect(effIndex).MiscValue; + SummonPropertiesRecord properties = CliDB.SummonPropertiesStorage.LookupByKey(GetSpellInfo().GetEffect(effIndex).MiscValueB); + uint duration = (uint)GetSpellInfo().GetDuration(); + + Position pos = caster.GetPosition(); + TempSummon summon = caster.GetMap().SummonCreature(entry, pos, properties, duration, caster, GetSpellInfo().Id); + if (!summon || !summon.IsVehicle()) + return; + + summon.CastSpell(summon, SPELL_ABOMINATION_VEHICLE_POWER_DRAIN, true); + summon.CastSpell(summon, SPELL_MUTATED_TRANSFORMATION_DAMAGE, true); + caster.CastSpell(summon, SPELL_MUTATED_TRANSFORMATION_NAME, true); + + caster.EnterVehicle(summon, 0); // VEHICLE_SPELL_RIDE_HARDCODED is used according to sniff, this is ok + summon.SetCreatorGUID(caster.GetGUID()); + putricide.GetAI().JustSummoned(summon); + } + + public override void Register() + { + OnEffectHit.Add(new EffectHandler(HandleSummon, 0, SpellEffectName.Summon)); + } + } + + public override SpellScript GetSpellScript() + { + return new spell_putricide_mutated_transformation_SpellScript(); + } + } + + class spell_putricide_mutated_transformation_dmg : SpellScriptLoader + { + public spell_putricide_mutated_transformation_dmg() : base("spell_putricide_mutated_transformation_dmg") { } + + class spell_putricide_mutated_transformation_dmg_SpellScript : SpellScript + { + void FilterTargetsInitial(List targets) + { + Unit owner = Global.ObjAccessor.GetUnit(GetCaster(), GetCaster().GetCreatorGUID()); + if (owner) + targets.Remove(owner); + } + + public override void Register() + { + OnObjectAreaTargetSelect.Add(new ObjectAreaTargetSelectHandler(FilterTargetsInitial, 0, Targets.UnitSrcAreaAlly)); + } + } + + public override SpellScript GetSpellScript() + { + return new spell_putricide_mutated_transformation_dmg_SpellScript(); + } + } + + class spell_putricide_regurgitated_ooze : SpellScriptLoader + { + public spell_putricide_regurgitated_ooze() : base("spell_putricide_regurgitated_ooze") { } + + class spell_putricide_regurgitated_ooze_SpellScript : SpellScript + { + // the only purpose of this hook is to fail the achievement + void ExtraEffect(uint effIndex) + { + InstanceScript instance = GetCaster().GetInstanceScript(); + if (instance != null) + instance.SetData(DataTypes.NauseaAchievement, 0); + } + + public override void Register() + { + OnEffectHitTarget.Add(new EffectHandler(ExtraEffect, 0, SpellEffectName.ApplyAura)); + } + } + + public override SpellScript GetSpellScript() + { + return new spell_putricide_regurgitated_ooze_SpellScript(); + } + } + + // Removes aura with id stored in effect value + class spell_putricide_clear_aura_effect_value : SpellScriptLoader + { + public spell_putricide_clear_aura_effect_value() : base("spell_putricide_clear_aura_effect_value") { } + + class spell_putricide_clear_aura_effect_value_SpellScript : SpellScript + { + void HandleScript(uint effIndex) + { + PreventHitDefaultEffect(effIndex); + GetHitUnit().RemoveAurasDueToSpell((uint)GetEffectValue()); + } + + public override void Register() + { + OnEffectHitTarget.Add(new EffectHandler(HandleScript, 0, SpellEffectName.ScriptEffect)); + } + } + + public override SpellScript GetSpellScript() + { + return new spell_putricide_clear_aura_effect_value_SpellScript(); + } + } + + // Stinky and Precious spell, it's here because its used for both (Festergut and Rotface "pets") + class spell_stinky_precious_decimate : SpellScriptLoader + { + public spell_stinky_precious_decimate() : base("spell_stinky_precious_decimate") { } + + class spell_stinky_precious_decimate_SpellScript : SpellScript + { + void HandleScript(uint effIndex) + { + if (GetHitUnit().GetHealthPct() > GetEffectValue()) + { + uint newHealth = GetHitUnit().GetMaxHealth() * (uint)GetEffectValue() / 100; + GetHitUnit().SetHealth(newHealth); + } + } + + public override void Register() + { + OnEffectHitTarget.Add(new EffectHandler(HandleScript, 0, SpellEffectName.ScriptEffect)); + } + } + + public override SpellScript GetSpellScript() + { + return new spell_stinky_precious_decimate_SpellScript(); + } + } +} diff --git a/Scripts/Northrend/IcecrownCitadel/Sindragosa.cs b/Scripts/Northrend/IcecrownCitadel/Sindragosa.cs new file mode 100644 index 000000000..9e48ea342 --- /dev/null +++ b/Scripts/Northrend/IcecrownCitadel/Sindragosa.cs @@ -0,0 +1,35 @@ +/* + * Copyright (C) 2012-2017 CypherCore + * + * 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, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +using Game.Entities; + +namespace Scripts.Northrend.IcecrownCitadel +{ + public class Sindragosa + { + Position RimefangFlyPos = new Position(4413.309f, 2456.421f, 233.3795f, 2.890186f); + Position RimefangLandPos = new Position(4413.309f, 2456.421f, 203.3848f, 2.890186f); + Position SpinestalkerFlyPos = new Position(4418.895f, 2514.233f, 230.4864f, 3.396045f); + Position SpinestalkerLandPos = new Position(4418.895f, 2514.233f, 203.3848f, 3.396045f); + public static Position SindragosaSpawnPos = new Position(4818.700f, 2483.710f, 287.0650f, 3.089233f); + Position SindragosaFlyPos = new Position(4475.190f, 2484.570f, 234.8510f, 3.141593f); + Position SindragosaLandPos = new Position(4419.190f, 2484.570f, 203.3848f, 3.141593f); + Position SindragosaAirPos = new Position(4475.990f, 2484.430f, 247.9340f, 3.141593f); + Position SindragosaAirPosFar = new Position(4525.600f, 2485.150f, 245.0820f, 3.141593f); + Position SindragosaFlyInPos = new Position(4419.190f, 2484.360f, 232.5150f, 3.141593f); + } +} diff --git a/Scripts/Northrend/IcecrownCitadel/TheLichKing.cs b/Scripts/Northrend/IcecrownCitadel/TheLichKing.cs new file mode 100644 index 000000000..f041274b8 --- /dev/null +++ b/Scripts/Northrend/IcecrownCitadel/TheLichKing.cs @@ -0,0 +1,39 @@ +/* + * Copyright (C) 2012-2017 CypherCore + * + * 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, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +using Game.Entities; + +namespace Scripts.Northrend.IcecrownCitadel +{ + public class TheLichKing + { + Position CenterPosition = new Position(503.6282f, -2124.655f, 840.8569f, 0.0f); + Position TirionIntro = new Position(489.2970f, -2124.840f, 840.8569f, 0.0f); + Position TirionCharge = new Position(482.9019f, -2124.479f, 840.8570f, 0.0f); + Position[] LichKingIntro = + { + new Position(432.0851f, -2123.673f, 864.6582f, 0.0f), + new Position(457.8351f, -2123.423f, 841.1582f, 0.0f), + new Position(465.0730f, -2123.470f, 840.8569f, 0.0f), + }; + Position OutroPosition1 = new Position(493.6286f, -2124.569f, 840.8569f, 0.0f); + Position OutroFlying = new Position(508.9897f, -2124.561f, 845.3565f, 0.0f); + public static Position TerenasSpawn = new Position(495.5542f, -2517.012f, 1050.000f, 4.6993f); + Position TerenasSpawnHeroic = new Position(495.7080f, -2523.760f, 1050.000f, 0.0f); + public static Position SpiritWardenSpawn = new Position(495.3406f, -2529.983f, 1050.000f, 1.5592f); + } +} diff --git a/Scripts/Northrend/IcecrownCitadel/ValithriaDreamwalker.cs b/Scripts/Northrend/IcecrownCitadel/ValithriaDreamwalker.cs new file mode 100644 index 000000000..fab4cebb0 --- /dev/null +++ b/Scripts/Northrend/IcecrownCitadel/ValithriaDreamwalker.cs @@ -0,0 +1,26 @@ +/* + * Copyright (C) 2012-2017 CypherCore + * + * 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, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +using Game.Entities; + +namespace Scripts.Northrend.IcecrownCitadel +{ + public class ValithriaDreamwalker + { + public static Position ValithriaSpawnPos = new Position(4210.813f, 2484.443f, 364.9558f, 0.01745329f); + } +} diff --git a/Scripts/Northrend/Naxxramas/BossAnubrekhan.cs b/Scripts/Northrend/Naxxramas/BossAnubrekhan.cs new file mode 100644 index 000000000..42736f9eb --- /dev/null +++ b/Scripts/Northrend/Naxxramas/BossAnubrekhan.cs @@ -0,0 +1,63 @@ +/* + * Copyright (C) 2012-2017 CypherCore + * + * 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, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + + +namespace Scripts.Northrend.Naxxramas +{ + struct TextIds + { + public const uint SayAggro = 0; + public const uint SayGreet = 1; + public const uint SaySlay = 2; + + public const uint EmoteLocust = 3; + + public const uint EmoteFrenzy = 0; + public const uint EmoteSpawn = 1; + public const uint EmoteScarab = 2; + } + + struct EventIds + { + public const uint Impale = 1; // Cast Impale On A Random Target + public const uint Locust = 2; // Begin Channeling Locust Swarm + public const uint LocustEnds = 3; // Locust Swarm Dissipates + public const uint SpawnGuard = 4; // 10-Man Only - Crypt Guard Has Delayed Spawn; Also Used For The Locust Swarm Crypt Guard In Both Modes + public const uint Scarabs = 5; // Spawn Corpse Scarabs + public const uint Berserk = 6; // Berserk + } + + struct SpellIds + { + public const uint Impale = 28783; // 25-Man: 56090 + public const uint LocustSwarm = 28785; // 25-Man: 54021 + public const uint SummonCorpseScarabsPlr = 29105; // This Spawns 5 Corpse Scarabs On Top Of Player + public const uint SummonCorpseScarabsMob = 28864; // This Spawns 10 Corpse Scarabs On Top Of Dead Guards + public const uint Berserk = 27680; + } + + struct Misc + { + public const uint achievTimedStartEvent = 9891; + + public const uint PhaseNormal = 1; + public const uint PhaseSwarm = 2; + + public const uint SpawnGroupsInitial25M = 1; + public const uint SpawnGroupsSingleSpawn = 2; + } +} diff --git a/Scripts/Northrend/Nexus/EyeOfEternity/BossMalygos.cs b/Scripts/Northrend/Nexus/EyeOfEternity/BossMalygos.cs new file mode 100644 index 000000000..cba918582 --- /dev/null +++ b/Scripts/Northrend/Nexus/EyeOfEternity/BossMalygos.cs @@ -0,0 +1,29 @@ +/* + * Copyright (C) 2012-2017 CypherCore + * + * 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, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Scripts.Northrend.Nexus.EyeOfEternity +{ + class BossMalygos + { + } +} \ No newline at end of file diff --git a/Scripts/Northrend/Nexus/EyeOfEternity/EyeOfEternity.cs b/Scripts/Northrend/Nexus/EyeOfEternity/EyeOfEternity.cs new file mode 100644 index 000000000..c33d24bf2 --- /dev/null +++ b/Scripts/Northrend/Nexus/EyeOfEternity/EyeOfEternity.cs @@ -0,0 +1,351 @@ +/* + * Copyright (C) 2012-2017 CypherCore + * + * 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, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +using Framework.Constants; +using Framework.GameMath; +using Game.Entities; +using Game.Maps; +using Game.Scripting; +using System.Collections.Generic; +using System.Linq; + +namespace Scripts.Northrend.Nexus.EyeOfEternity +{ + struct EyeOfEternityConst + { + public const uint MaxEncounter = 1; + public const uint EventFocusingIris = 20711; + } + + struct InstanceData + { + public const uint MalygosEvent = 1; + public const uint VortexHandling = 2; + public const uint PowerSparksHandling = 3; + public const uint RespawnIris = 4; + } + + struct InstanceData64 + { + public const uint Trigger = 0; + public const uint Malygos = 1; + public const uint Platform = 2; + public const uint AlexstraszaBunnyGUID = 3; + public const uint HeartOfMagicGUID = 4; + public const uint FocusingIrisGUID = 5; + public const uint GiftBoxBunnyGUID = 6; + } + + struct InstanceNpcs + { + public const uint Malygos = 28859; + public const uint VortexTrigger = 30090; + public const uint PortalTrigger = 30118; + public const uint PowerSpark = 30084; + public const uint HoverDiskMelee = 30234; + public const uint HoverDiskCaster = 30248; + public const uint ArcaneOverload = 30282; + public const uint WyrmrestSkytalon = 30161; + public const uint Alexstrasza = 32295; + public const uint AlexstraszaBunny = 31253; + public const uint AlexstraszasGift = 32448; + public const uint SurgeOfPower = 30334; + } + + struct InstanceGameObjects + { + public const uint NexusRaidPlatform = 193070; + public const uint ExitPortal = 193908; + public const uint FocusingIris10 = 193958; + public const uint FocusingIris25 = 193960; + public const uint AlexstraszaSGift10 = 193905; + public const uint AlexstraszaSGift25 = 193967; + public const uint HeartOfMagic10 = 194158; + public const uint HeartOfMagic25 = 194159; + } + + struct InstanceSpells + { + public const uint Vortex4 = 55853; // Damage | Used To Enter To The Vehicle + public const uint Vortex5 = 56263; // Damage | Used To Enter To The Vehicle + public const uint PortalOpened = 61236; + public const uint RideRedDragonTriggered = 56072; + public const uint IrisOpened = 61012; // Visual When Starting Encounter + public const uint SummomRedDragonBuddy = 56070; + } + + [Script] + class instance_eye_of_eternity : InstanceMapScript + { + public instance_eye_of_eternity() : base("instance_eye_of_eternity", 616) { } + + class instance_eye_of_eternity_InstanceMapScript : InstanceScript + { + public instance_eye_of_eternity_InstanceMapScript(Map map) : base(map) + { + SetHeaders("EOE"); + SetBossNumber(EyeOfEternityConst.MaxEncounter); + } + + public override void OnPlayerEnter(Player player) + { + if (GetBossState(0) == EncounterState.Done) + player.CastSpell(player, InstanceSpells.SummomRedDragonBuddy, true); + } + + public override bool SetBossState(uint type, EncounterState state) + { + if (!base.SetBossState(type, state)) + return false; + + if (type == InstanceData.MalygosEvent) + { + if (state == EncounterState.Fail) + { + foreach (var triggerGuid in portalTriggers) + { + Creature trigger = instance.GetCreature(triggerGuid); + if (trigger) + { + // just in case + trigger.RemoveAllAuras(); + trigger.GetAI().Reset(); + } + } + + SpawnGameObject(InstanceGameObjects.ExitPortal, exitPortalPosition); + + GameObject platform = instance.GetGameObject(platformGUID); + if (platform) + platform.RemoveFlag(GameObjectFields.Flags, GameObjectFlags.Destroyed); + } + else if (state == EncounterState.Done) + SpawnGameObject(InstanceGameObjects.ExitPortal, exitPortalPosition); + } + return true; + } + + /// @todo this should be handled in map, maybe add a summon function in map + // There is no other way afaik... + void SpawnGameObject(uint entry, Position pos) + { + GameObject go = new GameObject(); + if (go.Create(entry, instance, PhaseMasks.Normal, pos, Quaternion.WAxis, 255, GameObjectState.Ready)) + instance.AddToMap(go); + } + + public override void OnGameObjectCreate(GameObject go) + { + switch (go.GetEntry()) + { + case InstanceGameObjects.NexusRaidPlatform: + platformGUID = go.GetGUID(); + break; + case InstanceGameObjects.FocusingIris10: + case InstanceGameObjects.FocusingIris25: + irisGUID = go.GetGUID(); + focusingIrisPosition = go.GetPosition(); + break; + case InstanceGameObjects.ExitPortal: + exitPortalGUID = go.GetGUID(); + exitPortalPosition = go.GetPosition(); + break; + case InstanceGameObjects.HeartOfMagic10: + case InstanceGameObjects.HeartOfMagic25: + heartOfMagicGUID = go.GetGUID(); + break; + default: + break; + } + } + + public override void OnCreatureCreate(Creature creature) + { + switch (creature.GetEntry()) + { + case InstanceNpcs.VortexTrigger: + vortexTriggers.Add(creature.GetGUID()); + break; + case InstanceNpcs.Malygos: + malygosGUID = creature.GetGUID(); + break; + case InstanceNpcs.PortalTrigger: + portalTriggers.Add(creature.GetGUID()); + break; + case InstanceNpcs.AlexstraszaBunny: + alexstraszaBunnyGUID = creature.GetGUID(); + break; + case InstanceNpcs.AlexstraszasGift: + giftBoxBunnyGUID = creature.GetGUID(); + break; + } + } + + public override void OnUnitDeath(Unit unit) + { + if (!unit.IsTypeId(TypeId.Player)) + return; + + // Player continues to be moving after death no matter if spline will be cleared along with all movements, + // so on next world tick was all about delay if box will pop or not (when new movement will be registered) + // since in EoE you never stop falling. However root at this precise* moment works, + // it will get cleared on release. If by any chance some lag happen "Reload()" and "RepopMe()" works, + // last test I made now gave me 50/0 of this bug so I can't do more about it. + unit.SetControlled(true, UnitState.Root); + } + + public override void ProcessEvent(WorldObject obj, uint eventId) + { + if (eventId == EyeOfEternityConst.EventFocusingIris) + { + Creature alexstraszaBunny = instance.GetCreature(alexstraszaBunnyGUID); + if (alexstraszaBunny) + alexstraszaBunny.CastSpell(alexstraszaBunny, InstanceSpells.IrisOpened); + + GameObject iris = instance.GetGameObject(irisGUID); + if (iris) + iris.SetFlag(GameObjectFields.Flags, GameObjectFlags.InUse); + + Creature malygos = instance.GetCreature(malygosGUID); + if (malygos) + malygos.GetAI().DoAction(0); // ACTION_LAND_ENCOUNTER_START + + GameObject exitPortal = instance.GetGameObject(exitPortalGUID); + if (exitPortal) + exitPortal.Delete(); + } + } + + void VortexHandling() + { + Creature malygos = instance.GetCreature(malygosGUID); + if (malygos) + { + var threatList = malygos.GetThreatManager().getThreatList(); + foreach (var guid in vortexTriggers) + { + if (threatList.Empty()) + return; + + byte counter = 0; + Creature trigger = instance.GetCreature(guid); + if (trigger) + { + // each trigger have to cast the spell to 5 players. + foreach (var refe in threatList) + { + if (counter >= 5) + break; + + Unit target = refe.getTarget(); + if (target) + { + Player player = target.ToPlayer(); + + if (!player || player.IsGameMaster() || player.HasAura(InstanceSpells.Vortex4)) + continue; + + player.CastSpell(trigger, InstanceSpells.Vortex4, true); + counter++; + } + } + } + } + } + } + + void PowerSparksHandling() + { + bool next = (lastPortalGUID == portalTriggers.Last() || !lastPortalGUID.IsEmpty() ? true : false); + + foreach (var guid in portalTriggers) + { + if (next) + { + Creature trigger = instance.GetCreature(guid); + if (trigger) + { + lastPortalGUID = trigger.GetGUID(); + trigger.CastSpell(trigger, InstanceSpells.PortalOpened, true); + return; + } + } + + if (guid == lastPortalGUID) + next = true; + } + } + + public override void SetData(uint data, uint value) + { + switch (data) + { + case InstanceData.VortexHandling: + VortexHandling(); + break; + case InstanceData.PowerSparksHandling: + PowerSparksHandling(); + break; + case InstanceData.RespawnIris: + SpawnGameObject(instance.GetDifficultyID() == Difficulty.Raid10N ? InstanceGameObjects.FocusingIris10 : InstanceGameObjects.FocusingIris25, focusingIrisPosition); + break; + } + } + + public override ObjectGuid GetGuidData(uint data) + { + switch (data) + { + case InstanceData64.Trigger: + return vortexTriggers.First(); + case InstanceData64.Malygos: + return malygosGUID; + case InstanceData64.Platform: + return platformGUID; + case InstanceData64.AlexstraszaBunnyGUID: + return alexstraszaBunnyGUID; + case InstanceData64.HeartOfMagicGUID: + return heartOfMagicGUID; + case InstanceData64.FocusingIrisGUID: + return irisGUID; + case InstanceData64.GiftBoxBunnyGUID: + return giftBoxBunnyGUID; + } + + return ObjectGuid.Empty; + } + + List vortexTriggers = new List(); + List portalTriggers = new List(); + ObjectGuid malygosGUID; + ObjectGuid irisGUID; + ObjectGuid lastPortalGUID; + ObjectGuid platformGUID; + ObjectGuid exitPortalGUID; + ObjectGuid heartOfMagicGUID; + ObjectGuid alexstraszaBunnyGUID; + ObjectGuid giftBoxBunnyGUID; + Position focusingIrisPosition; + Position exitPortalPosition; + } + + public override InstanceScript GetInstanceScript(InstanceMap map) + { + return new instance_eye_of_eternity_InstanceMapScript(map); + } + } +} diff --git a/Scripts/Northrend/Nexus/Nexus/BossAnomalus.cs b/Scripts/Northrend/Nexus/Nexus/BossAnomalus.cs new file mode 100644 index 000000000..391e1c0fc --- /dev/null +++ b/Scripts/Northrend/Nexus/Nexus/BossAnomalus.cs @@ -0,0 +1,291 @@ +/* + * Copyright (C) 2012-2017 CypherCore + * + * 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, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +using Framework.Constants; +using Game.AI; +using Game.Entities; +using Game.Maps; +using Game.Scripting; +using System; + +namespace Scripts.Northrend.Nexus.Nexus +{ + struct AnomalusConst + { + //Spells + public const uint SpellSpark = 47751; + public const uint SpellSparkHeroic = 57062; + public const uint SpellRiftShield = 47748; + public const uint SpellChargeRift = 47747; // Works Wrong (Affect Players; Not Rifts) + public const uint SpellCreateRift = 47743; // Don'T Work; Using Wa + public const uint SpellArcaneAttraction = 57063; // No Idea; When It'S Used + + public const uint SpellChaoticEnergyBurst = 47688; + public const uint SpellChargedChaoticEnergyBurst = 47737; + public const uint SpellArcaneform = 48019; // Chaotic Rift Visual + + //Texts + public const uint SayAggro = 0; + public const uint SayDeath = 1; + public const uint SayRift = 2; + public const uint SayShield = 3; + public const uint SayRiftEmote = 4; // Needs To Be Added To Script + public const uint SayShieldEmote = 5; // Needs To Be Added To Script + + //Misc + public const uint NpcCrazedManaWraith = 26746; + public const uint NpcChaoticRift = 26918; + + public const uint DataChaosTheory = 1; + + public static Position[] RiftLocation = + { + new Position(652.64f, -273.70f, -8.75f, 0.0f), + new Position(634.45f, -265.94f, -8.44f, 0.0f), + new Position(620.73f, -281.17f, -9.02f, 0.0f), + new Position(626.10f, -304.67f, -9.44f, 0.0f), + new Position(639.87f, -314.11f, -9.49f, 0.0f), + new Position(651.72f, -297.44f, -9.37f, 0.0f) + }; + } + + [Script] + class boss_anomalus : CreatureScript + { + public boss_anomalus() : base("boss_anomalus") { } + + class boss_anomalusAI : ScriptedAI + { + public boss_anomalusAI(Creature creature) : base(creature) + { + instance = me.GetInstanceScript(); + } + + void Initialize() + { + _scheduler.Schedule(TimeSpan.FromSeconds(5), task => + { + Unit target = SelectTarget(SelectAggroTarget.Random, 0); + if (target) + DoCast(target, AnomalusConst.SpellSpark); + + task.Repeat(TimeSpan.FromSeconds(5)); + }); + + Phase = 0; + uiChaoticRiftGUID.Clear(); + chaosTheory = true; + } + + public override void Reset() + { + Initialize(); + + instance.SetBossState(DataTypes.Anomalus, EncounterState.NotStarted); + } + + public override void EnterCombat(Unit who) + { + Talk(AnomalusConst.SayAggro); + + instance.SetBossState(DataTypes.Anomalus, EncounterState.InProgress); + } + + public override void JustDied(Unit killer) + { + Talk(AnomalusConst.SayDeath); + + instance.SetBossState(DataTypes.Anomalus, EncounterState.Done); + } + + public override uint GetData(uint type) + { + if (type == AnomalusConst.DataChaosTheory) + return chaosTheory ? 1 : 0u; + + return 0; + } + + public override void SummonedCreatureDies(Creature summoned, Unit who) + { + if (summoned.GetEntry() == AnomalusConst.NpcChaoticRift) + chaosTheory = false; + } + + public override void UpdateAI(uint diff) + { + if (!UpdateVictim()) + return; + + if (me.GetDistance(me.GetHomePosition()) > 60.0f) + { + // Not blizzlike, hack to avoid an exploit + EnterEvadeMode(); + return; + } + + if (me.HasAura(AnomalusConst.SpellRiftShield)) + { + if (!uiChaoticRiftGUID.IsEmpty()) + { + Creature Rift = ObjectAccessor.GetCreature(me, uiChaoticRiftGUID); + if (Rift && Rift.IsDead()) + { + me.RemoveAurasDueToSpell(AnomalusConst.SpellRiftShield); + uiChaoticRiftGUID.Clear(); + } + return; + } + } + else + uiChaoticRiftGUID.Clear(); + + if ((Phase == 0) && HealthBelowPct(50)) + { + Phase = 1; + Talk(AnomalusConst.SayShield); + DoCast(me, AnomalusConst.SpellRiftShield); + Creature Rift = me.SummonCreature(AnomalusConst.NpcChaoticRift, AnomalusConst.RiftLocation[RandomHelper.IRand(0, 5)], TempSummonType.TimedDespawnOOC, 1000); + if (Rift) + { + //DoCast(Rift, SPELL_CHARGE_RIFT); + Unit target = SelectTarget(SelectAggroTarget.Random, 0); + if (target) + Rift.GetAI().AttackStart(target); + uiChaoticRiftGUID = Rift.GetGUID(); + Talk(AnomalusConst.SayRift); + } + } + + _scheduler.Update(diff); + + DoMeleeAttackIfReady(); + } + + InstanceScript instance; + + byte Phase; + ObjectGuid uiChaoticRiftGUID; + bool chaosTheory; + } + + public override CreatureAI GetAI(Creature creature) + { + return GetInstanceAI(creature); + } + } + + [Script] + class npc_chaotic_rift : CreatureScript + { + public npc_chaotic_rift() : base("npc_chaotic_rift") { } + + class npc_chaotic_riftAI : ScriptedAI + { + public npc_chaotic_riftAI(Creature creature) : base(creature) + { + Initialize(); + instance = me.GetInstanceScript(); + SetCombatMovement(false); + } + + void Initialize() + { + uiChaoticEnergyBurstTimer = 1000; + uiSummonCrazedManaWraithTimer = 5000; + } + + public override void Reset() + { + Initialize(); + me.SetDisplayId(me.GetCreatureTemplate().ModelId2); + DoCast(me, AnomalusConst.SpellArcaneform, false); + } + + public override void UpdateAI(uint diff) + { + if (!UpdateVictim()) + return; + + if (uiChaoticEnergyBurstTimer <= diff) + { + Creature Anomalus = ObjectAccessor.GetCreature(me, instance.GetGuidData(DataTypes.Anomalus)); + Unit target = SelectTarget(SelectAggroTarget.Random, 0); + if (target) + { + if (Anomalus && Anomalus.HasAura(AnomalusConst.SpellRiftShield)) + DoCast(target, AnomalusConst.SpellChargedChaoticEnergyBurst); + else + DoCast(target, AnomalusConst.SpellChaoticEnergyBurst); + } + uiChaoticEnergyBurstTimer = 1000; + } + else + uiChaoticEnergyBurstTimer -= diff; + + if (uiSummonCrazedManaWraithTimer <= diff) + { + Creature Wraith = me.SummonCreature(AnomalusConst.NpcCrazedManaWraith, me.GetPositionX() + 1, me.GetPositionY() + 1, me.GetPositionZ(), 0, TempSummonType.TimedDespawnOOC, 1000); + if (Wraith) + { + Unit target = SelectTarget(SelectAggroTarget.Random, 0); + if (target) + Wraith.GetAI().AttackStart(target); + } + Creature Anomalus = ObjectAccessor.GetCreature(me, instance.GetGuidData(DataTypes.Anomalus)); + if (Anomalus && Anomalus.HasAura(AnomalusConst.SpellRiftShield)) + uiSummonCrazedManaWraithTimer = 5000; + else + uiSummonCrazedManaWraithTimer = 10000; + } + else + uiSummonCrazedManaWraithTimer -= diff; + } + + InstanceScript instance; + + uint uiChaoticEnergyBurstTimer; + uint uiSummonCrazedManaWraithTimer; + } + + public override CreatureAI GetAI(Creature creature) + { + return GetInstanceAI(creature); + } + } + + [Script] + class achievement_chaos_theory : AchievementCriteriaScript + { + public achievement_chaos_theory() : base("achievement_chaos_theory") + { + } + + public override bool OnCheck(Player player, Unit target) + { + if (!target) + return false; + + Creature Anomalus = target.ToCreature(); + if (Anomalus) + if (Anomalus.GetAI().GetData(AnomalusConst.DataChaosTheory) != 0) + return true; + + return false; + } + } +} diff --git a/Scripts/Northrend/Nexus/Nexus/BossKeristrasza.cs b/Scripts/Northrend/Nexus/Nexus/BossKeristrasza.cs new file mode 100644 index 000000000..9c0b3d00a --- /dev/null +++ b/Scripts/Northrend/Nexus/Nexus/BossKeristrasza.cs @@ -0,0 +1,277 @@ +/* + * Copyright (C) 2012-2017 CypherCore + * + * 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, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +using Framework.Constants; +using Game.AI; +using Game.Entities; +using Game.Maps; +using Game.Scripting; +using Game.Spells; +using System; +using System.Collections.Generic; + +namespace Scripts.Northrend.Nexus.Nexus +{ + struct KeristraszaConst + { + //Spells + public const uint SpellFrozenPrison = 47854; + public const uint SpellTailSweep = 50155; + public const uint SpellCrystalChains = 50997; + public const uint SpellEnrage = 8599; + public const uint SpellCrystalFireBreath = 48096; + public const uint SpellCrystalize = 48179; + public const uint SpellIntenseCold = 48094; + public const uint SpellIntenseColdTriggered = 48095; + + //Yells + public const uint SayAggro = 0; + public const uint SaySlay = 1; + public const uint SayEnrage = 2; + public const uint SayDeath = 3; + public const uint SayCrystalNova = 4; + public const uint SayFrenzy = 5; + + //Misc + public const uint DataIntenseCold = 1; + public const uint DataContainmentSpheres = 3; + } + + [Script] + class boss_keristrasza : CreatureScript + { + public boss_keristrasza() : base("boss_keristrasza") { } + + public class boss_keristraszaAI : BossAI + { + public boss_keristraszaAI(Creature creature) : base(creature, DataTypes.Keristrasza) + { + Initialize(); + } + + void Initialize() + { + _enrage = false; + + //Crystal FireBreath + _scheduler.Schedule(TimeSpan.FromSeconds(14), task => + { + DoCastVictim(KeristraszaConst.SpellCrystalFireBreath); + task.Repeat(TimeSpan.FromSeconds(14)); + }); + + //CrystalChainsCrystalize + _scheduler.Schedule(TimeSpan.FromSeconds(DungeonMode(30, 11)), task => + { + Talk(KeristraszaConst.SayCrystalNova); + if (IsHeroic()) + DoCast(me, KeristraszaConst.SpellCrystalize); + else + { + Unit target = SelectTarget(SelectAggroTarget.Random, 0, 100.0f, true); + if (target) + DoCast(target, KeristraszaConst.SpellCrystalChains); + } + + task.Repeat(TimeSpan.FromSeconds(DungeonMode(30, 11))); + }); + + //TailSweep + _scheduler.Schedule(TimeSpan.FromSeconds(5), task => + { + DoCast(me, KeristraszaConst.SpellTailSweep); + task.Repeat(TimeSpan.FromSeconds(5)); + }); + } + + public override void Reset() + { + Initialize(); + _intenseColdList.Clear(); + + me.RemoveFlag(UnitFields.Flags, UnitFlags.Stunned); + + RemovePrison(CheckContainmentSpheres()); + _Reset(); + } + + public override void EnterCombat(Unit who) + { + Talk(KeristraszaConst.SayAggro); + DoCastAOE(KeristraszaConst.SpellIntenseCold); + _EnterCombat(); + } + + public override void JustDied(Unit killer) + { + Talk(KeristraszaConst.SayDeath); + _JustDied(); + } + + public override void KilledUnit(Unit who) + { + if (who.IsTypeId(TypeId.Player)) + Talk(KeristraszaConst.SaySlay); + } + + public bool CheckContainmentSpheres(bool removePrison = false) + { + for (uint i = DataTypes.AnomalusContainmetSphere; i < (DataTypes.AnomalusContainmetSphere + KeristraszaConst.DataContainmentSpheres); ++i) + { + GameObject containmentSpheres = ObjectAccessor.GetGameObject(me, instance.GetGuidData(i)); + if (!containmentSpheres || containmentSpheres.GetGoState() != GameObjectState.Active) + return false; + } + if (removePrison) + RemovePrison(true); + return true; + } + + void RemovePrison(bool remove) + { + if (remove) + { + me.RemoveFlag(UnitFields.Flags, UnitFlags.ImmuneToPc); + me.RemoveFlag(UnitFields.Flags, UnitFlags.NonAttackable); + if (me.HasAura(KeristraszaConst.SpellFrozenPrison)) + me.RemoveAurasDueToSpell(KeristraszaConst.SpellFrozenPrison); + } + else + { + me.SetFlag(UnitFields.Flags, UnitFlags.ImmuneToPc); + me.SetFlag(UnitFields.Flags, UnitFlags.NonAttackable); + DoCast(me, KeristraszaConst.SpellFrozenPrison, false); + } + } + + public override void SetGUID(ObjectGuid guid, int id = 0) + { + if (id == KeristraszaConst.DataIntenseCold) + _intenseColdList.Add(guid); + } + + public override void DamageTaken(Unit attacker, ref uint damage) + { + if (!_enrage && me.HealthBelowPctDamaged(25, damage)) + { + Talk(KeristraszaConst.SayEnrage); + Talk(KeristraszaConst.SayFrenzy); + DoCast(me, KeristraszaConst.SpellEnrage); + _enrage = true; + } + } + + public override void UpdateAI(uint diff) + { + if (!UpdateVictim()) + return; + + _scheduler.Update(diff); + + if (me.HasUnitState(UnitState.Casting)) + return; + + DoMeleeAttackIfReady(); + } + + bool _enrage; + + public List _intenseColdList = new List(); + } + + public override CreatureAI GetAI(Creature creature) + { + return GetInstanceAI(creature); + } + } + + [Script] + class containment_sphere : GameObjectScript + { + public containment_sphere() : base("containment_sphere") { } + + public override bool OnGossipHello(Player player, GameObject go) + { + InstanceScript instance = go.GetInstanceScript(); + + Creature pKeristrasza = ObjectAccessor.GetCreature(go, instance.GetGuidData(DataTypes.Keristrasza)); + if (pKeristrasza && pKeristrasza.IsAlive()) + { + // maybe these are hacks :( + go.SetFlag(GameObjectFields.Flags, GameObjectFlags.NotSelectable); + go.SetGoState(GameObjectState.Active); + + ((boss_keristrasza.boss_keristraszaAI)pKeristrasza.GetAI()).CheckContainmentSpheres(true); + } + return true; + } + + } + + [Script] + class spell_intense_cold : SpellScriptLoader + { + public spell_intense_cold() : base("spell_intense_cold") { } + + class spell_intense_cold_AuraScript : AuraScript + { + void HandlePeriodicTick(AuraEffect aurEff) + { + if (aurEff.GetBase().GetStackAmount() < 2) + return; + Unit caster = GetCaster(); + /// @todo the caster should be boss but not the player + if (!caster || caster.GetAI() == null) + return; + + caster.GetAI().SetGUID(GetTarget().GetGUID(), (int)KeristraszaConst.DataIntenseCold); + } + + public override void Register() + { + OnEffectPeriodic.Add(new EffectPeriodicHandler(HandlePeriodicTick, 1, AuraType.PeriodicDamage)); + } + } + + public override AuraScript GetAuraScript() + { + return new spell_intense_cold_AuraScript(); + } + } + + [Script] + class achievement_intense_cold : AchievementCriteriaScript + { + public achievement_intense_cold() : base("achievement_intense_cold") { } + + public override bool OnCheck(Player player, Unit target) + { + if (!target) + return false; + + var _intenseColdList = ((boss_keristrasza.boss_keristraszaAI)target.ToCreature().GetAI())._intenseColdList; + if (!_intenseColdList.Empty()) + { + foreach (var guid in _intenseColdList) + if (player.GetGUID() == guid) + return false; + } + + return true; + } + } +} diff --git a/Scripts/Northrend/Nexus/Nexus/BossMagusTelestra.cs b/Scripts/Northrend/Nexus/Nexus/BossMagusTelestra.cs new file mode 100644 index 000000000..7e50fe346 --- /dev/null +++ b/Scripts/Northrend/Nexus/Nexus/BossMagusTelestra.cs @@ -0,0 +1,349 @@ +/* + * Copyright (C) 2012-2017 CypherCore + * + * 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, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +using Framework.Constants; +using Game.AI; +using Game.Entities; +using Game.Maps; +using Game.Scripting; + +namespace Scripts.Northrend.Nexus.Nexus +{ + struct MagusTelestraConst + { + //Spells + public const uint SpellIceNova = 47772; + public const uint SpellIceNovaH = 56935; + public const uint SpellFirebomb = 47773; + public const uint SpellFirebombH = 56934; + public const uint SpellGravityWell = 47756; + public const uint SpellTelestraBack = 47714; + public const uint SpellFireMagusVisual = 47705; + public const uint SpellFrostMagusVisual = 47706; + public const uint SpellArcaneMagusVisual = 47704; + public const uint SpellWearChristmasHat = 61400; + + //Npcs + public const uint NpcFireMagus = 26928; + public const uint NpcFrostMagus = 26930; + public const uint NpcArcaneMagus = 26929; + + //Texts + public const uint SayAggro = 0; + public const uint SayKill = 1; + public const uint SayDeath = 2; + public const uint SayMerge = 3; + public const uint SaySplit = 4; + + //Misc + public const uint DataSplitPersonality = 1; + public const ushort GameEventWinterVeil = 2; + } + + [Script] + class boss_magus_telestra : CreatureScript + { + public boss_magus_telestra() : base("boss_magus_telestra") { } + + class boss_magus_telestraAI : ScriptedAI + { + public boss_magus_telestraAI(Creature creature) : base(creature) + { + instance = creature.GetInstanceScript(); + bFireMagusDead = false; + bFrostMagusDead = false; + bArcaneMagusDead = false; + uiIsWaitingToAppearTimer = 0; + } + + void Initialize() + { + Phase = 0; + + uiIceNovaTimer = 7 * Time.InMilliseconds; + uiFireBombTimer = 0; + uiGravityWellTimer = 15 * Time.InMilliseconds; + uiCooldown = 0; + + for (byte n = 0; n < 3; ++n) + time[n] = 0; + + splitPersonality = 0; + bIsWaitingToAppear = false; + } + + public override void Reset() + { + Initialize(); + + me.RemoveFlag(UnitFields.Flags, UnitFlags.NotSelectable); + + instance.SetBossState(DataTypes.MagusTelestra, EncounterState.NotStarted); + + if (IsHeroic() && Global.GameEventMgr.IsActiveEvent(MagusTelestraConst.GameEventWinterVeil) && !me.HasAura(MagusTelestraConst.SpellWearChristmasHat)) + me.AddAura(MagusTelestraConst.SpellWearChristmasHat, me); + } + + public override void EnterCombat(Unit who) + { + Talk(MagusTelestraConst.SayAggro); + + instance.SetBossState(DataTypes.MagusTelestra, EncounterState.InProgress); + } + + public override void JustDied(Unit killer) + { + Talk(MagusTelestraConst.SayDeath); + + instance.SetBossState(DataTypes.MagusTelestra, EncounterState.Done); + } + + public override void KilledUnit(Unit who) + { + if (who.IsTypeId(TypeId.Player)) + Talk(MagusTelestraConst.SayKill); + } + + public override uint GetData(uint type) + { + if (type == MagusTelestraConst.DataSplitPersonality) + return splitPersonality; + + return 0; + } + + public override void UpdateAI(uint diff) + { + //Return since we have no target + if (!UpdateVictim()) + return; + + if (bIsWaitingToAppear) + { + me.StopMoving(); + me.AttackStop(); + if (uiIsWaitingToAppearTimer <= diff) + { + me.CastSpell(me, 47714, true); + me.RemoveFlag(UnitFields.Flags, UnitFlags.NotSelectable); + bIsWaitingToAppear = false; + InVanish = false; + me.SendAIReaction(AiReaction.Hostile); + } + else + uiIsWaitingToAppearTimer -= diff; + + return; + } + + if ((Phase == 1) || (Phase == 3)) + { + if (bFireMagusDead && bFrostMagusDead && bArcaneMagusDead) + { + for (byte n = 0; n < 3; ++n) + time[n] = 0; + + me.GetMotionMaster().Clear(); + DoCast(me, MagusTelestraConst.SpellTelestraBack); + if (Phase == 1) + Phase = 2; + if (Phase == 3) + Phase = 4; + bIsWaitingToAppear = true; + uiIsWaitingToAppearTimer = 4 * Time.InMilliseconds; + Talk(MagusTelestraConst.SayMerge); + } + else + return; + } + + if ((Phase == 0) && HealthBelowPct(50)) + { + InVanish = true; + Phase = 1; + me.CastStop(); + me.RemoveAllAuras(); + me.CastSpell(me, 47710, false); + me.SetFlag(UnitFields.Flags, UnitFlags.NotSelectable); + bFireMagusDead = false; + bFrostMagusDead = false; + bArcaneMagusDead = false; + Talk(MagusTelestraConst.SaySplit); + return; + } + + if (IsHeroic() && (Phase == 2) && HealthBelowPct(10)) + { + InVanish = true; + Phase = 3; + me.CastStop(); + me.RemoveAllAuras(); + me.SetFlag(UnitFields.Flags, UnitFlags.NotSelectable); + bFireMagusDead = false; + bFrostMagusDead = false; + bArcaneMagusDead = false; + Talk(MagusTelestraConst.SaySplit); + return; + } + + if (uiCooldown != 0) + { + if (uiCooldown <= diff) + uiCooldown = 0; + else + { + uiCooldown -= diff; + return; + } + } + + if (uiIceNovaTimer <= diff) + { + Unit target = SelectTarget(SelectAggroTarget.Random, 0); + if (target) + { + DoCast(target, MagusTelestraConst.SpellIceNova, false); + uiCooldown = 1500; + } + uiIceNovaTimer = 15 * Time.InMilliseconds; + } + else uiIceNovaTimer -= diff; + + if (uiGravityWellTimer <= diff) + { + Unit target = me.GetVictim(); + if (target) + { + DoCast(target, MagusTelestraConst.SpellGravityWell); + uiCooldown = 6 * Time.InMilliseconds; + } + uiGravityWellTimer = 15 * Time.InMilliseconds; + } + else uiGravityWellTimer -= diff; + + if (uiFireBombTimer <= diff) + { + Unit target = SelectTarget(SelectAggroTarget.Random, 0); + if (target) + { + DoCast(target, MagusTelestraConst.SpellFirebomb, false); + uiCooldown = 2 * Time.InMilliseconds; + } + uiFireBombTimer = 2 * Time.InMilliseconds; + } + else uiFireBombTimer -= diff; + + if (!InVanish) + DoMeleeAttackIfReady(); + } + + public override void SummonedCreatureDies(Creature summon, Unit killer) + { + if (summon.IsAlive()) + return; + + switch (summon.GetEntry()) + { + case MagusTelestraConst.NpcFireMagus: + bFireMagusDead = true; + break; + case MagusTelestraConst.NpcFrostMagus: + bFrostMagusDead = true; + break; + case MagusTelestraConst.NpcArcaneMagus: + bArcaneMagusDead = true; + break; + } + + byte i = 0; + while (time[i] != 0) + ++i; + + time[i] = Global.WorldMgr.GetGameTime(); + if (i == 2 && (time[2] - time[1] < 5) && (time[1] - time[0] < 5)) + ++splitPersonality; + } + + InstanceScript instance; + + bool bFireMagusDead; + bool bFrostMagusDead; + bool bArcaneMagusDead; + bool bIsWaitingToAppear; + bool InVanish; + + uint uiIsWaitingToAppearTimer; + uint uiIceNovaTimer; + uint uiFireBombTimer; + uint uiGravityWellTimer; + uint uiCooldown; + + byte Phase; + byte splitPersonality; + long[] time = new long[3]; + } + + public override CreatureAI GetAI(Creature creature) + { + return GetInstanceAI(creature); + } + } + + [Script] + class achievement_split_personality : AchievementCriteriaScript + { + public achievement_split_personality() : base("achievement_split_personality") + { + } + + public override bool OnCheck(Player player, Unit target) + { + if (!target) + return false; + + Creature Telestra = target.ToCreature(); + if (Telestra) + if (Telestra.GetAI().GetData(MagusTelestraConst.DataSplitPersonality) == 2) + return true; + + return false; + } + } + + [Script] + class spell_gravity_well_effect : SpellScriptLoader + { + public spell_gravity_well_effect() : base("spell_gravity_well_effect") { } + + class spell_gravity_well_effect_SpellScript : SpellScript + { + void HandleDummy(uint index) + { + + } + + public override void Register() + { + } + } + + public override SpellScript GetSpellScript() + { + return new spell_gravity_well_effect_SpellScript(); + } + } +} diff --git a/Scripts/Northrend/Nexus/Nexus/BossNexusCommanders.cs b/Scripts/Northrend/Nexus/Nexus/BossNexusCommanders.cs new file mode 100644 index 000000000..3bac72f38 --- /dev/null +++ b/Scripts/Northrend/Nexus/Nexus/BossNexusCommanders.cs @@ -0,0 +1,113 @@ +/* + * Copyright (C) 2012-2017 CypherCore + * + * 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, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +using Framework.Constants; +using Game.AI; +using Game.Entities; +using Game.Scripting; +using System; + +namespace Scripts.Northrend.Nexus.Nexus +{ + struct NexusCommandersConst + { + //Spells + public const uint SpellBattleShout = 31403; + public const uint SpellCharge = 60067; + public const uint SpellFrighteningShout = 19134; + public const uint SpellWhirlwind = 38618; + public const uint SpellFrozenPrison = 47543; + + //Texts + public const uint SayAggro = 0; + public const uint SayKill = 1; + public const uint SayDeath = 2; + } + + [Script] + class boss_nexus_commanders : CreatureScript + { + public boss_nexus_commanders() : base("boss_nexus_commanders") { } + + class boss_nexus_commandersAI : BossAI + { + boss_nexus_commandersAI(Creature creature) : base(creature, DataTypes.Commander) { } + + public override void EnterCombat(Unit who) + { + _EnterCombat(); + Talk(NexusCommandersConst.SayAggro); + me.RemoveAurasDueToSpell(NexusCommandersConst.SpellFrozenPrison); + DoCast(me, NexusCommandersConst.SpellBattleShout); + + //Charge + _scheduler.Schedule(TimeSpan.FromSeconds(3), TimeSpan.FromSeconds(4), task => + { + Unit target = SelectTarget(SelectAggroTarget.Random, 0, 100.0f, true); + if (target) + DoCast(target, NexusCommandersConst.SpellCharge); + + task.Repeat(TimeSpan.FromSeconds(11), TimeSpan.FromSeconds(15)); + }); + + //Whirlwind + _scheduler.Schedule(TimeSpan.FromSeconds(6), TimeSpan.FromSeconds(8), task => + { + DoCast(me, NexusCommandersConst.SpellWhirlwind); + task.Repeat(TimeSpan.FromSeconds(19.5), TimeSpan.FromSeconds(25)); + }); + + //Frightening Shout + _scheduler.Schedule(TimeSpan.FromSeconds(13), TimeSpan.FromSeconds(15), task => + { + DoCastAOE(NexusCommandersConst.SpellFrighteningShout); + task.Repeat(TimeSpan.FromSeconds(45), TimeSpan.FromSeconds(55)); + }); + } + + public override void JustDied(Unit killer) + { + _JustDied(); + Talk(NexusCommandersConst.SayDeath); + } + + public override void KilledUnit(Unit who) + { + if (who.IsTypeId(TypeId.Player)) + Talk(NexusCommandersConst.SayKill); + } + + public override void UpdateAI(uint diff) + { + if (!UpdateVictim()) + return; + + _scheduler.Update(diff); + + if (me.HasUnitState(UnitState.Casting)) + return; + + DoMeleeAttackIfReady(); + } + } + + public override CreatureAI GetAI(Creature creature) + { + return GetInstanceAI(creature); + } + } +} diff --git a/Scripts/Northrend/Nexus/Nexus/BossOrmorok.cs b/Scripts/Northrend/Nexus/Nexus/BossOrmorok.cs new file mode 100644 index 000000000..1c9ba3ed3 --- /dev/null +++ b/Scripts/Northrend/Nexus/Nexus/BossOrmorok.cs @@ -0,0 +1,291 @@ +/* + * Copyright (C) 2012-2017 CypherCore + * + * 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, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +using Framework.Constants; +using Game.AI; +using Game.Entities; +using Game.Scripting; +using Game.Spells; +using System; + +namespace Scripts.Northrend.Nexus.Nexus +{ + struct OrmorokConst + { + //Spells + public const uint SpellReflection = 47981; + public const uint SpellTrample = 48016; + public const uint SpellFrenzy = 48017; + public const uint SpellSummonCrystallineTangler = 61564; + public const uint SpellCrystalSpikes = 47958; + + //Texts + public const uint SayAggro = 1; + public const uint SayDeath = 2; + public const uint SayReflect = 3; + public const uint SayCrystalSpikes = 4; + public const uint SayKill = 5; + public const uint SayFrenzy = 6; + } + + [Script] + class boss_ormorok : CreatureScript + { + public boss_ormorok() : base("boss_ormorok") { } + + class boss_ormorokAI : BossAI + { + public boss_ormorokAI(Creature creature) : base(creature, DataTypes.Ormorok) + { + Initialize(); + } + + void Initialize() + { + frenzy = false; + } + + public override void Reset() + { + base.Reset(); + Initialize(); + } + + public override void EnterCombat(Unit who) + { + _EnterCombat(); + + //Crystal Spikes + _scheduler.Schedule(TimeSpan.FromSeconds(12), task => + { + Talk(OrmorokConst.SayCrystalSpikes); + DoCast(OrmorokConst.SpellCrystalSpikes); + task.Repeat(TimeSpan.FromSeconds(12)); + }); + + //Trample + _scheduler.Schedule(TimeSpan.FromSeconds(10), task => + { + DoCast(me, OrmorokConst.SpellTrample); + task.Repeat(TimeSpan.FromSeconds(10)); + }); + + //Spell Reflection + _scheduler.Schedule(TimeSpan.FromSeconds(30), task => + { + Talk(OrmorokConst.SayReflect); + DoCast(me, OrmorokConst.SpellReflection); + task.Repeat(TimeSpan.FromSeconds(30)); + }); + + //Heroic Crystalline Tangler + if (IsHeroic()) + { + _scheduler.Schedule(TimeSpan.FromSeconds(17), task => + { + Unit target = SelectTarget(SelectAggroTarget.Random, 0, new OrmorokTanglerPredicate(me)); + if (target) + DoCast(target, OrmorokConst.SpellSummonCrystallineTangler); + + task.Repeat(TimeSpan.FromSeconds(17)); + }); + } + + Talk(OrmorokConst.SayAggro); + } + + public override void DamageTaken(Unit attacker, ref uint damage) + { + if (!frenzy && HealthBelowPct(25)) + { + Talk(OrmorokConst.SayFrenzy); + DoCast(me, OrmorokConst.SpellFrenzy); + frenzy = true; + } + } + + public override void JustDied(Unit killer) + { + _JustDied(); + Talk(OrmorokConst.SayDeath); + } + + public override void KilledUnit(Unit who) + { + if (who.IsTypeId(TypeId.Player)) + Talk(OrmorokConst.SayKill); + } + + public override void UpdateAI(uint diff) + { + if (!UpdateVictim()) + return; + + _scheduler.Update(diff); + + if (me.HasUnitState(UnitState.Casting)) + return; + + DoMeleeAttackIfReady(); + } + + bool frenzy; + } + + public override CreatureAI GetAI(Creature creature) + { + return GetInstanceAI(creature); + } + } + + class OrmorokTanglerPredicate : ISelector + { + public OrmorokTanglerPredicate(Unit unit) + { + me = unit; + } + + public bool Check(Unit target) + { + return target.GetDistance2d(me) >= 5.0f; + } + + Unit me; + } + + struct CrystalSpikesConst + { + public const uint NpcCrystalSpikeInitial = 27101; + public const uint NpcCrystalSpikeTrigger = 27079; + + public const uint DataCount = 1; + public const uint MaxCount = 5; + + public const uint SpellCrystalSpikeDamage = 47944; + + public const uint GoCrystalSpikeTrap = 188537; + + + public static uint[] CrystalSpikeSummon = + { + 47936, + 47942, + 7943 + }; + } + + [Script] + class npc_crystal_spike_trigger : CreatureScript + { + public npc_crystal_spike_trigger() : base("npc_crystal_spike_trigger") { } + + class npc_crystal_spike_triggerAI : ScriptedAI + { + public npc_crystal_spike_triggerAI(Creature creature) : base(creature) { } + + public override void IsSummonedBy(Unit owner) + { + switch (me.GetEntry()) + { + case CrystalSpikesConst.NpcCrystalSpikeInitial: + _count = 0; + me.SetFacingToObject(owner); + break; + case CrystalSpikesConst.NpcCrystalSpikeTrigger: + Creature trigger = owner.ToCreature(); + if (trigger) + _count = trigger.GetAI().GetData(CrystalSpikesConst.DataCount) + 1; + break; + default: + _count = CrystalSpikesConst.MaxCount; + break; + } + + if (me.GetEntry() == CrystalSpikesConst.NpcCrystalSpikeTrigger) + { + GameObject trap = me.FindNearestGameObject(CrystalSpikesConst.GoCrystalSpikeTrap, 1.0f); + if (trap) + trap.Use(me); + } + + //Despawn + _scheduler.Schedule(TimeSpan.FromSeconds(2), task => + { + if (me.GetEntry() == CrystalSpikesConst.NpcCrystalSpikeTrigger) + { + GameObject trap = me.FindNearestGameObject(CrystalSpikesConst.GoCrystalSpikeTrap, 1.0f); + if (trap) + trap.Delete(); + } + + me.DespawnOrUnsummon(); + }); + } + + public override uint GetData(uint type) + { + return type == CrystalSpikesConst.DataCount ? _count : 0; + } + + public override void UpdateAI(uint diff) + { + _scheduler.Update(diff); + } + + uint _count; + } + + public override CreatureAI GetAI(Creature creature) + { + return new npc_crystal_spike_triggerAI(creature); + } + } + + [Script] + class spell_crystal_spike : SpellScriptLoader + { + public spell_crystal_spike() : base("spell_crystal_spike") { } + + class spell_crystal_spike_AuraScript : AuraScript + { + void HandlePeriodic(AuraEffect aurEff) + { + Unit target = GetTarget(); + if (target.GetEntry() == CrystalSpikesConst.NpcCrystalSpikeInitial || target.GetEntry() == CrystalSpikesConst.NpcCrystalSpikeTrigger) + { + Creature trigger = target.ToCreature(); + if (trigger) + { + uint spell = target.GetEntry() == CrystalSpikesConst.NpcCrystalSpikeInitial ? CrystalSpikesConst.CrystalSpikeSummon[0] : CrystalSpikesConst.CrystalSpikeSummon[RandomHelper.IRand(0, 2)]; + if (trigger.GetAI().GetData(CrystalSpikesConst.DataCount) < CrystalSpikesConst.MaxCount) + trigger.CastSpell(trigger, spell, true); + } + } + } + + public override void Register() + { + OnEffectPeriodic.Add(new EffectPeriodicHandler(HandlePeriodic, 0, AuraType.PeriodicDummy)); + } + } + + public override AuraScript GetAuraScript() + { + return new spell_crystal_spike_AuraScript(); + } + } +} diff --git a/Scripts/Northrend/Nexus/Nexus/InstanceNexus.cs b/Scripts/Northrend/Nexus/Nexus/InstanceNexus.cs new file mode 100644 index 000000000..9cfc1da72 --- /dev/null +++ b/Scripts/Northrend/Nexus/Nexus/InstanceNexus.cs @@ -0,0 +1,227 @@ +/* + * Copyright (C) 2012-2017 CypherCore + * + * 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, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +using Framework.Constants; +using Game.Entities; +using Game.Maps; +using Game.Scripting; + +namespace Scripts.Northrend.Nexus.Nexus +{ + struct DataTypes + { + public const uint Commander = 0; + public const uint MagusTelestra = 1; + public const uint Anomalus = 2; + public const uint Ormorok = 3; + public const uint Keristrasza = 4; + + public const uint AnomalusContainmetSphere = 5; + public const uint OrmorokContainmetSphere = 6; + public const uint TelestraContainmetSphere = 7; + } + + struct CreatureIds + { + public const uint Anomalus = 26763; + public const uint Keristrasza = 26723; + + // Alliance + public const uint AllianceBerserker = 26800; + public const uint AllianceRanger = 26802; + public const uint AllianceCleric = 26805; + public const uint AllianceCommander = 27949; + public const uint CommanderStoutbeard = 26796; + + // Horde + public const uint HordeBerserker = 26799; + public const uint HordeRanger = 26801; + public const uint HordeCleric = 26803; + public const uint HordeCommander = 27947; + public const uint CommanderKolurg = 26798; + } + + struct GameObjectIds + { + public const uint AnomalusContainmetSphere = 188527; + public const uint OrmoroksContainmetSphere = 188528; + public const uint TelestrasContainmetSphere = 188526; + } + + [Script] + class instance_nexus : InstanceMapScript + { + public instance_nexus() : base(nameof(instance_nexus), 576) { } + + class instance_nexus_InstanceMapScript : InstanceScript + { + public instance_nexus_InstanceMapScript(Map map) : base(map) + { + SetHeaders("NEX"); + SetBossNumber(DataTypes.Keristrasza + 1); + _teamInInstance = 0; + } + + public override void OnPlayerEnter(Player player) + { + if (_teamInInstance == 0) + _teamInInstance = player.GetTeam(); + } + + public override void OnCreatureCreate(Creature creature) + { + switch (creature.GetEntry()) + { + case CreatureIds.Anomalus: + AnomalusGUID = creature.GetGUID(); + break; + case CreatureIds.Keristrasza: + KeristraszaGUID = creature.GetGUID(); + break; + // Alliance npcs are spawned by default, if you are alliance, you will fight against horde npcs. + case CreatureIds.AllianceBerserker: + if (ServerAllowsTwoSideGroups()) + creature.SetFaction(16); + if (_teamInInstance == Team.Alliance) + creature.UpdateEntry(CreatureIds.HordeBerserker); + break; + case CreatureIds.AllianceRanger: + if (ServerAllowsTwoSideGroups()) + creature.SetFaction(16); + if (_teamInInstance == Team.Alliance) + creature.UpdateEntry(CreatureIds.HordeRanger); + break; + case CreatureIds.AllianceCleric: + if (ServerAllowsTwoSideGroups()) + creature.SetFaction(16); + if (_teamInInstance == Team.Alliance) + creature.UpdateEntry(CreatureIds.HordeCleric); + break; + case CreatureIds.AllianceCommander: + if (ServerAllowsTwoSideGroups()) + creature.SetFaction(16); + if (_teamInInstance == Team.Alliance) + creature.UpdateEntry(CreatureIds.HordeCommander); + break; + case CreatureIds.CommanderStoutbeard: + if (ServerAllowsTwoSideGroups()) + creature.SetFaction(16); + if (_teamInInstance == Team.Alliance) + creature.UpdateEntry(CreatureIds.CommanderKolurg); + break; + default: + break; + } + } + + public override void OnGameObjectCreate(GameObject go) + { + switch (go.GetEntry()) + { + case GameObjectIds.AnomalusContainmetSphere: + AnomalusContainmentSphere = go.GetGUID(); + if (GetBossState(DataTypes.Anomalus) == EncounterState.Done) + go.RemoveFlag(GameObjectFields.Flags, GameObjectFlags.NotSelectable); + break; + case GameObjectIds.OrmoroksContainmetSphere: + OrmoroksContainmentSphere = go.GetGUID(); + if (GetBossState(DataTypes.Ormorok) == EncounterState.Done) + go.RemoveFlag(GameObjectFields.Flags, GameObjectFlags.NotSelectable); + break; + case GameObjectIds.TelestrasContainmetSphere: + TelestrasContainmentSphere = go.GetGUID(); + if (GetBossState(DataTypes.MagusTelestra) == EncounterState.Done) + go.RemoveFlag(GameObjectFields.Flags, GameObjectFlags.NotSelectable); + break; + default: + break; + } + } + + public override bool SetBossState(uint type, EncounterState state) + { + if (!base.SetBossState(type, state)) + return false; + + switch (type) + { + case DataTypes.MagusTelestra: + if (state == EncounterState.Done) + { + GameObject sphere = instance.GetGameObject(TelestrasContainmentSphere); + if (sphere) + sphere.RemoveFlag(GameObjectFields.Flags, GameObjectFlags.NotSelectable); + } + break; + case DataTypes.Anomalus: + if (state == EncounterState.Done) + { + GameObject sphere = instance.GetGameObject(AnomalusContainmentSphere); + if (sphere) + sphere.RemoveFlag(GameObjectFields.Flags, GameObjectFlags.NotSelectable); + } + break; + case DataTypes.Ormorok: + if (state == EncounterState.Done) + { + GameObject sphere = instance.GetGameObject(OrmoroksContainmentSphere); + if (sphere) + sphere.RemoveFlag(GameObjectFields.Flags, GameObjectFlags.NotSelectable); + } + break; + default: + break; + } + + return true; + } + + public override ObjectGuid GetGuidData(uint type) + { + switch (type) + { + case DataTypes.Anomalus: + return AnomalusGUID; + case DataTypes.Keristrasza: + return KeristraszaGUID; + case DataTypes.AnomalusContainmetSphere: + return AnomalusContainmentSphere; + case DataTypes.OrmorokContainmetSphere: + return OrmoroksContainmentSphere; + case DataTypes.TelestraContainmetSphere: + return TelestrasContainmentSphere; + default: + break; + } + + return ObjectGuid.Empty; + } + + ObjectGuid AnomalusGUID; + ObjectGuid KeristraszaGUID; + ObjectGuid AnomalusContainmentSphere; + ObjectGuid OrmoroksContainmentSphere; + ObjectGuid TelestrasContainmentSphere; + Team _teamInInstance; + } + + public override InstanceScript GetInstanceScript(InstanceMap map) + { + return new instance_nexus_InstanceMapScript(map); + } + } +} diff --git a/Scripts/Northrend/Nexus/Oculus/InstanceOculus.cs b/Scripts/Northrend/Nexus/Oculus/InstanceOculus.cs new file mode 100644 index 000000000..0ba0673e1 --- /dev/null +++ b/Scripts/Northrend/Nexus/Oculus/InstanceOculus.cs @@ -0,0 +1,368 @@ +/* + * Copyright (C) 2012-2017 CypherCore + * + * 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, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using Game.Entities; +using Game.Scripting; +using Game.Maps; +using Framework.Constants; +using Game.Network.Packets; +using Framework.Dynamic; + +namespace Scripts.Northrend.Nexus.Oculus +{ + /* + struct DataTypes + { + // Encounter States/Boss GUIDs + public const uint Drakos = 0; + public const uint Varos = 1; + public const uint Urom = 2; + public const uint Eregos = 3; + // GPS System + public const uint Constructs = 4; + } + + class instance_oculus : InstanceMapScript + { + public instance_oculus() : base(nameof(instance_oculus), 578) { } + + class instance_oculus_InstanceMapScript : InstanceScript + { + public instance_oculus_InstanceMapScript(Map map) : base(map) + { + SetHeaders("OC"); + SetBossNumber(DataTypes.Eregos + 1); + LoadDoorData(doorData); + + CentrifugueConstructCounter = 0; + } + + public override void OnCreatureCreate(Creature creature) + { + switch (creature.GetEntry()) + { + case NPC_DRAKOS: + DrakosGUID = creature.GetGUID(); + break; + case NPC_VAROS: + VarosGUID = creature.GetGUID(); + if (GetBossState(DATA_DRAKOS) == EncounterState.Done) + creature.SetPhaseMask(1, true); + break; + case NPC_UROM: + UromGUID = creature.GetGUID(); + if (GetBossState(DATA_VAROS) == EncounterState.Done) + creature.SetPhaseMask(1, true); + break; + case NPC_EREGOS: + EregosGUID = creature.GetGUID(); + if (GetBossState(DATA_UROM) == EncounterState.Done) + creature.SetPhaseMask(1, true); + break; + case NPC_CENTRIFUGE_CONSTRUCT: + if (creature.IsAlive()) + DoUpdateWorldState(WORLD_STATE_CENTRIFUGE_CONSTRUCT_AMOUNT, ++CentrifugueConstructCounter); + break; + case NPC_BELGARISTRASZ: + BelgaristraszGUID = creature.GetGUID(); + if (GetBossState(DATA_DRAKOS) == EncounterState.Done) + { + creature.SetFlag(UnitFields.NpcFlags, NPCFlags.Gossip); + creature.Relocate(BelgaristraszMove); + } + break; + case NPC_ETERNOS: + EternosGUID = creature.GetGUID(); + if (GetBossState(DATA_DRAKOS) == EncounterState.Done) + { + creature.SetFlag(UnitFields.NpcFlags, NPCFlags.Gossip); + creature.Relocate(EternosMove); + } + break; + case NPC_VERDISA: + VerdisaGUID = creature.GetGUID(); + if (GetBossState(DATA_DRAKOS) == EncounterState.Done) + { + creature.SetFlag(UnitFields.NpcFlags, NPCFlags.Gossip); + creature.Relocate(VerdisaMove); + } + break; + case NPC_GREATER_WHELP: + if (GetBossState(DATA_UROM) == EncounterState.Done) + { + creature.SetPhaseMask(1, true); + GreaterWhelpList.Add(creature.GetGUID()); + } + break; + default: + break; + } + } + + public override void OnGameObjectCreate(GameObject go) + { + switch (go.GetEntry()) + { + case GO_DRAGON_CAGE_DOOR: + AddDoor(go, true); + break; + case GO_EREGOS_CACHE_N: + case GO_EREGOS_CACHE_H: + EregosCacheGUID = go.GetGUID(); + break; + default: + break; + } + } + + public override void OnGameObjectRemove(GameObject go) + { + switch (go.GetEntry()) + { + case GO_DRAGON_CAGE_DOOR: + AddDoor(go, false); + break; + default: + break; + } + } + + public override void OnUnitDeath(Unit unit) + { + Creature creature = unit.ToCreature(); + if (!creature) + return; + + if (creature.GetEntry() == NPC_CENTRIFUGE_CONSTRUCT) + { + DoUpdateWorldState(WORLD_STATE_CENTRIFUGE_CONSTRUCT_AMOUNT, --CentrifugueConstructCounter); + + if (CentrifugueConstructCounter == 0) + { + Creature varos = instance.GetCreature(VarosGUID); + if (varos) + varos.RemoveAllAuras(); + } + } + } + + public override void FillInitialWorldStates(InitWorldStates packet) + { + if (GetBossState(DATA_DRAKOS) == EncounterState.Done && GetBossState(DATA_VAROS) != EncounterState.Done) + { + packet.Worldstates.Add(WORLD_STATE_CENTRIFUGE_CONSTRUCT_SHOW, 1); + packet.Worldstates.Add(WORLD_STATE_CENTRIFUGE_CONSTRUCT_AMOUNT, CentrifugueConstructCounter); + } + else + { + packet.Worldstates.Add(WORLD_STATE_CENTRIFUGE_CONSTRUCT_SHOW, 0); + packet.Worldstates.Add(WORLD_STATE_CENTRIFUGE_CONSTRUCT_AMOUNT, 0); + } + } + + public override void ProcessEvent(WorldObject Unit, uint eventId) + { + if (eventId != EVENT_CALL_DRAGON) + return; + + Creature varos = instance.GetCreature(VarosGUID); + if (varos) + { + Creature drake = varos.SummonCreature(NPC_AZURE_RING_GUARDIAN, varos.GetPositionX(), varos.GetPositionY(), varos.GetPositionZ() + 40); + if (drake) + drake.GetAI().DoAction(ACTION_CALL_DRAGON_EVENT); + } + } + + public override bool SetBossState(uint type, EncounterState state) + { + if (!base.SetBossState(type, state)) + return false; + + switch (type) + { + case DATA_DRAKOS: + if (state == EncounterState.Done) + { + DoUpdateWorldState(WORLD_STATE_CENTRIFUGE_CONSTRUCT_SHOW, 1); + DoUpdateWorldState(WORLD_STATE_CENTRIFUGE_CONSTRUCT_AMOUNT, CentrifugueConstructCounter); + FreeDragons(); + Creature varos = instance.GetCreature(VarosGUID); + if (varos) + varos.SetPhaseMask(1, true); + events.ScheduleEvent(EVENT_VAROS_INTRO, 15000); + } + break; + case DATA_VAROS: + if (state == EncounterState.Done) + { + DoUpdateWorldState(WORLD_STATE_CENTRIFUGE_CONSTRUCT_SHOW, 0); + Creature urom = instance.GetCreature(UromGUID); + if (urom) + urom.SetPhaseMask(1, true); + } + break; + case DATA_UROM: + if (state == EncounterState.Done) + { + Creature eregos = instance.GetCreature(EregosGUID); + if (eregos) + { + eregos.SetPhaseMask(1, true); + GreaterWhelps(); + events.ScheduleEvent(EVENT_EREGOS_INTRO, 5000); + } + } + break; + case DATA_EREGOS: + if (state == EncounterState.Done) + { + GameObject cache = instance.GetGameObject(EregosCacheGUID); + if (cache) + { + cache.SetRespawnTime((int)cache.GetRespawnDelay()); + cache.RemoveFlag(GAMEOBJECT_FLAGS, GO_FLAG_NOT_SELECTABLE); + } + } + break; + } + + return true; + } + + public override uint GetData(uint type) + { + if (type == DATA_CONSTRUCTS) + { + if (CentrifugueConstructCounter == 0) + return KILL_NO_CONSTRUCT; + else if (CentrifugueConstructCounter == 1) + return KILL_ONE_CONSTRUCT; + else if (CentrifugueConstructCounter > 1) + return KILL_MORE_CONSTRUCT; + } + + return KILL_NO_CONSTRUCT; + } + + public override ObjectGuid GetGuidData(uint type) + { + switch (type) + { + case DATA_DRAKOS: + return DrakosGUID; + case DATA_VAROS: + return VarosGUID; + case DATA_UROM: + return UromGUID; + case DATA_EREGOS: + return EregosGUID; + default: + break; + } + + return ObjectGuid.Empty; + } + + void FreeDragons() + { + Creature belgaristrasz = instance.GetCreature(BelgaristraszGUID); + if (belgaristrasz) + { + belgaristrasz.SetWalk(true); + belgaristrasz.GetMotionMaster().MovePoint(POINT_MOVE_OUT, BelgaristraszMove); + } + + Creature eternos = instance.GetCreature(EternosGUID); + if (eternos) + { + eternos.SetWalk(true); + eternos.GetMotionMaster().MovePoint(POINT_MOVE_OUT, EternosMove); + } + + Creature verdisa = instance.GetCreature(VerdisaGUID); + if (verdisa) + { + verdisa.SetWalk(true); + verdisa.GetMotionMaster().MovePoint(POINT_MOVE_OUT, VerdisaMove); + } + } + + public override void Update(uint diff) + { + _events.Update(diff); + + _events.ExecuteEvents(eventId => + { + switch (eventId) + { + case EVENT_VAROS_INTRO: + Creature varos = instance.GetCreature(VarosGUID); + if (varos) + varos.GetAI().Talk(SAY_VAROS_INTRO_TEXT); + break; + case EVENT_EREGOS_INTRO: + Creature eregos = instance.GetCreature(EregosGUID); + if (eregos) + eregos.GetAI().Talk(SAY_EREGOS_INTRO_TEXT); + break; + default: + break; + } + }); + } + + void GreaterWhelps() + { + foreach (ObjectGuid guid in GreaterWhelpList) + { + Creature gwhelp = instance.GetCreature(guid); + if (gwhelp) + gwhelp.SetPhaseMask(1, true); + } + } + + ObjectGuid DrakosGUID; + ObjectGuid VarosGUID; + ObjectGuid UromGUID; + ObjectGuid EregosGUID; + + ObjectGuid BelgaristraszGUID; + ObjectGuid EternosGUID; + ObjectGuid VerdisaGUID; + + byte CentrifugueConstructCounter; + + ObjectGuid EregosCacheGUID; + + List GreaterWhelpList = new List(); + + EventMap events = new EventMap(); + } + + public override InstanceScript GetInstanceScript(InstanceMap map) + { + return new instance_oculus_InstanceMapScript(map); + } + } + */ +} diff --git a/Scripts/Northrend/Ulduar/AlgalonTheObserver.cs b/Scripts/Northrend/Ulduar/AlgalonTheObserver.cs new file mode 100644 index 000000000..d8a6a9e8f --- /dev/null +++ b/Scripts/Northrend/Ulduar/AlgalonTheObserver.cs @@ -0,0 +1,30 @@ +/* + * Copyright (C) 2012-2017 CypherCore + * + * 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, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +using Game.Entities; + +namespace Scripts.Northrend.Ulduar +{ + namespace Algalon + { + class AlgalonTheObserver + { + public static Position AlgalonSummonPos = new Position(1632.531f, -304.8516f, 450.1123f, 1.530165f); + public static Position AlgalonLandPos = new Position(1632.668f, -302.7656f, 417.3211f, 1.530165f); + } + } +} diff --git a/Scripts/Northrend/Ulduar/FlameLeviathan.cs b/Scripts/Northrend/Ulduar/FlameLeviathan.cs new file mode 100644 index 000000000..8805ef7e7 --- /dev/null +++ b/Scripts/Northrend/Ulduar/FlameLeviathan.cs @@ -0,0 +1,1768 @@ +/* + * Copyright (C) 2012-2017 CypherCore + * + * 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, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +using Framework.Constants; +using Game.AI; +using Game.Entities; +using Game.Maps; +using Game.Scripting; +using Game.Spells; +using System; +using System.Collections.Generic; +using System.Diagnostics.Contracts; + +namespace Scripts.Northrend.Ulduar +{ + namespace FlameLeviathan + { + struct Spells + { + public const uint Pursued = 62374; + public const uint GatheringSpeed = 62375; + public const uint BatteringRam = 62376; + public const uint FlameVents = 62396; + public const uint MissileBarrage = 62400; + public const uint SystemsShutdown = 62475; + public const uint OverloadCircuit = 62399; + public const uint StartTheEngine = 62472; + public const uint SearingFlame = 62402; + public const uint Blaze = 62292; + public const uint TarPassive = 62288; + public const uint SmokeTrail = 63575; + public const uint Electroshock = 62522; + public const uint Napalm = 63666; + public const uint InvisAndStealthDetect = 18950; // Passive + //Tower Additional Spells + public const uint ThorimSHammer = 62911; // Tower Of Storms + public const uint MimironSInferno = 62909; // Tower Of Flames + public const uint HodirSFury = 62533; // Tower Of Frost + public const uint FreyaSWard = 62906; // Tower Of Nature + public const uint FreyaSummons = 62947; // Tower Of Nature + //Tower Ap & Health Spells + public const uint BuffTowerOfStorms = 65076; + public const uint BuffTowerOfFlames = 65075; + public const uint BuffTowerOfFrost = 65077; + public const uint BuffTowerOfLife = 64482; + //Additional Spells + public const uint Lash = 65062; + public const uint FreyaSWardEffect1 = 62947; + public const uint FreyaSWardEffect2 = 62907; + public const uint AutoRepair = 62705; + public const uint DummyBlue = 63294; + public const uint DummyGreen = 63295; + public const uint DummyYellow = 63292; + public const uint LiquidPyrite = 62494; + public const uint DustyExplosion = 63360; + public const uint DustCloudImpact = 54740; + public const uint StealthDetection = 18950; + public const uint RideVehicle = 46598; + } + + struct CreatureIds + { + public const uint Seat = 33114; + public const uint Mechanolift = 33214; + public const uint Liquid = 33189; + public const uint Container = 33218; + public const uint ThorimBeacon = 33365; + public const uint MimironBeacon = 33370; + public const uint HodirBeacon = 33212; + public const uint FreyaBeacon = 33367; + public const uint ThorimTargetBeacon = 33364; + public const uint MimironTargetBeacon = 33369; + public const uint HodirTargetBeacon = 33108; + public const uint FreyaTargetBeacon = 33366; + public const uint Lorekeeper = 33686; // Hard Mode Starter + public const uint BranzBronzbeard = 33579; + public const uint Delorah = 33701; + public const uint UlduarGauntletGenerator = 33571; // Trigger Tied To Towers + } + + struct Towers + { + public const uint ofStorms = 194377; + public const uint ofFlames = 194371; + public const uint ofFrost = 194370; + public const uint ofLife = 194375; + } + + struct Events + { + public const int Pursue = 1; + public const int Missile = 2; + public const int Vent = 3; + public const int Speed = 4; + public const int Summon = 5; + public const int Shutdown = 6; + public const int Repair = 7; + public const int ThorimSHammer = 8; // Tower Of Storms + public const int MimironSInferno = 9; // Tower Of Flames + public const int HodirSFury = 10; // Tower Of Frost + public const int FreyaSWard = 11; // Tower Of Nature + } + + struct Seats + { + public const int Player = 0; + public const int Turret = 1; + public const int Device = 2; + public const int Cannon = 7; + } + + struct Vehicles + { + public const uint Siege = 33060; + public const uint Chopper = 33062; + public const uint Demolisher = 33109; + } + + struct Leviathan + { + public const int DataShutout = 29112912; // 2911, 2912 are achievement IDs + public const int DataOrbitAchievements = 1; + public const int VehicleSpawns = 5; + public const int FreyaSpawns = 4; + + public const int ActionStartHardMode = 5; + public const int ActionSpawnVehicles = 6; + // Amount of seats depending on Raid mode + public const int TwoSeats = 2; + public const int FourSeats = 4; + + //Postions + public static Position Center = new Position(354.8771f, -12.90240f, 409.803650f); + public static Position InfernoStart = new Position(390.93f, -13.91f, 409.81f); + + public static Position[] PosSiege = + { + new Position(-814.59f, -64.54f, 429.92f, 5.969f), + new Position(-784.37f, -33.31f, 429.92f, 5.096f), + new Position(-808.99f, -52.10f, 429.92f, 5.668f), + new Position(-798.59f, -44.00f, 429.92f, 5.663f), + new Position(-812.83f, -77.71f, 429.92f, 0.046f), + }; + + public static Position[] PosChopper = + { + new Position(-717.83f, -106.56f, 430.02f, 0.122f), + new Position(-717.83f, -114.23f, 430.44f, 0.122f), + new Position(-717.83f, -109.70f, 430.22f, 0.122f), + new Position(-718.45f, -118.24f, 430.26f, 0.052f), + new Position(-718.45f, -123.58f, 430.41f, 0.085f), + }; + + public static Position[] PosDemolisher = + { + new Position(-724.12f, -176.64f, 430.03f, 2.543f), + new Position(-766.70f, -225.03f, 430.50f, 1.710f), + new Position(-729.54f, -186.26f, 430.12f, 1.902f), + new Position(-756.01f, -219.23f, 430.50f, 2.369f), + new Position(-798.01f, -227.24f, 429.84f, 1.446f), + }; + + public static Position[] FreyaBeacons = + { + new Position(377.02f, -119.10f, 409.81f), + new Position(185.62f, -119.10f, 409.81f), + new Position(377.02f, 54.78f, 409.81f), + new Position(185.62f, 54.78f, 409.81f), + }; + } + + struct Says + { + public const uint Aggro = 0; + public const uint Slay = 1; + public const uint Death = 2; + public const uint Target = 3; + public const uint Hardmode = 4; + public const uint TowerNone = 5; + public const uint TowerFrost = 6; + public const uint TowerFlame = 7; + public const uint TowerNature = 8; + public const uint TowerStorm = 9; + public const uint PlayerRiding = 10; + public const uint Overload = 11; + public const uint EmotePursue = 12; + public const uint EmoteOverload = 13; + public const int EmoteRepair = 14; + } + + [Script] + class boss_flame_leviathan : CreatureScript + { + public boss_flame_leviathan() : base("boss_flame_leviathan") { } + + class boss_flame_leviathanAI : BossAI + { + public boss_flame_leviathanAI(Creature creature) : base(creature, BossIds.Leviathan) + { + vehicle = creature.GetVehicleKit(); + } + + public override void InitializeAI() + { + Contract.Assert(vehicle); + if (!me.IsDead()) + Reset(); + + ActiveTowersCount = 4; + Shutdown = 0; + ActiveTowers = false; + towerOfStorms = false; + towerOfLife = false; + towerOfFlames = false; + towerOfFrost = false; + Shutout = true; + Unbroken = true; + + DoCast(Spells.InvisAndStealthDetect); + + me.SetFlag(UnitFields.Flags, UnitFlags.NonAttackable | UnitFlags.NotSelectable | UnitFlags.Stunned); + me.SetReactState(ReactStates.Passive); + } + + public override void Reset() + { + _Reset(); + //resets shutdown counter to 0. 2 or 4 depending on raid mode + Shutdown = 0; + _pursueTarget.Clear(); + + me.SetReactState(ReactStates.Defensive); + } + + public override void EnterCombat(Unit who) + { + _EnterCombat(); + me.SetReactState(ReactStates.Passive); + _events.ScheduleEvent(Events.Pursue, 1); + _events.ScheduleEvent(Events.Missile, RandomHelper.URand(1500, 4 * Time.InMilliseconds)); + _events.ScheduleEvent(Events.Vent, 20 * Time.InMilliseconds); + _events.ScheduleEvent(Events.Shutdown, 150 * Time.InMilliseconds); + _events.ScheduleEvent(Events.Speed, 15 * Time.InMilliseconds); + _events.ScheduleEvent(Events.Summon, 1 * Time.InMilliseconds); + ActiveTower(); //void ActiveTower + } + + void ActiveTower() + { + if (ActiveTowers) + { + if (towerOfStorms) + { + me.AddAura(Spells.BuffTowerOfStorms, me); + _events.ScheduleEvent(Events.ThorimSHammer, 35 * Time.InMilliseconds); + } + + if (towerOfFlames) + { + me.AddAura(Spells.BuffTowerOfFlames, me); + _events.ScheduleEvent(Events.MimironSInferno, 70 * Time.InMilliseconds); + } + + if (towerOfFrost) + { + me.AddAura(Spells.BuffTowerOfFrost, me); + _events.ScheduleEvent(Events.HodirSFury, 105 * Time.InMilliseconds); + } + + if (towerOfLife) + { + me.AddAura(Spells.BuffTowerOfLife, me); + _events.ScheduleEvent(Events.FreyaSWard, 140 * Time.InMilliseconds); + } + + if (!towerOfLife && !towerOfFrost && !towerOfFlames && !towerOfStorms) + Talk(Says.TowerNone); + else + Talk(Says.Hardmode); + } + else + Talk(Says.Aggro); + } + + public override void JustDied(Unit killer) + { + _JustDied(); + // Set Field Flags 67108928 = 64 | 67108864 = UNIT_FLAG_UNK_6 | UNIT_FLAG_SKINNABLE + // Set DynFlags 12 + // Set NPCFlags 0 + Talk(Says.Death); + } + + public override void SpellHit(Unit caster, SpellInfo spell) + { + if (spell.Id == Spells.StartTheEngine) + vehicle.InstallAllAccessories(false); + + if (spell.Id == Spells.Electroshock) + me.InterruptSpell(CurrentSpellTypes.Channeled); + + if (spell.Id == Spells.OverloadCircuit) + ++Shutdown; + } + + public override uint GetData(uint type) + { + switch (type) + { + case Leviathan.DataShutout: + return (uint)(Shutout ? 1 : 0); + case InstanceAchievementData.DataUnbroken: + return (uint)(Unbroken ? 1 : 0); + case Leviathan.DataOrbitAchievements: + if (ActiveTowers) // Only on HardMode + return ActiveTowersCount; + break; + default: + break; + } + + return 0; + } + + public override void SetData(uint id, uint data) + { + if (id == InstanceAchievementData.DataUnbroken) + Unbroken = data != 0 ? true : false; + } + + public override void UpdateAI(uint diff) + { + if (!UpdateVictim()) + return; + + _events.Update(diff); + + if (Shutdown == RaidMode(Leviathan.TwoSeats, Leviathan.FourSeats)) + { + Shutdown = 0; + _events.ScheduleEvent(Events.Shutdown, 4000); + me.RemoveAurasDueToSpell(Spells.OverloadCircuit); + me.InterruptNonMeleeSpells(true); + return; + } + + if (me.HasUnitState(UnitState.Casting)) + return; + _events.ExecuteEvents(eventId => + { + switch (eventId) + { + case Events.Pursue: + Talk(Says.Target); + DoCast(Spells.Pursued); // Will select target in spellscript + _events.ScheduleEvent(Events.Pursue, 35 * Time.InMilliseconds); + break; + case Events.Missile: + DoCast(me, Spells.MissileBarrage, true); + _events.ScheduleEvent(Events.Missile, 2 * Time.InMilliseconds); + break; + case Events.Vent: + DoCastAOE(Spells.FlameVents); + _events.ScheduleEvent(Events.Vent, 20 * Time.InMilliseconds); + break; + case Events.Speed: + DoCastAOE(Spells.GatheringSpeed); + _events.ScheduleEvent(Events.Speed, 15 * Time.InMilliseconds); + break; + case Events.Summon: + if (summons.Count < 15) + { + Creature lift = DoSummonFlyer(CreatureIds.Mechanolift, me, 30.0f, 50.0f, 0); + if (lift) + lift.GetMotionMaster().MoveRandom(100); + } + _events.ScheduleEvent(Events.Summon, 2 * Time.InMilliseconds); + break; + case Events.Shutdown: + Talk(Says.Overload); + Talk(Says.EmoteOverload); + me.CastSpell(me, Spells.SystemsShutdown, true); + if (Shutout) + Shutout = false; + _events.ScheduleEvent(Events.Repair, 4000); + _events.DelayEvents(20 * Time.InMilliseconds, 0); + break; + case Events.Repair: + Talk(Says.EmoteRepair); + me.ClearUnitState(UnitState.Stunned | UnitState.Root); + _events.ScheduleEvent(Events.Shutdown, 150 * Time.InMilliseconds); + _events.CancelEvent(Events.Repair); + break; + case Events.ThorimSHammer: // Tower of Storms + for (byte i = 0; i < 7; ++i) + { + Creature thorim = DoSummon(CreatureIds.ThorimBeacon, me, RandomHelper.URand(20, 60), 20000, TempSummonType.TimedDespawn); + if (thorim) + thorim.GetMotionMaster().MoveRandom(100); + } + Talk(Says.TowerStorm); + _events.CancelEvent(Events.ThorimSHammer); + break; + case Events.MimironSInferno: // Tower of Flames + me.SummonCreature(CreatureIds.MimironBeacon, Leviathan.InfernoStart); + Talk(Says.TowerFlame); + _events.CancelEvent(Events.MimironSInferno); + break; + case Events.HodirSFury: // Tower of Frost + for (byte i = 0; i < 7; ++i) + { + Creature hodir = DoSummon(CreatureIds.HodirBeacon, me, 50f, 0); + if (hodir) + hodir.GetMotionMaster().MoveRandom(100); + } + Talk(Says.TowerFrost); + _events.CancelEvent(Events.HodirSFury); + break; + case Events.FreyaSWard: // Tower of Nature + Talk(Says.TowerNature); + for (int i = 0; i < 4; ++i) + me.SummonCreature(CreatureIds.FreyaBeacon, Leviathan.FreyaBeacons[i]); + + Unit target = SelectTarget(SelectAggroTarget.Random); + if (target) + DoCast(target, Spells.FreyaSWard); + _events.CancelEvent(Events.FreyaSWard); + break; + } + }); + + DoBatteringRamIfReady(); + } + + public override void SpellHitTarget(Unit target, SpellInfo spell) + { + if (spell.Id == Spells.Pursued) + _pursueTarget = target.GetGUID(); + } + + public override void DoAction(int action) + { + if (action != 0 && action <= 4) // Tower destruction, debuff leviathan loot and reduce active tower count + { + if (me.HasLootMode(LootModes.Default | LootModes.HardMode1 | LootModes.HardMode2 | LootModes.HardMode3 | LootModes.HardMode4) && ActiveTowersCount == 4) + me.RemoveLootMode(LootModes.HardMode4); + + if (me.HasLootMode(LootModes.Default | LootModes.HardMode1 | LootModes.HardMode2 | LootModes.HardMode3) && ActiveTowersCount == 3) + me.RemoveLootMode(LootModes.HardMode3); + + if (me.HasLootMode(LootModes.Default | LootModes.HardMode1 | LootModes.HardMode2) && ActiveTowersCount == 2) + me.RemoveLootMode(LootModes.HardMode2); + + if (me.HasLootMode(LootModes.Default | LootModes.HardMode1) && ActiveTowersCount == 1) + me.RemoveLootMode(LootModes.HardMode1); + } + + switch (action) + { + case LeviathanActions.TowerOfStormDestroyed: + if (towerOfStorms) + { + towerOfStorms = false; + --ActiveTowersCount; + } + break; + case LeviathanActions.TowerOfFrostDestroyed: + if (towerOfFrost) + { + towerOfFrost = false; + --ActiveTowersCount; + } + break; + case LeviathanActions.TowerOfFlamesDestroyed: + if (towerOfFlames) + { + towerOfFlames = false; + --ActiveTowersCount; + } + break; + case LeviathanActions.TowerOfLifeDestroyed: + if (towerOfLife) + { + towerOfLife = false; + --ActiveTowersCount; + } + break; + case Leviathan.ActionStartHardMode: // Activate hard-mode enable all towers, apply buffs on leviathan + ActiveTowers = true; + towerOfStorms = true; + towerOfLife = true; + towerOfFlames = true; + towerOfFrost = true; + me.SetLootMode(LootModes.Default | LootModes.HardMode1 | LootModes.HardMode2 | LootModes.HardMode3 | LootModes.HardMode4); + break; + case LeviathanActions.MoveToCenterPosition: // Triggered by 2 Collossus near door + if (!me.IsDead()) + { + me.SetHomePosition(Leviathan.Center); + me.GetMotionMaster().MoveCharge(Leviathan.Center.GetPositionX(), Leviathan.Center.GetPositionY(), Leviathan.Center.GetPositionZ()); // position center + me.SetReactState(ReactStates.Aggressive); + me.RemoveFlag(UnitFields.Flags, UnitFlags.NonAttackable | UnitFlags.NotSelectable | UnitFlags.Stunned); + return; + } + break; + default: + break; + } + } + + //! Copypasta from DoSpellAttackIfReady, only difference is the target - it cannot be selected trough GetVictim this way - + //! I also removed the spellInfo check + void DoBatteringRamIfReady() + { + if (me.isAttackReady()) + { + Unit target = Global.ObjAccessor.GetUnit(me, _pursueTarget); + if (me.IsWithinCombatRange(target, 30.0f)) + { + DoCast(target, Spells.BatteringRam); + me.resetAttackTimer(); + } + } + } + + Vehicle vehicle; + byte ActiveTowersCount; + byte Shutdown; + bool ActiveTowers; + bool towerOfStorms; + bool towerOfLife; + bool towerOfFlames; + bool towerOfFrost; + bool Shutout; + bool Unbroken; + ObjectGuid _pursueTarget; + } + + public override CreatureAI GetAI(Creature creature) + { + return instance_ulduar.GetUlduarInstanceAI(creature); + } + } + + [Script] + class boss_flame_leviathan_seat : CreatureScript + { + public boss_flame_leviathan_seat() : base("boss_flame_leviathan_seat") { } + + class boss_flame_leviathan_seatAI : ScriptedAI + { + public boss_flame_leviathan_seatAI(Creature creature) + : base(creature) + { + vehicle = creature.GetVehicleKit(); + Contract.Assert(vehicle); + me.SetReactState(ReactStates.Passive); + me.SetDisplayId(me.GetCreatureTemplate().ModelId2); + instance = creature.GetInstanceScript(); + } + + InstanceScript instance; + Vehicle vehicle; + + public override void PassengerBoarded(Unit who, sbyte seatId, bool apply) + { + if (!me.GetVehicle()) + return; + + if (seatId == Seats.Player) + { + Creature leviathan = me.GetVehicleCreatureBase(); + if (!apply) + return; + else if (leviathan) + leviathan.GetAI().Talk(Says.PlayerRiding); + + Unit turretPassenger = me.GetVehicleKit().GetPassenger(Seats.Turret); + if (turretPassenger) + { + Creature turret = turretPassenger.ToCreature(); + if (turret) + { + turret.SetFaction(me.GetVehicleBase().getFaction()); + turret.SetUInt32Value(UnitFields.Flags, 0); // unselectable + turret.GetAI().AttackStart(who); + } + } + Unit devicePassenger = me.GetVehicleKit().GetPassenger(Seats.Device); + if (devicePassenger) + { + Creature device = devicePassenger.ToCreature(); + if (device) + { + device.SetFlag64(UnitFields.NpcFlags, NPCFlags.SpellClick); + device.RemoveFlag(UnitFields.Flags, UnitFlags.NotSelectable); + } + } + + me.SetFlag(UnitFields.Flags, UnitFlags.NotSelectable); + } + else if (seatId == Seats.Turret) + { + if (apply) + return; + + Unit device = vehicle.GetPassenger(Seats.Device); + if (device) + { + device.SetFlag64(UnitFields.NpcFlags, NPCFlags.SpellClick); + device.SetUInt32Value(UnitFields.Flags, 0); // unselectable + } + } + } + } + + public override CreatureAI GetAI(Creature creature) + { + return instance_ulduar.GetUlduarInstanceAI(creature); + } + } + + [Script] + class boss_flame_leviathan_defense_cannon : CreatureScript + { + public boss_flame_leviathan_defense_cannon() : base("boss_flame_leviathan_defense_cannon") { } + + class boss_flame_leviathan_defense_cannonAI : ScriptedAI + { + public boss_flame_leviathan_defense_cannonAI(Creature creature) + : base(creature) + { + } + + uint NapalmTimer; + + public override void Reset() + { + NapalmTimer = 5 * Time.InMilliseconds; + DoCast(me, Spells.StealthDetection); + } + + public override void UpdateAI(uint diff) + { + if (!UpdateVictim()) + return; + + if (NapalmTimer <= diff) + { + Unit target = SelectTarget(SelectAggroTarget.Random, 0); + if (target) + if (CanAIAttack(target)) + DoCast(target, Spells.Napalm, true); + + NapalmTimer = 5000; + } + else + NapalmTimer -= diff; + } + + public override bool CanAIAttack(Unit who) + { + if (!who.IsTypeId(TypeId.Player) || !who.GetVehicle() || who.GetVehicleBase().GetEntry() == CreatureIds.Seat) + return false; + return true; + } + } + + public override CreatureAI GetAI(Creature creature) + { + return new boss_flame_leviathan_defense_cannonAI(creature); + } + } + + [Script] + class boss_flame_leviathan_defense_turret : CreatureScript + { + public boss_flame_leviathan_defense_turret() : base("boss_flame_leviathan_defense_turret") { } + + class boss_flame_leviathan_defense_turretAI : TurretAI + { + public boss_flame_leviathan_defense_turretAI(Creature creature) : base(creature) { } + + public override void DamageTaken(Unit who, ref uint damage) + { + if (!CanAIAttack(who)) + damage = 0; + } + + public override bool CanAIAttack(Unit who) + { + if (!who.IsTypeId(TypeId.Player) || !who.GetVehicle() || who.GetVehicleBase().GetEntry() != CreatureIds.Seat) + return false; + return true; + } + } + + public override CreatureAI GetAI(Creature creature) + { + return new boss_flame_leviathan_defense_turretAI(creature); + } + } + + [Script] + class boss_flame_leviathan_overload_device : CreatureScript + { + public boss_flame_leviathan_overload_device() : base("boss_flame_leviathan_overload_device") { } + + class boss_flame_leviathan_overload_deviceAI : PassiveAI + { + public boss_flame_leviathan_overload_deviceAI(Creature creature) + : base(creature) + { + } + + public override void OnSpellClick(Unit clicker, ref bool result) + { + if (!result) + return; + + if (me.GetVehicle()) + { + me.RemoveFlag64(UnitFields.NpcFlags, NPCFlags.SpellClick); + me.SetFlag(UnitFields.Flags, UnitFlags.NotSelectable); + + Unit player = me.GetVehicle().GetPassenger(Seats.Player); + if (player) + { + me.GetVehicleBase().CastSpell(player, Spells.SmokeTrail, true); + player.GetMotionMaster().MoveKnockbackFrom(me.GetVehicleBase().GetPositionX(), me.GetVehicleBase().GetPositionY(), 30, 30); + player.ExitVehicle(); + } + } + } + } + + public override CreatureAI GetAI(Creature creature) + { + return new boss_flame_leviathan_overload_deviceAI(creature); + } + } + + [Script] + class boss_flame_leviathan_safety_container : CreatureScript + { + public boss_flame_leviathan_safety_container() : base("boss_flame_leviathan_safety_container") { } + + class boss_flame_leviathan_safety_containerAI : PassiveAI + { + public boss_flame_leviathan_safety_containerAI(Creature creature) + : base(creature) + { + } + + public override void JustDied(Unit killer) + { + float x, y, z; + me.GetPosition(out x, out y, out z); + z = me.GetMap().GetHeight(me.GetPhases(), x, y, z); + me.GetMotionMaster().MovePoint(0, x, y, z); + me.SetPosition(x, y, z, 0); + } + + public override void UpdateAI(uint diff) + { + } + } + + public override CreatureAI GetAI(Creature creature) + { + return new boss_flame_leviathan_safety_containerAI(creature); + } + } + + [Script] + class npc_mechanolift : CreatureScript + { + public npc_mechanolift() : base("npc_mechanolift") { } + + class npc_mechanoliftAI : PassiveAI + { + public npc_mechanoliftAI(Creature creature) : base(creature) + { + Contract.Assert(me.GetVehicleKit()); + } + + uint MoveTimer; + + public override void Reset() + { + MoveTimer = 0; + me.GetMotionMaster().MoveRandom(50); + } + + public override void JustDied(Unit killer) + { + me.GetMotionMaster().MoveTargetedHome(); + DoCast(Spells.DustyExplosion); + Creature liquid = DoSummon(CreatureIds.Liquid, me, 0f); + if (liquid) + { + liquid.CastSpell(liquid, Spells.LiquidPyrite, true); + liquid.CastSpell(liquid, Spells.DustCloudImpact, true); + } + } + + public override void MovementInform(MovementGeneratorType type, uint id) + { + if (type == MovementGeneratorType.Point && id == 1) + { + Creature container = me.FindNearestCreature(CreatureIds.Container, 5, true); + if (container) + container.EnterVehicle(me); + } + } + + public override void UpdateAI(uint diff) + { + if (MoveTimer <= diff) + { + if (me.GetVehicleKit().HasEmptySeat(-1)) + { + Creature container = me.FindNearestCreature(CreatureIds.Container, 50, true); + if (container && !container.GetVehicle()) + me.GetMotionMaster().MovePoint(1, container.GetPositionX(), container.GetPositionY(), container.GetPositionZ()); + } + + MoveTimer = 30000; //check next 30 seconds + } + else + MoveTimer -= diff; + } + } + + public override CreatureAI GetAI(Creature creature) + { + return new npc_mechanoliftAI(creature); + } + } + + [Script] + class npc_pool_of_tar : CreatureScript + { + public npc_pool_of_tar() : base("npc_pool_of_tar") { } + + class npc_pool_of_tarAI : ScriptedAI + { + public npc_pool_of_tarAI(Creature creature) + : base(creature) + { + me.RemoveFlag(UnitFields.Flags, UnitFlags.NotSelectable); + me.SetReactState(ReactStates.Passive); + me.CastSpell(me, Spells.TarPassive, true); + } + + public override void DamageTaken(Unit who, ref uint damage) + { + damage = 0; + } + + public override void SpellHit(Unit caster, SpellInfo spell) + { + if (spell.SchoolMask.HasAnyFlag(SpellSchoolMask.Fire) && !me.HasAura(Spells.Blaze)) + me.CastSpell(me, Spells.Blaze, true); + } + + public override void UpdateAI(uint diff) { } + } + + public override CreatureAI GetAI(Creature creature) + { + return new npc_pool_of_tarAI(creature); + } + } + + [Script] + class npc_colossus : CreatureScript + { + public npc_colossus() : base("npc_colossus") { } + + class npc_colossusAI : ScriptedAI + { + public npc_colossusAI(Creature creature) + : base(creature) + { + instance = creature.GetInstanceScript(); + } + + InstanceScript instance; + + public override void JustDied(Unit killer) + { + if (me.GetHomePosition().IsInDist(Leviathan.Center, 50.0f)) + instance.SetData(InstanceData.Colossus, instance.GetData(InstanceData.Colossus) + 1); + } + + public override void UpdateAI(uint diff) + { + if (!UpdateVictim()) + return; + + DoMeleeAttackIfReady(); + } + } + + public override CreatureAI GetAI(Creature creature) + { + return GetInstanceAI(creature); + } + } + + [Script] + class npc_thorims_hammer : CreatureScript + { + public npc_thorims_hammer() : base("npc_thorims_hammer") { } + + class npc_thorims_hammerAI : ScriptedAI + { + public npc_thorims_hammerAI(Creature creature) + : base(creature) + { + me.SetFlag(UnitFields.Flags, UnitFlags.NotSelectable); + me.CastSpell(me, Spells.DummyBlue, true); + } + + public override void MoveInLineOfSight(Unit who) + { + if (who.IsTypeId(TypeId.Player) && who.IsVehicle() && me.IsInRange(who, 0, 10, false)) + { + Creature trigger = DoSummonFlyer(CreatureIds.ThorimTargetBeacon, me, 20, 0, 1000, TempSummonType.TimedDespawn); + if (trigger) + trigger.CastSpell(who, Spells.ThorimSHammer, true); + } + } + + public override void UpdateAI(uint diff) + { + if (!me.HasAura(Spells.DummyBlue)) + me.CastSpell(me, Spells.DummyBlue, true); + + UpdateVictim(); + } + } + + public override CreatureAI GetAI(Creature creature) + { + return new npc_thorims_hammerAI(creature); + } + } + + [Script] + class npc_mimirons_inferno : CreatureScript + { + public npc_mimirons_inferno() : base("npc_mimirons_inferno") { } + + class npc_mimirons_infernoAI : npc_escortAI + { + public npc_mimirons_infernoAI(Creature creature) + : base(creature) + { + me.SetFlag(UnitFields.Flags, UnitFlags.NonAttackable | UnitFlags.NotSelectable); + me.CastSpell(me, Spells.DummyYellow, true); + me.SetReactState(ReactStates.Passive); + } + + public override void WaypointReached(uint waypointId) + { + + } + + public override void Reset() + { + infernoTimer = 2000; + } + + uint infernoTimer; + + public override void UpdateAI(uint diff) + { + base.UpdateAI(diff); + + if (!HasEscortState(eEscortState.Escorting)) + Start(false, true, ObjectGuid.Empty, null, false, true); + else + { + if (infernoTimer <= diff) + { + Creature trigger = DoSummonFlyer(CreatureIds.MimironTargetBeacon, me, 20, 0, 1000, TempSummonType.TimedDespawn); + if (trigger) + { + trigger.CastSpell(me.GetPositionX(), me.GetPositionY(), me.GetPositionZ(), Spells.MimironSInferno, true); + infernoTimer = 2000; + } + } + else + infernoTimer -= diff; + + if (!me.HasAura(Spells.DummyYellow)) + me.CastSpell(me, Spells.DummyYellow, true); + } + } + } + + public override CreatureAI GetAI(Creature creature) + { + return new npc_mimirons_infernoAI(creature); + } + } + + [Script] + class npc_hodirs_fury : CreatureScript + { + public npc_hodirs_fury() : base("npc_hodirs_fury") { } + + class npc_hodirs_furyAI : ScriptedAI + { + public npc_hodirs_furyAI(Creature creature) + : base(creature) + { + me.SetFlag(UnitFields.Flags, UnitFlags.NotSelectable); + me.CastSpell(me, Spells.DummyGreen, true); + } + + public override void MoveInLineOfSight(Unit who) + { + if (who.IsTypeId(TypeId.Player) && who.IsVehicle() && me.IsInRange(who, 0, 5, false)) + { + Creature trigger = DoSummonFlyer(CreatureIds.HodirTargetBeacon, me, 20, 0, 1000, TempSummonType.TimedDespawn); + if (trigger) + trigger.CastSpell(who, Spells.HodirSFury, true); + } + } + + public override void UpdateAI(uint diff) + { + if (!me.HasAura(Spells.DummyGreen)) + me.CastSpell(me, Spells.DummyGreen, true); + + UpdateVictim(); + } + } + + public override CreatureAI GetAI(Creature creature) + { + return new npc_hodirs_furyAI(creature); + } + } + + [Script] + class npc_freyas_ward : CreatureScript + { + public npc_freyas_ward() : base("npc_freyas_ward") { } + + class npc_freyas_wardAI : ScriptedAI + { + public npc_freyas_wardAI(Creature creature) + : base(creature) + { + me.CastSpell(me, Spells.DummyGreen, true); + } + + uint summonTimer; + + public override void Reset() + { + summonTimer = 5000; + } + + public override void UpdateAI(uint diff) + { + if (summonTimer <= diff) + { + DoCast(Spells.FreyaSWardEffect1); + DoCast(Spells.FreyaSWardEffect2); + summonTimer = 20000; + } + else + summonTimer -= diff; + + if (!me.HasAura(Spells.DummyGreen)) + me.CastSpell(me, Spells.DummyGreen, true); + + UpdateVictim(); + } + } + + public override CreatureAI GetAI(Creature creature) + { + return new npc_freyas_wardAI(creature); + } + } + + [Script] + class npc_freya_ward_summon : CreatureScript + { + public npc_freya_ward_summon() : base("npc_freya_ward_summon") { } + + class npc_freya_ward_summonAI : ScriptedAI + { + public npc_freya_ward_summonAI(Creature creature) + : base(creature) + { + creature.GetMotionMaster().MoveRandom(100); + } + + uint lashTimer; + + public override void Reset() + { + lashTimer = 5000; + } + + public override void UpdateAI(uint diff) + { + if (!UpdateVictim()) + return; + + if (lashTimer <= diff) + { + DoCast(Spells.Lash); + lashTimer = 20000; + } + else + lashTimer -= diff; + + DoMeleeAttackIfReady(); + } + } + + public override CreatureAI GetAI(Creature creature) + { + return new npc_freya_ward_summonAI(creature); + } + } + + [Script] + class npc_lorekeeper : CreatureScript + { + string GOSSIP_ITEM_1 = "Activate secondary defensive systems"; + string GOSSIP_ITEM_2 = "Confirmed"; + + public npc_lorekeeper() : base("npc_lorekeeper") { } + + class npc_lorekeeperAI : ScriptedAI + { + public npc_lorekeeperAI(Creature creature) + : base(creature) { } + + public override void DoAction(int action) + { + // Start encounter + if (action == Leviathan.ActionSpawnVehicles) + { + for (int i = 0; i < RaidMode(2, 5); ++i) + DoSummon(Vehicles.Siege, Leviathan.PosSiege[i], 3000, TempSummonType.CorpseTimedDespawn); + for (int i = 0; i < RaidMode(2, 5); ++i) + DoSummon(Vehicles.Chopper, Leviathan.PosChopper[i], 3000, TempSummonType.CorpseTimedDespawn); + for (int i = 0; i < RaidMode(2, 5); ++i) + DoSummon(Vehicles.Demolisher, Leviathan.PosDemolisher[i], 3000, TempSummonType.CorpseTimedDespawn); + return; + } + } + } + + public override bool OnGossipSelect(Player player, Creature creature, uint sender, uint action) + { + player.CLOSE_GOSSIP_MENU(); + InstanceScript instance = creature.GetInstanceScript(); + if (instance == null) + return true; + + switch (action) + { + case eTradeskill.GossipActionInfoDef + 1: + player.PrepareGossipMenu(creature); + instance.instance.LoadGrid(364, -16); //make sure leviathan is loaded + + player.ADD_GOSSIP_ITEM(GossipOptionIcon.Chat, GOSSIP_ITEM_2, eTradeskill.GossipSenderMain, eTradeskill.GossipActionInfoDef + 2); + player.SEND_GOSSIP_MENU(player.GetGossipTextId(creature), creature.GetGUID()); + break; + case eTradeskill.GossipActionInfoDef + 2: + Creature leviathan = instance.instance.GetCreature(instance.GetGuidData(BossIds.Leviathan)); + if (leviathan) + { + leviathan.GetAI().DoAction(Leviathan.ActionStartHardMode); + creature.SetVisible(false); + creature.GetAI().DoAction(Leviathan.ActionSpawnVehicles); // spawn the vehicles + Creature Delorah = creature.FindNearestCreature(CreatureIds.Delorah, 1000, true); + if (Delorah) + { + Creature Branz = creature.FindNearestCreature(CreatureIds.BranzBronzbeard, 1000, true); + if (Branz) + { + Delorah.GetMotionMaster().MovePoint(0, Branz.GetPositionX() - 4, Branz.GetPositionY(), Branz.GetPositionZ()); + // @todo Delorah.AI().Talk(xxxx, Branz); when reached at branz + } + } + } + break; + } + + return true; + } + + public override bool OnGossipHello(Player player, Creature creature) + { + InstanceScript instance = creature.GetInstanceScript(); + if (instance != null && instance.GetData(BossIds.Leviathan) != (uint)EncounterState.Done && player) + { + player.PrepareGossipMenu(creature); + + player.ADD_GOSSIP_ITEM(GossipOptionIcon.Chat, GOSSIP_ITEM_1, eTradeskill.GossipSenderMain, eTradeskill.GossipActionInfoDef + 1); + player.SEND_GOSSIP_MENU(player.GetGossipTextId(creature), creature.GetGUID()); + } + return true; + } + + public override CreatureAI GetAI(Creature creature) + { + return new npc_lorekeeperAI(creature); + } + } + + [Script] + class go_ulduar_tower : GameObjectScript + { + public go_ulduar_tower() : base("go_ulduar_tower") { } + + public override void OnDestroyed(GameObject go, Player player) + { + InstanceScript instance = go.GetInstanceScript(); + if (instance == null) + return; + + switch (go.GetEntry()) + { + case Towers.ofStorms: + instance.ProcessEvent(go, InstanceEventIds.TowerOfStormDestroyed); + break; + case Towers.ofFlames: + instance.ProcessEvent(go, InstanceEventIds.TowerOfFlamesDestroyed); + break; + case Towers.ofFrost: + instance.ProcessEvent(go, InstanceEventIds.TowerOfFrostDestroyed); + break; + case Towers.ofLife: + instance.ProcessEvent(go, InstanceEventIds.TowerOfLifeDestroyed); + break; + } + + Creature trigger = go.FindNearestCreature(CreatureIds.UlduarGauntletGenerator, 15.0f, true); + if (trigger) + trigger.DisappearAndDie(); + } + } + + [Script] + class achievement_three_car_garage_demolisher : AchievementCriteriaScript + { + public achievement_three_car_garage_demolisher() : base("achievement_three_car_garage_demolisher") { } + + public override bool OnCheck(Player source, Unit target) + { + Creature vehicle = source.GetVehicleCreatureBase(); + if (vehicle) + { + if (vehicle.GetEntry() == Vehicles.Demolisher) + return true; + } + + return false; + } + } + + [Script] + class achievement_three_car_garage_chopper : AchievementCriteriaScript + { + public achievement_three_car_garage_chopper() : base("achievement_three_car_garage_chopper") { } + + public override bool OnCheck(Player source, Unit target) + { + Creature vehicle = source.GetVehicleCreatureBase(); + if (vehicle) + { + if (vehicle.GetEntry() == Vehicles.Chopper) + return true; + } + + return false; + } + } + + [Script] + class achievement_three_car_garage_siege : AchievementCriteriaScript + { + public achievement_three_car_garage_siege() : base("achievement_three_car_garage_siege") { } + + public override bool OnCheck(Player source, Unit target) + { + Creature vehicle = source.GetVehicleCreatureBase(); + if (vehicle) + { + if (vehicle.GetEntry() == Vehicles.Siege) + return true; + } + + return false; + } + } + + [Script] + class achievement_shutout : AchievementCriteriaScript + { + public achievement_shutout() : base("achievement_shutout") { } + + public override bool OnCheck(Player source, Unit target) + { + if (target) + { + Creature leviathan = target.ToCreature(); + if (leviathan) + if (leviathan.GetAI().GetData(Leviathan.DataShutout) != 0) + return true; + } + + return false; + } + } + + [Script] + class achievement_unbroken : AchievementCriteriaScript + { + public achievement_unbroken() : base("achievement_unbroken") { } + + public override bool OnCheck(Player source, Unit target) + { + if (target) + { + InstanceScript instance = target.GetInstanceScript(); + if (instance != null) + return instance.GetData(InstanceAchievementData.DataUnbroken) != 0; + } + + return false; + } + } + + [Script] + class achievement_orbital_bombardment : AchievementCriteriaScript + { + public achievement_orbital_bombardment() : base("achievement_orbital_bombardment") { } + + public override bool OnCheck(Player source, Unit target) + { + if (!target) + return false; + + Creature leviathan = target.ToCreature(); + if (leviathan) + if (leviathan.GetAI().GetData(Leviathan.DataOrbitAchievements) >= 1) + return true; + + return false; + } + } + + [Script] + class achievement_orbital_devastation : AchievementCriteriaScript + { + public achievement_orbital_devastation() : base("achievement_orbital_devastation") { } + + public override bool OnCheck(Player source, Unit target) + { + if (!target) + return false; + Creature leviathan = target.ToCreature(); + if (leviathan) + if (leviathan.GetAI().GetData(Leviathan.DataOrbitAchievements) >= 2) + return true; + + return false; + } + } + + [Script] + class achievement_nuked_from_orbit : AchievementCriteriaScript + { + public achievement_nuked_from_orbit() : base("achievement_nuked_from_orbit") { } + + public override bool OnCheck(Player source, Unit target) + { + if (!target) + return false; + + Creature leviathan = target.ToCreature(); + if (leviathan) + if (leviathan.GetAI().GetData(Leviathan.DataOrbitAchievements) >= 3) + return true; + + return false; + } + } + + [Script] + class achievement_orbit_uary : AchievementCriteriaScript + { + public achievement_orbit_uary() : base("achievement_orbit_uary") { } + + public override bool OnCheck(Player source, Unit target) + { + if (!target) + return false; + + Creature leviathan = target.ToCreature(); + if (leviathan) + if (leviathan.GetAI().GetData(Leviathan.DataOrbitAchievements) == 4) + return true; + + return false; + } + } + + [Script] + class spell_load_into_catapult : SpellScriptLoader + { + enum Spells + { + SPELL_PASSENGER_LOADED = 62340, + } + + public spell_load_into_catapult() : base("spell_load_into_catapult") { } + + class spell_load_into_catapult_AuraScript : AuraScript + { + void OnApply(AuraEffect aurEff, AuraEffectHandleModes mode) + { + Unit owner = GetOwner().ToUnit(); + if (!owner) + return; + + owner.CastSpell(owner, 62340, true); + } + + void OnRemove(AuraEffect aurEff, AuraEffectHandleModes mode) + { + Unit owner = GetOwner().ToUnit(); + if (!owner) + return; + + owner.RemoveAurasDueToSpell(62340); + } + + public override void Register() + { + OnEffectApply.Add(new EffectApplyHandler(OnApply, 0, AuraType.ControlVehicle, AuraEffectHandleModes.Real)); + OnEffectRemove.Add(new EffectApplyHandler(OnRemove, 0, AuraType.ControlVehicle, AuraEffectHandleModes.RealOrReapplyMask)); + } + } + + public override AuraScript GetAuraScript() + { + return new spell_load_into_catapult_AuraScript(); + } + } + + [Script] + class spell_auto_repair : SpellScriptLoader + { + public spell_auto_repair() : base("spell_auto_repair") { } + + class spell_auto_repair_SpellScript : SpellScript + { + void CheckCooldownForTarget(SpellMissInfo missInfo) + { + if (missInfo != SpellMissInfo.None) + return; + + if (GetHitUnit().HasAuraEffect(62705, 2)) // Check presence of dummy aura indicating cooldown + { + PreventHitEffect(0); + PreventHitDefaultEffect(1); + PreventHitDefaultEffect(2); + //! Currently this doesn't work: if we call PreventHitAura(), the existing aura will be removed + //! because of recent aura refreshing changes. Since removing the existing aura negates the idea + //! of a cooldown marker, we just let the dummy aura refresh itself without executing the other SpellEffectName. + //! The spelleffects can be executed by letting the dummy aura expire naturally. + //! This is a temporary solution only. + } + } + + void HandleScript(uint eff) + { + Vehicle vehicle = GetHitUnit().GetVehicleKit(); + if (!vehicle) + return; + + Player driver = vehicle.GetPassenger(0) ? vehicle.GetPassenger(0).ToPlayer() : null; + if (!driver) + return; + + driver.TextEmote(Says.EmoteRepair, driver, true); + + InstanceScript instance = driver.GetInstanceScript(); + if (instance == null) + return; + + // Actually should/could use basepoints (100) for this spell effect as percentage of health, but oh well. + vehicle.GetBase().SetFullHealth(); + + // For achievement + instance.SetData(InstanceAchievementData.DataUnbroken, 0); + } + + public override void Register() + { + OnEffectHitTarget.Add(new EffectHandler(HandleScript, 0, SpellEffectName.ScriptEffect)); + BeforeHit.Add(new BeforeHitHandler(CheckCooldownForTarget)); + } + } + + public override SpellScript GetSpellScript() + { + return new spell_auto_repair_SpellScript(); + } + } + + [Script] + class spell_systems_shutdown : SpellScriptLoader + { + public spell_systems_shutdown() : base("spell_systems_shutdown") { } + + class spell_systems_shutdown_AuraScript : AuraScript + { + void OnApply(AuraEffect aurEff, AuraEffectHandleModes mode) + { + Creature owner = GetOwner().ToCreature(); + if (!owner) + return; + + //! This could probably in the SPELL_EFFECT_SEND_EVENT handler too: + owner.AddUnitState(UnitState.Stunned | UnitState.Root); + owner.SetFlag(UnitFields.Flags, UnitFlags.Stunned); + owner.RemoveAurasDueToSpell(Spells.GatheringSpeed); + } + + void OnRemove(AuraEffect aurEff, AuraEffectHandleModes mode) + { + Creature owner = GetOwner().ToCreature(); + if (!owner) + return; + + owner.RemoveFlag(UnitFields.Flags, UnitFlags.Stunned); + } + + public override void Register() + { + OnEffectApply.Add(new EffectApplyHandler(OnApply, 0, AuraType.ModDamagePercentTaken, AuraEffectHandleModes.Real)); + OnEffectRemove.Add(new EffectApplyHandler(OnRemove, 0, AuraType.ModDamagePercentTaken, AuraEffectHandleModes.Real)); + } + } + + public override AuraScript GetAuraScript() + { + return new spell_systems_shutdown_AuraScript(); + } + } + + [Script] + class spell_pursue : SpellScriptLoader + { + public spell_pursue() : base("spell_pursue") { } + + const uint AREA_FORMATION_GROUNDS = 4652; + + class spell_pursue_SpellScript : SpellScript + { + public override bool Load() + { + _target = null; + return true; + } + + void FilterTargets(List targets) + { + targets.RemoveAll(new Predicate(target => + { + //! No players, only vehicles (@todo check if blizzlike) + Creature creatureTarget = target.ToCreature(); + if (!creatureTarget) + return true; + + //! NPC entries must match + if (creatureTarget.GetEntry() != Vehicles.Demolisher && creatureTarget.GetEntry() != Vehicles.Siege) + return true; + + //! NPC must be a valid vehicle installation + Vehicle vehicle = creatureTarget.GetVehicleKit(); + if (!vehicle) + return true; + + //! Entity needs to be in appropriate area + if (target.GetAreaId() != AREA_FORMATION_GROUNDS) + return true; + + //! Vehicle must be in use by player + bool playerFound = false; + foreach (var seat in vehicle.Seats.Values) + { + if (seat.Passenger.Guid.IsPlayer()) + { + playerFound = true; + break; + } + } + + return !playerFound; + })); + + if (targets.Empty()) + { + Creature caster = GetCaster().ToCreature(); + if (caster) + caster.GetAI().EnterEvadeMode(); + } + else + { + //! In the end, only one target should be selected + _target = targets.PickRandom(); + FilterTargetsSubsequently(targets); + } + } + + void FilterTargetsSubsequently(List targets) + { + targets.Clear(); + if (_target) + targets.Add(_target); + } + + void HandleScript(uint eff) + { + Creature caster = GetCaster().ToCreature(); + if (!caster) + return; + + caster.GetAI().AttackStart(GetHitUnit()); // Chase target + + foreach (var seat in caster.GetVehicleKit().Seats.Values) + { + Player passenger = Global.ObjAccessor.GetPlayer(caster, seat.Passenger.Guid); + if (passenger) + { + caster.GetAI().Talk(Says.EmotePursue, passenger); + return; + } + } + } + + public override void Register() + { + OnObjectAreaTargetSelect.Add(new ObjectAreaTargetSelectHandler(FilterTargets, 0, Targets.UnitSrcAreaEnemy)); + OnObjectAreaTargetSelect.Add(new ObjectAreaTargetSelectHandler(FilterTargetsSubsequently, 1, Targets.UnitSrcAreaEnemy)); + OnEffectHitTarget.Add(new EffectHandler(HandleScript, 0, SpellEffectName.ApplyAura)); + } + + WorldObject _target; + } + + public override SpellScript GetSpellScript() + { + return new spell_pursue_SpellScript(); + } + } + + [Script] + class spell_vehicle_throw_passenger : SpellScriptLoader + { + public spell_vehicle_throw_passenger() : base("spell_vehicle_throw_passenger") { } + + class spell_vehicle_throw_passenger_SpellScript : SpellScript + { + void HandleScript(uint effIndex) + { + Spell baseSpell = GetSpell(); + SpellCastTargets targets = baseSpell.m_targets; + int damage = GetEffectValue(); + if (targets.HasTraj()) + { + Vehicle vehicle = GetCaster().GetVehicleKit(); + if (vehicle) + { + Unit passenger = vehicle.GetPassenger((sbyte)(damage - 1)); + if (passenger) + { + // use 99 because it is 3d search + List targetList = new List(); + var check = new WorldObjectSpellAreaTargetCheck(99, GetExplTargetDest(), GetCaster(), GetCaster(), GetSpellInfo(), SpellTargetCheckTypes.Default, null); + var searcher = new WorldObjectListSearcher(GetCaster(), targetList, check); + Cell.VisitAllObjects(GetCaster(), searcher, 99.0f); + float minDist = 99 * 99; + Unit target = null; + foreach (var obj in targetList) + { + Unit unit = obj.ToUnit(); + if (unit) + { + if (unit.GetEntry() == CreatureIds.Seat) + { + Vehicle seat = unit.GetVehicleKit(); + if (seat) + { + if (!seat.GetPassenger(0)) + { + Unit device = seat.GetPassenger(2); + if (device) + if (!device.GetCurrentSpell(CurrentSpellTypes.Channeled)) + { + float dist = unit.GetExactDistSq(targets.GetDstPos()); + if (dist < minDist) + { + minDist = dist; + target = unit; + } + } + } + } + } + } + } + if (target && target.IsWithinDist2d(targets.GetDstPos(), GetSpellInfo().GetEffect(effIndex).CalcRadius() * 2)) // now we use *2 because the location of the seat is not correct + passenger.EnterVehicle(target, 0); + else + { + passenger.ExitVehicle(); + passenger.GetMotionMaster().MoveJump(targets.GetDstPos(), targets.GetSpeedXY(), targets.GetSpeedZ()); + } + } + } + } + } + + public override void Register() + { + OnEffectHitTarget.Add(new EffectHandler(HandleScript, 0, SpellEffectName.Dummy)); + } + } + + public override SpellScript GetSpellScript() + { + return new spell_vehicle_throw_passenger_SpellScript(); + } + } + } +} diff --git a/Scripts/Northrend/Ulduar/Mimiron.cs b/Scripts/Northrend/Ulduar/Mimiron.cs new file mode 100644 index 000000000..e50d6d915 --- /dev/null +++ b/Scripts/Northrend/Ulduar/Mimiron.cs @@ -0,0 +1,2734 @@ +/* + * Copyright (C) 2012-2017 CypherCore + * + * 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, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +using Framework.Constants; +using Framework.GameMath; +using Game.AI; +using Game.Entities; +using Game.Maps; +using Game.Scripting; +using Game.Spells; +using System; +using System.Collections.Generic; +using System.Linq; + +namespace Scripts.Northrend.Ulduar +{ + namespace Mimiron + { + struct Yells + { + public const uint Aggro = 0; + public const uint HardmodeOn = 1; + public const uint MkiiActivate = 2; + public const uint MkiiSlay = 3; + public const uint MkiiDeath = 4; + public const uint Vx001Activate = 5; + public const uint Vx001Slay = 6; + public const uint Vx001Death = 7; + public const uint AerialActivate = 8; + public const uint AerialSlay = 9; + public const uint AerialDeath = 10; + public const uint V07tronActivate = 11; + public const uint V07tronSlay = 12; + public const uint V07tronDeath = 13; + public const uint Berserk = 14; + } + + struct ComputerYells + { + public const uint SelfDestructInitiated = 0; + public const uint SelfDestructTerminated = 1; + public const uint SelfDestruct10 = 2; + public const uint SelfDestruct9 = 3; + public const uint SelfDestruct8 = 4; + public const uint SelfDestruct7 = 5; + public const uint SelfDestruct6 = 6; + public const uint SelfDestruct5 = 7; + public const uint SelfDestruct4 = 8; + public const uint SelfDestruct3 = 9; + public const uint SelfDestruct2 = 10; + public const uint SelfDestruct1 = 11; + public const uint SelfDestructFinalized = 12; + } + + struct Spells + { + // Mimiron + public const uint Weld = 63339; // Idle Aura. + public const uint Seat1 = 52391; // Cast On All Vehicles; Cycled On Mkii + public const uint Seat2 = 63313; // Cast On Mkii And Vx-001; Cycled On Mkii + public const uint Seat3 = 63314; // Cast On Mkii; Cycled On Mkii + public const uint Seat5 = 63316; // Cast On Mkii And Vx-001; Cycled On Mkii + public const uint Seat6 = 63344; // Cast On Mkii + public const uint Seat7 = 63345; // Cast On Mkii + public const uint Jetpack = 63341; + public const uint DespawnAssaultBots = 64463; // Only Despawns Assault Bots... No Equivalent Spell For The Other Adds... + public const uint TeleportVisual = 41232; + public const uint SleepVisual1 = 64393; + public const uint SleepVisual2 = 64394; + + // Leviathan Mk Ii + public const uint FlameSuppressantMk = 64570; + public const uint NapalmShell = 63666; + public const uint ForceCastNapalmShell = 64539; + public const uint PlasmaBlast = 62997; + public const uint ScriptEffectPlasmaBlast = 64542; + public const uint ShockBlast = 63631; + public const uint ShockBlastAura = 63632; // Deprecated? It Is Never Cast. + + // Vx-001 + public const uint FlameSuppressantVx = 65192; + public const uint SpinningUp = 63414; + public const uint HeatWaveAura = 63679; + public const uint HandPulseLeft = 64348; + public const uint HandPulseRight = 64352; + public const uint MountMkii = 64387; + public const uint TorsoDisabled = 64120; + + // Aerial Command Unit + public const uint PlasmaBallP1 = 63689; + public const uint PlasmaBallP2 = 65647; + public const uint MountVx001 = 64388; + + // Proximity Mines + public const uint ProximityMines = 63027; // Cast By Leviathan Mk Ii + public const uint ProximityMineExplosion = 66351; + public const uint ProximityMineTrigger = 65346; + public const uint ProximityMinePeriodicTrigger = 65345; + public const uint PeriodicProximityAura = 65345; + public const uint SummonProximityMine = 65347; + + // Rapid Burst + public const uint RapidBurstLeft = 63387; + public const uint RapidBurstRight = 64019; + public const uint RapidBurst = 63382; // Cast By Vx-001 + public const uint RapidBurstTargetMe = 64841; // Cast By Burst Target + public const uint SummonBurstTarget = 64840; // Cast By Vx-001 + + // Rocket Strike + public const uint SummonRocketStrike = 63036; + public const uint ScriptEffectRocketStrike = 63681; // Cast By Rocket (Mimiron Visual) + public const uint RocketStrike = 64064; // Added In CreatureTemplateAddon + public const uint RocketStrikeSingle = 64402; // Cast By Vx-001 + public const uint RocketStrikeBoth = 65034; // Cast By Vx-001 + + // Flames + public const uint FlamesPeriodicTrigger = 64561; // Added In CreatureTemplateAddon + public const uint SummonFlamesSpreadTrigger = 64562; + public const uint SummonFlamesInitial = 64563; + public const uint SummonFlamesSpread = 64564; + public const uint Flames = 64566; + public const uint ScriptEffectSummonFlamesInitial = 64567; + + // Frost Bomb + public const uint ScriptEffectFrostBomb = 64623; // Cast By Vx-001 + public const uint FrostBombLinked = 64624; // Added In CreatureTemplateAddon + public const uint FrostBombDummy = 64625; + public const uint SummonFrostBomb = 64627; // Cast By Vx-001 + public const uint FrostBombExplosion = 64626; + public const uint ClearFires = 65354; + + // Bots + public const uint SummonFireBot = 64622; + public const uint SummonFireBotDummy = 64621; + public const uint SummonFireBotTrigger = 64620; // Cast By Areal Command Unit + public const uint DeafeningSiren = 64616; // Added In CreatureTemplateAddon + public const uint FireSearchAura = 64617; // Added In CreatureTemplateAddon + public const uint FireSearch = 64618; + public const uint WaterSpray = 64619; + + public const uint SummonJunkBot = 63819; + public const uint SummonJunkBotTrigger = 63820; // Cast By Areal Command Unit + public const uint SummonJunkBotDummy = 64398; + + public const uint SummonAssaultBotTrigger = 64425; // Cast By Areal Command Unit + public const uint SummonAssaultBotDummy = 64426; + public const uint SummonAssaultBot = 64427; + public const uint MagneticField = 64668; + + public const uint SummonBombBot = 63811; // Cast By Areal Command Unit + public const uint BombBotAura = 63767; // Added In CreatureTemplateAddon + + // Miscellaneous + public const uint SelfDestructionAura = 64610; + public const uint SelfDestructionVisual = 64613; + public const uint NotSoFriendlyFire = 65040; + public const uint ElevatorKnockback = 65096; // Cast By Worldtrigger. + public const uint VehicleDamaged = 63415; + public const uint EmergencyMode = 64582; // Mkii; Vx001; Aerial; Assault; Junk + public const uint EmergencyModeTurret = 65101; // Cast By Leviathan Mk Ii; Only Hits Leviathan Mk Ii Turret + public const uint SelfRepair = 64383; + public const uint MagneticCore = 64436; + public const uint MagneticCoreVisual = 64438; + public const uint HalfHeal = 64188; + public const uint ClearAllDebuffs = 34098; /// @Todo: Make Use Of This Spell... + public const uint FreezeAnimStun = 63354; // Used To Prevent Mkii From Doing Stuff?.. + public const uint FreezeAnim = 16245; // Idle Aura. Freezes Animation. + } + + struct Data + { + public const uint SetupMine = 0; + public const uint SetupBomb = 1; + public const uint SetupRocket = 2; + public const uint NotSoFriendlyFire = 3; + public const uint Firefighter = 4; + public const uint Waterspray = 5; + public const uint MoveNew = 6; + } + + struct Events + { + // Leviathan Mk Ii + public const uint ProximityMine = 1; + public const uint NapalmShell = 2; + public const uint PlasmaBlast = 3; + public const uint ShockBlast = 4; + public const uint FlameSuppressantMk = 5; + public const uint MovePoint2 = 6; + public const uint MovePoint3 = 7; + public const uint MovePoint5 = 8; + + // Vx-001 + public const uint RapidBurst = 1; + public const uint SpinningUp = 2; + public const uint RocketStrike = 3; + public const uint HandPulse = 4; + public const uint FrostBomb = 5; + public const uint FlameSuppressantVx = 6; + public const uint Reload = 7; + + // Aerial Command Unit + public const uint SummonFireBots = 1; + public const uint SummonJunkBot = 2; + public const uint SummonAssaultBot = 3; + public const uint SummonBombBot = 4; + + // Mimiron + public const uint SummonFlames = 1; + public const uint Intro1 = 2; + public const uint Intro2 = 3; + public const uint Intro3 = 4; + + public const uint Vx001Activation1 = 5; + public const uint Vx001Activation2 = 6; + public const uint Vx001Activation3 = 7; + public const uint Vx001Activation4 = 8; + public const uint Vx001Activation5 = 9; + public const uint Vx001Activation6 = 10; + public const uint Vx001Activation7 = 11; + public const uint Vx001Activation8 = 12; + + public const uint AerialActivation1 = 13; + public const uint AerialActivation2 = 14; + public const uint AerialActivation3 = 15; + public const uint AerialActivation4 = 16; + public const uint AerialActivation5 = 17; + public const uint AerialActivation6 = 18; + + public const uint Vol7ronActivation1 = 19; + public const uint Vol7ronActivation2 = 20; + public const uint Vol7ronActivation3 = 21; + public const uint Vol7ronActivation4 = 22; + public const uint Vol7ronActivation5 = 23; + public const uint Vol7ronActivation6 = 24; + public const uint Vol7ronActivation7 = 25; + + public const uint Outtro1 = 26; + public const uint Outtro2 = 27; + public const uint Outtro3 = 28; + + // Computer + public const uint SelfDestruct10 = 1; + public const uint SelfDestruct9 = 2; + public const uint SelfDestruct8 = 3; + public const uint SelfDestruct7 = 4; + public const uint SelfDestruct6 = 5; + public const uint SelfDestruct5 = 6; + public const uint SelfDestruct4 = 7; + public const uint SelfDestruct3 = 8; + public const uint SelfDestruct2 = 9; + public const uint SelfDestruct1 = 10; + public const uint SelfDestructFinalized = 11; + } + + struct Actions + { + public const int StartMkii = 1; + public const int HardmodeMkii = 2; + + public const int ActivateVx001 = 3; + public const int StartVx001 = 4; + public const int HardmodeVx001 = 5; + + public const int ActivateAerial = 6; + public const int StartAerial = 7; + public const int HardmodeAerial = 8; + public const int DisableAerial = 9; + public const int EnableAerial = 10; + + public const int ActivateV0l7r0n1 = 11; + public const int ActivateV0l7r0n2 = 12; + public const int AssembledCombat = 13; // All 3 Parts Use This Action = 1; Its Done On Purpose. + + public const int ActivateHardMode = 14; + public const int ActivateComputer = 15; + public const int DeactivateComputer = 16; + public const int ActivateSelfDestruct = 17; + + public const int EncounterDone = 18; + } + + struct Phases + { + public const byte LeviathanMkII = 1; + public const byte Vx001 = 2; + public const byte AerialCommandUnit = 3; + public const byte Vol7ron = 4; + } + + struct Waypoints + { + public const uint MkiiP1Idle = 1; + public const uint MkiiP4Pos1 = 2; + public const uint MkiiP4Pos2 = 3; + public const uint MkiiP4Pos3 = 4; + public const uint MkiiP4Pos4 = 5; + public const uint MkiiP4Pos5 = 6; + public const uint AerialP4Pos = 7; + } + + struct SeatIds + { + public const sbyte RocketLeft = 5; + public const sbyte RocketRight = 6; + } + + struct MimironConst + { + public static uint[] RepairSpells = + { + Spells.Seat1, + Spells.Seat2, + Spells.Seat3, + Spells.Seat5 + }; + + public static Position[] VehicleRelocation = + { + new Position(0.0f, 0.0f, 0.0f), + new Position(2792.07f, 2596.32f, 364.3136f, 3.560472f), // WP_MKII_P1_IDLE + new Position(2765.945f, 2571.095f, 364.0636f), // WP_MKII_P4_POS_1 + new Position(2768.195f, 2573.095f, 364.0636f), // WP_MKII_P4_POS_2 + new Position(2763.820f, 2568.870f, 364.3136f), // WP_MKII_P4_POS_3 + new Position(2761.215f, 2568.875f, 364.0636f), // WP_MKII_P4_POS_4 + new Position(2744.610f, 2569.380f, 364.3136f), // WP_MKII_P4_POS_5 + new Position(2748.513f, 2569.051f, 364.3136f) // WP_AERIAL_P4_POS + }; + + public static Position VX001SummonPos = new Position(2744.431f, 2569.385f, 364.3968f, 3.141593f); + public static Position ACUSummonPos = new Position(2744.650f, 2569.460f, 380.0000f, 0.0f); + + public static bool IsEncounterFinished(Unit who) + { + InstanceScript instance = who.GetInstanceScript(); + + Creature mkii = ObjectAccessor.GetCreature(who, instance.GetGuidData(InstanceData.LeviathanMKII)); + Creature vx001 = ObjectAccessor.GetCreature(who, instance.GetGuidData(InstanceData.VX001)); + Creature aerial = ObjectAccessor.GetCreature(who, instance.GetGuidData(InstanceData.AerialCommandUnit)); + if (!mkii || !vx001 || !aerial) + return false; + + if (mkii.GetStandState() == UnitStandStateType.Dead && vx001.GetStandState() == UnitStandStateType.Dead && aerial.GetStandState() == UnitStandStateType.Dead) + { + who.Kill(mkii); + who.Kill(vx001); + who.Kill(aerial); + mkii.DespawnOrUnsummon(120000); + vx001.DespawnOrUnsummon(120000); + aerial.DespawnOrUnsummon(120000); + Creature mimiron = ObjectAccessor.GetCreature(who, instance.GetGuidData(BossIds.Mimiron)); + if (mimiron) + mimiron.GetAI().JustDied(who); + return true; + } + return false; + } + } + + [Script] + class boss_mimiron : CreatureScript + { + public boss_mimiron() : base("boss_mimiron") { } + + class boss_mimironAI : BossAI + { + public boss_mimironAI(Creature creature) : base(creature, BossIds.Mimiron) + { + me.SetReactState(ReactStates.Passive); + _fireFighter = false; + } + + public override void InitializeAI() + { + SetupEncounter(); + } + + void SetupEncounter() + { + _Reset(); + me.SetReactState(ReactStates.Passive); + me.RemoveFlag(UnitFields.Flags, UnitFlags.NonAttackable); + + GameObject elevator = ObjectAccessor.GetGameObject(me, instance.GetGuidData(InstanceData.MimironElevator)); + if (elevator) + elevator.SetGoState(GameObjectState.Active); + + if (_fireFighter) + { + Creature computer = ObjectAccessor.GetCreature(me, instance.GetGuidData(InstanceData.Computer)); + if (computer) + computer.GetAI().DoAction(Actions.DeactivateComputer); + } + + GameObject button = ObjectAccessor.GetGameObject(me, instance.GetGuidData(InstanceData.MimironButton)); + if (button) + { + button.SetGoState(GameObjectState.Ready); + button.RemoveFlag(GameObjectFields.Flags, GameObjectFlags.NotSelectable); + } + + _fireFighter = false; + DoCast(me, Spells.Weld); + Unit mkii = Global.ObjAccessor.GetUnit(me, instance.GetGuidData(InstanceData.LeviathanMKII)); + if (mkii) + DoCast(mkii, Spells.Seat3); + + if (!_events.Empty()) + { + + } + } + + public override void DoAction(int action) + { + switch (action) + { + case Actions.ActivateVx001: + _events.ScheduleEvent(Events.Vx001Activation1, 1000); + break; + case Actions.ActivateAerial: + _events.ScheduleEvent(Events.AerialActivation1, 5000); + break; + case Actions.ActivateV0l7r0n1: + Talk(Yells.AerialDeath); + Creature mkii = ObjectAccessor.GetCreature(me, instance.GetGuidData(InstanceData.LeviathanMKII)); + if (mkii) + mkii.GetMotionMaster().MovePoint(Waypoints.MkiiP4Pos1, MimironConst.VehicleRelocation[Waypoints.MkiiP4Pos1]); + break; + case Actions.ActivateV0l7r0n2: + _events.ScheduleEvent(Events.Vol7ronActivation1, 1000); + break; + case Actions.ActivateHardMode: + _fireFighter = true; + DoZoneInCombat(me); + break; + default: + break; + } + } + + public override void EnterCombat(Unit who) + { + if (!me.GetVehicleBase()) + return; + + //PLay Sound number 15612 + + _EnterCombat(); + me.SetFlag(UnitFields.Flags, UnitFlags.NonAttackable); + me.RemoveAurasDueToSpell(Spells.Weld); + DoCast(me.GetVehicleBase(), Spells.Seat6); + + GameObject button = ObjectAccessor.GetGameObject(me, instance.GetGuidData(InstanceData.MimironButton)); + if (button) + button.SetFlag(GameObjectFields.Flags, GameObjectFlags.NotSelectable); + + if (_fireFighter) + _events.ScheduleEvent(Events.SummonFlames, 3000); + + _events.ScheduleEvent(Events.Intro1, 1500); + } + + public override void JustDied(Unit who) + { + instance.SetBossState(BossIds.Mimiron, EncounterState.Done); + _events.Reset(); + me.CombatStop(true); + me.SetDisableGravity(false); + DoCast(me, Spells.SleepVisual1); + DoCastAOE(Spells.DespawnAssaultBots); + me.ExitVehicle(); + // ExitVehicle() offset position is not implemented, so we make up for that with MoveJump()... + me.GetMotionMaster().MoveJump(me.GetPositionX() + (float)(10.0f * Math.Cos(me.GetOrientation())), me.GetPositionY() + (float)(10.0f * Math.Sin(me.GetOrientation())), me.GetPositionZ(), me.GetOrientation(), 10.0f, 5.0f); + _events.ScheduleEvent(Events.Outtro1, 7000); + } + + public override void JustRespawned() + { + //SetupEncounter(); + } + + public override void EnterEvadeMode(EvadeReason why = EvadeReason.Other) + { + _DespawnAtEvade(); + } + + public override void UpdateAI(uint diff) + { + if (!UpdateVictim() && instance.GetBossState(BossIds.Mimiron) != EncounterState.Done) + return; + + _events.Update(diff); + + if (me.HasUnitState(UnitState.Casting)) + return; + + _events.ExecuteEvents(eventId => + { + switch (eventId) + { + case Events.SummonFlames: + { + Unit worldtrigger = Global.ObjAccessor.GetUnit(me, instance.GetGuidData(InstanceData.MimironWorldTrigger)); + if (worldtrigger) + worldtrigger.CastCustomSpell(Spells.ScriptEffectSummonFlamesInitial, SpellValueMod.MaxTargets, 3, null, true, null, null, me.GetGUID()); + _events.RescheduleEvent(Events.SummonFlames, 28000); + } + break; + case Events.Intro1: + Talk(_fireFighter ? Yells.HardmodeOn : Yells.MkiiActivate); + _events.ScheduleEvent(Events.Intro2, 5000); + break; + case Events.Intro2: + { + Unit mkii = me.GetVehicleBase(); + if (mkii) + { + DoCast(mkii, Spells.Seat7); + mkii.RemoveAurasDueToSpell(Spells.FreezeAnim); + mkii.RemoveFlag(UnitFields.Flags, UnitFlags.NonAttackable | UnitFlags.NotSelectable); + } + _events.ScheduleEvent(Events.Intro3, 2000); + } + break; + case Events.Intro3: + { + Creature mkii = me.GetVehicleCreatureBase(); + if (mkii) + mkii.GetAI().DoAction(_fireFighter ? Actions.HardmodeMkii : Actions.StartMkii); + } + break; + case Events.Vx001Activation1: + { + Unit mkii = me.GetVehicleBase(); + if (mkii) + DoCast(mkii, Spells.Seat6); + _events.ScheduleEvent(Events.Vx001Activation2, 1000); + } + break; + case Events.Vx001Activation2: + { + Talk(Yells.MkiiDeath); + _events.ScheduleEvent(Events.Vx001Activation3, 5000); + } + break; + case Events.Vx001Activation3: + { + GameObject elevator = ObjectAccessor.GetGameObject(me, instance.GetGuidData(InstanceData.MimironElevator)); + if (elevator) + elevator.SetGoState(GameObjectState.Ready); + Unit worldtrigger = Global.ObjAccessor.GetUnit(me, instance.GetGuidData(InstanceData.MimironWorldTrigger)); + if (worldtrigger) + worldtrigger.CastSpell(worldtrigger, Spells.ElevatorKnockback); + _events.ScheduleEvent(Events.Vx001Activation4, 6000); + } + break; + case Events.Vx001Activation4: + { + GameObject elevator = ObjectAccessor.GetGameObject(me, instance.GetGuidData(InstanceData.MimironElevator)); + if (elevator) + elevator.SetGoState(GameObjectState.ActiveAlternative); + Creature vx001 = me.SummonCreature(InstanceCreatureIds.Vx001, MimironConst.VX001SummonPos, TempSummonType.CorpseTimedDespawn, 120000); + if (vx001) + vx001.CastSpell(vx001, Spells.FreezeAnim); + _events.ScheduleEvent(Events.Vx001Activation5, 19000); + } + break; + case Events.Vx001Activation5: + { + Unit vx001 = Global.ObjAccessor.GetUnit(me, instance.GetGuidData(InstanceData.VX001)); + if (vx001) + DoCast(vx001, Spells.Seat1); + _events.ScheduleEvent(Events.Vx001Activation6, 3500); + } + break; + case Events.Vx001Activation6: + Talk(Yells.Vx001Activate); + _events.ScheduleEvent(Events.Vx001Activation7, 4000); + break; + case Events.Vx001Activation7: + { + Unit vx001 = me.GetVehicleBase(); + if (vx001) + DoCast(vx001, Spells.Seat2); + _events.ScheduleEvent(Events.Vx001Activation8, 3000); + } + break; + case Events.Vx001Activation8: + { + Creature vx001 = me.GetVehicleCreatureBase(); + if (vx001) + vx001.GetAI().DoAction(_fireFighter ? Actions.HardmodeVx001 : Actions.StartVx001); + } + break; + case Events.AerialActivation1: + { + Unit mkii = me.GetVehicleBase(); + if (mkii) + DoCast(mkii, Spells.Seat5); + _events.ScheduleEvent(Events.AerialActivation2, 2500); + } + break; + case Events.AerialActivation2: + Talk(Yells.Vx001Death); + _events.ScheduleEvent(Events.AerialActivation3, 5000); + break; + case Events.AerialActivation3: + me.SummonCreature(InstanceCreatureIds.AerialCommandUnit, MimironConst.ACUSummonPos, TempSummonType.ManualDespawn); + _events.ScheduleEvent(Events.AerialActivation4, 5000); + break; + case Events.AerialActivation4: + { + Unit aerial = Global.ObjAccessor.GetUnit(me, instance.GetGuidData(InstanceData.AerialCommandUnit)); + if (aerial) + me.CastSpell(aerial, Spells.Seat1); + _events.ScheduleEvent(Events.AerialActivation5, 2000); + } + break; + case Events.AerialActivation5: + Talk(Yells.AerialActivate); + _events.ScheduleEvent(Events.AerialActivation6, 8000); + break; + case Events.AerialActivation6: + Creature acu = me.GetVehicleCreatureBase(); + if (acu) + acu.GetAI().DoAction(_fireFighter ? Actions.HardmodeAerial : Actions.StartAerial); + break; + case Events.Vol7ronActivation1: + { + Creature mkii = ObjectAccessor.GetCreature(me, instance.GetGuidData(InstanceData.LeviathanMKII)); + if (mkii) + mkii.SetFacingTo((float)Math.PI); + _events.ScheduleEvent(Events.Vol7ronActivation2, 1000); + } + break; + case Events.Vol7ronActivation2: + { + Creature mkii = ObjectAccessor.GetCreature(me, instance.GetGuidData(InstanceData.LeviathanMKII)); + if (mkii) + { + Creature vx001 = ObjectAccessor.GetCreature(me, instance.GetGuidData(InstanceData.VX001)); + if (vx001) + { + vx001.RemoveAurasDueToSpell(Spells.TorsoDisabled); + vx001.CastSpell(mkii, Spells.MountMkii); + } + } + _events.ScheduleEvent(Events.Vol7ronActivation3, 4500); + } + break; + case Events.Vol7ronActivation3: + { + Creature mkii = ObjectAccessor.GetCreature(me, instance.GetGuidData(InstanceData.LeviathanMKII)); + if (mkii) + mkii.GetMotionMaster().MovePoint(Waypoints.MkiiP4Pos4, MimironConst.VehicleRelocation[Waypoints.MkiiP4Pos4]); + _events.ScheduleEvent(Events.Vol7ronActivation4, 5000); + } + break; + case Events.Vol7ronActivation4: + { + Creature vx001 = ObjectAccessor.GetCreature(me, instance.GetGuidData(InstanceData.VX001)); + if (vx001) + { + Creature aerial = ObjectAccessor.GetCreature(me, instance.GetGuidData(InstanceData.AerialCommandUnit)); + if (aerial) + { + aerial.GetMotionMaster().MoveLand(0, new Position(aerial.GetPositionX(), aerial.GetPositionY(), aerial.GetPositionZMinusOffset())); + aerial.SetByteValue(UnitFields.Bytes1, UnitBytes1Offsets.AnimTier, 0); + aerial.CastSpell(vx001, Spells.MountVx001); + aerial.CastSpell(aerial, Spells.HalfHeal); + } + } + _events.ScheduleEvent(Events.Vol7ronActivation5, 4000); + } + break; + case Events.Vol7ronActivation5: + Talk(Yells.V07tronActivate); + _events.ScheduleEvent(Events.Vol7ronActivation6, 3000); + break; + case Events.Vol7ronActivation6: + { + Creature vx001 = ObjectAccessor.GetCreature(me, instance.GetGuidData(InstanceData.VX001)); + if (vx001) + DoCast(vx001, Spells.Seat2); + _events.ScheduleEvent(Events.Vol7ronActivation7, 5000); + } + break; + case Events.Vol7ronActivation7: + for (uint data = InstanceData.LeviathanMKII; data <= InstanceData.AerialCommandUnit; ++data) + { + Creature mimironVehicle = ObjectAccessor.GetCreature(me, instance.GetGuidData(data)); + if (mimironVehicle) + mimironVehicle.GetAI().DoAction(Actions.AssembledCombat); + } + break; + case Events.Outtro1: + me.RemoveAurasDueToSpell(Spells.SleepVisual1); + DoCast(me, Spells.SleepVisual2); + me.SetFaction(35); + _events.ScheduleEvent(Events.Outtro2, 3000); + break; + case Events.Outtro2: + Talk(Yells.V07tronDeath); + if (_fireFighter) + { + Creature computer = ObjectAccessor.GetCreature(me, instance.GetGuidData(InstanceData.Computer)); + if (computer) + computer.GetAI().DoAction(Actions.DeactivateComputer); + me.SummonGameObject(RaidMode(InstanceGameObjectIds.CacheOfInnovationFirefighter, InstanceGameObjectIds.CacheOfInnovationFirefighterHero), 2744.040f, 2569.352f, 364.3135f, 3.124123f, new Quaternion(0.0f, 0.0f, 0.9999619f, 0.008734641f), 604800); + } + else + me.SummonGameObject(RaidMode(InstanceGameObjectIds.CacheOfInnovation, InstanceGameObjectIds.CacheOfInnovationHero), 2744.040f, 2569.352f, 364.3135f, 3.124123f, new Quaternion(0.0f, 0.0f, 0.9999619f, 0.008734641f), 604800); + _events.ScheduleEvent(Events.Outtro3, 11000); + break; + case Events.Outtro3: + DoCast(me, Spells.TeleportVisual); + me.SetFlag(UnitFields.Flags, UnitFlags.NotSelectable); + me.DespawnOrUnsummon(1000); // sniffs say 6 sec after, but it doesnt matter. + break; + default: + break; + } + }); + } + + bool _fireFighter; + } + + public override CreatureAI GetAI(Creature creature) + { + return instance_ulduar.GetUlduarInstanceAI(creature); + } + } + + [Script] + class boss_leviathan_mk_ii : CreatureScript + { + public boss_leviathan_mk_ii() : base("boss_leviathan_mk_ii") { } + + class boss_leviathan_mk_iiAI : BossAI + { + public boss_leviathan_mk_iiAI(Creature creature) : base(creature, BossIds.Mimiron) + { + _fireFighter = false; + _setupMine = true; + _setupBomb = true; + _setupRocket = true; + } + + public override void InitializeAI() + { + SetupEncounter(); + } + + void SetupEncounter() + { + _Reset(); + me.SetFlag(UnitFields.Flags, UnitFlags.NonAttackable | UnitFlags.NotSelectable); + me.SetReactState(ReactStates.Passive); + _fireFighter = false; + _setupMine = true; + _setupBomb = true; + _setupRocket = true; + DoCast(me, Spells.FreezeAnim); + } + + public override void DamageTaken(Unit who, ref uint damage) + { + if (damage >= me.GetHealth()) + { + damage = (uint)(me.GetHealth() - 1); // Let creature fall to 1 hp, but do not let it die or damage itself with SetHealth(). + me.SetFlag(UnitFields.Flags, UnitFlags.NonAttackable); + DoCast(me, Spells.VehicleDamaged, true); + me.AttackStop(); + me.SetReactState(ReactStates.Passive); + me.RemoveAllAurasExceptType(AuraType.ControlVehicle, AuraType.ModIncreaseHealthPercent); + + if (_events.IsInPhase(Phases.LeviathanMkII)) + { + me.CastStop(); + Unit turret = me.GetVehicleKit().GetPassenger(3); + if (turret) + turret.KillSelf(); + + me.SetSpeedRate(UnitMoveType.Run, 1.5f); + me.GetMotionMaster().MovePoint(Waypoints.MkiiP1Idle, MimironConst.VehicleRelocation[Waypoints.MkiiP1Idle]); + } + else if (_events.IsInPhase(Phases.Vol7ron)) + { + me.SetStandState(UnitStandStateType.Dead); + + if (MimironConst.IsEncounterFinished(who)) + return; + + me.CastStop(); + DoCast(me, Spells.SelfRepair); + } + _events.Reset(); + } + } + + public override void DoAction(int action) + { + switch (action) + { + case Actions.HardmodeMkii: + _fireFighter = true; + DoCast(me, Spells.EmergencyMode); + DoCastAOE(Spells.EmergencyModeTurret); + _events.ScheduleEvent(Events.FlameSuppressantVx, 60000, 0, Phases.LeviathanMkII); + goto case Actions.StartMkii; + case Actions.StartMkii: + me.SetReactState(ReactStates.Aggressive); + _events.SetPhase(Phases.LeviathanMkII); + + _events.ScheduleEvent(Events.NapalmShell, 3000, 0, Phases.LeviathanMkII); + _events.ScheduleEvent(Events.PlasmaBlast, 15000, 0, Phases.LeviathanMkII); + _events.ScheduleEvent(Events.ProximityMine, 5000); + _events.ScheduleEvent(Events.ShockBlast, 18000); + break; + case Actions.AssembledCombat: + me.SetStandState(UnitStandStateType.Stand); + me.RemoveFlag(UnitFields.Flags, UnitFlags.NonAttackable | UnitFlags.NotSelectable); + me.SetReactState(ReactStates.Aggressive); + + _events.SetPhase(Phases.Vol7ron); + _events.ScheduleEvent(Events.ProximityMine, 15000); + _events.ScheduleEvent(Events.ShockBlast, 45000); + break; + default: + break; + } + } + + public override uint GetData(uint type) + { + switch (type) + { + case Data.SetupMine: + return _setupMine ? 1 : 0u; + case Data.SetupBomb: + return _setupBomb ? 1 : 0u; + case Data.SetupRocket: + return _setupRocket ? 1 : 0u; + case Data.Firefighter: + return _fireFighter ? 1 : 0u; + default: + return 0; + } + } + + public override void JustSummoned(Creature summon) + { + summons.Summon(summon); + } + + public override void KilledUnit(Unit victim) + { + if (victim.IsTypeId(TypeId.Player)) + { + Creature mimiron = ObjectAccessor.GetCreature(me, instance.GetGuidData(BossIds.Mimiron)); + if (mimiron) + mimiron.GetAI().Talk(_events.IsInPhase(Phases.LeviathanMkII) ? Yells.MkiiSlay : Yells.V07tronSlay); + } + } + + public override void MovementInform(MovementGeneratorType type, uint point) + { + if (type != MovementGeneratorType.Point) + return; + + switch (point) + { + case Waypoints.MkiiP1Idle: + { + me.SetFlag(UnitFields.Flags, UnitFlags.NotSelectable); + DoCast(me, Spells.HalfHeal); + + Creature mimiron = ObjectAccessor.GetCreature(me, instance.GetGuidData(BossIds.Mimiron)); + if (mimiron) + mimiron.GetAI().DoAction(Actions.ActivateVx001); + } + break; + case Waypoints.MkiiP4Pos1: + _events.ScheduleEvent(Events.MovePoint2, 1); + break; + case Waypoints.MkiiP4Pos2: + _events.ScheduleEvent(Events.MovePoint3, 1); + break; + case Waypoints.MkiiP4Pos3: + { + Creature mimiron = ObjectAccessor.GetCreature(me, instance.GetGuidData(BossIds.Mimiron)); + if (mimiron) + mimiron.GetAI().DoAction(Actions.ActivateV0l7r0n2); + } + break; + case Waypoints.MkiiP4Pos4: + _events.ScheduleEvent(Events.MovePoint5, 1); + break; + default: + break; + } + } + + public override void SetData(uint id, uint data) + { + switch (id) + { + case Data.SetupMine: + _setupMine = data != 0; + break; + case Data.SetupBomb: + _setupBomb = data != 0; + break; + case Data.SetupRocket: + _setupRocket = data != 0; + break; + default: + break; + } + } + + public override void JustRespawned() + { + SetupEncounter(); + } + + public override void EnterEvadeMode(EvadeReason why = EvadeReason.Other) + { + _DespawnAtEvade(); + } + + public override void UpdateAI(uint diff) + { + if (!UpdateVictim()) + return; + + _events.Update(diff); + + if (me.HasUnitState(UnitState.Casting)) + return; + + _events.ExecuteEvents(eventId => + { + switch (eventId) + { + case Events.ProximityMine: + DoCastAOE(Spells.ProximityMines); + _events.RescheduleEvent(Events.ProximityMine, 35000); + break; + case Events.PlasmaBlast: + DoCastVictim(Spells.ScriptEffectPlasmaBlast); + _events.RescheduleEvent(Events.PlasmaBlast, RandomHelper.URand(30000, 45000), 0, Phases.LeviathanMkII); + + if (_events.GetTimeUntilEvent(Events.NapalmShell) < 9000) + _events.RescheduleEvent(Events.NapalmShell, 9000, 0, Phases.LeviathanMkII); // The actual spell is cast by the turret, we should not let it interrupt itself. + break; + case Events.ShockBlast: + DoCastAOE(Spells.ShockBlast); + _events.RescheduleEvent(Events.ShockBlast, RandomHelper.URand(34000, 36000)); + break; + case Events.FlameSuppressantMk: + DoCastAOE(Spells.FlameSuppressantMk); + _events.RescheduleEvent(Events.FlameSuppressantMk, 60000, 0, Phases.LeviathanMkII); + break; + case Events.NapalmShell: + DoCastAOE(Spells.ForceCastNapalmShell); + _events.RescheduleEvent(Events.NapalmShell, RandomHelper.URand(6000, 15000), 0, Phases.LeviathanMkII); + + if (_events.GetTimeUntilEvent(Events.PlasmaBlast) < 2000) + _events.RescheduleEvent(Events.PlasmaBlast, 2000, 0, Phases.LeviathanMkII); // The actual spell is cast by the turret, we should not let it interrupt itself. + break; + case Events.MovePoint2: + me.GetMotionMaster().MovePoint(Waypoints.MkiiP4Pos2, MimironConst.VehicleRelocation[Waypoints.MkiiP4Pos2]); + break; + case Events.MovePoint3: + me.GetMotionMaster().MovePoint(Waypoints.MkiiP4Pos3, MimironConst.VehicleRelocation[Waypoints.MkiiP4Pos3]); + break; + case Events.MovePoint5: + me.GetMotionMaster().MovePoint(Waypoints.MkiiP4Pos5, MimironConst.VehicleRelocation[Waypoints.MkiiP4Pos5]); + break; + default: + break; + } + }); + DoMeleeAttackIfReady(); + } + + bool _fireFighter; + bool _setupMine; + bool _setupBomb; + bool _setupRocket; + } + + public override CreatureAI GetAI(Creature creature) + { + return instance_ulduar.GetUlduarInstanceAI(creature); + } + } + + [Script] //todo check for both rockets + class boss_vx_001 : CreatureScript + { + public boss_vx_001() : base("boss_vx_001") { } + + class boss_vx_001AI : BossAI + { + public boss_vx_001AI(Creature creature) : base(creature, BossIds.Mimiron) + { + me.SetDisableGravity(true); // This is the unfold visual state of VX-001, it has to be set on create as it requires an objectupdate if set later. + me.SetUInt32Value(UnitFields.NpcEmotestate, (uint)Emote.StateSpecialUnarmed); // This is a hack to force the yet to be unfolded visual state. + me.SetReactState(ReactStates.Passive); + _fireFighter = false; + } + + public override void DamageTaken(Unit who, ref uint damage) + { + if (damage >= me.GetHealth()) + { + //play sound 15615 + damage = (uint)(me.GetHealth() - 1); // Let creature fall to 1 hp, but do not let it die or damage itself with SetHealth(). + me.AttackStop(); + DoCast(me, Spells.VehicleDamaged, true); + me.RemoveAllAurasExceptType(AuraType.ControlVehicle, AuraType.ModIncreaseHealthPercent); + + if (_events.IsInPhase(Phases.Vx001)) + { + me.CastStop(); + me.SetFlag(UnitFields.Flags, UnitFlags.NonAttackable | UnitFlags.NotSelectable); + DoCast(me, Spells.HalfHeal); // has no effect, wat + DoCast(me, Spells.TorsoDisabled); + Creature mimiron = ObjectAccessor.GetCreature(me, instance.GetGuidData(BossIds.Mimiron)); + if (mimiron) + mimiron.GetAI().DoAction(Actions.ActivateAerial); + } + else if (_events.IsInPhase(Phases.Vol7ron)) + { + me.SetStandState(UnitStandStateType.Dead); + me.SetFlag(UnitFields.Flags, UnitFlags.NonAttackable); + + if (MimironConst.IsEncounterFinished(who)) + return; + + me.CastStop(); + DoCast(me, Spells.SelfRepair); + } + _events.Reset(); + } + } + + public override void DoAction(int action) + { + switch (action) + { + case Actions.HardmodeVx001: + _fireFighter = true; + DoCast(me, Spells.EmergencyMode); + _events.ScheduleEvent(Events.FrostBomb, 1000); + _events.ScheduleEvent(Events.FlameSuppressantVx, 6000); + goto case Actions.StartVx001; + case Actions.StartVx001: + me.RemoveFlag(UnitFields.Flags, UnitFlags.NonAttackable | UnitFlags.NotSelectable); + me.RemoveAurasDueToSpell(Spells.FreezeAnim); + me.SetUInt32Value(UnitFields.NpcEmotestate, (uint)Emote.OneshotNone); // Remove emotestate. + //me.SetuintValue(UnitFields.Bytes1, UnitBytes1Offsets.AnimTier, UnitBytes1Flags.AlwaysStand | UnitBytes1Flags.Hover); Blizzard handles hover animation like this it seems. + DoCast(me, Spells.HeatWaveAura); + + _events.SetPhase(Phases.Vx001); + _events.ScheduleEvent(Events.RocketStrike, 20000); + _events.ScheduleEvent(Events.SpinningUp, RandomHelper.URand(30000, 35000)); + _events.ScheduleEvent(Events.RapidBurst, 500, 0, Phases.Vx001); + break; + case Actions.AssembledCombat: + me.SetStandState(UnitStandStateType.Stand); + me.RemoveFlag(UnitFields.Flags, UnitFlags.NonAttackable | UnitFlags.NotSelectable); + + _events.SetPhase(Phases.Vol7ron); + _events.ScheduleEvent(Events.RocketStrike, 20000); + _events.ScheduleEvent(Events.SpinningUp, RandomHelper.URand(30000, 35000)); + _events.ScheduleEvent(Events.HandPulse, 500, 0, Phases.Vol7ron); + if (_fireFighter) + _events.ScheduleEvent(Events.FrostBomb, 1000); + break; + default: + break; + } + } + + public override void EnterEvadeMode(EvadeReason why) + { + summons.DespawnAll(); + } + + public override void JustSummoned(Creature summon) + { + summons.Summon(summon); + if (summon.GetEntry() == InstanceCreatureIds.BurstTarget) + summon.CastSpell(me, Spells.RapidBurstTargetMe); + } + + public override void KilledUnit(Unit victim) + { + if (victim.IsTypeId(TypeId.Player)) + { + Creature mimiron = ObjectAccessor.GetCreature(me, instance.GetGuidData(BossIds.Mimiron)); + if (mimiron) + mimiron.GetAI().Talk(_events.IsInPhase(Phases.Vx001) ? Yells.Vx001Slay : Yells.V07tronSlay); + } + } + + public override void SpellHit(Unit caster, SpellInfo spellProto) + { + if (caster.GetEntry() == InstanceCreatureIds.BurstTarget && !me.HasUnitState(UnitState.Casting)) + DoCast(caster, Spells.RapidBurst); + } + + public override void UpdateAI(uint diff) + { + if (!UpdateVictim()) + return; + + _events.Update(diff); + + // Handle rotation during SPELL_SPINNING_UP, SPELL_P3WX2_LASER_BARRAGE, SPELL_RAPID_BURST, and SPELL_HAND_PULSE_LEFT/RIGHT + if (me.HasUnitState(UnitState.Casting)) + { + List channelObjects = me.GetChannelObjects(); + Unit channelTarget = (channelObjects.Count == 1 ? Global.ObjAccessor.GetUnit(me, channelObjects[0]) : null); + if (channelTarget) + me.SetFacingToObject(channelTarget); + return; + } + + _events.ExecuteEvents(eventId => + { + switch (eventId) + { + case Events.RapidBurst: + { + Unit target = SelectTarget(SelectAggroTarget.Random, 0, 120, true); + if (target) + DoCast(target, Spells.SummonBurstTarget); + _events.RescheduleEvent(Events.RapidBurst, 3000, 0, Phases.Vx001); + } + break; + case Events.RocketStrike: + DoCastAOE(_events.IsInPhase(Phases.Vx001) ? Spells.RocketStrikeSingle : Spells.RocketStrikeBoth); + _events.ScheduleEvent(Events.Reload, 10000); + _events.RescheduleEvent(Events.RocketStrike, RandomHelper.URand(20000, 25000)); + break; + case Events.Reload: + for (sbyte seat = (sbyte)SeatIds.RocketLeft; seat <= SeatIds.RocketRight; ++seat) + { + Unit rocket = me.GetVehicleKit().GetPassenger(seat); + if (rocket) + rocket.SetDisplayId(rocket.GetNativeDisplayId()); + } + break; + case Events.HandPulse: + { + Unit target = SelectTarget(SelectAggroTarget.Random, 0, 120, true); + if (target) + DoCast(target, RandomHelper.RAND(Spells.HandPulseLeft, Spells.HandPulseRight)); + _events.RescheduleEvent(Events.HandPulse, RandomHelper.URand(1500, 3000), 0, Phases.Vol7ron); + } + break; + case Events.FrostBomb: + DoCastAOE(Spells.ScriptEffectFrostBomb); + _events.RescheduleEvent(Events.FrostBomb, 45000); + break; + case Events.SpinningUp: + DoCastAOE(Spells.SpinningUp); + _events.DelayEvents(14000); + _events.RescheduleEvent(Events.SpinningUp, RandomHelper.URand(55000, 65000)); + break; + case Events.FlameSuppressantVx: + DoCastAOE(Spells.FlameSuppressantVx); + _events.RescheduleEvent(Events.FlameSuppressantVx, RandomHelper.URand(10000, 12000), 0, Phases.Vx001); + break; + default: + break; + } + }); + } + + bool _fireFighter; + } + + public override CreatureAI GetAI(Creature creature) + { + return instance_ulduar.GetUlduarInstanceAI(creature); + } + } + + [Script] + class boss_aerial_command_unit : CreatureScript + { + public boss_aerial_command_unit() : base("boss_aerial_command_unit") { } + + class boss_aerial_command_unitAI : BossAI + { + public boss_aerial_command_unitAI(Creature creature) : base(creature, BossIds.Mimiron) + { + me.SetReactState(ReactStates.Passive); + fireFigther = false; + } + + public override void DamageTaken(Unit who, ref uint damage) + { + if (damage >= me.GetHealth()) + { + damage = (uint)(me.GetHealth() - 1); // Let creature fall to 1 hp, but do not let it die or damage itself with SetHealth(). + me.SetFlag(UnitFields.Flags, UnitFlags.NonAttackable); + me.AttackStop(); + me.SetReactState(ReactStates.Passive); + DoCast(me, Spells.VehicleDamaged, true); + me.RemoveAllAurasExceptType(AuraType.ControlVehicle, AuraType.ModIncreaseHealthPercent); + + if (_events.IsInPhase(Phases.AerialCommandUnit)) + { + me.GetMotionMaster().Clear(true); + me.GetMotionMaster().MovePoint(Waypoints.AerialP4Pos, MimironConst.VehicleRelocation[Waypoints.AerialP4Pos]); + } + else if (_events.IsInPhase(Phases.Vol7ron)) + { + me.SetStandState(UnitStandStateType.Dead); + + if (MimironConst.IsEncounterFinished(who)) + return; + + me.CastStop(); + DoCast(me, Spells.SelfRepair); + } + _events.Reset(); + } + } + + public override void DoAction(int action) + { + switch (action) + { + case Actions.HardmodeAerial: + fireFigther = true; + DoCast(me, Spells.EmergencyMode); + _events.ScheduleEvent(Events.SummonFireBots, 1000, 0, Phases.AerialCommandUnit); + goto case Actions.StartAerial; + case Actions.StartAerial: + me.RemoveFlag(UnitFields.Flags, UnitFlags.NonAttackable | UnitFlags.NotSelectable); + me.SetReactState(ReactStates.Aggressive); + + _events.SetPhase(Phases.AerialCommandUnit); + _events.ScheduleEvent(Events.SummonJunkBot, 5000, 0, Phases.AerialCommandUnit); + _events.ScheduleEvent(Events.SummonAssaultBot, 9000, 0, Phases.AerialCommandUnit); + _events.ScheduleEvent(Events.SummonBombBot, 9000, 0, Phases.AerialCommandUnit); + break; + case Actions.DisableAerial: + me.CastStop(); + me.AttackStop(); + me.SetReactState(ReactStates.Passive); + me.GetMotionMaster().MoveFall(); + _events.DelayEvents(23000); + break; + case Actions.EnableAerial: + me.SetReactState(ReactStates.Aggressive); + break; + case Actions.AssembledCombat: + me.RemoveFlag(UnitFields.Flags, UnitFlags.NonAttackable | UnitFlags.NotSelectable); + me.SetReactState(ReactStates.Aggressive); + me.SetStandState(UnitStandStateType.Stand); + _events.SetPhase(Phases.Vol7ron); + break; + default: + break; + } + } + + public override void EnterEvadeMode(EvadeReason why) + { + summons.DespawnAll(); + } + + public override void JustSummoned(Creature summon) + { + if (fireFigther && (summon.GetEntry() == InstanceCreatureIds.AssaultBot || summon.GetEntry() == InstanceCreatureIds.JunkBot)) + summon.CastSpell(summon, Spells.EmergencyMode); + base.JustSummoned(summon); + } + + public override void KilledUnit(Unit victim) + { + if (victim.IsTypeId(TypeId.Player)) + { + Creature mimiron = ObjectAccessor.GetCreature(me, instance.GetGuidData(BossIds.Mimiron)); + if (mimiron) + mimiron.GetAI().Talk(_events.IsInPhase(Phases.AerialCommandUnit) ? Yells.AerialSlay : Yells.V07tronSlay); + } + } + + public override void MovementInform(MovementGeneratorType type, uint point) + { + if (type == MovementGeneratorType.Point && point == Waypoints.AerialP4Pos) + { + me.SetFlag(UnitFields.Flags, UnitFlags.NotSelectable); + + Creature mimiron = ObjectAccessor.GetCreature(me, instance.GetGuidData(BossIds.Mimiron)); + if (mimiron) + mimiron.GetAI().DoAction(Actions.ActivateV0l7r0n1); + } + } + + public override void UpdateAI(uint diff) + { + if (!UpdateVictim()) + return; + + _events.Update(diff); + + if (me.HasUnitState(UnitState.Casting)) + return; + + _events.ExecuteEvents(eventId => + { + switch (eventId) + { + case Events.SummonFireBots: + me.CastCustomSpell(Spells.SummonFireBotTrigger, SpellValueMod.MaxTargets, 3, null, true); + _events.RescheduleEvent(Events.SummonFireBots, 45000, 0, Phases.AerialCommandUnit); + break; + case Events.SummonJunkBot: + me.CastCustomSpell(Spells.SummonJunkBotTrigger, SpellValueMod.MaxTargets, 1, null, true); + _events.RescheduleEvent(Events.SummonJunkBot, RandomHelper.URand(11000, 12000), 0, Phases.AerialCommandUnit); + break; + case Events.SummonAssaultBot: + me.CastCustomSpell(Spells.SummonAssaultBotTrigger, SpellValueMod.MaxTargets, 1, null, true); + _events.RescheduleEvent(Events.SummonAssaultBot, 30000, 0, Phases.AerialCommandUnit); + break; + case Events.SummonBombBot: + DoCast(me, Spells.SummonBombBot); + _events.RescheduleEvent(Events.SummonBombBot, RandomHelper.URand(15000, 20000), 0, Phases.AerialCommandUnit); + break; + default: + break; + } + }); + DoSpellAttackIfReady(_events.IsInPhase(Phases.AerialCommandUnit) ? Spells.PlasmaBallP1 : Spells.PlasmaBallP2); + } + + bool fireFigther; + } + + public override CreatureAI GetAI(Creature creature) + { + return instance_ulduar.GetUlduarInstanceAI(creature); + } + } + + [Script] + class npc_mimiron_assault_bot : CreatureScript + { + public npc_mimiron_assault_bot() : base("npc_mimiron_assault_bot") { } + + class npc_mimiron_assault_botAI : ScriptedAI + { + public npc_mimiron_assault_botAI(Creature creature) : base(creature) { } + + public override void EnterCombat(Unit who) + { + _scheduler.Schedule(TimeSpan.FromSeconds(14), task => + { + DoCastVictim(Spells.MagneticField); + me.ClearUnitState(UnitState.Casting); + task.Repeat(TimeSpan.FromSeconds(30)); + }); + } + + public override void UpdateAI(uint diff) + { + if (!UpdateVictim()) + return; + + if (me.HasUnitState(UnitState.Root)) + { + Unit newTarget = SelectTarget(SelectAggroTarget.Nearest, 0, 30.0f, true); + if (newTarget) + { + me.DeleteThreatList(); + AttackStart(newTarget); + } + } + + _scheduler.Update(diff, DoMeleeAttackIfReady); + } + } + + public override CreatureAI GetAI(Creature creature) + { + return instance_ulduar.GetUlduarInstanceAI(creature); + } + } + + [Script] + class npc_mimiron_emergency_fire_bot : CreatureScript + { + public npc_mimiron_emergency_fire_bot() : base("npc_mimiron_emergency_fire_bot") { } + + class npc_mimiron_emergency_fire_botAI : ScriptedAI + { + public npc_mimiron_emergency_fire_botAI(Creature creature) : base(creature) + { + me.SetReactState(ReactStates.Passive); + isWaterSprayReady = true; + moveNew = true; + } + + public override uint GetData(uint id) + { + if (id == Data.Waterspray) + return isWaterSprayReady ? 1 : 0u; + if (id == Data.MoveNew) + return moveNew ? 1 : 0u; + return 0; + } + + public override void SetData(uint id, uint data) + { + if (id == Data.Waterspray) + isWaterSprayReady = false; + else if (id == Data.MoveNew) + moveNew = data == 1; + } + + public override void Reset() + { + _scheduler.Schedule(TimeSpan.FromSeconds(7), task => + { + isWaterSprayReady = true; + task.Repeat(TimeSpan.FromSeconds(7), TimeSpan.FromSeconds(9)); + }); + + isWaterSprayReady = true; + moveNew = true; + } + + public override void UpdateAI(uint diff) + { + if (!isWaterSprayReady) + _scheduler.Update(diff); + } + + bool isWaterSprayReady; + bool moveNew; + } + + public override CreatureAI GetAI(Creature creature) + { + return instance_ulduar.GetUlduarInstanceAI(creature); + } + } + + [Script] + class npc_mimiron_computer : CreatureScript + { + public npc_mimiron_computer() : base("npc_mimiron_computer") { } + + class npc_mimiron_computerAI : ScriptedAI + { + public npc_mimiron_computerAI(Creature creature) : base(creature) + { + instance = me.GetInstanceScript(); + } + + public override void DoAction(int action) + { + switch (action) + { + case Actions.ActivateComputer: + Talk(ComputerYells.SelfDestructInitiated); + _events.ScheduleEvent(Events.SelfDestruct10, 3000); + break; + case Actions.DeactivateComputer: + Talk(ComputerYells.SelfDestructTerminated); + me.RemoveAurasDueToSpell(Spells.SelfDestructionAura); + me.RemoveAurasDueToSpell(Spells.SelfDestructionVisual); + _events.Reset(); + break; + default: + break; + } + } + + public override void UpdateAI(uint diff) + { + _events.Update(diff); + _events.ExecuteEvents(eventId => + { + switch (eventId) + { + case Events.SelfDestruct10: + { + Talk(ComputerYells.SelfDestruct10); + Creature mimiron = ObjectAccessor.GetCreature(me, instance.GetGuidData(BossIds.Mimiron)); + if (mimiron) + mimiron.GetAI().DoAction(Actions.ActivateHardMode); + _events.ScheduleEvent(Events.SelfDestruct9, 60000); + } + break; + case Events.SelfDestruct9: + Talk(ComputerYells.SelfDestruct9); + _events.ScheduleEvent(Events.SelfDestruct8, 60000); + break; + case Events.SelfDestruct8: + Talk(ComputerYells.SelfDestruct8); + _events.ScheduleEvent(Events.SelfDestruct7, 60000); + break; + case Events.SelfDestruct7: + Talk(ComputerYells.SelfDestruct7); + _events.ScheduleEvent(Events.SelfDestruct6, 60000); + break; + case Events.SelfDestruct6: + Talk(ComputerYells.SelfDestruct6); + _events.ScheduleEvent(Events.SelfDestruct5, 60000); + break; + case Events.SelfDestruct5: + Talk(ComputerYells.SelfDestruct5); + _events.ScheduleEvent(Events.SelfDestruct4, 60000); + break; + case Events.SelfDestruct4: + Talk(ComputerYells.SelfDestruct4); + _events.ScheduleEvent(Events.SelfDestruct3, 60000); + break; + case Events.SelfDestruct3: + Talk(ComputerYells.SelfDestruct3); + _events.ScheduleEvent(Events.SelfDestruct2, 60000); + break; + case Events.SelfDestruct2: + Talk(ComputerYells.SelfDestruct2); + _events.ScheduleEvent(Events.SelfDestruct1, 60000); + break; + case Events.SelfDestruct1: + Talk(ComputerYells.SelfDestruct1); + _events.ScheduleEvent(Events.SelfDestructFinalized, 60000); + break; + case Events.SelfDestructFinalized: + { + Talk(ComputerYells.SelfDestructFinalized); + Creature mimiron = ObjectAccessor.GetCreature(me, instance.GetGuidData(BossIds.Mimiron)); + if (mimiron) + mimiron.GetAI().DoAction(Actions.ActivateSelfDestruct); + DoCast(me, Spells.SelfDestructionAura); + DoCast(me, Spells.SelfDestructionVisual); + } + break; + default: + break; + } + }); + } + + InstanceScript instance; + } + + public override CreatureAI GetAI(Creature creature) + { + return instance_ulduar.GetUlduarInstanceAI(creature); + } + } + + [Script] + class npc_mimiron_flames : CreatureScript + { + public npc_mimiron_flames() : base("npc_mimiron_flames") { } + + class npc_mimiron_flamesAI : ScriptedAI + { + public npc_mimiron_flamesAI(Creature creature) : base(creature) + { + instance = me.GetInstanceScript(); + } + + public override void Reset() // Reset is possibly more suitable for this case. + { + _scheduler.Schedule(TimeSpan.FromSeconds(4), task => + { + DoCastAOE(Spells.SummonFlamesSpreadTrigger); + }); + } + + public override void UpdateAI(uint diff) + { + if (instance.GetBossState(BossIds.Mimiron) != EncounterState.InProgress) + me.DespawnOrUnsummon(); + + _scheduler.Update(diff); + } + + InstanceScript instance; + } + + public override CreatureAI GetAI(Creature creature) + { + return instance_ulduar.GetUlduarInstanceAI(creature); + } + } + + [Script] + class npc_mimiron_frost_bomb : CreatureScript + { + public npc_mimiron_frost_bomb() : base("npc_mimiron_frost_bomb") { } + + class npc_mimiron_frost_bombAI : ScriptedAI + { + public npc_mimiron_frost_bombAI(Creature creature) : base(creature) { } + + public override void Reset() + { + _scheduler.Schedule(TimeSpan.FromSeconds(10), task => + { + DoCastAOE(Spells.FrostBombExplosion); + + task.Schedule(TimeSpan.FromSeconds(3), () => + { + DoCastAOE(Spells.ClearFires); + me.DespawnOrUnsummon(3000); + }); + }); + } + + public override void UpdateAI(uint diff) + { + _scheduler.Update(diff); + } + } + + public override CreatureAI GetAI(Creature creature) + { + return instance_ulduar.GetUlduarInstanceAI(creature); + } + } + + [Script] + class npc_mimiron_proximity_mine : CreatureScript + { + public npc_mimiron_proximity_mine() : base("npc_mimiron_proximity_mine") { } + + class npc_mimiron_proximity_mineAI : ScriptedAI + { + public npc_mimiron_proximity_mineAI(Creature creature) : base(creature) { } + + public override void Reset() + { + _scheduler.Schedule(TimeSpan.FromSeconds(1.5), task => + { + DoCast(me, Spells.ProximityMinePeriodicTrigger); + + task.Schedule(TimeSpan.FromSeconds(33.5), () => + { + if (me.HasAura(Spells.ProximityMinePeriodicTrigger)) + DoCastAOE(Spells.ProximityMineExplosion); + me.DespawnOrUnsummon(1000); + }); + }); + + } + + public override void UpdateAI(uint diff) + { + _scheduler.Update(diff); + } + } + + public override CreatureAI GetAI(Creature creature) + { + return instance_ulduar.GetUlduarInstanceAI(creature); + } + } + + [Script] + class go_mimiron_hardmode_button : GameObjectScript + { + public go_mimiron_hardmode_button() : base("go_mimiron_hardmode_button") { } + + public override bool OnGossipHello(Player player, GameObject go) + { + if (go.HasFlag(GameObjectFields.Flags, GameObjectFlags.NotSelectable)) + return true; + + InstanceScript instance = go.GetInstanceScript(); + if (instance == null) + return false; + + Creature computer = ObjectAccessor.GetCreature(go, instance.GetGuidData(InstanceData.Computer)); + if (computer) + computer.GetAI().DoAction(Actions.ActivateComputer); + go.SetGoState(GameObjectState.Active); + go.SetFlag(GameObjectFields.Flags, GameObjectFlags.NotSelectable); + return true; + } + } + + [Script] // 63801 - Bomb Bot + class spell_mimiron_bomb_bot : SpellScriptLoader + { + public spell_mimiron_bomb_bot() : base("spell_mimiron_bomb_bot") { } + + class spell_mimiron_bomb_bot_SpellScript : SpellScript + { + void HandleScript(uint effIndex) + { + if (GetHitPlayer()) + { + InstanceScript instance = GetCaster().GetInstanceScript(); + if (instance != null) + { + Creature mkii = ObjectAccessor.GetCreature(GetCaster(), instance.GetGuidData(InstanceData.LeviathanMKII)); + if (mkii) + mkii.GetAI().SetData(Data.SetupBomb, 0); + } + } + } + + void HandleDespawn(uint effIndex) + { + Creature target = GetHitCreature(); + if (target) + { + target.SetFlag(UnitFields.Flags, UnitFlags.NotSelectable | UnitFlags.Pacified); + target.DespawnOrUnsummon(1000); + } + } + + public override void Register() + { + OnEffectHitTarget.Add(new EffectHandler(HandleScript, 0, SpellEffectName.SchoolDamage)); + OnEffectHitTarget.Add(new EffectHandler(HandleDespawn, 1, SpellEffectName.ApplyAura)); + } + } + + public override SpellScript GetSpellScript() + { + return new spell_mimiron_bomb_bot_SpellScript(); + } + } + + [Script] // 65192 - Flame Suppressant, 65224 - Clear Fires, 65354 - Clear Fires, 64619 - Water Spray + class spell_mimiron_clear_fires : SpellScriptLoader + { + public spell_mimiron_clear_fires() : base("spell_mimiron_clear_fires") { } + + class spell_mimiron_clear_fires_SpellScript : SpellScript + { + void HandleDummy(uint effIndex) + { + if (GetHitCreature()) + GetHitCreature().DespawnOrUnsummon(); + } + + public override void Register() + { + OnEffectHitTarget.Add(new EffectHandler(HandleDummy, 0, SpellEffectName.Dummy)); + } + } + + public override SpellScript GetSpellScript() + { + return new spell_mimiron_clear_fires_SpellScript(); + } + } + + [Script] // 64463 - Despawn Assault Bots + class spell_mimiron_despawn_assault_bots : SpellScriptLoader + { + public spell_mimiron_despawn_assault_bots() : base("spell_mimiron_despawn_assault_bots") { } + + class spell_mimiron_despawn_assault_bots_SpellScript : SpellScript + { + void HandleScript(uint effIndex) + { + if (GetHitCreature()) + GetHitCreature().DespawnOrUnsummon(); + } + + public override void Register() + { + OnEffectHitTarget.Add(new EffectHandler(HandleScript, 0, SpellEffectName.ScriptEffect)); + } + } + + public override SpellScript GetSpellScript() + { + return new spell_mimiron_despawn_assault_bots_SpellScript(); + } + } + + [Script] // 64618 - Fire Search + class spell_mimiron_fire_search : SpellScriptLoader + { + public spell_mimiron_fire_search() : base("spell_mimiron_fire_search") { } + + class spell_mimiron_fire_search_SpellScript : SpellScript + { + public spell_mimiron_fire_search_SpellScript() + { + _noTarget = false; + } + + public override bool Validate(SpellInfo spell) + { + return ValidateSpellInfo(Spells.WaterSpray); + } + + void FilterTargets(List targets) + { + _noTarget = targets.Empty(); + if (_noTarget) + return; + + WorldObject target = targets.PickRandom(); + targets.Clear(); + targets.Add(target); + } + + void HandleAftercast() + { + if (_noTarget) + GetCaster().GetMotionMaster().MoveRandom(15.0f); + } + + void HandleScript(uint effIndex) + { + Unit caster = GetCaster(); + UnitAI ai = caster.GetAI(); + if (ai != null) + { + if (caster.GetDistance2d(GetHitUnit()) <= 15.0f && ai.GetData(Data.Waterspray) != 0) + { + caster.CastSpell(GetHitUnit(), Spells.WaterSpray, true); + ai.SetData(Data.Waterspray, 0); + ai.SetData(Data.MoveNew, 1); + } + else if (caster.GetAI().GetData(Data.MoveNew) != 0) + { + caster.GetMotionMaster().MoveChase(GetHitUnit()); + ai.SetData(Data.MoveNew, 0); + } + } + } + + public override void Register() + { + AfterCast.Add(new CastHandler(HandleAftercast)); + OnObjectAreaTargetSelect.Add(new ObjectAreaTargetSelectHandler(FilterTargets, 0, Targets.UnitSrcAreaEntry)); + OnEffectHitTarget.Add(new EffectHandler(HandleScript, 0, SpellEffectName.ScriptEffect)); + } + + bool _noTarget; + } + + public override SpellScript GetSpellScript() + { + return new spell_mimiron_fire_search_SpellScript(); + } + } + + [Script] // 64436 - Magnetic Core + class spell_mimiron_magnetic_core : SpellScriptLoader + { + public spell_mimiron_magnetic_core() : base("spell_mimiron_magnetic_core") { } + + class spell_mimiron_magnetic_core_SpellScript : SpellScript + { + void FilterTargets(List targets) + { + targets.RemoveAll(obj => { return obj.ToUnit() && (obj.ToUnit().GetVehicleBase() || obj.HasFlag(UnitFields.Flags, UnitFlags.NonAttackable)); }); + } + + public override void Register() + { + OnObjectAreaTargetSelect.Add(new ObjectAreaTargetSelectHandler(FilterTargets, 1, Targets.UnitSrcAreaEntry)); + } + } + + public override SpellScript GetSpellScript() + { + return new spell_mimiron_magnetic_core_SpellScript(); + } + + class spell_mimiron_magnetic_core_AuraScript : AuraScript + { + public override bool Validate(SpellInfo spell) + { + return ValidateSpellInfo(Spells.MagneticCoreVisual); + } + + void OnApply(AuraEffect aurEff, AuraEffectHandleModes mode) + { + Creature target = GetTarget().ToCreature(); + if (target) + { + target.GetAI().DoAction(Actions.DisableAerial); + target.CastSpell(target, Spells.MagneticCoreVisual, true); + } + } + + void OnRemove(AuraEffect aurEff, AuraEffectHandleModes mode) + { + Creature target = GetTarget().ToCreature(); + if (target) + { + target.GetAI().DoAction(Actions.EnableAerial); + target.RemoveAurasDueToSpell(Spells.MagneticCoreVisual); + } + } + + void OnRemoveSelf(AuraEffect aurEff, AuraEffectHandleModes mode) + { + TempSummon summ = GetTarget().ToTempSummon(); + if (summ) + summ.DespawnOrUnsummon(); + } + + public override void Register() + { + AfterEffectApply.Add(new EffectApplyHandler(OnApply, 1, AuraType.ModDamagePercentTaken, AuraEffectHandleModes.Real)); + AfterEffectRemove.Add(new EffectApplyHandler(OnRemove, 1, AuraType.ModDamagePercentTaken, AuraEffectHandleModes.Real)); + AfterEffectRemove.Add(new EffectApplyHandler(OnRemoveSelf, 0, AuraType.Dummy, AuraEffectHandleModes.Real)); + } + } + + public override AuraScript GetAuraScript() + { + return new spell_mimiron_magnetic_core_AuraScript(); + } + } + + [Script] // 63667 - Napalm Shell + class spell_mimiron_napalm_shell : SpellScriptLoader + { + public spell_mimiron_napalm_shell() : base("spell_mimiron_napalm_shell") { } + + class spell_mimiron_napalm_shell_SpellScript : SpellScript + { + public override bool Validate(SpellInfo spell) + { + return ValidateSpellInfo(Spells.NapalmShell); + } + + void FilterTargets(List targets) + { + if (targets.Empty()) + return; + + WorldObject target = targets.PickRandom(); + + targets.RemoveAll(new AllWorldObjectsInRange(GetCaster(), 15.0f).Invoke); + + if (!targets.Empty()) + target = targets.PickRandom(); + + targets.Clear(); + targets.Add(target); + } + + void HandleScript(uint effIndex) + { + GetCaster().CastSpell(GetHitUnit(), Spells.NapalmShell); + } + + public override void Register() + { + OnObjectAreaTargetSelect.Add(new ObjectAreaTargetSelectHandler(FilterTargets, 0, Targets.UnitSrcAreaEnemy)); + OnEffectHitTarget.Add(new EffectHandler(HandleScript, 0, SpellEffectName.ScriptEffect)); + } + } + + public override SpellScript GetSpellScript() + { + return new spell_mimiron_napalm_shell_SpellScript(); + } + } + + [Script] // 64542 - Plasma Blast + class spell_mimiron_plasma_blast : SpellScriptLoader + { + public spell_mimiron_plasma_blast() : base("spell_mimiron_plasma_blast") { } + + class spell_mimiron_plasma_blast_SpellScript : SpellScript + { + public override bool Validate(SpellInfo spell) + { + return ValidateSpellInfo(Spells.PlasmaBlast); + } + + public override bool Load() + { + return GetCaster().GetVehicleKit() != null; + } + + void HandleScript(uint effIndex) + { + Unit caster = GetCaster().GetVehicleKit().GetPassenger(3); + if (caster) + caster.CastSpell(GetHitUnit(), Spells.PlasmaBlast); + } + + public override void Register() + { + OnEffectHitTarget.Add(new EffectHandler(HandleScript, 0, SpellEffectName.ScriptEffect)); + } + } + + public override SpellScript GetSpellScript() + { + return new spell_mimiron_plasma_blast_SpellScript(); + } + } + + [Script] // 66351 - Explosion + class spell_mimiron_proximity_explosion : SpellScriptLoader + { + public spell_mimiron_proximity_explosion() : base("spell_mimiron_proximity_explosion") { } + + class spell_mimiron_proximity_explosion_SpellScript : SpellScript + { + public void onHit(uint effIndex) + { + if (GetHitPlayer()) + { + InstanceScript instance = GetCaster().GetInstanceScript(); + if (instance != null) + { + Creature mkII = ObjectAccessor.GetCreature(GetCaster(), instance.GetGuidData(InstanceData.LeviathanMKII)); + if (mkII) + mkII.GetAI().SetData(Data.SetupMine, 0); + } + } + } + + void HandleAura(uint effIndex) + { + GetCaster().RemoveAurasDueToSpell(Spells.ProximityMinePeriodicTrigger); + } + + public override void Register() + { + OnEffectHitTarget.Add(new EffectHandler(onHit, 0, SpellEffectName.SchoolDamage)); + OnEffectHitTarget.Add(new EffectHandler(HandleAura, 1, SpellEffectName.ApplyAura)); + } + } + + public override SpellScript GetSpellScript() + { + return new spell_mimiron_proximity_explosion_SpellScript(); + } + } + + [Script] // 63027 - Proximity Mines + class spell_mimiron_proximity_mines : SpellScriptLoader + { + public spell_mimiron_proximity_mines() : base("spell_mimiron_proximity_mines") { } + + class spell_mimiron_proximity_mines_SpellScript : SpellScript + { + public override bool Validate(SpellInfo spell) + { + return ValidateSpellInfo(Spells.SummonProximityMine); + } + + void HandleScript(uint effIndex) + { + for (byte i = 0; i < 10; ++i) + GetCaster().CastSpell(GetCaster(), Spells.SummonProximityMine, true); + } + + public override void Register() + { + OnEffectHitTarget.Add(new EffectHandler(HandleScript, 0, SpellEffectName.ScriptEffect)); + } + } + + public override SpellScript GetSpellScript() + { + return new spell_mimiron_proximity_mines_SpellScript(); + } + } + + [Script] // 65346 - Proximity Mine + class spell_mimiron_proximity_trigger : SpellScriptLoader + { + public spell_mimiron_proximity_trigger() : base("spell_mimiron_proximity_trigger") { } + + class spell_mimiron_proximity_trigger_SpellScript : SpellScript + { + public override bool Validate(SpellInfo spell) + { + return ValidateSpellInfo(Spells.ProximityMineExplosion); + } + + void FilterTargets(List targets) + { + targets.Remove(GetExplTargetWorldObject()); + + if (targets.Empty()) + FinishCast(SpellCastResult.NoValidTargets); + } + + void HandleDummy(uint effIndex) + { + GetCaster().CastSpell((Unit)null, Spells.ProximityMineExplosion, true); + } + + public override void Register() + { + OnObjectAreaTargetSelect.Add(new ObjectAreaTargetSelectHandler(FilterTargets, 0, Targets.UnitSrcAreaEntry)); + OnEffectHit.Add(new EffectHandler(HandleDummy, 0, SpellEffectName.Dummy)); + } + } + + public override SpellScript GetSpellScript() + { + return new spell_mimiron_proximity_trigger_SpellScript(); + } + } + + [Script] // 63382 - Rapid Burst + class spell_mimiron_rapid_burst : SpellScriptLoader + { + public spell_mimiron_rapid_burst() : base("spell_mimiron_rapid_burst") { } + + class spell_mimiron_rapid_burst_AuraScript : AuraScript + { + public override bool Validate(SpellInfo spell) + { + return ValidateSpellInfo(Spells.RapidBurstLeft, Spells.RapidBurstRight); + } + + void AfterRemove(AuraEffect aurEff, AuraEffectHandleModes mode) + { + TempSummon summ = GetTarget().ToTempSummon(); + if (summ) + summ.DespawnOrUnsummon(); + } + + void HandleDummyTick(AuraEffect aurEff) + { + if (GetCaster()) + GetCaster().CastSpell(GetTarget(), aurEff.GetTickNumber() % 2 == 0 ? Spells.RapidBurstRight : Spells.RapidBurstLeft, true, null, aurEff); + } + + public override void Register() + { + AfterEffectRemove.Add(new EffectApplyHandler(AfterRemove, 0, AuraType.PeriodicDummy, AuraEffectHandleModes.Real)); + OnEffectPeriodic.Add(new EffectPeriodicHandler(HandleDummyTick, 1, AuraType.PeriodicDummy)); + } + } + + public override AuraScript GetAuraScript() + { + return new spell_mimiron_rapid_burst_AuraScript(); + } + } + + [Script] // 64402 - Rocket Strike, 65034 - Rocket Strike + class spell_mimiron_rocket_strike : SpellScriptLoader + { + public spell_mimiron_rocket_strike() : base("spell_mimiron_rocket_strike") { } + + class spell_mimiron_rocket_strike_SpellScript : SpellScript + { + public override bool Validate(SpellInfo spell) + { + return ValidateSpellInfo(Spells.ScriptEffectRocketStrike); + } + + void FilterTargets(List targets) + { + if (targets.Empty()) + return; + + if (m_scriptSpellId == Spells.RocketStrikeSingle && GetCaster().IsVehicle()) + { + WorldObject target = GetCaster().GetVehicleKit().GetPassenger(RandomHelper.RAND(SeatIds.RocketLeft, SeatIds.RocketRight)); + if (target) + { + targets.Clear(); + targets.Add(target); + } + } + } + + void HandleDummy(uint effIndex) + { + GetHitUnit().CastSpell((Unit)null, Spells.ScriptEffectRocketStrike, true, null, null, GetCaster().GetGUID()); + } + + public override void Register() + { + OnObjectAreaTargetSelect.Add(new ObjectAreaTargetSelectHandler(FilterTargets, 0, Targets.UnitSrcAreaEntry)); + OnEffectHitTarget.Add(new EffectHandler(HandleDummy, 0, SpellEffectName.Dummy)); + } + } + + public override SpellScript GetSpellScript() + { + return new spell_mimiron_rocket_strike_SpellScript(); + } + } + + [Script] // 63041 - Rocket Strike + class spell_mimiron_rocket_strike_damage : SpellScriptLoader + { + public spell_mimiron_rocket_strike_damage() : base("spell_mimiron_rocket_strike_damage") { } + + class spell_mimiron_rocket_strike_damage_SpellScript : SpellScript + { + public override bool Validate(SpellInfo spell) + { + return ValidateSpellInfo(Spells.NotSoFriendlyFire); + } + + void HandleAfterCast() + { + TempSummon summ = GetCaster().ToTempSummon(); + if (summ) + summ.DespawnOrUnsummon(); + } + + void HandleScript(uint effIndex) + { + if (GetHitPlayer()) + { + InstanceScript instance = GetCaster().GetInstanceScript(); + if (instance != null) + { + Creature mkii = ObjectAccessor.GetCreature(GetCaster(), instance.GetGuidData(InstanceData.LeviathanMKII)); + if (mkii) + mkii.GetAI().SetData(Data.SetupRocket, 0); + } + } + } + + void HandleFriendlyFire(uint effIndex) + { + GetHitUnit().CastSpell((Unit)null, Spells.NotSoFriendlyFire, true); + } + + public override void Register() + { + AfterCast.Add(new CastHandler(HandleAfterCast)); + OnEffectHitTarget.Add(new EffectHandler(HandleScript, 0, SpellEffectName.SchoolDamage)); + OnEffectHitTarget.Add(new EffectHandler(HandleFriendlyFire, 1, SpellEffectName.SchoolDamage)); + } + } + + public override SpellScript GetSpellScript() + { + return new spell_mimiron_rocket_strike_damage_SpellScript(); + } + } + + [Script] // 63681 - Rocket Strike + class spell_mimiron_rocket_strike_target_select : SpellScriptLoader + { + public spell_mimiron_rocket_strike_target_select() : base("spell_mimiron_rocket_strike_target_select") { } + + class spell_mimiron_rocket_strike_target_select_SpellScript : SpellScript + { + public override bool Validate(SpellInfo spell) + { + return ValidateSpellInfo(Spells.SummonRocketStrike); + } + + void FilterTargets(List targets) + { + if (targets.Empty()) + return; + + WorldObject target = targets.PickRandom(); + + targets.RemoveAll(new AllWorldObjectsInRange(GetCaster(), 15.0f).Invoke); + + if (!targets.Empty()) + target = targets.PickRandom(); + + targets.Clear(); + targets.Add(target); + } + + void HandleScript(uint effIndex) + { + InstanceScript instance = GetCaster().GetInstanceScript(); + if (instance != null) + GetCaster().CastSpell(GetHitUnit(), Spells.SummonRocketStrike, true, null, null, instance.GetGuidData(InstanceData.VX001)); + GetCaster().SetDisplayId(11686); + } + + public override void Register() + { + OnObjectAreaTargetSelect.Add(new ObjectAreaTargetSelectHandler(FilterTargets, 0, Targets.UnitSrcAreaEnemy)); + OnEffectHitTarget.Add(new EffectHandler(HandleScript, 0, SpellEffectName.ScriptEffect)); + } + } + + public override SpellScript GetSpellScript() + { + return new spell_mimiron_rocket_strike_target_select_SpellScript(); + } + } + + [Script] // 64383 - Self Repair + class spell_mimiron_self_repair : SpellScriptLoader + { + public spell_mimiron_self_repair() : base("spell_mimiron_self_repair") { } + + class spell_mimiron_self_repair_SpellScript : SpellScript + { + void HandleScript() + { + if (GetCaster().GetAI() != null) + GetCaster().GetAI().DoAction(Actions.AssembledCombat); + } + + public override void Register() + { + AfterHit.Add(new HitHandler(HandleScript)); + } + } + + public override SpellScript GetSpellScript() + { + return new spell_mimiron_self_repair_SpellScript(); + } + } + + [Script] // 64426 - Summon Scrap Bot + class spell_mimiron_summon_assault_bot : SpellScriptLoader + { + public spell_mimiron_summon_assault_bot() : base("spell_mimiron_summon_assault_bot") { } + + class spell_mimiron_summon_assault_bot_AuraScript : AuraScript + { + public override bool Validate(SpellInfo spell) + { + return ValidateSpellInfo(Spells.SummonAssaultBot); + } + + void OnRemove(AuraEffect aurEff, AuraEffectHandleModes mode) + { + Unit caster = GetCaster(); + if (caster) + { + InstanceScript instance = caster.GetInstanceScript(); + if (instance != null) + if (instance.GetBossState(BossIds.Mimiron) == EncounterState.InProgress) + caster.CastSpell(caster, Spells.SummonAssaultBot, false, null, aurEff, instance.GetGuidData(InstanceData.AerialCommandUnit)); + } + } + + public override void Register() + { + OnEffectRemove.Add(new EffectApplyHandler(OnRemove, 0, AuraType.Dummy, AuraEffectHandleModes.Real)); + } + } + + public override AuraScript GetAuraScript() + { + return new spell_mimiron_summon_assault_bot_AuraScript(); + } + } + + [Script] // 64425 - Summon Scrap Bot Trigger + class spell_mimiron_summon_assault_bot_target : SpellScriptLoader + { + public spell_mimiron_summon_assault_bot_target() : base("spell_mimiron_summon_assault_bot_target") { } + + class spell_mimiron_summon_assault_bot_target_SpellScript : SpellScript + { + public override bool Validate(SpellInfo spell) + { + return ValidateSpellInfo(Spells.SummonAssaultBotDummy); + } + + void HandleDummy(uint effIndex) + { + GetHitUnit().CastSpell(GetHitUnit(), Spells.SummonAssaultBotDummy, true); + } + + public override void Register() + { + OnEffectHitTarget.Add(new EffectHandler(HandleDummy, 0, SpellEffectName.Dummy)); + } + } + + public override SpellScript GetSpellScript() + { + return new spell_mimiron_summon_assault_bot_target_SpellScript(); + } + } + + [Script] // 64621 - Summon Fire Bot + class spell_mimiron_summon_fire_bot : SpellScriptLoader + { + public spell_mimiron_summon_fire_bot() : base("spell_mimiron_summon_fire_bot") { } + + class spell_mimiron_summon_fire_bot_AuraScript : AuraScript + { + public override bool Validate(SpellInfo spell) + { + return ValidateSpellInfo(Spells.SummonFireBot); + } + + void OnRemove(AuraEffect aurEff, AuraEffectHandleModes mode) + { + Unit caster = GetCaster(); + if (caster) + { + InstanceScript instance = caster.GetInstanceScript(); + if (instance != null) + if (instance.GetBossState(BossIds.Mimiron) == EncounterState.InProgress) + caster.CastSpell(caster, Spells.SummonFireBot, false, null, aurEff, instance.GetGuidData(InstanceData.AerialCommandUnit)); + } + } + + public override void Register() + { + OnEffectRemove.Add(new EffectApplyHandler(OnRemove, 0, AuraType.Dummy, AuraEffectHandleModes.Real)) + ; + } + } + + public override AuraScript GetAuraScript() + { + return new spell_mimiron_summon_fire_bot_AuraScript(); + } + } + + [Script] // 64620 - Summon Fire Bot Trigger + class spell_mimiron_summon_fire_bot_target : SpellScriptLoader + { + public spell_mimiron_summon_fire_bot_target() : base("spell_mimiron_summon_fire_bot_target") { } + + class spell_mimiron_summon_fire_bot_target_SpellScript : SpellScript + { + public override bool Validate(SpellInfo spell) + { + return ValidateSpellInfo(Spells.SummonFireBotDummy); + } + + void HandleDummy(uint effIndex) + { + GetHitUnit().CastSpell(GetHitUnit(), Spells.SummonFireBotDummy, true); + } + + public override void Register() + { + OnEffectHitTarget.Add(new EffectHandler(HandleDummy, 0, SpellEffectName.Dummy)); + } + } + + public override SpellScript GetSpellScript() + { + return new spell_mimiron_summon_fire_bot_target_SpellScript(); + } + } + + [Script] // 64562 - Summon Flames Spread Trigger + class spell_mimiron_summon_flames_spread : SpellScriptLoader + { + public spell_mimiron_summon_flames_spread() : base("spell_mimiron_summon_flames_spread") { } + + class spell_mimiron_summon_flames_spread_SpellScript : SpellScript + { + void FilterTargets(List targets) + { + if (targets.Empty()) + return; + + // Flames must chase the closest player + WorldObject target = targets.First(); + + foreach (var iter in targets) + if (GetCaster().GetDistance2d(iter) < GetCaster().GetDistance2d(target)) + target = iter; + + targets.Clear(); + targets.Add(target); + } + + public void onHit(uint effIndex) + { + GetCaster().SetInFront(GetHitUnit()); + } + + public override void Register() + { + OnObjectAreaTargetSelect.Add(new ObjectAreaTargetSelectHandler(FilterTargets, 0, Targets.UnitSrcAreaEnemy)); + OnEffectHitTarget.Add(new EffectHandler(onHit, 0, SpellEffectName.ApplyAura)); + } + } + + public override SpellScript GetSpellScript() + { + return new spell_mimiron_summon_flames_spread_SpellScript(); + } + + class spell_mimiron_summon_flames_spread_AuraScript : AuraScript + { + public override bool Validate(SpellInfo spell) + { + return ValidateSpellInfo(Spells.SummonFlamesSpread); + } + + void HandleTick(AuraEffect aurEff) + { + PreventDefaultAction(); + Unit caster = GetCaster(); + if (caster) + if (caster.HasAura(Spells.FlamesPeriodicTrigger)) + caster.CastSpell(GetTarget(), Spells.SummonFlamesSpread, true); + } + + public override void Register() + { + OnEffectPeriodic.Add(new EffectPeriodicHandler(HandleTick, 0, AuraType.PeriodicTriggerSpell)); + } + } + + public override AuraScript GetAuraScript() + { + return new spell_mimiron_summon_flames_spread_AuraScript(); + } + } + + [Script] // 64623 - Frost Bomb + class spell_mimiron_summon_frost_bomb_target : SpellScriptLoader + { + public spell_mimiron_summon_frost_bomb_target() : base("spell_mimiron_summon_frost_bomb_target") { } + + class spell_mimiron_summon_frost_bomb_target_SpellScript : SpellScript + { + public override bool Validate(SpellInfo spell) + { + return ValidateSpellInfo(Spells.SummonFrostBomb); + } + + void FilterTargets(List targets) + { + if (targets.Empty()) + return; + + targets.RemoveAll(new AllWorldObjectsInRange(GetCaster(), 15.0f).Invoke); + + if (targets.Empty()) + return; + + WorldObject target = targets.PickRandom(); + + targets.Clear(); + targets.Add(target); + } + + void HandleScript(uint effIndex) + { + GetCaster().CastSpell(GetHitUnit(), Spells.SummonFrostBomb, true); + } + + public override void Register() + { + OnObjectAreaTargetSelect.Add(new ObjectAreaTargetSelectHandler(FilterTargets, 0, Targets.UnitSrcAreaEntry)); + OnEffectHitTarget.Add(new EffectHandler(HandleScript, 0, SpellEffectName.ScriptEffect)); + } + } + + public override SpellScript GetSpellScript() + { + return new spell_mimiron_summon_frost_bomb_target_SpellScript(); + } + } + + [Script] // 64398 - Summon Scrap Bot + class spell_mimiron_summon_junk_bot : SpellScriptLoader + { + public spell_mimiron_summon_junk_bot() : base("spell_mimiron_summon_junk_bot") { } + + class spell_mimiron_summon_junk_bot_AuraScript : AuraScript + { + public override bool Validate(SpellInfo spell) + { + return ValidateSpellInfo(Spells.SummonJunkBot); + } + + void OnRemove(AuraEffect aurEff, AuraEffectHandleModes mode) + { + Unit caster = GetCaster(); + if (caster) + { + InstanceScript instance = caster.GetInstanceScript(); + if (instance != null) + if (instance.GetBossState(BossIds.Mimiron) == EncounterState.InProgress) + caster.CastSpell(caster, Spells.SummonJunkBot, false, null, aurEff, instance.GetGuidData(InstanceData.AerialCommandUnit)); + } + } + + public override void Register() + { + OnEffectRemove.Add(new EffectApplyHandler(OnRemove, 0, AuraType.Dummy, AuraEffectHandleModes.Real)); + } + } + + public override AuraScript GetAuraScript() + { + return new spell_mimiron_summon_junk_bot_AuraScript(); + } + } + + [Script] // 63820 - Summon Scrap Bot Trigger + class spell_mimiron_summon_junk_bot_target : SpellScriptLoader + { + public spell_mimiron_summon_junk_bot_target() : base("spell_mimiron_summon_junk_bot_target") { } + + class spell_mimiron_summon_junk_bot_target_SpellScript : SpellScript + { + public override bool Validate(SpellInfo spell) + { + return ValidateSpellInfo(Spells.SummonJunkBotDummy); + } + + void HandleDummy(uint effIndex) + { + GetHitUnit().CastSpell(GetHitUnit(), Spells.SummonJunkBotDummy, true); + } + + public override void Register() + { + OnEffectHitTarget.Add(new EffectHandler(HandleDummy, 0, SpellEffectName.Dummy)); + } + } + + public override SpellScript GetSpellScript() + { + return new spell_mimiron_summon_junk_bot_target_SpellScript(); + } + } + + [Script] // 63339 - Weld + class spell_mimiron_weld : SpellScriptLoader + { + public spell_mimiron_weld() : base("spell_mimiron_weld") { } + + class spell_mimiron_weld_AuraScript : AuraScript + { + void HandleTick(AuraEffect aurEff) + { + Unit caster = GetTarget(); + Unit vehicle = caster.GetVehicleBase(); + if (vehicle) + { + if (aurEff.GetTickNumber() % 5 == 0) + caster.CastSpell(vehicle, MimironConst.RepairSpells[RandomHelper.IRand(0, 3)]); + //caster.SetFacingToObject(vehicle); + } + } + + public override void Register() + { + OnEffectPeriodic.Add(new EffectPeriodicHandler(HandleTick, 0, AuraType.PeriodicTriggerSpell)); + } + } + + public override AuraScript GetAuraScript() + { + return new spell_mimiron_weld_AuraScript(); + } + } + + [Script] + class achievement_setup_boom : AchievementCriteriaScript + { + public achievement_setup_boom() : base("achievement_setup_boom") { } + + public override bool OnCheck(Player source, Unit target) + { + return target && target.GetAI().GetData(Data.SetupBomb) != 0; + } + } + + [Script] + class achievement_setup_mine : AchievementCriteriaScript + { + public achievement_setup_mine() : base("achievement_setup_mine") { } + + public override bool OnCheck(Player source, Unit target) + { + return target && target.GetAI().GetData(Data.SetupMine) != 0; + } + } + + [Script] + class achievement_setup_rocket : AchievementCriteriaScript + { + public achievement_setup_rocket() : base("achievement_setup_rocket") { } + + public override bool OnCheck(Player source, Unit target) + { + return target && target.GetAI().GetData(Data.SetupRocket) != 0; + } + } + + [Script] + class achievement_firefighter : AchievementCriteriaScript + { + public achievement_firefighter() : base("achievement_firefighter") { } + + public override bool OnCheck(Player source, Unit target) + { + return target && target.GetAI().GetData(Data.Firefighter) != 0; + } + } + } +} diff --git a/Scripts/Northrend/Ulduar/Razorscale.cs b/Scripts/Northrend/Ulduar/Razorscale.cs new file mode 100644 index 000000000..946b5b366 --- /dev/null +++ b/Scripts/Northrend/Ulduar/Razorscale.cs @@ -0,0 +1,162 @@ +/* + * Copyright (C) 2012-2017 CypherCore + * + * 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, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using Game.Entities; +using Game.Scripting; +using Game.AI; +using Framework.Constants; +using Game.Spells; + +namespace Scripts.Northrend.Ulduar +{ + namespace Razorscale + { + /*class boss_razorscale_controller : CreatureScript + { + public boss_razorscale_controller() : base("boss_razorscale_controller") { } + + class boss_razorscale_controllerAI : BossAI + { + public boss_razorscale_controllerAI(Creature creature) : base(creature, InstanceData.RazorscaleControl) + { + me.SetDisplayId(me.GetCreatureTemplate().ModelId2); + } + + public override void Reset() + { + _Reset(); + me.SetReactState(ReactStates.Passive); + } + + public override void SpellHit(Unit caster, SpellInfo spell) + { + switch (spell.Id) + { + case SPELL_FLAMED: + GameObject Harpoon = ObjectAccessor.GetGameObject(me, instance.GetGuidData(GO_RAZOR_HARPOON_1)); + if (Harpoon) + Harpoon.RemoveFromWorld(); + Harpoon = ObjectAccessor.GetGameObject(me, instance.GetGuidData(GO_RAZOR_HARPOON_2)); + if (Harpoon) + Harpoon.RemoveFromWorld(); + Harpoon = ObjectAccessor.GetGameObject(me, instance.GetGuidData(GO_RAZOR_HARPOON_3)); + if (Harpoon) + Harpoon.RemoveFromWorld(); + Harpoon = ObjectAccessor.GetGameObject(me, instance.GetGuidData(GO_RAZOR_HARPOON_4)); + if (Harpoon) + Harpoon.RemoveFromWorld(); + + DoAction(ACTION_HARPOON_BUILD); + DoAction(ACTION_PLACE_BROKEN_HARPOON); + break; + case SPELL_HARPOON_SHOT_1: + case SPELL_HARPOON_SHOT_2: + case SPELL_HARPOON_SHOT_3: + case SPELL_HARPOON_SHOT_4: + DoCast(SPELL_HARPOON_TRIGGER); + break; + } + } + + public override void JustDied(Unit killer) + { + _JustDied(); + } + + public override void DoAction(int action) + { + if (instance.GetBossState(BOSS_RAZORSCALE) != EncounterState.InProgress) + return; + + switch (action) + { + case ACTION_HARPOON_BUILD: + events.ScheduleEvent(EVENT_BUILD_HARPOON_1, 50000); + if (me->GetMap()->GetSpawnMode() == DIFFICULTY_25_N) + events.ScheduleEvent(EVENT_BUILD_HARPOON_3, 90000); + break; + case ACTION_PLACE_BROKEN_HARPOON: + for (uint8 n = 0; n < RAID_MODE(2, 4); n++) + me->SummonGameObject(GO_RAZOR_BROKEN_HARPOON, PosHarpoon[n].GetPositionX(), PosHarpoon[n].GetPositionY(), PosHarpoon[n].GetPositionZ(), 2.286f, 0, 0, 0, 0, 180); + break; + } + } + + public override void UpdateAI(uint Diff) + { + _events.Update(Diff); + + while (uint eventId = _events.ExecuteEvent()) + { + switch (eventId) + { + case EVENT_BUILD_HARPOON_1: + Talk(EMOTE_HARPOON); + if (GameObject * Harpoon = me->SummonGameObject(GO_RAZOR_HARPOON_1, PosHarpoon[0].GetPositionX(), PosHarpoon[0].GetPositionY(), PosHarpoon[0].GetPositionZ(), 4.790f, 0.0f, 0.0f, 0.0f, 0.0f, uint32(me->GetRespawnTime()))) + { + if (GameObject * BrokenHarpoon = Harpoon->FindNearestGameObject(GO_RAZOR_BROKEN_HARPOON, 5.0f)) //only nearest broken harpoon + BrokenHarpoon->RemoveFromWorld(); + events.ScheduleEvent(EVENT_BUILD_HARPOON_2, 20000); + events.CancelEvent(EVENT_BUILD_HARPOON_1); + } + return; + case EVENT_BUILD_HARPOON_2: + Talk(EMOTE_HARPOON); + if (GameObject * Harpoon = me->SummonGameObject(GO_RAZOR_HARPOON_2, PosHarpoon[1].GetPositionX(), PosHarpoon[1].GetPositionY(), PosHarpoon[1].GetPositionZ(), 4.659f, 0, 0, 0, 0, uint32(me->GetRespawnTime()))) + { + if (GameObject * BrokenHarpoon = Harpoon->FindNearestGameObject(GO_RAZOR_BROKEN_HARPOON, 5.0f)) + BrokenHarpoon->RemoveFromWorld(); + events.CancelEvent(EVENT_BUILD_HARPOON_2); + } + return; + case EVENT_BUILD_HARPOON_3: + Talk(EMOTE_HARPOON); + if (GameObject * Harpoon = me->SummonGameObject(GO_RAZOR_HARPOON_3, PosHarpoon[2].GetPositionX(), PosHarpoon[2].GetPositionY(), PosHarpoon[2].GetPositionZ(), 5.382f, 0, 0, 0, 0, uint32(me->GetRespawnTime()))) + { + if (GameObject * BrokenHarpoon = Harpoon->FindNearestGameObject(GO_RAZOR_BROKEN_HARPOON, 5.0f)) + BrokenHarpoon->RemoveFromWorld(); + events.ScheduleEvent(EVENT_BUILD_HARPOON_4, 20000); + events.CancelEvent(EVENT_BUILD_HARPOON_3); + } + return; + case EVENT_BUILD_HARPOON_4: + Talk(EMOTE_HARPOON); + if (GameObject * Harpoon = me->SummonGameObject(GO_RAZOR_HARPOON_4, PosHarpoon[3].GetPositionX(), PosHarpoon[3].GetPositionY(), PosHarpoon[3].GetPositionZ(), 4.266f, 0, 0, 0, 0, uint32(me->GetRespawnTime()))) + { + if (GameObject * BrokenHarpoon = Harpoon->FindNearestGameObject(GO_RAZOR_BROKEN_HARPOON, 5.0f)) + BrokenHarpoon->RemoveFromWorld(); + events.CancelEvent(EVENT_BUILD_HARPOON_4); + } + return; + } + } + } + } + + public override CreatureAI GetAI(Creature creature) + { + return GetInstanceAI(creature); + } + }*/ + + } +} diff --git a/Scripts/Northrend/Ulduar/UlduarConst.cs b/Scripts/Northrend/Ulduar/UlduarConst.cs new file mode 100644 index 000000000..7974e0b3a --- /dev/null +++ b/Scripts/Northrend/Ulduar/UlduarConst.cs @@ -0,0 +1,409 @@ +/* + * Copyright (C) 2012-2017 CypherCore + * + * 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, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +namespace Scripts.Northrend.Ulduar +{ + struct BossIds + { + public const uint MaxEncounter = 17; + + public const uint Leviathan = 0; + public const uint Ignis = 1; + public const uint Razorscale = 2; + public const uint Xt002 = 3; + public const uint AssemblyOfIron = 4; + public const uint Kologarn = 5; + public const uint Auriaya = 6; + public const uint Mimiron = 7; + public const uint Hodir = 8; + public const uint Thorim = 9; + public const uint Freya = 10; + public const uint Brightleaf = 11; + public const uint Ironbranch = 12; + public const uint Stonebark = 13; + public const uint Vezax = 14; + public const uint YoggSaron = 15; + public const uint Algalon = 16; + } + + struct InstanceCreatureIds + { + // General + public const uint Leviathan = 33113; + public const uint SalvagedDemolisher = 33109; + public const uint SalvagedSiegeEngine = 33060; + public const uint SalvagedChopper = 33062; + public const uint Ignis = 33118; + public const uint Razorscale = 33186; + public const uint RazorscaleController = 33233; + public const uint SteelforgedDeffender = 33236; + public const uint ExpeditionCommander = 33210; + public const uint Xt002 = 33293; + public const uint XtToyPile = 33337; + public const uint Steelbreaker = 32867; + public const uint Molgeim = 32927; + public const uint Brundir = 32857; + public const uint Kologarn = 32930; + public const uint FocusedEyebeam = 33632; + public const uint FocusedEyebeamRight = 33802; + public const uint LeftArm = 32933; + public const uint RightArm = 32934; + public const uint Rubble = 33768; + public const uint Auriaya = 33515; + public const uint Mimiron = 33350; + public const uint Hodir = 32845; + public const uint Thorim = 32865; + public const uint Freya = 32906; + public const uint Vezax = 33271; + public const uint YoggSaron = 33288; + public const uint Algalon = 32871; + + //XT002 + public const uint XS013Scrapbot = 33343; + + // Mimiron + public const uint LeviathanMkII = 33432; + public const uint Vx001 = 33651; + public const uint AerialCommandUnit = 33670; + public const uint AssaultBot = 34057; + public const uint BombBot = 33836; + public const uint JunkBot = 33855; + public const uint EmergencyFireBot = 34147; + public const uint FrostBomb = 34149; + public const uint BurstTarget = 34211; + public const uint Flame = 34363; + public const uint FlameSpread = 34121; + public const uint DBTarget = 33576; + public const uint RocketMimironVisual = 34050; + public const uint WorldTriggerMimiron = 21252; + public const uint Computer = 34143; + + // Freya'S Keepers + public const uint Ironbranch = 32913; + public const uint Brightleaf = 32915; + public const uint Stonebark = 32914; + + // Hodir'S Helper Npcs + public const uint TorGreycloud = 32941; + public const uint KarGreycloud = 33333; + public const uint EiviNightfeather = 33325; + public const uint EllieNightfeather = 32901; + public const uint SpiritwalkerTara = 33332; + public const uint SpiritwalkerYona = 32950; + public const uint ElementalistMahfuun = 33328; + public const uint ElementalistAvuun = 32900; + public const uint AmiraBlazeweaver = 33331; + public const uint VeeshaBlazeweaver = 32946; + public const uint MissyFlamecuffs = 32893; + public const uint SissyFlamecuffs = 33327; + public const uint BattlePriestEliza = 32948; + public const uint BattlePriestGina = 33330; + public const uint FieldMedicPenny = 32897; + public const uint FieldMedicJessi = 33326; + + // Freya'S Trash Npcs + public const uint CorruptedServitor = 33354; + public const uint MisguidedNymph = 33355; + public const uint GuardianLasher = 33430; + public const uint ForestSwarmer = 33431; + public const uint MangroveEnt = 33525; + public const uint IronrootLasher = 33526; + public const uint NaturesBlade = 33527; + public const uint GuardianOfLife = 33528; + + // Freya Achievement Trigger + public const uint FreyaAchieveTrigger = 33406; + + // Yogg-Saron + public const uint Sara = 33134; + public const uint GuardianOfYoggSaron = 33136; + public const uint HodirObservationRing = 33213; + public const uint FreyaObservationRing = 33241; + public const uint ThorimObservationRing = 33242; + public const uint MimironObservationRing = 33244; + public const uint VoiceOfYoggSaron = 33280; + public const uint OminousCloud = 33292; + public const uint FreyaYs = 33410; + public const uint HodirYs = 33411; + public const uint MimironYs = 33412; + public const uint ThorimYs = 33413; + public const uint SuitOfArmor = 33433; + public const uint KingLlane = 33437; + public const uint TheLichKing = 33441; + public const uint ImmolatedChampion = 33442; + public const uint Ysera = 33495; + public const uint Neltharion = 33523; + public const uint Malygos = 33535; + public const uint DeathRay = 33881; + public const uint DeathOrb = 33882; + public const uint BrainOfYoggSaron = 33890; + public const uint InfluenceTentacle = 33943; + public const uint TurnedChampion = 33962; + public const uint CrusherTentacle = 33966; + public const uint ConstrictorTentacle = 33983; + public const uint CorruptorTentacle = 33985; + public const uint ImmortalGuardian = 33988; + public const uint SanityWell = 33991; + public const uint DescendIntoMadness = 34072; + public const uint MarkedImmortalGuardian = 36064; + + // Algalon The Observer + public const uint BrannBronzbeardAlg = 34064; + public const uint Azeroth = 34246; + public const uint LivingConstellation = 33052; + public const uint AlgalonStalker = 33086; + public const uint CollapsingStar = 32955; + public const uint BlackHole = 32953; + public const uint WormHole = 34099; + public const uint AlgalonVoidZoneVisualStalker = 34100; + public const uint AlgalonStalkerAsteroidTarget01 = 33104; + public const uint AlgalonStalkerAsteroidTarget02 = 33105; + public const uint UnleashedDarkMatter = 34097; + } + + struct InstanceGameObjectIds + { + // Leviathan + public const uint LeviathanDoor = 194905; + public const uint LeviathanGate = 194630; + + // Razorscale + public const uint MoleMachine = 194316; + public const uint RazorHarpoon1 = 194542; + public const uint RazorHarpoon2 = 194541; + public const uint RazorHarpoon3 = 194543; + public const uint RazorHarpoon4 = 194519; + public const uint RazorBrokenHarpoon = 194565; + + // Xt-002 + public const uint Xt002Door = 194631; + + // Assembly Of Iron + public const uint IronCouncilDoor = 194554; + public const uint ArchivumDoor = 194556; + + // Kologarn + public const uint KologarnChestHero = 195047; + public const uint KologarnChest = 195046; + public const uint KologarnBridge = 194232; + public const uint KologarnDoor = 194553; + + // Hodir + public const uint HodirEntrance = 194442; + public const uint HodirDoor = 194634; + public const uint HodirIceDoor = 194441; + public const uint HodirRareCacheOfWinter = 194200; + public const uint HodirRareCacheOfWinterHero = 194201; + public const uint HodirChestHero = 194308; + public const uint HodirChest = 194307; + + // Thorim + public const uint ThorimChestHero = 194315; + public const uint ThorimChest = 194314; + + // Mimiron + public const uint MimironTram = 194675; + public const uint MimironElevator = 194749; + public const uint MimironButton = 194739; + public const uint MimironDoor1 = 194774; + public const uint MimironDoor2 = 194775; + public const uint MimironDoor3 = 194776; + public const uint CacheOfInnovation = 194789; + public const uint CacheOfInnovationFirefighter = 194957; + public const uint CacheOfInnovationHero = 194956; + public const uint CacheOfInnovationFirefighterHero = 194958; + + // Vezax + public const uint VezaxDoor = 194750; + + // Yogg-Saron + public const uint YoggSaronDoor = 194773; + public const uint BrainRoomDoor1 = 194635; + public const uint BrainRoomDoor2 = 194636; + public const uint BrainRoomDoor3 = 194637; + + // Algalon The Observer + public const uint CelestialPlanetariumAccess10 = 194628; + public const uint CelestialPlanetariumAccess25 = 194752; + public const uint DoodadUlSigildoor01 = 194767; + public const uint DoodadUlSigildoor02 = 194911; + public const uint DoodadUlSigildoor03 = 194910; + public const uint DoodadUlUniversefloor01 = 194715; + public const uint DoodadUlUniversefloor02 = 194716; + public const uint DoodadUlUniverseglobe01 = 194148; + public const uint DoodadUlUlduarTrapdoor03 = 194253; + public const uint GiftOfTheObserver10 = 194821; + public const uint GiftOfTheObserver25 = 194822; + } + + struct InstanceEventIds + { + public const int TowerOfStormDestroyed = 21031; + public const int TowerOfFrostDestroyed = 21032; + public const int TowerOfFlamesDestroyed = 21033; + public const int TowerOfLifeDestroyed = 21030; + public const int ActivateSanityWell = 21432; + public const int HodirsProtectiveGazeProc = 21437; + } + + struct LeviathanActions + { + public const int TowerOfStormDestroyed = 1; + public const int TowerOfFrostDestroyed = 2; + public const int TowerOfFlamesDestroyed = 3; + public const int TowerOfLifeDestroyed = 4; + public const int MoveToCenterPosition = 10; + } + + struct InstanceCriteriaIds + { + public const uint ConSpeedAtory = 21597; + public const uint Lumberjacked = 21686; + public const uint Disarmed = 21687; + public const uint WaitsDreamingStormwind25 = 10321; + public const uint WaitsDreamingChamber25 = 10322; + public const uint WaitsDreamingIcecrown25 = 10323; + public const uint WaitsDreamingStormwind10 = 10324; + public const uint WaitsDreamingChamber10 = 10325; + public const uint WaitsDreamingIcecrown10 = 10326; + public const uint DriveMeCrazy10 = 10185; + public const uint DriveMeCrazy25 = 10296; + public const uint ThreeLightsInTheDarkness10 = 10410; + public const uint ThreeLightsInTheDarkness25 = 10414; + public const uint TwoLightsInTheDarkness10 = 10388; + public const uint TwoLightsInTheDarkness25 = 10415; + public const uint OneLightInTheDarkness10 = 10409; + public const uint OneLightInTheDarkness25 = 10416; + public const uint AloneInTheDarkness10 = 10412; + public const uint AloneInTheDarkness25 = 10417; + public const uint HeraldOfTitans = 10678; + + // Champion Of Ulduar + public const uint ChampionLeviathan10 = 10042; + public const uint ChampionIgnis10 = 10342; + public const uint ChampionRazorscale10 = 10340; + public const uint ChampionXt002_10 = 10341; + public const uint ChampionIronCouncil10 = 10598; + public const uint ChampionKologarn10 = 10348; + public const uint ChampionAuriaya10 = 10351; + public const uint ChampionHodir10 = 10439; + public const uint ChampionThorim10 = 10403; + public const uint ChampionFreya10 = 10582; + public const uint ChampionMimiron10 = 10347; + public const uint ChampionVezax10 = 10349; + public const uint ChampionYoggSaron10 = 10350; + // Conqueror Of Ulduar + public const uint ChampionLeviathan25 = 10352; + public const uint ChampionIgnis25 = 10355; + public const uint ChampionRazorscale25 = 10353; + public const uint ChampionXt002_25 = 10354; + public const uint ChampionIronCouncil25 = 10599; + public const uint ChampionKologarn25 = 10357; + public const uint ChampionAuriaya25 = 10363; + public const uint ChampionHodir25 = 10719; + public const uint ChampionThorim25 = 10404; + public const uint ChampionFreya25 = 10583; + public const uint ChampionMimiron25 = 10361; + public const uint ChampionVezax25 = 10362; + public const uint ChampionYoggSaron25 = 10364; + } + + struct InstanceData + { + // Colossus (Leviathan) + public const uint Colossus = 20; + + // Razorscale + public const uint ExpeditionCommander = 21; + public const uint RazorscaleControl = 22; + + // Xt-002 + public const uint ToyPile0 = 23; + public const uint ToyPile1 = 24; + public const uint ToyPile2 = 25; + public const uint ToyPile3 = 26; + + // Assembly Of Iron + public const uint Steelbreaker = 27; + public const uint Molgeim = 28; + public const uint Brundir = 29; + + // Hodir + public const uint HodirRareCache = 30; + + // Mimiron + public const uint LeviathanMKII = 31; + public const uint VX001 = 32; + public const uint AerialCommandUnit = 33; + public const uint Computer = 34; + public const uint MimironWorldTrigger = 35; + public const uint MimironElevator = 36; + public const uint MimironTram = 37; + public const uint MimironButton = 38; + + // Yogg-Saron + public const uint VoiceOfYoggSaron = 39; + public const uint Sara = 40; + public const uint BrainOfYoggSaron = 41; + public const uint FreyaYs = 42; + public const uint HodirYs = 43; + public const uint ThorimYs = 44; + public const uint MimironYs = 45; + public const uint Illusion = 46; + public const uint DriveMeCrazy = 47; + public const uint KeepersCount = 48; + + // Algalon The Observer + public const uint AlgalonSummonState = 49; + public const uint Sigildoor01 = 50; + public const uint Sigildoor02 = 51; + public const uint Sigildoor03 = 52; + public const uint UniverseFloor01 = 53; + public const uint UniverseFloor02 = 54; + public const uint UniverseGlobe = 55; + public const uint AlgalonTrapdoor = 56; + public const uint BrannBronzebeardAlg = 57; + } + + struct InstanceWorldStates + { + public const uint AlgalonDespawnTimer = 4131; + public const uint AlgalonTimerEnabled = 4132; + } + + struct InstanceAchievementData + { + // Fl Achievement Boolean + public const uint DataUnbroken = 29052906; // 2905, 2906 Are Achievement Ids, + public const uint MaxHeraldArmorItemlevel = 226; + public const uint MaxHeraldWeaponItemlevel = 232; + } + + struct InstanceEvents + { + public const uint EventDespawnAlgalon = 1; + public const uint EventUpdateAlgalonTimer = 2; + public const uint ActionInitAlgalon = 6; + } + + struct YoggSaronIllusions + { + public const uint ChamberIllusion = 0; + public const uint IcecrownIllusion = 1; + public const uint StormwindIllusion = 2; + } +} diff --git a/Scripts/Northrend/Ulduar/UlduarInstance.cs b/Scripts/Northrend/Ulduar/UlduarInstance.cs new file mode 100644 index 000000000..f15ae7a25 --- /dev/null +++ b/Scripts/Northrend/Ulduar/UlduarInstance.cs @@ -0,0 +1,1241 @@ +/* + * Copyright (C) 2012-2017 CypherCore + * + * 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, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +using Framework.Constants; +using Framework.IO; +using Game.AI; +using Game.Entities; +using Game.Maps; +using Game.Network.Packets; +using Game.Scripting; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; + +namespace Scripts.Northrend.Ulduar +{ + [Script] + class instance_ulduar : InstanceMapScript + { + public instance_ulduar() : base("instance_ulduar", 603) { } + + class instance_ulduar_InstanceMapScript : InstanceScript + { + public instance_ulduar_InstanceMapScript(InstanceMap map) : base(map) + { + SetHeaders("UU"); + SetBossNumber(BossIds.MaxEncounter); + + LoadDoorData(doorData); + LoadMinionData(minionData); + + _algalonTimer = 61; + + Unbroken = true; + IsDriveMeCrazyEligible = true; + } + + public override void FillInitialWorldStates(InitWorldStates packet) + { + packet.AddState(InstanceWorldStates.AlgalonTimerEnabled, (_algalonTimer != 0 && _algalonTimer <= 60 ? 1 : 0)); + packet.AddState(InstanceWorldStates.AlgalonDespawnTimer, (int)Math.Min(_algalonTimer, 60)); + } + + public override void OnPlayerEnter(Player player) + { + if (TeamInInstance == 0) + TeamInInstance = player.GetTeam(); + + if (_summonAlgalon) + { + _summonAlgalon = false; + TempSummon algalon = instance.SummonCreature(InstanceCreatureIds.Algalon, Algalon.AlgalonTheObserver.AlgalonLandPos); + if (_algalonTimer != 0 && _algalonTimer <= 60) + algalon.GetAI().DoAction((int)InstanceEvents.ActionInitAlgalon); + else + algalon.RemoveFlag(UnitFields.Flags, UnitFlags.ImmuneToPc); + } + + // Keepers at Observation Ring + if (GetBossState(BossIds.Freya) == EncounterState.Done && _summonObservationRingKeeper[0] && KeeperGUIDs[0].IsEmpty()) + { + _summonObservationRingKeeper[0] = false; + instance.SummonCreature(InstanceCreatureIds.FreyaObservationRing, YoggSaron.ObservationRingKeepersPos[0]); + } + if (GetBossState(BossIds.Hodir) == EncounterState.Done && _summonObservationRingKeeper[1] && KeeperGUIDs[1].IsEmpty()) + { + _summonObservationRingKeeper[1] = false; + instance.SummonCreature(InstanceCreatureIds.HodirObservationRing, YoggSaron.ObservationRingKeepersPos[1]); + } + if (GetBossState(BossIds.Thorim) == EncounterState.Done && _summonObservationRingKeeper[2] && KeeperGUIDs[2].IsEmpty()) + { + _summonObservationRingKeeper[2] = false; + instance.SummonCreature(InstanceCreatureIds.ThorimObservationRing, YoggSaron.ObservationRingKeepersPos[2]); + } + if (GetBossState(BossIds.Mimiron) == EncounterState.Done && _summonObservationRingKeeper[3] && KeeperGUIDs[3].IsEmpty()) + { + _summonObservationRingKeeper[3] = false; + instance.SummonCreature(InstanceCreatureIds.MimironObservationRing, YoggSaron.ObservationRingKeepersPos[3]); + } + + // Keepers in Yogg-Saron's room + if (_summonYSKeeper[0]) + instance.SummonCreature(InstanceCreatureIds.FreyaYs, YoggSaron.YSKeepersPos[0]); + if (_summonYSKeeper[1]) + instance.SummonCreature(InstanceCreatureIds.HodirYs, YoggSaron.YSKeepersPos[1]); + if (_summonYSKeeper[2]) + instance.SummonCreature(InstanceCreatureIds.ThorimYs, YoggSaron.YSKeepersPos[2]); + if (_summonYSKeeper[3]) + instance.SummonCreature(InstanceCreatureIds.MimironYs, YoggSaron.YSKeepersPos[3]); + } + + public override void OnCreatureCreate(Creature creature) + { + if (TeamInInstance == 0) + { + var Players = instance.GetPlayers(); + if (!Players.Empty()) + { + Player player = Players.First(); + if (player) + TeamInInstance = player.GetTeam(); + } + } + + switch (creature.GetEntry()) + { + case InstanceCreatureIds.Leviathan: + LeviathanGUID = creature.GetGUID(); + break; + case InstanceCreatureIds.SalvagedDemolisher: + case InstanceCreatureIds.SalvagedSiegeEngine: + case InstanceCreatureIds.SalvagedChopper: + LeviathanVehicleGUIDs.Add(creature.GetGUID()); + break; + case InstanceCreatureIds.Ignis: + IgnisGUID = creature.GetGUID(); + break; + + // Razorscale + case InstanceCreatureIds.Razorscale: + RazorscaleGUID = creature.GetGUID(); + break; + case InstanceCreatureIds.RazorscaleController: + RazorscaleController = creature.GetGUID(); + break; + case InstanceCreatureIds.ExpeditionCommander: + ExpeditionCommanderGUID = creature.GetGUID(); + break; + + // XT-002 Deconstructor + case InstanceCreatureIds.Xt002: + XT002GUID = creature.GetGUID(); + break; + case InstanceCreatureIds.XtToyPile: + for (byte i = 0; i < 4; ++i) + { + if (XTToyPileGUIDs[i].IsEmpty()) + { + XTToyPileGUIDs[i] = creature.GetGUID(); + break; + } + } + break; + + // Assembly of Iron + case InstanceCreatureIds.Steelbreaker: + AssemblyGUIDs[0] = creature.GetGUID(); + AddMinion(creature, true); + break; + case InstanceCreatureIds.Molgeim: + AssemblyGUIDs[1] = creature.GetGUID(); + AddMinion(creature, true); + break; + case InstanceCreatureIds.Brundir: + AssemblyGUIDs[2] = creature.GetGUID(); + AddMinion(creature, true); + break; + + case InstanceCreatureIds.Kologarn: + KologarnGUID = creature.GetGUID(); + break; + case InstanceCreatureIds.Auriaya: + AuriayaGUID = creature.GetGUID(); + break; + + // Hodir + case InstanceCreatureIds.Hodir: + HodirGUID = creature.GetGUID(); + break; + case InstanceCreatureIds.EiviNightfeather: + if (TeamInInstance == Team.Horde) + creature.UpdateEntry(InstanceCreatureIds.TorGreycloud); + break; + case InstanceCreatureIds.EllieNightfeather: + if (TeamInInstance == Team.Horde) + creature.UpdateEntry(InstanceCreatureIds.KarGreycloud); + break; + case InstanceCreatureIds.ElementalistMahfuun: + if (TeamInInstance == Team.Horde) + creature.UpdateEntry(InstanceCreatureIds.SpiritwalkerTara); + break; + case InstanceCreatureIds.ElementalistAvuun: + if (TeamInInstance == Team.Horde) + creature.UpdateEntry(InstanceCreatureIds.SpiritwalkerYona); + break; + case InstanceCreatureIds.MissyFlamecuffs: + if (TeamInInstance == Team.Horde) + creature.UpdateEntry(InstanceCreatureIds.AmiraBlazeweaver); + break; + case InstanceCreatureIds.SissyFlamecuffs: + if (TeamInInstance == Team.Horde) + creature.UpdateEntry(InstanceCreatureIds.VeeshaBlazeweaver); + break; + case InstanceCreatureIds.FieldMedicPenny: + if (TeamInInstance == Team.Horde) + creature.UpdateEntry(InstanceCreatureIds.BattlePriestEliza); + break; + case InstanceCreatureIds.FieldMedicJessi: + if (TeamInInstance == Team.Horde) + creature.UpdateEntry(InstanceCreatureIds.BattlePriestGina); + break; + + case InstanceCreatureIds.Thorim: + ThorimGUID = creature.GetGUID(); + break; + + // Freya + case InstanceCreatureIds.Freya: + FreyaGUID = creature.GetGUID(); + break; + case InstanceCreatureIds.Ironbranch: + ElderGUIDs[0] = creature.GetGUID(); + if (GetBossState(BossIds.Freya) == EncounterState.Done) + creature.DespawnOrUnsummon(); + break; + case InstanceCreatureIds.Brightleaf: + ElderGUIDs[1] = creature.GetGUID(); + if (GetBossState(BossIds.Freya) == EncounterState.Done) + creature.DespawnOrUnsummon(); + break; + case InstanceCreatureIds.Stonebark: + ElderGUIDs[2] = creature.GetGUID(); + if (GetBossState(BossIds.Freya) == EncounterState.Done) + creature.DespawnOrUnsummon(); + break; + case InstanceCreatureIds.FreyaAchieveTrigger: + FreyaAchieveTriggerGUID = creature.GetGUID(); + break; + + // Mimiron + case InstanceCreatureIds.Mimiron: + MimironGUID = creature.GetGUID(); + break; + case InstanceCreatureIds.LeviathanMkII: + MimironVehicleGUIDs[0] = creature.GetGUID(); + break; + case InstanceCreatureIds.Vx001: + MimironVehicleGUIDs[1] = creature.GetGUID(); + break; + case InstanceCreatureIds.AerialCommandUnit: + MimironVehicleGUIDs[2] = creature.GetGUID(); + break; + case InstanceCreatureIds.Computer: + MimironComputerGUID = creature.GetGUID(); + break; + case InstanceCreatureIds.WorldTriggerMimiron: + MimironWorldTriggerGUID = creature.GetGUID(); + break; + + case InstanceCreatureIds.Vezax: + VezaxGUID = creature.GetGUID(); + break; + + // Yogg-Saron + case InstanceCreatureIds.YoggSaron: + YoggSaronGUID = creature.GetGUID(); + break; + case InstanceCreatureIds.VoiceOfYoggSaron: + VoiceOfYoggSaronGUID = creature.GetGUID(); + break; + case InstanceCreatureIds.BrainOfYoggSaron: + BrainOfYoggSaronGUID = creature.GetGUID(); + break; + case InstanceCreatureIds.Sara: + SaraGUID = creature.GetGUID(); + break; + case InstanceCreatureIds.FreyaYs: + KeeperGUIDs[0] = creature.GetGUID(); + _summonYSKeeper[0] = false; + SaveToDB(); + ++keepersCount; + break; + case InstanceCreatureIds.HodirYs: + KeeperGUIDs[1] = creature.GetGUID(); + _summonYSKeeper[1] = false; + SaveToDB(); + ++keepersCount; + break; + case InstanceCreatureIds.ThorimYs: + KeeperGUIDs[2] = creature.GetGUID(); + _summonYSKeeper[2] = false; + SaveToDB(); + ++keepersCount; + break; + case InstanceCreatureIds.MimironYs: + KeeperGUIDs[3] = creature.GetGUID(); + _summonYSKeeper[3] = false; + SaveToDB(); + ++keepersCount; + break; + case InstanceCreatureIds.SanityWell: + creature.SetReactState(ReactStates.Passive); + break; + + // Algalon + case InstanceCreatureIds.Algalon: + AlgalonGUID = creature.GetGUID(); + break; + case InstanceCreatureIds.BrannBronzbeardAlg: + BrannBronzebeardAlgGUID = creature.GetGUID(); + break; + //! These creatures are summoned by something else than Algalon + //! but need to be controlled/despawned by him - so they need to be + //! registered in his summon list + case InstanceCreatureIds.AlgalonVoidZoneVisualStalker: + case InstanceCreatureIds.AlgalonStalkerAsteroidTarget01: + case InstanceCreatureIds.AlgalonStalkerAsteroidTarget02: + case InstanceCreatureIds.UnleashedDarkMatter: + Creature algalon = instance.GetCreature(AlgalonGUID); + if (algalon) + algalon.GetAI().JustSummoned(creature); + break; + } + } + + public override void OnCreatureRemove(Creature creature) + { + switch (creature.GetEntry()) + { + case InstanceCreatureIds.XtToyPile: + for (byte i = 0; i < 4; ++i) + if (XTToyPileGUIDs[i] == creature.GetGUID()) + { + XTToyPileGUIDs[i].Clear(); + break; + } + break; + case InstanceCreatureIds.Steelbreaker: + case InstanceCreatureIds.Molgeim: + case InstanceCreatureIds.Brundir: + AddMinion(creature, false); + break; + case InstanceCreatureIds.BrannBronzbeardAlg: + if (BrannBronzebeardAlgGUID == creature.GetGUID()) + BrannBronzebeardAlgGUID.Clear(); + break; + default: + break; + } + } + + public override void OnGameObjectCreate(GameObject gameObject) + { + switch (gameObject.GetEntry()) + { + case InstanceGameObjectIds.KologarnChestHero: + case InstanceGameObjectIds.KologarnChest: + KologarnChestGUID = gameObject.GetGUID(); + break; + case InstanceGameObjectIds.KologarnBridge: + KologarnBridgeGUID = gameObject.GetGUID(); + if (GetBossState(BossIds.Kologarn) == EncounterState.Done) + HandleGameObject(ObjectGuid.Empty, false, gameObject); + break; + case InstanceGameObjectIds.ThorimChestHero: + case InstanceGameObjectIds.ThorimChest: + ThorimChestGUID = gameObject.GetGUID(); + break; + case InstanceGameObjectIds.HodirRareCacheOfWinterHero: + case InstanceGameObjectIds.HodirRareCacheOfWinter: + HodirRareCacheGUID = gameObject.GetGUID(); + break; + case InstanceGameObjectIds.HodirChestHero: + case InstanceGameObjectIds.HodirChest: + HodirChestGUID = gameObject.GetGUID(); + break; + case InstanceGameObjectIds.MimironTram: + MimironTramGUID = gameObject.GetGUID(); + break; + case InstanceGameObjectIds.MimironElevator: + MimironElevatorGUID = gameObject.GetGUID(); + break; + case InstanceGameObjectIds.MimironButton: + MimironButtonGUID = gameObject.GetGUID(); + break; + case InstanceGameObjectIds.LeviathanGate: + LeviathanGateGUID = gameObject.GetGUID(); + if (GetBossState(BossIds.Leviathan) == EncounterState.Done) + gameObject.SetGoState(GameObjectState.ActiveAlternative); + break; + case InstanceGameObjectIds.LeviathanDoor: + case InstanceGameObjectIds.Xt002Door: + case InstanceGameObjectIds.IronCouncilDoor: + case InstanceGameObjectIds.ArchivumDoor: + case InstanceGameObjectIds.HodirEntrance: + case InstanceGameObjectIds.HodirDoor: + case InstanceGameObjectIds.HodirIceDoor: + case InstanceGameObjectIds.MimironDoor1: + case InstanceGameObjectIds.MimironDoor2: + case InstanceGameObjectIds.MimironDoor3: + case InstanceGameObjectIds.VezaxDoor: + case InstanceGameObjectIds.YoggSaronDoor: + AddDoor(gameObject, true); + break; + case InstanceGameObjectIds.RazorHarpoon1: + RazorHarpoonGUIDs[0] = gameObject.GetGUID(); + break; + case InstanceGameObjectIds.RazorHarpoon2: + RazorHarpoonGUIDs[1] = gameObject.GetGUID(); + break; + case InstanceGameObjectIds.RazorHarpoon3: + RazorHarpoonGUIDs[2] = gameObject.GetGUID(); + break; + case InstanceGameObjectIds.RazorHarpoon4: + RazorHarpoonGUIDs[3] = gameObject.GetGUID(); + break; + case InstanceGameObjectIds.MoleMachine: + if (GetBossState(BossIds.Razorscale) == EncounterState.InProgress) + gameObject.SetGoState(GameObjectState.Active); + break; + case InstanceGameObjectIds.BrainRoomDoor1: + BrainRoomDoorGUIDs[0] = gameObject.GetGUID(); + break; + case InstanceGameObjectIds.BrainRoomDoor2: + BrainRoomDoorGUIDs[1] = gameObject.GetGUID(); + break; + case InstanceGameObjectIds.BrainRoomDoor3: + BrainRoomDoorGUIDs[2] = gameObject.GetGUID(); + break; + case InstanceGameObjectIds.CelestialPlanetariumAccess10: + case InstanceGameObjectIds.CelestialPlanetariumAccess25: + if (_algalonSummoned) + gameObject.SetFlag(GameObjectFields.Flags, GameObjectFlags.InUse); + break; + case InstanceGameObjectIds.DoodadUlSigildoor01: + AlgalonSigilDoorGUID[0] = gameObject.GetGUID(); + if (_algalonSummoned) + gameObject.SetGoState(GameObjectState.Active); + break; + case InstanceGameObjectIds.DoodadUlSigildoor02: + AlgalonSigilDoorGUID[1] = gameObject.GetGUID(); + if (_algalonSummoned) + gameObject.SetGoState(GameObjectState.Active); + break; + case InstanceGameObjectIds.DoodadUlSigildoor03: + AlgalonSigilDoorGUID[2] = gameObject.GetGUID(); + AddDoor(gameObject, true); + break; + case InstanceGameObjectIds.DoodadUlUniversefloor01: + AlgalonFloorGUID[0] = gameObject.GetGUID(); + AddDoor(gameObject, true); + break; + case InstanceGameObjectIds.DoodadUlUniversefloor02: + AlgalonFloorGUID[1] = gameObject.GetGUID(); + AddDoor(gameObject, true); + break; + case InstanceGameObjectIds.DoodadUlUniverseglobe01: + AlgalonUniverseGUID = gameObject.GetGUID(); + AddDoor(gameObject, true); + break; + case InstanceGameObjectIds.DoodadUlUlduarTrapdoor03: + AlgalonTrapdoorGUID = gameObject.GetGUID(); + AddDoor(gameObject, true); + break; + case InstanceGameObjectIds.GiftOfTheObserver10: + case InstanceGameObjectIds.GiftOfTheObserver25: + GiftOfTheObserverGUID = gameObject.GetGUID(); + break; + default: + break; + } + } + + public override void OnGameObjectRemove(GameObject gameObject) + { + switch (gameObject.GetEntry()) + { + case InstanceGameObjectIds.LeviathanDoor: + case InstanceGameObjectIds.Xt002Door: + case InstanceGameObjectIds.IronCouncilDoor: + case InstanceGameObjectIds.ArchivumDoor: + case InstanceGameObjectIds.HodirEntrance: + case InstanceGameObjectIds.HodirDoor: + case InstanceGameObjectIds.HodirIceDoor: + case InstanceGameObjectIds.MimironDoor1: + case InstanceGameObjectIds.MimironDoor2: + case InstanceGameObjectIds.MimironDoor3: + case InstanceGameObjectIds.VezaxDoor: + case InstanceGameObjectIds.YoggSaronDoor: + case InstanceGameObjectIds.DoodadUlSigildoor01: + case InstanceGameObjectIds.DoodadUlUniversefloor01: + case InstanceGameObjectIds.DoodadUlUniversefloor02: + case InstanceGameObjectIds.DoodadUlUniverseglobe01: + case InstanceGameObjectIds.DoodadUlUlduarTrapdoor03: + AddDoor(gameObject, false); + break; + default: + break; + } + } + + public override void OnUnitDeath(Unit unit) + { + // Champion/Conqueror of Ulduar + if (unit.IsTypeId(TypeId.Player)) + { + for (byte i = 0; i < BossIds.Algalon; i++) + { + if (GetBossState(i) == EncounterState.InProgress) + { + _CoUAchivePlayerDeathMask |= (1u << i); + SaveToDB(); + } + } + } + + Creature creature = unit.ToCreature(); + if (!creature) + return; + + switch (creature.GetEntry()) + { + case InstanceCreatureIds.CorruptedServitor: + case InstanceCreatureIds.MisguidedNymph: + case InstanceCreatureIds.GuardianLasher: + case InstanceCreatureIds.ForestSwarmer: + case InstanceCreatureIds.MangroveEnt: + case InstanceCreatureIds.IronrootLasher: + case InstanceCreatureIds.NaturesBlade: + case InstanceCreatureIds.GuardianOfLife: + if (!conSpeedAtory) + { + DoStartCriteriaTimer(CriteriaTimedTypes.Event, InstanceCriteriaIds.ConSpeedAtory); + conSpeedAtory = true; + } + break; + case InstanceCreatureIds.Ironbranch: + case InstanceCreatureIds.Stonebark: + case InstanceCreatureIds.Brightleaf: + if (!lumberjacked) + { + DoStartCriteriaTimer(CriteriaTimedTypes.Event, InstanceCriteriaIds.Lumberjacked); + lumberjacked = true; + } + break; + default: + break; + } + } + + public override void ProcessEvent(WorldObject gameObject, uint eventId) + { + // Flame Leviathan's Tower Event triggers + Creature FlameLeviathan = instance.GetCreature(LeviathanGUID); + + switch (eventId) + { + case LeviathanActions.TowerOfStormDestroyed: + if (FlameLeviathan && FlameLeviathan.IsAlive()) + FlameLeviathan.GetAI().DoAction(LeviathanActions.TowerOfStormDestroyed); + break; + case LeviathanActions.TowerOfFrostDestroyed: + if (FlameLeviathan && FlameLeviathan.IsAlive()) + FlameLeviathan.GetAI().DoAction(LeviathanActions.TowerOfFrostDestroyed); + break; + case LeviathanActions.TowerOfFlamesDestroyed: + if (FlameLeviathan && FlameLeviathan.IsAlive()) + FlameLeviathan.GetAI().DoAction(LeviathanActions.TowerOfFlamesDestroyed); + break; + case LeviathanActions.TowerOfLifeDestroyed: + if (FlameLeviathan && FlameLeviathan.IsAlive()) + FlameLeviathan.GetAI().DoAction(LeviathanActions.TowerOfLifeDestroyed); + break; + case InstanceEventIds.ActivateSanityWell: + Creature freya = instance.GetCreature(KeeperGUIDs[0]); + if (freya) + freya.GetAI().DoAction(4/*ACTION_SANITY_WELLS*/); + break; + case InstanceEventIds.HodirsProtectiveGazeProc: + Creature hodir = instance.GetCreature(KeeperGUIDs[1]); + if (hodir) + hodir.GetAI().DoAction(5/*ACTION_FLASH_FREEZE*/); + break; + } + } + + public override bool SetBossState(uint type, EncounterState state) + { + if (!base.SetBossState(type, state)) + return false; + + switch (type) + { + case BossIds.Leviathan: + if (state == EncounterState.Done) + { + // Eject all players from vehicles and make them untargetable. + // They will be despawned after a while + foreach (var vehicleGuid in LeviathanVehicleGUIDs) + { + Creature vehicleCreature = instance.GetCreature(vehicleGuid); + if (vehicleCreature != null) + { + Vehicle vehicle = vehicleCreature.GetVehicleKit(); + if (vehicle != null) + { + vehicle.RemoveAllPassengers(); + vehicleCreature.SetFlag(UnitFields.Flags, UnitFlags.NotSelectable); + vehicleCreature.DespawnOrUnsummon(5 * Time.Minute * Time.InMilliseconds); + } + } + } + } + break; + case BossIds.Ignis: + case BossIds.Razorscale: + case BossIds.Xt002: + case BossIds.AssemblyOfIron: + case BossIds.Auriaya: + case BossIds.Vezax: + case BossIds.YoggSaron: + break; + case BossIds.Mimiron: + if (state == EncounterState.Done) + instance.SummonCreature(InstanceCreatureIds.MimironObservationRing, YoggSaron.ObservationRingKeepersPos[3]); + break; + case BossIds.Freya: + if (state == EncounterState.Done) + instance.SummonCreature(InstanceCreatureIds.FreyaObservationRing, YoggSaron.ObservationRingKeepersPos[0]); + break; + case BossIds.Kologarn: + if (state == EncounterState.Done) + { + GameObject gameObject = instance.GetGameObject(KologarnChestGUID); + if (gameObject) + { + gameObject.SetRespawnTime((int)gameObject.GetRespawnDelay()); + gameObject.RemoveFlag(GameObjectFields.Flags, GameObjectFlags.NotSelectable); + } + HandleGameObject(KologarnBridgeGUID, false); + } + break; + case BossIds.Hodir: + if (state == EncounterState.Done) + { + GameObject HodirRareCache = instance.GetGameObject(HodirRareCacheGUID); + if (HodirRareCache) + if (GetData(InstanceData.HodirRareCache) != 0) + HodirRareCache.RemoveFlag(GameObjectFields.Flags, GameObjectFlags.NotSelectable); + GameObject HodirChest = instance.GetGameObject(HodirChestGUID); + if (HodirChest) + HodirChest.SetRespawnTime((int)HodirChest.GetRespawnDelay()); + + instance.SummonCreature(InstanceCreatureIds.HodirObservationRing, YoggSaron.ObservationRingKeepersPos[1]); + } + break; + case BossIds.Thorim: + if (state == EncounterState.Done) + { + GameObject gameObject = instance.GetGameObject(ThorimChestGUID); + if (gameObject) + gameObject.SetRespawnTime((int)gameObject.GetRespawnDelay()); + + instance.SummonCreature(InstanceCreatureIds.ThorimObservationRing, YoggSaron.ObservationRingKeepersPos[2]); + } + break; + case BossIds.Algalon: + if (state == EncounterState.Done) + { + _events.CancelEvent(InstanceEvents.EventUpdateAlgalonTimer); + _events.CancelEvent(InstanceEvents.EventDespawnAlgalon); + DoUpdateWorldState(InstanceWorldStates.AlgalonTimerEnabled, 0); + _algalonTimer = 61; + GameObject gameObject = instance.GetGameObject(GiftOfTheObserverGUID); + if (gameObject) + gameObject.SetRespawnTime((int)gameObject.GetRespawnDelay()); + // get item level (recheck weapons) + var players = instance.GetPlayers(); + foreach (var player in players) + { + for (byte slot = EquipmentSlot.MainHand; slot <= EquipmentSlot.OffHand; ++slot) + { + Item item = player.GetItemByPos(InventorySlots.Bag0, slot); + if (item) + if (item.GetTemplate().GetBaseItemLevel() > _maxWeaponItemLevel) + _maxWeaponItemLevel = item.GetTemplate().GetBaseItemLevel(); + } + } + } + else if (state == EncounterState.InProgress) + { + // get item level (armor cannot be swapped in combat) + var players = instance.GetPlayers(); + foreach (var player in players) + { + for (byte slot = EquipmentSlot.Start; slot < EquipmentSlot.End; ++slot) + { + if (slot == EquipmentSlot.Tabard || slot == EquipmentSlot.Cloak) + continue; + + Item item = player.GetItemByPos(InventorySlots.Bag0, slot); + if (item) + { + if (slot >= EquipmentSlot.MainHand && slot <= EquipmentSlot.OffHand) + { + if (item.GetTemplate().GetBaseItemLevel() > _maxWeaponItemLevel) + _maxWeaponItemLevel = item.GetTemplate().GetBaseItemLevel(); + } + else if (item.GetTemplate().GetBaseItemLevel() > _maxArmorItemLevel) + _maxArmorItemLevel = item.GetTemplate().GetBaseItemLevel(); + } + } + + } + } + break; + } + + return true; + } + + public override void SetData(uint type, uint data) + { + switch (type) + { + case InstanceData.Colossus: + ColossusData = data; + if (data == 2) + { + Creature Leviathan = instance.GetCreature(LeviathanGUID); + if (Leviathan) + Leviathan.GetAI().DoAction(LeviathanActions.MoveToCenterPosition); + GameObject gameObject = instance.GetGameObject(LeviathanGateGUID); + if (gameObject) + gameObject.SetGoState(GameObjectState.ActiveAlternative); + SaveToDB(); + } + break; + case InstanceData.HodirRareCache: + HodirRareCacheData = data; + if (HodirRareCacheData == 0) + { + Creature Hodir = instance.GetCreature(HodirGUID); + if (Hodir) + { + GameObject gameObject = instance.GetGameObject(HodirRareCacheGUID); + if (gameObject) + Hodir.RemoveGameObject(gameObject, false); + } + } + break; + case InstanceAchievementData.DataUnbroken: + Unbroken = data != 0; + break; + case InstanceData.Illusion: + illusion = (byte)data; + break; + case InstanceData.DriveMeCrazy: + IsDriveMeCrazyEligible = data != 0 ? true : false; + break; + case InstanceEvents.EventDespawnAlgalon: + DoUpdateWorldState(InstanceWorldStates.AlgalonTimerEnabled, 1); + DoUpdateWorldState(InstanceWorldStates.AlgalonDespawnTimer, 60); + _algalonTimer = 60; + _events.ScheduleEvent(InstanceEvents.EventDespawnAlgalon, 3600000); + _events.ScheduleEvent(InstanceEvents.EventUpdateAlgalonTimer, 60000); + break; + case InstanceData.AlgalonSummonState: + _algalonSummoned = true; + break; + default: + break; + } + } + + public override void SetGuidData(uint type, ObjectGuid data) { } + + public override ObjectGuid GetGuidData(uint data) + { + switch (data) + { + case BossIds.Leviathan: + return LeviathanGUID; + case BossIds.Ignis: + return IgnisGUID; + + // Razorscale + case BossIds.Razorscale: + return RazorscaleGUID; + case InstanceData.RazorscaleControl: + return RazorscaleController; + case InstanceData.ExpeditionCommander: + return ExpeditionCommanderGUID; + case InstanceGameObjectIds.RazorHarpoon1: + return RazorHarpoonGUIDs[0]; + case InstanceGameObjectIds.RazorHarpoon2: + return RazorHarpoonGUIDs[1]; + case InstanceGameObjectIds.RazorHarpoon3: + return RazorHarpoonGUIDs[2]; + case InstanceGameObjectIds.RazorHarpoon4: + return RazorHarpoonGUIDs[3]; + + // XT-002 Deconstructor + case BossIds.Xt002: + return XT002GUID; + case InstanceData.ToyPile0: + case InstanceData.ToyPile1: + case InstanceData.ToyPile2: + case InstanceData.ToyPile3: + return XTToyPileGUIDs[data - InstanceData.ToyPile0]; + + // Assembly of Iron + case InstanceData.Steelbreaker: + return AssemblyGUIDs[0]; + case InstanceData.Molgeim: + return AssemblyGUIDs[1]; + case InstanceData.Brundir: + return AssemblyGUIDs[2]; + + case BossIds.Kologarn: + return KologarnGUID; + case BossIds.Auriaya: + return AuriayaGUID; + case BossIds.Hodir: + return HodirGUID; + case BossIds.Thorim: + return ThorimGUID; + + // Freya + case BossIds.Freya: + return FreyaGUID; + case BossIds.Brightleaf: + return ElderGUIDs[0]; + case BossIds.Ironbranch: + return ElderGUIDs[1]; + case BossIds.Stonebark: + return ElderGUIDs[2]; + + // Mimiron + case BossIds.Mimiron: + return MimironGUID; + case InstanceData.LeviathanMKII: + return MimironVehicleGUIDs[0]; + case InstanceData.VX001: + return MimironVehicleGUIDs[1]; + case InstanceData.AerialCommandUnit: + return MimironVehicleGUIDs[2]; + case InstanceData.Computer: + return MimironComputerGUID; + case InstanceData.MimironWorldTrigger: + return MimironWorldTriggerGUID; + case InstanceData.MimironElevator: + return MimironElevatorGUID; + case InstanceData.MimironButton: + return MimironButtonGUID; + + case BossIds.Vezax: + return VezaxGUID; + + // Yogg-Saron + case BossIds.YoggSaron: + return YoggSaronGUID; + case InstanceData.VoiceOfYoggSaron: + return VoiceOfYoggSaronGUID; + case InstanceData.BrainOfYoggSaron: + return BrainOfYoggSaronGUID; + case InstanceData.Sara: + return SaraGUID; + case InstanceGameObjectIds.BrainRoomDoor1: + return BrainRoomDoorGUIDs[0]; + case InstanceGameObjectIds.BrainRoomDoor2: + return BrainRoomDoorGUIDs[1]; + case InstanceGameObjectIds.BrainRoomDoor3: + return BrainRoomDoorGUIDs[2]; + case InstanceData.FreyaYs: + return KeeperGUIDs[0]; + case InstanceData.HodirYs: + return KeeperGUIDs[1]; + case InstanceData.ThorimYs: + return KeeperGUIDs[2]; + case InstanceData.MimironYs: + return KeeperGUIDs[3]; + + // Algalon + case BossIds.Algalon: + return AlgalonGUID; + case InstanceData.Sigildoor01: + return AlgalonSigilDoorGUID[0]; + case InstanceData.Sigildoor02: + return AlgalonSigilDoorGUID[1]; + case InstanceData.Sigildoor03: + return AlgalonSigilDoorGUID[2]; + case InstanceData.UniverseFloor01: + return AlgalonFloorGUID[0]; + case InstanceData.UniverseFloor02: + return AlgalonFloorGUID[1]; + case InstanceData.UniverseGlobe: + return AlgalonUniverseGUID; + case InstanceData.AlgalonTrapdoor: + return AlgalonTrapdoorGUID; + case InstanceData.BrannBronzebeardAlg: + return BrannBronzebeardAlgGUID; + } + + return ObjectGuid.Empty; + } + + public override uint GetData(uint type) + { + switch (type) + { + case InstanceData.Colossus: + return ColossusData; + case InstanceData.HodirRareCache: + return HodirRareCacheData; + case InstanceAchievementData.DataUnbroken: + return (uint)(Unbroken ? 1 : 0); + case InstanceData.Illusion: + return illusion; + case InstanceData.KeepersCount: + return keepersCount; + default: + break; + } + + return 0; + } + + public override bool CheckAchievementCriteriaMeet(uint criteriaId, Player source, Unit target = null, uint miscvalue1 = 0) + { + switch (criteriaId) + { + case InstanceCriteriaIds.HeraldOfTitans: + return _maxArmorItemLevel <= InstanceAchievementData.MaxHeraldArmorItemlevel && _maxWeaponItemLevel <= InstanceAchievementData.MaxHeraldWeaponItemlevel; + case InstanceCriteriaIds.WaitsDreamingStormwind10: + case InstanceCriteriaIds.WaitsDreamingStormwind25: + return illusion == YoggSaronIllusions.StormwindIllusion; + case InstanceCriteriaIds.WaitsDreamingChamber10: + case InstanceCriteriaIds.WaitsDreamingChamber25: + return illusion == YoggSaronIllusions.ChamberIllusion; + case InstanceCriteriaIds.WaitsDreamingIcecrown10: + case InstanceCriteriaIds.WaitsDreamingIcecrown25: + return illusion == YoggSaronIllusions.IcecrownIllusion; + case InstanceCriteriaIds.DriveMeCrazy10: + case InstanceCriteriaIds.DriveMeCrazy25: + return IsDriveMeCrazyEligible; + case InstanceCriteriaIds.ThreeLightsInTheDarkness10: + case InstanceCriteriaIds.ThreeLightsInTheDarkness25: + return keepersCount <= 3; + case InstanceCriteriaIds.TwoLightsInTheDarkness10: + case InstanceCriteriaIds.TwoLightsInTheDarkness25: + return keepersCount <= 2; + case InstanceCriteriaIds.OneLightInTheDarkness10: + case InstanceCriteriaIds.OneLightInTheDarkness25: + return keepersCount <= 1; + case InstanceCriteriaIds.AloneInTheDarkness10: + case InstanceCriteriaIds.AloneInTheDarkness25: + return keepersCount == 0; + case InstanceCriteriaIds.ChampionLeviathan10: + case InstanceCriteriaIds.ChampionLeviathan25: + return (_CoUAchivePlayerDeathMask & (1 << (int)BossIds.Leviathan)) == 0; + case InstanceCriteriaIds.ChampionIgnis10: + case InstanceCriteriaIds.ChampionIgnis25: + return (_CoUAchivePlayerDeathMask & (1 << (int)BossIds.Ignis)) == 0; + case InstanceCriteriaIds.ChampionRazorscale10: + case InstanceCriteriaIds.ChampionRazorscale25: + return (_CoUAchivePlayerDeathMask & (1 << (int)BossIds.Razorscale)) == 0; + case InstanceCriteriaIds.ChampionXt002_10: + case InstanceCriteriaIds.ChampionXt002_25: + return (_CoUAchivePlayerDeathMask & (1 << (int)BossIds.Xt002)) == 0; + case InstanceCriteriaIds.ChampionIronCouncil10: + case InstanceCriteriaIds.ChampionIronCouncil25: + return (_CoUAchivePlayerDeathMask & (1 << (int)BossIds.AssemblyOfIron)) == 0; + case InstanceCriteriaIds.ChampionKologarn10: + case InstanceCriteriaIds.ChampionKologarn25: + return (_CoUAchivePlayerDeathMask & (1 << (int)BossIds.Kologarn)) == 0; + case InstanceCriteriaIds.ChampionAuriaya10: + case InstanceCriteriaIds.ChampionAuriaya25: + return (_CoUAchivePlayerDeathMask & (1 << (int)BossIds.Auriaya)) == 0; + case InstanceCriteriaIds.ChampionHodir10: + case InstanceCriteriaIds.ChampionHodir25: + return (_CoUAchivePlayerDeathMask & (1 << (int)BossIds.Hodir)) == 0; + case InstanceCriteriaIds.ChampionThorim10: + case InstanceCriteriaIds.ChampionThorim25: + return (_CoUAchivePlayerDeathMask & (1 << (int)BossIds.Thorim)) == 0; + case InstanceCriteriaIds.ChampionFreya10: + case InstanceCriteriaIds.ChampionFreya25: + return (_CoUAchivePlayerDeathMask & (1 << (int)BossIds.Freya)) == 0; + case InstanceCriteriaIds.ChampionMimiron10: + case InstanceCriteriaIds.ChampionMimiron25: + return (_CoUAchivePlayerDeathMask & (1 << (int)BossIds.Mimiron)) == 0; + case InstanceCriteriaIds.ChampionVezax10: + case InstanceCriteriaIds.ChampionVezax25: + return (_CoUAchivePlayerDeathMask & (1 << (int)BossIds.Vezax)) == 0; + case InstanceCriteriaIds.ChampionYoggSaron10: + case InstanceCriteriaIds.ChampionYoggSaron25: + return (_CoUAchivePlayerDeathMask & (1 << (int)BossIds.YoggSaron)) == 0; + } + + return false; + } + + public override void WriteSaveDataMore(StringBuilder data) + { + data.AppendFormat("{0} {1} {2}", GetData(InstanceData.Colossus), _algalonTimer, (_algalonSummoned ? 1 : 0)); + + for (byte i = 0; i < 4; ++i) + data.AppendFormat(" {0}", (KeeperGUIDs[i].IsEmpty() ? 0 : 1)); + + data.AppendFormat(" {0}", _CoUAchivePlayerDeathMask); + } + + public override void ReadSaveDataMore(StringArguments data) + { + EncounterState tempState = (EncounterState)data.NextUInt32(); + if (tempState == EncounterState.InProgress || tempState > EncounterState.Special) + tempState = EncounterState.NotStarted; + SetData(InstanceData.Colossus, (uint)tempState); + + _algalonTimer = data.NextUInt32(); + tempState = (EncounterState)data.NextUInt32(); + _algalonSummoned = tempState != 0; + if (_algalonSummoned && GetBossState(BossIds.Algalon) != EncounterState.Done) + { + _summonAlgalon = true; + if (_algalonTimer != 0 && _algalonTimer <= 60) + { + _events.ScheduleEvent(InstanceEvents.EventUpdateAlgalonTimer, 60000); + DoUpdateWorldState(InstanceWorldStates.AlgalonTimerEnabled, 1); + DoUpdateWorldState(InstanceWorldStates.AlgalonDespawnTimer, _algalonTimer); + } + } + + for (byte i = 0; i < 4; ++i) + { + tempState = (EncounterState)data.NextUInt32(); + _summonYSKeeper[i] = tempState != 0; + } + + if (GetBossState(BossIds.Freya) == EncounterState.Done && !_summonYSKeeper[0]) + _summonObservationRingKeeper[0] = true; + if (GetBossState(BossIds.Hodir) == EncounterState.Done && !_summonYSKeeper[1]) + _summonObservationRingKeeper[1] = true; + if (GetBossState(BossIds.Thorim) == EncounterState.Done && !_summonYSKeeper[2]) + _summonObservationRingKeeper[2] = true; + if (GetBossState(BossIds.Mimiron) == EncounterState.Done && !_summonYSKeeper[3]) + _summonObservationRingKeeper[3] = true; + + _CoUAchivePlayerDeathMask = data.NextUInt32(); + } + + public override void Update(uint diff) + { + if (_events.Empty()) + return; + + _events.Update(diff); + + _events.ExecuteEvents(eventId => + { + switch (eventId) + { + case InstanceEvents.EventUpdateAlgalonTimer: + SaveToDB(); + DoUpdateWorldState(InstanceWorldStates.AlgalonDespawnTimer, --_algalonTimer); + if (_algalonTimer != 0) + _events.ScheduleEvent(InstanceEvents.EventUpdateAlgalonTimer, 60000); + else + { + DoUpdateWorldState(InstanceWorldStates.AlgalonTimerEnabled, 0); + _events.CancelEvent(InstanceEvents.EventUpdateAlgalonTimer); + Creature algalon = instance.GetCreature(AlgalonGUID); + if (algalon) + algalon.GetAI().DoAction((int)InstanceEvents.EventDespawnAlgalon); + } + break; + } + }); + } + + public override void UpdateDoorState(GameObject door) + { + // Leviathan doors are set to DOOR_TYPE_ROOM except the one it uses to enter the room + // which has to be set to DOOR_TYPE_PASSAGE + if (door.GetEntry() == InstanceGameObjectIds.LeviathanDoor && door.GetPositionX() > 400.0f) + door.SetGoState(GetBossState(BossIds.Leviathan) == EncounterState.Done ? GameObjectState.Active : GameObjectState.Ready); + else + base.UpdateDoorState(door); + } + + public override void AddDoor(GameObject door, bool add) + { + // Leviathan doors are South except the one it uses to enter the room + // which is North and should not be used for boundary checks in BossAI.CheckBoundary() + if (door.GetEntry() == InstanceGameObjectIds.LeviathanDoor && door.GetPositionX() > 400.0f) + { + if (add) + GetBossInfo(BossIds.Leviathan).door[(int)DoorType.Passage].Add(door.GetGUID()); + else + GetBossInfo(BossIds.Leviathan).door[(int)DoorType.Passage].Remove(door.GetGUID()); + + if (add) + UpdateDoorState(door); + } + else + base.AddDoor(door, add); + } + + BossBoundaryEntry[] boundaries = + { + new BossBoundaryEntry(BossIds.Leviathan, new RectangleBoundary(148.0f, 401.3f, -155.0f, 90.0f) ), + new BossBoundaryEntry(BossIds.Ignis, new RectangleBoundary(495.0f, 680.0f, 90.0f, 400.0f) ), + new BossBoundaryEntry(BossIds.Razorscale, new RectangleBoundary(370.0f, 810.0f, -542.0f, -55.0f)), + new BossBoundaryEntry(BossIds.Xt002, new RectangleBoundary(755.0f, 940.0f, -125.0f, 95.0f)), + new BossBoundaryEntry(BossIds.AssemblyOfIron, new CircleBoundary(new Position(1587.2f, 121.0f), 90.0)), + new BossBoundaryEntry(BossIds.Algalon, new CircleBoundary(new Position(1632.668f, -307.7656f), 45.0)), + new BossBoundaryEntry(BossIds.Algalon, new ZRangeBoundary(410.0f, 440.0f)), + new BossBoundaryEntry(BossIds.Hodir, new EllipseBoundary(new Position(2001.5f, -240.0f), 50.0, 75.0)), + new BossBoundaryEntry(BossIds.Thorim, new CircleBoundary(new Position(2134.73f, -263.2f), 50.0)), + new BossBoundaryEntry(BossIds.Freya, new RectangleBoundary(2094.6f, 2520.0f, -250.0f, 200.0f)), + new BossBoundaryEntry(BossIds.Mimiron, new CircleBoundary(new Position(2744.0f, 2569.0f), 70.0)), + new BossBoundaryEntry(BossIds.Vezax, new RectangleBoundary(1740.0f, 1930.0f, 31.0f, 228.0f)), + new BossBoundaryEntry(BossIds.YoggSaron, new CircleBoundary(new Position(1980.42f, -27.68f), 105.0)) + }; + + DoorData[] doorData = + { + new DoorData(InstanceGameObjectIds.LeviathanDoor, BossIds.Leviathan, DoorType.Room), + new DoorData(InstanceGameObjectIds.Xt002Door, BossIds.Xt002, DoorType.Room), + new DoorData(InstanceGameObjectIds.IronCouncilDoor, BossIds.AssemblyOfIron, DoorType.Room), + new DoorData(InstanceGameObjectIds.ArchivumDoor, BossIds.AssemblyOfIron, DoorType.Passage), + new DoorData(InstanceGameObjectIds.HodirEntrance, BossIds.Hodir, DoorType.Room), + new DoorData(InstanceGameObjectIds.HodirDoor, BossIds.Hodir, DoorType.Passage), + new DoorData(InstanceGameObjectIds.HodirIceDoor, BossIds.Hodir, DoorType.Passage), + new DoorData(InstanceGameObjectIds.MimironDoor1, BossIds.Mimiron, DoorType.Room), + new DoorData(InstanceGameObjectIds.MimironDoor2, BossIds.Mimiron, DoorType.Room), + new DoorData(InstanceGameObjectIds.MimironDoor3, BossIds.Mimiron, DoorType.Room), + new DoorData(InstanceGameObjectIds.VezaxDoor, BossIds.Vezax, DoorType.Passage), + new DoorData(InstanceGameObjectIds.YoggSaronDoor, BossIds.YoggSaron, DoorType.Room), + new DoorData(InstanceGameObjectIds.DoodadUlSigildoor03, BossIds.Algalon, DoorType.Room), + new DoorData(InstanceGameObjectIds.DoodadUlUniversefloor01, BossIds.Algalon, DoorType.Room), + new DoorData(InstanceGameObjectIds.DoodadUlUniversefloor02, BossIds.Algalon, DoorType.SpawnHole), + new DoorData(InstanceGameObjectIds.DoodadUlUniverseglobe01, BossIds.Algalon, DoorType.SpawnHole), + new DoorData(InstanceGameObjectIds.DoodadUlUlduarTrapdoor03, BossIds.Algalon, DoorType.SpawnHole), + new DoorData(0, 0, DoorType.Room) + }; + + MinionData[] minionData = + { + new MinionData(InstanceCreatureIds.Steelbreaker, BossIds.AssemblyOfIron), + new MinionData(InstanceCreatureIds.Molgeim, BossIds.AssemblyOfIron), + new MinionData(InstanceCreatureIds.Brundir, BossIds.AssemblyOfIron), + new MinionData(0, 0 ) + }; + + // Creatures + ObjectGuid LeviathanGUID; + List LeviathanVehicleGUIDs = new List(); + ObjectGuid IgnisGUID; + ObjectGuid RazorscaleGUID; + ObjectGuid RazorscaleController; + ObjectGuid ExpeditionCommanderGUID; + ObjectGuid XT002GUID; + ObjectGuid[] XTToyPileGUIDs = new ObjectGuid[4]; + ObjectGuid[] AssemblyGUIDs = new ObjectGuid[3]; + ObjectGuid KologarnGUID; + ObjectGuid AuriayaGUID; + ObjectGuid HodirGUID; + ObjectGuid ThorimGUID; + ObjectGuid FreyaGUID; + ObjectGuid[] ElderGUIDs = new ObjectGuid[3]; + ObjectGuid FreyaAchieveTriggerGUID; + ObjectGuid MimironGUID; + ObjectGuid[] MimironVehicleGUIDs = new ObjectGuid[3]; + ObjectGuid MimironComputerGUID; + ObjectGuid MimironWorldTriggerGUID; + ObjectGuid VezaxGUID; + ObjectGuid YoggSaronGUID; + ObjectGuid VoiceOfYoggSaronGUID; + ObjectGuid SaraGUID; + ObjectGuid BrainOfYoggSaronGUID; + ObjectGuid[] KeeperGUIDs = new ObjectGuid[4]; + ObjectGuid AlgalonGUID; + ObjectGuid BrannBronzebeardAlgGUID; + + // GameObjects + ObjectGuid LeviathanGateGUID; + ObjectGuid[] RazorHarpoonGUIDs = new ObjectGuid[4]; + ObjectGuid KologarnChestGUID; + ObjectGuid KologarnBridgeGUID; + ObjectGuid ThorimChestGUID; + ObjectGuid HodirRareCacheGUID; + ObjectGuid HodirChestGUID; + ObjectGuid MimironTramGUID; + ObjectGuid MimironElevatorGUID; + ObjectGuid MimironButtonGUID; + ObjectGuid[] BrainRoomDoorGUIDs = new ObjectGuid[3]; + ObjectGuid[] AlgalonSigilDoorGUID = new ObjectGuid[3]; + ObjectGuid[] AlgalonFloorGUID = new ObjectGuid[2]; + ObjectGuid AlgalonUniverseGUID; + ObjectGuid AlgalonTrapdoorGUID; + ObjectGuid GiftOfTheObserverGUID; + + // Miscellaneous + Team TeamInInstance; + uint HodirRareCacheData; + uint ColossusData; + //byte elderCount; + byte illusion; + byte keepersCount; + bool conSpeedAtory; + bool lumberjacked; + bool Unbroken; + bool IsDriveMeCrazyEligible; + + uint _algalonTimer; + bool _summonAlgalon; + bool _algalonSummoned; + bool[] _summonObservationRingKeeper = new bool[4]; + bool[] _summonYSKeeper = new bool[4]; + uint _maxArmorItemLevel; + uint _maxWeaponItemLevel; + uint _CoUAchivePlayerDeathMask; + } + + public override InstanceScript GetInstanceScript(InstanceMap map) + { + return new instance_ulduar_InstanceMapScript(map); + } + + public static T GetUlduarInstanceAI(WorldObject obj) where T : CreatureAI + { + return GetInstanceAI(obj, "instance_ulduar"); + } + } +} diff --git a/Scripts/Northrend/Ulduar/Xt002.cs b/Scripts/Northrend/Ulduar/Xt002.cs new file mode 100644 index 000000000..6b24186f2 --- /dev/null +++ b/Scripts/Northrend/Ulduar/Xt002.cs @@ -0,0 +1,1024 @@ +/* + * Copyright (C) 2012-2017 CypherCore + * + * 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, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +using Framework.Constants; +using Framework.Dynamic; +using Game.AI; +using Game.Entities; +using Game.Maps; +using Game.Scripting; +using Game.Spells; +using System; +using System.Collections.Generic; + +namespace Scripts.Northrend.Ulduar.Xt002 +{ + struct SpellIds + { + public const uint TympanicTantrum = 62776; + public const uint SearingLight = 63018; + + public const uint SummonLifeSpark = 64210; + public const uint SummonVoidZone = 64203; + + public const uint GravityBomb = 63024; + + public const uint Heartbreak = 65737; + + // Cast By 33337 At Heartbreak: + public const uint RechargePummeler = 62831; // Summons 33344 + public const uint RechargeScrapbot = 62828; // Summons 33343 + public const uint RechargeBoombot = 62835; // Summons 33346 + + // Cast By 33329 On 33337 (Visual?) + public const uint EnergyOrb = 62790; // Triggers 62826 - Needs Spellscript For Periodic Tick To Cast One Of The Random Spells Above + + public const uint HeartHealToFull = 17683; + public const uint HeartOverload = 62789; + + public const uint HeartLightningTether = 64799; // Cast On Self? + public const uint Enrage = 26662; + public const uint Stand = 37752; + public const uint Submerge = 37751; + + //------------------Void Zone-------------------- + public const uint VoidZone = 64203; + public const uint Consumption = 64208; + + // Life Spark + public const uint ArcanePowerState = 49411; + public const uint StaticCharged = 64227; + public const uint Shock = 64230; + + //----------------Xt-002 Heart------------------- + public const uint ExposedHeart = 63849; + public const uint HeartRideVehicle = 63852; + public const uint RideVehicleExposed = 63313; //Heart Exposed + + //---------------Xm-024 Pummeller---------------- + public const uint ArcingSmash = 8374; + public const uint Trample = 5568; + public const uint Uppercut = 10966; + + // Scrabot: + public const uint ScrapbotRideVehicle = 47020; + public const uint ScrapRepair = 62832; + public const uint Suicide = 7; + + //------------------Boombot----------------------- + public const uint AuraBoombot = 65032; + public const uint Boom = 62834; + + // Achievement-Related Spells + public const uint AchievementCreditNerfScrapbots = 65037; + } + + struct XT002Data + { + public const uint TransferedHealth = 0; + public const uint HardMode = 1; + public const uint HealthRecovered = 2; + public const uint GravityBombCasualty = 3; + } + + struct Texts + { + public const uint Aggro = 0; + public const uint HeartOpened = 1; + public const uint HeartClosed = 2; + public const uint TympanicTantrum = 3; + public const uint Slay = 4; + public const uint Berserk = 5; + public const uint Death = 6; + public const uint Summon = 7; + public const uint EmoteHeartOpened = 8; + public const uint EmoteHeartClosed = 9; + public const uint EmoteTympanicTantrum = 10; + public const uint EmoteScrapbot = 11; + } + + struct Misc + { + public const uint PhaseOneGroup = 1; + public const int ActionHardMode = 1; + public const uint AchievMustDeconstructFaster = 21027; + + public const sbyte SeatHeartNormal = 0; + public const sbyte SeatHeartExposed = 1; + } + + [Script] + class boss_xt002 : CreatureScript + { + public boss_xt002() : base("boss_xt002") { } + + class boss_xt002_AI : BossAI + { + public boss_xt002_AI(Creature creature) : base(creature, BossIds.Xt002) + { + Initialize(); + _transferHealth = 0; + } + + void Initialize() + { + _healthRecovered = false; + _gravityBombCasualty = false; + _hardMode = false; + + _heartExposed = 0; + } + + public override void Reset() + { + _Reset(); + + me.RemoveFlag(UnitFields.Flags, UnitFlags.NotSelectable); + me.SetReactState(ReactStates.Aggressive); + DoCastSelf(SpellIds.Stand); + + Initialize(); + + instance.DoStopCriteriaTimer(CriteriaTimedTypes.Event, Misc.AchievMustDeconstructFaster); + } + + public override void EnterCombat(Unit who) + { + Talk(Texts.Aggro); + _EnterCombat(); + + //Enrage + _scheduler.Schedule(TimeSpan.FromMinutes(10), task => + { + Talk(Texts.Berserk); + DoCastSelf(SpellIds.Enrage); + }); + + //Gavity Bomb + _scheduler.Schedule(TimeSpan.FromSeconds(20), Misc.PhaseOneGroup, task => + { + Unit target = SelectTarget(SelectAggroTarget.Random, 0); + if (target) + DoCast(target, SpellIds.GravityBomb); + + task.Repeat(TimeSpan.FromSeconds(20)); + }); + + //Searing Light + _scheduler.Schedule(TimeSpan.FromSeconds(20), Misc.PhaseOneGroup, task => + { + Unit target = SelectTarget(SelectAggroTarget.Random, 0); + if (target) + DoCast(target, SpellIds.SearingLight); + + task.Repeat(TimeSpan.FromSeconds(20)); + }); + + //Tympanic Tantrum + _scheduler.Schedule(TimeSpan.FromSeconds(30), Misc.PhaseOneGroup, task => + { + Talk(Texts.TympanicTantrum); + Talk(Texts.EmoteTympanicTantrum); + DoCast(SpellIds.TympanicTantrum); + task.Repeat(TimeSpan.FromSeconds(30)); + }); + + instance.DoStartCriteriaTimer(CriteriaTimedTypes.Event, Misc.AchievMustDeconstructFaster); + } + + public override void DoAction(int action) + { + switch (action) + { + case Misc.ActionHardMode: + _scheduler.Schedule(TimeSpan.FromMilliseconds(1), task => + { + SetPhaseOne(true); + }); + break; + } + } + + public override void KilledUnit(Unit who) + { + if (who.GetTypeId() == TypeId.Player) + Talk(Texts.Slay); + } + + public override void JustDied(Unit killer) + { + Talk(Texts.Death); + _JustDied(); + me.RemoveFlag(UnitFields.Flags, UnitFlags.NotSelectable); + } + + public override void DamageTaken(Unit attacker, ref uint damage) + { + if (!_hardMode && !me.HasReactState(ReactStates.Passive) && !HealthAbovePct(100 - 25 * (_heartExposed + 1))) + ExposeHeart(); + } + + public override void UpdateAI(uint diff) + { + if (!UpdateVictim()) + return; + + _scheduler.Update(diff); + + if (!me.HasReactState(ReactStates.Passive)) + DoMeleeAttackIfReady(); + } + + public override void PassengerBoarded(Unit who, sbyte seatId, bool apply) + { + if (apply && who.GetEntry() == InstanceCreatureIds.XS013Scrapbot) + { + // Need this so we can properly determine when to expose heart again in damagetaken hook + if (me.GetHealthPct() > (25 * (4 - _heartExposed))) + ++_heartExposed; + + Talk(Texts.EmoteScrapbot); + DoCast(who, SpellIds.ScrapRepair, true); + _healthRecovered = true; + } + + if (apply && seatId == Misc.SeatHeartExposed) + who.CastSpell(who, SpellIds.ExposedHeart); // Channeled + } + + public override uint GetData(uint type) + { + switch (type) + { + case XT002Data.HardMode: + return _hardMode ? 1 : 0u; + case XT002Data.HealthRecovered: + return _healthRecovered ? 1 : 0u; + case XT002Data.GravityBombCasualty: + return _gravityBombCasualty ? 1 : 0u; + } + + return 0; + } + + public override void SetData(uint type, uint data) + { + switch (type) + { + case XT002Data.TransferedHealth: + _transferHealth = data; + break; + case XT002Data.GravityBombCasualty: + _gravityBombCasualty = (data > 0) ? true : false; + break; + } + } + + void ExposeHeart() + { + Talk(Texts.HeartOpened); + Talk(Texts.EmoteHeartOpened); + + DoCastSelf(SpellIds.Submerge); // WIll make creature untargetable + me.AttackStop(); + me.SetReactState(ReactStates.Passive); + + Unit heart = me.GetVehicleKit() ? me.GetVehicleKit().GetPassenger(Misc.SeatHeartNormal) : null; + if (heart) + { + heart.CastSpell(heart, SpellIds.HeartOverload); + heart.CastSpell(me, SpellIds.HeartLightningTether); + heart.CastSpell(heart, SpellIds.HeartHealToFull, true); + heart.CastSpell(me, SpellIds.RideVehicleExposed, true); + heart.RemoveFlag(UnitFields.Flags, UnitFlags.NotSelectable); + heart.SetFlag(UnitFields.Flags, UnitFlags.Unk29); + } + _scheduler.DelayGroup(Misc.PhaseOneGroup, TimeSpan.FromSeconds(30)); + + // Start "end of phase 2 timer" + _scheduler.Schedule(TimeSpan.FromSeconds(30), task => { SetPhaseOne(false); }); + + _heartExposed++; + } + + void SetPhaseOne(bool isHardMode) + { + if (isHardMode) + { + me.SetFullHealth(); + DoCastSelf(SpellIds.Heartbreak, true); + me.AddLootMode(LootModes.HardMode1); + _hardMode = true; + } + + Talk(Texts.HeartClosed); + Talk(Texts.EmoteHeartClosed); + + me.RemoveFlag(UnitFields.Flags, UnitFlags.NotSelectable); + me.SetReactState(ReactStates.Aggressive); + DoCastSelf(SpellIds.Stand); + + //_events.RescheduleEvent(EVENT_SEARING_LIGHT, TIMER_SEARING_LIGHT / 2); + //_events.RescheduleEvent(EVENT_GRAVITY_BOMB, TIMER_GRAVITY_BOMB); + //_events.RescheduleEvent(EVENT_TYMPANIC_TANTRUM, RandomHelper.URand(TIMER_TYMPANIC_TANTRUM_MIN, TIMER_TYMPANIC_TANTRUM_MAX)); + + Unit heart = me.GetVehicleKit() ? me.GetVehicleKit().GetPassenger(Misc.SeatHeartExposed) : null; + if (!heart) + return; + + heart.CastSpell(me, SpellIds.HeartRideVehicle, true); + heart.SetFlag(UnitFields.Flags, UnitFlags.NotSelectable); + heart.RemoveFlag(UnitFields.Flags, UnitFlags.Unk29); + heart.RemoveAurasDueToSpell(SpellIds.ExposedHeart); + + if (!_hardMode) + { + if (_transferHealth == 0) + _transferHealth = (uint)(heart.GetMaxHealth() - heart.GetHealth()); + + if (_transferHealth >= me.GetHealth()) + _transferHealth = (uint)me.GetHealth() - 1; + + me.ModifyHealth(-(int)_transferHealth); + me.LowerPlayerDamageReq(_transferHealth); + } + } + + // Achievement related + bool _healthRecovered; // Did a scrapbot recover XT-002's health during the encounter? + bool _hardMode; // Are we in hard mode? Or: was the heart killed during phase 2? + bool _gravityBombCasualty; // Did someone die because of Gravity Bomb damage? + + byte _heartExposed; + uint _transferHealth; + } + + public override CreatureAI GetAI(Creature creature) + { + return instance_ulduar.GetUlduarInstanceAI(creature); + } + } + + [Script] + class npc_xt002_heart : CreatureScript + { + public npc_xt002_heart() : base("npc_xt002_heart") { } + + class npc_xt002_heartAI : ScriptedAI + { + public npc_xt002_heartAI(Creature creature) : base(creature) + { + _instance = creature.GetInstanceScript(); + + SetCombatMovement(false); + } + + public override void UpdateAI(uint diff) { } + + public override void JustDied(Unit killer) + { + Creature xt002 = _instance != null ? ObjectAccessor.GetCreature(me, _instance.GetGuidData(BossIds.Xt002)) : null; + if (!xt002 || xt002.GetAI() == null) + return; + + xt002.GetAI().SetData(XT002Data.TransferedHealth, (uint)me.GetHealth()); + xt002.GetAI().DoAction(Misc.ActionHardMode); + } + + InstanceScript _instance; + } + + public override CreatureAI GetAI(Creature creature) + { + return GetInstanceAI(creature); + } + } + + [Script] + class npc_scrapbot : CreatureScript + { + public npc_scrapbot() : base("npc_scrapbot") { } + + class npc_scrapbotAI : ScriptedAI + { + public npc_scrapbotAI(Creature creature) : base(creature) + { + _instance = me.GetInstanceScript(); + } + + public override void Reset() + { + me.SetReactState(ReactStates.Passive); + Creature pXT002 = ObjectAccessor.GetCreature(me, _instance.GetGuidData(BossIds.Xt002)); + if (pXT002) + me.GetMotionMaster().MoveFollow(pXT002, 0.0f, 0.0f); + } + + public override void MovementInform(MovementGeneratorType type, uint id) + { + ObjectGuid guid = _instance.GetGuidData(BossIds.Xt002); + if (type == MovementGeneratorType.Follow && id == guid.GetCounter()) + { + Creature xt002 = ObjectAccessor.GetCreature(me, guid); + if (xt002) + { + if (me.IsWithinMeleeRange(xt002)) + { + DoCast(xt002, SpellIds.ScrapbotRideVehicle); + // Unapply vehicle aura again + xt002.RemoveAurasDueToSpell(SpellIds.ScrapbotRideVehicle); + me.DespawnOrUnsummon(); + } + } + } + } + + InstanceScript _instance; + } + + public override CreatureAI GetAI(Creature creature) + { + return GetInstanceAI(creature); + } + } + + [Script] + class npc_pummeller : CreatureScript + { + public npc_pummeller() : base("npc_pummeller") { } + + class npc_pummellerAI : ScriptedAI + { + public npc_pummellerAI(Creature creature) : base(creature) + { + Initialize(); + _instance = creature.GetInstanceScript(); + } + + void Initialize() + { + _scheduler.SetValidator(() => me.IsWithinMeleeRange(me.GetVictim())); + + //Arcing Smash + _scheduler.Schedule(TimeSpan.FromSeconds(27), task => + { + DoCastVictim(SpellIds.ArcingSmash); + task.Repeat(TimeSpan.FromSeconds(27)); + }); + + //Trample + _scheduler.Schedule(TimeSpan.FromSeconds(22), task => + { + DoCastVictim(SpellIds.Trample); + task.Repeat(TimeSpan.FromSeconds(22)); + }); + + //Uppercut + _scheduler.Schedule(TimeSpan.FromSeconds(17), task => + { + DoCastVictim(SpellIds.Uppercut); + task.Repeat(TimeSpan.FromSeconds(17)); + }); + } + + public override void Reset() + { + Initialize(); + Creature xt002 = ObjectAccessor.GetCreature(me, _instance.GetGuidData(BossIds.Xt002)); + if (xt002) + { + Position pos = xt002.GetPosition(); + me.GetMotionMaster().MovePoint(0, pos); + } + } + + public override void UpdateAI(uint diff) + { + if (!UpdateVictim()) + return; + + _scheduler.Update(diff); + + DoMeleeAttackIfReady(); + } + + InstanceScript _instance; + } + + public override CreatureAI GetAI(Creature creature) + { + return GetInstanceAI(creature); + } + } + + class BoomEvent : BasicEvent + { + public BoomEvent(Creature me) + { + _me = me; + } + + public override bool Execute(ulong time, uint diff) + { + // This hack is here because we suspect our implementation of spell effect execution on targets + // is done in the wrong order. We suspect that 0 needs to be applied on all targets, + // then 1, etc - instead of applying each effect on target1, then target2, etc. + // The above situation causes the visual for this spell to be bugged, so we remove the instakill + // effect and implement a script hack for that. + + _me.CastSpell(_me, SpellIds.Boom, false); + return true; + } + + Creature _me; + } + + [Script] + class npc_boombot : CreatureScript + { + public npc_boombot() : base("npc_boombot") { } + + class npc_boombotAI : ScriptedAI + { + public npc_boombotAI(Creature creature) : base(creature) + { + Initialize(); + _instance = creature.GetInstanceScript(); + } + + void Initialize() + { + _boomed = false; + } + + public override void Reset() + { + Initialize(); + + DoCast(SpellIds.AuraBoombot); // For achievement + + // HACK/workaround: + // these values aren't confirmed - lack of data - and the values in DB are incorrect + // these values are needed for correct damage of Boom spell + me.SetFloatValue(UnitFields.MinDamage, 15000.0f); + me.SetFloatValue(UnitFields.MaxDamage, 18000.0f); + + // @todo proper waypoints? + Creature pXT002 = ObjectAccessor.GetCreature(me, _instance.GetGuidData(BossIds.Xt002)); + if (pXT002) + me.GetMotionMaster().MoveFollow(pXT002, 0.0f, 0.0f); + } + + public override void DamageTaken(Unit who, ref uint damage) + { + if (damage >= (me.GetHealth() - me.GetMaxHealth() * 0.5f) && !_boomed) + { + _boomed = true; // Prevent recursive calls + + //me.SendSpellInstakillLog(Spells.Boom, me); + + //me.DealDamage(me, me.GetHealth(), null, DamageEffectType.NoDamage, SpellSchoolMask.Normal, null, false); + + damage = 0; + + me.CastSpell(me, SpellIds.Boom, false); + + // Visual only seems to work if the instant kill event is delayed or the spell itself is delayed + // Casting done from player and caster source has the same targetinfo flags, + // so that can't be the issue + // See BoomEvent class + // Schedule 1s delayed + //me.m_Events.AddEvent(new BoomEvent(me), me.m_Events.CalculateTime(1 * Time.InMilliseconds)); + } + } + + public override void UpdateAI(uint diff) + { + if (!UpdateVictim()) + return; + + // No melee attack + } + + InstanceScript _instance; + bool _boomed; + } + + public override CreatureAI GetAI(Creature creature) + { + return GetInstanceAI(creature); + } + } + + [Script] + class npc_life_spark : CreatureScript + { + public npc_life_spark() : base("npc_life_spark") { } + + class npc_life_sparkAI : ScriptedAI + { + public npc_life_sparkAI(Creature creature) : base(creature) { } + + void Initialize() + { + _scheduler.Schedule(TimeSpan.FromMilliseconds(1), task => + { + DoCastVictim(SpellIds.Shock); + task.Repeat(TimeSpan.FromSeconds(12)); + }); + } + + public override void Reset() + { + DoCastSelf(SpellIds.ArcanePowerState); + _scheduler.CancelAll(); + } + + public override void EnterCombat(Unit victim) + { + DoCastSelf(SpellIds.StaticCharged); + _scheduler.Schedule(TimeSpan.FromSeconds(12), task => + { + DoCastVictim(SpellIds.Shock); + task.Repeat(); + }); + } + + public override void UpdateAI(uint diff) + { + if (!UpdateVictim()) + return; + + if (me.HasUnitState(UnitState.Casting)) + return; + + _scheduler.Update(diff, DoMeleeAttackIfReady); + } + } + + public override CreatureAI GetAI(Creature creature) + { + return new npc_life_sparkAI(creature); + } + } + + [Script] + class npc_xt_void_zone : CreatureScript + { + public npc_xt_void_zone() : base("npc_xt_void_zone") { } + + class npc_xt_void_zoneAI : PassiveAI + { + public npc_xt_void_zoneAI(Creature creature) : base(creature) { } + + public override void Reset() + { + _scheduler.Schedule(TimeSpan.FromSeconds(1), consumption => + { + DoCastSelf(SpellIds.Consumption); + consumption.Repeat(); + }); + } + + public override void UpdateAI(uint diff) + { + _scheduler.Update(diff); + } + } + + public override CreatureAI GetAI(Creature creature) + { + return new npc_xt_void_zoneAI(creature); + } + } + + [Script] + class spell_xt002_searing_light_spawn_life_spark : SpellScriptLoader + { + public spell_xt002_searing_light_spawn_life_spark() : base("spell_xt002_searing_light_spawn_life_spark") { } + + class spell_xt002_searing_light_spawn_life_spark_AuraScript : AuraScript + { + public override bool Validate(SpellInfo spell) + { + return ValidateSpellInfo(SpellIds.SummonLifeSpark); + } + + void OnRemove(AuraEffect aurEff, AuraEffectHandleModes mode) + { + Player player = GetOwner().ToPlayer(); + if (player) + { + Unit xt002 = GetCaster(); + if (xt002) + if (xt002.HasAura((uint)aurEff.GetAmount())) // Heartbreak aura indicating hard mode + xt002.CastSpell(player, SpellIds.SummonLifeSpark, true); + } + } + + public override void Register() + { + AfterEffectRemove.Add(new EffectApplyHandler(OnRemove, 0, AuraType.PeriodicTriggerSpell, AuraEffectHandleModes.Real)); + } + } + + public override AuraScript GetAuraScript() + { + return new spell_xt002_searing_light_spawn_life_spark_AuraScript(); + } + } + + [Script] + class spell_xt002_gravity_bomb_aura : SpellScriptLoader + { + public spell_xt002_gravity_bomb_aura() : base("spell_xt002_gravity_bomb_aura") { } + + class spell_xt002_gravity_bomb_aura_AuraScript : AuraScript + { + public override bool Validate(SpellInfo spell) + { + return ValidateSpellInfo(SpellIds.SummonVoidZone); + } + + void OnRemove(AuraEffect aurEff, AuraEffectHandleModes mode) + { + Player player = GetOwner().ToPlayer(); + if (player) + { + Unit xt002 = GetCaster(); + if (xt002) + if (xt002.HasAura((uint)aurEff.GetAmount())) // Heartbreak aura indicating hard mode + xt002.CastSpell(player, SpellIds.SummonVoidZone, true); + } + } + + void OnPeriodic(AuraEffect aurEff) + { + Unit xt002 = GetCaster(); + if (!xt002) + return; + + Unit owner = GetOwner().ToUnit(); + if (!owner) + return; + + if ((uint)aurEff.GetAmount() >= owner.GetHealth()) + if (xt002.GetAI() != null) + xt002.GetAI().SetData(XT002Data.GravityBombCasualty, 1); + } + + public override void Register() + { + OnEffectPeriodic.Add(new EffectPeriodicHandler(OnPeriodic, 2, AuraType.PeriodicDamage)); + AfterEffectRemove.Add(new EffectApplyHandler(OnRemove, 0, AuraType.PeriodicTriggerSpell, AuraEffectHandleModes.Real)); + } + } + + public override AuraScript GetAuraScript() + { + return new spell_xt002_gravity_bomb_aura_AuraScript(); + } + } + + [Script] + class spell_xt002_gravity_bomb_damage : SpellScriptLoader + { + public spell_xt002_gravity_bomb_damage() : base("spell_xt002_gravity_bomb_damage") { } + + class spell_xt002_gravity_bomb_damage_SpellScript : SpellScript + { + void HandleScript(uint eff) + { + Unit caster = GetCaster(); + if (!caster) + return; + + if ((uint)GetHitDamage() >= GetHitUnit().GetHealth()) + if (caster.GetAI() != null) + caster.GetAI().SetData(XT002Data.GravityBombCasualty, 1); + } + + public override void Register() + { + OnEffectHitTarget.Add(new EffectHandler(HandleScript, 0, SpellEffectName.SchoolDamage)); + } + } + + public override SpellScript GetSpellScript() + { + return new spell_xt002_gravity_bomb_damage_SpellScript(); + } + } + + [Script] + class spell_xt002_heart_overload_periodic : SpellScriptLoader + { + public spell_xt002_heart_overload_periodic() : base("spell_xt002_heart_overload_periodic") { } + + class spell_xt002_heart_overload_periodic_SpellScript : SpellScript + { + public override bool Validate(SpellInfo spell) + { + return ValidateSpellInfo(SpellIds.EnergyOrb, SpellIds.RechargeBoombot, SpellIds.RechargePummeler, SpellIds.RechargeScrapbot); + } + + void HandleScript(uint effIndex) + { + Unit caster = GetCaster(); + if (caster) + { + InstanceScript instance = caster.GetInstanceScript(); + if (instance != null) + { + Unit toyPile = Global.ObjAccessor.GetUnit(caster, instance.GetGuidData(InstanceData.ToyPile0 + RandomHelper.URand(0, 3))); + if (toyPile) + { + caster.CastSpell(toyPile, SpellIds.EnergyOrb, true); + + // This should probably be incorporated in a dummy effect handler, but I've had trouble getting the correct target + // Weighed randomization (approximation) + uint[] spells = { SpellIds.RechargeScrapbot, SpellIds.RechargeScrapbot, SpellIds.RechargeScrapbot, SpellIds.RechargePummeler, SpellIds.RechargeBoombot }; + + for (byte i = 0; i < 5; ++i) + { + uint spellId = spells[RandomHelper.IRand(0, 4)]; + toyPile.CastSpell(toyPile, spellId, true, null, null, instance.GetGuidData(BossIds.Xt002)); + } + } + } + + Creature creatureBase = caster.GetVehicleCreatureBase(); + if (creatureBase) + creatureBase.GetAI().Talk(Texts.Summon); + } + } + + public override void Register() + { + OnEffectHit.Add(new EffectHandler(HandleScript, 0, SpellEffectName.Dummy)); + } + } + + public override SpellScript GetSpellScript() + { + return new spell_xt002_heart_overload_periodic_SpellScript(); + } + } + + [Script] + class spell_xt002_tympanic_tantrum : SpellScriptLoader + { + public spell_xt002_tympanic_tantrum() : base("spell_xt002_tympanic_tantrum") { } + + class spell_xt002_tympanic_tantrum_SpellScript : SpellScript + { + void FilterTargets(List targets) + { + targets.RemoveAll(new PlayerOrPetCheck()); + } + + void RecalculateDamage() + { + SetHitDamage((int)GetHitUnit().CountPctFromMaxHealth(GetHitDamage())); + } + + public override void Register() + { + OnObjectAreaTargetSelect.Add(new ObjectAreaTargetSelectHandler(FilterTargets, 0, Targets.UnitSrcAreaEnemy)); + OnObjectAreaTargetSelect.Add(new ObjectAreaTargetSelectHandler(FilterTargets, 1, Targets.UnitSrcAreaEnemy)); + OnHit.Add(new HitHandler(RecalculateDamage)); + } + } + + public override SpellScript GetSpellScript() + { + return new spell_xt002_tympanic_tantrum_SpellScript(); + } + } + + [Script] + class spell_xt002_submerged : SpellScriptLoader + { + public spell_xt002_submerged() : base("spell_xt002_submerged") { } + + class spell_xt002_submerged_SpellScript : SpellScript + { + void HandleScript(uint eff) + { + Creature target = GetHitCreature(); + if (!target) + return; + + target.SetFlag(UnitFields.Flags, UnitFlags.NotSelectable); + target.SetStandState(UnitStandStateType.Submerged); + } + + public override void Register() + { + OnEffectHitTarget.Add(new EffectHandler(HandleScript, 0, SpellEffectName.ScriptEffect)); + } + } + + public override SpellScript GetSpellScript() + { + return new spell_xt002_submerged_SpellScript(); + } + } + + [Script] + class spell_xt002_321_boombot_aura : SpellScriptLoader + { + public spell_xt002_321_boombot_aura() : base("spell_xt002_321_boombot_aura") { } + + class spell_xt002_321_boombot_aura_AuraScript : AuraScript + { + public override bool Validate(SpellInfo spellInfo) + { + return ValidateSpellInfo(SpellIds.AchievementCreditNerfScrapbots); + } + + bool CheckProc(ProcEventInfo eventInfo) + { + if (eventInfo.GetActionTarget().GetEntry() != InstanceCreatureIds.XS013Scrapbot) + return false; + return true; + } + + void HandleProc(AuraEffect aurEff, ProcEventInfo eventInfo) + { + InstanceScript instance = eventInfo.GetActor().GetInstanceScript(); + if (instance == null) + return; + + instance.DoCastSpellOnPlayers(SpellIds.AchievementCreditNerfScrapbots); + } + + public override void Register() + { + DoCheckProc.Add(new CheckProcHandler(CheckProc)); + OnEffectProc.Add(new EffectProcHandler(HandleProc, 0, AuraType.Dummy)); + } + } + + public override AuraScript GetAuraScript() + { + return new spell_xt002_321_boombot_aura_AuraScript(); + } + } + + [Script] + class achievement_nerf_engineering : AchievementCriteriaScript + { + public achievement_nerf_engineering() : base("achievement_nerf_engineering") { } + + public override bool OnCheck(Player source, Unit target) + { + if (!target || target.GetAI() == null) + return false; + + return target.GetAI().GetData(XT002Data.HealthRecovered) == 0; + } + } + + [Script] + class achievement_heartbreaker : AchievementCriteriaScript + { + public achievement_heartbreaker() : base("achievement_heartbreaker") { } + + public override bool OnCheck(Player source, Unit target) + { + if (!target || target.GetAI() == null) + return false; + + return target.GetAI().GetData(XT002Data.HardMode) != 0; + } + } + + [Script] + class achievement_nerf_gravity_bombs : AchievementCriteriaScript + { + public achievement_nerf_gravity_bombs() : base("achievement_nerf_gravity_bombs") { } + + public override bool OnCheck(Player source, Unit target) + { + if (!target || target.GetAI() == null) + return false; + + return target.GetAI().GetData(XT002Data.GravityBombCasualty) == 0; + } + } +} diff --git a/Scripts/Northrend/Ulduar/YoggSaron.cs b/Scripts/Northrend/Ulduar/YoggSaron.cs new file mode 100644 index 000000000..6a6fe0097 --- /dev/null +++ b/Scripts/Northrend/Ulduar/YoggSaron.cs @@ -0,0 +1,39 @@ +/* + * Copyright (C) 2012-2017 CypherCore + * + * 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, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +using Game.Entities; + +namespace Scripts.Northrend.Ulduar +{ + class YoggSaron + { + public static Position[] ObservationRingKeepersPos = + { + new Position(1945.682f, 33.34201f, 411.4408f, 5.270895f), // Freya + new Position(1945.761f, -81.52171f, 411.4407f, 1.029744f), // Hodir + new Position(2028.822f, -65.73573f, 411.4426f, 2.460914f), // Thorim + new Position(2028.766f, 17.42014f, 411.4446f, 3.857178f), // Mimiron + }; + public static Position[] YSKeepersPos = + { + new Position(2036.873f, 25.42513f, 338.4984f, 3.909538f), // Freya + new Position(1939.045f, -90.87457f, 338.5426f, 0.994837f), // Hodir + new Position(1939.148f, 42.49035f, 338.5427f, 5.235988f), // Thorim + new Position(2036.658f, -73.58822f, 338.4985f, 2.460914f), // Mimiron + }; + } +} diff --git a/Scripts/Northrend/VioletHold/InstanceVioletHold.cs b/Scripts/Northrend/VioletHold/InstanceVioletHold.cs new file mode 100644 index 000000000..afc384ac2 --- /dev/null +++ b/Scripts/Northrend/VioletHold/InstanceVioletHold.cs @@ -0,0 +1,756 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using Game.Scripting; +using Game.Maps; +using Game.WorldEntities; +using Framework.Util; +using Framework.Constants; +using Game.Spells; +using Game; + +namespace Scripts.Northrend.VioletHold +{ + class instance_violet_hold : InstanceMapScript + { + public instance_violet_hold() : base("instance_violet_hold", 608) { } + + public override InstanceScript GetInstanceScript(InstanceMap map) + { + return new instance_violet_hold_InstanceMapScript(map); + } + + class instance_violet_hold_InstanceMapScript : InstanceScript + { + public instance_violet_hold_InstanceMapScript(Map map) : base(map) { } + + public override void Initialize() + { + uiMoragg = 0; + uiErekem = 0; + uiIchoron = 0; + uiLavanthor = 0; + uiXevozz = 0; + uiZuramat = 0; + uiCyanigosa = 0; + uiSinclari = 0; + + uiMoraggCell = 0; + uiErekemCell = 0; + uiErekemGuard[0] = 0; + uiErekemGuard[1] = 0; + uiIchoronCell = 0; + uiLavanthorCell = 0; + uiXevozzCell = 0; + uiZuramatCell = 0; + uiMainDoor = 0; + uiTeleportationPortal = 0; + uiSaboteurPortal = 0; + + trashMobs.Clear(); + + uiRemoveNpc = 0; + + uiDoorIntegrity = 100; + + uiWaveCount = 0; + uiLocation = (byte)RandomHelper.URand(0, 5); + uiFirstBoss = 0; + uiSecondBoss = 0; + uiCountErekemGuards = 0; + uiCountActivationCrystals = 0; + uiCyanigosaEventPhase = 1; + + uiActivationTimer = 5000; + uiDoorSpellTimer = 2000; + uiCyanigosaEventTimer = 3 * Time.InMilliseconds; + + bActive = false; + bIsDoorSpellCast = false; + bCrystalActivated = false; + defenseless = true; + uiMainEventPhase = EncounterState.NotStarted; + } + + public override bool IsEncounterInProgress() + { + for (byte i = 0; i < MAX_ENCOUNTER; ++i) + if ((EncounterState)m_auiEncounter[i] == EncounterState.InProgress) + return true; + + return false; + } + + public override void OnCreatureCreate(Creature creature) + { + switch (creature.GetEntry()) + { + case CREATURE_XEVOZZ: + uiXevozz = creature.GetGUID(); + break; + case CREATURE_LAVANTHOR: + uiLavanthor = creature.GetGUID(); + break; + case CREATURE_ICHORON: + uiIchoron = creature.GetGUID(); + break; + case CREATURE_ZURAMAT: + uiZuramat = creature.GetGUID(); + break; + case CREATURE_EREKEM: + uiErekem = creature.GetGUID(); + break; + case CREATURE_EREKEM_GUARD: + if (uiCountErekemGuards < 2) + { + uiErekemGuard[uiCountErekemGuards++] = creature.GetGUID(); + creature.SetFlag(UnitFields.Flags, UnitFlags.ImmuneToPc | UnitFlags.NonAttackable); + } + break; + case CREATURE_MORAGG: + uiMoragg = creature.GetGUID(); + break; + case CREATURE_CYANIGOSA: + uiCyanigosa = creature.GetGUID(); + creature.SetFlag(UnitFields.Flags, UnitFlags.ImmuneToPc | UnitFlags.NonAttackable); + break; + case CREATURE_SINCLARI: + uiSinclari = creature.GetGUID(); + break; + } + + if (creature.GetGUID() == uiFirstBoss || creature.GetGUID() == uiSecondBoss) + { + creature.AllLootRemovedFromCorpse(); + creature.RemoveLootMode(1); + } + } + + public override void OnGameObjectCreate(GameObject go) + { + switch (go.GetEntry()) + { + case GO_EREKEM_GUARD_1_DOOR: + uiErekemLeftGuardCell = go.GetGUID(); + break; + case GO_EREKEM_GUARD_2_DOOR: + uiErekemRightGuardCell = go.GetGUID(); + break; + case GO_EREKEM_DOOR: + uiErekemCell = go.GetGUID(); + break; + case GO_ZURAMAT_DOOR: + uiZuramatCell = go.GetGUID(); + break; + case GO_LAVANTHOR_DOOR: + uiLavanthorCell = go.GetGUID(); + break; + case GO_MORAGG_DOOR: + uiMoraggCell = go.GetGUID(); + break; + case GO_ICHORON_DOOR: + uiIchoronCell = go.GetGUID(); + break; + case GO_XEVOZZ_DOOR: + uiXevozzCell = go.GetGUID(); + break; + case GO_MAIN_DOOR: + uiMainDoor = go.GetGUID(); + break; + case GO_ACTIVATION_CRYSTAL: + if (uiCountActivationCrystals < 4) + uiActivationCrystal[uiCountActivationCrystals++] = go.GetGUID(); + break; + } + } + + public override void SetData(uint type, uint data) + { + switch (type) + { + case DATA_1ST_BOSS_EVENT: + UpdateEncounterState(EncounterCreditType.KillCreature, CREATURE_EREKEM, null); + m_auiEncounter[0] = (byte)data; + if ((EncounterState)data == EncounterState.Done) + SaveToDB(); + break; + case DATA_2ND_BOSS_EVENT: + UpdateEncounterState(EncounterCreditType.KillCreature, CREATURE_MORAGG, null); + m_auiEncounter[1] = (byte)data; + if ((EncounterState)data == EncounterState.Done) + SaveToDB(); + break; + case DATA_CYANIGOSA_EVENT: + m_auiEncounter[2] = (byte)data; + if ((EncounterState)data == EncounterState.Done) + { + SaveToDB(); + uiMainEventPhase = EncounterState.EncounterState.Done; + if (GameObject pMainDoor = instance.GetGameObject(uiMainDoor)) + pMainDoor.SetGoState(GameObjectState.Active); + } + break; + case DATA_WAVE_COUNT: + uiWaveCount = (byte)data; + bActive = true; + break; + case DATA_REMOVE_NPC: + uiRemoveNpc = (byte)data; + break; + case DATA_PORTAL_LOCATION: + uiLocation = (byte)data; + break; + case DATA_DOOR_INTEGRITY: + uiDoorIntegrity = (byte)data; + defenseless = false; + DoUpdateWorldState(WORLD_STATE_VH_PRISON_STATE, uiDoorIntegrity); + break; + case DATA_NPC_PRESENCE_AT_DOOR_ADD: + NpcAtDoorCastingList.Add((byte)data); + break; + case DATA_NPC_PRESENCE_AT_DOOR_REMOVE: + if (!NpcAtDoorCastingList.Empty()) + NpcAtDoorCastingList.RemoveAt(0);//.pop_back(); + break; + case DATA_MAIN_DOOR: + if (GameObject pMainDoor = instance.GetGameObject(uiMainDoor)) + { + switch ((GameObjectState)data) + { + case GameObjectState.Active: + case GameObjectState.Ready: + case GameObjectState.ActiveAlternative: + pMainDoor.SetGoState((GameObjectState)data); + break; + } + } + break; + case DATA_START_BOSS_ENCOUNTER: + switch (uiWaveCount) + { + case 6: + StartBossEncounter(uiFirstBoss); + break; + case 12: + StartBossEncounter(uiSecondBoss); + break; + } + break; + case DATA_ACTIVATE_CRYSTAL: + ActivateCrystal(); + break; + case DATA_MAIN_EVENT_PHASE: + uiMainEventPhase = (EncounterState)data; + if ((EncounterState)data == EncounterState.InProgress) // Start event + { + if (GameObject mainDoor = instance.GetGameObject(uiMainDoor)) + mainDoor.SetGoState(GameObjectState.Ready); + uiWaveCount = 1; + bActive = true; + for (int i = 0; i < 4; ++i) + { + if (GameObject crystal = instance.GetGameObject(uiActivationCrystal[i])) + crystal.RemoveFlag(GameObjectFields.Flags, GameObjectFlags.NotSelectable); + } + uiRemoveNpc = 0; // might not have been reset after a wipe on a boss. + } + break; + } + } + + public override void SetData64(uint type, ulong data) + { + switch (type) + { + case DATA_ADD_TRASH_MOB: + trashMobs.Add(data); + break; + case DATA_DEL_TRASH_MOB: + trashMobs.Remove(data); + break; + } + } + + public override uint GetData(uint type) + { + switch (type) + { + case DATA_1ST_BOSS_EVENT: + return m_auiEncounter[0]; + case DATA_2ND_BOSS_EVENT: + return m_auiEncounter[1]; + case DATA_CYANIGOSA_EVENT: + return m_auiEncounter[2]; + case DATA_WAVE_COUNT: + return uiWaveCount; + case DATA_REMOVE_NPC: + return uiRemoveNpc; + case DATA_PORTAL_LOCATION: + return uiLocation; + case DATA_DOOR_INTEGRITY: + return uiDoorIntegrity; + case DATA_NPC_PRESENCE_AT_DOOR: + return (uint)NpcAtDoorCastingList.Count; + case DATA_FIRST_BOSS: + return uiFirstBoss; + case DATA_SECOND_BOSS: + return uiSecondBoss; + case DATA_MAIN_EVENT_PHASE: + return (uint)uiMainEventPhase; + case DATA_DEFENSELESS: + return (uint)(defenseless ? 1 : 0); + } + + return 0; + } + + public override ulong GetData64(uint identifier) + { + switch (identifier) + { + case DATA_MORAGG: return uiMoragg; + case DATA_EREKEM: return uiErekem; + case DATA_EREKEM_GUARD_1: return uiErekemGuard[0]; + case DATA_EREKEM_GUARD_2: return uiErekemGuard[1]; + case DATA_ICHORON: return uiIchoron; + case DATA_LAVANTHOR: return uiLavanthor; + case DATA_XEVOZZ: return uiXevozz; + case DATA_ZURAMAT: return uiZuramat; + case DATA_CYANIGOSA: return uiCyanigosa; + case DATA_MORAGG_CELL: return uiMoraggCell; + case DATA_EREKEM_CELL: return uiErekemCell; + case DATA_EREKEM_RIGHT_GUARD_CELL: return uiErekemRightGuardCell; + case DATA_EREKEM_LEFT_GUARD_CELL: return uiErekemLeftGuardCell; + case DATA_ICHORON_CELL: return uiIchoronCell; + case DATA_LAVANTHOR_CELL: return uiLavanthorCell; + case DATA_XEVOZZ_CELL: return uiXevozzCell; + case DATA_ZURAMAT_CELL: return uiZuramatCell; + case DATA_MAIN_DOOR: return uiMainDoor; + case DATA_SINCLARI: return uiSinclari; + case DATA_TELEPORTATION_PORTAL: return uiTeleportationPortal; + case DATA_SABOTEUR_PORTAL: return uiSaboteurPortal; + } + + return 0; + } + + void SpawnPortal() + { + SetData(DATA_PORTAL_LOCATION, (GetData(DATA_PORTAL_LOCATION) + RandomHelper.URand(1, 5)) % 6); + if (Creature pSinclari = instance.GetCreature(uiSinclari)) + if (Creature portal = pSinclari.SummonCreature(CREATURE_TELEPORTATION_PORTAL, PortalLocation[GetData(DATA_PORTAL_LOCATION)], TempSummonType.CorpseDespawn)) + uiTeleportationPortal = portal.GetGUID(); + } + + void StartBossEncounter(byte uiBoss, bool bForceRespawn = true) + { + Creature pBoss = null; + + switch (uiBoss) + { + case BOSS_MORAGG: + HandleGameObject(uiMoraggCell, bForceRespawn); + pBoss = instance.GetCreature(uiMoragg); + if (pBoss) + pBoss.GetMotionMaster().MovePoint(0, BossStartMove1); + break; + case BOSS_EREKEM: + HandleGameObject(uiErekemCell, bForceRespawn); + HandleGameObject(uiErekemRightGuardCell, bForceRespawn); + HandleGameObject(uiErekemLeftGuardCell, bForceRespawn); + + pBoss = instance.GetCreature(uiErekem); + + if (pBoss) + pBoss.GetMotionMaster().MovePoint(0, BossStartMove2); + + if (Creature pGuard1 = instance.GetCreature(uiErekemGuard[0])) + { + if (bForceRespawn) + pGuard1.SetFlag(UnitFields.Flags, UnitFlags.ImmuneToPc | UnitFlags.NonAttackable); + else + pGuard1.RemoveFlag(UnitFields.Flags, UnitFlags.ImmuneToPc | UnitFlags.NonAttackable); + pGuard1.GetMotionMaster().MovePoint(0, BossStartMove21); + } + + if (Creature pGuard2 = instance.GetCreature(uiErekemGuard[1])) + { + if (bForceRespawn) + pGuard2.SetFlag(UnitFields.Flags, UnitFlags.ImmuneToPc | UnitFlags.NonAttackable); + else + pGuard2.RemoveFlag(UnitFields.Flags, UnitFlags.ImmuneToPc | UnitFlags.NonAttackable); + pGuard2.GetMotionMaster().MovePoint(0, BossStartMove22); + } + break; + case BOSS_ICHORON: + HandleGameObject(uiIchoronCell, bForceRespawn); + pBoss = instance.GetCreature(uiIchoron); + if (pBoss) + pBoss.GetMotionMaster().MovePoint(0, BossStartMove3); + break; + case BOSS_LAVANTHOR: + HandleGameObject(uiLavanthorCell, bForceRespawn); + pBoss = instance.GetCreature(uiLavanthor); + if (pBoss) + pBoss.GetMotionMaster().MovePoint(0, BossStartMove4); + break; + case BOSS_XEVOZZ: + HandleGameObject(uiXevozzCell, bForceRespawn); + pBoss = instance.GetCreature(uiXevozz); + if (pBoss) + pBoss.GetMotionMaster().MovePoint(0, BossStartMove5); + break; + case BOSS_ZURAMAT: + HandleGameObject(uiZuramatCell, bForceRespawn); + pBoss = instance.GetCreature(uiZuramat); + if (pBoss) + pBoss.GetMotionMaster().MovePoint(0, BossStartMove6); + break; + } + + // generic boss state changes + if (pBoss) + { + pBoss.RemoveFlag(UnitFields.Flags, UnitFlags.ImmuneToPc | UnitFlags.NonAttackable); + pBoss.SetReactState(ReactStates.Aggressive); + + if (!bForceRespawn) + { + if (pBoss.IsDead()) + { + // respawn but avoid to be looted again + pBoss.Respawn(); + pBoss.RemoveLootMode(1); + } + pBoss.SetFlag(UnitFields.Flags, UnitFlags.ImmuneToPc | UnitFlags.NonAttackable); + uiWaveCount = 0; + } + } + } + + void AddWave() + { + DoUpdateWorldState(WORLD_STATE_VH, 1); + DoUpdateWorldState(WORLD_STATE_VH_WAVE_COUNT, uiWaveCount); + + switch (uiWaveCount) + { + case 6: + if (uiFirstBoss == 0) + uiFirstBoss = (byte)RandomHelper.URand(1, 6); + if (Creature pSinclari = instance.GetCreature(uiSinclari)) + { + if (Creature pPortal = pSinclari.SummonCreature(CREATURE_TELEPORTATION_PORTAL, MiddleRoomPortalSaboLocation, TempSummonType.CorpseDespawn)) + uiSaboteurPortal = pPortal.GetGUID(); + if (Creature pAzureSaboteur = pSinclari.SummonCreature(CREATURE_SABOTEOUR, MiddleRoomLocation, TempSummonType.DeadDespawn)) + pAzureSaboteur.CastSpell(pAzureSaboteur, SABOTEUR_SHIELD_EFFECT, false); + } + break; + case 12: + if (uiSecondBoss == 0) + do + { + uiSecondBoss = (byte)RandomHelper.URand(1, 6); + } while (uiSecondBoss == uiFirstBoss); + if (Creature pSinclari = instance.GetCreature(uiSinclari)) + { + if (Creature pPortal = pSinclari.SummonCreature(CREATURE_TELEPORTATION_PORTAL, MiddleRoomPortalSaboLocation, TempSummonType.CorpseDespawn)) + uiSaboteurPortal = pPortal.GetGUID(); + if (Creature pAzureSaboteur = pSinclari.SummonCreature(CREATURE_SABOTEOUR, MiddleRoomLocation, TempSummonType.DeadDespawn)) + pAzureSaboteur.CastSpell(pAzureSaboteur, SABOTEUR_SHIELD_EFFECT, false); + } + break; + case 18: + { + Creature pSinclari = instance.GetCreature(uiSinclari); + if (pSinclari) + pSinclari.SummonCreature(CREATURE_CYANIGOSA, CyanigosasSpawnLocation, TempSummonType.DeadDespawn); + break; + } + case 1: + { + if (GameObject pMainDoor = instance.GetGameObject(uiMainDoor)) + pMainDoor.SetGoState(GameObjectState.Ready); + DoUpdateWorldState(WORLD_STATE_VH_PRISON_STATE, 100); + // no break + } + goto default; + default: + SpawnPortal(); + break; + } + } + + public override string GetSaveData() + { + OUT_SAVE_INST_DATA(); + + str_data = string.Format("V H {0} {1} {2} {3} {4}", m_auiEncounter[0], m_auiEncounter[1], m_auiEncounter[2], uiFirstBoss, uiSecondBoss); + + OUT_SAVE_INST_DATA_COMPLETE(); + return str_data; + } + + public override void Load(string str) + { + if (!string.IsNullOrEmpty(str)) + { + OUT_LOAD_INST_DATA_FAIL(); + return; + } + + OUT_LOAD_INST_DATA(str); + + StringArguments args = new StringArguments(str); + char dataHead1 = args.NextChar(); + char dataHead2 = args.NextChar(); + ushort data0 = args.NextUInt16(); + ushort data1 = args.NextUInt16(); + ushort data2 = args.NextUInt16(); + ushort data3 = args.NextUInt16(); + ushort data4 = args.NextUInt16(); + + if (dataHead1 == 'V' && dataHead2 == 'H') + { + m_auiEncounter[0] = data0; + m_auiEncounter[1] = data1; + m_auiEncounter[2] = data2; + + for (byte i = 0; i < MAX_ENCOUNTER; ++i) + if ((EncounterState)m_auiEncounter[i] == EncounterState.InProgress) + m_auiEncounter[i] = (ushort)EncounterState.NotStarted; + + uiFirstBoss = (byte)data3; + uiSecondBoss = (byte)data4; + } + else + OUT_LOAD_INST_DATA_FAIL(); + + OUT_LOAD_INST_DATA_COMPLETE(); + } + + bool CheckWipe() + { + var players = instance.GetPlayers(); + foreach (var player in players) + { + if (player.IsGameMaster()) + continue; + + if (player.IsAlive()) + return false; + } + + return true; + } + + public override void Update(uint diff) + { + if (!instance.HavePlayers()) + return; + + // portals should spawn if other portal is dead and doors are closed + if (bActive && uiMainEventPhase == EncounterState.InProgress) + { + if (uiActivationTimer < diff) + { + AddWave(); + bActive = false; + // 1 minute waiting time after each boss fight + uiActivationTimer = (uint)((uiWaveCount == 6 || uiWaveCount == 12) ? 60000 : 5000); + } else uiActivationTimer -= diff; + } + + // if main event is in progress and players have wiped then reset instance + if (uiMainEventPhase == EncounterState.InProgress && CheckWipe()) + { + SetData(DATA_REMOVE_NPC, 1); + StartBossEncounter(uiFirstBoss, false); + StartBossEncounter(uiSecondBoss, false); + + SetData(DATA_MAIN_DOOR, GameObjectState.Active); + SetData(DATA_WAVE_COUNT, 0); + uiMainEventPhase = EncounterState.NotStarted; + + for (int i = 0; i < 4; ++i) + { + if (GameObject crystal = instance.GetGameObject(uiActivationCrystal[i])) + crystal.SetFlag(GameObjectFields.Flags, GameObjectFlags.NotSelectable); + } + + if (Creature pSinclari = instance.GetCreature(uiSinclari)) + { + pSinclari.SetVisible(true); + + List GuardList = new List(); + pSinclari.GetCreatureListWithEntryInGrid(GuardList, NPC_VIOLET_HOLD_GUARD, 40.0f); + if (!GuardList.Empty()) + { + foreach (var pGuard in GuardList) + { + pGuard.SetVisible(true); + pGuard.SetReactState(ReactStates.Aggressive); + pGuard.GetMotionMaster().MovePoint(1, pGuard.GetHomePosition()); + } + } + pSinclari.GetMotionMaster().MovePoint(1, pSinclari.GetHomePosition()); + pSinclari.RemoveFlag(UnitFields.Flags, UnitFlags.NotSelectable); + } + } + + // Cyanigosa is spawned but not tranformed, prefight event + Creature pCyanigosa = instance.GetCreature(uiCyanigosa); + if (pCyanigosa && !pCyanigosa.HasAura(CYANIGOSA_SPELL_TRANSFORM)) + { + if (uiCyanigosaEventTimer <= diff) + { + switch (uiCyanigosaEventPhase) + { + case 1: + pCyanigosa.CastSpell(pCyanigosa, CYANIGOSA_BLUE_AURA, false); + pCyanigosa.GetAI().Talk(CYANIGOSA_SAY_SPAWN); + uiCyanigosaEventTimer = 7 * Time.InMilliseconds; + ++uiCyanigosaEventPhase; + break; + case 2: + pCyanigosa.GetMotionMaster().MoveJump(MiddleRoomLocation.GetPositionX(), MiddleRoomLocation.GetPositionY(), MiddleRoomLocation.GetPositionZ(), 10.0f, 20.0f); + pCyanigosa.CastSpell(pCyanigosa, CYANIGOSA_BLUE_AURA, false); + uiCyanigosaEventTimer = 7 * Time.InMilliseconds; + ++uiCyanigosaEventPhase; + break; + case 3: + pCyanigosa.RemoveAurasDueToSpell(CYANIGOSA_BLUE_AURA); + pCyanigosa.CastSpell(pCyanigosa, CYANIGOSA_SPELL_TRANSFORM, 0); + pCyanigosa.RemoveFlag(UnitFields.Flags, UnitFlags.ImmuneToPc | UnitFlags.NonAttackable); + pCyanigosa.SetReactState(ReactStates.Aggressive); + uiCyanigosaEventTimer = 2 * Time.InMilliseconds; + ++uiCyanigosaEventPhase; + break; + case 4: + uiCyanigosaEventPhase = 0; + break; + } + } else uiCyanigosaEventTimer -= diff; + } + + // if there are NPCs in front of the prison door, which are casting the door seal spell and doors are active + if (GetData(DATA_NPC_PRESENCE_AT_DOOR) && uiMainEventPhase == EncounterState.InProgress) + { + // if door integrity is > 0 then decrase it's integrity state + if (GetData(DATA_DOOR_INTEGRITY)) + { + if (uiDoorSpellTimer < diff) + { + SetData(DATA_DOOR_INTEGRITY, GetData(DATA_DOOR_INTEGRITY) - 1); + uiDoorSpellTimer = 2000; + } else uiDoorSpellTimer -= diff; + } + // else set door state to active (means door will open and group have failed to sustain mob invasion on the door) + else + { + SetData(DATA_MAIN_DOOR, GameObjectState.Active); + uiMainEventPhase = EncounterState.Fail; + } + } + } + + void ActivateCrystal() + { + // just to make things easier we'll get the gameobject from the map + GameObject invoker = instance.GetGameObject(uiActivationCrystal[0]); + if (!invoker) + return; + + SpellInfo spellInfoLightning = Global.SpellMgr.GetSpellInfo(SPELL_ARCANE_LIGHTNING); + if (spellInfoLightning == null) + return; + + // the orb + TempSummon trigger = invoker.SummonCreature(NPC_DEFENSE_SYSTEM, ArcaneSphere, TempSummonType.ManualDespawn, 0); + if (!trigger) + return; + + // visuals + trigger.CastSpell(trigger, spellInfoLightning, true, 0, 0, trigger.GetGUID()); + + // Kill all mobs registered with SetData64(ADD_TRASH_MOB) + foreach (var guid in trashMobs) + { + Creature creature = instance.GetCreature(guid); + if (creature && creature.IsAlive()) + trigger.Kill(creature); + } + } + + public override void ProcessEvent(WorldObject go, uint uiEventId) + { + switch (uiEventId) + { + case EVENT_ACTIVATE_CRYSTAL: + bCrystalActivated = true; // Activation by player's will throw event signal + ActivateCrystal(); + break; + } + } + + #region Fields + + ulong uiMoragg; + ulong uiErekem; + ulong[] uiErekemGuard = new ulong[2]; + ulong uiIchoron; + ulong uiLavanthor; + ulong uiXevozz; + ulong uiZuramat; + ulong uiCyanigosa; + ulong uiSinclari; + + ulong uiMoraggCell; + ulong uiErekemCell; + ulong uiErekemLeftGuardCell; + ulong uiErekemRightGuardCell; + ulong uiIchoronCell; + ulong uiLavanthorCell; + ulong uiXevozzCell; + ulong uiZuramatCell; + ulong uiMainDoor; + ulong uiTeleportationPortal; + ulong uiSaboteurPortal; + + ulong[] uiActivationCrystal = new ulong[4]; + + uint uiActivationTimer; + uint uiCyanigosaEventTimer; + uint uiDoorSpellTimer; + + List trashMobs = new List(); // to kill with crystal + + byte uiWaveCount; + byte uiLocation; + byte uiFirstBoss; + byte uiSecondBoss; + byte uiRemoveNpc; + + byte uiDoorIntegrity; + + ushort[] m_auiEncounter = new ushort[MAX_ENCOUNTER]; + byte uiCountErekemGuards; + byte uiCountActivationCrystals; + byte uiCyanigosaEventPhase; + EncounterState uiMainEventPhase; // SPECIAL: pre event animations, InProgress: event itself + + bool bActive; + bool bWiped; + bool bIsDoorSpellCast; + bool bCrystalActivated; + bool defenseless; + + List NpcAtDoorCastingList = new List(); + + string str_data; + #endregion + } + } +} diff --git a/Scripts/Northrend/VioletHold/VioletHold.cs b/Scripts/Northrend/VioletHold/VioletHold.cs new file mode 100644 index 000000000..ef5f86117 --- /dev/null +++ b/Scripts/Northrend/VioletHold/VioletHold.cs @@ -0,0 +1,21 @@ +/* + * Copyright (C) 2012-2017 CypherCore + * + * 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, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +namespace Scripts.Northrend.VioletHold +{ + +} diff --git a/Scripts/Northrend/WinterGrasp.cs b/Scripts/Northrend/WinterGrasp.cs new file mode 100644 index 000000000..5c5ac0898 --- /dev/null +++ b/Scripts/Northrend/WinterGrasp.cs @@ -0,0 +1,116 @@ +/* + * Copyright (C) 2012-2017 CypherCore + * + * 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, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +using Framework.Constants; +using Game.BattleFields; +using Game.Conditions; +using Game.Entities; +using Game.Scripting; + +namespace Scripts.Northrend +{ + [Script] + class spell_wintergrasp_defender_teleport : SpellScriptLoader + { + public spell_wintergrasp_defender_teleport() : base("spell_wintergrasp_defender_teleport") { } + + class spell_wintergrasp_defender_teleport_SpellScript : SpellScript + { + SpellCastResult CheckCast() + { + BattleField wg = Global.BattleFieldMgr.GetBattlefieldByBattleId(1); + if (wg != null) + { + Player target = GetExplTargetUnit().ToPlayer(); + if (target) + // check if we are in Wintergrasp at all, SotA uses same teleport spells + if ((target.GetZoneId() == 4197 && target.GetTeamId() != wg.GetDefenderTeam()) || target.HasAura(54643)) + return SpellCastResult.BadTargets; + } + + return SpellCastResult.SpellCastOk; + } + + public override void Register() + { + OnCheckCast.Add(new CheckCastHandler(CheckCast)); + } + } + + public override SpellScript GetSpellScript() + { + return new spell_wintergrasp_defender_teleport_SpellScript(); + } + } + + [Script] + class spell_wintergrasp_defender_teleport_trigger : SpellScriptLoader + { + public spell_wintergrasp_defender_teleport_trigger() : base("spell_wintergrasp_defender_teleport_trigger") { } + + class spell_wintergrasp_defender_teleport_trigger_SpellScript : SpellScript + { + void HandleDummy(uint effindex) + { + Unit target = GetHitUnit(); + if (target) + { + WorldLocation loc = target.GetWorldLocation(); + SetExplTargetDest(loc); + } + } + + public override void Register() + { + OnEffectHitTarget.Add(new EffectHandler(HandleDummy, 0, SpellEffectName.Dummy)); + } + } + + public override SpellScript GetSpellScript() + { + return new spell_wintergrasp_defender_teleport_trigger_SpellScript(); + } + } + + [Script] + class condition_is_wintergrasp_horde : ConditionScript + { + public condition_is_wintergrasp_horde() : base("condition_is_wintergrasp_horde") { } + + public override bool OnConditionCheck(Condition condition, ConditionSourceInfo sourceInfo) + { + BattleField wintergrasp = Global.BattleFieldMgr.GetBattlefieldByBattleId(BattlefieldIds.WG); + if (wintergrasp.IsEnabled() && wintergrasp.GetDefenderTeam() == TeamId.Horde) + return true; + return false; + } + } + + [Script] + class condition_is_wintergrasp_alliance : ConditionScript + { + public condition_is_wintergrasp_alliance() : base("condition_is_wintergrasp_alliance") { } + + public override bool OnConditionCheck(Condition condition, ConditionSourceInfo sourceInfo) + { + BattleField wintergrasp = Global.BattleFieldMgr.GetBattlefieldByBattleId(BattlefieldIds.WG); + if (wintergrasp.IsEnabled() && wintergrasp.GetDefenderTeam() == TeamId.Alliance) + return true; + return false; + } + } +} diff --git a/Scripts/Outlands/HellfirePeninsula.cs b/Scripts/Outlands/HellfirePeninsula.cs new file mode 100644 index 000000000..67f39f602 --- /dev/null +++ b/Scripts/Outlands/HellfirePeninsula.cs @@ -0,0 +1,340 @@ +/* + * Copyright (C) 2012-2017 CypherCore + * + * 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, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +using Framework.Constants; +using Game; +using Game.AI; +using Game.Entities; +using Game.Scripting; + +namespace Scripts.Outlands +{ + struct Aeranas + { + public const uint SaySummon = 0; + public const uint SayFree = 1; + + public const uint FactionHostile = 16; + public const uint FactionFriendly = 35; + + public const uint SpellEncelopingWinds = 15535; + public const uint SpellShock = 12553; + } + + [Script] + class npc_aeranas : CreatureScript + { + public npc_aeranas() : base("npc_aeranas") { } + + class npc_aeranasAI : ScriptedAI + { + public npc_aeranasAI(Creature creature) : base(creature) { } + + public override void Reset() + { + faction_Timer = 8000; + envelopingWinds_Timer = 9000; + shock_Timer = 5000; + + me.RemoveFlag64(UnitFields.NpcFlags, NPCFlags.QuestGiver); + me.SetFaction(Aeranas.FactionFriendly); + + Talk(Aeranas.SaySummon); + } + + public override void UpdateAI(uint diff) + { + if (faction_Timer != 0) + { + if (faction_Timer <= diff) + { + me.SetFaction(Aeranas.FactionHostile); + faction_Timer = 0; + } + else + faction_Timer -= diff; + } + + if (!UpdateVictim()) + return; + + if (HealthBelowPct(30)) + { + me.SetFaction(Aeranas.FactionFriendly); + me.SetFlag64(UnitFields.NpcFlags, NPCFlags.QuestGiver); + me.RemoveAllAuras(); + me.DeleteThreatList(); + me.CombatStop(true); + Talk(Aeranas.SayFree); + return; + } + + if (shock_Timer <= diff) + { + DoCastVictim(Aeranas.SpellShock); + shock_Timer = 10000; + } + else + shock_Timer -= diff; + + if (envelopingWinds_Timer <= diff) + { + DoCastVictim(Aeranas.SpellEncelopingWinds); + envelopingWinds_Timer = 25000; + } + else + envelopingWinds_Timer -= diff; + + DoMeleeAttackIfReady(); + } + + uint faction_Timer; + uint envelopingWinds_Timer; + uint shock_Timer; + } + + public override CreatureAI GetAI(Creature creature) + { + return new npc_aeranasAI(creature); + } + } + + struct AncestralWolf + { + public const uint EmoteWoldLiftHead = 0; + public const uint EmoteWolfHowl = 1; + public const uint SayWolfWelcome = 2; + + public const uint SpellAncestralWoldBuff = 29981; + + public const uint NpcRyga = 17123; + } + + [Script] + class npc_ancestral_wolf : CreatureScript + { + public npc_ancestral_wolf() : base("npc_ancestral_wolf") { } + + class npc_ancestral_wolfAI : npc_escortAI + { + public npc_ancestral_wolfAI(Creature creature) : base(creature) + { + if (creature.GetOwner() && creature.GetOwner().IsTypeId(TypeId.Player)) + Start(false, false, creature.GetOwner().GetGUID()); + else + Log.outError(LogFilter.Scripts, "Scripts: npc_ancestral_wolf can not obtain owner or owner is not a player."); + + creature.SetSpeed(UnitMoveType.Walk, 1.5f); + Reset(); + } + + public override void Reset() + { + ryga = null; + DoCast(me, AncestralWolf.SpellAncestralWoldBuff, true); + } + + public override void MoveInLineOfSight(Unit who) + { + if (!ryga && who.GetEntry() == AncestralWolf.NpcRyga && me.IsWithinDistInMap(who, 15.0f)) + { + Creature temp = who.ToCreature(); + if (temp) + ryga = temp; + } + + base.MoveInLineOfSight(who); + } + + public override void WaypointReached(uint waypointId) + { + switch (waypointId) + { + case 0: + Talk(AncestralWolf.EmoteWoldLiftHead); + break; + case 2: + Talk(AncestralWolf.EmoteWolfHowl); + break; + case 50: + if (ryga && ryga.IsAlive() && !ryga.IsInCombat()) + ryga.GetAI().Talk(AncestralWolf.SayWolfWelcome); + break; + } + } + + Creature ryga; + } + + public override CreatureAI GetAI(Creature creature) + { + return new npc_ancestral_wolfAI(creature); + } + } + + [Script] + class npc_wounded_blood_elf : CreatureScript + { + const uint SAY_ELF_START = 0; + const uint SAY_ELF_SUMMON1 = 1; + const uint SAY_ELF_RESTING = 2; + const uint SAY_ELF_SUMMON2 = 3; + const uint SAY_ELF_COMPLETE = 4; + const uint SAY_ELF_AGGRO = 5; + const uint QUEST_ROAD_TO_FALCON_WATCH = 9375; + const uint NPC_HAALESHI_WINDWALKER = 16966; + const uint NPC_HAALESHI_TALONGUARD = 16967; + const uint FACTION_FALCON_WATCH_QUEST = 775; + + public npc_wounded_blood_elf() : base("npc_wounded_blood_elf") { } + + class npc_wounded_blood_elfAI : npc_escortAI + { + public npc_wounded_blood_elfAI(Creature creature) : base(creature) { } + + public override void Reset() { } + + public override void EnterCombat(Unit who) + { + if (HasEscortState(eEscortState.Escorting)) + Talk(SAY_ELF_AGGRO); + } + + public override void JustSummoned(Creature summoned) + { + summoned.GetAI().AttackStart(me); + } + + public override void sQuestAccept(Player player, Quest quest) + { + if (quest.Id == QUEST_ROAD_TO_FALCON_WATCH) + { + me.SetFaction(FACTION_FALCON_WATCH_QUEST); + base.Start(true, false, player.GetGUID()); + } + } + + public override void WaypointReached(uint waypointId) + { + Player player = GetPlayerForEscort(); + if (!player) + return; + + switch (waypointId) + { + case 0: + Talk(SAY_ELF_START, player); + break; + case 9: + Talk(SAY_ELF_SUMMON1, player); + // Spawn two Haal'eshi Talonguard + DoSpawnCreature(NPC_HAALESHI_TALONGUARD, -15, -15, 0, 0, TempSummonType.TimedDespawnOOC, 5000); + DoSpawnCreature(NPC_HAALESHI_TALONGUARD, -17, -17, 0, 0, TempSummonType.TimedDespawnOOC, 5000); + break; + case 13: + Talk(SAY_ELF_RESTING, player); + break; + case 14: + Talk(SAY_ELF_SUMMON2, player); + // Spawn two Haal'eshi Windwalker + DoSpawnCreature(NPC_HAALESHI_WINDWALKER, -15, -15, 0, 0, TempSummonType.TimedDespawnOOC, 5000); + DoSpawnCreature(NPC_HAALESHI_WINDWALKER, -17, -17, 0, 0, TempSummonType.TimedDespawnOOC, 5000); + break; + case 27: + Talk(SAY_ELF_COMPLETE, player); + // Award quest credit + player.GroupEventHappens(QUEST_ROAD_TO_FALCON_WATCH, me); + break; + } + } + } + + public override CreatureAI GetAI(Creature creature) + { + return new npc_wounded_blood_elfAI(creature); + } + } + + [Script] + class npc_fel_guard_hound : CreatureScript + { + const uint SPELL_SUMMON_POO = 37688; + const uint NPC_DERANGED_HELBOAR = 16863; + + public npc_fel_guard_hound() : base("npc_fel_guard_hound") { } + + class npc_fel_guard_houndAI : ScriptedAI + { + public npc_fel_guard_houndAI(Creature creature) : base(creature) { } + + public override void Reset() + { + checkTimer = 5000; //check for creature every 5 sec + helboarGUID.Clear(); + } + + public override void MovementInform(MovementGeneratorType type, uint id) + { + if (type != MovementGeneratorType.Point || id != 1) + return; + + Creature helboar = me.GetMap().GetCreature(helboarGUID); + if (helboar) + { + helboar.RemoveCorpse(); + DoCast(SPELL_SUMMON_POO); + + Player owner = me.GetCharmerOrOwnerPlayerOrPlayerItself(); + if (owner) + me.GetMotionMaster().MoveFollow(owner, 0.0f, 0.0f); + } + } + + public override void UpdateAI(uint diff) + { + if (checkTimer <= diff) + { + Creature helboar = me.FindNearestCreature(NPC_DERANGED_HELBOAR, 10.0f, false); + if (helboar) + { + if (helboar.GetGUID() != helboarGUID && me.GetMotionMaster().GetCurrentMovementGeneratorType() != MovementGeneratorType.Point && !me.FindCurrentSpellBySpellId(SPELL_SUMMON_POO)) + { + helboarGUID = helboar.GetGUID(); + me.GetMotionMaster().MovePoint(1, helboar.GetPositionX(), helboar.GetPositionY(), helboar.GetPositionZ()); + } + } + checkTimer = 5000; + } + else checkTimer -= diff; + + if (!UpdateVictim()) + return; + + DoMeleeAttackIfReady(); + } + + uint checkTimer; + ObjectGuid helboarGUID; + } + + public override CreatureAI GetAI(Creature creature) + { + return new npc_fel_guard_houndAI(creature); + } + } +} + diff --git a/Scripts/Outlands/Zangarmarsh.cs b/Scripts/Outlands/Zangarmarsh.cs new file mode 100644 index 000000000..7746543fb --- /dev/null +++ b/Scripts/Outlands/Zangarmarsh.cs @@ -0,0 +1,402 @@ +/* + * Copyright (C) 2012-2017 CypherCore + * + * 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, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +using Framework.Constants; +using Game; +using Game.AI; +using Game.Entities; +using Game.Scripting; +using System.Collections.Generic; + +namespace Scripts.Outlands +{ + [Script] + class npcs_ashyen_and_keleth : CreatureScript + { + public npcs_ashyen_and_keleth() : base("npcs_ashyen_and_keleth") { } + + public override bool OnGossipHello(Player player, Creature creature) + { + if (player.GetReputationRank(942) > ReputationRank.Neutral) + { + if (creature.GetEntry() == NPC_ASHYEN) + player.ADD_GOSSIP_ITEM(GossipOptionIcon.Chat, GOSSIP_ITEM_BLESS_ASH, eTradeskill.GossipSenderMain, eTradeskill.GossipActionInfoDef + 1); + + if (creature.GetEntry() == NPC_KELETH) + player.ADD_GOSSIP_ITEM(GossipOptionIcon.Chat, GOSSIP_ITEM_BLESS_KEL, eTradeskill.GossipSenderMain, eTradeskill.GossipActionInfoDef + 1); + } + player.SEND_GOSSIP_MENU(player.GetGossipTextId(creature), creature.GetGUID()); + + return true; + } + + public override bool OnGossipSelect(Player player, Creature creature, uint sender, uint action) + { + player.PlayerTalkClass.ClearMenus(); + if (action == eTradeskill.GossipActionInfoDef + 1) + { + creature.setPowerType(PowerType.Mana); + creature.SetMaxPower(PowerType.Mana, 200); //set a "fake" mana value, we can't depend on database doing it in this case + creature.SetPower(PowerType.Mana, 200); + + if (creature.GetEntry() == NPC_ASHYEN) //check which Creature we are dealing with + { + uint spell = 0; + switch (player.GetReputationRank(942)) + { //mark of lore + case ReputationRank.Friendly: + spell = SPELL_BLESS_ASH_FRI; + break; + case ReputationRank.Honored: + spell = SPELL_BLESS_ASH_HON; + break; + case ReputationRank.Revered: + spell = SPELL_BLESS_ASH_REV; + break; + case ReputationRank.Exalted: + spell = SPELL_BLESS_ASH_EXA; + break; + default: + break; + } + + if (spell != 0) + { + creature.CastSpell(player, spell, true); + creature.GetAI().Talk(GOSSIP_REWARD_BLESS); + } + } + + if (creature.GetEntry() == NPC_KELETH) + { + uint spell = 0; + switch (player.GetReputationRank(942)) //mark of war + { + case ReputationRank.Friendly: + spell = SPELL_BLESS_KEL_FRI; + break; + case ReputationRank.Honored: + spell = SPELL_BLESS_KEL_HON; + break; + case ReputationRank.Revered: + spell = SPELL_BLESS_KEL_REV; + break; + case ReputationRank.Exalted: + spell = SPELL_BLESS_KEL_EXA; + break; + default: + break; + } + + if (spell != 0) + { + creature.CastSpell(player, spell, true); + creature.GetAI().Talk(GOSSIP_REWARD_BLESS); + } + } + player.CLOSE_GOSSIP_MENU(); + player.TalkedToCreature(creature.GetEntry(), creature.GetGUID()); + } + return true; + } + + const uint GOSSIP_REWARD_BLESS = 0; + + const uint NPC_ASHYEN = 17900; + const uint NPC_KELETH = 17901; + + const uint SPELL_BLESS_ASH_EXA = 31815; + const uint SPELL_BLESS_ASH_REV = 31811; + const uint SPELL_BLESS_ASH_HON = 31810; + const uint SPELL_BLESS_ASH_FRI = 31808; + + const uint SPELL_BLESS_KEL_EXA = 31814; + const uint SPELL_BLESS_KEL_REV = 31813; + const uint SPELL_BLESS_KEL_HON = 31812; + const uint SPELL_BLESS_KEL_FRI = 31807; + + const string GOSSIP_ITEM_BLESS_ASH = "Grant me your mark, wise ancient."; + const string GOSSIP_ITEM_BLESS_KEL = "Grant me your mark, mighty ancient."; + } + + [Script] + class npc_cooshcoosh : CreatureScript + { + public npc_cooshcoosh() : base("npc_cooshcoosh") { } + + class npc_cooshcooshAI : ScriptedAI + { + public npc_cooshcooshAI(Creature creature) + : base(creature) + { + m_uiNormFaction = creature.getFaction(); + } + + uint m_uiNormFaction; + + public override void Reset() + { + _events.ScheduleEvent(Event_LightningBolt, 2000); + if (me.getFaction() != m_uiNormFaction) + me.SetFaction(m_uiNormFaction); + } + + public override void EnterCombat(Unit who) { } + + public override void UpdateAI(uint diff) + { + if (!UpdateVictim()) + return; + + _events.Update(diff); + + _events.ExecuteEvents(id => + { + DoCastVictim(SPELL_LIGHTNING_BOLT); + _events.ScheduleEvent(Event_LightningBolt, 5000); + }); + + DoMeleeAttackIfReady(); + } + } + + public override CreatureAI GetAI(Creature creature) + { + return new npc_cooshcooshAI(creature); + } + + public override bool OnGossipHello(Player player, Creature creature) + { + if (player.GetQuestStatus(QUEST_CRACK_SKULLS) == QuestStatus.Incomplete) + player.ADD_GOSSIP_ITEM(GossipOptionIcon.Chat, GOSSIP_COOSH, eTradeskill.GossipSenderMain, eTradeskill.GossipActionInfoDef); + + player.SEND_GOSSIP_MENU(9441, creature.GetGUID()); + return true; + } + + public override bool OnGossipSelect(Player player, Creature creature, uint sender, uint action) + { + player.PlayerTalkClass.ClearMenus(); + if (action == eTradeskill.GossipActionInfoDef) + { + player.CLOSE_GOSSIP_MENU(); + creature.SetFaction(FACTION_HOSTILE_CO); + creature.GetAI().AttackStart(player); + } + return true; + } + + const uint SPELL_LIGHTNING_BOLT = 9532; + const uint QUEST_CRACK_SKULLS = 10009; + const uint FACTION_HOSTILE_CO = 45; + const int Event_LightningBolt = 1; + + const string GOSSIP_COOSH = "You owe Sim'salabim money. Hand them over or die!"; + } + + [Script] + class npc_elder_kuruti : CreatureScript + { + public npc_elder_kuruti() : base("npc_elder_kuruti") { } + + public override bool OnGossipHello(Player player, Creature creature) + { + if (player.GetQuestStatus(9803) == QuestStatus.Incomplete) + player.ADD_GOSSIP_ITEM(GossipOptionIcon.Chat, GOSSIP_ITEM_KUR1, eTradeskill.GossipSenderMain, eTradeskill.GossipActionInfoDef); + + player.SEND_GOSSIP_MENU(9226, creature.GetGUID()); + + return true; + } + + public override bool OnGossipSelect(Player player, Creature creature, uint sender, uint action) + { + player.PlayerTalkClass.ClearMenus(); + switch (action) + { + case eTradeskill.GossipActionInfoDef: + player.ADD_GOSSIP_ITEM(GossipOptionIcon.Chat, GOSSIP_ITEM_KUR2, eTradeskill.GossipSenderMain, eTradeskill.GossipActionInfoDef + 1); + player.SEND_GOSSIP_MENU(9227, creature.GetGUID()); + break; + case eTradeskill.GossipActionInfoDef + 1: + player.ADD_GOSSIP_ITEM(GossipOptionIcon.Chat, GOSSIP_ITEM_KUR3, eTradeskill.GossipSenderMain, eTradeskill.GossipActionInfoDef + 2); + player.SEND_GOSSIP_MENU(9229, creature.GetGUID()); + break; + case eTradeskill.GossipActionInfoDef + 2: + { + if (!player.HasItemCount(24573)) + { + List dest = new List(); + uint itemId = 24573; + uint temp; + InventoryResult msg = player.CanStoreNewItem(ItemConst.NullBag, ItemConst.NullSlot, dest, itemId, 1, out temp); + if (msg == InventoryResult.Ok) + { + player.StoreNewItem(dest, itemId, true); + } + else + player.SendEquipError(msg, null, null, itemId); + } + player.SEND_GOSSIP_MENU(9231, creature.GetGUID()); + break; + } + } + return true; + } + + const string GOSSIP_ITEM_KUR1 = "Greetings, elder. It is time for your people to end their hostility towards the draenei and their allies."; + const string GOSSIP_ITEM_KUR2 = "I did not mean to deceive you, elder. The draenei of Telredor thought to approach you in a way that would seem familiar to you."; + const string GOSSIP_ITEM_KUR3 = "I will tell them. Farewell, elder."; + } + + [Script] + class npc_mortog_steamhead : CreatureScript + { + public npc_mortog_steamhead() : base("npc_mortog_steamhead") { } + + public override bool OnGossipHello(Player player, Creature creature) + { + if (creature.IsVendor() && player.GetReputationRank(942) == ReputationRank.Exalted) + player.ADD_GOSSIP_ITEM(GossipOptionIcon.Vendor, GOSSIP_TEXT_BROWSE_GOODS, eTradeskill.GossipSenderMain, eTradeskill.GossipActionTrade); + + player.SEND_GOSSIP_MENU(player.GetGossipTextId(creature), creature.GetGUID()); + + return true; + } + + public override bool OnGossipSelect(Player player, Creature creature, uint sender, uint action) + { + player.PlayerTalkClass.ClearMenus(); + if (action == eTradeskill.GossipActionTrade) + player.GetSession().SendListInventory(creature.GetGUID()); + return true; + } + + const string GOSSIP_TEXT_BROWSE_GOODS = "I'd like to browse your goods."; + } + + [Script] + class npc_kayra_longmane : CreatureScript + { + public npc_kayra_longmane() : base("npc_kayra_longmane") { } + + class npc_kayra_longmaneAI : npc_escortAI + { + public npc_kayra_longmaneAI(Creature creature) : base(creature) { } + + public override void Reset() { } + + public override void WaypointReached(uint waypointId) + { + Player player = GetPlayerForEscort(); + if (!player) + return; + + switch (waypointId) + { + case 4: + Talk(SAY_AMBUSH1, player); + DoSpawnCreature(NPC_SLAVEBINDER, -10.0f, -5.0f, 0.0f, 0.0f, TempSummonType.TimedDespawnOOC, 30000); + DoSpawnCreature(NPC_SLAVEBINDER, -8.0f, 5.0f, 0.0f, 0.0f, TempSummonType.TimedDespawnOOC, 30000); + break; + case 5: + Talk(SAY_PROGRESS, player); + SetRun(); + break; + case 16: + Talk(SAY_AMBUSH2, player); + DoSpawnCreature(NPC_SLAVEBINDER, -10.0f, -5.0f, 0.0f, 0.0f, TempSummonType.TimedDespawnOOC, 30000); + DoSpawnCreature(NPC_SLAVEBINDER, -8.0f, 5.0f, 0.0f, 0.0f, TempSummonType.TimedDespawnOOC, 30000); + break; + case 17: + SetRun(false); + break; + case 25: + Talk(SAY_END, player); + player.GroupEventHappens(QUEST_ESCAPE_FROM, me); + break; + } + } + } + + public override bool OnQuestAccept(Player player, Creature creature, Quest quest) + { + if (quest.Id == QUEST_ESCAPE_FROM) + { + creature.GetAI().Talk(SAY_START, player); + + npc_escortAI pEscortAI = (npc_kayra_longmaneAI)creature.GetAI(); + if (pEscortAI != null) + pEscortAI.Start(false, false, player.GetGUID()); + } + return true; + } + + public override CreatureAI GetAI(Creature creature) + { + return new npc_kayra_longmaneAI(creature); + } + + const uint SAY_START = 0; + const uint SAY_AMBUSH1 = 1; + const uint SAY_PROGRESS = 2; + const uint SAY_AMBUSH2 = 3; + const uint SAY_END = 4; + + const uint QUEST_ESCAPE_FROM = 9752; + const uint NPC_SLAVEBINDER = 18042; + } + + [Script] + class npc_timothy_daniels : CreatureScript + { + public npc_timothy_daniels() : base("npc_timothy_daniels") { } + + public override bool OnGossipHello(Player player, Creature creature) + { + if (creature.IsQuestGiver()) + player.PrepareQuestMenu(creature.GetGUID()); + + if (creature.IsVendor()) + player.ADD_GOSSIP_ITEM(GossipOptionIcon.Vendor, GOSSIP_TEXT_BROWSE_POISONS, eTradeskill.GossipSenderMain, eTradeskill.GossipActionTrade); + + player.ADD_GOSSIP_ITEM(GossipOptionIcon.Chat, GOSSIP_TIMOTHY_DANIELS_ITEM1, eTradeskill.GossipSenderMain, eTradeskill.GossipActionInfoDef + 1); + player.SEND_GOSSIP_MENU(player.GetGossipTextId(creature), creature.GetGUID()); + return true; + } + + public override bool OnGossipSelect(Player player, Creature creature, uint sender, uint action) + { + player.PlayerTalkClass.ClearMenus(); + switch (action) + { + case eTradeskill.GossipActionInfoDef + 1: + player.SEND_GOSSIP_MENU(GOSSIP_TEXTID_TIMOTHY_DANIELS1, creature.GetGUID()); + break; + case eTradeskill.GossipActionTrade: + player.GetSession().SendListInventory(creature.GetGUID()); + break; + } + + return true; + } + + const string GOSSIP_TIMOTHY_DANIELS_ITEM1 = "Specialist, eh? Just what kind of specialist are you, anyway?"; + const string GOSSIP_TEXT_BROWSE_POISONS = "Let me browse your reagents and poison supplies."; + const uint GOSSIP_TEXTID_TIMOTHY_DANIELS1 = 9239; + } +} diff --git a/Scripts/Pets/DeathKinght.cs b/Scripts/Pets/DeathKinght.cs new file mode 100644 index 000000000..57289fb9e --- /dev/null +++ b/Scripts/Pets/DeathKinght.cs @@ -0,0 +1,137 @@ +/* + * Copyright (C) 2012-2017 CypherCore + * + * 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, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +using Framework.Constants; +using Game.AI; +using Game.Entities; +using Game.Maps; +using Game.Scripting; +using Game.Spells; +using System; +using System.Collections.Generic; + +namespace Scripts.Pets +{ + [Script] + class npc_pet_dk_ebon_gargoyle : CreatureScript + { + public npc_pet_dk_ebon_gargoyle() : base("npc_pet_dk_ebon_gargoyle") { } + + class npc_pet_dk_ebon_gargoyleAI : CasterAI + { + public npc_pet_dk_ebon_gargoyleAI(Creature creature) : base(creature) { } + + public override void InitializeAI() + { + base.InitializeAI(); + ObjectGuid ownerGuid = me.GetOwnerGUID(); + if (ownerGuid.IsEmpty()) + return; + + // Find victim of Summon Gargoyle spell + List targets = new List(); + var u_check = new AnyUnfriendlyUnitInObjectRangeCheck(me, me, 30.0f); + var searcher = new UnitListSearcher(me, targets, u_check); + Cell.VisitAllObjects(me, searcher, 30.0f); + foreach (var iter in targets) + { + if (iter.GetAura(SpellSummonGargoyle1, ownerGuid) != null) + { + me.Attack(iter, false); + break; + } + } + } + + public override void JustDied(Unit killer) + { + // Stop Feeding Gargoyle when it dies + Unit owner = me.GetOwner(); + if (owner) + owner.RemoveAurasDueToSpell(SpellSummonGargoyle2); + } + + // Fly away when dismissed + public override void SpellHit(Unit source, SpellInfo spell) + { + if (spell.Id != SpellDismissGargoyle || !me.IsAlive()) + return; + + Unit owner = me.GetOwner(); + if (!owner || owner != source) + return; + + // Stop Fighting + me.ApplyModFlag(UnitFields.Flags, UnitFlags.NonAttackable, true); + + // Sanctuary + me.CastSpell(me, SpellSanctuary, true); + me.SetReactState(ReactStates.Passive); + + //! HACK: Creature's can't have MOVEMENTFLAG_FLYING + // Fly Away + me.SetCanFly(true); + me.SetSpeedRate(UnitMoveType.Flight, 0.75f); + me.SetSpeedRate(UnitMoveType.Run, 0.75f); + float x = me.GetPositionX() + 20 * (float)Math.Cos(me.GetOrientation()); + float y = me.GetPositionY() + 20 * (float)Math.Sin(me.GetOrientation()); + float z = me.GetPositionZ() + 40; + me.GetMotionMaster().Clear(false); + me.GetMotionMaster().MovePoint(0, x, y, z); + + // Despawn as soon as possible + me.DespawnOrUnsummon(4 * Time.InMilliseconds); + } + + const uint SpellSummonGargoyle1 = 49206; + const uint SpellSummonGargoyle2 = 50514; + const uint SpellDismissGargoyle = 50515; + const uint SpellSanctuary = 54661; + } + + public override CreatureAI GetAI(Creature creature) + { + return new npc_pet_dk_ebon_gargoyleAI(creature); + } + } + + [Script] + class npc_pet_dk_guardian : CreatureScript + { + public npc_pet_dk_guardian() : base("npc_pet_dk_guardian") { } + + class npc_pet_dk_guardianAI : AggressorAI + { + public npc_pet_dk_guardianAI(Creature creature) : base(creature) { } + + public override bool CanAIAttack(Unit target) + { + if (!target) + return false; + Unit owner = me.GetOwner(); + if (owner && !target.IsInCombatWith(owner)) + return false; + return base.CanAIAttack(target); + } + } + + public override CreatureAI GetAI(Creature creature) + { + return new npc_pet_dk_guardianAI(creature); + } + } +} diff --git a/Scripts/Pets/Generic.cs b/Scripts/Pets/Generic.cs new file mode 100644 index 000000000..53c26f39b --- /dev/null +++ b/Scripts/Pets/Generic.cs @@ -0,0 +1,85 @@ +/* + * Copyright (C) 2012-2017 CypherCore + * + * 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, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +using Framework.Constants; +using Game.AI; +using Game.Entities; +using Game.Scripting; + +namespace Scripts.Pets +{ + [Script] + class npc_pet_gen_mojo : CreatureScript + { + public npc_pet_gen_mojo() : base("npc_pet_gen_mojo") { } + + class npc_pet_gen_mojoAI : ScriptedAI + { + public npc_pet_gen_mojoAI(Creature creature) : base(creature) { } + + public override void Reset() + { + _victimGUID.Clear(); + + Unit owner = me.GetOwner(); + if (owner) + me.GetMotionMaster().MoveFollow(owner, 0.0f, 0.0f); + } + + public override void EnterCombat(Unit who) { } + + public override void UpdateAI(uint diff) { } + + public override void ReceiveEmote(Player player, TextEmotes emote) + { + me.HandleEmoteCommand((Emote)emote); + Unit owner = me.GetOwner(); + if (emote != TextEmotes.Kiss || !owner || !owner.IsTypeId(TypeId.Player) || + owner.ToPlayer().GetTeam() != player.GetTeam()) + { + return; + } + + Talk(SayMojo, player); + + if (!_victimGUID.IsEmpty()) + { + Player victim = Global.ObjAccessor.GetPlayer(me, _victimGUID); + if (victim) + victim.RemoveAura(SpellFeelingFroggy); + } + + _victimGUID = player.GetGUID(); + + DoCast(player, SpellFeelingFroggy, true); + DoCast(me, SpellSeductionVisual, true); + me.GetMotionMaster().MoveFollow(player, 0.0f, 0.0f); + } + + ObjectGuid _victimGUID; + + const uint SayMojo = 0; + const uint SpellFeelingFroggy = 43906; + const uint SpellSeductionVisual = 43919; + } + + public override CreatureAI GetAI(Creature creature) + { + return new npc_pet_gen_mojoAI(creature); + } + } +} diff --git a/Scripts/Pets/Hunter.cs b/Scripts/Pets/Hunter.cs new file mode 100644 index 000000000..1f68e6a1f --- /dev/null +++ b/Scripts/Pets/Hunter.cs @@ -0,0 +1,127 @@ +/* + * Copyright (C) 2012-2017 CypherCore + * + * 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, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +using Framework.Constants; +using Game.AI; +using Game.Entities; +using Game.Scripting; + +namespace Scripts.Pets +{ + [Script] + class npc_pet_hunter_snake_trap : CreatureScript + { + public npc_pet_hunter_snake_trap() : base("npc_pet_hunter_snake_trap") { } + + class npc_pet_hunter_snake_trapAI : ScriptedAI + { + public npc_pet_hunter_snake_trapAI(Creature creature) : base(creature) { } + + public override void EnterCombat(Unit who) { } + + public override void Reset() + { + _spellTimer = 0; + + CreatureTemplate Info = me.GetCreatureTemplate(); + + _isViper = Info.Entry == NpcViper ? true : false; + + me.SetMaxHealth((uint)(107 * (me.getLevel() - 40) * 0.025f)); + // Add delta to make them not all hit the same time + uint delta = (RandomHelper.Rand32() % 7) * 100; + me.SetStatFloatValue(UnitFields.BaseAttackTime, Info.BaseAttackTime + delta); + //me.SetStatFloatValue(UnitFields.RangedAttackPower, (float)Info.AttackPower); + + // Start attacking attacker of owner on first ai update after spawn - move in line of sight may choose better target + if (!me.GetVictim() && me.IsSummon()) + { + Unit owner = me.ToTempSummon().GetSummoner(); + if (owner) + if (owner.getAttackerForHelper()) + AttackStart(owner.getAttackerForHelper()); + } + + if (!_isViper) + DoCast(me, SpellDeadlyPoisonPassive, true); + } + + // Redefined for random target selection: + public override void MoveInLineOfSight(Unit who) + { + if (!me.GetVictim() && me.CanCreatureAttack(who)) + { + if (me.GetDistanceZ(who) > SharedConst.CreatureAttackRangeZ) + return; + + float attackRadius = me.GetAttackDistance(who); + if (me.IsWithinDistInMap(who, attackRadius) && me.IsWithinLOSInMap(who)) + { + if ((RandomHelper.Rand32() % 5) == 0) + { + me.setAttackTimer(WeaponAttackType.BaseAttack, (RandomHelper.Rand32() % 10) * 100); + _spellTimer = (RandomHelper.Rand32() % 10) * 100; + AttackStart(who); + } + } + } + } + + public override void UpdateAI(uint diff) + { + if (!UpdateVictim() || !me.GetVictim()) + return; + + if (me.GetVictim().HasBreakableByDamageCrowdControlAura(me)) + { + me.InterruptNonMeleeSpells(false); + return; + } + + //Viper + if (_isViper) + { + if (_spellTimer <= diff) + { + if (RandomHelper.IRand(0, 2) == 0) //33% chance to cast + DoCastVictim(RandomHelper.RAND(SpellMindNumbingPoison, SpellCripplingPoison)); + + _spellTimer = 3000; + } + else + _spellTimer -= diff; + } + + DoMeleeAttackIfReady(); + } + + bool _isViper; + uint _spellTimer; + } + + public override CreatureAI GetAI(Creature creature) + { + return new npc_pet_hunter_snake_trapAI(creature); + } + + const uint SpellCripplingPoison = 30981; // Viper + const uint SpellDeadlyPoisonPassive = 34657; // Venomous Snake + const uint SpellMindNumbingPoison = 25810; // Viper + + const int NpcViper = 19921; + } +} diff --git a/Scripts/Pets/Mage.cs b/Scripts/Pets/Mage.cs new file mode 100644 index 000000000..d3a2cdc5f --- /dev/null +++ b/Scripts/Pets/Mage.cs @@ -0,0 +1,252 @@ +/* + * Copyright (C) 2012-2017 CypherCore + * + * 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, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +using Framework.Constants; +using Game.AI; +using Game.Entities; +using Game.Maps; +using Game.Scripting; +using System.Collections.Generic; + +namespace Scripts.Pets +{ + struct PetMageConst + { + public const uint SpellCloneMe = 45204; + public const uint SpellMastersThreatList = 58838; + public const uint SpellMageFrostBolt = 59638; + public const uint SpellMageFireBlast = 59637; + + + public const uint TimerMirrorImageInit = 0; + public const uint TimerMirrorImageFrostBolt = 4000; + public const uint TimerMirrorImageFireBlast = 6000; + } + + + [Script] + class npc_pet_mage_mirror_image : CreatureScript + { + public npc_pet_mage_mirror_image() : base("npc_pet_mage_mirror_image") { } + + class npc_pet_mage_mirror_imageAI : CasterAI + { + public npc_pet_mage_mirror_imageAI(Creature creature) : base(creature) { } + + void Init() + { + Unit owner = me.GetCharmerOrOwner(); + + List targets = new List(); + var u_check = new AnyUnfriendlyUnitInObjectRangeCheck(me, me, 30.0f); + var searcher = new UnitListSearcher(me, targets, u_check); + Cell.VisitAllObjects(me, searcher, 40.0f); + + Unit highestThreatUnit = null; + float highestThreat = 0.0f; + Unit nearestPlayer = null; + foreach (var unit in targets) + { + // Consider only units without CC + if (!unit.HasBreakableByDamageCrowdControlAura(unit)) + { + // Take first found unit + if (!highestThreatUnit && !unit.IsTypeId(TypeId.Player)) + { + highestThreatUnit = unit; + continue; + } + if (!nearestPlayer && unit.IsTypeId(TypeId.Player)) + { + nearestPlayer = unit; + continue; + } + // else compare best fit unit with current unit + var triggers = unit.GetThreatManager().getThreatList(); + foreach (var reference in triggers) + { + // Try to find threat referenced to owner + if (reference.getTarget() == owner) + { + // Check if best fit hostile unit hs lower threat than this current unit + if (highestThreat < reference.getThreat()) + { + // If so, update best fit unit + highestThreat = reference.getThreat(); + highestThreatUnit = unit; + break; + } + } + } + // In case no unit with threat was found so far, always check for nearest unit (only for players) + if (unit.IsTypeId(TypeId.Player)) + { + // If this player is closer than the previous one, update it + if (me.GetDistance(unit.GetPosition()) < me.GetDistance(nearestPlayer.GetPosition())) + nearestPlayer = unit; + } + } + } + // Prioritize units with threat referenced to owner + if (highestThreat > 0.0f && highestThreatUnit) + me.Attack(highestThreatUnit, false); + // If there is no such target, try to attack nearest hostile unit if such exists + else if (nearestPlayer) + me.Attack(nearestPlayer, false); + } + + bool IsInThreatList(Unit target) + { + Unit owner = me.GetCharmerOrOwner(); + + List targets = new List(); + var u_check = new AnyUnfriendlyUnitInObjectRangeCheck(me, me, 30.0f); + var searcher = new UnitListSearcher(me, targets, u_check); + Cell.VisitAllObjects(me, searcher, 40.0f); + + foreach (var unit in targets) + { + if (unit == target) + { + // Consider only units without CC + if (!unit.HasBreakableByDamageCrowdControlAura(unit)) + { + var triggers = unit.GetThreatManager().getThreatList(); + foreach (var reference in triggers) + { + // Try to find threat referenced to owner + if (reference.getTarget() == owner) + return true; + } + } + } + } + return false; + } + + public override void InitializeAI() + { + base.InitializeAI(); + Unit owner = me.GetOwner(); + if (!owner) + return; + + // here mirror image casts on summoner spell (not present in client dbc) 49866 + // here should be auras (not present in client dbc): 35657, 35658, 35659, 35660 selfcasted by mirror images (stats related?) + // Clone Me! + owner.CastSpell(me, PetMageConst.SpellCloneMe, false); + } + + public override void EnterCombat(Unit victim) + { + if (me.GetVictim() && !me.GetVictim().HasBreakableByDamageCrowdControlAura(me)) + { + me.CastSpell(victim, PetMageConst.SpellMageFireBlast, false); + _events.ScheduleEvent(PetMageConst.SpellMageFrostBolt, PetMageConst.TimerMirrorImageInit); + _events.ScheduleEvent(PetMageConst.SpellMageFireBlast, PetMageConst.TimerMirrorImageFireBlast); + } + else + EnterEvadeMode(EvadeReason.Other); + } + + public override void Reset() + { + _events.Reset(); + } + + public override void UpdateAI(uint diff) + { + Unit owner = me.GetCharmerOrOwner(); + if (!owner) + return; + + Unit target = owner.getAttackerForHelper(); + + _events.Update(diff); + + // prevent CC interrupts by images + if (me.GetVictim() && me.GetVictim().HasBreakableByDamageCrowdControlAura(me)) + { + me.InterruptNonMeleeSpells(false); + return; + } + + if (me.HasUnitState(UnitState.Casting)) + return; + + // assign target if image doesnt have any or the target is not actual + if (!target || me.GetVictim() != target) + { + Unit ownerTarget = null; + Player owner1 = me.GetCharmerOrOwner().ToPlayer(); + if (owner1) + ownerTarget = owner1.GetSelectedUnit(); + + // recognize which victim will be choosen + if (ownerTarget && ownerTarget.IsTypeId(TypeId.Player)) + { + if (!ownerTarget.HasBreakableByDamageCrowdControlAura(ownerTarget)) + me.Attack(ownerTarget, false); + } + else if (ownerTarget && !ownerTarget.IsTypeId(TypeId.Player) && IsInThreatList(ownerTarget)) + { + if (!ownerTarget.HasBreakableByDamageCrowdControlAura(ownerTarget)) + me.Attack(ownerTarget, false); + } + else + Init(); + } + + _events.ExecuteEvents(spellId => + { + if (spellId == PetMageConst.SpellMageFrostBolt) + { + _events.ScheduleEvent(PetMageConst.SpellMageFrostBolt, PetMageConst.TimerMirrorImageFrostBolt); + DoCastVictim(spellId); + } + else if (spellId == PetMageConst.SpellMageFireBlast) + { + DoCastVictim(spellId); + _events.ScheduleEvent(PetMageConst.SpellMageFireBlast, PetMageConst.TimerMirrorImageFireBlast); + } + }); + } + + // Do not reload Creature templates on evade mode enter - prevent visual lost + public override void EnterEvadeMode(EvadeReason why) + { + if (me.IsInEvadeMode() || !me.IsAlive()) + return; + + Unit owner = me.GetCharmerOrOwner(); + + me.CombatStop(true); + if (owner && !me.HasUnitState(UnitState.Follow)) + { + me.GetMotionMaster().Clear(false); + me.GetMotionMaster().MoveFollow(owner, SharedConst.PetFollowDist, me.GetFollowAngle(), MovementSlot.Active); + } + Init(); + } + } + + public override CreatureAI GetAI(Creature creature) + { + return new npc_pet_mage_mirror_imageAI(creature); + } + } +} diff --git a/Scripts/Pets/Priest.cs b/Scripts/Pets/Priest.cs new file mode 100644 index 000000000..12608dca2 --- /dev/null +++ b/Scripts/Pets/Priest.cs @@ -0,0 +1,83 @@ +/* + * Copyright (C) 2012-2017 CypherCore + * + * 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, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +using Framework.Constants; +using Game.AI; +using Game.Entities; +using Game.Scripting; + +namespace Scripts.Pets.Priest +{ + struct SpellIds + { + public const uint GlyphOfShadowFiend = 58228; + public const uint ShadowFiendDeath = 57989; + public const uint LightWellCharges = 59907; + } + + [Script] + class npc_pet_pri_lightwell : CreatureScript + { + public npc_pet_pri_lightwell() : base("npc_pet_pri_lightwell") { } + + class npc_pet_pri_lightwellAI : PassiveAI + { + public npc_pet_pri_lightwellAI(Creature creature) : base(creature) + { + DoCast(creature, SpellIds.LightWellCharges, false); + } + + public override void EnterEvadeMode(EvadeReason why) + { + if (!me.IsAlive()) + return; + + me.DeleteThreatList(); + me.CombatStop(true); + me.ResetPlayerDamageReq(); + } + } + + public override CreatureAI GetAI(Creature creature) + { + return new npc_pet_pri_lightwellAI(creature); + } + } + + [Script] + class npc_pet_pri_shadowfiend : CreatureScript + { + public npc_pet_pri_shadowfiend() : base("npc_pet_pri_shadowfiend") { } + + class npc_pet_pri_shadowfiendAI : PetAI + { + public npc_pet_pri_shadowfiendAI(Creature creature) : base(creature) { } + + public override void IsSummonedBy(Unit summoner) + { + if (summoner.HasAura(SpellIds.GlyphOfShadowFiend)) + DoCastAOE(SpellIds.ShadowFiendDeath); + } + + } + + public override CreatureAI GetAI(Creature creature) + { + return new npc_pet_pri_shadowfiendAI(creature); + } + } +} diff --git a/Scripts/Pets/Shaman.cs b/Scripts/Pets/Shaman.cs new file mode 100644 index 000000000..37a6eb0d9 --- /dev/null +++ b/Scripts/Pets/Shaman.cs @@ -0,0 +1,133 @@ +/* + * Copyright (C) 2012-2017 CypherCore + * + * 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, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +using Framework.Constants; +using Game.AI; +using Game.Entities; +using Game.Scripting; + +namespace Scripts.Pets +{ + [Script] + class npc_pet_shaman_earth_elemental : CreatureScript + { + public npc_pet_shaman_earth_elemental() : base("npc_pet_shaman_earth_elemental") { } + + class npc_pet_shaman_earth_elementalAI : ScriptedAI + { + public npc_pet_shaman_earth_elementalAI(Creature creature) : base(creature) { } + + public override void Reset() + { + _events.Reset(); + _events.ScheduleEvent(EventAngeredEarth, 0); + me.ApplySpellImmune(0, SpellImmunity.School, SpellSchoolMask.Nature, true); + } + + public override void UpdateAI(uint diff) + { + if (!UpdateVictim()) + return; + + _events.Update(diff); + + if (_events.ExecuteEvent() == EventAngeredEarth) + { + DoCastVictim(SpellAngeredEarth); + _events.ScheduleEvent(EventAngeredEarth, RandomHelper.URand(5000, 20000)); + } + + DoMeleeAttackIfReady(); + } + + const int EventAngeredEarth = 1; + const uint SpellAngeredEarth = 36213; + } + + public override CreatureAI GetAI(Creature creature) + { + return new npc_pet_shaman_earth_elementalAI(creature); + } + } + + [Script] + class npc_pet_shaman_fire_elemental : CreatureScript + { + public npc_pet_shaman_fire_elemental() : base("npc_pet_shaman_fire_elemental") { } + + public class npc_pet_shaman_fire_elementalAI : ScriptedAI + { + public npc_pet_shaman_fire_elementalAI(Creature creature) : base(creature) { } + + public override void Reset() + { + _events.Reset(); + _events.ScheduleEvent(EventFireNova, RandomHelper.URand(5000, 20000)); + _events.ScheduleEvent(EventFireBlast, RandomHelper.URand(5000, 20000)); + _events.ScheduleEvent(EventFireShield, 0); + me.ApplySpellImmune(0, SpellImmunity.School, SpellSchoolMask.Fire, true); + } + + public override void UpdateAI(uint diff) + { + if (!UpdateVictim()) + return; + + if (me.HasUnitState(UnitState.Casting)) + return; + + _events.Update(diff); + + _events.ExecuteEvents(eventId => + { + switch (eventId) + { + case EventFireNova: + DoCastVictim(SpellFireNova); + _events.ScheduleEvent(EventFireNova, RandomHelper.URand(5000, 20000)); + break; + case EventFireShield: + DoCastVictim(SpellFireShield); + _events.ScheduleEvent(EventFireShield, 2000); + break; + case EventFireBlast: + DoCastVictim(SpellFireBlast); + _events.ScheduleEvent(EventFireBlast, RandomHelper.URand(5000, 20000)); + break; + default: + break; + } + }); + + DoMeleeAttackIfReady(); + } + + const int EventFireNova = 1; + const int EventFireShield = 2; + const int EventFireBlast = 3; + + const uint SpellFireBlast = 57984; + const uint SpellFireNova = 12470; + const uint SpellFireShield = 13376; + } + + public override CreatureAI GetAI(Creature creature) + { + return new npc_pet_shaman_fire_elementalAI(creature); + } + } +} diff --git a/Scripts/Properties/AssemblyInfo.cs b/Scripts/Properties/AssemblyInfo.cs new file mode 100644 index 000000000..f64984b4b --- /dev/null +++ b/Scripts/Properties/AssemblyInfo.cs @@ -0,0 +1,36 @@ +using System.Reflection; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; + +// General Information about an assembly is controlled through the following +// set of attributes. Change these attribute values to modify the information +// associated with an assembly. +[assembly: AssemblyTitle("Scripts")] +[assembly: AssemblyDescription("")] +[assembly: AssemblyConfiguration("")] +[assembly: AssemblyCompany("")] +[assembly: AssemblyProduct("Scripts")] +[assembly: AssemblyCopyright("Copyright © 2014")] +[assembly: AssemblyTrademark("")] +[assembly: AssemblyCulture("")] + +// Setting ComVisible to false makes the types in this assembly not visible +// to COM components. If you need to access a type in this assembly from +// COM, set the ComVisible attribute to true on that type. +[assembly: ComVisible(false)] + +// The following GUID is for the ID of the typelib if this project is exposed to COM +[assembly: Guid("92f1ec3f-c8fc-4423-91ee-00ec1b5c5272")] + +// Version information for an assembly consists of the following four values: +// +// Major Version +// Minor Version +// Build Number +// Revision +// +// You can specify all the values or you can default the Build and Revision Numbers +// by using the '*' as shown below: +// [assembly: AssemblyVersion("1.0.*")] +[assembly: AssemblyVersion("1.0.0.0")] +[assembly: AssemblyFileVersion("1.0.0.0")] diff --git a/Scripts/Scripts.csproj b/Scripts/Scripts.csproj new file mode 100644 index 000000000..31e60cc9f --- /dev/null +++ b/Scripts/Scripts.csproj @@ -0,0 +1,193 @@ + + + + + Debug + AnyCPU + {9E28B9C1-E473-4DAE-813F-3243CE318736} + Library + Properties + Scripts + Scripts + v4.6.2 + 512 + + + + true + ..\Build\Debug\ + DEBUG;TRACE + full + x86 + prompt + false + false + + + ..\Build\Release\ + TRACE + true + pdbonly + x86 + prompt + + + true + ..\Build\x64\Debug\ + DEBUG;TRACE + full + x64 + prompt + + + bin\x64\Release\ + TRACE + true + pdbonly + x64 + prompt + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + {82d442a9-18c0-4c59-8ec6-0dfe3e34d334} + Framework + + + {83ef8d5c-afa1-4546-bcdd-6422d55b1515} + Game + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/Scripts/Spells/DeathKnight.cs b/Scripts/Spells/DeathKnight.cs new file mode 100644 index 000000000..beb31d12c --- /dev/null +++ b/Scripts/Spells/DeathKnight.cs @@ -0,0 +1,1258 @@ +/* + * Copyright (C) 2012-2017 CypherCore + * + * 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, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +using Framework.Constants; +using Game.Entities; +using Game.Scripting; +using Game.Spells; +using System; +using System.Collections.Generic; + +namespace Scripts.Spells.DeathKnight +{ + struct SpellIds + { + public const uint ArmyFleshBeastTransform = 127533; + public const uint ArmyGeistTransform = 127534; + public const uint ArmyNorthrendSkeletonTransform = 127528; + public const uint ArmySkeletonTransform = 127527; + public const uint ArmySpikedGhoulTransform = 127525; + public const uint ArmySuperZombieTransform = 127526; + public const uint BloodPlague = 55078; + public const uint BloodPresence = 48263; + public const uint BloodShieldMastery = 77513; + public const uint BloodShieldAbsorb = 77535; + public const uint ChainsOfIce = 45524; + public const uint CorpseExplosionTriggered = 43999; + public const uint DeathAndDecayDamage = 52212; + public const uint DeathAndDecaySlow = 143375; + public const uint DeathCoilBarrier = 115635; + public const uint DeathCoilDamage = 47632; + public const uint DeathCoilHeal = 47633; + public const uint DeathGrip = 49560; + public const uint DeathStrikeHeal = 45470; + public const uint EnhancedDeathCoil = 157343; + public const uint FrostFever = 55095; + public const uint GhoulExplode = 47496; + public const uint GlyphOfAbsorbMagic = 159415; + public const uint GlyphOfAntiMagicShell = 58623; + public const uint GlyphOfArmyOfTheDead = 58669; + public const uint GlyphOfDeathCoil = 63333; + public const uint GlyphOfDeathAndDecay = 58629; + public const uint GlyphOfFoulMenagerie = 58642; + public const uint GlyphOfRegenerativeMagic = 146648; + public const uint GlyphOfRunicPowerTriggered = 159430; + public const uint GlyphOfSwiftDeath = 146645; + public const uint GlyphOfTheGeist = 58640; + public const uint GlyphOfTheSkeleton = 146652; + public const uint ImprovedBloodPresence = 50371; + public const uint ImprovedSoulReaper = 157342; + public const uint MarkOfBloodHeal = 206945; + public const uint NecrosisEffect = 216974; + public const uint RunicPowerEnergize = 49088; + public const uint RunicReturn = 61258; + public const uint ScourgeStrikeTriggered = 70890; + public const uint ShadowOfDeath = 164047; + public const uint SoulReaperDamage = 114867; + public const uint SoulReaperHaste = 114868; + public const uint T15Dps4pBonus = 138347; + public const uint UnholyPresence = 48265; + public const uint WillOfTheNecropolis = 157335; + + public static uint[] ArmyTransforms = + { + ArmyFleshBeastTransform, + ArmyGeistTransform, + ArmyNorthrendSkeletonTransform, + ArmySkeletonTransform, + ArmySpikedGhoulTransform, + ArmySuperZombieTransform + }; + } + + struct CreatureIds + { + public const uint DancingRuneWeapon = 27893; + } + + [Script] // 70656 - Advantage (T10 4P Melee Bonus) + class spell_dk_advantage_t10_4p : SpellScriptLoader + { + public spell_dk_advantage_t10_4p() : base("spell_dk_advantage_t10_4p") { } + + class spell_dk_advantage_t10_4p_AuraScript : AuraScript + { + bool CheckProc(ProcEventInfo eventInfo) + { + Unit caster = eventInfo.GetActor(); + if (caster) + { + if (!caster.IsTypeId(TypeId.Player) || caster.GetClass() != Class.Deathknight) + return false; + + for (byte i = 0; i < PlayerConst.MaxRunes; ++i) + if (caster.ToPlayer().GetRuneCooldown(i) == 0) + return false; + + return true; + } + + return false; + } + + public override void Register() + { + DoCheckProc.Add(new CheckProcHandler(CheckProc)); + } + } + + public override AuraScript GetAuraScript() + { + return new spell_dk_advantage_t10_4p_AuraScript(); + } + } + + [Script] // 48707 - Anti-Magic Shell + class spell_dk_anti_magic_shell : SpellScriptLoader + { + public spell_dk_anti_magic_shell() : base("spell_dk_anti_magic_shell") { } + + class spell_dk_anti_magic_shell_AuraScript : AuraScript + { + public spell_dk_anti_magic_shell_AuraScript() + { + absorbPct = 0; + maxHealth = 0; + absorbedAmount = 0; + } + + public override bool Validate(SpellInfo spellInfo) + { + return ValidateSpellInfo(SpellIds.RunicPowerEnergize, SpellIds.GlyphOfAbsorbMagic, SpellIds.GlyphOfRegenerativeMagic); + } + + public override bool Load() + { + absorbPct = GetSpellInfo().GetEffect(0).CalcValue(GetCaster()); + maxHealth = (int)GetCaster().GetMaxHealth(); + absorbedAmount = 0; + return true; + } + + void CalculateAmount(AuraEffect aurEff, ref int amount, ref bool canBeRecalculated) + { + amount = maxHealth; + + /// todo, check if AMS has basepoints for 2. in that case, this function should be rewritten. + if (!GetUnitOwner().HasAura(SpellIds.GlyphOfAbsorbMagic)) + amount /= 2; + } + + void Absorb(AuraEffect aurEff, DamageInfo dmgInfo, ref uint absorbAmount) + { + // we may only absorb a certain percentage of incoming damage. + absorbAmount = (uint)(dmgInfo.GetDamage() * absorbPct / 100); + } + + void Trigger(AuraEffect aurEff, DamageInfo dmgInfo, ref uint absorbAmount) + { + absorbedAmount += absorbAmount; + + if (!GetTarget().HasAura(SpellIds.GlyphOfAbsorbMagic)) + { + // Patch 6.0.2 (October 14, 2014): Anti-Magic Shell now restores 2 Runic Power per 1% of max health absorbed. + int bp = (int)(2 * absorbAmount * 100 / maxHealth); + GetTarget().CastCustomSpell(SpellIds.RunicPowerEnergize, SpellValueMod.BasePoint0, bp, GetTarget(), true, null, aurEff); + } + } + + void HandleEffectRemove(AuraEffect aurEff, AuraEffectHandleModes mode) + { + Player player = GetTarget().ToPlayer(); + if (player) + { + AuraEffect glyph = player.GetAuraEffect(SpellIds.GlyphOfRegenerativeMagic, 0); + if (glyph != null) // reduce cooldown of AMS if player has glyph + { + // Cannot reduce cooldown by more than 50% + int val = Math.Min(glyph.GetAmount(), (int)absorbedAmount * 100 / maxHealth); + player.GetSpellHistory().ModifyCooldown(GetId(), -(int)(player.GetSpellHistory().GetRemainingCooldown(GetSpellInfo()) * val / 100)); + } + } + } + + public override void Register() + { + DoEffectCalcAmount.Add(new EffectCalcAmountHandler(CalculateAmount, 0, AuraType.SchoolAbsorb)); + OnEffectAbsorb.Add(new EffectAbsorbHandler(Absorb, 0)); + AfterEffectAbsorb.Add(new EffectAbsorbHandler(Trigger, 0)); + AfterEffectRemove.Add(new EffectApplyHandler(HandleEffectRemove, 0, AuraType.SchoolAbsorb, AuraEffectHandleModes.Real)); + } + + int absorbPct; + int maxHealth; + uint absorbedAmount; + } + + public override AuraScript GetAuraScript() + { + return new spell_dk_anti_magic_shell_AuraScript(); + } + } + + [Script] // 43264 - Periodic Taunt // 6.x, does this belong here or in spell_generic? apply this in creature_template_addon? sniffs say this is always cast army of the dead ghouls. + class spell_dk_army_periodic_taunt : SpellScriptLoader + { + public spell_dk_army_periodic_taunt() : base("spell_dk_army_periodic_taunt") { } + + class spell_dk_army_periodic_taunt_SpellScript : SpellScript + { + public override bool Load() + { + return GetCaster().IsGuardian(); + } + + SpellCastResult CheckCast() + { + Unit owner = GetCaster().GetOwner(); + if (owner) + if (!owner.HasAura(SpellIds.GlyphOfArmyOfTheDead)) + return SpellCastResult.SpellCastOk; + + return SpellCastResult.SpellUnavailable; + } + + public override void Register() + { + OnCheckCast.Add(new CheckCastHandler(CheckCast)); + } + } + + public override SpellScript GetSpellScript() + { + return new spell_dk_army_periodic_taunt_SpellScript(); + } + } + + [Script] // 127517 - Army Transform // 6.x, does this belong here or in spell_generic? where do we cast this? sniffs say this is only cast when caster has glyph of foul menagerie. + class spell_dk_army_transform : SpellScriptLoader + { + public spell_dk_army_transform() : base("spell_dk_army_transform") { } + + class spell_dk_army_transform_SpellScript : SpellScript + { + public override bool Load() + { + return GetCaster().IsGuardian(); + } + + SpellCastResult CheckCast() + { + Unit owner = GetCaster().GetOwner(); + if (owner) + if (owner.HasAura(SpellIds.GlyphOfFoulMenagerie)) + return SpellCastResult.SpellCastOk; + + return SpellCastResult.SpellUnavailable; + } + + void HandleDummy(uint effIndex) + { + GetCaster().CastSpell(GetCaster(), SpellIds.ArmyTransforms[RandomHelper.URand(0, 5)], true); + } + + public override void Register() + { + OnCheckCast.Add(new CheckCastHandler(CheckCast)); + OnEffectHitTarget.Add(new EffectHandler(HandleDummy, 0, SpellEffectName.Dummy)); + } + } + + public override SpellScript GetSpellScript() + { + return new spell_dk_army_transform_SpellScript(); + } + } + + [Script] // 50842 - Blood Boil + class spell_dk_blood_boil : SpellScriptLoader + { + public spell_dk_blood_boil() : base("spell_dk_blood_boil") { } + + class spell_dk_blood_boil_SpellScript : SpellScript + { + public spell_dk_blood_boil_SpellScript() + { + bpDuration = 0; + ffDuration = 0; + } + + public override bool Validate(SpellInfo spellInfo) + { + return ValidateSpellInfo(SpellIds.BloodPlague, SpellIds.FrostFever); + } + + void FilterTargets(List targets) + { + if (targets.Empty()) + return; + + Unit caster = GetCaster(); + + foreach (WorldObject target in targets) + { + if (bpDuration != 0 && ffDuration != 0) + break; + + Unit unit = target.ToUnit(); + if (unit) + { + Aura bp = unit.GetAura(SpellIds.BloodPlague, caster.GetGUID()); + if (bp != null) + bpDuration = bp.GetDuration(); + Aura ff = unit.GetAura(SpellIds.FrostFever, caster.GetGUID()); + if (ff != null) + ffDuration = ff.GetDuration(); + } + } + } + + void HandleEffect(uint effIndex) + { + Unit caster = GetCaster(); + Unit target = GetHitUnit(); + + if (ffDuration != 0) + caster.CastSpell(target, SpellIds.FrostFever, true); + if (bpDuration != 0) + caster.CastSpell(target, SpellIds.BloodPlague, true); + + Aura bp = target.GetAura(SpellIds.BloodPlague, caster.GetGUID()); + if (bp != null) + { + bp.SetDuration(bpDuration); + bp.SetMaxDuration(bpDuration); + } + + Aura ff = target.GetAura(SpellIds.FrostFever, caster.GetGUID()); + if (ff != null) + { + ff.SetDuration(ffDuration); + ff.SetMaxDuration(ffDuration); + } + } + + public override void Register() + { + OnObjectAreaTargetSelect.Add(new ObjectAreaTargetSelectHandler(FilterTargets, 0, Targets.UnitDestAreaEnemy)); + OnEffectHitTarget.Add(new EffectHandler(HandleEffect, 0, SpellEffectName.SchoolDamage)); + } + + int bpDuration; + int ffDuration; + } + + public override SpellScript GetSpellScript() + { + return new spell_dk_blood_boil_SpellScript(); + } + } + + [Script] // 49028 - Dancing Rune Weapon 7.1.5 + class spell_dk_dancing_rune_weapon : SpellScriptLoader + { + public spell_dk_dancing_rune_weapon() : base("spell_dk_dancing_rune_weapon") { } + + class spell_dk_dancing_rune_weapon_AuraScript : AuraScript + { + public override bool Validate(SpellInfo spellInfo) + { + if (Global.ObjectMgr.GetCreatureTemplate(CreatureIds.DancingRuneWeapon) == null) + return false; + return true; + } + + // This is a port of the old switch hack in Unit.cpp, it's not correct + void HandleProc(AuraEffect aurEff, ProcEventInfo eventInfo) + { + PreventDefaultAction(); + Unit caster = GetCaster(); + if (!caster) + return; + + Unit drw = null; + foreach (Unit controlled in caster.m_Controlled) + { + if (controlled.GetEntry() == CreatureIds.DancingRuneWeapon) + { + drw = controlled; + break; + } + } + + if (!drw || !drw.GetVictim()) + return; + + SpellInfo spellInfo = eventInfo.GetSpellInfo(); + if (spellInfo == null) + return; + + DamageInfo damageInfo = eventInfo.GetDamageInfo(); + if (damageInfo == null || damageInfo.GetDamage() == 0) + return; + + int amount = (int)(damageInfo.GetDamage() / 2); + SpellNonMeleeDamage log = new SpellNonMeleeDamage(drw, drw.GetVictim(), spellInfo.Id, spellInfo.GetSpellXSpellVisualId(drw), spellInfo.GetSchoolMask()); + log.damage = (uint)amount; + drw.DealDamage(drw.GetVictim(), (uint)amount, null, DamageEffectType.Direct, spellInfo.GetSchoolMask(), spellInfo, true); + drw.SendSpellNonMeleeDamageLog(log); + } + + public override void Register() + { + OnEffectProc.Add(new EffectProcHandler(HandleProc, 1, AuraType.Dummy)); + } + } + + public override AuraScript GetAuraScript() + { + return new spell_dk_dancing_rune_weapon_AuraScript(); + } + } + + [Script] // 43265 - Death and Decay + class spell_dk_death_and_decay : SpellScriptLoader + { + public spell_dk_death_and_decay() : base("spell_dk_death_and_decay") { } + + class spell_dk_death_and_decay_SpellScript : SpellScript + { + void HandleDummy(uint effIndex) + { + if (GetCaster().HasAura(SpellIds.GlyphOfDeathAndDecay)) + { + Position pos = GetExplTargetDest(); + if (pos != null) + GetCaster().CastSpell(pos.GetPositionX(), pos.GetPositionY(), pos.GetPositionZ(), SpellIds.DeathAndDecaySlow, true); + } + } + + public override void Register() + { + OnEffectHitTarget.Add(new EffectHandler(HandleDummy, 0, SpellEffectName.Dummy)); + } + } + + public override SpellScript GetSpellScript() + { + return new spell_dk_death_and_decay_SpellScript(); + } + + class spell_dk_death_and_decay_AuraScript : AuraScript + { + void HandleDummyTick(AuraEffect aurEff) + { + Unit caster = GetCaster(); + if (caster) + caster.CastCustomSpell(SpellIds.DeathAndDecayDamage, SpellValueMod.BasePoint0, aurEff.GetAmount(), GetTarget(), true, null, aurEff); + } + + public override void Register() + { + OnEffectPeriodic.Add(new EffectPeriodicHandler(HandleDummyTick, 2, AuraType.PeriodicDummy)); + } + } + + public override AuraScript GetAuraScript() + { + return new spell_dk_death_and_decay_AuraScript(); + } + } + + [Script] // 47541 - Death Coil + class spell_dk_death_coil : SpellScriptLoader + { + public spell_dk_death_coil() : base("spell_dk_death_coil") { } + + class spell_dk_death_coil_SpellScript : SpellScript + { + public override bool Validate(SpellInfo spell) + { + return ValidateSpellInfo(SpellIds.DeathCoilDamage, SpellIds.DeathCoilHeal, SpellIds.GlyphOfDeathCoil); + } + + void HandleDummy(uint effIndex) + { + Unit caster = GetCaster(); + Unit target = GetHitUnit(); + if (target) + { + if (caster.IsFriendlyTo(target)) + { + if (target.GetCreatureType() == CreatureType.Undead) // Any undead ally, including caster if he has lichborne. + { + caster.CastSpell(target, SpellIds.DeathCoilHeal, true); + } + else if (target != caster) // Any non undead ally except caster and only if caster has glyph of death coil. + { + SpellInfo DCD = Global.SpellMgr.GetSpellInfo(SpellIds.DeathCoilDamage); + SpellEffectInfo eff = DCD.GetEffect(0); + int bp = (int)caster.SpellDamageBonusDone(target, DCD, (uint)eff.CalcValue(caster), DamageEffectType.SpellDirect, eff); + + caster.CastCustomSpell(target, SpellIds.DeathCoilBarrier, bp, 0, 0, true); + } + } + else // Any enemy target. + { + caster.CastSpell(target, SpellIds.DeathCoilDamage, true); + } + } + + if (caster.HasAura(SpellIds.EnhancedDeathCoil)) + caster.CastSpell(caster, SpellIds.ShadowOfDeath, true); + } + + SpellCastResult CheckCast() + { + Unit caster = GetCaster(); + Unit target = GetExplTargetUnit(); + if (target) + { + if (!caster.IsFriendlyTo(target) && !caster.isInFront(target)) + return SpellCastResult.UnitNotInfront; + + if (target.IsFriendlyTo(caster) && target.GetCreatureType() != CreatureType.Undead && !caster.HasAura(SpellIds.GlyphOfDeathCoil)) + return SpellCastResult.BadTargets; + } + else + return SpellCastResult.BadTargets; + + return SpellCastResult.SpellCastOk; + } + + public override void Register() + { + OnCheckCast.Add(new CheckCastHandler(CheckCast)); + OnEffectHitTarget.Add(new EffectHandler(HandleDummy, 0, SpellEffectName.Dummy)); + } + } + + public override SpellScript GetSpellScript() + { + return new spell_dk_death_coil_SpellScript(); + } + } + + [Script] // 52751 - Death Gate + class spell_dk_death_gate : SpellScriptLoader + { + public spell_dk_death_gate() : base("spell_dk_death_gate") { } + + class spell_dk_death_gate_SpellScript : SpellScript + { + SpellCastResult CheckClass() + { + if (GetCaster().GetClass() != Class.Deathknight) + { + SetCustomCastResultMessage(SpellCustomErrors.MustBeDeathKnight); + return SpellCastResult.CustomError; + } + + return SpellCastResult.SpellCastOk; + } + + void HandleScript(uint effIndex) + { + PreventHitDefaultEffect(effIndex); + Unit target = GetHitUnit(); + if (target) + target.CastSpell(target, (uint)GetEffectValue(), false); + } + + public override void Register() + { + OnCheckCast.Add(new CheckCastHandler(CheckClass)); + OnEffectHitTarget.Add(new EffectHandler(HandleScript, 0, SpellEffectName.ScriptEffect)); + } + } + + public override SpellScript GetSpellScript() + { + return new spell_dk_death_gate_SpellScript(); + } + } + + [Script] // 49560 - Death Grip + class spell_dk_death_grip : SpellScriptLoader + { + public spell_dk_death_grip() : base("spell_dk_death_grip") { } + + class spell_dk_death_grip_SpellScript : SpellScript + { + void HandleDummy(uint effIndex) + { + int damage = GetEffectValue(); + Position pos = GetExplTargetDest(); + Unit target = GetHitUnit(); + if (target) + { + if (!target.HasAuraType(AuraType.DeflectSpells)) // Deterrence + target.CastSpell(pos.GetPositionX(), pos.GetPositionY(), pos.GetPositionZ(), (uint)damage, true); + } + } + + public override void Register() + { + OnEffectHitTarget.Add(new EffectHandler(HandleDummy, 0, SpellEffectName.Dummy)); + } + + } + + public override SpellScript GetSpellScript() + { + return new spell_dk_death_grip_SpellScript(); + } + } + + [Script] // 48743 - Death Pact + class spell_dk_death_pact : SpellScriptLoader + { + public spell_dk_death_pact() : base("spell_dk_death_pact") { } + + class spell_dk_death_pact_AuraScript : AuraScript + { + void HandleCalcAmount(AuraEffect aurEff, ref int amount, ref bool canBeRecalculated) + { + Unit caster = GetCaster(); + if (caster) + amount = (int)caster.CountPctFromMaxHealth(amount); + } + + public override void Register() + { + DoEffectCalcAmount.Add(new EffectCalcAmountHandler(HandleCalcAmount, 1, AuraType.SchoolHealAbsorb)); + } + } + + public override AuraScript GetAuraScript() + { + return new spell_dk_death_pact_AuraScript(); + } + } + + [Script] // 49998 - Death Strike + class spell_dk_death_strike : SpellScriptLoader + { + public spell_dk_death_strike() : base("spell_dk_death_strike") { } + + class spell_dk_death_strike_SpellScript : SpellScript + { + public override bool Validate(SpellInfo spellInfo) + { + return ValidateSpellInfo(SpellIds.DeathStrikeHeal, SpellIds.BloodShieldMastery, SpellIds.BloodShieldAbsorb); + } + + void HandleHeal(uint effIndex) + { + Unit caster = GetCaster(); + int heal = (int)caster.GetTotalAttackPowerValue(WeaponAttackType.BaseAttack) * 4; /// todo, add versatality bonus as it will probably not apply to the heal due to its damageclass SPELL_DAMAGE_CLASS_NONE. + caster.CastCustomSpell(SpellIds.DeathStrikeHeal, SpellValueMod.BasePoint0, heal, caster, true); + + if (!caster.HasAura(SpellIds.BloodPresence) || !caster.HasAura(SpellIds.ImprovedBloodPresence)) + return; + + /// todo, if SPELL_AURA_MOD_ABSORB_PERCENTAGE will not apply to SPELL_DAMAGE_CLASS_NONE, resolve must be applied here. + AuraEffect aurEff = caster.GetAuraEffect(SpellIds.BloodShieldMastery, 0); + if (aurEff != null) + caster.CastCustomSpell(SpellIds.BloodShieldAbsorb, SpellValueMod.BasePoint0, MathFunctions.CalculatePct(heal, aurEff.GetAmount()), caster); + } + + public override void Register() + { + OnEffectHitTarget.Add(new EffectHandler(HandleHeal, 1, SpellEffectName.WeaponPercentDamage)); + } + } + + public override SpellScript GetSpellScript() + { + return new spell_dk_death_strike_SpellScript(); + } + } + + [Script] // 85948 - Festering Strike + class spell_dk_festering_strike : SpellScriptLoader + { + public spell_dk_festering_strike() : base("spell_dk_festering_strike") { } + + class spell_dk_festering_strike_SpellScript : SpellScript + { + public override bool Validate(SpellInfo spellInfo) + { + return ValidateSpellInfo(SpellIds.FrostFever, SpellIds.BloodPlague, SpellIds.ChainsOfIce); + } + + void HandleScriptEffect(uint effIndex) + { + int extraDuration = GetEffectValue(); + Unit target = GetHitUnit(); + ObjectGuid casterGUID = GetCaster().GetGUID(); + + Aura ff = target.GetAura(SpellIds.FrostFever, casterGUID); + if (ff != null) + { + int newDuration = Math.Min(ff.GetDuration() + extraDuration, 2 * Time.Minute * Time.InMilliseconds); // caps at 2min. + ff.SetDuration(newDuration); + ff.SetMaxDuration(newDuration); + } + + Aura bp = target.GetAura(SpellIds.BloodPlague, casterGUID); + if (bp != null) + { + int newDuration = Math.Min(bp.GetDuration() + extraDuration, 2 * Time.Minute * Time.InMilliseconds); // caps at 2min. + bp.SetDuration(newDuration); + bp.SetMaxDuration(newDuration); + } + + Aura coi = target.GetAura(SpellIds.ChainsOfIce, casterGUID); + if (coi != null) + { + int newDuration = Math.Min(coi.GetDuration() + extraDuration, 20 * Time.InMilliseconds); // is 20sec cap? couldnt manage to get runes up to pass 20. + coi.SetDuration(newDuration); + coi.SetMaxDuration(newDuration); + } + } + + public override void Register() + { + OnEffectHitTarget.Add(new EffectHandler(HandleScriptEffect, 2, SpellEffectName.ScriptEffect)); + } + } + + public override SpellScript GetSpellScript() + { + return new spell_dk_festering_strike_SpellScript(); + } + } + + [Script] // 47496 - Explode, Ghoul spell for Corpse Explosion + class spell_dk_ghoul_explode : SpellScriptLoader + { + public spell_dk_ghoul_explode() : base("spell_dk_ghoul_explode") { } + + class spell_dk_ghoul_explode_SpellScript : SpellScript + { + public override bool Validate(SpellInfo spellInfo) + { + return ValidateSpellInfo(SpellIds.CorpseExplosionTriggered); + } + + void HandleDamage(uint effIndex) + { + SetHitDamage((int)GetCaster().CountPctFromMaxHealth(GetEffectInfo(2).CalcValue(GetCaster()))); + } + + void Suicide(uint effIndex) + { + Unit unitTarget = GetHitUnit(); + if (unitTarget) + { + // Corpse Explosion (Suicide) + unitTarget.CastSpell(unitTarget, SpellIds.CorpseExplosionTriggered, true); + } + } + + public override void Register() + { + OnEffectHitTarget.Add(new EffectHandler(HandleDamage, 0, SpellEffectName.SchoolDamage)); + OnEffectHitTarget.Add(new EffectHandler(Suicide, 1, SpellEffectName.SchoolDamage)); + } + } + + public override SpellScript GetSpellScript() + { + return new spell_dk_ghoul_explode_SpellScript(); + } + } + + [Script] // 58677 - Glyph of Death's Embrace + class spell_dk_glyph_of_deaths_embrace : SpellScriptLoader + { + public spell_dk_glyph_of_deaths_embrace() : base("spell_dk_glyph_of_deaths_embrace") { } + + class spell_dk_glyph_of_deaths_embrace_AuraScript : AuraScript + { + bool CheckProc(ProcEventInfo eventInfo) + { + Unit actionTarget = eventInfo.GetActionTarget(); + return actionTarget && actionTarget.GetCreatureType() == CreatureType.Undead && actionTarget.GetOwner(); + } + + public override void Register() + { + DoCheckProc.Add(new CheckProcHandler(CheckProc)); + } + } + + public override AuraScript GetAuraScript() + { + return new spell_dk_glyph_of_deaths_embrace_AuraScript(); + } + } + + [Script] // 159429 - Glyph of Runic Power + class spell_dk_glyph_of_runic_power : SpellScriptLoader + { + public spell_dk_glyph_of_runic_power() : base("spell_dk_glyph_of_runic_power") { } + + class spell_dk_glyph_of_runic_power_AuraScript : AuraScript + { + public override bool Validate(SpellInfo spellInfo) + { + return ValidateSpellInfo(SpellIds.GlyphOfRunicPowerTriggered); + } + + public override bool Load() + { + return GetUnitOwner().GetClass() == Class.Deathknight; + } + + bool CheckProc(ProcEventInfo eventInfo) + { + return eventInfo.GetSpellInfo() != null + && Convert.ToBoolean(eventInfo.GetSpellInfo().GetAllEffectsMechanicMask() & (1 << (int)Mechanics.Snare | 1 << (int)Mechanics.Root | 1 << (int)Mechanics.Freeze)); + } + + void HandleProc(ProcEventInfo eventInfo) + { + Unit target = eventInfo.GetProcTarget(); + if (target) + target.CastSpell(target, SpellIds.GlyphOfRunicPowerTriggered, true); + } + + public override void Register() + { + DoCheckProc.Add(new CheckProcHandler(CheckProc)); + OnProc.Add(new AuraProcHandler(HandleProc)); + } + } + + public override AuraScript GetAuraScript() + { + return new spell_dk_glyph_of_runic_power_AuraScript(); + } + } + + [Script] // 48792 - Icebound Fortitude + class spell_dk_icebound_fortitude : SpellScriptLoader + { + public spell_dk_icebound_fortitude() : base("spell_dk_icebound_fortitude") { } + + class spell_dk_icebound_fortitude_AuraScript : AuraScript + { + void CalculateAmount(AuraEffect aurEff, ref int amount, ref bool canBeRecalculated) + { + if (GetUnitOwner().HasAura(SpellIds.ImprovedBloodPresence)) + amount += 30; /// todo, figure out how tooltip is updated + } + + public override void Register() + { + DoEffectCalcAmount.Add(new EffectCalcAmountHandler(CalculateAmount, 2, AuraType.ModDamagePercentTaken)); + } + } + + public override AuraScript GetAuraScript() + { + return new spell_dk_icebound_fortitude_AuraScript(); + } + } + + [Script] // 206940 - Mark of Blood 7.1.5 + class spell_dk_mark_of_blood : SpellScriptLoader + { + public spell_dk_mark_of_blood() : base("spell_dk_mark_of_blood") { } + + class spell_dk_mark_of_blood_AuraScript : AuraScript + { + public override bool Validate(SpellInfo spellInfo) + { + return ValidateSpellInfo(SpellIds.MarkOfBloodHeal); + } + + void HandleProc(AuraEffect aurEff, ProcEventInfo eventInfo) + { + PreventDefaultAction(); + Unit caster = GetCaster(); + if (caster) + caster.CastSpell(eventInfo.GetProcTarget(), SpellIds.MarkOfBloodHeal, true); + } + + public override void Register() + { + OnEffectProc.Add(new EffectProcHandler(HandleProc, 0, AuraType.Dummy)); + } + } + + public override AuraScript GetAuraScript() + { + return new spell_dk_mark_of_blood_AuraScript(); + } + } + + [Script] // 207346 - Necrosis 7.1.5 + class spell_dk_necrosis : SpellScriptLoader + { + public spell_dk_necrosis() : base("spell_dk_necrosis") { } + + class spell_dk_necrosis_AuraScript : AuraScript + { + public override bool Validate(SpellInfo spellInfo) + { + return ValidateSpellInfo(SpellIds.NecrosisEffect); + } + + void HandleProc(AuraEffect aurEff, ProcEventInfo eventInfo) + { + PreventDefaultAction(); + GetTarget().CastSpell(eventInfo.GetProcTarget(), SpellIds.NecrosisEffect, true); + } + + public override void Register() + { + OnEffectProc.Add(new EffectProcHandler(HandleProc, 0, AuraType.Dummy)); + } + } + + public override AuraScript GetAuraScript() + { + return new spell_dk_necrosis_AuraScript(); + } + } + + [Script] // 121916 - Glyph of the Geist (Unholy) // 6.x, does this belong here or in spell_generic? apply this in creature_template_addon? sniffs say this is always cast on raise dead. + class spell_dk_pet_geist_transform : SpellScriptLoader + { + public spell_dk_pet_geist_transform() : base("spell_dk_pet_geist_transform") { } + + class spell_dk_pet_geist_transform_SpellScript : SpellScript + { + public override bool Load() + { + return GetCaster().IsPet(); + } + + SpellCastResult CheckCast() + { + Unit owner = GetCaster().GetOwner(); + if (owner) + if (owner.HasAura(SpellIds.GlyphOfTheGeist)) + return SpellCastResult.SpellCastOk; + + return SpellCastResult.SpellUnavailable; + } + + public override void Register() + { + OnCheckCast.Add(new CheckCastHandler(CheckCast)); + } + } + + public override SpellScript GetSpellScript() + { + return new spell_dk_pet_geist_transform_SpellScript(); + } + } + + [Script] // 147157 Glyph of the Skeleton (Unholy) // 6.x, does this belong here or in spell_generic? apply this in creature_template_addon? sniffs say this is always cast on raise dead. + class spell_dk_pet_skeleton_transform : SpellScriptLoader + { + public spell_dk_pet_skeleton_transform() : base("spell_dk_pet_skeleton_transform") { } + + class spell_dk_pet_skeleton_transform_SpellScript : SpellScript + { + SpellCastResult CheckCast() + { + Unit owner = GetCaster().GetOwner(); + if (owner) + if (owner.HasAura(SpellIds.GlyphOfTheSkeleton)) + return SpellCastResult.SpellCastOk; + + return SpellCastResult.SpellUnavailable; + } + + public override void Register() + { + OnCheckCast.Add(new CheckCastHandler(CheckCast)); + } + } + + public override SpellScript GetSpellScript() + { + return new spell_dk_pet_skeleton_transform_SpellScript(); + } + } + + [Script] // 61257 - Runic Power Back on Snare/Root 7.1.5 + class spell_dk_pvp_4p_bonus : SpellScriptLoader + { + public spell_dk_pvp_4p_bonus() : base("spell_dk_pvp_4p_bonus") { } + + class spell_dk_pvp_4p_bonus_AuraScript : AuraScript + { + public override bool Validate(SpellInfo spellInfo) + { + return ValidateSpellInfo(SpellIds.RunicReturn); + } + + bool CheckProc(ProcEventInfo eventInfo) + { + SpellInfo spellInfo = eventInfo.GetSpellInfo(); + if (spellInfo == null) + return false; + + return (spellInfo.GetAllEffectsMechanicMask() & ((1 << (int)Mechanics.Root) | (1 << (int)Mechanics.Snare))) != 0; + } + + void HandleProc(AuraEffect aurEff, ProcEventInfo eventInfo) + { + PreventDefaultAction(); + eventInfo.GetActionTarget().CastSpell((Unit)null, SpellIds.RunicReturn, true); + } + + public override void Register() + { + DoCheckProc.Add(new CheckProcHandler(CheckProc)); + OnEffectProc.Add(new EffectProcHandler(HandleProc, 0, AuraType.Dummy)); + } + } + + public override AuraScript GetAuraScript() + { + return new spell_dk_pvp_4p_bonus_AuraScript(); + } + } + + [Script] // 46584 - Raise Dead + class spell_dk_raise_dead : SpellScriptLoader + { + public spell_dk_raise_dead() : base("spell_dk_raise_dead") { } + + class spell_dk_raise_dead_SpellScript : SpellScript + { + public spell_dk_raise_dead_SpellScript() { } + + public override bool Validate(SpellInfo spellInfo) + { + return spellInfo.GetEffect(0) != null && ValidateSpellInfo((uint)spellInfo.GetEffect(0).CalcValue()); + } + + public override bool Load() + { + return GetCaster().IsTypeId(TypeId.Player); + } + + void HandleDummy(uint effIndex) + { + GetCaster().CastSpell((Unit)null, (uint)GetEffectValue(), true); + } + + public override void Register() + { + OnEffectHitTarget.Add(new EffectHandler(HandleDummy, 0, SpellEffectName.Dummy)); + } + } + + public override SpellScript GetSpellScript() + { + return new spell_dk_raise_dead_SpellScript(); + } + } + + [Script] // 114866 - Soul Reaper, 130735 - Soul Reaper, 130736 - Soul Reaper + class spell_dk_soul_reaper : SpellScriptLoader + { + public spell_dk_soul_reaper() : base("spell_dk_soul_reaper") { } + + class spell_dk_soul_reaper_AuraScript : AuraScript + { + void HandlePeriodicDummy(AuraEffect aurEff) + { + Unit caster = GetCaster(); + Unit target = GetUnitOwner(); + + if (!caster || !target) + return; + + float pct = target.GetHealthPct(); + + if (pct < 35.0f || (pct < 45.0f && (caster.HasAura(SpellIds.ImprovedSoulReaper) || caster.HasAura(SpellIds.T15Dps4pBonus)))) + caster.CastSpell(target, SpellIds.SoulReaperDamage, true, null, aurEff); + } + + void HandleRemove(AuraEffect aurEff, AuraEffectHandleModes mode) + { + if (GetTargetApplication().GetRemoveMode() != AuraRemoveMode.ByDeath) + return; + + Unit caster = GetCaster(); + if (caster) + caster.CastSpell(caster, SpellIds.SoulReaperHaste, true); + } + + public override void Register() + { + OnEffectPeriodic.Add(new EffectPeriodicHandler(HandlePeriodicDummy, 1, AuraType.PeriodicDummy)); + AfterEffectRemove.Add(new EffectApplyHandler(HandleRemove, 1, AuraType.PeriodicDummy, AuraEffectHandleModes.Real)); + } + } + + public override AuraScript GetAuraScript() + { + return new spell_dk_soul_reaper_AuraScript(); + } + } + + [Script] // 115994 - Unholy Blight + class spell_dk_unholy_blight : SpellScriptLoader + { + public spell_dk_unholy_blight() : base("spell_dk_unholy_blight") { } + + class spell_dk_unholy_blight_SpellScript : SpellScript + { + void HandleDummy(uint effIndex) + { + GetCaster().CastSpell(GetHitUnit(), SpellIds.FrostFever, true); + GetCaster().CastSpell(GetHitUnit(), SpellIds.BloodPlague, true); + } + + public override void Register() + { + OnEffectHitTarget.Add(new EffectHandler(HandleDummy, 0, SpellEffectName.Dummy)); + } + } + + public override SpellScript GetSpellScript() + { + return new spell_dk_unholy_blight_SpellScript(); + } + } + + [Script] // 55233 - Vampiric Blood + class spell_dk_vampiric_blood : SpellScriptLoader + { + public spell_dk_vampiric_blood() : base("spell_dk_vampiric_blood") { } + + class spell_dk_vampiric_blood_AuraScript : AuraScript + { + void CalculateAmount(AuraEffect aurEff, ref int amount, ref bool canBeRecalculated) + { + amount = (int)GetUnitOwner().CountPctFromMaxHealth(amount); + } + + public override void Register() + { + DoEffectCalcAmount.Add(new EffectCalcAmountHandler(CalculateAmount, 1, AuraType.ModIncreaseHealth)); + } + } + + public override AuraScript GetAuraScript() + { + return new spell_dk_vampiric_blood_AuraScript(); + } + } + + [Script] // 81164 - Will of the Necropolis + class spell_dk_will_of_the_necropolis : SpellScriptLoader + { + public spell_dk_will_of_the_necropolis() : base("spell_dk_will_of_the_necropolis") { } + + class spell_dk_will_of_the_necropolis_AuraScript : AuraScript + { + public override bool Validate(SpellInfo spellInfo) + { + return spellInfo.GetEffect(0) != null && ValidateSpellInfo(SpellIds.WillOfTheNecropolis); + } + + bool CheckProc(ProcEventInfo eventInfo) + { + Unit target = GetTarget(); + + if (target.HasAura(SpellIds.WillOfTheNecropolis)) + return false; + + return target.HealthBelowPctDamaged(30, eventInfo.GetDamageInfo().GetDamage()); + } + + void HandleProc(AuraEffect aurEff, ProcEventInfo eventInfo) + { + GetTarget().CastSpell(GetTarget(), SpellIds.WillOfTheNecropolis, true, null, aurEff); + } + + public override void Register() + { + DoCheckProc.Add(new CheckProcHandler(CheckProc)); + OnEffectProc.Add(new EffectProcHandler(HandleProc, 0, AuraType.ProcTriggerSpell)); + } + } + + public override AuraScript GetAuraScript() + { + return new spell_dk_will_of_the_necropolis_AuraScript(); + } + } + + [Script] // 49576 - Death Grip Initial + class spell_dk_death_grip_initial : SpellScriptLoader + { + public spell_dk_death_grip_initial() : base("spell_dk_death_grip_initial") { } + + class spell_dk_death_grip_initial_SpellScript : SpellScript + { + SpellCastResult CheckCast() + { + Unit caster = GetCaster(); + // Death Grip should not be castable while jumping/falling + if (caster.HasUnitState(UnitState.Jumping) || caster.HasUnitMovementFlag(MovementFlag.Falling)) + return SpellCastResult.Moving; + + // Patch 3.3.3 (2010-03-23): Minimum range has been changed to 8 yards in PvP. + Unit target = GetExplTargetUnit(); + if (target && target.IsTypeId(TypeId.Player)) + if (caster.GetDistance(target) < 8.0f) + return SpellCastResult.TooClose; + + return SpellCastResult.SpellCastOk; + } + + void HandleDummy(uint effIndex) + { + GetCaster().CastSpell(GetHitUnit(), SpellIds.DeathGrip, true); + } + + public override void Register() + { + OnCheckCast.Add(new CheckCastHandler(CheckCast)); + OnEffectHitTarget.Add(new EffectHandler(HandleDummy, 0, SpellEffectName.Dummy)); + } + } + + public override SpellScript GetSpellScript() + { + return new spell_dk_death_grip_initial_SpellScript(); + } + } +} diff --git a/Scripts/Spells/DemonHunter.cs b/Scripts/Spells/DemonHunter.cs new file mode 100644 index 000000000..48210411e --- /dev/null +++ b/Scripts/Spells/DemonHunter.cs @@ -0,0 +1,29 @@ +/* + * Copyright (C) 2012-2017 CypherCore + * + * 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, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Scripts.Spells +{ + class DemonHunter + { + } +} diff --git a/Scripts/Spells/Druid.cs b/Scripts/Spells/Druid.cs new file mode 100644 index 000000000..171e86f4a --- /dev/null +++ b/Scripts/Spells/Druid.cs @@ -0,0 +1,1167 @@ +/* + * Copyright (C) 2012-2017 CypherCore + * + * 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, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +using Framework.Constants; +using Game.Entities; +using Game.Scripting; +using Game.Spells; +using System; +using System.Collections.Generic; +using System.Linq; + +namespace Scripts.Spells.Druid +{ + struct SpellIds + { + public const uint BalanceT10Bonus = 70718; + public const uint BalanceT10BonusProc = 70721; + public const uint BlessingOfTheClaw = 28750; + public const uint BlessingOfTheRemulos = 40445; + public const uint BlessingOfTheElune = 40446; + public const uint BlessingOfTheCenarius = 40452; + public const uint Exhilarate = 28742; + public const uint FeralChargeBear = 16979; + public const uint FeralChargeCat = 49376; + public const uint FormsTrinketBear = 37340; + public const uint FormsTrinketCat = 37341; + public const uint FormsTrinketMoonkin = 37343; + public const uint FormsTrinketNone = 37344; + public const uint FormsTrinketTree = 37342; + public const uint IdolOfFeralShadows = 34241; + public const uint IdolOfWorship = 60774; + public const uint Infusion = 37238; + public const uint Languish = 71023; + public const uint LifebloomEnergize = 64372; + public const uint LifebloomFinalHeal = 33778; + public const uint LivingSeedHeal = 48503; + public const uint LivingSeedProc = 48504; + public const uint MoonfireDamage = 164812; + public const uint RejuvenationT10Proc = 70691; + public const uint SavageRoar = 62071; + public const uint StampedeBearRank1 = 81016; + public const uint StampedeCatRank1 = 81021; + public const uint StampedeCatState = 109881; + public const uint SunfireDamage = 164815; + public const uint SurvivalInstincts = 50322; + } + + [Script] // 1850 - Dash + class spell_dru_dash : SpellScriptLoader + { + public spell_dru_dash() : base("spell_dru_dash") { } + + public class spell_dru_dash_AuraScript : AuraScript + { + void CalculateAmount(AuraEffect aurEff, ref int amount, ref bool canBeRecalculated) + { + // do not set speed if not in cat form + if (GetUnitOwner().GetShapeshiftForm() != ShapeShiftForm.CatForm) + amount = 0; + } + + public override void Register() + { + DoEffectCalcAmount.Add(new EffectCalcAmountHandler(CalculateAmount, 0, AuraType.ModIncreaseSpeed)); + } + } + + public override AuraScript GetAuraScript() + { + return new spell_dru_dash_AuraScript(); + } + } + + [Script] // 33943 - Flight Form + class spell_dru_flight_form : SpellScriptLoader + { + public spell_dru_flight_form() : base("spell_dru_flight_form") { } + + class spell_dru_flight_form_SpellScript : SpellScript + { + SpellCastResult CheckCast() + { + Unit caster = GetCaster(); + if (caster.IsInDisallowedMountForm()) + return SpellCastResult.NotShapeshift; + + return SpellCastResult.SpellCastOk; + } + + public override void Register() + { + OnCheckCast.Add(new CheckCastHandler(CheckCast)); + } + } + + public override SpellScript GetSpellScript() + { + return new spell_dru_flight_form_SpellScript(); + } + } + + [Script] // 37336 - Druid Forms Trinket + class spell_dru_forms_trinket : SpellScriptLoader + { + public spell_dru_forms_trinket() : base("spell_dru_forms_trinket") { } + + class spell_dru_forms_trinket_AuraScript : AuraScript + { + public override bool Validate(SpellInfo spellInfo) + { + return ValidateSpellInfo(SpellIds.FormsTrinketBear, SpellIds.FormsTrinketCat, SpellIds.FormsTrinketMoonkin, SpellIds.FormsTrinketNone, SpellIds.FormsTrinketTree); + } + + bool CheckProc(ProcEventInfo eventInfo) + { + Unit target = eventInfo.GetActor(); + + switch (target.GetShapeshiftForm()) + { + case ShapeShiftForm.BearForm: + case ShapeShiftForm.DireBearForm: + case ShapeShiftForm.CatForm: + case ShapeShiftForm.MoonkinForm: + case ShapeShiftForm.None: + case ShapeShiftForm.TreeOfLife: + return true; + default: + break; + } + + return false; + } + + void HandleProc(AuraEffect aurEff, ProcEventInfo eventInfo) + { + PreventDefaultAction(); + Unit target = eventInfo.GetActor(); + uint triggerspell = 0; + + switch (target.GetShapeshiftForm()) + { + case ShapeShiftForm.BearForm: + case ShapeShiftForm.DireBearForm: + triggerspell = SpellIds.FormsTrinketBear; + break; + case ShapeShiftForm.CatForm: + triggerspell = SpellIds.FormsTrinketCat; + break; + case ShapeShiftForm.MoonkinForm: + triggerspell = SpellIds.FormsTrinketMoonkin; + break; + case ShapeShiftForm.None: + triggerspell = SpellIds.FormsTrinketNone; + break; + case ShapeShiftForm.TreeOfLife: + triggerspell = SpellIds.FormsTrinketTree; + break; + default: + return; + } + + target.CastSpell(target, triggerspell, true, null, aurEff); + } + + public override void Register() + { + DoCheckProc.Add(new CheckProcHandler(CheckProc)); + OnEffectProc.Add(new EffectProcHandler(HandleProc, 0, AuraType.ProcTriggerSpell)); + } + } + + public override AuraScript GetAuraScript() + { + return new spell_dru_forms_trinket_AuraScript(); + } + } + + // 34246 - Idol of the Emerald Queen + [Script] // 60779 - Idol of Lush Moss + class spell_dru_idol_lifebloom : SpellScriptLoader + { + public spell_dru_idol_lifebloom() : base("spell_dru_idol_lifebloom") { } + + class spell_dru_idol_lifebloom_AuraScript : AuraScript + { + void HandleEffectCalcSpellMod(AuraEffect aurEff, ref SpellModifier spellMod) + { + if (spellMod == null) + { + spellMod = new SpellModifier(GetAura()); + spellMod.op = SpellModOp.Dot; + spellMod.type = SpellModType.Flat; + spellMod.spellId = GetId(); + spellMod.mask = GetSpellInfo().GetEffect(aurEff.GetEffIndex()).SpellClassMask; + } + spellMod.value = aurEff.GetAmount() / 7; + } + + public override void Register() + { + DoEffectCalcSpellMod.Add(new EffectCalcSpellModHandler(HandleEffectCalcSpellMod, 0, AuraType.Dummy)); + } + } + + public override AuraScript GetAuraScript() + { + return new spell_dru_idol_lifebloom_AuraScript(); + } + } + + // 29166 - Innervate + [Script] + class spell_dru_innervate : SpellScriptLoader + { + public spell_dru_innervate() : base("spell_dru_innervate") { } + + class spell_dru_innervate_AuraScript : AuraScript + { + void CalculateAmount(AuraEffect aurEff, ref int amount, ref bool canBeRecalculated) + { + Unit caster = GetCaster(); + if (caster) + amount = MathFunctions.CalculatePct(caster.GetCreatePowers(PowerType.Mana), amount) / aurEff.GetTotalTicks(); + else + amount = 0; + } + + public override void Register() + { + DoEffectCalcAmount.Add(new EffectCalcAmountHandler(CalculateAmount, 0, AuraType.PeriodicEnergize)); + } + } + + public override AuraScript GetAuraScript() + { + return new spell_dru_innervate_AuraScript(); + } + } + + // 33763 - Lifebloom + [Script] + class spell_dru_lifebloom : SpellScriptLoader + { + public spell_dru_lifebloom() : base("spell_dru_lifebloom") { } + + class spell_dru_lifebloom_AuraScript : AuraScript + { + public override bool Validate(SpellInfo spell) + { + return ValidateSpellInfo(SpellIds.LifebloomFinalHeal, SpellIds.LifebloomEnergize); + } + + void AfterRemove(AuraEffect aurEff, AuraEffectHandleModes mode) + { + // Final heal only on duration end + if (GetTargetApplication().GetRemoveMode() != AuraRemoveMode.Expire) + return; + + // final heal + uint stack = GetStackAmount(); + uint healAmount = (uint)aurEff.GetAmount(); + Unit caster = GetCaster(); + if (caster != null) + { + healAmount = caster.SpellHealingBonusDone(GetTarget(), GetSpellInfo(), healAmount, DamageEffectType.Heal, aurEff.GetSpellEffectInfo(), stack); + healAmount = GetTarget().SpellHealingBonusTaken(caster, GetSpellInfo(), healAmount, DamageEffectType.Heal, aurEff.GetSpellEffectInfo(), stack); + + GetTarget().CastCustomSpell(GetTarget(), SpellIds.LifebloomFinalHeal, (int)healAmount, 0, 0, true, null, aurEff, GetCasterGUID()); + + // restore mana + var costs = GetSpellInfo().CalcPowerCost(caster, GetSpellInfo().GetSchoolMask()); + var m = costs.Find(cost => cost.Power == PowerType.Mana); + if (m != null) + { + int returnMana = m.Amount * (int)stack / 2; + caster.CastCustomSpell(caster, SpellIds.LifebloomEnergize, returnMana, 0, 0, true, null, aurEff, GetCasterGUID()); + } + return; + } + + GetTarget().CastCustomSpell(GetTarget(), SpellIds.LifebloomFinalHeal, (int)healAmount, 0, 0, true, null, aurEff, GetCasterGUID()); + } + + void HandleDispel(DispelInfo dispelInfo) + { + Unit target = GetUnitOwner(); + if (target != null) + { + AuraEffect aurEff = GetEffect(1); + if (aurEff != null) + { + // final heal + uint healAmount = (uint)aurEff.GetAmount(); + Unit caster = GetCaster(); + if (caster != null) + { + healAmount = caster.SpellHealingBonusDone(target, GetSpellInfo(), healAmount, DamageEffectType.Heal, aurEff.GetSpellEffectInfo(), dispelInfo.GetRemovedCharges()); + healAmount = target.SpellHealingBonusTaken(caster, GetSpellInfo(), healAmount, DamageEffectType.Heal, aurEff.GetSpellEffectInfo(), dispelInfo.GetRemovedCharges()); + target.CastCustomSpell(target, SpellIds.LifebloomFinalHeal, (int)healAmount, 0, 0, true, null, null, GetCasterGUID()); + + // restore mana + var costs = GetSpellInfo().CalcPowerCost(caster, GetSpellInfo().GetSchoolMask()); + var m = costs.Find(cost => cost.Power == PowerType.Mana); + if (m != null) + { + int returnMana = m.Amount * dispelInfo.GetRemovedCharges() / 2; + caster.CastCustomSpell(caster, SpellIds.LifebloomEnergize, returnMana, 0, 0, true, null, null, GetCasterGUID()); + } + return; + } + + target.CastCustomSpell(target, SpellIds.LifebloomFinalHeal, (int)healAmount, 0, 0, true, null, null, GetCasterGUID()); + } + } + } + + public override void Register() + { + AfterEffectRemove.Add(new EffectApplyHandler(AfterRemove, 1, AuraType.Dummy, AuraEffectHandleModes.Real)); + AfterDispel.Add(new AuraDispelHandler(HandleDispel)); + } + } + + public override AuraScript GetAuraScript() + { + return new spell_dru_lifebloom_AuraScript(); + } + } + + // 48496 - Living Seed + [Script] + class spell_dru_living_seed : SpellScriptLoader + { + public spell_dru_living_seed() : base("spell_dru_living_seed") { } + + class spell_dru_living_seed_AuraScript : AuraScript + { + public override bool Validate(SpellInfo spellInfo) + { + return ValidateSpellInfo(SpellIds.LivingSeedProc); + } + + void HandleProc(AuraEffect aurEff, ProcEventInfo eventInfo) + { + PreventDefaultAction(); + int amount = (int)MathFunctions.CalculatePct(eventInfo.GetHealInfo().GetHeal(), aurEff.GetAmount()); + GetTarget().CastCustomSpell(SpellIds.LivingSeedProc, SpellValueMod.BasePoint0, amount, eventInfo.GetProcTarget(), true, null, aurEff); + } + + public override void Register() + { + OnEffectProc.Add(new EffectProcHandler(HandleProc, 0, AuraType.Dummy)); + } + } + + public override AuraScript GetAuraScript() + { + return new spell_dru_living_seed_AuraScript(); + } + } + + // 48504 - Living Seed (Proc) + [Script] + class spell_dru_living_seed_proc : SpellScriptLoader + { + public spell_dru_living_seed_proc() : base("spell_dru_living_seed_proc") { } + + class spell_dru_living_seed_proc_AuraScript : AuraScript + { + public override bool Validate(SpellInfo spellInfo) + { + return ValidateSpellInfo(SpellIds.LivingSeedHeal); + } + + void HandleProc(AuraEffect aurEff, ProcEventInfo eventInfo) + { + PreventDefaultAction(); + GetTarget().CastCustomSpell(SpellIds.LivingSeedHeal, SpellValueMod.BasePoint0, aurEff.GetAmount(), GetTarget(), true, null, aurEff); + } + + public override void Register() + { + OnEffectProc.Add(new EffectProcHandler(HandleProc, 0, AuraType.Dummy)); + } + } + + public override AuraScript GetAuraScript() + { + return new spell_dru_living_seed_proc_AuraScript(); + } + } + + [Script] // 8921 - Moonfire + class spell_dru_moonfire : SpellScriptLoader + { + public spell_dru_moonfire() : base("spell_dru_moonfire") { } + + class spell_dru_moonfire_SpellScript : SpellScript + { + void HandleOnHit(uint effIndex) + { + GetCaster().CastSpell(GetHitUnit(), SpellIds.MoonfireDamage, true); + } + + public override void Register() + { + OnEffectHitTarget.Add(new EffectHandler(HandleOnHit, 0, SpellEffectName.Dummy)); + } + } + + public override SpellScript GetSpellScript() + { + return new spell_dru_moonfire_SpellScript(); + } + } + + // 16972 - Predatory Strikes + [Script] + class spell_dru_predatory_strikes : SpellScriptLoader + { + public spell_dru_predatory_strikes() : base("spell_dru_predatory_strikes") { } + + class spell_dru_predatory_strikes_AuraScript : AuraScript + { + void UpdateAmount(AuraEffect aurEff, AuraEffectHandleModes mode) + { + Player target = GetTarget().ToPlayer(); + if (target != null) + target.UpdateAttackPowerAndDamage(); + } + + public override void Register() + { + AfterEffectApply.Add(new EffectApplyHandler(UpdateAmount, SpellConst.EffectAll, AuraType.Dummy, AuraEffectHandleModes.ChangeAmountMask)); + AfterEffectRemove.Add(new EffectApplyHandler(UpdateAmount, SpellConst.EffectAll, AuraType.Dummy, AuraEffectHandleModes.ChangeAmountMask)); + } + } + + public override AuraScript GetAuraScript() + { + return new spell_dru_predatory_strikes_AuraScript(); + } + } + + // 1079 - Rip + [Script] + class spell_dru_rip : SpellScriptLoader + { + public spell_dru_rip() : base("spell_dru_rip") { } + + class spell_dru_rip_AuraScript : AuraScript + { + public override bool Load() + { + Unit caster = GetCaster(); + return caster != null && caster.IsTypeId(TypeId.Player); + } + + void CalculateAmount(AuraEffect aurEff, ref int amount, ref bool canBeRecalculated) + { + canBeRecalculated = false; + + Unit caster = GetCaster(); + if (caster != null) + { + // 0.01 * $AP * cp + byte cp = caster.ToPlayer().GetComboPoints(); + + // Idol of Feral Shadows. Can't be handled as SpellMod due its dependency from CPs + AuraEffect idol = caster.GetAuraEffect(SpellIds.IdolOfFeralShadows, 0); + if (idol != null) + amount += cp * idol.GetAmount(); + // Idol of Worship. Can't be handled as SpellMod due its dependency from CPs + else if ((idol = caster.GetAuraEffect(SpellIds.IdolOfWorship, 0)) != null) + amount += cp * idol.GetAmount(); + + amount += (int)MathFunctions.CalculatePct(caster.GetTotalAttackPowerValue(WeaponAttackType.BaseAttack), cp); + } + } + + public override void Register() + { + DoEffectCalcAmount.Add(new EffectCalcAmountHandler(CalculateAmount, 0, AuraType.PeriodicDamage)); + } + } + + public override AuraScript GetAuraScript() + { + return new spell_dru_rip_AuraScript(); + } + } + + [Script] // 16864 - Omen of Clarity + class spell_dru_omen_of_clarity : SpellScriptLoader + { + public spell_dru_omen_of_clarity() : base("spell_dru_omen_of_clarity") { } + + class spell_dru_omen_of_clarity_AuraScript : AuraScript + { + public override bool Validate(SpellInfo spellInfo) + { + return ValidateSpellInfo(SpellIds.BalanceT10Bonus, SpellIds.BalanceT10BonusProc); + } + + void HandleProc(AuraEffect aurEff, ProcEventInfo eventInfo) + { + Unit target = GetTarget(); + if (target.HasAura(SpellIds.BalanceT10Bonus)) + target.CastSpell((Unit)null, SpellIds.BalanceT10BonusProc, true, null); + } + + public override void Register() + { + OnEffectProc.Add(new EffectProcHandler(HandleProc, 0, AuraType.ProcTriggerSpell)); + } + } + + public override AuraScript GetAuraScript() + { + return new spell_dru_omen_of_clarity_AuraScript(); + } + } + + // 52610 - Savage Roar + [Script] + class spell_dru_savage_roar : SpellScriptLoader + { + public spell_dru_savage_roar() : base("spell_dru_savage_roar") { } + + class spell_dru_savage_roar_SpellScript : SpellScript + { + SpellCastResult CheckCast() + { + Unit caster = GetCaster(); + if (caster.GetShapeshiftForm() != ShapeShiftForm.CatForm) + return SpellCastResult.OnlyShapeshift; + + return SpellCastResult.SpellCastOk; + } + + public override void Register() + { + OnCheckCast.Add(new CheckCastHandler(CheckCast)); + } + } + + class spell_dru_savage_roar_AuraScript : AuraScript + { + public override bool Validate(SpellInfo spellInfo) + { + return ValidateSpellInfo(SpellIds.SavageRoar); + } + + void AfterApply(AuraEffect aurEff, AuraEffectHandleModes mode) + { + Unit target = GetTarget(); + target.CastSpell(target, SpellIds.SavageRoar, true, null, aurEff, GetCasterGUID()); + } + + void AfterRemove(AuraEffect aurEff, AuraEffectHandleModes mode) + { + GetTarget().RemoveAurasDueToSpell(SpellIds.SavageRoar); + } + + public override void Register() + { + AfterEffectApply.Add(new EffectApplyHandler(AfterApply, 1, AuraType.Dummy, AuraEffectHandleModes.Real)); + AfterEffectRemove.Add(new EffectApplyHandler(AfterRemove, 1, AuraType.Dummy, AuraEffectHandleModes.Real)); + } + } + + public override SpellScript GetSpellScript() + { + return new spell_dru_savage_roar_SpellScript(); + } + + public override AuraScript GetAuraScript() + { + return new spell_dru_savage_roar_AuraScript(); + } + } + + [Script] // 78892 - Stampede + class spell_dru_stampede : SpellScriptLoader + { + public spell_dru_stampede() : base("spell_dru_stampede") { } + + class spell_dru_stampede_AuraScript : AuraScript + { + public override bool Validate(SpellInfo spellInfo) + { + return ValidateSpellInfo(SpellIds.StampedeBearRank1, SpellIds.StampedeCatRank1, SpellIds.StampedeCatState, SpellIds.FeralChargeCat, SpellIds.FeralChargeBear); + } + + void HandleEffectCatProc(AuraEffect aurEff, ProcEventInfo eventInfo) + { + PreventDefaultAction(); + if (GetTarget().GetShapeshiftForm() != ShapeShiftForm.CatForm || eventInfo.GetDamageInfo().GetSpellInfo().Id != SpellIds.FeralChargeCat) + return; + + GetTarget().CastSpell(GetTarget(), Global.SpellMgr.GetSpellWithRank(SpellIds.StampedeCatRank1, GetSpellInfo().GetRank()), true, null, aurEff); + GetTarget().CastSpell(GetTarget(), SpellIds.StampedeCatState, true, null, aurEff); + } + + void HandleEffectBearProc(AuraEffect aurEff, ProcEventInfo eventInfo) + { + PreventDefaultAction(); + if (GetTarget().GetShapeshiftForm() != ShapeShiftForm.BearForm || eventInfo.GetDamageInfo().GetSpellInfo().Id != SpellIds.FeralChargeBear) + return; + + GetTarget().CastSpell(GetTarget(), Global.SpellMgr.GetSpellWithRank(SpellIds.StampedeBearRank1, GetSpellInfo().GetRank()), true, null, aurEff); + } + + public override void Register() + { + OnEffectProc.Add(new EffectProcHandler(HandleEffectCatProc, 0, AuraType.Dummy)); + OnEffectProc.Add(new EffectProcHandler(HandleEffectBearProc, 1, AuraType.Dummy)); + } + } + + public override AuraScript GetAuraScript() + { + return new spell_dru_stampede_AuraScript(); + } + } + + // 50286 - Starfall (Dummy) + [Script] + class spell_dru_starfall_dummy : SpellScriptLoader + { + public spell_dru_starfall_dummy() : base("spell_dru_starfall_dummy") { } + + class spell_dru_starfall_dummy_SpellScript : SpellScript + { + void FilterTargets(List targets) + { + targets.Resize(2); + } + + void HandleDummy(uint effIndex) + { + Unit caster = GetCaster(); + // Shapeshifting into an animal form or mounting cancels the effect + if (caster.GetCreatureType() == CreatureType.Beast || caster.IsMounted()) + { + SpellInfo spellInfo = GetTriggeringSpell(); + if (spellInfo != null) + caster.RemoveAurasDueToSpell(spellInfo.Id); + return; + } + + // Any effect which causes you to lose control of your character will supress the starfall effect. + if (caster.HasUnitState(UnitState.Controlled)) + return; + + caster.CastSpell(GetHitUnit(), (uint)GetEffectValue(), true); + } + + public override void Register() + { + OnObjectAreaTargetSelect.Add(new ObjectAreaTargetSelectHandler(FilterTargets, 0, Targets.UnitSrcAreaEnemy)); + OnEffectHitTarget.Add(new EffectHandler(HandleDummy, 0, SpellEffectName.Dummy)); + } + } + + public override SpellScript GetSpellScript() + { + return new spell_dru_starfall_dummy_SpellScript(); + } + } + + [Script] // 93402 - Sunfire + class spell_dru_sunfire : SpellScriptLoader + { + public spell_dru_sunfire() : base("spell_dru_sunfire") { } + + class spell_dru_sunfire_SpellScript : SpellScript + { + void HandleOnHit(uint effIndex) + { + GetCaster().CastSpell(GetHitUnit(), SpellIds.SunfireDamage, true); + } + + public override void Register() + { + OnEffectHitTarget.Add(new EffectHandler(HandleOnHit, 0, SpellEffectName.Dummy)); + } + } + + public override SpellScript GetSpellScript() + { + return new spell_dru_sunfire_SpellScript(); + } + } + + // 61336 - Survival Instincts + [Script] + class spell_dru_survival_instincts : SpellScriptLoader + { + public spell_dru_survival_instincts() : base("spell_dru_survival_instincts") { } + + class spell_dru_survival_instincts_SpellScript : SpellScript + { + SpellCastResult CheckCast() + { + Unit caster = GetCaster(); + if (!caster.IsInFeralForm()) + return SpellCastResult.OnlyShapeshift; + + return SpellCastResult.SpellCastOk; + } + + public override void Register() + { + OnCheckCast.Add(new CheckCastHandler(CheckCast)); + } + } + + class spell_dru_survival_instincts_AuraScript : AuraScript + { + public override bool Validate(SpellInfo spell) + { + return ValidateSpellInfo(SpellIds.SurvivalInstincts); + } + + void AfterApply(AuraEffect aurEff, AuraEffectHandleModes mode) + { + Unit target = GetTarget(); + int bp0 = (int)target.CountPctFromMaxHealth(aurEff.GetAmount()); + target.CastCustomSpell(target, SpellIds.SurvivalInstincts, bp0, 0, 0, true); + } + + void AfterRemove(AuraEffect aurEff, AuraEffectHandleModes mode) + { + GetTarget().RemoveAurasDueToSpell(SpellIds.SurvivalInstincts); + } + + public override void Register() + { + AfterEffectApply.Add(new EffectApplyHandler(AfterApply, 0, AuraType.Dummy, AuraEffectHandleModes.ChangeAmountMask)); + AfterEffectRemove.Add(new EffectApplyHandler(AfterRemove, 0, AuraType.Dummy, AuraEffectHandleModes.ChangeAmountMask)); + } + } + + public override SpellScript GetSpellScript() + { + return new spell_dru_survival_instincts_SpellScript(); + } + + public override AuraScript GetAuraScript() + { + return new spell_dru_survival_instincts_AuraScript(); + } + } + + // 40121 - Swift Flight Form (Passive) + [Script] + class spell_dru_swift_flight_passive : SpellScriptLoader + { + public spell_dru_swift_flight_passive() : base("spell_dru_swift_flight_passive") { } + + class spell_dru_swift_flight_passive_AuraScript : AuraScript + { + public override bool Load() + { + return GetCaster().IsTypeId(TypeId.Player); + } + + void CalculateAmount(AuraEffect aurEff, ref int amount, ref bool canBeRecalculated) + { + Player caster = GetCaster().ToPlayer(); + if (caster != null) + if (caster.GetSkillValue(SkillType.Riding) >= 375) + amount = 310; + } + + public override void Register() + { + DoEffectCalcAmount.Add(new EffectCalcAmountHandler(CalculateAmount, 1, AuraType.ModIncreaseVehicleFlightSpeed)); + } + } + + public override AuraScript GetAuraScript() + { + return new spell_dru_swift_flight_passive_AuraScript(); + } + } + + [Script] // 28744 - Regrowth + class spell_dru_t3_6p_bonus : SpellScriptLoader + { + public spell_dru_t3_6p_bonus() : base("spell_dru_t3_6p_bonus") { } + + class spell_dru_t3_6p_bonus_AuraScript : AuraScript + { + public override bool Validate(SpellInfo spellInfo) + { + return ValidateSpellInfo(SpellIds.BlessingOfTheClaw); + } + + void HandleProc(AuraEffect aurEff, ProcEventInfo eventInfo) + { + PreventDefaultAction(); + eventInfo.GetActor().CastSpell(eventInfo.GetProcTarget(), SpellIds.BlessingOfTheClaw, true); + } + + public override void Register() + { + OnEffectProc.Add(new EffectProcHandler(HandleProc, 0, AuraType.OverrideClassScripts)); + } + } + + public override AuraScript GetAuraScript() + { + return new spell_dru_t3_6p_bonus_AuraScript(); + } + } + + [Script] // 28719 - Healing Touch + class spell_dru_t3_8p_bonus : SpellScriptLoader + { + public spell_dru_t3_8p_bonus() : base("spell_dru_t3_8p_bonus") { } + + class spell_dru_t3_8p_bonus_AuraScript : AuraScript + { + public override bool Validate(SpellInfo spellInfo) + { + return ValidateSpellInfo(SpellIds.Exhilarate); + } + + void HandleProc(AuraEffect aurEff, ProcEventInfo eventInfo) + { + PreventDefaultAction(); + SpellInfo spellInfo = eventInfo.GetSpellInfo(); + if (spellInfo == null) + return; + + Unit caster = eventInfo.GetActor(); + var costs = spellInfo.CalcPowerCost(caster, spellInfo.GetSchoolMask()); + var m = costs.First(cost => { return cost.Power == PowerType.Mana; }); + if (m == null) + return; + + int amount = MathFunctions.CalculatePct(m.Amount, aurEff.GetAmount()); + caster.CastCustomSpell(SpellIds.Exhilarate, SpellValueMod.BasePoint0, amount, (Unit)null, true); + } + + public override void Register() + { + OnEffectProc.Add(new EffectProcHandler(HandleProc, 0, AuraType.Dummy)); + } + } + + public override AuraScript GetAuraScript() + { + return new spell_dru_t3_8p_bonus_AuraScript(); + } + } + + // 37288 - Mana Restore + [Script] // 37295 - Mana Restore + class spell_dru_t4_2p_bonus : SpellScriptLoader + { + public spell_dru_t4_2p_bonus() : base("spell_dru_t4_2p_bonus") { } + + class spell_dru_t4_2p_bonus_AuraScript : AuraScript + { + public override bool Validate(SpellInfo spellInfo) + { + return ValidateSpellInfo(SpellIds.Infusion); + } + + void HandleProc(AuraEffect aurEff, ProcEventInfo eventInfo) + { + PreventDefaultAction(); + eventInfo.GetActor().CastSpell((Unit)null, SpellIds.Infusion, true); + } + + public override void Register() + { + OnEffectProc.Add(new EffectProcHandler(HandleProc, 0, AuraType.Dummy)); + } + } + + public override AuraScript GetAuraScript() + { + return new spell_dru_t4_2p_bonus_AuraScript(); + } + } + + [Script] // 40442 - Druid Tier 6 Trinket + class spell_dru_item_t6_trinket : SpellScriptLoader + { + public spell_dru_item_t6_trinket() : base("spell_dru_item_t6_trinket") { } + + class spell_dru_item_t6_trinket_AuraScript : AuraScript + { + public override bool Validate(SpellInfo spellInfo) + { + return ValidateSpellInfo(SpellIds.BlessingOfTheRemulos, SpellIds.BlessingOfTheElune, SpellIds.BlessingOfTheCenarius); + } + + void HandleProc(AuraEffect aurEff, ProcEventInfo eventInfo) + { + PreventDefaultAction(); + SpellInfo spellInfo = eventInfo.GetSpellInfo(); + if (spellInfo == null) + return; + + uint spellId; + int chance; + + // Starfire + if (spellInfo.SpellFamilyFlags[0].HasAnyFlag(0x00000004u)) + { + spellId = SpellIds.BlessingOfTheRemulos; + chance = 25; + } + // Rejuvenation + else if (spellInfo.SpellFamilyFlags[0].HasAnyFlag(0x00000010u)) + { + spellId = SpellIds.BlessingOfTheElune; + chance = 25; + } + // Mangle (Bear) and Mangle (Cat) + else if (spellInfo.SpellFamilyFlags[1].HasAnyFlag(0x00000440u)) + { + spellId = SpellIds.BlessingOfTheCenarius; + chance = 40; + } + else + return; + + if (RandomHelper.randChance(chance)) + eventInfo.GetActor().CastSpell((Unit)null, spellId, true); + } + + public override void Register() + { + OnEffectProc.Add(new EffectProcHandler(HandleProc, 0, AuraType.Dummy)); + } + } + + public override AuraScript GetAuraScript() + { + return new spell_dru_item_t6_trinket_AuraScript(); + } + } + + [Script] // 70723 - Item - Druid T10 Balance 4P Bonus + class spell_dru_t10_balance_4p_bonus : SpellScriptLoader + { + public spell_dru_t10_balance_4p_bonus() : base("spell_dru_t10_balance_4p_bonus") { } + + class spell_dru_t10_balance_4p_bonus_AuraScript : AuraScript + { + public override bool Validate(SpellInfo spellInfo) + { + return ValidateSpellInfo(SpellIds.Languish); + } + + void HandleProc(AuraEffect aurEff, ProcEventInfo eventInfo) + { + PreventDefaultAction(); + + DamageInfo damageInfo = eventInfo.GetDamageInfo(); + if (damageInfo == null || damageInfo.GetDamage() == 0) + return; + + Unit caster = eventInfo.GetActor(); + Unit target = eventInfo.GetProcTarget(); + + SpellInfo spellInfo = Global.SpellMgr.GetSpellInfo(SpellIds.Languish); + int amount = (int)MathFunctions.CalculatePct(damageInfo.GetDamage(), aurEff.GetAmount()); + amount /= (int)spellInfo.GetMaxTicks(Difficulty.None); + // Add remaining ticks to damage done + amount += (int)target.GetRemainingPeriodicAmount(caster.GetGUID(), SpellIds.Languish, AuraType.PeriodicDamage); + + caster.CastCustomSpell(SpellIds.Languish, SpellValueMod.BasePoint0, amount, target, true); + } + + public override void Register() + { + OnEffectProc.Add(new EffectProcHandler(HandleProc, 0, AuraType.Dummy)); + } + } + + public override AuraScript GetAuraScript() + { + return new spell_dru_t10_balance_4p_bonus_AuraScript(); + } + } + + // 70691 - Item T10 Restoration 4P Bonus + [Script] + class spell_dru_t10_restoration_4p_bonus : SpellScriptLoader + { + public spell_dru_t10_restoration_4p_bonus() : base("spell_dru_t10_restoration_4p_bonus") { } + + class spell_dru_t10_restoration_4p_bonus_SpellScript : SpellScript + { + public override bool Load() + { + return GetCaster().IsTypeId(TypeId.Player); + } + + void FilterTargets(List targets) + { + if (GetCaster().ToPlayer().GetGroup() == null) + { + targets.Clear(); + targets.Add(GetCaster()); + } + else + { + targets.Remove(GetExplTargetUnit()); + List tempTargets = new List(); + foreach (var obj in targets) + if (obj.IsTypeId(TypeId.Player) && GetCaster().IsInRaidWith(obj.ToUnit())) + tempTargets.Add(obj.ToUnit()); + + if (tempTargets.Empty()) + { + targets.Clear(); + FinishCast(SpellCastResult.DontReport); + return; + } + + Unit target = tempTargets.PickRandom(); + targets.Clear(); + targets.Add(target); + } + } + + public override void Register() + { + OnObjectAreaTargetSelect.Add(new ObjectAreaTargetSelectHandler(FilterTargets, 0, Targets.UnitDestAreaAlly)); + } + } + + public override SpellScript GetSpellScript() + { + return new spell_dru_t10_restoration_4p_bonus_SpellScript(); + } + } + + [Script] // 70664 - Druid T10 Restoration 4P Bonus (Rejuvenation) + class spell_dru_t10_restoration_4p_bonus_dummy : SpellScriptLoader + { + public spell_dru_t10_restoration_4p_bonus_dummy() : base("spell_dru_t10_restoration_4p_bonus_dummy") { } + + class spell_dru_t10_restoration_4p_bonus_dummy_AuraScript : AuraScript + { + public override bool Validate(SpellInfo spellInfo) + { + return ValidateSpellInfo(SpellIds.RejuvenationT10Proc); + } + + bool CheckProc(ProcEventInfo eventInfo) + { + SpellInfo spellInfo = eventInfo.GetSpellInfo(); + if (spellInfo == null || spellInfo.Id == SpellIds.RejuvenationT10Proc) + return false; + + HealInfo healInfo = eventInfo.GetHealInfo(); + if (healInfo == null || healInfo.GetHeal() == 0) + return false; + + Player caster = eventInfo.GetActor().ToPlayer(); + if (!caster) + return false; + + return caster.GetGroup() || caster != eventInfo.GetProcTarget(); + } + + void HandleProc(AuraEffect aurEff, ProcEventInfo eventInfo) + { + PreventDefaultAction(); + + int amount = (int)eventInfo.GetHealInfo().GetHeal(); + eventInfo.GetActor().CastCustomSpell(SpellIds.RejuvenationT10Proc, SpellValueMod.BasePoint0, amount, (Unit)null, true); + } + + public override void Register() + { + DoCheckProc.Add(new CheckProcHandler(CheckProc)); + OnEffectProc.Add(new EffectProcHandler(HandleProc, 0, AuraType.Dummy)); + } + } + + public override AuraScript GetAuraScript() + { + return new spell_dru_t10_restoration_4p_bonus_dummy_AuraScript(); + } + } + + [Script] // 48438 - Wild Growth + class spell_dru_wild_growth : SpellScriptLoader + { + public spell_dru_wild_growth() : base("spell_dru_wild_growth") { } + + class spell_dru_wild_growth_SpellScript : SpellScript + { + public override bool Validate(SpellInfo spellInfo) + { + SpellEffectInfo effect2 = spellInfo.GetEffect(2); + if (effect2 == null || effect2.IsEffect() || effect2.CalcValue() <= 0) + return false; + return true; + } + + void FilterTargets(List targets) + { + targets.RemoveAll(obj => + { + Unit target = obj.ToUnit(); + if (target) + return !GetCaster().IsInRaidWith(target); + + return true; + }); + + int maxTargets = GetSpellInfo().GetEffect(2).CalcValue(GetCaster()); + + if (targets.Count > maxTargets) + { + targets.Sort(new HealthPctOrderPred()); + targets.RemoveRange(maxTargets, targets.Count - maxTargets); + } + + _targets = targets; + } + + void SetTargets(List targets) + { + targets = _targets; + } + + public override void Register() + { + OnObjectAreaTargetSelect.Add(new ObjectAreaTargetSelectHandler(FilterTargets, 0, Targets.UnitDestAreaAlly)); + OnObjectAreaTargetSelect.Add(new ObjectAreaTargetSelectHandler(SetTargets, 1, Targets.UnitDestAreaAlly)); + } + + List _targets; + } + + public override SpellScript GetSpellScript() + { + return new spell_dru_wild_growth_SpellScript(); + } + } +} diff --git a/Scripts/Spells/Generic.cs b/Scripts/Spells/Generic.cs new file mode 100644 index 000000000..4a04ca1f7 --- /dev/null +++ b/Scripts/Spells/Generic.cs @@ -0,0 +1,4171 @@ +/* + * Copyright (C) 2012-2017 CypherCore + * + * 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, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +using Framework.Constants; +using Framework.Dynamic; +using Game.DataStorage; +using Game.Entities; +using Game.Maps; +using Game.Network.Packets; +using Game.Scripting; +using Game.Spells; +using System; +using System.Collections.Generic; +using System.Linq; + +namespace Scripts.Spells.Generic +{ + struct SpellIds + { + //Adaptivewarding + public const uint GenAdaptiveWardingFire = 28765; + public const uint GenAdaptiveWardingNature = 28768; + public const uint GenAdaptiveWardingFrost = 28766; + public const uint GenAdaptiveWardingShadow = 28769; + public const uint GenAdaptiveWardingArcane = 28770; + + //Animalbloodpoolspell + public const uint AnimalBlood = 46221; + public const uint SpawnBloodPool = 63471; + + //Serviceuniform + public const uint ServiceUniform = 71450; + + //Genericbandage + public const uint RecentlyBandaged = 11196; + + //Bloodreserve + public const uint BloodReserveAura = 64568; + public const uint BloodReserveHeal = 64569; + + //Bonked + public const uint Bonked = 62991; + public const uint FormSwordDefeat = 62994; + public const uint Onguard = 62972; + + //Breakshieldspells + public const uint BreakShieldDamage2k = 62626; + public const uint BreakShieldDamage10k = 64590; + public const uint BreakShieldTriggerFactionMounts = 62575; // Also On Toc5 Mounts + public const uint BreakShieldTriggerCampaingWarhorse = 64595; + public const uint BreakShieldTriggerUnk = 66480; + + //Cannibalizespells + public const uint CannibalizeTriggered = 20578; + + //Chaosblast + public const uint ChaosBlast = 37675; + + //Clone + public const uint NightmareFigmentMirrorImage = 57528; + + //Cloneweaponspells + public const uint WeaponAura = 41054; + public const uint Weapon2Aura = 63418; + public const uint Weapon3Aura = 69893; + + public const uint OffhandAura = 45205; + public const uint Offhand2Aura = 69896; + + public const uint RangedAura = 57594; + + //Createlancespells + public const uint CreateLanceAlliance = 63914; + public const uint CreateLanceHorde = 63919; + + //Dalarandisguisespells + public const uint SunreaverTrigger = 69672; + public const uint SunreaverFemale = 70973; + public const uint SunreaverMale = 70974; + + public const uint SilverCovenantTrigger = 69673; + public const uint SilverCovenantFemale = 70971; + public const uint SilverCovenantMale = 70972; + + //Defendvisuals + public const uint VisualShield1 = 63130; + public const uint VisualShield2 = 63131; + public const uint VisualShield3 = 63132; + + //Divinestormspell + public const uint DivineStorm = 53385; + + //Elunecandle + public const uint OmenHead = 26622; + public const uint OmenChest = 26624; + public const uint OmenHandR = 26625; + public const uint OmenHandL = 26649; + public const uint Normal = 26636; + + //Fishingspells + public const uint FishingNoFishingPole = 131476; + public const uint FishingWithPole = 131490; + + //Transporterbackfires + public const uint TransporterMalfunctionPolymorph = 23444; + public const uint TransporterEviltwin = 23445; + public const uint TransporterMalfunctionMiss = 36902; + + //Gnomishtransporter + public const uint TransporterSuccess = 23441; + public const uint TransporterFailure = 23446; + + //Interrupt + public const uint GenThrowInterrupt = 32747; + + //Genericlifebloomspells + public const uint HexlordMalacrass = 43422; + public const uint TurragePaw = 52552; + public const uint CenarionScout = 53692; + public const uint TwistedVisage = 57763; + public const uint FactionChampionsDru = 66094; + + //Chargespells + public const uint Damage8k5 = 62874; + public const uint Damage20k = 68498; + public const uint Damage45k = 64591; + + public const uint ChargingEffect8k5 = 63661; + public const uint ChargingEffect20k1 = 68284; + public const uint ChargingEffect20k2 = 68501; + public const uint ChargingEffect45k1 = 62563; + public const uint ChargingEffect45k2 = 66481; + + public const uint TriggerFactionMounts = 62960; + public const uint TriggerTrialChampion = 68282; + + public const uint MissEffect = 62977; + + //Netherbloom + public const uint NetherBloomPollen1 = 28703; + + //Nightmarevine + public const uint NightmarePollen = 28721; + + //Obsidianarmorspells + public const uint Holy = 27536; + public const uint Fire = 27533; + public const uint Nature = 27538; + public const uint Frost = 27534; + public const uint Shadow = 27535; + public const uint Arcane = 27540; + + //Tournamentpennantspells + public const uint StormwindAspirant = 62595; + public const uint StormwindValiant = 62596; + public const uint StormwindChampion = 62594; + public const uint GnomereganAspirant = 63394; + public const uint GnomereganValiant = 63395; + public const uint GnomereganChampion = 63396; + public const uint SenjinAspirant = 63397; + public const uint SenjinValiant = 63398; + public const uint SenjinChampion = 63399; + public const uint SilvermoonAspirant = 63401; + public const uint SilvermoonValiant = 63402; + public const uint SilvermoonChampion = 63403; + public const uint DarnassusAspirant = 63404; + public const uint DarnassusValiant = 63405; + public const uint DarnassusChampion = 63406; + public const uint ExodarAspirant = 63421; + public const uint ExodarValiant = 63422; + public const uint ExodarChampion = 63423; + public const uint IronforgeAspirant = 63425; + public const uint IronforgeValiant = 63426; + public const uint IronforgeChampion = 63427; + public const uint UndercityAspirant = 63428; + public const uint UndercityValiant = 63429; + public const uint UndercityChampion = 63430; + public const uint OrgrimmarAspirant = 63431; + public const uint OrgrimmarValiant = 63432; + public const uint OrgrimmarChampion = 63433; + public const uint ThunderbluffAspirant = 63434; + public const uint ThunderbluffValiant = 63435; + public const uint ThunderbluffChampion = 63436; + public const uint ArgentcrusadeAspirant = 63606; + public const uint ArgentcrusadeValiant = 63500; + public const uint ArgentcrusadeChampion = 63501; + public const uint EbonbladeAspirant = 63607; + public const uint EbonbladeValiant = 63608; + public const uint EbonbladeChampion = 63609; + + //Orcdisguisespells + public const uint OrcDisguiseTrigger = 45759; + public const uint OrcDisguiseMale = 45760; + public const uint OrcDisguiseFemale = 45762; + + //Parachutespells + public const uint Parachute = 45472; + public const uint ParachuteBuff = 44795; + + //Trinketspells + public const uint PvpTrinketAlliance = 97403; + public const uint PvpTrinketHorde = 97404; + + //Replenishment + public const uint Replenishment = 57669; + public const uint InfiniteReplenishment = 61782; + + //Runningwild + public const uint AlteredForm = 97709; + + //Seaforiumspells + public const uint PlantChargesCreditAchievement = 60937; + + //Summonelemental + public const uint SummonFireElemental = 8985; + public const uint SummonEarthElemental = 19704; + + //Tournamentmountsspells + public const uint LanceEquipped = 62853; + + //Mountedduelspells + public const uint OnTournamentMount = 63034; + public const uint MountedDuel = 62875; + + //Pvptrinkettriggeredspells + public const uint WillOfTheForsakenCooldownTrigger = 72752; + public const uint WillOfTheForsakenCooldownTriggerWotf = 72757; + + //Friendorfowl + public const uint TurkeyVengeance = 25285; + + //Vampirictouch + public const uint VampiricTouchHeal = 52724; + + //Vehiclescaling + public const uint GearScaling = 66668; + + //Whispergulchyoggsaronwhisper + public const uint YoggSaronWhisperDummy = 29072; + + //Gmfreeze + public const uint GmFreeze = 9454; + + //Landmineknockbackachievement + public const uint LandmineKnockbackAchievement = 57064; + + //Ponyspells + public const uint AchievementPonyup = 3736; + public const uint MountPony = 29736; + + //Kazrogalhellfiremark + public const uint MarkOfKazrogalHellfire = 189512; + public const uint MarkOfKazrogalDamageHellfire = 189515; + + // Auraprocremovespells + public const uint FaceRage = 99947; + public const uint ImpatientMind = 187213; + } + + struct CreatureIds + { + //EluneCandle + public const uint Omen = 15467; + + //TournamentMounts + public const uint StormwindSteed = 33217; + public const uint IronforgeRam = 33316; + public const uint GnomereganMechanostrider = 33317; + public const uint ExodarElekk = 33318; + public const uint DarnassianNightsaber = 33319; + public const uint OrgrimmarWolf = 33320; + public const uint DarkSpearRaptor = 33321; + public const uint ThunderBluffKodo = 33322; + public const uint SilvermoonHawkstrider = 33323; + public const uint ForsakenWarhorse = 33324; + public const uint ArgentWarhorse = 33782; + public const uint ArgentSteedAspirant = 33845; + public const uint ArgentHawkstriderAspirant = 33844; + + //PetSummoned + public const uint Doomguard = 11859; + public const uint Infernal = 89; + public const uint Imp = 416; + + //VendorBarkTrigger + public const uint AmphitheaterVendor = 30098; + } + + struct ModelIds + { + //ServiceUniform + public const uint GoblinMale = 31002; + public const uint GoblinFemale = 31003; + + //RunningWild + public const uint RunningWild = 73200; + } + + struct TextIds + { + //VendorBarkTrigger + public const uint SayAmphitheaterVendor = 0; + } + + struct AchievementIds + { + //TournamentAchievements + public const uint ChampionStormwind = 2781; + public const uint ChampionDarnassus = 2777; + public const uint ChampionIronforge = 2780; + public const uint ChampionGnomeregan = 2779; + public const uint ChampionTheExodar = 2778; + public const uint ChampionOrgrimmar = 2783; + public const uint ChampionSenJin = 2784; + public const uint ChampionThunderBluff = 2786; + public const uint ChampionUndercity = 2787; + public const uint ChampionSilvermoon = 2785; + public const uint ArgentValor = 2758; + public const uint ChampionAlliance = 2782; + public const uint ChampionHorde = 2788; + } + struct QuestIds + { + //TournamentQuests + public const uint ValiantOfStormwind = 13593; + public const uint A_ValiantOfStormwind = 13684; + public const uint ValiantOfDarnassus = 13706; + public const uint A_ValiantOfDarnassus = 13689; + public const uint ValiantOfIronforge = 13703; + public const uint A_ValiantOfIronforge = 13685; + public const uint ValiantOfGnomeregan = 13704; + public const uint A_ValiantOfGnomeregan = 13688; + public const uint ValiantOfTheExodar = 13705; + public const uint A_ValiantOfTheExodar = 13690; + public const uint ValiantOfOrgrimmar = 13707; + public const uint A_ValiantOfOrgrimmar = 13691; + public const uint ValiantOfSenJin = 13708; + public const uint A_ValiantOfSenJin = 13693; + public const uint ValiantOfThunderBluff = 13709; + public const uint A_ValiantOfThunderBluff = 13694; + public const uint ValiantOfUndercity = 13710; + public const uint A_ValiantOfUndercity = 13695; + public const uint ValiantOfSilvermoon = 13711; + public const uint A_ValiantOfSilvermoon = 13696; + } + + [Script] + class spell_gen_absorb0_hitlimit1 : SpellScriptLoader + { + public spell_gen_absorb0_hitlimit1() : base("spell_gen_absorb0_hitlimit1") { } + + class spell_gen_absorb0_hitlimit1_AuraScript : AuraScript + { + public override bool Load() + { + // Max absorb stored in 1 dummy effect + limit = GetSpellInfo().GetEffect(1).CalcValue(); + return true; + } + + void Absorb(AuraEffect aurEff, DamageInfo dmgInfo, ref uint absorbAmount) + { + absorbAmount = (uint)Math.Min(limit, absorbAmount); + } + + public override void Register() + { + OnEffectAbsorb.Add(new EffectAbsorbHandler(Absorb, 0)); + } + + int limit; + } + + public override AuraScript GetAuraScript() + { + return new spell_gen_absorb0_hitlimit1_AuraScript(); + } + } + + [Script] // 28764 - Adaptive Warding (Frostfire Regalia Set) + class spell_gen_adaptive_warding : SpellScriptLoader + { + public spell_gen_adaptive_warding() : base("spell_gen_adaptive_warding") { } + + class spell_gen_adaptive_warding_AuraScript : AuraScript + { + public override bool Validate(SpellInfo spellInfo) + { + return ValidateSpellInfo(SpellIds.GenAdaptiveWardingFire, SpellIds.GenAdaptiveWardingNature, SpellIds.GenAdaptiveWardingFrost, SpellIds.GenAdaptiveWardingShadow, SpellIds.GenAdaptiveWardingArcane); + } + + bool CheckProc(ProcEventInfo eventInfo) + { + if (eventInfo.GetDamageInfo().GetSpellInfo() != null) // eventInfo.GetSpellInfo() + return false; + + // find Mage Armor + if (GetTarget().GetAuraEffect(AuraType.ModManaRegenInterrupt, SpellFamilyNames.Mage, new FlagArray128(0x10000000, 0x0, 0x0)) == null) + return false; + + switch (SharedConst.GetFirstSchoolInMask(eventInfo.GetSchoolMask())) + { + case SpellSchools.Normal: + case SpellSchools.Holy: + return false; + default: + break; + } + return true; + } + + void HandleProc(AuraEffect aurEff, ProcEventInfo eventInfo) + { + PreventDefaultAction(); + + uint spellId = 0; + switch (SharedConst.GetFirstSchoolInMask(eventInfo.GetSchoolMask())) + { + case SpellSchools.Fire: + spellId = SpellIds.GenAdaptiveWardingFire; + break; + case SpellSchools.Nature: + spellId = SpellIds.GenAdaptiveWardingNature; + break; + case SpellSchools.Frost: + spellId = SpellIds.GenAdaptiveWardingFrost; + break; + case SpellSchools.Shadow: + spellId = SpellIds.GenAdaptiveWardingShadow; + break; + case SpellSchools.Arcane: + spellId = SpellIds.GenAdaptiveWardingArcane; + break; + default: + return; + } + GetTarget().CastSpell(GetTarget(), spellId, true, null, aurEff); + } + + public override void Register() + { + DoCheckProc.Add(new CheckProcHandler(CheckProc)); + OnEffectProc.Add(new EffectProcHandler(HandleProc, 0, AuraType.Dummy)); + } + } + + public override AuraScript GetAuraScript() + { + return new spell_gen_adaptive_warding_AuraScript(); + } + } + + [Script] + class spell_gen_allow_cast_from_item_only : SpellScriptLoader + { + public spell_gen_allow_cast_from_item_only() : base("spell_gen_allow_cast_from_item_only") { } + + class spell_gen_allow_cast_from_item_only_SpellScript : SpellScript + { + SpellCastResult CheckRequirement() + { + if (!GetCastItem()) + return SpellCastResult.CantDoThatRightNow; + return SpellCastResult.SpellCastOk; + } + + public override void Register() + { + OnCheckCast.Add(new CheckCastHandler(CheckRequirement)); + } + } + + public override SpellScript GetSpellScript() + { + return new spell_gen_allow_cast_from_item_only_SpellScript(); + } + } + + [Script] + class spell_gen_animal_blood : SpellScriptLoader + { + public spell_gen_animal_blood() : base("spell_gen_animal_blood") { } + + class spell_gen_animal_blood_AuraScript : AuraScript + { + public override bool Validate(SpellInfo spellInfo) + { + return ValidateSpellInfo(SpellIds.SpawnBloodPool); + } + + void OnApply(AuraEffect aurEff, AuraEffectHandleModes mode) + { + // Remove all auras with spell id 46221, except the one currently being applied + Aura aur; + while ((aur = GetUnitOwner().GetOwnedAura(SpellIds.AnimalBlood, ObjectGuid.Empty, ObjectGuid.Empty, 0, GetAura())) != null) + GetUnitOwner().RemoveOwnedAura(aur); + } + + void OnRemove(AuraEffect aurEff, AuraEffectHandleModes mode) + { + Unit owner = GetUnitOwner(); + if (owner) + if (owner.IsInWater()) + owner.CastSpell(owner, SpellIds.SpawnBloodPool, true); + } + + public override void Register() + { + AfterEffectApply.Add(new EffectApplyHandler(OnApply, 0, AuraType.PeriodicTriggerSpell, AuraEffectHandleModes.Real)); + AfterEffectRemove.Add(new EffectApplyHandler(OnRemove, 0, AuraType.PeriodicTriggerSpell, AuraEffectHandleModes.Real)); + } + } + + public override AuraScript GetAuraScript() + { + return new spell_gen_animal_blood_AuraScript(); + } + } + + [Script] // 41337 Aura of Anger + class spell_gen_aura_of_anger : SpellScriptLoader + { + public spell_gen_aura_of_anger() : base("spell_gen_aura_of_anger") { } + + class spell_gen_aura_of_anger_AuraScript : AuraScript + { + void HandleEffectPeriodicUpdate(AuraEffect aurEff) + { + AuraEffect aurEff1 = aurEff.GetBase().GetEffect(1); + if (aurEff1 != null) + aurEff1.ChangeAmount(aurEff1.GetAmount() + 5); + aurEff.SetAmount((int)(100 * aurEff.GetTickNumber())); + } + + public override void Register() + { + OnEffectUpdatePeriodic.Add(new EffectUpdatePeriodicHandler(HandleEffectPeriodicUpdate, 0, AuraType.PeriodicDamage)); + } + } + + public override AuraScript GetAuraScript() + { + return new spell_gen_aura_of_anger_AuraScript(); + } + } + + [Script] + class spell_gen_aura_service_uniform : SpellScriptLoader + { + public spell_gen_aura_service_uniform() : base("spell_gen_aura_service_uniform") { } + + class spell_gen_aura_service_uniform_AuraScript : AuraScript + { + public override bool Validate(SpellInfo spellInfo) + { + return ValidateSpellInfo(SpellIds.ServiceUniform); + } + + void OnApply(AuraEffect aurEff, AuraEffectHandleModes mode) + { + // Apply model goblin + Unit target = GetTarget(); + if (target.IsTypeId(TypeId.Player)) + { + if (target.GetGender() == Gender.Male) + target.SetDisplayId(ModelIds.GoblinMale); + else + target.SetDisplayId(ModelIds.GoblinFemale); + } + } + + void OnRemove(AuraEffect aurEff, AuraEffectHandleModes mode) + { + Unit target = GetTarget(); + if (target.IsTypeId(TypeId.Player)) + target.RestoreDisplayId(); + } + + public override void Register() + { + AfterEffectApply.Add(new EffectApplyHandler(OnApply, 0, AuraType.Transform, AuraEffectHandleModes.Real)); + AfterEffectRemove.Add(new EffectApplyHandler(OnRemove, 0, AuraType.Transform, AuraEffectHandleModes.Real)); + } + } + + public override AuraScript GetAuraScript() + { + return new spell_gen_aura_service_uniform_AuraScript(); + } + } + + [Script] + class spell_gen_av_drekthar_presence : SpellScriptLoader + { + public spell_gen_av_drekthar_presence() : base("spell_gen_av_drekthar_presence") { } + + class spell_gen_av_drekthar_presence_AuraScript : AuraScript + { + bool CheckAreaTarget(Unit target) + { + switch (target.GetEntry()) + { + // alliance + case 14762: // Dun Baldar North Marshal + case 14763: // Dun Baldar South Marshal + case 14764: // Icewing Marshal + case 14765: // Stonehearth Marshal + case 11948: // Vandar Stormspike + // horde + case 14772: // East Frostwolf Warmaster + case 14776: // Tower Point Warmaster + case 14773: // Iceblood Warmaster + case 14777: // West Frostwolf Warmaster + case 11946: // Drek'thar + return true; + default: + return false; + } + } + + public override void Register() + { + DoCheckAreaTarget.Add(new CheckAreaTargetHandler(CheckAreaTarget)); + } + } + + public override AuraScript GetAuraScript() + { + return new spell_gen_av_drekthar_presence_AuraScript(); + } + } + + [Script] + class spell_gen_bandage : SpellScriptLoader + { + public spell_gen_bandage() : base("spell_gen_bandage") { } + + class spell_gen_bandage_SpellScript : SpellScript + { + public override bool Validate(SpellInfo spellInfo) + { + return ValidateSpellInfo(SpellIds.RecentlyBandaged); + } + + SpellCastResult CheckCast() + { + Unit target = GetExplTargetUnit(); + if (target) + { + if (target.HasAura(SpellIds.RecentlyBandaged)) + return SpellCastResult.TargetAurastate; + } + return SpellCastResult.SpellCastOk; + } + + void HandleScript() + { + Unit target = GetHitUnit(); + if (target) + GetCaster().CastSpell(target, SpellIds.RecentlyBandaged, true); + } + + public override void Register() + { + OnCheckCast.Add(new CheckCastHandler(CheckCast)); + AfterHit.Add(new HitHandler(HandleScript)); + } + } + + public override SpellScript GetSpellScript() + { + return new spell_gen_bandage_SpellScript(); + } + } + + [Script] // Blood Reserve - 64568 + class spell_gen_blood_reserve : SpellScriptLoader + { + public spell_gen_blood_reserve() : base("spell_gen_blood_reserve") { } + + class spell_gen_blood_reserve_AuraScript : AuraScript + { + public override bool Validate(SpellInfo spellInfo) + { + return ValidateSpellInfo(SpellIds.BloodReserveHeal); + } + + bool CheckProc(ProcEventInfo eventInfo) + { + DamageInfo dmgInfo = eventInfo.GetDamageInfo(); + if (dmgInfo != null) + { + Unit caster = eventInfo.GetActionTarget(); + if (caster) + if (caster.HealthBelowPctDamaged(35, dmgInfo.GetDamage())) + return true; + } + + return false; + } + + void HandleProc(AuraEffect aurEff, ProcEventInfo eventInfo) + { + PreventDefaultAction(); + + Unit caster = eventInfo.GetActionTarget(); + caster.CastCustomSpell(SpellIds.BloodReserveHeal, SpellValueMod.BasePoint0, aurEff.GetAmount(), caster, TriggerCastFlags.FullMask, null, aurEff); + caster.RemoveAura(SpellIds.BloodReserveAura); + } + + public override void Register() + { + DoCheckProc.Add(new CheckProcHandler(CheckProc)); + OnEffectProc.Add(new EffectProcHandler(HandleProc, 0, AuraType.ProcTriggerSpell)); + } + } + + public override AuraScript GetAuraScript() + { + return new spell_gen_blood_reserve_AuraScript(); + } + } + + [Script] + class spell_gen_bonked : SpellScriptLoader + { + public spell_gen_bonked() : base("spell_gen_bonked") { } + + class spell_gen_bonked_SpellScript : SpellScript + { + void HandleScript(uint effIndex) + { + Player target = GetHitPlayer(); + if (target) + { + Aura aura = GetHitAura(); + if (!(aura != null && aura.GetStackAmount() == 3)) + return; + + target.CastSpell(target, SpellIds.FormSwordDefeat, true); + target.RemoveAurasDueToSpell(SpellIds.Bonked); + + aura = target.GetAura(SpellIds.Onguard); + if (aura != null) + { + Item item = target.GetItemByGuid(aura.GetCastItemGUID()); + if (item) + target.DestroyItemCount(item.GetEntry(), 1, true); + } + } + } + + public override void Register() + { + OnEffectHitTarget.Add(new EffectHandler(HandleScript, 1, SpellEffectName.ScriptEffect)); + } + } + + public override SpellScript GetSpellScript() + { + return new spell_gen_bonked_SpellScript(); + } + } + + [Script("spell_gen_break_shield")] + [Script("spell_gen_tournament_counterattack")] + class spell_gen_break_shield : SpellScriptLoader + { + public spell_gen_break_shield(string name) : base(name) { } + + class spell_gen_break_shield_SpellScript : SpellScript + { + public override bool Validate(SpellInfo spellInfo) + { + return ValidateSpellInfo(62552, 62719, 64100, 66482); + } + + void HandleScriptEffect(uint effIndex) + { + Unit target = GetHitUnit(); + + switch (effIndex) + { + case 0: // On spells wich trigger the damaging spell (and also the visual) + { + uint spellId; + + switch (GetSpellInfo().Id) + { + case SpellIds.BreakShieldTriggerUnk: + case SpellIds.BreakShieldTriggerCampaingWarhorse: + spellId = SpellIds.BreakShieldDamage10k; + break; + case SpellIds.BreakShieldTriggerFactionMounts: + spellId = SpellIds.BreakShieldDamage2k; + break; + default: + return; + } + Unit rider = GetCaster().GetCharmer(); + if (rider) + rider.CastSpell(target, spellId, false); + else + GetCaster().CastSpell(target, spellId, false); + break; + } + case 1: // On damaging spells, for removing a defend layer + { + var auras = target.GetAppliedAuras(); + foreach (var pair in auras) + { + Aura aura = pair.Value.GetBase(); + if (aura != null) + { + if (aura.GetId() == 62552 || aura.GetId() == 62719 || aura.GetId() == 64100 || aura.GetId() == 66482) + { + aura.ModStackAmount(-1, AuraRemoveMode.EnemySpell); + // Remove dummys from rider (Necessary for updating visual shields) + Unit rider = target.GetCharmer(); + if (rider) + { + Aura defend = rider.GetAura(aura.GetId()); + if (defend != null) + defend.ModStackAmount(-1, AuraRemoveMode.EnemySpell); + } + break; + } + } + } + break; + } + default: + break; + } + } + + public override void Register() + { + OnEffectHitTarget.Add(new EffectHandler(HandleScriptEffect, SpellConst.EffectFirstFound, SpellEffectName.ScriptEffect)); + } + } + + public override SpellScript GetSpellScript() + { + return new spell_gen_break_shield_SpellScript(); + } + } + + [Script] // 46394 Brutallus Burn + class spell_gen_burn_brutallus : SpellScriptLoader + { + public spell_gen_burn_brutallus() : base("spell_gen_burn_brutallus") { } + + class spell_gen_burn_brutallus_AuraScript : AuraScript + { + void HandleEffectPeriodicUpdate(AuraEffect aurEff) + { + if (aurEff.GetTickNumber() % 11 == 0) + aurEff.SetAmount(aurEff.GetAmount() * 2); + } + + public override void Register() + { + OnEffectUpdatePeriodic.Add(new EffectUpdatePeriodicHandler(HandleEffectPeriodicUpdate, 0, AuraType.PeriodicDamage)); + } + } + + public override AuraScript GetAuraScript() + { + return new spell_gen_burn_brutallus_AuraScript(); + } + } + + [Script] // 48750 - Burning Depths Necrolyte Image + class spell_gen_burning_depths_necrolyte_image : SpellScriptLoader + { + public spell_gen_burning_depths_necrolyte_image() : base("spell_gen_burning_depths_necrolyte_image") { } + + class spell_gen_burning_depths_necrolyte_image_AuraScript : AuraScript + { + public override bool Validate(SpellInfo spellInfo) + { + return ValidateSpellInfo((uint)spellInfo.GetEffect(2).CalcValue()); + } + + void HandleApply(AuraEffect aurEff, AuraEffectHandleModes mode) + { + Unit caster = GetCaster(); + if (caster) + caster.CastSpell(GetTarget(), (uint)GetSpellInfo().GetEffect(2).CalcValue()); + } + + void HandleRemove(AuraEffect aurEff, AuraEffectHandleModes mode) + { + GetTarget().RemoveAurasDueToSpell((uint)GetSpellInfo().GetEffect(2).CalcValue(), GetCasterGUID()); + } + + public override void Register() + { + AfterEffectApply.Add(new EffectApplyHandler(HandleApply, 0, AuraType.Transform, AuraEffectHandleModes.Real)); + AfterEffectRemove.Add(new EffectApplyHandler(HandleRemove, 0, AuraType.Transform, AuraEffectHandleModes.Real)); + } + } + + public override AuraScript GetAuraScript() + { + return new spell_gen_burning_depths_necrolyte_image_AuraScript(); + } + } + + [Script] + class spell_gen_cannibalize : SpellScriptLoader + { + public spell_gen_cannibalize() : base("spell_gen_cannibalize") { } + + class spell_gen_cannibalize_SpellScript : SpellScript + { + public override bool Validate(SpellInfo spellInfo) + { + return ValidateSpellInfo(SpellIds.CannibalizeTriggered); + } + + SpellCastResult CheckIfCorpseNear() + { + Unit caster = GetCaster(); + float max_range = GetSpellInfo().GetMaxRange(false); + // search for nearby enemy corpse in range + var check = new AnyDeadUnitSpellTargetInRangeCheck(caster, max_range, GetSpellInfo(), SpellTargetCheckTypes.Enemy); + var searcher = new UnitSearcher(caster, check); + Cell.VisitWorldObjects(caster, searcher, max_range); + if (!searcher.GetTarget()) + Cell.VisitGridObjects(caster, searcher, max_range); + if (!searcher.GetTarget()) + return SpellCastResult.NoEdibleCorpses; + return SpellCastResult.SpellCastOk; + } + + void HandleDummy(uint effIndex) + { + GetCaster().CastSpell(GetCaster(), SpellIds.CannibalizeTriggered, false); + } + + public override void Register() + { + OnEffectHit.Add(new EffectHandler(HandleDummy, 0, SpellEffectName.Dummy)); + OnCheckCast.Add(new CheckCastHandler(CheckIfCorpseNear)); + } + } + + public override SpellScript GetSpellScript() + { + return new spell_gen_cannibalize_SpellScript(); + } + } + + [Script] + class spell_gen_chaos_blast : SpellScriptLoader + { + public spell_gen_chaos_blast() : base("spell_gen_chaos_blast") { } + + class spell_gen_chaos_blast_SpellScript : SpellScript + { + public override bool Validate(SpellInfo spellInfo) + { + return ValidateSpellInfo(SpellIds.ChaosBlast); + } + + void HandleDummy(uint effIndex) + { + int basepoints0 = 100; + Unit caster = GetCaster(); + Unit target = GetHitUnit(); + if (target) + caster.CastCustomSpell(target, SpellIds.ChaosBlast, basepoints0, 0, 0, true); + } + + public override void Register() + { + OnEffectHitTarget.Add(new EffectHandler(HandleDummy, 0, SpellEffectName.Dummy)); + } + } + + public override SpellScript GetSpellScript() + { + return new spell_gen_chaos_blast_SpellScript(); + } + } + + [Script] + class spell_gen_clone : SpellScriptLoader + { + public spell_gen_clone() : base("spell_gen_clone") { } + + class spell_gen_clone_SpellScript : SpellScript + { + void HandleScriptEffect(uint effIndex) + { + PreventHitDefaultEffect(effIndex); + GetHitUnit().CastSpell(GetCaster(), (uint)GetEffectValue(), true); + } + + public override void Register() + { + if (m_scriptSpellId == SpellIds.NightmareFigmentMirrorImage) + { + OnEffectHitTarget.Add(new EffectHandler(HandleScriptEffect, 1, SpellEffectName.Dummy)); + OnEffectHitTarget.Add(new EffectHandler(HandleScriptEffect, 2, SpellEffectName.Dummy)); + } + else + { + OnEffectHitTarget.Add(new EffectHandler(HandleScriptEffect, 1, SpellEffectName.ScriptEffect)); + OnEffectHitTarget.Add(new EffectHandler(HandleScriptEffect, 2, SpellEffectName.ScriptEffect)); + } + } + } + + public override SpellScript GetSpellScript() + { + return new spell_gen_clone_SpellScript(); + } + } + + [Script] + class spell_gen_clone_weapon : SpellScriptLoader + { + public spell_gen_clone_weapon() : base("spell_gen_clone_weapon") { } + + class spell_gen_clone_weapon_SpellScript : SpellScript + { + void HandleScriptEffect(uint effIndex) + { + PreventHitDefaultEffect(effIndex); + GetHitUnit().CastSpell(GetCaster(), (uint)GetEffectValue(), true); + } + + public override void Register() + { + OnEffectHitTarget.Add(new EffectHandler(HandleScriptEffect, 0, SpellEffectName.ScriptEffect)); + } + } + + public override SpellScript GetSpellScript() + { + return new spell_gen_clone_weapon_SpellScript(); + } + } + + [Script] + class spell_gen_clone_weapon_aura : SpellScriptLoader + { + public spell_gen_clone_weapon_aura() : base("spell_gen_clone_weapon_aura") { } + + class spell_gen_clone_weapon_auraScript : AuraScript + { + public override bool Validate(SpellInfo spellInfo) + { + return ValidateSpellInfo(SpellIds.WeaponAura, SpellIds.Weapon2Aura, SpellIds.Weapon3Aura, SpellIds.OffhandAura, SpellIds.Offhand2Aura, SpellIds.RangedAura); + } + + public override bool Load() + { + prevItem = 0; + return true; + } + + void OnApply(AuraEffect aurEff, AuraEffectHandleModes mode) + { + Unit caster = GetCaster(); + Unit target = GetTarget(); + if (!caster) + return; + + switch (GetSpellInfo().Id) + { + case SpellIds.WeaponAura: + case SpellIds.Weapon2Aura: + case SpellIds.Weapon3Aura: + { + prevItem = target.GetVirtualItemId(0); + + Player player = caster.ToPlayer(); + if (player) + { + Item mainItem = player.GetItemByPos(InventorySlots.Bag0, EquipmentSlot.MainHand); + if (mainItem) + target.SetVirtualItem(0, mainItem.GetEntry()); + } + else + target.SetVirtualItem(0, caster.GetVirtualItemId(0)); + break; + } + case SpellIds.OffhandAura: + case SpellIds.Offhand2Aura: + { + prevItem = target.GetVirtualItemId(1); + + Player player = caster.ToPlayer(); + if (player) + { + Item offItem = player.GetItemByPos(InventorySlots.Bag0, EquipmentSlot.OffHand); + if (offItem) + target.SetVirtualItem(1, offItem.GetEntry()); + } + else + target.SetVirtualItem(1, caster.GetVirtualItemId(1)); + break; + } + case SpellIds.RangedAura: + { + prevItem = target.GetVirtualItemId(2); + + Player player = caster.ToPlayer(); + if (player) + { + Item rangedItem = player.GetItemByPos(InventorySlots.Bag0, EquipmentSlot.MainHand); + if (rangedItem) + target.SetVirtualItem(2, rangedItem.GetEntry()); + } + else + target.SetVirtualItem(2, caster.GetVirtualItemId(2)); + break; + } + default: + break; + } + } + + void OnRemove(AuraEffect aurEff, AuraEffectHandleModes mode) + { + Unit target = GetTarget(); + + switch (GetSpellInfo().Id) + { + case SpellIds.WeaponAura: + case SpellIds.Weapon2Aura: + case SpellIds.Weapon3Aura: + target.SetVirtualItem(0, prevItem); + break; + case SpellIds.OffhandAura: + case SpellIds.Offhand2Aura: + target.SetVirtualItem(1, prevItem); + break; + case SpellIds.RangedAura: + target.SetVirtualItem(2, prevItem); + break; + default: + break; + } + } + + public override void Register() + { + OnEffectApply.Add(new EffectApplyHandler(OnApply, 0, AuraType.PeriodicDummy, AuraEffectHandleModes.RealOrReapplyMask)); + OnEffectRemove.Add(new EffectApplyHandler(OnRemove, 0, AuraType.PeriodicDummy, AuraEffectHandleModes.RealOrReapplyMask)); + } + + uint prevItem; + } + + public override AuraScript GetAuraScript() + { + return new spell_gen_clone_weapon_auraScript(); + } + } + + [Script("spell_gen_default_count_pct_from_max_hp", 0)] + [Script("spell_gen_50pct_count_pct_from_max_hp", 50)] + class spell_gen_count_pct_from_max_hp : SpellScriptLoader + { + public spell_gen_count_pct_from_max_hp(string name, int damagePct) : base(name) + { + _damagePct = damagePct; + } + + class spell_gen_count_pct_from_max_hp_SpellScript : SpellScript + { + public spell_gen_count_pct_from_max_hp_SpellScript(int damagePct) + { + _damagePct = damagePct; + } + + void RecalculateDamage() + { + if (_damagePct == 0) + _damagePct = GetHitDamage(); + + SetHitDamage((int)GetHitUnit().CountPctFromMaxHealth(_damagePct)); + } + + public override void Register() + { + OnHit.Add(new HitHandler(RecalculateDamage)); + } + + int _damagePct; + } + + public override SpellScript GetSpellScript() + { + return new spell_gen_count_pct_from_max_hp_SpellScript(_damagePct); + } + + int _damagePct; + } + + [Script] // 63845 - Create Lance + class spell_gen_create_lance : SpellScriptLoader + { + public spell_gen_create_lance() : base("spell_gen_create_lance") { } + + class spell_gen_create_lance_SpellScript : SpellScript + { + public override bool Validate(SpellInfo spellInfo) + { + return ValidateSpellInfo(SpellIds.CreateLanceAlliance, SpellIds.CreateLanceHorde); + } + + void HandleScript(uint effIndex) + { + PreventHitDefaultEffect(effIndex); + + Player target = GetHitPlayer(); + if (target) + { + if (target.GetTeam() == Team.Alliance) + GetCaster().CastSpell(target, SpellIds.CreateLanceAlliance, true); + else + GetCaster().CastSpell(target, SpellIds.CreateLanceHorde, true); + } + } + + public override void Register() + { + OnEffectHitTarget.Add(new EffectHandler(HandleScript, 0, SpellEffectName.ScriptEffect)); + } + } + + public override SpellScript GetSpellScript() + { + return new spell_gen_create_lance_SpellScript(); + } + } + + [Script] + class spell_gen_creature_permanent_feign_death : SpellScriptLoader + { + public spell_gen_creature_permanent_feign_death() : base("spell_gen_creature_permanent_feign_death") { } + + class spell_gen_creature_permanent_feign_death_AuraScript : AuraScript + { + void HandleEffectApply(AuraEffect aurEff, AuraEffectHandleModes mode) + { + Unit target = GetTarget(); + target.SetFlag(ObjectFields.DynamicFlags, UnitDynFlags.Dead); + target.SetFlag(UnitFields.Flags2, UnitFlags2.FeignDeath); + + if (target.IsTypeId(TypeId.Unit)) + target.ToCreature().SetReactState(ReactStates.Passive); + } + + public override void Register() + { + OnEffectApply.Add(new EffectApplyHandler(HandleEffectApply, 0, AuraType.Dummy, AuraEffectHandleModes.Real)); + } + } + + public override AuraScript GetAuraScript() + { + return new spell_gen_creature_permanent_feign_death_AuraScript(); + } + } + + [Script("spell_gen_sunreaver_disguise")] + [Script("spell_gen_silver_covenant_disguise")] + class spell_gen_dalaran_disguise : SpellScriptLoader + { + public spell_gen_dalaran_disguise(string name) : base(name) { } + + class spell_gen_dalaran_disguise_SpellScript : SpellScript + { + public override bool Validate(SpellInfo spellInfo) + { + switch (spellInfo.Id) + { + case SpellIds.SunreaverTrigger: + return ValidateSpellInfo(SpellIds.SunreaverFemale, SpellIds.SunreaverMale); + case SpellIds.SilverCovenantTrigger: + return ValidateSpellInfo(SpellIds.SilverCovenantFemale, SpellIds.SilverCovenantMale); + } + return false; + } + + void HandleScript(uint effIndex) + { + Player player = GetHitPlayer(); + if (player) + { + Gender gender = player.GetGender(); + + uint spellId = GetSpellInfo().Id; + switch (spellId) + { + case SpellIds.SunreaverTrigger: + spellId = gender == Gender.Female ? SpellIds.SunreaverFemale : SpellIds.SunreaverMale; + break; + case SpellIds.SilverCovenantTrigger: + spellId = gender == Gender.Female ? SpellIds.SilverCovenantFemale : SpellIds.SilverCovenantMale; + break; + default: + break; + } + + GetCaster().CastSpell(player, spellId, true); + } + } + + public override void Register() + { + OnEffectHitTarget.Add(new EffectHandler(HandleScript, 0, SpellEffectName.ScriptEffect)); + } + } + + public override SpellScript GetSpellScript() + { + return new spell_gen_dalaran_disguise_SpellScript(); + } + } + + [Script] + class spell_gen_defend : SpellScriptLoader + { + public spell_gen_defend() : base("spell_gen_defend") { } + + class spell_gen_defend_AuraScript : AuraScript + { + public override bool Validate(SpellInfo spellInfo) + { + return ValidateSpellInfo(SpellIds.VisualShield1, SpellIds.VisualShield2, SpellIds.VisualShield3); + } + + void RefreshVisualShields(AuraEffect aurEff, AuraEffectHandleModes mode) + { + if (GetCaster()) + { + Unit target = GetTarget(); + + for (byte i = 0; i < GetSpellInfo().StackAmount; ++i) + target.RemoveAurasDueToSpell(SpellIds.VisualShield1 + i); + + target.CastSpell(target, SpellIds.VisualShield1 + GetAura().GetStackAmount() - 1, true, null, aurEff); + } + else + GetTarget().RemoveAurasDueToSpell(GetId()); + } + + void RemoveVisualShields(AuraEffect aurEff, AuraEffectHandleModes mode) + { + for (byte i = 0; i < GetSpellInfo().StackAmount; ++i) + GetTarget().RemoveAurasDueToSpell(SpellIds.VisualShield1 + i); + } + + void RemoveDummyFromDriver(AuraEffect aurEff, AuraEffectHandleModes mode) + { + Unit caster = GetCaster(); + if (caster) + { + TempSummon vehicle = caster.ToTempSummon(); + if (vehicle) + { + Unit rider = vehicle.GetSummoner(); + if (rider) + rider.RemoveAurasDueToSpell(GetId()); + } + } + } + + public override void Register() + { + /*SpellInfo spell = Global.SpellMgr.GetSpellInfo(m_scriptSpellId); + + // Defend spells cast by NPCs (add visuals) + if (spell.GetEffect(0).ApplyAuraName == AuraType.ModDamagePercentTaken) + { + AfterEffectApply.Add(new EffectApplyHandler(RefreshVisualShields, 0, AuraType.ModDamagePercentTaken, AuraEffectHandleModes.RealOrReapplyMask)); + OnEffectRemove.Add(new EffectApplyHandler(RemoveVisualShields, 0, AuraType.ModDamagePercentTaken, AuraEffectHandleModes.ChangeAmountMask)); + } + + // Remove Defend spell from player when he dismounts + if (spell.GetEffect(2).ApplyAuraName == AuraType.ModDamagePercentTaken) + OnEffectRemove.Add(new EffectApplyHandler(RemoveDummyFromDriver, 2, AuraType.ModDamagePercentTaken, AuraEffectHandleModes.Real)); + + // Defend spells cast by players (add/remove visuals) + if (spell.GetEffect(1).ApplyAuraName == AuraType.Dummy) + { + AfterEffectApply.Add(new EffectApplyHandler(RefreshVisualShields, 1, AuraType.Dummy, AuraEffectHandleModes.RealOrReapplyMask)); + OnEffectRemove.Add(new EffectApplyHandler(RemoveVisualShields, 1, AuraType.Dummy, AuraEffectHandleModes.ChangeAmountMask)); + }*/ + } + } + + public override AuraScript GetAuraScript() + { + return new spell_gen_defend_AuraScript(); + } + } + + [Script] + class spell_gen_despawn_self : SpellScriptLoader + { + public spell_gen_despawn_self() : base("spell_gen_despawn_self") { } + + class spell_gen_despawn_self_SpellScript : SpellScript + { + public override bool Load() + { + return GetCaster().IsTypeId(TypeId.Unit); + } + + void HandleDummy(uint effIndex) + { + if (GetSpellInfo().GetEffect(effIndex).Effect == SpellEffectName.Dummy || GetSpellInfo().GetEffect(effIndex).Effect == SpellEffectName.ScriptEffect) + GetCaster().ToCreature().DespawnOrUnsummon(); + } + + public override void Register() + { + OnEffectHitTarget.Add(new EffectHandler(HandleDummy, SpellConst.EffectAll, SpellEffectName.Any)); + } + } + + public override SpellScript GetSpellScript() + { + return new spell_gen_despawn_self_SpellScript(); + } + } + + [Script] // 70769 Divine Storm! + class spell_gen_divine_storm_cd_reset : SpellScriptLoader + { + public spell_gen_divine_storm_cd_reset() : base("spell_gen_divine_storm_cd_reset") { } + + class spell_gen_divine_storm_cd_reset_SpellScript : SpellScript + { + public override bool Load() + { + return GetCaster().IsTypeId(TypeId.Player); + } + + public override bool Validate(SpellInfo spellInfo) + { + return ValidateSpellInfo(SpellIds.DivineStorm); + } + + void HandleScript(uint effIndex) + { + GetCaster().GetSpellHistory().ResetCooldown(SpellIds.DivineStorm, true); + } + + public override void Register() + { + OnEffectHitTarget.Add(new EffectHandler(HandleScript, 0, SpellEffectName.Dummy)); + } + } + + public override SpellScript GetSpellScript() + { + return new spell_gen_divine_storm_cd_reset_SpellScript(); + } + } + + [Script] + class spell_gen_ds_flush_knockback : SpellScriptLoader + { + public spell_gen_ds_flush_knockback() : base("spell_gen_ds_flush_knockback") { } + + class spell_gen_ds_flush_knockback_SpellScript : SpellScript + { + void HandleScript(uint effIndex) + { + // Here the target is the water spout and determines the position where the player is knocked from + Unit target = GetHitUnit(); + if (target) + { + Player player = GetCaster().ToPlayer(); + if (player) + { + float horizontalSpeed = 20.0f + (40.0f - GetCaster().GetDistance(target)); + float verticalSpeed = 8.0f; + // This method relies on the Dalaran Sewer map disposition and Water Spout position + // What we do is knock the player from a position exactly behind him and at the end of the pipe + player.KnockbackFrom(target.GetPositionX(), player.GetPositionY(), horizontalSpeed, verticalSpeed); + } + } + } + + public override void Register() + { + OnEffectHitTarget.Add(new EffectHandler(HandleScript, 0, SpellEffectName.Dummy)); + } + } + + public override SpellScript GetSpellScript() + { + return new spell_gen_ds_flush_knockback_SpellScript(); + } + } + + [Script] + class spell_gen_dungeon_credit : SpellScriptLoader + { + public spell_gen_dungeon_credit() : base("spell_gen_dungeon_credit") { } + + class spell_gen_dungeon_credit_SpellScript : SpellScript + { + public override bool Load() + { + _handled = false; + return GetCaster().IsTypeId(TypeId.Unit); + } + + void CreditEncounter() + { + // This hook is executed for every target, make sure we only credit instance once + if (_handled) + return; + + _handled = true; + Unit caster = GetCaster(); + InstanceScript instance = caster.GetInstanceScript(); + if (instance != null) + instance.UpdateEncounterStateForSpellCast(GetSpellInfo().Id, caster); + } + + public override void Register() + { + AfterHit.Add(new HitHandler(CreditEncounter)); + } + + bool _handled; + } + + public override SpellScript GetSpellScript() + { + return new spell_gen_dungeon_credit_SpellScript(); + } + } + + [Script] + class spell_gen_elune_candle : SpellScriptLoader + { + public spell_gen_elune_candle() : base("spell_gen_elune_candle") { } + + class spell_gen_elune_candle_SpellScript : SpellScript + { + public override bool Validate(SpellInfo spellInfo) + { + return ValidateSpellInfo(SpellIds.OmenHead, SpellIds.OmenChest, SpellIds.OmenHandR, SpellIds.OmenHandL, SpellIds.Normal); + } + + void HandleScript(uint effIndex) + { + uint spellId = 0; + + if (GetHitUnit().GetEntry() == CreatureIds.Omen) + { + switch (RandomHelper.URand(0, 3)) + { + case 0: + spellId = SpellIds.OmenHead; + break; + case 1: + spellId = SpellIds.OmenChest; + break; + case 2: + spellId = SpellIds.OmenHandR; + break; + case 3: + spellId = SpellIds.OmenHandL; + break; + } + } + else + spellId = SpellIds.Normal; + + GetCaster().CastSpell(GetHitUnit(), spellId, true, null); + } + + public override void Register() + { + OnEffectHitTarget.Add(new EffectHandler(HandleScript, 0, SpellEffectName.Dummy)); + } + } + + public override SpellScript GetSpellScript() + { + return new spell_gen_elune_candle_SpellScript(); + } + } + + [Script] // 131474 - Fishing + class spell_gen_fishing : SpellScriptLoader + { + public spell_gen_fishing() : base("spell_gen_fishing") { } + + class spell_gen_fishing_SpellScript : SpellScript + { + public override bool Validate(SpellInfo spellInfo) + { + return ValidateSpellInfo(SpellIds.FishingNoFishingPole, SpellIds.FishingWithPole); + } + + public override bool Load() + { + return GetCaster().IsTypeId(TypeId.Player); + } + + void HandleDummy(uint effIndex) + { + PreventHitDefaultEffect(effIndex); + uint spellId; + Item mainHand = GetCaster().ToPlayer().GetItemByPos(InventorySlots.Bag0, EquipmentSlot.MainHand); + if (!mainHand || mainHand.GetTemplate().GetClass() != ItemClass.Weapon || (ItemSubClassWeapon)mainHand.GetTemplate().GetSubClass() != ItemSubClassWeapon.FishingPole) + spellId = SpellIds.FishingNoFishingPole; + else + spellId = SpellIds.FishingWithPole; + + GetCaster().CastSpell(GetCaster(), spellId, false); + } + + public override void Register() + { + OnEffectHitTarget.Add(new EffectHandler(HandleDummy, 0, SpellEffectName.Dummy)); + } + } + + public override SpellScript GetSpellScript() + { + return new spell_gen_fishing_SpellScript(); + } + } + + [Script] + class spell_gen_gadgetzan_transporter_backfire : SpellScriptLoader + { + public spell_gen_gadgetzan_transporter_backfire() : base("spell_gen_gadgetzan_transporter_backfire") { } + + class spell_gen_gadgetzan_transporter_backfire_SpellScript : SpellScript + { + public override bool Validate(SpellInfo spellInfo) + { + return ValidateSpellInfo(SpellIds.TransporterMalfunctionPolymorph, SpellIds.TransporterEviltwin, SpellIds.TransporterMalfunctionMiss); + } + + void HandleDummy(uint effIndex) + { + Unit caster = GetCaster(); + int r = RandomHelper.IRand(0, 119); + if (r < 20) // Transporter Malfunction - 1/6 polymorph + caster.CastSpell(caster, SpellIds.TransporterMalfunctionPolymorph, true); + else if (r < 100) // Evil Twin - 4/6 evil twin + caster.CastSpell(caster, SpellIds.TransporterEviltwin, true); + else // Transporter Malfunction - 1/6 miss the target + caster.CastSpell(caster, SpellIds.TransporterMalfunctionMiss, true); + } + + public override void Register() + { + OnEffectHitTarget.Add(new EffectHandler(HandleDummy, 0, SpellEffectName.Dummy)); + } + } + + public override SpellScript GetSpellScript() + { + return new spell_gen_gadgetzan_transporter_backfire_SpellScript(); + } + } + + [Script] + class spell_gen_gift_of_naaru : SpellScriptLoader + { + public spell_gen_gift_of_naaru() : base("spell_gen_gift_of_naaru") { } + + class spell_gen_gift_of_naaru_AuraScript : AuraScript + { + void CalculateAmount(AuraEffect aurEff, ref int amount, ref bool canBeRecalculated) + { + if (!GetCaster()) + return; + + float heal = 0.0f; + switch (GetSpellInfo().SpellFamilyName) + { + case SpellFamilyNames.Mage: + case SpellFamilyNames.Warlock: + case SpellFamilyNames.Priest: + heal = 1.885f * (GetCaster().SpellBaseDamageBonusDone(GetSpellInfo().GetSchoolMask())); + break; + case SpellFamilyNames.Paladin: + case SpellFamilyNames.Shaman: + heal = Math.Max(1.885f * (GetCaster().SpellBaseDamageBonusDone(GetSpellInfo().GetSchoolMask())), 1.1f * (GetCaster().GetTotalAttackPowerValue(WeaponAttackType.BaseAttack))); + break; + case SpellFamilyNames.Warrior: + case SpellFamilyNames.Hunter: + case SpellFamilyNames.Deathknight: + heal = 1.1f * (Math.Max(GetCaster().GetTotalAttackPowerValue(WeaponAttackType.BaseAttack), GetCaster().GetTotalAttackPowerValue(WeaponAttackType.RangedAttack))); + break; + case SpellFamilyNames.Generic: + default: + break; + } + + int healTick = (int)Math.Floor(heal / aurEff.GetTotalTicks()); + amount += Math.Max(healTick, 0); + } + + public override void Register() + { + DoEffectCalcAmount.Add(new EffectCalcAmountHandler(CalculateAmount, 0, AuraType.PeriodicHeal)); + } + } + + public override AuraScript GetAuraScript() + { + return new spell_gen_gift_of_naaru_AuraScript(); + } + } + + [Script] + class spell_gen_gnomish_transporter : SpellScriptLoader + { + public spell_gen_gnomish_transporter() : base("spell_gen_gnomish_transporter") { } + + class spell_gen_gnomish_transporter_SpellScript : SpellScript + { + public override bool Validate(SpellInfo spellInfo) + { + return ValidateSpellInfo(SpellIds.TransporterSuccess, SpellIds.TransporterFailure); + } + + void HandleDummy(uint effIndex) + { + GetCaster().CastSpell(GetCaster(), RandomHelper.randChance(50) ? SpellIds.TransporterSuccess : SpellIds.TransporterFailure, true); + } + + public override void Register() + { + OnEffectHitTarget.Add(new EffectHandler(HandleDummy, 0, SpellEffectName.Dummy)); + } + } + + public override SpellScript GetSpellScript() + { + return new spell_gen_gnomish_transporter_SpellScript(); + } + } + + [Script] + class spell_gen_interrupt : SpellScriptLoader + { + public spell_gen_interrupt() : base("spell_gen_interrupt") { } + + class spell_gen_interrupt_AuraScript : AuraScript + { + public override bool Validate(SpellInfo spellInfo) + { + return ValidateSpellInfo(SpellIds.GenThrowInterrupt); + } + + void HandleProc(AuraEffect aurEff, ProcEventInfo eventInfo) + { + PreventDefaultAction(); + GetTarget().CastSpell(eventInfo.GetProcTarget(), SpellIds.GenThrowInterrupt, true, null, aurEff); + } + + public override void Register() + { + OnEffectProc.Add(new EffectProcHandler(HandleProc, 0, AuraType.Dummy)); + } + } + + public override AuraScript GetAuraScript() + { + return new spell_gen_interrupt_AuraScript(); + } + } + + [Script("spell_pal_blessing_of_kings")] + [Script("spell_pal_blessing_of_might")] + [Script("spell_dru_mark_of_the_wild")] + [Script("spell_pri_power_word_fortitude")] + [Script("spell_pri_shadow_protection")] + [Script("spell_mage_arcane_brilliance")] + [Script("spell_mage_dalaran_brilliance")] + class spell_gen_increase_stats_buff : SpellScriptLoader + { + public spell_gen_increase_stats_buff(string scriptName) : base(scriptName) { } + + class spell_gen_increase_stats_buff_SpellScript : SpellScript + { + void HandleDummy(uint effIndex) + { + if (GetHitUnit().IsInRaidWith(GetCaster())) + GetCaster().CastSpell(GetCaster(), (uint)GetEffectValue() + 1, true); // raid buff + else + GetCaster().CastSpell(GetHitUnit(), (uint)GetEffectValue(), true); // single-target buff + } + + public override void Register() + { + OnEffectHitTarget.Add(new EffectHandler(HandleDummy, 0, SpellEffectName.Dummy)); + } + } + + public override SpellScript GetSpellScript() + { + return new spell_gen_increase_stats_buff_SpellScript(); + } + } + + [Script("spell_hexlord_lifebloom", SpellIds.HexlordMalacrass)] + [Script("spell_tur_ragepaw_lifebloom", SpellIds.TurragePaw)] + [Script("spell_cenarion_scout_lifebloom", SpellIds.CenarionScout)] + [Script("spell_twisted_visage_lifebloom", SpellIds.TwistedVisage)] + [Script("spell_faction_champion_dru_lifebloom", SpellIds.FactionChampionsDru)] + class spell_gen_lifebloom : SpellScriptLoader + { + public spell_gen_lifebloom(string name, uint spellId) : base(name) + { + _spellId = spellId; + } + + class spell_gen_lifebloom_AuraScript : AuraScript + { + public spell_gen_lifebloom_AuraScript(uint spellId) + { + _spellId = spellId; + } + + public override bool Validate(SpellInfo spellInfo) + { + return ValidateSpellInfo(_spellId); + } + + void AfterRemove(AuraEffect aurEff, AuraEffectHandleModes mode) + { + // Final heal only on duration end + if (GetTargetApplication().GetRemoveMode() != AuraRemoveMode.Expire && GetTargetApplication().GetRemoveMode() != AuraRemoveMode.EnemySpell) + return; + + // final heal + GetTarget().CastSpell(GetTarget(), _spellId, true, null, aurEff, GetCasterGUID()); + } + + public override void Register() + { + AfterEffectRemove.Add(new EffectApplyHandler(AfterRemove, 0, AuraType.PeriodicHeal, AuraEffectHandleModes.Real)); + } + + uint _spellId; + } + + public override AuraScript GetAuraScript() + { + return new spell_gen_lifebloom_AuraScript(_spellId); + } + + uint _spellId; + } + + [Script] + class spell_gen_mounted_charge : SpellScriptLoader + { + public spell_gen_mounted_charge() : base("spell_gen_mounted_charge") { } + + class spell_gen_mounted_charge_SpellScript : SpellScript + { + public override bool Validate(SpellInfo spellInfo) + { + return ValidateSpellInfo(62552, 62719, 64100, 66482); + } + + void HandleScriptEffect(uint effIndex) + { + Unit target = GetHitUnit(); + + switch (effIndex) + { + case 0: // On spells wich trigger the damaging spell (and also the visual) + { + uint spellId; + + switch (GetSpellInfo().Id) + { + case SpellIds.TriggerTrialChampion: + spellId = SpellIds.ChargingEffect20k1; + break; + case SpellIds.TriggerFactionMounts: + spellId = SpellIds.ChargingEffect8k5; + break; + default: + return; + } + + // If target isn't a training dummy there's a chance of failing the charge + if (!target.IsCharmedOwnedByPlayerOrPlayer() && RandomHelper.randChance(12.5f)) + spellId = SpellIds.MissEffect; + + Unit vehicle = GetCaster().GetVehicleBase(); + if (vehicle) + vehicle.CastSpell(target, spellId, false); + else + GetCaster().CastSpell(target, spellId, false); + break; + } + case 1: // On damaging spells, for removing a defend layer + case 2: + { + var auras = target.GetAppliedAuras(); + foreach (var pair in auras) + { + Aura aura = pair.Value.GetBase(); + if (aura != null) + { + if (aura.GetId() == 62552 || aura.GetId() == 62719 || aura.GetId() == 64100 || aura.GetId() == 66482) + { + aura.ModStackAmount(-1, AuraRemoveMode.EnemySpell); + // Remove dummys from rider (Necessary for updating visual shields) + Unit rider = target.GetCharmer(); + if (rider) + { + Aura defend = rider.GetAura(aura.GetId()); + if (defend != null) + defend.ModStackAmount(-1, AuraRemoveMode.EnemySpell); + } + break; + } + } + } + break; + } + } + } + + void HandleChargeEffect(uint effIndex) + { + uint spellId; + + switch (GetSpellInfo().Id) + { + case SpellIds.ChargingEffect8k5: + spellId = SpellIds.Damage8k5; + break; + case SpellIds.ChargingEffect20k1: + case SpellIds.ChargingEffect20k2: + spellId = SpellIds.Damage20k; + break; + case SpellIds.ChargingEffect45k1: + case SpellIds.ChargingEffect45k2: + spellId = SpellIds.Damage45k; + break; + default: + return; + } + Unit rider = GetCaster().GetCharmer(); + if (rider) + rider.CastSpell(GetHitUnit(), spellId, false); + else + GetCaster().CastSpell(GetHitUnit(), spellId, false); + } + + public override void Register() + { + SpellInfo spell = Global.SpellMgr.GetSpellInfo(m_scriptSpellId); + + if (spell.HasEffect(SpellEffectName.ScriptEffect)) + OnEffectHitTarget.Add(new EffectHandler(HandleScriptEffect, SpellConst.EffectFirstFound, SpellEffectName.ScriptEffect)); + + if (spell.GetEffect(0).Effect == SpellEffectName.Charge) + OnEffectHitTarget.Add(new EffectHandler(HandleChargeEffect, 0, SpellEffectName.Charge)); + } + } + + public override SpellScript GetSpellScript() + { + return new spell_gen_mounted_charge_SpellScript(); + } + } + + [Script] // 28702 - Netherbloom + class spell_gen_netherbloom : SpellScriptLoader + { + public spell_gen_netherbloom() : base("spell_gen_netherbloom") { } + + class spell_gen_netherbloom_SpellScript : SpellScript + { + public override bool Validate(SpellInfo spellInfo) + { + for (byte i = 0; i < 5; ++i) + if (!ValidateSpellInfo(SpellIds.NetherBloomPollen1 + i)) + return false; + + return true; + } + + void HandleScript(uint effIndex) + { + PreventHitDefaultEffect(effIndex); + Unit target = GetHitUnit(); + if (target) + { + // 25% chance of casting a random buff + if (RandomHelper.randChance(75)) + return; + + // triggered spells are 28703 to 28707 + // Note: some sources say, that there was the possibility of + // receiving a debuff. However, this seems to be removed by a patch. + + // don't overwrite an existing aura + for (byte i = 0; i < 5; ++i) + if (target.HasAura(SpellIds.NetherBloomPollen1 + i)) + return; + + target.CastSpell(target, SpellIds.NetherBloomPollen1 + RandomHelper.URand(0, 4), true); + } + } + + public override void Register() + { + OnEffectHitTarget.Add(new EffectHandler(HandleScript, 0, SpellEffectName.ScriptEffect)); + } + } + + public override SpellScript GetSpellScript() + { + return new spell_gen_netherbloom_SpellScript(); + } + } + + [Script] // 28720 - Nightmare Vine + class spell_gen_nightmare_vine : SpellScriptLoader + { + public spell_gen_nightmare_vine() : base("spell_gen_nightmare_vine") { } + + class spell_gen_nightmare_vine_SpellScript : SpellScript + { + public override bool Validate(SpellInfo spellInfo) + { + return ValidateSpellInfo(SpellIds.NightmarePollen); + } + + void HandleScript(uint effIndex) + { + PreventHitDefaultEffect(effIndex); + Unit target = GetHitUnit(); + if (target) + { + // 25% chance of casting Nightmare Pollen + if (RandomHelper.randChance(25)) + target.CastSpell(target, SpellIds.NightmarePollen, true); + } + } + + public override void Register() + { + OnEffectHitTarget.Add(new EffectHandler(HandleScript, 0, SpellEffectName.ScriptEffect)); + } + } + + public override SpellScript GetSpellScript() + { + return new spell_gen_nightmare_vine_SpellScript(); + } + } + + [Script] // 27539 - Obsidian Armor + class spell_gen_obsidian_armor : SpellScriptLoader + { + public spell_gen_obsidian_armor() : base("spell_gen_obsidian_armor") { } + + class spell_gen_obsidian_armor_AuraScript : AuraScript + { + public override bool Validate(SpellInfo spellInfo) + { + return ValidateSpellInfo(SpellIds.Holy, SpellIds.Fire, SpellIds.Nature, SpellIds.Frost, SpellIds.Shadow, SpellIds.Arcane); + } + + bool CheckProc(ProcEventInfo eventInfo) + { + if (eventInfo.GetDamageInfo().GetSpellInfo() != null) // eventInfo.GetSpellInfo() + return false; + + if (SharedConst.GetFirstSchoolInMask(eventInfo.GetSchoolMask()) == SpellSchools.Normal) + return false; + + return true; + } + + void onProc(AuraEffect aurEff, ProcEventInfo eventInfo) + { + PreventDefaultAction(); + + uint spellId = 0; + switch (SharedConst.GetFirstSchoolInMask(eventInfo.GetSchoolMask())) + { + case SpellSchools.Holy: + spellId = SpellIds.Holy; + break; + case SpellSchools.Fire: + spellId = SpellIds.Fire; + break; + case SpellSchools.Nature: + spellId = SpellIds.Nature; + break; + case SpellSchools.Frost: + spellId = SpellIds.Frost; + break; + case SpellSchools.Shadow: + spellId = SpellIds.Shadow; + break; + case SpellSchools.Arcane: + spellId = SpellIds.Arcane; + break; + default: + return; + } + GetTarget().CastSpell(GetTarget(), spellId, true, null, aurEff); + } + + public override void Register() + { + DoCheckProc.Add(new CheckProcHandler(CheckProc)); + OnEffectProc.Add(new EffectProcHandler(onProc, 0, AuraType.Dummy)); + } + } + + public override AuraScript GetAuraScript() + { + return new spell_gen_obsidian_armor_AuraScript(); + } + } + + [Script] + class spell_gen_on_tournament_mount : SpellScriptLoader + { + public spell_gen_on_tournament_mount() : base("spell_gen_on_tournament_mount") { } + + class spell_gen_on_tournament_mount_AuraScript : AuraScript + { + uint _pennantSpellId; + + public override bool Load() + { + _pennantSpellId = 0; + return GetCaster() && GetCaster().IsTypeId(TypeId.Player); + } + + void HandleApplyEffect(AuraEffect aurEff, AuraEffectHandleModes mode) + { + Unit caster = GetCaster(); + if (caster) + { + Unit vehicle = caster.GetVehicleBase(); + if (vehicle) + { + _pennantSpellId = GetPennatSpellId(caster.ToPlayer(), vehicle); + caster.CastSpell(caster, _pennantSpellId, true); + } + } + } + + void HandleRemoveEffect(AuraEffect aurEff, AuraEffectHandleModes mode) + { + Unit caster = GetCaster(); + if (caster) + caster.RemoveAurasDueToSpell(_pennantSpellId); + } + + uint GetPennatSpellId(Player player, Unit mount) + { + switch (mount.GetEntry()) + { + case CreatureIds.ArgentSteedAspirant: + case CreatureIds.StormwindSteed: + { + if (player.HasAchieved(AchievementIds.ChampionStormwind)) + return SpellIds.StormwindChampion; + else if (player.GetQuestRewardStatus(QuestIds.ValiantOfStormwind) || player.GetQuestRewardStatus(QuestIds.A_ValiantOfStormwind)) + return SpellIds.StormwindValiant; + else + return SpellIds.StormwindAspirant; + } + case CreatureIds.GnomereganMechanostrider: + { + if (player.HasAchieved(AchievementIds.ChampionGnomeregan)) + return SpellIds.GnomereganChampion; + else if (player.GetQuestRewardStatus(QuestIds.ValiantOfGnomeregan) || player.GetQuestRewardStatus(QuestIds.A_ValiantOfGnomeregan)) + return SpellIds.GnomereganValiant; + else + return SpellIds.GnomereganAspirant; + } + case CreatureIds.DarkSpearRaptor: + { + if (player.HasAchieved(AchievementIds.ChampionSenJin)) + return SpellIds.SenjinChampion; + else if (player.GetQuestRewardStatus(QuestIds.ValiantOfSenJin) || player.GetQuestRewardStatus(QuestIds.A_ValiantOfSenJin)) + return SpellIds.SenjinValiant; + else + return SpellIds.SenjinAspirant; + } + case CreatureIds.ArgentHawkstriderAspirant: + case CreatureIds.SilvermoonHawkstrider: + { + if (player.HasAchieved(AchievementIds.ChampionSilvermoon)) + return SpellIds.SilvermoonChampion; + else if (player.GetQuestRewardStatus(QuestIds.ValiantOfSilvermoon) || player.GetQuestRewardStatus(QuestIds.A_ValiantOfSilvermoon)) + return SpellIds.SilvermoonValiant; + else + return SpellIds.SilvermoonAspirant; + } + case CreatureIds.DarnassianNightsaber: + { + if (player.HasAchieved(AchievementIds.ChampionDarnassus)) + return SpellIds.DarnassusChampion; + else if (player.GetQuestRewardStatus(QuestIds.ValiantOfDarnassus) || player.GetQuestRewardStatus(QuestIds.A_ValiantOfDarnassus)) + return SpellIds.DarnassusValiant; + else + return SpellIds.DarnassusAspirant; + } + case CreatureIds.ExodarElekk: + { + if (player.HasAchieved(AchievementIds.ChampionTheExodar)) + return SpellIds.ExodarChampion; + else if (player.GetQuestRewardStatus(QuestIds.ValiantOfTheExodar) || player.GetQuestRewardStatus(QuestIds.A_ValiantOfTheExodar)) + return SpellIds.ExodarValiant; + else + return SpellIds.ExodarAspirant; + } + case CreatureIds.IronforgeRam: + { + if (player.HasAchieved(AchievementIds.ChampionIronforge)) + return SpellIds.IronforgeChampion; + else if (player.GetQuestRewardStatus(QuestIds.ValiantOfIronforge) || player.GetQuestRewardStatus(QuestIds.A_ValiantOfIronforge)) + return SpellIds.IronforgeValiant; + else + return SpellIds.IronforgeAspirant; + } + case CreatureIds.ForsakenWarhorse: + { + if (player.HasAchieved(AchievementIds.ChampionUndercity)) + return SpellIds.UndercityChampion; + else if (player.GetQuestRewardStatus(QuestIds.ValiantOfUndercity) || player.GetQuestRewardStatus(QuestIds.A_ValiantOfUndercity)) + return SpellIds.UndercityValiant; + else + return SpellIds.UndercityAspirant; + } + case CreatureIds.OrgrimmarWolf: + { + if (player.HasAchieved(AchievementIds.ChampionOrgrimmar)) + return SpellIds.OrgrimmarChampion; + else if (player.GetQuestRewardStatus(QuestIds.ValiantOfOrgrimmar) || player.GetQuestRewardStatus(QuestIds.A_ValiantOfOrgrimmar)) + return SpellIds.OrgrimmarValiant; + else + return SpellIds.OrgrimmarAspirant; + } + case CreatureIds.ThunderBluffKodo: + { + if (player.HasAchieved(AchievementIds.ChampionThunderBluff)) + return SpellIds.ThunderbluffChampion; + else if (player.GetQuestRewardStatus(QuestIds.ValiantOfThunderBluff) || player.GetQuestRewardStatus(QuestIds.A_ValiantOfThunderBluff)) + return SpellIds.ThunderbluffValiant; + else + return SpellIds.ThunderbluffAspirant; + } + case CreatureIds.ArgentWarhorse: + { + if (player.HasAchieved(AchievementIds.ChampionAlliance) || player.HasAchieved(AchievementIds.ChampionHorde)) + return player.GetClass() == Class.Deathknight ? SpellIds.EbonbladeChampion : SpellIds.ArgentcrusadeChampion; + else if (player.HasAchieved(AchievementIds.ArgentValor)) + return player.GetClass() == Class.Deathknight ? SpellIds.EbonbladeValiant : SpellIds.ArgentcrusadeValiant; + else + return player.GetClass() == Class.Deathknight ? SpellIds.EbonbladeAspirant : SpellIds.ArgentcrusadeAspirant; + } + default: + return 0; + } + } + + public override void Register() + { + AfterEffectApply.Add(new EffectApplyHandler(HandleApplyEffect, 0, AuraType.Dummy, AuraEffectHandleModes.RealOrReapplyMask)); + OnEffectRemove.Add(new EffectApplyHandler(HandleRemoveEffect, 0, AuraType.Dummy, AuraEffectHandleModes.RealOrReapplyMask)); + } + } + + public override AuraScript GetAuraScript() + { + return new spell_gen_on_tournament_mount_AuraScript(); + } + } + + [Script] + class spell_gen_oracle_wolvar_reputation : SpellScriptLoader + { + public spell_gen_oracle_wolvar_reputation() : base("spell_gen_oracle_wolvar_reputation") { } + + class spell_gen_oracle_wolvar_reputation_SpellScript : SpellScript + { + public override bool Load() + { + return GetCaster().IsTypeId(TypeId.Player); + } + + void HandleDummy(uint effIndex) + { + Player player = GetCaster().ToPlayer(); + uint factionId = (uint)GetSpellInfo().GetEffect(effIndex).CalcValue(); + int repChange = GetSpellInfo().GetEffect(1).CalcValue(); + + FactionRecord factionEntry = CliDB.FactionStorage.LookupByKey(factionId); + + if (factionEntry == null) + return; + + // Set rep to baserep + basepoints (expecting spillover for oposite faction . become hated) + // Not when player already has equal or higher rep with this faction + if (player.GetReputationMgr().GetBaseReputation(factionEntry) < repChange) + player.GetReputationMgr().SetReputation(factionEntry, repChange); + + // EFFECT_INDEX_2 most likely update at war state, we already handle this in SetReputation + } + + public override void Register() + { + OnEffectHit.Add(new EffectHandler(HandleDummy, 0, SpellEffectName.Dummy)); + } + } + + public override SpellScript GetSpellScript() + { + return new spell_gen_oracle_wolvar_reputation_SpellScript(); + } + } + + [Script] + class spell_gen_orc_disguise : SpellScriptLoader + { + public spell_gen_orc_disguise() : base("spell_gen_orc_disguise") { } + + class spell_gen_orc_disguise_SpellScript : SpellScript + { + public override bool Validate(SpellInfo spellInfo) + { + return ValidateSpellInfo(SpellIds.OrcDisguiseTrigger, SpellIds.OrcDisguiseMale, SpellIds.OrcDisguiseFemale); + } + + void HandleScript(uint effIndex) + { + Unit caster = GetCaster(); + Player target = GetHitPlayer(); + if (target) + { + Gender gender = target.GetGender(); + if (gender == Gender.Male) + caster.CastSpell(target, SpellIds.OrcDisguiseMale, true); + else + caster.CastSpell(target, SpellIds.OrcDisguiseFemale, true); + } + } + + public override void Register() + { + OnEffectHitTarget.Add(new EffectHandler(HandleScript, 0, SpellEffectName.ScriptEffect)); + } + } + + public override SpellScript GetSpellScript() + { + return new spell_gen_orc_disguise_SpellScript(); + } + } + + [Script("spell_item_soul_harvesters_charm")] + [Script("spell_item_commendation_of_kaelthas")] + [Script("spell_item_corpse_tongue_coin")] + [Script("spell_item_corpse_tongue_coin_heroic")] + [Script("spell_item_petrified_twilight_scale")] + [Script("spell_item_petrified_twilight_scale_heroic")] + class spell_gen_proc_below_pct_damaged : SpellScriptLoader + { + public spell_gen_proc_below_pct_damaged(string name) : base(name) { } + + class spell_gen_proc_below_pct_damaged_AuraScript : AuraScript + { + bool CheckProc(ProcEventInfo eventInfo) + { + DamageInfo damageInfo = eventInfo.GetDamageInfo(); + if (damageInfo == null || damageInfo.GetDamage() == 0) + return false; + + int pct = GetSpellInfo().GetEffect(0).CalcValue(); + + if (eventInfo.GetActionTarget().HealthBelowPctDamaged(pct, damageInfo.GetDamage())) + return true; + + return false; + } + + public override void Register() + { + DoCheckProc.Add(new CheckProcHandler(CheckProc)); + } + } + + public override AuraScript GetAuraScript() + { + return new spell_gen_proc_below_pct_damaged_AuraScript(); + } + } + + [Script] // 45472 Parachute + class spell_gen_parachute : SpellScriptLoader + { + public spell_gen_parachute() : base("spell_gen_parachute") { } + + class spell_gen_parachute_AuraScript : AuraScript + { + public override bool Validate(SpellInfo spellInfo) + { + return ValidateSpellInfo(SpellIds.Parachute, SpellIds.ParachuteBuff); + } + + void HandleEffectPeriodic(AuraEffect aurEff) + { + Player target = GetTarget().ToPlayer(); + if (target) + { + if (target.IsFalling()) + { + target.RemoveAurasDueToSpell(SpellIds.Parachute); + target.CastSpell(target, SpellIds.ParachuteBuff, true); + } + } + } + + public override void Register() + { + OnEffectPeriodic.Add(new EffectPeriodicHandler(HandleEffectPeriodic, 0, AuraType.PeriodicDummy)); + } + } + + public override AuraScript GetAuraScript() + { + return new spell_gen_parachute_AuraScript(); + } + } + + [Script] + class spell_gen_pet_summoned : SpellScriptLoader + { + public spell_gen_pet_summoned() : base("spell_gen_pet_summoned") { } + + class spell_gen_pet_summoned_SpellScript : SpellScript + { + public override bool Load() + { + return GetCaster().IsTypeId(TypeId.Player); + } + + void HandleScript(uint effIndex) + { + Player player = GetCaster().ToPlayer(); + if (player.GetLastPetNumber() != 0) + { + PetType newPetType = (player.GetClass() == Class.Hunter) ? PetType.Hunter : PetType.Summon; + Pet newPet = new Pet(player, newPetType); + if (newPet.LoadPetFromDB(player, 0, player.GetLastPetNumber(), true)) + { + // revive the pet if it is dead + if (newPet.getDeathState() == DeathState.Dead) + newPet.setDeathState(DeathState.Alive); + + newPet.SetFullHealth(); + newPet.SetPower(newPet.getPowerType(), newPet.GetMaxPower(newPet.getPowerType())); + + switch (newPet.GetEntry()) + { + case CreatureIds.Doomguard: + case CreatureIds.Infernal: + newPet.SetEntry(CreatureIds.Imp); + break; + default: + break; + } + } + } + } + + public override void Register() + { + OnEffectHitTarget.Add(new EffectHandler(HandleScript, 0, SpellEffectName.ScriptEffect)); + } + } + + public override SpellScript GetSpellScript() + { + return new spell_gen_pet_summoned_SpellScript(); + } + } + + [Script] + class spell_gen_profession_research : SpellScriptLoader + { + public spell_gen_profession_research() : base("spell_gen_profession_research") { } + + class spell_gen_profession_research_SpellScript : SpellScript + { + public override bool Load() + { + return GetCaster().IsTypeId(TypeId.Player); + } + + SpellCastResult CheckRequirement() + { + if (SkillDiscovery.HasDiscoveredAllSpells(GetSpellInfo().Id, GetCaster().ToPlayer())) + { + SetCustomCastResultMessage(SpellCustomErrors.NothingToDiscover); + return SpellCastResult.CustomError; + } + + return SpellCastResult.SpellCastOk; + } + + void HandleScript(uint effIndex) + { + Player caster = GetCaster().ToPlayer(); + uint spellId = GetSpellInfo().Id; + + // learn random explicit discovery recipe (if any) + uint discoveredSpellId = SkillDiscovery.GetExplicitDiscoverySpell(spellId, caster); + if (discoveredSpellId != 0) + caster.LearnSpell(discoveredSpellId, false); + + caster.UpdateCraftSkill(spellId); + } + + public override void Register() + { + OnCheckCast.Add(new CheckCastHandler(CheckRequirement)); + OnEffectHitTarget.Add(new EffectHandler(HandleScript, 1, SpellEffectName.ScriptEffect)); + } + } + + public override SpellScript GetSpellScript() + { + return new spell_gen_profession_research_SpellScript(); + } + } + + [Script] + class spell_gen_pvp_trinket : SpellScriptLoader + { + public spell_gen_pvp_trinket() : base("spell_gen_pvp_trinket") { } + + class spell_gen_pvp_trinket_SpellScript : SpellScript + { + void TriggerAnimation() + { + Player caster = GetCaster().ToPlayer(); + + switch (caster.GetTeam()) + { + case Team.Alliance: + caster.CastSpell(caster, SpellIds.PvpTrinketAlliance, TriggerCastFlags.FullMask); + break; + case Team.Horde: + caster.CastSpell(caster, SpellIds.PvpTrinketHorde, TriggerCastFlags.FullMask); + break; + } + } + + public override void Register() + { + AfterCast.Add(new CastHandler(TriggerAnimation)); + } + } + + public override SpellScript GetSpellScript() + { + return new spell_gen_pvp_trinket_SpellScript(); + } + } + + [Script] + class spell_gen_remove_flight_auras : SpellScriptLoader + { + public spell_gen_remove_flight_auras() : base("spell_gen_remove_flight_auras") { } + + class spell_gen_remove_flight_auras_SpellScript : SpellScript + { + void HandleScript(uint effIndex) + { + Unit target = GetHitUnit(); + if (target) + { + target.RemoveAurasByType(AuraType.Fly); + target.RemoveAurasByType(AuraType.ModIncreaseMountedFlightSpeed); + } + } + + public override void Register() + { + OnEffectHitTarget.Add(new EffectHandler(HandleScript, 1, SpellEffectName.ScriptEffect)); + } + } + + public override SpellScript GetSpellScript() + { + return new spell_gen_remove_flight_auras_SpellScript(); + } + } + + [Script] + class spell_gen_replenishment : SpellScriptLoader + { + public spell_gen_replenishment() : base("spell_gen_replenishment") { } + + class spell_gen_replenishment_SpellScript : SpellScript + { + void RemoveInvalidTargets(List targets) + { + // In arenas Replenishment may only affect the caster + Player caster = GetCaster().ToPlayer(); + if (caster) + { + if (caster.InArena()) + { + targets.Clear(); + targets.Add(caster); + return; + } + } + + targets.RemoveAll(obj => + { + var target = obj.ToUnit(); + if (target) + return target.getPowerType() != PowerType.Mana; + + return true; + }); + + byte maxTargets = 10; + + if (targets.Count > maxTargets) + { + targets.Sort(new PowerPctOrderPred(PowerType.Mana)); + targets.Resize(maxTargets); + } + } + + public override void Register() + { + OnObjectAreaTargetSelect.Add(new ObjectAreaTargetSelectHandler(RemoveInvalidTargets, 255, Targets.UnitCasterAreaRaid)); + } + } + + public override SpellScript GetSpellScript() + { + return new spell_gen_replenishment_SpellScript(); + } + + class spell_gen_replenishment_AuraScript : AuraScript + { + public override bool Load() + { + return GetUnitOwner().GetPower(PowerType.Mana) != 0; + } + + void CalculateAmount(AuraEffect aurEff, ref int amount, ref bool canBeRecalculated) + { + switch (GetSpellInfo().Id) + { + case SpellIds.Replenishment: + amount = (int)(GetUnitOwner().GetMaxPower(PowerType.Mana) * 0.002f); + break; + case SpellIds.InfiniteReplenishment: + amount = (int)(GetUnitOwner().GetMaxPower(PowerType.Mana) * 0.0025f); + break; + default: + break; + } + } + + public override void Register() + { + DoEffectCalcAmount.Add(new EffectCalcAmountHandler(CalculateAmount, 0, AuraType.PeriodicEnergize)); + } + } + + public override AuraScript GetAuraScript() + { + return new spell_gen_replenishment_AuraScript(); + } + } + + [Script] + class spell_gen_running_wild : SpellScriptLoader + { + public spell_gen_running_wild() : base("spell_gen_running_wild") { } + + class spell_gen_running_wild_AuraScript : AuraScript + { + public override bool Validate(SpellInfo spellInfo) + { + if (!CliDB.CreatureDisplayInfoStorage.ContainsKey(ModelIds.RunningWild)) + return false; + return true; + } + + void HandleMount(AuraEffect aurEff, AuraEffectHandleModes mode) + { + Unit target = GetTarget(); + PreventDefaultAction(); + + target.Mount(ModelIds.RunningWild, 0, 0); + + // cast speed aura + MountCapabilityRecord mountCapability = CliDB.MountCapabilityStorage.LookupByKey(aurEff.GetAmount()); + if (mountCapability != null) + target.CastSpell(target, mountCapability.SpeedModSpell, TriggerCastFlags.FullMask); + } + + public override void Register() + { + OnEffectApply.Add(new EffectApplyHandler(HandleMount, 1, AuraType.Mounted, AuraEffectHandleModes.Real)); + } + } + + class spell_gen_running_wild_SpellScript : SpellScript + { + public override bool Validate(SpellInfo spellInfo) + { + return ValidateSpellInfo(SpellIds.AlteredForm); + } + + public override bool Load() + { + // Definitely not a good thing, but currently the only way to do something at cast start + // Should be replaced as soon as possible with a new hook: BeforeCastStart + GetCaster().CastSpell(GetCaster(), SpellIds.AlteredForm, TriggerCastFlags.FullMask); + return false; + } + + public override void Register() + { + } + } + + public override AuraScript GetAuraScript() + { + return new spell_gen_running_wild_AuraScript(); + } + + public override SpellScript GetSpellScript() + { + return new spell_gen_running_wild_SpellScript(); + } + } + + [Script] + class spell_gen_two_forms : SpellScriptLoader + { + public spell_gen_two_forms() : base("spell_gen_two_forms") { } + + class spell_gen_two_forms_SpellScript : SpellScript + { + SpellCastResult CheckCast() + { + if (GetCaster().IsInCombat()) + { + SetCustomCastResultMessage(SpellCustomErrors.CantTransform); + return SpellCastResult.CustomError; + } + + // Player cannot transform to human form if he is forced to be worgen for some reason (Darkflight) + if (GetCaster().GetAuraEffectsByType(AuraType.WorgenAlteredForm).Count > 1) + { + SetCustomCastResultMessage(SpellCustomErrors.CantTransform); + return SpellCastResult.CustomError; + } + + return SpellCastResult.SpellCastOk; + } + + void HandleTransform(uint effIndex) + { + Unit target = GetHitUnit(); + PreventHitDefaultEffect(effIndex); + if (target.HasAuraType(AuraType.WorgenAlteredForm)) + target.RemoveAurasByType(AuraType.WorgenAlteredForm); + else // Basepoints 1 for this aura control whether to trigger transform transition animation or not. + target.CastCustomSpell(SpellIds.AlteredForm, SpellValueMod.BasePoint0, 1, target, TriggerCastFlags.FullMask); + } + + public override void Register() + { + OnCheckCast.Add(new CheckCastHandler(CheckCast)); + OnEffectHitTarget.Add(new EffectHandler(HandleTransform, 0, SpellEffectName.Dummy)); + } + } + + public override SpellScript GetSpellScript() + { + return new spell_gen_two_forms_SpellScript(); + } + } + + [Script] + class spell_gen_darkflight : SpellScriptLoader + { + public spell_gen_darkflight() : base("spell_gen_darkflight") { } + + class spell_gen_darkflight_SpellScript : SpellScript + { + void TriggerTransform() + { + GetCaster().CastSpell(GetCaster(), SpellIds.AlteredForm, TriggerCastFlags.FullMask); + } + + public override void Register() + { + AfterCast.Add(new CastHandler(TriggerTransform)); + } + } + + public override SpellScript GetSpellScript() + { + return new spell_gen_darkflight_SpellScript(); + } + } + + [Script] + class spell_gen_seaforium_blast : SpellScriptLoader + { + public spell_gen_seaforium_blast() : base("spell_gen_seaforium_blast") { } + + class spell_gen_seaforium_blast_SpellScript : SpellScript + { + public override bool Validate(SpellInfo spellInfo) + { + return ValidateSpellInfo(SpellIds.PlantChargesCreditAchievement); + } + + public override bool Load() + { + // OriginalCaster is always available in Spell.prepare + return GetOriginalCaster().IsTypeId(TypeId.Player); + } + + void AchievementCredit(uint effIndex) + { + // but in effect handling OriginalCaster can become null + Unit originalCaster = GetOriginalCaster(); + if (originalCaster) + { + GameObject go = GetHitGObj(); + if (go) + if (go.GetGoInfo().type == GameObjectTypes.DestructibleBuilding) + originalCaster.CastSpell(originalCaster, SpellIds.PlantChargesCreditAchievement, true); + } + } + + public override void Register() + { + OnEffectHitTarget.Add(new EffectHandler(AchievementCredit, 1, SpellEffectName.GameObjectDamage)); + } + } + + public override SpellScript GetSpellScript() + { + return new spell_gen_seaforium_blast_SpellScript(); + } + } + + [Script] + class spell_gen_spectator_cheer_trigger : SpellScriptLoader + { + public spell_gen_spectator_cheer_trigger() : base("spell_gen_spectator_cheer_trigger") { } + + class spell_gen_spectator_cheer_trigger_SpellScript : SpellScript + { + void HandleDummy(uint effIndex) + { + GetCaster().HandleEmoteCommand(EmoteArray[RandomHelper.URand(0, 2)]); + } + + public override void Register() + { + OnEffectHitTarget.Add(new EffectHandler(HandleDummy, 0, SpellEffectName.Dummy)); + } + } + + public override SpellScript GetSpellScript() + { + return new spell_gen_spectator_cheer_trigger_SpellScript(); + } + + static Emote[] EmoteArray = { Emote.OneshotCheer, Emote.OneshotExclamation, Emote.OneshotApplaud }; + } + + [Script] + class spell_gen_spirit_healer_res : SpellScriptLoader + { + public spell_gen_spirit_healer_res() : base("spell_gen_spirit_healer_res") { } + + class spell_gen_spirit_healer_res_SpellScript : SpellScript + { + public override bool Load() + { + return GetOriginalCaster() && GetOriginalCaster().IsTypeId(TypeId.Player); + } + + void HandleDummy(uint effIndex) + { + Player originalCaster = GetOriginalCaster().ToPlayer(); + Unit target = GetHitUnit(); + if (target) + { + SpiritHealerConfirm spiritHealerConfirm = new SpiritHealerConfirm(); + spiritHealerConfirm.Unit = target.GetGUID(); + originalCaster.SendPacket(spiritHealerConfirm); + } + } + + public override void Register() + { + OnEffectHitTarget.Add(new EffectHandler(HandleDummy, 0, SpellEffectName.Dummy)); + } + } + + public override SpellScript GetSpellScript() + { + return new spell_gen_spirit_healer_res_SpellScript(); + } + } + + [Script("spell_gen_summon_fire_elemental", SpellIds.SummonFireElemental)] + [Script("spell_gen_summon_earth_elemental", SpellIds.SummonEarthElemental)] + class spell_gen_summon_elemental : SpellScriptLoader + { + public spell_gen_summon_elemental(string name, uint spellId) : base(name) + { + _spellId = spellId; + } + + class spell_gen_summon_elemental_AuraScript : AuraScript + { + public spell_gen_summon_elemental_AuraScript(uint spellId) + { + _spellId = spellId; + } + + public override bool Validate(SpellInfo spellInfo) + { + return ValidateSpellInfo(_spellId); + } + + void AfterApply(AuraEffect aurEff, AuraEffectHandleModes mode) + { + if (GetCaster()) + { + Unit owner = GetCaster().GetOwner(); + if (owner) + owner.CastSpell(owner, _spellId, true); + } + } + + void AfterRemove(AuraEffect aurEff, AuraEffectHandleModes mode) + { + if (GetCaster()) + { + Unit owner = GetCaster().GetOwner(); + if (owner) + if (owner.IsTypeId(TypeId.Player)) /// @todo this check is maybe wrong + owner.ToPlayer().RemovePet(null, PetSaveMode.NotInSlot, true); + } + } + + public override void Register() + { + AfterEffectApply.Add(new EffectApplyHandler(AfterApply, 1, AuraType.Dummy, AuraEffectHandleModes.Real)); + AfterEffectRemove.Add(new EffectApplyHandler(AfterRemove, 1, AuraType.Dummy, AuraEffectHandleModes.Real)); + } + + uint _spellId; + } + + public override AuraScript GetAuraScript() + { + return new spell_gen_summon_elemental_AuraScript(_spellId); + } + + uint _spellId; + } + + [Script] + class spell_gen_summon_tournament_mount : SpellScriptLoader + { + public spell_gen_summon_tournament_mount() : base("spell_gen_summon_tournament_mount") { } + + class spell_gen_summon_tournament_mount_SpellScript : SpellScript + { + public override bool Validate(SpellInfo spellInfo) + { + return ValidateSpellInfo(SpellIds.LanceEquipped); + } + + SpellCastResult CheckIfLanceEquiped() + { + if (GetCaster().IsInDisallowedMountForm()) + GetCaster().RemoveAurasByType(AuraType.ModShapeshift); + + if (!GetCaster().HasAura(SpellIds.LanceEquipped)) + { + SetCustomCastResultMessage(SpellCustomErrors.MustHaveLanceEquipped); + return SpellCastResult.CustomError; + } + + return SpellCastResult.SpellCastOk; + } + + public override void Register() + { + OnCheckCast.Add(new CheckCastHandler(CheckIfLanceEquiped)); + } + } + + public override SpellScript GetSpellScript() + { + return new spell_gen_summon_tournament_mount_SpellScript(); + } + } + + [Script] // 41213, 43416, 69222, 73076 - Throw Shield + class spell_gen_throw_shield : SpellScriptLoader + { + public spell_gen_throw_shield() : base("spell_gen_throw_shield") { } + + class spell_gen_throw_shield_SpellScript : SpellScript + { + void HandleScriptEffect(uint effIndex) + { + PreventHitDefaultEffect(effIndex); + GetCaster().CastSpell(GetHitUnit(), (uint)GetEffectValue(), true); + } + + public override void Register() + { + OnEffectHitTarget.Add(new EffectHandler(HandleScriptEffect, 1, SpellEffectName.ScriptEffect)); + } + } + + public override SpellScript GetSpellScript() + { + return new spell_gen_throw_shield_SpellScript(); + } + } + + [Script] + class spell_gen_tournament_duel : SpellScriptLoader + { + public spell_gen_tournament_duel() : base("spell_gen_tournament_duel") { } + + class spell_gen_tournament_duel_SpellScript : SpellScript + { + public override bool Validate(SpellInfo spellInfo) + { + return ValidateSpellInfo(SpellIds.OnTournamentMount, SpellIds.MountedDuel); + } + + void HandleScriptEffect(uint effIndex) + { + Unit rider = GetCaster().GetCharmer(); + if (rider) + { + Player playerTarget = GetHitPlayer(); + if (playerTarget) + { + if (playerTarget.HasAura(SpellIds.OnTournamentMount) && playerTarget.GetVehicleBase()) + rider.CastSpell(playerTarget, SpellIds.MountedDuel, true); + return; + } + + Unit unitTarget = GetHitUnit(); + if (unitTarget) + { + if (unitTarget.GetCharmer() && unitTarget.GetCharmer().IsTypeId(TypeId.Player) && unitTarget.GetCharmer().HasAura(SpellIds.OnTournamentMount)) + rider.CastSpell(unitTarget.GetCharmer(), SpellIds.MountedDuel, true); + } + } + } + + public override void Register() + { + OnEffectHitTarget.Add(new EffectHandler(HandleScriptEffect, 0, SpellEffectName.ScriptEffect)); + } + } + + public override SpellScript GetSpellScript() + { + return new spell_gen_tournament_duel_SpellScript(); + } + } + + [Script] + class spell_gen_tournament_pennant : SpellScriptLoader + { + public spell_gen_tournament_pennant() : base("spell_gen_tournament_pennant") { } + + class spell_gen_tournament_pennant_AuraScript : AuraScript + { + public override bool Load() + { + return GetCaster() && GetCaster().IsTypeId(TypeId.Player); + } + + void HandleApplyEffect(AuraEffect aurEff, AuraEffectHandleModes mode) + { + Unit caster = GetCaster(); + if (caster) + if (!caster.GetVehicleBase()) + caster.RemoveAurasDueToSpell(GetId()); + } + + public override void Register() + { + OnEffectApply.Add(new EffectApplyHandler(HandleApplyEffect, 0, AuraType.Dummy, AuraEffectHandleModes.RealOrReapplyMask)); + } + } + + public override AuraScript GetAuraScript() + { + return new spell_gen_tournament_pennant_AuraScript(); + } + } + + [Script] + class spell_pvp_trinket_wotf_shared_cd : SpellScriptLoader + { + public spell_pvp_trinket_wotf_shared_cd() : base("spell_pvp_trinket_wotf_shared_cd") { } + + class spell_pvp_trinket_wotf_shared_cd_SpellScript : SpellScript + { + public override bool Load() + { + return GetCaster().IsTypeId(TypeId.Player); + } + + public override bool Validate(SpellInfo spellInfo) + { + return ValidateSpellInfo(SpellIds.WillOfTheForsakenCooldownTrigger, SpellIds.WillOfTheForsakenCooldownTriggerWotf); + } + + void HandleScript() + { + // This is only needed because spells cast from spell_linked_spell are triggered by default + // Spell.SendSpellCooldown() skips all spells with TRIGGERED_IGNORE_SPELL_AND_CATEGORY_CD + GetCaster().GetSpellHistory().StartCooldown(GetSpellInfo(), 0, GetSpell()); + } + + public override void Register() + { + AfterCast.Add(new CastHandler(HandleScript)); + } + } + + public override SpellScript GetSpellScript() + { + return new spell_pvp_trinket_wotf_shared_cd_SpellScript(); + } + } + + [Script] + class spell_gen_turkey_marker : SpellScriptLoader + { + public spell_gen_turkey_marker() : base("spell_gen_turkey_marker") { } + + class spell_gen_turkey_marker_AuraScript : AuraScript + { + void OnApply(AuraEffect aurEff, AuraEffectHandleModes mode) + { + // store stack apply times, so we can pop them while they expire + _applyTimes.Add(Time.GetMSTime()); + Unit target = GetTarget(); + + // on stack 15 cast the achievement crediting spell + if (GetStackAmount() >= 15) + target.CastSpell(target, SpellIds.TurkeyVengeance, true, null, aurEff, GetCasterGUID()); + } + + void OnPeriodic(AuraEffect aurEff) + { + if (_applyTimes.Empty()) + return; + + // pop stack if it expired for us + if (_applyTimes.First() + GetMaxDuration() < Time.GetMSTime()) + ModStackAmount(-1, AuraRemoveMode.Expire); + } + + public override void Register() + { + AfterEffectApply.Add(new EffectApplyHandler(OnApply, 0, AuraType.PeriodicDummy, AuraEffectHandleModes.RealOrReapplyMask)); + OnEffectPeriodic.Add(new EffectPeriodicHandler(OnPeriodic, 0, AuraType.PeriodicDummy)); + } + + List _applyTimes = new List(); + } + + public override AuraScript GetAuraScript() + { + return new spell_gen_turkey_marker_AuraScript(); + } + } + + [Script] + class spell_gen_upper_deck_create_foam_sword : SpellScriptLoader + { + public spell_gen_upper_deck_create_foam_sword() : base("spell_gen_upper_deck_create_foam_sword") { } + + class spell_gen_upper_deck_create_foam_sword_SpellScript : SpellScript + { + void HandleScript(uint effIndex) + { + Player player = GetHitPlayer(); + if (player) + { + // player can only have one of these items + for (byte i = 0; i < 5; ++i) + { + if (player.HasItemCount(itemId[i], 1, true)) + return; + } + + CreateItem(effIndex, itemId[RandomHelper.URand(0, 4)]); + } + } + + public override void Register() + { + OnEffectHitTarget.Add(new EffectHandler(HandleScript, 0, SpellEffectName.ScriptEffect)); + } + } + + public override SpellScript GetSpellScript() + { + return new spell_gen_upper_deck_create_foam_sword_SpellScript(); + } + + // green pink blue red yellow + static uint[] itemId = { 45061, 45176, 45177, 45178, 45179 }; + } + + // 52723 - Vampiric Touch + [Script] // 60501 - Vampiric Touch + class spell_gen_vampiric_touch : SpellScriptLoader + { + public spell_gen_vampiric_touch() : base("spell_gen_vampiric_touch") { } + + class spell_gen_vampiric_touch_AuraScript : AuraScript + { + public override bool Validate(SpellInfo spellInfo) + { + return ValidateSpellInfo(SpellIds.VampiricTouchHeal); + } + + void HandleProc(AuraEffect aurEff, ProcEventInfo eventInfo) + { + PreventDefaultAction(); + DamageInfo damageInfo = eventInfo.GetDamageInfo(); + if (damageInfo == null || damageInfo.GetDamage() == 0) + return; + + Unit caster = eventInfo.GetActor(); + int bp = (int)(damageInfo.GetDamage() / 2); + caster.CastCustomSpell(SpellIds.VampiricTouchHeal, SpellValueMod.BasePoint0, bp, caster, true); + } + + public override void Register() + { + OnEffectProc.Add(new EffectProcHandler(HandleProc, 0, AuraType.Dummy)); + } + } + + public override AuraScript GetAuraScript() + { + return new spell_gen_vampiric_touch_AuraScript(); + } + } + + [Script] + class spell_gen_vehicle_scaling : SpellScriptLoader + { + public spell_gen_vehicle_scaling() : base("spell_gen_vehicle_scaling") { } + + class spell_gen_vehicle_scaling_AuraScript : AuraScript + { + public override bool Load() + { + return GetCaster() && GetCaster().IsTypeId(TypeId.Player); + } + + void CalculateAmount(AuraEffect aurEff, ref int amount, ref bool canBeRecalculated) + { + Unit caster = GetCaster(); + float factor; + ushort baseItemLevel; + + /// @todo Reserach coeffs for different vehicles + switch (GetId()) + { + case SpellIds.GearScaling: + factor = 1.0f; + baseItemLevel = 205; + break; + default: + factor = 1.0f; + baseItemLevel = 170; + break; + } + + float avgILvl = caster.ToPlayer().GetAverageItemLevel(); + if (avgILvl < baseItemLevel) + return; /// @todo Research possibility of scaling down + + amount = (int)((avgILvl - baseItemLevel) * factor); + } + + public override void Register() + { + DoEffectCalcAmount.Add(new EffectCalcAmountHandler(CalculateAmount, 0, AuraType.ModHealingPct)); + DoEffectCalcAmount.Add(new EffectCalcAmountHandler(CalculateAmount, 1, AuraType.ModDamagePercentDone)); + DoEffectCalcAmount.Add(new EffectCalcAmountHandler(CalculateAmount, 2, AuraType.ModIncreaseHealthPercent)); + } + } + + public override AuraScript GetAuraScript() + { + return new spell_gen_vehicle_scaling_AuraScript(); + } + } + + [Script] + class spell_gen_vendor_bark_trigger : SpellScriptLoader + { + public spell_gen_vendor_bark_trigger() : base("spell_gen_vendor_bark_trigger") { } + + class spell_gen_vendor_bark_trigger_SpellScript : SpellScript + { + void HandleDummy(uint effIndex) + { + Creature vendor = GetCaster().ToCreature(); + if (vendor) + if (vendor.GetEntry() == CreatureIds.AmphitheaterVendor) + vendor.GetAI().Talk(TextIds.SayAmphitheaterVendor); + } + + public override void Register() + { + OnEffectHitTarget.Add(new EffectHandler(HandleDummy, 0, SpellEffectName.Dummy)); + } + } + + public override SpellScript GetSpellScript() + { + return new spell_gen_vendor_bark_trigger_SpellScript(); + } + } + + [Script] + class spell_gen_wg_water : SpellScriptLoader + { + public spell_gen_wg_water() : base("spell_gen_wg_water") { } + + class spell_gen_wg_water_SpellScript : SpellScript + { + SpellCastResult CheckCast() + { + if (!GetSpellInfo().CheckTargetCreatureType(GetCaster())) + return SpellCastResult.DontReport; + return SpellCastResult.SpellCastOk; + } + + public override void Register() + { + OnCheckCast.Add(new CheckCastHandler(CheckCast)); + } + } + + public override SpellScript GetSpellScript() + { + return new spell_gen_wg_water_SpellScript(); + } + } + + [Script] + class spell_gen_whisper_gulch_yogg_saron_whisper : SpellScriptLoader + { + public spell_gen_whisper_gulch_yogg_saron_whisper() : base("spell_gen_whisper_gulch_yogg_saron_whisper") { } + + class spell_gen_whisper_gulch_yogg_saron_whisper_AuraScript : AuraScript + { + public override bool Validate(SpellInfo spellInfo) + { + return ValidateSpellInfo(SpellIds.YoggSaronWhisperDummy); + } + + void HandleEffectPeriodic(AuraEffect aurEff) + { + PreventDefaultAction(); + GetTarget().CastSpell((Unit)null, SpellIds.YoggSaronWhisperDummy, true); + } + + public override void Register() + { + OnEffectPeriodic.Add(new EffectPeriodicHandler(HandleEffectPeriodic, 0, AuraType.PeriodicDummy)); + } + } + + public override AuraScript GetAuraScript() + { + return new spell_gen_whisper_gulch_yogg_saron_whisper_AuraScript(); + } + } + + [Script] + class spell_gen_eject_all_passengers : SpellScriptLoader + { + public spell_gen_eject_all_passengers() : base("spell_gen_eject_all_passengers") { } + + class spell_gen_eject_all_passengers_SpellScript : SpellScript + { + void RemoveVehicleAuras() + { + Vehicle vehicle = GetHitUnit().GetVehicleKit(); + if (vehicle) + vehicle.RemoveAllPassengers(); + } + + public override void Register() + { + AfterHit.Add(new HitHandler(RemoveVehicleAuras)); + } + } + + public override SpellScript GetSpellScript() + { + return new spell_gen_eject_all_passengers_SpellScript(); + } + } + + [Script] + class spell_gen_gm_freeze : SpellScriptLoader + { + public spell_gen_gm_freeze() : base("spell_gen_gm_freeze") { } + + class spell_gen_gm_freeze_AuraScript : AuraScript + { + public override bool Validate(SpellInfo spellInfo) + { + return ValidateSpellInfo(SpellIds.GmFreeze); + } + + void OnApply(AuraEffect aurEff, AuraEffectHandleModes mode) + { + // Do what was done before to the target in HandleFreezeCommand + Player player = GetTarget().ToPlayer(); + if (player) + { + // stop combat + make player unattackable + duel stop + stop some spells + player.SetFaction(35); + player.CombatStop(); + if (player.IsNonMeleeSpellCast(true)) + player.InterruptNonMeleeSpells(true); + player.SetFlag(UnitFields.Flags, UnitFlags.NonAttackable); + + // if player class = hunter || warlock remove pet if alive + if ((player.GetClass() == Class.Hunter) || (player.GetClass() == Class.Warlock)) + { + Pet pet = player.GetPet(); + if (pet) + { + pet.SavePetToDB(PetSaveMode.AsCurrent); + // not let dismiss dead pet + if (pet.IsAlive()) + player.RemovePet(pet, PetSaveMode.NotInSlot); + } + } + } + } + + void OnRemove(AuraEffect aurEff, AuraEffectHandleModes mode) + { + // Do what was done before to the target in HandleUnfreezeCommand + Player player = GetTarget().ToPlayer(); + if (player) + { + // Reset player faction + allow combat + allow duels + player.setFactionForRace(player.GetRace()); + player.RemoveFlag(UnitFields.Flags, UnitFlags.NonAttackable); + // save player + player.SaveToDB(); + } + } + + public override void Register() + { + OnEffectApply.Add(new EffectApplyHandler(OnApply, 0, AuraType.ModStun, AuraEffectHandleModes.Real)); + OnEffectRemove.Add(new EffectApplyHandler(OnRemove, 0, AuraType.ModStun, AuraEffectHandleModes.Real)); + } + } + + public override AuraScript GetAuraScript() + { + return new spell_gen_gm_freeze_AuraScript(); + } + } + + [Script] + class spell_gen_stand : SpellScriptLoader + { + public spell_gen_stand() : base("spell_gen_stand") { } + + class spell_gen_stand_SpellScript : SpellScript + { + void HandleScript(uint eff) + { + Creature target = GetHitCreature(); + if (!target) + return; + + target.SetStandState(UnitStandStateType.Stand); + target.HandleEmoteCommand(Emote.StateNone); + } + + public override void Register() + { + OnEffectHitTarget.Add(new EffectHandler(HandleScript, 0, SpellEffectName.ScriptEffect)); + } + } + + public override SpellScript GetSpellScript() + { + return new spell_gen_stand_SpellScript(); + } + } + + enum RequiredMixologySpells + { + Mixology = 53042, + // Flasks + FlaskOfTheFrostWyrm = 53755, + FlaskOfStoneblood = 53758, + FlaskOfEndlessRage = 53760, + FlaskOfPureMojo = 54212, + LesserFlaskOfResistance = 62380, + LesserFlaskOfToughness = 53752, + FlaskOfBlindingLight = 28521, + FlaskOfChromaticWonder = 42735, + FlaskOfFortification = 28518, + FlaskOfMightyRestoration = 28519, + FlaskOfPureDeath = 28540, + FlaskOfRelentlessAssault = 28520, + FlaskOfChromaticResistance = 17629, + FlaskOfDistilledWisdom = 17627, + FlaskOfSupremePower = 17628, + FlaskOfTheTitans = 17626, + // Elixirs + ElixirOfMightyAgility = 28497, + ElixirOfAccuracy = 60340, + ElixirOfDeadlyStrikes = 60341, + ElixirOfMightyDefense = 60343, + ElixirOfExpertise = 60344, + ElixirOfArmorPiercing = 60345, + ElixirOfLightningSpeed = 60346, + ElixirOfMightyFortitude = 53751, + ElixirOfMightyMageblood = 53764, + ElixirOfMightyStrength = 53748, + ElixirOfMightyToughts = 60347, + ElixirOfProtection = 53763, + ElixirOfSpirit = 53747, + GurusElixir = 53749, + ShadowpowerElixir = 33721, + WrathElixir = 53746, + ElixirOfEmpowerment = 28514, + ElixirOfMajorMageblood = 28509, + ElixirOfMajorShadowPower = 28503, + ElixirOfMajorDefense = 28502, + FelStrengthElixir = 38954, + ElixirOfIronskin = 39628, + ElixirOfMajorAgility = 54494, + ElixirOfDraenicWisdom = 39627, + ElixirOfMajorFirepower = 28501, + ElixirOfMajorFrostPower = 28493, + EarthenElixir = 39626, + ElixirOfMastery = 33726, + ElixirOfHealingPower = 28491, + ElixirOfMajorFortitude = 39625, + ElixirOfMajorStrength = 28490, + AdeptsElixir = 54452, + OnslaughtElixir = 33720, + MightyTrollsBloodElixir = 24361, + GreaterArcaneElixir = 17539, + ElixirOfTheMongoose = 17538, + ElixirOfBruteForce = 17537, + ElixirOfSages = 17535, + ElixirOfSuperiorDefense = 11348, + ElixirOfDemonslaying = 11406, + ElixirOfGreaterFirepower = 26276, + ElixirOfShadowPower = 11474, + MagebloodElixir = 24363, + ElixirOfGiants = 11405, + ElixirOfGreaterAgility = 11334, + ArcaneElixir = 11390, + ElixirOfGreaterIntellect = 11396, + ElixirOfGreaterDefense = 11349, + ElixirOfFrostPower = 21920, + ElixirOfAgility = 11328, + MajorTrollsBlloodElixir = 3223, + ElixirOfFortitude = 3593, + ElixirOfOgresStrength = 3164, + ElixirOfFirepower = 7844, + ElixirOfLesserAgility = 3160, + ElixirOfDefense = 3220, + StrongTrollsBloodElixir = 3222, + ElixirOfMinorAccuracy = 63729, + ElixirOfWisdom = 3166, + ElixirOfGianthGrowth = 8212, + ElixirOfMinorAgility = 2374, + ElixirOfMinorFortitude = 2378, + WeakTrollsBloodElixir = 3219, + ElixirOfLionsStrength = 2367, + ElixirOfMinorDefense = 673 + }; + + [Script] + class spell_gen_mixology_bonus : SpellScriptLoader + { + public spell_gen_mixology_bonus() : base("spell_gen_mixology_bonus") { } + + class spell_gen_mixology_bonus_AuraScript : AuraScript + { + public spell_gen_mixology_bonus_AuraScript() + { + bonus = 0; + } + + public override bool Validate(SpellInfo spellInfo) + { + return ValidateSpellInfo((uint)RequiredMixologySpells.Mixology); + } + + public override bool Load() + { + return GetCaster() && GetCaster().GetTypeId() == TypeId.Player; + } + + void SetBonusValueForEffect(uint effIndex, int value, AuraEffect aurEff) + { + if (aurEff.GetEffIndex() == effIndex) + bonus = value; + } + + void CalculateAmount(AuraEffect aurEff, ref int amount, ref bool canBeRecalculated) + { + if (GetCaster().HasAura((uint)RequiredMixologySpells.Mixology) && GetCaster().HasSpell(GetSpellInfo().GetEffect(0).TriggerSpell)) + { + switch ((RequiredMixologySpells)GetId()) + { + case RequiredMixologySpells.WeakTrollsBloodElixir: + case RequiredMixologySpells.MagebloodElixir: + bonus = amount; + break; + case RequiredMixologySpells.ElixirOfFrostPower: + case RequiredMixologySpells.LesserFlaskOfToughness: + case RequiredMixologySpells.LesserFlaskOfResistance: + bonus = MathFunctions.CalculatePct(amount, 80); + break; + case RequiredMixologySpells.ElixirOfMinorDefense: + case RequiredMixologySpells.ElixirOfLionsStrength: + case RequiredMixologySpells.ElixirOfMinorAgility: + case RequiredMixologySpells.MajorTrollsBlloodElixir: + case RequiredMixologySpells.ElixirOfShadowPower: + case RequiredMixologySpells.ElixirOfBruteForce: + case RequiredMixologySpells.MightyTrollsBloodElixir: + case RequiredMixologySpells.ElixirOfGreaterFirepower: + case RequiredMixologySpells.OnslaughtElixir: + case RequiredMixologySpells.EarthenElixir: + case RequiredMixologySpells.ElixirOfMajorAgility: + case RequiredMixologySpells.FlaskOfTheTitans: + case RequiredMixologySpells.FlaskOfRelentlessAssault: + case RequiredMixologySpells.FlaskOfStoneblood: + case RequiredMixologySpells.ElixirOfMinorAccuracy: + bonus = MathFunctions.CalculatePct(amount, 50); + break; + case RequiredMixologySpells.ElixirOfProtection: + bonus = 280; + break; + case RequiredMixologySpells.ElixirOfMajorDefense: + bonus = 200; + break; + case RequiredMixologySpells.ElixirOfGreaterDefense: + case RequiredMixologySpells.ElixirOfSuperiorDefense: + bonus = 140; + break; + case RequiredMixologySpells.ElixirOfFortitude: + bonus = 100; + break; + case RequiredMixologySpells.FlaskOfEndlessRage: + bonus = 82; + break; + case RequiredMixologySpells.ElixirOfDefense: + bonus = 70; + break; + case RequiredMixologySpells.ElixirOfDemonslaying: + bonus = 50; + break; + case RequiredMixologySpells.FlaskOfTheFrostWyrm: + bonus = 47; + break; + case RequiredMixologySpells.WrathElixir: + bonus = 32; + break; + case RequiredMixologySpells.ElixirOfMajorFrostPower: + case RequiredMixologySpells.ElixirOfMajorFirepower: + case RequiredMixologySpells.ElixirOfMajorShadowPower: + bonus = 29; + break; + case RequiredMixologySpells.ElixirOfMightyToughts: + bonus = 27; + break; + case RequiredMixologySpells.FlaskOfSupremePower: + case RequiredMixologySpells.FlaskOfBlindingLight: + case RequiredMixologySpells.FlaskOfPureDeath: + case RequiredMixologySpells.ShadowpowerElixir: + bonus = 23; + break; + case RequiredMixologySpells.ElixirOfMightyAgility: + case RequiredMixologySpells.FlaskOfDistilledWisdom: + case RequiredMixologySpells.ElixirOfSpirit: + case RequiredMixologySpells.ElixirOfMightyStrength: + case RequiredMixologySpells.FlaskOfPureMojo: + case RequiredMixologySpells.ElixirOfAccuracy: + case RequiredMixologySpells.ElixirOfDeadlyStrikes: + case RequiredMixologySpells.ElixirOfMightyDefense: + case RequiredMixologySpells.ElixirOfExpertise: + case RequiredMixologySpells.ElixirOfArmorPiercing: + case RequiredMixologySpells.ElixirOfLightningSpeed: + bonus = 20; + break; + case RequiredMixologySpells.FlaskOfChromaticResistance: + bonus = 17; + break; + case RequiredMixologySpells.ElixirOfMinorFortitude: + case RequiredMixologySpells.ElixirOfMajorStrength: + bonus = 15; + break; + case RequiredMixologySpells.FlaskOfMightyRestoration: + bonus = 13; + break; + case RequiredMixologySpells.ArcaneElixir: + bonus = 12; + break; + case RequiredMixologySpells.ElixirOfGreaterAgility: + case RequiredMixologySpells.ElixirOfGiants: + bonus = 11; + break; + case RequiredMixologySpells.ElixirOfAgility: + case RequiredMixologySpells.ElixirOfGreaterIntellect: + case RequiredMixologySpells.ElixirOfSages: + case RequiredMixologySpells.ElixirOfIronskin: + case RequiredMixologySpells.ElixirOfMightyMageblood: + bonus = 10; + break; + case RequiredMixologySpells.ElixirOfHealingPower: + bonus = 9; + break; + case RequiredMixologySpells.ElixirOfDraenicWisdom: + case RequiredMixologySpells.GurusElixir: + bonus = 8; + break; + case RequiredMixologySpells.ElixirOfFirepower: + case RequiredMixologySpells.ElixirOfMajorMageblood: + case RequiredMixologySpells.ElixirOfMastery: + bonus = 6; + break; + case RequiredMixologySpells.ElixirOfLesserAgility: + case RequiredMixologySpells.ElixirOfOgresStrength: + case RequiredMixologySpells.ElixirOfWisdom: + case RequiredMixologySpells.ElixirOfTheMongoose: + bonus = 5; + break; + case RequiredMixologySpells.StrongTrollsBloodElixir: + case RequiredMixologySpells.FlaskOfChromaticWonder: + bonus = 4; + break; + case RequiredMixologySpells.ElixirOfEmpowerment: + bonus = -10; + break; + case RequiredMixologySpells.AdeptsElixir: + SetBonusValueForEffect(0, 13, aurEff); + SetBonusValueForEffect(1, 13, aurEff); + SetBonusValueForEffect(2, 8, aurEff); + break; + case RequiredMixologySpells.ElixirOfMightyFortitude: + SetBonusValueForEffect(0, 160, aurEff); + break; + case RequiredMixologySpells.ElixirOfMajorFortitude: + SetBonusValueForEffect(0, 116, aurEff); + SetBonusValueForEffect(1, 6, aurEff); + break; + case RequiredMixologySpells.FelStrengthElixir: + SetBonusValueForEffect(0, 40, aurEff); + SetBonusValueForEffect(1, 40, aurEff); + break; + case RequiredMixologySpells.FlaskOfFortification: + SetBonusValueForEffect(0, 210, aurEff); + SetBonusValueForEffect(1, 5, aurEff); + break; + case RequiredMixologySpells.GreaterArcaneElixir: + SetBonusValueForEffect(0, 19, aurEff); + SetBonusValueForEffect(1, 19, aurEff); + SetBonusValueForEffect(2, 5, aurEff); + break; + case RequiredMixologySpells.ElixirOfGianthGrowth: + SetBonusValueForEffect(0, 5, aurEff); + break; + default: + Log.outError(LogFilter.Spells, "SpellId {0} couldn't be processed in spell_gen_mixology_bonus", GetId()); + break; + } + amount += bonus; + } + } + + int bonus; + + public override void Register() + { + DoEffectCalcAmount.Add(new EffectCalcAmountHandler(CalculateAmount, SpellConst.EffectAll, AuraType.Any)); + } + } + + public override AuraScript GetAuraScript() + { + return new spell_gen_mixology_bonus_AuraScript(); + } + } + + [Script] + class spell_gen_landmine_knockback_achievement : SpellScriptLoader + { + public spell_gen_landmine_knockback_achievement() : base("spell_gen_landmine_knockback_achievement") { } + + class spell_gen_landmine_knockback_achievement_SpellScript : SpellScript + { + void HandleScript(uint effIndex) + { + Player target = GetHitPlayer(); + if (target) + { + Aura aura = GetHitAura(); + if (aura == null || aura.GetStackAmount() < 10) + return; + + target.CastSpell(target, SpellIds.LandmineKnockbackAchievement, true); + } + } + + public override void Register() + { + OnEffectHitTarget.Add(new EffectHandler(HandleScript, 0, SpellEffectName.ScriptEffect)); + } + } + + public override SpellScript GetSpellScript() + { + return new spell_gen_landmine_knockback_achievement_SpellScript(); + } + } + + [Script] // 34098 - ClearAllDebuffs + class spell_gen_clear_debuffs : SpellScriptLoader + { + public spell_gen_clear_debuffs() : base("spell_gen_clear_debuffs") { } + + class spell_gen_clear_debuffs_SpellScript : SpellScript + { + void HandleScript(uint effIndex) + { + Unit target = GetHitUnit(); + if (target) + { + target.RemoveOwnedAuras(aura => + { + SpellInfo spellInfo = aura.GetSpellInfo(); + return !spellInfo.IsPositive() && !spellInfo.IsPassive(); + }); + } + } + + public override void Register() + { + OnEffectHitTarget.Add(new EffectHandler(HandleScript, 0, SpellEffectName.ScriptEffect)); + } + } + + public override SpellScript GetSpellScript() + { + return new spell_gen_clear_debuffs_SpellScript(); + } + } + + [Script] + class spell_gen_pony_mount_check : SpellScriptLoader + { + public spell_gen_pony_mount_check() : base("spell_gen_pony_mount_check") { } + + class spell_gen_pony_mount_check_AuraScript : AuraScript + { + void HandleEffectPeriodic(AuraEffect aurEff) + { + Unit caster = GetCaster(); + if (!caster) + return; + + Player owner = caster.GetOwner().ToPlayer(); + if (!owner || !owner.HasAchieved(SpellIds.AchievementPonyup)) + return; + + if (owner.IsMounted()) + { + caster.Mount(SpellIds.MountPony); + caster.SetSpeedRate(UnitMoveType.Run, owner.GetSpeedRate(UnitMoveType.Run)); + } + else if (caster.IsMounted()) + { + caster.Dismount(); + caster.SetSpeedRate(UnitMoveType.Run, owner.GetSpeedRate(UnitMoveType.Run)); + } + } + + public override void Register() + { + OnEffectPeriodic.Add(new EffectPeriodicHandler(HandleEffectPeriodic, 0, AuraType.PeriodicDummy)); + } + } + + public override AuraScript GetAuraScript() + { + return new spell_gen_pony_mount_check_AuraScript(); + } + } + + [Script] // 169869 - Transformation Sickness + class spell_gen_decimatus_transformation_sickness : SpellScriptLoader + { + public spell_gen_decimatus_transformation_sickness() : base("spell_gen_decimatus_transformation_sickness") { } + + class spell_gen_decimatus_transformation_sickness_SpellScript : SpellScript + { + void HandleScript(uint effIndex) + { + Unit target = GetHitUnit(); + if (target) + target.SetHealth(target.CountPctFromMaxHealth(25)); + } + + public override void Register() + { + OnEffectHitTarget.Add(new EffectHandler(HandleScript, 1, SpellEffectName.ScriptEffect)); + } + } + + public override SpellScript GetSpellScript() + { + return new spell_gen_decimatus_transformation_sickness_SpellScript(); + } + } + + [Script] // 189491 - Summon Towering Infernal. + class spell_gen_anetheron_summon_towering_infernal : SpellScriptLoader + { + public spell_gen_anetheron_summon_towering_infernal() : base("spell_gen_anetheron_summon_towering_infernal") { } + + class spell_gen_anetheron_summon_towering_infernal_SpellScript : SpellScript + { + void HandleDummy(uint effIndex) + { + GetCaster().CastSpell(GetHitUnit(), (uint)GetEffectValue(), true); + } + + public override void Register() + { + OnEffectHitTarget.Add(new EffectHandler(HandleDummy, 0, SpellEffectName.Dummy)); + } + } + + public override SpellScript GetSpellScript() + { + return new spell_gen_anetheron_summon_towering_infernal_SpellScript(); + } + } + + [Script] + class spell_gen_mark_of_kazrogal_hellfire : SpellScriptLoader + { + public spell_gen_mark_of_kazrogal_hellfire() : base("spell_gen_mark_of_kazrogal_hellfire") { } + + class spell_gen_mark_of_kazrogal_hellfire_SpellScript : SpellScript + { + void FilterTargets(List targets) + { + targets.RemoveAll(target => + { + Unit unit = target.ToUnit(); + if (unit) + return unit.getPowerType() != PowerType.Mana; + return false; + }); + } + + public override void Register() + { + OnObjectAreaTargetSelect.Add(new ObjectAreaTargetSelectHandler(FilterTargets, 0, Targets.UnitSrcAreaEnemy)); + } + } + + class spell_gen_mark_of_kazrogal_hellfire_AuraScript : AuraScript + { + public override bool Validate(SpellInfo spell) + { + return ValidateSpellInfo(SpellIds.MarkOfKazrogalDamageHellfire); + } + + void OnPeriodic(AuraEffect aurEff) + { + Unit target = GetTarget(); + + if (target.GetPower(PowerType.Mana) == 0) + { + target.CastSpell(target, SpellIds.MarkOfKazrogalDamageHellfire, true, null, aurEff); + // Remove aura + SetDuration(0); + } + } + + public override void Register() + { + OnEffectPeriodic.Add(new EffectPeriodicHandler(OnPeriodic, 0, AuraType.PowerBurn)); + } + } + + public override SpellScript GetSpellScript() + { + return new spell_gen_mark_of_kazrogal_hellfire_SpellScript(); + } + + public override AuraScript GetAuraScript() + { + return new spell_gen_mark_of_kazrogal_hellfire_AuraScript(); + } + } + + [Script] + class spell_gen_azgalor_rain_of_fire_hellfire_citadel : SpellScriptLoader + { + public spell_gen_azgalor_rain_of_fire_hellfire_citadel() : base("spell_gen_azgalor_rain_of_fire_hellfire_citadel") { } + + class spell_gen_azgalor_rain_of_fire_hellfire_citadel_SpellScript : SpellScript + { + void HandleDummy(uint effIndex) + { + GetCaster().CastSpell(GetHitUnit(), (uint)GetEffectValue(), true); + } + + public override void Register() + { + OnEffectHitTarget.Add(new EffectHandler(HandleDummy, 0, SpellEffectName.Dummy)); + } + } + + public override SpellScript GetSpellScript() + { + return new spell_gen_azgalor_rain_of_fire_hellfire_citadel_SpellScript(); + } + } + + [Script] // 99947 - Face Rage + class spell_gen_face_rage : SpellScriptLoader + { + public spell_gen_face_rage() : base("spell_gen_face_rage") { } + + class spell_gen_face_rage_AuraScript : AuraScript + { + public override bool Validate(SpellInfo spell) + { + return ValidateSpellInfo(SpellIds.FaceRage); + } + + void OnRemove(AuraEffect effect, AuraEffectHandleModes mode) + { + GetTarget().RemoveAurasDueToSpell(GetSpellInfo().GetEffect(2).TriggerSpell); + } + + public override void Register() + { + OnEffectRemove.Add(new EffectApplyHandler(OnRemove, 0, AuraType.ModStun, AuraEffectHandleModes.Real)); + } + } + + public override AuraScript GetAuraScript() + { + return new spell_gen_face_rage_AuraScript(); + } + } + + [Script] // 187213 - Impatient Mind + class spell_gen_impatient_mind : SpellScriptLoader + { + public spell_gen_impatient_mind() : base("spell_gen_impatient_mind") { } + + class spell_gen_impatient_mind_AuraScript : AuraScript + { + public override bool Validate(SpellInfo spell) + { + return ValidateSpellInfo(SpellIds.ImpatientMind); + } + + void OnRemove(AuraEffect effect, AuraEffectHandleModes mode) + { + GetTarget().RemoveAurasDueToSpell(effect.GetSpellEffectInfo().TriggerSpell); + } + + public override void Register() + { + OnEffectRemove.Add(new EffectApplyHandler(OnRemove, 0, AuraType.ProcTriggerSpell, AuraEffectHandleModes.Real)); + } + } + + public override AuraScript GetAuraScript() + { + return new spell_gen_impatient_mind_AuraScript(); + } + } +} diff --git a/Scripts/Spells/Holiday.cs b/Scripts/Spells/Holiday.cs new file mode 100644 index 000000000..55c87392c --- /dev/null +++ b/Scripts/Spells/Holiday.cs @@ -0,0 +1,984 @@ +/* + * Copyright (C) 2012-2017 CypherCore + * + * 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, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +using Framework.Constants; +using Game; +using Game.Entities; +using Game.Maps; +using Game.Scripting; +using Game.Spells; +using System; +using System.Collections.Generic; + +namespace Scripts.Spells.Holiday +{ + struct SpellIds + { + //Romantic Picnic + public const uint BasketCheck = 45119; // Holiday - Valentine - Romantic Picnic Near Basket Check + public const uint MealPeriodic = 45103; // Holiday - Valentine - Romantic Picnic Meal Periodic - Effect Dummy + public const uint MealEatVisual = 45120; // Holiday - Valentine - Romantic Picnic Meal Eat Visual + //public const uint MealParticle = 45114; // Holiday - Valentine - Romantic Picnic Meal Particle - Unused + public const uint DrinkVisual = 45121; // Holiday - Valentine - Romantic Picnic Drink Visual + public const uint RomanticPicnicAchiev = 45123; // Romantic Picnic Periodic = 5000 + + //Trickspells + public const uint PirateCostumeMale = 24708; + public const uint PirateCostumeFemale = 24709; + public const uint NinjaCostumeMale = 24710; + public const uint NinjaCostumeFemale = 24711; + public const uint LeperGnomeCostumeMale = 24712; + public const uint LeperGnomeCostumeFemale = 24713; + public const uint SkeletonCostume = 24723; + public const uint GhostCostumeMale = 24735; + public const uint GhostCostumeFemale = 24736; + public const uint TrickBuff = 24753; + + //Trickortreatspells + public const uint Trick = 24714; + public const uint Treat = 24715; + public const uint TrickedOrTreated = 24755; + public const uint TrickyTreatSpeed = 42919; + public const uint TrickyTreatTrigger = 42965; + public const uint UpsetTummy = 42966; + + //Wand Spells + public const uint HallowedWandPirate = 24717; + public const uint HallowedWandNinja = 24718; + public const uint HallowedWandLeperGnome = 24719; + public const uint HallowedWandRandom = 24720; + public const uint HallowedWandSkeleton = 24724; + public const uint HallowedWandWisp = 24733; + public const uint HallowedWandGhost = 24737; + public const uint HallowedWandBat = 24741; + + //Pilgrims Bounty + public const uint WellFedApTrigger = 65414; + public const uint WellFedZmTrigger = 65412; + public const uint WellFedHitTrigger = 65416; + public const uint WellFedHasteTrigger = 65410; + public const uint WellFedSpiritTrigger = 65415; + + //Mistletoe + public const uint CreateMistletoe = 26206; + public const uint CreateHolly = 26207; + public const uint CreateSnowflakes = 45036; + + //Winter Wondervolt + public const uint Px238WinterWondervoltTransform1 = 26157; + public const uint Px238WinterWondervoltTransform2 = 26272; + public const uint Px238WinterWondervoltTransform3 = 26273; + public const uint Px238WinterWondervoltTransform4 = 26274; + + //Ramblabla + public const uint Giddyup = 42924; + public const uint RentalRacingRam = 43883; + public const uint SwiftWorkRam = 43880; + public const uint RentalRacingRamAura = 42146; + public const uint RamLevelNeutral = 43310; + public const uint RamTrot = 42992; + public const uint RamCanter = 42993; + public const uint RamGallop = 42994; + public const uint RamFatigue = 43052; + public const uint ExhaustedRam = 43332; + public const uint RelayRaceTurnIn = 44501; + + //Brazierhit + public const uint TorchTossingTraining = 45716; + public const uint TorchTossingPractice = 46630; + public const uint TorchTossingTrainingSuccessAlliance = 45719; + public const uint TorchTossingTrainingSuccessHorde = 46651; + public const uint BraziersHit = 45724; + + //Ribbonpoledata + public const uint HasFullMidsummerSet = 58933; + public const uint BurningHotPoleDance = 58934; + public const uint RibbonDanceCosmetic = 29726; + public const uint RibbonDance = 29175; + } + + struct QuestIds + { + //Ramblabla + public const uint BrewfestSpeedBunnyGreen = 43345; + public const uint BrewfestSpeedBunnyYellow = 43346; + public const uint BrewfestSpeedBunnyRed = 43347; + + //Barkerbunny + // Horde + public const uint BarkForDrohnsDistillery = 11407; + public const uint BarkForTchalisVoodooBrewery = 11408; + + // Alliance + public const uint BarkBarleybrew = 11293; + public const uint BarkForThunderbrews = 11294; + } + + struct TextIds + { + // Bark For Drohn'S Distillery! + public const uint DrohnDistillery1 = 23520; + public const uint DrohnDistillery2 = 23521; + public const uint DrohnDistillery3 = 23522; + public const uint DrohnDistillery4 = 23523; + + // Bark For T'Chali'S Voodoo Brewery! + public const uint TChalisVoodoo1 = 23524; + public const uint TChalisVoodoo2 = 23525; + public const uint TChalisVoodoo3 = 23526; + public const uint TChalisVoodoo4 = 23527; + + // Bark For The Barleybrews! + public const uint Barleybrew1 = 23464; + public const uint Barleybrew2 = 23465; + public const uint Barleybrew3 = 23466; + public const uint Barleybrew4 = 22941; + + // Bark For The Thunderbrews! + public const uint Thunderbrews1 = 23467; + public const uint Thunderbrews2 = 23468; + public const uint Thunderbrews3 = 23469; + public const uint Thunderbrews4 = 22942; + } + + struct GameobjectIds + { + public const uint RibbonPole = 181605; + } + + + + [Script] // 45102 Romantic Picnic + class spell_love_is_in_the_air_romantic_picnic : SpellScriptLoader + { + public spell_love_is_in_the_air_romantic_picnic() : base("spell_love_is_in_the_air_romantic_picnic") { } + + class spell_love_is_in_the_air_romantic_picnic_AuraScript : AuraScript + { + void OnApply(AuraEffect aurEff, AuraEffectHandleModes mode) + { + Unit target = GetTarget(); + target.SetStandState(UnitStandStateType.Sit); + target.CastSpell(target, SpellIds.MealPeriodic, false); + } + + void OnPeriodic(AuraEffect aurEff) + { + // Every 5 seconds + Unit target = GetTarget(); + Unit caster = GetCaster(); + + // If our player is no longer sit, remove all auras + if (target.GetStandState() != UnitStandStateType.Sit) + { + target.RemoveAura(SpellIds.RomanticPicnicAchiev); + target.RemoveAura(GetAura()); + return; + } + + target.CastSpell(target, SpellIds.BasketCheck, false); // unknown use, it targets Romantic Basket + target.CastSpell(target, RandomHelper.RAND(SpellIds.MealEatVisual, SpellIds.DrinkVisual), false); + + bool foundSomeone = false; + // For nearby players, check if they have the same aura. If so, cast Romantic Picnic (45123) + // required by achievement and "hearts" visual + List playerList = new List(); + AnyPlayerInObjectRangeCheck checker = new AnyPlayerInObjectRangeCheck(target, SharedConst.InteractionDistance * 2); + var searcher = new PlayerListSearcher(target, playerList, checker); + Cell.VisitWorldObjects(target, searcher, SharedConst.InteractionDistance * 2); + foreach (var player in playerList) + { + if (player != target && player.HasAura(GetId())) // && player.GetStandState() == UNIT_STAND_STATE_SIT) + { + if (caster) + { + caster.CastSpell(player, SpellIds.RomanticPicnicAchiev, true); + caster.CastSpell(target, SpellIds.RomanticPicnicAchiev, true); + } + foundSomeone = true; + // break; + } + } + + if (!foundSomeone && target.HasAura(SpellIds.RomanticPicnicAchiev)) + target.RemoveAura(SpellIds.RomanticPicnicAchiev); + } + + public override void Register() + { + AfterEffectApply.Add(new EffectApplyHandler(OnApply, 0, AuraType.PeriodicDummy, AuraEffectHandleModes.Real)); + OnEffectPeriodic.Add(new EffectPeriodicHandler(OnPeriodic, 0, AuraType.PeriodicDummy)); + } + } + + public override AuraScript GetAuraScript() + { + return new spell_love_is_in_the_air_romantic_picnic_AuraScript(); + } + } + + [Script] // 24750 Trick + class spell_hallow_end_trick : SpellScriptLoader + { + public spell_hallow_end_trick() : base("spell_hallow_end_trick") { } + + class spell_hallow_end_trick_SpellScript : SpellScript + { + public override bool Validate(SpellInfo spell) + { + return ValidateSpellInfo(SpellIds.PirateCostumeMale, SpellIds.PirateCostumeFemale, SpellIds.NinjaCostumeMale, SpellIds.NinjaCostumeFemale, + SpellIds.LeperGnomeCostumeMale, SpellIds.LeperGnomeCostumeFemale, SpellIds.SkeletonCostume, SpellIds.GhostCostumeMale, SpellIds.GhostCostumeFemale, SpellIds.TrickBuff); + } + + void HandleScript(uint effIndex) + { + Unit caster = GetCaster(); + Player target = GetHitPlayer(); + if (target) + { + Gender gender = target.GetGender(); + uint spellId = SpellIds.TrickBuff; + switch (RandomHelper.URand(0, 5)) + { + case 1: + spellId = gender == Gender.Female ? SpellIds.LeperGnomeCostumeFemale : SpellIds.LeperGnomeCostumeMale; + break; + case 2: + spellId = gender == Gender.Female ? SpellIds.PirateCostumeFemale : SpellIds.PirateCostumeMale; + break; + case 3: + spellId = gender == Gender.Female ? SpellIds.GhostCostumeFemale : SpellIds.GhostCostumeMale; + break; + case 4: + spellId = gender == Gender.Female ? SpellIds.NinjaCostumeFemale : SpellIds.NinjaCostumeMale; + break; + case 5: + spellId = SpellIds.SkeletonCostume; + break; + default: + break; + } + + caster.CastSpell(target, spellId, true); + } + } + + public override void Register() + { + OnEffectHitTarget.Add(new EffectHandler(HandleScript, 0, SpellEffectName.ScriptEffect)); + } + } + + public override SpellScript GetSpellScript() + { + return new spell_hallow_end_trick_SpellScript(); + } + } + + [Script] // 24751 Trick or Treat + class spell_hallow_end_trick_or_treat : SpellScriptLoader + { + public spell_hallow_end_trick_or_treat() : base("spell_hallow_end_trick_or_treat") { } + + class spell_hallow_end_trick_or_treat_SpellScript : SpellScript + { + public override bool Validate(SpellInfo spell) + { + return ValidateSpellInfo(SpellIds.Trick, SpellIds.Treat, SpellIds.TrickedOrTreated); + } + + void HandleScript(uint effIndex) + { + Unit caster = GetCaster(); + Player target = GetHitPlayer(); + if (target) + { + caster.CastSpell(target, RandomHelper.randChance(50) ? SpellIds.Trick : SpellIds.Treat, true); + caster.CastSpell(target, SpellIds.TrickedOrTreated, true); + } + } + + public override void Register() + { + OnEffectHitTarget.Add(new EffectHandler(HandleScript, 0, SpellEffectName.ScriptEffect)); + } + } + + public override SpellScript GetSpellScript() + { + return new spell_hallow_end_trick_or_treat_SpellScript(); + } + } + + [Script] + class spell_hallow_end_tricky_treat : SpellScriptLoader + { + public spell_hallow_end_tricky_treat() : base("spell_hallow_end_tricky_treat") { } + + class spell_hallow_end_tricky_treat_SpellScript : SpellScript + { + public override bool Validate(SpellInfo spell) + { + return ValidateSpellInfo(SpellIds.TrickyTreatSpeed, SpellIds.TrickyTreatTrigger, SpellIds.UpsetTummy); + } + + void HandleScript(uint effIndex) + { + Unit caster = GetCaster(); + if (caster.HasAura(SpellIds.TrickyTreatTrigger) && caster.GetAuraCount(SpellIds.TrickyTreatSpeed) > 3 && RandomHelper.randChance(33)) + caster.CastSpell(caster, SpellIds.UpsetTummy, true); + } + + public override void Register() + { + OnEffectHitTarget.Add(new EffectHandler(HandleScript, 0, SpellEffectName.ScriptEffect)); + } + } + + public override SpellScript GetSpellScript() + { + return new spell_hallow_end_tricky_treat_SpellScript(); + } + } + + [Script] + class spell_hallow_end_wand : SpellScriptLoader + { + public spell_hallow_end_wand() : base("spell_hallow_end_wand") { } + + class spell_hallow_end_wand_SpellScript : SpellScript + { + public override bool Validate(SpellInfo spellEntry) + { + return ValidateSpellInfo(SpellIds.PirateCostumeMale, SpellIds.PirateCostumeFemale, SpellIds.NinjaCostumeMale, SpellIds.NinjaCostumeFemale, + SpellIds.LeperGnomeCostumeMale, SpellIds.LeperGnomeCostumeFemale, SpellIds.GhostCostumeMale, SpellIds.GhostCostumeFemale); + } + + void HandleScriptEffect() + { + Unit caster = GetCaster(); + Unit target = GetHitUnit(); + + uint spellId = 0; + bool female = target.GetGender() == Gender.Female; + + switch (GetSpellInfo().Id) + { + case SpellIds.HallowedWandLeperGnome: + spellId = female ? SpellIds.LeperGnomeCostumeFemale : SpellIds.LeperGnomeCostumeMale; + break; + case SpellIds.HallowedWandPirate: + spellId = female ? SpellIds.PirateCostumeFemale : SpellIds.PirateCostumeMale; + break; + case SpellIds.HallowedWandGhost: + spellId = female ? SpellIds.GhostCostumeFemale : SpellIds.GhostCostumeMale; + break; + case SpellIds.HallowedWandNinja: + spellId = female ? SpellIds.NinjaCostumeFemale : SpellIds.NinjaCostumeMale; + break; + case SpellIds.HallowedWandRandom: + spellId = RandomHelper.RAND(SpellIds.HallowedWandPirate, SpellIds.HallowedWandNinja, SpellIds.HallowedWandLeperGnome, SpellIds.HallowedWandSkeleton, SpellIds.HallowedWandWisp, SpellIds.HallowedWandGhost, SpellIds.HallowedWandBat); + break; + default: + return; + } + caster.CastSpell(target, spellId, true); + } + + public override void Register() + { + AfterHit.Add(new HitHandler(HandleScriptEffect)); + } + } + + public override SpellScript GetSpellScript() + { + return new spell_hallow_end_wand_SpellScript(); + } + } + + [Script("spell_gen_slow_roasted_turkey", SpellIds.WellFedApTrigger)] + [Script("spell_gen_cranberry_chutney", SpellIds.WellFedZmTrigger)] + [Script("spell_gen_spice_bread_stuffing", SpellIds.WellFedHitTrigger)] + [Script("spell_gen_pumpkin_pie", SpellIds.WellFedSpiritTrigger)] + [Script("spell_gen_candied_sweet_potato", SpellIds.WellFedHasteTrigger)] + class spell_pilgrims_bounty_buff_food : SpellScriptLoader + { + public spell_pilgrims_bounty_buff_food(string name, uint triggeredSpellId) : base(name) + { + _triggeredSpellId = triggeredSpellId; + } + + class spell_pilgrims_bounty_buff_food_AuraScript : AuraScript + { + public spell_pilgrims_bounty_buff_food_AuraScript(uint triggeredSpellId) : base() + { + _triggeredSpellId = triggeredSpellId; + _handled = false; + } + + void HandleTriggerSpell(AuraEffect aurEff) + { + PreventDefaultAction(); + if (_handled) + return; + + _handled = true; + GetTarget().CastSpell(GetTarget(), _triggeredSpellId, true); + } + + public override void Register() + { + OnEffectPeriodic.Add(new EffectPeriodicHandler(HandleTriggerSpell, 2, AuraType.PeriodicTriggerSpell)); + } + + uint _triggeredSpellId; + + bool _handled; + } + + public override AuraScript GetAuraScript() + { + return new spell_pilgrims_bounty_buff_food_AuraScript(_triggeredSpellId); + } + + uint _triggeredSpellId; + } + + [Script] + class spell_winter_veil_mistletoe : SpellScriptLoader + { + public spell_winter_veil_mistletoe() : base("spell_winter_veil_mistletoe") { } + + class spell_winter_veil_mistletoe_SpellScript : SpellScript + { + public override bool Validate(SpellInfo spell) + { + return ValidateSpellInfo(SpellIds.CreateMistletoe, SpellIds.CreateHolly, SpellIds.CreateSnowflakes); + } + + void HandleScript(uint effIndex) + { + Player target = GetHitPlayer(); + if (target) + { + uint spellId = RandomHelper.RAND(SpellIds.CreateHolly, SpellIds.CreateMistletoe, SpellIds.CreateSnowflakes); + GetCaster().CastSpell(target, spellId, true); + } + } + + public override void Register() + { + OnEffectHitTarget.Add(new EffectHandler(HandleScript, 0, SpellEffectName.ScriptEffect)); + } + } + + public override SpellScript GetSpellScript() + { + return new spell_winter_veil_mistletoe_SpellScript(); + } + } + + [Script] // 26275 - PX-238 Winter Wondervolt TRAP + class spell_winter_veil_px_238_winter_wondervolt : SpellScriptLoader + { + public spell_winter_veil_px_238_winter_wondervolt() : base("spell_winter_veil_px_238_winter_wondervolt") { } + + class spell_winter_veil_px_238_winter_wondervolt_SpellScript : SpellScript + { + public override bool Validate(SpellInfo spellInfo) + { + return ValidateSpellInfo(SpellIds.Px238WinterWondervoltTransform1, SpellIds.Px238WinterWondervoltTransform2, + SpellIds.Px238WinterWondervoltTransform3, SpellIds.Px238WinterWondervoltTransform4); + } + + void HandleScript(uint effIndex) + { + PreventHitDefaultEffect(effIndex); + + uint[] spells = + { + SpellIds.Px238WinterWondervoltTransform1, + SpellIds.Px238WinterWondervoltTransform2, + SpellIds.Px238WinterWondervoltTransform3, + SpellIds.Px238WinterWondervoltTransform4 + }; + + Unit target = GetHitUnit(); + if (target) + { + for (byte i = 0; i < 4; ++i) + if (target.HasAura(spells[i])) + return; + + target.CastSpell(target, spells[RandomHelper.URand(0, 3)], true); + } + } + + public override void Register() + { + OnEffectHitTarget.Add(new EffectHandler(HandleScript, 0, SpellEffectName.ScriptEffect)); + } + } + + public override SpellScript GetSpellScript() + { + return new spell_winter_veil_px_238_winter_wondervolt_SpellScript(); + } + } + + [Script] // 42924 - Giddyup! + class spell_brewfest_giddyup : SpellScriptLoader + { + public spell_brewfest_giddyup() : base("spell_brewfest_giddyup") { } + + class spell_brewfest_giddyup_AuraScript : AuraScript + { + void OnChange(AuraEffect aurEff, AuraEffectHandleModes mode) + { + Unit target = GetTarget(); + if (!target.HasAura(SpellIds.RentalRacingRam) && !target.HasAura(SpellIds.SwiftWorkRam)) + { + target.RemoveAura(GetId()); + return; + } + + if (target.HasAura(SpellIds.ExhaustedRam)) + return; + + switch (GetStackAmount()) + { + case 1: // green + target.RemoveAura(SpellIds.RamLevelNeutral); + target.RemoveAura(SpellIds.RamCanter); + target.CastSpell(target, SpellIds.RamTrot, true); + break; + case 6: // yellow + target.RemoveAura(SpellIds.RamTrot); + target.RemoveAura(SpellIds.RamGallop); + target.CastSpell(target, SpellIds.RamCanter, true); + break; + case 11: // red + target.RemoveAura(SpellIds.RamCanter); + target.CastSpell(target, SpellIds.RamGallop, true); + break; + default: + break; + } + + if (GetTargetApplication().GetRemoveMode() == AuraRemoveMode.Default) + { + target.RemoveAura(SpellIds.RamTrot); + target.CastSpell(target, SpellIds.RamLevelNeutral, true); + } + } + + void OnPeriodic(AuraEffect aurEff) + { + GetTarget().RemoveAuraFromStack(GetId()); + } + + public override void Register() + { + AfterEffectApply.Add(new EffectApplyHandler(OnChange, 0, AuraType.PeriodicDummy, AuraEffectHandleModes.ChangeAmountMask)); + OnEffectRemove.Add(new EffectApplyHandler(OnChange, 0, AuraType.PeriodicDummy, AuraEffectHandleModes.ChangeAmountMask)); + OnEffectPeriodic.Add(new EffectPeriodicHandler(OnPeriodic, 0, AuraType.PeriodicDummy)); + } + } + + public override AuraScript GetAuraScript() + { + return new spell_brewfest_giddyup_AuraScript(); + } + } + + // 43310 - Ram Level - Neutral + // 42992 - Ram - Trot + // 42993 - Ram - Canter + // 42994 - Ram - Gallop + [Script] + class spell_brewfest_ram : SpellScriptLoader + { + public spell_brewfest_ram() : base("spell_brewfest_ram") { } + + class spell_brewfest_ram_AuraScript : AuraScript + { + void OnPeriodic(AuraEffect aurEff) + { + Unit target = GetTarget(); + if (target.HasAura(SpellIds.ExhaustedRam)) + return; + + switch (GetId()) + { + case SpellIds.RamLevelNeutral: + { + Aura aura = target.GetAura(SpellIds.RamFatigue); + if (aura != null) + aura.ModStackAmount(-4); + } + break; + case SpellIds.RamTrot: // green + { + Aura aura = target.GetAura(SpellIds.RamFatigue); + if (aura != null) + aura.ModStackAmount(-2); + if (aurEff.GetTickNumber() == 4) + target.CastSpell(target, QuestIds.BrewfestSpeedBunnyGreen, true); + } + break; + case SpellIds.RamCanter: + target.CastCustomSpell(SpellIds.RamFatigue, SpellValueMod.AuraStack, 1, target, TriggerCastFlags.FullMask); + if (aurEff.GetTickNumber() == 8) + target.CastSpell(target, QuestIds.BrewfestSpeedBunnyYellow, true); + break; + case SpellIds.RamGallop: + target.CastCustomSpell(SpellIds.RamFatigue, SpellValueMod.AuraStack, target.HasAura(SpellIds.RamFatigue) ? 4 : 5 /*Hack*/, target, TriggerCastFlags.FullMask); + if (aurEff.GetTickNumber() == 8) + target.CastSpell(target, QuestIds.BrewfestSpeedBunnyRed, true); + break; + default: + break; + } + + } + + public override void Register() + { + OnEffectPeriodic.Add(new EffectPeriodicHandler(OnPeriodic, 1, AuraType.PeriodicDummy)); + } + } + + public override AuraScript GetAuraScript() + { + return new spell_brewfest_ram_AuraScript(); + } + } + + [Script] // 43052 - Ram Fatigue + class spell_brewfest_ram_fatigue : SpellScriptLoader + { + public spell_brewfest_ram_fatigue() : base("spell_brewfest_ram_fatigue") { } + + class spell_brewfest_ram_fatigue_AuraScript : AuraScript + { + void OnApply(AuraEffect aurEff, AuraEffectHandleModes mode) + { + Unit target = GetTarget(); + + if (GetStackAmount() == 101) + { + target.RemoveAura(SpellIds.RamLevelNeutral); + target.RemoveAura(SpellIds.RamTrot); + target.RemoveAura(SpellIds.RamCanter); + target.RemoveAura(SpellIds.RamGallop); + target.RemoveAura(SpellIds.Giddyup); + + target.CastSpell(target, SpellIds.ExhaustedRam, true); + } + } + + public override void Register() + { + AfterEffectApply.Add(new EffectApplyHandler(OnApply, 0, AuraType.Dummy, AuraEffectHandleModes.RealOrReapplyMask)); + } + } + + public override AuraScript GetAuraScript() + { + return new spell_brewfest_ram_fatigue_AuraScript(); + } + } + + [Script] // 43450 - Brewfest - apple trap - friendly DND + class spell_brewfest_apple_trap : SpellScriptLoader + { + public spell_brewfest_apple_trap() : base("spell_brewfest_apple_trap") { } + + class spell_brewfest_apple_trap_AuraScript : AuraScript + { + void OnApply(AuraEffect aurEff, AuraEffectHandleModes mode) + { + GetTarget().RemoveAura(SpellIds.RamFatigue); + } + + public override void Register() + { + OnEffectApply.Add(new EffectApplyHandler(OnApply, 0, AuraType.ForceReaction, AuraEffectHandleModes.Real)); + } + } + + public override AuraScript GetAuraScript() + { + return new spell_brewfest_apple_trap_AuraScript(); + } + } + + [Script] // 43332 - Exhausted Ram + class spell_brewfest_exhausted_ram : SpellScriptLoader + { + public spell_brewfest_exhausted_ram() : base("spell_brewfest_exhausted_ram") { } + + class spell_brewfest_exhausted_ram_AuraScript : AuraScript + { + void OnRemove(AuraEffect aurEff, AuraEffectHandleModes mode) + { + Unit target = GetTarget(); + target.CastSpell(target, SpellIds.RamLevelNeutral, true); + } + + public override void Register() + { + OnEffectRemove.Add(new EffectApplyHandler(OnRemove, 0, AuraType.ModDecreaseSpeed, AuraEffectHandleModes.Real)); + } + } + + public override AuraScript GetAuraScript() + { + return new spell_brewfest_exhausted_ram_AuraScript(); + } + } + + [Script] // 43714 - Brewfest - Relay Race - Intro - Force - Player to throw- DND + class spell_brewfest_relay_race_intro_force_player_to_throw : SpellScriptLoader + { + public spell_brewfest_relay_race_intro_force_player_to_throw() : base("spell_brewfest_relay_race_intro_force_player_to_throw") { } + + class spell_brewfest_relay_race_intro_force_player_to_throw_SpellScript : SpellScript + { + void HandleForceCast(uint effIndex) + { + PreventHitDefaultEffect(effIndex); + // All this spells trigger a spell that requires reagents; if the + // triggered spell is cast as "triggered", reagents are not consumed + GetHitUnit().CastSpell(null, GetSpellInfo().GetEffect(effIndex).TriggerSpell, TriggerCastFlags.FullMask & ~TriggerCastFlags.IgnorePowerAndReagentCost); + } + + public override void Register() + { + OnEffectHitTarget.Add(new EffectHandler(HandleForceCast, 0, SpellEffectName.ForceCast)); + } + } + + public override SpellScript GetSpellScript() + { + return new spell_brewfest_relay_race_intro_force_player_to_throw_SpellScript(); + } + } + + [Script] + class spell_brewfest_relay_race_turn_in : SpellScriptLoader + { + public spell_brewfest_relay_race_turn_in() : base("spell_brewfest_relay_race_turn_in") { } + + class spell_brewfest_relay_race_turn_in_SpellScript : SpellScript + { + void HandleDummy(uint effIndex) + { + PreventHitDefaultEffect(effIndex); + + Aura aura = GetHitUnit().GetAura(SpellIds.SwiftWorkRam); + if (aura != null) + { + aura.SetDuration(aura.GetDuration() + 30 * Time.InMilliseconds); + GetCaster().CastSpell(GetHitUnit(), SpellIds.RelayRaceTurnIn, TriggerCastFlags.FullMask); + } + } + + public override void Register() + { + OnEffectHitTarget.Add(new EffectHandler(HandleDummy, 0, SpellEffectName.Dummy)); + } + } + + public override SpellScript GetSpellScript() + { + return new spell_brewfest_relay_race_turn_in_SpellScript(); + } + } + + [Script] // 43876 - Dismount Ram + class spell_brewfest_dismount_ram : SpellScriptLoader + { + public spell_brewfest_dismount_ram() : base("spell_brewfest_dismount_ram") { } + + class spell_brewfest_relay_race_intro_force_player_to_throw_SpellScript : SpellScript + { + void HandleScript(uint effIndex) + { + GetCaster().RemoveAura(SpellIds.RentalRacingRam); + } + + public override void Register() + { + OnEffectHitTarget.Add(new EffectHandler(HandleScript, 0, SpellEffectName.ScriptEffect)); + } + } + + public override SpellScript GetSpellScript() + { + return new spell_brewfest_relay_race_intro_force_player_to_throw_SpellScript(); + } + } + + // 43259 Brewfest - Barker Bunny 1 + // 43260 Brewfest - Barker Bunny 2 + // 43261 Brewfest - Barker Bunny 3 + // 43262 Brewfest - Barker Bunny 4 + [Script] + class spell_brewfest_barker_bunny : SpellScriptLoader + { + public spell_brewfest_barker_bunny() : base("spell_brewfest_barker_bunny") { } + + class spell_brewfest_barker_bunny_AuraScript : AuraScript + { + public override bool Load() + { + return GetUnitOwner().IsTypeId(TypeId.Player); + } + + void OnApply(AuraEffect aurEff, AuraEffectHandleModes mode) + { + Player target = GetTarget().ToPlayer(); + + uint BroadcastTextId = 0; + + if (target.GetQuestStatus(QuestIds.BarkForDrohnsDistillery) == QuestStatus.Incomplete || + target.GetQuestStatus(QuestIds.BarkForDrohnsDistillery) == QuestStatus.Complete) + BroadcastTextId = RandomHelper.RAND(TextIds.DrohnDistillery1, TextIds.DrohnDistillery2, TextIds.DrohnDistillery3, TextIds.DrohnDistillery4); + + if (target.GetQuestStatus(QuestIds.BarkForTchalisVoodooBrewery) == QuestStatus.Incomplete || + target.GetQuestStatus(QuestIds.BarkForTchalisVoodooBrewery) == QuestStatus.Complete) + BroadcastTextId = RandomHelper.RAND(TextIds.TChalisVoodoo1, TextIds.TChalisVoodoo2, TextIds.TChalisVoodoo3, TextIds.TChalisVoodoo4); + + if (target.GetQuestStatus(QuestIds.BarkBarleybrew) == QuestStatus.Incomplete || + target.GetQuestStatus(QuestIds.BarkBarleybrew) == QuestStatus.Complete) + BroadcastTextId = RandomHelper.RAND(TextIds.Barleybrew1, TextIds.Barleybrew2, TextIds.Barleybrew3, TextIds.Barleybrew4); + + if (target.GetQuestStatus(QuestIds.BarkForThunderbrews) == QuestStatus.Incomplete || + target.GetQuestStatus(QuestIds.BarkForThunderbrews) == QuestStatus.Complete) + BroadcastTextId = RandomHelper.RAND(TextIds.Thunderbrews1, TextIds.Thunderbrews2, TextIds.Thunderbrews3, TextIds.Thunderbrews4); + + if (BroadcastTextId != 0) + target.Talk(BroadcastTextId, ChatMsg.Say, WorldConfig.GetFloatValue(WorldCfg.ListenRangeSay), target); + } + + public override void Register() + { + OnEffectApply.Add(new EffectApplyHandler(OnApply, 1, AuraType.Dummy, AuraEffectHandleModes.Real)); + } + } + + public override AuraScript GetAuraScript() + { + return new spell_brewfest_barker_bunny_AuraScript(); + } + } + + + [Script] // 45724 - Braziers Hit! + class spell_midsummer_braziers_hit : SpellScriptLoader + { + public spell_midsummer_braziers_hit() : base("spell_midsummer_braziers_hit") { } + + class spell_midsummer_braziers_hit_AuraScript : AuraScript + { + public override bool Validate(SpellInfo spellInfo) + { + return ValidateSpellInfo(SpellIds.TorchTossingTraining, SpellIds.TorchTossingPractice); + } + + void HandleEffectApply(AuraEffect aurEff, AuraEffectHandleModes mode) + { + Player player = GetTarget().ToPlayer(); + if (!player) + return; + + if ((player.HasAura(SpellIds.TorchTossingTraining) && GetStackAmount() == 8) || (player.HasAura(SpellIds.TorchTossingPractice) && GetStackAmount() == 20)) + { + if (player.GetTeam() == Team.Alliance) + player.CastSpell(player, SpellIds.TorchTossingTrainingSuccessAlliance, true); + else if (player.GetTeam() == Team.Horde) + player.CastSpell(player, SpellIds.TorchTossingTrainingSuccessHorde, true); + Remove(); + } + } + + public override void Register() + { + AfterEffectApply.Add(new EffectApplyHandler(HandleEffectApply, 0, AuraType.Dummy, AuraEffectHandleModes.Reapply)); + } + } + + public override AuraScript GetAuraScript() + { + return new spell_midsummer_braziers_hit_AuraScript(); + } + } + + [Script] + class spell_gen_ribbon_pole_dancer_check : SpellScriptLoader + { + public spell_gen_ribbon_pole_dancer_check() : base("spell_gen_ribbon_pole_dancer_check") { } + + class spell_gen_ribbon_pole_dancer_check_AuraScript : AuraScript + { + public override bool Validate(SpellInfo spellInfo) + { + return ValidateSpellInfo(SpellIds.HasFullMidsummerSet, SpellIds.RibbonDance, SpellIds.BurningHotPoleDance); + } + + void PeriodicTick(AuraEffect aurEff) + { + Unit target = GetTarget(); + + // check if aura needs to be removed + if (!target.FindNearestGameObject(GameobjectIds.RibbonPole, 8.0f) || !target.HasUnitState(UnitState.Casting)) + { + target.InterruptNonMeleeSpells(false); + target.RemoveAurasDueToSpell(GetId()); + target.RemoveAura(SpellIds.RibbonDanceCosmetic); + return; + } + + // set xp buff duration + Aura aur = target.GetAura(SpellIds.RibbonDance); + if (aur != null) + { + aur.SetMaxDuration(Math.Min(3600000, aur.GetMaxDuration() + 180000)); + aur.RefreshDuration(); + + // reward achievement criteria + if (aur.GetMaxDuration() == 3600000 && target.HasAura(SpellIds.HasFullMidsummerSet)) + target.CastSpell(target, SpellIds.BurningHotPoleDance, true); + } + else + target.AddAura(SpellIds.RibbonDance, target); + } + + public override void Register() + { + OnEffectPeriodic.Add(new EffectPeriodicHandler(PeriodicTick, 0, AuraType.PeriodicDummy)); + } + } + + public override AuraScript GetAuraScript() + { + return new spell_gen_ribbon_pole_dancer_check_AuraScript(); + } + } +} \ No newline at end of file diff --git a/Scripts/Spells/Hunter.cs b/Scripts/Spells/Hunter.cs new file mode 100644 index 000000000..a08d7ce6b --- /dev/null +++ b/Scripts/Spells/Hunter.cs @@ -0,0 +1,1049 @@ +/* + * Copyright (C) 2012-2017 CypherCore + * + * 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, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +using Framework.Constants; +using Game.Entities; +using Game.Maps; +using Game.Scripting; +using Game.Spells; +using System; +using System.Collections.Generic; + +namespace Scripts.Spells.Hunter +{ + struct SpellIds + { + public const uint AspectCheetahSlow = 186258; + public const uint BestialWrath = 19574; + public const uint ChimeraShotHeal = 53353; + public const uint Exhilaration = 109304; + public const uint ExhilarationPet = 128594; + public const uint ExhilarationR2 = 231546; + public const uint Fire = 82926; + public const uint GenericEnergizeFocus = 91954; + public const uint ImprovedMendPet = 24406; + public const uint LockAndLoad = 56453; + public const uint Lonewolf = 155228; + public const uint MastersCallTriggered = 62305; + public const uint MisdirectionProc = 35079; + public const uint PetLastStandTriggered = 53479; + public const uint PetHeartOfThePhoenix = 55709; + public const uint PetHeartOfThePhoenixTriggered = 54114; + public const uint PetHeartOfThePhoenixDebuff = 55711; + public const uint PetCarrionFeederTriggered = 54045; + public const uint Readiness = 23989; + public const uint SerpentSting = 1978; + public const uint SniperTrainingR1 = 53302; + public const uint SniperTrainingBuffR1 = 64418; + public const uint SteadyShotFocus = 77443; + public const uint T94PGreatness = 68130; + public const uint DraeneiGiftOfTheNaaru = 59543; + public const uint RoarOfSacrificeTriggered = 67481; + } + + [Script] // 186257 - Aspect of the Cheetah + class spell_hun_aspect_cheetah : SpellScriptLoader + { + public spell_hun_aspect_cheetah() : base("spell_hun_aspect_cheetah") { } + + class spell_hun_aspect_cheetah_AuraScript : AuraScript + { + public override bool Validate(SpellInfo spellInfo) + { + return ValidateSpellInfo(SpellIds.AspectCheetahSlow); + } + + void HandleOnRemove(AuraEffect aurEff, AuraEffectHandleModes mode) + { + if (GetTargetApplication().GetRemoveMode() == AuraRemoveMode.Expire) + GetTarget().CastSpell(GetTarget(), SpellIds.AspectCheetahSlow, true); + } + + public override void Register() + { + AfterEffectRemove.Add(new EffectApplyHandler(HandleOnRemove, 0, AuraType.ModIncreaseSpeed, AuraEffectHandleModes.Real)); + } + } + + public override AuraScript GetAuraScript() + { + return new spell_hun_aspect_cheetah_AuraScript(); + } + } + + // 53209 - Chimera Shot + [Script] + class HunterChimeraShot : SpellScriptLoader + { + public HunterChimeraShot() : base("spell_hun_chimera_shot") { } + + class spell_hun_chimera_shot_SpellScript : SpellScript + { + public override bool Validate(SpellInfo spellInfo) + { + return ValidateSpellInfo(SpellIds.ChimeraShotHeal, SpellIds.SerpentSting); + } + + public override bool Load() + { + return GetCaster().IsTypeId(TypeId.Player); + } + + void HandleScriptEffect(uint effIndex) + { + GetCaster().CastSpell(GetCaster(), SpellIds.ChimeraShotHeal, true); + Aura aur = GetHitUnit().GetAura(SpellIds.SerpentSting, GetCaster().GetGUID()); + if (aur != null) + aur.SetDuration(aur.GetSpellInfo().GetMaxDuration(), true); + } + + public override void Register() + { + OnEffectHitTarget.Add(new EffectHandler(HandleScriptEffect, 0, SpellEffectName.ScriptEffect)); + } + } + + public override SpellScript GetSpellScript() + { + return new spell_hun_chimera_shot_SpellScript(); + } + } + + // 77767 - Cobra Shot + [Script] + class spell_hun_cobra_shot : SpellScriptLoader + { + public spell_hun_cobra_shot() + : base("spell_hun_cobra_shot") + { } + + class spell_hun_cobra_shot_SpellScript : SpellScript + { + public override bool Validate(SpellInfo spellInfo) + { + return ValidateSpellInfo(SpellIds.GenericEnergizeFocus, SpellIds.SerpentSting); + } + + public override bool Load() + { + return GetCaster().IsTypeId(TypeId.Player); + } + + void HandleScriptEffect(uint effIndex) + { + GetCaster().CastSpell(GetCaster(), SpellIds.GenericEnergizeFocus, true); + Aura aur = GetHitUnit().GetAura(SpellIds.SerpentSting, GetCaster().GetGUID()); + if (aur != null) + { + int newDuration = aur.GetDuration() + GetEffectValue() * Time.InMilliseconds; + aur.SetDuration(Math.Min(newDuration, aur.GetMaxDuration()), true); + } + } + + public override void Register() + { + OnEffectHitTarget.Add(new EffectHandler(HandleScriptEffect, 1, SpellEffectName.ScriptEffect)); + } + } + + public override SpellScript GetSpellScript() + { + return new spell_hun_cobra_shot_SpellScript(); + } + } + + // 781 - Disengage + [Script] + class spell_hun_disengage : SpellScriptLoader + { + public spell_hun_disengage() + : base("spell_hun_disengage") + { } + + class spell_hun_disengage_SpellScript : SpellScript + { + SpellCastResult CheckCast() + { + Unit caster = GetCaster(); + if (caster.IsTypeId(TypeId.Player) && !caster.IsInCombat()) + return SpellCastResult.CantDoThatRightNow; + + return SpellCastResult.SpellCastOk; + } + + public override void Register() + { + OnCheckCast.Add(new CheckCastHandler(CheckCast)); + } + } + + public override SpellScript GetSpellScript() + { + return new spell_hun_disengage_SpellScript(); + } + } + + // 82926 - Fire! + [Script] + class spell_hun_fire : SpellScriptLoader + { + public spell_hun_fire() + : base("spell_hun_fire") + { } + + class spell_hun_fire_AuraScript : AuraScript + { + void HandleEffectCalcSpellMod(AuraEffect aurEff, ref SpellModifier spellMod) + { + if (spellMod == null) + { + spellMod = new SpellModifier(GetAura()); + spellMod.op = SpellModOp.CastingTime; + spellMod.type = SpellModType.Pct; + spellMod.spellId = GetId(); + spellMod.mask = GetSpellInfo().GetEffect(aurEff.GetEffIndex()).SpellClassMask; + } + + spellMod.value = -aurEff.GetAmount(); + } + + public override void Register() + { + DoEffectCalcSpellMod.Add(new EffectCalcSpellModHandler(HandleEffectCalcSpellMod, 0, AuraType.Dummy)); + } + } + + public override AuraScript GetAuraScript() + { + return new spell_hun_fire_AuraScript(); + } + } + + [Script] // 109304 - Exhilaration + class spell_hun_exhilaration : SpellScriptLoader + { + public spell_hun_exhilaration() : base("spell_hun_exhilaration") { } + + class spell_hun_exhilaration_SpellScript : SpellScript + { + public override bool Validate(SpellInfo spellInfo) + { + return ValidateSpellInfo(SpellIds.ExhilarationR2, SpellIds.Lonewolf); + } + + void HandleOnHit() + { + if (GetCaster().HasAura(SpellIds.ExhilarationR2) && !GetCaster().HasAura(SpellIds.Lonewolf)) + GetCaster().CastSpell((Unit)null, SpellIds.ExhilarationPet, true); + } + + public override void Register() + { + OnHit.Add(new HitHandler(HandleOnHit)); + } + } + + public override SpellScript GetSpellScript() + { + return new spell_hun_exhilaration_SpellScript(); + } + } + + [Script] // 212658 - Hunting Party + class spell_hun_hunting_party : SpellScriptLoader + { + public spell_hun_hunting_party() : base("spell_hun_hunting_party") { } + + class spell_hun_hunting_party_AuraScript : AuraScript + { + public override bool Validate(SpellInfo spellInfo) + { + return ValidateSpellInfo(SpellIds.Exhilaration, SpellIds.ExhilarationPet); + } + + void HandleProc(AuraEffect aurEff, ProcEventInfo eventInfo) + { + PreventDefaultAction(); + GetTarget().GetSpellHistory().ModifyCooldown(SpellIds.Exhilaration, -TimeSpan.FromSeconds(aurEff.GetAmount())); + GetTarget().GetSpellHistory().ModifyCooldown(SpellIds.ExhilarationPet, -TimeSpan.FromSeconds(aurEff.GetAmount())); + } + + public override void Register() + { + OnEffectProc.Add(new EffectProcHandler(HandleProc, 0, AuraType.Dummy)); + } + } + + public override AuraScript GetAuraScript() + { + return new spell_hun_hunting_party_AuraScript(); + } + } + + // -19572 - Improved Mend Pet + [Script] + class spell_hun_improved_mend_pet : SpellScriptLoader + { + public spell_hun_improved_mend_pet() : base("spell_hun_improved_mend_pet") { } + + class spell_hun_improved_mend_pet_AuraScript : AuraScript + { + public override bool Validate(SpellInfo spellInfo) + { + return ValidateSpellInfo(SpellIds.ImprovedMendPet); + } + + bool CheckProc(ProcEventInfo eventInfo) + { + return RandomHelper.randChance(GetEffect(0).GetAmount()); + } + + void HandleProc(AuraEffect aurEff, ProcEventInfo eventInfo) + { + PreventDefaultAction(); + GetTarget().CastSpell(GetTarget(), SpellIds.ImprovedMendPet, true, null, aurEff); + } + + public override void Register() + { + DoCheckProc.Add(new CheckProcHandler(CheckProc)); + OnEffectProc.Add(new EffectProcHandler(HandleProc, 0, AuraType.Dummy)); + } + } + + public override AuraScript GetAuraScript() + { + return new spell_hun_improved_mend_pet_AuraScript(); + } + } + + // -19464 Improved Serpent Sting + [Script] + class spell_hun_improved_serpent_sting : SpellScriptLoader + { + public spell_hun_improved_serpent_sting() + : base("spell_hun_improved_serpent_sting") + { } + + class spell_hun_improved_serpent_sting_AuraScript : AuraScript + { + void HandleEffectCalcSpellMod(AuraEffect aurEff, ref SpellModifier spellMod) + { + if (spellMod == null) + { + spellMod = new SpellModifier(GetAura()); + spellMod.op = (SpellModOp)aurEff.GetMiscValue(); + spellMod.type = SpellModType.Pct; + spellMod.spellId = GetId(); + spellMod.mask = GetSpellInfo().GetEffect(aurEff.GetEffIndex()).SpellClassMask; + } + + spellMod.value = aurEff.GetAmount(); + } + + public override void Register() + { + DoEffectCalcSpellMod.Add(new EffectCalcSpellModHandler(HandleEffectCalcSpellMod, 0, AuraType.Dummy)); + } + } + + public override AuraScript GetAuraScript() + { + return new spell_hun_improved_serpent_sting_AuraScript(); + } + } + + // 53478 - Last Stand Pet + [Script] + class spell_hun_last_stand_pet : SpellScriptLoader + { + public spell_hun_last_stand_pet() + : base("spell_hun_last_stand_pet") + { } + + class spell_hun_last_stand_pet_SpellScript : SpellScript + { + public override bool Validate(SpellInfo spellInfo) + { + return ValidateSpellInfo(SpellIds.PetLastStandTriggered); + } + + void HandleDummy(uint effIndex) + { + Unit caster = GetCaster(); + int healthModSpellBasePoints0 = (int)caster.CountPctFromMaxHealth(30); + caster.CastCustomSpell(caster, SpellIds.PetLastStandTriggered, healthModSpellBasePoints0, 0, 0, true, null); + } + + public override void Register() + { + OnEffectHitTarget.Add(new EffectHandler(HandleDummy, 0, SpellEffectName.Dummy)); + } + } + + public override SpellScript GetSpellScript() + { + return new spell_hun_last_stand_pet_SpellScript(); + } + } + + // 53271 - Masters Call + [Script] + class spell_hun_masters_call : SpellScriptLoader + { + public spell_hun_masters_call() + : base("spell_hun_masters_call") + { } + + class spell_hun_masters_call_SpellScript : SpellScript + { + public override bool Validate(SpellInfo spellInfo) + { + return ValidateSpellInfo(SpellIds.MastersCallTriggered, (uint)spellInfo.GetEffect(0).CalcValue()); + } + + void HandleDummy(uint effIndex) + { + Unit ally = GetHitUnit(); + if (ally) + { + Player caster = GetCaster().ToPlayer(); + if (caster) + { + Pet target = caster.GetPet(); + if (target) + { + TriggerCastFlags castMask = (TriggerCastFlags.FullMask & ~TriggerCastFlags.IgnoreCasterAurastate); + target.CastSpell(ally, (uint)GetEffectValue(), castMask); + } + } + } + } + + void HandleScriptEffect(uint effIndex) + { + Unit target = GetHitUnit(); + if (target) + { + // Cannot be processed while pet is dead + TriggerCastFlags castMask = (TriggerCastFlags.FullMask & ~TriggerCastFlags.IgnoreCasterAurastate); + target.CastSpell(target, SpellIds.MastersCallTriggered, castMask); + } + } + + public override void Register() + { + OnEffectHitTarget.Add(new EffectHandler(HandleDummy, 0, SpellEffectName.Dummy)); + OnEffectHitTarget.Add(new EffectHandler(HandleScriptEffect, 1, SpellEffectName.ScriptEffect)); + } + } + + public override SpellScript GetSpellScript() + { + return new spell_hun_masters_call_SpellScript(); + } + } + + // 34477 - Misdirection + [Script] + class spell_hun_misdirection : SpellScriptLoader + { + public spell_hun_misdirection() : base("spell_hun_misdirection") { } + + class spell_hun_misdirection_AuraScript : AuraScript + { + public override bool Validate(SpellInfo spellInfo) + { + return ValidateSpellInfo(SpellIds.MisdirectionProc); + } + + void OnRemove(AuraEffect aurEff, AuraEffectHandleModes mode) + { + if (GetTargetApplication().GetRemoveMode() != AuraRemoveMode.Default || !GetTarget().HasAura(SpellIds.MisdirectionProc)) + GetTarget().ResetRedirectThreat(); + } + + bool CheckProc(ProcEventInfo eventInfo) + { + return GetTarget().GetRedirectThreatTarget(); + } + + void HandleProc(AuraEffect aurEff, ProcEventInfo eventInfo) + { + PreventDefaultAction(); + GetTarget().CastSpell(GetTarget(), SpellIds.MisdirectionProc, true, null, aurEff); + } + + public override void Register() + { + AfterEffectRemove.Add(new EffectApplyHandler(OnRemove, 1, AuraType.Dummy, AuraEffectHandleModes.Real)); + DoCheckProc.Add(new CheckProcHandler(CheckProc)); + OnEffectProc.Add(new EffectProcHandler(HandleProc, 1, AuraType.Dummy)); + } + } + + public override AuraScript GetAuraScript() + { + return new spell_hun_misdirection_AuraScript(); + } + } + + // 35079 - Misdirection (Proc) + [Script] + class spell_hun_misdirection_proc : SpellScriptLoader + { + public spell_hun_misdirection_proc() + : base("spell_hun_misdirection_proc") + { } + + class spell_hun_misdirection_proc_AuraScript : AuraScript + { + void OnRemove(AuraEffect aurEff, AuraEffectHandleModes mode) + { + GetTarget().ResetRedirectThreat(); + } + + public override void Register() + { + AfterEffectRemove.Add(new EffectApplyHandler(OnRemove, 0, AuraType.Dummy, AuraEffectHandleModes.Real)); + } + } + + public override AuraScript GetAuraScript() + { + return new spell_hun_misdirection_proc_AuraScript(); + } + } + + // 54044 - Pet Carrion Feeder + [Script] + class spell_hun_pet_carrion_feeder : SpellScriptLoader + { + public spell_hun_pet_carrion_feeder() + : base("spell_hun_pet_carrion_feeder") + { } + + class spell_hun_pet_carrion_feeder_SpellScript : SpellScript + { + public override bool Load() + { + if (!GetCaster().IsPet()) + return false; + return true; + } + + public override bool Validate(SpellInfo spellInfo) + { + return ValidateSpellInfo(SpellIds.PetCarrionFeederTriggered); + } + + SpellCastResult CheckIfCorpseNear() + { + Unit caster = GetCaster(); + float max_range = GetSpellInfo().GetMaxRange(false); + + // search for nearby enemy corpse in range + var check = new AnyDeadUnitSpellTargetInRangeCheck(caster, max_range, GetSpellInfo(), SpellTargetCheckTypes.Enemy); + var searcher = new WorldObjectSearcher(caster, check); + Cell.VisitWorldObjects(caster, searcher, max_range); + if (!searcher.GetTarget()) + Cell.VisitGridObjects(caster, searcher, max_range); + if (!searcher.GetTarget()) + return SpellCastResult.NoEdibleCorpses; + return SpellCastResult.SpellCastOk; + } + + void HandleDummy(uint effIndex) + { + Unit caster = GetCaster(); + caster.CastSpell(caster, SpellIds.PetCarrionFeederTriggered, false); + } + + public override void Register() + { + OnEffectHit.Add(new EffectHandler(HandleDummy, 0, SpellEffectName.Dummy)); + OnCheckCast.Add(new CheckCastHandler(CheckIfCorpseNear)); + } + } + + public override SpellScript GetSpellScript() + { + return new spell_hun_pet_carrion_feeder_SpellScript(); + } + } + + // 55709 - Pet Heart of the Phoenix + [Script] + class spell_hun_pet_heart_of_the_phoenix : SpellScriptLoader + { + public spell_hun_pet_heart_of_the_phoenix() + : base("spell_hun_pet_heart_of_the_phoenix") + { } + + class spell_hun_pet_heart_of_the_phoenix_SpellScript : SpellScript + { + public override bool Load() + { + if (!GetCaster().IsPet()) + return false; + return true; + } + + public override bool Validate(SpellInfo spellInfo) + { + return ValidateSpellInfo(SpellIds.PetHeartOfThePhoenixTriggered, SpellIds.PetHeartOfThePhoenixDebuff); + } + + void HandleScript(uint effIndex) + { + Unit caster = GetCaster(); + Unit owner = caster.GetOwner(); + if (owner) + { + if (!caster.HasAura(SpellIds.PetHeartOfThePhoenixDebuff)) + { + owner.CastCustomSpell(SpellIds.PetHeartOfThePhoenixTriggered, SpellValueMod.BasePoint0, 100, caster, true); + caster.CastSpell(caster, SpellIds.PetHeartOfThePhoenixDebuff, true); + } + } + } + + public override void Register() + { + OnEffectHitTarget.Add(new EffectHandler(HandleScript, 0, SpellEffectName.ScriptEffect)); + } + } + + public override SpellScript GetSpellScript() + { + return new spell_hun_pet_heart_of_the_phoenix_SpellScript(); + } + } + + // 23989 - Readiness + [Script] + class spell_hun_readiness : SpellScriptLoader + { + public spell_hun_readiness() + : base("spell_hun_readiness") + { } + + class spell_hun_readiness_SpellScript : SpellScript + { + public override bool Load() + { + return GetCaster().IsTypeId(TypeId.Player); + } + + void HandleDummy(uint effIndex) + { + // immediately finishes the cooldown on your other Hunter abilities except Bestial Wrath + GetCaster().GetSpellHistory().ResetCooldowns(p => + { + SpellInfo spellInfo = Global.SpellMgr.GetSpellInfo(p.Key); + + ///! If spellId in cooldown map isn't valid, the above will return a null pointer. + if (spellInfo.SpellFamilyName == SpellFamilyNames.Hunter && + spellInfo.Id != SpellIds.Readiness && + spellInfo.Id != SpellIds.BestialWrath && + spellInfo.Id != SpellIds.DraeneiGiftOfTheNaaru && + spellInfo.GetRecoveryTime() > 0) + return true; + return false; + }, true); + } + + public override void Register() + { + OnEffectHitTarget.Add(new EffectHandler(HandleDummy, 0, SpellEffectName.Dummy)); + } + } + + public override SpellScript GetSpellScript() + { + return new spell_hun_readiness_SpellScript(); + } + } + + // 82925 - Ready, Set, Aim... + [Script] + class spell_hun_ready_set_aim : SpellScriptLoader + { + public spell_hun_ready_set_aim() + : base("spell_hun_ready_set_aim") + { } + + class spell_hun_ready_set_aim_AuraScript : AuraScript + { + public override bool Validate(SpellInfo spellInfo) + { + return ValidateSpellInfo(SpellIds.Fire); + } + + void OnApply(AuraEffect aurEff, AuraEffectHandleModes mode) + { + if (GetStackAmount() == 5) + { + GetTarget().CastSpell(GetTarget(), SpellIds.Fire, true, null, aurEff); + GetTarget().RemoveAura(GetId()); + } + } + + public override void Register() + { + AfterEffectApply.Add(new EffectApplyHandler(OnApply, 0, AuraType.Dummy, AuraEffectHandleModes.RealOrReapplyMask)); + } + } + + public override AuraScript GetAuraScript() + { + return new spell_hun_ready_set_aim_AuraScript(); + } + } + + [Script] // 53480 - Roar of Sacrifice + class spell_hun_roar_of_sacrifice : SpellScriptLoader + { + public spell_hun_roar_of_sacrifice() : base("spell_hun_roar_of_sacrifice") { } + + class spell_hun_roar_of_sacrifice_AuraScript : AuraScript + { + public override bool Validate(SpellInfo spellInfo) + { + return ValidateSpellInfo(SpellIds.RoarOfSacrificeTriggered); + } + + bool CheckProc(ProcEventInfo eventInfo) + { + return GetCaster() && ((uint)eventInfo.GetDamageInfo().GetSchoolMask() & GetEffect(1).GetMiscValue()) != 0; + } + + void HandleProc(AuraEffect aurEff, ProcEventInfo eventInfo) + { + PreventDefaultAction(); + + int damage = (int)MathFunctions.CalculatePct(eventInfo.GetDamageInfo().GetDamage(), aurEff.GetAmount()); + eventInfo.GetActor().CastCustomSpell(SpellIds.RoarOfSacrificeTriggered, SpellValueMod.BasePoint0, damage, GetCaster(), TriggerCastFlags.FullMask, null, aurEff); + } + + public override void Register() + { + DoCheckProc.Add(new CheckProcHandler(CheckProc)); + OnEffectProc.Add(new EffectProcHandler(HandleProc, 1, AuraType.Dummy)); + } + } + + public override AuraScript GetAuraScript() + { + return new spell_hun_roar_of_sacrifice_AuraScript(); + } + } + + // 37506 - Scatter Shot + [Script] + class spell_hun_scatter_shot : SpellScriptLoader + { + public spell_hun_scatter_shot() + : base("spell_hun_scatter_shot") + { } + + class spell_hun_scatter_shot_SpellScript : SpellScript + { + public override bool Load() + { + return GetCaster().IsTypeId(TypeId.Player); + } + + void HandleDummy(uint effIndex) + { + Player caster = GetCaster().ToPlayer(); + // break Auto Shot and autohit + caster.InterruptSpell(CurrentSpellTypes.AutoRepeat); + caster.AttackStop(); + caster.SendAttackSwingCancelAttack(); + } + + public override void Register() + { + OnEffectHitTarget.Add(new EffectHandler(HandleDummy, 0, SpellEffectName.Dummy)); + } + } + + public override SpellScript GetSpellScript() + { + return new spell_hun_scatter_shot_SpellScript(); + } + } + + // -53302 - Sniper Training + [Script] + class spell_hun_sniper_training : SpellScriptLoader + { + public spell_hun_sniper_training() + : base("spell_hun_sniper_training") + { } + + class spell_hun_sniper_training_AuraScript : AuraScript + { + public override bool Validate(SpellInfo spellInfo) + { + return ValidateSpellInfo(SpellIds.SniperTrainingR1, SpellIds.SniperTrainingBuffR1); + } + + void HandlePeriodic(AuraEffect aurEff) + { + PreventDefaultAction(); + if (aurEff.GetAmount() <= 0) + { + uint spellId = SpellIds.SniperTrainingBuffR1 + GetId() - SpellIds.SniperTrainingR1; + Unit target = GetTarget(); + target.CastSpell(target, spellId, true, null, aurEff); + Player playerTarget = GetUnitOwner().ToPlayer(); + if (playerTarget) + { + int baseAmount = aurEff.GetBaseAmount(); + int amount = playerTarget.CalculateSpellDamage(playerTarget, GetSpellInfo(), aurEff.GetEffIndex(), baseAmount); + GetEffect(0).SetAmount(amount); + } + } + } + + void HandleUpdatePeriodic(AuraEffect aurEff) + { + Player playerTarget = GetUnitOwner().ToPlayer(); + if (playerTarget) + { + int baseAmount = aurEff.GetBaseAmount(); + int amount = playerTarget.isMoving() ? + playerTarget.CalculateSpellDamage(playerTarget, GetSpellInfo(), aurEff.GetEffIndex(), baseAmount) : + aurEff.GetAmount() - 1; + aurEff.SetAmount(amount); + } + } + + public override void Register() + { + OnEffectPeriodic.Add(new EffectPeriodicHandler(HandlePeriodic, 0, AuraType.PeriodicTriggerSpell)); + OnEffectUpdatePeriodic.Add(new EffectUpdatePeriodicHandler(HandleUpdatePeriodic, 0, AuraType.PeriodicTriggerSpell)); + } + } + + public override AuraScript GetAuraScript() + { + return new spell_hun_sniper_training_AuraScript(); + } + } + + // 56641 - Steady Shot + [Script] + class spell_hun_steady_shot : SpellScriptLoader + { + public spell_hun_steady_shot() : base("spell_hun_steady_shot") { } + + class spell_hun_steady_shot_SpellScript : SpellScript + { + public override bool Validate(SpellInfo spellInfo) + { + return ValidateSpellInfo(SpellIds.SteadyShotFocus); + } + + public override bool Load() + { + return GetCaster().IsTypeId(TypeId.Player); + } + + void HandleOnHit() + { + GetCaster().CastSpell(GetCaster(), SpellIds.SteadyShotFocus, true); + } + + public override void Register() + { + OnHit.Add(new HitHandler(HandleOnHit)); + } + } + + public override SpellScript GetSpellScript() + { + return new spell_hun_steady_shot_SpellScript(); + } + } + + // 1515 - Tame Beast + [Script] + class spell_hun_tame_beast : SpellScriptLoader + { + public spell_hun_tame_beast() + : base("spell_hun_tame_beast") + { } + + class spell_hun_tame_beast_SpellScript : SpellScript + { + SpellCastResult CheckCast() + { + Unit caster = GetCaster(); + if (!caster.IsTypeId(TypeId.Player)) + return SpellCastResult.DontReport; + + if (!GetExplTargetUnit()) + return SpellCastResult.BadImplicitTargets; + + Creature target = GetExplTargetUnit().ToCreature(); + if (target) + { + if (target.getLevel() > caster.getLevel()) + return SpellCastResult.Highlevel; + + // use SMSG_PET_TAME_FAILURE? + if (!target.GetCreatureTemplate().IsTameable(caster.ToPlayer().CanTameExoticPets())) + return SpellCastResult.BadTargets; + + if (!caster.GetPetGUID().IsEmpty()) + return SpellCastResult.AlreadyHaveSummon; + + if (!caster.GetCharmGUID().IsEmpty()) + return SpellCastResult.AlreadyHaveCharm; + } + else + return SpellCastResult.BadImplicitTargets; + + return SpellCastResult.SpellCastOk; + } + + public override void Register() + { + OnCheckCast.Add(new CheckCastHandler(CheckCast)); + } + } + + public override SpellScript GetSpellScript() + { + return new spell_hun_tame_beast_SpellScript(); + } + } + + // 53434 - Call of the Wild + [Script] + class spell_hun_target_only_pet_and_owner : SpellScriptLoader + { + public spell_hun_target_only_pet_and_owner() + : base("spell_hun_target_only_pet_and_owner") + { } + + class spell_hun_target_only_pet_and_owner_SpellScript : SpellScript + { + void FilterTargets(List targets) + { + targets.Clear(); + targets.Add(GetCaster()); + Unit owner = GetCaster().GetOwner(); + if (owner) + targets.Add(owner); + } + + public override void Register() + { + OnObjectAreaTargetSelect.Add(new ObjectAreaTargetSelectHandler(FilterTargets, 0, Targets.UnitCasterAreaParty)); + OnObjectAreaTargetSelect.Add(new ObjectAreaTargetSelectHandler(FilterTargets, 1, Targets.UnitCasterAreaParty)); + } + } + + public override SpellScript GetSpellScript() + { + return new spell_hun_target_only_pet_and_owner_SpellScript(); + } + } + + [Script] // 67151 - Item - Hunter T9 4P Bonus (Steady Shot) + class spell_hun_t9_4p_bonus : SpellScriptLoader + { + public spell_hun_t9_4p_bonus() : base("spell_hun_t9_4p_bonus") { } + + class spell_hun_t9_4p_bonus_AuraScript : AuraScript + { + public override bool Validate(SpellInfo spellInfo) + { + return ValidateSpellInfo(SpellIds.T94PGreatness); + } + + bool CheckProc(ProcEventInfo eventInfo) + { + if (eventInfo.GetActor().IsTypeId(TypeId.Player) && eventInfo.GetActor().ToPlayer().GetPet()) + return true; + return false; + } + + void HandleProc(AuraEffect aurEff, ProcEventInfo eventInfo) + { + PreventDefaultAction(); + Unit caster = eventInfo.GetActor(); + + caster.CastSpell(caster.ToPlayer().GetPet(), SpellIds.T94PGreatness, true, null, aurEff); + } + + public override void Register() + { + DoCheckProc.Add(new CheckProcHandler(CheckProc)); + OnEffectProc.Add(new EffectProcHandler(HandleProc, 0, AuraType.ProcTriggerSpell)); + } + } + + public override AuraScript GetAuraScript() + { + return new spell_hun_t9_4p_bonus_AuraScript(); + } + } + + // -56333 - T.N.T. + [Script] + class spell_hun_tnt : SpellScriptLoader + { + public spell_hun_tnt() + : base("spell_hun_tnt") + { } + + class spell_hun_tnt_AuraScript : AuraScript + { + public override bool Validate(SpellInfo spellInfo) + { + return ValidateSpellInfo(SpellIds.LockAndLoad); + } + + bool CheckProc(ProcEventInfo eventInfo) + { + return RandomHelper.randChance(GetEffect(0).GetAmount()); + } + + void HandleEffectProc(AuraEffect aurEff, ProcEventInfo eventInfo) + { + PreventDefaultAction(); + GetTarget().CastSpell(GetTarget(), SpellIds.LockAndLoad, true, null, aurEff); + } + + public override void Register() + { + DoCheckProc.Add(new CheckProcHandler(CheckProc)); + OnEffectProc.Add(new EffectProcHandler(HandleEffectProc, 0, AuraType.Dummy)); + } + } + + public override AuraScript GetAuraScript() + { + return new spell_hun_tnt_AuraScript(); + } + } +} diff --git a/Scripts/Spells/Items.cs b/Scripts/Spells/Items.cs new file mode 100644 index 000000000..eb113404a --- /dev/null +++ b/Scripts/Spells/Items.cs @@ -0,0 +1,4237 @@ +/* + * Copyright (C) 2012-2017 CypherCore + * + * 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, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +using Framework.Constants; +using Framework.GameMath; +using Game.BattleGrounds; +using Game.DataStorage; +using Game.Entities; +using Game.Scripting; +using Game.Spells; +using System; +using System.Collections.Generic; +using System.Linq; + +namespace Scripts.Spells.Items +{ + struct SpellIds + { + //Aegisofpreservation + public const uint AegisHeal = 23781; + + //Alchemiststone + public const uint AlchemistStoneExtraHeal = 21399; + public const uint AlchemistStoneExtraMana = 21400; + + //Angercapacitor + public const uint MoteOfAnger = 71432; + public const uint ManifestAngerMainHand = 71433; + public const uint ManifestAngerOffHand = 71434; + + //Auraofmadness + public const uint Sociopath = 39511; // Sociopath: +35 Strength(Paladin; Rogue; Druid; Warrior) + public const uint Delusional = 40997; // Delusional: +70 Attack Power(Rogue; Hunter; Paladin; Warrior; Druid) + public const uint Kleptomania = 40998; // Kleptomania: +35 Agility(Warrior; Rogue; Paladin; Hunter; Druid) + public const uint Megalomania = 40999; // Megalomania: +41 Damage / Healing(Druid; Shaman; Priest; Warlock; Mage; Paladin) + public const uint Paranoia = 41002; // Paranoia: +35 Spell / Melee / Ranged Crit Strike Rating(All Classes) + public const uint Manic = 41005; // Manic: +35 Haste(Spell; Melee And Ranged) (All Classes) + public const uint Narcissism = 41009; // Narcissism: +35 Intellect(Druid; Shaman; Priest; Warlock; Mage; Paladin; Hunter) + public const uint MartyrComplex = 41011; // Martyr Complex: +35 Stamina(All Classes) + public const uint Dementia = 41404; // Dementia: Every 5 Seconds Either Gives You +5/-5% Damage/Healing. (Druid; Shaman; Priest; Warlock; Mage; Paladin) + public const uint DementiaPos = 41406; + public const uint DementiaNeg = 41409; + + //Blessingofancientkings + public const uint ProtectionOfAncientKings = 64413; + + //Deadlyprecision + public const uint DeadlyPrecision = 71564; + + //Deathbringerswill + public const uint StrengthOfTheTaunka = 71484; // +600 Strength + public const uint AgilityOfTheVrykul = 71485; // +600 Agility + public const uint PowerOfTheTaunka = 71486; // +1200 Attack Power + public const uint AimOfTheIronDwarves = 71491; // +600 Critical + public const uint SpeedOfTheVrykul = 71492; // +600 Haste + public const uint AgilityOfTheVrykulHero = 71556; // +700 Agility + public const uint PowerOfTheTaunkaHero = 71558; // +1400 Attack Power + public const uint AimOfTheIronDwarvesHero = 71559; // +700 Critical + public const uint SpeedOfTheVrykulHero = 71560; // +700 Haste + public const uint StrengthOfTheTaunkaHero = 71561; // +700 Strength + + //Defibrillate + public const uint GoblinJumperCablesFail = 8338; + public const uint GoblinJumperCablesXlFail = 23055; + + //Desperatedefense + public const uint DesperateRage = 33898; + + //Deviatefishspells + public const uint Sleepy = 8064; + public const uint Invigorate = 8065; + public const uint Shrink = 8066; + public const uint PartyTime = 8067; + public const uint HealthySpirit = 8068; + + //Discerningeyebeastmisc + public const uint DiscerningEyeBeast = 59914; + + //Fateruneofunsurpassedvigor + public const uint UnsurpassedVigor = 25733; + + //Flaskofthenorthspells + public const uint FlaskOfTheNorthSp = 67016; + public const uint FlaskOfTheNorthAp = 67017; + public const uint FlaskOfTheNorthStr = 67018; + + //Frozenshadoweave + public const uint Shadowmend = 39373; + + //Gnomishdeathray + public const uint GnomishDeathRaySelf = 13493; + public const uint GnomishDeathRayTarget = 13279; + + //Heartpierce + public const uint InvigorationMana = 71881; + public const uint InvigorationEnergy = 71882; + public const uint InvigorationRage = 71883; + public const uint InvigorationRp = 71884; + public const uint InvigorationRpHero = 71885; + public const uint InvigorationRageHero = 71886; + public const uint InvigorationEnergyHero = 71887; + public const uint InvigorationManaHero = 71888; + + //Makeawish + public const uint MrPinchysBlessing = 33053; + public const uint SummonMightyMrPinchy = 33057; + public const uint SummonFuriousMrPinchy = 33059; + public const uint TinyMagicalCrawdad = 33062; + public const uint MrPinchysGift = 33064; + + //Markofconquest + public const uint MarkOfConquestEnergize = 39599; + + //Necrotictouch + public const uint ItemNecroticTouchProc = 71879; + + //Netomaticspells + public const uint NetOMaticTriggered1 = 16566; + public const uint NetOMaticTriggered2 = 13119; + public const uint NetOMaticTriggered3 = 13099; + + //Noggenfoggerelixirspells + public const uint NoggenfoggerElixirTriggered1 = 16595; + public const uint NoggenfoggerElixirTriggered2 = 16593; + public const uint NoggenfoggerElixirTriggered3 = 16591; + + //Persistentshieldmisc + public const uint PersistentShieldTriggered = 26470; + + //Pethealing + public const uint HealthLink = 37382; + + //Savorydeviatedelight + public const uint FlipOutMale = 8219; + public const uint FlipOutFemale = 8220; + public const uint YaaarrrrMale = 8221; + public const uint YaaarrrrFemale = 8222; + + //Scrollofrecall + public const uint ScrollOfRecallI = 48129; + public const uint ScrollOfRecallII = 60320; + public const uint ScrollOfRecallIII = 60321; + public const uint Lost = 60444; + public const uint ScrollOfRecallFailAlliance1 = 60323; + public const uint ScrollOfRecallFailHorde1 = 60328; + + //Shadowsfate + public const uint SoulFeast = 71203; + + //Shadowmourne + public const uint ShadowmourneChaosBaneDamage = 71904; + public const uint ShadowmourneSoulFragment = 71905; + public const uint ShadowmourneVisualLow = 72521; + public const uint ShadowmourneVisualHigh = 72523; + public const uint ShadowmourneChaosBaneBuff = 73422; + + //Sixdemonbagspells + public const uint Frostbolt = 11538; + public const uint Polymorph = 14621; + public const uint SummonFelhoundMinion = 14642; + public const uint Fireball = 15662; + public const uint ChainLightning = 21179; + public const uint EnvelopingWinds = 25189; + + //Swifthandjusticemisc + public const uint SwiftHandOfJusticeHeal = 59913; + + //Underbellyelixirspells + public const uint UnderbellyElixirTriggered1 = 59645; + public const uint UnderbellyElixirTriggered2 = 59831; + public const uint UnderbellyElixirTriggered3 = 59843; + + //Wormholegeneratorpandariaspell + public const uint Wormholepandariaisleofreckoning = 126756; + public const uint Wormholepandariakunlaiunderwater = 126757; + public const uint Wormholepandariasravess = 126758; + public const uint Wormholepandariarikkitunvillage = 126759; + public const uint Wormholepandariazanvesstree = 126760; + public const uint Wormholepandariaanglerswharf = 126761; + public const uint Wormholepandariacranestatue = 126762; + public const uint Wormholepandariaemperorsomen = 126763; + public const uint Wormholepandariawhitepetallake = 126764; + + public static uint[] WormholeTargetLocations = + { + Wormholepandariaisleofreckoning, + Wormholepandariakunlaiunderwater, + Wormholepandariasravess, + Wormholepandariarikkitunvillage, + Wormholepandariazanvesstree, + Wormholepandariaanglerswharf, + Wormholepandariacranestatue, + Wormholepandariaemperorsomen, + Wormholepandariawhitepetallake + }; + + //Airriflespells + public const uint AirRifleHoldVisual = 65582; + public const uint AirRifleShoot = 67532; + public const uint AirRifleShootSelf = 65577; + + //Genericdata + public const uint ArcaniteDragonling = 19804; + public const uint BattleChicken = 13166; + public const uint MechanicalDragonling = 4073; + public const uint MithrilMechanicalDragonling = 12749; + + //Vanquishedclutchesspells + public const uint Crusher = 64982; + public const uint Constrictor = 64983; + public const uint Corruptor = 64984; + + //Magiceater + public const uint WildMagic = 58891; + public const uint WellFed1 = 57288; + public const uint WellFed2 = 57139; + public const uint WellFed3 = 57111; + public const uint WellFed4 = 57286; + public const uint WellFed5 = 57291; + + //Purifyhelboarmeat + public const uint SummonPurifiedHelboarMeat = 29277; + public const uint SummonToxicHelboarMeat = 29278; + + //Reindeertransformation + public const uint FlyingReindeer310 = 44827; + public const uint FlyingReindeer280 = 44825; + public const uint FlyingReindeer60 = 44824; + public const uint Reindeer100 = 25859; + public const uint Reindeer60 = 25858; + + //Nighinvulnerability + public const uint NighInvulnerability = 30456; + public const uint CompleteVulnerability = 30457; + + //Poultryzer + public const uint PoultryizerSuccess = 30501; + public const uint PoultryizerBackfire = 30504; + + //Socretharsstone + public const uint SocretharToSeat = 35743; + public const uint SocretharFromSeat = 35744; + + //Demonbroiledsurprise + public const uint CreateDemonBroiledSurprise = 43753; + + //Completeraptorcapture + public const uint RaptorCaptureCredit = 42337; + + //Impaleleviroth + public const uint LevirothSelfImpale = 49882; + + //Brewfestmounttransformation + public const uint MountRam100 = 43900; + public const uint MountRam60 = 43899; + public const uint MountKodo100 = 49379; + public const uint MountKodo60 = 49378; + public const uint BrewfestMountTransform = 49357; + public const uint BrewfestMountTransformReverse = 52845; + + //Nitroboots + public const uint NitroBootsSuccess = 54861; + public const uint NitroBootsBackfire = 46014; + + //Teachlanguage + public const uint LearnGnomishBinary = 50242; + public const uint LearnGoblinBinary = 50246; + + //Rocketboots + public const uint RocketBootsProc = 30452; + + //Pygmyoil + public const uint PygmyOilPygmyAura = 53806; + public const uint PygmyOilSmallerAura = 53805; + + //Chickencover + public const uint ChickenNet = 51959; + public const uint CaptureChickenEscape = 51037; + + //Greatmotherssoulcather + public const uint ForceCastSummonGnomeSoul = 46486; + + //Shardofthescale + public const uint PurifiedCauterizingHeal = 69733; + public const uint PurifiedSearingFlames = 69729; + public const uint ShinyCauterizingHeal = 69734; + public const uint ShinySearingFlames = 69730; + + //Soulpreserver + public const uint SoulPreserverDruid = 60512; + public const uint SoulPreserverPaladin = 60513; + public const uint SoulPreserverPriest = 60514; + public const uint SoulPreserverShaman = 60515; + + //ExaltedSunwellNeck + public const uint LightsWrath = 45479; // Light'S Wrath If Exalted By Aldor + public const uint ArcaneBolt = 45429; // Arcane Bolt If Exalted By Scryers + + public const uint LightsStrength = 45480; // Light'S Strength If Exalted By Aldor + public const uint ArcaneStrike = 45428; // Arcane Strike If Exalted By Scryers + + public const uint LightsWard = 45432; // Light'S Ward If Exalted By Aldor + public const uint ArcaneInsight = 45431; // Arcane Insight If Exalted By Scryers + + public const uint LightsSalvation = 45478; // Light'S Salvation If Exalted By Aldor + public const uint ArcaneSurge = 45430; // Arcane Surge If Exalted By Scryers + + //Deathchoicespells + public const uint DeathChoiceNormalAura = 67702; + public const uint DeathChoiceNormalAgility = 67703; + public const uint DeathChoiceNormalStrength = 67708; + public const uint DeathChoiceHeroicAura = 67771; + public const uint DeathChoiceHeroicAgility = 67772; + public const uint DeathChoiceHeroicStrength = 67773; + + //Trinketstackspells + public const uint LightningCapacitorAura = 37657; // Lightning Capacitor + public const uint LightningCapacitorStack = 37658; + public const uint LightningCapacitorTrigger = 37661; + public const uint ThunderCapacitorAura = 54841; // Thunder Capacitor + public const uint ThunderCapacitorStack = 54842; + public const uint ThunderCapacitorTrigger = 54843; + public const uint Toc25CasterTrinketNormalAura = 67712; // Item - Coliseum 25 Normal Caster Trinket + public const uint Toc25CasterTrinketNormalStack = 67713; + public const uint Toc25CasterTrinketNormalTrigger = 67714; + public const uint Toc25CasterTrinketHeroicAura = 67758; // Item - Coliseum 25 Heroic Caster Trinket + public const uint Toc25CasterTrinketHeroicStack = 67759; + public const uint Toc25CasterTrinketHeroicTrigger = 67760; + + //Darkmooncardspells + public const uint DarkmoonCardStrenght = 60229; + public const uint DarkmoonCardAgility = 60233; + public const uint DarkmoonCardIntellect = 60234; + public const uint DarkmoonCardVersatility = 60235; + + //Manadrainspells + public const uint ManaDrainEnergize = 29471; + public const uint ManaDrainLeech = 27526; + + //Tauntflag + public const uint TauntFlag = 51657; + + //Zandalariancharms + public const uint UnstablePowerAuraStack = 24659; + public const uint RestlessStrengthAuraStack = 24662; + + // Auraprocremovespells + public const uint TalismanOfAscendance = 28200; + public const uint JomGabbar = 29602; + public const uint BattleTrance = 45040; + public const uint WorldQuellerFocus = 90900; + public const uint AzureWaterStrider = 118089; + public const uint CrimsonWaterStrider = 127271; + public const uint OrangeWaterStrider = 127272; + public const uint JadeWaterStrider = 127274; + public const uint GoldenWaterStrider = 127278; + public const uint BrutalKinship1 = 144671; + public const uint BrutalKinship2 = 145738; + } + + struct TextIds + { + //Auraofmadness + public const uint SayMadness = 21954; + + //Roll Dice + public const uint DecahedralDwarvenDice = 26147; + + //Roll 'dem Bones + public const uint WornTrollDice = 26152; + + //TauntFlag + public const uint EmotePlantsFlag = 28008; + } + + struct FactionIds + { + //ExaltedSunwellNeck + public const uint Aldor = 932; + public const uint Scryers = 934; + } + + struct ObjectIds + { + //Crystalprison + public const uint ImprisonedDoomguard = 179644; + } + + struct CreatureIds + { + //Shadowsfate + public const uint Sindragosa = 36853; + + //Giftoftheharvester + public const uint Ghoul = 28845; + public const uint MaxGhouls = 5; + + //Sinkholes + public const uint SouthSinkhole = 25664; + public const uint NortheastSinkhole = 25665; + public const uint NorthwestSinkhole = 25666; + + //Demonbroiledsurprise + public const uint AbyssalFlamebringer = 19973; + + //Impaleleviroth + public const uint Leviroth = 26452; + } + + struct ItemIds + { + //Createheartcandy + public const uint HeartCandy1 = 21818; + public const uint HeartCandy2 = 21817; + public const uint HeartCandy3 = 21821; + public const uint HeartCandy4 = 21819; + public const uint HeartCandy5 = 21816; + public const uint HeartCandy6 = 21823; + public const uint HeartCandy7 = 21822; + public const uint HeartCandy8 = 21820; + } + + struct QuestIds + { + //Demonbroiledsurprise + public const uint SuperHotStew = 11379; + + //Chickencover + public const uint ChickenParty = 12702; + public const uint FlownTheCoop = 12532; + } + + struct SoundIds + { + //Ashbringersounds + public const uint Ashbringer1 = 8906; // "I Was Pure Once" + public const uint Ashbringer2 = 8907; // "Fought For Righteousness" + public const uint Ashbringer3 = 8908; // "I Was Once Called Ashbringer" + public const uint Ashbringer4 = 8920; // "Betrayed By My Order" + public const uint Ashbringer5 = 8921; // "Destroyed By Kel'Thuzad" + public const uint Ashbringer6 = 8922; // "Made To Serve" + public const uint Ashbringer7 = 8923; // "My Son Watched Me Die" + public const uint Ashbringer8 = 8924; // "Crusades Fed His Rage" + public const uint Ashbringer9 = 8925; // "Truth Is Unknown To Him" + public const uint Ashbringer10 = 8926; // "Scarlet Crusade Is Pure No Longer" + public const uint Ashbringer11 = 8927; // "Balnazzar'S Crusade Corrupted My Son" + public const uint Ashbringer12 = 8928; // "Kill Them All!" + } + + // 23074 Arcanite Dragonling + // 23133 Gnomish Battle Chicken + // 23076 Mechanical Dragonling + // 23075 Mithril Mechanical Dragonling + [Script("spell_item_arcanite_dragonling", SpellIds.ArcaniteDragonling)] + [Script("spell_item_gnomish_battle_chicken", SpellIds.BattleChicken)] + [Script("spell_item_mechanical_dragonling", SpellIds.MechanicalDragonling)] + [Script("spell_item_mithril_mechanical_dragonling", SpellIds.MithrilMechanicalDragonling)] + class spell_item_trigger_spell : SpellScriptLoader + { + public spell_item_trigger_spell(string name, uint triggeredSpellId) : base(name) + { + _triggeredSpellId = triggeredSpellId; + } + + class spell_item_trigger_spell_SpellScript : SpellScript + { + public spell_item_trigger_spell_SpellScript(uint triggeredSpellId) : base() + { + _triggeredSpellId = triggeredSpellId; + } + + public override bool Validate(SpellInfo spellInfo) + { + return ValidateSpellInfo(_triggeredSpellId); + } + + void HandleDummy(uint effIndex) + { + Unit caster = GetCaster(); + Item item = GetCastItem(); + if (item) + caster.CastSpell(caster, _triggeredSpellId, true, item); + } + + public override void Register() + { + OnEffectHit.Add(new EffectHandler(HandleDummy, 0, SpellEffectName.Dummy)); + } + + uint _triggeredSpellId; + } + + public override SpellScript GetSpellScript() + { + return new spell_item_trigger_spell_SpellScript(_triggeredSpellId); + } + + uint _triggeredSpellId; + } + + [Script] // 23780 - Aegis of Preservation + class spell_item_aegis_of_preservation : SpellScriptLoader + { + public spell_item_aegis_of_preservation() : base("spell_item_aegis_of_preservation") { } + + class spell_item_aegis_of_preservation_AuraScript : AuraScript + { + public override bool Validate(SpellInfo spellInfo) + { + return ValidateSpellInfo(SpellIds.AegisHeal); + } + + void HandleProc(AuraEffect aurEff, ProcEventInfo eventInfo) + { + PreventDefaultAction(); + GetTarget().CastSpell(GetTarget(), SpellIds.AegisHeal, true, null, aurEff); + } + + public override void Register() + { + OnEffectProc.Add(new EffectProcHandler(HandleProc, 1, AuraType.PeriodicTriggerSpell)); + } + } + + public override AuraScript GetAuraScript() + { + return new spell_item_aegis_of_preservation_AuraScript(); + } + } + + // Item - 13503: Alchemist's Stone + // Item - 35748: Guardian's Alchemist Stone + // Item - 35749: Sorcerer's Alchemist Stone + // Item - 35750: Redeemer's Alchemist Stone + // Item - 35751: Assassin's Alchemist Stone + // Item - 44322: Mercurial Alchemist Stone + // Item - 44323: Indestructible Alchemist's Stone + // Item - 44324: Mighty Alchemist's Stone + + [Script] // 17619 - Alchemist Stone + class spell_item_alchemist_stone : SpellScriptLoader + { + public spell_item_alchemist_stone() : base("spell_item_alchemist_stone") { } + + class spell_item_alchemist_stone_AuraScript : AuraScript + { + public override bool Validate(SpellInfo spellInfo) + { + return ValidateSpellInfo(SpellIds.AlchemistStoneExtraHeal, SpellIds.AlchemistStoneExtraMana); + } + + bool CheckProc(ProcEventInfo eventInfo) + { + return eventInfo.GetDamageInfo().GetSpellInfo().SpellFamilyName == SpellFamilyNames.Potion; + } + + void HandleProc(AuraEffect aurEff, ProcEventInfo eventInfo) + { + PreventDefaultAction(); + + uint spellId = 0; + int amount = (int)(eventInfo.GetDamageInfo().GetDamage() * 0.4f); + + if (eventInfo.GetDamageInfo().GetSpellInfo().HasEffect(Difficulty.None, SpellEffectName.Heal)) + spellId = SpellIds.AlchemistStoneExtraHeal; + else if (eventInfo.GetDamageInfo().GetSpellInfo().HasEffect(Difficulty.None, SpellEffectName.Energize)) + spellId = SpellIds.AlchemistStoneExtraMana; + + if (spellId == 0) + return; + + GetTarget().CastCustomSpell(spellId, SpellValueMod.BasePoint0, amount, GetTarget(), true, null, aurEff); + } + + public override void Register() + { + DoCheckProc.Add(new CheckProcHandler(CheckProc)); + OnEffectProc.Add(new EffectProcHandler(HandleProc, 0, AuraType.Dummy)); + } + } + + public override AuraScript GetAuraScript() + { + return new spell_item_alchemist_stone_AuraScript(); + } + } + + // Item - 50351: Tiny Abomination in a Jar + // 71406 - Anger Capacitor + + // Item - 50706: Tiny Abomination in a Jar (Heroic) + // 71545 - Anger Capacitor + [Script("spell_item_tiny_abomination_in_a_jar", 8)] + [Script("spell_item_tiny_abomination_in_a_jar_hero", 7)] + class spell_item_anger_capacitor : SpellScriptLoader + { + public spell_item_anger_capacitor(string scriptName, int stackAmount) : base(scriptName) + { + _stackAmount = stackAmount; + } + + class spell_item_anger_capacitor_AuraScript : AuraScript + { + public spell_item_anger_capacitor_AuraScript(int stackAmount) + { + _stackAmount = stackAmount; + } + + public override bool Validate(SpellInfo spellInfo) + { + return ValidateSpellInfo(SpellIds.MoteOfAnger, SpellIds.ManifestAngerMainHand, SpellIds.ManifestAngerOffHand); + } + + void HandleProc(AuraEffect aurEff, ProcEventInfo eventInfo) + { + PreventDefaultAction(); + Unit caster = eventInfo.GetActor(); + Unit target = eventInfo.GetProcTarget(); + + caster.CastSpell((Unit)null, SpellIds.MoteOfAnger, true); + Aura motes = caster.GetAura(SpellIds.MoteOfAnger); + if (motes == null || motes.GetStackAmount() < _stackAmount) + return; + + caster.RemoveAurasDueToSpell(SpellIds.MoteOfAnger); + uint spellId = SpellIds.ManifestAngerMainHand; + Player player = caster.ToPlayer(); + if (player) + if (player.GetWeaponForAttack(WeaponAttackType.OffAttack, true) && RandomHelper.URand(0, 1) != 0) + spellId = SpellIds.ManifestAngerOffHand; + + caster.CastSpell(target, spellId, true); + } + + public override void Register() + { + OnEffectProc.Add(new EffectProcHandler(HandleProc, 0, AuraType.Dummy)); + } + + int _stackAmount; + } + + public override AuraScript GetAuraScript() + { + return new spell_item_anger_capacitor_AuraScript(_stackAmount); + } + + int _stackAmount; + } + + [Script] // 26400 - Arcane Shroud + class spell_item_arcane_shroud : SpellScriptLoader + { + public spell_item_arcane_shroud() : base("spell_item_arcane_shroud") { } + + class spell_item_arcane_shroud_AuraScript : AuraScript + { + void CalculateAmount(AuraEffect aurEff, ref int amount, ref bool canBeRecalculated) + { + int diff = (int)GetUnitOwner().getLevel() - 60; + if (diff > 0) + amount += 2 * diff; + } + + public override void Register() + { + DoEffectCalcAmount.Add(new EffectCalcAmountHandler(CalculateAmount, 0, AuraType.ModThreat)); + } + } + + public override AuraScript GetAuraScript() + { + return new spell_item_arcane_shroud_AuraScript(); + } + } + + // Item - 31859: Darkmoon Card: Madness + [Script] // 39446 - Aura of Madness + class spell_item_aura_of_madness : SpellScriptLoader + { + public spell_item_aura_of_madness() : base("spell_item_aura_of_madness") { } + + class spell_item_aura_of_madness_AuraScript : AuraScript + { + public override bool Validate(SpellInfo spellInfo) + { + return CliDB.BroadcastTextStorage.ContainsKey(TextIds.SayMadness) && ValidateSpellInfo(SpellIds.Sociopath, SpellIds.Delusional, SpellIds.Kleptomania, + SpellIds.Megalomania, SpellIds.Paranoia, SpellIds.Manic, SpellIds.Narcissism, SpellIds.MartyrComplex, SpellIds.Dementia); + } + + void HandleProc(AuraEffect aurEff, ProcEventInfo eventInfo) + { + uint[][] triggeredSpells = + { + //CLASS_NONE + new uint[] { }, + //CLASS_WARRIOR + new uint[] { SpellIds.Sociopath, SpellIds.Delusional, SpellIds.Kleptomania, SpellIds.Paranoia, SpellIds.Manic, SpellIds.MartyrComplex }, + //CLASS_PALADIN + new uint[]{ SpellIds.Sociopath, SpellIds.Delusional, SpellIds.Kleptomania, SpellIds.Megalomania, SpellIds.Paranoia, SpellIds.Manic, SpellIds.Narcissism, SpellIds.MartyrComplex, SpellIds.Dementia }, + //CLASS_HUNTER + new uint[]{ SpellIds.Delusional, SpellIds.Megalomania, SpellIds.Paranoia, SpellIds.Manic, SpellIds.Narcissism, SpellIds.MartyrComplex, SpellIds.Dementia }, + //CLASS_ROGUE + new uint[]{ SpellIds.Sociopath, SpellIds.Delusional, SpellIds.Kleptomania, SpellIds.Paranoia, SpellIds.Manic, SpellIds.MartyrComplex }, + //CLASS_PRIEST + new uint[]{ SpellIds.Megalomania, SpellIds.Paranoia, SpellIds.Manic, SpellIds.Narcissism, SpellIds.MartyrComplex, SpellIds.Dementia }, + //CLASS_DEATH_KNIGHT + new uint[]{ SpellIds.Sociopath, SpellIds.Delusional, SpellIds.Kleptomania, SpellIds.Paranoia, SpellIds.Manic, SpellIds.MartyrComplex }, + //CLASS_SHAMAN + new uint[] { SpellIds.Megalomania, SpellIds.Paranoia, SpellIds.Manic, SpellIds.Narcissism, SpellIds.MartyrComplex, SpellIds.Dementia }, + //CLASS_MAGE + new uint[]{ SpellIds.Megalomania, SpellIds.Paranoia, SpellIds.Manic, SpellIds.Narcissism, SpellIds.MartyrComplex, SpellIds.Dementia }, + //CLASS_WARLOCK + new uint[]{ SpellIds.Megalomania, SpellIds.Paranoia, SpellIds.Manic, SpellIds.Narcissism, SpellIds.MartyrComplex, SpellIds.Dementia }, + //CLASS_UNK + new uint[]{ }, + //CLASS_DRUID + new uint[]{ SpellIds.Sociopath, SpellIds.Delusional, SpellIds.Kleptomania, SpellIds.Megalomania, SpellIds.Paranoia, SpellIds.Manic, SpellIds.Narcissism, SpellIds.MartyrComplex, SpellIds.Dementia } + }; + + PreventDefaultAction(); + Unit caster = eventInfo.GetActor(); + uint spellId = triggeredSpells[(int)caster.GetClass()].PickRandom(); + caster.CastSpell(caster, spellId, true); + + if (RandomHelper.randChance(10)) + caster.Say(TextIds.SayMadness); + } + + public override void Register() + { + OnEffectProc.Add(new EffectProcHandler(HandleProc, 0, AuraType.Dummy)); + } + } + + public override AuraScript GetAuraScript() + { + return new spell_item_aura_of_madness_AuraScript(); + } + } + + [Script] // 41404 - Dementia + class spell_item_dementia : SpellScriptLoader + { + public spell_item_dementia() : base("spell_item_dementia") { } + + class spell_item_dementia_AuraScript : AuraScript + { + public override bool Validate(SpellInfo spellInfo) + { + return ValidateSpellInfo(SpellIds.DementiaPos, SpellIds.DementiaNeg); + } + + void HandlePeriodicDummy(AuraEffect aurEff) + { + PreventDefaultAction(); + GetTarget().CastSpell(GetTarget(), RandomHelper.RAND(SpellIds.DementiaPos, SpellIds.DementiaNeg), true); + } + + public override void Register() + { + OnEffectPeriodic.Add(new EffectPeriodicHandler(HandlePeriodicDummy, 0, AuraType.PeriodicDummy)); + } + } + + public override AuraScript GetAuraScript() + { + return new spell_item_dementia_AuraScript(); + } + } + + [Script] // 64411 - Blessing of Ancient Kings (Val'anyr, Hammer of Ancient Kings) + class spell_item_blessing_of_ancient_kings : SpellScriptLoader + { + public spell_item_blessing_of_ancient_kings() : base("spell_item_blessing_of_ancient_kings") { } + + class spell_item_blessing_of_ancient_kings_AuraScript : AuraScript + { + public override bool Validate(SpellInfo spellInfo) + { + return ValidateSpellInfo(SpellIds.ProtectionOfAncientKings); + } + + bool CheckProc(ProcEventInfo eventInfo) + { + return eventInfo.GetProcTarget() != null; + } + + void HandleProc(AuraEffect aurEff, ProcEventInfo eventInfo) + { + PreventDefaultAction(); + + HealInfo healInfo = eventInfo.GetHealInfo(); + if (healInfo == null || healInfo.GetHeal() == 0) + return; + + int absorb = (int)MathFunctions.CalculatePct(healInfo.GetHeal(), 15.0f); + AuraEffect protEff = eventInfo.GetProcTarget().GetAuraEffect(SpellIds.ProtectionOfAncientKings, 0, eventInfo.GetActor().GetGUID()); + if (protEff != null) + { + // The shield can grow to a maximum size of 20,000 damage absorbtion + protEff.SetAmount(Math.Min(protEff.GetAmount() + absorb, 20000)); + + // Refresh and return to prevent replacing the aura + protEff.GetBase().RefreshDuration(); + } + else + GetTarget().CastCustomSpell(SpellIds.ProtectionOfAncientKings, SpellValueMod.BasePoint0, absorb, eventInfo.GetProcTarget(), true, null, aurEff); + } + + public override void Register() + { + DoCheckProc.Add(new CheckProcHandler(CheckProc)); + OnEffectProc.Add(new EffectProcHandler(HandleProc, 0, AuraType.Dummy)); + } + } + + public override AuraScript GetAuraScript() + { + return new spell_item_blessing_of_ancient_kings_AuraScript(); + } + } + + [Script] // 71564 - Deadly Precision + class spell_item_deadly_precision : SpellScriptLoader + { + public spell_item_deadly_precision() : base("spell_item_deadly_precision") { } + + class spell_item_deadly_precision_charm_AuraScript : AuraScript + { + void HandleStackDrop(AuraEffect aurEff, ProcEventInfo eventInfo) + { + PreventDefaultAction(); + GetTarget().RemoveAuraFromStack(GetId(), GetTarget().GetGUID()); + } + + public override void Register() + { + OnEffectProc.Add(new EffectProcHandler(HandleStackDrop, 0, AuraType.ModRating)); + } + } + + public override AuraScript GetAuraScript() + { + return new spell_item_deadly_precision_charm_AuraScript(); + } + } + + [Script] // 71563 - Deadly Precision Dummy + class spell_item_deadly_precision_dummy : SpellScriptLoader + { + public spell_item_deadly_precision_dummy() : base("spell_item_deadly_precision_dummy") { } + + class spell_item_deadly_precision_dummy_SpellScript : SpellScript + { + public override bool Validate(SpellInfo spellInfo) + { + return ValidateSpellInfo(SpellIds.DeadlyPrecision); + } + + void HandleDummy(uint effIndex) + { + SpellInfo spellInfo = Global.SpellMgr.GetSpellInfo(SpellIds.DeadlyPrecision); + GetCaster().CastCustomSpell(spellInfo.Id, SpellValueMod.AuraStack, (int)spellInfo.StackAmount, GetCaster(), true); + } + + public override void Register() + { + OnEffectHit.Add(new EffectHandler(HandleDummy, 0, SpellEffectName.ApplyAura)); + } + } + + public override SpellScript GetSpellScript() + { + return new spell_item_deadly_precision_dummy_SpellScript(); + } + } + + // Item - 50362: Deathbringer's Will + // 71519 - Item - Icecrown 25 Normal Melee Trinket + + // Item - 50363: Deathbringer's Will + // 71562 - Item - Icecrown 25 Heroic Melee Trinket + [Script("spell_item_deathbringers_will_normal", SpellIds.StrengthOfTheTaunka, SpellIds.AgilityOfTheVrykul, SpellIds.PowerOfTheTaunka, SpellIds.AimOfTheIronDwarves, SpellIds.SpeedOfTheVrykul)] + [Script("spell_item_deathbringers_will_heroic", SpellIds.StrengthOfTheTaunkaHero, SpellIds.AgilityOfTheVrykulHero, SpellIds.PowerOfTheTaunkaHero, SpellIds.AimOfTheIronDwarvesHero, SpellIds.SpeedOfTheVrykulHero)] + class spell_item_deathbringers_will : SpellScriptLoader + { + public spell_item_deathbringers_will(string scriptName, uint strengthSpellId, uint agilitySpellId, uint apSpellId, uint criticalSpellId, uint hasteSpellId) : base(scriptName) + { + _strengthSpellId = strengthSpellId; + _agilitySpellId = agilitySpellId; + _apSpellId = apSpellId; + _criticalSpellId = criticalSpellId; + _hasteSpellId = hasteSpellId; + } + + class spell_item_deathbringers_will_AuraScript : AuraScript + { + public spell_item_deathbringers_will_AuraScript(uint strengthSpellId, uint agilitySpellId, uint apSpellId, uint criticalSpellId, uint hasteSpellId) + { + _strengthSpellId = strengthSpellId; + _agilitySpellId = agilitySpellId; + _apSpellId = apSpellId; + _criticalSpellId = criticalSpellId; + _hasteSpellId = hasteSpellId; + } + + public override bool Validate(SpellInfo spellInfo) + { + return ValidateSpellInfo(_strengthSpellId, _agilitySpellId, _apSpellId, _criticalSpellId, _hasteSpellId); + } + + void HandleProc(AuraEffect aurEff, ProcEventInfo eventInfo) + { + uint[][] triggeredSpells = + { + //CLASS_NONE + new uint[] { }, + //CLASS_WARRIOR + new uint[] { _strengthSpellId, _criticalSpellId, _hasteSpellId }, + //CLASS_PALADIN + new uint[] { _strengthSpellId, _criticalSpellId, _hasteSpellId }, + //CLASS_HUNTER + new uint[] { _agilitySpellId, _criticalSpellId, _apSpellId }, + //CLASS_ROGUE + new uint[] { _agilitySpellId, _hasteSpellId, _apSpellId }, + //CLASS_PRIEST + new uint[] { }, + //CLASS_DEATH_KNIGHT + new uint[] { _strengthSpellId, _criticalSpellId, _hasteSpellId }, + //CLASS_SHAMAN + new uint[] { _agilitySpellId, _hasteSpellId, _apSpellId }, + //CLASS_MAGE + new uint[] { }, + //CLASS_WARLOCK + new uint[] { }, + //CLASS_UNK + new uint[] { }, + //CLASS_DRUID + new uint[] { _strengthSpellId, _agilitySpellId, _hasteSpellId } + }; + + PreventDefaultAction(); + Unit caster = eventInfo.GetActor(); + var randomSpells = triggeredSpells[(int)caster.GetClass()]; + if (randomSpells.Empty()) + return; + + uint spellId = randomSpells.PickRandom(); + caster.CastSpell(caster, spellId, true); + } + + public override void Register() + { + OnEffectProc.Add(new EffectProcHandler(HandleProc, 0, AuraType.Dummy)); + } + + uint _strengthSpellId; + uint _agilitySpellId; + uint _apSpellId; + uint _criticalSpellId; + uint _hasteSpellId; + } + + public override AuraScript GetAuraScript() + { + return new spell_item_deathbringers_will_AuraScript(_strengthSpellId, _agilitySpellId, _apSpellId, _criticalSpellId, _hasteSpellId); + } + + uint _strengthSpellId; + uint _agilitySpellId; + uint _apSpellId; + uint _criticalSpellId; + uint _hasteSpellId; + } + + [Script] // 47770 - Roll Dice + class spell_item_decahedral_dwarven_dice : SpellScriptLoader + { + public spell_item_decahedral_dwarven_dice() : base("spell_item_decahedral_dwarven_dice") { } + + class spell_item_decahedral_dwarven_dice_SpellScript : SpellScript + { + public override bool Validate(SpellInfo spellInfo) + { + if (!CliDB.BroadcastTextStorage.ContainsKey(TextIds.DecahedralDwarvenDice)) + return false; + return true; + } + + public override bool Load() + { + return GetCaster().GetTypeId() == TypeId.Player; + } + + void HandleScript(uint effIndex) + { + GetCaster().TextEmote(TextIds.DecahedralDwarvenDice, GetHitUnit()); + + uint minimum = 1; + uint maximum = 100; + + GetCaster().ToPlayer().DoRandomRoll(minimum, maximum); + } + + public override void Register() + { + OnEffectHitTarget.Add(new EffectHandler(HandleScript, 0, SpellEffectName.ScriptEffect)); + } + } + + public override SpellScript GetSpellScript() + { + return new spell_item_decahedral_dwarven_dice_SpellScript(); + } + } + + // 8342 - Defibrillate (Goblin Jumper Cables) have 33% chance on success + // 22999 - Defibrillate (Goblin Jumper Cables XL) have 50% chance on success + // 54732 - Defibrillate (Gnomish Army Knife) have 67% chance on success + [Script("spell_item_goblin_jumper_cables", 33u, SpellIds.GoblinJumperCablesFail)] + [Script("spell_item_goblin_jumper_cables_xl", 50u, SpellIds.GoblinJumperCablesXlFail)] + [Script("spell_item_gnomish_army_knife", 67u)] + class spell_item_defibrillate : SpellScriptLoader + { + public spell_item_defibrillate(string name, uint chance, uint failSpell = 0) : base(name) + { + _chance = chance; + _failSpell = failSpell; + } + + class spell_item_defibrillate_SpellScript : SpellScript + { + public spell_item_defibrillate_SpellScript(uint chance, uint failSpell) : base() + { + _chance = chance; + _failSpell = failSpell; + } + + public override bool Validate(SpellInfo spellInfo) + { + if (_failSpell != 0 && !ValidateSpellInfo(_failSpell)) + return false; + return true; + } + + void HandleScript(uint effIndex) + { + if (RandomHelper.randChance(_chance)) + { + PreventHitDefaultEffect(effIndex); + if (_failSpell != 0) + GetCaster().CastSpell(GetCaster(), _failSpell, true, GetCastItem()); + } + } + + public override void Register() + { + OnEffectHitTarget.Add(new EffectHandler(HandleScript, 0, SpellEffectName.Resurrect)); + } + + uint _chance; + uint _failSpell; + } + + public override SpellScript GetSpellScript() + { + return new spell_item_defibrillate_SpellScript(_chance, _failSpell); + } + + uint _chance; + uint _failSpell; + } + + [Script] // 33896 - Desperate Defense + class spell_item_desperate_defense : SpellScriptLoader + { + public spell_item_desperate_defense() : base("spell_item_desperate_defense") { } + + class spell_item_desperate_defense_AuraScript : AuraScript + { + public override bool Validate(SpellInfo spellInfo) + { + return ValidateSpellInfo(SpellIds.DesperateRage); + } + + void HandleProc(AuraEffect aurEff, ProcEventInfo eventInfo) + { + PreventDefaultAction(); + GetTarget().CastSpell(GetTarget(), SpellIds.DesperateRage, true, null, aurEff); + } + + public override void Register() + { + OnEffectProc.Add(new EffectProcHandler(HandleProc, 2, AuraType.PeriodicTriggerSpell)); + } + } + + public override AuraScript GetAuraScript() + { + return new spell_item_desperate_defense_AuraScript(); + } + } + + // http://www.wowhead.com/item=6522 Deviate Fish + [Script] // 8063 Deviate Fish + class spell_item_deviate_fish : SpellScriptLoader + { + public spell_item_deviate_fish() : base("spell_item_deviate_fish") { } + + class spell_item_deviate_fish_SpellScript : SpellScript + { + public override bool Load() + { + return GetCaster().GetTypeId() == TypeId.Player; + } + + public override bool Validate(SpellInfo spellInfo) + { + return ValidateSpellInfo(SpellIds.Sleepy, SpellIds.Invigorate, SpellIds.Shrink, SpellIds.PartyTime, SpellIds.HealthySpirit); + } + + void HandleDummy(uint effIndex) + { + Unit caster = GetCaster(); + uint spellId = RandomHelper.URand(SpellIds.Sleepy, SpellIds.HealthySpirit); + caster.CastSpell(caster, spellId, true, null); + } + + public override void Register() + { + OnEffectHit.Add(new EffectHandler(HandleDummy, 0, SpellEffectName.Dummy)); + } + } + + public override SpellScript GetSpellScript() + { + return new spell_item_deviate_fish_SpellScript(); + } + } + + [Script] // 59915 - Discerning Eye of the Beast Dummy + class spell_item_discerning_eye_beast_dummy : SpellScriptLoader + { + public spell_item_discerning_eye_beast_dummy() : base("spell_item_discerning_eye_beast_dummy") { } + + class spell_item_discerning_eye_beast_dummy_AuraScript : AuraScript + { + public override bool Validate(SpellInfo spellInfo) + { + return ValidateSpellInfo(SpellIds.DiscerningEyeBeast); + } + + void HandleProc(AuraEffect aurEff, ProcEventInfo eventInfo) + { + PreventDefaultAction(); + eventInfo.GetActor().CastSpell((Unit)null, SpellIds.DiscerningEyeBeast, true, null, aurEff); + } + + public override void Register() + { + OnEffectProc.Add(new EffectProcHandler(HandleProc, 0, AuraType.Dummy)); + } + } + + public override AuraScript GetAuraScript() + { + return new spell_item_discerning_eye_beast_dummy_AuraScript(); + } + } + + [Script] // 71610, 71641 - Echoes of Light (Althor's Abacus) + class spell_item_echoes_of_light : SpellScriptLoader + { + public spell_item_echoes_of_light() : base("spell_item_echoes_of_light") { } + + class spell_item_echoes_of_light_SpellScript : SpellScript + { + void FilterTargets(List targets) + { + if (targets.Count < 2) + return; + + targets.Sort(new HealthPctOrderPred()); + + WorldObject target = targets.FirstOrDefault(); + targets.Clear(); + targets.Add(target); + } + + public override void Register() + { + OnObjectAreaTargetSelect.Add(new ObjectAreaTargetSelectHandler(FilterTargets, 0, Targets.UnitDestAreaAlly)); + } + } + + public override SpellScript GetSpellScript() + { + return new spell_item_echoes_of_light_SpellScript(); + } + } + + [Script] // 7434 - Fate Rune of Unsurpassed Vigor + class spell_item_fate_rune_of_unsurpassed_vigor : SpellScriptLoader + { + public spell_item_fate_rune_of_unsurpassed_vigor() : base("spell_item_fate_rune_of_unsurpassed_vigor") { } + + class spell_item_fate_rune_of_unsurpassed_vigor_AuraScript : AuraScript + { + public override bool Validate(SpellInfo spellInfo) + { + return ValidateSpellInfo(SpellIds.UnsurpassedVigor); + } + + void HandleProc(AuraEffect aurEff, ProcEventInfo eventInfo) + { + PreventDefaultAction(); + GetTarget().CastSpell(GetTarget(), SpellIds.UnsurpassedVigor, true); + } + + public override void Register() + { + OnEffectProc.Add(new EffectProcHandler(HandleProc, 0, AuraType.Dummy)); + } + } + + public override AuraScript GetAuraScript() + { + return new spell_item_fate_rune_of_unsurpassed_vigor_AuraScript(); + } + } + + // http://www.wowhead.com/item=47499 Flask of the North + [Script] // 67019 Flask of the North + class spell_item_flask_of_the_north : SpellScriptLoader + { + public spell_item_flask_of_the_north() : base("spell_item_flask_of_the_north") { } + + class spell_item_flask_of_the_north_SpellScript : SpellScript + { + public override bool Validate(SpellInfo spellInfo) + { + return ValidateSpellInfo(SpellIds.FlaskOfTheNorthSp, SpellIds.FlaskOfTheNorthAp, SpellIds.FlaskOfTheNorthStr); + } + + void HandleDummy(uint effIndex) + { + Unit caster = GetCaster(); + List possibleSpells = new List(); + switch (caster.GetClass()) + { + case Class.Warlock: + case Class.Mage: + case Class.Priest: + possibleSpells.Add(SpellIds.FlaskOfTheNorthSp); + break; + case Class.Deathknight: + case Class.Warrior: + possibleSpells.Add(SpellIds.FlaskOfTheNorthStr); + break; + case Class.Rogue: + case Class.Hunter: + possibleSpells.Add(SpellIds.FlaskOfTheNorthAp); + break; + case Class.Druid: + case Class.Paladin: + possibleSpells.Add(SpellIds.FlaskOfTheNorthSp); + possibleSpells.Add(SpellIds.FlaskOfTheNorthStr); + break; + case Class.Shaman: + possibleSpells.Add(SpellIds.FlaskOfTheNorthSp); + possibleSpells.Add(SpellIds.FlaskOfTheNorthAp); + break; + } + + if (possibleSpells.Empty()) + { + Log.outWarn(LogFilter.Spells, "Missing spells for class {0} in script spell_item_flask_of_the_north", caster.GetClass()); + return; + } + + caster.CastSpell(caster, possibleSpells.PickRandom(), true); + } + + public override void Register() + { + OnEffectHit.Add(new EffectHandler(HandleDummy, 0, SpellEffectName.Dummy)); + } + } + + public override SpellScript GetSpellScript() + { + return new spell_item_flask_of_the_north_SpellScript(); + } + } + + // 39372 - Frozen Shadoweave + [Script] // Frozen Shadoweave set 3p bonus + class spell_item_frozen_shadoweave : SpellScriptLoader + { + public spell_item_frozen_shadoweave() : base("spell_item_frozen_shadoweave") { } + + class spell_item_frozen_shadoweave_AuraScript : AuraScript + { + public override bool Validate(SpellInfo spellInfo) + { + return ValidateSpellInfo(SpellIds.Shadowmend); + } + + void HandleProc(AuraEffect aurEff, ProcEventInfo eventInfo) + { + PreventDefaultAction(); + DamageInfo damageInfo = eventInfo.GetDamageInfo(); + if (damageInfo == null || damageInfo.GetDamage() == 0) + return; + + int amount = (int)MathFunctions.CalculatePct(damageInfo.GetDamage(), aurEff.GetAmount()); + Unit caster = eventInfo.GetActor(); + caster.CastCustomSpell(SpellIds.Shadowmend, SpellValueMod.BasePoint0, amount, (Unit)null, true); + } + + public override void Register() + { + OnEffectProc.Add(new EffectProcHandler(HandleProc, 0, AuraType.Dummy)); + } + } + + public override AuraScript GetAuraScript() + { + return new spell_item_frozen_shadoweave_AuraScript(); + } + } + + // http://www.wowhead.com/item=10645 Gnomish Death Ray + [Script] // 13280 Gnomish Death Ray + class spell_item_gnomish_death_ray : SpellScriptLoader + { + public spell_item_gnomish_death_ray() : base("spell_item_gnomish_death_ray") { } + + class spell_item_gnomish_death_ray_SpellScript : SpellScript + { + public override bool Validate(SpellInfo spellInfo) + { + return ValidateSpellInfo(SpellIds.GnomishDeathRaySelf, SpellIds.GnomishDeathRayTarget); + } + + void HandleDummy(uint effIndex) + { + Unit caster = GetCaster(); + Unit target = GetHitUnit(); + if (target) + { + if (RandomHelper.URand(0, 99) < 15) + caster.CastSpell(caster, SpellIds.GnomishDeathRaySelf, true, null); // failure + else + caster.CastSpell(target, SpellIds.GnomishDeathRayTarget, true, null); + } + } + + public override void Register() + { + OnEffectHitTarget.Add(new EffectHandler(HandleDummy, 0, SpellEffectName.Dummy)); + } + } + + public override SpellScript GetSpellScript() + { + return new spell_item_gnomish_death_ray_SpellScript(); + } + } + + // Item - 49982: Heartpierce + // 71880 - Item - Icecrown 25 Normal Dagger Proc + + // Item - 50641: Heartpierce (Heroic) + // 71892 - Item - Icecrown 25 Heroic Dagger Proc + [Script("spell_item_heartpierce", SpellIds.InvigorationEnergy, SpellIds.InvigorationMana, SpellIds.InvigorationRage, SpellIds.InvigorationRp)] + [Script("spell_item_heartpierce_hero", SpellIds.InvigorationEnergyHero, SpellIds.InvigorationManaHero, SpellIds.InvigorationRageHero, SpellIds.InvigorationRpHero)] + class spell_item_heartpierce : SpellScriptLoader + { + public spell_item_heartpierce(string scriptName, uint energySpellId, uint manaSpellId, uint rageSpellId, uint rpSpellId) : base(scriptName) + { + _energySpellId = energySpellId; + _manaSpellId = manaSpellId; + _rageSpellId = rageSpellId; + _rpSpellId = rpSpellId; + } + + class spell_item_heartpierce_AuraScript : AuraScript + { + public spell_item_heartpierce_AuraScript(uint energySpellId, uint manaSpellId, uint rageSpellId, uint rpSpellId) + { + _energySpellId = energySpellId; + _manaSpellId = manaSpellId; + _rageSpellId = rageSpellId; + _rpSpellId = rpSpellId; + } + + public override bool Validate(SpellInfo spellInfo) + { + return ValidateSpellInfo(_energySpellId, _manaSpellId, _rageSpellId, _rpSpellId); + } + + void HandleProc(AuraEffect aurEff, ProcEventInfo eventInfo) + { + PreventDefaultAction(); + Unit caster = eventInfo.GetActor(); + + uint spellId; + switch (caster.getPowerType()) + { + case PowerType.Mana: + spellId = _manaSpellId; + break; + case PowerType.Energy: + spellId = _energySpellId; + break; + case PowerType.Rage: + spellId = _rageSpellId; + break; + case PowerType.RunicPower: + spellId = _rpSpellId; + break; + default: + return; + } + + caster.CastSpell((Unit)null, spellId, true); + } + + public override void Register() + { + OnEffectProc.Add(new EffectProcHandler(HandleProc, 0, AuraType.Dummy)); + } + + uint _energySpellId; + uint _manaSpellId; + uint _rageSpellId; + uint _rpSpellId; + } + + public override AuraScript GetAuraScript() + { + return new spell_item_heartpierce_AuraScript(_energySpellId, _manaSpellId, _rageSpellId, _rpSpellId); + } + + uint _energySpellId; + uint _manaSpellId; + uint _rageSpellId; + uint _rpSpellId; + } + + [Script] // 40971 - Bonus Healing (Crystal Spire of Karabor) + class spell_item_crystal_spire_of_karabor : SpellScriptLoader + { + public spell_item_crystal_spire_of_karabor() : base("spell_item_crystal_spire_of_karabor") { } + + class spell_item_crystal_spire_of_karabor_AuraScript : AuraScript + { + public override bool Validate(SpellInfo spellInfo) + { + return spellInfo.GetEffect(0) != null; + } + + bool CheckProc(ProcEventInfo eventInfo) + { + int pct = GetSpellInfo().GetEffect(0).CalcValue(); + HealInfo healInfo = eventInfo.GetHealInfo(); + if (healInfo != null) + { + Unit healTarget = healInfo.GetTarget(); + if (healTarget) + if (healTarget.GetHealth() - healInfo.GetEffectiveHeal() <= healTarget.CountPctFromMaxHealth(pct)) + return true; + } + + return false; + } + + public override void Register() + { + DoCheckProc.Add(new CheckProcHandler(CheckProc)); + } + } + + public override AuraScript GetAuraScript() + { + return new spell_item_crystal_spire_of_karabor_AuraScript(); + } + } + + // http://www.wowhead.com/item=27388 Mr. Pinchy + [Script] // 33060 Make a Wish + class spell_item_make_a_wish : SpellScriptLoader + { + public spell_item_make_a_wish() : base("spell_item_make_a_wish") { } + + class spell_item_make_a_wish_SpellScript : SpellScript + { + public override bool Load() + { + return GetCaster().GetTypeId() == TypeId.Player; + } + + public override bool Validate(SpellInfo spellInfo) + { + return ValidateSpellInfo(SpellIds.MrPinchysBlessing, SpellIds.SummonMightyMrPinchy, SpellIds.SummonFuriousMrPinchy, SpellIds.TinyMagicalCrawdad, SpellIds.MrPinchysGift); + } + + void HandleDummy(uint effIndex) + { + Unit caster = GetCaster(); + uint spellId = SpellIds.MrPinchysGift; + switch (RandomHelper.URand(1, 5)) + { + case 1: + spellId = SpellIds.MrPinchysBlessing; + break; + case 2: + spellId = SpellIds.SummonMightyMrPinchy; + break; + case 3: + spellId = SpellIds.SummonFuriousMrPinchy; + break; + case 4: + spellId = SpellIds.TinyMagicalCrawdad; + break; + } + caster.CastSpell(caster, spellId, true, null); + } + + public override void Register() + { + OnEffectHit.Add(new EffectHandler(HandleDummy, 0, SpellEffectName.Dummy)); + } + } + + public override SpellScript GetSpellScript() + { + return new spell_item_make_a_wish_SpellScript(); + } + } + + // Item - 27920: Mark of Conquest + // Item - 27921: Mark of Conquest + [Script] // 33510 - Health Restore + class spell_item_mark_of_conquest : SpellScriptLoader + { + public spell_item_mark_of_conquest() : base("spell_item_mark_of_conquest") { } + + class spell_item_mark_of_conquest_AuraScript : AuraScript + { + public override bool Validate(SpellInfo spellInfo) + { + return ValidateSpellInfo(SpellIds.MarkOfConquestEnergize); + } + + void HandleProc(AuraEffect aurEff, ProcEventInfo eventInfo) + { + if (eventInfo.GetTypeMask().HasAnyFlag(ProcFlags.DoneRangedAutoAttack | ProcFlags.DoneSpellRangedDmgClass)) + { + // in that case, do not cast heal spell + PreventDefaultAction(); + // but mana instead + eventInfo.GetActor().CastSpell((Unit)null, SpellIds.MarkOfConquestEnergize, true); + } + } + + public override void Register() + { + OnEffectProc.Add(new EffectProcHandler(HandleProc, 0, AuraType.PeriodicTriggerSpell)); + } + } + + public override AuraScript GetAuraScript() + { + return new spell_item_mark_of_conquest_AuraScript(); + } + } + + // http://www.wowhead.com/item=32686 Mingo's Fortune Giblets + [Script] // 40802 Mingo's Fortune Generator + class spell_item_mingos_fortune_generator : SpellScriptLoader + { + public spell_item_mingos_fortune_generator() : base("spell_item_mingos_fortune_generator") { } + + class spell_item_mingos_fortune_generator_SpellScript : SpellScript + { + void HandleDummy(uint effIndex) + { + // Selecting one from Bloodstained Fortune item + uint newitemid; + switch (RandomHelper.URand(1, 20)) + { + case 1: newitemid = 32688; break; + case 2: newitemid = 32689; break; + case 3: newitemid = 32690; break; + case 4: newitemid = 32691; break; + case 5: newitemid = 32692; break; + case 6: newitemid = 32693; break; + case 7: newitemid = 32700; break; + case 8: newitemid = 32701; break; + case 9: newitemid = 32702; break; + case 10: newitemid = 32703; break; + case 11: newitemid = 32704; break; + case 12: newitemid = 32705; break; + case 13: newitemid = 32706; break; + case 14: newitemid = 32707; break; + case 15: newitemid = 32708; break; + case 16: newitemid = 32709; break; + case 17: newitemid = 32710; break; + case 18: newitemid = 32711; break; + case 19: newitemid = 32712; break; + case 20: newitemid = 32713; break; + default: + return; + } + + CreateItem(effIndex, newitemid); + } + + public override void Register() + { + OnEffectHit.Add(new EffectHandler(HandleDummy, 0, SpellEffectName.Dummy)); + } + } + + public override SpellScript GetSpellScript() + { + return new spell_item_mingos_fortune_generator_SpellScript(); + } + } + + [Script] // 71875, 71877 - Item - Black Bruise: Necrotic Touch Proc + class spell_item_necrotic_touch : SpellScriptLoader + { + public spell_item_necrotic_touch() : base("spell_item_necrotic_touch") { } + + class spell_item_necrotic_touch_AuraScript : AuraScript + { + public override bool Validate(SpellInfo spellInfo) + { + return ValidateSpellInfo(SpellIds.ItemNecroticTouchProc); + } + + bool CheckProc(ProcEventInfo eventInfo) + { + return eventInfo.GetProcTarget() && eventInfo.GetProcTarget().IsAlive(); + } + + void HandleProc(AuraEffect aurEff, ProcEventInfo eventInfo) + { + PreventDefaultAction(); + int bp = (int)MathFunctions.CalculatePct(eventInfo.GetDamageInfo().GetDamage(), aurEff.GetAmount()); + GetTarget().CastCustomSpell(SpellIds.ItemNecroticTouchProc, SpellValueMod.BasePoint0, bp, eventInfo.GetProcTarget(), true, null, aurEff); + } + + public override void Register() + { + DoCheckProc.Add(new CheckProcHandler(CheckProc)); + OnEffectProc.Add(new EffectProcHandler(HandleProc, 0, AuraType.Dummy)); + } + } + + public override AuraScript GetAuraScript() + { + return new spell_item_necrotic_touch_AuraScript(); + } + } + + // http://www.wowhead.com/item=10720 Gnomish Net-o-Matic Projector + [Script] // 13120 Net-o-Matic + class spell_item_net_o_matic : SpellScriptLoader + { + public spell_item_net_o_matic() : base("spell_item_net_o_matic") { } + + class spell_item_net_o_matic_SpellScript : SpellScript + { + public override bool Validate(SpellInfo spellInfo) + { + return ValidateSpellInfo(SpellIds.NetOMaticTriggered1, SpellIds.NetOMaticTriggered2, SpellIds.NetOMaticTriggered3); + } + + void HandleDummy(uint effIndex) + { + Unit target = GetHitUnit(); + if (target) + { + uint spellId = SpellIds.NetOMaticTriggered3; + uint roll = RandomHelper.URand(0, 99); + if (roll < 2) // 2% for 30 sec self root (off-like chance unknown) + spellId = SpellIds.NetOMaticTriggered1; + else if (roll < 4) // 2% for 20 sec root, charge to target (off-like chance unknown) + spellId = SpellIds.NetOMaticTriggered2; + + GetCaster().CastSpell(target, spellId, true, null); + } + } + + public override void Register() + { + OnEffectHitTarget.Add(new EffectHandler(HandleDummy, 0, SpellEffectName.Dummy)); + } + } + + public override SpellScript GetSpellScript() + { + return new spell_item_net_o_matic_SpellScript(); + } + } + + // http://www.wowhead.com/item=8529 Noggenfogger Elixir + [Script] // 16589 Noggenfogger Elixir + class spell_item_noggenfogger_elixir : SpellScriptLoader + { + public spell_item_noggenfogger_elixir() : base("spell_item_noggenfogger_elixir") { } + + class spell_item_noggenfogger_elixir_SpellScript : SpellScript + { + public override bool Load() + { + return GetCaster().GetTypeId() == TypeId.Player; + } + + public override bool Validate(SpellInfo spellInfo) + { + return ValidateSpellInfo(SpellIds.NoggenfoggerElixirTriggered1, SpellIds.NoggenfoggerElixirTriggered2, SpellIds.NoggenfoggerElixirTriggered3); + } + + void HandleDummy(uint effIndex) + { + Unit caster = GetCaster(); + uint spellId = SpellIds.NoggenfoggerElixirTriggered3; + switch (RandomHelper.URand(1, 3)) + { + case 1: + spellId = SpellIds.NoggenfoggerElixirTriggered1; + break; + case 2: + spellId = SpellIds.NoggenfoggerElixirTriggered2; + break; + } + + caster.CastSpell(caster, spellId, true, null); + } + + public override void Register() + { + OnEffectHit.Add(new EffectHandler(HandleDummy, 0, SpellEffectName.Dummy)); + } + } + + public override SpellScript GetSpellScript() + { + return new spell_item_noggenfogger_elixir_SpellScript(); + } + } + + [Script] // 29601 - Enlightenment (Pendant of the Violet Eye) + class spell_item_pendant_of_the_violet_eye : SpellScriptLoader + { + public spell_item_pendant_of_the_violet_eye() : base("spell_item_pendant_of_the_violet_eye") { } + + class spell_item_pendant_of_the_violet_eye_AuraScript : AuraScript + { + bool CheckProc(ProcEventInfo eventInfo) + { + SpellInfo spellInfo = eventInfo.GetSpellInfo(); + if (spellInfo != null) + { + var costs = spellInfo.CalcPowerCost(GetTarget(), spellInfo.GetSchoolMask()); + var m = costs.FirstOrDefault(cost => { return cost.Power == PowerType.Mana && cost.Amount > 0; }); + if (m != null) + return true; + } + + return false; + } + + public override void Register() + { + DoCheckProc.Add(new CheckProcHandler(CheckProc)); + } + } + + public override AuraScript GetAuraScript() + { + return new spell_item_pendant_of_the_violet_eye_AuraScript(); + } + } + + [Script] // 26467 - Persistent Shield + class spell_item_persistent_shield : SpellScriptLoader + { + public spell_item_persistent_shield() : base("spell_item_persistent_shield") { } + + class spell_item_persistent_shield_AuraScript : AuraScript + { + public override bool Validate(SpellInfo spellInfo) + { + return ValidateSpellInfo(SpellIds.PersistentShieldTriggered); + } + + bool CheckProc(ProcEventInfo eventInfo) + { + return eventInfo.GetHealInfo() != null && eventInfo.GetHealInfo().GetHeal() != 0; + } + + void HandleProc(AuraEffect aurEff, ProcEventInfo eventInfo) + { + Unit caster = eventInfo.GetActor(); + Unit target = eventInfo.GetProcTarget(); + int bp0 = (int)MathFunctions.CalculatePct(eventInfo.GetHealInfo().GetHeal(), 15); + + // Scarab Brooch does not replace stronger shields + AuraEffect shield = target.GetAuraEffect(SpellIds.PersistentShieldTriggered, 0, caster.GetGUID()); + if (shield != null) + if (shield.GetAmount() > bp0) + return; + + caster.CastCustomSpell(SpellIds.PersistentShieldTriggered, SpellValueMod.BasePoint0, bp0, target, true, null, aurEff); + } + + public override void Register() + { + DoCheckProc.Add(new CheckProcHandler(CheckProc)); + OnEffectProc.Add(new EffectProcHandler(HandleProc, 0, AuraType.PeriodicTriggerSpell)); + } + } + + public override AuraScript GetAuraScript() + { + return new spell_item_persistent_shield_AuraScript(); + } + } + + // 37381 - Pet Healing + // Hunter T5 2P Bonus + [Script] // Warlock T5 2P Bonus + class spell_item_pet_healing : SpellScriptLoader + { + public spell_item_pet_healing() : base("spell_item_pet_healing") { } + + class spell_item_pet_healing_AuraScript : AuraScript + { + public override bool Validate(SpellInfo spellInfo) + { + return ValidateSpellInfo(SpellIds.HealthLink); + } + + void HandleProc(AuraEffect aurEff, ProcEventInfo eventInfo) + { + PreventDefaultAction(); + DamageInfo damageInfo = eventInfo.GetDamageInfo(); + if (damageInfo == null || damageInfo.GetDamage() == 0) + return; + + int bp = (int)MathFunctions.CalculatePct(damageInfo.GetDamage(), aurEff.GetAmount()); + Unit caster = eventInfo.GetActor(); + caster.CastCustomSpell(SpellIds.HealthLink, SpellValueMod.BasePoint0, bp, (Unit)null, true); + } + + public override void Register() + { + OnEffectProc.Add(new EffectProcHandler(HandleProc, 0, AuraType.Dummy)); + } + } + + public override AuraScript GetAuraScript() + { + return new spell_item_pet_healing_AuraScript(); + } + } + + [Script] // 17512 - Piccolo of the Flaming Fire + class spell_item_piccolo_of_the_flaming_fire : SpellScriptLoader + { + public spell_item_piccolo_of_the_flaming_fire() : base("spell_item_piccolo_of_the_flaming_fire") { } + + class spell_item_piccolo_of_the_flaming_fire_SpellScript : SpellScript + { + void HandleScript(uint effIndex) + { + PreventHitDefaultEffect(effIndex); + Player target = GetHitPlayer(); + if (target) + target.HandleEmoteCommand(Emote.StateDance); + } + + public override void Register() + { + OnEffectHitTarget.Add(new EffectHandler(HandleScript, 0, SpellEffectName.ScriptEffect)); + } + } + + public override SpellScript GetSpellScript() + { + return new spell_item_piccolo_of_the_flaming_fire_SpellScript(); + } + } + + // http://www.wowhead.com/item=6657 Savory Deviate Delight + [Script] // 8213 Savory Deviate Delight + class spell_item_savory_deviate_delight : SpellScriptLoader + { + public spell_item_savory_deviate_delight() : base("spell_item_savory_deviate_delight") { } + + class spell_item_savory_deviate_delight_SpellScript : SpellScript + { + public override bool Load() + { + return GetCaster().GetTypeId() == TypeId.Player; + } + + public override bool Validate(SpellInfo spellInfo) + { + return ValidateSpellInfo(SpellIds.FlipOutMale, SpellIds.FlipOutFemale, SpellIds.YaaarrrrMale, SpellIds.YaaarrrrFemale); + } + + void HandleDummy(uint effIndex) + { + Unit caster = GetCaster(); + uint spellId = 0; + switch (RandomHelper.URand(1, 2)) + { + // Flip Out - ninja + case 1: spellId = (caster.GetGender() == Gender.Male ? SpellIds.FlipOutMale : SpellIds.FlipOutFemale); break; + // Yaaarrrr - pirate + case 2: spellId = (caster.GetGender() == Gender.Male ? SpellIds.YaaarrrrMale : SpellIds.YaaarrrrFemale); break; + } + caster.CastSpell(caster, spellId, true, null); + } + + public override void Register() + { + OnEffectHit.Add(new EffectHandler(HandleDummy, 0, SpellEffectName.Dummy)); + } + } + + public override SpellScript GetSpellScript() + { + return new spell_item_savory_deviate_delight_SpellScript(); + } + } + + // 48129 - Scroll of Recall + // 60320 - Scroll of Recall II + [Script] // 60321 - Scroll of Recall III + class spell_item_scroll_of_recall : SpellScriptLoader + { + public spell_item_scroll_of_recall() : base("spell_item_scroll_of_recall") { } + + class spell_item_scroll_of_recall_SpellScript : SpellScript + { + public override bool Load() + { + return GetCaster().GetTypeId() == TypeId.Player; + } + + void HandleScript(uint effIndex) + { + Unit caster = GetCaster(); + byte maxSafeLevel = 0; + switch (GetSpellInfo().Id) + { + case SpellIds.ScrollOfRecallI: // Scroll of Recall + maxSafeLevel = 40; + break; + case SpellIds.ScrollOfRecallII: // Scroll of Recall II + maxSafeLevel = 70; + break; + case SpellIds.ScrollOfRecallIII: // Scroll of Recal III + maxSafeLevel = 80; + break; + default: + break; + } + + if (caster.getLevel() > maxSafeLevel) + { + caster.CastSpell(caster, SpellIds.Lost, true); + + // ALLIANCE from 60323 to 60330 - HORDE from 60328 to 60335 + uint spellId = SpellIds.ScrollOfRecallFailAlliance1; + if (GetCaster().ToPlayer().GetTeam() == Team.Horde) + spellId = SpellIds.ScrollOfRecallFailHorde1; + + GetCaster().CastSpell(GetCaster(), spellId + RandomHelper.URand(0, 7), true); + + PreventHitDefaultEffect(effIndex); + } + } + + public override void Register() + { + OnEffectHitTarget.Add(new EffectHandler(HandleScript, 0, SpellEffectName.TeleportUnits)); + } + } + + public override SpellScript GetSpellScript() + { + return new spell_item_scroll_of_recall_SpellScript(); + } + } + + [Script] // 71169 - Shadow's Fate (Shadowmourne questline) + class spell_item_unsated_craving : SpellScriptLoader + { + public spell_item_unsated_craving() : base("spell_item_unsated_craving") { } + + class spell_item_unsated_craving_AuraScript : AuraScript + { + bool CheckProc(ProcEventInfo procInfo) + { + Unit caster = procInfo.GetActor(); + if (!caster || caster.GetTypeId() != TypeId.Player) + return false; + + Unit target = procInfo.GetActionTarget(); + if (!target || target.GetTypeId() != TypeId.Unit || target.IsCritter() || (target.GetEntry() != CreatureIds.Sindragosa && target.IsSummon())) + return false; + + return true; + } + + public override void Register() + { + DoCheckProc.Add(new CheckProcHandler(CheckProc)); + } + } + + public override AuraScript GetAuraScript() + { + return new spell_item_unsated_craving_AuraScript(); + } + } + + [Script] + class spell_item_shadows_fate : SpellScriptLoader + { + public spell_item_shadows_fate() : base("spell_item_shadows_fate") { } + + class spell_item_shadows_fate_AuraScript : AuraScript + { + void HandleProc(ProcEventInfo procInfo) + { + Unit caster = procInfo.GetActor(); + Unit target = GetCaster(); + if (!caster || !target) + return; + + caster.CastSpell(target, SpellIds.SoulFeast, TriggerCastFlags.FullMask); + } + + public override void Register() + { + OnProc.Add(new AuraProcHandler(HandleProc)); + } + } + + public override AuraScript GetAuraScript() + { + return new spell_item_shadows_fate_AuraScript(); + } + } + + [Script] // 71903 - Item - Shadowmourne Legendary + class spell_item_shadowmourne : SpellScriptLoader + { + public spell_item_shadowmourne() : base("spell_item_shadowmourne") { } + + class spell_item_shadowmourne_AuraScript : AuraScript + { + public override bool Validate(SpellInfo spellInfo) + { + return ValidateSpellInfo(SpellIds.ShadowmourneChaosBaneDamage, SpellIds.ShadowmourneSoulFragment, SpellIds.ShadowmourneChaosBaneBuff); + } + + bool CheckProc(ProcEventInfo eventInfo) + { + if (GetTarget().HasAura(SpellIds.ShadowmourneChaosBaneBuff)) // cant collect shards while under effect of Chaos Bane buff + return false; + return eventInfo.GetProcTarget() && eventInfo.GetProcTarget().IsAlive(); + } + + void HandleProc(AuraEffect aurEff, ProcEventInfo eventInfo) + { + PreventDefaultAction(); + GetTarget().CastSpell(GetTarget(), SpellIds.ShadowmourneSoulFragment, true, null, aurEff); + + // this can't be handled in AuraScript of SoulFragments because we need to know victim + Aura soulFragments = GetTarget().GetAura(SpellIds.ShadowmourneSoulFragment); + if (soulFragments != null) + { + if (soulFragments.GetStackAmount() >= 10) + { + GetTarget().CastSpell(eventInfo.GetProcTarget(), SpellIds.ShadowmourneChaosBaneDamage, true, null, aurEff); + soulFragments.Remove(); + } + } + } + + public override void Register() + { + DoCheckProc.Add(new CheckProcHandler(CheckProc)); + OnEffectProc.Add(new EffectProcHandler(HandleProc, 0, AuraType.Dummy)); + } + } + + public override AuraScript GetAuraScript() + { + return new spell_item_shadowmourne_AuraScript(); + } + } + + [Script] // 71905 - Soul Fragment + class spell_item_shadowmourne_soul_fragment : SpellScriptLoader + { + public spell_item_shadowmourne_soul_fragment() : base("spell_item_shadowmourne_soul_fragment") { } + + class spell_item_shadowmourne_soul_fragment_AuraScript : AuraScript + { + public override bool Validate(SpellInfo spellInfo) + { + return ValidateSpellInfo(SpellIds.ShadowmourneVisualLow, SpellIds.ShadowmourneVisualHigh, SpellIds.ShadowmourneChaosBaneBuff); + } + + void OnStackChange(AuraEffect aurEff, AuraEffectHandleModes mode) + { + Unit target = GetTarget(); + switch (GetStackAmount()) + { + case 1: + target.CastSpell(target, SpellIds.ShadowmourneVisualLow, true); + break; + case 6: + target.RemoveAurasDueToSpell(SpellIds.ShadowmourneVisualLow); + target.CastSpell(target, SpellIds.ShadowmourneVisualHigh, true); + break; + case 10: + target.RemoveAurasDueToSpell(SpellIds.ShadowmourneVisualHigh); + target.CastSpell(target, SpellIds.ShadowmourneChaosBaneBuff, true); + break; + default: + break; + } + } + + void OnRemove(AuraEffect aurEff, AuraEffectHandleModes mode) + { + Unit target = GetTarget(); + target.RemoveAurasDueToSpell(SpellIds.ShadowmourneVisualLow); + target.RemoveAurasDueToSpell(SpellIds.ShadowmourneVisualHigh); + } + + public override void Register() + { + AfterEffectApply.Add(new EffectApplyHandler(OnStackChange, 0, AuraType.ModStat, AuraEffectHandleModes.Real | AuraEffectHandleModes.Reapply)); + AfterEffectRemove.Add(new EffectApplyHandler(OnRemove, 0, AuraType.ModStat, AuraEffectHandleModes.Real)); + } + } + + public override AuraScript GetAuraScript() + { + return new spell_item_shadowmourne_soul_fragment_AuraScript(); + } + } + + // http://www.wowhead.com/item=7734 Six Demon Bag + [Script] // 14537 Six Demon Bag + class spell_item_six_demon_bag : SpellScriptLoader + { + public spell_item_six_demon_bag() : base("spell_item_six_demon_bag") { } + + class spell_item_six_demon_bag_SpellScript : SpellScript + { + public override bool Validate(SpellInfo spellInfo) + { + return ValidateSpellInfo(SpellIds.Frostbolt, SpellIds.Polymorph, SpellIds.SummonFelhoundMinion, SpellIds.Fireball, SpellIds.ChainLightning, SpellIds.EnvelopingWinds); + } + + void HandleDummy(uint effIndex) + { + Unit caster = GetCaster(); + Unit target = GetHitUnit(); + if (target) + { + uint spellId = 0; + uint rand = RandomHelper.URand(0, 99); + if (rand < 25) // Fireball (25% chance) + spellId = SpellIds.Fireball; + else if (rand < 50) // Frostball (25% chance) + spellId = SpellIds.Frostbolt; + else if (rand < 70) // Chain Lighting (20% chance) + spellId = SpellIds.ChainLightning; + else if (rand < 80) // Polymorph (10% chance) + { + spellId = SpellIds.Polymorph; + if (RandomHelper.URand(0, 100) <= 30) // 30% chance to self-cast + target = caster; + } + else if (rand < 95) // Enveloping Winds (15% chance) + spellId = SpellIds.EnvelopingWinds; + else // Summon Felhund minion (5% chance) + { + spellId = SpellIds.SummonFelhoundMinion; + target = caster; + } + + caster.CastSpell(target, spellId, true, GetCastItem()); + } + } + + public override void Register() + { + OnEffectHitTarget.Add(new EffectHandler(HandleDummy, 0, SpellEffectName.Dummy)); + } + } + + public override SpellScript GetSpellScript() + { + return new spell_item_six_demon_bag_SpellScript(); + } + } + + [Script] // 59906 - Swift Hand of Justice Dummy + class spell_item_swift_hand_justice_dummy : SpellScriptLoader + { + public spell_item_swift_hand_justice_dummy() : base("spell_item_swift_hand_justice_dummy") { } + + class spell_item_swift_hand_justice_dummy_AuraScript : AuraScript + { + public override bool Validate(SpellInfo spellInfo) + { + return ValidateSpellInfo(SpellIds.SwiftHandOfJusticeHeal); + } + + void HandleProc(AuraEffect aurEff, ProcEventInfo eventInfo) + { + PreventDefaultAction(); + + Unit caster = eventInfo.GetActor(); + int amount = (int)caster.CountPctFromMaxHealth(aurEff.GetAmount()); + caster.CastCustomSpell(SpellIds.SwiftHandOfJusticeHeal, SpellValueMod.BasePoint0, amount, (Unit)null, true); + } + + public override void Register() + { + OnEffectProc.Add(new EffectProcHandler(HandleProc, 0, AuraType.Dummy)); + } + } + + public override AuraScript GetAuraScript() + { + return new spell_item_swift_hand_justice_dummy_AuraScript(); + } + } + + [Script] // 28862 - The Eye of Diminution + class spell_item_the_eye_of_diminution : SpellScriptLoader + { + public spell_item_the_eye_of_diminution() : base("spell_item_the_eye_of_diminution") { } + + class spell_item_the_eye_of_diminution_AuraScript : AuraScript + { + void CalculateAmount(AuraEffect aurEff, ref int amount, ref bool canBeRecalculated) + { + int diff = (int)GetUnitOwner().getLevel() - 60; + if (diff > 0) + amount += diff; + } + + public override void Register() + { + DoEffectCalcAmount.Add(new EffectCalcAmountHandler(CalculateAmount, 0, AuraType.ModThreat)); + } + } + + public override AuraScript GetAuraScript() + { + return new spell_item_the_eye_of_diminution_AuraScript(); + } + } + + // http://www.wowhead.com/item=44012 Underbelly Elixir + [Script] // 59640 Underbelly Elixir + class spell_item_underbelly_elixir : SpellScriptLoader + { + public spell_item_underbelly_elixir() : base("spell_item_underbelly_elixir") { } + + class spell_item_underbelly_elixir_SpellScript : SpellScript + { + public override bool Load() + { + return GetCaster().GetTypeId() == TypeId.Player; + } + + public override bool Validate(SpellInfo spellInfo) + { + return ValidateSpellInfo(SpellIds.UnderbellyElixirTriggered1, SpellIds.UnderbellyElixirTriggered2, SpellIds.UnderbellyElixirTriggered3); + } + + void HandleDummy(uint effIndex) + { + Unit caster = GetCaster(); + uint spellId = SpellIds.UnderbellyElixirTriggered3; + switch (RandomHelper.URand(1, 3)) + { + case 1: + spellId = SpellIds.UnderbellyElixirTriggered1; + break; + case 2: + spellId = SpellIds.UnderbellyElixirTriggered2; + break; + } + caster.CastSpell(caster, spellId, true, null); + } + + public override void Register() + { + OnEffectHit.Add(new EffectHandler(HandleDummy, 0, SpellEffectName.Dummy)); + } + } + + public override SpellScript GetSpellScript() + { + return new spell_item_underbelly_elixir_SpellScript(); + } + } + + [Script] // 126755 - Wormhole: Pandaria + class spell_item_wormhole_pandaria : SpellScriptLoader + { + public spell_item_wormhole_pandaria() : base("spell_item_wormhole_pandaria") { } + + class spell_item_wormhole_pandaria_SpellScript : SpellScript + { + public override bool Validate(SpellInfo spellInfo) + { + return ValidateSpellInfo(SpellIds.WormholeTargetLocations); + } + + void HandleTeleport(uint effIndex) + { + PreventHitDefaultEffect(effIndex); + uint spellId = SpellIds.WormholeTargetLocations.PickRandom(); + GetCaster().CastSpell(GetHitUnit(), spellId, true); + } + + public override void Register() + { + OnEffectHitTarget.Add(new EffectHandler(HandleTeleport, 0, SpellEffectName.Dummy)); + } + } + + public override SpellScript GetSpellScript() + { + return new spell_item_wormhole_pandaria_SpellScript(); + } + } + + [Script] // 47776 - Roll 'dem Bones + class spell_item_worn_troll_dice : SpellScriptLoader + { + public spell_item_worn_troll_dice() : base("spell_item_worn_troll_dice") { } + + class spell_item_worn_troll_dice_SpellScript : SpellScript + { + public override bool Validate(SpellInfo spellInfo) + { + if (!CliDB.BroadcastTextStorage.ContainsKey(TextIds.WornTrollDice)) + return false; + return true; + } + + public override bool Load() + { + return GetCaster().GetTypeId() == TypeId.Player; + } + + void HandleScript(uint effIndex) + { + GetCaster().TextEmote(TextIds.WornTrollDice, GetHitUnit()); + + uint minimum = 1; + uint maximum = 6; + + // roll twice + GetCaster().ToPlayer().DoRandomRoll(minimum, maximum); + GetCaster().ToPlayer().DoRandomRoll(minimum, maximum); + } + + public override void Register() + { + OnEffectHitTarget.Add(new EffectHandler(HandleScript, 0, SpellEffectName.ScriptEffect)); + } + } + + public override SpellScript GetSpellScript() + { + return new spell_item_worn_troll_dice_SpellScript(); + } + } + + [Script] + class spell_item_red_rider_air_rifle : SpellScriptLoader + { + public spell_item_red_rider_air_rifle() : base("spell_item_red_rider_air_rifle") { } + + class spell_item_red_rider_air_rifle_SpellScript : SpellScript + { + public override bool Validate(SpellInfo spell) + { + return ValidateSpellInfo(SpellIds.AirRifleHoldVisual, SpellIds.AirRifleShoot, SpellIds.AirRifleShootSelf); + } + + void HandleScript(uint effIndex) + { + PreventHitDefaultEffect(effIndex); + Unit caster = GetCaster(); + Unit target = GetHitUnit(); + if (target) + { + caster.CastSpell(caster, SpellIds.AirRifleHoldVisual, true); + // needed because this spell shares GCD with its triggered spells (which must not be cast with triggered flag) + Player player = caster.ToPlayer(); + if (player) + player.GetSpellHistory().CancelGlobalCooldown(GetSpellInfo()); + if (RandomHelper.URand(0, 4) != 0) + caster.CastSpell(target, SpellIds.AirRifleShoot, false); + else + caster.CastSpell(caster, SpellIds.AirRifleShootSelf, false); + } + } + + public override void Register() + { + OnEffectHitTarget.Add(new EffectHandler(HandleScript, 0, SpellEffectName.ScriptEffect)); + } + } + + public override SpellScript GetSpellScript() + { + return new spell_item_red_rider_air_rifle_SpellScript(); + } + } + + [Script] + class spell_item_create_heart_candy : SpellScriptLoader + { + public spell_item_create_heart_candy() : base("spell_item_create_heart_candy") { } + + class spell_item_create_heart_candy_SpellScript : SpellScript + { + void HandleScript(uint effIndex) + { + PreventHitDefaultEffect(effIndex); + Player target = GetHitPlayer(); + if (target) + { + uint[] items = { ItemIds.HeartCandy1, ItemIds.HeartCandy2, ItemIds.HeartCandy3, ItemIds.HeartCandy4, ItemIds.HeartCandy5, ItemIds.HeartCandy6, ItemIds.HeartCandy7, ItemIds.HeartCandy8 }; + target.AddItem(items[RandomHelper.IRand(0, 7)], 1); + } + } + + public override void Register() + { + OnEffectHitTarget.Add(new EffectHandler(HandleScript, 0, SpellEffectName.ScriptEffect)); + } + } + + public override SpellScript GetSpellScript() + { + return new spell_item_create_heart_candy_SpellScript(); + } + } + + [Script] + class spell_item_book_of_glyph_mastery : SpellScriptLoader + { + public spell_item_book_of_glyph_mastery() : base("spell_item_book_of_glyph_mastery") { } + + class spell_item_book_of_glyph_mastery_SpellScript : SpellScript + { + public override bool Load() + { + return GetCaster().GetTypeId() == TypeId.Player; + } + + SpellCastResult CheckRequirement() + { + if (SkillDiscovery.HasDiscoveredAllSpells(GetSpellInfo().Id, GetCaster().ToPlayer())) + { + SetCustomCastResultMessage(SpellCustomErrors.LearnedEverything); + return SpellCastResult.CustomError; + } + + return SpellCastResult.SpellCastOk; + } + + void HandleScript(uint effIndex) + { + Player caster = GetCaster().ToPlayer(); + uint spellId = GetSpellInfo().Id; + + // learn random explicit discovery recipe (if any) + uint discoveredSpellId = SkillDiscovery.GetExplicitDiscoverySpell(spellId, caster); + if (discoveredSpellId != 0) + caster.LearnSpell(discoveredSpellId, false); + } + + public override void Register() + { + OnCheckCast.Add(new CheckCastHandler(CheckRequirement)); + OnEffectHitTarget.Add(new EffectHandler(HandleScript, 0, SpellEffectName.ScriptEffect)); + } + } + + public override SpellScript GetSpellScript() + { + return new spell_item_book_of_glyph_mastery_SpellScript(); + } + } + + [Script] + class spell_item_gift_of_the_harvester : SpellScriptLoader + { + public spell_item_gift_of_the_harvester() : base("spell_item_gift_of_the_harvester") { } + + class spell_item_gift_of_the_harvester_SpellScript : SpellScript + { + SpellCastResult CheckRequirement() + { + List ghouls = new List(); + GetCaster().GetAllMinionsByEntry(ghouls, CreatureIds.Ghoul); + if (ghouls.Count >= CreatureIds.MaxGhouls) + { + SetCustomCastResultMessage(SpellCustomErrors.TooManyGhouls); + return SpellCastResult.CustomError; + } + + return SpellCastResult.SpellCastOk; + } + + public override void Register() + { + OnCheckCast.Add(new CheckCastHandler(CheckRequirement)); + } + } + + public override SpellScript GetSpellScript() + { + return new spell_item_gift_of_the_harvester_SpellScript(); + } + } + + [Script] + class spell_item_map_of_the_geyser_fields : SpellScriptLoader + { + public spell_item_map_of_the_geyser_fields() : base("spell_item_map_of_the_geyser_fields") { } + + class spell_item_map_of_the_geyser_fields_SpellScript : SpellScript + { + SpellCastResult CheckSinkholes() + { + Unit caster = GetCaster(); + if (caster.FindNearestCreature(CreatureIds.SouthSinkhole, 30.0f, true) || + caster.FindNearestCreature(CreatureIds.NortheastSinkhole, 30.0f, true) || + caster.FindNearestCreature(CreatureIds.NorthwestSinkhole, 30.0f, true)) + return SpellCastResult.SpellCastOk; + + SetCustomCastResultMessage(SpellCustomErrors.MustBeCloseToSinkhole); + return SpellCastResult.CustomError; + } + + public override void Register() + { + OnCheckCast.Add(new CheckCastHandler(CheckSinkholes)); + } + } + + public override SpellScript GetSpellScript() + { + return new spell_item_map_of_the_geyser_fields_SpellScript(); + } + } + + [Script] + class spell_item_vanquished_clutches : SpellScriptLoader + { + public spell_item_vanquished_clutches() : base("spell_item_vanquished_clutches") { } + + class spell_item_vanquished_clutches_SpellScript : SpellScript + { + public override bool Validate(SpellInfo spellInfo) + { + return ValidateSpellInfo(SpellIds.Crusher, SpellIds.Constrictor, SpellIds.Corruptor); + } + + void HandleDummy(uint effIndex) + { + uint spellId = RandomHelper.RAND(SpellIds.Crusher, SpellIds.Constrictor, SpellIds.Corruptor); + Unit caster = GetCaster(); + caster.CastSpell(caster, spellId, true); + } + + public override void Register() + { + OnEffectHit.Add(new EffectHandler(HandleDummy, 0, SpellEffectName.Dummy)); + } + } + + public override SpellScript GetSpellScript() + { + return new spell_item_vanquished_clutches_SpellScript(); + } + } + + [Script] + class spell_item_ashbringer : SpellScriptLoader + { + public spell_item_ashbringer() : base("spell_item_ashbringer") { } + + class spell_item_ashbringer_SpellScript : SpellScript + { + public override bool Load() + { + return GetCaster().GetTypeId() == TypeId.Player; + } + + void OnDummyEffect(uint effIndex) + { + PreventHitDefaultEffect(effIndex); + + Player player = GetCaster().ToPlayer(); + uint sound_id = RandomHelper.RAND(SoundIds.Ashbringer1, SoundIds.Ashbringer2, SoundIds.Ashbringer3, SoundIds.Ashbringer4, SoundIds.Ashbringer5, SoundIds.Ashbringer6, + SoundIds.Ashbringer7, SoundIds.Ashbringer8, SoundIds.Ashbringer9, SoundIds.Ashbringer10, SoundIds.Ashbringer11, SoundIds.Ashbringer12); + + // Ashbringers effect (spellID 28441) retriggers every 5 seconds, with a chance of making it say one of the above 12 sounds + if (RandomHelper.URand(0, 60) < 1) + player.PlayDirectSound(sound_id, player); + } + + public override void Register() + { + OnEffectHit.Add(new EffectHandler(OnDummyEffect, 0, SpellEffectName.Dummy)); + } + } + + public override SpellScript GetSpellScript() + { + return new spell_item_ashbringer_SpellScript(); + } + } + + [Script] + class spell_magic_eater_food : SpellScriptLoader + { + public spell_magic_eater_food() : base("spell_magic_eater_food") { } + + class spell_magic_eater_food_AuraScript : AuraScript + { + void HandleTriggerSpell(AuraEffect aurEff) + { + PreventDefaultAction(); + Unit target = GetTarget(); + switch (RandomHelper.URand(0, 5)) + { + case 0: + target.CastSpell(target, SpellIds.WildMagic, true); + break; + case 1: + target.CastSpell(target, SpellIds.WellFed1, true); + break; + case 2: + target.CastSpell(target, SpellIds.WellFed2, true); + break; + case 3: + target.CastSpell(target, SpellIds.WellFed3, true); + break; + case 4: + target.CastSpell(target, SpellIds.WellFed4, true); + break; + case 5: + target.CastSpell(target, SpellIds.WellFed5, true); + break; + } + } + + public override void Register() + { + OnEffectPeriodic.Add(new EffectPeriodicHandler(HandleTriggerSpell, 1, AuraType.PeriodicTriggerSpell)); + } + } + + public override AuraScript GetAuraScript() + { + return new spell_magic_eater_food_AuraScript(); + } + } + + [Script] + class spell_item_shimmering_vessel : SpellScriptLoader + { + public spell_item_shimmering_vessel() : base("spell_item_shimmering_vessel") { } + + class spell_item_shimmering_vessel_SpellScript : SpellScript + { + void HandleDummy(uint effIndex) + { + Creature target = GetHitCreature(); + if (target) + target.setDeathState(DeathState.JustRespawned); + } + + public override void Register() + { + OnEffectHitTarget.Add(new EffectHandler(HandleDummy, 0, SpellEffectName.Dummy)); + } + } + + public override SpellScript GetSpellScript() + { + return new spell_item_shimmering_vessel_SpellScript(); + } + } + + [Script] + class spell_item_purify_helboar_meat : SpellScriptLoader + { + public spell_item_purify_helboar_meat() : base("spell_item_purify_helboar_meat") { } + + class spell_item_purify_helboar_meat_SpellScript : SpellScript + { + public override bool Load() + { + return GetCaster().GetTypeId() == TypeId.Player; + } + + public override bool Validate(SpellInfo spell) + { + return ValidateSpellInfo(SpellIds.SummonPurifiedHelboarMeat, SpellIds.SummonToxicHelboarMeat); + } + + void HandleDummy(uint effIndex) + { + Unit caster = GetCaster(); + caster.CastSpell(caster, RandomHelper.randChance(50) ? SpellIds.SummonPurifiedHelboarMeat : SpellIds.SummonToxicHelboarMeat, true, null); + } + + public override void Register() + { + OnEffectHitTarget.Add(new EffectHandler(HandleDummy, 0, SpellEffectName.Dummy)); + } + } + + public override SpellScript GetSpellScript() + { + return new spell_item_purify_helboar_meat_SpellScript(); + } + } + + [Script] + class spell_item_crystal_prison_dummy_dnd : SpellScriptLoader + { + public spell_item_crystal_prison_dummy_dnd() : base("spell_item_crystal_prison_dummy_dnd") { } + + class spell_item_crystal_prison_dummy_dnd_SpellScript : SpellScript + { + public override bool Validate(SpellInfo spell) + { + if (Global.ObjectMgr.GetGameObjectTemplate(ObjectIds.ImprisonedDoomguard) == null) + return false; + return true; + } + + void HandleDummy(uint effIndex) + { + Creature target = GetHitCreature(); + if (target) + { + if (target.IsDead() && !target.IsPet()) + { + GetCaster().SummonGameObject(ObjectIds.ImprisonedDoomguard, target, Quaternion.WAxis, (uint)(target.GetRespawnTime() - Time.UnixTime)); + target.DespawnOrUnsummon(); + } + } + } + + public override void Register() + { + OnEffectHitTarget.Add(new EffectHandler(HandleDummy, 0, SpellEffectName.Dummy)); + } + } + + public override SpellScript GetSpellScript() + { + return new spell_item_crystal_prison_dummy_dnd_SpellScript(); + } + } + + [Script] + class spell_item_reindeer_transformation : SpellScriptLoader + { + public spell_item_reindeer_transformation() : base("spell_item_reindeer_transformation") { } + + class spell_item_reindeer_transformation_SpellScript : SpellScript + { + public override bool Validate(SpellInfo spell) + { + return ValidateSpellInfo(SpellIds.FlyingReindeer310, SpellIds.FlyingReindeer280, SpellIds.FlyingReindeer60, SpellIds.Reindeer100, SpellIds.Reindeer60); + } + + void HandleDummy(uint effIndex) + { + Unit caster = GetCaster(); + if (caster.HasAuraType(AuraType.Mounted)) + { + float flyspeed = caster.GetSpeedRate(UnitMoveType.Flight); + float speed = caster.GetSpeedRate(UnitMoveType.Run); + + caster.RemoveAurasByType(AuraType.Mounted); + //5 different spells used depending on mounted speed and if mount can fly or not + + if (flyspeed >= 4.1f) + // Flying Reindeer + caster.CastSpell(caster, SpellIds.FlyingReindeer310, true); //310% flying Reindeer + else if (flyspeed >= 3.8f) + // Flying Reindeer + caster.CastSpell(caster, SpellIds.FlyingReindeer280, true); //280% flying Reindeer + else if (flyspeed >= 1.6f) + // Flying Reindeer + caster.CastSpell(caster, SpellIds.FlyingReindeer60, true); //60% flying Reindeer + else if (speed >= 2.0f) + // Reindeer + caster.CastSpell(caster, SpellIds.Reindeer100, true); //100% ground Reindeer + else + // Reindeer + caster.CastSpell(caster, SpellIds.Reindeer60, true); //60% ground Reindeer + } + } + + public override void Register() + { + OnEffectHitTarget.Add(new EffectHandler(HandleDummy, 0, SpellEffectName.Dummy)); + } + } + + public override SpellScript GetSpellScript() + { + return new spell_item_reindeer_transformation_SpellScript(); + } + } + + [Script] + class spell_item_nigh_invulnerability : SpellScriptLoader + { + public spell_item_nigh_invulnerability() : base("spell_item_nigh_invulnerability") { } + + class spell_item_nigh_invulnerability_SpellScript : SpellScript + { + public override bool Validate(SpellInfo spell) + { + return ValidateSpellInfo(SpellIds.NighInvulnerability, SpellIds.CompleteVulnerability); + } + + void HandleDummy(uint effIndex) + { + Unit caster = GetCaster(); + Item castItem = GetCastItem(); + if (castItem) + { + if (RandomHelper.randChance(86)) // Nigh-Invulnerability - success + caster.CastSpell(caster, SpellIds.NighInvulnerability, true, castItem); + else // Complete Vulnerability - backfire in 14% casts + caster.CastSpell(caster, SpellIds.CompleteVulnerability, true, castItem); + } + } + + public override void Register() + { + OnEffectHitTarget.Add(new EffectHandler(HandleDummy, 0, SpellEffectName.Dummy)); + } + } + + public override SpellScript GetSpellScript() + { + return new spell_item_nigh_invulnerability_SpellScript(); + } + } + + [Script] + class spell_item_poultryizer : SpellScriptLoader + { + public spell_item_poultryizer() : base("spell_item_poultryizer") { } + + class spell_item_poultryizer_SpellScript : SpellScript + { + public override bool Validate(SpellInfo spell) + { + return ValidateSpellInfo(SpellIds.PoultryizerSuccess, SpellIds.PoultryizerBackfire); + } + + void HandleDummy(uint effIndex) + { + if (GetCastItem() && GetHitUnit()) + GetCaster().CastSpell(GetHitUnit(), RandomHelper.randChance(80) ? SpellIds.PoultryizerSuccess : SpellIds.PoultryizerBackfire, true, GetCastItem()); + } + + public override void Register() + { + OnEffectHitTarget.Add(new EffectHandler(HandleDummy, 0, SpellEffectName.Dummy)); + } + } + + public override SpellScript GetSpellScript() + { + return new spell_item_poultryizer_SpellScript(); + } + } + + [Script] + class spell_item_socrethars_stone : SpellScriptLoader + { + public spell_item_socrethars_stone() : base("spell_item_socrethars_stone") { } + + class spell_item_socrethars_stone_SpellScript : SpellScript + { + public override bool Load() + { + return (GetCaster().GetAreaId() == 3900 || GetCaster().GetAreaId() == 3742); + } + + public override bool Validate(SpellInfo spell) + { + return ValidateSpellInfo(SpellIds.SocretharToSeat, SpellIds.SocretharFromSeat); + } + + void HandleDummy(uint effIndex) + { + Unit caster = GetCaster(); + switch (caster.GetAreaId()) + { + case 3900: + caster.CastSpell(caster, SpellIds.SocretharToSeat, true); + break; + case 3742: + caster.CastSpell(caster, SpellIds.SocretharFromSeat, true); + break; + default: + return; + } + } + + public override void Register() + { + OnEffectHitTarget.Add(new EffectHandler(HandleDummy, 0, SpellEffectName.Dummy)); + } + } + + public override SpellScript GetSpellScript() + { + return new spell_item_socrethars_stone_SpellScript(); + } + } + + [Script] + class spell_item_demon_broiled_surprise : SpellScriptLoader + { + public spell_item_demon_broiled_surprise() : base("spell_item_demon_broiled_surprise") { } + + class spell_item_demon_broiled_surprise_SpellScript : SpellScript + { + public override bool Validate(SpellInfo spell) + { + return Global.ObjectMgr.GetCreatureTemplate(CreatureIds.AbyssalFlamebringer) != null + && Global.ObjectMgr.GetQuestTemplate(QuestIds.SuperHotStew) != null + && ValidateSpellInfo(SpellIds.CreateDemonBroiledSurprise); + } + + public override bool Load() + { + return GetCaster().GetTypeId() == TypeId.Player; + } + + void HandleDummy(uint effIndex) + { + Unit player = GetCaster(); + player.CastSpell(player, SpellIds.CreateDemonBroiledSurprise, false); + } + + SpellCastResult CheckRequirement() + { + Player player = GetCaster().ToPlayer(); + if (player.GetQuestStatus(QuestIds.SuperHotStew) != QuestStatus.Incomplete) + return SpellCastResult.CantDoThatRightNow; + + Creature creature = player.FindNearestCreature(CreatureIds.AbyssalFlamebringer, 10, false); + if (creature) + if (creature.IsDead()) + return SpellCastResult.SpellCastOk; + return SpellCastResult.NotHere; + } + + public override void Register() + { + OnEffectHitTarget.Add(new EffectHandler(HandleDummy, 1, SpellEffectName.Dummy)); + OnCheckCast.Add(new CheckCastHandler(CheckRequirement)); + } + } + + public override SpellScript GetSpellScript() + { + return new spell_item_demon_broiled_surprise_SpellScript(); + } + } + + [Script] + class spell_item_complete_raptor_capture : SpellScriptLoader + { + public spell_item_complete_raptor_capture() : base("spell_item_complete_raptor_capture") { } + + class spell_item_complete_raptor_capture_SpellScript : SpellScript + { + public override bool Validate(SpellInfo spell) + { + return ValidateSpellInfo(SpellIds.RaptorCaptureCredit); + } + + void HandleDummy(uint effIndex) + { + Unit caster = GetCaster(); + if (GetHitCreature()) + { + GetHitCreature().DespawnOrUnsummon(); + + //cast spell Raptor Capture Credit + caster.CastSpell(caster, SpellIds.RaptorCaptureCredit, true, null); + } + } + + public override void Register() + { + OnEffectHitTarget.Add(new EffectHandler(HandleDummy, 0, SpellEffectName.Dummy)); + } + } + + public override SpellScript GetSpellScript() + { + return new spell_item_complete_raptor_capture_SpellScript(); + } + } + + [Script] + class spell_item_impale_leviroth : SpellScriptLoader + { + public spell_item_impale_leviroth() : base("spell_item_impale_leviroth") { } + + class spell_item_impale_leviroth_SpellScript : SpellScript + { + public override bool Validate(SpellInfo spell) + { + if (Global.ObjectMgr.GetCreatureTemplate(CreatureIds.Leviroth) == null) + return false; + return true; + } + + void HandleDummy(uint effIndex) + { + Creature target = GetHitCreature(); + if (target) + if (target.GetEntry() == CreatureIds.Leviroth && !target.HealthBelowPct(95)) + { + target.CastSpell(target, SpellIds.LevirothSelfImpale, true); + target.ResetPlayerDamageReq(); + } + } + + public override void Register() + { + OnEffectHitTarget.Add(new EffectHandler(HandleDummy, 0, SpellEffectName.Dummy)); + } + } + + public override SpellScript GetSpellScript() + { + return new spell_item_impale_leviroth_SpellScript(); + } + } + + [Script] + class spell_item_brewfest_mount_transformation : SpellScriptLoader + { + public spell_item_brewfest_mount_transformation() : base("spell_item_brewfest_mount_transformation") { } + + class spell_item_brewfest_mount_transformation_SpellScript : SpellScript + { + public override bool Validate(SpellInfo spell) + { + return ValidateSpellInfo(SpellIds.MountRam100, SpellIds.MountRam60, SpellIds.MountKodo100, SpellIds.MountKodo60); + } + + void HandleDummy(uint effIndex) + { + Player caster = GetCaster().ToPlayer(); + if (caster.HasAuraType(AuraType.Mounted)) + { + caster.RemoveAurasByType(AuraType.Mounted); + uint spell_id; + + switch (GetSpellInfo().Id) + { + case SpellIds.BrewfestMountTransform: + if (caster.GetSpeedRate(UnitMoveType.Run) >= 2.0f) + spell_id = caster.GetTeam() == Team.Alliance ? SpellIds.MountRam100 : SpellIds.MountKodo100; + else + spell_id = caster.GetTeam() == Team.Alliance ? SpellIds.MountRam60 : SpellIds.MountKodo60; + break; + case SpellIds.BrewfestMountTransformReverse: + if (caster.GetSpeedRate(UnitMoveType.Run) >= 2.0f) + spell_id = caster.GetTeam() == Team.Horde ? SpellIds.MountRam100 : SpellIds.MountKodo100; + else + spell_id = caster.GetTeam() == Team.Horde ? SpellIds.MountRam60 : SpellIds.MountKodo60; + break; + default: + return; + } + caster.CastSpell(caster, spell_id, true); + } + } + + public override void Register() + { + OnEffectHitTarget.Add(new EffectHandler(HandleDummy, 0, SpellEffectName.Dummy)); + } + } + + public override SpellScript GetSpellScript() + { + return new spell_item_brewfest_mount_transformation_SpellScript(); + } + } + + [Script] + class spell_item_nitro_boots : SpellScriptLoader + { + public spell_item_nitro_boots() : base("spell_item_nitro_boots") { } + + class spell_item_nitro_boots_SpellScript : SpellScript + { + public override bool Load() + { + if (!GetCastItem()) + return false; + return true; + } + + public override bool Validate(SpellInfo spell) + { + return ValidateSpellInfo(SpellIds.NitroBootsSuccess, SpellIds.NitroBootsBackfire); + } + + void HandleDummy(uint effIndex) + { + Unit caster = GetCaster(); + bool success = caster.GetMap().IsDungeon() || RandomHelper.randChance(95); + caster.CastSpell(caster, success ? SpellIds.NitroBootsSuccess : SpellIds.NitroBootsBackfire, true, GetCastItem()); + } + + public override void Register() + { + OnEffectHitTarget.Add(new EffectHandler(HandleDummy, 0, SpellEffectName.Dummy)); + } + } + + public override SpellScript GetSpellScript() + { + return new spell_item_nitro_boots_SpellScript(); + } + } + + [Script] + class spell_item_teach_language : SpellScriptLoader + { + public spell_item_teach_language() : base("spell_item_teach_language") { } + + class spell_item_teach_language_SpellScript : SpellScript + { + public override bool Load() + { + return GetCaster().GetTypeId() == TypeId.Player; + } + + public override bool Validate(SpellInfo spell) + { + return ValidateSpellInfo(SpellIds.LearnGnomishBinary, SpellIds.LearnGoblinBinary); + } + + void HandleDummy(uint effIndex) + { + Player caster = GetCaster().ToPlayer(); + + if (RandomHelper.randChance(34)) + caster.CastSpell(caster, caster.GetTeam() == Team.Alliance ? SpellIds.LearnGnomishBinary : SpellIds.LearnGoblinBinary, true); + } + + public override void Register() + { + OnEffectHitTarget.Add(new EffectHandler(HandleDummy, 0, SpellEffectName.Dummy)); + } + } + + public override SpellScript GetSpellScript() + { + return new spell_item_teach_language_SpellScript(); + } + } + + [Script] + class spell_item_rocket_boots : SpellScriptLoader + { + public spell_item_rocket_boots() : base("spell_item_rocket_boots") { } + + class spell_item_rocket_boots_SpellScript : SpellScript + { + public override bool Load() + { + return GetCaster().GetTypeId() == TypeId.Player; + } + + public override bool Validate(SpellInfo spell) + { + return ValidateSpellInfo(SpellIds.RocketBootsProc); + } + + void HandleDummy(uint effIndex) + { + Player caster = GetCaster().ToPlayer(); + + Battleground bg = caster.GetBattleground(); + if (bg) + bg.EventPlayerDroppedFlag(caster); + + caster.GetSpellHistory().ResetCooldown(SpellIds.RocketBootsProc); + caster.CastSpell(caster, SpellIds.RocketBootsProc, true, null); + } + + SpellCastResult CheckCast() + { + if (GetCaster().IsInWater()) + return SpellCastResult.OnlyAbovewater; + return SpellCastResult.SpellCastOk; + } + + public override void Register() + { + OnCheckCast.Add(new CheckCastHandler(CheckCast)); + OnEffectHitTarget.Add(new EffectHandler(HandleDummy, 0, SpellEffectName.Dummy)); + } + } + + public override SpellScript GetSpellScript() + { + return new spell_item_rocket_boots_SpellScript(); + } + } + + [Script] + class spell_item_pygmy_oil : SpellScriptLoader + { + public spell_item_pygmy_oil() : base("spell_item_pygmy_oil") { } + + class spell_item_pygmy_oil_SpellScript : SpellScript + { + public override bool Validate(SpellInfo spell) + { + return ValidateSpellInfo(SpellIds.PygmyOilPygmyAura, SpellIds.PygmyOilSmallerAura); + } + + void HandleDummy(uint effIndex) + { + Unit caster = GetCaster(); + Aura aura = caster.GetAura(SpellIds.PygmyOilPygmyAura); + if (aura != null) + aura.RefreshDuration(); + else + { + aura = caster.GetAura(SpellIds.PygmyOilSmallerAura); + if (aura == null || aura.GetStackAmount() < 5 || !RandomHelper.randChance(50)) + caster.CastSpell(caster, SpellIds.PygmyOilSmallerAura, true); + else + { + aura.Remove(); + caster.CastSpell(caster, SpellIds.PygmyOilPygmyAura, true); + } + } + } + + public override void Register() + { + OnEffectHitTarget.Add(new EffectHandler(HandleDummy, 0, SpellEffectName.Dummy)); + } + } + + public override SpellScript GetSpellScript() + { + return new spell_item_pygmy_oil_SpellScript(); + } + } + + [Script] + class spell_item_unusual_compass : SpellScriptLoader + { + public spell_item_unusual_compass() : base("spell_item_unusual_compass") { } + + class spell_item_unusual_compass_SpellScript : SpellScript + { + void HandleDummy(uint effIndex) + { + Unit caster = GetCaster(); + caster.SetFacingTo(RandomHelper.FRand(0.0f, 2.0f * (float)Math.PI)); + } + + public override void Register() + { + OnEffectHitTarget.Add(new EffectHandler(HandleDummy, 0, SpellEffectName.Dummy)); + } + } + + public override SpellScript GetSpellScript() + { + return new spell_item_unusual_compass_SpellScript(); + } + } + + [Script] + class spell_item_chicken_cover : SpellScriptLoader + { + public spell_item_chicken_cover() : base("spell_item_chicken_cover") { } + + class spell_item_chicken_cover_SpellScript : SpellScript + { + public override bool Load() + { + return GetCaster().GetTypeId() == TypeId.Player; + } + + public override bool Validate(SpellInfo spell) + { + return Global.ObjectMgr.GetQuestTemplate(QuestIds.ChickenParty) != null + && Global.ObjectMgr.GetQuestTemplate(QuestIds.FlownTheCoop) != null + && ValidateSpellInfo(SpellIds.ChickenNet, SpellIds.CaptureChickenEscape); + } + + void HandleDummy(uint effIndex) + { + Player caster = GetCaster().ToPlayer(); + Unit target = GetHitUnit(); + if (target) + { + if (!target.HasAura(SpellIds.ChickenNet) && (caster.GetQuestStatus(QuestIds.ChickenParty) == QuestStatus.Incomplete || caster.GetQuestStatus(QuestIds.FlownTheCoop) == QuestStatus.Incomplete)) + { + caster.CastSpell(caster, SpellIds.CaptureChickenEscape, true); + target.KillSelf(); + } + } + } + + public override void Register() + { + OnEffectHitTarget.Add(new EffectHandler(HandleDummy, 0, SpellEffectName.Dummy)); + } + } + + public override SpellScript GetSpellScript() + { + return new spell_item_chicken_cover_SpellScript(); + } + } + + [Script] + class spell_item_muisek_vessel : SpellScriptLoader + { + public spell_item_muisek_vessel() : base("spell_item_muisek_vessel") { } + + class spell_item_muisek_vessel_SpellScript : SpellScript + { + void HandleDummy(uint effIndex) + { + Creature target = GetHitCreature(); + if (target) + if (target.IsDead()) + target.DespawnOrUnsummon(); + } + + public override void Register() + { + OnEffectHitTarget.Add(new EffectHandler(HandleDummy, 0, SpellEffectName.Dummy)); + } + } + + public override SpellScript GetSpellScript() + { + return new spell_item_muisek_vessel_SpellScript(); + } + } + + [Script] + class spell_item_greatmothers_soulcatcher : SpellScriptLoader + { + public spell_item_greatmothers_soulcatcher() : base("spell_item_greatmothers_soulcatcher") { } + + class spell_item_greatmothers_soulcatcher_SpellScript : SpellScript + { + void HandleDummy(uint effIndex) + { + if (GetHitUnit()) + GetCaster().CastSpell(GetCaster(), SpellIds.ForceCastSummonGnomeSoul); + } + + public override void Register() + { + OnEffectHitTarget.Add(new EffectHandler(HandleDummy, 0, SpellEffectName.Dummy)); + } + } + + public override SpellScript GetSpellScript() + { + return new spell_item_greatmothers_soulcatcher_SpellScript(); + } + } + + // Item - 49310: Purified Shard of the Scale + // 69755 - Purified Shard of the Scale - Equip Effect + + // Item - 49488: Shiny Shard of the Scale + // 69739 - Shiny Shard of the Scale - Equip Effect + [Script("spell_item_purified_shard_of_the_scale", SpellIds.PurifiedCauterizingHeal, SpellIds.PurifiedSearingFlames)] + [Script("spell_item_shiny_shard_of_the_scale", SpellIds.ShinyCauterizingHeal, SpellIds.ShinySearingFlames)] + class spell_item_shard_of_the_scale : SpellScriptLoader + { + public spell_item_shard_of_the_scale(string scriptName, uint healProcSpellId, uint damageProcSpellId) : base(scriptName) + { + _healProcSpellId = healProcSpellId; + _damageProcSpellId = damageProcSpellId; + } + + class spell_item_shard_of_the_scale_AuraScript : AuraScript + { + public spell_item_shard_of_the_scale_AuraScript(uint healProcSpellId, uint damageProcSpellId) + { + _healProcSpellId = healProcSpellId; + _damageProcSpellId = damageProcSpellId; + } + + public override bool Validate(SpellInfo spellInfo) + { + return ValidateSpellInfo(_healProcSpellId, _damageProcSpellId); + } + + void HandleProc(AuraEffect aurEff, ProcEventInfo eventInfo) + { + PreventDefaultAction(); + Unit caster = eventInfo.GetActor(); + Unit target = eventInfo.GetProcTarget(); + + if (eventInfo.GetTypeMask().HasAnyFlag(ProcFlags.DoneSpellMagicDmgClassPos)) + caster.CastSpell(target, _healProcSpellId, true); + + if (eventInfo.GetTypeMask().HasAnyFlag(ProcFlags.DoneSpellMagicDmgClassNeg)) + caster.CastSpell(target, _damageProcSpellId, true); + } + + public override void Register() + { + OnEffectProc.Add(new EffectProcHandler(HandleProc, 0, AuraType.Dummy)); + } + + uint _healProcSpellId; + uint _damageProcSpellId; + } + + public override AuraScript GetAuraScript() + { + return new spell_item_shard_of_the_scale_AuraScript(_healProcSpellId, _damageProcSpellId); + } + + uint _healProcSpellId; + uint _damageProcSpellId; + } + + [Script] + class spell_item_soul_preserver : SpellScriptLoader + { + public spell_item_soul_preserver() : base("spell_item_soul_preserver") { } + + class spell_item_soul_preserver_AuraScript : AuraScript + { + public override bool Validate(SpellInfo spellInfo) + { + return ValidateSpellInfo(SpellIds.SoulPreserverDruid, SpellIds.SoulPreserverPaladin, SpellIds.SoulPreserverPriest, SpellIds.SoulPreserverShaman); + } + + void HandleProc(AuraEffect aurEff, ProcEventInfo eventInfo) + { + PreventDefaultAction(); + + Unit caster = eventInfo.GetActor(); + + switch (caster.GetClass()) + { + case Class.Druid: + caster.CastSpell(caster, SpellIds.SoulPreserverDruid, true, null, aurEff); + break; + case Class.Paladin: + caster.CastSpell(caster, SpellIds.SoulPreserverPaladin, true, null, aurEff); + break; + case Class.Priest: + caster.CastSpell(caster, SpellIds.SoulPreserverPriest, true, null, aurEff); + break; + case Class.Shaman: + caster.CastSpell(caster, SpellIds.SoulPreserverShaman, true, null, aurEff); + break; + default: + break; + } + } + + public override void Register() + { + OnEffectProc.Add(new EffectProcHandler(HandleProc, 0, AuraType.ProcTriggerSpell)); + } + } + + public override AuraScript GetAuraScript() + { + return new spell_item_soul_preserver_AuraScript(); + } + } + + [Script("spell_item_sunwell_exalted_caster_neck", SpellIds.LightsWrath, SpellIds.ArcaneBolt)] + [Script("spell_item_sunwell_exalted_melee_neck", SpellIds.LightsStrength, SpellIds.ArcaneStrike)] + [Script("spell_item_sunwell_exalted_tank_neck", SpellIds.LightsWard, SpellIds.ArcaneInsight)] + [Script("spell_item_sunwell_exalted_healer_neck", SpellIds.LightsSalvation, SpellIds.ArcaneSurge)] + class spell_item_sunwell_neck : SpellScriptLoader + { + public spell_item_sunwell_neck(string scriptName, uint aldorSpellId, uint scryersSpellId) : base(scriptName) + { + _aldorSpellId = aldorSpellId; + _scryersSpellId = scryersSpellId; + } + + class spell_item_sunwell_neck_AuraScript : AuraScript + { + public spell_item_sunwell_neck_AuraScript(uint aldorSpellId, uint scryersSpellId) + { + _aldorSpellId = aldorSpellId; + _scryersSpellId = scryersSpellId; + } + + public override bool Validate(SpellInfo spellInfo) + { + return CliDB.FactionStorage.ContainsKey(FactionIds.Aldor) && CliDB.FactionStorage.ContainsKey(FactionIds.Scryers) + && ValidateSpellInfo(_aldorSpellId, _scryersSpellId); + } + + bool CheckProc(ProcEventInfo eventInfo) + { + if (eventInfo.GetActor().GetTypeId() != TypeId.Player) + return false; + return true; + } + + void HandleProc(AuraEffect aurEff, ProcEventInfo eventInfo) + { + PreventDefaultAction(); + Player player = eventInfo.GetActor().ToPlayer(); + Unit target = eventInfo.GetProcTarget(); + + // Aggression checks are in the spell system... just cast and forget + if (player.GetReputationRank(FactionIds.Aldor) == ReputationRank.Exalted) + player.CastSpell(target, _aldorSpellId, true); + + if (player.GetReputationRank(FactionIds.Scryers) == ReputationRank.Exalted) + player.CastSpell(target, _scryersSpellId, true); + } + + public override void Register() + { + DoCheckProc.Add(new CheckProcHandler(CheckProc)); + OnEffectProc.Add(new EffectProcHandler(HandleProc, 0, AuraType.Dummy)); + } + + uint _aldorSpellId; + uint _scryersSpellId; + } + + public override AuraScript GetAuraScript() + { + return new spell_item_sunwell_neck_AuraScript(_aldorSpellId, _scryersSpellId); + } + + uint _aldorSpellId; + uint _scryersSpellId; + } + + [Script] + class spell_item_toy_train_set_pulse : SpellScriptLoader + { + public spell_item_toy_train_set_pulse() : base("spell_item_toy_train_set_pulse") { } + + class spell_item_toy_train_set_pulse_SpellScript : SpellScript + { + void HandleDummy(uint index) + { + Player target = GetHitUnit().ToPlayer(); + if (target) + { + target.HandleEmoteCommand(Emote.OneshotTrain); + EmotesTextSoundRecord soundEntry = Global.DB2Mgr.GetTextSoundEmoteFor((uint)TextEmotes.Train, target.GetRace(), target.GetGender(), target.GetClass()); + if (soundEntry != null) + target.PlayDistanceSound(soundEntry.SoundId); + } + } + + void HandleTargets(List targetList) + { + targetList.RemoveAll(obj => !obj.IsTypeId(TypeId.Player)); + } + + public override void Register() + { + OnEffectHitTarget.Add(new EffectHandler(HandleDummy, 0, SpellEffectName.ScriptEffect)); + OnObjectAreaTargetSelect.Add(new ObjectAreaTargetSelectHandler(HandleTargets, SpellConst.EffectAll, Targets.UnitSrcAreaAlly)); + } + } + + public override SpellScript GetSpellScript() + { + return new spell_item_toy_train_set_pulse_SpellScript(); + } + } + + [Script] + class spell_item_death_choice : SpellScriptLoader + { + public spell_item_death_choice() : base("spell_item_death_choice") { } + + class spell_item_death_choice_AuraScript : AuraScript + { + public override bool Validate(SpellInfo spellInfo) + { + return ValidateSpellInfo(SpellIds.DeathChoiceNormalStrength, SpellIds.DeathChoiceNormalAgility, SpellIds.DeathChoiceHeroicStrength, SpellIds.DeathChoiceHeroicAgility); + } + + void HandleProc(AuraEffect aurEff, ProcEventInfo eventInfo) + { + PreventDefaultAction(); + + Unit caster = eventInfo.GetActor(); + float str = caster.GetStat(Stats.Strength); + float agi = caster.GetStat(Stats.Agility); + + switch (aurEff.GetId()) + { + case SpellIds.DeathChoiceNormalAura: + { + if (str > agi) + caster.CastSpell(caster, SpellIds.DeathChoiceNormalStrength, true, null, aurEff); + else + caster.CastSpell(caster, SpellIds.DeathChoiceNormalAgility, true, null, aurEff); + break; + } + case SpellIds.DeathChoiceHeroicAura: + { + if (str > agi) + caster.CastSpell(caster, SpellIds.DeathChoiceHeroicStrength, true, null, aurEff); + else + caster.CastSpell(caster, SpellIds.DeathChoiceHeroicAgility, true, null, aurEff); + break; + } + default: + break; + } + } + + public override void Register() + { + OnEffectProc.Add(new EffectProcHandler(HandleProc, 0, AuraType.PeriodicTriggerSpell)); + } + } + + public override AuraScript GetAuraScript() + { + return new spell_item_death_choice_AuraScript(); + } + } + + [Script("spell_item_lightning_capacitor", SpellIds.LightningCapacitorStack, SpellIds.LightningCapacitorTrigger)] + [Script("spell_item_thunder_capacitor", SpellIds.ThunderCapacitorStack, SpellIds.ThunderCapacitorTrigger)] + [Script("spell_item_toc25_normal_caster_trinket", SpellIds.Toc25CasterTrinketNormalStack, SpellIds.Toc25CasterTrinketNormalTrigger)] + [Script("spell_item_toc25_heroic_caster_trinket", SpellIds.Toc25CasterTrinketHeroicStack, SpellIds.Toc25CasterTrinketHeroicTrigger)] + class spell_item_trinket_stack : SpellScriptLoader + { + public spell_item_trinket_stack(string scriptName, uint stackSpell, uint triggerSpell) : base(scriptName) + { + _stackSpell = stackSpell; + _triggerSpell = triggerSpell; + } + + class spell_item_trinket_stack_AuraScript : AuraScript + { + public spell_item_trinket_stack_AuraScript(uint stackSpell, uint triggerSpell) + { + _stackSpell = stackSpell; + _triggerSpell = triggerSpell; + } + + public override bool Validate(SpellInfo spellInfo) + { + return ValidateSpellInfo(_stackSpell, _triggerSpell); + } + + void HandleProc(AuraEffect aurEff, ProcEventInfo eventInfo) + { + PreventDefaultAction(); + + Unit caster = eventInfo.GetActor(); + + caster.CastSpell(caster, _stackSpell, true, null, aurEff); // cast the stack + + Aura dummy = caster.GetAura(_stackSpell); // retrieve aura + + //dont do anything if it's not the right amount of stacks; + if (dummy == null || dummy.GetStackAmount() < aurEff.GetAmount()) + return; + + // if right amount, remove the aura and cast real trigger + caster.RemoveAurasDueToSpell(_stackSpell); + Unit target = eventInfo.GetActionTarget(); + if (target) + caster.CastSpell(target, _triggerSpell, true, null, aurEff); + } + + public override void Register() + { + OnEffectProc.Add(new EffectProcHandler(HandleProc, 0, AuraType.PeriodicTriggerSpell)); + } + + uint _stackSpell; + uint _triggerSpell; + } + + public override AuraScript GetAuraScript() + { + return new spell_item_trinket_stack_AuraScript(_stackSpell, _triggerSpell); + } + + uint _stackSpell; + uint _triggerSpell; + } + + [Script] // 57345 - Darkmoon Card: Greatness + class spell_item_darkmoon_card_greatness : SpellScriptLoader + { + public spell_item_darkmoon_card_greatness() : base("spell_item_darkmoon_card_greatness") { } + + class spell_item_darkmoon_card_greatness_AuraScript : AuraScript + { + public override bool Validate(SpellInfo spellInfo) + { + return ValidateSpellInfo(SpellIds.DarkmoonCardStrenght, SpellIds.DarkmoonCardAgility, SpellIds.DarkmoonCardIntellect, SpellIds.DarkmoonCardVersatility); + } + + void HandleProc(AuraEffect aurEff, ProcEventInfo eventInfo) + { + PreventDefaultAction(); + + Unit caster = eventInfo.GetActor(); + float str = caster.GetStat(Stats.Strength); + float agi = caster.GetStat(Stats.Agility); + float intl = caster.GetStat(Stats.Intellect); + float vers = 0.0f; // caster.GetStat(STAT_VERSATILITY); + float stat = 0.0f; + + uint spellTrigger = SpellIds.DarkmoonCardStrenght; + + if (str > stat) + { + spellTrigger = SpellIds.DarkmoonCardStrenght; + stat = str; + } + + if (agi > stat) + { + spellTrigger = SpellIds.DarkmoonCardAgility; + stat = agi; + } + + if (intl > stat) + { + spellTrigger = SpellIds.DarkmoonCardIntellect; + stat = intl; + } + + if (vers > stat) + { + spellTrigger = SpellIds.DarkmoonCardVersatility; + stat = vers; + } + + caster.CastSpell(caster, spellTrigger, true, null, aurEff); + } + + public override void Register() + { + OnEffectProc.Add(new EffectProcHandler(HandleProc, 0, AuraType.PeriodicTriggerSpell)); + } + } + + public override AuraScript GetAuraScript() + { + return new spell_item_darkmoon_card_greatness_AuraScript(); + } + } + + [Script] // 27522, 40336 - Mana Drain + class spell_item_mana_drain : SpellScriptLoader + { + public spell_item_mana_drain() : base("spell_item_mana_drain") { } + + class spell_item_mana_drain_AuraScript : AuraScript + { + public override bool Validate(SpellInfo spellInfo) + { + return ValidateSpellInfo(SpellIds.ManaDrainEnergize, SpellIds.ManaDrainLeech); + } + + void HandleProc(AuraEffect aurEff, ProcEventInfo eventInfo) + { + PreventDefaultAction(); + + Unit caster = eventInfo.GetActor(); + Unit target = eventInfo.GetActionTarget(); + + if (caster.IsAlive()) + caster.CastSpell(caster, SpellIds.ManaDrainEnergize, true, null, aurEff); + + if (target && target.IsAlive()) + caster.CastSpell(target, SpellIds.ManaDrainLeech, true, null, aurEff); + } + + public override void Register() + { + OnEffectProc.Add(new EffectProcHandler(HandleProc, 0, AuraType.PeriodicTriggerSpell)); + } + } + + public override AuraScript GetAuraScript() + { + return new spell_item_mana_drain_AuraScript(); + } + } + + [Script] // 51640 - Taunt Flag Targeting + class spell_item_taunt_flag_targeting : SpellScriptLoader + { + public spell_item_taunt_flag_targeting() : base("spell_item_taunt_flag_targeting") { } + + class spell_item_taunt_flag_targeting_SpellScript : SpellScript + { + public override bool Validate(SpellInfo spellInfo) + { + return CliDB.BroadcastTextStorage.ContainsKey(TextIds.EmotePlantsFlag) && ValidateSpellInfo(SpellIds.TauntFlag); + } + + void FilterTargets(List targets) + { + targets.RemoveAll(obj => + { + return !obj.IsTypeId(TypeId.Player) && !obj.IsTypeId(TypeId.Corpse); + }); + + if (targets.Empty()) + { + FinishCast(SpellCastResult.NoValidTargets); + return; + } + + targets.RandomResize(1); + } + + void HandleDummy(uint effIndex) + { + // we *really* want the unit implementation here + // it sends a packet like seen on sniff + GetCaster().TextEmote(TextIds.EmotePlantsFlag, GetHitUnit(), false); + + GetCaster().CastSpell(GetHitUnit(), SpellIds.TauntFlag, true); + } + + public override void Register() + { + OnObjectAreaTargetSelect.Add(new ObjectAreaTargetSelectHandler(FilterTargets, 0, Targets.CorpseSrcAreaEnemy)); + OnEffectHitTarget.Add(new EffectHandler(HandleDummy, 0, SpellEffectName.Dummy)); + } + } + + public override SpellScript GetSpellScript() + { + return new spell_item_taunt_flag_targeting_SpellScript(); + } + } + + // Item - 19950: Zandalarian Hero Charm + // 24658 - Unstable Power + // Item - 19949: Zandalarian Hero Medallion + // 24661 - Restless Strength + [Script("spell_item_unstable_power", SpellIds.UnstablePowerAuraStack)] + [Script("spell_item_restless_strength", SpellIds.RestlessStrengthAuraStack)] + class spell_item_zandalarian_charm : SpellScriptLoader + { + public spell_item_zandalarian_charm(string ScriptName, uint SpellId) : base(ScriptName) + { + _spellId = SpellId; + } + + class spell_item_zandalarian_charm_AuraScript : AuraScript + { + public spell_item_zandalarian_charm_AuraScript(uint SpellId) + { + _spellId = SpellId; + } + + public override bool Validate(SpellInfo spellInfo) + { + return ValidateSpellInfo(_spellId); + } + + bool CheckProc(ProcEventInfo eventInfo) + { + SpellInfo spellInfo = eventInfo.GetSpellInfo(); + if (spellInfo != null) + if (spellInfo.Id != m_scriptSpellId) + return true; + + return false; + } + + void HandleStackDrop(AuraEffect aurEff, ProcEventInfo eventInfo) + { + PreventDefaultAction(); + GetTarget().RemoveAuraFromStack(_spellId); + } + + public override void Register() + { + DoCheckProc.Add(new CheckProcHandler(CheckProc)); + OnEffectProc.Add(new EffectProcHandler(HandleStackDrop, 0, AuraType.Dummy)); + } + + uint _spellId; + } + + public override AuraScript GetAuraScript() + { + return new spell_item_zandalarian_charm_AuraScript(_spellId); + } + + uint _spellId; + } + + [Script] + class spell_item_artifical_stamina : SpellScriptLoader + { + public spell_item_artifical_stamina() : base("spell_item_artifical_stamina") { } + + class spell_item_artifical_stamina_AuraScript : AuraScript + { + public override bool Validate(SpellInfo spellInfo) + { + return spellInfo.GetEffect(1) != null; + } + + public override bool Load() + { + return GetOwner().IsTypeId(TypeId.Player); + } + + void CalculateAmount(AuraEffect aurEff, ref int amount, ref bool canBeRecalculated) + { + Item artifact = GetOwner().ToPlayer().GetItemByGuid(GetAura().GetCastItemGUID()); + if (artifact) + amount = (int)(GetSpellInfo().GetEffect(1).BasePoints * artifact.GetTotalPurchasedArtifactPowers() / 100); + } + + public override void Register() + { + DoEffectCalcAmount.Add(new EffectCalcAmountHandler(CalculateAmount, 0, AuraType.ModTotalStatPercentage)); + } + } + + public override AuraScript GetAuraScript() + { + return new spell_item_artifical_stamina_AuraScript(); + } + } + + [Script] + class spell_item_artifical_damage : SpellScriptLoader + { + public spell_item_artifical_damage() : base("spell_item_artifical_damage") { } + + class spell_item_artifical_damage_AuraScript : AuraScript + { + public override bool Validate(SpellInfo spellInfo) + { + return spellInfo.GetEffect(1) != null; + } + + public override bool Load() + { + return GetOwner().IsTypeId(TypeId.Player); + } + + void CalculateAmount(AuraEffect aurEff, ref int amount, ref bool canBeRecalculated) + { + Item artifact = GetOwner().ToPlayer().GetItemByGuid(GetAura().GetCastItemGUID()); + if (artifact) + amount = (int)(GetSpellInfo().GetEffect(1).BasePoints * artifact.GetTotalPurchasedArtifactPowers() / 100); + } + + public override void Register() + { + DoEffectCalcAmount.Add(new EffectCalcAmountHandler(CalculateAmount, 0, AuraType.ModDamagePercentDone)); + } + } + + public override AuraScript GetAuraScript() + { + return new spell_item_artifical_damage_AuraScript(); + } + } + + [Script] // 28200 - Ascendance + class spell_item_talisman_of_ascendance : SpellScriptLoader + { + public spell_item_talisman_of_ascendance() : base("spell_item_talisman_of_ascendance") { } + + class spell_item_talisman_of_ascendance_AuraScript : AuraScript + { + public override bool Validate(SpellInfo spell) + { + return ValidateSpellInfo(SpellIds.TalismanOfAscendance); + } + + void OnRemove(AuraEffect effect, AuraEffectHandleModes mode) + { + GetTarget().RemoveAurasDueToSpell(effect.GetSpellEffectInfo().TriggerSpell); + } + + public override void Register() + { + OnEffectRemove.Add(new EffectApplyHandler(OnRemove, 0, AuraType.ProcTriggerSpell, AuraEffectHandleModes.Real)); + } + } + + public override AuraScript GetAuraScript() + { + return new spell_item_talisman_of_ascendance_AuraScript(); + } + } + + [Script] // 29602 - Jom Gabbar + class spell_item_jom_gabbar : SpellScriptLoader + { + public spell_item_jom_gabbar() : base("spell_item_jom_gabbar") { } + + class spell_item_jom_gabbar_AuraScript : AuraScript + { + public override bool Validate(SpellInfo spell) + { + return ValidateSpellInfo(SpellIds.JomGabbar); + } + + void OnRemove(AuraEffect effect, AuraEffectHandleModes mode) + { + GetTarget().RemoveAurasDueToSpell(effect.GetSpellEffectInfo().TriggerSpell); + } + + public override void Register() + { + OnEffectRemove.Add(new EffectApplyHandler(OnRemove, 0, AuraType.PeriodicTriggerSpell, AuraEffectHandleModes.Real)); + } + } + + public override AuraScript GetAuraScript() + { + return new spell_item_jom_gabbar_AuraScript(); + } + } + + [Script] // 45040 - Battle Trance + class spell_item_battle_trance : SpellScriptLoader + { + public spell_item_battle_trance() : base("spell_item_battle_trance") { } + + class spell_item_battle_trance_AuraScript : AuraScript + { + public override bool Validate(SpellInfo spell) + { + return ValidateSpellInfo(SpellIds.BattleTrance); + } + + void OnRemove(AuraEffect effect, AuraEffectHandleModes mode) + { + GetTarget().RemoveAurasDueToSpell(effect.GetSpellEffectInfo().TriggerSpell); + } + + public override void Register() + { + OnEffectRemove.Add(new EffectApplyHandler(OnRemove, 0, AuraType.ProcTriggerSpell, AuraEffectHandleModes.Real)); + } + } + + public override AuraScript GetAuraScript() + { + return new spell_item_battle_trance_AuraScript(); + } + } + + [Script] // 90900 - World-Queller Focus + class spell_item_world_queller_focus : SpellScriptLoader + { + public spell_item_world_queller_focus() : base("spell_item_world_queller_focus") { } + + class spell_item_world_queller_focus_AuraScript : AuraScript + { + public override bool Validate(SpellInfo spell) + { + return ValidateSpellInfo(SpellIds.WorldQuellerFocus); + } + + void OnRemove(AuraEffect effect, AuraEffectHandleModes mode) + { + GetTarget().RemoveAurasDueToSpell(effect.GetSpellEffectInfo().TriggerSpell); + } + + public override void Register() + { + OnEffectRemove.Add(new EffectApplyHandler(OnRemove, 0, AuraType.ProcTriggerSpell, AuraEffectHandleModes.Real)); + } + } + + public override AuraScript GetAuraScript() + { + return new spell_item_world_queller_focus_AuraScript(); + } + } + + // 118089 - Azure Water Strider + // 127271 - Crimson Water Strider + // 127272 - Orange Water Strider + // 127274 - Jade Water Strider + [Script] // 127278 - Golden Water Strider + class spell_item_water_strider : SpellScriptLoader + { + public spell_item_water_strider() : base("spell_item_water_strider") { } + + class spell_item_water_strider_AuraScript : AuraScript + { + public override bool Validate(SpellInfo spell) + { + return ValidateSpellInfo(SpellIds.AzureWaterStrider, SpellIds.CrimsonWaterStrider, SpellIds.OrangeWaterStrider, SpellIds.JadeWaterStrider, SpellIds.GoldenWaterStrider); + } + + void OnRemove(AuraEffect effect, AuraEffectHandleModes mode) + { + GetTarget().RemoveAurasDueToSpell(GetSpellInfo().GetEffect(1).TriggerSpell); + } + + public override void Register() + { + OnEffectRemove.Add(new EffectApplyHandler(OnRemove, 0, AuraType.Mounted, AuraEffectHandleModes.Real)); + } + } + + public override AuraScript GetAuraScript() + { + return new spell_item_water_strider_AuraScript(); + } + } + + // 144671 - Brutal Kinship + [Script] // 145738 - Brutal Kinship + class spell_item_brutal_kinship : SpellScriptLoader + { + public spell_item_brutal_kinship() : base("spell_item_brutal_kinship") { } + + class spell_item_brutal_kinship_AuraScript : AuraScript + { + public override bool Validate(SpellInfo spell) + { + return ValidateSpellInfo(SpellIds.BrutalKinship1, SpellIds.BrutalKinship2); + } + + void OnRemove(AuraEffect effect, AuraEffectHandleModes mode) + { + GetTarget().RemoveAurasDueToSpell(effect.GetSpellEffectInfo().TriggerSpell); + } + + public override void Register() + { + OnEffectRemove.Add(new EffectApplyHandler(OnRemove, 0, AuraType.ProcTriggerSpell, AuraEffectHandleModes.Real)); + } + } + + public override AuraScript GetAuraScript() + { + return new spell_item_brutal_kinship_AuraScript(); + } + } +} diff --git a/Scripts/Spells/Mage.cs b/Scripts/Spells/Mage.cs new file mode 100644 index 000000000..f57e91fea --- /dev/null +++ b/Scripts/Spells/Mage.cs @@ -0,0 +1,1031 @@ +/* + * Copyright (C) 2012-2017 CypherCore + * + * 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, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +using Framework.Constants; +using Game.Entities; +using Game.Groups; +using Game.Maps; +using Game.Scripting; +using Game.Spells; +using System; +using System.Collections.Generic; +using System.Linq; + +namespace Scripts.Spells.Mage +{ + struct SpellIds + { + public const uint BlazingBarrierTrigger = 235314; + public const uint ColdSnap = 11958; + public const uint ConjureRefreshment = 116136; + public const uint ConjureRefreshmentTable = 167145; + public const uint FingersOfFrost = 44544; + public const uint FocusMagicProc = 54648; + public const uint FrostNova = 122; + public const uint ImprovedPolymorphRank1 = 11210; + public const uint ImprovedPolymorphStunRank1 = 83046; + public const uint ImprovedPolymorphMarker = 87515; + public const uint Ignite = 12654; + public const uint ManaSurge = 37445; + public const uint MasterOfElementsEnergize = 29077; + public const uint Permafrost = 91394; + public const uint Slow = 31589; + public const uint SquirrelForm = 32813; + public const uint GiraffeForm = 32816; + public const uint SerpentForm = 32817; + public const uint DragonhawkForm = 32818; + public const uint WorgenForm = 32819; + public const uint SheepForm = 32820; + + public const uint ConeOfColdAuraR1 = 11190; + public const uint ConeOfColdAuraR2 = 12489; + public const uint ConeOfColdTriggerR1 = 83301; + public const uint ConeOfColdTriggerR2 = 83302; + + public const uint RingOfFrostSummon = 82676; + public const uint RingOfFrostFreeze = 82691; + public const uint RingOfFrostDummy = 91264; + + public const uint TemporalDisplacement = 80354; + + public const uint Chilled = 205708; + + //Misc + public const uint HunterInsanity = 95809; + public const uint PriestShadowWordDeath = 32409; + public const uint ShamanExhaustion = 57723; + public const uint ShamanSated = 57724; + } + + [Script] // 235313 - Blazing Barrier + class spell_mage_blazing_barrier : SpellScriptLoader + { + public spell_mage_blazing_barrier() : base("spell_mage_blazing_barrier") { } + + class spell_mage_blazing_barrier_AuraScript : AuraScript + { + public override bool Validate(SpellInfo spellInfo) + { + return ValidateSpellInfo(SpellIds.BlazingBarrierTrigger); + } + + void CalculateAmount(AuraEffect aurEff, ref int amount, ref bool canBeRecalculated) + { + canBeRecalculated = false; + Unit caster = GetCaster(); + if (caster) + amount = (int)(caster.SpellBaseHealingBonusDone(GetSpellInfo().GetSchoolMask()) * 7.0f); + } + + void HandleProc(AuraEffect aurEff, ProcEventInfo eventInfo) + { + PreventDefaultAction(); + Unit caster = eventInfo.GetDamageInfo().GetVictim(); + Unit target = eventInfo.GetDamageInfo().GetAttacker(); + + if (caster && target) + caster.CastSpell(target, SpellIds.BlazingBarrierTrigger, true); + } + + public override void Register() + { + DoEffectCalcAmount.Add(new EffectCalcAmountHandler(CalculateAmount, 0, AuraType.SchoolAbsorb)); + OnEffectProc.Add(new EffectProcHandler(HandleProc, 1, AuraType.ProcTriggerSpell)); + } + } + + public override AuraScript GetAuraScript() + { + return new spell_mage_blazing_barrier_AuraScript(); + } + } + + [Script] // 198063 - Burning Determination + class spell_mage_burning_determination : SpellScriptLoader + { + public spell_mage_burning_determination() : base("spell_mage_burning_determination") { } + + class spell_mage_burning_determination_AuraScript : AuraScript + { + bool CheckProc(ProcEventInfo eventInfo) + { + SpellInfo spellInfo = eventInfo.GetSpellInfo(); + if (spellInfo != null) + if (spellInfo.GetAllEffectsMechanicMask().HasAnyFlag((uint)((1 << (int)Mechanics.Interrupt) | (1 << (int)Mechanics.Silence)))) + return true; + + return false; + } + + public override void Register() + { + DoCheckProc.Add(new CheckProcHandler(CheckProc)); + } + } + + public override AuraScript GetAuraScript() + { + return new spell_mage_burning_determination_AuraScript(); + } + } + + // 11958 - Cold Snap + [Script] + class spell_mage_cold_snap : SpellScriptLoader + { + public spell_mage_cold_snap() : base("spell_mage_cold_snap") { } + + class spell_mage_cold_snap_SpellScript : SpellScript + { + public override bool Load() + { + return GetCaster().IsTypeId(TypeId.Player); + } + + void HandleDummy(uint effIndex) + { + GetCaster().GetSpellHistory().ResetCooldowns(p => + { + SpellInfo spellInfo = Global.SpellMgr.GetSpellInfo(p.Key); + return spellInfo.SpellFamilyName == SpellFamilyNames.Mage && spellInfo.GetSchoolMask().HasAnyFlag(SpellSchoolMask.Frost) && + spellInfo.Id != SpellIds.ColdSnap && spellInfo.GetRecoveryTime() > 0; + }, true); + } + + public override void Register() + { + OnEffectHit.Add(new EffectHandler(HandleDummy, 0, SpellEffectName.Dummy)); + } + } + + public override SpellScript GetSpellScript() + { + return new spell_mage_cold_snap_SpellScript(); + } + } + + // Updated 4.3.4 + [Script] // 120 - Cone of Cold + class spell_mage_cone_of_cold : SpellScriptLoader + { + public spell_mage_cone_of_cold() : base("spell_mage_cone_of_cold") { } + + class spell_mage_cone_of_cold_SpellScript : SpellScript + { + void HandleConeOfColdScript(uint effIndex) + { + Unit caster = GetCaster(); + Unit unitTarget = GetHitUnit(); + if (unitTarget) + { + if (caster.HasAura(SpellIds.ConeOfColdAuraR1)) // Improved Cone of Cold Rank 1 + unitTarget.CastSpell(unitTarget, SpellIds.ConeOfColdTriggerR1, true); + else if (caster.HasAura(SpellIds.ConeOfColdAuraR2)) // Improved Cone of Cold Rank 2 + unitTarget.CastSpell(unitTarget, SpellIds.ConeOfColdTriggerR2, true); + } + } + + public override void Register() + { + OnEffectHitTarget.Add(new EffectHandler(HandleConeOfColdScript, 0, SpellEffectName.ApplyAura)); + } + } + + public override SpellScript GetSpellScript() + { + return new spell_mage_cone_of_cold_SpellScript(); + } + } + + + [Script] // 190336 - Conjure Refreshment + class spell_mage_conjure_refreshment : SpellScriptLoader + { + public spell_mage_conjure_refreshment() : base("spell_mage_conjure_refreshment") { } + + class spell_mage_conjure_refreshment_SpellScript : SpellScript + { + public override bool Validate(SpellInfo spellInfo) + { + return ValidateSpellInfo(SpellIds.ConjureRefreshment, SpellIds.ConjureRefreshmentTable); + } + + void HandleDummy(uint effIndex) + { + Player caster = GetCaster().ToPlayer(); + if (caster) + { + Group group = caster.GetGroup(); + if (group) + caster.CastSpell(caster, SpellIds.ConjureRefreshmentTable, true); + else + caster.CastSpell(caster, SpellIds.ConjureRefreshment, true); + } + } + + public override void Register() + { + OnEffectHitTarget.Add(new EffectHandler(HandleDummy, 0, SpellEffectName.Dummy)); + } + } + + public override SpellScript GetSpellScript() + { + return new spell_mage_conjure_refreshment_SpellScript(); + } + } + + // 54646 - Focus Magic + [Script] + class spell_mage_focus_magic : SpellScriptLoader + { + public spell_mage_focus_magic() : base("spell_mage_focus_magic") { } + + class spell_mage_focus_magic_AuraScript : AuraScript + { + public override bool Validate(SpellInfo spellInfo) + { + return ValidateSpellInfo(SpellIds.FocusMagicProc); + } + + public override bool Load() + { + _procTarget = null; + return true; + } + + bool CheckProc(ProcEventInfo eventInfo) + { + _procTarget = GetCaster(); + return _procTarget && _procTarget.IsAlive(); + } + + void HandleProc(AuraEffect aurEff, ProcEventInfo eventInfo) + { + PreventDefaultAction(); + GetTarget().CastSpell(_procTarget, SpellIds.FocusMagicProc, true, null, aurEff); + } + + public override void Register() + { + DoCheckProc.Add(new CheckProcHandler(CheckProc)); + OnEffectProc.Add(new EffectProcHandler(HandleProc, 0, AuraType.ModSpellCritChance)); + } + + Unit _procTarget; + } + + public override AuraScript GetAuraScript() + { + return new spell_mage_focus_magic_AuraScript(); + } + } + + [Script] // 195283 - Hot Streak + class spell_mage_hot_streak : SpellScriptLoader + { + public spell_mage_hot_streak() : base("spell_mage_hot_streak") { } + + class spell_mage_hot_streak_AuraScript : AuraScript + { + public override bool Validate(SpellInfo spellInfo) + { + return true; + } + + void HandleProc(AuraEffect aurEff, ProcEventInfo eventInfo) + { + } + + public override void Register() + { + OnEffectProc.Add(new EffectProcHandler(HandleProc, 0, AuraType.Dummy)); + } + } + + public override AuraScript GetAuraScript() + { + return new spell_mage_hot_streak_AuraScript(); + } + } + + // 56374 - Glyph of Icy Veins + [Script] + class spell_mage_glyph_of_icy_veins : SpellScriptLoader + { + public spell_mage_glyph_of_icy_veins() : base("spell_mage_glyph_of_icy_veins") { } + + class spell_mage_glyph_of_icy_veins_AuraScript : AuraScript + { + void HandleEffectProc(AuraEffect aurEff, ProcEventInfo eventInfo) + { + PreventDefaultAction(); + + GetTarget().RemoveAurasByType(AuraType.HasteSpells, ObjectGuid.Empty, null, true, false); + GetTarget().RemoveAurasByType(AuraType.ModDecreaseSpeed); + } + + public override void Register() + { + OnEffectProc.Add(new EffectProcHandler(HandleEffectProc, 0, AuraType.Dummy)); + } + } + + public override AuraScript GetAuraScript() + { + return new spell_mage_glyph_of_icy_veins_AuraScript(); + } + } + + // 56375 - Glyph of Polymorph + [Script] + class spell_mage_glyph_of_polymorph : SpellScriptLoader + { + public spell_mage_glyph_of_polymorph() : base("spell_mage_glyph_of_polymorph") { } + + class spell_mage_glyph_of_polymorph_AuraScript : AuraScript + { + public override bool Validate(SpellInfo spellInfo) + { + return ValidateSpellInfo(SpellIds.PriestShadowWordDeath); + } + + void HandleEffectProc(AuraEffect aurEff, ProcEventInfo eventInfo) + { + PreventDefaultAction(); + Unit target = eventInfo.GetProcTarget(); + + target.RemoveAurasByType(AuraType.PeriodicDamage, ObjectGuid.Empty, target.GetAura(SpellIds.PriestShadowWordDeath)); // SW:D shall not be removed. + target.RemoveAurasByType(AuraType.PeriodicDamagePercent); + target.RemoveAurasByType(AuraType.PeriodicLeech); + } + + public override void Register() + { + OnEffectProc.Add(new EffectProcHandler(HandleEffectProc, 0, AuraType.Dummy)); + } + } + + public override AuraScript GetAuraScript() + { + return new spell_mage_glyph_of_polymorph_AuraScript(); + } + } + + // 37447 - Improved Mana Gems + [Script] // 61062 - Improved Mana Gems + class spell_mage_imp_mana_gems : SpellScriptLoader + { + public spell_mage_imp_mana_gems() : base("spell_mage_imp_mana_gems") { } + + class spell_mage_imp_mana_gems_AuraScript : AuraScript + { + public override bool Validate(SpellInfo spellInfo) + { + return ValidateSpellInfo(SpellIds.ManaSurge); + } + + void HandleProc(AuraEffect aurEff, ProcEventInfo eventInfo) + { + PreventDefaultAction(); + eventInfo.GetActor().CastSpell((Unit)null, SpellIds.ManaSurge, true); + } + + public override void Register() + { + OnEffectProc.Add(new EffectProcHandler(HandleProc, 1, AuraType.OverrideClassScripts)); + } + } + + public override AuraScript GetAuraScript() + { + return new spell_mage_imp_mana_gems_AuraScript(); + } + } + + // 44457 - Living Bomb + [Script] + class spell_mage_living_bomb : SpellScriptLoader + { + public spell_mage_living_bomb() : base("spell_mage_living_bomb") { } + + class spell_mage_living_bomb_AuraScript : AuraScript + { + public override bool Validate(SpellInfo spellInfo) + { + return ValidateSpellInfo((uint)spellInfo.GetEffect(1).CalcValue()); + } + + void AfterRemove(AuraEffect aurEff, AuraEffectHandleModes mode) + { + AuraRemoveMode removeMode = GetTargetApplication().GetRemoveMode(); + if (removeMode != AuraRemoveMode.EnemySpell && removeMode != AuraRemoveMode.Expire) + return; + Unit caster = GetCaster(); + if (caster) + caster.CastSpell(GetTarget(), (uint)aurEff.GetAmount(), true, null, aurEff); + } + + public override void Register() + { + AfterEffectRemove.Add(new EffectApplyHandler(AfterRemove, 1, AuraType.Dummy, AuraEffectHandleModes.Real)); + } + } + + public override AuraScript GetAuraScript() + { + return new spell_mage_living_bomb_AuraScript(); + } + } + + [Script] // 11426 - Ice Barrier + class spell_mage_ice_barrier : SpellScriptLoader + { + public spell_mage_ice_barrier() : base("spell_mage_ice_barrier") { } + + class spell_mage_ice_barrier_AuraScript : AuraScript + { + public override bool Validate(SpellInfo spellEntry) + { + return ValidateSpellInfo(SpellIds.Chilled); + } + + void CalculateAmount(AuraEffect aurEff, ref int amount, ref bool canBeRecalculated) + { + canBeRecalculated = false; + Unit caster = GetCaster(); + if (caster) + amount += (int)(caster.SpellBaseHealingBonusDone(GetSpellInfo().GetSchoolMask()) * 10.0f); + } + + void HandleProc(AuraEffect aurEff, ProcEventInfo eventInfo) + { + Unit caster = eventInfo.GetDamageInfo().GetVictim(); + Unit target = eventInfo.GetDamageInfo().GetAttacker(); + + if (caster && target) + caster.CastSpell(target, SpellIds.Chilled, true); + } + + public override void Register() + { + DoEffectCalcAmount.Add(new EffectCalcAmountHandler(CalculateAmount, 0, AuraType.SchoolAbsorb)); + OnEffectProc.Add(new EffectProcHandler(HandleProc, 0, AuraType.SchoolAbsorb)); + } + } + + public override AuraScript GetAuraScript() + { + return new spell_mage_ice_barrier_AuraScript(); + } + } + + // -11119 - Ignite + [Script] + class spell_mage_ignite : SpellScriptLoader + { + public spell_mage_ignite() : base("spell_mage_ignite") { } + + class spell_mage_ignite_AuraScript : AuraScript + { + public override bool Validate(SpellInfo spellInfo) + { + return ValidateSpellInfo(SpellIds.Ignite); + } + + bool CheckProc(ProcEventInfo eventInfo) + { + return eventInfo.GetProcTarget(); + } + + void HandleProc(AuraEffect aurEff, ProcEventInfo eventInfo) + { + PreventDefaultAction(); + + SpellInfo igniteDot = Global.SpellMgr.GetSpellInfo(SpellIds.Ignite); + int pct = 8 * GetSpellInfo().GetRank(); + + int amount = (int)(MathFunctions.CalculatePct(eventInfo.GetDamageInfo().GetDamage(), pct) / igniteDot.GetMaxTicks(Difficulty.None)); + amount += (int)eventInfo.GetProcTarget().GetRemainingPeriodicAmount(eventInfo.GetActor().GetGUID(), SpellIds.Ignite, AuraType.PeriodicDamage); + GetTarget().CastCustomSpell(SpellIds.Ignite, SpellValueMod.BasePoint0, amount, eventInfo.GetProcTarget(), true, null, aurEff); + } + + public override void Register() + { + DoCheckProc.Add(new CheckProcHandler(CheckProc)); + OnEffectProc.Add(new EffectProcHandler(HandleProc, 0, AuraType.Dummy)); + } + } + + public override AuraScript GetAuraScript() + { + return new spell_mage_ignite_AuraScript(); + } + } + + // -29074 - Master of Elements + [Script] + class spell_mage_master_of_elements : SpellScriptLoader + { + public spell_mage_master_of_elements() : base("spell_mage_master_of_elements") { } + + class spell_mage_master_of_elements_AuraScript : AuraScript + { + public override bool Validate(SpellInfo spellInfo) + { + return ValidateSpellInfo(SpellIds.MasterOfElementsEnergize); + } + + bool CheckProc(ProcEventInfo eventInfo) + { + return eventInfo.GetDamageInfo().GetSpellInfo() != null; // eventInfo.GetSpellInfo() + } + + void HandleProc(AuraEffect aurEff, ProcEventInfo eventInfo) + { + PreventDefaultAction(); + + var costs = eventInfo.GetDamageInfo().GetSpellInfo().CalcPowerCost(GetTarget(), eventInfo.GetDamageInfo().GetSchoolMask()); + var m = costs.Find(cost => cost.Power == PowerType.Mana); + if (m != null) + { + int mana = MathFunctions.CalculatePct(m.Amount, aurEff.GetAmount()); + if (mana > 0) + GetTarget().CastCustomSpell(SpellIds.MasterOfElementsEnergize, SpellValueMod.BasePoint0, mana, GetTarget(), true, null, aurEff); + } + } + + public override void Register() + { + DoCheckProc.Add(new CheckProcHandler(CheckProc)); + OnEffectProc.Add(new EffectProcHandler(HandleProc, 0, AuraType.Dummy)); + } + } + + public override AuraScript GetAuraScript() + { + return new spell_mage_master_of_elements_AuraScript(); + } + } + + // 86181 - Nether Vortex + [Script] + class spell_mage_nether_vortex : SpellScriptLoader + { + public spell_mage_nether_vortex() : base("spell_mage_nether_vortex") { } + + class spell_mage_nether_vortex_AuraScript : AuraScript + { + public override bool Validate(SpellInfo spellInfo) + { + return ValidateSpellInfo(SpellIds.Slow); + } + + bool DoCheck(ProcEventInfo eventInfo) + { + Aura aura = eventInfo.GetProcTarget().GetAura(SpellIds.Slow); + if (aura != null) + if (aura.GetCasterGUID() != GetTarget().GetGUID()) + return false; + + return true; + } + + void HandleEffectProc(AuraEffect aurEff, ProcEventInfo eventInfo) + { + PreventDefaultAction(); + GetTarget().CastSpell(eventInfo.GetProcTarget(), SpellIds.Slow, true, null, aurEff); + } + + public override void Register() + { + DoCheckProc.Add(new CheckProcHandler(DoCheck)); + OnEffectProc.Add(new EffectProcHandler(HandleEffectProc, 0, AuraType.Dummy)); + } + } + + public override AuraScript GetAuraScript() + { + return new spell_mage_nether_vortex_AuraScript(); + } + } + + // -11175 - Permafrost + [Script] + class spell_mage_permafrost : SpellScriptLoader + { + public spell_mage_permafrost() : base("spell_mage_permafrost") { } + + class spell_mage_permafrost_AuraScript : AuraScript + { + public override bool Validate(SpellInfo spellInfo) + { + return ValidateSpellInfo(SpellIds.Permafrost); + } + + bool DoCheck(ProcEventInfo eventInfo) + { + return GetTarget().GetGuardianPet() && eventInfo.GetDamageInfo().GetDamage() != 0; + } + + void HandleEffectProc(AuraEffect aurEff, ProcEventInfo eventInfo) + { + PreventDefaultAction(); + + int heal = (int)(MathFunctions.CalculatePct(eventInfo.GetDamageInfo().GetDamage(), aurEff.GetAmount())); + GetTarget().CastCustomSpell(SpellIds.Permafrost, SpellValueMod.BasePoint0, heal, null, true, null, aurEff); + } + + public override void Register() + { + DoCheckProc.Add(new CheckProcHandler(DoCheck)); + OnEffectProc.Add(new EffectProcHandler(HandleEffectProc, 0, AuraType.Dummy)); + } + } + + public override AuraScript GetAuraScript() + { + return new spell_mage_permafrost_AuraScript(); + } + } + + // 118 - Polymorph + [Script] + class spell_mage_polymorph : SpellScriptLoader + { + public spell_mage_polymorph() : base("spell_mage_polymorph") { } + + class spell_mage_polymorph_AuraScript : AuraScript + { + public override bool Validate(SpellInfo spellInfo) + { + return ValidateSpellInfo(SpellIds.ImprovedPolymorphRank1, SpellIds.ImprovedPolymorphStunRank1, SpellIds.ImprovedPolymorphMarker); + } + + public override bool Load() + { + _caster = null; + return true; + } + + bool DoCheck(ProcEventInfo eventInfo) + { + _caster = GetCaster(); + return _caster && eventInfo.GetDamageInfo() != null; + } + + void HandleEffectProc(AuraEffect aurEff, ProcEventInfo eventInfo) + { + PreventDefaultAction(); + // Improved Polymorph + AuraEffect improvedPolymorph = _caster.GetAuraEffectOfRankedSpell(SpellIds.ImprovedPolymorphRank1, 0); + if (improvedPolymorph != null) + { + if (_caster.HasAura(SpellIds.ImprovedPolymorphMarker)) + return; + + GetTarget().CastSpell(GetTarget(), Global.SpellMgr.GetSpellWithRank(SpellIds.ImprovedPolymorphStunRank1, improvedPolymorph.GetSpellInfo().GetRank()), true, null, aurEff); + _caster.CastSpell(_caster, SpellIds.ImprovedPolymorphMarker, true, null, aurEff); + } + } + + public override void Register() + { + DoCheckProc.Add(new CheckProcHandler(DoCheck)); + OnEffectProc.Add(new EffectProcHandler(HandleEffectProc, 0, AuraType.ModConfuse)); + } + + Unit _caster; + } + + public override AuraScript GetAuraScript() + { + return new spell_mage_polymorph_AuraScript(); + } + } + + // @todo move out of here and rename - not a mage spell + // 32826 - Polymorph (Visual) + [Script] + class spell_mage_polymorph_cast_visual : SpellScriptLoader + { + public spell_mage_polymorph_cast_visual() : base("spell_mage_polymorph_visual") { } + + const uint NPC_AUROSALIA = 18744; + + class spell_mage_polymorph_cast_visual_SpellScript : SpellScript + { + uint[] PolymorhForms = + { + SpellIds.SquirrelForm, + SpellIds.GiraffeForm, + SpellIds.SerpentForm, + SpellIds.DragonhawkForm, + SpellIds.WorgenForm, + SpellIds.SheepForm + }; + + public override bool Validate(SpellInfo spellInfo) + { + return ValidateSpellInfo(PolymorhForms); + } + + void HandleDummy(uint effIndex) + { + Unit target = GetCaster().FindNearestCreature(NPC_AUROSALIA, 30.0f); + if (target) + if (target.IsTypeId(TypeId.Unit)) + target.CastSpell(target, PolymorhForms[RandomHelper.IRand(0, 5)], true); + } + + public override void Register() + { + // add dummy effect spell handler to Polymorph visual + OnEffectHitTarget.Add(new EffectHandler(HandleDummy, 0, SpellEffectName.Dummy)); + } + } + + public override SpellScript GetSpellScript() + { + return new spell_mage_polymorph_cast_visual_SpellScript(); + } + } + + [Script] // 235450 - Prismatic Barrier + class spell_mage_prismatic_barrier : SpellScriptLoader + { + public spell_mage_prismatic_barrier() : base("spell_mage_prismatic_barrier") { } + + class spell_mage_prismatic_barrier_AuraScript : AuraScript + { + void CalculateAmount(AuraEffect aurEff, ref int amount, ref bool canBeRecalculated) + { + canBeRecalculated = false; + Unit caster = GetCaster(); + if (caster) + amount += (int)(caster.SpellBaseHealingBonusDone(GetSpellInfo().GetSchoolMask()) * 7.0f); + } + + public override void Register() + { + DoEffectCalcAmount.Add(new EffectCalcAmountHandler(CalculateAmount, 0, AuraType.SchoolAbsorb)); + } + } + + public override AuraScript GetAuraScript() + { + return new spell_mage_prismatic_barrier_AuraScript(); + } + } + + // 82676 - Ring of Frost + // Updated 4.3.4 + [Script] + class spell_mage_ring_of_frost : SpellScriptLoader + { + public spell_mage_ring_of_frost() : base("spell_mage_ring_of_frost") { } + + class spell_mage_ring_of_frost_AuraScript : AuraScript + { + public override bool Validate(SpellInfo spellInfo) + { + return ValidateSpellInfo(SpellIds.RingOfFrostSummon, SpellIds.RingOfFrostFreeze, SpellIds.RingOfFrostDummy); + } + + public override bool Load() + { + ringOfFrost = null; + return true; + } + + void HandleEffectPeriodic(AuraEffect aurEff) + { + if (ringOfFrost) + if (GetMaxDuration() - (int)ringOfFrost.GetTimer() >= Global.SpellMgr.GetSpellInfo(SpellIds.RingOfFrostDummy).GetDuration()) + GetTarget().CastSpell(ringOfFrost.GetPositionX(), ringOfFrost.GetPositionY(), ringOfFrost.GetPositionZ(), SpellIds.RingOfFrostFreeze, true); + } + + void Apply(AuraEffect aurEff, AuraEffectHandleModes mode) + { + List MinionList = new List(); + GetTarget().GetAllMinionsByEntry(MinionList, (uint)GetSpellInfo().GetEffect(0).MiscValue); + + // Get the last summoned RoF, save it and despawn older ones + foreach (var creature in MinionList) + { + TempSummon summon = creature.ToTempSummon(); + + if (ringOfFrost && summon) + { + if (summon.GetTimer() > ringOfFrost.GetTimer()) + { + ringOfFrost.DespawnOrUnsummon(); + ringOfFrost = summon; + } + else + summon.DespawnOrUnsummon(); + } + else if (summon) + ringOfFrost = summon; + } + } + + TempSummon ringOfFrost; + + public override void Register() + { + OnEffectPeriodic.Add(new EffectPeriodicHandler(HandleEffectPeriodic, 1, AuraType.ProcTriggerSpell)); + OnEffectApply.Add(new EffectApplyHandler(Apply, 1, AuraType.ProcTriggerSpell, AuraEffectHandleModes.RealOrReapplyMask)); + } + } + + public override AuraScript GetAuraScript() + { + return new spell_mage_ring_of_frost_AuraScript(); + } + } + + // 82691 - Ring of Frost (freeze efect) + // Updated 4.3.4 + [Script] + class spell_mage_ring_of_frost_freeze : SpellScriptLoader + { + public spell_mage_ring_of_frost_freeze() : base("spell_mage_ring_of_frost_freeze") { } + + class spell_mage_ring_of_frost_freeze_SpellScript : SpellScript + { + public override bool Validate(SpellInfo spellInfo) + { + return ValidateSpellInfo(SpellIds.RingOfFrostSummon, SpellIds.RingOfFrostFreeze); + } + + void FilterTargets(List targets) + { + float outRadius = Global.SpellMgr.GetSpellInfo(SpellIds.RingOfFrostSummon).GetEffect(0).CalcRadius(); + float inRadius = 4.7f; + + foreach (var obj in targets.ToList()) + { + Unit unit = obj.ToUnit(); + if (unit) + if (unit.HasAura(SpellIds.RingOfFrostDummy) || unit.HasAura(SpellIds.RingOfFrostFreeze) || unit.GetExactDist(GetExplTargetDest()) > outRadius || unit.GetExactDist(GetExplTargetDest()) < inRadius) + targets.Remove(obj); + } + } + + public override void Register() + { + OnObjectAreaTargetSelect.Add(new ObjectAreaTargetSelectHandler(FilterTargets, 0, Targets.UnitDestAreaEnemy)); + } + } + + public override SpellScript GetSpellScript() + { + return new spell_mage_ring_of_frost_freeze_SpellScript(); + } + + class spell_mage_ring_of_frost_freeze_AuraScript : AuraScript + { + public override bool Validate(SpellInfo spellInfo) + { + return ValidateSpellInfo(SpellIds.RingOfFrostDummy); + } + + void OnRemove(AuraEffect aurEff, AuraEffectHandleModes mode) + { + if (GetTargetApplication().GetRemoveMode() != AuraRemoveMode.Expire) + if (GetCaster()) + GetCaster().CastSpell(GetTarget(), SpellIds.RingOfFrostDummy, true); + } + + public override void Register() + { + AfterEffectRemove.Add(new EffectApplyHandler(OnRemove, 0, AuraType.ModStun, AuraEffectHandleModes.Real)); + } + } + + public override AuraScript GetAuraScript() + { + return new spell_mage_ring_of_frost_freeze_AuraScript(); + } + } + + // 80353 - Time Warp + [Script] + class spell_mage_time_warp : SpellScriptLoader + { + public spell_mage_time_warp() : base("spell_mage_time_warp") { } + + class spell_mage_time_warp_SpellScript : SpellScript + { + public override bool Validate(SpellInfo spellInfo) + { + return ValidateSpellInfo(SpellIds.TemporalDisplacement, SpellIds.HunterInsanity, SpellIds.ShamanExhaustion, SpellIds.ShamanSated); + } + + void RemoveInvalidTargets(List targets) + { + targets.RemoveAll(new UnitAuraCheck(true, SpellIds.TemporalDisplacement)); + targets.RemoveAll(new UnitAuraCheck(true, SpellIds.HunterInsanity)); + targets.RemoveAll(new UnitAuraCheck(true, SpellIds.ShamanExhaustion)); + targets.RemoveAll(new UnitAuraCheck(true, SpellIds.ShamanSated)); + } + + void ApplyDebuff() + { + Unit target = GetHitUnit(); + if (target) + target.CastSpell(target, SpellIds.TemporalDisplacement, true); + } + + public override void Register() + { + OnObjectAreaTargetSelect.Add(new ObjectAreaTargetSelectHandler(RemoveInvalidTargets, SpellConst.EffectAll, Targets.UnitCasterAreaRaid)); + AfterHit.Add(new HitHandler(ApplyDebuff)); + } + } + + public override SpellScript GetSpellScript() + { + return new spell_mage_time_warp_SpellScript(); + } + } + + [Script] //228597 - Frostbolt 84721 - Frozen Orb 190357 - Blizzard + class spell_mage_trigger_chilled : SpellScriptLoader + { + public spell_mage_trigger_chilled() : base("spell_mage_trigger_chilled") { } + + class spell_mage_trigger_chilled_SpellScript : SpellScript + { + public override bool Validate(SpellInfo spell) + { + return ValidateSpellInfo(SpellIds.Chilled); + } + + void HandleChilled() + { + Unit target = GetHitUnit(); + if (target) + GetCaster().CastSpell(target, SpellIds.Chilled, true); + } + + public override void Register() + { + OnHit.Add(new HitHandler(HandleChilled)); + } + } + + public override SpellScript GetSpellScript() + { + return new spell_mage_trigger_chilled_SpellScript(); + } + } + + [Script] // 33395 Water Elemental's Freeze + class spell_mage_water_elemental_freeze : SpellScriptLoader + { + public spell_mage_water_elemental_freeze() : base("spell_mage_water_elemental_freeze") { } + + class spell_mage_water_elemental_freeze_SpellScript : SpellScript + { + public override bool Validate(SpellInfo spellInfo) + { + return ValidateSpellInfo(SpellIds.FingersOfFrost); + } + + void HandleImprovedFreeze() + { + Unit owner = GetCaster().GetOwner(); + if (!owner) + return; + + owner.CastSpell(owner, SpellIds.FingersOfFrost, true); + } + + public override void Register() + { + AfterHit.Add(new HitHandler(HandleImprovedFreeze)); + } + } + + public override SpellScript GetSpellScript() + { + return new spell_mage_water_elemental_freeze_SpellScript(); + } + } +} diff --git a/Scripts/Spells/Monk.cs b/Scripts/Spells/Monk.cs new file mode 100644 index 000000000..212f6b17b --- /dev/null +++ b/Scripts/Spells/Monk.cs @@ -0,0 +1,169 @@ +/* + * Copyright (C) 2012-2017 CypherCore + * + * 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, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +using Framework.Constants; +using Game.Entities; +using Game.Scripting; +using Game.Spells; +using System; + +namespace Scripts.Spells.Monk +{ + struct SpellIds + { + public const uint CracklingJadeLightningChannel = 117952; + public const uint CracklingJadeLightningChiProc = 123333; + public const uint CracklingJadeLightningKnockback = 117962; + public const uint CracklingJadeLightningKnockbackCd = 117953; + public const uint ProvokeSingleTarget = 116189; + public const uint ProvokeAoe = 118635; + public const uint SoothingMist = 115175; + public const uint StanceOfTheSpiritedCrane = 154436; + public const uint SurgingMistHeal = 116995; + } + + [Script] // 117952 - Crackling Jade Lightning + class spell_monk_crackling_jade_lightning : SpellScriptLoader + { + public spell_monk_crackling_jade_lightning() : base("spell_monk_crackling_jade_lightning") { } + + class spell_monk_crackling_jade_lightning_AuraScript : AuraScript + { + public override bool Validate(SpellInfo spellInfo) + { + return ValidateSpellInfo(SpellIds.CracklingJadeLightningChiProc); + } + + void OnTick(AuraEffect aurEff) + { + Unit caster = GetCaster(); + if (caster) + if (caster.HasAura(SpellIds.StanceOfTheSpiritedCrane)) + caster.CastSpell(caster, SpellIds.CracklingJadeLightningChiProc, TriggerCastFlags.FullMask); + } + + public override void Register() + { + OnEffectPeriodic.Add(new EffectPeriodicHandler(OnTick, 0, Framework.Constants.AuraType.PeriodicDamage)); + } + } + + public override AuraScript GetAuraScript() + { + return new spell_monk_crackling_jade_lightning_AuraScript(); + } + } + + [Script] // 117959 - Crackling Jade Lightning + class spell_monk_crackling_jade_lightning_knockback_proc_aura : SpellScriptLoader + { + public spell_monk_crackling_jade_lightning_knockback_proc_aura() : base("spell_monk_crackling_jade_lightning_knockback_proc_aura") { } + + class spell_monk_crackling_jade_lightning_aura_AuraScript : AuraScript + { + public override bool Validate(SpellInfo spellInfo) + { + return ValidateSpellInfo(SpellIds.CracklingJadeLightningKnockback, SpellIds.CracklingJadeLightningKnockbackCd); + } + + bool CheckProc(ProcEventInfo eventInfo) + { + if (GetTarget().HasAura(SpellIds.CracklingJadeLightningKnockbackCd)) + return false; + + if (eventInfo.GetActor().HasAura(SpellIds.CracklingJadeLightningChannel, GetTarget().GetGUID())) + return false; + + Spell currentChanneledSpell = GetTarget().GetCurrentSpell(CurrentSpellTypes.Channeled); + if (!currentChanneledSpell || currentChanneledSpell.GetSpellInfo().Id != SpellIds.CracklingJadeLightningChannel) + return false; + + return true; + } + + void HandleProc(AuraEffect aurEff, ProcEventInfo eventInfo) + { + GetTarget().CastSpell(eventInfo.GetActor(), SpellIds.CracklingJadeLightningKnockback, TriggerCastFlags.FullMask); + GetTarget().CastSpell(GetTarget(), SpellIds.CracklingJadeLightningKnockbackCd, TriggerCastFlags.FullMask); + } + + public override void Register() + { + DoCheckProc.Add(new CheckProcHandler(CheckProc)); + OnEffectProc.Add(new EffectProcHandler(HandleProc, 0, Framework.Constants.AuraType.Dummy)); + } + } + + public override AuraScript GetAuraScript() + { + return new spell_monk_crackling_jade_lightning_aura_AuraScript(); + } + } + + [Script] // 115546 - Provoke + class spell_monk_provoke : SpellScriptLoader + { + public spell_monk_provoke() : base("spell_monk_provoke") { } + + class spell_monk_provoke_SpellScript : SpellScript + { + const uint BlackOxStatusEntry = 61146; + + public override bool Validate(SpellInfo spellInfo) + { + if (!spellInfo.GetExplicitTargetMask().HasAnyFlag(SpellCastTargetFlags.UnitMask)) // ensure GetExplTargetUnit() will return something meaningful during CheckCast + return false; + return ValidateSpellInfo(SpellIds.ProvokeSingleTarget, SpellIds.ProvokeAoe); + } + + SpellCastResult CheckExplicitTarget() + { + if (GetExplTargetUnit().GetEntry() != BlackOxStatusEntry) + { + SpellInfo singleTarget = Global.SpellMgr.GetSpellInfo(SpellIds.ProvokeSingleTarget); + SpellCastResult singleTargetExplicitResult = singleTarget.CheckExplicitTarget(GetCaster(), GetExplTargetUnit()); + if (singleTargetExplicitResult != SpellCastResult.SpellCastOk) + return singleTargetExplicitResult; + } + else if (GetExplTargetUnit().GetOwnerGUID() != GetCaster().GetGUID()) + return SpellCastResult.BadTargets; + + return SpellCastResult.SpellCastOk; + } + + void HandleDummy(uint effIndex) + { + PreventHitDefaultEffect(effIndex); + if (GetHitUnit().GetEntry() != BlackOxStatusEntry) + GetCaster().CastSpell(GetHitUnit(), SpellIds.ProvokeSingleTarget, true); + else + GetCaster().CastSpell(GetHitUnit(), SpellIds.ProvokeAoe, true); + } + + public override void Register() + { + OnCheckCast.Add(new CheckCastHandler(CheckExplicitTarget)); + OnEffectHitTarget.Add(new EffectHandler(HandleDummy, 0, SpellEffectName.Dummy)); + } + } + + public override SpellScript GetSpellScript() + { + return new spell_monk_provoke_SpellScript(); + } + } +} diff --git a/Scripts/Spells/Paladin.cs b/Scripts/Spells/Paladin.cs new file mode 100644 index 000000000..6e274c83b --- /dev/null +++ b/Scripts/Spells/Paladin.cs @@ -0,0 +1,1073 @@ +/* + * Copyright (C) 2012-2017 CypherCore + * + * 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, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +using Framework.Constants; +using Game.Entities; +using Game.Scripting; +using Game.Spells; +using System; +using System.Collections.Generic; +using System.Linq; + +namespace Scripts.Spells.Paladin +{ + struct SpellIds + { + public const uint AvengersShield = 31935; + public const uint AuraMasteryImmune = 64364; + public const uint BeaconOfLight = 53563; + public const uint BeaconOfLightHeal = 53652; + public const uint BlessingOfLowerCityDruid = 37878; + public const uint BlessingOfLowerCityPaladin = 37879; + public const uint BlessingOfLowerCityPriest = 37880; + public const uint BlessingOfLowerCityShaman = 37881; + public const uint ConcentractionAura = 19746; + public const uint DivinePurposeProc = 90174; + public const uint DivineSteedHuman = 221883; + public const uint DivineSteedDraenei = 221887; + public const uint DivineSteedBloodelf = 221886; + public const uint DivineSteedTauren = 221885; + public const uint DivineStormDamage = 224239; + public const uint EnduringLight = 40471; + public const uint EnduringJudgement = 40472; + public const uint EyeForAnEyeRank1 = 9799; + public const uint EyeForAnEyeDamage = 25997; + public const uint Forbearance = 25771; + public const uint HandOfSacrifice = 6940; + public const uint HolyMending = 64891; + public const uint HolyPowerArmor = 28790; + public const uint HolyPowerAttackPower = 28791; + public const uint HolyPowerSpellPower = 28793; + public const uint HolyPowerMp5 = 28795; + public const uint HolyShockR1 = 20473; + public const uint HolyShockR1Damage = 25912; + public const uint HolyShockR1Healing = 25914; + public const uint ImmuneShieldMarker = 61988; + public const uint ItemHealingTrance = 37706; + public const uint JudgementDamage = 54158; + public const uint RighteousDefenseTaunt = 31790; + public const uint SealOfCommand = 105361; + public const uint SealOfRighteousness = 25742; + } + + // 31821 - Aura Mastery + [Script] + class spell_pal_aura_mastery : SpellScriptLoader + { + public spell_pal_aura_mastery() : base("spell_pal_aura_mastery") { } + + class spell_pal_aura_mastery_AuraScript : AuraScript + { + public override bool Validate(SpellInfo spellInfo) + { + return ValidateSpellInfo(SpellIds.AuraMasteryImmune); + } + + void HandleEffectApply(AuraEffect aurEff, AuraEffectHandleModes mode) + { + GetTarget().CastSpell(GetTarget(), SpellIds.AuraMasteryImmune, true); + } + + void HandleEffectRemove(AuraEffect aurEff, AuraEffectHandleModes mode) + { + GetTarget().RemoveOwnedAura(SpellIds.AuraMasteryImmune, GetCasterGUID()); + } + + public override void Register() + { + AfterEffectApply.Add(new EffectApplyHandler(HandleEffectApply, 0, AuraType.AddPctModifier, AuraEffectHandleModes.Real)); + AfterEffectRemove.Add(new EffectApplyHandler(HandleEffectRemove, 0, AuraType.AddPctModifier, AuraEffectHandleModes.Real)); + } + } + + public override AuraScript GetAuraScript() + { + return new spell_pal_aura_mastery_AuraScript(); + } + } + + // 64364 - Aura Mastery Immune + [Script] + class spell_pal_aura_mastery_immune : SpellScriptLoader + { + public spell_pal_aura_mastery_immune() + : base("spell_pal_aura_mastery_immune") { } + + class spell_pal_aura_mastery_immune_AuraScript : AuraScript + { + public override bool Validate(SpellInfo spellInfo) + { + return ValidateSpellInfo(SpellIds.ConcentractionAura); + } + + bool CheckAreaTarget(Unit target) + { + return target.HasAura(SpellIds.ConcentractionAura, GetCasterGUID()); + } + + public override void Register() + { + DoCheckAreaTarget.Add(new CheckAreaTargetHandler(CheckAreaTarget)); + } + } + + public override AuraScript GetAuraScript() + { + return new spell_pal_aura_mastery_immune_AuraScript(); + } + } + + // 37877 - Blessing of Faith + [Script] + class spell_pal_blessing_of_faith : SpellScriptLoader + { + public spell_pal_blessing_of_faith() + : base("spell_pal_blessing_of_faith") { } + + class spell_pal_blessing_of_faith_SpellScript : SpellScript + { + public override bool Validate(SpellInfo spellInfo) + { + return ValidateSpellInfo(SpellIds.BlessingOfLowerCityDruid, SpellIds.BlessingOfLowerCityPaladin, SpellIds.BlessingOfLowerCityPriest, SpellIds.BlessingOfLowerCityShaman); + } + + void HandleDummy(uint effIndex) + { + Unit unitTarget = GetHitUnit(); + if (unitTarget) + { + uint spell_id = 0; + switch (unitTarget.GetClass()) + { + case Class.Druid: + spell_id = SpellIds.BlessingOfLowerCityDruid; + break; + case Class.Paladin: + spell_id = SpellIds.BlessingOfLowerCityPaladin; + break; + case Class.Priest: + spell_id = SpellIds.BlessingOfLowerCityPriest; + break; + case Class.Shaman: + spell_id = SpellIds.BlessingOfLowerCityShaman; + break; + default: + return; // ignore for non-healing classes + } + Unit caster = GetCaster(); + caster.CastSpell(caster, spell_id, true); + } + } + + public override void Register() + { + OnEffectHitTarget.Add(new EffectHandler(HandleDummy, 0, SpellEffectName.Dummy)); + } + } + + public override SpellScript GetSpellScript() + { + return new spell_pal_blessing_of_faith_SpellScript(); + } + } + + [Script] // 190784 - Divine Steed + class spell_pal_divine_steed : SpellScriptLoader + { + public spell_pal_divine_steed() : base("spell_pal_divine_steed") { } + + class spell_pal_divine_steed_SpellScript : SpellScript + { + public override bool Validate(SpellInfo spellInfo) + { + return ValidateSpellInfo(SpellIds.DivineSteedHuman, SpellIds.DivineSteedDraenei, SpellIds.DivineSteedBloodelf, SpellIds.DivineSteedTauren); + } + + void HandleOnCast() + { + Unit caster = GetCaster(); + + uint spellId = SpellIds.DivineSteedHuman; + switch (caster.GetRace()) + { + case Race.Draenei: + spellId = SpellIds.DivineSteedDraenei; + break; + case Race.BloodElf: + spellId = SpellIds.DivineSteedBloodelf; + break; + case Race.Tauren: + spellId = SpellIds.DivineSteedTauren; + break; + default: + break; + } + + caster.CastSpell(caster, spellId, true); + } + + public override void Register() + { + OnCast.Add(new CastHandler(HandleOnCast)); + } + } + + public override SpellScript GetSpellScript() + { + return new spell_pal_divine_steed_SpellScript(); + } + } + + // 224239 - Divine Storm + [Script] + class spell_pal_divine_storm : SpellScriptLoader + { + public spell_pal_divine_storm() : base("spell_pal_divine_storm") { } + + class spell_pal_divine_storm_SpellScript : SpellScript + { + public override bool Validate(SpellInfo spellInfo) + { + return ValidateSpellInfo(SpellIds.DivineStormDamage); + } + + void HandleOnCast() + { + Unit caster = GetCaster(); + caster.SendPlaySpellVisualKit(73892, 0, 0); + } + + void HandleDummy(uint effIndex) + { + Unit caster = GetCaster(); + Unit target = GetHitUnit(); + if (!target) + return; + + caster.CastSpell(target, SpellIds.DivineStormDamage, true); + } + + public override void Register() + { + OnCast.Add(new CastHandler(HandleOnCast)); + OnEffectHitTarget.Add(new EffectHandler(HandleDummy, 0, SpellEffectName.Dummy)); + } + } + + public override SpellScript GetSpellScript() + { + return new spell_pal_divine_storm_SpellScript(); + } + } + + // 33695 - Exorcism and Holy Wrath Damage + [Script] + class spell_pal_exorcism_and_holy_wrath_damage : SpellScriptLoader + { + public spell_pal_exorcism_and_holy_wrath_damage() + : base("spell_pal_exorcism_and_holy_wrath_damage") { } + + class spell_pal_exorcism_and_holy_wrath_damage_AuraScript : AuraScript + { + void HandleEffectCalcSpellMod(AuraEffect aurEff, ref SpellModifier spellMod) + { + if (spellMod == null) + { + spellMod = new SpellModifier(aurEff.GetBase()); + spellMod.op = SpellModOp.Damage; + spellMod.type = SpellModType.Flat; + spellMod.spellId = GetId(); + spellMod.mask[1] = 0x200002; + } + + spellMod.value = aurEff.GetAmount(); + } + + public override void Register() + { + DoEffectCalcSpellMod.Add(new EffectCalcSpellModHandler(HandleEffectCalcSpellMod, 0, AuraType.Dummy)); + } + } + + public override AuraScript GetAuraScript() + { + return new spell_pal_exorcism_and_holy_wrath_damage_AuraScript(); + } + } + + // -9799 - Eye for an Eye + [Script] + class spell_pal_eye_for_an_eye : SpellScriptLoader + { + public spell_pal_eye_for_an_eye() + : base("spell_pal_eye_for_an_eye") { } + + class spell_pal_eye_for_an_eye_AuraScript : AuraScript + { + public override bool Validate(SpellInfo spellInfo) + { + return ValidateSpellInfo(SpellIds.EyeForAnEyeDamage); + } + + void HandleEffectProc(AuraEffect aurEff, ProcEventInfo eventInfo) + { + PreventDefaultAction(); + int damage = (int)MathFunctions.CalculatePct(eventInfo.GetDamageInfo().GetDamage(), aurEff.GetAmount()); + GetTarget().CastCustomSpell(SpellIds.EyeForAnEyeDamage, SpellValueMod.BasePoint0, damage, eventInfo.GetProcTarget(), true, null, aurEff); + } + + public override void Register() + { + OnEffectProc.Add(new EffectProcHandler(HandleEffectProc, 0, m_scriptSpellId == SpellIds.EyeForAnEyeRank1 ? AuraType.Dummy : AuraType.ProcTriggerSpell)); + } + } + + public override AuraScript GetAuraScript() + { + return new spell_pal_eye_for_an_eye_AuraScript(); + } + } + + // -75806 - Grand Crusader + [Script] + class spell_pal_grand_crusader : SpellScriptLoader + { + public spell_pal_grand_crusader() + : base("spell_pal_grand_crusader") { } + + class spell_pal_grand_crusader_AuraScript : AuraScript + { + public override bool Validate(SpellInfo spellInfo) + { + return ValidateSpellInfo(SpellIds.AvengersShield); + } + + bool CheckProc(ProcEventInfo eventInfo) + { + return GetTarget().IsTypeId(TypeId.Player); + } + + void HandleEffectProc(AuraEffect aurEff, ProcEventInfo eventInfo) + { + GetTarget().GetSpellHistory().ResetCooldown(SpellIds.AvengersShield, true); + } + + public override void Register() + { + DoCheckProc.Add(new CheckProcHandler(CheckProc)); + OnEffectProc.Add(new EffectProcHandler(HandleEffectProc, 0, AuraType.ProcTriggerSpell)); + } + } + + public override AuraScript GetAuraScript() + { + return new spell_pal_grand_crusader_AuraScript(); + } + } + + // 54968 - Glyph of Holy Light + [Script] + class spell_pal_glyph_of_holy_light : SpellScriptLoader + { + public spell_pal_glyph_of_holy_light() + : base("spell_pal_glyph_of_holy_light") { } + + class spell_pal_glyph_of_holy_light_SpellScript : SpellScript + { + void FilterTargets(List targets) + { + uint maxTargets = GetSpellInfo().MaxAffectedTargets; + + if (targets.Count > maxTargets) + { + targets.Sort(new HealthPctOrderPred()); + targets.Resize(maxTargets); + } + } + + public override void Register() + { + OnObjectAreaTargetSelect.Add(new ObjectAreaTargetSelectHandler(FilterTargets, 0, Targets.UnitDestAreaAlly)); + } + } + + public override SpellScript GetSpellScript() + { + return new spell_pal_glyph_of_holy_light_SpellScript(); + } + } + + // 6940 - Hand of Sacrifice + [Script] + class spell_pal_hand_of_sacrifice : SpellScriptLoader + { + public spell_pal_hand_of_sacrifice() + : base("spell_pal_hand_of_sacrifice") { } + + class spell_pal_hand_of_sacrifice_AuraScript : AuraScript + { + int remainingAmount; + + public override bool Load() + { + Unit caster = GetCaster(); + if (caster) + { + remainingAmount = (int)caster.GetMaxHealth(); + return true; + } + return false; + } + + void Split(AuraEffect aurEff, DamageInfo dmgInfo, uint splitAmount) + { + remainingAmount -= (int)splitAmount; + + if (remainingAmount <= 0) + { + GetTarget().RemoveAura(SpellIds.HandOfSacrifice); + } + } + + public override void Register() + { + OnEffectSplit.Add(new EffectSplitHandler(Split, 0)); + } + } + + public override AuraScript GetAuraScript() + { + return new spell_pal_hand_of_sacrifice_AuraScript(); + } + } + + // 20473 - Holy Shock + [Script] + class spell_pal_holy_shock : SpellScriptLoader + { + public spell_pal_holy_shock() + : base("spell_pal_holy_shock") { } + + class spell_pal_holy_shock_SpellScript : SpellScript + { + public override bool Validate(SpellInfo spellInfo) + { + SpellInfo firstRankSpellInfo = Global.SpellMgr.GetSpellInfo(SpellIds.HolyShockR1); + if (firstRankSpellInfo == null) + return false; + + // can't use other spell than holy shock due to spell_ranks dependency + if (!spellInfo.IsRankOf(firstRankSpellInfo)) + return false; + + byte rank = spellInfo.GetRank(); + if (Global.SpellMgr.GetSpellWithRank(SpellIds.HolyShockR1Damage, rank, true) == 0 || Global.SpellMgr.GetSpellWithRank(SpellIds.HolyShockR1Healing, rank, true) == 0) + return false; + + return true; + } + + void HandleDummy(uint effIndex) + { + Unit caster = GetCaster(); + Unit unitTarget = GetHitUnit(); + if (unitTarget) + { + byte rank = GetSpellInfo().GetRank(); + if (caster.IsFriendlyTo(unitTarget)) + caster.CastSpell(unitTarget, Global.SpellMgr.GetSpellWithRank(SpellIds.HolyShockR1Healing, rank), true); + else + caster.CastSpell(unitTarget, Global.SpellMgr.GetSpellWithRank(SpellIds.HolyShockR1Damage, rank), true); + } + } + + SpellCastResult CheckCast() + { + Unit caster = GetCaster(); + Unit target = GetExplTargetUnit(); + if (target) + { + if (!caster.IsFriendlyTo(target)) + { + if (!caster.IsValidAttackTarget(target)) + return SpellCastResult.BadTargets; + + if (!caster.isInFront(target)) + return SpellCastResult.NotInfront; + } + } + else + return SpellCastResult.BadTargets; + return SpellCastResult.SpellCastOk; + } + + public override void Register() + { + OnCheckCast.Add(new CheckCastHandler(CheckCast)); + OnEffectHitTarget.Add(new EffectHandler(HandleDummy, 0, SpellEffectName.Dummy)); + } + } + + public override SpellScript GetSpellScript() + { + return new spell_pal_holy_shock_SpellScript(); + } + } + + // 37705 - Healing Discount + [Script] + class spell_pal_item_healing_discount : SpellScriptLoader + { + public spell_pal_item_healing_discount() + : base("spell_pal_item_healing_discount") { } + + class spell_pal_item_healing_discount_AuraScript : AuraScript + { + public override bool Validate(SpellInfo spellInfo) + { + return ValidateSpellInfo(SpellIds.ItemHealingTrance); + } + + void HandleProc(AuraEffect aurEff, ProcEventInfo eventInfo) + { + PreventDefaultAction(); + GetTarget().CastSpell(GetTarget(), SpellIds.ItemHealingTrance, true, null, aurEff); + } + + public override void Register() + { + OnEffectProc.Add(new EffectProcHandler(HandleProc, 0, AuraType.ProcTriggerSpell)); + } + } + + public override AuraScript GetAuraScript() + { + return new spell_pal_item_healing_discount_AuraScript(); + } + } + + [Script] // 40470 - Paladin Tier 6 Trinket + class spell_pal_item_t6_trinket : SpellScriptLoader + { + public spell_pal_item_t6_trinket() : base("spell_pal_item_t6_trinket") { } + + class spell_pal_item_t6_trinket_AuraScript : AuraScript + { + public override bool Validate(SpellInfo spellInfo) + { + return ValidateSpellInfo(SpellIds.EnduringLight, SpellIds.EnduringJudgement); + } + + void HandleProc(AuraEffect aurEff, ProcEventInfo eventInfo) + { + PreventDefaultAction(); + SpellInfo spellInfo = eventInfo.GetSpellInfo(); + if (spellInfo == null) + return; + + uint spellId; + int chance; + + // Holy Light & Flash of Light + if (spellInfo.SpellFamilyFlags[0].HasAnyFlag(0xC0000000)) + { + spellId = SpellIds.EnduringLight; + chance = 15; + } + // Judgements + else if (spellInfo.SpellFamilyFlags[0].HasAnyFlag(0x00800000u)) + { + spellId = SpellIds.EnduringJudgement; + chance = 50; + } + else + return; + + if (RandomHelper.randChance(chance)) + eventInfo.GetActor().CastSpell(eventInfo.GetProcTarget(), spellId, true); + } + + public override void Register() + { + OnEffectProc.Add(new EffectProcHandler(HandleProc, 0, AuraType.Dummy)); + } + } + + public override AuraScript GetAuraScript() + { + return new spell_pal_item_t6_trinket_AuraScript(); + } + } + + // 20271 - Judgement + // Updated 4.3.4 + [Script] + class spell_pal_judgement : SpellScriptLoader + { + public spell_pal_judgement() : base("spell_pal_judgement") { } + + class spell_pal_judgement_SpellScript : SpellScript + { + public override bool Validate(SpellInfo spellInfo) + { + return ValidateSpellInfo(SpellIds.JudgementDamage); + } + + void HandleScriptEffect(uint effIndex) + { + uint spellId = SpellIds.JudgementDamage; + + // some seals have SPELL_AURA_DUMMY in EFFECT_2 + var auras = GetCaster().GetAuraEffectsByType(AuraType.Dummy); + foreach (var eff in auras) + { + if (eff.GetSpellInfo().GetSpellSpecific() == SpellSpecificType.Seal && eff.GetEffIndex() == 2) + { + if (Global.SpellMgr.HasSpellInfo((uint)eff.GetAmount())) + { + spellId = (uint)eff.GetAmount(); + break; + } + } + } + + GetCaster().CastSpell(GetHitUnit(), spellId, true); + } + + public override void Register() + { + OnEffectHitTarget.Add(new EffectHandler(HandleScriptEffect, 0, SpellEffectName.Dummy)); + } + } + + public override SpellScript GetSpellScript() + { + return new spell_pal_judgement_SpellScript(); + } + } + + // 633 - Lay on Hands + [Script] + class spell_pal_lay_on_hands : SpellScriptLoader + { + public spell_pal_lay_on_hands() : base("spell_pal_lay_on_hands") { } + + class spell_pal_lay_on_hands_SpellScript : SpellScript + { + public override bool Validate(SpellInfo spell) + { + return ValidateSpellInfo(SpellIds.Forbearance, SpellIds.ImmuneShieldMarker); + } + + SpellCastResult CheckCast() + { + Unit caster = GetCaster(); + Unit target = GetExplTargetUnit(); + if (target) + { + if (caster == target) + { + if (target.HasAura(SpellIds.Forbearance) || target.HasAura(SpellIds.ImmuneShieldMarker)) + return SpellCastResult.TargetAurastate; + } + } + + return SpellCastResult.SpellCastOk; + } + + void HandleScript() + { + Unit caster = GetCaster(); + if (caster == GetHitUnit()) + { + caster.CastSpell(caster, SpellIds.Forbearance, true); + caster.CastSpell(caster, SpellIds.ImmuneShieldMarker, true); + } + } + + public override void Register() + { + OnCheckCast.Add(new CheckCastHandler(CheckCast)); + AfterHit.Add(new HitHandler(HandleScript)); + } + } + + public override SpellScript GetSpellScript() + { + return new spell_pal_lay_on_hands_SpellScript(); + } + } + + // 53651 - Beacon of Light + [Script] + class spell_pal_light_s_beacon : SpellScriptLoader + { + public spell_pal_light_s_beacon() : base("spell_pal_light_s_beacon") { } + + class spell_pal_light_s_beacon_AuraScript : AuraScript + { + public override bool Validate(SpellInfo spellInfo) + { + return ValidateSpellInfo(SpellIds.BeaconOfLight, SpellIds.BeaconOfLightHeal); + } + + bool CheckProc(ProcEventInfo eventInfo) + { + if (!eventInfo.GetActionTarget()) + return false; + if (eventInfo.GetActionTarget().HasAura(SpellIds.BeaconOfLight, eventInfo.GetActor().GetGUID())) + return false; + return true; + } + + void HandleProc(AuraEffect aurEff, ProcEventInfo eventInfo) + { + PreventDefaultAction(); + + HealInfo healInfo = eventInfo.GetHealInfo(); + if (healInfo == null || healInfo.GetHeal() == 0) + return; + + uint heal = MathFunctions.CalculatePct(healInfo.GetHeal(), aurEff.GetAmount()); + + var auras = GetCaster().GetSingleCastAuras(); + foreach (var eff in auras) + { + if (eff.GetId() == SpellIds.BeaconOfLight) + { + List applications = eff.GetApplicationList(); + if (!applications.Empty()) + eventInfo.GetActor().CastCustomSpell(SpellIds.BeaconOfLightHeal, SpellValueMod.BasePoint0, (int)heal, applications.First().GetTarget(), true); + return; + } + } + } + + public override void Register() + { + DoCheckProc.Add(new CheckProcHandler(CheckProc)); + OnEffectProc.Add(new EffectProcHandler(HandleProc, 0, AuraType.Dummy)); + } + } + + public override AuraScript GetAuraScript() + { + return new spell_pal_light_s_beacon_AuraScript(); + } + } + + // 31789 - Righteous Defense + [Script] + class spell_pal_righteous_defense : SpellScriptLoader + { + public spell_pal_righteous_defense() : base("spell_pal_righteous_defense") { } + + class spell_pal_righteous_defense_SpellScript : SpellScript + { + public override bool Validate(SpellInfo spellInfo) + { + return ValidateSpellInfo(SpellIds.RighteousDefenseTaunt); + } + + SpellCastResult CheckCast() + { + Unit caster = GetCaster(); + if (!caster.IsTypeId(TypeId.Player)) + return SpellCastResult.DontReport; + + Unit target = GetExplTargetUnit(); + if (target) + { + if (!target.IsFriendlyTo(caster) || target.getAttackers().Empty()) + return SpellCastResult.BadTargets; + } + else + return SpellCastResult.BadTargets; + + return SpellCastResult.SpellCastOk; + } + + void HandleTriggerSpellLaunch(uint effIndex) + { + PreventHitDefaultEffect(effIndex); + } + + void HandleTriggerSpellHit(uint effIndex) + { + PreventHitDefaultEffect(effIndex); + Unit target = GetHitUnit(); + if (target) + GetCaster().CastSpell(target, SpellIds.RighteousDefenseTaunt, true); + } + + public override void Register() + { + OnCheckCast.Add(new CheckCastHandler(CheckCast)); + //! WORKAROUND + //! target select will be executed in hitphase of effect 0 + //! so we must handle trigger spell also in hit phase (default execution in launch phase) + //! see issue #3718 + OnEffectLaunchTarget.Add(new EffectHandler(HandleTriggerSpellLaunch, 1, SpellEffectName.TriggerSpell)); + OnEffectHitTarget.Add(new EffectHandler(HandleTriggerSpellHit, 1, SpellEffectName.TriggerSpell)); + } + } + + public override SpellScript GetSpellScript() + { + return new spell_pal_righteous_defense_SpellScript(); + } + } + + // 85285 - Sacred Shield + [Script] + class spell_pal_sacred_shield : SpellScriptLoader + { + public spell_pal_sacred_shield() : base("spell_pal_sacred_shield") { } + + class spell_pal_sacred_shield_SpellScript : SpellScript + { + SpellCastResult CheckCast() + { + Unit caster = GetCaster(); + if (!caster.IsTypeId(TypeId.Player)) + return SpellCastResult.DontReport; + + if (!caster.HealthBelowPct(30)) + return SpellCastResult.CantDoThatRightNow; + + return SpellCastResult.SpellCastOk; + } + + public override void Register() + { + OnCheckCast.Add(new CheckCastHandler(CheckCast)); + } + }; + + public override SpellScript GetSpellScript() + { + return new spell_pal_sacred_shield_SpellScript(); + } + } + + // 85256 - Templar's Verdict + // Updated 4.3.4 + [Script] + class spell_pal_templar_s_verdict : SpellScriptLoader + { + public spell_pal_templar_s_verdict() : base("spell_pal_templar_s_verdict") { } + + class spell_pal_templar_s_verdict_SpellScript : SpellScript + { + public override bool Validate(SpellInfo spellEntry) + { + return ValidateSpellInfo(SpellIds.DivinePurposeProc); + } + + public override bool Load() + { + if (!GetCaster().IsTypeId(TypeId.Player)) + return false; + + if (GetCaster().ToPlayer().GetClass() != Class.Paladin) + return false; + + return true; + } + + void ChangeDamage(uint effIndex) + { + Unit caster = GetCaster(); + float damage = GetHitDamage(); + + if (caster.HasAura(SpellIds.DivinePurposeProc)) + damage *= 7.5f; // 7.5*30% = 225% + else + { + switch (caster.GetPower(PowerType.HolyPower)) + { + case 0: // 1 Holy Power + //damage = damage; + break; + case 1: // 2 Holy Power + damage *= 3; // 3*30 = 90% + break; + case 2: // 3 Holy Power + damage *= 7.5f; // 7.5*30% = 225% + break; + } + } + + SetHitDamage((int)damage); + } + + public override void Register() + { + OnEffectHitTarget.Add(new EffectHandler(ChangeDamage, 0, SpellEffectName.WeaponPercentDamage)); + } + } + + public override SpellScript GetSpellScript() + { + return new spell_pal_templar_s_verdict_SpellScript(); + } + } + + // 20154, 21084 - Seal of Righteousness - melee proc dummy (addition ${$MWS*(0.022*$AP+0.044*$SPH)} damage) + [Script] + class spell_pal_seal_of_righteousness : SpellScriptLoader + { + public spell_pal_seal_of_righteousness() : base("spell_pal_seal_of_righteousness") { } + + class spell_pal_seal_of_righteousness_AuraScript : AuraScript + { + public override bool Validate(SpellInfo spellInfo) + { + return ValidateSpellInfo(SpellIds.SealOfCommand); + } + + bool CheckProc(ProcEventInfo eventInfo) + { + return eventInfo.GetProcTarget(); + } + + void HandleProc(AuraEffect aurEff, ProcEventInfo eventInfo) + { + PreventDefaultAction(); + + float ap = GetTarget().GetTotalAttackPowerValue(WeaponAttackType.BaseAttack); + int holy = GetTarget().SpellBaseDamageBonusDone(SpellSchoolMask.Holy); + holy += eventInfo.GetProcTarget().SpellBaseDamageBonusTaken(SpellSchoolMask.Holy); + int bp = (int)((ap * 0.022f + 0.044f * holy) * GetTarget().GetBaseAttackTime(WeaponAttackType.BaseAttack) / 1000); + GetTarget().CastCustomSpell(SpellIds.SealOfCommand, SpellValueMod.BasePoint0, bp, eventInfo.GetProcTarget(), true, null, aurEff); + } + + public override void Register() + { + DoCheckProc.Add(new CheckProcHandler(CheckProc)); + OnEffectProc.Add(new EffectProcHandler(HandleProc, 0, AuraType.Dummy)); + } + } + + public override AuraScript GetAuraScript() + { + return new spell_pal_seal_of_righteousness_AuraScript(); + } + } + + [Script] // 28789 - Holy Power + class spell_pal_t3_6p_bonus : SpellScriptLoader + { + public spell_pal_t3_6p_bonus() : base("spell_pal_t3_6p_bonus") { } + + class spell_pal_t3_6p_bonus_AuraScript : AuraScript + { + public override bool Validate(SpellInfo spellInfo) + { + return ValidateSpellInfo(SpellIds.HolyPowerArmor, SpellIds.HolyPowerAttackPower, SpellIds.HolyPowerSpellPower, SpellIds.HolyPowerMp5); + } + + void HandleProc(AuraEffect aurEff, ProcEventInfo eventInfo) + { + PreventDefaultAction(); + + uint spellId; + Unit caster = eventInfo.GetActor(); + Unit target = eventInfo.GetProcTarget(); + + switch (target.GetClass()) + { + case Class.Paladin: + case Class.Priest: + case Class.Shaman: + case Class.Druid: + spellId = SpellIds.HolyPowerMp5; + break; + case Class.Mage: + case Class.Warlock: + spellId = SpellIds.HolyPowerSpellPower; + break; + case Class.Hunter: + case Class.Rogue: + spellId = SpellIds.HolyPowerAttackPower; + break; + case Class.Warrior: + spellId = SpellIds.HolyPowerArmor; + break; + default: + return; + } + + caster.CastSpell(target, spellId, true); + } + + public override void Register() + { + OnEffectProc.Add(new EffectProcHandler(HandleProc, 0, AuraType.Dummy)); + } + } + + public override AuraScript GetAuraScript() + { + return new spell_pal_t3_6p_bonus_AuraScript(); + } + } + + [Script] // 64890 Item - Paladin T8 Holy 2P Bonus + class spell_pal_t8_2p_bonus : SpellScriptLoader + { + public spell_pal_t8_2p_bonus() : base("spell_pal_t8_2p_bonus") { } + + class spell_pal_t8_2p_bonus_AuraScript : AuraScript + { + public override bool Validate(SpellInfo spellInfo) + { + return ValidateSpellInfo(SpellIds.HolyMending); + } + + void HandleProc(AuraEffect aurEff, ProcEventInfo eventInfo) + { + PreventDefaultAction(); + + HealInfo healInfo = eventInfo.GetHealInfo(); + if (healInfo == null || healInfo.GetHeal() == 0) + return; + + Unit caster = eventInfo.GetActor(); + Unit target = eventInfo.GetProcTarget(); + + SpellInfo spellInfo = Global.SpellMgr.GetSpellInfo(SpellIds.HolyMending); + int amount = (int)MathFunctions.CalculatePct(healInfo.GetHeal(), aurEff.GetAmount()); + amount /= (int)spellInfo.GetMaxTicks(Difficulty.None); + // Add remaining ticks to damage done + amount += (int)target.GetRemainingPeriodicAmount(caster.GetGUID(), SpellIds.HolyMending, AuraType.PeriodicHeal); + + caster.CastCustomSpell(SpellIds.HolyMending, SpellValueMod.BasePoint0, amount, target, true); + } + + public override void Register() + { + OnEffectProc.Add(new EffectProcHandler(HandleProc, 0, AuraType.Dummy)); + } + } + + public override AuraScript GetAuraScript() + { + return new spell_pal_t8_2p_bonus_AuraScript(); + } + } +} diff --git a/Scripts/Spells/Priest.cs b/Scripts/Spells/Priest.cs new file mode 100644 index 000000000..8470a8644 --- /dev/null +++ b/Scripts/Spells/Priest.cs @@ -0,0 +1,1471 @@ +/* + * Copyright (C) 2012-2017 CypherCore + * + * 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, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +using Framework.Constants; +using Framework.Dynamic; +using Game.AI; +using Game.Entities; +using Game.Scripting; +using Game.Spells; +using System; +using System.Collections.Generic; +using System.Linq; + +namespace Scripts.Spells.Priest +{ + struct SpellIds + { + public const uint Absolution = 33167; + public const uint AngelicFeatherAreatrigger = 158624; + public const uint AngelicFeatherAura = 121557; + public const uint AngelicFeatherTrigger = 121536; + public const uint ArmorOfFaith = 28810; + public const uint Atonement = 81749; + public const uint AtonementHeal = 81751; + public const uint AtonementTriggered = 194384; + public const uint BlessedHealing = 70772; + public const uint BodyAndSoul = 64129; + public const uint BodyAndSoulDispel = 64136; + public const uint BodyAndSoulSpeed = 65081; + public const uint CureDisease = 528; + public const uint DispelMagicFriendly = 97690; + public const uint DispelMagicHostile = 97691; + public const uint DivineAegis = 47753; + public const uint DivineBlessing = 40440; + public const uint DivineWrath = 40441; + public const uint GlyphOfCircleOfHealing = 55675; + public const uint GlyphOfDispelMagic = 55677; + public const uint GlyphOfDispelMagicHeal = 56131; + public const uint GlyphOfLightwell = 55673; + public const uint GlyphOfPrayerOfHealingHeal = 56161; + public const uint GlyphOfShadow = 107906; + public const uint GuardianSpiritHeal = 48153; + public const uint ItemEfficiency = 37595; + public const uint LeapOfFaith = 73325; + public const uint LeapOfFaithEffect = 92832; + public const uint LeapOfFaithEffectTrigger = 92833; + public const uint LeapOfFaithTriggered = 92572; + public const uint LevitateEffect = 111759; + public const uint ManaLeechProc = 34650; + public const uint OracularHeal = 26170; + public const uint PenanceR1 = 47540; + public const uint PenanceR1Damage = 47758; + public const uint PenanceR1Heal = 47757; + public const uint RenewedHope = 197469; + public const uint RenewedHopeEffect = 197470; + public const uint ShadowformVisualWithGlyph = 107904; + public const uint ShadowformVisualWithoutGlyph = 107903; + public const uint ShieldDisciplineEnergize = 47755; + public const uint ShieldDisciplinePassive = 197045; + public const uint StrengthOfSoul = 197535; + public const uint StrengthOfSoulEffect = 197548; + public const uint T9Healing2p = 67201; + public const uint ThePenitentAura = 200347; + public const uint VampiricEmbraceHeal = 15290; + public const uint VampiricTouchDispel = 64085; + public const uint VoidShield = 199144; + public const uint VoidShieldEffect = 199145; + + public const uint GenReplenishment = 57669; + } + + [Script] // 26169 - Oracle Healing Bonus + class spell_pri_aq_3p_bonus : SpellScriptLoader + { + public spell_pri_aq_3p_bonus() : base("spell_pri_aq_3p_bonus") { } + + class spell_pri_aq_3p_bonus_AuraScript : AuraScript + { + public override bool Validate(SpellInfo spellInfo) + { + return ValidateSpellInfo(SpellIds.OracularHeal); + } + + void HandleProc(AuraEffect aurEff, ProcEventInfo eventInfo) + { + PreventDefaultAction(); + Unit caster = eventInfo.GetActor(); + if (caster == eventInfo.GetProcTarget()) + return; + + HealInfo healInfo = eventInfo.GetHealInfo(); + if (healInfo == null || healInfo.GetHeal() == 0) + return; + + int amount = (int)MathFunctions.CalculatePct(healInfo.GetHeal(), 10); + caster.CastCustomSpell(SpellIds.OracularHeal, SpellValueMod.BasePoint0, amount, caster, true); + } + + public override void Register() + { + OnEffectProc.Add(new EffectProcHandler(HandleProc, 0, AuraType.Dummy)); + } + } + + public override AuraScript GetAuraScript() + { + return new spell_pri_aq_3p_bonus_AuraScript(); + } + } + + [Script] // 81749 - Atonement + class spell_pri_atonement : SpellScriptLoader + { + public spell_pri_atonement() : base("spell_pri_atonement") { } + + public class spell_pri_atonement_AuraScript : AuraScript + { + public override bool Validate(SpellInfo spellInfo) + { + return ValidateSpellInfo(SpellIds.AtonementHeal); + } + + bool CheckProc(ProcEventInfo eventInfo) + { + return eventInfo.GetDamageInfo() != null; + } + + void HandleProc(AuraEffect aurEff, ProcEventInfo eventInfo) + { + DamageInfo damageInfo = eventInfo.GetDamageInfo(); + int heal = (int)MathFunctions.CalculatePct(damageInfo.GetDamage(), aurEff.GetAmount()); + _appliedAtonements.RemoveAll(targetGuid => + { + Unit target = Global.ObjAccessor.GetUnit(GetTarget(), targetGuid); + if (target) + { + if (target.GetExactDist(GetTarget()) < GetSpellInfo().GetEffect(1).CalcValue()) + GetTarget().CastCustomSpell(SpellIds.AtonementHeal, SpellValueMod.BasePoint0, heal, target, true); + + return false; + } + return true; + }); + } + + public override void Register() + { + DoCheckProc.Add(new CheckProcHandler(CheckProc)); + OnEffectProc.Add(new EffectProcHandler(HandleProc, 0, AuraType.Dummy)); + } + + List _appliedAtonements = new List(); + + public void AddAtonementTarget(ObjectGuid target) + { + _appliedAtonements.Add(target); + } + + public void RemoveAtonementTarget(ObjectGuid target) + { + _appliedAtonements.Remove(target); + } + } + + public override AuraScript GetAuraScript() + { + return new spell_pri_atonement_AuraScript(); + } + } + + [Script] // 194384 - Atonement + class spell_pri_atonement_triggered : SpellScriptLoader + { + public spell_pri_atonement_triggered() : base("spell_pri_atonement_triggered") { } + + class spell_pri_atonement_triggered_AuraScript : AuraScript + { + public override bool Validate(SpellInfo spellInfo) + { + return ValidateSpellInfo(SpellIds.Atonement); + } + + void HandleOnApply(AuraEffect aurEff, AuraEffectHandleModes mode) + { + Unit caster = GetCaster(); + if (caster) + { + Aura atonement = caster.GetAura(SpellIds.Atonement); + if (atonement != null) + { + var script = atonement.GetScript(nameof(spell_pri_atonement)); + if (script != null) + script.AddAtonementTarget(GetTarget().GetGUID()); + } + } + } + + void HandleOnRemove(AuraEffect aurEff, AuraEffectHandleModes mode) + { + Unit caster = GetCaster(); + if (caster) + { + Aura atonement = caster.GetAura(SpellIds.Atonement); + if (atonement != null) + { + var script = atonement.GetScript(nameof(spell_pri_atonement)); + if (script != null) + script.RemoveAtonementTarget(GetTarget().GetGUID()); + } + } + } + + public override void Register() + { + OnEffectApply.Add(new EffectApplyHandler(HandleOnApply, 0, AuraType.Dummy, AuraEffectHandleModes.Real)); + OnEffectRemove.Add(new EffectApplyHandler(HandleOnRemove, 0, AuraType.Dummy, AuraEffectHandleModes.Real)); + } + } + + public override AuraScript GetAuraScript() + { + return new spell_pri_atonement_triggered_AuraScript(); + } + } + + [Script] // 64129 - Body and Soul + class spell_pri_body_and_soul : SpellScriptLoader + { + public spell_pri_body_and_soul() : base("spell_pri_body_and_soul") { } + + class spell_pri_body_and_soul_AuraScript : AuraScript + { + public override bool Validate(SpellInfo spellInfo) + { + return ValidateSpellInfo(SpellIds.CureDisease, SpellIds.BodyAndSoulDispel); + } + + void HandleEffectSpeedProc(AuraEffect aurEff, ProcEventInfo eventInfo) + { + PreventDefaultAction(); + // Proc only with Power Word: Shield or Leap of Faith + if (!(eventInfo.GetDamageInfo().GetSpellInfo().SpellFamilyFlags[0].HasAnyFlag(0x1u) || eventInfo.GetDamageInfo().GetSpellInfo().SpellFamilyFlags[2].HasAnyFlag(0x80000u))) + return; + + GetTarget().CastCustomSpell(SpellIds.BodyAndSoulSpeed, SpellValueMod.BasePoint0, aurEff.GetAmount(), eventInfo.GetProcTarget(), true, null, aurEff); + } + + void HandleEffectDispelProc(AuraEffect aurEff, ProcEventInfo eventInfo) + { + PreventDefaultAction(); + // Proc only with Cure Disease + if (eventInfo.GetDamageInfo().GetSpellInfo().Id != SpellIds.CureDisease || eventInfo.GetProcTarget() != GetTarget()) + return; + + if (RandomHelper.randChance(aurEff.GetAmount())) + GetTarget().CastSpell(eventInfo.GetProcTarget(), SpellIds.BodyAndSoulDispel, true, null, aurEff); + } + + public override void Register() + { + OnEffectProc.Add(new EffectProcHandler(HandleEffectSpeedProc, 0, AuraType.Dummy)); + OnEffectProc.Add(new EffectProcHandler(HandleEffectDispelProc, 1, AuraType.Dummy)); + } + } + + public override AuraScript GetAuraScript() + { + return new spell_pri_body_and_soul_AuraScript(); + } + } + + // 34861 - Circle of Healing + [Script] + class spell_pri_circle_of_healing : SpellScriptLoader + { + public spell_pri_circle_of_healing() : base("spell_pri_circle_of_healing") { } + + class spell_pri_circle_of_healing_SpellScript : SpellScript + { + public override bool Validate(SpellInfo spellInfo) + { + return ValidateSpellInfo(SpellIds.GlyphOfCircleOfHealing); + } + + void FilterTargets(List targets) + { + targets.RemoveAll(new Predicate(obj => + { + Unit target = obj.ToUnit(); + if (target) + return !GetCaster().IsInRaidWith(target); + + return true; + })); + + uint maxTargets = (uint)(GetCaster().HasAura(SpellIds.GlyphOfCircleOfHealing) ? 6 : 5); // Glyph of Circle of Healing + + if (targets.Count > maxTargets) + { + targets.Sort(new HealthPctOrderPred()); + targets.Resize(maxTargets); + } + } + + public override void Register() + { + OnObjectAreaTargetSelect.Add(new ObjectAreaTargetSelectHandler(FilterTargets, 0, Targets.UnitDestAreaAlly)); + } + } + + public override SpellScript GetSpellScript() + { + return new spell_pri_circle_of_healing_SpellScript(); + } + } + + // 527 - Dispel magic + [Script] + class spell_pri_dispel_magic : SpellScriptLoader + { + public spell_pri_dispel_magic() : base("spell_pri_dispel_magic") { } + + class spell_pri_dispel_magic_SpellScript : SpellScript + { + public override bool Validate(SpellInfo spellInfo) + { + return ValidateSpellInfo(SpellIds.Absolution, SpellIds.GlyphOfDispelMagicHeal, SpellIds.GlyphOfDispelMagic); + } + + SpellCastResult CheckCast() + { + Unit caster = GetCaster(); + Unit target = GetExplTargetUnit(); + + if (!target || (!caster.HasAura(SpellIds.Absolution) && caster != target && target.IsFriendlyTo(caster))) + return SpellCastResult.BadTargets; + return SpellCastResult.SpellCastOk; + } + + void AfterEffectHit(uint effIndex) + { + if (GetHitUnit().IsFriendlyTo(GetCaster())) + { + GetCaster().CastSpell(GetHitUnit(), SpellIds.DispelMagicFriendly, true); + AuraEffect aurEff = GetHitUnit().GetAuraEffect(SpellIds.GlyphOfDispelMagic, 0); + if (aurEff != null) + { + int heal = (int)GetHitUnit().CountPctFromMaxHealth(aurEff.GetAmount()); + GetCaster().CastCustomSpell(SpellIds.GlyphOfDispelMagicHeal, SpellValueMod.BasePoint0, heal, GetHitUnit()); + } + } + else + GetCaster().CastSpell(GetHitUnit(), SpellIds.DispelMagicHostile, true); + } + + public override void Register() + { + OnCheckCast.Add(new CheckCastHandler(CheckCast)); + OnEffectHitTarget.Add(new EffectHandler(AfterEffectHit, 0, SpellEffectName.Dummy)); + } + } + + public override SpellScript GetSpellScript() + { + return new spell_pri_dispel_magic_SpellScript(); + } + } + + // -47509 - Divine Aegis + [Script] + class spell_pri_divine_aegis : SpellScriptLoader + { + public spell_pri_divine_aegis() : base("spell_pri_divine_aegis") { } + + class spell_pri_divine_aegis_AuraScript : AuraScript + { + public override bool Validate(SpellInfo spellInfo) + { + return ValidateSpellInfo(SpellIds.DivineAegis); + } + + bool CheckProc(ProcEventInfo eventInfo) + { + return eventInfo.GetProcTarget(); + } + + void HandleProc(AuraEffect aurEff, ProcEventInfo eventInfo) + { + PreventDefaultAction(); + + HealInfo healInfo = eventInfo.GetHealInfo(); + if (healInfo == null || healInfo.GetHeal() == 0) + return; + + int absorb = (int)MathFunctions.CalculatePct(healInfo.GetHeal(), aurEff.GetAmount()); + + // Multiple effects stack, so let's try to find this aura. + AuraEffect aegis = eventInfo.GetProcTarget().GetAuraEffect(SpellIds.DivineAegis, 0); + if (aegis != null) + absorb += aegis.GetAmount(); + + absorb = (int)Math.Min(absorb, eventInfo.GetProcTarget().getLevel() * 125); + + GetTarget().CastCustomSpell(SpellIds.DivineAegis, SpellValueMod.BasePoint0, absorb, eventInfo.GetProcTarget(), true, null, aurEff); + } + + public override void Register() + { + DoCheckProc.Add(new CheckProcHandler(CheckProc)); + OnEffectProc.Add(new EffectProcHandler(HandleProc, 0, AuraType.Dummy)); + } + } + + public override AuraScript GetAuraScript() + { + return new spell_pri_divine_aegis_AuraScript(); + } + } + + // 64844 - Divine Hymn + [Script] + class spell_pri_divine_hymn : SpellScriptLoader + { + public spell_pri_divine_hymn() : base("spell_pri_divine_hymn") { } + + class spell_pri_divine_hymn_SpellScript : SpellScript + { + void FilterTargets(List targets) + { + targets.RemoveAll(new Predicate(obj => + { + Unit target = obj.ToUnit(); + if (target) + return !GetCaster().IsInRaidWith(target); + + return true; + })); + + uint maxTargets = 3; + + if (targets.Count > maxTargets) + { + targets.Sort(new HealthPctOrderPred()); + targets.Resize(maxTargets); + } + } + + public override void Register() + { + OnObjectAreaTargetSelect.Add(new ObjectAreaTargetSelectHandler(FilterTargets, SpellConst.EffectAll, Targets.UnitSrcAreaAlly)); + } + } + + public override SpellScript GetSpellScript() + { + return new spell_pri_divine_hymn_SpellScript(); + } + } + + // 55680 - Glyph of Prayer of Healing + [Script] + class spell_pri_glyph_of_prayer_of_healing : SpellScriptLoader + { + public spell_pri_glyph_of_prayer_of_healing() : base("spell_pri_glyph_of_prayer_of_healing") { } + + class spell_pri_glyph_of_prayer_of_healing_AuraScript : AuraScript + { + public override bool Validate(SpellInfo spellInfo) + { + return ValidateSpellInfo(SpellIds.GlyphOfPrayerOfHealingHeal); + } + + void HandleProc(AuraEffect aurEff, ProcEventInfo eventInfo) + { + PreventDefaultAction(); + + HealInfo healInfo = eventInfo.GetHealInfo(); + if (healInfo == null || healInfo.GetHeal() == 0) + return; + + SpellInfo triggeredSpellInfo = Global.SpellMgr.GetSpellInfo(SpellIds.GlyphOfPrayerOfHealingHeal); + int heal = (int)(MathFunctions.CalculatePct(healInfo.GetHeal(), aurEff.GetAmount()) / triggeredSpellInfo.GetMaxTicks(Difficulty.None)); + GetTarget().CastCustomSpell(SpellIds.GlyphOfPrayerOfHealingHeal, SpellValueMod.BasePoint0, heal, eventInfo.GetProcTarget(), true, null, aurEff); + } + + public override void Register() + { + OnEffectProc.Add(new EffectProcHandler(HandleProc, 0, AuraType.Dummy)); + } + } + + public override AuraScript GetAuraScript() + { + return new spell_pri_glyph_of_prayer_of_healing_AuraScript(); + } + } + + [Script] // 24191 - Improved Power Word Shield + class spell_pri_improved_power_word_shield : SpellScriptLoader + { + public spell_pri_improved_power_word_shield() : base("spell_pri_improved_power_word_shield") { } + + class spell_pri_improved_power_word_shield_AuraScript : AuraScript + { + void HandleEffectCalcSpellMod(AuraEffect aurEff, ref SpellModifier spellMod) + { + if (spellMod == null) + { + spellMod = new SpellModifier(GetAura()); + spellMod.op = (SpellModOp)aurEff.GetMiscValue(); + spellMod.type = SpellModType.Pct; + spellMod.spellId = GetId(); + spellMod.mask = GetSpellInfo().GetEffect(aurEff.GetEffIndex()).SpellClassMask; + } + + spellMod.value = aurEff.GetAmount(); + } + + public override void Register() + { + DoEffectCalcSpellMod.Add(new EffectCalcSpellModHandler(HandleEffectCalcSpellMod, 0, AuraType.Dummy)); + } + } + + public override AuraScript GetAuraScript() + { + return new spell_pri_improved_power_word_shield_AuraScript(); + } + } + + // 47788 - Guardian Spirit + [Script] + class spell_pri_guardian_spirit : SpellScriptLoader + { + public spell_pri_guardian_spirit() : base("spell_pri_guardian_spirit") { } + + class spell_pri_guardian_spirit_AuraScript : AuraScript + { + uint healPct; + + public override bool Validate(SpellInfo spellInfo) + { + return ValidateSpellInfo(SpellIds.GuardianSpiritHeal); + } + + public override bool Load() + { + healPct = (uint)GetSpellInfo().GetEffect(1).CalcValue(); + return true; + } + + void CalculateAmount(AuraEffect aurEff, ref int amount, ref bool canBeRecalculated) + { + // Set absorbtion amount to unlimited + amount = -1; + } + + void Absorb(AuraEffect aurEff, DamageInfo dmgInfo, ref uint absorbAmount) + { + Unit target = GetTarget(); + if (dmgInfo.GetDamage() < target.GetHealth()) + return; + + int healAmount = (int)target.CountPctFromMaxHealth((int)healPct); + // remove the aura now, we don't want 40% healing bonus + Remove(AuraRemoveMode.EnemySpell); + target.CastCustomSpell(target, SpellIds.GuardianSpiritHeal, healAmount, 0, 0, true); + absorbAmount = dmgInfo.GetDamage(); + } + + public override void Register() + { + DoEffectCalcAmount.Add(new EffectCalcAmountHandler(CalculateAmount, 1, AuraType.SchoolAbsorb)); + OnEffectAbsorb.Add(new EffectAbsorbHandler(Absorb, 1)); + } + } + + public override AuraScript GetAuraScript() + { + return new spell_pri_guardian_spirit_AuraScript(); + } + } + + // 64904 - Hymn of Hope + [Script] + class spell_pri_hymn_of_hope : SpellScriptLoader + { + public spell_pri_hymn_of_hope() : base("spell_pri_hymn_of_hope") { } + + class spell_pri_hymn_of_hope_SpellScript : SpellScript + { + void FilterTargets(List targets) + { + targets.RemoveAll(new Predicate(obj => + { + Unit target = obj.ToUnit(); + if (target) + return target.getPowerType() != PowerType.Mana; + + return true; + })); + + targets.RemoveAll(new Predicate(obj => + { + Unit target = obj.ToUnit(); + if (target) + return !GetCaster().IsInRaidWith(target); + + return true; + })); + + uint maxTargets = 3; + + if (targets.Count > maxTargets) + { + targets.Sort(new PowerPctOrderPred(PowerType.Mana)); + targets.Resize(maxTargets); + } + } + + public override void Register() + { + OnObjectAreaTargetSelect.Add(new ObjectAreaTargetSelectHandler(FilterTargets, SpellConst.EffectAll, Targets.UnitSrcAreaAlly)); + } + } + + public override SpellScript GetSpellScript() + { + return new spell_pri_hymn_of_hope_SpellScript(); + } + } + + [Script] // 40438 - Priest Tier 6 Trinket + class spell_pri_item_t6_trinket : SpellScriptLoader + { + public spell_pri_item_t6_trinket() : base("spell_pri_item_t6_trinket") { } + + class spell_pri_item_t6_trinket_AuraScript : AuraScript + { + public override bool Validate(SpellInfo spellInfo) + { + return ValidateSpellInfo(SpellIds.DivineBlessing, SpellIds.DivineWrath); + } + + void HandleProc(AuraEffect aurEff, ProcEventInfo eventInfo) + { + PreventDefaultAction(); + Unit caster = eventInfo.GetActor(); + if (eventInfo.GetSpellTypeMask().HasAnyFlag(ProcFlagsSpellType.Heal)) + caster.CastSpell((Unit)null, SpellIds.DivineBlessing, true); + + if (eventInfo.GetSpellTypeMask().HasAnyFlag(ProcFlagsSpellType.Damage)) + caster.CastSpell((Unit)null, SpellIds.DivineWrath, true); + } + + public override void Register() + { + OnEffectProc.Add(new EffectProcHandler(HandleProc, 0, AuraType.Dummy)); + } + } + + public override AuraScript GetAuraScript() + { + return new spell_pri_item_t6_trinket_AuraScript(); + } + } + + // 92833 - Leap of Faith + [Script] + class spell_pri_leap_of_faith_effect_trigger : SpellScriptLoader + { + public spell_pri_leap_of_faith_effect_trigger() : base("spell_pri_leap_of_faith_effect_trigger") { } + + class spell_pri_leap_of_faith_effect_trigger_SpellScript : SpellScript + { + public override bool Validate(SpellInfo spellInfo) + { + return ValidateSpellInfo(SpellIds.LeapOfFaithEffect); + } + + void HandleEffectDummy(uint effIndex) + { + Position destPos = GetHitDest().GetPosition(); + + SpellCastTargets targets = new SpellCastTargets(); + targets.SetDst(destPos); + targets.SetUnitTarget(GetCaster()); + GetHitUnit().CastSpell(targets, Global.SpellMgr.GetSpellInfo((uint)GetEffectValue()), null); + } + + public override void Register() + { + OnEffectHitTarget.Add(new EffectHandler(HandleEffectDummy, 0, SpellEffectName.Dummy)); + } + } + + public override SpellScript GetSpellScript() + { + return new spell_pri_leap_of_faith_effect_trigger_SpellScript(); + } + } + + [Script] // 1706 - Levitate + class spell_pri_levitate : SpellScriptLoader + { + public spell_pri_levitate() : base("spell_pri_levitate") { } + + class spell_pri_levitate_SpellScript : SpellScript + { + public override bool Validate(SpellInfo spellInfo) + { + return ValidateSpellInfo(SpellIds.LevitateEffect); + } + + void HandleDummy(uint effIndex) + { + GetCaster().CastSpell(GetHitUnit(), SpellIds.LevitateEffect, true); + } + + public override void Register() + { + OnEffectHitTarget.Add(new EffectHandler(HandleDummy, 0, SpellEffectName.Dummy)); + } + } + + public override SpellScript GetSpellScript() + { + return new spell_pri_levitate_SpellScript(); + } + } + + // 7001 - Lightwell Renew + [Script] + class spell_pri_lightwell_renew : SpellScriptLoader + { + public spell_pri_lightwell_renew() : base("spell_pri_lightwell_renew") { } + + class spell_pri_lightwell_renew_AuraScript : AuraScript + { + void CalculateAmount(AuraEffect aurEff, ref int amount, ref bool canBeRecalculated) + { + Unit caster = GetCaster(); + if (caster) + { + // Bonus from Glyph of Lightwell + AuraEffect modHealing = caster.GetAuraEffect(SpellIds.GlyphOfLightwell, 0); + if (modHealing != null) + MathFunctions.AddPct(ref amount, modHealing.GetAmount()); + } + } + + public override void Register() + { + DoEffectCalcAmount.Add(new EffectCalcAmountHandler(CalculateAmount, 0, AuraType.PeriodicHeal)); + } + } + + public override AuraScript GetAuraScript() + { + return new spell_pri_lightwell_renew_AuraScript(); + } + } + + // 8129 - Mana Burn + [Script] + class spell_pri_mana_burn : SpellScriptLoader + { + public spell_pri_mana_burn() : base("spell_pri_mana_burn") { } + + class spell_pri_mana_burn_SpellScript : SpellScript + { + void HandleAfterHit() + { + Unit unitTarget = GetHitUnit(); + if (unitTarget) + unitTarget.RemoveAurasWithMechanic((1 << (int)Mechanics.Fear) | (1 << (int)Mechanics.Polymorph)); + } + + public override void Register() + { + AfterHit.Add(new HitHandler(HandleAfterHit)); + } + } + + public override SpellScript GetSpellScript() + { + return new spell_pri_mana_burn_SpellScript(); + } + } + + // 28305 - Mana Leech (Passive) (Priest Pet Aura) + [Script] + class spell_pri_mana_leech : SpellScriptLoader + { + public spell_pri_mana_leech() : base("spell_pri_mana_leech") { } + + class spell_pri_mana_leech_AuraScript : AuraScript + { + public override bool Validate(SpellInfo spellInfo) + { + return ValidateSpellInfo(SpellIds.ManaLeechProc); + } + + public override bool Load() + { + _procTarget = null; + return true; + } + + bool CheckProc(ProcEventInfo eventInfo) + { + _procTarget = GetTarget().GetOwner(); + return _procTarget; + } + + void HandleProc(AuraEffect aurEff, ProcEventInfo eventInfo) + { + PreventDefaultAction(); + GetTarget().CastSpell(_procTarget, SpellIds.ManaLeechProc, true, null, aurEff); + } + + public override void Register() + { + DoCheckProc.Add(new CheckProcHandler(CheckProc)); + OnEffectProc.Add(new EffectProcHandler(HandleProc, 0, AuraType.Dummy)); + } + + Unit _procTarget; + } + + public override AuraScript GetAuraScript() + { + return new spell_pri_mana_leech_AuraScript(); + } + } + + // 47948 - Pain and Suffering (Proc) + [Script] + class spell_pri_pain_and_suffering_proc : SpellScriptLoader + { + public spell_pri_pain_and_suffering_proc() : base("spell_pri_pain_and_suffering_proc") { } + + class spell_pri_pain_and_suffering_proc_SpellScript : SpellScript + { + void HandleEffectScriptEffect(uint effIndex) + { + // Refresh Shadow Word: Pain on target + Unit unitTarget = GetHitUnit(); + if (unitTarget) + { + AuraEffect aur = unitTarget.GetAuraEffect(AuraType.PeriodicDamage, SpellFamilyNames.Priest, new FlagArray128(0x8000, 0, 0), GetCaster().GetGUID()); + if (aur != null) + aur.GetBase().RefreshDuration(); + } + } + + public override void Register() + { + OnEffectHitTarget.Add(new EffectHandler(HandleEffectScriptEffect, 0, SpellEffectName.ScriptEffect)); + } + } + + public override SpellScript GetSpellScript() + { + return new spell_pri_pain_and_suffering_proc_SpellScript(); + } + } + + // 47540 - Penance + [Script] + class spell_pri_penance : SpellScriptLoader + { + public spell_pri_penance() : base("spell_pri_penance") { } + + class spell_pri_penance_SpellScript : SpellScript + { + public override bool Load() + { + return GetCaster().IsTypeId(TypeId.Player); + } + + public override bool Validate(SpellInfo spellInfo) + { + SpellInfo firstRankSpellInfo = Global.SpellMgr.GetSpellInfo(SpellIds.PenanceR1); + if (firstRankSpellInfo == null) + return false; + + // can't use other spell than this penance due to spell_ranks dependency + if (!spellInfo.IsRankOf(firstRankSpellInfo)) + return false; + + byte rank = spellInfo.GetRank(); + if (Global.SpellMgr.GetSpellWithRank(SpellIds.PenanceR1Damage, rank, true) == 0) + return false; + if (Global.SpellMgr.GetSpellWithRank(SpellIds.PenanceR1Heal, rank, true) == 0) + return false; + + return true; + } + + void HandleDummy(uint effIndex) + { + Unit caster = GetCaster(); + Unit target = GetHitUnit(); + if (target) + { + if (!target.IsAlive()) + return; + + byte rank = GetSpellInfo().GetRank(); + + if (caster.IsFriendlyTo(target)) + caster.CastSpell(target, Global.SpellMgr.GetSpellWithRank(SpellIds.PenanceR1Heal, rank), false); + else + caster.CastSpell(target, Global.SpellMgr.GetSpellWithRank(SpellIds.PenanceR1Damage, rank), false); + } + } + + SpellCastResult CheckCast() + { + Player caster = GetCaster().ToPlayer(); + Unit target = GetExplTargetUnit(); + if (target) + { + if (!caster.IsFriendlyTo(target)) + { + if (!caster.IsValidAttackTarget(target)) + return SpellCastResult.BadTargets; + + if (!caster.isInFront(target)) + return SpellCastResult.NotInfront; + } + else + { + //Support for modifications of this spell in Legion with The Penitent talent (7.1.5) + if (!caster.HasAura(SpellIds.ThePenitentAura)) + return SpellCastResult.BadTargets; + + if (!caster.isInFront(target)) + return SpellCastResult.UnitNotInfront; + } + } + return SpellCastResult.SpellCastOk; + } + + public override void Register() + { + OnEffectHitTarget.Add(new EffectHandler(HandleDummy, 0, SpellEffectName.Dummy)); + OnCheckCast.Add(new CheckCastHandler(CheckCast)); + } + } + + public override SpellScript GetSpellScript() + { + return new spell_pri_penance_SpellScript(); + } + } + + // -47569 - Phantasm + [Script] + class spell_pri_phantasm : SpellScriptLoader + { + public spell_pri_phantasm() : base("spell_pri_phantasm") { } + + class spell_pri_phantasm_AuraScript : AuraScript + { + bool CheckProc(ProcEventInfo eventInfo) + { + return RandomHelper.randChance(GetEffect(0).GetAmount()); + } + + void HandleEffectProc(AuraEffect aurEff, ProcEventInfo eventInfo) + { + PreventDefaultAction(); + GetTarget().RemoveMovementImpairingAuras(); + } + + public override void Register() + { + DoCheckProc.Add(new CheckProcHandler(CheckProc)); + OnEffectProc.Add(new EffectProcHandler(HandleEffectProc, 0, AuraType.Dummy)); + } + } + + public override AuraScript GetAuraScript() + { + return new spell_pri_phantasm_AuraScript(); + } + } + + [Script] // 17 - Power Word: Shield + class spell_pri_power_word_shield : SpellScriptLoader + { + public spell_pri_power_word_shield() : base("spell_pri_power_word_shield") { } + + class spell_pri_power_word_shield_AuraScript : AuraScript + { + void CalculateAmount(AuraEffect auraEffect, ref int amount, ref bool canBeRecalculated) + { + canBeRecalculated = false; + + Player player = GetCaster().ToPlayer(); + if (player) + { + int playerMastery = (int)player.GetRatingBonusValue(CombatRating.Mastery); + int playerSpellPower = player.SpellBaseDamageBonusDone(SpellSchoolMask.Holy); + int playerVersatileDamage = (int)player.GetRatingBonusValue(CombatRating.VersatilityDamageDone); + + //Formula taken from SpellWork + amount = (int)((playerSpellPower * 5.5f) + playerMastery) * (1 + playerVersatileDamage); + } + } + + void HandleOnApply(AuraEffect aurEff, AuraEffectHandleModes mode) + { + Unit caster = GetCaster(); + Unit target = GetTarget(); + if (!caster) + return; + + if (caster.HasAura(SpellIds.BodyAndSoul)) + caster.CastSpell(target, SpellIds.BodyAndSoulSpeed, true); + if (caster.HasAura(SpellIds.StrengthOfSoul)) + caster.CastSpell(target, SpellIds.StrengthOfSoulEffect, true); + if (caster.HasAura(SpellIds.RenewedHope)) + caster.CastSpell(target, SpellIds.RenewedHopeEffect, true); + if (caster.HasAura(SpellIds.VoidShield) && caster == target) + caster.CastSpell(target, SpellIds.VoidShieldEffect, true); + if (caster.HasAura(SpellIds.Atonement)) + caster.CastSpell(target, SpellIds.AtonementTriggered, true); + } + + void HandleOnRemove(AuraEffect aurEff, AuraEffectHandleModes mode) + { + GetTarget().RemoveAura(SpellIds.StrengthOfSoulEffect); + Unit caster = GetCaster(); + if (caster) + if (GetTargetApplication().GetRemoveMode() == AuraRemoveMode.EnemySpell && caster.HasAura(SpellIds.ShieldDisciplinePassive)) + caster.CastSpell(caster, SpellIds.ShieldDisciplineEnergize, true); + } + + public override void Register() + { + DoEffectCalcAmount.Add(new EffectCalcAmountHandler(CalculateAmount, 0, AuraType.SchoolAbsorb)); + AfterEffectApply.Add(new EffectApplyHandler(HandleOnApply, 0, AuraType.SchoolAbsorb, AuraEffectHandleModes.RealOrReapplyMask)); + AfterEffectRemove.Add(new EffectApplyHandler(HandleOnRemove, 0, AuraType.SchoolAbsorb, AuraEffectHandleModes.Real)); + } + } + + public override AuraScript GetAuraScript() + { + return new spell_pri_power_word_shield_AuraScript(); + } + } + + [Script] // 33110 - Prayer of Mending Heal + class spell_pri_prayer_of_mending_heal : SpellScriptLoader + { + public spell_pri_prayer_of_mending_heal() : base("spell_pri_prayer_of_mending_heal") { } + + class spell_pri_prayer_of_mending_heal_SpellScript : SpellScript + { + void HandleHeal(uint effIndex) + { + Unit caster = GetOriginalCaster(); + if (caster) + { + AuraEffect aurEff = caster.GetAuraEffect(SpellIds.T9Healing2p, 0); + if (aurEff != null) + { + int heal = GetHitHeal(); + MathFunctions.AddPct(ref heal, aurEff.GetAmount()); + SetHitHeal(heal); + } + } + } + + public override void Register() + { + OnEffectHitTarget.Add(new EffectHandler(HandleHeal, 0, SpellEffectName.Heal)); + } + } + + public override SpellScript GetSpellScript() + { + return new spell_pri_prayer_of_mending_heal_SpellScript(); + } + } + + // 15473 - Shadowform + [Script] + class spell_pri_shadowform : SpellScriptLoader + { + public spell_pri_shadowform() : base("spell_pri_shadowform") { } + + class spell_pri_shadowform_AuraScript : AuraScript + { + public override bool Validate(SpellInfo spellInfo) + { + return ValidateSpellInfo(SpellIds.ShadowformVisualWithoutGlyph, SpellIds.ShadowformVisualWithGlyph); + } + + void HandleEffectApply(AuraEffect aurEff, AuraEffectHandleModes mode) + { + GetTarget().CastSpell(GetTarget(), GetTarget().HasAura(SpellIds.GlyphOfShadow) ? SpellIds.ShadowformVisualWithGlyph : SpellIds.ShadowformVisualWithoutGlyph, true); + } + + void HandleEffectRemove(AuraEffect aurEff, AuraEffectHandleModes mode) + { + GetTarget().RemoveAurasDueToSpell(GetTarget().HasAura(SpellIds.GlyphOfShadow) ? SpellIds.ShadowformVisualWithGlyph : SpellIds.ShadowformVisualWithoutGlyph); + } + + public override void Register() + { + AfterEffectApply.Add(new EffectApplyHandler(HandleEffectApply, 0, AuraType.ModShapeshift, AuraEffectHandleModes.RealOrReapplyMask)); + AfterEffectRemove.Add(new EffectApplyHandler(HandleEffectRemove, 0, AuraType.ModShapeshift, AuraEffectHandleModes.RealOrReapplyMask)); + } + } + + public override AuraScript GetAuraScript() + { + return new spell_pri_shadowform_AuraScript(); + } + } + + [Script] // 28809 - Greater Heal + class spell_pri_t3_4p_bonus : SpellScriptLoader + { + public spell_pri_t3_4p_bonus() : base("spell_pri_t3_4p_bonus") { } + + class spell_pri_t3_4p_bonus_AuraScript : AuraScript + { + public override bool Validate(SpellInfo spellInfo) + { + return ValidateSpellInfo(SpellIds.ArmorOfFaith); + } + + void HandleProc(AuraEffect aurEff, ProcEventInfo eventInfo) + { + PreventDefaultAction(); + eventInfo.GetActor().CastSpell(eventInfo.GetProcTarget(), SpellIds.ArmorOfFaith, true); + } + + public override void Register() + { + OnEffectProc.Add(new EffectProcHandler(HandleProc, 0, AuraType.Dummy)); + } + } + + public override AuraScript GetAuraScript() + { + return new spell_pri_t3_4p_bonus_AuraScript(); + } + } + + [Script] // 37594 - Greater Heal Refund + class spell_pri_t5_heal_2p_bonus : SpellScriptLoader + { + public spell_pri_t5_heal_2p_bonus() : base("spell_pri_t5_heal_2p_bonus") { } + + class spell_pri_t5_heal_2p_bonus_AuraScript : AuraScript + { + public override bool Validate(SpellInfo spellInfo) + { + return ValidateSpellInfo(SpellIds.ItemEfficiency); + } + + bool CheckProc(ProcEventInfo eventInfo) + { + HealInfo healInfo = eventInfo.GetHealInfo(); + if (healInfo != null) + { + Unit healTarget = healInfo.GetTarget(); + if (healTarget) + // @todo: fix me later if (healInfo.GetEffectiveHeal()) + if (healTarget.GetHealth() >= healTarget.GetMaxHealth()) + return true; + } + + return false; + } + + void HandleProc(AuraEffect aurEff, ProcEventInfo eventInfo) + { + PreventDefaultAction(); + GetTarget().CastSpell(GetTarget(), SpellIds.ItemEfficiency, true, null, aurEff); + } + + public override void Register() + { + DoCheckProc.Add(new CheckProcHandler(CheckProc)); + OnEffectProc.Add(new EffectProcHandler(HandleProc, 0, AuraType.ProcTriggerSpell)); + } + } + + public override AuraScript GetAuraScript() + { + return new spell_pri_t5_heal_2p_bonus_AuraScript(); + } + } + + [Script] // 70770 - Item - Priest T10 Healer 2P Bonus + class spell_pri_t10_heal_2p_bonus : SpellScriptLoader + { + public spell_pri_t10_heal_2p_bonus() : base("spell_pri_t10_heal_2p_bonus") { } + + class spell_pri_t10_heal_2p_bonus_AuraScript : AuraScript + { + public override bool Validate(SpellInfo spellInfo) + { + return ValidateSpellInfo(SpellIds.BlessedHealing); + } + + void HandleProc(AuraEffect aurEff, ProcEventInfo eventInfo) + { + PreventDefaultAction(); + + HealInfo healInfo = eventInfo.GetHealInfo(); + if (healInfo == null || healInfo.GetHeal() == 0) + return; + + SpellInfo spellInfo = Global.SpellMgr.GetSpellInfo(SpellIds.BlessedHealing); + int amount = (int)MathFunctions.CalculatePct(healInfo.GetHeal(), aurEff.GetAmount()); + amount /= (int)spellInfo.GetMaxTicks(Difficulty.None); + + // Add remaining ticks to healing done + Unit caster = eventInfo.GetActor(); + Unit target = eventInfo.GetProcTarget(); + amount += (int)target.GetRemainingPeriodicAmount(caster.GetGUID(), SpellIds.BlessedHealing, AuraType.PeriodicHeal); + + caster.CastCustomSpell(SpellIds.BlessedHealing, SpellValueMod.BasePoint0, amount, target, true); + } + + public override void Register() + { + OnEffectProc.Add(new EffectProcHandler(HandleProc, 0, AuraType.Dummy)); + } + } + + public override AuraScript GetAuraScript() + { + return new spell_pri_t10_heal_2p_bonus_AuraScript(); + } + } + + // 15286 - Vampiric Embrace + [Script] + class spell_pri_vampiric_embrace : SpellScriptLoader + { + public spell_pri_vampiric_embrace() : base("spell_pri_vampiric_embrace") { } + + class spell_pri_vampiric_embrace_AuraScript : AuraScript + { + public override bool Validate(SpellInfo spellInfo) + { + return ValidateSpellInfo(SpellIds.VampiricEmbraceHeal); + } + + bool CheckProc(ProcEventInfo eventInfo) + { + // Not proc from Mind Sear + return !eventInfo.GetDamageInfo().GetSpellInfo().SpellFamilyFlags[1].HasAnyFlag(0x80000u); + } + + void HandleEffectProc(AuraEffect aurEff, ProcEventInfo eventInfo) + { + PreventDefaultAction(); + DamageInfo damageInfo = eventInfo.GetDamageInfo(); + if (damageInfo == null || damageInfo.GetDamage() == 0) + return; + + int selfHeal = (int)MathFunctions.CalculatePct(damageInfo.GetDamage(), aurEff.GetAmount()); + int teamHeal = selfHeal / 2; + + GetTarget().CastCustomSpell(null, SpellIds.VampiricEmbraceHeal, teamHeal, selfHeal, 0, true, null, aurEff); + } + + public override void Register() + { + DoCheckProc.Add(new CheckProcHandler(CheckProc)); + OnEffectProc.Add(new EffectProcHandler(HandleEffectProc, 0, AuraType.Dummy)); + } + } + + public override AuraScript GetAuraScript() + { + return new spell_pri_vampiric_embrace_AuraScript(); + } + } + + // 15290 - Vampiric Embrace (heal) + [Script] + class spell_pri_vampiric_embrace_target : SpellScriptLoader + { + public spell_pri_vampiric_embrace_target() : base("spell_pri_vampiric_embrace_target") { } + + class spell_pri_vampiric_embrace_target_SpellScript : SpellScript + { + void FilterTargets(List unitList) + { + unitList.Remove(GetCaster()); + } + + public override void Register() + { + OnObjectAreaTargetSelect.Add(new ObjectAreaTargetSelectHandler(FilterTargets, 0, Targets.UnitCasterAreaParty)); + } + } + + public override SpellScript GetSpellScript() + { + return new spell_pri_vampiric_embrace_target_SpellScript(); + } + } + + // 34914 - Vampiric Touch + [Script] + class spell_pri_vampiric_touch : SpellScriptLoader + { + public spell_pri_vampiric_touch() : base("spell_pri_vampiric_touch") { } + + class spell_pri_vampiric_touch_AuraScript : AuraScript + { + public override bool Validate(SpellInfo spellInfo) + { + return ValidateSpellInfo(SpellIds.VampiricTouchDispel, SpellIds.GenReplenishment); + } + + void HandleDispel(DispelInfo dispelInfo) + { + Unit caster = GetCaster(); + if (caster) + { + Unit target = GetUnitOwner(); + if (target) + { + AuraEffect aurEff = GetEffect(1); + if (aurEff != null) + { + int damage = aurEff.GetAmount() * 8; + // backfire damage + caster.CastCustomSpell(target, SpellIds.VampiricTouchDispel, damage, 0, 0, true, null, aurEff); + } + } + } + } + + bool CheckProc(ProcEventInfo eventInfo) + { + return eventInfo.GetProcTarget() == GetCaster(); + } + + void HandleEffectProc(AuraEffect aurEff, ProcEventInfo eventInfo) + { + PreventDefaultAction(); + eventInfo.GetProcTarget().CastSpell((Unit)null, SpellIds.GenReplenishment, true, null, aurEff); + } + + public override void Register() + { + AfterDispel.Add(new AuraDispelHandler(HandleDispel)); + DoCheckProc.Add(new CheckProcHandler(CheckProc)); + OnEffectProc.Add(new EffectProcHandler(HandleEffectProc, 2, AuraType.Dummy)); + } + } + + public override AuraScript GetAuraScript() + { + return new spell_pri_vampiric_touch_AuraScript(); + } + } + + [Script] // 121536 - Angelic Feather talent + class spell_pri_angelic_feather_trigger : SpellScriptLoader + { + public spell_pri_angelic_feather_trigger() : base("spell_pri_angelic_feather_trigger") { } + + class spell_pri_angelic_feather_trigger_SpellScript : SpellScript + { + public override bool Validate(SpellInfo spellInfo) + { + return ValidateSpellInfo(SpellIds.AngelicFeatherAreatrigger); + } + + void HandleEffectDummy(uint effIndex) + { + Position destPos = GetHitDest().GetPosition(); + float radius = GetEffectInfo().CalcRadius(); + + // Caster is prioritary + if (GetCaster().IsWithinDist2d(destPos, radius)) + { + GetCaster().CastSpell(GetCaster(), SpellIds.AngelicFeatherAura, true); + } + else + { + SpellCastTargets targets = new SpellCastTargets(); + targets.SetDst(destPos); + GetCaster().CastSpell(targets, Global.SpellMgr.GetSpellInfo(SpellIds.AngelicFeatherAreatrigger), null); + } + } + + public override void Register() + { + OnEffectHit.Add(new EffectHandler(HandleEffectDummy, 0, SpellEffectName.Dummy)); + } + } + + public override SpellScript GetSpellScript() + { + return new spell_pri_angelic_feather_trigger_SpellScript(); + } + } + + [Script] // Angelic Feather areatrigger - created by SPELL_PRIEST_ANGELIC_FEATHER_AREATRIGGER + class areatrigger_pri_angelic_feather : AreaTriggerEntityScript + { + public areatrigger_pri_angelic_feather() : base("areatrigger_pri_angelic_feather") { } + + class areatrigger_pri_angelic_featherAI : AreaTriggerAI + { + public areatrigger_pri_angelic_featherAI(AreaTrigger areatrigger) : base(areatrigger) { } + + // Called when the AreaTrigger has just been initialized, just before added to map + public override void OnInitialize() + { + Unit caster = at.GetCaster(); + if (caster ) + { + List areaTriggers = caster.GetAreaTriggers(SpellIds.AngelicFeatherAreatrigger); + + if (areaTriggers.Count >= 3) + areaTriggers.First().SetDuration(0); + } + } + + public override void OnUnitEnter(Unit unit) + { + Unit caster = at.GetCaster(); + if (caster) + { + if (caster.IsFriendlyTo(unit)) + { + // If target already has aura, increase duration to max 130% of initial duration + caster.CastSpell(unit, SpellIds.AngelicFeatherAura, true); + at.SetDuration(0); + } + } + } + } + + public override AreaTriggerAI GetAI(AreaTrigger areatrigger) + { + return new areatrigger_pri_angelic_featherAI(areatrigger); + } + } +} \ No newline at end of file diff --git a/Scripts/Spells/Quest.cs b/Scripts/Spells/Quest.cs new file mode 100644 index 000000000..0341baa66 --- /dev/null +++ b/Scripts/Spells/Quest.cs @@ -0,0 +1,2435 @@ +/* + * Copyright (C) 2012-2017 CypherCore + * + * 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, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +using Framework.Constants; +using Game.Entities; +using Game.Maps; +using Game.Scripting; +using Game.Spells; +using System; +using System.Collections.Generic; + +namespace Scripts.Spells.Quest +{ + struct SpellIds + { + //Thaumaturgychannel + public const uint ThaumaturgyChannel = 21029; + + //Quest5206 + public const uint CreateResonatingSkull = 17269; + public const uint CreateBoneDust = 17270; + + //Quest11396-11399 + public const uint ForceShieldArcanePurpleX3 = 43874; + public const uint ScourgingCrystalController = 43878; + + //Quest11587 + public const uint SummonArcanePrisonerMale = 45446; // Summon Arcane Prisoner - Male + public const uint SummonArcanePrisonerFemale = 45448; // Summon Arcane Prisoner - Female + public const uint ArcanePrisonerKillCredit = 45456; // Arcane Prisoner Kill Credit + + //Quest11730 + public const uint SummonScavengebot004a8 = 46063; + public const uint SummonSentrybot57k = 46068; + public const uint SummonDefendotank66d = 46058; + public const uint SummonScavengebot005b6 = 46066; + public const uint Summon55dCollectatron = 46034; + public const uint RobotKillCredit = 46027; + + //Quest12634 + public const uint BananasFallToGround = 51836; + public const uint OrangeFallsToGround = 51837; + public const uint PapayaFallsToGround = 51839; + public const uint SummonAdventurousDwarf = 52070; + + //Quest12851 + public const uint FrostgiantCredit = 58184; + public const uint FrostworgCredit = 58183; + public const uint Immolation = 54690; + public const uint Ablaze = 54683; + + //Quest12937 + public const uint TriggerAidOfTheEarthen = 55809; + + //Whoarethey + public const uint MaleDisguise = 38080; + public const uint FemaleDisguise = 38081; + public const uint GenericDisguise = 32756; + + //Symboloflife + public const uint PermanentFeignDeath = 29266; + + //Stoppingthespread + public const uint Flames = 39199; + + //Chumthewatersummons + public const uint SummonAngryKvaldir = 66737; + public const uint SummonNorthSeaMako = 66738; + public const uint SummonNorthSeaThresher = 66739; + public const uint SummonNorthSeaBlueShark = 66740; + + //Redsnapperverytasty + public const uint CastNet = 29866; + public const uint FishedUpMurloc = 29869; + + //Pounddrumspells + public const uint SummonDeepJormungar = 66510; + public const uint StormforgedMoleMachine = 66492; + + //Leavenothingtochance + public const uint UpperMineShaftCredit = 48744; + public const uint LowerMineShaftCredit = 48745; + + //Focusonthebeach + public const uint BunnyCreditBeam = 47390; + + //Acleansingsong + public const uint SummonSpiritAtah = 52954; + public const uint SummonSpiritHakhalan = 52958; + public const uint SummonSpiritKoosu = 52959; + + //Defendingwyrmresttemple + public const uint SummonWyrmrestDefender = 49207; + + //Quest11010 11102 11023 + public const uint FlakCannonTrigger = 40110; + public const uint ChooseLoc = 40056; + public const uint AggroCheck = 40112; + + //Spellzuldrakrat + public const uint SummonGorgedLurkingBasilisk = 50928; + + //Quenchingmist + public const uint FlickeringFlames = 53504; + + //Quest13291 13292 13239 13261 + public const uint Ride = 59319; + + //Bearflankmaster + public const uint BearFlankMaster = 56565; + public const uint CreateBearFlank = 56566; + public const uint BearFlankFail = 56569; + + //Burstattheseams + public const uint BurstAtTheSeams = 52510; // Burst At The Seams + public const uint BurstAtTheSeamsDmg = 52508; // Damage Spell + public const uint BurstAtTheSeamsDmg2 = 59580; // Abomination Self Damage Spell + public const uint BurstAtTheSeamsBone = 52516; // Burst At The Seams:Bone + public const uint BurstAtTheSeamsMeat = 52520; // Explode Abomination:Meat + public const uint BurstAtTheSeamsBmeat = 52523; // Explode Abomination:Bloody Meat + public const uint DrakkariSkullcrusherCredit = 52590; // Credit For Drakkari Skullcrusher + public const uint SummonDrakkariChieftain = 52616; // Summon Drakkari Chieftain + public const uint DrakkariChieftainkKillCredit = 52620; // Drakkari Chieftain Kill Credit + + //Escapefromsilverbrook + public const uint SummonWorgen = 48681; + + //Deathcomesfromonhigh + public const uint ForgeCredit = 51974; + public const uint TownHallCredit = 51977; + public const uint ScarletHoldCredit = 51980; + public const uint ChapelCredit = 51982; + + //RecallEyeOfAcherus + public const uint TheEyeOfAcherus = 51852; + + //QuestTheStormKing + public const uint RideGymer = 43671; + public const uint Grabbed = 55424; + + //QuestTheStormKingThrow + public const uint VargulExplosion = 55569; + + //QuestTheHunterAndThePrince + public const uint IllidanKillCredit = 61748; + + //Relicoftheearthenring + public const uint TotemOfTheEarthenRing = 66747; + + //Fumping + public const uint SummonSandGnome = 39240; + public const uint SummonBoneSlicer = 39241; + + //Fearnoevil + public const uint RenewedLife = 93097; + } + + struct CreatureIds + { + //Quest55 + public const uint Morbent = 1200; + public const uint WeakenedMorbent = 24782; + + //Quests6124 6129 + public const uint SicklyGazelle = 12296; + public const uint CuredGazelle = 12297; + public const uint SicklyDeer = 12298; + public const uint CuredDeer = 12299; + + //Quest10255 + public const uint Helboar = 16880; + public const uint Dreadtusk = 16992; + + //Quest11515 + public const uint FelbloodInitiate = 24918; + public const uint EmaciatedFelblood = 24955; + + //Quest11730 + public const uint Scavengebot004a8 = 25752; + public const uint Sentrybot57k = 25753; + public const uint Defendotank66d = 25758; + public const uint Scavengebot005b6 = 25792; + public const uint Npc55dCollectatron = 25793; + + //Quest12459 + public const uint ReanimatedFrostwyrm = 26841; + public const uint WeakReanimatedFrostwyrm = 27821; + public const uint Turgid = 27808; + public const uint WeakTurgid = 27809; + public const uint Deathgaze = 27122; + public const uint WeakDeathgaze = 27807; + + //Quest12851 + public const uint Frostgiant = 29351; + public const uint Frostworg = 29358; + + //Quest12937 + public const uint FallenEarthenDefender = 30035; + + //Quest12659 + public const uint ScalpsKcBunny = 28622; + + //Stoppingthespread + public const uint VillagerKillCredit = 18240; + + //Salvaginglifesstength + public const uint ShardKillCredit = 29303; + + //Battlestandard + public const uint KingOfTheMountaintKc = 31766; + + //Hodirshelm + public const uint Killcredit = 30210; // Hodir'S Helm Kc Bunny + public const uint IceSpikeBunny = 30215; + + //Leavenothingtochance + public const uint UpperMineShaft = 27436; + public const uint LowerMineShaft = 27437; + + //Quest12372 + public const uint WyrmrestTempleCredit = 27698; + + //Quest11010 11102 11023 + public const uint FelCannon2 = 23082; + + //Quest13291 13292 13239 13261 + public const uint Skytalon = 31583; + public const uint Decoy = 31578; + + //Burstattheseams + public const uint DrakkariChieftaink = 29099; + + //Deathcomesfromonhigh + public const uint NewAvalonForge = 28525; + public const uint NewAvalonTownHall = 28543; + public const uint ScarletHold = 28542; + public const uint ChapelOfTheCrimsonFlame = 28544; + + //Fearnoevil + public const uint InjuredStormwindInfantry = 50047; + } + + struct Misc + { + //Quests6124 6129 + public const uint DespawnTime = 30000; + + //HodirsHelm + public const byte Say1 = 1; + public const byte Say2 = 2; + + //RedSnapperVeryTasty + public const uint ItemIdRedSnapper = 23614; + + //Acleansingsong + public const uint AreaIdBittertidelake = 4385; + public const uint AreaIdRiversheart = 4290; + public const uint AreaIdWintergraspriver = 4388; + + //Quest12372 + public const uint WhisperOnHitByForceWhisper = 1; + + //BurstAtTheSeams + public const uint QuestIdBurstAtTheSeams = 12690; + } + + class spell_generic_quest_update_entry_SpellScript : SpellScript + { + public spell_generic_quest_update_entry_SpellScript(SpellEffectName spellEffect, byte effIndex, uint originalEntry, uint newEntry, bool shouldAttack, uint despawnTime = 0) + { + _spellEffect = spellEffect; + _effIndex = effIndex; + _originalEntry = originalEntry; + _newEntry = newEntry; + _shouldAttack = shouldAttack; + _despawnTime = despawnTime; + } + + void HandleDummy(uint effIndex) + { + Creature creatureTarget = GetHitCreature(); + if (creatureTarget) + { + if (!creatureTarget.IsPet() && creatureTarget.GetEntry() == _originalEntry) + { + creatureTarget.UpdateEntry(_newEntry); + if (_shouldAttack && creatureTarget.IsAIEnabled) + creatureTarget.GetAI().AttackStart(GetCaster()); + + if (_despawnTime != 0) + creatureTarget.DespawnOrUnsummon(_despawnTime); + } + } + } + + public override void Register() + { + OnEffectHitTarget.Add(new EffectHandler(HandleDummy, _effIndex, _spellEffect)); + } + + SpellEffectName _spellEffect; + byte _effIndex; + uint _originalEntry; + uint _newEntry; + bool _shouldAttack; + uint _despawnTime; + } + + // http://www.wowhead.com/quest=55 Morbent Fel + // 8913 Sacred Cleansing + [Script] + class spell_q55_sacred_cleansing : SpellScriptLoader + { + public spell_q55_sacred_cleansing() : base("spell_q55_sacred_cleansing") { } + + public override SpellScript GetSpellScript() + { + return new spell_generic_quest_update_entry_SpellScript(SpellEffectName.Dummy, 1, CreatureIds.Morbent, CreatureIds.WeakenedMorbent, true); + } + } + + // 9712 - Thaumaturgy Channel + [Script] + class spell_q2203_thaumaturgy_channel : SpellScriptLoader + { + public spell_q2203_thaumaturgy_channel() : base("spell_q2203_thaumaturgy_channel") { } + + class spell_q2203_thaumaturgy_channel_AuraScript : AuraScript + { + public override bool Validate(SpellInfo spellInfo) + { + return ValidateSpellInfo(SpellIds.ThaumaturgyChannel); + } + + void HandleEffectPeriodic(AuraEffect aurEff) + { + PreventDefaultAction(); + Unit caster = GetCaster(); + if (caster) + caster.CastSpell(caster, SpellIds.ThaumaturgyChannel, false); + } + + public override void Register() + { + OnEffectPeriodic.Add(new EffectPeriodicHandler(HandleEffectPeriodic, 0, AuraType.PeriodicTriggerSpell)); + } + } + + public override AuraScript GetAuraScript() + { + return new spell_q2203_thaumaturgy_channel_AuraScript(); + } + } + + // http://www.wowhead.com/quest=5206 Marauders of Darrowshire + // 17271 Test Fetid Skull + [Script] + class spell_q5206_test_fetid_skull : SpellScriptLoader + { + public spell_q5206_test_fetid_skull() : base("spell_q5206_test_fetid_skull") { } + + class spell_q5206_test_fetid_skull_SpellScript : SpellScript + { + public override bool Load() + { + return GetCaster().IsTypeId(TypeId.Player); + } + + public override bool Validate(SpellInfo spellEntry) + { + return ValidateSpellInfo(SpellIds.CreateResonatingSkull, SpellIds.CreateBoneDust); + } + + void HandleDummy(uint effIndex) + { + Unit caster = GetCaster(); + uint spellId = RandomHelper.randChance(50) ? SpellIds.CreateResonatingSkull : SpellIds.CreateBoneDust; + caster.CastSpell(caster, spellId, true, null); + } + + public override void Register() + { + OnEffectHit.Add(new EffectHandler(HandleDummy, 0, SpellEffectName.Dummy)); + } + } + + public override SpellScript GetSpellScript() + { + return new spell_q5206_test_fetid_skull_SpellScript(); + } + } + + // http://www.wowhead.com/quest=6124 Curing the Sick (A) + // http://www.wowhead.com/quest=6129 Curing the Sick (H) + // 19512 Apply Salve + [Script] + class spell_q6124_6129_apply_salve : SpellScriptLoader + { + public spell_q6124_6129_apply_salve() : base("spell_q6124_6129_apply_salve") { } + + class spell_q6124_6129_apply_salve_SpellScript : SpellScript + { + public override bool Load() + { + return GetCaster().IsTypeId(TypeId.Player); + } + + void HandleDummy(uint effIndex) + { + Player caster = GetCaster().ToPlayer(); + if (GetCastItem()) + { + Creature creatureTarget = GetHitCreature(); + if (creatureTarget) + { + uint newEntry = 0; + switch (caster.GetTeam()) + { + case Team.Horde: + if (creatureTarget.GetEntry() == CreatureIds.SicklyGazelle) + newEntry = CreatureIds.CuredGazelle; + break; + case Team.Alliance: + if (creatureTarget.GetEntry() == CreatureIds.SicklyDeer) + newEntry = CreatureIds.CuredDeer; + break; + } + if (newEntry != 0) + { + creatureTarget.UpdateEntry(newEntry); + creatureTarget.DespawnOrUnsummon(Misc.DespawnTime); + caster.KilledMonsterCredit(newEntry); + } + } + } + } + + public override void Register() + { + OnEffectHitTarget.Add(new EffectHandler(HandleDummy, 0, SpellEffectName.Dummy)); + } + } + + public override SpellScript GetSpellScript() + { + return new spell_q6124_6129_apply_salve_SpellScript(); + } + } + + // http://www.wowhead.com/quest=10255 Testing the Antidote + // 34665 Administer Antidote + [Script] + class spell_q10255_administer_antidote : SpellScriptLoader + { + public spell_q10255_administer_antidote() : base("spell_q10255_administer_antidote") { } + + public override SpellScript GetSpellScript() + { + return new spell_generic_quest_update_entry_SpellScript(SpellEffectName.Dummy, 0, CreatureIds.Helboar, CreatureIds.Dreadtusk, true); + } + } + + // 43874 Scourge Mur'gul Camp: Force Shield Arcane Purple x3 + [Script] + class spell_q11396_11399_force_shield_arcane_purple_x3 : SpellScriptLoader + { + public spell_q11396_11399_force_shield_arcane_purple_x3() : base("spell_q11396_11399_force_shield_arcane_purple_x3") { } + + class spell_q11396_11399_force_shield_arcane_purple_x3_AuraScript : AuraScript + { + void HandleEffectApply(AuraEffect aurEff, AuraEffectHandleModes mode) + { + Unit target = GetTarget(); + target.SetFlag(UnitFields.Flags, UnitFlags.ImmuneToPc); + target.AddUnitState(UnitState.Root); + } + + void HandleEffectRemove(AuraEffect aurEff, AuraEffectHandleModes mode) + { + GetTarget().RemoveFlag(UnitFields.Flags, UnitFlags.ImmuneToPc); + } + + public override void Register() + { + OnEffectApply.Add(new EffectApplyHandler(HandleEffectApply, 0, AuraType.Dummy, AuraEffectHandleModes.Real)); + OnEffectRemove.Add(new EffectApplyHandler(HandleEffectRemove, 0, AuraType.Dummy, AuraEffectHandleModes.Real)); + } + } + + public override AuraScript GetAuraScript() + { + return new spell_q11396_11399_force_shield_arcane_purple_x3_AuraScript(); + } + } + + // 50133 Scourging Crystal Controller + [Script] + class spell_q11396_11399_scourging_crystal_controller : SpellScriptLoader + { + public spell_q11396_11399_scourging_crystal_controller() : base("spell_q11396_11399_scourging_crystal_controller") { } + + class spell_q11396_11399_scourging_crystal_controller_SpellScript : SpellScript + { + public override bool Validate(SpellInfo spellEntry) + { + return ValidateSpellInfo(SpellIds.ForceShieldArcanePurpleX3, SpellIds.ScourgingCrystalController); + } + + void HandleDummy(uint effIndex) + { + Unit target = GetExplTargetUnit(); + if (target) + if (target.IsTypeId(TypeId.Unit) && target.HasAura(SpellIds.ForceShieldArcanePurpleX3)) + // Make sure nobody else is channeling the same target + if (!target.HasAura(SpellIds.ScourgingCrystalController)) + GetCaster().CastSpell(target, SpellIds.ScourgingCrystalController, true, GetCastItem()); + } + + public override void Register() + { + OnEffectHitTarget.Add(new EffectHandler(HandleDummy, 0, SpellEffectName.Dummy)); + } + } + + public override SpellScript GetSpellScript() + { + return new spell_q11396_11399_scourging_crystal_controller_SpellScript(); + } + } + + // 43882 Scourging Crystal Controller Dummy + [Script] + class spell_q11396_11399_scourging_crystal_controller_dummy : SpellScriptLoader + { + public spell_q11396_11399_scourging_crystal_controller_dummy() : base("spell_q11396_11399_scourging_crystal_controller_dummy") { } + + class spell_q11396_11399_scourging_crystal_controller_dummy_SpellScript : SpellScript + { + public override bool Validate(SpellInfo spellEntry) + { + return ValidateSpellInfo(SpellIds.ForceShieldArcanePurpleX3); + } + + void HandleDummy(uint effIndex) + { + Unit target = GetHitUnit(); + if (target) + if (target.IsTypeId(TypeId.Unit)) + target.RemoveAurasDueToSpell(SpellIds.ForceShieldArcanePurpleX3); + } + + public override void Register() + { + OnEffectHitTarget.Add(new EffectHandler(HandleDummy, 0, SpellEffectName.Dummy)); + } + } + + public override SpellScript GetSpellScript() + { + return new spell_q11396_11399_scourging_crystal_controller_dummy_SpellScript(); + } + } + + // http://www.wowhead.com/quest=11515 Blood for Blood + // 44936 Quest - Fel Siphon Dummy + [Script] + class spell_q11515_fel_siphon_dummy : SpellScriptLoader + { + public spell_q11515_fel_siphon_dummy() : base("spell_q11515_fel_siphon_dummy") { } + + public override SpellScript GetSpellScript() + { + return new spell_generic_quest_update_entry_SpellScript(SpellEffectName.Dummy, 0, CreatureIds.FelbloodInitiate, CreatureIds.EmaciatedFelblood, true); + } + } + + // http://www.wowhead.com/quest=11587 Prison Break + // 45449 Arcane Prisoner Rescue + [Script] + class spell_q11587_arcane_prisoner_rescue : SpellScriptLoader + { + public spell_q11587_arcane_prisoner_rescue() : base("spell_q11587_arcane_prisoner_rescue") { } + + class spell_q11587_arcane_prisoner_rescue_SpellScript : SpellScript + { + public override bool Validate(SpellInfo spellEntry) + { + return ValidateSpellInfo(SpellIds.SummonArcanePrisonerMale, SpellIds.SummonArcanePrisonerFemale, SpellIds.ArcanePrisonerKillCredit); + } + + void HandleDummy(uint effIndex) + { + Unit caster = GetCaster(); + + Unit unitTarget = GetHitUnit(); + if (unitTarget) + { + uint spellId = SpellIds.SummonArcanePrisonerMale; + if (Convert.ToBoolean(RandomHelper.Rand32() % 2)) + spellId = SpellIds.SummonArcanePrisonerFemale; + caster.CastSpell(caster, spellId, true); + unitTarget.CastSpell(caster, SpellIds.ArcanePrisonerKillCredit, true); + } + } + + public override void Register() + { + OnEffectHitTarget.Add(new EffectHandler(HandleDummy, 0, SpellEffectName.Dummy)); + } + } + + public override SpellScript GetSpellScript() + { + return new spell_q11587_arcane_prisoner_rescue_SpellScript(); + } + } + + // http://www.wowhead.com/quest=11730 Master and Servant + // 46023 The Ultrasonic Screwdriver + [Script] + class spell_q11730_ultrasonic_screwdriver : SpellScriptLoader + { + public spell_q11730_ultrasonic_screwdriver() : base("spell_q11730_ultrasonic_screwdriver") { } + + class spell_q11730_ultrasonic_screwdriver_SpellScript : SpellScript + { + public override bool Load() + { + return GetCaster().IsTypeId(TypeId.Player) && GetCastItem(); + } + + public override bool Validate(SpellInfo spellEntry) + { + return ValidateSpellInfo(SpellIds.SummonScavengebot004a8, SpellIds.SummonSentrybot57k, SpellIds.SummonDefendotank66d, + SpellIds.SummonScavengebot005b6, SpellIds.Summon55dCollectatron, SpellIds.RobotKillCredit); + } + + void HandleDummy(uint effIndex) + { + Item castItem = GetCastItem(); + Unit caster = GetCaster(); + + Creature target = GetHitCreature(); + if (target) + { + uint spellId = 0; + switch (target.GetEntry()) + { + case CreatureIds.Scavengebot004a8: + spellId = SpellIds.SummonScavengebot004a8; + break; + case CreatureIds.Sentrybot57k: + spellId = SpellIds.SummonSentrybot57k; + break; + case CreatureIds.Defendotank66d: + spellId = SpellIds.SummonDefendotank66d; + break; + case CreatureIds.Scavengebot005b6: + spellId = SpellIds.SummonScavengebot005b6; + break; + case CreatureIds.Npc55dCollectatron: + spellId = SpellIds.Summon55dCollectatron; + break; + default: + return; + } + caster.CastSpell(caster, spellId, true, castItem); + caster.CastSpell(caster, SpellIds.RobotKillCredit, true); + target.DespawnOrUnsummon(); + } + } + + public override void Register() + { + OnEffectHitTarget.Add(new EffectHandler(HandleDummy, 0, SpellEffectName.Dummy)); + } + } + + public override SpellScript GetSpellScript() + { + return new spell_q11730_ultrasonic_screwdriver_SpellScript(); + } + } + + // http://www.wowhead.com/quest=12459 That Which Creates Can Also Destroy + // 49587 Seeds of Nature's Wrath + [Script] + class spell_q12459_seeds_of_natures_wrath : SpellScriptLoader + { + public spell_q12459_seeds_of_natures_wrath() : base("spell_q12459_seeds_of_natures_wrath") { } + + class spell_q12459_seeds_of_natures_wrath_SpellScript : SpellScript + { + void HandleDummy(uint effIndex) + { + Creature creatureTarget = GetHitCreature(); + if (creatureTarget) + { + uint uiNewEntry = 0; + switch (creatureTarget.GetEntry()) + { + case CreatureIds.ReanimatedFrostwyrm: + uiNewEntry = CreatureIds.WeakReanimatedFrostwyrm; + break; + case CreatureIds.Turgid: + uiNewEntry = CreatureIds.WeakTurgid; + break; + case CreatureIds.Deathgaze: + uiNewEntry = CreatureIds.WeakDeathgaze; + break; + } + if (uiNewEntry != 0) + creatureTarget.UpdateEntry(uiNewEntry); + } + } + + public override void Register() + { + OnEffectHitTarget.Add(new EffectHandler(HandleDummy, 0, SpellEffectName.Dummy)); + } + } + + public override SpellScript GetSpellScript() + { + return new spell_q12459_seeds_of_natures_wrath_SpellScript(); + } + } + + // http://www.wowhead.com/quest=12634 Some Make Lemonade, Some Make Liquor + // 51840 Despawn Fruit Tosser + [Script] + class spell_q12634_despawn_fruit_tosser : SpellScriptLoader + { + public spell_q12634_despawn_fruit_tosser() : base("spell_q12634_despawn_fruit_tosser") { } + + class spell_q12634_despawn_fruit_tosser_SpellScript : SpellScript + { + public override bool Validate(SpellInfo spellEntry) + { + return ValidateSpellInfo(SpellIds.BananasFallToGround, SpellIds.OrangeFallsToGround, SpellIds.PapayaFallsToGround, SpellIds.SummonAdventurousDwarf); + } + + void HandleDummy(uint effIndex) + { + uint spellId = SpellIds.BananasFallToGround; + switch (RandomHelper.URand(0, 3)) + { + case 1: + spellId = SpellIds.OrangeFallsToGround; + break; + case 2: + spellId = SpellIds.PapayaFallsToGround; + break; + } + // sometimes, if you're lucky, you get a dwarf + if (RandomHelper.randChance(5)) + spellId = SpellIds.SummonAdventurousDwarf; + GetCaster().CastSpell(GetCaster(), spellId, true, null); + } + + public override void Register() + { + OnEffectHit.Add(new EffectHandler(HandleDummy, 0, SpellEffectName.Dummy)); + } + } + + public override SpellScript GetSpellScript() + { + return new spell_q12634_despawn_fruit_tosser_SpellScript(); + } + } + + // http://www.wowhead.com/quest=12683 Burning to Help + // 52308 Take Sputum Sample + [Script] + class spell_q12683_take_sputum_sample : SpellScriptLoader + { + public spell_q12683_take_sputum_sample() : base("spell_q12683_take_sputum_sample") { } + + class spell_q12683_take_sputum_sample_SpellScript : SpellScript + { + void HandleDummy(uint effIndex) + { + uint reqAuraId = (uint)GetSpellInfo().GetEffect(1).CalcValue(); + + Unit caster = GetCaster(); + if (caster.HasAuraEffect(reqAuraId, 0)) + { + uint spellId = (uint)GetSpellInfo().GetEffect(0).CalcValue(); + caster.CastSpell(caster, spellId, true, null); + } + } + + public override void Register() + { + OnEffectHit.Add(new EffectHandler(HandleDummy, 0, SpellEffectName.Dummy)); + } + } + + public override SpellScript GetSpellScript() + { + return new spell_q12683_take_sputum_sample_SpellScript(); + } + } + + // http://www.wowhead.com/quest=12851 Going Bearback + // 54798 FLAMING Arrow Triggered Effect + [Script] + class spell_q12851_going_bearback : SpellScriptLoader + { + public spell_q12851_going_bearback() : base("spell_q12851_going_bearback") { } + + class spell_q12851_going_bearback_AuraScript : AuraScript + { + void HandleEffectApply(AuraEffect aurEff, AuraEffectHandleModes mode) + { + Unit caster = GetCaster(); + if (caster) + { + Unit target = GetTarget(); + // Already in fire + if (target.HasAura(SpellIds.Ablaze)) + return; + Player player = caster.GetCharmerOrOwnerPlayerOrPlayerItself(); + if (player) + { + switch (target.GetEntry()) + { + case CreatureIds.Frostworg: + target.CastSpell(player, SpellIds.FrostworgCredit, true); + target.CastSpell(target, SpellIds.Immolation, true); + target.CastSpell(target, SpellIds.Ablaze, true); + break; + case CreatureIds.Frostgiant: + target.CastSpell(player, SpellIds.FrostgiantCredit, true); + target.CastSpell(target, SpellIds.Immolation, true); + target.CastSpell(target, SpellIds.Ablaze, true); + break; + } + } + } + } + + public override void Register() + { + AfterEffectApply.Add(new EffectApplyHandler(HandleEffectApply, 0, AuraType.PeriodicDummy, AuraEffectHandleModes.RealOrReapplyMask)); + } + } + + public override AuraScript GetAuraScript() + { + return new spell_q12851_going_bearback_AuraScript(); + } + } + + // http://www.wowhead.com/quest=12937 Relief for the Fallen + // 55804 Healing Finished + [Script] + class spell_q12937_relief_for_the_fallen : SpellScriptLoader + { + public spell_q12937_relief_for_the_fallen() : base("spell_q12937_relief_for_the_fallen") { } + + class spell_q12937_relief_for_the_fallen_SpellScript : SpellScript + { + public override bool Load() + { + return GetCaster().IsTypeId(TypeId.Player); + } + + public override bool Validate(SpellInfo spellEntry) + { + return ValidateSpellInfo(SpellIds.TriggerAidOfTheEarthen); + } + + void HandleDummy(uint effIndex) + { + Player caster = GetCaster().ToPlayer(); + + Creature target = GetHitCreature(); + if (target) + { + caster.CastSpell(caster, SpellIds.TriggerAidOfTheEarthen, true, null); + caster.KilledMonsterCredit(CreatureIds.FallenEarthenDefender); + target.DespawnOrUnsummon(); + } + } + + public override void Register() + { + OnEffectHitTarget.Add(new EffectHandler(HandleDummy, 0, SpellEffectName.Dummy)); + } + } + + public override SpellScript GetSpellScript() + { + return new spell_q12937_relief_for_the_fallen_SpellScript(); + } + } + + [Script] + class spell_q10041_q10040_who_are_they : SpellScriptLoader + { + public spell_q10041_q10040_who_are_they() : base("spell_q10041_q10040_who_are_they") { } + + class spell_q10041_q10040_who_are_they_SpellScript : SpellScript + { + public override bool Validate(SpellInfo spellEntry) + { + return ValidateSpellInfo(SpellIds.MaleDisguise, SpellIds.FemaleDisguise, SpellIds.GenericDisguise); + } + + void HandleScript(uint effIndex) + { + PreventHitDefaultEffect(effIndex); + Player target = GetHitPlayer(); + if (target) + { + target.CastSpell(target, target.GetGender() == Gender.Male ? SpellIds.MaleDisguise : SpellIds.FemaleDisguise, true); + target.CastSpell(target, SpellIds.GenericDisguise, true); + } + } + + public override void Register() + { + OnEffectHitTarget.Add(new EffectHandler(HandleScript, 0, SpellEffectName.ScriptEffect)); + } + } + + public override SpellScript GetSpellScript() + { + return new spell_q10041_q10040_who_are_they_SpellScript(); + } + } + + // 8593 Symbol of life dummy + [Script] + class spell_symbol_of_life_dummy : SpellScriptLoader + { + public spell_symbol_of_life_dummy() : base("spell_symbol_of_life_dummy") { } + + class spell_symbol_of_life_dummy_SpellScript : SpellScript + { + void HandleDummy(uint effIndex) + { + Creature target = GetHitCreature(); + if (target) + { + if (target.HasAura(SpellIds.PermanentFeignDeath)) + { + target.RemoveAurasDueToSpell(SpellIds.PermanentFeignDeath); + target.SetUInt32Value(ObjectFields.DynamicFlags, 0); + target.SetUInt32Value(UnitFields.Flags2, 0); + target.SetHealth(target.GetMaxHealth() / 2); + target.SetPower(PowerType.Mana, (int)(target.GetMaxPower(PowerType.Mana) * 0.75f)); + } + } + } + + public override void Register() + { + OnEffectHitTarget.Add(new EffectHandler(HandleDummy, 0, SpellEffectName.Dummy)); + } + } + + public override SpellScript GetSpellScript() + { + return new spell_symbol_of_life_dummy_SpellScript(); + } + } + + // http://www.wowhead.com/quest=12659 Scalps! + // 52090 Ahunae's Knife + [Script] + class spell_q12659_ahunaes_knife : SpellScriptLoader + { + public spell_q12659_ahunaes_knife() : base("spell_q12659_ahunaes_knife") { } + + class spell_q12659_ahunaes_knife_SpellScript : SpellScript + { + public override bool Load() + { + return GetCaster().IsTypeId(TypeId.Player); + } + + void HandleDummy(uint effIndex) + { + Player caster = GetCaster().ToPlayer(); + + Creature target = GetHitCreature(); + if (target) + { + target.DespawnOrUnsummon(); + caster.KilledMonsterCredit(CreatureIds.ScalpsKcBunny); + } + } + + public override void Register() + { + OnEffectHitTarget.Add(new EffectHandler(HandleDummy, 0, SpellEffectName.Dummy)); + } + } + + public override SpellScript GetSpellScript() + { + return new spell_q12659_ahunaes_knife_SpellScript(); + } + } + + [Script] + class spell_q9874_liquid_fire : SpellScriptLoader + { + public spell_q9874_liquid_fire() : base("spell_q9874_liquid_fire") + { + } + + class spell_q9874_liquid_fire_SpellScript : SpellScript + { + public override bool Load() + { + return GetCaster().IsTypeId(TypeId.Player); + } + + void HandleDummy(uint effIndex) + { + Player caster = GetCaster().ToPlayer(); + + Creature target = GetHitCreature(); + if (target) + { + if (target && !target.HasAura(SpellIds.Flames)) + { + caster.KilledMonsterCredit(CreatureIds.VillagerKillCredit); + target.CastSpell(target, SpellIds.Flames, true); + target.DespawnOrUnsummon(60000); + } + } + } + + public override void Register() + { + OnEffectHitTarget.Add(new EffectHandler(HandleDummy, 0, SpellEffectName.Dummy)); + } + } + + public override SpellScript GetSpellScript() + { + return new spell_q9874_liquid_fire_SpellScript(); + } + } + + [Script] + class spell_q12805_lifeblood_dummy : SpellScriptLoader + { + public spell_q12805_lifeblood_dummy() : base("spell_q12805_lifeblood_dummy") + { + } + + class spell_q12805_lifeblood_dummy_SpellScript : SpellScript + { + public override bool Load() + { + return GetCaster().IsTypeId(TypeId.Player); + } + + void HandleScript(uint effIndex) + { + Player caster = GetCaster().ToPlayer(); + + Creature target = GetHitCreature(); + if (target) + { + caster.KilledMonsterCredit(CreatureIds.ShardKillCredit); + target.CastSpell(target, (uint)GetEffectValue(), true); + target.DespawnOrUnsummon(2000); + } + } + + public override void Register() + { + OnEffectHitTarget.Add(new EffectHandler(HandleScript, 0, SpellEffectName.ScriptEffect)); + } + } + + public override SpellScript GetSpellScript() + { + return new spell_q12805_lifeblood_dummy_SpellScript(); + } + } + + /* + http://www.wowhead.com/quest=13283 King of the Mountain + http://www.wowhead.com/quest=13280 King of the Mountain + 59643 Plant Horde Battle Standard + 4338 Plant Alliance Battle Standard + */ + [Script] + class spell_q13280_13283_plant_battle_standard : SpellScriptLoader + { + public spell_q13280_13283_plant_battle_standard() : base("spell_q13280_13283_plant_battle_standard") { } + + class spell_q13280_13283_plant_battle_standard_SpellScript : SpellScript + { + void HandleDummy(uint effIndex) + { + Unit caster = GetCaster(); + if (caster.IsVehicle()) + { + Unit player = caster.GetVehicleKit().GetPassenger(0); + if (player) + player.ToPlayer().KilledMonsterCredit(CreatureIds.KingOfTheMountaintKc); + } + } + + public override void Register() + { + OnEffectHit.Add(new EffectHandler(HandleDummy, 0, SpellEffectName.Dummy)); + } + } + + public override SpellScript GetSpellScript() + { + return new spell_q13280_13283_plant_battle_standard_SpellScript(); + } + } + + [Script] + class spell_q14112_14145_chum_the_water : SpellScriptLoader + { + public spell_q14112_14145_chum_the_water() : base("spell_q14112_14145_chum_the_water") { } + + class spell_q14112_14145_chum_the_water_SpellScript : SpellScript + { + public override bool Validate(SpellInfo spellEntry) + { + return ValidateSpellInfo(SpellIds.SummonAngryKvaldir, SpellIds.SummonNorthSeaMako, SpellIds.SummonNorthSeaThresher, SpellIds.SummonNorthSeaBlueShark); + } + + void HandleScriptEffect(uint effIndex) + { + Unit caster = GetCaster(); + caster.CastSpell(caster, RandomHelper.RAND(SpellIds.SummonAngryKvaldir, SpellIds.SummonNorthSeaMako, SpellIds.SummonNorthSeaThresher, SpellIds.SummonNorthSeaBlueShark)); + } + + public override void Register() + { + OnEffectHitTarget.Add(new EffectHandler(HandleScriptEffect, 0, SpellEffectName.ScriptEffect)); + } + } + + public override SpellScript GetSpellScript() + { + return new spell_q14112_14145_chum_the_water_SpellScript(); + } + } + + // http://old01.wowhead.com/quest=9452 - Red Snapper - Very Tasty! + [Script] + class spell_q9452_cast_net : SpellScriptLoader + { + public spell_q9452_cast_net() : base("spell_q9452_cast_net") { } + + class spell_q9452_cast_net_SpellScript : SpellScript + { + public override bool Load() + { + return GetCaster().IsTypeId(TypeId.Player); + } + + void HandleDummy(uint effIndex) + { + Player caster = GetCaster().ToPlayer(); + if (RandomHelper.randChance(66)) + caster.AddItem(Misc.ItemIdRedSnapper, 1); + else + caster.CastSpell(caster, SpellIds.FishedUpMurloc, true); + } + + void HandleActiveObject(uint effIndex) + { + PreventHitDefaultEffect(effIndex); + GetHitGObj().SetRespawnTime(RandomHelper.randChance(50) ? 2 * Time.Minute : 3 * Time.Minute); + GetHitGObj().Use(GetCaster()); + GetHitGObj().SetLootState(LootState.JustDeactivated); + } + + public override void Register() + { + OnEffectHit.Add(new EffectHandler(HandleDummy, 0, SpellEffectName.Dummy)); + OnEffectHitTarget.Add(new EffectHandler(HandleActiveObject, 1, SpellEffectName.ActivateObject)); + } + } + + public override SpellScript GetSpellScript() + { + return new spell_q9452_cast_net_SpellScript(); + } + } + + [Script] + class spell_q14076_14092_pound_drum : SpellScriptLoader + { + public spell_q14076_14092_pound_drum() : base("spell_q14076_14092_pound_drum") { } + + class spell_q14076_14092_pound_drum_SpellScript : SpellScript + { + void HandleSummon() + { + Unit caster = GetCaster(); + + if (RandomHelper.randChance(80)) + caster.CastSpell(caster, SpellIds.SummonDeepJormungar, true); + else + caster.CastSpell(caster, SpellIds.StormforgedMoleMachine, true); + } + + void HandleActiveObject(uint effIndex) + { + GetHitGObj().SetLootState(LootState.JustDeactivated); + } + + public override void Register() + { + OnCast.Add(new CastHandler(HandleSummon)); + OnEffectHitTarget.Add(new EffectHandler(HandleActiveObject, 0, SpellEffectName.ActivateObject)); + } + } + + public override SpellScript GetSpellScript() + { + return new spell_q14076_14092_pound_drum_SpellScript(); + } + } + + [Script] + class spell_q12279_cast_net : SpellScriptLoader + { + public spell_q12279_cast_net() : base("spell_q12279_cast_net") { } + + class spell_q12279_cast_net_SpellScript : SpellScript + { + void HandleActiveObject(uint effIndex) + { + GetHitGObj().SetLootState(LootState.JustDeactivated); + } + + public override void Register() + { + OnEffectHitTarget.Add(new EffectHandler(HandleActiveObject, 1, SpellEffectName.ActivateObject)); + } + } + + public override SpellScript GetSpellScript() + { + return new spell_q12279_cast_net_SpellScript(); + } + } + + [Script] + class spell_q12987_read_pronouncement : SpellScriptLoader + { + public spell_q12987_read_pronouncement() : base("spell_q12987_read_pronouncement") { } + + class spell_q12987_read_pronouncement_AuraScript : AuraScript + { + void OnApply(AuraEffect aurEff, AuraEffectHandleModes mode) + { + // player must cast kill credit and do emote text, according to sniff + Player target = GetTarget().ToPlayer(); + if (target) + { + Creature trigger = target.FindNearestCreature(CreatureIds.IceSpikeBunny, 25.0f); + if (trigger) + { + Global.CreatureTextMgr.SendChat(trigger, Misc.Say1, target, ChatMsg.Addon, Language.Addon, Game.CreatureTextRange.Normal, 0, Team.Other, false, target); + target.KilledMonsterCredit(CreatureIds.Killcredit); + Global.CreatureTextMgr.SendChat(trigger, Misc.Say2, target, ChatMsg.Addon, Language.Addon, Game.CreatureTextRange.Normal, 0, Team.Other, false, target); + } + } + } + + public override void Register() + { + AfterEffectApply.Add(new EffectApplyHandler(OnApply, 0, AuraType.None, AuraEffectHandleModes.Real)); + } + } + + public override AuraScript GetAuraScript() + { + return new spell_q12987_read_pronouncement_AuraScript(); + } + } + + [Script] + class spell_q12277_wintergarde_mine_explosion : SpellScriptLoader + { + public spell_q12277_wintergarde_mine_explosion() : base("spell_q12277_wintergarde_mine_explosion") { } + + class spell_q12277_wintergarde_mine_explosion_SpellScript : SpellScript + { + void HandleDummy(uint effIndex) + { + Creature unitTarget = GetHitCreature(); + if (unitTarget) + { + Unit caster = GetCaster(); + if (caster) + { + if (caster.IsTypeId(TypeId.Unit)) + { + Unit owner = caster.GetOwner(); + if (owner) + { + switch (unitTarget.GetEntry()) + { + case CreatureIds.UpperMineShaft: + caster.CastSpell(owner, SpellIds.UpperMineShaftCredit, true); + break; + case CreatureIds.LowerMineShaft: + caster.CastSpell(owner, SpellIds.LowerMineShaftCredit, true); + break; + default: + break; + } + } + } + } + } + } + + public override void Register() + { + OnEffectHitTarget.Add(new EffectHandler(HandleDummy, 0, SpellEffectName.Dummy)); + } + } + + public override SpellScript GetSpellScript() + { + return new spell_q12277_wintergarde_mine_explosion_SpellScript(); + } + } + + [Script] + class spell_q12066_bunny_kill_credit : SpellScriptLoader + { + public spell_q12066_bunny_kill_credit() : base("spell_q12066_bunny_kill_credit") { } + + class spell_q12066_bunny_kill_credit_SpellScript : SpellScript + { + void HandleDummy(uint effIndex) + { + Creature target = GetHitCreature(); + if (target) + target.CastSpell(GetCaster(), SpellIds.BunnyCreditBeam, false); + } + + public override void Register() + { + OnEffectHitTarget.Add(new EffectHandler(HandleDummy, 0, SpellEffectName.Dummy)); + } + } + + public override SpellScript GetSpellScript() + { + return new spell_q12066_bunny_kill_credit_SpellScript(); + } + } + + [Script] + class spell_q12735_song_of_cleansing : SpellScriptLoader + { + public spell_q12735_song_of_cleansing() : base("spell_q12735_song_of_cleansing") { } + + class spell_q12735_song_of_cleansing_SpellScript : SpellScript + { + void HandleScript(uint effIndex) + { + Unit caster = GetCaster(); + switch (caster.GetAreaId()) + { + case Misc.AreaIdBittertidelake: + caster.CastSpell(caster, SpellIds.SummonSpiritAtah); + break; + case Misc.AreaIdRiversheart: + caster.CastSpell(caster, SpellIds.SummonSpiritHakhalan); + break; + case Misc.AreaIdWintergraspriver: + caster.CastSpell(caster, SpellIds.SummonSpiritKoosu); + break; + default: + break; + } + } + + public override void Register() + { + OnEffectHitTarget.Add(new EffectHandler(HandleScript, 0, SpellEffectName.ScriptEffect)); + } + } + + public override SpellScript GetSpellScript() + { + return new spell_q12735_song_of_cleansing_SpellScript(); + } + } + + [Script] + class spell_q12372_cast_from_gossip_trigger : SpellScriptLoader + { + public spell_q12372_cast_from_gossip_trigger() : base("spell_q12372_cast_from_gossip_trigger") { } + + class spell_q12372_cast_from_gossip_trigger_SpellScript : SpellScript + { + void HandleScript(uint effIndex) + { + GetCaster().CastSpell(GetCaster(), SpellIds.SummonWyrmrestDefender, true); + } + + public override void Register() + { + OnEffectHitTarget.Add(new EffectHandler(HandleScript, 0, SpellEffectName.ScriptEffect)); + } + } + + public override SpellScript GetSpellScript() + { + return new spell_q12372_cast_from_gossip_trigger_SpellScript(); + } + } + + // http://www.wowhead.com/quest=12372 Defending Wyrmrest Temple + // 49370 - Wyrmrest Defender: Destabilize Azure Dragonshrine Effect + [Script] + class spell_q12372_destabilize_azure_dragonshrine_dummy : SpellScriptLoader + { + public spell_q12372_destabilize_azure_dragonshrine_dummy() : base("spell_q12372_destabilize_azure_dragonshrine_dummy") { } + + class spell_q12372_destabilize_azure_dragonshrine_dummy_SpellScript : SpellScript + { + void HandleDummy(uint effIndex) + { + if (GetHitCreature()) + { + Unit caster = GetOriginalCaster(); + if (caster) + { + Vehicle vehicle = caster.GetVehicleKit(); + if (vehicle) + { + Unit passenger = vehicle.GetPassenger(0); + if (passenger) + { + Player player = passenger.ToPlayer(); + if (player) + player.KilledMonsterCredit(CreatureIds.WyrmrestTempleCredit); + } + } + } + } + } + + public override void Register() + { + OnEffectHitTarget.Add(new EffectHandler(HandleDummy, 0, SpellEffectName.Dummy)); + } + } + + public override SpellScript GetSpellScript() + { + return new spell_q12372_destabilize_azure_dragonshrine_dummy_SpellScript(); + } + } + + // ID - 50287 Azure Dragon: On Death Force Cast Wyrmrest Defender to Whisper to Controller - Random (cast from Azure Dragons and Azure Drakes on death) + [Script] + class spell_q12372_azure_on_death_force_whisper : SpellScriptLoader + { + public spell_q12372_azure_on_death_force_whisper() : base("spell_q12372_azure_on_death_force_whisper") { } + + class spell_q12372_azure_on_death_force_whisper_SpellScript : SpellScript + { + void HandleScript(uint effIndex) + { + Creature defender = GetHitCreature(); + if (defender) + defender.GetAI().Talk(Misc.WhisperOnHitByForceWhisper, defender.GetCharmerOrOwner()); + } + + public override void Register() + { + OnEffectHitTarget.Add(new EffectHandler(HandleScript, 0, SpellEffectName.ScriptEffect)); + } + } + + public override SpellScript GetSpellScript() + { + return new spell_q12372_azure_on_death_force_whisper_SpellScript(); + } + } + + // 40113 Knockdown Fel Cannon: The Aggro Check Aura + [Script] + class spell_q11010_q11102_q11023_aggro_check_aura : SpellScriptLoader + { + public spell_q11010_q11102_q11023_aggro_check_aura() : base("spell_q11010_q11102_q11023_aggro_check_aura") { } + + class spell_q11010_q11102_q11023_aggro_check_aura_AuraScript : AuraScript + { + void HandleTriggerSpell(AuraEffect aurEff) + { + Unit target = GetTarget(); + if (target) + // On trigger proccing + target.CastSpell(target, SpellIds.AggroCheck); + } + + public override void Register() + { + OnEffectPeriodic.Add(new EffectPeriodicHandler(HandleTriggerSpell, 0, AuraType.PeriodicTriggerSpell)); + } + } + + public override AuraScript GetAuraScript() + { + return new spell_q11010_q11102_q11023_aggro_check_aura_AuraScript(); + } + } + + // 40112 Knockdown Fel Cannon: The Aggro Check + [Script] + class spell_q11010_q11102_q11023_aggro_check : SpellScriptLoader + { + public spell_q11010_q11102_q11023_aggro_check() : base("spell_q11010_q11102_q11023_aggro_check") { } + + class spell_q11010_q11102_q11023_aggro_check_SpellScript : SpellScript + { + void HandleDummy(uint effIndex) + { + Player playerTarget = GetHitPlayer(); + if (playerTarget) + // Check if found player target is on fly mount or using flying form + if (playerTarget.HasAuraType(AuraType.Fly) || playerTarget.HasAuraType(AuraType.ModIncreaseMountedFlightSpeed)) + playerTarget.CastSpell(playerTarget, SpellIds.FlakCannonTrigger, TriggerCastFlags.IgnoreCasterMountedOrOnVehicle); + } + + public override void Register() + { + OnEffectHitTarget.Add(new EffectHandler(HandleDummy, 0, SpellEffectName.Dummy)); + } + } + + public override SpellScript GetSpellScript() + { + return new spell_q11010_q11102_q11023_aggro_check_SpellScript(); + } + } + + // 40119 Knockdown Fel Cannon: The Aggro Burst + [Script] + class spell_q11010_q11102_q11023_aggro_burst : SpellScriptLoader + { + public spell_q11010_q11102_q11023_aggro_burst() : base("spell_q11010_q11102_q11023_aggro_burst") { } + + class spell_q11010_q11102_q11023_aggro_burst_AuraScript : AuraScript + { + void HandleEffectPeriodic(AuraEffect aurEff) + { + Unit target = GetTarget(); + if (target) + // On each tick cast Choose Loc to trigger summon + target.CastSpell(target, SpellIds.ChooseLoc); + } + + public override void Register() + { + OnEffectPeriodic.Add(new EffectPeriodicHandler(HandleEffectPeriodic, 0, AuraType.PeriodicDummy)); + } + } + + public override AuraScript GetAuraScript() + { + return new spell_q11010_q11102_q11023_aggro_burst_AuraScript(); + } + } + + // 40056 Knockdown Fel Cannon: Choose Loc + [Script] + class spell_q11010_q11102_q11023_choose_loc : SpellScriptLoader + { + public spell_q11010_q11102_q11023_choose_loc() : base("spell_q11010_q11102_q11023_choose_loc") { } + + class spell_q11010_q11102_q11023_choose_loc_SpellScript : SpellScript + { + void HandleDummy(uint effIndex) + { + Unit caster = GetCaster(); + // Check for player that is in 65 y range + List playerList = new List(); + AnyPlayerInObjectRangeCheck checker = new AnyPlayerInObjectRangeCheck(caster, 65.0f); + PlayerListSearcher searcher = new PlayerListSearcher(caster, playerList, checker); + Cell.VisitWorldObjects(caster, searcher, 65.0f); + foreach (var player in playerList) + { + // Check if found player target is on fly mount or using flying form + if (player.HasAuraType(AuraType.Fly) || player.HasAuraType(AuraType.ModIncreaseMountedFlightSpeed)) + // Summom Fel Cannon (bunny version) at found player + caster.SummonCreature(CreatureIds.FelCannon2, player.GetPositionX(), player.GetPositionY(), player.GetPositionZ()); + } + } + + public override void Register() + { + OnEffectHit.Add(new EffectHandler(HandleDummy, 0, SpellEffectName.Dummy)); + } + } + + public override SpellScript GetSpellScript() + { + return new spell_q11010_q11102_q11023_choose_loc_SpellScript(); + } + } + + // 39844 - Skyguard Blasting Charge + // 40160 - Throw Bomb + [Script] + class spell_q11010_q11102_q11023_q11008_check_fly_mount : SpellScriptLoader + { + public spell_q11010_q11102_q11023_q11008_check_fly_mount() : base("spell_q11010_q11102_q11023_q11008_check_fly_mount") { } + + class spell_q11010_q11102_q11023_q11008_check_fly_mount_SpellScript : SpellScript + { + SpellCastResult CheckRequirement() + { + Unit caster = GetCaster(); + // This spell will be cast only if caster has one of these auras + if (!(caster.HasAuraType(AuraType.Fly) || caster.HasAuraType(AuraType.ModIncreaseMountedFlightSpeed))) + return SpellCastResult.CantDoThatRightNow; + return SpellCastResult.SpellCastOk; + } + + public override void Register() + { + OnCheckCast.Add(new CheckCastHandler(CheckRequirement)); + } + } + + public override SpellScript GetSpellScript() + { + return new spell_q11010_q11102_q11023_q11008_check_fly_mount_SpellScript(); + } + } + + [Script] + class spell_q12527_zuldrak_rat : SpellScriptLoader + { + public spell_q12527_zuldrak_rat() : base("spell_q12527_zuldrak_rat") { } + + class spell_q12527_zuldrak_rat_SpellScript : SpellScript + { + public override bool Validate(SpellInfo spell) + { + return ValidateSpellInfo(SpellIds.SummonGorgedLurkingBasilisk); + } + + void HandleScriptEffect(uint effIndex) + { + if (GetHitAura() != null && GetHitAura().GetStackAmount() >= GetSpellInfo().StackAmount) + { + GetHitUnit().CastSpell((Unit)null, SpellIds.SummonGorgedLurkingBasilisk, true); + Creature basilisk = GetHitUnit().ToCreature(); + if (basilisk) + basilisk.DespawnOrUnsummon(); + } + } + + public override void Register() + { + OnEffectHitTarget.Add(new EffectHandler(HandleScriptEffect, 1, SpellEffectName.ScriptEffect)); + } + } + + public override SpellScript GetSpellScript() + { + return new spell_q12527_zuldrak_rat_SpellScript(); + } + } + + // 55368 - Summon Stefan + [Script] + class spell_q12661_q12669_q12676_q12677_q12713_summon_stefan : SpellScriptLoader + { + public spell_q12661_q12669_q12676_q12677_q12713_summon_stefan() : base("spell_q12661_q12669_q12676_q12677_q12713_summon_stefan") { } + + class spell_q12661_q12669_q12676_q12677_q12713_summon_stefan_SpellScript : SpellScript + { + void SetDest(ref SpellDestination dest) + { + // Adjust effect summon position + Position offset = new Position(0.0f, 0.0f, 20.0f, 0.0f); + dest.RelocateOffset(offset); + } + + public override void Register() + { + OnDestinationTargetSelect.Add(new DestinationTargetSelectHandler(SetDest, 0, Targets.DestCasterBack)); + } + } + + public override SpellScript GetSpellScript() + { + return new spell_q12661_q12669_q12676_q12677_q12713_summon_stefan_SpellScript(); + } + } + + [Script] + class spell_q12730_quenching_mist : SpellScriptLoader + { + public spell_q12730_quenching_mist() : base("spell_q12730_quenching_mist") { } + + class spell_q12730_quenching_mist_AuraScript : AuraScript + { + public override bool Validate(SpellInfo spellInfo) + { + return ValidateSpellInfo(SpellIds.FlickeringFlames); + } + + void HandleEffectPeriodic(AuraEffect aurEff) + { + GetTarget().RemoveAurasDueToSpell(SpellIds.FlickeringFlames); + } + + public override void Register() + { + OnEffectPeriodic.Add(new EffectPeriodicHandler(HandleEffectPeriodic, 0, AuraType.PeriodicHeal)); + } + } + + public override AuraScript GetAuraScript() + { + return new spell_q12730_quenching_mist_AuraScript(); + } + } + + // 13291 - Borrowed Technology/13292 - The Solution Solution /Daily//13239 - Volatility/13261 - Volatiliy /Daily// + [Script] + class spell_q13291_q13292_q13239_q13261_frostbrood_skytalon_grab_decoy : SpellScriptLoader + { + public spell_q13291_q13292_q13239_q13261_frostbrood_skytalon_grab_decoy() : base("spell_q13291_q13292_q13239_q13261_frostbrood_skytalon_grab_decoy") { } + + class spell_q13291_q13292_q13239_q13261_frostbrood_skytalon_grab_decoy_SpellScript : SpellScript + { + public override bool Validate(SpellInfo spell) + { + return ValidateSpellInfo(SpellIds.Ride); + } + + void HandleDummy(uint effIndex) + { + if (!GetHitCreature()) + return; + // TO DO: Being triggered is hack, but in checkcast it doesn't pass aurastate requirements. + // Beside that the decoy won't keep it's freeze animation state when enter. + GetHitCreature().CastSpell(GetCaster(), SpellIds.Ride, true); + } + + public override void Register() + { + OnEffectHitTarget.Add(new EffectHandler(HandleDummy, 0, SpellEffectName.Dummy)); + } + } + + public override SpellScript GetSpellScript() + { + return new spell_q13291_q13292_q13239_q13261_frostbrood_skytalon_grab_decoy_SpellScript(); + } + } + + // 59303 - Summon Frost Wyrm + [Script] + class spell_q13291_q13292_q13239_q13261_armored_decoy_summon_skytalon : SpellScriptLoader + { + public spell_q13291_q13292_q13239_q13261_armored_decoy_summon_skytalon() : base("spell_q13291_q13292_q13239_q13261_armored_decoy_summon_skytalon") { } + + class spell_q13291_q13292_q13239_q13261_armored_decoy_summon_skytalon_SpellScript : SpellScript + { + void SetDest(ref SpellDestination dest) + { + // Adjust effect summon position + Position offset = new Position(0.0f, 0.0f, 20.0f, 0.0f); + dest.RelocateOffset(offset); + } + + public override void Register() + { + OnDestinationTargetSelect.Add(new DestinationTargetSelectHandler(SetDest, 0, Targets.DestCasterBack)); + } + } + + public override SpellScript GetSpellScript() + { + return new spell_q13291_q13292_q13239_q13261_armored_decoy_summon_skytalon_SpellScript(); + } + } + + // 12601 - Second Chances: Summon Landgren's Soul Moveto Target Bunny + [Script] + class spell_q12847_summon_soul_moveto_bunny : SpellScriptLoader + { + public spell_q12847_summon_soul_moveto_bunny() : base("spell_q12847_summon_soul_moveto_bunny") { } + + class spell_q12847_summon_soul_moveto_bunny_SpellScript : SpellScript + { + void SetDest(ref SpellDestination dest) + { + // Adjust effect summon position + Position offset = new Position(0.0f, 0.0f, 2.5f, 0.0f); + dest.RelocateOffset(offset); + } + + public override void Register() + { + OnDestinationTargetSelect.Add(new DestinationTargetSelectHandler(SetDest, 0, Targets.DestCaster)); + } + } + + public override SpellScript GetSpellScript() + { + return new spell_q12847_summon_soul_moveto_bunny_SpellScript(); + } + } + + [Script] + class spell_q13011_bear_flank_master : SpellScriptLoader + { + public spell_q13011_bear_flank_master() : base("spell_q13011_bear_flank_master") { } + + class spell_q13011_bear_flank_master_SpellScript : SpellScript + { + public override bool Validate(SpellInfo spellInfo) + { + return ValidateSpellInfo(SpellIds.BearFlankMaster, SpellIds.CreateBearFlank); + } + + public override bool Load() + { + return GetCaster().IsTypeId(TypeId.Unit); + } + + void HandleScript(uint effIndex) + { + Player player = GetHitPlayer(); + if (player) + { + if (RandomHelper.randChance(50)) + { + Creature creature = GetCaster().ToCreature(); + player.CastSpell(creature, SpellIds.BearFlankFail); + creature.GetAI().Talk(0, player); + } + else + player.CastSpell(player, SpellIds.CreateBearFlank); + } + } + + public override void Register() + { + OnEffectHitTarget.Add(new EffectHandler(HandleScript, 0, SpellEffectName.ScriptEffect)); + } + } + + public override SpellScript GetSpellScript() + { + return new spell_q13011_bear_flank_master_SpellScript(); + } + } + + [Script] + class spell_q13086_cannons_target : SpellScriptLoader + { + public spell_q13086_cannons_target() : base("spell_q13086_cannons_target") { } + + class spell_q13086_cannons_target_SpellScript : SpellScript + { + public override bool Validate(SpellInfo spellInfo) + { + return ValidateSpellInfo((uint)spellInfo.GetEffect(0).CalcValue()); + } + + void HandleEffectDummy(uint effIndex) + { + WorldLocation pos = GetExplTargetDest(); + if (pos != null) + GetCaster().CastSpell(pos.GetPositionX(), pos.GetPositionY(), pos.GetPositionZ(), (uint)GetEffectValue(), true); + } + + public override void Register() + { + OnEffectHit.Add(new EffectHandler(HandleEffectDummy, 0, SpellEffectName.Dummy)); + } + } + + public override SpellScript GetSpellScript() + { + return new spell_q13086_cannons_target_SpellScript(); + } + } + + [Script] + class spell_q12690_burst_at_the_seams : SpellScriptLoader + { + public spell_q12690_burst_at_the_seams() : base("spell_q12690_burst_at_the_seams") { } + + class spell_q12690_burst_at_the_seams_SpellScript : SpellScript + { + public override bool Validate(SpellInfo spellInfo) + { + return ValidateSpellInfo(SpellIds.BurstAtTheSeams, SpellIds.BurstAtTheSeamsDmg, SpellIds.BurstAtTheSeamsDmg2, + SpellIds.BurstAtTheSeamsBone, SpellIds.BurstAtTheSeamsMeat, SpellIds.BurstAtTheSeamsBmeat); + } + + public override bool Load() + { + return GetCaster().IsTypeId(TypeId.Unit); + } + + void HandleKnockBack(uint effIndex) + { + Unit creature = GetHitCreature(); + if (creature) + { + Unit charmer = GetCaster().GetCharmerOrOwner(); + if (charmer) + { + Player player = charmer.ToPlayer(); + if (player) + { + if (player.GetQuestStatus(Misc.QuestIdBurstAtTheSeams) == QuestStatus.Incomplete) + { + creature.CastSpell(creature, SpellIds.BurstAtTheSeamsBone, true); + creature.CastSpell(creature, SpellIds.BurstAtTheSeamsMeat, true); + creature.CastSpell(creature, SpellIds.BurstAtTheSeamsBmeat, true); + creature.CastSpell(creature, SpellIds.BurstAtTheSeamsDmg, true); + creature.CastSpell(creature, SpellIds.BurstAtTheSeamsDmg2, true); + + player.CastSpell(player, SpellIds.DrakkariSkullcrusherCredit, true); + ushort count = player.GetReqKillOrCastCurrentCount(Misc.QuestIdBurstAtTheSeams, (int)CreatureIds.DrakkariChieftaink); + if ((count % 20) == 0) + player.CastSpell(player, SpellIds.SummonDrakkariChieftain, true); + } + } + } + } + } + + void HandleScript(uint effIndex) + { + GetCaster().ToCreature().DespawnOrUnsummon(2 * Time.InMilliseconds); + } + + public override void Register() + { + OnEffectHitTarget.Add(new EffectHandler(HandleKnockBack, 1, SpellEffectName.KnockBack)); + OnEffectHitTarget.Add(new EffectHandler(HandleScript, 0, SpellEffectName.ScriptEffect)); + } + } + + public override SpellScript GetSpellScript() + { + return new spell_q12690_burst_at_the_seams_SpellScript(); + } + } + + // 48682 - Escape from Silverbrook - Periodic Dummy + [Script] + class spell_q12308_escape_from_silverbrook : SpellScriptLoader + { + public spell_q12308_escape_from_silverbrook() : base("spell_q12308_escape_from_silverbrook") { } + + class spell_q12308_escape_from_silverbrook_SpellScript : SpellScript + { + public override bool Validate(SpellInfo spellInfo) + { + return ValidateSpellInfo(SpellIds.SummonWorgen); + } + + void HandleDummy(uint effIndex) + { + GetCaster().CastSpell(GetCaster(), SpellIds.SummonWorgen, true); + } + + public override void Register() + { + OnEffectHit.Add(new EffectHandler(HandleDummy, 0, SpellEffectName.Dummy)); + } + } + + public override SpellScript GetSpellScript() + { + return new spell_q12308_escape_from_silverbrook_SpellScript(); + } + } + + // 48681 - Summon Silverbrook Worgen + [Script] + class spell_q12308_escape_from_silverbrook_summon_worgen : SpellScriptLoader + { + public spell_q12308_escape_from_silverbrook_summon_worgen() : base("spell_q12308_escape_from_silverbrook_summon_worgen") { } + + class spell_q12308_escape_from_silverbrook_summon_worgen_SpellScript : SpellScript + { + void ModDest(ref SpellDestination dest) + { + float dist = GetSpellInfo().GetEffect(0).CalcRadius(GetCaster()); + float angle = RandomHelper.FRand(0.75f, 1.25f) * MathFunctions.PI; + + Position pos = GetCaster().GetNearPosition(dist, angle); + dest.Relocate(pos); + } + + public override void Register() + { + OnDestinationTargetSelect.Add(new DestinationTargetSelectHandler(ModDest, 0, Targets.DestCasterSummon)); + } + } + + public override SpellScript GetSpellScript() + { + return new spell_q12308_escape_from_silverbrook_summon_worgen_SpellScript(); + } + } + + // 51858 - Siphon of Acherus + [Script] + class spell_q12641_death_comes_from_on_high : SpellScriptLoader + { + public spell_q12641_death_comes_from_on_high() : base("spell_q12641_death_comes_from_on_high") { } + + class spell_q12641_death_comes_from_on_high_SpellScript : SpellScript + { + public override bool Validate(SpellInfo spellInfo) + { + return ValidateSpellInfo(SpellIds.ForgeCredit, SpellIds.TownHallCredit, SpellIds.ScarletHoldCredit, SpellIds.ChapelCredit); + } + + void HandleDummy(uint effIndex) + { + uint spellId = 0; + + switch (GetHitCreature().GetEntry()) + { + case CreatureIds.NewAvalonForge: + spellId = SpellIds.ForgeCredit; + break; + case CreatureIds.NewAvalonTownHall: + spellId = SpellIds.TownHallCredit; + break; + case CreatureIds.ScarletHold: + spellId = SpellIds.ScarletHoldCredit; + break; + case CreatureIds.ChapelOfTheCrimsonFlame: + spellId = SpellIds.ChapelCredit; + break; + default: + return; + } + + GetCaster().CastSpell((Unit)null, spellId, true); + } + + public override void Register() + { + OnEffectHitTarget.Add(new EffectHandler(HandleDummy, 0, SpellEffectName.Dummy)); + } + } + + public override SpellScript GetSpellScript() + { + return new spell_q12641_death_comes_from_on_high_SpellScript(); + } + } + + // 52694 - Recall Eye of Acherus + [Script] + class spell_q12641_recall_eye_of_acherus : SpellScriptLoader + { + public spell_q12641_recall_eye_of_acherus() : base("spell_q12641_recall_eye_of_acherus") { } + + class spell_q12641_recall_eye_of_acherus_SpellScript : SpellScript + { + void HandleDummy(uint effIndex) + { + Player player = GetCaster().GetCharmerOrOwner().ToPlayer(); + if (player) + { + player.StopCastingCharm(); + player.StopCastingBindSight(); + player.RemoveAura(SpellIds.TheEyeOfAcherus); + } + } + + public override void Register() + { + OnEffectHitTarget.Add(new EffectHandler(HandleDummy, 0, SpellEffectName.ScriptEffect)); + } + } + + public override SpellScript GetSpellScript() + { + return new spell_q12641_recall_eye_of_acherus_SpellScript(); + } + } + + // 51769 - Emblazon Runeblade + [Script] + class spell_q12619_emblazon_runeblade : SpellScriptLoader + { + public spell_q12619_emblazon_runeblade() : base("spell_q12619_emblazon_runeblade") { } + + class spell_q12619_emblazon_runeblade_AuraScript : AuraScript + { + void HandleEffectPeriodic(AuraEffect aurEff) + { + PreventDefaultAction(); + Unit caster = GetCaster(); + if (caster) + caster.CastSpell(caster, GetSpellInfo().GetEffect(aurEff.GetEffIndex()).TriggerSpell, true, null, aurEff); + } + + public override void Register() + { + OnEffectPeriodic.Add(new EffectPeriodicHandler(HandleEffectPeriodic, 0, AuraType.PeriodicTriggerSpell)); + } + } + + public override AuraScript GetAuraScript() + { + return new spell_q12619_emblazon_runeblade_AuraScript(); + } + } + + // 51770 - Emblazon Runeblade + [Script] + class spell_q12619_emblazon_runeblade_effect : SpellScriptLoader + { + public spell_q12619_emblazon_runeblade_effect() : base("spell_q12619_emblazon_runeblade_effect") { } + + class spell_q12619_emblazon_runeblade_effect_SpellScript : SpellScript + { + void HandleScript(uint effIndex) + { + GetCaster().CastSpell(GetCaster(), (uint)GetEffectValue(), false); + } + + public override void Register() + { + OnEffectHit.Add(new EffectHandler(HandleScript, 0, SpellEffectName.ScriptEffect)); + } + } + + public override SpellScript GetSpellScript() + { + return new spell_q12619_emblazon_runeblade_effect_SpellScript(); + } + } + + [Script] + class spell_q12919_gymers_grab : SpellScriptLoader + { + public spell_q12919_gymers_grab() : base("spell_q12919_gymers_grab") { } + + class spell_q12919_gymers_grab_SpellScript : SpellScript + { + public override bool Validate(SpellInfo spell) + { + return ValidateSpellInfo(SpellIds.RideGymer); + } + + void HandleScript(uint effIndex) + { + sbyte seatId = 2; + if (!GetHitCreature()) + return; + GetHitCreature().CastCustomSpell(SpellIds.RideGymer, SpellValueMod.BasePoint0, seatId, GetCaster(), true); + GetHitCreature().CastSpell(GetHitCreature(), SpellIds.Grabbed, true); + } + + public override void Register() + { + OnEffectHitTarget.Add(new EffectHandler(HandleScript, 0, SpellEffectName.ScriptEffect)); + } + } + + public override SpellScript GetSpellScript() + { + return new spell_q12919_gymers_grab_SpellScript(); + } + } + + [Script] + class spell_q12919_gymers_throw : SpellScriptLoader + { + public spell_q12919_gymers_throw() : base("spell_q12919_gymers_throw") { } + + class spell_q12919_gymers_throw_SpellScript : SpellScript + { + void HandleScript(uint effIndex) + { + Unit caster = GetCaster(); + if (caster.IsVehicle()) + { + Unit passenger = caster.GetVehicleKit().GetPassenger(1); + if (passenger) + { + passenger.ExitVehicle(); + caster.CastSpell(passenger, SpellIds.VargulExplosion, true); + } + } + } + + public override void Register() + { + OnEffectHitTarget.Add(new EffectHandler(HandleScript, 0, SpellEffectName.ScriptEffect)); + } + } + + public override SpellScript GetSpellScript() + { + return new spell_q12919_gymers_throw_SpellScript(); + } + } + + [Script] + class spell_q13400_illidan_kill_master : SpellScriptLoader + { + public spell_q13400_illidan_kill_master() : base("spell_q13400_illidan_kill_master") { } + + class spell_q13400_illidan_kill_master_SpellScript : SpellScript + { + public override bool Validate(SpellInfo spellInfo) + { + return ValidateSpellInfo(SpellIds.IllidanKillCredit); + } + + void HandleDummy(uint effIndex) + { + Unit caster = GetCaster(); + if (caster.IsVehicle()) + { + Unit passenger = caster.GetVehicleKit().GetPassenger(0); + if (passenger) + passenger.CastSpell(passenger, SpellIds.IllidanKillCredit, true); + } + } + + public override void Register() + { + OnEffectHitTarget.Add(new EffectHandler(HandleDummy, 0, SpellEffectName.Dummy)); + } + } + + public override SpellScript GetSpellScript() + { + return new spell_q13400_illidan_kill_master_SpellScript(); + } + } + + // 66744 - Make Player Destroy Totems + [Script] + class spell_q14100_q14111_make_player_destroy_totems : SpellScriptLoader + { + public spell_q14100_q14111_make_player_destroy_totems() : base("spell_q14100_q14111_make_player_destroy_totems") { } + + class spell_q14100_q14111_make_player_destroy_totems_SpellScript : SpellScript + { + public override bool Validate(SpellInfo spellInfo) + { + return ValidateSpellInfo(SpellIds.TotemOfTheEarthenRing); + } + + void HandleScriptEffect(uint effIndex) + { + Player player = GetHitPlayer(); + if (player) + player.CastSpell(player, SpellIds.TotemOfTheEarthenRing, TriggerCastFlags.FullMask); // ignore reagent cost, consumed by quest + } + + public override void Register() + { + OnEffectHitTarget.Add(new EffectHandler(HandleScriptEffect, 0, SpellEffectName.ScriptEffect)); + } + } + + public override SpellScript GetSpellScript() + { + return new spell_q14100_q14111_make_player_destroy_totems_SpellScript(); + } + } + + // 39238 - Fumping + [Script] + class spell_q10929_fumping : SpellScriptLoader + { + public spell_q10929_fumping() : base("spell_q10929_fumping") { } + + class spell_q10929_fumpingAuraScript : AuraScript + { + public override bool Validate(SpellInfo spell) + { + return ValidateSpellInfo(SpellIds.SummonSandGnome, SpellIds.SummonBoneSlicer); + } + + void HandleEffectRemove(AuraEffect aurEff, AuraEffectHandleModes mode) + { + if (GetTargetApplication().GetRemoveMode() != AuraRemoveMode.Expire) + return; + + Unit caster = GetCaster(); + if (caster) + caster.CastSpell(caster, RandomHelper.URand(SpellIds.SummonSandGnome, SpellIds.SummonBoneSlicer), true); + } + + public override void Register() + { + OnEffectRemove.Add(new EffectApplyHandler(HandleEffectRemove, 0, AuraType.Dummy, AuraEffectHandleModes.Real)); + } + } + + public override AuraScript GetAuraScript() + { + return new spell_q10929_fumpingAuraScript(); + } + } + + // 93072 - Get Our Boys Back Dummy + [Script] + class spell_q28813_get_our_boys_back_dummy : SpellScriptLoader + { + public spell_q28813_get_our_boys_back_dummy() : base("spell_q28813_get_our_boys_back_dummy") { } + + class spell_q28813_get_our_boys_back_dummy_SpellScript : SpellScript + { + public override bool Validate(SpellInfo spellInfo) + { + return ValidateSpellInfo(SpellIds.RenewedLife); + } + + void HandleDummyEffect() + { + Unit caster = GetCaster(); + Creature injuredStormwindInfantry = caster.FindNearestCreature(CreatureIds.InjuredStormwindInfantry, 5.0f, true); + if (injuredStormwindInfantry) + { + injuredStormwindInfantry.SetCreatorGUID(caster.GetGUID()); + injuredStormwindInfantry.CastSpell(injuredStormwindInfantry, SpellIds.RenewedLife, true); + } + } + + public override void Register() + { + OnCast.Add(new CastHandler(HandleDummyEffect)); + } + } + + public override SpellScript GetSpellScript() + { + return new spell_q28813_get_our_boys_back_dummy_SpellScript(); + } + } + + [Script] + class spell_q28813_set_health_random : SpellScriptLoader + { + public spell_q28813_set_health_random() : base("spell_q28813_set_health_random") { } + + class spell_q28813_set_health_random_SpellScript : SpellScript + { + void HandleDummyEffect() + { + Unit caster = GetCaster(); + caster.SetHealth(caster.CountPctFromMaxHealth(RandomHelper.IRand(3, 5) * 10)); + } + + public override void Register() + { + OnCast.Add(new CastHandler(HandleDummyEffect)); + } + } + + public override SpellScript GetSpellScript() + { + return new spell_q28813_set_health_random_SpellScript(); + } + } + + [Script] + class spell_q12414_hand_over_reins : SpellScriptLoader + { + public spell_q12414_hand_over_reins() : base("spell_q12414_hand_over_reins") { } + + class spell_q12414_hand_over_reins_SpellScript : SpellScript + { + void HandleScript(uint effIndex) + { + Creature caster = GetCaster().ToCreature(); + GetHitUnit().ExitVehicle(); + + if (caster) + caster.DespawnOrUnsummon(); + } + + public override void Register() + { + OnEffectHitTarget.Add(new EffectHandler(HandleScript, 1, SpellEffectName.ScriptEffect)); + } + } + + public override SpellScript GetSpellScript() + { + return new spell_q12414_hand_over_reins_SpellScript(); + } + } +} diff --git a/Scripts/Spells/Rogue.cs b/Scripts/Spells/Rogue.cs new file mode 100644 index 000000000..28d419b08 --- /dev/null +++ b/Scripts/Spells/Rogue.cs @@ -0,0 +1,217 @@ +/* + * Copyright (C) 2012-2017 CypherCore + * + * 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, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +using Framework.Constants; +using Game.Entities; +using Game.Scripting; +using Game.Spells; +using System.Collections.Generic; + +namespace Scripts.Spells.Rogue +{ + public struct SpellIds + { + public const uint BladeFlurryExtraAttack = 22482; + public const uint CheatDeathCooldown = 31231; + public const uint GlyphOfPreparation = 56819; + public const uint KillingSpree = 51690; + public const uint KillingSpreeTeleport = 57840; + public const uint KillingSpreeWeaponDmg = 57841; + public const uint KillingSpreeDmgBuff = 61851; + public const uint PreyOnTheWeak = 58670; + public const uint ShivTriggered = 5940; + public const uint TricksOfTheTradeDmgBoost = 57933; + public const uint TricksOfTheTradeProc = 59628; + public const uint SerratedBladesR1 = 14171; + public const uint Rupture = 1943; + public const uint HonorAmongThievesEnergize = 51699; + public const uint T52pSetBonus = 37169; + } + + [Script] // 51690 - Killing Spree + class spell_rog_killing_spree : SpellScriptLoader + { + public spell_rog_killing_spree() : base("spell_rog_killing_spree") { } + + class spell_rog_killing_spree_SpellScript : SpellScript + { + void FilterTargets(List targets) + { + if (targets.Empty() || GetCaster().GetVehicleBase()) + FinishCast(SpellCastResult.OutOfRange); + } + + void HandleDummy(uint effIndex) + { + Aura aura = GetCaster().GetAura(SpellIds.KillingSpree); + if (aura != null) + { + var script = aura.GetScript(nameof(spell_rog_killing_spree_AuraScript)); + if (script != null) + script.AddTarget(GetHitUnit()); + } + } + + public override void Register() + { + OnObjectAreaTargetSelect.Add(new ObjectAreaTargetSelectHandler(FilterTargets, 1, Targets.UnitDestAreaEnemy)); + OnEffectHitTarget.Add(new EffectHandler(HandleDummy, 1, SpellEffectName.Dummy)); + } + } + + public override SpellScript GetSpellScript() + { + return new spell_rog_killing_spree_SpellScript(); + } + + public class spell_rog_killing_spree_AuraScript : AuraScript + { + public override bool Validate(SpellInfo spellInfo) + { + return ValidateSpellInfo(SpellIds.KillingSpreeTeleport, SpellIds.KillingSpreeWeaponDmg, SpellIds.KillingSpreeDmgBuff); + } + + void HandleApply(AuraEffect aurEff, AuraEffectHandleModes mode) + { + GetTarget().CastSpell(GetTarget(), SpellIds.KillingSpreeDmgBuff, true); + } + + void HandleEffectPeriodic(AuraEffect aurEff) + { + while (!_targets.Empty()) + { + ObjectGuid guid = _targets.PickRandom(); + Unit target = Global.ObjAccessor.GetUnit(GetTarget(), guid); + if (target) + { + GetTarget().CastSpell(target, SpellIds.KillingSpreeTeleport, true); + GetTarget().CastSpell(target, SpellIds.KillingSpreeWeaponDmg, true); + break; + } + else + _targets.Remove(guid); + } + } + + void HandleRemove(AuraEffect aurEff, AuraEffectHandleModes mode) + { + GetTarget().RemoveAurasDueToSpell(SpellIds.KillingSpreeDmgBuff); + } + + public override void Register() + { + AfterEffectApply.Add(new EffectApplyHandler(HandleApply, 0, AuraType.Dummy, AuraEffectHandleModes.Real)); + OnEffectPeriodic.Add(new EffectPeriodicHandler(HandleEffectPeriodic, 0, AuraType.PeriodicDummy)); + AfterEffectRemove.Add(new EffectApplyHandler(HandleRemove, 0, AuraType.PeriodicDummy, AuraEffectHandleModes.Real)); + } + + public void AddTarget(Unit target) + { + _targets.Add(target.GetGUID()); + } + + List _targets = new List(); + } + + public override AuraScript GetAuraScript() + { + return new spell_rog_killing_spree_AuraScript(); + } + } + + [Script] // 70805 - Rogue T10 2P Bonus -- THIS SHOULD BE REMOVED WITH NEW PROC SYSTEM. + class spell_rog_t10_2p_bonus : SpellScriptLoader + { + public spell_rog_t10_2p_bonus() : base("spell_rog_t10_2p_bonus") { } + + class spell_rog_t10_2p_bonus_AuraScript : AuraScript + { + bool CheckProc(ProcEventInfo eventInfo) + { + return eventInfo.GetActor() == eventInfo.GetActionTarget(); + } + + public override void Register() + { + DoCheckProc.Add(new CheckProcHandler(CheckProc)); + } + } + + public override AuraScript GetAuraScript() + { + return new spell_rog_t10_2p_bonus_AuraScript(); + } + } + + [Script] // 2098 - Eviscerate + class spell_rog_eviscerate : SpellScriptLoader + { + public spell_rog_eviscerate() : base("spell_rog_eviscerate") { } + + class spell_rog_eviscerate_SpellScript : SpellScript + { + void CalculateDamage(uint effIndex) + { + int damagePerCombo = (int)(GetCaster().GetTotalAttackPowerValue(WeaponAttackType.BaseAttack) * 0.559f); + AuraEffect t5 = GetCaster().GetAuraEffect(SpellIds.T52pSetBonus, 0); + if (t5 != null) + damagePerCombo += t5.GetAmount(); + + SetEffectValue(GetEffectValue() + damagePerCombo * GetCaster().GetPower(PowerType.ComboPoints)); + } + + public override void Register() + { + OnEffectLaunchTarget.Add(new EffectHandler(CalculateDamage, 0, SpellEffectName.SchoolDamage)); + } + } + + public override SpellScript GetSpellScript() + { + return new spell_rog_eviscerate_SpellScript(); + } + } + + [Script] // 32645 - Envenom + class spell_rog_envenom : SpellScriptLoader + { + public spell_rog_envenom() : base("spell_rog_envenom") { } + + class spell_rog_envenom_SpellScript : SpellScript + { + void CalculateDamage(uint effIndex) + { + int damagePerCombo = (int)(GetCaster().GetTotalAttackPowerValue(WeaponAttackType.BaseAttack) * 0.417f); + AuraEffect t5 = GetCaster().GetAuraEffect(SpellIds.T52pSetBonus, 0); + if (t5 != null) + damagePerCombo += t5.GetAmount(); + + SetEffectValue(GetEffectValue() + damagePerCombo * GetCaster().GetPower(PowerType.ComboPoints)); + } + + public override void Register() + { + OnEffectLaunchTarget.Add(new EffectHandler(CalculateDamage, 0, SpellEffectName.SchoolDamage)); + } + } + + public override SpellScript GetSpellScript() + { + return new spell_rog_envenom_SpellScript(); + } + } +} diff --git a/Scripts/Spells/Shaman.cs b/Scripts/Spells/Shaman.cs new file mode 100644 index 000000000..733effdad --- /dev/null +++ b/Scripts/Spells/Shaman.cs @@ -0,0 +1,1188 @@ +/* + * Copyright (C) 2012-2017 CypherCore + * + * 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, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +using Framework.Constants; +using Framework.Dynamic; +using Game.Entities; +using Game.Maps; +using Game.Scripting; +using Game.Spells; +using System; +using System.Collections.Generic; + +namespace Scripts.Spells.Shaman +{ + struct SpellIds + { + public const uint AncestralGuidance = 108281; + public const uint AncestralGuidanceHeal = 114911; + public const uint ChainedHeal = 70809; + public const uint CrashLightningCleave = 187878; + public const uint EarthShieldHeal = 204290; + public const uint EarthenRagePassive = 170374; + public const uint EarthenRagePeriodic = 170377; + public const uint EarthenRageDamage = 170379; + public const uint Electrified = 64930; + public const uint ElementalBlastCrit = 118522; + public const uint ElementalBlastHaste = 173183; + public const uint ElementalBlastMastery = 173184; + public const uint ElementalMastery = 16166; + public const uint EnergySurge = 40465; + public const uint Exhaustion = 57723; + public const uint FireNovaTriggered = 8349; + public const uint FlameShock = 8050; + public const uint FlameShockMaelstrom = 188389; + public const uint FlametongueAttack = 10444; + public const uint GatheringStorms = 198299; + public const uint GatheringStormsBuff = 198300; + public const uint HighTide = 157154; + public const uint ItemLightningShield = 23552; + public const uint ItemLightningShieldDamage = 27635; + public const uint ItemManaSurge = 23571; + public const uint LavaBurst = 51505; + public const uint LavaBurstBonusDamage = 71824; + public const uint LavaLashSpreadFlameShock = 105792; + public const uint LavaSurge = 77762; + public const uint PathOfFlamesSpread = 210621; + public const uint PathOfFlamesTalent = 201909; + public const uint PowerSurge = 40466; + public const uint Sated = 57724; + public const uint TidalWaves = 53390; + public const uint TotemicPowerMp5 = 28824; + public const uint TotemicPowerSpellPower = 28825; + public const uint TotemicPowerAttackPower = 28826; + public const uint TotemicPowerArmor = 28827; + public const uint WindfuryAttack = 25504; + + //Misc + public const uint HunterInsanity = 95809; + public const uint MageTemporalDisplacement = 80354; + public const uint PetNetherwindsFatigued = 160455; + } + + [Script] // 108281 - Ancestral Guidance + class spell_sha_ancestral_guidance : SpellScriptLoader + { + public spell_sha_ancestral_guidance() : base("spell_sha_ancestral_guidance") { } + + class spell_sha_ancestral_guidance_AuraScript : AuraScript + { + public override bool Validate(SpellInfo spellInfo) + { + return ValidateSpellInfo(SpellIds.AncestralGuidanceHeal); + } + + bool CheckProc(ProcEventInfo eventInfo) + { + if (eventInfo.GetHealInfo().GetSpellInfo().Id == SpellIds.AncestralGuidanceHeal) + return false; + return true; + } + + void HandleEffectProc(AuraEffect aurEff, ProcEventInfo eventInfo) + { + PreventDefaultAction(); + int bp0 = MathFunctions.CalculatePct((int)eventInfo.GetDamageInfo().GetDamage(), aurEff.GetAmount()); + if (bp0 != 0) + eventInfo.GetActor().CastCustomSpell(SpellIds.AncestralGuidanceHeal, SpellValueMod.BasePoint0, bp0, eventInfo.GetActor(), true, null, aurEff); + } + + public override void Register() + { + DoCheckProc.Add(new CheckProcHandler(CheckProc)); + OnEffectProc.Add(new EffectProcHandler(HandleEffectProc, 0, AuraType.Dummy)); + } + } + + public override AuraScript GetAuraScript() + { + return new spell_sha_ancestral_guidance_AuraScript(); + } + } + + [Script] // 114911 - Ancestral Guidance Heal + class spell_sha_ancestral_guidance_heal : SpellScriptLoader + { + public spell_sha_ancestral_guidance_heal() : base("spell_sha_ancestral_guidance_heal") { } + + class spell_sha_ancestral_guidance_heal_SpellScript : SpellScript + { + public override bool Validate(SpellInfo spellInfo) + { + return ValidateSpellInfo(SpellIds.AncestralGuidance); + } + + void ResizeTargets(List targets) + { + targets.Resize(3); + } + + public override void Register() + { + OnObjectAreaTargetSelect.Add(new ObjectAreaTargetSelectHandler(ResizeTargets, 0, Targets.UnitDestAreaAlly)); + } + } + + public override SpellScript GetSpellScript() + { + return new spell_sha_ancestral_guidance_heal_SpellScript(); + } + } + + [Script] // 2825 - Bloodlust + class spell_sha_bloodlust : SpellScriptLoader + { + public spell_sha_bloodlust() : base("spell_sha_bloodlust") { } + + class spell_sha_bloodlust_SpellScript : SpellScript + { + public override bool Validate(SpellInfo spellInfo) + { + return ValidateSpellInfo(SpellIds.Sated, SpellIds.HunterInsanity, SpellIds.MageTemporalDisplacement, SpellIds.PetNetherwindsFatigued); + } + + void RemoveInvalidTargets(List targets) + { + targets.RemoveAll(new UnitAuraCheck(true, SpellIds.Sated)); + targets.RemoveAll(new UnitAuraCheck(true, SpellIds.HunterInsanity)); + targets.RemoveAll(new UnitAuraCheck(true, SpellIds.MageTemporalDisplacement)); + } + + void ApplyDebuff() + { + Unit target = GetHitUnit(); + if (target) + target.CastSpell(target, SpellIds.Sated, true); + } + + public override void Register() + { + OnObjectAreaTargetSelect.Add(new ObjectAreaTargetSelectHandler(RemoveInvalidTargets, 0, Targets.UnitCasterAreaRaid)); + OnObjectAreaTargetSelect.Add(new ObjectAreaTargetSelectHandler(RemoveInvalidTargets, 1, Targets.UnitCasterAreaRaid)); + AfterHit.Add(new HitHandler(ApplyDebuff)); + } + } + + public override SpellScript GetSpellScript() + { + return new spell_sha_bloodlust_SpellScript(); + } + } + + [Script] // 187874 - Crash Lightning + class spell_sha_crash_lightning : SpellScriptLoader + { + public spell_sha_crash_lightning() : base("spell_sha_crash_lightning") { } + + class spell_sha_crash_lightning_SpellScript : SpellScript + { + public override bool Validate(SpellInfo spellInfo) + { + return ValidateSpellInfo(SpellIds.CrashLightningCleave); + } + + void CountTargets(List targets) + { + _targetsHit = targets.Count; + } + + void TriggerCleaveBuff() + { + if (_targetsHit >= 2) + GetCaster().CastSpell(GetCaster(), SpellIds.CrashLightningCleave, true); + + AuraEffect gatheringStorms = GetCaster().GetAuraEffect(SpellIds.GatheringStorms, 0); + if (gatheringStorms != null) + GetCaster().CastCustomSpell(SpellIds.GatheringStormsBuff, SpellValueMod.BasePoint0, (int)(gatheringStorms.GetAmount() * _targetsHit), GetCaster(), true); + } + + public override void Register() + { + OnObjectAreaTargetSelect.Add(new ObjectAreaTargetSelectHandler(CountTargets, 0, Targets.UnitConeEnemy104)); + AfterCast.Add(new CastHandler(TriggerCleaveBuff)); + } + + int _targetsHit = 0; + } + + public override SpellScript GetSpellScript() + { + return new spell_sha_crash_lightning_SpellScript(); + } + } + + [Script] // 204288 - Earth Shield + class spell_sha_earth_shield : SpellScriptLoader + { + public spell_sha_earth_shield() : base("spell_sha_earth_shield") { } + + class spell_sha_earth_shield_AuraScript : AuraScript + { + public override bool Validate(SpellInfo spellInfo) + { + return ValidateSpellInfo(SpellIds.EarthShieldHeal); + } + + bool CheckProc(ProcEventInfo eventInfo) + { + if (eventInfo.GetDamageInfo() == null || !HasEffect(1) || eventInfo.GetDamageInfo().GetDamage() < GetTarget().CountPctFromMaxHealth(GetEffect(1).GetAmount())) + return false; + return true; + } + + void HandleProc(AuraEffect aurEff, ProcEventInfo eventInfo) + { + PreventDefaultAction(); + + GetTarget().CastSpell(GetTarget(), SpellIds.EarthShieldHeal, true, null, aurEff, GetCasterGUID()); + } + + public override void Register() + { + DoCheckProc.Add(new CheckProcHandler(CheckProc)); + OnEffectProc.Add(new EffectProcHandler(HandleProc, 1, AuraType.Dummy)); + } + } + + public override AuraScript GetAuraScript() + { + return new spell_sha_earth_shield_AuraScript(); + } + } + + [Script] // 170374 - Earthen Rage (Passive) + class spell_sha_earthen_rage_passive : SpellScriptLoader + { + public spell_sha_earthen_rage_passive() : base("spell_sha_earthen_rage_passive") { } + + public class spell_sha_earthen_rage_passive_AuraScript : AuraScript + { + public override bool Validate(SpellInfo spellInfo) + { + return ValidateSpellInfo(SpellIds.EarthenRagePeriodic, SpellIds.EarthenRageDamage); + } + + void HandleEffectProc(AuraEffect aurEff, ProcEventInfo eventInfo) + { + PreventDefaultAction(); + _procTargetGuid = eventInfo.GetProcTarget().GetGUID(); + eventInfo.GetActor().CastSpell(eventInfo.GetActor(), SpellIds.EarthenRagePeriodic, true); + } + + public override void Register() + { + OnEffectProc.Add(new EffectProcHandler(HandleEffectProc, 0, AuraType.Dummy)); + } + + public ObjectGuid GetProcTargetGuid() + { + return _procTargetGuid; + } + + ObjectGuid _procTargetGuid; + } + + public override AuraScript GetAuraScript() + { + return new spell_sha_earthen_rage_passive_AuraScript(); + } + } + + [Script] // 170377 - Earthen Rage (Proc Aura) + class spell_sha_earthen_rage_proc_aura : SpellScriptLoader + { + public spell_sha_earthen_rage_proc_aura() : base("spell_sha_earthen_rage_proc_aura") { } + + class spell_sha_earthen_rage_proc_aura_AuraScript : AuraScript + { + public override bool Validate(SpellInfo spellInfo) + { + return ValidateSpellInfo(SpellIds.EarthenRagePassive, SpellIds.EarthenRageDamage); + } + + void HandleEffectPeriodic(AuraEffect aurEff) + { + PreventDefaultAction(); + Aura aura = GetCaster().GetAura(SpellIds.EarthenRagePassive); + if (aura != null) + { + var earthen_rage_script = aura.GetScript(nameof(spell_sha_earthen_rage_passive)); + if (earthen_rage_script != null) + { + Unit procTarget = Global.ObjAccessor.GetUnit(GetCaster(), earthen_rage_script.GetProcTargetGuid()); + if (procTarget) + GetTarget().CastSpell(procTarget, SpellIds.EarthenRageDamage, true); + + } + } + } + + public override void Register() + { + OnEffectPeriodic.Add(new EffectPeriodicHandler(HandleEffectPeriodic, 0, AuraType.PeriodicDummy)); + } + } + + public override AuraScript GetAuraScript() + { + return new spell_sha_earthen_rage_proc_aura_AuraScript(); + } + } + + [Script] // 117014 - Elemental Blast + class spell_sha_elemental_blast : SpellScriptLoader + { + public spell_sha_elemental_blast() : base("spell_sha_elemental_blast") { } + + class spell_sha_elemental_blast_SpellScript : SpellScript + { + public override bool Validate(SpellInfo spellInfo) + { + return ValidateSpellInfo(SpellIds.ElementalBlastCrit, SpellIds.ElementalBlastHaste, SpellIds.ElementalBlastMastery); + } + + public override bool Load() + { + return GetCaster().IsTypeId(TypeId.Player); + } + + void TriggerBuff() + { + Player caster = GetCaster().ToPlayer(); + uint spellId = RandomHelper.RAND(SpellIds.ElementalBlastCrit, SpellIds.ElementalBlastHaste, SpellIds.ElementalBlastMastery); + + caster.CastSpell(caster, spellId, TriggerCastFlags.FullMask); + } + + public override void Register() + { + AfterCast.Add(new CastHandler(TriggerBuff)); + } + } + + public override SpellScript GetSpellScript() + { + return new spell_sha_elemental_blast_SpellScript(); + } + } + + [Script] // 1535 Fire Nova + class spell_sha_fire_nova : SpellScriptLoader + { + public spell_sha_fire_nova() : base("spell_sha_fire_nova") { } + + class spell_sha_fire_nova_SpellScript : SpellScript + { + void HandleDummy(uint effIndex) + { + Unit target = GetHitUnit(); + if (target) + if (target.HasAura(SpellIds.FlameShock)) + GetCaster().CastSpell(target, SpellIds.FireNovaTriggered, true); + } + + public override void Register() + { + OnEffectHitTarget.Add(new EffectHandler(HandleDummy, 0, SpellEffectName.Dummy)); + } + } + + public override SpellScript GetSpellScript() + { + return new spell_sha_fire_nova_SpellScript(); + } + } + + [Script] // 194084 - Flametongue + class spell_sha_flametongue : SpellScriptLoader + { + public spell_sha_flametongue() : base("spell_sha_flametongue") { } + + class spell_sha_flametongue_AuraScript : AuraScript + { + public override bool Validate(SpellInfo spellInfo) + { + return ValidateSpellInfo(SpellIds.FlametongueAttack); + } + + void HandleEffectProc(AuraEffect aurEff, ProcEventInfo eventInfo) + { + PreventDefaultAction(); + + Unit attacker = eventInfo.GetActor(); + int damage = (int)(attacker.GetTotalAttackPowerValue(WeaponAttackType.BaseAttack) * 0.12f / 2600 * attacker.GetBaseAttackTime(WeaponAttackType.BaseAttack)); + attacker.CastCustomSpell(SpellIds.FlametongueAttack, SpellValueMod.BasePoint0, damage, eventInfo.GetActionTarget(), TriggerCastFlags.FullMask, null, aurEff); + } + + public override void Register() + { + OnEffectProc.Add(new EffectProcHandler(HandleEffectProc, 0, AuraType.Dummy)); + } + } + + public override AuraScript GetAuraScript() + { + return new spell_sha_flametongue_AuraScript(); + } + } + + [Script] // 52042 - Healing Stream Totem + class spell_sha_healing_stream_totem_heal : SpellScriptLoader + { + public spell_sha_healing_stream_totem_heal() : base("spell_sha_healing_stream_totem_heal") { } + + class spell_sha_healing_stream_totem_heal_SpellScript : SpellScript + { + void SelectTargets(List targets) + { + targets.RemoveAll(target => + { + return !target.ToUnit() || target.ToUnit().IsFullHealth(); + }); + + targets.RandomResize(1); + + if (targets.Empty()) + targets.Add(GetOriginalCaster()); + } + + public override void Register() + { + OnObjectAreaTargetSelect.Add(new ObjectAreaTargetSelectHandler(SelectTargets, 0, Targets.UnitDestAreaAlly)); + } + } + + public override SpellScript GetSpellScript() + { + return new spell_sha_healing_stream_totem_heal_SpellScript(); + } + } + + [Script] // 32182 - Heroism + class spell_sha_heroism : SpellScriptLoader + { + public spell_sha_heroism() : base("spell_sha_heroism") { } + + class spell_sha_heroism_SpellScript : SpellScript + { + public override bool Validate(SpellInfo spellInfo) + { + return ValidateSpellInfo(SpellIds.Exhaustion, SpellIds.HunterInsanity, SpellIds.MageTemporalDisplacement, SpellIds.PetNetherwindsFatigued); + } + + void RemoveInvalidTargets(List targets) + { + targets.RemoveAll(new UnitAuraCheck(true, SpellIds.Exhaustion)); + targets.RemoveAll(new UnitAuraCheck(true, SpellIds.HunterInsanity)); + targets.RemoveAll(new UnitAuraCheck(true, SpellIds.MageTemporalDisplacement)); + } + + void ApplyDebuff() + { + Unit target = GetHitUnit(); + if (target) + target.CastSpell(target, SpellIds.Exhaustion, true); + } + + public override void Register() + { + OnObjectAreaTargetSelect.Add(new ObjectAreaTargetSelectHandler(RemoveInvalidTargets, 0, Targets.UnitCasterAreaRaid)); + OnObjectAreaTargetSelect.Add(new ObjectAreaTargetSelectHandler(RemoveInvalidTargets, 1, Targets.UnitCasterAreaRaid)); + AfterHit.Add(new HitHandler(ApplyDebuff)); + } + } + + public override SpellScript GetSpellScript() + { + return new spell_sha_heroism_SpellScript(); + } + } + + [Script] // 23551 - Lightning Shield T2 Bonus + class spell_sha_item_lightning_shield : SpellScriptLoader + { + public spell_sha_item_lightning_shield() : base("spell_sha_item_lightning_shield") { } + + class spell_sha_item_lightning_shield_AuraScript : AuraScript + { + public override bool Validate(SpellInfo spellInfo) + { + return ValidateSpellInfo(SpellIds.ItemLightningShield); + } + + void HandleProc(AuraEffect aurEff, ProcEventInfo eventInfo) + { + PreventDefaultAction(); + GetTarget().CastSpell(eventInfo.GetProcTarget(), SpellIds.ItemLightningShield, true, null, aurEff); + } + + public override void Register() + { + OnEffectProc.Add(new EffectProcHandler(HandleProc, 0, AuraType.ProcTriggerSpell)); + } + } + + public override AuraScript GetAuraScript() + { + return new spell_sha_item_lightning_shield_AuraScript(); + } + } + + [Script] // 23552 - Lightning Shield T2 Bonus + class spell_sha_item_lightning_shield_trigger : SpellScriptLoader + { + public spell_sha_item_lightning_shield_trigger() : base("spell_sha_item_lightning_shield_trigger") { } + + class spell_sha_item_lightning_shield_trigger_AuraScript : AuraScript + { + public override bool Validate(SpellInfo spellInfo) + { + return ValidateSpellInfo(SpellIds.ItemLightningShieldDamage); + } + + void HandleProc(AuraEffect aurEff, ProcEventInfo eventInfo) + { + PreventDefaultAction(); + GetTarget().CastSpell(GetTarget(), SpellIds.ItemLightningShieldDamage, true, null, aurEff); + } + + public override void Register() + { + OnEffectProc.Add(new EffectProcHandler(HandleProc, 0, AuraType.ProcTriggerSpell)); + } + } + + public override AuraScript GetAuraScript() + { + return new spell_sha_item_lightning_shield_trigger_AuraScript(); + } + } + + [Script] // 23572 - Mana Surge + class spell_sha_item_mana_surge : SpellScriptLoader + { + public spell_sha_item_mana_surge() : base("spell_sha_item_mana_surge") { } + + class spell_sha_item_mana_surge_AuraScript : AuraScript + { + public override bool Validate(SpellInfo spellInfo) + { + return ValidateSpellInfo(SpellIds.ItemManaSurge); + } + + bool CheckProc(ProcEventInfo eventInfo) + { + return eventInfo.GetSpellInfo() != null; + } + + void HandleProc(AuraEffect aurEff, ProcEventInfo eventInfo) + { + PreventDefaultAction(); + SpellInfo spellInfo = eventInfo.GetSpellInfo(); + if (spellInfo == null) + return; + + var costs = eventInfo.GetDamageInfo().GetSpellInfo().CalcPowerCost(GetTarget(), eventInfo.GetDamageInfo().GetSchoolMask()); + var m = costs.Find(cost => cost.Power == PowerType.Mana); + if (m != null) + { + int mana = MathFunctions.CalculatePct(m.Amount, 35); + if (mana > 0) + GetTarget().CastCustomSpell(SpellIds.ItemManaSurge, SpellValueMod.BasePoint0, mana, GetTarget(), true, null, aurEff); + } + } + + public override void Register() + { + DoCheckProc.Add(new CheckProcHandler(CheckProc)); + OnEffectProc.Add(new EffectProcHandler(HandleProc, 0, AuraType.ProcTriggerSpell)); + } + } + + public override AuraScript GetAuraScript() + { + return new spell_sha_item_mana_surge_AuraScript(); + } + } + + [Script] // 40463 - Shaman Tier 6 Trinket + class spell_sha_item_t6_trinket : SpellScriptLoader + { + public spell_sha_item_t6_trinket() : base("spell_sha_item_t6_trinket") { } + + class spell_sha_item_t6_trinket_AuraScript : AuraScript + { + public override bool Validate(SpellInfo spellInfo) + { + return ValidateSpellInfo(SpellIds.EnergySurge, SpellIds.PowerSurge); + } + + void HandleProc(AuraEffect aurEff, ProcEventInfo eventInfo) + { + PreventDefaultAction(); + SpellInfo spellInfo = eventInfo.GetSpellInfo(); + if (spellInfo == null) + return; + + uint spellId; + int chance; + + // Lesser Healing Wave + if (spellInfo.SpellFamilyFlags[0].HasAnyFlag(0x00000080u)) + { + spellId = SpellIds.EnergySurge; + chance = 10; + } + // Lightning Bolt + else if (spellInfo.SpellFamilyFlags[0].HasAnyFlag(0x00000001u)) + { + spellId = SpellIds.EnergySurge; + chance = 15; + } + // Stormstrike + else if (spellInfo.SpellFamilyFlags[1].HasAnyFlag(0x00000010u)) + { + spellId = SpellIds.PowerSurge; + chance = 50; + } + else + return; + + if (RandomHelper.randChance(chance)) + eventInfo.GetActor().CastSpell((Unit)null, spellId, true); + } + + public override void Register() + { + OnEffectProc.Add(new EffectProcHandler(HandleProc, 0, AuraType.Dummy)); + } + } + + public override AuraScript GetAuraScript() + { + return new spell_sha_item_t6_trinket_AuraScript(); + } + } + + [Script] // 70811 - Item - Shaman T10 Elemental 2P Bonus + class spell_sha_item_t10_elemental_2p_bonus : SpellScriptLoader + { + public spell_sha_item_t10_elemental_2p_bonus() : base("spell_sha_item_t10_elemental_2p_bonus") { } + + class spell_sha_item_t10_elemental_2p_bonus_AuraScript : AuraScript + { + public override bool Validate(SpellInfo spellInfo) + { + return ValidateSpellInfo(SpellIds.ElementalMastery); + } + + void HandleEffectProc(AuraEffect aurEff, ProcEventInfo eventInfo) + { + PreventDefaultAction(); + Player target = GetTarget().ToPlayer(); + if (target) + target.GetSpellHistory().ModifyCooldown(SpellIds.ElementalMastery, -aurEff.GetAmount()); + } + + public override void Register() + { + OnEffectProc.Add(new EffectProcHandler(HandleEffectProc, 0, AuraType.Dummy)); + } + } + + public override AuraScript GetAuraScript() + { + return new spell_sha_item_t10_elemental_2p_bonus_AuraScript(); + } + } + + [Script] // 189063 - Lightning Vortex (proc 185881 Item - Shaman T18 Elemental 4P Bonus) + class spell_sha_item_t18_elemental_4p_bonus : SpellScriptLoader + { + public spell_sha_item_t18_elemental_4p_bonus() : base("spell_sha_item_t18_elemental_4p_bonus") { } + + class spell_sha_item_t18_elemental_4p_bonus_AuraScript : AuraScript + { + void DiminishHaste(AuraEffect aurEff) + { + PreventDefaultAction(); + AuraEffect hasteBuff = GetEffect(0); + if (hasteBuff != null) + hasteBuff.ChangeAmount(hasteBuff.GetAmount() - aurEff.GetAmount()); + } + + public override void Register() + { + OnEffectPeriodic.Add(new EffectPeriodicHandler(DiminishHaste, 1, AuraType.PeriodicDummy)); + } + } + + public override AuraScript GetAuraScript() + { + return new spell_sha_item_t18_elemental_4p_bonus_AuraScript(); + } + } + + [Script] // 51505 - Lava burst + class spell_sha_lava_burst : SpellScriptLoader + { + public spell_sha_lava_burst() : base("spell_sha_lava_burst") { } + + class spell_sha_lava_burst_SpellScript : SpellScript + { + public override bool Validate(SpellInfo spellInfo) + { + return ValidateSpellInfo(SpellIds.PathOfFlamesTalent, SpellIds.PathOfFlamesSpread); + } + + void HandleScript(uint effIndex) + { + Unit caster = GetCaster(); + if (caster) + { + Unit target = GetExplTargetUnit(); + if (target) + if (caster.HasAura(SpellIds.PathOfFlamesTalent)) + caster.CastSpell(target, SpellIds.PathOfFlamesSpread, true); + } + } + + public override void Register() + { + OnEffectHitTarget.Add(new EffectHandler(HandleScript, 0, SpellEffectName.SchoolDamage)); + } + } + + public override SpellScript GetSpellScript() + { + return new spell_sha_lava_burst_SpellScript(); + } + } + + [Script] // 77756 - Lava Surge + class spell_sha_lava_surge : SpellScriptLoader + { + public spell_sha_lava_surge() : base("spell_sha_lava_surge") { } + + class spell_sha_lava_surge_AuraScript : AuraScript + { + public override bool Validate(SpellInfo spellInfo) + { + return ValidateSpellInfo(SpellIds.LavaSurge); + } + + void HandleEffectProc(AuraEffect aurEff, ProcEventInfo eventInfo) + { + PreventDefaultAction(); + GetTarget().CastSpell(GetTarget(), SpellIds.LavaSurge, true); + } + + public override void Register() + { + OnEffectProc.Add(new EffectProcHandler(HandleEffectProc, 0, AuraType.Dummy)); + } + } + + public override AuraScript GetAuraScript() + { + return new spell_sha_lava_surge_AuraScript(); + } + } + + [Script] // 77762 - Lava Surge + class spell_sha_lava_surge_proc : SpellScriptLoader + { + public spell_sha_lava_surge_proc() : base("spell_sha_lava_surge_proc") { } + + class spell_sha_lava_surge_proc_SpellScript : SpellScript + { + public override bool Validate(SpellInfo spellInfo) + { + return ValidateSpellInfo(SpellIds.LavaBurst); + } + + public override bool Load() + { + return GetCaster().IsTypeId(TypeId.Player); + } + + void ResetCooldown() + { + GetCaster().GetSpellHistory().RestoreCharge(Global.SpellMgr.GetSpellInfo(SpellIds.LavaBurst).ChargeCategoryId); + } + + public override void Register() + { + AfterHit.Add(new HitHandler(ResetCooldown)); + } + } + + public override SpellScript GetSpellScript() + { + return new spell_sha_lava_surge_proc_SpellScript(); + } + } + + [Script] // 210621 - Path of Flames Spread + class spell_sha_path_of_flames_spread : SpellScriptLoader + { + public spell_sha_path_of_flames_spread() : base("spell_sha_path_of_flames_spread") { } + + class spell_sha_path_of_flames_spread_SpellScript : SpellScript + { + public override bool Validate(SpellInfo spellInfo) + { + return ValidateSpellInfo(SpellIds.FlameShockMaelstrom); + } + + void FilterTargets(List targets) + { + targets.Remove(GetExplTargetUnit()); + targets.RandomResize(target => + { + return target.IsTypeId(TypeId.Unit) && !target.ToUnit().HasAura(SpellIds.FlameShockMaelstrom, GetCaster().GetGUID()); + }, 1); + } + + void HandleScript(uint effIndex) + { + Unit mainTarget = GetExplTargetUnit(); + if (mainTarget) + { + Aura flameShock = mainTarget.GetAura(SpellIds.FlameShockMaelstrom, GetCaster().GetGUID()); + if (flameShock != null) + { + Aura newAura = GetCaster().AddAura(SpellIds.FlameShockMaelstrom, GetHitUnit()); + if (newAura != null) + { + newAura.SetDuration(flameShock.GetDuration()); + newAura.SetMaxDuration(flameShock.GetDuration()); + } + } + } + } + + public override void Register() + { + OnObjectAreaTargetSelect.Add(new ObjectAreaTargetSelectHandler(FilterTargets, 1, Targets.UnitDestAreaEnemy)); + OnEffectHitTarget.Add(new EffectHandler(HandleScript, 1, SpellEffectName.Dummy)); + } + } + + public override SpellScript GetSpellScript() + { + return new spell_sha_path_of_flames_spread_SpellScript(); + } + } + + [Script] // 51562 - Tidal Waves + class spell_sha_tidal_waves : SpellScriptLoader + { + public spell_sha_tidal_waves() : base("spell_sha_tidal_waves") { } + + class spell_sha_tidal_waves_AuraScript : AuraScript + { + public override bool Validate(SpellInfo spellInfo) + { + return ValidateSpellInfo(SpellIds.TidalWaves); + } + + void HandleEffectProc(AuraEffect aurEff, ProcEventInfo eventInfo) + { + PreventDefaultAction(); + int basePoints0 = -aurEff.GetAmount(); + int basePoints1 = aurEff.GetAmount(); + + GetTarget().CastCustomSpell(GetTarget(), SpellIds.TidalWaves, basePoints0, basePoints1, 0, true, null, aurEff); + } + + public override void Register() + { + OnEffectProc.Add(new EffectProcHandler(HandleEffectProc, 0, AuraType.Dummy)); + } + } + + public override AuraScript GetAuraScript() + { + return new spell_sha_tidal_waves_AuraScript(); + } + } + + [Script] // 28823 - Totemic Power + class spell_sha_t3_6p_bonus : SpellScriptLoader + { + public spell_sha_t3_6p_bonus() : base("spell_sha_t3_6p_bonus") { } + + class spell_sha_t3_6p_bonus_AuraScript : AuraScript + { + public override bool Validate(SpellInfo spellInfo) + { + return ValidateSpellInfo(SpellIds.TotemicPowerArmor, SpellIds.TotemicPowerAttackPower, SpellIds.TotemicPowerSpellPower, SpellIds.TotemicPowerMp5); + } + + void HandleProc(AuraEffect aurEff, ProcEventInfo eventInfo) + { + PreventDefaultAction(); + + uint spellId; + Unit caster = eventInfo.GetActor(); + Unit target = eventInfo.GetProcTarget(); + + switch (target.GetClass()) + { + case Class.Paladin: + case Class.Priest: + case Class.Shaman: + case Class.Druid: + spellId = SpellIds.TotemicPowerMp5; + break; + case Class.Mage: + case Class.Warlock: + spellId = SpellIds.TotemicPowerSpellPower; + break; + case Class.Hunter: + case Class.Rogue: + spellId = SpellIds.TotemicPowerAttackPower; + break; + case Class.Warrior: + spellId = SpellIds.TotemicPowerArmor; + break; + default: + return; + } + + caster.CastSpell(target, spellId, true); + } + + public override void Register() + { + OnEffectProc.Add(new EffectProcHandler(HandleProc, 0, AuraType.Dummy)); + } + } + + public override AuraScript GetAuraScript() + { + return new spell_sha_t3_6p_bonus_AuraScript(); + } + } + + [Script] // 64928 - Item - Shaman T8 Elemental 4P Bonus + class spell_sha_t8_elemental_4p_bonus : SpellScriptLoader + { + public spell_sha_t8_elemental_4p_bonus() : base("spell_sha_t8_elemental_4p_bonus") { } + + class spell_sha_t8_elemental_4p_bonus_AuraScript : AuraScript + { + public override bool Validate(SpellInfo spellInfo) + { + return ValidateSpellInfo(SpellIds.Electrified); + } + + void HandleProc(AuraEffect aurEff, ProcEventInfo eventInfo) + { + PreventDefaultAction(); + + DamageInfo damageInfo = eventInfo.GetDamageInfo(); + if (damageInfo == null || damageInfo.GetDamage() == 0) + return; + + SpellInfo spellInfo = Global.SpellMgr.GetSpellInfo(SpellIds.Electrified); + int amount = (int)MathFunctions.CalculatePct(damageInfo.GetDamage(), aurEff.GetAmount()); + amount /= (int)spellInfo.GetMaxTicks(Difficulty.None); + + // Add remaining ticks to healing done + Unit caster = eventInfo.GetActor(); + Unit target = eventInfo.GetProcTarget(); + amount += (int)target.GetRemainingPeriodicAmount(caster.GetGUID(), SpellIds.Electrified, AuraType.PeriodicDamage); + + caster.CastCustomSpell(SpellIds.Electrified, SpellValueMod.BasePoint0, amount, target, true); + } + + public override void Register() + { + OnEffectProc.Add(new EffectProcHandler(HandleProc, 0, AuraType.Dummy)); + } + } + + public override AuraScript GetAuraScript() + { + return new spell_sha_t8_elemental_4p_bonus_AuraScript(); + } + } + + [Script] // 67228 - Item - Shaman T9 Elemental 4P Bonus (Lava Burst) + class spell_sha_t9_elemental_4p_bonus : SpellScriptLoader + { + public spell_sha_t9_elemental_4p_bonus() : base("spell_sha_t9_elemental_4p_bonus") { } + + class spell_sha_t9_elemental_4p_bonus_AuraScript : AuraScript + { + public override bool Validate(SpellInfo spellInfo) + { + return ValidateSpellInfo(SpellIds.LavaBurstBonusDamage); + } + + void HandleProc(AuraEffect aurEff, ProcEventInfo eventInfo) + { + PreventDefaultAction(); + + DamageInfo damageInfo = eventInfo.GetDamageInfo(); + if (damageInfo == null || damageInfo.GetDamage() == 0) + return; + + SpellInfo spellInfo = Global.SpellMgr.GetSpellInfo(SpellIds.LavaBurstBonusDamage); + int amount = (int)MathFunctions.CalculatePct(damageInfo.GetDamage(), aurEff.GetAmount()); + amount /= (int)spellInfo.GetMaxTicks(Difficulty.None); + + // Add remaining ticks to healing done + Unit caster = eventInfo.GetActor(); + Unit target = eventInfo.GetProcTarget(); + amount += (int)target.GetRemainingPeriodicAmount(caster.GetGUID(), SpellIds.LavaBurstBonusDamage, AuraType.PeriodicDamage); + + caster.CastCustomSpell(SpellIds.LavaBurstBonusDamage, SpellValueMod.BasePoint0, amount, target, true); + } + + public override void Register() + { + OnEffectProc.Add(new EffectProcHandler(HandleProc, 0, AuraType.Dummy)); + } + } + + public override AuraScript GetAuraScript() + { + return new spell_sha_t9_elemental_4p_bonus_AuraScript(); + } + } + + [Script] // 70817 - Item - Shaman T10 Elemental 4P Bonus + class spell_sha_t10_elemental_4p_bonus : SpellScriptLoader + { + public spell_sha_t10_elemental_4p_bonus() : base("spell_sha_t10_elemental_4p_bonus") { } + + class spell_sha_t10_elemental_4p_bonus_AuraScript : AuraScript + { + void HandleProc(AuraEffect aurEff, ProcEventInfo eventInfo) + { + PreventDefaultAction(); + + Unit caster = eventInfo.GetActor(); + Unit target = eventInfo.GetProcTarget(); + + // try to find spell Flame Shock on the target + AuraEffect flameShock = target.GetAuraEffect(AuraType.PeriodicDamage, SpellFamilyNames.Shaman, new FlagArray128(0x10000000), caster.GetGUID()); + if (flameShock == null) + return; + + Aura flameShockAura = flameShock.GetBase(); + + int maxDuration = flameShockAura.GetMaxDuration(); + int newDuration = flameShockAura.GetDuration() + aurEff.GetAmount() * Time.InMilliseconds; + + flameShockAura.SetDuration(newDuration); + // is it blizzlike to change max duration for FS? + if (newDuration > maxDuration) + flameShockAura.SetMaxDuration(newDuration); + } + + public override void Register() + { + OnEffectProc.Add(new EffectProcHandler(HandleProc, 0, AuraType.Dummy)); + } + } + + public override AuraScript GetAuraScript() + { + return new spell_sha_t10_elemental_4p_bonus_AuraScript(); + } + } + + [Script] // 70808 - Item - Shaman T10 Restoration 4P Bonus + class spell_sha_t10_restoration_4p_bonus : SpellScriptLoader + { + public spell_sha_t10_restoration_4p_bonus() : base("spell_sha_t10_restoration_4p_bonus") { } + + class spell_sha_t10_restoration_4p_bonus_AuraScript : AuraScript + { + public override bool Validate(SpellInfo spellInfo) + { + return ValidateSpellInfo(SpellIds.ChainedHeal); + } + + void HandleProc(AuraEffect aurEff, ProcEventInfo eventInfo) + { + PreventDefaultAction(); + + HealInfo healInfo = eventInfo.GetHealInfo(); + if (healInfo == null || healInfo.GetHeal() == 0) + return; + + SpellInfo spellInfo = Global.SpellMgr.GetSpellInfo(SpellIds.ChainedHeal); + int amount = (int)MathFunctions.CalculatePct(healInfo.GetHeal(), aurEff.GetAmount()); + amount /= (int)spellInfo.GetMaxTicks(Difficulty.None); + + // Add remaining ticks to healing done + Unit caster = eventInfo.GetActor(); + Unit target = eventInfo.GetProcTarget(); + amount += (int)target.GetRemainingPeriodicAmount(caster.GetGUID(), SpellIds.ChainedHeal, AuraType.PeriodicHeal); + + caster.CastCustomSpell(SpellIds.ChainedHeal, SpellValueMod.BasePoint0, amount, target, true); + } + + public override void Register() + { + OnEffectProc.Add(new EffectProcHandler(HandleProc, 0, AuraType.Dummy)); + } + } + + public override AuraScript GetAuraScript() + { + return new spell_sha_t10_restoration_4p_bonus_AuraScript(); + } + } + + [Script] // 33757 - Windfury + class spell_sha_windfury : SpellScriptLoader + { + public spell_sha_windfury() : base("spell_sha_windfury") { } + + class spell_sha_windfury_AuraScript : AuraScript + { + public override bool Validate(SpellInfo spellInfo) + { + return ValidateSpellInfo(SpellIds.WindfuryAttack); + } + + void HandleEffectProc(AuraEffect aurEff, ProcEventInfo eventInfo) + { + PreventDefaultAction(); + + for (uint i = 0; i < 2; ++i) + eventInfo.GetActor().CastSpell(eventInfo.GetProcTarget(), SpellIds.WindfuryAttack, true, null, aurEff); + } + + public override void Register() + { + OnEffectProc.Add(new EffectProcHandler(HandleEffectProc, 0, AuraType.Dummy)); + } + } + + public override AuraScript GetAuraScript() + { + return new spell_sha_windfury_AuraScript(); + } + } +} \ No newline at end of file diff --git a/Scripts/Spells/Warlock.cs b/Scripts/Spells/Warlock.cs new file mode 100644 index 000000000..05b887e46 --- /dev/null +++ b/Scripts/Spells/Warlock.cs @@ -0,0 +1,1347 @@ +/* + * Copyright (C) 2012-2017 CypherCore + * + * 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, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +using Framework.Constants; +using Framework.Dynamic; +using Game.Entities; +using Game.Scripting; +using Game.Spells; +using System; +using System.Collections.Generic; + +namespace Scripts.Spells.Warlock +{ + using SoulSwapOverrideAuraScript = spell_warl_soul_swap_override.spell_warl_soul_swap_override_AuraScript; + + struct SpellIds + { + public const uint BaneOfDoomEffect = 18662; + public const uint CreateHealthstone = 23517; + public const uint DemonicCircleAllowCast = 62388; + public const uint DemonicCircleSummon = 48018; + public const uint DemonicCircleTeleport = 48020; + public const uint DemonicEmpowermentFelguard = 54508; + public const uint DemonicEmpowermentFelhunter = 54509; + public const uint DemonicEmpowermentImp = 54444; + public const uint DemonicEmpowermentSuccubus = 54435; + public const uint DemonicEmpowermentVoidwalker = 54443; + public const uint DemonSoulImp = 79459; + public const uint DemonSoulFelhunter = 79460; + public const uint DemonSoulFelguard = 79452; + public const uint DemonSoulSuccubus = 79453; + public const uint DemonSoulVoidwalker = 79454; + public const uint DevourMagicHeal = 19658; + public const uint FelSynergyHeal = 54181; + public const uint GlyphOfDemonTraining = 56249; + public const uint GlyphOfShadowflame = 63311; + public const uint GlyphOfSoulSwap = 56226; + public const uint GlyphOfSuccubus = 56250; + public const uint HauntHeal = 48210; + public const uint Immolate = 348; + public const uint ImprovedHealthFunnelBuffR1 = 60955; + public const uint ImprovedHealthFunnelBuffR2 = 60956; + public const uint ImprovedHealthFunnelR1 = 18703; + public const uint ImprovedHealthFunnelR2 = 18704; + public const uint ImprovedSoulFirePct = 85383; + public const uint ImprovedSoulFireState = 85385; + public const uint NetherWard = 91711; + public const uint NetherTalent = 91713; + public const uint RainOfFire = 5740; + public const uint RainOfFireDamage = 42223; + public const uint SeedOfCorruptionDamage = 27285; + public const uint SeedOfCorruptionGeneric = 32865; + public const uint ShadowTrance = 17941; + public const uint ShadowWard = 6229; + public const uint Soulshatter = 32835; + public const uint SoulSwapCdMarker = 94229; + public const uint SoulSwapOverride = 86211; + public const uint SoulSwapModCost = 92794; + public const uint SoulSwapDotMarker = 92795; + public const uint UnstableAffliction = 30108; + public const uint UnstableAfflictionDispel = 31117; + public const uint Shadowflame = 37378; + public const uint Flameshadow = 37379; + + public const uint GenReplenishment = 57669; + public const uint PriestShadowWordDeath = 32409; + } + + [Script] // 710 - Banish + class spell_warl_banish : SpellScriptLoader + { + public spell_warl_banish() : base("spell_warl_banish") { } + + class spell_warl_banish_SpellScript : SpellScript + { + public spell_warl_banish_SpellScript() + { } + + void HandleBanish(SpellMissInfo missInfo) + { + if (missInfo != SpellMissInfo.Immune) + return; + + Unit target = GetHitUnit(); + if (target) + { + // Casting Banish on a banished target will remove applied aura + Aura banishAura = target.GetAura(GetSpellInfo().Id, GetCaster().GetGUID()); + if (banishAura != null) + banishAura.Remove(); + } + } + + public override void Register() + { + BeforeHit.Add(new BeforeHitHandler(HandleBanish)); + } + } + + public override SpellScript GetSpellScript() + { + return new spell_warl_banish_SpellScript(); + } + } + + [Script] // 17962 - Conflagrate - Updated to 4.3.4 + class spell_warl_conflagrate : SpellScriptLoader + { + public spell_warl_conflagrate() : base("spell_warl_conflagrate") { } + + class spell_warl_conflagrate_SpellScript : SpellScript + { + public override bool Validate(SpellInfo spellInfo) + { + return ValidateSpellInfo(SpellIds.Immolate); + } + + // 6.x dmg formula in tooltip + // void HandleHit(uint effIndex) + // { + // if (AuraEffect aurEff = GetHitUnit().GetAuraEffect(SPELL_WARLOCK_IMMOLATE, 2, GetCaster().GetGUID())) + // SetHitDamage(CalculatePct(aurEff.GetAmount(), HasSpellInfo().Effects[1].CalcValue(GetCaster()))); + // } + + public override void Register() + { + //OnEffectHitTarget.Add(new EffectHandler(spell_warl_conflagrate_SpellScript::HandleHit, 0, SPELL_EFFECT_SCHOOL_DAMAGE); + } + } + + public override SpellScript GetSpellScript() + { + return new spell_warl_conflagrate_SpellScript(); + } + } + + [Script] // 6201 - Create Healthstone + class spell_warl_create_healthstone : SpellScriptLoader + { + public spell_warl_create_healthstone() : base("spell_warl_create_healthstone") { } + + class spell_warl_create_healthstone_SpellScript : SpellScript + { + public override bool Validate(SpellInfo spellInfo) + { + return ValidateSpellInfo(SpellIds.CreateHealthstone); + } + + public override bool Load() + { + return GetCaster().IsTypeId(TypeId.Player); + } + + void HandleScriptEffect(uint effIndex) + { + GetCaster().CastSpell(GetCaster(), SpellIds.CreateHealthstone, true); + } + + public override void Register() + { + OnEffectHitTarget.Add(new EffectHandler(HandleScriptEffect, 0, SpellEffectName.ScriptEffect)); + } + } + + public override SpellScript GetSpellScript() + { + return new spell_warl_create_healthstone_SpellScript(); + } + } + + [Script] // 603 - Bane of Doom + class spell_warl_bane_of_doom : SpellScriptLoader + { + public spell_warl_bane_of_doom() : base("spell_warl_bane_of_doom") { } + + class spell_warl_curse_of_doom_AuraScript : AuraScript + { + public override bool Validate(SpellInfo spellInfo) + { + return ValidateSpellInfo(SpellIds.BaneOfDoomEffect); + } + + public override bool Load() + { + return GetCaster() && GetCaster().IsTypeId(TypeId.Player); + } + + void OnRemove(AuraEffect aurEff, AuraEffectHandleModes mode) + { + if (!GetCaster()) + return; + + AuraRemoveMode removeMode = GetTargetApplication().GetRemoveMode(); + if (removeMode != AuraRemoveMode.ByDeath || !IsExpired()) + return; + + if (GetCaster().ToPlayer().isHonorOrXPTarget(GetTarget())) + GetCaster().CastSpell(GetTarget(), SpellIds.BaneOfDoomEffect, true, null, aurEff); + } + + public override void Register() + { + AfterEffectRemove.Add(new EffectApplyHandler(OnRemove, 0, AuraType.PeriodicDamage, AuraEffectHandleModes.Real)); + } + } + + public override AuraScript GetAuraScript() + { + return new spell_warl_curse_of_doom_AuraScript(); + } + } + + [Script] // 48018 - Demonic Circle: Summon + class spell_warl_demonic_circle_summon : SpellScriptLoader + { + public spell_warl_demonic_circle_summon() : base("spell_warl_demonic_circle_summon") { } + + class spell_warl_demonic_circle_summon_AuraScript : AuraScript + { + void HandleRemove(AuraEffect aurEff, AuraEffectHandleModes mode) + { + // If effect is removed by expire remove the summoned demonic circle too. + if (!mode.HasAnyFlag(AuraEffectHandleModes.Reapply)) + GetTarget().RemoveGameObject(GetId(), true); + + GetTarget().RemoveAura(SpellIds.DemonicCircleAllowCast); + } + + void HandleDummyTick(AuraEffect aurEff) + { + GameObject circle = GetTarget().GetGameObject(GetId()); + if (circle) + { + // Here we check if player is in demonic circle teleport range, if so add + // WARLOCK_DEMONIC_CIRCLE_ALLOW_CAST; allowing him to cast the WARLOCK_DEMONIC_CIRCLE_TELEPORT. + // If not in range remove the WARLOCK_DEMONIC_CIRCLE_ALLOW_CAST. + + SpellInfo spellInfo = Global.SpellMgr.GetSpellInfo(SpellIds.DemonicCircleTeleport); + + if (GetTarget().IsWithinDist(circle, spellInfo.GetMaxRange(true))) + { + if (!GetTarget().HasAura(SpellIds.DemonicCircleAllowCast)) + GetTarget().CastSpell(GetTarget(), SpellIds.DemonicCircleAllowCast, true); + } + else + GetTarget().RemoveAura(SpellIds.DemonicCircleAllowCast); + } + } + + public override void Register() + { + OnEffectRemove.Add(new EffectApplyHandler(HandleRemove, 0, AuraType.PeriodicDummy, AuraEffectHandleModes.RealOrReapplyMask)); + OnEffectPeriodic.Add(new EffectPeriodicHandler(HandleDummyTick, 0, AuraType.PeriodicDummy)); + } + } + + public override AuraScript GetAuraScript() + { + return new spell_warl_demonic_circle_summon_AuraScript(); + } + } + + [Script] // 48020 - Demonic Circle: Teleport + class spell_warl_demonic_circle_teleport : SpellScriptLoader + { + public spell_warl_demonic_circle_teleport() : base("spell_warl_demonic_circle_teleport") { } + + class spell_warl_demonic_circle_teleport_AuraScript : AuraScript + { + void HandleTeleport(AuraEffect aurEff, AuraEffectHandleModes mode) + { + Player player = GetTarget().ToPlayer(); + if (player) + { + GameObject circle = player.GetGameObject(SpellIds.DemonicCircleSummon); + if (circle) + { + player.NearTeleportTo(circle.GetPositionX(), circle.GetPositionY(), circle.GetPositionZ(), circle.GetOrientation()); + player.RemoveMovementImpairingAuras(); + } + } + } + + public override void Register() + { + OnEffectApply.Add(new EffectApplyHandler(HandleTeleport, 0, AuraType.MechanicImmunity, AuraEffectHandleModes.Real)); + } + } + + public override AuraScript GetAuraScript() + { + return new spell_warl_demonic_circle_teleport_AuraScript(); + } + } + + [Script] // 77801 - Demon Soul - Updated to 4.3.4 + class spell_warl_demon_soul : SpellScriptLoader + { + public spell_warl_demon_soul() : base("spell_warl_demon_soul") { } + + class spell_warl_demon_soul_SpellScript : SpellScript + { + public override bool Validate(SpellInfo spellInfo) + { + return ValidateSpellInfo(SpellIds.DemonSoulImp, SpellIds.DemonSoulFelhunter, SpellIds.DemonSoulFelguard, SpellIds.DemonSoulSuccubus, SpellIds.DemonSoulVoidwalker); + } + + void OnHitTarget(uint effIndex) + { + Unit caster = GetCaster(); + Creature targetCreature = GetHitCreature(); + if (targetCreature) + { + if (targetCreature.IsPet()) + { + CreatureTemplate ci = targetCreature.GetCreatureTemplate(); + switch (ci.Family) + { + case CreatureFamily.Succubus: + caster.CastSpell(caster, SpellIds.DemonSoulSuccubus); + break; + case CreatureFamily.Voidwalker: + caster.CastSpell(caster, SpellIds.DemonSoulVoidwalker); + break; + case CreatureFamily.Felguard: + caster.CastSpell(caster, SpellIds.DemonSoulFelguard); + break; + case CreatureFamily.Felhunter: + caster.CastSpell(caster, SpellIds.DemonSoulFelhunter); + break; + case CreatureFamily.Imp: + caster.CastSpell(caster, SpellIds.DemonSoulImp); + break; + default: + break; + } + } + } + } + + public override void Register() + { + OnEffectHitTarget.Add(new EffectHandler(OnHitTarget, 0, SpellEffectName.ScriptEffect)); + } + } + + public override SpellScript GetSpellScript() + { + return new spell_warl_demon_soul_SpellScript(); + } + } + + [Script] // 47193 - Demonic Empowerment + class spell_warl_demonic_empowerment : SpellScriptLoader + { + public spell_warl_demonic_empowerment() : base("spell_warl_demonic_empowerment") { } + + class spell_warl_demonic_empowerment_SpellScript : SpellScript + { + public override bool Validate(SpellInfo spellInfo) + { + return ValidateSpellInfo(SpellIds.DemonicEmpowermentSuccubus, SpellIds.DemonicEmpowermentVoidwalker, SpellIds.DemonicEmpowermentFelguard, SpellIds.DemonicEmpowermentFelhunter, SpellIds.DemonicEmpowermentImp); + } + + void HandleScriptEffect(uint effIndex) + { + Creature targetCreature = GetHitCreature(); + if (targetCreature) + { + if (targetCreature.IsPet()) + { + CreatureTemplate ci = targetCreature.GetCreatureTemplate(); + switch (ci.Family) + { + case CreatureFamily.Succubus: + targetCreature.CastSpell(targetCreature, SpellIds.DemonicEmpowermentSuccubus, true); + break; + case CreatureFamily.Voidwalker: + { + SpellInfo spellInfo = Global.SpellMgr.GetSpellInfo(SpellIds.DemonicEmpowermentVoidwalker); + int hp = (int)targetCreature.CountPctFromMaxHealth(GetCaster().CalculateSpellDamage(targetCreature, spellInfo, 0)); + targetCreature.CastCustomSpell(targetCreature, SpellIds.DemonicEmpowermentVoidwalker, hp, 0, 0, true); + break; + } + case CreatureFamily.Felguard: + targetCreature.CastSpell(targetCreature, SpellIds.DemonicEmpowermentFelguard, true); + break; + case CreatureFamily.Felhunter: + targetCreature.CastSpell(targetCreature, SpellIds.DemonicEmpowermentFelhunter, true); + break; + case CreatureFamily.Imp: + targetCreature.CastSpell(targetCreature, SpellIds.DemonicEmpowermentImp, true); + break; + default: + break; + } + } + } + } + + public override void Register() + { + OnEffectHitTarget.Add(new EffectHandler(HandleScriptEffect, 0, SpellEffectName.ScriptEffect)); + } + } + + public override SpellScript GetSpellScript() + { + return new spell_warl_demonic_empowerment_SpellScript(); + } + } + + [Script] // 67518, 19505 - Devour Magic + class spell_warl_devour_magic : SpellScriptLoader + { + public spell_warl_devour_magic() : base("spell_warl_devour_magic") { } + + class spell_warl_devour_magic_SpellScript : SpellScript + { + public override bool Validate(SpellInfo spellInfo) + { + return ValidateSpellInfo(SpellIds.GlyphOfDemonTraining, SpellIds.DevourMagicHeal); + } + + void OnSuccessfulDispel(uint effIndex) + { + SpellEffectInfo effect = GetSpellInfo().GetEffect(1); + if (effect != null) + { + Unit caster = GetCaster(); + int heal_amount = effect.CalcValue(caster); + + caster.CastCustomSpell(caster, SpellIds.DevourMagicHeal, heal_amount, 0, 0, true); + + // Glyph of Felhunter + Unit owner = caster.GetOwner(); + if (owner) + if (owner.GetAura(SpellIds.GlyphOfDemonTraining) != null) + owner.CastCustomSpell(owner, SpellIds.DevourMagicHeal, heal_amount, 0, 0, true); + } + } + + public override void Register() + { + OnEffectSuccessfulDispel.Add(new EffectHandler(OnSuccessfulDispel, 0, SpellEffectName.Dispel)); + } + } + + public override SpellScript GetSpellScript() + { + return new spell_warl_devour_magic_SpellScript(); + } + } + + [Script] // 47422 - Everlasting Affliction + class spell_warl_everlasting_affliction : SpellScriptLoader + { + public spell_warl_everlasting_affliction() : base("spell_warl_everlasting_affliction") { } + + class spell_warl_everlasting_affliction_SpellScript : SpellScript + { + void HandleScriptEffect(uint effIndex) + { + Unit caster = GetCaster(); + Unit target = GetHitUnit(); + if (target) + { + // Refresh corruption on target + AuraEffect aurEff = target.GetAuraEffect(AuraType.PeriodicDamage, SpellFamilyNames.Warlock, new FlagArray128(0x2, 0, 0), caster.GetGUID()); + if (aurEff != null) + { + uint damage = (uint)Math.Max(aurEff.GetAmount(), 0); + Global.ScriptMgr.ModifyPeriodicDamageAurasTick(target, caster, ref damage); + aurEff.SetDamage((int)(caster.SpellDamageBonusDone(target, aurEff.GetSpellInfo(), damage, DamageEffectType.DOT, GetEffectInfo(effIndex)) * aurEff.GetDonePct())); + aurEff.CalculatePeriodic(caster, false, false); + aurEff.GetBase().RefreshDuration(true); + } + } + } + + public override void Register() + { + OnEffectHitTarget.Add(new EffectHandler(HandleScriptEffect, 0, SpellEffectName.ScriptEffect)); + } + } + + public override SpellScript GetSpellScript() + { + return new spell_warl_everlasting_affliction_SpellScript(); + } + } + + [Script] // -47230 - Fel Synergy + class spell_warl_fel_synergy : SpellScriptLoader + { + public spell_warl_fel_synergy() : base("spell_warl_fel_synergy") { } + + class spell_warl_fel_synergy_AuraScript : AuraScript + { + public override bool Validate(SpellInfo spellInfo) + { + return ValidateSpellInfo(SpellIds.FelSynergyHeal); + } + + bool CheckProc(ProcEventInfo eventInfo) + { + return GetTarget().GetGuardianPet() && eventInfo.GetDamageInfo().GetDamage() != 0; + } + + void onProc(AuraEffect aurEff, ProcEventInfo eventInfo) + { + PreventDefaultAction(); + + int heal = (int)MathFunctions.CalculatePct(eventInfo.GetDamageInfo().GetDamage(), aurEff.GetAmount()); + GetTarget().CastCustomSpell(SpellIds.FelSynergyHeal, SpellValueMod.BasePoint0, heal, (Unit)null, true, null, aurEff); // TARGET_UNIT_PET + } + + public override void Register() + { + DoCheckProc.Add(new CheckProcHandler(CheckProc)); + OnEffectProc.Add(new EffectProcHandler(onProc, 0, AuraType.Dummy)); + } + } + + public override AuraScript GetAuraScript() + { + return new spell_warl_fel_synergy_AuraScript(); + } + } + + [Script] // 63310 - Glyph of Shadowflame + class spell_warl_glyph_of_shadowflame : SpellScriptLoader + { + public spell_warl_glyph_of_shadowflame() : base("spell_warl_glyph_of_shadowflame") { } + + class spell_warl_glyph_of_shadowflame_AuraScript : AuraScript + { + public override bool Validate(SpellInfo spellInfo) + { + return ValidateSpellInfo(SpellIds.GlyphOfShadowflame); + } + + void onProc(AuraEffect aurEff, ProcEventInfo eventInfo) + { + PreventDefaultAction(); + GetTarget().CastSpell(eventInfo.GetProcTarget(), SpellIds.GlyphOfShadowflame, true, null, aurEff); + } + + public override void Register() + { + OnEffectProc.Add(new EffectProcHandler(onProc, 0, AuraType.Dummy)); + } + } + + public override AuraScript GetAuraScript() + { + return new spell_warl_glyph_of_shadowflame_AuraScript(); + } + } + + [Script] // 48181 - Haunt + class spell_warl_haunt : SpellScriptLoader + { + public spell_warl_haunt() : base("spell_warl_haunt") { } + + class spell_warl_haunt_SpellScript : SpellScript + { + void HandleAfterHit() + { + Aura aura = GetHitAura(); + if (aura != null) + { + AuraEffect aurEff = aura.GetEffect(1); + if (aurEff != null) + aurEff.SetAmount(MathFunctions.CalculatePct(aurEff.GetAmount(), GetHitDamage())); + } + } + + public override void Register() + { + AfterHit.Add(new HitHandler(HandleAfterHit)); + } + } + + class spell_warl_haunt_AuraScript : AuraScript + { + public override bool Validate(SpellInfo spellInfo) + { + return ValidateSpellInfo(SpellIds.HauntHeal); + } + + void HandleRemove(AuraEffect aurEff, AuraEffectHandleModes mode) + { + Unit caster = GetCaster(); + if (caster) + { + int amount = aurEff.GetAmount(); + GetTarget().CastCustomSpell(caster, SpellIds.HauntHeal, amount, 0, 0, true, null, aurEff, GetCasterGUID()); + } + } + + public override void Register() + { + OnEffectRemove.Add(new EffectApplyHandler(HandleRemove, 1, AuraType.Dummy, AuraEffectHandleModes.RealOrReapplyMask)); + } + } + + public override SpellScript GetSpellScript() + { + return new spell_warl_haunt_SpellScript(); + } + + public override AuraScript GetAuraScript() + { + return new spell_warl_haunt_AuraScript(); + } + } + + [Script] // 755 - Health Funnel + class spell_warl_health_funnel : SpellScriptLoader + { + public spell_warl_health_funnel() : base("spell_warl_health_funnel") { } + + class spell_warl_health_funnel_AuraScript : AuraScript + { + void ApplyEffect(AuraEffect aurEff, AuraEffectHandleModes mode) + { + Unit caster = GetCaster(); + if (!caster) + return; + + Unit target = GetTarget(); + if (caster.HasAura(SpellIds.ImprovedHealthFunnelR2)) + target.CastSpell(target, SpellIds.ImprovedHealthFunnelBuffR2, true); + else if (caster.HasAura(SpellIds.ImprovedHealthFunnelR1)) + target.CastSpell(target, SpellIds.ImprovedHealthFunnelBuffR1, true); + } + + void RemoveEffect(AuraEffect aurEff, AuraEffectHandleModes mode) + { + Unit target = GetTarget(); + target.RemoveAurasDueToSpell(SpellIds.ImprovedHealthFunnelBuffR1); + target.RemoveAurasDueToSpell(SpellIds.ImprovedHealthFunnelBuffR2); + } + + void OnPeriodic(AuraEffect aurEff) + { + Unit caster = GetCaster(); + if (!caster) + return; + //! HACK for self damage, is not blizz :/ + uint damage = (uint)caster.CountPctFromMaxHealth(aurEff.GetBaseAmount()); + + Player modOwner = caster.GetSpellModOwner(); + if (modOwner) + modOwner.ApplySpellMod(GetId(), SpellModOp.Cost, ref damage); + + SpellNonMeleeDamage damageInfo = new SpellNonMeleeDamage(caster, caster, GetSpellInfo().Id, GetAura().GetSpellXSpellVisualId(), GetSpellInfo().SchoolMask, GetAura().GetCastGUID()); + damageInfo.periodicLog = true; + damageInfo.damage = damage; + caster.DealSpellDamage(damageInfo, false); + caster.SendSpellNonMeleeDamageLog(damageInfo); + } + + public override void Register() + { + OnEffectApply.Add(new EffectApplyHandler(ApplyEffect, 0, AuraType.ObsModHealth, AuraEffectHandleModes.Real)); + OnEffectRemove.Add(new EffectApplyHandler(RemoveEffect, 0, AuraType.ObsModHealth, AuraEffectHandleModes.Real)); + OnEffectPeriodic.Add(new EffectPeriodicHandler(OnPeriodic, 0, AuraType.ObsModHealth)); + } + } + + public override AuraScript GetAuraScript() + { + return new spell_warl_health_funnel_AuraScript(); + } + } + + [Script] // 6262 - Healthstone + class spell_warl_healthstone_heal : SpellScriptLoader + { + public spell_warl_healthstone_heal() : base("spell_warl_healthstone_heal") { } + + class spell_warl_healthstone_heal_SpellScript : SpellScript + { + void HandleOnHit() + { + int heal = (int)MathFunctions.CalculatePct(GetCaster().GetCreateHealth(), GetHitHeal()); + SetHitHeal(heal); + } + + public override void Register() + { + OnHit.Add(new HitHandler(HandleOnHit)); + } + } + + public override SpellScript GetSpellScript() + { + return new spell_warl_healthstone_heal_SpellScript(); + } + } + + [Script] // -18119 - Improved Soul Fire + class spell_warl_improved_soul_fire : SpellScriptLoader + { + public spell_warl_improved_soul_fire() : base("spell_warl_improved_soul_fire") { } + + class spell_warl_improved_soul_fire_AuraScript : AuraScript + { + public override bool Validate(SpellInfo spellInfo) + { + return ValidateSpellInfo(SpellIds.ImprovedSoulFirePct, SpellIds.ImprovedSoulFireState); + } + + void onProc(AuraEffect aurEff, ProcEventInfo eventInfo) + { + PreventDefaultAction(); + GetTarget().CastCustomSpell(SpellIds.ImprovedSoulFirePct, SpellValueMod.BasePoint0, aurEff.GetAmount(), GetTarget(), true, null, aurEff); + GetTarget().CastSpell(GetTarget(), SpellIds.ImprovedSoulFireState, true, null, aurEff); + } + + public override void Register() + { + OnEffectProc.Add(new EffectProcHandler(onProc, 0, AuraType.Dummy)); + } + } + + public override AuraScript GetAuraScript() + { + return new spell_warl_improved_soul_fire_AuraScript(); + } + } + + // 687 - Demon Armor + [Script] // 28176 - Fel Armor + class spell_warl_nether_ward_overrride : SpellScriptLoader + { + public spell_warl_nether_ward_overrride() : base("spell_warl_nether_ward_overrride") { } + + class spell_warl_nether_ward_overrride_AuraScript : AuraScript + { + public override bool Validate(SpellInfo spellInfo) + { + return ValidateSpellInfo(SpellIds.NetherTalent, SpellIds.NetherWard, SpellIds.ShadowWard); + } + + void CalculateAmount(AuraEffect aurEff, ref int amount, ref bool canBeRecalculated) + { + if (GetUnitOwner().HasAura(SpellIds.NetherTalent)) + amount = (int)SpellIds.NetherWard; + else + amount = (int)SpellIds.ShadowWard; + } + + public override void Register() + { + DoEffectCalcAmount.Add(new EffectCalcAmountHandler(CalculateAmount, 2, AuraType.OverrideActionbarSpells)); + } + } + + public override AuraScript GetAuraScript() + { + return new spell_warl_nether_ward_overrride_AuraScript(); + } + } + + [Script] // 6358 - Seduction (Special Ability) + class spell_warl_seduction : SpellScriptLoader + { + public spell_warl_seduction() : base("spell_warl_seduction") { } + + class spell_warl_seduction_SpellScript : SpellScript + { + public override bool Validate(SpellInfo spellInfo) + { + return ValidateSpellInfo(SpellIds.GlyphOfSuccubus, SpellIds.PriestShadowWordDeath); + } + + void HandleScriptEffect(uint effIndex) + { + Unit caster = GetCaster(); + Unit target = GetHitUnit(); + if (target) + { + if (caster.GetOwner() && caster.GetOwner().HasAura(SpellIds.GlyphOfSuccubus)) + { + target.RemoveAurasByType(AuraType.PeriodicDamage, ObjectGuid.Empty, target.GetAura(SpellIds.PriestShadowWordDeath)); // SW:D shall not be removed. + target.RemoveAurasByType(AuraType.PeriodicDamagePercent); + target.RemoveAurasByType(AuraType.PeriodicLeech); + } + } + } + + public override void Register() + { + OnEffectHitTarget.Add(new EffectHandler(HandleScriptEffect, 0, SpellEffectName.ApplyAura)); + } + } + + public override SpellScript GetSpellScript() + { + return new spell_warl_seduction_SpellScript(); + } + } + + [Script] // 27285 - Seed of Corruption + class spell_warl_seed_of_corruption : SpellScriptLoader + { + public spell_warl_seed_of_corruption() : base("spell_warl_seed_of_corruption") { } + + class spell_warl_seed_of_corruption_SpellScript : SpellScript + { + void FilterTargets(List targets) + { + if (GetExplTargetUnit()) + targets.Remove(GetExplTargetUnit()); + } + + public override void Register() + { + OnObjectAreaTargetSelect.Add(new ObjectAreaTargetSelectHandler(FilterTargets, 0, Targets.UnitDestAreaEnemy)); + } + } + + public override SpellScript GetSpellScript() + { + return new spell_warl_seed_of_corruption_SpellScript(); + } + } + + [Script] // 27243 - Seed of Corruption + class spell_warl_seed_of_corruption_dummy : SpellScriptLoader + { + public spell_warl_seed_of_corruption_dummy() : base("spell_warl_seed_of_corruption_dummy") { } + + class spell_warl_seed_of_corruption_dummy_AuraScript : AuraScript + { + public override bool Validate(SpellInfo spellInfo) + { + return ValidateSpellInfo(SpellIds.SeedOfCorruptionDamage); + } + + void HandleProc(AuraEffect aurEff, ProcEventInfo eventInfo) + { + PreventDefaultAction(); + DamageInfo damageInfo = eventInfo.GetDamageInfo(); + if (damageInfo == null) + return; + + int amount = (int)(aurEff.GetAmount() - damageInfo.GetDamage()); + if (amount > 0) + { + aurEff.SetAmount(amount); + if (!GetTarget().HealthBelowPctDamaged(1, damageInfo.GetDamage())) + return; + } + + Remove(); + + Unit caster = GetCaster(); + if (!caster) + return; + + caster.CastSpell(eventInfo.GetActionTarget(), SpellIds.SeedOfCorruptionDamage, true); + } + + public override void Register() + { + OnEffectProc.Add(new EffectProcHandler(HandleProc, 1, AuraType.Dummy)); + } + } + + public override AuraScript GetAuraScript() + { + return new spell_warl_seed_of_corruption_dummy_AuraScript(); + } + } + + // 32863 - Seed of Corruption + // 36123 - Seed of Corruption + // 38252 - Seed of Corruption + // 39367 - Seed of Corruption + // 44141 - Seed of Corruption + // 70388 - Seed of Corruption + [Script] // Monster spells, triggered only on amount drop (not on death) + class spell_warl_seed_of_corruption_generic : SpellScriptLoader + { + public spell_warl_seed_of_corruption_generic() : base("spell_warl_seed_of_corruption_generic") { } + + class spell_warl_seed_of_corruption_generic_AuraScript : AuraScript + { + public override bool Validate(SpellInfo spellInfo) + { + return ValidateSpellInfo(SpellIds.SeedOfCorruptionGeneric); + } + + void HandleProc(AuraEffect aurEff, ProcEventInfo eventInfo) + { + PreventDefaultAction(); + DamageInfo damageInfo = eventInfo.GetDamageInfo(); + if (damageInfo == null) + return; + + int amount = aurEff.GetAmount() - (int)damageInfo.GetDamage(); + if (amount > 0) + { + aurEff.SetAmount(amount); + return; + } + + Remove(); + + Unit caster = GetCaster(); + if (!caster) + return; + + caster.CastSpell(eventInfo.GetActionTarget(), SpellIds.SeedOfCorruptionGeneric, true); + } + + public override void Register() + { + OnEffectProc.Add(new EffectProcHandler(HandleProc, 1, AuraType.Dummy)); + } + } + + public override AuraScript GetAuraScript() + { + return new spell_warl_seed_of_corruption_generic_AuraScript(); + } + } + + [Script] // -7235 - Shadow Ward + class spell_warl_shadow_ward : SpellScriptLoader + { + public spell_warl_shadow_ward() : base("spell_warl_shadow_ward") { } + + class spell_warl_shadow_ward_AuraScript : AuraScript + { + void CalculateAmount(AuraEffect aurEff, ref int amount, ref bool canBeRecalculated) + { + canBeRecalculated = false; + Unit caster = GetCaster(); + if (caster) + { + // +80.68% from sp bonus + float bonus = 0.8068f; + + bonus *= caster.SpellBaseHealingBonusDone(GetSpellInfo().GetSchoolMask()); + bonus *= caster.CalculateLevelPenalty(GetSpellInfo()); + + amount += (int)bonus; + } + } + + public override void Register() + { + DoEffectCalcAmount.Add(new EffectCalcAmountHandler(CalculateAmount, 0, AuraType.SchoolAbsorb)); + } + } + + public override AuraScript GetAuraScript() + { + return new spell_warl_shadow_ward_AuraScript(); + } + } + + [Script] // -30293 - Soul Leech + class spell_warl_soul_leech : SpellScriptLoader + { + public spell_warl_soul_leech() : base("spell_warl_soul_leech") { } + + class spell_warl_soul_leech_AuraScript : AuraScript + { + public override bool Validate(SpellInfo spellInfo) + { + return ValidateSpellInfo(SpellIds.GenReplenishment); + } + + void onProc(AuraEffect aurEff, ProcEventInfo eventInfo) + { + GetTarget().CastSpell((Unit)null, SpellIds.GenReplenishment, true, null, aurEff); + } + + public override void Register() + { + OnEffectProc.Add(new EffectProcHandler(onProc, 0, AuraType.ProcTriggerSpellWithValue)); + } + } + + public override AuraScript GetAuraScript() + { + return new spell_warl_soul_leech_AuraScript(); + } + } + + [Script] // 86121 - Soul Swap + class spell_warl_soul_swap : SpellScriptLoader + { + public spell_warl_soul_swap() : base("spell_warl_soul_swap") { } + + class spell_warl_soul_swap_SpellScript : SpellScript + { + public override bool Validate(SpellInfo spellInfo) + { + return ValidateSpellInfo(SpellIds.GlyphOfSoulSwap, SpellIds.SoulSwapCdMarker, SpellIds.SoulSwapOverride); + } + + void HandleHit(uint effIndex) + { + GetCaster().CastSpell(GetCaster(), SpellIds.SoulSwapOverride, true); + GetHitUnit().CastSpell(GetCaster(), SpellIds.SoulSwapDotMarker, true); + } + + public override void Register() + { + OnEffectHitTarget.Add(new EffectHandler(HandleHit, 0, SpellEffectName.SchoolDamage)); + } + } + + public override SpellScript GetSpellScript() + { + return new spell_warl_soul_swap_SpellScript(); + } + } + + [Script] // 86211 - Soul Swap - Also acts as a dot container + class spell_warl_soul_swap_override : SpellScriptLoader + { + public spell_warl_soul_swap_override() : base(nameof(spell_warl_soul_swap_override)) { } + + public class spell_warl_soul_swap_override_AuraScript : AuraScript + { + //! Forced to, pure virtual functions must have a body when linking + public override void Register() { } + + public void AddDot(uint id) { _dotList.Add(id); } + public List GetDotList() { return _dotList; } + public Unit GetOriginalSwapSource() { return _swapCaster; } + public void SetOriginalSwapSource(Unit victim) { _swapCaster = victim; } + + + List _dotList = new List(); + Unit _swapCaster = null; + } + + public override AuraScript GetAuraScript() + { + return new spell_warl_soul_swap_override_AuraScript(); + } + } + + [Script] //! Soul Swap Copy Spells - 92795 - Simply copies spell IDs. + class spell_warl_soul_swap_dot_marker : SpellScriptLoader + { + public spell_warl_soul_swap_dot_marker() : base("spell_warl_soul_swap_dot_marker") { } + + class spell_warl_soul_swap_dot_marker_SpellScript : SpellScript + { + void HandleHit(uint effIndex) + { + Unit swapVictim = GetCaster(); + Unit warlock = GetHitUnit(); + if (!warlock || !swapVictim) + return; + + var appliedAuras = swapVictim.GetAppliedAuras(); + SoulSwapOverrideAuraScript swapSpellScript = null; + Aura swapOverrideAura = warlock.GetAura(SpellIds.SoulSwapOverride); + if (swapOverrideAura != null) + swapSpellScript = swapOverrideAura.GetScript(nameof(spell_warl_soul_swap_override)); + + if (swapSpellScript == null) + return; + + FlagArray128 classMask = GetEffectInfo().SpellClassMask; + + foreach (var itr in appliedAuras) + { + SpellInfo spellProto = itr.Value.GetBase().GetSpellInfo(); + if (itr.Value.GetBase().GetCaster() == warlock) + if (spellProto.SpellFamilyName == SpellFamilyNames.Warlock && (spellProto.SpellFamilyFlags & classMask)) + swapSpellScript.AddDot(itr.Key); + } + + swapSpellScript.SetOriginalSwapSource(swapVictim); + } + + public override void Register() + { + OnEffectHitTarget.Add(new EffectHandler(HandleHit, 0, SpellEffectName.Dummy)); + } + } + + public override SpellScript GetSpellScript() + { + return new spell_warl_soul_swap_dot_marker_SpellScript(); + } + } + + [Script] // 86213 - Soul Swap Exhale + class spell_warl_soul_swap_exhale : SpellScriptLoader + { + public spell_warl_soul_swap_exhale() : base("spell_warl_soul_swap_exhale") { } + + class spell_warl_soul_swap_exhale_SpellScript : SpellScript + { + public override bool Validate(SpellInfo spellInfo) + { + return ValidateSpellInfo(SpellIds.SoulSwapModCost, SpellIds.SoulSwapOverride); + } + + SpellCastResult CheckCast() + { + Unit currentTarget = GetExplTargetUnit(); + Unit swapTarget = null; + Aura swapOverride = GetCaster().GetAura(SpellIds.SoulSwapOverride); + if (swapOverride != null) + { + SoulSwapOverrideAuraScript swapScript = swapOverride.GetScript(nameof(spell_warl_soul_swap_override)); + if (swapScript != null) + swapTarget = swapScript.GetOriginalSwapSource(); + } + + // Soul Swap Exhale can't be cast on the same target than Soul Swap + if (swapTarget && currentTarget && swapTarget == currentTarget) + return SpellCastResult.BadTargets; + + return SpellCastResult.SpellCastOk; + } + + void onEffectHit(uint effIndex) + { + GetCaster().CastSpell(GetCaster(), SpellIds.SoulSwapModCost, true); + bool hasGlyph = GetCaster().HasAura(SpellIds.GlyphOfSoulSwap); + + List dotList = new List(); + Unit swapSource = null; + Aura swapOverride = GetCaster().GetAura(SpellIds.SoulSwapOverride); + if (swapOverride != null) + { + SoulSwapOverrideAuraScript swapScript = swapOverride.GetScript(nameof(spell_warl_soul_swap_override)); + if (swapScript == null) + return; + dotList = swapScript.GetDotList(); + swapSource = swapScript.GetOriginalSwapSource(); + } + + if (dotList.Empty()) + return; + + foreach (var itr in dotList) + { + GetCaster().AddAura(itr, GetHitUnit()); + if (!hasGlyph && swapSource) + swapSource.RemoveAurasDueToSpell(itr); + } + + // Remove Soul Swap Exhale buff + GetCaster().RemoveAurasDueToSpell(SpellIds.SoulSwapOverride); + + if (hasGlyph) // Add a cooldown on Soul Swap if caster has the glyph + GetCaster().CastSpell(GetCaster(), SpellIds.SoulSwapCdMarker, false); + } + + public override void Register() + { + OnCheckCast.Add(new CheckCastHandler(CheckCast)); + OnEffectHitTarget.Add(new EffectHandler(onEffectHit, 0, SpellEffectName.SchoolDamage)); + } + } + + public override SpellScript GetSpellScript() + { + return new spell_warl_soul_swap_exhale_SpellScript(); + } + } + + [Script] // 29858 - Soulshatter + class spell_warl_soulshatter : SpellScriptLoader + { + public spell_warl_soulshatter() : base("spell_warl_soulshatter") { } + + class spell_warl_soulshatter_SpellScript : SpellScript + { + public override bool Validate(SpellInfo spellInfo) + { + return ValidateSpellInfo(SpellIds.Soulshatter); + } + + void HandleDummy(uint effIndex) + { + Unit caster = GetCaster(); + Unit target = GetHitUnit(); + if (target) + if (target.CanHaveThreatList() && target.GetThreatManager().getThreat(caster) > 0.0f) + caster.CastSpell(target, SpellIds.Soulshatter, true); + } + + public override void Register() + { + OnEffectHitTarget.Add(new EffectHandler(HandleDummy, 0, SpellEffectName.Dummy)); + } + } + + public override SpellScript GetSpellScript() + { + return new spell_warl_soulshatter_SpellScript(); + } + } + + [Script("spell_warl_t4_2p_bonus_shadow", SpellIds.Flameshadow)]// 37377 - Shadowflame + [Script("spell_warl_t4_2p_bonus_fire", SpellIds.Shadowflame)]// 39437 - Shadowflame Hellfire and RoF + class spell_warl_t4_2p_bonus : SpellScriptLoader + { + public spell_warl_t4_2p_bonus(string scriptName, uint triggerSpell) : base(scriptName) + { + _triggerSpell = triggerSpell; + } + + class spell_warl_t4_2p_bonus_AuraScript : AuraScript + { + public spell_warl_t4_2p_bonus_AuraScript(uint triggerSpell) + { + _triggerSpell = triggerSpell; + } + + public override bool Validate(SpellInfo spellInfo) + { + return ValidateSpellInfo(_triggerSpell); + } + + void HandleProc(AuraEffect aurEff, ProcEventInfo eventInfo) + { + PreventDefaultAction(); + Unit caster = eventInfo.GetActor(); + caster.CastSpell(caster, _triggerSpell, true); + } + + public override void Register() + { + OnEffectProc.Add(new EffectProcHandler(HandleProc, 0, AuraType.Dummy)); + } + + uint _triggerSpell; + } + + public override AuraScript GetAuraScript() + { + return new spell_warl_t4_2p_bonus_AuraScript(_triggerSpell); + } + + uint _triggerSpell; + } + + [Script] // 30108, 34438, 34439, 35183 - Unstable Affliction + class spell_warl_unstable_affliction : SpellScriptLoader + { + public spell_warl_unstable_affliction() : base("spell_warl_unstable_affliction") { } + + class spell_warl_unstable_affliction_AuraScript : AuraScript + { + public override bool Validate(SpellInfo spellInfo) + { + return ValidateSpellInfo(SpellIds.UnstableAfflictionDispel); + } + + void HandleDispel(DispelInfo dispelInfo) + { + Unit caster = GetCaster(); + if (caster) + { + AuraEffect aurEff = GetEffect(1); + if (aurEff != null) + { + int damage = aurEff.GetAmount() * 9; + // backfire damage and silence + caster.CastCustomSpell(dispelInfo.GetDispeller(), SpellIds.UnstableAfflictionDispel, damage, 0, 0, true, null, aurEff); + } + } + } + + public override void Register() + { + AfterDispel.Add(new AuraDispelHandler(HandleDispel)); + } + } + + public override AuraScript GetAuraScript() + { + return new spell_warl_unstable_affliction_AuraScript(); + } + } + + [Script] // 5740 - Rain of Fire Updated 7.1.5 + class spell_warl_rain_of_fire : SpellScriptLoader + { + public spell_warl_rain_of_fire() : base("spell_warl_rain_of_fire") { } + + class spell_warl_rain_of_fire_AuraScript : AuraScript + { + void HandleDummyTick(AuraEffect aurEff) + { + List rainOfFireAreaTriggers = GetTarget().GetAreaTriggers(SpellIds.RainOfFire); + List targetsInRainOfFire = new List(); + + foreach (AreaTrigger rainOfFireAreaTrigger in rainOfFireAreaTriggers) + { + var insideTargets = rainOfFireAreaTrigger.GetInsideUnits(); + targetsInRainOfFire.AddRange(insideTargets); + } + + foreach (ObjectGuid insideTargetGuid in targetsInRainOfFire) + { + Unit insideTarget = Global.ObjAccessor.GetUnit(GetTarget(), insideTargetGuid); + if (insideTarget) + if (!GetTarget().IsFriendlyTo(insideTarget)) + GetTarget().CastSpell(insideTarget, SpellIds.RainOfFireDamage, true); + } + } + + public override void Register() + { + OnEffectPeriodic.Add(new EffectPeriodicHandler(HandleDummyTick, 3, AuraType.PeriodicDummy)); + } + } + + public override AuraScript GetAuraScript() + { + return new spell_warl_rain_of_fire_AuraScript(); + } + } +} diff --git a/Scripts/Spells/Warrior.cs b/Scripts/Spells/Warrior.cs new file mode 100644 index 000000000..48be1306d --- /dev/null +++ b/Scripts/Spells/Warrior.cs @@ -0,0 +1,1272 @@ +/* + * Copyright (C) 2012-2017 CypherCore + * + * 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, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +using Framework.Constants; +using Framework.GameMath; +using Game.Entities; +using Game.Movement; +using Game.Scripting; +using Game.Spells; +using System; +using System.Collections.Generic; + +namespace Scripts.Spells.Warrior +{ + struct SpellIds + { + public const uint BloodthirstHeal = 117313; + public const uint Charge = 34846; + public const uint ChargeEffect = 218104; + public const uint ChargeEffectBlazingTrail = 198337; + public const uint ChargePauseRageDecay = 109128; + public const uint ChargeRootEffect = 105771; + public const uint ChargeSlowEffect = 236027; + public const uint ColossusSmash = 86346; + public const uint Execute = 20647; + public const uint GlyphOfTheBlazingTrail = 123779; + public const uint GlyphOfHeroicLeap = 159708; + public const uint GlyphOfHeroicLeapBuff = 133278; + public const uint HeroicLeapJump = 178368; + public const uint ImpendingVictory = 202168; + public const uint ImpendingVictoryHeal = 202166; + public const uint ImprovedHeroicLeap = 157449; + public const uint JuggernautCritBonusBuff = 65156; + public const uint JuggernautCritBonusTalent = 64976; + public const uint LastStandTriggered = 12976; + public const uint MortalStrike = 12294; + public const uint RallyingCry = 97463; + public const uint Rend = 94009; + public const uint RetaliationDamage = 22858; + public const uint SecoundWindProcRank1 = 29834; + public const uint SecoundWindProcRank2 = 29838; + public const uint SecoundWindTriggerRank1 = 29841; + public const uint SecoundWindTriggerRank2 = 29842; + public const uint ShieldSlam = 23922; + public const uint Shockwave = 46968; + public const uint ShockwaveStun = 132168; + public const uint Slam = 50782; + public const uint Stoicism = 70845; + public const uint StormBoltStun = 132169; + public const uint SweepingStrikesExtraAttack = 26654; + public const uint Taunt = 355; + public const uint TraumaEffect = 215537; + public const uint UnrelentingAssaultRank1 = 46859; + public const uint UnrelentingAssaultRank2 = 46860; + public const uint UnrelentingAssaultTrigger1 = 64849; + public const uint UnrelentingAssaultTrigger2 = 64850; + public const uint Vengeance = 76691; + public const uint Victorious = 32216; + public const uint VictoriousRushHeal = 118779; + } + + struct Misc + { + public const uint SpellVisualBlazingCharge = 26423; + } + + [Script] // 23881 - Bloodthirst + class spell_warr_bloodthirst : SpellScriptLoader + { + public spell_warr_bloodthirst() : base("spell_warr_bloodthirst") { } + + class spell_warr_bloodthirst_SpellScript : SpellScript + { + public override bool Validate(SpellInfo spellInfo) + { + return ValidateSpellInfo(SpellIds.BloodthirstHeal); + } + + void HandleDummy(uint effIndex) + { + GetCaster().CastSpell(GetCaster(), SpellIds.BloodthirstHeal, true); + } + + public override void Register() + { + OnEffectHit.Add(new EffectHandler(HandleDummy, 3, SpellEffectName.Dummy)); + } + } + + public override SpellScript GetSpellScript() + { + return new spell_warr_bloodthirst_SpellScript(); + } + } + + [Script] // 100 - Charge + class spell_warr_charge : SpellScriptLoader + { + public spell_warr_charge() : base("spell_warr_charge") { } + + class spell_warr_charge_SpellScript : SpellScript + { + public override bool Validate(SpellInfo spellInfo) + { + return ValidateSpellInfo(SpellIds.ChargeEffect, SpellIds.ChargeEffectBlazingTrail); + } + + void HandleDummy(uint effIndex) + { + uint spellId = SpellIds.ChargeEffect; + if (GetCaster().HasAura(SpellIds.GlyphOfTheBlazingTrail)) + spellId = SpellIds.ChargeEffectBlazingTrail; + + GetCaster().CastSpell(GetHitUnit(), spellId, true); + } + + public override void Register() + { + OnEffectHitTarget.Add(new EffectHandler(HandleDummy, 0, SpellEffectName.Dummy)); + } + } + + public override SpellScript GetSpellScript() + { + return new spell_warr_charge_SpellScript(); + } + } + + [Script] // 126661 - Warrior Charge Drop Fire Periodic + class spell_warr_charge_drop_fire_periodic : SpellScriptLoader + { + public spell_warr_charge_drop_fire_periodic() : base("spell_warr_charge_drop_fire_periodic") { } + + class spell_warr_charge_drop_fire_periodic_AuraScript : AuraScript + { + void DropFireVisual(AuraEffect aurEff) + { + PreventDefaultAction(); + if (GetTarget().IsSplineEnabled()) + { + for (uint i = 0; i < 5; ++i) + { + int timeOffset = (int)(6 * i * aurEff.GetPeriod() / 25); + Vector4 loc = GetTarget().moveSpline.ComputePosition(timeOffset); + GetTarget().SendPlaySpellVisual(new Vector3(loc.X, loc.Y, loc.Z), 0.0f, Misc.SpellVisualBlazingCharge, 0, 0, 1.0f, true); + } + } + } + + public override void Register() + { + OnEffectPeriodic.Add(new EffectPeriodicHandler(DropFireVisual, 0, AuraType.PeriodicTriggerSpell)); + } + } + + public override AuraScript GetAuraScript() + { + return new spell_warr_charge_drop_fire_periodic_AuraScript(); + } + } + + // 198337 - Charge Effect (dropping Blazing Trail) + [Script] // 218104 - Charge Effect + class spell_warr_charge_effect : SpellScriptLoader + { + public spell_warr_charge_effect() : base("spell_warr_charge_effect") { } + + class spell_warr_charge_effect_SpellScript : SpellScript + { + public override bool Validate(SpellInfo spellInfo) + { + return ValidateSpellInfo(SpellIds.ChargePauseRageDecay, SpellIds.ChargeRootEffect, SpellIds.ChargeSlowEffect); + } + + void HandleCharge(uint effIndex) + { + Unit caster = GetCaster(); + Unit target = GetHitUnit(); + caster.CastCustomSpell(SpellIds.ChargePauseRageDecay, SpellValueMod.BasePoint0, 0, caster, true); + caster.CastSpell(target, SpellIds.ChargeRootEffect, true); + caster.CastSpell(target, SpellIds.ChargeSlowEffect, true); + } + + public override void Register() + { + OnEffectLaunchTarget.Add(new EffectHandler(HandleCharge, 0, SpellEffectName.Charge)); + } + } + + public override SpellScript GetSpellScript() + { + return new spell_warr_charge_effect_SpellScript(); + } + } + + // Updated 4.3.4 + [Script] + class spell_warr_concussion_blow : SpellScriptLoader + { + public spell_warr_concussion_blow() : base("spell_warr_concussion_blow") { } + + class spell_warr_concussion_blow_SpellScript : SpellScript + { + void HandleDummy(uint effIndex) + { + SetHitDamage((int)MathFunctions.CalculatePct(GetCaster().GetTotalAttackPowerValue(WeaponAttackType.BaseAttack), GetEffectValue())); + } + + public override void Register() + { + OnEffectHitTarget.Add(new EffectHandler(HandleDummy, 2, SpellEffectName.Dummy)); + } + } + + public override SpellScript GetSpellScript() + { + return new spell_warr_concussion_blow_SpellScript(); + } + } + + // Updated 4.3.4 + [Script] // 5308 - Execute + class spell_warr_execute : SpellScriptLoader + { + public spell_warr_execute() : base("spell_warr_execute") { } + + class spell_warr_execute_SpellScript : SpellScript + { + void HandleEffect(uint effIndex) + { + /* + Unit caster = GetCaster(); + if (GetHitUnit()) + { + SpellInfo spellInfo = GetSpellInfo(); + int rageUsed = Math.Min(200 - spellInfo.CalcPowerCost(caster, spellInfo.SchoolMask), caster.GetPower(PowerType.Rage)); + int newRage = Math.Max(0, caster.GetPower(PowerType.Rage) - rageUsed); + + // Sudden Death rage save + AuraEffect aurEff = caster.GetAuraEffect(AuraType.ProcTriggerSpell, SpellFamilyNames.Generic, 1989, 0); // Icon SuddenDeath + if (aurEff != null) + { + int ragesave = aurEff.GetSpellInfo().GetEffect(0).CalcValue() * 10; + newRage = Math.Max(newRage, ragesave); + } + + caster.SetPower(PowerType.Rage, newRage); + + // Formula taken from the DBC: "${10+$AP*0.437*$m1/100}" + int baseDamage = (int)(10 + caster.GetTotalAttackPowerValue(WeaponAttackType.BaseAttack) * 0.437f * GetEffectValue() / 100.0f); + // Formula taken from the DBC: "${$ap*0.874*$m1/100-1} = 20 rage" + int moreDamage = (int)(rageUsed * (caster.GetTotalAttackPowerValue(WeaponAttackType.BaseAttack) * 0.874f * GetEffectValue() / 100.0f - 1) / 200); + SetHitDamage(baseDamage + moreDamage); + } + */ + } + + public override void Register() + { + OnEffectHitTarget.Add(new EffectHandler(HandleEffect, 0, SpellEffectName.SchoolDamage)); + } + } + + public override SpellScript GetSpellScript() + { + return new spell_warr_execute_SpellScript(); + } + } + + // 58387 - Glyph of Sunder Armor + [Script] + class spell_warr_glyph_of_sunder_armor : SpellScriptLoader + { + public spell_warr_glyph_of_sunder_armor() : base("spell_warr_glyph_of_sunder_armor") { } + + class spell_warr_glyph_of_sunder_armor_AuraScript : AuraScript + { + void HandleEffectCalcSpellMod(AuraEffect aurEff, ref SpellModifier spellMod) + { + if (spellMod == null) + { + spellMod = new SpellModifier(aurEff.GetBase()); + spellMod.op = (SpellModOp)aurEff.GetMiscValue(); + spellMod.type = SpellModType.Flat; + spellMod.spellId = GetId(); + spellMod.mask = GetSpellInfo().GetEffect(aurEff.GetEffIndex()).SpellClassMask; + } + + spellMod.value = aurEff.GetAmount(); + } + + public override void Register() + { + DoEffectCalcSpellMod.Add(new EffectCalcSpellModHandler(HandleEffectCalcSpellMod, 0, AuraType.Dummy)); + } + } + + public override AuraScript GetAuraScript() + { + return new spell_warr_glyph_of_sunder_armor_AuraScript(); + } + } + + [Script] // Heroic leap - 6544 + class spell_warr_heroic_leap : SpellScriptLoader + { + public spell_warr_heroic_leap() : base("spell_warr_heroic_leap") { } + + class spell_warr_heroic_leap_SpellScript : SpellScript + { + public override bool Validate(SpellInfo spellInfo) + { + return ValidateSpellInfo(SpellIds.HeroicLeapJump); + } + + SpellCastResult CheckElevation() + { + WorldLocation dest = GetExplTargetDest(); + if (dest != null) + { + if (GetCaster().HasUnitMovementFlag(MovementFlag.Root)) + return SpellCastResult.Rooted; + + if (GetCaster().GetMap().Instanceable()) + { + float range = GetSpellInfo().GetMaxRange(true, GetCaster()) * 1.5f; + + PathGenerator generatedPath = new PathGenerator(GetCaster()); + generatedPath.SetPathLengthLimit(range); + + bool result = generatedPath.CalculatePath(dest.GetPositionX(), dest.GetPositionY(), dest.GetPositionZ(), false, true); + if (generatedPath.GetPathType().HasAnyFlag(PathType.Short)) + return SpellCastResult.OutOfRange; + else if (!result || generatedPath.GetPathType().HasAnyFlag(PathType.NoPath)) + { + result = generatedPath.CalculatePath(dest.GetPositionX(), dest.GetPositionY(), dest.GetPositionZ(), false, false); + if (generatedPath.GetPathType().HasAnyFlag(PathType.Short)) + return SpellCastResult.OutOfRange; + else if (!result || generatedPath.GetPathType().HasAnyFlag(PathType.NoPath)) + return SpellCastResult.NoPath; + } + } + else if (dest.GetPositionZ() > GetCaster().GetPositionZ() + 4.0f) + return SpellCastResult.NoPath; + + return SpellCastResult.SpellCastOk; + } + + return SpellCastResult.NoValidTargets; + } + + void HandleDummy(uint effIndex) + { + WorldLocation dest = GetHitDest(); + if (dest != null) + GetCaster().CastSpell(dest.GetPositionX(), dest.GetPositionY(), dest.GetPositionZ(), SpellIds.HeroicLeapJump, true); + } + + public override void Register() + { + OnCheckCast.Add(new CheckCastHandler(CheckElevation)); + OnEffectHit.Add(new EffectHandler(HandleDummy, 0, SpellEffectName.Dummy)); + } + } + + public override SpellScript GetSpellScript() + { + return new spell_warr_heroic_leap_SpellScript(); + } + } + + [Script] // Heroic Leap (triggered by Heroic Leap (6544)) - 178368 + class spell_warr_heroic_leap_jump : SpellScriptLoader + { + public spell_warr_heroic_leap_jump() : base("spell_warr_heroic_leap_jump") { } + + class spell_warr_heroic_leap_jump_SpellScript : SpellScript + { + public override bool Validate(SpellInfo spellInfo) + { + return ValidateSpellInfo(SpellIds.GlyphOfHeroicLeap, + SpellIds.GlyphOfHeroicLeapBuff, + SpellIds.ImprovedHeroicLeap, + SpellIds.Taunt); + } + + void AfterJump(uint effIndex) + { + if (GetCaster().HasAura(SpellIds.GlyphOfHeroicLeap)) + GetCaster().CastSpell(GetCaster(), SpellIds.GlyphOfHeroicLeapBuff, true); + if (GetCaster().HasAura(SpellIds.ImprovedHeroicLeap)) + GetCaster().GetSpellHistory().ResetCooldown(SpellIds.Taunt, true); + } + + public override void Register() + { + OnEffectHit.Add(new EffectHandler(AfterJump, 1, SpellEffectName.JumpDest)); + } + } + + public override SpellScript GetSpellScript() + { + return new spell_warr_heroic_leap_jump_SpellScript(); + } + } + + [Script] // 202168 - Impending Victory + class spell_warr_impending_victory : SpellScriptLoader + { + public spell_warr_impending_victory() : base("spell_warr_impending_victory") { } + + class spell_warr_impending_victory_SpellScript : SpellScript + { + public override bool Validate(SpellInfo spellInfo) + { + return ValidateSpellInfo(SpellIds.ImpendingVictoryHeal); + } + + void HandleAfterCast() + { + Unit caster = GetCaster(); + caster.CastSpell(caster, SpellIds.ImpendingVictoryHeal, true); + caster.RemoveAurasDueToSpell(SpellIds.Victorious); + } + + public override void Register() + { + AfterCast.Add(new CastHandler(HandleAfterCast)); + } + } + + public override SpellScript GetSpellScript() + { + return new spell_warr_impending_victory_SpellScript(); + } + } + + // 5246 - Intimidating Shout + [Script] + class spell_warr_intimidating_shout : SpellScriptLoader + { + public spell_warr_intimidating_shout() : base("spell_warr_intimidating_shout") { } + + class spell_warr_intimidating_shout_SpellScript : SpellScript + { + void FilterTargets(List unitList) + { + unitList.Remove(GetExplTargetWorldObject()); + } + + public override void Register() + { + OnObjectAreaTargetSelect.Add(new ObjectAreaTargetSelectHandler(FilterTargets, 1, Targets.UnitSrcAreaEnemy)); + OnObjectAreaTargetSelect.Add(new ObjectAreaTargetSelectHandler(FilterTargets, 2, Targets.UnitSrcAreaEnemy)); + } + } + + public override SpellScript GetSpellScript() + { + return new spell_warr_intimidating_shout_SpellScript(); + } + } + + // 70844 - Item - Warrior T10 Protection 4P Bonus + [Script] /// 7.1.5 + class spell_warr_item_t10_prot_4p_bonus : SpellScriptLoader + { + public spell_warr_item_t10_prot_4p_bonus() : base("spell_warr_item_t10_prot_4p_bonus") { } + + class spell_warr_item_t10_prot_4p_bonus_AuraScript : AuraScript + { + public override bool Validate(SpellInfo spellInfo) + { + return ValidateSpellInfo(SpellIds.Stoicism); + } + + void HandleProc(ProcEventInfo eventInfo) + { + PreventDefaultAction(); + + Unit target = eventInfo.GetActionTarget(); + int bp0 = (int)MathFunctions.CalculatePct(target.GetMaxHealth(), GetSpellInfo().GetEffect(1).CalcValue()); + target.CastCustomSpell(SpellIds.Stoicism, SpellValueMod.BasePoint0, bp0, (Unit)null, true); + } + + public override void Register() + { + OnProc.Add(new AuraProcHandler(HandleProc)); + } + } + + public override AuraScript GetAuraScript() + { + return new spell_warr_item_t10_prot_4p_bonus_AuraScript(); + } + } + + // -84583 Lambs to the Slaughter + [Script] + class spell_warr_lambs_to_the_slaughter : SpellScriptLoader + { + public spell_warr_lambs_to_the_slaughter() : base("spell_warr_lambs_to_the_slaughter") { } + + class spell_warr_lambs_to_the_slaughter_AuraScript : AuraScript + { + public override bool Validate(SpellInfo spellInfo) + { + return ValidateSpellInfo(SpellIds.MortalStrike, SpellIds.Rend); + } + + void HandleProc(AuraEffect aurEff, ProcEventInfo eventInfo) + { + Aura aur = eventInfo.GetProcTarget().GetAura(SpellIds.Rend, GetTarget().GetGUID()); + if (aur != null) + aur.SetDuration(aur.GetSpellInfo().GetMaxDuration(), true); + + } + + public override void Register() + { + OnEffectProc.Add(new EffectProcHandler(HandleProc, 0, AuraType.ProcTriggerSpell)); + } + } + + public override AuraScript GetAuraScript() + { + return new spell_warr_lambs_to_the_slaughter_AuraScript(); + } + } + + // Updated 4.3.4 + // 12975 - Last Stand + [Script] + class spell_warr_last_stand : SpellScriptLoader + { + public spell_warr_last_stand() : base("spell_warr_last_stand") { } + + class spell_warr_last_stand_SpellScript : SpellScript + { + public override bool Validate(SpellInfo spellInfo) + { + return ValidateSpellInfo(SpellIds.LastStandTriggered); + } + + void HandleDummy(uint effIndex) + { + Unit caster = GetCaster(); + int healthModSpellBasePoints0 = (int)(caster.CountPctFromMaxHealth(GetEffectValue())); + caster.CastCustomSpell(caster, SpellIds.LastStandTriggered, healthModSpellBasePoints0, 0, 0, true, null); + } + + public override void Register() + { + // add dummy effect spell handler to Last Stand + OnEffectHit.Add(new EffectHandler(HandleDummy, 0, SpellEffectName.Dummy)); + } + } + + public override SpellScript GetSpellScript() + { + return new spell_warr_last_stand_SpellScript(); + } + } + + // 7384 - Overpower + [Script] + class spell_warr_overpower : SpellScriptLoader + { + public spell_warr_overpower() : base("spell_warr_overpower") { } + + class spell_warr_overpower_SpellScript : SpellScript + { + void HandleEffect(uint effIndex) + { + uint spellId = 0; + if (GetCaster().HasAura(SpellIds.UnrelentingAssaultRank1)) + spellId = SpellIds.UnrelentingAssaultTrigger1; + else if (GetCaster().HasAura(SpellIds.UnrelentingAssaultRank2)) + spellId = SpellIds.UnrelentingAssaultTrigger2; + + if (spellId == 0) + return; + + Player target = GetHitPlayer(); + if (target) + if (target.IsNonMeleeSpellCast(false, false, true)) // UNIT_STATE_CASTING should not be used here, it's present during a tick for instant casts + target.CastSpell(target, spellId, true); + } + + public override void Register() + { + OnEffectHitTarget.Add(new EffectHandler(HandleEffect, 0, SpellEffectName.Any)); + } + } + + public override SpellScript GetSpellScript() + { + return new spell_warr_overpower_SpellScript(); + } + } + + // 97462 - Rallying Cry + [Script] + class spell_warr_rallying_cry : SpellScriptLoader + { + public spell_warr_rallying_cry() : base("spell_warr_rallying_cry") { } + + class spell_warr_rallying_cry_SpellScript : SpellScript + { + public override bool Validate(SpellInfo spellInfo) + { + return ValidateSpellInfo(SpellIds.RallyingCry); + } + + public override bool Load() + { + return GetCaster().IsTypeId(TypeId.Player); + } + + void HandleScript(uint effIndex) + { + int basePoints0 = (int)(GetHitUnit().CountPctFromMaxHealth(GetEffectValue())); + + GetCaster().CastCustomSpell(GetHitUnit(), SpellIds.RallyingCry, basePoints0, 0, 0, true); + } + + public override void Register() + { + OnEffectHitTarget.Add(new EffectHandler(HandleScript, 0, SpellEffectName.Dummy)); + } + } + + public override SpellScript GetSpellScript() + { + return new spell_warr_rallying_cry_SpellScript(); + } + } + + // 94009 - Rend + [Script] + class spell_warr_rend : SpellScriptLoader + { + public spell_warr_rend() : base("spell_warr_rend") { } + + class spell_warr_rend_AuraScript : AuraScript + { + void CalculateAmount(AuraEffect aurEff, ref int amount, ref bool canBeRecalculated) + { + Unit caster = GetCaster(); + if (caster) + { + canBeRecalculated = false; + + // $0.25 * (($MWB + $mwb) / 2 + $AP / 14 * $MWS) bonus per tick + float ap = caster.GetTotalAttackPowerValue(WeaponAttackType.BaseAttack); + int mws = (int)caster.GetBaseAttackTime(WeaponAttackType.BaseAttack); + float mwbMin = caster.GetWeaponDamageRange(WeaponAttackType.BaseAttack, WeaponDamageRange.MinDamage); + float mwbMax = caster.GetWeaponDamageRange(WeaponAttackType.BaseAttack, WeaponDamageRange.MaxDamage); + float mwb = ((mwbMin + mwbMax) / 2 + ap * mws / 14000) * 0.25f; + amount += (int)(caster.ApplyEffectModifiers(GetSpellInfo(), aurEff.GetEffIndex(), mwb)); + } + } + + public override void Register() + { + DoEffectCalcAmount.Add(new EffectCalcAmountHandler(CalculateAmount, 0, AuraType.PeriodicDamage)); + } + } + + public override AuraScript GetAuraScript() + { + return new spell_warr_rend_AuraScript(); + } + } + + // 20230 - Retaliation + [Script] + class spell_warr_retaliation : SpellScriptLoader + { + public spell_warr_retaliation() : base("spell_warr_retaliation") { } + + class spell_warr_retaliation_AuraScript : AuraScript + { + public override bool Validate(SpellInfo spellInfo) + { + return ValidateSpellInfo(SpellIds.RetaliationDamage); + } + + bool CheckProc(ProcEventInfo eventInfo) + { + // check attack comes not from behind and warrior is not stunned + return GetTarget().isInFront(eventInfo.GetProcTarget(), MathFunctions.PI) && !GetTarget().HasUnitState(UnitState.Stunned); + } + + void HandleEffectProc(AuraEffect aurEff, ProcEventInfo eventInfo) + { + PreventDefaultAction(); + GetTarget().CastSpell(eventInfo.GetProcTarget(), SpellIds.RetaliationDamage, true, null, aurEff); + } + + public override void Register() + { + DoCheckProc.Add(new CheckProcHandler(CheckProc)); + OnEffectProc.Add(new EffectProcHandler(HandleEffectProc, 0, AuraType.Dummy)); + } + } + + public override AuraScript GetAuraScript() + { + return new spell_warr_retaliation_AuraScript(); + } + } + + // 64380, 65941 - Shattering Throw + [Script] + class spell_warr_shattering_throw : SpellScriptLoader + { + public spell_warr_shattering_throw() : base("spell_warr_shattering_throw") { } + + class spell_warr_shattering_throw_SpellScript : SpellScript + { + void HandleScript(uint effIndex) + { + PreventHitDefaultEffect(effIndex); + + // remove shields, will still display immune to damage part + Unit target = GetHitUnit(); + if (target) + target.RemoveAurasWithMechanic(1 << (int)Mechanics.Immune_Shield, AuraRemoveMode.EnemySpell); + } + + public override void Register() + { + OnEffectHitTarget.Add(new EffectHandler(HandleScript, 0, SpellEffectName.ScriptEffect)); + } + } + + public override SpellScript GetSpellScript() + { + return new spell_warr_shattering_throw_SpellScript(); + } + } + + // Updated 4.3.4 + [Script] + class spell_warr_slam : SpellScriptLoader + { + public spell_warr_slam() : base("spell_warr_slam") { } + + class spell_warr_slam_SpellScript : SpellScript + { + public override bool Validate(SpellInfo spellInfo) + { + return ValidateSpellInfo(SpellIds.Slam); + } + + void HandleDummy(uint effIndex) + { + if (GetHitUnit()) + GetCaster().CastCustomSpell(SpellIds.Slam, SpellValueMod.BasePoint0, GetEffectValue(), GetHitUnit(), TriggerCastFlags.FullMask); + } + + public override void Register() + { + OnEffectHitTarget.Add(new EffectHandler(HandleDummy, 0, SpellEffectName.Dummy)); + } + } + + public override SpellScript GetSpellScript() + { + return new spell_warr_slam_SpellScript(); + } + } + + [Script] + class spell_warr_second_wind_proc : SpellScriptLoader + { + public spell_warr_second_wind_proc() : base("spell_warr_second_wind_proc") { } + + class spell_warr_second_wind_proc_AuraScript : AuraScript + { + public override bool Validate(SpellInfo spellInfo) + { + return ValidateSpellInfo(SpellIds.SecoundWindProcRank1, SpellIds.SecoundWindProcRank2, SpellIds.SecoundWindTriggerRank1, SpellIds.SecoundWindTriggerRank2); + } + + bool CheckProc(ProcEventInfo eventInfo) + { + if (eventInfo.GetProcTarget() == GetTarget()) + return false; + if (eventInfo.GetDamageInfo().GetSpellInfo() == null || + (eventInfo.GetDamageInfo().GetSpellInfo().GetAllEffectsMechanicMask() & ((1 << (int)Mechanics.Root) | (1 << (int)Mechanics.Stun))) == 0) + return false; + return true; + } + + void HandleProc(AuraEffect aurEff, ProcEventInfo eventInfo) + { + PreventDefaultAction(); + uint spellId = 0; + + if (GetSpellInfo().Id == SpellIds.SecoundWindProcRank1) + spellId = SpellIds.SecoundWindTriggerRank1; + else if (GetSpellInfo().Id == SpellIds.SecoundWindProcRank2) + spellId = SpellIds.SecoundWindTriggerRank2; + if (spellId == 0) + return; + + GetTarget().CastSpell(GetTarget(), spellId, true, null, aurEff); + + } + + public override void Register() + { + DoCheckProc.Add(new CheckProcHandler(CheckProc)); + OnEffectProc.Add(new EffectProcHandler(HandleProc, 0, AuraType.Dummy)); + } + } + + public override AuraScript GetAuraScript() + { + return new spell_warr_second_wind_proc_AuraScript(); + } + } + + [Script] + class spell_warr_second_wind_trigger : SpellScriptLoader + { + public spell_warr_second_wind_trigger() : base("spell_warr_second_wind_trigger") { } + + class spell_warr_second_wind_trigger_AuraScript : AuraScript + { + void CalculateAmount(AuraEffect aurEff, ref int amount, ref bool canBeRecalculated) + { + amount = (int)(GetUnitOwner().CountPctFromMaxHealth(amount)); + } + + public override void Register() + { + DoEffectCalcAmount.Add(new EffectCalcAmountHandler(CalculateAmount, 1, AuraType.PeriodicHeal)); + } + } + + public override AuraScript GetAuraScript() + { + return new spell_warr_second_wind_trigger_AuraScript(); + } + } + + [Script] // 46968 - Shockwave + class spell_warr_shockwave : SpellScriptLoader + { + public spell_warr_shockwave() : base("spell_warr_shockwave") { } + + class spell_warr_shockwave_SpellScript : SpellScript + { + public override bool Validate(SpellInfo spellInfo) + { + if (!ValidateSpellInfo(SpellIds.Shockwave, SpellIds.ShockwaveStun)) + return false; + + return spellInfo.GetEffect(0) != null && spellInfo.GetEffect(3) != null; + } + + public override bool Load() + { + return GetCaster().IsTypeId(TypeId.Player); + } + + void HandleStun(uint effIndex) + { + GetCaster().CastSpell(GetHitUnit(), SpellIds.ShockwaveStun, true); + ++_targetCount; + } + + // Cooldown reduced by 20 sec if it strikes at least 3 targets. + void HandleAfterCast() + { + if (_targetCount >= (uint)GetSpellInfo().GetEffect(0).CalcValue()) + GetCaster().ToPlayer().GetSpellHistory().ModifyCooldown(GetSpellInfo().Id, -(GetSpellInfo().GetEffect(3).CalcValue() * Time.InMilliseconds)); + } + + public override void Register() + { + OnEffectHitTarget.Add(new EffectHandler(HandleStun, 0, SpellEffectName.Dummy)); + AfterCast.Add(new CastHandler(HandleAfterCast)); + } + + uint _targetCount = 0; + } + + public override SpellScript GetSpellScript() + { + return new spell_warr_shockwave_SpellScript(); + } + } + + [Script] // 107570 - Storm Bolt + class spell_warr_storm_bolt : SpellScriptLoader + { + public spell_warr_storm_bolt() : base("spell_warr_storm_bolt") { } + + class spell_warr_storm_bolt_SpellScript : SpellScript + { + public override bool Validate(SpellInfo spellInfo) + { + return ValidateSpellInfo(SpellIds.StormBoltStun); + } + + void HandleOnHit(uint effIndex) + { + GetCaster().CastSpell(GetHitUnit(), SpellIds.StormBoltStun, true); + } + + public override void Register() + { + OnEffectHitTarget.Add(new EffectHandler(HandleOnHit, 1, SpellEffectName.Dummy)); + } + } + + public override SpellScript GetSpellScript() + { + return new spell_warr_storm_bolt_SpellScript(); + } + } + + // 52437 - Sudden Death + [Script] + class spell_warr_sudden_death : SpellScriptLoader + { + public spell_warr_sudden_death() : base("spell_warr_sudden_death") { } + + class spell_warr_sudden_death_AuraScript : AuraScript + { + public override bool Validate(SpellInfo spellInfo) + { + return ValidateSpellInfo(SpellIds.ColossusSmash); + } + + void HandleApply(AuraEffect aurEff, AuraEffectHandleModes mode) + { + // Remove cooldown on Colossus Smash + Player player = GetTarget().ToPlayer(); + if (player) + player.GetSpellHistory().ResetCooldown(SpellIds.ColossusSmash, true); + } + + public override void Register() + { + AfterEffectApply.Add(new EffectApplyHandler(HandleApply, 0, AuraType.Dummy, AuraEffectHandleModes.Real)); // correct? + } + } + + public override AuraScript GetAuraScript() + { + return new spell_warr_sudden_death_AuraScript(); + } + } + + // 12328, 18765, 35429 - Sweeping Strikes + [Script] + class spell_warr_sweeping_strikes : SpellScriptLoader + { + public spell_warr_sweeping_strikes() : base("spell_warr_sweeping_strikes") { } + + class spell_warr_sweeping_strikes_AuraScript : AuraScript + { + public override bool Validate(SpellInfo spellInfo) + { + return ValidateSpellInfo(SpellIds.SweepingStrikesExtraAttack); + } + + public override bool Load() + { + _procTarget = null; + return true; + } + + bool CheckProc(ProcEventInfo eventInfo) + { + _procTarget = eventInfo.GetActor().SelectNearbyTarget(eventInfo.GetProcTarget()); + return _procTarget; + } + + void HandleProc(AuraEffect aurEff, ProcEventInfo eventInfo) + { + PreventDefaultAction(); + GetTarget().CastSpell(_procTarget, SpellIds.SweepingStrikesExtraAttack, true, null, aurEff); + } + + public override void Register() + { + DoCheckProc.Add(new CheckProcHandler(CheckProc)); + OnEffectProc.Add(new EffectProcHandler(HandleProc, 0, AuraType.Dummy)); + } + + Unit _procTarget; + } + + public override AuraScript GetAuraScript() + { + return new spell_warr_sweeping_strikes_AuraScript(); + } + } + + // -46951 - Sword and Board + [Script] + class spell_warr_sword_and_board : SpellScriptLoader + { + public spell_warr_sword_and_board() : base("spell_warr_sword_and_board") { } + + class spell_warr_sword_and_board_AuraScript : AuraScript + { + public override bool Validate(SpellInfo spellInfo) + { + return ValidateSpellInfo(SpellIds.ShieldSlam); + } + + void HandleProc(AuraEffect aurEff, ProcEventInfo eventInfo) + { + // Remove cooldown on Shield Slam + Player player = GetTarget().ToPlayer(); + if (player) + player.GetSpellHistory().ResetCooldown(SpellIds.ShieldSlam, true); + } + + public override void Register() + { + OnEffectProc.Add(new EffectProcHandler(HandleProc, 0, AuraType.ProcTriggerSpell)); + } + } + + public override AuraScript GetAuraScript() + { + return new spell_warr_sword_and_board_AuraScript(); + } + } + + [Script] // 215538 - Trauma + class spell_warr_trauma : SpellScriptLoader + { + public spell_warr_trauma() : base("spell_warr_trauma") { } + + class spell_warr_trauma_AuraScript : AuraScript + { + public override bool Validate(SpellInfo spellInfo) + { + return ValidateSpellInfo(SpellIds.TraumaEffect); + } + + void HandleProc(AuraEffect aurEff, ProcEventInfo eventInfo) + { + Unit target = eventInfo.GetActionTarget(); + //Get the Remaining Damage from the aura (if exist) + int remainingDamage = (int)target.GetRemainingPeriodicAmount(target.GetGUID(), SpellIds.TraumaEffect, AuraType.PeriodicDamage); + //Get 25% of damage from the spell casted (Slam & Whirlwind) plus Remaining Damage from Aura + int damage = (int)(MathFunctions.CalculatePct(eventInfo.GetDamageInfo().GetDamage(), aurEff.GetAmount()) / Global.SpellMgr.GetSpellInfo(SpellIds.TraumaEffect).GetMaxTicks(Difficulty.None)) + remainingDamage; + GetCaster().CastCustomSpell(SpellIds.TraumaEffect, SpellValueMod.BasePoint0, damage, target, true); + } + + public override void Register() + { + OnEffectProc.Add(new EffectProcHandler(HandleProc, 0, AuraType.Dummy)); + } + } + + public override AuraScript GetAuraScript() + { + return new spell_warr_trauma_AuraScript(); + } + } + + [Script] // 28845 - Cheat Death + class spell_warr_t3_prot_8p_bonus : SpellScriptLoader + { + public spell_warr_t3_prot_8p_bonus() : base("spell_warr_t3_prot_8p_bonus") { } + + class spell_warr_t3_prot_8p_bonus_AuraScript : AuraScript + { + bool CheckProc(ProcEventInfo eventInfo) + { + if (eventInfo.GetActionTarget().HealthBelowPct(20)) + return true; + + DamageInfo damageInfo = eventInfo.GetDamageInfo(); + if (damageInfo != null && damageInfo.GetDamage() != 0) + if (GetTarget().HealthBelowPctDamaged(20, damageInfo.GetDamage())) + return true; + + return false; + } + + public override void Register() + { + DoCheckProc.Add(new CheckProcHandler(CheckProc)); + } + } + + public override AuraScript GetAuraScript() + { + return new spell_warr_t3_prot_8p_bonus_AuraScript(); + } + } + + [Script] // 32215 - Victorious State + class spell_warr_victorious_state : SpellScriptLoader + { + public spell_warr_victorious_state() : base("spell_warr_victorious_state") { } + + class spell_warr_victorious_state_Aurascript : AuraScript + { + public override bool Validate(SpellInfo spellInfo) + { + return ValidateSpellInfo(SpellIds.ImpendingVictory); + } + + void HandleOnProc(AuraEffect aurEff, ProcEventInfo procInfo) + { + if (procInfo.GetActor().GetTypeId() == TypeId.Player && procInfo.GetActor().GetUInt32Value(PlayerFields.CurrentSpecId) == (uint)TalentSpecialization.WarriorFury) + PreventDefaultAction(); + + procInfo.GetActor().GetSpellHistory().ResetCooldown(SpellIds.ImpendingVictory, true); + } + + public override void Register() + { + OnEffectProc.Add(new EffectProcHandler(HandleOnProc, 0, AuraType.ProcTriggerSpell)); + } + } + + public override AuraScript GetAuraScript() + { + return new spell_warr_victorious_state_Aurascript(); + } + } + + [Script] // 34428 - Victory Rush + class spell_warr_victory_rush : SpellScriptLoader + { + public spell_warr_victory_rush() : base("spell_warr_victory_rush") { } + + class spell_warr_victory_rush_SpellScript : SpellScript + { + public override bool Validate(SpellInfo spellInfo) + { + return ValidateSpellInfo(SpellIds.Victorious, SpellIds.VictoriousRushHeal); + } + + void HandleHeal() + { + Unit caster = GetCaster(); + + caster.CastSpell(caster, SpellIds.VictoriousRushHeal, true); + caster.RemoveAurasDueToSpell(SpellIds.Victorious); + } + + public override void Register() + { + AfterCast.Add(new CastHandler(HandleHeal)); + } + } + + public override SpellScript GetSpellScript() + { + return new spell_warr_victory_rush_SpellScript(); + } + } + + // 50720 - Vigilance + [Script] + class spell_warr_vigilance : SpellScriptLoader + { + public spell_warr_vigilance() : base("spell_warr_vigilance") { } + + class spell_warr_vigilance_AuraScript : AuraScript + { + public override bool Validate(SpellInfo spellInfo) + { + return ValidateSpellInfo(SpellIds.Vengeance); + } + + public override bool Load() + { + //_procTarget = null; + return true; + } + + /*bool CheckProc(ProcEventInfo eventInfo) + { + _procTarget = GetCaster(); + return _procTarget && eventInfo.GetDamageInfo() != null; + } + + void HandleProc(AuraEffect aurEff, ProcEventInfo eventInfo) + { + PreventDefaultAction(); + int damage = (int)(MathFunctions.CalculatePct(eventInfo.GetDamageInfo().GetDamage(), aurEff.GetSpellInfo().GetEffect(1).CalcValue())); + + GetTarget().CastSpell(_procTarget, SpellIds.VigilanceProc, true, null, aurEff); + _procTarget.CastCustomSpell(_procTarget, SpellIds.Vengeance, damage, damage, damage, true, null, aurEff); + }*/ + + void HandleRemove(AuraEffect aurEff, AuraEffectHandleModes mode) + { + Unit caster = GetCaster(); + if (caster) + { + if (caster.HasAura(SpellIds.Vengeance)) + caster.RemoveAurasDueToSpell(SpellIds.Vengeance); + } + } + + public override void Register() + { + //DoCheckProc.Add(new CheckProcHandler(CheckProc)); + //OnEffectProc.Add(new EffectProcHandler(HandleProc, 0, AuraType.ProcTriggerSpell)); + OnEffectRemove.Add(new EffectApplyHandler(HandleRemove, 0, AuraType.ProcTriggerSpell, AuraEffectHandleModes.Real)); + } + + //Unit _procTarget; + } + + public override AuraScript GetAuraScript() + { + return new spell_warr_vigilance_AuraScript(); + } + } + + // 50725 Vigilance + [Script] + class spell_warr_vigilance_trigger : SpellScriptLoader + { + public spell_warr_vigilance_trigger() : base("spell_warr_vigilance_trigger") { } + + class spell_warr_vigilance_trigger_SpellScript : SpellScript + { + void HandleScript(uint effIndex) + { + PreventHitDefaultEffect(effIndex); + + // Remove Taunt cooldown + Player target = GetHitPlayer(); + if (target) + target.GetSpellHistory().ResetCooldown(SpellIds.Taunt, true); + } + + public override void Register() + { + OnEffectHitTarget.Add(new EffectHandler(HandleScript, 0, SpellEffectName.ScriptEffect)); + } + } + + public override SpellScript GetSpellScript() + { + return new spell_warr_vigilance_trigger_SpellScript(); + } + } +} diff --git a/Scripts/World/Achievements.cs b/Scripts/World/Achievements.cs new file mode 100644 index 000000000..db101509c --- /dev/null +++ b/Scripts/World/Achievements.cs @@ -0,0 +1,304 @@ +/* + * Copyright (C) 2012-2017 CypherCore + * + * 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, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +using Framework.Constants; +using Game.BattleGrounds; +using Game.BattleGrounds.Zones; +using Game.Entities; +using Game.Scripting; + +namespace Scripts.World +{ + struct AchievementConst + { + //Tilted + public const uint AreaArgentTournamentFields = 4658; + public const uint AreaRingOfAspirants = 4670; + public const uint AreaRingOfArgentValiants = 4671; + public const uint AreaRingOfAllianceValiants = 4672; + public const uint AreaRingOfHordeValiants = 4673; + public const uint AreaRingOfChampions = 4669; + + //Flirt With Disaster + public const uint AuraPerfumeForever = 70235; + public const uint AuraPerfumeEnchantress = 70234; + public const uint AuraPerfumeVictory = 70233; + } + + [Script] + class achievement_resilient_victory : AchievementCriteriaScript + { + public achievement_resilient_victory() : base("achievement_resilient_victory") { } + + public override bool OnCheck(Player source, Unit target) + { + Battleground bg = source.GetBattleground(); + if (bg) + return bg.CheckAchievementCriteriaMeet((uint)BattlegroundCriteriaId.ResilientVictory, source, target); + + return false; + } + } + + [Script] + class achievement_bg_control_all_nodes : AchievementCriteriaScript + { + public achievement_bg_control_all_nodes() : base("achievement_bg_control_all_nodes") { } + + public override bool OnCheck(Player source, Unit target) + { + Battleground bg = source.GetBattleground(); + if (bg) + return bg.IsAllNodesControlledByTeam(source.GetTeam()); + + return false; + } + } + + [Script] + class achievement_save_the_day : AchievementCriteriaScript + { + public achievement_save_the_day() : base("achievement_save_the_day") { } + + public override bool OnCheck(Player source, Unit target) + { + Battleground bg = source.GetBattleground(); + if (bg) + return bg.CheckAchievementCriteriaMeet((uint)BattlegroundCriteriaId.SaveTheDay, source, target); + + return false; + } + } + + [Script] + class achievement_bg_ic_resource_glut : AchievementCriteriaScript + { + public achievement_bg_ic_resource_glut() : base("achievement_bg_ic_resource_glut") { } + + public override bool OnCheck(Player source, Unit target) + { + if (source.HasAura(ICSpells.OilRefinery) && source.HasAura(ICSpells.Quarry)) + return true; + + return false; + } + } + + [Script] + class achievement_bg_ic_glaive_grave : AchievementCriteriaScript + { + public achievement_bg_ic_glaive_grave() : base("achievement_bg_ic_glaive_grave") { } + + public override bool OnCheck(Player source, Unit target) + { + Creature vehicle = source.GetVehicleCreatureBase(); + if (vehicle) + { + if (vehicle.GetEntry() == ICCreatures.GlaiveThrowerH || vehicle.GetEntry() == ICCreatures.GlaiveThrowerA) + return true; + } + + return false; + } + } + + [Script] + class achievement_bg_ic_mowed_down : AchievementCriteriaScript + { + public achievement_bg_ic_mowed_down() : base("achievement_bg_ic_mowed_down") { } + + public override bool OnCheck(Player source, Unit target) + { + Creature vehicle = source.GetVehicleCreatureBase(); + if (vehicle) + { + if (vehicle.GetEntry() == ICCreatures.KeepCannon) + return true; + } + + return false; + } + } + + [Script] + class achievement_bg_sa_artillery : AchievementCriteriaScript + { + public achievement_bg_sa_artillery() : base("achievement_bg_sa_artillery") { } + + public override bool OnCheck(Player source, Unit target) + { + Creature vehicle = source.GetVehicleCreatureBase(); + if (vehicle) + { + if (vehicle.GetEntry() == SACreatureIds.AntiPersonnalCannon) + return true; + } + + return false; + } + } + + [Script("achievement_arena_2v2_kills", ArenaTypes.Team2v2)] + [Script("achievement_arena_3v3_kills", ArenaTypes.Team3v3)] + [Script("achievement_arena_5v5_kills", ArenaTypes.Team5v5)] + class achievement_arena_kills : AchievementCriteriaScript + { + public achievement_arena_kills(string name, ArenaTypes arenaType) : base(name) + { + _arenaType = arenaType; + } + + public override bool OnCheck(Player source, Unit target) + { + // this checks GetBattleground() for NULL already + if (!source.InArena()) + return false; + + return source.GetBattleground().GetArenaType() == _arenaType; + } + + ArenaTypes _arenaType; + } + + [Script] + class achievement_sickly_gazelle : AchievementCriteriaScript + { + public achievement_sickly_gazelle() : base("achievement_sickly_gazelle") { } + + public override bool OnCheck(Player source, Unit target) + { + if (!target) + return false; + + Player victim = target.ToPlayer(); + if (victim) + if (victim.IsMounted()) + return true; + + return false; + } + } + + [Script] + class achievement_everything_counts : AchievementCriteriaScript + { + public achievement_everything_counts() : base("achievement_everything_counts") { } + + public override bool OnCheck(Player source, Unit target) + { + Battleground bg = source.GetBattleground(); + if (bg) + return bg.CheckAchievementCriteriaMeet((uint)BattlegroundCriteriaId.EverythingCounts, source, target); + + return false; + } + } + + [Script] + class achievement_bg_av_perfection : AchievementCriteriaScript + { + public achievement_bg_av_perfection() : base("achievement_bg_av_perfection") { } + + public override bool OnCheck(Player source, Unit target) + { + Battleground bg = source.GetBattleground(); + if (bg) + return bg.CheckAchievementCriteriaMeet((uint)BattlegroundCriteriaId.AvPerfection, source, target); + + return false; + } + } + + [Script] + class achievement_bg_sa_defense_of_ancients : AchievementCriteriaScript + { + public achievement_bg_sa_defense_of_ancients() : base("achievement_bg_sa_defense_of_ancients") { } + + public override bool OnCheck(Player source, Unit target) + { + Battleground bg = source.GetBattleground(); + if (bg) + return bg.CheckAchievementCriteriaMeet((uint)BattlegroundCriteriaId.DefenseOfTheAncients, source, target); + + return false; + } + } + + [Script] + class achievement_tilted : AchievementCriteriaScript + { + public achievement_tilted() : base("achievement_tilted") { } + + public override bool OnCheck(Player player, Unit target) + { + if (!player) + return false; + + bool checkArea = player.GetAreaId() == AchievementConst.AreaArgentTournamentFields || + player.GetAreaId() == AchievementConst.AreaRingOfAspirants || + player.GetAreaId() == AchievementConst.AreaRingOfArgentValiants || + player.GetAreaId() == AchievementConst.AreaRingOfAllianceValiants || + player.GetAreaId() == AchievementConst.AreaRingOfHordeValiants || + player.GetAreaId() == AchievementConst.AreaRingOfChampions; + + return checkArea && player.duel != null && player.duel.isMounted; + } + } + + [Script] + class achievement_not_even_a_scratch : AchievementCriteriaScript + { + public achievement_not_even_a_scratch() : base("achievement_not_even_a_scratch") { } + + public override bool OnCheck(Player source, Unit target) + { + Battleground bg = source.GetBattleground(); + if (bg) + return bg.CheckAchievementCriteriaMeet((uint)BattlegroundCriteriaId.NotEvenAScratch, source, target); + + return false; + } + } + + [Script] + class achievement_flirt_with_disaster_perf_check : AchievementCriteriaScript + { + public achievement_flirt_with_disaster_perf_check() : base("achievement_flirt_with_disaster_perf_check") { } + + public override bool OnCheck(Player player, Unit target) + { + if (!player) + return false; + + if (player.HasAura(AchievementConst.AuraPerfumeForever) || player.HasAura(AchievementConst.AuraPerfumeEnchantress) || player.HasAura(AchievementConst.AuraPerfumeVictory)) + return true; + + return false; + } + } + + [Script] + class achievement_killed_exp_or_honor_target : AchievementCriteriaScript + { + public achievement_killed_exp_or_honor_target() : base("achievement_killed_exp_or_honor_target") { } + + public override bool OnCheck(Player player, Unit target) + { + return target && player.isHonorOrXPTarget(target); + } + } +} \ No newline at end of file diff --git a/Scripts/World/AreaTrigger.cs b/Scripts/World/AreaTrigger.cs new file mode 100644 index 000000000..7e9cfba99 --- /dev/null +++ b/Scripts/World/AreaTrigger.cs @@ -0,0 +1,398 @@ +/* + * Copyright (C) 2012-2017 CypherCore + * + * 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, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +using Framework.Constants; +using Game.DataStorage; +using Game.Entities; +using Game.Scripting; +using System.Collections.Generic; + +namespace Scripts.World +{ + struct AreaTriggerConst + { + //Coilfang Waterfall + public const uint GoCoilfangWaterfall = 184212; + + //Legion Teleporter + public const uint SpellTeleATo = 37387; + public const uint QuestGainingAccessA = 10589; + + public const uint SpellTeleHTo = 37389; + public const uint QuestGainingAccessH = 10604; + + //Stormwright Shelf + public const uint QuestStrengthOfTheTempest = 12741; + public const uint SpellCreateTruePowerOfTheTempest = 53067; + + //Scent Larkorwi + public const uint QuestScentOfLarkorwi = 4291; + public const uint NpcLarkorwiMate = 9683; + + //Last Rites + public const uint QuestLastRites = 12019; + public const uint QuestBreakingThrough = 11898; + + //Sholazar Waygate + public const uint SpellSholazarToUngoroTeleport = 52056; + public const uint SpellUngoroToSholazarTeleport = 52057; + + public const uint AtSholazar = 5046; + public const uint AtUngoro = 5047; + + public const uint QuestTheMakersOverlook = 12613; + public const uint QuestTheMakersPerch = 12559; + public const uint QuestMeetingAGreatOne = 13956; + + //Nats Landing + public const uint QuestNatsBargain = 11209; + public const uint SpellFishPaste = 42644; + public const uint NpcLurkingShark = 23928; + + //Brewfest + public const uint NpcTapperSwindlekeg = 24711; + public const uint NpcIpfelkoferIronkeg = 24710; + + public const uint AtBrewfestDurotar = 4829; + public const uint AtBrewfestDunMorogh = 4820; + + public const uint SayWelcome = 4; + + public const uint AreatriggerTalkCooldown = 5; // In Seconds + + //Area 52 + public const uint SpellA52Neuralyzer = 34400; + public const uint NpcSpotlight = 19913; + public const uint SummonCooldown = 5; + + public const uint AtArea52South = 4472; + public const uint AtArea52North = 4466; + public const uint AtArea52West = 4471; + public const uint AtArea52East = 4422; + + //Frostgrips Hollow + public const uint QuestTheLonesomeWatcher = 12877; + + public const uint NpcStormforgedMonitor = 29862; + public const uint NpcStormforgedEradictor = 29861; + + public const uint TypeWaypoint = 0; + public const uint DataStart = 0; + + public static Position StormforgedMonitorPosition = new Position(6963.95f, 45.65f, 818.71f, 4.948f); + public static Position StormforgedEradictorPosition = new Position(6983.18f, 7.15f, 806.33f, 2.228f); + } + + [Script] + class AreaTrigger_at_coilfang_waterfall : AreaTriggerScript + { + public AreaTrigger_at_coilfang_waterfall() : base("at_coilfang_waterfall") { } + + public override bool OnTrigger(Player player, AreaTriggerRecord trigger, bool entered) + { + GameObject go = player.FindNearestGameObject(AreaTriggerConst.GoCoilfangWaterfall, 35.0f); + if (go) + if (go.getLootState() == LootState.Ready) + go.UseDoorOrButton(); + + return false; + } + } + + [Script] + class AreaTrigger_at_legion_teleporter : AreaTriggerScript + { + public AreaTrigger_at_legion_teleporter() : base("at_legion_teleporter") { } + + public override bool OnTrigger(Player player, AreaTriggerRecord trigger, bool entered) + { + if (player.IsAlive() && !player.IsInCombat()) + { + if (player.GetTeam() == Team.Alliance && player.GetQuestRewardStatus(AreaTriggerConst.QuestGainingAccessA)) + { + player.CastSpell(player, AreaTriggerConst.SpellTeleATo, false); + return true; + } + + if (player.GetTeam() == Team.Horde && player.GetQuestRewardStatus(AreaTriggerConst.QuestGainingAccessH)) + { + player.CastSpell(player, AreaTriggerConst.SpellTeleHTo, false); + return true; + } + + return false; + } + return false; + } + } + + [Script] + class AreaTrigger_at_stormwright_shelf : AreaTriggerScript + { + public AreaTrigger_at_stormwright_shelf() : base("at_stormwright_shelf") { } + + public override bool OnTrigger(Player player, AreaTriggerRecord trigger, bool entered) + { + if (!player.IsDead() && player.GetQuestStatus(AreaTriggerConst.QuestStrengthOfTheTempest) == QuestStatus.Incomplete) + player.CastSpell(player, AreaTriggerConst.SpellCreateTruePowerOfTheTempest, false); + + return true; + } + } + + [Script] + class AreaTrigger_at_scent_larkorwi : AreaTriggerScript + { + public AreaTrigger_at_scent_larkorwi() : base("at_scent_larkorwi") { } + + public override bool OnTrigger(Player player, AreaTriggerRecord trigger, bool entered) + { + if (!player.IsDead() && player.GetQuestStatus(AreaTriggerConst.QuestScentOfLarkorwi) == QuestStatus.Incomplete) + { + if (!player.FindNearestCreature(AreaTriggerConst.NpcLarkorwiMate, 15)) + player.SummonCreature(AreaTriggerConst.NpcLarkorwiMate, player.GetPositionX() + 5, player.GetPositionY(), player.GetPositionZ(), 3.3f, TempSummonType.TimedDespawnOOC, 100000); + } + + return false; + } + } + + [Script] + class AreaTrigger_at_last_rites : AreaTriggerScript + { + public AreaTrigger_at_last_rites() : base("at_last_rites") { } + + public override bool OnTrigger(Player player, AreaTriggerRecord trigger, bool entered) + { + if (!(player.GetQuestStatus(AreaTriggerConst.QuestLastRites) == QuestStatus.Incomplete || + player.GetQuestStatus(AreaTriggerConst.QuestLastRites) == QuestStatus.Complete || + player.GetQuestStatus(AreaTriggerConst.QuestBreakingThrough) == QuestStatus.Incomplete || + player.GetQuestStatus(AreaTriggerConst.QuestBreakingThrough) == QuestStatus.Complete)) + return false; + + WorldLocation pPosition; + + switch (trigger.Id) + { + case 5332: + case 5338: + pPosition = new WorldLocation(571, 3733.68f, 3563.25f, 290.812f, 3.665192f); + break; + case 5334: + pPosition = new WorldLocation(571, 3802.38f, 3585.95f, 49.5765f, 0.0f); + break; + case 5340: + if (player.GetQuestStatus(AreaTriggerConst.QuestLastRites) == QuestStatus.Incomplete || + player.GetQuestStatus(AreaTriggerConst.QuestLastRites) == QuestStatus.Complete) + pPosition = new WorldLocation(571, 3687.91f, 3577.28f, 473.342f); + else + pPosition = new WorldLocation(571, 3739.38f, 3567.09f, 341.58f); + break; + default: + return false; + } + + player.TeleportTo(pPosition); + + return false; + } + } + + [Script] + class AreaTrigger_at_sholazar_waygate : AreaTriggerScript + { + public AreaTrigger_at_sholazar_waygate() : base("at_sholazar_waygate") { } + + public override bool OnTrigger(Player player, AreaTriggerRecord trigger, bool entered) + { + if (!player.IsDead() && (player.GetQuestStatus(AreaTriggerConst.QuestMeetingAGreatOne) != QuestStatus.None || + (player.GetQuestStatus(AreaTriggerConst.QuestTheMakersOverlook) == QuestStatus.Rewarded && player.GetQuestStatus(AreaTriggerConst.QuestTheMakersPerch) == QuestStatus.Rewarded))) + { + switch (trigger.Id) + { + case AreaTriggerConst.AtSholazar: + player.CastSpell(player, AreaTriggerConst.SpellSholazarToUngoroTeleport, true); + break; + + case AreaTriggerConst.AtUngoro: + player.CastSpell(player, AreaTriggerConst.SpellUngoroToSholazarTeleport, true); + break; + } + } + + return false; + } + } + + [Script] + class AreaTrigger_at_nats_landing : AreaTriggerScript + { + public AreaTrigger_at_nats_landing() : base("at_nats_landing") { } + + public override bool OnTrigger(Player player, AreaTriggerRecord trigger, bool entered) + { + if (!player.IsAlive() || !player.HasAura(AreaTriggerConst.SpellFishPaste)) + return false; + + if (player.GetQuestStatus(AreaTriggerConst.QuestNatsBargain) == QuestStatus.Incomplete) + { + if (!player.FindNearestCreature(AreaTriggerConst.NpcLurkingShark, 20.0f)) + { + Creature shark = player.SummonCreature(AreaTriggerConst.NpcLurkingShark, -4246.243f, -3922.356f, -7.488f, 5.0f, TempSummonType.TimedDespawnOOC, 100000); + if (shark) + shark.GetAI().AttackStart(player); + + return false; + } + } + return true; + } + } + + [Script] + class AreaTrigger_at_brewfest : AreaTriggerScript + { + public AreaTrigger_at_brewfest() : base("at_brewfest") + { + // Initialize for cooldown + _triggerTimes[AreaTriggerConst.AtBrewfestDurotar] = _triggerTimes[AreaTriggerConst.AtBrewfestDunMorogh] = 0; + } + + public override bool OnTrigger(Player player, AreaTriggerRecord trigger, bool entered) + { + uint triggerId = trigger.Id; + // Second trigger happened too early after first, skip for now + if (Global.WorldMgr.GetGameTime() - _triggerTimes[triggerId] < AreaTriggerConst.AreatriggerTalkCooldown) + return false; + + switch (triggerId) + { + case AreaTriggerConst.AtBrewfestDurotar: + Creature tapper = player.FindNearestCreature(AreaTriggerConst.NpcTapperSwindlekeg, 20.0f); + if (tapper) + tapper.GetAI().Talk(AreaTriggerConst.SayWelcome, player); + break; + case AreaTriggerConst.AtBrewfestDunMorogh: + Creature ipfelkofer = player.FindNearestCreature(AreaTriggerConst.NpcIpfelkoferIronkeg, 20.0f); + if (ipfelkofer) + ipfelkofer.GetAI().Talk(AreaTriggerConst.SayWelcome, player); + break; + default: + break; + } + + _triggerTimes[triggerId] = Global.WorldMgr.GetGameTime(); + return false; + } + + Dictionary _triggerTimes = new Dictionary(); + } + + [Script] + class AreaTrigger_at_area_52_entrance : AreaTriggerScript + { + public AreaTrigger_at_area_52_entrance() : base("at_area_52_entrance") + { + _triggerTimes[AreaTriggerConst.AtArea52South] = _triggerTimes[AreaTriggerConst.AtArea52North] = _triggerTimes[AreaTriggerConst.AtArea52West] = _triggerTimes[AreaTriggerConst.AtArea52East] = 0; + } + + public override bool OnTrigger(Player player, AreaTriggerRecord trigger, bool entered) + { + float x = 0.0f, y = 0.0f, z = 0.0f; + + if (!player.IsAlive()) + return false; + + uint triggerId = trigger.Id; + if (Global.WorldMgr.GetGameTime() - _triggerTimes[trigger.Id] < AreaTriggerConst.SummonCooldown) + return false; + + switch (triggerId) + { + case AreaTriggerConst.AtArea52East: + x = 3044.176f; + y = 3610.692f; + z = 143.61f; + break; + case AreaTriggerConst.AtArea52North: + x = 3114.87f; + y = 3687.619f; + z = 143.62f; + break; + case AreaTriggerConst.AtArea52West: + x = 3017.79f; + y = 3746.806f; + z = 144.27f; + break; + case AreaTriggerConst.AtArea52South: + x = 2950.63f; + y = 3719.905f; + z = 143.33f; + break; + } + + player.SummonCreature(AreaTriggerConst.NpcSpotlight, x, y, z, 0.0f, TempSummonType.TimedDespawn, 5000); + player.AddAura(AreaTriggerConst.SpellA52Neuralyzer, player); + _triggerTimes[trigger.Id] = Global.WorldMgr.GetGameTime(); + return false; + } + + Dictionary _triggerTimes = new Dictionary(); + } + + [Script] + class AreaTrigger_at_frostgrips_hollow : AreaTriggerScript + { + public AreaTrigger_at_frostgrips_hollow() : base("at_frostgrips_hollow") { } + + public override bool OnTrigger(Player player, AreaTriggerRecord trigger, bool entered) + { + if (player.GetQuestStatus(AreaTriggerConst.QuestTheLonesomeWatcher) != QuestStatus.Incomplete) + return false; + + Creature stormforgedMonitor = ObjectAccessor.GetCreature(player, stormforgedMonitorGUID); + if (stormforgedMonitor) + return false; + + Creature stormforgedEradictor = ObjectAccessor.GetCreature(player, stormforgedEradictorGUID); + if (stormforgedEradictor) + return false; + + stormforgedMonitor = player.SummonCreature(AreaTriggerConst.NpcStormforgedMonitor, AreaTriggerConst.StormforgedMonitorPosition, TempSummonType.TimedDespawnOOC, 60000); + if (stormforgedMonitor) + { + stormforgedMonitorGUID = stormforgedMonitor.GetGUID(); + stormforgedMonitor.SetWalk(false); + // The npc would search an alternative way to get to the last waypoint without this unit state. + stormforgedMonitor.AddUnitState(UnitState.IgnorePathfinding); + stormforgedMonitor.GetMotionMaster().MovePath(AreaTriggerConst.NpcStormforgedMonitor * 100, false); + } + + stormforgedEradictor = player.SummonCreature(AreaTriggerConst.NpcStormforgedEradictor, AreaTriggerConst.StormforgedEradictorPosition, TempSummonType.TimedDespawnOOC, 60000); + if (stormforgedEradictor) + { + stormforgedEradictorGUID = stormforgedEradictor.GetGUID(); + stormforgedEradictor.GetMotionMaster().MovePath(AreaTriggerConst.NpcStormforgedEradictor * 100, false); + } + + return true; + } + + ObjectGuid stormforgedMonitorGUID; + ObjectGuid stormforgedEradictorGUID; + } +} diff --git a/Scripts/World/BossEmeraldDragons.cs b/Scripts/World/BossEmeraldDragons.cs new file mode 100644 index 000000000..7c6cfb5cc --- /dev/null +++ b/Scripts/World/BossEmeraldDragons.cs @@ -0,0 +1,621 @@ +/* + * Copyright (C) 2012-2017 CypherCore + * + * 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, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +using Framework.Constants; +using Game.AI; +using Game.Entities; +using Game.Scripting; +using Game.Spells; +using System; +using System.Collections.Generic; + +namespace Scripts.World.BossEmeraldDragons +{ + struct CreatureIds + { + public const uint DragonYsondre = 14887; + public const uint DragonLethon = 14888; + public const uint DragonEmeriss = 14889; + public const uint DragonTaerar = 14890; + public const uint DreamFog = 15224; + + //Ysondre + public const uint DementedDruid = 15260; + + //Lethon + public const uint SpiritShade = 15261; + } + + struct Spells + { + public const uint TailSweep = 15847; // Tail Sweep - Slap Everything Behind Dragon (2 Seconds Interval) + public const uint SummonPlayer = 24776; // Teleport Highest Threat Player In Front Of Dragon If Wandering Off + public const uint DreamFog = 24777; // Auraspell For Dream Fog Npc (15224) + public const uint Sleep = 24778; // Sleep Triggerspell (Used For Dream Fog) + public const uint SeepingFogLeft = 24813; // Dream Fog - Summon Left + public const uint SeepingFogRight = 24814; // Dream Fog - Summon Right + public const uint NoxiousBreath = 24818; + public const uint MarkOfNature = 25040; // Mark Of Nature Trigger (Applied On Target Death - 15 Minutes Of Being Suspectible To Aura Of Nature) + public const uint MarkOfNatureAura = 25041; // Mark Of Nature (Passive Marker-Test; Ticks Every 10 Seconds From Boss; Triggers Spellid 25042 (Scripted) + public const uint AuraOfNature = 25043; // Stun For 2 Minutes (Used When public const uint MarkOfNature Exists On The Target) + + //Ysondre + public const uint LightningWave = 24819; + public const uint SummonDruidSpirits = 24795; + + //Lethon + public const uint DrawSpirit = 24811; + public const uint ShadowBoltWhirl = 24834; + public const uint DarkOffering = 24804; + + //Emeriss + public const uint PutridMushroom = 24904; + public const uint CorruptionOfEarth = 24910; + public const uint VolatileInfection = 24928; + + //Taerar + public const uint BellowingRoar = 22686; + public const uint Shade = 24313; + public const uint ArcaneBlast = 24857; + + public static uint[] TaerarShadeSpells = { 24841, 24842, 24843 }; + } + + struct Texts + { + //Ysondre + public const uint YsondreAggro = 0; + public const uint YsondreSummonDruids = 1; + + //Lethon + public const uint LethonAggro = 0; + public const uint LethonDrawSpirit = 1; + + //Emeriss + public const uint EmerissAggro = 0; + public const uint EmerissCastCorruption = 1; + + //Taerar + public const uint TaerarAggro = 0; + public const uint TaerarSummonShades = 1; + } + + class emerald_dragonAI : WorldBossAI + { + public emerald_dragonAI(Creature creature) : base(creature) { } + + public override void Reset() + { + base.Reset(); + me.RemoveFlag(UnitFields.Flags, UnitFlags.NotSelectable | UnitFlags.NonAttackable); + me.SetReactState(ReactStates.Aggressive); + DoCast(me, Spells.MarkOfNatureAura, true); + + _scheduler.Schedule(TimeSpan.FromSeconds(4), task => + { + // Tail Sweep is cast every two seconds, no matter what goes on in front of the dragon + DoCast(me, Spells.TailSweep); + task.Repeat(TimeSpan.FromSeconds(2)); + }); + + _scheduler.Schedule(TimeSpan.FromSeconds(7.5), TimeSpan.FromSeconds(15), task => + { + // Noxious Breath is cast on random intervals, no less than 7.5 seconds between + DoCast(me, Spells.NoxiousBreath); + task.Repeat(); + }); + + _scheduler.Schedule(TimeSpan.FromSeconds(12.5), TimeSpan.FromSeconds(20), task => + { + // Seeping Fog appears only as "pairs", and only ONE pair at any given time! + // Despawntime is 2 minutes, so reschedule it for new cast after 2 minutes + a minor "random time" (30 seconds at max) + DoCast(me, Spells.SeepingFogLeft, true); + DoCast(me, Spells.SeepingFogRight, true); + task.Repeat(TimeSpan.FromMinutes(2), TimeSpan.FromMinutes(2.5)); + }); + } + + // Target killed during encounter, mark them as suspectible for Aura Of Nature + public override void KilledUnit(Unit who) + { + if (who.IsTypeId(TypeId.Player)) + who.CastSpell(who, Spells.MarkOfNature, true); + } + + public override void UpdateAI(uint diff) + { + if (!UpdateVictim()) + return; + + if (me.HasUnitState(UnitState.Casting)) + return; + + _scheduler.Update(diff); + + Unit target = SelectTarget(SelectAggroTarget.TopAggro, 0, -50.0f, true); + if (target) + DoCast(target, Spells.SummonPlayer); + + DoMeleeAttackIfReady(); + } + } + + [Script] + class npc_dream_fog : CreatureScript + { + public npc_dream_fog() : base("npc_dream_fog") { } + + class npc_dream_fogAI : ScriptedAI + { + public npc_dream_fogAI(Creature creature) : base(creature) + { + Initialize(); + } + + void Initialize() + { + _roamTimer = 0; + } + + public override void Reset() + { + Initialize(); + } + + public override void UpdateAI(uint diff) + { + if (!UpdateVictim()) + return; + + if (_roamTimer == 0) + { + // Chase target, but don't attack - otherwise just roam around + Unit target = SelectTarget(SelectAggroTarget.Random, 0, 0.0f, true); + if (target) + { + _roamTimer = RandomHelper.URand(15000, 30000); + me.GetMotionMaster().Clear(false); + me.GetMotionMaster().MoveChase(target, 0.2f); + } + else + { + _roamTimer = 2500; + me.GetMotionMaster().Clear(false); + me.GetMotionMaster().MoveRandom(25.0f); + } + // Seeping fog movement is slow enough for a player to be able to walk backwards and still outpace it + me.SetWalk(true); + me.SetSpeedRate(UnitMoveType.Walk, 0.75f); + } + else + _roamTimer -= diff; + } + + uint _roamTimer; + } + + public override CreatureAI GetAI(Creature creature) + { + return new npc_dream_fogAI(creature); + } + } + + [Script] + class boss_ysondre : CreatureScript + { + public boss_ysondre() : base("boss_ysondre") { } + + class boss_ysondreAI : emerald_dragonAI + { + public boss_ysondreAI(Creature creature) : base(creature) + { + Initialize(); + } + + void Initialize() + { + _stage = 1; + } + + public override void Reset() + { + Initialize(); + base.Reset(); + _scheduler.Schedule(TimeSpan.FromSeconds(12), task => + { + DoCastVictim(Spells.LightningWave); + task.Repeat(TimeSpan.FromSeconds(10), TimeSpan.FromSeconds(20)); + }); + } + + public override void EnterCombat(Unit who) + { + Talk(Texts.YsondreAggro); + base.EnterCombat(who); + } + + // Summon druid spirits on 75%, 50% and 25% health + public override void DamageTaken(Unit attacker, ref uint damage) + { + if (!HealthAbovePct(100 - 25 * _stage)) + { + Talk(Texts.YsondreSummonDruids); + + for (byte i = 0; i < 10; ++i) + DoCast(me, Spells.SummonDruidSpirits, true); + ++_stage; + } + } + + byte _stage; + } + + public override CreatureAI GetAI(Creature creature) + { + return new boss_ysondreAI(creature); + } + } + + [Script] + class boss_lethon : CreatureScript + { + public boss_lethon() : base("boss_lethon") { } + + class boss_lethonAI : emerald_dragonAI + { + public boss_lethonAI(Creature creature) : base(creature) + { + Initialize(); + } + + void Initialize() + { + _stage = 1; + } + + public override void Reset() + { + Initialize(); + base.Reset(); + + _scheduler.Schedule(TimeSpan.FromSeconds(10), task => + { + me.CastSpell((Unit)null, Spells.ShadowBoltWhirl, false); + task.Repeat(TimeSpan.FromSeconds(15), TimeSpan.FromSeconds(30)); + }); + } + + public override void EnterCombat(Unit who) + { + Talk(Texts.LethonAggro); + base.EnterCombat(who); + } + + public override void DamageTaken(Unit attacker, ref uint damage) + { + if (!HealthAbovePct(100 - 25 * _stage)) + { + Talk(Texts.LethonDrawSpirit); + DoCast(me, Spells.DrawSpirit); + ++_stage; + } + } + + public override void SpellHitTarget(Unit target, SpellInfo spell) + { + if (spell.Id == Spells.DrawSpirit && target.IsTypeId(TypeId.Player)) + { + Position targetPos = target.GetPosition(); + me.SummonCreature(CreatureIds.SpiritShade, targetPos, TempSummonType.TimedDespawnOOC, 50000); + } + } + + byte _stage; + } + + public override CreatureAI GetAI(Creature creature) + { + return new boss_lethonAI(creature); + } + } + + [Script] + class npc_spirit_shade : CreatureScript + { + public npc_spirit_shade() : base("npc_spirit_shade") { } + + class npc_spirit_shadeAI : PassiveAI + { + public npc_spirit_shadeAI(Creature creature) : base(creature) { } + + public override void IsSummonedBy(Unit summoner) + { + _summonerGuid = summoner.GetGUID(); + me.GetMotionMaster().MoveFollow(summoner, 0.0f, 0.0f); + } + + public override void MovementInform(MovementGeneratorType moveType, uint data) + { + if (moveType == MovementGeneratorType.Follow && data == _summonerGuid.GetCounter()) + { + me.CastSpell((Unit)null, Spells.DarkOffering, false); + me.DespawnOrUnsummon(1000); + } + } + + ObjectGuid _summonerGuid; + } + + public override CreatureAI GetAI(Creature creature) + { + return new npc_spirit_shadeAI(creature); + } + } + + [Script] + class boss_emeriss : CreatureScript + { + public boss_emeriss() : base("boss_emeriss") { } + + class boss_emerissAI : emerald_dragonAI + { + public boss_emerissAI(Creature creature) : base(creature) + { + Initialize(); + } + + void Initialize() + { + _stage = 1; + } + + public override void Reset() + { + Initialize(); + base.Reset(); + + _scheduler.Schedule(TimeSpan.FromSeconds(12), task => + { + DoCastVictim(Spells.VolatileInfection); + task.Repeat(TimeSpan.FromSeconds(120)); + }); + } + + public override void KilledUnit(Unit who) + { + if (who.IsTypeId(TypeId.Player)) + DoCast(who, Spells.PutridMushroom, true); + base.KilledUnit(who); + } + + public override void EnterCombat(Unit who) + { + Talk(Texts.EmerissAggro); + base.EnterCombat(who); + } + + public override void DamageTaken(Unit attacker, ref uint damage) + { + if (!HealthAbovePct(100 - 25 * _stage)) + { + Talk(Texts.EmerissCastCorruption); + DoCast(me, Spells.CorruptionOfEarth, true); + ++_stage; + } + } + + byte _stage; + } + + public override CreatureAI GetAI(Creature creature) + { + return new boss_emerissAI(creature); + } + } + + [Script] + class boss_taerar : CreatureScript + { + public boss_taerar() : base("boss_taerar") { } + + class boss_taerarAI : emerald_dragonAI + { + public boss_taerarAI(Creature creature) : base(creature) + { + Initialize(); + } + + void Initialize() + { + _stage = 1; + _shades = 0; + _banished = false; + _banishedTimer = 0; + } + + public override void Reset() + { + me.RemoveAurasDueToSpell(Spells.Shade); + + Initialize(); + + base.Reset(); + + _scheduler.Schedule(TimeSpan.FromSeconds(12), task => + { + DoCast(Spells.ArcaneBlast); + task.Repeat(TimeSpan.FromSeconds(7), TimeSpan.FromSeconds(12)); + }); + + _scheduler.Schedule(TimeSpan.FromSeconds(30), task => + { + DoCast(Spells.BellowingRoar); + task.Repeat(TimeSpan.FromSeconds(20), TimeSpan.FromSeconds(30)); + }); + } + + public override void EnterCombat(Unit who) + { + Talk(Texts.TaerarAggro); + base.EnterCombat(who); + } + + public override void SummonedCreatureDies(Creature summon, Unit killer) + { + --_shades; + } + + public override void DamageTaken(Unit attacker, ref uint damage) + { + // At 75, 50 or 25 percent health, we need to activate the shades and go "banished" + // Note: _stage holds the amount of times they have been summoned + if (!_banished && !HealthAbovePct(100 - 25 * _stage)) + { + _banished = true; + _banishedTimer = 60000; + + me.InterruptNonMeleeSpells(false); + DoStopAttack(); + + Talk(Texts.TaerarSummonShades); + + foreach (var spell in Spells.TaerarShadeSpells) + DoCastVictim(spell, true); + _shades += (byte)Spells.TaerarShadeSpells.Length; + + DoCast(Spells.Shade); + me.SetFlag(UnitFields.Flags, UnitFlags.NotSelectable | UnitFlags.NonAttackable); + me.SetReactState(ReactStates.Passive); + + ++_stage; + } + } + + public override void UpdateAI(uint diff) + { + if (!me.IsInCombat()) + return; + + if (_banished) + { + // If all three shades are dead, OR it has taken too long, end the current event and get Taerar back into business + if (_banishedTimer <= diff || _shades == 0) + { + _banished = false; + + me.RemoveFlag(UnitFields.Flags, UnitFlags.NotSelectable | UnitFlags.NonAttackable); + me.RemoveAurasDueToSpell(Spells.Shade); + me.SetReactState(ReactStates.Aggressive); + } + // _banishtimer has not expired, and we still have active shades: + else + _banishedTimer -= diff; + + // Update the scheduler before we return (handled under emerald_dragonAI.UpdateAI(diff); if we're not inside this check) + _scheduler.Update(diff); + + return; + } + + base.UpdateAI(diff); + } + + bool _banished; // used for shades activation testing + uint _banishedTimer; // counter for banishment timeout + byte _shades; // keep track of how many shades are dead + byte _stage; // check which "shade phase" we're at (75-50-25 percentage counters) + } + + public override CreatureAI GetAI(Creature creature) + { + return new boss_taerarAI(creature); + } + } + + [Script] + class spell_dream_fog_sleep : SpellScriptLoader + { + public spell_dream_fog_sleep() : base("spell_dream_fog_sleep") { } + + class spell_dream_fog_sleep_SpellScript : SpellScript + { + void FilterTargets(List targets) + { + targets.RemoveAll(obj => + { + Unit unit = obj.ToUnit(); + if (unit) + return unit.HasAura(Spells.Sleep); + return true; + }); + } + + public override void Register() + { + OnObjectAreaTargetSelect.Add(new ObjectAreaTargetSelectHandler(FilterTargets, 0, Targets.UnitDestAreaEnemy)); + } + } + + public override SpellScript GetSpellScript() + { + return new spell_dream_fog_sleep_SpellScript(); + } + } + + [Script] + class spell_mark_of_nature : SpellScriptLoader + { + public spell_mark_of_nature() : base("spell_mark_of_nature") { } + + class spell_mark_of_nature_SpellScript : SpellScript + { + public override bool Validate(SpellInfo spellInfo) + { + return ValidateSpellInfo(Spells.MarkOfNature, Spells.AuraOfNature); + } + + void FilterTargets(List targets) + { + targets.RemoveAll(obj => + { + // return those not tagged or already under the influence of Aura of Nature + Unit unit = obj.ToUnit(); + if (unit) + return !(unit.HasAura(Spells.MarkOfNature) && !unit.HasAura(Spells.AuraOfNature)); + return true; + }); + } + + void HandleEffect(uint effIndex) + { + PreventHitDefaultEffect(effIndex); + GetHitUnit().CastSpell(GetHitUnit(), Spells.AuraOfNature, true); + } + + public override void Register() + { + OnObjectAreaTargetSelect.Add(new ObjectAreaTargetSelectHandler(FilterTargets, 0, Targets.UnitSrcAreaEnemy)); + OnEffectHitTarget.Add(new EffectHandler(HandleEffect, 0, SpellEffectName.ApplyAura)); + } + } + + public override SpellScript GetSpellScript() + { + return new spell_mark_of_nature_SpellScript(); + } + } +} diff --git a/Scripts/World/DuelReset.cs b/Scripts/World/DuelReset.cs new file mode 100644 index 000000000..6f0bdb812 --- /dev/null +++ b/Scripts/World/DuelReset.cs @@ -0,0 +1,144 @@ +/* + * Copyright (C) 2012-2017 CypherCore + * + * 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, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +using Framework.Constants; +using Game; +using Game.Entities; +using Game.Scripting; +using Game.Spells; +using System; + +namespace Scripts.World +{ + [Script] + class DuelResetScript : PlayerScript + { + public DuelResetScript() : base("DuelResetScript") + { + _resetCooldowns = WorldConfig.GetBoolValue(WorldCfg.ResetDuelCooldowns); + _resetHealthMana = WorldConfig.GetBoolValue(WorldCfg.ResetDuelHealthMana); + } + + public override void OnDuelStart(Player player1, Player player2) + { + // Cooldowns reset + if (_resetCooldowns) + { + player1.GetSpellHistory().SaveCooldownStateBeforeDuel(); + player2.GetSpellHistory().SaveCooldownStateBeforeDuel(); + + ResetSpellCooldowns(player1, true); + ResetSpellCooldowns(player2, true); + } + + // Health and mana reset + if (_resetHealthMana) + { + player1.SaveHealthBeforeDuel(); + player1.SetHealth(player1.GetMaxHealth()); + + player2.SaveHealthBeforeDuel(); + player2.SetHealth(player2.GetMaxHealth()); + + // check if player1 class uses mana + if (player1.getPowerType() == PowerType.Mana || player1.GetClass() == Class.Druid) + { + player1.SaveManaBeforeDuel(); + player1.SetPower(PowerType.Mana, player1.GetMaxPower(PowerType.Mana)); + } + + // check if player2 class uses mana + if (player2.getPowerType() == PowerType.Mana || player2.GetClass() == Class.Druid) + { + player2.SaveManaBeforeDuel(); + player2.SetPower(PowerType.Mana, player2.GetMaxPower(PowerType.Mana)); + } + } + } + + public override void OnDuelEnd(Player winner, Player loser, DuelCompleteType type) + { + // do not reset anything if DUEL_INTERRUPTED or DUEL_FLED + if (type == DuelCompleteType.Won) + { + // Cooldown restore + if (_resetCooldowns) + { + ResetSpellCooldowns(winner, false); + ResetSpellCooldowns(loser, false); + + winner.GetSpellHistory().RestoreCooldownStateAfterDuel(); + loser.GetSpellHistory().RestoreCooldownStateAfterDuel(); + } + + // Health and mana restore + if (_resetHealthMana) + { + winner.RestoreHealthAfterDuel(); + loser.RestoreHealthAfterDuel(); + + // check if player1 class uses mana + if (winner.getPowerType() == PowerType.Mana || winner.GetClass() == Class.Druid) + winner.RestoreManaAfterDuel(); + + // check if player2 class uses mana + if (loser.getPowerType() == PowerType.Mana || loser.GetClass() == Class.Druid) + loser.RestoreManaAfterDuel(); + } + } + } + + void ResetSpellCooldowns(Player player, bool onStartDuel) + { + if (onStartDuel) + { + // remove cooldowns on spells that have < 10 min CD > 30 sec and has no onHold + player.GetSpellHistory().ResetCooldowns(pair => + { + DateTime now = DateTime.Now; + uint cooldownDuration = pair.Value.CooldownEnd > now ? (uint)(pair.Value.CooldownEnd - now).TotalMilliseconds : 0; + SpellInfo spellInfo = Global.SpellMgr.GetSpellInfo(pair.Key); + return spellInfo.RecoveryTime < 10 * Time.Minute * Time.InMilliseconds + && spellInfo.CategoryRecoveryTime < 10 * Time.Minute * Time.InMilliseconds + && !pair.Value.OnHold + && cooldownDuration > 0 + && (spellInfo.RecoveryTime - cooldownDuration) > (Time.Minute / 2) * Time.InMilliseconds + && (spellInfo.CategoryRecoveryTime - cooldownDuration) > (Time.Minute / 2) * Time.InMilliseconds; + }, true); + } + else + { + // remove cooldowns on spells that have < 10 min CD and has no onHold + player.GetSpellHistory().ResetCooldowns(pair => + { + SpellInfo spellInfo = Global.SpellMgr.GetSpellInfo(pair.Key); + return spellInfo.RecoveryTime < 10 * Time.Minute * Time.InMilliseconds + && spellInfo.CategoryRecoveryTime < 10 * Time.Minute * Time.InMilliseconds + && !pair.Value.OnHold; + }, true); + } + + // pet cooldowns + Pet pet = player.GetPet(); + if (pet) + pet.GetSpellHistory().ResetAllCooldowns(); + } + + bool _resetCooldowns; + bool _resetHealthMana; + } +} diff --git a/Scripts/World/GameObjects.cs b/Scripts/World/GameObjects.cs new file mode 100644 index 000000000..e70e268e4 --- /dev/null +++ b/Scripts/World/GameObjects.cs @@ -0,0 +1,979 @@ +/* + * Copyright (C) 2012-2017 CypherCore + * + * 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, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +using Framework.Constants; +using Game.AI; +using Game.DataStorage; +using Game.Entities; +using Game.Scripting; +using System; +using System.Collections.Generic; + +namespace Scripts.World +{ + struct GameobjectConst + { + //CatFigurine + public const uint SpellSummonGhostSaber = 5968; + + //GildedBrazier + public const uint NpcStillblade = 17716; + public const uint QuestTheFirstTrial = 9678; + + //EthereumPrison + public const uint SpellRepLc = 39456; + public const uint SpellRepShat = 39457; + public const uint SpellRepCe = 39460; + public const uint SpellRepCon = 39474; + public const uint SpellRepKt = 39475; + public const uint SpellRepSpor = 39476; + public static uint[] NpcPrisonEntry = + { + 22810, 22811, 22812, 22813, 22814, 22815, //Good Guys + 20783, 20784, 20785, 20786, 20788, 20789, 20790 //Bad Guys + }; + + //Ethereum Stasis + public static uint[] NpcStasisEntry = + { + 22825, 20888, 22827, 22826, 22828 + }; + + //ResoniteCask + public const uint NpcGoggeroc = 11920; + + //Sacredfireoflife + public const uint NpcArikara = 10882; + + //Shrineofthebirds + public const uint NpcHawkGuard = 22992; + public const uint NpcEagleGuard = 22993; + public const uint NpcFalconGuard = 22994; + public const uint GoShrineHawk = 185551; + public const uint GoShrineEagle = 185547; + public const uint GoShrineFalcon = 185553; + + //Southfury + public const uint NpcRizzle = 23002; + public const uint SpellBlackjack = 39865; //Stuns Player + public const uint SpellSummonRizzle = 39866; + + //Dalarancrystal + public const uint QuestLearnLeaveReturn = 12790; + public const uint QuestTeleCrystalFlag = 12845; + public const string GoTeleToDalaranCrystalFailed = "This Teleport Crystal Cannot Be Used Until The Teleport Crystal In Dalaran Has Been Used At Least Once."; + + //Felcrystalforge + public const uint SpellCreate1FlaskOfBeast = 40964; + public const uint SpellCreate5FlaskOfBeast = 40965; + public const uint GossipFelCrystalforgeText = 31000; + public const uint GossipFelCrystalforgeItemTextReturn = 31001; + public const string GossipFelCrystalforgeItem1 = "Purchase 1 Unstable Flask Of The Beast For The Cost Of 10 Apexis Shards"; + public const string GossipFelCrystalforgeItem5 = "Purchase 5 Unstable Flask Of The Beast For The Cost Of 50 Apexis Shards"; + public const string GossipFelCrystalforgeItemReturn = "Use The Fel Crystalforge To Make Another Purchase."; + + //Bashircrystalforge + public const uint SpellCreate1FlaskOfSorcerer = 40968; + public const uint SpellCreate5FlaskOfSorcerer = 40970; + public const uint GossipBashirCrystalforgeText = 31100; + public const uint GossipBashirCrystalforgeItemTextReturn = 31101; + public const string GossipBashirCrystalforgeItem1 = "Purchase 1 Unstable Flask Of The Sorcerer For The Cost Of 10 Apexis Shards"; + public const string GossipBashirCrystalforgeItem5 = "Purchase 5 Unstable Flask Of The Sorcerer For The Cost Of 50 Apexis Shards"; + public const string GossipBashirCrystalforgeItemReturn = "Use The Bashir Crystalforge To Make Another Purchase."; + + //Matrixpunchograph + public const uint ItemWhitePunchCard = 9279; + public const uint ItemYellowPunchCard = 9280; + public const uint ItemBluePunchCard = 9282; + public const uint ItemRedPunchCard = 9281; + public const uint ItemPrismaticPunchCard = 9316; + public const uint SpellYellowPunchCard = 11512; + public const uint SpellBluePunchCard = 11525; + public const uint SpellRedPunchCard = 11528; + public const uint SpellPrismaticPunchCard = 11545; + public const uint MatrixPunchograph3005A = 142345; + public const uint MatrixPunchograph3005B = 142475; + public const uint MatrixPunchograph3005C = 142476; + public const uint MatrixPunchograph3005D = 142696; + + //Scourgecage + public const uint NpcScourgePrisoner = 25610; + + //Arcaneprison + public const uint QuestPrisonBreak = 11587; + public const uint SpellArcanePrisonerKillCredit = 45456; + + //Bloodfilledorb + public const uint NpcZelemar = 17830; + + //Jotunheimcage + public const uint NpcEbonBladePrisonerHuman = 30186; + public const uint NpcEbonBladePrisonerNe = 30194; + public const uint NpcEbonBladePrisonerTroll = 30196; + public const uint NpcEbonBladePrisonerOrc = 30195; + + public const uint SpellSummonBladeKnightH = 56207; + public const uint SpellSummonBladeKnightNe = 56209; + public const uint SpellSummonBladeKnightOrc = 56212; + public const uint SpellSummonBladeKnightTroll = 56214; + + //Tabletheka + public const uint GossipTableTheka = 1653; + public const uint QuestSpiderGold = 2936; + + //Inconspicuouslandmark + public const uint SpellSummonPiratesTreasureAndTriggerMob = 11462; + public const uint ItemCuergosKey = 9275; + + //Prisonersofwyrmskull + public const uint QuestPrisonersOfWyrmskull = 11255; + public const uint NpcPrisonerPriest = 24086; + public const uint NpcPrisonerMage = 24088; + public const uint NpcPrisonerWarrior = 24089; + public const uint NpcPrisonerPaladin = 24090; + public const uint NpcCapturedValgardePrisonerProxy = 24124; + + //Tadpoles + public const uint QuestOhNoesTheTadpoles = 11560; + public const uint NpcWinterfinTadpole = 25201; + + //Amberpineouthouse + public const uint ItemAnderholsSliderCider = 37247; + public const uint NpcOuthouseBunny = 27326; + public const uint QuestDoingYourDuty = 12227; + public const uint SpellIndisposed = 53017; + public const uint SpellIndisposedIii = 48341; + public const uint SpellCreateAmberseeds = 48330; + public const uint GossipOuthouseInuse = 12775; + public const uint GossipOuthouseVacant = 12779; + + public const string GossipUseOuthouse = "Use The Outhouse."; + public const string GoAnderholsSliderCiderNotFound = "Quest Item Anderhol'S Slider Cider Not Found."; + + //Hives + public const uint QuestHiveInTheTower = 9544; + public const uint NpcHiveAmbusher = 13301; + + //Missingfriends + public const uint QuestMissingFriends = 10852; + public const uint NpcCaptiveChild = 22314; + public const uint SayFree0 = 0; + + //Thecleansing + public const uint QuestTheCleansingHorde = 11317; + public const uint QuestTheCleansingAlliance = 11322; + public const uint SpellCleansingSoul = 43351; + public const uint SpellRecentMeditation = 61720; + + //Midsummerbonfire + public const uint StampOutBonfireQuestComplete = 45458; + + //MidsummerPoleRibbon + public const uint SpellPoleDance = 29726; + public const uint SpellBlueFireRing = 46842; + public const uint NpcPoleRibbonBunny = 17066; + + //Toy Train Set + public const uint SpellToyTrainPulse = 61551; + } + + [Script] + class go_cat_figurine : GameObjectScript + { + public go_cat_figurine() : base("go_cat_figurine") { } + + public override bool OnGossipHello(Player player, GameObject go) + { + player.CastSpell(player, GameobjectConst.SpellSummonGhostSaber, true); + return false; + } + } + + [Script] //go_barov_journal + class go_barov_journal : GameObjectScript + { + public go_barov_journal() : base("go_barov_journal") { } + + public override bool OnGossipHello(Player player, GameObject go) + { + if (player.HasSkill(SkillType.Tailoring) && player.GetBaseSkillValue(SkillType.Tailoring) >= 280 && !player.HasSpell(26086)) + player.CastSpell(player, 26095, false); + + return true; + } + } + + [Script] //go_gilded_brazier (Paladin First Trail quest (9678)) + class go_gilded_brazier : GameObjectScript + { + public go_gilded_brazier() : base("go_gilded_brazier") { } + + public override bool OnGossipHello(Player player, GameObject go) + { + if (go.GetGoType() == GameObjectTypes.Goober) + { + if (player.GetQuestStatus(GameobjectConst.QuestTheFirstTrial) == QuestStatus.Incomplete) + { + Creature Stillblade = player.SummonCreature(GameobjectConst.NpcStillblade, 8106.11f, -7542.06f, 151.775f, 3.02598f, TempSummonType.DeadDespawn, 60000); + if (Stillblade) + Stillblade.GetAI().AttackStart(player); + } + } + return true; + } + } + + [Script] //go_orb_of_command + class go_orb_of_command : GameObjectScript + { + public go_orb_of_command() : base("go_orb_of_command") { } + + public override bool OnGossipHello(Player player, GameObject go) + { + if (player.GetQuestRewardStatus(7761)) + player.CastSpell(player, 23460, true); + + return true; + } + } + + [Script] //go_tablet_of_madness + class go_tablet_of_madness : GameObjectScript + { + public go_tablet_of_madness() : base("go_tablet_of_madness") { } + + public override bool OnGossipHello(Player player, GameObject go) + { + if (player.HasSkill(SkillType.Alchemy) && player.GetSkillValue(SkillType.Alchemy) >= 300 && !player.HasSpell(24266)) + player.CastSpell(player, 24267, false); + + return true; + } + } + + [Script] //go_tablet_of_the_seven + class go_tablet_of_the_seven : GameObjectScript + { + public go_tablet_of_the_seven() : base("go_tablet_of_the_seven") { } + + /// @todo use gossip option ("Transcript the Tablet") instead, if Trinity adds support. + public override bool OnGossipHello(Player player, GameObject go) + { + if (go.GetGoType() != GameObjectTypes.QuestGiver) + return true; + + if (player.GetQuestStatus(4296) == QuestStatus.Incomplete) + player.CastSpell(player, 15065, false); + + return true; + } + } + + [Script] //go_jump_a_tron + class go_jump_a_tron : GameObjectScript + { + public go_jump_a_tron() : base("go_jump_a_tron") { } + + public override bool OnGossipHello(Player player, GameObject go) + { + if (player.GetQuestStatus(10111) == QuestStatus.Incomplete) + player.CastSpell(player, 33382, true); + + return true; + } + } + + [Script] //go_ethereum_prison + class go_ethereum_prison : GameObjectScript + { + public go_ethereum_prison() : base("go_ethereum_prison") { } + + public override bool OnGossipHello(Player player, GameObject go) + { + go.UseDoorOrButton(); + int Random = (int)(RandomHelper.Rand32() % (GameobjectConst.NpcPrisonEntry.Length / sizeof(uint))); + Creature creature = player.SummonCreature(GameobjectConst.NpcPrisonEntry[Random], go.GetPositionX(), go.GetPositionY(), go.GetPositionZ(), go.GetAngle(player), TempSummonType.TimedDespawnOOC, 30000); + if (creature) + { + if (!creature.IsHostileTo(player)) + { + FactionTemplateRecord pFaction = creature.GetFactionTemplateEntry(); + if (pFaction != null) + { + uint Spell = 0; + + switch (pFaction.Faction) + { + case 1011: Spell = GameobjectConst.SpellRepLc; break; + case 935: Spell = GameobjectConst.SpellRepShat; break; + case 942: Spell = GameobjectConst.SpellRepCe; break; + case 933: Spell = GameobjectConst.SpellRepCon; break; + case 989: Spell = GameobjectConst.SpellRepKt; break; + case 970: Spell = GameobjectConst.SpellRepSpor; break; + } + + if (Spell != 0) + creature.CastSpell(player, Spell, false); + else + Log.outError(LogFilter.Scripts, "go_ethereum_prison summoned Creature (entry {0}) but faction ({1}) are not expected by script.", creature.GetEntry(), creature.getFaction()); + } + } + } + + return false; + } + } + + [Script] //go_ethereum_stasis + class go_ethereum_stasis : GameObjectScript + { + public go_ethereum_stasis() : base("go_ethereum_stasis") { } + + public override bool OnGossipHello(Player player, GameObject go) + { + go.UseDoorOrButton(); + int Random = (int)(RandomHelper.Rand32() % GameobjectConst.NpcStasisEntry.Length / sizeof(uint)); + + player.SummonCreature(GameobjectConst.NpcStasisEntry[Random], go.GetPositionX(), go.GetPositionY(), go.GetPositionZ(), go.GetAngle(player), TempSummonType.TimedDespawnOOC, 30000); + + return false; + } + } + + [Script] //go_resonite_cask + class go_resonite_cask : GameObjectScript + { + public go_resonite_cask() : base("go_resonite_cask") { } + + public override bool OnGossipHello(Player Player, GameObject go) + { + if (go.GetGoType() == GameObjectTypes.Goober) + go.SummonCreature(GameobjectConst.NpcGoggeroc, 0.0f, 0.0f, 0.0f, 0.0f, TempSummonType.TimedDespawnOOC, 300000); + + return false; + } + } + + [Script] //go_sacred_fire_of_life + class go_sacred_fire_of_life : GameObjectScript + { + public go_sacred_fire_of_life() : base("go_sacred_fire_of_life") { } + + public override bool OnGossipHello(Player player, GameObject go) + { + if (go.GetGoType() == GameObjectTypes.Goober) + player.SummonCreature(GameobjectConst.NpcArikara, -5008.338f, -2118.894f, 83.657f, 0.874f, TempSummonType.TimedDespawnOOC, 30000); + + return true; + } + } + + [Script] //go_shrine_of_the_birds + class go_shrine_of_the_birds : GameObjectScript + { + public go_shrine_of_the_birds() : base("go_shrine_of_the_birds") { } + + public override bool OnGossipHello(Player player, GameObject go) + { + uint BirdEntry = 0; + + float fX, fY, fZ; + go.GetClosePoint(out fX, out fY, out fZ, go.GetObjectSize(), SharedConst.InteractionDistance); + + switch (go.GetEntry()) + { + case GameobjectConst.GoShrineHawk: + BirdEntry = GameobjectConst.NpcHawkGuard; + break; + case GameobjectConst.GoShrineEagle: + BirdEntry = GameobjectConst.NpcEagleGuard; + break; + case GameobjectConst.GoShrineFalcon: + BirdEntry = GameobjectConst.NpcFalconGuard; + break; + } + + if (BirdEntry != 0) + player.SummonCreature(BirdEntry, fX, fY, fZ, go.GetOrientation(), TempSummonType.TimedDespawnOOC, 60000); + + return false; + } + } + + [Script] //go_southfury_moonstone + class go_southfury_moonstone : GameObjectScript + { + public go_southfury_moonstone() : base("go_southfury_moonstone") { } + + public override bool OnGossipHello(Player player, GameObject go) + { + //implicitTarget=48 not implemented as of writing this code, and manual summon may be just ok for our purpose + //player.CastSpell(player, SPELL_SUMMON_RIZZLE, false); + + Creature creature = player.SummonCreature(GameobjectConst.NpcRizzle, 0.0f, 0.0f, 0.0f, 0.0f, TempSummonType.DeadDespawn, 0); + if (creature) + creature.CastSpell(player, GameobjectConst.SpellBlackjack, false); + + return false; + } + } + + [Script] //go_tele_to_dalaran_crystal + class go_tele_to_dalaran_crystal : GameObjectScript + { + public go_tele_to_dalaran_crystal() : base("go_tele_to_dalaran_crystal") { } + + public override bool OnGossipHello(Player player, GameObject go) + { + if (player.GetQuestRewardStatus(GameobjectConst.QuestTeleCrystalFlag)) + return false; + + player.GetSession().SendNotification(GameobjectConst.GoTeleToDalaranCrystalFailed); + + return true; + } + } + + [Script] //go_tele_to_violet_stand + class go_tele_to_violet_stand : GameObjectScript + { + public go_tele_to_violet_stand() : base("go_tele_to_violet_stand") { } + + public override bool OnGossipHello(Player player, GameObject go) + { + if (player.GetQuestRewardStatus(GameobjectConst.QuestLearnLeaveReturn) || player.GetQuestStatus(GameobjectConst.QuestLearnLeaveReturn) == QuestStatus.Incomplete) + return false; + + return true; + } + } + + [Script] //go_fel_crystalforge + class go_fel_crystalforge : GameObjectScript + { + public go_fel_crystalforge() : base("go_fel_crystalforge") { } + + public override bool OnGossipHello(Player player, GameObject go) + { + if (go.GetGoType() == GameObjectTypes.QuestGiver) /* != GAMEOBJECT_TYPE_QUESTGIVER) */ + player.PrepareQuestMenu(go.GetGUID()); /* return true*/ + + player.ADD_GOSSIP_ITEM(GossipOptionIcon.Chat, GameobjectConst.GossipFelCrystalforgeItem1, eTradeskill.GossipSenderMain, eTradeskill.GossipActionInfoDef); + player.ADD_GOSSIP_ITEM(GossipOptionIcon.Chat, GameobjectConst.GossipFelCrystalforgeItem5, eTradeskill.GossipSenderMain, eTradeskill.GossipActionInfoDef + 1); + + player.SEND_GOSSIP_MENU(GameobjectConst.GossipFelCrystalforgeText, go.GetGUID()); + + return true; + } + + public override bool OnGossipSelect(Player player, GameObject go, uint sender, uint action) + { + player.PlayerTalkClass.ClearMenus(); + switch (action) + { + case eTradeskill.GossipActionInfoDef: + player.CastSpell(player, GameobjectConst.SpellCreate1FlaskOfBeast, false); + player.ADD_GOSSIP_ITEM(GossipOptionIcon.Chat, GameobjectConst.GossipFelCrystalforgeItemReturn, eTradeskill.GossipSenderMain, eTradeskill.GossipActionInfoDef + 2); + player.SEND_GOSSIP_MENU(GameobjectConst.GossipFelCrystalforgeItemTextReturn, go.GetGUID()); + break; + case eTradeskill.GossipActionInfoDef + 1: + player.CastSpell(player, GameobjectConst.SpellCreate5FlaskOfBeast, false); + player.ADD_GOSSIP_ITEM(GossipOptionIcon.Chat, GameobjectConst.GossipFelCrystalforgeItemReturn, eTradeskill.GossipSenderMain, eTradeskill.GossipActionInfoDef + 2); + player.SEND_GOSSIP_MENU(GameobjectConst.GossipFelCrystalforgeItemTextReturn, go.GetGUID()); + break; + case eTradeskill.GossipActionInfoDef + 2: + player.ADD_GOSSIP_ITEM(GossipOptionIcon.Chat, GameobjectConst.GossipFelCrystalforgeItem1, eTradeskill.GossipSenderMain, eTradeskill.GossipActionInfoDef); + player.ADD_GOSSIP_ITEM(GossipOptionIcon.Chat, GameobjectConst.GossipFelCrystalforgeItem5, eTradeskill.GossipSenderMain, eTradeskill.GossipActionInfoDef + 1); + player.SEND_GOSSIP_MENU(GameobjectConst.GossipFelCrystalforgeText, go.GetGUID()); + break; + } + return true; + } + } + + [Script] //go_bashir_crystalforge + class go_bashir_crystalforge : GameObjectScript + { + public go_bashir_crystalforge() : base("go_bashir_crystalforge") { } + + public override bool OnGossipHello(Player player, GameObject go) + { + if (go.GetGoType() == GameObjectTypes.QuestGiver) /* != GAMEOBJECT_TYPE_QUESTGIVER) */ + player.PrepareQuestMenu(go.GetGUID()); /* return true*/ + + player.ADD_GOSSIP_ITEM(GossipOptionIcon.Chat, GameobjectConst.GossipBashirCrystalforgeItem1, eTradeskill.GossipSenderMain, eTradeskill.GossipActionInfoDef); + player.ADD_GOSSIP_ITEM(GossipOptionIcon.Chat, GameobjectConst.GossipBashirCrystalforgeItem5, eTradeskill.GossipSenderMain, eTradeskill.GossipActionInfoDef + 1); + + player.SEND_GOSSIP_MENU(GameobjectConst.GossipBashirCrystalforgeText, go.GetGUID()); + + return true; + } + + public override bool OnGossipSelect(Player player, GameObject go, uint sender, uint action) + { + player.PlayerTalkClass.ClearMenus(); + switch (action) + { + case eTradeskill.GossipActionInfoDef: + player.CastSpell(player, GameobjectConst.SpellCreate1FlaskOfSorcerer, false); + player.ADD_GOSSIP_ITEM(GossipOptionIcon.Chat, GameobjectConst.GossipBashirCrystalforgeItemReturn, eTradeskill.GossipSenderMain, eTradeskill.GossipActionInfoDef + 2); + player.SEND_GOSSIP_MENU(GameobjectConst.GossipBashirCrystalforgeItemTextReturn, go.GetGUID()); + break; + case eTradeskill.GossipActionInfoDef + 1: + player.CastSpell(player, GameobjectConst.SpellCreate5FlaskOfBeast, false); + player.ADD_GOSSIP_ITEM(GossipOptionIcon.Chat, GameobjectConst.GossipBashirCrystalforgeItemReturn, eTradeskill.GossipSenderMain, eTradeskill.GossipActionInfoDef + 2); + player.SEND_GOSSIP_MENU(GameobjectConst.GossipBashirCrystalforgeItemTextReturn, go.GetGUID()); + break; + case eTradeskill.GossipActionInfoDef + 2: + player.ADD_GOSSIP_ITEM(GossipOptionIcon.Chat, GameobjectConst.GossipBashirCrystalforgeItem1, eTradeskill.GossipSenderMain, eTradeskill.GossipActionInfoDef); + player.ADD_GOSSIP_ITEM(GossipOptionIcon.Chat, GameobjectConst.GossipBashirCrystalforgeItem5, eTradeskill.GossipSenderMain, eTradeskill.GossipActionInfoDef + 1); + player.SEND_GOSSIP_MENU(GameobjectConst.GossipBashirCrystalforgeText, go.GetGUID()); + break; + } + return true; + } + } + + [Script] //matrix_punchograph + class go_matrix_punchograph : GameObjectScript + { + public go_matrix_punchograph() : base("go_matrix_punchograph") { } + + public override bool OnGossipHello(Player player, GameObject go) + { + switch (go.GetEntry()) + { + case GameobjectConst.MatrixPunchograph3005A: + if (player.HasItemCount(GameobjectConst.ItemWhitePunchCard)) + { + player.DestroyItemCount(GameobjectConst.ItemWhitePunchCard, 1, true); + player.CastSpell(player, GameobjectConst.SpellYellowPunchCard, true); + } + break; + case GameobjectConst.MatrixPunchograph3005B: + if (player.HasItemCount(GameobjectConst.ItemYellowPunchCard)) + { + player.DestroyItemCount(GameobjectConst.ItemYellowPunchCard, 1, true); + player.CastSpell(player, GameobjectConst.SpellBluePunchCard, true); + } + break; + case GameobjectConst.MatrixPunchograph3005C: + if (player.HasItemCount(GameobjectConst.ItemBluePunchCard)) + { + player.DestroyItemCount(GameobjectConst.ItemBluePunchCard, 1, true); + player.CastSpell(player, GameobjectConst.SpellRedPunchCard, true); + } + break; + case GameobjectConst.MatrixPunchograph3005D: + if (player.HasItemCount(GameobjectConst.ItemRedPunchCard)) + { + player.DestroyItemCount(GameobjectConst.ItemRedPunchCard, 1, true); + player.CastSpell(player, GameobjectConst.SpellPrismaticPunchCard, true); + } + break; + default: + break; + } + return false; + } + } + + [Script] //go_scourge_cage + class go_scourge_cage : GameObjectScript + { + public go_scourge_cage() : base("go_scourge_cage") { } + + public override bool OnGossipHello(Player player, GameObject go) + { + go.UseDoorOrButton(); + Creature pNearestPrisoner = go.FindNearestCreature(GameobjectConst.NpcScourgePrisoner, 5.0f, true); + if (pNearestPrisoner) + { + player.KilledMonsterCredit(GameobjectConst.NpcScourgePrisoner, pNearestPrisoner.GetGUID()); + pNearestPrisoner.DisappearAndDie(); + } + + return true; + } + } + + [Script] //go_arcane_prison + class go_arcane_prison : GameObjectScript + { + public go_arcane_prison() : base("go_arcane_prison") { } + + public override bool OnGossipHello(Player player, GameObject go) + { + if (player.GetQuestStatus(GameobjectConst.QuestPrisonBreak) == QuestStatus.Incomplete) + { + go.SummonCreature(25318, 3485.089844f, 6115.7422188f, 70.966812f, 0, TempSummonType.TimedDespawn, 60000); + player.CastSpell(player, GameobjectConst.SpellArcanePrisonerKillCredit, true); + return true; + } + return false; + } + } + + [Script] //go_blood_filled_orb + class go_blood_filled_orb : GameObjectScript + { + public go_blood_filled_orb() : base("go_blood_filled_orb") { } + + public override bool OnGossipHello(Player player, GameObject go) + { + if (go.GetGoType() == GameObjectTypes.Goober) + player.SummonCreature(GameobjectConst.NpcZelemar, -369.746f, 166.759f, -21.50f, 5.235f, TempSummonType.TimedDespawnOOC, 30000); + + return true; + } + } + + [Script] //go_jotunheim_cage + class go_jotunheim_cage : GameObjectScript + { + public go_jotunheim_cage() : base("go_jotunheim_cage") { } + + public override bool OnGossipHello(Player player, GameObject go) + { + go.UseDoorOrButton(); + Creature pPrisoner = go.FindNearestCreature(GameobjectConst.NpcEbonBladePrisonerHuman, 5.0f, true); + if (!pPrisoner) + { + pPrisoner = go.FindNearestCreature(GameobjectConst.NpcEbonBladePrisonerTroll, 5.0f, true); + if (!pPrisoner) + { + pPrisoner = go.FindNearestCreature(GameobjectConst.NpcEbonBladePrisonerOrc, 5.0f, true); + if (!pPrisoner) + pPrisoner = go.FindNearestCreature(GameobjectConst.NpcEbonBladePrisonerNe, 5.0f, true); + } + } + if (!pPrisoner || !pPrisoner.IsAlive()) + return false; + + pPrisoner.DisappearAndDie(); + player.KilledMonsterCredit(GameobjectConst.NpcEbonBladePrisonerHuman); + switch (pPrisoner.GetEntry()) + { + case GameobjectConst.NpcEbonBladePrisonerHuman: + player.CastSpell(player, GameobjectConst.SpellSummonBladeKnightH, true); + break; + case GameobjectConst.NpcEbonBladePrisonerNe: + player.CastSpell(player, GameobjectConst.SpellSummonBladeKnightNe, true); + break; + case GameobjectConst.NpcEbonBladePrisonerTroll: + player.CastSpell(player, GameobjectConst.SpellSummonBladeKnightTroll, true); + break; + case GameobjectConst.NpcEbonBladePrisonerOrc: + player.CastSpell(player, GameobjectConst.SpellSummonBladeKnightOrc, true); + break; + } + return true; + } + } + + [Script] + class go_table_theka : GameObjectScript + { + public go_table_theka() : base("go_table_theka") { } + + public override bool OnGossipHello(Player player, GameObject go) + { + if (player.GetQuestStatus(GameobjectConst.QuestSpiderGold) == QuestStatus.Incomplete) + player.AreaExploredOrEventHappens(GameobjectConst.QuestSpiderGold); + + player.SEND_GOSSIP_MENU(GameobjectConst.GossipTableTheka, go.GetGUID()); + + return true; + } + } + + [Script] //go_inconspicuous_landmark + class go_inconspicuous_landmark : GameObjectScript + { + public go_inconspicuous_landmark() : base("go_inconspicuous_landmark") { } + + public override bool OnGossipHello(Player player, GameObject go) + { + if (player.HasItemCount(GameobjectConst.ItemCuergosKey)) + return false; + + player.CastSpell(player, GameobjectConst.SpellSummonPiratesTreasureAndTriggerMob, true); + + return true; + } + } + + [Script] //go_soulwell + class go_soulwell : GameObjectScript + { + public go_soulwell() : base("go_soulwell") { } + + class go_soulwellAI : GameObjectAI + { + public go_soulwellAI(GameObject go) : base(go) { } + + public override bool GossipHello(Player player, bool isUse) + { + if (!isUse) + return true; + + Unit owner = go.GetOwner(); + if (!owner || !owner.IsTypeId(TypeId.Player) || !player.IsInSameRaidWith(owner.ToPlayer())) + return true; + return false; + } + } + + public override GameObjectAI GetAI(GameObject go) + { + return new go_soulwellAI(go); + } + } + + [Script] //go_dragonflayer_cage + class go_dragonflayer_cage : GameObjectScript + { + public go_dragonflayer_cage() : base("go_dragonflayer_cage") { } + + public override bool OnGossipHello(Player player, GameObject go) + { + go.UseDoorOrButton(); + if (player.GetQuestStatus(GameobjectConst.QuestPrisonersOfWyrmskull) != QuestStatus.Incomplete) + return true; + + Creature pPrisoner = go.FindNearestCreature(GameobjectConst.NpcPrisonerPriest, 2.0f); + if (!pPrisoner) + { + pPrisoner = go.FindNearestCreature(GameobjectConst.NpcPrisonerMage, 2.0f); + if (!pPrisoner) + { + pPrisoner = go.FindNearestCreature(GameobjectConst.NpcPrisonerWarrior, 2.0f); + if (!pPrisoner) + pPrisoner = go.FindNearestCreature(GameobjectConst.NpcPrisonerPaladin, 2.0f); + } + } + + if (!pPrisoner || !pPrisoner.IsAlive()) + return true; + + /// @todo prisoner should help player for a short period of time + player.KilledMonsterCredit(GameobjectConst.NpcCapturedValgardePrisonerProxy); + pPrisoner.DespawnOrUnsummon(); + return true; + } + } + + [Script] //go_tadpole_cage + class go_tadpole_cage : GameObjectScript + { + public go_tadpole_cage() : base("go_tadpole_cage") { } + + public override bool OnGossipHello(Player player, GameObject go) + { + go.UseDoorOrButton(); + if (player.GetQuestStatus(GameobjectConst.QuestOhNoesTheTadpoles) == QuestStatus.Incomplete) + { + Creature pTadpole = go.FindNearestCreature(GameobjectConst.NpcWinterfinTadpole, 1.0f); + if (pTadpole) + { + pTadpole.DisappearAndDie(); + player.KilledMonsterCredit(GameobjectConst.NpcWinterfinTadpole); + //FIX: Summon minion tadpole + } + } + return true; + } + } + + [Script] //go_amberpine_outhouse + class go_amberpine_outhouse : GameObjectScript + { + public go_amberpine_outhouse() : base("go_amberpine_outhouse") { } + + public override bool OnGossipHello(Player player, GameObject go) + { + QuestStatus status = player.GetQuestStatus(GameobjectConst.QuestDoingYourDuty); + if (status == QuestStatus.Incomplete || status == QuestStatus.Complete || status == QuestStatus.Rewarded) + { + player.ADD_GOSSIP_ITEM(GossipOptionIcon.Chat, GameobjectConst.GossipUseOuthouse, eTradeskill.GossipSenderMain, eTradeskill.GossipActionInfoDef + 1); + player.SEND_GOSSIP_MENU(GameobjectConst.GossipOuthouseVacant, go.GetGUID()); + } + else + player.SEND_GOSSIP_MENU(GameobjectConst.GossipOuthouseInuse, go.GetGUID()); + + return true; + } + + public override bool OnGossipSelect(Player player, GameObject go, uint sender, uint action) + { + player.PlayerTalkClass.ClearMenus(); + if (action == eTradeskill.GossipActionInfoDef + 1) + { + player.CLOSE_GOSSIP_MENU(); + Creature target = ScriptedAI.GetClosestCreatureWithEntry(player, GameobjectConst.NpcOuthouseBunny, 3.0f); + if (target) + { + target.GetAI().SetData(1, (uint)player.GetGender()); + go.CastSpell(target, GameobjectConst.SpellIndisposedIii); + } + go.CastSpell(player, GameobjectConst.SpellIndisposed); + if (player.HasItemCount(GameobjectConst.ItemAnderholsSliderCider)) + go.CastSpell(player, GameobjectConst.SpellCreateAmberseeds); + return true; + } + else + { + player.CLOSE_GOSSIP_MENU(); + player.GetSession().SendNotification(GameobjectConst.GoAnderholsSliderCiderNotFound); + return false; + } + } + } + + [Script] //go_hive_pod + class go_hive_pod : GameObjectScript + { + public go_hive_pod() : base("go_hive_pod") { } + + public override bool OnGossipHello(Player player, GameObject go) + { + player.SendLoot(go.GetGUID(), LootType.Corpse); + go.SummonCreature(GameobjectConst.NpcHiveAmbusher, go.GetPositionX() + 1, go.GetPositionY(), go.GetPositionZ(), go.GetAngle(player), TempSummonType.TimedOrDeadDespawn, 60000); + go.SummonCreature(GameobjectConst.NpcHiveAmbusher, go.GetPositionX(), go.GetPositionY() + 1, go.GetPositionZ(), go.GetAngle(player), TempSummonType.TimedOrDeadDespawn, 60000); + return true; + } + } + + [Script] + class go_massive_seaforium_charge : GameObjectScript + { + public go_massive_seaforium_charge() : base("go_massive_seaforium_charge") { } + + public override bool OnGossipHello(Player Player, GameObject go) + { + go.SetLootState(LootState.JustDeactivated); + return true; + } + } + + [Script] //go_veil_skith_cage + class go_veil_skith_cage : GameObjectScript + { + public go_veil_skith_cage() : base("go_veil_skith_cage") { } + + public override bool OnGossipHello(Player player, GameObject go) + { + go.UseDoorOrButton(); + if (player.GetQuestStatus(GameobjectConst.QuestMissingFriends) == QuestStatus.Incomplete) + { + List childrenList = new List(); + go.GetCreatureListWithEntryInGrid(childrenList, GameobjectConst.NpcCaptiveChild, SharedConst.InteractionDistance); + foreach (var creature in childrenList) + { + player.KilledMonsterCredit(GameobjectConst.NpcCaptiveChild, creature.GetGUID()); + creature.DespawnOrUnsummon(5000); + creature.GetMotionMaster().MovePoint(1, go.GetPositionX() + 5, go.GetPositionY(), go.GetPositionZ()); + creature.GetAI().Talk(GameobjectConst.SayFree0); + creature.GetMotionMaster().Clear(); + } + } + return false; + } + } + + [Script] //go_frostblade_shrine + class go_frostblade_shrine : GameObjectScript + { + public go_frostblade_shrine() : base("go_frostblade_shrine") { } + + public override bool OnGossipHello(Player player, GameObject go) + { + go.UseDoorOrButton(10); + if (!player.HasAura(GameobjectConst.SpellRecentMeditation)) + if (player.GetQuestStatus(GameobjectConst.QuestTheCleansingHorde) == QuestStatus.Incomplete || player.GetQuestStatus(GameobjectConst.QuestTheCleansingAlliance) == QuestStatus.Incomplete) + { + player.CastSpell(player, GameobjectConst.SpellCleansingSoul); + player.SetStandState(UnitStandStateType.Sit); + } + return true; + } + } + + [Script] //go_midsummer_bonfire + class go_midsummer_bonfire : GameObjectScript + { + public go_midsummer_bonfire() : base("go_midsummer_bonfire") { } + + public override bool OnGossipSelect(Player player, GameObject go, uint sender, uint action) + { + player.CastSpell(player, GameobjectConst.StampOutBonfireQuestComplete, true); + player.CLOSE_GOSSIP_MENU(); + return false; + } + } + + [Script] + class go_midsummer_ribbon_pole : GameObjectScript + { + public go_midsummer_ribbon_pole() : base("go_midsummer_ribbon_pole") { } + + public override bool OnGossipHello(Player player, GameObject go) + { + Creature creature = go.FindNearestCreature(GameobjectConst.NpcPoleRibbonBunny, 10.0f); + if (creature) + { + creature.GetAI().DoAction(0); + player.CastSpell(creature, GameobjectConst.SpellPoleDance, true); + } + return true; + } + } + + [Script] + class go_toy_train_set : GameObjectScript + { + public go_toy_train_set() : base("go_toy_train_set") { } + + class go_toy_train_setAI : GameObjectAI + { + public go_toy_train_setAI(GameObject go) : base(go) + { + _scheduler.Schedule(TimeSpan.FromSeconds(3), task => + { + go.CastSpell(null, GameobjectConst.SpellToyTrainPulse, true); + task.Repeat(TimeSpan.FromSeconds(6)); + }); + } + + public override void UpdateAI(uint diff) + { + _scheduler.Update(diff); + } + + // triggered on wrecker'd + public override void DoAction(int action) + { + go.Delete(); + } + } + + public override GameObjectAI GetAI(GameObject go) + { + return new go_toy_train_setAI(go); + } + } +} diff --git a/Scripts/World/Guards.cs b/Scripts/World/Guards.cs new file mode 100644 index 000000000..a0fb85827 --- /dev/null +++ b/Scripts/World/Guards.cs @@ -0,0 +1,382 @@ +/* + * Copyright (C) 2012-2017 CypherCore + * + * 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, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +using Framework.Constants; +using Game.AI; +using Game.Entities; +using Game.Scripting; +using Game.Spells; +using System; + +namespace Scripts.World +{ + public struct GuardsConst + { + public const int CreatureCooldown = 5000; + public const int SaySilAggro = 0; + } + + struct CreatureIds + { + public const int CenarionHoldIndantry = 15184; + public const int StormwindCityGuard = 68; + public const int StormwindCityPatroller = 1976; + public const int OrgimmarGrunt = 3296; + } + + struct Spells + { + public const uint BanishedA = 36642; + public const uint BanishedS = 36671; + public const uint BanishTeleport = 36643; + public const uint Exile = 39533; + } + + [Script] + class guard_generic : CreatureScript + { + public guard_generic() : base("guard_generic") { } + + class guard_genericAI : GuardAI + { + public guard_genericAI(Creature creature) : base(creature) { } + + public override void Reset() + { + globalCooldown = 0; + buffTimer = 0; + } + + public override void EnterCombat(Unit who) + { + if (me.GetEntry() == CreatureIds.CenarionHoldIndantry) + Talk(GuardsConst.SaySilAggro, who); + SpellInfo spell = me.reachWithSpellAttack(who); + if (spell != null) + DoCast(who, spell.Id); + } + + public override void UpdateAI(uint diff) + { + //Always decrease our global cooldown first + if (globalCooldown > diff) + globalCooldown -= diff; + else + globalCooldown = 0; + + //Buff timer (only buff when we are alive and not in combat + if (me.IsAlive() && !me.IsInCombat()) + { + if (buffTimer <= diff) + { + //Find a spell that targets friendly and applies an aura (these are generally buffs) + SpellInfo info = SelectSpell(me, 0, 0, SelectTargetType.AnyFriend, 0, 0, SelectEffect.Aura); + + if (info != null && globalCooldown == 0) + { + //Cast the buff spell + DoCast(me, info.Id); + + //Set our global cooldown + globalCooldown = GuardsConst.CreatureCooldown; + + //Set our timer to 10 minutes before rebuff + buffTimer = 600000; + } //Try again in 30 seconds + else buffTimer = 30000; + } + else buffTimer -= diff; + } + + //Return since we have no target + if (!UpdateVictim()) + return; + + // Make sure our attack is ready and we arn't currently casting + if (me.isAttackReady() && !me.IsNonMeleeSpellCast(false)) + { + //If we are within range melee the target + if (me.IsWithinMeleeRange(me.GetVictim())) + { + bool healing = false; + SpellInfo info = null; + + //Select a healing spell if less than 30% hp + if (me.HealthBelowPct(30)) + info = SelectSpell(me, 0, 0, SelectTargetType.AnyFriend, 0, 0, SelectEffect.Healing); + + //No healing spell available, select a hostile spell + if (info != null) + healing = true; + else + info = SelectSpell(me.GetVictim(), 0, 0, SelectTargetType.AnyEnemy, 0, 0, SelectEffect.DontCare); + + //20% chance to replace our white hit with a spell + if (info != null && RandomHelper.IRand(0, 99) < 20 && globalCooldown == 0) + { + //Cast the spell + if (healing) + DoCast(me, info.Id); + else + DoCastVictim(info.Id); + + //Set our global cooldown + globalCooldown = GuardsConst.CreatureCooldown; + } + else + me.AttackerStateUpdate(me.GetVictim()); + + me.resetAttackTimer(); + } + } + else + { + //Only run this code if we arn't already casting + if (!me.IsNonMeleeSpellCast(false)) + { + bool healing = false; + SpellInfo info = null; + + //Select a healing spell if less than 30% hp ONLY 33% of the time + if (me.HealthBelowPct(30) && 33 > RandomHelper.IRand(0, 99)) + info = SelectSpell(me, 0, 0, SelectTargetType.AnyFriend, 0, 0, SelectEffect.Healing); + + //No healing spell available, See if we can cast a ranged spell (Range must be greater than ATTACK_DISTANCE) + if (info != null) + healing = true; + else + info = SelectSpell(me.GetVictim(), 0, 0, SelectTargetType.AnyEnemy, SharedConst.NominalMeleeRange, 0, SelectEffect.DontCare); + + //Found a spell, check if we arn't on cooldown + if (info != null && globalCooldown == 0) + { + //If we are currently moving stop us and set the movement generator + if (me.GetMotionMaster().GetCurrentMovementGeneratorType() != MovementGeneratorType.Idle) + { + me.GetMotionMaster().Clear(false); + me.GetMotionMaster().MoveIdle(); + } + + //Cast spell + if (healing) + DoCast(me, info.Id); + else + DoCastVictim(info.Id); + + //Set our global cooldown + globalCooldown = GuardsConst.CreatureCooldown; + } //If no spells available and we arn't moving run to target + else if (me.GetMotionMaster().GetCurrentMovementGeneratorType() != MovementGeneratorType.Chase) + { + //Cancel our current spell and then mutate new movement generator + me.InterruptNonMeleeSpells(false); + me.GetMotionMaster().Clear(false); + me.GetMotionMaster().MoveChase(me.GetVictim()); + } + } + } + + DoMeleeAttackIfReady(); + } + + public void DoReplyToTextEmote(TextEmotes emote) + { + switch (emote) + { + case TextEmotes.Kiss: + me.HandleEmoteCommand(Emote.OneshotBow); + break; + + case TextEmotes.Wave: + me.HandleEmoteCommand(Emote.OneshotWave); + break; + + case TextEmotes.Salute: + me.HandleEmoteCommand(Emote.OneshotSalute); + break; + + case TextEmotes.Shy: + me.HandleEmoteCommand(Emote.OneshotFlex); + break; + + case TextEmotes.Rude: + case TextEmotes.Chicken: + me.HandleEmoteCommand(Emote.OneshotPoint); + break; + } + } + + public override void ReceiveEmote(Player player, TextEmotes textEmote) + { + switch (me.GetEntry()) + { + case CreatureIds.StormwindCityGuard: + case CreatureIds.StormwindCityPatroller: + case CreatureIds.OrgimmarGrunt: + break; + default: + return; + } + + if (!me.IsFriendlyTo(player)) + return; + + DoReplyToTextEmote(textEmote); + } + + uint globalCooldown; + uint buffTimer; + } + + public override CreatureAI GetAI(Creature creature) + { + return new guard_genericAI(creature); + } + } + + [Script] + class guard_shattrath_scryer : CreatureScript + { + public guard_shattrath_scryer() : base("guard_shattrath_scryer") { } + + class guard_shattrath_scryerAI : GuardAI + { + public guard_shattrath_scryerAI(Creature creature) : base(creature) { } + + public override void Reset() + { + playerGUID.Clear(); + canTeleport = false; + + _scheduler.Schedule(TimeSpan.FromSeconds(5), task => + { + Unit temp = me.GetVictim(); + if (temp && temp.IsTypeId(TypeId.Player)) + { + DoCast(temp, Spells.BanishedA); + playerGUID = temp.GetGUID(); + if (!playerGUID.IsEmpty()) + canTeleport = true; + + task.Repeat(TimeSpan.FromSeconds(9)); + } + }); + + _scheduler.Schedule(TimeSpan.FromSeconds(8.5), task => + { + if (canTeleport) + { + Unit temp = Global.ObjAccessor.GetUnit(me, playerGUID); + if (temp) + { + temp.CastSpell(temp, Spells.Exile, true); + temp.CastSpell(temp, Spells.BanishTeleport, true); + } + playerGUID.Clear(); + canTeleport = false; + + task.Repeat(); + } + }); + } + + public override void UpdateAI(uint diff) + { + if (!UpdateVictim()) + return; + + _scheduler.Update(diff); + + DoMeleeAttackIfReady(); + } + + ObjectGuid playerGUID; + bool canTeleport; + } + + public override CreatureAI GetAI(Creature creature) + { + return new guard_shattrath_scryerAI(creature); + } + } + + [Script] + class guard_shattrath_aldor : CreatureScript + { + public guard_shattrath_aldor() : base("guard_shattrath_aldor") { } + + class guard_shattrath_aldorAI : GuardAI + { + public guard_shattrath_aldorAI(Creature creature) : base(creature) { } + + public override void Reset() + { + playerGUID.Clear(); + canTeleport = false; + + _scheduler.Schedule(TimeSpan.FromSeconds(5), task => + { + Unit temp = me.GetVictim(); + if (temp && temp.IsTypeId(TypeId.Player)) + { + DoCast(temp, Spells.BanishedA); + playerGUID = temp.GetGUID(); + if (!playerGUID.IsEmpty()) + canTeleport = true; + + task.Repeat(TimeSpan.FromSeconds(9)); + } + }); + + _scheduler.Schedule(TimeSpan.FromSeconds(8.5), task => + { + if (canTeleport) + { + Unit temp = Global.ObjAccessor.GetUnit(me, playerGUID); + if (temp) + { + temp.CastSpell(temp, Spells.Exile, true); + temp.CastSpell(temp, Spells.BanishTeleport, true); + } + playerGUID.Clear(); + canTeleport = false; + + task.Repeat(); + } + }); + } + + public override void UpdateAI(uint diff) + { + if (!UpdateVictim()) + return; + + _scheduler.Update(diff); + + DoMeleeAttackIfReady(); + } + + ObjectGuid playerGUID; + bool canTeleport; + } + + public override CreatureAI GetAI(Creature creature) + { + return new guard_shattrath_aldorAI(creature); + } + } +} diff --git a/Scripts/World/ItemScripts.cs b/Scripts/World/ItemScripts.cs new file mode 100644 index 000000000..13b1d4450 --- /dev/null +++ b/Scripts/World/ItemScripts.cs @@ -0,0 +1,361 @@ +/* +* Copyright (C) 2012-2017 CypherCore +* +* 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, either version 3 of the License, or +* (at your option) any later version. +* +* This program is distributed in the hope that it will be useful, +* but WITHOUT ANY WARRANTY; without even the implied warranty of +* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +* GNU General Public License for more details. +* +* You should have received a copy of the GNU General Public License +* along with this program. If not, see . +*/ + +using Framework.Constants; +using Framework.GameMath; +using Game.Entities; +using Game.Scripting; +using Game.Spells; +using System.Collections.Generic; + +namespace Scripts.World +{ + struct ItemScriptConst + { + //Onlyforflight + public const uint SpellArcaneCharges = 45072; + + //Pilefakefur + public const uint GoCaribouTrap1 = 187982; + public const uint GoCaribouTrap2 = 187995; + public const uint GoCaribouTrap3 = 187996; + public const uint GoCaribouTrap4 = 187997; + public const uint GoCaribouTrap5 = 187998; + public const uint GoCaribouTrap6 = 187999; + public const uint GoCaribouTrap7 = 188000; + public const uint GoCaribouTrap8 = 188001; + public const uint GoCaribouTrap9 = 188002; + public const uint GoCaribouTrap10 = 188003; + public const uint GoCaribouTrap11 = 188004; + public const uint GoCaribouTrap12 = 188005; + public const uint GoCaribouTrap13 = 188006; + public const uint GoCaribouTrap14 = 188007; + public const uint GoCaribouTrap15 = 188008; + public const uint GoHighQualityFur = 187983; + public const uint NpcNesingwaryTrapper = 25835; + + public static uint[] CaribouTraps = + { + GoCaribouTrap1, GoCaribouTrap2, GoCaribouTrap3, GoCaribouTrap4, GoCaribouTrap5, + GoCaribouTrap6, GoCaribouTrap7, GoCaribouTrap8, GoCaribouTrap9, GoCaribouTrap10, + GoCaribouTrap11, GoCaribouTrap12, GoCaribouTrap13, GoCaribouTrap14, GoCaribouTrap15, + }; + + //Petrovclusterbombs + public const uint SpellPetrovBomb = 42406; + public const uint AreaIdShatteredStraits = 4064; + public const uint ZoneIdHowling = 495; + + //Helpthemselves + public const uint QuestCannotHelpThemselves = 11876; + public const uint NpcTrappedMammothCalf = 25850; + public const uint GoMammothTrap1 = 188022; + public const uint GoMammothTrap2 = 188024; + public const uint GoMammothTrap3 = 188025; + public const uint GoMammothTrap4 = 188026; + public const uint GoMammothTrap5 = 188027; + public const uint GoMammothTrap6 = 188028; + public const uint GoMammothTrap7 = 188029; + public const uint GoMammothTrap8 = 188030; + public const uint GoMammothTrap9 = 188031; + public const uint GoMammothTrap10 = 188032; + public const uint GoMammothTrap11 = 188033; + public const uint GoMammothTrap12 = 188034; + public const uint GoMammothTrap13 = 188035; + public const uint GoMammothTrap14 = 188036; + public const uint GoMammothTrap15 = 188037; + public const uint GoMammothTrap16 = 188038; + public const uint GoMammothTrap17 = 188039; + public const uint GoMammothTrap18 = 188040; + public const uint GoMammothTrap19 = 188041; + public const uint GoMammothTrap20 = 188042; + public const uint GoMammothTrap21 = 188043; + public const uint GoMammothTrap22 = 188044; + + public static uint[] MammothTraps = + { + GoMammothTrap1, GoMammothTrap2, GoMammothTrap3, GoMammothTrap4, GoMammothTrap5, + GoMammothTrap6, GoMammothTrap7, GoMammothTrap8, GoMammothTrap9, GoMammothTrap10, + GoMammothTrap11, GoMammothTrap12, GoMammothTrap13, GoMammothTrap14, GoMammothTrap15, + GoMammothTrap16, GoMammothTrap17, GoMammothTrap18, GoMammothTrap19, GoMammothTrap20, + GoMammothTrap21, GoMammothTrap22 + }; + + //Theemissary + public const uint QuestTheEmissary = 11626; + public const uint NpcLeviroth = 26452; + + //Capturedfrog + public const uint QuestThePerfectSpies = 25444; + public const uint NpcVanirasSentryTotem = 40187; + } + + [Script] + class item_only_for_flight : ItemScript + { + public item_only_for_flight() : base("item_only_for_flight") { } + + public override bool OnUse(Player player, Item item, SpellCastTargets targets, ObjectGuid castId) + { + uint itemId = item.GetEntry(); + bool disabled = false; + + //for special scripts + switch (itemId) + { + case 24538: + if (player.GetAreaId() != 3628) + disabled = true; + break; + case 34489: + if (player.GetZoneId() != 4080) + disabled = true; + break; + case 34475: + SpellInfo spellInfo = Global.SpellMgr.GetSpellInfo(ItemScriptConst.SpellArcaneCharges); + if (spellInfo != null) + Spell.SendCastResult(player, spellInfo, 0, castId, SpellCastResult.NotOnGround); + break; + } + + // allow use in flight only + if (player.IsInFlight() && !disabled) + return false; + + // error + player.SendEquipError(InventoryResult.ClientLockedOut, item, null); + return true; + } + } + + [Script] //item_nether_wraith_beacon + class item_nether_wraith_beacon : ItemScript + { + public item_nether_wraith_beacon() : base("item_nether_wraith_beacon") { } + + public override bool OnUse(Player player, Item item, SpellCastTargets targets, ObjectGuid castId) + { + if (player.GetQuestStatus(10832) == QuestStatus.Incomplete) + { + Creature nether = player.SummonCreature(22408, player.GetPositionX(), player.GetPositionY() + 20, player.GetPositionZ(), 0, TempSummonType.TimedDespawn, 180000); + if (nether) + nether.GetAI().AttackStart(player); + + Creature nether1 = player.SummonCreature(22408, player.GetPositionX(), player.GetPositionY() - 20, player.GetPositionZ(), 0, TempSummonType.TimedDespawn, 180000); + if (nether1) + nether1.GetAI().AttackStart(player); + } + return false; + } + } + + [Script] //item_gor_dreks_ointment + class item_gor_dreks_ointment : ItemScript + { + public item_gor_dreks_ointment() : base("item_gor_dreks_ointment") { } + + public override bool OnUse(Player player, Item item, SpellCastTargets targets, ObjectGuid castId) + { + if (targets.GetUnitTarget() && targets.GetUnitTarget().IsTypeId(TypeId.Unit) && + targets.GetUnitTarget().GetEntry() == 20748 && !targets.GetUnitTarget().HasAura(32578)) + return false; + + player.SendEquipError(InventoryResult.ClientLockedOut, item, null); + return true; + } + } + + [Script] //item_incendiary_explosives + class item_incendiary_explosives : ItemScript + { + public item_incendiary_explosives() : base("item_incendiary_explosives") { } + + public override bool OnUse(Player player, Item item, SpellCastTargets targets, ObjectGuid castId) + { + if (player.FindNearestCreature(26248, 15) || player.FindNearestCreature(26249, 15)) + return false; + else + { + player.SendEquipError(InventoryResult.OutOfRange, item, null); + return true; + } + } + } + + [Script] //item_mysterious_egg + class item_mysterious_egg : ItemScript + { + public item_mysterious_egg() : base("item_mysterious_egg") { } + + public override bool OnExpire(Player player, ItemTemplate pItemProto) + { + List dest = new List(); + InventoryResult msg = player.CanStoreNewItem(ItemConst.NullBag, ItemConst.NullSlot, dest, 39883, 1); // Cracked Egg + if (msg == InventoryResult.Ok) + player.StoreNewItem(dest, 39883, true, ItemEnchantment.GenerateItemRandomPropertyId(39883)); + + return true; + } + } + + [Script] //item_disgusting_jar + class item_disgusting_jar : ItemScript + { + public item_disgusting_jar() : base("item_disgusting_jar") { } + + public override bool OnExpire(Player player, ItemTemplate pItemProto) + { + List dest = new List(); + InventoryResult msg = player.CanStoreNewItem(ItemConst.NullBag, ItemConst.NullSlot, dest, 44718, 1); // Ripe Disgusting Jar + if (msg == InventoryResult.Ok) + player.StoreNewItem(dest, 44718, true, ItemEnchantment.GenerateItemRandomPropertyId(44718)); + + return true; + } + } + + [Script] //item_pile_fake_furs + class item_pile_fake_furs : ItemScript + { + public item_pile_fake_furs() : base("item_pile_fake_furs") { } + + public override bool OnUse(Player player, Item item, SpellCastTargets targets, ObjectGuid castId) + { + GameObject go = null; + for (byte i = 0; i < ItemScriptConst.CaribouTraps.Length; ++i) + { + go = player.FindNearestGameObject(ItemScriptConst.CaribouTraps[i], 5.0f); + if (go) + break; + } + + if (!go) + return false; + + if (go.FindNearestCreature(ItemScriptConst.NpcNesingwaryTrapper, 10.0f, true) || go.FindNearestCreature(ItemScriptConst.NpcNesingwaryTrapper, 10.0f, false) || go.FindNearestGameObject(ItemScriptConst.GoHighQualityFur, 2.0f)) + return true; + + float x, y, z; + go.GetClosePoint(out x, out y, out z, go.GetObjectSize() / 3, 7.0f); + go.SummonGameObject(ItemScriptConst.GoHighQualityFur, go, Quaternion.WAxis, 1); + TempSummon summon = player.SummonCreature(ItemScriptConst.NpcNesingwaryTrapper, x, y, z, go.GetOrientation(), TempSummonType.DeadDespawn, 1000); + if (summon) + { + summon.SetVisible(false); + summon.SetReactState(ReactStates.Passive); + summon.SetFlag(UnitFields.Flags, UnitFlags.ImmuneToPc); + } + return false; + } + } + + [Script] //item_petrov_cluster_bombs + class item_petrov_cluster_bombs : ItemScript + { + public item_petrov_cluster_bombs() : base("item_petrov_cluster_bombs") { } + + public override bool OnUse(Player player, Item item, SpellCastTargets targets, ObjectGuid castId) + { + if (player.GetZoneId() != ItemScriptConst.ZoneIdHowling) + return false; + + if (!player.GetTransport() || player.GetAreaId() != ItemScriptConst.AreaIdShatteredStraits) + { + SpellInfo spellInfo = Global.SpellMgr.GetSpellInfo(ItemScriptConst.SpellPetrovBomb); + if (spellInfo != null) + Spell.SendCastResult(player, spellInfo, 0, castId, SpellCastResult.NotHere); + + return true; + } + + return false; + } + } + + //item_dehta_trap_smasher + [Script] //For quest 11876, Help Those That Cannot Help Themselves + class item_dehta_trap_smasher : ItemScript + { + public item_dehta_trap_smasher() : base("item_dehta_trap_smasher") { } + + public override bool OnUse(Player player, Item item, SpellCastTargets targets, ObjectGuid castId) + { + if (player.GetQuestStatus(ItemScriptConst.QuestCannotHelpThemselves) != QuestStatus.Incomplete) + return false; + + Creature pMammoth = player.FindNearestCreature(ItemScriptConst.NpcTrappedMammothCalf, 5.0f); + if (!pMammoth) + return false; + + GameObject pTrap = null; + for (byte i = 0; i < ItemScriptConst.MammothTraps.Length; ++i) + { + pTrap = player.FindNearestGameObject(ItemScriptConst.MammothTraps[i], 11.0f); + if (pTrap) + { + pMammoth.GetAI().DoAction(1); + pTrap.SetGoState(GameObjectState.Ready); + player.KilledMonsterCredit(ItemScriptConst.NpcTrappedMammothCalf); + return true; + } + } + return false; + } + } + + [Script] + class item_trident_of_nazjan : ItemScript + { + public item_trident_of_nazjan() : base("item_Trident_of_Nazjan") { } + + public override bool OnUse(Player player, Item item, SpellCastTargets targets, ObjectGuid castId) + { + if (player.GetQuestStatus(ItemScriptConst.QuestTheEmissary) == QuestStatus.Incomplete) + { + Creature pLeviroth = player.FindNearestCreature(ItemScriptConst.NpcLeviroth, 10.0f); + if (pLeviroth) // spell range + { + pLeviroth.GetAI().AttackStart(player); + return false; + } else + player.SendEquipError(InventoryResult.OutOfRange, item, null); + } else + player.SendEquipError(InventoryResult.ClientLockedOut, item, null); + return true; + } + } + + [Script] + class item_captured_frog : ItemScript + { + public item_captured_frog() : base("item_captured_frog") { } + + public override bool OnUse(Player player, Item item, SpellCastTargets targets, ObjectGuid castId) + { + if (player.GetQuestStatus(ItemScriptConst.QuestThePerfectSpies) == QuestStatus.Incomplete) + { + if (player.FindNearestCreature(ItemScriptConst.NpcVanirasSentryTotem, 10.0f)) + return false; + else + player.SendEquipError(InventoryResult.OutOfRange, item, null); + } + else + player.SendEquipError(InventoryResult.ClientLockedOut, item, null); + return true; + } + } +} diff --git a/Scripts/World/MobGenericCreature.cs b/Scripts/World/MobGenericCreature.cs new file mode 100644 index 000000000..e893c8700 --- /dev/null +++ b/Scripts/World/MobGenericCreature.cs @@ -0,0 +1,53 @@ +/* + * Copyright (C) 2012-2017 CypherCore + * + * 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, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +using Game.AI; +using Game.Entities; +using Game.Scripting; +using System; + +namespace Scripts.World +{ + [Script] + class trigger_periodic : CreatureScript + { + public trigger_periodic() : base("trigger_periodic") { } + + class trigger_periodicAI : NullCreatureAI + { + public trigger_periodicAI(Creature creature) : base(creature) + { + var interval = me.GetBaseAttackTime(Framework.Constants.WeaponAttackType.BaseAttack); + _scheduler.Schedule(TimeSpan.FromMilliseconds(interval), task => + { + me.CastSpell(me, me.m_spells[0], true); + task.Repeat(TimeSpan.FromMilliseconds(interval)); + }); + } + + public override void UpdateAI(uint diff) + { + _scheduler.Update(); + } + } + + public override CreatureAI GetAI(Creature creature) + { + return new trigger_periodicAI(creature); + } + } +} diff --git a/Scripts/World/NpcProfessions.cs b/Scripts/World/NpcProfessions.cs new file mode 100644 index 000000000..6d7e8b704 --- /dev/null +++ b/Scripts/World/NpcProfessions.cs @@ -0,0 +1,139 @@ +/* + * Copyright (C) 2012-2017 CypherCore + * + * 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, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +using Game.AI; +using Game.Entities; +using Game.Scripting; + +namespace Scripts.World +{ + enum GossipOptionIds + { + Alchemy = 0, + Blacksmithing = 1, + Enchanting = 2, + Engineering = 3, + Herbalism = 4, + Inscription = 5, + Jewelcrafting = 6, + Leatherworking = 7, + Mining = 8, + Skinning = 9, + Tailoring = 10, + Multi = 11, + } + + enum GossipMenuIds + { + Herbalism = 12188, + Mining = 12189, + Skinning = 12190, + Alchemy = 12191, + Blacksmithing = 12192, + Enchanting = 12193, + Engineering = 12195, + Inscription = 12196, + Jewelcrafting = 12197, + Leatherworking = 12198, + Tailoring = 12199, + } + + [Script] //start menu multi profession trainer + class npc_multi_profession_trainer : CreatureScript + { + public npc_multi_profession_trainer() : base("npc_multi_profession_trainer") { } + + class npc_multi_profession_trainerAI : ScriptedAI + { + public npc_multi_profession_trainerAI(Creature creature) : base(creature) { } + + public override void sGossipSelect(Player player, uint menuId, uint gossipListId) + { + switch ((GossipOptionIds)gossipListId) + { + case GossipOptionIds.Alchemy: + case GossipOptionIds.Blacksmithing: + case GossipOptionIds.Enchanting: + case GossipOptionIds.Engineering: + case GossipOptionIds.Herbalism: + case GossipOptionIds.Inscription: + case GossipOptionIds.Jewelcrafting: + case GossipOptionIds.Leatherworking: + case GossipOptionIds.Mining: + case GossipOptionIds.Skinning: + case GossipOptionIds.Tailoring: + SendTrainerList(player, (GossipOptionIds)gossipListId); + break; + case GossipOptionIds.Multi: + { + switch ((GossipMenuIds)menuId) + { + case GossipMenuIds.Herbalism: + SendTrainerList(player, GossipOptionIds.Herbalism); + break; + case GossipMenuIds.Mining: + SendTrainerList(player, GossipOptionIds.Mining); + break; + case GossipMenuIds.Skinning: + SendTrainerList(player, GossipOptionIds.Skinning); + break; + case GossipMenuIds.Alchemy: + SendTrainerList(player, GossipOptionIds.Alchemy); + break; + case GossipMenuIds.Blacksmithing: + SendTrainerList(player, GossipOptionIds.Blacksmithing); + break; + case GossipMenuIds.Enchanting: + SendTrainerList(player, GossipOptionIds.Enchanting); + break; + case GossipMenuIds.Engineering: + SendTrainerList(player, GossipOptionIds.Engineering); + break; + case GossipMenuIds.Inscription: + SendTrainerList(player, GossipOptionIds.Inscription); + break; + case GossipMenuIds.Jewelcrafting: + SendTrainerList(player, GossipOptionIds.Jewelcrafting); + break; + case GossipMenuIds.Leatherworking: + SendTrainerList(player, GossipOptionIds.Leatherworking); + break; + case GossipMenuIds.Tailoring: + SendTrainerList(player, GossipOptionIds.Tailoring); + break; + default: + break; + } + } + break; + default: + break; + } + } + + void SendTrainerList(Player player, GossipOptionIds Index) + { + player.GetSession().SendTrainerList(me.GetGUID(), (uint)Index + 1); + } + } + + public override CreatureAI GetAI(Creature creature) + { + return new npc_multi_profession_trainerAI(creature); + } + } +} diff --git a/Scripts/World/NpcSpecial.cs b/Scripts/World/NpcSpecial.cs new file mode 100644 index 000000000..12dbebd7d --- /dev/null +++ b/Scripts/World/NpcSpecial.cs @@ -0,0 +1,2469 @@ +/* + * Copyright (C) 2012-2017 CypherCore + * + * 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, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +using Framework.Constants; +using Framework.GameMath; +using Game; +using Game.AI; +using Game.Entities; +using Game.Maps; +using Game.Scripting; +using Game.Spells; +using System; +using System.Collections.Generic; +using System.Linq; + +namespace Scripts.World.NpcSpecial +{ + struct NpcSpecialConst + { + public static SpawnAssociation[] spawnAssociations = + { + new SpawnAssociation(2614, 15241, SpawnType.AlarmBot), //Air Force Alarm Bot (Alliance) + new SpawnAssociation(2615, 15242, SpawnType.AlarmBot), //Air Force Alarm Bot (Horde) + new SpawnAssociation(21974, 21976, SpawnType.AlarmBot), //Air Force Alarm Bot (Area 52) + new SpawnAssociation(21993, 15242, SpawnType.AlarmBot), //Air Force Guard Post (Horde - Bat Rider) + new SpawnAssociation(21996, 15241, SpawnType.AlarmBot), //Air Force Guard Post (Alliance - Gryphon) + new SpawnAssociation(21997, 21976, SpawnType.AlarmBot), //Air Force Guard Post (Goblin - Area 52 - Zeppelin) + new SpawnAssociation(21999, 15241, SpawnType.TripwireRooftop), //Air Force Trip Wire - Rooftop (Alliance) + new SpawnAssociation(22001, 15242, SpawnType.TripwireRooftop), //Air Force Trip Wire - Rooftop (Horde) + new SpawnAssociation(22002, 15242, SpawnType.TripwireRooftop), //Air Force Trip Wire - Ground (Horde) + new SpawnAssociation(22003, 15241, SpawnType.TripwireRooftop), //Air Force Trip Wire - Ground (Alliance) + new SpawnAssociation(22063, 21976, SpawnType.TripwireRooftop), //Air Force Trip Wire - Rooftop (Goblin - Area 52) + new SpawnAssociation(22065, 22064, SpawnType.AlarmBot), //Air Force Guard Post (Ethereal - Stormspire) + new SpawnAssociation(22066, 22067, SpawnType.AlarmBot), //Air Force Guard Post (Scryer - Dragonhawk) + new SpawnAssociation(22068, 22064, SpawnType.TripwireRooftop), //Air Force Trip Wire - Rooftop (Ethereal - Stormspire) + new SpawnAssociation(22069, 22064, SpawnType.AlarmBot), //Air Force Alarm Bot (Stormspire) + new SpawnAssociation(22070, 22067, SpawnType.TripwireRooftop), //Air Force Trip Wire - Rooftop (Scryer) + new SpawnAssociation(22071, 22067, SpawnType.AlarmBot), //Air Force Alarm Bot (Scryer) + new SpawnAssociation(22078, 22077, SpawnType.AlarmBot), //Air Force Alarm Bot (Aldor) + new SpawnAssociation(22079, 22077, SpawnType.AlarmBot), //Air Force Guard Post (Aldor - Gryphon) + new SpawnAssociation(22080, 22077, SpawnType.TripwireRooftop), //Air Force Trip Wire - Rooftop (Aldor) + new SpawnAssociation(22086, 22085, SpawnType.AlarmBot), //Air Force Alarm Bot (Sporeggar) + new SpawnAssociation(22087, 22085, SpawnType.AlarmBot), //Air Force Guard Post (Sporeggar - Spore Bat) + new SpawnAssociation(22088, 22085, SpawnType.TripwireRooftop), //Air Force Trip Wire - Rooftop (Sporeggar) + new SpawnAssociation(22090, 22089, SpawnType.AlarmBot), //Air Force Guard Post (Toshley's Station - Flying Machine) + new SpawnAssociation(22124, 22122, SpawnType.AlarmBot), //Air Force Alarm Bot (Cenarion) + new SpawnAssociation(22125, 22122, SpawnType.AlarmBot), //Air Force Guard Post (Cenarion - Stormcrow) + new SpawnAssociation(22126, 22122, SpawnType.AlarmBot) //Air Force Trip Wire - Rooftop (Cenarion Expedition) + }; + + public const float RangeTripwire = 15.0f; + public const float RangeGuardsMark = 50.0f; + + //ChickenCluck + public const uint FactionFriendly = 35; + public const uint FactionChicken = 31; + + //Doctor + public static Position[] DoctorAllianceCoords = + { + new Position(-3757.38f, -4533.05f, 14.16f, 3.62f), // Top-far-right bunk as seen from entrance + new Position(-3754.36f, -4539.13f, 14.16f, 5.13f), // Top-far-left bunk + new Position(-3749.54f, -4540.25f, 14.28f, 3.34f), // Far-right bunk + new Position(-3742.10f, -4536.85f, 14.28f, 3.64f), // Right bunk near entrance + new Position(-3755.89f, -4529.07f, 14.05f, 0.57f), // Far-left bunk + new Position(-3749.51f, -4527.08f, 14.07f, 5.26f), // Mid-left bunk + new Position(-3746.37f, -4525.35f, 14.16f, 5.22f), // Left bunk near entrance + }; + + //alliance run to where + public static Position DoctorAllianceRunTo = new Position(-3742.96f, -4531.52f, 11.91f); + + public static Position[] DoctorHordeCoords = + { + new Position(-1013.75f, -3492.59f, 62.62f, 4.34f), // Left, Behind + new Position(-1017.72f, -3490.92f, 62.62f, 4.34f), // Right, Behind + new Position(-1015.77f, -3497.15f, 62.82f, 4.34f), // Left, Mid + new Position(-1019.51f, -3495.49f, 62.82f, 4.34f), // Right, Mid + new Position(-1017.25f, -3500.85f, 62.98f, 4.34f), // Left, front + new Position(-1020.95f, -3499.21f, 62.98f, 4.34f) // Right, Front + }; + + //horde run to where + public static Position DoctorHordeRunTo = new Position(-1016.44f, -3508.48f, 62.96f); + + public static uint[] AllianceSoldierId = + { + 12938, // 12938 Injured Alliance Soldier + 12936, // 12936 Badly injured Alliance Soldier + 12937 // 12937 Critically injured Alliance Soldier + }; + + public static uint[] HordeSoldierId = + { + 12923, //12923 Injured Soldier + 12924, //12924 Badly injured Soldier + 12925 //12925 Critically injured Soldier + }; + + // WormholeSpells + public const uint DataShowUnderground = 1; + + //Fireworks + public const uint AnimGoLaunchFirework = 3; + public const uint ZoneMoonglade = 493; + + public static Position omenSummonPos = new Position(7558.993f, -2839.999f, 450.0214f, 4.46f); + + public const uint AuraDurationTimeLeft = 5000; + + //Argent squire/gruntling + public static Tuple[] bannerSpells = + { + Tuple.Create(Spells.DarnassusPennant, Spells.SenjinPennant), + Tuple.Create(Spells.ExodarPennant, Spells.UndercityPennant), + Tuple.Create(Spells.GnomereganPennant, Spells.OrgrimmarPennant), + Tuple.Create(Spells.IronforgePennant, Spells.SilvermoonPennant), + Tuple.Create(Spells.StormwindPennant, Spells.ThunderbluffPennant) + }; + } + + enum SpawnType + { + TripwireRooftop, // no warning, summon Creature at smaller range + AlarmBot, // cast guards mark and summon npc - if player shows up with that buff duration < 5 seconds attack + } + + class SpawnAssociation + { + public SpawnAssociation(uint _thisCreatureEntry, uint _spawnedCreatureEntry, SpawnType _spawnType) + { + thisCreatureEntry = _thisCreatureEntry; + spawnedCreatureEntry = _spawnedCreatureEntry; + spawnType = _spawnType; + } + + public uint thisCreatureEntry; + public uint spawnedCreatureEntry; + public SpawnType spawnType; + } + + struct CreatureIds + { + //Torchtossingtarget + public const uint TorchTossingTargetBunny = 25535; + + //Garments + public const uint Shaya = 12429; + public const uint Roberts = 12423; + public const uint Dolf = 12427; + public const uint Korja = 12430; + public const uint DgKel = 12428; + + //Doctor + public const uint DoctorAlliance = 12939; + public const uint DoctorHorde = 12920; + + //Fireworks + public const uint Omen = 15467; + public const uint MinionOfOmen = 15466; + public const uint FireworkBlue = 15879; + public const uint FireworkGreen = 15880; + public const uint FireworkPurple = 15881; + public const uint FireworkRed = 15882; + public const uint FireworkYellow = 15883; + public const uint FireworkWhite = 15884; + public const uint FireworkBigBlue = 15885; + public const uint FireworkBigGreen = 15886; + public const uint FireworkBigPurple = 15887; + public const uint FireworkBigRed = 15888; + public const uint FireworkBigYellow = 15889; + public const uint FireworkBigWhite = 15890; + + public const uint ClusterBlue = 15872; + public const uint ClusterRed = 15873; + public const uint ClusterGreen = 15874; + public const uint ClusterPurple = 15875; + public const uint ClusterWhite = 15876; + public const uint ClusterYellow = 15877; + public const uint ClusterBigBlue = 15911; + public const uint ClusterBigGreen = 15912; + public const uint ClusterBigPurple = 15913; + public const uint ClusterBigRed = 15914; + public const uint ClusterBigWhite = 15915; + public const uint ClusterBigYellow = 15916; + public const uint ClusterElune = 15918; + + // Rabbitspells + public const uint SpringRabbit = 32791; + + // TrainWrecker + public const uint ExultingWindUpTrainWrecker = 81071; + + //Argent squire/gruntling + public const uint ArgentSquire = 33238; + } + + struct GameobjectIds + { + //Fireworks + public const uint FireworkLauncher1 = 180771; + public const uint FireworkLauncher2 = 180868; + public const uint FireworkLauncher3 = 180850; + public const uint ClusterLauncher1 = 180772; + public const uint ClusterLauncher2 = 180859; + public const uint ClusterLauncher3 = 180869; + public const uint ClusterLauncher4 = 180874; + + //TrainWrecker + public const uint ToyTrain = 193963; + + //RibbonPole + public const uint RibbonPole = 181605; + } + + struct Spells + { + public const uint GuardsMark = 38067; + + //Dancingflames + public const uint Brazier = 45423; + public const uint Seduction = 47057; + public const uint FieryAura = 45427; + + //RibbonPole + public const uint RibbonDanceCosmetic = 29726; + public const uint RedFireRing = 46836; + public const uint BlueFireRing = 46842; + + //Torchtossingtarget + public const uint TargetIndicator = 45723; + + //Garments + public const uint LesserHealR2 = 2052; + public const uint FortitudeR1 = 1243; + + //Guardianspells + public const uint Deathtouch = 5; + + //Sayge + public const uint Strength = 23735; // +10% Strength + public const uint Agility = 23736; // +10% Agility + public const uint Stamina = 23737; // +10% Stamina + public const uint Spirit = 23738; // +10% Spirit + public const uint Intellect = 23766; // +10% Intellect + public const uint Armor = 23767; // +10% Armor + public const uint Damage = 23768; // +10% Damage + public const uint Resistance = 23769; // +25 Magic Resistance (All) + public const uint Fortune = 23765; // Darkmoon Faire Fortune + + //Tonkmine + public const uint TonkMineDetonate = 25099; + + //Brewfestreveler + public const uint BrewfestToast = 41586; + + //Wormholespells + public const uint BoreanTundra = 67834; + public const uint SholazarBasin = 67835; + public const uint Icecrown = 67836; + public const uint StormPeaks = 67837; + public const uint HowlingFjord = 67838; + public const uint Underground = 68081; + + //Fireworks + public const uint RocketBlue = 26344; + public const uint RocketGreen = 26345; + public const uint RocketPurple = 26346; + public const uint RocketRed = 26347; + public const uint RocketWhite = 26348; + public const uint RocketYellow = 26349; + public const uint RocketBigBlue = 26351; + public const uint RocketBigGreen = 26352; + public const uint RocketBigPurple = 26353; + public const uint RocketBigRed = 26354; + public const uint RocketBigWhite = 26355; + public const uint RocketBigYellow = 26356; + public const uint LunarFortune = 26522; + + //Rabbitspells + public const uint SpringFling = 61875; + public const uint SpringRabbitJump = 61724; + public const uint SpringRabbitWander = 61726; + public const uint SummonBabyBunny = 61727; + public const uint SpringRabbitInLove = 61728; + + //TrainWrecker + public const uint ToyTrainPulse = 61551; + public const uint WreckTrain = 62943; + + //Argent squire/gruntling + public const uint DarnassusPennant = 63443; + public const uint ExodarPennant = 63439; + public const uint GnomereganPennant = 63442; + public const uint IronforgePennant = 63440; + public const uint StormwindPennant = 62727; + public const uint SenjinPennant = 63446; + public const uint UndercityPennant = 63441; + public const uint OrgrimmarPennant = 63444; + public const uint SilvermoonPennant = 63438; + public const uint ThunderbluffPennant = 63445; + public const uint AuraPostmanS = 67376; + public const uint AuraShopS = 67377; + public const uint AuraBankS = 67368; + public const uint AuraTiredS = 67401; + public const uint AuraBankG = 68849; + public const uint AuraPostmanG = 68850; + public const uint AuraShopG = 68851; + public const uint AuraTiredG = 68852; + public const uint TiredPlayer = 67334; + } + + struct QuestConst + { + //Lunaclawspirit + public const uint BodyHeartA = 6001; + public const uint BodyHeartH = 6002; + + //ChickenCluck + public const uint Cluck = 3861; + + //Garments + public const uint Moon = 5621; + public const uint Light1 = 5624; + public const uint Light2 = 5625; + public const uint Spirit = 5648; + public const uint Darkness = 5650; + } + + struct Texts + { + //Lunaclawspirit + public const uint TextIdDefault = 4714; + public const uint TextIdProgress = 4715; + + //Chickencluck + public const uint EmoteHelloA = 0; + public const uint EmoteHelloH = 1; + public const uint EmoteCluck = 2; + + //Doctor + public const uint SayDoc = 0; + + // Garments + // Used By 12429; 12423; 12427; 12430; 12428; But Signed For 12429 + public const uint SayThanks = 0; + public const uint SayGoodbye = 1; + public const uint SayHealed = 2; + + //Wormholespells + public const uint Wormhole = 14785; + + //NpcExperience + public const uint XpOnOff = 14736; + } + + struct GossipMenus + { + //Sayge + public const int OptionIdAnswer1 = 0; + public const int OptionIdAnswer2 = 1; + public const int OptionIdAnswer3 = 2; + public const int OptionIdAnswer4 = 3; + public const int IAmReadyToDiscover = 6186; + public const int OptionSayge = 6185; + public const int OptionSayge2 = 6187; + public const int OptionSayge3 = 6208; + public const int OptionSayge4 = 6209; + public const int OptionSayge5 = 6210; + public const int OptionSayge6 = 6211; + public const int IHaveLongKnown = 7339; + public const int YouHaveBeenTasked = 7340; + public const int SwornExecutioner = 7341; + public const int DiplomaticMission = 7361; + public const int YourBrotherSeeks = 7362; + public const int ATerribleBeast = 7363; + public const int YourFortuneIsCast = 7364; + public const int HereIsYourFortune = 7365; + public const int CantGiveYouYour = 7393; + + //Wormhole + public const int MenuIdWormhole = 10668; // "This tear in the fabric of time and space looks ominous." + public const int OptionIdWormhole1 = 0; // "Borean Tundra" + public const int OptionIdWormhole2 = 1; // "Howling Fjord" + public const int OptionIdWormhole3 = 2; // "Sholazar Basin" + public const int OptionIdWormhole4 = 3; // "Icecrown" + public const int OptionIdWormhole5 = 4; // "Storm Peaks" + public const int OptionIdWormhole6 = 5; // "Underground..." + + //Lunaclawspirit + public const string ItemGrant = "You Have Thought Well; Spirit. I Ask You To Grant Me The Strength Of Your Body And The Strength Of Your Heart."; + + //Pettrainer + public const uint MenuIdPetUnlearn = 6520; + public const uint OptionIdPleaseDo = 0; + + //NpcExperience + public const uint MenuIdXpOnOff = 10638; + public const uint OptionIdXpOff = 0; + public const uint OptionIdXpOn = 1; + + //Argent squire/gruntling + public const uint OptionIdBank = 0; + public const uint OptionIdShop = 1; + public const uint OptionIdMail = 2; + public const uint OptionIdDarnassusSenjinPennant = 3; + public const uint OptionIdExodarUndercityPennant = 4; + public const uint OptionIdGnomereganOrgrimmarPennant = 5; + public const uint OptionIdIronforgeSilvermoonPennant = 6; + public const uint OptionIdStormwindThunderbluffPennant = 7; + } + + [Script] + class npc_air_force_bots : CreatureScript + { + public npc_air_force_bots() : base("npc_air_force_bots") { } + + class npc_air_force_botsAI : ScriptedAI + { + public npc_air_force_botsAI(Creature creature) : base(creature) + { + SpawnAssoc = null; + SpawnedGUID.Clear(); + + // find the correct spawnhandling + foreach (var association in NpcSpecialConst.spawnAssociations) + { + if (association.thisCreatureEntry == creature.GetEntry()) + { + SpawnAssoc = association; + break; + } + } + + if (SpawnAssoc == null) + Log.outError(LogFilter.Sql, "TCSR: Creature template entry {0} has ScriptName npc_air_force_bots, but it's not handled by that script", creature.GetEntry()); + else + { + CreatureTemplate spawnedTemplate = Global.ObjectMgr.GetCreatureTemplate(SpawnAssoc.spawnedCreatureEntry); + if (spawnedTemplate == null) + { + Log.outError(LogFilter.Sql, "TCSR: Creature template entry {0} does not exist in DB, which is required by npc_air_force_bots", SpawnAssoc.spawnedCreatureEntry); + SpawnAssoc = null; + return; + } + } + } + + SpawnAssociation SpawnAssoc; + ObjectGuid SpawnedGUID; + + public override void Reset() { } + + Creature SummonGuard() + { + Creature summoned = me.SummonCreature(SpawnAssoc.spawnedCreatureEntry, 0.0f, 0.0f, 0.0f, 0.0f, TempSummonType.TimedDespawnOOC, 300000); + + if (summoned) + SpawnedGUID = summoned.GetGUID(); + else + { + Log.outError(LogFilter.Sql, "npc_air_force_bots: wasn't able to spawn Creature {0}", SpawnAssoc.spawnedCreatureEntry); + SpawnAssoc = null; + } + + return summoned; + } + + Creature GetSummonedGuard() + { + Creature creature = ObjectAccessor.GetCreature(me, SpawnedGUID); + if (creature && creature.IsAlive()) + return creature; + + return null; + } + + public override void MoveInLineOfSight(Unit who) + { + if (SpawnAssoc == null) + return; + + if (me.IsValidAttackTarget(who)) + { + Player playerTarget = who.ToPlayer(); + + // airforce guards only spawn for players + if (!playerTarget) + return; + + Creature lastSpawnedGuard = SpawnedGUID.IsEmpty() ? null : GetSummonedGuard(); + + // prevent calling Unit::GetUnit at next MoveInLineOfSight call - speedup + if (!lastSpawnedGuard) + SpawnedGUID.Clear(); + + switch (SpawnAssoc.spawnType) + { + case SpawnType.AlarmBot: + { + if (!who.IsWithinDistInMap(me, NpcSpecialConst.RangeGuardsMark)) + return; + + Aura markAura = who.GetAura(Spells.GuardsMark); + if (markAura != null) + { + // the target wasn't able to move out of our range within 25 seconds + if (!lastSpawnedGuard) + { + lastSpawnedGuard = SummonGuard(); + + if (!lastSpawnedGuard) + return; + } + + if (markAura.GetDuration() < NpcSpecialConst.AuraDurationTimeLeft) + if (!lastSpawnedGuard.GetVictim()) + lastSpawnedGuard.GetAI().AttackStart(who); + } + else + { + if (!lastSpawnedGuard) + lastSpawnedGuard = SummonGuard(); + + if (!lastSpawnedGuard) + return; + + lastSpawnedGuard.CastSpell(who, Spells.GuardsMark, true); + } + break; + } + case SpawnType.TripwireRooftop: + { + if (!who.IsWithinDistInMap(me, NpcSpecialConst.RangeTripwire)) + return; + + if (!lastSpawnedGuard) + lastSpawnedGuard = SummonGuard(); + + if (!lastSpawnedGuard) + return; + + // ROOFTOP only triggers if the player is on the ground + if (!playerTarget.IsFlying() && !lastSpawnedGuard.GetVictim()) + lastSpawnedGuard.GetAI().AttackStart(who); + + break; + } + } + } + } + } + + public override CreatureAI GetAI(Creature creature) + { + return new npc_air_force_botsAI(creature); + } + } + + [Script] + class npc_chicken_cluck : CreatureScript + { + public npc_chicken_cluck() : base("npc_chicken_cluck") { } + + class npc_chicken_cluckAI : ScriptedAI + { + public npc_chicken_cluckAI(Creature creature) : base(creature) + { + Initialize(); + } + + void Initialize() + { + ResetFlagTimer = 120000; + } + + uint ResetFlagTimer; + + public override void Reset() + { + Initialize(); + me.SetFaction(NpcSpecialConst.FactionChicken); + me.RemoveFlag(UnitFields.NpcFlags, NPCFlags.QuestGiver); + } + + public override void EnterCombat(Unit who) { } + + public override void UpdateAI(uint diff) + { + // Reset flags after a certain time has passed so that the next player has to start the 'event' again + if (me.HasFlag(UnitFields.NpcFlags, NPCFlags.QuestGiver)) + { + if (ResetFlagTimer <= diff) + { + EnterEvadeMode(); + return; + } + else + ResetFlagTimer -= diff; + } + + if (UpdateVictim()) + DoMeleeAttackIfReady(); + } + + public override void ReceiveEmote(Player player, TextEmotes emote) + { + switch (emote) + { + case TextEmotes.Chicken: + if (player.GetQuestStatus(QuestConst.Cluck) == QuestStatus.None && RandomHelper.Rand32() % 30 == 1) + { + me.SetFlag(UnitFields.NpcFlags, NPCFlags.QuestGiver); + me.SetFaction(NpcSpecialConst.FactionFriendly); + Talk(player.GetTeam() == Team.Horde ? Texts.EmoteHelloH : Texts.EmoteHelloA); + } + break; + case TextEmotes.Cheer: + if (player.GetQuestStatus(QuestConst.Cluck) == QuestStatus.Complete) + { + me.SetFlag(UnitFields.NpcFlags, NPCFlags.QuestGiver); + me.SetFaction(NpcSpecialConst.FactionFriendly); + Talk(Texts.EmoteCluck); + } + break; + } + } + } + + public override CreatureAI GetAI(Creature creature) + { + return new npc_chicken_cluckAI(creature); + } + + public override bool OnQuestAccept(Player player, Creature creature, Quest quest) + { + if (quest.Id == QuestConst.Cluck) + ((npc_chicken_cluck.npc_chicken_cluckAI)creature.GetAI()).Reset(); + + return true; + } + + public override bool OnQuestReward(Player player, Creature creature, Quest quest, uint opt) + { + if (quest.Id == QuestConst.Cluck) + ((npc_chicken_cluck.npc_chicken_cluckAI)creature.GetAI()).Reset(); + + return true; + } + } + + [Script] + class npc_dancing_flames : CreatureScript + { + public npc_dancing_flames() : base("npc_dancing_flames") { } + + class npc_dancing_flamesAI : ScriptedAI + { + public npc_dancing_flamesAI(Creature creature) : base(creature) + { + Initialize(); + } + + void Initialize() + { + Active = true; + CanIteract = 3500; + } + + bool Active; + uint CanIteract; + + public override void Reset() + { + Initialize(); + DoCast(me, Spells.Brazier, true); + DoCast(me, Spells.FieryAura, false); + float x, y, z; + me.GetPosition(out x, out y, out z); + me.Relocate(x, y, z + 0.94f); + me.SetDisableGravity(true); + me.HandleEmoteCommand(Emote.OneshotDance); + } + + public override void UpdateAI(uint diff) + { + if (!Active) + { + if (CanIteract <= diff) + { + Active = true; + CanIteract = 3500; + me.HandleEmoteCommand(Emote.OneshotDance); + } + else + CanIteract -= diff; + } + } + + public override void EnterCombat(Unit who) { } + + public override void ReceiveEmote(Player player, TextEmotes emote) + { + if (me.IsWithinLOS(player.GetPositionX(), player.GetPositionY(), player.GetPositionZ()) && me.IsWithinDistInMap(player, 30.0f)) + { + me.SetInFront(player); + Active = false; + + switch (emote) + { + case TextEmotes.Kiss: + me.HandleEmoteCommand(Emote.OneshotShy); + break; + case TextEmotes.Wave: + me.HandleEmoteCommand(Emote.OneshotWave); + break; + case TextEmotes.Bow: + me.HandleEmoteCommand(Emote.OneshotBow); + break; + case TextEmotes.Joke: + me.HandleEmoteCommand(Emote.OneshotLaugh); + break; + case TextEmotes.Dance: + if (!player.HasAura(Spells.Seduction)) + DoCast(player, Spells.Seduction, true); + break; + } + } + } + } + + public override CreatureAI GetAI(Creature creature) + { + return new npc_dancing_flamesAI(creature); + } + } + + [Script] + class npc_torch_tossing_target_bunny_controller : CreatureScript + { + public npc_torch_tossing_target_bunny_controller() : base("npc_torch_tossing_target_bunny_controller") { } + + class npc_torch_tossing_target_bunny_controllerAI : ScriptedAI + { + public npc_torch_tossing_target_bunny_controllerAI(Creature creature) : base(creature) + { + _targetTimer = 3000; + } + + ObjectGuid DoSearchForTargets(ObjectGuid lastTargetGUID) + { + List targets = new List(); + me.GetCreatureListWithEntryInGrid(targets, CreatureIds.TorchTossingTargetBunny, 60.0f); + targets.RemoveAll(creature => { return creature.GetGUID() == lastTargetGUID; }); + + if (!targets.Empty()) + { + _lastTargetGUID = targets.PickRandom().GetGUID(); + + return _lastTargetGUID; + } + return ObjectGuid.Empty; + } + + public override void UpdateAI(uint diff) + { + if (_targetTimer < diff) + { + Unit target = Global.ObjAccessor.GetUnit(me, DoSearchForTargets(_lastTargetGUID)); + if (target) + target.CastSpell(target, Spells.TargetIndicator, true); + + _targetTimer = 3000; + } + else + _targetTimer -= diff; + } + + uint _targetTimer; + ObjectGuid _lastTargetGUID; + } + + public override CreatureAI GetAI(Creature creature) + { + return new npc_torch_tossing_target_bunny_controllerAI(creature); + } + } + + [Script] + class npc_midsummer_bunny_pole : CreatureScript + { + public npc_midsummer_bunny_pole() : base("npc_midsummer_bunny_pole") { } + + class npc_midsummer_bunny_poleAI : ScriptedAI + { + public npc_midsummer_bunny_poleAI(Creature creature) : base(creature) + { + Initialize(); + } + + void Initialize() + { + _scheduler.CancelAll(); + running = false; + } + + public override void Reset() + { + Initialize(); + + _scheduler.SetValidator(() => { return running; }); + + _scheduler.Schedule(TimeSpan.FromMilliseconds(1), task => + { + if (checkNearbyPlayers()) + { + Reset(); + return; + } + + GameObject go = me.FindNearestGameObject(GameobjectIds.RibbonPole, 10.0f); + if (go) + me.CastSpell(go, Spells.RedFireRing, true); + + task.Schedule(TimeSpan.FromSeconds(5), task1 => + { + if (checkNearbyPlayers()) + { + Reset(); + return; + } + + go = me.FindNearestGameObject(GameobjectIds.RibbonPole, 10.0f); + if (go) + me.CastSpell(go, Spells.BlueFireRing, true); + + task.Repeat(TimeSpan.FromSeconds(5)); + }); + }); + } + + public override void DoAction(int action) + { + // Don't start event if it's already running. + if (running) + return; + + running = true; + //events.ScheduleEvent(EVENT_CAST_RED_FIRE_RING, 1); + } + + bool checkNearbyPlayers() + { + // Returns true if no nearby player has aura "Test Ribbon Pole Channel". + List players = new List(); + var check = new UnitAuraCheck(true, Spells.RibbonDanceCosmetic); + var searcher = new PlayerListSearcher(me, players, check); + Cell.VisitWorldObjects(me, searcher, 10.0f); + + return players.Empty(); + } + + public override void UpdateAI(uint diff) + { + if (!running) + return; + + _scheduler.Update(diff); + } + + bool running; + } + + public override CreatureAI GetAI(Creature creature) + { + return new npc_midsummer_bunny_poleAI(creature); + } + } + + [Script] + class npc_doctor : CreatureScript + { + public npc_doctor() : base("npc_doctor") { } + + public class npc_doctorAI : ScriptedAI + { + public npc_doctorAI(Creature creature) : base(creature) + { + Initialize(); + } + + void Initialize() + { + PlayerGUID.Clear(); + + SummonPatientTimer = 10000; + SummonPatientCount = 0; + PatientDiedCount = 0; + PatientSavedCount = 0; + + Patients.Clear(); + Coordinates.Clear(); + + Event = false; + } + + public override void Reset() + { + Initialize(); + me.RemoveFlag(UnitFields.Flags, UnitFlags.NotSelectable); + } + + public void BeginEvent(Player player) + { + PlayerGUID = player.GetGUID(); + + SummonPatientTimer = 10000; + SummonPatientCount = 0; + PatientDiedCount = 0; + PatientSavedCount = 0; + + switch (me.GetEntry()) + { + case CreatureIds.DoctorAlliance: + foreach (var coord in NpcSpecialConst.DoctorAllianceCoords) + Coordinates.Add(coord); + break; + case CreatureIds.DoctorHorde: + foreach (var coord in NpcSpecialConst.DoctorHordeCoords) + Coordinates.Add(coord); + break; + } + + Event = true; + me.SetFlag(UnitFields.Flags, UnitFlags.NotSelectable); + } + + public void PatientDied(Position point) + { + Player player = Global.ObjAccessor.GetPlayer(me, PlayerGUID); + if (player && ((player.GetQuestStatus(6624) == QuestStatus.Incomplete) || (player.GetQuestStatus(6622) == QuestStatus.Incomplete))) + { + ++PatientDiedCount; + + if (PatientDiedCount > 5 && Event) + { + if (player.GetQuestStatus(6624) == QuestStatus.Incomplete) + player.FailQuest(6624); + else if (player.GetQuestStatus(6622) == QuestStatus.Incomplete) + player.FailQuest(6622); + + Reset(); + return; + } + + Coordinates.Add(point); + } + else + // If no player or player abandon quest in progress + Reset(); + } + + public void PatientSaved(Creature soldier, Player player, Position point) + { + if (player && PlayerGUID == player.GetGUID()) + { + if ((player.GetQuestStatus(6624) == QuestStatus.Incomplete) || (player.GetQuestStatus(6622) == QuestStatus.Incomplete)) + { + ++PatientSavedCount; + + if (PatientSavedCount == 15) + { + if (!Patients.Empty()) + { + foreach (var guid in Patients) + { + Creature patient = ObjectAccessor.GetCreature(me, guid); + if (patient) + patient.setDeathState(DeathState.JustDied); + } + } + + if (player.GetQuestStatus(6624) == QuestStatus.Incomplete) + player.AreaExploredOrEventHappens(6624); + else if (player.GetQuestStatus(6622) == QuestStatus.Incomplete) + player.AreaExploredOrEventHappens(6622); + + Reset(); + return; + } + + Coordinates.Add(point); + } + } + } + + public override void UpdateAI(uint diff) + { + if (Event && SummonPatientCount >= 20) + { + Reset(); + return; + } + + if (Event) + { + if (SummonPatientTimer <= diff) + { + if (Coordinates.Empty()) + return; + + uint patientEntry = 0; + + switch (me.GetEntry()) + { + case CreatureIds.DoctorAlliance: + patientEntry = NpcSpecialConst.AllianceSoldierId[RandomHelper.Rand32() % 3]; + break; + case CreatureIds.DoctorHorde: + patientEntry = NpcSpecialConst.HordeSoldierId[RandomHelper.Rand32() % 3]; + break; + default: + Log.outError(LogFilter.Scripts, "Invalid entry for Triage doctor. Please check your database"); + return; + } + + var index = RandomHelper.IRand(0, Coordinates.Count - 1); + + Creature Patient = me.SummonCreature(patientEntry, Coordinates[index], TempSummonType.TimedDespawnOOC, 5000); + if (Patient) + { + //303, this flag appear to be required for client side item.spell to work (TARGET_SINGLE_FRIEND) + Patient.SetFlag(UnitFields.Flags, UnitFlags.PvpAttackable); + + Patients.Add(Patient.GetGUID()); + ((npc_injured_patient.npc_injured_patientAI)Patient.GetAI()).DoctorGUID = me.GetGUID(); + ((npc_injured_patient.npc_injured_patientAI)Patient.GetAI()).Coord = Coordinates[index]; + + Coordinates.RemoveAt(index); + } + + SummonPatientTimer = 10000; + ++SummonPatientCount; + } + else + SummonPatientTimer -= diff; + } + } + + public override void EnterCombat(Unit who) { } + + ObjectGuid PlayerGUID; + + uint SummonPatientTimer; + uint SummonPatientCount; + uint PatientDiedCount; + uint PatientSavedCount; + + bool Event; + + List Patients = new List(); + List Coordinates = new List(); + } + + public override bool OnQuestAccept(Player player, Creature creature, Quest quest) + { + if ((quest.Id == 6624) || (quest.Id == 6622)) + ((npc_doctorAI)creature.GetAI()).BeginEvent(player); + + return true; + } + + public override CreatureAI GetAI(Creature creature) + { + return new npc_doctorAI(creature); + } + } + + [Script] + class npc_injured_patient : CreatureScript + { + public npc_injured_patient() : base("npc_injured_patient") { } + + public class npc_injured_patientAI : ScriptedAI + { + public npc_injured_patientAI(Creature creature) : base(creature) + { + Initialize(); + } + + void Initialize() + { + DoctorGUID.Clear(); + Coord = null; + } + + public ObjectGuid DoctorGUID; + public Position Coord; + + public override void Reset() + { + Initialize(); + + //no select + me.RemoveFlag(UnitFields.Flags, UnitFlags.NotSelectable); + + //no regen health + me.SetFlag(UnitFields.Flags, UnitFlags.InCombat); + + //to make them lay with face down + me.SetUInt32Value(UnitFields.Bytes1, (uint)UnitStandStateType.Dead); + + uint mobId = me.GetEntry(); + + switch (mobId) + { //lower max health + case 12923: + case 12938: //Injured Soldier + me.SetHealth(me.CountPctFromMaxHealth(75)); + break; + case 12924: + case 12936: //Badly injured Soldier + me.SetHealth(me.CountPctFromMaxHealth(50)); + break; + case 12925: + case 12937: //Critically injured Soldier + me.SetHealth(me.CountPctFromMaxHealth(25)); + break; + } + } + + public override void EnterCombat(Unit who) { } + + public override void SpellHit(Unit caster, SpellInfo spell) + { + Player player = caster.ToPlayer(); + if (!player || !me.IsAlive() || spell.Id != 20804) + return; + + if (player.GetQuestStatus(6624) == QuestStatus.Incomplete || player.GetQuestStatus(6622) == QuestStatus.Incomplete) + { + if (!DoctorGUID.IsEmpty()) + { + Creature doctor = ObjectAccessor.GetCreature(me, DoctorGUID); + if (doctor) + ((npc_doctor.npc_doctorAI)doctor.GetAI()).PatientSaved(me, player, Coord); + } + } + + //make not selectable + me.SetFlag(UnitFields.Flags, UnitFlags.NotSelectable); + + //regen health + me.RemoveFlag(UnitFields.Flags, UnitFlags.InCombat); + + //stand up + me.SetUInt32Value(UnitFields.Bytes1, (uint)UnitStandStateType.Stand); + + Talk(Texts.SayDoc); + + uint mobId = me.GetEntry(); + me.SetWalk(false); + + switch (mobId) + { + case 12923: + case 12924: + case 12925: + me.GetMotionMaster().MovePoint(0, NpcSpecialConst.DoctorHordeRunTo); + break; + case 12936: + case 12937: + case 12938: + me.GetMotionMaster().MovePoint(0, NpcSpecialConst.DoctorAllianceRunTo); + break; + } + } + + public override void UpdateAI(uint diff) + { + //lower HP on every world tick makes it a useful counter, not officlone though + if (me.IsAlive() && me.GetHealth() > 6) + me.ModifyHealth(-5); + + if (me.IsAlive() && me.GetHealth() <= 6) + { + me.RemoveFlag(UnitFields.Flags, UnitFlags.InCombat); + me.SetFlag(UnitFields.Flags, UnitFlags.NotSelectable); + me.setDeathState(DeathState.JustDied); + me.SetFlag(ObjectFields.DynamicFlags, 32); + + if (!DoctorGUID.IsEmpty()) + { + Creature doctor = ObjectAccessor.GetCreature((me), DoctorGUID); + if (doctor) + ((npc_doctor.npc_doctorAI)doctor.GetAI()).PatientDied(Coord); + } + } + } + } + + public override CreatureAI GetAI(Creature creature) + { + return new npc_injured_patientAI(creature); + } + } + + [Script] + class npc_garments_of_quests : CreatureScript + { + public npc_garments_of_quests() : base("npc_garments_of_quests") { } + + class npc_garments_of_questsAI : npc_escortAI + { + public npc_garments_of_questsAI(Creature creature) : base(creature) + { + switch (me.GetEntry()) + { + case CreatureIds.Shaya: + quest = QuestConst.Moon; + break; + case CreatureIds.Roberts: + quest = QuestConst.Light1; + break; + case CreatureIds.Dolf: + quest = QuestConst.Light2; + break; + case CreatureIds.Korja: + quest = QuestConst.Spirit; + break; + case CreatureIds.DgKel: + quest = QuestConst.Darkness; + break; + default: + quest = 0; + break; + } + + Reset(); + } + + ObjectGuid CasterGUID; + + bool IsHealed; + bool CanRun; + + uint RunAwayTimer; + uint quest; + + public override void Reset() + { + CasterGUID.Clear(); + + IsHealed = false; + CanRun = false; + + RunAwayTimer = 5000; + + me.SetStandState(UnitStandStateType.Kneel); + // expect database to have RegenHealth=0 + me.SetHealth(me.CountPctFromMaxHealth(70)); + } + + public override void EnterCombat(Unit who) { } + + public override void SpellHit(Unit caster, SpellInfo spell) + { + if (spell.Id == Spells.LesserHealR2 || spell.Id == Spells.FortitudeR1) + { + //not while in combat + if (me.IsInCombat()) + return; + + //nothing to be done now + if (IsHealed && CanRun) + return; + + Player player = caster.ToPlayer(); + if (player) + { + if (quest != 0 && player.GetQuestStatus(quest) == QuestStatus.Incomplete) + { + if (IsHealed && !CanRun && spell.Id == Spells.FortitudeR1) + { + Talk(Texts.SayThanks, caster); + CanRun = true; + } + else if (!IsHealed && spell.Id == Spells.LesserHealR2) + { + CasterGUID = caster.GetGUID(); + me.SetStandState(UnitStandStateType.Stand); + Talk(Texts.SayHealed, caster); + IsHealed = true; + } + } + + // give quest credit, not expect any special quest objectives + if (CanRun) + player.TalkedToCreature(me.GetEntry(), me.GetGUID()); + } + } + } + + public override void WaypointReached(uint waypointId) + { + + } + + public override void UpdateAI(uint diff) + { + if (CanRun && !me.IsInCombat()) + { + if (RunAwayTimer <= diff) + { + Unit unit = Global.ObjAccessor.GetUnit(me, CasterGUID); + if (unit) + { + switch (me.GetEntry()) + { + case CreatureIds.Shaya: + case CreatureIds.Roberts: + case CreatureIds.Dolf: + case CreatureIds.Korja: + case CreatureIds.DgKel: + Talk(Texts.SayGoodbye, unit); + break; + } + + Start(false, true); + } + else + EnterEvadeMode(); //something went wrong + + RunAwayTimer = 30000; + } + else + RunAwayTimer -= diff; + } + + base.UpdateAI(diff); + } + } + + public override CreatureAI GetAI(Creature creature) + { + return new npc_garments_of_questsAI(creature); + } + } + + [Script] + class npc_guardian : CreatureScript + { + public npc_guardian() : base("npc_guardian") { } + + class npc_guardianAI : ScriptedAI + { + public npc_guardianAI(Creature creature) : base(creature) { } + + public override void Reset() + { + me.SetFlag(UnitFields.Flags, UnitFlags.NonAttackable); + } + + public override void EnterCombat(Unit who) + { + } + + public override void UpdateAI(uint diff) + { + if (!UpdateVictim()) + return; + + if (me.isAttackReady()) + { + DoCastVictim(Spells.Deathtouch, true); + me.resetAttackTimer(); + } + } + } + + public override CreatureAI GetAI(Creature creature) + { + return new npc_guardianAI(creature); + } + } + + [Script] + class npc_sayge : CreatureScript + { + public npc_sayge() : base("npc_sayge") { } + + public override bool OnGossipHello(Player player, Creature creature) + { + if (creature.IsQuestGiver()) + player.PrepareQuestMenu(creature.GetGUID()); + + if (player.GetSpellHistory().HasCooldown(Spells.Strength) || + player.GetSpellHistory().HasCooldown(Spells.Agility) || + player.GetSpellHistory().HasCooldown(Spells.Stamina) || + player.GetSpellHistory().HasCooldown(Spells.Spirit) || + player.GetSpellHistory().HasCooldown(Spells.Intellect) || + player.GetSpellHistory().HasCooldown(Spells.Armor) || + player.GetSpellHistory().HasCooldown(Spells.Damage) || + player.GetSpellHistory().HasCooldown(Spells.Resistance)) + player.SEND_GOSSIP_MENU(GossipMenus.CantGiveYouYour, creature.GetGUID()); + else + { + player.ADD_GOSSIP_ITEM_DB(GossipMenus.IAmReadyToDiscover, GossipMenus.OptionIdAnswer1, eTradeskill.GossipSenderMain, eTradeskill.GossipActionInfoDef + 1); + player.SEND_GOSSIP_MENU(GossipMenus.IHaveLongKnown, creature.GetGUID()); + } + + return true; + } + + void SendAction(Player player, Creature creature, uint action) + { + switch (action) + { + case eTradeskill.GossipActionInfoDef + 1: + player.ADD_GOSSIP_ITEM_DB(GossipMenus.OptionSayge, GossipMenus.OptionIdAnswer1, eTradeskill.GossipSenderMain, eTradeskill.GossipActionInfoDef + 2); + player.ADD_GOSSIP_ITEM_DB(GossipMenus.OptionSayge, GossipMenus.OptionIdAnswer2, eTradeskill.GossipSenderMain, eTradeskill.GossipActionInfoDef + 3); + player.ADD_GOSSIP_ITEM_DB(GossipMenus.OptionSayge, GossipMenus.OptionIdAnswer3, eTradeskill.GossipSenderMain, eTradeskill.GossipActionInfoDef + 4); + player.ADD_GOSSIP_ITEM_DB(GossipMenus.OptionSayge, GossipMenus.OptionIdAnswer4, eTradeskill.GossipSenderMain, eTradeskill.GossipActionInfoDef + 5); + player.SEND_GOSSIP_MENU(GossipMenus.YouHaveBeenTasked, creature.GetGUID()); + break; + case eTradeskill.GossipActionInfoDef + 2: + player.ADD_GOSSIP_ITEM_DB(GossipMenus.OptionSayge2, GossipMenus.OptionIdAnswer1, eTradeskill.GossipSenderMain + 1, eTradeskill.GossipActionInfoDef); + player.ADD_GOSSIP_ITEM_DB(GossipMenus.OptionSayge2, GossipMenus.OptionIdAnswer2, eTradeskill.GossipSenderMain + 2, eTradeskill.GossipActionInfoDef); + player.ADD_GOSSIP_ITEM_DB(GossipMenus.OptionSayge2, GossipMenus.OptionIdAnswer3, eTradeskill.GossipSenderMain + 3, eTradeskill.GossipActionInfoDef); + player.SEND_GOSSIP_MENU(GossipMenus.SwornExecutioner, creature.GetGUID()); + break; + case eTradeskill.GossipActionInfoDef + 3: + player.ADD_GOSSIP_ITEM_DB(GossipMenus.OptionSayge3, GossipMenus.OptionIdAnswer1, eTradeskill.GossipSenderMain + 4, eTradeskill.GossipActionInfoDef); + player.ADD_GOSSIP_ITEM_DB(GossipMenus.OptionSayge3, GossipMenus.OptionIdAnswer2, eTradeskill.GossipSenderMain + 5, eTradeskill.GossipActionInfoDef); + player.ADD_GOSSIP_ITEM_DB(GossipMenus.OptionSayge3, GossipMenus.OptionIdAnswer3, eTradeskill.GossipSenderMain + 2, eTradeskill.GossipActionInfoDef); + player.SEND_GOSSIP_MENU(GossipMenus.DiplomaticMission, creature.GetGUID()); + break; + case eTradeskill.GossipActionInfoDef + 4: + player.ADD_GOSSIP_ITEM_DB(GossipMenus.OptionSayge4, GossipMenus.OptionIdAnswer1, eTradeskill.GossipSenderMain + 6, eTradeskill.GossipActionInfoDef); + player.ADD_GOSSIP_ITEM_DB(GossipMenus.OptionSayge4, GossipMenus.OptionIdAnswer2, eTradeskill.GossipSenderMain + 7, eTradeskill.GossipActionInfoDef); + player.ADD_GOSSIP_ITEM_DB(GossipMenus.OptionSayge4, GossipMenus.OptionIdAnswer3, eTradeskill.GossipSenderMain + 8, eTradeskill.GossipActionInfoDef); + player.SEND_GOSSIP_MENU(GossipMenus.YourBrotherSeeks, creature.GetGUID()); + break; + case eTradeskill.GossipActionInfoDef + 5: + player.ADD_GOSSIP_ITEM_DB(GossipMenus.OptionSayge5, GossipMenus.OptionIdAnswer1, eTradeskill.GossipSenderMain + 5, eTradeskill.GossipActionInfoDef); + player.ADD_GOSSIP_ITEM_DB(GossipMenus.OptionSayge5, GossipMenus.OptionIdAnswer2, eTradeskill.GossipSenderMain + 4, eTradeskill.GossipActionInfoDef); + player.ADD_GOSSIP_ITEM_DB(GossipMenus.OptionSayge5, GossipMenus.OptionIdAnswer3, eTradeskill.GossipSenderMain + 3, eTradeskill.GossipActionInfoDef); + player.SEND_GOSSIP_MENU(GossipMenus.ATerribleBeast, creature.GetGUID()); + break; + case eTradeskill.GossipActionInfoDef: + player.ADD_GOSSIP_ITEM_DB(GossipMenus.OptionSayge6, GossipMenus.OptionIdAnswer1, eTradeskill.GossipSenderMain, eTradeskill.GossipActionInfoDef + 6); + player.SEND_GOSSIP_MENU(GossipMenus.YourFortuneIsCast, creature.GetGUID()); + break; + case eTradeskill.GossipActionInfoDef + 6: + creature.CastSpell(player, Spells.Fortune, false); + player.SEND_GOSSIP_MENU(GossipMenus.HereIsYourFortune, creature.GetGUID()); + break; + } + } + + public override bool OnGossipSelect(Player player, Creature creature, uint sender, uint action) + { + player.PlayerTalkClass.ClearMenus(); + uint spellId = 0; + switch (sender) + { + case eTradeskill.GossipSenderMain: + SendAction(player, creature, action); + break; + case eTradeskill.GossipSenderMain + 1: + spellId = Spells.Damage; + break; + case eTradeskill.GossipSenderMain + 2: + spellId = Spells.Resistance; + break; + case eTradeskill.GossipSenderMain + 3: + spellId = Spells.Armor; + break; + case eTradeskill.GossipSenderMain + 4: + spellId = Spells.Spirit; + break; + case eTradeskill.GossipSenderMain + 5: + spellId = Spells.Intellect; + break; + case eTradeskill.GossipSenderMain + 6: + spellId = Spells.Stamina; + break; + case eTradeskill.GossipSenderMain + 7: + spellId = Spells.Strength; + break; + case eTradeskill.GossipSenderMain + 8: + spellId = Spells.Agility; + break; + } + + if (spellId != 0) + { + creature.CastSpell(player, spellId, false); + player.GetSpellHistory().AddCooldown(spellId, 0, TimeSpan.FromHours(2)); + SendAction(player, creature, action); + } + return true; + } + } + + [Script] + class npc_steam_tonk : CreatureScript + { + public npc_steam_tonk() : base("npc_steam_tonk") { } + + class npc_steam_tonkAI : ScriptedAI + { + public npc_steam_tonkAI(Creature creature) : base(creature) { } + + public override void Reset() { } + public override void EnterCombat(Unit who) { } + + public override void OnPossess(bool apply) + { + if (apply) + { + // Initialize the action bar without the melee attack command + me.InitCharmInfo(); + me.GetCharmInfo().InitEmptyActionBar(false); + + me.SetReactState(ReactStates.Passive); + } + else + me.SetReactState(ReactStates.Aggressive); + } + } + + public override CreatureAI GetAI(Creature creature) + { + return new npc_steam_tonkAI(creature); + } + } + + [Script] + class npc_tonk_mine : CreatureScript + { + public npc_tonk_mine() : base("npc_tonk_mine") { } + + class npc_tonk_mineAI : ScriptedAI + { + public npc_tonk_mineAI(Creature creature) : base(creature) + { + Initialize(); + me.SetReactState(ReactStates.Passive); + } + + void Initialize() + { + ExplosionTimer = 3000; + } + + uint ExplosionTimer; + + public override void Reset() + { + Initialize(); + } + + public override void EnterCombat(Unit who) { } + public override void AttackStart(Unit who) { } + public override void MoveInLineOfSight(Unit who) { } + + public override void UpdateAI(uint diff) + { + if (ExplosionTimer <= diff) + { + DoCast(me, Spells.TonkMineDetonate, true); + me.setDeathState(DeathState.Dead); // unsummon it + } + else + ExplosionTimer -= diff; + } + } + + public override CreatureAI GetAI(Creature creature) + { + return new npc_tonk_mineAI(creature); + } + } + + [Script] + class npc_brewfest_reveler : CreatureScript + { + public npc_brewfest_reveler() : base("npc_brewfest_reveler") { } + + class npc_brewfest_revelerAI : ScriptedAI + { + public npc_brewfest_revelerAI(Creature creature) : base(creature) { } + + public override void ReceiveEmote(Player player, TextEmotes emote) + { + if (!Global.GameEventMgr.IsHolidayActive(HolidayIds.Brewfest)) + return; + + if (emote == TextEmotes.Dance) + me.CastSpell(player, Spells.BrewfestToast, false); + } + } + + public override CreatureAI GetAI(Creature creature) + { + return new npc_brewfest_revelerAI(creature); + } + } + + [Script] + class npc_training_dummy : CreatureScript + { + public npc_training_dummy() : base("npc_training_dummy") { } + + class npc_training_dummyAI : ScriptedAI + { + public npc_training_dummyAI(Creature creature) : base(creature) + { + SetCombatMovement(false); + } + + public override void Reset() + { + // TODO: solve this in a different way! setting them as stunned prevents dummies from parrying + me.SetControlled(true, UnitState.Stunned);//disable rotate + + _events.Reset(); + _damageTimes.Clear(); + if (me.GetEntry() != AdvancedTargetDummy && me.GetEntry() != TargetDummy) + _events.ScheduleEvent(EventCheckCombat, 1000); + else + _events.ScheduleEvent(EventDespawn, 15000); + } + + public override void EnterEvadeMode(EvadeReason why) + { + if (!_EnterEvadeMode(why)) + return; + + Reset(); + } + + public override void DamageTaken(Unit doneBy, ref uint damage) + { + me.AddThreat(doneBy, damage); // just to create threat reference + _damageTimes[doneBy.GetGUID()] = Time.UnixTime; + damage = 0; + } + + public override void UpdateAI(uint diff) + { + if (!me.IsInCombat()) + return; + + if (!me.HasUnitState(UnitState.Stunned)) + me.SetControlled(true, UnitState.Stunned);//disable rotate + + _events.Update(diff); + + _events.ExecuteEvents(eventId => + { + switch (eventId) + { + case EventCheckCombat: + long now = Time.UnixTime; + foreach (var pair in _damageTimes.ToList()) + { + // If unit has not dealt damage to training dummy for 5 seconds, remove him from combat + if (pair.Value < now - 5) + { + Unit unit = Global.ObjAccessor.GetUnit(me, pair.Key); + if (unit) + unit.getHostileRefManager().deleteReference(me); + + _damageTimes.Remove(pair.Key); + } + } + _events.ScheduleEvent(EventCheckCombat, 1000); + break; + case EventDespawn: + me.DespawnOrUnsummon(); + break; + } + }); + } + + public override void MoveInLineOfSight(Unit who) { } + + Dictionary _damageTimes = new Dictionary(); + + const int EventCheckCombat = 1; + const int EventDespawn = 2; + const uint AdvancedTargetDummy = 2674; + const uint TargetDummy = 2673; + } + + public override CreatureAI GetAI(Creature creature) + { + return new npc_training_dummyAI(creature); + } + } + + [Script] + class npc_wormhole : CreatureScript + { + public npc_wormhole() : base("npc_wormhole") { } + + class npc_wormholeAI : PassiveAI + { + public npc_wormholeAI(Creature creature) : base(creature) + { + Initialize(); + } + + void Initialize() + { + _showUnderground = RandomHelper.URand(0, 100) == 0; // Guessed value, it is really rare though + } + + public override void InitializeAI() + { + Initialize(); + } + + public override uint GetData(uint type) + { + return (type == NpcSpecialConst.DataShowUnderground && _showUnderground) ? 1 : 0u; + } + + bool _showUnderground; + } + + public override bool OnGossipHello(Player player, Creature creature) + { + if (creature.IsSummon()) + { + if (player == creature.ToTempSummon().GetSummoner()) + { + player.ADD_GOSSIP_ITEM_DB(GossipMenus.MenuIdWormhole, GossipMenus.OptionIdWormhole1, eTradeskill.GossipSenderMain, eTradeskill.GossipActionInfoDef + 1); + player.ADD_GOSSIP_ITEM_DB(GossipMenus.MenuIdWormhole, GossipMenus.OptionIdWormhole2, eTradeskill.GossipSenderMain, eTradeskill.GossipActionInfoDef + 2); + player.ADD_GOSSIP_ITEM_DB(GossipMenus.MenuIdWormhole, GossipMenus.OptionIdWormhole3, eTradeskill.GossipSenderMain, eTradeskill.GossipActionInfoDef + 3); + player.ADD_GOSSIP_ITEM_DB(GossipMenus.MenuIdWormhole, GossipMenus.OptionIdWormhole4, eTradeskill.GossipSenderMain, eTradeskill.GossipActionInfoDef + 4); + player.ADD_GOSSIP_ITEM_DB(GossipMenus.MenuIdWormhole, GossipMenus.OptionIdWormhole5, eTradeskill.GossipSenderMain, eTradeskill.GossipActionInfoDef + 5); + + if (creature.GetAI().GetData(NpcSpecialConst.DataShowUnderground) != 0) + player.ADD_GOSSIP_ITEM_DB(GossipMenus.MenuIdWormhole, GossipMenus.OptionIdWormhole6, eTradeskill.GossipSenderMain, eTradeskill.GossipActionInfoDef + 6); + + player.SEND_GOSSIP_MENU(Texts.Wormhole, creature.GetGUID()); + } + } + + return true; + } + + public override bool OnGossipSelect(Player player, Creature creature, uint sender, uint action) + { + player.PlayerTalkClass.ClearMenus(); + + switch (action) + { + case eTradeskill.GossipActionInfoDef + 1: // Borean Tundra + player.CLOSE_GOSSIP_MENU(); + creature.CastSpell(player, Spells.BoreanTundra, false); + break; + case eTradeskill.GossipActionInfoDef + 2: // Howling Fjord + player.CLOSE_GOSSIP_MENU(); + creature.CastSpell(player, Spells.HowlingFjord, false); + break; + case eTradeskill.GossipActionInfoDef + 3: // Sholazar Basin + player.CLOSE_GOSSIP_MENU(); + creature.CastSpell(player, Spells.SholazarBasin, false); + break; + case eTradeskill.GossipActionInfoDef + 4: // Icecrown + player.CLOSE_GOSSIP_MENU(); + creature.CastSpell(player, Spells.Icecrown, false); + break; + case eTradeskill.GossipActionInfoDef + 5: // Storm peaks + player.CLOSE_GOSSIP_MENU(); + creature.CastSpell(player, Spells.StormPeaks, false); + break; + case eTradeskill.GossipActionInfoDef + 6: // Underground + player.CLOSE_GOSSIP_MENU(); + creature.CastSpell(player, Spells.Underground, false); + break; + } + + return true; + } + + public override CreatureAI GetAI(Creature creature) + { + return new npc_wormholeAI(creature); + } + } + + [Script] + class npc_pet_trainer : CreatureScript + { + public npc_pet_trainer() : base("npc_pet_trainer") { } + + class npc_pet_trainerAI : ScriptedAI + { + public npc_pet_trainerAI(Creature creature) : base(creature) { } + + public override void sGossipSelect(Player player, uint menuId, uint gossipListId) + { + if (menuId == GossipMenus.MenuIdPetUnlearn && gossipListId == GossipMenus.OptionIdPleaseDo) + { + player.ResetPetTalents(); + player.CLOSE_GOSSIP_MENU(); + } + } + } + + public override CreatureAI GetAI(Creature creature) + { + return new npc_pet_trainerAI(creature); + } + } + + [Script] + class npc_experience : CreatureScript + { + public npc_experience() : base("npc_experience") { } + + public override bool OnGossipHello(Player player, Creature creature) + { + if (player.HasFlag(PlayerFields.Flags, PlayerFlags.NoXPGain)) // not gaining XP + { + player.ADD_GOSSIP_ITEM_DB(GossipMenus.MenuIdXpOnOff, GossipMenus.OptionIdXpOn, eTradeskill.GossipSenderMain, eTradeskill.GossipActionInfoDef + 1); + player.SEND_GOSSIP_MENU(Texts.XpOnOff, creature.GetGUID()); + } + else // currently gaining XP + { + player.ADD_GOSSIP_ITEM_DB(GossipMenus.MenuIdXpOnOff, GossipMenus.OptionIdXpOff, eTradeskill.GossipSenderMain, eTradeskill.GossipActionInfoDef + 2); + player.SEND_GOSSIP_MENU(Texts.XpOnOff, creature.GetGUID()); + } + return true; + } + + public override bool OnGossipSelect(Player player, Creature Creature, uint sender, uint action) + { + player.PlayerTalkClass.ClearMenus(); + + switch (action) + { + case eTradeskill.GossipActionInfoDef + 1:// XP ON selected + player.RemoveFlag(PlayerFields.Flags, PlayerFlags.NoXPGain); // turn on XP gain + break; + case eTradeskill.GossipActionInfoDef + 2:// XP OFF selected + player.SetFlag(PlayerFields.Flags, PlayerFlags.NoXPGain); // turn off XP gain + break; + } + + player.PlayerTalkClass.SendCloseGossip(); + return true; + } + } + + [Script] + class npc_firework : CreatureScript + { + public npc_firework() : base("npc_firework") { } + + class npc_fireworkAI : ScriptedAI + { + public npc_fireworkAI(Creature creature) : base(creature) { } + + bool isCluster() + { + switch (me.GetEntry()) + { + case CreatureIds.FireworkBlue: + case CreatureIds.FireworkGreen: + case CreatureIds.FireworkPurple: + case CreatureIds.FireworkRed: + case CreatureIds.FireworkYellow: + case CreatureIds.FireworkWhite: + case CreatureIds.FireworkBigBlue: + case CreatureIds.FireworkBigGreen: + case CreatureIds.FireworkBigPurple: + case CreatureIds.FireworkBigRed: + case CreatureIds.FireworkBigYellow: + case CreatureIds.FireworkBigWhite: + return false; + case CreatureIds.ClusterBlue: + case CreatureIds.ClusterGreen: + case CreatureIds.ClusterPurple: + case CreatureIds.ClusterRed: + case CreatureIds.ClusterYellow: + case CreatureIds.ClusterWhite: + case CreatureIds.ClusterBigBlue: + case CreatureIds.ClusterBigGreen: + case CreatureIds.ClusterBigPurple: + case CreatureIds.ClusterBigRed: + case CreatureIds.ClusterBigYellow: + case CreatureIds.ClusterBigWhite: + case CreatureIds.ClusterElune: + default: + return true; + } + } + + GameObject FindNearestLauncher() + { + GameObject launcher = null; + + if (isCluster()) + { + GameObject launcher1 = GetClosestGameObjectWithEntry(me, GameobjectIds.ClusterLauncher1, 0.5f); + GameObject launcher2 = GetClosestGameObjectWithEntry(me, GameobjectIds.ClusterLauncher2, 0.5f); + GameObject launcher3 = GetClosestGameObjectWithEntry(me, GameobjectIds.ClusterLauncher3, 0.5f); + GameObject launcher4 = GetClosestGameObjectWithEntry(me, GameobjectIds.ClusterLauncher4, 0.5f); + + if (launcher1) + launcher = launcher1; + else if (launcher2) + launcher = launcher2; + else if (launcher3) + launcher = launcher3; + else if (launcher4) + launcher = launcher4; + } + else + { + GameObject launcher1 = GetClosestGameObjectWithEntry(me, GameobjectIds.FireworkLauncher1, 0.5f); + GameObject launcher2 = GetClosestGameObjectWithEntry(me, GameobjectIds.FireworkLauncher2, 0.5f); + GameObject launcher3 = GetClosestGameObjectWithEntry(me, GameobjectIds.FireworkLauncher3, 0.5f); + + if (launcher1) + launcher = launcher1; + else if (launcher2) + launcher = launcher2; + else if (launcher3) + launcher = launcher3; + } + + return launcher; + } + + uint GetFireworkSpell(uint entry) + { + switch (entry) + { + case CreatureIds.FireworkBlue: + return Spells.RocketBlue; + case CreatureIds.FireworkGreen: + return Spells.RocketGreen; + case CreatureIds.FireworkPurple: + return Spells.RocketPurple; + case CreatureIds.FireworkRed: + return Spells.RocketRed; + case CreatureIds.FireworkYellow: + return Spells.RocketYellow; + case CreatureIds.FireworkWhite: + return Spells.RocketWhite; + case CreatureIds.FireworkBigBlue: + return Spells.RocketBigBlue; + case CreatureIds.FireworkBigGreen: + return Spells.RocketBigGreen; + case CreatureIds.FireworkBigPurple: + return Spells.RocketBigPurple; + case CreatureIds.FireworkBigRed: + return Spells.RocketBigRed; + case CreatureIds.FireworkBigYellow: + return Spells.RocketBigYellow; + case CreatureIds.FireworkBigWhite: + return Spells.RocketBigWhite; + default: + return 0; + } + } + + uint GetFireworkGameObjectId() + { + uint spellId = 0; + + switch (me.GetEntry()) + { + case CreatureIds.ClusterBlue: + spellId = GetFireworkSpell(CreatureIds.FireworkBlue); + break; + case CreatureIds.ClusterGreen: + spellId = GetFireworkSpell(CreatureIds.FireworkGreen); + break; + case CreatureIds.ClusterPurple: + spellId = GetFireworkSpell(CreatureIds.FireworkPurple); + break; + case CreatureIds.ClusterRed: + spellId = GetFireworkSpell(CreatureIds.FireworkRed); + break; + case CreatureIds.ClusterYellow: + spellId = GetFireworkSpell(CreatureIds.FireworkYellow); + break; + case CreatureIds.ClusterWhite: + spellId = GetFireworkSpell(CreatureIds.FireworkWhite); + break; + case CreatureIds.ClusterBigBlue: + spellId = GetFireworkSpell(CreatureIds.FireworkBigBlue); + break; + case CreatureIds.ClusterBigGreen: + spellId = GetFireworkSpell(CreatureIds.FireworkBigGreen); + break; + case CreatureIds.ClusterBigPurple: + spellId = GetFireworkSpell(CreatureIds.FireworkBigPurple); + break; + case CreatureIds.ClusterBigRed: + spellId = GetFireworkSpell(CreatureIds.FireworkBigRed); + break; + case CreatureIds.ClusterBigYellow: + spellId = GetFireworkSpell(CreatureIds.FireworkBigYellow); + break; + case CreatureIds.ClusterBigWhite: + spellId = GetFireworkSpell(CreatureIds.FireworkBigWhite); + break; + case CreatureIds.ClusterElune: + spellId = GetFireworkSpell(RandomHelper.URand(CreatureIds.FireworkBlue, CreatureIds.FireworkWhite)); + break; + } + + SpellInfo spellInfo = Global.SpellMgr.GetSpellInfo(spellId); + if (spellInfo != null && spellInfo.GetEffect(0).Effect == SpellEffectName.SummonObjectWild) + return (uint)spellInfo.GetEffect(0).MiscValue; + + return 0; + } + + public override void Reset() + { + GameObject launcher = FindNearestLauncher(); + if (launcher) + { + launcher.SendCustomAnim(NpcSpecialConst.AnimGoLaunchFirework); + me.SetOrientation(launcher.GetOrientation() + MathFunctions.PI / 2); + } + else + return; + + if (isCluster()) + { + // Check if we are near Elune'ara lake south, if so try to summon Omen or a minion + if (me.GetZoneId() == NpcSpecialConst.ZoneMoonglade) + { + if (!me.FindNearestCreature(CreatureIds.Omen, 100.0f) && me.GetDistance2d(NpcSpecialConst.omenSummonPos.GetPositionX(), NpcSpecialConst.omenSummonPos.GetPositionY()) <= 100.0f) + { + switch (RandomHelper.URand(0, 9)) + { + case 0: + case 1: + case 2: + case 3: + Creature minion = me.SummonCreature(CreatureIds.MinionOfOmen, me.GetPositionX() + RandomHelper.FRand(-5.0f, 5.0f), me.GetPositionY() + RandomHelper.FRand(-5.0f, 5.0f), me.GetPositionZ(), 0.0f, TempSummonType.CorpseTimedDespawn, 20000); + if (minion) + minion.GetAI().AttackStart(me.SelectNearestPlayer(20.0f)); + break; + case 9: + me.SummonCreature(CreatureIds.Omen, NpcSpecialConst.omenSummonPos); + break; + } + } + } + if (me.GetEntry() == CreatureIds.ClusterElune) + DoCast(Spells.LunarFortune); + + float displacement = 0.7f; + for (byte i = 0; i < 4; i++) + me.SummonGameObject(GetFireworkGameObjectId(), me.GetPositionX() + (i % 2 == 0 ? displacement : -displacement), me.GetPositionY() + (i > 1 ? displacement : -displacement), me.GetPositionZ() + 4.0f, me.GetOrientation(), Quaternion.WAxis, 1); + } + else + //me.CastSpell(me, GetFireworkSpell(me.GetEntry()), true); + me.CastSpell(me.GetPositionX(), me.GetPositionY(), me.GetPositionZ(), GetFireworkSpell(me.GetEntry()), true); + } + } + + public override CreatureAI GetAI(Creature creature) + { + return new npc_fireworkAI(creature); + } + } + + [Script] + class npc_spring_rabbit : CreatureScript + { + public npc_spring_rabbit() : base("npc_spring_rabbit") { } + + public override CreatureAI GetAI(Creature creature) + { + return new npc_spring_rabbitAI(creature); + } + + class npc_spring_rabbitAI : ScriptedAI + { + public npc_spring_rabbitAI(Creature creature) : base(creature) + { + Initialize(); + } + + void Initialize() + { + inLove = false; + rabbitGUID.Clear(); + jumpTimer = RandomHelper.URand(5000, 10000); + bunnyTimer = RandomHelper.URand(10000, 20000); + searchTimer = RandomHelper.URand(5000, 10000); + } + + bool inLove; + uint jumpTimer; + uint bunnyTimer; + uint searchTimer; + ObjectGuid rabbitGUID; + + public override void Reset() + { + Initialize(); + Unit owner = me.GetOwner(); + if (owner) + me.GetMotionMaster().MoveFollow(owner, SharedConst.PetFollowDist, SharedConst.PetFollowAngle); + } + + public override void EnterCombat(Unit who) { } + + public override void DoAction(int param) + { + inLove = true; + Unit owner = me.GetOwner(); + if (owner) + owner.CastSpell(owner, Spells.SpringFling, true); + } + + public override void UpdateAI(uint diff) + { + if (inLove) + { + if (jumpTimer <= diff) + { + Unit rabbit = Global.ObjAccessor.GetUnit(me, rabbitGUID); + if (rabbit) + DoCast(rabbit, Spells.SpringRabbitJump); + jumpTimer = RandomHelper.URand(5000, 10000); + } + else jumpTimer -= diff; + + if (bunnyTimer <= diff) + { + DoCast(Spells.SummonBabyBunny); + bunnyTimer = RandomHelper.URand(20000, 40000); + } + else bunnyTimer -= diff; + } + else + { + if (searchTimer <= diff) + { + Creature rabbit = me.FindNearestCreature(CreatureIds.SpringRabbit, 10.0f); + if (rabbit) + { + if (rabbit == me || rabbit.HasAura(Spells.SpringRabbitInLove)) + return; + + me.AddAura(Spells.SpringRabbitInLove, me); + DoAction(1); + rabbit.AddAura(Spells.SpringRabbitInLove, rabbit); + rabbit.GetAI().DoAction(1); + rabbit.CastSpell(rabbit, Spells.SpringRabbitJump, true); + rabbitGUID = rabbit.GetGUID(); + } + searchTimer = RandomHelper.URand(5000, 10000); + } + else searchTimer -= diff; + } + } + } + } + + [Script] + class npc_imp_in_a_ball : CreatureScript + { + public npc_imp_in_a_ball() : base("npc_imp_in_a_ball") { } + + class npc_imp_in_a_ballAI : ScriptedAI + { + public npc_imp_in_a_ballAI(Creature creature) : base(creature) + { + summonerGUID.Clear(); + } + + public override void IsSummonedBy(Unit summoner) + { + if (summoner.IsTypeId(TypeId.Player)) + { + summonerGUID = summoner.GetGUID(); + + _scheduler.Schedule(TimeSpan.FromSeconds(3), task => + { + Player owner = Global.ObjAccessor.GetPlayer(me, summonerGUID); + if (owner) + Global.CreatureTextMgr.SendChat(me, 0, owner, owner.GetGroup() ? ChatMsg.MonsterParty : ChatMsg.MonsterWhisper, Language.Addon, CreatureTextRange.Normal); + }); + } + } + + public override void UpdateAI(uint diff) + { + _scheduler.Update(diff); + } + + ObjectGuid summonerGUID; + } + + public override CreatureAI GetAI(Creature creature) + { + return new npc_imp_in_a_ballAI(creature); + } + } + + struct TrainWrecker + { + public const int ActionWrecked = 1; + public const int EventDoJump = 1; + public const int EventDoFacing = 2; + public const int EventDoWreck = 3; + public const int EventDoDance = 4; + public const uint MoveidChase = 1; + public const uint MoveidJump = 2; + } + + [Script] + class npc_train_wrecker : CreatureScript + { + public npc_train_wrecker() : base("npc_train_wrecker") { } + + class npc_train_wreckerAI : NullCreatureAI + { + public npc_train_wreckerAI(Creature creature) : base(creature) + { + _isSearching = true; + _nextAction = 0; + _timer = 1 * Time.InMilliseconds; + } + + GameObject VerifyTarget() + { + GameObject target = ObjectAccessor.GetGameObject(me, _target); + if (target) + return target; + + me.HandleEmoteCommand(Emote.OneshotRude); + me.DespawnOrUnsummon(3 * Time.InMilliseconds); + return null; + } + + public override void UpdateAI(uint diff) + { + if (_isSearching) + { + if (diff < _timer) + _timer -= diff; + else + { + GameObject target = me.FindNearestGameObject(GameobjectIds.ToyTrain, 15.0f); + if (target) + { + _isSearching = false; + _target = target.GetGUID(); + me.SetWalk(true); + me.GetMotionMaster().MovePoint(TrainWrecker.MoveidChase, target.GetNearPosition(3.0f, target.GetAngle(me))); + } + else + _timer = 3 * Time.InMilliseconds; + } + } + else + { + switch (_nextAction) + { + case TrainWrecker.EventDoJump: + { + GameObject target = VerifyTarget(); + if (target) + me.GetMotionMaster().MoveJump(target, 5.0f, 10.0f, TrainWrecker.MoveidJump); + _nextAction = 0; + } + break; + case TrainWrecker.EventDoFacing: + { + GameObject target = VerifyTarget(); + if (target) + { + me.SetFacingTo(target.GetOrientation()); + me.HandleEmoteCommand(Emote.OneshotAttack1h); + _timer = (uint)(1.5 * Time.InMilliseconds); + _nextAction = TrainWrecker.EventDoWreck; + } + else + _nextAction = 0; + } + break; + case TrainWrecker.EventDoWreck: + { + if (diff < _timer) + { + _timer -= diff; + break; + } + + GameObject target = VerifyTarget(); + if (target) + { + me.CastSpell(target, Spells.WreckTrain, false); + target.GetAI().DoAction(TrainWrecker.ActionWrecked); + _timer = 2 * Time.InMilliseconds; + _nextAction = TrainWrecker.EventDoDance; + } + else + _nextAction = 0; + } + break; + case TrainWrecker.EventDoDance: + if (diff < _timer) + { + _timer -= diff; + break; + } + me.UpdateEntry(CreatureIds.ExultingWindUpTrainWrecker); + me.SetUInt32Value(UnitFields.NpcEmotestate, (uint)Emote.OneshotDance); + me.DespawnOrUnsummon(5 * Time.InMilliseconds); + _nextAction = 0; + break; + default: + break; + } + } + } + + public override void MovementInform(MovementGeneratorType type, uint id) + { + if (id == TrainWrecker.MoveidChase) + _nextAction = TrainWrecker.EventDoJump; + else if (id == TrainWrecker.MoveidJump) + _nextAction = TrainWrecker.EventDoFacing; + } + + bool _isSearching; + byte _nextAction; + uint _timer; + ObjectGuid _target; + } + + public override CreatureAI GetAI(Creature creature) + { + return new npc_train_wreckerAI(creature); + } + } + + [Script] + class npc_argent_squire_gruntling : CreatureScript + { + public npc_argent_squire_gruntling() : base("npc_argent_squire_gruntling") { } + + class npc_argent_squire_gruntlingAI : ScriptedAI + { + public npc_argent_squire_gruntlingAI(Creature creature) : base(creature) + { + ScheduleTasks(); + } + + void ScheduleTasks() + { + _scheduler.Schedule(TimeSpan.FromSeconds(1), task => + { + Aura ownerTired = me.GetOwner().GetAura(Spells.TiredPlayer); + if (ownerTired != null) + { + Aura squireTired = me.AddAura(IsArgentSquire() ? Spells.AuraTiredS : Spells.AuraTiredG, me); + if (squireTired != null) + squireTired.SetDuration(ownerTired.GetDuration()); + } + }); + _scheduler.Schedule(TimeSpan.FromSeconds(1), task => + { + if ((me.HasAura(Spells.AuraTiredS) || me.HasAura(Spells.AuraTiredG)) && me.HasFlag(UnitFields.NpcFlags, NPCFlags.Banker | NPCFlags.Mailbox | NPCFlags.Vendor)) + me.RemoveFlag(UnitFields.NpcFlags, NPCFlags.Banker | NPCFlags.Mailbox | NPCFlags.Vendor); + task.Repeat(); + }); + } + + public override void sGossipSelect(Player player, uint menuId, uint gossipListId) + { + switch (gossipListId) + { + case GossipMenus.OptionIdBank: + { + me.SetFlag(UnitFields.NpcFlags, NPCFlags.Banker); + uint _bankAura = IsArgentSquire() ? Spells.AuraBankS : Spells.AuraBankG; + if (!me.HasAura(_bankAura)) + DoCastSelf(_bankAura); + + if (!player.HasAura(Spells.TiredPlayer)) + player.CastSpell(player, Spells.TiredPlayer, true); + break; + } + case GossipMenus.OptionIdShop: + { + me.SetFlag(UnitFields.NpcFlags, NPCFlags.Vendor); + uint _shopAura = IsArgentSquire() ? Spells.AuraShopS : Spells.AuraShopG; + if (!me.HasAura(_shopAura)) + DoCastSelf(_shopAura); + + if (!player.HasAura(Spells.TiredPlayer)) + player.CastSpell(player, Spells.TiredPlayer, true); + break; + } + case GossipMenus.OptionIdMail: + { + me.SetFlag(UnitFields.NpcFlags, NPCFlags.Mailbox); + player.GetSession().SendShowMailBox(me.GetGUID()); + + uint _mailAura = IsArgentSquire() ? Spells.AuraPostmanS : Spells.AuraPostmanG; + if (!me.HasAura(_mailAura)) + DoCastSelf(_mailAura); + + if (!player.HasAura(Spells.TiredPlayer)) + player.CastSpell(player, Spells.TiredPlayer, true); + break; + } + case GossipMenus.OptionIdDarnassusSenjinPennant: + case GossipMenus.OptionIdExodarUndercityPennant: + case GossipMenus.OptionIdGnomereganOrgrimmarPennant: + case GossipMenus.OptionIdIronforgeSilvermoonPennant: + case GossipMenus.OptionIdStormwindThunderbluffPennant: + if (IsArgentSquire()) + DoCastSelf(NpcSpecialConst.bannerSpells[gossipListId - 3].Item1, true); + else + DoCastSelf(NpcSpecialConst.bannerSpells[gossipListId - 3].Item2, true); + break; + } + player.PlayerTalkClass.SendCloseGossip(); + } + + public override void UpdateAI(uint diff) + { + _scheduler.Update(diff); + } + + bool IsArgentSquire() { return me.GetEntry() == CreatureIds.ArgentSquire; } + } + + public override CreatureAI GetAI(Creature creature) + { + return new npc_argent_squire_gruntlingAI(creature); + } + } +} diff --git a/Scripts/World/SceneScripts.cs b/Scripts/World/SceneScripts.cs new file mode 100644 index 000000000..959563be5 --- /dev/null +++ b/Scripts/World/SceneScripts.cs @@ -0,0 +1,41 @@ +/* + * Copyright (C) 2012-2017 CypherCore + * + * 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, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +using Game; +using Game.Entities; +using Game.Scripting; + +namespace Scripts.World +{ + struct SceneSpells + { + public const uint DeathwingSimulator = 201184; + } + + [Script] + class scene_deathwing_simulator : SceneScript + { + public scene_deathwing_simulator() : base("scene_deathwing_simulator") { } + + // Called when a player receive trigger from scene + public override void OnSceneTriggerEvent(Player player, uint sceneInstanceID, SceneTemplate sceneTemplate, string triggerName) + { + if (triggerName == "BURN PLAYER") + player.CastSpell(player, SceneSpells.DeathwingSimulator, true); // Deathwing Simulator Burn player + } + } +} diff --git a/THANKS b/THANKS new file mode 100644 index 000000000..3ffdb9f69 --- /dev/null +++ b/THANKS @@ -0,0 +1,7 @@ += CypherCore -- Thanks/credits file = + +Special thanks goes out to Arctium And TrinityCore. We have gained base code and help from +them many times in the creation of this project. Keep up the good work guys. + +Arctium (https://arctium.org/) +TrinityCore (http://www.trinitycore.org) \ No newline at end of file diff --git a/WorldServer/Blue.ico b/WorldServer/Blue.ico new file mode 100644 index 0000000000000000000000000000000000000000..1eaba52c92b84c3efaced25f4fc0873c011e7c0c GIT binary patch literal 150531 zcmd3N1y>zSuKe9ZBEm@)$6cQ-ki*%|6IoBe;W3xM|&y!V@|mv!MGw!FU` zIlN6MtJo?Eg?cf+vU+Ss2e}_Vb+XK0(3o^v(H$r>MQ8!uS37}l%n-G& zZ@1H*7qJ9fKfC_b=0$huvEHxh?J%d}0(`dRjr<32L>OiS0C-$B`LzwXdR)#nS3y(W zuQ4OPX#K2PHjc4=ywZHSwR&^u9_9}3zI4^y__;CcQu`P)J3BWZFz%b^sKpZ>t9ROD zd(+yw%_{kpdzsvp@H%wd5b2h7c#|%Oq}PV3n_d-Pqm#A5ZGsOqT4Vl^@IWlibb&EX z8=^S-U10sgS@5~L?ztX(IGH^y@cDMz^SY#C-}NQG%k@k>+Vo($29RLr7YzOGet64W zt=|<@eO4~4BhJj3=#(32g6EdpyK14SdG4I{tYD0`-kcl5Lds3MGLUTk3d*zf4>u}% zVc~?8h4Txu>O*vlS>iL${Y4afH$D}vwpZC%1=;kz*KLpPBujqr2lu?nsLo`snSqH_ zo*p$Pt!4;$gG#^o%}u$B53aJa?W@jkm-i#U5Kda1;An$<--VhM00;2h=B4oWN6ZJp zIQ(?dj=rK1V=3k4+8kfzTI@LKZm4$99Iv-hCJv?dTLbf*^N*GDpMFSgdZ%&wEa>u& zq1%_gDw(V-Y|FpBN*~?POE^^eJne=HC7sX))ao+ob^DVedQd zwS|2>{!qw1Q;4mbei;h6VE5fzsFCFNPh)vwhxsgSuLK{Q-Tbv0x55N_Mv0?S&zfO) z@yT!gItgF!{drW9lV-s=IjPD2i+17&ud2L~5gUmsZ+bcwPz)b)1<+k@_ISD~Xa~$i zvaDG1jy%TovCtxW7xRtT6Wre7y=bkla|%24gf`x|BS$&<4=GOInr@jYamow42AU+K z`a1FoxS7xg$1R!-|9NDl|3a$juq+^`KE09?fB&f@H9IxFqPyz3&aVI35!sj;V7RhHDp44bU5U$Bg~N+URSsn48W zX20~BO*pOccwA&`Q6pSR-|{}ZSz$3wPfEWNN3+Dza#^bm9WFe?`X|3|X+0-nW;2+` zL=FFuU>}M&aiJ5IQ3vGr6oJ{{dmSlRN*DeN8Y{UvV6eq%mDd4=a3O}Eg{aRcZEb?)Dorb|R=aJ`5kZ~tA^;1Zrn*S>nDo+m9Gp~?KuB%fd0m~1zX&|O zIB|nZHuT6%VoZ2&g9*0E#cqDt`Lbq5c1rt(^()Iza@rBsB*b!;G{TrgN0ia*&WO)9 zfSpZTYYq%V>%tK`jgck*o;3fgeJmwI!68}!)&|QP_x2$U2nO-DvJZg z=#}z41E3wYUaZNfrTjr3)NS>(iUSi&;taCA z_0bZa@If36O zk1hFfGVH>$IDA5!HV$qB2C(Jb^Rjioe`L6#8*84^_tc4_QKi2yxFmajSEk3V)&kkD z6RA+~jrwjES>eCSe`rVBoF%^wIpNv(pKG4CGtGw#gCKuc8RN~eu9Z^qu1hStu7!!S zsTWA`^5yXfI>p1cf)Vh3nR(g8FVAXO#ZT6Oxu%GBu44wj9tiMSG&9heI8yGiI1*$M z+f1W|WGOcZnXT2OZ+;1z(^f8_$3F$azag@V4l!0Ze9c{>B zTn8VOw=TvD)O8C7SSDzO^eznt@tgAvr2v*qpgPqP;|s`TK|ocMQ(F;u)|MqQ9WzFg z=ArKwo0u8Roz|N;hrpAbE(#MMvdg6)NOq`4D&GO?fB?=#|?(%kw5MA2WQ?%`<@ zGf=r_x^?)WQ`{>u{!W-o@ambYWYTDgeEK`cUs)j-;6ga}4I>P}s_ECgt>+azJZIFn zj1YZfXhu%CRiu>(`VAjOG8XW0Spo7f4=X_peSOw-J^30Kf7eZI?DQc83RwhDkZ~@x+sZbb1Vip2?noZo@e(%sIZGScX}FH zS61bH&J~yxKju7J5r01F{svZcS?UYaX>M}i(siwIEi@qxkLKF6-GDW?#*yA19KYu! zI-j>QIHn^}vDOk;u|u&SWwvd_7Rk`fRn3hFYE5W{Y;QuYR}^k0OZ?UQMh&dZ%;=Z= z8)JsgrSr_>(hjqKf-{2Ory$L+5;02yeZyWCU2_GPoY@aS4WS2~hU^1;W9%5(4LW_d zKSI+y9qFt4I!S#-eQPD+U(IURnAS5;rsKu2k7zB_0VWAc`WCHp!9D+cq94O7#+n)J z5Nk2|g6Cc8kv3j-DtZ&T*=t0vlLC)c*s{x#O|;|g>H%$x-nPk$P1rf9aDP144fH<< z2>uiqb8(g$9Szbov6!`3{mn+y`H_t^IyUqmdx7)k&vD!tFAopC+T0T5OR5h_wRPnR&s3q9 zAO5spc^pd^Zc(3=<#m~WrZ7Jfg9pmA_8v0;mNwV?&19SFeP|i4O=^>xe|Sl|TC1eD z$9JD#yO;Q5m$)2q=OfxF!DA9(>~tZ7FCB>_*GOt`ltV}uvh(5fX+M7dpObQA>-5hN z`@99e+@D!j(}5u4j5MLVzEU`3$qDJJJE(UIYLXSr0S$dbQPbb8r}Gb}68fF)jBJFG z=5~hsi7CbOiQ1grtu{LDQF@oGMfqKI7SvwXOIaUmTx0!Qs&$VXdBZ{Rg0USfeo5WK zmiSo4wx_1gYNwTz1xq_uG}ZGA+L!!gDwejf^(+yg`@IEU5lTAMXy{)U!{HAovt8(> zWn*(+VQmXWg81$a)|lyeOUDgk)H4XqK6$1o?e$JD7CU>6Ee}JbrzB#O91seIg7Q4Sy4!zEXC%cN+Zn(PAA=sod?_;v`Z{SG}h zo_D?gdYPqs=nveK`3nIVQ-5Ef%7Tv6mn2fAe{TeQ_bC%}AAt2dSS#D6T@r&)4M~0b zy0k-a5SJK=o3-8GBFmo zn_Y9Z(@sbV(SET5;nBKbK@?-cGfIdI_(t~kceiFNElaKj57rIkR26+pVE&xt#2Ax= z_Ux$hF^Q}L1Q;mt)lpNW7sz682?~_gi*KvYhg8Z$=}u5-C;8;w10FdU_!QX3X!TLN zKn5UDoqOZf2VF&os+OGIe3CCx?$ga@Tj~$#S)SyG62eyXD051bY1L9XjNBSI8%=3X z6$8HY+w$@>3vN_zE{BGQpTfc2-Mln#;b*@H6A?UHUor%#y;%KBc2oW%@q}Q#Ms;y;)LtV~5$Grwp57u(GQRC1x?p@)ZSMvR~OPjh)3jwAw^Gek{ z)J2NB^$UH4ZTbsLB-R10)BAzCDUR?fj+sJKeZ#ozD2)>DvzDV?%8*b=?_%$>qKD07 z3Bk5gPPvoyqm8xzzb$t~ofWA&SAh15Zsz_P3_8c6B6v9A8-R0n-Xj>33a z6%FoniPlJF=4yPrsDg(vJC^h=4bKjdFGpa>c45ZCa;jk}ovte7S~GMBi=28Nqf5La zO`3r&0*eTrZ`rfx#o}s=CnrPF7h#}$R`yRz(vk2`juvuDT8RPD^4olq3SFkmwR;!y z4~x{q4fn6RZ~}plXy1%)yQ_NFo2JTZo8)kAC?fb>Ic!vkS3nc$Ypm6@{x<7%aR;wk#^wwTD4h}918+(!)4fS7S%^Erif1l?z6{0tL}8N0nf|04MK(1>%)YB?I(PsF%>bPRIbpd09WF zO!Mmc2Ovw0HkyAMSwJv+4QNQ-ir)2yVx614uHCX{P3LcRqQum?E`W_D9_7ly4=a>k zG#1>by%4mhhV*+QeuDOXyYS}kG0cKU&SI+8)=Q_i{Hd^^nu3A40M><+l|VVfoN2^m zQvA=VZUedpX<^#s-Zy>dCC|teq~DgK_Uzq>iP5)%PO2>7>9#8;7+#Tv<_ENjZ z0HemkjzsWJXS%Hebxv9k9OHP3GSi-vxtCH-y) zR$5mBTP54Er4c&n`|C=x@~k43hI+cF{3ow8twGjhW>r6pzFn5q6oMH!_t}ZZe`ied z=xtD-aH$^AwSk6&c-a?~Bx0tXQ~|94p+KfKrXhMHZl=Plo=!}4Qou>PBaB{MMsqIY zzTP72aH8=;-zvY%`yr-Jo-lqWX0GYiLztA+`lwvu_5GaSdMTAEzG^O zjP@I~=P{b)W1*yhxHV-9N=;1=nRtDG3_Mw(3GVor3Fb_LUYjHIe$!u?wPPA?wdz^q zZ|YjhW0@Kl30QrKY1Ie#NWV~$NNO-hG0c8wRnbU9ceC4tkco+Byoz8rW?K@{WvCH% zF)mB@vY`+S1vSbsv7n1?Tl=!k64(U>8-D}^4>!k zf^t8Lj%gfIW#1WYUts&}o-X)L!@YuU;D>s`*QKLE?Za%PF8U*BwDPE?%ESU1a-+X=yA~5*(^}ZxXTTRUnVuE?dPal}#SQgTZ$(S8*tQMz zlV(Sx1g!BHjb){rSo&ISQN@%@x>3!RR47068fgdtOn33p1@08JAi}d5w+O>I0%d{D zJ=m0BkxFlmEwf%Wd$^^DV?_l|Z~zQ(Sl`EJ7D2w;QM9Z3C_XQ-lPMb+gT0xDJ&70M zR$_iD=6S5AYv6Wl#+(&p<_OfWY%V>Z(qSki;DLB8?2fx>GZE`Ry1dFD)=5|G7ovr} zIm10K`@G%+lWw2QC0#q1wBHnwo=`14y*{8+6(uUfaGv7!=wBt*#%iSHXZ zGjx)Dx#728nC}2f>A2#g)_QHzK~wAk7{5QFroPTPZcvfaQd~RXadYP-L>&r7Tm>rq z*6_RS?whH~_`__dh$Cqkj010z)vDKJfl!G=()7To%j&nBE=iof6}QBKLV*bu6Bj7&*6cvL`a^@W+(||n^_3kNH%przbgU>~lZ@sTn^PX|$&C!sPNYLuImxgQTpXwLUS{78` zJ)d6F_UP5>8LeXkala%&tbIUw=1^!S>yBvs&?v>x7XjL4jI0TQhFppm+n-S1o-{En zrJvKT;a`&Nkg*J?c`l8*-C>efzpjA`zEBdVd*_l;f z{pQEchRJ*Pe~ukbJ)15JQk^1a$LPG;`||c+|AFx~ZvSqt`E^rNOY&>*H^%R(FE-)y zD*HaYQx{+TF0(%|{r$lB`o7QWxNBy%?$L|9kC5 z@ZFjo_3|XH=B-wsC@ZF>=F4xP)!a>_r!M;s{ExRWj?RxU_Z>oq=ep^=mwW40uM;w= z54^i*-ft_b!rP1mrEMZ4u&&?Y(eOK;=F+d$d+zggzjxpF7<+nrX9I9mWjPR`+`DPF z4L=8LcKN&MDh5yuW+2?&>iTDXxGN^8F z+l3L&eToWl^ZmGL0tw30LOWyPddw-*=-BbBi?~4NtGN;S?Z}D>4+TWUH4Xm`S{4Gz z;p@}Xqkn`(_vhvH)l|pvdMGRTryy!|{YPq^jf`dKQ(w7w43u5ZBi-7dk-5k3k@v=6DA-MWn@MrFUsF&M)U8vg|M{cmPhYP4 zY>TXAX+dm3?aibu8xNgbo+w-9E_e}9bbEJ+S5T~jPf=W_E&RZ%gt!6S$o;!+J?^e= zugxWE_idNP)y?B4Bph*1aJSAeddURvh$*6wa329Kob=FSARA9#iveO_f&dOT%q|zZ z^s3Ht>5J4~r35bGaJQdM<~Y6mer@JO?^A83SA%s`{ph&TA337iRSq7PzMa?FLM=QB z7=+}2z*u0@o64(J5$_Q^3xym03(DpVT20S0Hp=Dpz)XqsZw7DGtsOI3Bc~2kDlTN- z=-`ga{n%xEmvIyMDziW)e`^?Zx7jMT73RBR^7I;VnZ<7}hZ%er8OYGZ1?>Zb2;dIwbGJvb1Zs-IGv4YJU6^q7&pm+SQ zH6DHhyE!Jzpq&rS3~)l7a}OU<>|>Zz#!YfP`qDA>;ujOoD>`j{z0tC!P326D{C0=i zjuqCu=(=?J0py{<7zm+Qpi@|IhN>mbs?Z~HYo({lXEc#j7(V*O+Dh{$+3?Qq?Xnf~a`yx9f4Ef)wj(khQB z;UDL<;vj?6yKvqvcqmD&&6KvP2LI|DCM$$qnL|bSGh>t>fa)tDDlYUxd_>Dz+-fm` zwn^}gKjCEK5fo5W>sO8Cg}$>fEJuKV=;Smv(}EjT-3n5Ak?>7u8_#YGK$naVjr-CX zQTUB;Z)c`ue8AloY4e!!orp(@PX=*#>MyO12{mxN=xVwh-=SJ{hlMyKexO;BJ{}Aa z(HjN$B_vf@1>6e@jBSG=97QS7m;5MLV(Pa)qw}vge$>w9NKT``YY-F)@z(}Viy$c<;EbS z)FBs7gPjpvBLqDV`q&6C}%;va#aq&henbas`#&_>fEN-uGOaDMNYlyhYHtV_lKg^Ie>m zeD{-}&Wa|%`D6wVsKQI`q{Zd8TPi&fGh+!(AbP|BE73DJ5yca8Mr7h)ZJ+{?IWQz{B3jT&hrN32KOk8#FzlM@R}O0W;m zE{3KcrNJO6Bw-ejnB>o}f258EZ5PYWWNy{0gQIVHeAwC&MR!WJTHj9w@;9?bXUszL zdUR;9#_iIV&K+OpE_m07baj9on3iwJu&5&m7<9h+D5d-`6 zm}k$WLo=`tfyNmGU@Dd4E=dl3(&Ln#O#?v&Pp642|2>tZ9@0_!yAq^UCP{xd?EGf* zzbe6X+`{6xQJ-vYea%{B%t2axd5(IP<*9CzM^LGRZzSMD0Sd(Z&VK{PYAT~1p~BNC zimOPjEr{p-FBhP$qB~)*hWeKuRGpKLj`Pm*!5SJ|1ZqL4ihr zIfW~22M?p_om5oOUnr@^MUb2|hy>T)k)dOD>FTHCQ{eXmM4O_1^%dJ;PFu|~E_*4c zvdU`&{y?k&<#fwKr>?tcO_!#WCP3*I!cIPOW8AZLZ`=OU?zjRRQ*p)PUPO4{4_c~u zy}wfga=##whm1H8GNzB*S)39`vnllgOdMu$KQQAGK-m&xYoGqFWh8KVwam6tTO<(7}pVxr=!wVV{ zPqLx4_85(CmksjFNxLRXcF(8@JM60#hyKA}r7vAizrnuJXC}nxS!i;erYam;cuxOlV{4@fO|DiZP%le6RgjsF z@O6qe)l~d3|Ck=D6w0B%}r-;@`(-HkjEZOA}z4A1!veA+KgW9?ZxF zYYlwN(o6o5%8`&O2PkoaDLl4cKgr|A?$HW_{T)QYe73Vr(oTv|cW@3Vsk6bJymTf^ zS6a+yyElC0WiwbK>)N%uS>jbCt$_-X`y_yUBv9}Iy5?Qhf`NjOF$gVW6REisIZt@` zhqVPv56wx2vgkOx6l}&b7md6)nwY6s98Hy)ZzX+5mBLP*oW7kly+V}ILc5mJVlZM) zxclfAODf*y73cKtjnbZ29AtDfbnS&XKR%POl4iuWK6JLE{@fnwDlMc^PO`H?Np{f% z&@WKW&}}!keE<|816{bt?ZDy&hmnc$L@5%XE9F>dN1SD7xH1LU|G{>mCHkyuT98b0cm#LR``YS$wU(v}LtD$*DomqeTs--~k z3~NrQg@>oiFQ3Wwc`*!R^;QjG*O!XwP#a(z`MpUI*x-}D32Rkk-0f&X@+cenQ54}$OGVbP=HaPyBtu%Gxr45Y#WO;PTCZT2-!eDR z7Bz}=9!)7uh~`XhJd=uo&TN7cKPCL| zIFBzO0++>1sY0^Pgcmm9b%ub;Sda4U~qysR_(;vKoS%L|wKb(f( zaE>xm3*^Uzh?P0UCyg1vTqul4qmq(&#Dt*^EE8s6M{6Uu?$?LgjVe*ouFF0=h|)PD zV3mC~4;poelHT9-^=h|LrYDgzcQ7(Ork0i$1&#>x=#4z^laqNyqk`P{DOuoj10je-4dH@=(3>Fpp<*1HHVL&=94tIbU^73}6h*kVfMmp0T?Sq+NJ z1T0c@YFV%T2zDws@NY+rmYBp%%eJBFfZU?nZcON>5Kll1w5cH}^p~eqte|pS+0pK& zm4hP*q=tx8&ay(GwK|f-jbTKPI`iCvT5}?HwCW#?3mwYVz2NeGAz^+*CC>`_V;<25 z)s}y&t}Is^66koe(wCv48$Kyd{AP zXxcAubw+iCfiy~7=o1Zgoc_(0Ykq>V+!c@x^v82F%ZEyU_4d^soigRJC0wQ zL0Iax8Uj^RD=B{1cak!spw|J(xq!)h%wR*bej&)=GNo{{#L(ai!O?9FU(J+;rTxC| zsokZoT6p80sr|gH0@;WYVXpLZ_Ep{76nAo2JM`lL+O3Y>Hb5N>fh~`e*#*{RG#&(< zFz0yfx%z3kzvFAEzCEs(t)6Aj(|7NX)izjZVEQH>XoJ;hde?$uibYDyz_eBv+UU6= zmkVKdQY0UAzP#C9Q`W#~>v^YRF|S&&2J5VAZGKXA{07bMCAlai`49-&KlZRJmLdh_ zS45{4oXt<76VO4vXK8iOXmC-)o(iwnq}T-@_##5*wAWlc2I4GW3DG#MgxIg78WNL2 z4*ze**LP&6i`>z7)`j4eQNe+>x=Wf@1HuQHTN31VL3Z$Q`=`cPzAa{1ll&ggfJWZ?gnSM zbqOOnZv780)S@__A76IR>Is0Iq8N}=^Hy4j2Y0&9ax$DzgDM6Z87eO)bQK1M)=Ca! z@M;3xLb~cSixe|aVp!A>hYc!}Q?HzJD=B3If{^cUz6vNZX5gXB#H4Oeq<;AZzga@o zVko*lq6qu^r%yad2w&cqJxW5#PS8ul15aaPw0eyB_G6YU$Y~I-l|K2vE)O3jhZjo| zA8h>>@k%GSwMcE(u6(u&HIg(DUB{ruo1{73Q zoNl``6zi~O2gU+u9hrORy#;tx^0@`P;6_&KZ^tUmQh*l5_?3j|$`aQ>SAw@dhQ3kT z6VvSt?CT&xNvii5mJwb^22>tY5Fi{#4v z<5BE(C6kPWX|s-iV%pK**4~OKh<+M@V%nFoWHQ^eP+R#1oD1di+aDLvc&BaW8_XA~ zd8+AM#i87hQzXB7kZY1x22-lv^0`w^y^ zJJMNs$aIocCTO<$Jl^a`@$%SEY{hT72yh^W`wrP9oonWS)47eqWfN(CF$5IRyyJz( z!mqbq=3*%BT1D3B2k;fsICEzNdZLibBuRV(WYP0YD+xQN?4jnk7YV~QaAXR#$<5i% zqx&81&qF_1wQJdu&hU_`ozYhPwiOjrA&=Z3zQOi#e>Mkq+9+9Tp#KQ!W_2`smP&zI zf|(HOs7TjPHNp8teB-3WPfovZ4^%X>)z4>nO>uIn5m9S$IdKO*-Aw>O_dSul3Xxqk zGQy#Z1lZvx>A{~zVk9J#ke*Bs{hh=l=uqWq@_uFGHEoc}A=>)R3?t&0H1>n8LHY~g zCSYC5il}8Iy`VVZs_Md4Z4Afus$h7t%9&tYSy!ChWgqCMbWA7gE#)jP2@kyA9yKN| z+@6w$oVlM&t_Qh(kndVPbYI6F!6ZdDglC3F(K;f>87=d-3Oda%PI`x;V+b;aCfpiy zwe0E)M>Fo-Q$1=}p5GVEVsT2ZCZUa3MKdPQUK!=Xv<);63}dXa>D?K`mt=EC`Q@np zzRdJ~H;<@*KSKY@5YR?H37>;Ca;%HqevcIyf|+HWc9v)CP<0rM(?YEkyl}6%^&Fm| z|1gTi_1O1HHA`Nc?B<(MHa%U8ybALV=J8<*ejLU%t_vUZlKwttlq&GpW>tFpifvrt z`Y~|RE~4T(`^2=EGu4+}syS#Hh5}?n0XHA3C6X1T^VEp&=spS{_+|YuZ-*i-4LEc9 zVH-j}=!jMyG#0Z5AN5KA-A3x7oL#4MUo zOq4C!L&r$Gl7YL*d_z8z++dw)F@^D-?v%y`Q4v*D0|{^&jfXRDVF)u<*p!F5E9lz- zPwx&b`yp(1_!FGppvC!4%NWgA*bbk3a?B(*y|{G86EF#cceHE`_HDYexX5oA(=9UF z%tw=tfxhe8k+ZA()qg>@^Et=4-h^wJVTgy1WF{XzeJ-@I+4StcMG#`*`=rMnmb5H& z{+0*5#e4m(LbX$jU|H(@FU6&TmJp^dsxuADb3~xqdc)m$*JRNDc)2ao!6(9Fq#+^U zEVn!qRG`*1Wm4}9--45YsXk+p$vR5*XP_bZXNKN0q5jJ$(_V+W zeWBi0vX`$-O0{^uO17FdKICvOq5cIue6EL(TD{No_}-p~klztSm+2rO70vj@GAhVd z78VIGA;Qe#y2kScWAmQBUvGL7n{UzZLZ(6@LppokOnpiFg2 ziLMquIyg}1{B~UkMi370-S;~Ebm+#k^RD0d={fI}^}_EBh&?2WR0M60r&lsiG@3A&qJ)-(eghGuBj;rh8Cg-L-L+CL65%1rQ18)!z8Ei3~SM= z;*J~o@87?B%iY)UDO@kz1X_iM#;Gq}N`8LVo^SojJyG77TtknSl0J7!?qe;Qd1Zqv z5Zyz|l8}LF?wt3x6T?B)u>?M-506^jt7d&I1|NcB&Mf6UcT?8eL)*TWcs@@T*3VKW zk`$1!57C&(&JI_W=DbhXgde+b{-F;~Mgp2nGXWY55!WvLJIXE}o69{#z8C693u(6R znX$`O$Wy=4i{H-RH$JY2bp0RBEIX*t_>go9&zu(bx6ZR$pAWZpj{Cw-0Fz&WNI-~} zoiEEz9hU;KE~O2xyNVY5u{y^=#;hhU0smqE_N05n`p@o~M1o@53|5^4xG5V^8T$-b z^9k=>#HAibi#_RoLyXQ*@ZFP@}93#wfmres^-W7=W?xQeBgf z_*cW~E4EKBg@?{>sOOTX=P1+(TZwQ82KDIhZvs4uyEW9@@zW3|@AtY~@w91DP0kT@ zqT7shx{RsneY|oaH$~x{Q-qN7y6BdsaBDn>3FJy^;DG&o(YsA_7VPL>bTdycPV(ue z<+wuHPR=ZaIz$#c#OAxiGMkXOpb~vcS`$Ux9220lA#Yap^@Mndy_Dt=gzY{5>XgQt1Fh@ znV#k`a5huHx=|c9Q&mu>O40@>arKIzZe>s8?)Mb^N*{orv}J|S59=O>YVf<$)V=~7 zG+ZU%@zJ4pp7tH%J)Q#<%O4twi*t^O zTV3B2G5(5qXp9FAQstBjxo7N|JpnlNdIPd{3`R0 z9NB`X7{5eCp74P}>#*+bh1^VdGRO{cO+wArsR*4)A;0{G7zf{yE)#%4^=2K8*}gAm zMGZ=&7IhoP2yL4<(5~>gREYe-1>QQEPi3-UntFZ%c{$op+~F6EpS$Cd&DEP9cEx+R7m#i(Gzf^!P6@+z7iOgT`?}lf>=?B%D=}x^c6h86ZSsy z;h?w34Uy>?b&(RumcWG)qP`M@@u#{^=hyup?(Htt9pn^E^S3Oo0GE2X-{T!r77$dJ z%%)&06FFhwJYCpJu;C10lcY4XQykhbb8lN^EiBJfFoUlBJ{!-KsH(Mko@!~ku{RlT>%St|1)HqaDvtjp6`xcBs{i}ww#}g zXh3CH$WnQr zLLF2`d>T22sCsLFL`&H)fnyyMVT8tTk|5pp@xTY3#liAEhDRUe;`i@&Q! zHJE`87>ieo)*C7qqiFXMaNU+Z4UY~*VUa+7r|XFs=wxyja9CU9Ea7k4KHzy%#R^cz zHj#Nv-IOs2vV$`wjcJ3(WMaD<)zQiM`ly=WA?474z6L1^l1)+yd|7(Vhiy_{x-74h z`u*L9gTF&QbUal$oC#ZlgnzT5CeWNA?-hu}DUzv`AQBRc)0=@2Yl<;*O(s0v{T)7k zbU4&=Ta_jBhX-pTeMWoHbQ>m<(NLU()bl!;b76vzYDY(ndIA=^njXVBbA1%_lj#&c zSycWHj4kp98ObAg zn?wbsyYQHYTw~>PX7F+Sokb!?UgKcBh3Qpd)Q&C_T$4S+Y9oquIx8JaZ=UN7Ekkl* z7!BgV1BpORuh^;KRM@;%MYjq{Gd%x&5#g(91{DubI7Xqr z1REtvLf2fesoc}Xs>i?njzxly0Ua_!9+CICNLhq|JJX#ZC>pZ zClr$yw=4r6j@m6JqD=3;YKZZBTOqZ7ZOwb@hELUgr741Jxgad)!9yzk72C-UG7rDl zxoAuWGcZB&aOP>foNs=`c;fegXm~;VaT=T^(YCB3a@?z_YP}U3nqm)`4gLIskL+qm zjt+$8F0szp(U}Q+Krkt%dn_G^huqIB5+q{}$wx;J%g{^IZ9qJXl2Dd@cmi1j@{>?< zMuJe;X|=+^tWk#RLk5&D`yu{VYRY;e26Mg@U%OrJu48mF@ys+;>w=s!ve2A8)DLkv zMwEPq_IB*a{ED{Pjwjti=dI2`mM^OP>c(Q7zNwxPB%Q@jqIzs`$Ifewj|DUko5TP> zfrO68?T;pe0Q>x?-uY-gI(&G9Q;!%Dz$!N3zT!Q@5NVG;YZvlBM}S`f`<0hYlQZeC z;pwCci^L|vi2C-}=1S6z)_A5$V2R3{uvLbjb4U_f`jRfR5Qm{tp6Rgu;$8%0eA&An z1(MuYf8A$lu%z+eosHN1rAghX4Fj1B*&K;F9h-$+gZZ}wY_O?}`5`H+vz2JfbhvJD zy24>@0I8t>f@KE;YP=3THc|T~1diK0i_w(#Ci(=XzDg%@2Z$mcD*qbC@~;?oyD5aM z52YZSYLGestg&C!cJm8?R022iPrZ_iZXE^n^9%EVn(B2lEx765-*i_h^JC@$ z2RIf{vWKaM2~UBA;S^=SRTxJ~n09&$f=7j+J_4^4WV|#`(5)-)pMYv!F~atVB453h ze^4toS8Z!-aQoL&NF%)wF7Q%qEt?41D*2C}g+wDR9*p{@M84RhiR1?(PSfCQr`xiLTSIHzq_a-H_A0BH0`aW zjPwA-Bmgcox9Iy04t|iAe5J_&gXMpM|4^dDX@7&OT>IK!6@V&i0;8)Dcu4~xS=ibC zQu!w1vN0os7EK3%B9uD8Q%XBPVg{iaw#@%gjGXjLsf;d9sHPDSP3CaItdKrYB><|q ztR}vlSuDv2iQnf8?*5u1D_2uB-3AF9%+--SN)w1snxz~{+2D}yPXiKcBwPQsKN~+o zMf#{N1X<7yKWv`dxScmX=m#qO1Myu-AXsRl8>eV z6oWa^^VG+ii_v2PL#sz!RnJFsAK(hFg^j(QrZPHB;GpYtgUg3~mPnoe+j2@MHI?aUOQN9Aw1|3>nhAu*d9}V@#-j`2@uJdctWi1}WX!5& z;zD&{bY@ILDOOwwCFy7CT2HWLTlLAQt%XGC8ZZa^kb}OC+)^|Q#dVb90>});+eMe@ zJlpLt={k7d&QzKmD*9s_s!ciLs%J=yk(=t|V!NP(4kgqNII9Nu%X2)rNUSM=POEJL zL=9)j*0KR8wTU0~HJ$KXh!GpG#qQNd7W6i$M=~u)8|jkh-U%fn;J#Ybv`3lkACS=x zX0WLMeTp6xsMMugMOoohR}q}8;7d_~&*|N(*1g{%5_UarqZZ4q}(aZAU?Bos$twvLx$gCxZ>pG_2Mnxe0#{C8R zfMxWC?*KS%x3iu4N_retmOXABURP~%&gPzPyWW&fqVrLQgHwqbfL9*2z4%`DbbJrW z8{@a8UQv|?V#ER$nx&76>$48d)DA}Nq_`J?KEM_Xjp~Ps9w92Fi44P5uQkKh^~1A; zf3A%a1zR%Mo$wWfd`CvU5m(ZhbhR+je#yN2)vYeZf%a`%hxO6;_ttZ}KnV#)lR6mH9)^)ua1;O@!Sy5u{CKoIVdE+8AQr2llXMqCgr1-rX>FxNnd+pxB083r6 zs7l1}>3b|)(pADic1EgDDB$1&*eYX3L9v!CFOb45HY53J?tAa!Th-E(q}}jDuFZ=# z__NzA5HG4gg6u2Z{PWLrn*K`9^)4YS(_l` zl#D$AAnTqpV+ocTCPKR~R&dD|ns5>Nj)R3)Dp06Prn3;{-r3ooTg`!{L<<46T|q>phi;0Ro$peWYWY^tzL z`7vY{$Wr#J{-c+>1u9-AVUF9-_p*KySv7>=b6P}MnR(_(X!M>5bvZ#hL$!)?pgw;= zBXz(sJH!Bwivu#)gEpaKOk_SSN(D2ObNdqBu$Hs~ zp3iw*7PvqlxD+fXz!VHRW6i}sh8**q?ZfJL`H&YoEr>HhX~(fGDnucV242WR+48~{ zN|NZo2y*qn%$Z_LHO1s%{xX;@wEMCG=RBt1q9KJ14g$>d`bJn*`*?+r4=@Ir? zouJx-;bSiQZ$UOhUnwM!$xMavY=a3!Gq|MnqB97w5EMJW6`~%J()ch?S0aC}a>liJ zD&Rpp=MCXJ^;v#C+bnx`4q2fVJyw2RG822^Fz#^^ zg|Th>X?!1U6Cs=b59iMkbcKR0m9&J-Yq!o^+B3K0)C1ns7pKpNtvzgl$){P>I3m>3 z9uB@av#^`JQIbvXn*}nC`+^6Iq>q-o#4~%3sy10Kw3VO+9in6>hg1aeQc$m~U?51z zfqmGmOu2v&*7ldl(xQmKs;CApk4@#a99YmOCYaNkNypO2P=c8I?TFCSzq{LfZ3y|U zgM3B(!coD_94vzU-BOfMLYQ`r2*nax8{P?+S8v4^MrvGlI@^Neat`U+GT^jmg3gzP z_K&D_M--@l+eFs7pqoJ{vErh-i6$x~_e!-Lc~Z{JBRbaczgz$v>l*B|IT**^F~(8m zUl*7cq~duh#<(Ad!t7F^^`Y^SP?JKxVTT;ogOcm78=bx#q=ye^R*!px3tV!e7gL`s zW8zMZkkT@_*^=8bFpWrVpQc2+rVn;ui;j;3Bys}Z=G`Iwo<$$9xv#m+sTRuhFuqBb zk!@1HUX=L8BK;qlt}!~YuIa|f#7-u*Ik9cqo?v3zb~3ST+qN;WosMmO{j6`jYjywZ zUiY4Rt7@NJd)Kk(FsJ{$4y`PG$6*Sx0YS7VO|6zUmD-WY?dYTsV{Y7X|dwlW<_ zSwce$#t=vaGxz3$6Il~II{mp&OP3Umzk5<-DrC!F$enINL`aZG*FEDT?uXc5E;6F4 zD7n6>`b64xagK!DR7((W+=PDpuX6AZ9yVQJ)QR!qKw3H3 z0jXF`5)+J!4jJ7I=577y2>+vwX|jfc5T}T=3!lB;Ne;)DoMavQQM8iC!y`aE7lx4` z5XR9AM9X%cZL0%`Xn3C(a6t|lowQ}X*Q=Do4Ro=RArd*?Y?oH|ZRcM?(P{97x)z_7 zPO62$9Hxeb27;7bs+yx~fI)`1NKD9S#c>9H3mh=&QP7#65 zdx}OrBJl^1JgUBA_Mtu)oU-2rx&dX+o;!xWHU_<~VF#QMt1!cDgT%46gk+6xcra8?I@$0%^y5MEa98LhP_o(*wYIIsn)p1= zBV{ozO@mZxEK4rbawX0zGoZXM#gsYCoPJrd6a%a;IaHUTtVX?C5wg-6Tn; z9Ri{o!#>ZB*Uwt->+gX*pr*AZ_D<951{J>_z6QD8&S~cJSFT=Kj0nr<a>Il3dooGnBra7;LA3m@ws7Crw`onZ2Gvhh~r2{Lrf>$utQpeG~ zC4Xp&4dECQhp|DC9)RLhp6k744cw;6180lkEUi)a13dg1Ly7_NhLmxcNukd(prG^% zGxn+{Hzmwbziq%nif*q>_-S;q-Gf4FteBs0+Y70-n_FmwfHH)f83l$;wM8iUP@L1E z7vEA0<$feF3<;@DpgsDOtf;W>jlf4A3Z1G;_i;5FzyYr`BeHIG`KwT9!6B??uz&0M zW<9QK_RJ2B9t7@@tp?@Ktu%&r9H?L?FR+{N4IX>t<6om!1TJxZ-UihvAXN6t*`ra8 zTXxZ0ObOEqSnDDNd9HUQ1kx%Jj>vg9QxmLZ!Bmx$|NLQC+5(>^?r`2#?Bwl>(|?Bs zN@)e3??g_9q14Y*RGK_u0+ z0|NV;QEzTPA=hiJ+F=dXtwMrY{SNr_o4wT3Rsh#ggf|C#qkDal_yVgOc?QBR|W zq$Xmt;t|%!?~%IZ?qSRZlle6&(DWkrq9MEPF36*vI51LT(6Ms(KSj z>Vp}4!IZ6A&|W!MtwKz-va%=pcAlQ(Z_Bw!bus@*kCPHfp*rQW7@ZR<&vGP1d=Yuz zUsT=c@81V;5Z^=EBgB``tLcCk!6g~#>e(C_SAoR=vBRe!dSdYs6%TiL(SS3Zq~t6< zsC5ovE5NVgd>zY&o>jL}RyqqLF(IpXoA{Y?H%jwgX8CBv91oSeCERS>)G1vOLamUa z#?}>yQJXjjE^LCx(nT7DaNKjU;udCRO6ZCq8uHRLrY6>EF$lE?Eq0nvycn=AqF$gE zccM6D2t#dWzBN+~B@{L{jmfw|>DrDJQMyqsSwx8xd@^<@46h(fU$qQ^Ta;={7Zu@Z z(#hCR)YXok1$ZNlh!qD#MSjAyU9zdJV4ig9QYY`;8C4%VytWReekr5521o=(Z_SA0 zYAtxXC!$-hN=N3^1E#@;G(yxeW(Ay~J!;`dKGM3eAET4X+EY}BTJ56MsiVaFO`#~i z8H{C_c9P#ktzEQIfq0TTN~dzPRVTwkETG_0cNj_Onig&9m1g*E@JN(YDV7fF@L*#V zr5uo7W^h4LaL) zmg#(%G(%*CQ28B1SWg-FOK~*P2a_D_;9Ym_{?=iT)Y8b=L1q19NWF zx8#6ULcG>A`TLfIIdt?qxh2?&xjN)nh2r{@+ygXrE9ELbW&Q$?!V$eUSEWD?WO7Ci z2yzhZK;!maW)gbvly_aRgN&SnPR1YN36VN?pQOYfs`twB$Dm8Iwv}v_0T8V)zCQB8 zqGRKZ#=61R3FOm}j=y>8&{KXgcbQznv%(n4nMu&j9#w4wVC*;I_C@@_1K;(c2NR@A zDTBGk?l(!6nZe}!)^<@%OPSTKC03uh;pi1$wEAoog7apzC}7fcar%k=iD{yB-aK#o zd7;Rhpb1X|*GOyr-Kqt8QaODW+cNAJa!SVBnOc<6+>=Z}uYDj`#5-uuJ1~_DQ=?=i zvIFcl8F_RGwz`Ms7e9k_{Bo6Qj8WV8&DAI-AJ&rMirsL|zr_OSJqMG7_C zGvQyyZ%H!y5?MjpEP9@6`2>;`c1RHVBq{QOtbY0D2_7(uX|=!DSx_p$N^r`RpUjqB zvi^uj!xe>nPtE*6=rOLHuH@E=2}YG_wsSxwTq5?elzQ&7MPQiQPf&P|bUNBVqm#K7 z{F0d8T{5I-)bk7#_lUHx2b5q(srsTDUXMGWCBGfan;bH(fPvUk=wX!&Zs$hlFsQ8&Jo-LT5^UpGtL?3%tsHOTtPkd${Phrym49 zK+GR4+JfB3sWbcx3qRk*V=Z(82 zpghf#Vp7_6%X&H?V~gO(JEm zMf~O*49om_IP^;mY84h~SnHe?Sk*{1gm}R!Dx3CP@Mz<4G%YfT9qW>DAIUfXyr39X zc})XYUnCbMS>7_K-?AV1I%7`F1M0P^*6tp>ZB@?!BZ*yzD@0MAC=cf6`z-TpWfqYRjUm0x z6Ubpi9O-8iNqBo?N`pXFuUqYC{+{FEHGv9!O$8Kp%nIY7 zOV&`{p)#)#wlW!{#H)gjo^d?&AKnJ$jFG4b69uY-=Q1St593rXA}3Vq*~-9cm}121 z`Qw4Z5KOxERmcSNuDT5)U~|)9(s%eunuBqc+WM>Un?}TK;srM1U{7748&3(1V!Zi6 zx|BOol8UyG*Cn5xHtb_z*ZMY9d6>gR;8mebDun;W*0xZL$L=b7+b`i%Mu#0&<4mAU zHdm?c9B_5u(lr9pTUQ+!v*?2cHqY_{yYA4lgWTkD7HnF zm`dt!i2_`36&ZQiJK)_b98285C(KcP2aw|x8Il?Dv@v7m`C3BguFY$R6dL!F5RwTq z!3+>B3%H5%aBF3jaPMnox}29^CWhz{h5Qvprk6q08Ld+i4^-+HTut}5jR-pTrup|DbsH@;#vIH z%hT2eeQ*FS>jM6ZS&59D$ae2eb?hDgSMt-SITpj;I1PAFdsFYAr`o)z4}C}>aw;gA z5lQHp4%z$PQ{FJxp1HqZUx6KK*=P?_;=&|!By1Qq(_DRIsGoU?SlT0J{A+NIfk%F1u$&q+Ol}iBt=87xy&-g`EE=@9ZqXFK`>S9KgXcU18%H5x ze>EM`j3y}tMh(vT%UG##*%mz7@Ge?s^{)VO$s=Ck?Et)g}Lxx7$3IhMPkOqr+}6cz?IA7%%cw(@wcR0}tt5#CKmO4&Tm{0ZFCKQZ`Wrq=ZO%P8>1 zMWqrBhQMJlWtNyusB!Gx+`v3vc?WlL7>s@*?eq<92ncz33s47SaWICLY1d3i3G0U# z5GBbu4Z5Co99p zf76eaTPHM9fI!tkp`EQWh(xH%ZOx*EBGU0McJQJKfA+P(y*VitEFUpuLkJpZ@9HmH zPRL>^kQ=*8e6y~yw+xj}cIQOzUm1#}szh4{YldrCEwE7Rudf)!`5-9N+I zJ$vK6w)fNx$^Y^KyWRT+!QbVMKW&ZlY}b?0GV>3|V_hyym&8>@^!6r72>a8*ll5l+ z*f$I8&6;~S++JqZkw#MFBroZ9M$uQvBs$qtcriP2dG!Ogy#XS_EvUXRSc#^{1nnO> zb9Swhs-r1$Fllq_ZEQwntf3ty2x1<_P8H(VdS9S z$hVgFI&d=6F0^M5*hUE1`k@R$lZz-DNXSgq+hYfZr&s7c4o&q*2pi&5MN|%hMlNBz z&Xf)m&*^&Zi}5}^%NgyVlXq6i>IONJ#R zi-J;@6^ajJCPBVOyNUp!T5JGXx-&t8l}+4-u_)1sgzmaa??bR-jj5mzs30svqyP#2 z!NCB#7!?M#xqEGCKTx5xle)6e-8HF4$?)!?TRouf?+vP2n-vHX@=|S`-{`pllI<=3 zh(68J@BEDVRLd7LZjBW7%rCe=7CN_!)T|Nc4Ve(tTnAcsX3_K^-8fFRd%}5{B_nM( zCdhX-Gaht+QKO$l>AXMrEAM#EJe<*|?1Wfd&tkG3Nsk^yVAa|aG-=Hz&G=WZKhXI7 zzLGPNnCpjW@cj?X{p=00>vbFaA?)X_xW*!N$R-M^YCe)jW21#o- ziC(hmKg^H=O$n%iWQBkl1;y5ciGqwqHJwhp#D^?&?<#U=?i`LToEUdwk{0@>ucxPR z^d7bUb62>}`uP_F{}-NFXDTRQj`51bw8BM?+uZ)?kn(DBNLl?=x9mP)o93Cd| zR3wM&@oM`E#1jL5s7xGRL~YLvHvjhvr1!G~;oh{{_5MNJ_9)O~T+iDT6%N|CMZ4;E z@pIGXh75C(z`*y4<_aHZaD4#-vH*Np{dE@?2L(71lph2n zw@yjq!h#WH#MO#`xug&cGG($TLg~Mskq3~Q4I`^z4>MLb^~$CRvf=wJ&&BuU7ug-R z5T6bbom}RbkDQtzX|c%5$e?c7P&iFJ6OI;-<_v_uy#p=8oIMK^7Li~PYOcX?FmfPD zuNui{!@~bw-8@L+-7GqL=s+u6P8_A}!wL_?_%SRZYWSoB8DXufqe($Bj(ArtCVm_I zWO%LpyH)jZm#`)C--=x5cSV}>d;OU8eF73tL7DE4)kqHSTZH>F?*c|)=&Pn5Tev1^3{{t-cYG=suRuJjCEHSf4XAp<5k4x{p)=^=1 zm#mRd#9av1u>i%}^N&PN=GlFCQ1n_c1~&80ZF2Au+rLBLPzZ-4o!Nh`qy=#q@bO1X z>H0^t{d%suxIp@Tq6l_JYw&M48{*yqSRk5zUH{X`UbCG1eR9~=<?5V*m)lJTP0I!u`>~=BB?nJ3!g6QLmzH>j$kNaS+qnq+AS7D^Aw|kB2>5s#@?`T z_(tG+)A<3B)7B6M*NL{-=?NJHy~4EBo+f{Gni9`3Tx6&vL>@V(D+3s=E%Ppv>Jj|Mp_;Tf{b=J?<6ym4`LJT!C&N~8P9WjK265wIBb`inoQ8LnpRKc%C5AK`7k4u3dL zl9~?41QJx2WC4ea8h=C-)24PQ zRe_0U@7^8b<8`EtM$4**c%n-hwj9>JwRgE zFD_*~_(ZDR9ax$@c-gb*^SZbY2*v$A(YWn?Cj{!%i+$Pa1LoJSzmL~ z6h$ol8*I{}-{glH8=`hXPm<)3_B1TG?g>B{1)r?hYUdr7_tAN8bZ?Iv z5B?$zGaex6yMo+SBju&aA5NjZOKPugPivyx7{vPxhGU|vucu+m^{Sll%jEN-A@6l= zw-_sTknv+v!~Xdou1*tg1vivHO;htU;~A8Pr}O2}?t{)hoUr>%d|S#bw8Kd+Ki%Z} z%=~tBNv5rhV^Bpwx3Av;4nQzC%J{{5W$K+jHKpbB025R$7bQmVARnVH%>+l z`dCRCJdT$ta8ClPlT|cgM!U`<{7mUrhJXPR7=>y3*9a^Wgb`V1#Fu|DST}^q}8-Qw(`}yhBm50ukSb06(Z_{x7Cms8JvE>WFv%dSw^c zMRy)y1lDiu7ys-nzK>(ZZ+K)H(hVLk_cGy<5y%b+pZVLkA1%f zwf}_rKq0j$q#*i?(2b6rU#&4X;=`vvSNCbH>52>C%LX8er~A1=f2q>=ub4847(x-1 zWkZRm1KmahzC^)=D-QzhmgHOvk)I&P+-H8k7kJ0tFXTywELP-Id+;APFj>x{0RW{I zx`-s;Z-4`XwZN@B?>i5;%&))dMtrX5Bd>q_BaVFT@tWl`&+UHJK5sQs5ZNx<{`3x! zxp^m`6bI>H(8BU`Cmrej_|VAtPP`IRLg&-%ZVCnDctEt)_{B-P)h@1lPNX$8#9%a3 z<4(Y;*#&>Y{YW+Vuv%;+ttq3$uu4T>+`5qkff0-7f78-+e^BB4?jxlFtE*oge5)Hc zI%m1K-X5Oj$QeA`#q8YTW#-c3#ntmgWV_THJq~$JJRJDs#AB>WBiO&W%3STf{beSx z0kJkoAYRI7c%Dx=kDDXBC8}yV;N1|Gjmx-a0l-;*MMw20dvnyeTF|6sr#8uzpE=|@RMh?=Lzfz zSR&2B+^X?3S${+to{7(&n|w}*wB7U&iYPrfd_Hk?eecO__B^svK)2R=9gypK9nIcu zvH(Xa*!6_B+zyN5H6h^@67_Ta`%^*HrlK$m2uDa$-0GzU(|^6%F=Xb;d)lMM$&Vz~ zv>}sqCV4f*N23GI!87jq+F!Yu;vg8_u+tZAOe?U#ver#9WKcr5FFb#~u2|lx5t^KO z-@+el0fjNQ%%C8(_lw2*$||-RE>ncb^T7}@EiPS%K<#bQx5%MenBsp%mf~c#(_d@S zHTPxDbo4mZj^XkcUJp-dyQ2;BG>hOa7etX(?M7)mXPCQAZo9sKIUgigX1^Xa{DcRt zcsP?^x2|4IuFOt=u|_B{ru;#eA%C%1*La8-JA5#z7YR>31esPzk(1 z#Cw9!L>fqLjbEA_yT_oMMC-jM`4mh6V*+YKg#=XJ;{u?L`Rrh-92D8#sCTFkDQ9F! zCv27Pn_u?HE?+--z9_t9{PWxwwy6qB;xwXO`hZwCpxG#dXCqAZaFJFB@RwyWxO^HD zVR>aNK`und`a|oGV76(UI!VguLSzJ8=!#Z}@Im0}h;viW&jte zr>Yi-3o-9saKUOMkMeTB{c3lU@m7x6YP;T%w;dM9FhbH2a!@nNNu2ya&%JqlnGVp6 zmRD|`7V|8r5Z14!cC;HDP}Zb(TuO^G>S?)& z4NElRz^lKC>L&;aMwe4!|7G`Q-ZvrdQ#itY5iIt(>%W~Z)2&E1UIsoG8nrzk1sp^6 z2YALC@o&RtLGd*(;FH9AUKIwpkkB9%ofhz^72uTJ1cDJ3)aWoINt^Sma^j5q4eBIz zog?n`Sa{Ezf3f;{uR9<*eaCIlz@HD`*+9TJvX`?8N(x|*)b$oINSPn{R`Lq&C|3D4 z%_rHE!8aDcDD^ho@+|1@v^1zHLB&h221nMy>O3s{p=WCk{$+Z(AH4A!%er5Km)^X1 z&F)|Fn8=RA73AQPG$WLRxwR!K1f~4w)xl$pa+HZ!w`yGSTxE1%O}7*P*^(@fp!i3s z5}7t*riokqSCmFZqHhFm+Vo6V$tb@H(~-XC9xuIEkU`c#195pK@2qyO{#m#0T*IwC z?Mfwn_9Hwn#eGI35H2ZTd*ix1O*_~#;ME-|@iP-UhdVBHr!A_K|AokQvn^o0{GExf zE2sg-g6Xf68Z;$m&Tr>4yyaQlp_2#5kAJhicrIwSjNhLeOa`p0ylg{^U)_|q0^iTM zqrKmsjqXn&w3-4=DAX-!1lvM`R81#W8x6|h^e`axM`OF;{$p$Y9;;r`QbF;HU&#qj z#;=7kW0ku8IXS^bSw#Z75!n97Eq$na(%aA1TuU^A!59*nrcH(~MV>t{hgt1&-G-I| z=dL6+blW>yq-$mxOG20tcuwuIkL0v88B1Pr)Q z@Y^f2Aiaq>1;y3Wp^JpIX`BP3jFeO~ma2m<_IXjZ(3dL>WPwREQ@}b}9h~Lm+RkUx z#A7tk(+_sCo(KqBIgoG=8O9F|`XWiJ#|D^`Glv5ww%~Tj{%(VmpVZ4>#SPLn4LW*I zDYINW|1F@LMw<(1!O)8ujtth{wQY!h4o}|7zH&P@tm1yP#M9TiwD{fkSHhS}j zMdEwx6t_#s|BC!&s9(4TRVubNXx&xFP?EBt5T&F{Tn`t5Xd2PLAzG_p*n^y=fIFpg z_&33Jeef0{4xva4yO+d1e=_hjpdAd3foMXF&Kd)+9-0}xVklqnpkegJ&m9MgkTdSK zY2<4Lf2#ET1c{@~&S=tq0$XV2C9x))86t8h6F3OT(;ipi-2CD31mB2{<9rW zO3S4VxzMdLzeOm8=kEsTs)t!g?qlyOBOn;D6pG9Ht#Y8YpYf;VH896+SaNZflKBi& z&=DglD$t}8j*UdC33xbyn2qDN;9r+QxmbrC-neIht_a{QNSVFw%KOp*ZwCnz*!Z?2yOqDuxMb~Y+9F%m6mWMq>*3JDlg zDk^ywNb?3fL3`!*CkV^8htLEgQ9GRhug-+`g*HiQxqH1+wI{Rmd9q_aMc1~4-z&v7 zC>9sM)xdk+|DC@aG5R1r3)~>rFqU9v_xKNAAm6u${|=(}P?$RFb$#=NUuM6(rQ8V% zx&^b-^TTZ%?Bv{R4}EpV6N%)2x1edX~BYRm*~({6j;;lG}DtQ}6(?)GMjMblM{ zj0;(&kx=|Lrv65P)mNiTKn$fUE#yJe&PP7rpuc9%FUjt#&iJ~u?>MtfFK(Pc<=R*_OAkrm#YTYE`#K+33n^JrP26++W z8B=PMUepBF@R^jjz@?M+AC{ix{2IXXpJp2w{ztO})~q4~+a zxhf3icIwg%RlHDN5Ic5@MRYCf*Cb`z{79e021a5KFvVNwGCgH1 zUAu)cI?8BohIFw6xMIvB*)zWbhaT5 zHA}*>^I+dv(IrC5aQih}qq-L0?imQ{*^;{Rmn%!T)_!Y{ao|^%Gg)lVr7_#@qS$#$ zhjK?fvS2Z9t`6+H9Ui#9-r2|^T^YMNW8L58{2awTuH=Ixx$w#U9*basB&mWjj6}dZ z;FEo_HDp>J0yc&tDG56L3BRWjUlwX(QFN56j}!SsXV9zO&atz(;~rU%3=gKjU;Zih zfpN4_x_{o`7AfOx&%1vh`@cSb41dWbbrZXam)_3$o!8z7d>+gm_`RFwj{3bM!o8ed zcZC2a#ZHws*bqi)<_73khYpxyqkK+PDRBd`N2Apx@JZn%mO zJ>>v8B6F~CByS2P7|qbMZT_}>5H^$-xUl%gfSybZvdFW5pKEaM=X}(O8u@?kudAKG z$Wt>G({_8PnNR{1yY;P|nWiifGtYYegFWs_t=_PO3EFe+&QK6m%gw6-f22|G!1g?z z-S+BmvQ=wcuM_UzxCb4Dm*x~=d6l|dc+4XT#r??7x2f>o^yO$pHrjfOg89`}z+A}8 z?E?y|aP~fMK{523ZhN4*UG|9P>)dSAnGh3;GVLu$XKTNNKAQsk$M#=HA*mgRTCa=+7OcR$im zeBT8=5r8ZPGcdMa-8BlSl#;@2VulYv7J_s*U9P&@JU(H+X1_a!1U%kMUMLN`!2}-5 z!)~W0TW{Z*eKyzA4V7B4A7rev3HJA2T^_t384Lva?iqVyflP=$wjHvxCRuP_@f7+G zoqkj&IGyMK0d>e()3L%JL^6V1veAfW|E*OMBUoYx7pa$R|H@coY4m(mDOyo!wnLeJ z8p-Zn6$eu1v?kceJ(yV{24y)q)I+oru%G=P)YbVKO5e6w3Qf~%E*GD@<&kqw->M5p0L)NPQNEGMAsUlhG%wQ z+NEkzuYO-NS7?Ctp@YcY-A56|Z}| zdRcGPADrLoA&LC5$G>iOC-8euycPkux&FH6Z~%;8M7!0RINB%Xe}EYqfcdxR8S-yK z?&Bo};RDB{0%4@MuBQ`{z zdF7?54N?;nJOd0~q*JjiOBGg2+R{!A0cWj0K2kSrSsb;PpoZk4d3eC7XJn8lW)cz~5=$SgF-E2i*4mu7@(X5g9%?2_f3Uq; zPM6aUQ-RkN#cGfMsyna)F+j#c!K%yi2+{Z5?F%oAhr_!K z@Q<`)fU^Z`Yy_2XvQ$qW1lk7FJrLGZy}^iuv_^vU`aC1@-!2}!g!Fz(sgl2*4UaT= zagMI!dV$yaUc&;pNv3Tt+^`J|DYidWYBE2ZStSD|N-@g&fpqHGFa+=}a%arjJkODH zU*>$=?|gLH+0pd=hZ9H2J~$>cE0(;@T#+asv>I4kc#6 ziOdH13F@^0qSZIs3ou#j^cbg5SA%ai%Wn`0kA)6sDaP?y80-XMnRaF~=jfoAzZXxI zxM#&HQ*pphv=`q;Hnj_`5D>O=&v3bC`ERG+T&Zyjoa=%5eVNK1f$KKH**!lRgvEZ- z45%R*E?e?$CW)DmpW+3L>KHzVv(B%8r8^5(;P(Y()a!|{0>jSLJq#{iQ$>A) z+YC|llrlzBP!d!MHk<4*8(Ps=T}6R;t5*!Sg`>~AE36iOd$@6DZD{0_1sl=RA_G(6 zfEsfF+){~u4ajV>m%O(!;a(H5<0Vv$0H?Xu{l!6vIwgj*p)dKb*l?{6UuH|(jKqNR zevN4XDdh$Cn-6Yw4k&DUy>uYf?F@5TjQU|*HNBO3p%1dC0_@c_Kx-@&-`GCb+W1#g z%&8%%2XFM!>hwA21eNy?+YceN;l|H}(5|_-7c}hV^d+L;KHW<*MPkRB8i%Y-8B_Yn(mKlr+3&F#%Jf$2;{mW8)@&4US~WaBKg0{dz@l7< zaQjF;Y5mLUW~k8I;prh>HO)8T`-5_4#JD5wzu;|hQd3KPtbR7^dTv=atRNU3)Zn9$ z6owikkX9c>#eK*ahJU<(X}m=|me>HU)f~I(S*L%TjN4T13O&Sq2q`*D!9R%9L-KKL zp)^5Zomn7#88be@18xy0QFuZrgL|A8G^tg3W6B7aL@%TkK`$gqt6uH*8z)L{_0(%j>*z zd5Z!BW=Fu59H@D@0^ytJGg`zgjxCN&78gY5DBeW{^ZN|^?jgIb_vpP3KLLsyC=2b* z5EW}sb8KM$Hl-jAt+x%&ACNO~!9n^a?Jf+xC5bzLL{1t(T@C>(kjpjcZ-(6Er4loR z(wfLm<@QoM(5(VkV4}@pY<|HBU?9WE~gJ$#%uVAPvw18wzCTS00`+J6(@sI31 zsR6mx0-P#lClb53Lz#GDrvS3_@csQ{&xDAF`(K)$eIyW$e!rYBGUXf73_b0ky;|Ar zO+MlE*Equ-Rm|%>Ji&?4SGTwOuc}v;zW#DHs46KQwxwg2!@FDacr6x^qh;Lrs(lN@ zAy!np{api5MYuun+g<_)+*9@GtR88a!6*l=*C3?0!mh;ciu_9SP2cH!*Z;wCfE)4f z?m6RM96R0>pfFQzfBiB}(NO=qcHpN3)ZvvhbO|wtC zmaF>d%m*aE17+&K(`Lqpo0$uI12ydM3yjLkh8c|EXd1H@d*vVoxE-gnoypV#m#8>H z@wkL%61-8d*4WM9*U}JC=DxXJ$RQ}YQqG>39ZI^^z>7NTMCIA*$L*p5^qy;8RBt{xx zzD=>UPS7gDV##lYc@W88C>stIo+*#dYQ>LYZ*G}kZ@XRA1v|qIBoMpMEwB~N?`gHp zN=y#sqD98wV-LEmgiSdaLSfdrf`U|Jg5fdrpmp3A0WSBUFM$)haFwL#_Preqn)YmHB*d7Sov~i;EYH1-zauRci6@rOh3d;h~xV%*0dK zR}EaW7T`fF8#s^N1E`Vl!g~c2P85?;T-Mg`L|{uv#xRaf!H7nyi(DE@b;$mzWyLx= zQKucZY;YR&SWW6e*Th=+io&Tr@oj0)V65Yzu>R8VNG!1}#h&$DJ9MYfLGkE5l8$QA zAv~aqEXJ5(A#-$d%PNMhWftOZza;4(nJs}M4@sX(AQpV510jm8&OEq$oI^}o&stdb zZxMtQ)=6)M^g-J5!#<6(PQ+m*y!^(%08sqq^X8h+5hBk+YxAFvIaj=YnQ%_Az>H?Kcz08-8I@`B%X!mJcxgUOK{X67KJ0e9r#e_EPDe-Pqd z?9E)EyFGEj)j8k??6JeB)T~m*qf!91gV>;o=zCv#!MOf2oDm^GwXl^$@b)DcGt?MW zH@B}DHskY70WLn&?838H0dos;=g2Z76sJ^0{!@S{k!#Ye+=g=sODJ#$aG7jy>N6a} zDH?khqS}TK;au9X7>b>+Z40?E3Tl<``usdL#r*g&6Qu&V!&fP59b~UC$bsauKd|J? zSX&0XJx#)FDYwo+eupfCe>!1}dN&TBg+YAhh#_pCj|HxUeLic-b~Bww6y^GWt+T-x z`Xn9>s$5+dHqIXv*TV@F8j#J{q}0@Kgc~?0f@;ZSP*$+FE{#&A#FE(@mWY;;|CC+! z!WFo0Q$A6if8hNMzCq=dim;LYlpR+Tt$Q#5v9JtGj9LTD`QFCIi!B8qg{XVfv|Rsz zR%BQ#%&}#yPYYI-8U8BV05kk3Vf)XrE*97;PWnWt10k8Bk`@7oXHiF9l@3vO^${`j zGI~VLt?9)60H=q$8Tr@?9`;RMP890@`u?diS1AE3^$ zYjVYwaP#|=1qTzeV0R%^B0S*|B_2Uz2 z2+Tbps5X2+AnE}sreu770ZGIc;^`aQzpl0A{6#fE0IYqANqnp}q8tBwFrzT#iV%6N z%o)x~=p#|%1YXf|eBb_joN0Vv93S8##!as@G@f;+SALhkOf<(Vyd?WQkbitFd+2&z zF|ysypNc$@6YZ(&*Y5K&VD24oOf2hFX~m%BrbCp2JBlSGq;p^0s+rZ*e%fd z3Jo;b?(Z0~Z<806!w_vlQ;S{nd`8YpRx}x8Ct_GF6b~NYQ34M-XjY*Honhs`^rf6RbfAJXfeTrUf^p4uskN)k9hMDSaso6+Z}G^Iu;@{AMn_ZQcH|kN7<(;w(X=w7*@FO z^47?>%d_*rf@f<$?pCe4k`LXkKe8#pM20+FDRffM=Pd35XRGHE5?yCZDrsLK$Uvqs zpTr9K$a?f{pY_1$s%B_+^z7Ly3Ld%m>;Yy>ImY1=taeELW3B`eNYUJ$6-UzaI$n55 zlN8x91!XV=W0>ft4aNDL3h+*+zsQ;Y z{ek`IQGoj0^j}D8x~@N{WG_W^`6Ezr)&Yh1(Oy|oEW9B>1qFB8Q4yrRCnZuulP;4# zz2$I$9l)U^rdk~uP8{4OdNwS%da=x7&h?&14Sk$Et>`~0W+vnXn7zHh4K3w^H0Lt?5gms>57zALM){>ml$dB7r8x@(oEWM41O@5%N#Ze889zh^ZgkeG@Oe04!z)T*^FJZUq}SR|NufZE3RZ-Dq2R^|rX<94AHqz8;_q zaIsh{#RuGzONr4)AFKt{Vq`Z;Z1sNGQ3T$QOk|NF#fHvB--XuT8-?stGN4K)C|vFiDt{PNk+Hp*&%dTkT^wM`0UVdC|` zn;0TVPze-jSnmgbx@mQeq?x5wwP|aLi)V%;kW`}ca+jVTFLbWREMR5BQfj#sdCjH3 zZ5?GU0!04~j5=6dWX5hY%u$X!*F^}HuH4jyAb9R1<4|C=F>#3&1u;!-7bZ596dUcd z)HZWS!x)Lv58PHv5{^Vw5(|G2)ieaOP3=_Ez-u<4q^hQ7TaHr+Q2?<)aCS49-@2|* za$iWVMt6&Zu4*81oa03G0A!HwbVC`qH&poJUR+O!QT{Q!Q&JX=gu=-vk6mw{CCk0RZD|CN2h;3i!7@PS7)5E)Hn>kLOe-uqU zHC@>oAw@!_h{TzDZOg$!U{`Yx1EEymWoFF!szQHJe-g7Im*?QU8m1cfdJnAf!GgZD zr5h7?f)HH0bWLSKqW2>?w3m1aBS~?mF8G*wBD@xP<5D+%Sgb-mCXNo2z2_tOQl!SI zMZt3^@dC*iUO9Gv3)7HmPt;&8cQ5A%?2oyzFc&Pe#vV;~1Ly?)i+$kx(c(KDCrSqv zi=!tjZ>U%mAhhb}2JV=S2zY61ZW5M~inod+;hjaM#JT(s1AUk5Ot5~=@JOiYBb=Yj>e-3f%3*&b7>)C zv!I=>Q5D_IrA*Mirt7*OF<4aQ(nc2s>E>UJcWmbg2}nGC`7Nn}4pAxbbM64w6(Ciq zRRW|_^F3jzfd%Sht>OgYJ6P>i*3`Dn#~+BO;l?k_8Bj!#N&h7Fq=+C*ZTaYy0_#G3 zy2Ns0&>u-rP&qu;kxB9#%YWuo20g@>rg||D;Hgaf${f;@B4Rz`Qqz0^8x3?LgV2Q< zZh~o+JlM`}l9LeK2{b8hDuC=vx`rkvWs?Kjv;hX zBTy7NKS?gZ>MfZaWZg(rbMl<$vvHR;=G>L+J|YmXSe%pS{&KwA975Z^PQv@_r0#${ zcUsxGv^!q;-bcEDm;%MjA2{N-v8=d|zKSbpx%#jWPO^=f>Fdz%6xO>y7JnYmOqZ$n zVF}YncF*iw8;4$7A``nVzmzxVFeW1~vjGAyuetFA0?M*5i7gNmep3P>UHP360PNZp z;b+fpCOeBPlq$}N)sB?xe18w?O+cR&f#L!p6EQ(_PyD7%*!qe-%w88Zs!C+1R|O@t zSk^3ntoQWFwg!#kk4t+?R0By=617X(eL>V7b$J@|5|vBlkmkn9ipLC@EPK)9@a}b? zwX!VLZP+>9l}>Q%Bw{bsJ$LFR{q+0&ET`GS7ymHs1IJ})!J5TX%VgOG(8`^;(9{X{ z9Mlp_{Cy#?kWPZ=nVH02We@P5s|wIzEM9H!herf@I9S+u79TCe;5rTfgU5@Swpq!h&AbaqoO3TkIAj3e+~2_XyA(z(^O ziHdXuq0vx``T50{tXdyV-;MMWI$Kc=497i=#Q?fD_QdVsiFZrL(dA}3V1b;l$!Zoe z)RO~k+~~P}9;Pe5bi^r}fEQ;~qQyNP4u0Wi_1R*)N=iEpztRBHMGM(=Wz1bfAOz>X zm*0TVHbGoE%xbP#dNE?QXc0M*u`Elf*ZRi@Cy~j&M4d#*S zIu#;gI3cip?V{m=5Ti&)VGNQ6Eq)0>CovlXoEnjmBsk^Wg9uf1Jq=K1nsk|Dy^wzei6y70Pu*B;=Bz9@>Ui_l5(0e&DGKzD#XJ~_sS zUaz+->D}RjDiRXnVopsmPn~y!V4Ku_CmU}S{Lcw41(-zRk283w7d4xKo~TkS*!L=Z za*mu|%kGb*G2X-0Aj|~fdk)Uy0o`o$M*$-#dM6oZDYmG&A7Nk>K*~@VY7l~Q2moxf zb2b?ozUc2ZKf3wVCCfTDlF#O*(}N^^!M;egroNrFga;FoDor>@Dl+nmiw(d$5FJ}d z0)Q4>Ij1(F;G+luLVUcR*-szJ9wbT0dj~v2mus1ABs*y;|*?(bkizPdCAY{z>iT?1i-4>u0lGinXTsOC+J$qXH+>?9!>OW5932; zzU`R^HS*p}h6+uc;eEwk6R=KBy1Jl-p!W`d2BnGc5mi$29i(bg+iC$9Sr;9dBIO*# zbcInjg0yS#?-tqdZo%UoCoTxm9k6##NRfiBK_mMOfVNqnS&X4BAcgJu1o)%N<>{U! zOd5D1(jU3=Q%6FvwKxMCxxT9UyS)0)O*a=wH*$VG?-r6z^1nNTk`fV#5~e#hP!$~_ zQXnO>y|kQ;+Qf)OO_HZ9f~gP5;Jb}E$*e#K3homdUs7VBDJnV}H;4IV>|$c_#$9H* zQ7rO+B7vaAUU<+!BrXJ_fZlmzBIqgb^0o+}vUN`Yf;31CnlPq}U+f#gwA5`EfgoL{ ztpE!W6(7+wW>zgI-vBWC1S@RLYhuE|o+aN4P&6%cP}jbeVcwX>*X8_SxdAgdv4<543YSa`|+6Wx>n~fH^bH3hk6+`ky z(PjEE=aT(4Cuk!IwT|mo!JQ*iEaQzJUosO~Y|%DzGMp}2%oZ&cHS5uBt6{HlUDb6p zAS$^jmz;gcdXtuGF7&nYEGbcae~+m%9ap0C_%|9M=nQ^3Z2<)7MR>s1Z1f03Tz>Lf z$DGvLkzG{BnHb#&+DvZE6dC!{Ur{+d(_-DeC@XRW#s)sOuc-P)_)%Jko8eK%xB^c4 zxPKpR?{fg|WrxEucD(0tA{9Ugylq?k=LtW%K_@^+j^&EFJYvF`3gOInrDwQTj{>^E zEe69gBc(fc`Pw4oy|nTSNeHsYUm**?5=QRpHe_)BW%m?)dMW87XifQy@hBV@-*;lZ z)uNeCs8Hvu#`QZ#*gc%1scRSC9`$lG9mzcdk504ZT!4)f*5DroH@|)pD?-Q~&oRK1 zAZkP5vhzal*9N4O`SLc_fh*AzL0r($7)kO`lAt{uXR4bp3A%>{%`n68Oif}D==>7uo^yNFo=%>X)j($KCLk>Cfxa>iK$h&o zj8x}It}DohlZj-k(tuU=C)~B=sOuU{(`1#lWv;xT-5-v|j>QR%oenI7->p;TUp*Z0 zNei8;=~#{X6Nf}m=ysy-&An*x200;lJ+LTB#MVpN>`Xn+HocPwE`fsxLpHZ$HTsG7 zgvf-MsTJL|=sWu65f9C&hRVcuS+V4F`4DXGrbt@9y%r= zCO3|hTB>EgeHC}kTtrF{lY?8dh1xUg55pcE`WA*gcO1qIE zLV@*k$vkL^2+`S$0)W(VYde}l3W@gVyR?U`E07e%k$t3M$DZA(E8^?-pLut?d@iZO zCpk{U0KEIb2O;=U;az6XEm}zX!Y`~4Yyq)|r!2Un%LHET@* z+o4c60v_bc2nr6w^C#r!r5dI*?iM;@Fct!x2j?yh;&>d{y zYqSB3gnKA@18qwM%uJ#eQl}o7RF#-=g?TFJf-Qbga-Tcufrl802_(m0lIH`uEm>c5 z42X5-g7wHylqmZ{l>HG^*sV$0=F+AE%e=8qrAb0jGUG%53hHNAlqHJZK%ZAPZby&y zI(f68f+C?P(9|E&Wp6F`q|Ga$MLQGx1H4Js62oYkLjrfh=V(eHct<|;VC@%#D}nd=L(Lt z-$Fw*(>la&fmAA`6Hw2`N*F~+{qtzkQ$K)>t%vc%C%%AZ_imz@k5O>!J265-Qlaz^ z*<0l$Fo@T+C{PqV^w&<~!yo=2_V2ubgF9Cc+Xaf!h4LmV^93hmAxHrZV!PQZdqbY* zpTM{F-q!kyLFW-}oW7*ETL-oW0Ig7m@r`4;yD6#uK0TyLe{j2I|EGh4h`2E#?B5=%*no_Z31wuh+}e0GXpM z@;-?gaGdVIL2lfQLh`zz*HPZdGWqb6Kh?Pd^`8Im6Cc25KlL%}Zr{OlI$3g&9P1D! zRTLPF*6=ew@ncA_#mkqzXPtKLadan~E`(fND2Px${GrG2*-w8QJ9lqmHk-P_knc1! zuU{{}r&8u?_?a(!3Moar{L1&Ju;+?7_wNx1LKjj}s%@bSD;RMVWr-&q{{TMssgGgj z?k&t_GamE1F(7pjjY-&|C@~tX;b*__DIg`h^vY{ozb|%v>zJ-Ev+m|~-%-i<3Qs)# z0etq;AII*_ZOo>Vl}z>?lkEUp$W9DJfwi@D{DYtTG>{TrzVsT}hO-IPb;(2W3_RGU z*_^l`6;%&UeCYl7+^0W=-MhDGKMU$}Cno?qH(u7?N#tKQ*1s!weRu8-!tTPedmblJ z0pwq|aiK>8UCH0gX}d)T>kni>-f$$-;dkZUO~I?MMpbh z4i+7lZAd#oHpNjb`LD=q59)t67< zTVH=aZol;+UDst2+BHRmp$Yae|H-YJd|p<4l>IgQD+Ij$-9MpD$BUUs<^-hFYWAKP zVx4gFh=q|%N(n{P!<#Rz<6B?uoxow!A9P6{f3d1f1kJaZymY zsW(h?!-4C%R`}9&70L$4vYeV4O+3VOGUg_hTs(1wWKtOE{*w4^iYuTZpD9HYDA3k3 z%%)S+(?i61VJ*Pr*CLeolT`>HleEGpnuG?xbaF%?w_Ke!CBGrPYUBtYh3q+0!-%x0 zn#IJ%+;C;tC1@*SwQ*5EiwlZD(7ZFCKx`747%?3mpjk|HoPbXiidct7YNCdVR?S$y|)|yTx=Lv&oS*bfxpnIB5)| zff*icj(Ey#zodXvQ)upFGR9(h$P9~%f5P@qkj%kM+8ZO5`(+F#Q83YTe1LW_B?KpB z%Tlf0jV==PwBpgW&jIjp0G?4C_c*Z;&=Z5t4UhqLs{wZhXUQ5qZn{AP6@j2?wVwFA zJ7igkm{H9nqgs3;S)(atyH(=Ibm?S^Yn&M!CXmOI>+`oYS=#-zbwjd#=mSr}G7(Eq zX6|#2R_v3BDBJM*>>`B7)fD-XV&&HHhoIwYTWfzM;mJuDpu%3BYcG{W@&{s6X2b(C zo`}@87Dbm56NrSeQql|npeU&LC*(XUcuI^**e6vlE<(^UT5_&5*%Sky|Gh8-+j>En zO}2)y231sAK$@)5y&b~Qs6FxsAlO_L+-X<}ypnNP+Vt*Hh^RM5mp)c?p6Q4o1@(8_1twNiYsN$&xQsEw++fETnbK)%cL@2ASGy6 zr&zP;EYsuk$We*i6HDh%)E57(d?Tp@bx$C>16uk?7+06>ll_JmoC}&t{47Y2klk(@ zbwd?Vfx6!ei3!YHX9bMGMV%8mmo3^XnRP9Ao+*~y5hy>C5P8lh%)?@VRBQlr##9U7 zf-(vFF6sSE^+-;NO2G}gUYD5sJ#0=mJQ!klbg>ZPP2hM70NPW5mVx^Ov^B(?sZ3ku z47Pu*I*?iQ+vv98`J@iSldt^-D6)^;jaN#28Dr;y%gDuhB|0c1x&Q0;dOuIx{+R^=cdwVS zM;3z3VgPtEiLUCn63vg^mvZmvwyQ11wDBz^H;eO6cJGuC4zw7kkGs!M5;MrajM*1D z?S}gL0vW-r(AhcfbR0(oux-ybl?XrS2qYPSG9X_l zbh%jWYOYEkd#`78r3q0ReZDGuH)AYvu52DB zsU(31BxM4U`n;FW5MM8gS%51}0k|+&Av7pb3i4jAs7A14m*ekz;FW$aReSh(3>|nW z=>vkq2IJ;Ht|w#U)gT7bQN!Hd;?>Z40G+iJVS&Iw{O5RGi1*FuF;SdQinN31nmdv~rJ?T;>mGDAOUf&l4AVqh4 z;T^XZmx>LF9m+d}xmWIU^yrDdr$)O)z)pLaaQ7cr636TM``Q$&> zeW_am(B?`Yc38P>6!EZN(Xptt+w3XH7vc2z6OeFkAxe?Bp4gcIdx_6;uIR%Pib8*3 z?QAp*1!8P;4uU?L5uD8}p|Y+gCRaBM#nAJ$#5kC6i=uL4Fm=bCf#+USQJDjtf2kFb z&3Upl5#j08Zl5CnkgDx-1aJ%laH3ekZhXJb3>4_^iSwPq7DamG6BP9SEGrzbsU9v@ z$lNGV&yg_TlygV@EWfUYnr<_OK$1hd%y{YEWoq)7hxmT1P6d*j0C{GH=fqf6PLp0I zrBt{GH%i}dg6{brxAv8qnIlDi~+eu3Y- zW)gC&YCyq+>2eKJ_XfoHsN*O~y`^U!G5Yun&uEa%36d!Yf#<@XL$)Ad-rDUK0XZ7- zISH`T^aM5Pl99K`8N45%69iq`+N$>_IgaZE2qA>5V1ai9b_0Xt^}bz{ztUM+hZ{c?(06b>;Oe1m^qbJ5f(e;>wtVJXg{M z!Vo9H}g}`8BQ0%rlt23xY5`KSV5HLE0wc_rwchvUGjX(<0#Ku%1S*Nt4Ml0!5}QbjE&+(gkjR)w?zyB}UITTa zWDp7wHz4MkqfPlu;-DNeq`7r3Bp8STu!@578zdw<0x4+1S&X%!C=|J>81(x>cWj>Y z#`ETA$=lJoZTGqiq9bJA6(cw{e(37`{^kPz<@;W*ch9{S^sE2?AOJ~3K~!?Z-gqL5 zo4Yh}2qn+7T9>u&Eam#X$Z}65X+coQ#**B9>9A*rnN383bKqj!&4{Rec3~zAuLZ_S zyo12*EPFADp)9fhPeKL!J*hv5Q*RA&3qv%of!s@C`T-!ggqf5g#byAo@^B7uL{FHe zaFW5X&^gHVNU-}Kuyh^&xrJ-8-stZgR{T~rGQ@1&Y3V^GykilRF8Az7jqF=TP=(ps3ONEiaQ_7-m2=L78OM`z=Xe(O(0=>Ojsw+#F=6$HtqI>GB?ClAoK2%h?F{N1bPrd*nGQdA_yU$%ZLpyGsWCoZZ^JqcGJPTvaN&M+z3lXW#*7dw1HG8H+Xb|Q$ zBX0-@p>n9i#-%%N#XcuK=NY3l5h0|(9fskIezImoN6^G%YuLGGSIPtV^R;b@s;YkA z#vmtR9l6282ITdYcRIIoH2|H>>ZJ*N2){@{E4XyNliw*)=p4a*lV3h8!R>6M!F}3+ z{hb@w7(3Sy$z^r~K*D$PA+i)w6~^>aPgx?-EOEt2F*?Eh6&#bukwyrmZb0=MbnG?~ zAR~%FmKdbZw!FUjcL!$sd-)A7WxIWaC6UBIAK%tiGafx7PSG;=oMbd+8E9@(LP|}x z&#dxqYxnVl9$V&>j4+ByuLWi}#*huMs;z{7o6DtK7y-B^a`x}5s?y5AGJijG6~OEE z;`WoqATro4{J-a*oWxl=FLkWXy7B2>mT=P{Ah=Cq#Nt1fj~#;O5H!K@j%wonRz9+V z5rppNPFkz}=kM9Mx8KacV6(Xgt60S!fd!%Q;zj=+3V7vboGcNB#M z=za;={h`lkE@OJuqGVSk?-xNd^nGbeDJ9|=Jby6@*veG^?^*NqHX(lREm&=5d*|as z2tcI0xA=EqNY`SyG_bJRJFxob%2WQ->&60eRk8q94`9Uxc4)vhuYA(U(8>TvvhS9L z&zmQc$R_Phh(Tgkpf4~xg;!_8;XQ0vU+(I72SQe z$Gc$w+2~dgwf9f@h(I~sIS8n;Ym!a$^7jV6Gh<3NQaN8rSBn+9a}PfT5912xR z3Z$XG_y2O@&s~-#NxQIL?xp%9|LuMD)=Ljc{Ickk{9f z`4%S2%`(hO_QW-f&|_~DuYIF+ror{7&GPiq2PG}Jd~%kOFGZ=dC7)Q zu^+fG>3kDo@Jv$XU(^(_Erd{@C^_}lA=Iv#?bm-*P@NCG_rHI)$FY-vx|081wE*OM zKVbiTa1-GJfyAxjt-~Y{eBhCsC$;`%VwZ_lH+xYbl%@M!?t+reHo}pKW-N)r{`t>n zV-#gY9b%QJN=TAP{+~w#BHieo6f7ih25ebpI7wY*eG>|ME{jQ!ClP}jM;1C1!9UbZ zS`-yRQK1NxG7}+Q`mWGt7ULfwDj35RXpBT^L|Gz~C6~f3be?EBd9$jzBa&GpmxXQ% z1GBtH0oDbtZ?KL9T2tC3O>L9dIxQ$#^LySgisQPP2 zDWXkDA#|p-z)((5xS6;#Iej9Q`w$9*vO?8g$6#X%Wq*WrF+qxnt0}jxF2CjbqvJm( z-J0PrM4FZ;sv(AhA*#VTQmhf9wAZFJBdHg;GY^H)0v7zD2q`6%&eVabLNz>v;l?>s z{WUa;2?IA~AR%aAP!K5N5mu5$rVWbF)BRI$Df;1Hh~8j>e&3iaLhM0-$6Hh+kg#Hp zAh2!@0HH)tl&A(97;W5#gZ`UH3ug;JK-F`djb&yXLHj+|Eea~U9JE3V4OPAD*3n5jWndoRTF#FJ0q>8GE@SAOj`Fgw~-|Fq17 zY>Mv4%@WU2RE<$UQC1kOZQz%G>5KS-t^084yMIYVB|Gl36_bH`3jq4Ata^Cj$*1t8 zr+*$_`L$oi{AdT-v=TvyU)DxF5h*g7&m;DPkrzdY(fVop%0K!Net+w{joDetN!PY( z32e4?vpJDC`684kstQj$`B8l7=`Z6eU-_5R@U&Zf*+rB>4V0!fWMk&%EQ*T9{4{=V z>pon1?k~7xJlBl$SZ;+<4-cRfld;1b>qq6SQ8{h@rFx%H( zL^C_=EV9Q+<|{WHPa@>lxDpHjj7O*Ojc2}#7oYtj9Bsdab}_Y0#|!uF+$bio5CY0- zfEVf+kRtA0eICv1kQl&9LUgcMuj4nrgN{XIV}9ljad`JlG>a*3Xg8WrIK%;CQh;t$ zvHp`$j(RdYU(Wyl+gG1Q&0|JzbE(WW_p^87EBtvySz)|(248>XJ9zP#Kf>YND`*!} zMmH(@l~rVBW|C}%{Jmr?%W8n<=Ts&7?px2Ho{x!01()k|hm0trgzJickWRqNB#N@H zV}9wGKg8kon^fGjV{|p9V%PTqL(r(A5msYfY!*7ti`fC^1GpeHxu&jXZcOi1{qgkH zjTbnHC*au4LH!N812%V6$kYLUo?S~yBMbbYWQy_5HC%eRk9I!MTJwWOq<_F3%8SgJwR)%a^`~@$L<@%^cu50A!l9Ahi~Osn+Wx2{%!hh-cj3LZrk*1KRlnuUvW^<6UaLl0Zd5 zDL%eYWTB`rL>qTXdTGs^D@v}vmvFRw6|cNBM6;NoZ79T+s_#gC0(foc$s>HYv3Zsx zQHMMUHJbStue^F0N4wXk;;<_WD#%QUu4zz|C89uY$5{2eAV$?dV}7xRdS(@eHKhbZXvZF@3Y;ADGNSap zemJfy+%#m+k21q*q_&~Tug=}36BdF&Ncxpr+;M1=RPtwxEmAYraZ@0TuU9XU*dVS9 zHOpnsBrR{cmJ(CiX6B?%MBWdN?qI*y$)%Uhl8FC0HVd?Etx#90+<0}!2i^onK!U93 z&eo|YrHHnfQ?g7$g)%g{E?VVYKNmC)Tz(r;moO*+dKIv3=REE?ES8?vQ8EZwWk~?a zvWJ%YDVnj=sfo<2W4oX+*9*j0t2(2JKq(@GiuPm(hDv2UlEydEO7Qhtmi}$KKpUkD zeK2P0&}Y6zw#j^goe+HY_>qMId;LSKlYn(H@kci^u&S!=#T+r&E{)N$uY7=u{iYiq z;B<4lc<0rPU1Jg)1Y4z{JEFI6Tc`$T#L9>uiMvdg6LV}AH!fq%BiTCPz)8=9GAQ0q zIWwfOYdpL#CzXN{*=K<&V)&BJ==&yn7Rxzsx~_~-(47);*M?y(GP8rEe9ltT65BK# z-d^H64MVR-LumEdVm~k@Zr4-k8UhiZx&<5C6(Rtf2TIl?v3d}7-T;Z>gGzW>8)>>~ zwJRBMMYC@t@LaHKQl67!R6=UorRJbsl6y4WH2`+q@jns-;LCx@4ePqjx|VhaDYXKZ zj^GJTf(%Zl6q38JQJ6oU><)1EE%ds|DrLini#2-&F`-Sp_{;U6`O?rlKg-ra_sJavh5mq;yFH-3dRYsHK9V3v`Ce=~GEW+F z&+!ndn`^Z1#r?l14V zUV(dM>3+ZC-7)|jrEs8bMWA~HC5Q*0tGn0+%Lz<3lanCkGU51Zfv=kL?yfE3-dTrN zPqc?vQYSu-+#?CmQA3tq^GP!4FR)p@bK#)EcmRce7W>Y`6N+p>gI>cS6)`xnvZmn`Tmi?5pVYE7~JcxoV zkp?okW?2WHDTj7)1?bgF2FT=eCOH#UtDuv?`^@+5hM){68#%ItP$QWB(E0-yxdbK* zWKt5rt`)@?#s5X$COk+>a$$_jEL0V6k1e`1-`QM!4qcR$N>i~J2>kb%hw5qJd%{Uz zLq3j?M>BFNlZ&EfJ0m7DLa|{Gq33xL2ti}*wK7l2Bk|0$VDl}{Waog<3NIzAA_>NT z0Jg#h$AI`c^0_>pd>yj=lQR$LE)=-FV4j|x$It+|8(qHl337hBw)s1RO>d)qmiO@^W@HxW!tN3LwU8m6h)(%3h4L*@ z67Pyw(Y+)Eq!gC82JJqHqZkcc1$~teUawyZaWM=>A`}p*nyv*2!LA8LW+L5FLgAlN zAKc9^r_u&UWT>e+;>)*d23>)`r7=jbS;s zD$l`ipCZ@fxoj=S(CB*!@UsQ?tZpu~XPz{|aOw?Z##I_`pU)eQOJQyV)WvA1Lhj=W zg9!82JqczQuIPxk75y5flsLJI$0)E}_OUQo4@QG{mXb1;;Fz%4JY*dt2B85$POp*% z>=E>;U?7hUoWby26F+dmH~_D6h+EKq*7ZxSj#ci$d3j}ml7JezGk+W0Ir+0LLp%DN zLUgBsWJW}lguH0NjOavujmAIVH#XdPj*)b9MQc7Ob`RV9h6rTebiF1?^?&+ZYYZaW zI35Ca=0F=#KnYv*R*{^JSQSExaZxs!q|VAF58`dGc~F!f&=)R}PBwmw!Y6DD4apF@p{g7%c5vV;};2{U77y@AF~#Flyv0Q5H#s;!F!@E9z2 zuG0jaLsee|wIoJDQjV>&pSlF^@8J!C_bS16oa5L=K)Io}1xS6erfHUX0!kO5?!YX< zO4>;=gm(SkJ!1$Vw}6LWwbkWsyEb$I=uOmQ7YQpB=%H! zAQ31TenVj~_LR$R*NCx3j4kWk$&iUoX{kEBSo#CoEj&Lnn~|svMYdLICvmn9_!*Xc zA!);EUl27*9zSu^A_qljkW|v&K-CDx=#w-bq5UC@P>@?pqTic6Mo^>Lm5@vTxTC9T z2%Sl60FWc@ALKZ8AW%2_SB|nQ6}tHIWYFD6K$-;CO%}mT2D|2x>!=H_@7t!!EvTGR zuiV|BwSs-wEU7?Ic**BrXW@U8sMQoFcR&MRWSpH4+)yB}E-B9mg@e2?6V!qoWcVDIphz*I zsQQ^6o}$fjC`yv}%mmG5z=1_#6JZa45+bh`)l%TD!cvl~C=u(0qA%-HAmPt;m}+FI znY?e?7QJ3i>CCt2JIQgPNI)5sCw328e5oQCbT@B#F+HR=D^fP+lReBQd;F*ARL0;l zQNh=N5rgW5xwqoh-px5~S0Cr+=}xG{zQ_DizSd%P#Ltv7C}k{8CGwIAD!pXP z9{D8PzVa>1CkHxY?~+tv7kJ%yhK!K-dZyw8)Uz?>lYRY8U!1{Bq+impcIHlumcoz` zaO?6nQP0Lkq?%Iep52C&9jgM1gh>HFvzVfukCizqA?kX-q<75sm0y=;G0pbl?JLh> zF*{^IEpAn>P6ZuyW`y<6kR)Atq1SBzH1jFull@!`QQ}`#*#%8oR41L|jq6_3mhVmy z33ws~u*z5Ip^9e?-aX-oYChdxzBnJZ-+1P@!+KZ859_)=%xnKp$9(14AIf#_y!q`P z%5{Ms;+U^|>xXh(;P~_L|F?HGJ8mRL5DuC{&gftt3@qTg4tfkfm|o6)2|*WK1xN$K z-jVDPf7^#vy@v$8THqpGW$%oXR8=g@d{U=qY7D&XS#0dR(2 z(%@=8{!`}XaAC1FEDLk}0t`FuXe8Lp(|88&J7XFFB}=i==90oc&hYPBZ; z-l^0^OGEa$zrWX;H*fU%^=nDm|8Zz;`w)if2>RpY7%0b0R4Qc7yuo5_m&fGYS;sEE zNE`BVwlgpEsC-R^r_!qan3e6$w$rBktg0mG^HguSpRxDZmbCYwePlmoySPoZf%~Cv z(g#^(^SFMWc2@R36nFex&hT?m-K%Tgzkk0^`fA}_9zT734lceQfDPc1L?>gil^gl) z-Mb@0os3LS&m%BdxgV>GYPQs`IWOU>sC>49q{E>~)v?}owOS2UXK@*(&vx)CNlAli zAZ$neha!he^aIbP99>5-QX#5)GgO4eZR`)WKd;@W$f(*s{pTw{xgCdt^dsZR?ag*) z!X_!wA3R^4q|LaMw7-g#iNkoSe3Le??-(<*JNuXIOJDjzIv39*_w9DOUtC8Uf2sno zgIP@-I5wJWH@0Na`(*NDz%tqL{%HP>H15x!owlp%`hQj7m5OZ3So>IYt4eQz;P>p5 zF~(;5_%N!(P8)g8c%N;dZ%Bfb66m`Al5Q#!@1JDA)45gkB&f<56YO(;&cac}M*1!% zpQ%DQdBz*pR<^R8xo`fKwx{2?KOdX@&Nd{mFb2G8mbRx-BQf&WybS+0R{)g+V>OY6 zfuDhpcQ@s!LMCC?4dp#qku0C57snx_kr5&Dw%;Lo_|x^91Zj^AfoSL!hfCgU=TO;tR+-EVysp!0#)5KBmQkdv1Z#ttw` zI?km!@iWS}Of8u+^mIM0W;{nfbB$d2W6u9nP1 zGP24r=)8Xi?_bKhoeN%b5EvYUU ztBf~q&pxgCgh|fWaZEDllLQzC85`MVf6iEBAExhY=F!ea@TheFKDeYoqO*h8IV1-r z9UVCqnEuT2UDBZ9_(F-X>cMK3a=5DA*=BZbN?}$TR%KQr%0LGQrMnY0t1goD+X z{pWq38##&lpgzx@z1#{exGXOT%0`zu-$I8TrRa-F3T!sxyf-|E&`$~ zt|Yaq4JS7$3C(*Fc(Dw@eg8R)x;~WA5pH)vf1f^BaX0y?JKHu-#_D`o% ztyZgnDx$3nT-rw17Te}yUR_;jxm@mtP@BzWkhG}~+-9mJ+m-&HEjh2kebZ0u8z!Q3 z7$TH@ToXk0cTW=s{NeaCt6Z`vpV`nPkjiEf5tENhnqxf% zb51V#_q2h5K^bcsSYf20La`mo<#PWW1IdBHik33c$6l>ggW*r!x{dowSBNDl5F#(|5-xj2OY2EDdkueDe#26z3ngHs-H9>B)i@Vs`wzhTns z4S+N0JSH7KryZ4)Cfq5m5=?da7oyijJ%MH$DoRIz>{d~cmK`_o&7wPh^gARJymLs zxj9^8EU^#Rmu#OuPi0JUaSTqU(|u9MqQqhJVzH=hy!%}_aP|#BI0P`V!&7<>EZRAT ztjEA~r*J~e%uA`12}QOiGg4LXI7|}EqyGQ^1{6s|K~x?lgKhM)6DeoID|u%ySXo_+ zoYY*cR-@I0ZDn9()u9B0`5BT~r(^RHd`ojCP(p8Sp&< z=;C>AlvncE`RV99J;zRFC+6Q9`xx$cl7#ok1W1`}XVY0(#dwU(W~1xt>r!U={bVxH zY&JXUxU7=y6d&6Ydnv)$2LFzei|LPtf~Tq=oG{9>Ae@Ax|I!`@7fDXi(X4vax7+Pj z*Votk-^}Op!Ga^k21(ZmoMg=53S%oPbdsfg%H#1=GHqwth*(nBBnJ(Zrf<)3-b z&$id=FRrex2JNQJCPl^-iL)5`b}DkW!r4QB>2#`BuU-wRp`FTe=nu)o3|z|XN=hCX z9RZZGDvvw1tu!Q!w98e6_E2H4OI2HTS?M>oxw`G zQfX1fk~}!9VsNKTtaOeUsyo`3iQ?awsHq6KfQBmIp{wag*sStNc8tl~E=ifggCu(Q z6DOl>)6LDz;Fu(8w##iJ>G2rz`8!HSXzmXeI0 zr5Z?QvO{@XRx$6p8isJ}n}p>+t)wEEHis4TMau56cJccRAlk(3X#=YOk4wK+0nGhX zEAcW>awS|fbYpR= z`?=?Hrkt5)W`5_KnP=X4p65?9ZeDrkN29L&>d!vj|LyBWo__LXTet3yzxma-n&0%jv-;d}!cYHs_1&AEd}sK_@3&aJ^5*HgUZ1$)-_~~T)A8A_ z|9V}=2FLwr{g7s@s|;*&`+#qcn6Nx-dFSUXcm2bjt|#0&e)_Dhw!UxWy3yNK)@oPM z^C$1jo-^T{o1gsMz8`&Z_Pe{n?waGDKX?APlefJysbTd_7dHDiJKfe2%%4^@=*x^_I z_)q`zPeX^S|NX!G`}Sr-bN@PRY42XW{=8t#X%GGBPk;L8>-P)|Grl=zeAOm1Uu)m= zy^n`J((amb#&5joH*>~sy!kipfBM^d+Wg@EzH-k$HT~8Pe!A+}x3)~Y;D0e?&;F{i@ir2^Uw!J{=WIE zk52uE-Iw0-{OqYmt^VyXs~))fh_(&eh6hId%h;-IpZ)hI-ulDzKmTs}_kUe~<}r_d zv-^>Mp1R@k+}Lk!T=%1)XIFpip&I-Dp~27Yy#K`?{ORgHzV*;i-A?$;v#SOl^Xu(1 zkNm-T8@qIy^5#GP4&c>m2U)(zW$6xs4f;LCq@Vj?A+`Osnrnf)+ z%Ngr7ol~t-*!{y@qXHdFL;+{&L&>8z24sye2=o=7D!6{_f5G=lN3zr8p-U*oma zRkm!|Qswqvwrlgs1)qM~I@+xN`#<{d!&UtsU9x#i%Om&fd}-Fz8$X!!>fWEUX;Jkz z-(AtBWbzMh`AxNIQ%^c~+(#dN*1vzb@Lx85WB7+}T-f>g?nl_)_|a2FeQCtvE81Il z6A4H1-<;49M;vdF%D>7Hs2qXH5vUx2$`Pm>fyxo69D&Las2qXH5vUx2$`Pm>fyxo^ z9fKo|vVl0-=oq78jXqaNf0JVXc3=pWV0x$xAo7s~@8gZC8rl1ZkEm{R(tiVS-T$q` zxc+C;iFS=DMqr69fbGHlz#r@>SJSAzQ4^!)MlJrvi9Y;CsF{`jTcM`bR&Z-%RM+Ta zBWxLL!MMCXe8dSxb&OgXl^FFg8e}xoXxM)dajnCpAy(!;lLlM;|CPj;e-;{O%iqYFf?;3qIqpunNpGs=u z|Ihq=!{yPpLScI#owm_1t*ljrlr}OWT*qOHS8HucNf|I^?T9 zIOVCEeko7&OHS#?5l%_vRR*5crFN?>xMT?{U1{o%>Q%nVDP8r#NdrqT1zRI9F7J=^ zL7SR2Yi_;ny6eKtH{YD6UAuOb3h&&x)8pc4K54|$H2Mvtb*ij5`II4^hLtZa8PzY@ zv~FFitTLo2Jq@dTnwEx@FTQ*C?tFhF2PYp+KJ6f^HkT!rmQh*K)Gy_yd1=0`p>|15 z@oTTWHW;Q`z}5(i%ljj*oylIaY15_vU4HrHPJ}mZ+~}k*c}jzC-n=<%*s#In({zPZ zF6GogTy-ff8P%(F)g`X7l7}m-I+0D=MLI+pd6H9HTy+)KCwZmA>3f>4u&#>?X)2?9 zuCFw4r6H5brEN`RD5tXU^g7A|Q^U5cQAP0IYG+x(RaadV;9IwDb$-n?*W|;b6NjfV zXy*V+HsxxA>YzT=m2!oV*EQ2?s7}=Yo_gztjbF!l_xB@G)x)!SIMW>OUoduYaA?{u}<5d z>#2-yAxIQZA9RqGncYR;m5D4p-paGFmUNcqL_Vp-zIh;v8bNxbu8%yGK}K=X%4%0yzWAETqYja-t2i(JnaYsf@z#;{5d5k~`eAv|Jii`^42w z)nAq@X=*=VQCddj#3g^rEw}hsikDXgd9_KhimQ#PGrb1mOFnso4;O#cO`WOcv=r><)x=`X}B!;G`~2U z%BNxEisLFVK06-_}8vo`x;>l{tAol*|R5@FA2vTb96ZV z*hn?19Oq%u(sad-%jA$pRxKwR5+>1 z=RAI_r5$^;k+_ACBTe#oS>!~CJZZ-$-8k}+Q+mo3MkcLW>F`qFqhh@J37-qcWZ`3u zI?}^naocUT<^7K^gWr1Vt)33174`qfQVzQP_S+qQ>W7Hmb=O^vf8(0fLepB+Lz7z7 zLic8MEL<%#u6d%z$s--EFmY&fr>3DDb5NcN|O|>jeeusuNJ&!mtd46IH6R%S}(y$JVqs)oNhngAHs9HMgaZ794phl== zb=0VOoXehMwgLWk+;NBF14iH`yc9lM{J~ju-g)Pp`8ugDk3aYtfqT#9bsbZ%6P|>d zIR2JqGGGtxO?8I0 zjWV63jO&SEi;VD3IrVgGR5PEC4wQwzbU@e>KTQ0$f;~DQJ%CdWocRWwlRblJuMB_E zWb^2OWbhLZvg1pdh1T^>_VILiD-+8V^gud4nX zbtL);d_l1ukcM6%m)8&3y!64ak*J6X^?mpl;#svPJqLpFkdZ#dW~`wD2Q%^v{|ev#*QrxyD`iB~hdxLr z?z!h4#~*tDQ^uuti+Z6&os&FHdRe$1h=0@x=>R@LxVsKnoVxKX#8a$`!{j47zNES9 z74<|NsOs~9<_zfqdCXZ#k2+A8BZx!z31r7)_-pP!FXT7S1Mn06$TPRNpUCtBJt1Fn zM~qkT>r;0uAAJCF(PtFq7JLWwQ7?KRAAuee<1ehhk1!leO|C-wKghDiqZ}Mcb)bO1 z*>j4&jXyT%l-axNUYtDazm3^HWu-&X0qKqA6)^2#_ZJ%+LqT9nuG6uz$}p%#;p(X_$JrKKOHO@c;0QZXdmHonxHAlX36UGTOiJ7vAF1 z31la9Z64azui@pqjstWht|=(n!sL6IZz+Yp`-#-Ym@Zfv_)s_3D#%By!9;5X!i>A? zmFW~?kDY71;PC`|$H>+!Ei?R6J#ihdvhoMQlr<}PvUfPxWA~&>htN^hL#k8q_<&;U z(Tx(*t0)ukM+d+kY`{qNDy)?LS@1^(qzA$uzIX3lzb4}iVaro`w+rnX)bu!*firn< z5pmXi^9FZupU!wnMtYNS%5}B=j_KCgA^9EGCxp1Po8hKoAQ3G>W2$wj0n?ux6R}%ADN7>^}M`=>F?Q7 zhPZzr&&lQk)|h%IRv5{oHm_5b9Cs}y1vI*3v{;iAXtDq*nl-*aixQ)$yI3nM;04b zebl8e>FoP)P1!Ye46fLn(!{Y*!s5tI>ej++wZ;LqkDW!EEpX(~jkr#THmq=Ji~w{IbaBf3vrkm-ukQPisW5gqTaHo9h((cf`>#&M+0O z_yWom?iHdA$Tx$r%8lvL)axfsI#_{&aAd8gc^w&W0&Dqj{I$ZY6;zJvkG1(&QZ;LD#OLyQYu@{ZbY5ry0@!4RjG%)aR zhASAis1tSI0Q^lh+H^q|q!YqF#t+0_*i)W%V5eL+;u`M}BHpPVi2i_ifO09`vTHaL z%f>Z2^$Y(Ldts?G^a=b)Pwig%P(Ry$B)=GcbR@3>gbnkEzuh}pTTLmI>Y*%QxWfEKk>U^5vVX>&cpiTXgB?T~c&7N1R+in9 zMj3R*_m(Uyti@&j^7EP1RHhy6B*$yjt@YW zs59bJSi=c>VJlr>t{7)JOPk~$s84!8o$~dR#s0~U_{Vz$umEfD5%$7Pn1QLu9TxuS zZ%%1A#osXnBlG`^kJ~($Cxnbh80GK(kjt=^9uQZUa?ws>ny`lpcX8@wE~Sj*z^;IK z=`i@aew2Yf^KqeEw*O=OBn|xl17Rrq#knT-uls-JnOzf>9Gtcbf8yv5GRzSX_rjh4 z`K&YXAG$x({4e{LorjhBkGPQHkH1pI$H+y6nHeCd$L; zkEjO)?9n0g3Qj(_7L)Xa>pA|`c9%8Wz)s}2X1Wmlf3$z$T`E7rpR!z!wt_$HK<`|q z@Bt=QT#h*R7078G!4IVPr)!)_=m8am|NKhB98x zWZ9$R{xY9$vW{WK!A<&ru3@Ldg=fr*_m<$UdT95At}VPhss4FcbO61|>LH&!0P>gv ze2rj!04(tL;0#9a;_%_vfBpLPucn?y9(ly;Ll+?Ggrirof8mV%gRL+X-e93H9Qy|& z?*EmZ>VU9>gE=y+Ex}V*!(AVcF_OP={JU|VURZ~KGbAh-cWh1gQ$G5Dg8d5{IOUKX zm;Gibe?hwAmEoUajf{Rqwq_#)3AhUi1(wSpN$f;UxSH6aO~gpO#I-)Fu4cYlBX)vAOZ=Sw8kkgHO)#CwI57 z(G;U;JzIurR?G~`NB0O*dc<_k&tzly=2kD%A}krvEo@#m(aTS?G<=71&zD@3oo(Z| z>%v9e9+g)at}(ZNyD+s!OJ5^QF@D2YvrHG;c$_(yy#pr;vyKo^cFP$v!?Kax{o15Q z*=EL}G~PTc9^Nf%JZ*x>w(z+f`xcP~`S^5i8D{ru7j~~%;x;ka+BDhHXdB^47S3}E zql4SdnGgX8#p={>Sl8eZTxZdO*7H$A+-oHuX;qgKZz3Ii0xd z8VZ~5{QaAeT|e%FaM8RcYy(Y6bIgF5@#et4`; zHXMI*qP*&)jo3f_kg=egh&;kvJLLmyoEa~*m1~1P>wmC@zz7mv@Xvz(qmMoszVL-F zt-k)x+Dn^7OmgjH}>uG2m4to;l=uodNh}_pTykiYXqP7qyI)HnE$DlvXa+)Ud)m2Zgs~w zUiTbvEkRk$3$&3oPc^&7PBczh&(Lnt)Q9Fa_c6|-Q4jTVZQ&0-qGBEREcmMq;jTD3 z#x-e|kH57?_kS99+NeCR2gj%b;2(egkxi?=$a`N6lXTAGcfK^;^D-I2x+Y`D{J?cc zBM&UN&Z=?U!V6a{3Co9d@;q=n0Dl`#;V-T|M(+O#_(wV6uC+hwH3*%Gdy55KiS%^nW^4H`7KN?40a2jD6G z;6i(R|2MHaVUqR%92i4v6kjczkV)}ne1tzTvj2#ua4m4kQU`u9fB%=&0nYqJddspg z;oUt~glpyw_W4TqGmg=gqCIi%ChWzL75>(S|^E|_jvBEd7{{v3M;}|AmX=&0I)lI#rY=8T`{L!_u z!}p$hAlzztz&f6KbpJ0L;Sn?A8J5J6Ew%rHoaIN{b&p=S|EI0^|Fpj7_hTREvv6fi zaf+=$SqI<)3KZk`w75QsI>C7X&HuEEdWC;6M#3KcS@2KGOCKJ4?6Exl-J99j1UrL( z9_aTU;VGW#0k{Yw$`s=dW>8)aY@CpZI2G(&*hXE5_@e{V&Dxx6lc)S)c6R@X%T5b_ z|MFvD_tH`B53pm^o7#VhyYQnP$KUElmUP)Xdvb*HbAaVJ{OCk{ldn% zzhym-9xxZQ&p2Uhqz#nm1!)7@Hu7>B-xPmrQ#gr(A((v@{HcSoqAz~&i(bF%AN;%7 zeS8nwlN`}8-v6szaLw!VnYeTwjEeDh+=V|nkP+jg-~Xf^;u=#gYxOg?6yr}l+@tN^ zye$0vD_;yZEga_fQ#Q4KFe>)#1^f#5D@~Zk{kgbTOB(sKC$9gZ4pOh~FX#{B0QTV8 z$?%`qx1H+&I)FZOu(^@>k^Kz(0%69BFlU_51NzFCqHEYc`}4wDc%|{fz`r%J)IoXa z5pn4QoNIzT#9r4ZTVMBVZs(lrK8t<{bMX{^_y5)x#)0vW{}$$Xp6Sei`~T4wfMHse zF{N&<6?G%tb8%1j;x#M7-@p3BaO0_iOW{xZgnQJ1Xy3w_H0qN5D;+-}+m|nZV~^mE zZn5U`Habqx251}po3?@@=Xq!MZ}0cR=mPk|S+l@B4ZSczKZe?~Lg)i^uqL4WV8J~( zxCpZpFTxrAtxfiGM&7(%HcYNu7ia$EQ zmTx@_v6W23IL6&i*qJ}Dras9ux9iUC+d8~_4JpWkR?VjJTIdduemFN3B zwm+2bzv(P{Dd@TE-|@Hc0rPw~!w?@TZOF2lb*K*eSSybHNwHaMZDC?pGLdWThvPC64W*FUWJvzO5P* zbP()~uvgY|)FWSy%`6z)$;Xg(F_*@(t93jLpGsOh8)|2vZJY^H9vz}S;V=6vw%5bO zzhT3MSEYC>Y#&$4`vk5z(w>Qf*gL)M>^Z(~$vog>=?Wvm`hc^x?73aKU}BhKK94h_ zJU5{8zTD>(6X(iT&l%(U3uzucL2=H*f<4&YFn?$m(ymb<&#rOKs4n51ySId&eC36( z%jW;Fc9xH8Z(Mqc%TgD_c|a$^39+`lc>aVi!{+I{T$0aRuk*q^O?I-idClB$u3vrZ zI_LuTZTu#{`i(t&elHnr<1)qW{qDGEiS6^Y3KP1q-`grovHgIlmOjPK8xx*rd%xq^ zH#WX~)k4q14=@)Dvh{%Q2OntPzJ1QYO=)0iauxFb$fotE4yB=AToc_zKYO>V>w6e| zTh+_!Ea|Sh&g1x^*U%i>hey{7_cnz+PJCoa=s9?%X@v0;?o(_J1z&UVq@LlOJM8^a zQ+r$8b@KW6*fU3T4(~m%E&S|**TXJb|NDNj)iK1*tK(mo1EA) zK8zP*hYl3uFMLQxmT_4^F(58MkeMDEscy7No;X-TcZrh{UzjbAJ@80XfgKOs6+}7T8W1#tn z2{s4NUfPXsOz{_1;=&%AHT!evu>g^kntgw50Y2GWVI9981D zf^mvJ>t_0nKb&hea^9G(;l>5S!izVq4li$C8@~R?&ah|YG_$3~u0Qwy)}LJG{m*X= zkFK5(-oAZP*k!(gab9D3v)1yin%O_xdd7tC$fb+J{%g()FKxdlJhf$IxM|54Ustf+ zj_)jp=l_Ja?)&f`q`_$~3IU#&u)s*n~+L_^*%T5i?ZCn&yy!wpr#`X)tyLa2W{^ksHf5-fwHR2NUiCbn3 z2z$?;?)LHO_SNB;E$4*mZOyl4Lih0bbEk!eE?MYyjQ;?C^o2H{BkRC=_Q3G8+1493 ztqpJPTo+#6w%Ra1H#~gFqOg1U_;BsqLE-9I*4_m}!wXjCfwl9(j9#t$ECchnu+IB_ zzvqv8N2~#O7H;G8e&Ne|t_t^@drG)+MnCJzfbi(03&Xp6uM1b0uJJ4h?PA@;T))Mx z_r#{t!zbT-C;arQuZHhDy*GUKxd+3~zV=#p%k*{C*lzAihGh6t7QNQ~ko>KD`cO1^2zYT6Hj;@@RYdE;F{pVI-2zqXBLK;FNUyB=49qG_TKotcdDKBz_;QL z@Yl?55I$kW@J_D(=bJrmojoADwDoMq1|PwELAfhu_6sjucV2jN*CpXg>lcTc7mx5Y zANO|5om_9*X~V<5E6?&d6gy-N;ND{EoI&A0cvDQS_?mTOfUx#^xdb!U1tLKDQZoV|I9%0PD z6JLi8|K#15!Xp>W^ZkkSQ+xR}(L=_B{*N}l${G>>kNx}l&+MQ5V&M;;itX`2gC%{Wkb( zUnt_PJwJP|k+uEy6%)d<=1;(t_OTYAjeV_szD~J)v;PLce25M{v~GU*;oJKyZM53} z>jm_2w#{$gP8+C~djsZL)_;sQ_YbTk&^hqO{|g`CmU1vPxeDW-mQ`4Kkm8RH&>q%* z%x7BnX^tZg9e``SH_z_<=?glMzfa8MNMpUTf8*ltfz5@_te@|5_siF=wE45Aj|2PT z_;(Suj?OXXzO-#kSTwl3V?{dt=#F!yg?m@eb(_|_fNpSKLEJITaE*sSMSU%h_Qx^R~HV8(?!#_;~N^TVxYPIh|-Z|x)McLm`e@icojU0^Q9 z-Z?MA-tpb$gYe_Rzn}wg&5tj+%`iol^$+VhwGAIYTj~3X;hn?77cU6!-giTI^_C6x zd(;S9C-wGrQU`WU|A!jp;Lbe39E;zi{8&5h$9VbrkNbaXV=?~t0k9K2{q)n}aPfcY zsi%A{Q60n;=3bC%f&;jL*;vz8+DANZ^OjCqu_N-_{~Mn_utTZ)Kk}j~rFO&;jNfaN<4! zJXuR{@4{Mv_0WpZ-5qc8Qo=^!9%;dL#%1{X`p@dq{U82c_@vkoh7J?|E6^FrrJOJ% z{5iJ_F`r+#>=fI_?&xu)&$fBEG}+u{vRjv*63!ae+xLOy*!fpv;3`LWo!P{fcVB92 z;m?PM*Ukw`hIY#5OOA4c;R|fexW?9wZ{2d4-Ou$4({0VpJrlS*WA}77&L84)D*HGP zzZ0{k%f23Q{DgQ6H}!iy_F{G|92&lI$Hs88aqa~Qa&dodu3dl6@-g8nw{P_O9L_dh zVDGq?X#2VB*|MiQ)%J3y**TAyjF;*E!Xcf)qM@C`8Kb&~TQ6D~mf9Y`!l7NlX`&%r z!UCtxVgBIGR;Hu#t!K`5nVJ3C`+oE(_8tvvQTPk*6uS)nD@?AU^Zyl;L7!Bg^nkeV zXWtziWnH>_WLNiF11t`aF7h+uHbmc|zc0)I?$`0}Mm`5*>whrUdeHqpGFA@+Zp?k0)p^YB0hlk`_glKwm*S*z zO~#b_-EPkIW>HBuYH@q{10tspP;v$)1@rL9Kc!-ePYeV-mUb3 zb}t;#+1GYH4$PO?I5MYct%E%Gd?QU?&`Ii{KIR_bFaIpuH6J|l%rgN@D{B8HyO}bo z2kg_Z@K^g7f4~3B+MS>O9j|N+0KP`_vDErME5o=!l!YUAhV2Wm_r-G%5$~9%>q=hu zC7%es2KY+}D9OxNxo*9_RhS+@HqL0c@RnwI|mv2;cm| zE#a9>OTw-tqr%R`Bkf$#((tV>-e!A?GkxA?&PTWOn+4^y4gf3s3u{w!0R5rurW&U$(lrjOuCAHj95|MpwY!^B-*z@7S%^7ugZVMd#8QM$0tUz;*scU`}S zdgaDdVc(?-{N5QqiQj+cwhg`)iC@NFGoH)?=mYp*?^?@=b1y(xK7ePH&KlV{>@!>c z;Qkv-4|cmQeDJ`H;Y%0K^D_oK2SXXxE$TKs6G;cqCFuZj1O4E)aInU{3wUQ|e++AE zU8M70)I*(IOZW?;6fdQjO>e3o{wNYFwvaBqOGWqcvZ@~&uf z0pAR6IIQ$-f5Hc@a1s73P4-axf8f9I z*=L{4<1YNASHeHqeqsM7#Zg#FCa(vE6Tkm5HzQBFbS34KBaRICD^HkL5%$ zy29^4><5FjKmXzBaen|A$o+uLLE47|>lyaUDtK#c3a4J?3laCSl;Qk0?GX0VDg40) z+=Y{D7!IZ;R}uVM2!H8LYWvim;vdJqu>U7KQcf5GODKhX$7yeOyqF6M=aGd;qF!GD^a_0d|6dk`>Z4N1Q1OC}5ch39{? z4_O-jVp|16Y&pZfMFs31{4Y=KKMhlVI{!PaHilpn?Kxr{@r!f1aAmB)AjKcM75-kv z@PR9hHKY6gL{9kS@z2VzSN^4oW{3Ce_rtYzPli6+yK-`P$?pI7&7bS5EcJswJU=HS zIzSpe0iTR5vR{A=>a1XjJGSrf?6)A{pPm&I_Hb+$0$=LZ^Izagn&WQgn!p~pl=rc- zWTbIT{~gM%Df|Gvn|J}cE76?qZzbHKa8RrJ+4dvqfW?UOGm-CD_ zx+T33M~@&qJ3u;nmaHkMhxvx*55S-M|6-iLN|=GE$u&1R)cl{vKdlcvkWLiikKGsB zzwi@h?9222!c&|wgu#aWfQY~JDDq<4N1plq!E-11`kZ@l_HCZIVtIIK(=xwjr@m77 zXX}b$+_8J%k3K*=r_X)`>E!!w4`#1=UNH3s;B&z9pQaZ+ADI0!7x;aF;puzSc7Kb_ zgKr*lmy2=v4)WMnpgh;p`4jMG{U^L-v%;SA!^D4su%-@?(u6fyY>BWC|!e`rV8 zWoJ#;4|@6f^TQYI{)~OwSZ~xH+H$JBhnjV$bQJD*8&?|oLi?7F?&-h9p=Yi`t`|`k zIy8*GK?|oYjcbR|-C8-8N&_?OUburLc(dMLYVVb3Yxj=$dHH(fOCR9q2xp7aH79xi z{<8lRbK&;<^Us6-29s-UR1y2%KpEk!u<926*d@gI?~RKl`xy}YKuY9~He5tJC$PcZ zcf82`dAA0Vr1r)6f@`v0-Dv;6nMy=RW|vp1XOIhz+v@^wAF9s7nnKjV_;Y|84n<7{uZk=Kil;eBAjADmuz;e`Mf zR!Tce{MV}-TzUW}p059xudxrtDDK_HIb8l5J0gsYV(Zu)zZbC=?>@oOBny{39KuIX zcOkFznjy96!M5LX`?67fZs7iv6aC!PyLVm@?zP|KIA^6g#nDO1YfgxEo$U|!T7dOH z{B2QZmechC^8ot?;)EgEicaYFY0VLF%~rS%U`^mQZ90hE%U8g4rpVWLX5-j7<(rEo?b`=%an_ccJq z)A^rtWciH|UDW)~UY&4HIr}*14Bzx3%)KN!A^%>$Hm(Q2eS)3oiEFxuvDWqJvt86I^~k98675npMF6H*4Co>18jz_@dWAdEu_Q`;PI# z{7GDCvUk@1%)6AsFBD@*JW^r(r|YP`vN-n?+w6B<&iX!Y`|=N-W9MmYuXgw1;qkm! zhBbIo9-n`p{TJ>B!8*l1UH|3vAhCbx0X_o!!QJ=uOb5^d!lbdk(~A2D`(7F6&bYT^ zevkek+B*A4+D}TKGxhgB8vdl|9ij67;3JYQC=RBFf&Zn#UtIMN7yiNxT(SQY|FUdU zYdzr~u`k2^Gkm}(z5g%1HzZ$e07rCctDPJE${m}+>-IaqGZ)VeA3ki~aXxLR|L&an z`SS1==6P;-A&dR9b_HAc2e|tI(-Uyb<8SidPo6Mmod+S864yD!_J4RjR=!^A`n-?l z9gv1M_j=%uT(tk_1Hhl>KfwrmWdA8jQFXeS9t%q;{|7g45D*rFL z1rLa^6V6%#V;eVx_uYP;?CE@*hwuIMi zI4^wTk(=$gz(Ky=$m4GqQXc=05A)yJES}EoX&71gf9lZhG-de!utkQop!7jH;B!Id z1Ed39xA{ZrVlR;W0PYKPeJH#9+{FSD9ye#@4Of8HIx)hmY(Gmrv{M*JX?ySL5Iw@h@KgM;VtjeBfM%Gim6T(xoeTUC49?Op)P! zQ1%bj(ha`fsB}28_!e|B#UDM06z2`r4E!#@`k%Fd`~vTZ#{R({e1w@eX)nM0GWLIQ z1?|5HvXn`2SKXwi{-3c2BjFF$*mcA`-tQ52>}~#J40-;K`#-`5o&hT84CRpFc^R%F zy>Q$!?DKV-EbG73_RPq$S1hw=joT{0x1fZo;G&aF1cu3`(P(cwZ6U|G}OTdI0{slUX(kM#3MS z;NPSI{=Z4HX3ZRb)u%cX7yjwkb8fwl;mbIaE+X#O8)y8WQ|&w#_iBV$Q%MI@pUMzN z2K-eYdga$he2VHvr&f*Y9-i8;(4V9FF4n&llMu6Ir7v?+vEI^ z%_WAf;?gzBF0#Ep>PHXBk9uIbW4aL6kcIUrddNLJdJ=s_frE3ZV_bi+odd>i%J*=# zK>ERa64#qn&g$bG5y+;xBK(CF(<>D{fr*1v}yI@eqaGKF#JDYvm zg$u&6;U!_gpbi;zG#&{VXrY}CUu5T=cb-2#te!f+Tc!4&K0Y|1aF(ve(ame`C{?izu)8Kdmdp zAKit}cYed>d@?pp8n#V(dQKbvz`5VWrUN|7L%z#oa`;#I1fG{8%o#r3iA|V%rz}sh z@jN)+f3&mZqq~OtS55clrt$x8?!3r<`^APeufVbYN%lMzb!dI+YnNt*3UcbmU{*9ZJnZ$fowMFzNhmLHTq(0e{~ACSNZ- z5N>cVHMzz{hq8ai|J7GtbseH?N?b$sPd_30fWHGvrKOyF=?eOR{eo+%E66j}T0>}@ zDXXe`{Y^FU+;8-Pon|R?j3t?FB&6)$? z=nHm9AJ7rfd=AKbx%{^78_@yw7KEE~oBvCoY|WrO2ik!jKo{JnTR!u@)_mgHJMw)c z%O}n~qUL|_PjONhT>#6&#DDE;uf3M{A!%Ky{ewXsf75sH^mwKdsSY^)mX8d!#9W%k zKhq=d&)Xf?8c{CdAMX`N0}q!qo?egVB$f^9?C;O`$lmq&^RK@VUcB~PfA*Yl#%}W5 z%Icnl^nxA7^*(7XZ*pLcFQ+YzyX6(zHQ0kKx_}J$yFM^~C%6~zkMgt!-2#8|G_R-j zjSip_q_M6t7ev2-&m+%q2YU!zF;2SX3h?JSv2FHD<@aBB*zWwoNDSb=>S20Jh{{2Nuk|AYS;%1DP)7o4;df8mP%Pw^++aY=MU zvYJa}|9RXq`!8S{=U&bXAmg|j7Q$cUq(iJPSc`74cYJ?$|9#=(cVD!3MJ)1jL3zKO z;R>dlKXAPu%`l7jNBmp|&=bgHS$jqQU$A{J!*28VXS(3{+r1^}!W_&LPC2p#{0q1v zul-`p0dVXeo$#~biT(5b@A5Ev{q@(uf6d|GPZ{B!+CFK*U-k>v{O`Dfy%FOj+mtMG z^l7&C#{cJKOioy)-1AMIwN#o;7#(4)Kv>sgZOQ$``l)@wx1YEx{P^wt;Q@Q+24{%C zIVE%m{Hdp~w@|pZ0~cXWIqaXdyIy2EKe~~ z$p51oU=POP9kaba;@Veo{Ifj(!_~k4A^Q|=aIpIf_^+l6Ws8Y);jeLLT=*6Z_UATj zaj;Lr!V;O&?0uK%{4d>5ed5$X-Q(?I9 z(J8GhTqjIE)d9+=9Pe79Zq9wbZqH+X!+!hW-5Ojw`i*Sen9UpDPhH?ET+8C<4fapl zCF?q3b}nC#_X~z2GCZ?bxTh%S2Rcx!E6gpFC(q{!3Oq%d#JirIL;|v4)Bg9i;?9({1OKiWB=kvY1YGHL)LYMwL(Vd4Cn4CPhQlI$idJ3g>kSaj9;NG z+S8UEpfBix^aj5`o!k$R9_NF?oFRQ7oiNvCJw$ot07!mkh5n@L1bi{y z{}W!~vi%JI3r((}(V_N#9RD}peADZLL(~Bg7XFMM#5eSI*#0PcmY!z50Dk~Yv^_TV z?<70T&S777=B#kGz4MTFZ;&ooII`@^dD!y!=H8Xdre$*R+j-Qf@8}U$dG<-~ShLXY zr+E+8etW0jQri!}e-sn%tGjXKJpX1P@6;k64sON7HMX8H!{5__9DV~FMBJaIv}jn@ zuxarWm*XB&gijb_vU##Nznk20!2%yA&Z8pB+~Mb%voLYilU(=Ovv|*TE062^vG7mr zRAFI;O;-^A1}$2&xInmztDe;Uv1M$Y_Z6{+!Mm!6V<+6-iz7!E!tbA9?>in>(lq9q zJm+@t1*#9OFwb=GT}SHk^h}O0WmJwh*I!|EQ)a!rN0j$X^4u=|N3zT{#F1zJJY9z{ zAIYDj{)Mvqwt>$g-}B6F-KQiRKS3I8<(WSD6nurhQ64vBO@`mQcn_neXENy=p|IC)`S=9FN-yTRCi+F)&du*%V9$SW?^g=b3@Vm9@g9LVb+EQX7Qc=hm}(x# zuLsQklNQgya4%WdF9d)5zp#g=^jYwK>#esAs6+ac$KS?-F#%KXXWWSk58<0~{6oaw z-d%5)N)}8NhAWOdb3~e!;;(elgg<3?pC`}!aBbo5wrkjEy@+2I{=yV4j8o2@C3Q(I z>PcyP*8K&#g|C*aM?Hwxx(*n|(hcgHZ*^0aytp_}jQfyrT~% zoDy>jYfI{7PQvz8mt$|`Q(ch#3uj?3Jt6LCR*pSO>Q$Qjgs%rP+ZX=GIR1tyI3J9V zrEYXZYeRH^-}Yg;ja3)hVU0| z?*DB~f*(*?iVI<6gg+ShS|RfnX&K=KkL42lEsgnG7)lm7!m@v^!@JVqq)T30^E~TJ z@>5I|mQG;H$m9RfbK;Vb4iwJ;woqHWdFiQT>5}5R}lYWtgiYdd%o~j9VyTIe;Yf-7~=PT$3KfZ29_^9kQ`%(|Bq`$ zE1Sp&Pq@mVH>~f4aVnF_k?;0z>wVf04rgyD`D^#A(QS|f|;-v zm#>F&9zp(Jejdy{?D)sE0CBG6_`Cm)_lt4gAkG2ckF9Gxkhg#Qf3_ze{8Nll9RN!( z1zRI9F7Gdof3g2h>z4g9=E9YC94ajA#M5-}=e>vcX3`~-@)TF(OXFX9KY?=632^6} z7WFDkwl3TqTN`uY#q)pO<_%vkcKkD0`G3ifFFkPVleIlMk+*-t7@XzX&=#)&Qk_Keq2=>D&t<7k@*DYd{0j*MDG?;;wLJ|A&P?IzxGg@`@7{{wJOAxsazS zNjObcILYVi`pH#}i)EAad|fJYxZLXEnYfb z`k{!r%aS?K$`%uHo_3t^jKH7yKgCjhn_1e(}W@hjr`L<>{h}>>ZXCheK=Et_`bKuXes> z%^H7KYl@LD6F203o z6;JDwY;ifsP)7BqvPzR2T(ZhztdypF)d5!?T)DFersa~XQNM)#pvZN7Kp3>9nAgAj{Ry-|7xU4$U^z!o4>k%&NdXmw##i_rn zGD;WrY56n_IZ-O3u-c-wmnU0RdYYDo!PIQO?jh{|Xk<)wC1q)IO7Fh=ZmBqVq=^() z8KtY7xa8h@?>#R|T-Os%>yW(aQ=ZZlC!g|?6HoJ%22ax!7Ek4hW!3KDbjm6%<%AVa z>sJ`Aey4JTm7dnEa9Q;h%c~AB1=}N!JQ9q{`y;Qm$)5eSuYE0i=R4nV`n$jTy8xwe zg^PJATV9wl$`eE+c~UOHHUDcBx!%rOVU|0vUsnkKvAqmMoc|NPJYTrPe3>8JU;G+bP!yga2B z^RmjPGK59NvWJSRp7P|%%P%jTddkZyFTK2LA1u8hbsbCwEWz}sqmHU+_#aiC@mu<;>(mm3o-^KY#mn>W|;$@|kRW7aLU}4!&c{0Uyr|l@tE3O0C zRKBeI;BZ&KJe7m%no0vpFg@eZ`P zGyXrCPCohMYNG$?ZT)X-UzIj(+8k@R9@O?zzi{G-Cmw0|A8BJ-Nq@s*U^;gsSf9Do3Dl1S&_Mas(9Do3Dl1S&_Mas(i0^21HtJ^7Jt5-%5$bB?4~I%j{;x${tS*RtF)i?}_^L+f*&)sj zx2|2g_U1Zu>fCO$*XZ5~60(17`n=Wo*QUFz&ReXH8?CRcjMDScN1t%Q3Dd8?{`zp! zP4->Egm&!M5q9p}c|ck!Lm0V~7v~=;udIAsS9O;qQ+yp{Q&PUt=p%i#{--uT|L5?m zEzjhN(s&wHzMkJr zu;^fEMiCfD_&l{($o4>rZ`RMlp(LY{_ong%lp22_wI1pZMTIx?zkiD z*|Vp@bk|*Xl_`6;>E&IsxSlj#UYI`67xj&?D69Wfs#KYC%PqHrTW`J9iE<|#drYWx z;_;!OeT%DR)#F1=3yU|iZ*-AY!_o_MoPWRZ3egXUcVF<0W4>)mdqsTDPT#oIw=I$9`^Lo6 z@7wX+yYzdPeDjd+-XY8T9_rZp6KYpK!T1UOJw(2lSj)zMzQGxfu1)LsI1+b!$TQ-* zyNvts#~$r^tbW1E>py*lr1vMCaGck}`&g-uv1nQgUfud%&%g0a8sGaC#j(j6Yvcp`d3!}t5&T#hj%TK59hsmb!;4Y58KF+mih0t@XmH%VlLelj*?^&els z*dXJ+!uk$Jmj0vvV99qRMCJ9LZ%w9DjK6HZh2hz!WqsE_=|r@7;U8_E{*sRUGhT)M z$2lYR-@@RYVbAzD{+V7d22kvOoG+vg_y@_RV?{pS*u@`IIqn$8RQ=+6s^#_n;fEjg z?=ADqWeEJ~D?WfeKrJ)sYwrT%o6e+ZT;Ogi)@S)c($GcZ2^YrTME3{8eO|EsYJO#` zTt{q7qHR=l8Ttg)ZYP$9OmD+Mbw=5!TWw9Z7zfq>^j&>|m(_oJmSh(F*BCHPVA8*J zgD|~+M}PNO{2m+c*lG}F4zO>%*}IELqi-T)38Npp1FNs`f%dMixr0kQOy78i7kN|q z*tb`#E->W%S#Z+l4e9FsLhsZ<$C*FIb#G&G^;`~rLf=mv+Rf$w-t}kKGa{{TD|>Hw zKi<7v&vgfzrr+uxeJ!?s(s=)!2wC(Wzsec|48fjwYx8^b0sSRC>PFOazV(Amh{!{p zF(XX+>^^N=r{q_`55iW^5ysTl6`8L9XX4B!O)L#ru~!o|A+pk{oz`Sx4vmkTcq#OfB6H_kw+hpW4!LYaJIkut$hRQkF}BhxxO1G9_Nba z6QtAhTmBLm_bXZdeU1d53=6K;#pX-oSU01~!j*TNm4p9GzJEtP9QnGo?!@MVQ#uC4 z{*QjrCe44~rFK$I{tsNyY2YF8)@8o10xVH44uyh}ettgHTFgDmT?>i*SyG%cLWS4)t5_^`N z3w!#<*vcLl1M+15)WJ3JDfE-~M(Q1caNa4&cO~$v=mYma=(76HH=fJtf3<4WW`5~Q zU-EB&!|8vMS||EDH|GxO;(i8S!~0k9Wm7ZXO@F~$G@)l(4{KeF9$vR_jN=VPlkMHy z-`Rh^f6sGFw>EC4S{K8SIi;WJhH>nJJ_^r~C9Q0H=_Bvkj_>wdI;xkyJ9~n?mznp8 z^L|dgrNq3zII=E4m+6y;z8330Y5Vr=3y{V^_|s>`w!6sgx%}JJHZOr8a{|5x+o0dz zFPz5MdmX=g-**3g9ch#7{na16{DrV--bj1bYi+{_TdnPL7;9PT$ooITU3Nh_a}Mu9 zWjlQ= z`|`==>*s`r*Dv+&0@H_CeOiY{FIyb$zi2_Y^V}Iu_pF*3K7YaNaNnAFVX=MZ1KiYa z*#&jqx8~IFd8_Zfb*I_;@>`mX*KxgiXycjT?_b>){_o#^E8MsKj4;~16EM`ir-x3` ze{^5{f?NM*l!5<@C!TmBh|qxsCs#4QRoCBX%X>w6Cn?_*;ClhHZ9Kqoa`)!p6l=?t zdBehE8fR%z5g%Xcwu;d@3mp` z>>-W~x{a@*O*gHW=Dw3}EuTNBuj_gAe}y$MbM0*_r-$YCu3z~@<^#U*e7E(VJtu4p z`?%|Zx#5|uE5qK4PIDa4Q*go0(yqa_hamj%W$HhDEU*7hJ@r%&p(pI2@LtwAgY3ID zw$>#K@jZ_PLrcOWdp9oc?N>T&`tqKu?Hdt%$HUrVgfGHwK7YX+*Pp9R56(7Q#fHap zYwhzQ_(P;!WZyNq>-?EMzmUey{L0O$RfFrdYwm%u<+N|s{{w#iuy-O%f99EI z!qZPb?S#HW{jX#19WLlUc7V>O`X6nhw%hWI-mQI}@cANp-|u95A2r`A#jf|Rp6B1O zVlAV!0=mw;5215wC-(`j?_3w2+_Eyfe)GltjUv`wj0ZlHv0`q=XN*fIa1TTO)i3%;UyJ>prJb^W|NcNeROo+gpYyqf`h>k>1B^9&&*zzp=1S%?rH`?G zKfmRyFwMUA@X)#i{{05p%Q~X7^?|*LbPT`}yIMY?tIa=6J&%0W0b2i|E9ee>o^c_M zbuel4n>7Ms#=Cv#zxqWV>1!GNpGKZ2-+%8@T>t4^-SnCEp?~?btiMrLV&63ee6!%C zYgdLvgW89E8y362(wq+$cC2^k7dAsbwFYD!AfDd;pa=L?)@0siTMrT!u}-24zacQ6 zt6y3FrM(pUETH3n(_nG=Y!&PwmA857o65bH3;jrS_!%hWIWX!uVpqyJOL z6X7qKSb2Qh+`+7W?VaW})^Per8h(**%!~Q+EIprQ{U#4xdHKc*Y|S(%?7w2UeTOQ3 zyJ*%xzG-9Mu`xON!CC?N#Ur|V7&~B31ASv(W4P@tqvz;5L?37L@8s)1w|k3|PI!FJ zb}q|r0Q6t|dj9$6>Hn0n`cEE&EdC<(f3p3^qyJ+*aNlRROFstLc$Jo|?fU%Gm1l;# z&KMh>zH){44gBdZvijzYzx&+ctgU4e_(+I2_mHd+uxoyAK?k@GlkWg;Q9Bza*8co9 zM*r2f^7?P@QXVxzWPh7FWzg1VN z|0Yw=fBP0t(tqwbA@cj!{06rAMksT@0Q>!e{?qSxzZ~}q$kYBG{ik2_9Zp}1@wc=| zFWURRi~EnQN1HCZzn(PaKkz4<_80xtIBBftKYKp!-?Jq=w&@HX4=_jXkty^&erLV7 z|H2=vJA415PLR&JjPb!=;QQilkL>@}@ZXBq|HPN8U-Xf_meK!-FTM0qARhw%2Dbmf zT#pY(-}6s@_%0ml4Z`_6$@-YYZ~m|r!Jpr1->d!M8&8A>E?w&K^gFNr7DgW+#*(qs zxCnppy#Ll$*#!6btT7qCs2B13;q4pN2qh+upOpTqZ*c4X#4`Fnf$z+d4`;q){TJ_l znz*m0uf+M*qP`2vcXCMgJmY-FT5-wa$8K6WHvIg}p3i-ozAcEX!f^8VCgE*oPYXZ$+H2wV^JeA0DY($SCx{Gq zL+BLo6=VB&7(a-gfhOAC#+-qjd_4r_y5~53jO~Az9CpCH21Nd;LrM&bChjY-fAtNX z^*=rTME}R@yYO)O5B^{crs%MUZ)GqR%tNxZ_Km(8{-yeF z??W5McQyIGE=2zu*}fzF8*Xb%^&L*y0sW`XM&AEyEQovmtu5Tw@!jgnW)1T`(?9%5 zq5s*vwe=qzkp8<3sQ*R!&sY@qAOBch|J678T1NlJ(ti>5-`M&OcIcG8kK*4>vHtUo zl@x!COX;zS{RcPpr@r_6L*e|1ee(VR`$tdW?>zCo3ZLQqcl@IdWejBh8V`LZ1>G;U z|9t=L-h%r->|cH7yJ^<{v4`qE__Ow;?bs9JAR>*uM!rWw*vBN}=~$sB5Z|_1YdrgH)a3Zu7wZRw~pX^yNd53>sy!Dfr#&tDlE=^9N*Htcv4UA zm%d%Z*v4;UM;$^}&?okG+}_;hM!!NF+d?l=;u}iDu@CehQXRhzezBhYhDHC?FZyWx zA6-`e`F?}G2gQ9N_doQPd5FI7O)rfD-vwhYRDFaq_OVX|9K4*>f&4hD2N|~+>!;g_ zar*83%{YA`O?HB<5Jn%OUBo>e!uSff??YJs@dXfTcI<)Q@VJ*%zYfy>Q3vaP`aLto zAokzBb!Gig-{JH>#`&IA+%rapkSCqKi|9XL`l>M?&UXy7;}Fy?{vAEcZiP1O4UqPwwT_ zFZyWxA6Z`i$%oLB^vqOxhN$>F4d)AVwkAD4M4I9U3#%-gx+_YYXQEvq+N<+>I`76= zT719yb&&p#;2Q&ct0LtJ>l+qu>PX{dh07{emRx!DrR52WkXK$YFROmZqLl)1UtIzofp3UWY!`s#U9hQU8yyXG5xX>C$CVNlD4H z|II`n?S0`q|5N!_IRcdE}o_x5Kf-&El>V{GG$$x zHtAVu+FIOx`+oH&S-W!+#*NMO>(MpWw|hyhcehA=EIg=pcZ+v%>SerN&#pPRXkf2y z9`4z-OAhMWqr`cSuAOrbY5lECkCM*0?iuyAdOh4Frjg&x!ri)ba-wd+W5?;_MZD>tgNKv-tLn z^aI*&-&_BLeMwr}F+9qoKyj&m*K zTc5n|^uJ%*#-5%%6Px2)6lLfa&&2XOR^PUbIZu}3Od0W^9h>DiFG9R&?drMSts8l` zM@grg_I+tC-jRn^6eRD@Ic2HfAlF=(y3$4_K$P4k+v4M-`=nB3478! z6V}UYHm^Ul-!NoMdNlLr5L}(m?nooG1h-XUhx!9xi9Uq$GxjUXq;(&MeyayN|*@ zab4^MTVFMy7iYCxXV4+jDKE$GI*hZ8t^bD0Sscb2-J)JQ3+UHFZ>S4@i*7Op@Vp0Y zq^-s6zxLW|Ke4tWWAjeeTnEz)n*$tk>M=cKTs$tF!53NGPNabYb}_c3dG4W&EAUfp zv$PjN7W<6)6LrSgpU<;#%Go)7*E_SnoQ;|58)MZywm)a>&(YT6_Fs41b)WD&184ft zAI?OAFYRVrz(C`xF~|Q~d);Q?;6oc{_H7#;z3P0&aXfoRI^$0n z@B?pq2Ep*-tf@bV0+&idgx$2yGmSs!w=)f!%^{oA%} z`-JmgoCz4(w?}TQ{U*=zOY;Y`&7C!(b67XAho4j4v2aB0j({9-@bkOFUaRNee_?y zkK6CxjHCZv&TsJG!nyk|ZCsXn=#oWY{j4E&-TLmYwpkw--wks{*m;JAtoM8##D45d zWp0-}!-9_Bi|{ua<_r(>2J*aWjog(B#)KXAEYlaRT$!^m^JlQ^xg#)-?YHr#ZM2cL z7PsHt!}SZx&)NB++^~Kv>^TO<(8eJ*({vL&NxN>@R{^IeP}$%j{h>GdH@VMb7j)XJ>J8 z7ftP-J8NvO+<3e8I3s&jCO4&bJF|yIxj{Cj_Io*f$kEo~_S^fTe!=hQoR4Ci&-ihs z!_H`!FS6&A?5rDSG8li(Z7dqvIk$RBe?O~$ogj1bnN!2`UM+K`D?aDYX4bIya^?-r zG(338Le4tocwT75n4Z2S$42ls=%bwxF?*=z=cw$ARE~2(_^duQ{+!*RjkL9>{q^nM z>lfs6zCrrKISM;7;(E?mG|pw9Pn;v6&79HC&Xr)};reBh!;&E#b9>G`#m`4td%XSV z3Tp~{@P*dr2QFQb+c0mGug~#c%pI(Q2wQt|oYmvJ1bW3i2V>CF?nyX%LmO$UwZFcR z{%HTtEk9@NH~-`MpZ7oXfw8djnz`;4_C6W6bJiZlzmRstf-$)@ll$iGIe(^?#V*KW z&ayKWJ_mvc{vI2^j^#jj8&_>#7?XRc7CrldooF^aLr+a?>vU58&r`PxE zp3=7+=XyL2pJ4C)A&)r@&U142ubG>>+n$*vKCWA<9Or&G-)}nYHZ{@8WOIxAqG7gv zWB%h@JMRG)(WQmYdCYChd3F!vb97Iee`y2^-n=O)7FxB2rtG2ZBio%wOSVSQUz zKUZa*&eqQzy=}CSw%VAd`#*K;-DE$bf1J@C&@=jf*B`S%bb&DXgZ-j2=n-RVZF2oV zX0e^Y{Mvmt8&Unyv<}=QATiE9_^G7+(HgXOWY>+v1 zK)dk$7az@Cy~O;dwcF~*aTb(5F!r1eV_idEXg~d;UfNIFiQD-~57Qqz-(_<{UDu!P zT{`Ay8*QYmMeVOm8fVCn8Q!l)ZfY-k4mv&uYjaCeKfAqPNEglw+Pvb=#^z=Z=%DlJ zIp*dwNB0Qdv-a~$G`@Cv-}Wxcv(U_cod2FRpd;tbnFqZu*c5H%TqrbmP$v(^^P!D$ zQ+v1dv!VlgmU#PVW7huK$@tf{ccA^$+McuXz`21o{&ptM^&4BWv+m$wYocb%EzDKS zVW!IsofAfGYR{IrZ#{W;?t&=;-QVJ0@w@06dWW2B2)_zZANnL>e#bsCopL`64!z8O z(l**iTdnPB`)k?W&rdna%vo02ulbkyjr=@e_H2r;jc70P7xOOtK@TR`+3oip+?iW$ zIIw0i{c`-#G5Ue5wV!#`{c5ITrcd5}*B{!C9-&XzHG0W8!JZ`@bDTG$jkML;Un?1Z zZ$J5*N2UFojpaNgv@NR$E`nh4$^gizNwF#$9-Yv`rMp;?Yv)(kF}XLU@!C$ zAAr4hpUmc|7u%pO5cu$10et`m$}k`IH2r66X`}696}P{pov-_;wLNF&19L-qcgs!g z*_!o=ug|E{bUt^g>8734jbv#v`nAu;88`B8S~fms{XmD&UC+m-K-TsgHbK7(U#BT{ zzixeS0x#Bx=$+3q=6`LD$uST1?bazr`>YQ+YwO20_BE67uW5T*A2arxhh+RYQ~dwh zJKNrq2bns3lAu+#tV?9Q@mO_iWFRaLKaG;`<9%>T@p zx%bXJ=iIYgPh1Dg1HP(!-@>s7xxtUguYhkpr~0Pf&NF9>!TawrM?A~87qIr&148(4 zGdJ9uAmqf{Fc(}O^uv50e|#_DaV`F(H~fj8f4{#xvv7 zSdCA3Wlq3njj`VF+r?+y_~*Lu{OcageUf#i`Zb$X^ayzOv=jxTO-BOY3Xl*gx;1OQ6b~cr) zpH0#$e(C)`A3l7T6S+r^9y#))g?#b2v|Q(kb$q_i-ufX={X$xvx8ny79*{bvIr8`K z-?uzzf#Bc#Q{lCY|L;h*_&0nY3zSzo@;D>J;;oaqP>K{uxZLp`MK-J`g=Db}e*%=wq=(qYFfbgH1XgKO!Gf z8vP-D4)}5qs|Y`b7Zq0xe~E$qK8u+TrpFJ3LihCU;v(Te^KHO(CS9cTJQbG0Q&SbO z%&=E&Qq1(1jGvvA`8c!qBv6+3O%UIOSU!xQS-ukR3{OAU{GwzKuy4M=^!P(VLwEU> zEHV@_y(i;>cvnaFi4E|vT=H?H=nwrnGQs95owU-t?Oy&77E_wo{OB9;`@j!`x}m|K zeXj9+DdqUo z421?QzA2ain;?1Qz+9mF!k)NS_IK>)9U^oXV05Ys zoiJ;I`1cl@QuX(#4{V6^(OOgC;t|C+6c(5sKNgG0#C+HAY467mmd(XKjsLrDs&Bu= zvSMya$In>tVWFIU%+9LygROwMhz^C#77wP!kH_P8uwSMVQPcY|m&m^qpY@3RS%3KY zTpk-#{JcuLR?6{Nr|?Z1;?JQ|M0UtB9vL=UFPI)bkx1OZhMG;r&9;kf1;WmYY~h>r zPk(O&519^d`CQfwE4~G50X=4`d>B{{@V#4k-n+5;^y@q7u^+u6onwvLulRPUc+}#1 zf$8z(H+Tmd=U6IX->vs5c1N_c)}2xOsdGo$+|@+4OB`r&^MQTttk#qkCLZHcZwczyIQB@>lVlIKx*1_NSD7D_t-&kNLnfZ5)R7L;u@c*(tA2|c9f_MQ6iknwPN)BXeU9;ijb8~m3v{id=vRZ%r=ou$X5zfY zHm~tBr^uUimi}RjTZs3#rSao--EzNR-oZtG!yI8FO(kMx3k1{SkB*Ms#)btx^Nx;( zxkERDPM%|5j^1Q3`I@^J?lRu6?}5ejwMVgyuywEwxQAm`1Cw>**Z+v*4;GjnKa1Y+oL{WfZ?!UKG9KLOw5u_Zr# zu)y^A*=+Xr*tf>h$>KQC6WRFl`l4bvvixE$=FSHX7%p-}CyBnQpqsRLf)B>bwaWF$ zeSx((5|5cJ4NQ+eHa7M<*>`OGtTANITw~K9|*VRdHr}fV@hOQq>k3TUn@wxQpZelcLeK6S@?PJvcO*|lT4T`1Wb>A;>3y1(I214 zq%0OI{6fUg@Oo0l1~1slKb_B79N$jq9a($K2{K}B__^YmflB9vbKo16Fg^aslP7@#S~@89LY-i|@K@eAs0~*+j%mBx6dWmLD6*r&*=>@`$4>qk5Tm#EmOI zs&=C>+DBYkDMVk%NZ5@;LP|sK+B;WGmky@KpPHKbwbzAyQz@7Q{0+Umy&3tw-To%& z*s)_7;jjPx%M!@z4V+uZzYG4yxdF#xlA@gwvwi)IT% z{%Leh=RD1*ZB1=$ZCKZbd*jTsJNx<>OUXm8L(`A*XOyRmly5_5tNz0CbF;RWWlzhy z8hZksjkaIceuZP+op^U)U&S7NcJ{oZZ9_vt_@{4QEbe1?FQX1^c&_t|&$s!uy_3>E z&-{xQE;!oiJO0q+OFzrM!^n4tJvDsrZt~;iZPss{_H*p-=@b7V_STmc794FA;~@0z z)ho8&;hlm#K7AZ)*y>LAcUc?uUfWg1I}v*);x%5qa@o(zf)9npVNh4gTZChL0z{e}g+LOF&z7h0cdnCoxCdCFx z3Qs(5Efp^-w?1&RZEtVyXOH>w50~91BA + * + * 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, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +using Framework.Configuration; +using Framework.Constants; +using Framework.Database; +using Framework.Runtime; +using Game; +using Game.Chat; +using Game.Network; +using System; +using System.Threading; + +namespace WorldServer +{ + public class Server + { + const uint WorldSleep = 50; + + static void Main() + { + ApplicationSignal.SetConsoleCtrlHandler(AbortHandler, true); + ApplicationSignal.RemoveConsoleQuickEditMode(); + + if (!ConfigMgr.Load(System.Diagnostics.Process.GetCurrentProcess().ProcessName + ".conf")) + ExitNow(); + + var WorldSocketMgr = new WorldSocketManager(); + //AppDomain.CurrentDomain.UnhandledException += UnhandledExceptionHandler; // Watch for any unhandled exceptions. + + if (!StartDB()) + ExitNow(); + + Testing(); + + // set server offline (not connectable) + DB.Login.Execute("UPDATE realmlist SET flag = (flag & ~{0}) | {1} WHERE id = '{2}'", (uint)RealmFlags.VersionMismatch, (uint)RealmFlags.Offline, Global.WorldMgr.GetRealm().Id.Realm); + + Global.RealmMgr.Initialize(ConfigMgr.GetDefaultValue("RealmsStateUpdateDelay", 10)); + + Global.WorldMgr.SetInitialWorldSettings(); + + // Launch the worldserver listener socket + int worldPort = WorldConfig.GetIntValue(WorldCfg.PortWorld); + string worldListener = ConfigMgr.GetDefaultValue("BindIP", "0.0.0.0"); + + int networkThreads = ConfigMgr.GetDefaultValue("Network.Threads", 1); + if (networkThreads <= 0) + { + Log.outError(LogFilter.Server, "Network.Threads must be greater than 0"); + ExitNow(); + return; + } + + if (!WorldSocketMgr.StartNetwork(worldListener, worldPort, networkThreads)) + { + Log.outError(LogFilter.Network, "Failed to start Realm Network"); + ExitNow(); + } + + // set server online (allow connecting now) + DB.Login.Execute("UPDATE realmlist SET flag = flag & ~{0}, population = 0 WHERE id = '{1}'", (uint)RealmFlags.Offline, Global.WorldMgr.GetRealm().Id.Realm); + Global.WorldMgr.GetRealm().PopulationLevel = 0.0f; + Global.WorldMgr.GetRealm().Flags = Global.WorldMgr.GetRealm().Flags & ~RealmFlags.VersionMismatch; + + //- Launch CliRunnable thread + if (ConfigMgr.GetDefaultValue("Console.Enable", true)) + { + Thread commandThread = new Thread(CommandManager.InitConsole); + commandThread.Start(); + } + + WorldUpdateLoop(); + + try + { + // Shutdown starts here + Global.WorldMgr.KickAll(); // save and kick all players + Global.WorldMgr.UpdateSessions(1); // real players unload required UpdateSessions call + + // unload Battlegroundtemplates before different singletons destroyed + Global.BattlegroundMgr.DeleteAllBattlegrounds(); + + WorldSocketMgr.StopNetwork(); + + Global.MapMgr.UnloadAll(); // unload all grids (including locked in memory) + Global.ScriptMgr.Unload(); + + // set server offline + DB.Login.Execute("UPDATE realmlist SET flag = flag | {0} WHERE id = '{1}'", (uint)RealmFlags.Offline, Global.WorldMgr.GetRealm().Id.Realm); + Global.RealmMgr.Close(); + + ClearOnlineAccounts(); + + ExitNow(); + } + catch (Exception ex) + { + Log.outException(ex); + } + } + + static bool StartDB() + { + // Load databases + DatabaseLoader loader = new DatabaseLoader(DatabaseTypeFlags.All); + loader.AddDatabase(DB.Login, "Login"); + loader.AddDatabase(DB.Characters, "Character"); + loader.AddDatabase(DB.World, "World"); + loader.AddDatabase(DB.Hotfix, "Hotfix"); + + if (!loader.Load()) + return false; + + // Get the realm Id from the configuration file + Global.WorldMgr.GetRealm().Id.Realm = ConfigMgr.GetDefaultValue("RealmID", 0u); + if (Global.WorldMgr.GetRealm().Id.Realm == 0) + { + Log.outError(LogFilter.Server, "Realm ID not defined in configuration file"); + return false; + } + Log.outInfo(LogFilter.ServerLoading, "Realm running as realm ID {0} ", Global.WorldMgr.GetRealm().Id.Realm); + + // Clean the database before starting + ClearOnlineAccounts(); + + Log.outInfo(LogFilter.Server, "Using World DB: {0}", Global.WorldMgr.LoadDBVersion()); + return true; + } + + static void ClearOnlineAccounts() + { + // Reset online status for all accounts with characters on the current realm + DB.Login.Execute("UPDATE account SET online = 0 WHERE online > 0 AND id IN (SELECT acctid FROM realmcharacters WHERE realmid = {0})", Global.WorldMgr.GetRealm().Id.Realm); + + // Reset online status for all characters + DB.Characters.Execute("UPDATE characters SET online = 0 WHERE online <> 0"); + + // Battlegroundinstance ids reset at server restart + DB.Characters.Execute("UPDATE character_Battleground_data SET instanceId = 0"); + } + + static void WorldUpdateLoop() + { + uint realPrevTime = Time.GetMSTime(); + uint prevSleepTime = 0; // used for balanced full tick time length near WORLD_SLEEP_CONST + + while (!Global.WorldMgr.IsStopped) + { + var realCurrTime = Time.GetMSTime(); + + uint diff = Time.GetMSTimeDiff(realPrevTime, realCurrTime); + Global.WorldMgr.Update(diff); + realPrevTime = realCurrTime; + + if (diff <= (WorldSleep + prevSleepTime)) + { + prevSleepTime = WorldSleep + prevSleepTime - diff; + Thread.Sleep((int)prevSleepTime); + } + else + prevSleepTime = 0; + } + } + + static void UnhandledExceptionHandler(object sender, UnhandledExceptionEventArgs e) + { + var ex = e.ExceptionObject as Exception; + Log.outException(ex); + } + + static void ExitNow() + { + Log.outInfo(LogFilter.Server, "Halting process..."); + Environment.Exit(-1); + } + + static bool AbortHandler(CtrlType sig) + { + Global.WorldMgr.StopNow(ShutdownExitCode.Shutdown); + return true; + } + + static void Testing() + { + } + } +} \ No newline at end of file diff --git a/WorldServer/WorldServer.conf b/WorldServer/WorldServer.conf new file mode 100644 index 000000000..09a58ac6a --- /dev/null +++ b/WorldServer/WorldServer.conf @@ -0,0 +1,3642 @@ +############################################### +# Cypher BNet Server Configuration File # +############################################### + +################################################################################################### +# CONNECTIONS AND DIRECTORIES +# +# RealmID +# Description: ID of the Realm using this config. +# Important: RealmID must match the realmlist inside the auth database. +# Default: 1 + +RealmID = 1 + +# +# DataDir +# Description: Data directory setting. +# Important: DataDir needs to be quoted, as the string might contain space characters. +# Example: "@prefix@/share/cyphercore" +# Default: "./Data" + +DataDir = "." + +# +# LogsDir +# Description: Logs directory setting. +# Important: LogsDir needs to be quoted, as the string might contain space characters. +# Logs directory must exists, or log file creation will be disabled. +# Default: "./Logs" - (Log files will be stored in the current path) + +LogsDir = "./Logs" + +# +# LoginDatabaseInfo +# WorldDatabaseInfo +# CharacterDatabaseInfo +# Description: Database connection settings for the world server. +# Example: "hostname;port;username;password;database" + +# Default: "127.0.0.1;3306;cypher;cypher;auth" - (LoginDatabaseInfo) +# "127.0.0.1;3306;cypher;cypher;world" - (WorldDatabaseInfo) +# "127.0.0.1;3306;cypher;cypher;characters" - (CharacterDatabaseInfo) +# "127.0.0.1;3306;cypher;cypher;hotfixes" - (HotfixDatabaseInfo) + +LoginDatabaseInfo = "127.0.0.1;3306;username;password;auth" +WorldDatabaseInfo = "127.0.0.1;3306;username;password;world" +CharacterDatabaseInfo = "127.0.0.1;3306;username;password;characters" +HotfixDatabaseInfo = "127.0.0.1;3306;username;password;hotfixes" + +# +# WorldServerPort +# Description: TCP port to reach the world server. +# Default: 8085 + +WorldServerPort = 8085 + +# +# InstanceServerPort +# Description: TCP port to for second world connection. +# Default: 8086 + +InstanceServerPort = 8086 + +# +# BindIP +# Description: Bind world server to IP/hostname. +# Default: "0.0.0.0" - (Bind to all IPs on the system) + +BindIP = "0.0.0.0" + +# +# ThreadPool +# Description: Number of threads to be used for the global thread pool +# The thread pool is currently used for: +# - Signal handling (NYI) +# - Remote access (NYI) +# - Core freeze check (NYI) +# - World socket networking (NYI) +# Default: 2 + +ThreadPool = 2 + +# +################################################################################################### + +################################################################################################### +# SERVER LOGGING +# +# PacketLogFile +# Description: Binary packet logging file for the world server. +# Filename extension must be .pkt to be parsable with WowPacketParser. +# Example: "World.pkt" - (Enabled) +# Default: "" - (Disabled) + +PacketLogFile = "CypherCore.pkt" + +# +# Appender config values: Given a appender "name" +# Appender.name +# Description: Defines 'where to log'. +# Format: Type,LogLevel,Flags,optional1 +# +# Type +# 0 - (None) +# 1 - (Console) +# 2 - (File) +# 3 - (DB) +# +# LogLevel +# 0 - (Disabled) +# 1 - (Trace) +# 2 - (Debug) +# 3 - (Info) +# 4 - (Warn) +# 5 - (Error) +# 6 - (Fatal) +# +# Flags: +# 0 - None +# 1 - Prefix Timestamp to the text +# 2 - Prefix Log Level to the text +# 4 - Prefix Log Filter type to the text +# +# File: Name of the file (read as optional1 if Type = File) +# Allows to use one "{0}" to create dynamic files +# Leave Appender.Server optional1 blank to use exe as filename. +# + +Appender.Console = 1,3,0 +Appender.Server = 2,2,0 +Appender.GM = 2,2,0,gm/gm_{0}.log +Appender.DBErrors = 2,4,0,DBErrors.log + +# Logger config values: Given a logger "name" +# Logger.name +# Description: Defines 'What to log' +# Format: LogLevel,AppenderList +# +# LogLevel +# 0 - (Disabled) +# 1 - (Trace) +# 2 - (Debug) +# 3 - (Info) +# 4 - (Warn) +# 5 - (Error) +# 6 - (Fatal) +# +# AppenderList: List of appenders linked to logger +# (Using spaces as separator). +# + +Logger.Server = 3,Console Server +Logger.ServerLoading = 3,Console Server +Logger.Commands = 3,Console GM +Logger.Sql = 3,Console DBErrors +Logger.SqlUpdates = 3,Console Server +Logger.Network = 1,Console Server + +#Logger.Achievement=3,Console Server +#Logger.AreaTrigger=3,Console Server +#Logger.Ahbot=3,Console Server +#Logger.Auctionhouse=3,Console Server +#Logger.Arena=3,Console Server +#Logger.Battlefield=3,Console Server +#Logger.Battleground=3,Console Server +#Logger.BattlegroundReportPvpAfk=3,Console Server +#Logger.ChatLog=3,Console Server +#Logger.Calendar=3,Console Server +#Logger.ChatSystem=3,Console Server +#Logger.Cheat=3,Console Server +#Logger.Commands.ra=3,Console Server +#Logger.Condition=3,Console Server +#Logger.Gameevent=3,Console Server +#Logger.Guild=3,Console Server +#Logger.Lfg=3,Console Server +#Logger.Loot=3,Console Server +#Logger.MapsScript=3,Console Server +#Logger.Maps=3,Console Server +#Logger.Misc=3,Console Server +#Logger.Outdoorpvp=3,Console Server +#Logger.Pet=3,Console Server +#Logger.PlayerCharacter=3,Console Server +#Logger.PlayerDump=3,Console Server +#Logger.Player=3,Console Server +#Logger.PlayerItems=3,Console Server +#Logger.PlayerLoading=3,Console Server +#Logger.PlayerSkills=3,Console Server +#Logger.Pool=3,Console Server +#Logger.Rbac=3,Console Server +#Logger.Scripts=3,Console Server +#Logger.ScriptsAi=3,Console Server +#Logger.Spells=3,Console Server +#Logger.SpellsPeriodic=3,Console Server +#Logger.SqlDev=3,Console Server +#Logger.SqlDriver=3,Console Server +#Logger.Transport=3,Console Server +#Logger.Unit=3,Console Server +#Logger.Vehicle=3,Console Server +#Logger.Warden=3,Console Server + +# +# Log.Async.Enable +# Description: Enables asyncronous message logging. +# Default: 0 - (Disabled) +# 1 - (Enabled) + +Log.Async.Enable = 0 + +# +# Allow.IP.Based.Action.Logging +# Description: Logs actions, e.g. account login and logout to name a few, based on IP of +# current session. +# Default: 0 - (Disabled) +# 1 - (Enabled) + +Allow.IP.Based.Action.Logging = 0 + +# +################################################################################################### + +################################################################################################### +# PERFORMANCE SETTINGS +# +# UseProcessors (NYI) +# Description: Processors mask for Windows based multi-processor systems. +# Example: A computer with 2 CPUs: +# 1 - 1st CPU only, 2 - 2nd CPU only, 3 - 1st and 2nd CPU, because 1 | 2 is 3 +# Default: 0 - (Selected by OS) +# 1+ - (Bit mask value of selected processors) + +UseProcessors = 0 + +# +# ProcessPriority (NYI) +# Description: Process priority setting for Windows based systems. +# Details: On Windows, process is set to HIGH class. +# Default: 0 - (Normal) +# 1 - (High) + +ProcessPriority = 0 + +# +# RealmsStateUpdateDelay +# Description: Time (in seconds) between realm list updates. +# Default: 10 +# 0 - (Disabled) + +RealmsStateUpdateDelay = 10 + +# +# PlayerLimit +# Description: Maximum number of players in the world. Excluding Mods, GMs and Admins. +# Important: If you want to block players and only allow Mods, GMs or Admins to join the +# server, use the DB field "auth.realmlist.allowedSecurityLevel". +# Default: 0 - (Disabled, No limit) +# 1+ - (Enabled) + +PlayerLimit = 0 + +# +# SaveRespawnTimeImmediately +# Description: Save respawn time for creatures at death and gameobjects at use/open. +# Default: 1 - (Enabled, Save respawn time immediately) +# 0 - (Disabled, Save respawn time at grid unloading) + +SaveRespawnTimeImmediately = 1 + +# +# MaxOverspeedPings +# Description: Maximum overspeed ping count before character is disconnected. +# Default: 2 - (Enabled, Minimum value) +# 3+ - (Enabled, More checks before kick) +# 0 - (Disabled) + +MaxOverspeedPings = 2 + +# +# GridUnload +# Description: Unload grids to save memory. Can be disabled if enough memory is available +# to speed up moving players to new grids. +# Default: 1 - (enable, Unload grids) +# 0 - (disable, Do not unload grids) + +GridUnload = 1 + +# +# BaseMapLoadAllGrids +# Description: Load all grids for base maps upon load. Requires GridUnload to be 0. +# This will take around 5GB of ram upon server load, and will take some time +# to initially load the server. +# Default: 0 - (Don't pre-load all base maps, dynamically load as used) +# 1 - (Preload all grids in all base maps upon load) + +BaseMapLoadAllGrids = 0 + +# +# InstanceMapLoadAllGrids +# Description: Load all grids for instance maps upon load. Requires GridUnload to be 0. +# Upon loading an instance map, all creatures/objects in the map will be pre-loaded +# Default: 0 - (Don't pre-load all base maps, dynamically load as used) +# 1 - (Preload all grids in the instance upon load) + +InstanceMapLoadAllGrids = 0 + +# +# SocketTimeOutTime +# Description: Time (in milliseconds) after which a connection being idle on the character +# selection screen is disconnected. +# Default: 900000 - (15 minutes) + +SocketTimeOutTime = 900000 + +# +# SessionAddDelay +# Description: Time (in microseconds) that a network thread will sleep after authentication +# protocol handling before adding a connection to the world session map. +# Default: 10000 - (10 milliseconds, 0.01 second) + +SessionAddDelay = 10000 + +# +# GridCleanUpDelay +# Description: Time (in milliseconds) grid clean up delay. +# Default: 300000 - (5 minutes) + +GridCleanUpDelay = 300000 + +# +# MapUpdateInterval +# Description: Time (milliseconds) for map update interval. +# Default: 100 - (0.1 second) + +MapUpdateInterval = 100 + +# +# ChangeWeatherInterval +# Description: Time (in milliseconds) for weather update interval. +# Default: 600000 - (10 min) + +ChangeWeatherInterval = 600000 + +# +# PlayerSaveInterval +# Description: Time (in milliseconds) for player save interval. +# Default: 90000 - (90 seconds) + +PlayerSaveInterval = 90000 + +# +# PlayerSave.Stats.MinLevel +# Description: Minimum level for saving character stats in the database for external usage. +# Default: 0 - (Disabled, Do not save character stats) +# 1+ - (Enabled, Level beyond which character stats are saved) + +PlayerSave.Stats.MinLevel = 0 + +# +# PlayerSave.Stats.SaveOnlyOnLogout +# Description: Save player stats only on logout. +# Default: 1 - (Enabled, Only save on logout) +# 0 - (Disabled, Save on every player save) + +PlayerSave.Stats.SaveOnlyOnLogout = 1 + +# +# DisconnectToleranceInterval +# Description: Tolerance (in seconds) for disconnected players before reentering the queue. +# Default: 0 (disabled) + +DisconnectToleranceInterval = 0 + +# +# mmap.enablePathFinding +# Description: Enable/Disable pathfinding using mmaps - recommended. +# Default: 0 - (Disabled) +# 1 - (Enabled) + +mmap.EnablePathFinding = 1 + +# +# vmap.enableLOS +# vmap.enableHeight +# Description: VMmap support for line of sight and height calculation. +# Default: 1 - (Enabled, vmap.enableLOS) +# 1 - (Enabled, vmap.enableHeight) +# 0 - (Disabled) + +vmap.EnableLOS = 1 +vmap.EnableHeight = 1 + +# +# vmap.enableIndoorCheck +# Description: VMap based indoor check to remove outdoor-only auras (mounts etc.). +# Default: 1 - (Enabled) +# 0 - (Disabled, somewhat less CPU usage) + +vmap.EnableIndoorCheck = 1 + +# +# DetectPosCollision +# Description: Check final move position, summon position, etc for visible collision with +# other objects or walls (walls only if vmaps are enabled). +# Default: 1 - (Enabled) +# 0 - (Disabled, Less position precision but less CPU usage) + +DetectPosCollision = 1 + +# +# TargetPosRecalculateRange +# Description: Max distance from movement target point (+moving unit size) and targeted +# object (+size) after that new target movement point calculated. +# Range: 0.5-5.0 +# Default: 1.5 +# 0.5 - (Minimum, Contact Range, More sensitive reaction to target movement) +# 5.0 - (Maximum, Melee attack range, Less CPU usage) + +TargetPosRecalculateRange = 1.5 + +# +# UpdateUptimeInterval +# Description: Update realm uptime period (in minutes). +# Default: 10 - (10 minutes) +# 1+ + +UpdateUptimeInterval = 10 + +# +# LogDB.Opt.ClearInterval +# Description: Time (in minutes) for the WUPDATE_CLEANDB timer that clears the `logs` table +# of old entries. +# Default: 10 - (10 minutes) +# 1+ + +LogDB.Opt.ClearInterval = 10 + +# +# LogDB.Opt.ClearTime +# Description: Time (in seconds) for keeping old `logs` table entries. +# Default: 1209600 - (Enabled, 14 days) +# 0 - (Disabled, Do not clear entries) + +LogDB.Opt.ClearTime = 1209600 + +# +# MaxCoreStuckTime (NYI) +# Description: Time (in seconds) before the server is forced to crash if it is frozen. +# Default: 0 - (Disabled) +# 10+ - (Enabled, Recommended 10+) + +MaxCoreStuckTime = 0 + +# +# AddonChannel +# Description: Configure the use of the addon channel through the server (some client side +# addons will not work correctly with disabled addon channel) +# Default: 1 - (Enabled) +# 0 - (Disabled) + +AddonChannel = 1 + +# +# MapUpdate.Threads (NYI) +# Description: Number of threads to update maps. +# Default: 1 + +MapUpdate.Threads = 1 + +# +# CleanCharacterDB +# Description: Clean out deprecated achievements, skills, spells and talents from the db. +# Default: 0 - (Disabled) +# 1 - (Enable) + +CleanCharacterDB = 0 + +# +# PersistentCharacterCleanFlags +# Description: Determines the character clean flags that remain set after cleanups. +# This is a bitmask value, check /doc/CharacterDBCleanup.txt for more +# information. +# Example: 14 - (Cleaning up skills, talents and spells will remain enabled after the +# next cleanup) +# Default: 0 - (All cleanup methods will be disabled after the next cleanup) + +PersistentCharacterCleanFlags = 0 + +# +# Auction.GetAllScanDelay +# Description: Sets the minimum time in seconds, a single player character can perform a getall scan. +# The value is only held in memory so a server restart will clear it. +# Setting this to zero, will disable GetAll functions completely. +# Default: 900 - (GetAll scan limited to once every 15mins per player character) + +Auction.GetAllScanDelay = 900 + +# +# Auction.SearchDelay +# Description: Sets the minimum time in milliseconds (seconds x 1000), that the client must wait between +# auction search operations. This can be increased if somehow Auction House activity is causing +# too much load. +# Default: 300 - (Time delay between auction searches set to 0.3secs) + +Auction.SearchDelay = 300 + +# +################################################################################################### + +################################################################################################### +# SERVER SETTINGS +# +# GameType +# Description: Server realm type. +# Default: 0 - (NORMAL) +# 1 - (PVP) +# 4 - (NORMAL) +# 6 - (RP) +# 8 - (RPPVP) +# 16 - (FFA_PVP, Free for all pvp mode like arena PvP in all zones except rest +# activated places and sanctuaries) + +GameType = 0 + +# +# RealmZone +# Description: Server realm zone. Set allowed alphabet in character, etc. names. +# Default 1 - (Development - any language) +# 2 - (United States - extended-Latin) +# 3 - (Oceanic - extended-Latin) +# 4 - (Latin America - extended-Latin) +# 5 - (Tournament - basic-Latin at create, any at login) +# 6 - (Korea - East-Asian) +# 7 - (Tournament - basic-Latin at create, any at login) +# 8 - (English - extended-Latin) +# 9 - (German - extended-Latin) +# 10 - (French - extended-Latin) +# 11 - (Spanish - extended-Latin) +# 12 - (Russian - Cyrillic) +# 13 - (Tournament - basic-Latin at create, any at login) +# 14 - (Taiwan - East-Asian) +# 15 - (Tournament - basic-Latin at create, any at login) +# 16 - (China - East-Asian) +# 17 - (CN1 - basic-Latin at create, any at login) +# 18 - (CN2 - basic-Latin at create, any at login) +# 19 - (CN3 - basic-Latin at create, any at login) +# 20 - (CN4 - basic-Latin at create, any at login) +# 21 - (CN5 - basic-Latin at create, any at login) +# 22 - (CN6 - basic-Latin at create, any at login) +# 23 - (CN7 - basic-Latin at create, any at login) +# 24 - (CN8 - basic-Latin at create, any at login) +# 25 - (Tournament - basic-Latin at create, any at login) +# 26 - (Test Server - any language) +# 27 - (Tournament - basic-Latin at create, any at login) +# 28 - (QA Server - any language) +# 29 - (CN9 - basic-Latin at create, any at login) + +RealmZone = 1 + +# +# StrictPlayerNames +# Description: Limit player name to language specific symbol set. Prevents character +# creation and forces rename request if not allowed symbols are used +# Default: 0 - (Disable, Limited server timezone dependent client check) +# 1 - (Enabled, Strictly basic Latin characters) +# 2 - (Enabled, Strictly realm zone specific, See RealmZone setting, +# Note: Client needs to have the appropriate fonts installed which support +# the charset. For non-official localization, custom fonts need to be +# placed in clientdir/Fonts. +# 3 - (Enabled, Basic Latin characters + server timezone specific) + +StrictPlayerNames = 0 + +# +# StrictCharterNames +# Description: Limit guild/arena team charter names to language specific symbol set. +# Prevents charter creation if not allowed symbols are used. +# Default: 0 - (Disable, Limited server timezone dependent client check) +# 1 - (Enabled, Strictly basic Latin characters) +# 2 - (Enabled, Strictly realm zone specific, See RealmZone setting, +# Note: Client needs to have the appropriate fonts installed which support +# the charset. For non-official localization, custom fonts need to be +# placed in clientdir/Fonts. +# 3 - (Enabled, Basic Latin characters + server timezone specific) + +StrictCharterNames = 0 + +# +# StrictPetNames +# Description: Limit pet names to language specific symbol set. +# Prevents pet naming if not allowed symbols are used. +# Default: 0 - (Disable, Limited server timezone dependent client check) +# 1 - (Enabled, Strictly basic Latin characters) +# 2 - (Enabled, Strictly realm zone specific, See RealmZone setting, +# Note: Client needs to have the appropriate fonts installed which support +# the charset. For non-official localization, custom fonts need to be +# placed in clientdir/Fonts. +# 3 - (Enabled, Basic Latin characters + server timezone specific) + +StrictPetNames = 0 + +# +# DBC.Locale +# Description: DBC language settings. +# Default: 0 - (English) +# 1 - (Korean) +# 2 - (French) +# 3 - (German) +# 4 - (Chinese) +# 5 - (Taiwanese) +# 6 - (Spanish) +# 7 - (Spanish Mexico) +# 8 - (Russian) +# 9 - (none) +# 10 - (ptBR) +# 11 - (itIT) + +DBC.Locale = 0 + +# +# DeclinedNames +# Description: Allow Russian clients to set and use declined names. +# Default: 0 - (Disabled, Except when the Russian RealmZone is set) +# 1 - (Enabled) + +DeclinedNames = 0 + +# +# Expansion +# Description: Allow server to use content from expansions. Checks for expansion-related +# map files, client compatibility and class/race character creation. +# Default: 6 - (Expansion 6) +# 5 - (Expansion 5) +# 4 - (Expansion 4) +# 3 - (Expansion 3) +# 2 - (Expansion 2) +# 1 - (Expansion 1) +# 0 - (Disabled, Ignore and disable expansion content (maps, races, classes) + +Expansion = 6 + +# +# MinPlayerName +# Description: Minimal player name length. +# Range: 1-12 +# Default: 2 + +MinPlayerName = 2 + +# +# MinCharterName +# Description: Minimal charter name length. +# Range: 1-24 +# Default: 2 + +MinCharterName = 2 + +# +# MinPetName +# Description: Minimal pet name length. +# Range: 1-12 +# Default: 2 + +MinPetName = 2 + +# +# Guild.CharterCost +# ArenaTeam.CharterCost.2v2 +# ArenaTeam.CharterCost.3v3 +# ArenaTeam.CharterCost.5v5 +# Description: Amount of money (in Copper) the petitions costs. +# Default: 1000 - (10 Silver) +# 800000 - (80 Gold) +# 1200000 - (120 Gold) +# 2000000 - (200 Gold) + +Guild.CharterCost = 1000 +ArenaTeam.CharterCost.2v2 = 800000 +ArenaTeam.CharterCost.3v3 = 1200000 +ArenaTeam.CharterCost.5v5 = 2000000 + +# +# MaxWhoListReturns +# Description: Set the max number of players returned in the /who list and interface. +# Default: 50 - (stable) + +MaxWhoListReturns = 50 + +# +# CharacterCreating.Disabled +# Description: Disable character creation for players based on faction. +# Example: 3 - (1 + 2, Alliance and Horde are disabled) +# Default: 0 - (Enabled, All factions are allowed) +# 1 - (Disabled, Alliance) +# 2 - (Disabled, Horde) +# 4 - (Disabled, Neutral) + +CharacterCreating.Disabled = 0 + +# +# CharacterCreating.Disabled.RaceMask +# Description: Mask of races which cannot be created by players. +# Example: 1536 - (1024 + 512, Blood Elf and Draenei races are disabled) +# Default: 0 - (Enabled, All races are allowed) +# 1 - (Disabled, Human) +# 2 - (Disabled, Orc) +# 4 - (Disabled, Dwarf) +# 8 - (Disabled, Night Elf) +# 16 - (Disabled, Undead) +# 32 - (Disabled, Tauren) +# 64 - (Disabled, Gnome) +# 128 - (Disabled, Troll) +# 256 - (Disabled, Goblin) +# 512 - (Disabled, Blood Elf) +# 1024 - (Disabled, Draenei) +# 2097152 - (Disabled, Worgen) +# 8388608 - (Disabled, Pandaren Neutral) +# 16777216 - (Disabled, Pandaren Alliance) +# 33554432 - (Disabled, Pandaren Horde) + +CharacterCreating.Disabled.RaceMask = 0 + +# +# CharacterCreating.Disabled.ClassMask +# Description: Mask of classes which cannot be created by players. +# Example: 288 - (32 + 256, Death Knight and Warlock classes are disabled) +# Default: 0 - (Enabled, All classes are allowed) +# 1 - (Disabled, Warrior) +# 2 - (Disabled, Paladin) +# 4 - (Disabled, Hunter) +# 8 - (Disabled, Rogue) +# 16 - (Disabled, Priest) +# 32 - (Disabled, Death Knight) +# 64 - (Disabled, Shaman) +# 128 - (Disabled, Mage) +# 256 - (Disabled, Warlock) +# 512 - (Disabled, Monk) +# 1024 - (Disabled, Druid) + +CharacterCreating.Disabled.ClassMask = 0 + +# +# CharactersPerAccount +# Description: Limit number of characters per account on all realms on this realmlist. +# Important: Number must be >= CharactersPerRealm +# Default: 50 + +CharactersPerAccount = 50 + +# +# CharactersPerRealm +# Description: Limit number of characters per account on this realm. +# Range: 1-12 +# Default: 12 - (Client limitation) + +CharactersPerRealm = 12 + +# +# DeathKnightsPerRealm +# Description: Limit number of death knight characters per account on this realm. +# Range: 1-12 +# Default: 1 + +DeathKnightsPerRealm = 1 + +# +# CharacterCreating.MinLevelForDeathKnight +# Description: Limit creating death knights only for account with another +# character of specific level (ignored for GM accounts). +# Default: 55 - (Enabled, Requires at least another level 55 character) +# 0 - (Disabled) +# 1 - (Enabled, Requires at least another level 1 character) + +CharacterCreating.MinLevelForDeathKnight = 55 + +# +# DemonHuntersPerRealm +# Description: Limit number of demon hunter characters per account on this realm. +# Range: 1-12 +# Default: 1 + +DemonHuntersPerRealm = 1 + +# +# CharacterCreating.MinLevelForDemonHunter +# Description: Limit creating demon hunters only for account with another +# character of specific level. +# Default: 70 - (Enabled, Requires at least another level 70 character) +# 0 - (Disabled) +# 1 - (Enabled, Requires at least another level 1 character) + +CharacterCreating.MinLevelForDemonHunter = 70 + +# +# SkipCinematics +# Description: Disable cinematic intro at first login after character creation. +# Prevents buggy intros in case of custom start location coordinates. +# Default: 0 - (Show intro for each new character) +# 1 - (Show intro only for first character of selected race) +# 2 - (Disable intro for all classes) + +SkipCinematics = 0 + +# +# MaxPlayerLevel +# Description: Maximum level that can be reached by players. +# Important: Levels beyond 100 are not recommended at all. +# Range: 1-255 +# Default: 100 + +MaxPlayerLevel = 110 + +# +# MinDualSpecLevel +# Description: Level requirement for Dual Talent Specialization. +# Default: 30 + +MinDualSpecLevel = 30 + +# +# StartPlayerLevel +# Description: Starting level for characters after creation. +# Range: 1-MaxPlayerLevel +# Default: 1 + +StartPlayerLevel = 1 + +# +# StartDeathKnightPlayerLevel +# Description: Staring level for death knights after creation. +# Range: 1-MaxPlayerLevel +# Default: 55 + +StartDeathKnightPlayerLevel = 55 + +# +# StartDemonHunterPlayerLevel +# Description: Staring level for demon hunters after creation. +# Range: 98-MaxPlayerLevel +# Default: 98 + +StartDemonHunterPlayerLevel = 98 + +# +# StartPlayerMoney +# Description: Amount of money (in Copper) that a character has after creation. +# Default: 0 +# 100 - (1 Silver) + +StartPlayerMoney = 0 + +# +# RecruitAFriend.MaxLevel +# Description: Highest level up to which a character can benefit from the Recruit-A-Friend +# experience multiplier. +# Default: 85 + +RecruitAFriend.MaxLevel = 85 + +# +# RecruitAFriend.MaxDifference +# Description: Highest level difference between linked Recruiter and Friend benefit from +# the Recruit-A-Friend experience multiplier. +# Default: 4 + +RecruitAFriend.MaxDifference = 4 + +# +# DisableWaterBreath +# Description: Required security level for water breathing. +# Default: 4 - (Disabled) +# 0 - (Enabled, Everyone) +# 1 - (Enabled, Mods/GMs/Admins) +# 2 - (Enabled, GMs/Admins) +# 3 - (Enabled, Admins) + +DisableWaterBreath = 4 + +# +# AllFlightPaths +# Description: Character knows all flight paths (of both factions) after creation. +# Default: 0 - (Disabled) +# 1 - (Enabled) + +AllFlightPaths = 0 + +# +# InstantFlightPaths +# Description: Flight paths will take players to their destination instantly instead +# of making them wait while flying. +# Default: 0 - (Disabled) +# 1 - (Enabled) + +InstantFlightPaths = 0 + +# +# ActivateWeather +# Description: Activate the weather system. +# Default: 1 - (Enabled) +# 0 - (Disabled) + +ActivateWeather = 1 + +# +# CastUnstuck +# Description: Allow casting the Unstuck spell using .start or unstuck button in client +# help options. +# Default: 1 - (Enabled) +# 0 - (Disabled) + +CastUnstuck = 1 + +# +# Instance.IgnoreLevel +# Description: Ignore level requirement when entering instances. +# Default: 0 - (Disabled) +# 1 - (Enabled) + +Instance.IgnoreLevel = 0 + +# +# Instance.IgnoreRaid +# Description: Ignore raid group requirement when entering instances. +# Default: 0 - (Disabled) +# 1 - (Enabled) + +Instance.IgnoreRaid = 0 + +# +# Instance.ResetTimeHour +# Description: Hour of the day when the global instance reset occurs. +# Range: 0-23 +# Default: 4 - (04:00 AM) + +Instance.ResetTimeHour = 4 + +# +# Instance.UnloadDelay +# Description: Time (in milliseconds) before instance maps are unloaded from memory if no +# characters are inside. +# Default: 1800000 - (Enabled, 30 minutes) +# 0 - (Disabled, Instance maps are kept in memory until the instance +# resets) + +Instance.UnloadDelay = 1800000 + +# +# InstancesResetAnnounce +# Description: Announce the reset of one instance to whole party. +# Default: false - (Disabled, don't show, blizzlike) +# true - (Enabled, show) + +InstancesResetAnnounce = false + +# +# Quests.EnableQuestTracker +# Description: Store datas in the database about quest completion and abandonment to help finding out bugged quests. +# Default: 0 - (Disabled) +# 1 - (Enabled) + +Quests.EnableQuestTracker = 0 + +# +# Quests.LowLevelHideDiff +# Description: Level difference between player and quest level at which quests are +# considered low-level and are not shown via exclamation mark (!) at quest +# givers. +# Default: 4 - (Enabled, Hide quests that have 4 levels less than the character) +# -1 - (Disabled, Show all available quest marks) + +Quests.LowLevelHideDiff = 4 + +# +# Quests.HighLevelHideDiff +# Description: Level difference between player and quest level at which quests are +# considered high-level and are not shown via exclamation mark (!) at quest +# givers. +# Default: 7 - (Enabled, Hide quests that have 7 levels more than the character) +# -1 - (Disabled, Show all available quest marks) + +Quests.HighLevelHideDiff = 7 + +# +# Quests.IgnoreRaid +# Description: Allow non-raid quests to be completed while in a raid group. +# Default: 0 - (Disabled) +# 1 - (Enabled) + +Quests.IgnoreRaid = 0 + +# +# Quests.IgnoreAutoAccept +# Description: Ignore auto accept flag. Clients will have to manually accept all quests. +# Default: 0 - (Disabled, DB values determine if quest is marked auto accept or not.) +# 1 - (Enabled, clients will not be told to automatically accept any quest.) + +Quests.IgnoreAutoAccept = 0 + +# +# Quests.IgnoreAutoComplete +# Description: Ignore auto complete flag. Clients will have to manually complete all quests. +# Default: 0 - (Disabled, DB values determine if quest is marked auto complete or not.) +# 1 - (Enabled, clients will not be told to automatically complete any quest.) + +Quests.IgnoreAutoComplete = 0 + +# +# Quests.DailyResetTime +# Description: Hour of the day when daily quest reset occurs. +# Range: 0-23 +# Default: 3 - (3:00 AM, Blizzlike) +# + +Quests.DailyResetTime = 3 + +# +# Guild.EventLogRecordsCount +# Description: Number of log entries for guild events that are stored per guild. Old entries +# will be overwritten if the number of log entries exceed the configured value. +# High numbers prevent this behavior but may have performance impacts. +# Default: 100 + +Guild.EventLogRecordsCount = 100 + +# +# Guild.ResetHour +# Description: Hour of the day when the daily cap resets occur. +# Range: 0-23 +# Default: 6 - (06:00 AM) + +Guild.ResetHour = 6 + +# +# Guild.BankEventLogRecordsCount +# Description: Number of log entries for guild bank events that are stored per guild. Old +# entries will be overwritten if the number of log entries exceed the +# configured value. High numbers prevent this behavior but may have performance +# impacts. +# Default: 25 - (Minimum) + +Guild.BankEventLogRecordsCount = 25 + +# +# Guild.NewsLogRecordsCount +# Description: Number of log entries for guild news that are stored per guild. Old +# entries will be overwritten if the number of log entries exceed the +# configured value. High numbers prevent this behavior but may have performance +# impacts. +# Default: 250 + +Guild.NewsLogRecordsCount = 250 + +# +# MaxPrimaryTradeSkill +# Description: Maximum number of primary professions a character can learn. +# Range: 0-11 +# Default: 2 + +MaxPrimaryTradeSkill = 2 + +# +# MinPetitionSigns +# Description: Number of required signatures on charters to create a guild. +# Range: 0-4 +# Default: 4 + +MinPetitionSigns = 4 + +# +# MaxGroupXPDistance +# Description: Max distance to creature for group member to get experience at creature +# death. +# Default: 74 + +MaxGroupXPDistance = 74 + +# +# MaxRecruitAFriendBonusDistance +# Description: Max distance between character and and group to gain the Recruit-A-Friend +# XP multiplier. +# Default: 100 + +MaxRecruitAFriendBonusDistance = 100 + +# +# MailDeliveryDelay +# Description: Time (in seconds) mail delivery is delayed when sending items. +# Default: 3600 - (1 hour) + +MailDeliveryDelay = 3600 + +# +# SkillChance.Prospecting +# Description: Allow skill increase from prospecting. +# Default: 0 - (Disabled) +# 1 - (Enabled) + +SkillChance.Prospecting = 0 + +# +# SkillChance.Milling +# Description: Allow skill increase from milling. +# Default: 0 - (Disabled) +# 1 - (Enabled) + +SkillChance.Milling = 0 + +# +# OffhandCheckAtSpellUnlearn +# Description: Unlearning certain spells can change offhand weapon restrictions +# for equip slots. +# Default: 1 - (Recheck offhand slot weapon at unlearning a spell) +# 0 - (Recheck offhand slot weapon only at zone update) + +OffhandCheckAtSpellUnlearn = 1 + +# +# ClientCacheVersion +# Description: Client cache version for client cache data reset. Use any value different +# from DB and not recently been used to trigger client side cache reset. +# Default: 0 - (Use DB value from world DB version.cache_id field) + +ClientCacheVersion = 0 + +# +# HotfixCacheVersion +# Description: Hotfix cache version for hotfix cache data reset. Use any value different +# from DB and not recently been used to trigger client side cache reset. +# Default: 0 - (Use DB value from world DB version.hotfix_id field) + +HotfixCacheVersion = 0 + +# +# Event.Announce +# Description: Announce events. +# Default: 0 - (Disabled) +# 1 - (Enabled) + +Event.Announce = 0 + +# +# BeepAtStart +# Description: Beep when the world server finished starting. +# Default: 1 - (Enabled) +# 0 - (Disabled) + +BeepAtStart = 1 + +# +# Motd +# Description: Message of the Day, displayed at login. Use '@' for a newline. +# Example: "Welcome to John's Server!@This server is proud to be powered by Trinity Core." +# Default: "Welcome to a Trinity Core server." + +Motd = "Welcome to a Cypher Core server." + +# +# Server.LoginInfo +# Description: Display core version (.server info) on login. +# Default: 0 - (Disabled) +# 1 - (Enabled) + +Server.LoginInfo = 0 + +# +# Command.LookupMaxResults +# Description: Number of results being displayed using a .lookup command. +# Default: 0 - (Unlimited) + +Command.LookupMaxResults = 0 + +# +# DungeonFinder.OptionsMask +# Description: Dungeon and raid finder system. +# Value is a bitmask consisting of: +# LFG_OPTION_ENABLE_DUNGEON_FINDER = 1, Enable the dungeon finder browser +# LFG_OPTION_ENABLE_RAID_BROWSER = 2, Enable the raid browser +# Default: 1 + +DungeonFinder.OptionsMask = 1 + +# +# DBC.EnforceItemAttributes +# Description: Disallow overriding item attributes stored in DBC files with values from the +# database. +# Default: 1 - (Enabled, Enforce DBC values) +# 0 - (Disabled, Use database values) + +DBC.EnforceItemAttributes = 1 + +# +# AccountInstancesPerHour +# Description: Controls the max amount of different instances player can enter within hour. +# Default: 10 + +AccountInstancesPerHour = 10 + +# +# Account.PasswordChangeSecurity +# Description: Controls how secure the password changes are. +# Default: 0 - None (Old and new password) +# 1 - Email (Email confirmation necessary) +# 2 - RBAC (RBAC enable or disables email confirmation per group) + +Account.PasswordChangeSecurity = 0 + +# +# BirthdayTime +# Description: Set to date of project's birth in UNIX time. By default the date when +# TrinityCore was started (Thu Oct 2, 2008) +# Default: 1222964635 + +BirthdayTime = 1222964635 + +# +# FeatureSystem.BpayStore.Enabled +# Description: Not yet implemented +# Default: 0 - (Disabled) +# 1 - (Enabled) + +FeatureSystem.BpayStore.Enabled = 0 + +# +# FeatureSystem.CharacterUndelete.Enabled +# Description: Controls Feature in CharacterList to restore delete Characters. +# Default: 0 - (Disabled) +# 1 - (Enabled, Experimental) + +FeatureSystem.CharacterUndelete.Enabled = 0 + +# +# FeatureSystem.CharacterUndelete.Cooldown +# Description: Time between available character restorations. (in sec) +# Default: 2592000 (30 days) + +FeatureSystem.CharacterUndelete.Cooldown = 2592000 + +# +################################################################################################### + +################################################################################################### +# UPDATE SETTINGS +# +# Updates.EnableDatabases +# Description: A mask that describes which databases shall be updated. +# +# Following flags are available +# DATABASE_LOGIN = 1, // Auth database +# DATABASE_CHARACTER = 2, // Character database +# DATABASE_WORLD = 4, // World database +# DATABASE_HOTFIX = 8, // Hotfixes database +# +# Default: 15 - (All enabled) +# 4 - (Enable world only) +# 0 - (All Disabled) + +Updates.EnableDatabases = 15 + +# Updates.SourcePath +# Description: The path to your SQL Files directory. +# Example: "../CypherCore" + +Updates.SourcePath = "." + +# +# Updates.AutoSetup +# Description: Auto populate empty databases. +# Default: 1 - (Enabled) +# 0 - (Disabled) + +Updates.AutoSetup = 1 + +# +# Updates.Redundancy +# Description: Perform data redundancy checks through hashing +# to detect changes on sql updates and reapply it. +# Default: 1 - (Enabled) +# 0 - (Disabled) + +Updates.Redundancy = 1 + +# +# Updates.ArchivedRedundancy +# Description: Check hashes of archived updates (slows down startup). +# Default: 0 - (Disabled) +# 1 - (Enabled) + +Updates.ArchivedRedundancy = 0 + +# +# Updates.AllowRehash +# Description: Inserts the current file hash in the database if it is left empty. +# Useful if you want to mark a file as applied but you don't know its hash. +# Default: 1 - (Enabled) +# 0 - (Disabled) + +Updates.AllowRehash = 1 + +# +# Updates.CleanDeadRefMaxCount +# Description: Cleans dead/ orphaned references that occur if an update was removed or renamed and edited in one step. +# It only starts the clean up if the count of the missing updates is below or equal the Updates.CleanDeadRefMaxCount value. +# This way prevents erasing of the update history due to wrong source directory state (maybe wrong branch or bad revision). +# Disable this if you want to know if the database is in a possible "dirty state". +# Default: 3 - (Enabled) +# 0 - (Disabled) +# -1 - (Enabled - unlimited) + +Updates.CleanDeadRefMaxCount = 3 + +# +################################################################################################### + +################################################################################################### +# WARDEN SETTINGS +# +# Warden.Enabled +# Description: Enable Warden anticheat system. +# Default: 0 - (Disabled) +# 1 - (Enabled) + +Warden.Enabled = 0 + +# +# Warden.NumMemChecks +# Description: Number of Warden memory checks that are sent to the client each cycle. +# Default: 3 - (Enabled) +# 0 - (Disabled) + +Warden.NumMemChecks = 3 + +# +# Warden.NumOtherChecks +# Description: Number of Warden checks other than memory checks that are added to request +# each checking cycle. +# Default: 7 - (Enabled) +# 0 - (Disabled) + +Warden.NumOtherChecks = 7 + +# +# Warden.ClientResponseDelay +# Description: Time (in seconds) before client is getting disconnecting for not responding. +# Default: 600 - (10 Minutes) +# 0 - (Disabled, client won't be kicked) + +Warden.ClientResponseDelay = 600 + +# +# Warden.ClientCheckHoldOff +# Description: Time (in seconds) to wait before sending the next check request to the client. +# A low number increases traffic and load on client and server side. +# Default: 30 - (30 Seconds) +# 0 - (Send check as soon as possible) + +Warden.ClientCheckHoldOff = 30 + +# +# Warden.ClientCheckFailAction +# Description: Default action being taken if a client check failed. Actions can be +# overwritten for each single check via warden_action table in characters +# database. +# Default: 0 - (Disabled, Logging only) +# 1 - (Kick) +# 2 - (Ban) + +Warden.ClientCheckFailAction = 0 + +# +# Warden.BanDuration +# Description: Time (in seconds) an account will be banned if ClientCheckFailAction is set +# to ban. +# Default: 86400 - (24 hours) +# 0 - (Permanent ban) + +Warden.BanDuration = 86400 + +# +################################################################################################### + +################################################################################################### +# PLAYER INTERACTION +# +# AllowTwoSide.Interaction.Calendar +# Description: Allow calendar invites between factions. +# Default: 0 - (Disabled) +# 1 - (Enabled) + +AllowTwoSide.Interaction.Calendar = 0 + +# +# AllowTwoSide.Interaction.Channel +# Description: Allow channel chat between factions. +# Default: 0 - (Disabled) +# 1 - (Enabled) + +AllowTwoSide.Interaction.Channel = 0 + +# +# AllowTwoSide.Interaction.Group +# Description: Allow group joining between factions. +# Default: 0 - (Disabled) +# 1 - (Enabled) + +AllowTwoSide.Interaction.Group = 0 + +# +# AllowTwoSide.Interaction.Guild +# Description: Allow guild joining between factions. +# Default: 0 - (Disabled) +# 1 - (Enabled) + +AllowTwoSide.Interaction.Guild = 0 + +# +# AllowTwoSide.Interaction.Auction +# Description: Allow auctions between factions. +# Default: 0 - (Disabled) +# 1 - (Enabled) + +AllowTwoSide.Interaction.Auction = 0 + +# +# AllowTwoSide.Trade +# Description: Allow trading between factions. +# Default: 0 - (Disabled) +# 1 - (Enabled) + +AllowTwoSide.Trade = 0 + +# +# TalentsInspecting +# Description: Allow/disallow inspecting other characters' talents. +# Doesn't affect game master accounts. +# 2 - (Enabled for all characters) +# Default: 1 - (Enabled for characters of the same faction) +# 0 - (Talent inspecting is disabled) + +TalentsInspecting = 1 + +# +################################################################################################### + +################################################################################################### +# CREATURE SETTINGS +# +# ThreatRadius +# Description: Distance for creatures to evade after being pulled away from the combat +# starting point. If ThreatRadius is less than creature aggro radius then aggro +# radius will be used. +# Default: 60 + +ThreatRadius = 60 + +# +# Rate.Creature.Aggro +# Description: Aggro radius percentage. +# Default: 1 - (Enabled, 100%) +# 1.5 - (Enabled, 150%) +# 0 - (Disabled, 0%) + +Rate.Creature.Aggro = 1 + +# +# CreatureFamilyFleeAssistanceRadius +# Description: Distance for fleeing creatures seeking assistance from other creatures. +# Default: 30 - (Enabled) +# 0 - (Disabled) + +CreatureFamilyFleeAssistanceRadius = 30 + +# +# CreatureFamilyAssistanceRadius +# Description: Distance for creatures calling for assistance from other creatures without +# moving. +# Default: 10 - (Enabled) +# 0 - (Disabled) + +CreatureFamilyAssistanceRadius = 10 + +# +# CreatureFamilyAssistanceDelay +# Description: Time (in milliseconds) before creature assistance call. +# Default: 1500 - (1.5 Seconds) + +CreatureFamilyAssistanceDelay = 1500 + +# +# CreatureFamilyFleeDelay +# Description: Time (in milliseconds) during which creature can flee if no assistance was +# found. +# Default: 7000 (7 Seconds) + +CreatureFamilyFleeDelay = 7000 + +# +# WorldBossLevelDiff +# Description: World boss level difference. +# Default: 3 + +WorldBossLevelDiff = 3 + +# +# Corpse.Decay.NORMAL +# Corpse.Decay.RARE +# Corpse.Decay.ELITE +# Corpse.Decay.RAREELITE +# Corpse.Decay.WORLDBOSS +# Description: Time (in seconds) until creature corpse will decay if not looted or skinned. +# Default: 60 - (1 Minute, Corpse.Decay.NORMAL) +# 300 - (5 Minutes, Corpse.Decay.RARE) +# 300 - (5 Minutes, Corpse.Decay.ELITE) +# 300 - (5 Minutes, Corpse.Decay.RAREELITE) +# 3600 - (1 Hour, Corpse.Decay.WORLDBOSS) + +Corpse.Decay.NORMAL = 60 +Corpse.Decay.RARE = 300 +Corpse.Decay.ELITE = 300 +Corpse.Decay.RAREELITE = 300 +Corpse.Decay.WORLDBOSS = 3600 + +# +# Rate.Corpse.Decay.Looted +# Description: Multiplier for Corpse.Decay.* to configure how long creature corpses stay +# after they have been looted. +# Default: 0.5 + +Rate.Corpse.Decay.Looted = 0.5 + +# +# Rate.Creature.Normal.Damage +# Rate.Creature.Elite.Elite.Damage +# Rate.Creature.Elite.RARE.Damage +# Rate.Creature.Elite.RAREELITE.Damage +# Rate.Creature.Elite.WORLDBOSS.Damage +# Description: Mulitplier for creature melee damage. +# Default: 1 - (Rate.Creature.Normal.Damage) +# 1 - (Rate.Creature.Elite.Elite.Damage) +# 1 - (Rate.Creature.Elite.RARE.Damage) +# 1 - (Rate.Creature.Elite.RAREELITE.Damage) +# 1 - (Rate.Creature.Elite.WORLDBOSS.Damage) +# + +Rate.Creature.Normal.Damage = 1 +Rate.Creature.Elite.Elite.Damage = 1 +Rate.Creature.Elite.RARE.Damage = 1 +Rate.Creature.Elite.RAREELITE.Damage = 1 +Rate.Creature.Elite.WORLDBOSS.Damage = 1 + +# +# Rate.Creature.Normal.SpellDamage +# Rate.Creature.Elite.Elite.SpellDamage +# Rate.Creature.Elite.RARE.SpellDamage +# Rate.Creature.Elite.RAREELITE.SpellDamage +# Rate.Creature.Elite.WORLDBOSS.SpellDamage +# Description: Mulitplier for creature spell damage. +# Default: 1 - (Rate.Creature.Normal.SpellDamage) +# 1 - (Rate.Creature.Elite.Elite.SpellDamage) +# 1 - (Rate.Creature.Elite.RARE.SpellDamage) +# 1 - (Rate.Creature.Elite.RAREELITE.SpellDamage) +# 1 - (Rate.Creature.Elite.WORLDBOSS.SpellDamage) + +Rate.Creature.Normal.SpellDamage = 1 +Rate.Creature.Elite.Elite.SpellDamage = 1 +Rate.Creature.Elite.RARE.SpellDamage = 1 +Rate.Creature.Elite.RAREELITE.SpellDamage = 1 +Rate.Creature.Elite.WORLDBOSS.SpellDamage = 1 + +# +# Rate.Creature.Normal.HP +# Rate.Creature.Elite.Elite.HP +# Rate.Creature.Elite.RARE.HP +# Rate.Creature.Elite.RAREELITE.HP +# Rate.Creature.Elite.WORLDBOSS.HP +# Description: Mulitplier for creature health. +# Default: 1 - (Rate.Creature.Normal.HP) +# 1 - (Rate.Creature.Elite.Elite.HP) +# 1 - (Rate.Creature.Elite.RARE.HP) +# 1 - (Rate.Creature.Elite.RAREELITE.HP) +# 1 - (Rate.Creature.Elite.WORLDBOSS.HP) + +Rate.Creature.Normal.HP = 1 +Rate.Creature.Elite.Elite.HP = 1 +Rate.Creature.Elite.RARE.HP = 1 +Rate.Creature.Elite.RAREELITE.HP = 1 +Rate.Creature.Elite.WORLDBOSS.HP = 1 + +# +# Creature.PickPocketRefillDelay +# Description: Time in seconds that the server will wait before refilling the pickpocket loot +# for a creature +# Default: 600 + +Creature.PickPocketRefillDelay = 600 + +# +# ListenRange.Say +# Description: Distance in which players can read say messages from creatures or +# gameobjects. +# Default: 40 + +ListenRange.Say = 40 + +# +# ListenRange.TextEmote +# Description: Distance in which players can read emotes from creatures or gameobjects. +# Default: 40 + +ListenRange.TextEmote = 40 + +# +# ListenRange.Yell +# Description: Distance in which players can read yell messages from creatures or +# gameobjects. +# Default: 300 + +ListenRange.Yell = 300 + +# +# Creature.MovingStopTimeForPlayer +# Description: Time (in milliseconds) during which creature will not move after +# interaction with player. +# Default: 180000 + +Creature.MovingStopTimeForPlayer = 180000 + +# +################################################################################################### + +################################################################################################### +# CHAT SETTINGS +# +# ChatFakeMessagePreventing +# Description: Chat protection from creating fake messages using a lot spaces or other +# invisible symbols. Not applied to the addon language, but may break old +# addons that use normal languages for sending data to other clients. +# Default: 1 - (Enabled, Blizzlike) +# 0 - (Disabled) + +ChatFakeMessagePreventing = 1 + +# +# ChatStrictLinkChecking.Severity +# Description: Check chat messages for ingame links to spells, items, quests, etc. +# Default: 0 - (Disabled) +# 1 - (Enabled, Check if only valid pipe commands are used, Prevents posting +# pictures.) +# 2 - (Enabled, Verify that pipe commands are used in a correct order) +# 3 - (Check if color, entry and name don't contradict each other. For this to +# work correctly, please assure that you have extracted locale DBCs of +# every language specific client playing on this server) + +ChatStrictLinkChecking.Severity = 0 + +# +# ChatStrictLinkChecking.Kick +# Description: Defines what should be done if a message is considered to contain invalid +# pipe commands. +# Default: 0 - (Silently ignore message) +# 1 - (Disconnect players who sent malformed messages) + +ChatStrictLinkChecking.Kick = 0 + +# +# ChatFlood.MessageCount +# Description: Chat flood protection, number of messages before player gets muted. +# Default: 10 - (Enabled) +# 0 - (Disabled) + +ChatFlood.MessageCount = 10 + +# +# ChatFlood.MessageDelay +# Description: Time (in seconds) between messages to be counted into ChatFlood.MessageCount. +# Default: 1 + +ChatFlood.MessageDelay = 1 + +# +# ChatFlood.MuteTime +# Description: Time (in seconds) characters get muted for violating ChatFlood.MessageCount. +# Default: 10 + +ChatFlood.MuteTime = 10 + +# +# Channel.RestrictedLfg +# Description: Restrict LookupForGroup channel to characters registered in the LFG tool. +# Default: 1 - (Enabled, Allow join to channel only if registered in LFG) +# 0 - (Disabled, Allow join to channel in any time) + +Channel.RestrictedLfg = 1 + +# +# ChatLevelReq.Channel +# ChatLevelReq.Whisper +# ChatLevelReq.Say +# ChatLevelReq.Yell +# Description: Level requirement for characters to be able to use chats. +# Default: 1 + +ChatLevelReq.Channel = 1 +ChatLevelReq.Whisper = 1 +ChatLevelReq.Emote = 1 +ChatLevelReq.Say = 1 +ChatLevelReq.Yell = 1 + +# +# PartyLevelReq +# Description: Minimum level at which players can invite to group, even if they aren't on +# the invitee friends list. (Players who are on that friend list can always +# invite despite having lower level) +# Default: 1 + +PartyLevelReq = 1 + +# +# PreserveCustomChannels +# Description: Store custom chat channel settings like password, automatic ownership handout +# or ban list in the database. Needs to be enabled to save custom +# world/trade/etc. channels that have automatic ownership handout disabled. +# (.channel set ownership $channel off) +# Default: 0 - (Disabled, Blizzlike, Channel settings are lost if last person left) +# 1 - (Enabled) + +PreserveCustomChannels = 1 + +# +# PreserveCustomChannelDuration +# Description: Time (in days) that needs to pass before the customs chat channels get +# cleaned up from the database. Only channels with ownership handout enabled +# (default behavior) will be cleaned. +# Default: 14 - (Enabled, Clean channels that haven't been used for 14 days) +# 0 - (Disabled, Infinite channel storage) + +PreserveCustomChannelDuration = 14 + +# +################################################################################################### + +################################################################################################### +# GAME MASTER SETTINGS +# +# GM.LoginState +# Description: GM mode at login. +# Default: 2 - (Last save state) +# 0 - (Disable) +# 1 - (Enable) + +GM.LoginState = 2 + +# +# GM.Visible +# Description: GM visibility at login. +# Default: 2 - (Last save state) +# 0 - (Invisible) +# 1 - (Visible) + +GM.Visible = 2 + +# +# GM.Chat +# Description: GM chat mode at login. +# Default: 2 - (Last save state) +# 0 - (Disable) +# 1 - (Enable) + +GM.Chat = 2 + +# +# GM.WhisperingTo +# Description: Is GM accepting whispers from player by default or not. +# Default: 2 - (Last save state) +# 0 - (Disable) +# 1 - (Enable) + +GM.WhisperingTo = 2 + +# +# GM.FreezeAuraDuration +# Description: Allows to set a default duration to the Freeze Aura +# applied on players when using the .freeze command +# Default: 0 - (Original aura duration. Lasts until the .unfreeze command is used) +# N - (Aura duration if unspecified in .freeze command, in seconds) + +GM.FreezeAuraDuration = 0 + +# +# GM.InGMList.Level +# Description: Maximum GM level shown in GM list (if enabled) in non-GM state (.gm off). +# Default: 3 - (Anyone) +# 0 - (Only players) +# 1 - (Only moderators) +# 2 - (Only gamemasters) + +GM.InGMList.Level = 3 + +# +# GM.InWhoList.Level +# Description: Max GM level showed in who list (if visible). +# Default: 3 - (Anyone) +# 0 - (Only players) +# 1 - (Only moderators) +# 2 - (Only gamemasters) + +GM.InWhoList.Level = 3 + +# +# GM.StartLevel +# Description: GM character starting level. +# Default: 1 + +GM.StartLevel = 1 + +# +# GM.AllowInvite +# Description: Allow players to invite GM characters. +# Default: 0 - (Disabled) +# 1 - (Enabled) + +GM.AllowInvite = 0 + +# +# GM.LowerSecurity +# Description: Allow lower security levels to use commands on higher security level +# characters. +# Default: 0 - (Disabled) +# 1 - (Enabled) + +GM.LowerSecurity = 0 + +# +# GM.ForceShutdownThreshold +# Description: Minimum shutdown time in seconds before 'force' is required if other players are connected. +# Default: 30 + +GM.ForceShutdownThreshold = 30 + +# +################################################################################################### + +################################################################################################### +# SUPPORT SETTINGS +# +# Support.Enabled +# Description: Enable/disable the ticket system. This disables the whole customer +# support UI after trying to send a ticket in disabled state +# (MessageBox: "GM Help Tickets are currently unavailable."). +# UI remains disabled until the character relogs. +# Default: 1 - (Enabled) +# 0 - (Disabled) + +Support.Enabled = 1 + +# +# Support.TicketsEnabled +# Description: Allow/disallow opening new tickets. +# Default: 0 - (Disabled) +# 1 - (Enabled, Experimental) + +Support.TicketsEnabled = 0 + +# +# Support.BugsEnabled +# Description: Allow/disallow opening new bug reports. +# Default: 0 - (Disabled) +# 1 - (Enabled, Experimental) + +Support.BugsEnabled = 0 + +# +# Support.ComplaintsEnabled +# Description: Allow/disallow creating new player complaints. +# Default: 0 - (Disabled) +# 1 - (Enabled, Experimental) + +Support.ComplaintsEnabled = 0 + +# +# Support.SuggestionsEnabled +# Description: Allow/disallow opening new suggestion reports. +# Default: 0 - (Disabled) +# 1 - (Enabled, Experimental) + +Support.SuggestionsEnabled = 0 + +# +################################################################################################### + +################################################################################################### +# VISIBILITY AND DISTANCES +# +# Visibility.GroupMode +# Description: Group visibility modes. Defines which groups can aways detect invisible +# characters of the same raid, group or faction. +# Default: 1 - (Raid) +# 0 - (Party) +# 2 - (Faction) + +Visibility.GroupMode = 1 + +# +# Visibility.Distance.Continents +# Visibility.Distance.Instances +# Visibility.Distance.BGArenas +# Description: Visibility distance to see other players or gameobjects. +# Visibility on continents on retail ~90 yards. In BG/Arenas ~533. +# For instances default ~170. +# Max limited by grid size: 533.33333 +# Min limit is max aggro radius (45) * Rate.Creature.Aggro +# Default: 90 - (Visibility.Distance.Continents) +# 170 - (Visibility.Distance.Instances) +# 533 - (Visibility.Distance.BGArenas) + +Visibility.Distance.Continents = 90 +Visibility.Distance.Instances = 170 +Visibility.Distance.BGArenas = 533 + +# +# Visibility.Notify.Period.OnContinents +# Visibility.Notify.Period.InInstances +# Visibility.Notify.Period.InBGArenas +# Description: Time (in milliseconds) for visibility update period. Lower values may have +# performance impact. +# Default: 1000 - (Visibility.Notify.Period.OnContinents) +# 1000 - (Visibility.Notify.Period.InInstances) +# 1000 - (Visibility.Notify.Period.InBGArenas) + +Visibility.Notify.Period.OnContinents = 1000 +Visibility.Notify.Period.InInstances = 1000 +Visibility.Notify.Period.InBGArenas = 1000 + +# +################################################################################################### + +################################################################################################### +# SERVER RATES +# +# Rate.Health +# Rate.Mana +# Rate.Rage.Income +# Rate.Rage.Loss +# Rate.RunicPower.Income +# Rate.RunicPower.Loss +# Rate.Focus +# Rate.Energy +# Description: Multiplier to configure health and power increase or decrease. +# Default: 1 for all regen rates + +Rate.Health = 1 +Rate.Mana = 1 +Rate.Rage.Gain = 1 +Rate.Rage.Loss = 1 +Rate.Focus = 1 +Rate.Energy = 1 +Rate.ComboPoints.Loss = 1 +Rate.RunicPower.Gain = 1 +Rate.RunicPower.Loss = 1 +Rate.SoulShards.Loss = 1 +Rate.LunarPower.Loss = 1 +Rate.HolyPower.Loss = 1 +Rate.Maelstrom.Loss = 1 +Rate.Chi.Loss = 1 +Rate.Insanity.Loss = 1 +Rate.ArcaneCharges.Loss= 1 +Rate.Fury.Loss = 1 +Rate.Pain.Loss = 1 + +# +# Rate.Skill.Discovery +# Description: Multiplier for skill discovery. +# Default: 1 + +Rate.Skill.Discovery = 1 + +# +# Rate.Drop.Item.Poor +# Rate.Drop.Item.Normal +# Rate.Drop.Item.Uncommon +# Rate.Drop.Item.Rare +# Rate.Drop.Item.Epic +# Rate.Drop.Item.Legendary +# Rate.Drop.Item.Artifact +# Rate.Drop.Item.Referenced +# Rate.Drop.Money +# Description: Drop rates for money and items based on quality. +# Default: 1 - (Rate.Drop.Item.Poor) +# 1 - (Rate.Drop.Item.Normal) +# 1 - (Rate.Drop.Item.Uncommon) +# 1 - (Rate.Drop.Item.Rare) +# 1 - (Rate.Drop.Item.Epic) +# 1 - (Rate.Drop.Item.Legendary) +# 1 - (Rate.Drop.Item.Artifact) +# 1 - (Rate.Drop.Item.Referenced) +# 1 - (Rate.Drop.Money) + +Rate.Drop.Item.Poor = 1 +Rate.Drop.Item.Normal = 1 +Rate.Drop.Item.Uncommon = 1 +Rate.Drop.Item.Rare = 1 +Rate.Drop.Item.Epic = 1 +Rate.Drop.Item.Legendary = 1 +Rate.Drop.Item.Artifact = 1 +Rate.Drop.Item.Referenced = 1 +Rate.Drop.Money = 1 + +# +# Rate.Drop.Item.ReferencedAmount +# Description: Multiplier for referenced loot amount. +# Default: 1 + +Rate.Drop.Item.ReferencedAmount = 1 + +# +# Rate.XP.Kill +# Rate.XP.Quest +# Rate.XP.Explore +# Description: Experience rates. +# Default: 1 - (Rate.XP.Kill, affects only kills outside of Battlegrounds) +# 1 - (Rate.XP.Quest) +# 1 - (Rate.XP.Explore) + +Rate.XP.Kill = 1 +Rate.XP.Quest = 1 +Rate.XP.Explore = 1 + +# +# Rate.XP.BattlegroundKill +# Description: Experience rate for honorable kills in battlegrounds, +# it works when Battleground.GiveXPForKills = 1 +# Default: 1 + +Rate.XP.BattlegroundKill = 1 + +# +# Rate.Quest.Money.Reward +# Rate.Quest.Money.Max.Level.Reward +# Description: Multiplier for money quest rewards. Can not be below 0. +# Default: 1 + +Rate.Quest.Money.Reward = 1 +Rate.Quest.Money.Max.Level.Reward = 1 + +# +# Rate.RepairCost +# Description: Repair cost rate. +# Default: 1 + +Rate.RepairCost = 1 + +# +# Rate.Rest.InGame +# Rate.Rest.Offline.InTavernOrCity +# Rate.Rest.Offline.InWilderness +# Description: Resting points grow rates. +# Default: 1 - (Rate.Rest.InGame) +# 1 - (Rate.Rest.Offline.InTavernOrCity) +# 1 - (Rate.Rest.Offline.InWilderness) + +Rate.Rest.InGame = 1 +Rate.Rest.Offline.InTavernOrCity = 1 +Rate.Rest.Offline.InWilderness = 1 + +# +# Rate.Damage.Fall +# Description: Damage after fall rate. +# Default: 1 + +Rate.Damage.Fall = 1 + +# +# Rate.Auction.Time +# Rate.Auction.Deposit +# Rate.Auction.Cut +# Description: Auction rates (auction time, deposit get at auction start, +# auction cut from price at auction end). +# Default: 1 - (Rate.Auction.Time) +# 1 - (Rate.Auction.Deposit) +# 1 - (Rate.Auction.Cut) + +Rate.Auction.Time = 1 +Rate.Auction.Deposit = 1 +Rate.Auction.Cut = 1 + +# +# Rate.Honor +# Description: Honor gain rate. +# Default: 1 + +Rate.Honor = 1 + +# +# Rate.Talent +# Description: Talent point rate. +# Default: 1 + +Rate.Talent = 1 + +# +# Rate.Reputation.Gain +# Description: Reputation gain rate. +# Default: 1 + +Rate.Reputation.Gain = 1 + +# +# Rate.Reputation.LowLevel.Kill +# Description: Reputation gain from killing low level (grey) creatures. +# Default: 1 + +Rate.Reputation.LowLevel.Kill = 1 + +# +# Rate.Reputation.LowLevel.Quest +# Description: Reputation gain rate. +# Default: 1 + +Rate.Reputation.LowLevel.Quest = 1 + +# +# Rate.Reputation.RecruitAFriendBonus +# Description: Reputation bonus rate for recruit-a-friend. +# Default: 0.1 + +Rate.Reputation.RecruitAFriendBonus = 0.1 + +# +# Rate.MoveSpeed +# Description: Movement speed rate. +# Default: 1 + +Rate.MoveSpeed = 1 + +# +# Rate.InstanceResetTime +# Description: Multiplier for the rate between global raid/heroic instance resets +# (dbc value). Higher value increases the time between resets, +# lower value lowers the time, you need clean instance_reset in +# characters db in order to let new values work. +# Default: 1 + +Rate.InstanceResetTime = 1 + +# +# SkillGain.Crafting +# SkillGain.Gathering +# Description: Crafting/defense/gathering/weapon skills gain rate. +# Default: 1 - (SkillGain.Crafting) +# 1 - (SkillGain.Gathering) + +SkillGain.Crafting = 1 +SkillGain.Gathering = 1 + +# +# SkillChance.Orange +# SkillChance.Yellow +# SkillChance.Green +# SkillChance.Grey +# Description: Chance to increase skill based on recipe color. +# Default: 100 - (SkillChance.Orange) +# 75 - (SkillChance.Yellow) +# 25 - (SkillChance.Green) +# 0 - (SkillChance.Grey) + +SkillChance.Orange = 100 +SkillChance.Yellow = 75 +SkillChance.Green = 25 +SkillChance.Grey = 0 + +# +# SkillChance.MiningSteps +# SkillChance.SkinningSteps +# Description: Skinning and Mining chance decreases with skill level. +# Default: 0 - (Disabled) +# 75 - (In 2 times each 75 skill points) + +SkillChance.MiningSteps = 0 +SkillChance.SkinningSteps = 0 + +# +# DurabilityLoss.InPvP +# Description: Durability loss on death during PvP. +# Default: 0 - (Disabled) +# 1 - (Enabled) + +DurabilityLoss.InPvP = 0 + +# +# DurabilityLoss.OnDeath +# Description: Durability loss percentage on death. +# Default: 10 + +DurabilityLoss.OnDeath = 10 + +# +# DurabilityLossChance.Damage +# Description: Chance to lose durability on one equipped item from damage. +# Default: 0.5 - (100/0.5 = 200, Each 200 damage one equipped item will use durability) + +DurabilityLossChance.Damage = 0.5 + +# +# DurabilityLossChance.Absorb +# Description: Chance to lose durability on one equipped armor item when absorbing damage. +# Default: 0.5 - (100/0.5 = 200, Each 200 absorbed damage one equipped item will lose +# durability) + +DurabilityLossChance.Absorb = 0.5 + +# +# DurabilityLossChance.Parry +# Description: Chance to lose durability on main weapon when parrying attacks. +# Default: 0.05 - (100/0.05 = 2000, Each 2000 parried damage the main weapon will lose +# durability) + +DurabilityLossChance.Parry = 0.05 + +# +# DurabilityLossChance.Block +# Description: Chance to lose durability on shield when blocking attacks. +# Default: 0.05 - (100/0.05 = 2000, Each 2000 blocked damage the shield will lose +# durability) + +DurabilityLossChance.Block = 0.05 + +# +# Death.SicknessLevel +# Description: Starting level for resurrection sickness. +# Example: 11 - (Level 1-10 characters will not be affected, +# Level 11-19 characters will be affected for 1 minute, +# Level 20-MaxPlayerLevel characters will be affected for 10 minutes) +# Default: 11 - (Enabled, See Example) +# MaxPlayerLevel+1 - (Disabled) +# -10 - (Enabled, Level 1+ characters have 10 minute duration) + +Death.SicknessLevel = 11 + +# +# Death.CorpseReclaimDelay.PvP +# Death.CorpseReclaimDelay.PvE +# Description: Increase corpse reclaim delay at PvP/PvE deaths. +# Default: 1 - (Enabled) +# 0 - (Disabled) + +Death.CorpseReclaimDelay.PvP = 1 +Death.CorpseReclaimDelay.PvE = 0 + +# +# Death.Bones.World +# Death.Bones.BattlegroundOrArena +# Description: Create bones instead of corpses at resurrection in normal zones, instances, +# battleground or arenas. +# Default: 1 - (Enabled, Death.Bones.World) +# 1 - (Enabled, Death.Bones.BattlegroundOrArena) +# 0 - (Disabled) + +Death.Bones.World = 1 +Death.Bones.BattlegroundOrArena = 1 + +# +# Die.Command.Mode +# Description: Do not trigger things like loot from .die command. +# Default: 1 - (Enabled) +# 0 - (Disabled) + +Die.Command.Mode = 1 + +# +################################################################################################### + +################################################################################################### +# STATS LIMITS +# +# Stats.Limits.Enable +# Description: Enable or disable stats system. +# Default: 0 - Disabled + +Stats.Limits.Enable = 0 + +# +# Stats.Limit.[STAT] +# Description: Set percentage limit for dodge, parry, block and crit rating. +# Default: 95.0 (95%) + +Stats.Limits.Dodge = 95.0 +Stats.Limits.Parry = 95.0 +Stats.Limits.Block = 95.0 +Stats.Limits.Crit = 95.0 + +# +################################################################################################### + +################################################################################################### +# AUTO BROADCAST +# +# AutoBroadcast.On +# Description: Enable auto broadcast. +# Default: 0 - (Disabled) +# 1 - (Enabled) + +AutoBroadcast.On = 0 + +# +# AutoBroadcast.Center +# Description: Auto broadcasting display method. +# Default: 0 - (Announce) +# 1 - (Notify) +# 2 - (Both) + +AutoBroadcast.Center = 0 + +# +# AutoBroadcast.Timer +# Description: Timer (in milliseconds) for auto broadcasts. +# Default: 600000 - (10 minutes) + +AutoBroadcast.Timer = 600000 + +# +################################################################################################### + +################################################################################################### +# BATTLEGROUND CONFIG +# +# Battleground.CastDeserter +# Description: Cast Deserter spell at players who leave battlegrounds in progress. +# Default: 1 - (Enabled) +# 0 - (Disabled) + +Battleground.CastDeserter = 1 + +# +# Battleground.QueueAnnouncer.Enable +# Description: Announce battleground queue status to chat. +# Default: 0 - (Disabled) +# 1 - (Enabled) + +Battleground.QueueAnnouncer.Enable = 0 + +# +# Battleground.QueueAnnouncer.PlayerOnly +# Description: Battleground queue announcement type. +# Default: 0 - (System message, Anyone can see it) +# 1 - (Private, Only queued players can see it) + +Battleground.QueueAnnouncer.PlayerOnly = 0 + +# +# Battleground.StoreStatistics.Enable +# Description: Store Battleground scores in the database. +# Default: 0 - (Disabled) +# 1 - (Enabled) + +Battleground.StoreStatistics.Enable = 0 + +# +# Battleground.InvitationType +# Description: Set Battleground invitation type. +# Default: 0 - (Normal, Invite as much players to battlegrounds as queued, +# Don't bother with balance) +# 1 - (Experimental, Don't allow to invite much more players +# of one faction) +# 2 - (Experimental, Try to have even teams) + +Battleground.InvitationType = 0 + +# +# Battleground.PrematureFinishTimer +# Description: Time (in milliseconds) before battleground will end prematurely if there are +# not enough players on one team. (Values defined in battleground template) +# Default: 300000 - (Enabled, 5 minutes) +# 0 - (Disabled, Not recommended) + +Battleground.PrematureFinishTimer = 300000 + +# +# Battleground.PremadeGroupWaitForMatch +# Description: Time (in milliseconds) a pre-made group has to wait for matching group of the +# other faction. +# Default: 1800000 - (Enabled, 30 minutes) +# 0 - (Disabled, Not recommended) + +Battleground.PremadeGroupWaitForMatch = 1800000 + +# +# Battleground.GiveXPForKills +# Description: Give experience for honorable kills in battlegrounds, +# the rate can be changed in the Rate.XP.BattlegroundKill setting. +# Default: 0 - (Disabled) +# 1 - (Enabled) + +Battleground.GiveXPForKills = 0 + +# +# Battleground.Random.ResetHour +# Description: Hour of the day when the global instance resets occur. +# Range: 0-23 +# Default: 6 - (06:00 AM) + +Battleground.Random.ResetHour = 6 + +# +# Battleground.RewardWinnerHonorFirst +# Battleground.RewardWinnerConquestFirst +# Battleground.RewardWinnerHonorLast +# Battleground.RewardWinnerConquestLast +# Battleground.RewardLoserHonorFirst +# Battleground.RewardLoserHonorLast +# Description: Random Battlegrounds / call to the arms rewards. +# Default: 30 - Battleground.RewardWinnerHonorFirst +# 25 - Battleground.RewardWinnerArenaFirst +# 15 - Battleground.RewardWinnerHonorLast +# 0 - Battleground.RewardWinnerArenaLast +# 5 - Battleground.RewardLoserHonorFirst +# 5 - Battleground.RewardLoserHonorLast +# + +Battleground.RewardWinnerHonorFirst = 27000 +Battleground.RewardWinnerConquestFirst = 10000 +Battleground.RewardWinnerHonorLast = 13500 +Battleground.RewardWinnerConquestLast = 5000 +Battleground.RewardLoserHonorFirst = 4500 +Battleground.RewardLoserHonorLast = 3500 + +# +# Battleground.ReportAFK +# Description: Number of reports needed to kick someone AFK from Battleground. +# Range: 1-9 +# Default: 3 + +Battleground.ReportAFK = 3 + +# +################################################################################################### + +################################################################################################### +# BATTLEFIELD CONFIG +# +# Wintergrasp.Enable +# Description: Enable the Wintergrasp battlefield. +# Default: 0 - (Disabled) +# 1 - (Enabled, Experimental as in incomplete, bugged and with crashes) + +Wintergrasp.Enable = 0 + +# +# Wintergrasp.PlayerMax +# Description: Maximum number of players allowed in Wintergrasp. +# Default: 100 + +Wintergrasp.PlayerMax = 100 + +# +# Wintergrasp.PlayerMin +# Description: Minimum number of players required for Wintergrasp. +# Default: 0 + +Wintergrasp.PlayerMin = 0 + +# +# Wintergrasp.PlayerMinLvl +# Description: Required character level for the Wintergrasp battle. +# Default: 77 + +Wintergrasp.PlayerMinLvl = 77 + +# +# Wintergrasp.BattleTimer +# Description: Time (in minutes) for the Wintergrasp battle to last. +# Default: 30 + +Wintergrasp.BattleTimer = 30 + +# +# Wintergrasp.NoBattleTimer +# Description: Time (in minutes) between Wintergrasp battles. +# Default: 150 + +Wintergrasp.NoBattleTimer = 150 + +# +# Wintergrasp.CrashRestartTimer +# Description: Time (in minutes) to delay the restart of Wintergrasp if the world server +# crashed during a running battle. +# Default: 10 + +Wintergrasp.CrashRestartTimer = 10 + +# +################################################################################################### + +################################################################################################### +# ARENA CONFIG +# +# Arena.MaxRatingDifference +# Description: Maximum rating difference between two teams in rated matches. +# Default: 150 - (Enabled) +# 0 - (Disabled) + +Arena.MaxRatingDifference = 150 + +# +# Arena.RatingDiscardTimer +# Description: Time (in milliseconds) after which rating differences are ignored when +# setting up matches. +# Default: 600000 - (Enabled, 10 minutes) +# 0 - (Disabled) + +Arena.RatingDiscardTimer = 600000 + +# +# Arena.RatedUpdateTimer +# Description: Time (in milliseconds) between checks for matchups in rated arena. +# Default: 5000 - (5 seconds) + +Arena.RatedUpdateTimer = 5000 + +# +# Arena.AutoDistributePoints +# Description: Automatically distribute arena points. +# Default: 0 - (Disabled) +# 1 - (Enabled) + +Arena.AutoDistributePoints = 0 + +# +# Arena.AutoDistributeInterval +# Description: Time (in days) how often arena points should be distributed if automatic +# distribution is enabled. +# Default: 7 - (Weekly) + +Arena.AutoDistributeInterval = 7 + +# +# Arena.QueueAnnouncer.Enable +# Description: Announce arena queue status to chat. +# Default: 0 - (Disabled) +# 1 - (Enabled) + +Arena.QueueAnnouncer.Enable = 0 + +# +# Arena.ArenaSeason.ID +# Description: Current arena season id shown in clients. +# Default: 15 + +Arena.ArenaSeason.ID = 15 + +# +# Arena.ArenaSeason.InProgress +# Description: State of current arena season. +# Default: 1 - (Active) +# 0 - (Finished) + +Arena.ArenaSeason.InProgress = 1 + +# +# Arena.ArenaStartRating +# Description: Start rating for new arena teams. +# Default: 0 + +Arena.ArenaStartRating = 0 + +# +# Arena.ArenaStartPersonalRating +# Description: Start personal rating when joining a team. +# Default: 0 + +Arena.ArenaStartPersonalRating = 0 + +# +# Arena.ArenaStartMatchmakerRating +# Description: Start matchmaker rating for players. +# Default: 1500 + +Arena.ArenaStartMatchmakerRating = 1500 + +# +# Arena.ArenaWinRatingModifier1 +# Description: Modifier of rating addition when winner team rating is less than 1300 +# be aware that from 1000 to 1300 it gradually decreases automatically down to the half of it +# (increasing this value will give more rating) +# Default: 48 + +Arena.ArenaWinRatingModifier1 = 48 + +# +# Arena.ArenaWinRatingModifier2 +# Description: Modifier of rating addition when winner team rating is equal or more than 1300 +# (increasing this value will give more rating) +# Default: 24 + +Arena.ArenaWinRatingModifier2 = 24 + + +# +# Arena.ArenaLoseRatingModifier +# Description: Modifier of rating subtraction for loser team +# (increasing this value will subtract more rating) +# Default: 24 + +Arena.ArenaLoseRatingModifier = 24 + +# +# Arena.ArenaMatchmakerRatingModifier +# Description: Modifier of matchmaker rating +# Default: 24 + +Arena.ArenaMatchmakerRatingModifier = 24 + +# +# ArenaLog.ExtendedInfo +# Description: Include extended info to ArenaLogFile for each player after rated arena +# matches (guid, name, team, IP, healing/damage done, killing blows). +# Default: 0 - (Disabled) +# 1 - (Enabled) + +ArenaLog.ExtendedInfo = 0 + +# +################################################################################################### + +################################################################################################### +# NETWORK CONFIG +# +# Network.Threads (NYI) +# Description: Number of threads for network. +# Default: 1 - (Recommended 1 thread per 1000 connections) + +Network.Threads = 1 + +# +# Network.OutKBuff (NYI) +# Description: Amount of memory (in bytes) used for the output kernel buffer (see SO_SNDBUF +# socket option, TCP manual). +# Default: -1 - (Use system default setting) + +Network.OutKBuff = -1 + +# +# Network.OutUBuff (NYI) +# Description: Amount of memory (in bytes) reserved in the user space per connection for +# output buffering. +# Default: 65536 + +Network.OutUBuff = 65536 + +# +# Network.TcpNoDelay: +# Description: TCP Nagle algorithm setting. +# Default: 0 - (Enabled, Less traffic, More latency) +# 1 - (Disabled, More traffic, Less latency, TCP_NO_DELAY) + +Network.TcpNodelay = 1 + +# +################################################################################################### + +################################################################################################### +# CONSOLE AND REMOTE ACCESS +# +# Console.Enable (NYI) +# Description: Enable console. +# Default: 1 - (Enabled) +# 0 - (Disabled) + +Console.Enable = 1 + +# +# Ra.Enable (NYI) +# Description: Enable remote console (telnet). +# Default: 0 - (Disabled) +# 1 - (Enabled) + +Ra.Enable = 0 + +# +# Ra.IP (NYI) +# Description: Bind remote access to IP/hostname. +# Default: "0.0.0.0" - (Bind to all IPs on the system) + +Ra.IP = "0.0.0.0" + +# +# Ra.Port (NYI) +# Description: TCP port to reach the remote console. +# Default: 3443 + +Ra.Port = 3443 + +# +# Ra.MinLevel (NYI) +# Description: Required security level to use the remote console. +# Default: 3 + +Ra.MinLevel = 3 + +# +# SOAP.Enable (NYI) +# Description: Enable soap service. +# Default: 0 - (Disabled) +# 1 - (Enabled) + +SOAP.Enabled = 0 + +# +# SOAP.IP (NYI) +# Description: Bind SOAP service to IP/hostname. +# Default: "127.0.0.1" - (Bind to localhost) + +SOAP.IP = "127.0.0.1" + +# +# SOAP.Port (NYI) +# Description: TCP port to reach the SOAP service. +# Default: 7878 + +SOAP.Port = 7878 + +# +################################################################################################### + +################################################################################################### +# CHARACTER DELETE OPTIONS +# +# CharDelete.Method +# Description: Character deletion behavior. +# Default: 0 - (Completely remove character from the database) +# 1 - (Unlink the character from account and free up the name, Appears as +# deleted ingame) + +CharDelete.Method = 0 + +# +# CharDelete.MinLevel +# Description: Required level to use the unlinking method if enabled for non-heroic classes. +# Default: 0 - (Same method for every level) +# 1+ - (Only characters with the specified level will use the unlinking method) + +CharDelete.MinLevel = 0 + +# +# CharDelete.DeathKnight.MinLevel +# Description: Required level to use the unlinking method if enabled for death knights. +# Default: 0 - (Same method for every level) +# 1+ - (Only characters with the specified level will use the unlinking method) + +CharDelete.DeathKnight.MinLevel = 0 + +# +# CharDelete.DemonHunter.MinLevel +# Description: Required level to use the unlinking method if enabled for demon hunters. +# Default: 0 - (Same method for every level) +# 1+ - (Only characters with the specified level will use the unlinking method) + +CharDelete.DemonHunter.MinLevel = 0 + +# +# CharDelete.KeepDays +# Description: Time (in days) before unlinked characters will be removed from the database. +# Default: 30 - (Enabled) +# 0 - (Disabled, Don't delete any characters) + +CharDelete.KeepDays = 30 + +# +################################################################################################### + +################################################################################################### +# CUSTOM SERVER OPTIONS +# +# AllowTrackBothResources +# Description: Allows players to track herbs and minerals at the same time (if they have the skills) +# Default: 0 - (Do not allow) +# 1 - (Allow) +# +# Note: The following are client limitations and cannot be coded for: +# * The minimap tracking icon will display whichever skill is activated second. +# * The minimap tracking list will only show a check mark next to the last skill activated (sometimes this +# bugs out and doesn't switch the check mark. It has no effect on the actual tracking though). +# * The minimap dots are yellow for both resources. + +AllowTrackBothResources = 0 + +# +# PlayerStart.AllReputation +# Description: Players will start with most of the high level reputations that are needed +# for items, mounts etc. +# Default: 0 - (Disabled) +# 1 - (Enabled) + +PlayerStart.AllReputation = 0 + +# +# PlayerStart.AllSpells +# Description: If enabled, players will start with all their class spells (not talents). +# You must populate playercreateinfo_spell_custom table with the spells you +# want, or this will not work! The table has data for all classes / races up +# to TBC expansion. +# Default: 0 - (Disabled) +# 1 - (Enabled) + +PlayerStart.AllSpells = 0 + +# +# PlayerStart.MapsExplored +# Description: Characters start with all maps explored. +# Default: 0 - (Disabled) +# 1 - (Enabled) + +PlayerStart.MapsExplored = 0 + +# +# HonorPointsAfterDuel +# Description: Amount of honor points the duel winner will get after a duel. +# Default: 0 - (Disabled) +# 1+ - (Enabled) + +HonorPointsAfterDuel = 0 + +# +# ResetDuelCooldowns +# Description: Reset all cooldowns before duel starts and restore them when duel ends. +# Default: 0 - (Disabled) +# 1 - (Enabled) + +ResetDuelCooldowns = 0 + +# ResetDuelHealthMana +# Description: Reset health and mana before duel starts and restore them when duel ends. +# Default: 0 - (Disabled) +# 1 - (Enabled) + +ResetDuelHealthMana = 0 + +# +# AlwaysMaxWeaponSkill +# Description: Players will automatically gain max weapon/defense skill when logging in, +# or leveling. +# Default: 0 - (Disabled) +# 1 - (Enabled) + +AlwaysMaxWeaponSkill = 0 + +# +# PvPToken.Enable +# Description: Character will receive a token after defeating another character that yields +# honor. +# Default: 0 - (Disabled) +# 1 - (Enabled) + +PvPToken.Enable = 0 + +# +# PvPToken.MapAllowType +# Description: Define where characters can receive tokens. +# Default: 4 - (All maps) +# 3 - (Battlegrounds) +# 2 - (FFA areas only like Gurubashi arena) +# 1 - (Battlegrounds and FFA areas) + +PvPToken.MapAllowType = 4 + +# +# PvPToken.ItemID +# Description: Item characters will receive after defeating another character if PvP Token +# system is enabled. +# Default: 29434 - (Badge of justice) + +PvPToken.ItemID = 29434 + +# +# PvPToken.ItemCount +# Description: Number of tokens a character will receive. +# Default: 1 + +PvPToken.ItemCount = 1 + +# +# NoResetTalentsCost +# Description: Resetting talents doesn't cost anything. +# Default: 0 - (Disabled) +# 1 - (Enabled) + +NoResetTalentsCost = 0 + +# +# Guild.AllowMultipleGuildMaster +# Description: Allow more than one guild master. Additional Guild Masters must be set using +# the ".guild rank" command. +# Default: 0 - (Disabled) +# 1 - (Enabled) + +Guild.AllowMultipleGuildMaster = 0 + +# +# ShowKickInWorld +# Description: Determines whether a message is broadcasted to the entire server when a +# player gets kicked. +# Default: 0 - (Disabled) +# 1 - (Enabled) + +ShowKickInWorld = 0 + +# ShowMuteInWorld +# Description: Determines whether a message is broadcasted to the entire server when a +# player gets muted. +# Default: 0 - (Disabled) +# 1 - (Enabled) + +ShowMuteInWorld = 0 + +# +# ShowBanInWorld +# Description: Determines whether a message is broadcasted to the entire server when a +# player gets banned. +# Default: 0 - (Disabled) +# 1 - (Enabled) + +ShowBanInWorld = 0 + +# +# RecordUpdateTimeDiffInterval +# Description: Time (in milliseconds) update time diff is written to the log file. +# Update diff can be used as a performance indicator. Diff < 300: good +# performance. Diff > 600 bad performance, may be caused by high CPU usage. +# Default: 60000 - (Enabled, 1 minute) +# 0 - (Disabled) + +RecordUpdateTimeDiffInterval = 60000 + +# +# MinRecordUpdateTimeDiff +# Description: Only record update time diff which is greater than this value. +# Default: 100 + +MinRecordUpdateTimeDiff = 100 + +# +# PlayerStart.String +# Description: String to be displayed at first login of newly created characters. +# Default: "" - (Disabled) + +PlayerStart.String = "" + +# +# LevelReq.Trade +# Description: Level requirement for characters to be able to trade. +# Default: 1 + +LevelReq.Trade = 1 + +# +# LevelReq.Auction +# Description: Level requirement for characters to be able to use the auction house. +# Default: 1 + +LevelReq.Auction = 1 + +# +# LevelReq.Mail +# Description: Level requirement for characters to be able to send and receive mails. +# Default: 1 + +LevelReq.Mail = 1 + +# +# PlayerDump.DisallowPaths +# Description: Disallow using paths in PlayerDump output files +# Default: 1 + +PlayerDump.DisallowPaths = 1 + +# +# PlayerDump.DisallowOverwrite +# Description: Disallow overwriting existing files with PlayerDump +# Default: 1 + +PlayerDump.DisallowOverwrite = 1 + +# +# UI.ShowQuestLevelsInDialogs +# Description: Show quest levels next to quest titles in UI dialogs +# Example: [13] Westfall Stew +# Default: 0 - (Do not show) + +UI.ShowQuestLevelsInDialogs = 0 + +# +# Calculate.Creature.Zone.Area.Data +# Description: Calculate at loading creature zoneId / areaId and save in creature table (WARNING: SLOW WORLD SERVER STARTUP) +# Default: 0 - (Do not show) + +Calculate.Creature.Zone.Area.Data = 0 + +# +# Calculate.Gameoject.Zone.Area.Data +# Description: Calculate at loading gameobject zoneId / areaId and save in gameobject table (WARNING: SLOW WORLD SERVER STARTUP) +# Default: 0 - (Do not show) + +Calculate.Gameoject.Zone.Area.Data = 0 + +# +# NoGrayAggro +# Description: Gray mobs will not aggro players above/below some levels +# NoGrayAggro.Above: If player is at this level or above, gray mobs will not attack +# NoGrayAggro.Below: If player is at this level or below, gray mobs will not attack +# Example: You can for example make players free from gray until they reach level 30. +# Then gray will start to attack them, until they reach max level (80 for example): +# NoGrayAggro.Above = 80 +# NoGrayAggro.Below = 29 +# Default: 0 - (Blizzlike) +# + +NoGrayAggro.Above = 0 +NoGrayAggro.Below = 0 + +# +# PreventRenameCharacterOnCustomization +# Description: If option is set to 1, player can not rename the character in character customization. +# Applies to all character customization commands. +# Default: 0 - (Disabled, character can be renamed in Character Customization) +# 1 - (Enabled, character can not be renamed in Character Customization) +# + +PreventRenameCharacterOnCustomization = 0 + +# +# Creature.CheckInvalidPosition +# Description: Check possible invalid position for creatures at startup (WARNING: SLOW WORLD SERVER STARTUP) +# Default: 0 - (Do not show) + +Creature.CheckInvalidPosition = 0 + +# +# GameObject.CheckInvalidPosition +# Description: Check possible invalid position for game objects at startup (WARNING: SLOW WORLD SERVER STARTUP) +# Default: 0 - (Do not show) + +GameObject.CheckInvalidPosition = 0 + +# +################################################################################################### + +################################################################################################### +# AUCTION HOUSE BOT SETTINGS +# +# AuctionHouseBot.Update.Interval +# Description: Interval in seconds for AHBot to get updated +# Default: 20 +# + +AuctionHouseBot.Update.Interval = 20 + +# +# AuctionHouseBot.Seller.Enabled +# Description: General enable or disable AuctionHouseBot Seller functionality +# Default: 0 - (Disabled) +# 1 - (Enabled) + +AuctionHouseBot.Seller.Enabled = 0 + +# +# AuctionHouseBot.Alliance.Items.Amount.Ratio +# Description: Enable/Disable (disabled if 0) the part of AHBot that puts items up for auction on Alliance AH +# Default: 100 - (Enabled with 100% of items specified in AuctionHouse.Items.Amount.color section) + +AuctionHouseBot.Alliance.Items.Amount.Ratio = 100 + +# +# AuctionHouseBot.Horde.Items.Amount.Ratio +# Enable/Disable (disabled if 0) the part of AHBot that puts items up for auction on Horde AH +# Default: 100 (Enabled with 100% of items specified in AuctionHouse.Items.Amount.color section) + +AuctionHouseBot.Horde.Items.Amount.Ratio = 100 + +# +# AuctionHouseBot.Neutral.Items.Amount.Ratio +# Description: Enable/Disable (disabled if 0) the part of AHBot that puts items up for auction on Neutral AH +# Default: 100 - (Enabled with 100% of items specified in AuctionHouse.Items.Amount.color section) + +AuctionHouseBot.Neutral.Items.Amount.Ratio = 100 + +# +# AuctionHouseBot.MinTime +# Description: Minimum time for the new auction in hours +# Default: 1 - (Hour) + +AuctionHouseBot.MinTime = 1 + +# +# AuctionHouseBot.MaxTime +# Description: Maximum time for the new auction in hours +# Default: 72 - (Hours) + +AuctionHouseBot.MaxTime = 72 + +# +# AuctionHouseBot.Class.CLASS.Allow.Zero = 0 +# Description: Include items without a sell or buy price. +# Default: 0 - (Disabled) +# 1 - (Enabled) + +AuctionHouseBot.Class.Consumable.Allow.Zero = 0 +AuctionHouseBot.Class.Container.Allow.Zero = 0 +AuctionHouseBot.Class.Weapon.Allow.Zero = 0 +AuctionHouseBot.Class.Gem.Allow.Zero = 0 +AuctionHouseBot.Class.Armor.Allow.Zero = 0 +AuctionHouseBot.Class.Reagent.Allow.Zero = 0 +AuctionHouseBot.Class.Projectile.Allow.Zero = 0 +AuctionHouseBot.Class.TradeGood.Allow.Zero = 0 +AuctionHouseBot.Class.Recipe.Allow.Zero = 0 +AuctionHouseBot.Class.Quiver.Allow.Zero = 0 +AuctionHouseBot.Class.Quest.Allow.Zero = 0 +AuctionHouseBot.Class.Key.Allow.Zero = 0 +AuctionHouseBot.Class.Misc.Allow.Zero = 0 +AuctionHouseBot.Class.Glyph.Allow.Zero = 0 + +# +# AuctionHouseBot.Items.Vendor +# Description: Include items that can be bought from vendors. +# Default: 0 - (Disabled) +# 1 - (Enabled) + +AuctionHouseBot.Items.Vendor = 0 + +# +# AuctionHouseBot.Items.Loot +# Description: Include items that can be looted or fished for. +# Default: 1 - (Enabled) +# 0 - (Disabled) + +AuctionHouseBot.Items.Loot = 1 + +# +# AuctionHouseBot.Items.Misc +# Description: Include misc. items. +# Default: 0 - (Disabled) +# 1 - (Enabled) + +AuctionHouseBot.Items.Misc = 0 + +# +# AuctionHouseBot.Bind.* +# Description: Indicates which bonding types to allow the bot to put up for auction +# No - Items that don't bind Default 1 (Allowed) +# Pickup - Items that bind on pickup Default 0 (Not Allowed) +# Equip - Items that bind on equip Default 1 (Allowed) +# Use - Items that bind on use Default 1 (Allowed) +# Quest - Quest Items Default 0 (Not Allowed) +# Values: 0 - (Disabled) +# 1 - (Enabled) + +AuctionHouseBot.Bind.No = 1 +AuctionHouseBot.Bind.Pickup = 0 +AuctionHouseBot.Bind.Equip = 1 +AuctionHouseBot.Bind.Use = 1 +AuctionHouseBot.Bind.Quest = 0 + +# +# AuctionHouseBot.LockBox.Enabled +# Description: Enable or not lockbox in auctionhouse +# Default 0 - (Disabled) +# 1 - (Enabled) + +AuctionHouseBot.LockBox.Enabled = 0 + +# +# AuctionHouseBot.ItemsPerCycle.Boost +# Description: This value is used to fill AH faster than normal when there is more than this value on missed items (not auctioned items). +# Usually this value is only used once on server start with empty auction table. +# Default: 1000 + +AuctionHouseBot.ItemsPerCycle.Boost = 1000 + +# +# AuctionHouseBot.ItemsPerCycle.Normal +# Description: This value is used to fill AH for sold and expired items. A high value will be more resource intensive +# Usually this value is used always when auction table is already initialised. +# Default: 20 + +AuctionHouseBot.ItemsPerCycle.Normal = 20 + +# +# AuctionHouseBot.BuyPrice.Seller +# Description: Should the Seller use BuyPrice or SellPrice to determine Bid Prices +# Default: 1 - (use SellPrice) +# 0 - (use BuyPrice) + +AuctionHouseBot.BuyPrice.Seller = 1 + +# +# AuctionHouseBot.Alliance.Price.Ratio +# Description: Percentage by which the price of items selled on Alliance Auction House is incremented / decreased +# Default: 100 - (Not modify) + +AuctionHouseBot.Alliance.Price.Ratio = 100 + +# +# AuctionHouseBot.Horde.Price.Ratio +# Description: Percentage by which the price of items selled on Horde Auction House is incremented / decreased +# Default: 100 - (Not modify) + +AuctionHouseBot.Horde.Price.Ratio = 100 + +# +# AuctionHouseBot.Neutral.Price.Ratio +# Description: Percentage by which the price of items selled on Neutral Auction House is incremented / decreased +# Default: 100 - (Not modify) + +AuctionHouseBot.Neutral.Price.Ratio = 100 + +# +# AuctionHouseBot.Items.QUALITY.Price.Ratio +# Description: Percentage by which the price of items sold of each quality is incremented / decreased (for all houses) +# Default: 100 - (No change) + +AuctionHouseBot.Items.Gray.Price.Ratio = 100 +AuctionHouseBot.Items.White.Price.Ratio = 100 +AuctionHouseBot.Items.Green.Price.Ratio = 100 +AuctionHouseBot.Items.Blue.Price.Ratio = 100 +AuctionHouseBot.Items.Purple.Price.Ratio = 100 +AuctionHouseBot.Items.Orange.Price.Ratio = 100 +AuctionHouseBot.Items.Yellow.Price.Ratio = 100 + +# +# AuctionHouseBot.Class.CLASS.Price.Ratio +# Description: Percentage by which the price of items sold of each class is incremented / decreased (for all houses) +# Default: 100 - (No change) + +AuctionHouseBot.Class.Consumable.Price.Ratio = 100 +AuctionHouseBot.Class.Container.Price.Ratio = 100 +AuctionHouseBot.Class.Weapon.Price.Ratio = 100 +AuctionHouseBot.Class.Gem.Price.Ratio = 100 +AuctionHouseBot.Class.Armor.Price.Ratio = 100 +AuctionHouseBot.Class.Reagent.Price.Ratio = 100 +AuctionHouseBot.Class.Projectile.Price.Ratio = 100 +AuctionHouseBot.Class.TradeGood.Price.Ratio = 100 +AuctionHouseBot.Class.Generic.Price.Ratio = 100 +AuctionHouseBot.Class.Recipe.Price.Ratio = 100 +AuctionHouseBot.Class.Quiver.Price.Ratio = 100 +AuctionHouseBot.Class.Quest.Price.Ratio = 100 +AuctionHouseBot.Class.Key.Price.Ratio = 100 +AuctionHouseBot.Class.Misc.Price.Ratio = 100 +AuctionHouseBot.Class.Glyph.Price.Ratio = 100 + +# +# AuctionHouseBot.Items.ItemLevel.* +# Description: Prevent seller from listing items below/above this item level +# Default: 0 - (Disabled) + +AuctionHouseBot.Items.ItemLevel.Min = 0 +AuctionHouseBot.Items.ItemLevel.Max = 0 + +# +# AuctionHouseBot.Items.ReqLevel.* +# Description: Prevent seller from listing items below/above this required level +# Default: 0 - (Disabled) + +AuctionHouseBot.Items.ReqLevel.Min = 0 +AuctionHouseBot.Items.ReqLevel.Max = 0 + +# +# AuctionHouseBot.Items.ReqSkill.* +# Description: Prevent seller from listing items below/above this skill level +# Default: 0 - (Disabled) +# 1 - (Enabled) + +AuctionHouseBot.Items.ReqSkill.Min = 0 +AuctionHouseBot.Items.ReqSkill.Max = 0 + +# +# AuctionHouseBot.Items.Amount.* +# Description: Define here for every item qualities how many items you want to be shown in Auction House +# This value will be adjusted by AuctionHouseBot.FACTION.Items.Amount.Ratio to define the exact amount of +# items that will finally be shown on Auction House +# Default: 0, 2000, 2500, 1500, 1000, 0, 0 (Gray, white, green, blue, purple, orange, yellow) + +AuctionHouseBot.Items.Amount.Gray = 0 +AuctionHouseBot.Items.Amount.White = 2000 +AuctionHouseBot.Items.Amount.Green = 2500 +AuctionHouseBot.Items.Amount.Blue = 1500 +AuctionHouseBot.Items.Amount.Purple = 1000 +AuctionHouseBot.Items.Amount.Orange = 0 +AuctionHouseBot.Items.Amount.Yellow = 0 + +# +# AuctionHouseBot.Class.* +# Description: Here you can set the class of items you prefer to be show on AH +# These value are sorted by preference, from 0 (disabled) to 10 (max. preference) +# Default: Consumable: 6 +# Container: 4 +# Weapon: 8 +# Gem: 3 +# Armor: 8 +# Reagent: 1 +# Projectile: 2 +# TradeGod: 10 +# Generic: 1 +# Recipe: 6 +# Quiver: 1 +# Quest: 1 +# Key: 1 +# Misc: 5 +# Glyph: 3 + +AuctionHouseBot.Class.Consumable = 6 +AuctionHouseBot.Class.Container = 4 +AuctionHouseBot.Class.Weapon = 8 +AuctionHouseBot.Class.Gem = 3 +AuctionHouseBot.Class.Armor = 8 +AuctionHouseBot.Class.Reagent = 1 +AuctionHouseBot.Class.Projectile = 2 +AuctionHouseBot.Class.TradeGood = 10 +AuctionHouseBot.Class.Generic = 1 +AuctionHouseBot.Class.Recipe = 6 +AuctionHouseBot.Class.Quiver = 1 +AuctionHouseBot.Class.Quest = 1 +AuctionHouseBot.Class.Key = 1 +AuctionHouseBot.Class.Misc = 5 +AuctionHouseBot.Class.Glyph = 3 + + +# +################################################################################################### + +################################################################################################### +# AUCTION HOUSE BOT ITEM FINE TUNING +# +# The following are usefull for limiting what character levels can +# benefit from the auction house +# +# AuctionHouseBot.Class.Misc.Mount.ReqLevel.* +# Description: Prevent seller from listing mounts below/above this required level +# Default: 0 + +AuctionHouseBot.Class.Misc.Mount.ReqLevel.Min = 0 +AuctionHouseBot.Class.Misc.Mount.ReqLevel.Max = 0 + +# +# AuctionHouseBot.Class.Misc.Mount.ReqSkill.* +# Description: Prevent seller from listing mounts below/above this skill level +# Default: 0 + +AuctionHouseBot.Class.Misc.Mount.ReqSkill.Min = 0 +AuctionHouseBot.Class.Misc.Mount.ReqSkill.Max = 0 + +# +# AuctionHouseBot.Class.Glyph.ReqLevel.* +# Description: Prevent seller from listing glyphs below/above this required level +# Default: 0 + +AuctionHouseBot.Class.Glyph.ReqLevel.Min = 0 +AuctionHouseBot.Class.Glyph.ReqLevel.Max = 0 + +# +# AuctionHouseBot.Class.Glyph.ItemLevel.* +# Description: Prevent seller from listing glyphs below/above this item level +# Default: 0 + +AuctionHouseBot.Class.Glyph.ItemLevel.Min = 0 +AuctionHouseBot.Class.Glyph.ItemLevel.Max = 0 + +# +# AuctionHouseBot.Class.TradeGood.ItemLevel.* +# Description: Prevent seller from listing trade good items below/above this item level +# Default: 0 + +AuctionHouseBot.Class.TradeGood.ItemLevel.Min = 0 +AuctionHouseBot.Class.TradeGood.ItemLevel.Max = 0 + +# +# AuctionHouseBot.Class.Container.ItemLevel.* +# Description: Prevent seller from listing contianers below/above this item level +# Default: 0 + +AuctionHouseBot.Class.Container.ItemLevel.Min = 0 +AuctionHouseBot.Class.Container.ItemLevel.Max = 0 + +# +# AuctionHouseBot.forceIncludeItems +# Description: Include these items and ignore ALL filters +# List of ids with delimiter ',' +# Default: "" + +AuctionHouseBot.forceIncludeItems = "" + +# +# AuctionHouseBot.forceExcludeItems +# Description: Exclude these items even if they would pass the filters +# List of ids with delimiter ',' +# Example: "21878,27774,27811,28117,28122,43949" (this removes old items) +# Default: "" +# + +AuctionHouseBot.forceExcludeItems = "" + +# +# AuctionHouseBot.Class.RandomStackRatio.* +# Description: Used to determine how often a stack of the class will be single or randomly-size stacked when posted +# Value needs to be between 0 and 100, no decimal. Anything higher than 100 will be treated as 100 +# Examples: 100 = stacks will always be random in size +# 50 = half the time the stacks are random, the other half being single stack +# 0 = stacks will always single size +# Default: Consumable: 20 (20% random stack size, 80% single stack size) +# Container: 0 (100% single stack size) +# Weapon: 0 (100% single stack size) +# Gem: 20 (20% random stack size, 80% single stack size) +# Armor: 0 (100% single stack size) +# Reagent: 100 (100% random stack size) +# Projectile: 100 (100% random stack size) +# TradeGood: 50 (50% random stack size, 50% single stack size) +# Generic: 100 (100% random stack size) +# Recipe: 0 (100% single stack size) +# Quiver: 0 (100% single stack size) +# Quest: 100 (100% random stack size) +# Key: 100 (100% random stack size) +# Misc: 100 (100% random stack size) +# Glyph: 0 (100% single stack size) +# + +AuctionHouseBot.Class.RandomStackRatio.Consumable = 20 +AuctionHouseBot.Class.RandomStackRatio.Container = 0 +AuctionHouseBot.Class.RandomStackRatio.Weapon = 0 +AuctionHouseBot.Class.RandomStackRatio.Gem = 20 +AuctionHouseBot.Class.RandomStackRatio.Armor = 0 +AuctionHouseBot.Class.RandomStackRatio.Reagent = 100 +AuctionHouseBot.Class.RandomStackRatio.Projectile = 100 +AuctionHouseBot.Class.RandomStackRatio.TradeGood = 50 +AuctionHouseBot.Class.RandomStackRatio.Generic = 100 +AuctionHouseBot.Class.RandomStackRatio.Recipe = 0 +AuctionHouseBot.Class.RandomStackRatio.Quiver = 0 +AuctionHouseBot.Class.RandomStackRatio.Quest = 100 +AuctionHouseBot.Class.RandomStackRatio.Key = 100 +AuctionHouseBot.Class.RandomStackRatio.Misc = 100 +AuctionHouseBot.Class.RandomStackRatio.Glyph = 0 + +# +################################################################################################### + +################################################################################################### +# AUCTION HOUSE BOT BUYER CONFIG +# +# AuctionHouseBot.Buyer.Enabled +# Description: General enable or disable AuctionHouseBot Buyer functionality +# Default: 0 - (Disabled) +# 1 - (Enabled) + +AuctionHouseBot.Buyer.Enabled = 0 + +# +# AuctionHouseBot.Buyer.FACTION.Enabled +# Description: Enable or disable buyer independently by faction +# Default: 0 - (Disabled) +# 1 - (Enabled) + +AuctionHouseBot.Buyer.Alliance.Enabled = 0 +AuctionHouseBot.Buyer.Horde.Enabled = 0 +AuctionHouseBot.Buyer.Neutral.Enabled = 0 + +# AuctionHouseBot.Buyer.ChanceFactor +# Description: k value in the formula used for the chance to buy an item "100^(1 + (1 - (AuctionBid / ItemPrice)) / k)" +# It must be a decimal number in the range of (0, +infinity). The higher the number the higher chance to buy overpriced auctions +# Default: 2 + +AuctionHouseBot.Buyer.ChanceFactor = 2 + +# +# AuctionHouseBot.Buyer.Baseprice.QUALITY +# Description: Base sellprices in copper for non priced items for each quality. +# The default values are based on average item prices of each quality. +# Defaults: Gray 3504 +# White 5429 +# Green 21752 +# Blue 36463 +# Purple 87124 +# Orange 214347 +# Yellow 407406 + +AuctionHouseBot.Buyer.Baseprice.Gray = 3504 +AuctionHouseBot.Buyer.Baseprice.White = 5429 +AuctionHouseBot.Buyer.Baseprice.Green = 21752 +AuctionHouseBot.Buyer.Baseprice.Blue = 36463 +AuctionHouseBot.Buyer.Baseprice.Purple = 87124 +AuctionHouseBot.Buyer.Baseprice.Orange = 214347 +AuctionHouseBot.Buyer.Baseprice.Yellow = 407406 + +# +# AuctionHouseBot.Buyer.ChanceMultiplier.QUALITY +# Description: Multipliers for the buy/bid chances for each quality. 100 means the chance is 100% of the original, +# 1 would mean 1 % of the original and 200 would mean 200% of the original chance. +# Defaults: Gray 100 +# White 100 +# Green 100 +# Blue 100 +# Purple 100 +# Orange 100 +# Yellow 100 + +AuctionHouseBot.Buyer.ChanceMultiplier.Gray = 100 +AuctionHouseBot.Buyer.ChanceMultiplier.White = 100 +AuctionHouseBot.Buyer.ChanceMultiplier.Green = 100 +AuctionHouseBot.Buyer.ChanceMultiplier.Blue = 100 +AuctionHouseBot.Buyer.ChanceMultiplier.Purple = 100 +AuctionHouseBot.Buyer.ChanceMultiplier.Orange = 100 +AuctionHouseBot.Buyer.ChanceMultiplier.Yellow = 100 + +# +# AuctionHouseBot.Buyer.Recheck.Interval +# Description: This specifies the time interval (in minutes) between two evaluations of the same sold item. +# The smaller this value is, the more chances you give for an item to be bought by ahbot. +# Default: 20 (20min.) + +AuctionHouseBot.Buyer.Recheck.Interval = 20 + +# +################################################################################################### + +################################################################################################### +# BLACK MARKET SETTINGS +# +# BlackMarket.Enabled +# Description: General Enable or Disable of the Black Market. +# Default: 1 - (Enabled) +# 0 - (Disabled) +# + +BlackMarket.Enabled = 1 + +# +# BlackMarket.MaxAuctions +# Description: Maximum amount of auctions present at the same time. +# Default: 12 +# + +BlackMarket.MaxAuctions = 12 + +# +# BlackMarket.UpdatePeriod +# Description: How often are the auctions on the black market restored +# Default: 24 (hours) +# + +BlackMarket.UpdatePeriod = 24 + +# +# +################################################################################################### + +################################################################################################### +# CURRENCIES SETTINGS +# +# Currency.ResetInterval +# How often should currency week count reset (days) +# Default: 7 (weekly) +# + +Currency.ResetInterval = 7 + +# +# Currency.ResetWeekDay +# Week day when currency week count is reset (0..6) 0 == Sunday +# Default: 3 (Wednesday) +# + +Currency.ResetDay = 3 + +# +# Currency.ResetHour +# Hour of a day when currency week count is reset (0..23) +# Default: 6 +# + +Currency.ResetHour = 6 + +# +# Currency.StartApexisCrystals +# Amount of Apexis Crystals that new players will start with +# Default: 0 (with precision) +# + +Currency.StartApexisCrystals = 0 + +# +# Currency.MaxApexisCrystals +# Amount Apexis Crystals a player can have +# Default: 20000 +# + +Currency.MaxApexisCrystals = 20000 + +# +# Currency.StartJusticePoints +# Amount of justice points that new players will start with +# Default: 0 (with precision) +# + +Currency.StartJusticePoints = 0 + +# +# Currency.MaxJusticePoints +# Amount justice points a player can have +# Default: 4000 +# + +Currency.MaxJusticePoints = 4000 + +# +################################################################################################### + +################################################################################################### +# PACKET SPOOF PROTECTION SETTINGS +# +# These settings determine which action to take when harmful packet spoofing is detected. +# +# PacketSpoof.Policy +# Description: Determines the course of action when packet spoofing is detected. +# Default: 1 - (Log + kick) +# 0 - (Log only 'network') +# 2 - (Log + kick + ban) + +PacketSpoof.Policy = 1 + +# +# PacketSpoof.BanMode +# Description: If PacketSpoof.Policy equals 2, this will determine the ban mode. +# Note: Banning by character not supported for logical reasons. +# Default: 0 - Ban Account +# 2 - Ban IP +# + +PacketSpoof.BanMode = 0 + +# +# PacketSpoof.BanDuration +# Description: Duration of the ban in seconds. Only valid if PacketSpoof.Policy is set to 2. +# Set to 0 for permanent ban. +# Default: 86400 seconds (1 day) +# + +PacketSpoof.BanDuration = 86400 + +# +################################################################################################### \ No newline at end of file diff --git a/WorldServer/WorldServer.csproj b/WorldServer/WorldServer.csproj new file mode 100644 index 000000000..5fac2c5ca --- /dev/null +++ b/WorldServer/WorldServer.csproj @@ -0,0 +1,136 @@ + + + + + Debug + AnyCPU + {D157534F-86F1-4E8E-80B0-ECAD321ECAA6} + Exe + Properties + WorldServer + WorldServer + v4.6.2 + 512 + + publish\ + true + Disk + false + Foreground + 7 + Days + false + false + true + 0 + 1.0.0.%2a + false + false + true + + + true + ..\Build\Debug\ + DEBUG + full + x86 + prompt + false + false + + + ..\Build\Release\ + TRACE + true + pdbonly + x86 + prompt + true + + + true + ..\Build\x64\Debug\ + DEBUG;TRACE + full + x64 + prompt + false + + + bin\x64\Release\ + TRACE + true + pdbonly + x64 + prompt + true + + + WorldServer.Server + + + Blue.ico + + + OnBuildSuccess + + + + + + + + + + + + + + + + + {82d442a9-18c0-4c59-8ec6-0dfe3e34d334} + Framework + + + {83ef8d5c-afa1-4546-bcdd-6422d55b1515} + Game + + + + + + + + PreserveNewest + + + + + False + .NET Framework 3.5 SP1 Client Profile + false + + + False + .NET Framework 3.5 SP1 + false + + + + + + + + + + + + + \ No newline at end of file

    An*rpbd+0Fb~OF901U6sV#t1M5Z7pbOI-&BA>Rhp zgS)`c0S2LH`3N>B(1PN;{lDb_CbL-vf?;ekiyR@_70ekh4IEU!moe zYB6}D6|EjU4fpY*qt#x}4HI_{jw{XsUj`+g&RtEs0bn9_<-@?C;2co$s{4nB&$3*;2m<#10 zdy@mm;pBL7A~{WSaziP20mgp-l=*+Y=H!MAptSq-pp<7fn1G$pKJsPq9rAPXH?SYV z9RnqPY(K-6WaIY5X(aTLei|s{m`K@6c{=F>ha=rDKuPyo@+a~rDCy)RnSLw-7b1Km zDC=|`c|N%rJP-c2f|A~yV)E%d(+l=9j0^dFFSyIZO+!TkvL&LI3IT{E}3aIjJ*z=mG}N`Cfw_c-O3 zKq>E+U<$&yF@LhbjzjQUb>PjzOuRe5eJ7ybz-Ld0R(rsDtQ)32Xh*IaFG2qz!o3NG z@{3W+N8%3WNik|I_*}CXrW>QSKpqVJHgF8=;Dg`+@LA9k6{B7UN5KDmQ1bg)`&;S{ zP~y2q;aOy`19%kidVuLT+wMbo7&z&qXf+nhgFa2`qtz5mOLZM%%2_?uw8OdJQ9PLx zeI|Z;F21FT1Lxy>xK1SVc$o5uSnEU{ z2a11?^6ix0rQ8Z@i1^E?%mzHOb!DC@=e6Kg?3r!?rQf!acais!yU0h$z2renJfovI zxdGpojdUhA;ER`adfA`{=i)sJP5lOfGS1EbCEpBC`Zco%b_(G>1D^ohQ%pE~J*@W6 z2F1QqfJtcg72rOEyN>?5!Dk`AMt%iKeWGyh>;>p=nx<3+c7nHqy^+pN&1m%mDDC^{ zS!nl`ZPeG`7@S-E2!^j?$EM?V4qFv9xMvuiOrA>i(G0D7qab%j_(|jpvYf0Xmy?%} zH<0&{d&uX=_sP%5uQe_8CzypDYV_G={}Bs@mwy1{IVjIaGL6h5&n9P+^T=BAJaQR% zDS0({ohI&afno>l1;x%hM)?^MHyE`4JCwg9<@u|mlVCp}%Y3!Yk@Ciqd+k3|Z#wXmCB+T3XY0y8@2K!htAM2jTvi}l}qW)}h0QnpF7xonr z?il6hG9&k(Jcn#feFw6eW~dzfAb*2&hH4u9FObJV{vAvw^T`?HTyima5qUHDB>5Wo z6?v3wHpisjhU`lA(zM%apc(;r3{LOU$U<^9Sxa6(UPazQ-bd~uUnf5!e~-Gc|qHvhDvg=W;=C0Scls!tHH(KjbKCDmMV8H)(ObBgI%$XJ~9u#QwqKg z%D6fLO8B@+$q&!Qg0ikBgBK&*G*H&-J-C*Y_4p@H)?={?@3k}Ka-CAt@DGHG1)m`g zk?)b;kiU}Fd=uVHwj#TbeaYeE8RSHA3OS3cAy<%>kvEZdlaG*x$)7b%J!BsmU!~M1 zs7EKVFFBl)^;G<2eHErsKbbsR)1)W!tP1kC2p1&l$z|k)NViZ%Mf$DNpB!z85La=!h&&=m7~B{UPDyS<6lldH+~q}Vr!w~_L-I zavYq$Go199PkYaBoTn**rKUdyqrO6tak%O$NxN8o@eswLMD=DlDT9Fxq!Toyp4REe2ILI{DwS6wq9cL>rD>9;47$YgRFSw_~74diqU+tCy`}jJ$W&C zEqOnAfP9zyj`!KpGsqfpEqM)j zH@SyAM1D&COvbHbJ;3rxBl$-ZPNc{aI#Tup8!carhJVtg}W6~Q=7Lax1jpQTb zd*ol_>1$2=apZJzA$be=2KgJ=cAbgWha5{5lGWs;L+SCjXUPm%ACKaeL~V$$zICXo}#bI2gMlDwMSO70?`CEp`|B#)78)|>oJ zBL|UZlDXtLq@P?(ZXxd>pCu2I|0JU?HTj%Ob|MqW@njKMOwJ|i$P38J$Q#MK$phs3 zrM_QGss!wdh$l{G4ez5dlHW+>g$krvICh&jv;eM zKiNQDLT)DykROsiknvZVeEO4P$b8aIt|qS}?kQJ-(=E1olGI;k{6QKkq?oF$gjx1$Wu3)^wP<*$hqWF@>239ayR(~ z8FRHsrzJUr%p!f{Lh^F*KJozhA^AJm_8OD!ATo!XMOKg(kvEVJlP{3}B!42+7L#rV zav+&OPA99$2J&L^Ch{Tjb@FraD0$+wCZEn^fAUOn7FkDLOx{F3Nq$8BOvYYk((Onl zkR!=#vYK2?UPInPzDoW~cDdf9dj^?DmXoW<4dhmGH~BVslx%r}Nv{`q2ANHsP5Q|T z$!o}a$i3tt@(VKdMw5Owax7U$`pISF7IGK4m;5LB8`=COlWtdXG+98-C6|+%$ot8| zOd%rIvv5JcYSGhx<2>pgf=P%k6lMBLN?F*@pXrpxn=St{LvXK$hn_ zT9Nki6CEMTeVtyQ+)o|{ZbrQEcPRZF)h*zQ2`$wf;F}4p)kEL`l>2k=%LF&?JL2=k zcPiDPw_BxxemrMY1WLH8z(Ku3&rz6gxWA!?gt0J zJzywgniS%FW14^yv+ftnj zj>dCzb>ItqW7HMkM+o;4DCPMCl=7=xc>XLA&wqd-+y1GLWt^P@O85rKueLDt-$waf za5CaQ1{Q+yT*-8BA7pv1;brn&P}2Vtlytu(|DfLWpwY{7HxjNrWO@G0Zl`XLMcU{~hiQEp#^BLQAV;=WwsU9Qc`Kj~z;rW1nm~x7I89s2(6Y^j@M=$`C`>yu$ zR`zq4V;DZ0JO{i9K|CSJd#EW=nNF{H1;FB6orET+&mZw3GcD&45SD ze6gRe5n1MuEoVTM{t*2K$k*1dpx!*^VDUKz6HeO24kz*OAuYP#+v%F) z!pZs*p3X0L<~dxJ_1Tug^AEM}JBOUY}=ZREY=6XXlzo8%|tPo#R>lQ}hHd#u&pR6O7kSjq+cP-_szz)cF3wa~; zTgiLLe~|mggXBBp7vzs*%#*Aa*@5gv_9w@Y@?4j+LoVg1WC>XXN;wyi%gHt1Y0zIr zZX$1>|2D8I{O<-O{Rhb>Kxv2ll%FRLlOK|wk>8TPlTmw2dMAVPlA={R$}c}<^zV|N zlHZemXijdh_DZy-mKT&2D2`E8_c@wZ7}Pyx52E--UfSJw%5)7)i^wt zma5l&SqJ{t^U>RoiF6MrZ$mygU30LS1@iZ)`hb+a24^7`x+b zcpc#n;CVx1R|cz(Aa_7M-)gp2e}KaWv{rFX8@AVsR=q%3R|kVKzQ%&mj+tbx=Kr-E z-Ueg;ybZ>#dK-*g^)?v0>TNJ~*W2*CPTwx~Koza~aj<#|dTFOm$?wU(z+5$0#U3zh zO?p5HpQstF#(+|KLt8K^GT<#elS!CYL&-%9y@@)58e=M4Mk|04D8lAqE4J8%rf!B5~P z1ESR*;FqB6pX7WX`dPDHGzY&O&{9nRt${670q7ps+NwON)E`4yt4A>gdPlWZ*EBag z71!1x_a{>ftsr=FptY5WXR{xzZEc+k?g1~M{3O_^uC?_8cskev&uI4qN0RfvzL3u+ z?*j)w-cLCl*9;pWp9M;|Dp1nD9^4H1W$-#M8qet744wi?_`YD@I(&}uTYP)d9DFw< zu0{4&yY+M5&w+y>9|luEIY-F`MK9+(?^Yl5U{t8f+ONmedVkD{8%`JbN_8dV!H^GV z`7(8bmZMbnuHwI{{auhpqV*HQo2%KB*Fs)_ zPtulad4<{tc@4fjYgTtjf0?=w@_KcG`k3-Pkgvq|2zz=M|J~aEM74%R!TjgPF5H7lKk!dX{j#L@_we@Qf<=mUS-n5w_X^3rq@cfO9+qON_n)r zSDE&1rB-TrzcTe}rM4QG>9yUKkn^nJl&|!B3HdB*q$A_&NtDkT?Z~HS zd5pSF=Wn;ySoM6E?6=0^1WlqC|2lQ1D%J9*Rwm+)RO8em#$Qc_9JIzM`E;Ni-VZq| zOs-SOYI&Hv#2T+2(z2ais`@C*-*2U=9~;SOs(rsu`rE8@6)E`+=d z@=D63khiD_Y8~Z8kkcSvL3y3_&s5h^zFzxhs@o{vr~N0YyD2}R{U@piDZi-wC#lCN zzpwo#sRNXcX#XsAkn*3}KTEwyIqno3Fk0E_L(1(S`yqcxxhv$|R*w3faw6n4_?|2I zbRyb2c9@Qj>pz^PiJYV3=c*GamqFg5@>CnjwUE;wpGJ8lkMEMcO zyRCdRjPe_h*Qk7TCgtySe6Px&>~1gdy(*7#N61fG1!^kgevqGsTugZ!UqlX9hI68HBGe`ti!90ko)61+S*f2q#V>!?s9boLmHAq(78(E8iy?21Dpw6!-XD7dQ2f(OL)jTwY)0U-ATsJJoOx9GhgSaLzK;You?*` z(e;aMi|~&|RjO%}yF-2oaxvv0ke`7(mvSoP-S{X?4do)pYt($Tm~uJfT~?J^LAe&P zAM!dauZUd^`Cyb^ZKS*o@@~towou*-c?&)|atr1A=)axv0my6M|A3bFtM?#}gatpO z)kGO!)rr9|}23%PZ7)$lIfqsJ&WV zrt%>F3^^;)gfE6XI(n&UKT+fltvQfyP)n6Z%PZ9Rkdxt`N_h?BE$V#r_#_FxOx*(c zJjkzW*|t{=>Ju&RRbmg;Ipl1} z!zlj=dAGGfjiubWi=@9oWoUV?>J7OddZo%Sa_kw9r$H{FoCWzD$fcCehFpqAB`YZ} zgj@kRNckejRgjk(SzQA;7`;lZ)v~=lUMQ8rF4wF-yP|D*zqP|7{e2Pr_gfD_ek6LW zBR>WC+35A^Qk|Z?-d(C543l5RBVqfE9Qy*oAI2kEuTp*&^2?Cl(ejn*Ysl|KZ%`9* z75*+$#~^?*ZA*)?2g&2&Y>*+tz)iLb(9Zl`8steW z$6T*o(eg6=>HE$xH>kI@Y}#vF%uOn`K+<0oI}XlRIPfs4bM`Q^X_HKJ|{4uT-w?m_IT5)Qqzv{$;8+-koT1Pn^@^0&8wUP2^kk{aQJg=pksQq72w^B~l{;#OJDHmz~SJf`c zrP}{h^*H4k?SDu;O?jpEKco&)zEb;V%}DpW{LkYwHETYn0M3;%GW|3soqm#ijDt0lxIve2o#f4ry~BgTuY^4;J%!d-UA{lG2lKMLX_ z!JTZll*jp>MZ;qb>`CArHeAAEtL4+fhD&ekIT(jaY3x_QebnuF_6qb(1^xg$*#1X> zcVYS43VZ-O(ymb8pTW=Al?puTc34YocpQuC{~h9!YWgHWZzlf z^5DfjR{_6iH!O(PL^qNPaXj_@uBpr~G`>ycZgiG-9P+QRn@ZVN%`wXx1YQUB$RdN| z<xWI{By@(i1>)HdpFtM8!I#aZvSUA0f2#Nf{E^*Eu4A7?H6Dsz;L}ls<-x4|I~G%1kql0 zNhW{B&S8HKzF@SMJJ5RhbdcZreA(_GFOj`JAb+mxB-LBz`uJ7K=_JRX_3zuxayiAl z^cwIN!ko@>4O*YCcb0jSAFp3!iq5jquuy$TqKj-s#{E?t=V93moiAwr@QAdAQ-5xO z>n?SiN8}lHJUBsgm8+jralL=*Ds$MhKkF*rW7GbwtK7h*{aaVLgH8Llu5u5X_HSL~ zUNZ0By2*WP+P`&^)kfm{dH?d5Y`~`d-Q%*$QxvEDTW{H$%=@=KvLBoFZ+&DkoAz&g zWD1-1Z++!>HtpZ~%Bg5to{3IBc@&+6*Xde1gJp%M)%?jFo#8SCJyVQ@@$2Y3DQB=V z(IezK_CoYXd5FCR{ghO1*{b@sgS$CT%SiS?WY31{NI7z> zOk$T{w_)GN?t;z|w}S^bW94n5)ci9i&Qz7;&FCnnoaA+1bLNB>&FCHY_w{RUSHE>6*evJG+B>L=cDOz z1euqAx=dx$@=ur3*tGo9Wj33Z|3tZrP0N3xTu0{f-we6v8P%Rtu@S~2K}?pd#*lY| zljUT&g#8_Os*@?B#!~!e%%3Uao>k80^*vKIL8l5MPVEmewdGK{6;pd?^EO_ zY&zaflZVh5-ge0Ufipvv8K>&c@V-LNlx@g-{GBBS`J5zX$uVfXznLx7Td45M!xQmv z{m7Xu*RZ3&d%+vnbpL0z%tNPow?lkC#CLH#9{htbM;>M01wI50jK}-I^O^RhhI z>j=)3Su%;;i`|Bu49*l;@*#AF_Y62k&Xv8;`QB7;wwNo2vR~x*SoSK8Pvr6&!MRX= z7RNsW|K!Y-*=%(`OVzi9{VkV2!9D{{g7}V90srzmyEgnjcjn1Y(HUM@aITyu_oMSg zZSVz%e>*|t&k)VPTaEehgwNNU`Lag3io@@9h$o2`WN@N#nivjt+!tkQbcT2aoGTZ| zdFXuHzbC-=HP1h%@@I(g5KlB0$f!xm$ECVnv@N;+X9(_ zP7^OdJR98R9B9#$6=e zL}!VpI_moJD{>wCCU6}5O^gq?JYBC^EY;hPsy}qUW{EtG&Ja}~e>r!FyySCburpQV zr}!Q2t1{N-X6|cp1iKlO@8~X-={|RLm&rry&Jge8zA4pPlBz!CLEv`(B=-c5aF@#- zK961<3K>6*%BMqovb$0?@p-QMj$DGy7YiW1)Lkpz^7(CWp3iID z9C;O;CtipA8{Ks>YPzaFPvnC0-S=b}bgK9WoB)5Tv>V6idf$6;1iIk*fmEeK*T-_% zbbag>cSC{SEf4FryRpDq!Irm0reS^hc(qkd@j2StD&Iis<+)AnrumEcb>Zm{Z=2jS zL$x3a?}t|MK9&<_D$f+JLcFH8OD;!ei8sLYz1?!jEQ;rX@Akfs3A2^;{P)P_WbY!x z+k1QD7`9ta`AfNmT@n1Kw^yEG*F%3LZ=K_>Z=Y0e8|wP+G55(9KF>G4mR)`B<9#bf zlD!u7)b@EmPGENd-vge)9*FUSavpmO#t%yM_9D#Bn}+f4dh|r^2icLG4DlR!LY`oc!Tcv=-MN(iId)U_eDGB7 zgzUk73q0HVNe*Uj1uyY_ktytNz-zr<-^Kz5VyS(3I`FU#o z6yNV%l<_{F@Gi+2K40{%$VEO|fvXZOkrtGX4P2M8KHnCQMvBjM1C}w#=cIsZobvgR zKyl-$&jSLHMvoUl^*<9RX^ikWGf>*t?{jvbtZ~NYw*uvjE-!}4Zw^#42K&4>P~F($ z^Dlwg#wnl8Vs{!{vZ;K@`tW^`HT z&*O`=H8%J>uUH3Tx6iAJbuk(&43*zi>`|kY&j*Y3FqZrLYq4I&2A}QXeU0)jh02#L zKG3M=bDiQtjBNI8(7)EjpEQ>H{BZFRMsN|8SMkl>Q${(TcX>}6V|?E4jWVYCe8L-T zobq`<@iE3#pGOsc*68uFfPWc6)ql}THHP|Z1=0)^4egIJ*t9>s*_u?~cgw?GvUp~J zw}LHi8k_cqIp*|&_)3VsRD5QERr%M8&o1y*EI-%SL+!!y$+gAj8HdSU8nidj%r<^u zPY2%vzE~*#0s}7j!@ms9|FRK_PQ&`XDE_jMLH5o<`R|G^F{ZJp{v}2h`#QvrL3|-Q z;!gNmwcyv#`QEMI9J$1JpB)d*1@B=evcF^B%lWUe)%}RGP@lb6wKpH{Ph2Sesu4@( z-_NfZ>a9k#yt+gA3&v|kS9Cr)(tX_+!14YN|GoH9BZcG2BDl<0!||bDJNSl?=X0^( zn}&E*)t@g$LA*q8xp4)ZDkgw$46ZWDzoz0D;-BCO!8JxaIv-s%xYlURejehrf;q+| z_Cj!swcbd2oyxxrZV=pHbo9A#@O`5fdk4f@1UDKZ(D_2SP4ENbD987JI|ery=X~xO z+-w+2ss2+C?-|@;#G>D68uK+ z3!^PML$n89Huo4kIIbLHeQB)acsKCI;9euo=WW5Sj1!!{Kg7=#-)GEOPW2B1?+kuz zY)8ZI1@Py=Z;X9De--@JDEAiiZz9C^2lpE_d_EjJU<_qXhxqZ}L1VnnCxhP^dFaD( zKE%%i4;j1A`C>CT(miY(Vt)kAl}C)ImDE0UeMdPDohrVB_=Odz1r z!G8pg8-sneBYrT(p!4y5=~DNEas6GDKVLM3{K1G5M(K4}-s{m2u3v+1W)A^(1y>>C z{%*hXqcH=m+w+sLiSv(v{5kR`V;6fexM##q#y4#G{r!`1giXJ{e=<(8>3PeO#yR%$ zP`-b}N#iQ}6>xIIDZ_e?+WQ`OBshY-8=NbDHcGM8b32*hXX9q}d5r&JRAOHTkBRuj zsKxf~g1?#$zLOmdUT&N=8l%&^+rc^Vw9%5?2%HRV$9@<*IpU1bh06~D=gKq2Q*8BI zkMd0R3vBh4raHc?W^ZTH@9{Inr|2~AbBJe(GsZrOixc3>=2_ze`z)6K)rikk^=0Ax zjM)*t8q3icqIe?w9*#I?Y)0oxbw4OUoHq`!>H6w<;{>}DK_XAHCmksrHh_c$gwnbbqhM@E0 zFo^GpxN4}U`BnTG@ZN~)##HtsaITc*JoX%Ll91*ObcT2Zyg$M)|KRvr;G+?y8L>&# zmmxNSe~GZof3QCR{~qC))!7HZ$ueLju<3e5F|!3aU!H_`k|<_&WM2fs^SWkFws*HG zA2f%u>Hc_xIhK6`#G}0ka~?X6uYZ>?t85nVPjCP5dJg`}k0{Ydt#2XI_7rkCh*yXV znVw%Ru&tI~f(V(GcgVCnLRQN^Q$*|e+tmM(1y;+aT4czyd}0gYG~T5POyg6gz%)KL z7MRBKrUKJ=slQRF{^i@Wyh5hsb#p$R`X6AUpik)5Vx9* zeNFnMd|YHdfcS;>^A5e}I#~ zN7;1!Fy8!~T?OOuX5dq-&$|OWJhHY~p4}Rp0Y5YSGr+mBo;jcW62|M9%h_*Z{0?&~I~U`3n0wjz;A~Of{F(hNI0^ha`wW)9 z(=550`XigD`QK?)WS0bwi)>&fvde<&I1S7P*y?)gxX1*vKf40P6U;Bz4Y7Pf^Cxyk zj5jo|v4?=O#a(9XX9E6Zc%#5c;M>^g7*8}4*fTMnXtrc81n0`T%}3ce;7oD1ISifV zeS+~u<~a5@7;j|GT*p0wxk@uLF*8@2%zT3V4aS?B>FhHYZ*Io_gVQtYW%zLi;n{Th~U zWj1DS!uY-BL+sBney=%zeH7#Ond8~NWBfjIHoIgq)&BdI~% zTVlMm`2+h=jJGlWV2{Lj8#8(j^=B%^+nQC_i!t8Tyo;TS@dwQI?9VX%fZ2zA0^{w> zF>Ir`YF|5ZI=dVg?kAhCvEwoRp!puVImX+YyV(z7yuJB7`%&s zCAFWPAL?jUVAJzM9nCx0qp|)@W?S|YtiO}_I6E5*&y$-e?6u%z@D%nZSiZCQ3i}9_ z?`*DRU%+@5b0^zLQvL5@e#b5i&W0y8FRxlRaIWlX zHeo*x&J=xi$*~2XP74_#4aHi;CR%8#s_~T|GdlJSUHy>cr`xHIR zzHEA*qNh26{R)=vWlmwgi{*Qn3)wrt+3>{cTK13NB=A zXRhRc<(+jCiJv z8t<6}&H!JF3^@zDNQAryY?cUl1vontw>N_Wp}4&h98=vXMh`*$Tq)WuLQR$vA{gc-V1)9#6t5Z`w#F# zC0;UrVBhwD5Zy~GGJj&L=d^p5c-j1!Js3Qw#4F|*_GIwz5{u1q>^H%qN-Qxiuy=vS zmw45@!oC82uEcBRHFl+TaQzBwAE5rW1!t9b-SpT)!HY^PGlOV4zg=G94YP*NT_aYR z4SasL#5?A@J|~IQ<_Li= z-gR&e*kgMSs^iNhGnyR@&XJqV8`)(szS+EuT@mA3%xdhK7~f*nW#57Et>#_qMi}2} zHfOiQc%Ips-5%q4W+!%cjBhi$v-@Lwo7tEBB*yd2A?!4a=bMYzvoXHiT*_XI@$Kd} z=;Hy}9(I^V(1!!7A$~1#hxrrx5O|T;VP53&YWwIEvBSL1akstNK6aSFLuxz^2kL^e zA%81$s^|$m2=R7@F)oLK10_B(Q;sOl#^-lVl=#G)=kw_jpPK8?sbU`FZxH;<9Qr+# zUkr{Z@wpj)l>8BRy7RWTp2^Bvq%YdV!_M3BmRHpT{V$=b1i_dXUht2C~TK}3woiwZb zr1GcY^N9)KteJ?;_j+{{qC?bKvpIVt_+fAx_H*ED@vGU1JrA4&ev~YhffK|zvmeLT zf^+3LGnxGvI1}P2?4Q9sqRyM+*_Xh5!86dQ!tA8#`_1%DQG3gQ`$qj{X8)|Le}7&u zSNXg)@`AbD=O>~rnfrVm8Fj_{5uNWn2=!%)Yvx&YFK`n03Odaj34Y)B!!&+T^Goy6 z!OuqhVHRi8^}y?944bY8UN_6JXG4CtyKhxyzXr~RTSh(6`QB>qMEG;k1KFFwQ@~HM zKgIIWdWL-voGGN0j?VDt_+VI5**`;kR+M37qmKu!f#*k=))IE{ht%^=ru7#4M)3S7 z%Ua969pjd@5uNYV1m{TG%4gRHCxbs_)A7i$_OTm7d_kmRRXk1O^$_@#DA%fqPWAeN ze~)mj`sfUA40vgjXEoyd)4^|pTT*`U3OGpwtU+kKeH61sP=2u*;yJRIb&CBycx_ZM zYyBBD|5ULZJS!@~+Rxq#P7sk+`Ex3sA=L9b-x!frZ?t~jE7BT_*6)i&TBp(Z;z$?x z>S9!+6?I;f&lgb-!}aW_C@c9ltPiiZB#2n+X)?ZF6O4|vrlRvjdB|TTx{P%c4ew`x zZ;8Iq`q}4-(KlHa*!3Y^J-V!AUr_bu3FSJ`|FB}w`B;B+)GbyLyBWkU80D>wKJSXW z)q0NI8sZ7jw^=W;>H2yFYbCn_#P5l&V0}yW9s%ddO4bSXK=yC!QEcm?YG1xk=VJ>Z zD_K3*Qz4!tDqCZGPL@@y1!V7eh%XXVtvArLf4ax4Y8~VFHxSQ})vRC9^gh#r(bcTq z+3I|=OLTS1zNF?)@gC7Ntw^?t_lvG&l_BGJB}d0wxA~kBUE8Y7J__?oiLPrk@x{kS z-(fxAb7u6NRu8m3e{E>(L8pqdP(E2Uw0>b<1wR;lmt|d6?MoHPJ)#q>G3@9^RQv9> z*7)2dy0MjbMdi=O_oLnj-eV2*IXbGTwFI3eDna?@qnlZ0(DeS+!sr%O@G8ykE{NyI zR#t2DOwkIwG`f{FiQ^qqJi4{Dhus6*Bf70s;hHL+CdPs{L_c6nMeFN-4_amZQ1L9U zbXR!)I{HDY0=qW&063nV2>u>?H@hWxjosdAP8L1DKSj5HFGfFP<+0yJcd@Rs^TAi6AF)cnH#$Pci$|>+(0TkmdzOI+3GKV>-Nnr`MC8Z*=zl%T7EsP1MHsQWbi3=3iw)NFY7#;_V>N4t8Cie z_p)sG9iz4n+TZuKBG|OQ?`@T4r{etjSU0n$2vSZkl;CGhl-|gTWXwMz&JHf9(d(~gh zg}AsMyb62=61{k>lB;Xmu~gISCr^|_L!IqYY#e4e1`LzY&C(e0;%np=9kIs$+wI5 zgs<7gOtCg$T#wIGYX@47&s0l&RS1@^r2hWX`iAoJ@qU_hgyMqgn`RwD>+zp%{p|BH zIo+y)uTbgs&9LU6Gx+`Snbs+eQ+sDwgW)fT>ixqqIole8&X?->)fjh=Sy`zGBIYpm58rsrHalFzdz;$tHLcR|4h*z z+$mw5uFMud+_m zR?d?*fwN-Xu@==+P8C(P@fER|qA55V;;{`W{{tK^*F<@yc#PwF&~wC4@GFqN+&xs^ zXz=owHP-TGlwUnxvNk5i+Jw%R6Cs`$v(7rzoa&znj&a|!HnmVr6AQub$E>%4EtS*7 zN^oAx25SvkA8$8Wo6vfD*l6uU=LzcH2iB%mSihk5e_-uttqjl8z<4Fbd|*{+L#Fj% zlQo7-%YUkK+y(0FgN=Cq^uQHc=z{eLT0g(Cf)A5v{oH4rN2iIo zkpD`|*H*c%RNrc_6T9Ck*Ijw0$Oo5k=F{W z{`z%`*y{yWe>Jv6tYII+_S5>>B-XaCQ2+4!yG5*P2YagV$QRW9V)pH9YJbp9HwZD`-n@#P%!G42H?Z469z^3+>vv;H6dtA_;L9w^k zRxj0F^3$;u?H)dlkF9FY@cFse8uliiXTawc&#=cqeIsODyL@k|ZwmM+QP*zG&IZqm zt!LNkL-7^hS7RI6JK3ASZ^hnicj-&3N|gD&h{oY zEuSv-RdlMT)?bJkr5?7Q=&#xj&v!!m2N;joW7xF3y4h3Mw7ed*Uu4trdfZ-)rsb1R zs<+)^0F8%Qe$7h_u*JZT+m?F5UgvWkZ>TK>h2ot`4YynS+^y6|dk#Ae+WVAv+CIuo z0w;+SJ8CeMZwu~SYLvZ>P0M?hZEYJU0N4FlBq@@U&br+Nusv&3jSp4}Qe z2l9W3J}ifVUjUa#R^|2i%QLo#*6n}BzQGrNz0@;yc{Dv=@^+~)c2zX}o?2UKtX;?F z4W*v7@A5fGjI)QJY5m(;YP>y)ecwPhew0eHQ_)%YeZ;T8lh7HSx<7gV`~q9u4^>{y z?gHgcfH$(${nm5f-E4Kg^)mQ8na}6a?Sp9D-*o#b=br@iWs7t>awv|EHxrx$F2`2C z$FjvlyB7Nej8C)^$y|Si-JIi8UxxhzgX7L1b-gmnZp85zuvucR-Gbxg!Rt%TwIASkO>nlDXLsg! z0yqiWgX2lyMPk0)pW_dK7evmthjF|gctGhF>`@#a4o(J7;P_bZQ>9WiJHpcNW-Hp2Yr(CE&Eu3vDq{IaRy^wqsthXXALy6glv_u2Qp??X~P5 z!L^$$u_3bvye6o(cq`cQR)&>dRS;hZ@x0Qj3#`h2QhIHH zRr!z1_v}P$Z<_b|VAvi?=i1H5y#D0c-N<-+J_qsTY_-4M9J$`kM`w6X4^zu$gT0%b z!45t}>&G;9NA@E2VD?+=Q|$NIU7n`$dF=h{&)F4HXuJ=AbL9rRAzH7$8|_u@D6YJ`;_rvEp}jbDuw|c*JhUR=@A}RXk>Q^Z8)KAM9+O zPgMNLUh4Ciil^-07&ZTyVjZ;aYQ@ub3j1U557udWB|1y&19x-I*s)`&y~ogJ?TPG* zV6)P%_8gyMEB$6)Vi$iM{`yX(%eMC{l~+!vbk!d0bDK)S8RK)eN~Uv&T^{lespLAR zeSW4=z`5e{luAJ-IF9O9`LipPaEAK4vQmsQ&gTy*m2xtD{;X0NC)?+vmC8C|Jk?hZ z>bp|u7AMMQr}FL22=q+R65^#QS8*o!T%&SL=M3jp@fMZqI@f*fQ27q0SSr=8;=L+2 zaLW2TymCXQiqGkl?{*sa{CwpmPPWg>DmQoD@_9q$mQF7F5vc$3%J(}T`h1{r8)uKt zCo8vej{1D1atEi?1ZtluAFR^Z>Fo23RUUSF`dqb2H)n*;4XZrn>_hA0Z7=5%T5pfN zoZHe={j|OJkLcyp_PIlq-cA#;NB86UIIY-pKdz7SAbT#f53W}_gV^f(2yng78O2uL zM@R-wVyo{dGe@KCiKcIIY>=!2ELL zFlP+=82Irj!<-H5pTWzGC!MH?H2-VhlffsQ5$s}1)%!LhoHOhg@X6pvXK)7P{|7in zKIMGEt_EHae9Ae>t`F{4*KXN$2;Z+15Li7I2AZ1!^Wv(9Co;d_maH<{{J@!?g*IpfKAJbbpwcxN&? zUmS<{&Qht)OmwQaZyDS#Et%>p_vPR3OmKQ;VtxGm?lfm0o8BKubDm_=_a4%nXV9tM zLr`C?On0WRp8)58mvH{^9AA!3^Jan8BrZ}6}s=e2%Omz;i z)%OgXs{eGNX5#$BH7I|vGUS!{h8`j=QvSwFuxcJ<*QeH-swrk^(C=tmXkvERzti+)p<@fdpj7u zSK;hKAC`N;31Xhp`*~I0VR`#*Mpw#ZJ7>%j9CGvCm0jiPP5SDOF!}dXxEn z+w0C?Hhus1b!Q~|8q6*_~ok09q&a|ex?`=egj+&ohl}S*H(So z8L@!sUjW`xb)|C{ogr3&kIPlg8J|ysp)%j^g?HHWdtpb_H3e4Z`@5^=6qtT5e@v0vbn96T;+AUP=P4l)x`({_oa~?)#cwd6E#Wtre`v-82+~z#V zrsL^0XFQpg|2Aim&tstcT+Sc60@mNE`OZu1+rYUp-+7%~6PzjXot0>PKCs3y!Rog3M7{_?eRE1TZu`r4_=ruUb=c50(Dyke_V zd%t!P&}n=;{Mu>3rt`C}oet}toF6jpW=er|BaKvt_bl2@vW21rt$dJ*~GpB z;+YUnS%UrH_uuwAJJ|Go*#YM~o8B)w;PhUq@~4V)DF1G?15USPn#_vwCh z+ODSg)BASkovCbkpYDRQpH17(Mdv&^O?1TiE;)PGsPeSEE|Zs>V`Sd{Uv_>*r+T#i zzwBH<=gUD@{)%&noyxvK`Eh-}$Gqait_{`yW3}I%3h4a6cE}IU|2Z|#>E3tX)m5%J zjo9@4`D@Po>{4*O^?l=-(}l`QwLA}jA7TFlZ}nWzIxDI{=O=oCK`k9s$RlP->94_ z?guxk9_?27fZQIO0C!N^p=XM&;5OA`+Xo-0|$$;3V)g_RHWLd4s!%y#kyJUd?_V{6zIK?lv;dzl^(!<23&= z?ssgOe;M~@_Aw}*D{pkKvwsC=iW}XMo2h^Fy|kO$3T*mb+D&d9GS9E9n~2uudu82n zTU7bkUir1~{CoAX?(OKAUUl$1aCM4{`rswi%ef7FUS9oXcM`iX#Mf58)m_5A7yN$p z+ud?ov3}7W{DW1&9nbCq&Z}P0otKC0$My{{s<_+89*svecNaRHucudY_i~)ZySjUj z<22sY-5=O$yiZlH;hsjPdJABF7r~d%6U8#HS)-;~Zkw9_M6nv2B;wtUY}KD&joK~* z;omk}Ii^M(cOAyJN#&br)OD@xsyuu*3tXYb9qzq8SFcguZI7n)eSp!x?M-n(^)+_JYCT=Z` zSIAN8(>-o|c0F*WxW{cw#^o`%MpL(&&%`o2p`_d_;458To{z;5#{e6P1g zOZOPN3wQ$fXLc`eu59W4#vTpM6fNC9*fYWKe4^`oi0g;<3V6BE%8g{N;&^HH2ke{K zpMrCtd?ofb;7rlVZB6Fyf!yoP;JEt!%;XyPxlKO8@fOFh{QYhUIzyZR&!};~TjgVl zUqZKb$DrZ+L9oA15N+HVJ1Oq0BR6A5vj?FwL^*Jz+r}Nqt^}T2qpdrh^WOo^mF?U^ z>~`Q}@RD6re>ZTZc+kD;6J@Ev^cRBkK7`}(=x}Q_5APJ$nNeEU;J$G$K0QM@q40sxWO;f{8L2}nBUsSo^Cz1`fkRB z;=SDzbeiY^zO7~-_gi!Zk8fZ1dp3=4U-uMS-M=$S^mEU%)&0BmCHuM8$h^Pq@7jAp z{r|CAe>V!9Dt5yBzA*;6V<_%@2hNoP-E?%mN9QjC-Kl6geqJ^Qx?R4c{+PM&K32^^ z?j&@Uhz56a2D`gCeiQhz`GlLem-1H!KTvas>wU%LTf_TVHHW)L(fNYTZ=Q75?W1@j z$ltr>2=~(0`<2s#`rgbT;~6&(ohsD#W)2x++`$J_oW75F z$QbJ$LhJLvXWcFbRXjt``QA8p9GlMf#<{Pd_3>_;`z|_5d;#-MtvSx!#y$w1Tr<_( z$36qTSTfB$#un?<^OWiCS+w5X(%s8GC(Cp<_B(8^RQ0cooaoMAtNOERKIfJ{MD11e zC&3peQ_%UcG}NCUGTlw+JXw~VcUa|5^XU6oneI+B%|FA*bT6Y*#W2XfH)4ug=7`Fl zDl);Z)%>Tsg#A3$H_bha*58+&=KjLIt%_QIrn$ef>3hP{T zxev4Hd&1M)zHD{9Ji?ylj^Ofiy?VO4fqe$9Pv^*4u62~^r|;{|a%0%^z2P}-1vY(; z_j$KJnZGwY&mGC8?+wp$C$M`$f0E@qcLtljH$2~6z^3mF&v)M>^Zxq<_X~7}px+}e zxEIm%dvuI=(Jg*VwU@?!zmx4&Ko@)u)=gp4@nC_wl1<+$S>UE0SLO2reJ^IATki+z zKYdT;Wp_WDem^aCE1aM>{a$+2?aij|kG$qCV$<)l*WKWcl%IZYEp@Zm^!=7M+&ygi zzRhyC|4%A^s`wn%k9TUm$CfSGsO*L$?c?`tzaNn@#=s&>f6EF6n*V58dT#dY^Zf8+%!`@3^G*d3U=F(E5GB&)l!B z_}l-v`-{)t*Zjh@f5-CtzU3Y_0d%+%P%=;g8By&0Y6O~>&*;8tS4g5!O_jYsSL9&}e< z3-vcieCKXJ)ALA~;*fj7=Ol61jrqgh|08Z$wC?{A_hxj4NAFi0aVxRu{fZ-Q9Gl*+ z_};yfP48EH?>0dn4$$}=b=z=Utfft>1S$>W<*@G@eJ@@oXB; zqwXX!kLOW$C!5Cegd26;Kb|MudT2dfr`!*O0pm^EFMN;8g}-o9V6)aQ?hk0aKA(0^ zqxJZlc7J74|4zHtsXVS97iyk%U8(9j9H91}c4OGo{?l$bGPnP<+n-JCKj&HomgnvB zf*Xt0?Z4>GHL<RqPn>E0LGnjbuFk$`qH}oj#YUb=lqTbJ<$I zyQk2!{_HIAhg-u^^CRC@3;v+4jq}I)cGa}J`Di^pj`uQJkDuc$<2a3%eedh?F&$x-an4@h89btK&kdd%#P;(0-ZyxWY#Of{ys~K8|J+yW2Jd!`(|FzB#j$C;Zt$9u zdAx4$ya-i4y#EdDYgenR*9EP|?`Cfe$CDx6wbrfPCA1#T+r1Hy*gjq#D|z$K`gl>< zyCn+8hu5zvUYyTEYE|_bqji6)c`ea;eW~W%$EN;P^Ey*`TtA+yRn2>hAwAy~C#Msg`$^O~3kgo7&&Zb4p`<*#6~4 zGcOXY_ixR-o7mL;W?ls{FOO#4<7{ewOYbE%wf{bEC0e)tey?sBfBRc|Eq%_Y)yC_L z*6nZWJ%-lpZ|n7BQ~TR`$y6TOKc!Y%?`e+H__y`a*wp^E-c&NTzpb~AP3>>*-FYJ| zA8LOmuN7Li{~_<=n?mj1S)#Ld+~?V~9`>%Gb^E(|c3G^CxBsqQ1e@C5)hmUj?LS*| z_5Q(eYJXR+Dx2Eh)$2m$_ILGGvZ?)#c~&`p`+Is-(7OG-yk$3s+Mg+Ud++=FaBLrM z7h1Q!ulFTdkAGk9Yc{pNuXmiv7Sq+y8_&9-LZJ)}wX%$9h}X)c&#Fr&J!>|7xwV-d7x__K)?xXH)yf zdZ)ddm<`AO4{E*St&9)#CqXRs z)}iVAPq|%f%74oib$w^CSEUZQGJ6d>j-6PS;tj!-;ud?`(6dDnxMtj|-WiU!!T2(- z!X1>qGdM@S=`CgV0pA(-rdO^$#YccE#l7uSy_5Vbc#XBvYv=PUXSMef`#Fdw%QfCK z_Du9z?-lj}aHF^!FOU5?xQVsS>)*h@@mU3izrX73X0HeDjmY(yG^F@8a8ld`uhm`T zJ>Uef(c8p6!mgS~@ssSn?BBp`;x>A7*sb<2UfC|gQMds2BxM{d<(cze67Gci~RN14U|Fa`s)UA z(KMck@pS`-URC*Zyg^{(Ysy*Tk?rvJJ>we$QhaV5pAdM)=Lh2(2A=i#q4>K3sq9`* zzH5A9Alv81;~NED^SNJqlfYJ=pNMZ7*v%dU<)4gi9*BLN+M_%+zE$8M_IU85_|}15 z?5W^s@ofS_+3I^e&&Rh7O!YZCzFlBB`&G!lB))y%WA+B{oADh2-=X#L=@j?@JzLb@ z0oUW>I|YJEReRFJ!{FTbhXPIBAooUh4x}tso-LBWo8!9#PO(RUyT*47+`n4Ib^YA~ z{ohg6=LhD3b7c3xNcOAXWbm`>)!^mEqk$>x57_h2G`|ez z(Lk9sq4|Fl|5#uin*LsNj(j|@f#M<)wx{LR-Nbt^&mO2`r}kxc^8HeFATyb$|KL%voBGo?a0adW z(>KuSU4MW21s0NpdY(O54hXD6!~5jmSE<35;OV-}fC7NM+Ogi6Mb>Hr;<17RX@J{fXg$Ofvud#SwvjvTv)Ru3wA@ z%wW^~lM#X0Y`R}EA~2Ut_e(|uUS!k#k`aLgX!u?w?5}d<$iN#uuLzC|guyawWFfznzbNI&cqL9j{gdM+I8@{CoW9KtHxRzN`q238b^> z_%t@KlC6$UzsHXae9TtIr}OdS0w>UEBI+YG{;7eb@2mEtiJQU8t<*q+jnqDMd`S@L zfycIR@!AsuAG1@y(csV7>EIi{ z->_$Z565Q&&ahu$J0Ga_W_h{na_oKVI_$ITR_xLrQ+Fk~Cx7kP7AG0s953nOQQTxxaE3l(?Qu`XRtFzm&o3Q(`A7+nXC$nd<)7eYeFR?eW z-(&A#f6V@geS$4EQ+t1BmuBCvi`rX*U7y{Q-Im>jJ&-+woyJaQzr=oloyT6u-p|fw z|Hj_W4t_%I`IUV$+uK6zug|`j-Hu(4J%D{5`&o7m_FVQ;>~-v^>^?WKUzaXMe!%&;Ek_Ec+CD4!gu|YTp~|O6*PS zChWcJhuNptL)k_iwRZ};4EtqvE%q9AbM~j~N7%>ML)pe>)V>V%&FpM;BKsZoL+l-F z^&Nb*KRL*r#6Hhn%r3T#+MmxZ&pyVk&%VZP%`Wvh)!&m{lbym&VozgtXD?xoWaqN~ z$==O=iG7^Cj(wGVfL$`5+F$YuYEKn*Lv|x}H+Dz%DE2`1T=sbOo9rz1C+y|y@7P<| zm)QH*fj!ioU)W{Y)^=)dE%r_97VLO-XLbvAu^XtqZtMXZAI{ETPh!8sUdaBCy@q{& z{So^&_91rUmo&c%>|5Ev9n}7Y?AzE6up6)+W4C3GWcOxgvPZKQvuCh3vR`9=&ECL1 z&;FcUdM~y21UsJn2m3*G?1$9;WOg<7cy<%^BKAYPM~j=Iq}V<5xxj~o;*$e= z$YKP=eHrgh*CC1i=#CCat%r!2(!Azpz4{jqHwD7>$E+V zGep<0@_oPtlpn(JSJ5VXcZ}*=$k zsbm|XBH9#jm|Eg)Ol@&5rjF=(w{|wR!xT$-`uebRGkYhe;il<^_?F|x!_wuYXnv=7`v2?W&f9 zb)4E0Zr#o(j2ogT559@&8HTA8nT66&Ihw9*iZVRk|GWKf)R5X=f$N)6fvi(Ky(Y(L zyEKHJuG2)0H^bBrt=XS&e;#{_>dmcBsork8aK4_L{#AeU{QC2J^nN3}U4?B5YL_Lb zoD{mf$zk)=?R%Q1>+~7+G)`aQ^es-;V`>P>Z;EYbOMJoc@4`|&-!ok9BFC??gFnzX zl`51PLXQtkFWUWXQ{@fu6sEGE+>KbDA^ySj7Pcv-^YYizAI?+t{CRqK`3hX$tlMe6 zaqNagq?2B!e2Ex`<-_@kn*P8*%6~8CYa5n!X6t<2(5BG&A441Ban9G5Q$3#lt(4{? z#bB;~2+wx}dlaWS9-gLg{(E`cH}U?isOh@hMU7jL)43hGyiRF(7-Bld@2g4UPUCF~ z-JUFN=S!HH;w?-q@h+ye_y|)+9K_TWXH{BW)usMPv8}R74MFWQ#Vse4Em0j)Tik)E zBbunRyo#xRQgi|v@V_g@P0<@|iBV`<{8OdnRbKUv@=M=*UgP}hi^z}dDvyP+{J%8R zyBp_Yh(lP9DNbWr^z;iDH>ezJPk|rRJS=e&rnac2((-DW`X@zWoGwKhObyYA{V2Nw zE*DtO*tEYhMKb5p+hGdY6k~Y0-VVm|^rvd6`B`EH+7>Tj>WEh{b;T-7JsMXy@7Ri# zLf8Kl+7$c4rXS_$CALxhKmN_QKJJ|6{Qq-mh)cY`Dx5ETddRx|@a<0f9M4xDKN|3P zq&`kx;_D^bD4UvTFfhlM^wM196E&A~KQM^y# ze8sU`C|@kvDcG)Q|0%^^rFuVCI5otN+@8~zTH-fuM`@go6cxDLH8C|seV*PH*Tc|p zD!jkiMU4}!XQA>%m2~=*8COJ9_`A z%Rj;S#&AA;o~ZNd^G#hprH0Vc_53IeP1kWCgJf$vJLSr#--SRsUfyt3cuTU zy8azV`Ao5sr|;oZFQ=l?y`1kTruzI`3hfJjvmRZ)dy=--2u!88g8w z;}5YZ9y$-z<^L+K>(}X{TrZ`e^`|dSr!+KO=NlSUjvQK_bh%Ms^^pIZPxnWk|LgLF zr&|T^RMG}!TfbSy5H0y_f~ z<#P(^rG6M(PpEw~4tBxsvcH!vRIhGFQT?L&or3!Rz5H%L`*pcoULJ)VTJGV~!^fY> zo4i~?%l%)MgY&~8=12AF_@9@bo)7h3uLq%V4xit@&KDZzg7uc$Q`n*Yhfmk-|JTz) z{SS}p_WWtP!TFn_a6Ht1+V6z+Kl*q={SB?RdOFQl3O#>4J$(OF*rERFaSUJ1di&A! z6?J^kzccmmg4wr_uS8`F8Cp8j`JU0$bjy~PmDDXLGO2kP@Holn=V*Drm(uKn+(`Z|-YCyM7^ z`210dQbkO^k*7bsn$BO!hItEK_k-(}?^3^XzB;AJ`gd6~K7S({B8S&^JubImy=KAr zd1anYZBCOoZOv&nPWAaD%~uLNp87t)V!kd(?KJ+PouPK=^15C<|M2$ec0R`K(f!uv zw`V`3aiG)?dbw+lZlvO-7{ulE^kL|trc3cO#tkus%cXPr@2(&Je|}m%|EG_`R9^m+ zhOQrnTkj7F=a*s*mtVx`EoaoY7=n(wq3yEpbU2R)%lAVY8qdPh|C~>c_y5!RXgfB< z66}}mhgEQV)N%TKX^JeqKdHC>H+ek%SEXxkeufC2-z(TIL%fbDT$jbv600z^MGmHp zcpp<&?8a1zt=#^6PCw#QUk9UlO|IACdTp-P;d))J*DI)3m)GN^{RP*rm+ya?>gA%# zYZtW~!>?D-eLE@ibX{IA=dZY(dO3fGHpFpE;kqxTmbk$Abie*!>+OW@6B(jSN44IY zg03T4LjFwKK~eEYjN76NrjED)zTqN-D{jTqXzNs$(;kTJFbeAVt8#jN^!p(+|L}5peDr#u z=ciNM&%g3tEoa>xo$CID*B{;X zDXfR=@N{h0bTX`OINcO8@&2tPp67gY9Y7XYpRWIP%x{PlT+aqfEwP>J(eVy^pLlOr zoGgXTNAoqsA(TSmDV+zvdQ(HKCvbfq^M(3P&x?e%KV2?-ob>$+y}zO5CJUZ} z(dGWr^zy%Kzag#{njUg^`@GYte}*WHsVVe+yev-&)5_xJREq40E=4{qOjxx)QI zJ^w<}i^^w-PGR{UD`dFt7iK-b!uj<4i<(c-)BnHLpTRg@h8T{i$;-#$?at=q3X?`Pp9Q&3B9}~{15Y0&kNB0;qSFm?MK4r_jlVt^D#sQ zw?`j`_4N;Z-S_|P^@H{!q4igfquvhm{tw?XY#mPynfgP+vWe9Pl|sX z51$@xvtWJD<@IvY>&IkXF8X<@>FgIdeUsCiFzfs5l z#kZJR;wYxJIE$$xu3+j4c}DGbrTBa0{`$PEsCLqFlfvWq>3mUO^&FFS;p-dvcS!j4 z5XsLk8U@cw|Gjd$K8v4&wEy%RWcdA%!u5u?qv-o8w-*{$DXw*+<6DK{Dm6t_O!awy zAy&Ub)2negy*}vaiT~Dgc zE$^bH|Ig+AJRbUfVR(Q3ZvBPZ{pWGi^%u_fpZ7a_JqvGl;dtmit)Bm1t&e{%U+6w{ z;rSOmzPjF`);B#)h3gBAQ~31oJjc_EO6j>lL+H4E?n>`h zC@zIgbzG-2xc+QT_4N;Zy-v47r#i0F!uR?A{Jf&hr&C?8J|0(WsQN2~zCKI$H4U*C z+hdC6Z&G|2w@;^fxz{TqPTQg6_m$*-{GJBQ2d+!MMeWe@)z;$@p3?IJFdj##oL;UU za6gvd^RkBcn5}g_cAJV1~0MSee_u!}wq_;1yt`=_tZXdmSEPvGY@>G@GZkWF!H z2lZd)*Y{bq!&BPMLeHbqb_C~3`hN5Zs!u;xs;8e>Nz?Us>-*s9TNrBn()WY)xLv_^ zo5DG(;+6r~%&rnnTX z`96hC_4PG<-#z?3y}r+_$5XGTdOYcOLg;sdetufNXVi(?S$IBX!TWmpIxx-O;OX!? z1j|`MKj%o#Eg3@Buja4b+w0HuU*hMo&F*TP4KWg@!*lQ)CmZ+;Ahn$I_C?eGY~8r*nJs^!aF5?wpS-Md1{lFXeK2+;x4ro}${P^HKdU-n`w=^w4ufdj6Ec za|i>~_(?&(uXSn&y*=uB_55{tJw1GWMYT^)4}bn*HIECeccxg&KIRQE&QchJwf z=~Pdr=VuJ@7_axbzW2FaU5~yWTDX2g=iO--%7<_wfEnp*Y;{N1Fxb z|Ka)c_8y+<^_I>T4e=>Icca~-EUj<)d{X@C_h09yT%Wk@$mDd zqTUZED!;BzrxkfV`nr_fe)Mrc?@x<*9!ftqRe1j>1>JWF9Z&TBTGyxd>v}(?_tSd3 z(kezi@L8Lejk?ZGl!n1xEH=k(~DYf{x9m@1+1#7-TNMM&b8)Yo!0{8 zuqZJpCGi9(AgCaCpfDxTL@_}yK`=$pG_5qP#H_TeG_kNurL-guY1xycrIePImKK&I zm6Vkxc_rG)?fBoC_y4c=OW?fZf2)7~x61uLotNf)h`&D`{QtdwDes%dzXoxg zs?gv2SXkiSF$TR$$46R!0`@{$PZkNau$^^09p1x2>HE`l@1r6f@8_WORYL!By1?Jr znEy^)`J?{8zlU8-`2zQm0)M|^)+ezwRF8SwlUO3PV>9K;Bzu%liJ99gQ#}fMozne- z^iyhoAl*B{udqP;-}wXi0=l2dbM7?z(R>b}u+RQWzr_BU&U~+gg*AxuR`#7xl?|r; zHq-x^@3e?l*!jPdFP-J}E6gGE&-8(I9>hBg`4Q)sP=$Rf6#t$_sFht3DzSZ{Uof8H zz5tFVR8PPk7_WdGLiL%p!~)k*fpIqH(Vyi8(uawBGK-*gw-IV#*9o<{#`kLSHD@F{zv(Na{i;@?|T2zUkRf9e{TO@$LT-j zmzX*KWtJrJDa`CYJTDj5dsgaSm6_-L8eJ#q%-l~6mMZeuSh`UAANNHA`_niY*J7a- zO8;lS^%wbMmPz%S`7G4$RvIsrnd7C=IO$Z5@kjssUH|@doZ9a(=VhEwh4mI{VVOel zoJ%O~ABpz-z2;E61N+^-YmcJ*=KDqzX3pO~@5>ec5AidpeGifb?(;1sdnM%$*e{X) zb@B)9znkmYpMNK8j{9HNQ-xL2`d~iq46Jj1o;UtP>zk=EJs ze*QIoU|kQaPl5Y)fqo8bf8e@0a6dI*SK?h_d>vf^)tQso@e5{Q_&HfS{G!D#jKx4Z z<1MhASugy;SR5OKpA)}q77xYylkq!_UjggMO7NSAUl>b8Tn?*bxlF}x6)R^O@T+CB z*dg{L(m#dY)A%jN?-~47;8%s;O8lP1?>YRc@mq!8^Z31h-)j6`#BU9L-B`i33@KKz z?TPpbmt#ANyM?hE-P_p+Xg4VT8doCpMQ9)Be&|4G3v@6vd@y6fp?#pEp+(RlXk2H; zN};P^Pl3J%oe6D*&VhyxVeBDj2J{hV3G^xGBhYGh9qT?6Z?W}wr6)=ld!|*c6uO?j zMK@P!k2|X!qjRNu@Fb*Tbb)jX{tBs^WrK&uCkgG`EtBkQ(p;haSOIAXX*uak(z&FS zq)SMblddAIA*~g1J4Y`zJ|Wp-C))ncEY= zGVvy*L6#`i7k5q*TgR|@=P==7>*dFw_)0d*4CdP%!G0KsuQtbqS(m{d78}7*k*~j{ zMt%-PbQRM)=cXDT<@8#8z5=hFvBtr}V3=?JnS}aIvebH$s1hCO}zSrL{lQ z5mzJM0rkbLvd)0|p$|dZA)araBd+$3a(KHtAf9U?;>~_fp?=B5o5LCVUd?3|Y)=yQ z-%oI7aFP_w*fo6na_Q~bHPQx@pA(ls`D!FyPeELbv>*OAqzu*!Ek*oU=xmmOPt5Z2 zGMEEe9#=>0sk2@`4sVB#3$wl*#@J)7WwBeue99p^k8}yUH4*d2l0)%JSTXD~_M=w5 zf1iuZ`@_%Q9{z^ZXdQ)io{ej?7C?iebEy6tsy~nHJhGd`_Wl-khPFGJ>WyX_(ZBuJ zMJu1j3!(F2Ct?28q1@Zz!>sQ^$3XeK8y9b=d^`FvulfW0qq>^@()e7q0^_z7;}qQn zS6YCV15KyQfRx= zSl70AN4}QE(nxQG#wXN@cJp@bM?0&a$Ap^eT+f6w_88J#Q5q%w_3yq3bt+$P9nNNn zucPPe(X4-K#&VTriLb|7TybP4uhVQ164PSQ@p_N%L`%2nPUX_|SQFtv4(xxM{ZH_@Jmq)ZWiT~anPaNKx9 z%4X(Q5f0LNmrebUP5qY5x}e;Ir0r4?l&8;uc2~^pacSAqf7vWgq|0XGp%y(!n=H~N zY35h5vuS-x(#-8Q_xqcZ%z1rll6l;nU+VC5e1BLIx1Q$X4(hKRwEyk+!yib;_m|I;*0V(zhe^_U zHWbQjKL1MN>cxIj#Y!|jFN~h+sXT+3$AL_F66Wh0Ql@HdU#5Hj?Y*IArB%lHAwLf| z2D?*F^El9_XQuic?EZ-7=b7teGu_~xtE_xp=0JZ&d>PUo5!+L)*>M-^`JU!+Ze7oM z>ou@9_ROaFnJaW>PxLd5tNC?{Y}Rr;FF%`^U$e-j^RR4|PU+0AbZ4`%uqR=B&98E2 zGxKZQ=6W4qd4AMD=^7|q1Ep)A@){_8E?XJP*ejZ;TeO1;-#^#3%BAf&s5GJ7BT^13 zzd&&wu3QwlTa=$_DWH11Dw8BO9On@{|HfW5v|p4^d zZl6nk^h-MOPlaus=U$?5!a?;SZ^tDXrxM1~J%RGLu0lCnYx`Kl^+pn|bh-T@Y_lAT zWS%cuB(ofgWS%dZ`G27N7ooiW?5U>m?c+AzKH={(SG04BeJ-2V1M5xdT=p3Bcxom2 zE6Klvwr2?&fb%$hK4dB(}33&O`UYKgdxlkH_}LIhU|guuB|At^E3T0P>xOJqry{_LWIxT>0Hla|LnYkMIru0Nh8FdeLQxw!{5|fAn?GyfMe=99^Frgx|bQj-dDm z$vnP9Nc_C!$G*{`JxQ#F#@p0>WT(0$X(Z|iPtR6(zQ}YlexxhvkM{BLO;0zkJ8n*o zqIznnycm&gO&uVG?trhd?oV`|T*A`bZjbAP8zcCU&->giEm(1(?c*#8O z){67Ib29GuV3*jOo+i>coNbl)u)W>P>%%wD9<#nE5q~&6MU>N=o*_MrbSyTC`a477 zUt7P7c(eR8(Jy^7vV~%OCoK@VkS!6%rIjvI19m^QU9^9aR4ZS_{M`!mW}A9FX?M~A zq@zi_R{WJ4u8aK84I^tsIs2jAV1G{b87M!WzNoZSdcyDR)>at{{e5J)J`GyMYUPDO zXNq>d>8oeUa`^G7o=t=1dg|Gi!}xh>J&jL2jjqK|YI4}0p$hW8xJ3FeL{fPK{ zr2h!{-jJ4ybeBgpG7r{+E1t%{{G8b~X8gJ`pf5SHq;%v9ip`>VoJI4* ztBi#I4auua5ZX1&-={lB;N1l z{@P6(w{ae=v>t`^cXSf-dqkH`BPC5qfWji!3*vu?8Q$?uhkM8zULdqMYw61ep?{=19yRyi}3ejb0tZaSlXDm_+C6uSD z!(f~9$S|hC9yKQE58IsQhQa556*G)gi0_E>=6W$#;_H6Fm`tOxC+^#_Y|*aqW3r7; zkZ#hL0_7BR`j`U4{EA$GVYaiIsDJ&Ki!8W+Z@+2337cFzssHCwy7^SjB1*T2$}!LT3-itOv=n*;?VXxG)36!*xMAL>d@A3o zwuU{b>ryJWiuQwCCVP3mtTHMP-wXXc7kd3zb9^3Dy%zKS>JnCmesXEiig~|$X<&U9 z=Z&eZCDNNlp*sq*Wqus0kWBmY!f0h1(w{0c&ks^v=KY>#3dVX}_-({%D8fc&&Sr|3@R^7 z=-O7M?jZG#3lsJ1Y85T|1NS-T{zknKhjOkP*Ou}hr1FmnebX0Besh03d&g#J9oEz9 zTJNBC@00kykZe7EAEnzTnO|)=Bqctm{CaTo_-1ikSTw#- zXzBP>LZ^&hV&(BO$G2ttykPP8T0O$f*R5bN|E}c4P&qNwo?yD}38rRf3Sl<$B1{@}^+bEPL>|1$oJVeVHKsb4M{x#Msj zWV~eK`=Pbiu<_%#z1VAua9~}r8#eQJ+idiM-L^Q)#?PPH7q_*U^`l<+^VH??Lg(zQ^!7E0Gb=`K;aOO);srMpDwE>Sx3dUg`#J%2v5vm{L8&v*7f`StmS zQ2xBBp`@FQuNxOivPHS~cCg6fu)mbXtrF|uLgt|J4a*;D_K!n0)gqhs#T>GE-sh0b zs(WesrcSNNBe07U-S{*|sw1ew1h4?+X_7 z46+2v=J{~2%*VOEC4#minA#sq+nFNf_arG;=I4X<(kPkFx3;A*;yyBqjiKj4DRf;K zPyTrFM^JeVITFX$(C!YAzcML>(xp(ka@#z#b6IJ*?MdiXsoYireYUh#c@>((&alYQ ze4VdVUKjRE(I0reLG8?-{w$H5NVlW3t+>8;2g>)?_e(RVA2a07kRQ)UMBCR2*(1}MyJ=57y(LXNDeBO57#2R@$(k-1hSAPS#Vq&)bA+(Bhv-9J^>WO*O z4|z0Rd1BtJiOZAC`*cNr+-ES`kw^Vx)`$D>is?Tp#$&7ZDBWi*66H>k%KlJuo|n;l zt)TkKXug-p=6PTd?e9gjAD7WQT}17iEyg#o^->z&`D9m-Z61gEOHN~%{ z`LLeKTTkiMQ$6b`{bowPnbN1(+v2_OB3Uubk3%6NwaT(|NF^lcE3oM zZRhjs>&dwy9_MkCF4z7I()|YiY3NGV4(f;9)bIPKUHks9&3V~C{wC5>q>_FW{T=f< zxK8F@5wDQyMY(kq=KYX$xNpsmqwiI$qU~8F;ved3*7HF{rOxy7cJcd1hhg(}HB?CA zKGcfNl6)NX&a+5zGjyvY$-j{*KGSxRhLM`r@8)sx9Ll*Kex9GNQ%*cL;`7!rEs5^y zCyDzu5z~%Z`SHK)w474y7*^7L}CzT%&E>%Xb}*m6Z?G4U?$_xx@)qdt)1$>am+p$T@cU}+ zyJ|$bkh^sJCB_H%OQ1-00E}XX!4B+mFot~vb``PltO?;nJQdaPB>yKcRroUm%LFG2 zR){n+kk`dFvIUZhZDtQk3fsn(NIJ7fye?hhez5R_3QvUa><~OHow4YwMLLJS z&Yxbq<(eeE=M2T?l4bTYK<#kk$`_t?YpH0C>pyF?hs! z2yC!^3Ldu}0h_F!gD0&G;3?~u;A!h|u*Lc{c){8PUb21*GW8^AQNIUu^%Uq)&w|0~ zufZCNP_KfKs@zIrQK}W}pz2_ZY6rWjE-+s8f{AJ{n4*S&spZ}k_I-$z&C&Q6&DZ!C zEztNFEz}FtF;#7U8kJ`*J~HRjT#@d%^DxIZ5ki7 z9U33Co!W1R-K|{$_i9(beHshJtk7iekY)joXe!vC>ELnA1~zF9@TBGfPiY?TwB`%N z?A7?5bxCX~)A^QKbUv$fo$nnEoo}y4=i45v^L->#=kq^8=W{+%=W{+v=X1V;V2sXZ zepj8({CGVW1klT-XAQ| zZw8C?Az+z444kZw1S|A0;0(P8oUNCDbM!mG`TA6Ffj$FVq~8NB*6+vmF4OtAROx(N zs&zgtt93pu>vTRY>vcXZ8+AS|n{_@e+jKrIJ9IuSJ9R!TyLCP;dv!i8`*c1o2XsCz zhxA1#|A_u5*q|>4kL!>JB=CsQ8*DJr!Q;k2 zu*nz%o-}R+PZ_s?r;R+Y#V7zT7$8^ZI6SeY)^xyZB<~4 zZ54RIwg$Xpdl_W*jiAN81=Q_tfDZfHpvS%&47S&Uq4p2K2>T~sr2PmOWj_XXuzwB4 z*uMk2+E0P;_A_9j{T!HL{~b)VGe7#@ZUqP0ZD5w&4GysfgE{svaD@F@Fwfo&%(r&{ zi|n1iVtXuDW={Yo+f%>_dm1>y-XEN8zZslk9|F#|4+9t2M}mv&W5C7sB5W4r;AZ<0aGU)paEE;bxYJ$@?zX=O?zO)J?z6uN z9lD91Hm2gh|_jH5l+)e!^6JGz01jvio& zBN8;}x*Vu?ehpYy($2-U8P- z-T~J;_JA85`@qePgWxvDr{E697vN6Ead5Ze8*s1VdvKrQXYhcd1w7=q2p(}<0UI33 zHRykb4mLTQ;7NxMJmm-hPdg&O7Drp~g5w78lA|NYoLxbSvpc9edx8#UAJF5>0E3;G zV5svJFv2+$jC76wqnx9`4$iS)jB`BL)ma9{JIldD=X5Z|ISWj6&H*!=4}b%m3&AYs zW8e_yQZUE4930_%7R+$WGo0^%vz;G+bDRgj z`Od@O0_W%8BIj4&VrLV$)Y%L!bN&QYInRRC&I{mb=Vfr6Q@$4c@6^DJP6xQz=>@kr zTZ22CesHHV65Q>)9^C7^5!~nO0v>S2gNK|+;1OqUu)&!Q9(N7|o1BBdlg?YgQ_kDK z)6P7w#aRGeaNYr4a+ZS3H5s(Hrh&TaZqVVn5A?X^gTby!Fx2%Z7~y&XjC3snqg*S& z4zB0H7}r{`t7|+-2 zy3c_r?%%;wH%>lThT94bblbozw;LSd4hD1FVc-b&wP2pR9hmR#02aABfyM4vu*{tR zPIjk&749@}hPyvF+kG=Q$2|m`?;Zv&aE}BRxyOKu-9_M1cL})6eJ5Duo(fjGXMn5S z_kio%_k-)*4}lxqi@?q9CEzyqQ{WEw3UH^p8r zEN#*M9s>;ZxWEWc5E$tR1*1IIfE_&7fia%;U{_BJ81Lx@CVF~+DV}67)sqTlc=~|@ zJvV__p26S{PY#&l$puGvZU^%`gH6z?oB)jJ2w@IC+z^ezOmypMrHyi36x?{aX2 z_gOH{`vREnT?ZC)>SX4zR*o2hQ-m2hR3>0M79q0OxxTgA2T$gNwXh zfs4IO;8JfhxXk+#SmiwnR(mgitG$=ObzZq0`roU88@v)2o5^R@Ul`ZzQhm0+mvQ82>y1Q_XC21fZ-f*pL%gE79fU{~LI zFy2=SCi=F5DZV$sRNqc8!}l&Y(Dy!=<@*R6;yVQ9_>O`jd|!fjz7t@+?<82{`w=Ym z{Q{Qx&V!SEm%s|26ovlxso-p%9h~Fyfb)H=zy-c=aFMSKxY!p3F7-u&%Y2=|DqkE} z?Mnn#`+9-ve0{<7z5(DyUlzF8mkn<74F`AlMu9th`QUEfIB>6T0=UmN2|VDd01x?Q zf=7J!f(^cT;Bnsqu*vrbc+&Scc*^%Qc-mJ5w)j?o7kq2LOTL#u7PJwx1Z@HJpf^BA z(A%IVXg3%fR1by*eF#PbeF8=X9RZ_)j)5J5z6N80z5}}kodV;7&VY$Q=fISp-@()% zc0Kw($O;Y&vVmDaZg5CYFqjh*295~27R(E32j&NL0E>b;fyF_wU|CQCI2pvfnjn5> zU_lVSGq5P=<6sxd3clq=7aI~>36{0$(g}UoieG(iYsIhD_164e;^Nj@I%6zazX2|5 z&F`61wT`i3EL!vX9;;jPJ16T}^IYp&^IRKS^K1Og!m~|yb_mbT*8F;ZcWZt(X|M3_ z6aE7t2ybmf2ybm<2ybmv2ybl%;fWERuEG-^!tduK3Z{gdM&8trU%`wJ zz7+#QenU7bg!jOZkgEvigjl*@EJ6%0FNELQ%om&-%8wTnp}fW!p}fY~p}fX9p}Y_0 zhw}C>2<7cx6w2GbIFz@4X((_1GLfrFehw&Oi!+4DmVZ6r3FkWL+7_YHI7_TuVjMvyTjMo@1awUpfDI!;D z7_TuSjMq3YjMtbI#%mlB#%s(8<28;5<25b|cl>l(Sltvrd$=UX-&@l(Sjn z+9uNM5NURbG`mF$_lg$o6D>R-T6jpb@Q7$(gUEGUSVlPSv4P>d$FjnCj|~au zJ(d&Bdu&8F@3Fja-edWq-#{b3~+R5NVDJPg6LbhbP1N3_KOiXW(h!X$ha>!=CH^v@2TW=Pe)M=Pl3k z^DWKy^OhG0PqCkGOPNSBS+GL*X9)jnKOf&Yem=hQMeG6*yGXk3J|pX*t6 z?Oj|IsaU98nkqC%x>u-Qy0trxZzoj=yCd0g(mG)$lYKMkP|{J-MG;?!=RZ7usWc#& z>r{%LB{^qsd!E#0#tE)?zB5loyhV=ZI?FOBeit-Zy|p`6Zl8b_Y7-JN72ITkzXTej zju7fs#|mwyKGc04{JgwMs9(LM2iK13T|$G@j2<hlbPCe2i8p_kR!{d`=bwD!Lsg`p+x$^c!LwWoz;XlxetJn^x-`TErp=!4O1Zld) z*ayC-QS+elf(9gWZKt-*N_Ucpxf5*t%pH6B7@p3~mxcb!&LhZiHjA-(6^{clEoBK1#FVt5qY`?lt zD331@>E9C?+xy|EixF5Dq-=Kb6{p#&Pd3-&k4fe3upd{y`fWcW*esu`-oo2 z-JdHjpQ~RzDr_EK!sAJKys&+@58&!oXAP(n@yCSncwzf~5Vl{vFrbFwxwi8SNao7Z zarLWx2iB9#)vvA-c942(0JnpDj|^<)e&6Cup=y~>%$H2o%A9uwG|2aIrcn5~`qjG3 zNQ#dZ@uxF|BA%;XwcV6T@tGpN(@k9c>L8&VeGdrb_2g5!5}pn=SHF7vCSm(_-&9F{ zu5rFwy9?WQ|IJ+e>WZ6d$j>!MJuK`X-wC09^^#EDAN7b2C{M@jAYW7#SHIdvC@;TR z_#em;iga9gKCT#7;pgd$)@FTNdAzWF?`3gC`-OH?^9Dr=`+lK8zHvf%e(vY_xbpU7 za`k;Qh^t>cAv6x-$!(ra#3Nk^Z0_e8a;WjUi zt6$xJOO1%{FqkXmi%?!Zw|P1dk9@mecWj6C3fivS=ILDdx>OGp{R#D}PY>oc&(D?T z7xBK?gHOQb>6@WEJy#wtY~SI*T>a|LLPh_EaC^`Y0~)Y-K5hs3;)S2*ZwH%~8%^oB z2KnX=5sLI&{pzzr(kWf0u+I(Q%FE>%znO#Hqxbl3$_9foR729!ZJ&*UDF%{)=+po$)x$^YQB7W{rp@`?&Q9UVaw121( zYPO3j?_Xj2&IrF>)pDYFyf4>ONmpX{ZkK{`?&@o9akI|hI8fH(Jbti z;aqvVP~S0O^ZeZA`He6$UZ}6@ZCw580HGb#c|v)*Xi6v4x9B#me)V~wd^@yu3;t55G{~rV(8I>OP^O|6ucU zHI$Ak*6$Hqxxb$LT>WZNE?4evgo^!!$NTQhZRU#go2y@4Bf ze)T$`qW)`mz7gGPNR4Z`ozY`1l$T#4>=E7Tq2l}rnvV0AX7U?t%=~SkVt*q$jqFUa z3&<{ke&D-j6jxsFT=K6Xts`wDl_Je@+mfb{7Ld**T}4_)+DIz3HS^DHXX+}_I?_f` zDT?YLO(QKJErG^`wjakeIdsUlx#X`D{{7>)a{nsw*9iNXJGjP$wv4MIf4#6*-_a~| z*B!?7JpPM2qJ{3dBa`fs8_++#&c&5NQ;Tba-c(#KbWCxx&^wEb_B`F3;%K3j#hF5% zDXxn#wCQ6KK2C}kS&4EZNmEJlN#~GOlkOxvPRj12{G`>BOnWEk zaZ)y!(vhZ;=9A7LttQ<`dVHptkKIlANmEJZkXDo8Wj^NikfxI6lkOxvPRj0~^rUl0 zt4Vi~9w%k@QhL&zq{jpHJU%YQ=+zIK<5B&HnSLkf@yAS?EjG37v!lID}nA)QNFO}dk`j`TQbBPm;JmM4)$lBSZTk>-;Ykj^2UOIl63igYJw{yMY# zIiz(jQGG9)+Lkm5%I9lZz}{xs^GTaXBe$FRQc34S#XNt5bO+@lje5(pcazTlhiO~h zHnoVfi8Ny;r4MMGX=l7cx{KmToA#J?(O&YCX5h_xyj>aZlb;lS9%{}*(%n#zZl9^u z2hI4M0sYvtkCU=bC_iZ`X+G)BLzJErf68jsN195SPdbORI$$3)(^r#5eqq|F0c|kt ze9}3j)uflDkAv;<$H5`^wZpGHejV}ahF=1H{qR%qSFe+A*8~+dL>gKoFv?}>dX_Sx8)*jHlfV!w<%5&K>2FR@x&x45*pthiZm>*Mys zeG>OWTzULm@%P2Ai9ZnkS^SsrKg6GoZ`b|C?gP6^3I2qE3AZN{C9Fu;kgz4;i-hkJ zeopu;K}p0z=EVMqgA*quu1owl@$O_Y=LBuA1jsdG}lr2M3*Ni&nGk~Sp0 znY1sdG3m#o^GR2dl%AfRVLfAe=JuS@^TD2rdOqIs>7FZlZteMA&kuTj((}unr+bzq z&q#hKd1dmt%CqxdN1t#V(%Tj zzv=yB?>T)w@AF;iAS1%(Y|Ni1cyk6Vofw?@q5wUy=S| z`g`ee#^RArjeK_G+L4<^){Sf&`Ta;~ly}sKQTe0p7&U3sU8A(T19_+N&gRV>{n+TG zqhA=karDoljoU+RkGj3_FLPWvwTsO&m1w_KBqvCr_L? zv2x&rhYZ!SMwexY2N5;o#lecEiXSSjR5+(~oz`bsziES}-8Sv^X+_h@rrkYl-n0eN7EjwUZO^pB(@svi zI4yX3uj#i=zkT}j=?kZ?n{M0{jUBHucEYI)J1Bdz3u8DCF!egVmopZQv!I>O>O95{ zU=RHcdI!tB{LMQHXRTQ$7J^bkSr@!(vn%tnSp5BdFV+VC zA|R6WXKmR4yv1`M{*FJBwa1$}JK$ZM(fGIPo$(&CZfputPsJNNEAR%-`*8ZTnDxLr zITP42{AK?U{F|X;c!TFD{C)owlxUSwnIZMXN|nK`kp{49r5x5q8qThhZe#7G5iD97 z$vR4-@edHkvpA`kB}-*^W6PbapH$9nmZq{n(sZnEcd;DlZZ=$+#YRc9S)p_f{+;!` ztXP_hJH7YgZHe>PWV~T)s`LQ0gdYCPe7U2z0OW0!RNw!>i ziajSi&0dt2v-Q$5Y_qfi|600=?UGiq52R<=LFqa6nN-b=Nvqfi>3P;Hy}*8!Rw=+2pOvhm}$D;a`=Z|2m2=qD9 zmNt0nih?`yk!F2vD6i-7aJ&Tq{%c|L`U35ZhW#-7@z5pEREoa^%Jba@eH#AJ9_+vz2}jx>g}Cuth#%~0On;iUPbB^3X12eZ6Qq&rA=LwSGk^jtq6|EEx1-q)o3 z?PEJ|NB+l-X8T+*W_gc5+o+5M$`6zqsOMiTKc%x-UtiLJq=9k+>dMS4h_t6IS&7d3bp~|{Txgh0p;5r4dvq#Pj-K@2Sa)K+o4}0Ki^+?x+02qxbd&d z;h#q7I`?4sOIXHINN*w?L3#(2mp2v4>s>+qfZmLD`!J^h_B}W~3WYuV8q8(rkR&tT zC@3$dkhF~ae7;2?9oJ~+Z1VT(g%cvo&B0J!?{6N4zx(^Me**jc2*kVKFNN~*XOhl= z;;}z_0Ltgh6Hs2xOVDumw?TROgQQ=PwvYz;XHf6|SpRU?J&`}K{uEMtH0BvE{}gOq zZVTxFymgZIe_(t*gZ(-3e^36vc%6mK^IaiTZ!+_RK)F8(dK~%qcyo;=yAP%7PdXUN z(-)GKl7ANIW6&n#Uq|*opgjFS(i5cTNwu5J`of_+U02fHq}il-q?1VRCw-E19qB(v zKP3GO%C{%5ZZwkrCn)bvCCjWgh_nrq_h)y~O!DVLc{x+bo<;V>?S`8e6B*yparzQ^B=#q<3L zje>mv%FFFD)XX;+%C|d*bR_BRq=lptNXtp@Aq~`537gmV8kE<^>*f2?Vc5Lh%TQkL zbtCXrWb9jAq1^5TF%NU2ccgg{bKS5ws!^WCiq_@ zeHqHPFVGJgVV^|&4(LzN$48p=KTBFex(&*=qYldR^Zwn9V?Wmqpa=0b9s_IL7f?6! zYiKLzd1x3kaNG+VS97{y%;0}@j9K0$)Xn?rukEd)c)q`Xz<;oL|9nCE4e4ppi==A4 zna)YtnzSuxCn(>JUeJTQ9R=q3NkE@1G4t<$^6~lrx~i4A{^7Ke@%=!B^8JA8lnFQ% zL-~FZ0et~BA5XqrT>om9m*Knt>F$Q|?U@JV^@PWm=@-G~{k==>=b@XRzd^q% zH`Dz>dXDr?l*Q|_Ofl6%8bo>x=@8Q4P~H!t$PUb}*Vmct3Y;hK<0((Ki~I+mGvWK| zaUpOVEqck!e;1VJdywoWpuDVa)-#qB&DgI{zWsrDv7pFo_gk-+%8zdsk?u6vSD?JS zK)Yo0ne|3ov4@%E4Bm);9Sl3L9V21$`9B`Y^YP=+O^6R1KmP9c5;(3Dy@t2wA${Py zAW&|gy*ZoAb{@vQsdfBwJE9}a{q;A*^Lnp^&9~FoY}zxRW05{EFY00Qar=bgk3nbr zQ~XufJb&Ok;?XUPefCfB|D3N+%%A1{HQ&muW;ucLfxvoDx((;Z$TtPL209bU>stin z?OzV%$MMx>cl<^6NL|5g9YLO$*foJR!m z1-$~EcvwtUj z&A-`xejfMll+V}GzoxgEHSP)%`p1eXON8cX;v$)(hxL=#9{qN#7J4W3k)(;F14+k_P9&Y> zH`C80o8R5%<55fcCFxJ3$}uzkI@0c>Hw$>7-ARZY14JdW7^8DPGVawkw#l18EBBO{8N;=aANt9wt3Y>NswecLQk;(qW`y zN$(<^Px?6ND$*^a?~wBQX1xCokljprg*2+sjE^TBNIIIdjC3yPL;L&ln*~89BDnh@4GVV9?`_4aE z@t5B0XHaG~`0zK>RvSELKpB6~hxcr-vsMTEzkv9PG`{{M<8HJYyq%R?D6zh{k1gY?7M$tG$DezJA>0r5v?bih_9Hw1pRkc|H=FY&l!~vqf&9r> zTZC^G9K_nebBo{*)QK;#3ud$S@ZT!P?}5u~sNgWx5&q$VBXBoeX19S78;LvWGRqYl zg?r^P?wZGelX0J1!kzO3gv&wxoGl6Ase;p3GCb2kncao0l<`!q4?HtLnca=;u@JuVl|+Qd(}e` zei@Y61~v@gS3sG)itWIA%CQA9?qla7{3aWP@Ed~r*l2k63x3STz<*HiD8Al>Zz{4v z@E0}?Y0e7%%ErUfB6tq(JjWYp1uwEv_%DEXrx2Tn@Na^b*d%x^gEF2Tl!I5%LlO>| zoU$|x9!XHaGXcEU9hC8mVJ5;VD6>#JBf$K@69SoqOZS0(JSD*Q74eKf!V`#j2)Dy? z0+~hONdew?D%c)R3uJbKU|&2jkXe7}5pb0B7V=l7P{X)`AP98n9A&8C)oB z03ViKMfyjiT7(w~E|E4N{J69Q{wD;VlwOBtso;y!c7)eRZz6V`^cKQv1#2Y!toJ2Q z#&eE3aD&93?!E$Il%;pUjnW>lR;mYIlRf}9N&CUg(nsJH=^(gO`UJUN2W7TRIt*@? zJ_CQ3j>3OQ@QU;WJeLKpO2^=5GN+7pBVoKijFjAnup+3*C*aXRnGKY`K{yk{o+y6@ z4w9R}TjU?W!Savb5cy{?TRsEcDxU?1%D;j+_ym{ChRGMf;qvd`Qu#7Udq%#B@N&VG zGG6V)o|P5wIoT>ntQwTrDp^DLc~E9A$OgizL7BZM+Yw#^q9{q!P!YzX5&j4Sx$oI5-72&ax%hK zK#568FN7J0_vI;l5SBqa7gW*^wty1TlyrnuL0#zwj{#z>Rt6w!7j!C_@Hhlr%FXb* z1-;53_&tI?Wib3ff~}Nn_=5#oD?{NA5e!p?!5=CZuG|K{U+@|w7ybyrYn4&(w-Ics zjD|l_@H%A-{Otsd61+jd3x!#G!DwY1{2f5NXIB}I@Qt9vIw>Ux#|U;-O5y1u z*iD%Te^(ISCr~CK94i>Fl*7|qFhQ9Le-9Av09B?ToG92+xeK0TP-ZF0OoV$0_EBcR z(_1iAxd;9JdsmC6=yq4GNTu(BOotGtPnHOgBE zuM>Ppc^jUW1ve;l@UI6Y_NuZA;a3DVD(}KmE4WG71OICv-e0ZMBfJ^JTBm$~@K(WX z%6^2mD<6SxCRd4ZgaC^+EXovEP9*JE{DLaI@eK%Fpn8FL+8h z1OJbLKPzY9|4Hz)@+v8pH+5&l*1yz)Cd=L9{L%LscdS3w`%Z78$W z76lBoSY>=A!lEG@Cg`^q@I-*vUo3Wn<1J2v;{>}~+z2P)>si(qC&Ggzz<>#ICarM!2nDJ8L#PQ6SDGtV0oQFBokd22TgUZr0o2 zj}?r!=E5H**xfn`{vLvf*3s}M2qsy_z~56a#aaM=vS2T3A^g2T^p$lSm~9;o-fArY zhgwU)9P31Im~|33+*%IaW}OO-uucQ>tapK#a{B z&HJFtPFSA?o2<*>xoBO1@CCu&tT=A7-vuvQpM(DrDB-Pvs}T08FMvMvMKDNR3$|8k zz!3FiqzMHvUg`$0jruA)k)X`3Q)>}!2jZxqZbJBa!S?DFcy18vtiFzL7j-+>Recle zroIKns&9jFY8@D_?gG23?}8)LJt!v^L?5d4;28A-Fkjsd7N{SArRqVXDFZR<)K9=W z)x+Q{^)v7u^(bQR6`Z4f0ndGcbJb(;-w)zkQT+!aFco&+^k+jnk^t!PE}H5_PVNo+f*yKUDXs^N2mtEZwS7n z+7aHTI^o|B%Irhc4SuA0;rUz*LiniQ7iueb8U(*oL*PFq__Z1a{|ONDNA)B84Jfl_ z^&0SdwGF6gZ9#|D4s>eQgD$N-=)u0)+3- z3c(U>95_K650+~s;1sPCoT^O(E3`@AG_4$*u1y7JY16>j+Fjs1+Dvf1HVb?}y9azw zyAOOwn+q<`=7E*k1K?8aA++QvtrFoU1)tU)MtGU_2)JB(417jg0lctooQk7^%)pKJTU zFSL)q27F@;E0y*M*rXi>ztKJeztxU{-)UcfC$(eX8SN|Z7p)OItDOK_v~R#)weP@l zS~GZA`vJV7{Rm#weg>I-29)%(psfE2+Vu0FUB3uA^xr|Jei?M>S3$QfS+L^i3K*hW z!BAZT!*l}-*X^KRcY^bDw*~Lw2QiMi7kp3;0vGG8;9mmbx>64TpU}g=YTXa6(ysxZ z*V}+E=xxE(dOM_l5ybwhUk|>iw})p3h_fI)8vKXe5qw+k1n$(kfOUE|@Etu4+@*I1 zck2n@yLuA%o}LWu(R+b=^*&&|o(8_Jr-L8p{lI z__00|{6rrH9@1|E59_($r}`-HGkr98L>~hl)eFGS^+NCqeH_@Jj|Y$GCE%BODfpE> z5j?I>0vq*m@M|6aMx33{r-4oSUEnwROz>NM7Wkch4|r0)4{X-wg5T@&z#sGnz*G7| z;E#GG_>=xH__O{9cv^o9JfklGf6<=+&+1QtE&9{ouljQEoW24)udf6z=+A){^;O_+ z`U~Li`itNteJyxduK};x{EtJL6X{$~X^R zZ(Ia#Fn$Ny8<)Wj##J!dknmvgMneHR8dfmI(7;ZH0d_X*U>Cy)b~W5!H^U3Y8bM&3 z(F%+=Lcs1u7}&$`g9*kpV4~3mOfuSnJ&kr?vT;3_VzdW)8PQ;Gqa)bI=me%3UBEP> z8`#%~1JjM}V1|(Z_A`>e{zfu5!02TSW~q{bdxRdV754&rYVE*C%>b{{>|m7U1aHvX zU+{sQ<~TSwfJ+h^|rzHjdYerWFo9S2%dDD?)o4`Z!nQP*v`aS zHpY;USc>+tcV>2GCmzPG?yl}zneMLMuIjln6NlRBs<*pqZ*^7OdaLfey}^!!ED}W` zQ9@+LX8k+6GAIcl2m%2d0tBQ;@JEoMC;_1;A)ts@QP2Wbq`>F^SnP<{1Hn2B!1?;i{C#?$)CjA+^hG0s`w>RehLqBukiaTl=-RR-?8*R zO$$F&{8?J~Dg4ZR_Wn;7f0dFyjc>VE`2A~?{AoPNy~^)jr{quLPwq4P{!L2$4Bq2j z<@fJV{%5e8`W}A&F13E9_%%xY43;FH;rAa=^54RH+$;S4rtkbNeqZ^{@8S2mzVlUn zzvnx@m*4OG&d>1sneY7U{jU{olKZvdHA;RBzj3ed`$bBAt@z!P{93U`$*Avd#(gipA5rpW@f-Kq`#*<&xL5i83MGH8m{IcQ@E-RH zze`H~9Dd_I!|yXn{ybjdUgh^+qvX#Q|2idq9v^bA@cV};`SW;>`wYK-gp$92-?&%! z{TWLBLh*By`~|$ny~^(|QSuk?A@>=6zd^}g#ADp6{Qh}L{$lYTQt}t^C-(}!f02^E zh$p$v@cXNj{3ZOxy~^)jrQ|Oa{~aZN39oXm@cY*&`Ahhd`wYK-osz$d54l(P{d<)B z<>K#C@|W=>_bR`?M#*2spWJ8o{YRAiQ+Selh2MWd$v;)xd*yrh{q!rZ^80PCd@sMh z@s-c;`|YoM_Wsw4?<4)|_>=oCem_geuNSYr@(RCrmE-qcpyb!_E%({`-zXlC{tdj! zeHXuPQ1Tnao7DXVzU97`-``EiZ{T6>v-j`+U)W{w@5lb{qWFXS`xE^8dH#JjHwXXo z!(V&&e?FYQ)_(n?*Z;!nf9v(X^ZHv~_}g#({Wt&jH&<`{=v#mAtv~tJ-+1fC-~Kn> z{_o%Zu^;?r-uYecy!+1A-u?IA{U6?){Lo+dp+B(qAME|zy&v5Fk^LXv|J(cD-2a}y=_`NgD}VMYzvap2p1k$s^2wih@>{3BWBRMNzj^lA+41bhX7}dbvG^^E z?_UfSKfCzTi*JAW-A{kp(>I>J`}FAP+0!3?`bVGs$)|t$>2mq!mVa^iebo|e#f9m%Yf1eN(<5Pp;Q{UsEVewl+ z=<&TD!Sd)gfVV!pcTMQignsY6wS|gL{l0rY7IFvo{!qvr-uuVD)60$S{RvC^^ry;u zUk{<{d;hM5icd}M{iP6ka__&j&<}s=_TK*(Li2n7AcUUY`{Tb2I~4k@?)_uZ6k6ZA z&waZ>&+okwLU;GRKZJh&y&njnAHDZELf=;WM~|=&zW>ADL}I-E10!+&3w}Ry|I2G} z|G)AR(5L&q@-^tw{U7)_=+pgQ;rE}r{{ugN5A6I4{QmRz|JPrDKHb0hI`rxO_x~B_ z)BQim@4tBel|Ku8y8mT`=bpOS_2YtH#5BdFF_dosP8pA)uKkWxE znjgOSKlsCcpY}iY`>Eou|E3sirvFB4g#5o|`v0`E{I?f>3)_Y5^4~81mOIkX?P5#e z|25nHV_)8t{_VxpukTL(244-U|5NxM{$~CD@S)H9cf6|DZ{}6X_i;aW%AHl0e`oyr zz1*e!2>+fGU*UzxA1(fP@yEEk`W4=|{e|K`=4R?Ia<}!L@cW+<|IhgMtNi;*{QJ-O z_m}ziU-0k0hrTFv3AF#D}g_|3NzIpHJ z2ful5`R+IG{lvT9{cXQ~_04<#%ad>3`*r^P)6?Goz56u(?7w$T=Dg%7-z`q=dQYbh z4u-4YgYk4)wAQPea<-a`hO5bZb~asKPi7~>+3>nt=0&a6{&Ygo2hA#l*Xhiz=3b$h z?YW+FN*~Y4vtd;|pD)KDr*|`a?F((zkSp`uM-}%P7jXTs&w|`dVfA!l`mF<-sGz#k9)LW zd+X_w(pA|kcD9_%my^}qW;$(OO(&yOccJwz_HTyEUb#9ME-W}4txCnc*8cqV zmMIji>TWhVD5vH1a8)+4_STcB#}NjYN$mc{Atwj7^L7G=oxeLN{wH}i3&HCt<@ ztrdM-u2(GC*>t$NnlEpQFRjbv-6^5r)LODO3;Xp=V*2Iyq`aLk?+PFxMM6*hXP-f> z=F5T@lekz-rdrs8z4yyyWs5k!T`bG0D#xLkL+gAtD@V4<78it`Pzum)KLd^(Is%k$ zX112s6>Def-=ExSiI;baurE41t?_WdUing;&!1l`#z6P%db{bES&m*L(FuR8Ui-zUbkN#Z1Yf)eFdfT%e>hzSfO4SGrUZzmpHegT8J-Q7 z0705?iUM8?VORQenlFBTIUJS6g%-axem1G*%g4jzjA&_l8EnL~S;eXF zZJw@gXIX{ANjV)C`|D~ozwOMb)sQ}y#on+g!_IX$$4jjTz>|k}WUE$MyS!{~#St7X z$2&83CiXasx5KmL{Mkfjv(wv1a5U<3_GS02a_q`s80j>Ri@nKgxV#IcyNmt#SbH